-- phpMyAdmin SQL Dump
-- version 4.9.0.1
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: May 21, 2020 at 07:45 AM
-- Server version: 5.7.26
-- PHP Version: 7.3.7

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `textplosion1`
--

-- --------------------------------------------------------

--
-- Table structure for table `arc_meta`
--

CREATE TABLE `arc_meta` (
  `id` int(11) NOT NULL,
  `type` varchar(8) NOT NULL,
  `type_id` varchar(128) NOT NULL,
  `title` varchar(250) DEFAULT NULL,
  `override_title` tinyint(1) DEFAULT NULL,
  `robots` varchar(45) DEFAULT NULL,
  `image` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `arc_redirect`
--

CREATE TABLE `arc_redirect` (
  `arc_redirectID` int(11) NOT NULL,
  `originalUrl` varchar(255) DEFAULT NULL,
  `redirectUrl` varchar(255) DEFAULT NULL,
  `statusCode` int(11) NOT NULL DEFAULT '301'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `arc_twitter`
--

CREATE TABLE `arc_twitter` (
  `arc_twitterid` int(11) NOT NULL,
  `article_id` int(11) DEFAULT NULL,
  `tweet_id` varchar(100) DEFAULT NULL,
  `tweet` varchar(280) DEFAULT NULL,
  `tinyurl` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `bot_wtc`
--

CREATE TABLE `bot_wtc` (
  `id` int(11) NOT NULL,
  `item` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `position` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `destination` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `sections` text COLLATE utf8mb4_unicode_ci,
  `class` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Table structure for table `custom_fields`
--

CREATE TABLE `custom_fields` (
  `id` int(11) UNSIGNED NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `value` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `custom_fields`
--

INSERT INTO `custom_fields` (`id`, `name`, `value`) VALUES
(1, 'custom_1_set', 'default'),
(2, 'custom_1_set', 'option1'),
(3, 'custom_1_set', 'option2');

-- --------------------------------------------------------

--
-- Table structure for table `kuo_file_stats`
--

CREATE TABLE `kuo_file_stats` (
  `id` int(11) NOT NULL,
  `file_id` smallint(5) UNSIGNED NOT NULL,
  `file_name` varchar(255) NOT NULL,
  `ip` varchar(45) NOT NULL,
  `username` varchar(255) DEFAULT NULL COMMENT 'If user is logged in',
  `privs` tinyint(1) UNSIGNED DEFAULT NULL COMMENT 'If user is logged in',
  `when` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `misc` text NOT NULL COMMENT 'Contains serialized data'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `kuo_sortable`
--

CREATE TABLE `kuo_sortable` (
  `id` int(11) NOT NULL,
  `place` varchar(45) NOT NULL,
  `order` text NOT NULL,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `username` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `kuo_user_last_action`
--

CREATE TABLE `kuo_user_last_action` (
  `id` int(11) UNSIGNED NOT NULL,
  `username` varchar(255) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `action` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `kuo_zcr_smtp`
--

CREATE TABLE `kuo_zcr_smtp` (
  `id` tinyint(3) UNSIGNED NOT NULL,
  `debugging` tinyint(1) UNSIGNED NOT NULL DEFAULT '0',
  `email_host` varchar(255) DEFAULT NULL,
  `email_username` blob,
  `email_password` blob,
  `use_auth` tinyint(1) UNSIGNED DEFAULT '0',
  `ssl_tls` tinyint(1) UNSIGNED DEFAULT '0',
  `port` smallint(5) UNSIGNED DEFAULT NULL,
  `use_html` tinyint(1) UNSIGNED DEFAULT '1',
  `email_to` blob,
  `email_cc` blob,
  `email_bcc` blob,
  `subject` varchar(255) DEFAULT NULL,
  `xpriority` tinyint(1) UNSIGNED NOT NULL DEFAULT '0',
  `charset` varchar(15) NOT NULL DEFAULT 'UTF-8',
  `xmailer` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `kuo_zcr_smtp`
--

INSERT INTO `kuo_zcr_smtp` (`id`, `debugging`, `email_host`, `email_username`, `email_password`, `use_auth`, `ssl_tls`, `port`, `use_html`, `email_to`, `email_cc`, `email_bcc`, `subject`, `xpriority`, `charset`, `xmailer`) VALUES
(1, 0, NULL, NULL, NULL, 0, 1, NULL, 1, NULL, NULL, NULL, NULL, 0, 'UTF-8', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `rah_plugin_installer_def`
--

CREATE TABLE `rah_plugin_installer_def` (
  `name` varchar(64) NOT NULL DEFAULT '',
  `author` varchar(128) NOT NULL DEFAULT '',
  `author_uri` varchar(128) NOT NULL DEFAULT '',
  `version` varchar(10) NOT NULL DEFAULT '1.0',
  `description` text NOT NULL,
  `help` text NOT NULL,
  `type` int(2) NOT NULL DEFAULT '0',
  `md5_checksum` varchar(32) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8 PACK_KEYS=1;

-- --------------------------------------------------------

--
-- Table structure for table `rah_post_versions_prefs`
--

CREATE TABLE `rah_post_versions_prefs` (
  `name` varchar(255) NOT NULL,
  `value` longtext NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `rah_post_versions_prefs`
--

INSERT INTO `rah_post_versions_prefs` (`name`, `value`) VALUES
('authors', ''),
('email', 'No'),
('email_additional', ''),
('events', 'article:edit, article:create, article:publish, article:save, page:page_create, page:page_edit, page:page_save, form:form_create, form:form_edit, form:form_save, prefs:prefs_save, prefs:advanced_prefs_save, category:cat_article_create, category:cat_image_create, category:cat_file_create, category:cat_link_create, category:cat_article_save, category:cat_image_save, category:cat_file_save, category:cat_link_save, section:section_create, section:section_save, link:link_post, link:link_save, discuss:discuss_save, image:image_save, file:file_save, css:css_save'),
('exclude', ''),
('hidden', 'event,step,save'),
('ident', 'switch($event) {\n	case \"article\":\n		$grid = rah_post_versions_id();\n		$title = gps(\"Title\");\n	break;\n	case \"category\":\n		$grid = rah_post_versions_id(\"id\");\n		$title = gps(\"title\");\n	break;\n	case \"link\":\n		$grid = rah_post_versions_id(\"id\");\n		$title = gps(\"linkname\");\n	break;\n	case \"prefs\":\n		$grid = $title = ($step == \"advanced_prefs_save\") ? \"Advanced prefs\" : \"Preferences\";\n	break;\n	case \"discuss\":\n		$grid = gps(\"discussid\");\n		$title = gps(\"name\");\n	break;\n	case \"section\":\n		$grid = gps(\"name\");\n		$title = (gps(\"title\")) ? gps(\"title\") : gps(\"name\");\n	break;\n	case \"image\":\n		$grid = rah_post_versions_id(\"id\");\n		$title = (gps(\"name\")) ? gps(\"name\") : $grid;\n	break;\n	case \"file\":\n		$grid = gps(\"id\");\n		$title = gps(\"filename\");\n	break;\n	default:\n		$grid = $title = gps(\"name\");\n}'),
('posting_now', 'No'),
('statuses', ''),
('version', '0.1');

-- --------------------------------------------------------

--
-- Table structure for table `rah_tabtor`
--

CREATE TABLE `rah_tabtor` (
  `id` int(11) NOT NULL,
  `tabgroup` varchar(255) NOT NULL DEFAULT '',
  `page` varchar(255) NOT NULL DEFAULT '',
  `label` varchar(255) NOT NULL DEFAULT '',
  `position` int(2) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 PACK_KEYS=1;

-- --------------------------------------------------------

--
-- Table structure for table `smd_bio`
--

CREATE TABLE `smd_bio` (
  `user_ref` varchar(64) NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=1;

-- --------------------------------------------------------

--
-- Table structure for table `smd_bio_meta`
--

CREATE TABLE `smd_bio_meta` (
  `id` int(4) NOT NULL,
  `title` varchar(64) DEFAULT '',
  `name` varchar(64) NOT NULL DEFAULT '',
  `type` set('checkbox','date','datetime','email','image','list','month','multilist','number','radio','range','text','textarea','time','url','week','yesnoradio') NOT NULL DEFAULT 'text',
  `size` varchar(10) DEFAULT '0',
  `coltype` set('date','datetime','double','float','int','longtext','mediumint','mediumtext','smallint','text','time','timestamp','tinyint','tinytext','varbinary','varchar') NOT NULL DEFAULT 'varchar',
  `colsize` smallint(4) DEFAULT '0',
  `val` text,
  `position` varchar(16) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_featured`
--

CREATE TABLE `smd_featured` (
  `feat_id` int(8) NOT NULL DEFAULT '0',
  `label` varchar(32) DEFAULT '',
  `feat_position` varchar(16) DEFAULT '',
  `feat_title` varchar(255) DEFAULT '',
  `feat_title_html` varchar(255) DEFAULT '',
  `description` varchar(255) DEFAULT '',
  `desc_html` varchar(255) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_macro`
--

CREATE TABLE `smd_macro` (
  `macro_name` varchar(32) NOT NULL DEFAULT '',
  `description` varchar(255) DEFAULT '',
  `attributes` text,
  `definition` mediumtext,
  `code` mediumtext
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_tabber`
--

CREATE TABLE `smd_tabber` (
  `name` varchar(32) NOT NULL DEFAULT '',
  `sort_order` varchar(32) DEFAULT '',
  `area` varchar(32) DEFAULT '',
  `view_privs` varchar(32) NOT NULL DEFAULT '',
  `page` varchar(32) DEFAULT '',
  `style` varchar(32) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_tags`
--

CREATE TABLE `smd_tags` (
  `id` int(6) NOT NULL,
  `name` varchar(64) NOT NULL DEFAULT '',
  `type` varchar(64) NOT NULL DEFAULT '',
  `parent` varchar(64) NOT NULL DEFAULT '',
  `lft` int(6) NOT NULL DEFAULT '0',
  `rgt` int(6) NOT NULL DEFAULT '0',
  `title` varchar(255) NOT NULL DEFAULT '',
  `description` text,
  `desc_html` text
) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=1;

--
-- Dumping data for table `smd_tags`
--

INSERT INTO `smd_tags` (`id`, `name`, `type`, `parent`, `lft`, `rgt`, `title`, `description`, `desc_html`) VALUES
(1, 'root', 'article', '', 1, 2, 'root', 'DO NOT DELETE', NULL),
(2, 'root', 'link', '', 1, 2, 'root', 'DO NOT DELETE', NULL),
(3, 'root', 'image', '', 1, 2, 'root', 'DO NOT DELETE', NULL),
(4, 'root', 'file', '', 1, 2, 'root', 'DO NOT DELETE', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `smd_tags_cat`
--

CREATE TABLE `smd_tags_cat` (
  `tag_id` int(6) NOT NULL DEFAULT '0',
  `cat_id` int(6) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_tags_used`
--

CREATE TABLE `smd_tags_used` (
  `item_id` int(11) NOT NULL DEFAULT '0',
  `tag_id` int(6) NOT NULL DEFAULT '0',
  `type` varchar(64) NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `smd_thumbnail`
--

CREATE TABLE `smd_thumbnail` (
  `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `width` int(4) NOT NULL DEFAULT '0',
  `height` int(4) NOT NULL DEFAULT '0',
  `quality` tinyint(2) DEFAULT '75',
  `flags` tinyint(2) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Table structure for table `smd_um_groups`
--

CREATE TABLE `smd_um_groups` (
  `id` smallint(4) NOT NULL DEFAULT '0',
  `name` varchar(64) NOT NULL DEFAULT '',
  `core` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=1;

--
-- Dumping data for table `smd_um_groups`
--

INSERT INTO `smd_um_groups` (`id`, `name`, `core`) VALUES
(1, 'publisher', 1),
(2, 'managing_editor', 1),
(3, 'copy_editor', 1),
(4, 'staff_writer', 1),
(5, 'freelancer', 1),
(6, 'designer', 1),
(0, 'privs_none', 1);

-- --------------------------------------------------------

--
-- Table structure for table `smd_um_privs`
--

CREATE TABLE `smd_um_privs` (
  `area` varchar(127) NOT NULL DEFAULT '',
  `priv` smallint(6) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Dumping data for table `smd_um_privs`
--

INSERT INTO `smd_um_privs` (`area`, `priv`) VALUES
('admin', 1),
('admin', 2),
('admin', 3),
('admin', 4),
('admin', 5),
('admin', 6),
('admin.edit', 1),
('admin.edit.own', 1),
('admin.edit.own', 2),
('admin.edit.own', 3),
('admin.edit.own', 4),
('admin.edit.own', 5),
('admin.edit.own', 6),
('admin.list', 1),
('admin.list', 2),
('admin.list', 3),
('article', 1),
('article', 2),
('article', 3),
('article', 4),
('article', 5),
('article', 6),
('article.delete', 1),
('article.delete', 2),
('article.delete.own', 1),
('article.delete.own', 2),
('article.delete.own', 3),
('article.delete.own', 4),
('article.delete.own', 5),
('article.edit', 1),
('article.edit', 2),
('article.edit', 3),
('article.edit.own', 1),
('article.edit.own', 2),
('article.edit.own', 3),
('article.edit.own', 4),
('article.edit.own', 5),
('article.edit.own', 6),
('article.edit.own.published', 1),
('article.edit.own.published', 2),
('article.edit.own.published', 3),
('article.edit.own.published', 4),
('article.edit.published', 1),
('article.edit.published', 2),
('article.edit.published', 3),
('article.php', 1),
('article.php', 2),
('article.preview', 1),
('article.preview', 2),
('article.preview', 3),
('article.preview', 4),
('article.publish', 1),
('article.publish', 2),
('article.publish', 3),
('article.publish', 4),
('article.set_markup', 1),
('article.set_markup', 2),
('article.set_markup', 3),
('article.set_markup', 6),
('category', 1),
('category', 2),
('category', 3),
('css', 1),
('css', 2),
('css', 6),
('debug.backtrace', 1),
('debug.verbose', 1),
('debug.verbose', 2),
('diag', 1),
('diag', 2),
('discuss', 1),
('discuss', 2),
('discuss', 3),
('file', 1),
('file', 2),
('file', 3),
('file', 4),
('file.delete', 1),
('file.delete', 2),
('file.delete.own', 1),
('file.delete.own', 2),
('file.delete.own', 3),
('file.delete.own', 4),
('file.edit', 1),
('file.edit', 2),
('file.edit.own', 1),
('file.edit.own', 2),
('file.edit.own', 3),
('file.edit.own', 4),
('file.publish', 1),
('file.publish', 2),
('file.publish', 3),
('file.publish', 4),
('form', 1),
('form', 2),
('form', 3),
('form', 6),
('image', 1),
('image', 2),
('image', 3),
('image', 4),
('image', 5),
('image', 6),
('list', 1),
('list', 2),
('list', 3),
('list', 4),
('list', 5),
('list', 6);

-- --------------------------------------------------------

--
-- Table structure for table `spf_js`
--

CREATE TABLE `spf_js` (
  `name` varchar(255) NOT NULL DEFAULT '',
  `js` longtext NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `spf_js`
--

INSERT INTO `spf_js` (`name`, `js`) VALUES
('default', '// Your default JavaScript');

-- --------------------------------------------------------

--
-- Table structure for table `textpattern`
--

CREATE TABLE `textpattern` (
  `ID` int(11) NOT NULL,
  `Posted` datetime NOT NULL,
  `Expires` datetime DEFAULT NULL,
  `AuthorID` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `LastMod` datetime NOT NULL,
  `LastModID` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Title_html` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Body` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `Body_html` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `Excerpt` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `Excerpt_html` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `Image` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Category1` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Category2` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Annotate` int(11) NOT NULL DEFAULT '0',
  `AnnotateInvite` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `comments_count` int(11) NOT NULL DEFAULT '0',
  `Status` int(11) NOT NULL DEFAULT '4',
  `textile_body` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1',
  `textile_excerpt` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1',
  `Section` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `override_form` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Keywords` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `url_title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_1` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_2` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_3` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_4` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_5` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_6` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_7` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_8` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_9` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `custom_10` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `uid` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `feed_time` date NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `textpattern`
--

INSERT INTO `textpattern` (`ID`, `Posted`, `Expires`, `AuthorID`, `LastMod`, `LastModID`, `Title`, `Title_html`, `Body`, `Body_html`, `Excerpt`, `Excerpt_html`, `Image`, `Category1`, `Category2`, `Annotate`, `AnnotateInvite`, `comments_count`, `Status`, `textile_body`, `textile_excerpt`, `Section`, `override_form`, `Keywords`, `description`, `url_title`, `custom_1`, `custom_2`, `custom_3`, `custom_4`, `custom_5`, `custom_6`, `custom_7`, `custom_8`, `custom_9`, `custom_10`, `uid`, `feed_time`) VALUES
(1, '2019-07-23 21:58:54', NULL, 'marios', '2019-07-24 23:04:12', 'marios', 'Welcome to your site', '', 'h3. What do you want to do next?\r\n\r\n* Write a \"new article\"://localhost/site1/textpattern/index.php?event=article? Let your creativity flow!\r\n* Change this site\'s name, slogan or select a different article URL style? Check and modify your \"preferences\"://localhost/site1/textpattern/index.php?event=prefs.\r\n* Edit or delete this article? Your \"articles list\"://localhost/site1/textpattern/index.php?event=list is the place to start.\r\n* Upload \"images\"://localhost/site1/textpattern/index.php?event=image or \"files\"://localhost/site1/textpattern/index.php?event=file to accompany your articles?\r\n* Learn Textile, the markup generator included with Textpattern? You can try it in the \"Textile sandbox\":https://textpattern.com/textile-sandbox.\r\n** If you want to learn more, you can refer to an extensive \"Textile manual\":https://textpattern.com/textile-reference-manual.\r\n* Be guided through your \"Textpattern first steps\":https://textpattern.com/textpattern-first-steps by completing some tasks?\r\n* Study the \"Textpattern Semantic Model\":https://textpattern.com/textpattern-semantic-model?\r\n* Add one or more additional \"users\"://localhost/site1/textpattern/index.php?event=admin, or extend Textpattern\'s capabilities with \"plugins\"://localhost/site1/textpattern/index.php?event=plugin from the \"Textpattern plugin directory\":https://textpattern.com/plugins?\r\n* Dive in and learn by doing? Please note:\r\n** When you write an article you assign it to a \"section\"://localhost/site1/textpattern/index.php?event=section of your site.\r\n** Sections use a \"page\"://localhost/site1/textpattern/index.php?event=page template and a \"style\"://localhost/site1/textpattern/index.php?event=css to define how site content appears in a browser.\r\n** Page templates typically use HTML(HyperText Markup Language) and \"Textpattern tags\":https://docs.textpattern.com/tags/ (like this: @<txp:article />@) to build the output code.\r\n** Some Textpattern tags use \"forms\"://localhost/site1/textpattern/index.php?event=form, reusable building blocks that provide extensive control and customization over your site construction.\r\n** Pages, styles and forms can be packaged into \"themes\"://localhost/site1/textpattern/index.php?event=skin and assigned to one or more sections.\r\n\r\nTextpattern tags, their attributes and values are explained within the \"Textpattern User Documentation\":https://docs.textpattern.com/, where you will also find valuable examples, advice and tutorials.\r\n\r\nThere\'s also a group of friendly, helpful Textpattern users and administrators at the \"Textpattern support forum\":https://forum.textpattern.com/.\r\n\r\nAdditional language translations and corrections are welcomed. Please visit \"Textpattern language translations\":https://textpattern.com/languages for further details.\r\n\r\nThis is an <txp:permlink id=\"1\">example article</txp:permlink> included with Textpattern to demonstrate some of the first steps you can undertake. An example comment is associated with this article. The article and comment can be safely deleted using the \"articles\"://localhost/site1/textpattern/index.php?event=list and \"comments\"://localhost/site1/textpattern/index.php?event=discuss lists.', '<h3>What do you want to do next?</h3>\n\n<ul>\n	<li>Write a <a href=\"//localhost/site1/textpattern/index.php?event=article\">new article</a>? Let your creativity flow!</li>\n	<li>Change this site&#8217;s name, slogan or select a different article <span class=\"caps\">URL</span> style? Check and modify your <a href=\"//localhost/site1/textpattern/index.php?event=prefs\">preferences</a>.</li>\n	<li>Edit or delete this article? Your <a href=\"//localhost/site1/textpattern/index.php?event=list\">articles list</a> is the place to start.</li>\n	<li>Upload <a href=\"//localhost/site1/textpattern/index.php?event=image\">images</a> or <a href=\"//localhost/site1/textpattern/index.php?event=file\">files</a> to accompany your articles?</li>\n	<li>Learn Textile, the markup generator included with Textpattern? You can try it in the <a href=\"https://textpattern.com/textile-sandbox\">Textile sandbox</a>.\n	<ul>\n		<li>If you want to learn more, you can refer to an extensive <a href=\"https://textpattern.com/textile-reference-manual\">Textile manual</a>.</li>\n	</ul></li>\n	<li>Be guided through your <a href=\"https://textpattern.com/textpattern-first-steps\">Textpattern first steps</a> by completing some tasks?</li>\n	<li>Study the <a href=\"https://textpattern.com/textpattern-semantic-model\">Textpattern Semantic Model</a>?</li>\n	<li>Add one or more additional <a href=\"//localhost/site1/textpattern/index.php?event=admin\">users</a>, or extend Textpattern&#8217;s capabilities with <a href=\"//localhost/site1/textpattern/index.php?event=plugin\">plugins</a> from the <a href=\"https://textpattern.com/plugins\">Textpattern plugin directory</a>?</li>\n	<li>Dive in and learn by doing? Please note:\n	<ul>\n		<li>When you write an article you assign it to a <a href=\"//localhost/site1/textpattern/index.php?event=section\">section</a> of your site.</li>\n		<li>Sections use a <a href=\"//localhost/site1/textpattern/index.php?event=page\">page</a> template and a <a href=\"//localhost/site1/textpattern/index.php?event=css\">style</a> to define how site content appears in a browser.</li>\n		<li>Page templates typically use <abbr title=\"HyperText Markup Language\"><span class=\"caps\">HTML</span></abbr> and <a href=\"https://docs.textpattern.com/tags/\">Textpattern tags</a> (like this: <code>&lt;txp:article /&gt;</code>) to build the output code.</li>\n		<li>Some Textpattern tags use <a href=\"//localhost/site1/textpattern/index.php?event=form\">forms</a>, reusable building blocks that provide extensive control and customization over your site construction.</li>\n		<li>Pages, styles and forms can be packaged into <a href=\"//localhost/site1/textpattern/index.php?event=skin\">themes</a> and assigned to one or more sections.</li>\n	</ul></li>\n	</ul>\n\n<p>Textpattern tags, their attributes and values are explained within the <a href=\"https://docs.textpattern.com/\">Textpattern User Documentation</a>, where you will also find valuable examples, advice and tutorials.</p>\n\n<p>There&#8217;s also a group of friendly, helpful Textpattern users and administrators at the <a href=\"https://forum.textpattern.com/\">Textpattern support forum</a>.</p>\n\n<p>Additional language translations and corrections are welcomed. Please visit <a href=\"https://textpattern.com/languages\">Textpattern language translations</a> for further details.</p>\n\n<p>This is an <txp:permlink id=\"1\">example article</txp:permlink> included with Textpattern to demonstrate some of the first steps you can undertake. An example comment is associated with this article. The article and comment can be safely deleted using the <a href=\"//localhost/site1/textpattern/index.php?event=list\">articles</a> and <a href=\"//localhost/site1/textpattern/index.php?event=discuss\">comments</a> lists.</p>', 'This is a Textpattern article excerpt. Article excerpts are optional and can be used effectively to summarize an article\'s content, for example with Textpattern\'s built-in article syndication.', '<p>This is a Textpattern article excerpt. Article excerpts are optional and can be used effectively to summarize an article&#8217;s content, for example with Textpattern&#8217;s built-in article syndication.</p>', '1', 'hope-for-the-future', 'meaningful-labor', 1, 'Comment', 1, 4, '1', '1', 'articles', '', '', '', 'welcome-to-your-site', '', '', '', '', '', '', '', '', '', '', '5d4b577464baaca6483f31a74cbd3c4e', '2019-07-23'),
(2, '2019-07-25 07:23:51', NULL, 'marios', '2019-07-25 07:30:18', 'marios', 'Textpattern Solution Book', '', 'In modern web development, there is a big focus on using frameworks and other reusable code to build up web sites, rather than writing everything from scratch. Textpattern is one such framework, allowing it\'s users to build up standards-compliant professional web sites using PHP, MySQL, and CSS, with only a basic knowledge of the underlying technology. It\'s also open source, so free to use. It has become very popular, with tens of thousands of downloads to date, and is used on some high profile sites, such as that of Senator Ted Kennedy (tedkennedy.com) We are proud to present the first book dedicated to Textpattern development, written by 5 experts. It takes the reader right from the basics of the subject such as installation, right up to advanced subjects like writing plugins. It also includes case studies and reference sections.\r\n\r\nSynopsis\r\nTextpattern is a powerful, PHP-based content management system that allows you to build pretty much any kind of data-driven website quickly and easily. It is very popular among designers and developers alike, and has an active community of users. Sound good? Well, you\'re in luck the book you\'re holding now shows you how to use every aspect of Textpattern to a professional standard. Textpattern (and this book) appeals to two main groups of people: Firstly, the web designer who has mastered the fundamentals of XHTML and CSS and wants to create sites that have fresh and dynamically changing information, but is not familiar with server-side coding and databases. Never fear Textpattern enables you to integrate your XHTML knowledge, inserting dynamic aspects into your already solid site design. Secondly, PHP developers who are looking for a content management system upon which to build additional functionality and want a hand with the design aspects of their site. Again, Textpattern fills in those holes. The book leaves no stone unturned. It starts by introducing you to Textpattern and guiding you through its very simple installation process.After the core features of the administrative interface are explored, the book dives into building sites with TXP, using CSS, articles, forms, pages, and more to create fully customized templates.\r\n\r\nDevelopment follows next, looking at installing plugins and building your own custom plugins to further enhance your Textpattern-based site. The book concludes with several case studies to provide ideas and inspiration, including a weblog, an e-commerce site, and an online review directory. In this book you will: * Learn to develop websites quickly using Textpattern\'s intuitive templating language. * Learn to use Textpattern to build e-commerce websites, including customer feedback and reviews. * Learn to use Textpattern and web standards to create blogs and community sites with dynamically driven content. * Learn how to integrate your own style of coding and design with a content management system that catalyses, rather than hinders, your creativity. * Learn how to use PHP to create Textpattern plugins, adding custom functionality and features to uniquely suit the needs of your website', '<p>In modern web development, there is a big focus on using frameworks and other reusable code to build up web sites, rather than writing everything from scratch. Textpattern is one such framework, allowing it&#8217;s users to build up standards-compliant professional web sites using <span class=\"caps\">PHP</span>, MySQL, and <span class=\"caps\">CSS</span>, with only a basic knowledge of the underlying technology. It&#8217;s also open source, so free to use. It has become very popular, with tens of thousands of downloads to date, and is used on some high profile sites, such as that of Senator Ted Kennedy (tedkennedy.com) We are proud to present the first book dedicated to Textpattern development, written by 5 experts. It takes the reader right from the basics of the subject such as installation, right up to advanced subjects like writing plugins. It also includes case studies and reference sections.</p>\n\n<p>Synopsis<br />\nTextpattern is a powerful, <span class=\"caps\">PHP</span>-based content management system that allows you to build pretty much any kind of data-driven website quickly and easily. It is very popular among designers and developers alike, and has an active community of users. Sound good? Well, you&#8217;re in luck the book you&#8217;re holding now shows you how to use every aspect of Textpattern to a professional standard. Textpattern (and this book) appeals to two main groups of people: Firstly, the web designer who has mastered the fundamentals of <span class=\"caps\">XHTML</span> and <span class=\"caps\">CSS</span> and wants to create sites that have fresh and dynamically changing information, but is not familiar with server-side coding and databases. Never fear Textpattern enables you to integrate your <span class=\"caps\">XHTML</span> knowledge, inserting dynamic aspects into your already solid site design. Secondly, <span class=\"caps\">PHP</span> developers who are looking for a content management system upon which to build additional functionality and want a hand with the design aspects of their site. Again, Textpattern fills in those holes. The book leaves no stone unturned. It starts by introducing you to Textpattern and guiding you through its very simple installation process.After the core features of the administrative interface are explored, the book dives into building sites with <span class=\"caps\">TXP</span>, using <span class=\"caps\">CSS</span>, articles, forms, pages, and more to create fully customized templates.</p>\n\n<p>Development follows next, looking at installing plugins and building your own custom plugins to further enhance your Textpattern-based site. The book concludes with several case studies to provide ideas and inspiration, including a weblog, an e-commerce site, and an online review directory. In this book you will: * Learn to develop websites quickly using Textpattern&#8217;s intuitive templating language. * Learn to use Textpattern to build e-commerce websites, including customer feedback and reviews. * Learn to use Textpattern and web standards to create blogs and community sites with dynamically driven content. * Learn how to integrate your own style of coding and design with a content management system that catalyses, rather than hinders, your creativity. * Learn how to use <span class=\"caps\">PHP</span> to create Textpattern plugins, adding custom functionality and features to uniquely suit the needs of your website</p>', 'Development follows next, looking at installing plugins and building your own custom plugins to further enhance your Textpattern-based site. The book concludes with several case studies to provide ideas and inspiration, including a weblog, an e-commerce site, and an online review directory. In this book you will: * Learn to develop websites quickly using Textpattern\'s intuitive templating language.', '<p>Development follows next, looking at installing plugins and building your own custom plugins to further enhance your Textpattern-based site. The book concludes with several case studies to provide ideas and inspiration, including a weblog, an e-commerce site, and an online review directory. In this book you will: * Learn to develop websites quickly using Textpattern&#8217;s intuitive templating language.</p>', '3', 'hope-for-the-future', '', 1, 'Comment', 1, 4, '1', '1', 'articles', '', 'TXP Solutions Potts', 'Kevin Potts', 'Top Solutios', '', '', '', '', '', '', '', '', '', '', '5c176c881765d2e483e87869bbee8850', '2019-07-25'),
(3, '2019-07-25 13:11:29', NULL, 'marios', '2019-07-25 13:13:32', 'marios', 'Dean Allen, R.I.P.', '', 'Dean Allen, the father of Textpattern, has passed away.\r\n\r\nRaise a glass\r\nPersonally, I never met Dean nor had any interaction with him via email or on the forums. When I came upon Textpattern in 2005, Dean was exiting stage left.\r\n\r\nThis weekend, I bought a bottle of Jameson and polished it off in Dean’s name.\r\n\r\nHello world!\r\nCreativity cannot be cubicled, it either flows or it doesn’t.\r\n\r\nIn 2001, Dean went on a coding rampage, a 24/7 romp in PHP/MySQL land because he felt an itch. When he finally decided to let it loose on the web, the result was Textpattern and Textile.\r\n\r\nTextpattern powers all of my dynamic sites, and will till the end of my time.\r\n\r\nGoodbye\r\nThank you Dean, rest in peace.', '<p>Dean Allen, the father of Textpattern, has passed away.</p>\n\n<p>Raise a glass<br />\nPersonally, I never met Dean nor had any interaction with him via email or on the forums. When I came upon Textpattern in 2005, Dean was exiting stage left.</p>\n\n<p>This weekend, I bought a bottle of Jameson and polished it off in Dean’s name.</p>\n\n<p>Hello world!<br />\nCreativity cannot be cubicled, it either flows or it doesn’t.</p>\n\n<p>In 2001, Dean went on a coding rampage, a 24/7 romp in <span class=\"caps\">PHP</span>/MySQL land because he felt an itch. When he finally decided to let it loose on the web, the result was Textpattern and Textile.</p>\n\n<p>Textpattern powers all of my dynamic sites, and will till the end of my time.</p>\n\n<p>Goodbye<br />\nThank you Dean, rest in peace.</p>', 'Dean Allen, the father of Textpattern, has passed away.\r\n\r\nRaise a glass\r\nPersonally, I never met Dean nor had any interaction with him via email or on the forums. When I came upon Textpattern in 2005, Dean was exiting stage left.', '<p>Dean Allen, the father of Textpattern, has passed away.</p>\n\n<p>Raise a glass<br />\nPersonally, I never met Dean nor had any interaction with him via email or on the forums. When I came upon Textpattern in 2005, Dean was exiting stage left.</p>', '4', 'hope-for-the-future', '', 1, 'Comment', 0, 4, '1', '1', 'articles', '', 'DeanAllen TXP', 'Dean Allen has passed away', 'Dean Allen has passed away', '', '', '', '', '', '', '', '', '', '', '5d82dfe907bcadd13a579ec95e24f317', '2019-07-25'),
(4, '2019-07-25 13:21:57', NULL, 'marios', '2019-07-25 13:27:44', 'marios', 'Textpattern versus Wordpress', '', 'Its as fast as a Lamborghini\r\nTextpattern 4.6.0\r\nbert@jessie:~$ ab -l -n 1000 -c 10 http://localhost/projects/textpattern/\r\n\r\nDocument Path: /projects/textpattern/\r\nDocument Length: Variable\r\n\r\nConcurrency Level: 10\r\nTime taken for tests: 7.798 seconds\r\nComplete requests: 1000\r\nFailed requests: 0\r\nTotal transferred: 12038009 bytes\r\nHTML transferred: 11868009 bytes\r\nRequests per second: 128.23 (mean)\r\nTime per request: 77.983 [ms] (mean)\r\nTime per request: 7.798 [ms] (mean, across all concurrent requests)\r\nTransfer rate: 1507.49 [Kbytes/sec] received\r\n\r\nConnection Times (ms) min mean[+/-sd] median max\r\nConnect: 0 0 3.2 0 40\r\nProcessing: 20 77 22.0 76 278\r\nWaiting: 1 74 22.2 74 277\r\nTotal: 20 78 21.9 76 278\r\n\r\nPercentage of the requests served within a certain time (ms) 50% 76 66% 81 75% 85 80% 88 90% 96 95% 104 98% 119 99% 162 100% 278 (longest request)\r\n\r\nWordPress 4.6.1\r\nbert@jessie:~$ ab -l -n 1000 -c 10 http://localhost/projects/wordpress/\r\n\r\nDocument Path: /projects/wordpress/\r\nDocument Length: Variable\r\n\r\nConcurrency Level: 10\r\nTime taken for tests: 28.164 seconds\r\nComplete requests: 1000\r\nFailed requests: 0\r\nTotal transferred: 11536000 bytes\r\nHTML transferred: 11289000 bytes\r\nRequests per second: 35.51 (mean)\r\nTime per request: 281.637 [ms] (mean)\r\nTime per request: 28.164 [ms] (mean, across all concurrent requests)\r\nTransfer rate: 400.01 [Kbytes/sec] received\r\n\r\nConnection Times (ms) min mean[+/-sd] median max\r\nConnect: 0 0 2.7 0 45\r\nProcessing: 130 281 26.2 279 428\r\nWaiting: 109 190 29.2 188 338\r\nTotal: 130 281 26.3 279 428\r\n\r\nPercentage of the requests served within a certain time (ms) 50% 279 66% 285 75% 289 80% 292 90% 300 95% 327 98% 370 99% 388 100% 428 (longest request)\r\n\r\nJessie Specs\r\nVirtualBox VM: 4GB DDR4 RAM, 1 Core i7 @ 2.5GHz, 80GB vdi on an SSD.\r\n\r\nLAMP: Debian 8.6 “Jessie” 64-bit, Apache 2.4.10, MySQL 5.5.52, PHP 5.6.24.\r\n\r\nClean installs of Textpattern 4.6.0 and WordPress 4.6.1.\r\n\r\nZend OPcache is enabled.\r\n\r\nAll right, sparky!\r\n\r\n', '<p>Its as fast as a Lamborghini<br />\nTextpattern 4.6.0<br />\nbert@jessie:~$ ab -l -n 1000 -c 10 http://localhost/projects/textpattern/</p>\n\n<p>Document Path: /projects/textpattern/<br />\nDocument Length: Variable</p>\n\n<p>Concurrency Level: 10<br />\nTime taken for tests: 7.798 seconds<br />\nComplete requests: 1000<br />\nFailed requests: 0<br />\nTotal transferred: 12038009 bytes<br />\n<span class=\"caps\">HTML</span> transferred: 11868009 bytes<br />\nRequests per second: 128.23 (mean)<br />\nTime per request: 77.983 [ms] (mean)<br />\nTime per request: 7.798 [ms] (mean, across all concurrent requests)<br />\nTransfer rate: 1507.49 [Kbytes/sec] received</p>\n\n<p>Connection Times (ms) min mean[+/-sd] median max<br />\nConnect: 0 0 3.2 0 40<br />\nProcessing: 20 77 22.0 76 278<br />\nWaiting: 1 74 22.2 74 277<br />\nTotal: 20 78 21.9 76 278</p>\n\n<p>Percentage of the requests served within a certain time (ms) 50% 76 66% 81 75% 85 80% 88 90% 96 95% 104 98% 119 99% 162 100% 278 (longest request)</p>\n\n<p>WordPress 4.6.1<br />\nbert@jessie:~$ ab -l -n 1000 -c 10 http://localhost/projects/wordpress/</p>\n\n<p>Document Path: /projects/wordpress/<br />\nDocument Length: Variable</p>\n\n<p>Concurrency Level: 10<br />\nTime taken for tests: 28.164 seconds<br />\nComplete requests: 1000<br />\nFailed requests: 0<br />\nTotal transferred: 11536000 bytes<br />\n<span class=\"caps\">HTML</span> transferred: 11289000 bytes<br />\nRequests per second: 35.51 (mean)<br />\nTime per request: 281.637 [ms] (mean)<br />\nTime per request: 28.164 [ms] (mean, across all concurrent requests)<br />\nTransfer rate: 400.01 [Kbytes/sec] received</p>\n\n<p>Connection Times (ms) min mean[+/-sd] median max<br />\nConnect: 0 0 2.7 0 45<br />\nProcessing: 130 281 26.2 279 428<br />\nWaiting: 109 190 29.2 188 338<br />\nTotal: 130 281 26.3 279 428</p>\n\n<p>Percentage of the requests served within a certain time (ms) 50% 279 66% 285 75% 289 80% 292 90% 300 95% 327 98% 370 99% 388 100% 428 (longest request)</p>\n\n<p>Jessie Specs<br />\nVirtualBox VM: 4GB DDR4 <span class=\"caps\">RAM</span>, 1 Core i7 @ 2.5GHz, 80GB vdi on an <span class=\"caps\">SSD</span>.</p>\n\n<p><span class=\"caps\">LAMP</span>: Debian 8.6 “Jessie” 64-bit, Apache 2.4.10, MySQL 5.5.52, <span class=\"caps\">PHP</span> 5.6.24.</p>\n\n<p>Clean installs of Textpattern 4.6.0 and WordPress 4.6.1.</p>\n\n<p>Zend OPcache is enabled.</p>\n\n<p>All right, sparky!</p>', 'Its as fast as a Lamborghini\r\n\r\n\r\nConcurrency Level: 10\r\nTime taken for tests: 7.798 seconds\r\nComplete requests: 1000\r\nFailed requests: 0\r\nTotal transferred: 12038009 bytes\r\nHTML transferred: 11868009 bytes\r\nRequests per second: 128.23 (mean)\r\nTime per request: 77.983 [ms] (mean)\r\nTime per request: 7.798 [ms] (mean, across all concurrent requests)\r\nTransfer rate: 1507.49 [Kbytes/sec] received', '<p>Its as fast as a Lamborghini</p>\n\n\n<p>Concurrency Level: 10<br />\nTime taken for tests: 7.798 seconds<br />\nComplete requests: 1000<br />\nFailed requests: 0<br />\nTotal transferred: 12038009 bytes<br />\n<span class=\"caps\">HTML</span> transferred: 11868009 bytes<br />\nRequests per second: 128.23 (mean)<br />\nTime per request: 77.983 [ms] (mean)<br />\nTime per request: 7.798 [ms] (mean, across all concurrent requests)<br />\nTransfer rate: 1507.49 [Kbytes/sec] received</p>', '5', 'hope-for-the-future', '', 1, 'Comment', 0, 4, '1', '1', 'articles', '', 'Wordpress TXP', 'Fast like a Lamborghini', 'Top versus Wordpress', '', '', '', '', '', '', '', '', '', '', '7e31eda2bb106553cfaa06c0df9f62af', '2019-07-25'),
(5, '2019-07-25 13:37:28', NULL, 'marios', '2019-07-25 13:49:24', 'marios', 'Ben Goertzel Singularitynet', '', 'In June, we introduced the seven “SingularityNET Supervisory Council Nominees”. We are happy to announce the results of the election after tallying the votes.\r\nOur Method\r\nIn order to correctly calculate the results of our election with a high degree of accuracy, we used methods that are currently standard within global electoral systems. It was necessary for our election to achieve proportional representation within a ranked voting system of a multi-seat organization. It is for these reasons that we used the method of Single Transferable Vote (STV).\r\nWithin an STV system candidates need to meet a quota of votes in order to be elected. This quota is based upon the number of votes compared to the number of vacancies within the results. As previously mentioned in SingularityNET Supervisory Council Nominees, each vote is weighted according to the amount of AGI held by a voter, and any candidate who ends with more votes than the specified quota is elected. If no one was to receive enough votes to achieve quota then simply put, the least popular candidates would not be elected.\r\nThe Results\r\nDisplayed below are the results of our top three Supervisory Council nominees. Following the community vote our first, second, and third place candidates are as follows David Orban, Penny Wong, and Matthew Newman.', '<p>In June, we introduced the seven “SingularityNET Supervisory Council Nominees”. We are happy to announce the results of the election after tallying the votes.<br />\nOur Method<br />\nIn order to correctly calculate the results of our election with a high degree of accuracy, we used methods that are currently standard within global electoral systems. It was necessary for our election to achieve proportional representation within a ranked voting system of a multi-seat organization. It is for these reasons that we used the method of Single Transferable Vote (<span class=\"caps\">STV</span>).<br />\nWithin an <span class=\"caps\">STV</span> system candidates need to meet a quota of votes in order to be elected. This quota is based upon the number of votes compared to the number of vacancies within the results. As previously mentioned in SingularityNET Supervisory Council Nominees, each vote is weighted according to the amount of <span class=\"caps\">AGI</span> held by a voter, and any candidate who ends with more votes than the specified quota is elected. If no one was to receive enough votes to achieve quota then simply put, the least popular candidates would not be elected.<br />\nThe Results<br />\nDisplayed below are the results of our top three Supervisory Council nominees. Following the community vote our first, second, and third place candidates are as follows David Orban, Penny Wong, and Matthew Newman.</p>', 'In June, we introduced the seven “SingularityNET Supervisory Council Nominees”. We are happy to announce the results of the election after tallying the votes.', '<p>In June, we introduced the seven “SingularityNET Supervisory Council Nominees”. We are happy to announce the results of the election after tallying the votes.</p>', '7', 'artifcial-intelligence', '', 1, 'Comment', 0, 4, '1', '1', 'agi', '', 'Ben Goertzel Singularitynet AI AGI', 'AGI Singularitynet', 'Ben Goertzel', '', '', '', '', '', '', '', '', '', '', '2eb1f3426923482a5598fef37db582a8', '2019-07-25'),
(6, '2019-07-25 13:57:10', NULL, 'marios', '2019-07-25 14:00:24', 'marios', 'Hosting WordPress', '', 'Hosting WordPress\r\nMonday October 24, 2016\r\n\r\nWhy hosting WordPress sucks.\r\n\r\nManaged WordPress\r\nLooks like if you want to run a WordPress site in the future, it’s going to cost you more money. On top of the hosting costs, you’re gonna get hit with a managed charge. Managed means that the hosting provider will hold your WordPress hand to make sure the software and it’s plugins are kept up to date.\r\n\r\nWordPress the Virus\r\nMany years ago I dubbed WordPress a Virus. Now it seems that my predictions are coming true. The amount of script kiddie traffic, directed at WordPress sites, that a hosting provider has to put up with is getting down right annoying.\r\n\r\nIf I were a hosting provider, I’d refuse entry to WordPress on my servers, then I’d block any and all “wp-login” and related traffic at the firewall.\r\n\r\nScanning for Viruses\r\nShared hosting providers have resorted to security scans, probing the innards of client sites to see if a vulnerable WordPress plugin is installed. DreamHost used to send me an email every week, informing me that certain cross-references at PHPCrossRef contained vulnerable code. It wasn’t until I moved to a VPS, from a shared account, that the emails stopped.\r\n\r\n25% of the web\r\nWordPress claims to run 25% of the websites worldwide. That’s 25% of the web which is vulnerable, 25% of the web that generates unneeded traffic, 25% of the web that slows down the other 75%.\r\n\r\nOut of that 25%, I would say that between 50 to 60% of the WP sites installed should of just been plain HTML sites. You’ve got a ton of single page WordPress sites that serve no dynamic purpose other than to infect the web.\r\n\r\nOnce upon a time you dressed so fine\r\nYou threw the bums a dime in your prime, didn’t you?', '<p>Hosting WordPress<br />\nMonday October 24, 2016</p>\n\n<p>Why hosting WordPress sucks.</p>\n\n<p>Managed WordPress<br />\nLooks like if you want to run a WordPress site in the future, it’s going to cost you more money. On top of the hosting costs, you’re gonna get hit with a managed charge. Managed means that the hosting provider will hold your WordPress hand to make sure the software and it’s plugins are kept up to date.</p>\n\n<p>WordPress the Virus<br />\nMany years ago I dubbed WordPress a Virus. Now it seems that my predictions are coming true. The amount of script kiddie traffic, directed at WordPress sites, that a hosting provider has to put up with is getting down right annoying.</p>\n\n<p>If I were a hosting provider, I’d refuse entry to WordPress on my servers, then I’d block any and all “wp-login” and related traffic at the firewall.</p>\n\n<p>Scanning for Viruses<br />\nShared hosting providers have resorted to security scans, probing the innards of client sites to see if a vulnerable WordPress plugin is installed. DreamHost used to send me an email every week, informing me that certain cross-references at PHPCrossRef contained vulnerable code. It wasn’t until I moved to a <span class=\"caps\">VPS</span>, from a shared account, that the emails stopped.</p>\n\n<p>25% of the web<br />\nWordPress claims to run 25% of the websites worldwide. That’s 25% of the web which is vulnerable, 25% of the web that generates unneeded traffic, 25% of the web that slows down the other 75%.</p>\n\n<p>Out of that 25%, I would say that between 50 to 60% of the WP sites installed should of just been plain <span class=\"caps\">HTML</span> sites. You’ve got a ton of single page WordPress sites that serve no dynamic purpose other than to infect the web.</p>\n\n<p>Once upon a time you dressed so fine<br />\nYou threw the bums a dime in your prime, didn’t you?</p>', 'Hosting WordPress\r\n\r\nManaged WordPress\r\nLooks like if you want to run a WordPress site in the future, it’s going to cost you more money. On top of the hosting costs, you’re gonna get hit with a managed charge. Managed means that the hosting provider will hold your WordPress hand to make sure the software and it’s plugins are kept up to date.\r\n', '<p>Hosting WordPress</p>\n\n<p>Managed WordPress<br />\nLooks like if you want to run a WordPress site in the future, it’s going to cost you more money. On top of the hosting costs, you’re gonna get hit with a managed charge. Managed means that the hosting provider will hold your WordPress hand to make sure the software and it’s plugins are kept up to date.</p>', '8', 'meaningful-labor', '', 1, 'Comment', 0, 4, '1', '1', 'articles', '', 'Wordpress', 'Wordpress', 'Wordpress', '', '', '', '', '', '', '', '', '', '', '133aa9b0803dc616583854b52960c007', '2019-07-25');

-- --------------------------------------------------------

--
-- Table structure for table `txp_category`
--

CREATE TABLE `txp_category` (
  `id` int(11) NOT NULL,
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `type` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `parent` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `lft` int(11) NOT NULL DEFAULT '0',
  `rgt` int(11) NOT NULL DEFAULT '0',
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `categorysort` tinyint(4) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_category`
--

INSERT INTO `txp_category` (`id`, `name`, `type`, `parent`, `lft`, `rgt`, `title`, `description`, `categorysort`) VALUES
(1, 'root', 'article', '', 1, 10, 'root', '', NULL),
(2, 'root', 'link', '', 1, 14, 'root', '', NULL),
(3, 'root', 'image', '', 1, 2, 'root', '', NULL),
(4, 'root', 'file', '', 1, 2, 'root', '', NULL),
(5, 'everything-is-going-to-be-alright', 'article', 'root', 4, 5, 'Everything is going to be alright', '', NULL),
(6, 'hope-for-the-future', 'article', 'root', 6, 7, 'Hope for the future', '', NULL),
(7, 'meaningful-labor', 'article', 'root', 8, 9, 'Meaningful labor', '', NULL),
(8, 'textpattern', 'link', 'root', 12, 13, 'Textpattern', '', NULL),
(9, 'artifcial-intelligence', 'link', 'root', 2, 3, 'Artifcial Intelligence', '', NULL),
(10, 'artifcial-intelligence', 'article', 'root', 2, 3, 'Artifcial Intelligence', '', NULL),
(11, 'mac-os', 'link', 'root', 8, 9, 'Mac OS', '', NULL),
(12, 'design', 'link', 'root', 4, 5, 'Design', '', NULL),
(13, 'programming', 'link', 'root', 10, 11, 'Programming', '', NULL),
(14, 'google', 'link', 'root', 6, 7, 'google', '', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_css`
--

CREATE TABLE `txp_css` (
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `css` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `skin` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'default',
  `lastmod` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_css`
--

INSERT INTO `txp_css` (`name`, `css`, `skin`, `lastmod`) VALUES
('default', '/* ==========================================================================\n   Styling and layout for all media\n   ========================================================================== */\n/* Reset\n   ========================================================================== */\n/**\n * Add the correct display in Edge, IE 11, and Firefox.\n */\ndetails,\nmain {\n  display: block;\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\nprogress {\n  vertical-align: baseline;\n}\n\n/**\n * Add the correct display in all browsers.\n */\nsummary {\n  display: list-item;\n}\n\n/**\n * Add the correct display in IE 11.\n */\ntemplate {\n  display: none;\n}\n\n/**\n * Remove tap delay in modern browsers.\n */\na,\ninput,\nbutton {\n  touch-action: manipulation;\n}\n\n/* Clearfix\n   ========================================================================== */\nheader::after,\nfooter::after,\nnav ul::after,\nnav ol::after,\n.container::after,\n.paginator::after {\n  content: \"\";\n  display: table;\n  clear: both;\n}\n\n/* ==========================================================================\n   Styling and layout for screen media (mobile first)\n   ========================================================================== */\n@media screen {\n  /* Layout\n     ========================================================================== */\n  /**\n   * 1. Prevent adjustments of font size after orientation changes in\n   *    IE on Windows Phone and in iOS.\n   */\n  html {\n    /* 1 */\n    -webkit-text-size-adjust: 100%;\n        -ms-text-size-adjust: 100%;\n            text-size-adjust: 100%;\n  }\n\n  /**\n   * 1. Set `body` to `relative` to allow positioning of absolute elements.\n   * 2. Remove default margin.\n   */\n  body {\n    /* 1 */\n    position: relative;\n    /* 2 */\n    margin: 0;\n    background: #f7f7f7;\n  }\n\n  /**\n   * Outer wrapper for main layouts.\n   *\n   * Example HTML:\n   *\n   * <div class=\"wrapper\">\n   *     <div class=\"container\">\n   *         Content\n   *     </div>\n   * </div>\n   */\n  .wrapper {\n    padding: 1px 0;\n    border-bottom: 1px solid #cccccc;\n    background: #ffffff;\n  }\n\n  /**\n   * Wrapper for layouts, and for site header/footer.\n   *\n   * Example HTML:\n   *\n   * <div class=\"wrapper\">\n   *     <div class=\"container\">\n   *         Content\n   *     </div>\n   * </div>\n   */\n  .site-header,\n.site-footer,\n.container {\n    width: 88%;\n    max-width: 54em;\n    margin: 0 auto;\n  }\n\n  /**\n   * Additional styling for child content within site header.\n   */\n  .site-header {\n    padding: 1em 0;\n  }\n  .site-header h2 {\n    margin: 0;\n  }\n  .site-header h3 {\n    margin: 0.5em 0 0;\n    font-family: \"PT Serif\", Georgia, serif;\n    font-size: 1.25rem;\n    font-style: italic;\n    font-weight: normal;\n    line-height: 1.4;\n  }\n\n  /**\n   * Additional styling for child content within site footer.\n   */\n  .site-footer {\n    padding: 0.5em 0;\n  }\n\n  /**\n   * Styling for articles.\n   *\n   * 1. Prevent really, really long words in article from breaking layout.\n   */\n  .article {\n    margin-bottom: 2em;\n    /* 1 */\n    word-wrap: break-word;\n  }\n\n  /**\n   * Styling for complementary content.\n   *\n   * Initially the sidebar appears under main content, it is then repositioned\n   * with media queries at 2nd breakpoint.\n   *\n   * 1. Prevent really, really long words in article from breaking layout.\n   */\n  .complementary-content {\n    margin: 2em -6% 0;\n    padding: 0 6%;\n    border-top: 2px dashed #cccccc;\n    /* 1 */\n    word-wrap: break-word;\n  }\n\n  /* Navigation\n     ========================================================================== */\n  /**\n   * Style mobile first version of the navigation menu. Desktop version will\n   * override some rules with extra styling at 1st breakpoint.\n   *\n   * Example HTML:\n   *\n   * <nav class=\"site-navigation\">\n   *     <ul>\n   *         <li class=\"active\"><a></li>\n   *         <li><a></li>\n   *         <li><a></li>\n   *     </ul>\n   * </nav>\n   */\n  .site-navigation {\n    background-color: #dddddd;\n  }\n  .site-navigation > div {\n    position: relative;\n    max-width: 54em;\n    margin: 0 auto;\n  }\n  .site-navigation ul {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n  .site-navigation li {\n    border-bottom: 1px solid #cccccc;\n    background-color: #eeeeee;\n  }\n  .site-navigation li:first-child {\n    border-top: 1px solid #cccccc;\n  }\n  .site-navigation li:hover {\n    background-color: #f8f8f8;\n  }\n  .site-navigation li:active {\n    background-color: lightgray;\n    box-shadow: inset 0 0.2em 0.25em rgba(0, 0, 0, 0.15);\n  }\n  .site-navigation li.active {\n    background-color: white;\n    box-shadow: none;\n  }\n  .site-navigation a {\n    display: block;\n    padding: 0.5em 6%;\n    color: #333333;\n  }\n  .site-navigation a:hover, .site-navigation a:visited {\n    color: #333333;\n    text-decoration: none;\n  }\n  .site-navigation a:active {\n    color: #1a1a1a;\n    text-decoration: none;\n  }\n\n  /**\n   * Search field in navigation.\n   */\n  .search {\n    padding: 1em 6% 0.5em;\n    background: #ffffff;\n  }\n  .search input[type=search] {\n    width: 16em;\n    height: 2.125em;\n    padding-right: 0.1875em;\n    padding-left: 2em;\n    border-radius: 0.5em;\n    background: url(\"data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'32\' height=\'32\' viewBox=\'0 0 32 32\'%3E%3Cg fill=\'%23333\'%3E%3Cpath d=\'M4,13c0-5,4-9,9-9c5,0,9,4,9,9c0,5-4,9-9,9C8,22,4,18,4,13z M13,19c3.3,0,6-2.7,6-6s-2.7-6-6-6s-6,2.7-6,6S9.7,19,13,19z\'/%3E%3Cpath d=\'M17.5,19l1.5-1.5l4.5,3L28,25c0,0,0,1.5-0.75,2.25S25,28,25,28l-4.5-4.5L17.5,19z\'/%3E%3C/g%3E%3C/svg%3E\") no-repeat left center;\n    background-size: 2em 2em;\n  }\n\n  /* Links\n     ========================================================================== */\n  /**\n   * 1. Specify link colour.\n   * 2. Remove default underline style from non-hover state links.\n   * 3. Interrupt the decoration line to let the shape of the text show through\n   *    in supported browsers.\n   */\n  a {\n    /* 1 */\n    color: #004cbf;\n    /* 2 */\n    text-decoration: none;\n    /* 3 */\n    -webkit-text-decoration-skip: ink;\n            text-decoration-skip-ink: auto;\n  }\n  a:hover, a:active {\n    outline: 0;\n    color: #0066ff;\n    text-decoration: underline;\n  }\n  a:focus {\n    outline: 1px solid #0066ff;\n  }\n\n  /**\n   * Additional styling for `h1`-`h3` heading links.\n   *\n   * 1. Expanded CSS level 3 `text-decoration-color` property in supported\n   *    browsers, older browsers ignore this addition.\n   */\n  h1 a,\nh2 a,\nh3 a,\nh4 a,\nh5 a,\nh6 a {\n    color: #000000;\n  }\n  h1 a:hover, h1 a:active,\nh2 a:hover,\nh2 a:active,\nh3 a:hover,\nh3 a:active,\nh4 a:hover,\nh4 a:active,\nh5 a:hover,\nh5 a:active,\nh6 a:hover,\nh6 a:active {\n    color: #000000;\n    /* 1 */\n    -webkit-text-decoration-color: rgba(51, 51, 51, 0.5);\n            text-decoration-color: rgba(51, 51, 51, 0.5);\n  }\n\n  /**\n   * Paginator (prev/next) navigation links on articles/article lists.\n   *\n   * Example HTML:\n   *\n   * <nav class=\"paginator\">\n   *     <a rel=\"prev\">\n   *     <a rel=\"next\">\n   */\n  .paginator {\n    clear: both;\n  }\n  .paginator [rel=prev] {\n    float: left;\n  }\n  .paginator [rel=next] {\n    float: right;\n  }\n\n  /* Typography\n     ========================================================================== */\n  /**\n   * Establish baseline.\n   */\n  html {\n    font-size: 16px;\n    line-height: 1.5;\n  }\n\n  /**\n   * Global font and text colour.\n   */\n  body {\n    color: #333333;\n    font-family: \"PT Serif\", Georgia, serif;\n  }\n\n  /**\n   * Sans-serif font for main navigation menu.\n   */\n  .site-navigation {\n    font-family: system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n    font-weight: bold;\n  }\n\n  /**\n   * Harmonize size, style and vertical margin of headings.\n   */\n  h1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n    clear: both;\n    color: #000000;\n    font-family: system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  }\n\n  h1 {\n    margin: 0.6315789em 0;\n    font-size: 2.375rem;\n    letter-spacing: -0.25px;\n    line-height: 1.1842105;\n  }\n\n  h2 {\n    margin: 0.75em 0;\n    font-size: 2rem;\n    letter-spacing: -0.25px;\n    line-height: 1.25;\n  }\n\n  h3 {\n    margin: 1em 0;\n    font-size: 1.5rem;\n    letter-spacing: -0.25px;\n    line-height: 1.3333333;\n  }\n\n  h4 {\n    margin: 1em 0;\n    font-size: 1.25rem;\n    line-height: 1.4;\n  }\n\n  h5 {\n    margin: 1em 0;\n    font-size: 1.125rem;\n    line-height: 1.4444444;\n  }\n\n  h6 {\n    margin: 1em 0;\n    font-size: 1rem;\n    line-height: 1.5;\n  }\n\n  /**\n   * Add the correct font weight in Chrome, Edge, and Safari.\n   */\n  b,\nstrong {\n    font-weight: bolder;\n  }\n\n  /**\n   * Add vertical margin to addresses.\n   */\n  address {\n    margin: 1em 0;\n  }\n\n  /**\n   * Additional styling for blockquotes.\n   */\n  blockquote {\n    margin: 0.8em 0;\n    font-size: 1.25rem;\n    font-style: italic;\n    line-height: 1.4;\n    text-align: center;\n  }\n\n  /**\n   * 1. Remove the bottom border in Chrome 57+.\n   * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n   */\n  abbr[title] {\n    /* 1 */\n    border-bottom: 0;\n    /* 2 */\n    -webkit-text-decoration: underline dotted;\n            text-decoration: underline dotted;\n  }\n\n  /**\n   * Consistent styling for `mark` and `var` tags.\n   */\n  mark,\nvar {\n    padding: 0 0.25em;\n    border-radius: 0.25em;\n    background: #fff9e1;\n    color: #333333;\n  }\n\n  /**\n   * Harmonize size and style of computer text.\n   */\n  pre,\ncode,\nkbd,\nsamp {\n    border: 1px solid #e3e3e3;\n    border-radius: 0.2857143em;\n    background-color: #f7f7f7;\n    color: #333333;\n    font-family: Menlo, Consolas, Monaco, monospace;\n    font-size: 0.875rem;\n    line-height: 1.5;\n  }\n\n  code,\nkbd,\nsamp {\n    padding: 1px 0.21429em;\n  }\n\n  /**\n   * Additional stylng for preformatted text/code.\n   *\n   * 1. Contain overflow in all browsers.\n   * 2. Don\'t wrap long words.\n   * 3. Set tab size to 4 spaces.\n   */\n  pre {\n    padding: 0.5714286em 1.1428571em;\n    /* 1 */\n    overflow-x: auto;\n    /* 2 */\n    word-wrap: normal;\n    /* 3 */\n    -moz-tab-size: 4;\n      -o-tab-size: 4;\n         tab-size: 4;\n  }\n  pre code {\n    padding: 0;\n    border: 0;\n    background-color: transparent;\n    font-size: 1em;\n    text-align: left;\n    word-wrap: normal;\n    word-break: normal;\n    white-space: pre;\n    word-spacing: normal;\n    direction: ltr;\n    -webkit-hyphens: none;\n        -ms-hyphens: none;\n            hyphens: none;\n  }\n\n  /**\n   * Prevent `sub` and `sup` elements from affecting the line height in all\n   * browsers.\n   */\n  sub,\nsup.footnote,\nsup {\n    position: relative;\n    font-size: 0.625em;\n    line-height: 0;\n    vertical-align: baseline;\n  }\n\n  sup {\n    top: -0.5em;\n  }\n\n  sub {\n    bottom: -0.25em;\n  }\n\n  /**\n   * Harmonize size and style of small text.\n   */\n  small,\nfigcaption,\ntfoot,\n.footnote {\n    font-size: 0.8125rem;\n    line-height: 1.5;\n  }\n\n  figcaption,\ntfoot,\n.footnote {\n    color: #585858;\n  }\n\n  figcaption {\n    margin-top: 0.5em;\n    font-style: italic;\n  }\n\n  /**\n   * 1. Add the correct box sizing in Firefox.\n   * 2. Show the overflow in Edge and IE.\n   */\n  hr {\n    /* 1 */\n    box-sizing: content-box;\n    height: 24px;\n    margin: 1em 0;\n    /* 2 */\n    overflow: visible;\n    border: 0;\n    background: url(\"data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 240 24\'%3E%3Cpath fill=\'%23333\' d=\'M0,0c1.742,13.61,54.74,20.912,119.995,15.279C184.922,9.679,238.594,13.024,240,24c-1.744-13.613-54.742-20.913-120.005-15.284C55.078,14.32,1.405,10.973,0,0z\'/%3E%3C/svg%3E\") 50% 50% no-repeat;\n    background-size: 240px 24px;\n  }\n\n  /* Support for non-latin languages (can be removed if not required)\n     ========================================================================== */\n  /**\n   * Bidirectional (bidi) text support.\n   * See: https://www.w3.org/International/articles/inline-bidi-markup/#cssshim\n   */\n  [dir=ltr],\n[dir=rtl] {\n    unicode-bidi: -webkit-isolate;\n    unicode-bidi: isolate;\n  }\n\n  bdo[dir=ltr],\nbdo[dir=rtl] {\n    unicode-bidi: isolate-override;\n  }\n\n  /**\n   * Preferred font for Japanese language.\n   */\n  :lang(ja) {\n    font-family: \"Hiragino Sans\", \"Hiragino Kaku Gothic Pro\", \"Meiryo UI\", sans-serif;\n    font-weight: 300;\n  }\n\n  /**\n   * Preferred font for Korean language.\n   */\n  :lang(ko) {\n    font-family: GulimChe, Gulim, sans-serif;\n  }\n\n  /**\n   * Preferred font for simplified Chinese (PRC) language.\n   */\n  :lang(zh-cn) {\n    font-family: \"PingFang SC\", SimHei, sans-serif;\n  }\n\n  /**\n   * Preferred font for traditional Chinese (Taiwan, Hong Kong) language.\n   */\n  :lang(zh-tw) {\n    font-family: \"PingFang TC\", PMingLiU, sans-serif;\n  }\n\n  /**\n   * Suppress `text-decoration` in some RTL languages for better legibility.\n   */\n  [lang|=ar] a:hover,\n[lang|=fa] a:hover,\n[lang|=ur] a:hover {\n    text-decoration: none;\n  }\n\n  /**\n   * Computer code should be LTR.\n   */\n  [dir=rtl] code {\n    direction: ltr;\n  }\n\n  /**\n   * Swap image alignment right/left positions in RTL languages.\n   */\n  [dir=rtl] img.align-left {\n    margin: 1em 0 1em 1em;\n    float: right;\n  }\n  [dir=rtl] img.align-right {\n    margin: 1em 1em 1em 0;\n    float: left;\n  }\n\n  /**\n   * Swap table caption alignment in RTL languages.\n   */\n  [dir=rtl] caption {\n    text-align: right;\n  }\n\n  /**\n   * Swap table cell text alignments in RTL languages.\n   */\n  [dir=rtl] th,\n[dir=rtl] td {\n    text-align: right;\n  }\n\n  /**\n   * Swap menu/list padding positions in RTL languages.\n   */\n  [dir=rtl] menu,\n[dir=rtl] ol,\n[dir=rtl] ul {\n    padding: 0 2em 0 0;\n  }\n\n  [dir=rtl] .site-navigation ul,\n[dir=rtl] .article-list {\n    padding: 0;\n  }\n\n  /**\n   * Swap definition description margin positions in RTL languages.\n   */\n  [dir=rtl] dd {\n    margin: 0 2em 0 0;\n  }\n\n  /**\n   * Swap checkbox/radio margin positions in RTL languages.\n   */\n  [dir=rtl] input[type=checkbox] + label,\n[dir=rtl] input[type=radio] + label {\n    margin: 0 0 0 0.5em;\n  }\n  [dir=rtl] input[type=checkbox] + label:last-of-type,\n[dir=rtl] input[type=radio] + label:last-of-type {\n    margin: 0;\n  }\n\n  /**\n   * Swap paginator (prev/next) navigation links on articles/article lists.\n   */\n  [dir=rtl] .paginator [rel=prev] {\n    float: right;\n  }\n  [dir=rtl] .paginator [rel=next] {\n    float: left;\n  }\n\n  /**\n   * Swap anchor float position on comments.\n   */\n  [dir=rtl] .comments h4 .comment-anchor {\n    float: left;\n  }\n\n  /* Embedded content\n     ========================================================================== */\n  /**\n   * Add the correct display in IE 11.\n   */\n  video {\n    display: inline-block;\n  }\n\n  /**\n   * Make embedded elements responsive.\n   */\n  img,\nvideo {\n    max-width: 100%;\n    height: auto;\n  }\n\n  /**\n   * Images.\n   *\n   * 1. Remove the gap between images and the bottom of their containers.\n   *\n   * Image alignment (compatible with Textile markup syntax).\n   *\n   * Example HTML:\n   *\n   * <img class=\"align-left\">\n   * <img class=\"align-right\">\n   * <img class=\"align-center\">\n   */\n  img {\n    /* 1 */\n    vertical-align: middle;\n  }\n  img.align-left {\n    margin: 1em 1em 1em 0;\n    float: left;\n  }\n  img.align-right {\n    margin: 1em 0 1em 1em;\n    float: right;\n  }\n  img.align-center {\n    display: block;\n    margin: 1em auto;\n  }\n\n  /**\n   * Consistent margins on `figure`.\n   */\n  figure {\n    margin: 1em 0;\n  }\n\n  /* Tables\n     ========================================================================== */\n  /**\n   * Consistent tables.\n   */\n  table {\n    width: 100%;\n    margin-bottom: 1em;\n    border-spacing: 0;\n    border-collapse: collapse;\n  }\n\n  /**\n   * Styling of table captions.\n   */\n  caption {\n    margin-bottom: 0.5em;\n    font-style: italic;\n    text-align: left;\n  }\n\n  /**\n   * Make table cells align top and left by default.\n   */\n  th,\ntd {\n    padding: 0.5em;\n    border: 1px solid #d4d4d4;\n    text-align: left;\n    vertical-align: top;\n  }\n\n  /**\n   * Emphasize table header.\n   */\n  thead tr {\n    background-color: #eeeeee;\n  }\n  thead th,\nthead td {\n    border: 1px solid #cccccc;\n  }\n\n  /**\n   * \'Zebra striping\' of `tbody` rows.\n   */\n  tbody tr:nth-child(even) {\n    background-color: #f7f7f7;\n  }\n\n  /**\n   * Adjust padding of table footer due to smaller font size.\n   */\n  tfoot th,\ntfoot td {\n    padding: 0.6666667em;\n  }\n\n  /**\n   * Multi-row span vertical cell alignments.\n   */\n  [rowspan] {\n    vertical-align: middle;\n  }\n\n  /* Lists\n     ========================================================================== */\n  /**\n   * Address paddings set differently.\n   */\n  menu,\nol,\nul {\n    padding: 0 0 0 2em;\n  }\n\n  /**\n   * Remove margins from nested lists.\n   */\n  li > ul,\nli > ol {\n    margin: 0;\n  }\n\n  /**\n   * CSS Lists and Counters Module Level 3 list marker styling.\n   */\n  li::marker {\n    color: color-text-heading;\n  }\n\n  /**\n   * Address margins set differently.\n   */\n  dd {\n    margin: 0 0 0 2em;\n  }\n\n  /**\n   * Additional styling for article lists.\n   *\n   * Example HTML:\n   *\n   * <ul class=\"article-list\">\n   */\n  .article-list {\n    margin: 0 0 2em;\n    padding: 0;\n    border-top: 1px solid #cccccc;\n    list-style: none;\n  }\n  .article-list li {\n    margin-bottom: 0;\n    border-bottom: 1px solid #cccccc;\n  }\n\n  /* Forms\n     ========================================================================== */\n  /**\n   * 1. Address width being affected by wide descendants in Chrome, Firefox.\n   * 2. Define consistent fieldset border, margin, and padding.\n   */\n  fieldset {\n    /* 1 */\n    min-width: 0;\n    /* 2 */\n    margin: 1em 0;\n    padding: 1px 1em;\n    border: 1px solid #cccccc;\n  }\n\n  /**\n   * Normalize styling of `legend`.\n   *\n   * 1. Correct wrapping not present in IE 11 and Edge 12/13.\n   * 2. Remove padding so people aren\'t caught out if they zero out fieldsets.\n   * 3. Correct `color` not being inherited from fieldset in IE 11.\n   */\n  legend {\n    /* 1 */\n    display: table;\n    /* 1 */\n    box-sizing: border-box;\n    /* 1 */\n    max-width: 100%;\n    /* 2 */\n    padding: 0;\n    /* 3 */\n    color: inherit;\n    /* 1 */\n    white-space: normal;\n  }\n\n  /**\n   * Show the overflow in Edge.\n   */\n  button,\ninput {\n    overflow: visible;\n  }\n\n  /**\n   * 1. Prevent elements from spilling out of their parent.\n   * 2. Address margins set differently in Firefox 4+, Safari, and Chrome.\n   * 3. Correct font properties not being inherited.\n   */\n  button,\ninput,\noptgroup,\nselect,\ntextarea {\n    box-sizing: border-box;\n    /* 1 */\n    max-width: 100%;\n    /* 2 */\n    margin: 0;\n    color: #000000;\n    font-family: system-ui, -apple-system, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n    /* 3 */\n    font-size: 1rem;\n    line-height: 1.375;\n    vertical-align: baseline;\n  }\n\n  /**\n   * Styling of form input fields.\n   *\n   * 1. Remove browser-specific default styling.\n   */\n  [type=color],\n[type=date],\n[type=datetime],\n[type=datetime-local],\n[type=email],\n[type=month],\n[type=number],\n[type=password],\n[type=search],\n[type=tel],\n[type=text],\n[type=time],\n[type=url],\n[type=week],\nselect,\ntextarea {\n    height: 2em;\n    padding: 0.25em 0.1875em;\n    border: 1px solid #cccccc;\n    border-radius: 0;\n    background: #ffffff;\n    /* 1 */\n    -webkit-appearance: none;\n       -moz-appearance: none;\n            appearance: none;\n  }\n  [type=color]:focus,\n[type=date]:focus,\n[type=datetime]:focus,\n[type=datetime-local]:focus,\n[type=email]:focus,\n[type=month]:focus,\n[type=number]:focus,\n[type=password]:focus,\n[type=search]:focus,\n[type=tel]:focus,\n[type=text]:focus,\n[type=time]:focus,\n[type=url]:focus,\n[type=week]:focus,\nselect:focus,\ntextarea:focus {\n    border-color: #0066ff;\n    outline: 1px solid transparent;\n  }\n\n  /**\n   * 1. Remove any excess padding.\n   * 2. Correct margins for inline checkbox/radio labels.\n   */\n  [type=checkbox],\n[type=radio] {\n    /* 1 */\n    padding: 0;\n  }\n  [type=checkbox] + label,\n[type=radio] + label {\n    /* 2 */\n    margin: 0 0.5em 0 0;\n  }\n  [type=checkbox] + label:last-of-type,\n[type=radio] + label:last-of-type {\n    /* 2 */\n    margin: 0;\n  }\n\n  /**\n   * Correct the cursor style of increment and decrement buttons in Safari.\n   */\n  [type=number]::-webkit-inner-spin-button,\n[type=number]::-webkit-outer-spin-button {\n    height: auto;\n  }\n\n  /**\n   * Remove the inner padding in Chrome and Safari on macOS.\n   */\n  [type=search]::-webkit-search-decoration {\n    /* autoprefixer: off */\n    appearance: none;\n  }\n\n  /**\n   * Use indicator icon to signify the drop-down ability of `select`.\n   */\n  select {\n    padding-right: 1.5em;\n    background: #ffffff url(\"data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'17\' height=\'5\' viewBox=\'0 0 17 5\'%3E%3Cpolygon fill=\'%23333\' points=\'0,0 5,5 10,0\'/%3E%3C/svg%3E\") right center no-repeat;\n    background-size: 1.0625em 0.3125em;\n    text-transform: none;\n  }\n\n  /**\n   * Swap `select` icon position in RTL languages.\n   */\n  [dir=rtl] select {\n    padding-right: 0.1875em;\n    padding-left: 1.5em;\n    background-position: 0.5em center;\n  }\n\n  /**\n   * Remove browser-specific `select` UI in IE 11.\n   */\n  select::-ms-expand {\n    opacity: 0;\n  }\n\n  /**\n   * Remove browser-specific `select` UI in Firefox 4+.\n   */\n  select:-moz-focusring {\n    color: transparent;\n    text-shadow: 0 0 0 #000000;\n  }\n\n  /**\n   * Override height and background set in a previous rule and allow auto height.\n   */\n  select[size],\nselect[multiple] {\n    height: auto;\n    padding-right: 0.5em;\n    background-image: none;\n  }\n\n  /**\n   * Override height set in rule above and restrict to one line field.\n   */\n  select[size=\"0\"],\nselect[size=\"1\"] {\n    height: 2em;\n  }\n\n  /**\n   * Normalize styling of `optgroup`.\n   */\n  optgroup {\n    color: #000000;\n    font-style: normal;\n    font-weight: bold;\n  }\n\n  /**\n   * 1. Remove default vertical scrollbar in IE 11.\n   * 2. Remove unwanted space below `textarea` in Safari, Chrome, Opera.\n   * 3. Restrict to vertical resizing to prevent layout breakage.\n   */\n  textarea {\n    width: 100%;\n    height: auto;\n    min-height: 3em;\n    /* 1 */\n    overflow: auto;\n    /* 2 */\n    vertical-align: top;\n    /* 3 */\n    resize: vertical;\n  }\n\n  /**\n   * Make sure disabled elements really are disabled and styled appropriately.\n   *\n   * 1. Override default iOS opacity setting.\n   * 2. Re-set default cursor for disabled elements.\n   */\n  [disabled],\n[disabled] option,\n[disabled] optgroup,\nspan.disabled {\n    border-color: #d4d4d4 !important;\n    /* 1 */\n    opacity: 1;\n    background: #eeeeee !important;\n    box-shadow: none !important;\n    color: #aaaaaa !important;\n    text-shadow: none !important;\n    /* 2 */\n    cursor: default !important;\n  }\n\n  /**\n   * Width display options for `input` fields. Height display options\n   * for textareas.\n   *\n   * Example HTML:\n   *\n   * <input class=\"small\">\n   * <input class=\"large\">\n   */\n  .small input {\n    width: 25%;\n    min-width: 151px;\n  }\n  .small textarea {\n    height: 5.5em;\n  }\n\n  .large input {\n    width: 50%;\n    min-width: 302px;\n  }\n  .large textarea {\n    height: 9.75em;\n  }\n\n  /**\n   * Styling for form field validation.\n   */\n  input:focus:invalid,\nselect:focus:invalid,\ntextarea:focus:invalid {\n    border-color: #9d261d;\n    box-shadow: none;\n  }\n\n  /**\n   * Styling for Firefox-specfic form field validation.\n   */\n  input:-moz-ui-invalid,\nselect:-moz-ui-invalid,\ntextarea:-moz-ui-invalid {\n    border-color: #9d261d;\n    box-shadow: none;\n  }\n\n  /**\n   * Normalize form placeholder style across browsers.\n   *\n   * 1. Fix placeholder font properties inheritance.\n   */\n  ::-webkit-input-placeholder {\n    opacity: 1;\n    color: #999999;\n    /* 1 */\n    font: inherit;\n  }\n  :-ms-input-placeholder {\n    opacity: 1;\n    color: #999999;\n    /* 1 */\n    font: inherit;\n  }\n  ::-ms-input-placeholder {\n    opacity: 1;\n    color: #999999;\n    /* 1 */\n    font: inherit;\n  }\n  ::placeholder {\n    opacity: 1;\n    color: #999999;\n    /* 1 */\n    font: inherit;\n  }\n\n  /**\n   * Styling for required field indicators.\n   *\n   * Example HTML:\n   *\n   * <b class=\"required\" title=\"Required\">*</b>\n   */\n  .required {\n    border: 0;\n    color: #9d261d;\n  }\n\n  /* Buttons\n     ========================================================================== */\n  /**\n   * 1. Address `overflow` set to `hidden` in IE 11.\n   * 2. Remove the inheritance of text transform in Edge, Firefox, and IE 11.\n   */\n  button {\n    /* 1 */\n    overflow: visible;\n    /* 2 */\n    text-transform: none;\n  }\n\n  /**\n   * Remove the inner border and padding in Firefox.\n   */\n  button::-moz-focus-inner,\n[type=button]::-moz-focus-inner,\n[type=reset]::-moz-focus-inner,\n[type=submit]::-moz-focus-inner {\n    padding: 0;\n    border-style: none;\n  }\n\n  /**\n   * 1. Remove browser-specific default styling.\n   * 2. Improve usability and consistency of cursor style between image-type\n   *    `input` and others.\n   */\n  button,\n[type=button],\n[type=reset],\n[type=submit] {\n    background-color: #dfdfdf;\n    background-image: linear-gradient(#eeeeee, #dfdfdf);\n    display: inline-block;\n    position: relative;\n    width: auto;\n    height: 2em;\n    padding: 0.25em 1em;\n    border: 1px solid #dfdfdf;\n    border-radius: 1em;\n    background-clip: padding-box;\n    box-shadow: 0 2px 0 #acacac;\n    color: #333333;\n    font-weight: normal;\n    text-align: center;\n    /* 1 */\n    -webkit-appearance: none;\n       -moz-appearance: none;\n            appearance: none;\n    /* 2 */\n    cursor: pointer;\n  }\n  button:hover,\n[type=button]:hover,\n[type=reset]:hover,\n[type=submit]:hover {\n    background-color: #e9e9e9;\n    background-image: linear-gradient(#f8f8f8, #e9e9e9);\n    border-color: #bebebe;\n    color: #333333;\n    text-decoration: none;\n  }\n  button:active,\n[type=button]:active,\n[type=reset]:active,\n[type=submit]:active {\n    top: 2px;\n    border-color: #bebebe;\n    box-shadow: none;\n    color: #1a1a1a;\n    text-decoration: none;\n  }\n  button:focus,\n[type=button]:focus,\n[type=reset]:focus,\n[type=submit]:focus {\n    border-color: #0066ff;\n    outline: 1px solid transparent;\n  }\n\n  /**\n   * Disbaled button additional styling.\n   */\n  button[disabled],\n[type=button][disabled],\n[type=reset][disabled],\n[type=submit][disabled] {\n    top: 2px !important;\n  }\n\n  /* Comments\n     ========================================================================== */\n  /**\n   * Styling for user comments.\n   *\n   * Example HTML:\n   *\n   * <article class=\"comments\">\n   */\n  .comments {\n    margin-bottom: 1em;\n    padding: 1px 1em;\n    border-radius: 0.5em;\n    background: #f7f7f7;\n    word-wrap: break-word;\n  }\n  .comments h4 .is-author {\n    font-weight: normal;\n  }\n  .comments h4 .comment-anchor {\n    float: right;\n    font-weight: normal;\n  }\n\n  /**\n   * Additional styling for article author\'s comments.\n   *\n   * Example HTML:\n   *\n   * <article class=\"comments comments-author\">\n   */\n  .comments-author {\n    background: #efefef;\n  }\n\n  /**\n   * Styling for user comments preview.\n   */\n  #cpreview {\n    margin-bottom: 2px;\n    padding: 1em;\n    border-radius: 0.5em;\n    background-color: #fff9e1;\n  }\n\n  /**\n   * Highlight text colour for comment errors.\n   */\n  .error_message li {\n    color: #9d261d;\n  }\n\n  /**\n   * Highlighting for comment form errors.\n   */\n  .comments_error {\n    border-color: #9d261d;\n  }\n\n  /* Popup comments (can be removed if you don\'t use popups)\n     ========================================================================== */\n  /**\n   * Restrict maximum width of popup container.\n   */\n  #popup-page .container {\n    max-width: 52em;\n  }\n}\n/* ==========================================================================\n   Additional layout for screen media 576px and up\n   ========================================================================== */\n@media only screen and (min-width: 38em) {\n  /**\n   * Desktop version of the navigation menu. Overrides mobile first version.\n   *\n   * Example HTML:\n   *\n   * <nav class=\"site-navigation\">\n   *     <ul>\n   *         <li class=\"active\"><a></li>\n   *         <li><a></li>\n   *         <li><a></li>\n   *     </ul>\n   * </nav>\n   */\n  .site-navigation {\n    border-top: 1px solid #cccccc;\n    border-bottom: 1px solid #cccccc;\n  }\n  .site-navigation > div {\n    width: 88%;\n  }\n  .site-navigation li {\n    float: left;\n    border-right: 1px solid #cccccc;\n    border-bottom: 0;\n  }\n  .site-navigation li:first-child {\n    border-top: 0;\n    border-left: 1px solid #cccccc;\n  }\n  [dir=rtl] .site-navigation li {\n    float: right;\n  }\n  [dir=rtl] .site-navigation li:first-child {\n    border-left: 0;\n  }\n  [dir=rtl] .site-navigation li:last-child {\n    border-left: 1px solid #cccccc;\n  }\n  .site-navigation a {\n    padding: 0.5em 1em;\n  }\n\n  .search {\n    position: absolute;\n    z-index: 2;\n    top: 50%;\n    right: 0;\n    margin-top: -1.0625em;\n    padding: 0;\n    background: transparent;\n  }\n  [dir=rtl] .search {\n    right: auto;\n    left: 0;\n  }\n  .search input[type=search] {\n    width: 2.125em;\n    padding-right: 0;\n    transition: width 0.2s ease-in-out, border-color 0.2s ease-in-out;\n    background-color: #eeeeee;\n    cursor: pointer;\n  }\n  .search input[type=search]:hover {\n    background-color: #f8f8f8;\n  }\n  .search input[type=search]:focus {\n    width: 16em;\n    padding-right: 0.1875em;\n    background-color: white;\n    cursor: auto;\n  }\n}\n/* ==========================================================================\n   Additional layout for screen media 864px and up\n   ========================================================================== */\n@media only screen and (min-width: 46em) {\n  /**\n   * Enlarge baseline text size.\n   */\n  html {\n    font-size: 1.125rem;\n  }\n}\n/* ==========================================================================\n   Styling and layout for print media\n   ========================================================================== */\n@media print {\n  /**\n   * Remove unnecessary global styling from printed media.\n   */\n  *,\n*::before,\n*::after {\n    background: transparent !important;\n    box-shadow: none !important;\n    color: #000000 !important;\n    text-shadow: none !important;\n  }\n\n  /**\n   * Use a print-friendly font size.\n   */\n  html {\n    font-size: 8pt;\n    line-height: 1.5;\n  }\n\n  /**\n   * Use a print-friendly font family.\n   */\n  body {\n    margin: 0.5cm;\n    padding: 2em 5em;\n    font-family: \"Helvetica Neue\", sans-serif;\n  }\n\n  /**\n   * Make sure links are not underlined.\n   */\n  a {\n    text-decoration: none;\n  }\n\n  /**\n   * Visually separate header from body.\n   */\n  .site-header {\n    border-bottom: 1pt solid #000000;\n  }\n\n  /**\n   * Visually separate footer from body.\n   */\n  .site-footer {\n    margin-top: 12pt;\n    border-top: 1pt solid #000000;\n  }\n\n  /**\n   * Hide unnecessary content from print.\n   */\n  nav,\naudio,\nvideo,\nform,\n#comments-form,\n.comments h4 a:last-child,\n.complementary-content,\n.paginator {\n    display: none;\n  }\n\n  /**\n   * Show long-form for abbreviations in print.\n   */\n  abbr[title]::after {\n    content: \" (\" attr(title) \")\";\n  }\n\n  /**\n   * Harmonize size, style and vertical margin of headings.\n   */\n  h1 {\n    margin: 16pt 0;\n    font-size: 32pt;\n    font-weight: normal;\n    line-height: 36pt;\n  }\n\n  h2 {\n    margin: 14pt 0;\n    font-size: 24pt;\n    line-height: 28pt;\n    page-break-after: avoid;\n    orphans: 2;\n    widows: 2;\n  }\n\n  h3 {\n    margin: 12pt 0;\n    font-size: 18pt;\n    line-height: 22pt;\n    page-break-after: avoid;\n    orphans: 2;\n    widows: 2;\n  }\n\n  h4 {\n    margin: 11pt 0;\n    font-size: 14pt;\n    line-height: 18pt;\n    page-break-after: avoid;\n    orphans: 2;\n    widows: 2;\n  }\n\n  h5 {\n    margin: 10pt 0;\n    font-size: 12pt;\n    line-height: 16pt;\n    page-break-after: avoid;\n    orphans: 2;\n    widows: 2;\n  }\n\n  h6 {\n    margin: 9pt 0;\n    font-size: 10pt;\n    line-height: 14pt;\n    page-break-after: avoid;\n    orphans: 2;\n    widows: 2;\n  }\n\n  /**\n   * Prevent widows (single final paragraph line on next page) and orphans (single\n   * first paragraph line on previous page).\n   */\n  p {\n    orphans: 2;\n    widows: 2;\n  }\n\n  /**\n   * Harmonize size and style of small text.\n   */\n  footer,\nfigcaption,\ntfoot,\nsmall,\n.footnote {\n    font-size: 6pt;\n  }\n\n  /**\n   * Simple blockquote styling.\n   *\n   * 1. Avoid blockquotes breaking across multiple pages.\n   */\n  blockquote {\n    padding: 0 0 0 8pt;\n    border-left: 3pt solid #000000;\n    font-size: 16pt;\n    /* 1 */\n    page-break-inside: avoid;\n  }\n\n  [dir=rtl] blockquote {\n    padding: 0 8pt 0 0;\n    border-right: 3pt solid #000000;\n    border-left: 0;\n  }\n\n  /**\n   * Simple preformatted text styling.\n   *\n   * 1. Ensure pre blocks are wrapped when printed.\n   */\n  pre {\n    margin-bottom: 8pt;\n    padding: 8pt;\n    border: 1pt solid #000000;\n    /* 1 */\n    white-space: pre-wrap !important;\n  }\n\n  /**\n   * Use a print-friendly monospaced font and size.\n   */\n  pre,\ncode,\nkbd,\nsamp,\nvar {\n    font-family: \"Courier New\", Courier, monospace;\n  }\n\n  /**\n   * Images.\n   *\n   * 1. Avoid images breaking across multiple pages.\n   *\n   * Image alignment (compatible with Textile markup syntax).\n   *\n   * Example HTML:\n   *\n   * <img class=\"align-left\">\n   * <img class=\"align-right\">\n   * <img class=\"align-center\">\n   */\n  img {\n    /* 1 */\n    page-break-inside: avoid;\n  }\n  img.align-left {\n    margin: 1em 1em 1em 0;\n    float: left;\n  }\n  img.align-right {\n    margin: 1em 0 1em 1em;\n    float: right;\n  }\n  img.align-center {\n    display: block;\n    margin: 1em auto;\n  }\n\n  /**\n   * Swap image alignment right/left positions in RTL languages.\n   */\n  [dir=rtl] img.align-left {\n    margin: 1em 0 1em 1em;\n    float: right;\n  }\n  [dir=rtl] img.align-right {\n    margin: 1em 1em 1em 0;\n    float: left;\n  }\n\n  /**\n   * Ensure margin below `figure`.\n   */\n  figure {\n    margin-bottom: 8pt;\n  }\n\n  /**\n   * Ensure margin above `figcaption`.\n   */\n  figcaption {\n    margin-top: 4pt;\n  }\n\n  /**\n   * Simple bullet styling for `ul` unordered lists.\n   */\n  ul {\n    padding: 0 0 8pt 1.8em;\n    list-style: square;\n  }\n\n  [dir=rtl] ul {\n    padding: 0 1.8em 8pt 0;\n  }\n\n  /**\n   * Simple numerical styling for `ol` ordered lists.\n   */\n  ol {\n    padding: 0 0 8pt 1.8em;\n    list-style: decimal;\n  }\n\n  [dir=rtl] ol {\n    padding: 0 1.8em 8pt 0;\n  }\n\n  /**\n   * Normalize margins on `dl` definition lists.\n   */\n  dl {\n    padding: 0 0 8pt 1.8em;\n  }\n\n  [dir=rtl] dl {\n    padding: 0 1.8em 8pt 0;\n  }\n\n  /**\n   * 1. Make `table` span entire page width.\n   * 2. Ensure margin below `table`.\n   */\n  table {\n    /* 1 */\n    width: 100%;\n    /* 2 */\n    margin-bottom: 8pt;\n  }\n\n  /**\n   * Harmonize styling for `caption`.\n   */\n  caption {\n    margin-bottom: 4pt;\n    font-weight: bold;\n  }\n\n  /**\n   * Avoid table rows breaking across multiple pages.\n   */\n  tr {\n    page-break-inside: avoid;\n  }\n\n  /**\n   * Simple styling for table cells.\n   */\n  th,\ntd {\n    padding: 4pt 8pt;\n    border-bottom: 1pt solid #000000;\n  }\n\n  /**\n   * Display table head across multi-page tables.\n   */\n  thead {\n    display: table-header-group;\n  }\n  thead th {\n    border-top: 1pt solid #000000;\n  }\n\n  /**\n   * Avoid user comments breaking across multiple pages.\n   */\n  .comments {\n    page-break-inside: avoid;\n  }\n}', 'four-point-seven', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_discuss`
--

CREATE TABLE `txp_discuss` (
  `discussid` int(6) UNSIGNED ZEROFILL NOT NULL,
  `parentid` int(11) NOT NULL DEFAULT '0',
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `email` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `web` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `posted` datetime NOT NULL,
  `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `visible` tinyint(4) NOT NULL DEFAULT '1',
  `yab_rr_rating` tinyint(3) UNSIGNED NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_discuss`
--

INSERT INTO `txp_discuss` (`discussid`, `parentid`, `name`, `email`, `web`, `posted`, `message`, `visible`, `yab_rr_rating`) VALUES
(000001, 1, 'Donald Swain', 'donald-swain@example.com', 'https://docs.textpattern.com/brand/donald-swain', '2019-07-23 21:58:54', '<p>I enjoy your site very much.</p>', 1, 0),
(000002, 2, 'Cookiemarios', 'user@fakemail.com', 'example.com', '2019-07-25 07:26:12', '<p>Excellent Book Mate</p>', 1, 0);

-- --------------------------------------------------------

--
-- Table structure for table `txp_discuss_nonce`
--

CREATE TABLE `txp_discuss_nonce` (
  `issue_time` datetime NOT NULL,
  `nonce` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `used` tinyint(4) NOT NULL DEFAULT '0',
  `secret` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_discuss_nonce`
--

INSERT INTO `txp_discuss_nonce` (`issue_time`, `nonce`, `used`, `secret`) VALUES
('2019-07-25 07:26:07', '9484185ea969d907438aed76f260e63d', 1, 'e10c03d63ba440a99e30337f7076bbb0');

-- --------------------------------------------------------

--
-- Table structure for table `txp_file`
--

CREATE TABLE `txp_file` (
  `id` int(11) NOT NULL,
  `filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `category` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `permissions` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0',
  `description` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `downloads` int(10) UNSIGNED NOT NULL DEFAULT '0',
  `status` smallint(6) NOT NULL DEFAULT '4',
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL,
  `size` bigint(20) DEFAULT NULL,
  `author` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `jcr_file_custom` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Table structure for table `txp_form`
--

CREATE TABLE `txp_form` (
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `type` varchar(28) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `Form` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `skin` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'default',
  `lastmod` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_form`
--

INSERT INTO `txp_form` (`name`, `type`, `Form`, `skin`, `lastmod`) VALUES
('images', 'misc', '<!-- set up a variable to check whether an image also has a caption associated with it... -->\n<txp:variable name=\"caption\" value=\'<txp:image_info />\' />\n\n<!-- ...now use that image caption and wrap img inside a figure with figcaption tags, otherwise just use a plain img tag -->\n<txp:if_variable name=\"caption\" value=\"\">\n\n    <p itemprop=\"image\" itemscope itemtype=\"https://schema.org/ImageObject\">\n        <img itemprop=\"url contentUrl\" src=\"<txp:image_url link=\'0\' />\" alt=\"<txp:image_info type=\'alt\' />\">\n        <meta itemprop=\"width\" content=\"<txp:image_info type=\"w\" />\">\n        <meta itemprop=\"height\" content=\"<txp:image_info type=\"h\" />\">\n    </p>\n\n<txp:else />\n\n    <figure itemprop=\"image\" itemscope itemtype=\"https://schema.org/ImageObject\">\n\n        <img itemprop=\"url contentUrl\" src=\"<txp:image_url link=\'0\' />\" alt=\"<txp:image_info type=\'alt\' />\">\n        <meta itemprop=\"width\" content=\"<txp:image_info type=\"w\" />\">\n        <meta itemprop=\"height\" content=\"<txp:image_info type=\"h\" />\">\n\n        <!-- you do not need to specify the attribute type=\"caption\" as that is the default setting for <txp:image_info /> tag -->\n        <figcaption itemprop=\"caption\">\n            <txp:image_info />\n        </figcaption>\n\n    </figure>\n\n</txp:if_variable>', 'four-point-seven', NULL),
('search_display', 'misc', '<h1 itemprop=\"name\"><txp:text item=\"search_results\" /></h1>\n\n<txp:if_search_results>\n\n    <!-- if search result count greater than 200 then display excessive results message, otherwise show search result count -->\n    <txp:if_search_results max=\"200\">\n        <h3>\n            <txp:search_result_count />\n            <txp:text item=\"matching_search_request\" />\n            <q><txp:search_term /></q>\n        </h3>\n    <txp:else />\n        <h3>\n            <txp:text item=\"too_common_search_term\" />\n            <q><txp:search_term /></q>\n        </h3>\n    </txp:if_search_results>\n\n<!-- if no search results, then display no search results message -->\n<txp:else />\n    <h3>\n        <txp:text item=\"no_search_matches\" />\n    </h3>\n\n</txp:if_search_results>\n\n<!-- display resulting articles (10 per page; default setting) -->\n<txp:article class=\"article-list\" wraptag=\"ul\" />\n\n<!-- check if there are further results and provide pagination links depending on the result -->\n<txp:if_search_results min=\"11\">\n    <nav class=\"paginator\" aria-label=\"<txp:text item=\"page_nav\" />\">\n        <txp:evaluate>\n            <txp:newer rel=\"prev\">\n                <txp:text item=\"prev\" />\n            </txp:newer>\n            <txp:older rel=\"next\">\n                <txp:text item=\"next\" />\n            </txp:older>\n        </txp:evaluate>\n    </nav>\n</txp:if_search_results>', 'four-point-seven', NULL),
('body_header', 'misc', '<header class=\"site-header\">\n    <h2><txp:link_to_home><txp:site_name /></txp:link_to_home></h2>\n    <txp:evaluate>\n        <h3><txp:site_slogan /></h3>\n    </txp:evaluate>\n</header>\n\n<nav class=\"site-navigation\" aria-label=\"<txp:text item=\"site_nav\" />\" itemscope itemtype=\"https://schema.org/SiteNavigationElement\">\n    <div>\n        <txp:section_list default_title=\'<txp:text item=\"home\" />\' include_default wraptag=\"ul\" break=\"\">\n            <li<txp:if_section name=\'<txp:section />\'> class=\"active\"</txp:if_section>>\n                <a itemprop=\"url\" href=\"<txp:section url />\">\n                    <txp:section title />\n                </a>\n            </li>\n        </txp:section_list>\n\n        <!-- links by default to form: \'search_input.txp\' unless you specify a different form -->\n        <txp:search_input />\n    </div>\n</nav>', 'four-point-seven', NULL),
('body_aside', 'misc', '<aside class=\"complementary-content\">\n    <!-- feed links, default flavor is RSS, so we don\'t need to specify a flavor on the first feed_link -->\n    <p><txp:feed_link class=\"feed-rss\" label=\"RSS\" /> / <txp:feed_link class=\"feed-atom\" flavor=\"atom\" label=\"Atom\" /></p>\n\n    <!-- if links exist, renders a links list -->\n    <txp:evaluate test=\"linklist\">\n        <section>\n            <h4><txp:text item=\"links\" /></h4>\n            <!-- links by default to form: \'plainlinks.txp\' unless you specify a different form -->\n            <txp:linklist wraptag=\"ul\" break=\"li\" />\n        </section>\n    </txp:evaluate>\n</aside>', 'four-point-seven', NULL),
('body_footer', 'misc', '<footer class=\"site-footer\">\n    <p><small><txp:text item=\"published_with\" /> <a rel=\"external\" href=\"https://textpattern.com/\" title=\"<txp:text item=\"go_txp_com\" />\">Textpattern CMS</a></small></p>\n</footer>', 'four-point-seven', NULL),
('search_input', 'misc', '<div class=\"search\" aria-label=\"<txp:text item=\"search\" />\" itemscope itemtype=\"https://schema.org/WebSite\">\n    <meta itemprop=\"url\" content=\"<txp:site_url />\">\n    <form role=\"search\" method=\"get\" action=\"<txp:site_url />\" itemprop=\"potentialAction\" itemscope itemtype=\"https://schema.org/SearchAction\">\n        <meta itemprop=\"target\" content=\"<txp:site_url />?q={q}\">\n        <input name=\"q\" type=\"search\" itemprop=\"query-input\" placeholder=\"<txp:text item=\"search\" />\"<txp:if_search> value=\"<txp:search_term />\"</txp:if_search>>\n    </form>\n</div>', 'four-point-seven', NULL),
('files', 'file', '<div itemscope itemtype=\"https://schema.org/DataDownload\">\n\n    <!-- ...if exists, use the file title, otherwise use file name -->\n    <a href=\"<txp:file_download_link />\" itemprop=\"url contentUrl\">\n        <strong itemprop=\"name\">\n            <txp:evaluate>\n                <txp:file_download_name title />\n            <txp:else />\n                <txp:file_download_name />\n            </txp:evaluate>\n        </strong>\n    </a>\n\n    <!-- ...if exists, use the file description, otherwise omit that line -->\n    <txp:evaluate test>\n        <div itemprop=\"description\">\n            <txp:file_download_description />\n        </div>\n    </txp:evaluate>\n\n    <div class=\"footnote\">\n\n        <!-- ...if exists, use the file category, otherwise omit that line -->\n        <txp:evaluate test=\"file_download_category\">\n            <strong>\n                <txp:text item=\"category\" />\n            </strong>\n            <span itemprop=\"keywords\">\n                <txp:file_download_category title />\n            </span>\n            &#183;\n        </txp:evaluate>\n\n        <strong>\n            <txp:text item=\"author\" />\n        </strong>\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\">\n            <span itemprop=\"name\">\n                <txp:file_download_author link />\n            </span>\n        </span>\n        &#183;\n        <strong>\n            <txp:text item=\"file_size\" />\n        </strong>\n        <span itemprop=\"contentSize\">\n            <txp:file_download_size />\n        </span>\n        &#183;\n        <strong>\n            <txp:text item=\"last_modified\" />\n        </strong>\n        <time datetime=\"<txp:file_download_modified format=\"iso8601\" />\" itemprop=\"dateModified\">\n            <txp:file_download_modified />\n        </time>\n        &#183;\n        <strong>\n            <txp:text item=\"download_count\" />\n        </strong>\n        <span itemprop=\"interactionStatistic\" itemscope itemtype=\"https://schema.org/InteractionCounter\">\n            <meta itemprop=\"interactionType\" content=\"https://schema.org/DownloadAction\" />\n            <span itemprop=\"userInteractionCount\">\n                <txp:file_download_downloads />\n            </span>\n        </span>\n\n    </div>\n\n</div>', 'four-point-seven', NULL),
('popup_comments', 'comment', '<!DOCTYPE html>\n<html lang=\"<txp:lang />\" dir=\"<txp:text item=\"lang_dir\" />\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <title><txp:page_title /></title>\n\n    <!-- Google font API -->\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i&amp;subset=latin-ext\">\n\n    <!-- CSS -->\n    <txp:css format=\"link\" media=\"\" />\n    <!-- ...or you can use (faster) external CSS files e.g. <link rel=\"stylesheet\" href=\"<txp:page_url type=\"theme_path\" />/styles/default.css\"> -->\n\n    <meta name=\"generator\" content=\"Textpattern CMS\">\n    <meta name=\"robots\" content=\"noindex, follow\">\n</head>\n\n<body class=\"popup-page\">\n    <div class=\"wrapper\">\n        <div class=\"container\">\n\n            <!-- this form is only used if you set \'Comments mode\' to \'popup\' format in preferences -->\n            <txp:popup_comments />\n\n        </div> <!-- /.container -->\n    </div> <!-- /.wrapper -->\n</body>\n</html>', 'four-point-seven', NULL),
('comment_form', 'comment', '<p>\n    <txp:text item=\"enter_comment_here\" />\n</p>\n\n<!-- if there is an error, then inform user -->\n<txp:if_comments_error>\n    <txp:comments_error class=\"error_message\" wraptag=\"ol\" break=\"li\" />\n</txp:if_comments_error>\n\n<p class=\"large\">\n    <label for=\"name\">\n        <txp:text item=\"comment_name\" />\n        <b class=\"required\" title=\"<txp:text item=\"required\" />\">*</b>\n    </label>\n    <br>\n    <txp:comment_name_input size=\"32\" />\n</p>\n\n<p class=\"large\">\n    <label for=\"email\">\n        <txp:text item=\"comment_email\" />\n        <b class=\"required\" title=\"<txp:text item=\"required\" />\">*</b>\n    </label>\n    <br>\n    <txp:comment_email_input size=\"32\" />\n</p>\n\n<p class=\"large\">\n    <label for=\"web\">\n        <txp:text item=\"comment_web\" />\n    </label>\n    <br>\n    <txp:comment_web_input size=\"32\" />\n</p>\n\n<p class=\"small\">\n    <label for=\"message\">\n        <txp:text item=\"comment_message\" />\n        <b class=\"required\" title=\"<txp:text item=\"required\" />\">*</b>\n    </label>\n    <br>\n    <txp:comment_message_input cols=\"64\" rows=\"4\" />\n</p>\n\n<!-- preview and submit buttons (note: submit button will have a class of \'disabled\'\n     applied until you have previewed the message at least once) -->\n<p>\n    <txp:comment_preview />\n    <txp:comment_submit />\n</p>', 'four-point-seven', NULL),
('comments', 'comment', '<!-- load the comment email into a variable. you will be using this below along with author email variable loaded in form: default.article.txp\n     then check the comment email variable against article author email variable, and if it matches add \'comments-author\' class -->\n<txp:variable name=\"this_comment\" value=\'<txp:comment_email />\' />\n<txp:if_variable name=\"this_comment\" value=\'<txp:author_email />\'>\n    <article class=\"comments comments-author\" itemscope itemtype=\"https://schema.org/Comment\">\n<txp:else />\n    <article class=\"comments\" itemscope itemtype=\"https://schema.org/Comment\">\n</txp:if_variable>\n\n    <h4>\n        <span class=\"comment-author\" itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\">\n            <span itemprop=\"name\">\n                <txp:comment_name />\n            </span>\n        </span>\n\n        <!-- ...now check the comment email variable against article author email variable, and if it matches add \'(author)\' text -->\n        <txp:if_variable name=\"this_comment\" value=\'<txp:author_email />\'>\n            <span class=\"is-author\">\n                (<txp:text item=\"author\" />)\n            </span>\n        </txp:if_variable>\n\n        <!-- add a permlink so people can link direct to this comment -->\n        <span class=\"comment-anchor\" itemprop=\"url\">\n            <txp:comment_permlink>#</txp:comment_permlink>\n        </span>\n\n    </h4>\n\n    <!-- also add a \'since\' to show comment freshness -->\n    <p class=\"footnote\">\n        <time datetime=\"<txp:comment_time format=\"iso8601\" />\" itemprop=\"dateCreated\">\n            <txp:comment_time />\n            (<txp:comment_time format=\"since\" />)\n        </time>\n    </p>\n\n    <div itemprop=\"text\">\n        <txp:comment_message />\n    </div>\n\n</article>', 'four-point-seven', NULL),
('comments_display', 'comment', '<!-- if there are comments, display them (note: example code below overrides the global preference setting for comments wrapping by stating\n     attributes of wraptag=\"\" and break=\"\", you are instead using ol and li tags below)... -->\n<txp:if_comments>\n\n    <section id=\"comments-head\">\n        <h3>\n            <txp:text item=\"comments\" />\n        </h3>\n        <ol class=\"comments-list\">\n\n            <txp:comments wraptag=\"\" break=\"li\" /> <!-- links by default to form: \'comments.comment.txp\' unless you specify a different form -->\n\n            <!-- if this is a comment preview, display it (but only if there is no error) -->\n            <txp:if_comments_preview>\n                <li>\n                    <p id=\"cpreview\">\n                        <txp:text item=\"press_preview_then_submit\" />\n                    </p>\n\n                    <!-- links by default to form: \'comments.comment.txp\' unless you specify a different form -->\n                    <txp:comments_preview wraptag=\"\" />\n\n                </li>\n            </txp:if_comments_preview>\n\n        </ol>\n\n<!-- else if there are no comments yet and user is currently previewing comment, display it (but only if there is no error) -->\n<txp:else />\n\n    <txp:if_comments_preview>\n        <section id=\"comments-head\">\n            <h3>\n                <txp:text item=\"comments\" />\n            </h3>\n            <ol class=\"comments-list\">\n                <li>\n                    <p id=\"cpreview\">\n                        <txp:text item=\"press_preview_then_submit\" />\n                    </p>\n\n                    <!-- links by default to form: \'comments.comment.txp\' unless you specify a different form -->\n                    <txp:comments_preview wraptag=\"\" />\n\n                </li>\n            </ol>\n\n<!-- else just display that there are simply no comments whatsoever :( ...but only if comments are allowed -->\n    <txp:else />\n\n        <txp:if_comments_allowed>\n            <section id=\"comments-head\">\n                <h3>\n                    <txp:text item=\"comments\" />\n                </h3>\n                <p>\n                    <txp:text item=\"no_comments\" />\n                </p>\n\n        </txp:if_comments_allowed>\n    </txp:if_comments_preview>\n</txp:if_comments>\n\n<!-- next, if new comments are allowed for this article then display comment form -->\n<txp:if_comments_allowed>\n\n    <section id=\"comments-form\">\n\n        <!-- comment invite text is taken for the article\'s comment invitation field on the \'write\' screen -->\n        <h3>\n            <txp:comments_invite showcount=\"0\" textonly showalways />\n        </h3>\n\n        <!-- links by default to form: \'comment_form.comment.txp\' unless you specify a different form -->\n        <txp:comments_form />\n\n    </section>\n\n<!-- else display a comments expired message ...but only if comments had previously been allowed -->\n<txp:else />\n\n    <txp:if_comments>\n        <p>\n            <strong>\n                <txp:text item=\"comments_expired\" />\n            </strong>\n        </p>\n    </txp:if_comments>\n\n</txp:if_comments_allowed>\n\n<!-- if a comments section was rendered above, close the tag -->\n<txp:if_comments>\n    </section>\n<txp:else />\n    <txp:if_comments_allowed>\n        </section>\n    </txp:if_comments_allowed>\n</txp:if_comments>', 'four-point-seven', NULL),
('plainlinks', 'link', '<!-- This is being used as an external links form, therefore rel is set to \'external\' -->\n<txp:linkdesctitle rel=\"external\" />', 'four-point-seven', NULL),
('default', 'article', '<article class=\"article\" itemprop=\"blogPost\" itemscope itemtype=\"https://schema.org/BlogPosting\">\r\n    <meta itemprop=\"mainEntityOfPage\" content=\"<txp:permlink />\">\r\n\r\n    <txp:if_individual_article>\r\n        <h1 itemprop=\"headline\"><txp:title /></h1>\r\n    <txp:else />\r\n        <h1 itemprop=\"headline\"><a href=\"<txp:permlink />\" itemprop=\"url\" title=\"<txp:text item=\"read_more\" />\"><txp:title /></a>\r\n        </h1>\r\n\r\n    </txp:if_individual_article>\r\n\r\n    <p>\r\n        <strong>\r\n            <txp:text item=\"posted\" />\r\n        </strong>\r\n        <time datetime=\"<txp:posted format=\"iso8601\" />\" itemprop=\"datePublished\">\r\n            <txp:posted />\r\n        </time>\r\n        <ta itemprop=\"dateModified\" content=\"<txp:modified format=\"iso8601\" />\" />\r\n\r\n        <!-- only display comments count if comments posted, or if new comments allowed -->\r\n        <txp:if_comments>\r\n            <br>\r\n            <strong>\r\n                <txp:text item=\"comments\" />\r\n            </strong>\r\n            <a href=\"<txp:permlink />#comments-head\" title=\"<txp:text item=\"view\" />\" itemprop=\"discussionUrl\">\r\n                <span itemprop=\"interactionStatistic\" itemscope itemtype=\"https://schema.org/InteractionCounter\">\r\n                    <meta itemprop=\"interactionType\" content=\"https://schema.org/CommentAction\" />\r\n                    <span itemprop=\"userInteractionCount\"><txp:comments_count /></span>\r\n                </span>\r\n            </a>\r\n        <txp:else />\r\n            <txp:if_comments_allowed>\r\n                <br>\r\n                <strong>\r\n                    <txp:text item=\"comments\" />\r\n                </strong>\r\n                <a href=\"<txp:permlink />#comments-head\" title=\"<txp:text item=\"view\" />\" itemprop=\"discussionUrl\">\r\n                    <span itemprop=\"interactionStatistic\" itemscope itemtype=\"https://schema.org/InteractionCounter\">\r\n                        <meta itemprop=\"interactionType\" content=\"https://schema.org/CommentAction\" />\r\n                        <span itemprop=\"userInteractionCount\">0</span>\r\n                    </span>\r\n                </a>\r\n            </txp:if_comments_allowed>\r\n        </txp:if_comments>\r\n    </p>\r\n\r\n    <txp:if_article_image>\r\n        <txp:images form=\"images\" />\r\n    </txp:if_article_image>\r\n\r\n    <div itemprop=\"articleBody\">\r\n        <txp:body />\r\n    </div>\r\n\r\n    <p>\r\n        <strong>\r\n            <txp:text item=\"author\" />\r\n        </strong>\r\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\">\r\n            <span itemprop=\"name\">\r\n                <txp:author link this_section />\r\n            </span>\r\n        </span>\r\n\r\n        <!-- only display categories if they are actually set for an article, otherwise omit -->\r\n        <txp:if_article_category>\r\n            <br>\r\n            <strong>\r\n                <txp:text item=\"categories\" />\r\n            </strong>\r\n            <span itemprop=\"keywords\">\r\n                <txp:if_article_category number=\"1\">\r\n                    <txp:if_article_category number=\"2\">\r\n                        <txp:category1 title link />,\r\n                        <txp:category2 title link />\r\n                    <txp:else />\r\n                        <txp:category1 title link />\r\n                    </txp:if_article_category>\r\n                <txp:else />\r\n                    <txp:category2 title link />\r\n                </txp:if_article_category>\r\n            </span>\r\n        </txp:if_article_category>\r\n    </p>\r\n\r\n    <!-- if this is an individual article then add the comments section via form: comments_display.article.txp -->\r\n    <txp:if_individual_article>\r\n        <txp:output_form form=\"comments_display\" />\r\n    </txp:if_individual_article>\r\n\r\n</article>', 'four-point-seven', NULL),
('article_listing', 'article', '<li class=\"article\" itemprop=\"blogPost\" itemscope itemtype=\"https://schema.org/BlogPosting\">\n    <h4 itemprop=\"headline\">\n        <a href=\"<txp:permlink />\" itemprop=\"url mainEntityOfPage\" title=\"<txp:text item=\"read_more\" />\">\n            <txp:title />\n        </a>\n    </h4>\n\n    <!-- if the article has an excerpt, display that -->\n    <txp:if_excerpt>\n        <div itemprop=\"description\">\n            <txp:excerpt />\n        </div>\n    </txp:if_excerpt>\n\n    <p class=\"footnote\">\n        <txp:text item=\"posted\" />\n        <time datetime=\"<txp:posted format=\"iso8601\" />\" itemprop=\"datePublished\">\n            <txp:posted />\n        </time>\n        <meta itemprop=\"dateModified\" content=\"<txp:modified format=\"iso8601\" />\" />\n        &#183;\n        <txp:text item=\"author\" />\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\">\n            <span itemprop=\"name\">\n                <txp:author link this_section />\n            </span>\n        </span>\n    </p>\n</li>', 'four-point-seven', NULL),
('search_results', 'article', '<li class=\"article\" itemscope itemtype=\"https://schema.org/Article\">\n    <h4 itemprop=\"headline\">\n        <a href=\"<txp:permlink />\" itemprop=\"url mainEntityOfPage\" title=\"<txp:text item=\"read_more\" />\">\n            <txp:title />\n        </a>\n    </h4>\n\n    <!-- if the article has an excerpt, display that, otherwise show highlighted keywords in context of article -->\n    <txp:if_excerpt>\n        <div itemprop=\"description\">\n            <txp:excerpt />\n        </div>\n    <txp:else />\n        <p>\n            <txp:search_result_excerpt />\n        </p>\n    </txp:if_excerpt>\n\n    <p class=\"footnote\">\n        <txp:text item=\"posted\" />\n        <time datetime=\"<txp:posted format=\"iso8601\" />\" itemprop=\"datePublished\">\n            <txp:posted />\n        </time>\n        <meta itemprop=\"dateModified\" content=\"<txp:modified format=\"iso8601\" />\" />\n        &#183;\n        <txp:text item=\"author\" />\n        <span itemprop=\"author\" itemscope itemtype=\"https://schema.org/Person\">\n            <span itemprop=\"name\">\n                <txp:author link this_section />\n            </span>\n        </span>\n    </p>\n</li>', 'four-point-seven', NULL),
('rah_eo_cbe_helpful_ajax', 'misc', '; Content-type: text/html\r\n<txp:php>\r\n  extract( psa( array( \'article_id\', \'up_down\', \'unit\' ) ) );\r\n  $up = $down = 0 ;\r\n\r\n  if( $article_id !== \'\' && $up_down !== \'\' && isset( $_SERVER[\'REMOTE_ADDR\'] ) )\r\n  {\r\n    $$up_down = 1 ;\r\n    $ip = $_SERVER[\'REMOTE_ADDR\'] ;\r\n    $dvote = date( \'Y-m-d H:i:s\' ) ;\r\n\r\n    safe_insert( \'cbe_helpful_votes\', \"textpattern_id=\'$article_id\', plus=\'$up\', minus=\'$down\', ip=\'$ip\', dvote=\'$dvote\'\" ) ;\r\n    safe_upsert( \'cbe_helpful_counts\', \"count=count+$up-$down, pluses=pluses+$up, minuses=minuses+$down\", \"textpattern_id=\'$article_id\'\" ) ;\r\n\r\n    extract( safe_row( \'count, pluses, minuses\', \'cbe_helpful_counts\', \"textpattern_id=\'$article_id\'\" ) ) ;\r\n    if( $unit === \'simple\' )\r\n        $score = $count ;\r\n    else\r\n    {\r\n        $score = $pluses/($pluses+$minuses) ;\r\n        $score = ($unit === \'ratio\') ? sprintf( \"%4.2f\"   , round( $score, 2 ) )\r\n                                     : sprintf( \"%5.2f %%\", round( 100.0*$score, 2 ) ) ;\r\n    }\r\n\r\n    echo json_encode( array( \'plus\' => $up, \'minus\' => $down, \'score\' => $score ) ) ;\r\n  }\r\n</txp:php>', 'default', NULL),
('rah_eo_cbe_helpful_js', 'misc', '; Content-type: text/javascript\r\n$(document).ready(function(){function a(a,b){var c=a.parents(\".helpfulrater\");var d=a.parent(\".plus\").children(\".thecount\");var e=a.parent(\".minus\").children(\".thecount\");var f=c.find(\".hlp_votes\");unit=c.children(\".unit\").html();article_id=c.children(\".thisid\").html();pluses=d.html();minuses=e.html();votes=f.html();$.post(\"http://localhost/site1/?rah_external_output=cbe_helpful_ajax\",{article_id:article_id,up_down:b,unit:unit},function(g){d.html(parseInt(pluses)+g.plus);e.html(parseInt(minuses)+g.minus);c.find(\".value\").html(g.score);f.html(parseInt(votes)+1);a.children(\".distext\").remove();if(b==\"up\"){a.parent().siblings(\".minus\").find(\".distext\").remove()}else{a.parent().siblings(\".plus\").find(\".distext\").remove()}},\"json\")}$(\".helpfulplus\").click(function(){a($(this),\"up\");return false});$(\".helpfulminus\").click(function(){a($(this),\"down\");return false})})', 'default', NULL),
('rah_feed_body', 'article', '<txp:body />', 'default', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_image`
--

CREATE TABLE `txp_image` (
  `id` int(11) NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `category` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `ext` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `w` int(11) NOT NULL DEFAULT '0',
  `h` int(11) NOT NULL DEFAULT '0',
  `alt` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `caption` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `date` datetime NOT NULL,
  `author` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `thumbnail` int(11) NOT NULL DEFAULT '0',
  `thumb_w` int(11) NOT NULL DEFAULT '0',
  `thumb_h` int(11) NOT NULL DEFAULT '0',
  `jcr_image_custom` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_image`
--

INSERT INTO `txp_image` (`id`, `name`, `category`, `ext`, `w`, `h`, `alt`, `caption`, `date`, `author`, `thumbnail`, `thumb_w`, `thumb_h`, `jcr_image_custom`) VALUES
(1, 'Egyptian rule', '', '.png', 179, 231, 'Egyptian rule', '', '2019-07-25 04:31:46', 'marios', 1, 60, 60, NULL),
(2, 'abstract_0094.jpg', '', '.jpg', 1600, 1200, '', '', '2019-07-25 04:31:01', 'marios', 1, 60, 60, NULL),
(3, 'TXP Solutions', '', '.jpg', 406, 500, '', '', '2019-07-25 07:18:14', 'marios', 1, 60, 60, NULL),
(4, 'Dean Allen', '', '.jpg', 400, 300, '', '', '2019-07-25 13:12:06', 'marios', 1, 60, 60, NULL),
(5, '233.jpg', '', '.jpg', 800, 600, '', '', '2019-07-25 13:25:29', 'marios', 1, 60, 60, NULL),
(7, 'Ben Goertzel', '', '.jpg', 1030, 579, 'Singularitynet', '', '2019-07-25 13:47:17', 'marios', 1, 60, 60, NULL),
(8, 'In Ruins', '', '.jpg', 3872, 2592, 'In Ruins', '', '2019-07-25 14:01:00', 'marios', 1, 60, 60, NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_lang`
--

CREATE TABLE `txp_lang` (
  `id` int(11) NOT NULL,
  `lang` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `event` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `owner` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `data` text COLLATE utf8mb4_unicode_ci,
  `lastmod` timestamp NULL DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_lang`
--

INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(1, 'en', 'active', 'common', '', 'Active', '2019-07-23 19:58:53'),
(2, 'en', 'add', 'common', '', 'Add', '2019-07-23 19:58:53'),
(3, 'en', 'admin', 'common', '', 'Admin', '2019-07-23 19:58:53'),
(4, 'en', 'ago', 'common', '', '{since} ago', '2019-07-23 19:58:53'),
(5, 'en', 'all', 'common', '', 'All', '2019-07-23 19:58:53'),
(6, 'en', 'are_you_sure', 'common', '', 'Are you sure?', '2019-07-23 19:58:53'),
(7, 'en', 'article', 'common', '', 'Article', '2019-07-23 19:58:53'),
(8, 'en', 'articles', 'common', '', 'Articles', '2019-07-23 19:58:53'),
(9, 'en', 'article_image', 'common', '', 'Article image', '2019-07-23 19:58:53'),
(10, 'en', 'any', 'common', '', 'Any', '2019-07-23 19:58:53'),
(11, 'en', 'ascending', 'common', '', 'Ascending', '2019-07-23 19:58:53'),
(12, 'en', 'auth_required', 'common', '', 'Authorization required.', '2019-07-23 19:58:53'),
(13, 'en', 'author', 'common', '', 'Author', '2019-07-23 19:58:53'),
(14, 'en', 'authors', 'common', '', 'Authors', '2019-07-23 19:58:53'),
(15, 'en', 'back_to_login', 'common', '', 'Back to Textpattern login', '2019-07-23 19:58:53'),
(16, 'en', 'back_to_top', 'common', '', 'Back to top', '2019-07-23 19:58:53'),
(17, 'en', 'bad_cookie', 'common', '', 'Your session has expired. Please log in again.', '2019-07-23 19:58:53'),
(18, 'en', 'bad_login', 'common', '', 'Could not log in with that username/password.', '2019-07-23 19:58:53'),
(19, 'en', 'browse', 'common', '', 'Browse', '2019-07-23 19:58:53'),
(20, 'en', 'cancel', 'common', '', 'Cancel', '2019-07-23 19:58:53'),
(21, 'en', 'caption', 'common', '', 'Caption', '2019-07-23 19:58:53'),
(22, 'en', 'category', 'common', '', 'Category', '2019-07-23 19:58:53'),
(23, 'en', 'categories', 'common', '', 'Categories', '2019-07-23 19:58:53'),
(24, 'en', 'categorize', 'common', '', 'Categorize', '2019-07-23 19:58:53'),
(25, 'en', 'category1', 'common', '', 'Category 1', '2019-07-23 19:58:53'),
(26, 'en', 'category2', 'common', '', 'Category 2', '2019-07-23 19:58:53'),
(27, 'en', 'choose', 'common', '', 'Choose…', '2019-07-23 19:58:53'),
(28, 'en', 'close', 'common', '', 'Close', '2019-07-23 19:58:53'),
(29, 'en', 'code', 'common', '', 'Code', '2019-07-23 19:58:53'),
(30, 'en', 'comment', 'common', '', 'Comment', '2019-07-23 19:58:53'),
(31, 'en', 'comment_invitation', 'common', '', 'Invitation', '2019-07-23 19:58:53'),
(32, 'en', 'comments', 'common', '', 'Comments', '2019-07-23 19:58:53'),
(33, 'en', 'comments_expired', 'common', '', 'Commenting has expired for this article.', '2019-07-23 19:58:53'),
(34, 'en', 'confirm_delete_popup', 'common', '', 'Really delete?', '2019-07-23 19:58:53'),
(35, 'en', 'contact', 'common', '', 'Contact', '2019-07-23 19:58:53'),
(36, 'en', 'cookies_must_be_enabled', 'common', '', 'Browser cookies must be enabled to use Textpattern.', '2019-07-23 19:58:53'),
(37, 'en', 'copy', 'common', '', 'Copy', '2019-07-23 19:58:53'),
(38, 'en', 'could_not_log_in', 'common', '', 'Could not log in with that username/password.', '2019-07-23 19:58:53'),
(39, 'en', 'create', 'common', '', 'Create', '2019-07-23 19:58:53'),
(40, 'en', 'css', 'common', '', 'Style', '2019-07-23 19:58:53'),
(41, 'en', 'date', 'common', '', 'Date', '2019-07-23 19:58:53'),
(42, 'en', 'date_added', 'common', '', 'Date added', '2019-07-23 19:58:53'),
(43, 'en', 'dateformat', 'common', '', 'Date format', '2019-07-23 19:58:53'),
(44, 'en', 'default', 'common', '', 'Default', '2019-07-23 19:58:53'),
(45, 'en', 'delete', 'common', '', 'Delete', '2019-07-23 19:58:53'),
(46, 'en', 'delete_selected', 'common', '', 'Delete selected', '2019-07-23 19:58:53'),
(47, 'en', 'descending', 'common', '', 'Descending', '2019-07-23 19:58:53'),
(48, 'en', 'description', 'common', '', 'Description', '2019-07-23 19:58:53'),
(49, 'en', 'documentation', 'common', '', 'Documentation', '2019-07-23 19:58:53'),
(50, 'en', 'download', 'common', '', 'Download', '2019-07-23 19:58:53'),
(51, 'en', 'download_count', 'common', '', 'Download count', '2019-07-23 19:58:53'),
(52, 'en', 'downloads', 'common', '', 'Downloads', '2019-07-23 19:58:53'),
(53, 'en', 'duplicate', 'common', '', 'Duplicate', '2019-07-23 19:58:53'),
(54, 'en', 'edit', 'common', '', 'Edit', '2019-07-23 19:58:53'),
(55, 'en', 'email', 'common', '', 'Email', '2019-07-23 19:58:53'),
(56, 'en', 'email_address', 'common', '', 'Email address', '2019-07-23 19:58:53'),
(57, 'en', 'exact', 'common', '', 'Exact', '2019-07-23 19:58:53'),
(58, 'en', 'excerpt', 'common', '', 'Excerpt', '2019-07-23 19:58:53'),
(59, 'en', 'expired', 'common', '', 'Expired', '2019-07-23 19:58:53'),
(60, 'en', 'expires', 'common', '', 'Expires', '2019-07-23 19:58:53'),
(61, 'en', 'expire_date', 'common', '', 'Expire date', '2019-07-23 19:58:53'),
(62, 'en', 'expire_time', 'common', '', 'Expire time', '2019-07-23 19:58:53'),
(63, 'en', 'export', 'common', '', 'Export', '2019-07-23 19:58:53'),
(64, 'en', 'extension', 'common', '', 'Extension', '2019-07-23 19:58:53'),
(65, 'en', 'extensions', 'common', '', 'Extensions', '2019-07-23 19:58:53'),
(66, 'en', 'file', 'common', '', 'File', '2019-07-23 19:58:53'),
(67, 'en', 'file_download', 'common', '', 'File download', '2019-07-23 19:58:53'),
(68, 'en', 'file_size', 'common', '', 'File size', '2019-07-23 19:58:53'),
(69, 'en', 'forget', 'common', '', 'Forget', '2019-07-23 19:58:53'),
(70, 'en', 'form', 'common', '', 'Form', '2019-07-23 19:58:53'),
(71, 'en', 'form_submission_error', 'common', '', 'Sorry, the form could not be submitted. Please try again later.', '2019-07-23 19:58:53'),
(72, 'en', 'forms', 'common', '', 'Forms', '2019-07-23 19:58:53'),
(73, 'en', 'general_error', 'common', '', 'General error', '2019-07-23 19:58:53'),
(74, 'en', 'go', 'common', '', 'Go', '2019-07-23 19:58:53'),
(75, 'en', 'go_back', 'common', '', 'Go back', '2019-07-23 19:58:53'),
(76, 'en', 'go_content', 'common', '', 'Go to content', '2019-07-23 19:58:53'),
(77, 'en', 'go_nav', 'common', '', 'Go to navigation', '2019-07-23 19:58:53'),
(78, 'en', 'go_search', 'common', '', 'Go to search', '2019-07-23 19:58:53'),
(79, 'en', 'go_to', 'common', '', 'Go to', '2019-07-23 19:58:53'),
(80, 'en', 'go_txp_com', 'common', '', 'Go to the Textpattern website', '2019-07-23 19:58:53'),
(81, 'en', 'height', 'common', '', 'Height', '2019-07-23 19:58:53'),
(82, 'en', 'help', 'common', '', 'Help', '2019-07-23 19:58:53'),
(83, 'en', 'hidden', 'common', '', 'Hidden', '2019-07-23 19:58:53'),
(84, 'en', 'home', 'common', '', 'Home', '2019-07-23 19:58:53'),
(85, 'en', 'host', 'common', '', 'Host', '2019-07-23 19:58:53'),
(86, 'en', 'id', 'common', '', 'ID#', '2019-07-23 19:58:53'),
(87, 'en', 'image', 'common', '', 'Image', '2019-07-23 19:58:53'),
(88, 'en', 'images', 'common', '', 'Images', '2019-07-23 19:58:53'),
(89, 'en', 'import', 'common', '', 'Import', '2019-07-23 19:58:53'),
(90, 'en', 'input_day', 'common', '', 'Day', '2019-07-23 19:58:53'),
(91, 'en', 'input_hour', 'common', '', 'Hour', '2019-07-23 19:58:53'),
(92, 'en', 'input_minute', 'common', '', 'Minute', '2019-07-23 19:58:53'),
(93, 'en', 'input_month', 'common', '', 'Month', '2019-07-23 19:58:53'),
(94, 'en', 'input_second', 'common', '', 'Second', '2019-07-23 19:58:53'),
(95, 'en', 'input_year', 'common', '', 'Year', '2019-07-23 19:58:53'),
(96, 'en', 'install', 'common', '', 'Install', '2019-07-23 19:58:53'),
(97, 'en', 'internal_error', 'common', '', 'Internal error', '2019-07-23 19:58:53'),
(98, 'en', 'keywords', 'common', '', 'Keywords', '2019-07-23 19:58:53'),
(99, 'en', 'label', 'common', '', 'Label', '2019-07-23 19:58:53'),
(100, 'en', 'last_modification', 'common', '', 'Last modification', '2019-07-23 19:58:53'),
(101, 'en', 'last_modified', 'common', '', 'Last modified', '2019-07-23 19:58:53'),
(102, 'en', 'link', 'common', '', 'Link', '2019-07-23 19:58:53'),
(103, 'en', 'links', 'common', '', 'Links', '2019-07-23 19:58:53'),
(104, 'en', 'list', 'common', '', 'List', '2019-07-23 19:58:53'),
(105, 'en', 'list_articles', 'common', '', 'List articles', '2019-07-23 19:58:53'),
(106, 'en', 'list_categories', 'common', '', 'List categories', '2019-07-23 19:58:53'),
(107, 'en', 'list_links', 'common', '', 'List links', '2019-07-23 19:58:53'),
(108, 'en', 'list_options', 'common', '', 'List options', '2019-07-23 19:58:53'),
(109, 'en', 'logged_in_as', 'common', '', 'Logged in as', '2019-07-23 19:58:53'),
(110, 'en', 'login', 'common', '', 'Log in', '2019-07-23 19:58:53'),
(111, 'en', 'login_to_textpattern', 'common', '', 'Log in to Textpattern', '2019-07-23 19:58:53'),
(112, 'en', 'logout', 'common', '', 'Log out', '2019-07-23 19:58:53'),
(113, 'en', 'log_in_button', 'common', '', 'Log in', '2019-07-23 19:58:53'),
(114, 'en', 'manual', 'common', '', 'Manual', '2019-07-23 19:58:53'),
(115, 'en', 'message', 'common', '', 'Message', '2019-07-23 19:58:53'),
(116, 'en', 'method', 'common', '', 'Method', '2019-07-23 19:58:53'),
(117, 'en', 'misc', 'common', '', 'Miscellaneous', '2019-07-23 19:58:53'),
(118, 'en', 'modified', 'common', '', 'Modified', '2019-07-23 19:58:53'),
(119, 'en', 'modified_by', 'common', '', 'Last modified by', '2019-07-23 19:58:53'),
(120, 'en', 'month', 'common', '', 'Month', '2019-07-23 19:58:53'),
(121, 'en', 'more', 'common', '', 'More', '2019-07-23 19:58:53'),
(122, 'en', 'navigation', 'common', '', 'Navigation', '2019-07-23 19:58:53'),
(123, 'en', 'name', 'common', '', 'Name', '2019-07-23 19:58:53'),
(124, 'en', 'never', 'common', '', 'Never', '2019-07-23 19:58:53'),
(125, 'en', 'newer', 'common', '', 'Newer', '2019-07-23 19:58:53'),
(126, 'en', 'next', 'common', '', 'Next', '2019-07-23 19:58:53'),
(127, 'en', 'no', 'common', '', 'No', '2019-07-23 19:58:53'),
(128, 'en', 'no_results_found', 'common', '', 'No results found.', '2019-07-23 19:58:53'),
(129, 'en', 'none', 'common', '', 'None', '2019-07-23 19:58:53'),
(130, 'en', 'of', 'common', '', 'of', '2019-07-23 19:58:53'),
(131, 'en', 'off', 'common', '', 'Off', '2019-07-23 19:58:53'),
(132, 'en', 'older', 'common', '', 'Older', '2019-07-23 19:58:53'),
(133, 'en', 'on', 'common', '', 'On', '2019-07-23 19:58:53'),
(134, 'en', 'opens_external_link', 'common', '', '(opens an external link in a new window)', '2019-07-23 19:58:53'),
(135, 'en', 'options', 'common', '', 'Options', '2019-07-23 19:58:53'),
(136, 'en', 'page', 'common', '', 'Page', '2019-07-23 19:58:53'),
(137, 'en', 'pages', 'common', '', 'Pages', '2019-07-23 19:58:53'),
(138, 'en', 'password', 'common', '', 'Password', '2019-07-23 19:58:53'),
(139, 'en', 'password_forgotten', 'common', '', 'Forgot password?', '2019-07-23 19:58:53'),
(140, 'en', 'permlink', 'common', '', 'Permanent link', '2019-07-23 19:58:53'),
(141, 'en', 'pophelp', 'common', '', 'Pop-up help links', '2019-07-23 19:58:53'),
(142, 'en', 'post', 'common', '', 'Post', '2019-07-23 19:58:53'),
(143, 'en', 'posted', 'common', '', 'Posted', '2019-07-23 19:58:53'),
(144, 'en', 'posted_by', 'common', '', 'Posted by', '2019-07-23 19:58:53'),
(145, 'en', 'prev', 'common', '', 'Previous', '2019-07-23 19:58:53'),
(146, 'en', 'preview', 'common', '', 'Preview', '2019-07-23 19:58:53'),
(147, 'en', 'publish', 'common', '', 'Publish', '2019-07-23 19:58:53'),
(148, 'en', 'published_at', 'common', '', 'Published at', '2019-07-23 19:58:53'),
(149, 'en', 'published_with', 'common', '', 'Published with', '2019-07-23 19:58:53'),
(150, 'en', 'publish_date', 'common', '', 'Publish date', '2019-07-23 19:58:53'),
(151, 'en', 'publish_time', 'common', '', 'Publish time', '2019-07-23 19:58:53'),
(152, 'en', 'range', 'common', '', 'Range', '2019-07-23 19:58:53'),
(153, 'en', 'recently', 'common', '', 'Recently', '2019-07-23 19:58:53'),
(154, 'en', 'recent_articles', 'common', '', 'Recent articles', '2019-07-23 19:58:53'),
(155, 'en', 'recent_comments', 'common', '', 'Recent comments', '2019-07-23 19:58:53'),
(156, 'en', 'recent_posts', 'common', '', 'Recent posts', '2019-07-23 19:58:53'),
(157, 'en', 'related_articles', 'common', '', 'Related articles', '2019-07-23 19:58:53'),
(158, 'en', 'reload', 'common', '', 'Reload', '2019-07-23 19:58:53'),
(159, 'en', 'remember', 'common', '', 'Remember', '2019-07-23 19:58:53'),
(160, 'en', 'remove', 'common', '', 'Remove', '2019-07-23 19:58:53'),
(161, 'en', 'required', 'common', '', 'Required', '2019-07-23 19:58:53'),
(162, 'en', 'reset', 'common', '', 'Reset', '2019-07-23 19:58:53'),
(163, 'en', 'restricted_area', 'common', '', 'Restricted area.', '2019-07-23 19:58:53'),
(164, 'en', 'revert', 'common', '', 'Revert', '2019-07-23 19:58:53'),
(165, 'en', 'salutation', 'common', '', 'Dear {name},', '2019-07-23 19:58:53'),
(166, 'en', 'save', 'common', '', 'Save', '2019-07-23 19:58:53'),
(167, 'en', 'save_new', 'common', '', 'Save new', '2019-07-23 19:58:53'),
(168, 'en', 'search', 'common', '', 'Search', '2019-07-23 19:58:53'),
(169, 'en', 'search_results', 'common', '', 'Search results', '2019-07-23 19:58:53'),
(170, 'en', 'section', 'common', '', 'Section', '2019-07-23 19:58:53'),
(171, 'en', 'sections', 'common', '', 'Sections', '2019-07-23 19:58:53'),
(172, 'en', 'select', 'common', '', 'Select', '2019-07-23 19:58:53'),
(173, 'en', 'selected', 'common', '', 'Selected', '2019-07-23 19:58:53'),
(174, 'en', 'site', 'common', '', 'Site', '2019-07-23 19:58:53'),
(175, 'en', 'sitename', 'common', '', 'Site name', '2019-07-23 19:58:53'),
(176, 'en', 'siteurl', 'common', '', 'Site URL', '2019-07-23 19:58:53'),
(177, 'en', 'site_slogan', 'common', '', 'Site slogan', '2019-07-23 19:58:53'),
(178, 'en', 'spam', 'common', '', 'Spam', '2019-07-23 19:58:53'),
(179, 'en', 'status', 'common', '', 'Status', '2019-07-23 19:58:53'),
(180, 'en', 'stay_logged_in', 'common', '', 'Remain logged in with this browser', '2019-07-23 19:58:53'),
(181, 'en', 'submit', 'common', '', 'Submit', '2019-07-23 19:58:53'),
(182, 'en', 'thumbnail', 'common', '', 'Thumbnail', '2019-07-23 19:58:53'),
(183, 'en', 'time', 'common', '', 'Time', '2019-07-23 19:58:53'),
(184, 'en', 'title', 'common', '', 'Title', '2019-07-23 19:58:53'),
(185, 'en', 'toggle_all_selected', 'common', '', 'Toggle all/none selected', '2019-07-23 19:58:53'),
(186, 'en', 'txt_quote_double_close', 'common', '', '”', '2019-07-23 19:58:53'),
(187, 'en', 'txt_quote_double_open', 'common', '', '“', '2019-07-23 19:58:53'),
(188, 'en', 'txt_quote_single_close', 'common', '', '’', '2019-07-23 19:58:53'),
(189, 'en', 'txt_quote_single_open', 'common', '', '‘', '2019-07-23 19:58:53'),
(190, 'en', 'type', 'common', '', 'Type', '2019-07-23 19:58:53'),
(191, 'en', 'undefined', 'common', '', 'Undefined', '2019-07-23 19:58:53'),
(192, 'en', 'unknown', 'common', '', 'Unknown', '2019-07-23 19:58:53'),
(193, 'en', 'units_b', 'common', '', 'B', '2019-07-23 19:58:53'),
(194, 'en', 'units_e', 'common', '', 'EB', '2019-07-23 19:58:53'),
(195, 'en', 'units_g', 'common', '', 'GB', '2019-07-23 19:58:53'),
(196, 'en', 'units_k', 'common', '', 'kB', '2019-07-23 19:58:53'),
(197, 'en', 'units_m', 'common', '', 'MB', '2019-07-23 19:58:53'),
(198, 'en', 'units_p', 'common', '', 'PB', '2019-07-23 19:58:53'),
(199, 'en', 'units_t', 'common', '', 'TB', '2019-07-23 19:58:53'),
(200, 'en', 'units_y', 'common', '', 'YB', '2019-07-23 19:58:53'),
(201, 'en', 'units_z', 'common', '', 'ZB', '2019-07-23 19:58:53'),
(202, 'en', 'untitled', 'common', '', 'Untitled', '2019-07-23 19:58:53'),
(203, 'en', 'update', 'common', '', 'Update', '2019-07-23 19:58:53'),
(204, 'en', 'upload', 'common', '', 'Upload', '2019-07-23 19:58:53'),
(205, 'en', 'upload_err_cant_write', 'common', '', 'Failed to write file to disk.', '2019-07-23 19:58:53'),
(206, 'en', 'upload_err_extension', 'common', '', 'File upload stopped by PHP extension.', '2019-07-23 19:58:53'),
(207, 'en', 'upload_err_form_size', 'common', '', 'File exceeds the maximum size specified in Textpattern’s preferences.', '2019-07-23 19:58:53'),
(208, 'en', 'upload_err_ini_size', 'common', '', 'File exceeds the <code>upload_max_filesize</code> directive in <code>php.ini</code>.', '2019-07-23 19:58:53'),
(209, 'en', 'upload_err_no_file', 'common', '', 'No file was specified.', '2019-07-23 19:58:53'),
(210, 'en', 'upload_err_partial', 'common', '', 'File was only partially uploaded.', '2019-07-23 19:58:53'),
(211, 'en', 'upload_err_tmp_dir', 'common', '', 'No valid temporary directory was found. Please consult your website host.', '2019-07-23 19:58:53'),
(212, 'en', 'upload_file', 'common', '', 'Upload file', '2019-07-23 19:58:53'),
(213, 'en', 'url', 'common', '', 'URL', '2019-07-23 19:58:53'),
(214, 'en', 'value', 'common', '', 'Value', '2019-07-23 19:58:53'),
(215, 'en', 'version', 'common', '', 'Version', '2019-07-23 19:58:53'),
(216, 'en', 'view', 'common', '', 'View', '2019-07-23 19:58:53'),
(217, 'en', 'viewsite', 'common', '', 'View site', '2019-07-23 19:58:53'),
(218, 'en', 'website', 'common', '', 'Website', '2019-07-23 19:58:53'),
(219, 'en', 'width', 'common', '', 'Width', '2019-07-23 19:58:53'),
(220, 'en', 'with_selected', 'common', '', 'With selected:', '2019-07-23 19:58:53'),
(221, 'en', 'with_selected_option', 'common', '', 'With {count} selected…', '2019-07-23 19:58:53'),
(222, 'en', 'yes', 'common', '', 'Yes', '2019-07-23 19:58:53'),
(223, 'en', 'lang_name', 'common', '', 'English', '2019-07-23 19:58:53'),
(224, 'en', 'lang_code', 'common', '', 'en', '2019-07-23 19:58:53'),
(225, 'en', 'lang_dir', 'common', '', 'ltr', '2019-07-23 19:58:53'),
(226, 'en', 'account_activation', 'admin', '', 'Please activate your account', '2019-07-23 19:58:53'),
(227, 'en', 'account_activation_confirmation', 'admin', '', 'Please use the link below to activate your account and set up a password.', '2019-07-23 19:58:53'),
(228, 'en', 'alias_is_taken', 'admin', '', 'Alias already exists.', '2019-07-23 19:58:53'),
(229, 'en', 'assign_assets_to', 'admin', '', 'Assign author’s content to', '2019-07-23 19:58:53'),
(230, 'en', 'author_already_exists', 'admin', '', 'Author <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(231, 'en', 'author_deleted', 'admin', '', 'Authors deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(232, 'en', 'author_save_failed', 'admin', '', 'Author <strong>{name}</strong> could not be saved.', '2019-07-23 19:58:53'),
(233, 'en', 'author_updated', 'admin', '', 'Authors updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(234, 'en', 'cannot_assign_assets_to_deletee', 'admin', '', 'You cannot assign assets to a deleted account.', '2019-07-23 19:58:53'),
(235, 'en', 'changeprivilege', 'admin', '', 'Change role', '2019-07-23 19:58:53'),
(236, 'en', 'change_email_address', 'admin', '', 'Change your email address', '2019-07-23 19:58:53'),
(237, 'en', 'change_password', 'admin', '', 'Change your password', '2019-07-23 19:58:53'),
(238, 'en', 'copy_editor', 'admin', '', 'Copy Editor', '2019-07-23 19:58:53'),
(239, 'en', 'could_not_mail', 'admin', '', 'Could not email', '2019-07-23 19:58:53'),
(240, 'en', 'could_not_update_author', 'admin', '', 'Could not update author.', '2019-07-23 19:58:53'),
(241, 'en', 'create_author', 'admin', '', 'New author', '2019-07-23 19:58:53'),
(242, 'en', 'current_password', 'admin', '', 'Current password', '2019-07-23 19:58:53'),
(243, 'en', 'designer', 'admin', '', 'Designer', '2019-07-23 19:58:53'),
(244, 'en', 'edit_author', 'admin', '', 'Edit author', '2019-07-23 19:58:53'),
(245, 'en', 'email_changed', 'admin', '', 'Email address changed to <strong>{email}</strong>.', '2019-07-23 19:58:53'),
(246, 'en', 'error_adding_new_author', 'admin', '', 'Error adding new author.', '2019-07-23 19:58:53'),
(247, 'en', 'from_or_to_address_missing', 'admin', '', 'Sender and/or recipient address is missing.', '2019-07-23 19:58:53'),
(248, 'en', 'freelancer', 'admin', '', 'Freelancer', '2019-07-23 19:58:53'),
(249, 'en', 'invalid_header', 'admin', '', 'Invalid mail header.', '2019-07-23 19:58:53'),
(250, 'en', 'invalid_token', 'admin', '', 'Password reset security token is invalid.', '2019-07-23 19:58:53'),
(251, 'en', 'last_login', 'admin', '', 'Last log in', '2019-07-23 19:58:53'),
(252, 'en', 'link_expires', 'admin', '', 'You have until {day} {month} {year}, {time} to respond before this link expires.', '2019-07-23 19:58:53'),
(253, 'en', 'login_name', 'admin', '', 'Login', '2019-07-23 19:58:53'),
(254, 'en', 'login_sent_to', 'admin', '', 'Password sent to {email}.', '2019-07-23 19:58:53'),
(255, 'en', 'log_in_at', 'admin', '', 'Log in at', '2019-07-23 19:58:53'),
(256, 'en', 'managing_editor', 'admin', '', 'Managing Editor', '2019-07-23 19:58:53'),
(257, 'en', 'must_reassign_assets', 'admin', '', 'Please reassign author’s content', '2019-07-23 19:58:53'),
(258, 'en', 'new_email', 'admin', '', 'New email', '2019-07-23 19:58:53'),
(259, 'en', 'new_password', 'admin', '', 'New password', '2019-07-23 19:58:53'),
(260, 'en', 'password_changed', 'admin', '', 'Password changed.', '2019-07-23 19:58:53'),
(261, 'en', 'password_change_confirmation', 'admin', '', 'Your password has been changed. If you did not request a change to your password, contact your site administrator.', '2019-07-23 19:58:53'),
(262, 'en', 'password_confirm_button', 'admin', '', 'Set password', '2019-07-23 19:58:53'),
(263, 'en', 'password_invalid', 'admin', '', 'Your password is incorrect.', '2019-07-23 19:58:53'),
(264, 'en', 'password_required', 'admin', '', 'Please provide a new password.', '2019-07-23 19:58:53'),
(265, 'en', 'password_reset', 'admin', '', 'Reset password', '2019-07-23 19:58:53'),
(266, 'en', 'password_reset_button', 'admin', '', 'Reset password', '2019-07-23 19:58:53'),
(267, 'en', 'password_reset_confirmation', 'admin', '', 'Somebody (probably you) has requested to reset your password. Please confirm this reset request using the link below.', '2019-07-23 19:58:53'),
(268, 'en', 'password_reset_confirmation_request', 'admin', '', 'Please confirm your password reset request', '2019-07-23 19:58:53'),
(269, 'en', 'password_reset_confirmation_request_sent', 'admin', '', 'A confirmation message was sent. Please check your email and follow the instructions.', '2019-07-23 19:58:53'),
(270, 'en', 'password_sent_to', 'admin', '', 'Password sent to', '2019-07-23 19:58:53'),
(271, 'en', 'password_set', 'admin', '', 'Your password has been set', '2019-07-23 19:58:53'),
(272, 'en', 'password_set_confirmation', 'admin', '', 'Your password has been set. If you did not request this, contact your site administrator.', '2019-07-23 19:58:53'),
(273, 'en', 'privileges', 'admin', '', 'Role', '2019-07-23 19:58:53'),
(274, 'en', 'privs_none', 'admin', '', 'None', '2019-07-23 19:58:53'),
(275, 'en', 'publisher', 'admin', '', 'Publisher', '2019-07-23 19:58:53'),
(276, 'en', 'real_name', 'admin', '', 'Real name', '2019-07-23 19:58:53'),
(277, 'en', 'resend_activation', 'admin', '', 'Resend activation link', '2019-07-23 19:58:53'),
(278, 'en', 'resend_activation_request_sent', 'admin', '', 'Activation message sent to {name}.', '2019-07-23 19:58:53'),
(279, 'en', 'resetpassword', 'admin', '', 'Reset password', '2019-07-23 19:58:53'),
(280, 'en', 'search_users', 'admin', '', 'Search users', '2019-07-23 19:58:53'),
(281, 'en', 'sending_failed', 'admin', '', 'The message failed to send.', '2019-07-23 19:58:53'),
(282, 'en', 'set_password', 'admin', '', 'Set your password', '2019-07-23 19:58:53'),
(283, 'en', 'show_password', 'admin', '', 'Show password', '2019-07-23 19:58:53'),
(284, 'en', 'staff_writer', 'admin', '', 'Staff Writer', '2019-07-23 19:58:53'),
(285, 'en', 'token_expired', 'admin', '', 'Password reset security token has expired.', '2019-07-23 19:58:53'),
(286, 'en', 'unable_retrieve_template', 'admin', '', 'Unable to retrieve template.', '2019-07-23 19:58:53'),
(287, 'en', 'unable_retrieve_user', 'admin', '', 'Unable to retrieve user.', '2019-07-23 19:58:53'),
(288, 'en', 'unable_set_template', 'admin', '', 'Unable to set template.', '2019-07-23 19:58:53'),
(289, 'en', 'your_login_info', 'admin', '', 'Your login info', '2019-07-23 19:58:53'),
(290, 'en', 'your_login_is', 'admin', '', 'Your login is', '2019-07-23 19:58:53'),
(291, 'en', 'your_new_password', 'admin', '', 'Your new password', '2019-07-23 19:58:53'),
(292, 'en', 'your_password_is', 'admin', '', 'Your password is', '2019-07-23 19:58:53'),
(293, 'en', 'you_have_been_registered', 'admin', '', 'You have been registered as a contributor to the site', '2019-07-23 19:58:53'),
(294, 'en', 'advanced_options', 'admin-side', '', 'Advanced options', '2019-07-23 19:58:53'),
(295, 'en', 'auto_dst', 'admin-side', '', 'Automatically adjust Daylight Saving Time?', '2019-07-23 19:58:53'),
(296, 'en', 'body', 'admin-side', '', 'Body', '2019-07-23 19:58:53'),
(297, 'en', 'cannot_delete', 'admin-side', '', 'Cannot delete {thing}.', '2019-07-23 19:58:53'),
(298, 'en', 'cannot_instantiate_theme', 'admin-side', '', 'Theme <strong>{name}</strong> ({class}) failed to load properly from file {path}. The default theme will be used as a fallback when you refresh this page.', '2019-07-23 19:58:53'),
(299, 'en', 'changeauthor', 'admin-side', '', 'Change author', '2019-07-23 19:58:53'),
(300, 'en', 'changecategory', 'admin-side', '', 'Change category', '2019-07-23 19:58:53'),
(301, 'en', 'changecategory1', 'admin-side', '', 'Change Category 1', '2019-07-23 19:58:53'),
(302, 'en', 'changecategory2', 'admin-side', '', 'Change Category 2', '2019-07-23 19:58:53'),
(303, 'en', 'changecomments', 'admin-side', '', 'Change comments', '2019-07-23 19:58:53'),
(304, 'en', 'changeparent', 'admin-side', '', 'Change parent', '2019-07-23 19:58:53'),
(305, 'en', 'changesection', 'admin-side', '', 'Change section', '2019-07-23 19:58:53'),
(306, 'en', 'changestatus', 'admin-side', '', 'Change status', '2019-07-23 19:58:53'),
(307, 'en', 'collapse_all', 'admin-side', '', 'Collapse all', '2019-07-23 19:58:53'),
(308, 'en', 'convert_linebreaks', 'admin-side', '', 'Convert line breaks', '2019-07-23 19:58:53'),
(309, 'en', 'create_article', 'admin-side', '', 'New article', '2019-07-23 19:58:53'),
(310, 'en', 'custom', 'admin-side', '', 'Custom fields', '2019-07-23 19:58:53'),
(311, 'en', 'date_settings', 'admin-side', '', 'Date and time', '2019-07-23 19:58:53'),
(312, 'en', 'deleteforce', 'admin-side', '', 'Force delete', '2019-07-23 19:58:53'),
(313, 'en', 'detail', 'admin-side', '', 'Detail', '2019-07-23 19:58:53'),
(314, 'en', 'directory_permissions', 'admin-side', '', 'Check directory permissions: <strong>{path}</strong>.', '2019-07-23 19:58:53'),
(315, 'en', 'draft', 'admin-side', '', 'Draft', '2019-07-23 19:58:53'),
(316, 'en', 'expand_all', 'admin-side', '', 'Expand all', '2019-07-23 19:58:53'),
(317, 'en', 'export_to_disk', 'admin-side', '', 'Export to disk', '2019-07-23 19:58:53'),
(318, 'en', 'file_base_path', 'admin-side', '', 'File directory path', '2019-07-23 19:58:53'),
(319, 'en', 'get_off_my_lawn', 'admin-side', '', 'I’m sorry. I’m afraid I can’t do that; <code>{event}</code> <code>{step}</code> is an unsafe operation.', '2019-07-23 19:58:53'),
(320, 'en', 'gmtoffset', 'admin-side', '', 'Time zone (GMT offset in seconds)', '2019-07-23 19:58:53'),
(321, 'en', 'img_dir', 'admin-side', '', 'Image directory', '2019-07-23 19:58:53'),
(322, 'en', 'import_from_disk', 'admin-side', '', 'Import from disk', '2019-07-23 19:58:53'),
(323, 'en', 'invalid_argument', 'admin-side', '', 'Invalid argument', '2019-07-23 19:58:53'),
(324, 'en', 'invalid_article_id', 'admin-side', '', 'Invalid article ID', '2019-07-23 19:58:53'),
(325, 'en', 'invalid_expiredate', 'admin-side', '', 'Invalid expire date or time.', '2019-07-23 19:58:53'),
(326, 'en', 'invalid_json', 'admin-side', '', 'Invalid JSON files: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(327, 'en', 'invalid_postdate', 'admin-side', '', 'Invalid publish date or time.', '2019-07-23 19:58:53'),
(328, 'en', 'is_dst', 'admin-side', '', 'Daylight Saving Time enabled?', '2019-07-23 19:58:53'),
(329, 'en', 'leave_text_untouched', 'admin-side', '', 'Leave text untouched', '2019-07-23 19:58:53'),
(330, 'en', 'live', 'admin-side', '', 'Live', '2019-07-23 19:58:53'),
(331, 'en', 'locale', 'admin-side', '', 'Locale', '2019-07-23 19:58:53'),
(332, 'en', 'manage', 'admin-side', '', 'Manage', '2019-07-23 19:58:53'),
(333, 'en', 'meta', 'admin-side', '', 'Meta', '2019-07-23 19:58:53'),
(334, 'en', 'on_front_page', 'admin-side', '', 'On default page?', '2019-07-23 19:58:53'),
(335, 'en', 'override', 'admin-side', '', 'Override', '2019-07-23 19:58:53'),
(336, 'en', 'parent', 'admin-side', '', 'Parent', '2019-07-23 19:58:53'),
(337, 'en', 'path_creation_or_writing_failed', 'admin-side', '', 'Unable to create or write in files/directories: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(338, 'en', 'path_not_readable', 'admin-side', '', '<strong>Warning:</strong> cannot read from directories/files <code>{list}</code>.', '2019-07-23 19:58:53'),
(339, 'en', 'path_not_writable', 'admin-side', '', '<strong>Warning:</strong> cannot write to directories/files <code>{list}</code>.', '2019-07-23 19:58:53'),
(340, 'en', 'path_renaming_failed', 'admin-side', '', 'Files or directories renaming failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(341, 'en', 'pending', 'admin-side', '', 'Pending', '2019-07-23 19:58:53'),
(342, 'en', 'permlink_mode', 'admin-side', '', 'Article URL pattern', '2019-07-23 19:58:53'),
(343, 'en', 'preferences_saved', 'admin-side', '', 'Preferences saved', '2019-07-23 19:58:53'),
(344, 'en', 'reset_time', 'admin-side', '', 'Reset timestamp to now', '2019-07-23 19:58:53'),
(345, 'en', 'search_all', 'admin-side', '', 'Search all', '2019-07-23 19:58:53'),
(346, 'en', 'search_clear', 'admin-side', '', 'Clear search', '2019-07-23 19:58:53'),
(347, 'en', 'search_options', 'admin-side', '', 'Search options', '2019-07-23 19:58:53'),
(348, 'en', 'set_expire_now', 'admin-side', '', 'Set expiry to now', '2019-07-23 19:58:53'),
(349, 'en', 'set_to_now', 'admin-side', '', 'Set timestamp to now', '2019-07-23 19:58:53'),
(350, 'en', 'show', 'admin-side', '', 'Show', '2019-07-23 19:58:53'),
(351, 'en', 'showing_search_results', 'admin-side', '', 'Showing {from} to {to} of {total}', '2019-07-23 19:58:53'),
(352, 'en', 'skin', 'admin-side', '', 'Theme', '2019-07-23 19:58:53'),
(353, 'en', 'skin_dir', 'admin-side', '', 'Theme directory', '2019-07-23 19:58:53'),
(354, 'en', 'sort_value', 'admin-side', '', 'Sort value', '2019-07-23 19:58:53'),
(355, 'en', 'sticky', 'admin-side', '', 'Sticky', '2019-07-23 19:58:53'),
(356, 'en', 'swap_values', 'admin-side', '', 'Swap values', '2019-07-23 19:58:53'),
(357, 'en', 'tab_admin', 'admin-side', '', 'Admin', '2019-07-23 19:58:53'),
(358, 'en', 'tab_comments', 'admin-side', '', 'Comments', '2019-07-23 19:58:53'),
(359, 'en', 'tab_content', 'admin-side', '', 'Content', '2019-07-23 19:58:53'),
(360, 'en', 'tab_diagnostics', 'admin-side', '', 'Diagnostics', '2019-07-23 19:58:53'),
(361, 'en', 'tab_extensions', 'admin-side', '', 'Extensions', '2019-07-23 19:58:53'),
(362, 'en', 'tab_file', 'admin-side', '', 'Files', '2019-07-23 19:58:53'),
(363, 'en', 'tab_forms', 'admin-side', '', 'Forms', '2019-07-23 19:58:53'),
(364, 'en', 'tab_help', 'admin-side', '', 'Help', '2019-07-23 19:58:53'),
(365, 'en', 'tab_image', 'admin-side', '', 'Images', '2019-07-23 19:58:53'),
(366, 'en', 'tab_import', 'admin-side', '', 'Import', '2019-07-23 19:58:53'),
(367, 'en', 'tab_languages', 'admin-side', '', 'Languages', '2019-07-23 19:58:53'),
(368, 'en', 'tab_link', 'admin-side', '', 'Links', '2019-07-23 19:58:53'),
(369, 'en', 'tab_list', 'admin-side', '', 'Articles', '2019-07-23 19:58:53'),
(370, 'en', 'tab_logs', 'admin-side', '', 'Visitor logs', '2019-07-23 19:58:53'),
(371, 'en', 'tab_organise', 'admin-side', '', 'Categories', '2019-07-23 19:58:53'),
(372, 'en', 'tab_pages', 'admin-side', '', 'Pages', '2019-07-23 19:58:53'),
(373, 'en', 'tab_plugins', 'admin-side', '', 'Plugins', '2019-07-23 19:58:53'),
(374, 'en', 'tab_preferences', 'admin-side', '', 'Preferences', '2019-07-23 19:58:53'),
(375, 'en', 'tab_presentation', 'admin-side', '', 'Presentation', '2019-07-23 19:58:53'),
(376, 'en', 'tab_sections', 'admin-side', '', 'Sections', '2019-07-23 19:58:53'),
(377, 'en', 'tab_site_account', 'admin-side', '', 'Account', '2019-07-23 19:58:53'),
(378, 'en', 'tab_site_admin', 'admin-side', '', 'Users', '2019-07-23 19:58:53'),
(379, 'en', 'tab_skin', 'admin-side', '', 'Themes', '2019-07-23 19:58:53'),
(380, 'en', 'tab_start', 'admin-side', '', 'Home', '2019-07-23 19:58:53'),
(381, 'en', 'tab_style', 'admin-side', '', 'Styles', '2019-07-23 19:58:53'),
(382, 'en', 'tab_view_site', 'admin-side', '', 'View site', '2019-07-23 19:58:53'),
(383, 'en', 'tab_write', 'admin-side', '', 'Write', '2019-07-23 19:58:53'),
(384, 'en', 'tagbuilder', 'admin-side', '', 'Tag builder', '2019-07-23 19:58:53'),
(385, 'en', 'tags', 'admin-side', '', 'Tags', '2019-07-23 19:58:53'),
(386, 'en', 'tempdir', 'admin-side', '', 'Temporary directory path', '2019-07-23 19:58:53'),
(387, 'en', 'textfilter', 'admin-side', '', 'Format:', '2019-07-23 19:58:53'),
(388, 'en', 'textfilter_help', 'admin-side', '', 'Text formatting help', '2019-07-23 19:58:53'),
(389, 'en', 'theme_name', 'admin-side', '', 'Admin-side theme', '2019-07-23 19:58:53'),
(390, 'en', 'timestamp', 'admin-side', '', 'Timestamp', '2019-07-23 19:58:53'),
(391, 'en', 'title_body', 'admin-side', '', 'Title and body', '2019-07-23 19:58:53'),
(392, 'en', 'title_body_excerpt', 'admin-side', '', 'Title, body and excerpt', '2019-07-23 19:58:53'),
(393, 'en', 'unmoderated', 'admin-side', '', 'Unmoderated', '2019-07-23 19:58:53'),
(394, 'en', 'update_from_disk', 'admin-side', '', 'Update from disk', '2019-07-23 19:58:53'),
(395, 'en', 'use_textile', 'admin-side', '', 'Use Textile', '2019-07-23 19:58:53'),
(396, 'en', 'view_html', 'admin-side', '', 'View HTML markup', '2019-07-23 19:58:53'),
(397, 'en', 'view_html_short', 'admin-side', '', 'HTML', '2019-07-23 19:58:53'),
(398, 'en', 'view_per_page', 'admin-side', '', 'View {page} per page', '2019-07-23 19:58:53'),
(399, 'en', 'view_preview', 'admin-side', '', 'Preview article', '2019-07-23 19:58:53'),
(400, 'en', 'view_preview_short', 'admin-side', '', 'Preview', '2019-07-23 19:58:53'),
(401, 'en', 'view_text', 'admin-side', '', 'Edit article text', '2019-07-23 19:58:53'),
(402, 'en', 'view_text_short', 'admin-side', '', 'Text', '2019-07-23 19:58:53'),
(403, 'en', 'visible', 'admin-side', '', 'Visible', '2019-07-23 19:58:53'),
(404, 'en', 'article_expires_before_postdate', 'article', '', 'Error: article expires <strong>before</strong> publish date.', '2019-07-23 19:58:53'),
(405, 'en', 'article_markup', 'article', '', 'Body markup', '2019-07-23 19:58:53'),
(406, 'en', 'article_posted', 'article', '', 'Article posted.', '2019-07-23 19:58:53'),
(407, 'en', 'article_saved', 'article', '', 'Article saved.', '2019-07-23 19:58:53'),
(408, 'en', 'article_saved_draft', 'article', '', 'Article saved as draft.', '2019-07-23 19:58:53'),
(409, 'en', 'article_saved_hidden', 'article', '', 'Article saved as hidden.', '2019-07-23 19:58:53'),
(410, 'en', 'article_saved_pending', 'article', '', 'Article saved as pending.', '2019-07-23 19:58:53'),
(411, 'en', 'article_save_failed', 'article', '', 'The article was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(412, 'en', 'comment_settings', 'article', '', 'Comment options', '2019-07-23 19:58:53'),
(413, 'en', 'concurrent_edit_by', 'article', '', 'Article <strong>not</strong> saved. {author} modified the article while you were editing it. If you’re sure, press the ‘Save’ button once more.', '2019-07-23 19:58:53'),
(414, 'en', 'excerpt_markup', 'article', '', 'Excerpt markup', '2019-07-23 19:58:53'),
(415, 'en', 'override_default_form', 'article', '', 'Override form', '2019-07-23 19:58:53'),
(416, 'en', 'problem_creating_article', 'article', '', 'There was a problem creating the article.', '2019-07-23 19:58:53'),
(417, 'en', 'problem_deleting_article', 'article', '', 'There was a problem deleting the article.', '2019-07-23 19:58:53'),
(418, 'en', 'problem_getting_articles', 'article', '', 'There was a problem getting articles.', '2019-07-23 19:58:53'),
(419, 'en', 'problem_retrieving_article', 'article', '', 'There was a problem retrieving the article.', '2019-07-23 19:58:53'),
(420, 'en', 'problem_retrieving_article_categories', 'article', '', 'There was a problem retrieving article categories.', '2019-07-23 19:58:53'),
(421, 'en', 'problem_retrieving_categories', 'article', '', 'There was a problem retrieving categories.', '2019-07-23 19:58:53'),
(422, 'en', 'problem_retrieving_category_info', 'article', '', 'There was a problem retrieving category information.', '2019-07-23 19:58:53'),
(423, 'en', 'problem_retrieving_sections', 'article', '', 'There was a problem retrieving sections.', '2019-07-23 19:58:53'),
(424, 'en', 'problem_saving_article_categories', 'article', '', 'There was a problem saving article categories.', '2019-07-23 19:58:53'),
(425, 'en', 'problem_updating_article', 'article', '', 'There was a problem updating the article.', '2019-07-23 19:58:53'),
(426, 'en', 'sort_display', 'article', '', 'Sort and display', '2019-07-23 19:58:53'),
(427, 'en', 'trying_to_assign_unexisting_category_to_the_article', 'article', '', 'A non-existent category cannot be assigned to an article.', '2019-07-23 19:58:53'),
(428, 'en', 'url_title', 'article', '', 'URL-only title', '2019-07-23 19:58:53'),
(429, 'en', 'url_title_is_blank', 'article', '', 'URL-only title was left blank.', '2019-07-23 19:58:53'),
(430, 'en', 'url_title_is_multiple', 'article', '', 'The same URL-only title is used by {count} different articles.', '2019-07-23 19:58:53'),
(431, 'en', 'write', 'article', '', 'Write', '2019-07-23 19:58:53'),
(432, 'en', 'article_categories_deleted', 'category', '', 'Article categories deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(433, 'en', 'article_category', 'category', '', 'Article category', '2019-07-23 19:58:53'),
(434, 'en', 'article_category_already_exists', 'category', '', 'Article category <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(435, 'en', 'article_category_created', 'category', '', 'Article category <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(436, 'en', 'article_category_description', 'category', '', 'Article category description', '2019-07-23 19:58:53'),
(437, 'en', 'article_category_invalid', 'category', '', 'Article category <strong>{name}</strong> invalid.', '2019-07-23 19:58:53'),
(438, 'en', 'article_category_name', 'category', '', 'Article category name', '2019-07-23 19:58:53'),
(439, 'en', 'article_category_title', 'category', '', 'Article category title', '2019-07-23 19:58:53'),
(440, 'en', 'article_category_updated', 'category', '', 'Article category <strong>{name}</strong> updated.', '2019-07-23 19:58:53'),
(441, 'en', 'categories_set_parent', 'category', '', '{list} assigned to {type} category parent {parent}', '2019-07-23 19:58:53'),
(442, 'en', 'category_not_found', 'category', '', 'Category not found', '2019-07-23 19:58:53'),
(443, 'en', 'category_save_failed', 'category', '', 'The category was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(444, 'en', 'create_category', 'category', '', 'New category', '2019-07-23 19:58:53'),
(445, 'en', 'edit_category', 'category', '', 'Edit category', '2019-07-23 19:58:53'),
(446, 'en', 'file_categories_deleted', 'category', '', 'File categories deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(447, 'en', 'file_category_already_exists', 'category', '', 'File category <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(448, 'en', 'file_category_created', 'category', '', 'File category <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(449, 'en', 'file_category_description', 'category', '', 'File category description', '2019-07-23 19:58:53'),
(450, 'en', 'file_category_invalid', 'category', '', 'File category <strong>{name}</strong> invalid.', '2019-07-23 19:58:53'),
(451, 'en', 'file_category_name', 'category', '', 'File category name', '2019-07-23 19:58:53'),
(452, 'en', 'file_category_title', 'category', '', 'File category title', '2019-07-23 19:58:53'),
(453, 'en', 'file_category_updated', 'category', '', 'File category <strong>{name}</strong> updated.', '2019-07-23 19:58:53'),
(454, 'en', 'image_categories_deleted', 'category', '', 'Image categories deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(455, 'en', 'image_category_already_exists', 'category', '', 'Image category <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(456, 'en', 'image_category_created', 'category', '', 'Image category <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(457, 'en', 'image_category_description', 'category', '', 'Image category description', '2019-07-23 19:58:53'),
(458, 'en', 'image_category_invalid', 'category', '', 'Image category <strong>{name}</strong> invalid.', '2019-07-23 19:58:53'),
(459, 'en', 'image_category_name', 'category', '', 'Image category name', '2019-07-23 19:58:53'),
(460, 'en', 'image_category_title', 'category', '', 'Image category title', '2019-07-23 19:58:53'),
(461, 'en', 'image_category_updated', 'category', '', 'Image category <strong>{name}</strong> updated.', '2019-07-23 19:58:53'),
(462, 'en', 'link_categories_deleted', 'category', '', 'Link categories deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(463, 'en', 'link_category_already_exists', 'category', '', 'Link category <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(464, 'en', 'link_category_created', 'category', '', 'Link category <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(465, 'en', 'link_category_description', 'category', '', 'Link category description', '2019-07-23 19:58:53'),
(466, 'en', 'link_category_invalid', 'category', '', 'Link category <strong>{name}</strong> invalid.', '2019-07-23 19:58:53'),
(467, 'en', 'link_category_name', 'category', '', 'Link category name', '2019-07-23 19:58:53'),
(468, 'en', 'link_category_title', 'category', '', 'Link category title', '2019-07-23 19:58:53'),
(469, 'en', 'link_category_updated', 'category', '', 'Link category <strong>{name}</strong> updated.', '2019-07-23 19:58:53'),
(470, 'en', 'no_categories_exist', 'category', '', 'None exist.', '2019-07-23 19:58:53'),
(471, 'en', 'no_other_categories_exist', 'category', '', 'No other categories exist.', '2019-07-23 19:58:53'),
(472, 'en', 'all_stylesheets', 'css', '', 'All styles', '2019-07-23 19:58:53'),
(473, 'en', 'create_css', 'css', '', 'New style', '2019-07-23 19:58:53'),
(474, 'en', 'css_already_exists', 'css', '', 'Style <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(475, 'en', 'css_code', 'css', '', 'Style code', '2019-07-23 19:58:53'),
(476, 'en', 'css_created', 'css', '', 'Styles created: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(477, 'en', 'css_deleted', 'css', '', 'Styles deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(478, 'en', 'css_name', 'css', '', 'Style name', '2019-07-23 19:58:53'),
(479, 'en', 'css_name_required', 'css', '', 'Please provide a name for your style.', '2019-07-23 19:58:53'),
(480, 'en', 'css_save_failed', 'css', '', 'The style was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(481, 'en', 'css_updated', 'css', '', 'Styles updated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(482, 'en', 'css_used_by_section', 'css', '', 'Style <strong>{name}</strong> not deleted: used by {count} section(s).', '2019-07-23 19:58:53'),
(483, 'en', 'all_checks_passed', 'diag', '', 'All checks passed.', '2019-07-23 19:58:53'),
(484, 'en', 'cgi_header_config', 'diag', '', 'CGI header configuration', '2019-07-23 19:58:53'),
(485, 'en', 'clean_url_data_failed', 'diag', '', 'Clean URL data test failed: {data}.', '2019-07-23 19:58:53'),
(486, 'en', 'clean_url_test_failed', 'diag', '', 'Clean URL test failed.', '2019-07-23 19:58:53'),
(487, 'en', 'diag_clear_private', 'diag', '', 'Hide private information', '2019-07-23 19:58:53'),
(488, 'en', 'dev_version_live', 'diag', '', 'You are running a development version of Textpattern on a live server.', '2019-07-23 19:58:53'),
(489, 'en', 'diagnostic_info', 'diag', '', 'Diagnostic info', '2019-07-23 19:58:53'),
(490, 'en', 'dir_not_writable', 'diag', '', '{dirtype} is not writable: {path}.', '2019-07-23 19:58:53'),
(491, 'en', 'dns_lookup_fails', 'diag', '', 'DNS lookup failed: {domain}.', '2019-07-23 19:58:53'),
(492, 'en', 'file_uploads_disabled', 'diag', '', 'File uploads are disabled.', '2019-07-23 19:58:53'),
(493, 'en', 'high', 'diag', '', 'High', '2019-07-23 19:58:53'),
(494, 'en', 'htaccess_missing', 'diag', '', '.htaccess file is missing.', '2019-07-23 19:58:53'),
(495, 'en', 'low', 'diag', '', 'Low', '2019-07-23 19:58:53'),
(496, 'en', 'missing_files', 'diag', '', 'Missing files: {list}', '2019-07-23 19:58:53'),
(497, 'en', 'modified_files', 'diag', '', 'Some Textpattern files have been modified: {list}', '2019-07-23 19:58:53'),
(498, 'en', 'mod_rewrite_missing', 'diag', '', 'Apache module <code>mod_rewrite</code> is not enabled.', '2019-07-23 19:58:53'),
(499, 'en', 'mysql_table_errors', 'diag', '', 'The following errors were detected in your MySQL tables: {list}', '2019-07-23 19:58:53'),
(500, 'en', 'no_temp_dir', 'diag', '', 'No temporary directory defined.', '2019-07-23 19:58:53'),
(501, 'en', 'old_placeholder', 'diag', '', 'Old placeholder file is in the way: {path}.', '2019-07-23 19:58:53'),
(502, 'en', 'path_inaccessible', 'diag', '', '<strong>{path}</strong> is not accessible.', '2019-07-23 19:58:53'),
(503, 'en', 'php_extensions', 'diag', '', 'PHP extensions', '2019-07-23 19:58:53'),
(504, 'en', 'php_version_required', 'diag', '', 'Textpattern requires at least version {version} of PHP to be installed on your server.', '2019-07-23 19:58:53'),
(505, 'en', 'preflight_check', 'diag', '', 'Pre-flight check', '2019-07-23 19:58:53'),
(506, 'en', 'problem_connecting_update_server', 'diag', '', 'There was a problem connecting to the Textpattern update server. Please try again later.', '2019-07-23 19:58:53'),
(507, 'en', 'site_trailing_slash', 'diag', '', 'Site URL has a trailing slash: {path}.', '2019-07-23 19:58:53'),
(508, 'en', 'site_url_mismatch', 'diag', '', 'Site URL preference might be incorrect: {url}.', '2019-07-23 19:58:53'),
(509, 'en', 'some_php_functions_disabled', 'diag', '', 'The following PHP functions (which may be necessary to run Textpattern) are disabled on your server: {list}.', '2019-07-23 19:58:53'),
(510, 'en', 'still_exists', 'diag', '', '<strong>{path}</strong> still exists.', '2019-07-23 19:58:53'),
(511, 'en', 'textpattern_update_available', 'diag', '', 'New Textpattern version {version} <a href=\"https://textpattern.com/download\" rel=\"external\">available for download</a>.', '2019-07-23 19:58:53'),
(512, 'en', 'textpattern_update_available_beta', 'diag', '', 'New Textpattern pre-release version {version} available for download. Please <a href=\"https://textpattern.com/download-beta\" rel=\"external\">visit the Textpattern website</a> for more information.', '2019-07-23 19:58:53'),
(513, 'en', 'tmp_plugin_paths_match', 'diag', '', 'Temporary directory path and plugin cache directory path should <strong>not</strong> match.', '2019-07-23 19:58:53'),
(514, 'en', 'warn_mail_unavailable', 'diag', '', 'Your PHP installation is missing the <code>mail()</code> function. Therefore no emails can be sent from Textpattern, which limits certain functionality.', '2019-07-23 19:58:53'),
(515, 'en', 'article_deleted', 'discuss', '', 'Article deleted.', '2019-07-23 19:58:53'),
(516, 'en', 'comments_deleted', 'discuss', '', 'Comments deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(517, 'en', 'comments_marked_spam', 'discuss', '', 'Comments hidden and marked as spam: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(518, 'en', 'comments_marked_unmoderated', 'discuss', '', 'Comments hidden and marked as unmoderated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(519, 'en', 'comments_marked_visible', 'discuss', '', 'Comments made visible: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(520, 'en', 'comment_not_found', 'discuss', '', 'Comment does not exist, it may have been deleted.', '2019-07-23 19:58:53'),
(521, 'en', 'comment_save_failed', 'discuss', '', 'The comment was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(522, 'en', 'comment_updated', 'discuss', '', 'Comment <strong>{id}</strong> updated.', '2019-07-23 19:58:53'),
(523, 'en', 'edit_comment', 'discuss', '', 'Edit comment', '2019-07-23 19:58:53'),
(524, 'en', 'hide_spam', 'discuss', '', 'Hide (spam)', '2019-07-23 19:58:53'),
(525, 'en', 'hide_unmoderated', 'discuss', '', 'Hide (unmoderated)', '2019-07-23 19:58:53'),
(526, 'en', 'just_spam_results_found', 'discuss', '', 'Only spam comments found. Use ‘Show spam’ to view them.', '2019-07-23 19:58:53'),
(527, 'en', 'no_comments_recorded', 'discuss', '', 'No comments recorded.', '2019-07-23 19:58:53'),
(528, 'en', 'search_comments', 'discuss', '', 'Search comments', '2019-07-23 19:58:53'),
(529, 'en', 'show_spam', 'discuss', '', 'Show spam', '2019-07-23 19:58:53'),
(530, 'en', 'condition', 'file', '', 'Condition', '2019-07-23 19:58:53'),
(531, 'en', 'edit_file', 'file', '', 'Edit file', '2019-07-23 19:58:53'),
(532, 'en', 'existing_file', 'file', '', 'Existing file', '2019-07-23 19:58:53'),
(533, 'en', 'file_already_exists', 'file', '', 'File <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(534, 'en', 'file_cannot_rename', 'file', '', 'File <strong>{name}</strong> could not be renamed.', '2019-07-23 19:58:53'),
(535, 'en', 'file_deleted', 'file', '', 'Files deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(536, 'en', 'file_delete_failed', 'file', '', 'Failed to delete files: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(537, 'en', 'file_dir_not_writeable', 'file', '', '<strong>Warning:</strong> cannot write to file directory <code>{filedir}</code>.', '2019-07-23 19:58:53'),
(538, 'en', 'file_not_found', 'file', '', 'Files not found: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(539, 'en', 'file_not_updated', 'file', '', 'File <strong>{name}</strong> not updated.', '2019-07-23 19:58:53'),
(540, 'en', 'file_relink', 'file', '', 'Upload/assign file', '2019-07-23 19:58:53'),
(541, 'en', 'file_status', 'file', '', 'File status', '2019-07-23 19:58:53'),
(542, 'en', 'file_status_missing', 'file', '', 'Missing', '2019-07-23 19:58:53'),
(543, 'en', 'file_status_ok', 'file', '', 'OK', '2019-07-23 19:58:53'),
(544, 'en', 'file_unsynchronized', 'file', '', 'File <strong>{name}</strong> has become unsynchronized with the database. Please manually fix filename.', '2019-07-23 19:58:53'),
(545, 'en', 'file_updated', 'file', '', 'Files updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(546, 'en', 'file_uploaded', 'file', '', 'Files uploaded: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(547, 'en', 'file_upload_failed', 'file', '', 'Failed to upload: <strong>{list}</strong>.', '2019-07-23 19:58:53');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(548, 'en', 'invalid_filename', 'file', '', 'Invalid filenames: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(549, 'en', 'invalid_id', 'file', '', 'Invalid ID <strong>{id}</strong>.', '2019-07-23 19:58:53'),
(550, 'en', 'linked_to_file', 'file', '', 'Files linked: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(551, 'en', 'no_files_recorded', 'file', '', 'No files recorded.', '2019-07-23 19:58:53'),
(552, 'en', 'permissions', 'file', '', 'Permissions', '2019-07-23 19:58:53'),
(553, 'en', 'private', 'file', '', 'Private', '2019-07-23 19:58:53'),
(554, 'en', 'public', 'file', '', 'Public', '2019-07-23 19:58:53'),
(555, 'en', 'replace_file', 'file', '', 'Replace file', '2019-07-23 19:58:53'),
(556, 'en', 'reset_download_count', 'file', '', 'Reset download count', '2019-07-23 19:58:53'),
(557, 'en', 'search_files', 'file', '', 'Search files', '2019-07-23 19:58:53'),
(558, 'en', 'all_forms', 'form', '', 'All forms', '2019-07-23 19:58:53'),
(559, 'en', 'changetype', 'form', '', 'Change type', '2019-07-23 19:58:53'),
(560, 'en', 'create_form', 'form', '', 'New form', '2019-07-23 19:58:53'),
(561, 'en', 'form_already_exists', 'form', '', 'Form <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(562, 'en', 'form_clone', 'form', '', 'Duplicate form', '2019-07-23 19:58:53'),
(563, 'en', 'form_code', 'form', '', 'Form code', '2019-07-23 19:58:53'),
(564, 'en', 'form_created', 'form', '', 'Forms created: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(565, 'en', 'form_deleted', 'form', '', 'Forms deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(566, 'en', 'form_name', 'form', '', 'Form name', '2019-07-23 19:58:53'),
(567, 'en', 'form_name_invalid', 'form', '', 'Form name invalid.', '2019-07-23 19:58:53'),
(568, 'en', 'form_save_failed', 'form', '', 'The form was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(569, 'en', 'form_type', 'form', '', 'Form type', '2019-07-23 19:58:53'),
(570, 'en', 'form_type_missing', 'form', '', 'Form type is missing', '2019-07-23 19:58:53'),
(571, 'en', 'form_updated', 'form', '', 'Forms updated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(572, 'en', 'alt_text', 'image', '', 'Alternate text', '2019-07-23 19:58:53'),
(573, 'en', 'create_thumbnail', 'image', '', 'Create thumbnail', '2019-07-23 19:58:53'),
(574, 'en', 'edit_image', 'image', '', 'Edit image', '2019-07-23 19:58:53'),
(575, 'en', 'image_deleted', 'image', '', 'Images deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(576, 'en', 'image_delete_failed', 'image', '', 'Images could not be completely deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(577, 'en', 'image_details', 'image', '', 'Image details', '2019-07-23 19:58:53'),
(578, 'en', 'image_name', 'image', '', 'Image name', '2019-07-23 19:58:53'),
(579, 'en', 'image_save_error', 'image', '', 'There was a problem saving image data.', '2019-07-23 19:58:53'),
(580, 'en', 'image_save_failed', 'image', '', 'The image was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(581, 'en', 'image_updated', 'image', '', 'Images updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(582, 'en', 'image_uploaded', 'image', '', 'Images uploaded: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(583, 'en', 'img_dir_not_writeable', 'image', '', '<strong>Warning:</strong> cannot write to image directory <code>{imgdir}</code>.', '2019-07-23 19:58:53'),
(584, 'en', 'invalid_width_or_height', 'image', '', 'Invalid width or height.', '2019-07-23 19:58:53'),
(585, 'en', 'keep_square_pixels', 'image', '', 'Crop', '2019-07-23 19:58:53'),
(586, 'en', 'no_images_recorded', 'image', '', 'No images recorded.', '2019-07-23 19:58:53'),
(587, 'en', 'only_graphic_files_allowed', 'image', '', 'Only .jpg, .gif or .png file types allowed.', '2019-07-23 19:58:53'),
(588, 'en', 'replace_image', 'image', '', 'Replace image', '2019-07-23 19:58:53'),
(589, 'en', 'search_images', 'image', '', 'Search images', '2019-07-23 19:58:53'),
(590, 'en', 'thumbnail_delete_failed', 'image', '', 'Thumbnail could not be completely deleted.', '2019-07-23 19:58:53'),
(591, 'en', 'thumbnail_deleted', 'image', '', 'Thumbnail deleted.', '2019-07-23 19:58:53'),
(592, 'en', 'thumbnail_not_saved', 'image', '', 'Thumbnail <strong>{id}</strong> not saved.', '2019-07-23 19:58:53'),
(593, 'en', 'thumbnail_saved', 'image', '', 'Thumbnail <strong>{id}</strong> saved.', '2019-07-23 19:58:53'),
(594, 'en', 'upload_image', 'image', '', 'Upload image', '2019-07-23 19:58:53'),
(595, 'en', 'upload_thumbnail', 'image', '', 'Upload thumbnail', '2019-07-23 19:58:53'),
(596, 'en', 'active_language', 'lang', '', 'Site language', '2019-07-23 19:58:53'),
(597, 'en', 'active_language_ui', 'lang', '', 'User language', '2019-07-23 19:58:53'),
(598, 'en', 'install_from_textpack', 'lang', '', 'Install from Textpack', '2019-07-23 19:58:53'),
(599, 'en', 'install_textpack', 'lang', '', 'Install Textpack', '2019-07-23 19:58:53'),
(600, 'en', 'language_deleted', 'lang', '', 'Language <strong>{name}</strong> removed.', '2019-07-23 19:58:53'),
(601, 'en', 'language_installed', 'lang', '', 'Language <strong>{name}</strong> installed.', '2019-07-23 19:58:53'),
(602, 'en', 'language_not_installed', 'lang', '', 'Language <strong>{name}</strong> not installed.', '2019-07-23 19:58:53'),
(603, 'en', 'language_preamble', 'lang', '', '<strong>You can help us improve Textpattern!</strong> We welcome additional translations (and corrections to current translations) by our user community. Please visit <a href=\"https://textpattern.com/languages\" rel=\"external\" target=\"_blank\">Textpattern language translations <span class=\"ui-icon ui-icon-extlink\">(opens an external link in a new window)</span></a> for further details.', '2019-07-23 19:58:53'),
(604, 'en', 'language_updated', 'lang', '', 'Language <strong>{name}</strong> updated.', '2019-07-23 19:58:53'),
(605, 'en', 'locale_not_available_for_language', 'lang', '', 'No locale available for language <strong>{name}</strong>. Using default locale instead.', '2019-07-23 19:58:53'),
(606, 'en', 'textpack_strings_installed', 'lang', '', '{count} strings installed from Textpack.', '2019-07-23 19:58:53'),
(607, 'en', 'create_link', 'link', '', 'New link', '2019-07-23 19:58:53'),
(608, 'en', 'edit_link', 'link', '', 'Edit link', '2019-07-23 19:58:53'),
(609, 'en', 'links_deleted', 'link', '', 'Links deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(610, 'en', 'link_created', 'link', '', 'Link <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(611, 'en', 'link_empty', 'link', '', 'Links should not be empty.', '2019-07-23 19:58:53'),
(612, 'en', 'link_saved', 'link', '', 'Link saved.', '2019-07-23 19:58:53'),
(613, 'en', 'link_save_failed', 'link', '', 'Link could not be saved.', '2019-07-23 19:58:53'),
(614, 'en', 'link_updated', 'link', '', 'Links updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(615, 'en', 'no_links_recorded', 'link', '', 'No links recorded.', '2019-07-23 19:58:53'),
(616, 'en', 'search_links', 'link', '', 'Search links', '2019-07-23 19:58:53'),
(617, 'en', 'articles_deleted', 'list', '', 'Articles deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(618, 'en', 'articles_duplicated', 'list', '', 'Articles duplicated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(619, 'en', 'articles_modified', 'list', '', 'Articles modified: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(620, 'en', 'no_articles_recorded', 'list', '', 'No articles recorded.', '2019-07-23 19:58:53'),
(621, 'en', 'search_articles', 'list', '', 'Search articles', '2019-07-23 19:58:53'),
(622, 'en', 'logs_deleted', 'log', '', 'Logs deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(623, 'en', 'no_refers_recorded', 'log', '', 'No hits recorded.', '2019-07-23 19:58:53'),
(624, 'en', 'referrer', 'log', '', 'Referrer', '2019-07-23 19:58:53'),
(625, 'en', 'search_logs', 'log', '', 'Search logs', '2019-07-23 19:58:53'),
(626, 'en', 'all_pages', 'page', '', 'All pages', '2019-07-23 19:58:53'),
(627, 'en', 'create_page', 'page', '', 'New page', '2019-07-23 19:58:53'),
(628, 'en', 'page_already_exists', 'page', '', 'Page <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(629, 'en', 'page_code', 'page', '', 'Page code', '2019-07-23 19:58:53'),
(630, 'en', 'page_created', 'page', '', 'Pages created: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(631, 'en', 'page_deleted', 'page', '', 'Pages deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(632, 'en', 'page_name', 'page', '', 'Page name', '2019-07-23 19:58:53'),
(633, 'en', 'page_name_invalid', 'page', '', 'Page name is invalid.', '2019-07-23 19:58:53'),
(634, 'en', 'page_save_failed', 'page', '', 'The page was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(635, 'en', 'page_updated', 'page', '', 'Pages updated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(636, 'en', 'page_used_by_section', 'page', '', 'Page <strong>{name}</strong> not deleted; used by {count} section(s).', '2019-07-23 19:58:53'),
(637, 'en', 'bad_plugin_code', 'plugin', '', 'Badly formed or empty plugin code.', '2019-07-23 19:58:53'),
(638, 'en', 'changeorder', 'plugin', '', 'Change order', '2019-07-23 19:58:53'),
(639, 'en', 'edit_plugin', 'plugin', '', 'Edit plugin <strong>{name}</strong>', '2019-07-23 19:58:53'),
(640, 'en', 'edit_plugins', 'plugin', '', 'Edit plugin', '2019-07-23 19:58:53'),
(641, 'en', 'install_plugin', 'plugin', '', 'Install plugin', '2019-07-23 19:58:53'),
(642, 'en', 'order', 'plugin', '', 'Order', '2019-07-23 19:58:53'),
(643, 'en', 'plugin', 'plugin', '', 'Plugin', '2019-07-23 19:58:53'),
(644, 'en', 'plugin_compression_unsupported', 'plugin', '', 'Plugin decompression is not supported by your server. Contact the plugin author for an uncompressed version.', '2019-07-23 19:58:53'),
(645, 'en', 'plugin_deleted', 'plugin', '', 'Plugins deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(646, 'en', 'plugin_help', 'plugin', '', 'Plugin help', '2019-07-23 19:58:53'),
(647, 'en', 'plugin_installed', 'plugin', '', 'Plugin <strong>{name}</strong> installed.', '2019-07-23 19:58:53'),
(648, 'en', 'plugin_install_failed', 'plugin', '', 'Plugin <strong>{name}</strong> install failed.', '2019-07-23 19:58:53'),
(649, 'en', 'plugin_saved', 'plugin', '', 'Plugin <strong>{name}</strong> saved.', '2019-07-23 19:58:53'),
(650, 'en', 'plugin_updated', 'plugin', '', 'Plugins updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(651, 'en', 'previewing_plugin', 'plugin', '', 'Plugin preview', '2019-07-23 19:58:53'),
(652, 'en', 'search_plugins', 'plugin', '', 'Search plugins', '2019-07-23 19:58:53'),
(653, 'en', 'verify_plugin', 'plugin', '', 'Verify plugin', '2019-07-23 19:58:53'),
(654, 'en', 'admin_side_plugins', 'prefs', '', 'Use admin-side plugins?', '2019-07-23 19:58:53'),
(655, 'en', 'allow_article_php_scripting', 'prefs', '', 'Allow PHP in articles?', '2019-07-23 19:58:53'),
(656, 'en', 'allow_form_override', 'prefs', '', 'Allow form override?', '2019-07-23 19:58:53'),
(657, 'en', 'allow_page_php_scripting', 'prefs', '', 'Allow PHP in pages?', '2019-07-23 19:58:53'),
(658, 'en', 'all_hits', 'prefs', '', 'All hits', '2019-07-23 19:58:53'),
(659, 'en', 'all_preferences', 'prefs', '', 'All preferences', '2019-07-23 19:58:53'),
(660, 'en', 'archive_dateformat', 'prefs', '', 'Archive date format', '2019-07-23 19:58:53'),
(661, 'en', 'articles_use_excerpts', 'prefs', '', 'Use excerpts on articles?', '2019-07-23 19:58:53'),
(662, 'en', 'attach_titles_to_permalinks', 'prefs', '', 'Attach titles to permalinks?', '2019-07-23 19:58:53'),
(663, 'en', 'category_subcategory', 'prefs', '', '/category/subcategory', '2019-07-23 19:58:53'),
(664, 'en', 'clean', 'prefs', '', '/clean/', '2019-07-23 19:58:53'),
(665, 'en', 'comments_are_ol', 'prefs', '', 'Present comments as a numbered list?', '2019-07-23 19:58:53'),
(666, 'en', 'comments_auto_append', 'prefs', '', 'Automatically append comments to articles?', '2019-07-23 19:58:53'),
(667, 'en', 'comments_dateformat', 'prefs', '', 'Comments date format', '2019-07-23 19:58:53'),
(668, 'en', 'comments_default_invite', 'prefs', '', 'Default comments invite', '2019-07-23 19:58:53'),
(669, 'en', 'comments_disabled_after', 'prefs', '', 'Comments disabled after', '2019-07-23 19:58:53'),
(670, 'en', 'comments_disallow_images', 'prefs', '', 'Strip commenter images from comments?', '2019-07-23 19:58:53'),
(671, 'en', 'comments_mode', 'prefs', '', 'Comments mode', '2019-07-23 19:58:53'),
(672, 'en', 'comments_moderate', 'prefs', '', 'Moderate comments?', '2019-07-23 19:58:53'),
(673, 'en', 'comments_on_default', 'prefs', '', 'Comments on by default?', '2019-07-23 19:58:53'),
(674, 'en', 'comments_require_email', 'prefs', '', 'Comments require commenter’s email address?', '2019-07-23 19:58:53'),
(675, 'en', 'comments_require_name', 'prefs', '', 'Comments require commenter’s name?', '2019-07-23 19:58:53'),
(676, 'en', 'comments_sendmail', 'prefs', '', 'Email comments to author?', '2019-07-23 19:58:53'),
(677, 'en', 'comments_use_fat_textile', 'prefs', '', 'Allow more Textile markup in comments?', '2019-07-23 19:58:53'),
(678, 'en', 'comment_means_site_updated', 'prefs', '', 'New comment means site updated?', '2019-07-23 19:58:53'),
(679, 'en', 'comment_nofollow', 'prefs', '', 'Apply <code>rel=\"nofollow\"</code> to comments?', '2019-07-23 19:58:53'),
(680, 'en', 'custom_10_set', 'prefs', '', 'Custom field 10 name', '2019-07-23 19:58:53'),
(681, 'en', 'custom_1_set', 'prefs', '', 'Custom field 1 name', '2019-07-23 19:58:53'),
(682, 'en', 'custom_2_set', 'prefs', '', 'Custom field 2 name', '2019-07-23 19:58:53'),
(683, 'en', 'custom_3_set', 'prefs', '', 'Custom field 3 name', '2019-07-23 19:58:53'),
(684, 'en', 'custom_4_set', 'prefs', '', 'Custom field 4 name', '2019-07-23 19:58:53'),
(685, 'en', 'custom_5_set', 'prefs', '', 'Custom field 5 name', '2019-07-23 19:58:53'),
(686, 'en', 'custom_6_set', 'prefs', '', 'Custom field 6 name', '2019-07-23 19:58:53'),
(687, 'en', 'custom_7_set', 'prefs', '', 'Custom field 7 name', '2019-07-23 19:58:53'),
(688, 'en', 'custom_8_set', 'prefs', '', 'Custom field 8 name', '2019-07-23 19:58:53'),
(689, 'en', 'custom_9_set', 'prefs', '', 'Custom field 9 name', '2019-07-23 19:58:53'),
(690, 'en', 'custom_form_types', 'prefs', '', 'Custom form template types', '2019-07-23 19:58:53'),
(691, 'en', 'default_event', 'prefs', '', 'Default panel', '2019-07-23 19:58:53'),
(692, 'en', 'default_publish_status', 'prefs', '', 'Default publishing status', '2019-07-23 19:58:53'),
(693, 'en', 'doctype', 'prefs', '', 'Doctype', '2019-07-23 19:58:53'),
(694, 'en', 'module_pophelp', 'prefs', '', 'Display admin-side inline help links', '2019-07-23 19:58:53'),
(695, 'en', 'enable_dev_preview', 'prefs', '', 'Enable development theme preview?', '2019-07-23 19:58:53'),
(696, 'en', 'enable_short_tags', 'prefs', '', 'Enable short tag support?', '2019-07-23 19:58:53'),
(697, 'en', 'enable_xmlrpc_server', 'prefs', '', 'Enable XML-RPC server?', '2019-07-23 19:58:53'),
(698, 'en', 'expire_logs_after', 'prefs', '', 'Logs expire after how many days?', '2019-07-23 19:58:53'),
(699, 'en', 'feeds', 'prefs', '', 'Feeds', '2019-07-23 19:58:53'),
(700, 'en', 'file_max_upload_size', 'prefs', '', 'Maximum file size of uploads (in bytes)', '2019-07-23 19:58:53'),
(701, 'en', 'ham', 'prefs', '', 'All but spam', '2019-07-23 19:58:53'),
(702, 'en', 'hours_days_ago', 'prefs', '', 'hours/days ago', '2019-07-23 19:58:53'),
(703, 'en', 'id_title', 'prefs', '', '/id/title', '2019-07-23 19:58:53'),
(704, 'en', 'include_email_atom', 'prefs', '', 'Include email address in Atom feeds?', '2019-07-23 19:58:53'),
(705, 'en', 'language', 'prefs', '', 'Language', '2019-07-23 19:58:53'),
(706, 'en', 'logging', 'prefs', '', 'Logging', '2019-07-23 19:58:53'),
(707, 'en', 'logs_expire', 'prefs', '', 'Logs expire after how many days?', '2019-07-23 19:58:53'),
(708, 'en', 'markup_default', 'prefs', '', 'Default markup', '2019-07-23 19:58:53'),
(709, 'en', 'max_url_len', 'prefs', '', 'Maximum URL length (in characters)', '2019-07-23 19:58:53'),
(710, 'en', 'messy', 'prefs', '', '?=messy', '2019-07-23 19:58:53'),
(711, 'en', 'never_display_email', 'prefs', '', 'Hide commenter’s email address?', '2019-07-23 19:58:53'),
(712, 'en', 'no_preferences', 'prefs', '', 'No preferences available.', '2019-07-23 19:58:53'),
(713, 'en', 'override_emailcharset', 'prefs', '', 'Use ISO-8859-1 encoding in emails (default is UTF-8)?', '2019-07-23 19:58:53'),
(714, 'en', 'path_from_root', 'prefs', '', 'Sub-directory (if any)', '2019-07-23 19:58:53'),
(715, 'en', 'permlink_format', 'prefs', '', 'Permalink title URL pattern', '2019-07-23 19:58:53'),
(716, 'en', 'permlink_hyphenated', 'prefs', '', 'Hyphenated (title-like-this)', '2019-07-23 19:58:53'),
(717, 'en', 'permlink_intercapped', 'prefs', '', 'Intercapped (TitleLikeThis)', '2019-07-23 19:58:53'),
(718, 'en', 'plugin_cache_dir', 'prefs', '', 'Plugin cache directory path', '2019-07-23 19:58:53'),
(719, 'en', 'prefs', 'prefs', '', 'Prefs', '2019-07-23 19:58:53'),
(720, 'en', 'production_debug', 'prefs', '', 'Debugging', '2019-07-23 19:58:53'),
(721, 'en', 'production_live', 'prefs', '', 'Live', '2019-07-23 19:58:53'),
(722, 'en', 'production_status', 'prefs', '', 'Production status', '2019-07-23 19:58:53'),
(723, 'en', 'production_test', 'prefs', '', 'Testing', '2019-07-23 19:58:53'),
(724, 'en', 'publisher_email', 'prefs', '', 'Send login details from this email address', '2019-07-23 19:58:53'),
(725, 'en', 'publish_expired_articles', 'prefs', '', 'Publish expired articles?', '2019-07-23 19:58:53'),
(726, 'en', 'referrers_only', 'prefs', '', 'Referrers only', '2019-07-23 19:58:53'),
(727, 'en', 'rss_how_many', 'prefs', '', 'How many articles should be included in feeds?', '2019-07-23 19:58:53'),
(728, 'en', 'section_id_title', 'prefs', '', '/section/id/title', '2019-07-23 19:58:53'),
(729, 'en', 'section_title', 'prefs', '', '/section/title', '2019-07-23 19:58:53'),
(730, 'en', 'send_lastmod', 'prefs', '', 'Send \"Last-Modified\" header?', '2019-07-23 19:58:53'),
(731, 'en', 'show_comment_count_in_feed', 'prefs', '', 'Show comment count in feeds?', '2019-07-23 19:58:53'),
(732, 'en', 'smtp_from', 'prefs', '', 'SMTP envelope sender address', '2019-07-23 19:58:53'),
(733, 'en', 'spam_blacklists', 'prefs', '', 'Spam blocklists (comma-separated)', '2019-07-23 19:58:53'),
(734, 'en', 'syndicate_body_or_excerpt', 'prefs', '', 'Syndicate article excerpt <strong>only</strong>?', '2019-07-23 19:58:53'),
(735, 'en', 'timeoffset', 'prefs', '', 'Time offset (hours)', '2019-07-23 19:58:53'),
(736, 'en', 'title_no_widow', 'prefs', '', 'Prevent widowed words in article titles?', '2019-07-23 19:58:53'),
(737, 'en', 'title_only', 'prefs', '', '/title', '2019-07-23 19:58:53'),
(738, 'en', 'txp_evaluate_functions', 'prefs', '', 'PHP functions enabled in <code>txp:evaluate</code>', '2019-07-23 19:58:53'),
(739, 'en', 'tz_timezone', 'prefs', '', 'Time zone', '2019-07-23 19:58:53'),
(740, 'en', 'up_to_date', 'prefs', '', 'Up-to-date', '2019-07-23 19:58:53'),
(741, 'en', 'url_mode', 'prefs', '', 'URL mode', '2019-07-23 19:58:53'),
(742, 'en', 'use_comments', 'prefs', '', 'Accept comments?', '2019-07-23 19:58:53'),
(743, 'en', 'use_mail_on_feeds_id', 'prefs', '', 'Use email address to construct feed ID (default is site URL)?', '2019-07-23 19:58:53'),
(744, 'en', 'use_plugins', 'prefs', '', 'Use plugins?', '2019-07-23 19:58:53'),
(745, 'en', 'year_month_day_title', 'prefs', '', '/year/month/day/title', '2019-07-23 19:58:53'),
(746, 'en', '403_forbidden', 'public', '', 'Forbidden.', '2019-07-23 19:58:53'),
(747, 'en', '404_not_found', 'public', '', 'The requested resource was not found.', '2019-07-23 19:58:53'),
(748, 'en', '410_gone', 'public', '', 'The requested resource is no longer available.', '2019-07-23 19:58:53'),
(749, 'en', '500_internal_server_error', 'public', '', 'Internal server error.', '2019-07-23 19:58:53'),
(750, 'en', 'articles_found', 'public', '', 'articles found', '2019-07-23 19:58:53'),
(751, 'en', 'article_context', 'public', '', 'article', '2019-07-23 19:58:53'),
(752, 'en', 'article_found', 'public', '', 'article found', '2019-07-23 19:58:53'),
(753, 'en', 'atom_feed_title', 'public', '', 'Atom feed', '2019-07-23 19:58:53'),
(754, 'en', 'a_few_seconds', 'public', '', 'a few seconds', '2019-07-23 19:58:53'),
(755, 'en', 'comments_closed', 'public', '', 'Commenting is closed for this article.', '2019-07-23 19:58:53'),
(756, 'en', 'comments_on', 'public', '', 'Comments on', '2019-07-23 19:58:53'),
(757, 'en', 'comments_permlink', 'public', '', 'Permanent link', '2019-07-23 19:58:53'),
(758, 'en', 'comment_comment', 'public', '', 'Comment', '2019-07-23 19:58:53'),
(759, 'en', 'comment_duplicate', 'public', '', 'Duplicate comments are not accepted.', '2019-07-23 19:58:53'),
(760, 'en', 'comment_email', 'public', '', 'Email', '2019-07-23 19:58:53'),
(761, 'en', 'comment_email_required', 'public', '', 'Please enter a valid email address.', '2019-07-23 19:58:53'),
(762, 'en', 'comment_message', 'public', '', 'Message', '2019-07-23 19:58:53'),
(763, 'en', 'comment_moderated', 'public', '', 'Your comment is pending moderation. It will appear after it has been approved.', '2019-07-23 19:58:53'),
(764, 'en', 'comment_name', 'public', '', 'Name', '2019-07-23 19:58:53'),
(765, 'en', 'comment_name_required', 'public', '', 'Please enter your name.', '2019-07-23 19:58:53'),
(766, 'en', 'comment_posted', 'public', '', 'Thank you for adding your comment.', '2019-07-23 19:58:53'),
(767, 'en', 'comment_received', 'public', '', '{site} comment received: {title}', '2019-07-23 19:58:53'),
(768, 'en', 'comment_recorded', 'public', '', 'A comment on your post “{title}” was received.', '2019-07-23 19:58:53'),
(769, 'en', 'comment_required', 'public', '', 'Please enter a comment.', '2019-07-23 19:58:53'),
(770, 'en', 'comment_web', 'public', '', 'Website', '2019-07-23 19:58:53'),
(771, 'en', 'continue_reading', 'public', '', 'Continue reading', '2019-07-23 19:58:53'),
(772, 'en', 'day', 'public', '', 'day', '2019-07-23 19:58:53'),
(773, 'en', 'days', 'public', '', 'days', '2019-07-23 19:58:53'),
(774, 'en', 'enter_comment_here', 'public', '', '<strong>Enter your comment below.</strong> Fields marked <b class=\"required\" title=\"Required\">*</b> are required. You must preview your comment before submitting it.', '2019-07-23 19:58:53'),
(775, 'en', 'file_context', 'public', '', 'file', '2019-07-23 19:58:53'),
(776, 'en', 'hour', 'public', '', 'hour', '2019-07-23 19:58:53'),
(777, 'en', 'hours', 'public', '', 'hours', '2019-07-23 19:58:53'),
(778, 'en', 'image_context', 'public', '', 'image', '2019-07-23 19:58:53'),
(779, 'en', 'link_context', 'public', '', 'link', '2019-07-23 19:58:53'),
(780, 'en', 'main_content', 'public', '', 'Main content', '2019-07-23 19:58:53'),
(781, 'en', 'matching_search_request', 'public', '', 'matching your search request', '2019-07-23 19:58:53'),
(782, 'en', 'minute', 'public', '', 'minute', '2019-07-23 19:58:53'),
(783, 'en', 'minutes', 'public', '', 'minutes', '2019-07-23 19:58:53'),
(784, 'en', 'nopopup', 'public', '', 'current window', '2019-07-23 19:58:53'),
(785, 'en', 'no_comments', 'public', '', 'There are currently no comments on this article.', '2019-07-23 19:58:53'),
(786, 'en', 'no_search_matches', 'public', '', 'Sorry, no results found matching your search request', '2019-07-23 19:58:53'),
(787, 'en', 'page_nav', 'public', '', 'Page navigation', '2019-07-23 19:58:53'),
(788, 'en', 'permanent_link', 'public', '', 'Permanent link to this article', '2019-07-23 19:58:53'),
(789, 'en', 'popup', 'public', '', 'pop-up window', '2019-07-23 19:58:53'),
(790, 'en', 'press_preview_then_submit', 'public', '', 'The following is a preview of what your comment will look like. When you are happy with your comment, use the <strong><a href=\"#txpCommentSubmit\" title=\"Go to the submit button\">submit</a></strong> button.', '2019-07-23 19:58:53'),
(791, 'en', 'read_more', 'public', '', 'Read more', '2019-07-23 19:58:53'),
(792, 'en', 'rss_feed_title', 'public', '', 'RSS feed', '2019-07-23 19:58:53'),
(793, 'en', 'site_nav', 'public', '', 'Site navigation', '2019-07-23 19:58:53'),
(794, 'en', 'textile_help', 'public', '', 'Textile help', '2019-07-23 19:58:53'),
(795, 'en', 'too_common_search_term', 'public', '', 'It seems like you are looking for a very common search term, maybe try a more specific phrase than', '2019-07-23 19:58:53'),
(796, 'en', 'week', 'public', '', 'week', '2019-07-23 19:58:53'),
(797, 'en', 'weeks', 'public', '', 'weeks', '2019-07-23 19:58:53'),
(798, 'en', 'your_ip_is_blacklisted_by', 'public', '', 'Your IP address has been blocklisted by', '2019-07-23 19:58:53'),
(799, 'en', 'yyyy-mm', 'public', '', 'yyyy-mm', '2019-07-23 19:58:53'),
(800, 'en', 'article_count', 'section', '', 'Articles in this section: {num}.', '2019-07-23 19:58:53'),
(801, 'en', 'change_page_style', 'section', '', 'Change theme/page/style', '2019-07-23 19:58:53'),
(802, 'en', 'create_section', 'section', '', 'New section', '2019-07-23 19:58:53'),
(803, 'en', 'default_section_updated', 'section', '', 'Default section updated', '2019-07-23 19:58:53'),
(804, 'en', 'default_write_section', 'section', '', 'Default publishing section', '2019-07-23 19:58:53'),
(805, 'en', 'dev_theme', 'section', '', 'Development', '2019-07-23 19:58:53'),
(806, 'en', 'dev_to_live', 'section', '', 'Reset to live', '2019-07-23 19:58:53'),
(807, 'en', 'edit_default_section', 'section', '', 'Edit default section', '2019-07-23 19:58:53'),
(808, 'en', 'edit_section', 'section', '', 'Edit section', '2019-07-23 19:58:53'),
(809, 'en', 'include_in_search', 'section', '', 'Searchable?', '2019-07-23 19:58:53'),
(810, 'en', 'live_theme', 'section', '', 'Live', '2019-07-23 19:58:53'),
(811, 'en', 'live_to_dev', 'section', '', 'Deploy to live', '2019-07-23 19:58:53'),
(812, 'en', 'search_sections', 'section', '', 'Search sections', '2019-07-23 19:58:53'),
(813, 'en', 'section_created', 'section', '', 'Section <strong>{name}</strong> created.', '2019-07-23 19:58:53'),
(814, 'en', 'section_delete_failure', 'section', '', 'Could not delete section(s), articles are currently assigned: {name}.', '2019-07-23 19:58:53'),
(815, 'en', 'section_deleted', 'section', '', 'Sections deleted: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(816, 'en', 'section_head', 'section', '', 'Site sections', '2019-07-23 19:58:53'),
(817, 'en', 'section_longtitle', 'section', '', 'Section title', '2019-07-23 19:58:53'),
(818, 'en', 'section_name', 'section', '', 'Section name', '2019-07-23 19:58:53'),
(819, 'en', 'section_name_already_exists', 'section', '', 'Section <strong>{name}</strong> already exists.', '2019-07-23 19:58:53'),
(820, 'en', 'section_save_failed', 'section', '', 'The section was not saved due to an error. Please try again.', '2019-07-23 19:58:53'),
(821, 'en', 'section_updated', 'section', '', 'Sections updated: <strong>{name}</strong>.', '2019-07-23 19:58:53'),
(822, 'en', 'switch_dev_live', 'section', '', 'Developer preview', '2019-07-23 19:58:53'),
(823, 'en', 'syndicate', 'section', '', 'Syndicate articles?', '2019-07-23 19:58:53'),
(824, 'en', 'uses_page', 'section', '', 'Uses page', '2019-07-23 19:58:53'),
(825, 'en', 'uses_skin', 'section', '', 'Uses theme', '2019-07-23 19:58:53'),
(826, 'en', 'uses_style', 'section', '', 'Uses style', '2019-07-23 19:58:53'),
(827, 'en', 'create_skin', 'skin', '', 'New theme', '2019-07-23 19:58:53'),
(828, 'en', 'css_creation_failed', 'skin', '', 'Styles creation failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(829, 'en', 'css_deletion_failed', 'skin', '', 'Styles deletion failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(830, 'en', 'css_files_deletion_failed', 'skin', '', 'Styles files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 19:58:53'),
(831, 'en', 'css_import_failed', 'skin', '', 'Styles import failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(832, 'en', 'css_name_unsafe', 'skin', '', 'Not exportable styles names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 19:58:53'),
(833, 'en', 'css_not_found', 'skin', '', 'No style data found for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(834, 'en', 'css_update_failed', 'skin', '', 'Styles update failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(835, 'en', 'edit_skin', 'skin', '', 'Edit theme', '2019-07-23 19:58:53'),
(836, 'en', 'form_creation_failed', 'skin', '', 'Forms creation failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(837, 'en', 'form_deletion_failed', 'skin', '', 'Forms deletion failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(838, 'en', 'form_duplicate_failed', 'skin', '', 'Duplicated forms import failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(839, 'en', 'form_files_deletion_failed', 'skin', '', 'Forms files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 19:58:53'),
(840, 'en', 'form_import_failed', 'skin', '', 'Forms import failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(841, 'en', 'form_name_unsafe', 'skin', '', 'Not exportable form names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 19:58:53'),
(842, 'en', 'form_not_found', 'skin', '', 'No form data found for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(843, 'en', 'form_subdir_error', 'skin', '', 'Form types error for: <strong>{list}</strong>. These forms are used as default by tags and should use defined types.', '2019-07-23 19:58:53'),
(844, 'en', 'form_subdir_invalid', 'skin', '', 'Invalid form type for: <strong>{list}</strong>. Current valid types are: <code>article</code>, <code>category</code>, <code>comment</code>, <code>file</code>, <code>link</code>, <code>section</code> and <code>misc</code>. <code>misc</code> will be used by default.', '2019-07-23 19:58:53'),
(845, 'en', 'form_update_failed', 'skin', '', 'Forms update failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(846, 'en', 'no_skin_recorded', 'skin', '', 'No themes recorded.', '2019-07-23 19:58:53'),
(847, 'en', 'page_creation_failed', 'skin', '', 'Pages creation failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(848, 'en', 'page_deletion_failed', 'skin', '', 'Pages deletion failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(849, 'en', 'page_files_deletion_failed', 'skin', '', 'Pages files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 19:58:53'),
(850, 'en', 'page_import_failed', 'skin', '', 'Pages import failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(851, 'en', 'page_name_unsafe', 'skin', '', 'Not exportable page names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 19:58:53'),
(852, 'en', 'page_not_found', 'skin', '', 'No page data found for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(853, 'en', 'page_update_failed', 'skin', '', 'Pages update failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(854, 'en', 'search_skins', 'skin', '', 'Search themes', '2019-07-23 19:58:53'),
(855, 'en', 'skin_already_exists', 'skin', '', 'Themes already exist in the database or in the themes directory and may cause conflicts: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(856, 'en', 'skin_author', 'skin', '', 'Theme author', '2019-07-23 19:58:53'),
(857, 'en', 'skin_author_uri', 'skin', '', 'Theme website', '2019-07-23 19:58:53'),
(858, 'en', 'skin_count_css', 'skin', '', 'Styles in this theme: {num}.', '2019-07-23 19:58:53'),
(859, 'en', 'skin_count_form', 'skin', '', 'Forms in this theme: {num}.', '2019-07-23 19:58:53'),
(860, 'en', 'skin_count_page', 'skin', '', 'Pages in this theme: {num}.', '2019-07-23 19:58:53'),
(861, 'en', 'skin_count_section', 'skin', '', 'Sections used by this theme: {num}.', '2019-07-23 19:58:53'),
(862, 'en', 'skin_created', 'skin', '', 'Themes created: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(863, 'en', 'skin_creation_failed', 'skin', '', 'Themes creation failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(864, 'en', 'skin_delete_entirely', 'skin', '', 'Delete theme templates from disk too', '2019-07-23 19:58:53'),
(865, 'en', 'skin_delete_from_database', 'skin', '', 'Delete unused templates from database on import', '2019-07-23 19:58:53'),
(866, 'en', 'skin_delete_from_disk', 'skin', '', 'Delete unused templates from disk on export', '2019-07-23 19:58:53'),
(867, 'en', 'skin_deleted', 'skin', '', 'Themes deleted: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(868, 'en', 'skin_deletion_failed', 'skin', '', 'Theme(s) deletion failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(869, 'en', 'skin_description', 'skin', '', 'Theme description', '2019-07-23 19:58:53'),
(870, 'en', 'skin_export_failed', 'skin', '', 'Themes export failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(871, 'en', 'skin_exported', 'skin', '', 'Themes exported: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(872, 'en', 'skin_files_deletion_failed', 'skin', '', 'Theme files deletion failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(873, 'en', 'skin_import_failed', 'skin', '', 'Themes import failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(874, 'en', 'skin_imported', 'skin', '', 'Themes imported: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(875, 'en', 'skin_in_use', 'skin', '', 'Deletion aborted for themes used by sections: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(876, 'en', 'skin_name', 'skin', '', 'Theme name', '2019-07-23 19:58:53'),
(877, 'en', 'skin_name_unsafe', 'skin', '', 'Not exportable theme names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 19:58:53'),
(878, 'en', 'skin_not_found', 'skin', '', 'No theme data found for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(879, 'en', 'skin_related_sections_update_failed', 'skin', '', 'Themes related sections update failed for: <strong>{list}</strong>. Please visit the Sections panel to update manually.', '2019-07-23 19:58:53'),
(880, 'en', 'skin_title', 'skin', '', 'Theme title', '2019-07-23 19:58:53'),
(881, 'en', 'skin_unknown', 'skin', '', 'Unknown themes: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(882, 'en', 'skin_update_failed', 'skin', '', 'Themes update failed for: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(883, 'en', 'skin_updated', 'skin', '', 'Themes updated: <strong>{list}</strong>.', '2019-07-23 19:58:53'),
(884, 'en', 'skin_version', 'skin', '', 'Theme version', '2019-07-23 19:58:53'),
(885, 'en', 'active_class', 'tag', '', 'CSS class for active list item', '2019-07-23 19:58:53'),
(886, 'en', 'allowoverride', 'tag', '', 'Allow form to be overridden?', '2019-07-23 19:58:53'),
(887, 'en', 'breadcrumb_linked', 'tag', '', 'Link breadcrumbs?', '2019-07-23 19:58:53'),
(888, 'en', 'breadcrumb_separator', 'tag', '', 'Breadcrumbs separator', '2019-07-23 19:58:53'),
(889, 'en', 'break', 'tag', '', 'List break tag', '2019-07-23 19:58:53'),
(890, 'en', 'breakclass', 'tag', '', 'CSS class for list break tag', '2019-07-23 19:58:53'),
(891, 'en', 'build', 'tag', '', 'Build tag', '2019-07-23 19:58:53'),
(892, 'en', 'button_text', 'tag', '', 'Button text', '2019-07-23 19:58:53'),
(893, 'en', 'category_list_section', 'tag', '', 'Link to specific section?', '2019-07-23 19:58:53'),
(894, 'en', 'category_tags', 'tag', '', 'Category', '2019-07-23 19:58:53'),
(895, 'en', 'class', 'tag', '', 'CSS class', '2019-07-23 19:58:53'),
(896, 'en', 'comments_form', 'tag', '', 'Comments form', '2019-07-23 19:58:53'),
(897, 'en', 'comment_details', 'tag', '', 'Comment details', '2019-07-23 19:58:53'),
(898, 'en', 'comment_form', 'tag', '', 'Comment form', '2019-07-23 19:58:53'),
(899, 'en', 'comment_name_link', 'tag', '', 'Link to commenter’s email address/website?', '2019-07-23 19:58:53'),
(900, 'en', 'decimals', 'tag', '', 'Display # numbers after the decimal point', '2019-07-23 19:58:53'),
(901, 'en', 'default_title', 'tag', '', 'Text to use for default section link', '2019-07-23 19:58:53'),
(902, 'en', 'depth', 'tag', '', 'Depth', '2019-07-23 19:58:53'),
(903, 'en', 'escape', 'tag', '', 'Escape', '2019-07-23 19:58:53'),
(904, 'en', 'exclude', 'tag', '', 'Exclude', '2019-07-23 19:58:53'),
(905, 'en', 'filename', 'tag', '', 'Name', '2019-07-23 19:58:53'),
(906, 'en', 'file_download_tags', 'tag', '', 'File downloads', '2019-07-23 19:58:53'),
(907, 'en', 'flavor', 'tag', '', 'Syndication format', '2019-07-23 19:58:53'),
(908, 'en', 'format', 'tag', '', 'Format', '2019-07-23 19:58:53'),
(909, 'en', 'gmt', 'tag', '', 'Format according to <abbr title=\"Greenwich Mean Time\">GMT</abbr> time?', '2019-07-23 19:58:53'),
(910, 'en', 'has_excerpt', 'tag', '', 'Has excerpt', '2019-07-23 19:58:53'),
(911, 'en', 'hilight', 'tag', '', 'Highlight tag', '2019-07-23 19:58:53'),
(912, 'en', 'hilight_limit', 'tag', '', 'Highlight how many instances?', '2019-07-23 19:58:53'),
(913, 'en', 'html_id', 'tag', '', 'HTML <code>id</code> attribute', '2019-07-23 19:58:53'),
(914, 'en', 'include_default', 'tag', '', 'Include default section?', '2019-07-23 19:58:53'),
(915, 'en', 'inline_style', 'tag', '', 'Inline style (CSS)', '2019-07-23 19:58:53'),
(916, 'en', 'input_size', 'tag', '', 'Input size', '2019-07-23 19:58:53'),
(917, 'en', 'labeltag', 'tag', '', 'Label tag', '2019-07-23 19:58:53'),
(918, 'en', 'limit', 'tag', '', 'Display how many?', '2019-07-23 19:58:53'),
(919, 'en', 'linkclass', 'tag', '', 'CSS class for links', '2019-07-23 19:58:53'),
(920, 'en', 'link_text', 'tag', '', 'Link text', '2019-07-23 19:58:53'),
(921, 'en', 'link_to_this_author', 'tag', '', 'Link to a list of other articles by this author?', '2019-07-23 19:58:53'),
(922, 'en', 'link_to_this_category', 'tag', '', 'Link to a list of other articles in this category?', '2019-07-23 19:58:53'),
(923, 'en', 'link_to_this_section', 'tag', '', 'Link to a list of other articles in this section?', '2019-07-23 19:58:53'),
(924, 'en', 'listform', 'tag', '', 'List form', '2019-07-23 19:58:53'),
(925, 'en', 'match_type', 'tag', '', 'Match type', '2019-07-23 19:58:53'),
(926, 'en', 'media', 'tag', '', 'HTML <code>media</code> attribute', '2019-07-23 19:58:53'),
(927, 'en', 'msgcols', 'tag', '', 'Message textarea columns', '2019-07-23 19:58:53'),
(928, 'en', 'msgrows', 'tag', '', 'Message textarea rows', '2019-07-23 19:58:53'),
(929, 'en', 'no_categories_available', 'tag', '', 'No categories available.', '2019-07-23 19:58:53'),
(930, 'en', 'no_forms_available', 'tag', '', 'No forms available.', '2019-07-23 19:58:53'),
(931, 'en', 'no_sections_available', 'tag', '', 'No sections available.', '2019-07-23 19:58:53'),
(932, 'en', 'offset', 'tag', '', 'Skip', '2019-07-23 19:58:53'),
(933, 'en', 'page_article_hed', 'tag', '', 'Article output', '2019-07-23 19:58:53'),
(934, 'en', 'page_article_nav_hed', 'tag', '', 'Article navigation', '2019-07-23 19:58:53'),
(935, 'en', 'page_file_hed', 'tag', '', 'File downloads', '2019-07-23 19:58:53'),
(936, 'en', 'page_misc_hed', 'tag', '', 'Miscellaneous', '2019-07-23 19:58:53'),
(937, 'en', 'page_nav_hed', 'tag', '', 'Site navigation', '2019-07-23 19:58:53'),
(938, 'en', 'page_xml_hed', 'tag', '', 'XML feeds', '2019-07-23 19:58:53'),
(939, 'en', 'pageby', 'tag', '', 'Paginate by', '2019-07-23 19:58:53'),
(940, 'en', 'pgonly', 'tag', '', 'Count articles, but show nothing', '2019-07-23 19:58:53'),
(941, 'en', 'random', 'tag', '', 'Random', '2019-07-23 19:58:53'),
(942, 'en', 'rel', 'tag', '', 'HTML <code>rel</code> attribute', '2019-07-23 19:58:53'),
(943, 'en', 'searchall', 'tag', '', 'Search all sections?', '2019-07-23 19:58:53'),
(944, 'en', 'searchsticky', 'tag', '', 'Search sticky articles?', '2019-07-23 19:58:53'),
(945, 'en', 'search_input_form', 'tag', '', 'Search input', '2019-07-23 19:58:53'),
(946, 'en', 'search_results_form', 'tag', '', 'Search results', '2019-07-23 19:58:53'),
(947, 'en', 'section_tags', 'tag', '', 'Section', '2019-07-23 19:58:53'),
(948, 'en', 'separator', 'tag', '', 'Separator', '2019-07-23 19:58:53'),
(949, 'en', 'showalways', 'tag', '', 'Show always', '2019-07-23 19:58:53'),
(950, 'en', 'showcount', 'tag', '', 'Show count?', '2019-07-23 19:58:53'),
(951, 'en', 'size_format', 'tag', '', 'Size format', '2019-07-23 19:58:53'),
(952, 'en', 'sort', 'tag', '', 'Sort by', '2019-07-23 19:58:53'),
(953, 'en', 'tag', 'tag', '', 'Tag', '2019-07-23 19:58:53'),
(954, 'en', 'tag_article', 'tag', '', 'Articles (single or list)', '2019-07-23 19:58:53'),
(955, 'en', 'tag_article_custom', 'tag', '', 'Articles (custom list)', '2019-07-23 19:58:53'),
(956, 'en', 'tag_article_image', 'tag', '', 'Article image', '2019-07-23 19:58:53'),
(957, 'en', 'tag_author', 'tag', '', 'Author', '2019-07-23 19:58:53'),
(958, 'en', 'tag_body', 'tag', '', 'Body', '2019-07-23 19:58:53'),
(959, 'en', 'tag_body_excerpt', 'tag', '', 'Body excerpt', '2019-07-23 19:58:53'),
(960, 'en', 'tag_breadcrumb', 'tag', '', 'Breadcrumb', '2019-07-23 19:58:53'),
(961, 'en', 'tag_category', 'tag', '', 'Category', '2019-07-23 19:58:53'),
(962, 'en', 'tag_category1', 'tag', '', 'Category 1', '2019-07-23 19:58:53'),
(963, 'en', 'tag_category2', 'tag', '', 'Category 2', '2019-07-23 19:58:53'),
(964, 'en', 'tag_category_list', 'tag', '', 'Category list', '2019-07-23 19:58:53'),
(965, 'en', 'tag_comments', 'tag', '', 'Comments', '2019-07-23 19:58:53'),
(966, 'en', 'tag_comments_form', 'tag', '', 'Comments form', '2019-07-23 19:58:53'),
(967, 'en', 'tag_comments_invite', 'tag', '', 'Comments invite', '2019-07-23 19:58:53'),
(968, 'en', 'tag_comments_preview', 'tag', '', 'Comments preview', '2019-07-23 19:58:53'),
(969, 'en', 'tag_comment_anchor', 'tag', '', 'Comment anchor', '2019-07-23 19:58:53'),
(970, 'en', 'tag_comment_email', 'tag', '', 'Comment email', '2019-07-23 19:58:53'),
(971, 'en', 'tag_comment_email_input', 'tag', '', 'Comment email input', '2019-07-23 19:58:53'),
(972, 'en', 'tag_comment_id', 'tag', '', 'Comment ID', '2019-07-23 19:58:53'),
(973, 'en', 'tag_comment_message', 'tag', '', 'Comment message', '2019-07-23 19:58:53'),
(974, 'en', 'tag_comment_message_input', 'tag', '', 'Comment message input', '2019-07-23 19:58:53'),
(975, 'en', 'tag_comment_name', 'tag', '', 'Comment name', '2019-07-23 19:58:53'),
(976, 'en', 'tag_comment_name_input', 'tag', '', 'Comment name input', '2019-07-23 19:58:53'),
(977, 'en', 'tag_comment_permlink', 'tag', '', 'Comment permanent link', '2019-07-23 19:58:53'),
(978, 'en', 'tag_comment_preview', 'tag', '', 'Comment preview button', '2019-07-23 19:58:53'),
(979, 'en', 'tag_comment_remember', 'tag', '', 'Remember details checkbox', '2019-07-23 19:58:53'),
(980, 'en', 'tag_comment_submit', 'tag', '', 'Comment submit button', '2019-07-23 19:58:53'),
(981, 'en', 'tag_comment_time', 'tag', '', 'Comment time', '2019-07-23 19:58:53'),
(982, 'en', 'tag_comment_web', 'tag', '', 'Comment website', '2019-07-23 19:58:53'),
(983, 'en', 'tag_comment_web_input', 'tag', '', 'Comment website input', '2019-07-23 19:58:53'),
(984, 'en', 'tag_css', 'tag', '', 'CSS link (<code>head</code>)', '2019-07-23 19:58:53'),
(985, 'en', 'tag_email', 'tag', '', 'Email link (spam-resistant)', '2019-07-23 19:58:53'),
(986, 'en', 'tag_excerpt', 'tag', '', 'Excerpt', '2019-07-23 19:58:53'),
(987, 'en', 'tag_feed_link', 'tag', '', 'Articles feed link', '2019-07-23 19:58:53'),
(988, 'en', 'tag_file_download', 'tag', '', 'File download', '2019-07-23 19:58:53'),
(989, 'en', 'tag_file_download_category', 'tag', '', 'File category', '2019-07-23 19:58:53'),
(990, 'en', 'tag_file_download_created', 'tag', '', 'File created time', '2019-07-23 19:58:53'),
(991, 'en', 'tag_file_download_description', 'tag', '', 'File description', '2019-07-23 19:58:53'),
(992, 'en', 'tag_file_download_downloads', 'tag', '', 'File download count', '2019-07-23 19:58:53'),
(993, 'en', 'tag_file_download_id', 'tag', '', 'File ID#', '2019-07-23 19:58:53'),
(994, 'en', 'tag_file_download_link', 'tag', '', 'File download link', '2019-07-23 19:58:53'),
(995, 'en', 'tag_file_download_list', 'tag', '', 'File download list', '2019-07-23 19:58:53'),
(996, 'en', 'tag_file_download_modified', 'tag', '', 'File modified time', '2019-07-23 19:58:53'),
(997, 'en', 'tag_file_download_name', 'tag', '', 'File name', '2019-07-23 19:58:53'),
(998, 'en', 'tag_file_download_size', 'tag', '', 'File size', '2019-07-23 19:58:53'),
(999, 'en', 'tag_home', 'tag', '', 'Home', '2019-07-23 19:58:53'),
(1000, 'en', 'tag_if_category', 'tag', '', 'If category', '2019-07-23 19:58:53'),
(1001, 'en', 'tag_if_section', 'tag', '', 'If section', '2019-07-23 19:58:53'),
(1002, 'en', 'tag_image', 'tag', '', 'Image', '2019-07-23 19:58:53'),
(1003, 'en', 'tag_lang', 'tag', '', 'Language', '2019-07-23 19:58:53'),
(1004, 'en', 'tag_link', 'tag', '', 'Link', '2019-07-23 19:58:53'),
(1005, 'en', 'tag_linkdesctitle', 'tag', '', 'Link, title=description', '2019-07-23 19:58:53'),
(1006, 'en', 'tag_linklist', 'tag', '', 'Links list', '2019-07-23 19:58:53'),
(1007, 'en', 'tag_link_category', 'tag', '', 'Link category', '2019-07-23 19:58:53'),
(1008, 'en', 'tag_link_date', 'tag', '', 'Link date', '2019-07-23 19:58:53'),
(1009, 'en', 'tag_link_description', 'tag', '', 'Link description', '2019-07-23 19:58:53'),
(1010, 'en', 'tag_link_feed_link', 'tag', '', 'Links feed link', '2019-07-23 19:58:53'),
(1011, 'en', 'tag_link_name', 'tag', '', 'Link name', '2019-07-23 19:58:53'),
(1012, 'en', 'tag_link_text', 'tag', '', 'Link name', '2019-07-23 19:58:53'),
(1013, 'en', 'tag_link_to_home', 'tag', '', 'Homepage link', '2019-07-23 19:58:53'),
(1014, 'en', 'tag_link_to_next', 'tag', '', 'Next article link', '2019-07-23 19:58:53'),
(1015, 'en', 'tag_link_to_prev', 'tag', '', 'Previous article link', '2019-07-23 19:58:53'),
(1016, 'en', 'tag_name', 'tag', '', 'Commenter name', '2019-07-23 19:58:53'),
(1017, 'en', 'tag_newer', 'tag', '', 'Newer articles link', '2019-07-23 19:58:53'),
(1018, 'en', 'tag_next_article', 'tag', '', 'Next article', '2019-07-23 19:58:53'),
(1019, 'en', 'tag_next_title', 'tag', '', 'Next article title', '2019-07-23 19:58:53'),
(1020, 'en', 'tag_older', 'tag', '', 'Older articles link', '2019-07-23 19:58:53'),
(1021, 'en', 'tag_output_form', 'tag', '', 'Output form', '2019-07-23 19:58:53'),
(1022, 'en', 'tag_page_title', 'tag', '', 'Page title', '2019-07-23 19:58:53'),
(1023, 'en', 'tag_password_protect', 'tag', '', 'Password protection', '2019-07-23 19:58:53'),
(1024, 'en', 'tag_permlink', 'tag', '', 'Permanent link', '2019-07-23 19:58:53'),
(1025, 'en', 'tag_popup', 'tag', '', 'Pop-up list', '2019-07-23 19:58:53'),
(1026, 'en', 'tag_posted', 'tag', '', 'Posted', '2019-07-23 19:58:53'),
(1027, 'en', 'tag_prev_article', 'tag', '', 'Previous article', '2019-07-23 19:58:53'),
(1028, 'en', 'tag_prev_title', 'tag', '', 'Previous article title', '2019-07-23 19:58:53'),
(1029, 'en', 'tag_recent_articles', 'tag', '', 'Recent articles', '2019-07-23 19:58:53'),
(1030, 'en', 'tag_recent_comments', 'tag', '', 'Recent comments', '2019-07-23 19:58:53'),
(1031, 'en', 'tag_related_articles', 'tag', '', 'Related articles', '2019-07-23 19:58:53'),
(1032, 'en', 'tag_search_input', 'tag', '', 'Search input form', '2019-07-23 19:58:53'),
(1033, 'en', 'tag_search_result_date', 'tag', '', 'Search result date', '2019-07-23 19:58:53'),
(1034, 'en', 'tag_search_result_excerpt', 'tag', '', 'Search result excerpt', '2019-07-23 19:58:53'),
(1035, 'en', 'tag_search_result_title', 'tag', '', 'Search result title', '2019-07-23 19:58:53'),
(1036, 'en', 'tag_search_result_url', 'tag', '', 'Search result URL', '2019-07-23 19:58:53'),
(1037, 'en', 'tag_section', 'tag', '', 'Section', '2019-07-23 19:58:53'),
(1038, 'en', 'tag_section_list', 'tag', '', 'Section list', '2019-07-23 19:58:53'),
(1039, 'en', 'tag_site_name', 'tag', '', 'Site name', '2019-07-23 19:58:53'),
(1040, 'en', 'tag_site_slogan', 'tag', '', 'Site slogan', '2019-07-23 19:58:53'),
(1041, 'en', 'tag_title', 'tag', '', 'Title', '2019-07-23 19:58:53'),
(1042, 'en', 'textonly', 'tag', '', 'Text only?', '2019-07-23 19:58:53'),
(1043, 'en', 'this_section', 'tag', '', 'Link to current section?', '2019-07-23 19:58:53'),
(1044, 'en', 'time_any', 'tag', '', 'Any', '2019-07-23 19:58:53'),
(1045, 'en', 'time_format', 'tag', '', 'Time format string', '2019-07-23 19:58:53'),
(1046, 'en', 'time_future', 'tag', '', 'Future', '2019-07-23 19:58:53'),
(1047, 'en', 'time_past', 'tag', '', 'Past', '2019-07-23 19:58:53'),
(1048, 'en', 'title_separator', 'tag', '', 'Individual article and site name separator', '2019-07-23 19:58:53'),
(1049, 'en', 'tooltip', 'tag', '', 'Link tooltip', '2019-07-23 19:58:53'),
(1050, 'en', 'use_thumbnail', 'tag', '', 'Use thumbnail?', '2019-07-23 19:58:53'),
(1051, 'en', 'wraptag', 'tag', '', 'Wrap tag', '2019-07-23 19:58:53'),
(1052, 'en', 'rvm_css_dir', 'prefs', 'rvm_css', 'Stylesheet directory', '2019-07-23 22:34:19'),
(1053, 'en', 'arc_meta', 'admin', 'site', 'arc_meta', '2019-07-23 22:34:19'),
(1054, 'en', 'arc_meta_title', 'admin', 'site', 'Meta title', '2019-07-23 22:34:19'),
(1055, 'en', 'arc_meta_description', 'admin', 'site', 'Meta description', '2019-07-23 22:34:19'),
(1056, 'en', 'arc_meta_image', 'admin', 'site', 'Meta image', '2019-07-23 22:34:19'),
(1057, 'en', 'arc_meta_robots', 'admin', 'site', 'Meta robots', '2019-07-23 22:34:19'),
(1058, 'en', 'arc_meta_article_title', 'admin', 'site', 'Article page titles pattern', '2019-07-23 22:34:19'),
(1059, 'en', 'arc_meta_comment_title', 'admin', 'site', 'Comment page titles pattern', '2019-07-23 22:34:19'),
(1060, 'en', 'arc_meta_search_title', 'admin', 'site', 'Search page titles pattern', '2019-07-23 22:34:19'),
(1061, 'en', 'arc_meta_category_title', 'admin', 'site', 'Category titles pattern', '2019-07-23 22:34:19'),
(1062, 'en', 'arc_meta_section_title', 'admin', 'site', 'Section titles pattern', '2019-07-23 22:34:19'),
(1063, 'en', 'arc_meta_section_category_title', 'admin', 'site', 'Section category titles pattern', '2019-07-23 22:34:19'),
(1064, 'en', 'arc_meta_homepage_title', 'admin', 'site', 'Homepage title pattern', '2019-07-23 22:34:19'),
(1065, 'en', 'arc_meta_section_tab', 'admin', 'site', 'Additional panel location for sections meta data edit', '2019-07-23 22:34:19'),
(1066, 'en', 'arc_meta_section_tab_content', 'admin', 'site', 'Content', '2019-07-23 22:34:19'),
(1067, 'en', 'arc_meta_section_tab_extensions', 'admin', 'site', 'Extensions', '2019-07-23 22:34:19'),
(1068, 'en', 'arc_meta_section_tab_hidden', 'admin', 'site', 'Hidden', '2019-07-23 22:34:19');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(1069, 'en', 'ied_plugin_any', 'ied_plugin', 'site', 'Any', '2019-07-25 01:42:31'),
(1070, 'en', 'ied_plugin_auto_enable', 'ied_plugin', 'site', 'Auto-enable plugins on install', '2019-07-25 01:42:31'),
(1071, 'en', 'ied_plugin_cache_not_set', 'ied_plugin', 'site', 'Advanced pref \"plugin cache directory\" not defined', '2019-07-25 01:42:31'),
(1072, 'en', 'ied_plugin_check_type', 'ied_plugin', 'site', 'Check your plugin type!', '2019-07-25 01:42:31'),
(1073, 'en', 'ied_plugin_choose_file', 'ied_plugin', 'site', 'Please choose a file first', '2019-07-25 01:42:31'),
(1074, 'en', 'ied_plugin_code_dist', 'ied_plugin', 'site', 'Plugin code for distribution', '2019-07-25 01:42:31'),
(1075, 'en', 'ied_plugin_code_legend', 'ied_plugin', 'site', 'Plugin code', '2019-07-25 01:42:31'),
(1076, 'en', 'ied_plugin_code_save', 'ied_plugin', 'site', 'Save code', '2019-07-25 01:42:31'),
(1077, 'en', 'ied_plugin_code_saved', 'ied_plugin', 'site', 'Code saved', '2019-07-25 01:42:31'),
(1078, 'en', 'ied_plugin_code_saved_fail', 'ied_plugin', 'site', 'Code saving failed', '2019-07-25 01:42:31'),
(1079, 'en', 'ied_plugin_compress', 'ied_plugin', 'site', 'Zip', '2019-07-25 01:42:31'),
(1080, 'en', 'ied_plugin_cpanel_legend', 'ied_plugin', 'site', 'Installation', '2019-07-25 01:42:31'),
(1081, 'en', 'ied_plugin_create_new', 'ied_plugin', 'site', 'Create new plugin', '2019-07-25 01:42:31'),
(1082, 'en', 'ied_plugin_dbplugs_legend', 'ied_plugin', 'site', 'Plugins in database', '2019-07-25 01:42:31'),
(1083, 'en', 'ied_plugin_docs', 'ied_plugin', 'site', 'Docs', '2019-07-25 01:42:31'),
(1084, 'en', 'ied_plugin_docs_legend', 'ied_plugin', 'site', 'Plugin help', '2019-07-25 01:42:31'),
(1085, 'en', 'ied_plugin_edit', 'ied_plugin', 'site', 'Edit: {name} v{version}', '2019-07-25 01:42:31'),
(1086, 'en', 'ied_plugin_editing', 'ied_plugin', 'site', 'Editing {name} in Plugin Composer', '2019-07-25 01:42:31'),
(1087, 'en', 'ied_plugin_editor', 'ied_plugin', 'site', 'Plugin editor', '2019-07-25 01:42:31'),
(1088, 'en', 'ied_plugin_editor_options', 'ied_plugin', 'site', 'Plugin editor configuration options', '2019-07-25 01:42:31'),
(1089, 'en', 'ied_plugin_editor_path', 'ied_plugin', 'site', 'Plugin editor URL', '2019-07-25 01:42:31'),
(1090, 'en', 'ied_plugin_editor_width', 'ied_plugin', 'site', 'Plugin editor width', '2019-07-25 01:42:31'),
(1091, 'en', 'ied_plugin_edit_new', 'ied_plugin', 'site', 'Edit your new plugin', '2019-07-25 01:42:31'),
(1092, 'en', 'ied_plugin_enable', 'ied_plugin', 'site', 'Enabled', '2019-07-25 01:42:31'),
(1093, 'en', 'ied_plugin_export', 'ied_plugin', 'site', 'Export as {name}', '2019-07-25 01:42:31'),
(1094, 'en', 'ied_plugin_export_textpack', 'ied_plugin', 'site', 'Export textpack(s)', '2019-07-25 01:42:31'),
(1095, 'en', 'ied_plugin_export_zip', 'ied_plugin', 'site', 'Export as {name} (compressed)', '2019-07-25 01:42:31'),
(1096, 'en', 'ied_plugin_flags', 'ied_plugin', 'site', 'Flags', '2019-07-25 01:42:31'),
(1097, 'en', 'ied_plugin_flag_has_prefs', 'ied_plugin', 'site', 'Has prefs', '2019-07-25 01:42:31'),
(1098, 'en', 'ied_plugin_flag_lifecycle_notify', 'ied_plugin', 'site', 'Event notify', '2019-07-25 01:42:31'),
(1099, 'en', 'ied_plugin_fn_not_exist', 'ied_plugin', 'site', '(the function is probably javascript or mangled PHP)', '2019-07-25 01:42:31'),
(1100, 'en', 'ied_plugin_help_editor', 'ied_plugin', 'site', 'Help editor', '2019-07-25 01:42:31'),
(1101, 'en', 'ied_plugin_help_editor_options', 'ied_plugin', 'site', 'Help editor configuration options', '2019-07-25 01:42:31'),
(1102, 'en', 'ied_plugin_help_editor_path', 'ied_plugin', 'site', 'Help editor URL', '2019-07-25 01:42:31'),
(1103, 'en', 'ied_plugin_help_not_available', 'ied_plugin', 'site', 'Plugin help not available', '2019-07-25 01:42:31'),
(1104, 'en', 'ied_plugin_if_el_dist', 'ied_plugin', 'site', 'Code for distribution', '2019-07-25 01:42:31'),
(1105, 'en', 'ied_plugin_if_el_style', 'ied_plugin', 'site', 'Help style block', '2019-07-25 01:42:31'),
(1106, 'en', 'ied_plugin_if_settings', 'ied_plugin', 'site', 'Interface settings', '2019-07-25 01:42:31'),
(1107, 'en', 'ied_plugin_install_txt', 'ied_plugin', 'site', 'Install from .txt file', '2019-07-25 01:42:31'),
(1108, 'en', 'ied_plugin_interface_elems', 'ied_plugin', 'site', 'Optional interface elements', '2019-07-25 01:42:31'),
(1109, 'en', 'ied_plugin_jump_to_line', 'ied_plugin', 'site', 'Jump to line:', '2019-07-25 01:42:31'),
(1110, 'en', 'ied_plugin_langs_all', 'ied_plugin', 'site', 'All available', '2019-07-25 01:42:31'),
(1111, 'en', 'ied_plugin_langs_installed', 'ied_plugin', 'site', 'Only installed', '2019-07-25 01:42:31'),
(1112, 'en', 'ied_plugin_lang_choose', 'ied_plugin', 'site', 'Textpack language list', '2019-07-25 01:42:31'),
(1113, 'en', 'ied_plugin_lang_default', 'ied_plugin', 'site', 'Default textpack language', '2019-07-25 01:42:31'),
(1114, 'en', 'ied_plugin_lbl_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-23 22:34:19'),
(1115, 'en', 'ied_plugin_lbl_lc_delete', 'ied_plugin', 'site', 'Delete', '2019-07-25 01:42:31'),
(1116, 'en', 'ied_plugin_lbl_lc_disable', 'ied_plugin', 'site', 'Disable', '2019-07-25 01:42:31'),
(1117, 'en', 'ied_plugin_lbl_lc_enable', 'ied_plugin', 'site', 'Enable', '2019-07-25 01:42:31'),
(1118, 'en', 'ied_plugin_lbl_lc_install', 'ied_plugin', 'site', 'Install', '2019-07-25 01:42:31'),
(1119, 'en', 'ied_plugin_lbl_op_code_first', 'ied_plugin', 'site', 'Code first', '2019-07-25 01:42:31'),
(1120, 'en', 'ied_plugin_lbl_op_help_first', 'ied_plugin', 'site', 'Help first', '2019-07-25 01:42:31'),
(1121, 'en', 'ied_plugin_lbl_setup', 'ied_plugin', 'site', 'Plugin composer setup', '2019-07-25 01:42:31'),
(1122, 'en', 'ied_plugin_lifecycle_options', 'ied_plugin', 'site', 'Perform lifecycle actions on', '2019-07-25 01:42:31'),
(1123, 'en', 'ied_plugin_load', 'ied_plugin', 'site', 'Load', '2019-07-25 01:42:31'),
(1124, 'en', 'ied_plugin_load_order', 'ied_plugin', 'site', 'Load order', '2019-07-25 01:42:31'),
(1125, 'en', 'ied_plugin_load_order_help', 'ied_plugin', 'site', '(1=first > 5=normal > 9=last)', '2019-07-25 01:42:31'),
(1126, 'en', 'ied_plugin_meta_legend', 'ied_plugin', 'site', 'Meta information', '2019-07-25 01:42:31'),
(1127, 'en', 'ied_plugin_msgpop_lbl', 'ied_plugin', 'site', 'phpdoc block', '2019-07-25 01:42:31'),
(1128, 'en', 'ied_plugin_name_first', 'ied_plugin', 'site', 'Please name the plugin before creating it', '2019-07-25 01:42:31'),
(1129, 'en', 'ied_plugin_output_order', 'ied_plugin', 'site', 'PHP export order', '2019-07-25 01:42:31'),
(1130, 'en', 'ied_plugin_output_sfile', 'ied_plugin', 'site', 'Export plugin filename format', '2019-07-25 01:42:31'),
(1131, 'en', 'ied_plugin_output_sfilec', 'ied_plugin', 'site', 'Export compressed filename format', '2019-07-25 01:42:31'),
(1132, 'en', 'ied_plugin_output_sfilep', 'ied_plugin', 'site', 'Plugin template filename format', '2019-07-25 01:42:31'),
(1133, 'en', 'ied_plugin_output_sfilet', 'ied_plugin', 'site', 'Textpack filename format', '2019-07-25 01:42:31'),
(1134, 'en', 'ied_plugin_output_tmpcache', 'ied_plugin', 'site', 'Cache Textiled help path', '2019-07-25 01:42:31'),
(1135, 'en', 'ied_plugin_php_doc', 'ied_plugin', 'site', 'Make phpdoc', '2019-07-25 01:42:31'),
(1136, 'en', 'ied_plugin_prefs', 'ied_plugin', 'site', 'Preferences', '2019-07-25 01:42:31'),
(1137, 'en', 'ied_plugin_prefs_deleted', 'ied_plugin', 'site', 'Preferences deleted', '2019-07-25 01:42:31'),
(1138, 'en', 'ied_plugin_renamed', 'ied_plugin', 'site', '(renamed)', '2019-07-25 01:42:31'),
(1139, 'en', 'ied_plugin_rename_failed', 'ied_plugin', 'site', '(rename failed)', '2019-07-25 01:42:31'),
(1140, 'en', 'ied_plugin_rename_file', 'ied_plugin', 'site', 'Rename file', '2019-07-25 01:42:31'),
(1141, 'en', 'ied_plugin_restored', 'ied_plugin', 'site', 'Plugin {name} restored.', '2019-07-25 01:42:31'),
(1142, 'en', 'ied_plugin_restore_help', 'ied_plugin', 'site', 'Roll back the plugin code to the most recent restore point', '2019-07-25 01:42:31'),
(1143, 'en', 'ied_plugin_restore_point', 'ied_plugin', 'site', 'Restore point', '2019-07-25 01:42:31'),
(1144, 'en', 'ied_plugin_restore_verify', 'ied_plugin', 'site', 'Are you sure you want to rollback the {name} source code to its last restore point?', '2019-07-25 01:42:31'),
(1145, 'en', 'ied_plugin_run_from', 'ied_plugin', 'site', 'Les Plugins sont chargés depuis le cache', '2019-07-23 22:34:19'),
(1146, 'en', 'ied_plugin_run_install', 'ied_plugin', 'site', 'Perform post-install actions', '2019-07-25 01:42:31'),
(1147, 'en', 'ied_plugin_same', 'ied_plugin', 'site', 'Same', '2019-07-25 01:42:31'),
(1148, 'en', 'ied_plugin_save_as', 'ied_plugin', 'site', 'Save as {name}', '2019-07-25 01:42:31'),
(1149, 'en', 'ied_plugin_setup', 'ied_plugin', 'site', 'Setup', '2019-07-25 01:42:31'),
(1150, 'en', 'ied_plugin_syntax_check', 'ied_plugin', 'site', 'Syntax check on code save', '2019-07-25 01:42:31'),
(1151, 'en', 'ied_plugin_syntax_err', 'ied_plugin', 'site', 'Syntax error', '2019-07-25 01:42:31'),
(1152, 'en', 'ied_plugin_tp_legend', 'ied_plugin', 'site', 'Chaînes du Textpack', '2019-07-23 22:34:19'),
(1153, 'en', 'ied_plugin_tp_populate', 'ied_plugin', 'site', 'Load strings from function', '2019-07-25 01:42:31'),
(1154, 'en', 'ied_plugin_tp_prefix', 'ied_plugin', 'site', 'Textpack prefix', '2019-07-25 01:42:31'),
(1155, 'en', 'ied_plugin_type', 'ied_plugin', 'site', 'Plugin type', '2019-07-25 01:42:31'),
(1156, 'en', 'ied_plugin_type_0', 'ied_plugin', 'site', 'Public', '2019-07-25 01:42:31'),
(1157, 'en', 'ied_plugin_type_1', 'ied_plugin', 'site', 'Admin + Public', '2019-07-25 01:42:31'),
(1158, 'en', 'ied_plugin_type_2', 'ied_plugin', 'site', 'Library', '2019-07-25 01:42:31'),
(1159, 'en', 'ied_plugin_type_3', 'ied_plugin', 'site', 'Admin only', '2019-07-25 01:42:31'),
(1160, 'en', 'ied_plugin_type_4', 'ied_plugin', 'site', 'Admin (+AJAX)', '2019-07-25 01:42:31'),
(1161, 'en', 'ied_plugin_type_5', 'ied_plugin', 'site', 'Admin + Public (+AJAX)', '2019-07-25 01:42:31'),
(1162, 'en', 'ied_plugin_updated', 'ied_plugin', 'site', 'Plugin {name} updated', '2019-07-25 01:42:31'),
(1163, 'en', 'ied_plugin_uploaded', 'ied_plugin', 'site', 'Plugin {name} uploaded', '2019-07-25 01:42:31'),
(1164, 'en', 'ied_plugin_upload_php', 'ied_plugin', 'site', 'Upload plugin', '2019-07-25 01:42:31'),
(1165, 'en', 'ied_plugin_utils_legend', 'ied_plugin', 'site', 'Distribution', '2019-07-25 01:42:31'),
(1166, 'en', 'ied_plugin_view_help', 'ied_plugin', 'site', 'Help: {name}', '2019-07-25 01:42:31'),
(1167, 'el', 'active', 'common', '', 'Ενεργό', '2019-07-23 22:33:32'),
(1168, 'el', 'add', 'common', '', 'Προσθήκη', '2019-07-23 22:33:32'),
(1169, 'el', 'admin', 'common', '', 'Διαχείριση', '2019-07-23 22:33:32'),
(1170, 'el', 'ago', 'common', '', '{since} πριν', '2019-07-23 22:33:32'),
(1171, 'el', 'all', 'common', '', 'Όλα', '2019-07-23 22:33:32'),
(1172, 'el', 'are_you_sure', 'common', '', 'Είστε βέβαιος/η;', '2019-07-23 22:33:32'),
(1173, 'el', 'article', 'common', '', 'Άρθρο', '2019-07-23 22:33:32'),
(1174, 'el', 'articles', 'common', '', 'Άρθρα', '2019-07-23 22:33:32'),
(1175, 'el', 'article_image', 'common', '', 'Εικόνα άρθρου', '2019-07-23 22:33:32'),
(1176, 'el', 'any', 'common', '', 'Οποιαδήποτε', '2019-07-23 22:33:32'),
(1177, 'el', 'ascending', 'common', '', 'Αύξουσα σειρά', '2019-07-23 22:33:32'),
(1178, 'el', 'auth_required', 'common', '', 'Χρειάζεται πιστοποίηση χρήστη', '2019-07-23 22:33:32'),
(1179, 'el', 'author', 'common', '', 'Χρήστης', '2019-07-23 22:33:32'),
(1180, 'el', 'authors', 'common', '', 'Χρήστες', '2019-07-23 22:33:32'),
(1181, 'el', 'back_to_login', 'common', '', 'Επιστροφή στην Είσοδο του Textpattern', '2019-07-23 22:33:32'),
(1182, 'el', 'back_to_top', 'common', '', 'Επιστροφή στις κορυφή', '2019-07-23 22:33:32'),
(1183, 'el', 'bad_cookie', 'common', '', 'Έχει λήξει η συνεδρία σας. Παρακαλώ συνδεθείτε ξανά.', '2019-07-23 22:33:32'),
(1184, 'el', 'bad_login', 'common', '', 'Δεν είναι δυνατή η είσοδος στο σύστημα με αυτό το όνομα χρήστη και τον κωδικό πρόσβασης.', '2019-07-23 22:33:32'),
(1185, 'el', 'browse', 'common', '', 'Αναζήτηση', '2019-07-23 22:33:32'),
(1186, 'el', 'cancel', 'common', '', 'Άκυρο', '2019-07-23 22:33:32'),
(1187, 'el', 'caption', 'common', '', 'Λεζάντα', '2019-07-23 22:33:32'),
(1188, 'el', 'category', 'common', '', 'Κατηγορία', '2019-07-23 22:33:32'),
(1189, 'el', 'categories', 'common', '', 'Κατηγορίες', '2019-07-23 22:33:32'),
(1190, 'el', 'categorize', 'common', '', 'Κατηγοριοποίηση', '2019-07-23 22:33:32'),
(1191, 'el', 'category1', 'common', '', 'Κατηγορία 1', '2019-07-23 22:33:32'),
(1192, 'el', 'category2', 'common', '', 'Κατηγορία 2', '2019-07-23 22:33:32'),
(1193, 'el', 'choose', 'common', '', 'Επιλέξτε…', '2019-07-23 22:33:32'),
(1194, 'el', 'close', 'common', '', 'Κλείσιμο', '2019-07-23 22:33:32'),
(1195, 'el', 'code', 'common', '', 'Κώδικας', '2019-07-23 22:33:32'),
(1196, 'el', 'comment', 'common', '', 'Σχόλιο', '2019-07-23 22:33:32'),
(1197, 'el', 'comment_invitation', 'common', '', 'Πρόσκληση', '2019-07-23 22:33:32'),
(1198, 'el', 'comments', 'common', '', 'Σχόλια', '2019-07-23 22:33:32'),
(1199, 'el', 'comments_expired', 'common', '', 'Ο χρόνος για σχόλια έχει λήξει για αυτό το άρθρο.', '2019-07-23 22:33:32'),
(1200, 'el', 'confirm_delete_popup', 'common', '', 'Επιθυμείτε πραγματικά τη διαγραφή;', '2019-07-23 22:33:32'),
(1201, 'el', 'contact', 'common', '', 'Επικοινωνία με το συντάκτη', '2019-07-23 22:33:32'),
(1202, 'el', 'cookies_must_be_enabled', 'common', '', 'Τα \"cookies\" του φυλλομετρητή σας πρέπει να είναι ενεργοποιημένα για να χρησιμοποιήσετε το Textpattern.', '2019-07-23 22:33:32'),
(1203, 'el', 'copy', 'common', '', 'Αντιγραφή', '2019-07-23 22:33:32'),
(1204, 'el', 'could_not_log_in', 'common', '', 'Δεν είναι δυνατή η είσοδος στο σύστημα με αυτό το όνομα χρήστη και τον κωδικό πρόσβασης.', '2019-07-23 22:33:32'),
(1205, 'el', 'create', 'common', '', 'Δημιουργία', '2019-07-23 22:33:32'),
(1206, 'el', 'css', 'common', '', 'CSS', '2019-07-23 22:33:32'),
(1207, 'el', 'date', 'common', '', 'Ημ/νία', '2019-07-23 22:33:32'),
(1208, 'el', 'date_added', 'common', '', 'Η ημερομηνία προστέθηκε', '2019-07-23 22:33:32'),
(1209, 'el', 'dateformat', 'common', '', 'Μορφή ημερομηνίας', '2019-07-23 22:33:32'),
(1210, 'el', 'default', 'common', '', 'Προκαθορισμένο', '2019-07-23 22:33:32'),
(1211, 'el', 'delete', 'common', '', 'Διαγραφή', '2019-07-23 22:33:32'),
(1212, 'el', 'delete_selected', 'common', '', 'Διαγραφή επιλεγμένων', '2019-07-23 22:33:32'),
(1213, 'el', 'descending', 'common', '', 'Φθίνουσα σειρά', '2019-07-23 22:33:32'),
(1214, 'el', 'description', 'common', '', 'Περιγραφή', '2019-07-23 22:33:32'),
(1215, 'el', 'documentation', 'common', '', 'Τεκμηρίωση', '2019-07-23 22:33:32'),
(1216, 'el', 'download', 'common', '', 'Μεταφόρτωση', '2019-07-23 22:33:32'),
(1217, 'el', 'download_count', 'common', '', 'Αριθμός μεταφορτώσεων', '2019-07-23 22:33:32'),
(1218, 'el', 'downloads', 'common', '', 'Μεταφορτώσεις', '2019-07-23 22:33:32'),
(1219, 'el', 'duplicate', 'common', '', 'Αντίγραφο', '2019-07-23 22:33:32'),
(1220, 'el', 'edit', 'common', '', 'Επεξεργασία', '2019-07-23 22:33:32'),
(1221, 'el', 'email', 'common', '', 'Email', '2019-07-23 22:33:32'),
(1222, 'el', 'email_address', 'common', '', 'Διεύθυνση email', '2019-07-23 22:33:32'),
(1223, 'el', 'exact', 'common', '', 'Ακριβής', '2019-07-23 22:33:32'),
(1224, 'el', 'excerpt', 'common', '', 'Απόσπασμα', '2019-07-23 22:33:32'),
(1225, 'el', 'expired', 'common', '', 'Λήξαν', '2019-07-23 22:33:32'),
(1226, 'el', 'expires', 'common', '', 'Λήξη', '2019-07-23 22:33:32'),
(1227, 'el', 'expire_date', 'common', '', 'Ημερομηνία λήξης', '2019-07-23 22:33:32'),
(1228, 'el', 'expire_time', 'common', '', 'Ώρα λήξης', '2019-07-23 22:33:32'),
(1229, 'el', 'export', 'common', '', 'Εξαγωγή', '2019-07-23 22:33:32'),
(1230, 'el', 'extension', 'common', '', 'Επέκταση αρχείου', '2019-07-23 22:33:32'),
(1231, 'el', 'extensions', 'common', '', 'Επεκτάσεις', '2019-07-23 22:33:32'),
(1232, 'el', 'file', 'common', '', 'Αρχείο', '2019-07-23 22:33:32'),
(1233, 'el', 'file_download', 'common', '', 'Λήψη αρχείου', '2019-07-23 22:33:32'),
(1234, 'el', 'file_size', 'common', '', 'Μέγεθος αρχείου', '2019-07-23 22:33:32'),
(1235, 'el', 'forget', 'common', '', 'Ξέχασε', '2019-07-23 22:33:32'),
(1236, 'el', 'form', 'common', '', 'Φόρμα', '2019-07-23 22:33:32'),
(1237, 'el', 'form_submission_error', 'common', '', 'Συγγνώμη, η φόρμα δεν μπορούσε να υποβληθεί. Παρακαλώ προσπαθήστε ξανά αργότερα.', '2019-07-23 22:33:32'),
(1238, 'el', 'forms', 'common', '', 'Φόρμες', '2019-07-23 22:33:32'),
(1239, 'el', 'general_error', 'common', '', 'Γενικό σφάλμα', '2019-07-23 22:33:32'),
(1240, 'el', 'go', 'common', '', 'Πήγαινε', '2019-07-23 22:33:32'),
(1241, 'el', 'go_back', 'common', '', 'Επιστροφή', '2019-07-23 22:33:32'),
(1242, 'el', 'go_content', 'common', '', 'Mετάβαση στο περιεχόμενο', '2019-07-23 22:33:32'),
(1243, 'el', 'go_nav', 'common', '', 'Μετάβαση στο «menu» επιλογής', '2019-07-23 22:33:32'),
(1244, 'el', 'go_search', 'common', '', 'Mετάβαση στην αναζήτηση', '2019-07-23 22:33:32'),
(1245, 'el', 'go_to', 'common', '', 'Πήγαινε σε', '2019-07-23 22:33:32'),
(1246, 'el', 'go_txp_com', 'common', '', 'Μεταβείτε στον ιστοχώρο του Textpattern', '2019-07-23 22:33:32'),
(1247, 'el', 'height', 'common', '', 'Ύψος', '2019-07-23 22:33:32'),
(1248, 'el', 'help', 'common', '', 'Βοήθεια', '2019-07-23 22:33:32'),
(1249, 'el', 'hidden', 'common', '', 'Κρυφό', '2019-07-23 22:33:32'),
(1250, 'el', 'home', 'common', '', 'Αρχική σελίδα', '2019-07-23 22:33:32'),
(1251, 'el', 'host', 'common', '', 'Δ/νση υπολογιστή', '2019-07-23 22:33:32'),
(1252, 'el', 'id', 'common', '', 'ΑΑ#', '2019-07-23 22:33:32'),
(1253, 'el', 'image', 'common', '', 'Εικόνα', '2019-07-23 22:33:32'),
(1254, 'el', 'images', 'common', '', 'Εικόνες', '2019-07-23 22:33:32'),
(1255, 'el', 'import', 'common', '', 'Εισαγωγή', '2019-07-23 22:33:32'),
(1256, 'el', 'input_day', 'common', '', 'Ημέρα', '2019-07-23 22:33:32'),
(1257, 'el', 'input_hour', 'common', '', 'Ώρα', '2019-07-23 22:33:32'),
(1258, 'el', 'input_minute', 'common', '', 'Λεπτό', '2019-07-23 22:33:32'),
(1259, 'el', 'input_month', 'common', '', 'Μήνας', '2019-07-23 22:33:32'),
(1260, 'el', 'input_second', 'common', '', 'Δευτερόλεπτο', '2019-07-23 22:33:32'),
(1261, 'el', 'input_year', 'common', '', 'Έτος', '2019-07-23 22:33:32'),
(1262, 'el', 'install', 'common', '', 'Εγκατάσταση', '2019-07-23 22:33:32'),
(1263, 'el', 'internal_error', 'common', '', 'Εσωτερικό σφάλμα', '2019-07-23 22:33:32'),
(1264, 'el', 'keywords', 'common', '', 'Λέξεις κλειδιά', '2019-07-23 22:33:32'),
(1265, 'el', 'label', 'common', '', 'Ετικέτα', '2019-07-23 22:33:32'),
(1266, 'el', 'last_modification', 'common', '', 'Τελευταία τροποποίηση', '2019-07-23 22:33:32'),
(1267, 'el', 'last_modified', 'common', '', 'Τελευταία τροποποίηση', '2019-07-23 22:33:32'),
(1268, 'el', 'link', 'common', '', 'Σύνδεσμος μόνο', '2019-07-23 22:33:32'),
(1269, 'el', 'links', 'common', '', 'Σύνδεσμοι', '2019-07-23 22:33:32'),
(1270, 'el', 'list', 'common', '', 'Λίστα', '2019-07-23 22:33:32'),
(1271, 'el', 'list_articles', 'common', '', 'Λίστα άρθρων', '2019-07-23 22:33:32'),
(1272, 'el', 'list_categories', 'common', '', 'Λίστα κατηγοριών', '2019-07-23 22:33:32'),
(1273, 'el', 'list_links', 'common', '', 'Λίστα συνδέσμων', '2019-07-23 22:33:32'),
(1274, 'el', 'list_options', 'common', '', 'Επιλογές λίστας', '2019-07-23 22:33:32'),
(1275, 'el', 'logged_in_as', 'common', '', 'Πρόσβαση με όνομα χρήστη', '2019-07-23 22:33:32'),
(1276, 'el', 'login', 'common', '', 'Όνομα χρήστη', '2019-07-23 22:33:32'),
(1277, 'el', 'login_to_textpattern', 'common', '', 'Πρόσβαση στη διαχείριση του Textpattern', '2019-07-23 22:33:32'),
(1278, 'el', 'logout', 'common', '', 'Έξοδος', '2019-07-23 22:33:32'),
(1279, 'el', 'log_in_button', 'common', '', 'Είσοδος', '2019-07-23 22:33:32'),
(1280, 'el', 'manual', 'common', '', 'Οδηγίες χρήσης', '2019-07-23 22:33:32'),
(1281, 'el', 'message', 'common', '', 'Μήνυμα', '2019-07-23 22:33:32'),
(1282, 'el', 'method', 'common', '', 'Μέθοδος', '2019-07-23 22:33:32'),
(1283, 'el', 'misc', 'common', '', 'Διάφορα', '2019-07-23 22:33:32'),
(1284, 'el', 'modified', 'common', '', 'Τροποποιήθηκε', '2019-07-23 22:33:32'),
(1285, 'el', 'modified_by', 'common', '', 'Τελευταία τροποποίηση από', '2019-07-23 22:33:32'),
(1286, 'el', 'month', 'common', '', 'Μήνας', '2019-07-23 22:33:32'),
(1287, 'el', 'more', 'common', '', 'Περισσότερα', '2019-07-23 22:33:32'),
(1288, 'el', 'navigation', 'common', '', 'Πλοήγηση', '2019-07-23 22:33:32'),
(1289, 'el', 'name', 'common', '', 'Όνομα', '2019-07-23 22:33:32'),
(1290, 'el', 'never', 'common', '', 'Ποτέ', '2019-07-23 22:33:32'),
(1291, 'el', 'newer', 'common', '', 'Νεότερα', '2019-07-23 22:33:32'),
(1292, 'el', 'next', 'common', '', 'Επόμενο', '2019-07-23 22:33:32'),
(1293, 'el', 'no', 'common', '', 'Όχι', '2019-07-23 22:33:32'),
(1294, 'el', 'no_results_found', 'common', '', 'Δεν βρέθηκαν αποτελέσματα.', '2019-07-23 22:33:32'),
(1295, 'el', 'none', 'common', '', 'Κανένα', '2019-07-23 22:33:32'),
(1296, 'el', 'of', 'common', '', 'από', '2019-07-23 22:33:32'),
(1297, 'el', 'off', 'common', '', 'Όχι', '2019-07-23 22:33:32'),
(1298, 'el', 'older', 'common', '', 'Παλαιότερα', '2019-07-23 22:33:32'),
(1299, 'el', 'on', 'common', '', 'Ναι', '2019-07-23 22:33:32'),
(1300, 'el', 'opens_external_link', 'common', '', '(ανοίγει ένα εξωτερικό σύνδεσμο σε ένα νέο παράθυρο)', '2019-07-23 22:33:32'),
(1301, 'el', 'options', 'common', '', 'Επιλογές', '2019-07-23 22:33:32'),
(1302, 'el', 'page', 'common', '', 'Σελίδα', '2019-07-23 22:33:32'),
(1303, 'el', 'pages', 'common', '', 'Σελίδες', '2019-07-23 22:33:32'),
(1304, 'el', 'password', 'common', '', 'Κωδικός πρόσβασης', '2019-07-23 22:33:32'),
(1305, 'el', 'password_forgotten', 'common', '', 'Ξεχάσατε τον κωδικό πρόσβασης;', '2019-07-23 22:33:32'),
(1306, 'el', 'permlink', 'common', '', 'Μόνιμος σύνδεσμος', '2019-07-23 22:33:32'),
(1307, 'el', 'pophelp', 'common', '', 'Pop-up συνδέσεις για βοήθεια', '2019-07-23 22:33:32'),
(1308, 'el', 'post', 'common', '', 'Δημοσίευση', '2019-07-23 22:33:32'),
(1309, 'el', 'posted', 'common', '', 'Ημ/νία', '2019-07-23 22:33:32'),
(1310, 'el', 'posted_by', 'common', '', 'Δημοσιεύτηκε από', '2019-07-23 22:33:32'),
(1311, 'el', 'prev', 'common', '', 'Προηγούμενα', '2019-07-23 22:33:32'),
(1312, 'el', 'preview', 'common', '', 'Προεπισκόπηση', '2019-07-23 22:33:32'),
(1313, 'el', 'publish', 'common', '', 'Δημοσίευση', '2019-07-23 22:33:32'),
(1314, 'el', 'published_at', 'common', '', 'Δημοσιεύτηκε στις', '2019-07-23 22:33:32'),
(1315, 'el', 'published_with', 'common', '', 'Δημοσιεύθηκε με', '2019-07-23 22:33:32'),
(1316, 'el', 'publish_date', 'common', '', 'Ημερομηνία δημοσίευσης', '2019-07-23 22:33:32'),
(1317, 'el', 'publish_time', 'common', '', 'Ώρα δημοσίευσης', '2019-07-23 22:33:32'),
(1318, 'el', 'range', 'common', '', 'Φάσμα', '2019-07-23 22:33:32'),
(1319, 'el', 'recently', 'common', '', 'Πρόσφατα', '2019-07-23 22:33:32'),
(1320, 'el', 'recent_articles', 'common', '', 'Πρόσφατα άρθρα', '2019-07-23 22:33:32'),
(1321, 'el', 'recent_comments', 'common', '', 'Πρόσφατα σχόλια', '2019-07-23 22:33:32'),
(1322, 'el', 'recent_posts', 'common', '', 'Πρόσφατες δημοσιεύσεις', '2019-07-23 22:33:32'),
(1323, 'el', 'related_articles', 'common', '', 'Σχετικά άρθρα', '2019-07-23 22:33:32'),
(1324, 'el', 'reload', 'common', '', 'Ανανέωση', '2019-07-23 22:33:32'),
(1325, 'el', 'remember', 'common', '', 'Θυμήσου', '2019-07-23 22:33:32'),
(1326, 'el', 'remove', 'common', '', 'Αφαιρώ', '2019-07-23 22:33:32'),
(1327, 'el', 'required', 'common', '', 'Υποχρεωτικό', '2019-07-23 22:33:32'),
(1328, 'el', 'reset', 'common', '', 'Επαναφορά', '2019-07-23 22:33:32'),
(1329, 'el', 'restricted_area', 'common', '', 'Περιορισμένη περιοχή.', '2019-07-23 22:33:32'),
(1330, 'el', 'revert', 'common', '', 'Αναίρεση αλλαγών', '2019-07-23 22:33:32'),
(1331, 'el', 'salutation', 'common', '', 'Αγαπητέ/ή {name},', '2019-07-23 22:33:32'),
(1332, 'el', 'save', 'common', '', 'Αποθήκευση', '2019-07-23 22:33:32'),
(1333, 'el', 'save_new', 'common', '', 'Αποθήκευση νέου', '2019-07-23 22:33:32'),
(1334, 'el', 'search', 'common', '', 'Αναζήτηση', '2019-07-23 22:33:32'),
(1335, 'el', 'search_results', 'common', '', 'Αποτελέσματα αναζήτησης', '2019-07-23 22:33:32'),
(1336, 'el', 'section', 'common', '', 'Τομέας', '2019-07-23 22:33:32'),
(1337, 'el', 'sections', 'common', '', 'Τομείς', '2019-07-23 22:33:32'),
(1338, 'el', 'select', 'common', '', 'Επιλογή', '2019-07-23 22:33:32'),
(1339, 'el', 'selected', 'common', '', 'Επιλεγμένο', '2019-07-23 22:33:32'),
(1340, 'el', 'site', 'common', '', 'Ιστότοπος', '2019-07-23 22:33:32'),
(1341, 'el', 'sitename', 'common', '', 'Όνομα ιστότοπου', '2019-07-23 22:33:32'),
(1342, 'el', 'siteurl', 'common', '', 'URL ιστοσελίδας', '2019-07-23 22:33:32'),
(1343, 'el', 'site_slogan', 'common', '', 'Σλόγκαν ιστοτόπου', '2019-07-23 22:33:32'),
(1344, 'el', 'spam', 'common', '', 'Spam', '2019-07-23 22:33:32'),
(1345, 'el', 'status', 'common', '', 'Κατάσταση', '2019-07-23 22:33:32'),
(1346, 'el', 'stay_logged_in', 'common', '', 'Να με θυμάσαι με αυτό τον φυλλομετρητή', '2019-07-23 22:33:32'),
(1347, 'el', 'submit', 'common', '', 'Υποβολή', '2019-07-23 22:33:32'),
(1348, 'el', 'thumbnail', 'common', '', 'Εικονίδιο', '2019-07-23 22:33:32'),
(1349, 'el', 'time', 'common', '', 'Ώρα', '2019-07-23 22:33:32'),
(1350, 'el', 'title', 'common', '', 'Τίτλος', '2019-07-23 22:33:32'),
(1351, 'el', 'toggle_all_selected', 'common', '', 'Εναλλαγή όλων/καμία επιλογή', '2019-07-23 22:33:32'),
(1352, 'el', 'txt_quote_double_close', 'common', '', '»', '2019-07-23 22:33:32'),
(1353, 'el', 'txt_quote_double_open', 'common', '', '«', '2019-07-23 22:33:32'),
(1354, 'el', 'txt_quote_single_close', 'common', '', '‘', '2019-07-23 22:33:32'),
(1355, 'el', 'txt_quote_single_open', 'common', '', '’', '2019-07-23 22:33:32'),
(1356, 'el', 'type', 'common', '', 'Τύπος', '2019-07-23 22:33:32'),
(1357, 'el', 'undefined', 'common', '', 'Μη ορισμένο', '2019-07-23 22:33:32'),
(1358, 'el', 'unknown', 'common', '', 'Άγνωστος', '2019-07-23 22:33:32'),
(1359, 'el', 'units_b', 'common', '', 'B', '2019-07-23 22:33:32'),
(1360, 'el', 'units_e', 'common', '', 'EB', '2019-07-23 22:33:32'),
(1361, 'el', 'units_g', 'common', '', 'GB', '2019-07-23 22:33:32'),
(1362, 'el', 'units_k', 'common', '', 'kB', '2019-07-23 22:33:32'),
(1363, 'el', 'units_m', 'common', '', 'MB', '2019-07-23 22:33:32'),
(1364, 'el', 'units_p', 'common', '', 'PB', '2019-07-23 22:33:32'),
(1365, 'el', 'units_t', 'common', '', 'TB', '2019-07-23 22:33:32'),
(1366, 'el', 'units_y', 'common', '', 'YB', '2019-07-23 22:33:32'),
(1367, 'el', 'units_z', 'common', '', 'ZB', '2019-07-23 22:33:32'),
(1368, 'el', 'untitled', 'common', '', 'Χωρίς τίτλο', '2019-07-23 22:33:32'),
(1369, 'el', 'update', 'common', '', 'Ενημέρωση', '2019-07-23 22:33:32'),
(1370, 'el', 'upload', 'common', '', 'Μεταφόρτωση', '2019-07-23 22:33:32'),
(1371, 'el', 'upload_err_cant_write', 'common', '', 'Σφάλμα στην εγγραφή αρχείου.', '2019-07-23 22:33:32'),
(1372, 'el', 'upload_err_extension', 'common', '', 'Η εγγραφή αρχείου τερματίστηκε από επέκταση του PHP.', '2019-07-23 22:33:32'),
(1373, 'el', 'upload_err_form_size', 'common', '', 'Το μέγεθος του αρχείου ξεπερνά το όριο που έχει οριστεί στις ρυθμίσεις του Textpattern.', '2019-07-23 22:33:32'),
(1374, 'el', 'upload_err_ini_size', 'common', '', 'Το μέγεθος του αρχείου ξεπερνά το όριο που έχει οριστεί <code>upload_max_filesize</code> στο <code>php.ini</code>.', '2019-07-23 22:33:32'),
(1375, 'el', 'upload_err_no_file', 'common', '', 'Δεν έχει οριστεί αρχείο.', '2019-07-23 22:33:32'),
(1376, 'el', 'upload_err_partial', 'common', '', 'Το αρχείο φορτώθηκε μόνον εν μέρει.', '2019-07-23 22:33:32'),
(1377, 'el', 'upload_err_tmp_dir', 'common', '', 'Δεν βρέθηκε κανένας έγκυρος προσωρινός φάκελος. Παρακαλούμε συμβουλευτείτε τον διαχειριστή του ιστοχώρου σας.', '2019-07-23 22:33:32'),
(1378, 'el', 'upload_file', 'common', '', 'Ανεβάστε αρχεία', '2019-07-23 22:33:32'),
(1379, 'el', 'url', 'common', '', 'URL', '2019-07-23 22:33:32'),
(1380, 'el', 'value', 'common', '', 'Περιεχόμενο', '2019-07-23 22:33:32'),
(1381, 'el', 'version', 'common', '', 'Έκδοση', '2019-07-23 22:33:32'),
(1382, 'el', 'view', 'common', '', 'Προβολή', '2019-07-23 22:33:32'),
(1383, 'el', 'viewsite', 'common', '', 'Προβολή ιστοτόπου', '2019-07-23 22:33:32'),
(1384, 'el', 'website', 'common', '', 'Ιστότοπος', '2019-07-23 22:33:32'),
(1385, 'el', 'width', 'common', '', 'Πλάτος', '2019-07-23 22:33:32'),
(1386, 'el', 'with_selected', 'common', '', 'Με τα επιλεγμένα:', '2019-07-23 22:33:32'),
(1387, 'el', 'with_selected_option', 'common', '', 'Με {count} επέλεξε…', '2019-07-23 22:33:32'),
(1388, 'el', 'yes', 'common', '', 'Ναι', '2019-07-23 22:33:32'),
(1389, 'el', 'lang_name', 'common', '', 'Ελληνικά', '2019-07-23 22:33:32'),
(1390, 'el', 'lang_code', 'common', '', 'el', '2019-07-23 22:33:32'),
(1391, 'el', 'lang_dir', 'common', '', 'ltr', '2019-07-23 22:33:32'),
(1392, 'el', 'account_activation', 'admin', '', 'Παρακαλούμε να ενεργοποιήσετε το λογαριασμό σας', '2019-07-23 22:33:32'),
(1393, 'el', 'account_activation_confirmation', 'admin', '', 'Παρακαλούμε χρησιμοποιήστε την παρακάτω σύνδεση για να ενεργοποιήσετε το λογαριασμό και τον κωδικό πρόσβασης σας.', '2019-07-23 22:33:32'),
(1394, 'el', 'alias_is_taken', 'admin', '', 'Το ψευδώνυμο ήδη υπάρχει.', '2019-07-23 22:33:32'),
(1395, 'el', 'assign_assets_to', 'admin', '', 'Αναθέσατε το περιεχόμενο του χρήστη στον', '2019-07-23 22:33:32'),
(1396, 'el', 'author_already_exists', 'admin', '', 'Το όνομα του χρήστη <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1397, 'el', 'author_deleted', 'admin', '', 'Ο συγγραφέας <strong>{name}</strong> έχει διαγραφεί.', '2019-07-23 22:33:32'),
(1398, 'el', 'author_save_failed', 'admin', '', 'Η αποθήκευση του συγγραφέα <strong>{name}</strong> δεν έγινε.', '2019-07-23 22:33:32'),
(1399, 'el', 'author_updated', 'admin', '', 'Ο συγγραφέας <strong>{name}</strong> έχει διαγραφεί.', '2019-07-23 22:33:32'),
(1400, 'el', 'cannot_assign_assets_to_deletee', 'admin', '', 'Δεν μπορείτε να αναθέσετε τα στοιχεία σε διαγραμμένο λογαριασμό.', '2019-07-23 22:33:32'),
(1401, 'el', 'changeprivilege', 'admin', '', 'Αλλάξετε τα προνόμια', '2019-07-23 22:33:32'),
(1402, 'el', 'change_email_address', 'admin', '', 'Αλλάξτε τη διεύθυνση του email σας', '2019-07-23 22:33:32'),
(1403, 'el', 'change_password', 'admin', '', 'Αλλάξτε τον κωδικό πρόσβασης σας', '2019-07-23 22:33:32'),
(1404, 'el', 'copy_editor', 'admin', '', 'Διορθωτής κειμένων', '2019-07-23 22:33:32'),
(1405, 'el', 'could_not_mail', 'admin', '', 'Δεν είναι δυνατή η αποστολή email', '2019-07-23 22:33:32'),
(1406, 'el', 'could_not_update_author', 'admin', '', 'Δεν είναι δυνατή η ενημέρωση των πληροφοριών του χρήστη.', '2019-07-23 22:33:32'),
(1407, 'el', 'create_author', 'admin', '', 'Νέος συγγραφέας', '2019-07-23 22:33:32'),
(1408, 'el', 'current_password', 'admin', '', 'Τρέχων Κωδικός Πρόσβασης', '2019-07-23 22:33:32'),
(1409, 'el', 'designer', 'admin', '', 'Σχεδιαστής', '2019-07-23 22:33:32'),
(1410, 'el', 'edit_author', 'admin', '', 'Αλλάξετε τα δεδομένα του χρήστη', '2019-07-23 22:33:32'),
(1411, 'el', 'email_changed', 'admin', '', 'Η διεύθυνση email άλλαξε σε <strong>{email}</strong>.', '2019-07-23 22:33:32'),
(1412, 'el', 'error_adding_new_author', 'admin', '', 'Σφάλμα στην προσθήκη νέου χρήστη.', '2019-07-23 22:33:32'),
(1413, 'el', 'from_or_to_address_missing', 'admin', '', 'Λείπει η διεύθυνση αποστολέα ή παραλήπτη.', '2019-07-23 22:33:32'),
(1414, 'el', 'freelancer', 'admin', '', 'Ελεύθερος συνεργάτης', '2019-07-23 22:33:32'),
(1415, 'el', 'invalid_header', 'admin', '', 'Η κεφαλίδα αλληλογραφίας δεν είναι έγκυρη.', '2019-07-23 22:33:32'),
(1416, 'el', 'invalid_token', 'admin', '', 'Η επαναφορά του κωδικού πρόσβασης δεν είναι έγκυρη λόγω του διακριτικού ασφαλείας.', '2019-07-23 22:33:32'),
(1417, 'el', 'last_login', 'admin', '', 'Τελευταία σύνδεση', '2019-07-23 22:33:32'),
(1418, 'el', 'link_expires', 'admin', '', 'Έχετε να απαντήσετε μέχρι {day} {month} {year}, {time} πριν τη λήξη του συνδέσμου.', '2019-07-23 22:33:32'),
(1419, 'el', 'login_name', 'admin', '', 'Σύνδεση', '2019-07-23 22:33:32'),
(1420, 'el', 'login_sent_to', 'admin', '', 'Ο κωδικός πρόσβασης στάλθηκε στο {email}.', '2019-07-23 22:33:32'),
(1421, 'el', 'log_in_at', 'admin', '', 'Τόπος πρόσβασης:', '2019-07-23 22:33:32'),
(1422, 'el', 'managing_editor', 'admin', '', 'Αρχισυντάκτης', '2019-07-23 22:33:32'),
(1423, 'el', 'must_reassign_assets', 'admin', '', 'Παρακαλώ αναθέστε το περιεχόμενο σε νέο χρήστη', '2019-07-23 22:33:32'),
(1424, 'el', 'new_email', 'admin', '', 'Νέα διεύθυνση email', '2019-07-23 22:33:32'),
(1425, 'el', 'new_password', 'admin', '', 'Νέος κωδικός πρόσβασης', '2019-07-23 22:33:32'),
(1426, 'el', 'password_changed', 'admin', '', 'Ο κωδικός πρόσβασης τροποποιήθηκε.', '2019-07-23 22:33:32'),
(1427, 'el', 'password_change_confirmation', 'admin', '', 'Έχει αλλάξει ο κωδικός πρόσβασης σας. Αν δεν ζητήσατε αλλαγή στον κωδικό πρόσβασης σας, επικοινωνήστε με το διαχειριστή του ιστοτόπου.', '2019-07-23 22:33:32'),
(1428, 'el', 'password_confirm_button', 'admin', '', 'Ορίσατε τον κωδικό πρόσβασης', '2019-07-23 22:33:32'),
(1429, 'el', 'password_invalid', 'admin', '', 'Ο κωδικός πρόσβασης είναι εσφαλμένος.', '2019-07-23 22:33:32'),
(1430, 'el', 'password_required', 'admin', '', 'Παρακαλούμε εισάγετε ένα νέο κωδικό πρόσβασης.', '2019-07-23 22:33:32'),
(1431, 'el', 'password_reset', 'admin', '', 'Ανανέωση κωδικού πρόσβασης', '2019-07-23 22:33:32'),
(1432, 'el', 'password_reset_button', 'admin', '', 'Ανανέωση κωδικού πρόσβασης', '2019-07-23 22:33:32'),
(1433, 'el', 'password_reset_confirmation', 'admin', '', 'Κάποιος (μάλλον εσείς) έχει ζητήσει την ανανέωση του κωδικού πρόσβασης. Παρακαλείστε να επιβεβαιώστε την ανανέωση ακολουθώντας τη σύνδεση πιο κάτω.', '2019-07-23 22:33:32'),
(1434, 'el', 'password_reset_confirmation_request', 'admin', '', 'Παρακαλείστε να επιβεβαιώστε την ανανέωση του κωδικού πρόσβασης ακολουθώντας τον σύνδεσμο στο email που στας στάλθηκε', '2019-07-23 22:33:32'),
(1435, 'el', 'password_reset_confirmation_request_sent', 'admin', '', 'Ένα μήνυμα email με πληροφορίες πρόσβασης έχει αποσταλεί στο email σας. Ελέγξτε το email σας, και ακολουθήστε της οδηγίες.', '2019-07-23 22:33:32'),
(1436, 'el', 'password_sent_to', 'admin', '', 'Ο κωδικός πρόσβασης απεστάλη στον/στη', '2019-07-23 22:33:32'),
(1437, 'el', 'password_set', 'admin', '', 'Έχει οριστεί ο κωδικός πρόσβασης σας', '2019-07-23 22:33:32'),
(1438, 'el', 'password_set_confirmation', 'admin', '', 'Έχει οριστεί νέος κωδικός πρόσβασης για σας. Αν δεν το ζητήσατε αυτό, επικοινωνήστε με το διαχειριστή του ιστοτόπου.', '2019-07-23 22:33:32'),
(1439, 'el', 'privileges', 'admin', '', 'Προνόμια', '2019-07-23 22:33:32'),
(1440, 'el', 'privs_none', 'admin', '', 'Χωρίς προνόμια', '2019-07-23 22:33:32'),
(1441, 'el', 'publisher', 'admin', '', 'Εκδότης', '2019-07-23 22:33:32'),
(1442, 'el', 'real_name', 'admin', '', 'Αληθινό όνομα', '2019-07-23 22:33:32'),
(1443, 'el', 'resend_activation', 'admin', '', 'Νέα αποστολή της σύνδεσης ενεργοποίησης', '2019-07-23 22:33:32'),
(1444, 'el', 'resend_activation_request_sent', 'admin', '', 'Το μήνυμα ενεργοποίησης στάλθηκε στον/στην {name}.', '2019-07-23 22:33:32'),
(1445, 'el', 'resetpassword', 'admin', '', 'Ανανέωση κωδικού πρόσβασης', '2019-07-23 22:33:32'),
(1446, 'el', 'search_users', 'admin', '', 'Αναζήτηση χρηστών', '2019-07-23 22:33:32'),
(1447, 'el', 'sending_failed', 'admin', '', 'Το μήνυμα απέτυχε να σταλθεί.', '2019-07-23 22:33:32'),
(1448, 'el', 'set_password', 'admin', '', 'Ορίστε τον κωδικό πρόσβασης', '2019-07-23 22:33:32'),
(1449, 'el', 'show_password', 'admin', '', 'Εμφάνιση κωδικού', '2019-07-23 22:33:32'),
(1450, 'el', 'staff_writer', 'admin', '', 'Μόνιμος συντάκτης', '2019-07-23 22:33:32'),
(1451, 'el', 'token_expired', 'admin', '', 'Έχει λήξει το διακριτικό ασφαλείας της επαναφοράς του κωδικού πρόσβασης.', '2019-07-23 22:33:32'),
(1452, 'el', 'unable_retrieve_template', 'admin', '', 'Δεν είναι δυνατή η ανάκτηση προτύπου.', '2019-07-23 22:33:32'),
(1453, 'el', 'unable_retrieve_user', 'admin', '', 'Δεν είναι δυνατή η ανάκτηση του χρήστη.', '2019-07-23 22:33:32'),
(1454, 'el', 'unable_set_template', 'admin', '', 'Δεν είναι δυνατό να ορίσετε πρότυπο.', '2019-07-23 22:33:32'),
(1455, 'el', 'your_login_info', 'admin', '', 'Οι πληροφορίες για την πρόσβασή σας', '2019-07-23 22:33:32'),
(1456, 'el', 'your_login_is', 'admin', '', 'Το όνομα χρήστη σας είναι', '2019-07-23 22:33:32'),
(1457, 'el', 'your_new_password', 'admin', '', 'Ο νέος σας κωδικός πρόσβασης', '2019-07-23 22:33:32'),
(1458, 'el', 'your_password_is', 'admin', '', 'Ο κωδικός πρόσβασης σας', '2019-07-23 22:33:32'),
(1459, 'el', 'you_have_been_registered', 'admin', '', 'Έχετε εγγραφεί ως χρήστης στον ιστότοπο', '2019-07-23 22:33:32'),
(1460, 'el', 'advanced_options', 'admin-side', '', 'Προχωρημένες επιλογές', '2019-07-23 22:33:32'),
(1461, 'el', 'auto_dst', 'admin-side', '', 'Προσαρμόσετε αυτόματα τη ρύθμιση χειμερινής / θερινής ώρας;', '2019-07-23 22:33:32'),
(1462, 'el', 'body', 'admin-side', '', 'Σώμα', '2019-07-23 22:33:32'),
(1463, 'el', 'cannot_delete', 'admin-side', '', 'Δεν είναι δυνατή η διαγραφή του {thing}.', '2019-07-23 22:33:32'),
(1464, 'el', 'cannot_instantiate_theme', 'admin-side', '', 'Το Θέμα <strong>{name}</strong> ({class}) απέτυχε να φορτωθεί σωστά από το αρχείο {path}. Θα χρησιμοποιηθεί το προεπιλεγμένο θέμα.', '2019-07-23 22:33:32'),
(1465, 'el', 'changeauthor', 'admin-side', '', 'Αλλαγή χρήστη', '2019-07-23 22:33:32'),
(1466, 'el', 'changecategory', 'admin-side', '', 'Αλλάξετε Κατηγορία', '2019-07-23 22:33:32'),
(1467, 'el', 'changecategory1', 'admin-side', '', 'Αλλαγή Κατηγορίας 1', '2019-07-23 22:33:32'),
(1468, 'el', 'changecategory2', 'admin-side', '', 'Αλλαγή Κατηγορίας 2', '2019-07-23 22:33:32'),
(1469, 'el', 'changecomments', 'admin-side', '', 'Τροποποίηση σχολίων', '2019-07-23 22:33:32'),
(1470, 'el', 'changeparent', 'admin-side', '', 'Αλλάξετε γονέα', '2019-07-23 22:33:32'),
(1471, 'el', 'changesection', 'admin-side', '', 'Αλλαγή τομέα', '2019-07-23 22:33:32'),
(1472, 'el', 'changestatus', 'admin-side', '', 'Αλλαγή κατάστασης', '2019-07-23 22:33:32'),
(1473, 'el', 'collapse_all', 'admin-side', '', 'Σύμπτυξη όλων', '2019-07-23 22:33:32'),
(1474, 'el', 'convert_linebreaks', 'admin-side', '', 'Μετατροπή των νέων γραμμών', '2019-07-23 22:33:32'),
(1475, 'el', 'create_article', 'admin-side', '', 'Νέο άρθρο', '2019-07-23 22:33:32'),
(1476, 'el', 'custom', 'admin-side', '', 'Προσαρμοσμένα πεδία', '2019-07-23 22:33:32'),
(1477, 'el', 'date_settings', 'admin-side', '', 'Ημερομηνία και ώρα', '2019-07-23 22:33:32'),
(1478, 'el', 'deleteforce', 'admin-side', '', 'Επιβολή διαγραφής', '2019-07-23 22:33:32'),
(1479, 'el', 'detail', 'admin-side', '', 'Λεπτομέρειες', '2019-07-23 22:33:32'),
(1480, 'el', 'directory_permissions', 'admin-side', '', 'Ελέγξτε τα δικαιώματα καταλόγου: <strong>{path}</strong>.', '2019-07-23 22:33:32'),
(1481, 'el', 'draft', 'admin-side', '', 'Προσχέδιο', '2019-07-23 22:33:32'),
(1482, 'el', 'expand_all', 'admin-side', '', 'Ανάπτυξη όλων', '2019-07-23 22:33:32'),
(1483, 'el', 'export_to_disk', 'admin-side', '', 'Εξαγωγή στο δίσκο', '2019-07-23 22:33:32'),
(1484, 'el', 'file_base_path', 'admin-side', '', 'Διαδρομή καταλόγου μεταφόρτωσης αρχείων', '2019-07-23 22:33:32'),
(1485, 'el', 'get_off_my_lawn', 'admin-side', '', 'Συγγνώμη. Φοβάμαι πως δεν μπορώ να το κάνω αυτό. Το <strong>{event}</strong> <strong>{step}</strong> δεν είναι μια ασφαλής λειτουργία.', '2019-07-23 22:33:32'),
(1486, 'el', 'gmtoffset', 'admin-side', '', 'Χρονική ζώνη', '2019-07-23 22:33:32'),
(1487, 'el', 'img_dir', 'admin-side', '', 'Κατάλογος εικόνων', '2019-07-23 22:33:32'),
(1488, 'el', 'import_from_disk', 'admin-side', '', 'Εισαγωγή από το δίσκο', '2019-07-23 22:33:32'),
(1489, 'el', 'invalid_argument', 'admin-side', '', 'Το όρισμα δεν είναι έγκυρο', '2019-07-23 22:33:32'),
(1490, 'el', 'invalid_article_id', 'admin-side', '', 'Το αναγνωριστικό (ID) άρθρου δεν είναι έγκυρο', '2019-07-23 22:33:32'),
(1491, 'el', 'invalid_expiredate', 'admin-side', '', 'Μη έγκυρη λήξη ημερομηνίας ή ώρας.', '2019-07-23 22:33:32'),
(1492, 'el', 'invalid_json', 'admin-side', '', 'Άκυρα αρχεία JSON: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1493, 'el', 'invalid_postdate', 'admin-side', '', 'Λανθασμένη ημερομηνία ή ώρα.', '2019-07-23 22:33:32'),
(1494, 'el', 'is_dst', 'admin-side', '', 'Ενεργοποίηση θερινής ώρας;', '2019-07-23 22:33:32'),
(1495, 'el', 'leave_text_untouched', 'admin-side', '', 'Να μείνει το κείμενο ανέπαφο', '2019-07-23 22:33:32'),
(1496, 'el', 'live', 'admin-side', '', 'Ενεργό', '2019-07-23 22:33:32'),
(1497, 'el', 'locale', 'admin-side', '', 'Τοπική ρύθμιση', '2019-07-23 22:33:32'),
(1498, 'el', 'manage', 'admin-side', '', 'Διαχείριση', '2019-07-23 22:33:32'),
(1499, 'el', 'meta', 'admin-side', '', 'Μετα', '2019-07-23 22:33:32'),
(1500, 'el', 'on_front_page', 'admin-side', '', 'Ο τομέας εμφανίζετε στην αρχική σελίδα;', '2019-07-23 22:33:32'),
(1501, 'el', 'override', 'admin-side', '', 'Παράκαμψη', '2019-07-23 22:33:32'),
(1502, 'el', 'parent', 'admin-side', '', 'Γονέας', '2019-07-23 22:33:32'),
(1503, 'el', 'path_creation_or_writing_failed', 'admin-side', '', 'Δεν είναι δυνατή η δημιουργία ή η εγγραφή σε αρχεία/καταλόγους: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1504, 'el', 'path_not_readable', 'admin-side', '', 'Δυσανάγνωστα αρχεία ή κατάλογοι: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1505, 'el', 'path_not_writable', 'admin-side', '', 'Μη εγγράψιμα αρχεία/καταλόγοι: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1506, 'el', 'path_renaming_failed', 'admin-side', '', 'Η μετονομασία των Αρχείων ή καταλόγων απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1507, 'el', 'pending', 'admin-side', '', 'Προς έγκριση', '2019-07-23 22:33:32'),
(1508, 'el', 'permlink_mode', 'admin-side', '', 'Μορφή μόνιμων συνδέσεων', '2019-07-23 22:33:32'),
(1509, 'el', 'preferences_saved', 'admin-side', '', 'Οι επιλογές σας αποθηκεύτηκαν', '2019-07-23 22:33:32'),
(1510, 'el', 'reset_time', 'admin-side', '', 'Να οριστεί το «τώρα» ως χρονοσήμανση', '2019-07-23 22:33:32'),
(1511, 'el', 'search_all', 'admin-side', '', 'Αναζήτηση όλων', '2019-07-23 22:33:32'),
(1512, 'el', 'search_clear', 'admin-side', '', 'Εκκαθάριση αναζητήσεων', '2019-07-23 22:33:32'),
(1513, 'el', 'search_options', 'admin-side', '', 'Επιλογές αναζήτησης', '2019-07-23 22:33:32'),
(1514, 'el', 'set_expire_now', 'admin-side', '', 'Ορίστε τη λήξη στο τώρα', '2019-07-23 22:33:32'),
(1515, 'el', 'set_to_now', 'admin-side', '', 'Αλλαγή χρονοσήμανσης σε «τώρα»', '2019-07-23 22:33:32'),
(1516, 'el', 'show', 'admin-side', '', 'Προβολή', '2019-07-23 22:33:32'),
(1517, 'el', 'showing_search_results', 'admin-side', '', 'Προβολή {from} ως {to} από {total}', '2019-07-23 22:33:32'),
(1518, 'el', 'skin', 'admin-side', '', 'Θέμα', '2019-07-23 22:33:32'),
(1519, 'el', 'skin_dir', 'admin-side', '', 'Κατάλογος θεμάτων', '2019-07-23 22:33:32'),
(1520, 'el', 'sort_value', 'admin-side', '', 'Τιμή ταξινόμησης', '2019-07-23 22:33:32'),
(1521, 'el', 'sticky', 'admin-side', '', '«Sticky»', '2019-07-23 22:33:32'),
(1522, 'el', 'swap_values', 'admin-side', '', 'Μετάθεσης αξιών', '2019-07-23 22:33:32'),
(1523, 'el', 'tab_admin', 'admin-side', '', 'Διαχείριση', '2019-07-23 22:33:32'),
(1524, 'el', 'tab_comments', 'admin-side', '', 'Σχόλια', '2019-07-23 22:33:32'),
(1525, 'el', 'tab_content', 'admin-side', '', 'Περιεχόμενο', '2019-07-23 22:33:32'),
(1526, 'el', 'tab_diagnostics', 'admin-side', '', 'Διαγνωστικά', '2019-07-23 22:33:32'),
(1527, 'el', 'tab_extensions', 'admin-side', '', 'Επεκτάσεις', '2019-07-23 22:33:32'),
(1528, 'el', 'tab_file', 'admin-side', '', 'Αρχεία', '2019-07-23 22:33:32'),
(1529, 'el', 'tab_forms', 'admin-side', '', 'Φόρμες', '2019-07-23 22:33:32'),
(1530, 'el', 'tab_help', 'admin-side', '', 'Βοήθεια', '2019-07-23 22:33:32'),
(1531, 'el', 'tab_image', 'admin-side', '', 'Εικόνες', '2019-07-23 22:33:32'),
(1532, 'el', 'tab_import', 'admin-side', '', 'Ενσωμάτωση', '2019-07-23 22:33:32'),
(1533, 'el', 'tab_languages', 'admin-side', '', 'Γλώσσες', '2019-07-23 22:33:32'),
(1534, 'el', 'tab_link', 'admin-side', '', 'Συνδέσεις', '2019-07-23 22:33:32'),
(1535, 'el', 'tab_list', 'admin-side', '', 'Άρθρα', '2019-07-23 22:33:32'),
(1536, 'el', 'tab_logs', 'admin-side', '', 'Μητρώα', '2019-07-23 22:33:32'),
(1537, 'el', 'tab_organise', 'admin-side', '', 'Κατηγορίες', '2019-07-23 22:33:32'),
(1538, 'el', 'tab_pages', 'admin-side', '', 'Σελίδες', '2019-07-23 22:33:32'),
(1539, 'el', 'tab_plugins', 'admin-side', '', 'Αρθρώματα', '2019-07-23 22:33:32'),
(1540, 'el', 'tab_preferences', 'admin-side', '', 'Ρυθμίσεις', '2019-07-23 22:33:32'),
(1541, 'el', 'tab_presentation', 'admin-side', '', 'Παρουσίαση', '2019-07-23 22:33:32'),
(1542, 'el', 'tab_sections', 'admin-side', '', 'Τομείς', '2019-07-23 22:33:32'),
(1543, 'el', 'tab_site_account', 'admin-side', '', 'Λογαριασμός', '2019-07-23 22:33:32'),
(1544, 'el', 'tab_site_admin', 'admin-side', '', 'Ιστότοπος', '2019-07-23 22:33:32'),
(1545, 'el', 'tab_skin', 'admin-side', '', 'Θέματα', '2019-07-23 22:33:32'),
(1546, 'el', 'tab_start', 'admin-side', '', 'Αρχική σελίδα', '2019-07-23 22:33:32'),
(1547, 'el', 'tab_style', 'admin-side', '', 'Στυλ', '2019-07-23 22:33:32'),
(1548, 'el', 'tab_view_site', 'admin-side', '', 'Προβολή', '2019-07-23 22:33:32'),
(1549, 'el', 'tab_write', 'admin-side', '', 'Συγγραφή', '2019-07-23 22:33:32'),
(1550, 'el', 'tagbuilder', 'admin-side', '', 'Δημιουργός tags', '2019-07-23 22:33:32'),
(1551, 'el', 'tags', 'admin-side', '', 'Tags', '2019-07-23 22:33:32'),
(1552, 'el', 'tempdir', 'admin-side', '', 'Διαδρομή προσωρινού φακέλου', '2019-07-23 22:33:32'),
(1553, 'el', 'textfilter', 'admin-side', '', 'Μορφή:', '2019-07-23 22:33:32'),
(1554, 'el', 'textfilter_help', 'admin-side', '', 'Βοήθεια για τη μορφοποίηση κειμένου', '2019-07-23 22:33:32'),
(1555, 'el', 'theme_name', 'admin-side', '', 'Θέμα εμφάνισης πλευράς διαχείρισης', '2019-07-23 22:33:32'),
(1556, 'el', 'timestamp', 'admin-side', '', 'Χρονοσήμανση', '2019-07-23 22:33:32'),
(1557, 'el', 'title_body', 'admin-side', '', 'Τίτλος και κυρίως άρθρο', '2019-07-23 22:33:32'),
(1558, 'el', 'title_body_excerpt', 'admin-side', '', 'Τίτλος, σώμα και απόσπασμα', '2019-07-23 22:33:32'),
(1559, 'el', 'unmoderated', 'admin-side', '', 'Προς έγκριση', '2019-07-23 22:33:32'),
(1560, 'el', 'update_from_disk', 'admin-side', '', 'Ενημέρωση από το δίσκο', '2019-07-23 22:33:32'),
(1561, 'el', 'use_textile', 'admin-side', '', 'Χρησιμοποιήστε Textile', '2019-07-23 22:33:32'),
(1562, 'el', 'view_html', 'admin-side', '', 'Δείτε το HTML', '2019-07-23 22:33:32'),
(1563, 'el', 'view_html_short', 'admin-side', '', 'HTML', '2019-07-23 22:33:32'),
(1564, 'el', 'view_per_page', 'admin-side', '', 'Προβολή {page} ανά σελίδα', '2019-07-23 22:33:32'),
(1565, 'el', 'view_preview', 'admin-side', '', 'Προεπισκόπηση άρθρου', '2019-07-23 22:33:32'),
(1566, 'el', 'view_preview_short', 'admin-side', '', 'Προεπισκόπηση', '2019-07-23 22:33:32'),
(1567, 'el', 'view_text', 'admin-side', '', 'Επεξεργαστείτε το κείμενο', '2019-07-23 22:33:32'),
(1568, 'el', 'view_text_short', 'admin-side', '', 'Κείμενο', '2019-07-23 22:33:32'),
(1569, 'el', 'visible', 'admin-side', '', 'Ορατό', '2019-07-23 22:33:32'),
(1570, 'el', 'article_expires_before_postdate', 'article', '', 'Σφάλμα: το άρθρο λήγει <strong>πριν από την</strong> ημερομηνία δημοσίευσης.', '2019-07-23 22:33:32'),
(1571, 'el', 'article_markup', 'article', '', 'Σημειοθέτηση άρθρου', '2019-07-23 22:33:32'),
(1572, 'el', 'article_posted', 'article', '', 'Το άρθρο δημοσιεύτηκε.', '2019-07-23 22:33:32'),
(1573, 'el', 'article_saved', 'article', '', 'Το άρθρο αποθηκεύτηκε.', '2019-07-23 22:33:32'),
(1574, 'el', 'article_saved_draft', 'article', '', 'Το άρθρο αποθηκεύτηκε ως προσχέδιο.', '2019-07-23 22:33:32'),
(1575, 'el', 'article_saved_hidden', 'article', '', 'Το άρθρο αποθηκεύτηκε ως κρυφό.', '2019-07-23 22:33:32'),
(1576, 'el', 'article_saved_pending', 'article', '', 'Το άρθρο αποθηκεύτηκε προς έγκριση.', '2019-07-23 22:33:32'),
(1577, 'el', 'article_save_failed', 'article', '', 'Το άρθρο δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1578, 'el', 'comment_settings', 'article', '', 'Ρυθμίσεις σχολίων', '2019-07-23 22:33:32'),
(1579, 'el', 'concurrent_edit_by', 'article', '', 'Το άρθρο <strong>δεν</strong> αποθηκεύτηκε. Ο/Η {author} τροποποίησε το άρθρο, ενώ εσείς το επεξεργαζόσαστε. Αν είστε σίγουροι, πατήστε πάλι το κουμπί “Αποθήκευση”.', '2019-07-23 22:33:32'),
(1580, 'el', 'excerpt_markup', 'article', '', 'Σημειοθέτηση αποσπάσματος', '2019-07-23 22:33:32'),
(1581, 'el', 'override_default_form', 'article', '', 'Παράκαμψη προκαθορισμένης φόρμας', '2019-07-23 22:33:32'),
(1582, 'el', 'problem_creating_article', 'article', '', 'Υπήρχε ένα πρόβλημα στη δημιουργία του άρθρου.', '2019-07-23 22:33:32'),
(1583, 'el', 'problem_deleting_article', 'article', '', 'Υπήρξε ένα πρόβλημα κατά τη διαγραφή του άρθρου.', '2019-07-23 22:33:32'),
(1584, 'el', 'problem_getting_articles', 'article', '', 'Υπήρχε ένα πρόβλημα στη ανάκτηση των άρθρων.', '2019-07-23 22:33:32'),
(1585, 'el', 'problem_retrieving_article', 'article', '', 'Υπήρξε ένα πρόβλημα κατά την ανάκτηση του άρθρου.', '2019-07-23 22:33:32'),
(1586, 'el', 'problem_retrieving_article_categories', 'article', '', 'Υπήρχε ένα πρόβλημα στην ανάκτηση των κατηγοριών του άρθρου.', '2019-07-23 22:33:32'),
(1587, 'el', 'problem_retrieving_categories', 'article', '', 'Υπήρξε ένα πρόβλημα κατά την ανάκτηση των κατηγόριων.', '2019-07-23 22:33:32'),
(1588, 'el', 'problem_retrieving_category_info', 'article', '', 'Υπήρξε ένα πρόβλημα κατά την ανάκτηση πληροφοριών της κατηγορίας.', '2019-07-23 22:33:32'),
(1589, 'el', 'problem_retrieving_sections', 'article', '', 'Υπήρχε ένα πρόβλημα στην ανάκτηση των τμημάτων.', '2019-07-23 22:33:32'),
(1590, 'el', 'problem_saving_article_categories', 'article', '', 'Υπήρξε ένα πρόβλημα κατά την αποθήκευση της κατηγορίας άρθρων.', '2019-07-23 22:33:32'),
(1591, 'el', 'problem_updating_article', 'article', '', 'Υπήρξε ένα πρόβλημα κατά την ενημέρωση στο άρθρο.', '2019-07-23 22:33:32'),
(1592, 'el', 'sort_display', 'article', '', 'Ταξινόμηση και προβολή', '2019-07-23 22:33:32'),
(1593, 'el', 'trying_to_assign_unexisting_category_to_the_article', 'article', '', 'Μια ανύπαρκτη κατηγορία δεν μπορεί να αντιστοιχιστεί σε άρθρο.', '2019-07-23 22:33:32'),
(1594, 'el', 'url_title', 'article', '', 'τίτλος URL', '2019-07-23 22:33:32'),
(1595, 'el', 'url_title_is_blank', 'article', '', 'ο τίτλος URL αφέθηκε κενός.', '2019-07-23 22:33:32'),
(1596, 'el', 'url_title_is_multiple', 'article', '', 'Ο ίδιος τίτλος URL χρησιμοποιείται από {count} διαφορετικά άρθρα.', '2019-07-23 22:33:32');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(1597, 'el', 'write', 'article', '', 'Συγγραφή', '2019-07-23 22:33:32'),
(1598, 'el', 'article_categories_deleted', 'category', '', 'Διαγραμμένες κατηγορίες άρθρων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1599, 'el', 'article_category', 'category', '', 'Κατηγορία άρθρου', '2019-07-23 22:33:32'),
(1600, 'el', 'article_category_already_exists', 'category', '', 'Η κατηγορία άρθρου <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1601, 'el', 'article_category_created', 'category', '', 'Η κατηγορία άρθρου <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1602, 'el', 'article_category_description', 'category', '', 'Περιγραφή κατηγορίας άρθρων', '2019-07-23 22:33:32'),
(1603, 'el', 'article_category_invalid', 'category', '', 'Η κατηγορία άρθρου <strong>{name}</strong> είναι λανθασμένη.', '2019-07-23 22:33:32'),
(1604, 'el', 'article_category_name', 'category', '', 'Όνομα κατηγορίας άρθρου', '2019-07-23 22:33:32'),
(1605, 'el', 'article_category_title', 'category', '', 'Τίτλος κατηγορίας άρθρου', '2019-07-23 22:33:32'),
(1606, 'el', 'article_category_updated', 'category', '', 'Η κατηγορία άρθρου <strong>{name}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1607, 'el', 'categories_set_parent', 'category', '', 'Η λίστα {list} έχει ανατεθεί στη κατηγορία {type} με γονέα το {parent}', '2019-07-23 22:33:32'),
(1608, 'el', 'category_not_found', 'category', '', 'Η κατηγορία δεν βρέθηκε', '2019-07-23 22:33:32'),
(1609, 'el', 'category_save_failed', 'category', '', 'Η κατηγορία δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1610, 'el', 'create_category', 'category', '', 'Νέα κατηγορία', '2019-07-23 22:33:32'),
(1611, 'el', 'edit_category', 'category', '', 'Επεξεργασία κατηγορίας', '2019-07-23 22:33:32'),
(1612, 'el', 'file_categories_deleted', 'category', '', 'Διαγραμμένες κατηγορίες αρχείων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1613, 'el', 'file_category_already_exists', 'category', '', 'Η κατηγορία αρχείου <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1614, 'el', 'file_category_created', 'category', '', 'Η κατηγορία αρχείου <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1615, 'el', 'file_category_description', 'category', '', 'Περιγραφή κατηγορίας αρχείου', '2019-07-23 22:33:32'),
(1616, 'el', 'file_category_invalid', 'category', '', 'Η κατηγορία αρχείου <strong>{name}</strong> είναι λανθασμένη.', '2019-07-23 22:33:32'),
(1617, 'el', 'file_category_name', 'category', '', 'Όνομα κατηγορίας αρχείου', '2019-07-23 22:33:32'),
(1618, 'el', 'file_category_title', 'category', '', 'Τίτλος κατηγορίας αρχείου', '2019-07-23 22:33:32'),
(1619, 'el', 'file_category_updated', 'category', '', 'Η κατηγορία αρχείου <strong>{name}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1620, 'el', 'image_categories_deleted', 'category', '', 'Διαγραμμένες κατηγορίες εικόνων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1621, 'el', 'image_category_already_exists', 'category', '', 'Η κατηγορία εικόνας <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1622, 'el', 'image_category_created', 'category', '', 'Η κατηγορία εικόνας <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1623, 'el', 'image_category_description', 'category', '', 'Περιγραφή κατηγορίας εικόνων', '2019-07-23 22:33:32'),
(1624, 'el', 'image_category_invalid', 'category', '', 'Η κατηγορία εικόνας <strong>{name}</strong> είναι λανθασμένη.', '2019-07-23 22:33:32'),
(1625, 'el', 'image_category_name', 'category', '', 'Όνομα κατηγορίας εικόνας', '2019-07-23 22:33:32'),
(1626, 'el', 'image_category_title', 'category', '', 'Τίτλος κατηγορίας εικόνας', '2019-07-23 22:33:32'),
(1627, 'el', 'image_category_updated', 'category', '', 'Η κατηγορία εικόνας <strong>{name}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1628, 'el', 'link_categories_deleted', 'category', '', 'Διαγραμμένες κατηγορίες συνδέσμων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1629, 'el', 'link_category_already_exists', 'category', '', 'Η κατηγορία συνδέσμου <strong>{name}</strong> ήδη υπάρχει.', '2019-07-23 22:33:32'),
(1630, 'el', 'link_category_created', 'category', '', 'Η κατηγορία συνδέσμων <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1631, 'el', 'link_category_description', 'category', '', 'Περιγραφή κατηγορίας συνδέσμου', '2019-07-23 22:33:32'),
(1632, 'el', 'link_category_invalid', 'category', '', 'Η κατηγορία συνδέσμων <strong>{name}</strong> είναι άκυρη.', '2019-07-23 22:33:32'),
(1633, 'el', 'link_category_name', 'category', '', 'Όνομα κατηγορίας συνδέσμου', '2019-07-23 22:33:32'),
(1634, 'el', 'link_category_title', 'category', '', 'Τίτλος κατηγορίας συνδέσμου', '2019-07-23 22:33:32'),
(1635, 'el', 'link_category_updated', 'category', '', 'Η κατηγορία συνδέσμου <strong>{name}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1636, 'el', 'no_categories_exist', 'category', '', 'Δεν υπάρχουν κατηγορίες.', '2019-07-23 22:33:32'),
(1637, 'el', 'no_other_categories_exist', 'category', '', 'Δεν υπάρχουν άλλες κατηγορίες.', '2019-07-23 22:33:32'),
(1638, 'el', 'all_stylesheets', 'css', '', 'Όλα τα στυλ', '2019-07-23 22:33:32'),
(1639, 'el', 'create_css', 'css', '', 'Νέο στυλ', '2019-07-23 22:33:32'),
(1640, 'el', 'css_already_exists', 'css', '', 'Το φύλλο στυλ <strong>{name}</strong> ήδη υπάρχει.', '2019-07-23 22:33:32'),
(1641, 'el', 'css_code', 'css', '', 'Στυλ κώδικα', '2019-07-23 22:33:32'),
(1642, 'el', 'css_created', 'css', '', 'Το στυλ δημιουργήθηκε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1643, 'el', 'css_deleted', 'css', '', 'Το στυλ έχει διαγραφεί: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1644, 'el', 'css_name', 'css', '', 'Όνομα στυλ', '2019-07-23 22:33:32'),
(1645, 'el', 'css_name_required', 'css', '', 'Παρακαλούμε εισάξτε ένα όνομα για το στυλ σας.', '2019-07-23 22:33:32'),
(1646, 'el', 'css_save_failed', 'css', '', 'Το άρθρο δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1647, 'el', 'css_updated', 'css', '', 'Ανανέωση στυλ: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1648, 'el', 'css_used_by_section', 'css', '', 'Το στυλ <strong>{name}</strong> δεν διαγράφτηκε: χρησιμοποιείται από {count} τμήμα(τα).', '2019-07-23 22:33:32'),
(1649, 'el', 'all_checks_passed', 'diag', '', 'Όλοι οι έλεγχοι ολοκληρώθηκαν με επιτυχία.', '2019-07-23 22:33:32'),
(1650, 'el', 'cgi_header_config', 'diag', '', 'Ρύθμιση παραμέτρων κεφαλίδας CGI', '2019-07-23 22:33:32'),
(1651, 'el', 'clean_url_data_failed', 'diag', '', 'Η δοκιμή για καθαρά URL απέτυχε: {data}.', '2019-07-23 22:33:32'),
(1652, 'el', 'clean_url_test_failed', 'diag', '', 'Η ανάλυση για καθαρές διαδρομές URL απέτυχε.', '2019-07-23 22:33:32'),
(1653, 'el', 'diag_clear_private', 'diag', '', 'Απόκρυψη προσωπικών πληροφοριών', '2019-07-23 22:33:32'),
(1654, 'el', 'dev_version_live', 'diag', '', 'Τρέχετε αναπτυσσόμενα αρχεία του Textpattern σε περιβάλλον «LIVE» διακομιστή.', '2019-07-23 22:33:32'),
(1655, 'el', 'diagnostic_info', 'diag', '', 'Διαγνωστικές πληροφορίες', '2019-07-23 22:33:32'),
(1656, 'el', 'dir_not_writable', 'diag', '', 'Το {dirtype} δεν είναι εγγράψιμο: {path}.', '2019-07-23 22:33:32'),
(1657, 'el', 'dns_lookup_fails', 'diag', '', 'Η αναζήτηση του DNS απέτυχε: {domain}.', '2019-07-23 22:33:32'),
(1658, 'el', 'file_uploads_disabled', 'diag', '', 'Η μεταφόρτωση αρχείων έχει απενεργοποιηθεί', '2019-07-23 22:33:32'),
(1659, 'el', 'high', 'diag', '', 'πολλές', '2019-07-23 22:33:32'),
(1660, 'el', 'htaccess_missing', 'diag', '', 'Το αρχείο .htaccess απουσιάζει', '2019-07-23 22:33:32'),
(1661, 'el', 'low', 'diag', '', 'λίγες', '2019-07-23 22:33:32'),
(1662, 'el', 'missing_files', 'diag', '', 'Λείπουν αρχεία: {list}', '2019-07-23 22:33:32'),
(1663, 'el', 'modified_files', 'diag', '', 'Ορισμένα αρχεία του Textpattern έχουν τροποποιηθεί: {list}', '2019-07-23 22:33:32'),
(1664, 'el', 'mod_rewrite_missing', 'diag', '', 'Το άρθρωμα <code>mod_rewrite</code> του Apache δεν είναι ενεργοποιημένο.', '2019-07-23 22:33:32'),
(1665, 'el', 'mysql_table_errors', 'diag', '', 'Τα ακόλουθα σφάλματα εντοπίστηκαν στους πίνακές του MySQL: {list}', '2019-07-23 22:33:32'),
(1666, 'el', 'no_temp_dir', 'diag', '', 'Δεν ορίστηκε κανένας προσωρινός φάκελος', '2019-07-23 22:33:32'),
(1667, 'el', 'old_placeholder', 'diag', '', 'Κάποιο παλιό αρχείο “placeholder” εμποδίζει την πρόσβαση: {path}.', '2019-07-23 22:33:32'),
(1668, 'el', 'path_inaccessible', 'diag', '', 'το <strong>{path}</strong> δεν είναι προσπελάσιμο.', '2019-07-23 22:33:32'),
(1669, 'el', 'php_extensions', 'diag', '', 'Επεκτάσεις PHP', '2019-07-23 22:33:32'),
(1670, 'el', 'php_version_required', 'diag', '', 'Το Textpattern απαιτεί τουλάχιστον PHP {version} για να εγκατασταθεί στον server σας', '2019-07-23 22:33:32'),
(1671, 'el', 'preflight_check', 'diag', '', 'Προκαταρκτικός έλεγχος', '2019-07-23 22:33:32'),
(1672, 'el', 'problem_connecting_update_server', 'diag', '', 'Δεν είναι δυνατή η σύνδεση με το διακομιστή. Παρακαλούμε δοκιμάστε αργότερα.', '2019-07-23 22:33:32'),
(1673, 'el', 'site_trailing_slash', 'diag', '', 'To URL του ιστοτόπου τελειώνει με κάθετο (<code>/</code>): {path}.', '2019-07-23 22:33:32'),
(1674, 'el', 'site_url_mismatch', 'diag', '', 'Η προτίμηση της διεύθυνσης URL ενδέχεται να είναι λανθασμένη: {url}.', '2019-07-23 22:33:32'),
(1675, 'el', 'some_php_functions_disabled', 'diag', '', 'Οι ακόλουθες λειτουργίες PHP (που ενδεχομένως να χρειάζονται για την σωστή λειτουργία του Textpattern) είναι απενεργοποιημένες στον διακομιστή σας: {list}.', '2019-07-23 22:33:32'),
(1676, 'el', 'still_exists', 'diag', '', 'το <strong>{path}</strong> εξακολουθεί να υπάρχει.', '2019-07-23 22:33:32'),
(1677, 'el', 'textpattern_update_available', 'diag', '', 'Η νέα έκδοση του Textpattern {version} <strong>είναι διαθέσιμη για κατέβασμα</strong>.', '2019-07-23 22:33:32'),
(1678, 'el', 'textpattern_update_available_beta', 'diag', '', 'Νέα έκδοση προ-απελευθέρωσης {version} Textpattern διαθέσιμη για λήψη. <a href=\"https://textpattern.com/download-beta\" rel=\"external\">επισκεφθείτε την ιστοσελίδα του Textpattern</a> για περισσότερες πληροφορίες.', '2019-07-23 22:33:32'),
(1679, 'el', 'tmp_plugin_paths_match', 'diag', '', 'Οι φάκελοι για το cache των αρθρωμάτων και ο προσωρινός φάκελος <strong>δεν</strong> πρέπει να είναι οι ίδιοι.', '2019-07-23 22:33:32'),
(1680, 'el', 'warn_mail_unavailable', 'diag', '', 'Η λειτουργία <code>mail()</code> δεν περιλαμβάνεται στην εγκατάσταση της PHP που διαθέτετε. Συνεπώς, δεν είναι δυνατή η αποστολή email από τo Textpattern, γεγονός πο.', '2019-07-23 22:33:32'),
(1681, 'el', 'article_deleted', 'discuss', '', 'Το άρθρο διαγράφηκε!', '2019-07-23 22:33:32'),
(1682, 'el', 'comments_deleted', 'discuss', '', 'Διαγραφέντα σχόλια: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1683, 'el', 'comments_marked_spam', 'discuss', '', 'Κρυφά σχόλια χαρακτηρισμένα ως spam: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1684, 'el', 'comments_marked_unmoderated', 'discuss', '', 'Κρυφά σχόλια προς έγκριση: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1685, 'el', 'comments_marked_visible', 'discuss', '', 'Ορατά σχόλια: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1686, 'el', 'comment_not_found', 'discuss', '', 'Το σχόλιο αυτό δεν υπάρχει. Πιθανόν να έχει διαγραφεί.', '2019-07-23 22:33:32'),
(1687, 'el', 'comment_save_failed', 'discuss', '', 'Το σχόλιο σας δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1688, 'el', 'comment_updated', 'discuss', '', 'Το σχόλιο <strong>{id}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1689, 'el', 'edit_comment', 'discuss', '', 'Επεξεργασία σχολίου', '2019-07-23 22:33:32'),
(1690, 'el', 'hide_spam', 'discuss', '', 'Απόκρυψη (spam)', '2019-07-23 22:33:32'),
(1691, 'el', 'hide_unmoderated', 'discuss', '', 'Απόκρυψη (προς έγκριση)', '2019-07-23 22:33:32'),
(1692, 'el', 'just_spam_results_found', 'discuss', '', 'Βρέθηκαν μόνο spam σχόλια. Χρησιμοποιήστε την \"Εμφάνιση spam\" για να τα δείτε.', '2019-07-23 22:33:32'),
(1693, 'el', 'no_comments_recorded', 'discuss', '', 'Δεν έχουν ακόμα καταγραφεί σχόλια.', '2019-07-23 22:33:32'),
(1694, 'el', 'search_comments', 'discuss', '', 'Αναζήτηση σχολίων', '2019-07-23 22:33:32'),
(1695, 'el', 'show_spam', 'discuss', '', 'Εμφάνιση spam', '2019-07-23 22:33:32'),
(1696, 'el', 'condition', 'file', '', 'Προϋπόθεση', '2019-07-23 22:33:32'),
(1697, 'el', 'edit_file', 'file', '', 'Επεξεργαστείτε το αρχείο', '2019-07-23 22:33:32'),
(1698, 'el', 'existing_file', 'file', '', 'Ήδη υπάρχον αρχείο', '2019-07-23 22:33:32'),
(1699, 'el', 'file_already_exists', 'file', '', 'Το αρχείο <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1700, 'el', 'file_cannot_rename', 'file', '', 'Το αρχείο <strong>{name}</strong> δεν μπορεί να μετονομαστεί.', '2019-07-23 22:33:32'),
(1701, 'el', 'file_deleted', 'file', '', 'Αρχεία που διαγράφηκαν: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1702, 'el', 'file_delete_failed', 'file', '', 'Απέτυχε η διαγραφή αρχείων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1703, 'el', 'file_dir_not_writeable', 'file', '', '<strong>Προειδοποίηση:</strong> Δεν είναι δυνατή η εγγραφή στον κατάλογο αρχείων <code>{filedir}</code>.', '2019-07-23 22:33:32'),
(1704, 'el', 'file_not_found', 'file', '', 'Δεν βρέθηκαν τα αρχεία: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1705, 'el', 'file_not_updated', 'file', '', 'Το αρχείο <strong>{name}</strong> δεν ενημερώθηκε.', '2019-07-23 22:33:32'),
(1706, 'el', 'file_relink', 'file', '', 'Επιφόρτωση/ανάθεση αρχείου', '2019-07-23 22:33:32'),
(1707, 'el', 'file_status', 'file', '', 'Κατάσταση αρχείου', '2019-07-23 22:33:32'),
(1708, 'el', 'file_status_missing', 'file', '', 'Απουσιάζει', '2019-07-23 22:33:32'),
(1709, 'el', 'file_status_ok', 'file', '', 'Εντάξει', '2019-07-23 22:33:32'),
(1710, 'el', 'file_unsynchronized', 'file', '', 'Το αρχείο <strong>{name}</strong> δεν είναι πια συγχρονισμένο με το καταχωρημένο αρχείο στη βάση δεδομένων. Παρακαλούμε αλλάξτε το όνομα του αρχείου.', '2019-07-23 22:33:32'),
(1711, 'el', 'file_updated', 'file', '', 'Αρχεία που έχουν ενημερωθεί: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1712, 'el', 'file_uploaded', 'file', '', 'Αρχεία που έχουν φορτωθεί: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1713, 'el', 'file_upload_failed', 'file', '', 'Η φόρτωση απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1714, 'el', 'invalid_filename', 'file', '', 'Μη έγκυρα ονόματα αρχείων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1715, 'el', 'invalid_id', 'file', '', 'Μη έγκυρο αναγνωριστικό id <strong>{id}</strong>.', '2019-07-23 22:33:32'),
(1716, 'el', 'linked_to_file', 'file', '', 'Συνδεδεμένα αρχεία: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1717, 'el', 'no_files_recorded', 'file', '', 'Δεν υπάρχουν αρχεία.', '2019-07-23 22:33:32'),
(1718, 'el', 'permissions', 'file', '', 'Δικαιώματα πρόσβασης', '2019-07-23 22:33:32'),
(1719, 'el', 'private', 'file', '', 'Ιδιωτικό', '2019-07-23 22:33:32'),
(1720, 'el', 'public', 'file', '', 'Δημόσιο', '2019-07-23 22:33:32'),
(1721, 'el', 'replace_file', 'file', '', 'Αντικατάσταση αρχείου', '2019-07-23 22:33:32'),
(1722, 'el', 'reset_download_count', 'file', '', 'Μηδενισμός καταμέτρησης μεταφορτώσεων', '2019-07-23 22:33:32'),
(1723, 'el', 'search_files', 'file', '', 'Αναζήτηση αρχείων', '2019-07-23 22:33:32'),
(1724, 'el', 'all_forms', 'form', '', 'Όλες οι φόρμες', '2019-07-23 22:33:32'),
(1725, 'el', 'changetype', 'form', '', 'Αλλαγή τύπου', '2019-07-23 22:33:32'),
(1726, 'el', 'create_form', 'form', '', 'Νέα φόρμα', '2019-07-23 22:33:32'),
(1727, 'el', 'form_already_exists', 'form', '', 'Η φόρμα <strong>{name}</strong> υπάρχει ήδη.', '2019-07-23 22:33:32'),
(1728, 'el', 'form_clone', 'form', '', 'Αντιγραφή φόρμας', '2019-07-23 22:33:32'),
(1729, 'el', 'form_code', 'form', '', 'Φόρμα κώδικα', '2019-07-23 22:33:32'),
(1730, 'el', 'form_created', 'form', '', 'Φόρμες που δημιουργήθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1731, 'el', 'form_deleted', 'form', '', 'Φόρμες που διαγράφηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1732, 'el', 'form_name', 'form', '', 'Όνομα φόρμας', '2019-07-23 22:33:32'),
(1733, 'el', 'form_name_invalid', 'form', '', 'Λανθασμένο όνομα φόρμας.', '2019-07-23 22:33:32'),
(1734, 'el', 'form_save_failed', 'form', '', 'Η φόρμα δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1735, 'el', 'form_type', 'form', '', 'Τύπος φόρμας', '2019-07-23 22:33:32'),
(1736, 'el', 'form_type_missing', 'form', '', 'Δεν έχει προσδιοριστεί ο τύπος της φόρμας', '2019-07-23 22:33:32'),
(1737, 'el', 'form_updated', 'form', '', 'Φόρμες που έχουν ενημερωθεί: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1738, 'el', 'alt_text', 'image', '', 'Εναλλακτικό κείμενο', '2019-07-23 22:33:32'),
(1739, 'el', 'create_thumbnail', 'image', '', 'Δημιουργία εικονιδίου', '2019-07-23 22:33:32'),
(1740, 'el', 'edit_image', 'image', '', 'Επεξεργασία εικόνας', '2019-07-23 22:33:32'),
(1741, 'el', 'image_deleted', 'image', '', 'Εικόνες που έχουν διαγραφεί: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1742, 'el', 'image_delete_failed', 'image', '', 'Οι εικόνες δεν μπορούν να διαγραφούν εντελώς: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1743, 'el', 'image_details', 'image', '', 'Λεπτομέρειες εικόνας', '2019-07-23 22:33:32'),
(1744, 'el', 'image_name', 'image', '', 'Όνομα εικόνας', '2019-07-23 22:33:32'),
(1745, 'el', 'image_save_error', 'image', '', 'Υπήρξε πρόβλημα κατά την αποθήκευση δεδομένων εικόνας.', '2019-07-23 22:33:32'),
(1746, 'el', 'image_save_failed', 'image', '', 'Η εικόνα δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1747, 'el', 'image_updated', 'image', '', 'Εικόνες που ενημερώθηκαν: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1748, 'el', 'image_uploaded', 'image', '', 'Εικόνες που έχουν φορτωθεί: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1749, 'el', 'img_dir_not_writeable', 'image', '', '<strong>Προειδοποίηση:</strong> Δεν είναι δυνατή η εγγραφή στον φάκελο εικόνων <code>{imgdir}</code>.', '2019-07-23 22:33:32'),
(1750, 'el', 'invalid_width_or_height', 'image', '', 'Λανθασμένο πλάτος ή ύψος.', '2019-07-23 22:33:32'),
(1751, 'el', 'keep_square_pixels', 'image', '', 'Αποκοπή περιθωρίων', '2019-07-23 22:33:32'),
(1752, 'el', 'no_images_recorded', 'image', '', 'Δεν υπάρχουν εικόνες.', '2019-07-23 22:33:32'),
(1753, 'el', 'only_graphic_files_allowed', 'image', '', 'Επιτρέπονται τα αρχεία εικόνας .jpg, .gif και .png', '2019-07-23 22:33:32'),
(1754, 'el', 'replace_image', 'image', '', 'Αντικατάσταση εικόνας', '2019-07-23 22:33:32'),
(1755, 'el', 'search_images', 'image', '', 'Αναζήτηση εικόνων', '2019-07-23 22:33:32'),
(1756, 'el', 'thumbnail_delete_failed', 'image', '', 'Το εικονίδιο δεν μπορεί να διαγραφεί εντελώς.', '2019-07-23 22:33:32'),
(1757, 'el', 'thumbnail_deleted', 'image', '', 'Το εικονίδιο έχει διαγραφεί.', '2019-07-23 22:33:32'),
(1758, 'el', 'thumbnail_not_saved', 'image', '', 'Το εικονίδιο <strong>{id}</strong> δεν αποθηκεύτηκε.', '2019-07-23 22:33:32'),
(1759, 'el', 'thumbnail_saved', 'image', '', 'Το εικονίδιο <strong>{id}</strong> αποθηκεύτηκε.', '2019-07-23 22:33:32'),
(1760, 'el', 'upload_image', 'image', '', 'Eπιφόρτωση εικόνας', '2019-07-23 22:33:32'),
(1761, 'el', 'upload_thumbnail', 'image', '', 'Επιφόρτωση εικονιδίου', '2019-07-23 22:33:32'),
(1762, 'el', 'active_language', 'lang', '', 'Ενεργή γλώσσα', '2019-07-23 22:33:32'),
(1763, 'el', 'active_language_ui', 'lang', '', 'Γλώσσα χρήστη', '2019-07-23 22:33:32'),
(1764, 'el', 'install_from_textpack', 'lang', '', 'Εγκατάσταση από Textpack', '2019-07-23 22:33:32'),
(1765, 'el', 'install_textpack', 'lang', '', 'Εγκατάσταση Textpack', '2019-07-23 22:33:32'),
(1766, 'el', 'language_deleted', 'lang', '', 'Η γλώσσα <strong>{name}</strong> αφαιρέθηκε.', '2019-07-23 22:33:32'),
(1767, 'el', 'language_installed', 'lang', '', 'Η γλώσσα <strong>{name}</strong> έχει εγκατασταθεί.', '2019-07-23 22:33:32'),
(1768, 'el', 'language_not_installed', 'lang', '', 'Η γλώσσα <strong>{name}</strong> δεν έχει εγκατασταθεί.', '2019-07-23 22:33:32'),
(1769, 'el', 'language_preamble', 'lang', '', '<strong>Μπορείτε να μας βοηθήσετε να βελτιώσουμε to Textpattern!</strong>. Επιζητούμε επιπλέον μεταφράσεις (και διορθώσεις για τις τρέχουσες μεταφράσεις) από την κοινότητα των χρηστών μας. Παρακαλούμε επισκεφθείτε το <a href=\"https://textpattern.com/languages\" rel=\"external\" target=\"_blank\" lang=\"en\"\">Textpattern language translations <span class=\"ui-icon ui-icon-extlink\">(ανοίγει μια εξωτερική σύνδεση σε νέο παράθυρο)</span></a> για περαιτέρω λεπτομέρειες.', '2019-07-23 22:33:32'),
(1770, 'el', 'language_updated', 'lang', '', 'Η γλώσσα <strong>{name}</strong> ενημερώθηκε.', '2019-07-23 22:33:32'),
(1771, 'el', 'locale_not_available_for_language', 'lang', '', 'Δεν υπάρχει διαθέσιμο locale για τη <strong>{name}</strong> γλώσσα. Θα χρησιμοποιηθεί το προεπιλεγμένο locale.', '2019-07-23 22:33:32'),
(1772, 'el', 'textpack_strings_installed', 'lang', '', '{count} ακολουθίες χαρακτήρων έχουν εγκατασταθεί από το Textpack.', '2019-07-23 22:33:32'),
(1773, 'el', 'create_link', 'link', '', 'Νέος σύνδεσμος', '2019-07-23 22:33:32'),
(1774, 'el', 'edit_link', 'link', '', 'Επεξεργαστείτε τον σύνδεσμο', '2019-07-23 22:33:32'),
(1775, 'el', 'links_deleted', 'link', '', 'Διαγραφέντες σύνδεσμοι: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1776, 'el', 'link_created', 'link', '', 'Ο σύνδεσος <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1777, 'el', 'link_empty', 'link', '', 'Οι σύνδεσμοι δεν πρέπει να είναι κενοί.', '2019-07-23 22:33:32'),
(1778, 'el', 'link_saved', 'link', '', 'Ο σύνδεσμος αποθηκεύτηκε.', '2019-07-23 22:33:32'),
(1779, 'el', 'link_save_failed', 'link', '', 'Ο σύνδεσμος δεν μπορούσε να αποθηκευτεί.', '2019-07-23 22:33:32'),
(1780, 'el', 'link_updated', 'link', '', 'Οι σύνδεσμοι: <strong>{name}</strong> ενημερώθηκαν.', '2019-07-23 22:33:32'),
(1781, 'el', 'no_links_recorded', 'link', '', 'Δεν υπάρχουν συνδέσεις.', '2019-07-23 22:33:32'),
(1782, 'el', 'search_links', 'link', '', 'Αναζήτηση συνδέσεων', '2019-07-23 22:33:32'),
(1783, 'el', 'articles_deleted', 'list', '', 'Τα άρθρα έχουν διαγραφεί: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1784, 'el', 'articles_duplicated', 'list', '', 'Άρθρα που αντιγράφτηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1785, 'el', 'articles_modified', 'list', '', 'Τα άρθρα τροποποιήθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1786, 'el', 'no_articles_recorded', 'list', '', 'Δεν υπάρχουν άρθρα.', '2019-07-23 22:33:32'),
(1787, 'el', 'search_articles', 'list', '', 'Αναζήτηση άρθρων', '2019-07-23 22:33:32'),
(1788, 'el', 'logs_deleted', 'log', '', 'Διαγραφέντα μητρώα: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1789, 'el', 'no_refers_recorded', 'log', '', 'Δεν έχουν ακόμα καταγραφεί επισκέψεις από άλλους ιστότοπους.', '2019-07-23 22:33:32'),
(1790, 'el', 'referrer', 'log', '', 'Επίσκεψη από άλλο ιστότοπο', '2019-07-23 22:33:32'),
(1791, 'el', 'search_logs', 'log', '', 'Αναζήτησης αρχείου καταγραφής', '2019-07-23 22:33:32'),
(1792, 'el', 'all_pages', 'page', '', 'Όλες οι σελίδες', '2019-07-23 22:33:32'),
(1793, 'el', 'create_page', 'page', '', 'Νέα σελίδα', '2019-07-23 22:33:32'),
(1794, 'el', 'page_already_exists', 'page', '', 'Η σελίδα <strong>{name}</strong> ήδη υπάρχει.', '2019-07-23 22:33:32'),
(1795, 'el', 'page_code', 'page', '', 'Κώδικα σελίδας', '2019-07-23 22:33:32'),
(1796, 'el', 'page_created', 'page', '', 'Οι σελίδες δημιουργήθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1797, 'el', 'page_deleted', 'page', '', 'Σελίδες που έχουν διαγραφεί: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1798, 'el', 'page_name', 'page', '', 'Όνομα σελίδας (προαπαιτούμενο)', '2019-07-23 22:33:32'),
(1799, 'el', 'page_name_invalid', 'page', '', 'Λανθασμένο όνομα σελίδας.', '2019-07-23 22:33:32'),
(1800, 'el', 'page_save_failed', 'page', '', 'Το άρθρο δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1801, 'el', 'page_updated', 'page', '', 'Ανανέωση σελίδων: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1802, 'el', 'page_used_by_section', 'page', '', 'Η Σελίδα <strong>{name}</strong> δεν διεγράφη, χρησιμοποιείται από {count} τομείς.', '2019-07-23 22:33:32'),
(1803, 'el', 'bad_plugin_code', 'plugin', '', 'O πηγαίος κώδικας του αρθρώματος είναι άκυρος ή άδειος.', '2019-07-23 22:33:32'),
(1804, 'el', 'changeorder', 'plugin', '', 'Αλλαγή σειράς', '2019-07-23 22:33:32'),
(1805, 'el', 'edit_plugin', 'plugin', '', 'Επεξεργαστείτε το άρθρωμα <strong>{name}</strong>', '2019-07-23 22:33:32'),
(1806, 'el', 'edit_plugins', 'plugin', '', 'Επεξεργασία αρθρωμάτων', '2019-07-23 22:33:32'),
(1807, 'el', 'install_plugin', 'plugin', '', 'Εγκατάσταση αρθρώματος', '2019-07-23 22:33:32'),
(1808, 'el', 'order', 'plugin', '', 'Ιεραρχία', '2019-07-23 22:33:32'),
(1809, 'el', 'plugin', 'plugin', '', 'Άρθρωμα', '2019-07-23 22:33:32'),
(1810, 'el', 'plugin_compression_unsupported', 'plugin', '', 'Η αποσυμπίεση του αρθρώματος δεν υποστηρίζεται τον διακομιστή σας. Παρακαλείστε όπως έρθετε σε επαφή με τον συντάκτη του αρθρώματος για τη μη συμπιεσμένη έκδοση.', '2019-07-23 22:33:32'),
(1811, 'el', 'plugin_deleted', 'plugin', '', 'Plugins που έχουν διαγραφεί: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1812, 'el', 'plugin_help', 'plugin', '', 'Βοήθεια αρθρώματος', '2019-07-23 22:33:32'),
(1813, 'el', 'plugin_installed', 'plugin', '', 'Το άρθρωμα <strong>{name}</strong> εγκαταστάθηκε.', '2019-07-23 22:33:32'),
(1814, 'el', 'plugin_install_failed', 'plugin', '', 'Απέτυχε η εγκατάσταση του plugin <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1815, 'el', 'plugin_saved', 'plugin', '', 'Το άρθρωμα <strong>{name}</strong> αποθηκεύτηκε.', '2019-07-23 22:33:32'),
(1816, 'el', 'plugin_updated', 'plugin', '', 'Τα plugins ενημερώθηκαν: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1817, 'el', 'previewing_plugin', 'plugin', '', 'Προεπισκόπηση αρθρώματος', '2019-07-23 22:33:32'),
(1818, 'el', 'search_plugins', 'plugin', '', 'Αναζήτηση εντός των plugins', '2019-07-23 22:33:32'),
(1819, 'el', 'verify_plugin', 'plugin', '', 'Επαλήθευση του αρθρώματος', '2019-07-23 22:33:32'),
(1820, 'el', 'admin_side_plugins', 'prefs', '', 'Χρήση αρθρωμάτων στη πλευρά διαχείρισης;', '2019-07-23 22:33:32'),
(1821, 'el', 'allow_article_php_scripting', 'prefs', '', 'Να επιτρέπεται το PHP στα άρθρα;', '2019-07-23 22:33:32'),
(1822, 'el', 'allow_form_override', 'prefs', '', 'Παροχή δυνατότητας παράκαμψης της προκαθορισμένης φόρμας;', '2019-07-23 22:33:32'),
(1823, 'el', 'allow_page_php_scripting', 'prefs', '', 'Δυνατότητα χρήσης PHP μέσα στις σελίδες;', '2019-07-23 22:33:32'),
(1824, 'el', 'all_hits', 'prefs', '', 'Όλες οι επισκέψεις', '2019-07-23 22:33:32'),
(1825, 'el', 'all_preferences', 'prefs', '', 'Όλες τις προτιμήσεις', '2019-07-23 22:33:32'),
(1826, 'el', 'archive_dateformat', 'prefs', '', 'Μορφή ημερομηνίας αρχείου', '2019-07-23 22:33:32'),
(1827, 'el', 'articles_use_excerpts', 'prefs', '', 'Χρήση αποσπασμάτων για άρθρα;', '2019-07-23 22:33:32'),
(1828, 'el', 'attach_titles_to_permalinks', 'prefs', '', 'Επισύναψη τίτλων στις μόνιμες συνδέσεις;', '2019-07-23 22:33:32'),
(1829, 'el', 'category_subcategory', 'prefs', '', '/κατηγορία/υποκατηγορία', '2019-07-23 22:33:32'),
(1830, 'el', 'clean', 'prefs', '', '/καθαρή/', '2019-07-23 22:33:32'),
(1831, 'el', 'comments_are_ol', 'prefs', '', 'Παρουσίαση αριθμημένης λίστας σχολίων;', '2019-07-23 22:33:32'),
(1832, 'el', 'comments_auto_append', 'prefs', '', 'Αυτόματη προσάρτηση σχολίων στα άρθρα;', '2019-07-23 22:33:32'),
(1833, 'el', 'comments_dateformat', 'prefs', '', 'Μορφή ημερομηνίας σχολίων', '2019-07-23 22:33:32'),
(1834, 'el', 'comments_default_invite', 'prefs', '', 'Προκαθορισμένη πρόσκληση', '2019-07-23 22:33:32'),
(1835, 'el', 'comments_disabled_after', 'prefs', '', 'Η προσθήκη σχολίων κλείνει μετά από', '2019-07-23 22:33:32'),
(1836, 'el', 'comments_disallow_images', 'prefs', '', 'Απαγόρευση των εικόνων στα σχόλια;', '2019-07-23 22:33:32'),
(1837, 'el', 'comments_mode', 'prefs', '', 'Μορφή σχολίων', '2019-07-23 22:33:32'),
(1838, 'el', 'comments_moderate', 'prefs', '', 'Έγκριση σχολίων πριν τη δημοσίευση τους;', '2019-07-23 22:33:32'),
(1839, 'el', 'comments_on_default', 'prefs', '', 'Επιτρέπονται τα σχόλια παντού;', '2019-07-23 22:33:32'),
(1840, 'el', 'comments_require_email', 'prefs', '', 'Προαπαιτούμενο το email του σχολιαστή;', '2019-07-23 22:33:32'),
(1841, 'el', 'comments_require_name', 'prefs', '', 'Προαπαιτούμενο το όνομα του σχολιαστή;', '2019-07-23 22:33:32'),
(1842, 'el', 'comments_sendmail', 'prefs', '', 'Αποστολή με email των σχολίων στο συγγραφέα;', '2019-07-23 22:33:32'),
(1843, 'el', 'comments_use_fat_textile', 'prefs', '', 'Επιτρέψετε περισσότερες σήμανσης Textile στα σχόλια;', '2019-07-23 22:33:32'),
(1844, 'el', 'comment_means_site_updated', 'prefs', '', 'Ο ιστότοπος θεωρείται ανανεωμένος με την προσθήκη σχολίου;', '2019-07-23 22:33:32'),
(1845, 'el', 'comment_nofollow', 'prefs', '', 'Χρήση οδηγίας <code>rel=\"nofollow\"</code> στα σχόλια;', '2019-07-23 22:33:32'),
(1846, 'el', 'custom_10_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 10', '2019-07-23 22:33:32'),
(1847, 'el', 'custom_1_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 1', '2019-07-23 22:33:32'),
(1848, 'el', 'custom_2_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 2', '2019-07-23 22:33:32'),
(1849, 'el', 'custom_3_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 3', '2019-07-23 22:33:32'),
(1850, 'el', 'custom_4_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 4', '2019-07-23 22:33:32'),
(1851, 'el', 'custom_5_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 5', '2019-07-23 22:33:32'),
(1852, 'el', 'custom_6_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 6', '2019-07-23 22:33:32'),
(1853, 'el', 'custom_7_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 7', '2019-07-23 22:33:32'),
(1854, 'el', 'custom_8_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 8', '2019-07-23 22:33:32'),
(1855, 'el', 'custom_9_set', 'prefs', '', 'Όνομα προσαρμοσμένου πεδίου 9', '2019-07-23 22:33:32'),
(1856, 'el', 'custom_form_types', 'prefs', '', 'Πρότυπη προσαρμοσμένη φόρμα', '2019-07-23 22:33:32'),
(1857, 'el', 'default_event', 'prefs', '', 'Προεπιλεγμένος πίνακας διαχείρισης', '2019-07-23 22:33:32'),
(1858, 'el', 'default_publish_status', 'prefs', '', 'Προεπιλογή κατάστασης έκδοσης', '2019-07-23 22:33:32'),
(1859, 'el', 'doctype', 'prefs', '', 'Doctype', '2019-07-23 22:33:32'),
(1860, 'el', 'module_pophelp', 'prefs', '', 'Εμφάνιση inline συνδέσμων βοήθειας admin-side', '2019-07-23 22:33:32'),
(1861, 'el', 'enable_dev_preview', 'prefs', '', 'Enable development theme preview?', '2019-07-23 22:33:32'),
(1862, 'el', 'enable_short_tags', 'prefs', '', 'Ενεργοποιήσετε την υποστήριξη short tag', '2019-07-23 22:33:32'),
(1863, 'el', 'enable_xmlrpc_server', 'prefs', '', 'Θέλετε να ενεργοποιήσετε τον XML-RPC server;', '2019-07-23 22:33:32'),
(1864, 'el', 'expire_logs_after', 'prefs', '', 'Σε πόσες μέρες λήγουν οι καταχωρήσεις στο μητρώο επισκέψεων;', '2019-07-23 22:33:32'),
(1865, 'el', 'feeds', 'prefs', '', 'Ροές', '2019-07-23 22:33:32'),
(1866, 'el', 'file_max_upload_size', 'prefs', '', 'Μέγιστο επιτρεπόμενο μέγεθος αρχείου (σε bytes)', '2019-07-23 22:33:32'),
(1867, 'el', 'ham', 'prefs', '', 'Όλα εκτός από spam', '2019-07-23 22:33:32'),
(1868, 'el', 'hours_days_ago', 'prefs', '', 'πριν από ώρες/ημέρες', '2019-07-23 22:33:32'),
(1869, 'el', 'id_title', 'prefs', '', '/ΑΑ/τίτλος', '2019-07-23 22:33:32'),
(1870, 'el', 'include_email_atom', 'prefs', '', 'Περιλάβετε το email σας στη ροή ειδήσεων Atom;', '2019-07-23 22:33:32'),
(1871, 'el', 'language', 'prefs', '', 'Γλώσσα', '2019-07-23 22:33:32'),
(1872, 'el', 'logging', 'prefs', '', 'Καταχώρηση επισκέψεων', '2019-07-23 22:33:32'),
(1873, 'el', 'logs_expire', 'prefs', '', 'Σε πόσες μέρες λήγουν οι καταχωρήσεις στο μητρώο επισκέψεων;', '2019-07-23 22:33:32'),
(1874, 'el', 'markup_default', 'prefs', '', 'Προκαθορισμένη μορφοποίηση', '2019-07-23 22:33:32'),
(1875, 'el', 'max_url_len', 'prefs', '', 'Μέγιστο μήκος URL', '2019-07-23 22:33:32'),
(1876, 'el', 'messy', 'prefs', '', '?=μπερδεμένο', '2019-07-23 22:33:32'),
(1877, 'el', 'never_display_email', 'prefs', '', 'Αποκρύψτε το email του σχολιαστή;', '2019-07-23 22:33:32'),
(1878, 'el', 'no_preferences', 'prefs', '', 'Δεν υπάρχουν διαθέσιμες προτιμήσεις.', '2019-07-23 22:33:32'),
(1879, 'el', 'override_emailcharset', 'prefs', '', 'Κωδικοποίηση ISO-8859-1 για email (η προκαθορισμένη είναι UTF-8);', '2019-07-23 22:33:32'),
(1880, 'el', 'path_from_root', 'prefs', '', 'Υποκατάλογος (αν υπάρχει)', '2019-07-23 22:33:32'),
(1881, 'el', 'permlink_format', 'prefs', '', 'Μοτίβο του URL permalink', '2019-07-23 22:33:32'),
(1882, 'el', 'permlink_hyphenated', 'prefs', '', 'Ενωτικό (τίτλος-όπως-αυτός)', '2019-07-23 22:33:32'),
(1883, 'el', 'permlink_intercapped', 'prefs', '', 'Με Κεφαλαία (TitleLikeThis)', '2019-07-23 22:33:32'),
(1884, 'el', 'plugin_cache_dir', 'prefs', '', 'Διαδρομή φακέλου αρφρωμάτων «plugin cache»', '2019-07-23 22:33:32'),
(1885, 'el', 'prefs', 'prefs', '', 'Ρυθμίσεις', '2019-07-23 22:33:32'),
(1886, 'el', 'production_debug', 'prefs', '', 'Αποσφαλμάτωση', '2019-07-23 22:33:32'),
(1887, 'el', 'production_live', 'prefs', '', 'Τελικό', '2019-07-23 22:33:32'),
(1888, 'el', 'production_status', 'prefs', '', 'Στάδιο παραγωγής', '2019-07-23 22:33:32'),
(1889, 'el', 'production_test', 'prefs', '', 'Δοκιμή', '2019-07-23 22:33:32'),
(1890, 'el', 'publisher_email', 'prefs', '', 'Στείλτε τα στοιχεία εισόδου από αυτή τη διεύθυνση ηλεκτρονικού ταχυδρομείου', '2019-07-23 22:33:32'),
(1891, 'el', 'publish_expired_articles', 'prefs', '', 'Δημοσίευσε άρθρα που έχουν λήξει;', '2019-07-23 22:33:32'),
(1892, 'el', 'referrers_only', 'prefs', '', 'Μόνο επισκέψεις από άλλους ιστότοπους', '2019-07-23 22:33:32'),
(1893, 'el', 'rss_how_many', 'prefs', '', 'Αριθμός άρθρων στις ροή ειδήσεων RSS;', '2019-07-23 22:33:32'),
(1894, 'el', 'section_id_title', 'prefs', '', '/τομέας/ΑΑ/τίτλος', '2019-07-23 22:33:32'),
(1895, 'el', 'section_title', 'prefs', '', '/τομέας/τίτλος', '2019-07-23 22:33:32'),
(1896, 'el', 'send_lastmod', 'prefs', '', 'Αποστολή επικεφαλίδας «τελευταία τροποποίησης»;', '2019-07-23 22:33:32'),
(1897, 'el', 'show_comment_count_in_feed', 'prefs', '', 'Εμφάνιση του αριθμού σχολίων στη ροή ειδήσεων;', '2019-07-23 22:33:32'),
(1898, 'el', 'smtp_from', 'prefs', '', 'διεύθυνση αποστολέα του SMTP envelope', '2019-07-23 22:33:32'),
(1899, 'el', 'spam_blacklists', 'prefs', '', 'Μαύρες λίστες «spam» (χωρισμένες με κόμματα)', '2019-07-23 22:33:32'),
(1900, 'el', 'syndicate_body_or_excerpt', 'prefs', '', 'Ροή ειδήσεων με αποσπάσματα άρθρων <strong>μόνο</strong>;', '2019-07-23 22:33:32'),
(1901, 'el', 'timeoffset', 'prefs', '', 'Διαφορά ώρας', '2019-07-23 22:33:32'),
(1902, 'el', 'title_no_widow', 'prefs', '', 'Να ενεργοποιηθεί η αποφυγή «widowed words» σε τίτλους άρθρων;', '2019-07-23 22:33:32'),
(1903, 'el', 'title_only', 'prefs', '', '/τίτλος', '2019-07-23 22:33:32'),
(1904, 'el', 'txp_evaluate_functions', 'prefs', '', 'Ενεργοποιημένες PHP λειτουργίες στο <code>txp:evaluate</code>', '2019-07-23 22:33:32'),
(1905, 'el', 'tz_timezone', 'prefs', '', 'Χρονική ζώνη', '2019-07-23 22:33:32'),
(1906, 'el', 'up_to_date', 'prefs', '', 'Ενημερωμένα', '2019-07-23 22:33:32'),
(1907, 'el', 'url_mode', 'prefs', '', 'μορφή URL', '2019-07-23 22:33:32'),
(1908, 'el', 'use_comments', 'prefs', '', 'Δυνατότητα σχολιασμού;', '2019-07-23 22:33:32'),
(1909, 'el', 'use_mail_on_feeds_id', 'prefs', '', 'Χρήση email στον ΑΑ της ροής ειδήσεων; (το προκαθορισμένο είναι το URL του ιστοτόπου)', '2019-07-23 22:33:32'),
(1910, 'el', 'use_plugins', 'prefs', '', 'Χρήση αρθρωμάτων;', '2019-07-23 22:33:32'),
(1911, 'el', 'year_month_day_title', 'prefs', '', '/χρόνος/μήνας/μέρα/τίτλος', '2019-07-23 22:33:32'),
(1912, 'el', '403_forbidden', 'public', '', 'Απαγορεύεται.', '2019-07-23 22:33:32'),
(1913, 'el', '404_not_found', 'public', '', '404 - Η ιστοσελίδα δεν βρέθηκε.', '2019-07-23 22:33:32'),
(1914, 'el', '410_gone', 'public', '', 'Ο πόρος που ζητήθηκε δεν είναι πλέον διαθέσιμος.', '2019-07-23 22:33:32'),
(1915, 'el', '500_internal_server_error', 'public', '', 'Εσωτερικό σφάλμα διακομιστή.', '2019-07-23 22:33:32'),
(1916, 'el', 'articles_found', 'public', '', 'άρθρα βρέθηκαν', '2019-07-23 22:33:32'),
(1917, 'el', 'article_context', 'public', '', 'άρθρο', '2019-07-23 22:33:32'),
(1918, 'el', 'article_found', 'public', '', 'άρθρο βρέθηκε', '2019-07-23 22:33:32'),
(1919, 'el', 'atom_feed_title', 'public', '', 'Τροφοδοσία Atom', '2019-07-23 22:33:32'),
(1920, 'el', 'a_few_seconds', 'public', '', 'λίγα δευτερόλεπτα', '2019-07-23 22:33:32'),
(1921, 'el', 'comments_closed', 'public', '', 'Η δυνατότητα προσθήκης σχολίων έχει κλείσει για αυτό το άρθρο', '2019-07-23 22:33:32'),
(1922, 'el', 'comments_on', 'public', '', 'σχόλια ενεργά', '2019-07-23 22:33:32'),
(1923, 'el', 'comments_permlink', 'public', '', 'Μόνιμη σύνδεση', '2019-07-23 22:33:32'),
(1924, 'el', 'comment_comment', 'public', '', 'Σχόλιο', '2019-07-23 22:33:32'),
(1925, 'el', 'comment_duplicate', 'public', '', 'Δεν επιτρέπονται διπλότυπα σχόλια.', '2019-07-23 22:33:32'),
(1926, 'el', 'comment_email', 'public', '', 'Email', '2019-07-23 22:33:32'),
(1927, 'el', 'comment_email_required', 'public', '', 'Παρακαλούμε καταχωρίστε μια έγκυρη διεύθυνση email.', '2019-07-23 22:33:32'),
(1928, 'el', 'comment_message', 'public', '', 'μήνυμα', '2019-07-23 22:33:32'),
(1929, 'el', 'comment_moderated', 'public', '', 'Το σχόλιό σας αποθηκεύτηκε και θα εμφανιστεί μετά την έγκρισή του.', '2019-07-23 22:33:32'),
(1930, 'el', 'comment_name', 'public', '', 'Όνομα', '2019-07-23 22:33:32'),
(1931, 'el', 'comment_name_required', 'public', '', 'Παρακαλούμε καταχωρίστε το όνομά σας.', '2019-07-23 22:33:32'),
(1932, 'el', 'comment_posted', 'public', '', 'Ευχαριστούμε για το σχόλιό σας.', '2019-07-23 22:33:32'),
(1933, 'el', 'comment_received', 'public', '', '{site} σχόλιο καταγραμμένο {title}', '2019-07-23 22:33:32'),
(1934, 'el', 'comment_recorded', 'public', '', 'Έχει καταγραφεί ένα σχόλιο στη δημοσίευσή σας «{title}».', '2019-07-23 22:33:32'),
(1935, 'el', 'comment_required', 'public', '', 'Παρακαλούμε καταχωρίστε ένα σχόλιο.', '2019-07-23 22:33:32'),
(1936, 'el', 'comment_web', 'public', '', 'Ιστότοπος', '2019-07-23 22:33:32'),
(1937, 'el', 'continue_reading', 'public', '', 'Συνεχίστε την ανάγνωση', '2019-07-23 22:33:32'),
(1938, 'el', 'day', 'public', '', 'μέρα', '2019-07-23 22:33:32'),
(1939, 'el', 'days', 'public', '', 'μέρες', '2019-07-23 22:33:32'),
(1940, 'el', 'enter_comment_here', 'public', '', '<strong>Καταχωρίστε το σχόλιό σας ποιο κάτω.</strong> Τα πεδία με <b class=\"required\" title=\"Υποχρεωτικά\">*</b> είναι υποχρεωτικά. Πρέπει να κάνετε προεπισκόπηση στο σχόλιό σας πριν από την υποβολή του.', '2019-07-23 22:33:32'),
(1941, 'el', 'file_context', 'public', '', 'αρχείο', '2019-07-23 22:33:32'),
(1942, 'el', 'hour', 'public', '', 'ώρα', '2019-07-23 22:33:32'),
(1943, 'el', 'hours', 'public', '', 'ώρες', '2019-07-23 22:33:32'),
(1944, 'el', 'image_context', 'public', '', 'εικόνα', '2019-07-23 22:33:32'),
(1945, 'el', 'link_context', 'public', '', 'σύνδεσμος', '2019-07-23 22:33:32'),
(1946, 'el', 'main_content', 'public', '', 'Κύριο περιεχόμενο', '2019-07-23 22:33:32'),
(1947, 'el', 'matching_search_request', 'public', '', 'Βρέθηκαν {count} αποτελέσματα που ταιριάζουν με την αναζήτηση σας', '2019-07-23 22:33:32'),
(1948, 'el', 'minute', 'public', '', 'λεπτό', '2019-07-23 22:33:32'),
(1949, 'el', 'minutes', 'public', '', 'λεπτά', '2019-07-23 22:33:32'),
(1950, 'el', 'nopopup', 'public', '', 'χωρίς “popup”', '2019-07-23 22:33:32'),
(1951, 'el', 'no_comments', 'public', '', 'Επί του παρόντος δεν υπάρχουν σχόλια για αυτό το άρθρο.', '2019-07-23 22:33:32'),
(1952, 'el', 'no_search_matches', 'public', '', 'Συγνώμη, δεν βρέθηκαν αποτελέσματα που ταιριάζουν στο αίτημα της αναζήτησής σας', '2019-07-23 22:33:32'),
(1953, 'el', 'page_nav', 'public', '', 'Πλοήγηση σελίδων', '2019-07-23 22:33:32'),
(1954, 'el', 'permanent_link', 'public', '', 'Μόνιμη σύνδεση άρθρου', '2019-07-23 22:33:32'),
(1955, 'el', 'popup', 'public', '', '«popup»', '2019-07-23 22:33:32'),
(1956, 'el', 'press_preview_then_submit', 'public', '', 'Το παρακάτω είναι μια προεπισκόπηση του σχολίου σας. Όταν είστε ευχαριστημένοι με το σχόλιό σας, χρησιμοποιήστε το κουμπί <strong><a href=\"#txpCommentSubmit\" title=\"κουμπί υποβολής\"> υποβολή</a></strong>.', '2019-07-23 22:33:32'),
(1957, 'el', 'read_more', 'public', '', 'Διαβάστε περισσότερα', '2019-07-23 22:33:32'),
(1958, 'el', 'rss_feed_title', 'public', '', '«RSS Feed»', '2019-07-23 22:33:32'),
(1959, 'el', 'site_nav', 'public', '', 'Πλοήγηση ιστοτόπου', '2019-07-23 22:33:32'),
(1960, 'el', 'textile_help', 'public', '', 'Βοήθεια Textile', '2019-07-23 22:33:32'),
(1961, 'el', 'too_common_search_term', 'public', '', 'Φαίνεται σαν να ψάχνετε για ένα πολύ κοινό όρο αναζήτησης, ίσως δοκιμάστε μια πιο συγκεκριμένη φράση από', '2019-07-23 22:33:32'),
(1962, 'el', 'week', 'public', '', 'εβδομάδα', '2019-07-23 22:33:32'),
(1963, 'el', 'weeks', 'public', '', 'εβδομάδες', '2019-07-23 22:33:32'),
(1964, 'el', 'your_ip_is_blacklisted_by', 'public', '', 'Η διεύθυνσή σας (IP) είναι στη «μαύρη λίστα» της υπηρεσίας', '2019-07-23 22:33:32'),
(1965, 'el', 'yyyy-mm', 'public', '', 'χχχχ-μμ', '2019-07-23 22:33:32'),
(1966, 'el', 'article_count', 'section', '', 'Άρθρα σε αυτή την ενότητα: {num}.', '2019-07-23 22:33:32'),
(1967, 'el', 'change_page_style', 'section', '', 'Αλλαγή θέματος/σελίδας/στυλ', '2019-07-23 22:33:32'),
(1968, 'el', 'create_section', 'section', '', 'Νέο τμήμα', '2019-07-23 22:33:32'),
(1969, 'el', 'default_section_updated', 'section', '', 'Η προεπιλεγμένη ενότητα ενημερώθηκε', '2019-07-23 22:33:32'),
(1970, 'el', 'default_write_section', 'section', '', 'Προεπιλεγμένος τομέας', '2019-07-23 22:33:32'),
(1971, 'el', 'dev_theme', 'section', '', 'Development', '2019-07-23 22:33:32'),
(1972, 'el', 'dev_to_live', 'section', '', 'Επαναφορά στο Ενεργό', '2019-07-23 22:33:32'),
(1973, 'el', 'edit_default_section', 'section', '', 'Επεξεργαστείτε τον προεπιλεγμένο τομέα', '2019-07-23 22:33:32'),
(1974, 'el', 'edit_section', 'section', '', 'Επεξεργαστείτε τον τομέα', '2019-07-23 22:33:32'),
(1975, 'el', 'include_in_search', 'section', '', 'Συμπεριλάβετε τον τομέα στην αναζήτηση;', '2019-07-23 22:33:32'),
(1976, 'el', 'live_theme', 'section', '', 'Ενεργό', '2019-07-23 22:33:32'),
(1977, 'el', 'live_to_dev', 'section', '', 'Deploy to live', '2019-07-23 22:33:32'),
(1978, 'el', 'search_sections', 'section', '', 'Αναζήτηση τομέων', '2019-07-23 22:33:32'),
(1979, 'el', 'section_created', 'section', '', 'Ο τομέας <strong>{name}</strong> δημιουργήθηκε.', '2019-07-23 22:33:32'),
(1980, 'el', 'section_delete_failure', 'section', '', 'Δεν ήταν δυνατή η διαγραφή της/των ενότητας/ενοτήτων, καθώς περιέχει/ουν άρθρα: {name}.', '2019-07-23 22:33:32'),
(1981, 'el', 'section_deleted', 'section', '', 'Οι ενότητες διαγράφηκαν: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1982, 'el', 'section_head', 'section', '', 'Τομείς ιστοτόπου', '2019-07-23 22:33:32'),
(1983, 'el', 'section_longtitle', 'section', '', 'Τίτλος τομέα', '2019-07-23 22:33:32'),
(1984, 'el', 'section_name', 'section', '', 'Όνομα τομέα', '2019-07-23 22:33:32'),
(1985, 'el', 'section_name_already_exists', 'section', '', 'Υπάρχει ήδη τομέας με αυτό το όνομα <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1986, 'el', 'section_save_failed', 'section', '', 'Ο τομέας δεν αποθηκεύτηκε λόγω σφάλματος. Παρακαλώ ξαναπροσπαθήστε.', '2019-07-23 22:33:32'),
(1987, 'el', 'section_updated', 'section', '', 'Οι ενότητες ενημερώθηκαν: <strong>{name}</strong>.', '2019-07-23 22:33:32'),
(1988, 'el', 'switch_dev_live', 'section', '', 'Προεπισκόπηση για τους προγραμματιστές', '2019-07-23 22:33:32'),
(1989, 'el', 'syndicate', 'section', '', 'Ροή ειδήσεων από τον τομέα;', '2019-07-23 22:33:32'),
(1990, 'el', 'uses_page', 'section', '', 'Χρησιμοποιεί τη σελίδα', '2019-07-23 22:33:32'),
(1991, 'el', 'uses_skin', 'section', '', 'Χρησιμοποιεί θέμα', '2019-07-23 22:33:32'),
(1992, 'el', 'uses_style', 'section', '', 'Χρησιμοποιεί το στυλ', '2019-07-23 22:33:32'),
(1993, 'el', 'create_skin', 'skin', '', 'Νέο θέμα', '2019-07-23 22:33:32'),
(1994, 'el', 'css_creation_failed', 'skin', '', 'Η δημιουργία στυλ απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1995, 'el', 'css_deletion_failed', 'skin', '', 'Η διαγραφή του στυλ απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1996, 'el', 'css_files_deletion_failed', 'skin', '', 'Η διαγραφή των στυλ απέτυχε: <strong>{$list}</strong>.', '2019-07-23 22:33:32'),
(1997, 'el', 'css_import_failed', 'skin', '', 'Η εισαγωγή του στυλ απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(1998, 'el', 'css_name_unsafe', 'skin', '', 'Ονόματα μη εξαγώγιμων στυλ για: <strong>{list}</strong>. Τα ονόματα πρέπει να ταιριάζουν με την ακόλουθη κανονική έκφραση: <code>^[a-z][ένα-z0 - 9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:32'),
(1999, 'el', 'css_not_found', 'skin', '', 'Δεν βρέθηκαν τα δεδομένα του στυλ για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2000, 'el', 'css_update_failed', 'skin', '', 'Η ενημέρωση του στυλ απέτυχε για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2001, 'el', 'edit_skin', 'skin', '', 'Επεξεργασία του skin', '2019-07-23 22:33:32'),
(2002, 'el', 'form_creation_failed', 'skin', '', 'Η δημιουργία της φόρμας απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2003, 'el', 'form_deletion_failed', 'skin', '', 'Η διαγραφή της φόρμας απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2004, 'el', 'form_duplicate_failed', 'skin', '', 'Η εισαγωγή για τις φόρμες απέτυχε διότι είναι διπλές: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2005, 'el', 'form_files_deletion_failed', 'skin', '', 'Απέτυχε η διαγραφή των αρχείων για τις φόρμες: <strong>{$list}</strong>.', '2019-07-23 22:33:32'),
(2006, 'el', 'form_import_failed', 'skin', '', 'Η εισαγωγή της φόρμας απέτυχε για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2007, 'el', 'form_name_unsafe', 'skin', '', 'Μη εξαγώγιμες μορφές ονομάτων για: <strong>{list}</strong>. Τα ονόματα πρέπει να ταιριάζουν με την ακόλουθη κανονική έκφραση: <code>^[a-z][ένα-z0 - 9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:32'),
(2008, 'el', 'form_not_found', 'skin', '', 'Δεν βρέθηκαν στοιχεία φόρμας για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2009, 'el', 'form_subdir_error', 'skin', '', 'Σφάλμα στο τύπων της φόρμας για: <strong>{list}</strong>. Αυτές οι φόρμες είναι προεπιλεγμένες από ετικέτες και να χρειάζονται καθορισμένους τύπους.', '2019-07-23 22:33:32'),
(2010, 'el', 'form_subdir_invalid', 'skin', '', 'Μη έγκυρη μορφή φόρμας για: <strong>{list}</strong>. Οι έγκυροι τύποι είναι: <code>article</code>, <code>category</code>, <code>comment</code>, <code>file</code>, <code>link</code>, <code>section</code> και <code>misc</code>. Το <code>misc</code> χρησιμοποιείτε σαν προεπιλογή.', '2019-07-23 22:33:32'),
(2011, 'el', 'form_update_failed', 'skin', '', 'Απέτυχε η ενημέρωση της φόρμας για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2012, 'el', 'no_skin_recorded', 'skin', '', 'Δεν καταγράφηκαν θέματα.', '2019-07-23 22:33:32'),
(2013, 'el', 'page_creation_failed', 'skin', '', 'Η δημιουργία των σελίδων απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2014, 'el', 'page_deletion_failed', 'skin', '', 'Η διαγραφή των σελίδων απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2015, 'el', 'page_files_deletion_failed', 'skin', '', 'Απέτυχε η διαγραφή των αρχείων των σελίδων: <strong>{$list}</strong>.', '2019-07-23 22:33:32'),
(2016, 'el', 'page_import_failed', 'skin', '', 'Η εισαγωγή σελίδων απέτυχε για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2017, 'el', 'page_name_unsafe', 'skin', '', 'Μη εξαγώγιμες μορφές ονομάτων σελίδων για: <strong>{list}</strong>. Τα ονόματα πρέπει να ταιριάζουν με την ακόλουθη κανονική έκφραση: <code>^[a-z][ένα-z0 - 9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:32'),
(2018, 'el', 'page_not_found', 'skin', '', 'Δεν βρέθηκαν στοιχεία σελίδας για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2019, 'el', 'page_update_failed', 'skin', '', 'Απέτυχε η ενημέρωση σελίδων για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2020, 'el', 'search_skins', 'skin', '', 'Αναζήτηση θεμάτων', '2019-07-23 22:33:32'),
(2021, 'el', 'skin_already_exists', 'skin', '', 'Τα θέματα ήδη υπάρχουν στη βάση δεδομένων ή στον κατάλογο θεμάτων και μπορεί να προκαλέσουν διενέξεις: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2022, 'el', 'skin_author', 'skin', '', 'Δημιουργός του skin', '2019-07-23 22:33:32'),
(2023, 'el', 'skin_author_uri', 'skin', '', 'Ιστοσελίδα του skin', '2019-07-23 22:33:32'),
(2024, 'el', 'skin_count_css', 'skin', '', 'Στυλ σε αυτό το θέμα: {num}.', '2019-07-23 22:33:32'),
(2025, 'el', 'skin_count_form', 'skin', '', 'Φόρμες σε αυτό το θέμα: {num}.', '2019-07-23 22:33:32'),
(2026, 'el', 'skin_count_page', 'skin', '', 'Σελίδες σε αυτό το θέμα: {num}.', '2019-07-23 22:33:32'),
(2027, 'el', 'skin_count_section', 'skin', '', 'Τμήματα που χρησιμοποιούνται από αυτό το θέμα: {num}.', '2019-07-23 22:33:32'),
(2028, 'el', 'skin_created', 'skin', '', 'Θέματα που δημιουργήθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2029, 'el', 'skin_creation_failed', 'skin', '', 'Απέτυχε η δημιουργία του θέματα για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2030, 'el', 'skin_delete_entirely', 'skin', '', 'Διαγράψτε τα πρότυπα του θέματος από δίσκο', '2019-07-23 22:33:32'),
(2031, 'el', 'skin_delete_from_database', 'skin', '', 'Διαγραφή αχρησιμοποίητων προτύπων από βάση δεδομένων κατά την εισαγωγή', '2019-07-23 22:33:32'),
(2032, 'el', 'skin_delete_from_disk', 'skin', '', 'Διαγραφή αχρησιμοποίητων προτύπων από το δίσκο κατά την εξαγωγή', '2019-07-23 22:33:32'),
(2033, 'el', 'skin_deleted', 'skin', '', 'Τα θέματα έχουν διαγραφεί: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2034, 'el', 'skin_deletion_failed', 'skin', '', 'Η διαγραφή του/των Θεµα(-των) απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2035, 'el', 'skin_description', 'skin', '', 'Περιγραφή θέματος', '2019-07-23 22:33:32'),
(2036, 'el', 'skin_export_failed', 'skin', '', 'Η εξαγωγή των θεμάτων απέτυχε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2037, 'el', 'skin_exported', 'skin', '', 'Θέματα που εξάχθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(2038, 'el', 'skin_files_deletion_failed', 'skin', '', 'Απέτυχε η διαγραφή αρχείων για τα θέματα: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2039, 'el', 'skin_import_failed', 'skin', '', 'Η εισαγωγή των θεμάτων απέτυχε για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2040, 'el', 'skin_imported', 'skin', '', 'Θέματα που εισάχθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2041, 'el', 'skin_in_use', 'skin', '', 'Η διαγραφή θεμάτων που χρησιμοποιούνται από τμήματα ματαιώθηκε: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2042, 'el', 'skin_name', 'skin', '', 'Όνομα του skin', '2019-07-23 22:33:32'),
(2043, 'el', 'skin_name_unsafe', 'skin', '', 'Μη εξαγώγιμες μορφές ονομάτων θεμάτων για: <strong>{list}</strong>. Τα ονόματα πρέπει να ταιριάζουν με την ακόλουθη κανονική έκφραση: <code>^[a-z][ένα-z0 - 9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:32'),
(2044, 'el', 'skin_not_found', 'skin', '', 'Δεν βρέθηκαν δεδομένα για το θέμα για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2045, 'el', 'skin_related_sections_update_failed', 'skin', '', 'Απέτυχε η ενημέρωση των θεμάτων που σχετίζονται με τμήματα: <strong>{list}</strong>. Παρακαλούμε επισκεφθείτε τα τμήματα για να τα ενημερώσετε με μη αυτόματο τρόπο.', '2019-07-23 22:33:32'),
(2046, 'el', 'skin_title', 'skin', '', 'Τίτλος του skin', '2019-07-23 22:33:32'),
(2047, 'el', 'skin_unknown', 'skin', '', 'Άγνωστα θέματα: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2048, 'el', 'skin_update_failed', 'skin', '', 'Η ενημέρωση των θεμάτων απέτυχε για: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2049, 'el', 'skin_updated', 'skin', '', 'Τα θέματα ενημερώθηκαν: <strong>{list}</strong>.', '2019-07-23 22:33:32'),
(2050, 'el', 'skin_version', 'skin', '', 'Έκδοση του skin', '2019-07-23 22:33:32'),
(2051, 'el', 'active_class', 'tag', '', 'κλάση CSS για ενεργή ετικέτα λίστας HTML', '2019-07-23 22:33:32'),
(2052, 'el', 'allowoverride', 'tag', '', 'Να επιτραπεί η προσπέλαση φόρμας παρουσίασης;', '2019-07-23 22:33:32'),
(2053, 'el', 'breadcrumb_linked', 'tag', '', 'Συνδέσεις για τα «breadcrumb»;', '2019-07-23 22:33:32'),
(2054, 'el', 'breadcrumb_separator', 'tag', '', 'Διαχωριστικό «breadcrumb»', '2019-07-23 22:33:32'),
(2055, 'el', 'break', 'tag', '', 'Νέα γραμμή', '2019-07-23 22:33:32'),
(2056, 'el', 'breakclass', 'tag', '', 'Κλάση CSS για την μορφοποίηση σειρών λίστας HTML', '2019-07-23 22:33:32'),
(2057, 'el', 'build', 'tag', '', 'Δημιουργία Tag', '2019-07-23 22:33:32'),
(2058, 'el', 'button_text', 'tag', '', 'Κείμενο κουμπιού', '2019-07-23 22:33:32'),
(2059, 'el', 'category_list_section', 'tag', '', 'Σύνδεση προς συγκεκριμένο τομέα;', '2019-07-23 22:33:32'),
(2060, 'el', 'category_tags', 'tag', '', 'Κατηγορία', '2019-07-23 22:33:32'),
(2061, 'el', 'class', 'tag', '', 'Κλάση CSS', '2019-07-23 22:33:32'),
(2062, 'el', 'comments_form', 'tag', '', 'Φόρμα', '2019-07-23 22:33:32'),
(2063, 'el', 'comment_details', 'tag', '', 'Λεπτομέρειες σχολίων', '2019-07-23 22:33:32'),
(2064, 'el', 'comment_form', 'tag', '', 'Φόρμα σχολίων', '2019-07-23 22:33:32'),
(2065, 'el', 'comment_name_link', 'tag', '', 'Να εμφανιστεί η σύνδεση διεύθυνσης email και ιστότοπου του σχολιαστή;', '2019-07-23 22:33:32'),
(2066, 'el', 'decimals', 'tag', '', 'Εμφάνηση χ δεκαδικών κλασματικών Ψηφίων', '2019-07-23 22:33:32'),
(2067, 'el', 'default_title', 'tag', '', 'Κείμενο σύνδεσης για τον προκαθορισμένο τομέα', '2019-07-23 22:33:32'),
(2068, 'el', 'depth', 'tag', '', 'Βάθος', '2019-07-23 22:33:32'),
(2069, 'el', 'escape', 'tag', '', 'Διαφυγή', '2019-07-23 22:33:32'),
(2070, 'el', 'exclude', 'tag', '', 'Εξαίρεση', '2019-07-23 22:33:32'),
(2071, 'el', 'filename', 'tag', '', 'Όνομα', '2019-07-23 22:33:32'),
(2072, 'el', 'file_download_tags', 'tag', '', 'Μεταφορτώσεις αρχείων', '2019-07-23 22:33:32'),
(2073, 'el', 'flavor', 'tag', '', 'Μορφή ανταλλαγής περιεχομένου', '2019-07-23 22:33:32'),
(2074, 'el', 'format', 'tag', '', 'Μορφή', '2019-07-23 22:33:32'),
(2075, 'el', 'gmt', 'tag', '', 'Εμφάνηση σύμφωνα με με τον χρόνο του <abbr title=\"Greenwich Mean Time\">GMT</abbr>;', '2019-07-23 22:33:32'),
(2076, 'el', 'has_excerpt', 'tag', '', 'Έχει απόσπασμα', '2019-07-23 22:33:32'),
(2077, 'el', 'hilight', 'tag', '', 'Τονισμός του Tag', '2019-07-23 22:33:32'),
(2078, 'el', 'hilight_limit', 'tag', '', 'Πόσα αντικείμενα να τονίζονται;', '2019-07-23 22:33:32'),
(2079, 'el', 'html_id', 'tag', '', 'Ιδιότητα <code>id</code> HTML', '2019-07-23 22:33:32'),
(2080, 'el', 'include_default', 'tag', '', 'Να συμπεριλαμβάνεται ο προκαθορισμένος τομέας;', '2019-07-23 22:33:32'),
(2081, 'el', 'inline_style', 'tag', '', 'Κλάση CSS «inline»', '2019-07-23 22:33:32'),
(2082, 'el', 'input_size', 'tag', '', 'Μέγεθος πεδίου εισαγωγής', '2019-07-23 22:33:32'),
(2083, 'el', 'labeltag', 'tag', '', 'ετικέτα HTML για επικεφαλίδα', '2019-07-23 22:33:32'),
(2084, 'el', 'limit', 'tag', '', 'Όριο', '2019-07-23 22:33:32'),
(2085, 'el', 'linkclass', 'tag', '', 'Κλάση CSS για συνδέσεις', '2019-07-23 22:33:32'),
(2086, 'el', 'link_text', 'tag', '', 'Κείμενο σύνδεσμου', '2019-07-23 22:33:32'),
(2087, 'el', 'link_to_this_author', 'tag', '', 'Σύνδεση προς λίστα με άλλα άρθρα του ίδιου χρήστη;', '2019-07-23 22:33:32'),
(2088, 'el', 'link_to_this_category', 'tag', '', 'Σύνδεση προς λίστα με άλλα άρθρα της ίδιας κατηγορίας;', '2019-07-23 22:33:32'),
(2089, 'el', 'link_to_this_section', 'tag', '', 'Σύνδεση προς λίστα με άλλα άρθρα του ίδιου τομέα;', '2019-07-23 22:33:32'),
(2090, 'el', 'listform', 'tag', '', 'Φόρμα λίστας', '2019-07-23 22:33:32'),
(2091, 'el', 'match_type', 'tag', '', 'Αντιστοιχίες', '2019-07-23 22:33:32'),
(2092, 'el', 'media', 'tag', '', 'Ιδιότητα <code>media</code> HTML', '2019-07-23 22:33:32'),
(2093, 'el', 'msgcols', 'tag', '', 'Στήλες πεδίου εισαγωγής μηνύματος', '2019-07-23 22:33:32'),
(2094, 'el', 'msgrows', 'tag', '', 'Σειρές πεδίου εισαγωγής μηνύματος', '2019-07-23 22:33:32'),
(2095, 'el', 'no_categories_available', 'tag', '', 'Δεν υπάρχουν διαθέσιμες κατηγορίες.', '2019-07-23 22:33:32'),
(2096, 'el', 'no_forms_available', 'tag', '', 'Δεν υπάρχουν διαθέσιμες φόρμες.', '2019-07-23 22:33:32'),
(2097, 'el', 'no_sections_available', 'tag', '', 'Δεν υπάρχουν διαθέσιμοι τομείς.', '2019-07-23 22:33:32'),
(2098, 'el', 'offset', 'tag', '', 'Προσπέλαση', '2019-07-23 22:33:32'),
(2099, 'el', 'page_article_hed', 'tag', '', 'Επισκόπηση άρθρου', '2019-07-23 22:33:32'),
(2100, 'el', 'page_article_nav_hed', 'tag', '', 'Πλοήγηση άρθρου', '2019-07-23 22:33:32'),
(2101, 'el', 'page_file_hed', 'tag', '', 'Μεταφορτώσεις αρχείων', '2019-07-23 22:33:32'),
(2102, 'el', 'page_misc_hed', 'tag', '', 'Διάφορα', '2019-07-23 22:33:32'),
(2103, 'el', 'page_nav_hed', 'tag', '', 'Πλοήγηση ιστοτόπου', '2019-07-23 22:33:32'),
(2104, 'el', 'page_xml_hed', 'tag', '', 'Τροφοδοσίες XML', '2019-07-23 22:33:32'),
(2105, 'el', 'pageby', 'tag', '', 'Σελιδοποίηση ανά', '2019-07-23 22:33:32'),
(2106, 'el', 'pgonly', 'tag', '', 'Μέτρηση άρθρων χωρίς εμφάνιση', '2019-07-23 22:33:32'),
(2107, 'el', 'random', 'tag', '', 'Τυχαίο', '2019-07-23 22:33:32'),
(2108, 'el', 'rel', 'tag', '', 'Ιδιότητα <code>rel</code> HTML', '2019-07-23 22:33:32'),
(2109, 'el', 'searchall', 'tag', '', 'Αναζήτηση σε όλους τους τομείς;', '2019-07-23 22:33:32'),
(2110, 'el', 'searchsticky', 'tag', '', 'Αναζήτηση και σε «sticky» άρθρα;', '2019-07-23 22:33:32'),
(2111, 'el', 'search_input_form', 'tag', '', 'Όροι αναζήτησης', '2019-07-23 22:33:32'),
(2112, 'el', 'search_results_form', 'tag', '', 'Όροι αναζήτησης', '2019-07-23 22:33:32'),
(2113, 'el', 'section_tags', 'tag', '', 'Τομέας', '2019-07-23 22:33:32'),
(2114, 'el', 'separator', 'tag', '', 'Διαχωριστικό', '2019-07-23 22:33:32'),
(2115, 'el', 'showalways', 'tag', '', 'Ολική εμφάνιση', '2019-07-23 22:33:32'),
(2116, 'el', 'showcount', 'tag', '', 'Εμφάνιση μετρήσεων;', '2019-07-23 22:33:32'),
(2117, 'el', 'size_format', 'tag', '', 'Μορφή εμφάνησης μεγέθους', '2019-07-23 22:33:32'),
(2118, 'el', 'sort', 'tag', '', 'Αναζήτηση ανα', '2019-07-23 22:33:32'),
(2119, 'el', 'tag', 'tag', '', 'Tag', '2019-07-23 22:33:32'),
(2120, 'el', 'tag_article', 'tag', '', 'Άρθρα (μεμονωμένα ή σε λίστα)', '2019-07-23 22:33:32'),
(2121, 'el', 'tag_article_custom', 'tag', '', 'Άρθρα (προσαρμοσμένη λίστα)', '2019-07-23 22:33:32'),
(2122, 'el', 'tag_article_image', 'tag', '', 'Εικόνα άρθρου', '2019-07-23 22:33:32'),
(2123, 'el', 'tag_author', 'tag', '', 'Χρήστης', '2019-07-23 22:33:32'),
(2124, 'el', 'tag_body', 'tag', '', 'Κυρίως άρθρο', '2019-07-23 22:33:32'),
(2125, 'el', 'tag_body_excerpt', 'tag', '', 'Απόσπασμα άρθρου', '2019-07-23 22:33:32'),
(2126, 'el', 'tag_breadcrumb', 'tag', '', '«Breadcrumb»', '2019-07-23 22:33:32'),
(2127, 'el', 'tag_category', 'tag', '', 'Κατηγορία αρχείου', '2019-07-23 22:33:32'),
(2128, 'el', 'tag_category1', 'tag', '', 'Κατηγορία 1', '2019-07-23 22:33:32'),
(2129, 'el', 'tag_category2', 'tag', '', 'Κατηγορία 2', '2019-07-23 22:33:32'),
(2130, 'el', 'tag_category_list', 'tag', '', 'Λίστα κατηγοριών', '2019-07-23 22:33:32'),
(2131, 'el', 'tag_comments', 'tag', '', 'Σχόλια', '2019-07-23 22:33:32'),
(2132, 'el', 'tag_comments_form', 'tag', '', 'Φόρμα', '2019-07-23 22:33:32'),
(2133, 'el', 'tag_comments_invite', 'tag', '', 'Πρόσκληση για σχολιασμό', '2019-07-23 22:33:32'),
(2134, 'el', 'tag_comments_preview', 'tag', '', 'Προεπισκόπηση', '2019-07-23 22:33:32'),
(2135, 'el', 'tag_comment_anchor', 'tag', '', 'Αγκύρωση (HTML)', '2019-07-23 22:33:32'),
(2136, 'el', 'tag_comment_email', 'tag', '', 'Σχόλιο email', '2019-07-23 22:33:32'),
(2137, 'el', 'tag_comment_email_input', 'tag', '', 'Εισαγωγή email', '2019-07-23 22:33:32'),
(2138, 'el', 'tag_comment_id', 'tag', '', 'ΑΑ', '2019-07-23 22:33:32'),
(2139, 'el', 'tag_comment_message', 'tag', '', 'Μήνυμα', '2019-07-23 22:33:32'),
(2140, 'el', 'tag_comment_message_input', 'tag', '', 'Εισαγωγή μηνύματος', '2019-07-23 22:33:32'),
(2141, 'el', 'tag_comment_name', 'tag', '', 'Όνομα σχολιαστή', '2019-07-23 22:33:32'),
(2142, 'el', 'tag_comment_name_input', 'tag', '', 'Εισαγωγή ονόματος', '2019-07-23 22:33:32'),
(2143, 'el', 'tag_comment_permlink', 'tag', '', 'Μόνιμη σύνδεση', '2019-07-23 22:33:32'),
(2144, 'el', 'tag_comment_preview', 'tag', '', 'Κουμπί προεπισκόπησης', '2019-07-23 22:33:32'),
(2145, 'el', 'tag_comment_remember', 'tag', '', 'Συγκράτηση προσωπικών στοιχείων', '2019-07-23 22:33:32'),
(2146, 'el', 'tag_comment_submit', 'tag', '', 'Κουμπί υποβολής', '2019-07-23 22:33:32'),
(2147, 'el', 'tag_comment_time', 'tag', '', 'Ώρα', '2019-07-23 22:33:32'),
(2148, 'el', 'tag_comment_web', 'tag', '', 'Ιστότοπος', '2019-07-23 22:33:32'),
(2149, 'el', 'tag_comment_web_input', 'tag', '', 'Εισαγωγή ιστότοπου', '2019-07-23 22:33:32'),
(2150, 'el', 'tag_css', 'tag', '', 'Σύνδεση CSS (<code>head</code>)', '2019-07-23 22:33:32'),
(2151, 'el', 'tag_email', 'tag', '', 'Σύνδεση email (αντι-spam)', '2019-07-23 22:33:32'),
(2152, 'el', 'tag_excerpt', 'tag', '', 'Απόσπασμα', '2019-07-23 22:33:32'),
(2153, 'el', 'tag_feed_link', 'tag', '', 'Σύνδεση ροής ειδήσεων άρθρων', '2019-07-23 22:33:32'),
(2154, 'el', 'tag_file_download', 'tag', '', 'Φόρμα μεταφόρτωσης αρχείου', '2019-07-23 22:33:32'),
(2155, 'el', 'tag_file_download_category', 'tag', '', 'Κατηγορία', '2019-07-23 22:33:32'),
(2156, 'el', 'tag_file_download_created', 'tag', '', 'Χρόνος δημιουργίας', '2019-07-23 22:33:32'),
(2157, 'el', 'tag_file_download_description', 'tag', '', 'Περιγραφή', '2019-07-23 22:33:32'),
(2158, 'el', 'tag_file_download_downloads', 'tag', '', 'Αριθμός μεταφορτώσεων', '2019-07-23 22:33:32'),
(2159, 'el', 'tag_file_download_id', 'tag', '', 'ΑΑ', '2019-07-23 22:33:32'),
(2160, 'el', 'tag_file_download_link', 'tag', '', 'Σύνδεση για τη μεταφόρτωση αρχείου', '2019-07-23 22:33:32'),
(2161, 'el', 'tag_file_download_list', 'tag', '', 'Λίστα αρχείων', '2019-07-23 22:33:32'),
(2162, 'el', 'tag_file_download_modified', 'tag', '', 'Χρόνος τροποποίησης', '2019-07-23 22:33:32'),
(2163, 'el', 'tag_file_download_name', 'tag', '', 'Όνομα', '2019-07-23 22:33:32'),
(2164, 'el', 'tag_file_download_size', 'tag', '', 'Μέγεθος', '2019-07-23 22:33:32'),
(2165, 'el', 'tag_home', 'tag', '', 'Αρχική σελίδα', '2019-07-23 22:33:32'),
(2166, 'el', 'tag_if_category', 'tag', '', 'Αν βρίσκεστε στη κατηγορία', '2019-07-23 22:33:32'),
(2167, 'el', 'tag_if_section', 'tag', '', 'Αν βρίσκεστε στον τομέα', '2019-07-23 22:33:32'),
(2168, 'el', 'tag_image', 'tag', '', 'Εικόνα', '2019-07-23 22:33:32'),
(2169, 'el', 'tag_lang', 'tag', '', 'Γλώσσα', '2019-07-23 22:33:32'),
(2170, 'el', 'tag_link', 'tag', '', 'Σύνδεση', '2019-07-23 22:33:32'),
(2171, 'el', 'tag_linkdesctitle', 'tag', '', 'Σύνδεση, title=Περιγραφή', '2019-07-23 22:33:32'),
(2172, 'el', 'tag_linklist', 'tag', '', 'Λίστα συνδέσεων', '2019-07-23 22:33:32'),
(2173, 'el', 'tag_link_category', 'tag', '', 'Κατηγορία συνδέσεων', '2019-07-23 22:33:32'),
(2174, 'el', 'tag_link_date', 'tag', '', 'Ημερομηνία συνδέσεων', '2019-07-23 22:33:32'),
(2175, 'el', 'tag_link_description', 'tag', '', 'Περιγραφή σύνδεσης', '2019-07-23 22:33:32'),
(2176, 'el', 'tag_link_feed_link', 'tag', '', 'Σύνδεση ροής ειδήσεων', '2019-07-23 22:33:32'),
(2177, 'el', 'tag_link_name', 'tag', '', 'Όνομα σύνδεσης', '2019-07-23 22:33:32'),
(2178, 'el', 'tag_link_text', 'tag', '', 'Όνομα σύνδεσης', '2019-07-23 22:33:32'),
(2179, 'el', 'tag_link_to_home', 'tag', '', 'Σύνδεση προς την αρχική σελίδα', '2019-07-23 22:33:32'),
(2180, 'el', 'tag_link_to_next', 'tag', '', 'Σύνδεση προς την επόμενη σελίδα', '2019-07-23 22:33:32'),
(2181, 'el', 'tag_link_to_prev', 'tag', '', 'Σύνδεση προς την προηγούμενη σελίδα', '2019-07-23 22:33:32'),
(2182, 'el', 'tag_name', 'tag', '', 'Όνομα σχολιαστή', '2019-07-23 22:33:32'),
(2183, 'el', 'tag_newer', 'tag', '', 'Σύνδεση προς νεότερα άρθρα', '2019-07-23 22:33:32'),
(2184, 'el', 'tag_next_article', 'tag', '', 'Επόμενο άρθρο', '2019-07-23 22:33:32'),
(2185, 'el', 'tag_next_title', 'tag', '', 'Τίτλος επόμενου άρθρου', '2019-07-23 22:33:32'),
(2186, 'el', 'tag_older', 'tag', '', 'Σύνδεση προς παλαιότερα άρθρα', '2019-07-23 22:33:32'),
(2187, 'el', 'tag_output_form', 'tag', '', 'Φόρμα παρουσίασης', '2019-07-23 22:33:32'),
(2188, 'el', 'tag_page_title', 'tag', '', 'Τίτλος σελίδας', '2019-07-23 22:33:32'),
(2189, 'el', 'tag_password_protect', 'tag', '', 'Προστασία με κωδικό πρόσβασης', '2019-07-23 22:33:32'),
(2190, 'el', 'tag_permlink', 'tag', '', 'Μόνιμος σύνδεσμος', '2019-07-23 22:33:32'),
(2191, 'el', 'tag_popup', 'tag', '', 'Λίστα «popup»', '2019-07-23 22:33:32'),
(2192, 'el', 'tag_posted', 'tag', '', 'Ημ/νία δημοσίευσης', '2019-07-23 22:33:32'),
(2193, 'el', 'tag_prev_article', 'tag', '', 'Προηγούμενο άρθρο', '2019-07-23 22:33:32'),
(2194, 'el', 'tag_prev_title', 'tag', '', 'Τίτλος προηγούμενου άρθρου', '2019-07-23 22:33:32'),
(2195, 'el', 'tag_recent_articles', 'tag', '', 'Πρόσφατα άρθρα', '2019-07-23 22:33:32'),
(2196, 'el', 'tag_recent_comments', 'tag', '', 'Πρόσφατα σχόλια', '2019-07-23 22:33:32'),
(2197, 'el', 'tag_related_articles', 'tag', '', 'Σχετικά άρθρα', '2019-07-23 22:33:32'),
(2198, 'el', 'tag_search_input', 'tag', '', 'Φόρμα όρων αναζήτησης', '2019-07-23 22:33:32'),
(2199, 'el', 'tag_search_result_date', 'tag', '', 'Ημ/νία αποτελέσματος', '2019-07-23 22:33:32'),
(2200, 'el', 'tag_search_result_excerpt', 'tag', '', 'Απόσπασμα αποτελέσματος', '2019-07-23 22:33:32'),
(2201, 'el', 'tag_search_result_title', 'tag', '', 'Τίτλος αποτελέσματος', '2019-07-23 22:33:32'),
(2202, 'el', 'tag_search_result_url', 'tag', '', 'URL αποτελέσματος', '2019-07-23 22:33:32'),
(2203, 'el', 'tag_section', 'tag', '', 'Τομέας', '2019-07-23 22:33:32'),
(2204, 'el', 'tag_section_list', 'tag', '', 'Λίστα τομέων', '2019-07-23 22:33:32'),
(2205, 'el', 'tag_site_name', 'tag', '', 'Όνομα ιστοτόπου', '2019-07-23 22:33:32'),
(2206, 'el', 'tag_site_slogan', 'tag', '', 'Σλόγκαν ιστοτόπου', '2019-07-23 22:33:32'),
(2207, 'el', 'tag_title', 'tag', '', 'Τίτλος', '2019-07-23 22:33:32'),
(2208, 'el', 'textonly', 'tag', '', 'Μόνο κείμενο;', '2019-07-23 22:33:32'),
(2209, 'el', 'this_section', 'tag', '', 'Σύνδεση στον ενεργό τομέα;', '2019-07-23 22:33:32'),
(2210, 'el', 'time_any', 'tag', '', 'Οποιαδήποτε', '2019-07-23 22:33:32'),
(2211, 'el', 'time_format', 'tag', '', 'Παραμετροποίηση χρονομετρικών στοιχείων', '2019-07-23 22:33:32'),
(2212, 'el', 'time_future', 'tag', '', 'Mέλλον', '2019-07-23 22:33:32'),
(2213, 'el', 'time_past', 'tag', '', 'Παρελθόν', '2019-07-23 22:33:32'),
(2214, 'el', 'title_separator', 'tag', '', 'Όνομα ιστοτόπου: Διαχωριστικό μεταξύ μεμονωμένων άρθρων', '2019-07-23 22:33:32'),
(2215, 'el', 'tooltip', 'tag', '', 'Κείμενο κέρσορα για συνδέσεις', '2019-07-23 22:33:32'),
(2216, 'el', 'use_thumbnail', 'tag', '', 'Χρήση εικονιδίων;', '2019-07-23 22:33:32'),
(2217, 'el', 'wraptag', 'tag', '', 'Tags που περικλείουν', '2019-07-23 22:33:32'),
(2218, 'el', 'rvm_css_dir', 'prefs', 'rvm_css', 'Stylesheet directory', '2019-07-23 22:34:19'),
(2219, 'el', 'arc_meta', 'admin', 'site', 'arc_meta', '2019-07-23 22:34:19'),
(2220, 'el', 'arc_meta_title', 'admin', 'site', 'Meta title', '2019-07-23 22:34:19'),
(2221, 'el', 'arc_meta_description', 'admin', 'site', 'Meta description', '2019-07-23 22:34:19'),
(2222, 'el', 'arc_meta_image', 'admin', 'site', 'Meta image', '2019-07-23 22:34:19'),
(2223, 'el', 'arc_meta_robots', 'admin', 'site', 'Meta robots', '2019-07-23 22:34:19'),
(2224, 'el', 'arc_meta_article_title', 'admin', 'site', 'Article page titles pattern', '2019-07-23 22:34:19'),
(2225, 'el', 'arc_meta_comment_title', 'admin', 'site', 'Comment page titles pattern', '2019-07-23 22:34:19'),
(2226, 'el', 'arc_meta_search_title', 'admin', 'site', 'Search page titles pattern', '2019-07-23 22:34:19'),
(2227, 'el', 'arc_meta_category_title', 'admin', 'site', 'Category titles pattern', '2019-07-23 22:34:19'),
(2228, 'el', 'arc_meta_section_title', 'admin', 'site', 'Section titles pattern', '2019-07-23 22:34:19'),
(2229, 'el', 'arc_meta_section_category_title', 'admin', 'site', 'Section category titles pattern', '2019-07-23 22:34:19'),
(2230, 'el', 'arc_meta_homepage_title', 'admin', 'site', 'Homepage title pattern', '2019-07-23 22:34:19'),
(2231, 'el', 'arc_meta_section_tab', 'admin', 'site', 'Additional panel location for sections meta data edit', '2019-07-23 22:34:19'),
(2232, 'el', 'arc_meta_section_tab_content', 'admin', 'site', 'Content', '2019-07-23 22:34:19'),
(2233, 'el', 'arc_meta_section_tab_extensions', 'admin', 'site', 'Extensions', '2019-07-23 22:34:19'),
(2234, 'el', 'arc_meta_section_tab_hidden', 'admin', 'site', 'Hidden', '2019-07-23 22:34:19'),
(2235, 'el', 'ied_plugin_any', 'ied_plugin', 'site', 'Any', '2019-07-25 01:42:31'),
(2236, 'el', 'ied_plugin_auto_enable', 'ied_plugin', 'site', 'Auto-enable plugins on install', '2019-07-25 01:42:31'),
(2237, 'el', 'ied_plugin_cache_not_set', 'ied_plugin', 'site', 'Advanced pref \"plugin cache directory\" not defined', '2019-07-25 01:42:31'),
(2238, 'el', 'ied_plugin_check_type', 'ied_plugin', 'site', 'Check your plugin type!', '2019-07-25 01:42:31'),
(2239, 'el', 'ied_plugin_choose_file', 'ied_plugin', 'site', 'Please choose a file first', '2019-07-25 01:42:31'),
(2240, 'el', 'ied_plugin_code_dist', 'ied_plugin', 'site', 'Plugin code for distribution', '2019-07-25 01:42:31'),
(2241, 'el', 'ied_plugin_code_legend', 'ied_plugin', 'site', 'Plugin code', '2019-07-25 01:42:31'),
(2242, 'el', 'ied_plugin_code_save', 'ied_plugin', 'site', 'Save code', '2019-07-25 01:42:31'),
(2243, 'el', 'ied_plugin_code_saved', 'ied_plugin', 'site', 'Code saved', '2019-07-25 01:42:31'),
(2244, 'el', 'ied_plugin_code_saved_fail', 'ied_plugin', 'site', 'Code saving failed', '2019-07-25 01:42:31'),
(2245, 'el', 'ied_plugin_compress', 'ied_plugin', 'site', 'Zip', '2019-07-25 01:42:31'),
(2246, 'el', 'ied_plugin_cpanel_legend', 'ied_plugin', 'site', 'Installation', '2019-07-25 01:42:31'),
(2247, 'el', 'ied_plugin_create_new', 'ied_plugin', 'site', 'Create new plugin', '2019-07-25 01:42:31'),
(2248, 'el', 'ied_plugin_dbplugs_legend', 'ied_plugin', 'site', 'Plugins in database', '2019-07-25 01:42:31'),
(2249, 'el', 'ied_plugin_docs', 'ied_plugin', 'site', 'Docs', '2019-07-25 01:42:31'),
(2250, 'el', 'ied_plugin_docs_legend', 'ied_plugin', 'site', 'Plugin help', '2019-07-25 01:42:31'),
(2251, 'el', 'ied_plugin_edit', 'ied_plugin', 'site', 'Edit: {name} v{version}', '2019-07-25 01:42:31'),
(2252, 'el', 'ied_plugin_editing', 'ied_plugin', 'site', 'Editing {name} in Plugin Composer', '2019-07-25 01:42:31'),
(2253, 'el', 'ied_plugin_editor', 'ied_plugin', 'site', 'Plugin editor', '2019-07-25 01:42:31'),
(2254, 'el', 'ied_plugin_editor_options', 'ied_plugin', 'site', 'Plugin editor configuration options', '2019-07-25 01:42:31'),
(2255, 'el', 'ied_plugin_editor_path', 'ied_plugin', 'site', 'Plugin editor URL', '2019-07-25 01:42:31'),
(2256, 'el', 'ied_plugin_editor_width', 'ied_plugin', 'site', 'Plugin editor width', '2019-07-25 01:42:31'),
(2257, 'el', 'ied_plugin_edit_new', 'ied_plugin', 'site', 'Edit your new plugin', '2019-07-25 01:42:31'),
(2258, 'el', 'ied_plugin_enable', 'ied_plugin', 'site', 'Enabled', '2019-07-25 01:42:31'),
(2259, 'el', 'ied_plugin_export', 'ied_plugin', 'site', 'Export as {name}', '2019-07-25 01:42:31'),
(2260, 'el', 'ied_plugin_export_textpack', 'ied_plugin', 'site', 'Export textpack(s)', '2019-07-25 01:42:31'),
(2261, 'el', 'ied_plugin_export_zip', 'ied_plugin', 'site', 'Export as {name} (compressed)', '2019-07-25 01:42:31'),
(2262, 'el', 'ied_plugin_flags', 'ied_plugin', 'site', 'Flags', '2019-07-25 01:42:31'),
(2263, 'el', 'ied_plugin_flag_has_prefs', 'ied_plugin', 'site', 'Has prefs', '2019-07-25 01:42:31'),
(2264, 'el', 'ied_plugin_flag_lifecycle_notify', 'ied_plugin', 'site', 'Event notify', '2019-07-25 01:42:31'),
(2265, 'el', 'ied_plugin_fn_not_exist', 'ied_plugin', 'site', '(the function is probably javascript or mangled PHP)', '2019-07-25 01:42:31'),
(2266, 'el', 'ied_plugin_help_editor', 'ied_plugin', 'site', 'Help editor', '2019-07-25 01:42:31'),
(2267, 'el', 'ied_plugin_help_editor_options', 'ied_plugin', 'site', 'Help editor configuration options', '2019-07-25 01:42:31'),
(2268, 'el', 'ied_plugin_help_editor_path', 'ied_plugin', 'site', 'Help editor URL', '2019-07-25 01:42:31'),
(2269, 'el', 'ied_plugin_help_not_available', 'ied_plugin', 'site', 'Plugin help not available', '2019-07-25 01:42:31'),
(2270, 'el', 'ied_plugin_if_el_dist', 'ied_plugin', 'site', 'Code for distribution', '2019-07-25 01:42:31'),
(2271, 'el', 'ied_plugin_if_el_style', 'ied_plugin', 'site', 'Help style block', '2019-07-25 01:42:31'),
(2272, 'el', 'ied_plugin_if_settings', 'ied_plugin', 'site', 'Interface settings', '2019-07-25 01:42:31'),
(2273, 'el', 'ied_plugin_install_txt', 'ied_plugin', 'site', 'Install from .txt file', '2019-07-25 01:42:31'),
(2274, 'el', 'ied_plugin_interface_elems', 'ied_plugin', 'site', 'Optional interface elements', '2019-07-25 01:42:31'),
(2275, 'el', 'ied_plugin_jump_to_line', 'ied_plugin', 'site', 'Jump to line:', '2019-07-25 01:42:31'),
(2276, 'el', 'ied_plugin_langs_all', 'ied_plugin', 'site', 'All available', '2019-07-25 01:42:31'),
(2277, 'el', 'ied_plugin_langs_installed', 'ied_plugin', 'site', 'Only installed', '2019-07-25 01:42:31'),
(2278, 'el', 'ied_plugin_lang_choose', 'ied_plugin', 'site', 'Textpack language list', '2019-07-25 01:42:31'),
(2279, 'el', 'ied_plugin_lang_default', 'ied_plugin', 'site', 'Default textpack language', '2019-07-25 01:42:31'),
(2280, 'el', 'ied_plugin_lbl_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-23 22:34:19'),
(2281, 'el', 'ied_plugin_lbl_lc_delete', 'ied_plugin', 'site', 'Delete', '2019-07-25 01:42:31'),
(2282, 'el', 'ied_plugin_lbl_lc_disable', 'ied_plugin', 'site', 'Disable', '2019-07-25 01:42:31'),
(2283, 'el', 'ied_plugin_lbl_lc_enable', 'ied_plugin', 'site', 'Enable', '2019-07-25 01:42:31'),
(2284, 'el', 'ied_plugin_lbl_lc_install', 'ied_plugin', 'site', 'Install', '2019-07-25 01:42:31'),
(2285, 'el', 'ied_plugin_lbl_op_code_first', 'ied_plugin', 'site', 'Code first', '2019-07-25 01:42:31'),
(2286, 'el', 'ied_plugin_lbl_op_help_first', 'ied_plugin', 'site', 'Help first', '2019-07-25 01:42:31'),
(2287, 'el', 'ied_plugin_lbl_setup', 'ied_plugin', 'site', 'Plugin composer setup', '2019-07-25 01:42:31'),
(2288, 'el', 'ied_plugin_lifecycle_options', 'ied_plugin', 'site', 'Perform lifecycle actions on', '2019-07-25 01:42:31'),
(2289, 'el', 'ied_plugin_load', 'ied_plugin', 'site', 'Load', '2019-07-25 01:42:31'),
(2290, 'el', 'ied_plugin_load_order', 'ied_plugin', 'site', 'Load order', '2019-07-25 01:42:31'),
(2291, 'el', 'ied_plugin_load_order_help', 'ied_plugin', 'site', '(1=first > 5=normal > 9=last)', '2019-07-25 01:42:31'),
(2292, 'el', 'ied_plugin_meta_legend', 'ied_plugin', 'site', 'Meta information', '2019-07-25 01:42:31'),
(2293, 'el', 'ied_plugin_msgpop_lbl', 'ied_plugin', 'site', 'phpdoc block', '2019-07-25 01:42:31'),
(2294, 'el', 'ied_plugin_name_first', 'ied_plugin', 'site', 'Please name the plugin before creating it', '2019-07-25 01:42:31'),
(2295, 'el', 'ied_plugin_output_order', 'ied_plugin', 'site', 'PHP export order', '2019-07-25 01:42:31'),
(2296, 'el', 'ied_plugin_output_sfile', 'ied_plugin', 'site', 'Export plugin filename format', '2019-07-25 01:42:31'),
(2297, 'el', 'ied_plugin_output_sfilec', 'ied_plugin', 'site', 'Export compressed filename format', '2019-07-25 01:42:31'),
(2298, 'el', 'ied_plugin_output_sfilep', 'ied_plugin', 'site', 'Plugin template filename format', '2019-07-25 01:42:31'),
(2299, 'el', 'ied_plugin_output_sfilet', 'ied_plugin', 'site', 'Textpack filename format', '2019-07-25 01:42:31'),
(2300, 'el', 'ied_plugin_output_tmpcache', 'ied_plugin', 'site', 'Cache Textiled help path', '2019-07-25 01:42:31'),
(2301, 'el', 'ied_plugin_php_doc', 'ied_plugin', 'site', 'Make phpdoc', '2019-07-25 01:42:31'),
(2302, 'el', 'ied_plugin_prefs', 'ied_plugin', 'site', 'Preferences', '2019-07-25 01:42:31'),
(2303, 'el', 'ied_plugin_prefs_deleted', 'ied_plugin', 'site', 'Preferences deleted', '2019-07-25 01:42:31'),
(2304, 'el', 'ied_plugin_renamed', 'ied_plugin', 'site', '(renamed)', '2019-07-25 01:42:31'),
(2305, 'el', 'ied_plugin_rename_failed', 'ied_plugin', 'site', '(rename failed)', '2019-07-25 01:42:31'),
(2306, 'el', 'ied_plugin_rename_file', 'ied_plugin', 'site', 'Rename file', '2019-07-25 01:42:31'),
(2307, 'el', 'ied_plugin_restored', 'ied_plugin', 'site', 'Plugin {name} restored.', '2019-07-25 01:42:31'),
(2308, 'el', 'ied_plugin_restore_help', 'ied_plugin', 'site', 'Roll back the plugin code to the most recent restore point', '2019-07-25 01:42:31'),
(2309, 'el', 'ied_plugin_restore_point', 'ied_plugin', 'site', 'Restore point', '2019-07-25 01:42:31'),
(2310, 'el', 'ied_plugin_restore_verify', 'ied_plugin', 'site', 'Are you sure you want to rollback the {name} source code to its last restore point?', '2019-07-25 01:42:31'),
(2311, 'el', 'ied_plugin_run_from', 'ied_plugin', 'site', 'Les Plugins sont chargés depuis le cache', '2019-07-23 22:34:19'),
(2312, 'el', 'ied_plugin_run_install', 'ied_plugin', 'site', 'Perform post-install actions', '2019-07-25 01:42:31'),
(2313, 'el', 'ied_plugin_same', 'ied_plugin', 'site', 'Same', '2019-07-25 01:42:31'),
(2314, 'el', 'ied_plugin_save_as', 'ied_plugin', 'site', 'Save as {name}', '2019-07-25 01:42:31'),
(2315, 'el', 'ied_plugin_setup', 'ied_plugin', 'site', 'Setup', '2019-07-25 01:42:31'),
(2316, 'el', 'ied_plugin_syntax_check', 'ied_plugin', 'site', 'Syntax check on code save', '2019-07-25 01:42:31'),
(2317, 'el', 'ied_plugin_syntax_err', 'ied_plugin', 'site', 'Syntax error', '2019-07-25 01:42:31'),
(2318, 'el', 'ied_plugin_tp_legend', 'ied_plugin', 'site', 'Chaînes du Textpack', '2019-07-23 22:34:19'),
(2319, 'el', 'ied_plugin_tp_populate', 'ied_plugin', 'site', 'Load strings from function', '2019-07-25 01:42:31'),
(2320, 'el', 'ied_plugin_tp_prefix', 'ied_plugin', 'site', 'Textpack prefix', '2019-07-25 01:42:31'),
(2321, 'el', 'ied_plugin_type', 'ied_plugin', 'site', 'Plugin type', '2019-07-25 01:42:31'),
(2322, 'el', 'ied_plugin_type_0', 'ied_plugin', 'site', 'Public', '2019-07-25 01:42:31'),
(2323, 'el', 'ied_plugin_type_1', 'ied_plugin', 'site', 'Admin + Public', '2019-07-25 01:42:31'),
(2324, 'el', 'ied_plugin_type_2', 'ied_plugin', 'site', 'Library', '2019-07-25 01:42:31'),
(2325, 'el', 'ied_plugin_type_3', 'ied_plugin', 'site', 'Admin only', '2019-07-25 01:42:31'),
(2326, 'el', 'ied_plugin_type_4', 'ied_plugin', 'site', 'Admin (+AJAX)', '2019-07-25 01:42:31'),
(2327, 'el', 'ied_plugin_type_5', 'ied_plugin', 'site', 'Admin + Public (+AJAX)', '2019-07-25 01:42:31'),
(2328, 'el', 'ied_plugin_updated', 'ied_plugin', 'site', 'Plugin {name} updated', '2019-07-25 01:42:31'),
(2329, 'el', 'ied_plugin_uploaded', 'ied_plugin', 'site', 'Plugin {name} uploaded', '2019-07-25 01:42:31'),
(2330, 'el', 'ied_plugin_upload_php', 'ied_plugin', 'site', 'Upload plugin', '2019-07-25 01:42:31'),
(2331, 'el', 'ied_plugin_utils_legend', 'ied_plugin', 'site', 'Distribution', '2019-07-25 01:42:31'),
(2332, 'el', 'ied_plugin_view_help', 'ied_plugin', 'site', 'Help: {name}', '2019-07-25 01:42:31'),
(2333, 'en-us', 'active', 'common', '', 'Active', '2019-07-23 22:33:42'),
(2334, 'en-us', 'add', 'common', '', 'Add', '2019-07-23 22:33:42'),
(2335, 'en-us', 'admin', 'common', '', 'Admin', '2019-07-23 22:33:42'),
(2336, 'en-us', 'ago', 'common', '', '{since} ago', '2019-07-23 22:33:42'),
(2337, 'en-us', 'all', 'common', '', 'All', '2019-07-23 22:33:42'),
(2338, 'en-us', 'are_you_sure', 'common', '', 'Are you sure?', '2019-07-23 22:33:42'),
(2339, 'en-us', 'article', 'common', '', 'Article', '2019-07-23 22:33:42'),
(2340, 'en-us', 'articles', 'common', '', 'Articles', '2019-07-23 22:33:42'),
(2341, 'en-us', 'article_image', 'common', '', 'Article image', '2019-07-23 22:33:42'),
(2342, 'en-us', 'any', 'common', '', 'Any', '2019-07-23 22:33:42'),
(2343, 'en-us', 'ascending', 'common', '', 'Ascending', '2019-07-23 22:33:42'),
(2344, 'en-us', 'auth_required', 'common', '', 'Authorization required.', '2019-07-23 22:33:42'),
(2345, 'en-us', 'author', 'common', '', 'Author', '2019-07-23 22:33:42'),
(2346, 'en-us', 'authors', 'common', '', 'Authors', '2019-07-23 22:33:42'),
(2347, 'en-us', 'back_to_login', 'common', '', 'Back to Textpattern login', '2019-07-23 22:33:42'),
(2348, 'en-us', 'back_to_top', 'common', '', 'Back to top', '2019-07-23 22:33:42'),
(2349, 'en-us', 'bad_cookie', 'common', '', 'Your session has expired. Please log in again.', '2019-07-23 22:33:42'),
(2350, 'en-us', 'bad_login', 'common', '', 'Could not log in with that username/password.', '2019-07-23 22:33:42'),
(2351, 'en-us', 'browse', 'common', '', 'Browse', '2019-07-23 22:33:42'),
(2352, 'en-us', 'cancel', 'common', '', 'Cancel', '2019-07-23 22:33:42'),
(2353, 'en-us', 'caption', 'common', '', 'Caption', '2019-07-23 22:33:42'),
(2354, 'en-us', 'category', 'common', '', 'Category', '2019-07-23 22:33:42'),
(2355, 'en-us', 'categories', 'common', '', 'Categories', '2019-07-23 22:33:42'),
(2356, 'en-us', 'categorize', 'common', '', 'Categorize', '2019-07-23 22:33:42'),
(2357, 'en-us', 'category1', 'common', '', 'Category 1', '2019-07-23 22:33:42'),
(2358, 'en-us', 'category2', 'common', '', 'Category 2', '2019-07-23 22:33:42'),
(2359, 'en-us', 'choose', 'common', '', 'Choose…', '2019-07-23 22:33:42'),
(2360, 'en-us', 'close', 'common', '', 'Close', '2019-07-23 22:33:42'),
(2361, 'en-us', 'code', 'common', '', 'Code', '2019-07-23 22:33:42'),
(2362, 'en-us', 'comment', 'common', '', 'Comment', '2019-07-23 22:33:42'),
(2363, 'en-us', 'comment_invitation', 'common', '', 'Invitation', '2019-07-23 22:33:42'),
(2364, 'en-us', 'comments', 'common', '', 'Comments', '2019-07-23 22:33:42'),
(2365, 'en-us', 'comments_expired', 'common', '', 'Commenting has expired for this article.', '2019-07-23 22:33:42'),
(2366, 'en-us', 'confirm_delete_popup', 'common', '', 'Really delete?', '2019-07-23 22:33:42'),
(2367, 'en-us', 'contact', 'common', '', 'Contact', '2019-07-23 22:33:42'),
(2368, 'en-us', 'cookies_must_be_enabled', 'common', '', 'Browser cookies must be enabled to use Textpattern.', '2019-07-23 22:33:42'),
(2369, 'en-us', 'copy', 'common', '', 'Copy', '2019-07-23 22:33:42'),
(2370, 'en-us', 'could_not_log_in', 'common', '', 'Could not log in with that username/password.', '2019-07-23 22:33:42'),
(2371, 'en-us', 'create', 'common', '', 'Create', '2019-07-23 22:33:42'),
(2372, 'en-us', 'css', 'common', '', 'Style', '2019-07-23 22:33:42'),
(2373, 'en-us', 'date', 'common', '', 'Date', '2019-07-23 22:33:42'),
(2374, 'en-us', 'date_added', 'common', '', 'Date added', '2019-07-23 22:33:42'),
(2375, 'en-us', 'dateformat', 'common', '', 'Date format', '2019-07-23 22:33:42'),
(2376, 'en-us', 'default', 'common', '', 'Default', '2019-07-23 22:33:42'),
(2377, 'en-us', 'delete', 'common', '', 'Delete', '2019-07-23 22:33:42'),
(2378, 'en-us', 'delete_selected', 'common', '', 'Delete selected', '2019-07-23 22:33:42'),
(2379, 'en-us', 'descending', 'common', '', 'Descending', '2019-07-23 22:33:42'),
(2380, 'en-us', 'description', 'common', '', 'Description', '2019-07-23 22:33:42'),
(2381, 'en-us', 'documentation', 'common', '', 'Documentation', '2019-07-23 22:33:42'),
(2382, 'en-us', 'download', 'common', '', 'Download', '2019-07-23 22:33:42'),
(2383, 'en-us', 'download_count', 'common', '', 'Download count', '2019-07-23 22:33:42'),
(2384, 'en-us', 'downloads', 'common', '', 'Downloads', '2019-07-23 22:33:42'),
(2385, 'en-us', 'duplicate', 'common', '', 'Duplicate', '2019-07-23 22:33:42'),
(2386, 'en-us', 'edit', 'common', '', 'Edit', '2019-07-23 22:33:42'),
(2387, 'en-us', 'email', 'common', '', 'Email', '2019-07-23 22:33:42'),
(2388, 'en-us', 'email_address', 'common', '', 'Email address', '2019-07-23 22:33:42'),
(2389, 'en-us', 'exact', 'common', '', 'Exact', '2019-07-23 22:33:42'),
(2390, 'en-us', 'excerpt', 'common', '', 'Excerpt', '2019-07-23 22:33:42'),
(2391, 'en-us', 'expired', 'common', '', 'Expired', '2019-07-23 22:33:42'),
(2392, 'en-us', 'expires', 'common', '', 'Expires', '2019-07-23 22:33:42'),
(2393, 'en-us', 'expire_date', 'common', '', 'Expiration date', '2019-07-23 22:33:42'),
(2394, 'en-us', 'expire_time', 'common', '', 'Expiration time', '2019-07-23 22:33:42'),
(2395, 'en-us', 'export', 'common', '', 'Export', '2019-07-23 22:33:42'),
(2396, 'en-us', 'extension', 'common', '', 'Extension', '2019-07-23 22:33:42'),
(2397, 'en-us', 'extensions', 'common', '', 'Extensions', '2019-07-23 22:33:42'),
(2398, 'en-us', 'file', 'common', '', 'File', '2019-07-23 22:33:42'),
(2399, 'en-us', 'file_download', 'common', '', 'File download', '2019-07-23 22:33:42'),
(2400, 'en-us', 'file_size', 'common', '', 'File size', '2019-07-23 22:33:42'),
(2401, 'en-us', 'forget', 'common', '', 'Forget', '2019-07-23 22:33:42'),
(2402, 'en-us', 'form', 'common', '', 'Form', '2019-07-23 22:33:42'),
(2403, 'en-us', 'form_submission_error', 'common', '', 'Sorry, the form could not be submitted. Please try again later.', '2019-07-23 22:33:42'),
(2404, 'en-us', 'forms', 'common', '', 'Forms', '2019-07-23 22:33:42'),
(2405, 'en-us', 'general_error', 'common', '', 'General error', '2019-07-23 22:33:42'),
(2406, 'en-us', 'go', 'common', '', 'Go', '2019-07-23 22:33:42'),
(2407, 'en-us', 'go_back', 'common', '', 'Go back', '2019-07-23 22:33:42'),
(2408, 'en-us', 'go_content', 'common', '', 'Go to content', '2019-07-23 22:33:42'),
(2409, 'en-us', 'go_nav', 'common', '', 'Go to navigation', '2019-07-23 22:33:42'),
(2410, 'en-us', 'go_search', 'common', '', 'Go to search', '2019-07-23 22:33:42'),
(2411, 'en-us', 'go_to', 'common', '', 'Go to', '2019-07-23 22:33:42'),
(2412, 'en-us', 'go_txp_com', 'common', '', 'Go to the Textpattern website', '2019-07-23 22:33:42'),
(2413, 'en-us', 'height', 'common', '', 'Height', '2019-07-23 22:33:42'),
(2414, 'en-us', 'help', 'common', '', 'Help', '2019-07-23 22:33:42'),
(2415, 'en-us', 'hidden', 'common', '', 'Hidden', '2019-07-23 22:33:42'),
(2416, 'en-us', 'home', 'common', '', 'Home', '2019-07-23 22:33:42'),
(2417, 'en-us', 'host', 'common', '', 'Host', '2019-07-23 22:33:42'),
(2418, 'en-us', 'id', 'common', '', 'ID#', '2019-07-23 22:33:42'),
(2419, 'en-us', 'image', 'common', '', 'Image', '2019-07-23 22:33:42'),
(2420, 'en-us', 'images', 'common', '', 'Images', '2019-07-23 22:33:42'),
(2421, 'en-us', 'import', 'common', '', 'Import', '2019-07-23 22:33:42'),
(2422, 'en-us', 'input_day', 'common', '', 'Day', '2019-07-23 22:33:42'),
(2423, 'en-us', 'input_hour', 'common', '', 'Hour', '2019-07-23 22:33:42'),
(2424, 'en-us', 'input_minute', 'common', '', 'Minute', '2019-07-23 22:33:42'),
(2425, 'en-us', 'input_month', 'common', '', 'Month', '2019-07-23 22:33:42'),
(2426, 'en-us', 'input_second', 'common', '', 'Second', '2019-07-23 22:33:42'),
(2427, 'en-us', 'input_year', 'common', '', 'Year', '2019-07-23 22:33:42'),
(2428, 'en-us', 'install', 'common', '', 'Install', '2019-07-23 22:33:42'),
(2429, 'en-us', 'internal_error', 'common', '', 'Internal error', '2019-07-23 22:33:42'),
(2430, 'en-us', 'keywords', 'common', '', 'Keywords', '2019-07-23 22:33:42'),
(2431, 'en-us', 'label', 'common', '', 'Label', '2019-07-23 22:33:42'),
(2432, 'en-us', 'last_modification', 'common', '', 'Last modification', '2019-07-23 22:33:42'),
(2433, 'en-us', 'last_modified', 'common', '', 'Last modified', '2019-07-23 22:33:42'),
(2434, 'en-us', 'link', 'common', '', 'Link', '2019-07-23 22:33:42'),
(2435, 'en-us', 'links', 'common', '', 'Links', '2019-07-23 22:33:42'),
(2436, 'en-us', 'list', 'common', '', 'List', '2019-07-23 22:33:42'),
(2437, 'en-us', 'list_articles', 'common', '', 'List articles', '2019-07-23 22:33:42'),
(2438, 'en-us', 'list_categories', 'common', '', 'List categories', '2019-07-23 22:33:42'),
(2439, 'en-us', 'list_links', 'common', '', 'List links', '2019-07-23 22:33:42'),
(2440, 'en-us', 'list_options', 'common', '', 'List options', '2019-07-23 22:33:42'),
(2441, 'en-us', 'logged_in_as', 'common', '', 'Logged in as', '2019-07-23 22:33:42'),
(2442, 'en-us', 'login', 'common', '', 'Log in', '2019-07-23 22:33:42'),
(2443, 'en-us', 'login_to_textpattern', 'common', '', 'Log in to Textpattern', '2019-07-23 22:33:42'),
(2444, 'en-us', 'logout', 'common', '', 'Log out', '2019-07-23 22:33:42'),
(2445, 'en-us', 'log_in_button', 'common', '', 'Log in', '2019-07-23 22:33:42'),
(2446, 'en-us', 'manual', 'common', '', 'Manual', '2019-07-23 22:33:42'),
(2447, 'en-us', 'message', 'common', '', 'Message', '2019-07-23 22:33:42'),
(2448, 'en-us', 'method', 'common', '', 'Method', '2019-07-23 22:33:42'),
(2449, 'en-us', 'misc', 'common', '', 'Miscellaneous', '2019-07-23 22:33:42'),
(2450, 'en-us', 'modified', 'common', '', 'Modified', '2019-07-23 22:33:42'),
(2451, 'en-us', 'modified_by', 'common', '', 'Last modified by', '2019-07-23 22:33:42'),
(2452, 'en-us', 'month', 'common', '', 'Month', '2019-07-23 22:33:42'),
(2453, 'en-us', 'more', 'common', '', 'More', '2019-07-23 22:33:42'),
(2454, 'en-us', 'navigation', 'common', '', 'Navigation', '2019-07-23 22:33:42'),
(2455, 'en-us', 'name', 'common', '', 'Name', '2019-07-23 22:33:42'),
(2456, 'en-us', 'never', 'common', '', 'Never', '2019-07-23 22:33:42'),
(2457, 'en-us', 'newer', 'common', '', 'Newer', '2019-07-23 22:33:42'),
(2458, 'en-us', 'next', 'common', '', 'Next', '2019-07-23 22:33:42'),
(2459, 'en-us', 'no', 'common', '', 'No', '2019-07-23 22:33:42'),
(2460, 'en-us', 'no_results_found', 'common', '', 'No results found.', '2019-07-23 22:33:42'),
(2461, 'en-us', 'none', 'common', '', 'None', '2019-07-23 22:33:42'),
(2462, 'en-us', 'of', 'common', '', 'of', '2019-07-23 22:33:42'),
(2463, 'en-us', 'off', 'common', '', 'Off', '2019-07-23 22:33:42'),
(2464, 'en-us', 'older', 'common', '', 'Older', '2019-07-23 22:33:42'),
(2465, 'en-us', 'on', 'common', '', 'On', '2019-07-23 22:33:42'),
(2466, 'en-us', 'opens_external_link', 'common', '', '(opens an external link in a new window)', '2019-07-23 22:33:42'),
(2467, 'en-us', 'options', 'common', '', 'Options', '2019-07-23 22:33:42'),
(2468, 'en-us', 'page', 'common', '', 'Page', '2019-07-23 22:33:42'),
(2469, 'en-us', 'pages', 'common', '', 'Pages', '2019-07-23 22:33:42'),
(2470, 'en-us', 'password', 'common', '', 'Password', '2019-07-23 22:33:42'),
(2471, 'en-us', 'password_forgotten', 'common', '', 'Forgot password?', '2019-07-23 22:33:42'),
(2472, 'en-us', 'permlink', 'common', '', 'Permanent link', '2019-07-23 22:33:42'),
(2473, 'en-us', 'pophelp', 'common', '', 'Pop-up help links', '2019-07-23 22:33:42'),
(2474, 'en-us', 'post', 'common', '', 'Post', '2019-07-23 22:33:42'),
(2475, 'en-us', 'posted', 'common', '', 'Posted', '2019-07-23 22:33:42'),
(2476, 'en-us', 'posted_by', 'common', '', 'Posted by', '2019-07-23 22:33:42'),
(2477, 'en-us', 'prev', 'common', '', 'Previous', '2019-07-23 22:33:42'),
(2478, 'en-us', 'preview', 'common', '', 'Preview', '2019-07-23 22:33:42'),
(2479, 'en-us', 'publish', 'common', '', 'Publish', '2019-07-23 22:33:42'),
(2480, 'en-us', 'published_at', 'common', '', 'Published at', '2019-07-23 22:33:42'),
(2481, 'en-us', 'published_with', 'common', '', 'Published with', '2019-07-23 22:33:42'),
(2482, 'en-us', 'publish_date', 'common', '', 'Publish date', '2019-07-23 22:33:42'),
(2483, 'en-us', 'publish_time', 'common', '', 'Publish time', '2019-07-23 22:33:42'),
(2484, 'en-us', 'range', 'common', '', 'Range', '2019-07-23 22:33:42'),
(2485, 'en-us', 'recently', 'common', '', 'Recently', '2019-07-23 22:33:42'),
(2486, 'en-us', 'recent_articles', 'common', '', 'Recent articles', '2019-07-23 22:33:42'),
(2487, 'en-us', 'recent_comments', 'common', '', 'Recent comments', '2019-07-23 22:33:42'),
(2488, 'en-us', 'recent_posts', 'common', '', 'Recent posts', '2019-07-23 22:33:42'),
(2489, 'en-us', 'related_articles', 'common', '', 'Related articles', '2019-07-23 22:33:42'),
(2490, 'en-us', 'reload', 'common', '', 'Reload', '2019-07-23 22:33:42'),
(2491, 'en-us', 'remember', 'common', '', 'Remember', '2019-07-23 22:33:42'),
(2492, 'en-us', 'remove', 'common', '', 'Remove', '2019-07-23 22:33:42'),
(2493, 'en-us', 'required', 'common', '', 'Required', '2019-07-23 22:33:42'),
(2494, 'en-us', 'reset', 'common', '', 'Reset', '2019-07-23 22:33:42'),
(2495, 'en-us', 'restricted_area', 'common', '', 'Restricted area.', '2019-07-23 22:33:42'),
(2496, 'en-us', 'revert', 'common', '', 'Revert', '2019-07-23 22:33:42'),
(2497, 'en-us', 'salutation', 'common', '', 'Dear {name},', '2019-07-23 22:33:42'),
(2498, 'en-us', 'save', 'common', '', 'Save', '2019-07-23 22:33:42'),
(2499, 'en-us', 'save_new', 'common', '', 'Save new', '2019-07-23 22:33:42'),
(2500, 'en-us', 'search', 'common', '', 'Search', '2019-07-23 22:33:42'),
(2501, 'en-us', 'search_results', 'common', '', 'Search results', '2019-07-23 22:33:42'),
(2502, 'en-us', 'section', 'common', '', 'Section', '2019-07-23 22:33:42'),
(2503, 'en-us', 'sections', 'common', '', 'Sections', '2019-07-23 22:33:42'),
(2504, 'en-us', 'select', 'common', '', 'Select', '2019-07-23 22:33:42'),
(2505, 'en-us', 'selected', 'common', '', 'Selected', '2019-07-23 22:33:42'),
(2506, 'en-us', 'site', 'common', '', 'Site', '2019-07-23 22:33:42'),
(2507, 'en-us', 'sitename', 'common', '', 'Site name', '2019-07-23 22:33:42'),
(2508, 'en-us', 'siteurl', 'common', '', 'Site URL', '2019-07-23 22:33:42'),
(2509, 'en-us', 'site_slogan', 'common', '', 'Site slogan', '2019-07-23 22:33:42'),
(2510, 'en-us', 'spam', 'common', '', 'Spam', '2019-07-23 22:33:42'),
(2511, 'en-us', 'status', 'common', '', 'Status', '2019-07-23 22:33:42'),
(2512, 'en-us', 'stay_logged_in', 'common', '', 'Remain logged in with this browser', '2019-07-23 22:33:42'),
(2513, 'en-us', 'submit', 'common', '', 'Submit', '2019-07-23 22:33:42'),
(2514, 'en-us', 'thumbnail', 'common', '', 'Thumbnail', '2019-07-23 22:33:42'),
(2515, 'en-us', 'time', 'common', '', 'Time', '2019-07-23 22:33:42'),
(2516, 'en-us', 'title', 'common', '', 'Title', '2019-07-23 22:33:42'),
(2517, 'en-us', 'toggle_all_selected', 'common', '', 'Toggle all/none selected', '2019-07-23 22:33:42'),
(2518, 'en-us', 'txt_quote_double_close', 'common', '', '”', '2019-07-23 22:33:42'),
(2519, 'en-us', 'txt_quote_double_open', 'common', '', '“', '2019-07-23 22:33:42'),
(2520, 'en-us', 'txt_quote_single_close', 'common', '', '’', '2019-07-23 22:33:42'),
(2521, 'en-us', 'txt_quote_single_open', 'common', '', '‘', '2019-07-23 22:33:42'),
(2522, 'en-us', 'type', 'common', '', 'Type', '2019-07-23 22:33:42'),
(2523, 'en-us', 'undefined', 'common', '', 'Undefined', '2019-07-23 22:33:42'),
(2524, 'en-us', 'unknown', 'common', '', 'Unknown', '2019-07-23 22:33:42'),
(2525, 'en-us', 'units_b', 'common', '', 'B', '2019-07-23 22:33:42'),
(2526, 'en-us', 'units_e', 'common', '', 'EB', '2019-07-23 22:33:42'),
(2527, 'en-us', 'units_g', 'common', '', 'GB', '2019-07-23 22:33:42'),
(2528, 'en-us', 'units_k', 'common', '', 'kB', '2019-07-23 22:33:42'),
(2529, 'en-us', 'units_m', 'common', '', 'MB', '2019-07-23 22:33:42'),
(2530, 'en-us', 'units_p', 'common', '', 'PB', '2019-07-23 22:33:42'),
(2531, 'en-us', 'units_t', 'common', '', 'TB', '2019-07-23 22:33:42'),
(2532, 'en-us', 'units_y', 'common', '', 'YB', '2019-07-23 22:33:42'),
(2533, 'en-us', 'units_z', 'common', '', 'ZB', '2019-07-23 22:33:42'),
(2534, 'en-us', 'untitled', 'common', '', 'Untitled', '2019-07-23 22:33:42'),
(2535, 'en-us', 'update', 'common', '', 'Update', '2019-07-23 22:33:42'),
(2536, 'en-us', 'upload', 'common', '', 'Upload', '2019-07-23 22:33:42'),
(2537, 'en-us', 'upload_err_cant_write', 'common', '', 'Failed to write file to disk.', '2019-07-23 22:33:42'),
(2538, 'en-us', 'upload_err_extension', 'common', '', 'File upload stopped by PHP extension.', '2019-07-23 22:33:42'),
(2539, 'en-us', 'upload_err_form_size', 'common', '', 'File exceeds the maximum size specified in Textpattern’s preferences.', '2019-07-23 22:33:42'),
(2540, 'en-us', 'upload_err_ini_size', 'common', '', 'File exceeds the <code>upload_max_filesize</code> directive in <code>php.ini</code>.', '2019-07-23 22:33:42'),
(2541, 'en-us', 'upload_err_no_file', 'common', '', 'No file was specified.', '2019-07-23 22:33:42'),
(2542, 'en-us', 'upload_err_partial', 'common', '', 'File was only partially uploaded.', '2019-07-23 22:33:42'),
(2543, 'en-us', 'upload_err_tmp_dir', 'common', '', 'No valid temporary directory was found. Please consult your website host.', '2019-07-23 22:33:42'),
(2544, 'en-us', 'upload_file', 'common', '', 'Upload file', '2019-07-23 22:33:42'),
(2545, 'en-us', 'url', 'common', '', 'URL', '2019-07-23 22:33:42'),
(2546, 'en-us', 'value', 'common', '', 'Value', '2019-07-23 22:33:42'),
(2547, 'en-us', 'version', 'common', '', 'Version', '2019-07-23 22:33:42'),
(2548, 'en-us', 'view', 'common', '', 'View', '2019-07-23 22:33:42'),
(2549, 'en-us', 'viewsite', 'common', '', 'View site', '2019-07-23 22:33:42'),
(2550, 'en-us', 'website', 'common', '', 'Website', '2019-07-23 22:33:42'),
(2551, 'en-us', 'width', 'common', '', 'Width', '2019-07-23 22:33:42'),
(2552, 'en-us', 'with_selected', 'common', '', 'With selected:', '2019-07-23 22:33:42'),
(2553, 'en-us', 'with_selected_option', 'common', '', 'With {count} selected…', '2019-07-23 22:33:42'),
(2554, 'en-us', 'yes', 'common', '', 'Yes', '2019-07-23 22:33:42'),
(2555, 'en-us', 'lang_name', 'common', '', 'English (American)', '2019-07-23 22:33:42'),
(2556, 'en-us', 'lang_code', 'common', '', 'en-US', '2019-07-23 22:33:42'),
(2557, 'en-us', 'lang_dir', 'common', '', 'ltr', '2019-07-23 22:33:42'),
(2558, 'en-us', 'account_activation', 'admin', '', 'Please activate your account', '2019-07-23 22:33:42'),
(2559, 'en-us', 'account_activation_confirmation', 'admin', '', 'Please use the link below to activate your account and set up a password.', '2019-07-23 22:33:42'),
(2560, 'en-us', 'alias_is_taken', 'admin', '', 'Alias already exists.', '2019-07-23 22:33:42'),
(2561, 'en-us', 'assign_assets_to', 'admin', '', 'Assign author’s content to', '2019-07-23 22:33:42'),
(2562, 'en-us', 'author_already_exists', 'admin', '', 'Author <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2563, 'en-us', 'author_deleted', 'admin', '', 'Authors deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2564, 'en-us', 'author_save_failed', 'admin', '', 'Author <strong>{name}</strong> could not be saved.', '2019-07-23 22:33:42'),
(2565, 'en-us', 'author_updated', 'admin', '', 'Authors updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2566, 'en-us', 'cannot_assign_assets_to_deletee', 'admin', '', 'You cannot assign assets to a deleted account.', '2019-07-23 22:33:42'),
(2567, 'en-us', 'changeprivilege', 'admin', '', 'Change role', '2019-07-23 22:33:42'),
(2568, 'en-us', 'change_email_address', 'admin', '', 'Change your email address', '2019-07-23 22:33:42'),
(2569, 'en-us', 'change_password', 'admin', '', 'Change your password', '2019-07-23 22:33:42'),
(2570, 'en-us', 'copy_editor', 'admin', '', 'Copy Editor', '2019-07-23 22:33:42'),
(2571, 'en-us', 'could_not_mail', 'admin', '', 'Could not email', '2019-07-23 22:33:42'),
(2572, 'en-us', 'could_not_update_author', 'admin', '', 'Could not update author.', '2019-07-23 22:33:42'),
(2573, 'en-us', 'create_author', 'admin', '', 'New author', '2019-07-23 22:33:42'),
(2574, 'en-us', 'current_password', 'admin', '', 'Current password', '2019-07-23 22:33:42'),
(2575, 'en-us', 'designer', 'admin', '', 'Designer', '2019-07-23 22:33:42'),
(2576, 'en-us', 'edit_author', 'admin', '', 'Edit author', '2019-07-23 22:33:42'),
(2577, 'en-us', 'email_changed', 'admin', '', 'Email address changed to <strong>{email}</strong>.', '2019-07-23 22:33:42'),
(2578, 'en-us', 'error_adding_new_author', 'admin', '', 'Error adding new author.', '2019-07-23 22:33:42'),
(2579, 'en-us', 'from_or_to_address_missing', 'admin', '', 'Sender and/or recipient address is missing.', '2019-07-23 22:33:42'),
(2580, 'en-us', 'freelancer', 'admin', '', 'Freelancer', '2019-07-23 22:33:42'),
(2581, 'en-us', 'invalid_header', 'admin', '', 'Invalid mail header.', '2019-07-23 22:33:42'),
(2582, 'en-us', 'invalid_token', 'admin', '', 'Password reset security token is invalid.', '2019-07-23 22:33:42'),
(2583, 'en-us', 'last_login', 'admin', '', 'Last log in', '2019-07-23 22:33:42'),
(2584, 'en-us', 'link_expires', 'admin', '', 'You have until {month} {day} {year}, {time} to respond before this link expires.', '2019-07-23 22:33:42'),
(2585, 'en-us', 'login_name', 'admin', '', 'Login', '2019-07-23 22:33:42'),
(2586, 'en-us', 'login_sent_to', 'admin', '', 'Password sent to {email}.', '2019-07-23 22:33:42'),
(2587, 'en-us', 'log_in_at', 'admin', '', 'Log in at', '2019-07-23 22:33:42'),
(2588, 'en-us', 'managing_editor', 'admin', '', 'Managing Editor', '2019-07-23 22:33:42'),
(2589, 'en-us', 'must_reassign_assets', 'admin', '', 'Please reassign author’s content', '2019-07-23 22:33:42'),
(2590, 'en-us', 'new_email', 'admin', '', 'New email', '2019-07-23 22:33:42'),
(2591, 'en-us', 'new_password', 'admin', '', 'New password', '2019-07-23 22:33:42'),
(2592, 'en-us', 'password_changed', 'admin', '', 'Password changed.', '2019-07-23 22:33:42');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(2593, 'en-us', 'password_change_confirmation', 'admin', '', 'Your password has been changed. If you did not request a change to your password, contact your site administrator.', '2019-07-23 22:33:42'),
(2594, 'en-us', 'password_confirm_button', 'admin', '', 'Set password', '2019-07-23 22:33:42'),
(2595, 'en-us', 'password_invalid', 'admin', '', 'Your password is incorrect.', '2019-07-23 22:33:42'),
(2596, 'en-us', 'password_required', 'admin', '', 'Please provide a new password.', '2019-07-23 22:33:42'),
(2597, 'en-us', 'password_reset', 'admin', '', 'Reset password', '2019-07-23 22:33:42'),
(2598, 'en-us', 'password_reset_button', 'admin', '', 'Reset password', '2019-07-23 22:33:42'),
(2599, 'en-us', 'password_reset_confirmation', 'admin', '', 'Somebody (probably you) has requested to reset your password. Please confirm this reset request using the link below.', '2019-07-23 22:33:42'),
(2600, 'en-us', 'password_reset_confirmation_request', 'admin', '', 'Please confirm your password reset request', '2019-07-23 22:33:42'),
(2601, 'en-us', 'password_reset_confirmation_request_sent', 'admin', '', 'A confirmation message was sent. Please check your email and follow the instructions.', '2019-07-23 22:33:42'),
(2602, 'en-us', 'password_sent_to', 'admin', '', 'Password sent to', '2019-07-23 22:33:42'),
(2603, 'en-us', 'password_set', 'admin', '', 'Your password has been set', '2019-07-23 22:33:42'),
(2604, 'en-us', 'password_set_confirmation', 'admin', '', 'Your password has been set. If you did not request this, contact your site administrator.', '2019-07-23 22:33:42'),
(2605, 'en-us', 'privileges', 'admin', '', 'Role', '2019-07-23 22:33:42'),
(2606, 'en-us', 'privs_none', 'admin', '', 'None', '2019-07-23 22:33:42'),
(2607, 'en-us', 'publisher', 'admin', '', 'Publisher', '2019-07-23 22:33:42'),
(2608, 'en-us', 'real_name', 'admin', '', 'Real name', '2019-07-23 22:33:42'),
(2609, 'en-us', 'resend_activation', 'admin', '', 'Resend activation link', '2019-07-23 22:33:42'),
(2610, 'en-us', 'resend_activation_request_sent', 'admin', '', 'Activation message sent to {name}.', '2019-07-23 22:33:42'),
(2611, 'en-us', 'resetpassword', 'admin', '', 'Reset password', '2019-07-23 22:33:42'),
(2612, 'en-us', 'search_users', 'admin', '', 'Search users', '2019-07-23 22:33:42'),
(2613, 'en-us', 'sending_failed', 'admin', '', 'The message failed to send.', '2019-07-23 22:33:42'),
(2614, 'en-us', 'set_password', 'admin', '', 'Set your password', '2019-07-23 22:33:42'),
(2615, 'en-us', 'show_password', 'admin', '', 'Show password', '2019-07-23 22:33:42'),
(2616, 'en-us', 'staff_writer', 'admin', '', 'Staff Writer', '2019-07-23 22:33:42'),
(2617, 'en-us', 'token_expired', 'admin', '', 'Password reset security token has expired.', '2019-07-23 22:33:42'),
(2618, 'en-us', 'unable_retrieve_template', 'admin', '', 'Unable to retrieve template.', '2019-07-23 22:33:42'),
(2619, 'en-us', 'unable_retrieve_user', 'admin', '', 'Unable to retrieve user.', '2019-07-23 22:33:42'),
(2620, 'en-us', 'unable_set_template', 'admin', '', 'Unable to set template.', '2019-07-23 22:33:42'),
(2621, 'en-us', 'your_login_info', 'admin', '', 'Your login info', '2019-07-23 22:33:42'),
(2622, 'en-us', 'your_login_is', 'admin', '', 'Your login is', '2019-07-23 22:33:42'),
(2623, 'en-us', 'your_new_password', 'admin', '', 'Your new password', '2019-07-23 22:33:42'),
(2624, 'en-us', 'your_password_is', 'admin', '', 'Your password is', '2019-07-23 22:33:42'),
(2625, 'en-us', 'you_have_been_registered', 'admin', '', 'You have been registered as a contributor to the site', '2019-07-23 22:33:42'),
(2626, 'en-us', 'advanced_options', 'admin-side', '', 'Advanced options', '2019-07-23 22:33:42'),
(2627, 'en-us', 'auto_dst', 'admin-side', '', 'Automatically adjust Daylight Saving Time?', '2019-07-23 22:33:42'),
(2628, 'en-us', 'body', 'admin-side', '', 'Body', '2019-07-23 22:33:42'),
(2629, 'en-us', 'cannot_delete', 'admin-side', '', 'Cannot delete {thing}.', '2019-07-23 22:33:42'),
(2630, 'en-us', 'cannot_instantiate_theme', 'admin-side', '', 'Theme <strong>{name}</strong> ({class}) failed to load properly from file {path}. The default theme will be used as a fallback when you refresh this page.', '2019-07-23 22:33:42'),
(2631, 'en-us', 'changeauthor', 'admin-side', '', 'Change author', '2019-07-23 22:33:42'),
(2632, 'en-us', 'changecategory', 'admin-side', '', 'Change category', '2019-07-23 22:33:42'),
(2633, 'en-us', 'changecategory1', 'admin-side', '', 'Change Category 1', '2019-07-23 22:33:42'),
(2634, 'en-us', 'changecategory2', 'admin-side', '', 'Change Category 2', '2019-07-23 22:33:42'),
(2635, 'en-us', 'changecomments', 'admin-side', '', 'Change comments', '2019-07-23 22:33:42'),
(2636, 'en-us', 'changeparent', 'admin-side', '', 'Change parent', '2019-07-23 22:33:42'),
(2637, 'en-us', 'changesection', 'admin-side', '', 'Change section', '2019-07-23 22:33:42'),
(2638, 'en-us', 'changestatus', 'admin-side', '', 'Change status', '2019-07-23 22:33:42'),
(2639, 'en-us', 'collapse_all', 'admin-side', '', 'Collapse all', '2019-07-23 22:33:42'),
(2640, 'en-us', 'convert_linebreaks', 'admin-side', '', 'Convert line breaks', '2019-07-23 22:33:42'),
(2641, 'en-us', 'create_article', 'admin-side', '', 'New article', '2019-07-23 22:33:42'),
(2642, 'en-us', 'custom', 'admin-side', '', 'Custom fields', '2019-07-23 22:33:42'),
(2643, 'en-us', 'date_settings', 'admin-side', '', 'Date and time', '2019-07-23 22:33:42'),
(2644, 'en-us', 'deleteforce', 'admin-side', '', 'Force delete', '2019-07-23 22:33:42'),
(2645, 'en-us', 'detail', 'admin-side', '', 'Detail', '2019-07-23 22:33:42'),
(2646, 'en-us', 'directory_permissions', 'admin-side', '', 'Check directory permissions: <strong>{path}</strong>.', '2019-07-23 22:33:42'),
(2647, 'en-us', 'draft', 'admin-side', '', 'Draft', '2019-07-23 22:33:42'),
(2648, 'en-us', 'expand_all', 'admin-side', '', 'Expand all', '2019-07-23 22:33:42'),
(2649, 'en-us', 'export_to_disk', 'admin-side', '', 'Export to disk', '2019-07-23 22:33:42'),
(2650, 'en-us', 'file_base_path', 'admin-side', '', 'File directory path', '2019-07-23 22:33:42'),
(2651, 'en-us', 'get_off_my_lawn', 'admin-side', '', 'I’m sorry. I’m afraid I can’t do that; <code>{event}</code> <code>{step}</code> is an unsafe operation.', '2019-07-23 22:33:42'),
(2652, 'en-us', 'gmtoffset', 'admin-side', '', 'Time zone (GMT offset in seconds)', '2019-07-23 22:33:42'),
(2653, 'en-us', 'img_dir', 'admin-side', '', 'Image directory', '2019-07-23 22:33:42'),
(2654, 'en-us', 'import_from_disk', 'admin-side', '', 'Import from disk', '2019-07-23 22:33:42'),
(2655, 'en-us', 'invalid_argument', 'admin-side', '', 'Invalid argument', '2019-07-23 22:33:42'),
(2656, 'en-us', 'invalid_article_id', 'admin-side', '', 'Invalid article ID', '2019-07-23 22:33:42'),
(2657, 'en-us', 'invalid_expiredate', 'admin-side', '', 'Invalid expiration date or time.', '2019-07-23 22:33:42'),
(2658, 'en-us', 'invalid_json', 'admin-side', '', 'Invalid JSON files: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2659, 'en-us', 'invalid_postdate', 'admin-side', '', 'Invalid publish date or time.', '2019-07-23 22:33:42'),
(2660, 'en-us', 'is_dst', 'admin-side', '', 'Daylight Saving Time enabled?', '2019-07-23 22:33:42'),
(2661, 'en-us', 'leave_text_untouched', 'admin-side', '', 'Leave text untouched', '2019-07-23 22:33:42'),
(2662, 'en-us', 'live', 'admin-side', '', 'Live', '2019-07-23 22:33:42'),
(2663, 'en-us', 'locale', 'admin-side', '', 'Locale', '2019-07-23 22:33:42'),
(2664, 'en-us', 'manage', 'admin-side', '', 'Manage', '2019-07-23 22:33:42'),
(2665, 'en-us', 'meta', 'admin-side', '', 'Meta', '2019-07-23 22:33:42'),
(2666, 'en-us', 'on_front_page', 'admin-side', '', 'On default page?', '2019-07-23 22:33:42'),
(2667, 'en-us', 'override', 'admin-side', '', 'Override', '2019-07-23 22:33:42'),
(2668, 'en-us', 'parent', 'admin-side', '', 'Parent', '2019-07-23 22:33:42'),
(2669, 'en-us', 'path_creation_or_writing_failed', 'admin-side', '', 'Unable to create or write in files/directories: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2670, 'en-us', 'path_not_readable', 'admin-side', '', '<strong>Warning:</strong> cannot read from directories/files <code>{list}</code>.', '2019-07-23 22:33:42'),
(2671, 'en-us', 'path_not_writable', 'admin-side', '', '<strong>Warning:</strong> cannot write to directories/files <code>{list}</code>.', '2019-07-23 22:33:42'),
(2672, 'en-us', 'path_renaming_failed', 'admin-side', '', 'Files or directories renaming failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2673, 'en-us', 'pending', 'admin-side', '', 'Pending', '2019-07-23 22:33:42'),
(2674, 'en-us', 'permlink_mode', 'admin-side', '', 'Article URL pattern', '2019-07-23 22:33:42'),
(2675, 'en-us', 'preferences_saved', 'admin-side', '', 'Preferences saved', '2019-07-23 22:33:42'),
(2676, 'en-us', 'reset_time', 'admin-side', '', 'Reset timestamp to now', '2019-07-23 22:33:42'),
(2677, 'en-us', 'search_all', 'admin-side', '', 'Search all', '2019-07-23 22:33:42'),
(2678, 'en-us', 'search_clear', 'admin-side', '', 'Clear search', '2019-07-23 22:33:42'),
(2679, 'en-us', 'search_options', 'admin-side', '', 'Search options', '2019-07-23 22:33:42'),
(2680, 'en-us', 'set_expire_now', 'admin-side', '', 'Set expiry to now', '2019-07-23 22:33:42'),
(2681, 'en-us', 'set_to_now', 'admin-side', '', 'Set timestamp to now', '2019-07-23 22:33:42'),
(2682, 'en-us', 'show', 'admin-side', '', 'Show', '2019-07-23 22:33:42'),
(2683, 'en-us', 'showing_search_results', 'admin-side', '', 'Showing {from} to {to} of {total}', '2019-07-23 22:33:42'),
(2684, 'en-us', 'skin', 'admin-side', '', 'Theme', '2019-07-23 22:33:42'),
(2685, 'en-us', 'skin_dir', 'admin-side', '', 'Theme directory', '2019-07-23 22:33:42'),
(2686, 'en-us', 'sort_value', 'admin-side', '', 'Sort value', '2019-07-23 22:33:42'),
(2687, 'en-us', 'sticky', 'admin-side', '', 'Sticky', '2019-07-23 22:33:42'),
(2688, 'en-us', 'swap_values', 'admin-side', '', 'Swap values', '2019-07-23 22:33:42'),
(2689, 'en-us', 'tab_admin', 'admin-side', '', 'Admin', '2019-07-23 22:33:42'),
(2690, 'en-us', 'tab_comments', 'admin-side', '', 'Comments', '2019-07-23 22:33:42'),
(2691, 'en-us', 'tab_content', 'admin-side', '', 'Content', '2019-07-23 22:33:42'),
(2692, 'en-us', 'tab_diagnostics', 'admin-side', '', 'Diagnostics', '2019-07-23 22:33:42'),
(2693, 'en-us', 'tab_extensions', 'admin-side', '', 'Extensions', '2019-07-23 22:33:42'),
(2694, 'en-us', 'tab_file', 'admin-side', '', 'Files', '2019-07-23 22:33:42'),
(2695, 'en-us', 'tab_forms', 'admin-side', '', 'Forms', '2019-07-23 22:33:42'),
(2696, 'en-us', 'tab_help', 'admin-side', '', 'Help', '2019-07-23 22:33:42'),
(2697, 'en-us', 'tab_image', 'admin-side', '', 'Images', '2019-07-23 22:33:42'),
(2698, 'en-us', 'tab_import', 'admin-side', '', 'Import', '2019-07-23 22:33:42'),
(2699, 'en-us', 'tab_languages', 'admin-side', '', 'Languages', '2019-07-23 22:33:42'),
(2700, 'en-us', 'tab_link', 'admin-side', '', 'Links', '2019-07-23 22:33:42'),
(2701, 'en-us', 'tab_list', 'admin-side', '', 'Articles', '2019-07-23 22:33:42'),
(2702, 'en-us', 'tab_logs', 'admin-side', '', 'Visitor logs', '2019-07-23 22:33:42'),
(2703, 'en-us', 'tab_organise', 'admin-side', '', 'Categories', '2019-07-23 22:33:42'),
(2704, 'en-us', 'tab_pages', 'admin-side', '', 'Pages', '2019-07-23 22:33:42'),
(2705, 'en-us', 'tab_plugins', 'admin-side', '', 'Plugins', '2019-07-23 22:33:42'),
(2706, 'en-us', 'tab_preferences', 'admin-side', '', 'Preferences', '2019-07-23 22:33:42'),
(2707, 'en-us', 'tab_presentation', 'admin-side', '', 'Presentation', '2019-07-23 22:33:42'),
(2708, 'en-us', 'tab_sections', 'admin-side', '', 'Sections', '2019-07-23 22:33:42'),
(2709, 'en-us', 'tab_site_account', 'admin-side', '', 'Account', '2019-07-23 22:33:42'),
(2710, 'en-us', 'tab_site_admin', 'admin-side', '', 'Users', '2019-07-23 22:33:42'),
(2711, 'en-us', 'tab_skin', 'admin-side', '', 'Themes', '2019-07-23 22:33:42'),
(2712, 'en-us', 'tab_start', 'admin-side', '', 'Home', '2019-07-23 22:33:42'),
(2713, 'en-us', 'tab_style', 'admin-side', '', 'Styles', '2019-07-23 22:33:42'),
(2714, 'en-us', 'tab_view_site', 'admin-side', '', 'View site', '2019-07-23 22:33:42'),
(2715, 'en-us', 'tab_write', 'admin-side', '', 'Write', '2019-07-23 22:33:42'),
(2716, 'en-us', 'tagbuilder', 'admin-side', '', 'Tag builder', '2019-07-23 22:33:42'),
(2717, 'en-us', 'tags', 'admin-side', '', 'Tags', '2019-07-23 22:33:42'),
(2718, 'en-us', 'tempdir', 'admin-side', '', 'Temporary directory path', '2019-07-23 22:33:42'),
(2719, 'en-us', 'textfilter', 'admin-side', '', 'Format:', '2019-07-23 22:33:42'),
(2720, 'en-us', 'textfilter_help', 'admin-side', '', 'Text formatting help', '2019-07-23 22:33:42'),
(2721, 'en-us', 'theme_name', 'admin-side', '', 'Admin-side theme', '2019-07-23 22:33:42'),
(2722, 'en-us', 'timestamp', 'admin-side', '', 'Timestamp', '2019-07-23 22:33:42'),
(2723, 'en-us', 'title_body', 'admin-side', '', 'Title and body', '2019-07-23 22:33:42'),
(2724, 'en-us', 'title_body_excerpt', 'admin-side', '', 'Title, body and excerpt', '2019-07-23 22:33:42'),
(2725, 'en-us', 'unmoderated', 'admin-side', '', 'Unmoderated', '2019-07-23 22:33:42'),
(2726, 'en-us', 'update_from_disk', 'admin-side', '', 'Update from disk', '2019-07-23 22:33:42'),
(2727, 'en-us', 'use_textile', 'admin-side', '', 'Use Textile', '2019-07-23 22:33:42'),
(2728, 'en-us', 'view_html', 'admin-side', '', 'View HTML markup', '2019-07-23 22:33:42'),
(2729, 'en-us', 'view_html_short', 'admin-side', '', 'HTML', '2019-07-23 22:33:42'),
(2730, 'en-us', 'view_per_page', 'admin-side', '', 'View {page} per page', '2019-07-23 22:33:42'),
(2731, 'en-us', 'view_preview', 'admin-side', '', 'Preview article', '2019-07-23 22:33:42'),
(2732, 'en-us', 'view_preview_short', 'admin-side', '', 'Preview', '2019-07-23 22:33:42'),
(2733, 'en-us', 'view_text', 'admin-side', '', 'Edit article text', '2019-07-23 22:33:42'),
(2734, 'en-us', 'view_text_short', 'admin-side', '', 'Text', '2019-07-23 22:33:42'),
(2735, 'en-us', 'visible', 'admin-side', '', 'Visible', '2019-07-23 22:33:42'),
(2736, 'en-us', 'article_expires_before_postdate', 'article', '', 'Error: article expires <strong>before</strong> publish date.', '2019-07-23 22:33:42'),
(2737, 'en-us', 'article_markup', 'article', '', 'Body markup', '2019-07-23 22:33:42'),
(2738, 'en-us', 'article_posted', 'article', '', 'Article posted.', '2019-07-23 22:33:42'),
(2739, 'en-us', 'article_saved', 'article', '', 'Article saved.', '2019-07-23 22:33:42'),
(2740, 'en-us', 'article_saved_draft', 'article', '', 'Article saved as draft.', '2019-07-23 22:33:42'),
(2741, 'en-us', 'article_saved_hidden', 'article', '', 'Article saved as hidden.', '2019-07-23 22:33:42'),
(2742, 'en-us', 'article_saved_pending', 'article', '', 'Article saved as pending.', '2019-07-23 22:33:42'),
(2743, 'en-us', 'article_save_failed', 'article', '', 'The article was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2744, 'en-us', 'comment_settings', 'article', '', 'Comment options', '2019-07-23 22:33:42'),
(2745, 'en-us', 'concurrent_edit_by', 'article', '', 'Article <strong>not</strong> saved. {author} modified the article while you were editing it. If you’re sure, press the ‘Save’ button once more.', '2019-07-23 22:33:42'),
(2746, 'en-us', 'excerpt_markup', 'article', '', 'Excerpt markup', '2019-07-23 22:33:42'),
(2747, 'en-us', 'override_default_form', 'article', '', 'Override form', '2019-07-23 22:33:42'),
(2748, 'en-us', 'problem_creating_article', 'article', '', 'There was a problem creating the article.', '2019-07-23 22:33:42'),
(2749, 'en-us', 'problem_deleting_article', 'article', '', 'There was a problem deleting the article.', '2019-07-23 22:33:42'),
(2750, 'en-us', 'problem_getting_articles', 'article', '', 'There was a problem getting articles.', '2019-07-23 22:33:42'),
(2751, 'en-us', 'problem_retrieving_article', 'article', '', 'There was a problem retrieving the article.', '2019-07-23 22:33:42'),
(2752, 'en-us', 'problem_retrieving_article_categories', 'article', '', 'There was a problem retrieving article categories.', '2019-07-23 22:33:42'),
(2753, 'en-us', 'problem_retrieving_categories', 'article', '', 'There was a problem retrieving categories.', '2019-07-23 22:33:42'),
(2754, 'en-us', 'problem_retrieving_category_info', 'article', '', 'There was a problem retrieving category information.', '2019-07-23 22:33:42'),
(2755, 'en-us', 'problem_retrieving_sections', 'article', '', 'There was a problem retrieving sections.', '2019-07-23 22:33:42'),
(2756, 'en-us', 'problem_saving_article_categories', 'article', '', 'There was a problem saving article categories.', '2019-07-23 22:33:42'),
(2757, 'en-us', 'problem_updating_article', 'article', '', 'There was a problem updating the article.', '2019-07-23 22:33:42'),
(2758, 'en-us', 'sort_display', 'article', '', 'Sort and display', '2019-07-23 22:33:42'),
(2759, 'en-us', 'trying_to_assign_unexisting_category_to_the_article', 'article', '', 'A non-existent category cannot be assigned to an article.', '2019-07-23 22:33:42'),
(2760, 'en-us', 'url_title', 'article', '', 'URL-only title', '2019-07-23 22:33:42'),
(2761, 'en-us', 'url_title_is_blank', 'article', '', 'URL-only title was left blank.', '2019-07-23 22:33:42'),
(2762, 'en-us', 'url_title_is_multiple', 'article', '', 'The same URL-only title is used by {count} different articles.', '2019-07-23 22:33:42'),
(2763, 'en-us', 'write', 'article', '', 'Write', '2019-07-23 22:33:42'),
(2764, 'en-us', 'article_categories_deleted', 'category', '', 'Article categories deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2765, 'en-us', 'article_category', 'category', '', 'Article category', '2019-07-23 22:33:42'),
(2766, 'en-us', 'article_category_already_exists', 'category', '', 'Article category <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2767, 'en-us', 'article_category_created', 'category', '', 'Article category <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(2768, 'en-us', 'article_category_description', 'category', '', 'Article category description', '2019-07-23 22:33:42'),
(2769, 'en-us', 'article_category_invalid', 'category', '', 'Article category <strong>{name}</strong> invalid.', '2019-07-23 22:33:42'),
(2770, 'en-us', 'article_category_name', 'category', '', 'Article category name', '2019-07-23 22:33:42'),
(2771, 'en-us', 'article_category_title', 'category', '', 'Article category title', '2019-07-23 22:33:42'),
(2772, 'en-us', 'article_category_updated', 'category', '', 'Article category <strong>{name}</strong> updated.', '2019-07-23 22:33:42'),
(2773, 'en-us', 'categories_set_parent', 'category', '', '{list} assigned to {type} category parent {parent}', '2019-07-23 22:33:42'),
(2774, 'en-us', 'category_not_found', 'category', '', 'Category not found', '2019-07-23 22:33:42'),
(2775, 'en-us', 'category_save_failed', 'category', '', 'The category was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2776, 'en-us', 'create_category', 'category', '', 'New category', '2019-07-23 22:33:42'),
(2777, 'en-us', 'edit_category', 'category', '', 'Edit category', '2019-07-23 22:33:42'),
(2778, 'en-us', 'file_categories_deleted', 'category', '', 'File categories deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2779, 'en-us', 'file_category_already_exists', 'category', '', 'File category <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2780, 'en-us', 'file_category_created', 'category', '', 'File category <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(2781, 'en-us', 'file_category_description', 'category', '', 'File category description', '2019-07-23 22:33:42'),
(2782, 'en-us', 'file_category_invalid', 'category', '', 'File category <strong>{name}</strong> invalid.', '2019-07-23 22:33:42'),
(2783, 'en-us', 'file_category_name', 'category', '', 'File category name', '2019-07-23 22:33:42'),
(2784, 'en-us', 'file_category_title', 'category', '', 'File category title', '2019-07-23 22:33:42'),
(2785, 'en-us', 'file_category_updated', 'category', '', 'File category <strong>{name}</strong> updated.', '2019-07-23 22:33:42'),
(2786, 'en-us', 'image_categories_deleted', 'category', '', 'Image categories deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2787, 'en-us', 'image_category_already_exists', 'category', '', 'Image category <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2788, 'en-us', 'image_category_created', 'category', '', 'Image category <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(2789, 'en-us', 'image_category_description', 'category', '', 'Image category description', '2019-07-23 22:33:42'),
(2790, 'en-us', 'image_category_invalid', 'category', '', 'Image category <strong>{name}</strong> invalid.', '2019-07-23 22:33:42'),
(2791, 'en-us', 'image_category_name', 'category', '', 'Image category name', '2019-07-23 22:33:42'),
(2792, 'en-us', 'image_category_title', 'category', '', 'Image category title', '2019-07-23 22:33:42'),
(2793, 'en-us', 'image_category_updated', 'category', '', 'Image category <strong>{name}</strong> updated.', '2019-07-23 22:33:42'),
(2794, 'en-us', 'link_categories_deleted', 'category', '', 'Link categories deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2795, 'en-us', 'link_category_already_exists', 'category', '', 'Link category <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2796, 'en-us', 'link_category_created', 'category', '', 'Link category <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(2797, 'en-us', 'link_category_description', 'category', '', 'Link category description', '2019-07-23 22:33:42'),
(2798, 'en-us', 'link_category_invalid', 'category', '', 'Link category <strong>{name}</strong> invalid.', '2019-07-23 22:33:42'),
(2799, 'en-us', 'link_category_name', 'category', '', 'Link category name', '2019-07-23 22:33:42'),
(2800, 'en-us', 'link_category_title', 'category', '', 'Link category title', '2019-07-23 22:33:42'),
(2801, 'en-us', 'link_category_updated', 'category', '', 'Link category <strong>{name}</strong> updated.', '2019-07-23 22:33:42'),
(2802, 'en-us', 'no_categories_exist', 'category', '', 'None exist.', '2019-07-23 22:33:42'),
(2803, 'en-us', 'no_other_categories_exist', 'category', '', 'No other categories exist.', '2019-07-23 22:33:42'),
(2804, 'en-us', 'all_stylesheets', 'css', '', 'All styles', '2019-07-23 22:33:42'),
(2805, 'en-us', 'create_css', 'css', '', 'New style', '2019-07-23 22:33:42'),
(2806, 'en-us', 'css_already_exists', 'css', '', 'Style <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2807, 'en-us', 'css_code', 'css', '', 'Style code', '2019-07-23 22:33:42'),
(2808, 'en-us', 'css_created', 'css', '', 'Styles created: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2809, 'en-us', 'css_deleted', 'css', '', 'Styles deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2810, 'en-us', 'css_name', 'css', '', 'Style name', '2019-07-23 22:33:42'),
(2811, 'en-us', 'css_name_required', 'css', '', 'Please provide a name for your style.', '2019-07-23 22:33:42'),
(2812, 'en-us', 'css_save_failed', 'css', '', 'The style was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2813, 'en-us', 'css_updated', 'css', '', 'Styles updated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2814, 'en-us', 'css_used_by_section', 'css', '', 'Style <strong>{name}</strong> not deleted: used by {count} section(s).', '2019-07-23 22:33:42'),
(2815, 'en-us', 'all_checks_passed', 'diag', '', 'All checks passed.', '2019-07-23 22:33:42'),
(2816, 'en-us', 'cgi_header_config', 'diag', '', 'CGI header configuration', '2019-07-23 22:33:42'),
(2817, 'en-us', 'clean_url_data_failed', 'diag', '', 'Clean URL data test failed: {data}.', '2019-07-23 22:33:42'),
(2818, 'en-us', 'clean_url_test_failed', 'diag', '', 'Clean URL test failed.', '2019-07-23 22:33:42'),
(2819, 'en-us', 'diag_clear_private', 'diag', '', 'Hide private information', '2019-07-23 22:33:42'),
(2820, 'en-us', 'dev_version_live', 'diag', '', 'You are running a development version of Textpattern on a live server.', '2019-07-23 22:33:42'),
(2821, 'en-us', 'diagnostic_info', 'diag', '', 'Diagnostic info', '2019-07-23 22:33:42'),
(2822, 'en-us', 'dir_not_writable', 'diag', '', '{dirtype} is not writable: {path}.', '2019-07-23 22:33:42'),
(2823, 'en-us', 'dns_lookup_fails', 'diag', '', 'DNS lookup failed: {domain}.', '2019-07-23 22:33:42'),
(2824, 'en-us', 'file_uploads_disabled', 'diag', '', 'File uploads are disabled.', '2019-07-23 22:33:42'),
(2825, 'en-us', 'high', 'diag', '', 'High', '2019-07-23 22:33:42'),
(2826, 'en-us', 'htaccess_missing', 'diag', '', '.htaccess file is missing.', '2019-07-23 22:33:42'),
(2827, 'en-us', 'low', 'diag', '', 'Low', '2019-07-23 22:33:42'),
(2828, 'en-us', 'missing_files', 'diag', '', 'Missing files: {list}', '2019-07-23 22:33:42'),
(2829, 'en-us', 'modified_files', 'diag', '', 'Some Textpattern files have been modified: {list}', '2019-07-23 22:33:42'),
(2830, 'en-us', 'mod_rewrite_missing', 'diag', '', 'Apache module <code>mod_rewrite</code> is not enabled.', '2019-07-23 22:33:42'),
(2831, 'en-us', 'mysql_table_errors', 'diag', '', 'The following errors were detected in your MySQL tables: {list}', '2019-07-23 22:33:42'),
(2832, 'en-us', 'no_temp_dir', 'diag', '', 'No temporary directory defined.', '2019-07-23 22:33:42'),
(2833, 'en-us', 'old_placeholder', 'diag', '', 'Old placeholder file is in the way: {path}.', '2019-07-23 22:33:42'),
(2834, 'en-us', 'path_inaccessible', 'diag', '', '<strong>{path}</strong> is not accessible.', '2019-07-23 22:33:42'),
(2835, 'en-us', 'php_extensions', 'diag', '', 'PHP extensions', '2019-07-23 22:33:42'),
(2836, 'en-us', 'php_version_required', 'diag', '', 'Textpattern requires at least version {version} of PHP to be installed on your server.', '2019-07-23 22:33:42'),
(2837, 'en-us', 'preflight_check', 'diag', '', 'Pre-flight check', '2019-07-23 22:33:42'),
(2838, 'en-us', 'problem_connecting_update_server', 'diag', '', 'There was a problem connecting to the Textpattern update server. Please try again later.', '2019-07-23 22:33:42'),
(2839, 'en-us', 'site_trailing_slash', 'diag', '', 'Site URL has a trailing slash: {path}.', '2019-07-23 22:33:42'),
(2840, 'en-us', 'site_url_mismatch', 'diag', '', 'Site URL preference might be incorrect: {url}.', '2019-07-23 22:33:42'),
(2841, 'en-us', 'some_php_functions_disabled', 'diag', '', 'The following PHP functions (which may be necessary to run Textpattern) are disabled on your server: {list}.', '2019-07-23 22:33:42'),
(2842, 'en-us', 'still_exists', 'diag', '', '<strong>{path}</strong> still exists.', '2019-07-23 22:33:42'),
(2843, 'en-us', 'textpattern_update_available', 'diag', '', 'New Textpattern version {version} <a href=\"https://textpattern.com/download\" rel=\"external\">available for download</a>.', '2019-07-23 22:33:42'),
(2844, 'en-us', 'textpattern_update_available_beta', 'diag', '', 'New Textpattern pre-release version {version} available for download. Please <a href=\"https://textpattern.com/download-beta\" rel=\"external\">visit the Textpattern website</a> for more information.', '2019-07-23 22:33:42'),
(2845, 'en-us', 'tmp_plugin_paths_match', 'diag', '', 'Temporary directory path and plugin cache directory path should <strong>not</strong> match.', '2019-07-23 22:33:42'),
(2846, 'en-us', 'warn_mail_unavailable', 'diag', '', 'Your PHP installation is missing the <code>mail()</code> function. Therefore no emails can be sent from Textpattern, which limits certain functionality.', '2019-07-23 22:33:42'),
(2847, 'en-us', 'article_deleted', 'discuss', '', 'Article deleted.', '2019-07-23 22:33:42'),
(2848, 'en-us', 'comments_deleted', 'discuss', '', 'Comments deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2849, 'en-us', 'comments_marked_spam', 'discuss', '', 'Comments hidden and marked as spam: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2850, 'en-us', 'comments_marked_unmoderated', 'discuss', '', 'Comments hidden and marked as unmoderated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2851, 'en-us', 'comments_marked_visible', 'discuss', '', 'Comments made visible: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2852, 'en-us', 'comment_not_found', 'discuss', '', 'Comment does not exist, it may have been deleted.', '2019-07-23 22:33:42'),
(2853, 'en-us', 'comment_save_failed', 'discuss', '', 'The comment was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2854, 'en-us', 'comment_updated', 'discuss', '', 'Comment <strong>{id}</strong> updated.', '2019-07-23 22:33:42'),
(2855, 'en-us', 'edit_comment', 'discuss', '', 'Edit comment', '2019-07-23 22:33:42'),
(2856, 'en-us', 'hide_spam', 'discuss', '', 'Hide (spam)', '2019-07-23 22:33:42'),
(2857, 'en-us', 'hide_unmoderated', 'discuss', '', 'Hide (unmoderated)', '2019-07-23 22:33:42'),
(2858, 'en-us', 'just_spam_results_found', 'discuss', '', 'Only spam comments found. Use ‘Show spam’ to view them.', '2019-07-23 22:33:42'),
(2859, 'en-us', 'no_comments_recorded', 'discuss', '', 'No comments recorded.', '2019-07-23 22:33:42'),
(2860, 'en-us', 'search_comments', 'discuss', '', 'Search comments', '2019-07-23 22:33:42'),
(2861, 'en-us', 'show_spam', 'discuss', '', 'Show spam', '2019-07-23 22:33:42'),
(2862, 'en-us', 'condition', 'file', '', 'Condition', '2019-07-23 22:33:42'),
(2863, 'en-us', 'edit_file', 'file', '', 'Edit file', '2019-07-23 22:33:42'),
(2864, 'en-us', 'existing_file', 'file', '', 'Existing file', '2019-07-23 22:33:42'),
(2865, 'en-us', 'file_already_exists', 'file', '', 'File <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2866, 'en-us', 'file_cannot_rename', 'file', '', 'File <strong>{name}</strong> could not be renamed.', '2019-07-23 22:33:42'),
(2867, 'en-us', 'file_deleted', 'file', '', 'Files deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2868, 'en-us', 'file_delete_failed', 'file', '', 'Failed to delete files: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2869, 'en-us', 'file_dir_not_writeable', 'file', '', '<strong>Warning:</strong> cannot write to file directory <code>{filedir}</code>.', '2019-07-23 22:33:42'),
(2870, 'en-us', 'file_not_found', 'file', '', 'Files not found: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2871, 'en-us', 'file_not_updated', 'file', '', 'File <strong>{name}</strong> not updated.', '2019-07-23 22:33:42'),
(2872, 'en-us', 'file_relink', 'file', '', 'Upload/assign file', '2019-07-23 22:33:42'),
(2873, 'en-us', 'file_status', 'file', '', 'File status', '2019-07-23 22:33:42'),
(2874, 'en-us', 'file_status_missing', 'file', '', 'Missing', '2019-07-23 22:33:42'),
(2875, 'en-us', 'file_status_ok', 'file', '', 'OK', '2019-07-23 22:33:42'),
(2876, 'en-us', 'file_unsynchronized', 'file', '', 'File <strong>{name}</strong> has become unsynchronized with the database. Please manually fix filename.', '2019-07-23 22:33:42'),
(2877, 'en-us', 'file_updated', 'file', '', 'Files updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2878, 'en-us', 'file_uploaded', 'file', '', 'Files uploaded: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2879, 'en-us', 'file_upload_failed', 'file', '', 'Failed to upload: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2880, 'en-us', 'invalid_filename', 'file', '', 'Invalid filenames: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2881, 'en-us', 'invalid_id', 'file', '', 'Invalid ID <strong>{id}</strong>.', '2019-07-23 22:33:42'),
(2882, 'en-us', 'linked_to_file', 'file', '', 'Files linked: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2883, 'en-us', 'no_files_recorded', 'file', '', 'No files recorded.', '2019-07-23 22:33:42'),
(2884, 'en-us', 'permissions', 'file', '', 'Permissions', '2019-07-23 22:33:42'),
(2885, 'en-us', 'private', 'file', '', 'Private', '2019-07-23 22:33:42'),
(2886, 'en-us', 'public', 'file', '', 'Public', '2019-07-23 22:33:42'),
(2887, 'en-us', 'replace_file', 'file', '', 'Replace file', '2019-07-23 22:33:42'),
(2888, 'en-us', 'reset_download_count', 'file', '', 'Reset download count', '2019-07-23 22:33:42'),
(2889, 'en-us', 'search_files', 'file', '', 'Search files', '2019-07-23 22:33:42'),
(2890, 'en-us', 'all_forms', 'form', '', 'All forms', '2019-07-23 22:33:42'),
(2891, 'en-us', 'changetype', 'form', '', 'Change type', '2019-07-23 22:33:42'),
(2892, 'en-us', 'create_form', 'form', '', 'New form', '2019-07-23 22:33:42'),
(2893, 'en-us', 'form_already_exists', 'form', '', 'Form <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2894, 'en-us', 'form_clone', 'form', '', 'Duplicate form', '2019-07-23 22:33:42'),
(2895, 'en-us', 'form_code', 'form', '', 'Form code', '2019-07-23 22:33:42'),
(2896, 'en-us', 'form_created', 'form', '', 'Forms created: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2897, 'en-us', 'form_deleted', 'form', '', 'Forms deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2898, 'en-us', 'form_name', 'form', '', 'Form name', '2019-07-23 22:33:42'),
(2899, 'en-us', 'form_name_invalid', 'form', '', 'Form name invalid.', '2019-07-23 22:33:42'),
(2900, 'en-us', 'form_save_failed', 'form', '', 'The form was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2901, 'en-us', 'form_type', 'form', '', 'Form type', '2019-07-23 22:33:42'),
(2902, 'en-us', 'form_type_missing', 'form', '', 'Form type is missing', '2019-07-23 22:33:42'),
(2903, 'en-us', 'form_updated', 'form', '', 'Forms updated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2904, 'en-us', 'alt_text', 'image', '', 'Alternate text', '2019-07-23 22:33:42'),
(2905, 'en-us', 'create_thumbnail', 'image', '', 'Create thumbnail', '2019-07-23 22:33:42'),
(2906, 'en-us', 'edit_image', 'image', '', 'Edit image', '2019-07-23 22:33:42'),
(2907, 'en-us', 'image_deleted', 'image', '', 'Images deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2908, 'en-us', 'image_delete_failed', 'image', '', 'Images could not be completely deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2909, 'en-us', 'image_details', 'image', '', 'Image details', '2019-07-23 22:33:42'),
(2910, 'en-us', 'image_name', 'image', '', 'Image name', '2019-07-23 22:33:42'),
(2911, 'en-us', 'image_save_error', 'image', '', 'There was a problem saving image data.', '2019-07-23 22:33:42'),
(2912, 'en-us', 'image_save_failed', 'image', '', 'The image was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2913, 'en-us', 'image_updated', 'image', '', 'Images updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2914, 'en-us', 'image_uploaded', 'image', '', 'Images uploaded: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2915, 'en-us', 'img_dir_not_writeable', 'image', '', '<strong>Warning:</strong> cannot write to image directory <code>{imgdir}</code>.', '2019-07-23 22:33:42'),
(2916, 'en-us', 'invalid_width_or_height', 'image', '', 'Invalid width or height.', '2019-07-23 22:33:42'),
(2917, 'en-us', 'keep_square_pixels', 'image', '', 'Crop', '2019-07-23 22:33:42'),
(2918, 'en-us', 'no_images_recorded', 'image', '', 'No images recorded.', '2019-07-23 22:33:42'),
(2919, 'en-us', 'only_graphic_files_allowed', 'image', '', 'Only .jpg, .gif or .png file types allowed.', '2019-07-23 22:33:42'),
(2920, 'en-us', 'replace_image', 'image', '', 'Replace image', '2019-07-23 22:33:42'),
(2921, 'en-us', 'search_images', 'image', '', 'Search images', '2019-07-23 22:33:42'),
(2922, 'en-us', 'thumbnail_delete_failed', 'image', '', 'Thumbnail could not be completely deleted.', '2019-07-23 22:33:42'),
(2923, 'en-us', 'thumbnail_deleted', 'image', '', 'Thumbnail deleted.', '2019-07-23 22:33:42'),
(2924, 'en-us', 'thumbnail_not_saved', 'image', '', 'Thumbnail <strong>{id}</strong> not saved.', '2019-07-23 22:33:42'),
(2925, 'en-us', 'thumbnail_saved', 'image', '', 'Thumbnail <strong>{id}</strong> saved.', '2019-07-23 22:33:42'),
(2926, 'en-us', 'upload_image', 'image', '', 'Upload image', '2019-07-23 22:33:42'),
(2927, 'en-us', 'upload_thumbnail', 'image', '', 'Upload thumbnail', '2019-07-23 22:33:42'),
(2928, 'en-us', 'active_language', 'lang', '', 'Site language', '2019-07-23 22:33:42'),
(2929, 'en-us', 'active_language_ui', 'lang', '', 'User language', '2019-07-23 22:33:42'),
(2930, 'en-us', 'install_from_textpack', 'lang', '', 'Install from Textpack', '2019-07-23 22:33:42'),
(2931, 'en-us', 'install_textpack', 'lang', '', 'Install Textpack', '2019-07-23 22:33:42'),
(2932, 'en-us', 'language_deleted', 'lang', '', 'Language <strong>{name}</strong> removed.', '2019-07-23 22:33:42'),
(2933, 'en-us', 'language_installed', 'lang', '', 'Language <strong>{name}</strong> installed.', '2019-07-23 22:33:42'),
(2934, 'en-us', 'language_not_installed', 'lang', '', 'Language <strong>{name}</strong> not installed.', '2019-07-23 22:33:42'),
(2935, 'en-us', 'language_preamble', 'lang', '', '<strong>You can help us improve Textpattern!</strong> We welcome additional translations (and corrections to current translations) by our user community. Please visit <a href=\"https://textpattern.com/languages\" rel=\"external\" target=\"_blank\">Textpattern language translations <span class=\"ui-icon ui-icon-extlink\">(opens an external link in a new window)</span></a> for further details.', '2019-07-23 22:33:42'),
(2936, 'en-us', 'language_updated', 'lang', '', 'Language <strong>{name}</strong> updated.', '2019-07-23 22:33:42'),
(2937, 'en-us', 'locale_not_available_for_language', 'lang', '', 'No locale available for language <strong>{name}</strong>. Using default locale instead.', '2019-07-23 22:33:42'),
(2938, 'en-us', 'textpack_strings_installed', 'lang', '', '{count} strings installed from Textpack.', '2019-07-23 22:33:42'),
(2939, 'en-us', 'create_link', 'link', '', 'New link', '2019-07-23 22:33:42'),
(2940, 'en-us', 'edit_link', 'link', '', 'Edit link', '2019-07-23 22:33:42'),
(2941, 'en-us', 'links_deleted', 'link', '', 'Links deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2942, 'en-us', 'link_created', 'link', '', 'Link <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(2943, 'en-us', 'link_empty', 'link', '', 'Links should not be empty.', '2019-07-23 22:33:42'),
(2944, 'en-us', 'link_saved', 'link', '', 'Link saved.', '2019-07-23 22:33:42'),
(2945, 'en-us', 'link_save_failed', 'link', '', 'Link could not be saved.', '2019-07-23 22:33:42'),
(2946, 'en-us', 'link_updated', 'link', '', 'Links updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2947, 'en-us', 'no_links_recorded', 'link', '', 'No links recorded.', '2019-07-23 22:33:42'),
(2948, 'en-us', 'search_links', 'link', '', 'Search links', '2019-07-23 22:33:42'),
(2949, 'en-us', 'articles_deleted', 'list', '', 'Articles deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2950, 'en-us', 'articles_duplicated', 'list', '', 'Articles duplicated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2951, 'en-us', 'articles_modified', 'list', '', 'Articles modified: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2952, 'en-us', 'no_articles_recorded', 'list', '', 'No articles recorded.', '2019-07-23 22:33:42'),
(2953, 'en-us', 'search_articles', 'list', '', 'Search articles', '2019-07-23 22:33:42'),
(2954, 'en-us', 'logs_deleted', 'log', '', 'Logs deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2955, 'en-us', 'no_refers_recorded', 'log', '', 'No hits recorded.', '2019-07-23 22:33:42'),
(2956, 'en-us', 'referrer', 'log', '', 'Referrer', '2019-07-23 22:33:42'),
(2957, 'en-us', 'search_logs', 'log', '', 'Search logs', '2019-07-23 22:33:42'),
(2958, 'en-us', 'all_pages', 'page', '', 'All pages', '2019-07-23 22:33:42'),
(2959, 'en-us', 'create_page', 'page', '', 'New page', '2019-07-23 22:33:42'),
(2960, 'en-us', 'page_already_exists', 'page', '', 'Page <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(2961, 'en-us', 'page_code', 'page', '', 'Page code', '2019-07-23 22:33:42'),
(2962, 'en-us', 'page_created', 'page', '', 'Pages created: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2963, 'en-us', 'page_deleted', 'page', '', 'Pages deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2964, 'en-us', 'page_name', 'page', '', 'Page name', '2019-07-23 22:33:42'),
(2965, 'en-us', 'page_name_invalid', 'page', '', 'Page name is invalid.', '2019-07-23 22:33:42'),
(2966, 'en-us', 'page_save_failed', 'page', '', 'The page was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(2967, 'en-us', 'page_updated', 'page', '', 'Pages updated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(2968, 'en-us', 'page_used_by_section', 'page', '', 'Page <strong>{name}</strong> not deleted; used by {count} section(s).', '2019-07-23 22:33:42'),
(2969, 'en-us', 'bad_plugin_code', 'plugin', '', 'Badly formed or empty plugin code.', '2019-07-23 22:33:42'),
(2970, 'en-us', 'changeorder', 'plugin', '', 'Change order', '2019-07-23 22:33:42'),
(2971, 'en-us', 'edit_plugin', 'plugin', '', 'Edit plugin <strong>{name}</strong>', '2019-07-23 22:33:42'),
(2972, 'en-us', 'edit_plugins', 'plugin', '', 'Edit plugin', '2019-07-23 22:33:42'),
(2973, 'en-us', 'install_plugin', 'plugin', '', 'Install plugin', '2019-07-23 22:33:42'),
(2974, 'en-us', 'order', 'plugin', '', 'Order', '2019-07-23 22:33:42'),
(2975, 'en-us', 'plugin', 'plugin', '', 'Plugin', '2019-07-23 22:33:42'),
(2976, 'en-us', 'plugin_compression_unsupported', 'plugin', '', 'Plugin decompression is not supported by your server. Contact the plugin author for an uncompressed version.', '2019-07-23 22:33:42'),
(2977, 'en-us', 'plugin_deleted', 'plugin', '', 'Plugins deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2978, 'en-us', 'plugin_help', 'plugin', '', 'Plugin help', '2019-07-23 22:33:42'),
(2979, 'en-us', 'plugin_installed', 'plugin', '', 'Plugin <strong>{name}</strong> installed.', '2019-07-23 22:33:42'),
(2980, 'en-us', 'plugin_install_failed', 'plugin', '', 'Plugin <strong>{name}</strong> install failed.', '2019-07-23 22:33:42'),
(2981, 'en-us', 'plugin_saved', 'plugin', '', 'Plugin <strong>{name}</strong> saved.', '2019-07-23 22:33:42'),
(2982, 'en-us', 'plugin_updated', 'plugin', '', 'Plugins updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(2983, 'en-us', 'previewing_plugin', 'plugin', '', 'Plugin preview', '2019-07-23 22:33:42'),
(2984, 'en-us', 'search_plugins', 'plugin', '', 'Search plugins', '2019-07-23 22:33:42'),
(2985, 'en-us', 'verify_plugin', 'plugin', '', 'Verify plugin', '2019-07-23 22:33:42'),
(2986, 'en-us', 'admin_side_plugins', 'prefs', '', 'Use admin-side plugins?', '2019-07-23 22:33:42'),
(2987, 'en-us', 'allow_article_php_scripting', 'prefs', '', 'Allow PHP in articles?', '2019-07-23 22:33:42'),
(2988, 'en-us', 'allow_form_override', 'prefs', '', 'Allow form override?', '2019-07-23 22:33:42'),
(2989, 'en-us', 'allow_page_php_scripting', 'prefs', '', 'Allow PHP in pages?', '2019-07-23 22:33:42'),
(2990, 'en-us', 'all_hits', 'prefs', '', 'All hits', '2019-07-23 22:33:42'),
(2991, 'en-us', 'all_preferences', 'prefs', '', 'All preferences', '2019-07-23 22:33:42'),
(2992, 'en-us', 'archive_dateformat', 'prefs', '', 'Archive date format', '2019-07-23 22:33:42'),
(2993, 'en-us', 'articles_use_excerpts', 'prefs', '', 'Use excerpts on articles?', '2019-07-23 22:33:42'),
(2994, 'en-us', 'attach_titles_to_permalinks', 'prefs', '', 'Attach titles to permalinks?', '2019-07-23 22:33:42'),
(2995, 'en-us', 'category_subcategory', 'prefs', '', '/category/subcategory', '2019-07-23 22:33:42'),
(2996, 'en-us', 'clean', 'prefs', '', '/clean/', '2019-07-23 22:33:42'),
(2997, 'en-us', 'comments_are_ol', 'prefs', '', 'Present comments as a numbered list?', '2019-07-23 22:33:42'),
(2998, 'en-us', 'comments_auto_append', 'prefs', '', 'Automatically append comments to articles?', '2019-07-23 22:33:42'),
(2999, 'en-us', 'comments_dateformat', 'prefs', '', 'Comments date format', '2019-07-23 22:33:42'),
(3000, 'en-us', 'comments_default_invite', 'prefs', '', 'Default comments invite', '2019-07-23 22:33:42'),
(3001, 'en-us', 'comments_disabled_after', 'prefs', '', 'Comments disabled after', '2019-07-23 22:33:42'),
(3002, 'en-us', 'comments_disallow_images', 'prefs', '', 'Strip commenter images from comments?', '2019-07-23 22:33:42'),
(3003, 'en-us', 'comments_mode', 'prefs', '', 'Comments mode', '2019-07-23 22:33:42'),
(3004, 'en-us', 'comments_moderate', 'prefs', '', 'Moderate comments?', '2019-07-23 22:33:42'),
(3005, 'en-us', 'comments_on_default', 'prefs', '', 'Comments on by default?', '2019-07-23 22:33:42'),
(3006, 'en-us', 'comments_require_email', 'prefs', '', 'Comments require commenter’s email address?', '2019-07-23 22:33:42'),
(3007, 'en-us', 'comments_require_name', 'prefs', '', 'Comments require commenter’s name?', '2019-07-23 22:33:42'),
(3008, 'en-us', 'comments_sendmail', 'prefs', '', 'Email comments to author?', '2019-07-23 22:33:42'),
(3009, 'en-us', 'comments_use_fat_textile', 'prefs', '', 'Allow more Textile markup in comments?', '2019-07-23 22:33:42'),
(3010, 'en-us', 'comment_means_site_updated', 'prefs', '', 'New comment means site updated?', '2019-07-23 22:33:42'),
(3011, 'en-us', 'comment_nofollow', 'prefs', '', 'Apply <code>rel=\"nofollow\"</code> to comments?', '2019-07-23 22:33:42'),
(3012, 'en-us', 'custom_10_set', 'prefs', '', 'Custom field 10 name', '2019-07-23 22:33:42'),
(3013, 'en-us', 'custom_1_set', 'prefs', '', 'Custom field 1 name', '2019-07-23 22:33:42'),
(3014, 'en-us', 'custom_2_set', 'prefs', '', 'Custom field 2 name', '2019-07-23 22:33:42'),
(3015, 'en-us', 'custom_3_set', 'prefs', '', 'Custom field 3 name', '2019-07-23 22:33:42'),
(3016, 'en-us', 'custom_4_set', 'prefs', '', 'Custom field 4 name', '2019-07-23 22:33:42'),
(3017, 'en-us', 'custom_5_set', 'prefs', '', 'Custom field 5 name', '2019-07-23 22:33:42'),
(3018, 'en-us', 'custom_6_set', 'prefs', '', 'Custom field 6 name', '2019-07-23 22:33:42'),
(3019, 'en-us', 'custom_7_set', 'prefs', '', 'Custom field 7 name', '2019-07-23 22:33:42'),
(3020, 'en-us', 'custom_8_set', 'prefs', '', 'Custom field 8 name', '2019-07-23 22:33:42'),
(3021, 'en-us', 'custom_9_set', 'prefs', '', 'Custom field 9 name', '2019-07-23 22:33:42'),
(3022, 'en-us', 'custom_form_types', 'prefs', '', 'Custom form template types', '2019-07-23 22:33:42'),
(3023, 'en-us', 'default_event', 'prefs', '', 'Default panel', '2019-07-23 22:33:42'),
(3024, 'en-us', 'default_publish_status', 'prefs', '', 'Default publishing status', '2019-07-23 22:33:42'),
(3025, 'en-us', 'doctype', 'prefs', '', 'Doctype', '2019-07-23 22:33:42'),
(3026, 'en-us', 'module_pophelp', 'prefs', '', 'Display admin-side inline help links', '2019-07-23 22:33:42'),
(3027, 'en-us', 'enable_dev_preview', 'prefs', '', 'Enable development theme preview?', '2019-07-23 22:33:42'),
(3028, 'en-us', 'enable_short_tags', 'prefs', '', 'Enable short tag support?', '2019-07-23 22:33:42'),
(3029, 'en-us', 'enable_xmlrpc_server', 'prefs', '', 'Enable XML-RPC server?', '2019-07-23 22:33:42'),
(3030, 'en-us', 'expire_logs_after', 'prefs', '', 'Logs expire after how many days?', '2019-07-23 22:33:42'),
(3031, 'en-us', 'feeds', 'prefs', '', 'Feeds', '2019-07-23 22:33:42'),
(3032, 'en-us', 'file_max_upload_size', 'prefs', '', 'Maximum file size of uploads (in bytes)', '2019-07-23 22:33:42'),
(3033, 'en-us', 'ham', 'prefs', '', 'All but spam', '2019-07-23 22:33:42'),
(3034, 'en-us', 'hours_days_ago', 'prefs', '', 'hours/days ago', '2019-07-23 22:33:42'),
(3035, 'en-us', 'id_title', 'prefs', '', '/id/title', '2019-07-23 22:33:42'),
(3036, 'en-us', 'include_email_atom', 'prefs', '', 'Include email address in Atom feeds?', '2019-07-23 22:33:42'),
(3037, 'en-us', 'language', 'prefs', '', 'Language', '2019-07-23 22:33:42'),
(3038, 'en-us', 'logging', 'prefs', '', 'Logging', '2019-07-23 22:33:42'),
(3039, 'en-us', 'logs_expire', 'prefs', '', 'Logs expire after how many days?', '2019-07-23 22:33:42'),
(3040, 'en-us', 'markup_default', 'prefs', '', 'Default markup', '2019-07-23 22:33:42'),
(3041, 'en-us', 'max_url_len', 'prefs', '', 'Maximum URL length (in characters)', '2019-07-23 22:33:42'),
(3042, 'en-us', 'messy', 'prefs', '', '?=messy', '2019-07-23 22:33:42'),
(3043, 'en-us', 'never_display_email', 'prefs', '', 'Hide commenter’s email address?', '2019-07-23 22:33:42'),
(3044, 'en-us', 'no_preferences', 'prefs', '', 'No preferences available.', '2019-07-23 22:33:42'),
(3045, 'en-us', 'override_emailcharset', 'prefs', '', 'Use ISO-8859-1 encoding in emails (default is UTF-8)?', '2019-07-23 22:33:42'),
(3046, 'en-us', 'path_from_root', 'prefs', '', 'Sub-directory (if any)', '2019-07-23 22:33:42'),
(3047, 'en-us', 'permlink_format', 'prefs', '', 'Permalink title URL pattern', '2019-07-23 22:33:42'),
(3048, 'en-us', 'permlink_hyphenated', 'prefs', '', 'Hyphenated (title-like-this)', '2019-07-23 22:33:42'),
(3049, 'en-us', 'permlink_intercapped', 'prefs', '', 'Intercapped (TitleLikeThis)', '2019-07-23 22:33:42'),
(3050, 'en-us', 'plugin_cache_dir', 'prefs', '', 'Plugin cache directory path', '2019-07-23 22:33:42'),
(3051, 'en-us', 'prefs', 'prefs', '', 'Prefs', '2019-07-23 22:33:42'),
(3052, 'en-us', 'production_debug', 'prefs', '', 'Debugging', '2019-07-23 22:33:42'),
(3053, 'en-us', 'production_live', 'prefs', '', 'Live', '2019-07-23 22:33:42'),
(3054, 'en-us', 'production_status', 'prefs', '', 'Production status', '2019-07-23 22:33:42'),
(3055, 'en-us', 'production_test', 'prefs', '', 'Testing', '2019-07-23 22:33:42'),
(3056, 'en-us', 'publisher_email', 'prefs', '', 'Send login details from this email address', '2019-07-23 22:33:42'),
(3057, 'en-us', 'publish_expired_articles', 'prefs', '', 'Publish expired articles?', '2019-07-23 22:33:42'),
(3058, 'en-us', 'referrers_only', 'prefs', '', 'Referrers only', '2019-07-23 22:33:42'),
(3059, 'en-us', 'rss_how_many', 'prefs', '', 'How many articles should be included in feeds?', '2019-07-23 22:33:42'),
(3060, 'en-us', 'section_id_title', 'prefs', '', '/section/id/title', '2019-07-23 22:33:42'),
(3061, 'en-us', 'section_title', 'prefs', '', '/section/title', '2019-07-23 22:33:42'),
(3062, 'en-us', 'send_lastmod', 'prefs', '', 'Send \"Last-Modified\" header?', '2019-07-23 22:33:42'),
(3063, 'en-us', 'show_comment_count_in_feed', 'prefs', '', 'Show comment count in feeds?', '2019-07-23 22:33:42'),
(3064, 'en-us', 'smtp_from', 'prefs', '', 'SMTP envelope sender address', '2019-07-23 22:33:42'),
(3065, 'en-us', 'spam_blacklists', 'prefs', '', 'Spam blocklists (comma-separated)', '2019-07-23 22:33:42'),
(3066, 'en-us', 'syndicate_body_or_excerpt', 'prefs', '', 'Syndicate article excerpt <strong>only</strong>?', '2019-07-23 22:33:42'),
(3067, 'en-us', 'timeoffset', 'prefs', '', 'Time offset (hours)', '2019-07-23 22:33:42');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(3068, 'en-us', 'title_no_widow', 'prefs', '', 'Prevent widowed words in article titles?', '2019-07-23 22:33:42'),
(3069, 'en-us', 'title_only', 'prefs', '', '/title', '2019-07-23 22:33:42'),
(3070, 'en-us', 'txp_evaluate_functions', 'prefs', '', 'PHP functions enabled in <code>txp:evaluate</code>', '2019-07-23 22:33:42'),
(3071, 'en-us', 'tz_timezone', 'prefs', '', 'Time zone', '2019-07-23 22:33:42'),
(3072, 'en-us', 'up_to_date', 'prefs', '', 'Up-to-date', '2019-07-23 22:33:42'),
(3073, 'en-us', 'url_mode', 'prefs', '', 'URL mode', '2019-07-23 22:33:42'),
(3074, 'en-us', 'use_comments', 'prefs', '', 'Accept comments?', '2019-07-23 22:33:42'),
(3075, 'en-us', 'use_mail_on_feeds_id', 'prefs', '', 'Use email address to construct feed ID (default is site URL)?', '2019-07-23 22:33:42'),
(3076, 'en-us', 'use_plugins', 'prefs', '', 'Use plugins?', '2019-07-23 22:33:42'),
(3077, 'en-us', 'year_month_day_title', 'prefs', '', '/year/month/day/title', '2019-07-23 22:33:42'),
(3078, 'en-us', '403_forbidden', 'public', '', 'Forbidden.', '2019-07-23 22:33:42'),
(3079, 'en-us', '404_not_found', 'public', '', 'The requested resource was not found.', '2019-07-23 22:33:42'),
(3080, 'en-us', '410_gone', 'public', '', 'The requested resource is no longer available.', '2019-07-23 22:33:42'),
(3081, 'en-us', '500_internal_server_error', 'public', '', 'Internal server error.', '2019-07-23 22:33:42'),
(3082, 'en-us', 'articles_found', 'public', '', 'articles found', '2019-07-23 22:33:42'),
(3083, 'en-us', 'article_context', 'public', '', 'article', '2019-07-23 22:33:42'),
(3084, 'en-us', 'article_found', 'public', '', 'article found', '2019-07-23 22:33:42'),
(3085, 'en-us', 'atom_feed_title', 'public', '', 'Atom feed', '2019-07-23 22:33:42'),
(3086, 'en-us', 'a_few_seconds', 'public', '', 'a few seconds', '2019-07-23 22:33:42'),
(3087, 'en-us', 'comments_closed', 'public', '', 'Commenting is closed for this article.', '2019-07-23 22:33:42'),
(3088, 'en-us', 'comments_on', 'public', '', 'Comments on', '2019-07-23 22:33:42'),
(3089, 'en-us', 'comments_permlink', 'public', '', 'Permanent link', '2019-07-23 22:33:42'),
(3090, 'en-us', 'comment_comment', 'public', '', 'Comment', '2019-07-23 22:33:42'),
(3091, 'en-us', 'comment_duplicate', 'public', '', 'Duplicate comments are not accepted.', '2019-07-23 22:33:42'),
(3092, 'en-us', 'comment_email', 'public', '', 'Email', '2019-07-23 22:33:42'),
(3093, 'en-us', 'comment_email_required', 'public', '', 'Please enter a valid email address.', '2019-07-23 22:33:42'),
(3094, 'en-us', 'comment_message', 'public', '', 'Message', '2019-07-23 22:33:42'),
(3095, 'en-us', 'comment_moderated', 'public', '', 'Your comment is pending moderation. It will appear after it has been approved.', '2019-07-23 22:33:42'),
(3096, 'en-us', 'comment_name', 'public', '', 'Name', '2019-07-23 22:33:42'),
(3097, 'en-us', 'comment_name_required', 'public', '', 'Please enter your name.', '2019-07-23 22:33:42'),
(3098, 'en-us', 'comment_posted', 'public', '', 'Thank you for adding your comment.', '2019-07-23 22:33:42'),
(3099, 'en-us', 'comment_received', 'public', '', '{site} comment received: {title}', '2019-07-23 22:33:42'),
(3100, 'en-us', 'comment_recorded', 'public', '', 'A comment on your post “{title}” was received.', '2019-07-23 22:33:42'),
(3101, 'en-us', 'comment_required', 'public', '', 'Please enter a comment.', '2019-07-23 22:33:42'),
(3102, 'en-us', 'comment_web', 'public', '', 'Website', '2019-07-23 22:33:42'),
(3103, 'en-us', 'continue_reading', 'public', '', 'Continue reading', '2019-07-23 22:33:42'),
(3104, 'en-us', 'day', 'public', '', 'day', '2019-07-23 22:33:42'),
(3105, 'en-us', 'days', 'public', '', 'days', '2019-07-23 22:33:42'),
(3106, 'en-us', 'enter_comment_here', 'public', '', '<strong>Enter your comment below.</strong> Fields marked <b class=\"required\" title=\"Required\">*</b> are required. You must preview your comment before submitting it.', '2019-07-23 22:33:42'),
(3107, 'en-us', 'file_context', 'public', '', 'file', '2019-07-23 22:33:42'),
(3108, 'en-us', 'hour', 'public', '', 'hour', '2019-07-23 22:33:42'),
(3109, 'en-us', 'hours', 'public', '', 'hours', '2019-07-23 22:33:42'),
(3110, 'en-us', 'image_context', 'public', '', 'image', '2019-07-23 22:33:42'),
(3111, 'en-us', 'link_context', 'public', '', 'link', '2019-07-23 22:33:42'),
(3112, 'en-us', 'main_content', 'public', '', 'Main content', '2019-07-23 22:33:42'),
(3113, 'en-us', 'matching_search_request', 'public', '', 'matching your search request', '2019-07-23 22:33:42'),
(3114, 'en-us', 'minute', 'public', '', 'minute', '2019-07-23 22:33:42'),
(3115, 'en-us', 'minutes', 'public', '', 'minutes', '2019-07-23 22:33:42'),
(3116, 'en-us', 'nopopup', 'public', '', 'current window', '2019-07-23 22:33:42'),
(3117, 'en-us', 'no_comments', 'public', '', 'There are currently no comments on this article.', '2019-07-23 22:33:42'),
(3118, 'en-us', 'no_search_matches', 'public', '', 'Sorry, no results found matching your search request', '2019-07-23 22:33:42'),
(3119, 'en-us', 'page_nav', 'public', '', 'Page navigation', '2019-07-23 22:33:42'),
(3120, 'en-us', 'permanent_link', 'public', '', 'Permanent link to this article', '2019-07-23 22:33:42'),
(3121, 'en-us', 'popup', 'public', '', 'pop-up window', '2019-07-23 22:33:42'),
(3122, 'en-us', 'press_preview_then_submit', 'public', '', 'The following is a preview of what your comment will look like. When you are happy with your comment, use the <strong><a href=\"#txpCommentSubmit\" title=\"Go to the submit button\">submit</a></strong> button.', '2019-07-23 22:33:42'),
(3123, 'en-us', 'read_more', 'public', '', 'Read more', '2019-07-23 22:33:42'),
(3124, 'en-us', 'rss_feed_title', 'public', '', 'RSS feed', '2019-07-23 22:33:42'),
(3125, 'en-us', 'site_nav', 'public', '', 'Site navigation', '2019-07-23 22:33:42'),
(3126, 'en-us', 'textile_help', 'public', '', 'Textile help', '2019-07-23 22:33:42'),
(3127, 'en-us', 'too_common_search_term', 'public', '', 'It seems like you are looking for a very common search term, maybe try a more specific phrase than', '2019-07-23 22:33:42'),
(3128, 'en-us', 'week', 'public', '', 'week', '2019-07-23 22:33:42'),
(3129, 'en-us', 'weeks', 'public', '', 'weeks', '2019-07-23 22:33:42'),
(3130, 'en-us', 'your_ip_is_blacklisted_by', 'public', '', 'Your IP address has been blocklisted by', '2019-07-23 22:33:42'),
(3131, 'en-us', 'yyyy-mm', 'public', '', 'yyyy-mm', '2019-07-23 22:33:42'),
(3132, 'en-us', 'article_count', 'section', '', 'Articles in this section: {num}.', '2019-07-23 22:33:42'),
(3133, 'en-us', 'change_page_style', 'section', '', 'Change theme/page/style', '2019-07-23 22:33:42'),
(3134, 'en-us', 'create_section', 'section', '', 'New section', '2019-07-23 22:33:42'),
(3135, 'en-us', 'default_section_updated', 'section', '', 'Default section updated', '2019-07-23 22:33:42'),
(3136, 'en-us', 'default_write_section', 'section', '', 'Default publishing section', '2019-07-23 22:33:42'),
(3137, 'en-us', 'dev_theme', 'section', '', 'Development', '2019-07-23 22:33:42'),
(3138, 'en-us', 'dev_to_live', 'section', '', 'Reset to live', '2019-07-23 22:33:42'),
(3139, 'en-us', 'edit_default_section', 'section', '', 'Edit default section', '2019-07-23 22:33:42'),
(3140, 'en-us', 'edit_section', 'section', '', 'Edit section', '2019-07-23 22:33:42'),
(3141, 'en-us', 'include_in_search', 'section', '', 'Searchable?', '2019-07-23 22:33:42'),
(3142, 'en-us', 'live_theme', 'section', '', 'Live', '2019-07-23 22:33:42'),
(3143, 'en-us', 'live_to_dev', 'section', '', 'Deploy to live', '2019-07-23 22:33:42'),
(3144, 'en-us', 'search_sections', 'section', '', 'Search sections', '2019-07-23 22:33:42'),
(3145, 'en-us', 'section_created', 'section', '', 'Section <strong>{name}</strong> created.', '2019-07-23 22:33:42'),
(3146, 'en-us', 'section_delete_failure', 'section', '', 'Could not delete section(s), articles are currently assigned: {name}.', '2019-07-23 22:33:42'),
(3147, 'en-us', 'section_deleted', 'section', '', 'Sections deleted: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(3148, 'en-us', 'section_head', 'section', '', 'Site sections', '2019-07-23 22:33:42'),
(3149, 'en-us', 'section_longtitle', 'section', '', 'Section title', '2019-07-23 22:33:42'),
(3150, 'en-us', 'section_name', 'section', '', 'Section name', '2019-07-23 22:33:42'),
(3151, 'en-us', 'section_name_already_exists', 'section', '', 'Section <strong>{name}</strong> already exists.', '2019-07-23 22:33:42'),
(3152, 'en-us', 'section_save_failed', 'section', '', 'The section was not saved due to an error. Please try again.', '2019-07-23 22:33:42'),
(3153, 'en-us', 'section_updated', 'section', '', 'Sections updated: <strong>{name}</strong>.', '2019-07-23 22:33:42'),
(3154, 'en-us', 'switch_dev_live', 'section', '', 'Developer preview', '2019-07-23 22:33:42'),
(3155, 'en-us', 'syndicate', 'section', '', 'Syndicate articles?', '2019-07-23 22:33:42'),
(3156, 'en-us', 'uses_page', 'section', '', 'Uses page', '2019-07-23 22:33:42'),
(3157, 'en-us', 'uses_skin', 'section', '', 'Uses theme', '2019-07-23 22:33:42'),
(3158, 'en-us', 'uses_style', 'section', '', 'Uses style', '2019-07-23 22:33:42'),
(3159, 'en-us', 'create_skin', 'skin', '', 'New theme', '2019-07-23 22:33:42'),
(3160, 'en-us', 'css_creation_failed', 'skin', '', 'Styles creation failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3161, 'en-us', 'css_deletion_failed', 'skin', '', 'Styles deletion failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3162, 'en-us', 'css_files_deletion_failed', 'skin', '', 'Styles files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 22:33:42'),
(3163, 'en-us', 'css_import_failed', 'skin', '', 'Styles import failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3164, 'en-us', 'css_name_unsafe', 'skin', '', 'Not exportable styles names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:42'),
(3165, 'en-us', 'css_not_found', 'skin', '', 'No styles found for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3166, 'en-us', 'css_update_failed', 'skin', '', 'Styles update failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3167, 'en-us', 'edit_skin', 'skin', '', 'Edit theme', '2019-07-23 22:33:42'),
(3168, 'en-us', 'form_creation_failed', 'skin', '', 'Forms creation failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3169, 'en-us', 'form_deletion_failed', 'skin', '', 'Forms deletion failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3170, 'en-us', 'form_duplicate_failed', 'skin', '', 'Duplicated forms import failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3171, 'en-us', 'form_files_deletion_failed', 'skin', '', 'Forms files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 22:33:42'),
(3172, 'en-us', 'form_import_failed', 'skin', '', 'Forms import failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3173, 'en-us', 'form_name_unsafe', 'skin', '', 'Not exportable form names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:42'),
(3174, 'en-us', 'form_not_found', 'skin', '', 'No form found for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3175, 'en-us', 'form_subdir_error', 'skin', '', 'Form types error for: <strong>{list}</strong>. These forms are used as default by tags and should use defined types.', '2019-07-23 22:33:42'),
(3176, 'en-us', 'form_subdir_invalid', 'skin', '', 'Invalid form type for: <strong>{list}</strong>. Current valid types are: <code>article</code>, <code>category</code>, <code>comment</code>, <code>file</code>, <code>link</code>, <code>section</code> and <code>misc</code>. <code>misc</code> will be used by default.', '2019-07-23 22:33:42'),
(3177, 'en-us', 'form_update_failed', 'skin', '', 'Forms update failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3178, 'en-us', 'no_skin_recorded', 'skin', '', 'No themes recorded.', '2019-07-23 22:33:42'),
(3179, 'en-us', 'page_creation_failed', 'skin', '', 'Pages creation failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3180, 'en-us', 'page_deletion_failed', 'skin', '', 'Pages deletion failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3181, 'en-us', 'page_files_deletion_failed', 'skin', '', 'Pages files deletion failed for: <strong>{$list}</strong>.', '2019-07-23 22:33:42'),
(3182, 'en-us', 'page_import_failed', 'skin', '', 'Pages import failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3183, 'en-us', 'page_name_unsafe', 'skin', '', 'Not exportable page names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:42'),
(3184, 'en-us', 'page_not_found', 'skin', '', 'No page found for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3185, 'en-us', 'page_update_failed', 'skin', '', 'Pages update failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3186, 'en-us', 'search_skins', 'skin', '', 'Search themes', '2019-07-23 22:33:42'),
(3187, 'en-us', 'skin_already_exists', 'skin', '', 'Themes already exist in the database or in the themes directory and may cause conflicts: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3188, 'en-us', 'skin_author', 'skin', '', 'Theme author', '2019-07-23 22:33:42'),
(3189, 'en-us', 'skin_author_uri', 'skin', '', 'Theme website', '2019-07-23 22:33:42'),
(3190, 'en-us', 'skin_count_css', 'skin', '', 'Styles in this theme: {num}.', '2019-07-23 22:33:42'),
(3191, 'en-us', 'skin_count_form', 'skin', '', 'Forms in this theme: {num}.', '2019-07-23 22:33:42'),
(3192, 'en-us', 'skin_count_page', 'skin', '', 'Pages in this theme: {num}.', '2019-07-23 22:33:42'),
(3193, 'en-us', 'skin_count_section', 'skin', '', 'Sections used by this theme: {num}.', '2019-07-23 22:33:42'),
(3194, 'en-us', 'skin_created', 'skin', '', 'Themes created: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3195, 'en-us', 'skin_creation_failed', 'skin', '', 'Themes creation failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3196, 'en-us', 'skin_delete_entirely', 'skin', '', 'Delete theme templates from disk too', '2019-07-23 22:33:42'),
(3197, 'en-us', 'skin_delete_from_database', 'skin', '', 'Delete unused templates from database on import', '2019-07-23 22:33:42'),
(3198, 'en-us', 'skin_delete_from_disk', 'skin', '', 'Delete unused templates from disk on export', '2019-07-23 22:33:42'),
(3199, 'en-us', 'skin_deleted', 'skin', '', 'Themes deleted: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3200, 'en-us', 'skin_deletion_failed', 'skin', '', 'Theme(s) deletion failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3201, 'en-us', 'skin_description', 'skin', '', 'Theme description', '2019-07-23 22:33:42'),
(3202, 'en-us', 'skin_export_failed', 'skin', '', 'Themes export failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3203, 'en-us', 'skin_exported', 'skin', '', 'Themes exported: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3204, 'en-us', 'skin_files_deletion_failed', 'skin', '', 'Theme files deletion failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3205, 'en-us', 'skin_import_failed', 'skin', '', 'Themes import failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3206, 'en-us', 'skin_imported', 'skin', '', 'Themes imported: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3207, 'en-us', 'skin_in_use', 'skin', '', 'Deletion aborted for themes used by sections: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3208, 'en-us', 'skin_name', 'skin', '', 'Theme name', '2019-07-23 22:33:42'),
(3209, 'en-us', 'skin_name_unsafe', 'skin', '', 'Not exportable theme names for: <strong>{list}</strong>. Names must match the following regular expression: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:33:42'),
(3210, 'en-us', 'skin_not_found', 'skin', '', 'No theme data found for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3211, 'en-us', 'skin_related_sections_update_failed', 'skin', '', 'Themes related sections update failed for: <strong>{list}</strong>. Please visit the Sections panel to update manually.', '2019-07-23 22:33:42'),
(3212, 'en-us', 'skin_title', 'skin', '', 'Theme title', '2019-07-23 22:33:42'),
(3213, 'en-us', 'skin_unknown', 'skin', '', 'Unknown themes: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3214, 'en-us', 'skin_update_failed', 'skin', '', 'Themes update failed for: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3215, 'en-us', 'skin_updated', 'skin', '', 'Themes updated: <strong>{list}</strong>.', '2019-07-23 22:33:42'),
(3216, 'en-us', 'skin_version', 'skin', '', 'Theme version', '2019-07-23 22:33:42'),
(3217, 'en-us', 'active_class', 'tag', '', 'CSS class for active list item', '2019-07-23 22:33:42'),
(3218, 'en-us', 'allowoverride', 'tag', '', 'Allow form to be overridden?', '2019-07-23 22:33:42'),
(3219, 'en-us', 'breadcrumb_linked', 'tag', '', 'Link breadcrumbs?', '2019-07-23 22:33:42'),
(3220, 'en-us', 'breadcrumb_separator', 'tag', '', 'Breadcrumbs separator', '2019-07-23 22:33:42'),
(3221, 'en-us', 'break', 'tag', '', 'List break tag', '2019-07-23 22:33:42'),
(3222, 'en-us', 'breakclass', 'tag', '', 'CSS class for list break tag', '2019-07-23 22:33:42'),
(3223, 'en-us', 'build', 'tag', '', 'Build tag', '2019-07-23 22:33:42'),
(3224, 'en-us', 'button_text', 'tag', '', 'Button text', '2019-07-23 22:33:42'),
(3225, 'en-us', 'category_list_section', 'tag', '', 'Link to specific section?', '2019-07-23 22:33:42'),
(3226, 'en-us', 'category_tags', 'tag', '', 'Category', '2019-07-23 22:33:42'),
(3227, 'en-us', 'class', 'tag', '', 'CSS class', '2019-07-23 22:33:42'),
(3228, 'en-us', 'comments_form', 'tag', '', 'Comments form', '2019-07-23 22:33:42'),
(3229, 'en-us', 'comment_details', 'tag', '', 'Comment details', '2019-07-23 22:33:42'),
(3230, 'en-us', 'comment_form', 'tag', '', 'Comment form', '2019-07-23 22:33:42'),
(3231, 'en-us', 'comment_name_link', 'tag', '', 'Link to commenter’s email address/website?', '2019-07-23 22:33:42'),
(3232, 'en-us', 'decimals', 'tag', '', 'Display # numbers after the decimal point', '2019-07-23 22:33:42'),
(3233, 'en-us', 'default_title', 'tag', '', 'Text to use for default section link', '2019-07-23 22:33:42'),
(3234, 'en-us', 'depth', 'tag', '', 'Depth', '2019-07-23 22:33:42'),
(3235, 'en-us', 'escape', 'tag', '', 'Escape', '2019-07-23 22:33:42'),
(3236, 'en-us', 'exclude', 'tag', '', 'Exclude', '2019-07-23 22:33:42'),
(3237, 'en-us', 'filename', 'tag', '', 'Name', '2019-07-23 22:33:42'),
(3238, 'en-us', 'file_download_tags', 'tag', '', 'File downloads', '2019-07-23 22:33:42'),
(3239, 'en-us', 'flavor', 'tag', '', 'Syndication format', '2019-07-23 22:33:42'),
(3240, 'en-us', 'format', 'tag', '', 'Format', '2019-07-23 22:33:42'),
(3241, 'en-us', 'gmt', 'tag', '', 'Format according to <abbr title=\"Greenwich Mean Time\">GMT</abbr> time?', '2019-07-23 22:33:42'),
(3242, 'en-us', 'has_excerpt', 'tag', '', 'Has excerpt', '2019-07-23 22:33:42'),
(3243, 'en-us', 'hilight', 'tag', '', 'Highlight tag', '2019-07-23 22:33:42'),
(3244, 'en-us', 'hilight_limit', 'tag', '', 'Highlight how many instances?', '2019-07-23 22:33:42'),
(3245, 'en-us', 'html_id', 'tag', '', 'HTML <code>id</code> attribute', '2019-07-23 22:33:42'),
(3246, 'en-us', 'include_default', 'tag', '', 'Include default section?', '2019-07-23 22:33:42'),
(3247, 'en-us', 'inline_style', 'tag', '', 'Inline style (CSS)', '2019-07-23 22:33:42'),
(3248, 'en-us', 'input_size', 'tag', '', 'Input size', '2019-07-23 22:33:42'),
(3249, 'en-us', 'labeltag', 'tag', '', 'Label tag', '2019-07-23 22:33:42'),
(3250, 'en-us', 'limit', 'tag', '', 'Display how many?', '2019-07-23 22:33:42'),
(3251, 'en-us', 'linkclass', 'tag', '', 'CSS class for links', '2019-07-23 22:33:42'),
(3252, 'en-us', 'link_text', 'tag', '', 'Link text', '2019-07-23 22:33:42'),
(3253, 'en-us', 'link_to_this_author', 'tag', '', 'Link to a list of other articles by this author?', '2019-07-23 22:33:42'),
(3254, 'en-us', 'link_to_this_category', 'tag', '', 'Link to a list of other articles in this category?', '2019-07-23 22:33:42'),
(3255, 'en-us', 'link_to_this_section', 'tag', '', 'Link to a list of other articles in this section?', '2019-07-23 22:33:42'),
(3256, 'en-us', 'listform', 'tag', '', 'List form', '2019-07-23 22:33:42'),
(3257, 'en-us', 'match_type', 'tag', '', 'Match type', '2019-07-23 22:33:42'),
(3258, 'en-us', 'media', 'tag', '', 'HTML <code>media</code> attribute', '2019-07-23 22:33:42'),
(3259, 'en-us', 'msgcols', 'tag', '', 'Message textarea columns', '2019-07-23 22:33:42'),
(3260, 'en-us', 'msgrows', 'tag', '', 'Message textarea rows', '2019-07-23 22:33:42'),
(3261, 'en-us', 'no_categories_available', 'tag', '', 'No categories available.', '2019-07-23 22:33:42'),
(3262, 'en-us', 'no_forms_available', 'tag', '', 'No forms available.', '2019-07-23 22:33:42'),
(3263, 'en-us', 'no_sections_available', 'tag', '', 'No sections available.', '2019-07-23 22:33:42'),
(3264, 'en-us', 'offset', 'tag', '', 'Skip', '2019-07-23 22:33:42'),
(3265, 'en-us', 'page_article_hed', 'tag', '', 'Article output', '2019-07-23 22:33:42'),
(3266, 'en-us', 'page_article_nav_hed', 'tag', '', 'Article navigation', '2019-07-23 22:33:42'),
(3267, 'en-us', 'page_file_hed', 'tag', '', 'File downloads', '2019-07-23 22:33:42'),
(3268, 'en-us', 'page_misc_hed', 'tag', '', 'Miscellaneous', '2019-07-23 22:33:42'),
(3269, 'en-us', 'page_nav_hed', 'tag', '', 'Site navigation', '2019-07-23 22:33:42'),
(3270, 'en-us', 'page_xml_hed', 'tag', '', 'XML feeds', '2019-07-23 22:33:42'),
(3271, 'en-us', 'pageby', 'tag', '', 'Paginate by', '2019-07-23 22:33:42'),
(3272, 'en-us', 'pgonly', 'tag', '', 'Count articles, but show nothing', '2019-07-23 22:33:42'),
(3273, 'en-us', 'random', 'tag', '', 'Random', '2019-07-23 22:33:42'),
(3274, 'en-us', 'rel', 'tag', '', 'HTML <code>rel</code> attribute', '2019-07-23 22:33:42'),
(3275, 'en-us', 'searchall', 'tag', '', 'Search all sections?', '2019-07-23 22:33:42'),
(3276, 'en-us', 'searchsticky', 'tag', '', 'Search sticky articles?', '2019-07-23 22:33:42'),
(3277, 'en-us', 'search_input_form', 'tag', '', 'Search input', '2019-07-23 22:33:42'),
(3278, 'en-us', 'search_results_form', 'tag', '', 'Search results', '2019-07-23 22:33:42'),
(3279, 'en-us', 'section_tags', 'tag', '', 'Section', '2019-07-23 22:33:42'),
(3280, 'en-us', 'separator', 'tag', '', 'Separator', '2019-07-23 22:33:42'),
(3281, 'en-us', 'showalways', 'tag', '', 'Show always', '2019-07-23 22:33:42'),
(3282, 'en-us', 'showcount', 'tag', '', 'Show count?', '2019-07-23 22:33:42'),
(3283, 'en-us', 'size_format', 'tag', '', 'Size format', '2019-07-23 22:33:42'),
(3284, 'en-us', 'sort', 'tag', '', 'Sort by', '2019-07-23 22:33:42'),
(3285, 'en-us', 'tag', 'tag', '', 'Tag', '2019-07-23 22:33:42'),
(3286, 'en-us', 'tag_article', 'tag', '', 'Articles (single or list)', '2019-07-23 22:33:42'),
(3287, 'en-us', 'tag_article_custom', 'tag', '', 'Articles (custom list)', '2019-07-23 22:33:42'),
(3288, 'en-us', 'tag_article_image', 'tag', '', 'Article image', '2019-07-23 22:33:42'),
(3289, 'en-us', 'tag_author', 'tag', '', 'Author', '2019-07-23 22:33:42'),
(3290, 'en-us', 'tag_body', 'tag', '', 'Body', '2019-07-23 22:33:42'),
(3291, 'en-us', 'tag_body_excerpt', 'tag', '', 'Body excerpt', '2019-07-23 22:33:42'),
(3292, 'en-us', 'tag_breadcrumb', 'tag', '', 'Breadcrumb', '2019-07-23 22:33:42'),
(3293, 'en-us', 'tag_category', 'tag', '', 'Category', '2019-07-23 22:33:42'),
(3294, 'en-us', 'tag_category1', 'tag', '', 'Category 1', '2019-07-23 22:33:42'),
(3295, 'en-us', 'tag_category2', 'tag', '', 'Category 2', '2019-07-23 22:33:42'),
(3296, 'en-us', 'tag_category_list', 'tag', '', 'Category list', '2019-07-23 22:33:42'),
(3297, 'en-us', 'tag_comments', 'tag', '', 'Comments', '2019-07-23 22:33:42'),
(3298, 'en-us', 'tag_comments_form', 'tag', '', 'Comments form', '2019-07-23 22:33:42'),
(3299, 'en-us', 'tag_comments_invite', 'tag', '', 'Comments invite', '2019-07-23 22:33:42'),
(3300, 'en-us', 'tag_comments_preview', 'tag', '', 'Comments preview', '2019-07-23 22:33:42'),
(3301, 'en-us', 'tag_comment_anchor', 'tag', '', 'Comment anchor', '2019-07-23 22:33:42'),
(3302, 'en-us', 'tag_comment_email', 'tag', '', 'Comment email', '2019-07-23 22:33:42'),
(3303, 'en-us', 'tag_comment_email_input', 'tag', '', 'Comment email input', '2019-07-23 22:33:42'),
(3304, 'en-us', 'tag_comment_id', 'tag', '', 'Comment ID', '2019-07-23 22:33:42'),
(3305, 'en-us', 'tag_comment_message', 'tag', '', 'Comment message', '2019-07-23 22:33:42'),
(3306, 'en-us', 'tag_comment_message_input', 'tag', '', 'Comment message input', '2019-07-23 22:33:42'),
(3307, 'en-us', 'tag_comment_name', 'tag', '', 'Comment name', '2019-07-23 22:33:42'),
(3308, 'en-us', 'tag_comment_name_input', 'tag', '', 'Comment name input', '2019-07-23 22:33:42'),
(3309, 'en-us', 'tag_comment_permlink', 'tag', '', 'Comment permanent link', '2019-07-23 22:33:42'),
(3310, 'en-us', 'tag_comment_preview', 'tag', '', 'Comment preview button', '2019-07-23 22:33:42'),
(3311, 'en-us', 'tag_comment_remember', 'tag', '', 'Remember details checkbox', '2019-07-23 22:33:42'),
(3312, 'en-us', 'tag_comment_submit', 'tag', '', 'Comment submit button', '2019-07-23 22:33:42'),
(3313, 'en-us', 'tag_comment_time', 'tag', '', 'Comment time', '2019-07-23 22:33:42'),
(3314, 'en-us', 'tag_comment_web', 'tag', '', 'Comment website', '2019-07-23 22:33:42'),
(3315, 'en-us', 'tag_comment_web_input', 'tag', '', 'Comment website input', '2019-07-23 22:33:42'),
(3316, 'en-us', 'tag_css', 'tag', '', 'CSS link (<code>head</code>)', '2019-07-23 22:33:42'),
(3317, 'en-us', 'tag_email', 'tag', '', 'Email link (spam-resistant)', '2019-07-23 22:33:42'),
(3318, 'en-us', 'tag_excerpt', 'tag', '', 'Excerpt', '2019-07-23 22:33:42'),
(3319, 'en-us', 'tag_feed_link', 'tag', '', 'Articles feed link', '2019-07-23 22:33:42'),
(3320, 'en-us', 'tag_file_download', 'tag', '', 'File download', '2019-07-23 22:33:42'),
(3321, 'en-us', 'tag_file_download_category', 'tag', '', 'File category', '2019-07-23 22:33:42'),
(3322, 'en-us', 'tag_file_download_created', 'tag', '', 'File created time', '2019-07-23 22:33:42'),
(3323, 'en-us', 'tag_file_download_description', 'tag', '', 'File description', '2019-07-23 22:33:42'),
(3324, 'en-us', 'tag_file_download_downloads', 'tag', '', 'File download count', '2019-07-23 22:33:42'),
(3325, 'en-us', 'tag_file_download_id', 'tag', '', 'File ID#', '2019-07-23 22:33:42'),
(3326, 'en-us', 'tag_file_download_link', 'tag', '', 'File download link', '2019-07-23 22:33:42'),
(3327, 'en-us', 'tag_file_download_list', 'tag', '', 'File download list', '2019-07-23 22:33:42'),
(3328, 'en-us', 'tag_file_download_modified', 'tag', '', 'File modified time', '2019-07-23 22:33:42'),
(3329, 'en-us', 'tag_file_download_name', 'tag', '', 'File name', '2019-07-23 22:33:42'),
(3330, 'en-us', 'tag_file_download_size', 'tag', '', 'File size', '2019-07-23 22:33:42'),
(3331, 'en-us', 'tag_home', 'tag', '', 'Home', '2019-07-23 22:33:42'),
(3332, 'en-us', 'tag_if_category', 'tag', '', 'If category', '2019-07-23 22:33:42'),
(3333, 'en-us', 'tag_if_section', 'tag', '', 'If section', '2019-07-23 22:33:42'),
(3334, 'en-us', 'tag_image', 'tag', '', 'Image', '2019-07-23 22:33:42'),
(3335, 'en-us', 'tag_lang', 'tag', '', 'Language', '2019-07-23 22:33:42'),
(3336, 'en-us', 'tag_link', 'tag', '', 'Link', '2019-07-23 22:33:42'),
(3337, 'en-us', 'tag_linkdesctitle', 'tag', '', 'Link, title=description', '2019-07-23 22:33:42'),
(3338, 'en-us', 'tag_linklist', 'tag', '', 'Links list', '2019-07-23 22:33:42'),
(3339, 'en-us', 'tag_link_category', 'tag', '', 'Link category', '2019-07-23 22:33:42'),
(3340, 'en-us', 'tag_link_date', 'tag', '', 'Link date', '2019-07-23 22:33:42'),
(3341, 'en-us', 'tag_link_description', 'tag', '', 'Link description', '2019-07-23 22:33:42'),
(3342, 'en-us', 'tag_link_feed_link', 'tag', '', 'Links feed link', '2019-07-23 22:33:42'),
(3343, 'en-us', 'tag_link_name', 'tag', '', 'Link name', '2019-07-23 22:33:42'),
(3344, 'en-us', 'tag_link_text', 'tag', '', 'Link name', '2019-07-23 22:33:42'),
(3345, 'en-us', 'tag_link_to_home', 'tag', '', 'Homepage link', '2019-07-23 22:33:42'),
(3346, 'en-us', 'tag_link_to_next', 'tag', '', 'Next article link', '2019-07-23 22:33:42'),
(3347, 'en-us', 'tag_link_to_prev', 'tag', '', 'Previous article link', '2019-07-23 22:33:42'),
(3348, 'en-us', 'tag_name', 'tag', '', 'Commenter name', '2019-07-23 22:33:42'),
(3349, 'en-us', 'tag_newer', 'tag', '', 'Newer articles link', '2019-07-23 22:33:42'),
(3350, 'en-us', 'tag_next_article', 'tag', '', 'Next article', '2019-07-23 22:33:42'),
(3351, 'en-us', 'tag_next_title', 'tag', '', 'Next article title', '2019-07-23 22:33:42'),
(3352, 'en-us', 'tag_older', 'tag', '', 'Older articles link', '2019-07-23 22:33:42'),
(3353, 'en-us', 'tag_output_form', 'tag', '', 'Output form', '2019-07-23 22:33:42'),
(3354, 'en-us', 'tag_page_title', 'tag', '', 'Page title', '2019-07-23 22:33:42'),
(3355, 'en-us', 'tag_password_protect', 'tag', '', 'Password protection', '2019-07-23 22:33:42'),
(3356, 'en-us', 'tag_permlink', 'tag', '', 'Permanent link', '2019-07-23 22:33:42'),
(3357, 'en-us', 'tag_popup', 'tag', '', 'Pop-up list', '2019-07-23 22:33:42'),
(3358, 'en-us', 'tag_posted', 'tag', '', 'Posted', '2019-07-23 22:33:42'),
(3359, 'en-us', 'tag_prev_article', 'tag', '', 'Previous article', '2019-07-23 22:33:42'),
(3360, 'en-us', 'tag_prev_title', 'tag', '', 'Previous article title', '2019-07-23 22:33:42'),
(3361, 'en-us', 'tag_recent_articles', 'tag', '', 'Recent articles', '2019-07-23 22:33:42'),
(3362, 'en-us', 'tag_recent_comments', 'tag', '', 'Recent comments', '2019-07-23 22:33:42'),
(3363, 'en-us', 'tag_related_articles', 'tag', '', 'Related articles', '2019-07-23 22:33:42'),
(3364, 'en-us', 'tag_search_input', 'tag', '', 'Search input form', '2019-07-23 22:33:42'),
(3365, 'en-us', 'tag_search_result_date', 'tag', '', 'Search result date', '2019-07-23 22:33:42'),
(3366, 'en-us', 'tag_search_result_excerpt', 'tag', '', 'Search result excerpt', '2019-07-23 22:33:42'),
(3367, 'en-us', 'tag_search_result_title', 'tag', '', 'Search result title', '2019-07-23 22:33:42'),
(3368, 'en-us', 'tag_search_result_url', 'tag', '', 'Search result URL', '2019-07-23 22:33:42'),
(3369, 'en-us', 'tag_section', 'tag', '', 'Section', '2019-07-23 22:33:42'),
(3370, 'en-us', 'tag_section_list', 'tag', '', 'Section list', '2019-07-23 22:33:42'),
(3371, 'en-us', 'tag_site_name', 'tag', '', 'Site name', '2019-07-23 22:33:42'),
(3372, 'en-us', 'tag_site_slogan', 'tag', '', 'Site slogan', '2019-07-23 22:33:42'),
(3373, 'en-us', 'tag_title', 'tag', '', 'Title', '2019-07-23 22:33:42'),
(3374, 'en-us', 'textonly', 'tag', '', 'Text only?', '2019-07-23 22:33:42'),
(3375, 'en-us', 'this_section', 'tag', '', 'Link to current section?', '2019-07-23 22:33:42'),
(3376, 'en-us', 'time_any', 'tag', '', 'Any', '2019-07-23 22:33:42'),
(3377, 'en-us', 'time_format', 'tag', '', 'Time format string', '2019-07-23 22:33:42'),
(3378, 'en-us', 'time_future', 'tag', '', 'Future', '2019-07-23 22:33:42'),
(3379, 'en-us', 'time_past', 'tag', '', 'Past', '2019-07-23 22:33:42'),
(3380, 'en-us', 'title_separator', 'tag', '', 'Individual article and site name separator', '2019-07-23 22:33:42'),
(3381, 'en-us', 'tooltip', 'tag', '', 'Link tooltip', '2019-07-23 22:33:42'),
(3382, 'en-us', 'use_thumbnail', 'tag', '', 'Use thumbnail?', '2019-07-23 22:33:42'),
(3383, 'en-us', 'wraptag', 'tag', '', 'Wrap tag', '2019-07-23 22:33:42'),
(3384, 'en-us', 'rvm_css_dir', 'prefs', 'rvm_css', 'Stylesheet directory', '2019-07-23 22:34:19'),
(3385, 'en-us', 'arc_meta', 'admin', 'site', 'arc_meta', '2019-07-23 22:34:19'),
(3386, 'en-us', 'arc_meta_title', 'admin', 'site', 'Meta title', '2019-07-23 22:34:19'),
(3387, 'en-us', 'arc_meta_description', 'admin', 'site', 'Meta description', '2019-07-23 22:34:19'),
(3388, 'en-us', 'arc_meta_image', 'admin', 'site', 'Meta image', '2019-07-23 22:34:19'),
(3389, 'en-us', 'arc_meta_robots', 'admin', 'site', 'Meta robots', '2019-07-23 22:34:19'),
(3390, 'en-us', 'arc_meta_article_title', 'admin', 'site', 'Article page titles pattern', '2019-07-23 22:34:19'),
(3391, 'en-us', 'arc_meta_comment_title', 'admin', 'site', 'Comment page titles pattern', '2019-07-23 22:34:19'),
(3392, 'en-us', 'arc_meta_search_title', 'admin', 'site', 'Search page titles pattern', '2019-07-23 22:34:19'),
(3393, 'en-us', 'arc_meta_category_title', 'admin', 'site', 'Category titles pattern', '2019-07-23 22:34:19'),
(3394, 'en-us', 'arc_meta_section_title', 'admin', 'site', 'Section titles pattern', '2019-07-23 22:34:19'),
(3395, 'en-us', 'arc_meta_section_category_title', 'admin', 'site', 'Section category titles pattern', '2019-07-23 22:34:19'),
(3396, 'en-us', 'arc_meta_homepage_title', 'admin', 'site', 'Homepage title pattern', '2019-07-23 22:34:19'),
(3397, 'en-us', 'arc_meta_section_tab', 'admin', 'site', 'Additional panel location for sections meta data edit', '2019-07-23 22:34:19'),
(3398, 'en-us', 'arc_meta_section_tab_content', 'admin', 'site', 'Content', '2019-07-23 22:34:19'),
(3399, 'en-us', 'arc_meta_section_tab_extensions', 'admin', 'site', 'Extensions', '2019-07-23 22:34:19'),
(3400, 'en-us', 'arc_meta_section_tab_hidden', 'admin', 'site', 'Hidden', '2019-07-23 22:34:19'),
(3401, 'en-us', 'ied_plugin_any', 'ied_plugin', 'site', 'Any', '2019-07-25 01:42:31'),
(3402, 'en-us', 'ied_plugin_auto_enable', 'ied_plugin', 'site', 'Auto-enable plugins on install', '2019-07-25 01:42:31'),
(3403, 'en-us', 'ied_plugin_cache_not_set', 'ied_plugin', 'site', 'Advanced pref \"plugin cache directory\" not defined', '2019-07-25 01:42:31'),
(3404, 'en-us', 'ied_plugin_check_type', 'ied_plugin', 'site', 'Check your plugin type!', '2019-07-25 01:42:31'),
(3405, 'en-us', 'ied_plugin_choose_file', 'ied_plugin', 'site', 'Please choose a file first', '2019-07-25 01:42:31'),
(3406, 'en-us', 'ied_plugin_code_dist', 'ied_plugin', 'site', 'Plugin code for distribution', '2019-07-25 01:42:31'),
(3407, 'en-us', 'ied_plugin_code_legend', 'ied_plugin', 'site', 'Plugin code', '2019-07-25 01:42:31'),
(3408, 'en-us', 'ied_plugin_code_save', 'ied_plugin', 'site', 'Save code', '2019-07-25 01:42:31'),
(3409, 'en-us', 'ied_plugin_code_saved', 'ied_plugin', 'site', 'Code saved', '2019-07-25 01:42:31'),
(3410, 'en-us', 'ied_plugin_code_saved_fail', 'ied_plugin', 'site', 'Code saving failed', '2019-07-25 01:42:31'),
(3411, 'en-us', 'ied_plugin_compress', 'ied_plugin', 'site', 'Zip', '2019-07-25 01:42:31'),
(3412, 'en-us', 'ied_plugin_cpanel_legend', 'ied_plugin', 'site', 'Installation', '2019-07-25 01:42:31'),
(3413, 'en-us', 'ied_plugin_create_new', 'ied_plugin', 'site', 'Create new plugin', '2019-07-25 01:42:31'),
(3414, 'en-us', 'ied_plugin_dbplugs_legend', 'ied_plugin', 'site', 'Plugins in database', '2019-07-25 01:42:31'),
(3415, 'en-us', 'ied_plugin_docs', 'ied_plugin', 'site', 'Docs', '2019-07-25 01:42:31'),
(3416, 'en-us', 'ied_plugin_docs_legend', 'ied_plugin', 'site', 'Plugin help', '2019-07-25 01:42:31'),
(3417, 'en-us', 'ied_plugin_edit', 'ied_plugin', 'site', 'Edit: {name} v{version}', '2019-07-25 01:42:31'),
(3418, 'en-us', 'ied_plugin_editing', 'ied_plugin', 'site', 'Editing {name} in Plugin Composer', '2019-07-25 01:42:31'),
(3419, 'en-us', 'ied_plugin_editor', 'ied_plugin', 'site', 'Plugin editor', '2019-07-25 01:42:31'),
(3420, 'en-us', 'ied_plugin_editor_options', 'ied_plugin', 'site', 'Plugin editor configuration options', '2019-07-25 01:42:31'),
(3421, 'en-us', 'ied_plugin_editor_path', 'ied_plugin', 'site', 'Plugin editor URL', '2019-07-25 01:42:31'),
(3422, 'en-us', 'ied_plugin_editor_width', 'ied_plugin', 'site', 'Plugin editor width', '2019-07-25 01:42:31'),
(3423, 'en-us', 'ied_plugin_edit_new', 'ied_plugin', 'site', 'Edit your new plugin', '2019-07-25 01:42:31'),
(3424, 'en-us', 'ied_plugin_enable', 'ied_plugin', 'site', 'Enabled', '2019-07-25 01:42:31'),
(3425, 'en-us', 'ied_plugin_export', 'ied_plugin', 'site', 'Export as {name}', '2019-07-25 01:42:31'),
(3426, 'en-us', 'ied_plugin_export_textpack', 'ied_plugin', 'site', 'Export textpack(s)', '2019-07-25 01:42:31'),
(3427, 'en-us', 'ied_plugin_export_zip', 'ied_plugin', 'site', 'Export as {name} (compressed)', '2019-07-25 01:42:31'),
(3428, 'en-us', 'ied_plugin_flags', 'ied_plugin', 'site', 'Flags', '2019-07-25 01:42:31'),
(3429, 'en-us', 'ied_plugin_flag_has_prefs', 'ied_plugin', 'site', 'Has prefs', '2019-07-25 01:42:31'),
(3430, 'en-us', 'ied_plugin_flag_lifecycle_notify', 'ied_plugin', 'site', 'Event notify', '2019-07-25 01:42:31'),
(3431, 'en-us', 'ied_plugin_fn_not_exist', 'ied_plugin', 'site', '(the function is probably javascript or mangled PHP)', '2019-07-25 01:42:31'),
(3432, 'en-us', 'ied_plugin_help_editor', 'ied_plugin', 'site', 'Help editor', '2019-07-25 01:42:31'),
(3433, 'en-us', 'ied_plugin_help_editor_options', 'ied_plugin', 'site', 'Help editor configuration options', '2019-07-25 01:42:31'),
(3434, 'en-us', 'ied_plugin_help_editor_path', 'ied_plugin', 'site', 'Help editor URL', '2019-07-25 01:42:31'),
(3435, 'en-us', 'ied_plugin_help_not_available', 'ied_plugin', 'site', 'Plugin help not available', '2019-07-25 01:42:31'),
(3436, 'en-us', 'ied_plugin_if_el_dist', 'ied_plugin', 'site', 'Code for distribution', '2019-07-25 01:42:31'),
(3437, 'en-us', 'ied_plugin_if_el_style', 'ied_plugin', 'site', 'Help style block', '2019-07-25 01:42:31'),
(3438, 'en-us', 'ied_plugin_if_settings', 'ied_plugin', 'site', 'Interface settings', '2019-07-25 01:42:31'),
(3439, 'en-us', 'ied_plugin_install_txt', 'ied_plugin', 'site', 'Install from .txt file', '2019-07-25 01:42:31'),
(3440, 'en-us', 'ied_plugin_interface_elems', 'ied_plugin', 'site', 'Optional interface elements', '2019-07-25 01:42:31'),
(3441, 'en-us', 'ied_plugin_jump_to_line', 'ied_plugin', 'site', 'Jump to line:', '2019-07-25 01:42:31'),
(3442, 'en-us', 'ied_plugin_langs_all', 'ied_plugin', 'site', 'All available', '2019-07-25 01:42:31'),
(3443, 'en-us', 'ied_plugin_langs_installed', 'ied_plugin', 'site', 'Only installed', '2019-07-25 01:42:31'),
(3444, 'en-us', 'ied_plugin_lang_choose', 'ied_plugin', 'site', 'Textpack language list', '2019-07-25 01:42:31'),
(3445, 'en-us', 'ied_plugin_lang_default', 'ied_plugin', 'site', 'Default textpack language', '2019-07-25 01:42:31'),
(3446, 'en-us', 'ied_plugin_lbl_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-23 22:34:19'),
(3447, 'en-us', 'ied_plugin_lbl_lc_delete', 'ied_plugin', 'site', 'Delete', '2019-07-25 01:42:31'),
(3448, 'en-us', 'ied_plugin_lbl_lc_disable', 'ied_plugin', 'site', 'Disable', '2019-07-25 01:42:31'),
(3449, 'en-us', 'ied_plugin_lbl_lc_enable', 'ied_plugin', 'site', 'Enable', '2019-07-25 01:42:31'),
(3450, 'en-us', 'ied_plugin_lbl_lc_install', 'ied_plugin', 'site', 'Install', '2019-07-25 01:42:31'),
(3451, 'en-us', 'ied_plugin_lbl_op_code_first', 'ied_plugin', 'site', 'Code first', '2019-07-25 01:42:31'),
(3452, 'en-us', 'ied_plugin_lbl_op_help_first', 'ied_plugin', 'site', 'Help first', '2019-07-25 01:42:31'),
(3453, 'en-us', 'ied_plugin_lbl_setup', 'ied_plugin', 'site', 'Plugin composer setup', '2019-07-25 01:42:31'),
(3454, 'en-us', 'ied_plugin_lifecycle_options', 'ied_plugin', 'site', 'Perform lifecycle actions on', '2019-07-25 01:42:31'),
(3455, 'en-us', 'ied_plugin_load', 'ied_plugin', 'site', 'Load', '2019-07-25 01:42:31'),
(3456, 'en-us', 'ied_plugin_load_order', 'ied_plugin', 'site', 'Load order', '2019-07-25 01:42:31'),
(3457, 'en-us', 'ied_plugin_load_order_help', 'ied_plugin', 'site', '(1=first > 5=normal > 9=last)', '2019-07-25 01:42:31'),
(3458, 'en-us', 'ied_plugin_meta_legend', 'ied_plugin', 'site', 'Meta information', '2019-07-25 01:42:31'),
(3459, 'en-us', 'ied_plugin_msgpop_lbl', 'ied_plugin', 'site', 'phpdoc block', '2019-07-25 01:42:31'),
(3460, 'en-us', 'ied_plugin_name_first', 'ied_plugin', 'site', 'Please name the plugin before creating it', '2019-07-25 01:42:31'),
(3461, 'en-us', 'ied_plugin_output_order', 'ied_plugin', 'site', 'PHP export order', '2019-07-25 01:42:31'),
(3462, 'en-us', 'ied_plugin_output_sfile', 'ied_plugin', 'site', 'Export plugin filename format', '2019-07-25 01:42:31'),
(3463, 'en-us', 'ied_plugin_output_sfilec', 'ied_plugin', 'site', 'Export compressed filename format', '2019-07-25 01:42:31'),
(3464, 'en-us', 'ied_plugin_output_sfilep', 'ied_plugin', 'site', 'Plugin template filename format', '2019-07-25 01:42:31'),
(3465, 'en-us', 'ied_plugin_output_sfilet', 'ied_plugin', 'site', 'Textpack filename format', '2019-07-25 01:42:31'),
(3466, 'en-us', 'ied_plugin_output_tmpcache', 'ied_plugin', 'site', 'Cache Textiled help path', '2019-07-25 01:42:31'),
(3467, 'en-us', 'ied_plugin_php_doc', 'ied_plugin', 'site', 'Make phpdoc', '2019-07-25 01:42:31'),
(3468, 'en-us', 'ied_plugin_prefs', 'ied_plugin', 'site', 'Preferences', '2019-07-25 01:42:31'),
(3469, 'en-us', 'ied_plugin_prefs_deleted', 'ied_plugin', 'site', 'Preferences deleted', '2019-07-25 01:42:31'),
(3470, 'en-us', 'ied_plugin_renamed', 'ied_plugin', 'site', '(renamed)', '2019-07-25 01:42:31'),
(3471, 'en-us', 'ied_plugin_rename_failed', 'ied_plugin', 'site', '(rename failed)', '2019-07-25 01:42:31'),
(3472, 'en-us', 'ied_plugin_rename_file', 'ied_plugin', 'site', 'Rename file', '2019-07-25 01:42:31'),
(3473, 'en-us', 'ied_plugin_restored', 'ied_plugin', 'site', 'Plugin {name} restored.', '2019-07-25 01:42:31'),
(3474, 'en-us', 'ied_plugin_restore_help', 'ied_plugin', 'site', 'Roll back the plugin code to the most recent restore point', '2019-07-25 01:42:31'),
(3475, 'en-us', 'ied_plugin_restore_point', 'ied_plugin', 'site', 'Restore point', '2019-07-25 01:42:31'),
(3476, 'en-us', 'ied_plugin_restore_verify', 'ied_plugin', 'site', 'Are you sure you want to rollback the {name} source code to its last restore point?', '2019-07-25 01:42:31'),
(3477, 'en-us', 'ied_plugin_run_from', 'ied_plugin', 'site', 'Les Plugins sont chargés depuis le cache', '2019-07-23 22:34:19'),
(3478, 'en-us', 'ied_plugin_run_install', 'ied_plugin', 'site', 'Perform post-install actions', '2019-07-25 01:42:31'),
(3479, 'en-us', 'ied_plugin_same', 'ied_plugin', 'site', 'Same', '2019-07-25 01:42:31'),
(3480, 'en-us', 'ied_plugin_save_as', 'ied_plugin', 'site', 'Save as {name}', '2019-07-25 01:42:31'),
(3481, 'en-us', 'ied_plugin_setup', 'ied_plugin', 'site', 'Setup', '2019-07-25 01:42:31'),
(3482, 'en-us', 'ied_plugin_syntax_check', 'ied_plugin', 'site', 'Syntax check on code save', '2019-07-25 01:42:31'),
(3483, 'en-us', 'ied_plugin_syntax_err', 'ied_plugin', 'site', 'Syntax error', '2019-07-25 01:42:31'),
(3484, 'en-us', 'ied_plugin_tp_legend', 'ied_plugin', 'site', 'Chaînes du Textpack', '2019-07-23 22:34:19'),
(3485, 'en-us', 'ied_plugin_tp_populate', 'ied_plugin', 'site', 'Load strings from function', '2019-07-25 01:42:31'),
(3486, 'en-us', 'ied_plugin_tp_prefix', 'ied_plugin', 'site', 'Textpack prefix', '2019-07-25 01:42:31'),
(3487, 'en-us', 'ied_plugin_type', 'ied_plugin', 'site', 'Plugin type', '2019-07-25 01:42:31'),
(3488, 'en-us', 'ied_plugin_type_0', 'ied_plugin', 'site', 'Public', '2019-07-25 01:42:31'),
(3489, 'en-us', 'ied_plugin_type_1', 'ied_plugin', 'site', 'Admin + Public', '2019-07-25 01:42:31'),
(3490, 'en-us', 'ied_plugin_type_2', 'ied_plugin', 'site', 'Library', '2019-07-25 01:42:31'),
(3491, 'en-us', 'ied_plugin_type_3', 'ied_plugin', 'site', 'Admin only', '2019-07-25 01:42:31'),
(3492, 'en-us', 'ied_plugin_type_4', 'ied_plugin', 'site', 'Admin (+AJAX)', '2019-07-25 01:42:31'),
(3493, 'en-us', 'ied_plugin_type_5', 'ied_plugin', 'site', 'Admin + Public (+AJAX)', '2019-07-25 01:42:31'),
(3494, 'en-us', 'ied_plugin_updated', 'ied_plugin', 'site', 'Plugin {name} updated', '2019-07-25 01:42:31'),
(3495, 'en-us', 'ied_plugin_uploaded', 'ied_plugin', 'site', 'Plugin {name} uploaded', '2019-07-25 01:42:31'),
(3496, 'en-us', 'ied_plugin_upload_php', 'ied_plugin', 'site', 'Upload plugin', '2019-07-25 01:42:31'),
(3497, 'en-us', 'ied_plugin_utils_legend', 'ied_plugin', 'site', 'Distribution', '2019-07-25 01:42:31'),
(3498, 'en-us', 'ied_plugin_view_help', 'ied_plugin', 'site', 'Help: {name}', '2019-07-25 01:42:31'),
(3499, 'de', 'active', 'common', '', 'Aktiv', '2019-07-23 22:34:19'),
(3500, 'de', 'add', 'common', '', 'Hinzufügen', '2019-07-23 22:34:19'),
(3501, 'de', 'admin', 'common', '', 'Administrator', '2019-07-23 22:34:19'),
(3502, 'de', 'ago', 'common', '', 'vor {since}', '2019-07-23 22:34:19'),
(3503, 'de', 'all', 'common', '', 'Alle', '2019-07-23 22:34:19'),
(3504, 'de', 'are_you_sure', 'common', '', 'Sind Sie sicher?', '2019-07-23 22:34:19'),
(3505, 'de', 'article', 'common', '', 'Artikel', '2019-07-23 22:34:19'),
(3506, 'de', 'articles', 'common', '', 'Artikel', '2019-07-23 22:34:19'),
(3507, 'de', 'article_image', 'common', '', 'Artikelbild', '2019-07-23 22:34:19'),
(3508, 'de', 'any', 'common', '', 'Irgendeine', '2019-07-23 22:34:19'),
(3509, 'de', 'ascending', 'common', '', 'Aufsteigend', '2019-07-23 22:34:19'),
(3510, 'de', 'auth_required', 'common', '', 'Benötigt Berechtigung.', '2019-07-23 22:34:19'),
(3511, 'de', 'author', 'common', '', 'Autor', '2019-07-23 22:34:19'),
(3512, 'de', 'authors', 'common', '', 'Liste der Autoren', '2019-07-23 22:34:19'),
(3513, 'de', 'back_to_login', 'common', '', 'Zurück zur Anmeldung', '2019-07-23 22:34:19'),
(3514, 'de', 'back_to_top', 'common', '', 'Nach oben', '2019-07-23 22:34:19'),
(3515, 'de', 'bad_cookie', 'common', '', 'Ungültiges Cookie. Bitte geben Sie Ihre Anmeldeinformationen ein.', '2019-07-23 22:34:19'),
(3516, 'de', 'bad_login', 'common', '', 'Mit diesem Benutzernamen/Passwort konnte nicht eingeloggt werden.', '2019-07-23 22:34:19'),
(3517, 'de', 'browse', 'common', '', 'Blättern', '2019-07-23 22:34:19'),
(3518, 'de', 'cancel', 'common', '', 'Abbrechen', '2019-07-23 22:34:19'),
(3519, 'de', 'caption', 'common', '', 'Bildunterschrift', '2019-07-23 22:34:19'),
(3520, 'de', 'category', 'common', '', 'Kategorie', '2019-07-23 22:34:19'),
(3521, 'de', 'categories', 'common', '', 'Kategorien', '2019-07-23 22:34:19'),
(3522, 'de', 'categorize', 'common', '', 'Kategorisieren', '2019-07-23 22:34:19'),
(3523, 'de', 'category1', 'common', '', 'Kategorie 1', '2019-07-23 22:34:19'),
(3524, 'de', 'category2', 'common', '', 'Kategorie 2', '2019-07-23 22:34:19'),
(3525, 'de', 'choose', 'common', '', 'Wähle…', '2019-07-23 22:34:19'),
(3526, 'de', 'close', 'common', '', 'Schließen', '2019-07-23 22:34:19'),
(3527, 'de', 'code', 'common', '', 'Code', '2019-07-23 22:34:19'),
(3528, 'de', 'comment', 'common', '', 'Kommentar', '2019-07-23 22:34:19'),
(3529, 'de', 'comment_invitation', 'common', '', 'Kommentare einladen mit:', '2019-07-23 22:34:19'),
(3530, 'de', 'comments', 'common', '', 'Kommentare', '2019-07-23 22:34:19'),
(3531, 'de', 'comments_expired', 'common', '', 'Die Frist für Kommentare zu diesem Artikel ist abgelaufen.', '2019-07-23 22:34:19'),
(3532, 'de', 'confirm_delete_popup', 'common', '', 'Wirklich löschen?', '2019-07-23 22:34:19'),
(3533, 'de', 'contact', 'common', '', 'Kontakt', '2019-07-23 22:34:19'),
(3534, 'de', 'cookies_must_be_enabled', 'common', '', 'Für die Benutzung von Textpattern muss der Browser Cookies akzeptieren.', '2019-07-23 22:34:19'),
(3535, 'de', 'copy', 'common', '', 'Kopieren', '2019-07-23 22:34:19'),
(3536, 'de', 'could_not_log_in', 'common', '', 'Mit dieser Namens-/Passwortkombination war keine Anmeldung möglich.', '2019-07-23 22:34:19'),
(3537, 'de', 'create', 'common', '', 'Erstellen', '2019-07-23 22:34:19'),
(3538, 'de', 'css', 'common', '', 'CSS', '2019-07-23 22:34:19'),
(3539, 'de', 'date', 'common', '', 'Datum', '2019-07-23 22:34:19'),
(3540, 'de', 'date_added', 'common', '', 'Erstellt am', '2019-07-23 22:34:19'),
(3541, 'de', 'dateformat', 'common', '', 'Datumsformat', '2019-07-23 22:34:19'),
(3542, 'de', 'default', 'common', '', 'Standard', '2019-07-23 22:34:19'),
(3543, 'de', 'delete', 'common', '', 'Löschen', '2019-07-23 22:34:19'),
(3544, 'de', 'delete_selected', 'common', '', 'Auswahl löschen', '2019-07-23 22:34:19'),
(3545, 'de', 'descending', 'common', '', 'Absteigend', '2019-07-23 22:34:19'),
(3546, 'de', 'description', 'common', '', 'Beschreibung', '2019-07-23 22:34:19'),
(3547, 'de', 'documentation', 'common', '', 'Dokumentation', '2019-07-23 22:34:19'),
(3548, 'de', 'download', 'common', '', 'Herunterladen', '2019-07-23 22:34:19'),
(3549, 'de', 'download_count', 'common', '', 'Download-Zähler', '2019-07-23 22:34:19'),
(3550, 'de', 'downloads', 'common', '', 'Downloads', '2019-07-23 22:34:19'),
(3551, 'de', 'duplicate', 'common', '', 'Kopie erstellen', '2019-07-23 22:34:19'),
(3552, 'de', 'edit', 'common', '', 'Bearbeiten', '2019-07-23 22:34:19'),
(3553, 'de', 'email', 'common', '', 'E-Mail', '2019-07-23 22:34:19'),
(3554, 'de', 'email_address', 'common', '', 'E-Mail', '2019-07-23 22:34:19'),
(3555, 'de', 'exact', 'common', '', 'Genau', '2019-07-23 22:34:19'),
(3556, 'de', 'excerpt', 'common', '', 'Exzerpt', '2019-07-23 22:34:19'),
(3557, 'de', 'expired', 'common', '', 'Abgelaufen', '2019-07-23 22:34:19'),
(3558, 'de', 'expires', 'common', '', 'Ablaufdatum', '2019-07-23 22:34:19'),
(3559, 'de', 'expire_date', 'common', '', 'Ablaufdatum', '2019-07-23 22:34:19'),
(3560, 'de', 'expire_time', 'common', '', 'Ablaufzeit', '2019-07-23 22:34:19'),
(3561, 'de', 'export', 'common', '', 'Exportieren', '2019-07-23 22:34:19'),
(3562, 'de', 'extension', 'common', '', 'Erweiterung', '2019-07-23 22:34:19'),
(3563, 'de', 'extensions', 'common', '', 'Erweiterung', '2019-07-23 22:34:19'),
(3564, 'de', 'file', 'common', '', 'Datei', '2019-07-23 22:34:19'),
(3565, 'de', 'file_download', 'common', '', 'Datei-Download', '2019-07-23 22:34:19'),
(3566, 'de', 'file_size', 'common', '', 'Dateigröße', '2019-07-23 22:34:19'),
(3567, 'de', 'forget', 'common', '', 'Vergessen', '2019-07-23 22:34:19'),
(3568, 'de', 'form', 'common', '', 'Baustein', '2019-07-23 22:34:19'),
(3569, 'de', 'form_submission_error', 'common', '', 'Das Formular konnte leider nicht verarbeitet werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(3570, 'de', 'forms', 'common', '', 'Bausteine', '2019-07-23 22:34:19'),
(3571, 'de', 'general_error', 'common', '', 'Allgemeiner Fehler', '2019-07-23 22:34:19'),
(3572, 'de', 'go', 'common', '', 'Los', '2019-07-23 22:34:19'),
(3573, 'de', 'go_back', 'common', '', 'Zurück', '2019-07-23 22:34:19'),
(3574, 'de', 'go_content', 'common', '', 'Gehe zum Inhalt', '2019-07-23 22:34:19'),
(3575, 'de', 'go_nav', 'common', '', 'Gehe zur Navigation', '2019-07-23 22:34:19'),
(3576, 'de', 'go_search', 'common', '', 'Gehe zur Suche', '2019-07-23 22:34:19'),
(3577, 'de', 'go_to', 'common', '', 'Gehe zu', '2019-07-23 22:34:19'),
(3578, 'de', 'go_txp_com', 'common', '', 'Die Website von Textpattern besuchen', '2019-07-23 22:34:19');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(3579, 'de', 'height', 'common', '', 'Höhe', '2019-07-23 22:34:19'),
(3580, 'de', 'help', 'common', '', 'Hilfe', '2019-07-23 22:34:19'),
(3581, 'de', 'hidden', 'common', '', 'Versteckt', '2019-07-23 22:34:19'),
(3582, 'de', 'home', 'common', '', 'Start', '2019-07-23 22:34:19'),
(3583, 'de', 'host', 'common', '', 'Host', '2019-07-23 22:34:19'),
(3584, 'de', 'id', 'common', '', 'ID#', '2019-07-23 22:34:19'),
(3585, 'de', 'image', 'common', '', 'Bild', '2019-07-23 22:34:19'),
(3586, 'de', 'images', 'common', '', 'Bilder', '2019-07-23 22:34:19'),
(3587, 'de', 'import', 'common', '', 'Importieren', '2019-07-23 22:34:19'),
(3588, 'de', 'input_day', 'common', '', 'Tag', '2019-07-23 22:34:19'),
(3589, 'de', 'input_hour', 'common', '', 'Stunde', '2019-07-23 22:34:19'),
(3590, 'de', 'input_minute', 'common', '', 'Minute', '2019-07-23 22:34:19'),
(3591, 'de', 'input_month', 'common', '', 'Monat', '2019-07-23 22:34:19'),
(3592, 'de', 'input_second', 'common', '', 'Sekunde', '2019-07-23 22:34:19'),
(3593, 'de', 'input_year', 'common', '', 'Jahr', '2019-07-23 22:34:19'),
(3594, 'de', 'install', 'common', '', 'Installation', '2019-07-23 22:34:19'),
(3595, 'de', 'internal_error', 'common', '', 'Interner Fehler', '2019-07-23 22:34:19'),
(3596, 'de', 'keywords', 'common', '', 'Stichworte', '2019-07-23 22:34:19'),
(3597, 'de', 'label', 'common', '', 'Bezeichnung', '2019-07-23 22:34:19'),
(3598, 'de', 'last_modification', 'common', '', 'Letzte Änderung', '2019-07-23 22:34:19'),
(3599, 'de', 'last_modified', 'common', '', 'Zuletzt geändert', '2019-07-23 22:34:19'),
(3600, 'de', 'link', 'common', '', 'Link', '2019-07-23 22:34:19'),
(3601, 'de', 'links', 'common', '', 'Links', '2019-07-23 22:34:19'),
(3602, 'de', 'list', 'common', '', 'Liste', '2019-07-23 22:34:19'),
(3603, 'de', 'list_articles', 'common', '', 'Artikel auflisten', '2019-07-23 22:34:19'),
(3604, 'de', 'list_categories', 'common', '', 'Kategorien auflisten', '2019-07-23 22:34:19'),
(3605, 'de', 'list_links', 'common', '', 'Links auflisten', '2019-07-23 22:34:19'),
(3606, 'de', 'list_options', 'common', '', 'Listenoptionen', '2019-07-23 22:34:19'),
(3607, 'de', 'logged_in_as', 'common', '', 'Angemeldet als', '2019-07-23 22:34:19'),
(3608, 'de', 'login', 'common', '', 'Login', '2019-07-23 22:34:19'),
(3609, 'de', 'login_to_textpattern', 'common', '', 'Textpattern-Anmeldung', '2019-07-23 22:34:19'),
(3610, 'de', 'logout', 'common', '', 'Abmelden', '2019-07-23 22:34:19'),
(3611, 'de', 'log_in_button', 'common', '', 'Anmelden', '2019-07-23 22:34:19'),
(3612, 'de', 'manual', 'common', '', 'Handbuch', '2019-07-23 22:34:19'),
(3613, 'de', 'message', 'common', '', 'Nachricht', '2019-07-23 22:34:19'),
(3614, 'de', 'method', 'common', '', 'Methode', '2019-07-23 22:34:19'),
(3615, 'de', 'misc', 'common', '', 'Diverse', '2019-07-23 22:34:19'),
(3616, 'de', 'modified', 'common', '', 'Verändert', '2019-07-23 22:34:19'),
(3617, 'de', 'modified_by', 'common', '', 'Zuletzt geändert von', '2019-07-23 22:34:19'),
(3618, 'de', 'month', 'common', '', 'Monat', '2019-07-23 22:34:19'),
(3619, 'de', 'more', 'common', '', 'Mehr', '2019-07-23 22:34:19'),
(3620, 'de', 'navigation', 'common', '', 'Navigation', '2019-07-23 22:34:19'),
(3621, 'de', 'name', 'common', '', 'Name', '2019-07-23 22:34:19'),
(3622, 'de', 'never', 'common', '', 'Niemals', '2019-07-23 22:34:19'),
(3623, 'de', 'newer', 'common', '', 'Neuer', '2019-07-23 22:34:19'),
(3624, 'de', 'next', 'common', '', 'Nächstes', '2019-07-23 22:34:19'),
(3625, 'de', 'no', 'common', '', 'Nein', '2019-07-23 22:34:19'),
(3626, 'de', 'no_results_found', 'common', '', 'Es liegen keine Ergebnisse vor.', '2019-07-23 22:34:19'),
(3627, 'de', 'none', 'common', '', 'Keine', '2019-07-23 22:34:19'),
(3628, 'de', 'of', 'common', '', 'von', '2019-07-23 22:34:19'),
(3629, 'de', 'off', 'common', '', 'Aus', '2019-07-23 22:34:19'),
(3630, 'de', 'older', 'common', '', 'Älter', '2019-07-23 22:34:19'),
(3631, 'de', 'on', 'common', '', 'Ein', '2019-07-23 22:34:19'),
(3632, 'de', 'opens_external_link', 'common', '', '(öffnet einen externen Link in einem neuen Fenster)', '2019-07-23 22:34:19'),
(3633, 'de', 'options', 'common', '', 'Einstellungen', '2019-07-23 22:34:19'),
(3634, 'de', 'page', 'common', '', 'Seite', '2019-07-23 22:34:19'),
(3635, 'de', 'pages', 'common', '', 'Seiten', '2019-07-23 22:34:19'),
(3636, 'de', 'password', 'common', '', 'Passwort', '2019-07-23 22:34:19'),
(3637, 'de', 'password_forgotten', 'common', '', 'Passwort vergessen?', '2019-07-23 22:34:19'),
(3638, 'de', 'permlink', 'common', '', 'Permanenter Link', '2019-07-23 22:34:19'),
(3639, 'de', 'pophelp', 'common', '', 'Links für Pop-up-Hilfe', '2019-07-23 22:34:19'),
(3640, 'de', 'post', 'common', '', 'Eintrag', '2019-07-23 22:34:19'),
(3641, 'de', 'posted', 'common', '', 'Datum', '2019-07-23 22:34:19'),
(3642, 'de', 'posted_by', 'common', '', 'Verfasst von', '2019-07-23 22:34:19'),
(3643, 'de', 'prev', 'common', '', 'Vorheriges', '2019-07-23 22:34:19'),
(3644, 'de', 'preview', 'common', '', 'Vorschau', '2019-07-23 22:34:19'),
(3645, 'de', 'publish', 'common', '', 'Veröffentlichen', '2019-07-23 22:34:19'),
(3646, 'de', 'published_at', 'common', '', 'Veröffentlicht am', '2019-07-23 22:34:19'),
(3647, 'de', 'published_with', 'common', '', 'Veröffentlicht mit', '2019-07-23 22:34:19'),
(3648, 'de', 'publish_date', 'common', '', 'Veröffentlichungsdatum', '2019-07-23 22:34:19'),
(3649, 'de', 'publish_time', 'common', '', 'Uhrzeit der Veröffentlichung', '2019-07-23 22:34:19'),
(3650, 'de', 'range', 'common', '', 'Nachfolgende', '2019-07-23 22:34:19'),
(3651, 'de', 'recently', 'common', '', 'kürzlich', '2019-07-23 22:34:19'),
(3652, 'de', 'recent_articles', 'common', '', 'Neueste Artikel', '2019-07-23 22:34:19'),
(3653, 'de', 'recent_comments', 'common', '', 'Neuesten Kommentare', '2019-07-23 22:34:19'),
(3654, 'de', 'recent_posts', 'common', '', 'Neueste Einträge', '2019-07-23 22:34:19'),
(3655, 'de', 'related_articles', 'common', '', 'Ähnlichen Artikel', '2019-07-23 22:34:19'),
(3656, 'de', 'reload', 'common', '', 'Neu laden', '2019-07-23 22:34:19'),
(3657, 'de', 'remember', 'common', '', 'Merken', '2019-07-23 22:34:19'),
(3658, 'de', 'remove', 'common', '', 'Löschen', '2019-07-23 22:34:19'),
(3659, 'de', 'required', 'common', '', 'Erforderlich', '2019-07-23 22:34:19'),
(3660, 'de', 'reset', 'common', '', 'Zurücksetzen', '2019-07-23 22:34:19'),
(3661, 'de', 'restricted_area', 'common', '', 'Eingeschränkter Bereich', '2019-07-23 22:34:19'),
(3662, 'de', 'revert', 'common', '', 'umkehren', '2019-07-23 22:34:19'),
(3663, 'de', 'salutation', 'common', '', 'Hallo {name},', '2019-07-23 22:34:19'),
(3664, 'de', 'save', 'common', '', 'Speichern', '2019-07-23 22:34:19'),
(3665, 'de', 'save_new', 'common', '', 'Neu anlegen', '2019-07-23 22:34:19'),
(3666, 'de', 'search', 'common', '', 'Suchen', '2019-07-23 22:34:19'),
(3667, 'de', 'search_results', 'common', '', 'Suchergebnisse', '2019-07-23 22:34:19'),
(3668, 'de', 'section', 'common', '', 'Sektion', '2019-07-23 22:34:19'),
(3669, 'de', 'sections', 'common', '', 'Sektionen', '2019-07-23 22:34:19'),
(3670, 'de', 'select', 'common', '', 'Auswählen', '2019-07-23 22:34:19'),
(3671, 'de', 'selected', 'common', '', 'der Auswahl', '2019-07-23 22:34:19'),
(3672, 'de', 'site', 'common', '', 'Webseite', '2019-07-23 22:34:19'),
(3673, 'de', 'sitename', 'common', '', 'Name der Webseite', '2019-07-23 22:34:19'),
(3674, 'de', 'siteurl', 'common', '', 'Webadresse', '2019-07-23 22:34:19'),
(3675, 'de', 'site_slogan', 'common', '', 'Motto der Seite', '2019-07-23 22:34:19'),
(3676, 'de', 'spam', 'common', '', 'Spam', '2019-07-23 22:34:19'),
(3677, 'de', 'status', 'common', '', 'Status', '2019-07-23 22:34:19'),
(3678, 'de', 'stay_logged_in', 'common', '', 'Mit diesem Browser eingeloggt bleiben', '2019-07-23 22:34:19'),
(3679, 'de', 'submit', 'common', '', 'Absenden', '2019-07-23 22:34:19'),
(3680, 'de', 'thumbnail', 'common', '', 'Vorschaubild', '2019-07-23 22:34:19'),
(3681, 'de', 'time', 'common', '', 'Zeit', '2019-07-23 22:34:19'),
(3682, 'de', 'title', 'common', '', 'Titel', '2019-07-23 22:34:19'),
(3683, 'de', 'toggle_all_selected', 'common', '', 'Alle/keines auswählen', '2019-07-23 22:34:19'),
(3684, 'de', 'txt_quote_double_close', 'common', '', '“', '2019-07-23 22:34:19'),
(3685, 'de', 'txt_quote_double_open', 'common', '', '„', '2019-07-23 22:34:19'),
(3686, 'de', 'txt_quote_single_close', 'common', '', '‘', '2019-07-23 22:34:19'),
(3687, 'de', 'txt_quote_single_open', 'common', '', '‚', '2019-07-23 22:34:19'),
(3688, 'de', 'type', 'common', '', 'Typ', '2019-07-23 22:34:19'),
(3689, 'de', 'undefined', 'common', '', 'Undefiniert', '2019-07-23 22:34:19'),
(3690, 'de', 'unknown', 'common', '', 'Unbekannt', '2019-07-23 22:34:19'),
(3691, 'de', 'units_b', 'common', '', 'B', '2019-07-23 22:34:19'),
(3692, 'de', 'units_e', 'common', '', 'EB', '2019-07-23 22:34:19'),
(3693, 'de', 'units_g', 'common', '', 'GB', '2019-07-23 22:34:19'),
(3694, 'de', 'units_k', 'common', '', 'kB', '2019-07-23 22:34:19'),
(3695, 'de', 'units_m', 'common', '', 'MB', '2019-07-23 22:34:19'),
(3696, 'de', 'units_p', 'common', '', 'PB', '2019-07-23 22:34:19'),
(3697, 'de', 'units_t', 'common', '', 'TB', '2019-07-23 22:34:19'),
(3698, 'de', 'units_y', 'common', '', 'YB', '2019-07-23 22:34:19'),
(3699, 'de', 'units_z', 'common', '', 'ZB', '2019-07-23 22:34:19'),
(3700, 'de', 'untitled', 'common', '', 'Ohne Namen', '2019-07-23 22:34:19'),
(3701, 'de', 'update', 'common', '', 'Aktualisierung', '2019-07-23 22:34:19'),
(3702, 'de', 'upload', 'common', '', 'Hochladen', '2019-07-23 22:34:19'),
(3703, 'de', 'upload_err_cant_write', 'common', '', 'Datei konnte nicht gespeichert werden.', '2019-07-23 22:34:19'),
(3704, 'de', 'upload_err_extension', 'common', '', 'Datei-Upload durch PHP-Erweiterung gestoppt.', '2019-07-23 22:34:19'),
(3705, 'de', 'upload_err_form_size', 'common', '', 'Die Datei überschreitet die maximale in den Textpattern-Voreinstellungen definierte Größe.', '2019-07-23 22:34:19'),
(3706, 'de', 'upload_err_ini_size', 'common', '', 'Die Datei überschreitet die <code>upload_max_filesize</code>-Direktive der <code>php.ini</code>.', '2019-07-23 22:34:19'),
(3707, 'de', 'upload_err_no_file', 'common', '', 'Es wurde keine Datei ausgewählt.', '2019-07-23 22:34:19'),
(3708, 'de', 'upload_err_partial', 'common', '', 'Die Datei wurde nur teilweise hochgeladen.', '2019-07-23 22:34:19'),
(3709, 'de', 'upload_err_tmp_dir', 'common', '', 'Das Temporär-Verzeichnis ist nicht definiert. Bitte aktualisieren Sie die erweiterten Einstellungen.', '2019-07-23 22:34:19'),
(3710, 'de', 'upload_file', 'common', '', 'Datei hochladen', '2019-07-23 22:34:19'),
(3711, 'de', 'url', 'common', '', 'URL', '2019-07-23 22:34:19'),
(3712, 'de', 'value', 'common', '', 'Wert', '2019-07-23 22:34:19'),
(3713, 'de', 'version', 'common', '', 'Version', '2019-07-23 22:34:19'),
(3714, 'de', 'view', 'common', '', 'Anzeigen', '2019-07-23 22:34:19'),
(3715, 'de', 'viewsite', 'common', '', 'Zeige Seite', '2019-07-23 22:34:19'),
(3716, 'de', 'website', 'common', '', 'Webseite', '2019-07-23 22:34:19'),
(3717, 'de', 'width', 'common', '', 'Breite', '2019-07-23 22:34:19'),
(3718, 'de', 'with_selected', 'common', '', 'ausgewählte Elemente:', '2019-07-23 22:34:19'),
(3719, 'de', 'with_selected_option', 'common', '', 'Mit {count} ausgewählt…', '2019-07-23 22:34:19'),
(3720, 'de', 'yes', 'common', '', 'Ja', '2019-07-23 22:34:19'),
(3721, 'de', 'lang_name', 'common', '', 'Deutsch', '2019-07-23 22:34:19'),
(3722, 'de', 'lang_code', 'common', '', 'de', '2019-07-23 22:34:19'),
(3723, 'de', 'lang_dir', 'common', '', 'ltr', '2019-07-23 22:34:19'),
(3724, 'de', 'account_activation', 'admin', '', 'Bitte aktivieren Sie Ihren Account', '2019-07-23 22:34:19'),
(3725, 'de', 'account_activation_confirmation', 'admin', '', 'Bitte verwenden Sie den unten stehenden Link, um Ihren Account zu aktivieren und ein Passwort anzulegen.', '2019-07-23 22:34:19'),
(3726, 'de', 'alias_is_taken', 'admin', '', 'Alias existiert bereits.', '2019-07-23 22:34:19'),
(3727, 'de', 'assign_assets_to', 'admin', '', 'Bestand übertragen auf', '2019-07-23 22:34:19'),
(3728, 'de', 'author_already_exists', 'admin', '', 'Autor <strong>{name}</strong> bereits vorhanden.', '2019-07-23 22:34:19'),
(3729, 'de', 'author_deleted', 'admin', '', 'Folgende Autoren wurden gelöscht: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3730, 'de', 'author_save_failed', 'admin', '', 'Autor <strong>{name}</strong> konnte nicht gespeichert werden.', '2019-07-23 22:34:19'),
(3731, 'de', 'author_updated', 'admin', '', 'Folgende Autoren wurden aktualisiert: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3732, 'de', 'cannot_assign_assets_to_deletee', 'admin', '', 'Der Bestand kann nicht dem gelöschten Autor zugewiesen werden.', '2019-07-23 22:34:19'),
(3733, 'de', 'changeprivilege', 'admin', '', 'Rechte ändern', '2019-07-23 22:34:19'),
(3734, 'de', 'change_email_address', 'admin', '', 'E-Mail ändern', '2019-07-23 22:34:19'),
(3735, 'de', 'change_password', 'admin', '', 'Passwort ändern', '2019-07-23 22:34:19'),
(3736, 'de', 'copy_editor', 'admin', '', 'Redakteur', '2019-07-23 22:34:19'),
(3737, 'de', 'could_not_mail', 'admin', '', 'E-Mail konnte nicht gesendet werden', '2019-07-23 22:34:19'),
(3738, 'de', 'could_not_update_author', 'admin', '', 'Der Autor konnte nicht aktualisiert werden.', '2019-07-23 22:34:19'),
(3739, 'de', 'create_author', 'admin', '', 'Neuer Autor', '2019-07-23 22:34:19'),
(3740, 'de', 'current_password', 'admin', '', 'Momentanes Passwort', '2019-07-23 22:34:19'),
(3741, 'de', 'designer', 'admin', '', 'Designer', '2019-07-23 22:34:19'),
(3742, 'de', 'edit_author', 'admin', '', 'Autor bearbeiten', '2019-07-23 22:34:19'),
(3743, 'de', 'email_changed', 'admin', '', 'E-Mail-Adresse geändert, sie lautet nun <strong>{email}</strong>.', '2019-07-23 22:34:19'),
(3744, 'de', 'error_adding_new_author', 'admin', '', 'Während des Hinzufügens eines neuen Autors trat ein Fehler auf.', '2019-07-23 22:34:19'),
(3745, 'de', 'from_or_to_address_missing', 'admin', '', 'Adresse des Absenders bzw. Empfängers fehlt.', '2019-07-23 22:34:19'),
(3746, 'de', 'freelancer', 'admin', '', 'Freier Mitarbeiter', '2019-07-23 22:34:19'),
(3747, 'de', 'invalid_header', 'admin', '', 'Ungültiger E-Mail-Header.', '2019-07-23 22:34:19'),
(3748, 'de', 'invalid_token', 'admin', '', 'Der Sicherheits-Token zum Zurücksetzen des Passworts ist abgelaufen.', '2019-07-23 22:34:19'),
(3749, 'de', 'last_login', 'admin', '', 'Letzte Anmeldung', '2019-07-23 22:34:19'),
(3750, 'de', 'link_expires', 'admin', '', 'Sie haben Zeit bis {day} {month} {year}, {time}, bevor der Link ungültig wird.', '2019-07-23 22:34:19'),
(3751, 'de', 'login_name', 'admin', '', 'Benutzername', '2019-07-23 22:34:19'),
(3752, 'de', 'login_sent_to', 'admin', '', 'Das Passwort wurde an {email} verschickt.', '2019-07-23 22:34:19'),
(3753, 'de', 'log_in_at', 'admin', '', 'Anmeldung unter', '2019-07-23 22:34:19'),
(3754, 'de', 'managing_editor', 'admin', '', 'Leitender Redakteur', '2019-07-23 22:34:19'),
(3755, 'de', 'must_reassign_assets', 'admin', '', 'Bitte Inhalte dieses Autors an einen anderen übertragen', '2019-07-23 22:34:19'),
(3756, 'de', 'new_email', 'admin', '', 'Neue E-Mail', '2019-07-23 22:34:19'),
(3757, 'de', 'new_password', 'admin', '', 'Neues Passwort', '2019-07-23 22:34:19'),
(3758, 'de', 'password_changed', 'admin', '', 'Passwort geändert.', '2019-07-23 22:34:19'),
(3759, 'de', 'password_change_confirmation', 'admin', '', 'Ihr Passwort wurde geändert. Falls Sie keine Passwortänderung veranlasst haben, wenden Sie sich an den Administrator der Website.', '2019-07-23 22:34:19'),
(3760, 'de', 'password_confirm_button', 'admin', '', 'Passwort festlegen', '2019-07-23 22:34:19'),
(3761, 'de', 'password_invalid', 'admin', '', 'Ihr Passwort ist falsch.', '2019-07-23 22:34:19'),
(3762, 'de', 'password_required', 'admin', '', 'Bitte geben Sie ein neues Passwort ein.', '2019-07-23 22:34:19'),
(3763, 'de', 'password_reset', 'admin', '', 'Passwort zurücksetzen', '2019-07-23 22:34:19'),
(3764, 'de', 'password_reset_button', 'admin', '', 'Zurücksetzen', '2019-07-23 22:34:19'),
(3765, 'de', 'password_reset_confirmation', 'admin', '', 'Jemand (wahrscheinlich Sie) hat das Zurücksetzen Ihres Passworts verlangt. Bitte bestätigen Sie diese Anforderung, indem Sie auf den folgenden Link klicken.', '2019-07-23 22:34:19'),
(3766, 'de', 'password_reset_confirmation_request', 'admin', '', 'Bitte bestätigen Sie die Passwort-Anforderung', '2019-07-23 22:34:19'),
(3767, 'de', 'password_reset_confirmation_request_sent', 'admin', '', 'Eine Bestätigungsnachricht wurde an Ihre E-Mail-Adresse gesandt. Bitte öffnen Sie Ihren Posteingang und folgen Sie der Anleitung in dieser Nachricht.', '2019-07-23 22:34:19'),
(3768, 'de', 'password_sent_to', 'admin', '', 'Passwort gesendet an', '2019-07-23 22:34:19'),
(3769, 'de', 'password_set', 'admin', '', 'Ihr Passwort wurde angelegt', '2019-07-23 22:34:19'),
(3770, 'de', 'password_set_confirmation', 'admin', '', 'Ihr Passwort wurde angelegt. Wenn Sie dies nicht veranlasst haben, wenden Sie sich an den Administrator der Website.', '2019-07-23 22:34:19'),
(3771, 'de', 'privileges', 'admin', '', 'Rechte', '2019-07-23 22:34:19'),
(3772, 'de', 'privs_none', 'admin', '', 'Keine', '2019-07-23 22:34:19'),
(3773, 'de', 'publisher', 'admin', '', 'Herausgeber', '2019-07-23 22:34:19'),
(3774, 'de', 'real_name', 'admin', '', 'Realer Name', '2019-07-23 22:34:19'),
(3775, 'de', 'resend_activation', 'admin', '', 'Aktivierungslink erneut zusenden', '2019-07-23 22:34:19'),
(3776, 'de', 'resend_activation_request_sent', 'admin', '', 'Eine Aktivierungsmail an {name} wurde versendet.', '2019-07-23 22:34:19'),
(3777, 'de', 'resetpassword', 'admin', '', 'Passwort zurücksetzen', '2019-07-23 22:34:19'),
(3778, 'de', 'search_users', 'admin', '', 'Nach Benutzern suchen', '2019-07-23 22:34:19'),
(3779, 'de', 'sending_failed', 'admin', '', 'Die Nachricht konnte nicht gesendet werden.', '2019-07-23 22:34:19'),
(3780, 'de', 'set_password', 'admin', '', 'Passwort anlegen', '2019-07-23 22:34:19'),
(3781, 'de', 'show_password', 'admin', '', 'Passwort zeigen', '2019-07-23 22:34:19'),
(3782, 'de', 'staff_writer', 'admin', '', 'Autor', '2019-07-23 22:34:19'),
(3783, 'de', 'token_expired', 'admin', '', 'Der Sicherheits-Token zum Zurücksetzen des Passworts ist abgelaufen.', '2019-07-23 22:34:19'),
(3784, 'de', 'unable_retrieve_template', 'admin', '', 'Vorlage konnte nicht wiederhergestellt werden.', '2019-07-23 22:34:19'),
(3785, 'de', 'unable_retrieve_user', 'admin', '', 'Benutzer konnte nicht wiederhergestellt werden.', '2019-07-23 22:34:19'),
(3786, 'de', 'unable_set_template', 'admin', '', 'Vorlage konnte nicht wiederhergestellt werden.', '2019-07-23 22:34:19'),
(3787, 'de', 'your_login_info', 'admin', '', 'Ihre Anmeldeinformationen', '2019-07-23 22:34:19'),
(3788, 'de', 'your_login_is', 'admin', '', 'Ihr Benutzername lautet', '2019-07-23 22:34:19'),
(3789, 'de', 'your_new_password', 'admin', '', 'Ihr neues Passwort', '2019-07-23 22:34:19'),
(3790, 'de', 'your_password_is', 'admin', '', 'Das Passwort lautet', '2019-07-23 22:34:19'),
(3791, 'de', 'you_have_been_registered', 'admin', '', 'Sie wurden als Mitarbeiter(in) dieser Seite registriert', '2019-07-23 22:34:19'),
(3792, 'de', 'advanced_options', 'admin-side', '', 'Erweiterte Einstellungen', '2019-07-23 22:34:19'),
(3793, 'de', 'auto_dst', 'admin-side', '', 'Sommer-/Winterzeit automatisch umstellen?', '2019-07-23 22:34:19'),
(3794, 'de', 'body', 'admin-side', '', 'Haupttext', '2019-07-23 22:34:19'),
(3795, 'de', 'cannot_delete', 'admin-side', '', 'Kann {thing} nicht löschen.', '2019-07-23 22:34:19'),
(3796, 'de', 'cannot_instantiate_theme', 'admin-side', '', 'Das Design <strong>{name}</strong> ({class}) konnte nicht richtig von der Datei {path} geladen werden. Wenn Sie diese Seite aktualisieren, wird das Standard-Design als Ersatz verwendet.', '2019-07-23 22:34:19'),
(3797, 'de', 'changeauthor', 'admin-side', '', 'Autor ändern', '2019-07-23 22:34:19'),
(3798, 'de', 'changecategory', 'admin-side', '', 'Kategorie ändern', '2019-07-23 22:34:19'),
(3799, 'de', 'changecategory1', 'admin-side', '', 'Kategorie 1 ändern', '2019-07-23 22:34:19'),
(3800, 'de', 'changecategory2', 'admin-side', '', 'Kategorie 2 ändern', '2019-07-23 22:34:19'),
(3801, 'de', 'changecomments', 'admin-side', '', 'Kommentare ändern', '2019-07-23 22:34:19'),
(3802, 'de', 'changeparent', 'admin-side', '', 'Elternelement ändern', '2019-07-23 22:34:19'),
(3803, 'de', 'changesection', 'admin-side', '', 'Sektion ändern', '2019-07-23 22:34:19'),
(3804, 'de', 'changestatus', 'admin-side', '', 'Status ändern', '2019-07-23 22:34:19'),
(3805, 'de', 'collapse_all', 'admin-side', '', 'Alle einklappen', '2019-07-23 22:34:19'),
(3806, 'de', 'convert_linebreaks', 'admin-side', '', 'Nur Zeilenumbrüche', '2019-07-23 22:34:19'),
(3807, 'de', 'create_article', 'admin-side', '', 'Neuer Artikel', '2019-07-23 22:34:19'),
(3808, 'de', 'custom', 'admin-side', '', 'Benutzerdefinierte Felder', '2019-07-23 22:34:19'),
(3809, 'de', 'date_settings', 'admin-side', '', 'Datum und Zeit', '2019-07-23 22:34:19'),
(3810, 'de', 'deleteforce', 'admin-side', '', 'Löschen erzwingen', '2019-07-23 22:34:19'),
(3811, 'de', 'detail', 'admin-side', '', 'Darstellung', '2019-07-23 22:34:19'),
(3812, 'de', 'directory_permissions', 'admin-side', '', 'Verzeichnisrechte prüfen: <strong>{path}</strong>.', '2019-07-23 22:34:19'),
(3813, 'de', 'draft', 'admin-side', '', 'Entwurf', '2019-07-23 22:34:19'),
(3814, 'de', 'expand_all', 'admin-side', '', 'Alle ausklappen', '2019-07-23 22:34:19'),
(3815, 'de', 'export_to_disk', 'admin-side', '', 'Auf die Festplatte exportieren', '2019-07-23 22:34:19'),
(3816, 'de', 'file_base_path', 'admin-side', '', 'Datei-Upload-Pfad', '2019-07-23 22:34:19'),
(3817, 'de', 'get_off_my_lawn', 'admin-side', '', 'Es tut mir leid, aber das kann ich nicht tun. Ich glaube, dass es zur Zeit nicht sicher ist, <code>{event}</code> <code>{step}</code> auszuführen.', '2019-07-23 22:34:19'),
(3818, 'de', 'gmtoffset', 'admin-side', '', 'Zeitzone (Differenz zu GMT in Sekunden)', '2019-07-23 22:34:19'),
(3819, 'de', 'img_dir', 'admin-side', '', 'Bilderverzeichnis', '2019-07-23 22:34:19'),
(3820, 'de', 'import_from_disk', 'admin-side', '', 'Von der Festplatte importieren', '2019-07-23 22:34:19'),
(3821, 'de', 'invalid_argument', 'admin-side', '', 'Ungültiges Argument', '2019-07-23 22:34:19'),
(3822, 'de', 'invalid_article_id', 'admin-side', '', 'Ungültige Artikel-ID', '2019-07-23 22:34:19'),
(3823, 'de', 'invalid_expiredate', 'admin-side', '', 'Ungültige Ablaufzeit oder -datum.', '2019-07-23 22:34:19'),
(3824, 'de', 'invalid_json', 'admin-side', '', 'Ungültige JSON-Dateien: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3825, 'de', 'invalid_postdate', 'admin-side', '', 'Ungültiges Veröffentlichungsdatum.', '2019-07-23 22:34:19'),
(3826, 'de', 'is_dst', 'admin-side', '', 'Sommerzeit', '2019-07-23 22:34:19'),
(3827, 'de', 'leave_text_untouched', 'admin-side', '', 'keine Anpassungen', '2019-07-23 22:34:19'),
(3828, 'de', 'live', 'admin-side', '', 'Live', '2019-07-23 22:34:19'),
(3829, 'de', 'locale', 'admin-side', '', 'Regionale Einstellungen', '2019-07-23 22:34:19'),
(3830, 'de', 'manage', 'admin-side', '', 'Verwalten', '2019-07-23 22:34:19'),
(3831, 'de', 'meta', 'admin-side', '', 'Meta', '2019-07-23 22:34:19'),
(3832, 'de', 'on_front_page', 'admin-side', '', 'Auf der Startseite anzeigen?', '2019-07-23 22:34:19'),
(3833, 'de', 'override', 'admin-side', '', 'Überschreiben', '2019-07-23 22:34:19'),
(3834, 'de', 'parent', 'admin-side', '', 'Elternelement', '2019-07-23 22:34:19'),
(3835, 'de', 'path_creation_or_writing_failed', 'admin-side', '', 'Nicht möglich, in Dateien/Verzeichnisse zu schreiben: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3836, 'de', 'path_not_readable', 'admin-side', '', 'Nicht lesbare Dateien oder Verzeichnisse: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3837, 'de', 'path_not_writable', 'admin-side', '', 'Nicht beschreibbare Dateien/Verzeichnisse: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3838, 'de', 'path_renaming_failed', 'admin-side', '', 'Umbenennen von Dateien oder Verzeichnissen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3839, 'de', 'pending', 'admin-side', '', 'Anstehend', '2019-07-23 22:34:19'),
(3840, 'de', 'permlink_mode', 'admin-side', '', 'Schema der URLs', '2019-07-23 22:34:19'),
(3841, 'de', 'preferences_saved', 'admin-side', '', 'Einstellungen gespeichert', '2019-07-23 22:34:19'),
(3842, 'de', 'reset_time', 'admin-side', '', 'Auf aktuelle Zeit setzen', '2019-07-23 22:34:19'),
(3843, 'de', 'search_all', 'admin-side', '', 'Alles durchsuchen', '2019-07-23 22:34:19'),
(3844, 'de', 'search_clear', 'admin-side', '', 'Suche löschen', '2019-07-23 22:34:19'),
(3845, 'de', 'search_options', 'admin-side', '', 'Suchoptionen', '2019-07-23 22:34:19'),
(3846, 'de', 'set_expire_now', 'admin-side', '', 'Ablauftermin auf jetzt setzen', '2019-07-23 22:34:19'),
(3847, 'de', 'set_to_now', 'admin-side', '', 'Aktuelle Zeit verwenden', '2019-07-23 22:34:19'),
(3848, 'de', 'show', 'admin-side', '', 'Zeige', '2019-07-23 22:34:19'),
(3849, 'de', 'showing_search_results', 'admin-side', '', 'Treffer {from} bis {to} von {total}', '2019-07-23 22:34:19'),
(3850, 'de', 'skin', 'admin-side', '', 'Design', '2019-07-23 22:34:19'),
(3851, 'de', 'skin_dir', 'admin-side', '', 'Verzeichnis für Designs', '2019-07-23 22:34:19'),
(3852, 'de', 'sort_value', 'admin-side', '', 'Sortierkriterium', '2019-07-23 22:34:19'),
(3853, 'de', 'sticky', 'admin-side', '', 'Fixiert', '2019-07-23 22:34:19'),
(3854, 'de', 'swap_values', 'admin-side', '', 'Werte austauschen', '2019-07-23 22:34:19'),
(3855, 'de', 'tab_admin', 'admin-side', '', 'Administration', '2019-07-23 22:34:19'),
(3856, 'de', 'tab_comments', 'admin-side', '', 'Kommentare', '2019-07-23 22:34:19'),
(3857, 'de', 'tab_content', 'admin-side', '', 'Inhalt', '2019-07-23 22:34:19'),
(3858, 'de', 'tab_diagnostics', 'admin-side', '', 'Diagnose', '2019-07-23 22:34:19'),
(3859, 'de', 'tab_extensions', 'admin-side', '', 'Erweiterungen', '2019-07-23 22:34:19'),
(3860, 'de', 'tab_file', 'admin-side', '', 'Dateien', '2019-07-23 22:34:19'),
(3861, 'de', 'tab_forms', 'admin-side', '', 'Bausteine', '2019-07-23 22:34:19'),
(3862, 'de', 'tab_help', 'admin-side', '', 'Hilfe', '2019-07-23 22:34:19'),
(3863, 'de', 'tab_image', 'admin-side', '', 'Bilder', '2019-07-23 22:34:19'),
(3864, 'de', 'tab_import', 'admin-side', '', 'Import', '2019-07-23 22:34:19'),
(3865, 'de', 'tab_languages', 'admin-side', '', 'Sprachen', '2019-07-23 22:34:19'),
(3866, 'de', 'tab_link', 'admin-side', '', 'Links', '2019-07-23 22:34:19'),
(3867, 'de', 'tab_list', 'admin-side', '', 'Artikel', '2019-07-23 22:34:19'),
(3868, 'de', 'tab_logs', 'admin-side', '', 'Protokolle', '2019-07-23 22:34:19'),
(3869, 'de', 'tab_organise', 'admin-side', '', 'Kategorien', '2019-07-23 22:34:19'),
(3870, 'de', 'tab_pages', 'admin-side', '', 'Seitenvorlagen', '2019-07-23 22:34:19'),
(3871, 'de', 'tab_plugins', 'admin-side', '', 'Plugins', '2019-07-23 22:34:19'),
(3872, 'de', 'tab_preferences', 'admin-side', '', 'Einstellungen', '2019-07-23 22:34:19'),
(3873, 'de', 'tab_presentation', 'admin-side', '', 'Präsentation', '2019-07-23 22:34:19'),
(3874, 'de', 'tab_sections', 'admin-side', '', 'Sektionen', '2019-07-23 22:34:19'),
(3875, 'de', 'tab_site_account', 'admin-side', '', 'Benutzerkonto', '2019-07-23 22:34:19'),
(3876, 'de', 'tab_site_admin', 'admin-side', '', 'Benutzer', '2019-07-23 22:34:19'),
(3877, 'de', 'tab_skin', 'admin-side', '', 'Designs', '2019-07-23 22:34:19'),
(3878, 'de', 'tab_start', 'admin-side', '', 'Start', '2019-07-23 22:34:19'),
(3879, 'de', 'tab_style', 'admin-side', '', 'Stilvorlagen', '2019-07-23 22:34:19'),
(3880, 'de', 'tab_view_site', 'admin-side', '', 'Seite anzeigen', '2019-07-23 22:34:19'),
(3881, 'de', 'tab_write', 'admin-side', '', 'Verfassen', '2019-07-23 22:34:19'),
(3882, 'de', 'tagbuilder', 'admin-side', '', 'Baukasten', '2019-07-23 22:34:19'),
(3883, 'de', 'tags', 'admin-side', '', 'Tags', '2019-07-23 22:34:19'),
(3884, 'de', 'tempdir', 'admin-side', '', 'Temporäres Verzeichnis', '2019-07-23 22:34:19'),
(3885, 'de', 'textfilter', 'admin-side', '', 'Format:', '2019-07-23 22:34:19'),
(3886, 'de', 'textfilter_help', 'admin-side', '', 'Hilfe zur Textformatierung', '2019-07-23 22:34:19'),
(3887, 'de', 'theme_name', 'admin-side', '', 'Admin-Oberfläche', '2019-07-23 22:34:19'),
(3888, 'de', 'timestamp', 'admin-side', '', 'Zeitstempel', '2019-07-23 22:34:19'),
(3889, 'de', 'title_body', 'admin-side', '', 'Titel und Haupttext', '2019-07-23 22:34:19'),
(3890, 'de', 'title_body_excerpt', 'admin-side', '', 'Titel, Haupttext und Exzerpt', '2019-07-23 22:34:19'),
(3891, 'de', 'unmoderated', 'admin-side', '', 'Nicht moderiert', '2019-07-23 22:34:19'),
(3892, 'de', 'update_from_disk', 'admin-side', '', 'Von der Festplatte aktualisieren', '2019-07-23 22:34:19'),
(3893, 'de', 'use_textile', 'admin-side', '', 'Textile verwenden', '2019-07-23 22:34:19'),
(3894, 'de', 'view_html', 'admin-side', '', 'HTML anzeigen', '2019-07-23 22:34:19'),
(3895, 'de', 'view_html_short', 'admin-side', '', 'HTML', '2019-07-23 22:34:19'),
(3896, 'de', 'view_per_page', 'admin-side', '', 'Zeige {page} pro Seite', '2019-07-23 22:34:19'),
(3897, 'de', 'view_preview', 'admin-side', '', 'Vorschau', '2019-07-23 22:34:19'),
(3898, 'de', 'view_preview_short', 'admin-side', '', 'Vorschau', '2019-07-23 22:34:19'),
(3899, 'de', 'view_text', 'admin-side', '', 'Text bearbeiten', '2019-07-23 22:34:19'),
(3900, 'de', 'view_text_short', 'admin-side', '', 'Text', '2019-07-23 22:34:19'),
(3901, 'de', 'visible', 'admin-side', '', 'Sichtbar', '2019-07-23 22:34:19'),
(3902, 'de', 'article_expires_before_postdate', 'article', '', 'Fehler: Artikel läuft <strong>vor</strong> dem Veröffentlichungsdatum ab.', '2019-07-23 22:34:19'),
(3903, 'de', 'article_markup', 'article', '', 'Artikel-Formatierung', '2019-07-23 22:34:19'),
(3904, 'de', 'article_posted', 'article', '', 'Artikel veröffentlicht.', '2019-07-23 22:34:19'),
(3905, 'de', 'article_saved', 'article', '', 'Artikel gespeichert.', '2019-07-23 22:34:19'),
(3906, 'de', 'article_saved_draft', 'article', '', 'Artikel als Entwurf gespeichert.', '2019-07-23 22:34:19'),
(3907, 'de', 'article_saved_hidden', 'article', '', 'Artikel als „versteckt“ gespeichert.', '2019-07-23 22:34:19'),
(3908, 'de', 'article_saved_pending', 'article', '', 'Artikel als „anstehend“ gespeichert.', '2019-07-23 22:34:19'),
(3909, 'de', 'article_save_failed', 'article', '', 'Der Artikel konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(3910, 'de', 'comment_settings', 'article', '', 'Kommentar', '2019-07-23 22:34:19'),
(3911, 'de', 'concurrent_edit_by', 'article', '', 'Dieser Artikel wurde gleichzeitig von {author} bearbeitet.', '2019-07-23 22:34:19'),
(3912, 'de', 'excerpt_markup', 'article', '', 'Exzerpt-Formatierung', '2019-07-23 22:34:19'),
(3913, 'de', 'override_default_form', 'article', '', 'Alternativen Baustein verwenden', '2019-07-23 22:34:19'),
(3914, 'de', 'problem_creating_article', 'article', '', 'Es gab ein Problem beim Erstellen des Artikels.', '2019-07-23 22:34:19'),
(3915, 'de', 'problem_deleting_article', 'article', '', 'Es gab ein Problem beim Löschen des Artikels.', '2019-07-23 22:34:19'),
(3916, 'de', 'problem_getting_articles', 'article', '', 'Die Artikel konnten nicht wiederhergestellt werden.', '2019-07-23 22:34:19'),
(3917, 'de', 'problem_retrieving_article', 'article', '', 'Es gab ein Problem beim Wiederherstellen des Artikels.', '2019-07-23 22:34:19'),
(3918, 'de', 'problem_retrieving_article_categories', 'article', '', 'Es gab ein Problem beim Wiederherstellen der Artikelkategorien.', '2019-07-23 22:34:19'),
(3919, 'de', 'problem_retrieving_categories', 'article', '', 'Es gab ein Problem beim Wiederherstellen der Kategorien.', '2019-07-23 22:34:19'),
(3920, 'de', 'problem_retrieving_category_info', 'article', '', 'Es gab ein Problem beim Wiederherstellen der Kategorieinformationen.', '2019-07-23 22:34:19'),
(3921, 'de', 'problem_retrieving_sections', 'article', '', 'Es gab ein Problem beim Wiederherstellen der Sektionen.', '2019-07-23 22:34:19'),
(3922, 'de', 'problem_saving_article_categories', 'article', '', 'Es gab ein Problem beim Sichern der Artikelkategorien.', '2019-07-23 22:34:19'),
(3923, 'de', 'problem_updating_article', 'article', '', 'Es gab ein Problem beim Aktualisieren des Artikels.', '2019-07-23 22:34:19'),
(3924, 'de', 'sort_display', 'article', '', 'Sortierung und Anzeige', '2019-07-23 22:34:19'),
(3925, 'de', 'trying_to_assign_unexisting_category_to_the_article', 'article', '', 'Eine nicht-existente Kategorie kann einem Artikel nicht zugeordnet werden.', '2019-07-23 22:34:19'),
(3926, 'de', 'url_title', 'article', '', 'benutzerdefinierte URL', '2019-07-23 22:34:19'),
(3927, 'de', 'url_title_is_blank', 'article', '', 'Die benutzerdefinierte URL ist leer.', '2019-07-23 22:34:19'),
(3928, 'de', 'url_title_is_multiple', 'article', '', 'Die selbe benutzerdefinierte URL wird von {count} verschiedenen Artikeln benutzt.', '2019-07-23 22:34:19'),
(3929, 'de', 'write', 'article', '', 'Verfassen', '2019-07-23 22:34:19'),
(3930, 'de', 'article_categories_deleted', 'category', '', 'Artikelkategorie gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3931, 'de', 'article_category', 'category', '', 'Artikelkategorie', '2019-07-23 22:34:19'),
(3932, 'de', 'article_category_already_exists', 'category', '', 'Artikelkategorie <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(3933, 'de', 'article_category_created', 'category', '', 'Artikelkategorie <strong>{name}</strong> erstellt.', '2019-07-23 22:34:19'),
(3934, 'de', 'article_category_description', 'category', '', 'Beschreibung der Artikelkategorie', '2019-07-23 22:34:19'),
(3935, 'de', 'article_category_invalid', 'category', '', 'Ungültige Artikelkategorie <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3936, 'de', 'article_category_name', 'category', '', 'Name der Artikelkategorie', '2019-07-23 22:34:19'),
(3937, 'de', 'article_category_title', 'category', '', 'Titel der Artikelkategorie', '2019-07-23 22:34:19'),
(3938, 'de', 'article_category_updated', 'category', '', 'Artikelkategorie <strong>{name}</strong> aktualisiert', '2019-07-23 22:34:19'),
(3939, 'de', 'categories_set_parent', 'category', '', '{list} wurde Eltern-{type}-Kategorie {parent} zugewiesen', '2019-07-23 22:34:19'),
(3940, 'de', 'category_not_found', 'category', '', 'Kategorie nicht gefunden', '2019-07-23 22:34:19'),
(3941, 'de', 'category_save_failed', 'category', '', 'Die Kategorie konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(3942, 'de', 'create_category', 'category', '', 'Neue Kategorie', '2019-07-23 22:34:19'),
(3943, 'de', 'edit_category', 'category', '', 'Kategorie bearbeiten', '2019-07-23 22:34:19'),
(3944, 'de', 'file_categories_deleted', 'category', '', 'Dateikategorie gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3945, 'de', 'file_category_already_exists', 'category', '', 'Dateikategorie <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(3946, 'de', 'file_category_created', 'category', '', 'Dateikategorie <strong>{name}</strong> erstellt.', '2019-07-23 22:34:19'),
(3947, 'de', 'file_category_description', 'category', '', 'Beschreibung der Dateikategorie', '2019-07-23 22:34:19'),
(3948, 'de', 'file_category_invalid', 'category', '', 'Ungültige Dateikategorie <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3949, 'de', 'file_category_name', 'category', '', 'Name der Dateikategorie', '2019-07-23 22:34:19'),
(3950, 'de', 'file_category_title', 'category', '', 'Titel der Dateikategorie', '2019-07-23 22:34:19'),
(3951, 'de', 'file_category_updated', 'category', '', 'Dateikategorie <strong>{name}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(3952, 'de', 'image_categories_deleted', 'category', '', 'Bildkategorie gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3953, 'de', 'image_category_already_exists', 'category', '', 'Bildkategorie <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(3954, 'de', 'image_category_created', 'category', '', 'Bildkategorie <strong>{name}</strong> erstellt.', '2019-07-23 22:34:19'),
(3955, 'de', 'image_category_description', 'category', '', 'Beschreibung der Bildkategorie', '2019-07-23 22:34:19'),
(3956, 'de', 'image_category_invalid', 'category', '', 'Ungültige Bildkategorie <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3957, 'de', 'image_category_name', 'category', '', 'Name der Bildkategorie', '2019-07-23 22:34:19'),
(3958, 'de', 'image_category_title', 'category', '', 'Titel der Bildkategorie', '2019-07-23 22:34:19'),
(3959, 'de', 'image_category_updated', 'category', '', 'Bildkategorie <strong>{name}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(3960, 'de', 'link_categories_deleted', 'category', '', 'Linkkategorie gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3961, 'de', 'link_category_already_exists', 'category', '', 'Linkkategorie <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(3962, 'de', 'link_category_created', 'category', '', 'Linkkategorie <strong>{name}</strong> erstellt.', '2019-07-23 22:34:19'),
(3963, 'de', 'link_category_description', 'category', '', 'Beschreibung der Linkkategorie', '2019-07-23 22:34:19'),
(3964, 'de', 'link_category_invalid', 'category', '', 'Ungültige Linkkategorie <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(3965, 'de', 'link_category_name', 'category', '', 'Name der Linkkategorie', '2019-07-23 22:34:19'),
(3966, 'de', 'link_category_title', 'category', '', 'Titel der Linkkategorie', '2019-07-23 22:34:19'),
(3967, 'de', 'link_category_updated', 'category', '', 'Linkkategorie <strong>{name}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(3968, 'de', 'no_categories_exist', 'category', '', 'Keine vorhanden.', '2019-07-23 22:34:19'),
(3969, 'de', 'no_other_categories_exist', 'category', '', 'Keine weiteren Kategorien vorhanden.', '2019-07-23 22:34:19'),
(3970, 'de', 'all_stylesheets', 'css', '', 'Alle Stilvorlagen', '2019-07-23 22:34:19'),
(3971, 'de', 'create_css', 'css', '', 'Neue Stilvorlage', '2019-07-23 22:34:19'),
(3972, 'de', 'css_already_exists', 'css', '', 'Stilvorlage <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(3973, 'de', 'css_code', 'css', '', 'Stilvorlage', '2019-07-23 22:34:19'),
(3974, 'de', 'css_created', 'css', '', 'Stilvorlagen erstellt: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3975, 'de', 'css_deleted', 'css', '', 'Stilvorlagen gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3976, 'de', 'css_name', 'css', '', 'Stilname', '2019-07-23 22:34:19'),
(3977, 'de', 'css_name_required', 'css', '', 'Bitte geben sie einen Namen für ihre Stilvorlage an.', '2019-07-23 22:34:19'),
(3978, 'de', 'css_save_failed', 'css', '', 'Die Stilvorlage konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(3979, 'de', 'css_updated', 'css', '', 'Stilvorlagen aktualisiert: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(3980, 'de', 'css_used_by_section', 'css', '', 'Die Stilvorlage <strong>{name}</strong> wurde nicht gelöscht, weil sie von {count} Sektion(en) verwendet wird.', '2019-07-23 22:34:19'),
(3981, 'de', 'all_checks_passed', 'diag', '', 'Alle Untersuchungen abgeschlossen.', '2019-07-23 22:34:19'),
(3982, 'de', 'cgi_header_config', 'diag', '', 'CGI-Header-Konfiguration', '2019-07-23 22:34:19'),
(3983, 'de', 'clean_url_data_failed', 'diag', '', 'Der Test für saubere URLs ist fehlgeschlagen: {data}.', '2019-07-23 22:34:19'),
(3984, 'de', 'clean_url_test_failed', 'diag', '', 'Test für saubere URLs fehlgeschlagen.', '2019-07-23 22:34:19'),
(3985, 'de', 'diag_clear_private', 'diag', '', 'Private Informationen ausblenden', '2019-07-23 22:34:19'),
(3986, 'de', 'dev_version_live', 'diag', '', 'Sie verwenden Dateien aus dem Entwicklungsbereich in einer Live-Umgebung!', '2019-07-23 22:34:19'),
(3987, 'de', 'diagnostic_info', 'diag', '', 'Ergebnisse', '2019-07-23 22:34:19'),
(3988, 'de', 'dir_not_writable', 'diag', '', '{dirtype} ist nicht beschreibbar: {path}.', '2019-07-23 22:34:19'),
(3989, 'de', 'dns_lookup_fails', 'diag', '', 'DNS-Lookup fehlgeschlagen: {domain}.', '2019-07-23 22:34:19'),
(3990, 'de', 'file_uploads_disabled', 'diag', '', 'Das Hochladen von Dateien ist deaktiviert.', '2019-07-23 22:34:19'),
(3991, 'de', 'high', 'diag', '', 'detailliert', '2019-07-23 22:34:19'),
(3992, 'de', 'htaccess_missing', 'diag', '', '.htaccess-Datei fehlt', '2019-07-23 22:34:19'),
(3993, 'de', 'low', 'diag', '', 'allgemein', '2019-07-23 22:34:19'),
(3994, 'de', 'missing_files', 'diag', '', 'Fehlende Dateien: {list}', '2019-07-23 22:34:19'),
(3995, 'de', 'modified_files', 'diag', '', 'Einige Textpattern-Dateien wurden modifiziert: {list}', '2019-07-23 22:34:19'),
(3996, 'de', 'mod_rewrite_missing', 'diag', '', 'Das Apache-Modul <code>mod_rewrite</code> ist nicht installiert', '2019-07-23 22:34:19'),
(3997, 'de', 'mysql_table_errors', 'diag', '', 'Die folgenden Fehler wurden in den MySQL-Tabellen entdeckt: {list}', '2019-07-23 22:34:19'),
(3998, 'de', 'no_temp_dir', 'diag', '', 'Kein Temporärverzeichnis definiert', '2019-07-23 22:34:19'),
(3999, 'de', 'old_placeholder', 'diag', '', 'Alte Platzhalterdatei steht im Weg: {path}.', '2019-07-23 22:34:19'),
(4000, 'de', 'path_inaccessible', 'diag', '', 'Kein Zugriff auf <strong>{path}</strong> möglich.', '2019-07-23 22:34:19'),
(4001, 'de', 'php_extensions', 'diag', '', 'PHP-Erweiterungen', '2019-07-23 22:34:19'),
(4002, 'de', 'php_version_required', 'diag', '', 'Textpattern CMS erfordert mindestens PHP Version {version} auf dem Server.', '2019-07-23 22:34:19'),
(4003, 'de', 'preflight_check', 'diag', '', 'Voruntersuchung', '2019-07-23 22:34:19'),
(4004, 'de', 'problem_connecting_update_server', 'diag', '', 'Beim Versuch den Server zu erreichen, ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.', '2019-07-23 22:34:19'),
(4005, 'de', 'site_trailing_slash', 'diag', '', 'Die Seiten-URL hat einen abschließenden Slash (Schrägstrich): {path}.', '2019-07-23 22:34:19'),
(4006, 'de', 'site_url_mismatch', 'diag', '', 'Die Einstellungen der Seiten-URL scheinen falsch zu sein: {url}.', '2019-07-23 22:34:19'),
(4007, 'de', 'some_php_functions_disabled', 'diag', '', 'Die folgenden PHP-Funktionen, die für den Betrieb von Textpattern nötig sein könnten, sind auf Ihrem Server deaktiviert: {list}.', '2019-07-23 22:34:19'),
(4008, 'de', 'still_exists', 'diag', '', '<strong>{path}</strong> existiert noch.', '2019-07-23 22:34:19'),
(4009, 'de', 'textpattern_update_available', 'diag', '', 'Eine neue Version {version} von Textpattern CMS ist <a href=\"https://textpattern.com/download\" rel=\"external\">zum Download verfügbar</a>.', '2019-07-23 22:34:19'),
(4010, 'de', 'textpattern_update_available_beta', 'diag', '', 'Neue Textpattern-Vorabversion {version} ist zum Herunterladen verfügbar. Bitte besuche die <a href=\"https://textpattern.com/download-beta\" rel=\"external\">Textpattern website</a> für weitere Informationen.', '2019-07-23 22:34:19'),
(4011, 'de', 'tmp_plugin_paths_match', 'diag', '', 'Temporäres Verzeichnis und Pfad des Pluginzwischenspeichers sollten <strong>nicht</strong> identisch sein.', '2019-07-23 22:34:19'),
(4012, 'de', 'warn_mail_unavailable', 'diag', '', 'Die <code>mail()</code>-Funktion ist in dieser PHP-Installation nicht vorhanden. Textpattern kann deshalb keine E-Mails versenden und die Funktionalität ist teilweise eingeschränkt.', '2019-07-23 22:34:19'),
(4013, 'de', 'article_deleted', 'discuss', '', 'Artikel gelöscht!', '2019-07-23 22:34:19'),
(4014, 'de', 'comments_deleted', 'discuss', '', 'Kommentare gelöscht: <strong>{list}</strong>', '2019-07-23 22:34:19'),
(4015, 'de', 'comments_marked_spam', 'discuss', '', 'Kommentare versteckt und als Spam eingestuft: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4016, 'de', 'comments_marked_unmoderated', 'discuss', '', 'Kommentare versteckt und als unmoderiert eingestuft: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4017, 'de', 'comments_marked_visible', 'discuss', '', 'Kommentare sichtbar gemacht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4018, 'de', 'comment_not_found', 'discuss', '', 'Der Kommentar wurde nicht gefunden. Möglicherweise wurde er gelöscht.', '2019-07-23 22:34:19'),
(4019, 'de', 'comment_save_failed', 'discuss', '', 'Der Kommentar konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(4020, 'de', 'comment_updated', 'discuss', '', 'Kommentar <strong>{id}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(4021, 'de', 'edit_comment', 'discuss', '', 'Kommentar bearbeiten', '2019-07-23 22:34:19'),
(4022, 'de', 'hide_spam', 'discuss', '', 'Versteckt (Spam)', '2019-07-23 22:34:19'),
(4023, 'de', 'hide_unmoderated', 'discuss', '', 'Versteckt (nicht moderiert)', '2019-07-23 22:34:19'),
(4024, 'de', 'just_spam_results_found', 'discuss', '', 'Es wurden nur Spam-Kommentare gefunden. Bitte “Spam anzeigen”; einschalten, um sie zu sehen.', '2019-07-23 22:34:19'),
(4025, 'de', 'no_comments_recorded', 'discuss', '', 'Keine Kommentare verfügbar.', '2019-07-23 22:34:19'),
(4026, 'de', 'search_comments', 'discuss', '', 'Kommentare durchsuchen', '2019-07-23 22:34:19'),
(4027, 'de', 'show_spam', 'discuss', '', 'Spam anzeigen', '2019-07-23 22:34:19'),
(4028, 'de', 'condition', 'file', '', 'Zustand', '2019-07-23 22:34:19'),
(4029, 'de', 'edit_file', 'file', '', 'Datei bearbeiten', '2019-07-23 22:34:19'),
(4030, 'de', 'existing_file', 'file', '', 'Existierende Datei:', '2019-07-23 22:34:19'),
(4031, 'de', 'file_already_exists', 'file', '', 'Datei <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(4032, 'de', 'file_cannot_rename', 'file', '', 'Datei <strong>{name}</strong> konnte nicht umbenannt werden.', '2019-07-23 22:34:19'),
(4033, 'de', 'file_deleted', 'file', '', 'Gelöschte Dateien: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4034, 'de', 'file_delete_failed', 'file', '', 'Nicht gelöscht werden konnte(n): <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4035, 'de', 'file_dir_not_writeable', 'file', '', '<strong>Warnung:</strong> In das Dateiverzeichnis <code>{filedir}</code> konnte nicht geschrieben werden.', '2019-07-23 22:34:19'),
(4036, 'de', 'file_not_found', 'file', '', 'Nicht gefundene Datei(en): <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4037, 'de', 'file_not_updated', 'file', '', 'Nicht aktualisierte Datei(en): <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4038, 'de', 'file_relink', 'file', '', 'Datei hochladen/zuweisen', '2019-07-23 22:34:19'),
(4039, 'de', 'file_status', 'file', '', 'Dateistatus', '2019-07-23 22:34:19'),
(4040, 'de', 'file_status_missing', 'file', '', 'Fehlt', '2019-07-23 22:34:19'),
(4041, 'de', 'file_status_ok', 'file', '', 'OK', '2019-07-23 22:34:19'),
(4042, 'de', 'file_unsynchronized', 'file', '', 'Die Datei <strong>{name}</strong> ist nicht mehr mit der Datenbank synchronisiert. Bitte korrigieren Sie den Dateinamen manuell.', '2019-07-23 22:34:19'),
(4043, 'de', 'file_updated', 'file', '', 'Aktualisierte Datei(en): <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4044, 'de', 'file_uploaded', 'file', '', 'Hochgeladene Datei(en): <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4045, 'de', 'file_upload_failed', 'file', '', 'Hochladen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4046, 'de', 'invalid_filename', 'file', '', 'Folgende Dateinamen sind unzulässig: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4047, 'de', 'invalid_id', 'file', '', 'Ungültige ID <strong>{id}</strong>.', '2019-07-23 22:34:19'),
(4048, 'de', 'linked_to_file', 'file', '', 'Verknüpfte Dateien: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4049, 'de', 'no_files_recorded', 'file', '', 'Keine Dateien vorhanden.', '2019-07-23 22:34:19'),
(4050, 'de', 'permissions', 'file', '', 'Rechte', '2019-07-23 22:34:19'),
(4051, 'de', 'private', 'file', '', 'privat', '2019-07-23 22:34:19'),
(4052, 'de', 'public', 'file', '', 'öffentlich', '2019-07-23 22:34:19'),
(4053, 'de', 'replace_file', 'file', '', 'Datei ersetzen', '2019-07-23 22:34:19'),
(4054, 'de', 'reset_download_count', 'file', '', 'Download-Zähler zurücksetzen', '2019-07-23 22:34:19'),
(4055, 'de', 'search_files', 'file', '', 'Dateien suchen', '2019-07-23 22:34:19'),
(4056, 'de', 'all_forms', 'form', '', 'Alle Bausteine', '2019-07-23 22:34:19'),
(4057, 'de', 'changetype', 'form', '', 'Typ ändern', '2019-07-23 22:34:19'),
(4058, 'de', 'create_form', 'form', '', 'Neuer Baustein', '2019-07-23 22:34:19'),
(4059, 'de', 'form_already_exists', 'form', '', 'Baustein <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(4060, 'de', 'form_clone', 'form', '', 'Duplizieren Sie den Baustein', '2019-07-23 22:34:19'),
(4061, 'de', 'form_code', 'form', '', 'Baustein', '2019-07-23 22:34:19'),
(4062, 'de', 'form_created', 'form', '', 'Erzeugte Bausteine: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4063, 'de', 'form_deleted', 'form', '', 'Gelöschte Bausteine: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4064, 'de', 'form_name', 'form', '', 'Bausteinname (benötigt)', '2019-07-23 22:34:19'),
(4065, 'de', 'form_name_invalid', 'form', '', 'Der Name des Bausteins ist ungültig.', '2019-07-23 22:34:19'),
(4066, 'de', 'form_save_failed', 'form', '', 'Der Baustein konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(4067, 'de', 'form_type', 'form', '', 'Bausteintyp (benötigt)', '2019-07-23 22:34:19'),
(4068, 'de', 'form_type_missing', 'form', '', 'Der Typ des Bausteins fehlt.', '2019-07-23 22:34:19'),
(4069, 'de', 'form_updated', 'form', '', 'Aktualisierte Bausteine: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4070, 'de', 'alt_text', 'image', '', 'Alternativtext', '2019-07-23 22:34:19'),
(4071, 'de', 'create_thumbnail', 'image', '', 'Vorschaubild erstellen', '2019-07-23 22:34:19'),
(4072, 'de', 'edit_image', 'image', '', 'Bild bearbeiten', '2019-07-23 22:34:19'),
(4073, 'de', 'image_deleted', 'image', '', 'Gelöschte Bilder: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4074, 'de', 'image_delete_failed', 'image', '', 'Diese Bilder konnten nicht vollständig gelöscht werden: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4075, 'de', 'image_details', 'image', '', 'Bildinformationen', '2019-07-23 22:34:19'),
(4076, 'de', 'image_name', 'image', '', 'Bildname', '2019-07-23 22:34:19'),
(4077, 'de', 'image_save_error', 'image', '', 'Beim Speichern des Bildes ist ein Problem aufgetreten.', '2019-07-23 22:34:19');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(4078, 'de', 'image_save_failed', 'image', '', 'Das Bild konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(4079, 'de', 'image_updated', 'image', '', 'Aktualisierte Bilder: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4080, 'de', 'image_uploaded', 'image', '', 'Hochgeladene Bilder: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4081, 'de', 'img_dir_not_writeable', 'image', '', '<strong>Warnung:</strong> In das Bilderverzeichnis <code>{imgdir}</code> konnte nicht geschrieben werden.', '2019-07-23 22:34:19'),
(4082, 'de', 'invalid_width_or_height', 'image', '', 'Ungültige Breite oder Höhe.', '2019-07-23 22:34:19'),
(4083, 'de', 'keep_square_pixels', 'image', '', 'zuschneiden', '2019-07-23 22:34:19'),
(4084, 'de', 'no_images_recorded', 'image', '', 'Keine Bilder vorhanden.', '2019-07-23 22:34:19'),
(4085, 'de', 'only_graphic_files_allowed', 'image', '', 'Nur .jpg-, .gif- oder .png-Grafikformate erlaubt.', '2019-07-23 22:34:19'),
(4086, 'de', 'replace_image', 'image', '', 'Bild ersetzen', '2019-07-23 22:34:19'),
(4087, 'de', 'search_images', 'image', '', 'Bilder suchen', '2019-07-23 22:34:19'),
(4088, 'de', 'thumbnail_delete_failed', 'image', '', 'Vorschaubild konnte nicht vollständig gelöscht werden.', '2019-07-23 22:34:19'),
(4089, 'de', 'thumbnail_deleted', 'image', '', 'Vorschaubild gelöscht.', '2019-07-23 22:34:19'),
(4090, 'de', 'thumbnail_not_saved', 'image', '', 'Vorschaubild <strong>{id}</strong> wurde nicht gesichert.', '2019-07-23 22:34:19'),
(4091, 'de', 'thumbnail_saved', 'image', '', 'Vorschaubild <strong>{id}</strong> gespeichert.', '2019-07-23 22:34:19'),
(4092, 'de', 'upload_image', 'image', '', 'Bild hochladen', '2019-07-23 22:34:19'),
(4093, 'de', 'upload_thumbnail', 'image', '', 'Vorschaubild hochladen', '2019-07-23 22:34:19'),
(4094, 'de', 'active_language', 'lang', '', 'Aktuell verwendete Sprache', '2019-07-23 22:34:19'),
(4095, 'de', 'active_language_ui', 'lang', '', 'Sprache der Benutzeroberfläche', '2019-07-23 22:34:19'),
(4096, 'de', 'install_from_textpack', 'lang', '', 'Aus Textpack installieren', '2019-07-23 22:34:19'),
(4097, 'de', 'install_textpack', 'lang', '', 'Textpack installieren', '2019-07-23 22:34:19'),
(4098, 'de', 'language_deleted', 'lang', '', 'Sprache <strong>{name}</strong> gelöscht.', '2019-07-23 22:34:19'),
(4099, 'de', 'language_installed', 'lang', '', 'Sprache <strong>{name}</strong> installiert.', '2019-07-23 22:34:19'),
(4100, 'de', 'language_not_installed', 'lang', '', 'Sprache <strong>{name}</strong> wurde <em>nicht</em> installiert.', '2019-07-23 22:34:19'),
(4101, 'de', 'language_preamble', 'lang', '', '<strong>Sie können uns helfen, Textpattern noch besser zu machen!</strong> Wir begrüßen weitere Übersetzungen vonseiten der Community ebenso wie Verbesserungen an bereits bestehenden. Für Einzelheiten besuchen Sie bitte <a href=\"https://textpattern.com/languages\" rel=\"external\" target=\"_blank\">Textpattern language translations <span class=\"ui-icon ui-icon-extlink\">(öffnet einen externen Link in einem neuen Fenster)</span></a>.', '2019-07-23 22:34:19'),
(4102, 'de', 'language_updated', 'lang', '', 'Sprache <strong>{name}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(4103, 'de', 'locale_not_available_for_language', 'lang', '', 'Kein Locale für Sprache <strong>{name}</strong> verfügbar. Das Standard-Locale wird stattdessen verwendet.', '2019-07-23 22:34:19'),
(4104, 'de', 'textpack_strings_installed', 'lang', '', '{count} Texte aus dem Textpack installiert.', '2019-07-23 22:34:19'),
(4105, 'de', 'create_link', 'link', '', 'Neuer Link', '2019-07-23 22:34:19'),
(4106, 'de', 'edit_link', 'link', '', 'Link bearbeiten', '2019-07-23 22:34:19'),
(4107, 'de', 'links_deleted', 'link', '', 'Gelöschte Links: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4108, 'de', 'link_created', 'link', '', 'Link <strong>{name}</strong> erstellt.', '2019-07-23 22:34:19'),
(4109, 'de', 'link_empty', 'link', '', 'Links sollten nicht leer sein.', '2019-07-23 22:34:19'),
(4110, 'de', 'link_saved', 'link', '', 'Link gespeichert.', '2019-07-23 22:34:19'),
(4111, 'de', 'link_save_failed', 'link', '', 'Link konnte nicht gespeichert werden.', '2019-07-23 22:34:19'),
(4112, 'de', 'link_updated', 'link', '', 'Aktualisierte Links: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4113, 'de', 'no_links_recorded', 'link', '', 'Keine Links vorhanden.', '2019-07-23 22:34:19'),
(4114, 'de', 'search_links', 'link', '', 'Links suchen', '2019-07-23 22:34:19'),
(4115, 'de', 'articles_deleted', 'list', '', 'Gelöschte Artikel: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4116, 'de', 'articles_duplicated', 'list', '', 'Duplizierte Artikel: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4117, 'de', 'articles_modified', 'list', '', 'Bearbeitete Artikel: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4118, 'de', 'no_articles_recorded', 'list', '', 'Bisher gibt es keine Artikel.', '2019-07-23 22:34:19'),
(4119, 'de', 'search_articles', 'list', '', 'Artikel suchen', '2019-07-23 22:34:19'),
(4120, 'de', 'logs_deleted', 'log', '', 'Folgende Einträge wurden gelöscht: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4121, 'de', 'no_refers_recorded', 'log', '', 'Kein Referrer verfügbar.', '2019-07-23 22:34:19'),
(4122, 'de', 'referrer', 'log', '', 'Referrer', '2019-07-23 22:34:19'),
(4123, 'de', 'search_logs', 'log', '', 'Logs durchsuchen', '2019-07-23 22:34:19'),
(4124, 'de', 'all_pages', 'page', '', 'Alle Seitenvorlagen', '2019-07-23 22:34:19'),
(4125, 'de', 'create_page', 'page', '', 'Neue Seitenvorlage', '2019-07-23 22:34:19'),
(4126, 'de', 'page_already_exists', 'page', '', 'Seitenvorlage <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(4127, 'de', 'page_code', 'page', '', 'Seitenvorlage', '2019-07-23 22:34:19'),
(4128, 'de', 'page_created', 'page', '', 'Erstellte Seitenvorlagen: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4129, 'de', 'page_deleted', 'page', '', 'Gelöschte Seitenvorlagen: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4130, 'de', 'page_name', 'page', '', 'Name der Seitenvorlage (notwendig)', '2019-07-23 22:34:19'),
(4131, 'de', 'page_name_invalid', 'page', '', 'Der Name der Seitenvorlage ist ungültig.', '2019-07-23 22:34:19'),
(4132, 'de', 'page_save_failed', 'page', '', 'Die Seitenvorlage konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(4133, 'de', 'page_updated', 'page', '', 'Aktualisierte Seitenvorlagen: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4134, 'de', 'page_used_by_section', 'page', '', 'Seite <strong>{name}</strong> wurde nicht gelöscht. Sie wird von {count} Sektion(en) verwendet.', '2019-07-23 22:34:19'),
(4135, 'de', 'bad_plugin_code', 'plugin', '', 'Defekter oder leerer Plugin-Code', '2019-07-23 22:34:19'),
(4136, 'de', 'changeorder', 'plugin', '', 'Reihenfolge ändern', '2019-07-23 22:34:19'),
(4137, 'de', 'edit_plugin', 'plugin', '', 'Plugin <strong>{name}</strong> bearbeiten', '2019-07-23 22:34:19'),
(4138, 'de', 'edit_plugins', 'plugin', '', 'Plugins', '2019-07-23 22:34:19'),
(4139, 'de', 'install_plugin', 'plugin', '', 'Plugin installieren', '2019-07-23 22:34:19'),
(4140, 'de', 'order', 'plugin', '', 'Reihenfolge', '2019-07-23 22:34:19'),
(4141, 'de', 'plugin', 'plugin', '', 'Plugin', '2019-07-23 22:34:19'),
(4142, 'de', 'plugin_compression_unsupported', 'plugin', '', 'Komprimierte Plugins werden von diesem Server nicht unterstützt. Bitte kontaktieren Sie den Autor des Plugins wegen einer unkomprimierten Fassung.', '2019-07-23 22:34:19'),
(4143, 'de', 'plugin_deleted', 'plugin', '', 'Gelöschte Plugins: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4144, 'de', 'plugin_help', 'plugin', '', 'Plugin-Hilfe', '2019-07-23 22:34:19'),
(4145, 'de', 'plugin_installed', 'plugin', '', 'Plugin <strong>{name}</strong> installiert.', '2019-07-23 22:34:19'),
(4146, 'de', 'plugin_install_failed', 'plugin', '', 'Installation des Plugins <strong>{name}</strong> ist fehlgeschlagen.', '2019-07-23 22:34:19'),
(4147, 'de', 'plugin_saved', 'plugin', '', 'Plugin <strong>{name}</strong> aktualisiert.', '2019-07-23 22:34:19'),
(4148, 'de', 'plugin_updated', 'plugin', '', 'Aktualisierte Plugins: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4149, 'de', 'previewing_plugin', 'plugin', '', 'Plugin-Vorschau', '2019-07-23 22:34:19'),
(4150, 'de', 'search_plugins', 'plugin', '', 'Plugins durchsuchen', '2019-07-23 22:34:19'),
(4151, 'de', 'verify_plugin', 'plugin', '', 'Plugin-Vorschau', '2019-07-23 22:34:19'),
(4152, 'de', 'admin_side_plugins', 'prefs', '', 'Adminplugins verwenden?', '2019-07-23 22:34:19'),
(4153, 'de', 'allow_article_php_scripting', 'prefs', '', 'PHP in Artikeln erlauben?', '2019-07-23 22:34:19'),
(4154, 'de', 'allow_form_override', 'prefs', '', 'Zulassen, dass alternative Bausteine verwendet werden?', '2019-07-23 22:34:19'),
(4155, 'de', 'allow_page_php_scripting', 'prefs', '', 'PHP in Seiten erlauben?', '2019-07-23 22:34:19'),
(4156, 'de', 'all_hits', 'prefs', '', 'Alle Zugriffe', '2019-07-23 22:34:19'),
(4157, 'de', 'all_preferences', 'prefs', '', 'Alle Einstellungen', '2019-07-23 22:34:19'),
(4158, 'de', 'archive_dateformat', 'prefs', '', 'Archivdatumsformat', '2019-07-23 22:34:19'),
(4159, 'de', 'articles_use_excerpts', 'prefs', '', 'Artikel verwenden Exzerpte?', '2019-07-23 22:34:19'),
(4160, 'de', 'attach_titles_to_permalinks', 'prefs', '', 'Titel zu Permalinks anfügen?', '2019-07-23 22:34:19'),
(4161, 'de', 'category_subcategory', 'prefs', '', '/kategorie/unterkategorie', '2019-07-23 22:34:19'),
(4162, 'de', 'clean', 'prefs', '', '/sauber/', '2019-07-23 22:34:19'),
(4163, 'de', 'comments_are_ol', 'prefs', '', 'Kommentare als nummerierte Liste einfügen?', '2019-07-23 22:34:19'),
(4164, 'de', 'comments_auto_append', 'prefs', '', 'Kommentare automatisch an Artikel anhängen?', '2019-07-23 22:34:19'),
(4165, 'de', 'comments_dateformat', 'prefs', '', 'Datumsformat der Kommentare', '2019-07-23 22:34:19'),
(4166, 'de', 'comments_default_invite', 'prefs', '', 'Standardeinladung', '2019-07-23 22:34:19'),
(4167, 'de', 'comments_disabled_after', 'prefs', '', 'Kommentareingaben automatisch deaktivieren nach', '2019-07-23 22:34:19'),
(4168, 'de', 'comments_disallow_images', 'prefs', '', 'Bilder aus Kommentaren löschen?', '2019-07-23 22:34:19'),
(4169, 'de', 'comments_mode', 'prefs', '', 'Kommentarmodus', '2019-07-23 22:34:19'),
(4170, 'de', 'comments_moderate', 'prefs', '', 'Kommentare moderieren?', '2019-07-23 22:34:19'),
(4171, 'de', 'comments_on_default', 'prefs', '', 'Standardmäßig eingeschaltet', '2019-07-23 22:34:19'),
(4172, 'de', 'comments_require_email', 'prefs', '', 'E-Mail des Benutzers für Kommentare erforderlich?', '2019-07-23 22:34:19'),
(4173, 'de', 'comments_require_name', 'prefs', '', 'Name des Benutzers für Kommentare erforderlich?', '2019-07-23 22:34:19'),
(4174, 'de', 'comments_sendmail', 'prefs', '', 'Kommentare an Autor senden', '2019-07-23 22:34:19'),
(4175, 'de', 'comments_use_fat_textile', 'prefs', '', 'Zusätzliches Textile-Markup zulassen?', '2019-07-23 22:34:19'),
(4176, 'de', 'comment_means_site_updated', 'prefs', '', 'Neue Kommentare setzen Seitenstatus auf aktualisiert?', '2019-07-23 22:34:19'),
(4177, 'de', 'comment_nofollow', 'prefs', '', 'Verwende rel=\"nofollow\" bei Kommentaren?', '2019-07-23 22:34:19'),
(4178, 'de', 'custom_10_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 10', '2019-07-23 22:34:19'),
(4179, 'de', 'custom_1_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 1', '2019-07-23 22:34:19'),
(4180, 'de', 'custom_2_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 2', '2019-07-23 22:34:19'),
(4181, 'de', 'custom_3_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 3', '2019-07-23 22:34:19'),
(4182, 'de', 'custom_4_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 4', '2019-07-23 22:34:19'),
(4183, 'de', 'custom_5_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 5', '2019-07-23 22:34:19'),
(4184, 'de', 'custom_6_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 6', '2019-07-23 22:34:19'),
(4185, 'de', 'custom_7_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 7', '2019-07-23 22:34:19'),
(4186, 'de', 'custom_8_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 8', '2019-07-23 22:34:19'),
(4187, 'de', 'custom_9_set', 'prefs', '', 'Name des benutzerdefinierten Feldes Nr. 9', '2019-07-23 22:34:19'),
(4188, 'de', 'custom_form_types', 'prefs', '', 'Benutzerdefinierte Bausteintypen', '2019-07-23 22:34:19'),
(4189, 'de', 'default_event', 'prefs', '', 'Standard-Menü', '2019-07-23 22:34:19'),
(4190, 'de', 'default_publish_status', 'prefs', '', 'Status beim Erstellen von Artikeln', '2019-07-23 22:34:19'),
(4191, 'de', 'doctype', 'prefs', '', 'Doctype', '2019-07-23 22:34:19'),
(4192, 'de', 'module_pophelp', 'prefs', '', 'Links für Inline-Hilfe anzeigen', '2019-07-23 22:34:19'),
(4193, 'de', 'enable_dev_preview', 'prefs', '', 'Vorschau des in Entwicklung befindlichen Designs aktivieren?', '2019-07-23 22:34:19'),
(4194, 'de', 'enable_short_tags', 'prefs', '', 'Verwendung von Kurz-Tags aktivieren', '2019-07-23 22:34:19'),
(4195, 'de', 'enable_xmlrpc_server', 'prefs', '', 'XML-RPC-Server aktivieren?', '2019-07-23 22:34:19'),
(4196, 'de', 'expire_logs_after', 'prefs', '', 'Protokolle nach wievielen Tagen löschen?', '2019-07-23 22:34:19'),
(4197, 'de', 'feeds', 'prefs', '', 'XML-Feeds', '2019-07-23 22:34:19'),
(4198, 'de', 'file_max_upload_size', 'prefs', '', 'Maximale Upload-Dateigröße (in Bytes)', '2019-07-23 22:34:19'),
(4199, 'de', 'ham', 'prefs', '', 'Alle außer Spam', '2019-07-23 22:34:19'),
(4200, 'de', 'hours_days_ago', 'prefs', '', 'vor Stunden/Tagen', '2019-07-23 22:34:19'),
(4201, 'de', 'id_title', 'prefs', '', '/id/titel', '2019-07-23 22:34:19'),
(4202, 'de', 'include_email_atom', 'prefs', '', 'E-Mail in Atom-Feeds publizieren?', '2019-07-23 22:34:19'),
(4203, 'de', 'language', 'prefs', '', 'Sprache', '2019-07-23 22:34:19'),
(4204, 'de', 'logging', 'prefs', '', 'Protokollierung', '2019-07-23 22:34:19'),
(4205, 'de', 'logs_expire', 'prefs', '', 'Lösche Protokolle nach', '2019-07-23 22:34:19'),
(4206, 'de', 'markup_default', 'prefs', '', 'Standard-Formatierung', '2019-07-23 22:34:19'),
(4207, 'de', 'max_url_len', 'prefs', '', 'Maximale URL-Länge', '2019-07-23 22:34:19'),
(4208, 'de', 'messy', 'prefs', '', '?=unordentlich', '2019-07-23 22:34:19'),
(4209, 'de', 'never_display_email', 'prefs', '', 'E-Mail in Kommentaren verbergen?', '2019-07-23 22:34:19'),
(4210, 'de', 'no_preferences', 'prefs', '', 'Keine Einstellungen verfügbar.', '2019-07-23 22:34:19'),
(4211, 'de', 'override_emailcharset', 'prefs', '', 'E-Mails mit ISO-8559-1 versenden? (Standard ist utf-8)', '2019-07-23 22:34:19'),
(4212, 'de', 'path_from_root', 'prefs', '', 'Unterverzeichnis (falls vorhanden)', '2019-07-23 22:34:19'),
(4213, 'de', 'permlink_format', 'prefs', '', 'Permalink-Format', '2019-07-23 22:34:19'),
(4214, 'de', 'permlink_hyphenated', 'prefs', '', 'Getrennt (ein-titel)', '2019-07-23 22:34:19'),
(4215, 'de', 'permlink_intercapped', 'prefs', '', 'Angrenzend (EinTitel)', '2019-07-23 22:34:19'),
(4216, 'de', 'plugin_cache_dir', 'prefs', '', 'Pfad des Pluginzwischenspeichers', '2019-07-23 22:34:19'),
(4217, 'de', 'prefs', 'prefs', '', 'Einstellungen', '2019-07-23 22:34:19'),
(4218, 'de', 'production_debug', 'prefs', '', 'Debug', '2019-07-23 22:34:19'),
(4219, 'de', 'production_live', 'prefs', '', 'Live', '2019-07-23 22:34:19'),
(4220, 'de', 'production_status', 'prefs', '', 'Produktions-Status', '2019-07-23 22:34:19'),
(4221, 'de', 'production_test', 'prefs', '', 'Test', '2019-07-23 22:34:19'),
(4222, 'de', 'publisher_email', 'prefs', '', 'Anmeldeinformationen von dieser E-Mail-Adresse senden', '2019-07-23 22:34:19'),
(4223, 'de', 'publish_expired_articles', 'prefs', '', 'Abgelaufene Artikel veröffentlichen?', '2019-07-23 22:34:19'),
(4224, 'de', 'referrers_only', 'prefs', '', 'nur Referrer', '2019-07-23 22:34:19'),
(4225, 'de', 'rss_how_many', 'prefs', '', 'Wieviele Artikel sollen im RSS-Feed angezeigt werden?', '2019-07-23 22:34:19'),
(4226, 'de', 'section_id_title', 'prefs', '', '/sektion/id/titel', '2019-07-23 22:34:19'),
(4227, 'de', 'section_title', 'prefs', '', '/sektion/titel', '2019-07-23 22:34:19'),
(4228, 'de', 'send_lastmod', 'prefs', '', 'Sende \"Last-Modified\" Header', '2019-07-23 22:34:19'),
(4229, 'de', 'show_comment_count_in_feed', 'prefs', '', 'Zeige die Anzahl der Kommentare in den XML-Feeds?', '2019-07-23 22:34:19'),
(4230, 'de', 'smtp_from', 'prefs', '', 'Inhalt des SMTP-Headers \"From:\"', '2019-07-23 22:34:19'),
(4231, 'de', 'spam_blacklists', 'prefs', '', 'Spam-Blacklists (kommagetrennt)', '2019-07-23 22:34:19'),
(4232, 'de', 'syndicate_body_or_excerpt', 'prefs', '', '<strong>Nur</strong> Exzerpte syndizieren?', '2019-07-23 22:34:19'),
(4233, 'de', 'timeoffset', 'prefs', '', 'Zeitverschiebung (Stunden)', '2019-07-23 22:34:19'),
(4234, 'de', 'title_no_widow', 'prefs', '', 'Verhindere Witwen (\"Hurenkinder\") in Artikeltiteln?', '2019-07-23 22:34:19'),
(4235, 'de', 'title_only', 'prefs', '', '/titel', '2019-07-23 22:34:19'),
(4236, 'de', 'txp_evaluate_functions', 'prefs', '', 'PHP-Funktionen in <code>txp:evaluate</code> aktiviert', '2019-07-23 22:34:19'),
(4237, 'de', 'tz_timezone', 'prefs', '', 'Zeitzone', '2019-07-23 22:34:19'),
(4238, 'de', 'up_to_date', 'prefs', '', 'Aktuell', '2019-07-23 22:34:19'),
(4239, 'de', 'url_mode', 'prefs', '', 'URL-Modus', '2019-07-23 22:34:19'),
(4240, 'de', 'use_comments', 'prefs', '', 'Kommentare annehmen?', '2019-07-23 22:34:19'),
(4241, 'de', 'use_mail_on_feeds_id', 'prefs', '', 'E-Mail für XML-Feeds verwenden?', '2019-07-23 22:34:19'),
(4242, 'de', 'use_plugins', 'prefs', '', 'Plugins verwenden?', '2019-07-23 22:34:19'),
(4243, 'de', 'year_month_day_title', 'prefs', '', '/jahr/monat/tag/titel', '2019-07-23 22:34:19'),
(4244, 'de', '403_forbidden', 'public', '', 'Verboten.', '2019-07-23 22:34:19'),
(4245, 'de', '404_not_found', 'public', '', 'Die angeforderte Seite konnte nicht gefunden werden.', '2019-07-23 22:34:19'),
(4246, 'de', '410_gone', 'public', '', 'Die angeforderte Seite ist nicht mehr vorhanden.', '2019-07-23 22:34:19'),
(4247, 'de', '500_internal_server_error', 'public', '', 'Interner Serverfehler.', '2019-07-23 22:34:19'),
(4248, 'de', 'articles_found', 'public', '', 'Artikel gefunden', '2019-07-23 22:34:19'),
(4249, 'de', 'article_context', 'public', '', 'artikel', '2019-07-23 22:34:19'),
(4250, 'de', 'article_found', 'public', '', 'Artikel gefunden', '2019-07-23 22:34:19'),
(4251, 'de', 'atom_feed_title', 'public', '', 'Atom-Feed', '2019-07-23 22:34:19'),
(4252, 'de', 'a_few_seconds', 'public', '', 'ein paar Sekunden', '2019-07-23 22:34:19'),
(4253, 'de', 'comments_closed', 'public', '', 'Kommentarfunktion für diesen Artikel geschlossen.', '2019-07-23 22:34:19'),
(4254, 'de', 'comments_on', 'public', '', 'Kommentare eingeschaltet', '2019-07-23 22:34:19'),
(4255, 'de', 'comments_permlink', 'public', '', 'Permanenter Link', '2019-07-23 22:34:19'),
(4256, 'de', 'comment_comment', 'public', '', 'Kommentar', '2019-07-23 22:34:19'),
(4257, 'de', 'comment_duplicate', 'public', '', 'Doppelte Kommentare werden nicht akzeptiert.', '2019-07-23 22:34:19'),
(4258, 'de', 'comment_email', 'public', '', 'E-Mail', '2019-07-23 22:34:19'),
(4259, 'de', 'comment_email_required', 'public', '', 'Bitte eine gültige E-Mail eingeben', '2019-07-23 22:34:19'),
(4260, 'de', 'comment_message', 'public', '', 'Nachricht', '2019-07-23 22:34:19'),
(4261, 'de', 'comment_moderated', 'public', '', 'Der Kommentar wird moderiert. Er wird öffentlich sichtbar, sobald er freigeschaltet wurde.', '2019-07-23 22:34:19'),
(4262, 'de', 'comment_name', 'public', '', 'Name', '2019-07-23 22:34:19'),
(4263, 'de', 'comment_name_required', 'public', '', 'Bitte Namen eingeben.', '2019-07-23 22:34:19'),
(4264, 'de', 'comment_posted', 'public', '', 'Danke für den Kommentar.', '2019-07-23 22:34:19'),
(4265, 'de', 'comment_received', 'public', '', '{site} Kommentar zu: {title}', '2019-07-23 22:34:19'),
(4266, 'de', 'comment_recorded', 'public', '', 'Es wurde ein Kommentar zu deinem Beitrag “{title}” abgegeben.', '2019-07-23 22:34:19'),
(4267, 'de', 'comment_required', 'public', '', 'Bitte einen Kommentar eingeben.', '2019-07-23 22:34:19'),
(4268, 'de', 'comment_web', 'public', '', 'Web', '2019-07-23 22:34:19'),
(4269, 'de', 'continue_reading', 'public', '', 'Weiterlesen', '2019-07-23 22:34:19'),
(4270, 'de', 'day', 'public', '', 'Tag', '2019-07-23 22:34:19'),
(4271, 'de', 'days', 'public', '', 'Tage', '2019-07-23 22:34:19'),
(4272, 'de', 'enter_comment_here', 'public', '', '<strong>Geben Sie Ihren Kommentar hier ein.</strong> <b class=\"required\" title=\"Erforderlich\">*</b> Eingabe erforderlich. Sie müssen die Vorschau vor dem Absenden ansehen.', '2019-07-23 22:34:19'),
(4273, 'de', 'file_context', 'public', '', 'datei', '2019-07-23 22:34:19'),
(4274, 'de', 'hour', 'public', '', 'Stunde', '2019-07-23 22:34:19'),
(4275, 'de', 'hours', 'public', '', 'Stunden', '2019-07-23 22:34:19'),
(4276, 'de', 'image_context', 'public', '', 'bild', '2019-07-23 22:34:19'),
(4277, 'de', 'link_context', 'public', '', 'link', '2019-07-23 22:34:19'),
(4278, 'de', 'main_content', 'public', '', 'Hauptinhalt', '2019-07-23 22:34:19'),
(4279, 'de', 'matching_search_request', 'public', '', 'passend zu Ihren Suchkriterien', '2019-07-23 22:34:19'),
(4280, 'de', 'minute', 'public', '', 'Minute', '2019-07-23 22:34:19'),
(4281, 'de', 'minutes', 'public', '', 'Minuten', '2019-07-23 22:34:19'),
(4282, 'de', 'nopopup', 'public', '', 'Aktuelles Fenster', '2019-07-23 22:34:19'),
(4283, 'de', 'no_comments', 'public', '', 'Keine Kommentare', '2019-07-23 22:34:19'),
(4284, 'de', 'no_search_matches', 'public', '', 'Leider wurden keine Ergebnisse zu Ihren Suchkriterien gefunden.', '2019-07-23 22:34:19'),
(4285, 'de', 'page_nav', 'public', '', 'Artikelnavigation', '2019-07-23 22:34:19'),
(4286, 'de', 'permanent_link', 'public', '', 'Permanenter Link zu diesem Artikel', '2019-07-23 22:34:19'),
(4287, 'de', 'popup', 'public', '', 'Pop-up-Fenster', '2019-07-23 22:34:19'),
(4288, 'de', 'press_preview_then_submit', 'public', '', 'Das ist eine Vorschau Ihres Kommentars. Bitte vergessen Sie nicht, die Schaltfläche <strong><a href=\"#txpCommentSubmit\" title=\"Weiter zur Absenden-Schaltfläche\">Absenden</a></strong> zu drücken!', '2019-07-23 22:34:19'),
(4289, 'de', 'read_more', 'public', '', 'Mehr infos', '2019-07-23 22:34:19'),
(4290, 'de', 'rss_feed_title', 'public', '', 'RSS-Feed', '2019-07-23 22:34:19'),
(4291, 'de', 'site_nav', 'public', '', 'Seitennavigation', '2019-07-23 22:34:19'),
(4292, 'de', 'textile_help', 'public', '', 'Textile-Hilfe', '2019-07-23 22:34:19'),
(4293, 'de', 'too_common_search_term', 'public', '', 'Ihre Suchkriterien sind sehr allgemein gehalten. Bitte versuchen Sie etwas speziellere Suchbegriffe.', '2019-07-23 22:34:19'),
(4294, 'de', 'week', 'public', '', 'Woche', '2019-07-23 22:34:19'),
(4295, 'de', 'weeks', 'public', '', 'Wochen', '2019-07-23 22:34:19'),
(4296, 'de', 'your_ip_is_blacklisted_by', 'public', '', 'Ihre IP-Adresse wurde auf die schwarze Liste gesetzt von', '2019-07-23 22:34:19'),
(4297, 'de', 'yyyy-mm', 'public', '', 'mm-jjjj', '2019-07-23 22:34:19'),
(4298, 'de', 'article_count', 'section', '', '{num} Artikel in dieser Sektion.', '2019-07-23 22:34:19'),
(4299, 'de', 'change_page_style', 'section', '', 'Design/Seitenvorlage/Stilvorlage ändern', '2019-07-23 22:34:19'),
(4300, 'de', 'create_section', 'section', '', 'Neue Sektion', '2019-07-23 22:34:19'),
(4301, 'de', 'default_section_updated', 'section', '', 'Sektion <strong>default</strong> aktualisiert', '2019-07-23 22:34:19'),
(4302, 'de', 'default_write_section', 'section', '', 'Standard-Sektion', '2019-07-23 22:34:19'),
(4303, 'de', 'dev_theme', 'section', '', 'Entwicklung', '2019-07-23 22:34:19'),
(4304, 'de', 'dev_to_live', 'section', '', 'Auf den Live-Status zurücksetzen', '2019-07-23 22:34:19'),
(4305, 'de', 'edit_default_section', 'section', '', 'Standard-Sektion bearbeiten', '2019-07-23 22:34:19'),
(4306, 'de', 'edit_section', 'section', '', 'Sektion bearbeiten', '2019-07-23 22:34:19'),
(4307, 'de', 'include_in_search', 'section', '', 'In Seitensuche einbeziehen?', '2019-07-23 22:34:19'),
(4308, 'de', 'live_theme', 'section', '', 'Live', '2019-07-23 22:34:19'),
(4309, 'de', 'live_to_dev', 'section', '', 'Auf den Live-Status anwenden', '2019-07-23 22:34:19'),
(4310, 'de', 'search_sections', 'section', '', 'Sektionen durchsuchen', '2019-07-23 22:34:19'),
(4311, 'de', 'section_created', 'section', '', 'Sektion <strong><a href=\"#section-{name}\">{name}</a></strong> erstellt.', '2019-07-23 22:34:19'),
(4312, 'de', 'section_delete_failure', 'section', '', 'Sektion(en) konnte(n) nicht gelöscht werden, Artikel sind momentan {name} zugeordnet.', '2019-07-23 22:34:19'),
(4313, 'de', 'section_deleted', 'section', '', 'Gelöschte Sektionen: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4314, 'de', 'section_head', 'section', '', 'Sektionen', '2019-07-23 22:34:19'),
(4315, 'de', 'section_longtitle', 'section', '', 'Sektionstitel', '2019-07-23 22:34:19'),
(4316, 'de', 'section_name', 'section', '', 'Sektionsname', '2019-07-23 22:34:19'),
(4317, 'de', 'section_name_already_exists', 'section', '', 'Sektion <strong>{name}</strong> existiert bereits.', '2019-07-23 22:34:19'),
(4318, 'de', 'section_save_failed', 'section', '', 'Die Sektion konnte wegen eines Fehlers nicht gespeichert werden. Bitte nochmals versuchen.', '2019-07-23 22:34:19'),
(4319, 'de', 'section_updated', 'section', '', 'Aktualisierte Sektionen: <strong>{name}</strong>.', '2019-07-23 22:34:19'),
(4320, 'de', 'switch_dev_live', 'section', '', 'Entwickler-Vorschau', '2019-07-23 22:34:19'),
(4321, 'de', 'syndicate', 'section', '', 'In Newsfeeds aufnehmen?', '2019-07-23 22:34:19'),
(4322, 'de', 'uses_page', 'section', '', 'Verwendet Seitenvorlage', '2019-07-23 22:34:19'),
(4323, 'de', 'uses_skin', 'section', '', 'Verwendet das Design', '2019-07-23 22:34:19'),
(4324, 'de', 'uses_style', 'section', '', 'Verwendet CSS', '2019-07-23 22:34:19'),
(4325, 'de', 'create_skin', 'skin', '', 'Neues Design', '2019-07-23 22:34:19'),
(4326, 'de', 'css_creation_failed', 'skin', '', 'Folgende Stilvorlagen konnten nicht erstellt werden: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4327, 'de', 'css_deletion_failed', 'skin', '', 'Folgende Stilvorlagen konnten nicht gelöscht werden: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4328, 'de', 'css_files_deletion_failed', 'skin', '', 'Löschen der Stilvorlagen-Dateien fehlgeschlagen bei: <strong>{$list}</strong>.', '2019-07-23 22:34:19'),
(4329, 'de', 'css_import_failed', 'skin', '', 'Folgende Stilvorlagen konnten nicht importiert werden: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4330, 'de', 'css_name_unsafe', 'skin', '', 'Folgende Stilvorlagen haben nicht-exportierbare Namen: <strong>{list}</strong>. Namen müssen dem folgenden regulären Ausdruck entsprechen: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:34:19'),
(4331, 'de', 'css_not_found', 'skin', '', 'Keine Stilvorlagen gefunden für: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4332, 'de', 'css_update_failed', 'skin', '', 'Aktualisierung von Stilvorlagen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4333, 'de', 'edit_skin', 'skin', '', 'Design bearbeiten', '2019-07-23 22:34:19'),
(4334, 'de', 'form_creation_failed', 'skin', '', 'Erstellung von Bausteinen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4335, 'de', 'form_deletion_failed', 'skin', '', 'Löschen von Bausteinen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4336, 'de', 'form_duplicate_failed', 'skin', '', 'Der Import folgender duplizierter Bausteine ist fehlgeschlagen: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4337, 'de', 'form_files_deletion_failed', 'skin', '', 'Löschen der Baustein-Dateien fehlgeschlagen bei: <strong>{$list}</strong>.', '2019-07-23 22:34:19'),
(4338, 'de', 'form_import_failed', 'skin', '', 'Import von Bausteinen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4339, 'de', 'form_name_unsafe', 'skin', '', 'Folgende Bausteine haben nicht-exportierbare Namen: <strong>{list}</strong>. Namen müssen dem folgenden regulären Ausdruck entsprechen: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:34:19'),
(4340, 'de', 'form_not_found', 'skin', '', 'Kein Baustein gefunden für: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4341, 'de', 'form_subdir_error', 'skin', '', 'Fehler bei Bausteintypen: <strong>{list}</strong>. Diese Bausteine werden als Standardvorgabe von Tags benutzt und sollten bestimmte Typen verwenden.', '2019-07-23 22:34:19'),
(4342, 'de', 'form_subdir_invalid', 'skin', '', 'Unzulässiger Bausteintyp für: <strong>{list}</strong>. Zulässige Typen sind: <code>article</code>, <code>category</code>, <code>comment</code>, <code>file</code>, <code>link</code>, <code>section</code> and <code>misc</code>. <code>misc</code> wird als Standardtyp verwendet.', '2019-07-23 22:34:19'),
(4343, 'de', 'form_update_failed', 'skin', '', 'Aktualisierung der Bausteine fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4344, 'de', 'no_skin_recorded', 'skin', '', 'Keine Designs vorhanden.', '2019-07-23 22:34:19'),
(4345, 'de', 'page_creation_failed', 'skin', '', 'Erstellung der Seitenvorlagen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4346, 'de', 'page_deletion_failed', 'skin', '', 'Löschen der Seitenvorlagen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4347, 'de', 'page_files_deletion_failed', 'skin', '', 'Löschen der Seitenvorlagen-Dateien fehlgeschlagen bei: <strong>{$list}</strong>.', '2019-07-23 22:34:19'),
(4348, 'de', 'page_import_failed', 'skin', '', 'Import der Seitenvorlagen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4349, 'de', 'page_name_unsafe', 'skin', '', 'Folgende Seitenvorlagen haben nicht-exportierbare Namen: <strong>{list}</strong>. Namen müssen dem folgenden regulären Ausdruck entsprechen: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:34:19'),
(4350, 'de', 'page_not_found', 'skin', '', 'Es wurde keine Seitenvorlage gefunden für: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4351, 'de', 'page_update_failed', 'skin', '', 'Aktualisierung der Seitenvorlagen fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4352, 'de', 'search_skins', 'skin', '', 'Designs suchen', '2019-07-23 22:34:19'),
(4353, 'de', 'skin_already_exists', 'skin', '', 'Diese Designs gibt es bereits in der Datenbank oder als Verzeichnisse, was Probleme verursachen könnte: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4354, 'de', 'skin_author', 'skin', '', 'Urheber des Designs', '2019-07-23 22:34:19'),
(4355, 'de', 'skin_author_uri', 'skin', '', 'Website des Designs', '2019-07-23 22:34:19'),
(4356, 'de', 'skin_count_css', 'skin', '', 'Stilvorlagen in diesem Design: {num}.', '2019-07-23 22:34:19'),
(4357, 'de', 'skin_count_form', 'skin', '', 'Bausteine in diesem Design: {num}.', '2019-07-23 22:34:19'),
(4358, 'de', 'skin_count_page', 'skin', '', 'Seitenvorlagen in diesem Design: {num}.', '2019-07-23 22:34:19'),
(4359, 'de', 'skin_count_section', 'skin', '', 'Sektionen in diesem Design: {num}.', '2019-07-23 22:34:19'),
(4360, 'de', 'skin_created', 'skin', '', 'Designs erzeugt: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4361, 'de', 'skin_creation_failed', 'skin', '', 'Erstellung von Designs fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4362, 'de', 'skin_delete_entirely', 'skin', '', 'Design-Vorlagen auch von der Festplatte löschen.', '2019-07-23 22:34:19'),
(4363, 'de', 'skin_delete_from_database', 'skin', '', 'Beim Importieren die nicht benutzten Vorlagen aus der Datenbank löschen.', '2019-07-23 22:34:19'),
(4364, 'de', 'skin_delete_from_disk', 'skin', '', 'Beim Exportieren die nicht benutzten Vorlagen von der Festplatte löschen.', '2019-07-23 22:34:19'),
(4365, 'de', 'skin_deleted', 'skin', '', 'Gelöschte Designs: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4366, 'de', 'skin_deletion_failed', 'skin', '', 'Löschen von Design(s) fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4367, 'de', 'skin_description', 'skin', '', 'Beschreibung des Designs', '2019-07-23 22:34:19'),
(4368, 'de', 'skin_export_failed', 'skin', '', 'Export des Designs fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4369, 'de', 'skin_exported', 'skin', '', 'Exportierte Designs: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4370, 'de', 'skin_files_deletion_failed', 'skin', '', 'Löschen der Design-Dateien fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4371, 'de', 'skin_import_failed', 'skin', '', 'Import des Designs fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4372, 'de', 'skin_imported', 'skin', '', 'Importierte Designs: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4373, 'de', 'skin_in_use', 'skin', '', 'Löschen folgender Designs ist fehlgeschlagen, da sie von Sektionen benutzt werden: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4374, 'de', 'skin_name', 'skin', '', 'Name des Designs', '2019-07-23 22:34:19'),
(4375, 'de', 'skin_name_unsafe', 'skin', '', 'Folgende Designs haben nicht-exportierbare Namen: <strong>{list}</strong>. Namen müssen dem folgenden regulären Ausdruck entsprechen: <code>^[a-z][a-z0-9_\\-\\.]{0,63}$</code>.', '2019-07-23 22:34:19'),
(4376, 'de', 'skin_not_found', 'skin', '', 'Keine Design-Daten gefunden für: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4377, 'de', 'skin_related_sections_update_failed', 'skin', '', 'Aktualisierung der folgenden Design-zugehörigen Sektionen fehlgeschlagen: <strong>{list}</strong>. Bitte aktualisieren Sie diese Sektionen manuell auf der Sektionen-Seite.', '2019-07-23 22:34:19'),
(4378, 'de', 'skin_title', 'skin', '', 'Titel des Designs', '2019-07-23 22:34:19'),
(4379, 'de', 'skin_unknown', 'skin', '', 'Unbekannte Designs: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4380, 'de', 'skin_update_failed', 'skin', '', 'Aktualisierung des Designs fehlgeschlagen bei: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4381, 'de', 'skin_updated', 'skin', '', 'Aktualisierte Designs: <strong>{list}</strong>.', '2019-07-23 22:34:19'),
(4382, 'de', 'skin_version', 'skin', '', 'Version des Designs', '2019-07-23 22:34:19'),
(4383, 'de', 'active_class', 'tag', '', 'CSS-Klasse für aktives Listenelement', '2019-07-23 22:34:19'),
(4384, 'de', 'allowoverride', 'tag', '', 'Zulassen, dass alternative Bausteine verwendet werden?', '2019-07-23 22:34:19'),
(4385, 'de', 'breadcrumb_linked', 'tag', '', 'Breadcrumb-Navigation verlinken?', '2019-07-23 22:34:19'),
(4386, 'de', 'breadcrumb_separator', 'tag', '', 'Trennzeichen für Breadcrumb-Navigation', '2019-07-23 22:34:19'),
(4387, 'de', 'break', 'tag', '', 'Listenumbruch', '2019-07-23 22:34:19'),
(4388, 'de', 'breakclass', 'tag', '', 'CSS-Klasse für den Listenumbruch', '2019-07-23 22:34:19'),
(4389, 'de', 'build', 'tag', '', 'Tag erstellen', '2019-07-23 22:34:19'),
(4390, 'de', 'button_text', 'tag', '', 'Beschriftung der Schaltfläche', '2019-07-23 22:34:19'),
(4391, 'de', 'category_list_section', 'tag', '', 'Link auf bestimmte Sektion?', '2019-07-23 22:34:19'),
(4392, 'de', 'category_tags', 'tag', '', 'Kategorie', '2019-07-23 22:34:19'),
(4393, 'de', 'class', 'tag', '', 'CSS-Klasse', '2019-07-23 22:34:19'),
(4394, 'de', 'comments_form', 'tag', '', 'Kommentarformular', '2019-07-23 22:34:19'),
(4395, 'de', 'comment_details', 'tag', '', 'Kommentardetails', '2019-07-23 22:34:19'),
(4396, 'de', 'comment_form', 'tag', '', 'Kommentarformular', '2019-07-23 22:34:19'),
(4397, 'de', 'comment_name_link', 'tag', '', 'Verlinkung zur E-Mail-Adresse und Webseite des Kommentiereden einfügen?', '2019-07-23 22:34:19'),
(4398, 'de', 'decimals', 'tag', '', 'Zeige x Stellen nach dem Dezimalpunkt', '2019-07-23 22:34:19'),
(4399, 'de', 'default_title', 'tag', '', 'Text für den Standard-Sektionslink', '2019-07-23 22:34:19'),
(4400, 'de', 'depth', 'tag', '', 'Tiefe', '2019-07-23 22:34:19'),
(4401, 'de', 'escape', 'tag', '', 'Escape', '2019-07-23 22:34:19'),
(4402, 'de', 'exclude', 'tag', '', 'Ausschließen', '2019-07-23 22:34:19'),
(4403, 'de', 'filename', 'tag', '', 'Name', '2019-07-23 22:34:19'),
(4404, 'de', 'file_download_tags', 'tag', '', 'Dateidownloads', '2019-07-23 22:34:19'),
(4405, 'de', 'flavor', 'tag', '', 'Newsfeed-Format', '2019-07-23 22:34:19'),
(4406, 'de', 'format', 'tag', '', 'Format', '2019-07-23 22:34:19'),
(4407, 'de', 'gmt', 'tag', '', 'Als <abbr title=\"Greenwich Mean Time\">GMT</abbr> formatieren?', '2019-07-23 22:34:19'),
(4408, 'de', 'has_excerpt', 'tag', '', 'Beinhaltet ein Exzerpt', '2019-07-23 22:34:19'),
(4409, 'de', 'hilight', 'tag', '', 'Tag hervorheben', '2019-07-23 22:34:19'),
(4410, 'de', 'hilight_limit', 'tag', '', 'Zeige die Anzahl der Ebenen?', '2019-07-23 22:34:19'),
(4411, 'de', 'html_id', 'tag', '', '<code>id</code>-Attribut (HTML)', '2019-07-23 22:34:19'),
(4412, 'de', 'include_default', 'tag', '', 'Standard-Sektion einbeziehen?', '2019-07-23 22:34:19'),
(4413, 'de', 'inline_style', 'tag', '', 'direkte Stilangaben (CSS)', '2019-07-23 22:34:19'),
(4414, 'de', 'input_size', 'tag', '', 'Textfeldgröße', '2019-07-23 22:34:19'),
(4415, 'de', 'labeltag', 'tag', '', 'Beschriftungstag', '2019-07-23 22:34:19'),
(4416, 'de', 'limit', 'tag', '', 'Zeige wie viele?', '2019-07-23 22:34:19'),
(4417, 'de', 'linkclass', 'tag', '', 'CSS-Klasse für Links', '2019-07-23 22:34:19'),
(4418, 'de', 'link_text', 'tag', '', 'Linktext', '2019-07-23 22:34:19'),
(4419, 'de', 'link_to_this_author', 'tag', '', 'Link zur Liste weiterer Artikel dieses Autors?', '2019-07-23 22:34:19'),
(4420, 'de', 'link_to_this_category', 'tag', '', 'Link zur Liste weiterer Artikel in dieser Kategorie?', '2019-07-23 22:34:19'),
(4421, 'de', 'link_to_this_section', 'tag', '', 'Link zur Liste weiterer Artikel in dieser Sektion?', '2019-07-23 22:34:19'),
(4422, 'de', 'listform', 'tag', '', 'Listenbaustein', '2019-07-23 22:34:19'),
(4423, 'de', 'match_type', 'tag', '', 'Art der Übereinstimmung', '2019-07-23 22:34:19'),
(4424, 'de', 'media', 'tag', '', '<code>media</code>-Attribut (HTML)', '2019-07-23 22:34:19'),
(4425, 'de', 'msgcols', 'tag', '', 'Spalten des Nachrichtenfeldes', '2019-07-23 22:34:19'),
(4426, 'de', 'msgrows', 'tag', '', 'Zeilen des Nachrichtenfeld', '2019-07-23 22:34:19'),
(4427, 'de', 'no_categories_available', 'tag', '', 'Keine Kategorie verfügbar.', '2019-07-23 22:34:19'),
(4428, 'de', 'no_forms_available', 'tag', '', 'Keine Bausteine verfügbar.', '2019-07-23 22:34:19'),
(4429, 'de', 'no_sections_available', 'tag', '', 'Keine Sektionen verfügbar.', '2019-07-23 22:34:19'),
(4430, 'de', 'offset', 'tag', '', 'Überspringe', '2019-07-23 22:34:19'),
(4431, 'de', 'page_article_hed', 'tag', '', 'Artikelausgabe', '2019-07-23 22:34:19'),
(4432, 'de', 'page_article_nav_hed', 'tag', '', 'Artikelnavigation', '2019-07-23 22:34:19'),
(4433, 'de', 'page_file_hed', 'tag', '', 'Dateidownloads', '2019-07-23 22:34:19'),
(4434, 'de', 'page_misc_hed', 'tag', '', 'Verschiedenes', '2019-07-23 22:34:19'),
(4435, 'de', 'page_nav_hed', 'tag', '', 'Seitennavigation', '2019-07-23 22:34:19'),
(4436, 'de', 'page_xml_hed', 'tag', '', 'XML-Feeds', '2019-07-23 22:34:19'),
(4437, 'de', 'pageby', 'tag', '', 'Paginieren mit', '2019-07-23 22:34:19'),
(4438, 'de', 'pgonly', 'tag', '', 'Zähle Artikel, aber zeige keine (pgonly)', '2019-07-23 22:34:19'),
(4439, 'de', 'random', 'tag', '', 'Zufällig', '2019-07-23 22:34:19'),
(4440, 'de', 'rel', 'tag', '', '<code>rel</code>-Attribut (HTML)', '2019-07-23 22:34:19'),
(4441, 'de', 'searchall', 'tag', '', 'Durchsuche alle Sektionen?', '2019-07-23 22:34:19'),
(4442, 'de', 'searchsticky', 'tag', '', 'Durchsuche fixierte Artikel?', '2019-07-23 22:34:19'),
(4443, 'de', 'search_input_form', 'tag', '', 'Sucheingabefeld', '2019-07-23 22:34:19'),
(4444, 'de', 'search_results_form', 'tag', '', 'Suchergebnisse', '2019-07-23 22:34:19'),
(4445, 'de', 'section_tags', 'tag', '', 'Sektion', '2019-07-23 22:34:19'),
(4446, 'de', 'separator', 'tag', '', 'Trennzeichen', '2019-07-23 22:34:19'),
(4447, 'de', 'showalways', 'tag', '', 'Zeige immer', '2019-07-23 22:34:19'),
(4448, 'de', 'showcount', 'tag', '', 'Zeige Anzahl?', '2019-07-23 22:34:19'),
(4449, 'de', 'size_format', 'tag', '', 'Größenformat', '2019-07-23 22:34:19'),
(4450, 'de', 'sort', 'tag', '', 'Sortiere nach', '2019-07-23 22:34:19'),
(4451, 'de', 'tag', 'tag', '', 'Tag', '2019-07-23 22:34:19'),
(4452, 'de', 'tag_article', 'tag', '', 'Artikel (einzeln oder als Liste)', '2019-07-23 22:34:19'),
(4453, 'de', 'tag_article_custom', 'tag', '', 'Artikel (benutzerdefinierte Liste)', '2019-07-23 22:34:19'),
(4454, 'de', 'tag_article_image', 'tag', '', 'Artikelbild', '2019-07-23 22:34:19'),
(4455, 'de', 'tag_author', 'tag', '', 'Autor', '2019-07-23 22:34:19'),
(4456, 'de', 'tag_body', 'tag', '', 'Haupttext', '2019-07-23 22:34:19'),
(4457, 'de', 'tag_body_excerpt', 'tag', '', 'Exzerpt', '2019-07-23 22:34:19'),
(4458, 'de', 'tag_breadcrumb', 'tag', '', 'Breadcrumb-Navigation', '2019-07-23 22:34:19'),
(4459, 'de', 'tag_category', 'tag', '', 'Kategorie', '2019-07-23 22:34:19'),
(4460, 'de', 'tag_category1', 'tag', '', 'Kategorie 1', '2019-07-23 22:34:19'),
(4461, 'de', 'tag_category2', 'tag', '', 'Kategorie 2', '2019-07-23 22:34:19'),
(4462, 'de', 'tag_category_list', 'tag', '', 'Kategorieliste', '2019-07-23 22:34:19'),
(4463, 'de', 'tag_comments', 'tag', '', 'Kommentare', '2019-07-23 22:34:19'),
(4464, 'de', 'tag_comments_form', 'tag', '', 'Kommentarformular', '2019-07-23 22:34:19'),
(4465, 'de', 'tag_comments_invite', 'tag', '', 'Kommentareinladung', '2019-07-23 22:34:19'),
(4466, 'de', 'tag_comments_preview', 'tag', '', 'Kommentarvorschau', '2019-07-23 22:34:19'),
(4467, 'de', 'tag_comment_anchor', 'tag', '', 'Kommentaranker', '2019-07-23 22:34:19'),
(4468, 'de', 'tag_comment_email', 'tag', '', 'Kommentar-E-Mail', '2019-07-23 22:34:19'),
(4469, 'de', 'tag_comment_email_input', 'tag', '', 'Feld für Kommentar-E-Mail', '2019-07-23 22:34:19'),
(4470, 'de', 'tag_comment_id', 'tag', '', 'Kommentar-ID', '2019-07-23 22:34:19'),
(4471, 'de', 'tag_comment_message', 'tag', '', 'Kommentarnachricht', '2019-07-23 22:34:19'),
(4472, 'de', 'tag_comment_message_input', 'tag', '', 'Feld für Kommentarnachrichten', '2019-07-23 22:34:19'),
(4473, 'de', 'tag_comment_name', 'tag', '', 'Name des Kommentierenden', '2019-07-23 22:34:19'),
(4474, 'de', 'tag_comment_name_input', 'tag', '', 'Feld für den Namen des Kommentierenden', '2019-07-23 22:34:19'),
(4475, 'de', 'tag_comment_permlink', 'tag', '', 'Permanenter Link des Kommentars', '2019-07-23 22:34:19'),
(4476, 'de', 'tag_comment_preview', 'tag', '', 'Schaltfläche für Kommentarvorschau', '2019-07-23 22:34:19'),
(4477, 'de', 'tag_comment_remember', 'tag', '', 'Merken-Checkbox', '2019-07-23 22:34:19'),
(4478, 'de', 'tag_comment_submit', 'tag', '', 'Absendeschaltfläche des Kommentars', '2019-07-23 22:34:19'),
(4479, 'de', 'tag_comment_time', 'tag', '', 'Erstellungsdatum des Kommentars', '2019-07-23 22:34:19'),
(4480, 'de', 'tag_comment_web', 'tag', '', 'Webseite des Kommentierenden', '2019-07-23 22:34:19'),
(4481, 'de', 'tag_comment_web_input', 'tag', '', 'Homepage-Eingabefeld', '2019-07-23 22:34:19'),
(4482, 'de', 'tag_css', 'tag', '', 'Link zur CSS-Datei (<code>head</code>)', '2019-07-23 22:34:19'),
(4483, 'de', 'tag_email', 'tag', '', 'Kontaktlink (spamgeschützte E-Mail)', '2019-07-23 22:34:19'),
(4484, 'de', 'tag_excerpt', 'tag', '', 'Exzerpt', '2019-07-23 22:34:19'),
(4485, 'de', 'tag_feed_link', 'tag', '', 'Newsfeed der Artikel', '2019-07-23 22:34:19'),
(4486, 'de', 'tag_file_download', 'tag', '', 'Dateidownloads', '2019-07-23 22:34:19'),
(4487, 'de', 'tag_file_download_category', 'tag', '', 'Dateikategorie', '2019-07-23 22:34:19'),
(4488, 'de', 'tag_file_download_created', 'tag', '', 'Erstellungsdatum', '2019-07-23 22:34:19'),
(4489, 'de', 'tag_file_download_description', 'tag', '', 'Dateibeschreibung', '2019-07-23 22:34:19'),
(4490, 'de', 'tag_file_download_downloads', 'tag', '', 'Zahl der Downloads', '2019-07-23 22:34:19'),
(4491, 'de', 'tag_file_download_id', 'tag', '', 'Datei-ID', '2019-07-23 22:34:19'),
(4492, 'de', 'tag_file_download_link', 'tag', '', 'Dateilink', '2019-07-23 22:34:19'),
(4493, 'de', 'tag_file_download_list', 'tag', '', 'Dateiliste', '2019-07-23 22:34:19'),
(4494, 'de', 'tag_file_download_modified', 'tag', '', 'Dateiänderungsdatum', '2019-07-23 22:34:19'),
(4495, 'de', 'tag_file_download_name', 'tag', '', 'Dateiname', '2019-07-23 22:34:19'),
(4496, 'de', 'tag_file_download_size', 'tag', '', 'Dateigröße', '2019-07-23 22:34:19'),
(4497, 'de', 'tag_home', 'tag', '', 'Startseite', '2019-07-23 22:34:19'),
(4498, 'de', 'tag_if_category', 'tag', '', 'Wenn Kategorie', '2019-07-23 22:34:19'),
(4499, 'de', 'tag_if_section', 'tag', '', 'Wenn Sektion', '2019-07-23 22:34:19'),
(4500, 'de', 'tag_image', 'tag', '', 'Bild', '2019-07-23 22:34:19'),
(4501, 'de', 'tag_lang', 'tag', '', 'Sprache', '2019-07-23 22:34:19'),
(4502, 'de', 'tag_link', 'tag', '', 'Link', '2019-07-23 22:34:19'),
(4503, 'de', 'tag_linkdesctitle', 'tag', '', 'Link, title=beschreibung', '2019-07-23 22:34:19'),
(4504, 'de', 'tag_linklist', 'tag', '', 'Linkliste', '2019-07-23 22:34:19'),
(4505, 'de', 'tag_link_category', 'tag', '', 'Linkategorie', '2019-07-23 22:34:19'),
(4506, 'de', 'tag_link_date', 'tag', '', 'Linkdatum', '2019-07-23 22:34:19'),
(4507, 'de', 'tag_link_description', 'tag', '', 'Linkbeschreibung', '2019-07-23 22:34:19'),
(4508, 'de', 'tag_link_feed_link', 'tag', '', 'Newsfeed der Links', '2019-07-23 22:34:19'),
(4509, 'de', 'tag_link_name', 'tag', '', 'Linkname', '2019-07-23 22:34:19'),
(4510, 'de', 'tag_link_text', 'tag', '', 'Linkname', '2019-07-23 22:34:19'),
(4511, 'de', 'tag_link_to_home', 'tag', '', 'Link zur Startseite', '2019-07-23 22:34:19'),
(4512, 'de', 'tag_link_to_next', 'tag', '', 'Link zum nächsten Artikel', '2019-07-23 22:34:19'),
(4513, 'de', 'tag_link_to_prev', 'tag', '', 'Link zum vorherigen Artikel', '2019-07-23 22:34:19'),
(4514, 'de', 'tag_name', 'tag', '', 'Name des Kommentierenden', '2019-07-23 22:34:19'),
(4515, 'de', 'tag_newer', 'tag', '', 'Link zu neueren Artikeln', '2019-07-23 22:34:19'),
(4516, 'de', 'tag_next_article', 'tag', '', 'Nächster Artikel', '2019-07-23 22:34:19'),
(4517, 'de', 'tag_next_title', 'tag', '', 'Titel des nächsten Artikels', '2019-07-23 22:34:19'),
(4518, 'de', 'tag_older', 'tag', '', 'Link zu älteren Artikeln', '2019-07-23 22:34:19'),
(4519, 'de', 'tag_output_form', 'tag', '', 'Baustein einbinden', '2019-07-23 22:34:19'),
(4520, 'de', 'tag_page_title', 'tag', '', 'Titel der Seite', '2019-07-23 22:34:19'),
(4521, 'de', 'tag_password_protect', 'tag', '', 'Passwortschutz', '2019-07-23 22:34:19'),
(4522, 'de', 'tag_permlink', 'tag', '', 'Permanenter Link', '2019-07-23 22:34:19'),
(4523, 'de', 'tag_popup', 'tag', '', 'Pop-Up-Liste', '2019-07-23 22:34:19'),
(4524, 'de', 'tag_posted', 'tag', '', 'Veröffentlicht', '2019-07-23 22:34:19'),
(4525, 'de', 'tag_prev_article', 'tag', '', 'Vorheriger Artikel', '2019-07-23 22:34:19'),
(4526, 'de', 'tag_prev_title', 'tag', '', 'Titel des vorherigen Artikels', '2019-07-23 22:34:19'),
(4527, 'de', 'tag_recent_articles', 'tag', '', 'Liste der neuesten Artikel', '2019-07-23 22:34:19'),
(4528, 'de', 'tag_recent_comments', 'tag', '', 'Liste der neuesten Kommentare', '2019-07-23 22:34:19'),
(4529, 'de', 'tag_related_articles', 'tag', '', 'Liste von ähnlichen Artikel', '2019-07-23 22:34:19'),
(4530, 'de', 'tag_search_input', 'tag', '', 'Sucheingabefeld', '2019-07-23 22:34:19'),
(4531, 'de', 'tag_search_result_date', 'tag', '', 'Datum der Suchergebnisse', '2019-07-23 22:34:19'),
(4532, 'de', 'tag_search_result_excerpt', 'tag', '', 'Exzerpt der Suchergebnisse', '2019-07-23 22:34:19'),
(4533, 'de', 'tag_search_result_title', 'tag', '', 'Titel der Suchergebnisse', '2019-07-23 22:34:19'),
(4534, 'de', 'tag_search_result_url', 'tag', '', 'URL der Suchergebnisse', '2019-07-23 22:34:19'),
(4535, 'de', 'tag_section', 'tag', '', 'Sektion', '2019-07-23 22:34:19'),
(4536, 'de', 'tag_section_list', 'tag', '', 'Sektionsliste', '2019-07-23 22:34:19'),
(4537, 'de', 'tag_site_name', 'tag', '', 'Name der Webseite', '2019-07-23 22:34:19'),
(4538, 'de', 'tag_site_slogan', 'tag', '', 'Motto der Seite', '2019-07-23 22:34:19'),
(4539, 'de', 'tag_title', 'tag', '', 'Titel', '2019-07-23 22:34:19'),
(4540, 'de', 'textonly', 'tag', '', 'Nur Text?', '2019-07-23 22:34:19'),
(4541, 'de', 'this_section', 'tag', '', 'Verlinke zur aktuellen Sektion?', '2019-07-23 22:34:19'),
(4542, 'de', 'time_any', 'tag', '', 'Jederzeit', '2019-07-23 22:34:19'),
(4543, 'de', 'time_format', 'tag', '', 'String für Datumsformat', '2019-07-23 22:34:19'),
(4544, 'de', 'time_future', 'tag', '', 'Zukunft', '2019-07-23 22:34:19'),
(4545, 'de', 'time_past', 'tag', '', 'Vergangenheit', '2019-07-23 22:34:19'),
(4546, 'de', 'title_separator', 'tag', '', 'Seitenname: Individueller Artikeltrenner', '2019-07-23 22:34:19'),
(4547, 'de', 'tooltip', 'tag', '', 'Link-Tooltip', '2019-07-23 22:34:19'),
(4548, 'de', 'use_thumbnail', 'tag', '', 'Vorschaubild verwenden?', '2019-07-23 22:34:19'),
(4549, 'de', 'wraptag', 'tag', '', 'Wraptag', '2019-07-23 22:34:19'),
(4550, 'de', 'rvm_css_dir', 'prefs', 'rvm_css', 'Stilvorlageverzeichnis', '2019-07-23 22:34:19'),
(4551, 'de', 'arc_meta', 'admin', 'site', 'arc_meta', '2019-07-23 22:34:19'),
(4552, 'de', 'arc_meta_title', 'admin', 'site', 'Meta title', '2019-07-23 22:34:19'),
(4553, 'de', 'arc_meta_description', 'admin', 'site', 'Meta description', '2019-07-23 22:34:19'),
(4554, 'de', 'arc_meta_image', 'admin', 'site', 'Meta image', '2019-07-23 22:34:19'),
(4555, 'de', 'arc_meta_robots', 'admin', 'site', 'Meta robots', '2019-07-23 22:34:19'),
(4556, 'de', 'arc_meta_article_title', 'admin', 'site', 'Article page titles pattern', '2019-07-23 22:34:19'),
(4557, 'de', 'arc_meta_comment_title', 'admin', 'site', 'Comment page titles pattern', '2019-07-23 22:34:19'),
(4558, 'de', 'arc_meta_search_title', 'admin', 'site', 'Search page titles pattern', '2019-07-23 22:34:19'),
(4559, 'de', 'arc_meta_category_title', 'admin', 'site', 'Category titles pattern', '2019-07-23 22:34:19'),
(4560, 'de', 'arc_meta_section_title', 'admin', 'site', 'Section titles pattern', '2019-07-23 22:34:19'),
(4561, 'de', 'arc_meta_section_category_title', 'admin', 'site', 'Section category titles pattern', '2019-07-23 22:34:19'),
(4562, 'de', 'arc_meta_homepage_title', 'admin', 'site', 'Homepage title pattern', '2019-07-23 22:34:19');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(4563, 'de', 'arc_meta_section_tab', 'admin', 'site', 'Additional panel location for sections meta data edit', '2019-07-23 22:34:19'),
(4564, 'de', 'arc_meta_section_tab_content', 'admin', 'site', 'Content', '2019-07-23 22:34:19'),
(4565, 'de', 'arc_meta_section_tab_extensions', 'admin', 'site', 'Extensions', '2019-07-23 22:34:19'),
(4566, 'de', 'arc_meta_section_tab_hidden', 'admin', 'site', 'Hidden', '2019-07-23 22:34:19'),
(4567, 'de', 'ied_plugin_any', 'ied_plugin', 'site', 'Any', '2019-07-25 01:42:31'),
(4568, 'de', 'ied_plugin_auto_enable', 'ied_plugin', 'site', 'Auto-enable plugins on install', '2019-07-25 01:42:31'),
(4569, 'de', 'ied_plugin_cache_not_set', 'ied_plugin', 'site', 'Advanced pref \"plugin cache directory\" not defined', '2019-07-25 01:42:31'),
(4570, 'de', 'ied_plugin_check_type', 'ied_plugin', 'site', 'Check your plugin type!', '2019-07-25 01:42:31'),
(4571, 'de', 'ied_plugin_choose_file', 'ied_plugin', 'site', 'Please choose a file first', '2019-07-25 01:42:31'),
(4572, 'de', 'ied_plugin_code_dist', 'ied_plugin', 'site', 'Plugin code for distribution', '2019-07-25 01:42:31'),
(4573, 'de', 'ied_plugin_code_legend', 'ied_plugin', 'site', 'Plugin code', '2019-07-25 01:42:31'),
(4574, 'de', 'ied_plugin_code_save', 'ied_plugin', 'site', 'Save code', '2019-07-25 01:42:31'),
(4575, 'de', 'ied_plugin_code_saved', 'ied_plugin', 'site', 'Code saved', '2019-07-25 01:42:31'),
(4576, 'de', 'ied_plugin_code_saved_fail', 'ied_plugin', 'site', 'Code saving failed', '2019-07-25 01:42:31'),
(4577, 'de', 'ied_plugin_compress', 'ied_plugin', 'site', 'Zip', '2019-07-25 01:42:31'),
(4578, 'de', 'ied_plugin_cpanel_legend', 'ied_plugin', 'site', 'Installation', '2019-07-25 01:42:31'),
(4579, 'de', 'ied_plugin_create_new', 'ied_plugin', 'site', 'Create new plugin', '2019-07-25 01:42:31'),
(4580, 'de', 'ied_plugin_dbplugs_legend', 'ied_plugin', 'site', 'Plugins in database', '2019-07-25 01:42:31'),
(4581, 'de', 'ied_plugin_docs', 'ied_plugin', 'site', 'Docs', '2019-07-25 01:42:31'),
(4582, 'de', 'ied_plugin_docs_legend', 'ied_plugin', 'site', 'Plugin help', '2019-07-25 01:42:31'),
(4583, 'de', 'ied_plugin_edit', 'ied_plugin', 'site', 'Edit: {name} v{version}', '2019-07-25 01:42:31'),
(4584, 'de', 'ied_plugin_editing', 'ied_plugin', 'site', 'Editing {name} in Plugin Composer', '2019-07-25 01:42:31'),
(4585, 'de', 'ied_plugin_editor', 'ied_plugin', 'site', 'Plugin editor', '2019-07-25 01:42:31'),
(4586, 'de', 'ied_plugin_editor_options', 'ied_plugin', 'site', 'Plugin editor configuration options', '2019-07-25 01:42:31'),
(4587, 'de', 'ied_plugin_editor_path', 'ied_plugin', 'site', 'Plugin editor URL', '2019-07-25 01:42:31'),
(4588, 'de', 'ied_plugin_editor_width', 'ied_plugin', 'site', 'Plugin editor width', '2019-07-25 01:42:31'),
(4589, 'de', 'ied_plugin_edit_new', 'ied_plugin', 'site', 'Edit your new plugin', '2019-07-25 01:42:31'),
(4590, 'de', 'ied_plugin_enable', 'ied_plugin', 'site', 'Enabled', '2019-07-25 01:42:31'),
(4591, 'de', 'ied_plugin_export', 'ied_plugin', 'site', 'Export as {name}', '2019-07-25 01:42:31'),
(4592, 'de', 'ied_plugin_export_textpack', 'ied_plugin', 'site', 'Export textpack(s)', '2019-07-25 01:42:31'),
(4593, 'de', 'ied_plugin_export_zip', 'ied_plugin', 'site', 'Export as {name} (compressed)', '2019-07-25 01:42:31'),
(4594, 'de', 'ied_plugin_flags', 'ied_plugin', 'site', 'Flags', '2019-07-25 01:42:31'),
(4595, 'de', 'ied_plugin_flag_has_prefs', 'ied_plugin', 'site', 'Has prefs', '2019-07-25 01:42:31'),
(4596, 'de', 'ied_plugin_flag_lifecycle_notify', 'ied_plugin', 'site', 'Event notify', '2019-07-25 01:42:31'),
(4597, 'de', 'ied_plugin_fn_not_exist', 'ied_plugin', 'site', '(the function is probably javascript or mangled PHP)', '2019-07-25 01:42:31'),
(4598, 'de', 'ied_plugin_help_editor', 'ied_plugin', 'site', 'Help editor', '2019-07-25 01:42:31'),
(4599, 'de', 'ied_plugin_help_editor_options', 'ied_plugin', 'site', 'Help editor configuration options', '2019-07-25 01:42:31'),
(4600, 'de', 'ied_plugin_help_editor_path', 'ied_plugin', 'site', 'Help editor URL', '2019-07-25 01:42:31'),
(4601, 'de', 'ied_plugin_help_not_available', 'ied_plugin', 'site', 'Plugin help not available', '2019-07-25 01:42:31'),
(4602, 'de', 'ied_plugin_if_el_dist', 'ied_plugin', 'site', 'Code for distribution', '2019-07-25 01:42:31'),
(4603, 'de', 'ied_plugin_if_el_style', 'ied_plugin', 'site', 'Help style block', '2019-07-25 01:42:31'),
(4604, 'de', 'ied_plugin_if_settings', 'ied_plugin', 'site', 'Interface settings', '2019-07-25 01:42:31'),
(4605, 'de', 'ied_plugin_install_txt', 'ied_plugin', 'site', 'Install from .txt file', '2019-07-25 01:42:31'),
(4606, 'de', 'ied_plugin_interface_elems', 'ied_plugin', 'site', 'Optional interface elements', '2019-07-25 01:42:31'),
(4607, 'de', 'ied_plugin_jump_to_line', 'ied_plugin', 'site', 'Jump to line:', '2019-07-25 01:42:31'),
(4608, 'de', 'ied_plugin_langs_all', 'ied_plugin', 'site', 'All available', '2019-07-25 01:42:31'),
(4609, 'de', 'ied_plugin_langs_installed', 'ied_plugin', 'site', 'Only installed', '2019-07-25 01:42:31'),
(4610, 'de', 'ied_plugin_lang_choose', 'ied_plugin', 'site', 'Textpack language list', '2019-07-25 01:42:31'),
(4611, 'de', 'ied_plugin_lang_default', 'ied_plugin', 'site', 'Default textpack language', '2019-07-25 01:42:31'),
(4612, 'de', 'ied_plugin_lbl_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-23 22:34:19'),
(4613, 'de', 'ied_plugin_lbl_lc_delete', 'ied_plugin', 'site', 'Delete', '2019-07-25 01:42:31'),
(4614, 'de', 'ied_plugin_lbl_lc_disable', 'ied_plugin', 'site', 'Disable', '2019-07-25 01:42:31'),
(4615, 'de', 'ied_plugin_lbl_lc_enable', 'ied_plugin', 'site', 'Enable', '2019-07-25 01:42:31'),
(4616, 'de', 'ied_plugin_lbl_lc_install', 'ied_plugin', 'site', 'Install', '2019-07-25 01:42:31'),
(4617, 'de', 'ied_plugin_lbl_op_code_first', 'ied_plugin', 'site', 'Code first', '2019-07-25 01:42:31'),
(4618, 'de', 'ied_plugin_lbl_op_help_first', 'ied_plugin', 'site', 'Help first', '2019-07-25 01:42:31'),
(4619, 'de', 'ied_plugin_lbl_setup', 'ied_plugin', 'site', 'Plugin composer setup', '2019-07-25 01:42:31'),
(4620, 'de', 'ied_plugin_lifecycle_options', 'ied_plugin', 'site', 'Perform lifecycle actions on', '2019-07-25 01:42:31'),
(4621, 'de', 'ied_plugin_load', 'ied_plugin', 'site', 'Load', '2019-07-25 01:42:31'),
(4622, 'de', 'ied_plugin_load_order', 'ied_plugin', 'site', 'Load order', '2019-07-25 01:42:31'),
(4623, 'de', 'ied_plugin_load_order_help', 'ied_plugin', 'site', '(1=first > 5=normal > 9=last)', '2019-07-25 01:42:31'),
(4624, 'de', 'ied_plugin_meta_legend', 'ied_plugin', 'site', 'Meta information', '2019-07-25 01:42:31'),
(4625, 'de', 'ied_plugin_msgpop_lbl', 'ied_plugin', 'site', 'phpdoc block', '2019-07-25 01:42:31'),
(4626, 'de', 'ied_plugin_name_first', 'ied_plugin', 'site', 'Please name the plugin before creating it', '2019-07-25 01:42:31'),
(4627, 'de', 'ied_plugin_output_order', 'ied_plugin', 'site', 'PHP export order', '2019-07-25 01:42:31'),
(4628, 'de', 'ied_plugin_output_sfile', 'ied_plugin', 'site', 'Export plugin filename format', '2019-07-25 01:42:31'),
(4629, 'de', 'ied_plugin_output_sfilec', 'ied_plugin', 'site', 'Export compressed filename format', '2019-07-25 01:42:31'),
(4630, 'de', 'ied_plugin_output_sfilep', 'ied_plugin', 'site', 'Plugin template filename format', '2019-07-25 01:42:31'),
(4631, 'de', 'ied_plugin_output_sfilet', 'ied_plugin', 'site', 'Textpack filename format', '2019-07-25 01:42:31'),
(4632, 'de', 'ied_plugin_output_tmpcache', 'ied_plugin', 'site', 'Cache Textiled help path', '2019-07-25 01:42:31'),
(4633, 'de', 'ied_plugin_php_doc', 'ied_plugin', 'site', 'Make phpdoc', '2019-07-25 01:42:31'),
(4634, 'de', 'ied_plugin_prefs', 'ied_plugin', 'site', 'Preferences', '2019-07-25 01:42:31'),
(4635, 'de', 'ied_plugin_prefs_deleted', 'ied_plugin', 'site', 'Preferences deleted', '2019-07-25 01:42:31'),
(4636, 'de', 'ied_plugin_renamed', 'ied_plugin', 'site', '(renamed)', '2019-07-25 01:42:31'),
(4637, 'de', 'ied_plugin_rename_failed', 'ied_plugin', 'site', '(rename failed)', '2019-07-25 01:42:31'),
(4638, 'de', 'ied_plugin_rename_file', 'ied_plugin', 'site', 'Rename file', '2019-07-25 01:42:31'),
(4639, 'de', 'ied_plugin_restored', 'ied_plugin', 'site', 'Plugin {name} restored.', '2019-07-25 01:42:31'),
(4640, 'de', 'ied_plugin_restore_help', 'ied_plugin', 'site', 'Roll back the plugin code to the most recent restore point', '2019-07-25 01:42:31'),
(4641, 'de', 'ied_plugin_restore_point', 'ied_plugin', 'site', 'Restore point', '2019-07-25 01:42:31'),
(4642, 'de', 'ied_plugin_restore_verify', 'ied_plugin', 'site', 'Are you sure you want to rollback the {name} source code to its last restore point?', '2019-07-25 01:42:31'),
(4643, 'de', 'ied_plugin_run_from', 'ied_plugin', 'site', 'Les Plugins sont chargés depuis le cache', '2019-07-23 22:34:19'),
(4644, 'de', 'ied_plugin_run_install', 'ied_plugin', 'site', 'Perform post-install actions', '2019-07-25 01:42:31'),
(4645, 'de', 'ied_plugin_same', 'ied_plugin', 'site', 'Same', '2019-07-25 01:42:31'),
(4646, 'de', 'ied_plugin_save_as', 'ied_plugin', 'site', 'Save as {name}', '2019-07-25 01:42:31'),
(4647, 'de', 'ied_plugin_setup', 'ied_plugin', 'site', 'Setup', '2019-07-25 01:42:31'),
(4648, 'de', 'ied_plugin_syntax_check', 'ied_plugin', 'site', 'Syntax check on code save', '2019-07-25 01:42:31'),
(4649, 'de', 'ied_plugin_syntax_err', 'ied_plugin', 'site', 'Syntax error', '2019-07-25 01:42:31'),
(4650, 'de', 'ied_plugin_tp_legend', 'ied_plugin', 'site', 'Chaînes du Textpack', '2019-07-23 22:34:19'),
(4651, 'de', 'ied_plugin_tp_populate', 'ied_plugin', 'site', 'Load strings from function', '2019-07-25 01:42:31'),
(4652, 'de', 'ied_plugin_tp_prefix', 'ied_plugin', 'site', 'Textpack prefix', '2019-07-25 01:42:31'),
(4653, 'de', 'ied_plugin_type', 'ied_plugin', 'site', 'Plugin type', '2019-07-25 01:42:31'),
(4654, 'de', 'ied_plugin_type_0', 'ied_plugin', 'site', 'Public', '2019-07-25 01:42:31'),
(4655, 'de', 'ied_plugin_type_1', 'ied_plugin', 'site', 'Admin + Public', '2019-07-25 01:42:31'),
(4656, 'de', 'ied_plugin_type_2', 'ied_plugin', 'site', 'Library', '2019-07-25 01:42:31'),
(4657, 'de', 'ied_plugin_type_3', 'ied_plugin', 'site', 'Admin only', '2019-07-25 01:42:31'),
(4658, 'de', 'ied_plugin_type_4', 'ied_plugin', 'site', 'Admin (+AJAX)', '2019-07-25 01:42:31'),
(4659, 'de', 'ied_plugin_type_5', 'ied_plugin', 'site', 'Admin + Public (+AJAX)', '2019-07-25 01:42:31'),
(4660, 'de', 'ied_plugin_updated', 'ied_plugin', 'site', 'Plugin {name} updated', '2019-07-25 01:42:31'),
(4661, 'de', 'ied_plugin_uploaded', 'ied_plugin', 'site', 'Plugin {name} uploaded', '2019-07-25 01:42:31'),
(4662, 'de', 'ied_plugin_upload_php', 'ied_plugin', 'site', 'Upload plugin', '2019-07-25 01:42:31'),
(4663, 'de', 'ied_plugin_utils_legend', 'ied_plugin', 'site', 'Distribution', '2019-07-25 01:42:31'),
(4664, 'de', 'ied_plugin_view_help', 'ied_plugin', 'site', 'Help: {name}', '2019-07-25 01:42:31'),
(4665, 'de', 'smd_macro_attributes', 'smd_macro', 'site', 'Attribute', '2019-07-23 22:53:33'),
(4666, 'de', 'smd_macro_att_clone', 'smd_macro', 'site', '[+]', '2019-07-23 22:53:33'),
(4667, 'de', 'smd_macro_att_clone_help', 'smd_macro', 'site', 'Ein Attribut hinzufügen', '2019-07-23 22:53:33'),
(4668, 'de', 'smd_macro_cannot_import', 'smd_macro', 'site', 'Das Makro kann nicht importiert werden. Bitte überprüfen Sie die Berechtigungen der Ordner und die Einstellungen Ihres Servers.', '2019-07-23 22:53:33'),
(4669, 'de', 'smd_macro_choose', 'smd_macro', 'site', 'Makro zum Bearbeiten auswählen', '2019-07-23 22:53:33'),
(4670, 'de', 'smd_macro_clone', 'smd_macro', 'site', 'Duplizieren', '2019-07-23 22:53:33'),
(4671, 'de', 'smd_macro_created', 'smd_macro', 'site', 'Makro erstellt', '2019-07-23 22:53:33'),
(4672, 'de', 'smd_macro_deleted', 'smd_macro', 'site', 'Makro gelöscht', '2019-07-23 22:53:33'),
(4673, 'de', 'smd_macro_exists', 'smd_macro', 'site', 'Der Name des Makros existiert bereits oder ist inkompatibel', '2019-07-23 22:53:33'),
(4674, 'de', 'smd_macro_export', 'smd_macro', 'site', 'Exportieren', '2019-07-23 22:53:33'),
(4675, 'de', 'smd_macro_file_size', 'smd_macro', 'site', 'Die Makrodatei ist zu groß', '2019-07-23 22:53:33'),
(4676, 'de', 'smd_macro_import', 'smd_macro', 'site', 'Importieren', '2019-07-23 22:53:33'),
(4677, 'de', 'smd_macro_imported', 'smd_macro', 'site', 'Importiert:', '2019-07-23 22:53:33'),
(4678, 'de', 'smd_macro_invalid', 'smd_macro', 'site', 'Der Name des Makros ist ungültig', '2019-07-23 22:53:33'),
(4679, 'de', 'smd_macro_invalid_ini', 'smd_macro', 'site', 'Die Makrodatei ist nicht im erwarteten Format', '2019-07-23 22:53:33'),
(4680, 'de', 'smd_macro_not_imported', 'smd_macro', 'site', 'Nicht importiert:', '2019-07-23 22:53:33'),
(4681, 'de', 'smd_macro_overwrite', 'smd_macro', 'site', 'Überschreiben erzwingen', '2019-07-23 22:53:33'),
(4682, 'de', 'smd_macro_repname', 'smd_macro', 'site', 'Ersatzname', '2019-07-23 22:53:33'),
(4683, 'de', 'smd_macro_saved', 'smd_macro', 'site', 'Makro gespeichert', '2019-07-23 22:53:33'),
(4684, 'de', 'smd_macro_skipped', 'smd_macro', 'site', 'Übersprungen:', '2019-07-23 22:53:33'),
(4685, 'de', 'smd_macro_tab_name', 'smd_macro', 'site', 'Makros', '2019-07-23 22:53:33'),
(4686, 'de', 'smd_macro_tag_definition', 'smd_macro', 'site', 'Makrodefinition', '2019-07-23 22:53:33'),
(4687, 'de', 'smd_macro_tbl_installed', 'smd_macro', 'site', 'Tabelle installiert', '2019-07-23 22:53:33'),
(4688, 'de', 'smd_macro_tbl_not_installed', 'smd_macro', 'site', 'Tabelle nicht installiert', '2019-07-23 22:53:33'),
(4689, 'de', 'smd_macro_tbl_not_removed', 'smd_macro', 'site', 'Tabelle nicht entfernt', '2019-07-23 22:53:33'),
(4690, 'de', 'smd_macro_tbl_removed', 'smd_macro', 'site', 'Tabelle entfernt', '2019-07-23 22:53:33'),
(4691, 'de', 'smd_macro_upload', 'smd_macro', 'site', 'Makrodatei', '2019-07-23 22:53:33'),
(4692, 'en-us', 'smd_macro_attributes', 'smd_macro', 'site', 'Attribute', '2019-07-23 22:53:33'),
(4693, 'en-us', 'smd_macro_att_clone', 'smd_macro', 'site', '[+]', '2019-07-23 22:53:33'),
(4694, 'en-us', 'smd_macro_att_clone_help', 'smd_macro', 'site', 'Ein Attribut hinzufügen', '2019-07-23 22:53:33'),
(4695, 'en-us', 'smd_macro_cannot_import', 'smd_macro', 'site', 'Das Makro kann nicht importiert werden. Bitte überprüfen Sie die Berechtigungen der Ordner und die Einstellungen Ihres Servers.', '2019-07-23 22:53:33'),
(4696, 'en-us', 'smd_macro_choose', 'smd_macro', 'site', 'Makro zum Bearbeiten auswählen', '2019-07-23 22:53:33'),
(4697, 'en-us', 'smd_macro_clone', 'smd_macro', 'site', 'Duplizieren', '2019-07-23 22:53:33'),
(4698, 'en-us', 'smd_macro_created', 'smd_macro', 'site', 'Makro erstellt', '2019-07-23 22:53:33'),
(4699, 'en-us', 'smd_macro_deleted', 'smd_macro', 'site', 'Makro gelöscht', '2019-07-23 22:53:33'),
(4700, 'en-us', 'smd_macro_exists', 'smd_macro', 'site', 'Der Name des Makros existiert bereits oder ist inkompatibel', '2019-07-23 22:53:33'),
(4701, 'en-us', 'smd_macro_export', 'smd_macro', 'site', 'Exportieren', '2019-07-23 22:53:33'),
(4702, 'en-us', 'smd_macro_file_size', 'smd_macro', 'site', 'Die Makrodatei ist zu groß', '2019-07-23 22:53:33'),
(4703, 'en-us', 'smd_macro_import', 'smd_macro', 'site', 'Importieren', '2019-07-23 22:53:33'),
(4704, 'en-us', 'smd_macro_imported', 'smd_macro', 'site', 'Importiert:', '2019-07-23 22:53:33'),
(4705, 'en-us', 'smd_macro_invalid', 'smd_macro', 'site', 'Der Name des Makros ist ungültig', '2019-07-23 22:53:33'),
(4706, 'en-us', 'smd_macro_invalid_ini', 'smd_macro', 'site', 'Die Makrodatei ist nicht im erwarteten Format', '2019-07-23 22:53:33'),
(4707, 'en-us', 'smd_macro_not_imported', 'smd_macro', 'site', 'Nicht importiert:', '2019-07-23 22:53:33'),
(4708, 'en-us', 'smd_macro_overwrite', 'smd_macro', 'site', 'Überschreiben erzwingen', '2019-07-23 22:53:33'),
(4709, 'en-us', 'smd_macro_repname', 'smd_macro', 'site', 'Ersatzname', '2019-07-23 22:53:33'),
(4710, 'en-us', 'smd_macro_saved', 'smd_macro', 'site', 'Makro gespeichert', '2019-07-23 22:53:33'),
(4711, 'en-us', 'smd_macro_skipped', 'smd_macro', 'site', 'Übersprungen:', '2019-07-23 22:53:33'),
(4712, 'en-us', 'smd_macro_tab_name', 'smd_macro', 'site', 'Makros', '2019-07-23 22:53:33'),
(4713, 'en-us', 'smd_macro_tag_definition', 'smd_macro', 'site', 'Makrodefinition', '2019-07-23 22:53:33'),
(4714, 'en-us', 'smd_macro_tbl_installed', 'smd_macro', 'site', 'Tabelle installiert', '2019-07-23 22:53:33'),
(4715, 'en-us', 'smd_macro_tbl_not_installed', 'smd_macro', 'site', 'Tabelle nicht installiert', '2019-07-23 22:53:33'),
(4716, 'en-us', 'smd_macro_tbl_not_removed', 'smd_macro', 'site', 'Tabelle nicht entfernt', '2019-07-23 22:53:33'),
(4717, 'en-us', 'smd_macro_tbl_removed', 'smd_macro', 'site', 'Tabelle entfernt', '2019-07-23 22:53:33'),
(4718, 'en-us', 'smd_macro_upload', 'smd_macro', 'site', 'Makrodatei', '2019-07-23 22:53:33'),
(4719, 'el', 'smd_macro_attributes', 'smd_macro', 'site', 'Attribute', '2019-07-23 22:53:33'),
(4720, 'el', 'smd_macro_att_clone', 'smd_macro', 'site', '[+]', '2019-07-23 22:53:33'),
(4721, 'el', 'smd_macro_att_clone_help', 'smd_macro', 'site', 'Ein Attribut hinzufügen', '2019-07-23 22:53:33'),
(4722, 'el', 'smd_macro_cannot_import', 'smd_macro', 'site', 'Das Makro kann nicht importiert werden. Bitte überprüfen Sie die Berechtigungen der Ordner und die Einstellungen Ihres Servers.', '2019-07-23 22:53:33'),
(4723, 'el', 'smd_macro_choose', 'smd_macro', 'site', 'Makro zum Bearbeiten auswählen', '2019-07-23 22:53:33'),
(4724, 'el', 'smd_macro_clone', 'smd_macro', 'site', 'Duplizieren', '2019-07-23 22:53:33'),
(4725, 'el', 'smd_macro_created', 'smd_macro', 'site', 'Makro erstellt', '2019-07-23 22:53:33'),
(4726, 'el', 'smd_macro_deleted', 'smd_macro', 'site', 'Makro gelöscht', '2019-07-23 22:53:33'),
(4727, 'el', 'smd_macro_exists', 'smd_macro', 'site', 'Der Name des Makros existiert bereits oder ist inkompatibel', '2019-07-23 22:53:33'),
(4728, 'el', 'smd_macro_export', 'smd_macro', 'site', 'Exportieren', '2019-07-23 22:53:33'),
(4729, 'el', 'smd_macro_file_size', 'smd_macro', 'site', 'Die Makrodatei ist zu groß', '2019-07-23 22:53:33'),
(4730, 'el', 'smd_macro_import', 'smd_macro', 'site', 'Importieren', '2019-07-23 22:53:33'),
(4731, 'el', 'smd_macro_imported', 'smd_macro', 'site', 'Importiert:', '2019-07-23 22:53:33'),
(4732, 'el', 'smd_macro_invalid', 'smd_macro', 'site', 'Der Name des Makros ist ungültig', '2019-07-23 22:53:33'),
(4733, 'el', 'smd_macro_invalid_ini', 'smd_macro', 'site', 'Die Makrodatei ist nicht im erwarteten Format', '2019-07-23 22:53:33'),
(4734, 'el', 'smd_macro_not_imported', 'smd_macro', 'site', 'Nicht importiert:', '2019-07-23 22:53:33'),
(4735, 'el', 'smd_macro_overwrite', 'smd_macro', 'site', 'Überschreiben erzwingen', '2019-07-23 22:53:33'),
(4736, 'el', 'smd_macro_repname', 'smd_macro', 'site', 'Ersatzname', '2019-07-23 22:53:33'),
(4737, 'el', 'smd_macro_saved', 'smd_macro', 'site', 'Makro gespeichert', '2019-07-23 22:53:33'),
(4738, 'el', 'smd_macro_skipped', 'smd_macro', 'site', 'Übersprungen:', '2019-07-23 22:53:33'),
(4739, 'el', 'smd_macro_tab_name', 'smd_macro', 'site', 'Makros', '2019-07-23 22:53:33'),
(4740, 'el', 'smd_macro_tag_definition', 'smd_macro', 'site', 'Makrodefinition', '2019-07-23 22:53:33'),
(4741, 'el', 'smd_macro_tbl_installed', 'smd_macro', 'site', 'Tabelle installiert', '2019-07-23 22:53:33'),
(4742, 'el', 'smd_macro_tbl_not_installed', 'smd_macro', 'site', 'Tabelle nicht installiert', '2019-07-23 22:53:33'),
(4743, 'el', 'smd_macro_tbl_not_removed', 'smd_macro', 'site', 'Tabelle nicht entfernt', '2019-07-23 22:53:33'),
(4744, 'el', 'smd_macro_tbl_removed', 'smd_macro', 'site', 'Tabelle entfernt', '2019-07-23 22:53:33'),
(4745, 'el', 'smd_macro_upload', 'smd_macro', 'site', 'Makrodatei', '2019-07-23 22:53:33'),
(4746, 'en', 'smd_macro_attributes', 'smd_macro', 'site', 'Attribute', '2019-07-23 22:53:33'),
(4747, 'en', 'smd_macro_att_clone', 'smd_macro', 'site', '[+]', '2019-07-23 22:53:33'),
(4748, 'en', 'smd_macro_att_clone_help', 'smd_macro', 'site', 'Ein Attribut hinzufügen', '2019-07-23 22:53:33'),
(4749, 'en', 'smd_macro_cannot_import', 'smd_macro', 'site', 'Das Makro kann nicht importiert werden. Bitte überprüfen Sie die Berechtigungen der Ordner und die Einstellungen Ihres Servers.', '2019-07-23 22:53:33'),
(4750, 'en', 'smd_macro_choose', 'smd_macro', 'site', 'Makro zum Bearbeiten auswählen', '2019-07-23 22:53:33'),
(4751, 'en', 'smd_macro_clone', 'smd_macro', 'site', 'Duplizieren', '2019-07-23 22:53:33'),
(4752, 'en', 'smd_macro_created', 'smd_macro', 'site', 'Makro erstellt', '2019-07-23 22:53:33'),
(4753, 'en', 'smd_macro_deleted', 'smd_macro', 'site', 'Makro gelöscht', '2019-07-23 22:53:33'),
(4754, 'en', 'smd_macro_exists', 'smd_macro', 'site', 'Der Name des Makros existiert bereits oder ist inkompatibel', '2019-07-23 22:53:33'),
(4755, 'en', 'smd_macro_export', 'smd_macro', 'site', 'Exportieren', '2019-07-23 22:53:33'),
(4756, 'en', 'smd_macro_file_size', 'smd_macro', 'site', 'Die Makrodatei ist zu groß', '2019-07-23 22:53:33'),
(4757, 'en', 'smd_macro_import', 'smd_macro', 'site', 'Importieren', '2019-07-23 22:53:33'),
(4758, 'en', 'smd_macro_imported', 'smd_macro', 'site', 'Importiert:', '2019-07-23 22:53:33'),
(4759, 'en', 'smd_macro_invalid', 'smd_macro', 'site', 'Der Name des Makros ist ungültig', '2019-07-23 22:53:33'),
(4760, 'en', 'smd_macro_invalid_ini', 'smd_macro', 'site', 'Die Makrodatei ist nicht im erwarteten Format', '2019-07-23 22:53:33'),
(4761, 'en', 'smd_macro_not_imported', 'smd_macro', 'site', 'Nicht importiert:', '2019-07-23 22:53:33'),
(4762, 'en', 'smd_macro_overwrite', 'smd_macro', 'site', 'Überschreiben erzwingen', '2019-07-23 22:53:33'),
(4763, 'en', 'smd_macro_repname', 'smd_macro', 'site', 'Ersatzname', '2019-07-23 22:53:33'),
(4764, 'en', 'smd_macro_saved', 'smd_macro', 'site', 'Makro gespeichert', '2019-07-23 22:53:33'),
(4765, 'en', 'smd_macro_skipped', 'smd_macro', 'site', 'Übersprungen:', '2019-07-23 22:53:33'),
(4766, 'en', 'smd_macro_tab_name', 'smd_macro', 'site', 'Makros', '2019-07-23 22:53:33'),
(4767, 'en', 'smd_macro_tag_definition', 'smd_macro', 'site', 'Makrodefinition', '2019-07-23 22:53:33'),
(4768, 'en', 'smd_macro_tbl_installed', 'smd_macro', 'site', 'Tabelle installiert', '2019-07-23 22:53:33'),
(4769, 'en', 'smd_macro_tbl_not_installed', 'smd_macro', 'site', 'Tabelle nicht installiert', '2019-07-23 22:53:33'),
(4770, 'en', 'smd_macro_tbl_not_removed', 'smd_macro', 'site', 'Tabelle nicht entfernt', '2019-07-23 22:53:33'),
(4771, 'en', 'smd_macro_tbl_removed', 'smd_macro', 'site', 'Tabelle entfernt', '2019-07-23 22:53:33'),
(4772, 'en', 'smd_macro_upload', 'smd_macro', 'site', 'Makrodatei', '2019-07-23 22:53:33'),
(4773, 'de', 'smd_artstat', 'smd_artstat', 'site', 'Statistiques d\'article', '2019-07-23 23:32:07'),
(4774, 'de', 'smd_artstat_fields', 'smd_artstat', 'site', 'Champs de décompte et sélecteur du DOM', '2019-07-23 23:32:07'),
(4775, 'de', 'smd_artstat_id', 'smd_artstat', 'site', 'Afficher l\'ID de l\'article', '2019-07-23 23:32:07'),
(4776, 'de', 'smd_artstat_legend', 'smd_artstat', 'site', 'Statistiques article', '2019-07-23 23:32:07'),
(4777, 'de', 'smd_artstat_pos', 'smd_artstat', 'site', 'Emplacement dans la page', '2019-07-23 23:32:07'),
(4778, 'de', 'smd_artstat_pos_above_status', 'smd_artstat', 'site', 'Au dessus des statuts', '2019-07-23 23:32:07'),
(4779, 'de', 'smd_artstat_pos_above_textfilter', 'smd_artstat', 'site', 'Au dessus du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4780, 'de', 'smd_artstat_pos_above_textile', 'smd_artstat', 'site', 'Au dessus de l\'aide Textile', '2019-07-23 23:32:07'),
(4781, 'de', 'smd_artstat_pos_above_title', 'smd_artstat', 'site', 'Au dessus du titre', '2019-07-23 23:32:07'),
(4782, 'de', 'smd_artstat_pos_below_author', 'smd_artstat', 'site', 'Sous le nom d\'auteur', '2019-07-23 23:32:07'),
(4783, 'de', 'smd_artstat_pos_below_excerpt', 'smd_artstat', 'site', 'Sous le résumé', '2019-07-23 23:32:07'),
(4784, 'de', 'smd_artstat_pos_below_textfilter', 'smd_artstat', 'site', 'En dessous du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4785, 'de', 'smd_artstat_pos_below_textile', 'smd_artstat', 'site', 'En dessous de l\'aide Textile', '2019-07-23 23:32:07'),
(4786, 'de', 'smd_artstat_singular', 'smd_artstat', 'site', 'Nombre pris au singulier', '2019-07-23 23:32:07'),
(4787, 'de', 'smd_artstat_word_plural', 'smd_artstat', 'site', 'Mots', '2019-07-23 23:32:07'),
(4788, 'de', 'smd_artstat_word_singular', 'smd_artstat', 'site', 'Mot', '2019-07-23 23:32:07'),
(4789, 'en-us', 'smd_artstat', 'smd_artstat', 'site', 'Statistiques d\'article', '2019-07-23 23:32:07'),
(4790, 'en-us', 'smd_artstat_fields', 'smd_artstat', 'site', 'Champs de décompte et sélecteur du DOM', '2019-07-23 23:32:07'),
(4791, 'en-us', 'smd_artstat_id', 'smd_artstat', 'site', 'Afficher l\'ID de l\'article', '2019-07-23 23:32:07'),
(4792, 'en-us', 'smd_artstat_legend', 'smd_artstat', 'site', 'Statistiques article', '2019-07-23 23:32:07'),
(4793, 'en-us', 'smd_artstat_pos', 'smd_artstat', 'site', 'Emplacement dans la page', '2019-07-23 23:32:07'),
(4794, 'en-us', 'smd_artstat_pos_above_status', 'smd_artstat', 'site', 'Au dessus des statuts', '2019-07-23 23:32:07'),
(4795, 'en-us', 'smd_artstat_pos_above_textfilter', 'smd_artstat', 'site', 'Au dessus du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4796, 'en-us', 'smd_artstat_pos_above_textile', 'smd_artstat', 'site', 'Au dessus de l\'aide Textile', '2019-07-23 23:32:07'),
(4797, 'en-us', 'smd_artstat_pos_above_title', 'smd_artstat', 'site', 'Au dessus du titre', '2019-07-23 23:32:07'),
(4798, 'en-us', 'smd_artstat_pos_below_author', 'smd_artstat', 'site', 'Sous le nom d\'auteur', '2019-07-23 23:32:07'),
(4799, 'en-us', 'smd_artstat_pos_below_excerpt', 'smd_artstat', 'site', 'Sous le résumé', '2019-07-23 23:32:07'),
(4800, 'en-us', 'smd_artstat_pos_below_textfilter', 'smd_artstat', 'site', 'En dessous du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4801, 'en-us', 'smd_artstat_pos_below_textile', 'smd_artstat', 'site', 'En dessous de l\'aide Textile', '2019-07-23 23:32:07'),
(4802, 'en-us', 'smd_artstat_singular', 'smd_artstat', 'site', 'Nombre pris au singulier', '2019-07-23 23:32:07'),
(4803, 'en-us', 'smd_artstat_word_plural', 'smd_artstat', 'site', 'Mots', '2019-07-23 23:32:07'),
(4804, 'en-us', 'smd_artstat_word_singular', 'smd_artstat', 'site', 'Mot', '2019-07-23 23:32:07'),
(4805, 'el', 'smd_artstat', 'smd_artstat', 'site', 'Statistiques d\'article', '2019-07-23 23:32:07'),
(4806, 'el', 'smd_artstat_fields', 'smd_artstat', 'site', 'Champs de décompte et sélecteur du DOM', '2019-07-23 23:32:07'),
(4807, 'el', 'smd_artstat_id', 'smd_artstat', 'site', 'Afficher l\'ID de l\'article', '2019-07-23 23:32:07'),
(4808, 'el', 'smd_artstat_legend', 'smd_artstat', 'site', 'Statistiques article', '2019-07-23 23:32:07'),
(4809, 'el', 'smd_artstat_pos', 'smd_artstat', 'site', 'Emplacement dans la page', '2019-07-23 23:32:07'),
(4810, 'el', 'smd_artstat_pos_above_status', 'smd_artstat', 'site', 'Au dessus des statuts', '2019-07-23 23:32:07'),
(4811, 'el', 'smd_artstat_pos_above_textfilter', 'smd_artstat', 'site', 'Au dessus du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4812, 'el', 'smd_artstat_pos_above_textile', 'smd_artstat', 'site', 'Au dessus de l\'aide Textile', '2019-07-23 23:32:07'),
(4813, 'el', 'smd_artstat_pos_above_title', 'smd_artstat', 'site', 'Au dessus du titre', '2019-07-23 23:32:07'),
(4814, 'el', 'smd_artstat_pos_below_author', 'smd_artstat', 'site', 'Sous le nom d\'auteur', '2019-07-23 23:32:07'),
(4815, 'el', 'smd_artstat_pos_below_excerpt', 'smd_artstat', 'site', 'Sous le résumé', '2019-07-23 23:32:07'),
(4816, 'el', 'smd_artstat_pos_below_textfilter', 'smd_artstat', 'site', 'En dessous du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4817, 'el', 'smd_artstat_pos_below_textile', 'smd_artstat', 'site', 'En dessous de l\'aide Textile', '2019-07-23 23:32:07'),
(4818, 'el', 'smd_artstat_singular', 'smd_artstat', 'site', 'Nombre pris au singulier', '2019-07-23 23:32:07'),
(4819, 'el', 'smd_artstat_word_plural', 'smd_artstat', 'site', 'Mots', '2019-07-23 23:32:07'),
(4820, 'el', 'smd_artstat_word_singular', 'smd_artstat', 'site', 'Mot', '2019-07-23 23:32:07'),
(4821, 'en', 'smd_artstat', 'smd_artstat', 'site', 'Statistiques d\'article', '2019-07-23 23:32:07'),
(4822, 'en', 'smd_artstat_fields', 'smd_artstat', 'site', 'Champs de décompte et sélecteur du DOM', '2019-07-23 23:32:07'),
(4823, 'en', 'smd_artstat_id', 'smd_artstat', 'site', 'Afficher l\'ID de l\'article', '2019-07-23 23:32:07'),
(4824, 'en', 'smd_artstat_legend', 'smd_artstat', 'site', 'Statistiques article', '2019-07-23 23:32:07'),
(4825, 'en', 'smd_artstat_pos', 'smd_artstat', 'site', 'Emplacement dans la page', '2019-07-23 23:32:07'),
(4826, 'en', 'smd_artstat_pos_above_status', 'smd_artstat', 'site', 'Au dessus des statuts', '2019-07-23 23:32:07'),
(4827, 'en', 'smd_artstat_pos_above_textfilter', 'smd_artstat', 'site', 'Au dessus du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4828, 'en', 'smd_artstat_pos_above_textile', 'smd_artstat', 'site', 'Au dessus de l\'aide Textile', '2019-07-23 23:32:07'),
(4829, 'en', 'smd_artstat_pos_above_title', 'smd_artstat', 'site', 'Au dessus du titre', '2019-07-23 23:32:07'),
(4830, 'en', 'smd_artstat_pos_below_author', 'smd_artstat', 'site', 'Sous le nom d\'auteur', '2019-07-23 23:32:07'),
(4831, 'en', 'smd_artstat_pos_below_excerpt', 'smd_artstat', 'site', 'Sous le résumé', '2019-07-23 23:32:07'),
(4832, 'en', 'smd_artstat_pos_below_textfilter', 'smd_artstat', 'site', 'En dessous du sélecteur Aide Textile', '2019-07-23 23:32:07'),
(4833, 'en', 'smd_artstat_pos_below_textile', 'smd_artstat', 'site', 'En dessous de l\'aide Textile', '2019-07-23 23:32:07'),
(4834, 'en', 'smd_artstat_singular', 'smd_artstat', 'site', 'Nombre pris au singulier', '2019-07-23 23:32:07'),
(4835, 'en', 'smd_artstat_word_plural', 'smd_artstat', 'site', 'Mots', '2019-07-23 23:32:07'),
(4836, 'en', 'smd_artstat_word_singular', 'smd_artstat', 'site', 'Mot', '2019-07-23 23:32:07'),
(4837, 'de', 'smd_prognostics', 'smd_prognostics', 'site', 'Prognostics', '2019-07-23 23:37:20'),
(4838, 'de', 'smd_prognostics_acked', 'smd_prognostics', 'site', 'Alarms acknowledged.', '2019-07-23 23:37:20'),
(4839, 'de', 'smd_prognostics_alarm_freq', 'smd_prognostics', 'site', 'Alarm on detection and every:', '2019-07-23 23:37:20'),
(4840, 'de', 'smd_prognostics_and', 'smd_prognostics', 'site', 'and', '2019-07-23 23:37:20'),
(4841, 'de', 'smd_prognostics_auth_users', 'smd_prognostics', 'site', 'Restrict prognostic config to:', '2019-07-23 23:37:20'),
(4842, 'de', 'smd_prognostics_block', 'smd_prognostics', 'site', 'Block', '2019-07-23 23:37:20'),
(4843, 'de', 'smd_prognostics_btn_ackit', 'smd_prognostics', 'site', 'Acknowledge', '2019-07-23 23:37:20'),
(4844, 'de', 'smd_prognostics_btn_csi', 'smd_prognostics', 'site', 'Send Forensics', '2019-07-23 23:37:20'),
(4845, 'de', 'smd_prognostics_btn_ignore', 'smd_prognostics', 'site', 'Ignore', '2019-07-23 23:37:20'),
(4846, 'de', 'smd_prognostics_check_between', 'smd_prognostics', 'site', 'Check files between:', '2019-07-23 23:37:20'),
(4847, 'de', 'smd_prognostics_check_for', 'smd_prognostics', 'site', 'Check files for:', '2019-07-23 23:37:20'),
(4848, 'de', 'smd_prognostics_check_freq', 'smd_prognostics', 'site', 'Check files (at most) every:', '2019-07-23 23:37:20'),
(4849, 'de', 'smd_prognostics_check_qty', 'smd_prognostics', 'site', 'Check this many files each time:', '2019-07-23 23:37:20'),
(4850, 'de', 'smd_prognostics_check_where', 'smd_prognostics', 'site', 'Check files on public side clicks:', '2019-07-23 23:37:20'),
(4851, 'de', 'smd_prognostics_ch_add', 'smd_prognostics', 'site', 'Additions', '2019-07-23 23:37:20'),
(4852, 'de', 'smd_prognostics_ch_del', 'smd_prognostics', 'site', 'Deletions', '2019-07-23 23:37:20'),
(4853, 'de', 'smd_prognostics_ch_mod', 'smd_prognostics', 'site', 'Modifications', '2019-07-23 23:37:20'),
(4854, 'de', 'smd_prognostics_csense', 'smd_prognostics', 'site', '(case-sensitive)', '2019-07-23 23:37:20'),
(4855, 'de', 'smd_prognostics_csi_sent', 'smd_prognostics', 'site', 'Forensics data sent', '2019-07-23 23:37:20'),
(4856, 'de', 'smd_prognostics_csv', 'smd_prognostics', 'site', '(comma-separated)', '2019-07-23 23:37:20'),
(4857, 'de', 'smd_prognostics_currmon', 'smd_prognostics', 'site', 'You are currently monitoring {curr} out of {outof} available files.', '2019-07-23 23:37:20'),
(4858, 'de', 'smd_prognostics_docroot_files', 'smd_prognostics', 'site', 'Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.', '2019-07-23 23:37:20'),
(4859, 'de', 'smd_prognostics_docroot_prognostics', 'smd_prognostics', 'site', 'Please change the \"Prognostics folder\" setting to a directory outside your site root folder.', '2019-07-23 23:37:20'),
(4860, 'de', 'smd_prognostics_docroot_tmp', 'smd_prognostics', 'site', 'Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.', '2019-07-23 23:37:20'),
(4861, 'de', 'smd_prognostics_excludir', 'smd_prognostics', 'site', 'Exclude folders:', '2019-07-23 23:37:20'),
(4862, 'de', 'smd_prognostics_files_updated', 'smd_prognostics', 'site', 'File list updated', '2019-07-23 23:37:20'),
(4863, 'de', 'smd_prognostics_help_link', 'smd_prognostics', 'site', '(<a href=\"{link}\">Help</a>)', '2019-07-23 23:37:20'),
(4864, 'de', 'smd_prognostics_hms', 'smd_prognostics', 'site', '(hrs:mins:secs)', '2019-07-23 23:37:20'),
(4865, 'de', 'smd_prognostics_ignores', 'smd_prognostics', 'site', 'Ignore files:', '2019-07-23 23:37:20'),
(4866, 'de', 'smd_prognostics_lbl_added', 'smd_prognostics', 'site', 'Added (not monitored)', '2019-07-23 23:37:20'),
(4867, 'de', 'smd_prognostics_lbl_changed', 'smd_prognostics', 'site', 'Changed', '2019-07-23 23:37:20'),
(4868, 'de', 'smd_prognostics_lbl_hashmod', 'smd_prognostics', 'site', 'COMPROMISED', '2019-07-23 23:37:20'),
(4869, 'de', 'smd_prognostics_lbl_missing', 'smd_prognostics', 'site', 'Missing', '2019-07-23 23:37:20'),
(4870, 'de', 'smd_prognostics_listloc', 'smd_prognostics', 'site', 'File locations:', '2019-07-23 23:37:20'),
(4871, 'de', 'smd_prognostics_mailto', 'smd_prognostics', 'site', 'Send e-mail to:', '2019-07-23 23:37:20'),
(4872, 'de', 'smd_prognostics_mailto_csi', 'smd_prognostics', 'site', 'Send forensics to:', '2019-07-23 23:37:20'),
(4873, 'de', 'smd_prognostics_monfiles_explain', 'smd_prognostics', 'site', 'Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.', '2019-07-23 23:37:20'),
(4874, 'de', 'smd_prognostics_none_selected', 'smd_prognostics', 'site', 'No files selected', '2019-07-23 23:37:20'),
(4875, 'de', 'smd_prognostics_notify_email', 'smd_prognostics', 'site', 'E-mail', '2019-07-23 23:37:20'),
(4876, 'de', 'smd_prognostics_notify_txp', 'smd_prognostics', 'site', 'Txp interface', '2019-07-23 23:37:20'),
(4877, 'de', 'smd_prognostics_notify_via', 'smd_prognostics', 'site', 'Notify via:', '2019-07-23 23:37:20'),
(4878, 'de', 'smd_prognostics_not_writable', 'smd_prognostics', 'site', '{location} is not a directory or is not writable', '2019-07-23 23:37:20'),
(4879, 'de', 'smd_prognostics_no_alarms', 'smd_prognostics', 'site', 'No alarms to acknowledge at present.', '2019-07-23 23:37:20'),
(4880, 'de', 'smd_prognostics_no_files', 'smd_prognostics', 'site', 'No files to list. Check the plugin setup (and Save it).', '2019-07-23 23:37:20'),
(4881, 'de', 'smd_prognostics_no_log_entries', 'smd_prognostics', 'site', 'No log activity around the time of the differences.', '2019-07-23 23:37:20'),
(4882, 'de', 'smd_prognostics_pnl_ack', 'smd_prognostics', 'site', 'Alarms', '2019-07-23 23:37:20'),
(4883, 'de', 'smd_prognostics_pnl_advice', 'smd_prognostics', 'site', 'Advice', '2019-07-23 23:37:20'),
(4884, 'de', 'smd_prognostics_pnl_files', 'smd_prognostics', 'site', 'Files', '2019-07-23 23:37:20'),
(4885, 'de', 'smd_prognostics_pnl_setup', 'smd_prognostics', 'site', 'Setup', '2019-07-23 23:37:20'),
(4886, 'de', 'smd_prognostics_postamble', 'smd_prognostics', 'site', 'Acknowledge alarms', '2019-07-23 23:37:20'),
(4887, 'de', 'smd_prognostics_preamble_added', 'smd_prognostics', 'site', 'These files have been added:', '2019-07-23 23:37:20'),
(4888, 'de', 'smd_prognostics_preamble_files', 'smd_prognostics', 'site', 'File contents follows:', '2019-07-23 23:37:20'),
(4889, 'de', 'smd_prognostics_preamble_hashdown', 'smd_prognostics', 'site', 'WARNING! The checksums file is missing.', '2019-07-23 23:37:20'),
(4890, 'de', 'smd_prognostics_preamble_hashmod', 'smd_prognostics', 'site', 'WARNING! The checksums file has been altered:', '2019-07-23 23:37:20'),
(4891, 'de', 'smd_prognostics_preamble_miss', 'smd_prognostics', 'site', 'These files are missing:', '2019-07-23 23:37:20'),
(4892, 'de', 'smd_prognostics_preamble_nok', 'smd_prognostics', 'site', 'These files differ from their expected content:', '2019-07-23 23:37:20'),
(4893, 'de', 'smd_prognostics_preamble_sql_inject', 'smd_prognostics', 'site', 'Possible SQL injection detected', '2019-07-23 23:37:20'),
(4894, 'de', 'smd_prognostics_preamble_txplog', 'smd_prognostics', 'site', 'Log entries prior to this detection:', '2019-07-23 23:37:20'),
(4895, 'de', 'smd_prognostics_progloc', 'smd_prognostics', 'site', 'Prognostics folder:', '2019-07-23 23:37:20'),
(4896, 'de', 'smd_prognostics_progpfx', 'smd_prognostics', 'site', 'Unique prefix:', '2019-07-23 23:37:20'),
(4897, 'de', 'smd_prognostics_req_headers', 'smd_prognostics', 'site', 'Block request headers:', '2019-07-23 23:37:20'),
(4898, 'de', 'smd_prognostics_req_not_allowed', 'smd_prognostics', 'site', 'REQUEST_METHOD not allowed: {req}', '2019-07-23 23:37:20'),
(4899, 'de', 'smd_prognostics_rh_del', 'smd_prognostics', 'site', 'DELETE', '2019-07-23 23:37:20'),
(4900, 'de', 'smd_prognostics_rh_put', 'smd_prognostics', 'site', 'PUT', '2019-07-23 23:37:20'),
(4901, 'de', 'smd_prognostics_rh_trace', 'smd_prognostics', 'site', 'TRACE', '2019-07-23 23:37:20'),
(4902, 'de', 'smd_prognostics_rpc_exists', 'smd_prognostics', 'site', 'The rpc directory is not being used. Consider removing it.', '2019-07-23 23:37:20'),
(4903, 'de', 'smd_prognostics_rt_hdr', 'smd_prognostics', 'site', 'Header violations', '2019-07-23 23:37:20'),
(4904, 'de', 'smd_prognostics_rt_sql', 'smd_prognostics', 'site', 'SQL injections', '2019-07-23 23:37:20'),
(4905, 'de', 'smd_prognostics_seconds', 'smd_prognostics', 'site', '(seconds)', '2019-07-23 23:37:20'),
(4906, 'de', 'smd_prognostics_send_forensics', 'smd_prognostics', 'site', 'Send forensics for:', '2019-07-23 23:37:20'),
(4907, 'de', 'smd_prognostics_sensitivity', 'smd_prognostics', 'site', 'Sensitivity threshold:', '2019-07-23 23:37:20'),
(4908, 'de', 'smd_prognostics_sensitivity_explain', 'smd_prognostics', 'site', '(1=most sensitive)', '2019-07-23 23:37:20'),
(4909, 'de', 'smd_prognostics_setup_exists', 'smd_prognostics', 'site', 'The setup directory still exists. Please delete it.', '2019-07-23 23:37:20'),
(4910, 'de', 'smd_prognostics_show_grants', 'smd_prognostics', 'site', 'Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.', '2019-07-23 23:37:20'),
(4911, 'de', 'smd_prognostics_sql_file_privs', 'smd_prognostics', 'site', 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.', '2019-07-23 23:37:20'),
(4912, 'de', 'smd_prognostics_sql_inject', 'smd_prognostics', 'site', 'Protect against SQL injection:', '2019-07-23 23:37:20'),
(4913, 'de', 'smd_prognostics_stat_gid', 'smd_prognostics', 'site', 'Group ID:', '2019-07-23 23:37:20'),
(4914, 'de', 'smd_prognostics_stat_mod', 'smd_prognostics', 'site', 'Modified:', '2019-07-23 23:37:20'),
(4915, 'de', 'smd_prognostics_stat_name', 'smd_prognostics', 'site', 'Filename:', '2019-07-23 23:37:20'),
(4916, 'de', 'smd_prognostics_stat_size', 'smd_prognostics', 'site', 'Size:', '2019-07-23 23:37:20'),
(4917, 'de', 'smd_prognostics_stat_uid', 'smd_prognostics', 'site', 'User ID:', '2019-07-23 23:37:20'),
(4918, 'de', 'smd_prognostics_subject', 'smd_prognostics', 'site', 'Prognostics ({site})', '2019-07-23 23:37:20'),
(4919, 'de', 'smd_prognostics_subject_csi', 'smd_prognostics', 'site', 'Prognostics ({site}) forensics', '2019-07-23 23:37:20'),
(4920, 'de', 'smd_prognostics_tight', 'smd_prognostics', 'site', 'You run a pretty tight ship. Stay frosty.', '2019-07-23 23:37:20'),
(4921, 'de', 'smd_prognostics_ttl_ack', 'smd_prognostics', 'site', 'Prognostic alarm acknowledgement', '2019-07-23 23:37:20'),
(4922, 'de', 'smd_prognostics_ttl_advice', 'smd_prognostics', 'site', 'Prognostic advice', '2019-07-23 23:37:20'),
(4923, 'de', 'smd_prognostics_ttl_fileopts', 'smd_prognostics', 'site', 'Filesystem options', '2019-07-23 23:37:20'),
(4924, 'de', 'smd_prognostics_ttl_files', 'smd_prognostics', 'site', 'Prognostic file monitoring', '2019-07-23 23:37:20'),
(4925, 'de', 'smd_prognostics_ttl_monopts', 'smd_prognostics', 'site', 'Monitoring options', '2019-07-23 23:37:20'),
(4926, 'de', 'smd_prognostics_ttl_plugopts', 'smd_prognostics', 'site', 'Plugin options', '2019-07-23 23:37:20'),
(4927, 'de', 'smd_prognostics_ttl_realopts', 'smd_prognostics', 'site', 'Realtime options', '2019-07-23 23:37:20'),
(4928, 'de', 'smd_prognostics_ttl_setup', 'smd_prognostics', 'site', 'Prognostic setup', '2019-07-23 23:37:20'),
(4929, 'de', 'smd_prognostics_txpdir', 'smd_prognostics', 'site', 'Admin-side URL:', '2019-07-23 23:37:20'),
(4930, 'de', 'smd_prognostics_warn_loc', 'smd_prognostics', 'site', 'File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now', '2019-07-23 23:37:20'),
(4931, 'de', 'smd_prognostics_with', 'smd_prognostics', 'site', 'with:', '2019-07-23 23:37:20'),
(4932, 'de', 'smd_prognostics_xss', 'smd_prognostics', 'site', 'XSS shield', '2019-07-23 23:37:20'),
(4933, 'de', 'smd_prognostics_xss_explain', 'smd_prognostics', 'site', '(may break Textiled comments)', '2019-07-23 23:37:20'),
(4934, 'en-us', 'smd_prognostics', 'smd_prognostics', 'site', 'Prognostics', '2019-07-23 23:37:20'),
(4935, 'en-us', 'smd_prognostics_acked', 'smd_prognostics', 'site', 'Alarms acknowledged.', '2019-07-23 23:37:20'),
(4936, 'en-us', 'smd_prognostics_alarm_freq', 'smd_prognostics', 'site', 'Alarm on detection and every:', '2019-07-23 23:37:20'),
(4937, 'en-us', 'smd_prognostics_and', 'smd_prognostics', 'site', 'and', '2019-07-23 23:37:20'),
(4938, 'en-us', 'smd_prognostics_auth_users', 'smd_prognostics', 'site', 'Restrict prognostic config to:', '2019-07-23 23:37:20'),
(4939, 'en-us', 'smd_prognostics_block', 'smd_prognostics', 'site', 'Block', '2019-07-23 23:37:20'),
(4940, 'en-us', 'smd_prognostics_btn_ackit', 'smd_prognostics', 'site', 'Acknowledge', '2019-07-23 23:37:20'),
(4941, 'en-us', 'smd_prognostics_btn_csi', 'smd_prognostics', 'site', 'Send Forensics', '2019-07-23 23:37:20'),
(4942, 'en-us', 'smd_prognostics_btn_ignore', 'smd_prognostics', 'site', 'Ignore', '2019-07-23 23:37:20'),
(4943, 'en-us', 'smd_prognostics_check_between', 'smd_prognostics', 'site', 'Check files between:', '2019-07-23 23:37:20'),
(4944, 'en-us', 'smd_prognostics_check_for', 'smd_prognostics', 'site', 'Check files for:', '2019-07-23 23:37:20'),
(4945, 'en-us', 'smd_prognostics_check_freq', 'smd_prognostics', 'site', 'Check files (at most) every:', '2019-07-23 23:37:20'),
(4946, 'en-us', 'smd_prognostics_check_qty', 'smd_prognostics', 'site', 'Check this many files each time:', '2019-07-23 23:37:20'),
(4947, 'en-us', 'smd_prognostics_check_where', 'smd_prognostics', 'site', 'Check files on public side clicks:', '2019-07-23 23:37:20'),
(4948, 'en-us', 'smd_prognostics_ch_add', 'smd_prognostics', 'site', 'Additions', '2019-07-23 23:37:20'),
(4949, 'en-us', 'smd_prognostics_ch_del', 'smd_prognostics', 'site', 'Deletions', '2019-07-23 23:37:20'),
(4950, 'en-us', 'smd_prognostics_ch_mod', 'smd_prognostics', 'site', 'Modifications', '2019-07-23 23:37:20'),
(4951, 'en-us', 'smd_prognostics_csense', 'smd_prognostics', 'site', '(case-sensitive)', '2019-07-23 23:37:20'),
(4952, 'en-us', 'smd_prognostics_csi_sent', 'smd_prognostics', 'site', 'Forensics data sent', '2019-07-23 23:37:20'),
(4953, 'en-us', 'smd_prognostics_csv', 'smd_prognostics', 'site', '(comma-separated)', '2019-07-23 23:37:20'),
(4954, 'en-us', 'smd_prognostics_currmon', 'smd_prognostics', 'site', 'You are currently monitoring {curr} out of {outof} available files.', '2019-07-23 23:37:20'),
(4955, 'en-us', 'smd_prognostics_docroot_files', 'smd_prognostics', 'site', 'Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.', '2019-07-23 23:37:20'),
(4956, 'en-us', 'smd_prognostics_docroot_prognostics', 'smd_prognostics', 'site', 'Please change the \"Prognostics folder\" setting to a directory outside your site root folder.', '2019-07-23 23:37:20'),
(4957, 'en-us', 'smd_prognostics_docroot_tmp', 'smd_prognostics', 'site', 'Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.', '2019-07-23 23:37:20'),
(4958, 'en-us', 'smd_prognostics_excludir', 'smd_prognostics', 'site', 'Exclude folders:', '2019-07-23 23:37:20'),
(4959, 'en-us', 'smd_prognostics_files_updated', 'smd_prognostics', 'site', 'File list updated', '2019-07-23 23:37:20'),
(4960, 'en-us', 'smd_prognostics_help_link', 'smd_prognostics', 'site', '(<a href=\"{link}\">Help</a>)', '2019-07-23 23:37:20'),
(4961, 'en-us', 'smd_prognostics_hms', 'smd_prognostics', 'site', '(hrs:mins:secs)', '2019-07-23 23:37:20'),
(4962, 'en-us', 'smd_prognostics_ignores', 'smd_prognostics', 'site', 'Ignore files:', '2019-07-23 23:37:20'),
(4963, 'en-us', 'smd_prognostics_lbl_added', 'smd_prognostics', 'site', 'Added (not monitored)', '2019-07-23 23:37:20'),
(4964, 'en-us', 'smd_prognostics_lbl_changed', 'smd_prognostics', 'site', 'Changed', '2019-07-23 23:37:20'),
(4965, 'en-us', 'smd_prognostics_lbl_hashmod', 'smd_prognostics', 'site', 'COMPROMISED', '2019-07-23 23:37:20'),
(4966, 'en-us', 'smd_prognostics_lbl_missing', 'smd_prognostics', 'site', 'Missing', '2019-07-23 23:37:20'),
(4967, 'en-us', 'smd_prognostics_listloc', 'smd_prognostics', 'site', 'File locations:', '2019-07-23 23:37:20'),
(4968, 'en-us', 'smd_prognostics_mailto', 'smd_prognostics', 'site', 'Send e-mail to:', '2019-07-23 23:37:20'),
(4969, 'en-us', 'smd_prognostics_mailto_csi', 'smd_prognostics', 'site', 'Send forensics to:', '2019-07-23 23:37:20'),
(4970, 'en-us', 'smd_prognostics_monfiles_explain', 'smd_prognostics', 'site', 'Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.', '2019-07-23 23:37:20'),
(4971, 'en-us', 'smd_prognostics_none_selected', 'smd_prognostics', 'site', 'No files selected', '2019-07-23 23:37:20'),
(4972, 'en-us', 'smd_prognostics_notify_email', 'smd_prognostics', 'site', 'E-mail', '2019-07-23 23:37:20'),
(4973, 'en-us', 'smd_prognostics_notify_txp', 'smd_prognostics', 'site', 'Txp interface', '2019-07-23 23:37:20'),
(4974, 'en-us', 'smd_prognostics_notify_via', 'smd_prognostics', 'site', 'Notify via:', '2019-07-23 23:37:20'),
(4975, 'en-us', 'smd_prognostics_not_writable', 'smd_prognostics', 'site', '{location} is not a directory or is not writable', '2019-07-23 23:37:20'),
(4976, 'en-us', 'smd_prognostics_no_alarms', 'smd_prognostics', 'site', 'No alarms to acknowledge at present.', '2019-07-23 23:37:20'),
(4977, 'en-us', 'smd_prognostics_no_files', 'smd_prognostics', 'site', 'No files to list. Check the plugin setup (and Save it).', '2019-07-23 23:37:20'),
(4978, 'en-us', 'smd_prognostics_no_log_entries', 'smd_prognostics', 'site', 'No log activity around the time of the differences.', '2019-07-23 23:37:20'),
(4979, 'en-us', 'smd_prognostics_pnl_ack', 'smd_prognostics', 'site', 'Alarms', '2019-07-23 23:37:20'),
(4980, 'en-us', 'smd_prognostics_pnl_advice', 'smd_prognostics', 'site', 'Advice', '2019-07-23 23:37:20'),
(4981, 'en-us', 'smd_prognostics_pnl_files', 'smd_prognostics', 'site', 'Files', '2019-07-23 23:37:20'),
(4982, 'en-us', 'smd_prognostics_pnl_setup', 'smd_prognostics', 'site', 'Setup', '2019-07-23 23:37:20'),
(4983, 'en-us', 'smd_prognostics_postamble', 'smd_prognostics', 'site', 'Acknowledge alarms', '2019-07-23 23:37:20'),
(4984, 'en-us', 'smd_prognostics_preamble_added', 'smd_prognostics', 'site', 'These files have been added:', '2019-07-23 23:37:20'),
(4985, 'en-us', 'smd_prognostics_preamble_files', 'smd_prognostics', 'site', 'File contents follows:', '2019-07-23 23:37:20'),
(4986, 'en-us', 'smd_prognostics_preamble_hashdown', 'smd_prognostics', 'site', 'WARNING! The checksums file is missing.', '2019-07-23 23:37:20'),
(4987, 'en-us', 'smd_prognostics_preamble_hashmod', 'smd_prognostics', 'site', 'WARNING! The checksums file has been altered:', '2019-07-23 23:37:20'),
(4988, 'en-us', 'smd_prognostics_preamble_miss', 'smd_prognostics', 'site', 'These files are missing:', '2019-07-23 23:37:20'),
(4989, 'en-us', 'smd_prognostics_preamble_nok', 'smd_prognostics', 'site', 'These files differ from their expected content:', '2019-07-23 23:37:20'),
(4990, 'en-us', 'smd_prognostics_preamble_sql_inject', 'smd_prognostics', 'site', 'Possible SQL injection detected', '2019-07-23 23:37:20'),
(4991, 'en-us', 'smd_prognostics_preamble_txplog', 'smd_prognostics', 'site', 'Log entries prior to this detection:', '2019-07-23 23:37:20'),
(4992, 'en-us', 'smd_prognostics_progloc', 'smd_prognostics', 'site', 'Prognostics folder:', '2019-07-23 23:37:20'),
(4993, 'en-us', 'smd_prognostics_progpfx', 'smd_prognostics', 'site', 'Unique prefix:', '2019-07-23 23:37:20'),
(4994, 'en-us', 'smd_prognostics_req_headers', 'smd_prognostics', 'site', 'Block request headers:', '2019-07-23 23:37:20'),
(4995, 'en-us', 'smd_prognostics_req_not_allowed', 'smd_prognostics', 'site', 'REQUEST_METHOD not allowed: {req}', '2019-07-23 23:37:20'),
(4996, 'en-us', 'smd_prognostics_rh_del', 'smd_prognostics', 'site', 'DELETE', '2019-07-23 23:37:20'),
(4997, 'en-us', 'smd_prognostics_rh_put', 'smd_prognostics', 'site', 'PUT', '2019-07-23 23:37:20'),
(4998, 'en-us', 'smd_prognostics_rh_trace', 'smd_prognostics', 'site', 'TRACE', '2019-07-23 23:37:20');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(4999, 'en-us', 'smd_prognostics_rpc_exists', 'smd_prognostics', 'site', 'The rpc directory is not being used. Consider removing it.', '2019-07-23 23:37:20'),
(5000, 'en-us', 'smd_prognostics_rt_hdr', 'smd_prognostics', 'site', 'Header violations', '2019-07-23 23:37:20'),
(5001, 'en-us', 'smd_prognostics_rt_sql', 'smd_prognostics', 'site', 'SQL injections', '2019-07-23 23:37:20'),
(5002, 'en-us', 'smd_prognostics_seconds', 'smd_prognostics', 'site', '(seconds)', '2019-07-23 23:37:20'),
(5003, 'en-us', 'smd_prognostics_send_forensics', 'smd_prognostics', 'site', 'Send forensics for:', '2019-07-23 23:37:20'),
(5004, 'en-us', 'smd_prognostics_sensitivity', 'smd_prognostics', 'site', 'Sensitivity threshold:', '2019-07-23 23:37:20'),
(5005, 'en-us', 'smd_prognostics_sensitivity_explain', 'smd_prognostics', 'site', '(1=most sensitive)', '2019-07-23 23:37:20'),
(5006, 'en-us', 'smd_prognostics_setup_exists', 'smd_prognostics', 'site', 'The setup directory still exists. Please delete it.', '2019-07-23 23:37:20'),
(5007, 'en-us', 'smd_prognostics_show_grants', 'smd_prognostics', 'site', 'Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.', '2019-07-23 23:37:20'),
(5008, 'en-us', 'smd_prognostics_sql_file_privs', 'smd_prognostics', 'site', 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.', '2019-07-23 23:37:20'),
(5009, 'en-us', 'smd_prognostics_sql_inject', 'smd_prognostics', 'site', 'Protect against SQL injection:', '2019-07-23 23:37:20'),
(5010, 'en-us', 'smd_prognostics_stat_gid', 'smd_prognostics', 'site', 'Group ID:', '2019-07-23 23:37:20'),
(5011, 'en-us', 'smd_prognostics_stat_mod', 'smd_prognostics', 'site', 'Modified:', '2019-07-23 23:37:20'),
(5012, 'en-us', 'smd_prognostics_stat_name', 'smd_prognostics', 'site', 'Filename:', '2019-07-23 23:37:20'),
(5013, 'en-us', 'smd_prognostics_stat_size', 'smd_prognostics', 'site', 'Size:', '2019-07-23 23:37:20'),
(5014, 'en-us', 'smd_prognostics_stat_uid', 'smd_prognostics', 'site', 'User ID:', '2019-07-23 23:37:20'),
(5015, 'en-us', 'smd_prognostics_subject', 'smd_prognostics', 'site', 'Prognostics ({site})', '2019-07-23 23:37:20'),
(5016, 'en-us', 'smd_prognostics_subject_csi', 'smd_prognostics', 'site', 'Prognostics ({site}) forensics', '2019-07-23 23:37:20'),
(5017, 'en-us', 'smd_prognostics_tight', 'smd_prognostics', 'site', 'You run a pretty tight ship. Stay frosty.', '2019-07-23 23:37:20'),
(5018, 'en-us', 'smd_prognostics_ttl_ack', 'smd_prognostics', 'site', 'Prognostic alarm acknowledgement', '2019-07-23 23:37:20'),
(5019, 'en-us', 'smd_prognostics_ttl_advice', 'smd_prognostics', 'site', 'Prognostic advice', '2019-07-23 23:37:20'),
(5020, 'en-us', 'smd_prognostics_ttl_fileopts', 'smd_prognostics', 'site', 'Filesystem options', '2019-07-23 23:37:20'),
(5021, 'en-us', 'smd_prognostics_ttl_files', 'smd_prognostics', 'site', 'Prognostic file monitoring', '2019-07-23 23:37:20'),
(5022, 'en-us', 'smd_prognostics_ttl_monopts', 'smd_prognostics', 'site', 'Monitoring options', '2019-07-23 23:37:20'),
(5023, 'en-us', 'smd_prognostics_ttl_plugopts', 'smd_prognostics', 'site', 'Plugin options', '2019-07-23 23:37:20'),
(5024, 'en-us', 'smd_prognostics_ttl_realopts', 'smd_prognostics', 'site', 'Realtime options', '2019-07-23 23:37:20'),
(5025, 'en-us', 'smd_prognostics_ttl_setup', 'smd_prognostics', 'site', 'Prognostic setup', '2019-07-23 23:37:20'),
(5026, 'en-us', 'smd_prognostics_txpdir', 'smd_prognostics', 'site', 'Admin-side URL:', '2019-07-23 23:37:20'),
(5027, 'en-us', 'smd_prognostics_warn_loc', 'smd_prognostics', 'site', 'File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now', '2019-07-23 23:37:20'),
(5028, 'en-us', 'smd_prognostics_with', 'smd_prognostics', 'site', 'with:', '2019-07-23 23:37:20'),
(5029, 'en-us', 'smd_prognostics_xss', 'smd_prognostics', 'site', 'XSS shield', '2019-07-23 23:37:20'),
(5030, 'en-us', 'smd_prognostics_xss_explain', 'smd_prognostics', 'site', '(may break Textiled comments)', '2019-07-23 23:37:20'),
(5031, 'el', 'smd_prognostics', 'smd_prognostics', 'site', 'Prognostics', '2019-07-23 23:37:20'),
(5032, 'el', 'smd_prognostics_acked', 'smd_prognostics', 'site', 'Alarms acknowledged.', '2019-07-23 23:37:20'),
(5033, 'el', 'smd_prognostics_alarm_freq', 'smd_prognostics', 'site', 'Alarm on detection and every:', '2019-07-23 23:37:20'),
(5034, 'el', 'smd_prognostics_and', 'smd_prognostics', 'site', 'and', '2019-07-23 23:37:20'),
(5035, 'el', 'smd_prognostics_auth_users', 'smd_prognostics', 'site', 'Restrict prognostic config to:', '2019-07-23 23:37:20'),
(5036, 'el', 'smd_prognostics_block', 'smd_prognostics', 'site', 'Block', '2019-07-23 23:37:20'),
(5037, 'el', 'smd_prognostics_btn_ackit', 'smd_prognostics', 'site', 'Acknowledge', '2019-07-23 23:37:20'),
(5038, 'el', 'smd_prognostics_btn_csi', 'smd_prognostics', 'site', 'Send Forensics', '2019-07-23 23:37:20'),
(5039, 'el', 'smd_prognostics_btn_ignore', 'smd_prognostics', 'site', 'Ignore', '2019-07-23 23:37:20'),
(5040, 'el', 'smd_prognostics_check_between', 'smd_prognostics', 'site', 'Check files between:', '2019-07-23 23:37:20'),
(5041, 'el', 'smd_prognostics_check_for', 'smd_prognostics', 'site', 'Check files for:', '2019-07-23 23:37:20'),
(5042, 'el', 'smd_prognostics_check_freq', 'smd_prognostics', 'site', 'Check files (at most) every:', '2019-07-23 23:37:20'),
(5043, 'el', 'smd_prognostics_check_qty', 'smd_prognostics', 'site', 'Check this many files each time:', '2019-07-23 23:37:20'),
(5044, 'el', 'smd_prognostics_check_where', 'smd_prognostics', 'site', 'Check files on public side clicks:', '2019-07-23 23:37:20'),
(5045, 'el', 'smd_prognostics_ch_add', 'smd_prognostics', 'site', 'Additions', '2019-07-23 23:37:20'),
(5046, 'el', 'smd_prognostics_ch_del', 'smd_prognostics', 'site', 'Deletions', '2019-07-23 23:37:20'),
(5047, 'el', 'smd_prognostics_ch_mod', 'smd_prognostics', 'site', 'Modifications', '2019-07-23 23:37:20'),
(5048, 'el', 'smd_prognostics_csense', 'smd_prognostics', 'site', '(case-sensitive)', '2019-07-23 23:37:20'),
(5049, 'el', 'smd_prognostics_csi_sent', 'smd_prognostics', 'site', 'Forensics data sent', '2019-07-23 23:37:20'),
(5050, 'el', 'smd_prognostics_csv', 'smd_prognostics', 'site', '(comma-separated)', '2019-07-23 23:37:20'),
(5051, 'el', 'smd_prognostics_currmon', 'smd_prognostics', 'site', 'You are currently monitoring {curr} out of {outof} available files.', '2019-07-23 23:37:20'),
(5052, 'el', 'smd_prognostics_docroot_files', 'smd_prognostics', 'site', 'Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.', '2019-07-23 23:37:20'),
(5053, 'el', 'smd_prognostics_docroot_prognostics', 'smd_prognostics', 'site', 'Please change the \"Prognostics folder\" setting to a directory outside your site root folder.', '2019-07-23 23:37:20'),
(5054, 'el', 'smd_prognostics_docroot_tmp', 'smd_prognostics', 'site', 'Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.', '2019-07-23 23:37:20'),
(5055, 'el', 'smd_prognostics_excludir', 'smd_prognostics', 'site', 'Exclude folders:', '2019-07-23 23:37:20'),
(5056, 'el', 'smd_prognostics_files_updated', 'smd_prognostics', 'site', 'File list updated', '2019-07-23 23:37:20'),
(5057, 'el', 'smd_prognostics_help_link', 'smd_prognostics', 'site', '(<a href=\"{link}\">Help</a>)', '2019-07-23 23:37:20'),
(5058, 'el', 'smd_prognostics_hms', 'smd_prognostics', 'site', '(hrs:mins:secs)', '2019-07-23 23:37:20'),
(5059, 'el', 'smd_prognostics_ignores', 'smd_prognostics', 'site', 'Ignore files:', '2019-07-23 23:37:20'),
(5060, 'el', 'smd_prognostics_lbl_added', 'smd_prognostics', 'site', 'Added (not monitored)', '2019-07-23 23:37:20'),
(5061, 'el', 'smd_prognostics_lbl_changed', 'smd_prognostics', 'site', 'Changed', '2019-07-23 23:37:20'),
(5062, 'el', 'smd_prognostics_lbl_hashmod', 'smd_prognostics', 'site', 'COMPROMISED', '2019-07-23 23:37:20'),
(5063, 'el', 'smd_prognostics_lbl_missing', 'smd_prognostics', 'site', 'Missing', '2019-07-23 23:37:20'),
(5064, 'el', 'smd_prognostics_listloc', 'smd_prognostics', 'site', 'File locations:', '2019-07-23 23:37:20'),
(5065, 'el', 'smd_prognostics_mailto', 'smd_prognostics', 'site', 'Send e-mail to:', '2019-07-23 23:37:20'),
(5066, 'el', 'smd_prognostics_mailto_csi', 'smd_prognostics', 'site', 'Send forensics to:', '2019-07-23 23:37:20'),
(5067, 'el', 'smd_prognostics_monfiles_explain', 'smd_prognostics', 'site', 'Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.', '2019-07-23 23:37:20'),
(5068, 'el', 'smd_prognostics_none_selected', 'smd_prognostics', 'site', 'No files selected', '2019-07-23 23:37:20'),
(5069, 'el', 'smd_prognostics_notify_email', 'smd_prognostics', 'site', 'E-mail', '2019-07-23 23:37:20'),
(5070, 'el', 'smd_prognostics_notify_txp', 'smd_prognostics', 'site', 'Txp interface', '2019-07-23 23:37:20'),
(5071, 'el', 'smd_prognostics_notify_via', 'smd_prognostics', 'site', 'Notify via:', '2019-07-23 23:37:20'),
(5072, 'el', 'smd_prognostics_not_writable', 'smd_prognostics', 'site', '{location} is not a directory or is not writable', '2019-07-23 23:37:20'),
(5073, 'el', 'smd_prognostics_no_alarms', 'smd_prognostics', 'site', 'No alarms to acknowledge at present.', '2019-07-23 23:37:20'),
(5074, 'el', 'smd_prognostics_no_files', 'smd_prognostics', 'site', 'No files to list. Check the plugin setup (and Save it).', '2019-07-23 23:37:20'),
(5075, 'el', 'smd_prognostics_no_log_entries', 'smd_prognostics', 'site', 'No log activity around the time of the differences.', '2019-07-23 23:37:20'),
(5076, 'el', 'smd_prognostics_pnl_ack', 'smd_prognostics', 'site', 'Alarms', '2019-07-23 23:37:20'),
(5077, 'el', 'smd_prognostics_pnl_advice', 'smd_prognostics', 'site', 'Advice', '2019-07-23 23:37:20'),
(5078, 'el', 'smd_prognostics_pnl_files', 'smd_prognostics', 'site', 'Files', '2019-07-23 23:37:20'),
(5079, 'el', 'smd_prognostics_pnl_setup', 'smd_prognostics', 'site', 'Setup', '2019-07-23 23:37:20'),
(5080, 'el', 'smd_prognostics_postamble', 'smd_prognostics', 'site', 'Acknowledge alarms', '2019-07-23 23:37:20'),
(5081, 'el', 'smd_prognostics_preamble_added', 'smd_prognostics', 'site', 'These files have been added:', '2019-07-23 23:37:20'),
(5082, 'el', 'smd_prognostics_preamble_files', 'smd_prognostics', 'site', 'File contents follows:', '2019-07-23 23:37:20'),
(5083, 'el', 'smd_prognostics_preamble_hashdown', 'smd_prognostics', 'site', 'WARNING! The checksums file is missing.', '2019-07-23 23:37:20'),
(5084, 'el', 'smd_prognostics_preamble_hashmod', 'smd_prognostics', 'site', 'WARNING! The checksums file has been altered:', '2019-07-23 23:37:20'),
(5085, 'el', 'smd_prognostics_preamble_miss', 'smd_prognostics', 'site', 'These files are missing:', '2019-07-23 23:37:20'),
(5086, 'el', 'smd_prognostics_preamble_nok', 'smd_prognostics', 'site', 'These files differ from their expected content:', '2019-07-23 23:37:20'),
(5087, 'el', 'smd_prognostics_preamble_sql_inject', 'smd_prognostics', 'site', 'Possible SQL injection detected', '2019-07-23 23:37:20'),
(5088, 'el', 'smd_prognostics_preamble_txplog', 'smd_prognostics', 'site', 'Log entries prior to this detection:', '2019-07-23 23:37:20'),
(5089, 'el', 'smd_prognostics_progloc', 'smd_prognostics', 'site', 'Prognostics folder:', '2019-07-23 23:37:20'),
(5090, 'el', 'smd_prognostics_progpfx', 'smd_prognostics', 'site', 'Unique prefix:', '2019-07-23 23:37:20'),
(5091, 'el', 'smd_prognostics_req_headers', 'smd_prognostics', 'site', 'Block request headers:', '2019-07-23 23:37:20'),
(5092, 'el', 'smd_prognostics_req_not_allowed', 'smd_prognostics', 'site', 'REQUEST_METHOD not allowed: {req}', '2019-07-23 23:37:20'),
(5093, 'el', 'smd_prognostics_rh_del', 'smd_prognostics', 'site', 'DELETE', '2019-07-23 23:37:20'),
(5094, 'el', 'smd_prognostics_rh_put', 'smd_prognostics', 'site', 'PUT', '2019-07-23 23:37:20'),
(5095, 'el', 'smd_prognostics_rh_trace', 'smd_prognostics', 'site', 'TRACE', '2019-07-23 23:37:20'),
(5096, 'el', 'smd_prognostics_rpc_exists', 'smd_prognostics', 'site', 'The rpc directory is not being used. Consider removing it.', '2019-07-23 23:37:20'),
(5097, 'el', 'smd_prognostics_rt_hdr', 'smd_prognostics', 'site', 'Header violations', '2019-07-23 23:37:20'),
(5098, 'el', 'smd_prognostics_rt_sql', 'smd_prognostics', 'site', 'SQL injections', '2019-07-23 23:37:20'),
(5099, 'el', 'smd_prognostics_seconds', 'smd_prognostics', 'site', '(seconds)', '2019-07-23 23:37:20'),
(5100, 'el', 'smd_prognostics_send_forensics', 'smd_prognostics', 'site', 'Send forensics for:', '2019-07-23 23:37:20'),
(5101, 'el', 'smd_prognostics_sensitivity', 'smd_prognostics', 'site', 'Sensitivity threshold:', '2019-07-23 23:37:20'),
(5102, 'el', 'smd_prognostics_sensitivity_explain', 'smd_prognostics', 'site', '(1=most sensitive)', '2019-07-23 23:37:20'),
(5103, 'el', 'smd_prognostics_setup_exists', 'smd_prognostics', 'site', 'The setup directory still exists. Please delete it.', '2019-07-23 23:37:20'),
(5104, 'el', 'smd_prognostics_show_grants', 'smd_prognostics', 'site', 'Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.', '2019-07-23 23:37:20'),
(5105, 'el', 'smd_prognostics_sql_file_privs', 'smd_prognostics', 'site', 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.', '2019-07-23 23:37:20'),
(5106, 'el', 'smd_prognostics_sql_inject', 'smd_prognostics', 'site', 'Protect against SQL injection:', '2019-07-23 23:37:20'),
(5107, 'el', 'smd_prognostics_stat_gid', 'smd_prognostics', 'site', 'Group ID:', '2019-07-23 23:37:20'),
(5108, 'el', 'smd_prognostics_stat_mod', 'smd_prognostics', 'site', 'Modified:', '2019-07-23 23:37:20'),
(5109, 'el', 'smd_prognostics_stat_name', 'smd_prognostics', 'site', 'Filename:', '2019-07-23 23:37:20'),
(5110, 'el', 'smd_prognostics_stat_size', 'smd_prognostics', 'site', 'Size:', '2019-07-23 23:37:20'),
(5111, 'el', 'smd_prognostics_stat_uid', 'smd_prognostics', 'site', 'User ID:', '2019-07-23 23:37:20'),
(5112, 'el', 'smd_prognostics_subject', 'smd_prognostics', 'site', 'Prognostics ({site})', '2019-07-23 23:37:20'),
(5113, 'el', 'smd_prognostics_subject_csi', 'smd_prognostics', 'site', 'Prognostics ({site}) forensics', '2019-07-23 23:37:20'),
(5114, 'el', 'smd_prognostics_tight', 'smd_prognostics', 'site', 'You run a pretty tight ship. Stay frosty.', '2019-07-23 23:37:20'),
(5115, 'el', 'smd_prognostics_ttl_ack', 'smd_prognostics', 'site', 'Prognostic alarm acknowledgement', '2019-07-23 23:37:20'),
(5116, 'el', 'smd_prognostics_ttl_advice', 'smd_prognostics', 'site', 'Prognostic advice', '2019-07-23 23:37:20'),
(5117, 'el', 'smd_prognostics_ttl_fileopts', 'smd_prognostics', 'site', 'Filesystem options', '2019-07-23 23:37:20'),
(5118, 'el', 'smd_prognostics_ttl_files', 'smd_prognostics', 'site', 'Prognostic file monitoring', '2019-07-23 23:37:20'),
(5119, 'el', 'smd_prognostics_ttl_monopts', 'smd_prognostics', 'site', 'Monitoring options', '2019-07-23 23:37:20'),
(5120, 'el', 'smd_prognostics_ttl_plugopts', 'smd_prognostics', 'site', 'Plugin options', '2019-07-23 23:37:20'),
(5121, 'el', 'smd_prognostics_ttl_realopts', 'smd_prognostics', 'site', 'Realtime options', '2019-07-23 23:37:20'),
(5122, 'el', 'smd_prognostics_ttl_setup', 'smd_prognostics', 'site', 'Prognostic setup', '2019-07-23 23:37:20'),
(5123, 'el', 'smd_prognostics_txpdir', 'smd_prognostics', 'site', 'Admin-side URL:', '2019-07-23 23:37:20'),
(5124, 'el', 'smd_prognostics_warn_loc', 'smd_prognostics', 'site', 'File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now', '2019-07-23 23:37:20'),
(5125, 'el', 'smd_prognostics_with', 'smd_prognostics', 'site', 'with:', '2019-07-23 23:37:20'),
(5126, 'el', 'smd_prognostics_xss', 'smd_prognostics', 'site', 'XSS shield', '2019-07-23 23:37:20'),
(5127, 'el', 'smd_prognostics_xss_explain', 'smd_prognostics', 'site', '(may break Textiled comments)', '2019-07-23 23:37:20'),
(5128, 'en', 'smd_prognostics', 'smd_prognostics', 'site', 'Prognostics', '2019-07-23 23:37:20'),
(5129, 'en', 'smd_prognostics_acked', 'smd_prognostics', 'site', 'Alarms acknowledged.', '2019-07-23 23:37:20'),
(5130, 'en', 'smd_prognostics_alarm_freq', 'smd_prognostics', 'site', 'Alarm on detection and every:', '2019-07-23 23:37:20'),
(5131, 'en', 'smd_prognostics_and', 'smd_prognostics', 'site', 'and', '2019-07-23 23:37:20'),
(5132, 'en', 'smd_prognostics_auth_users', 'smd_prognostics', 'site', 'Restrict prognostic config to:', '2019-07-23 23:37:20'),
(5133, 'en', 'smd_prognostics_block', 'smd_prognostics', 'site', 'Block', '2019-07-23 23:37:20'),
(5134, 'en', 'smd_prognostics_btn_ackit', 'smd_prognostics', 'site', 'Acknowledge', '2019-07-23 23:37:20'),
(5135, 'en', 'smd_prognostics_btn_csi', 'smd_prognostics', 'site', 'Send Forensics', '2019-07-23 23:37:20'),
(5136, 'en', 'smd_prognostics_btn_ignore', 'smd_prognostics', 'site', 'Ignore', '2019-07-23 23:37:20'),
(5137, 'en', 'smd_prognostics_check_between', 'smd_prognostics', 'site', 'Check files between:', '2019-07-23 23:37:20'),
(5138, 'en', 'smd_prognostics_check_for', 'smd_prognostics', 'site', 'Check files for:', '2019-07-23 23:37:20'),
(5139, 'en', 'smd_prognostics_check_freq', 'smd_prognostics', 'site', 'Check files (at most) every:', '2019-07-23 23:37:20'),
(5140, 'en', 'smd_prognostics_check_qty', 'smd_prognostics', 'site', 'Check this many files each time:', '2019-07-23 23:37:20'),
(5141, 'en', 'smd_prognostics_check_where', 'smd_prognostics', 'site', 'Check files on public side clicks:', '2019-07-23 23:37:20'),
(5142, 'en', 'smd_prognostics_ch_add', 'smd_prognostics', 'site', 'Additions', '2019-07-23 23:37:20'),
(5143, 'en', 'smd_prognostics_ch_del', 'smd_prognostics', 'site', 'Deletions', '2019-07-23 23:37:20'),
(5144, 'en', 'smd_prognostics_ch_mod', 'smd_prognostics', 'site', 'Modifications', '2019-07-23 23:37:20'),
(5145, 'en', 'smd_prognostics_csense', 'smd_prognostics', 'site', '(case-sensitive)', '2019-07-23 23:37:20'),
(5146, 'en', 'smd_prognostics_csi_sent', 'smd_prognostics', 'site', 'Forensics data sent', '2019-07-23 23:37:20'),
(5147, 'en', 'smd_prognostics_csv', 'smd_prognostics', 'site', '(comma-separated)', '2019-07-23 23:37:20'),
(5148, 'en', 'smd_prognostics_currmon', 'smd_prognostics', 'site', 'You are currently monitoring {curr} out of {outof} available files.', '2019-07-23 23:37:20'),
(5149, 'en', 'smd_prognostics_docroot_files', 'smd_prognostics', 'site', 'Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.', '2019-07-23 23:37:20'),
(5150, 'en', 'smd_prognostics_docroot_prognostics', 'smd_prognostics', 'site', 'Please change the \"Prognostics folder\" setting to a directory outside your site root folder.', '2019-07-23 23:37:20'),
(5151, 'en', 'smd_prognostics_docroot_tmp', 'smd_prognostics', 'site', 'Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.', '2019-07-23 23:37:20'),
(5152, 'en', 'smd_prognostics_excludir', 'smd_prognostics', 'site', 'Exclude folders:', '2019-07-23 23:37:20'),
(5153, 'en', 'smd_prognostics_files_updated', 'smd_prognostics', 'site', 'File list updated', '2019-07-23 23:37:20'),
(5154, 'en', 'smd_prognostics_help_link', 'smd_prognostics', 'site', '(<a href=\"{link}\">Help</a>)', '2019-07-23 23:37:20'),
(5155, 'en', 'smd_prognostics_hms', 'smd_prognostics', 'site', '(hrs:mins:secs)', '2019-07-23 23:37:20'),
(5156, 'en', 'smd_prognostics_ignores', 'smd_prognostics', 'site', 'Ignore files:', '2019-07-23 23:37:20'),
(5157, 'en', 'smd_prognostics_lbl_added', 'smd_prognostics', 'site', 'Added (not monitored)', '2019-07-23 23:37:20'),
(5158, 'en', 'smd_prognostics_lbl_changed', 'smd_prognostics', 'site', 'Changed', '2019-07-23 23:37:20'),
(5159, 'en', 'smd_prognostics_lbl_hashmod', 'smd_prognostics', 'site', 'COMPROMISED', '2019-07-23 23:37:20'),
(5160, 'en', 'smd_prognostics_lbl_missing', 'smd_prognostics', 'site', 'Missing', '2019-07-23 23:37:20'),
(5161, 'en', 'smd_prognostics_listloc', 'smd_prognostics', 'site', 'File locations:', '2019-07-23 23:37:20'),
(5162, 'en', 'smd_prognostics_mailto', 'smd_prognostics', 'site', 'Send e-mail to:', '2019-07-23 23:37:20'),
(5163, 'en', 'smd_prognostics_mailto_csi', 'smd_prognostics', 'site', 'Send forensics to:', '2019-07-23 23:37:20'),
(5164, 'en', 'smd_prognostics_monfiles_explain', 'smd_prognostics', 'site', 'Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.', '2019-07-23 23:37:20'),
(5165, 'en', 'smd_prognostics_none_selected', 'smd_prognostics', 'site', 'No files selected', '2019-07-23 23:37:20'),
(5166, 'en', 'smd_prognostics_notify_email', 'smd_prognostics', 'site', 'E-mail', '2019-07-23 23:37:20'),
(5167, 'en', 'smd_prognostics_notify_txp', 'smd_prognostics', 'site', 'Txp interface', '2019-07-23 23:37:20'),
(5168, 'en', 'smd_prognostics_notify_via', 'smd_prognostics', 'site', 'Notify via:', '2019-07-23 23:37:20'),
(5169, 'en', 'smd_prognostics_not_writable', 'smd_prognostics', 'site', '{location} is not a directory or is not writable', '2019-07-23 23:37:20'),
(5170, 'en', 'smd_prognostics_no_alarms', 'smd_prognostics', 'site', 'No alarms to acknowledge at present.', '2019-07-23 23:37:20'),
(5171, 'en', 'smd_prognostics_no_files', 'smd_prognostics', 'site', 'No files to list. Check the plugin setup (and Save it).', '2019-07-23 23:37:20'),
(5172, 'en', 'smd_prognostics_no_log_entries', 'smd_prognostics', 'site', 'No log activity around the time of the differences.', '2019-07-23 23:37:20'),
(5173, 'en', 'smd_prognostics_pnl_ack', 'smd_prognostics', 'site', 'Alarms', '2019-07-23 23:37:20'),
(5174, 'en', 'smd_prognostics_pnl_advice', 'smd_prognostics', 'site', 'Advice', '2019-07-23 23:37:20'),
(5175, 'en', 'smd_prognostics_pnl_files', 'smd_prognostics', 'site', 'Files', '2019-07-23 23:37:20'),
(5176, 'en', 'smd_prognostics_pnl_setup', 'smd_prognostics', 'site', 'Setup', '2019-07-23 23:37:20'),
(5177, 'en', 'smd_prognostics_postamble', 'smd_prognostics', 'site', 'Acknowledge alarms', '2019-07-23 23:37:20'),
(5178, 'en', 'smd_prognostics_preamble_added', 'smd_prognostics', 'site', 'These files have been added:', '2019-07-23 23:37:20'),
(5179, 'en', 'smd_prognostics_preamble_files', 'smd_prognostics', 'site', 'File contents follows:', '2019-07-23 23:37:20'),
(5180, 'en', 'smd_prognostics_preamble_hashdown', 'smd_prognostics', 'site', 'WARNING! The checksums file is missing.', '2019-07-23 23:37:20'),
(5181, 'en', 'smd_prognostics_preamble_hashmod', 'smd_prognostics', 'site', 'WARNING! The checksums file has been altered:', '2019-07-23 23:37:20'),
(5182, 'en', 'smd_prognostics_preamble_miss', 'smd_prognostics', 'site', 'These files are missing:', '2019-07-23 23:37:20'),
(5183, 'en', 'smd_prognostics_preamble_nok', 'smd_prognostics', 'site', 'These files differ from their expected content:', '2019-07-23 23:37:20'),
(5184, 'en', 'smd_prognostics_preamble_sql_inject', 'smd_prognostics', 'site', 'Possible SQL injection detected', '2019-07-23 23:37:20'),
(5185, 'en', 'smd_prognostics_preamble_txplog', 'smd_prognostics', 'site', 'Log entries prior to this detection:', '2019-07-23 23:37:20'),
(5186, 'en', 'smd_prognostics_progloc', 'smd_prognostics', 'site', 'Prognostics folder:', '2019-07-23 23:37:20'),
(5187, 'en', 'smd_prognostics_progpfx', 'smd_prognostics', 'site', 'Unique prefix:', '2019-07-23 23:37:20'),
(5188, 'en', 'smd_prognostics_req_headers', 'smd_prognostics', 'site', 'Block request headers:', '2019-07-23 23:37:20'),
(5189, 'en', 'smd_prognostics_req_not_allowed', 'smd_prognostics', 'site', 'REQUEST_METHOD not allowed: {req}', '2019-07-23 23:37:20'),
(5190, 'en', 'smd_prognostics_rh_del', 'smd_prognostics', 'site', 'DELETE', '2019-07-23 23:37:20'),
(5191, 'en', 'smd_prognostics_rh_put', 'smd_prognostics', 'site', 'PUT', '2019-07-23 23:37:20'),
(5192, 'en', 'smd_prognostics_rh_trace', 'smd_prognostics', 'site', 'TRACE', '2019-07-23 23:37:20'),
(5193, 'en', 'smd_prognostics_rpc_exists', 'smd_prognostics', 'site', 'The rpc directory is not being used. Consider removing it.', '2019-07-23 23:37:20'),
(5194, 'en', 'smd_prognostics_rt_hdr', 'smd_prognostics', 'site', 'Header violations', '2019-07-23 23:37:20'),
(5195, 'en', 'smd_prognostics_rt_sql', 'smd_prognostics', 'site', 'SQL injections', '2019-07-23 23:37:20'),
(5196, 'en', 'smd_prognostics_seconds', 'smd_prognostics', 'site', '(seconds)', '2019-07-23 23:37:20'),
(5197, 'en', 'smd_prognostics_send_forensics', 'smd_prognostics', 'site', 'Send forensics for:', '2019-07-23 23:37:20'),
(5198, 'en', 'smd_prognostics_sensitivity', 'smd_prognostics', 'site', 'Sensitivity threshold:', '2019-07-23 23:37:20'),
(5199, 'en', 'smd_prognostics_sensitivity_explain', 'smd_prognostics', 'site', '(1=most sensitive)', '2019-07-23 23:37:20'),
(5200, 'en', 'smd_prognostics_setup_exists', 'smd_prognostics', 'site', 'The setup directory still exists. Please delete it.', '2019-07-23 23:37:20'),
(5201, 'en', 'smd_prognostics_show_grants', 'smd_prognostics', 'site', 'Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.', '2019-07-23 23:37:20'),
(5202, 'en', 'smd_prognostics_sql_file_privs', 'smd_prognostics', 'site', 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.', '2019-07-23 23:37:20'),
(5203, 'en', 'smd_prognostics_sql_inject', 'smd_prognostics', 'site', 'Protect against SQL injection:', '2019-07-23 23:37:20'),
(5204, 'en', 'smd_prognostics_stat_gid', 'smd_prognostics', 'site', 'Group ID:', '2019-07-23 23:37:20'),
(5205, 'en', 'smd_prognostics_stat_mod', 'smd_prognostics', 'site', 'Modified:', '2019-07-23 23:37:20'),
(5206, 'en', 'smd_prognostics_stat_name', 'smd_prognostics', 'site', 'Filename:', '2019-07-23 23:37:20'),
(5207, 'en', 'smd_prognostics_stat_size', 'smd_prognostics', 'site', 'Size:', '2019-07-23 23:37:20'),
(5208, 'en', 'smd_prognostics_stat_uid', 'smd_prognostics', 'site', 'User ID:', '2019-07-23 23:37:20'),
(5209, 'en', 'smd_prognostics_subject', 'smd_prognostics', 'site', 'Prognostics ({site})', '2019-07-23 23:37:20'),
(5210, 'en', 'smd_prognostics_subject_csi', 'smd_prognostics', 'site', 'Prognostics ({site}) forensics', '2019-07-23 23:37:20'),
(5211, 'en', 'smd_prognostics_tight', 'smd_prognostics', 'site', 'You run a pretty tight ship. Stay frosty.', '2019-07-23 23:37:20'),
(5212, 'en', 'smd_prognostics_ttl_ack', 'smd_prognostics', 'site', 'Prognostic alarm acknowledgement', '2019-07-23 23:37:20'),
(5213, 'en', 'smd_prognostics_ttl_advice', 'smd_prognostics', 'site', 'Prognostic advice', '2019-07-23 23:37:20'),
(5214, 'en', 'smd_prognostics_ttl_fileopts', 'smd_prognostics', 'site', 'Filesystem options', '2019-07-23 23:37:20'),
(5215, 'en', 'smd_prognostics_ttl_files', 'smd_prognostics', 'site', 'Prognostic file monitoring', '2019-07-23 23:37:20'),
(5216, 'en', 'smd_prognostics_ttl_monopts', 'smd_prognostics', 'site', 'Monitoring options', '2019-07-23 23:37:20'),
(5217, 'en', 'smd_prognostics_ttl_plugopts', 'smd_prognostics', 'site', 'Plugin options', '2019-07-23 23:37:20'),
(5218, 'en', 'smd_prognostics_ttl_realopts', 'smd_prognostics', 'site', 'Realtime options', '2019-07-23 23:37:20'),
(5219, 'en', 'smd_prognostics_ttl_setup', 'smd_prognostics', 'site', 'Prognostic setup', '2019-07-23 23:37:20'),
(5220, 'en', 'smd_prognostics_txpdir', 'smd_prognostics', 'site', 'Admin-side URL:', '2019-07-23 23:37:20'),
(5221, 'en', 'smd_prognostics_warn_loc', 'smd_prognostics', 'site', 'File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now', '2019-07-23 23:37:20'),
(5222, 'en', 'smd_prognostics_with', 'smd_prognostics', 'site', 'with:', '2019-07-23 23:37:20'),
(5223, 'en', 'smd_prognostics_xss', 'smd_prognostics', 'site', 'XSS shield', '2019-07-23 23:37:20'),
(5224, 'en', 'smd_prognostics_xss_explain', 'smd_prognostics', 'site', '(may break Textiled comments)', '2019-07-23 23:37:20'),
(5225, 'de', 'smd_thumb_actions', 'smd_thumb', 'site', 'Actions', '2019-07-23 23:46:12'),
(5226, 'de', 'smd_thumb_all_sizes', 'smd_thumb', 'site', 'Toutes les tailles', '2019-07-23 23:46:12'),
(5227, 'de', 'smd_thumb_all_thumbs', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5228, 'de', 'smd_thumb_batch_legend', 'smd_thumb', 'site', 'Créer/Recréer des vignettes', '2019-07-23 23:46:12'),
(5229, 'de', 'smd_thumb_batch_preamble', 'smd_thumb', 'site', 'Pour les profils actifs :', '2019-07-23 23:46:12'),
(5230, 'de', 'smd_thumb_btn_pnl', 'smd_thumb', 'site', 'Profils', '2019-07-23 23:46:12'),
(5231, 'de', 'smd_thumb_btn_tools', 'smd_thumb', 'site', 'Outils', '2019-07-23 23:46:12'),
(5232, 'de', 'smd_thumb_btn_tools_prefs', 'smd_thumb', 'site', 'Outils/Préf.', '2019-07-23 23:46:12'),
(5233, 'de', 'smd_thumb_byAll', 'smd_thumb', 'site', 'Tous', '2019-07-23 23:46:12'),
(5234, 'de', 'smd_thumb_byCat', 'smd_thumb', 'site', 'Par catégorie', '2019-07-23 23:46:12'),
(5235, 'de', 'smd_thumb_bySel', 'smd_thumb', 'site', 'Sélectionnées', '2019-07-23 23:46:12'),
(5236, 'de', 'smd_thumb_byUsr', 'smd_thumb', 'site', 'Par auteur', '2019-07-23 23:46:12'),
(5237, 'de', 'smd_thumb_create', 'smd_thumb', 'site', 'Création', '2019-07-23 23:46:12'),
(5238, 'de', 'smd_thumb_create_group_confirm', 'smd_thumb', 'site', 'Créer les vignettes pour TOUS les profils existant ? Les précédentes vignettes seront écrasées.', '2019-07-23 23:46:12'),
(5239, 'de', 'smd_thumb_delete', 'smd_thumb', 'site', 'Suppression', '2019-07-23 23:46:12'),
(5240, 'de', 'smd_thumb_delete_confirm', 'smd_thumb', 'site', 'Voulez-vous vraiment supprimer le profil {name} ? Les vignettes de ce type seront TOUTES supprimées.', '2019-07-23 23:46:12'),
(5241, 'de', 'smd_thumb_image', 'smd_thumb', 'site', 'Image=', '2019-07-23 23:46:12'),
(5242, 'de', 'smd_thumb_new', 'smd_thumb', 'site', 'Nouveau profil', '2019-07-23 23:46:12'),
(5243, 'de', 'smd_thumb_prefs', 'smd_thumb', 'site', 'Préférences', '2019-07-23 23:46:12'),
(5244, 'de', 'smd_thumb_profile', 'smd_thumb', 'site', 'Profil=', '2019-07-23 23:46:12'),
(5245, 'de', 'smd_thumb_profile_deleted', 'smd_thumb', 'site', 'Le profil \"{name}\" a été supprimé.', '2019-07-23 23:46:12'),
(5246, 'de', 'smd_thumb_profile_exists', 'smd_thumb', 'site', 'Le profil \"{name}\" existe déjà.', '2019-07-23 23:46:12'),
(5247, 'de', 'smd_thumb_profile_heading', 'smd_thumb', 'site', 'Profils de vignettes', '2019-07-23 23:46:12'),
(5248, 'de', 'smd_thumb_profile_preftool_heading', 'smd_thumb', 'site', 'Outils et préférences du vignettage', '2019-07-23 23:46:12'),
(5249, 'de', 'smd_thumb_profile_tool_heading', 'smd_thumb', 'site', 'Outils de vignettage', '2019-07-23 23:46:12'),
(5250, 'de', 'smd_thumb_quality', 'smd_thumb', 'site', 'Qualité (%)', '2019-07-23 23:46:12'),
(5251, 'de', 'smd_thumb_sharpen', 'smd_thumb', 'site', 'Rendre net', '2019-07-23 23:46:12'),
(5252, 'de', 'smd_thumb_tables_not_installed', 'smd_thumb', 'site', 'Tables non installées : essayez de réinstaller le plugin.', '2019-07-23 23:46:12'),
(5253, 'de', 'smd_thumb_thumbnail_heading', 'smd_thumb', 'site', 'Vignettes', '2019-07-23 23:46:12'),
(5254, 'de', 'smd_thumb_txp_auto_replace', 'smd_thumb', 'site', 'Recréer les vignettes au téléchargement des images :', '2019-07-23 23:46:12'),
(5255, 'de', 'smd_thumb_txp_create', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5256, 'de', 'smd_thumb_txp_create_from', 'smd_thumb', 'site', 'Créer les vignettes à partir de :', '2019-07-23 23:46:12'),
(5257, 'de', 'smd_thumb_txp_create_from_full', 'smd_thumb', 'site', 'l\'image originale', '2019-07-23 23:46:12'),
(5258, 'de', 'smd_thumb_txp_create_from_thumb', 'smd_thumb', 'site', 'la vignette', '2019-07-23 23:46:12'),
(5259, 'de', 'smd_thumb_txp_delete', 'smd_thumb', 'site', 'Supprimer', '2019-07-23 23:46:12'),
(5260, 'de', 'smd_thumb_upload', 'smd_thumb', 'site', 'Remplacer les vignettes sélectionnées', '2019-07-23 23:46:12'),
(5261, 'en-us', 'smd_thumb_actions', 'smd_thumb', 'site', 'Actions', '2019-07-23 23:46:12'),
(5262, 'en-us', 'smd_thumb_all_sizes', 'smd_thumb', 'site', 'Toutes les tailles', '2019-07-23 23:46:12'),
(5263, 'en-us', 'smd_thumb_all_thumbs', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5264, 'en-us', 'smd_thumb_batch_legend', 'smd_thumb', 'site', 'Créer/Recréer des vignettes', '2019-07-23 23:46:12'),
(5265, 'en-us', 'smd_thumb_batch_preamble', 'smd_thumb', 'site', 'Pour les profils actifs :', '2019-07-23 23:46:12'),
(5266, 'en-us', 'smd_thumb_btn_pnl', 'smd_thumb', 'site', 'Profils', '2019-07-23 23:46:12'),
(5267, 'en-us', 'smd_thumb_btn_tools', 'smd_thumb', 'site', 'Outils', '2019-07-23 23:46:12'),
(5268, 'en-us', 'smd_thumb_btn_tools_prefs', 'smd_thumb', 'site', 'Outils/Préf.', '2019-07-23 23:46:12'),
(5269, 'en-us', 'smd_thumb_byAll', 'smd_thumb', 'site', 'Tous', '2019-07-23 23:46:12'),
(5270, 'en-us', 'smd_thumb_byCat', 'smd_thumb', 'site', 'Par catégorie', '2019-07-23 23:46:12'),
(5271, 'en-us', 'smd_thumb_bySel', 'smd_thumb', 'site', 'Sélectionnées', '2019-07-23 23:46:12'),
(5272, 'en-us', 'smd_thumb_byUsr', 'smd_thumb', 'site', 'Par auteur', '2019-07-23 23:46:12'),
(5273, 'en-us', 'smd_thumb_create', 'smd_thumb', 'site', 'Création', '2019-07-23 23:46:12'),
(5274, 'en-us', 'smd_thumb_create_group_confirm', 'smd_thumb', 'site', 'Créer les vignettes pour TOUS les profils existant ? Les précédentes vignettes seront écrasées.', '2019-07-23 23:46:12'),
(5275, 'en-us', 'smd_thumb_delete', 'smd_thumb', 'site', 'Suppression', '2019-07-23 23:46:12'),
(5276, 'en-us', 'smd_thumb_delete_confirm', 'smd_thumb', 'site', 'Voulez-vous vraiment supprimer le profil {name} ? Les vignettes de ce type seront TOUTES supprimées.', '2019-07-23 23:46:12'),
(5277, 'en-us', 'smd_thumb_image', 'smd_thumb', 'site', 'Image=', '2019-07-23 23:46:12'),
(5278, 'en-us', 'smd_thumb_new', 'smd_thumb', 'site', 'Nouveau profil', '2019-07-23 23:46:12'),
(5279, 'en-us', 'smd_thumb_prefs', 'smd_thumb', 'site', 'Préférences', '2019-07-23 23:46:12'),
(5280, 'en-us', 'smd_thumb_profile', 'smd_thumb', 'site', 'Profil=', '2019-07-23 23:46:12'),
(5281, 'en-us', 'smd_thumb_profile_deleted', 'smd_thumb', 'site', 'Le profil \"{name}\" a été supprimé.', '2019-07-23 23:46:12'),
(5282, 'en-us', 'smd_thumb_profile_exists', 'smd_thumb', 'site', 'Le profil \"{name}\" existe déjà.', '2019-07-23 23:46:12'),
(5283, 'en-us', 'smd_thumb_profile_heading', 'smd_thumb', 'site', 'Profils de vignettes', '2019-07-23 23:46:12'),
(5284, 'en-us', 'smd_thumb_profile_preftool_heading', 'smd_thumb', 'site', 'Outils et préférences du vignettage', '2019-07-23 23:46:12'),
(5285, 'en-us', 'smd_thumb_profile_tool_heading', 'smd_thumb', 'site', 'Outils de vignettage', '2019-07-23 23:46:12'),
(5286, 'en-us', 'smd_thumb_quality', 'smd_thumb', 'site', 'Qualité (%)', '2019-07-23 23:46:12'),
(5287, 'en-us', 'smd_thumb_sharpen', 'smd_thumb', 'site', 'Rendre net', '2019-07-23 23:46:12'),
(5288, 'en-us', 'smd_thumb_tables_not_installed', 'smd_thumb', 'site', 'Tables non installées : essayez de réinstaller le plugin.', '2019-07-23 23:46:12'),
(5289, 'en-us', 'smd_thumb_thumbnail_heading', 'smd_thumb', 'site', 'Vignettes', '2019-07-23 23:46:12'),
(5290, 'en-us', 'smd_thumb_txp_auto_replace', 'smd_thumb', 'site', 'Recréer les vignettes au téléchargement des images :', '2019-07-23 23:46:12'),
(5291, 'en-us', 'smd_thumb_txp_create', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5292, 'en-us', 'smd_thumb_txp_create_from', 'smd_thumb', 'site', 'Créer les vignettes à partir de :', '2019-07-23 23:46:12'),
(5293, 'en-us', 'smd_thumb_txp_create_from_full', 'smd_thumb', 'site', 'l\'image originale', '2019-07-23 23:46:12'),
(5294, 'en-us', 'smd_thumb_txp_create_from_thumb', 'smd_thumb', 'site', 'la vignette', '2019-07-23 23:46:12'),
(5295, 'en-us', 'smd_thumb_txp_delete', 'smd_thumb', 'site', 'Supprimer', '2019-07-23 23:46:12'),
(5296, 'en-us', 'smd_thumb_upload', 'smd_thumb', 'site', 'Remplacer les vignettes sélectionnées', '2019-07-23 23:46:12'),
(5297, 'el', 'smd_thumb_actions', 'smd_thumb', 'site', 'Actions', '2019-07-23 23:46:12'),
(5298, 'el', 'smd_thumb_all_sizes', 'smd_thumb', 'site', 'Toutes les tailles', '2019-07-23 23:46:12'),
(5299, 'el', 'smd_thumb_all_thumbs', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5300, 'el', 'smd_thumb_batch_legend', 'smd_thumb', 'site', 'Créer/Recréer des vignettes', '2019-07-23 23:46:12'),
(5301, 'el', 'smd_thumb_batch_preamble', 'smd_thumb', 'site', 'Pour les profils actifs :', '2019-07-23 23:46:12'),
(5302, 'el', 'smd_thumb_btn_pnl', 'smd_thumb', 'site', 'Profils', '2019-07-23 23:46:12'),
(5303, 'el', 'smd_thumb_btn_tools', 'smd_thumb', 'site', 'Outils', '2019-07-23 23:46:12'),
(5304, 'el', 'smd_thumb_btn_tools_prefs', 'smd_thumb', 'site', 'Outils/Préf.', '2019-07-23 23:46:12'),
(5305, 'el', 'smd_thumb_byAll', 'smd_thumb', 'site', 'Tous', '2019-07-23 23:46:12'),
(5306, 'el', 'smd_thumb_byCat', 'smd_thumb', 'site', 'Par catégorie', '2019-07-23 23:46:12'),
(5307, 'el', 'smd_thumb_bySel', 'smd_thumb', 'site', 'Sélectionnées', '2019-07-23 23:46:12'),
(5308, 'el', 'smd_thumb_byUsr', 'smd_thumb', 'site', 'Par auteur', '2019-07-23 23:46:12'),
(5309, 'el', 'smd_thumb_create', 'smd_thumb', 'site', 'Création', '2019-07-23 23:46:12'),
(5310, 'el', 'smd_thumb_create_group_confirm', 'smd_thumb', 'site', 'Créer les vignettes pour TOUS les profils existant ? Les précédentes vignettes seront écrasées.', '2019-07-23 23:46:12'),
(5311, 'el', 'smd_thumb_delete', 'smd_thumb', 'site', 'Suppression', '2019-07-23 23:46:12'),
(5312, 'el', 'smd_thumb_delete_confirm', 'smd_thumb', 'site', 'Voulez-vous vraiment supprimer le profil {name} ? Les vignettes de ce type seront TOUTES supprimées.', '2019-07-23 23:46:12'),
(5313, 'el', 'smd_thumb_image', 'smd_thumb', 'site', 'Image=', '2019-07-23 23:46:12'),
(5314, 'el', 'smd_thumb_new', 'smd_thumb', 'site', 'Nouveau profil', '2019-07-23 23:46:12'),
(5315, 'el', 'smd_thumb_prefs', 'smd_thumb', 'site', 'Préférences', '2019-07-23 23:46:12'),
(5316, 'el', 'smd_thumb_profile', 'smd_thumb', 'site', 'Profil=', '2019-07-23 23:46:12'),
(5317, 'el', 'smd_thumb_profile_deleted', 'smd_thumb', 'site', 'Le profil \"{name}\" a été supprimé.', '2019-07-23 23:46:12'),
(5318, 'el', 'smd_thumb_profile_exists', 'smd_thumb', 'site', 'Le profil \"{name}\" existe déjà.', '2019-07-23 23:46:12'),
(5319, 'el', 'smd_thumb_profile_heading', 'smd_thumb', 'site', 'Profils de vignettes', '2019-07-23 23:46:12'),
(5320, 'el', 'smd_thumb_profile_preftool_heading', 'smd_thumb', 'site', 'Outils et préférences du vignettage', '2019-07-23 23:46:12'),
(5321, 'el', 'smd_thumb_profile_tool_heading', 'smd_thumb', 'site', 'Outils de vignettage', '2019-07-23 23:46:12'),
(5322, 'el', 'smd_thumb_quality', 'smd_thumb', 'site', 'Qualité (%)', '2019-07-23 23:46:12'),
(5323, 'el', 'smd_thumb_sharpen', 'smd_thumb', 'site', 'Rendre net', '2019-07-23 23:46:12'),
(5324, 'el', 'smd_thumb_tables_not_installed', 'smd_thumb', 'site', 'Tables non installées : essayez de réinstaller le plugin.', '2019-07-23 23:46:12'),
(5325, 'el', 'smd_thumb_thumbnail_heading', 'smd_thumb', 'site', 'Vignettes', '2019-07-23 23:46:12'),
(5326, 'el', 'smd_thumb_txp_auto_replace', 'smd_thumb', 'site', 'Recréer les vignettes au téléchargement des images :', '2019-07-23 23:46:12'),
(5327, 'el', 'smd_thumb_txp_create', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5328, 'el', 'smd_thumb_txp_create_from', 'smd_thumb', 'site', 'Créer les vignettes à partir de :', '2019-07-23 23:46:12'),
(5329, 'el', 'smd_thumb_txp_create_from_full', 'smd_thumb', 'site', 'l\'image originale', '2019-07-23 23:46:12'),
(5330, 'el', 'smd_thumb_txp_create_from_thumb', 'smd_thumb', 'site', 'la vignette', '2019-07-23 23:46:12'),
(5331, 'el', 'smd_thumb_txp_delete', 'smd_thumb', 'site', 'Supprimer', '2019-07-23 23:46:12'),
(5332, 'el', 'smd_thumb_upload', 'smd_thumb', 'site', 'Remplacer les vignettes sélectionnées', '2019-07-23 23:46:12'),
(5333, 'en', 'smd_thumb_actions', 'smd_thumb', 'site', 'Actions', '2019-07-23 23:46:12'),
(5334, 'en', 'smd_thumb_all_sizes', 'smd_thumb', 'site', 'Toutes les tailles', '2019-07-23 23:46:12'),
(5335, 'en', 'smd_thumb_all_thumbs', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5336, 'en', 'smd_thumb_batch_legend', 'smd_thumb', 'site', 'Créer/Recréer des vignettes', '2019-07-23 23:46:12'),
(5337, 'en', 'smd_thumb_batch_preamble', 'smd_thumb', 'site', 'Pour les profils actifs :', '2019-07-23 23:46:12'),
(5338, 'en', 'smd_thumb_btn_pnl', 'smd_thumb', 'site', 'Profils', '2019-07-23 23:46:12'),
(5339, 'en', 'smd_thumb_btn_tools', 'smd_thumb', 'site', 'Outils', '2019-07-23 23:46:12'),
(5340, 'en', 'smd_thumb_btn_tools_prefs', 'smd_thumb', 'site', 'Outils/Préf.', '2019-07-23 23:46:12'),
(5341, 'en', 'smd_thumb_byAll', 'smd_thumb', 'site', 'Tous', '2019-07-23 23:46:12'),
(5342, 'en', 'smd_thumb_byCat', 'smd_thumb', 'site', 'Par catégorie', '2019-07-23 23:46:12'),
(5343, 'en', 'smd_thumb_bySel', 'smd_thumb', 'site', 'Sélectionnées', '2019-07-23 23:46:12'),
(5344, 'en', 'smd_thumb_byUsr', 'smd_thumb', 'site', 'Par auteur', '2019-07-23 23:46:12'),
(5345, 'en', 'smd_thumb_create', 'smd_thumb', 'site', 'Création', '2019-07-23 23:46:12'),
(5346, 'en', 'smd_thumb_create_group_confirm', 'smd_thumb', 'site', 'Créer les vignettes pour TOUS les profils existant ? Les précédentes vignettes seront écrasées.', '2019-07-23 23:46:12'),
(5347, 'en', 'smd_thumb_delete', 'smd_thumb', 'site', 'Suppression', '2019-07-23 23:46:12'),
(5348, 'en', 'smd_thumb_delete_confirm', 'smd_thumb', 'site', 'Voulez-vous vraiment supprimer le profil {name} ? Les vignettes de ce type seront TOUTES supprimées.', '2019-07-23 23:46:12'),
(5349, 'en', 'smd_thumb_image', 'smd_thumb', 'site', 'Image=', '2019-07-23 23:46:12'),
(5350, 'en', 'smd_thumb_new', 'smd_thumb', 'site', 'Nouveau profil', '2019-07-23 23:46:12'),
(5351, 'en', 'smd_thumb_prefs', 'smd_thumb', 'site', 'Préférences', '2019-07-23 23:46:12'),
(5352, 'en', 'smd_thumb_profile', 'smd_thumb', 'site', 'Profil=', '2019-07-23 23:46:12'),
(5353, 'en', 'smd_thumb_profile_deleted', 'smd_thumb', 'site', 'Le profil \"{name}\" a été supprimé.', '2019-07-23 23:46:12'),
(5354, 'en', 'smd_thumb_profile_exists', 'smd_thumb', 'site', 'Le profil \"{name}\" existe déjà.', '2019-07-23 23:46:12'),
(5355, 'en', 'smd_thumb_profile_heading', 'smd_thumb', 'site', 'Profils de vignettes', '2019-07-23 23:46:12'),
(5356, 'en', 'smd_thumb_profile_preftool_heading', 'smd_thumb', 'site', 'Outils et préférences du vignettage', '2019-07-23 23:46:12'),
(5357, 'en', 'smd_thumb_profile_tool_heading', 'smd_thumb', 'site', 'Outils de vignettage', '2019-07-23 23:46:12'),
(5358, 'en', 'smd_thumb_quality', 'smd_thumb', 'site', 'Qualité (%)', '2019-07-23 23:46:12'),
(5359, 'en', 'smd_thumb_sharpen', 'smd_thumb', 'site', 'Rendre net', '2019-07-23 23:46:12'),
(5360, 'en', 'smd_thumb_tables_not_installed', 'smd_thumb', 'site', 'Tables non installées : essayez de réinstaller le plugin.', '2019-07-23 23:46:12'),
(5361, 'en', 'smd_thumb_thumbnail_heading', 'smd_thumb', 'site', 'Vignettes', '2019-07-23 23:46:12'),
(5362, 'en', 'smd_thumb_txp_auto_replace', 'smd_thumb', 'site', 'Recréer les vignettes au téléchargement des images :', '2019-07-23 23:46:12'),
(5363, 'en', 'smd_thumb_txp_create', 'smd_thumb', 'site', 'Créer', '2019-07-23 23:46:12'),
(5364, 'en', 'smd_thumb_txp_create_from', 'smd_thumb', 'site', 'Créer les vignettes à partir de :', '2019-07-23 23:46:12'),
(5365, 'en', 'smd_thumb_txp_create_from_full', 'smd_thumb', 'site', 'l\'image originale', '2019-07-23 23:46:12'),
(5366, 'en', 'smd_thumb_txp_create_from_thumb', 'smd_thumb', 'site', 'la vignette', '2019-07-23 23:46:12'),
(5367, 'en', 'smd_thumb_txp_delete', 'smd_thumb', 'site', 'Supprimer', '2019-07-23 23:46:12'),
(5368, 'en', 'smd_thumb_upload', 'smd_thumb', 'site', 'Remplacer les vignettes sélectionnées', '2019-07-23 23:46:12'),
(5369, 'de', 'smd_um_active', 'smd_um', 'site', 'Usuarios actualmente conectados:', '2019-07-23 23:46:55'),
(5370, 'de', 'smd_um_active_timeout', 'smd_um', 'site', 'Desconectar a los usuarios después de (segundos)', '2019-07-23 23:46:55'),
(5371, 'de', 'smd_um_admin_group', 'smd_um', 'site', 'Grupo protegido de administradores', '2019-07-23 23:46:55'),
(5372, 'de', 'smd_um_article_count', 'smd_um', 'site', 'Artículos', '2019-07-23 23:46:55'),
(5373, 'de', 'smd_um_based_on', 'smd_um', 'site', 'basado en', '2019-07-23 23:46:55'),
(5374, 'de', 'smd_um_chp_lbl', 'smd_um', 'site', 'Cambiar contraseña', '2019-07-23 23:46:55'),
(5375, 'de', 'smd_um_file_count', 'smd_um', 'site', 'Ficheros', '2019-07-23 23:46:55'),
(5376, 'de', 'smd_um_grp_affected', 'smd_um', 'site', '. Usuarios afectados: {num}', '2019-07-23 23:46:55'),
(5377, 'de', 'smd_um_grp_created', 'smd_um', 'site', 'Grupo \"{name}\" creado', '2019-07-23 23:46:55'),
(5378, 'de', 'smd_um_grp_deleted', 'smd_um', 'site', 'Grupo eliminado', '2019-07-23 23:46:55'),
(5379, 'de', 'smd_um_grp_exists', 'smd_um', 'site', 'El grupo ya existe, su ID de privilegios es {id}', '2019-07-23 23:46:55'),
(5380, 'de', 'smd_um_grp_lbl', 'smd_um', 'site', 'Grupos', '2019-07-23 23:46:55'),
(5381, 'de', 'smd_um_grp_new', 'smd_um', 'site', 'Nuevo nombre de grupo', '2019-07-23 23:46:55'),
(5382, 'de', 'smd_um_grp_new_name', 'smd_um', 'site', 'nombre', '2019-07-23 23:46:55'),
(5383, 'de', 'smd_um_grp_saved', 'smd_um', 'site', 'Información de grupo actualizada', '2019-07-23 23:46:55'),
(5384, 'de', 'smd_um_heading_grp', 'smd_um', 'site', 'Grupos de usuarios', '2019-07-23 23:46:55'),
(5385, 'de', 'smd_um_heading_prf', 'smd_um', 'site', 'Preferencias del gestor de usuarios', '2019-07-23 23:46:55'),
(5386, 'de', 'smd_um_heading_prv', 'smd_um', 'site', 'Privilegios de usuarios', '2019-07-23 23:46:55'),
(5387, 'de', 'smd_um_heading_usr', 'smd_um', 'site', 'Gestión de usuarios', '2019-07-23 23:46:55'),
(5388, 'de', 'smd_um_hierarchical_groups', 'smd_um', 'site', 'Asumir grupos jerárquicos (niveles)', '2019-07-23 23:46:55'),
(5389, 'de', 'smd_um_image_count', 'smd_um', 'site', 'Imágenes', '2019-07-23 23:46:55'),
(5390, 'de', 'smd_um_link_count', 'smd_um', 'site', 'Enlaces', '2019-07-23 23:46:55'),
(5391, 'de', 'smd_um_max_search_limit', 'smd_um', 'site', 'Límite máximo de resultados en búsqueda de usuarios', '2019-07-23 23:46:55'),
(5392, 'de', 'smd_um_name_required', 'smd_um', 'site', 'Se requiere un nombre', '2019-07-23 23:46:55'),
(5393, 'de', 'smd_um_new_user', 'smd_um', 'site', 'Nuevo usuario', '2019-07-23 23:46:55'),
(5394, 'de', 'smd_um_pass_change_error', 'smd_um', 'site', 'Contraseña NO guardada', '2019-07-23 23:46:55'),
(5395, 'de', 'smd_um_pass_length', 'smd_um', 'site', 'Longitud de contraseña (caracteres)', '2019-07-23 23:46:55'),
(5396, 'de', 'smd_um_prf_lbl', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5397, 'de', 'smd_um_prv_created', 'smd_um', 'site', 'Área de privilegios \"{area}\" creada', '2019-07-23 23:46:55'),
(5398, 'de', 'smd_um_prv_exists', 'smd_um', 'site', 'Área de privilegios ya existe', '2019-07-23 23:46:55'),
(5399, 'de', 'smd_um_prv_lbl', 'smd_um', 'site', 'Privilegios', '2019-07-23 23:46:55'),
(5400, 'de', 'smd_um_prv_new', 'smd_um', 'site', 'Nuevo área de privilegios', '2019-07-23 23:46:55'),
(5401, 'de', 'smd_um_prv_saved', 'smd_um', 'site', 'Privilegios actualizados', '2019-07-23 23:46:55'),
(5402, 'de', 'smd_um_prv_smd_um', 'smd_um', 'site', 'Imposible crear privilegios para smd_user_manager', '2019-07-23 23:46:55'),
(5403, 'de', 'smd_um_reset', 'smd_um', 'site', '[R]', '2019-07-23 23:46:55'),
(5404, 'de', 'smd_um_self_alter', 'smd_um', 'site', 'Permitir cambiar privilegios a smd_um', '2019-07-23 23:46:55'),
(5405, 'de', 'smd_um_sel_all', 'smd_um', 'site', 'Selecciona esta área completa, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5406, 'de', 'smd_um_sel_grp', 'smd_um', 'site', 'Selecciona este grupo, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5407, 'de', 'smd_um_sel_prv', 'smd_um', 'site', 'Selecciona este área, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5408, 'de', 'smd_um_sel_reset', 'smd_um', 'site', 'Reajustar: todas las áreas marcadas volverán a sus valores por defecto después de guardar', '2019-07-23 23:46:55'),
(5409, 'de', 'smd_um_settings', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5410, 'de', 'smd_um_tab_name', 'smd_um', 'site', 'Gestor de usuarios', '2019-07-23 23:46:55'),
(5411, 'de', 'smd_um_tbl_installed', 'smd_um', 'site', 'Tablas instaladas', '2019-07-23 23:46:55'),
(5412, 'de', 'smd_um_tbl_not_installed', 'smd_um', 'site', 'Tablas no instaladas', '2019-07-23 23:46:55'),
(5413, 'de', 'smd_um_tbl_not_removed', 'smd_um', 'site', 'Tablas no eliminadas', '2019-07-23 23:46:55'),
(5414, 'de', 'smd_um_tbl_removed', 'smd_um', 'site', 'Tablas eliminadas', '2019-07-23 23:46:55'),
(5415, 'de', 'smd_um_user_count', 'smd_um', 'site', 'Usuarios en este grupo:', '2019-07-23 23:46:55'),
(5416, 'de', 'smd_um_usr_lbl', 'smd_um', 'site', 'Usuarios', '2019-07-23 23:46:55'),
(5417, 'en-us', 'smd_um_active', 'smd_um', 'site', 'Usuarios actualmente conectados:', '2019-07-23 23:46:55'),
(5418, 'en-us', 'smd_um_active_timeout', 'smd_um', 'site', 'Desconectar a los usuarios después de (segundos)', '2019-07-23 23:46:55'),
(5419, 'en-us', 'smd_um_admin_group', 'smd_um', 'site', 'Grupo protegido de administradores', '2019-07-23 23:46:55'),
(5420, 'en-us', 'smd_um_article_count', 'smd_um', 'site', 'Artículos', '2019-07-23 23:46:55'),
(5421, 'en-us', 'smd_um_based_on', 'smd_um', 'site', 'basado en', '2019-07-23 23:46:55'),
(5422, 'en-us', 'smd_um_chp_lbl', 'smd_um', 'site', 'Cambiar contraseña', '2019-07-23 23:46:55'),
(5423, 'en-us', 'smd_um_file_count', 'smd_um', 'site', 'Ficheros', '2019-07-23 23:46:55'),
(5424, 'en-us', 'smd_um_grp_affected', 'smd_um', 'site', '. Usuarios afectados: {num}', '2019-07-23 23:46:55'),
(5425, 'en-us', 'smd_um_grp_created', 'smd_um', 'site', 'Grupo \"{name}\" creado', '2019-07-23 23:46:55'),
(5426, 'en-us', 'smd_um_grp_deleted', 'smd_um', 'site', 'Grupo eliminado', '2019-07-23 23:46:55'),
(5427, 'en-us', 'smd_um_grp_exists', 'smd_um', 'site', 'El grupo ya existe, su ID de privilegios es {id}', '2019-07-23 23:46:55');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(5428, 'en-us', 'smd_um_grp_lbl', 'smd_um', 'site', 'Grupos', '2019-07-23 23:46:55'),
(5429, 'en-us', 'smd_um_grp_new', 'smd_um', 'site', 'Nuevo nombre de grupo', '2019-07-23 23:46:55'),
(5430, 'en-us', 'smd_um_grp_new_name', 'smd_um', 'site', 'nombre', '2019-07-23 23:46:55'),
(5431, 'en-us', 'smd_um_grp_saved', 'smd_um', 'site', 'Información de grupo actualizada', '2019-07-23 23:46:55'),
(5432, 'en-us', 'smd_um_heading_grp', 'smd_um', 'site', 'Grupos de usuarios', '2019-07-23 23:46:55'),
(5433, 'en-us', 'smd_um_heading_prf', 'smd_um', 'site', 'Preferencias del gestor de usuarios', '2019-07-23 23:46:55'),
(5434, 'en-us', 'smd_um_heading_prv', 'smd_um', 'site', 'Privilegios de usuarios', '2019-07-23 23:46:55'),
(5435, 'en-us', 'smd_um_heading_usr', 'smd_um', 'site', 'Gestión de usuarios', '2019-07-23 23:46:55'),
(5436, 'en-us', 'smd_um_hierarchical_groups', 'smd_um', 'site', 'Asumir grupos jerárquicos (niveles)', '2019-07-23 23:46:55'),
(5437, 'en-us', 'smd_um_image_count', 'smd_um', 'site', 'Imágenes', '2019-07-23 23:46:55'),
(5438, 'en-us', 'smd_um_link_count', 'smd_um', 'site', 'Enlaces', '2019-07-23 23:46:55'),
(5439, 'en-us', 'smd_um_max_search_limit', 'smd_um', 'site', 'Límite máximo de resultados en búsqueda de usuarios', '2019-07-23 23:46:55'),
(5440, 'en-us', 'smd_um_name_required', 'smd_um', 'site', 'Se requiere un nombre', '2019-07-23 23:46:55'),
(5441, 'en-us', 'smd_um_new_user', 'smd_um', 'site', 'Nuevo usuario', '2019-07-23 23:46:55'),
(5442, 'en-us', 'smd_um_pass_change_error', 'smd_um', 'site', 'Contraseña NO guardada', '2019-07-23 23:46:55'),
(5443, 'en-us', 'smd_um_pass_length', 'smd_um', 'site', 'Longitud de contraseña (caracteres)', '2019-07-23 23:46:55'),
(5444, 'en-us', 'smd_um_prf_lbl', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5445, 'en-us', 'smd_um_prv_created', 'smd_um', 'site', 'Área de privilegios \"{area}\" creada', '2019-07-23 23:46:55'),
(5446, 'en-us', 'smd_um_prv_exists', 'smd_um', 'site', 'Área de privilegios ya existe', '2019-07-23 23:46:55'),
(5447, 'en-us', 'smd_um_prv_lbl', 'smd_um', 'site', 'Privilegios', '2019-07-23 23:46:55'),
(5448, 'en-us', 'smd_um_prv_new', 'smd_um', 'site', 'Nuevo área de privilegios', '2019-07-23 23:46:55'),
(5449, 'en-us', 'smd_um_prv_saved', 'smd_um', 'site', 'Privilegios actualizados', '2019-07-23 23:46:55'),
(5450, 'en-us', 'smd_um_prv_smd_um', 'smd_um', 'site', 'Imposible crear privilegios para smd_user_manager', '2019-07-23 23:46:55'),
(5451, 'en-us', 'smd_um_reset', 'smd_um', 'site', '[R]', '2019-07-23 23:46:55'),
(5452, 'en-us', 'smd_um_self_alter', 'smd_um', 'site', 'Permitir cambiar privilegios a smd_um', '2019-07-23 23:46:55'),
(5453, 'en-us', 'smd_um_sel_all', 'smd_um', 'site', 'Selecciona esta área completa, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5454, 'en-us', 'smd_um_sel_grp', 'smd_um', 'site', 'Selecciona este grupo, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5455, 'en-us', 'smd_um_sel_prv', 'smd_um', 'site', 'Selecciona este área, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5456, 'en-us', 'smd_um_sel_reset', 'smd_um', 'site', 'Reajustar: todas las áreas marcadas volverán a sus valores por defecto después de guardar', '2019-07-23 23:46:55'),
(5457, 'en-us', 'smd_um_settings', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5458, 'en-us', 'smd_um_tab_name', 'smd_um', 'site', 'Gestor de usuarios', '2019-07-23 23:46:55'),
(5459, 'en-us', 'smd_um_tbl_installed', 'smd_um', 'site', 'Tablas instaladas', '2019-07-23 23:46:55'),
(5460, 'en-us', 'smd_um_tbl_not_installed', 'smd_um', 'site', 'Tablas no instaladas', '2019-07-23 23:46:55'),
(5461, 'en-us', 'smd_um_tbl_not_removed', 'smd_um', 'site', 'Tablas no eliminadas', '2019-07-23 23:46:55'),
(5462, 'en-us', 'smd_um_tbl_removed', 'smd_um', 'site', 'Tablas eliminadas', '2019-07-23 23:46:55'),
(5463, 'en-us', 'smd_um_user_count', 'smd_um', 'site', 'Usuarios en este grupo:', '2019-07-23 23:46:55'),
(5464, 'en-us', 'smd_um_usr_lbl', 'smd_um', 'site', 'Usuarios', '2019-07-23 23:46:55'),
(5465, 'el', 'smd_um_active', 'smd_um', 'site', 'Usuarios actualmente conectados:', '2019-07-23 23:46:55'),
(5466, 'el', 'smd_um_active_timeout', 'smd_um', 'site', 'Desconectar a los usuarios después de (segundos)', '2019-07-23 23:46:55'),
(5467, 'el', 'smd_um_admin_group', 'smd_um', 'site', 'Grupo protegido de administradores', '2019-07-23 23:46:55'),
(5468, 'el', 'smd_um_article_count', 'smd_um', 'site', 'Artículos', '2019-07-23 23:46:55'),
(5469, 'el', 'smd_um_based_on', 'smd_um', 'site', 'basado en', '2019-07-23 23:46:55'),
(5470, 'el', 'smd_um_chp_lbl', 'smd_um', 'site', 'Cambiar contraseña', '2019-07-23 23:46:55'),
(5471, 'el', 'smd_um_file_count', 'smd_um', 'site', 'Ficheros', '2019-07-23 23:46:55'),
(5472, 'el', 'smd_um_grp_affected', 'smd_um', 'site', '. Usuarios afectados: {num}', '2019-07-23 23:46:55'),
(5473, 'el', 'smd_um_grp_created', 'smd_um', 'site', 'Grupo \"{name}\" creado', '2019-07-23 23:46:55'),
(5474, 'el', 'smd_um_grp_deleted', 'smd_um', 'site', 'Grupo eliminado', '2019-07-23 23:46:55'),
(5475, 'el', 'smd_um_grp_exists', 'smd_um', 'site', 'El grupo ya existe, su ID de privilegios es {id}', '2019-07-23 23:46:55'),
(5476, 'el', 'smd_um_grp_lbl', 'smd_um', 'site', 'Grupos', '2019-07-23 23:46:55'),
(5477, 'el', 'smd_um_grp_new', 'smd_um', 'site', 'Nuevo nombre de grupo', '2019-07-23 23:46:55'),
(5478, 'el', 'smd_um_grp_new_name', 'smd_um', 'site', 'nombre', '2019-07-23 23:46:55'),
(5479, 'el', 'smd_um_grp_saved', 'smd_um', 'site', 'Información de grupo actualizada', '2019-07-23 23:46:55'),
(5480, 'el', 'smd_um_heading_grp', 'smd_um', 'site', 'Grupos de usuarios', '2019-07-23 23:46:55'),
(5481, 'el', 'smd_um_heading_prf', 'smd_um', 'site', 'Preferencias del gestor de usuarios', '2019-07-23 23:46:55'),
(5482, 'el', 'smd_um_heading_prv', 'smd_um', 'site', 'Privilegios de usuarios', '2019-07-23 23:46:55'),
(5483, 'el', 'smd_um_heading_usr', 'smd_um', 'site', 'Gestión de usuarios', '2019-07-23 23:46:55'),
(5484, 'el', 'smd_um_hierarchical_groups', 'smd_um', 'site', 'Asumir grupos jerárquicos (niveles)', '2019-07-23 23:46:55'),
(5485, 'el', 'smd_um_image_count', 'smd_um', 'site', 'Imágenes', '2019-07-23 23:46:55'),
(5486, 'el', 'smd_um_link_count', 'smd_um', 'site', 'Enlaces', '2019-07-23 23:46:55'),
(5487, 'el', 'smd_um_max_search_limit', 'smd_um', 'site', 'Límite máximo de resultados en búsqueda de usuarios', '2019-07-23 23:46:55'),
(5488, 'el', 'smd_um_name_required', 'smd_um', 'site', 'Se requiere un nombre', '2019-07-23 23:46:55'),
(5489, 'el', 'smd_um_new_user', 'smd_um', 'site', 'Nuevo usuario', '2019-07-23 23:46:55'),
(5490, 'el', 'smd_um_pass_change_error', 'smd_um', 'site', 'Contraseña NO guardada', '2019-07-23 23:46:55'),
(5491, 'el', 'smd_um_pass_length', 'smd_um', 'site', 'Longitud de contraseña (caracteres)', '2019-07-23 23:46:55'),
(5492, 'el', 'smd_um_prf_lbl', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5493, 'el', 'smd_um_prv_created', 'smd_um', 'site', 'Área de privilegios \"{area}\" creada', '2019-07-23 23:46:55'),
(5494, 'el', 'smd_um_prv_exists', 'smd_um', 'site', 'Área de privilegios ya existe', '2019-07-23 23:46:55'),
(5495, 'el', 'smd_um_prv_lbl', 'smd_um', 'site', 'Privilegios', '2019-07-23 23:46:55'),
(5496, 'el', 'smd_um_prv_new', 'smd_um', 'site', 'Nuevo área de privilegios', '2019-07-23 23:46:55'),
(5497, 'el', 'smd_um_prv_saved', 'smd_um', 'site', 'Privilegios actualizados', '2019-07-23 23:46:55'),
(5498, 'el', 'smd_um_prv_smd_um', 'smd_um', 'site', 'Imposible crear privilegios para smd_user_manager', '2019-07-23 23:46:55'),
(5499, 'el', 'smd_um_reset', 'smd_um', 'site', '[R]', '2019-07-23 23:46:55'),
(5500, 'el', 'smd_um_self_alter', 'smd_um', 'site', 'Permitir cambiar privilegios a smd_um', '2019-07-23 23:46:55'),
(5501, 'el', 'smd_um_sel_all', 'smd_um', 'site', 'Selecciona esta área completa, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5502, 'el', 'smd_um_sel_grp', 'smd_um', 'site', 'Selecciona este grupo, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5503, 'el', 'smd_um_sel_prv', 'smd_um', 'site', 'Selecciona este área, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5504, 'el', 'smd_um_sel_reset', 'smd_um', 'site', 'Reajustar: todas las áreas marcadas volverán a sus valores por defecto después de guardar', '2019-07-23 23:46:55'),
(5505, 'el', 'smd_um_settings', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5506, 'el', 'smd_um_tab_name', 'smd_um', 'site', 'Gestor de usuarios', '2019-07-23 23:46:55'),
(5507, 'el', 'smd_um_tbl_installed', 'smd_um', 'site', 'Tablas instaladas', '2019-07-23 23:46:55'),
(5508, 'el', 'smd_um_tbl_not_installed', 'smd_um', 'site', 'Tablas no instaladas', '2019-07-23 23:46:55'),
(5509, 'el', 'smd_um_tbl_not_removed', 'smd_um', 'site', 'Tablas no eliminadas', '2019-07-23 23:46:55'),
(5510, 'el', 'smd_um_tbl_removed', 'smd_um', 'site', 'Tablas eliminadas', '2019-07-23 23:46:55'),
(5511, 'el', 'smd_um_user_count', 'smd_um', 'site', 'Usuarios en este grupo:', '2019-07-23 23:46:55'),
(5512, 'el', 'smd_um_usr_lbl', 'smd_um', 'site', 'Usuarios', '2019-07-23 23:46:55'),
(5513, 'en', 'smd_um_active', 'smd_um', 'site', 'Usuarios actualmente conectados:', '2019-07-23 23:46:55'),
(5514, 'en', 'smd_um_active_timeout', 'smd_um', 'site', 'Desconectar a los usuarios después de (segundos)', '2019-07-23 23:46:55'),
(5515, 'en', 'smd_um_admin_group', 'smd_um', 'site', 'Grupo protegido de administradores', '2019-07-23 23:46:55'),
(5516, 'en', 'smd_um_article_count', 'smd_um', 'site', 'Artículos', '2019-07-23 23:46:55'),
(5517, 'en', 'smd_um_based_on', 'smd_um', 'site', 'basado en', '2019-07-23 23:46:55'),
(5518, 'en', 'smd_um_chp_lbl', 'smd_um', 'site', 'Cambiar contraseña', '2019-07-23 23:46:55'),
(5519, 'en', 'smd_um_file_count', 'smd_um', 'site', 'Ficheros', '2019-07-23 23:46:55'),
(5520, 'en', 'smd_um_grp_affected', 'smd_um', 'site', '. Usuarios afectados: {num}', '2019-07-23 23:46:55'),
(5521, 'en', 'smd_um_grp_created', 'smd_um', 'site', 'Grupo \"{name}\" creado', '2019-07-23 23:46:55'),
(5522, 'en', 'smd_um_grp_deleted', 'smd_um', 'site', 'Grupo eliminado', '2019-07-23 23:46:55'),
(5523, 'en', 'smd_um_grp_exists', 'smd_um', 'site', 'El grupo ya existe, su ID de privilegios es {id}', '2019-07-23 23:46:55'),
(5524, 'en', 'smd_um_grp_lbl', 'smd_um', 'site', 'Grupos', '2019-07-23 23:46:55'),
(5525, 'en', 'smd_um_grp_new', 'smd_um', 'site', 'Nuevo nombre de grupo', '2019-07-23 23:46:55'),
(5526, 'en', 'smd_um_grp_new_name', 'smd_um', 'site', 'nombre', '2019-07-23 23:46:55'),
(5527, 'en', 'smd_um_grp_saved', 'smd_um', 'site', 'Información de grupo actualizada', '2019-07-23 23:46:55'),
(5528, 'en', 'smd_um_heading_grp', 'smd_um', 'site', 'Grupos de usuarios', '2019-07-23 23:46:55'),
(5529, 'en', 'smd_um_heading_prf', 'smd_um', 'site', 'Preferencias del gestor de usuarios', '2019-07-23 23:46:55'),
(5530, 'en', 'smd_um_heading_prv', 'smd_um', 'site', 'Privilegios de usuarios', '2019-07-23 23:46:55'),
(5531, 'en', 'smd_um_heading_usr', 'smd_um', 'site', 'Gestión de usuarios', '2019-07-23 23:46:55'),
(5532, 'en', 'smd_um_hierarchical_groups', 'smd_um', 'site', 'Asumir grupos jerárquicos (niveles)', '2019-07-23 23:46:55'),
(5533, 'en', 'smd_um_image_count', 'smd_um', 'site', 'Imágenes', '2019-07-23 23:46:55'),
(5534, 'en', 'smd_um_link_count', 'smd_um', 'site', 'Enlaces', '2019-07-23 23:46:55'),
(5535, 'en', 'smd_um_max_search_limit', 'smd_um', 'site', 'Límite máximo de resultados en búsqueda de usuarios', '2019-07-23 23:46:55'),
(5536, 'en', 'smd_um_name_required', 'smd_um', 'site', 'Se requiere un nombre', '2019-07-23 23:46:55'),
(5537, 'en', 'smd_um_new_user', 'smd_um', 'site', 'Nuevo usuario', '2019-07-23 23:46:55'),
(5538, 'en', 'smd_um_pass_change_error', 'smd_um', 'site', 'Contraseña NO guardada', '2019-07-23 23:46:55'),
(5539, 'en', 'smd_um_pass_length', 'smd_um', 'site', 'Longitud de contraseña (caracteres)', '2019-07-23 23:46:55'),
(5540, 'en', 'smd_um_prf_lbl', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5541, 'en', 'smd_um_prv_created', 'smd_um', 'site', 'Área de privilegios \"{area}\" creada', '2019-07-23 23:46:55'),
(5542, 'en', 'smd_um_prv_exists', 'smd_um', 'site', 'Área de privilegios ya existe', '2019-07-23 23:46:55'),
(5543, 'en', 'smd_um_prv_lbl', 'smd_um', 'site', 'Privilegios', '2019-07-23 23:46:55'),
(5544, 'en', 'smd_um_prv_new', 'smd_um', 'site', 'Nuevo área de privilegios', '2019-07-23 23:46:55'),
(5545, 'en', 'smd_um_prv_saved', 'smd_um', 'site', 'Privilegios actualizados', '2019-07-23 23:46:55'),
(5546, 'en', 'smd_um_prv_smd_um', 'smd_um', 'site', 'Imposible crear privilegios para smd_user_manager', '2019-07-23 23:46:55'),
(5547, 'en', 'smd_um_reset', 'smd_um', 'site', '[R]', '2019-07-23 23:46:55'),
(5548, 'en', 'smd_um_self_alter', 'smd_um', 'site', 'Permitir cambiar privilegios a smd_um', '2019-07-23 23:46:55'),
(5549, 'en', 'smd_um_sel_all', 'smd_um', 'site', 'Selecciona esta área completa, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5550, 'en', 'smd_um_sel_grp', 'smd_um', 'site', 'Selecciona este grupo, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5551, 'en', 'smd_um_sel_prv', 'smd_um', 'site', 'Selecciona este área, luego marca (c), desmarca (u) o invierte (t) la selección', '2019-07-23 23:46:55'),
(5552, 'en', 'smd_um_sel_reset', 'smd_um', 'site', 'Reajustar: todas las áreas marcadas volverán a sus valores por defecto después de guardar', '2019-07-23 23:46:55'),
(5553, 'en', 'smd_um_settings', 'smd_um', 'site', 'Preferencias', '2019-07-23 23:46:55'),
(5554, 'en', 'smd_um_tab_name', 'smd_um', 'site', 'Gestor de usuarios', '2019-07-23 23:46:55'),
(5555, 'en', 'smd_um_tbl_installed', 'smd_um', 'site', 'Tablas instaladas', '2019-07-23 23:46:55'),
(5556, 'en', 'smd_um_tbl_not_installed', 'smd_um', 'site', 'Tablas no instaladas', '2019-07-23 23:46:55'),
(5557, 'en', 'smd_um_tbl_not_removed', 'smd_um', 'site', 'Tablas no eliminadas', '2019-07-23 23:46:55'),
(5558, 'en', 'smd_um_tbl_removed', 'smd_um', 'site', 'Tablas eliminadas', '2019-07-23 23:46:55'),
(5559, 'en', 'smd_um_user_count', 'smd_um', 'site', 'Usuarios en este grupo:', '2019-07-23 23:46:55'),
(5560, 'en', 'smd_um_usr_lbl', 'smd_um', 'site', 'Usuarios', '2019-07-23 23:46:55'),
(5561, 'de', 'smd_tags', 'smd_tag', 'site', 'Tags (smd)', '2019-07-23 23:51:02'),
(5562, 'de', 'smd_tag_ac_std', 'smd_tag', 'site', 'Standard', '2019-07-23 23:51:02'),
(5563, 'de', 'smd_tag_ac_str', 'smd_tag', 'site', 'Strict', '2019-07-23 23:51:02'),
(5564, 'de', 'smd_tag_all_lbl', 'smd_tag', 'site', 'All', '2019-07-23 23:51:02'),
(5565, 'de', 'smd_tag_already_exists', 'smd_tag', 'site', 'already exists', '2019-07-23 23:51:02'),
(5566, 'de', 'smd_tag_assign_parent_lbl', 'smd_tag', 'site', 'Assign parent', '2019-07-23 23:51:02'),
(5567, 'de', 'smd_tag_bi_both', 'smd_tag', 'site', 'Both', '2019-07-23 23:51:02'),
(5568, 'de', 'smd_tag_bi_down', 'smd_tag', 'site', 'Down', '2019-07-23 23:51:02'),
(5569, 'de', 'smd_tag_bi_up', 'smd_tag', 'site', 'Up', '2019-07-23 23:51:02'),
(5570, 'de', 'smd_tag_by_column', 'smd_tag', 'site', 'Column', '2019-07-23 23:51:02'),
(5571, 'de', 'smd_tag_by_row', 'smd_tag', 'site', 'Row', '2019-07-23 23:51:02'),
(5572, 'de', 'smd_tag_category_linked_lbl', 'smd_tag', 'site', 'Linked to category \"{category}\":', '2019-07-23 23:51:02'),
(5573, 'de', 'smd_tag_category_unlinked_lbl', 'smd_tag', 'site', 'Unlinked:', '2019-07-23 23:51:02'),
(5574, 'de', 'smd_tag_children_and', 'smd_tag', 'site', 'and its children', '2019-07-23 23:51:02'),
(5575, 'de', 'smd_tag_children_del', 'smd_tag', 'site', 'Delete children', '2019-07-23 23:51:02'),
(5576, 'de', 'smd_tag_children_pro', 'smd_tag', 'site', 'Promote children', '2019-07-23 23:51:02'),
(5577, 'de', 'smd_tag_clear', 'smd_tag', 'site', 'clr', '2019-07-23 23:51:02'),
(5578, 'de', 'smd_tag_clink_lbl', 'smd_tag', 'site', 'Linked cat', '2019-07-23 23:51:02'),
(5579, 'de', 'smd_tag_count_lbl', 'smd_tag', 'site', 'Matched tags:', '2019-07-23 23:51:02'),
(5580, 'de', 'smd_tag_created', 'smd_tag', 'site', '{type} tag \"{name}\" created', '2019-07-23 23:51:02'),
(5581, 'de', 'smd_tag_created_rep_lbl', 'smd_tag', 'site', 'Created tags:', '2019-07-23 23:51:02'),
(5582, 'de', 'smd_tag_create_lbl', 'smd_tag', 'site', 'Create/Edit tag', '2019-07-23 23:51:02'),
(5583, 'de', 'smd_tag_deleted', 'smd_tag', 'site', '{type} tag \"{name}\" deleted', '2019-07-23 23:51:02'),
(5584, 'de', 'smd_tag_deleted_lbl', 'smd_tag', 'site', 'Deleted tags:', '2019-07-23 23:51:02'),
(5585, 'de', 'smd_tag_err_in_use_lbl', 'smd_tag', 'site', 'Tags in use:', '2019-07-23 23:51:02'),
(5586, 'de', 'smd_tag_err_orphaned_lbl', 'smd_tag', 'site', 'Orphaned tags:', '2019-07-23 23:51:02'),
(5587, 'de', 'smd_tag_exists', 'smd_tag', 'site', '{type} tag \"{name}\" already exists', '2019-07-23 23:51:02'),
(5588, 'de', 'smd_tag_extras', 'smd_tag', 'site', 'More...', '2019-07-23 23:51:02'),
(5589, 'de', 'smd_tag_import_results_pt1', 'smd_tag', 'site', 'Articles:', '2019-07-23 23:51:02'),
(5590, 'de', 'smd_tag_import_results_pt2', 'smd_tag', 'site', 'Tags linked:', '2019-07-23 23:51:02'),
(5591, 'de', 'smd_tag_in_use', 'smd_tag', 'site', '{type} tag \"{name}\" is in use', '2019-07-23 23:51:02'),
(5592, 'de', 'smd_tag_link_to_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5593, 'de', 'smd_tag_manage_lbl', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5594, 'de', 'smd_tag_missing_rep_lbl', 'smd_tag', 'site', 'Missing tags:', '2019-07-23 23:51:02'),
(5595, 'de', 'smd_tag_multi_delete', 'smd_tag', 'site', 'Tags deleted: {number} (see report for details)', '2019-07-23 23:51:02'),
(5596, 'de', 'smd_tag_not_available', 'smd_tag', 'site', 'Tables are not installed', '2019-07-23 23:51:02'),
(5597, 'de', 'smd_tag_not_created', 'smd_tag', 'site', '{type} tag \"{name}\" NOT created', '2019-07-23 23:51:02'),
(5598, 'de', 'smd_tag_not_created_rep_lbl', 'smd_tag', 'site', 'Tags NOT created:', '2019-07-23 23:51:02'),
(5599, 'de', 'smd_tag_not_parent_linked_lbl', 'smd_tag', 'site', 'Not linked:', '2019-07-23 23:51:02'),
(5600, 'de', 'smd_tag_not_updated', 'smd_tag', 'site', '{type} tag \"{name}\" NOT updated', '2019-07-23 23:51:02'),
(5601, 'de', 'smd_tag_no_desc', 'smd_tag', 'site', 'No description available', '2019-07-23 23:51:02'),
(5602, 'de', 'smd_tag_no_name', 'smd_tag', 'site', '{type} tag NOT created: needs a name', '2019-07-23 23:51:02'),
(5603, 'de', 'smd_tag_no_parent', 'smd_tag', 'site', 'No parent tags.', '2019-07-23 23:51:02'),
(5604, 'de', 'smd_tag_no_sections', 'smd_tag', 'site', 'No sections', '2019-07-23 23:51:02'),
(5605, 'de', 'smd_tag_no_tags', 'smd_tag', 'site', 'No {type} tags defined', '2019-07-23 23:51:02'),
(5606, 'de', 'smd_tag_parent_lbl', 'smd_tag', 'site', 'Parent', '2019-07-23 23:51:02'),
(5607, 'de', 'smd_tag_parent_linked_lbl', 'smd_tag', 'site', 'Assigned these tag(s) to \"{parent}\":', '2019-07-23 23:51:02'),
(5608, 'de', 'smd_tag_pool_lbl', 'smd_tag', 'site', 'Tag pool', '2019-07-23 23:51:02'),
(5609, 'de', 'smd_tag_prefs_installed', 'smd_tag', 'site', 'Preferences installed', '2019-07-23 23:51:02'),
(5610, 'de', 'smd_tag_prefs_not_installed', 'smd_tag', 'site', 'Preferences not installed.', '2019-07-23 23:51:02'),
(5611, 'de', 'smd_tag_prefs_p', 'smd_tag', 'site', 'Interface settings', '2019-07-23 23:51:02'),
(5612, 'de', 'smd_tag_prefs_pane', 'smd_tag', 'site', 'Prefs', '2019-07-23 23:51:02'),
(5613, 'de', 'smd_tag_prefs_removed', 'smd_tag', 'site', 'Preferences removed', '2019-07-23 23:51:02'),
(5614, 'de', 'smd_tag_prefs_some', 'smd_tag', 'site', 'Not all preferences available.', '2019-07-23 23:51:02'),
(5615, 'de', 'smd_tag_prefs_some_explain', 'smd_tag', 'site', 'This is either a new installation or a different version<br />of the plugin to one you had before.', '2019-07-23 23:51:02'),
(5616, 'de', 'smd_tag_prefs_some_opts1', 'smd_tag', 'site', 'Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.', '2019-07-23 23:51:02'),
(5617, 'de', 'smd_tag_prefs_some_opts2', 'smd_tag', 'site', 'Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.', '2019-07-23 23:51:02'),
(5618, 'de', 'smd_tag_prefs_some_tbl', 'smd_tag', 'site', 'Not all table info available.', '2019-07-23 23:51:02'),
(5619, 'de', 'smd_tag_prefs_t', 'smd_tag', 'site', 'Tag management', '2019-07-23 23:51:02'),
(5620, 'de', 'smd_tag_prefs_title', 'smd_tag', 'site', 'Tag Preferences', '2019-07-23 23:51:02'),
(5621, 'de', 'smd_tag_prefs_u', 'smd_tag', 'site', 'URL management', '2019-07-23 23:51:02'),
(5622, 'de', 'smd_tag_pref_install_lbl', 'smd_tag', 'site', 'Install prefs', '2019-07-23 23:51:02'),
(5623, 'de', 'smd_tag_pref_remove_lbl', 'smd_tag', 'site', 'Remove prefs', '2019-07-23 23:51:02'),
(5624, 'de', 'smd_tag_pref_show_lbl', 'smd_tag', 'site', 'Preferences', '2019-07-23 23:51:02'),
(5625, 'de', 'smd_tag_p_enable', 'smd_tag', 'site', 'Enable tags in', '2019-07-23 23:51:02'),
(5626, 'de', 'smd_tag_p_input', 'smd_tag', 'site', 'Enter tags using', '2019-07-23 23:51:02'),
(5627, 'de', 'smd_tag_p_lbi', 'smd_tag', 'site', 'Bi-directional tag trees', '2019-07-23 23:51:02'),
(5628, 'de', 'smd_tag_p_linkcat', 'smd_tag', 'site', 'Link tags to categories', '2019-07-23 23:51:02'),
(5629, 'de', 'smd_tag_p_listpar', 'smd_tag', 'site', 'Permit parent tag selection', '2019-07-23 23:51:02'),
(5630, 'de', 'smd_tag_p_master', 'smd_tag', 'site', 'Master parent tag', '2019-07-23 23:51:02'),
(5631, 'de', 'smd_tag_p_qtag', 'smd_tag', 'site', 'Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)', '2019-07-23 23:51:02'),
(5632, 'de', 'smd_tag_p_qtpath', 'smd_tag', 'site', 'JS plugin dir', '2019-07-23 23:51:02'),
(5633, 'de', 'smd_tag_p_qtstyl', 'smd_tag', 'site', 'JS style dir', '2019-07-23 23:51:02'),
(5634, 'de', 'smd_tag_p_size', 'smd_tag', 'site', 'Select/textarea rows', '2019-07-23 23:51:02'),
(5635, 'de', 'smd_tag_recent_report', 'smd_tag', 'site', 'Display recent report', '2019-07-23 23:51:02'),
(5636, 'de', 'smd_tag_report_lbl', 'smd_tag', 'site', 'Report: smd_tags', '2019-07-23 23:51:02'),
(5637, 'de', 'smd_tag_rss_uc_only', 'smd_tag', 'site', '(ignored for tru_tags)', '2019-07-23 23:51:02'),
(5638, 'de', 'smd_tag_rss_uc_tru_tags_only', 'smd_tag', 'site', '(ignored for Textpattern cats)', '2019-07-23 23:51:02'),
(5639, 'de', 'smd_tag_sel_list', 'smd_tag', 'site', 'Select list', '2019-07-23 23:51:02'),
(5640, 'de', 'smd_tag_sync_cfs_delim_lbl', 'smd_tag', 'site', 'Custom field delimiter', '2019-07-23 23:51:02'),
(5641, 'de', 'smd_tag_sync_cfs_lbl', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5642, 'de', 'smd_tag_sync_delete_orig_lbl', 'smd_tag', 'site', 'Delete original', '2019-07-23 23:51:02'),
(5643, 'de', 'smd_tag_sync_force_cat_lbl', 'smd_tag', 'site', 'Force category link if tag already exists', '2019-07-23 23:51:02'),
(5644, 'de', 'smd_tag_sync_force_parent_lbl', 'smd_tag', 'site', 'Force parent if tag already exists', '2019-07-23 23:51:02'),
(5645, 'de', 'smd_tag_sync_import_opts', 'smd_tag', 'site', 'Import options', '2019-07-23 23:51:02'),
(5646, 'de', 'smd_tag_sync_lbl', 'smd_tag', 'site', 'Import tags', '2019-07-23 23:51:02'),
(5647, 'de', 'smd_tag_sync_link_cat_lbl', 'smd_tag', 'site', 'Link tags to category', '2019-07-23 23:51:02'),
(5648, 'de', 'smd_tag_sync_parent_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5649, 'de', 'smd_tag_sync_parent_lbl', 'smd_tag', 'site', 'Start from parent category', '2019-07-23 23:51:02'),
(5650, 'de', 'smd_tag_sync_parent_tag_lbl', 'smd_tag', 'site', 'Assign to parent tag', '2019-07-23 23:51:02'),
(5651, 'de', 'smd_tag_sync_plugin_opts', 'smd_tag', 'site', 'Source options', '2019-07-23 23:51:02'),
(5652, 'de', 'smd_tag_sync_plugs_not_installed', 'smd_tag', 'site', 'tru_tags or rss_unlimited_categories must be installed and activated to import', '2019-07-23 23:51:02'),
(5653, 'de', 'smd_tag_sync_section_lbl', 'smd_tag', 'site', 'Articles from section', '2019-07-23 23:51:02'),
(5654, 'de', 'smd_tag_sync_txpcats_lbl', 'smd_tag', 'site', 'Textpattern category', '2019-07-23 23:51:02'),
(5655, 'de', 'smd_tag_sync_type1', 'smd_tag', 'site', 'tru_tags', '2019-07-23 23:51:02'),
(5656, 'de', 'smd_tag_sync_type2', 'smd_tag', 'site', 'rss_unlimited_categories', '2019-07-23 23:51:02'),
(5657, 'de', 'smd_tag_sync_type3', 'smd_tag', 'site', 'Textpattern categories', '2019-07-23 23:51:02'),
(5658, 'de', 'smd_tag_sync_type4', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5659, 'de', 'smd_tag_sync_type_lbl', 'smd_tag', 'site', 'Import from', '2019-07-23 23:51:02'),
(5660, 'de', 'smd_tag_tags_pane', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5661, 'de', 'smd_tag_tag_search', 'smd_tag', 'site', 'Tag search', '2019-07-23 23:51:02'),
(5662, 'de', 'smd_tag_tbl_installed', 'smd_tag', 'site', 'Tag tables installed.', '2019-07-23 23:51:02'),
(5663, 'de', 'smd_tag_tbl_install_lbl', 'smd_tag', 'site', 'Install tables', '2019-07-23 23:51:02'),
(5664, 'de', 'smd_tag_tbl_not_installed', 'smd_tag', 'site', 'Tag tables NOT installed.', '2019-07-23 23:51:02'),
(5665, 'de', 'smd_tag_tbl_not_removed', 'smd_tag', 'site', 'Tag tables NOT removed.', '2019-07-23 23:51:02'),
(5666, 'de', 'smd_tag_tbl_rebuild_lbl', 'smd_tag', 'site', 'Rebuild tags', '2019-07-23 23:51:02'),
(5667, 'de', 'smd_tag_tbl_rebuilt', 'smd_tag', 'site', 'Tag tables rebuilt', '2019-07-23 23:51:02'),
(5668, 'de', 'smd_tag_tbl_removed', 'smd_tag', 'site', 'Tag tables removed.', '2019-07-23 23:51:02'),
(5669, 'de', 'smd_tag_tbl_remove_lbl', 'smd_tag', 'site', 'Remove tables', '2019-07-23 23:51:02'),
(5670, 'de', 'smd_tag_textbox', 'smd_tag', 'site', 'Text area', '2019-07-23 23:51:02'),
(5671, 'de', 'smd_tag_textboxplus', 'smd_tag', 'site', 'Text area+', '2019-07-23 23:51:02'),
(5672, 'de', 'smd_tag_textlist', 'smd_tag', 'site', 'Text list', '2019-07-23 23:51:02'),
(5673, 'de', 'smd_tag_title', 'smd_tag', 'site', 'Tags', '2019-07-23 23:51:02'),
(5674, 'de', 'smd_tag_toggle', 'smd_tag', 'site', 'tog', '2019-07-23 23:51:02'),
(5675, 'de', 'smd_tag_t_astart', 'smd_tag', 'site', 'Initial pane', '2019-07-23 23:51:02'),
(5676, 'de', 'smd_tag_t_auto', 'smd_tag', 'site', 'Auto name', '2019-07-23 23:51:02'),
(5677, 'de', 'smd_tag_t_cols', 'smd_tag', 'site', 'Tag layout', '2019-07-23 23:51:02'),
(5678, 'de', 'smd_tag_t_colsord', 'smd_tag', 'site', 'Order tags by', '2019-07-23 23:51:02'),
(5679, 'de', 'smd_tag_t_count', 'smd_tag', 'site', 'Show tag usage counts', '2019-07-23 23:51:02'),
(5680, 'de', 'smd_tag_t_deltree', 'smd_tag', 'site', 'When deleting a parent', '2019-07-23 23:51:02'),
(5681, 'de', 'smd_tag_t_delused', 'smd_tag', 'site', 'Allow deletion of used tags', '2019-07-23 23:51:02'),
(5682, 'de', 'smd_tag_t_desc_textile', 'smd_tag', 'site', 'Textile description', '2019-07-23 23:51:02'),
(5683, 'de', 'smd_tag_t_desc_tooltip', 'smd_tag', 'site', 'Show description as tooltip', '2019-07-23 23:51:02'),
(5684, 'de', 'smd_tag_t_enrep', 'smd_tag', 'site', 'Automatically display reports', '2019-07-23 23:51:02'),
(5685, 'de', 'smd_tag_t_hilite', 'smd_tag', 'site', 'Clicked RGB colour', '2019-07-23 23:51:02'),
(5686, 'de', 'smd_tag_t_hover', 'smd_tag', 'site', 'Mouse-over RGB colour', '2019-07-23 23:51:02'),
(5687, 'de', 'smd_tag_t_indent', 'smd_tag', 'site', 'Sub-tag level indicator', '2019-07-23 23:51:02'),
(5688, 'de', 'smd_tag_t_mdelim', 'smd_tag', 'site', 'Multi-tag delimiter (1 char)', '2019-07-23 23:51:02'),
(5689, 'de', 'smd_tag_t_size_desc', 'smd_tag', 'site', 'Description width, height', '2019-07-23 23:51:02'),
(5690, 'de', 'smd_tag_unable_to_create', 'smd_tag', 'site', 'cannot create', '2019-07-23 23:51:02'),
(5691, 'de', 'smd_tag_updated', 'smd_tag', 'site', '{type} tag \"{name}\" updated', '2019-07-23 23:51:02'),
(5692, 'de', 'smd_tag_u_combi', 'smd_tag', 'site', 'Use tag combinations', '2019-07-23 23:51:02'),
(5693, 'de', 'smd_tag_u_combi_and', 'smd_tag', 'site', 'AND combinator char', '2019-07-23 23:51:02'),
(5694, 'de', 'smd_tag_u_combi_or', 'smd_tag', 'site', 'OR combinator char', '2019-07-23 23:51:02'),
(5695, 'de', 'smd_tag_u_pnam', 'smd_tag', 'site', 'URL name parameter', '2019-07-23 23:51:02'),
(5696, 'de', 'smd_tag_u_ptyp', 'smd_tag', 'site', 'URL type parameter', '2019-07-23 23:51:02'),
(5697, 'de', 'smd_tag_u_sec', 'smd_tag', 'site', 'Trigger(s) for tag lists', '2019-07-23 23:51:02'),
(5698, 'de', 'smd_tag_with_filtered', 'smd_tag', 'site', 'With filtered:', '2019-07-23 23:51:02'),
(5699, 'en-us', 'smd_tags', 'smd_tag', 'site', 'Tags (smd)', '2019-07-23 23:51:02'),
(5700, 'en-us', 'smd_tag_ac_std', 'smd_tag', 'site', 'Standard', '2019-07-23 23:51:02'),
(5701, 'en-us', 'smd_tag_ac_str', 'smd_tag', 'site', 'Strict', '2019-07-23 23:51:02'),
(5702, 'en-us', 'smd_tag_all_lbl', 'smd_tag', 'site', 'All', '2019-07-23 23:51:02'),
(5703, 'en-us', 'smd_tag_already_exists', 'smd_tag', 'site', 'already exists', '2019-07-23 23:51:02'),
(5704, 'en-us', 'smd_tag_assign_parent_lbl', 'smd_tag', 'site', 'Assign parent', '2019-07-23 23:51:02'),
(5705, 'en-us', 'smd_tag_bi_both', 'smd_tag', 'site', 'Both', '2019-07-23 23:51:02'),
(5706, 'en-us', 'smd_tag_bi_down', 'smd_tag', 'site', 'Down', '2019-07-23 23:51:02'),
(5707, 'en-us', 'smd_tag_bi_up', 'smd_tag', 'site', 'Up', '2019-07-23 23:51:02'),
(5708, 'en-us', 'smd_tag_by_column', 'smd_tag', 'site', 'Column', '2019-07-23 23:51:02'),
(5709, 'en-us', 'smd_tag_by_row', 'smd_tag', 'site', 'Row', '2019-07-23 23:51:02'),
(5710, 'en-us', 'smd_tag_category_linked_lbl', 'smd_tag', 'site', 'Linked to category \"{category}\":', '2019-07-23 23:51:02'),
(5711, 'en-us', 'smd_tag_category_unlinked_lbl', 'smd_tag', 'site', 'Unlinked:', '2019-07-23 23:51:02'),
(5712, 'en-us', 'smd_tag_children_and', 'smd_tag', 'site', 'and its children', '2019-07-23 23:51:02'),
(5713, 'en-us', 'smd_tag_children_del', 'smd_tag', 'site', 'Delete children', '2019-07-23 23:51:02'),
(5714, 'en-us', 'smd_tag_children_pro', 'smd_tag', 'site', 'Promote children', '2019-07-23 23:51:02'),
(5715, 'en-us', 'smd_tag_clear', 'smd_tag', 'site', 'clr', '2019-07-23 23:51:02'),
(5716, 'en-us', 'smd_tag_clink_lbl', 'smd_tag', 'site', 'Linked cat', '2019-07-23 23:51:02'),
(5717, 'en-us', 'smd_tag_count_lbl', 'smd_tag', 'site', 'Matched tags:', '2019-07-23 23:51:02'),
(5718, 'en-us', 'smd_tag_created', 'smd_tag', 'site', '{type} tag \"{name}\" created', '2019-07-23 23:51:02'),
(5719, 'en-us', 'smd_tag_created_rep_lbl', 'smd_tag', 'site', 'Created tags:', '2019-07-23 23:51:02'),
(5720, 'en-us', 'smd_tag_create_lbl', 'smd_tag', 'site', 'Create/Edit tag', '2019-07-23 23:51:02'),
(5721, 'en-us', 'smd_tag_deleted', 'smd_tag', 'site', '{type} tag \"{name}\" deleted', '2019-07-23 23:51:02'),
(5722, 'en-us', 'smd_tag_deleted_lbl', 'smd_tag', 'site', 'Deleted tags:', '2019-07-23 23:51:02'),
(5723, 'en-us', 'smd_tag_err_in_use_lbl', 'smd_tag', 'site', 'Tags in use:', '2019-07-23 23:51:02'),
(5724, 'en-us', 'smd_tag_err_orphaned_lbl', 'smd_tag', 'site', 'Orphaned tags:', '2019-07-23 23:51:02'),
(5725, 'en-us', 'smd_tag_exists', 'smd_tag', 'site', '{type} tag \"{name}\" already exists', '2019-07-23 23:51:02'),
(5726, 'en-us', 'smd_tag_extras', 'smd_tag', 'site', 'More...', '2019-07-23 23:51:02'),
(5727, 'en-us', 'smd_tag_import_results_pt1', 'smd_tag', 'site', 'Articles:', '2019-07-23 23:51:02'),
(5728, 'en-us', 'smd_tag_import_results_pt2', 'smd_tag', 'site', 'Tags linked:', '2019-07-23 23:51:02'),
(5729, 'en-us', 'smd_tag_in_use', 'smd_tag', 'site', '{type} tag \"{name}\" is in use', '2019-07-23 23:51:02'),
(5730, 'en-us', 'smd_tag_link_to_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5731, 'en-us', 'smd_tag_manage_lbl', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5732, 'en-us', 'smd_tag_missing_rep_lbl', 'smd_tag', 'site', 'Missing tags:', '2019-07-23 23:51:02'),
(5733, 'en-us', 'smd_tag_multi_delete', 'smd_tag', 'site', 'Tags deleted: {number} (see report for details)', '2019-07-23 23:51:02'),
(5734, 'en-us', 'smd_tag_not_available', 'smd_tag', 'site', 'Tables are not installed', '2019-07-23 23:51:02'),
(5735, 'en-us', 'smd_tag_not_created', 'smd_tag', 'site', '{type} tag \"{name}\" NOT created', '2019-07-23 23:51:02'),
(5736, 'en-us', 'smd_tag_not_created_rep_lbl', 'smd_tag', 'site', 'Tags NOT created:', '2019-07-23 23:51:02'),
(5737, 'en-us', 'smd_tag_not_parent_linked_lbl', 'smd_tag', 'site', 'Not linked:', '2019-07-23 23:51:02'),
(5738, 'en-us', 'smd_tag_not_updated', 'smd_tag', 'site', '{type} tag \"{name}\" NOT updated', '2019-07-23 23:51:02'),
(5739, 'en-us', 'smd_tag_no_desc', 'smd_tag', 'site', 'No description available', '2019-07-23 23:51:02'),
(5740, 'en-us', 'smd_tag_no_name', 'smd_tag', 'site', '{type} tag NOT created: needs a name', '2019-07-23 23:51:02'),
(5741, 'en-us', 'smd_tag_no_parent', 'smd_tag', 'site', 'No parent tags.', '2019-07-23 23:51:02'),
(5742, 'en-us', 'smd_tag_no_sections', 'smd_tag', 'site', 'No sections', '2019-07-23 23:51:02'),
(5743, 'en-us', 'smd_tag_no_tags', 'smd_tag', 'site', 'No {type} tags defined', '2019-07-23 23:51:02'),
(5744, 'en-us', 'smd_tag_parent_lbl', 'smd_tag', 'site', 'Parent', '2019-07-23 23:51:02'),
(5745, 'en-us', 'smd_tag_parent_linked_lbl', 'smd_tag', 'site', 'Assigned these tag(s) to \"{parent}\":', '2019-07-23 23:51:02'),
(5746, 'en-us', 'smd_tag_pool_lbl', 'smd_tag', 'site', 'Tag pool', '2019-07-23 23:51:02'),
(5747, 'en-us', 'smd_tag_prefs_installed', 'smd_tag', 'site', 'Preferences installed', '2019-07-23 23:51:02'),
(5748, 'en-us', 'smd_tag_prefs_not_installed', 'smd_tag', 'site', 'Preferences not installed.', '2019-07-23 23:51:02'),
(5749, 'en-us', 'smd_tag_prefs_p', 'smd_tag', 'site', 'Interface settings', '2019-07-23 23:51:02'),
(5750, 'en-us', 'smd_tag_prefs_pane', 'smd_tag', 'site', 'Prefs', '2019-07-23 23:51:02'),
(5751, 'en-us', 'smd_tag_prefs_removed', 'smd_tag', 'site', 'Preferences removed', '2019-07-23 23:51:02'),
(5752, 'en-us', 'smd_tag_prefs_some', 'smd_tag', 'site', 'Not all preferences available.', '2019-07-23 23:51:02'),
(5753, 'en-us', 'smd_tag_prefs_some_explain', 'smd_tag', 'site', 'This is either a new installation or a different version<br />of the plugin to one you had before.', '2019-07-23 23:51:02'),
(5754, 'en-us', 'smd_tag_prefs_some_opts1', 'smd_tag', 'site', 'Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.', '2019-07-23 23:51:02'),
(5755, 'en-us', 'smd_tag_prefs_some_opts2', 'smd_tag', 'site', 'Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.', '2019-07-23 23:51:02'),
(5756, 'en-us', 'smd_tag_prefs_some_tbl', 'smd_tag', 'site', 'Not all table info available.', '2019-07-23 23:51:02'),
(5757, 'en-us', 'smd_tag_prefs_t', 'smd_tag', 'site', 'Tag management', '2019-07-23 23:51:02'),
(5758, 'en-us', 'smd_tag_prefs_title', 'smd_tag', 'site', 'Tag Preferences', '2019-07-23 23:51:02'),
(5759, 'en-us', 'smd_tag_prefs_u', 'smd_tag', 'site', 'URL management', '2019-07-23 23:51:02'),
(5760, 'en-us', 'smd_tag_pref_install_lbl', 'smd_tag', 'site', 'Install prefs', '2019-07-23 23:51:02'),
(5761, 'en-us', 'smd_tag_pref_remove_lbl', 'smd_tag', 'site', 'Remove prefs', '2019-07-23 23:51:02'),
(5762, 'en-us', 'smd_tag_pref_show_lbl', 'smd_tag', 'site', 'Preferences', '2019-07-23 23:51:02'),
(5763, 'en-us', 'smd_tag_p_enable', 'smd_tag', 'site', 'Enable tags in', '2019-07-23 23:51:02'),
(5764, 'en-us', 'smd_tag_p_input', 'smd_tag', 'site', 'Enter tags using', '2019-07-23 23:51:02'),
(5765, 'en-us', 'smd_tag_p_lbi', 'smd_tag', 'site', 'Bi-directional tag trees', '2019-07-23 23:51:02'),
(5766, 'en-us', 'smd_tag_p_linkcat', 'smd_tag', 'site', 'Link tags to categories', '2019-07-23 23:51:02'),
(5767, 'en-us', 'smd_tag_p_listpar', 'smd_tag', 'site', 'Permit parent tag selection', '2019-07-23 23:51:02'),
(5768, 'en-us', 'smd_tag_p_master', 'smd_tag', 'site', 'Master parent tag', '2019-07-23 23:51:02'),
(5769, 'en-us', 'smd_tag_p_qtag', 'smd_tag', 'site', 'Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)', '2019-07-23 23:51:02'),
(5770, 'en-us', 'smd_tag_p_qtpath', 'smd_tag', 'site', 'JS plugin dir', '2019-07-23 23:51:02'),
(5771, 'en-us', 'smd_tag_p_qtstyl', 'smd_tag', 'site', 'JS style dir', '2019-07-23 23:51:02'),
(5772, 'en-us', 'smd_tag_p_size', 'smd_tag', 'site', 'Select/textarea rows', '2019-07-23 23:51:02'),
(5773, 'en-us', 'smd_tag_recent_report', 'smd_tag', 'site', 'Display recent report', '2019-07-23 23:51:02'),
(5774, 'en-us', 'smd_tag_report_lbl', 'smd_tag', 'site', 'Report: smd_tags', '2019-07-23 23:51:02'),
(5775, 'en-us', 'smd_tag_rss_uc_only', 'smd_tag', 'site', '(ignored for tru_tags)', '2019-07-23 23:51:02'),
(5776, 'en-us', 'smd_tag_rss_uc_tru_tags_only', 'smd_tag', 'site', '(ignored for Textpattern cats)', '2019-07-23 23:51:02'),
(5777, 'en-us', 'smd_tag_sel_list', 'smd_tag', 'site', 'Select list', '2019-07-23 23:51:02'),
(5778, 'en-us', 'smd_tag_sync_cfs_delim_lbl', 'smd_tag', 'site', 'Custom field delimiter', '2019-07-23 23:51:02'),
(5779, 'en-us', 'smd_tag_sync_cfs_lbl', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5780, 'en-us', 'smd_tag_sync_delete_orig_lbl', 'smd_tag', 'site', 'Delete original', '2019-07-23 23:51:02'),
(5781, 'en-us', 'smd_tag_sync_force_cat_lbl', 'smd_tag', 'site', 'Force category link if tag already exists', '2019-07-23 23:51:02'),
(5782, 'en-us', 'smd_tag_sync_force_parent_lbl', 'smd_tag', 'site', 'Force parent if tag already exists', '2019-07-23 23:51:02'),
(5783, 'en-us', 'smd_tag_sync_import_opts', 'smd_tag', 'site', 'Import options', '2019-07-23 23:51:02'),
(5784, 'en-us', 'smd_tag_sync_lbl', 'smd_tag', 'site', 'Import tags', '2019-07-23 23:51:02'),
(5785, 'en-us', 'smd_tag_sync_link_cat_lbl', 'smd_tag', 'site', 'Link tags to category', '2019-07-23 23:51:02'),
(5786, 'en-us', 'smd_tag_sync_parent_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5787, 'en-us', 'smd_tag_sync_parent_lbl', 'smd_tag', 'site', 'Start from parent category', '2019-07-23 23:51:02'),
(5788, 'en-us', 'smd_tag_sync_parent_tag_lbl', 'smd_tag', 'site', 'Assign to parent tag', '2019-07-23 23:51:02'),
(5789, 'en-us', 'smd_tag_sync_plugin_opts', 'smd_tag', 'site', 'Source options', '2019-07-23 23:51:02'),
(5790, 'en-us', 'smd_tag_sync_plugs_not_installed', 'smd_tag', 'site', 'tru_tags or rss_unlimited_categories must be installed and activated to import', '2019-07-23 23:51:02'),
(5791, 'en-us', 'smd_tag_sync_section_lbl', 'smd_tag', 'site', 'Articles from section', '2019-07-23 23:51:02'),
(5792, 'en-us', 'smd_tag_sync_txpcats_lbl', 'smd_tag', 'site', 'Textpattern category', '2019-07-23 23:51:02'),
(5793, 'en-us', 'smd_tag_sync_type1', 'smd_tag', 'site', 'tru_tags', '2019-07-23 23:51:02'),
(5794, 'en-us', 'smd_tag_sync_type2', 'smd_tag', 'site', 'rss_unlimited_categories', '2019-07-23 23:51:02'),
(5795, 'en-us', 'smd_tag_sync_type3', 'smd_tag', 'site', 'Textpattern categories', '2019-07-23 23:51:02'),
(5796, 'en-us', 'smd_tag_sync_type4', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5797, 'en-us', 'smd_tag_sync_type_lbl', 'smd_tag', 'site', 'Import from', '2019-07-23 23:51:02'),
(5798, 'en-us', 'smd_tag_tags_pane', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5799, 'en-us', 'smd_tag_tag_search', 'smd_tag', 'site', 'Tag search', '2019-07-23 23:51:02'),
(5800, 'en-us', 'smd_tag_tbl_installed', 'smd_tag', 'site', 'Tag tables installed.', '2019-07-23 23:51:02'),
(5801, 'en-us', 'smd_tag_tbl_install_lbl', 'smd_tag', 'site', 'Install tables', '2019-07-23 23:51:02'),
(5802, 'en-us', 'smd_tag_tbl_not_installed', 'smd_tag', 'site', 'Tag tables NOT installed.', '2019-07-23 23:51:02'),
(5803, 'en-us', 'smd_tag_tbl_not_removed', 'smd_tag', 'site', 'Tag tables NOT removed.', '2019-07-23 23:51:02'),
(5804, 'en-us', 'smd_tag_tbl_rebuild_lbl', 'smd_tag', 'site', 'Rebuild tags', '2019-07-23 23:51:02'),
(5805, 'en-us', 'smd_tag_tbl_rebuilt', 'smd_tag', 'site', 'Tag tables rebuilt', '2019-07-23 23:51:02'),
(5806, 'en-us', 'smd_tag_tbl_removed', 'smd_tag', 'site', 'Tag tables removed.', '2019-07-23 23:51:02'),
(5807, 'en-us', 'smd_tag_tbl_remove_lbl', 'smd_tag', 'site', 'Remove tables', '2019-07-23 23:51:02'),
(5808, 'en-us', 'smd_tag_textbox', 'smd_tag', 'site', 'Text area', '2019-07-23 23:51:02'),
(5809, 'en-us', 'smd_tag_textboxplus', 'smd_tag', 'site', 'Text area+', '2019-07-23 23:51:02'),
(5810, 'en-us', 'smd_tag_textlist', 'smd_tag', 'site', 'Text list', '2019-07-23 23:51:02'),
(5811, 'en-us', 'smd_tag_title', 'smd_tag', 'site', 'Tags', '2019-07-23 23:51:02'),
(5812, 'en-us', 'smd_tag_toggle', 'smd_tag', 'site', 'tog', '2019-07-23 23:51:02'),
(5813, 'en-us', 'smd_tag_t_astart', 'smd_tag', 'site', 'Initial pane', '2019-07-23 23:51:02'),
(5814, 'en-us', 'smd_tag_t_auto', 'smd_tag', 'site', 'Auto name', '2019-07-23 23:51:02'),
(5815, 'en-us', 'smd_tag_t_cols', 'smd_tag', 'site', 'Tag layout', '2019-07-23 23:51:02'),
(5816, 'en-us', 'smd_tag_t_colsord', 'smd_tag', 'site', 'Order tags by', '2019-07-23 23:51:02'),
(5817, 'en-us', 'smd_tag_t_count', 'smd_tag', 'site', 'Show tag usage counts', '2019-07-23 23:51:02'),
(5818, 'en-us', 'smd_tag_t_deltree', 'smd_tag', 'site', 'When deleting a parent', '2019-07-23 23:51:02'),
(5819, 'en-us', 'smd_tag_t_delused', 'smd_tag', 'site', 'Allow deletion of used tags', '2019-07-23 23:51:02'),
(5820, 'en-us', 'smd_tag_t_desc_textile', 'smd_tag', 'site', 'Textile description', '2019-07-23 23:51:02'),
(5821, 'en-us', 'smd_tag_t_desc_tooltip', 'smd_tag', 'site', 'Show description as tooltip', '2019-07-23 23:51:02'),
(5822, 'en-us', 'smd_tag_t_enrep', 'smd_tag', 'site', 'Automatically display reports', '2019-07-23 23:51:02'),
(5823, 'en-us', 'smd_tag_t_hilite', 'smd_tag', 'site', 'Clicked RGB colour', '2019-07-23 23:51:02'),
(5824, 'en-us', 'smd_tag_t_hover', 'smd_tag', 'site', 'Mouse-over RGB colour', '2019-07-23 23:51:02'),
(5825, 'en-us', 'smd_tag_t_indent', 'smd_tag', 'site', 'Sub-tag level indicator', '2019-07-23 23:51:02'),
(5826, 'en-us', 'smd_tag_t_mdelim', 'smd_tag', 'site', 'Multi-tag delimiter (1 char)', '2019-07-23 23:51:02'),
(5827, 'en-us', 'smd_tag_t_size_desc', 'smd_tag', 'site', 'Description width, height', '2019-07-23 23:51:02'),
(5828, 'en-us', 'smd_tag_unable_to_create', 'smd_tag', 'site', 'cannot create', '2019-07-23 23:51:02'),
(5829, 'en-us', 'smd_tag_updated', 'smd_tag', 'site', '{type} tag \"{name}\" updated', '2019-07-23 23:51:02'),
(5830, 'en-us', 'smd_tag_u_combi', 'smd_tag', 'site', 'Use tag combinations', '2019-07-23 23:51:02'),
(5831, 'en-us', 'smd_tag_u_combi_and', 'smd_tag', 'site', 'AND combinator char', '2019-07-23 23:51:02'),
(5832, 'en-us', 'smd_tag_u_combi_or', 'smd_tag', 'site', 'OR combinator char', '2019-07-23 23:51:02'),
(5833, 'en-us', 'smd_tag_u_pnam', 'smd_tag', 'site', 'URL name parameter', '2019-07-23 23:51:02'),
(5834, 'en-us', 'smd_tag_u_ptyp', 'smd_tag', 'site', 'URL type parameter', '2019-07-23 23:51:02'),
(5835, 'en-us', 'smd_tag_u_sec', 'smd_tag', 'site', 'Trigger(s) for tag lists', '2019-07-23 23:51:02'),
(5836, 'en-us', 'smd_tag_with_filtered', 'smd_tag', 'site', 'With filtered:', '2019-07-23 23:51:02'),
(5837, 'el', 'smd_tags', 'smd_tag', 'site', 'Tags (smd)', '2019-07-23 23:51:02'),
(5838, 'el', 'smd_tag_ac_std', 'smd_tag', 'site', 'Standard', '2019-07-23 23:51:02'),
(5839, 'el', 'smd_tag_ac_str', 'smd_tag', 'site', 'Strict', '2019-07-23 23:51:02'),
(5840, 'el', 'smd_tag_all_lbl', 'smd_tag', 'site', 'All', '2019-07-23 23:51:02'),
(5841, 'el', 'smd_tag_already_exists', 'smd_tag', 'site', 'already exists', '2019-07-23 23:51:02'),
(5842, 'el', 'smd_tag_assign_parent_lbl', 'smd_tag', 'site', 'Assign parent', '2019-07-23 23:51:02'),
(5843, 'el', 'smd_tag_bi_both', 'smd_tag', 'site', 'Both', '2019-07-23 23:51:02'),
(5844, 'el', 'smd_tag_bi_down', 'smd_tag', 'site', 'Down', '2019-07-23 23:51:02'),
(5845, 'el', 'smd_tag_bi_up', 'smd_tag', 'site', 'Up', '2019-07-23 23:51:02'),
(5846, 'el', 'smd_tag_by_column', 'smd_tag', 'site', 'Column', '2019-07-23 23:51:02'),
(5847, 'el', 'smd_tag_by_row', 'smd_tag', 'site', 'Row', '2019-07-23 23:51:02'),
(5848, 'el', 'smd_tag_category_linked_lbl', 'smd_tag', 'site', 'Linked to category \"{category}\":', '2019-07-23 23:51:02'),
(5849, 'el', 'smd_tag_category_unlinked_lbl', 'smd_tag', 'site', 'Unlinked:', '2019-07-23 23:51:02'),
(5850, 'el', 'smd_tag_children_and', 'smd_tag', 'site', 'and its children', '2019-07-23 23:51:02'),
(5851, 'el', 'smd_tag_children_del', 'smd_tag', 'site', 'Delete children', '2019-07-23 23:51:02'),
(5852, 'el', 'smd_tag_children_pro', 'smd_tag', 'site', 'Promote children', '2019-07-23 23:51:02'),
(5853, 'el', 'smd_tag_clear', 'smd_tag', 'site', 'clr', '2019-07-23 23:51:02'),
(5854, 'el', 'smd_tag_clink_lbl', 'smd_tag', 'site', 'Linked cat', '2019-07-23 23:51:02'),
(5855, 'el', 'smd_tag_count_lbl', 'smd_tag', 'site', 'Matched tags:', '2019-07-23 23:51:02'),
(5856, 'el', 'smd_tag_created', 'smd_tag', 'site', '{type} tag \"{name}\" created', '2019-07-23 23:51:02'),
(5857, 'el', 'smd_tag_created_rep_lbl', 'smd_tag', 'site', 'Created tags:', '2019-07-23 23:51:02'),
(5858, 'el', 'smd_tag_create_lbl', 'smd_tag', 'site', 'Create/Edit tag', '2019-07-23 23:51:02'),
(5859, 'el', 'smd_tag_deleted', 'smd_tag', 'site', '{type} tag \"{name}\" deleted', '2019-07-23 23:51:02'),
(5860, 'el', 'smd_tag_deleted_lbl', 'smd_tag', 'site', 'Deleted tags:', '2019-07-23 23:51:02'),
(5861, 'el', 'smd_tag_err_in_use_lbl', 'smd_tag', 'site', 'Tags in use:', '2019-07-23 23:51:02'),
(5862, 'el', 'smd_tag_err_orphaned_lbl', 'smd_tag', 'site', 'Orphaned tags:', '2019-07-23 23:51:02'),
(5863, 'el', 'smd_tag_exists', 'smd_tag', 'site', '{type} tag \"{name}\" already exists', '2019-07-23 23:51:02'),
(5864, 'el', 'smd_tag_extras', 'smd_tag', 'site', 'More...', '2019-07-23 23:51:02'),
(5865, 'el', 'smd_tag_import_results_pt1', 'smd_tag', 'site', 'Articles:', '2019-07-23 23:51:02'),
(5866, 'el', 'smd_tag_import_results_pt2', 'smd_tag', 'site', 'Tags linked:', '2019-07-23 23:51:02'),
(5867, 'el', 'smd_tag_in_use', 'smd_tag', 'site', '{type} tag \"{name}\" is in use', '2019-07-23 23:51:02'),
(5868, 'el', 'smd_tag_link_to_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5869, 'el', 'smd_tag_manage_lbl', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5870, 'el', 'smd_tag_missing_rep_lbl', 'smd_tag', 'site', 'Missing tags:', '2019-07-23 23:51:02'),
(5871, 'el', 'smd_tag_multi_delete', 'smd_tag', 'site', 'Tags deleted: {number} (see report for details)', '2019-07-23 23:51:02'),
(5872, 'el', 'smd_tag_not_available', 'smd_tag', 'site', 'Tables are not installed', '2019-07-23 23:51:02'),
(5873, 'el', 'smd_tag_not_created', 'smd_tag', 'site', '{type} tag \"{name}\" NOT created', '2019-07-23 23:51:02'),
(5874, 'el', 'smd_tag_not_created_rep_lbl', 'smd_tag', 'site', 'Tags NOT created:', '2019-07-23 23:51:02'),
(5875, 'el', 'smd_tag_not_parent_linked_lbl', 'smd_tag', 'site', 'Not linked:', '2019-07-23 23:51:02'),
(5876, 'el', 'smd_tag_not_updated', 'smd_tag', 'site', '{type} tag \"{name}\" NOT updated', '2019-07-23 23:51:02'),
(5877, 'el', 'smd_tag_no_desc', 'smd_tag', 'site', 'No description available', '2019-07-23 23:51:02'),
(5878, 'el', 'smd_tag_no_name', 'smd_tag', 'site', '{type} tag NOT created: needs a name', '2019-07-23 23:51:02'),
(5879, 'el', 'smd_tag_no_parent', 'smd_tag', 'site', 'No parent tags.', '2019-07-23 23:51:02'),
(5880, 'el', 'smd_tag_no_sections', 'smd_tag', 'site', 'No sections', '2019-07-23 23:51:02'),
(5881, 'el', 'smd_tag_no_tags', 'smd_tag', 'site', 'No {type} tags defined', '2019-07-23 23:51:02'),
(5882, 'el', 'smd_tag_parent_lbl', 'smd_tag', 'site', 'Parent', '2019-07-23 23:51:02'),
(5883, 'el', 'smd_tag_parent_linked_lbl', 'smd_tag', 'site', 'Assigned these tag(s) to \"{parent}\":', '2019-07-23 23:51:02'),
(5884, 'el', 'smd_tag_pool_lbl', 'smd_tag', 'site', 'Tag pool', '2019-07-23 23:51:02'),
(5885, 'el', 'smd_tag_prefs_installed', 'smd_tag', 'site', 'Preferences installed', '2019-07-23 23:51:02'),
(5886, 'el', 'smd_tag_prefs_not_installed', 'smd_tag', 'site', 'Preferences not installed.', '2019-07-23 23:51:02'),
(5887, 'el', 'smd_tag_prefs_p', 'smd_tag', 'site', 'Interface settings', '2019-07-23 23:51:02'),
(5888, 'el', 'smd_tag_prefs_pane', 'smd_tag', 'site', 'Prefs', '2019-07-23 23:51:02'),
(5889, 'el', 'smd_tag_prefs_removed', 'smd_tag', 'site', 'Preferences removed', '2019-07-23 23:51:02'),
(5890, 'el', 'smd_tag_prefs_some', 'smd_tag', 'site', 'Not all preferences available.', '2019-07-23 23:51:02'),
(5891, 'el', 'smd_tag_prefs_some_explain', 'smd_tag', 'site', 'This is either a new installation or a different version<br />of the plugin to one you had before.', '2019-07-23 23:51:02'),
(5892, 'el', 'smd_tag_prefs_some_opts1', 'smd_tag', 'site', 'Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.', '2019-07-23 23:51:02'),
(5893, 'el', 'smd_tag_prefs_some_opts2', 'smd_tag', 'site', 'Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.', '2019-07-23 23:51:02'),
(5894, 'el', 'smd_tag_prefs_some_tbl', 'smd_tag', 'site', 'Not all table info available.', '2019-07-23 23:51:02'),
(5895, 'el', 'smd_tag_prefs_t', 'smd_tag', 'site', 'Tag management', '2019-07-23 23:51:02'),
(5896, 'el', 'smd_tag_prefs_title', 'smd_tag', 'site', 'Tag Preferences', '2019-07-23 23:51:02'),
(5897, 'el', 'smd_tag_prefs_u', 'smd_tag', 'site', 'URL management', '2019-07-23 23:51:02'),
(5898, 'el', 'smd_tag_pref_install_lbl', 'smd_tag', 'site', 'Install prefs', '2019-07-23 23:51:02'),
(5899, 'el', 'smd_tag_pref_remove_lbl', 'smd_tag', 'site', 'Remove prefs', '2019-07-23 23:51:02'),
(5900, 'el', 'smd_tag_pref_show_lbl', 'smd_tag', 'site', 'Preferences', '2019-07-23 23:51:02'),
(5901, 'el', 'smd_tag_p_enable', 'smd_tag', 'site', 'Enable tags in', '2019-07-23 23:51:02'),
(5902, 'el', 'smd_tag_p_input', 'smd_tag', 'site', 'Enter tags using', '2019-07-23 23:51:02'),
(5903, 'el', 'smd_tag_p_lbi', 'smd_tag', 'site', 'Bi-directional tag trees', '2019-07-23 23:51:02'),
(5904, 'el', 'smd_tag_p_linkcat', 'smd_tag', 'site', 'Link tags to categories', '2019-07-23 23:51:02'),
(5905, 'el', 'smd_tag_p_listpar', 'smd_tag', 'site', 'Permit parent tag selection', '2019-07-23 23:51:02'),
(5906, 'el', 'smd_tag_p_master', 'smd_tag', 'site', 'Master parent tag', '2019-07-23 23:51:02');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(5907, 'el', 'smd_tag_p_qtag', 'smd_tag', 'site', 'Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)', '2019-07-23 23:51:02'),
(5908, 'el', 'smd_tag_p_qtpath', 'smd_tag', 'site', 'JS plugin dir', '2019-07-23 23:51:02'),
(5909, 'el', 'smd_tag_p_qtstyl', 'smd_tag', 'site', 'JS style dir', '2019-07-23 23:51:02'),
(5910, 'el', 'smd_tag_p_size', 'smd_tag', 'site', 'Select/textarea rows', '2019-07-23 23:51:02'),
(5911, 'el', 'smd_tag_recent_report', 'smd_tag', 'site', 'Display recent report', '2019-07-23 23:51:02'),
(5912, 'el', 'smd_tag_report_lbl', 'smd_tag', 'site', 'Report: smd_tags', '2019-07-23 23:51:02'),
(5913, 'el', 'smd_tag_rss_uc_only', 'smd_tag', 'site', '(ignored for tru_tags)', '2019-07-23 23:51:02'),
(5914, 'el', 'smd_tag_rss_uc_tru_tags_only', 'smd_tag', 'site', '(ignored for Textpattern cats)', '2019-07-23 23:51:02'),
(5915, 'el', 'smd_tag_sel_list', 'smd_tag', 'site', 'Select list', '2019-07-23 23:51:02'),
(5916, 'el', 'smd_tag_sync_cfs_delim_lbl', 'smd_tag', 'site', 'Custom field delimiter', '2019-07-23 23:51:02'),
(5917, 'el', 'smd_tag_sync_cfs_lbl', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5918, 'el', 'smd_tag_sync_delete_orig_lbl', 'smd_tag', 'site', 'Delete original', '2019-07-23 23:51:02'),
(5919, 'el', 'smd_tag_sync_force_cat_lbl', 'smd_tag', 'site', 'Force category link if tag already exists', '2019-07-23 23:51:02'),
(5920, 'el', 'smd_tag_sync_force_parent_lbl', 'smd_tag', 'site', 'Force parent if tag already exists', '2019-07-23 23:51:02'),
(5921, 'el', 'smd_tag_sync_import_opts', 'smd_tag', 'site', 'Import options', '2019-07-23 23:51:02'),
(5922, 'el', 'smd_tag_sync_lbl', 'smd_tag', 'site', 'Import tags', '2019-07-23 23:51:02'),
(5923, 'el', 'smd_tag_sync_link_cat_lbl', 'smd_tag', 'site', 'Link tags to category', '2019-07-23 23:51:02'),
(5924, 'el', 'smd_tag_sync_parent_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(5925, 'el', 'smd_tag_sync_parent_lbl', 'smd_tag', 'site', 'Start from parent category', '2019-07-23 23:51:02'),
(5926, 'el', 'smd_tag_sync_parent_tag_lbl', 'smd_tag', 'site', 'Assign to parent tag', '2019-07-23 23:51:02'),
(5927, 'el', 'smd_tag_sync_plugin_opts', 'smd_tag', 'site', 'Source options', '2019-07-23 23:51:02'),
(5928, 'el', 'smd_tag_sync_plugs_not_installed', 'smd_tag', 'site', 'tru_tags or rss_unlimited_categories must be installed and activated to import', '2019-07-23 23:51:02'),
(5929, 'el', 'smd_tag_sync_section_lbl', 'smd_tag', 'site', 'Articles from section', '2019-07-23 23:51:02'),
(5930, 'el', 'smd_tag_sync_txpcats_lbl', 'smd_tag', 'site', 'Textpattern category', '2019-07-23 23:51:02'),
(5931, 'el', 'smd_tag_sync_type1', 'smd_tag', 'site', 'tru_tags', '2019-07-23 23:51:02'),
(5932, 'el', 'smd_tag_sync_type2', 'smd_tag', 'site', 'rss_unlimited_categories', '2019-07-23 23:51:02'),
(5933, 'el', 'smd_tag_sync_type3', 'smd_tag', 'site', 'Textpattern categories', '2019-07-23 23:51:02'),
(5934, 'el', 'smd_tag_sync_type4', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(5935, 'el', 'smd_tag_sync_type_lbl', 'smd_tag', 'site', 'Import from', '2019-07-23 23:51:02'),
(5936, 'el', 'smd_tag_tags_pane', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(5937, 'el', 'smd_tag_tag_search', 'smd_tag', 'site', 'Tag search', '2019-07-23 23:51:02'),
(5938, 'el', 'smd_tag_tbl_installed', 'smd_tag', 'site', 'Tag tables installed.', '2019-07-23 23:51:02'),
(5939, 'el', 'smd_tag_tbl_install_lbl', 'smd_tag', 'site', 'Install tables', '2019-07-23 23:51:02'),
(5940, 'el', 'smd_tag_tbl_not_installed', 'smd_tag', 'site', 'Tag tables NOT installed.', '2019-07-23 23:51:02'),
(5941, 'el', 'smd_tag_tbl_not_removed', 'smd_tag', 'site', 'Tag tables NOT removed.', '2019-07-23 23:51:02'),
(5942, 'el', 'smd_tag_tbl_rebuild_lbl', 'smd_tag', 'site', 'Rebuild tags', '2019-07-23 23:51:02'),
(5943, 'el', 'smd_tag_tbl_rebuilt', 'smd_tag', 'site', 'Tag tables rebuilt', '2019-07-23 23:51:02'),
(5944, 'el', 'smd_tag_tbl_removed', 'smd_tag', 'site', 'Tag tables removed.', '2019-07-23 23:51:02'),
(5945, 'el', 'smd_tag_tbl_remove_lbl', 'smd_tag', 'site', 'Remove tables', '2019-07-23 23:51:02'),
(5946, 'el', 'smd_tag_textbox', 'smd_tag', 'site', 'Text area', '2019-07-23 23:51:02'),
(5947, 'el', 'smd_tag_textboxplus', 'smd_tag', 'site', 'Text area+', '2019-07-23 23:51:02'),
(5948, 'el', 'smd_tag_textlist', 'smd_tag', 'site', 'Text list', '2019-07-23 23:51:02'),
(5949, 'el', 'smd_tag_title', 'smd_tag', 'site', 'Tags', '2019-07-23 23:51:02'),
(5950, 'el', 'smd_tag_toggle', 'smd_tag', 'site', 'tog', '2019-07-23 23:51:02'),
(5951, 'el', 'smd_tag_t_astart', 'smd_tag', 'site', 'Initial pane', '2019-07-23 23:51:02'),
(5952, 'el', 'smd_tag_t_auto', 'smd_tag', 'site', 'Auto name', '2019-07-23 23:51:02'),
(5953, 'el', 'smd_tag_t_cols', 'smd_tag', 'site', 'Tag layout', '2019-07-23 23:51:02'),
(5954, 'el', 'smd_tag_t_colsord', 'smd_tag', 'site', 'Order tags by', '2019-07-23 23:51:02'),
(5955, 'el', 'smd_tag_t_count', 'smd_tag', 'site', 'Show tag usage counts', '2019-07-23 23:51:02'),
(5956, 'el', 'smd_tag_t_deltree', 'smd_tag', 'site', 'When deleting a parent', '2019-07-23 23:51:02'),
(5957, 'el', 'smd_tag_t_delused', 'smd_tag', 'site', 'Allow deletion of used tags', '2019-07-23 23:51:02'),
(5958, 'el', 'smd_tag_t_desc_textile', 'smd_tag', 'site', 'Textile description', '2019-07-23 23:51:02'),
(5959, 'el', 'smd_tag_t_desc_tooltip', 'smd_tag', 'site', 'Show description as tooltip', '2019-07-23 23:51:02'),
(5960, 'el', 'smd_tag_t_enrep', 'smd_tag', 'site', 'Automatically display reports', '2019-07-23 23:51:02'),
(5961, 'el', 'smd_tag_t_hilite', 'smd_tag', 'site', 'Clicked RGB colour', '2019-07-23 23:51:02'),
(5962, 'el', 'smd_tag_t_hover', 'smd_tag', 'site', 'Mouse-over RGB colour', '2019-07-23 23:51:02'),
(5963, 'el', 'smd_tag_t_indent', 'smd_tag', 'site', 'Sub-tag level indicator', '2019-07-23 23:51:02'),
(5964, 'el', 'smd_tag_t_mdelim', 'smd_tag', 'site', 'Multi-tag delimiter (1 char)', '2019-07-23 23:51:02'),
(5965, 'el', 'smd_tag_t_size_desc', 'smd_tag', 'site', 'Description width, height', '2019-07-23 23:51:02'),
(5966, 'el', 'smd_tag_unable_to_create', 'smd_tag', 'site', 'cannot create', '2019-07-23 23:51:02'),
(5967, 'el', 'smd_tag_updated', 'smd_tag', 'site', '{type} tag \"{name}\" updated', '2019-07-23 23:51:02'),
(5968, 'el', 'smd_tag_u_combi', 'smd_tag', 'site', 'Use tag combinations', '2019-07-23 23:51:02'),
(5969, 'el', 'smd_tag_u_combi_and', 'smd_tag', 'site', 'AND combinator char', '2019-07-23 23:51:02'),
(5970, 'el', 'smd_tag_u_combi_or', 'smd_tag', 'site', 'OR combinator char', '2019-07-23 23:51:02'),
(5971, 'el', 'smd_tag_u_pnam', 'smd_tag', 'site', 'URL name parameter', '2019-07-23 23:51:02'),
(5972, 'el', 'smd_tag_u_ptyp', 'smd_tag', 'site', 'URL type parameter', '2019-07-23 23:51:02'),
(5973, 'el', 'smd_tag_u_sec', 'smd_tag', 'site', 'Trigger(s) for tag lists', '2019-07-23 23:51:02'),
(5974, 'el', 'smd_tag_with_filtered', 'smd_tag', 'site', 'With filtered:', '2019-07-23 23:51:02'),
(5975, 'en', 'smd_tags', 'smd_tag', 'site', 'Tags (smd)', '2019-07-23 23:51:02'),
(5976, 'en', 'smd_tag_ac_std', 'smd_tag', 'site', 'Standard', '2019-07-23 23:51:02'),
(5977, 'en', 'smd_tag_ac_str', 'smd_tag', 'site', 'Strict', '2019-07-23 23:51:02'),
(5978, 'en', 'smd_tag_all_lbl', 'smd_tag', 'site', 'All', '2019-07-23 23:51:02'),
(5979, 'en', 'smd_tag_already_exists', 'smd_tag', 'site', 'already exists', '2019-07-23 23:51:02'),
(5980, 'en', 'smd_tag_assign_parent_lbl', 'smd_tag', 'site', 'Assign parent', '2019-07-23 23:51:02'),
(5981, 'en', 'smd_tag_bi_both', 'smd_tag', 'site', 'Both', '2019-07-23 23:51:02'),
(5982, 'en', 'smd_tag_bi_down', 'smd_tag', 'site', 'Down', '2019-07-23 23:51:02'),
(5983, 'en', 'smd_tag_bi_up', 'smd_tag', 'site', 'Up', '2019-07-23 23:51:02'),
(5984, 'en', 'smd_tag_by_column', 'smd_tag', 'site', 'Column', '2019-07-23 23:51:02'),
(5985, 'en', 'smd_tag_by_row', 'smd_tag', 'site', 'Row', '2019-07-23 23:51:02'),
(5986, 'en', 'smd_tag_category_linked_lbl', 'smd_tag', 'site', 'Linked to category \"{category}\":', '2019-07-23 23:51:02'),
(5987, 'en', 'smd_tag_category_unlinked_lbl', 'smd_tag', 'site', 'Unlinked:', '2019-07-23 23:51:02'),
(5988, 'en', 'smd_tag_children_and', 'smd_tag', 'site', 'and its children', '2019-07-23 23:51:02'),
(5989, 'en', 'smd_tag_children_del', 'smd_tag', 'site', 'Delete children', '2019-07-23 23:51:02'),
(5990, 'en', 'smd_tag_children_pro', 'smd_tag', 'site', 'Promote children', '2019-07-23 23:51:02'),
(5991, 'en', 'smd_tag_clear', 'smd_tag', 'site', 'clr', '2019-07-23 23:51:02'),
(5992, 'en', 'smd_tag_clink_lbl', 'smd_tag', 'site', 'Linked cat', '2019-07-23 23:51:02'),
(5993, 'en', 'smd_tag_count_lbl', 'smd_tag', 'site', 'Matched tags:', '2019-07-23 23:51:02'),
(5994, 'en', 'smd_tag_created', 'smd_tag', 'site', '{type} tag \"{name}\" created', '2019-07-23 23:51:02'),
(5995, 'en', 'smd_tag_created_rep_lbl', 'smd_tag', 'site', 'Created tags:', '2019-07-23 23:51:02'),
(5996, 'en', 'smd_tag_create_lbl', 'smd_tag', 'site', 'Create/Edit tag', '2019-07-23 23:51:02'),
(5997, 'en', 'smd_tag_deleted', 'smd_tag', 'site', '{type} tag \"{name}\" deleted', '2019-07-23 23:51:02'),
(5998, 'en', 'smd_tag_deleted_lbl', 'smd_tag', 'site', 'Deleted tags:', '2019-07-23 23:51:02'),
(5999, 'en', 'smd_tag_err_in_use_lbl', 'smd_tag', 'site', 'Tags in use:', '2019-07-23 23:51:02'),
(6000, 'en', 'smd_tag_err_orphaned_lbl', 'smd_tag', 'site', 'Orphaned tags:', '2019-07-23 23:51:02'),
(6001, 'en', 'smd_tag_exists', 'smd_tag', 'site', '{type} tag \"{name}\" already exists', '2019-07-23 23:51:02'),
(6002, 'en', 'smd_tag_extras', 'smd_tag', 'site', 'More...', '2019-07-23 23:51:02'),
(6003, 'en', 'smd_tag_import_results_pt1', 'smd_tag', 'site', 'Articles:', '2019-07-23 23:51:02'),
(6004, 'en', 'smd_tag_import_results_pt2', 'smd_tag', 'site', 'Tags linked:', '2019-07-23 23:51:02'),
(6005, 'en', 'smd_tag_in_use', 'smd_tag', 'site', '{type} tag \"{name}\" is in use', '2019-07-23 23:51:02'),
(6006, 'en', 'smd_tag_link_to_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(6007, 'en', 'smd_tag_manage_lbl', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(6008, 'en', 'smd_tag_missing_rep_lbl', 'smd_tag', 'site', 'Missing tags:', '2019-07-23 23:51:02'),
(6009, 'en', 'smd_tag_multi_delete', 'smd_tag', 'site', 'Tags deleted: {number} (see report for details)', '2019-07-23 23:51:02'),
(6010, 'en', 'smd_tag_not_available', 'smd_tag', 'site', 'Tables are not installed', '2019-07-23 23:51:02'),
(6011, 'en', 'smd_tag_not_created', 'smd_tag', 'site', '{type} tag \"{name}\" NOT created', '2019-07-23 23:51:02'),
(6012, 'en', 'smd_tag_not_created_rep_lbl', 'smd_tag', 'site', 'Tags NOT created:', '2019-07-23 23:51:02'),
(6013, 'en', 'smd_tag_not_parent_linked_lbl', 'smd_tag', 'site', 'Not linked:', '2019-07-23 23:51:02'),
(6014, 'en', 'smd_tag_not_updated', 'smd_tag', 'site', '{type} tag \"{name}\" NOT updated', '2019-07-23 23:51:02'),
(6015, 'en', 'smd_tag_no_desc', 'smd_tag', 'site', 'No description available', '2019-07-23 23:51:02'),
(6016, 'en', 'smd_tag_no_name', 'smd_tag', 'site', '{type} tag NOT created: needs a name', '2019-07-23 23:51:02'),
(6017, 'en', 'smd_tag_no_parent', 'smd_tag', 'site', 'No parent tags.', '2019-07-23 23:51:02'),
(6018, 'en', 'smd_tag_no_sections', 'smd_tag', 'site', 'No sections', '2019-07-23 23:51:02'),
(6019, 'en', 'smd_tag_no_tags', 'smd_tag', 'site', 'No {type} tags defined', '2019-07-23 23:51:02'),
(6020, 'en', 'smd_tag_parent_lbl', 'smd_tag', 'site', 'Parent', '2019-07-23 23:51:02'),
(6021, 'en', 'smd_tag_parent_linked_lbl', 'smd_tag', 'site', 'Assigned these tag(s) to \"{parent}\":', '2019-07-23 23:51:02'),
(6022, 'en', 'smd_tag_pool_lbl', 'smd_tag', 'site', 'Tag pool', '2019-07-23 23:51:02'),
(6023, 'en', 'smd_tag_prefs_installed', 'smd_tag', 'site', 'Preferences installed', '2019-07-23 23:51:02'),
(6024, 'en', 'smd_tag_prefs_not_installed', 'smd_tag', 'site', 'Preferences not installed.', '2019-07-23 23:51:02'),
(6025, 'en', 'smd_tag_prefs_p', 'smd_tag', 'site', 'Interface settings', '2019-07-23 23:51:02'),
(6026, 'en', 'smd_tag_prefs_pane', 'smd_tag', 'site', 'Prefs', '2019-07-23 23:51:02'),
(6027, 'en', 'smd_tag_prefs_removed', 'smd_tag', 'site', 'Preferences removed', '2019-07-23 23:51:02'),
(6028, 'en', 'smd_tag_prefs_some', 'smd_tag', 'site', 'Not all preferences available.', '2019-07-23 23:51:02'),
(6029, 'en', 'smd_tag_prefs_some_explain', 'smd_tag', 'site', 'This is either a new installation or a different version<br />of the plugin to one you had before.', '2019-07-23 23:51:02'),
(6030, 'en', 'smd_tag_prefs_some_opts1', 'smd_tag', 'site', 'Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.', '2019-07-23 23:51:02'),
(6031, 'en', 'smd_tag_prefs_some_opts2', 'smd_tag', 'site', 'Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.', '2019-07-23 23:51:02'),
(6032, 'en', 'smd_tag_prefs_some_tbl', 'smd_tag', 'site', 'Not all table info available.', '2019-07-23 23:51:02'),
(6033, 'en', 'smd_tag_prefs_t', 'smd_tag', 'site', 'Tag management', '2019-07-23 23:51:02'),
(6034, 'en', 'smd_tag_prefs_title', 'smd_tag', 'site', 'Tag Preferences', '2019-07-23 23:51:02'),
(6035, 'en', 'smd_tag_prefs_u', 'smd_tag', 'site', 'URL management', '2019-07-23 23:51:02'),
(6036, 'en', 'smd_tag_pref_install_lbl', 'smd_tag', 'site', 'Install prefs', '2019-07-23 23:51:02'),
(6037, 'en', 'smd_tag_pref_remove_lbl', 'smd_tag', 'site', 'Remove prefs', '2019-07-23 23:51:02'),
(6038, 'en', 'smd_tag_pref_show_lbl', 'smd_tag', 'site', 'Preferences', '2019-07-23 23:51:02'),
(6039, 'en', 'smd_tag_p_enable', 'smd_tag', 'site', 'Enable tags in', '2019-07-23 23:51:02'),
(6040, 'en', 'smd_tag_p_input', 'smd_tag', 'site', 'Enter tags using', '2019-07-23 23:51:02'),
(6041, 'en', 'smd_tag_p_lbi', 'smd_tag', 'site', 'Bi-directional tag trees', '2019-07-23 23:51:02'),
(6042, 'en', 'smd_tag_p_linkcat', 'smd_tag', 'site', 'Link tags to categories', '2019-07-23 23:51:02'),
(6043, 'en', 'smd_tag_p_listpar', 'smd_tag', 'site', 'Permit parent tag selection', '2019-07-23 23:51:02'),
(6044, 'en', 'smd_tag_p_master', 'smd_tag', 'site', 'Master parent tag', '2019-07-23 23:51:02'),
(6045, 'en', 'smd_tag_p_qtag', 'smd_tag', 'site', 'Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)', '2019-07-23 23:51:02'),
(6046, 'en', 'smd_tag_p_qtpath', 'smd_tag', 'site', 'JS plugin dir', '2019-07-23 23:51:02'),
(6047, 'en', 'smd_tag_p_qtstyl', 'smd_tag', 'site', 'JS style dir', '2019-07-23 23:51:02'),
(6048, 'en', 'smd_tag_p_size', 'smd_tag', 'site', 'Select/textarea rows', '2019-07-23 23:51:02'),
(6049, 'en', 'smd_tag_recent_report', 'smd_tag', 'site', 'Display recent report', '2019-07-23 23:51:02'),
(6050, 'en', 'smd_tag_report_lbl', 'smd_tag', 'site', 'Report: smd_tags', '2019-07-23 23:51:02'),
(6051, 'en', 'smd_tag_rss_uc_only', 'smd_tag', 'site', '(ignored for tru_tags)', '2019-07-23 23:51:02'),
(6052, 'en', 'smd_tag_rss_uc_tru_tags_only', 'smd_tag', 'site', '(ignored for Textpattern cats)', '2019-07-23 23:51:02'),
(6053, 'en', 'smd_tag_sel_list', 'smd_tag', 'site', 'Select list', '2019-07-23 23:51:02'),
(6054, 'en', 'smd_tag_sync_cfs_delim_lbl', 'smd_tag', 'site', 'Custom field delimiter', '2019-07-23 23:51:02'),
(6055, 'en', 'smd_tag_sync_cfs_lbl', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(6056, 'en', 'smd_tag_sync_delete_orig_lbl', 'smd_tag', 'site', 'Delete original', '2019-07-23 23:51:02'),
(6057, 'en', 'smd_tag_sync_force_cat_lbl', 'smd_tag', 'site', 'Force category link if tag already exists', '2019-07-23 23:51:02'),
(6058, 'en', 'smd_tag_sync_force_parent_lbl', 'smd_tag', 'site', 'Force parent if tag already exists', '2019-07-23 23:51:02'),
(6059, 'en', 'smd_tag_sync_import_opts', 'smd_tag', 'site', 'Import options', '2019-07-23 23:51:02'),
(6060, 'en', 'smd_tag_sync_lbl', 'smd_tag', 'site', 'Import tags', '2019-07-23 23:51:02'),
(6061, 'en', 'smd_tag_sync_link_cat_lbl', 'smd_tag', 'site', 'Link tags to category', '2019-07-23 23:51:02'),
(6062, 'en', 'smd_tag_sync_parent_cat_lbl', 'smd_tag', 'site', 'Link to category', '2019-07-23 23:51:02'),
(6063, 'en', 'smd_tag_sync_parent_lbl', 'smd_tag', 'site', 'Start from parent category', '2019-07-23 23:51:02'),
(6064, 'en', 'smd_tag_sync_parent_tag_lbl', 'smd_tag', 'site', 'Assign to parent tag', '2019-07-23 23:51:02'),
(6065, 'en', 'smd_tag_sync_plugin_opts', 'smd_tag', 'site', 'Source options', '2019-07-23 23:51:02'),
(6066, 'en', 'smd_tag_sync_plugs_not_installed', 'smd_tag', 'site', 'tru_tags or rss_unlimited_categories must be installed and activated to import', '2019-07-23 23:51:02'),
(6067, 'en', 'smd_tag_sync_section_lbl', 'smd_tag', 'site', 'Articles from section', '2019-07-23 23:51:02'),
(6068, 'en', 'smd_tag_sync_txpcats_lbl', 'smd_tag', 'site', 'Textpattern category', '2019-07-23 23:51:02'),
(6069, 'en', 'smd_tag_sync_type1', 'smd_tag', 'site', 'tru_tags', '2019-07-23 23:51:02'),
(6070, 'en', 'smd_tag_sync_type2', 'smd_tag', 'site', 'rss_unlimited_categories', '2019-07-23 23:51:02'),
(6071, 'en', 'smd_tag_sync_type3', 'smd_tag', 'site', 'Textpattern categories', '2019-07-23 23:51:02'),
(6072, 'en', 'smd_tag_sync_type4', 'smd_tag', 'site', 'Custom field', '2019-07-23 23:51:02'),
(6073, 'en', 'smd_tag_sync_type_lbl', 'smd_tag', 'site', 'Import from', '2019-07-23 23:51:02'),
(6074, 'en', 'smd_tag_tags_pane', 'smd_tag', 'site', 'Manage tags', '2019-07-23 23:51:02'),
(6075, 'en', 'smd_tag_tag_search', 'smd_tag', 'site', 'Tag search', '2019-07-23 23:51:02'),
(6076, 'en', 'smd_tag_tbl_installed', 'smd_tag', 'site', 'Tag tables installed.', '2019-07-23 23:51:02'),
(6077, 'en', 'smd_tag_tbl_install_lbl', 'smd_tag', 'site', 'Install tables', '2019-07-23 23:51:02'),
(6078, 'en', 'smd_tag_tbl_not_installed', 'smd_tag', 'site', 'Tag tables NOT installed.', '2019-07-23 23:51:02'),
(6079, 'en', 'smd_tag_tbl_not_removed', 'smd_tag', 'site', 'Tag tables NOT removed.', '2019-07-23 23:51:02'),
(6080, 'en', 'smd_tag_tbl_rebuild_lbl', 'smd_tag', 'site', 'Rebuild tags', '2019-07-23 23:51:02'),
(6081, 'en', 'smd_tag_tbl_rebuilt', 'smd_tag', 'site', 'Tag tables rebuilt', '2019-07-23 23:51:02'),
(6082, 'en', 'smd_tag_tbl_removed', 'smd_tag', 'site', 'Tag tables removed.', '2019-07-23 23:51:02'),
(6083, 'en', 'smd_tag_tbl_remove_lbl', 'smd_tag', 'site', 'Remove tables', '2019-07-23 23:51:02'),
(6084, 'en', 'smd_tag_textbox', 'smd_tag', 'site', 'Text area', '2019-07-23 23:51:02'),
(6085, 'en', 'smd_tag_textboxplus', 'smd_tag', 'site', 'Text area+', '2019-07-23 23:51:02'),
(6086, 'en', 'smd_tag_textlist', 'smd_tag', 'site', 'Text list', '2019-07-23 23:51:02'),
(6087, 'en', 'smd_tag_title', 'smd_tag', 'site', 'Tags', '2019-07-23 23:51:02'),
(6088, 'en', 'smd_tag_toggle', 'smd_tag', 'site', 'tog', '2019-07-23 23:51:02'),
(6089, 'en', 'smd_tag_t_astart', 'smd_tag', 'site', 'Initial pane', '2019-07-23 23:51:02'),
(6090, 'en', 'smd_tag_t_auto', 'smd_tag', 'site', 'Auto name', '2019-07-23 23:51:02'),
(6091, 'en', 'smd_tag_t_cols', 'smd_tag', 'site', 'Tag layout', '2019-07-23 23:51:02'),
(6092, 'en', 'smd_tag_t_colsord', 'smd_tag', 'site', 'Order tags by', '2019-07-23 23:51:02'),
(6093, 'en', 'smd_tag_t_count', 'smd_tag', 'site', 'Show tag usage counts', '2019-07-23 23:51:02'),
(6094, 'en', 'smd_tag_t_deltree', 'smd_tag', 'site', 'When deleting a parent', '2019-07-23 23:51:02'),
(6095, 'en', 'smd_tag_t_delused', 'smd_tag', 'site', 'Allow deletion of used tags', '2019-07-23 23:51:02'),
(6096, 'en', 'smd_tag_t_desc_textile', 'smd_tag', 'site', 'Textile description', '2019-07-23 23:51:02'),
(6097, 'en', 'smd_tag_t_desc_tooltip', 'smd_tag', 'site', 'Show description as tooltip', '2019-07-23 23:51:02'),
(6098, 'en', 'smd_tag_t_enrep', 'smd_tag', 'site', 'Automatically display reports', '2019-07-23 23:51:02'),
(6099, 'en', 'smd_tag_t_hilite', 'smd_tag', 'site', 'Clicked RGB colour', '2019-07-23 23:51:02'),
(6100, 'en', 'smd_tag_t_hover', 'smd_tag', 'site', 'Mouse-over RGB colour', '2019-07-23 23:51:02'),
(6101, 'en', 'smd_tag_t_indent', 'smd_tag', 'site', 'Sub-tag level indicator', '2019-07-23 23:51:02'),
(6102, 'en', 'smd_tag_t_mdelim', 'smd_tag', 'site', 'Multi-tag delimiter (1 char)', '2019-07-23 23:51:02'),
(6103, 'en', 'smd_tag_t_size_desc', 'smd_tag', 'site', 'Description width, height', '2019-07-23 23:51:02'),
(6104, 'en', 'smd_tag_unable_to_create', 'smd_tag', 'site', 'cannot create', '2019-07-23 23:51:02'),
(6105, 'en', 'smd_tag_updated', 'smd_tag', 'site', '{type} tag \"{name}\" updated', '2019-07-23 23:51:02'),
(6106, 'en', 'smd_tag_u_combi', 'smd_tag', 'site', 'Use tag combinations', '2019-07-23 23:51:02'),
(6107, 'en', 'smd_tag_u_combi_and', 'smd_tag', 'site', 'AND combinator char', '2019-07-23 23:51:02'),
(6108, 'en', 'smd_tag_u_combi_or', 'smd_tag', 'site', 'OR combinator char', '2019-07-23 23:51:02'),
(6109, 'en', 'smd_tag_u_pnam', 'smd_tag', 'site', 'URL name parameter', '2019-07-23 23:51:02'),
(6110, 'en', 'smd_tag_u_ptyp', 'smd_tag', 'site', 'URL type parameter', '2019-07-23 23:51:02'),
(6111, 'en', 'smd_tag_u_sec', 'smd_tag', 'site', 'Trigger(s) for tag lists', '2019-07-23 23:51:02'),
(6112, 'en', 'smd_tag_with_filtered', 'smd_tag', 'site', 'With filtered:', '2019-07-23 23:51:02'),
(6113, 'de', 'yab_copy_to_new', 'admin', 'site', 'Kopieren', '2019-07-24 00:46:04'),
(6114, 'en-us', 'yab_copy_to_new', 'admin', 'site', 'Copy', '2019-07-24 00:46:04'),
(6115, 'el', 'yab_copy_to_new', 'admin', 'site', 'Copy', '2019-07-24 00:46:04'),
(6116, 'en', 'yab_copy_to_new', 'admin', 'site', 'Copy', '2019-07-24 00:46:04'),
(6117, 'de', 'yab_rating_label', 'admin', 'site', 'Bewertung', '2019-07-24 00:50:24'),
(6118, 'en-us', 'yab_rating_label', 'admin', 'site', 'Rating', '2019-07-24 00:50:24'),
(6119, 'el', 'yab_rating_label', 'admin', 'site', 'Rating', '2019-07-24 00:50:24'),
(6120, 'en', 'yab_rating_label', 'admin', 'site', 'Rating', '2019-07-24 00:50:24'),
(6121, 'de', 'spf_javascript', 'spf_js', 'site', 'JavaScript', '2019-07-24 16:08:00'),
(6122, 'de', 'spf_js_dir', 'spf_js', 'site', 'JavaScript-Verzeichnis', '2019-07-24 16:08:00'),
(6123, 'de', 'spf_script_name', 'spf_js', 'site', 'Name dieses Script', '2019-07-24 16:08:00'),
(6124, 'de', 'spf_edit_script', 'spf_js', 'site', 'Sie bearbeiten das Script', '2019-07-24 16:08:00'),
(6125, 'de', 'spf_copy_script', 'spf_js', 'site', 'Kopiere Script als:', '2019-07-24 16:08:00'),
(6126, 'de', 'spf_all_scripts', 'spf_js', 'site', 'Alle Scripts', '2019-07-24 16:08:00'),
(6127, 'de', 'spf_create_new_script', 'spf_js', 'site', 'Neues Script erstellen', '2019-07-24 16:08:00'),
(6128, 'de', 'spf_script_created', 'spf_js', 'site', 'Script <strong>{name}</strong> wurde erstellt.', '2019-07-24 16:08:00'),
(6129, 'de', 'spf_script_exists', 'spf_js', 'site', 'Script <strong>{name}</strong> existiert bereits.', '2019-07-24 16:08:00'),
(6130, 'de', 'spf_script_name_required', 'spf_js', 'site', 'Bitte vergeben Sie einen Namen für Ihr Script.', '2019-07-24 16:08:00'),
(6131, 'de', 'spf_script_updated', 'spf_js', 'site', 'Script <strong>{name}</strong> wurde aktualisiert.', '2019-07-24 16:08:00'),
(6132, 'de', 'spf_script_deleted', 'spf_js', 'site', 'Script <strong>{name}</strong> wurde gelöscht.', '2019-07-24 16:08:00'),
(6133, 'de', 'spf_cannot_delete_default_script', 'spf_js', 'site', 'Script <strong>default</strong> konnte nicht gelöscht werden.', '2019-07-24 16:08:00'),
(6134, 'en-us', 'spf_javascript', 'spf_js', 'site', 'JavaScript', '2019-07-24 16:08:00'),
(6135, 'en-us', 'spf_js_dir', 'spf_js', 'site', 'JavaScript directory', '2019-07-24 16:08:00'),
(6136, 'en-us', 'spf_script_name', 'spf_js', 'site', 'Name for this script', '2019-07-24 16:08:00'),
(6137, 'en-us', 'spf_edit_script', 'spf_js', 'site', 'You are editing script', '2019-07-24 16:08:00'),
(6138, 'en-us', 'spf_copy_script', 'spf_js', 'site', '&#8230;or copy script as', '2019-07-24 16:08:00'),
(6139, 'en-us', 'spf_all_scripts', 'spf_js', 'site', 'All Scripts', '2019-07-24 16:08:00'),
(6140, 'en-us', 'spf_create_new_script', 'spf_js', 'site', 'Create new script', '2019-07-24 16:08:00'),
(6141, 'en-us', 'spf_script_created', 'spf_js', 'site', 'Script <strong>{name}</strong> created.', '2019-07-24 16:08:00'),
(6142, 'en-us', 'spf_script_exists', 'spf_js', 'site', 'Script <strong>{name}</strong> already exists.', '2019-07-24 16:08:00'),
(6143, 'en-us', 'spf_script_name_required', 'spf_js', 'site', 'Please provide a name for your script.', '2019-07-24 16:08:00'),
(6144, 'en-us', 'spf_script_updated', 'spf_js', 'site', 'Script <strong>{name}</strong> updated.', '2019-07-24 16:08:00'),
(6145, 'en-us', 'spf_script_deleted', 'spf_js', 'site', 'Script <strong>{name}</strong> deleted.', '2019-07-24 16:08:00'),
(6146, 'en-us', 'spf_cannot_delete_default_script', 'spf_js', 'site', 'Script <strong>default</strong> cannot be deleted.', '2019-07-24 16:08:00'),
(6147, 'el', 'spf_javascript', 'spf_js', 'site', 'JavaScript', '2019-07-24 16:08:00'),
(6148, 'el', 'spf_js_dir', 'spf_js', 'site', 'JavaScript directory', '2019-07-24 16:08:00'),
(6149, 'el', 'spf_script_name', 'spf_js', 'site', 'Name for this script', '2019-07-24 16:08:00'),
(6150, 'el', 'spf_edit_script', 'spf_js', 'site', 'You are editing script', '2019-07-24 16:08:00'),
(6151, 'el', 'spf_copy_script', 'spf_js', 'site', '&#8230;or copy script as', '2019-07-24 16:08:00'),
(6152, 'el', 'spf_all_scripts', 'spf_js', 'site', 'All Scripts', '2019-07-24 16:08:00'),
(6153, 'el', 'spf_create_new_script', 'spf_js', 'site', 'Create new script', '2019-07-24 16:08:00'),
(6154, 'el', 'spf_script_created', 'spf_js', 'site', 'Script <strong>{name}</strong> created.', '2019-07-24 16:08:00'),
(6155, 'el', 'spf_script_exists', 'spf_js', 'site', 'Script <strong>{name}</strong> already exists.', '2019-07-24 16:08:00'),
(6156, 'el', 'spf_script_name_required', 'spf_js', 'site', 'Please provide a name for your script.', '2019-07-24 16:08:00'),
(6157, 'el', 'spf_script_updated', 'spf_js', 'site', 'Script <strong>{name}</strong> updated.', '2019-07-24 16:08:00'),
(6158, 'el', 'spf_script_deleted', 'spf_js', 'site', 'Script <strong>{name}</strong> deleted.', '2019-07-24 16:08:00'),
(6159, 'el', 'spf_cannot_delete_default_script', 'spf_js', 'site', 'Script <strong>default</strong> cannot be deleted.', '2019-07-24 16:08:00'),
(6160, 'en', 'spf_javascript', 'spf_js', 'site', 'JavaScript', '2019-07-24 16:08:00'),
(6161, 'en', 'spf_js_dir', 'spf_js', 'site', 'JavaScript directory', '2019-07-24 16:08:00'),
(6162, 'en', 'spf_script_name', 'spf_js', 'site', 'Name for this script', '2019-07-24 16:08:00'),
(6163, 'en', 'spf_edit_script', 'spf_js', 'site', 'You are editing script', '2019-07-24 16:08:00'),
(6164, 'en', 'spf_copy_script', 'spf_js', 'site', '&#8230;or copy script as', '2019-07-24 16:08:00'),
(6165, 'en', 'spf_all_scripts', 'spf_js', 'site', 'All Scripts', '2019-07-24 16:08:00'),
(6166, 'en', 'spf_create_new_script', 'spf_js', 'site', 'Create new script', '2019-07-24 16:08:00'),
(6167, 'en', 'spf_script_created', 'spf_js', 'site', 'Script <strong>{name}</strong> created.', '2019-07-24 16:08:00'),
(6168, 'en', 'spf_script_exists', 'spf_js', 'site', 'Script <strong>{name}</strong> already exists.', '2019-07-24 16:08:00'),
(6169, 'en', 'spf_script_name_required', 'spf_js', 'site', 'Please provide a name for your script.', '2019-07-24 16:08:00'),
(6170, 'en', 'spf_script_updated', 'spf_js', 'site', 'Script <strong>{name}</strong> updated.', '2019-07-24 16:08:00'),
(6171, 'en', 'spf_script_deleted', 'spf_js', 'site', 'Script <strong>{name}</strong> deleted.', '2019-07-24 16:08:00'),
(6172, 'en', 'spf_cannot_delete_default_script', 'spf_js', 'site', 'Script <strong>default</strong> cannot be deleted.', '2019-07-24 16:08:00'),
(6173, 'de', 'oui_player', 'admin', 'site', 'Embedded players', '2019-07-25 03:10:00'),
(6174, 'de', 'oui_player_provider', 'admin', 'site', 'Favourite provider', '2019-07-25 03:10:00'),
(6175, 'de', 'oui_player_custom_field', 'admin', 'site', 'Field used by default', '2019-07-25 03:10:00'),
(6176, 'de', 'oui_player_microdata', 'admin', 'site', 'Add microdata', '2019-07-25 03:10:00'),
(6177, 'de', 'oui_player_duplicate_provider_extensions', 'admin', 'site', 'Duplicate provider extensions found for {provider}. Please disable or delete duplicates.', '2019-07-25 03:10:00'),
(6178, 'en-us', 'oui_player', 'admin', 'site', 'Embedded players', '2019-07-25 03:10:00'),
(6179, 'en-us', 'oui_player_provider', 'admin', 'site', 'Favourite provider', '2019-07-25 03:10:00'),
(6180, 'en-us', 'oui_player_custom_field', 'admin', 'site', 'Field used by default', '2019-07-25 03:10:00'),
(6181, 'en-us', 'oui_player_microdata', 'admin', 'site', 'Add microdata', '2019-07-25 03:10:00'),
(6182, 'en-us', 'oui_player_duplicate_provider_extensions', 'admin', 'site', 'Duplicate provider extensions found for {provider}. Please disable or delete duplicates.', '2019-07-25 03:10:00'),
(6183, 'el', 'oui_player', 'admin', 'site', 'Embedded players', '2019-07-25 03:10:00'),
(6184, 'el', 'oui_player_provider', 'admin', 'site', 'Favourite provider', '2019-07-25 03:10:00'),
(6185, 'el', 'oui_player_custom_field', 'admin', 'site', 'Field used by default', '2019-07-25 03:10:00'),
(6186, 'el', 'oui_player_microdata', 'admin', 'site', 'Add microdata', '2019-07-25 03:10:00'),
(6187, 'el', 'oui_player_duplicate_provider_extensions', 'admin', 'site', 'Duplicate provider extensions found for {provider}. Please disable or delete duplicates.', '2019-07-25 03:10:00'),
(6188, 'en', 'oui_player', 'admin', 'site', 'Embedded players', '2019-07-25 03:10:00'),
(6189, 'en', 'oui_player_provider', 'admin', 'site', 'Favourite provider', '2019-07-25 03:10:00'),
(6190, 'en', 'oui_player_custom_field', 'admin', 'site', 'Field used by default', '2019-07-25 03:10:00'),
(6191, 'en', 'oui_player_microdata', 'admin', 'site', 'Add microdata', '2019-07-25 03:10:00'),
(6192, 'en', 'oui_player_duplicate_provider_extensions', 'admin', 'site', 'Duplicate provider extensions found for {provider}. Please disable or delete duplicates.', '2019-07-25 03:10:00'),
(6193, 'de', 'smd_imagery_dialog_title', 'smd_imagery', 'site', 'Insert images', '2019-07-24 01:38:02'),
(6194, 'de', 'smd_imagery_fetch_btn', 'smd_imagery', 'site', 'Fetch', '2019-07-24 01:38:02'),
(6195, 'de', 'smd_imagery_id_btn', 'smd_imagery', 'site', 'Get IDs', '2019-07-24 01:38:02'),
(6196, 'de', 'smd_imagery_image', 'smd_imagery', 'site', 'Image', '2019-07-24 01:38:02'),
(6197, 'de', 'smd_imagery_opt_cat', 'smd_imagery', 'site', 'By category', '2019-07-24 01:38:02'),
(6198, 'de', 'smd_imagery_opt_fld', 'smd_imagery', 'site', 'From field', '2019-07-24 01:38:02'),
(6199, 'de', 'smd_imagery_organise_btn', 'smd_imagery', 'site', 'Manage', '2019-07-24 01:38:02'),
(6200, 'de', 'smd_imagery_result', 'smd_imagery', 'site', 'Result', '2019-07-24 01:38:02'),
(6201, 'de', 'smd_imagery_template', 'smd_imagery', 'site', 'Template', '2019-07-24 01:38:02'),
(6202, 'en-us', 'smd_imagery_dialog_title', 'smd_imagery', 'site', 'Insert images', '2019-07-24 01:38:02'),
(6203, 'en-us', 'smd_imagery_fetch_btn', 'smd_imagery', 'site', 'Fetch', '2019-07-24 01:38:02'),
(6204, 'en-us', 'smd_imagery_id_btn', 'smd_imagery', 'site', 'Get IDs', '2019-07-24 01:38:02'),
(6205, 'en-us', 'smd_imagery_image', 'smd_imagery', 'site', 'Image', '2019-07-24 01:38:02'),
(6206, 'en-us', 'smd_imagery_opt_cat', 'smd_imagery', 'site', 'By category', '2019-07-24 01:38:02'),
(6207, 'en-us', 'smd_imagery_opt_fld', 'smd_imagery', 'site', 'From field', '2019-07-24 01:38:02'),
(6208, 'en-us', 'smd_imagery_organise_btn', 'smd_imagery', 'site', 'Manage', '2019-07-24 01:38:02'),
(6209, 'en-us', 'smd_imagery_result', 'smd_imagery', 'site', 'Result', '2019-07-24 01:38:02'),
(6210, 'en-us', 'smd_imagery_template', 'smd_imagery', 'site', 'Template', '2019-07-24 01:38:02'),
(6211, 'el', 'smd_imagery_dialog_title', 'smd_imagery', 'site', 'Insert images', '2019-07-24 01:38:02'),
(6212, 'el', 'smd_imagery_fetch_btn', 'smd_imagery', 'site', 'Fetch', '2019-07-24 01:38:02'),
(6213, 'el', 'smd_imagery_id_btn', 'smd_imagery', 'site', 'Get IDs', '2019-07-24 01:38:02'),
(6214, 'el', 'smd_imagery_image', 'smd_imagery', 'site', 'Image', '2019-07-24 01:38:02'),
(6215, 'el', 'smd_imagery_opt_cat', 'smd_imagery', 'site', 'By category', '2019-07-24 01:38:02'),
(6216, 'el', 'smd_imagery_opt_fld', 'smd_imagery', 'site', 'From field', '2019-07-24 01:38:02'),
(6217, 'el', 'smd_imagery_organise_btn', 'smd_imagery', 'site', 'Manage', '2019-07-24 01:38:02'),
(6218, 'el', 'smd_imagery_result', 'smd_imagery', 'site', 'Result', '2019-07-24 01:38:02'),
(6219, 'el', 'smd_imagery_template', 'smd_imagery', 'site', 'Template', '2019-07-24 01:38:02'),
(6220, 'en', 'smd_imagery_dialog_title', 'smd_imagery', 'site', 'Insert images', '2019-07-24 01:38:02'),
(6221, 'en', 'smd_imagery_fetch_btn', 'smd_imagery', 'site', 'Fetch', '2019-07-24 01:38:02'),
(6222, 'en', 'smd_imagery_id_btn', 'smd_imagery', 'site', 'Get IDs', '2019-07-24 01:38:02'),
(6223, 'en', 'smd_imagery_image', 'smd_imagery', 'site', 'Image', '2019-07-24 01:38:02'),
(6224, 'en', 'smd_imagery_opt_cat', 'smd_imagery', 'site', 'By category', '2019-07-24 01:38:02'),
(6225, 'en', 'smd_imagery_opt_fld', 'smd_imagery', 'site', 'From field', '2019-07-24 01:38:02'),
(6226, 'en', 'smd_imagery_organise_btn', 'smd_imagery', 'site', 'Manage', '2019-07-24 01:38:02'),
(6227, 'en', 'smd_imagery_result', 'smd_imagery', 'site', 'Result', '2019-07-24 01:38:02'),
(6228, 'en', 'smd_imagery_template', 'smd_imagery', 'site', 'Template', '2019-07-24 01:38:02'),
(6229, 'de', 'smd_at_actions', 'smd_at', 'site', 'Actions', '2019-07-24 02:03:25'),
(6230, 'de', 'smd_at_allowed_skins', 'smd_at', 'site', 'Allowed themes', '2019-07-24 02:03:25'),
(6231, 'de', 'smd_at_all_themes', 'smd_at', 'site', 'All themes', '2019-07-24 02:03:25'),
(6232, 'de', 'smd_at_apply_skin', 'smd_at', 'site', 'Apply this theme', '2019-07-24 02:03:25'),
(6233, 'de', 'smd_at_avail_title', 'smd_at', 'site', 'Available themes', '2019-07-24 02:03:25'),
(6234, 'de', 'smd_at_based_on', 'smd_at', 'site', '(based on {skin})', '2019-07-24 02:03:25'),
(6235, 'de', 'smd_at_by', 'smd_at', 'site', 'by', '2019-07-24 02:03:25'),
(6236, 'de', 'smd_at_case_sort', 'smd_at', 'site', 'Case-sensitive theme list', '2019-07-24 02:03:25'),
(6237, 'de', 'smd_at_clear', 'smd_at', 'site', 'none', '2019-07-24 02:03:25'),
(6238, 'de', 'smd_at_clone', 'smd_at', 'site', 'Base', '2019-07-24 02:03:25'),
(6239, 'de', 'smd_at_confirm', 'smd_at', 'site', 'Go', '2019-07-24 02:03:25'),
(6240, 'de', 'smd_at_core_theme', 'smd_at', 'site', 'Cannot delete {skin}: core theme', '2019-07-24 02:03:25'),
(6241, 'de', 'smd_at_core_theme_file', 'smd_at', 'site', 'Cannot delete files from {skin}: core theme', '2019-07-24 02:03:25'),
(6242, 'de', 'smd_at_crush_format', 'smd_at', 'site', 'Format', '2019-07-24 02:03:25'),
(6243, 'de', 'smd_at_crush_type', 'smd_at', 'site', 'Export compression type', '2019-07-24 02:03:25'),
(6244, 'de', 'smd_at_css', 'smd_at', 'site', 'Stylesheet', '2019-07-24 02:03:25'),
(6245, 'de', 'smd_at_c_bzip2', 'smd_at', 'site', 'BZip', '2019-07-24 02:03:25'),
(6246, 'de', 'smd_at_c_gzip', 'smd_at', 'site', 'GZip', '2019-07-24 02:03:25'),
(6247, 'de', 'smd_at_c_tar', 'smd_at', 'site', 'Tar', '2019-07-24 02:03:25'),
(6248, 'de', 'smd_at_c_zip', 'smd_at', 'site', 'Zip', '2019-07-24 02:03:25'),
(6249, 'de', 'smd_at_delete_confirm', 'smd_at', 'site', 'Really delete theme {skin}?', '2019-07-24 02:03:25'),
(6250, 'de', 'smd_at_delete_failed', 'smd_at', 'site', 'Delete failed. Try removing it manually', '2019-07-24 02:03:25'),
(6251, 'de', 'smd_at_edit_lbl', 'smd_at', 'site', 'Edit {theme} theme', '2019-07-24 02:03:25'),
(6252, 'de', 'smd_at_empty_info', 'smd_at', 'site', 'You should choose a file first', '2019-07-24 02:03:25'),
(6253, 'de', 'smd_at_export', 'smd_at', 'site', 'Export', '2019-07-24 02:03:25'),
(6254, 'de', 'smd_at_filename_format', 'smd_at', 'site', 'Filename format', '2019-07-24 02:03:25'),
(6255, 'de', 'smd_at_files_deleted', 'smd_at', 'site', 'Deleted: {list}. Note that empty folders may need removing manually', '2019-07-24 02:03:25'),
(6256, 'de', 'smd_at_file_name', 'smd_at', 'site', 'File name', '2019-07-24 02:03:25'),
(6257, 'de', 'smd_at_file_not_deleted', 'smd_at', 'site', 'File {name} <strong>not</strong> deleted', '2019-07-24 02:03:25'),
(6258, 'de', 'smd_at_file_saved', 'smd_at', 'site', 'File {name} saved', '2019-07-24 02:03:25'),
(6259, 'de', 'smd_at_find_theme', 'smd_at', 'site', 'Browse themes', '2019-07-24 02:03:25'),
(6260, 'de', 'smd_at_folder', 'smd_at', 'site', '... to folder', '2019-07-24 02:03:25'),
(6261, 'de', 'smd_at_folders', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6262, 'de', 'smd_at_folder_deleted', 'smd_at', 'site', 'Folder {name} deleted', '2019-07-24 02:03:25'),
(6263, 'de', 'smd_at_folder_name', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6264, 'de', 'smd_at_global_skin', 'smd_at', 'site', 'Default theme', '2019-07-24 02:03:25'),
(6265, 'de', 'smd_at_help_link', 'smd_at', 'site', '<a href=\"{link}\">Help</a>', '2019-07-24 02:03:25'),
(6266, 'de', 'smd_at_image_files', 'smd_at', 'site', 'Image', '2019-07-24 02:03:25'),
(6267, 'de', 'smd_at_import_failed', 'smd_at', 'site', 'Installation failed :-( Check the archive structure', '2019-07-24 02:03:25'),
(6268, 'de', 'smd_at_import_ok', 'smd_at', 'site', 'Installation successful.', '2019-07-24 02:03:25'),
(6269, 'de', 'smd_at_install_skin', 'smd_at', 'site', 'Install theme', '2019-07-24 02:03:25'),
(6270, 'de', 'smd_at_is_global', 'smd_at', 'site', '(default)', '2019-07-24 02:03:25'),
(6271, 'de', 'smd_at_js', 'smd_at', 'site', 'Javascript', '2019-07-24 02:03:25'),
(6272, 'de', 'smd_at_layout', 'smd_at', 'site', 'Layout method', '2019-07-24 02:03:25'),
(6273, 'de', 'smd_at_layout_grid', 'smd_at', 'site', 'Grid', '2019-07-24 02:03:25'),
(6274, 'de', 'smd_at_layout_list', 'smd_at', 'site', 'List', '2019-07-24 02:03:25'),
(6275, 'de', 'smd_at_list_title', 'smd_at', 'site', 'Installed themes', '2019-07-24 02:03:25'),
(6276, 'de', 'smd_at_manage_lbl', 'smd_at', 'site', 'Theme manager', '2019-07-24 02:03:25'),
(6277, 'de', 'smd_at_max_theme_size', 'smd_at', 'site', 'Max theme file size (bytes)', '2019-07-24 02:03:25'),
(6278, 'de', 'smd_at_mkdir_failed', 'smd_at', 'site', 'Cannot create directory for file {name}. Try manually creating it.', '2019-07-24 02:03:25'),
(6279, 'de', 'smd_at_name_empty', 'smd_at', 'site', 'You should give your theme a name', '2019-07-24 02:03:25'),
(6280, 'de', 'smd_at_new', 'smd_at', 'site', 'Create new theme', '2019-07-24 02:03:25'),
(6281, 'de', 'smd_at_new_cloneskin', 'smd_at', 'site', 'Name of copy', '2019-07-24 02:03:25'),
(6282, 'de', 'smd_at_new_file', 'smd_at', 'site', 'New file', '2019-07-24 02:03:25'),
(6283, 'de', 'smd_at_new_skin', 'smd_at', 'site', 'Theme name', '2019-07-24 02:03:25'),
(6284, 'de', 'smd_at_one_theme', 'smd_at', 'site', 'One theme', '2019-07-24 02:03:25'),
(6285, 'de', 'smd_at_other', 'smd_at', 'site', 'Other files', '2019-07-24 02:03:25'),
(6286, 'de', 'smd_at_other_files', 'smd_at', 'site', 'Others', '2019-07-24 02:03:25'),
(6287, 'de', 'smd_at_per_group', 'smd_at', 'site', 'per priv level', '2019-07-24 02:03:25'),
(6288, 'de', 'smd_at_per_site', 'smd_at', 'site', 'for all', '2019-07-24 02:03:25'),
(6289, 'de', 'smd_at_per_user', 'smd_at', 'site', 'per user', '2019-07-24 02:03:25'),
(6290, 'de', 'smd_at_php', 'smd_at', 'site', 'PHP', '2019-07-24 02:03:25'),
(6291, 'de', 'smd_at_prefs_deleted', 'smd_at', 'site', 'Preferences deleted', '2019-07-24 02:03:25'),
(6292, 'de', 'smd_at_prefs_installed', 'smd_at', 'site', 'Preferences installed', '2019-07-24 02:03:25'),
(6293, 'de', 'smd_at_prefs_title', 'smd_at', 'site', 'Admin theme preferences', '2019-07-24 02:03:25'),
(6294, 'de', 'smd_at_preview', 'smd_at', 'site', 'Preview of {name}', '2019-07-24 02:03:25'),
(6295, 'de', 'smd_at_preview_image', 'smd_at', 'site', 'View image', '2019-07-24 02:03:25'),
(6296, 'de', 'smd_at_renamed', 'smd_at', 'site', 'Renamed to {name}', '2019-07-24 02:03:25'),
(6297, 'de', 'smd_at_rename_failed', 'smd_at', 'site', 'Rename failed', '2019-07-24 02:03:25'),
(6298, 'de', 'smd_at_scss', 'smd_at', 'site', 'SCSS', '2019-07-24 02:03:25'),
(6299, 'de', 'smd_at_set', 'smd_at', 'site', 'Set', '2019-07-24 02:03:25'),
(6300, 'de', 'smd_at_setup', 'smd_at', 'site', 'Preferences', '2019-07-24 02:03:25'),
(6301, 'de', 'smd_at_skinner', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6302, 'de', 'smd_at_skin_cloned', 'smd_at', 'site', 'Theme extended as {name}', '2019-07-24 02:03:25'),
(6303, 'de', 'smd_at_skin_created', 'smd_at', 'site', 'Theme {name} created', '2019-07-24 02:03:25'),
(6304, 'de', 'smd_at_skin_deleted', 'smd_at', 'site', 'Theme {name} deleted', '2019-07-24 02:03:25'),
(6305, 'de', 'smd_at_skin_exists', 'smd_at', 'site', 'Theme {name} already exists', '2019-07-24 02:03:25'),
(6306, 'de', 'smd_at_skin_files', 'smd_at', 'site', 'Theme files', '2019-07-24 02:03:25'),
(6307, 'de', 'smd_at_skin_gbl', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6308, 'de', 'smd_at_skin_groups', 'smd_at', 'site', 'Group themes', '2019-07-24 02:03:25'),
(6309, 'de', 'smd_at_skin_switched', 'smd_at', 'site', 'Theme switched to {skin}', '2019-07-24 02:03:25'),
(6310, 'de', 'smd_at_skin_system', 'smd_at', 'site', 'Theme system', '2019-07-24 02:03:25'),
(6311, 'de', 'smd_at_ssc', 'smd_at', 'site', 'Styleplate', '2019-07-24 02:03:25'),
(6312, 'de', 'smd_at_supported_import', 'smd_at', 'site', 'Supported import types: {types}', '2019-07-24 02:03:25'),
(6313, 'de', 'smd_at_tab_name', 'smd_at', 'site', 'Admin themes', '2019-07-24 02:03:25'),
(6314, 'de', 'smd_at_textile', 'smd_at', 'site', 'Textile', '2019-07-24 02:03:25'),
(6315, 'de', 'smd_at_thumbsize', 'smd_at', 'site', 'Thumb dimensions', '2019-07-24 02:03:25'),
(6316, 'de', 'smd_at_times', 'smd_at', 'site', 'x', '2019-07-24 02:03:25'),
(6317, 'de', 'smd_at_txt', 'smd_at', 'site', 'Text', '2019-07-24 02:03:25'),
(6318, 'de', 'smd_at_unsupported_compressiontype', 'smd_at', 'site', '{crush} is not a supported compression format in this installation', '2019-07-24 02:03:25'),
(6319, 'de', 'smd_at_unsupported_filetype', 'smd_at', 'site', 'File is not of a supported type', '2019-07-24 02:03:25'),
(6320, 'de', 'smd_at_unsupported_fudge', 'smd_at', 'site', ': hit Save again to store anyway', '2019-07-24 02:03:25'),
(6321, 'de', 'smd_at_upload_report', 'smd_at', 'site', 'Uploaded {num_success} files(s) {uploaded}. Failed: {num_fail}', '2019-07-24 02:03:25'),
(6322, 'de', 'smd_at_version', 'smd_at', 'site', 'v', '2019-07-24 02:03:25'),
(6323, 'en-us', 'smd_at_actions', 'smd_at', 'site', 'Actions', '2019-07-24 02:03:25'),
(6324, 'en-us', 'smd_at_allowed_skins', 'smd_at', 'site', 'Allowed themes', '2019-07-24 02:03:25'),
(6325, 'en-us', 'smd_at_all_themes', 'smd_at', 'site', 'All themes', '2019-07-24 02:03:25'),
(6326, 'en-us', 'smd_at_apply_skin', 'smd_at', 'site', 'Apply this theme', '2019-07-24 02:03:25'),
(6327, 'en-us', 'smd_at_avail_title', 'smd_at', 'site', 'Available themes', '2019-07-24 02:03:25'),
(6328, 'en-us', 'smd_at_based_on', 'smd_at', 'site', '(based on {skin})', '2019-07-24 02:03:25'),
(6329, 'en-us', 'smd_at_by', 'smd_at', 'site', 'by', '2019-07-24 02:03:25'),
(6330, 'en-us', 'smd_at_case_sort', 'smd_at', 'site', 'Case-sensitive theme list', '2019-07-24 02:03:25'),
(6331, 'en-us', 'smd_at_clear', 'smd_at', 'site', 'none', '2019-07-24 02:03:25'),
(6332, 'en-us', 'smd_at_clone', 'smd_at', 'site', 'Base', '2019-07-24 02:03:25'),
(6333, 'en-us', 'smd_at_confirm', 'smd_at', 'site', 'Go', '2019-07-24 02:03:25'),
(6334, 'en-us', 'smd_at_core_theme', 'smd_at', 'site', 'Cannot delete {skin}: core theme', '2019-07-24 02:03:25'),
(6335, 'en-us', 'smd_at_core_theme_file', 'smd_at', 'site', 'Cannot delete files from {skin}: core theme', '2019-07-24 02:03:25'),
(6336, 'en-us', 'smd_at_crush_format', 'smd_at', 'site', 'Format', '2019-07-24 02:03:25'),
(6337, 'en-us', 'smd_at_crush_type', 'smd_at', 'site', 'Export compression type', '2019-07-24 02:03:25'),
(6338, 'en-us', 'smd_at_css', 'smd_at', 'site', 'Stylesheet', '2019-07-24 02:03:25'),
(6339, 'en-us', 'smd_at_c_bzip2', 'smd_at', 'site', 'BZip', '2019-07-24 02:03:25'),
(6340, 'en-us', 'smd_at_c_gzip', 'smd_at', 'site', 'GZip', '2019-07-24 02:03:25'),
(6341, 'en-us', 'smd_at_c_tar', 'smd_at', 'site', 'Tar', '2019-07-24 02:03:25'),
(6342, 'en-us', 'smd_at_c_zip', 'smd_at', 'site', 'Zip', '2019-07-24 02:03:25'),
(6343, 'en-us', 'smd_at_delete_confirm', 'smd_at', 'site', 'Really delete theme {skin}?', '2019-07-24 02:03:25'),
(6344, 'en-us', 'smd_at_delete_failed', 'smd_at', 'site', 'Delete failed. Try removing it manually', '2019-07-24 02:03:25'),
(6345, 'en-us', 'smd_at_edit_lbl', 'smd_at', 'site', 'Edit {theme} theme', '2019-07-24 02:03:25'),
(6346, 'en-us', 'smd_at_empty_info', 'smd_at', 'site', 'You should choose a file first', '2019-07-24 02:03:25'),
(6347, 'en-us', 'smd_at_export', 'smd_at', 'site', 'Export', '2019-07-24 02:03:25'),
(6348, 'en-us', 'smd_at_filename_format', 'smd_at', 'site', 'Filename format', '2019-07-24 02:03:25'),
(6349, 'en-us', 'smd_at_files_deleted', 'smd_at', 'site', 'Deleted: {list}. Note that empty folders may need removing manually', '2019-07-24 02:03:25'),
(6350, 'en-us', 'smd_at_file_name', 'smd_at', 'site', 'File name', '2019-07-24 02:03:25'),
(6351, 'en-us', 'smd_at_file_not_deleted', 'smd_at', 'site', 'File {name} <strong>not</strong> deleted', '2019-07-24 02:03:25'),
(6352, 'en-us', 'smd_at_file_saved', 'smd_at', 'site', 'File {name} saved', '2019-07-24 02:03:25'),
(6353, 'en-us', 'smd_at_find_theme', 'smd_at', 'site', 'Browse themes', '2019-07-24 02:03:25'),
(6354, 'en-us', 'smd_at_folder', 'smd_at', 'site', '... to folder', '2019-07-24 02:03:25'),
(6355, 'en-us', 'smd_at_folders', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6356, 'en-us', 'smd_at_folder_deleted', 'smd_at', 'site', 'Folder {name} deleted', '2019-07-24 02:03:25'),
(6357, 'en-us', 'smd_at_folder_name', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6358, 'en-us', 'smd_at_global_skin', 'smd_at', 'site', 'Default theme', '2019-07-24 02:03:25'),
(6359, 'en-us', 'smd_at_help_link', 'smd_at', 'site', '<a href=\"{link}\">Help</a>', '2019-07-24 02:03:25'),
(6360, 'en-us', 'smd_at_image_files', 'smd_at', 'site', 'Image', '2019-07-24 02:03:25'),
(6361, 'en-us', 'smd_at_import_failed', 'smd_at', 'site', 'Installation failed :-( Check the archive structure', '2019-07-24 02:03:25'),
(6362, 'en-us', 'smd_at_import_ok', 'smd_at', 'site', 'Installation successful.', '2019-07-24 02:03:25'),
(6363, 'en-us', 'smd_at_install_skin', 'smd_at', 'site', 'Install theme', '2019-07-24 02:03:25'),
(6364, 'en-us', 'smd_at_is_global', 'smd_at', 'site', '(default)', '2019-07-24 02:03:25'),
(6365, 'en-us', 'smd_at_js', 'smd_at', 'site', 'Javascript', '2019-07-24 02:03:25'),
(6366, 'en-us', 'smd_at_layout', 'smd_at', 'site', 'Layout method', '2019-07-24 02:03:25'),
(6367, 'en-us', 'smd_at_layout_grid', 'smd_at', 'site', 'Grid', '2019-07-24 02:03:25'),
(6368, 'en-us', 'smd_at_layout_list', 'smd_at', 'site', 'List', '2019-07-24 02:03:25'),
(6369, 'en-us', 'smd_at_list_title', 'smd_at', 'site', 'Installed themes', '2019-07-24 02:03:25'),
(6370, 'en-us', 'smd_at_manage_lbl', 'smd_at', 'site', 'Theme manager', '2019-07-24 02:03:25'),
(6371, 'en-us', 'smd_at_max_theme_size', 'smd_at', 'site', 'Max theme file size (bytes)', '2019-07-24 02:03:25'),
(6372, 'en-us', 'smd_at_mkdir_failed', 'smd_at', 'site', 'Cannot create directory for file {name}. Try manually creating it.', '2019-07-24 02:03:25'),
(6373, 'en-us', 'smd_at_name_empty', 'smd_at', 'site', 'You should give your theme a name', '2019-07-24 02:03:25'),
(6374, 'en-us', 'smd_at_new', 'smd_at', 'site', 'Create new theme', '2019-07-24 02:03:25'),
(6375, 'en-us', 'smd_at_new_cloneskin', 'smd_at', 'site', 'Name of copy', '2019-07-24 02:03:25'),
(6376, 'en-us', 'smd_at_new_file', 'smd_at', 'site', 'New file', '2019-07-24 02:03:25'),
(6377, 'en-us', 'smd_at_new_skin', 'smd_at', 'site', 'Theme name', '2019-07-24 02:03:25'),
(6378, 'en-us', 'smd_at_one_theme', 'smd_at', 'site', 'One theme', '2019-07-24 02:03:25'),
(6379, 'en-us', 'smd_at_other', 'smd_at', 'site', 'Other files', '2019-07-24 02:03:25'),
(6380, 'en-us', 'smd_at_other_files', 'smd_at', 'site', 'Others', '2019-07-24 02:03:25'),
(6381, 'en-us', 'smd_at_per_group', 'smd_at', 'site', 'per priv level', '2019-07-24 02:03:25'),
(6382, 'en-us', 'smd_at_per_site', 'smd_at', 'site', 'for all', '2019-07-24 02:03:25'),
(6383, 'en-us', 'smd_at_per_user', 'smd_at', 'site', 'per user', '2019-07-24 02:03:25'),
(6384, 'en-us', 'smd_at_php', 'smd_at', 'site', 'PHP', '2019-07-24 02:03:25'),
(6385, 'en-us', 'smd_at_prefs_deleted', 'smd_at', 'site', 'Preferences deleted', '2019-07-24 02:03:25'),
(6386, 'en-us', 'smd_at_prefs_installed', 'smd_at', 'site', 'Preferences installed', '2019-07-24 02:03:25'),
(6387, 'en-us', 'smd_at_prefs_title', 'smd_at', 'site', 'Admin theme preferences', '2019-07-24 02:03:25'),
(6388, 'en-us', 'smd_at_preview', 'smd_at', 'site', 'Preview of {name}', '2019-07-24 02:03:25'),
(6389, 'en-us', 'smd_at_preview_image', 'smd_at', 'site', 'View image', '2019-07-24 02:03:25'),
(6390, 'en-us', 'smd_at_renamed', 'smd_at', 'site', 'Renamed to {name}', '2019-07-24 02:03:25'),
(6391, 'en-us', 'smd_at_rename_failed', 'smd_at', 'site', 'Rename failed', '2019-07-24 02:03:25'),
(6392, 'en-us', 'smd_at_scss', 'smd_at', 'site', 'SCSS', '2019-07-24 02:03:25'),
(6393, 'en-us', 'smd_at_set', 'smd_at', 'site', 'Set', '2019-07-24 02:03:25'),
(6394, 'en-us', 'smd_at_setup', 'smd_at', 'site', 'Preferences', '2019-07-24 02:03:25'),
(6395, 'en-us', 'smd_at_skinner', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6396, 'en-us', 'smd_at_skin_cloned', 'smd_at', 'site', 'Theme extended as {name}', '2019-07-24 02:03:25'),
(6397, 'en-us', 'smd_at_skin_created', 'smd_at', 'site', 'Theme {name} created', '2019-07-24 02:03:25'),
(6398, 'en-us', 'smd_at_skin_deleted', 'smd_at', 'site', 'Theme {name} deleted', '2019-07-24 02:03:25'),
(6399, 'en-us', 'smd_at_skin_exists', 'smd_at', 'site', 'Theme {name} already exists', '2019-07-24 02:03:25'),
(6400, 'en-us', 'smd_at_skin_files', 'smd_at', 'site', 'Theme files', '2019-07-24 02:03:25'),
(6401, 'en-us', 'smd_at_skin_gbl', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6402, 'en-us', 'smd_at_skin_groups', 'smd_at', 'site', 'Group themes', '2019-07-24 02:03:25'),
(6403, 'en-us', 'smd_at_skin_switched', 'smd_at', 'site', 'Theme switched to {skin}', '2019-07-24 02:03:25'),
(6404, 'en-us', 'smd_at_skin_system', 'smd_at', 'site', 'Theme system', '2019-07-24 02:03:25'),
(6405, 'en-us', 'smd_at_ssc', 'smd_at', 'site', 'Styleplate', '2019-07-24 02:03:25'),
(6406, 'en-us', 'smd_at_supported_import', 'smd_at', 'site', 'Supported import types: {types}', '2019-07-24 02:03:25');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(6407, 'en-us', 'smd_at_tab_name', 'smd_at', 'site', 'Admin themes', '2019-07-24 02:03:25'),
(6408, 'en-us', 'smd_at_textile', 'smd_at', 'site', 'Textile', '2019-07-24 02:03:25'),
(6409, 'en-us', 'smd_at_thumbsize', 'smd_at', 'site', 'Thumb dimensions', '2019-07-24 02:03:25'),
(6410, 'en-us', 'smd_at_times', 'smd_at', 'site', 'x', '2019-07-24 02:03:25'),
(6411, 'en-us', 'smd_at_txt', 'smd_at', 'site', 'Text', '2019-07-24 02:03:25'),
(6412, 'en-us', 'smd_at_unsupported_compressiontype', 'smd_at', 'site', '{crush} is not a supported compression format in this installation', '2019-07-24 02:03:25'),
(6413, 'en-us', 'smd_at_unsupported_filetype', 'smd_at', 'site', 'File is not of a supported type', '2019-07-24 02:03:25'),
(6414, 'en-us', 'smd_at_unsupported_fudge', 'smd_at', 'site', ': hit Save again to store anyway', '2019-07-24 02:03:25'),
(6415, 'en-us', 'smd_at_upload_report', 'smd_at', 'site', 'Uploaded {num_success} files(s) {uploaded}. Failed: {num_fail}', '2019-07-24 02:03:25'),
(6416, 'en-us', 'smd_at_version', 'smd_at', 'site', 'v', '2019-07-24 02:03:25'),
(6417, 'el', 'smd_at_actions', 'smd_at', 'site', 'Actions', '2019-07-24 02:03:25'),
(6418, 'el', 'smd_at_allowed_skins', 'smd_at', 'site', 'Allowed themes', '2019-07-24 02:03:25'),
(6419, 'el', 'smd_at_all_themes', 'smd_at', 'site', 'All themes', '2019-07-24 02:03:25'),
(6420, 'el', 'smd_at_apply_skin', 'smd_at', 'site', 'Apply this theme', '2019-07-24 02:03:25'),
(6421, 'el', 'smd_at_avail_title', 'smd_at', 'site', 'Available themes', '2019-07-24 02:03:25'),
(6422, 'el', 'smd_at_based_on', 'smd_at', 'site', '(based on {skin})', '2019-07-24 02:03:25'),
(6423, 'el', 'smd_at_by', 'smd_at', 'site', 'by', '2019-07-24 02:03:25'),
(6424, 'el', 'smd_at_case_sort', 'smd_at', 'site', 'Case-sensitive theme list', '2019-07-24 02:03:25'),
(6425, 'el', 'smd_at_clear', 'smd_at', 'site', 'none', '2019-07-24 02:03:25'),
(6426, 'el', 'smd_at_clone', 'smd_at', 'site', 'Base', '2019-07-24 02:03:25'),
(6427, 'el', 'smd_at_confirm', 'smd_at', 'site', 'Go', '2019-07-24 02:03:25'),
(6428, 'el', 'smd_at_core_theme', 'smd_at', 'site', 'Cannot delete {skin}: core theme', '2019-07-24 02:03:25'),
(6429, 'el', 'smd_at_core_theme_file', 'smd_at', 'site', 'Cannot delete files from {skin}: core theme', '2019-07-24 02:03:25'),
(6430, 'el', 'smd_at_crush_format', 'smd_at', 'site', 'Format', '2019-07-24 02:03:25'),
(6431, 'el', 'smd_at_crush_type', 'smd_at', 'site', 'Export compression type', '2019-07-24 02:03:25'),
(6432, 'el', 'smd_at_css', 'smd_at', 'site', 'Stylesheet', '2019-07-24 02:03:25'),
(6433, 'el', 'smd_at_c_bzip2', 'smd_at', 'site', 'BZip', '2019-07-24 02:03:25'),
(6434, 'el', 'smd_at_c_gzip', 'smd_at', 'site', 'GZip', '2019-07-24 02:03:25'),
(6435, 'el', 'smd_at_c_tar', 'smd_at', 'site', 'Tar', '2019-07-24 02:03:25'),
(6436, 'el', 'smd_at_c_zip', 'smd_at', 'site', 'Zip', '2019-07-24 02:03:25'),
(6437, 'el', 'smd_at_delete_confirm', 'smd_at', 'site', 'Really delete theme {skin}?', '2019-07-24 02:03:25'),
(6438, 'el', 'smd_at_delete_failed', 'smd_at', 'site', 'Delete failed. Try removing it manually', '2019-07-24 02:03:25'),
(6439, 'el', 'smd_at_edit_lbl', 'smd_at', 'site', 'Edit {theme} theme', '2019-07-24 02:03:25'),
(6440, 'el', 'smd_at_empty_info', 'smd_at', 'site', 'You should choose a file first', '2019-07-24 02:03:25'),
(6441, 'el', 'smd_at_export', 'smd_at', 'site', 'Export', '2019-07-24 02:03:25'),
(6442, 'el', 'smd_at_filename_format', 'smd_at', 'site', 'Filename format', '2019-07-24 02:03:25'),
(6443, 'el', 'smd_at_files_deleted', 'smd_at', 'site', 'Deleted: {list}. Note that empty folders may need removing manually', '2019-07-24 02:03:25'),
(6444, 'el', 'smd_at_file_name', 'smd_at', 'site', 'File name', '2019-07-24 02:03:25'),
(6445, 'el', 'smd_at_file_not_deleted', 'smd_at', 'site', 'File {name} <strong>not</strong> deleted', '2019-07-24 02:03:25'),
(6446, 'el', 'smd_at_file_saved', 'smd_at', 'site', 'File {name} saved', '2019-07-24 02:03:25'),
(6447, 'el', 'smd_at_find_theme', 'smd_at', 'site', 'Browse themes', '2019-07-24 02:03:25'),
(6448, 'el', 'smd_at_folder', 'smd_at', 'site', '... to folder', '2019-07-24 02:03:25'),
(6449, 'el', 'smd_at_folders', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6450, 'el', 'smd_at_folder_deleted', 'smd_at', 'site', 'Folder {name} deleted', '2019-07-24 02:03:25'),
(6451, 'el', 'smd_at_folder_name', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6452, 'el', 'smd_at_global_skin', 'smd_at', 'site', 'Default theme', '2019-07-24 02:03:25'),
(6453, 'el', 'smd_at_help_link', 'smd_at', 'site', '<a href=\"{link}\">Help</a>', '2019-07-24 02:03:25'),
(6454, 'el', 'smd_at_image_files', 'smd_at', 'site', 'Image', '2019-07-24 02:03:25'),
(6455, 'el', 'smd_at_import_failed', 'smd_at', 'site', 'Installation failed :-( Check the archive structure', '2019-07-24 02:03:25'),
(6456, 'el', 'smd_at_import_ok', 'smd_at', 'site', 'Installation successful.', '2019-07-24 02:03:25'),
(6457, 'el', 'smd_at_install_skin', 'smd_at', 'site', 'Install theme', '2019-07-24 02:03:25'),
(6458, 'el', 'smd_at_is_global', 'smd_at', 'site', '(default)', '2019-07-24 02:03:25'),
(6459, 'el', 'smd_at_js', 'smd_at', 'site', 'Javascript', '2019-07-24 02:03:25'),
(6460, 'el', 'smd_at_layout', 'smd_at', 'site', 'Layout method', '2019-07-24 02:03:25'),
(6461, 'el', 'smd_at_layout_grid', 'smd_at', 'site', 'Grid', '2019-07-24 02:03:25'),
(6462, 'el', 'smd_at_layout_list', 'smd_at', 'site', 'List', '2019-07-24 02:03:25'),
(6463, 'el', 'smd_at_list_title', 'smd_at', 'site', 'Installed themes', '2019-07-24 02:03:25'),
(6464, 'el', 'smd_at_manage_lbl', 'smd_at', 'site', 'Theme manager', '2019-07-24 02:03:25'),
(6465, 'el', 'smd_at_max_theme_size', 'smd_at', 'site', 'Max theme file size (bytes)', '2019-07-24 02:03:25'),
(6466, 'el', 'smd_at_mkdir_failed', 'smd_at', 'site', 'Cannot create directory for file {name}. Try manually creating it.', '2019-07-24 02:03:25'),
(6467, 'el', 'smd_at_name_empty', 'smd_at', 'site', 'You should give your theme a name', '2019-07-24 02:03:25'),
(6468, 'el', 'smd_at_new', 'smd_at', 'site', 'Create new theme', '2019-07-24 02:03:25'),
(6469, 'el', 'smd_at_new_cloneskin', 'smd_at', 'site', 'Name of copy', '2019-07-24 02:03:25'),
(6470, 'el', 'smd_at_new_file', 'smd_at', 'site', 'New file', '2019-07-24 02:03:25'),
(6471, 'el', 'smd_at_new_skin', 'smd_at', 'site', 'Theme name', '2019-07-24 02:03:25'),
(6472, 'el', 'smd_at_one_theme', 'smd_at', 'site', 'One theme', '2019-07-24 02:03:25'),
(6473, 'el', 'smd_at_other', 'smd_at', 'site', 'Other files', '2019-07-24 02:03:25'),
(6474, 'el', 'smd_at_other_files', 'smd_at', 'site', 'Others', '2019-07-24 02:03:25'),
(6475, 'el', 'smd_at_per_group', 'smd_at', 'site', 'per priv level', '2019-07-24 02:03:25'),
(6476, 'el', 'smd_at_per_site', 'smd_at', 'site', 'for all', '2019-07-24 02:03:25'),
(6477, 'el', 'smd_at_per_user', 'smd_at', 'site', 'per user', '2019-07-24 02:03:25'),
(6478, 'el', 'smd_at_php', 'smd_at', 'site', 'PHP', '2019-07-24 02:03:25'),
(6479, 'el', 'smd_at_prefs_deleted', 'smd_at', 'site', 'Preferences deleted', '2019-07-24 02:03:25'),
(6480, 'el', 'smd_at_prefs_installed', 'smd_at', 'site', 'Preferences installed', '2019-07-24 02:03:25'),
(6481, 'el', 'smd_at_prefs_title', 'smd_at', 'site', 'Admin theme preferences', '2019-07-24 02:03:25'),
(6482, 'el', 'smd_at_preview', 'smd_at', 'site', 'Preview of {name}', '2019-07-24 02:03:25'),
(6483, 'el', 'smd_at_preview_image', 'smd_at', 'site', 'View image', '2019-07-24 02:03:25'),
(6484, 'el', 'smd_at_renamed', 'smd_at', 'site', 'Renamed to {name}', '2019-07-24 02:03:25'),
(6485, 'el', 'smd_at_rename_failed', 'smd_at', 'site', 'Rename failed', '2019-07-24 02:03:25'),
(6486, 'el', 'smd_at_scss', 'smd_at', 'site', 'SCSS', '2019-07-24 02:03:25'),
(6487, 'el', 'smd_at_set', 'smd_at', 'site', 'Set', '2019-07-24 02:03:25'),
(6488, 'el', 'smd_at_setup', 'smd_at', 'site', 'Preferences', '2019-07-24 02:03:25'),
(6489, 'el', 'smd_at_skinner', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6490, 'el', 'smd_at_skin_cloned', 'smd_at', 'site', 'Theme extended as {name}', '2019-07-24 02:03:25'),
(6491, 'el', 'smd_at_skin_created', 'smd_at', 'site', 'Theme {name} created', '2019-07-24 02:03:25'),
(6492, 'el', 'smd_at_skin_deleted', 'smd_at', 'site', 'Theme {name} deleted', '2019-07-24 02:03:25'),
(6493, 'el', 'smd_at_skin_exists', 'smd_at', 'site', 'Theme {name} already exists', '2019-07-24 02:03:25'),
(6494, 'el', 'smd_at_skin_files', 'smd_at', 'site', 'Theme files', '2019-07-24 02:03:25'),
(6495, 'el', 'smd_at_skin_gbl', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6496, 'el', 'smd_at_skin_groups', 'smd_at', 'site', 'Group themes', '2019-07-24 02:03:25'),
(6497, 'el', 'smd_at_skin_switched', 'smd_at', 'site', 'Theme switched to {skin}', '2019-07-24 02:03:25'),
(6498, 'el', 'smd_at_skin_system', 'smd_at', 'site', 'Theme system', '2019-07-24 02:03:25'),
(6499, 'el', 'smd_at_ssc', 'smd_at', 'site', 'Styleplate', '2019-07-24 02:03:25'),
(6500, 'el', 'smd_at_supported_import', 'smd_at', 'site', 'Supported import types: {types}', '2019-07-24 02:03:25'),
(6501, 'el', 'smd_at_tab_name', 'smd_at', 'site', 'Admin themes', '2019-07-24 02:03:25'),
(6502, 'el', 'smd_at_textile', 'smd_at', 'site', 'Textile', '2019-07-24 02:03:25'),
(6503, 'el', 'smd_at_thumbsize', 'smd_at', 'site', 'Thumb dimensions', '2019-07-24 02:03:25'),
(6504, 'el', 'smd_at_times', 'smd_at', 'site', 'x', '2019-07-24 02:03:25'),
(6505, 'el', 'smd_at_txt', 'smd_at', 'site', 'Text', '2019-07-24 02:03:25'),
(6506, 'el', 'smd_at_unsupported_compressiontype', 'smd_at', 'site', '{crush} is not a supported compression format in this installation', '2019-07-24 02:03:25'),
(6507, 'el', 'smd_at_unsupported_filetype', 'smd_at', 'site', 'File is not of a supported type', '2019-07-24 02:03:25'),
(6508, 'el', 'smd_at_unsupported_fudge', 'smd_at', 'site', ': hit Save again to store anyway', '2019-07-24 02:03:25'),
(6509, 'el', 'smd_at_upload_report', 'smd_at', 'site', 'Uploaded {num_success} files(s) {uploaded}. Failed: {num_fail}', '2019-07-24 02:03:25'),
(6510, 'el', 'smd_at_version', 'smd_at', 'site', 'v', '2019-07-24 02:03:25'),
(6511, 'en', 'smd_at_actions', 'smd_at', 'site', 'Actions', '2019-07-24 02:03:25'),
(6512, 'en', 'smd_at_allowed_skins', 'smd_at', 'site', 'Allowed themes', '2019-07-24 02:03:25'),
(6513, 'en', 'smd_at_all_themes', 'smd_at', 'site', 'All themes', '2019-07-24 02:03:25'),
(6514, 'en', 'smd_at_apply_skin', 'smd_at', 'site', 'Apply this theme', '2019-07-24 02:03:25'),
(6515, 'en', 'smd_at_avail_title', 'smd_at', 'site', 'Available themes', '2019-07-24 02:03:25'),
(6516, 'en', 'smd_at_based_on', 'smd_at', 'site', '(based on {skin})', '2019-07-24 02:03:25'),
(6517, 'en', 'smd_at_by', 'smd_at', 'site', 'by', '2019-07-24 02:03:25'),
(6518, 'en', 'smd_at_case_sort', 'smd_at', 'site', 'Case-sensitive theme list', '2019-07-24 02:03:25'),
(6519, 'en', 'smd_at_clear', 'smd_at', 'site', 'none', '2019-07-24 02:03:25'),
(6520, 'en', 'smd_at_clone', 'smd_at', 'site', 'Base', '2019-07-24 02:03:25'),
(6521, 'en', 'smd_at_confirm', 'smd_at', 'site', 'Go', '2019-07-24 02:03:25'),
(6522, 'en', 'smd_at_core_theme', 'smd_at', 'site', 'Cannot delete {skin}: core theme', '2019-07-24 02:03:25'),
(6523, 'en', 'smd_at_core_theme_file', 'smd_at', 'site', 'Cannot delete files from {skin}: core theme', '2019-07-24 02:03:25'),
(6524, 'en', 'smd_at_crush_format', 'smd_at', 'site', 'Format', '2019-07-24 02:03:25'),
(6525, 'en', 'smd_at_crush_type', 'smd_at', 'site', 'Export compression type', '2019-07-24 02:03:25'),
(6526, 'en', 'smd_at_css', 'smd_at', 'site', 'Stylesheet', '2019-07-24 02:03:25'),
(6527, 'en', 'smd_at_c_bzip2', 'smd_at', 'site', 'BZip', '2019-07-24 02:03:25'),
(6528, 'en', 'smd_at_c_gzip', 'smd_at', 'site', 'GZip', '2019-07-24 02:03:25'),
(6529, 'en', 'smd_at_c_tar', 'smd_at', 'site', 'Tar', '2019-07-24 02:03:25'),
(6530, 'en', 'smd_at_c_zip', 'smd_at', 'site', 'Zip', '2019-07-24 02:03:25'),
(6531, 'en', 'smd_at_delete_confirm', 'smd_at', 'site', 'Really delete theme {skin}?', '2019-07-24 02:03:25'),
(6532, 'en', 'smd_at_delete_failed', 'smd_at', 'site', 'Delete failed. Try removing it manually', '2019-07-24 02:03:25'),
(6533, 'en', 'smd_at_edit_lbl', 'smd_at', 'site', 'Edit {theme} theme', '2019-07-24 02:03:25'),
(6534, 'en', 'smd_at_empty_info', 'smd_at', 'site', 'You should choose a file first', '2019-07-24 02:03:25'),
(6535, 'en', 'smd_at_export', 'smd_at', 'site', 'Export', '2019-07-24 02:03:25'),
(6536, 'en', 'smd_at_filename_format', 'smd_at', 'site', 'Filename format', '2019-07-24 02:03:25'),
(6537, 'en', 'smd_at_files_deleted', 'smd_at', 'site', 'Deleted: {list}. Note that empty folders may need removing manually', '2019-07-24 02:03:25'),
(6538, 'en', 'smd_at_file_name', 'smd_at', 'site', 'File name', '2019-07-24 02:03:25'),
(6539, 'en', 'smd_at_file_not_deleted', 'smd_at', 'site', 'File {name} <strong>not</strong> deleted', '2019-07-24 02:03:25'),
(6540, 'en', 'smd_at_file_saved', 'smd_at', 'site', 'File {name} saved', '2019-07-24 02:03:25'),
(6541, 'en', 'smd_at_find_theme', 'smd_at', 'site', 'Browse themes', '2019-07-24 02:03:25'),
(6542, 'en', 'smd_at_folder', 'smd_at', 'site', '... to folder', '2019-07-24 02:03:25'),
(6543, 'en', 'smd_at_folders', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6544, 'en', 'smd_at_folder_deleted', 'smd_at', 'site', 'Folder {name} deleted', '2019-07-24 02:03:25'),
(6545, 'en', 'smd_at_folder_name', 'smd_at', 'site', 'Folder', '2019-07-24 02:03:25'),
(6546, 'en', 'smd_at_global_skin', 'smd_at', 'site', 'Default theme', '2019-07-24 02:03:25'),
(6547, 'en', 'smd_at_help_link', 'smd_at', 'site', '<a href=\"{link}\">Help</a>', '2019-07-24 02:03:25'),
(6548, 'en', 'smd_at_image_files', 'smd_at', 'site', 'Image', '2019-07-24 02:03:25'),
(6549, 'en', 'smd_at_import_failed', 'smd_at', 'site', 'Installation failed :-( Check the archive structure', '2019-07-24 02:03:25'),
(6550, 'en', 'smd_at_import_ok', 'smd_at', 'site', 'Installation successful.', '2019-07-24 02:03:25'),
(6551, 'en', 'smd_at_install_skin', 'smd_at', 'site', 'Install theme', '2019-07-24 02:03:25'),
(6552, 'en', 'smd_at_is_global', 'smd_at', 'site', '(default)', '2019-07-24 02:03:25'),
(6553, 'en', 'smd_at_js', 'smd_at', 'site', 'Javascript', '2019-07-24 02:03:25'),
(6554, 'en', 'smd_at_layout', 'smd_at', 'site', 'Layout method', '2019-07-24 02:03:25'),
(6555, 'en', 'smd_at_layout_grid', 'smd_at', 'site', 'Grid', '2019-07-24 02:03:25'),
(6556, 'en', 'smd_at_layout_list', 'smd_at', 'site', 'List', '2019-07-24 02:03:25'),
(6557, 'en', 'smd_at_list_title', 'smd_at', 'site', 'Installed themes', '2019-07-24 02:03:25'),
(6558, 'en', 'smd_at_manage_lbl', 'smd_at', 'site', 'Theme manager', '2019-07-24 02:03:25'),
(6559, 'en', 'smd_at_max_theme_size', 'smd_at', 'site', 'Max theme file size (bytes)', '2019-07-24 02:03:25'),
(6560, 'en', 'smd_at_mkdir_failed', 'smd_at', 'site', 'Cannot create directory for file {name}. Try manually creating it.', '2019-07-24 02:03:25'),
(6561, 'en', 'smd_at_name_empty', 'smd_at', 'site', 'You should give your theme a name', '2019-07-24 02:03:25'),
(6562, 'en', 'smd_at_new', 'smd_at', 'site', 'Create new theme', '2019-07-24 02:03:25'),
(6563, 'en', 'smd_at_new_cloneskin', 'smd_at', 'site', 'Name of copy', '2019-07-24 02:03:25'),
(6564, 'en', 'smd_at_new_file', 'smd_at', 'site', 'New file', '2019-07-24 02:03:25'),
(6565, 'en', 'smd_at_new_skin', 'smd_at', 'site', 'Theme name', '2019-07-24 02:03:25'),
(6566, 'en', 'smd_at_one_theme', 'smd_at', 'site', 'One theme', '2019-07-24 02:03:25'),
(6567, 'en', 'smd_at_other', 'smd_at', 'site', 'Other files', '2019-07-24 02:03:25'),
(6568, 'en', 'smd_at_other_files', 'smd_at', 'site', 'Others', '2019-07-24 02:03:25'),
(6569, 'en', 'smd_at_per_group', 'smd_at', 'site', 'per priv level', '2019-07-24 02:03:25'),
(6570, 'en', 'smd_at_per_site', 'smd_at', 'site', 'for all', '2019-07-24 02:03:25'),
(6571, 'en', 'smd_at_per_user', 'smd_at', 'site', 'per user', '2019-07-24 02:03:25'),
(6572, 'en', 'smd_at_php', 'smd_at', 'site', 'PHP', '2019-07-24 02:03:25'),
(6573, 'en', 'smd_at_prefs_deleted', 'smd_at', 'site', 'Preferences deleted', '2019-07-24 02:03:25'),
(6574, 'en', 'smd_at_prefs_installed', 'smd_at', 'site', 'Preferences installed', '2019-07-24 02:03:25'),
(6575, 'en', 'smd_at_prefs_title', 'smd_at', 'site', 'Admin theme preferences', '2019-07-24 02:03:25'),
(6576, 'en', 'smd_at_preview', 'smd_at', 'site', 'Preview of {name}', '2019-07-24 02:03:25'),
(6577, 'en', 'smd_at_preview_image', 'smd_at', 'site', 'View image', '2019-07-24 02:03:25'),
(6578, 'en', 'smd_at_renamed', 'smd_at', 'site', 'Renamed to {name}', '2019-07-24 02:03:25'),
(6579, 'en', 'smd_at_rename_failed', 'smd_at', 'site', 'Rename failed', '2019-07-24 02:03:25'),
(6580, 'en', 'smd_at_scss', 'smd_at', 'site', 'SCSS', '2019-07-24 02:03:25'),
(6581, 'en', 'smd_at_set', 'smd_at', 'site', 'Set', '2019-07-24 02:03:25'),
(6582, 'en', 'smd_at_setup', 'smd_at', 'site', 'Preferences', '2019-07-24 02:03:25'),
(6583, 'en', 'smd_at_skinner', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6584, 'en', 'smd_at_skin_cloned', 'smd_at', 'site', 'Theme extended as {name}', '2019-07-24 02:03:25'),
(6585, 'en', 'smd_at_skin_created', 'smd_at', 'site', 'Theme {name} created', '2019-07-24 02:03:25'),
(6586, 'en', 'smd_at_skin_deleted', 'smd_at', 'site', 'Theme {name} deleted', '2019-07-24 02:03:25'),
(6587, 'en', 'smd_at_skin_exists', 'smd_at', 'site', 'Theme {name} already exists', '2019-07-24 02:03:25'),
(6588, 'en', 'smd_at_skin_files', 'smd_at', 'site', 'Theme files', '2019-07-24 02:03:25'),
(6589, 'en', 'smd_at_skin_gbl', 'smd_at', 'site', 'Theme', '2019-07-24 02:03:25'),
(6590, 'en', 'smd_at_skin_groups', 'smd_at', 'site', 'Group themes', '2019-07-24 02:03:25'),
(6591, 'en', 'smd_at_skin_switched', 'smd_at', 'site', 'Theme switched to {skin}', '2019-07-24 02:03:25'),
(6592, 'en', 'smd_at_skin_system', 'smd_at', 'site', 'Theme system', '2019-07-24 02:03:25'),
(6593, 'en', 'smd_at_ssc', 'smd_at', 'site', 'Styleplate', '2019-07-24 02:03:25'),
(6594, 'en', 'smd_at_supported_import', 'smd_at', 'site', 'Supported import types: {types}', '2019-07-24 02:03:25'),
(6595, 'en', 'smd_at_tab_name', 'smd_at', 'site', 'Admin themes', '2019-07-24 02:03:25'),
(6596, 'en', 'smd_at_textile', 'smd_at', 'site', 'Textile', '2019-07-24 02:03:25'),
(6597, 'en', 'smd_at_thumbsize', 'smd_at', 'site', 'Thumb dimensions', '2019-07-24 02:03:25'),
(6598, 'en', 'smd_at_times', 'smd_at', 'site', 'x', '2019-07-24 02:03:25'),
(6599, 'en', 'smd_at_txt', 'smd_at', 'site', 'Text', '2019-07-24 02:03:25'),
(6600, 'en', 'smd_at_unsupported_compressiontype', 'smd_at', 'site', '{crush} is not a supported compression format in this installation', '2019-07-24 02:03:25'),
(6601, 'en', 'smd_at_unsupported_filetype', 'smd_at', 'site', 'File is not of a supported type', '2019-07-24 02:03:25'),
(6602, 'en', 'smd_at_unsupported_fudge', 'smd_at', 'site', ': hit Save again to store anyway', '2019-07-24 02:03:25'),
(6603, 'en', 'smd_at_upload_report', 'smd_at', 'site', 'Uploaded {num_success} files(s) {uploaded}. Failed: {num_fail}', '2019-07-24 02:03:25'),
(6604, 'en', 'smd_at_version', 'smd_at', 'site', 'v', '2019-07-24 02:03:25'),
(6605, 'de', 'etc_cache_actions', 'etc_cache', 'site', 'Actions', '2019-07-24 02:17:02'),
(6606, 'de', 'etc_cache_cached_at', 'etc_cache', 'site', 'Cached at', '2019-07-24 02:17:02'),
(6607, 'de', 'etc_cache_filter', 'etc_cache', 'site', 'Filter', '2019-07-24 02:17:02'),
(6608, 'de', 'etc_cache_heading', 'etc_cache', 'site', 'Cached items', '2019-07-24 02:17:02'),
(6609, 'de', 'etc_cache_no_cached_items', 'etc_cache', 'site', 'No cached items recorded.', '2019-07-24 02:17:02'),
(6610, 'de', 'etc_cache_reset', 'etc_cache', 'site', 'Reset', '2019-07-24 02:17:02'),
(6611, 'de', 'etc_cache_tab', 'etc_cache', 'site', 'Cache', '2019-07-24 02:17:02'),
(6612, 'en-us', 'etc_cache_actions', 'etc_cache', 'site', 'Actions', '2019-07-24 02:17:02'),
(6613, 'en-us', 'etc_cache_cached_at', 'etc_cache', 'site', 'Cached at', '2019-07-24 02:17:02'),
(6614, 'en-us', 'etc_cache_filter', 'etc_cache', 'site', 'Filter', '2019-07-24 02:17:02'),
(6615, 'en-us', 'etc_cache_heading', 'etc_cache', 'site', 'Cached items', '2019-07-24 02:17:02'),
(6616, 'en-us', 'etc_cache_no_cached_items', 'etc_cache', 'site', 'No cached items recorded.', '2019-07-24 02:17:02'),
(6617, 'en-us', 'etc_cache_reset', 'etc_cache', 'site', 'Reset', '2019-07-24 02:17:02'),
(6618, 'en-us', 'etc_cache_tab', 'etc_cache', 'site', 'Cache', '2019-07-24 02:17:02'),
(6619, 'el', 'etc_cache_actions', 'etc_cache', 'site', 'Actions', '2019-07-24 02:17:02'),
(6620, 'el', 'etc_cache_cached_at', 'etc_cache', 'site', 'Cached at', '2019-07-24 02:17:02'),
(6621, 'el', 'etc_cache_filter', 'etc_cache', 'site', 'Filter', '2019-07-24 02:17:02'),
(6622, 'el', 'etc_cache_heading', 'etc_cache', 'site', 'Cached items', '2019-07-24 02:17:02'),
(6623, 'el', 'etc_cache_no_cached_items', 'etc_cache', 'site', 'No cached items recorded.', '2019-07-24 02:17:02'),
(6624, 'el', 'etc_cache_reset', 'etc_cache', 'site', 'Reset', '2019-07-24 02:17:02'),
(6625, 'el', 'etc_cache_tab', 'etc_cache', 'site', 'Cache', '2019-07-24 02:17:02'),
(6626, 'en', 'etc_cache_actions', 'etc_cache', 'site', 'Actions', '2019-07-24 02:17:02'),
(6627, 'en', 'etc_cache_cached_at', 'etc_cache', 'site', 'Cached at', '2019-07-24 02:17:02'),
(6628, 'en', 'etc_cache_filter', 'etc_cache', 'site', 'Filter', '2019-07-24 02:17:02'),
(6629, 'en', 'etc_cache_heading', 'etc_cache', 'site', 'Cached items', '2019-07-24 02:17:02'),
(6630, 'en', 'etc_cache_no_cached_items', 'etc_cache', 'site', 'No cached items recorded.', '2019-07-24 02:17:02'),
(6631, 'en', 'etc_cache_reset', 'etc_cache', 'site', 'Reset', '2019-07-24 02:17:02'),
(6632, 'en', 'etc_cache_tab', 'etc_cache', 'site', 'Cache', '2019-07-24 02:17:02'),
(6633, 'de', 'words', 'admin', 'site', 'Wörter', '2019-07-24 03:40:38'),
(6634, 'en-us', 'words', 'admin', 'site', 'Wörter', '2019-07-24 03:40:38'),
(6635, 'el', 'words', 'admin', 'site', 'Wörter', '2019-07-24 03:40:38'),
(6636, 'en', 'words', 'admin', 'site', 'Wörter', '2019-07-24 03:40:38'),
(6637, 'de', 'com_connect_checkbox', 'public', 'site', 'Checkbox', '2020-05-20 22:42:30'),
(6638, 'de', 'com_connect_contact', 'public', 'site', 'Kontakt', '2020-05-20 22:42:30'),
(6639, 'de', 'com_connect_email', 'public', 'site', 'E-Mail', '2020-05-20 22:42:30'),
(6640, 'de', 'com_connect_email_subject', 'public', 'site', '{site} > Anfrage', '2020-05-20 22:42:30'),
(6641, 'de', 'com_connect_email_thanks', 'public', 'site', 'Vielen Dank, Ihre Nachricht wurde gesendet.', '2020-05-20 22:42:30'),
(6642, 'de', 'com_connect_field_missing', 'public', 'site', 'Erforderliche Eingabe im Feld <strong>{field}</strong> fehlt.', '2020-05-20 22:42:30'),
(6643, 'de', 'com_connect_format_warning', 'public', 'site', 'Eingabe {value} im Feld <strong>{field}</strong> entspricht nicht dem erwarteten Format.', '2020-05-20 22:42:30'),
(6644, 'de', 'com_connect_form_expired', 'public', 'site', 'Dieses Formular ist abgelaufen, bitte versuchen Sie es erneut.', '2020-05-20 22:42:30'),
(6645, 'de', 'com_connect_form_used', 'public', 'site', 'Dieses Formular wurde bereits gesendet. Bitte laden Sie das Formular noch einmal.', '2020-05-20 22:42:30'),
(6646, 'de', 'com_connect_general_inquiry', 'public', 'site', 'Allgemeine Anfrage', '2020-05-20 22:42:30'),
(6647, 'de', 'com_connect_invalid_email', 'public', 'site', '<strong>{email}</strong> ist keine gültige E-Mailadresse.', '2020-05-20 22:42:30'),
(6648, 'de', 'com_connect_invalid_host', 'public', 'site', '<strong>{host}</strong> ist kein gültiger E-Mail-Server.', '2020-05-20 22:42:30'),
(6649, 'de', 'com_connect_invalid_utf8', 'public', 'site', '<strong>{field}</strong> enthält ungültige UTF-8-Zeichen.', '2020-05-20 22:42:30'),
(6650, 'de', 'com_connect_invalid_value', 'public', 'site', 'Ungültiger Wert für <strong>{field}</strong>, <strong>{value}</strong> ist keine verfügbare Option.', '2020-05-20 22:42:30'),
(6651, 'de', 'com_connect_mail_sorry', 'public', 'site', 'Leider kann keine E-Mail gesendet werden.', '2020-05-20 22:42:30'),
(6652, 'de', 'com_connect_maxval_warning', 'public', 'site', '<strong>{field}</strong> darf {value} nicht überschreiten.', '2020-05-20 22:42:30'),
(6653, 'de', 'com_connect_max_warning', 'public', 'site', '<strong>{field}</strong> darf nicht länger als {value} Zeichen sein.', '2020-05-20 22:42:30'),
(6654, 'de', 'com_connect_message', 'public', 'site', 'Nachricht', '2020-05-20 22:42:30'),
(6655, 'de', 'com_connect_minval_warning', 'public', 'site', '<strong>{field}</strong> darf {value} nicht unterschreiten.', '2020-05-20 22:42:30'),
(6656, 'de', 'com_connect_min_warning', 'public', 'site', '<strong>{field}</strong> darf nicht kürzer als {value} Zeichen sein.', '2020-05-20 22:42:30'),
(6657, 'de', 'com_connect_name', 'public', 'site', 'Name', '2020-05-20 22:42:30'),
(6658, 'de', 'com_connect_option', 'public', 'site', 'Option', '2020-05-20 22:42:30'),
(6659, 'de', 'com_connect_pattern_warning', 'public', 'site', '<strong>{field}</strong> entspricht nicht dem Muster {value}.', '2020-05-20 22:42:30'),
(6660, 'de', 'com_connect_radio', 'public', 'site', 'Radio', '2020-05-20 22:42:30'),
(6661, 'de', 'com_connect_recipient', 'public', 'site', 'Empfänger', '2020-05-20 22:42:30'),
(6662, 'de', 'com_connect_refresh', 'public', 'site', 'Bitte folgen Sie diesem Link, falls die Seite icht automatisch neu geladen wird.', '2020-05-20 22:42:30'),
(6663, 'de', 'com_connect_secret', 'public', 'site', 'Geheimnis', '2020-05-20 22:42:30'),
(6664, 'de', 'com_connect_send', 'public', 'site', 'Senden', '2020-05-20 22:42:30'),
(6665, 'de', 'com_connect_send_article', 'public', 'site', 'Artikel senden', '2020-05-20 22:42:30'),
(6666, 'de', 'com_connect_spam', 'public', 'site', 'Danke, wir brauchen keinen Spam!', '2020-05-20 22:42:30'),
(6667, 'de', 'com_connect_text', 'public', 'site', 'Text', '2020-05-20 22:42:30'),
(6668, 'de', 'com_connect_to_missing', 'public', 'site', '<strong>To</strong> E-Mail-Adresse fehlt.', '2020-05-20 22:42:30'),
(6669, 'en-us', 'com_connect_checkbox', 'public', 'site', 'Checkbox', '2020-05-20 22:42:30'),
(6670, 'en-us', 'com_connect_contact', 'public', 'site', 'Contact', '2020-05-20 22:42:30'),
(6671, 'en-us', 'com_connect_email', 'public', 'site', 'Email', '2020-05-20 22:42:30'),
(6672, 'en-us', 'com_connect_email_subject', 'public', 'site', '{site} > Enquiry', '2020-05-20 22:42:30'),
(6673, 'en-us', 'com_connect_email_thanks', 'public', 'site', 'Thank you, your message has been sent.', '2020-05-20 22:42:30'),
(6674, 'en-us', 'com_connect_field_missing', 'public', 'site', 'Required field <strong>{field}</strong> is missing.', '2020-05-20 22:42:30'),
(6675, 'en-us', 'com_connect_format_warning', 'public', 'site', 'Value {value} in <strong>{field}</strong> is not of the expected format.', '2020-05-20 22:42:30'),
(6676, 'en-us', 'com_connect_form_expired', 'public', 'site', 'The form has expired, please try again.', '2020-05-20 22:42:30'),
(6677, 'en-us', 'com_connect_form_used', 'public', 'site', 'The form was already submitted, please fill out a new form.', '2020-05-20 22:42:30'),
(6678, 'en-us', 'com_connect_general_inquiry', 'public', 'site', 'General enquiry', '2020-05-20 22:42:30'),
(6679, 'en-us', 'com_connect_invalid_email', 'public', 'site', '<strong>{email}</strong> is not a valid email address.', '2020-05-20 22:42:30'),
(6680, 'en-us', 'com_connect_invalid_host', 'public', 'site', '<strong>{host}</strong> is not a valid email host.', '2020-05-20 22:42:30'),
(6681, 'en-us', 'com_connect_invalid_utf8', 'public', 'site', '<strong>{field}</strong> contains invalid UTF-8 characters.', '2020-05-20 22:42:30'),
(6682, 'en-us', 'com_connect_invalid_value', 'public', 'site', 'Invalid value for <strong>{field}</strong>: <strong>{value}</strong> is not one of the available options.', '2020-05-20 22:42:30'),
(6683, 'en-us', 'com_connect_mail_sorry', 'public', 'site', 'Sorry, unable to send email.', '2020-05-20 22:42:30'),
(6684, 'en-us', 'com_connect_maxval_warning', 'public', 'site', '<strong>{field}</strong> must not exceed {value}.', '2020-05-20 22:42:30'),
(6685, 'en-us', 'com_connect_max_warning', 'public', 'site', '<strong>{field}</strong> must not contain more than {value} characters.', '2020-05-20 22:42:30'),
(6686, 'en-us', 'com_connect_message', 'public', 'site', 'Message', '2020-05-20 22:42:30'),
(6687, 'en-us', 'com_connect_minval_warning', 'public', 'site', '<strong>{field}</strong> must be at least {value}.', '2020-05-20 22:42:30'),
(6688, 'en-us', 'com_connect_min_warning', 'public', 'site', '<strong>{field}</strong> must contain at least {value} characters.', '2020-05-20 22:42:30'),
(6689, 'en-us', 'com_connect_name', 'public', 'site', 'Name', '2020-05-20 22:42:30'),
(6690, 'en-us', 'com_connect_option', 'public', 'site', 'Option', '2020-05-20 22:42:30'),
(6691, 'en-us', 'com_connect_pattern_warning', 'public', 'site', '<strong>{field}</strong> does not match the pattern {value}.', '2020-05-20 22:42:30'),
(6692, 'en-us', 'com_connect_radio', 'public', 'site', 'Radio', '2020-05-20 22:42:30'),
(6693, 'en-us', 'com_connect_recipient', 'public', 'site', 'Recipient', '2020-05-20 22:42:30'),
(6694, 'en-us', 'com_connect_refresh', 'public', 'site', 'Follow this link if the page does not refresh automatically.', '2020-05-20 22:42:30'),
(6695, 'en-us', 'com_connect_secret', 'public', 'site', 'Secret', '2020-05-20 22:42:30'),
(6696, 'en-us', 'com_connect_send', 'public', 'site', 'Send', '2020-05-20 22:42:30'),
(6697, 'en-us', 'com_connect_send_article', 'public', 'site', 'Send article', '2020-05-20 22:42:30'),
(6698, 'en-us', 'com_connect_spam', 'public', 'site', 'We do not accept spam, thank you!', '2020-05-20 22:42:30'),
(6699, 'en-us', 'com_connect_text', 'public', 'site', 'Text', '2020-05-20 22:42:30'),
(6700, 'en-us', 'com_connect_to_missing', 'public', 'site', '<strong>To</strong> email address is missing.', '2020-05-20 22:42:30'),
(6701, 'el', 'com_connect_checkbox', 'public', 'site', 'Checkbox', '2020-05-20 22:42:30'),
(6702, 'el', 'com_connect_contact', 'public', 'site', 'Contact', '2020-05-20 22:42:30'),
(6703, 'el', 'com_connect_email', 'public', 'site', 'Email', '2020-05-20 22:42:30'),
(6704, 'el', 'com_connect_email_subject', 'public', 'site', '{site} > Enquiry', '2020-05-20 22:42:30'),
(6705, 'el', 'com_connect_email_thanks', 'public', 'site', 'Thank you, your message has been sent.', '2020-05-20 22:42:30'),
(6706, 'el', 'com_connect_field_missing', 'public', 'site', 'Required field <strong>{field}</strong> is missing.', '2020-05-20 22:42:30'),
(6707, 'el', 'com_connect_format_warning', 'public', 'site', 'Value {value} in <strong>{field}</strong> is not of the expected format.', '2020-05-20 22:42:30'),
(6708, 'el', 'com_connect_form_expired', 'public', 'site', 'The form has expired, please try again.', '2020-05-20 22:42:30'),
(6709, 'el', 'com_connect_form_used', 'public', 'site', 'The form was already submitted, please fill out a new form.', '2020-05-20 22:42:30'),
(6710, 'el', 'com_connect_general_inquiry', 'public', 'site', 'General enquiry', '2020-05-20 22:42:30'),
(6711, 'el', 'com_connect_invalid_email', 'public', 'site', '<strong>{email}</strong> is not a valid email address.', '2020-05-20 22:42:30'),
(6712, 'el', 'com_connect_invalid_host', 'public', 'site', '<strong>{host}</strong> is not a valid email host.', '2020-05-20 22:42:30'),
(6713, 'el', 'com_connect_invalid_utf8', 'public', 'site', '<strong>{field}</strong> contains invalid UTF-8 characters.', '2020-05-20 22:42:30'),
(6714, 'el', 'com_connect_invalid_value', 'public', 'site', 'Invalid value for <strong>{field}</strong>: <strong>{value}</strong> is not one of the available options.', '2020-05-20 22:42:30'),
(6715, 'el', 'com_connect_mail_sorry', 'public', 'site', 'Sorry, unable to send email.', '2020-05-20 22:42:30'),
(6716, 'el', 'com_connect_maxval_warning', 'public', 'site', '<strong>{field}</strong> must not exceed {value}.', '2020-05-20 22:42:30'),
(6717, 'el', 'com_connect_max_warning', 'public', 'site', '<strong>{field}</strong> must not contain more than {value} characters.', '2020-05-20 22:42:30'),
(6718, 'el', 'com_connect_message', 'public', 'site', 'Message', '2020-05-20 22:42:30'),
(6719, 'el', 'com_connect_minval_warning', 'public', 'site', '<strong>{field}</strong> must be at least {value}.', '2020-05-20 22:42:30'),
(6720, 'el', 'com_connect_min_warning', 'public', 'site', '<strong>{field}</strong> must contain at least {value} characters.', '2020-05-20 22:42:30'),
(6721, 'el', 'com_connect_name', 'public', 'site', 'Name', '2020-05-20 22:42:30'),
(6722, 'el', 'com_connect_option', 'public', 'site', 'Option', '2020-05-20 22:42:30'),
(6723, 'el', 'com_connect_pattern_warning', 'public', 'site', '<strong>{field}</strong> does not match the pattern {value}.', '2020-05-20 22:42:30'),
(6724, 'el', 'com_connect_radio', 'public', 'site', 'Radio', '2020-05-20 22:42:30'),
(6725, 'el', 'com_connect_recipient', 'public', 'site', 'Recipient', '2020-05-20 22:42:30'),
(6726, 'el', 'com_connect_refresh', 'public', 'site', 'Follow this link if the page does not refresh automatically.', '2020-05-20 22:42:30'),
(6727, 'el', 'com_connect_secret', 'public', 'site', 'Secret', '2020-05-20 22:42:30'),
(6728, 'el', 'com_connect_send', 'public', 'site', 'Send', '2020-05-20 22:42:30'),
(6729, 'el', 'com_connect_send_article', 'public', 'site', 'Send article', '2020-05-20 22:42:30'),
(6730, 'el', 'com_connect_spam', 'public', 'site', 'We do not accept spam, thank you!', '2020-05-20 22:42:30'),
(6731, 'el', 'com_connect_text', 'public', 'site', 'Text', '2020-05-20 22:42:30'),
(6732, 'el', 'com_connect_to_missing', 'public', 'site', '<strong>To</strong> email address is missing.', '2020-05-20 22:42:30'),
(6733, 'en', 'com_connect_checkbox', 'public', 'site', 'Checkbox', '2020-05-20 22:42:30'),
(6734, 'en', 'com_connect_contact', 'public', 'site', 'Contact', '2020-05-20 22:42:30'),
(6735, 'en', 'com_connect_email', 'public', 'site', 'Email', '2020-05-20 22:42:30'),
(6736, 'en', 'com_connect_email_subject', 'public', 'site', '{site} > Enquiry', '2020-05-20 22:42:30'),
(6737, 'en', 'com_connect_email_thanks', 'public', 'site', 'Thank you, your message has been sent.', '2020-05-20 22:42:30'),
(6738, 'en', 'com_connect_field_missing', 'public', 'site', 'Required field <strong>{field}</strong> is missing.', '2020-05-20 22:42:30'),
(6739, 'en', 'com_connect_format_warning', 'public', 'site', 'Value {value} in <strong>{field}</strong> is not of the expected format.', '2020-05-20 22:42:30'),
(6740, 'en', 'com_connect_form_expired', 'public', 'site', 'The form has expired, please try again.', '2020-05-20 22:42:30'),
(6741, 'en', 'com_connect_form_used', 'public', 'site', 'The form was already submitted, please fill out a new form.', '2020-05-20 22:42:30'),
(6742, 'en', 'com_connect_general_inquiry', 'public', 'site', 'General enquiry', '2020-05-20 22:42:30'),
(6743, 'en', 'com_connect_invalid_email', 'public', 'site', '<strong>{email}</strong> is not a valid email address.', '2020-05-20 22:42:30'),
(6744, 'en', 'com_connect_invalid_host', 'public', 'site', '<strong>{host}</strong> is not a valid email host.', '2020-05-20 22:42:30'),
(6745, 'en', 'com_connect_invalid_utf8', 'public', 'site', '<strong>{field}</strong> contains invalid UTF-8 characters.', '2020-05-20 22:42:30'),
(6746, 'en', 'com_connect_invalid_value', 'public', 'site', 'Invalid value for <strong>{field}</strong>: <strong>{value}</strong> is not one of the available options.', '2020-05-20 22:42:30'),
(6747, 'en', 'com_connect_mail_sorry', 'public', 'site', 'Sorry, unable to send email.', '2020-05-20 22:42:30'),
(6748, 'en', 'com_connect_maxval_warning', 'public', 'site', '<strong>{field}</strong> must not exceed {value}.', '2020-05-20 22:42:30'),
(6749, 'en', 'com_connect_max_warning', 'public', 'site', '<strong>{field}</strong> must not contain more than {value} characters.', '2020-05-20 22:42:30'),
(6750, 'en', 'com_connect_message', 'public', 'site', 'Message', '2020-05-20 22:42:30'),
(6751, 'en', 'com_connect_minval_warning', 'public', 'site', '<strong>{field}</strong> must be at least {value}.', '2020-05-20 22:42:30'),
(6752, 'en', 'com_connect_min_warning', 'public', 'site', '<strong>{field}</strong> must contain at least {value} characters.', '2020-05-20 22:42:30'),
(6753, 'en', 'com_connect_name', 'public', 'site', 'Name', '2020-05-20 22:42:30'),
(6754, 'en', 'com_connect_option', 'public', 'site', 'Option', '2020-05-20 22:42:30'),
(6755, 'en', 'com_connect_pattern_warning', 'public', 'site', '<strong>{field}</strong> does not match the pattern {value}.', '2020-05-20 22:42:30'),
(6756, 'en', 'com_connect_radio', 'public', 'site', 'Radio', '2020-05-20 22:42:30'),
(6757, 'en', 'com_connect_recipient', 'public', 'site', 'Recipient', '2020-05-20 22:42:30'),
(6758, 'en', 'com_connect_refresh', 'public', 'site', 'Follow this link if the page does not refresh automatically.', '2020-05-20 22:42:30'),
(6759, 'en', 'com_connect_secret', 'public', 'site', 'Secret', '2020-05-20 22:42:30'),
(6760, 'en', 'com_connect_send', 'public', 'site', 'Send', '2020-05-20 22:42:30'),
(6761, 'en', 'com_connect_send_article', 'public', 'site', 'Send article', '2020-05-20 22:42:30'),
(6762, 'en', 'com_connect_spam', 'public', 'site', 'We do not accept spam, thank you!', '2020-05-20 22:42:30'),
(6763, 'en', 'com_connect_text', 'public', 'site', 'Text', '2020-05-20 22:42:30'),
(6764, 'en', 'com_connect_to_missing', 'public', 'site', '<strong>To</strong> email address is missing.', '2020-05-20 22:42:30'),
(6765, 'de', 'rah_wrach_frontpage_label', 'admin', 'site', 'FRONT', '2019-07-24 12:51:58'),
(6766, 'de', 'rah_wrach_frontpage_tooltip', 'admin', 'site', 'Published on the frontpage', '2019-07-24 12:51:58'),
(6767, 'de', 'rah_wrach_rss_label', 'admin', 'site', 'RSS', '2019-07-24 12:51:58'),
(6768, 'de', 'rah_wrach_rss_tooltip', 'admin', 'site', 'Included in RSS feeds', '2019-07-24 12:51:58'),
(6769, 'de', 'rah_wrach', 'admin', 'site', 'Wrach', '2019-07-24 12:51:58'),
(6770, 'de', 'rah_wrach_show_sections', 'admin', 'site', 'Display the following sections (comma-separated)', '2019-07-24 12:51:58'),
(6771, 'de', 'rah_wrach_hide_section_input', 'admin', 'site', 'Hide the section input?', '2019-07-24 12:51:58'),
(6772, 'en-us', 'rah_wrach_frontpage_label', 'admin', 'site', 'FRONT', '2019-07-24 12:51:58'),
(6773, 'en-us', 'rah_wrach_frontpage_tooltip', 'admin', 'site', 'Published on the frontpage', '2019-07-24 12:51:58'),
(6774, 'en-us', 'rah_wrach_rss_label', 'admin', 'site', 'RSS', '2019-07-24 12:51:58'),
(6775, 'en-us', 'rah_wrach_rss_tooltip', 'admin', 'site', 'Included in RSS feeds', '2019-07-24 12:51:58'),
(6776, 'en-us', 'rah_wrach', 'admin', 'site', 'Wrach', '2019-07-24 12:51:58'),
(6777, 'en-us', 'rah_wrach_show_sections', 'admin', 'site', 'Display the following sections (comma-separated)', '2019-07-24 12:51:58'),
(6778, 'en-us', 'rah_wrach_hide_section_input', 'admin', 'site', 'Hide the section input?', '2019-07-24 12:51:58'),
(6779, 'el', 'rah_wrach_frontpage_label', 'admin', 'site', 'FRONT', '2019-07-24 12:51:58'),
(6780, 'el', 'rah_wrach_frontpage_tooltip', 'admin', 'site', 'Published on the frontpage', '2019-07-24 12:51:58'),
(6781, 'el', 'rah_wrach_rss_label', 'admin', 'site', 'RSS', '2019-07-24 12:51:58'),
(6782, 'el', 'rah_wrach_rss_tooltip', 'admin', 'site', 'Included in RSS feeds', '2019-07-24 12:51:58'),
(6783, 'el', 'rah_wrach', 'admin', 'site', 'Wrach', '2019-07-24 12:51:58'),
(6784, 'el', 'rah_wrach_show_sections', 'admin', 'site', 'Display the following sections (comma-separated)', '2019-07-24 12:51:58'),
(6785, 'el', 'rah_wrach_hide_section_input', 'admin', 'site', 'Hide the section input?', '2019-07-24 12:51:58'),
(6786, 'en', 'rah_wrach_frontpage_label', 'admin', 'site', 'FRONT', '2019-07-24 12:51:58'),
(6787, 'en', 'rah_wrach_frontpage_tooltip', 'admin', 'site', 'Published on the frontpage', '2019-07-24 12:51:58'),
(6788, 'en', 'rah_wrach_rss_label', 'admin', 'site', 'RSS', '2019-07-24 12:51:58'),
(6789, 'en', 'rah_wrach_rss_tooltip', 'admin', 'site', 'Included in RSS feeds', '2019-07-24 12:51:58'),
(6790, 'en', 'rah_wrach', 'admin', 'site', 'Wrach', '2019-07-24 12:51:58'),
(6791, 'en', 'rah_wrach_show_sections', 'admin', 'site', 'Display the following sections (comma-separated)', '2019-07-24 12:51:58'),
(6792, 'en', 'rah_wrach_hide_section_input', 'admin', 'site', 'Hide the section input?', '2019-07-24 12:51:58'),
(6793, 'de', 'rah_bitly', 'admin', 'site', 'Bitly integration', '2019-07-24 12:24:11'),
(6794, 'de', 'rah_bitly_login', 'admin', 'site', 'Bitly login', '2019-07-24 12:24:11'),
(6795, 'de', 'rah_bitly_apikey', 'admin', 'site', 'API key', '2019-07-24 12:24:11'),
(6796, 'de', 'rah_bitly_field', 'admin', 'site', 'Store in a custom field', '2019-07-24 12:24:11'),
(6797, 'en-us', 'rah_bitly', 'admin', 'site', 'Bitly integration', '2019-07-24 12:24:11'),
(6798, 'en-us', 'rah_bitly_login', 'admin', 'site', 'Bitly login', '2019-07-24 12:24:11'),
(6799, 'en-us', 'rah_bitly_apikey', 'admin', 'site', 'API key', '2019-07-24 12:24:11'),
(6800, 'en-us', 'rah_bitly_field', 'admin', 'site', 'Store in a custom field', '2019-07-24 12:24:11'),
(6801, 'el', 'rah_bitly', 'admin', 'site', 'Bitly integration', '2019-07-24 12:24:11'),
(6802, 'el', 'rah_bitly_login', 'admin', 'site', 'Bitly login', '2019-07-24 12:24:11'),
(6803, 'el', 'rah_bitly_apikey', 'admin', 'site', 'API key', '2019-07-24 12:24:11'),
(6804, 'el', 'rah_bitly_field', 'admin', 'site', 'Store in a custom field', '2019-07-24 12:24:11'),
(6805, 'en', 'rah_bitly', 'admin', 'site', 'Bitly integration', '2019-07-24 12:24:11'),
(6806, 'en', 'rah_bitly_login', 'admin', 'site', 'Bitly login', '2019-07-24 12:24:11'),
(6807, 'en', 'rah_bitly_apikey', 'admin', 'site', 'API key', '2019-07-24 12:24:11'),
(6808, 'en', 'rah_bitly_field', 'admin', 'site', 'Store in a custom field', '2019-07-24 12:24:11'),
(6809, 'de', 'rah_function_illegal_type', 'public', 'site', 'Resulted illegal type. \"{type}\" in not a scalar or an array.', '2019-07-24 12:31:34'),
(6810, 'en-us', 'rah_function_illegal_type', 'public', 'site', 'Resulted illegal type. \"{type}\" in not a scalar or an array.', '2019-07-24 12:31:34'),
(6811, 'el', 'rah_function_illegal_type', 'public', 'site', 'Resulted illegal type. \"{type}\" in not a scalar or an array.', '2019-07-24 12:31:35'),
(6812, 'en', 'rah_function_illegal_type', 'public', 'site', 'Resulted illegal type. \"{type}\" in not a scalar or an array.', '2019-07-24 12:31:35'),
(6813, 'de', 'rah_change_passwords', 'admin', 'site', 'Change password', '2019-07-24 12:38:12'),
(6814, 'de', 'rah_change_passwords_pass', 'admin', 'site', 'New password', '2019-07-24 12:38:12'),
(6815, 'de', 'rah_change_passwords_confirm', 'admin', 'site', 'Confirm new password', '2019-07-24 12:38:12'),
(6816, 'de', 'rah_change_passwords_email_pass', 'admin', 'site', 'Email the password?', '2019-07-24 12:38:12'),
(6817, 'de', 'rah_change_passwords_reset_session', 'admin', 'site', 'Reset session?', '2019-07-24 12:38:12'),
(6818, 'de', 'rah_change_passwords_confirm_error', 'admin', 'site', 'Passwords do not match.', '2019-07-24 12:38:12'),
(6819, 'de', 'rah_change_passwords_update_failed', 'admin', 'site', 'Saving changes failed.', '2019-07-24 12:38:12'),
(6820, 'de', 'rah_change_passwords_changed', 'admin', 'site', 'Password changed.', '2019-07-24 12:38:12'),
(6821, 'de', 'rah_change_passwords_mailed', 'admin', 'site', 'Password changed and sent to <strong>{email}</strong>.', '2019-07-24 12:38:12'),
(6822, 'de', 'rah_change_passwords_mailing_failed', 'admin', 'site', 'Mailing password to <strong>{email}</strong> failed.', '2019-07-24 12:38:12'),
(6823, 'en-us', 'rah_change_passwords', 'admin', 'site', 'Change password', '2019-07-24 12:38:12'),
(6824, 'en-us', 'rah_change_passwords_pass', 'admin', 'site', 'New password', '2019-07-24 12:38:12'),
(6825, 'en-us', 'rah_change_passwords_confirm', 'admin', 'site', 'Confirm new password', '2019-07-24 12:38:12'),
(6826, 'en-us', 'rah_change_passwords_email_pass', 'admin', 'site', 'Email the password?', '2019-07-24 12:38:12'),
(6827, 'en-us', 'rah_change_passwords_reset_session', 'admin', 'site', 'Reset session?', '2019-07-24 12:38:12'),
(6828, 'en-us', 'rah_change_passwords_confirm_error', 'admin', 'site', 'Passwords do not match.', '2019-07-24 12:38:12'),
(6829, 'en-us', 'rah_change_passwords_update_failed', 'admin', 'site', 'Saving changes failed.', '2019-07-24 12:38:12'),
(6830, 'en-us', 'rah_change_passwords_changed', 'admin', 'site', 'Password changed.', '2019-07-24 12:38:12'),
(6831, 'en-us', 'rah_change_passwords_mailed', 'admin', 'site', 'Password changed and sent to <strong>{email}</strong>.', '2019-07-24 12:38:12'),
(6832, 'en-us', 'rah_change_passwords_mailing_failed', 'admin', 'site', 'Mailing password to <strong>{email}</strong> failed.', '2019-07-24 12:38:12'),
(6833, 'el', 'rah_change_passwords', 'admin', 'site', 'Change password', '2019-07-24 12:38:12'),
(6834, 'el', 'rah_change_passwords_pass', 'admin', 'site', 'New password', '2019-07-24 12:38:12'),
(6835, 'el', 'rah_change_passwords_confirm', 'admin', 'site', 'Confirm new password', '2019-07-24 12:38:12'),
(6836, 'el', 'rah_change_passwords_email_pass', 'admin', 'site', 'Email the password?', '2019-07-24 12:38:12'),
(6837, 'el', 'rah_change_passwords_reset_session', 'admin', 'site', 'Reset session?', '2019-07-24 12:38:12'),
(6838, 'el', 'rah_change_passwords_confirm_error', 'admin', 'site', 'Passwords do not match.', '2019-07-24 12:38:12'),
(6839, 'el', 'rah_change_passwords_update_failed', 'admin', 'site', 'Saving changes failed.', '2019-07-24 12:38:12'),
(6840, 'el', 'rah_change_passwords_changed', 'admin', 'site', 'Password changed.', '2019-07-24 12:38:12'),
(6841, 'el', 'rah_change_passwords_mailed', 'admin', 'site', 'Password changed and sent to <strong>{email}</strong>.', '2019-07-24 12:38:12'),
(6842, 'el', 'rah_change_passwords_mailing_failed', 'admin', 'site', 'Mailing password to <strong>{email}</strong> failed.', '2019-07-24 12:38:12'),
(6843, 'en', 'rah_change_passwords', 'admin', 'site', 'Change password', '2019-07-24 12:38:12'),
(6844, 'en', 'rah_change_passwords_pass', 'admin', 'site', 'New password', '2019-07-24 12:38:12'),
(6845, 'en', 'rah_change_passwords_confirm', 'admin', 'site', 'Confirm new password', '2019-07-24 12:38:12'),
(6846, 'en', 'rah_change_passwords_email_pass', 'admin', 'site', 'Email the password?', '2019-07-24 12:38:12'),
(6847, 'en', 'rah_change_passwords_reset_session', 'admin', 'site', 'Reset session?', '2019-07-24 12:38:12'),
(6848, 'en', 'rah_change_passwords_confirm_error', 'admin', 'site', 'Passwords do not match.', '2019-07-24 12:38:12'),
(6849, 'en', 'rah_change_passwords_update_failed', 'admin', 'site', 'Saving changes failed.', '2019-07-24 12:38:12'),
(6850, 'en', 'rah_change_passwords_changed', 'admin', 'site', 'Password changed.', '2019-07-24 12:38:12'),
(6851, 'en', 'rah_change_passwords_mailed', 'admin', 'site', 'Password changed and sent to <strong>{email}</strong>.', '2019-07-24 12:38:12'),
(6852, 'en', 'rah_change_passwords_mailing_failed', 'admin', 'site', 'Mailing password to <strong>{email}</strong> failed.', '2019-07-24 12:38:12'),
(6853, 'de', 'rah_defcat', 'admin', 'site', 'Default article categories', '2019-07-24 18:20:12'),
(6854, 'de', 'rah_default_category_1', 'admin', 'site', 'Default category 1', '2019-07-24 18:20:12'),
(6855, 'de', 'rah_default_category_2', 'admin', 'site', 'Default category 2', '2019-07-24 18:20:12'),
(6856, 'en-us', 'rah_defcat', 'admin', 'site', 'Default article categories', '2019-07-24 18:20:12'),
(6857, 'en-us', 'rah_default_category_1', 'admin', 'site', 'Default category 1', '2019-07-24 18:20:12'),
(6858, 'en-us', 'rah_default_category_2', 'admin', 'site', 'Default category 2', '2019-07-24 18:20:12'),
(6859, 'el', 'rah_defcat', 'admin', 'site', 'Default article categories', '2019-07-24 18:20:12'),
(6860, 'el', 'rah_default_category_1', 'admin', 'site', 'Default category 1', '2019-07-24 18:20:12'),
(6861, 'el', 'rah_default_category_2', 'admin', 'site', 'Default category 2', '2019-07-24 18:20:12'),
(6862, 'en', 'rah_defcat', 'admin', 'site', 'Default article categories', '2019-07-24 18:20:12'),
(6863, 'en', 'rah_default_category_1', 'admin', 'site', 'Default category 1', '2019-07-24 18:20:12'),
(6864, 'en', 'rah_default_category_2', 'admin', 'site', 'Default category 2', '2019-07-24 18:20:12'),
(6865, 'de', 'rah_tabtor', 'admin', 'site', 'Tabtor', '2019-07-24 12:50:09'),
(6866, 'de', 'rah_tabtor_create_new', 'admin', 'site', 'Create rule', '2019-07-24 12:50:09'),
(6867, 'de', 'rah_tabtor_label', 'admin', 'site', 'Label', '2019-07-24 12:50:09'),
(6868, 'de', 'rah_tabtor_page', 'admin', 'site', 'Event', '2019-07-24 12:50:09'),
(6869, 'de', 'rah_tabtor_group', 'admin', 'site', 'Group', '2019-07-24 12:50:09'),
(6870, 'de', 'rah_tabtor_nothing_to_show', 'admin', 'site', 'Nothing to show yet. {link}.', '2019-07-24 12:50:09'),
(6871, 'de', 'rah_tabtor_start_by_link', 'admin', 'site', 'Start by creating your first rule', '2019-07-24 12:50:09'),
(6872, 'de', 'rah_tabtor_delete', 'admin', 'site', 'Delete', '2019-07-24 12:50:09'),
(6873, 'de', 'rah_tabtor_position', 'admin', 'site', 'Position', '2019-07-24 12:50:09'),
(6874, 'de', 'rah_tabtor_save', 'admin', 'site', 'Save', '2019-07-24 12:50:09'),
(6875, 'de', 'rah_tabtor_unknown_item', 'admin', 'site', 'Unknown item.', '2019-07-24 12:50:09'),
(6876, 'de', 'rah_tabtor_required_fields', 'admin', 'site', 'All fields are required.', '2019-07-24 12:50:09'),
(6877, 'de', 'rah_tabtor_updated', 'admin', 'site', 'Item updated.', '2019-07-24 12:50:09'),
(6878, 'de', 'rah_tabtor_saved', 'admin', 'site', 'Item saved.', '2019-07-24 12:50:09'),
(6879, 'de', 'rah_tabtor_removed', 'admin', 'site', 'Selected items removed.', '2019-07-24 12:50:09'),
(6880, 'de', 'rah_tabtor_save_failed', 'admin', 'site', 'The item was not saved due to an error. Please try again.', '2019-07-24 12:50:09'),
(6881, 'de', 'rah_tabtor_delete_failed', 'admin', 'site', 'Item(s) were not deleted due to an error. Please try again.', '2019-07-24 12:50:09'),
(6882, 'de', 'rah_tabtor_select_something', 'admin', 'site', 'Nothing selected.', '2019-07-24 12:50:09'),
(6883, 'de', 'rah_tabtor_already_exists', 'admin', 'site', 'Identical rule already exists.', '2019-07-24 12:50:09'),
(6884, 'en-us', 'rah_tabtor', 'admin', 'site', 'Tabtor', '2019-07-24 12:50:09'),
(6885, 'en-us', 'rah_tabtor_create_new', 'admin', 'site', 'Create rule', '2019-07-24 12:50:09'),
(6886, 'en-us', 'rah_tabtor_label', 'admin', 'site', 'Label', '2019-07-24 12:50:09'),
(6887, 'en-us', 'rah_tabtor_page', 'admin', 'site', 'Event', '2019-07-24 12:50:09'),
(6888, 'en-us', 'rah_tabtor_group', 'admin', 'site', 'Group', '2019-07-24 12:50:09');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(6889, 'en-us', 'rah_tabtor_nothing_to_show', 'admin', 'site', 'Nothing to show yet. {link}.', '2019-07-24 12:50:09'),
(6890, 'en-us', 'rah_tabtor_start_by_link', 'admin', 'site', 'Start by creating your first rule', '2019-07-24 12:50:09'),
(6891, 'en-us', 'rah_tabtor_delete', 'admin', 'site', 'Delete', '2019-07-24 12:50:09'),
(6892, 'en-us', 'rah_tabtor_position', 'admin', 'site', 'Position', '2019-07-24 12:50:09'),
(6893, 'en-us', 'rah_tabtor_save', 'admin', 'site', 'Save', '2019-07-24 12:50:09'),
(6894, 'en-us', 'rah_tabtor_unknown_item', 'admin', 'site', 'Unknown item.', '2019-07-24 12:50:09'),
(6895, 'en-us', 'rah_tabtor_required_fields', 'admin', 'site', 'All fields are required.', '2019-07-24 12:50:09'),
(6896, 'en-us', 'rah_tabtor_updated', 'admin', 'site', 'Item updated.', '2019-07-24 12:50:09'),
(6897, 'en-us', 'rah_tabtor_saved', 'admin', 'site', 'Item saved.', '2019-07-24 12:50:09'),
(6898, 'en-us', 'rah_tabtor_removed', 'admin', 'site', 'Selected items removed.', '2019-07-24 12:50:09'),
(6899, 'en-us', 'rah_tabtor_save_failed', 'admin', 'site', 'The item was not saved due to an error. Please try again.', '2019-07-24 12:50:09'),
(6900, 'en-us', 'rah_tabtor_delete_failed', 'admin', 'site', 'Item(s) were not deleted due to an error. Please try again.', '2019-07-24 12:50:09'),
(6901, 'en-us', 'rah_tabtor_select_something', 'admin', 'site', 'Nothing selected.', '2019-07-24 12:50:09'),
(6902, 'en-us', 'rah_tabtor_already_exists', 'admin', 'site', 'Identical rule already exists.', '2019-07-24 12:50:09'),
(6903, 'el', 'rah_tabtor', 'admin', 'site', 'Tabtor', '2019-07-24 12:50:09'),
(6904, 'el', 'rah_tabtor_create_new', 'admin', 'site', 'Create rule', '2019-07-24 12:50:09'),
(6905, 'el', 'rah_tabtor_label', 'admin', 'site', 'Label', '2019-07-24 12:50:09'),
(6906, 'el', 'rah_tabtor_page', 'admin', 'site', 'Event', '2019-07-24 12:50:09'),
(6907, 'el', 'rah_tabtor_group', 'admin', 'site', 'Group', '2019-07-24 12:50:09'),
(6908, 'el', 'rah_tabtor_nothing_to_show', 'admin', 'site', 'Nothing to show yet. {link}.', '2019-07-24 12:50:09'),
(6909, 'el', 'rah_tabtor_start_by_link', 'admin', 'site', 'Start by creating your first rule', '2019-07-24 12:50:09'),
(6910, 'el', 'rah_tabtor_delete', 'admin', 'site', 'Delete', '2019-07-24 12:50:09'),
(6911, 'el', 'rah_tabtor_position', 'admin', 'site', 'Position', '2019-07-24 12:50:09'),
(6912, 'el', 'rah_tabtor_save', 'admin', 'site', 'Save', '2019-07-24 12:50:09'),
(6913, 'el', 'rah_tabtor_unknown_item', 'admin', 'site', 'Unknown item.', '2019-07-24 12:50:09'),
(6914, 'el', 'rah_tabtor_required_fields', 'admin', 'site', 'All fields are required.', '2019-07-24 12:50:09'),
(6915, 'el', 'rah_tabtor_updated', 'admin', 'site', 'Item updated.', '2019-07-24 12:50:09'),
(6916, 'el', 'rah_tabtor_saved', 'admin', 'site', 'Item saved.', '2019-07-24 12:50:09'),
(6917, 'el', 'rah_tabtor_removed', 'admin', 'site', 'Selected items removed.', '2019-07-24 12:50:09'),
(6918, 'el', 'rah_tabtor_save_failed', 'admin', 'site', 'The item was not saved due to an error. Please try again.', '2019-07-24 12:50:09'),
(6919, 'el', 'rah_tabtor_delete_failed', 'admin', 'site', 'Item(s) were not deleted due to an error. Please try again.', '2019-07-24 12:50:09'),
(6920, 'el', 'rah_tabtor_select_something', 'admin', 'site', 'Nothing selected.', '2019-07-24 12:50:09'),
(6921, 'el', 'rah_tabtor_already_exists', 'admin', 'site', 'Identical rule already exists.', '2019-07-24 12:50:09'),
(6922, 'en', 'rah_tabtor', 'admin', 'site', 'Tabtor', '2019-07-24 12:50:09'),
(6923, 'en', 'rah_tabtor_create_new', 'admin', 'site', 'Create rule', '2019-07-24 12:50:09'),
(6924, 'en', 'rah_tabtor_label', 'admin', 'site', 'Label', '2019-07-24 12:50:09'),
(6925, 'en', 'rah_tabtor_page', 'admin', 'site', 'Event', '2019-07-24 12:50:09'),
(6926, 'en', 'rah_tabtor_group', 'admin', 'site', 'Group', '2019-07-24 12:50:09'),
(6927, 'en', 'rah_tabtor_nothing_to_show', 'admin', 'site', 'Nothing to show yet. {link}.', '2019-07-24 12:50:09'),
(6928, 'en', 'rah_tabtor_start_by_link', 'admin', 'site', 'Start by creating your first rule', '2019-07-24 12:50:09'),
(6929, 'en', 'rah_tabtor_delete', 'admin', 'site', 'Delete', '2019-07-24 12:50:09'),
(6930, 'en', 'rah_tabtor_position', 'admin', 'site', 'Position', '2019-07-24 12:50:09'),
(6931, 'en', 'rah_tabtor_save', 'admin', 'site', 'Save', '2019-07-24 12:50:09'),
(6932, 'en', 'rah_tabtor_unknown_item', 'admin', 'site', 'Unknown item.', '2019-07-24 12:50:09'),
(6933, 'en', 'rah_tabtor_required_fields', 'admin', 'site', 'All fields are required.', '2019-07-24 12:50:09'),
(6934, 'en', 'rah_tabtor_updated', 'admin', 'site', 'Item updated.', '2019-07-24 12:50:09'),
(6935, 'en', 'rah_tabtor_saved', 'admin', 'site', 'Item saved.', '2019-07-24 12:50:09'),
(6936, 'en', 'rah_tabtor_removed', 'admin', 'site', 'Selected items removed.', '2019-07-24 12:50:09'),
(6937, 'en', 'rah_tabtor_save_failed', 'admin', 'site', 'The item was not saved due to an error. Please try again.', '2019-07-24 12:50:09'),
(6938, 'en', 'rah_tabtor_delete_failed', 'admin', 'site', 'Item(s) were not deleted due to an error. Please try again.', '2019-07-24 12:50:09'),
(6939, 'en', 'rah_tabtor_select_something', 'admin', 'site', 'Nothing selected.', '2019-07-24 12:50:09'),
(6940, 'en', 'rah_tabtor_already_exists', 'admin', 'site', 'Identical rule already exists.', '2019-07-24 12:50:09'),
(6941, 'de', 'rah_unlog_me_auto', 'admin', 'site', 'Exclude site authors\' IPs from the logs?', '2019-07-24 12:51:03'),
(6942, 'de', 'rah_unlog_me_ip', 'admin', 'site', 'Additional IPs to exclude from the logs (comma-separated)', '2019-07-24 12:51:03'),
(6943, 'en-us', 'rah_unlog_me_auto', 'admin', 'site', 'Exclude site authors\' IPs from the logs?', '2019-07-24 12:51:03'),
(6944, 'en-us', 'rah_unlog_me_ip', 'admin', 'site', 'Additional IPs to exclude from the logs (comma-separated)', '2019-07-24 12:51:03'),
(6945, 'el', 'rah_unlog_me_auto', 'admin', 'site', 'Exclude site authors\' IPs from the logs?', '2019-07-24 12:51:03'),
(6946, 'el', 'rah_unlog_me_ip', 'admin', 'site', 'Additional IPs to exclude from the logs (comma-separated)', '2019-07-24 12:51:03'),
(6947, 'en', 'rah_unlog_me_auto', 'admin', 'site', 'Exclude site authors\' IPs from the logs?', '2019-07-24 12:51:03'),
(6948, 'en', 'rah_unlog_me_ip', 'admin', 'site', 'Additional IPs to exclude from the logs (comma-separated)', '2019-07-24 12:51:03'),
(6949, 'de', 'kuo_accesskey', 'kuo', 'site', 'Accesskey', '2019-07-24 13:33:51'),
(6950, 'de', 'kuo_approx', 'kuo', 'site', 'approx.', '2019-07-24 13:33:51'),
(6951, 'de', 'kuo_characters', 'kuo', 'site', 'Characters', '2019-07-24 13:33:51'),
(6952, 'de', 'kuo_lines', 'kuo', 'site', 'Lines', '2019-07-24 13:33:51'),
(6953, 'de', 'kuo_search_for_code_tag_markup', 'kuo', 'site', 'Search for code, tag, markup', '2019-07-24 13:33:51'),
(6954, 'de', 'kuo_words', 'kuo', 'site', 'Words', '2019-07-24 13:33:51'),
(6955, 'en-us', 'kuo_accesskey', 'kuo', 'site', 'Accesskey', '2019-07-24 13:33:51'),
(6956, 'en-us', 'kuo_approx', 'kuo', 'site', 'approx.', '2019-07-24 13:33:51'),
(6957, 'en-us', 'kuo_characters', 'kuo', 'site', 'Characters', '2019-07-24 13:33:51'),
(6958, 'en-us', 'kuo_lines', 'kuo', 'site', 'Lines', '2019-07-24 13:33:51'),
(6959, 'en-us', 'kuo_search_for_code_tag_markup', 'kuo', 'site', 'Search for code, tag, markup', '2019-07-24 13:33:51'),
(6960, 'en-us', 'kuo_words', 'kuo', 'site', 'Words', '2019-07-24 13:33:51'),
(6961, 'el', 'kuo_accesskey', 'kuo', 'site', 'Accesskey', '2019-07-24 13:33:51'),
(6962, 'el', 'kuo_approx', 'kuo', 'site', 'approx.', '2019-07-24 13:33:51'),
(6963, 'el', 'kuo_characters', 'kuo', 'site', 'Characters', '2019-07-24 13:33:51'),
(6964, 'el', 'kuo_lines', 'kuo', 'site', 'Lines', '2019-07-24 13:33:51'),
(6965, 'el', 'kuo_search_for_code_tag_markup', 'kuo', 'site', 'Search for code, tag, markup', '2019-07-24 13:33:51'),
(6966, 'el', 'kuo_words', 'kuo', 'site', 'Words', '2019-07-24 13:33:51'),
(6967, 'en', 'kuo_accesskey', 'kuo', 'site', 'Accesskey', '2019-07-24 13:33:51'),
(6968, 'en', 'kuo_approx', 'kuo', 'site', 'approx.', '2019-07-24 13:33:51'),
(6969, 'en', 'kuo_characters', 'kuo', 'site', 'Characters', '2019-07-24 13:33:51'),
(6970, 'en', 'kuo_lines', 'kuo', 'site', 'Lines', '2019-07-24 13:33:51'),
(6971, 'en', 'kuo_search_for_code_tag_markup', 'kuo', 'site', 'Search for code, tag, markup', '2019-07-24 13:33:51'),
(6972, 'en', 'kuo_words', 'kuo', 'site', 'Words', '2019-07-24 13:33:51'),
(6973, 'de', 'oui_instagram', 'admin', 'site', 'Instagram (oui_instagram)', '2019-07-24 15:32:33'),
(6974, 'de', 'oui_instagram_access_token', 'admin', 'site', 'Access token', '2019-07-24 15:32:33'),
(6975, 'en-us', 'oui_instagram', 'admin', 'site', 'Instagram (oui_instagram)', '2019-07-24 15:32:33'),
(6976, 'en-us', 'oui_instagram_access_token', 'admin', 'site', 'Access token', '2019-07-24 15:32:33'),
(6977, 'el', 'oui_instagram', 'admin', 'site', 'Instagram (oui_instagram)', '2019-07-24 15:32:33'),
(6978, 'el', 'oui_instagram_access_token', 'admin', 'site', 'Access token', '2019-07-24 15:32:33'),
(6979, 'en', 'oui_instagram', 'admin', 'site', 'Instagram (oui_instagram)', '2019-07-24 15:32:33'),
(6980, 'en', 'oui_instagram_access_token', 'admin', 'site', 'Access token', '2019-07-24 15:32:33'),
(6981, 'de', 'tok_mailalert_boxtitle_plugin_error', 'admin', 'site', '»tok_mailalert« Plugin Fehler', '2019-07-24 23:49:17'),
(6982, 'de', 'tok_mailalert_boxtxt_not_yet_setup', 'admin', 'site', 'Das Plugin »tok_mailalert« ist noch nicht konfiguriert', '2019-07-24 23:49:17'),
(6983, 'de', 'tok_mailalert_conf_val_mailsubject', 'admin', 'site', 'Ein neues Posting wurde veröffentlicht', '2019-07-24 23:49:17'),
(6984, 'de', 'tok_mailalert_conf_val_mailbody', 'admin', 'site', 'Hallo!\r\rBitte besuche alsbald\r\r<%siteurl%>\r\rViele Grüße\r<%userName%>', '2019-07-24 23:49:17'),
(6985, 'en-us', 'tok_mailalert_boxtitle_plugin_error', 'admin', 'site', '»tok_mailalert« plugin error', '2019-07-24 23:49:17'),
(6986, 'en-us', 'tok_mailalert_boxtxt_not_yet_setup', 'admin', 'site', 'The plugin »tok_mailalert« is not yet setup', '2019-07-24 23:49:17'),
(6987, 'en-us', 'tok_mailalert_conf_val_mailsubject', 'admin', 'site', 'A new posting was published', '2019-07-24 23:49:17'),
(6988, 'en-us', 'tok_mailalert_conf_val_mailbody', 'admin', 'site', 'Hi!\r\rPlease visit <%siteurl%> as soon as possible\r\rSincerely, <%userName%>', '2019-07-24 23:49:17'),
(6989, 'el', 'tok_mailalert_boxtitle_plugin_error', 'admin', 'site', '»tok_mailalert« plugin error', '2019-07-24 23:49:17'),
(6990, 'el', 'tok_mailalert_boxtxt_not_yet_setup', 'admin', 'site', 'The plugin »tok_mailalert« is not yet setup', '2019-07-24 23:49:17'),
(6991, 'el', 'tok_mailalert_conf_val_mailsubject', 'admin', 'site', 'A new posting was published', '2019-07-24 23:49:17'),
(6992, 'el', 'tok_mailalert_conf_val_mailbody', 'admin', 'site', 'Hi!\r\rPlease visit <%siteurl%> as soon as possible\r\rSincerely, <%userName%>', '2019-07-24 23:49:17'),
(6993, 'en', 'tok_mailalert_boxtitle_plugin_error', 'admin', 'site', '»tok_mailalert« plugin error', '2019-07-24 23:49:17'),
(6994, 'en', 'tok_mailalert_boxtxt_not_yet_setup', 'admin', 'site', 'The plugin »tok_mailalert« is not yet setup', '2019-07-24 23:49:17'),
(6995, 'en', 'tok_mailalert_conf_val_mailsubject', 'admin', 'site', 'A new posting was published', '2019-07-24 23:49:17'),
(6996, 'en', 'tok_mailalert_conf_val_mailbody', 'admin', 'site', 'Hi!\r\rPlease visit <%siteurl%> as soon as possible\r\rSincerely, <%userName%>', '2019-07-24 23:49:17'),
(6997, 'de', 'spf_tab_script', 'spf_js', 'site', 'JavaScripts', '2019-07-24 16:08:00'),
(6998, 'en-us', 'spf_tab_script', 'spf_js', 'site', 'JavaScripts', '2019-07-24 16:08:00'),
(6999, 'el', 'spf_tab_script', 'spf_js', 'site', 'JavaScripts', '2019-07-24 16:08:00'),
(7000, 'en', 'spf_tab_script', 'spf_js', 'site', 'JavaScripts', '2019-07-24 16:08:00'),
(7001, 'de', 'ied_plugin_cacheplugs_legend', 'ied_plugin', 'site', 'Plugins in cache directory', '2019-07-25 01:42:31'),
(7002, 'de', 'ied_plugin_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-25 01:42:31'),
(7003, 'de', 'ied_plugin_install_textpack', 'ied_plugin', 'site', 'Install file\'s Textpack', '2019-07-25 01:42:31'),
(7004, 'de', 'ied_plugin_lbl_lc_disdel', 'ied_plugin', 'site', 'Disable+Delete', '2019-07-25 01:42:31'),
(7005, 'de', 'ied_plugin_lbl_lc_instable', 'ied_plugin', 'site', 'Install+Enable', '2019-07-25 01:42:31'),
(7006, 'de', 'ied_plugin_lc_fired', 'ied_plugin', 'site', 'Lifecycle event {event} fired for {name}', '2019-07-25 01:42:31'),
(7007, 'de', 'ied_plugin_lifecycle', 'ied_plugin', 'site', 'Fire lifecycle event', '2019-07-25 01:42:31'),
(7008, 'de', 'ied_plugin_list', 'ied_plugin', 'site', 'Plugin list', '2019-07-25 01:42:31'),
(7009, 'de', 'ied_plugin_meta_save', 'ied_plugin', 'site', 'Save meta', '2019-07-25 01:42:31'),
(7010, 'de', 'ied_plugin_meta_saved', 'ied_plugin', 'site', 'Meta info saved', '2019-07-25 01:42:31'),
(7011, 'de', 'ied_plugin_meta_saved_fail', 'ied_plugin', 'site', 'Meta info not saved', '2019-07-25 01:42:31'),
(7012, 'de', 'ied_plugin_pack_legend', 'ied_plugin', 'site', 'Textpack strings', '2019-07-25 01:42:31'),
(7013, 'de', 'ied_plugin_toggle', 'ied_plugin', 'site', 'Toggle', '2019-07-25 01:42:31'),
(7014, 'de', 'ied_plugin_utils_legend_extra', 'ied_plugin', 'site', 'Only for use after saving', '2019-07-25 01:42:31'),
(7015, 'en-us', 'ied_plugin_cacheplugs_legend', 'ied_plugin', 'site', 'Plugins in cache directory', '2019-07-25 01:42:31'),
(7016, 'en-us', 'ied_plugin_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-25 01:42:31'),
(7017, 'en-us', 'ied_plugin_install_textpack', 'ied_plugin', 'site', 'Install file\'s Textpack', '2019-07-25 01:42:31'),
(7018, 'en-us', 'ied_plugin_lbl_lc_disdel', 'ied_plugin', 'site', 'Disable+Delete', '2019-07-25 01:42:31'),
(7019, 'en-us', 'ied_plugin_lbl_lc_instable', 'ied_plugin', 'site', 'Install+Enable', '2019-07-25 01:42:31'),
(7020, 'en-us', 'ied_plugin_lc_fired', 'ied_plugin', 'site', 'Lifecycle event {event} fired for {name}', '2019-07-25 01:42:31'),
(7021, 'en-us', 'ied_plugin_lifecycle', 'ied_plugin', 'site', 'Fire lifecycle event', '2019-07-25 01:42:31'),
(7022, 'en-us', 'ied_plugin_list', 'ied_plugin', 'site', 'Plugin list', '2019-07-25 01:42:31'),
(7023, 'en-us', 'ied_plugin_meta_save', 'ied_plugin', 'site', 'Save meta', '2019-07-25 01:42:31'),
(7024, 'en-us', 'ied_plugin_meta_saved', 'ied_plugin', 'site', 'Meta info saved', '2019-07-25 01:42:31'),
(7025, 'en-us', 'ied_plugin_meta_saved_fail', 'ied_plugin', 'site', 'Meta info not saved', '2019-07-25 01:42:31'),
(7026, 'en-us', 'ied_plugin_pack_legend', 'ied_plugin', 'site', 'Textpack strings', '2019-07-25 01:42:31'),
(7027, 'en-us', 'ied_plugin_toggle', 'ied_plugin', 'site', 'Toggle', '2019-07-25 01:42:31'),
(7028, 'en-us', 'ied_plugin_utils_legend_extra', 'ied_plugin', 'site', 'Only for use after saving', '2019-07-25 01:42:31'),
(7029, 'el', 'ied_plugin_cacheplugs_legend', 'ied_plugin', 'site', 'Plugins in cache directory', '2019-07-25 01:42:31'),
(7030, 'el', 'ied_plugin_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-25 01:42:31'),
(7031, 'el', 'ied_plugin_install_textpack', 'ied_plugin', 'site', 'Install file\'s Textpack', '2019-07-25 01:42:31'),
(7032, 'el', 'ied_plugin_lbl_lc_disdel', 'ied_plugin', 'site', 'Disable+Delete', '2019-07-25 01:42:31'),
(7033, 'el', 'ied_plugin_lbl_lc_instable', 'ied_plugin', 'site', 'Install+Enable', '2019-07-25 01:42:31'),
(7034, 'el', 'ied_plugin_lc_fired', 'ied_plugin', 'site', 'Lifecycle event {event} fired for {name}', '2019-07-25 01:42:31'),
(7035, 'el', 'ied_plugin_lifecycle', 'ied_plugin', 'site', 'Fire lifecycle event', '2019-07-25 01:42:31'),
(7036, 'el', 'ied_plugin_list', 'ied_plugin', 'site', 'Plugin list', '2019-07-25 01:42:31'),
(7037, 'el', 'ied_plugin_meta_save', 'ied_plugin', 'site', 'Save meta', '2019-07-25 01:42:31'),
(7038, 'el', 'ied_plugin_meta_saved', 'ied_plugin', 'site', 'Meta info saved', '2019-07-25 01:42:31'),
(7039, 'el', 'ied_plugin_meta_saved_fail', 'ied_plugin', 'site', 'Meta info not saved', '2019-07-25 01:42:31'),
(7040, 'el', 'ied_plugin_pack_legend', 'ied_plugin', 'site', 'Textpack strings', '2019-07-25 01:42:31'),
(7041, 'el', 'ied_plugin_toggle', 'ied_plugin', 'site', 'Toggle', '2019-07-25 01:42:31'),
(7042, 'el', 'ied_plugin_utils_legend_extra', 'ied_plugin', 'site', 'Only for use after saving', '2019-07-25 01:42:31'),
(7043, 'en', 'ied_plugin_cacheplugs_legend', 'ied_plugin', 'site', 'Plugins in cache directory', '2019-07-25 01:42:31'),
(7044, 'en', 'ied_plugin_composer', 'ied_plugin', 'site', 'Plugin composer', '2019-07-25 01:42:31'),
(7045, 'en', 'ied_plugin_install_textpack', 'ied_plugin', 'site', 'Install file\'s Textpack', '2019-07-25 01:42:31'),
(7046, 'en', 'ied_plugin_lbl_lc_disdel', 'ied_plugin', 'site', 'Disable+Delete', '2019-07-25 01:42:31'),
(7047, 'en', 'ied_plugin_lbl_lc_instable', 'ied_plugin', 'site', 'Install+Enable', '2019-07-25 01:42:31'),
(7048, 'en', 'ied_plugin_lc_fired', 'ied_plugin', 'site', 'Lifecycle event {event} fired for {name}', '2019-07-25 01:42:31'),
(7049, 'en', 'ied_plugin_lifecycle', 'ied_plugin', 'site', 'Fire lifecycle event', '2019-07-25 01:42:31'),
(7050, 'en', 'ied_plugin_list', 'ied_plugin', 'site', 'Plugin list', '2019-07-25 01:42:31'),
(7051, 'en', 'ied_plugin_meta_save', 'ied_plugin', 'site', 'Save meta', '2019-07-25 01:42:31'),
(7052, 'en', 'ied_plugin_meta_saved', 'ied_plugin', 'site', 'Meta info saved', '2019-07-25 01:42:31'),
(7053, 'en', 'ied_plugin_meta_saved_fail', 'ied_plugin', 'site', 'Meta info not saved', '2019-07-25 01:42:31'),
(7054, 'en', 'ied_plugin_pack_legend', 'ied_plugin', 'site', 'Textpack strings', '2019-07-25 01:42:31'),
(7055, 'en', 'ied_plugin_toggle', 'ied_plugin', 'site', 'Toggle', '2019-07-25 01:42:31'),
(7056, 'en', 'ied_plugin_utils_legend_extra', 'ied_plugin', 'site', 'Only for use after saving', '2019-07-25 01:42:31'),
(7057, 'de', 'rah_pathway_field', 'admin', 'site', 'Free-form article URL field', '2019-07-24 19:54:16'),
(7058, 'en-us', 'rah_pathway_field', 'admin', 'site', 'Free-form article URL field', '2019-07-24 19:54:16'),
(7059, 'el', 'rah_pathway_field', 'admin', 'site', 'Free-form article URL field', '2019-07-24 19:54:16'),
(7060, 'en', 'rah_pathway_field', 'admin', 'site', 'Free-form article URL field', '2019-07-24 19:54:16'),
(7061, 'de', 'glz_cf_tab_name', 'glz_cf', 'site', 'Benutzerdefinierte Felder', '2019-07-24 22:11:21'),
(7062, 'de', 'glz_cf_title', 'glz_cf', 'site', 'Benutzerdefiniertes Feld', '2019-07-24 22:11:21'),
(7063, 'de', 'glz_cf_no_name', 'glz_cf', 'site', 'Das <strong>benutzerdefinierte Feld</strong> hat keinen Namen.', '2019-07-24 22:11:21'),
(7064, 'de', 'glz_cf_deleted', 'glz_cf', 'site', 'Das <strong>benutzerdefinierte Feld {custom_set_id}</strong> wurde gelöscht.', '2019-07-24 22:11:21'),
(7065, 'de', 'glz_cf_deleted_error', 'glz_cf', 'site', 'Es gab ein Problem beim Löschen des <strong>benutzerdefinierten Felds {custom_set_id}</strong>.', '2019-07-24 22:11:21'),
(7066, 'de', 'glz_cf_reset', 'glz_cf', 'site', '<strong>Benutzerdefiniertes Feld {custom_set_id}</strong> wurde zurückgesetzt.', '2019-07-24 22:11:21'),
(7067, 'de', 'glz_cf_reset_error', 'glz_cf', 'site', 'Es gab ein Problem beim Zurücksetzen des <strong>benutzerdefinierten Felds {custom_set_id}</strong>.', '2019-07-24 22:11:21'),
(7068, 'de', 'glz_cf_created', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> wurde erstellt.', '2019-07-24 22:11:21'),
(7069, 'de', 'glz_cf_updated', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> wurde aktualisiert.', '2019-07-24 22:11:21'),
(7070, 'de', 'glz_cf_exists', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> existiert bereits.', '2019-07-24 22:11:21'),
(7071, 'de', 'glz_cf_doesnt_exist', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> existiert nicht.', '2019-07-24 22:11:21'),
(7072, 'de', 'glz_cf_name_renamed_notice', 'glz_cf', 'site', 'Unzulässiger Name: <strong>{custom_name_input}</strong> wurde als <strong>{custom_name_output}</strong> erstellt. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.', '2019-07-24 22:11:21'),
(7073, 'de', 'glz_cf_name_invalid_chars', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nur <strong>kleingeschriebene Zeichen, Ziffern und Unterstriche</strong> enthalten, jedoch <strong>keine Leerzeichen</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.', '2019-07-24 22:11:21'),
(7074, 'de', 'glz_cf_name_invalid_starts_with_number', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nicht mit einer Ziffer beginnen. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.', '2019-07-24 22:11:21'),
(7075, 'de', 'glz_cf_name_invalid_double_underscores', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten keine doppelten Unterstreichungszeichen enthalten. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.', '2019-07-24 22:11:21'),
(7076, 'de', 'glz_cf_no_values', 'glz_cf', 'site', 'Ein Checkbox-Feld benötigt mindestens einen Wert!', '2019-07-24 22:11:21'),
(7077, 'de', 'glz_cf_not_enough_values', 'glz_cf', 'site', 'Ein {cf_type}-Feld benötigt mindestens zwei Werte!', '2019-07-24 22:11:21'),
(7078, 'de', 'glz_cf_field_problems', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> ist fehlerhaft. <a href=\"?event=glz_custom_fields\">Bitte berichtigen.</a>.', '2019-07-24 22:11:21'),
(7079, 'de', 'glz_cf_types_normal', 'glz_cf', 'site', 'Normal', '2019-07-24 22:11:21'),
(7080, 'de', 'glz_cf_types_special', 'glz_cf', 'site', 'Spezial', '2019-07-24 22:11:21'),
(7081, 'de', 'glz_cf_text_input', 'glz_cf', 'site', 'Texteingabe', '2019-07-24 22:11:21'),
(7082, 'de', 'glz_cf_select', 'glz_cf', 'site', 'Auswahlmenü', '2019-07-24 22:11:21'),
(7083, 'de', 'glz_cf_multi-select', 'glz_cf', 'site', 'Multiselect', '2019-07-24 22:11:21'),
(7084, 'de', 'glz_cf_textarea', 'glz_cf', 'site', 'Textblock', '2019-07-24 22:11:21'),
(7085, 'de', 'glz_cf_checkbox', 'glz_cf', 'site', 'Checkboxen', '2019-07-24 22:11:21'),
(7086, 'de', 'glz_cf_radio', 'glz_cf', 'site', 'Radiobuttons', '2019-07-24 22:11:21'),
(7087, 'de', 'glz_cf_date-picker', 'glz_cf', 'site', 'Datumswähler', '2019-07-24 22:11:21'),
(7088, 'de', 'glz_cf_time-picker', 'glz_cf', 'site', 'Zeitwähler', '2019-07-24 22:11:21'),
(7089, 'de', 'glz_cf_custom-script', 'glz_cf', 'site', 'Benutzerdefiniertes Skript', '2019-07-24 22:11:21'),
(7090, 'de', 'glz_cf_type_not_supported', 'glz_cf', 'site', 'Typ nicht unterstützt', '2019-07-24 22:11:21'),
(7091, 'de', 'glz_cf_action_add', 'glz_cf', 'site', 'Hinzufügen', '2019-07-24 22:11:21'),
(7092, 'de', 'glz_cf_action_edit', 'glz_cf', 'site', 'Bearbeiten', '2019-07-24 22:11:21'),
(7093, 'de', 'glz_cf_action_delete', 'glz_cf', 'site', 'Löschen', '2019-07-24 22:11:21'),
(7094, 'de', 'glz_cf_action_reset', 'glz_cf', 'site', 'Zurücksetzen', '2019-07-24 22:11:21'),
(7095, 'de', 'glz_cf_not_specified', 'glz_cf', 'site', '{what} wurde nicht angegeben.', '2019-07-24 22:11:21'),
(7096, 'de', 'glz_cf_migration_success', 'glz_cf', 'site', 'Datenmigration der benutzerdefinierten Felder erfolgreich.', '2019-07-24 22:11:21'),
(7097, 'de', 'glz_cf_migration_skip', 'glz_cf', 'site', '<strong>custom_fields</strong> Datenbanktabelle existiert bereits: Die Datenmigration wurde übersprungen.', '2019-07-24 22:11:21'),
(7098, 'de', 'glz_cf_search_section_created', 'glz_cf', 'site', '<strong>search</strong> Rubrik wurde erstellt.', '2019-07-24 22:11:21'),
(7099, 'de', 'glz_cf_not_found', 'glz_cf', 'site', '<strong>{file}</strong> wurde nicht gefunden. Bitte den Pfad überprüfen.', '2019-07-24 22:11:21'),
(7100, 'de', 'glz_cf_not_callable', 'glz_cf', 'site', '<strong>{function}()</strong> konnte nicht aufgerufen werden. Bitte sicherstellen, dass <strong>{file}</strong> ausführbar ist.', '2019-07-24 22:11:21'),
(7101, 'de', 'glz_cf_no_such_custom_field', 'glz_cf', 'site', 'Kein benutzerdefiniertes Feld mit dieser ID!', '2019-07-24 22:11:21'),
(7102, 'de', 'glz_cf_prefpane_title', 'glz_cf', 'site', 'Benutzerdefinierte Felder', '2019-07-24 22:11:21'),
(7103, 'de', 'glz_cf_js_textarea_msg', 'glz_cf', 'site', 'Einen Wert pro Zeile.<br>Bei Bedarf einen {voreingestellten} Wert angeben.', '2019-07-24 22:11:21'),
(7104, 'de', 'glz_cf_js_script_msg', 'glz_cf', 'site', 'php-Dateiname im custom scripts Pfad.', '2019-07-24 22:11:21'),
(7105, 'de', 'glz_cf_js_configure_msg', 'glz_cf', 'site', 'Zum Konfigurieren siehe Einstellungen » Benutzerdefinierte Felder', '2019-07-24 22:11:21'),
(7106, 'de', 'glz_cf_public_error_datepicker', 'glz_cf', 'site', 'Problembehebung erforderlich: <u>Pfad des Datumswähler-Plugins überprüfen</u>', '2019-07-24 22:11:21'),
(7107, 'de', 'glz_cf_public_error_timepicker', 'glz_cf', 'site', 'Problembehebung erforderlich: <u>Pfad des Zeitwähler-Plugins überprüfen</u>', '2019-07-24 22:11:21'),
(7108, 'de', 'position', 'glz_cf', 'site', 'Position', '2019-07-24 22:11:21'),
(7109, 'de', 'glz_cf_col_sort', 'glz_cf', 'site', 'Reihenfolge', '2019-07-24 22:11:21'),
(7110, 'de', 'glz_cf_action_new_title', 'glz_cf', 'site', 'Neues benutzerdefiniertes Feld', '2019-07-24 22:11:21'),
(7111, 'de', 'glz_cf_action_edit_title', 'glz_cf', 'site', '{custom_set_name} bearbeiten.', '2019-07-24 22:11:21'),
(7112, 'de', 'glz_cf_add_new_cf', 'glz_cf', 'site', 'Neues benutzerdefiniertes Feld', '2019-07-24 22:11:21'),
(7113, 'de', 'glz_cf_multiple_values_instructions', 'glz_cf', 'site', 'Einen Wert pro Zeile <br>Bei Bedarf einen {voreingestellten} Wert angeben.', '2019-07-24 22:11:21'),
(7114, 'de', 'glz_cf_edit_name_hint', 'glz_cf', 'site', 'Nur alphanumerische Zeichen und einzelne Unterstriche', '2019-07-24 22:11:21'),
(7115, 'de', 'glz_cf_edit_position_hint', 'glz_cf', 'site', 'Wird automatisch vergeben, falls leer.', '2019-07-24 22:11:21'),
(7116, 'de', 'glz_cf_edit_name', 'glz_cf', 'site', 'Name:', '2019-07-24 22:11:21'),
(7117, 'de', 'glz_cf_edit_title', 'glz_cf', 'site', 'Titel:', '2019-07-24 22:11:21'),
(7118, 'de', 'glz_cf_edit_title_hint', 'glz_cf', 'site', 'Feldbezeichnung', '2019-07-24 22:11:21'),
(7119, 'de', 'glz_cf_edit_instructions', 'glz_cf', 'site', 'Anleitungstext:', '2019-07-24 22:11:21'),
(7120, 'de', 'glz_cf_edit_instructions_hint', 'glz_cf', 'site', 'Optionaler Hinweistext (erscheint zwischen Label und Element)', '2019-07-24 22:11:21'),
(7121, 'de', 'glz_cf_edit_type', 'glz_cf', 'site', 'Typ:', '2019-07-24 22:11:21'),
(7122, 'de', 'glz_cf_edit_position', 'glz_cf', 'site', 'Position:', '2019-07-24 22:11:21'),
(7123, 'de', 'glz_cf_edit_value', 'glz_cf', 'site', 'Wert(e):', '2019-07-24 22:11:21'),
(7124, 'de', 'glz_cf_confirm_reset', 'glz_cf', 'site', 'ALLE DATEN für {custom} ZURÜCKSETZEN – Sind Sie sicher?', '2019-07-24 22:11:21'),
(7125, 'de', 'glz_cf_confirm_delete', 'glz_cf', 'site', 'ALLE DATEN für {custom} LÖSCHEN – Sind Sie sicher?', '2019-07-24 22:11:21'),
(7126, 'de', 'glz_custom_f', 'glz_cf', 'site', 'Benutzerdefinierte Felder <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Pfade überprüfen</a>', '2019-07-24 22:11:21'),
(7127, 'de', 'glz_cf_values_ordering', 'glz_cf', 'site', 'Anzeigereihenfolge bei mehreren Werten', '2019-07-24 22:11:21'),
(7128, 'de', 'glz_cf_prefs_value_asc', 'glz_cf', 'site', 'Aufsteigend', '2019-07-24 22:11:21'),
(7129, 'de', 'glz_cf_prefs_value_desc', 'glz_cf', 'site', 'Absteigend', '2019-07-24 22:11:21'),
(7130, 'de', 'glz_cf_prefs_value_custom', 'glz_cf', 'site', 'Wie eingetragen', '2019-07-24 22:11:21'),
(7131, 'de', 'glz_cf_multiselect_size', 'glz_cf', 'site', 'Zeilenanzahl der Multiselects', '2019-07-24 22:11:21'),
(7132, 'de', 'glz_cf_css_folder', 'glz_cf', 'site', 'URL des CSS-Verzeichnis für glz custom fields', '2019-07-24 22:11:21'),
(7133, 'de', 'glz_cf_js_folder', 'glz_cf', 'site', 'URL des JS-Verzeichnis für glz custom fields', '2019-07-24 22:11:21'),
(7134, 'de', 'glz_cf_custom_folder', 'glz_cf', 'site', 'Pfad für benutzerdefinierte Skripte', '2019-07-24 22:11:21'),
(7135, 'de', 'glz_cf_use_sortable', 'glz_cf', 'site', 'Reihenfolge der Felder per Maus ändern', '2019-07-24 22:11:21'),
(7136, 'de', 'glz_cf_permit_full_deinstall', 'glz_cf', 'site', 'Beim Deinstallieren alle Daten löschen', '2019-07-24 22:11:21'),
(7137, 'de', 'glz_cf_datepicker_folder', 'glz_cf', 'site', 'URL des Datumswähler-Verzeichnisses', '2019-07-24 22:11:21'),
(7138, 'de', 'glz_cf_timepicker_folder', 'glz_cf', 'site', 'URL des Zeitwähler-Verzeichnisses', '2019-07-24 22:11:21'),
(7139, 'de', 'glz_cf_folder_error', 'glz_cf', 'site', 'URL/Pfad enthält Fehler, bitte überprüfen.', '2019-07-24 22:11:21'),
(7140, 'de', 'glz_cf_folder_success', 'glz_cf', 'site', 'URL/Pfad ist korrekt.', '2019-07-24 22:11:21'),
(7141, 'de', 'glz_cf_css_asset_url', 'glz_cf', 'site', 'URL des CSS-Verzeichnisses', '2019-07-24 22:11:21'),
(7142, 'de', 'glz_cf_js_asset_url', 'glz_cf', 'site', 'URL des JS-Verzeichnisses', '2019-07-24 22:11:21'),
(7143, 'de', 'glz_cf_custom_scripts_path', 'glz_cf', 'site', 'Pfad für benutzerdefinierte Skripte', '2019-07-24 22:11:21'),
(7144, 'de', 'glz_cf_custom_scripts_path_error', 'glz_cf', 'site', 'Pfad für benutzerdefinierte Skripte fehlerhaft, bitte überprüfen.', '2019-07-24 22:11:21'),
(7145, 'de', 'glz_cf_datepicker', 'glz_cf', 'site', 'Datumswähler', '2019-07-24 22:11:21'),
(7146, 'de', 'glz_cf_datepicker_url', 'glz_cf', 'site', 'URL des Datumswähler-Plugins', '2019-07-24 22:11:21'),
(7147, 'de', 'glz_cf_datepicker_url_error', 'glz_cf', 'site', 'URL/Pfad enthält Fehler, bitte überprüfen.', '2019-07-24 22:11:21'),
(7148, 'de', 'glz_cf_datepicker_format', 'glz_cf', 'site', 'Datumsformat', '2019-07-24 22:11:21'),
(7149, 'de', 'glz_cf_datepicker_first_day', 'glz_cf', 'site', 'Wochenbeginn', '2019-07-24 22:11:21'),
(7150, 'de', 'glz_cf_datepicker_start_date', 'glz_cf', 'site', 'Anfangsdatum', '2019-07-24 22:11:21'),
(7151, 'de', 'glz_cf_datepicker_start_date_error', 'glz_cf', 'site', 'Format des Anfangsdatums muss dem Datumsformat entsprechen.', '2019-07-24 22:11:21'),
(7152, 'de', 'glz_cf_prefs_monday', 'glz_cf', 'site', 'Montag', '2019-07-24 22:11:21'),
(7153, 'de', 'glz_cf_prefs_tuesday', 'glz_cf', 'site', 'Dienstag', '2019-07-24 22:11:21'),
(7154, 'de', 'glz_cf_prefs_wednesday', 'glz_cf', 'site', 'Mittwoch', '2019-07-24 22:11:21'),
(7155, 'de', 'glz_cf_prefs_thursday', 'glz_cf', 'site', 'Donnerstag', '2019-07-24 22:11:21'),
(7156, 'de', 'glz_cf_prefs_friday', 'glz_cf', 'site', 'Freitag', '2019-07-24 22:11:21'),
(7157, 'de', 'glz_cf_prefs_saturday', 'glz_cf', 'site', 'Samstag', '2019-07-24 22:11:21'),
(7158, 'de', 'glz_cf_prefs_sunday', 'glz_cf', 'site', 'Sonntag', '2019-07-24 22:11:21'),
(7159, 'de', 'glz_cf_prefs_slash_dmyy', 'glz_cf', 'site', 'TT/MM/JJJJ', '2019-07-24 22:11:21'),
(7160, 'de', 'glz_cf_prefs_slash_mdyy', 'glz_cf', 'site', 'MM/TT/JJJJ', '2019-07-24 22:11:21'),
(7161, 'de', 'glz_cf_prefs_dash_yymd', 'glz_cf', 'site', 'JJJJ-MM-TT', '2019-07-24 22:11:21'),
(7162, 'de', 'glz_cf_prefs_space_dmy', 'glz_cf', 'site', 'TT MM JJ', '2019-07-24 22:11:21'),
(7163, 'de', 'glz_cf_prefs_dot_dmyy', 'glz_cf', 'site', 'TT.MM.JJJJ', '2019-07-24 22:11:21'),
(7164, 'de', 'glz_cf_timepicker', 'glz_cf', 'site', 'Zeitwähler', '2019-07-24 22:11:21'),
(7165, 'de', 'glz_cf_timepicker_url', 'glz_cf', 'site', 'URL des Zeitwähler-Plugins', '2019-07-24 22:11:21'),
(7166, 'de', 'glz_cf_timepicker_url_error', 'glz_cf', 'site', 'URL/Pfad enthält Fehler, bitte überprüfen.', '2019-07-24 22:11:21'),
(7167, 'de', 'glz_cf_timepicker_start_time', 'glz_cf', 'site', 'Erster Zeiteintrag im Menü', '2019-07-24 22:11:21'),
(7168, 'de', 'glz_cf_timepicker_end_time', 'glz_cf', 'site', 'Letzter Zeiteintrag im Menü', '2019-07-24 22:11:21'),
(7169, 'de', 'glz_cf_timepicker_step', 'glz_cf', 'site', 'Zeitsprünge des Menüs in Minuten', '2019-07-24 22:11:21'),
(7170, 'de', 'glz_cf_timepicker_show_24', 'glz_cf', 'site', 'Zeitformat', '2019-07-24 22:11:21'),
(7171, 'de', 'glz_cf_prefs_24_hours', 'glz_cf', 'site', '24 Stunden', '2019-07-24 22:11:21'),
(7172, 'de', 'glz_cf_prefs_12_hours', 'glz_cf', 'site', '12 Stunden', '2019-07-24 22:11:21'),
(7173, 'de', 'glz_cf_sort_success', 'glz_cf', 'site', 'Reihenfolge geändert', '2019-07-24 22:11:21'),
(7174, 'de', 'glz_cf_sort_error', 'glz_cf', 'site', 'Reihenfolge konnte nicht geändert werden.', '2019-07-24 22:11:21'),
(7175, 'de', 'custom_x_set', 'prefs', 'site', 'Name des benutzerdefinierten Feldes Nr. {number}', '2019-07-24 22:11:21'),
(7176, 'de', 'instructions_glz_cf_datepicker_start_date', 'prefs', 'site', 'Die Angabe muss mit dem Datumsformat übereinstimmen.', '2019-07-24 22:11:21'),
(7177, 'de', 'instructions_glz_cf_custom_scripts_path', 'prefs', 'site', 'Pfad relativ zum root-Verzeichnis', '2019-07-24 22:11:21'),
(7178, 'de', 'instructions_glz_cf_permit_full_deinstall', 'prefs', 'site', 'ACHTUNG: \"Ja\" löscht ALLE DATEN in den benutzerdefinierten Feldern.', '2019-07-24 22:11:21'),
(7179, 'en-us', 'glz_cf_tab_name', 'glz_cf', 'site', 'Custom Fields', '2019-07-24 22:11:21'),
(7180, 'en-us', 'glz_cf_title', 'glz_cf', 'site', 'Custom Field', '2019-07-24 22:11:21'),
(7181, 'en-us', 'glz_cf_no_name', 'glz_cf', 'site', 'A <strong>custom field</strong> must have a name', '2019-07-24 22:11:21'),
(7182, 'en-us', 'glz_cf_deleted', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was deleted', '2019-07-24 22:11:21'),
(7183, 'en-us', 'glz_cf_deleted_error', 'glz_cf', 'site', 'There was a problem deleting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7184, 'en-us', 'glz_cf_reset', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was reset', '2019-07-24 22:11:21'),
(7185, 'en-us', 'glz_cf_reset_error', 'glz_cf', 'site', 'There was a problem resetting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7186, 'en-us', 'glz_cf_created', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was created', '2019-07-24 22:11:21'),
(7187, 'en-us', 'glz_cf_updated', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was updated', '2019-07-24 22:11:21'),
(7188, 'en-us', 'glz_cf_exists', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> already exists', '2019-07-24 22:11:21'),
(7189, 'en-us', 'glz_cf_doesnt_exist', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> is not set', '2019-07-24 22:11:21'),
(7190, 'en-us', 'glz_cf_name_renamed_notice', 'glz_cf', 'site', 'Invalid name: <strong>{custom_name_input}</strong> has been created as <strong>{custom_name_output}</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See why</a>.', '2019-07-24 22:11:21'),
(7191, 'en-us', 'glz_cf_name_invalid_chars', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should contain <strong>only lowercase letters, numbers, underscores</strong> and <strong>no spaces</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7192, 'en-us', 'glz_cf_name_invalid_starts_with_number', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not start with a number. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7193, 'en-us', 'glz_cf_name_invalid_double_underscores', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not have double underscores. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7194, 'en-us', 'glz_cf_no_values', 'glz_cf', 'site', 'A checkbox custom field must have at least one value', '2019-07-24 22:11:21'),
(7195, 'en-us', 'glz_cf_not_enough_values', 'glz_cf', 'site', 'A {cf_type} custom field must have at least two values', '2019-07-24 22:11:21'),
(7196, 'en-us', 'glz_cf_field_problems', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> has some problems. <a href=\"?event=glz_custom_fields\">Go fix it</a>.', '2019-07-24 22:11:21'),
(7197, 'en-us', 'glz_cf_types_normal', 'glz_cf', 'site', 'Normal', '2019-07-24 22:11:21'),
(7198, 'en-us', 'glz_cf_types_special', 'glz_cf', 'site', 'Special', '2019-07-24 22:11:21'),
(7199, 'en-us', 'glz_cf_text_input', 'glz_cf', 'site', 'Text Input', '2019-07-24 22:11:21'),
(7200, 'en-us', 'glz_cf_select', 'glz_cf', 'site', 'Select', '2019-07-24 22:11:21'),
(7201, 'en-us', 'glz_cf_multi-select', 'glz_cf', 'site', 'Multi-Select', '2019-07-24 22:11:21'),
(7202, 'en-us', 'glz_cf_textarea', 'glz_cf', 'site', 'Textarea', '2019-07-24 22:11:21'),
(7203, 'en-us', 'glz_cf_checkbox', 'glz_cf', 'site', 'Checkbox', '2019-07-24 22:11:21'),
(7204, 'en-us', 'glz_cf_radio', 'glz_cf', 'site', 'Radio', '2019-07-24 22:11:21'),
(7205, 'en-us', 'glz_cf_date-picker', 'glz_cf', 'site', 'Date Picker', '2019-07-24 22:11:21'),
(7206, 'en-us', 'glz_cf_time-picker', 'glz_cf', 'site', 'Time Picker', '2019-07-24 22:11:21'),
(7207, 'en-us', 'glz_cf_custom-script', 'glz_cf', 'site', 'Custom Script', '2019-07-24 22:11:21'),
(7208, 'en-us', 'glz_cf_type_not_supported', 'glz_cf', 'site', 'Type not supported', '2019-07-24 22:11:21'),
(7209, 'en-us', 'glz_cf_action_add', 'glz_cf', 'site', 'Add', '2019-07-24 22:11:21'),
(7210, 'en-us', 'glz_cf_action_edit', 'glz_cf', 'site', 'Edit', '2019-07-24 22:11:21'),
(7211, 'en-us', 'glz_cf_action_delete', 'glz_cf', 'site', 'Delete', '2019-07-24 22:11:21'),
(7212, 'en-us', 'glz_cf_action_reset', 'glz_cf', 'site', 'Reset', '2019-07-24 22:11:21'),
(7213, 'en-us', 'glz_cf_not_specified', 'glz_cf', 'site', '{what} is not specified', '2019-07-24 22:11:21'),
(7214, 'en-us', 'glz_cf_migration_success', 'glz_cf', 'site', 'Migrating custom fields was successful', '2019-07-24 22:11:21'),
(7215, 'en-us', 'glz_cf_migration_skip', 'glz_cf', 'site', '<strong>custom_fields</strong> table already has data in it, migration skipped.', '2019-07-24 22:11:21'),
(7216, 'en-us', 'glz_cf_search_section_created', 'glz_cf', 'site', '<strong>search</strong> section has been created', '2019-07-24 22:11:21'),
(7217, 'en-us', 'glz_cf_not_found', 'glz_cf', 'site', '<strong>{file}</strong> cannot be found, check path', '2019-07-24 22:11:21'),
(7218, 'en-us', 'glz_cf_not_callable', 'glz_cf', 'site', '<strong>{function}()</strong> cannot be called. Ensure <strong>{file}</strong> can be executed.', '2019-07-24 22:11:21'),
(7219, 'en-us', 'glz_cf_no_such_custom_field', 'glz_cf', 'site', 'No such custom field!', '2019-07-24 22:11:21'),
(7220, 'en-us', 'glz_cf_prefpane_title', 'glz_cf', 'site', 'Custom Fields Preferences', '2019-07-24 22:11:21'),
(7221, 'en-us', 'glz_cf_js_textarea_msg', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7222, 'en-us', 'glz_cf_js_script_msg', 'glz_cf', 'site', 'php file name in your custom scripts path', '2019-07-24 22:11:21'),
(7223, 'en-us', 'glz_cf_js_configure_msg', 'glz_cf', 'site', 'For settings, see custom field preferences', '2019-07-24 22:11:21'),
(7224, 'en-us', 'glz_cf_public_error_datepicker', 'glz_cf', 'site', 'Fix the DatePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7225, 'en-us', 'glz_cf_public_error_timepicker', 'glz_cf', 'site', 'Fix the TimePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7226, 'en-us', 'position', 'glz_cf', 'site', 'Position', '2019-07-24 22:11:21'),
(7227, 'en-us', 'glz_cf_col_sort', 'glz_cf', 'site', 'Sort', '2019-07-24 22:11:21'),
(7228, 'en-us', 'glz_cf_action_new_title', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7229, 'en-us', 'glz_cf_action_edit_title', 'glz_cf', 'site', 'Edit {custom_set_name}', '2019-07-24 22:11:21'),
(7230, 'en-us', 'glz_cf_add_new_cf', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7231, 'en-us', 'glz_cf_multiple_values_instructions', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7232, 'en-us', 'glz_cf_edit_name_hint', 'glz_cf', 'site', 'Alphanumeric characters and underscores', '2019-07-24 22:11:21'),
(7233, 'en-us', 'glz_cf_edit_position_hint', 'glz_cf', 'site', 'Automatically assigned if blank', '2019-07-24 22:11:21'),
(7234, 'en-us', 'glz_cf_edit_name', 'glz_cf', 'site', 'Name:', '2019-07-24 22:11:21'),
(7235, 'en-us', 'glz_cf_edit_title', 'glz_cf', 'site', 'Title:', '2019-07-24 22:11:21'),
(7236, 'en-us', 'glz_cf_edit_title_hint', 'glz_cf', 'site', 'Descriptive field label', '2019-07-24 22:11:21'),
(7237, 'en-us', 'glz_cf_edit_instructions', 'glz_cf', 'site', 'Instructions:', '2019-07-24 22:11:21'),
(7238, 'en-us', 'glz_cf_edit_instructions_hint', 'glz_cf', 'site', 'Optional help text (appears alongside label)', '2019-07-24 22:11:21'),
(7239, 'en-us', 'glz_cf_edit_type', 'glz_cf', 'site', 'Type:', '2019-07-24 22:11:21'),
(7240, 'en-us', 'glz_cf_edit_position', 'glz_cf', 'site', 'Position:', '2019-07-24 22:11:21'),
(7241, 'en-us', 'glz_cf_edit_value', 'glz_cf', 'site', 'Value(s):', '2019-07-24 22:11:21'),
(7242, 'en-us', 'glz_cf_confirm_reset', 'glz_cf', 'site', 'This will RESET ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7243, 'en-us', 'glz_cf_confirm_delete', 'glz_cf', 'site', 'This will DELETE ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7244, 'en-us', 'glz_custom_f', 'glz_cf', 'site', 'Custom fields preferences <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Check paths</a>', '2019-07-24 22:11:21'),
(7245, 'en-us', 'glz_cf_values_ordering', 'glz_cf', 'site', 'Order for custom field values', '2019-07-24 22:11:21'),
(7246, 'en-us', 'glz_cf_prefs_value_asc', 'glz_cf', 'site', 'Ascending', '2019-07-24 22:11:21'),
(7247, 'en-us', 'glz_cf_prefs_value_desc', 'glz_cf', 'site', 'Descending', '2019-07-24 22:11:21'),
(7248, 'en-us', 'glz_cf_prefs_value_custom', 'glz_cf', 'site', 'As entered', '2019-07-24 22:11:21'),
(7249, 'en-us', 'glz_cf_multiselect_size', 'glz_cf', 'site', 'Multi-select field size', '2019-07-24 22:11:21'),
(7250, 'en-us', 'glz_cf_css_folder', 'glz_cf', 'site', 'Glz custom fields CSS folder', '2019-07-24 22:11:21'),
(7251, 'en-us', 'glz_cf_js_folder', 'glz_cf', 'site', 'Glz custom fields JS folder', '2019-07-24 22:11:21'),
(7252, 'en-us', 'glz_cf_custom_folder', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7253, 'en-us', 'glz_cf_use_sortable', 'glz_cf', 'site', 'Set custom field order per drag and drop', '2019-07-24 22:11:21'),
(7254, 'en-us', 'glz_cf_permit_full_deinstall', 'glz_cf', 'site', 'Delete all data when deinstalling', '2019-07-24 22:11:21'),
(7255, 'en-us', 'glz_cf_datepicker_folder', 'glz_cf', 'site', 'Date picker folder', '2019-07-24 22:11:21'),
(7256, 'en-us', 'glz_cf_timepicker_folder', 'glz_cf', 'site', 'Time picker folder', '2019-07-24 22:11:21'),
(7257, 'en-us', 'glz_cf_folder_error', 'glz_cf', 'site', '{folder} does not exist, please create it.', '2019-07-24 22:11:21'),
(7258, 'en-us', 'glz_cf_folder_success', 'glz_cf', 'site', 'URL / path is correct', '2019-07-24 22:11:21'),
(7259, 'en-us', 'glz_cf_css_asset_url', 'glz_cf', 'site', 'CSS folder URL', '2019-07-24 22:11:21'),
(7260, 'en-us', 'glz_cf_js_asset_url', 'glz_cf', 'site', 'JS folder URL', '2019-07-24 22:11:21'),
(7261, 'en-us', 'glz_cf_custom_scripts_path', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7262, 'en-us', 'glz_cf_custom_scripts_path_error', 'glz_cf', 'site', 'Custom scripts folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7263, 'en-us', 'glz_cf_datepicker', 'glz_cf', 'site', 'Date picker', '2019-07-24 22:11:21'),
(7264, 'en-us', 'glz_cf_datepicker_url', 'glz_cf', 'site', 'Date picker plugin folder URL', '2019-07-24 22:11:21'),
(7265, 'en-us', 'glz_cf_datepicker_url_error', 'glz_cf', 'site', 'Date picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7266, 'en-us', 'glz_cf_datepicker_format', 'glz_cf', 'site', 'Date format', '2019-07-24 22:11:21'),
(7267, 'en-us', 'glz_cf_datepicker_first_day', 'glz_cf', 'site', 'First day of week', '2019-07-24 22:11:21'),
(7268, 'en-us', 'glz_cf_datepicker_start_date', 'glz_cf', 'site', 'Start date', '2019-07-24 22:11:21'),
(7269, 'en-us', 'glz_cf_datepicker_start_date_error', 'glz_cf', 'site', 'Start date does not match date format', '2019-07-24 22:11:21'),
(7270, 'en-us', 'glz_cf_prefs_monday', 'glz_cf', 'site', 'Monday', '2019-07-24 22:11:21'),
(7271, 'en-us', 'glz_cf_prefs_tuesday', 'glz_cf', 'site', 'Tuesday', '2019-07-24 22:11:21'),
(7272, 'en-us', 'glz_cf_prefs_wednesday', 'glz_cf', 'site', 'Wednesday', '2019-07-24 22:11:21'),
(7273, 'en-us', 'glz_cf_prefs_thursday', 'glz_cf', 'site', 'Thursday', '2019-07-24 22:11:21'),
(7274, 'en-us', 'glz_cf_prefs_friday', 'glz_cf', 'site', 'Friday', '2019-07-24 22:11:21'),
(7275, 'en-us', 'glz_cf_prefs_saturday', 'glz_cf', 'site', 'Saturday', '2019-07-24 22:11:21'),
(7276, 'en-us', 'glz_cf_prefs_sunday', 'glz_cf', 'site', 'Sunday', '2019-07-24 22:11:21'),
(7277, 'en-us', 'glz_cf_prefs_slash_dmyy', 'glz_cf', 'site', 'dd/mm/yyyy', '2019-07-24 22:11:21'),
(7278, 'en-us', 'glz_cf_prefs_slash_mdyy', 'glz_cf', 'site', 'mm/dd/yyyy', '2019-07-24 22:11:21'),
(7279, 'en-us', 'glz_cf_prefs_dash_yymd', 'glz_cf', 'site', 'yyyy-mm-dd', '2019-07-24 22:11:21'),
(7280, 'en-us', 'glz_cf_prefs_space_dmy', 'glz_cf', 'site', 'dd mm yy', '2019-07-24 22:11:21'),
(7281, 'en-us', 'glz_cf_prefs_dot_dmyy', 'glz_cf', 'site', 'dd.mm.yyyy', '2019-07-24 22:11:21'),
(7282, 'en-us', 'glz_cf_timepicker', 'glz_cf', 'site', 'Time picker', '2019-07-24 22:11:21'),
(7283, 'en-us', 'glz_cf_timepicker_url', 'glz_cf', 'site', 'Time picker plugin folder URL', '2019-07-24 22:11:21'),
(7284, 'en-us', 'glz_cf_timepicker_url_error', 'glz_cf', 'site', 'Time picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7285, 'en-us', 'glz_cf_timepicker_start_time', 'glz_cf', 'site', 'Start time', '2019-07-24 22:11:21'),
(7286, 'en-us', 'glz_cf_timepicker_end_time', 'glz_cf', 'site', 'End time', '2019-07-24 22:11:21'),
(7287, 'en-us', 'glz_cf_timepicker_step', 'glz_cf', 'site', 'Step', '2019-07-24 22:11:21'),
(7288, 'en-us', 'glz_cf_timepicker_show_24', 'glz_cf', 'site', 'Time format', '2019-07-24 22:11:21'),
(7289, 'en-us', 'glz_cf_prefs_24_hours', 'glz_cf', 'site', '24 hours', '2019-07-24 22:11:21'),
(7290, 'en-us', 'glz_cf_prefs_12_hours', 'glz_cf', 'site', '12 hours', '2019-07-24 22:11:21'),
(7291, 'en-us', 'glz_cf_sort_success', 'glz_cf', 'site', 'Custom field order changed', '2019-07-24 22:11:21'),
(7292, 'en-us', 'glz_cf_sort_error', 'glz_cf', 'site', 'Custom field order save failed', '2019-07-24 22:11:21'),
(7293, 'en-us', 'custom_x_set', 'prefs', 'site', 'Custom field {number} name', '2019-07-24 22:11:21'),
(7294, 'en-us', 'instructions_glz_cf_datepicker_start_date', 'prefs', 'site', 'Format MUST match \"Date format\"', '2019-07-24 22:11:21'),
(7295, 'en-us', 'instructions_glz_cf_custom_scripts_path', 'prefs', 'site', 'Path from your web root folder', '2019-07-24 22:11:21'),
(7296, 'en-us', 'instructions_glz_cf_permit_full_deinstall', 'prefs', 'site', 'WARNING: \"Yes\" = Deinstalling deletes All USER INPUT in custom fields', '2019-07-24 22:11:21'),
(7297, 'el', 'glz_cf_tab_name', 'glz_cf', 'site', 'Custom Fields', '2019-07-24 22:11:21'),
(7298, 'el', 'glz_cf_title', 'glz_cf', 'site', 'Custom Field', '2019-07-24 22:11:21'),
(7299, 'el', 'glz_cf_no_name', 'glz_cf', 'site', 'A <strong>custom field</strong> must have a name', '2019-07-24 22:11:21'),
(7300, 'el', 'glz_cf_deleted', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was deleted', '2019-07-24 22:11:21'),
(7301, 'el', 'glz_cf_deleted_error', 'glz_cf', 'site', 'There was a problem deleting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7302, 'el', 'glz_cf_reset', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was reset', '2019-07-24 22:11:21'),
(7303, 'el', 'glz_cf_reset_error', 'glz_cf', 'site', 'There was a problem resetting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7304, 'el', 'glz_cf_created', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was created', '2019-07-24 22:11:21'),
(7305, 'el', 'glz_cf_updated', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was updated', '2019-07-24 22:11:21'),
(7306, 'el', 'glz_cf_exists', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> already exists', '2019-07-24 22:11:21'),
(7307, 'el', 'glz_cf_doesnt_exist', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> is not set', '2019-07-24 22:11:21'),
(7308, 'el', 'glz_cf_name_renamed_notice', 'glz_cf', 'site', 'Invalid name: <strong>{custom_name_input}</strong> has been created as <strong>{custom_name_output}</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See why</a>.', '2019-07-24 22:11:21'),
(7309, 'el', 'glz_cf_name_invalid_chars', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should contain <strong>only lowercase letters, numbers, underscores</strong> and <strong>no spaces</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7310, 'el', 'glz_cf_name_invalid_starts_with_number', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not start with a number. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7311, 'el', 'glz_cf_name_invalid_double_underscores', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not have double underscores. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7312, 'el', 'glz_cf_no_values', 'glz_cf', 'site', 'A checkbox custom field must have at least one value', '2019-07-24 22:11:21'),
(7313, 'el', 'glz_cf_not_enough_values', 'glz_cf', 'site', 'A {cf_type} custom field must have at least two values', '2019-07-24 22:11:21');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(7314, 'el', 'glz_cf_field_problems', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> has some problems. <a href=\"?event=glz_custom_fields\">Go fix it</a>.', '2019-07-24 22:11:21'),
(7315, 'el', 'glz_cf_types_normal', 'glz_cf', 'site', 'Normal', '2019-07-24 22:11:21'),
(7316, 'el', 'glz_cf_types_special', 'glz_cf', 'site', 'Special', '2019-07-24 22:11:21'),
(7317, 'el', 'glz_cf_text_input', 'glz_cf', 'site', 'Text Input', '2019-07-24 22:11:21'),
(7318, 'el', 'glz_cf_select', 'glz_cf', 'site', 'Select', '2019-07-24 22:11:21'),
(7319, 'el', 'glz_cf_multi-select', 'glz_cf', 'site', 'Multi-Select', '2019-07-24 22:11:21'),
(7320, 'el', 'glz_cf_textarea', 'glz_cf', 'site', 'Textarea', '2019-07-24 22:11:21'),
(7321, 'el', 'glz_cf_checkbox', 'glz_cf', 'site', 'Checkbox', '2019-07-24 22:11:21'),
(7322, 'el', 'glz_cf_radio', 'glz_cf', 'site', 'Radio', '2019-07-24 22:11:21'),
(7323, 'el', 'glz_cf_date-picker', 'glz_cf', 'site', 'Date Picker', '2019-07-24 22:11:21'),
(7324, 'el', 'glz_cf_time-picker', 'glz_cf', 'site', 'Time Picker', '2019-07-24 22:11:21'),
(7325, 'el', 'glz_cf_custom-script', 'glz_cf', 'site', 'Custom Script', '2019-07-24 22:11:21'),
(7326, 'el', 'glz_cf_type_not_supported', 'glz_cf', 'site', 'Type not supported', '2019-07-24 22:11:21'),
(7327, 'el', 'glz_cf_action_add', 'glz_cf', 'site', 'Add', '2019-07-24 22:11:21'),
(7328, 'el', 'glz_cf_action_edit', 'glz_cf', 'site', 'Edit', '2019-07-24 22:11:21'),
(7329, 'el', 'glz_cf_action_delete', 'glz_cf', 'site', 'Delete', '2019-07-24 22:11:21'),
(7330, 'el', 'glz_cf_action_reset', 'glz_cf', 'site', 'Reset', '2019-07-24 22:11:21'),
(7331, 'el', 'glz_cf_not_specified', 'glz_cf', 'site', '{what} is not specified', '2019-07-24 22:11:21'),
(7332, 'el', 'glz_cf_migration_success', 'glz_cf', 'site', 'Migrating custom fields was successful', '2019-07-24 22:11:21'),
(7333, 'el', 'glz_cf_migration_skip', 'glz_cf', 'site', '<strong>custom_fields</strong> table already has data in it, migration skipped.', '2019-07-24 22:11:21'),
(7334, 'el', 'glz_cf_search_section_created', 'glz_cf', 'site', '<strong>search</strong> section has been created', '2019-07-24 22:11:21'),
(7335, 'el', 'glz_cf_not_found', 'glz_cf', 'site', '<strong>{file}</strong> cannot be found, check path', '2019-07-24 22:11:21'),
(7336, 'el', 'glz_cf_not_callable', 'glz_cf', 'site', '<strong>{function}()</strong> cannot be called. Ensure <strong>{file}</strong> can be executed.', '2019-07-24 22:11:21'),
(7337, 'el', 'glz_cf_no_such_custom_field', 'glz_cf', 'site', 'No such custom field!', '2019-07-24 22:11:21'),
(7338, 'el', 'glz_cf_prefpane_title', 'glz_cf', 'site', 'Custom Fields Preferences', '2019-07-24 22:11:21'),
(7339, 'el', 'glz_cf_js_textarea_msg', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7340, 'el', 'glz_cf_js_script_msg', 'glz_cf', 'site', 'php file name in your custom scripts path', '2019-07-24 22:11:21'),
(7341, 'el', 'glz_cf_js_configure_msg', 'glz_cf', 'site', 'For settings, see custom field preferences', '2019-07-24 22:11:21'),
(7342, 'el', 'glz_cf_public_error_datepicker', 'glz_cf', 'site', 'Fix the DatePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7343, 'el', 'glz_cf_public_error_timepicker', 'glz_cf', 'site', 'Fix the TimePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7344, 'el', 'position', 'glz_cf', 'site', 'Position', '2019-07-24 22:11:21'),
(7345, 'el', 'glz_cf_col_sort', 'glz_cf', 'site', 'Sort', '2019-07-24 22:11:21'),
(7346, 'el', 'glz_cf_action_new_title', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7347, 'el', 'glz_cf_action_edit_title', 'glz_cf', 'site', 'Edit {custom_set_name}', '2019-07-24 22:11:21'),
(7348, 'el', 'glz_cf_add_new_cf', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7349, 'el', 'glz_cf_multiple_values_instructions', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7350, 'el', 'glz_cf_edit_name_hint', 'glz_cf', 'site', 'Alphanumeric characters and underscores', '2019-07-24 22:11:21'),
(7351, 'el', 'glz_cf_edit_position_hint', 'glz_cf', 'site', 'Automatically assigned if blank', '2019-07-24 22:11:21'),
(7352, 'el', 'glz_cf_edit_name', 'glz_cf', 'site', 'Name:', '2019-07-24 22:11:21'),
(7353, 'el', 'glz_cf_edit_title', 'glz_cf', 'site', 'Title:', '2019-07-24 22:11:21'),
(7354, 'el', 'glz_cf_edit_title_hint', 'glz_cf', 'site', 'Descriptive field label', '2019-07-24 22:11:21'),
(7355, 'el', 'glz_cf_edit_instructions', 'glz_cf', 'site', 'Instructions:', '2019-07-24 22:11:21'),
(7356, 'el', 'glz_cf_edit_instructions_hint', 'glz_cf', 'site', 'Optional help text (appears alongside label)', '2019-07-24 22:11:21'),
(7357, 'el', 'glz_cf_edit_type', 'glz_cf', 'site', 'Type:', '2019-07-24 22:11:21'),
(7358, 'el', 'glz_cf_edit_position', 'glz_cf', 'site', 'Position:', '2019-07-24 22:11:21'),
(7359, 'el', 'glz_cf_edit_value', 'glz_cf', 'site', 'Value(s):', '2019-07-24 22:11:21'),
(7360, 'el', 'glz_cf_confirm_reset', 'glz_cf', 'site', 'This will RESET ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7361, 'el', 'glz_cf_confirm_delete', 'glz_cf', 'site', 'This will DELETE ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7362, 'el', 'glz_custom_f', 'glz_cf', 'site', 'Custom fields preferences <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Check paths</a>', '2019-07-24 22:11:21'),
(7363, 'el', 'glz_cf_values_ordering', 'glz_cf', 'site', 'Order for custom field values', '2019-07-24 22:11:21'),
(7364, 'el', 'glz_cf_prefs_value_asc', 'glz_cf', 'site', 'Ascending', '2019-07-24 22:11:21'),
(7365, 'el', 'glz_cf_prefs_value_desc', 'glz_cf', 'site', 'Descending', '2019-07-24 22:11:21'),
(7366, 'el', 'glz_cf_prefs_value_custom', 'glz_cf', 'site', 'As entered', '2019-07-24 22:11:21'),
(7367, 'el', 'glz_cf_multiselect_size', 'glz_cf', 'site', 'Multi-select field size', '2019-07-24 22:11:21'),
(7368, 'el', 'glz_cf_css_folder', 'glz_cf', 'site', 'Glz custom fields CSS folder', '2019-07-24 22:11:21'),
(7369, 'el', 'glz_cf_js_folder', 'glz_cf', 'site', 'Glz custom fields JS folder', '2019-07-24 22:11:21'),
(7370, 'el', 'glz_cf_custom_folder', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7371, 'el', 'glz_cf_use_sortable', 'glz_cf', 'site', 'Set custom field order per drag and drop', '2019-07-24 22:11:21'),
(7372, 'el', 'glz_cf_permit_full_deinstall', 'glz_cf', 'site', 'Delete all data when deinstalling', '2019-07-24 22:11:21'),
(7373, 'el', 'glz_cf_datepicker_folder', 'glz_cf', 'site', 'Date picker folder', '2019-07-24 22:11:21'),
(7374, 'el', 'glz_cf_timepicker_folder', 'glz_cf', 'site', 'Time picker folder', '2019-07-24 22:11:21'),
(7375, 'el', 'glz_cf_folder_error', 'glz_cf', 'site', '{folder} does not exist, please create it.', '2019-07-24 22:11:21'),
(7376, 'el', 'glz_cf_folder_success', 'glz_cf', 'site', 'URL / path is correct', '2019-07-24 22:11:21'),
(7377, 'el', 'glz_cf_css_asset_url', 'glz_cf', 'site', 'CSS folder URL', '2019-07-24 22:11:21'),
(7378, 'el', 'glz_cf_js_asset_url', 'glz_cf', 'site', 'JS folder URL', '2019-07-24 22:11:21'),
(7379, 'el', 'glz_cf_custom_scripts_path', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7380, 'el', 'glz_cf_custom_scripts_path_error', 'glz_cf', 'site', 'Custom scripts folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7381, 'el', 'glz_cf_datepicker', 'glz_cf', 'site', 'Date picker', '2019-07-24 22:11:21'),
(7382, 'el', 'glz_cf_datepicker_url', 'glz_cf', 'site', 'Date picker plugin folder URL', '2019-07-24 22:11:21'),
(7383, 'el', 'glz_cf_datepicker_url_error', 'glz_cf', 'site', 'Date picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7384, 'el', 'glz_cf_datepicker_format', 'glz_cf', 'site', 'Date format', '2019-07-24 22:11:21'),
(7385, 'el', 'glz_cf_datepicker_first_day', 'glz_cf', 'site', 'First day of week', '2019-07-24 22:11:21'),
(7386, 'el', 'glz_cf_datepicker_start_date', 'glz_cf', 'site', 'Start date', '2019-07-24 22:11:21'),
(7387, 'el', 'glz_cf_datepicker_start_date_error', 'glz_cf', 'site', 'Start date does not match date format', '2019-07-24 22:11:21'),
(7388, 'el', 'glz_cf_prefs_monday', 'glz_cf', 'site', 'Monday', '2019-07-24 22:11:21'),
(7389, 'el', 'glz_cf_prefs_tuesday', 'glz_cf', 'site', 'Tuesday', '2019-07-24 22:11:21'),
(7390, 'el', 'glz_cf_prefs_wednesday', 'glz_cf', 'site', 'Wednesday', '2019-07-24 22:11:21'),
(7391, 'el', 'glz_cf_prefs_thursday', 'glz_cf', 'site', 'Thursday', '2019-07-24 22:11:21'),
(7392, 'el', 'glz_cf_prefs_friday', 'glz_cf', 'site', 'Friday', '2019-07-24 22:11:21'),
(7393, 'el', 'glz_cf_prefs_saturday', 'glz_cf', 'site', 'Saturday', '2019-07-24 22:11:21'),
(7394, 'el', 'glz_cf_prefs_sunday', 'glz_cf', 'site', 'Sunday', '2019-07-24 22:11:21'),
(7395, 'el', 'glz_cf_prefs_slash_dmyy', 'glz_cf', 'site', 'dd/mm/yyyy', '2019-07-24 22:11:21'),
(7396, 'el', 'glz_cf_prefs_slash_mdyy', 'glz_cf', 'site', 'mm/dd/yyyy', '2019-07-24 22:11:21'),
(7397, 'el', 'glz_cf_prefs_dash_yymd', 'glz_cf', 'site', 'yyyy-mm-dd', '2019-07-24 22:11:21'),
(7398, 'el', 'glz_cf_prefs_space_dmy', 'glz_cf', 'site', 'dd mm yy', '2019-07-24 22:11:21'),
(7399, 'el', 'glz_cf_prefs_dot_dmyy', 'glz_cf', 'site', 'dd.mm.yyyy', '2019-07-24 22:11:21'),
(7400, 'el', 'glz_cf_timepicker', 'glz_cf', 'site', 'Time picker', '2019-07-24 22:11:21'),
(7401, 'el', 'glz_cf_timepicker_url', 'glz_cf', 'site', 'Time picker plugin folder URL', '2019-07-24 22:11:21'),
(7402, 'el', 'glz_cf_timepicker_url_error', 'glz_cf', 'site', 'Time picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7403, 'el', 'glz_cf_timepicker_start_time', 'glz_cf', 'site', 'Start time', '2019-07-24 22:11:21'),
(7404, 'el', 'glz_cf_timepicker_end_time', 'glz_cf', 'site', 'End time', '2019-07-24 22:11:21'),
(7405, 'el', 'glz_cf_timepicker_step', 'glz_cf', 'site', 'Step', '2019-07-24 22:11:21'),
(7406, 'el', 'glz_cf_timepicker_show_24', 'glz_cf', 'site', 'Time format', '2019-07-24 22:11:21'),
(7407, 'el', 'glz_cf_prefs_24_hours', 'glz_cf', 'site', '24 hours', '2019-07-24 22:11:21'),
(7408, 'el', 'glz_cf_prefs_12_hours', 'glz_cf', 'site', '12 hours', '2019-07-24 22:11:21'),
(7409, 'el', 'glz_cf_sort_success', 'glz_cf', 'site', 'Custom field order changed', '2019-07-24 22:11:21'),
(7410, 'el', 'glz_cf_sort_error', 'glz_cf', 'site', 'Custom field order save failed', '2019-07-24 22:11:21'),
(7411, 'el', 'custom_x_set', 'prefs', 'site', 'Custom field {number} name', '2019-07-24 22:11:21'),
(7412, 'el', 'instructions_glz_cf_datepicker_start_date', 'prefs', 'site', 'Format MUST match \"Date format\"', '2019-07-24 22:11:21'),
(7413, 'el', 'instructions_glz_cf_custom_scripts_path', 'prefs', 'site', 'Path from your web root folder', '2019-07-24 22:11:21'),
(7414, 'el', 'instructions_glz_cf_permit_full_deinstall', 'prefs', 'site', 'WARNING: \"Yes\" = Deinstalling deletes All USER INPUT in custom fields', '2019-07-24 22:11:21'),
(7415, 'en', 'glz_cf_tab_name', 'glz_cf', 'site', 'Custom Fields', '2019-07-24 22:11:21'),
(7416, 'en', 'glz_cf_title', 'glz_cf', 'site', 'Custom Field', '2019-07-24 22:11:21'),
(7417, 'en', 'glz_cf_no_name', 'glz_cf', 'site', 'A <strong>custom field</strong> must have a name', '2019-07-24 22:11:21'),
(7418, 'en', 'glz_cf_deleted', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was deleted', '2019-07-24 22:11:21'),
(7419, 'en', 'glz_cf_deleted_error', 'glz_cf', 'site', 'There was a problem deleting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7420, 'en', 'glz_cf_reset', 'glz_cf', 'site', '<strong>Custom field {custom_set_id}</strong> was reset', '2019-07-24 22:11:21'),
(7421, 'en', 'glz_cf_reset_error', 'glz_cf', 'site', 'There was a problem resetting <strong>custom field {custom_set_id}</strong>', '2019-07-24 22:11:21'),
(7422, 'en', 'glz_cf_created', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was created', '2019-07-24 22:11:21'),
(7423, 'en', 'glz_cf_updated', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> was updated', '2019-07-24 22:11:21'),
(7424, 'en', 'glz_cf_exists', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> already exists', '2019-07-24 22:11:21'),
(7425, 'en', 'glz_cf_doesnt_exist', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> is not set', '2019-07-24 22:11:21'),
(7426, 'en', 'glz_cf_name_renamed_notice', 'glz_cf', 'site', 'Invalid name: <strong>{custom_name_input}</strong> has been created as <strong>{custom_name_output}</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See why</a>.', '2019-07-24 22:11:21'),
(7427, 'en', 'glz_cf_name_invalid_chars', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should contain <strong>only lowercase letters, numbers, underscores</strong> and <strong>no spaces</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7428, 'en', 'glz_cf_name_invalid_starts_with_number', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not start with a number. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7429, 'en', 'glz_cf_name_invalid_double_underscores', 'glz_cf', 'site', '<strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not have double underscores. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.', '2019-07-24 22:11:21'),
(7430, 'en', 'glz_cf_no_values', 'glz_cf', 'site', 'A checkbox custom field must have at least one value', '2019-07-24 22:11:21'),
(7431, 'en', 'glz_cf_not_enough_values', 'glz_cf', 'site', 'A {cf_type} custom field must have at least two values', '2019-07-24 22:11:21'),
(7432, 'en', 'glz_cf_field_problems', 'glz_cf', 'site', '<strong>{custom_set_name}</strong> has some problems. <a href=\"?event=glz_custom_fields\">Go fix it</a>.', '2019-07-24 22:11:21'),
(7433, 'en', 'glz_cf_types_normal', 'glz_cf', 'site', 'Normal', '2019-07-24 22:11:21'),
(7434, 'en', 'glz_cf_types_special', 'glz_cf', 'site', 'Special', '2019-07-24 22:11:21'),
(7435, 'en', 'glz_cf_text_input', 'glz_cf', 'site', 'Text Input', '2019-07-24 22:11:21'),
(7436, 'en', 'glz_cf_select', 'glz_cf', 'site', 'Select', '2019-07-24 22:11:21'),
(7437, 'en', 'glz_cf_multi-select', 'glz_cf', 'site', 'Multi-Select', '2019-07-24 22:11:21'),
(7438, 'en', 'glz_cf_textarea', 'glz_cf', 'site', 'Textarea', '2019-07-24 22:11:21'),
(7439, 'en', 'glz_cf_checkbox', 'glz_cf', 'site', 'Checkbox', '2019-07-24 22:11:21'),
(7440, 'en', 'glz_cf_radio', 'glz_cf', 'site', 'Radio', '2019-07-24 22:11:21'),
(7441, 'en', 'glz_cf_date-picker', 'glz_cf', 'site', 'Date Picker', '2019-07-24 22:11:21'),
(7442, 'en', 'glz_cf_time-picker', 'glz_cf', 'site', 'Time Picker', '2019-07-24 22:11:21'),
(7443, 'en', 'glz_cf_custom-script', 'glz_cf', 'site', 'Custom Script', '2019-07-24 22:11:21'),
(7444, 'en', 'glz_cf_type_not_supported', 'glz_cf', 'site', 'Type not supported', '2019-07-24 22:11:21'),
(7445, 'en', 'glz_cf_action_add', 'glz_cf', 'site', 'Add', '2019-07-24 22:11:21'),
(7446, 'en', 'glz_cf_action_edit', 'glz_cf', 'site', 'Edit', '2019-07-24 22:11:21'),
(7447, 'en', 'glz_cf_action_delete', 'glz_cf', 'site', 'Delete', '2019-07-24 22:11:21'),
(7448, 'en', 'glz_cf_action_reset', 'glz_cf', 'site', 'Reset', '2019-07-24 22:11:21'),
(7449, 'en', 'glz_cf_not_specified', 'glz_cf', 'site', '{what} is not specified', '2019-07-24 22:11:21'),
(7450, 'en', 'glz_cf_migration_success', 'glz_cf', 'site', 'Migrating custom fields was successful', '2019-07-24 22:11:21'),
(7451, 'en', 'glz_cf_migration_skip', 'glz_cf', 'site', '<strong>custom_fields</strong> table already has data in it, migration skipped.', '2019-07-24 22:11:21'),
(7452, 'en', 'glz_cf_search_section_created', 'glz_cf', 'site', '<strong>search</strong> section has been created', '2019-07-24 22:11:21'),
(7453, 'en', 'glz_cf_not_found', 'glz_cf', 'site', '<strong>{file}</strong> cannot be found, check path', '2019-07-24 22:11:21'),
(7454, 'en', 'glz_cf_not_callable', 'glz_cf', 'site', '<strong>{function}()</strong> cannot be called. Ensure <strong>{file}</strong> can be executed.', '2019-07-24 22:11:21'),
(7455, 'en', 'glz_cf_no_such_custom_field', 'glz_cf', 'site', 'No such custom field!', '2019-07-24 22:11:21'),
(7456, 'en', 'glz_cf_prefpane_title', 'glz_cf', 'site', 'Custom Fields Preferences', '2019-07-24 22:11:21'),
(7457, 'en', 'glz_cf_js_textarea_msg', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7458, 'en', 'glz_cf_js_script_msg', 'glz_cf', 'site', 'php file name in your custom scripts path', '2019-07-24 22:11:21'),
(7459, 'en', 'glz_cf_js_configure_msg', 'glz_cf', 'site', 'For settings, see custom field preferences', '2019-07-24 22:11:21'),
(7460, 'en', 'glz_cf_public_error_datepicker', 'glz_cf', 'site', 'Fix the DatePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7461, 'en', 'glz_cf_public_error_timepicker', 'glz_cf', 'site', 'Fix the TimePicker jQuery plugin » <u>Check path</u>', '2019-07-24 22:11:21'),
(7462, 'en', 'position', 'glz_cf', 'site', 'Position', '2019-07-24 22:11:21'),
(7463, 'en', 'glz_cf_col_sort', 'glz_cf', 'site', 'Sort', '2019-07-24 22:11:21'),
(7464, 'en', 'glz_cf_action_new_title', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7465, 'en', 'glz_cf_action_edit_title', 'glz_cf', 'site', 'Edit {custom_set_name}', '2019-07-24 22:11:21'),
(7466, 'en', 'glz_cf_add_new_cf', 'glz_cf', 'site', 'New custom field', '2019-07-24 22:11:21'),
(7467, 'en', 'glz_cf_multiple_values_instructions', 'glz_cf', 'site', 'Each value on a separate line<br>One {default} value allowed', '2019-07-24 22:11:21'),
(7468, 'en', 'glz_cf_edit_name_hint', 'glz_cf', 'site', 'Alphanumeric characters and underscores', '2019-07-24 22:11:21'),
(7469, 'en', 'glz_cf_edit_position_hint', 'glz_cf', 'site', 'Automatically assigned if blank', '2019-07-24 22:11:21'),
(7470, 'en', 'glz_cf_edit_name', 'glz_cf', 'site', 'Name:', '2019-07-24 22:11:21'),
(7471, 'en', 'glz_cf_edit_title', 'glz_cf', 'site', 'Title:', '2019-07-24 22:11:21'),
(7472, 'en', 'glz_cf_edit_title_hint', 'glz_cf', 'site', 'Descriptive field label', '2019-07-24 22:11:21'),
(7473, 'en', 'glz_cf_edit_instructions', 'glz_cf', 'site', 'Instructions:', '2019-07-24 22:11:21'),
(7474, 'en', 'glz_cf_edit_instructions_hint', 'glz_cf', 'site', 'Optional help text (appears alongside label)', '2019-07-24 22:11:21'),
(7475, 'en', 'glz_cf_edit_type', 'glz_cf', 'site', 'Type:', '2019-07-24 22:11:21'),
(7476, 'en', 'glz_cf_edit_position', 'glz_cf', 'site', 'Position:', '2019-07-24 22:11:21'),
(7477, 'en', 'glz_cf_edit_value', 'glz_cf', 'site', 'Value(s):', '2019-07-24 22:11:21'),
(7478, 'en', 'glz_cf_confirm_reset', 'glz_cf', 'site', 'This will RESET ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7479, 'en', 'glz_cf_confirm_delete', 'glz_cf', 'site', 'This will DELETE ALL data for custom field {custom}. Are you sure?', '2019-07-24 22:11:21'),
(7480, 'en', 'glz_custom_f', 'glz_cf', 'site', 'Custom fields preferences <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Check paths</a>', '2019-07-24 22:11:21'),
(7481, 'en', 'glz_cf_values_ordering', 'glz_cf', 'site', 'Order for custom field values', '2019-07-24 22:11:21'),
(7482, 'en', 'glz_cf_prefs_value_asc', 'glz_cf', 'site', 'Ascending', '2019-07-24 22:11:21'),
(7483, 'en', 'glz_cf_prefs_value_desc', 'glz_cf', 'site', 'Descending', '2019-07-24 22:11:21'),
(7484, 'en', 'glz_cf_prefs_value_custom', 'glz_cf', 'site', 'As entered', '2019-07-24 22:11:21'),
(7485, 'en', 'glz_cf_multiselect_size', 'glz_cf', 'site', 'Multi-select field size', '2019-07-24 22:11:21'),
(7486, 'en', 'glz_cf_css_folder', 'glz_cf', 'site', 'Glz custom fields CSS folder', '2019-07-24 22:11:21'),
(7487, 'en', 'glz_cf_js_folder', 'glz_cf', 'site', 'Glz custom fields JS folder', '2019-07-24 22:11:21'),
(7488, 'en', 'glz_cf_custom_folder', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7489, 'en', 'glz_cf_use_sortable', 'glz_cf', 'site', 'Set custom field order per drag and drop', '2019-07-24 22:11:21'),
(7490, 'en', 'glz_cf_permit_full_deinstall', 'glz_cf', 'site', 'Delete all data when deinstalling', '2019-07-24 22:11:21'),
(7491, 'en', 'glz_cf_datepicker_folder', 'glz_cf', 'site', 'Date picker folder', '2019-07-24 22:11:21'),
(7492, 'en', 'glz_cf_timepicker_folder', 'glz_cf', 'site', 'Time picker folder', '2019-07-24 22:11:21'),
(7493, 'en', 'glz_cf_folder_error', 'glz_cf', 'site', '{folder} does not exist, please create it.', '2019-07-24 22:11:21'),
(7494, 'en', 'glz_cf_folder_success', 'glz_cf', 'site', 'URL / path is correct', '2019-07-24 22:11:21'),
(7495, 'en', 'glz_cf_css_asset_url', 'glz_cf', 'site', 'CSS folder URL', '2019-07-24 22:11:21'),
(7496, 'en', 'glz_cf_js_asset_url', 'glz_cf', 'site', 'JS folder URL', '2019-07-24 22:11:21'),
(7497, 'en', 'glz_cf_custom_scripts_path', 'glz_cf', 'site', 'Custom scripts path', '2019-07-24 22:11:21'),
(7498, 'en', 'glz_cf_custom_scripts_path_error', 'glz_cf', 'site', 'Custom scripts folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7499, 'en', 'glz_cf_datepicker', 'glz_cf', 'site', 'Date picker', '2019-07-24 22:11:21'),
(7500, 'en', 'glz_cf_datepicker_url', 'glz_cf', 'site', 'Date picker plugin folder URL', '2019-07-24 22:11:21'),
(7501, 'en', 'glz_cf_datepicker_url_error', 'glz_cf', 'site', 'Date picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7502, 'en', 'glz_cf_datepicker_format', 'glz_cf', 'site', 'Date format', '2019-07-24 22:11:21'),
(7503, 'en', 'glz_cf_datepicker_first_day', 'glz_cf', 'site', 'First day of week', '2019-07-24 22:11:21'),
(7504, 'en', 'glz_cf_datepicker_start_date', 'glz_cf', 'site', 'Start date', '2019-07-24 22:11:21'),
(7505, 'en', 'glz_cf_datepicker_start_date_error', 'glz_cf', 'site', 'Start date does not match date format', '2019-07-24 22:11:21'),
(7506, 'en', 'glz_cf_prefs_monday', 'glz_cf', 'site', 'Monday', '2019-07-24 22:11:21'),
(7507, 'en', 'glz_cf_prefs_tuesday', 'glz_cf', 'site', 'Tuesday', '2019-07-24 22:11:21'),
(7508, 'en', 'glz_cf_prefs_wednesday', 'glz_cf', 'site', 'Wednesday', '2019-07-24 22:11:21'),
(7509, 'en', 'glz_cf_prefs_thursday', 'glz_cf', 'site', 'Thursday', '2019-07-24 22:11:21'),
(7510, 'en', 'glz_cf_prefs_friday', 'glz_cf', 'site', 'Friday', '2019-07-24 22:11:21'),
(7511, 'en', 'glz_cf_prefs_saturday', 'glz_cf', 'site', 'Saturday', '2019-07-24 22:11:21'),
(7512, 'en', 'glz_cf_prefs_sunday', 'glz_cf', 'site', 'Sunday', '2019-07-24 22:11:21'),
(7513, 'en', 'glz_cf_prefs_slash_dmyy', 'glz_cf', 'site', 'dd/mm/yyyy', '2019-07-24 22:11:21'),
(7514, 'en', 'glz_cf_prefs_slash_mdyy', 'glz_cf', 'site', 'mm/dd/yyyy', '2019-07-24 22:11:21'),
(7515, 'en', 'glz_cf_prefs_dash_yymd', 'glz_cf', 'site', 'yyyy-mm-dd', '2019-07-24 22:11:21'),
(7516, 'en', 'glz_cf_prefs_space_dmy', 'glz_cf', 'site', 'dd mm yy', '2019-07-24 22:11:21'),
(7517, 'en', 'glz_cf_prefs_dot_dmyy', 'glz_cf', 'site', 'dd.mm.yyyy', '2019-07-24 22:11:21'),
(7518, 'en', 'glz_cf_timepicker', 'glz_cf', 'site', 'Time picker', '2019-07-24 22:11:21'),
(7519, 'en', 'glz_cf_timepicker_url', 'glz_cf', 'site', 'Time picker plugin folder URL', '2019-07-24 22:11:21'),
(7520, 'en', 'glz_cf_timepicker_url_error', 'glz_cf', 'site', 'Time picker folder does not exist, please create it.', '2019-07-24 22:11:21'),
(7521, 'en', 'glz_cf_timepicker_start_time', 'glz_cf', 'site', 'Start time', '2019-07-24 22:11:21'),
(7522, 'en', 'glz_cf_timepicker_end_time', 'glz_cf', 'site', 'End time', '2019-07-24 22:11:21'),
(7523, 'en', 'glz_cf_timepicker_step', 'glz_cf', 'site', 'Step', '2019-07-24 22:11:21'),
(7524, 'en', 'glz_cf_timepicker_show_24', 'glz_cf', 'site', 'Time format', '2019-07-24 22:11:21'),
(7525, 'en', 'glz_cf_prefs_24_hours', 'glz_cf', 'site', '24 hours', '2019-07-24 22:11:21'),
(7526, 'en', 'glz_cf_prefs_12_hours', 'glz_cf', 'site', '12 hours', '2019-07-24 22:11:21'),
(7527, 'en', 'glz_cf_sort_success', 'glz_cf', 'site', 'Custom field order changed', '2019-07-24 22:11:21'),
(7528, 'en', 'glz_cf_sort_error', 'glz_cf', 'site', 'Custom field order save failed', '2019-07-24 22:11:21'),
(7529, 'en', 'custom_x_set', 'prefs', 'site', 'Custom field {number} name', '2019-07-24 22:11:21'),
(7530, 'en', 'instructions_glz_cf_datepicker_start_date', 'prefs', 'site', 'Format MUST match \"Date format\"', '2019-07-24 22:11:21'),
(7531, 'en', 'instructions_glz_cf_custom_scripts_path', 'prefs', 'site', 'Path from your web root folder', '2019-07-24 22:11:21'),
(7532, 'en', 'instructions_glz_cf_permit_full_deinstall', 'prefs', 'site', 'WARNING: \"Yes\" = Deinstalling deletes All USER INPUT in custom fields', '2019-07-24 22:11:21'),
(7533, 'de', 'jcr_file_custom', 'admin', 'site', 'Bild ID', '2019-07-24 22:14:32'),
(7534, 'en-us', 'jcr_file_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:14:32'),
(7535, 'el', 'jcr_file_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:14:32'),
(7536, 'en', 'jcr_file_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:14:32'),
(7537, 'de', 'jcr_image_custom', 'admin', 'site', 'Video ID', '2019-07-24 22:15:31'),
(7538, 'en-us', 'jcr_image_custom', 'admin', 'site', 'Video ID', '2019-07-24 22:15:31'),
(7539, 'el', 'jcr_image_custom', 'admin', 'site', 'Video ID', '2019-07-24 22:15:31'),
(7540, 'en', 'jcr_image_custom', 'admin', 'site', 'Video ID', '2019-07-24 22:15:31'),
(7541, 'de', 'jcr_link_custom', 'admin', 'site', 'Bild ID', '2019-07-24 22:16:35'),
(7542, 'en-us', 'jcr_link_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:16:35'),
(7543, 'el', 'jcr_link_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:16:35'),
(7544, 'en', 'jcr_link_custom', 'admin', 'site', 'Image ID', '2019-07-24 22:16:35'),
(7545, 'de', 'article_image_group', 'admin', 'site', 'Artikelbild-Gruppe', '2019-07-24 22:19:28'),
(7546, 'de', 'writetab_main_content', 'admin', 'site', 'Hauptspalte', '2019-07-24 22:19:28'),
(7547, 'de', 'writetab_sidebar', 'admin', 'site', 'Seitenspalte', '2019-07-24 22:19:28'),
(7548, 'de', 'writetab_view_modes', 'admin', 'site', 'Modus-Umschaltung anzeigen', '2019-07-24 22:19:28'),
(7549, 'de', 'writetab_article_actions', 'admin', 'site', 'Artikelaktionen', '2019-07-24 22:19:28'),
(7550, 'de', 'bot_wtc_tab_name', 'admin', 'site', 'Write Tab Customize', '2019-07-24 22:19:28'),
(7551, 'de', 'bot_wtc_advanced', 'admin', 'site', 'Erweiterte Auswahl ein/aus', '2019-07-24 22:19:28'),
(7552, 'de', 'bot_wtc_item', 'admin', 'site', 'Element', '2019-07-24 22:19:28'),
(7553, 'de', 'bot_wtc_position', 'admin', 'site', 'Anordnung', '2019-07-24 22:19:28'),
(7554, 'de', 'bot_wtc_destination', 'admin', 'site', 'Zielelement', '2019-07-24 22:19:28'),
(7555, 'de', 'bot_wtc_hide', 'admin', 'site', 'Verbergen in', '2019-07-24 22:19:28'),
(7556, 'de', 'bot_wtc_class', 'admin', 'site', 'CSS-Klasse', '2019-07-24 22:19:28'),
(7557, 'de', 'bot_wtc_before', 'admin', 'site', 'vor', '2019-07-24 22:19:28'),
(7558, 'de', 'bot_wtc_after', 'admin', 'site', 'nach', '2019-07-24 22:19:28'),
(7559, 'de', 'bot_wtc_section_list', 'admin', 'site', 'Sektionen', '2019-07-24 22:19:28'),
(7560, 'de', 'bot_wtc_update_button', 'admin', 'site', 'Aktualisieren', '2019-07-24 22:19:28'),
(7561, 'de', 'bot_wtc_advanced_multiple_group', 'admin', 'site', 'Erweiterte Auswahl / Mehrfachauswahlen', '2019-07-24 22:19:28'),
(7562, 'de', 'bot_wtc_items', 'admin', 'site', 'Elemente', '2019-07-24 22:19:28'),
(7563, 'de', 'bot_wtc_hide_in_section', 'admin', 'site', 'In diesen Sektionen verbergen', '2019-07-24 22:19:28'),
(7564, 'de', 'bot_wtc_set_css_class', 'admin', 'site', 'CSS-Klasse festlegen', '2019-07-24 22:19:28'),
(7565, 'de', 'bot_wtc_static_sections_group', 'admin', 'site', 'Ausgewählte Sektionen nicht im Sektionsmenü anzeigen', '2019-07-24 22:19:28'),
(7566, 'de', 'bot_wtc_add_javascript_group', 'admin', 'site', 'Benutzerdefiniertes JavaScript hinzufügen', '2019-07-24 22:19:28'),
(7567, 'de', 'bot_wtc_add_external_script', 'admin', 'site', 'Externes Script einbinden', '2019-07-24 22:19:28'),
(7568, 'de', 'bot_wtc_add_inline_code', 'admin', 'site', 'jQuery-Code hinzufügen', '2019-07-24 22:19:28'),
(7569, 'de', 'bot_wtc_region_warning', 'admin', 'site', 'Hinweis: Ganze Seitenregionen (Haupt-/Nebenspalte) können nicht relativ zu einzelnen Elementen positioniert werden (und umgekehrt)', '2019-07-24 22:19:28'),
(7570, 'de', 'bot_wtc_same_item_warning', 'admin', 'site', 'Hinweis: Ein Element kann nicht relativ zu sich selbst positioniert werden.', '2019-07-24 22:19:28'),
(7571, 'de', 'bot_wtc_combo_warning', 'admin', 'site', 'Hinweis: Die eingefügte Regel war nicht vollständig.', '2019-07-24 22:19:28'),
(7572, 'de', 'bot_wtc_no_items_warning', 'admin', 'site', 'Hinweis: Es muss wenigstens ein Element ausgewählt sein.', '2019-07-24 22:19:28'),
(7573, 'en-us', 'article_image_group', 'admin', 'site', 'Article image group', '2019-07-24 22:19:28'),
(7574, 'en-us', 'writetab_main_content', 'admin', 'site', 'Main content', '2019-07-24 22:19:28'),
(7575, 'en-us', 'writetab_sidebar', 'admin', 'site', 'Sidebar', '2019-07-24 22:19:28'),
(7576, 'en-us', 'writetab_view_modes', 'admin', 'site', 'View mode switcher', '2019-07-24 22:19:28'),
(7577, 'en-us', 'writetab_article_actions', 'admin', 'site', 'Article actions', '2019-07-24 22:19:28'),
(7578, 'en-us', 'bot_wtc_tab_name', 'admin', 'site', 'Write Tab Customize', '2019-07-24 22:19:28'),
(7579, 'en-us', 'bot_wtc_advanced', 'admin', 'site', 'Toggle advanced', '2019-07-24 22:19:28'),
(7580, 'en-us', 'bot_wtc_item', 'admin', 'site', 'Item', '2019-07-24 22:19:28'),
(7581, 'en-us', 'bot_wtc_position', 'admin', 'site', 'Position', '2019-07-24 22:19:28'),
(7582, 'en-us', 'bot_wtc_destination', 'admin', 'site', 'Destination', '2019-07-24 22:19:28'),
(7583, 'en-us', 'bot_wtc_hide', 'admin', 'site', 'Hide in', '2019-07-24 22:19:28'),
(7584, 'en-us', 'bot_wtc_class', 'admin', 'site', 'Class', '2019-07-24 22:19:28'),
(7585, 'en-us', 'bot_wtc_before', 'admin', 'site', 'Before', '2019-07-24 22:19:28'),
(7586, 'en-us', 'bot_wtc_after', 'admin', 'site', 'After', '2019-07-24 22:19:28'),
(7587, 'en-us', 'bot_wtc_section_list', 'admin', 'site', 'Section list', '2019-07-24 22:19:28'),
(7588, 'en-us', 'bot_wtc_update_button', 'admin', 'site', 'Update', '2019-07-24 22:19:28'),
(7589, 'en-us', 'bot_wtc_advanced_multiple_group', 'admin', 'site', 'Advanced / Multiple selection', '2019-07-24 22:19:28'),
(7590, 'en-us', 'bot_wtc_items', 'admin', 'site', 'Items', '2019-07-24 22:19:28'),
(7591, 'en-us', 'bot_wtc_hide_in_section', 'admin', 'site', 'Hide in section', '2019-07-24 22:19:28'),
(7592, 'en-us', 'bot_wtc_set_css_class', 'admin', 'site', 'Set CSS class', '2019-07-24 22:19:28'),
(7593, 'en-us', 'bot_wtc_static_sections_group', 'admin', 'site', 'Hide sections in sections dropdown', '2019-07-24 22:19:28'),
(7594, 'en-us', 'bot_wtc_add_javascript_group', 'admin', 'site', 'Add javascript to write panel', '2019-07-24 22:19:28'),
(7595, 'en-us', 'bot_wtc_add_external_script', 'admin', 'site', 'Add external script', '2019-07-24 22:19:28'),
(7596, 'en-us', 'bot_wtc_add_inline_code', 'admin', 'site', 'Add inline jQuery code', '2019-07-24 22:19:28'),
(7597, 'en-us', 'bot_wtc_region_warning', 'admin', 'site', 'Entire column or sidebar regions cannot be moved relative to single items and vice-versa', '2019-07-24 22:19:28'),
(7598, 'en-us', 'bot_wtc_same_item_warning', 'admin', 'site', 'Oops! You are trying to move an item relative to itself', '2019-07-24 22:19:28'),
(7599, 'en-us', 'bot_wtc_combo_warning', 'admin', 'site', 'Oops! You tried to insert an incomplete rule', '2019-07-24 22:19:28'),
(7600, 'en-us', 'bot_wtc_no_items_warning', 'admin', 'site', 'Warning: at least an item must be selected', '2019-07-24 22:19:28'),
(7601, 'el', 'article_image_group', 'admin', 'site', 'Article image group', '2019-07-24 22:19:28'),
(7602, 'el', 'writetab_main_content', 'admin', 'site', 'Main content', '2019-07-24 22:19:28'),
(7603, 'el', 'writetab_sidebar', 'admin', 'site', 'Sidebar', '2019-07-24 22:19:28'),
(7604, 'el', 'writetab_view_modes', 'admin', 'site', 'View mode switcher', '2019-07-24 22:19:28'),
(7605, 'el', 'writetab_article_actions', 'admin', 'site', 'Article actions', '2019-07-24 22:19:28'),
(7606, 'el', 'bot_wtc_tab_name', 'admin', 'site', 'Write Tab Customize', '2019-07-24 22:19:28'),
(7607, 'el', 'bot_wtc_advanced', 'admin', 'site', 'Toggle advanced', '2019-07-24 22:19:28'),
(7608, 'el', 'bot_wtc_item', 'admin', 'site', 'Item', '2019-07-24 22:19:28'),
(7609, 'el', 'bot_wtc_position', 'admin', 'site', 'Position', '2019-07-24 22:19:28'),
(7610, 'el', 'bot_wtc_destination', 'admin', 'site', 'Destination', '2019-07-24 22:19:28'),
(7611, 'el', 'bot_wtc_hide', 'admin', 'site', 'Hide in', '2019-07-24 22:19:28'),
(7612, 'el', 'bot_wtc_class', 'admin', 'site', 'Class', '2019-07-24 22:19:28'),
(7613, 'el', 'bot_wtc_before', 'admin', 'site', 'Before', '2019-07-24 22:19:28'),
(7614, 'el', 'bot_wtc_after', 'admin', 'site', 'After', '2019-07-24 22:19:28'),
(7615, 'el', 'bot_wtc_section_list', 'admin', 'site', 'Section list', '2019-07-24 22:19:28'),
(7616, 'el', 'bot_wtc_update_button', 'admin', 'site', 'Update', '2019-07-24 22:19:28'),
(7617, 'el', 'bot_wtc_advanced_multiple_group', 'admin', 'site', 'Advanced / Multiple selection', '2019-07-24 22:19:28'),
(7618, 'el', 'bot_wtc_items', 'admin', 'site', 'Items', '2019-07-24 22:19:28'),
(7619, 'el', 'bot_wtc_hide_in_section', 'admin', 'site', 'Hide in section', '2019-07-24 22:19:28'),
(7620, 'el', 'bot_wtc_set_css_class', 'admin', 'site', 'Set CSS class', '2019-07-24 22:19:28'),
(7621, 'el', 'bot_wtc_static_sections_group', 'admin', 'site', 'Hide sections in sections dropdown', '2019-07-24 22:19:28'),
(7622, 'el', 'bot_wtc_add_javascript_group', 'admin', 'site', 'Add javascript to write panel', '2019-07-24 22:19:28'),
(7623, 'el', 'bot_wtc_add_external_script', 'admin', 'site', 'Add external script', '2019-07-24 22:19:28'),
(7624, 'el', 'bot_wtc_add_inline_code', 'admin', 'site', 'Add inline jQuery code', '2019-07-24 22:19:28'),
(7625, 'el', 'bot_wtc_region_warning', 'admin', 'site', 'Entire column or sidebar regions cannot be moved relative to single items and vice-versa', '2019-07-24 22:19:28'),
(7626, 'el', 'bot_wtc_same_item_warning', 'admin', 'site', 'Oops! You are trying to move an item relative to itself', '2019-07-24 22:19:28'),
(7627, 'el', 'bot_wtc_combo_warning', 'admin', 'site', 'Oops! You tried to insert an incomplete rule', '2019-07-24 22:19:28'),
(7628, 'el', 'bot_wtc_no_items_warning', 'admin', 'site', 'Warning: at least an item must be selected', '2019-07-24 22:19:28'),
(7629, 'en', 'article_image_group', 'admin', 'site', 'Article image group', '2019-07-24 22:19:28'),
(7630, 'en', 'writetab_main_content', 'admin', 'site', 'Main content', '2019-07-24 22:19:28'),
(7631, 'en', 'writetab_sidebar', 'admin', 'site', 'Sidebar', '2019-07-24 22:19:28'),
(7632, 'en', 'writetab_view_modes', 'admin', 'site', 'View mode switcher', '2019-07-24 22:19:28'),
(7633, 'en', 'writetab_article_actions', 'admin', 'site', 'Article actions', '2019-07-24 22:19:28'),
(7634, 'en', 'bot_wtc_tab_name', 'admin', 'site', 'Write Tab Customize', '2019-07-24 22:19:28'),
(7635, 'en', 'bot_wtc_advanced', 'admin', 'site', 'Toggle advanced', '2019-07-24 22:19:28'),
(7636, 'en', 'bot_wtc_item', 'admin', 'site', 'Item', '2019-07-24 22:19:28'),
(7637, 'en', 'bot_wtc_position', 'admin', 'site', 'Position', '2019-07-24 22:19:28'),
(7638, 'en', 'bot_wtc_destination', 'admin', 'site', 'Destination', '2019-07-24 22:19:28'),
(7639, 'en', 'bot_wtc_hide', 'admin', 'site', 'Hide in', '2019-07-24 22:19:28'),
(7640, 'en', 'bot_wtc_class', 'admin', 'site', 'Class', '2019-07-24 22:19:28'),
(7641, 'en', 'bot_wtc_before', 'admin', 'site', 'Before', '2019-07-24 22:19:28'),
(7642, 'en', 'bot_wtc_after', 'admin', 'site', 'After', '2019-07-24 22:19:28'),
(7643, 'en', 'bot_wtc_section_list', 'admin', 'site', 'Section list', '2019-07-24 22:19:28'),
(7644, 'en', 'bot_wtc_update_button', 'admin', 'site', 'Update', '2019-07-24 22:19:28'),
(7645, 'en', 'bot_wtc_advanced_multiple_group', 'admin', 'site', 'Advanced / Multiple selection', '2019-07-24 22:19:28'),
(7646, 'en', 'bot_wtc_items', 'admin', 'site', 'Items', '2019-07-24 22:19:28'),
(7647, 'en', 'bot_wtc_hide_in_section', 'admin', 'site', 'Hide in section', '2019-07-24 22:19:28'),
(7648, 'en', 'bot_wtc_set_css_class', 'admin', 'site', 'Set CSS class', '2019-07-24 22:19:28'),
(7649, 'en', 'bot_wtc_static_sections_group', 'admin', 'site', 'Hide sections in sections dropdown', '2019-07-24 22:19:28'),
(7650, 'en', 'bot_wtc_add_javascript_group', 'admin', 'site', 'Add javascript to write panel', '2019-07-24 22:19:28'),
(7651, 'en', 'bot_wtc_add_external_script', 'admin', 'site', 'Add external script', '2019-07-24 22:19:28'),
(7652, 'en', 'bot_wtc_add_inline_code', 'admin', 'site', 'Add inline jQuery code', '2019-07-24 22:19:28'),
(7653, 'en', 'bot_wtc_region_warning', 'admin', 'site', 'Entire column or sidebar regions cannot be moved relative to single items and vice-versa', '2019-07-24 22:19:28'),
(7654, 'en', 'bot_wtc_same_item_warning', 'admin', 'site', 'Oops! You are trying to move an item relative to itself', '2019-07-24 22:19:28'),
(7655, 'en', 'bot_wtc_combo_warning', 'admin', 'site', 'Oops! You tried to insert an incomplete rule', '2019-07-24 22:19:28'),
(7656, 'en', 'bot_wtc_no_items_warning', 'admin', 'site', 'Warning: at least an item must be selected', '2019-07-24 22:19:28'),
(7657, 'de', 'rah_privs', 'admin', 'site', 'Benutzerrechte', '2019-07-24 22:21:36'),
(7658, 'de', 'rah_privileges_privs', 'admin', 'site', 'Benutzerrechte verwalten für Admin-Bereiche bzw. Aktionen', '2019-07-24 22:21:36'),
(7659, 'en-us', 'rah_privs', 'admin', 'site', 'User Privileges', '2019-07-24 22:21:36'),
(7660, 'en-us', 'rah_privileges_privs', 'admin', 'site', 'Set account-level rights for access to textpattern’s actions or panels', '2019-07-24 22:21:36'),
(7661, 'el', 'rah_privs', 'admin', 'site', 'User Privileges', '2019-07-24 22:21:36'),
(7662, 'el', 'rah_privileges_privs', 'admin', 'site', 'Set account-level rights for access to textpattern’s actions or panels', '2019-07-24 22:21:36'),
(7663, 'en', 'rah_privs', 'admin', 'site', 'User Privileges', '2019-07-24 22:21:36'),
(7664, 'en', 'rah_privileges_privs', 'admin', 'site', 'Set account-level rights for access to textpattern’s actions or panels', '2019-07-24 22:21:36'),
(7665, 'de', 'arc_redirect', 'arc_redirect', 'site', 'Weiterleitungen', '2019-07-24 22:34:35'),
(7666, 'de', 'tab_arc_redirect', 'arc_redirect', 'site', 'Weiterleitungen', '2019-07-24 22:34:35'),
(7667, 'de', 'arc_original_url', 'arc_redirect', 'site', 'Von URL', '2019-07-24 22:34:35'),
(7668, 'de', 'arc_redirect_url', 'arc_redirect', 'site', 'Ziel URL', '2019-07-24 22:34:35'),
(7669, 'de', 'redirect_type', 'arc_redirect', 'site', 'Art der Weiterleitung', '2019-07-24 22:34:35'),
(7670, 'de', 'redirect_permanent', 'arc_redirect', 'site', 'Dauerhaft', '2019-07-24 22:34:35'),
(7671, 'de', 'redirect_temporary', 'arc_redirect', 'site', 'Vorübergehend', '2019-07-24 22:34:35'),
(7672, 'de', 'search_redirects', 'arc_redirect', 'site', 'Weiterleitungen suchen', '2019-07-24 22:34:35'),
(7673, 'de', 'no_redirects_founded', 'arc_redirect', 'site', 'Keine Weiterleitungen gefunden', '2019-07-24 22:34:35'),
(7674, 'de', 'redirect_added', 'arc_redirect', 'site', 'Weiterleitung hinzugefügt', '2019-07-24 22:34:35'),
(7675, 'de', 'redirect_updated', 'arc_redirect', 'site', 'Weiterleitung aktualisiert', '2019-07-24 22:34:35'),
(7676, 'de', 'add_new_redirect', 'arc_redirect', 'site', 'Neue Weiterleitung', '2019-07-24 22:34:35'),
(7677, 'de', 'edit_redirect', 'arc_redirect', 'site', 'Weiterleitung bearbeiten', '2019-07-24 22:34:35'),
(7678, 'de', 'redirect_deleted', 'arc_redirect', 'site', 'Weiterleitung gelöscht', '2019-07-24 22:34:35'),
(7679, 'de', 'redirects_deleted', 'arc_redirect', 'site', 'Weiterleitungen gelöscht', '2019-07-24 22:34:35'),
(7680, 'de', 'error_details_incomplete_no_id', 'arc_redirect', 'site', 'ID# fehlt – die Weiterleitung konnte nicht gespeichert werden', '2019-07-24 22:34:35'),
(7681, 'de', 'error_details_incomplete', 'arc_redirect', 'site', 'Angaben unvollständing – die Weiterleitung konnte nicht gespeichert werden', '2019-07-24 22:34:35'),
(7682, 'de', 'error_no_such_redirect_id', 'arc_redirect', 'site', 'Eine Weiterleitung mit der ID# existiert nicht. Bitte erneuert versuchen!', '2019-07-24 22:34:35'),
(7683, 'de', 'test_redirect', 'arc_redirect', 'site', 'Prüfen', '2019-07-24 22:34:35'),
(7684, 'en-us', 'arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7685, 'en-us', 'tab_arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7686, 'en-us', 'arc_original_url', 'arc_redirect', 'site', 'Original URL', '2019-07-24 22:34:35'),
(7687, 'en-us', 'arc_redirect_url', 'arc_redirect', 'site', 'Redirect URL', '2019-07-24 22:34:35'),
(7688, 'en-us', 'redirect_type', 'arc_redirect', 'site', 'Redirect type', '2019-07-24 22:34:35'),
(7689, 'en-us', 'redirect_permanent', 'arc_redirect', 'site', 'Permanent', '2019-07-24 22:34:35'),
(7690, 'en-us', 'redirect_temporary', 'arc_redirect', 'site', 'Temporary', '2019-07-24 22:34:35'),
(7691, 'en-us', 'search_redirects', 'arc_redirect', 'site', 'Search redirects', '2019-07-24 22:34:35'),
(7692, 'en-us', 'no_redirects_founded', 'arc_redirect', 'site', 'No redirects found', '2019-07-24 22:34:35'),
(7693, 'en-us', 'redirect_added', 'arc_redirect', 'site', 'Redirect added', '2019-07-24 22:34:35'),
(7694, 'en-us', 'redirect_updated', 'arc_redirect', 'site', 'Redirect updated', '2019-07-24 22:34:35'),
(7695, 'en-us', 'add_new_redirect', 'arc_redirect', 'site', 'Add new redirect', '2019-07-24 22:34:35'),
(7696, 'en-us', 'edit_redirect', 'arc_redirect', 'site', 'Edit redirect', '2019-07-24 22:34:35'),
(7697, 'en-us', 'redirect_deleted', 'arc_redirect', 'site', 'Redirect deleted', '2019-07-24 22:34:35'),
(7698, 'en-us', 'redirects_deleted', 'arc_redirect', 'site', 'redirects deleted', '2019-07-24 22:34:35'),
(7699, 'en-us', 'error_details_incomplete_no_id', 'arc_redirect', 'site', 'No ID# specified – unable to save redirect', '2019-07-24 22:34:35'),
(7700, 'en-us', 'error_details_incomplete', 'arc_redirect', 'site', 'Some details are missing – unable to save redirect', '2019-07-24 22:34:35'),
(7701, 'en-us', 'error_no_such_redirect_id', 'arc_redirect', 'site', 'A redirect with that ID# does not exist. Please try again.', '2019-07-24 22:34:35'),
(7702, 'en-us', 'test_redirect', 'arc_redirect', 'site', 'Test', '2019-07-24 22:34:35'),
(7703, 'el', 'arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7704, 'el', 'tab_arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7705, 'el', 'arc_original_url', 'arc_redirect', 'site', 'Original URL', '2019-07-24 22:34:35'),
(7706, 'el', 'arc_redirect_url', 'arc_redirect', 'site', 'Redirect URL', '2019-07-24 22:34:35'),
(7707, 'el', 'redirect_type', 'arc_redirect', 'site', 'Redirect type', '2019-07-24 22:34:35'),
(7708, 'el', 'redirect_permanent', 'arc_redirect', 'site', 'Permanent', '2019-07-24 22:34:35'),
(7709, 'el', 'redirect_temporary', 'arc_redirect', 'site', 'Temporary', '2019-07-24 22:34:35'),
(7710, 'el', 'search_redirects', 'arc_redirect', 'site', 'Search redirects', '2019-07-24 22:34:35'),
(7711, 'el', 'no_redirects_founded', 'arc_redirect', 'site', 'No redirects found', '2019-07-24 22:34:35'),
(7712, 'el', 'redirect_added', 'arc_redirect', 'site', 'Redirect added', '2019-07-24 22:34:35'),
(7713, 'el', 'redirect_updated', 'arc_redirect', 'site', 'Redirect updated', '2019-07-24 22:34:35'),
(7714, 'el', 'add_new_redirect', 'arc_redirect', 'site', 'Add new redirect', '2019-07-24 22:34:35'),
(7715, 'el', 'edit_redirect', 'arc_redirect', 'site', 'Edit redirect', '2019-07-24 22:34:35'),
(7716, 'el', 'redirect_deleted', 'arc_redirect', 'site', 'Redirect deleted', '2019-07-24 22:34:35'),
(7717, 'el', 'redirects_deleted', 'arc_redirect', 'site', 'redirects deleted', '2019-07-24 22:34:35'),
(7718, 'el', 'error_details_incomplete_no_id', 'arc_redirect', 'site', 'No ID# specified – unable to save redirect', '2019-07-24 22:34:35'),
(7719, 'el', 'error_details_incomplete', 'arc_redirect', 'site', 'Some details are missing – unable to save redirect', '2019-07-24 22:34:35'),
(7720, 'el', 'error_no_such_redirect_id', 'arc_redirect', 'site', 'A redirect with that ID# does not exist. Please try again.', '2019-07-24 22:34:35'),
(7721, 'el', 'test_redirect', 'arc_redirect', 'site', 'Test', '2019-07-24 22:34:35'),
(7722, 'en', 'arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7723, 'en', 'tab_arc_redirect', 'arc_redirect', 'site', 'Redirects', '2019-07-24 22:34:35'),
(7724, 'en', 'arc_original_url', 'arc_redirect', 'site', 'Original URL', '2019-07-24 22:34:35'),
(7725, 'en', 'arc_redirect_url', 'arc_redirect', 'site', 'Redirect URL', '2019-07-24 22:34:35'),
(7726, 'en', 'redirect_type', 'arc_redirect', 'site', 'Redirect type', '2019-07-24 22:34:35'),
(7727, 'en', 'redirect_permanent', 'arc_redirect', 'site', 'Permanent', '2019-07-24 22:34:35'),
(7728, 'en', 'redirect_temporary', 'arc_redirect', 'site', 'Temporary', '2019-07-24 22:34:35'),
(7729, 'en', 'search_redirects', 'arc_redirect', 'site', 'Search redirects', '2019-07-24 22:34:35'),
(7730, 'en', 'no_redirects_founded', 'arc_redirect', 'site', 'No redirects found', '2019-07-24 22:34:35'),
(7731, 'en', 'redirect_added', 'arc_redirect', 'site', 'Redirect added', '2019-07-24 22:34:35'),
(7732, 'en', 'redirect_updated', 'arc_redirect', 'site', 'Redirect updated', '2019-07-24 22:34:35'),
(7733, 'en', 'add_new_redirect', 'arc_redirect', 'site', 'Add new redirect', '2019-07-24 22:34:35'),
(7734, 'en', 'edit_redirect', 'arc_redirect', 'site', 'Edit redirect', '2019-07-24 22:34:35'),
(7735, 'en', 'redirect_deleted', 'arc_redirect', 'site', 'Redirect deleted', '2019-07-24 22:34:35'),
(7736, 'en', 'redirects_deleted', 'arc_redirect', 'site', 'redirects deleted', '2019-07-24 22:34:35'),
(7737, 'en', 'error_details_incomplete_no_id', 'arc_redirect', 'site', 'No ID# specified – unable to save redirect', '2019-07-24 22:34:35'),
(7738, 'en', 'error_details_incomplete', 'arc_redirect', 'site', 'Some details are missing – unable to save redirect', '2019-07-24 22:34:35'),
(7739, 'en', 'error_no_such_redirect_id', 'arc_redirect', 'site', 'A redirect with that ID# does not exist. Please try again.', '2019-07-24 22:34:35'),
(7740, 'en', 'test_redirect', 'arc_redirect', 'site', 'Test', '2019-07-24 22:34:35'),
(7741, 'de', 'jcr_export_txp_tabname', 'jcr_export_txp_templates', 'site', 'Textpattern Vorlagen exportieren', '2019-07-25 04:14:59'),
(7742, 'de', 'jcr_export_txp_pageheader', 'jcr_export_txp_templates', 'site', 'Textpattern Vorlagen exportieren', '2019-07-25 04:14:59'),
(7743, 'de', 'jcr_export_txp_exporting', 'jcr_export_txp_templates', 'site', 'Exportiert', '2019-07-25 04:14:59'),
(7744, 'de', 'jcr_export_txp_export_btn', 'jcr_export_txp_templates', 'site', 'Exportieren', '2019-07-25 04:14:59'),
(7745, 'de', 'jcr_export_txp_export_success', 'jcr_export_txp_templates', 'site', 'Export erfolgreich:', '2019-07-25 04:14:59'),
(7746, 'de', 'jcr_export_txp_export_error', 'jcr_export_txp_templates', 'site', 'Export fehlgeschlagen:', '2019-07-25 04:14:59'),
(7747, 'de', 'jcr_export_txp_export_to', 'jcr_export_txp_templates', 'site', 'zu', '2019-07-25 04:14:59'),
(7748, 'de', 'jcr_export_txp_pages_title', 'jcr_export_txp_templates', 'site', 'Seitenvorlagen', '2019-07-25 04:14:59'),
(7749, 'de', 'jcr_export_txp_pages', 'jcr_export_txp_templates', 'site', 'Seitenvorlage', '2019-07-25 04:14:59'),
(7750, 'de', 'jcr_export_txp_forms_title', 'jcr_export_txp_templates', 'site', 'Bausteine', '2019-07-25 04:14:59'),
(7751, 'de', 'jcr_export_txp_forms', 'jcr_export_txp_templates', 'site', 'Baustein', '2019-07-25 04:14:59'),
(7752, 'de', 'jcr_export_txp_styles_title', 'jcr_export_txp_templates', 'site', 'CSS Stilvorlagen', '2019-07-25 04:14:59'),
(7753, 'de', 'jcr_export_txp_styles', 'jcr_export_txp_templates', 'site', 'CSS Stilvorlage', '2019-07-25 04:14:59'),
(7754, 'de', 'jcr_export_txp_sections_title', 'jcr_export_txp_templates', 'site', 'Sektionen', '2019-07-25 04:14:59'),
(7755, 'de', 'jcr_export_txp_sections', 'jcr_export_txp_templates', 'site', 'Sektion', '2019-07-25 04:14:59'),
(7756, 'de', 'jcr_export_txp_plugins_title', 'jcr_export_txp_templates', 'site', 'Plugins', '2019-07-25 04:14:59'),
(7757, 'de', 'jcr_export_txp_plugins', 'jcr_export_txp_templates', 'site', 'Plugin', '2019-07-25 04:14:59'),
(7758, 'de', 'jcr_export_txp_variables_title', 'jcr_export_txp_templates', 'site', 'Variablen', '2019-07-25 04:14:59'),
(7759, 'de', 'jcr_export_txp_variables', 'jcr_export_txp_templates', 'site', 'variable', '2019-07-25 04:14:59'),
(7760, 'de', 'jcr_export_txp_name_export', 'jcr_export_txp_templates', 'site', 'Export-Ordner benennen', '2019-07-25 04:14:59'),
(7761, 'de', 'jcr_export_txp_dir_missing', 'jcr_export_txp_templates', 'site', '<strong>Export-Verzeichnis fehlt</strong>: Das Verzeichnis <strong>{path}</strong> existiert nicht und konnte nicht automatisch angelegt werden. Bitte das Verzeichnis manuell anlegen und mit Schreibrechte versehen. Siehe die Plugin-Hilfe.', '2019-07-25 04:14:59'),
(7762, 'de', 'jcr_export_txp_dir_not_writable', 'jcr_export_txp_templates', 'site', '<strong>Export-Verzeichnis hat keine Schreibrechte<strong>: Bitte das Verzeichnis <strong>{path}</strong> manuell mit Schreibrechte versehen. Siehe die Plugin-Hilfe.\";', '2019-07-25 04:14:59'),
(7763, 'en-us', 'jcr_export_txp_tabname', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7764, 'en-us', 'jcr_export_txp_pageheader', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7765, 'en-us', 'jcr_export_txp_exporting', 'jcr_export_txp_templates', 'site', 'Exporting', '2019-07-25 04:14:59'),
(7766, 'en-us', 'jcr_export_txp_export_btn', 'jcr_export_txp_templates', 'site', 'Export', '2019-07-25 04:14:59'),
(7767, 'en-us', 'jcr_export_txp_export_success', 'jcr_export_txp_templates', 'site', 'Successfully exported', '2019-07-25 04:14:59'),
(7768, 'en-us', 'jcr_export_txp_export_error', 'jcr_export_txp_templates', 'site', 'Failure exporting', '2019-07-25 04:14:59'),
(7769, 'en-us', 'jcr_export_txp_export_to', 'jcr_export_txp_templates', 'site', 'to', '2019-07-25 04:14:59');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(7770, 'en-us', 'jcr_export_txp_pages_title', 'jcr_export_txp_templates', 'site', 'page templates', '2019-07-25 04:14:59'),
(7771, 'en-us', 'jcr_export_txp_pages', 'jcr_export_txp_templates', 'site', 'page template', '2019-07-25 04:14:59'),
(7772, 'en-us', 'jcr_export_txp_forms_title', 'jcr_export_txp_templates', 'site', 'form templates', '2019-07-25 04:14:59'),
(7773, 'en-us', 'jcr_export_txp_forms', 'jcr_export_txp_templates', 'site', 'form template', '2019-07-25 04:14:59'),
(7774, 'en-us', 'jcr_export_txp_styles_title', 'jcr_export_txp_templates', 'site', 'CSS stylesheets', '2019-07-25 04:14:59'),
(7775, 'en-us', 'jcr_export_txp_styles', 'jcr_export_txp_templates', 'site', 'CSS stylesheet', '2019-07-25 04:14:59'),
(7776, 'en-us', 'jcr_export_txp_sections_title', 'jcr_export_txp_templates', 'site', 'sections', '2019-07-25 04:14:59'),
(7777, 'en-us', 'jcr_export_txp_sections', 'jcr_export_txp_templates', 'site', 'section', '2019-07-25 04:14:59'),
(7778, 'en-us', 'jcr_export_txp_plugins_title', 'jcr_export_txp_templates', 'site', 'plugins', '2019-07-25 04:14:59'),
(7779, 'en-us', 'jcr_export_txp_plugins', 'jcr_export_txp_templates', 'site', 'plugin', '2019-07-25 04:14:59'),
(7780, 'en-us', 'jcr_export_txp_variables_title', 'jcr_export_txp_templates', 'site', 'variables', '2019-07-25 04:14:59'),
(7781, 'en-us', 'jcr_export_txp_variables', 'jcr_export_txp_templates', 'site', 'variable', '2019-07-25 04:14:59'),
(7782, 'en-us', 'jcr_export_txp_name_export', 'jcr_export_txp_templates', 'site', 'Name this export', '2019-07-25 04:14:59'),
(7783, 'en-us', 'jcr_export_txp_dir_missing', 'jcr_export_txp_templates', 'site', '<strong>Template directory missing</strong>: The template directory <strong>{path}</strong> does not exist and could not be automatically created. Please create the directory manually and make it writable. See the plugin help.', '2019-07-25 04:14:59'),
(7784, 'en-us', 'jcr_export_txp_dir_not_writable', 'jcr_export_txp_templates', 'site', '<strong>Template directory not writable<strong>: Please make the directory <strong>{path}</strong> writable manually. See the plugin help for details.\";', '2019-07-25 04:14:59'),
(7785, 'el', 'jcr_export_txp_tabname', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7786, 'el', 'jcr_export_txp_pageheader', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7787, 'el', 'jcr_export_txp_exporting', 'jcr_export_txp_templates', 'site', 'Exporting', '2019-07-25 04:14:59'),
(7788, 'el', 'jcr_export_txp_export_btn', 'jcr_export_txp_templates', 'site', 'Export', '2019-07-25 04:14:59'),
(7789, 'el', 'jcr_export_txp_export_success', 'jcr_export_txp_templates', 'site', 'Successfully exported', '2019-07-25 04:14:59'),
(7790, 'el', 'jcr_export_txp_export_error', 'jcr_export_txp_templates', 'site', 'Failure exporting', '2019-07-25 04:14:59'),
(7791, 'el', 'jcr_export_txp_export_to', 'jcr_export_txp_templates', 'site', 'to', '2019-07-25 04:14:59'),
(7792, 'el', 'jcr_export_txp_pages_title', 'jcr_export_txp_templates', 'site', 'page templates', '2019-07-25 04:14:59'),
(7793, 'el', 'jcr_export_txp_pages', 'jcr_export_txp_templates', 'site', 'page template', '2019-07-25 04:14:59'),
(7794, 'el', 'jcr_export_txp_forms_title', 'jcr_export_txp_templates', 'site', 'form templates', '2019-07-25 04:14:59'),
(7795, 'el', 'jcr_export_txp_forms', 'jcr_export_txp_templates', 'site', 'form template', '2019-07-25 04:14:59'),
(7796, 'el', 'jcr_export_txp_styles_title', 'jcr_export_txp_templates', 'site', 'CSS stylesheets', '2019-07-25 04:14:59'),
(7797, 'el', 'jcr_export_txp_styles', 'jcr_export_txp_templates', 'site', 'CSS stylesheet', '2019-07-25 04:14:59'),
(7798, 'el', 'jcr_export_txp_sections_title', 'jcr_export_txp_templates', 'site', 'sections', '2019-07-25 04:14:59'),
(7799, 'el', 'jcr_export_txp_sections', 'jcr_export_txp_templates', 'site', 'section', '2019-07-25 04:14:59'),
(7800, 'el', 'jcr_export_txp_plugins_title', 'jcr_export_txp_templates', 'site', 'plugins', '2019-07-25 04:14:59'),
(7801, 'el', 'jcr_export_txp_plugins', 'jcr_export_txp_templates', 'site', 'plugin', '2019-07-25 04:14:59'),
(7802, 'el', 'jcr_export_txp_variables_title', 'jcr_export_txp_templates', 'site', 'variables', '2019-07-25 04:14:59'),
(7803, 'el', 'jcr_export_txp_variables', 'jcr_export_txp_templates', 'site', 'variable', '2019-07-25 04:14:59'),
(7804, 'el', 'jcr_export_txp_name_export', 'jcr_export_txp_templates', 'site', 'Name this export', '2019-07-25 04:14:59'),
(7805, 'el', 'jcr_export_txp_dir_missing', 'jcr_export_txp_templates', 'site', '<strong>Template directory missing</strong>: The template directory <strong>{path}</strong> does not exist and could not be automatically created. Please create the directory manually and make it writable. See the plugin help.', '2019-07-25 04:14:59'),
(7806, 'el', 'jcr_export_txp_dir_not_writable', 'jcr_export_txp_templates', 'site', '<strong>Template directory not writable<strong>: Please make the directory <strong>{path}</strong> writable manually. See the plugin help for details.\";', '2019-07-25 04:14:59'),
(7807, 'en', 'jcr_export_txp_tabname', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7808, 'en', 'jcr_export_txp_pageheader', 'jcr_export_txp_templates', 'site', 'Export templates', '2019-07-25 04:14:59'),
(7809, 'en', 'jcr_export_txp_exporting', 'jcr_export_txp_templates', 'site', 'Exporting', '2019-07-25 04:14:59'),
(7810, 'en', 'jcr_export_txp_export_btn', 'jcr_export_txp_templates', 'site', 'Export', '2019-07-25 04:14:59'),
(7811, 'en', 'jcr_export_txp_export_success', 'jcr_export_txp_templates', 'site', 'Successfully exported', '2019-07-25 04:14:59'),
(7812, 'en', 'jcr_export_txp_export_error', 'jcr_export_txp_templates', 'site', 'Failure exporting', '2019-07-25 04:14:59'),
(7813, 'en', 'jcr_export_txp_export_to', 'jcr_export_txp_templates', 'site', 'to', '2019-07-25 04:14:59'),
(7814, 'en', 'jcr_export_txp_pages_title', 'jcr_export_txp_templates', 'site', 'page templates', '2019-07-25 04:14:59'),
(7815, 'en', 'jcr_export_txp_pages', 'jcr_export_txp_templates', 'site', 'page template', '2019-07-25 04:14:59'),
(7816, 'en', 'jcr_export_txp_forms_title', 'jcr_export_txp_templates', 'site', 'form templates', '2019-07-25 04:14:59'),
(7817, 'en', 'jcr_export_txp_forms', 'jcr_export_txp_templates', 'site', 'form template', '2019-07-25 04:14:59'),
(7818, 'en', 'jcr_export_txp_styles_title', 'jcr_export_txp_templates', 'site', 'CSS stylesheets', '2019-07-25 04:14:59'),
(7819, 'en', 'jcr_export_txp_styles', 'jcr_export_txp_templates', 'site', 'CSS stylesheet', '2019-07-25 04:14:59'),
(7820, 'en', 'jcr_export_txp_sections_title', 'jcr_export_txp_templates', 'site', 'sections', '2019-07-25 04:14:59'),
(7821, 'en', 'jcr_export_txp_sections', 'jcr_export_txp_templates', 'site', 'section', '2019-07-25 04:14:59'),
(7822, 'en', 'jcr_export_txp_plugins_title', 'jcr_export_txp_templates', 'site', 'plugins', '2019-07-25 04:14:59'),
(7823, 'en', 'jcr_export_txp_plugins', 'jcr_export_txp_templates', 'site', 'plugin', '2019-07-25 04:14:59'),
(7824, 'en', 'jcr_export_txp_variables_title', 'jcr_export_txp_templates', 'site', 'variables', '2019-07-25 04:14:59'),
(7825, 'en', 'jcr_export_txp_variables', 'jcr_export_txp_templates', 'site', 'variable', '2019-07-25 04:14:59'),
(7826, 'en', 'jcr_export_txp_name_export', 'jcr_export_txp_templates', 'site', 'Name this export', '2019-07-25 04:14:59'),
(7827, 'en', 'jcr_export_txp_dir_missing', 'jcr_export_txp_templates', 'site', '<strong>Template directory missing</strong>: The template directory <strong>{path}</strong> does not exist and could not be automatically created. Please create the directory manually and make it writable. See the plugin help.', '2019-07-25 04:14:59'),
(7828, 'en', 'jcr_export_txp_dir_not_writable', 'jcr_export_txp_templates', 'site', '<strong>Template directory not writable<strong>: Please make the directory <strong>{path}</strong> writable manually. See the plugin help for details.\";', '2019-07-25 04:14:59'),
(7829, 'en', '', '', '', '', NULL),
(7830, 'de', 'abl_droploader_open', 'admin', 'site', 'Bilder hochladen', '2019-07-25 01:36:06'),
(7831, 'de', 'abl_droploader_open_title', 'admin', 'site', 'Drag & Drop Bild Uploader öffnen', '2019-07-25 01:36:06'),
(7832, 'de', 'abl_droploader_close', 'admin', 'site', '&#x00d7;', '2019-07-25 01:36:06'),
(7833, 'de', 'abl_droploader_close_title', 'admin', 'site', 'DropLoader schliessen', '2019-07-25 01:36:06'),
(7834, 'de', 'abl_droploader_error_method', 'admin', 'site', 'Methode {{method}} existiert in abl.droploader-app.js nicht.', '2019-07-25 01:36:06'),
(7835, 'de', 'abl_droploader_info_text', 'admin', 'site', 'Bilder hier ablegen\\noder klicken um auszuwählen', '2019-07-25 01:36:06'),
(7836, 'de', 'abl_droploader_err_invalid_filetype', 'admin', 'site', '{{filename}} nicht hochgeladen. Es sind nur Bilder erlaubt (jpg, jpeg, gif, png)!', '2019-07-25 01:36:06'),
(7837, 'de', 'abl_droploader_err_browser_not_supported', 'admin', 'site', 'Ihr Browser unterstützt keine HTML5 Datei Uploads!', '2019-07-25 01:36:06'),
(7838, 'de', 'abl_droploader_err_too_many_files', 'admin', 'site', 'Zu viele Dateien ausgewählt!\\n\\nEs können maximal {{maxfiles}} Dateien gleichzeitig hochgeladen werden!', '2019-07-25 01:36:06'),
(7839, 'de', 'abl_droploader_err_file_too_large', 'admin', 'site', '{{filename}} ist zu gross!', '2019-07-25 01:36:06'),
(7840, 'de', 'abl_droploader_all_files_uploaded', 'admin', 'site', '{{filecount}} Dateien hochgeladen.', '2019-07-25 01:36:06'),
(7841, 'de', 'abl_droploader_no_files_uploaded', 'admin', 'site', '\\nEs wurden keine Dateien hochgeladen.', '2019-07-25 01:36:06'),
(7842, 'de', 'abl_droploader_some_files_uploaded', 'admin', 'site', '\\n{{uploaded_files}} von {{filecount}} Dateien wurden hochgeladen.', '2019-07-25 01:36:06'),
(7843, 'de', 'abl_droploader_prefs_image_max_upload_count', 'admin', 'site', 'Maximale Anzahl Dateien beim Bild-Upload', '2019-07-25 01:36:06'),
(7844, 'de', 'abl_droploader_prefs_file_max_upload_count', 'admin', 'site', 'Maximale Anzahl Dateien beim Datei-Upload', '2019-07-25 01:36:06'),
(7845, 'de', 'abl_droploader_prefs_reload_image_tab', 'admin', 'site', 'Bilder Register: Droploader nach dem upload schliessen und Bildliste neu laden', '2019-07-25 01:36:06'),
(7846, 'de', 'abl_droploader_prefs_use_default_stylesheet', 'admin', 'site', 'Standarddarstellung verwenden', '2019-07-25 01:36:06'),
(7847, 'de', 'abl_droploader_prefs_custom_stylesheet', 'admin', 'site', 'Eigenes Stylesheet (Pfad/Dateiname)', '2019-07-25 01:36:06'),
(7848, 'de', 'abl_droploader_prefs_article_image_fields', 'admin', 'site', 'Feldnamen für Artikelbilder (Komma-getrennte Liste von Feldnamen, #custom-n für Benutzerdefinierte Felder)', '2019-07-25 01:36:06'),
(7849, 'en-us', 'abl_droploader_open', 'admin', 'site', 'Upload Images', '2019-07-25 01:36:06'),
(7850, 'en-us', 'abl_droploader_open_title', 'admin', 'site', 'Open drag & drop multiple image uploader', '2019-07-25 01:36:06'),
(7851, 'en-us', 'abl_droploader_close', 'admin', 'site', '&#x00d7;', '2019-07-25 01:36:06'),
(7852, 'en-us', 'abl_droploader_close_title', 'admin', 'site', 'Close DropLoader', '2019-07-25 01:36:06'),
(7853, 'en-us', 'abl_droploader_error_method', 'admin', 'site', 'Method {{method}} does not exist in abl.droploader-app.js.', '2019-07-25 01:36:06'),
(7854, 'en-us', 'abl_droploader_info_text', 'admin', 'site', 'Drop files here\\n\\nor click to select files', '2019-07-25 01:36:06'),
(7855, 'en-us', 'abl_droploader_err_invalid_filetype', 'admin', 'site', 'Cannot upload {{filename}}. Only images are allowed (jpg, jpeg, gif, png)!', '2019-07-25 01:36:06'),
(7856, 'en-us', 'abl_droploader_err_browser_not_supported', 'admin', 'site', 'Your browser does not support HTML5 file uploads!', '2019-07-25 01:36:06'),
(7857, 'en-us', 'abl_droploader_err_too_many_files', 'admin', 'site', 'Too many files!\\nPlease select {{maxfiles}} at most!', '2019-07-25 01:36:06'),
(7858, 'en-us', 'abl_droploader_err_file_too_large', 'admin', 'site', '{{filename}} is too large!', '2019-07-25 01:36:06'),
(7859, 'en-us', 'abl_droploader_all_files_uploaded', 'admin', 'site', '{{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7860, 'en-us', 'abl_droploader_no_files_uploaded', 'admin', 'site', '\\nNo files where uploaded.', '2019-07-25 01:36:06'),
(7861, 'en-us', 'abl_droploader_some_files_uploaded', 'admin', 'site', '\\n{{uploaded_files}} of {{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7862, 'en-us', 'abl_droploader_prefs_image_max_upload_count', 'admin', 'site', 'Maximum file count of image uploads', '2019-07-25 01:36:06'),
(7863, 'en-us', 'abl_droploader_prefs_file_max_upload_count', 'admin', 'site', 'Maximum file count of file uploads', '2019-07-25 01:36:06'),
(7864, 'en-us', 'abl_droploader_prefs_reload_image_tab', 'admin', 'site', 'Images-Tab: Close Droploader and reload image list after upload', '2019-07-25 01:36:06'),
(7865, 'en-us', 'abl_droploader_prefs_use_default_stylesheet', 'admin', 'site', 'Use default styles', '2019-07-25 01:36:06'),
(7866, 'en-us', 'abl_droploader_prefs_custom_stylesheet', 'admin', 'site', 'Custom stylesheet (path/filename)', '2019-07-25 01:36:06'),
(7867, 'en-us', 'abl_droploader_prefs_article_image_fields', 'admin', 'site', 'Article-image field(s) (comma separated list of CSS fieldnames, use #custom-n for custom field names)', '2019-07-25 01:36:06'),
(7868, 'el', 'abl_droploader_open', 'admin', 'site', 'Upload Images', '2019-07-25 01:36:06'),
(7869, 'el', 'abl_droploader_open_title', 'admin', 'site', 'Open drag & drop multiple image uploader', '2019-07-25 01:36:06'),
(7870, 'el', 'abl_droploader_close', 'admin', 'site', '&#x00d7;', '2019-07-25 01:36:06'),
(7871, 'el', 'abl_droploader_close_title', 'admin', 'site', 'Close DropLoader', '2019-07-25 01:36:06'),
(7872, 'el', 'abl_droploader_error_method', 'admin', 'site', 'Method {{method}} does not exist in abl.droploader-app.js.', '2019-07-25 01:36:06'),
(7873, 'el', 'abl_droploader_info_text', 'admin', 'site', 'Drop files here\\n\\nor click to select files', '2019-07-25 01:36:06'),
(7874, 'el', 'abl_droploader_err_invalid_filetype', 'admin', 'site', 'Cannot upload {{filename}}. Only images are allowed (jpg, jpeg, gif, png)!', '2019-07-25 01:36:06'),
(7875, 'el', 'abl_droploader_err_browser_not_supported', 'admin', 'site', 'Your browser does not support HTML5 file uploads!', '2019-07-25 01:36:06'),
(7876, 'el', 'abl_droploader_err_too_many_files', 'admin', 'site', 'Too many files!\\nPlease select {{maxfiles}} at most!', '2019-07-25 01:36:06'),
(7877, 'el', 'abl_droploader_err_file_too_large', 'admin', 'site', '{{filename}} is too large!', '2019-07-25 01:36:06'),
(7878, 'el', 'abl_droploader_all_files_uploaded', 'admin', 'site', '{{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7879, 'el', 'abl_droploader_no_files_uploaded', 'admin', 'site', '\\nNo files where uploaded.', '2019-07-25 01:36:06'),
(7880, 'el', 'abl_droploader_some_files_uploaded', 'admin', 'site', '\\n{{uploaded_files}} of {{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7881, 'el', 'abl_droploader_prefs_image_max_upload_count', 'admin', 'site', 'Maximum file count of image uploads', '2019-07-25 01:36:06'),
(7882, 'el', 'abl_droploader_prefs_file_max_upload_count', 'admin', 'site', 'Maximum file count of file uploads', '2019-07-25 01:36:06'),
(7883, 'el', 'abl_droploader_prefs_reload_image_tab', 'admin', 'site', 'Images-Tab: Close Droploader and reload image list after upload', '2019-07-25 01:36:06'),
(7884, 'el', 'abl_droploader_prefs_use_default_stylesheet', 'admin', 'site', 'Use default styles', '2019-07-25 01:36:06'),
(7885, 'el', 'abl_droploader_prefs_custom_stylesheet', 'admin', 'site', 'Custom stylesheet (path/filename)', '2019-07-25 01:36:06'),
(7886, 'el', 'abl_droploader_prefs_article_image_fields', 'admin', 'site', 'Article-image field(s) (comma separated list of CSS fieldnames, use #custom-n for custom field names)', '2019-07-25 01:36:06'),
(7887, 'en', 'abl_droploader_open', 'admin', 'site', 'Upload Images', '2019-07-25 01:36:06'),
(7888, 'en', 'abl_droploader_open_title', 'admin', 'site', 'Open drag & drop multiple image uploader', '2019-07-25 01:36:06'),
(7889, 'en', 'abl_droploader_close', 'admin', 'site', '&#x00d7;', '2019-07-25 01:36:06'),
(7890, 'en', 'abl_droploader_close_title', 'admin', 'site', 'Close DropLoader', '2019-07-25 01:36:06'),
(7891, 'en', 'abl_droploader_error_method', 'admin', 'site', 'Method {{method}} does not exist in abl.droploader-app.js.', '2019-07-25 01:36:06'),
(7892, 'en', 'abl_droploader_info_text', 'admin', 'site', 'Drop files here\\n\\nor click to select files', '2019-07-25 01:36:06'),
(7893, 'en', 'abl_droploader_err_invalid_filetype', 'admin', 'site', 'Cannot upload {{filename}}. Only images are allowed (jpg, jpeg, gif, png)!', '2019-07-25 01:36:06'),
(7894, 'en', 'abl_droploader_err_browser_not_supported', 'admin', 'site', 'Your browser does not support HTML5 file uploads!', '2019-07-25 01:36:06'),
(7895, 'en', 'abl_droploader_err_too_many_files', 'admin', 'site', 'Too many files!\\nPlease select {{maxfiles}} at most!', '2019-07-25 01:36:06'),
(7896, 'en', 'abl_droploader_err_file_too_large', 'admin', 'site', '{{filename}} is too large!', '2019-07-25 01:36:06'),
(7897, 'en', 'abl_droploader_all_files_uploaded', 'admin', 'site', '{{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7898, 'en', 'abl_droploader_no_files_uploaded', 'admin', 'site', '\\nNo files where uploaded.', '2019-07-25 01:36:06'),
(7899, 'en', 'abl_droploader_some_files_uploaded', 'admin', 'site', '\\n{{uploaded_files}} of {{filecount}} files uploaded.', '2019-07-25 01:36:06'),
(7900, 'en', 'abl_droploader_prefs_image_max_upload_count', 'admin', 'site', 'Maximum file count of image uploads', '2019-07-25 01:36:06'),
(7901, 'en', 'abl_droploader_prefs_file_max_upload_count', 'admin', 'site', 'Maximum file count of file uploads', '2019-07-25 01:36:06'),
(7902, 'en', 'abl_droploader_prefs_reload_image_tab', 'admin', 'site', 'Images-Tab: Close Droploader and reload image list after upload', '2019-07-25 01:36:06'),
(7903, 'en', 'abl_droploader_prefs_use_default_stylesheet', 'admin', 'site', 'Use default styles', '2019-07-25 01:36:06'),
(7904, 'en', 'abl_droploader_prefs_custom_stylesheet', 'admin', 'site', 'Custom stylesheet (path/filename)', '2019-07-25 01:36:06'),
(7905, 'en', 'abl_droploader_prefs_article_image_fields', 'admin', 'site', 'Article-image field(s) (comma separated list of CSS fieldnames, use #custom-n for custom field names)', '2019-07-25 01:36:06'),
(7906, 'de', 'oui_player_provider_youtube', 'admin', 'site', 'Youtube', '2019-07-25 03:07:22'),
(7907, 'de', 'oui_player_youtube', 'admin', 'site', 'Youtube player', '2019-07-25 03:07:22'),
(7908, 'de', 'oui_player_youtube_prefs', 'admin', 'site', 'Display the Youtube player preferences', '2019-07-25 03:07:22'),
(7909, 'de', 'oui_player_youtube_no_cookie', 'admin', 'site', 'Use the cookie free service', '2019-07-25 03:07:22'),
(7910, 'de', 'oui_player_youtube_autohide', 'admin', 'site', 'Player commands display while playing', '2019-07-25 03:07:22'),
(7911, 'de', 'oui_player_youtube_autohide_0', 'admin', 'site', 'Commands and progress bar always visible', '2019-07-25 03:07:22'),
(7912, 'de', 'oui_player_youtube_autohide_1', 'admin', 'site', 'Commands and progress bar on hover only', '2019-07-25 03:07:22'),
(7913, 'de', 'oui_player_youtube_autohide_2', 'admin', 'site', 'Commands on hover and progress bar always visible', '2019-07-25 03:07:22'),
(7914, 'de', 'oui_player_youtube_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:07:22'),
(7915, 'de', 'oui_player_youtube_cc_load_policy', 'admin', 'site', 'Force subtitles display', '2019-07-25 03:07:22'),
(7916, 'de', 'oui_player_youtube_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:07:22'),
(7917, 'de', 'oui_player_youtube_color_red', 'admin', 'site', 'Red', '2019-07-25 03:07:22'),
(7918, 'de', 'oui_player_youtube_color_white', 'admin', 'site', 'White', '2019-07-25 03:07:22'),
(7919, 'de', 'oui_player_youtube_controls', 'admin', 'site', 'Commands display and player loading', '2019-07-25 03:07:22'),
(7920, 'de', 'oui_player_youtube_controls_0', 'admin', 'site', 'Player loaded directly without commands', '2019-07-25 03:07:22'),
(7921, 'de', 'oui_player_youtube_controls_1', 'admin', 'site', 'Player loaded directly with commands', '2019-07-25 03:07:22'),
(7922, 'de', 'oui_player_youtube_controls_2', 'admin', 'site', 'Commands displayed and player loading on playing', '2019-07-25 03:07:22'),
(7923, 'de', 'oui_player_youtube_disablekb', 'admin', 'site', 'Disable keyboard commands', '2019-07-25 03:07:22'),
(7924, 'de', 'oui_player_youtube_enablejsapi', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:07:22'),
(7925, 'de', 'oui_player_youtube_end', 'admin', 'site', 'Stop playing at second:', '2019-07-25 03:07:22'),
(7926, 'de', 'oui_player_youtube_fs', 'admin', 'site', 'Allow full screen display', '2019-07-25 03:07:22'),
(7927, 'de', 'oui_player_youtube_hl', 'admin', 'site', 'Player interface language', '2019-07-25 03:07:22'),
(7928, 'de', 'oui_player_youtube_iv_load_policy', 'admin', 'site', 'Video annotations display', '2019-07-25 03:07:22'),
(7929, 'de', 'oui_player_youtube_iv_load_policy_1', 'admin', 'site', 'Enable by default', '2019-07-25 03:07:22'),
(7930, 'de', 'oui_player_youtube_iv_load_policy_3', 'admin', 'site', 'Disable by default', '2019-07-25 03:07:22'),
(7931, 'de', 'oui_player_youtube_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:07:22'),
(7932, 'de', 'oui_player_youtube_listtype', 'admin', 'site', 'List Type', '2019-07-25 03:07:22'),
(7933, 'de', 'oui_player_youtube_listtype_playlist', 'admin', 'site', 'playlist', '2019-07-25 03:07:22'),
(7934, 'de', 'oui_player_youtube_listtype_search', 'admin', 'site', 'search', '2019-07-25 03:07:22'),
(7935, 'de', 'oui_player_youtube_listtype_user_uploads', 'admin', 'site', 'user_uploads', '2019-07-25 03:07:22'),
(7936, 'de', 'oui_player_youtube_modestbranding', 'admin', 'site', 'Modest branding', '2019-07-25 03:07:22'),
(7937, 'de', 'oui_player_youtube_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:07:22'),
(7938, 'de', 'oui_player_youtube_playsinline', 'admin', 'site', 'Avoid fullscreen playback by default on iOS', '2019-07-25 03:07:22'),
(7939, 'de', 'oui_player_youtube_playlist', 'admin', 'site', 'Video list (id\'s) to play then', '2019-07-25 03:07:22'),
(7940, 'de', 'oui_player_youtube_rel', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:07:22'),
(7941, 'de', 'oui_player_youtube_showinfo', 'admin', 'site', 'Display video informations', '2019-07-25 03:07:22'),
(7942, 'de', 'oui_player_youtube_start', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:07:22'),
(7943, 'de', 'oui_player_youtube_theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:07:22'),
(7944, 'de', 'oui_player_youtube_theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:07:22'),
(7945, 'de', 'oui_player_youtube_theme_light', 'admin', 'site', 'Light', '2019-07-25 03:07:22'),
(7946, 'de', 'oui_player_youtube_width', 'admin', 'site', 'Player width', '2019-07-25 03:07:22'),
(7947, 'de', 'oui_player_youtube_height', 'admin', 'site', 'Player height', '2019-07-25 03:07:22'),
(7948, 'de', 'oui_player_youtube_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:07:22'),
(7949, 'de', 'oui_player_youtube_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:07:22'),
(7950, 'en-us', 'oui_player_provider_youtube', 'admin', 'site', 'Youtube', '2019-07-25 03:07:22'),
(7951, 'en-us', 'oui_player_youtube', 'admin', 'site', 'Youtube player', '2019-07-25 03:07:22'),
(7952, 'en-us', 'oui_player_youtube_prefs', 'admin', 'site', 'Display the Youtube player preferences', '2019-07-25 03:07:22'),
(7953, 'en-us', 'oui_player_youtube_no_cookie', 'admin', 'site', 'Use the cookie free service', '2019-07-25 03:07:22'),
(7954, 'en-us', 'oui_player_youtube_autohide', 'admin', 'site', 'Player commands display while playing', '2019-07-25 03:07:22'),
(7955, 'en-us', 'oui_player_youtube_autohide_0', 'admin', 'site', 'Commands and progress bar always visible', '2019-07-25 03:07:22'),
(7956, 'en-us', 'oui_player_youtube_autohide_1', 'admin', 'site', 'Commands and progress bar on hover only', '2019-07-25 03:07:22'),
(7957, 'en-us', 'oui_player_youtube_autohide_2', 'admin', 'site', 'Commands on hover and progress bar always visible', '2019-07-25 03:07:22'),
(7958, 'en-us', 'oui_player_youtube_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:07:22'),
(7959, 'en-us', 'oui_player_youtube_cc_load_policy', 'admin', 'site', 'Force subtitles display', '2019-07-25 03:07:22'),
(7960, 'en-us', 'oui_player_youtube_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:07:22'),
(7961, 'en-us', 'oui_player_youtube_color_red', 'admin', 'site', 'Red', '2019-07-25 03:07:22'),
(7962, 'en-us', 'oui_player_youtube_color_white', 'admin', 'site', 'White', '2019-07-25 03:07:22'),
(7963, 'en-us', 'oui_player_youtube_controls', 'admin', 'site', 'Commands display and player loading', '2019-07-25 03:07:22'),
(7964, 'en-us', 'oui_player_youtube_controls_0', 'admin', 'site', 'Player loaded directly without commands', '2019-07-25 03:07:22'),
(7965, 'en-us', 'oui_player_youtube_controls_1', 'admin', 'site', 'Player loaded directly with commands', '2019-07-25 03:07:22'),
(7966, 'en-us', 'oui_player_youtube_controls_2', 'admin', 'site', 'Commands displayed and player loading on playing', '2019-07-25 03:07:22'),
(7967, 'en-us', 'oui_player_youtube_disablekb', 'admin', 'site', 'Disable keyboard commands', '2019-07-25 03:07:22'),
(7968, 'en-us', 'oui_player_youtube_enablejsapi', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:07:22'),
(7969, 'en-us', 'oui_player_youtube_end', 'admin', 'site', 'Stop playing at second:', '2019-07-25 03:07:22'),
(7970, 'en-us', 'oui_player_youtube_fs', 'admin', 'site', 'Allow full screen display', '2019-07-25 03:07:22'),
(7971, 'en-us', 'oui_player_youtube_hl', 'admin', 'site', 'Player interface language', '2019-07-25 03:07:22'),
(7972, 'en-us', 'oui_player_youtube_iv_load_policy', 'admin', 'site', 'Video annotations display', '2019-07-25 03:07:22'),
(7973, 'en-us', 'oui_player_youtube_iv_load_policy_1', 'admin', 'site', 'Enable by default', '2019-07-25 03:07:22'),
(7974, 'en-us', 'oui_player_youtube_iv_load_policy_3', 'admin', 'site', 'Disable by default', '2019-07-25 03:07:22'),
(7975, 'en-us', 'oui_player_youtube_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:07:22'),
(7976, 'en-us', 'oui_player_youtube_listtype', 'admin', 'site', 'List Type', '2019-07-25 03:07:22'),
(7977, 'en-us', 'oui_player_youtube_listtype_playlist', 'admin', 'site', 'playlist', '2019-07-25 03:07:22'),
(7978, 'en-us', 'oui_player_youtube_listtype_search', 'admin', 'site', 'search', '2019-07-25 03:07:22'),
(7979, 'en-us', 'oui_player_youtube_listtype_user_uploads', 'admin', 'site', 'user_uploads', '2019-07-25 03:07:22'),
(7980, 'en-us', 'oui_player_youtube_modestbranding', 'admin', 'site', 'Modest branding', '2019-07-25 03:07:22'),
(7981, 'en-us', 'oui_player_youtube_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:07:22'),
(7982, 'en-us', 'oui_player_youtube_playsinline', 'admin', 'site', 'Avoid fullscreen playback by default on iOS', '2019-07-25 03:07:22'),
(7983, 'en-us', 'oui_player_youtube_playlist', 'admin', 'site', 'Video list (id\'s) to play then', '2019-07-25 03:07:22'),
(7984, 'en-us', 'oui_player_youtube_rel', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:07:22'),
(7985, 'en-us', 'oui_player_youtube_showinfo', 'admin', 'site', 'Display video informations', '2019-07-25 03:07:22'),
(7986, 'en-us', 'oui_player_youtube_start', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:07:22'),
(7987, 'en-us', 'oui_player_youtube_theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:07:22'),
(7988, 'en-us', 'oui_player_youtube_theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:07:22'),
(7989, 'en-us', 'oui_player_youtube_theme_light', 'admin', 'site', 'Light', '2019-07-25 03:07:22'),
(7990, 'en-us', 'oui_player_youtube_width', 'admin', 'site', 'Player width', '2019-07-25 03:07:22'),
(7991, 'en-us', 'oui_player_youtube_height', 'admin', 'site', 'Player height', '2019-07-25 03:07:22'),
(7992, 'en-us', 'oui_player_youtube_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:07:22'),
(7993, 'en-us', 'oui_player_youtube_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:07:22'),
(7994, 'el', 'oui_player_provider_youtube', 'admin', 'site', 'Youtube', '2019-07-25 03:07:22'),
(7995, 'el', 'oui_player_youtube', 'admin', 'site', 'Youtube player', '2019-07-25 03:07:22'),
(7996, 'el', 'oui_player_youtube_prefs', 'admin', 'site', 'Display the Youtube player preferences', '2019-07-25 03:07:22'),
(7997, 'el', 'oui_player_youtube_no_cookie', 'admin', 'site', 'Use the cookie free service', '2019-07-25 03:07:22'),
(7998, 'el', 'oui_player_youtube_autohide', 'admin', 'site', 'Player commands display while playing', '2019-07-25 03:07:22'),
(7999, 'el', 'oui_player_youtube_autohide_0', 'admin', 'site', 'Commands and progress bar always visible', '2019-07-25 03:07:22'),
(8000, 'el', 'oui_player_youtube_autohide_1', 'admin', 'site', 'Commands and progress bar on hover only', '2019-07-25 03:07:22'),
(8001, 'el', 'oui_player_youtube_autohide_2', 'admin', 'site', 'Commands on hover and progress bar always visible', '2019-07-25 03:07:22'),
(8002, 'el', 'oui_player_youtube_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:07:22'),
(8003, 'el', 'oui_player_youtube_cc_load_policy', 'admin', 'site', 'Force subtitles display', '2019-07-25 03:07:22'),
(8004, 'el', 'oui_player_youtube_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:07:22'),
(8005, 'el', 'oui_player_youtube_color_red', 'admin', 'site', 'Red', '2019-07-25 03:07:22'),
(8006, 'el', 'oui_player_youtube_color_white', 'admin', 'site', 'White', '2019-07-25 03:07:22'),
(8007, 'el', 'oui_player_youtube_controls', 'admin', 'site', 'Commands display and player loading', '2019-07-25 03:07:22'),
(8008, 'el', 'oui_player_youtube_controls_0', 'admin', 'site', 'Player loaded directly without commands', '2019-07-25 03:07:22'),
(8009, 'el', 'oui_player_youtube_controls_1', 'admin', 'site', 'Player loaded directly with commands', '2019-07-25 03:07:22'),
(8010, 'el', 'oui_player_youtube_controls_2', 'admin', 'site', 'Commands displayed and player loading on playing', '2019-07-25 03:07:22'),
(8011, 'el', 'oui_player_youtube_disablekb', 'admin', 'site', 'Disable keyboard commands', '2019-07-25 03:07:22'),
(8012, 'el', 'oui_player_youtube_enablejsapi', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:07:22'),
(8013, 'el', 'oui_player_youtube_end', 'admin', 'site', 'Stop playing at second:', '2019-07-25 03:07:22'),
(8014, 'el', 'oui_player_youtube_fs', 'admin', 'site', 'Allow full screen display', '2019-07-25 03:07:22'),
(8015, 'el', 'oui_player_youtube_hl', 'admin', 'site', 'Player interface language', '2019-07-25 03:07:22'),
(8016, 'el', 'oui_player_youtube_iv_load_policy', 'admin', 'site', 'Video annotations display', '2019-07-25 03:07:22'),
(8017, 'el', 'oui_player_youtube_iv_load_policy_1', 'admin', 'site', 'Enable by default', '2019-07-25 03:07:22'),
(8018, 'el', 'oui_player_youtube_iv_load_policy_3', 'admin', 'site', 'Disable by default', '2019-07-25 03:07:22'),
(8019, 'el', 'oui_player_youtube_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:07:22'),
(8020, 'el', 'oui_player_youtube_listtype', 'admin', 'site', 'List Type', '2019-07-25 03:07:22'),
(8021, 'el', 'oui_player_youtube_listtype_playlist', 'admin', 'site', 'playlist', '2019-07-25 03:07:22'),
(8022, 'el', 'oui_player_youtube_listtype_search', 'admin', 'site', 'search', '2019-07-25 03:07:22'),
(8023, 'el', 'oui_player_youtube_listtype_user_uploads', 'admin', 'site', 'user_uploads', '2019-07-25 03:07:22'),
(8024, 'el', 'oui_player_youtube_modestbranding', 'admin', 'site', 'Modest branding', '2019-07-25 03:07:22'),
(8025, 'el', 'oui_player_youtube_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:07:22'),
(8026, 'el', 'oui_player_youtube_playsinline', 'admin', 'site', 'Avoid fullscreen playback by default on iOS', '2019-07-25 03:07:22'),
(8027, 'el', 'oui_player_youtube_playlist', 'admin', 'site', 'Video list (id\'s) to play then', '2019-07-25 03:07:22'),
(8028, 'el', 'oui_player_youtube_rel', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:07:22'),
(8029, 'el', 'oui_player_youtube_showinfo', 'admin', 'site', 'Display video informations', '2019-07-25 03:07:22'),
(8030, 'el', 'oui_player_youtube_start', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:07:22'),
(8031, 'el', 'oui_player_youtube_theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:07:22'),
(8032, 'el', 'oui_player_youtube_theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:07:22'),
(8033, 'el', 'oui_player_youtube_theme_light', 'admin', 'site', 'Light', '2019-07-25 03:07:22'),
(8034, 'el', 'oui_player_youtube_width', 'admin', 'site', 'Player width', '2019-07-25 03:07:22'),
(8035, 'el', 'oui_player_youtube_height', 'admin', 'site', 'Player height', '2019-07-25 03:07:22'),
(8036, 'el', 'oui_player_youtube_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:07:22'),
(8037, 'el', 'oui_player_youtube_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:07:22'),
(8038, 'en', 'oui_player_provider_youtube', 'admin', 'site', 'Youtube', '2019-07-25 03:07:22'),
(8039, 'en', 'oui_player_youtube', 'admin', 'site', 'Youtube player', '2019-07-25 03:07:22'),
(8040, 'en', 'oui_player_youtube_prefs', 'admin', 'site', 'Display the Youtube player preferences', '2019-07-25 03:07:22'),
(8041, 'en', 'oui_player_youtube_no_cookie', 'admin', 'site', 'Use the cookie free service', '2019-07-25 03:07:22'),
(8042, 'en', 'oui_player_youtube_autohide', 'admin', 'site', 'Player commands display while playing', '2019-07-25 03:07:22'),
(8043, 'en', 'oui_player_youtube_autohide_0', 'admin', 'site', 'Commands and progress bar always visible', '2019-07-25 03:07:22'),
(8044, 'en', 'oui_player_youtube_autohide_1', 'admin', 'site', 'Commands and progress bar on hover only', '2019-07-25 03:07:22'),
(8045, 'en', 'oui_player_youtube_autohide_2', 'admin', 'site', 'Commands on hover and progress bar always visible', '2019-07-25 03:07:22'),
(8046, 'en', 'oui_player_youtube_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:07:22'),
(8047, 'en', 'oui_player_youtube_cc_load_policy', 'admin', 'site', 'Force subtitles display', '2019-07-25 03:07:22'),
(8048, 'en', 'oui_player_youtube_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:07:22'),
(8049, 'en', 'oui_player_youtube_color_red', 'admin', 'site', 'Red', '2019-07-25 03:07:22'),
(8050, 'en', 'oui_player_youtube_color_white', 'admin', 'site', 'White', '2019-07-25 03:07:22'),
(8051, 'en', 'oui_player_youtube_controls', 'admin', 'site', 'Commands display and player loading', '2019-07-25 03:07:22'),
(8052, 'en', 'oui_player_youtube_controls_0', 'admin', 'site', 'Player loaded directly without commands', '2019-07-25 03:07:22'),
(8053, 'en', 'oui_player_youtube_controls_1', 'admin', 'site', 'Player loaded directly with commands', '2019-07-25 03:07:22'),
(8054, 'en', 'oui_player_youtube_controls_2', 'admin', 'site', 'Commands displayed and player loading on playing', '2019-07-25 03:07:22'),
(8055, 'en', 'oui_player_youtube_disablekb', 'admin', 'site', 'Disable keyboard commands', '2019-07-25 03:07:22'),
(8056, 'en', 'oui_player_youtube_enablejsapi', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:07:22'),
(8057, 'en', 'oui_player_youtube_end', 'admin', 'site', 'Stop playing at second:', '2019-07-25 03:07:22'),
(8058, 'en', 'oui_player_youtube_fs', 'admin', 'site', 'Allow full screen display', '2019-07-25 03:07:22'),
(8059, 'en', 'oui_player_youtube_hl', 'admin', 'site', 'Player interface language', '2019-07-25 03:07:22'),
(8060, 'en', 'oui_player_youtube_iv_load_policy', 'admin', 'site', 'Video annotations display', '2019-07-25 03:07:22'),
(8061, 'en', 'oui_player_youtube_iv_load_policy_1', 'admin', 'site', 'Enable by default', '2019-07-25 03:07:22'),
(8062, 'en', 'oui_player_youtube_iv_load_policy_3', 'admin', 'site', 'Disable by default', '2019-07-25 03:07:22'),
(8063, 'en', 'oui_player_youtube_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:07:22'),
(8064, 'en', 'oui_player_youtube_listtype', 'admin', 'site', 'List Type', '2019-07-25 03:07:22'),
(8065, 'en', 'oui_player_youtube_listtype_playlist', 'admin', 'site', 'playlist', '2019-07-25 03:07:22'),
(8066, 'en', 'oui_player_youtube_listtype_search', 'admin', 'site', 'search', '2019-07-25 03:07:22'),
(8067, 'en', 'oui_player_youtube_listtype_user_uploads', 'admin', 'site', 'user_uploads', '2019-07-25 03:07:22'),
(8068, 'en', 'oui_player_youtube_modestbranding', 'admin', 'site', 'Modest branding', '2019-07-25 03:07:22'),
(8069, 'en', 'oui_player_youtube_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:07:22'),
(8070, 'en', 'oui_player_youtube_playsinline', 'admin', 'site', 'Avoid fullscreen playback by default on iOS', '2019-07-25 03:07:22'),
(8071, 'en', 'oui_player_youtube_playlist', 'admin', 'site', 'Video list (id\'s) to play then', '2019-07-25 03:07:22'),
(8072, 'en', 'oui_player_youtube_rel', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:07:22'),
(8073, 'en', 'oui_player_youtube_showinfo', 'admin', 'site', 'Display video informations', '2019-07-25 03:07:22'),
(8074, 'en', 'oui_player_youtube_start', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:07:22'),
(8075, 'en', 'oui_player_youtube_theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:07:22'),
(8076, 'en', 'oui_player_youtube_theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:07:22'),
(8077, 'en', 'oui_player_youtube_theme_light', 'admin', 'site', 'Light', '2019-07-25 03:07:22'),
(8078, 'en', 'oui_player_youtube_width', 'admin', 'site', 'Player width', '2019-07-25 03:07:22'),
(8079, 'en', 'oui_player_youtube_height', 'admin', 'site', 'Player height', '2019-07-25 03:07:22'),
(8080, 'en', 'oui_player_youtube_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:07:22'),
(8081, 'en', 'oui_player_youtube_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:07:22'),
(8082, 'de', 'oui_player_provider_spotify', 'admin', 'site', 'Spotify', '2019-07-25 03:08:46'),
(8083, 'de', 'oui_player_spotify', 'admin', 'site', 'Spotify player', '2019-07-25 03:08:46'),
(8084, 'de', 'oui_player_spotify_prefs', 'admin', 'site', 'Display the Spotify player preferences', '2019-07-25 03:08:46'),
(8085, 'de', 'oui_player_spotify_width', 'admin', 'site', 'Player width', '2019-07-25 03:08:46'),
(8086, 'de', 'oui_player_spotify_height', 'admin', 'site', 'Player height', '2019-07-25 03:08:46'),
(8087, 'de', 'oui_player_spotify_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:08:46'),
(8088, 'de', 'oui_player_spotify_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:08:46'),
(8089, 'en-us', 'oui_player_provider_spotify', 'admin', 'site', 'Spotify', '2019-07-25 03:08:46'),
(8090, 'en-us', 'oui_player_spotify', 'admin', 'site', 'Spotify player', '2019-07-25 03:08:46'),
(8091, 'en-us', 'oui_player_spotify_prefs', 'admin', 'site', 'Display the Spotify player preferences', '2019-07-25 03:08:46'),
(8092, 'en-us', 'oui_player_spotify_width', 'admin', 'site', 'Player width', '2019-07-25 03:08:46'),
(8093, 'en-us', 'oui_player_spotify_height', 'admin', 'site', 'Player height', '2019-07-25 03:08:46'),
(8094, 'en-us', 'oui_player_spotify_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:08:46'),
(8095, 'en-us', 'oui_player_spotify_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:08:46'),
(8096, 'el', 'oui_player_provider_spotify', 'admin', 'site', 'Spotify', '2019-07-25 03:08:46'),
(8097, 'el', 'oui_player_spotify', 'admin', 'site', 'Spotify player', '2019-07-25 03:08:46'),
(8098, 'el', 'oui_player_spotify_prefs', 'admin', 'site', 'Display the Spotify player preferences', '2019-07-25 03:08:46'),
(8099, 'el', 'oui_player_spotify_width', 'admin', 'site', 'Player width', '2019-07-25 03:08:46'),
(8100, 'el', 'oui_player_spotify_height', 'admin', 'site', 'Player height', '2019-07-25 03:08:46'),
(8101, 'el', 'oui_player_spotify_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:08:46'),
(8102, 'el', 'oui_player_spotify_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:08:46'),
(8103, 'en', 'oui_player_provider_spotify', 'admin', 'site', 'Spotify', '2019-07-25 03:08:46'),
(8104, 'en', 'oui_player_spotify', 'admin', 'site', 'Spotify player', '2019-07-25 03:08:46'),
(8105, 'en', 'oui_player_spotify_prefs', 'admin', 'site', 'Display the Spotify player preferences', '2019-07-25 03:08:46'),
(8106, 'en', 'oui_player_spotify_width', 'admin', 'site', 'Player width', '2019-07-25 03:08:46'),
(8107, 'en', 'oui_player_spotify_height', 'admin', 'site', 'Player height', '2019-07-25 03:08:46'),
(8108, 'en', 'oui_player_spotify_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:08:46'),
(8109, 'en', 'oui_player_spotify_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:08:46'),
(8110, 'de', 'oui_player_provider_audio', 'admin', 'site', 'HTML audio', '2019-07-25 03:13:24'),
(8111, 'de', 'oui_player_audio', 'admin', 'site', 'HTML audio player', '2019-07-25 03:13:24'),
(8112, 'de', 'oui_player_audio_prefs', 'admin', 'site', 'Display the HTML audio player prefs', '2019-07-25 03:13:24'),
(8113, 'de', 'oui_player_audio_width', 'admin', 'site', 'Player width', '2019-07-25 03:13:24'),
(8114, 'de', 'oui_player_audio_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:13:24'),
(8115, 'de', 'oui_player_audio_autoplay', 'admin', 'site', 'automatic playing', '2019-07-25 03:13:24'),
(8116, 'de', 'oui_player_audio_controls', 'admin', 'site', 'Display controls', '2019-07-25 03:13:24'),
(8117, 'de', 'oui_player_audio_loop', 'admin', 'site', 'Replay once finished', '2019-07-25 03:13:24'),
(8118, 'de', 'oui_player_audio_muted', 'admin', 'site', 'Disable sound', '2019-07-25 03:13:24'),
(8119, 'de', 'oui_player_audio_preload', 'admin', 'site', 'Loading type', '2019-07-25 03:13:24'),
(8120, 'de', 'oui_player_audio_preload_auto', 'admin', 'site', 'auto', '2019-07-25 03:13:24'),
(8121, 'de', 'oui_player_audio_preload_metadata', 'admin', 'site', 'metadata', '2019-07-25 03:13:24'),
(8122, 'de', 'oui_player_audio_preload_none', 'admin', 'site', 'none', '2019-07-25 03:13:24'),
(8123, 'de', 'oui_player_audio_volume', 'admin', 'site', 'Volume (0 to 1)', '2019-07-25 03:13:24'),
(8124, 'de', 'oui_player_audio_unavailable', 'admin', 'site', 'Sorry, the HTML audio player is not supported by your browser.', '2019-07-25 03:13:24'),
(8125, 'de', 'oui_player_html_player_not_supported', 'public', 'site', 'Unfortunatly, your browser is not able to read <a href=\"{src}\">{file}</a>. The HTML5 {player} tag is not supported.', '2019-07-25 03:13:24'),
(8126, 'en-us', 'oui_player_provider_audio', 'admin', 'site', 'HTML audio', '2019-07-25 03:13:24'),
(8127, 'en-us', 'oui_player_audio', 'admin', 'site', 'HTML audio player', '2019-07-25 03:13:24'),
(8128, 'en-us', 'oui_player_audio_prefs', 'admin', 'site', 'Display the HTML audio player prefs', '2019-07-25 03:13:24'),
(8129, 'en-us', 'oui_player_audio_width', 'admin', 'site', 'Player width', '2019-07-25 03:13:24'),
(8130, 'en-us', 'oui_player_audio_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:13:24'),
(8131, 'en-us', 'oui_player_audio_autoplay', 'admin', 'site', 'automatic playing', '2019-07-25 03:13:24'),
(8132, 'en-us', 'oui_player_audio_controls', 'admin', 'site', 'Display controls', '2019-07-25 03:13:24'),
(8133, 'en-us', 'oui_player_audio_loop', 'admin', 'site', 'Replay once finished', '2019-07-25 03:13:24'),
(8134, 'en-us', 'oui_player_audio_muted', 'admin', 'site', 'Disable sound', '2019-07-25 03:13:24'),
(8135, 'en-us', 'oui_player_audio_preload', 'admin', 'site', 'Loading type', '2019-07-25 03:13:24'),
(8136, 'en-us', 'oui_player_audio_preload_auto', 'admin', 'site', 'auto', '2019-07-25 03:13:24'),
(8137, 'en-us', 'oui_player_audio_preload_metadata', 'admin', 'site', 'metadata', '2019-07-25 03:13:24'),
(8138, 'en-us', 'oui_player_audio_preload_none', 'admin', 'site', 'none', '2019-07-25 03:13:24'),
(8139, 'en-us', 'oui_player_audio_volume', 'admin', 'site', 'Volume (0 to 1)', '2019-07-25 03:13:24'),
(8140, 'en-us', 'oui_player_audio_unavailable', 'admin', 'site', 'Sorry, the HTML audio player is not supported by your browser.', '2019-07-25 03:13:24'),
(8141, 'en-us', 'oui_player_html_player_not_supported', 'public', 'site', 'Unfortunatly, your browser is not able to read <a href=\"{src}\">{file}</a>. The HTML5 {player} tag is not supported.', '2019-07-25 03:13:24'),
(8142, 'el', 'oui_player_provider_audio', 'admin', 'site', 'HTML audio', '2019-07-25 03:13:24'),
(8143, 'el', 'oui_player_audio', 'admin', 'site', 'HTML audio player', '2019-07-25 03:13:24'),
(8144, 'el', 'oui_player_audio_prefs', 'admin', 'site', 'Display the HTML audio player prefs', '2019-07-25 03:13:24'),
(8145, 'el', 'oui_player_audio_width', 'admin', 'site', 'Player width', '2019-07-25 03:13:24'),
(8146, 'el', 'oui_player_audio_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:13:24'),
(8147, 'el', 'oui_player_audio_autoplay', 'admin', 'site', 'automatic playing', '2019-07-25 03:13:24'),
(8148, 'el', 'oui_player_audio_controls', 'admin', 'site', 'Display controls', '2019-07-25 03:13:24'),
(8149, 'el', 'oui_player_audio_loop', 'admin', 'site', 'Replay once finished', '2019-07-25 03:13:24'),
(8150, 'el', 'oui_player_audio_muted', 'admin', 'site', 'Disable sound', '2019-07-25 03:13:24'),
(8151, 'el', 'oui_player_audio_preload', 'admin', 'site', 'Loading type', '2019-07-25 03:13:24'),
(8152, 'el', 'oui_player_audio_preload_auto', 'admin', 'site', 'auto', '2019-07-25 03:13:24'),
(8153, 'el', 'oui_player_audio_preload_metadata', 'admin', 'site', 'metadata', '2019-07-25 03:13:24'),
(8154, 'el', 'oui_player_audio_preload_none', 'admin', 'site', 'none', '2019-07-25 03:13:24'),
(8155, 'el', 'oui_player_audio_volume', 'admin', 'site', 'Volume (0 to 1)', '2019-07-25 03:13:24'),
(8156, 'el', 'oui_player_audio_unavailable', 'admin', 'site', 'Sorry, the HTML audio player is not supported by your browser.', '2019-07-25 03:13:24'),
(8157, 'el', 'oui_player_html_player_not_supported', 'public', 'site', 'Unfortunatly, your browser is not able to read <a href=\"{src}\">{file}</a>. The HTML5 {player} tag is not supported.', '2019-07-25 03:13:24'),
(8158, 'en', 'oui_player_provider_audio', 'admin', 'site', 'HTML audio', '2019-07-25 03:13:24'),
(8159, 'en', 'oui_player_audio', 'admin', 'site', 'HTML audio player', '2019-07-25 03:13:24'),
(8160, 'en', 'oui_player_audio_prefs', 'admin', 'site', 'Display the HTML audio player prefs', '2019-07-25 03:13:24'),
(8161, 'en', 'oui_player_audio_width', 'admin', 'site', 'Player width', '2019-07-25 03:13:24'),
(8162, 'en', 'oui_player_audio_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:13:24'),
(8163, 'en', 'oui_player_audio_autoplay', 'admin', 'site', 'automatic playing', '2019-07-25 03:13:24'),
(8164, 'en', 'oui_player_audio_controls', 'admin', 'site', 'Display controls', '2019-07-25 03:13:24'),
(8165, 'en', 'oui_player_audio_loop', 'admin', 'site', 'Replay once finished', '2019-07-25 03:13:24'),
(8166, 'en', 'oui_player_audio_muted', 'admin', 'site', 'Disable sound', '2019-07-25 03:13:24'),
(8167, 'en', 'oui_player_audio_preload', 'admin', 'site', 'Loading type', '2019-07-25 03:13:24'),
(8168, 'en', 'oui_player_audio_preload_auto', 'admin', 'site', 'auto', '2019-07-25 03:13:24'),
(8169, 'en', 'oui_player_audio_preload_metadata', 'admin', 'site', 'metadata', '2019-07-25 03:13:24'),
(8170, 'en', 'oui_player_audio_preload_none', 'admin', 'site', 'none', '2019-07-25 03:13:24'),
(8171, 'en', 'oui_player_audio_volume', 'admin', 'site', 'Volume (0 to 1)', '2019-07-25 03:13:24'),
(8172, 'en', 'oui_player_audio_unavailable', 'admin', 'site', 'Sorry, the HTML audio player is not supported by your browser.', '2019-07-25 03:13:24'),
(8173, 'en', 'oui_player_html_player_not_supported', 'public', 'site', 'Unfortunatly, your browser is not able to read <a href=\"{src}\">{file}</a>. The HTML5 {player} tag is not supported.', '2019-07-25 03:13:24'),
(8174, 'de', 'oui_player_provider_abcnews', 'admin', 'site', 'ABC News', '2019-07-25 03:19:00'),
(8175, 'de', 'oui_player_abcnews', 'admin', 'site', 'ABC News player', '2019-07-25 03:19:00'),
(8176, 'de', 'oui_player_abcnews_prefs', 'admin', 'site', 'Display the ABC News player preferences', '2019-07-25 03:19:00'),
(8177, 'de', 'oui_player_abcnews_width', 'admin', 'site', 'Player width', '2019-07-25 03:19:00'),
(8178, 'de', 'oui_player_abcnews_height', 'admin', 'site', 'Player height', '2019-07-25 03:19:00'),
(8179, 'de', 'oui_player_abcnews_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:19:00'),
(8180, 'de', 'oui_player_abcnews_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:19:00'),
(8181, 'en-us', 'oui_player_provider_abcnews', 'admin', 'site', 'ABC News', '2019-07-25 03:19:01'),
(8182, 'en-us', 'oui_player_abcnews', 'admin', 'site', 'ABC News player', '2019-07-25 03:19:01'),
(8183, 'en-us', 'oui_player_abcnews_prefs', 'admin', 'site', 'Display the ABC News player preferences', '2019-07-25 03:19:01'),
(8184, 'en-us', 'oui_player_abcnews_width', 'admin', 'site', 'Player width', '2019-07-25 03:19:01'),
(8185, 'en-us', 'oui_player_abcnews_height', 'admin', 'site', 'Player height', '2019-07-25 03:19:01'),
(8186, 'en-us', 'oui_player_abcnews_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:19:01');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(8187, 'en-us', 'oui_player_abcnews_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:19:01'),
(8188, 'el', 'oui_player_provider_abcnews', 'admin', 'site', 'ABC News', '2019-07-25 03:19:01'),
(8189, 'el', 'oui_player_abcnews', 'admin', 'site', 'ABC News player', '2019-07-25 03:19:01'),
(8190, 'el', 'oui_player_abcnews_prefs', 'admin', 'site', 'Display the ABC News player preferences', '2019-07-25 03:19:01'),
(8191, 'el', 'oui_player_abcnews_width', 'admin', 'site', 'Player width', '2019-07-25 03:19:01'),
(8192, 'el', 'oui_player_abcnews_height', 'admin', 'site', 'Player height', '2019-07-25 03:19:01'),
(8193, 'el', 'oui_player_abcnews_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:19:01'),
(8194, 'el', 'oui_player_abcnews_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:19:01'),
(8195, 'en', 'oui_player_provider_abcnews', 'admin', 'site', 'ABC News', '2019-07-25 03:19:01'),
(8196, 'en', 'oui_player_abcnews', 'admin', 'site', 'ABC News player', '2019-07-25 03:19:01'),
(8197, 'en', 'oui_player_abcnews_prefs', 'admin', 'site', 'Display the ABC News player preferences', '2019-07-25 03:19:01'),
(8198, 'en', 'oui_player_abcnews_width', 'admin', 'site', 'Player width', '2019-07-25 03:19:01'),
(8199, 'en', 'oui_player_abcnews_height', 'admin', 'site', 'Player height', '2019-07-25 03:19:01'),
(8200, 'en', 'oui_player_abcnews_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:19:01'),
(8201, 'en', 'oui_player_abcnews_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:19:01'),
(8202, 'de', 'oui_player_provider_soundcloud', 'admin', 'site', 'Soundcloud', '2019-07-25 03:23:50'),
(8203, 'de', 'oui_player_soundcloud', 'admin', 'site', 'Soundcloud player', '2019-07-25 03:23:50'),
(8204, 'de', 'oui_player_soundcloud_prefs', 'admin', 'site', 'Display the Soundcloud player preferences', '2019-07-25 03:23:50'),
(8205, 'de', 'oui_player_soundcloud_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:23:50'),
(8206, 'de', 'oui_player_soundcloud_theme_color', 'admin', 'site', 'Preferred theme color', '2019-07-25 03:23:50'),
(8207, 'de', 'oui_player_soundcloud_text_download_track', 'admin', 'site', 'Text for the <em>Download</em> link', '2019-07-25 03:23:50'),
(8208, 'de', 'oui_player_soundcloud_buying', 'admin', 'site', 'Enable the <em>Buy</em> button', '2019-07-25 03:23:50'),
(8209, 'de', 'oui_player_soundcloud_sharing', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:23:50'),
(8210, 'de', 'oui_player_soundcloud_download', 'admin', 'site', 'Enable the <em>Download</em> link', '2019-07-25 03:23:50'),
(8211, 'de', 'oui_player_soundcloud_hide_related', 'admin', 'site', 'Hide related tracks', '2019-07-25 03:23:50'),
(8212, 'de', 'oui_player_soundcloud_show_reposts', 'admin', 'site', 'Display reposts', '2019-07-25 03:23:50'),
(8213, 'de', 'oui_player_soundcloud_show_playcount', 'admin', 'site', 'Display the play count', '2019-07-25 03:23:50'),
(8214, 'de', 'oui_player_soundcloud_start_track', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:23:50'),
(8215, 'de', 'oui_player_soundcloud_single_active', 'admin', 'site', 'Stop playing when another Soundcloud player is on', '2019-07-25 03:23:50'),
(8216, 'de', 'oui_player_soundcloud_show_user', 'admin', 'site', 'Display the uploader name', '2019-07-25 03:23:50'),
(8217, 'de', 'oui_player_soundcloud_auto_play', 'admin', 'site', 'Automatic playing', '2019-07-25 03:23:50'),
(8218, 'de', 'oui_player_soundcloud_show_artwork', 'admin', 'site', 'Display the art work', '2019-07-25 03:23:50'),
(8219, 'de', 'oui_player_soundcloud_show_comments', 'admin', 'site', 'Display comments', '2019-07-25 03:23:50'),
(8220, 'de', 'oui_player_soundcloud_visual', 'admin', 'site', 'Display the visual as a player background', '2019-07-25 03:23:50'),
(8221, 'de', 'oui_player_soundcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:23:50'),
(8222, 'de', 'oui_player_soundcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:23:50'),
(8223, 'de', 'oui_player_soundcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:23:50'),
(8224, 'de', 'oui_player_soundcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:23:50'),
(8225, 'en-us', 'oui_player_provider_soundcloud', 'admin', 'site', 'Soundcloud', '2019-07-25 03:23:50'),
(8226, 'en-us', 'oui_player_soundcloud', 'admin', 'site', 'Soundcloud player', '2019-07-25 03:23:50'),
(8227, 'en-us', 'oui_player_soundcloud_prefs', 'admin', 'site', 'Display the Soundcloud player preferences', '2019-07-25 03:23:50'),
(8228, 'en-us', 'oui_player_soundcloud_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:23:50'),
(8229, 'en-us', 'oui_player_soundcloud_theme_color', 'admin', 'site', 'Preferred theme color', '2019-07-25 03:23:50'),
(8230, 'en-us', 'oui_player_soundcloud_text_download_track', 'admin', 'site', 'Text for the <em>Download</em> link', '2019-07-25 03:23:50'),
(8231, 'en-us', 'oui_player_soundcloud_buying', 'admin', 'site', 'Enable the <em>Buy</em> button', '2019-07-25 03:23:50'),
(8232, 'en-us', 'oui_player_soundcloud_sharing', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:23:50'),
(8233, 'en-us', 'oui_player_soundcloud_download', 'admin', 'site', 'Enable the <em>Download</em> link', '2019-07-25 03:23:50'),
(8234, 'en-us', 'oui_player_soundcloud_hide_related', 'admin', 'site', 'Hide related tracks', '2019-07-25 03:23:50'),
(8235, 'en-us', 'oui_player_soundcloud_show_reposts', 'admin', 'site', 'Display reposts', '2019-07-25 03:23:50'),
(8236, 'en-us', 'oui_player_soundcloud_show_playcount', 'admin', 'site', 'Display the play count', '2019-07-25 03:23:50'),
(8237, 'en-us', 'oui_player_soundcloud_start_track', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:23:50'),
(8238, 'en-us', 'oui_player_soundcloud_single_active', 'admin', 'site', 'Stop playing when another Soundcloud player is on', '2019-07-25 03:23:50'),
(8239, 'en-us', 'oui_player_soundcloud_show_user', 'admin', 'site', 'Display the uploader name', '2019-07-25 03:23:50'),
(8240, 'en-us', 'oui_player_soundcloud_auto_play', 'admin', 'site', 'Automatic playing', '2019-07-25 03:23:50'),
(8241, 'en-us', 'oui_player_soundcloud_show_artwork', 'admin', 'site', 'Display the art work', '2019-07-25 03:23:50'),
(8242, 'en-us', 'oui_player_soundcloud_show_comments', 'admin', 'site', 'Display comments', '2019-07-25 03:23:50'),
(8243, 'en-us', 'oui_player_soundcloud_visual', 'admin', 'site', 'Display the visual as a player background', '2019-07-25 03:23:50'),
(8244, 'en-us', 'oui_player_soundcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:23:50'),
(8245, 'en-us', 'oui_player_soundcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:23:50'),
(8246, 'en-us', 'oui_player_soundcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:23:50'),
(8247, 'en-us', 'oui_player_soundcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:23:50'),
(8248, 'el', 'oui_player_provider_soundcloud', 'admin', 'site', 'Soundcloud', '2019-07-25 03:23:50'),
(8249, 'el', 'oui_player_soundcloud', 'admin', 'site', 'Soundcloud player', '2019-07-25 03:23:50'),
(8250, 'el', 'oui_player_soundcloud_prefs', 'admin', 'site', 'Display the Soundcloud player preferences', '2019-07-25 03:23:50'),
(8251, 'el', 'oui_player_soundcloud_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:23:50'),
(8252, 'el', 'oui_player_soundcloud_theme_color', 'admin', 'site', 'Preferred theme color', '2019-07-25 03:23:50'),
(8253, 'el', 'oui_player_soundcloud_text_download_track', 'admin', 'site', 'Text for the <em>Download</em> link', '2019-07-25 03:23:50'),
(8254, 'el', 'oui_player_soundcloud_buying', 'admin', 'site', 'Enable the <em>Buy</em> button', '2019-07-25 03:23:50'),
(8255, 'el', 'oui_player_soundcloud_sharing', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:23:50'),
(8256, 'el', 'oui_player_soundcloud_download', 'admin', 'site', 'Enable the <em>Download</em> link', '2019-07-25 03:23:50'),
(8257, 'el', 'oui_player_soundcloud_hide_related', 'admin', 'site', 'Hide related tracks', '2019-07-25 03:23:50'),
(8258, 'el', 'oui_player_soundcloud_show_reposts', 'admin', 'site', 'Display reposts', '2019-07-25 03:23:50'),
(8259, 'el', 'oui_player_soundcloud_show_playcount', 'admin', 'site', 'Display the play count', '2019-07-25 03:23:50'),
(8260, 'el', 'oui_player_soundcloud_start_track', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:23:50'),
(8261, 'el', 'oui_player_soundcloud_single_active', 'admin', 'site', 'Stop playing when another Soundcloud player is on', '2019-07-25 03:23:50'),
(8262, 'el', 'oui_player_soundcloud_show_user', 'admin', 'site', 'Display the uploader name', '2019-07-25 03:23:50'),
(8263, 'el', 'oui_player_soundcloud_auto_play', 'admin', 'site', 'Automatic playing', '2019-07-25 03:23:50'),
(8264, 'el', 'oui_player_soundcloud_show_artwork', 'admin', 'site', 'Display the art work', '2019-07-25 03:23:50'),
(8265, 'el', 'oui_player_soundcloud_show_comments', 'admin', 'site', 'Display comments', '2019-07-25 03:23:50'),
(8266, 'el', 'oui_player_soundcloud_visual', 'admin', 'site', 'Display the visual as a player background', '2019-07-25 03:23:50'),
(8267, 'el', 'oui_player_soundcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:23:50'),
(8268, 'el', 'oui_player_soundcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:23:50'),
(8269, 'el', 'oui_player_soundcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:23:50'),
(8270, 'el', 'oui_player_soundcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:23:50'),
(8271, 'en', 'oui_player_provider_soundcloud', 'admin', 'site', 'Soundcloud', '2019-07-25 03:23:50'),
(8272, 'en', 'oui_player_soundcloud', 'admin', 'site', 'Soundcloud player', '2019-07-25 03:23:50'),
(8273, 'en', 'oui_player_soundcloud_prefs', 'admin', 'site', 'Display the Soundcloud player preferences', '2019-07-25 03:23:50'),
(8274, 'en', 'oui_player_soundcloud_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:23:50'),
(8275, 'en', 'oui_player_soundcloud_theme_color', 'admin', 'site', 'Preferred theme color', '2019-07-25 03:23:50'),
(8276, 'en', 'oui_player_soundcloud_text_download_track', 'admin', 'site', 'Text for the <em>Download</em> link', '2019-07-25 03:23:50'),
(8277, 'en', 'oui_player_soundcloud_buying', 'admin', 'site', 'Enable the <em>Buy</em> button', '2019-07-25 03:23:50'),
(8278, 'en', 'oui_player_soundcloud_sharing', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:23:50'),
(8279, 'en', 'oui_player_soundcloud_download', 'admin', 'site', 'Enable the <em>Download</em> link', '2019-07-25 03:23:50'),
(8280, 'en', 'oui_player_soundcloud_hide_related', 'admin', 'site', 'Hide related tracks', '2019-07-25 03:23:50'),
(8281, 'en', 'oui_player_soundcloud_show_reposts', 'admin', 'site', 'Display reposts', '2019-07-25 03:23:50'),
(8282, 'en', 'oui_player_soundcloud_show_playcount', 'admin', 'site', 'Display the play count', '2019-07-25 03:23:50'),
(8283, 'en', 'oui_player_soundcloud_start_track', 'admin', 'site', 'Start playing at second:', '2019-07-25 03:23:50'),
(8284, 'en', 'oui_player_soundcloud_single_active', 'admin', 'site', 'Stop playing when another Soundcloud player is on', '2019-07-25 03:23:50'),
(8285, 'en', 'oui_player_soundcloud_show_user', 'admin', 'site', 'Display the uploader name', '2019-07-25 03:23:50'),
(8286, 'en', 'oui_player_soundcloud_auto_play', 'admin', 'site', 'Automatic playing', '2019-07-25 03:23:50'),
(8287, 'en', 'oui_player_soundcloud_show_artwork', 'admin', 'site', 'Display the art work', '2019-07-25 03:23:50'),
(8288, 'en', 'oui_player_soundcloud_show_comments', 'admin', 'site', 'Display comments', '2019-07-25 03:23:50'),
(8289, 'en', 'oui_player_soundcloud_visual', 'admin', 'site', 'Display the visual as a player background', '2019-07-25 03:23:50'),
(8290, 'en', 'oui_player_soundcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:23:50'),
(8291, 'en', 'oui_player_soundcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:23:50'),
(8292, 'en', 'oui_player_soundcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:23:50'),
(8293, 'en', 'oui_player_soundcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:23:50'),
(8294, 'de', 'oui_player_provider_giphy', 'admin', 'site', 'Giphy', '2019-07-25 03:22:24'),
(8295, 'de', 'oui_player_giphy', 'admin', 'site', 'Giphy player', '2019-07-25 03:22:24'),
(8296, 'de', 'oui_player_giphy_prefs', 'admin', 'site', 'Display the Giphy player preferences', '2019-07-25 03:22:24'),
(8297, 'de', 'oui_player_giphy_width', 'admin', 'site', 'Player width', '2019-07-25 03:22:24'),
(8298, 'de', 'oui_player_giphy_height', 'admin', 'site', 'Player height', '2019-07-25 03:22:24'),
(8299, 'de', 'oui_player_giphy_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:22:24'),
(8300, 'de', 'oui_player_giphy_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:22:24'),
(8301, 'en-us', 'oui_player_provider_giphy', 'admin', 'site', 'Giphy', '2019-07-25 03:22:24'),
(8302, 'en-us', 'oui_player_giphy', 'admin', 'site', 'Giphy player', '2019-07-25 03:22:24'),
(8303, 'en-us', 'oui_player_giphy_prefs', 'admin', 'site', 'Display the Giphy player preferences', '2019-07-25 03:22:24'),
(8304, 'en-us', 'oui_player_giphy_width', 'admin', 'site', 'Player width', '2019-07-25 03:22:24'),
(8305, 'en-us', 'oui_player_giphy_height', 'admin', 'site', 'Player height', '2019-07-25 03:22:24'),
(8306, 'en-us', 'oui_player_giphy_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:22:24'),
(8307, 'en-us', 'oui_player_giphy_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:22:24'),
(8308, 'el', 'oui_player_provider_giphy', 'admin', 'site', 'Giphy', '2019-07-25 03:22:24'),
(8309, 'el', 'oui_player_giphy', 'admin', 'site', 'Giphy player', '2019-07-25 03:22:24'),
(8310, 'el', 'oui_player_giphy_prefs', 'admin', 'site', 'Display the Giphy player preferences', '2019-07-25 03:22:24'),
(8311, 'el', 'oui_player_giphy_width', 'admin', 'site', 'Player width', '2019-07-25 03:22:24'),
(8312, 'el', 'oui_player_giphy_height', 'admin', 'site', 'Player height', '2019-07-25 03:22:24'),
(8313, 'el', 'oui_player_giphy_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:22:24'),
(8314, 'el', 'oui_player_giphy_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:22:24'),
(8315, 'en', 'oui_player_provider_giphy', 'admin', 'site', 'Giphy', '2019-07-25 03:22:24'),
(8316, 'en', 'oui_player_giphy', 'admin', 'site', 'Giphy player', '2019-07-25 03:22:24'),
(8317, 'en', 'oui_player_giphy_prefs', 'admin', 'site', 'Display the Giphy player preferences', '2019-07-25 03:22:24'),
(8318, 'en', 'oui_player_giphy_width', 'admin', 'site', 'Player width', '2019-07-25 03:22:24'),
(8319, 'en', 'oui_player_giphy_height', 'admin', 'site', 'Player height', '2019-07-25 03:22:24'),
(8320, 'en', 'oui_player_giphy_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:22:24'),
(8321, 'en', 'oui_player_giphy_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:22:24'),
(8322, 'de', 'oui_player_provider_vimeo', 'admin', 'site', 'Vimeo', '2019-07-25 03:25:36'),
(8323, 'de', 'oui_player_vimeo', 'admin', 'site', 'Vimeo player', '2019-07-25 03:25:36'),
(8324, 'de', 'oui_player_vimeo_prefs', 'admin', 'site', 'Display the Vimeo player preferences', '2019-07-25 03:25:36'),
(8325, 'de', 'oui_player_vimeo_width', 'admin', 'site', 'Player width', '2019-07-25 03:25:36'),
(8326, 'de', 'oui_player_vimeo_height', 'admin', 'site', 'Player height', '2019-07-25 03:25:36'),
(8327, 'de', 'oui_player_vimeo_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:25:36'),
(8328, 'de', 'oui_player_vimeo_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions/ratio', '2019-07-25 03:25:36'),
(8329, 'de', 'oui_player_vimeo_api', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:25:36'),
(8330, 'de', 'oui_player_vimeo_autopause', 'admin', 'site', 'Automatic pausing when another video is playing', '2019-07-25 03:25:36'),
(8331, 'de', 'oui_player_vimeo_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:25:36'),
(8332, 'de', 'oui_player_vimeo_badge', 'admin', 'site', 'Display the badge', '2019-07-25 03:25:36'),
(8333, 'de', 'oui_player_vimeo_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:25:36'),
(8334, 'de', 'oui_player_vimeo_title', 'admin', 'site', 'Display the video title', '2019-07-25 03:25:36'),
(8335, 'de', 'oui_player_vimeo_byline', 'admin', 'site', 'Display the video author', '2019-07-25 03:25:36'),
(8336, 'de', 'oui_player_vimeo_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:25:36'),
(8337, 'de', 'oui_player_vimeo_portrait', 'admin', 'site', 'Display the author avatar', '2019-07-25 03:25:36'),
(8338, 'en-us', 'oui_player_provider_vimeo', 'admin', 'site', 'Vimeo', '2019-07-25 03:25:36'),
(8339, 'en-us', 'oui_player_vimeo', 'admin', 'site', 'Vimeo player', '2019-07-25 03:25:36'),
(8340, 'en-us', 'oui_player_vimeo_prefs', 'admin', 'site', 'Display the Vimeo player preferences', '2019-07-25 03:25:36'),
(8341, 'en-us', 'oui_player_vimeo_width', 'admin', 'site', 'Player width', '2019-07-25 03:25:36'),
(8342, 'en-us', 'oui_player_vimeo_height', 'admin', 'site', 'Player height', '2019-07-25 03:25:36'),
(8343, 'en-us', 'oui_player_vimeo_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:25:36'),
(8344, 'en-us', 'oui_player_vimeo_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions/ratio', '2019-07-25 03:25:36'),
(8345, 'en-us', 'oui_player_vimeo_api', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:25:36'),
(8346, 'en-us', 'oui_player_vimeo_autopause', 'admin', 'site', 'Automatic pausing when another video is playing', '2019-07-25 03:25:36'),
(8347, 'en-us', 'oui_player_vimeo_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:25:36'),
(8348, 'en-us', 'oui_player_vimeo_badge', 'admin', 'site', 'Display the badge', '2019-07-25 03:25:36'),
(8349, 'en-us', 'oui_player_vimeo_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:25:36'),
(8350, 'en-us', 'oui_player_vimeo_title', 'admin', 'site', 'Display the video title', '2019-07-25 03:25:36'),
(8351, 'en-us', 'oui_player_vimeo_byline', 'admin', 'site', 'Display the video author', '2019-07-25 03:25:36'),
(8352, 'en-us', 'oui_player_vimeo_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:25:36'),
(8353, 'en-us', 'oui_player_vimeo_portrait', 'admin', 'site', 'Display the author avatar', '2019-07-25 03:25:36'),
(8354, 'el', 'oui_player_provider_vimeo', 'admin', 'site', 'Vimeo', '2019-07-25 03:25:36'),
(8355, 'el', 'oui_player_vimeo', 'admin', 'site', 'Vimeo player', '2019-07-25 03:25:36'),
(8356, 'el', 'oui_player_vimeo_prefs', 'admin', 'site', 'Display the Vimeo player preferences', '2019-07-25 03:25:36'),
(8357, 'el', 'oui_player_vimeo_width', 'admin', 'site', 'Player width', '2019-07-25 03:25:36'),
(8358, 'el', 'oui_player_vimeo_height', 'admin', 'site', 'Player height', '2019-07-25 03:25:36'),
(8359, 'el', 'oui_player_vimeo_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:25:36'),
(8360, 'el', 'oui_player_vimeo_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions/ratio', '2019-07-25 03:25:36'),
(8361, 'el', 'oui_player_vimeo_api', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:25:36'),
(8362, 'el', 'oui_player_vimeo_autopause', 'admin', 'site', 'Automatic pausing when another video is playing', '2019-07-25 03:25:36'),
(8363, 'el', 'oui_player_vimeo_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:25:36'),
(8364, 'el', 'oui_player_vimeo_badge', 'admin', 'site', 'Display the badge', '2019-07-25 03:25:36'),
(8365, 'el', 'oui_player_vimeo_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:25:36'),
(8366, 'el', 'oui_player_vimeo_title', 'admin', 'site', 'Display the video title', '2019-07-25 03:25:36'),
(8367, 'el', 'oui_player_vimeo_byline', 'admin', 'site', 'Display the video author', '2019-07-25 03:25:36'),
(8368, 'el', 'oui_player_vimeo_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:25:36'),
(8369, 'el', 'oui_player_vimeo_portrait', 'admin', 'site', 'Display the author avatar', '2019-07-25 03:25:36'),
(8370, 'en', 'oui_player_provider_vimeo', 'admin', 'site', 'Vimeo', '2019-07-25 03:25:36'),
(8371, 'en', 'oui_player_vimeo', 'admin', 'site', 'Vimeo player', '2019-07-25 03:25:36'),
(8372, 'en', 'oui_player_vimeo_prefs', 'admin', 'site', 'Display the Vimeo player preferences', '2019-07-25 03:25:36'),
(8373, 'en', 'oui_player_vimeo_width', 'admin', 'site', 'Player width', '2019-07-25 03:25:36'),
(8374, 'en', 'oui_player_vimeo_height', 'admin', 'site', 'Player height', '2019-07-25 03:25:36'),
(8375, 'en', 'oui_player_vimeo_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:25:36'),
(8376, 'en', 'oui_player_vimeo_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions/ratio', '2019-07-25 03:25:36'),
(8377, 'en', 'oui_player_vimeo_api', 'admin', 'site', 'Enable the player JS API', '2019-07-25 03:25:36'),
(8378, 'en', 'oui_player_vimeo_autopause', 'admin', 'site', 'Automatic pausing when another video is playing', '2019-07-25 03:25:36'),
(8379, 'en', 'oui_player_vimeo_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:25:36'),
(8380, 'en', 'oui_player_vimeo_badge', 'admin', 'site', 'Display the badge', '2019-07-25 03:25:36'),
(8381, 'en', 'oui_player_vimeo_loop', 'admin', 'site', 'Replay the video once finished', '2019-07-25 03:25:36'),
(8382, 'en', 'oui_player_vimeo_title', 'admin', 'site', 'Display the video title', '2019-07-25 03:25:36'),
(8383, 'en', 'oui_player_vimeo_byline', 'admin', 'site', 'Display the video author', '2019-07-25 03:25:36'),
(8384, 'en', 'oui_player_vimeo_color', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:25:36'),
(8385, 'en', 'oui_player_vimeo_portrait', 'admin', 'site', 'Display the author avatar', '2019-07-25 03:25:36'),
(8386, 'de', 'oui_player_provider_mixcloud', 'admin', 'site', 'Mixcloud', '2019-07-25 03:32:52'),
(8387, 'de', 'oui_player_mixcloud', 'admin', 'site', 'Mixcloud player', '2019-07-25 03:32:52'),
(8388, 'de', 'oui_player_mixcloud_prefs', 'admin', 'site', 'Display the Mixcloud player preferences', '2019-07-25 03:32:52'),
(8389, 'de', 'oui_player_mixcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:32:52'),
(8390, 'de', 'oui_player_mixcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:32:52'),
(8391, 'de', 'oui_player_mixcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:32:52'),
(8392, 'de', 'oui_player_mixcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:32:52'),
(8393, 'de', 'oui_player_mixcloud_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:32:52'),
(8394, 'de', 'oui_player_mixcloud_light', 'admin', 'site', 'Enables the light theme', '2019-07-25 03:32:52'),
(8395, 'de', 'oui_player_mixcloud_hide_artwork', 'admin', 'site', 'Hides the artwork', '2019-07-25 03:32:52'),
(8396, 'de', 'oui_player_mixcloud_hide_cover', 'admin', 'site', 'Hides the cover', '2019-07-25 03:32:52'),
(8397, 'de', 'oui_player_mixcloud_mini', 'admin', 'site', 'Uses the mini player config', '2019-07-25 03:32:52'),
(8398, 'en-us', 'oui_player_provider_mixcloud', 'admin', 'site', 'Mixcloud', '2019-07-25 03:32:52'),
(8399, 'en-us', 'oui_player_mixcloud', 'admin', 'site', 'Mixcloud player', '2019-07-25 03:32:52'),
(8400, 'en-us', 'oui_player_mixcloud_prefs', 'admin', 'site', 'Display the Mixcloud player preferences', '2019-07-25 03:32:52'),
(8401, 'en-us', 'oui_player_mixcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:32:52'),
(8402, 'en-us', 'oui_player_mixcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:32:52'),
(8403, 'en-us', 'oui_player_mixcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:32:52'),
(8404, 'en-us', 'oui_player_mixcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:32:52'),
(8405, 'en-us', 'oui_player_mixcloud_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:32:52'),
(8406, 'en-us', 'oui_player_mixcloud_light', 'admin', 'site', 'Enables the light theme', '2019-07-25 03:32:52'),
(8407, 'en-us', 'oui_player_mixcloud_hide_artwork', 'admin', 'site', 'Hides the artwork', '2019-07-25 03:32:52'),
(8408, 'en-us', 'oui_player_mixcloud_hide_cover', 'admin', 'site', 'Hides the cover', '2019-07-25 03:32:52'),
(8409, 'en-us', 'oui_player_mixcloud_mini', 'admin', 'site', 'Uses the mini player config', '2019-07-25 03:32:52'),
(8410, 'el', 'oui_player_provider_mixcloud', 'admin', 'site', 'Mixcloud', '2019-07-25 03:32:52'),
(8411, 'el', 'oui_player_mixcloud', 'admin', 'site', 'Mixcloud player', '2019-07-25 03:32:52'),
(8412, 'el', 'oui_player_mixcloud_prefs', 'admin', 'site', 'Display the Mixcloud player preferences', '2019-07-25 03:32:52'),
(8413, 'el', 'oui_player_mixcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:32:52'),
(8414, 'el', 'oui_player_mixcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:32:52'),
(8415, 'el', 'oui_player_mixcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:32:52'),
(8416, 'el', 'oui_player_mixcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:32:52'),
(8417, 'el', 'oui_player_mixcloud_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:32:52'),
(8418, 'el', 'oui_player_mixcloud_light', 'admin', 'site', 'Enables the light theme', '2019-07-25 03:32:52'),
(8419, 'el', 'oui_player_mixcloud_hide_artwork', 'admin', 'site', 'Hides the artwork', '2019-07-25 03:32:52'),
(8420, 'el', 'oui_player_mixcloud_hide_cover', 'admin', 'site', 'Hides the cover', '2019-07-25 03:32:52'),
(8421, 'el', 'oui_player_mixcloud_mini', 'admin', 'site', 'Uses the mini player config', '2019-07-25 03:32:52'),
(8422, 'en', 'oui_player_provider_mixcloud', 'admin', 'site', 'Mixcloud', '2019-07-25 03:32:52'),
(8423, 'en', 'oui_player_mixcloud', 'admin', 'site', 'Mixcloud player', '2019-07-25 03:32:52'),
(8424, 'en', 'oui_player_mixcloud_prefs', 'admin', 'site', 'Display the Mixcloud player preferences', '2019-07-25 03:32:52'),
(8425, 'en', 'oui_player_mixcloud_width', 'admin', 'site', 'Player width', '2019-07-25 03:32:52'),
(8426, 'en', 'oui_player_mixcloud_height', 'admin', 'site', 'Player height', '2019-07-25 03:32:52'),
(8427, 'en', 'oui_player_mixcloud_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:32:52'),
(8428, 'en', 'oui_player_mixcloud_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:32:52'),
(8429, 'en', 'oui_player_mixcloud_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:32:52'),
(8430, 'en', 'oui_player_mixcloud_light', 'admin', 'site', 'Enables the light theme', '2019-07-25 03:32:52'),
(8431, 'en', 'oui_player_mixcloud_hide_artwork', 'admin', 'site', 'Hides the artwork', '2019-07-25 03:32:52'),
(8432, 'en', 'oui_player_mixcloud_hide_cover', 'admin', 'site', 'Hides the cover', '2019-07-25 03:32:52'),
(8433, 'en', 'oui_player_mixcloud_mini', 'admin', 'site', 'Uses the mini player config', '2019-07-25 03:32:52'),
(8434, 'de', 'oui_player_provider_archive', 'admin', 'site', 'Archive', '2019-07-25 03:28:41'),
(8435, 'de', 'oui_player_archive', 'admin', 'site', 'Archive player', '2019-07-25 03:28:41'),
(8436, 'de', 'oui_player_archive_prefs', 'admin', 'site', 'Display the Archive player preferences', '2019-07-25 03:28:41'),
(8437, 'de', 'oui_player_archive_width', 'admin', 'site', 'Player width', '2019-07-25 03:28:41'),
(8438, 'de', 'oui_player_archive_height', 'admin', 'site', 'Player height', '2019-07-25 03:28:41'),
(8439, 'de', 'oui_player_archive_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:28:41'),
(8440, 'de', 'oui_player_archive_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:28:41'),
(8441, 'de', 'oui_player_archive_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:28:41'),
(8442, 'de', 'oui_player_archive_playlist', 'admin', 'site', 'Display the next titles for a playlist', '2019-07-25 03:28:41'),
(8443, 'de', 'oui_player_archive_poster', 'admin', 'site', 'Image url of a custom starting shot', '2019-07-25 03:28:41'),
(8444, 'en-us', 'oui_player_provider_archive', 'admin', 'site', 'Archive', '2019-07-25 03:28:41'),
(8445, 'en-us', 'oui_player_archive', 'admin', 'site', 'Archive player', '2019-07-25 03:28:41'),
(8446, 'en-us', 'oui_player_archive_prefs', 'admin', 'site', 'Display the Archive player preferences', '2019-07-25 03:28:41'),
(8447, 'en-us', 'oui_player_archive_width', 'admin', 'site', 'Player width', '2019-07-25 03:28:41'),
(8448, 'en-us', 'oui_player_archive_height', 'admin', 'site', 'Player height', '2019-07-25 03:28:41'),
(8449, 'en-us', 'oui_player_archive_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:28:41'),
(8450, 'en-us', 'oui_player_archive_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:28:41'),
(8451, 'en-us', 'oui_player_archive_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:28:41'),
(8452, 'en-us', 'oui_player_archive_playlist', 'admin', 'site', 'Display the next titles for a playlist', '2019-07-25 03:28:41'),
(8453, 'en-us', 'oui_player_archive_poster', 'admin', 'site', 'Image url of a custom starting shot', '2019-07-25 03:28:41'),
(8454, 'el', 'oui_player_provider_archive', 'admin', 'site', 'Archive', '2019-07-25 03:28:41'),
(8455, 'el', 'oui_player_archive', 'admin', 'site', 'Archive player', '2019-07-25 03:28:41'),
(8456, 'el', 'oui_player_archive_prefs', 'admin', 'site', 'Display the Archive player preferences', '2019-07-25 03:28:41'),
(8457, 'el', 'oui_player_archive_width', 'admin', 'site', 'Player width', '2019-07-25 03:28:41'),
(8458, 'el', 'oui_player_archive_height', 'admin', 'site', 'Player height', '2019-07-25 03:28:41'),
(8459, 'el', 'oui_player_archive_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:28:41'),
(8460, 'el', 'oui_player_archive_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:28:41'),
(8461, 'el', 'oui_player_archive_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:28:41'),
(8462, 'el', 'oui_player_archive_playlist', 'admin', 'site', 'Display the next titles for a playlist', '2019-07-25 03:28:41'),
(8463, 'el', 'oui_player_archive_poster', 'admin', 'site', 'Image url of a custom starting shot', '2019-07-25 03:28:41'),
(8464, 'en', 'oui_player_provider_archive', 'admin', 'site', 'Archive', '2019-07-25 03:28:41'),
(8465, 'en', 'oui_player_archive', 'admin', 'site', 'Archive player', '2019-07-25 03:28:41'),
(8466, 'en', 'oui_player_archive_prefs', 'admin', 'site', 'Display the Archive player preferences', '2019-07-25 03:28:41'),
(8467, 'en', 'oui_player_archive_width', 'admin', 'site', 'Player width', '2019-07-25 03:28:41'),
(8468, 'en', 'oui_player_archive_height', 'admin', 'site', 'Player height', '2019-07-25 03:28:41'),
(8469, 'en', 'oui_player_archive_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:28:41'),
(8470, 'en', 'oui_player_archive_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:28:41'),
(8471, 'en', 'oui_player_archive_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:28:41'),
(8472, 'en', 'oui_player_archive_playlist', 'admin', 'site', 'Display the next titles for a playlist', '2019-07-25 03:28:41'),
(8473, 'en', 'oui_player_archive_poster', 'admin', 'site', 'Image url of a custom starting shot', '2019-07-25 03:28:41'),
(8474, 'de', 'oui_player_provider_bandcamp', 'admin', 'site', 'Bandcamp', '2019-07-25 03:30:14'),
(8475, 'de', 'oui_player_bandcamp', 'admin', 'site', 'Bandcamp player', '2019-07-25 03:30:14'),
(8476, 'de', 'oui_player_bandcamp_prefs', 'admin', 'site', 'Display the Bandcamp player preferences', '2019-07-25 03:30:14'),
(8477, 'de', 'oui_player_bandcamp_width', 'admin', 'site', 'Player width', '2019-07-25 03:30:14'),
(8478, 'de', 'oui_player_bandcamp_height', 'admin', 'site', 'Player height', '2019-07-25 03:30:14'),
(8479, 'de', 'oui_player_bandcamp_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:30:14'),
(8480, 'de', 'oui_player_bandcamp_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:30:14'),
(8481, 'de', 'oui_player_bandcamp_artwork', 'admin', 'site', 'Artwork (optional)', '2019-07-25 03:30:14'),
(8482, 'de', 'oui_player_bandcamp_artwork_none', 'admin', 'site', 'None', '2019-07-25 03:30:14'),
(8483, 'de', 'oui_player_bandcamp_artwork_big', 'admin', 'site', 'Big (large player only)', '2019-07-25 03:30:14'),
(8484, 'de', 'oui_player_bandcamp_artwork_small', 'admin', 'site', 'Small (large player only)', '2019-07-25 03:30:14'),
(8485, 'de', 'oui_player_bandcamp_bgcol', 'admin', 'site', 'Background color', '2019-07-25 03:30:14'),
(8486, 'de', 'oui_player_bandcamp_linkcol', 'admin', 'site', 'Links color', '2019-07-25 03:30:14'),
(8487, 'de', 'oui_player_bandcamp_minimal', 'admin', 'site', 'Artwork only', '2019-07-25 03:30:14'),
(8488, 'de', 'oui_player_bandcamp_tracklist', 'admin', 'site', 'Display the tracklist (large player only)', '2019-07-25 03:30:14'),
(8489, 'de', 'oui_player_bandcamp_size', 'admin', 'site', 'Size', '2019-07-25 03:30:14'),
(8490, 'de', 'oui_player_bandcamp_size_large', 'admin', 'site', 'Large', '2019-07-25 03:30:14'),
(8491, 'de', 'oui_player_bandcamp_size_small', 'admin', 'site', 'Small', '2019-07-25 03:30:14'),
(8492, 'en-us', 'oui_player_provider_bandcamp', 'admin', 'site', 'Bandcamp', '2019-07-25 03:30:14'),
(8493, 'en-us', 'oui_player_bandcamp', 'admin', 'site', 'Bandcamp player', '2019-07-25 03:30:14'),
(8494, 'en-us', 'oui_player_bandcamp_prefs', 'admin', 'site', 'Display the Bandcamp player preferences', '2019-07-25 03:30:14'),
(8495, 'en-us', 'oui_player_bandcamp_width', 'admin', 'site', 'Player width', '2019-07-25 03:30:14'),
(8496, 'en-us', 'oui_player_bandcamp_height', 'admin', 'site', 'Player height', '2019-07-25 03:30:14'),
(8497, 'en-us', 'oui_player_bandcamp_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:30:14'),
(8498, 'en-us', 'oui_player_bandcamp_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:30:14'),
(8499, 'en-us', 'oui_player_bandcamp_artwork', 'admin', 'site', 'Artwork (optional)', '2019-07-25 03:30:14'),
(8500, 'en-us', 'oui_player_bandcamp_artwork_none', 'admin', 'site', 'None', '2019-07-25 03:30:14'),
(8501, 'en-us', 'oui_player_bandcamp_artwork_big', 'admin', 'site', 'Big (large player only)', '2019-07-25 03:30:14'),
(8502, 'en-us', 'oui_player_bandcamp_artwork_small', 'admin', 'site', 'Small (large player only)', '2019-07-25 03:30:14'),
(8503, 'en-us', 'oui_player_bandcamp_bgcol', 'admin', 'site', 'Background color', '2019-07-25 03:30:14'),
(8504, 'en-us', 'oui_player_bandcamp_linkcol', 'admin', 'site', 'Links color', '2019-07-25 03:30:14'),
(8505, 'en-us', 'oui_player_bandcamp_minimal', 'admin', 'site', 'Artwork only', '2019-07-25 03:30:14'),
(8506, 'en-us', 'oui_player_bandcamp_tracklist', 'admin', 'site', 'Display the tracklist (large player only)', '2019-07-25 03:30:14'),
(8507, 'en-us', 'oui_player_bandcamp_size', 'admin', 'site', 'Size', '2019-07-25 03:30:14'),
(8508, 'en-us', 'oui_player_bandcamp_size_large', 'admin', 'site', 'Large', '2019-07-25 03:30:14'),
(8509, 'en-us', 'oui_player_bandcamp_size_small', 'admin', 'site', 'Small', '2019-07-25 03:30:14'),
(8510, 'el', 'oui_player_provider_bandcamp', 'admin', 'site', 'Bandcamp', '2019-07-25 03:30:14'),
(8511, 'el', 'oui_player_bandcamp', 'admin', 'site', 'Bandcamp player', '2019-07-25 03:30:14'),
(8512, 'el', 'oui_player_bandcamp_prefs', 'admin', 'site', 'Display the Bandcamp player preferences', '2019-07-25 03:30:14'),
(8513, 'el', 'oui_player_bandcamp_width', 'admin', 'site', 'Player width', '2019-07-25 03:30:14'),
(8514, 'el', 'oui_player_bandcamp_height', 'admin', 'site', 'Player height', '2019-07-25 03:30:14'),
(8515, 'el', 'oui_player_bandcamp_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:30:14'),
(8516, 'el', 'oui_player_bandcamp_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:30:14'),
(8517, 'el', 'oui_player_bandcamp_artwork', 'admin', 'site', 'Artwork (optional)', '2019-07-25 03:30:14'),
(8518, 'el', 'oui_player_bandcamp_artwork_none', 'admin', 'site', 'None', '2019-07-25 03:30:14'),
(8519, 'el', 'oui_player_bandcamp_artwork_big', 'admin', 'site', 'Big (large player only)', '2019-07-25 03:30:14'),
(8520, 'el', 'oui_player_bandcamp_artwork_small', 'admin', 'site', 'Small (large player only)', '2019-07-25 03:30:14'),
(8521, 'el', 'oui_player_bandcamp_bgcol', 'admin', 'site', 'Background color', '2019-07-25 03:30:14'),
(8522, 'el', 'oui_player_bandcamp_linkcol', 'admin', 'site', 'Links color', '2019-07-25 03:30:14'),
(8523, 'el', 'oui_player_bandcamp_minimal', 'admin', 'site', 'Artwork only', '2019-07-25 03:30:14'),
(8524, 'el', 'oui_player_bandcamp_tracklist', 'admin', 'site', 'Display the tracklist (large player only)', '2019-07-25 03:30:14'),
(8525, 'el', 'oui_player_bandcamp_size', 'admin', 'site', 'Size', '2019-07-25 03:30:14'),
(8526, 'el', 'oui_player_bandcamp_size_large', 'admin', 'site', 'Large', '2019-07-25 03:30:14'),
(8527, 'el', 'oui_player_bandcamp_size_small', 'admin', 'site', 'Small', '2019-07-25 03:30:14'),
(8528, 'en', 'oui_player_provider_bandcamp', 'admin', 'site', 'Bandcamp', '2019-07-25 03:30:14'),
(8529, 'en', 'oui_player_bandcamp', 'admin', 'site', 'Bandcamp player', '2019-07-25 03:30:14'),
(8530, 'en', 'oui_player_bandcamp_prefs', 'admin', 'site', 'Display the Bandcamp player preferences', '2019-07-25 03:30:14'),
(8531, 'en', 'oui_player_bandcamp_width', 'admin', 'site', 'Player width', '2019-07-25 03:30:14'),
(8532, 'en', 'oui_player_bandcamp_height', 'admin', 'site', 'Player height', '2019-07-25 03:30:14'),
(8533, 'en', 'oui_player_bandcamp_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:30:14'),
(8534, 'en', 'oui_player_bandcamp_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:30:14'),
(8535, 'en', 'oui_player_bandcamp_artwork', 'admin', 'site', 'Artwork (optional)', '2019-07-25 03:30:14'),
(8536, 'en', 'oui_player_bandcamp_artwork_none', 'admin', 'site', 'None', '2019-07-25 03:30:14'),
(8537, 'en', 'oui_player_bandcamp_artwork_big', 'admin', 'site', 'Big (large player only)', '2019-07-25 03:30:14'),
(8538, 'en', 'oui_player_bandcamp_artwork_small', 'admin', 'site', 'Small (large player only)', '2019-07-25 03:30:14'),
(8539, 'en', 'oui_player_bandcamp_bgcol', 'admin', 'site', 'Background color', '2019-07-25 03:30:14'),
(8540, 'en', 'oui_player_bandcamp_linkcol', 'admin', 'site', 'Links color', '2019-07-25 03:30:14'),
(8541, 'en', 'oui_player_bandcamp_minimal', 'admin', 'site', 'Artwork only', '2019-07-25 03:30:14'),
(8542, 'en', 'oui_player_bandcamp_tracklist', 'admin', 'site', 'Display the tracklist (large player only)', '2019-07-25 03:30:14'),
(8543, 'en', 'oui_player_bandcamp_size', 'admin', 'site', 'Size', '2019-07-25 03:30:14'),
(8544, 'en', 'oui_player_bandcamp_size_large', 'admin', 'site', 'Large', '2019-07-25 03:30:14'),
(8545, 'en', 'oui_player_bandcamp_size_small', 'admin', 'site', 'Small', '2019-07-25 03:30:14'),
(8546, 'de', 'oui_player_provider_myspace', 'admin', 'site', 'Myspace videos', '2019-07-25 03:31:36'),
(8547, 'de', 'oui_player_myspace', 'admin', 'site', 'Myspace player', '2019-07-25 03:31:36'),
(8548, 'de', 'oui_player_myspace_prefs', 'admin', 'site', 'Display the Myspace player preferences', '2019-07-25 03:31:36'),
(8549, 'de', 'oui_player_myspace_width', 'admin', 'site', 'Player width', '2019-07-25 03:31:36'),
(8550, 'de', 'oui_player_myspace_height', 'admin', 'site', 'Player height', '2019-07-25 03:31:36'),
(8551, 'de', 'oui_player_myspace_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:31:36'),
(8552, 'de', 'oui_player_myspace_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:31:36'),
(8553, 'en-us', 'oui_player_provider_myspace', 'admin', 'site', 'Myspace videos', '2019-07-25 03:31:36'),
(8554, 'en-us', 'oui_player_myspace', 'admin', 'site', 'Myspace player', '2019-07-25 03:31:36'),
(8555, 'en-us', 'oui_player_myspace_prefs', 'admin', 'site', 'Display the Myspace player preferences', '2019-07-25 03:31:36'),
(8556, 'en-us', 'oui_player_myspace_width', 'admin', 'site', 'Player width', '2019-07-25 03:31:36'),
(8557, 'en-us', 'oui_player_myspace_height', 'admin', 'site', 'Player height', '2019-07-25 03:31:36'),
(8558, 'en-us', 'oui_player_myspace_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:31:36'),
(8559, 'en-us', 'oui_player_myspace_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:31:36'),
(8560, 'el', 'oui_player_provider_myspace', 'admin', 'site', 'Myspace videos', '2019-07-25 03:31:36'),
(8561, 'el', 'oui_player_myspace', 'admin', 'site', 'Myspace player', '2019-07-25 03:31:36'),
(8562, 'el', 'oui_player_myspace_prefs', 'admin', 'site', 'Display the Myspace player preferences', '2019-07-25 03:31:36'),
(8563, 'el', 'oui_player_myspace_width', 'admin', 'site', 'Player width', '2019-07-25 03:31:36'),
(8564, 'el', 'oui_player_myspace_height', 'admin', 'site', 'Player height', '2019-07-25 03:31:36'),
(8565, 'el', 'oui_player_myspace_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:31:36'),
(8566, 'el', 'oui_player_myspace_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:31:36'),
(8567, 'en', 'oui_player_provider_myspace', 'admin', 'site', 'Myspace videos', '2019-07-25 03:31:36'),
(8568, 'en', 'oui_player_myspace', 'admin', 'site', 'Myspace player', '2019-07-25 03:31:36'),
(8569, 'en', 'oui_player_myspace_prefs', 'admin', 'site', 'Display the Myspace player preferences', '2019-07-25 03:31:36'),
(8570, 'en', 'oui_player_myspace_width', 'admin', 'site', 'Player width', '2019-07-25 03:31:36'),
(8571, 'en', 'oui_player_myspace_height', 'admin', 'site', 'Player height', '2019-07-25 03:31:36'),
(8572, 'en', 'oui_player_myspace_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:31:36'),
(8573, 'en', 'oui_player_myspace_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:31:36'),
(8574, 'de', 'oui_player_provider_viddsee', 'admin', 'site', 'Viddsee', '2019-07-25 03:34:19'),
(8575, 'de', 'oui_player_viddsee', 'admin', 'site', 'Viddsee player', '2019-07-25 03:34:19'),
(8576, 'de', 'oui_player_viddsee_prefs', 'admin', 'site', 'Display the Viddsee player preferences', '2019-07-25 03:34:19'),
(8577, 'de', 'oui_player_viddsee_width', 'admin', 'site', 'Player width', '2019-07-25 03:34:19'),
(8578, 'de', 'oui_player_viddsee_height', 'admin', 'site', 'Player height', '2019-07-25 03:34:19'),
(8579, 'de', 'oui_player_viddsee_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:34:19'),
(8580, 'de', 'oui_player_viddsee_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:34:19'),
(8581, 'en-us', 'oui_player_provider_viddsee', 'admin', 'site', 'Viddsee', '2019-07-25 03:34:19'),
(8582, 'en-us', 'oui_player_viddsee', 'admin', 'site', 'Viddsee player', '2019-07-25 03:34:19'),
(8583, 'en-us', 'oui_player_viddsee_prefs', 'admin', 'site', 'Display the Viddsee player preferences', '2019-07-25 03:34:19'),
(8584, 'en-us', 'oui_player_viddsee_width', 'admin', 'site', 'Player width', '2019-07-25 03:34:19'),
(8585, 'en-us', 'oui_player_viddsee_height', 'admin', 'site', 'Player height', '2019-07-25 03:34:19'),
(8586, 'en-us', 'oui_player_viddsee_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:34:19'),
(8587, 'en-us', 'oui_player_viddsee_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:34:19'),
(8588, 'el', 'oui_player_provider_viddsee', 'admin', 'site', 'Viddsee', '2019-07-25 03:34:19'),
(8589, 'el', 'oui_player_viddsee', 'admin', 'site', 'Viddsee player', '2019-07-25 03:34:19'),
(8590, 'el', 'oui_player_viddsee_prefs', 'admin', 'site', 'Display the Viddsee player preferences', '2019-07-25 03:34:19'),
(8591, 'el', 'oui_player_viddsee_width', 'admin', 'site', 'Player width', '2019-07-25 03:34:19'),
(8592, 'el', 'oui_player_viddsee_height', 'admin', 'site', 'Player height', '2019-07-25 03:34:19'),
(8593, 'el', 'oui_player_viddsee_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:34:19'),
(8594, 'el', 'oui_player_viddsee_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:34:19'),
(8595, 'en', 'oui_player_provider_viddsee', 'admin', 'site', 'Viddsee', '2019-07-25 03:34:19'),
(8596, 'en', 'oui_player_viddsee', 'admin', 'site', 'Viddsee player', '2019-07-25 03:34:19'),
(8597, 'en', 'oui_player_viddsee_prefs', 'admin', 'site', 'Display the Viddsee player preferences', '2019-07-25 03:34:19'),
(8598, 'en', 'oui_player_viddsee_width', 'admin', 'site', 'Player width', '2019-07-25 03:34:19'),
(8599, 'en', 'oui_player_viddsee_height', 'admin', 'site', 'Player height', '2019-07-25 03:34:19'),
(8600, 'en', 'oui_player_viddsee_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:34:19'),
(8601, 'en', 'oui_player_viddsee_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:34:19'),
(8602, 'de', 'oui_player_provider_vine', 'admin', 'site', 'Vine', '2019-07-25 03:36:10'),
(8603, 'de', 'oui_player_vine', 'admin', 'site', 'Vine player', '2019-07-25 03:36:10'),
(8604, 'de', 'oui_player_vine_prefs', 'admin', 'site', 'Display the Vine player preferences', '2019-07-25 03:36:10'),
(8605, 'de', 'oui_player_vine_width', 'admin', 'site', 'Video width', '2019-07-25 03:36:10'),
(8606, 'de', 'oui_player_vine_height', 'admin', 'site', 'Video height', '2019-07-25 03:36:10'),
(8607, 'de', 'oui_player_vine_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:36:10'),
(8608, 'de', 'oui_player_vine_type', 'admin', 'site', 'Display type', '2019-07-25 03:36:10'),
(8609, 'de', 'oui_player_vine_type_simple', 'admin', 'site', 'Simple', '2019-07-25 03:36:10'),
(8610, 'de', 'oui_player_vine_type_postcard', 'admin', 'site', 'Postcard', '2019-07-25 03:36:10'),
(8611, 'de', 'oui_player_vine_audio', 'admin', 'site', 'Enable sound by default', '2019-07-25 03:36:10'),
(8612, 'en-us', 'oui_player_provider_vine', 'admin', 'site', 'Vine', '2019-07-25 03:36:10'),
(8613, 'en-us', 'oui_player_vine', 'admin', 'site', 'Vine player', '2019-07-25 03:36:10'),
(8614, 'en-us', 'oui_player_vine_prefs', 'admin', 'site', 'Display the Vine player preferences', '2019-07-25 03:36:10'),
(8615, 'en-us', 'oui_player_vine_width', 'admin', 'site', 'Video width', '2019-07-25 03:36:10'),
(8616, 'en-us', 'oui_player_vine_height', 'admin', 'site', 'Video height', '2019-07-25 03:36:10'),
(8617, 'en-us', 'oui_player_vine_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:36:10'),
(8618, 'en-us', 'oui_player_vine_type', 'admin', 'site', 'Display type', '2019-07-25 03:36:10'),
(8619, 'en-us', 'oui_player_vine_type_simple', 'admin', 'site', 'Simple', '2019-07-25 03:36:10'),
(8620, 'en-us', 'oui_player_vine_type_postcard', 'admin', 'site', 'Postcard', '2019-07-25 03:36:10'),
(8621, 'en-us', 'oui_player_vine_audio', 'admin', 'site', 'Enable sound by default', '2019-07-25 03:36:10'),
(8622, 'el', 'oui_player_provider_vine', 'admin', 'site', 'Vine', '2019-07-25 03:36:10'),
(8623, 'el', 'oui_player_vine', 'admin', 'site', 'Vine player', '2019-07-25 03:36:10'),
(8624, 'el', 'oui_player_vine_prefs', 'admin', 'site', 'Display the Vine player preferences', '2019-07-25 03:36:10'),
(8625, 'el', 'oui_player_vine_width', 'admin', 'site', 'Video width', '2019-07-25 03:36:10'),
(8626, 'el', 'oui_player_vine_height', 'admin', 'site', 'Video height', '2019-07-25 03:36:10'),
(8627, 'el', 'oui_player_vine_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:36:10'),
(8628, 'el', 'oui_player_vine_type', 'admin', 'site', 'Display type', '2019-07-25 03:36:10'),
(8629, 'el', 'oui_player_vine_type_simple', 'admin', 'site', 'Simple', '2019-07-25 03:36:10'),
(8630, 'el', 'oui_player_vine_type_postcard', 'admin', 'site', 'Postcard', '2019-07-25 03:36:10'),
(8631, 'el', 'oui_player_vine_audio', 'admin', 'site', 'Enable sound by default', '2019-07-25 03:36:10'),
(8632, 'en', 'oui_player_provider_vine', 'admin', 'site', 'Vine', '2019-07-25 03:36:10'),
(8633, 'en', 'oui_player_vine', 'admin', 'site', 'Vine player', '2019-07-25 03:36:10'),
(8634, 'en', 'oui_player_vine_prefs', 'admin', 'site', 'Display the Vine player preferences', '2019-07-25 03:36:10'),
(8635, 'en', 'oui_player_vine_width', 'admin', 'site', 'Video width', '2019-07-25 03:36:10'),
(8636, 'en', 'oui_player_vine_height', 'admin', 'site', 'Video height', '2019-07-25 03:36:10'),
(8637, 'en', 'oui_player_vine_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:36:10'),
(8638, 'en', 'oui_player_vine_type', 'admin', 'site', 'Display type', '2019-07-25 03:36:10'),
(8639, 'en', 'oui_player_vine_type_simple', 'admin', 'site', 'Simple', '2019-07-25 03:36:10'),
(8640, 'en', 'oui_player_vine_type_postcard', 'admin', 'site', 'Postcard', '2019-07-25 03:36:10'),
(8641, 'en', 'oui_player_vine_audio', 'admin', 'site', 'Enable sound by default', '2019-07-25 03:36:10'),
(8642, 'de', 'oui_player_provider_dailymotion', 'admin', 'site', 'Dailymotion', '2019-07-25 03:40:19');
INSERT INTO `txp_lang` (`id`, `lang`, `name`, `event`, `owner`, `data`, `lastmod`) VALUES
(8643, 'de', 'oui_player_dailymotion', 'admin', 'site', 'Dailymotion player', '2019-07-25 03:40:19'),
(8644, 'de', 'oui_player_dailymotion_prefs', 'admin', 'site', 'Display the Dailymotion player preferences', '2019-07-25 03:40:19'),
(8645, 'de', 'oui_player_dailymotion_api', 'admin', 'site', 'Player JS API', '2019-07-25 03:40:19'),
(8646, 'de', 'oui_player_dailymotion_api_false', 'admin', 'site', 'Disable', '2019-07-25 03:40:19'),
(8647, 'de', 'oui_player_dailymotion_api_postmessage', 'admin', 'site', 'postMessage', '2019-07-25 03:40:19'),
(8648, 'de', 'oui_player_dailymotion_api_location', 'admin', 'site', 'location (native mobile apps)', '2019-07-25 03:40:19'),
(8649, 'de', 'oui_player_dailymotion_api_1', 'admin', 'site', 'Enable', '2019-07-25 03:40:19'),
(8650, 'de', 'oui_player_dailymotion_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:40:19'),
(8651, 'de', 'oui_player_dailymotion_controls', 'admin', 'site', 'Control bar display', '2019-07-25 03:40:19'),
(8652, 'de', 'oui_player_dailymotion_endscreen-enable', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:40:19'),
(8653, 'de', 'oui_player_dailymotion_mute', 'admin', 'site', 'Disable sound by default', '2019-07-25 03:40:19'),
(8654, 'de', 'oui_player_dailymotion_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:40:19'),
(8655, 'de', 'oui_player_dailymotion_quality', 'admin', 'site', 'Video quality', '2019-07-25 03:40:19'),
(8656, 'de', 'oui_player_dailymotion_quality_auto', 'admin', 'site', 'Auto', '2019-07-25 03:40:19'),
(8657, 'de', 'oui_player_dailymotion_quality_240', 'admin', 'site', '240p', '2019-07-25 03:40:19'),
(8658, 'de', 'oui_player_dailymotion_quality_380', 'admin', 'site', '380p', '2019-07-25 03:40:19'),
(8659, 'de', 'oui_player_dailymotion_quality_480', 'admin', 'site', '480p', '2019-07-25 03:40:19'),
(8660, 'de', 'oui_player_dailymotion_quality_720', 'admin', 'site', '720p', '2019-07-25 03:40:19'),
(8661, 'de', 'oui_player_dailymotion_quality_1080', 'admin', 'site', '1080p', '2019-07-25 03:40:19'),
(8662, 'de', 'oui_player_dailymotion_quality_1440', 'admin', 'site', '1440p', '2019-07-25 03:40:19'),
(8663, 'de', 'oui_player_dailymotion_quality_2160', 'admin', 'site', '2160p', '2019-07-25 03:40:19'),
(8664, 'de', 'oui_player_dailymotion_sharing-enable', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:40:19'),
(8665, 'de', 'oui_player_dailymotion_start', 'admin', 'site', 'Commencer la lecture à la seconde:', '2019-07-25 03:40:19'),
(8666, 'de', 'oui_player_dailymotion_subtitles-default', 'admin', 'site', 'Subtitles language by default', '2019-07-25 03:40:19'),
(8667, 'de', 'oui_player_dailymotion_syndication', 'admin', 'site', 'Syndication key for the player', '2019-07-25 03:40:19'),
(8668, 'de', 'oui_player_dailymotion_ui-highlight', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:40:19'),
(8669, 'de', 'oui_player_dailymotion_ui-logo', 'admin', 'site', 'Display the Dailymotion logo', '2019-07-25 03:40:19'),
(8670, 'de', 'oui_player_dailymotion_ui-start-screen-info', 'admin', 'site', 'Display video informations', '2019-07-25 03:40:19'),
(8671, 'de', 'oui_player_dailymotion_ui-theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:40:19'),
(8672, 'de', 'oui_player_dailymotion_ui-theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:40:19'),
(8673, 'de', 'oui_player_dailymotion_ui-theme_light', 'admin', 'site', 'Light', '2019-07-25 03:40:19'),
(8674, 'de', 'oui_player_dailymotion_width', 'admin', 'site', 'Player width', '2019-07-25 03:40:19'),
(8675, 'de', 'oui_player_dailymotion_height', 'admin', 'site', 'Player height', '2019-07-25 03:40:19'),
(8676, 'de', 'oui_player_dailymotion_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:40:19'),
(8677, 'de', 'oui_player_dailymotion_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:40:19'),
(8678, 'en-us', 'oui_player_provider_dailymotion', 'admin', 'site', 'Dailymotion', '2019-07-25 03:40:19'),
(8679, 'en-us', 'oui_player_dailymotion', 'admin', 'site', 'Dailymotion player', '2019-07-25 03:40:19'),
(8680, 'en-us', 'oui_player_dailymotion_prefs', 'admin', 'site', 'Display the Dailymotion player preferences', '2019-07-25 03:40:19'),
(8681, 'en-us', 'oui_player_dailymotion_api', 'admin', 'site', 'Player JS API', '2019-07-25 03:40:19'),
(8682, 'en-us', 'oui_player_dailymotion_api_false', 'admin', 'site', 'Disable', '2019-07-25 03:40:19'),
(8683, 'en-us', 'oui_player_dailymotion_api_postmessage', 'admin', 'site', 'postMessage', '2019-07-25 03:40:19'),
(8684, 'en-us', 'oui_player_dailymotion_api_location', 'admin', 'site', 'location (native mobile apps)', '2019-07-25 03:40:19'),
(8685, 'en-us', 'oui_player_dailymotion_api_1', 'admin', 'site', 'Enable', '2019-07-25 03:40:19'),
(8686, 'en-us', 'oui_player_dailymotion_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:40:19'),
(8687, 'en-us', 'oui_player_dailymotion_controls', 'admin', 'site', 'Control bar display', '2019-07-25 03:40:19'),
(8688, 'en-us', 'oui_player_dailymotion_endscreen-enable', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:40:19'),
(8689, 'en-us', 'oui_player_dailymotion_mute', 'admin', 'site', 'Disable sound by default', '2019-07-25 03:40:19'),
(8690, 'en-us', 'oui_player_dailymotion_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:40:19'),
(8691, 'en-us', 'oui_player_dailymotion_quality', 'admin', 'site', 'Video quality', '2019-07-25 03:40:19'),
(8692, 'en-us', 'oui_player_dailymotion_quality_auto', 'admin', 'site', 'Auto', '2019-07-25 03:40:19'),
(8693, 'en-us', 'oui_player_dailymotion_quality_240', 'admin', 'site', '240p', '2019-07-25 03:40:19'),
(8694, 'en-us', 'oui_player_dailymotion_quality_380', 'admin', 'site', '380p', '2019-07-25 03:40:19'),
(8695, 'en-us', 'oui_player_dailymotion_quality_480', 'admin', 'site', '480p', '2019-07-25 03:40:19'),
(8696, 'en-us', 'oui_player_dailymotion_quality_720', 'admin', 'site', '720p', '2019-07-25 03:40:19'),
(8697, 'en-us', 'oui_player_dailymotion_quality_1080', 'admin', 'site', '1080p', '2019-07-25 03:40:19'),
(8698, 'en-us', 'oui_player_dailymotion_quality_1440', 'admin', 'site', '1440p', '2019-07-25 03:40:19'),
(8699, 'en-us', 'oui_player_dailymotion_quality_2160', 'admin', 'site', '2160p', '2019-07-25 03:40:19'),
(8700, 'en-us', 'oui_player_dailymotion_sharing-enable', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:40:19'),
(8701, 'en-us', 'oui_player_dailymotion_start', 'admin', 'site', 'Commencer la lecture à la seconde:', '2019-07-25 03:40:19'),
(8702, 'en-us', 'oui_player_dailymotion_subtitles-default', 'admin', 'site', 'Subtitles language by default', '2019-07-25 03:40:19'),
(8703, 'en-us', 'oui_player_dailymotion_syndication', 'admin', 'site', 'Syndication key for the player', '2019-07-25 03:40:19'),
(8704, 'en-us', 'oui_player_dailymotion_ui-highlight', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:40:19'),
(8705, 'en-us', 'oui_player_dailymotion_ui-logo', 'admin', 'site', 'Display the Dailymotion logo', '2019-07-25 03:40:19'),
(8706, 'en-us', 'oui_player_dailymotion_ui-start-screen-info', 'admin', 'site', 'Display video informations', '2019-07-25 03:40:19'),
(8707, 'en-us', 'oui_player_dailymotion_ui-theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:40:19'),
(8708, 'en-us', 'oui_player_dailymotion_ui-theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:40:19'),
(8709, 'en-us', 'oui_player_dailymotion_ui-theme_light', 'admin', 'site', 'Light', '2019-07-25 03:40:19'),
(8710, 'en-us', 'oui_player_dailymotion_width', 'admin', 'site', 'Player width', '2019-07-25 03:40:19'),
(8711, 'en-us', 'oui_player_dailymotion_height', 'admin', 'site', 'Player height', '2019-07-25 03:40:19'),
(8712, 'en-us', 'oui_player_dailymotion_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:40:19'),
(8713, 'en-us', 'oui_player_dailymotion_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:40:19'),
(8714, 'el', 'oui_player_provider_dailymotion', 'admin', 'site', 'Dailymotion', '2019-07-25 03:40:19'),
(8715, 'el', 'oui_player_dailymotion', 'admin', 'site', 'Dailymotion player', '2019-07-25 03:40:19'),
(8716, 'el', 'oui_player_dailymotion_prefs', 'admin', 'site', 'Display the Dailymotion player preferences', '2019-07-25 03:40:19'),
(8717, 'el', 'oui_player_dailymotion_api', 'admin', 'site', 'Player JS API', '2019-07-25 03:40:19'),
(8718, 'el', 'oui_player_dailymotion_api_false', 'admin', 'site', 'Disable', '2019-07-25 03:40:19'),
(8719, 'el', 'oui_player_dailymotion_api_postmessage', 'admin', 'site', 'postMessage', '2019-07-25 03:40:19'),
(8720, 'el', 'oui_player_dailymotion_api_location', 'admin', 'site', 'location (native mobile apps)', '2019-07-25 03:40:19'),
(8721, 'el', 'oui_player_dailymotion_api_1', 'admin', 'site', 'Enable', '2019-07-25 03:40:19'),
(8722, 'el', 'oui_player_dailymotion_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:40:19'),
(8723, 'el', 'oui_player_dailymotion_controls', 'admin', 'site', 'Control bar display', '2019-07-25 03:40:19'),
(8724, 'el', 'oui_player_dailymotion_endscreen-enable', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:40:19'),
(8725, 'el', 'oui_player_dailymotion_mute', 'admin', 'site', 'Disable sound by default', '2019-07-25 03:40:19'),
(8726, 'el', 'oui_player_dailymotion_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:40:19'),
(8727, 'el', 'oui_player_dailymotion_quality', 'admin', 'site', 'Video quality', '2019-07-25 03:40:19'),
(8728, 'el', 'oui_player_dailymotion_quality_auto', 'admin', 'site', 'Auto', '2019-07-25 03:40:19'),
(8729, 'el', 'oui_player_dailymotion_quality_240', 'admin', 'site', '240p', '2019-07-25 03:40:19'),
(8730, 'el', 'oui_player_dailymotion_quality_380', 'admin', 'site', '380p', '2019-07-25 03:40:19'),
(8731, 'el', 'oui_player_dailymotion_quality_480', 'admin', 'site', '480p', '2019-07-25 03:40:19'),
(8732, 'el', 'oui_player_dailymotion_quality_720', 'admin', 'site', '720p', '2019-07-25 03:40:19'),
(8733, 'el', 'oui_player_dailymotion_quality_1080', 'admin', 'site', '1080p', '2019-07-25 03:40:19'),
(8734, 'el', 'oui_player_dailymotion_quality_1440', 'admin', 'site', '1440p', '2019-07-25 03:40:19'),
(8735, 'el', 'oui_player_dailymotion_quality_2160', 'admin', 'site', '2160p', '2019-07-25 03:40:19'),
(8736, 'el', 'oui_player_dailymotion_sharing-enable', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:40:19'),
(8737, 'el', 'oui_player_dailymotion_start', 'admin', 'site', 'Commencer la lecture à la seconde:', '2019-07-25 03:40:19'),
(8738, 'el', 'oui_player_dailymotion_subtitles-default', 'admin', 'site', 'Subtitles language by default', '2019-07-25 03:40:19'),
(8739, 'el', 'oui_player_dailymotion_syndication', 'admin', 'site', 'Syndication key for the player', '2019-07-25 03:40:19'),
(8740, 'el', 'oui_player_dailymotion_ui-highlight', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:40:19'),
(8741, 'el', 'oui_player_dailymotion_ui-logo', 'admin', 'site', 'Display the Dailymotion logo', '2019-07-25 03:40:19'),
(8742, 'el', 'oui_player_dailymotion_ui-start-screen-info', 'admin', 'site', 'Display video informations', '2019-07-25 03:40:19'),
(8743, 'el', 'oui_player_dailymotion_ui-theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:40:19'),
(8744, 'el', 'oui_player_dailymotion_ui-theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:40:19'),
(8745, 'el', 'oui_player_dailymotion_ui-theme_light', 'admin', 'site', 'Light', '2019-07-25 03:40:19'),
(8746, 'el', 'oui_player_dailymotion_width', 'admin', 'site', 'Player width', '2019-07-25 03:40:19'),
(8747, 'el', 'oui_player_dailymotion_height', 'admin', 'site', 'Player height', '2019-07-25 03:40:19'),
(8748, 'el', 'oui_player_dailymotion_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:40:19'),
(8749, 'el', 'oui_player_dailymotion_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:40:19'),
(8750, 'en', 'oui_player_provider_dailymotion', 'admin', 'site', 'Dailymotion', '2019-07-25 03:40:19'),
(8751, 'en', 'oui_player_dailymotion', 'admin', 'site', 'Dailymotion player', '2019-07-25 03:40:19'),
(8752, 'en', 'oui_player_dailymotion_prefs', 'admin', 'site', 'Display the Dailymotion player preferences', '2019-07-25 03:40:19'),
(8753, 'en', 'oui_player_dailymotion_api', 'admin', 'site', 'Player JS API', '2019-07-25 03:40:19'),
(8754, 'en', 'oui_player_dailymotion_api_false', 'admin', 'site', 'Disable', '2019-07-25 03:40:19'),
(8755, 'en', 'oui_player_dailymotion_api_postmessage', 'admin', 'site', 'postMessage', '2019-07-25 03:40:19'),
(8756, 'en', 'oui_player_dailymotion_api_location', 'admin', 'site', 'location (native mobile apps)', '2019-07-25 03:40:19'),
(8757, 'en', 'oui_player_dailymotion_api_1', 'admin', 'site', 'Enable', '2019-07-25 03:40:19'),
(8758, 'en', 'oui_player_dailymotion_autoplay', 'admin', 'site', 'Automatic playing', '2019-07-25 03:40:19'),
(8759, 'en', 'oui_player_dailymotion_controls', 'admin', 'site', 'Control bar display', '2019-07-25 03:40:19'),
(8760, 'en', 'oui_player_dailymotion_endscreen-enable', 'admin', 'site', 'Display related videos at the end', '2019-07-25 03:40:19'),
(8761, 'en', 'oui_player_dailymotion_mute', 'admin', 'site', 'Disable sound by default', '2019-07-25 03:40:19'),
(8762, 'en', 'oui_player_dailymotion_origin', 'admin', 'site', 'Origin domain URL', '2019-07-25 03:40:19'),
(8763, 'en', 'oui_player_dailymotion_quality', 'admin', 'site', 'Video quality', '2019-07-25 03:40:19'),
(8764, 'en', 'oui_player_dailymotion_quality_auto', 'admin', 'site', 'Auto', '2019-07-25 03:40:19'),
(8765, 'en', 'oui_player_dailymotion_quality_240', 'admin', 'site', '240p', '2019-07-25 03:40:19'),
(8766, 'en', 'oui_player_dailymotion_quality_380', 'admin', 'site', '380p', '2019-07-25 03:40:19'),
(8767, 'en', 'oui_player_dailymotion_quality_480', 'admin', 'site', '480p', '2019-07-25 03:40:19'),
(8768, 'en', 'oui_player_dailymotion_quality_720', 'admin', 'site', '720p', '2019-07-25 03:40:19'),
(8769, 'en', 'oui_player_dailymotion_quality_1080', 'admin', 'site', '1080p', '2019-07-25 03:40:19'),
(8770, 'en', 'oui_player_dailymotion_quality_1440', 'admin', 'site', '1440p', '2019-07-25 03:40:19'),
(8771, 'en', 'oui_player_dailymotion_quality_2160', 'admin', 'site', '2160p', '2019-07-25 03:40:19'),
(8772, 'en', 'oui_player_dailymotion_sharing-enable', 'admin', 'site', 'Enable sharing features', '2019-07-25 03:40:19'),
(8773, 'en', 'oui_player_dailymotion_start', 'admin', 'site', 'Commencer la lecture à la seconde:', '2019-07-25 03:40:19'),
(8774, 'en', 'oui_player_dailymotion_subtitles-default', 'admin', 'site', 'Subtitles language by default', '2019-07-25 03:40:19'),
(8775, 'en', 'oui_player_dailymotion_syndication', 'admin', 'site', 'Syndication key for the player', '2019-07-25 03:40:19'),
(8776, 'en', 'oui_player_dailymotion_ui-highlight', 'admin', 'site', 'Preferred colour for UI player elements', '2019-07-25 03:40:19'),
(8777, 'en', 'oui_player_dailymotion_ui-logo', 'admin', 'site', 'Display the Dailymotion logo', '2019-07-25 03:40:19'),
(8778, 'en', 'oui_player_dailymotion_ui-start-screen-info', 'admin', 'site', 'Display video informations', '2019-07-25 03:40:19'),
(8779, 'en', 'oui_player_dailymotion_ui-theme', 'admin', 'site', 'Preferred theme for the player', '2019-07-25 03:40:19'),
(8780, 'en', 'oui_player_dailymotion_ui-theme_dark', 'admin', 'site', 'Dark', '2019-07-25 03:40:19'),
(8781, 'en', 'oui_player_dailymotion_ui-theme_light', 'admin', 'site', 'Light', '2019-07-25 03:40:19'),
(8782, 'en', 'oui_player_dailymotion_width', 'admin', 'site', 'Player width', '2019-07-25 03:40:19'),
(8783, 'en', 'oui_player_dailymotion_height', 'admin', 'site', 'Player height', '2019-07-25 03:40:19'),
(8784, 'en', 'oui_player_dailymotion_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:40:19'),
(8785, 'en', 'oui_player_dailymotion_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:40:19'),
(8786, 'de', 'oui_player_provider_ted', 'admin', 'site', 'Ted', '2019-07-25 03:42:30'),
(8787, 'de', 'oui_player_ted', 'admin', 'site', 'Ted player', '2019-07-25 03:42:30'),
(8788, 'de', 'oui_player_ted_prefs', 'admin', 'site', 'Display the Ted player preferences', '2019-07-25 03:42:30'),
(8789, 'de', 'oui_player_ted_width', 'admin', 'site', 'Player width', '2019-07-25 03:42:30'),
(8790, 'de', 'oui_player_ted_height', 'admin', 'site', 'Player height', '2019-07-25 03:42:30'),
(8791, 'de', 'oui_player_ted_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:42:30'),
(8792, 'de', 'oui_player_ted_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:42:30'),
(8793, 'en-us', 'oui_player_provider_ted', 'admin', 'site', 'Ted', '2019-07-25 03:42:30'),
(8794, 'en-us', 'oui_player_ted', 'admin', 'site', 'Ted player', '2019-07-25 03:42:30'),
(8795, 'en-us', 'oui_player_ted_prefs', 'admin', 'site', 'Display the Ted player preferences', '2019-07-25 03:42:30'),
(8796, 'en-us', 'oui_player_ted_width', 'admin', 'site', 'Player width', '2019-07-25 03:42:30'),
(8797, 'en-us', 'oui_player_ted_height', 'admin', 'site', 'Player height', '2019-07-25 03:42:30'),
(8798, 'en-us', 'oui_player_ted_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:42:30'),
(8799, 'en-us', 'oui_player_ted_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:42:30'),
(8800, 'el', 'oui_player_provider_ted', 'admin', 'site', 'Ted', '2019-07-25 03:42:30'),
(8801, 'el', 'oui_player_ted', 'admin', 'site', 'Ted player', '2019-07-25 03:42:30'),
(8802, 'el', 'oui_player_ted_prefs', 'admin', 'site', 'Display the Ted player preferences', '2019-07-25 03:42:30'),
(8803, 'el', 'oui_player_ted_width', 'admin', 'site', 'Player width', '2019-07-25 03:42:30'),
(8804, 'el', 'oui_player_ted_height', 'admin', 'site', 'Player height', '2019-07-25 03:42:30'),
(8805, 'el', 'oui_player_ted_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:42:30'),
(8806, 'el', 'oui_player_ted_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:42:30'),
(8807, 'en', 'oui_player_provider_ted', 'admin', 'site', 'Ted', '2019-07-25 03:42:30'),
(8808, 'en', 'oui_player_ted', 'admin', 'site', 'Ted player', '2019-07-25 03:42:30'),
(8809, 'en', 'oui_player_ted_prefs', 'admin', 'site', 'Display the Ted player preferences', '2019-07-25 03:42:30'),
(8810, 'en', 'oui_player_ted_width', 'admin', 'site', 'Player width', '2019-07-25 03:42:30'),
(8811, 'en', 'oui_player_ted_height', 'admin', 'site', 'Player height', '2019-07-25 03:42:30'),
(8812, 'en', 'oui_player_ted_ratio', 'admin', 'site', 'Player ratio by default', '2019-07-25 03:42:30'),
(8813, 'en', 'oui_player_ted_responsive', 'admin', 'site', 'Render responsive players from the provided dimensions', '2019-07-25 03:42:30'),
(8814, 'de', 'oui_flat', 'admin', 'site', 'Flat template files', '2019-07-25 03:49:17'),
(8815, 'de', 'oui_flat_key', 'admin', 'site', 'Security key for the public callback', '2019-07-25 03:49:17'),
(8816, 'de', 'oui_flat_path', 'admin', 'site', 'Path to the templates directory', '2019-07-25 03:49:17'),
(8817, 'de', 'oui_flat_status', 'admin', 'site', 'Production status for the systematic upload', '2019-07-25 03:49:17'),
(8818, 'de', 'oui_flat_variables', 'admin', 'site', 'Template variables', '2019-07-25 03:49:17'),
(8819, 'en-us', 'oui_flat', 'admin', 'site', 'Flat template files', '2019-07-25 03:49:17'),
(8820, 'en-us', 'oui_flat_key', 'admin', 'site', 'Security key for the public callback', '2019-07-25 03:49:17'),
(8821, 'en-us', 'oui_flat_path', 'admin', 'site', 'Path to the templates directory', '2019-07-25 03:49:17'),
(8822, 'en-us', 'oui_flat_status', 'admin', 'site', 'Production status for the systematic upload', '2019-07-25 03:49:17'),
(8823, 'en-us', 'oui_flat_variables', 'admin', 'site', 'Template variables', '2019-07-25 03:49:17'),
(8824, 'el', 'oui_flat', 'admin', 'site', 'Flat template files', '2019-07-25 03:49:17'),
(8825, 'el', 'oui_flat_key', 'admin', 'site', 'Security key for the public callback', '2019-07-25 03:49:17'),
(8826, 'el', 'oui_flat_path', 'admin', 'site', 'Path to the templates directory', '2019-07-25 03:49:17'),
(8827, 'el', 'oui_flat_status', 'admin', 'site', 'Production status for the systematic upload', '2019-07-25 03:49:17'),
(8828, 'el', 'oui_flat_variables', 'admin', 'site', 'Template variables', '2019-07-25 03:49:17'),
(8829, 'en', 'oui_flat', 'admin', 'site', 'Flat template files', '2019-07-25 03:49:17'),
(8830, 'en', 'oui_flat_key', 'admin', 'site', 'Security key for the public callback', '2019-07-25 03:49:17'),
(8831, 'en', 'oui_flat_path', 'admin', 'site', 'Path to the templates directory', '2019-07-25 03:49:17'),
(8832, 'en', 'oui_flat_status', 'admin', 'site', 'Production status for the systematic upload', '2019-07-25 03:49:17'),
(8833, 'en', 'oui_flat_variables', 'admin', 'site', 'Template variables', '2019-07-25 03:49:17'),
(8834, 'de', 'wet_quicklink_insert_link', 'admin', 'site', 'Link einfügen', '2019-07-25 04:44:56'),
(8835, 'de', 'wet_quicklink_loading', 'admin', 'site', 'Laden…', '2019-07-25 04:44:56'),
(8836, 'en-us', 'wet_quicklink_insert_link', 'admin', 'site', 'Insert Link', '2019-07-25 04:44:56'),
(8837, 'en-us', 'wet_quicklink_loading', 'admin', 'site', 'Loading…', '2019-07-25 04:44:56'),
(8838, 'el', 'wet_quicklink_insert_link', 'admin', 'site', 'Insert Link', '2019-07-25 04:44:56'),
(8839, 'el', 'wet_quicklink_loading', 'admin', 'site', 'Loading…', '2019-07-25 04:44:56'),
(8840, 'en', 'wet_quicklink_insert_link', 'admin', 'site', 'Insert Link', '2019-07-25 04:44:56'),
(8841, 'en', 'wet_quicklink_loading', 'admin', 'site', 'Loading…', '2019-07-25 04:44:56'),
(8842, 'en', 'cf_custom1', 'glz_cf', 'glz_cf_labels', 'kind', '2019-08-12 22:42:32'),
(8843, 'en', 'instructions_custom_1', 'glz_cf', 'glz_cf_labels', 'choose1', '2019-08-12 22:42:32');

-- --------------------------------------------------------

--
-- Table structure for table `txp_link`
--

CREATE TABLE `txp_link` (
  `id` int(11) NOT NULL,
  `date` datetime NOT NULL,
  `category` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `url` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `linkname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `linksort` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `description` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `author` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `jcr_link_custom` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_link`
--

INSERT INTO `txp_link` (`id`, `date`, `category`, `url`, `linkname`, `linksort`, `description`, `author`, `jcr_link_custom`) VALUES
(1, '2019-07-23 21:58:53', 'textpattern', 'https://textpattern.com/', 'Textpattern website', '10', '', '', NULL),
(2, '2019-07-23 21:58:53', 'textpattern', 'https://docs.textpattern.com/', 'Textpattern user documentation', '20', '', '', NULL),
(3, '2019-07-23 21:58:53', 'textpattern', 'https://textpattern.com/github', 'Textpattern on GitHub', '30', '', '', NULL),
(4, '2019-07-23 21:58:53', 'textpattern', 'https://textpattern.com/@textpattern', 'Textpattern on Twitter', '40', '', '', NULL),
(5, '2019-07-24 17:08:28', 'textpattern', 'http://txpthemes.com', 'Opensource Html Templates for Themes', 'Opensource Html Templates for Themes', 'Opensource Html Templates for Themes', 'marios', NULL),
(6, '2019-07-24 17:13:21', 'textpattern', 'http://welovetxp.com', 'Portfolio', 'Portfolio', ' Design gallery showcasing TXP powered sites', 'marios', NULL),
(7, '2019-07-24 17:15:48', '', 'http://txptag.org', 'Textpattern template tags', 'Textpattern template tags', 'Textpattern template tags, the content components, the conditionals.', 'marios', NULL),
(8, '2019-07-24 17:24:34', 'artifcial-intelligence', 'http://txplanet.net', ' News of the flexible, elegant and easy-to-use CMS', ' News of the flexible, elegant and easy-to-use CMS', ' News of the flexible, elegant and easy-to-use CMS', 'marios', NULL),
(9, '2019-07-24 17:27:05', 'textpattern', 'https://textpattern.org/notices/985/adopt-orphans', 'Adopt orphaned plugins', 'Adopt orphaned plugins', 'lease help us keep Textpattern Resources current and as useful as possible by repairing links or helping to adopt orphaned plugins.', 'marios', NULL),
(10, '2019-07-24 18:01:50', 'textpattern', 'http://www.forkosh.com/mimetex.html', 'This page discusses mimeTeX', 'This page discusses mimeTeX', 'This page discusses mimeTeX, a program that displays math on the web.\r\nSee Writing Math on the Web for a more general discussion.', 'marios', NULL),
(11, '2019-07-24 18:54:02', 'textpattern', 'http://makss.uaho.net/howto/', 'Textpattern Tips and Tricks', 'Textpattern Tips and Tricks', 'Textpattern Tips and Tricks\r\nPlugin Author Support active threads:', 'marios', NULL),
(12, '2019-07-24 19:48:41', 'textpattern', 'https://thelocalyarn.com', 'THE LOCAL YARN', 'THE LOCAL YARN', 'THE LOCAL YARN', 'marios', NULL),
(13, '2019-07-24 20:08:51', 'textpattern', 'http://www.dev.michaelgravel.com/text/podcasting-with-textpattern-a-tutorial', 'Podcasting With Textpattern - A Tutorial', 'Podcasting With Textpattern - A Tutorial', 'Podcasting With Textpattern - A Tutorial\r\n\r\nThis tutorial is aimed at those who wish to publish an iTunes-friendly audio podcast with Textpattern', 'marios', NULL),
(14, '2019-07-24 20:23:05', 'textpattern', 'https://rahforum.biz/blog/textpattern-has-apache-dependencies', 'aking some cool plugins for Textpattern CMS since `07', 'aking some cool plugins for Textpattern CMS since `07', 'aking some cool plugins for Textpattern CMS since `07', 'marios', NULL),
(15, '2019-07-24 22:21:48', 'textpattern', 'https://textpattern.com/weblog/403/textpattern-cms-gains-themes-support', 'Textpattern CMS gains Themes support', 'Textpattern CMS gains Themes support', 'Textpattern CMS gains Themes support', 'marios', NULL),
(16, '2019-07-24 23:52:22', '', 'https://www.accusoft.com/blog/artificial-intelligence-whats-new-next-notable/', 'Artificial Intelligence: What’s New, Next, & Notable', 'Artificial Intelligence: What’s New, Next, & Notable', 'Artificial Intelligence: What’s New, Next, & Notable', 'marios', NULL),
(17, '2019-07-25 00:32:13', 'textpattern', 'https://alistapart.com/blog/topic/code/', 'Responsible JavaScript: Part II', 'Responsible JavaScript: Part II', 'Responsible JavaScript: Part II', 'marios', NULL),
(18, '2019-07-25 00:57:48', 'textpattern', 'https://tommyschmucker.de', 'Tommy Schmucker', 'Tommy Schmucker', 'Tommy Schmucker ist freier Webentwickler aus Köln und seit mehreren Jahren spezialisiert auf Frontend-Entwicklung.\r\nAußerdem ist er Open-Source-Enthusiast.', 'marios', NULL),
(19, '2019-07-25 01:46:55', 'textpattern', 'https://openweathermap.org/api', 'Weather API', 'Weather API', 'Please sign up and use our fast and easy-to-work weather APIs for free. Look at our monthly subscriptions for more options than Free account can provide you. Read How to start first and enjoy using our powerful weather APIs.', 'marios', NULL),
(20, '2019-07-25 01:59:57', 'textpattern', 'https://github.com/drmonkeyninja/awesome-textpattern', 'A curated list of amazingly awesome Textpattern CMS plugins', 'A curated list of amazingly awesome Textpattern CMS plugins', 'A curated list of amazingly awesome Textpattern CMS plugins, resources and shiny things.\r\n\r\nThis list is for the latest version of Textpattern (v4.7.x). For Textpattern v4.6.x see the txp-4.6 branch.', 'marios', NULL),
(21, '2019-07-25 02:06:22', 'textpattern', 'https://github.com/ziadoz/awesome-php', 'Awesome PHP', 'Awesome PHP', 'Awesome PHP\r\n\r\nA curated list of amazingly awesome PHP libraries, resources and shiny things.', 'marios', NULL),
(22, '2019-07-25 02:09:39', 'textpattern', 'http://www.textpattern.tips', 'TEXTPATTERN TIPS', 'TEXTPATTERN TIPS', 'Tips, tutorials and code examples to help build better Textpattern sites.\r\n', 'marios', NULL),
(23, '2019-07-25 02:45:24', 'textpattern', 'https://bitbucket.org/%7B950f30ff-465a-4cc7-8801-6175bc6405ad%7D/', 'Syntax highlighting with Python', 'Syntax highlighting with Python', 'Syntax highlighting with Python', 'marios', NULL),
(24, '2019-07-25 04:20:57', 'textpattern', 'https://docs.textpattern.com', 'Textpattern user documentation', 'Textpattern user documentation', 'Textpattern user documentation\r\n\r\nWelcome to the new user documentation, which replaces the old wiki at textpattern.net. This is a construction zone for a while, but any viewable page is ready for use, albeit subject to further editing. Occasional progress updates and feedback requests will be made in the sites forum.', 'marios', NULL),
(25, '2019-07-25 04:27:47', 'textpattern', 'https://docs.textpattern.com/tags/', 'TXP Tagreference', 'TXP Tagreference', 'TXP Tagreference', 'marios', NULL),
(26, '2019-07-25 05:51:25', 'textpattern', 'https://github.com/NicolasGraph/Expand-Textpattern', 'Expand Textpattern', 'Expand Textpattern', 'Expand Textpattern\r\n\r\nTextpattern tips and plugins for your needs.', 'marios', NULL),
(27, '2019-07-25 05:53:10', 'textpattern', 'http://txplanet.net', 'Textpattern CMS News of the flexible, elegant and easy-to-use CMS', 'Textpattern CMS News of the flexible, elegant and easy-to-use CMS', 'Textpattern CMS News of the flexible, elegant and easy-to-use CMS', 'marios', NULL),
(28, '2019-07-25 05:55:33', 'textpattern', 'http://txptag.com', 'TXP Make PHP in templates is so last decade', 'TXP Make PHP in templates is so last decade', 'TXP Make PHP in templates is so last decade', 'marios', NULL),
(29, '2019-07-25 05:56:33', '', 'http://welovetxp.com', ' Design gallery showcasing TXP powered sites', ' Design gallery showcasing TXP powered sites', ' Design gallery showcasing TXP powered sites', 'marios', NULL),
(30, '2019-07-25 05:58:32', 'textpattern', 'http://txpthemes.com', 'TXP Themes', 'TXP Themes', 'TXP Themes', 'marios', NULL),
(31, '2019-07-25 05:58:47', 'textpattern', 'http://txpthemes.com', 'TXP Themes', 'TXP Themes', 'TXP Themes', 'marios', NULL),
(32, '2019-07-25 06:00:29', 'textpattern', 'http://txptag.com/blog/dean-allen-rip', ' Dean Allen, R.I.P.', ' Dean Allen, R.I.P.', 'Dean Allen, R.I.P.\r\n\r\nDean Allen, the father of Textpattern, has passed away.\r\n\r\nRaise a glass\r\nPersonally, I never met Dean nor had any interaction with him via email or on the forums. When I came upon Textpattern in 2005, Dean was exiting stage left.\r\n\r\nThis weekend, I bought a bottle of Jameson and polished it off in Dean’s name.', 'marios', NULL),
(33, '2019-07-25 06:19:22', 'textpattern', 'https://github.com/c2mrdale/textpattern-documentation/blob/master/index.textile', 'Index  Installation', 'Index  Installation', 'Textpattern documentation', 'marios', NULL),
(34, '2019-07-25 06:24:28', '', 'https://transmissionbt.com/download/', 'Transmission', 'Transmission', 'Transmission is a cross-platform BitTorrent client that is:\r\n\r\nOpen Source.', 'marios', NULL),
(35, '2019-07-25 06:26:17', 'mac-os', 'https://github.com/starckio/My-macOS', 'APPS for Mac OS', 'APPS for Mac OS', 'APPS for Mac OS', 'marios', NULL),
(36, '2019-07-25 06:30:45', 'textpattern', 'https://forum.textpattern.com/viewtopic.php?id=33182', 'Textpacks: I18N facility for plugins and other third party software', 'Textpacks: I18N facility for plugins and other third party software', 'Textpacks: I18N facility for plugins and other third party software', 'marios', NULL),
(37, '2019-07-25 07:06:24', 'textpattern', 'https://hicksdesign.co.uk/search/?q=textpattern', 'Hicks Design', 'Hicks Design', 'Search Results for \'textpattern\'', 'marios', NULL),
(38, '2019-07-25 07:08:50', 'design', 'http://iconhandbook.co.uk', 'The Icon Design Handbook', 'The Icon Design Handbook', 'The Icon Handbook is the only resource that guides you through the complete process of designing icons - from deciding on the correct metaphor, drawing clear & understandable symbols and implementing in your websites and apps. The ebook is available at Gumroad.', 'marios', NULL),
(39, '2019-07-25 07:12:20', 'design', 'https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes/', 'iOS Design Themes', 'iOS Design Themes', 'iOS Design Themes\r\n\r\nAs an app designer, you have the opportunity to deliver an extraordinary product that rises to the top of the App Store charts. To do so, you\'ll need to meet high expectations for quality and functionality.', 'marios', NULL),
(40, '2019-07-25 07:16:44', 'textpattern', 'https://www.amazon.de/Textpattern-Solutions-PHP-Based-Content-Management/dp/1590598326?SubscriptionId=AKIAILSHYYTFIVPWUY6Q&tag=duckduckgo-osx-de-21&linkCode=xm2&camp=2025&creative=165953&creativeASIN=1590598326', 'Textpattern Solutions Textpattern Book', 'Textpattern Solutions Textpattern Book', 'In modern web development, there is a big focus on using frameworks and other reusable code to build up web sites, rather than writing everything from scratch. ', 'marios', NULL),
(41, '2019-07-25 09:10:13', 'design', 'https://www.free-css.com', 'Free CSS', 'Free CSS', 'Free CSS\r\n\r\nFree CSS has 2900 free website templates coded using HTML & CSS in its gallery. The HTML website templates that are showcased on Free CSS.com are the best that can be found in and around the net.\r\n\r\nWe would personally like to thank all of the website template designers and developers for all of their hard work in creating these free website templates. Without them Free CSS would probably not exist.\r\n\r\nFree CSS Admin', 'marios', NULL),
(42, '2019-07-25 09:14:12', 'design', 'https://startbootstrap.com/templates/', 'Bootstrap Templates', 'Bootstrap Templates', 'Bootstrap Templates', 'marios', NULL),
(43, '2019-07-25 09:16:02', 'design', 'https://startbootstrap.com/guides/how-to-create-a-website-with-github-pages/', 'Bootstrap Tutorial', 'Bootstrap Tutorial', 'How to Create and Launch a Website from Start to Finish', 'marios', NULL),
(44, '2019-07-25 09:23:25', 'design', 'https://developer.qlik.com/playground?utm_medium=direct_buy&utm_source=carbon&utm_campaign=justclicklogo', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE\r\n\r\nSee what\'s possible with these Playground creations from our Qlik Branch squad.', 'marios', NULL),
(45, '2019-07-25 13:06:43', 'programming', 'https://www.youtube.com/watch?v=HXNhEYqFo0o', 'Fortran A Loop within a Loop', 'Fortran A Loop within a Loop', 'Fortran A Loop within a Loop', 'marios', NULL),
(46, '2019-07-25 13:32:59', 'artifcial-intelligence', 'https://blog.singularitynet.io/@bengoertzel', 'Ben Geortel The Granddaddy of AI', 'Ben Geortel The Granddaddy of AI', 'Ben Geortel The Granddaddy of AI', 'marios', NULL),
(47, '2019-07-25 14:05:25', 'design', 'http://txpcms.com/about/', 'TXP abd Bootstrap', 'TXP abd Bootstrap', 'Top and Bootstrap', 'marios', NULL),
(48, '2019-07-25 14:08:07', 'textpattern', 'https://twitter.com/textism/', 'Dean Allen Facebook', 'Dean Allen Facebook', 'Dean Allen Facebook', 'marios', NULL),
(49, '2019-07-30 10:01:42', 'design', 'https://developer.qlik.com/playground?utm_medium=direct_buy&utm_source=carbon&utm_campaign=justclicklogo', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE\r\n\r\nSee what\'s possible with these Playground creations from our Qlik Branch squad.', 'marios', NULL),
(50, '2019-08-01 02:30:08', 'design', 'https://developer.qlik.com/playground?utm_medium=direct_buy&utm_source=carbon&utm_campaign=justclicklogo', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE  See what\'s possible with these Playground creations from our Qlik Branch squad.', 'SQUAD SHOWCASE\r\n\r\nSee what\'s possible with these Playground creations from our Qlik Branch squad.', 'marios', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_log`
--

CREATE TABLE `txp_log` (
  `id` int(11) NOT NULL,
  `time` datetime NOT NULL,
  `page` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `refer` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `status` int(11) NOT NULL DEFAULT '200',
  `method` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'GET'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Table structure for table `txp_page`
--

CREATE TABLE `txp_page` (
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `user_html` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `skin` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'default',
  `lastmod` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_page`
--

INSERT INTO `txp_page` (`name`, `user_html`, `skin`, `lastmod`) VALUES
('default', '<!DOCTYPE html>\n<html lang=\"<txp:lang />\" dir=\"<txp:text item=\"lang_dir\" />\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <title><txp:page_title /></title>\n\n    <!-- Google font API -->\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i&amp;subset=latin-ext\">\n\n    <!-- CSS -->\n    <txp:css format=\"link\" media=\"\" />\n    <!-- ...or you can use (faster) external CSS files e.g. <link rel=\"stylesheet\" href=\"<txp:page_url type=\"theme_path\" />/styles/default.css\"> -->\n\n    <meta name=\"generator\" content=\"Textpattern CMS\">\n\n    <txp:if_search>\n        <meta name=\"robots\" content=\"none\">\n    <txp:else />\n        <txp:if_author>\n            <meta name=\"robots\" content=\"noindex, follow\">\n        <txp:else />\n            <txp:meta_description />\n            <txp:if_category>\n                <meta name=\"robots\" content=\"noindex, follow\">\n            <txp:else />\n                <txp:variable name=\"page\" value=\'<txp:page_url type=\"pg\" />\' />\n                <txp:if_variable name=\"page\" value=\"1\">\n                    <meta name=\"robots\" content=\"index, follow\">\n                    <link rel=\"canonical\" href=\"<txp:site_url />\">\n                    <script type=\"application/ld+json\">\n                        {\n                            \"@context\": \"https://schema.org\",\n                            \"@type\": \"WebSite\",\n                            \"headline\": \"<txp:site_name escape=\"json\" />\",\n                            <txp:if_description>\"description\": \"<txp:meta_description format=\"\" escape=\"json\" />\",</txp:if_description>\n                            \"url\": \"<txp:site_url escape=\"json\" />\"\n                        }\n                    </script>\n                <txp:else />\n                    <meta name=\"robots\" content=\"noindex, follow\">\n                </txp:if_variable>\n                <txp:variable name=\"front-page\" value=\"1\" />\n            </txp:if_category>\n        </txp:if_author>\n    </txp:if_search>\n\n    <!-- content feeds -->\n    <txp:feed_link flavor=\"atom\" format=\"link\" label=\"Atom\" />\n    <txp:feed_link flavor=\"rss\" format=\"link\" label=\"RSS\" />\n</head>\n\n<txp:if_variable name=\"front-page\">\n    <body class=\"front-page\" itemscope itemtype=\"https://schema.org/WebPage\">\n<txp:else />\n    <txp:if_search>\n        <body class=\"search-page\" itemscope itemtype=\"https://schema.org/SearchResultsPage\">\n    <txp:else />\n        <body class=\"listing-page\" itemscope itemtype=\"https://schema.org/CollectionPage\">\n    </txp:if_search>\n</txp:if_variable>\n\n    <txp:output_form form=\"body_header\" />\n\n    <div class=\"wrapper\">\n        <div class=\"container\">\n\n            <!-- Main content -->\n            <main aria-label=\"<txp:text item=\"main_content\" />\" itemscope itemtype=\"https://schema.org/Blog\">\n\n                <!-- is this the search result page? also omits the pagination links below (uses pagination format within search_display.txp instead) -->\n                <txp:if_search>\n                    <txp:output_form form=\"search_display\" />\n\n                <txp:else />\n                    <!-- else is this the front page? -->\n                    <txp:if_variable name=\"front-page\">\n                        <!-- links by default to form: \'default.txp\' unless you specify a different form -->\n                        <txp:article limit=\"5\" />\n\n                    <txp:else />\n                        <!-- else is this an article author list? -->\n                        <txp:if_author>\n                            <h1 itemprop=\"name\"><txp:text item=\"author\" /> <txp:author /></h1>\n\n                        <txp:else />\n                            <!-- else is this an article category list? -->\n                            <txp:if_category>\n                                <h1 itemprop=\"name\"><txp:text item=\"category\" /> <txp:category title /></h1>\n                            </txp:if_category>\n                        </txp:if_author>\n                        <txp:article form=\"article_listing\" limit=\"10\" wraptag=\"ul\" class=\"article-list\" />\n                    </txp:if_variable>\n\n                    <!-- add pagination links to foot of front page/author listings/category listings if there are more articles available -->\n                    <txp:evaluate test=\"newer,older\">\n                        <nav class=\"paginator\" aria-label=\"<txp:text item=\"page_nav\" />\">\n                            <txp:newer rel=\"prev\">\n                                <txp:text item=\"newer\" />\n                            </txp:newer>\n                            <txp:older rel=\"next\">\n                                <txp:text item=\"older\" />\n                            </txp:older>\n                        </nav>\n                    </txp:evaluate>\n                </txp:if_search>\n\n            </main>\n\n            <txp:output_form form=\"body_aside\" />\n\n        </div> <!-- /.container -->\n    </div> <!-- /.wrapper -->\n\n    <txp:output_form form=\"body_footer\" />\n\n</body>\n</html>', 'four-point-seven', NULL),
('error_default', '<!DOCTYPE html>\n<html lang=\"<txp:lang />\" dir=\"<txp:text item=\"lang_dir\" />\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <title><txp:error_status /></title>\n\n    <!-- Google font API -->\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i&amp;subset=latin-ext\">\n\n    <!-- CSS -->\n    <txp:css format=\"link\" media=\"\" />\n    <!-- ...or you can use (faster) external CSS files e.g. <link rel=\"stylesheet\" href=\"<txp:page_url type=\"theme_path\" />/styles/default.css\"> -->\n\n    <meta name=\"generator\" content=\"Textpattern CMS\">\n    <meta name=\"robots\" content=\"none\">\n\n    <!-- content feeds -->\n    <txp:feed_link flavor=\"atom\" format=\"link\" label=\"Atom\" />\n    <txp:feed_link flavor=\"rss\" format=\"link\" label=\"RSS\" />\n</head>\n\n<body class=\"error-page\" itemscope itemtype=\"https://schema.org/WebPage\">\n\n    <txp:output_form form=\"body_header\" />\n\n    <div class=\"wrapper\">\n        <div class=\"container\">\n\n            <!-- Main content -->\n            <main aria-label=\"<txp:text item=\"main_content\" />\">\n                <h1 class=\"error-status\"><txp:error_status /></h1>\n                <p class=\"error-msg\"><txp:error_message /></p>\n            </main>\n\n            <txp:output_form form=\"body_aside\" />\n\n        </div> <!-- /.container -->\n    </div> <!-- /.wrapper -->\n\n    <txp:output_form form=\"body_footer\" />\n\n</body>\n</html>', 'four-point-seven', NULL),
('archive', '<!DOCTYPE html>\n<html lang=\"<txp:lang />\" dir=\"<txp:text item=\"lang_dir\" />\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <title><txp:page_title /></title>\n\n    <!-- Google font API -->\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=PT+Serif:400,400i,700,700i&amp;subset=latin-ext\">\n\n    <!-- CSS -->\n    <txp:css format=\"link\" media=\"\" />\n    <!-- ...or you can use (faster) external CSS files e.g. <link rel=\"stylesheet\" href=\"<txp:page_url type=\"theme_path\" />/styles/default.css\"> -->\n\n    <meta name=\"generator\" content=\"Textpattern CMS\">\n\n    <txp:if_search>\n        <meta name=\"robots\" content=\"none\">\n    <txp:else />\n        <txp:if_author>\n            <meta name=\"robots\" content=\"noindex, follow\">\n        <txp:else />\n            <txp:meta_description />\n            <txp:if_article_list>\n                <txp:variable name=\"page\" value=\'<txp:page_url type=\"pg\" />\' />\n                <txp:if_variable name=\"page\" value=\"1\">\n                    <meta name=\"robots\" content=\"index, follow\">\n                    <link rel=\"canonical\" href=\"<txp:section url />\">\n                    <script type=\"application/ld+json\">\n                        {\n                            \"@context\": \"https://schema.org\",\n                            \"@type\": \"Blog\",\n                            \"headline\": \"<txp:section title escape=\"json\" />\",\n                            <txp:if_description>\"description\": \"<txp:meta_description format=\"\" escape=\"json\" />\",</txp:if_description>\n                            \"url\": \"<txp:section url escape=\"json\" />\"\n                        }\n                    </script>\n                <txp:else />\n                    <meta name=\"robots\" content=\"noindex, follow\">\n                </txp:if_variable>\n            <txp:else />\n                <txp:meta_keywords separator=\",\" />\n                <txp:meta_author title />\n                <meta name=\"robots\" content=\"<txp:if_expires>unavailable_after: <txp:expires gmt format=\"%d-%b-%y %T\" /> GMT<txp:else />index, follow</txp:if_expires>\">\n                <link rel=\"canonical\" href=\"<txp:permlink />\">\n                <meta property=\"og:site_name\" content=\"<txp:site_name />\">\n                <meta property=\"og:type\" content=\"article\">\n                <meta property=\"og:title\" content=\"<txp:title />\">\n                <txp:if_description><meta property=\"og:description\" content=\"<txp:meta_description format=\"\" />\"></txp:if_description>\n                <meta property=\"og:url\" content=\"<txp:permlink />\">\n                <txp:if_article_image>\n                    <txp:images limit=\"1\">\n                        <meta property=\"og:image\" content=\"<txp:image_url />\">\n                        <meta property=\"og:image:width\" content=\"<txp:image_info type=\"w\" />\">\n                        <meta property=\"og:image:height\" content=\"<txp:image_info type=\"h\" />\">\n                        <meta property=\"og:image:alt\" content=\"<txp:image_info type=\"alt\" />\">\n                    </txp:images>\n                </txp:if_article_image>\n                <script type=\"application/ld+json\">\n                    {\n                        \"@context\": \"https://schema.org\",\n                        \"@type\": \"Blog\",\n                        \"blogPost\": [{\n                            \"@type\": \"BlogPosting\",\n                            \"headline\": \"<txp:title escape=\"json\" />\",\n                            <txp:if_description>\"description\": \"<txp:meta_description format=\"\" escape=\"json\" />\",</txp:if_description>\n                            \"url\": \"<txp:permlink escape=\"json\" />\"\n                        }]\n                    }\n                </script>\n            </txp:if_article_list>\n        </txp:if_author>\n    </txp:if_search>\n\n    <!-- content feeds -->\n    <txp:feed_link flavor=\"atom\" format=\"link\" label=\"Atom\" />\n    <txp:feed_link flavor=\"rss\" format=\"link\" label=\"RSS\" />\n</head>\n\n<txp:if_search>\n    <body class=\"search-page\" itemscope itemtype=\"https://schema.org/SearchResultsPage\">\n<txp:else />\n    <txp:if_article_list>\n        <body class=\"<txp:section />-page\" itemscope itemtype=\"https://schema.org/CollectionPage\">\n    <txp:else />\n        <body class=\"<txp:section />-page\" itemscope itemtype=\"https://schema.org/WebPage\">\n    </txp:if_article_list>\n</txp:if_search>\n\n    <txp:output_form form=\"body_header\" />\n\n    <div class=\"wrapper\">\n        <div class=\"container\">\n\n            <!-- Main content -->\n            <main aria-label=\"<txp:text item=\"main_content\" />\" itemscope itemtype=\"https://schema.org/Blog\">\n\n                <!-- is this the search result page? also omits the pagination links below (uses pagination format within search_display.txp instead) -->\n                <txp:if_search>\n                    <txp:output_form form=\"search_display\" />\n\n                <txp:else />\n                    <txp:if_article_list>\n\n                        <h1 itemprop=\"name\"><txp:section title /></h1>\n                        <txp:article class=\"article-list\" form=\"article_listing\" limit=\"10\" wraptag=\"ul\" />\n                        <!-- ...or if you want to list all articles from all sections instead, then replace txp:article with txp:article_custom -->\n\n                        <!-- add pagination links to foot of article listings if there are more articles available -->\n                        <txp:evaluate test=\"newer,older\">\n                            <nav class=\"paginator\" aria-label=\"<txp:text item=\"page_nav\" />\">\n                                <txp:newer rel=\"prev\">\n                                    <txp:text item=\"newer\" />\n                                </txp:newer>\n                                <txp:older rel=\"next\">\n                                    <txp:text item=\"older\" />\n                                </txp:older>\n                            </nav>\n                        </txp:evaluate>\n\n                    <txp:else />\n                        <txp:article status />\n\n                        <!-- add pagination links to foot of article if there are more articles available -->\n                        <txp:evaluate test=\"link_to_prev,link_to_next\">\n                            <nav class=\"paginator\" aria-label=\"<txp:text item=\"page_nav\" />\">\n                                <txp:link_to_prev>\n                                    <txp:text item=\"prev\" />\n                                </txp:link_to_prev>\n                                <txp:link_to_next>\n                                    <txp:text item=\"next\" />\n                                </txp:link_to_next>\n                            </nav>\n                        </txp:evaluate>\n                    </txp:if_article_list>\n                </txp:if_search>\n\n            </main>\n\n            <txp:output_form form=\"body_aside\" />\n\n        </div> <!-- /.container -->\n    </div> <!-- /.wrapper -->\n\n    <txp:output_form form=\"body_footer\" />\n\n</body>\n</html>', 'four-point-seven', NULL),
('test_layout', '<!DOCTYPE html>\r\n<html lang=\"en\">\r\n\r\n<head>\r\n\r\n<meta charset=\"utf-8\">\r\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n\r\n<title>TXP Themes - 4 Col Portfolio</title>\r\n<meta name=\"description\" content=\"TXP Themes - 4 Col Portfolio\" />\r\n<meta name=\"author\" content=\"TXP Themes\" />\r\n<meta name=\"generator\" content=\"Textpattern CMS\">\r\n\r\n<!-- Bootstrap Core CSS -->\r\n<link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" rel=\"stylesheet\">\r\n\r\n<!-- Custom CSS -->\r\n<link rel=\"stylesheet\" href=\"http://txpthemes.com/css.php?n=4-col-portfolio\" />\r\n\r\n<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->\r\n<!-- WARNING: Respond.js doesn\'t work if you view the page via file:// -->\r\n<!--[if lt IE 9]>\r\n<script src=\"https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js\"></script>\r\n<script src=\"https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js\"></script>\r\n<![endif]-->\r\n\r\n</head>\r\n\r\n<body>\r\n\r\n<!-- Navigation -->\r\n<nav class=\"navbar navbar-inverse navbar-fixed-top\" role=\"navigation\">\r\n<div class=\"container\">\r\n<!-- Brand and toggle get grouped for better mobile display -->\r\n<div class=\"navbar-header\">\r\n<button type=\"button\" class=\"navbar-toggle\" data-toggle=\"collapse\" data-target=\"#bs-example-navbar-collapse-1\">\r\n<span class=\"sr-only\">Toggle navigation</span>\r\n<span class=\"icon-bar\"></span>\r\n<span class=\"icon-bar\"></span>\r\n<span class=\"icon-bar\"></span>\r\n</button>\r\n<a class=\"navbar-brand\" href=\"http://txpthemes.com/\">TXP Themes</a>\r\n</div>\r\n<!-- Collect the nav links, forms, and other content for toggling -->\r\n<div class=\"collapse navbar-collapse\" id=\"bs-example-navbar-collapse-1\">\r\n<ul class=\"nav navbar-nav\">\r\n\r\n<li>\r\n<a href=\"http://txpthemes.com/carousel/\">Carousel</a>\r\n</li>\r\n\r\n<li class=\"active\">\r\n<a href=\"http://txpthemes.com/4-col-portfolio/\">4 Col Portfolio</a>\r\n</li>\r\n\r\n</ul>\r\n</div>\r\n<!-- /.navbar-collapse -->\r\n</div>\r\n<!-- /.container -->\r\n</nav>\r\n\r\n<!-- Page Content -->\r\n<div class=\"container\">\r\n\r\n<!-- Page Heading -->\r\n<div class=\"row\">\r\n<div class=\"col-lg-12\">\r\n<h1 class=\"page-header\">\r\n<a href=\"http://textpattern.com/\"><img src=\"http://txpthemes.com/themes/txpthemes/img/txp/TXP-logo.png\" alt=\"Textpattern CMS\" title=\"Textpattern CMS\" width=\"143\" height=\"56\" /></a>\r\n<small>Built upon the mobile-friendly Bootstrap framework</small>\r\n</h1>\r\n</div>\r\n</div>\r\n<!-- /.row -->\r\n\r\n<!-- Projects Row -->\r\n<div class=\"row\">\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/01.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/02.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/03.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/04.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n</div>\r\n<!-- /.row -->\r\n\r\n<!-- Projects Row -->\r\n<div class=\"row\">\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/05.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/06.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/07.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/08.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n</div>\r\n<!-- /.row -->\r\n\r\n<!-- Projects Row -->\r\n<div class=\"row\">\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/09.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/10.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/11.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n\r\n<div class=\"col-md-3 portfolio-item\">\r\n<a href=\"#\">\r\n<img class=\"img-responsive img-thumbnail\" src=\"http://txpthemes.com/themes/txpthemes/img/4col/12.jpg\" alt=\"\">\r\n</a>\r\n</div>\r\n</div> \r\n<!-- /.row -->\r\n\r\n<hr>\r\n\r\n<!-- Pagination -->\r\n<div class=\"row text-center\">\r\n<div class=\"col-lg-12\">\r\n<ul class=\"pagination\">\r\n<li>\r\n<a href=\"#\">&laquo;</a>\r\n</li>\r\n<li class=\"active\">\r\n<a href=\"#\">1</a>\r\n</li>\r\n<li>\r\n<a href=\"#\">2</a>\r\n</li>\r\n<li>\r\n<a href=\"#\">3</a>\r\n</li>\r\n<li>\r\n<a href=\"#\">4</a>\r\n</li>\r\n<li>\r\n<a href=\"#\">5</a>\r\n</li>\r\n<li>\r\n<a href=\"#\">&raquo;</a>\r\n</li>\r\n</ul>\r\n</div>\r\n</div>\r\n<!-- /.row -->\r\n\r\n<hr>\r\n\r\n<!-- Footer -->\r\n<footer>\r\n<div class=\"container\">\r\n<div class=\"row\">\r\n\r\n<div class=\"col-md-4 text-center\">\r\n<p>\r\nTheme by <a href=\"http://txpthemes.com/\">TXP Themes</a>\r\n</p>\r\n</div>\r\n\r\n<div class=\"col-md-4 text-center\">\r\n<p>\r\n<span class=\"glyphicon glyphicon-download-alt\"></span>  \r\n<a href=\"http://txpthemes.com/themes/txpthemes/pages/4-col-portfolio.txp\">4-col-portfolio.txp</a> - \r\n<a href=\"http://txpthemes.com/themes/txpthemes/styles/4-col-portfolio.css\">4-col-portfolio.css</a>\r\n</p>\r\n</div>\r\n\r\n<div class=\"col-md-4 text-center\">\r\n<p>\r\nTemplate by <a href=\"http://startbootstrap.com/template-overviews/4-col-portfolio/\">Start Bootstrap</a>\r\n<br />\r\n<small>Licensed under <a href=\"https://opensource.org/licenses/MIT\">MIT License</a></small>\r\n</p>\r\n</div>\r\n\r\n</div>\r\n</div>\r\n</footer>\r\n\r\n</div>\r\n<!-- /.container -->\r\n\r\n<!-- jQuery -->\r\n<script src=\"https://code.jquery.com/jquery-1.12.4.min.js\"></script>\r\n\r\n<!-- Bootstrap Core JavaScript -->\r\n<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script>\r\n\r\n</body>\r\n\r\n</html>\r\n', 'four-point-seven', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_plugin`
--

CREATE TABLE `txp_plugin` (
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `status` int(11) NOT NULL DEFAULT '1',
  `author` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `author_uri` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `version` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1.0',
  `description` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `help` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `code` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `code_restore` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `code_md5` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `textpack` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `data` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `type` int(11) NOT NULL DEFAULT '0',
  `load_order` tinyint(3) UNSIGNED NOT NULL DEFAULT '5',
  `flags` smallint(5) UNSIGNED NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_plugin`
--

INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('zem_contact_reborn', 0, 'TXP Community', 'http://forum.textpattern.com/viewtopic.php?id=23728', '4.0.3.21', 'Form mailer for Textpattern', ' <style>\n   li code {font-weight: bold;}\n   pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\n   h1, h2, h3, h3 code {font-family: sans-serif; font-weight: bold;}\n   h1, h2, h3 {margin-left: -1em;}\n   h2, h3 {margin-top: 2em;}\n   h1 {font-size: 3em;}\n   h2 {font-size: 2em;}\n   h3 {font-size: 1.5em;}\n   li a code {font-weight: normal;}\n   .required, .warning {color:red;}\n </style>\n\n<h1 id=\"top\">Zem Contact Reborn</h1>\n\n<p>Please reports bugs and problems with this plugin in <a href=\"http://forum.textpattern.com/viewtopic.php?id=23728\">this forum thread</a>.</p>\n\n<p class=\"required\">This plugin requires a separate language plugin called <code>zem_contact_lang</code> to be installed and activated to work properly.</p>\n\n\n<h2 id=\"contents\">Contents</h2>\n\n<ul>\n	<li><a href=\"#features\">Features</a></li>\n	<li><a href=\"#start\">Getting started</a>\n	<ul>\n		<li><a href=\"#contactform\">Contact form</a></li>\n		<li><a href=\"#sendarticle\">Send article</a></li>\n	</ul></li>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#zc\"> <code>&lt;txp:zem_contact /&gt;</code> </a></li>\n		<li><a href=\"#zc_text\"> <code>&lt;txp:zem_contact_text /&gt;</code> </a></li>\n		<li><a href=\"#zc_email\"> <code>&lt;txp:zem_contact_email /&gt;</code> </a></li>\n		<li><a href=\"#zc_textarea\"> <code>&lt;txp:zem_contact_textarea /&gt;</code> </a></li>\n		<li><a href=\"#zc_submit\"> <code>&lt;txp:zem_contact_submit /&gt;</code> </a></li>\n		<li><a href=\"#zc_select\"> <code>&lt;txp:zem_contact_select /&gt;</code> </a></li>\n		<li><a href=\"#zc_checkbox\"> <code>&lt;txp:zem_contact_checkbox /&gt;</code> </a></li>\n		<li><a href=\"#zc_radio\"> <code>&lt;txp:zem_contact_radio /&gt;</code> </a></li>\n		<li><a href=\"#zc_secret\"> <code>&lt;txp:zem_contact_secret /&gt;</code> </a></li>\n		<li><a href=\"#zc_server_info\"> <code>&lt;txp:zem_contact_serverinfo /&gt;</code> </a></li>\n		<li><a href=\"#zc_send_article\"> <code>&lt;txp:zem_contact_send_article /&gt;</code> </a></li>\n	</ul></li>\n	<li><a href=\"#advanced\">Advanced examples</a>\n	<ul>\n		<li><a href=\"#show_error\">Separate input and error forms</a></li>\n		<li><a href=\"#subject_form\">User selectable subject field</a></li>\n		<li><a href=\"#to_form\">User selectable recipient, without showing email addresses</a></li>\n	</ul></li>\n	<li><a href=\"#styling\">Styling</a></li>\n	<li><a href=\"#history\">History</a></li>\n	<li><a href=\"#credits\">Credits</a></li>\n	<li><a href=\"#api\">Plugin <span class=\"caps\">API</span> and callback events</a></li>\n</ul>\n\n\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n	<li>Arbitrary text fields can be specified, with min/max/required settings for validation.</li>\n	<li>Email address validation, including a check for a valid MX record (Unix only).</li>\n	<li>Safe escaping of input data.</li>\n	<li><span class=\"caps\">UTF</span>-8 safe.</li>\n	<li>Accessible form layout, including <code>&lt;label&gt;</code>, <code>&lt;legend&gt;</code> and <code>&lt;fieldset&gt;</code> tags.</li>\n	<li>Various classes and ids to allow easy styling of all parts of the form.</li>\n	<li>A separate language plug-in to enable easy localisation.</li>\n	<li>Spam prevention <span class=\"caps\">API</span> (used by Tranquillo&#8217;s <code>pap_contact_cleaner</code> plugin).</li>\n</ul>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"start\">Getting started</h2>\n\n\n<h3 id=\"contactform\">Contact form</h3>\n\n<p>The simplest form is shown below, which produces a default form with Name, Email and Message fields. Email will be delivered to recipient@example.com, with the user&#8217;s supplied email as the &#8220;From:&#8221; address.</p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;recipient@example.com&quot; /&gt;</code></pre>\n\n<p>To specify fields explicitly, use something like this:</p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;recipient@example.com&quot;&gt;\n  &lt;txp:zem_contact_email /&gt;\n  &lt;txp:zem_contact_text label=&quot;Phone&quot; min=7 max=15/&gt;\n  &lt;txp:zem_contact_textarea label=&quot;Your question&quot; /&gt;\n  &lt;txp:zem_contact_submit label=&quot;Send&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>Alternatively, place the field specifications in a Textpattern form, and call it like this:</p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;recipient@example.com&quot; form=&quot;mycontactform&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"sendarticle\">Send article</h3>\n\n<p>Within the context of an individual article, this plugin can be used to send the article (or excerpt, if it exists) to an email address specified by the visitor. This requires at least two tags:<br />\n<ul>\n	<li><code>zem_contact</code>, to create form that is initially hidden by setting the <code>send_article</code> attribute.</li>\n	<li><code>zem_contact_send_article</code>, to create a &#8216;send article&#8217; link which reveals the aforementioned form when clicked.</li><br />\n</ul></p>\n\n<pre><code>&lt;txp:zem_contact send_article=&quot;1&quot; /&gt;\n&lt;txp:zem_contact_send_article /&gt;</code></pre>\n\n<p>By default the form contains fields for your name and email address, the recipient&#8217;s email address and a personal message, but similar to contact forms you can create your own form layout. Some things you need to know:<br />\n<ul>\n	<li>Set the <code>send_article</code> attribute to <code>1</code> in the <code>zem_contact</code> tag.</li>\n	<li>Use a <code>zem_contact_email</code> tag with the <code>send_article</code> attribute set to <code>1</code>. This field will be used as the recipient email address.</li><br />\n</ul></p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;you@example.com&quot; send_article=&quot;1&quot;&gt;\n  &lt;txp:zem_contact_email label=&quot;Recipient Email&quot; send_article=&quot;1&quot; /&gt;\n  &lt;txp:zem_contact_email label=&quot;Your Email&quot; /&gt;\n  &lt;txp:zem_contact_submit label=&quot;Send Article&quot; /&gt;\n&lt;/txp:zem_contact&gt;\n\n&lt;txp:zem_contact_send_article /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"tags\">Tags</h2>\n\n<p><a href=\"#zc\"><code>&lt;txp:zem_contact /&gt;</code></a> produces a flexible, customisable email contact form. It is intended for use as an enquiry form for commercial and private sites, and includes several features to help reduce common problems with such forms (invalid email addresses, missing information).</p>\n\n<p><a href=\"#zc_send_article\"><code>&lt;txp:zem_contact_send_article /&gt;</code></a> can be used to create a &#8220;send article&#8221; link within an article form, connecting it to the contact form.</p>\n\n<p>All other tags provided by this plugin can only be used inside a <code>&lt;txp:zem_contact&gt;</code> &#8230; <code>&lt;/txp:zem_contact&gt;</code> container tag or in a Textpattern form used as the <code>form</code> attribute in the <code>&lt;txp:zem_contact /&gt;</code> tag.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc\"><code>&lt;txp:zem_contact /&gt;</code></h3>\n\n<p>May be used as a self-closing or container tag. Place this where you want the input form to go. Status and error messages, if any, will be displayed before the form.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>to=&quot;email address&quot;</code> <span class=\"required\">required</span><br />\nRecipient email address.</li>\n	<li><code>to_form=&quot;form name&quot;</code><br />\nUse specified form (overrides <strong>to</strong> attribute).</li>\n</ul>\n\n<ul>\n	<li><code>from=&quot;email address&quot;</code><br />\nEmail address used in the &#8220;From:&#8221; field when sending email. Defaults to the sender&#8217;s email address. If specified, the sender&#8217;s email address will be placed in the &#8220;Reply-To:&#8221; field instead.</li>\n	<li><code>from_form=&quot;form name&quot;</code><br />\nUse specified form (overrides <strong>from</strong> attribute).</li>\n</ul>\n\n<ul>\n	<li><code>subject=&quot;subject text&quot;</code><br />\nSubject used when sending an email. Default is the site name.</li>\n	<li><code>subject_form=&quot;form name&quot;</code><br />\nUse specified form (overrides <strong>subject</strong> attribute).</li>\n</ul>\n\n<ul>\n	<li><code>thanks=&quot;text&quot;</code><br />\nMessage shown after successfully submitting a message. Default is <strong>Thank you, your message has been sent</strong>.</li>\n	<li><code>thanks_form=&quot;form name&quot;</code><br />\nUse specified form (overrides <strong>thanks</strong> attribute).</li>\n	<li><code>redirect=&quot;URL&quot;</code><br />\nRedirect to specified <span class=\"caps\">URL</span> (overrides <strong>thanks</strong> and <strong>thanks_form</strong> attributes). <span class=\"caps\">URL</span> must be relative to the Textpattern Site <span class=\"caps\">URL</span>. Example: <em>redirect=&#8220;monkey&#8221;</em> would redirect to http://example.com/monkey.</li>\n</ul>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nLabel for the contact form. If set to an empty string, display of the fieldset and legend tags will be suppressed. Default is <strong>Contact</strong>.</li>\n	<li><code>send_article=&quot;boolean&quot;</code><br />\nWhether to use this form to <a href=\"#article\">send an article</a>. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>\n	<li><code>copysender=&quot;boolean&quot;</code><br />\nWhether to send a copy of the email to the sender&#8217;s address. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>\n</ul>\n\n<ul>\n	<li><code>form=&quot;form name&quot;</code><br />\nUse specified form, containing the layout of the contact form fields.</li>\n	<li><code>show_input=&quot;boolean&quot;</code><br />\n Whether to display the form input fields. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n	<li><code>show_error=&quot;boolean&quot;</code><br />\n Whether to display error and status messages. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>See <a href=\"#contactform\">Getting started</a> and <a href=\"#advanced\">Advanced examples</a>.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_text\"><code>&lt;txp:zem_contact_text /&gt;</code></h3>\n\n<p>Creates a text input field and corresponding <code>&lt;label&gt;</code> tag. The input value will be included in the email, preceded by the label.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nText label displayed to the user. Default is <strong>Text</strong>.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and input field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>default=&quot;value&quot;</code><br />\nDefault value when no input is provided.</li>\n	<li><code>min=&quot;integer&quot;</code><br />\nMinimum input length in characters. Default is <strong>0</strong>.</li>\n	<li><code>max=&quot;integer&quot;</code><br />\nMaximum input length in characters. Default is <strong>100</strong>.</li>\n	<li><code>size=&quot;integer&quot;</code><br />\nSize of the input field as displayed to the user.</li>\n	<li><code>required=&quot;boolean&quot;</code><br />\nWhether this text field must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n</ul>\n\n<h4>Example</h4>\n\n<pre><code>&lt;txp:zem_contact_text label=&quot;Your name&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_email\"><code>&lt;txp:zem_contact_email /&gt;</code></h3>\n\n<p>Input field for user&#8217;s email address.</p>\n\n<p>The entered email address will automatically be validated to make sure it is of the form &#8220;abc@xxx.yyy[.zzz]&#8221;. On non-Windows servers, a test will be done to verify that an A or MX record exists for the domain. Neither test prevents spam, but it does help detecting accidental typing errors.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nText label displayed to the user. Default is <strong>Email</strong>.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and input field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>default=&quot;value&quot;</code><br />\nDefault value when no input is provided.</li>\n	<li><code>min=&quot;integer&quot;</code><br />\nMinimum input length in characters. Default is <strong>0</strong>.</li>\n	<li><code>max=&quot;integer&quot;</code><br />\nMaximum input length in characters. Default is <strong>100</strong>.</li>\n	<li><code>size=&quot;integer&quot;</code><br />\nSize of the input field as displayed to the user.</li>\n	<li><code>required=&quot;boolean&quot;</code><br />\nWhether this text field must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n	<li><code>send_article=&quot;boolean&quot;</code><br />\nWhether this field is used as the recipient email address when using the send_article function. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>\n</ul>\n\n<h4>Example</h4>\n\n<pre><code>&lt;txp:zem_contact_email label=&quot;Your email address&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_textarea\"><code>&lt;txp:zem_contact_textarea /&gt;</code></h3>\n\n<p>Creates a textarea.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nText label displayed to the user. Default is <strong>Message</strong>.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and input field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>default=&quot;value&quot;</code><br />\nDefault value when no input is provided.</li>\n	<li><code>cols=&quot;integer&quot;</code><br />\nColumn width. Default is <strong>58</strong>.</li>\n	<li><code>rows=&quot;integer&quot;</code><br />\nRow height. Default is <strong>8</strong>.</li>\n	<li><code>min=&quot;integer&quot;</code><br />\nMinimum input length in characters. Default is <strong>0</strong>.</li>\n	<li><code>max=&quot;integer&quot;</code><br />\nMaximum input length in characters. Default is <strong>10000</strong>.</li>\n	<li><code>required=&quot;boolean&quot;</code><br />\nWhether this text field must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n</ul>\n\n<h4>Example</h4>\n\n<p>Textarea that is 40 chars wide, 10 lines high, with a customized label:</p>\n\n<pre><code>&lt;txp:zem_contact_textarea cols=&quot;40&quot; rows=&quot;10&quot; label=&quot;Your question&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_submit\"><code>&lt;txp:zem_contact_submit /&gt;</code></h3>\n\n<p>Creates a submit button.<br />\nWhen used as a container tag, a &#8220;button&#8221; element will be used instead of an &#8220;input&#8221; element.</p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nText shown on the submit button. Default is &#8220;Send&#8221;.</li>\n	<li><code>button=&quot;boolean&quot;</code><br />\n<em>Deprecated. Use a container tag if you want a button element.</em></li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Standard submit button:</p>\n\n<pre><code>&lt;txp:zem_contact_submit /&gt;</code></pre>\n\n<p>Submit button with your own text:</p>\n\n<pre><code>&lt;txp:zem_contact_submit label=&quot;Send&quot; /&gt;</code></pre>\n\n<p>Usage as a container tag, which allows you to use Textpattern tags and <span class=\"caps\">HTML</span> markup in the submit button:</p>\n\n<pre><code>&lt;txp:zem_contact_submit&gt;&lt;strong&gt;Send&lt;/strong&gt; question&lt;/txp:zem_contact_submit&gt;</code></pre>\n\n<pre><code>&lt;txp:zem_contact_submit&gt;&lt;img src=&quot;path/to/img.png&quot; alt=&quot;submit&quot;&gt;&lt;/txp:zem_contact_submit&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_select\"><code>&lt;txp:zem_contact_select /&gt;</code></h3>\n\n<p>Creates a drop-down selection list.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>list=&quot;comma-separated values&quot;</code> <span class=\"required\">required</span><br />\nList of items to show in the select box.</li>\n	<li><code>selected=&quot;value&quot;</code><br />\nList item that is selected by default.</li>\n	<li><code>label=&quot;text&quot;</code><br />\nText label displayed to the user. Default is <strong>Option</strong>.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and input field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>delimiter=&quot;character&quot;</code><br />\nSeparator character used in the <strong>list</strong> attribute. Default is <strong>,</strong> (comma).</li>\n	<li><code>required=&quot;boolean&quot;</code><br />\nWhether a non-empty option must be selected. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Select list labeled &#8216;Department&#8217;, containing three options and a blank option (due to the comma before &#8216;Marketing&#8217;) shown by default, forcing the user to make a selection.</p>\n\n<pre><code>&lt;txp:zem_contact_select label=&quot;Department&quot; list=&quot;,Marketing,Sales,Support&quot; /&gt;</code></pre>\n\n<p>Select list containing three options with &#8216;Marketing&#8217; selected by default.</p>\n\n<pre><code>&lt;txp:zem_contact_select list=&quot;Marketing,Sales,Support&quot; selected=&quot;Marketing&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_checkbox\"><code>&lt;txp:zem_contact_checkbox /&gt;</code></h3>\n\n<p>Creates a check box.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code><br />\nText label displayed to the user. Default is <strong>Checkbox</strong>.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and input field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>checked=&quot;boolean&quot;</code><br />\nWhether this box is checked when first displayed. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is &#8220;0&#8221; (no).</li>\n	<li><code>required=&quot;boolean&quot;</code><br />\nWhether this checkbox must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Shrink-wrap agreement which must be checked by the user before the email will be sent.</p>\n\n<pre><code>&lt;txp:zem_contact_checkbox label=&quot;I accept the terms and conditions&quot; /&gt;</code></pre>\n\n<p>Optional checkboxes:</p>\n\n<pre><code>Select which operating systems are you familiar with:&lt;br /&gt;\n&lt;txp:zem_contact_checkbox label=&quot;Windows&quot; required=&quot;0&quot; /&gt;&lt;br /&gt;\n&lt;txp:zem_contact_checkbox label=&quot;Unix/Linux/BSD&quot; required=&quot;0&quot; /&gt;&lt;br /&gt;\n&lt;txp:zem_contact_checkbox label=&quot;MacOS&quot; required=&quot;0&quot; /&gt;&lt;br /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_radio\"><code>&lt;txp:zem_contact_radio /&gt;</code></h3>\n\n<p>Creates a radio button.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>group=&quot;text&quot;</code> <span class=\"required\">required</span><br />\nText used in the email to describe this group of radio buttons. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. Default is <strong>Radio</strong>.</li>\n	<li><code>label=&quot;text&quot;</code> <span class=\"required\">required</span><br />\nText label displayed to the user as radio button option.</li>\n	<li><code>name=&quot;value&quot;</code><br />\nField name, as used in the <span class=\"caps\">HTML</span> input tag. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. If it hasn&#8217;t been set at all, it will be derived from the <code>group</code> attribute.</li>\n	<li><code>break=&quot;tag&quot;</code><br />\nBreak tag between the label and field. Default is a space.</li>\n	<li><code>checked=&quot;boolean&quot;</code><br />\nWhether this radio option is checked when the form is first displayed. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>\n</ul>\n\n<h4>Example</h4>\n\n<p>Group mutually exclusive radio buttons by setting the <code>group</code> attribute on the first radio button in a group. Only the chosen radio button from each group will be used in the email message. The message will be output in the form <strong>group: label</strong> for each of the chosen radio buttons.</p>\n\n<pre><code>&lt;txp:zem_contact_radio label=&quot;Medium&quot; group=&quot;I like my steak&quot; /&gt;\n&lt;txp:zem_contact_radio label=&quot;Rare&quot; /&gt;\n&lt;txp:zem_contact_radio label=&quot;Well done&quot; /&gt;\n\n&lt;txp:zem_contact_radio label=&quot;Wine&quot; group=&quot;With a glass of&quot; /&gt;\n&lt;txp:zem_contact_radio label=&quot;Beer&quot; /&gt;\n&lt;txp:zem_contact_radio label=&quot;Water&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_secret\"><code>&lt;txp:zem_contact_secret /&gt;</code></h3>\n\n<p>This tag has no effect on the form or <span class=\"caps\">HTML</span> output, but will include additional information in the email. It can be used as a self-closing tag or as a container tag.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code><br />\nUsed internally. Set this only if you have multiple &#8216;secret&#8217; form elements with identical labels.</li>\n	<li><code>label=&quot;text&quot;</code><br />\nUsed to identify the field in the email. Default is <strong>Secret</strong>.</li>\n	<li><code>value=&quot;value&quot;</code><br />\nSome text you want to add to the email.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Usage as a self-closing tag</p>\n\n<pre><code>&lt;txp:zem_contact_secret value=&quot;The answer is 42&quot; /&gt;</code></pre>\n\n<p>Usage as a container tag</p>\n\n<pre><code>&lt;txp:zem_contact_secret label=&quot;Dear user&quot;&gt;\n  Please provide a useful example for this tag!\n&lt;/txp:zem_contact_secret&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_serverinfo\"><code>&lt;txp:zem_contact_serverinfo /&gt;</code></h3>\n\n<p>This tag has no effect on the form or <span class=\"caps\">HTML</span> output, but will include additional information in the email based on the <span class=\"caps\">PHP</span> $_SERVER variable.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;value&quot;</code> <span class=\"required\">required</span><br />\nName of the server variable. See the <a href=\"http://php.net/manual/reserved.variables.php#reserved.variables.server\"><span class=\"caps\">PHP</span> manual</a> for a full list.</li>\n	<li><code>label=&quot;text&quot;</code><br />\nUsed to identify the field in the email. Defaults to the value of the <strong>name</strong> attribute.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Add the IP address of the visitor to the email</p>\n\n<pre><code>&lt;txp:zem_contact_serverinfo name=&quot;REMOTE_ADDR&quot; label=&quot;IP number&quot; /&gt;</code></pre>\n\n<p>Add the name of the visitor&#8217;s browser to the email</p>\n\n<pre><code>&lt;txp:zem_contact_serverinfo name=&quot;HTTP_USER_AGENT&quot; label=&quot;Browser&quot; /&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"zc_send_article\"><code>&lt;txp:zem_contact_send_article /&gt;</code></h3>\n\n<p>Use this tag in your individual article form, where you want the &#8220;send article&#8221; link to be displayed.</p>\n\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>linktext=&quot;text&quot;</code><br />\nText displayed for the link. Default is <strong>send article</strong></li>\n</ul>\n\n<h4>Examples:</h4>\n\n<p>See <a href=\"#sendarticle\">Getting started</a></p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"advanced\">Advanced examples</h2>\n\n\n<h3 id=\"show_error\">Separate input and error forms</h3>\n\n<p>Using <code>show_input</code> and <code>show_error</code> to display the form and error messages on different parts of a page. A form is used to make sure the contents of both forms are identical, otherwise they would be seen as two independent forms. The first form only shows errors (no input), the second form only shows the input fields (no errors).</p>\n\n<pre><code>&lt;div id=&quot;error&quot;&gt;\n  &lt;txp:zem_contact form=&quot;contact_form&quot; show_input=&quot;0&quot; /&gt;\n&lt;/div&gt;\n\n&lt;div id=&quot;inputform&quot;&gt;\n  &lt;txp:zem_contact form=&quot;contact_form&quot; show_error=&quot;0&quot; /&gt;\n&lt;/div&gt;</code></pre>\n\n<p>Apart from the <code>show_error</code> and <code>show_input</code> attributes, all other attributes must be 100% identical in both forms, otherwise they would be seen as two unrelated forms.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"subject_form\">User selectable subject field</h3>\n\n<p>Specify the <code>subject_form</code> attribute and create a form which includes a <code>zem_contact_select</code> tag:</p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;you@example.com&quot; subject_form=&quot;my_subject_form&quot; /&gt;\n  &lt;txp:zem_contact_text label=&quot;Name&quot; /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_email /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_select label=&quot;Choose Subject&quot; list=&quot;,Question,Feedback&quot; /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_textarea label=&quot;Message&quot; /&gt;&lt;br /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>Create a Textpattern form called &#8220;my_subject_form&#8221;, containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n  global $zem_contact_form;\n  echo $zem_contact_form[&#39;Choose Subject&#39;];\n&lt;/txp:php&gt;</code></pre>\n\n<p>The <code>label</code> used in the <code>zem_contact_select</code> tag must be identical to the corresponding variable in the <code>subject_form</code>. Here we used <code>Choose subject</code>.</p>\n\n<p>If you&#8217;d prefer to add a common prefix for all subjects, use a <code>subject_form</code> containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n  global $zem_contact_form;\n  echo &#39;My common prefix - &#39; . $zem_contact_form[&#39;Choose Subject&#39;];\n&lt;/txp:php&gt;</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h3 id=\"to_form\">User selectable recipient, without showing email address</h3>\n\n<p>Specify the <code>to_form</code> attribute and create a form which includes a <code>zem_contact_select</code> tag:</p>\n\n<pre><code>&lt;txp:zem_contact to_form=&quot;my_zem_contact_to_form&quot;&gt;\n  &lt;txp:zem_contact_text label=&quot;Name&quot; /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_email /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_select label=&quot;Department&quot; list=&quot;,Support,Sales&quot; /&gt;&lt;br /&gt;\n  &lt;txp:zem_contact_textarea label=&quot;Message&quot; /&gt;&lt;br /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>Create a Textpattern form called &#8220;my_zem_contact_to_form&#8221;, containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n  global $zem_contact_form;\n  switch($zem_contact_form[&#39;Department&#39;])\n  {\n    case &#39;Support&#39;:\n      echo &#39;crew@example.com&#39;;\n      break;\n    case &#39;Sales&#39;:\n      echo &#39;showmethemoney@example.com&#39;;\n      break;\n    default:\n      echo &#39;someone@example.com&#39;;\n  }\n&lt;/txp:php&gt;</code></pre>\n\n<p>The <code>label</code> used in the <code>zem_contact_select</code> tag must be identical to the corresponsing variable in the <code>to_form</code>. Here we used <code>Department</code>.</p>\n\n<p>A &#8216;default&#8217; email address in the <code>to_form</code> is specified to ensure that a valid email address is used in cases where you add or change a select/radio option and forget to update the <code>to_form</code>.</p>\n\n<p class=\"warning\">Never use tags like <code>zem_contact_text</code>, <code>zem_contact_email</code> or <code>zem_contact_textarea</code> for setting the recipient address, otherwise your form can be abused to send spam to any email address!</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"styling\">Styling</h2>\n\n<p>The form itself has a class <strong>zemContactForm</strong> set on the <code>FORM</code> <span class=\"caps\">HTML</span> tag.</p>\n\n<p>The list of error messages (if any) has a class <strong>zemError</strong> set on the <code>UL</code> <span class=\"caps\">HTML</span> tag that encloses the list of errors.</p>\n\n<p>All form elements and corresponding labels have the following classes (or ids set):<br />\n<ol>\n	<li>One of <strong>zemText</strong>, <strong>zemTextarea</strong>, <strong>zemSelect</strong>, <strong>zemRadio</strong>, <strong>zemCheckbox</strong>, <strong>zemSubmit</strong>. It should be obvious which class is used for which form element (and corresponding label).</li>\n	<li><strong>zemRequired</strong> or <strong>errorElement</strong> or <strong>zemRequirederrorElement</strong>, depending on whether the form element is required, an error was found in whatever the visitor entered&#8230; or both.</li>\n	<li>An individual &#8220;id&#8221; or &#8220;class&#8221; set to the value of the <code>name</code> attribute of the corresponding tag. When styling forms based on this class, you should explicitly set the <code>name</code> attribute because automatically generated names may change in newer zem_contact_reborn versions.</li><br />\n</ol></p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"history\">History</h2>\n\n<p>Only the changes that may affect people who upgrade are detailed below.<br />\nTo save space, links to forum topics that detail all the changes in each version have been added.</p>\n\n<ul>\n	<li>07 feb 2016: <strong>version 4.0.3.21</strong>: Compatible with <span class=\"caps\">TXP</span> 4.6 and <span class=\"caps\">PHP</span> 5.3+</li>\n	<li>23 aug 2007: <strong>version 4.0.3.20</strong> <a href=\"http://forum.textpattern.com/viewtopic.php?id=23728\">changelog</a></li>\n	<li>14 feb 2007: <strong>version 4.0.3.19</strong> <a href=\"http://forum.textpattern.com/viewtopic.php?id=21144\">changelog</a>\n	<ul>\n		<li><a href=\"#sendarticle\">send_article</a> functionality revised, requiring changes when upgrading from earlier versions</li>\n		<li>New language strings: &#8216;send_article&#8217; and &#8216;recipient&#8217; (replaces &#8216;receiver&#8217;)</li>\n		<li>Sets of radio buttons require the new <a href=\"#zc_radio\">group</a> attribute</li>\n		<li>Yes/No values deprecated in favor for the <span class=\"caps\">TXP</span> standard 1/0 values (yes/no still work in this version)</li>\n	</ul></li>\n	<li>20 nov 2006: <strong>version 4.0.3.18</strong> <a href=\"http://forum.textpattern.com/viewtopic.php?id=19823\">changelog</a>\n	<ul>\n		<li>IDs &#8216;zemContactForm&#8217; and &#8216;zemSubmit&#8217; have changed to classes to allow multiple forms per page</li>\n		<li>New language strings: &#8216;form_used&#8217;, &#8216;invalid_utf8&#8217;, &#8216;max_warning&#8217;, &#8216;name&#8217;, &#8216;refresh&#8217;, &#8216;secret&#8217;</li>\n	</ul></li>\n	<li>12 mar 2006: <strong>version 4.0.3.17</strong> <a href=\"http://forum.textpattern.com/viewtopic.php?id=13416\">changelog</a></li>\n	<li>11 feb 2006: <strong>version .16</strong></li>\n	<li>06 feb 2006: <strong>version .15</strong></li>\n	<li>03 feb 2006: <strong>version .14</strong>\n	<ul>\n		<li>Requires separate zem_contact_lang plugin</li>\n	</ul></li>\n	<li>29 jan 2006: <strong>version .12</strong></li>\n	<li>27 jan 2006: <strong>version .11</strong></li>\n	<li>23 jan 2006: <strong>version .09 and .10</strong></li>\n	<li>23 jan 2006: <strong>version .08</strong></li>\n	<li>17 jan 2006: <strong>version .07</strong></li>\n	<li>16 jan 2006: <strong>version .05 and .06</strong></li>\n	<li>15 jan 2006: <strong>version .04</strong></li>\n	<li>10 jan 2006: <strong>version .03</strong></li>\n	<li>19 dec 2005: <strong>version .02</strong></li>\n</ul>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"credits\">Credits</h2>\n\n<ul>\n	<li><strong>zem</strong> wrote the zem_contact 0.6 plugin on which this plugin was initially based.</li>\n	<li><strong>Mary</strong> completely revised the plugin code.</li>\n	<li><strong>Stuart</strong> Turned it into a plugin, added a revised help text and additional code. Maintained all plugin versions till 4.0.3.17.</li>\n	<li><strong>wet</strong> added the zem_contact_radio tag.</li>\n	<li><strong>Tranquillo</strong> added the anti-spam <span class=\"caps\">API</span> and zem_contact_send_article functionality.</li>\n	<li><strong>aslsw66</strong>, <strong>jdykast</strong> and others (?) provided additional code</li>\n	<li><strong>Ruud</strong> cleaned up and audited the code to weed out bugs and completely revised the help text. Maintainer of versions 4.0.3.18 and up.</li>\n	<li>Supported and tested to destruction by the Textpattern community.</li>\n</ul>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"api\">Zem Contact Reborn&#8217;s <span class=\"caps\">API</span></h2>\n\n<p>The plugin <span class=\"caps\">API</span> of zem contact, developed by Tranquillo, is similar to the comments <span class=\"caps\">API</span> of Textpattern, which is explained in the Textbook <a href=\"http://textpattern.net/wiki/index.php?title=Plugin_Development_Topics\">Plugin Development Topics</a> and <a href=\"http://textpattern.net/wiki/index.php?title=Combat_Comment_Spam\">Combat Comment Spam</a>.</p>\n\n<p>Two callback events exist in zem_contact_reborn:<br />\n<ul>\n	<li><code>zemcontact.submit</code> is called after the form is submitted and the values are checked if empty or valid email addresses, but before the mail is sent.</li>\n	<li><code>zemcontact.form</code> let&#8217;s you insert content in the contact form as displayed to the visitor.</li><br />\n</ul></p>\n\n<p>For reference here are the commands that will be interesting to plugin developers:</p>\n\n<pre><code>// This will call your function before the form is submitted\n// So you can analyse the submitted data\nregister_callback(&#39;abc_myfunction&#39;,&#39;zemcontact.submit&#39;);\n\n// This will call your function and add the output (use return $mystuff)\n// to the contact-form.\nregister_callback(&#39;abc_myotherfunction2&#39;,&#39;zemcontact.form&#39;);\n\n// To get hold of the form-variables you can use\nglobal zem_contact_form;\n\n// With the following two lines you can tell zem_contact if your\n// plugin found spam\n$evaluator =&amp; get_zemcontact_evaluator();\n\n// The passed value must be non-zero to mark the content as spam.\n// Value must be a number between 0 and 1.\n$evaluator -&gt; add_zemcontact_status(1);</code></pre>\n\n<p>Multiple plugins can be active at the same time and each of them can mark the submitted content as spam and prevent the form from being submitted.</p>\n\n<p><strong>An example of a plug-in connecting to Zem Contact Reborn&#8217;s <span class=\"caps\">API</span>:</strong></p>\n\n<pre><code>register_callback(&#39;pap_zemcontact_form&#39;,&#39;zemcontact.form&#39;);\nregister_callback(&#39;pap_zemcontact_submit&#39;,&#39;zemcontact.submit&#39;);\n\nfunction pap_zemcontact_form() {\n  $field = &#39;&lt;div style=&quot;display:none&quot;&gt;&#39;.\n    finput(&#39;text&#39;,&#39;phone&#39;,ps(&#39;phone&#39;),&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;phone&#39;).&#39;&lt;br /&gt;&#39;.\n    finput(&#39;text&#39;,&#39;mail&#39;,ps(&#39;mail&#39;),&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;mail&#39;).&#39;&lt;/div&gt;&#39;;\n  return $field;&lt;/code&gt;\n}\n\nfunction pap_zemcontact_submit() {\n  $checking_mail_field = trim(ps(&#39;mail&#39;));\n  $checking_phone_field = trim(ps(&#39;phone&#39;));\n\n  $evaluation =&amp; get_zemcontact_evaluator();\n\n  // If the hidden fields are filled out, the contact form won&#39;t be submitted!\n  if ($checking_mail_field != &#39;&#39; or $checking_phone_field != &#39;&#39;) {\n    $evaluation -&gt; add_zemcontact_status(1);\n  }\n  return;\n}</code></pre>\n\n<p><a href=\"#top\">Back to top</a></p>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'zem_contact\')\n    ->register(\'zem_contact_text\')\n    ->register(\'zem_contact_email\')\n    ->register(\'zem_contact_textarea\')\n    ->register(\'zem_contact_submit\')\n    ->register(\'zem_contact_select\')\n    ->register(\'zem_contact_checkbox\')\n    ->register(\'zem_contact_radio\')\n    ->register(\'zem_contact_secret\')\n    ->register(\'zem_contact_serverinfo\')\n    ->register(\'zem_contact_send_article\');\n}\n\n\nfunction zem_contact($atts, $thing = \'\')\n{\n	global $sitename, $prefs, $production_status, $zem_contact_from,\n		$zem_contact_recipient, $zem_contact_error, $zem_contact_submit,\n		$zem_contact_form, $zem_contact_labels, $zem_contact_values;\n\n	extract(zem_contact_lAtts(array(\n		\'copysender\'	=> 0,\n		\'form\'		=> \'\',\n		\'from\'		=> \'\',\n		\'from_form\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'contact\'),\n		\'redirect\'	=> \'\',\n		\'show_error\'	=> 1,\n		\'show_input\'	=> 1,\n		\'send_article\'	=> 0,\n		\'subject\'	=> zem_contact_gTxt(\'email_subject\', html_entity_decode($sitename,ENT_QUOTES)),\n		\'subject_form\'	=> \'\',\n		\'to\'		=> \'\',\n		\'to_form\'	=> \'\',\n		\'thanks\'	=> graf(zem_contact_gTxt(\'email_thanks\')),\n		\'thanks_form\'	=> \'\'\n	), $atts));\n\n	unset($atts[\'show_error\'], $atts[\'show_input\']);\n	$zem_contact_form_id = md5(serialize($atts).preg_replace(\'/[\\t\\s\\r\\n]/\',\'\',$thing));\n	$zem_contact_submit = (ps(\'zem_contact_form_id\') == $zem_contact_form_id);\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			zem_contact_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	static $headers_sent = false;\n	if (!$headers_sent) {\n		header(\'Last-Modified: \'.gmdate(\'D, d M Y H:i:s\',time()-3600*24*7).\' GMT\');\n		header(\'Expires: \'.gmdate(\'D, d M Y H:i:s\',time()+600).\' GMT\');\n		header(\'Cache-Control: no-cache, must-revalidate\');\n		$headers_sent = true;\n	}\n\n	$nonce   = mysql_real_escape_string(ps(\'zem_contact_nonce\'));\n	$renonce = false;\n\n	if ($zem_contact_submit)\n	{\n		safe_delete(\'txp_discuss_nonce\', \'issue_time < date_sub(now(), interval 10 minute)\');\n		if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\"))\n		{\n			if ($rs[\'used\'])\n			{\n				unset($zem_contact_error);\n				$zem_contact_error[] = zem_contact_gTxt(\'form_used\');\n				$renonce = true;\n				$_POST = array();\n				$_POST[\'zem_contact_submit\'] = TRUE;\n				$_POST[\'zem_contact_form_id\'] = $zem_contact_form_id;\n				$_POST[\'zem_contact_nonce\'] = $nonce;\n			}\n		}\n		else\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'form_expired\');\n			$renonce = true;\n		}\n	}\n\n	if ($zem_contact_submit and $nonce and !$renonce)\n	{\n		$zem_contact_nonce = $nonce;\n	}\n\n	elseif (!$show_error or $show_input)\n	{\n		$zem_contact_nonce = md5(uniqid(rand(), true));\n		safe_insert(\'txp_discuss_nonce\', \"issue_time = now(), nonce = \'$zem_contact_nonce\'\");\n	}\n\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	if (empty($form))\n	{\n		$form = \'\n<txp:zem_contact_text label=\"\'.zem_contact_gTxt(\'name\').\'\" /><br />\n<txp:zem_contact_email /><br />\'.\n($send_article ? \'<txp:zem_contact_email send_article=\"1\" label=\"\'.zem_contact_gTxt(\'recipient\').\'\" /><br />\' : \'\').\n\'<txp:zem_contact_textarea /><br />\n<txp:zem_contact_submit />\n\';\n	}\n\n	$form = parse($form);\n\n	if ($to_form)\n	{\n		$to = parse(fetch_form($to_form));\n	}\n\n	if (!$to and !$send_article)\n	{\n		return zem_contact_gTxt(\'to_missing\');\n	}\n\n	$out = \'\';\n\n	if (!$zem_contact_submit) {\n	  # don\'t show errors or send mail\n	}\n\n	elseif (!empty($zem_contact_error))\n	{\n		if ($show_error or !$show_input)\n		{\n			$out .= n.\'<ul class=\"zemError\">\';\n\n			foreach (array_unique($zem_contact_error) as $error)\n			{\n				$out .= n.t.\'<li>\'.$error.\'</li>\';\n			}\n\n			$out .= n.\'</ul>\';\n\n			if (!$show_input) return $out;\n		}\n	}\n\n	elseif ($show_input and is_array($zem_contact_form))\n	{\n		/// load and check spam plugins/\n		callback_event(\'zemcontact.submit\');\n		$evaluation =& get_zemcontact_evaluator();\n		$clean = $evaluation->get_zemcontact_status();\n		if ($clean != 0) {\n			return zem_contact_gTxt(\'spam\');\n		}\n\n		if ($from_form)\n		{\n			$from = parse(fetch_form($from_form));\n		}\n\n		if ($subject_form)\n		{\n			$subject = parse(fetch_form($subject_form));\n		}\n\n		$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n\n		$msg = array();\n\n		foreach ($zem_contact_labels as $name => $label)\n		{\n			if (!trim($zem_contact_values[$name])) continue;\n			$msg[] = $label.\': \'.$zem_contact_values[$name];\n		}\n\n		if ($send_article)\n		{\n			global $thisarticle;\n			$subject = str_replace(\'&#38;\', \'&\', $thisarticle[\'title\']);\n			$msg[] = permlinkurl($thisarticle);\n			$msg[] = $subject;\n			$s_ar = array(\'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8217;\', \'&#8242;\', \'&#8243;\', \'&#8230;\', \'&#8211;\', \'&#8212;\', \'&#215;\', \'&#8482;\', \'&#174;\', \'&#169;\', \'&lt;\', \'&gt;\', \'&quot;\', \'&amp;\', \'&#38;\', \"\\t\", \'<p\');\n			if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\')) {\n				$r_ar = array(\"\'\", \"\'\", \'\"\', \'\"\', \"\'\", \"\'\", \'\"\', \'...\', \'-\', \'--\', \'x\', \'[tm]\', \'(r)\', \'(c)\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n			}\n			else\n			{\n				$r_ar = array(\'‘\', \'’\', \'“\', \'”\', \'’\', \'′\', \'″\', \'…\', \'–\', \'—\', \'×\', \'™\', \'®\', \'©\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n			}\n			$msg[] = trim(strip_tags(str_replace($s_ar,$r_ar,(trim(strip_tags($thisarticle[\'excerpt\'])) ? $thisarticle[\'excerpt\'] : $thisarticle[\'body\']))));\n			if (empty($zem_contact_recipient))\n			{\n				return zem_contact_gTxt(\'field_missing\', zem_contact_gTxt(\'recipient\'));\n			}\n			else\n			{\n				$to = $zem_contact_recipient;\n			}\n		}\n\n		$msg = join(\"\\n\\n\", $msg);\n		$msg = str_replace(\"\\r\\n\", \"\\n\", $msg);\n		$msg = str_replace(\"\\r\", \"\\n\", $msg);\n		$msg = str_replace(\"\\n\", $sep, $msg);\n\n		if ($from)\n		{\n			$reply = $zem_contact_from;\n		}\n\n		else\n		{\n			$from = $zem_contact_from;\n			$reply = \'\';\n		}\n\n		$from    = zem_contact_strip($from);\n		$to      = zem_contact_strip($to);\n		$subject = zem_contact_strip($subject);\n		$reply   = zem_contact_strip($reply);\n		$msg     = zem_contact_strip($msg, FALSE);\n\n		if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\'))\n		{\n			$charset = \'ISO-8859-1\';\n			$subject = utf8_decode($subject);\n			$msg     = utf8_decode($msg);\n		}\n\n		else\n		{\n			$charset = \'UTF-8\';\n		}\n\n		$subject = zem_contact_mailheader($subject, \'text\');\n\n		$headers = \'From: \'.$from.\n			($reply ? ($sep.\'Reply-To: \'.$reply) : \'\').\n			$sep.\'X-Mailer: Textpattern (zem_contact_reborn)\'.\n			$sep.\'X-Originating-IP: \'.zem_contact_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'].\' via \' : \'\').$_SERVER[\'REMOTE_ADDR\']).\n			$sep.\'Content-Transfer-Encoding: 8bit\'.\n			$sep.\'Content-Type: text/plain; charset=\"\'.$charset.\'\"\';\n\n		safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = now()\", \"nonce = \'$nonce\'\");\n\n		if (mail($to, $subject, $msg, $headers))\n		{\n			$_POST = array();\n\n			if ($copysender and $zem_contact_from)\n			{\n				mail(zem_contact_strip($zem_contact_from), $subject, $msg, $headers);\n			}\n\n			if ($redirect)\n			{\n				while (@ob_end_clean());\n				$uri = hu.ltrim($redirect,\'/\');\n				if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\']))\n				{\n					txp_status_header(\'303 See Other\');\n					header(\'Location: \'.$uri);\n					header(\'Connection: close\');\n					header(\'Content-Length: 0\');\n				}\n				else\n				{\n					$uri = htmlspecialchars($uri);\n					$refresh = zem_contact_gTxt(\'refresh\');\n					echo <<<END\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n	<title>$sitename</title>\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n	<meta http-equiv=\"refresh\" content=\"0;url=$uri\" />\n</head>\n<body>\n<a href=\"$uri\">$refresh</a>\n</body>\n</html>\nEND;\n				}\n				exit;\n			}\n\n			else\n			{\n				return \'<div class=\"zemThanks\" id=\"zcr\'.$zem_contact_form_id.\'\">\' .\n					($thanks_form ? fetch_form($thanks_form) : $thanks) .\n					\'</div>\';\n			}\n		}\n\n		else\n		{\n			$out .= graf(zem_contact_gTxt(\'mail_sorry\'));\n		}\n	}\n\n	if ($show_input and !$send_article or gps(\'zem_contact_send_article\'))\n	{\n		return \'<form method=\"post\"\'.((!$show_error and $zem_contact_error) ? \'\' : \' id=\"zcr\'.$zem_contact_form_id.\'\"\').\' class=\"zemContactForm\" action=\"\'.htmlspecialchars(serverSet(\'REQUEST_URI\')).\'#zcr\'.$zem_contact_form_id.\'\">\'.\n			( $label ? n.\'<fieldset>\' : n.\'<div>\' ).\n			( $label ? n.\'<legend>\'.htmlspecialchars($label).\'</legend>\' : \'\' ).\n			$out.\n			n.\'<input type=\"hidden\" name=\"zem_contact_nonce\" value=\"\'.$zem_contact_nonce.\'\" />\'.\n			n.\'<input type=\"hidden\" name=\"zem_contact_form_id\" value=\"\'.$zem_contact_form_id.\'\" />\'.\n			$form.\n			callback_event(\'zemcontact.form\').\n			( $label ? (n.\'</fieldset>\') : (n.\'</div>\') ).\n			n.\'</form>\';\n	}\n\n	return \'\';\n}\n\nfunction zem_contact_strip($str, $header = TRUE) {\n	if ($header) $str = strip_rn($str);\n	return preg_replace(\'/[\\x00]/\', \' \', $str);\n}\n\nfunction zem_contact_text($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> br,\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'text\'),\n		\'max\'		=> 100,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'size\'		=> \'\'\n	), $atts));\n\n	$min = intval($min);\n	$max = intval($max);\n	$size = intval($size);\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = trim(ps($name));\n		$utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\n		$hlabel = htmlspecialchars($label);\n\n		if (strlen($value))\n		{\n			if (!$utf8len)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_utf8\', $hlabel);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($min and $utf8len < $min)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'min_warning\', $hlabel, $min);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($max and $utf8len > $max)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'max_warning\', $hlabel, $max);\n				$isError = \"errorElement\";\n				#$value = join(\'\',array_slice($ar[0],0,$max));\n			}\n\n			else\n			{\n				zem_contact_store($name, $label, $value);\n			}\n		}\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', $hlabel);\n			$isError = \"errorElement\";\n		}\n	}\n\n	else\n	{\n		$value = $default;\n	}\n\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\n	$maxlength = ($max) ? \' maxlength=\"\'.$max.\'\"\' : \'\';\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n        return \'<label for=\"\'.$name.\'\" class=\"zemText \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		\'<input type=\"text\" id=\"\'.$name.\'\" class=\"zemText \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\" value=\"\'.htmlspecialchars($value).\'\"\'.$size.$maxlength.\' />\';\n}\n\nfunction zem_contact_textarea($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> br,\n		\'cols\'		=> 58,\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'message\'),\n		\'max\'		=> 10000,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'rows\'		=> 8\n	), $atts));\n\n	$min = intval($min);\n	$max = intval($max);\n	$cols = intval($cols);\n	$rows = intval($rows);\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\n		$hlabel = htmlspecialchars($label);\n\n		if (strlen(ltrim($value)))\n		{\n			if (!$utf8len)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_utf8\', $hlabel);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($min and $utf8len < $min)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'min_warning\', $hlabel, $min);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($max and $utf8len > $max)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'max_warning\', $hlabel, $max);\n				$isError = \"errorElement\";\n				#$value = join(\'\',array_slice($utf8ar[0],0,$max));\n			}\n\n			else\n			{\n				zem_contact_store($name, $label, $value);\n			}\n		}\n\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', $hlabel);\n			$isError = \"errorElement\";\n		}\n	}\n\n	else\n	{\n		$value = $default;\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<label for=\"\'.$name.\'\" class=\"zemTextarea \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		\'<textarea id=\"\'.$name.\'\" class=\"zemTextarea \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\" cols=\"\'.$cols.\'\" rows=\"\'.$rows.\'\">\'.htmlspecialchars($value).\'</textarea>\';\n}\n\nfunction zem_contact_email($atts)\n{\n	global $zem_contact_error, $zem_contact_submit, $zem_contact_from, $zem_contact_recipient;\n\n	extract(zem_contact_lAtts(array(\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'email\'),\n		\'max\'		=> 100,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'break\'		=> br,\n		\'size\'		=> \'\',\n		\'send_article\'	=> 0\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	$email = $zem_contact_submit ? trim(ps($name)) : $default;\n\n	if ($zem_contact_submit and strlen($email))\n	{\n		if (!is_valid_email($email))\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'invalid_email\', htmlspecialchars($email));\n			$isError = \"errorElement\";\n		}\n\n		else\n		{\n			preg_match(\"/@(.+)$/\", $email, $match);\n			$domain = $match[1];\n\n			if (is_callable(\'checkdnsrr\') and checkdnsrr(\'textpattern.com.\',\'A\') and !checkdnsrr($domain.\'.\',\'MX\') and !checkdnsrr($domain.\'.\',\'A\'))\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_host\', htmlspecialchars($domain));\n				$isError = \"errorElement\";\n			}\n\n			else\n			{\n				if ($send_article) {\n					$zem_contact_recipient = $email;\n				}\n				else {\n					$zem_contact_from = $email;\n				}\n			}\n		}\n	}\n\n	return zem_contact_text(array(\n		\'default\'	=> $email,\n		\'isError\'	=> $isError,\n		\'label\'		=> $label,\n		\'max\'		=> $max,\n		\'min\'		=> $min,\n		\'name\'		=> $name,\n		\'required\'	=> $required,\n		\'break\'		=> $break,\n		\'size\'		=> $size\n	));\n}\n\nfunction zem_contact_select($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'name\'		=> \'\',\n		\'break\'		=> \' \',\n		\'delimiter\'	=> \',\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'option\'),\n		\'list\'		=> zem_contact_gTxt(\'general_inquiry\'),\n		\'required\'	=> 1,\n		\'selected\'	=> \'\'\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	$list = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$list)));\n\n	if ($zem_contact_submit)\n	{\n		$value = trim(ps($name));\n\n		if (strlen($value))\n		{\n			if (in_array($value, $list))\n			{\n				zem_contact_store($name, $label, $value);\n			}\n\n			else\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_value\', htmlspecialchars($label), htmlspecialchars($value));\n				$isError = \"errorElement\";\n			}\n		}\n\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', htmlspecialchars($label));\n			$isError = \"errorElement\";\n		}\n	}\n	else\n	{\n		$value = $selected;\n	}\n\n	$out = \'\';\n\n	foreach ($list as $item)\n	{\n		$out .= n.t.\'<option\'.($item == $value ? \' selected=\"selected\">\' : \'>\').(strlen($item) ? htmlspecialchars($item) : \' \').\'</option>\';\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<label for=\"\'.$name.\'\" class=\"zemSelect \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		n.\'<select id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"zemSelect \'.$zemRequired.$isError.\'\">\'.\n			$out.\n		n.\'</select>\';\n}\n\nfunction zem_contact_checkbox($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> \' \',\n		\'checked\'	=> 0,\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'checkbox\'),\n		\'name\'		=> \'\',\n		\'required\'	=> 1\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = (bool) ps($name);\n\n		if ($required and !$value)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', htmlspecialchars($label));\n			$isError = \"errorElement\";\n		}\n\n		else\n		{\n			zem_contact_store($name, $label, $value ? gTxt(\'yes\') : gTxt(\'no\'));\n		}\n	}\n\n	else {\n		$value = $checked;\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<input type=\"checkbox\" id=\"\'.$name.\'\" class=\"zemCheckbox \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\"\'.\n		($value ? \' checked=\"checked\"\' : \'\').\' />\'.$break.\n		\'<label for=\"\'.$name.\'\" class=\"zemCheckbox \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\n}\n\nfunction zem_contact_serverinfo($atts)\n{\n	global $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'label\'		=> \'\',\n		\'name\'		=> \'\'\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if (strlen($name) and $zem_contact_submit)\n	{\n		if (!$label) $label = $name;\n		zem_contact_store($name, $label, serverSet($name));\n	}\n}\n\nfunction zem_contact_secret($atts, $thing = \'\')\n{\n	global $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'name\'	=> \'\',\n		\'label\'	=> zem_contact_gTxt(\'secret\'),\n		\'value\'	=> \'\'\n	), $atts));\n\n	$name = zem_contact_label2name($name ? $name : $label);\n\n	if ($zem_contact_submit)\n	{\n		if ($thing) $value = trim(parse($thing));\n		zem_contact_store($name, $label, $value);\n	}\n\n	return \'\';\n}\n\nfunction zem_contact_radio($atts)\n{\n	global $zem_contact_error, $zem_contact_submit, $zem_contact_values;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> \' \',\n		\'checked\'	=> 0,\n		\'group\'		=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'option\'),\n		\'name\'		=> \'\'\n	), $atts));\n\n	static $cur_name = \'\';\n	static $cur_group = \'\';\n\n	if (!$name and !$group and !$cur_name and !$cur_group) {\n		$cur_group = zem_contact_gTxt(\'radio\');\n		$cur_name = $cur_group;\n	}\n	if ($group and !$name and $group != $cur_group) $name = $group;\n\n	if ($name) $cur_name = $name;\n	else $name = $cur_name;\n\n	if ($group) $cur_group = $group;\n	else $group = $cur_group;\n\n	$id   = \'q\'.md5($name.\'=>\'.$label);\n	$name = zem_contact_label2name($name);\n\n	if ($zem_contact_submit)\n	{\n		$is_checked = (ps($name) == $id);\n\n		if ($is_checked or $checked and !isset($zem_contact_values[$name]))\n		{\n			zem_contact_store($name, $group, $label);\n		}\n	}\n\n	else\n	{\n		$is_checked = $checked;\n	}\n\n	return \'<input value=\"\'.$id.\'\" type=\"radio\" id=\"\'.$id.\'\" class=\"zemRadio \'.$name.\'\" name=\"\'.$name.\'\"\'.\n		( $is_checked ? \' checked=\"checked\" />\' : \' />\').$break.\n		\'<label for=\"\'.$id.\'\" class=\"zemRadio \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\n}\n\nfunction zem_contact_send_article($atts)\n{\n	if (!isset($_REQUEST[\'zem_contact_send_article\'])) {\n		$linktext = (empty($atts[\'linktext\'])) ? zem_contact_gTxt(\'send_article\') : $atts[\'linktext\'];\n		$join = (empty($_SERVER[\'QUERY_STRING\'])) ? \'?\' : \'&\';\n		$href = $_SERVER[\'REQUEST_URI\'].$join.\'zem_contact_send_article=yes\';\n		return \'<a href=\"\'.htmlspecialchars($href).\'\">\'.htmlspecialchars($linktext).\'</a>\';\n	}\n	return;\n}\n\nfunction zem_contact_submit($atts, $thing)\n{\n	extract(zem_contact_lAtts(array(\n		\'button\'	=> 0,\n		\'label\'		=> zem_contact_gTxt(\'send\')\n	), $atts));\n\n	$label = htmlspecialchars($label);\n\n	if ($button or strlen($thing))\n	{\n		return \'<button type=\"submit\" class=\"zemSubmit\" name=\"zem_contact_submit\" value=\"\'.$label.\'\">\'.($thing ? trim(parse($thing)) : $label).\'</button>\';\n	}\n	else\n	{\n		return \'<input type=\"submit\" class=\"zemSubmit\" name=\"zem_contact_submit\" value=\"\'.$label.\'\" />\';\n	}\n}\n\nfunction zem_contact_lAtts($arr, $atts)\n{\n	foreach(array(\'button\', \'copysender\', \'checked\', \'required\', \'send_article\', \'show_input\', \'show_error\') as $key)\n	{\n		if (isset($atts[$key]))\n		{\n			$atts[$key] = ($atts[$key] === \'yes\' or intval($atts[$key])) ? 1 : 0;\n		}\n	}\n	if (isset($atts[\'break\']) and $atts[\'break\'] == \'br\') $atts[\'break\'] = \'<br />\';\n	return lAtts($arr, $atts);\n}\n\nclass zemcontact_evaluation\n{\n	var $status;\n\n	function zemcontact_evaluation() {\n		$this->status = 0;\n	}\n\n	function add_zemcontact_status($check) {\n		$this->status += $check;\n	}\n\n	function get_zemcontact_status() {\n		return $this->status;\n	}\n}\n\nfunction &get_zemcontact_evaluator()\n{\n	static $instance;\n\n	if(!isset($instance)) {\n		$instance = new zemcontact_evaluation();\n	}\n	return $instance;\n}\n\nfunction zem_contact_label2name($label)\n{\n	$label = trim($label);\n	if (strlen($label) == 0) return \'invalid\';\n	if (strlen($label) <= 32 and preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) return $label;\n	else return \'q\'.md5($label);\n}\n\nfunction zem_contact_store($name, $label, $value)\n{\n	global $zem_contact_form, $zem_contact_labels, $zem_contact_values;\n	$zem_contact_form[$label] = $value;\n	$zem_contact_labels[$name] = $label;\n	$zem_contact_values[$name] = $value;\n}\n\nfunction zem_contact_mailheader($string, $type)\n{\n	global $prefs;\n	if (!strstr($string,\'=?\') and !preg_match(\'/[\\x00-\\x1F\\x7F-\\xFF]/\', $string)) {\n		if (\"phrase\" == $type) {\n			if (preg_match(\'/[][()<>@,;:\".\\x5C]/\', $string)) {\n				$string = \'\"\'. strtr($string, array(\"\\\\\" => \"\\\\\\\\\", \'\"\' => \'\\\"\')) . \'\"\';\n			}\n		}\n		elseif (\"text\" != $type) {\n			trigger_error(\'Unknown encode_mailheader type\', E_USER_WARNING);\n		}\n		return $string;\n	}\n	if ($prefs[\'override_emailcharset\']) {\n		$start = \'=?ISO-8859-1?B?\';\n		$pcre  = \'/.{1,42}/s\';\n	}\n	else {\n		$start = \'=?UTF-8?B?\';\n		$pcre  = \'/.{1,45}(?=[\\x00-\\x7F\\xC0-\\xFF]|$)/s\';\n	}\n	$end = \'?=\';\n	$sep = is_windows() ? \"\\r\\n\" : \"\\n\";\n	preg_match_all($pcre, $string, $matches);\n	return $start . join($end.$sep.\' \'.$start, array_map(\'base64_encode\',$matches[0])) . $end;\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'zem_contact\')\n    ->register(\'zem_contact_text\')\n    ->register(\'zem_contact_email\')\n    ->register(\'zem_contact_textarea\')\n    ->register(\'zem_contact_submit\')\n    ->register(\'zem_contact_select\')\n    ->register(\'zem_contact_checkbox\')\n    ->register(\'zem_contact_radio\')\n    ->register(\'zem_contact_secret\')\n    ->register(\'zem_contact_serverinfo\')\n    ->register(\'zem_contact_send_article\');\n}\n\n\nfunction zem_contact($atts, $thing = \'\')\n{\n	global $sitename, $prefs, $production_status, $zem_contact_from,\n		$zem_contact_recipient, $zem_contact_error, $zem_contact_submit,\n		$zem_contact_form, $zem_contact_labels, $zem_contact_values;\n\n	extract(zem_contact_lAtts(array(\n		\'copysender\'	=> 0,\n		\'form\'		=> \'\',\n		\'from\'		=> \'\',\n		\'from_form\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'contact\'),\n		\'redirect\'	=> \'\',\n		\'show_error\'	=> 1,\n		\'show_input\'	=> 1,\n		\'send_article\'	=> 0,\n		\'subject\'	=> zem_contact_gTxt(\'email_subject\', html_entity_decode($sitename,ENT_QUOTES)),\n		\'subject_form\'	=> \'\',\n		\'to\'		=> \'\',\n		\'to_form\'	=> \'\',\n		\'thanks\'	=> graf(zem_contact_gTxt(\'email_thanks\')),\n		\'thanks_form\'	=> \'\'\n	), $atts));\n\n	unset($atts[\'show_error\'], $atts[\'show_input\']);\n	$zem_contact_form_id = md5(serialize($atts).preg_replace(\'/[\\t\\s\\r\\n]/\',\'\',$thing));\n	$zem_contact_submit = (ps(\'zem_contact_form_id\') == $zem_contact_form_id);\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			zem_contact_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	static $headers_sent = false;\n	if (!$headers_sent) {\n		header(\'Last-Modified: \'.gmdate(\'D, d M Y H:i:s\',time()-3600*24*7).\' GMT\');\n		header(\'Expires: \'.gmdate(\'D, d M Y H:i:s\',time()+600).\' GMT\');\n		header(\'Cache-Control: no-cache, must-revalidate\');\n		$headers_sent = true;\n	}\n\n	$nonce   = mysql_real_escape_string(ps(\'zem_contact_nonce\'));\n	$renonce = false;\n\n	if ($zem_contact_submit)\n	{\n		safe_delete(\'txp_discuss_nonce\', \'issue_time < date_sub(now(), interval 10 minute)\');\n		if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\"))\n		{\n			if ($rs[\'used\'])\n			{\n				unset($zem_contact_error);\n				$zem_contact_error[] = zem_contact_gTxt(\'form_used\');\n				$renonce = true;\n				$_POST = array();\n				$_POST[\'zem_contact_submit\'] = TRUE;\n				$_POST[\'zem_contact_form_id\'] = $zem_contact_form_id;\n				$_POST[\'zem_contact_nonce\'] = $nonce;\n			}\n		}\n		else\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'form_expired\');\n			$renonce = true;\n		}\n	}\n\n	if ($zem_contact_submit and $nonce and !$renonce)\n	{\n		$zem_contact_nonce = $nonce;\n	}\n\n	elseif (!$show_error or $show_input)\n	{\n		$zem_contact_nonce = md5(uniqid(rand(), true));\n		safe_insert(\'txp_discuss_nonce\', \"issue_time = now(), nonce = \'$zem_contact_nonce\'\");\n	}\n\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	if (empty($form))\n	{\n		$form = \'\n<txp:zem_contact_text label=\"\'.zem_contact_gTxt(\'name\').\'\" /><br />\n<txp:zem_contact_email /><br />\'.\n($send_article ? \'<txp:zem_contact_email send_article=\"1\" label=\"\'.zem_contact_gTxt(\'recipient\').\'\" /><br />\' : \'\').\n\'<txp:zem_contact_textarea /><br />\n<txp:zem_contact_submit />\n\';\n	}\n\n	$form = parse($form);\n\n	if ($to_form)\n	{\n		$to = parse(fetch_form($to_form));\n	}\n\n	if (!$to and !$send_article)\n	{\n		return zem_contact_gTxt(\'to_missing\');\n	}\n\n	$out = \'\';\n\n	if (!$zem_contact_submit) {\n	  # don\'t show errors or send mail\n	}\n\n	elseif (!empty($zem_contact_error))\n	{\n		if ($show_error or !$show_input)\n		{\n			$out .= n.\'<ul class=\"zemError\">\';\n\n			foreach (array_unique($zem_contact_error) as $error)\n			{\n				$out .= n.t.\'<li>\'.$error.\'</li>\';\n			}\n\n			$out .= n.\'</ul>\';\n\n			if (!$show_input) return $out;\n		}\n	}\n\n	elseif ($show_input and is_array($zem_contact_form))\n	{\n		/// load and check spam plugins/\n		callback_event(\'zemcontact.submit\');\n		$evaluation =& get_zemcontact_evaluator();\n		$clean = $evaluation->get_zemcontact_status();\n		if ($clean != 0) {\n			return zem_contact_gTxt(\'spam\');\n		}\n\n		if ($from_form)\n		{\n			$from = parse(fetch_form($from_form));\n		}\n\n		if ($subject_form)\n		{\n			$subject = parse(fetch_form($subject_form));\n		}\n\n		$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n\n		$msg = array();\n\n		foreach ($zem_contact_labels as $name => $label)\n		{\n			if (!trim($zem_contact_values[$name])) continue;\n			$msg[] = $label.\': \'.$zem_contact_values[$name];\n		}\n\n		if ($send_article)\n		{\n			global $thisarticle;\n			$subject = str_replace(\'&#38;\', \'&\', $thisarticle[\'title\']);\n			$msg[] = permlinkurl($thisarticle);\n			$msg[] = $subject;\n			$s_ar = array(\'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8217;\', \'&#8242;\', \'&#8243;\', \'&#8230;\', \'&#8211;\', \'&#8212;\', \'&#215;\', \'&#8482;\', \'&#174;\', \'&#169;\', \'&lt;\', \'&gt;\', \'&quot;\', \'&amp;\', \'&#38;\', \"\\t\", \'<p\');\n			if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\')) {\n				$r_ar = array(\"\'\", \"\'\", \'\"\', \'\"\', \"\'\", \"\'\", \'\"\', \'...\', \'-\', \'--\', \'x\', \'[tm]\', \'(r)\', \'(c)\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n			}\n			else\n			{\n				$r_ar = array(\'‘\', \'’\', \'“\', \'”\', \'’\', \'′\', \'″\', \'…\', \'–\', \'—\', \'×\', \'™\', \'®\', \'©\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n			}\n			$msg[] = trim(strip_tags(str_replace($s_ar,$r_ar,(trim(strip_tags($thisarticle[\'excerpt\'])) ? $thisarticle[\'excerpt\'] : $thisarticle[\'body\']))));\n			if (empty($zem_contact_recipient))\n			{\n				return zem_contact_gTxt(\'field_missing\', zem_contact_gTxt(\'recipient\'));\n			}\n			else\n			{\n				$to = $zem_contact_recipient;\n			}\n		}\n\n		$msg = join(\"\\n\\n\", $msg);\n		$msg = str_replace(\"\\r\\n\", \"\\n\", $msg);\n		$msg = str_replace(\"\\r\", \"\\n\", $msg);\n		$msg = str_replace(\"\\n\", $sep, $msg);\n\n		if ($from)\n		{\n			$reply = $zem_contact_from;\n		}\n\n		else\n		{\n			$from = $zem_contact_from;\n			$reply = \'\';\n		}\n\n		$from    = zem_contact_strip($from);\n		$to      = zem_contact_strip($to);\n		$subject = zem_contact_strip($subject);\n		$reply   = zem_contact_strip($reply);\n		$msg     = zem_contact_strip($msg, FALSE);\n\n		if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\'))\n		{\n			$charset = \'ISO-8859-1\';\n			$subject = utf8_decode($subject);\n			$msg     = utf8_decode($msg);\n		}\n\n		else\n		{\n			$charset = \'UTF-8\';\n		}\n\n		$subject = zem_contact_mailheader($subject, \'text\');\n\n		$headers = \'From: \'.$from.\n			($reply ? ($sep.\'Reply-To: \'.$reply) : \'\').\n			$sep.\'X-Mailer: Textpattern (zem_contact_reborn)\'.\n			$sep.\'X-Originating-IP: \'.zem_contact_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'].\' via \' : \'\').$_SERVER[\'REMOTE_ADDR\']).\n			$sep.\'Content-Transfer-Encoding: 8bit\'.\n			$sep.\'Content-Type: text/plain; charset=\"\'.$charset.\'\"\';\n\n		safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = now()\", \"nonce = \'$nonce\'\");\n\n		if (mail($to, $subject, $msg, $headers))\n		{\n			$_POST = array();\n\n			if ($copysender and $zem_contact_from)\n			{\n				mail(zem_contact_strip($zem_contact_from), $subject, $msg, $headers);\n			}\n\n			if ($redirect)\n			{\n				while (@ob_end_clean());\n				$uri = hu.ltrim($redirect,\'/\');\n				if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\']))\n				{\n					txp_status_header(\'303 See Other\');\n					header(\'Location: \'.$uri);\n					header(\'Connection: close\');\n					header(\'Content-Length: 0\');\n				}\n				else\n				{\n					$uri = htmlspecialchars($uri);\n					$refresh = zem_contact_gTxt(\'refresh\');\n					echo <<<END\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n	<title>$sitename</title>\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n	<meta http-equiv=\"refresh\" content=\"0;url=$uri\" />\n</head>\n<body>\n<a href=\"$uri\">$refresh</a>\n</body>\n</html>\nEND;\n				}\n				exit;\n			}\n\n			else\n			{\n				return \'<div class=\"zemThanks\" id=\"zcr\'.$zem_contact_form_id.\'\">\' .\n					($thanks_form ? fetch_form($thanks_form) : $thanks) .\n					\'</div>\';\n			}\n		}\n\n		else\n		{\n			$out .= graf(zem_contact_gTxt(\'mail_sorry\'));\n		}\n	}\n\n	if ($show_input and !$send_article or gps(\'zem_contact_send_article\'))\n	{\n		return \'<form method=\"post\"\'.((!$show_error and $zem_contact_error) ? \'\' : \' id=\"zcr\'.$zem_contact_form_id.\'\"\').\' class=\"zemContactForm\" action=\"\'.htmlspecialchars(serverSet(\'REQUEST_URI\')).\'#zcr\'.$zem_contact_form_id.\'\">\'.\n			( $label ? n.\'<fieldset>\' : n.\'<div>\' ).\n			( $label ? n.\'<legend>\'.htmlspecialchars($label).\'</legend>\' : \'\' ).\n			$out.\n			n.\'<input type=\"hidden\" name=\"zem_contact_nonce\" value=\"\'.$zem_contact_nonce.\'\" />\'.\n			n.\'<input type=\"hidden\" name=\"zem_contact_form_id\" value=\"\'.$zem_contact_form_id.\'\" />\'.\n			$form.\n			callback_event(\'zemcontact.form\').\n			( $label ? (n.\'</fieldset>\') : (n.\'</div>\') ).\n			n.\'</form>\';\n	}\n\n	return \'\';\n}\n\nfunction zem_contact_strip($str, $header = TRUE) {\n	if ($header) $str = strip_rn($str);\n	return preg_replace(\'/[\\x00]/\', \' \', $str);\n}\n\nfunction zem_contact_text($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> br,\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'text\'),\n		\'max\'		=> 100,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'size\'		=> \'\'\n	), $atts));\n\n	$min = intval($min);\n	$max = intval($max);\n	$size = intval($size);\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = trim(ps($name));\n		$utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\n		$hlabel = htmlspecialchars($label);\n\n		if (strlen($value))\n		{\n			if (!$utf8len)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_utf8\', $hlabel);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($min and $utf8len < $min)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'min_warning\', $hlabel, $min);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($max and $utf8len > $max)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'max_warning\', $hlabel, $max);\n				$isError = \"errorElement\";\n				#$value = join(\'\',array_slice($ar[0],0,$max));\n			}\n\n			else\n			{\n				zem_contact_store($name, $label, $value);\n			}\n		}\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', $hlabel);\n			$isError = \"errorElement\";\n		}\n	}\n\n	else\n	{\n		$value = $default;\n	}\n\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\n	$maxlength = ($max) ? \' maxlength=\"\'.$max.\'\"\' : \'\';\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n        return \'<label for=\"\'.$name.\'\" class=\"zemText \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		\'<input type=\"text\" id=\"\'.$name.\'\" class=\"zemText \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\" value=\"\'.htmlspecialchars($value).\'\"\'.$size.$maxlength.\' />\';\n}\n\nfunction zem_contact_textarea($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> br,\n		\'cols\'		=> 58,\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'message\'),\n		\'max\'		=> 10000,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'rows\'		=> 8\n	), $atts));\n\n	$min = intval($min);\n	$max = intval($max);\n	$cols = intval($cols);\n	$rows = intval($rows);\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\n		$hlabel = htmlspecialchars($label);\n\n		if (strlen(ltrim($value)))\n		{\n			if (!$utf8len)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_utf8\', $hlabel);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($min and $utf8len < $min)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'min_warning\', $hlabel, $min);\n				$isError = \"errorElement\";\n			}\n\n			elseif ($max and $utf8len > $max)\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'max_warning\', $hlabel, $max);\n				$isError = \"errorElement\";\n				#$value = join(\'\',array_slice($utf8ar[0],0,$max));\n			}\n\n			else\n			{\n				zem_contact_store($name, $label, $value);\n			}\n		}\n\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', $hlabel);\n			$isError = \"errorElement\";\n		}\n	}\n\n	else\n	{\n		$value = $default;\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<label for=\"\'.$name.\'\" class=\"zemTextarea \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		\'<textarea id=\"\'.$name.\'\" class=\"zemTextarea \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\" cols=\"\'.$cols.\'\" rows=\"\'.$rows.\'\">\'.htmlspecialchars($value).\'</textarea>\';\n}\n\nfunction zem_contact_email($atts)\n{\n	global $zem_contact_error, $zem_contact_submit, $zem_contact_from, $zem_contact_recipient;\n\n	extract(zem_contact_lAtts(array(\n		\'default\'	=> \'\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'email\'),\n		\'max\'		=> 100,\n		\'min\'		=> 0,\n		\'name\'		=> \'\',\n		\'required\'	=> 1,\n		\'break\'		=> br,\n		\'size\'		=> \'\',\n		\'send_article\'	=> 0\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	$email = $zem_contact_submit ? trim(ps($name)) : $default;\n\n	if ($zem_contact_submit and strlen($email))\n	{\n		if (!is_valid_email($email))\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'invalid_email\', htmlspecialchars($email));\n			$isError = \"errorElement\";\n		}\n\n		else\n		{\n			preg_match(\"/@(.+)$/\", $email, $match);\n			$domain = $match[1];\n\n			if (is_callable(\'checkdnsrr\') and checkdnsrr(\'textpattern.com.\',\'A\') and !checkdnsrr($domain.\'.\',\'MX\') and !checkdnsrr($domain.\'.\',\'A\'))\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_host\', htmlspecialchars($domain));\n				$isError = \"errorElement\";\n			}\n\n			else\n			{\n				if ($send_article) {\n					$zem_contact_recipient = $email;\n				}\n				else {\n					$zem_contact_from = $email;\n				}\n			}\n		}\n	}\n\n	return zem_contact_text(array(\n		\'default\'	=> $email,\n		\'isError\'	=> $isError,\n		\'label\'		=> $label,\n		\'max\'		=> $max,\n		\'min\'		=> $min,\n		\'name\'		=> $name,\n		\'required\'	=> $required,\n		\'break\'		=> $break,\n		\'size\'		=> $size\n	));\n}\n\nfunction zem_contact_select($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'name\'		=> \'\',\n		\'break\'		=> \' \',\n		\'delimiter\'	=> \',\',\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'option\'),\n		\'list\'		=> zem_contact_gTxt(\'general_inquiry\'),\n		\'required\'	=> 1,\n		\'selected\'	=> \'\'\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	$list = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$list)));\n\n	if ($zem_contact_submit)\n	{\n		$value = trim(ps($name));\n\n		if (strlen($value))\n		{\n			if (in_array($value, $list))\n			{\n				zem_contact_store($name, $label, $value);\n			}\n\n			else\n			{\n				$zem_contact_error[] = zem_contact_gTxt(\'invalid_value\', htmlspecialchars($label), htmlspecialchars($value));\n				$isError = \"errorElement\";\n			}\n		}\n\n		elseif ($required)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', htmlspecialchars($label));\n			$isError = \"errorElement\";\n		}\n	}\n	else\n	{\n		$value = $selected;\n	}\n\n	$out = \'\';\n\n	foreach ($list as $item)\n	{\n		$out .= n.t.\'<option\'.($item == $value ? \' selected=\"selected\">\' : \'>\').(strlen($item) ? htmlspecialchars($item) : \' \').\'</option>\';\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<label for=\"\'.$name.\'\" class=\"zemSelect \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\n		n.\'<select id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"zemSelect \'.$zemRequired.$isError.\'\">\'.\n			$out.\n		n.\'</select>\';\n}\n\nfunction zem_contact_checkbox($atts)\n{\n	global $zem_contact_error, $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> \' \',\n		\'checked\'	=> 0,\n		\'isError\'	=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'checkbox\'),\n		\'name\'		=> \'\',\n		\'required\'	=> 1\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if ($zem_contact_submit)\n	{\n		$value = (bool) ps($name);\n\n		if ($required and !$value)\n		{\n			$zem_contact_error[] = zem_contact_gTxt(\'field_missing\', htmlspecialchars($label));\n			$isError = \"errorElement\";\n		}\n\n		else\n		{\n			zem_contact_store($name, $label, $value ? gTxt(\'yes\') : gTxt(\'no\'));\n		}\n	}\n\n	else {\n		$value = $checked;\n	}\n\n	$zemRequired = $required ? \'zemRequired\' : \'\';\n\n	return \'<input type=\"checkbox\" id=\"\'.$name.\'\" class=\"zemCheckbox \'.$zemRequired.$isError.\'\" name=\"\'.$name.\'\"\'.\n		($value ? \' checked=\"checked\"\' : \'\').\' />\'.$break.\n		\'<label for=\"\'.$name.\'\" class=\"zemCheckbox \'.$zemRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\n}\n\nfunction zem_contact_serverinfo($atts)\n{\n	global $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'label\'		=> \'\',\n		\'name\'		=> \'\'\n	), $atts));\n\n	if (empty($name)) $name = zem_contact_label2name($label);\n\n	if (strlen($name) and $zem_contact_submit)\n	{\n		if (!$label) $label = $name;\n		zem_contact_store($name, $label, serverSet($name));\n	}\n}\n\nfunction zem_contact_secret($atts, $thing = \'\')\n{\n	global $zem_contact_submit;\n\n	extract(zem_contact_lAtts(array(\n		\'name\'	=> \'\',\n		\'label\'	=> zem_contact_gTxt(\'secret\'),\n		\'value\'	=> \'\'\n	), $atts));\n\n	$name = zem_contact_label2name($name ? $name : $label);\n\n	if ($zem_contact_submit)\n	{\n		if ($thing) $value = trim(parse($thing));\n		zem_contact_store($name, $label, $value);\n	}\n\n	return \'\';\n}\n\nfunction zem_contact_radio($atts)\n{\n	global $zem_contact_error, $zem_contact_submit, $zem_contact_values;\n\n	extract(zem_contact_lAtts(array(\n		\'break\'		=> \' \',\n		\'checked\'	=> 0,\n		\'group\'		=> \'\',\n		\'label\'		=> zem_contact_gTxt(\'option\'),\n		\'name\'		=> \'\'\n	), $atts));\n\n	static $cur_name = \'\';\n	static $cur_group = \'\';\n\n	if (!$name and !$group and !$cur_name and !$cur_group) {\n		$cur_group = zem_contact_gTxt(\'radio\');\n		$cur_name = $cur_group;\n	}\n	if ($group and !$name and $group != $cur_group) $name = $group;\n\n	if ($name) $cur_name = $name;\n	else $name = $cur_name;\n\n	if ($group) $cur_group = $group;\n	else $group = $cur_group;\n\n	$id   = \'q\'.md5($name.\'=>\'.$label);\n	$name = zem_contact_label2name($name);\n\n	if ($zem_contact_submit)\n	{\n		$is_checked = (ps($name) == $id);\n\n		if ($is_checked or $checked and !isset($zem_contact_values[$name]))\n		{\n			zem_contact_store($name, $group, $label);\n		}\n	}\n\n	else\n	{\n		$is_checked = $checked;\n	}\n\n	return \'<input value=\"\'.$id.\'\" type=\"radio\" id=\"\'.$id.\'\" class=\"zemRadio \'.$name.\'\" name=\"\'.$name.\'\"\'.\n		( $is_checked ? \' checked=\"checked\" />\' : \' />\').$break.\n		\'<label for=\"\'.$id.\'\" class=\"zemRadio \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\n}\n\nfunction zem_contact_send_article($atts)\n{\n	if (!isset($_REQUEST[\'zem_contact_send_article\'])) {\n		$linktext = (empty($atts[\'linktext\'])) ? zem_contact_gTxt(\'send_article\') : $atts[\'linktext\'];\n		$join = (empty($_SERVER[\'QUERY_STRING\'])) ? \'?\' : \'&\';\n		$href = $_SERVER[\'REQUEST_URI\'].$join.\'zem_contact_send_article=yes\';\n		return \'<a href=\"\'.htmlspecialchars($href).\'\">\'.htmlspecialchars($linktext).\'</a>\';\n	}\n	return;\n}\n\nfunction zem_contact_submit($atts, $thing)\n{\n	extract(zem_contact_lAtts(array(\n		\'button\'	=> 0,\n		\'label\'		=> zem_contact_gTxt(\'send\')\n	), $atts));\n\n	$label = htmlspecialchars($label);\n\n	if ($button or strlen($thing))\n	{\n		return \'<button type=\"submit\" class=\"zemSubmit\" name=\"zem_contact_submit\" value=\"\'.$label.\'\">\'.($thing ? trim(parse($thing)) : $label).\'</button>\';\n	}\n	else\n	{\n		return \'<input type=\"submit\" class=\"zemSubmit\" name=\"zem_contact_submit\" value=\"\'.$label.\'\" />\';\n	}\n}\n\nfunction zem_contact_lAtts($arr, $atts)\n{\n	foreach(array(\'button\', \'copysender\', \'checked\', \'required\', \'send_article\', \'show_input\', \'show_error\') as $key)\n	{\n		if (isset($atts[$key]))\n		{\n			$atts[$key] = ($atts[$key] === \'yes\' or intval($atts[$key])) ? 1 : 0;\n		}\n	}\n	if (isset($atts[\'break\']) and $atts[\'break\'] == \'br\') $atts[\'break\'] = \'<br />\';\n	return lAtts($arr, $atts);\n}\n\nclass zemcontact_evaluation\n{\n	var $status;\n\n	function zemcontact_evaluation() {\n		$this->status = 0;\n	}\n\n	function add_zemcontact_status($check) {\n		$this->status += $check;\n	}\n\n	function get_zemcontact_status() {\n		return $this->status;\n	}\n}\n\nfunction &get_zemcontact_evaluator()\n{\n	static $instance;\n\n	if(!isset($instance)) {\n		$instance = new zemcontact_evaluation();\n	}\n	return $instance;\n}\n\nfunction zem_contact_label2name($label)\n{\n	$label = trim($label);\n	if (strlen($label) == 0) return \'invalid\';\n	if (strlen($label) <= 32 and preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) return $label;\n	else return \'q\'.md5($label);\n}\n\nfunction zem_contact_store($name, $label, $value)\n{\n	global $zem_contact_form, $zem_contact_labels, $zem_contact_values;\n	$zem_contact_form[$label] = $value;\n	$zem_contact_labels[$name] = $label;\n	$zem_contact_values[$name] = $value;\n}\n\nfunction zem_contact_mailheader($string, $type)\n{\n	global $prefs;\n	if (!strstr($string,\'=?\') and !preg_match(\'/[\\x00-\\x1F\\x7F-\\xFF]/\', $string)) {\n		if (\"phrase\" == $type) {\n			if (preg_match(\'/[][()<>@,;:\".\\x5C]/\', $string)) {\n				$string = \'\"\'. strtr($string, array(\"\\\\\" => \"\\\\\\\\\", \'\"\' => \'\\\"\')) . \'\"\';\n			}\n		}\n		elseif (\"text\" != $type) {\n			trigger_error(\'Unknown encode_mailheader type\', E_USER_WARNING);\n		}\n		return $string;\n	}\n	if ($prefs[\'override_emailcharset\']) {\n		$start = \'=?ISO-8859-1?B?\';\n		$pcre  = \'/.{1,42}/s\';\n	}\n	else {\n		$start = \'=?UTF-8?B?\';\n		$pcre  = \'/.{1,45}(?=[\\x00-\\x7F\\xC0-\\xFF]|$)/s\';\n	}\n	$end = \'?=\';\n	$sep = is_windows() ? \"\\r\\n\" : \"\\n\";\n	preg_match_all($pcre, $string, $matches);\n	return $start . join($end.$sep.\' \'.$start, array_map(\'base64_encode\',$matches[0])) . $end;\n}\n', '5eea1fd89317925ef1a8e2e2bc2be4f8', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rvm_latin1_to_utf8', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.5', 'Alter pre-MySQL 4.1 tables to use UTF-8 charset', '<h1>latin1 to utf8</h1>\n\n<p><strong><span class=\"caps\">WARNING</span>: carefully read this documentation and <span class=\"caps\">BACKUP</span> <span class=\"caps\">YOUR</span> <span class=\"caps\">DATABASE</span> before activating/using this plugin.</strong></p>\n\n<p>Textpattern always uses &#8216;utf8&#8217; to store data in the database, but depending on which MySQL version you used when installing Textpattern, the character set of the tables and columns differs:</p>\n\n<ol>\n	<li>MySQL versions older than 4.1: the character set of the tables created by Textpattern is typically &#8216;latin1&#8217;.</li>\n	<li>MySQL versions 4.1 and higher: the tables are set to use the &#8216;utf8&#8217; character set, which matches the actual character set of the data stored in those tables.</li>\n</ol>\n\n<p>Having the tables/columns themselves use the same character set as the data stored in them has some advantages when performing searches or sorting in Textpattern, especially if you use characters outside US-<span class=\"caps\">ASCII</span>.</p>\n\n<p>If you upgrade your MySQL version from pre-4.1 to 4.1 or higher, the table/column character sets are not automatically converted to &#8216;utf8&#8217;, so they stay &#8216;latin1&#8217; while the data stored in those tables is still &#8216;utf8&#8217;.</p>\n\n<p>Changing this is tricky, because if you simply change the character set, MySQL assumes that the stored data must also be converted from &#8216;latin1&#8217; to &#8216;utf8&#8217;, which leads to garbage, because the data is already &#8216;utf8&#8217;.</p>\n\n<p>This plugin avoids the unwanted character set conversion of the stored data by taking the following steps for each Textpattern table:</p>\n\n<ol>\n	<li>drop indexes</li>\n	<li>convert all columns that contain text to use a &#8216;binary&#8217; charset (instead of &#8216;latin1&#8217;)</li>\n	<li>convert those columns yet again, this time to &#8216;utf8&#8217; charset (instead of &#8216;binary&#8217;)</li>\n	<li>add indexes</li>\n	<li>optimize table</li>\n</ol>\n\n<p>Afterwards an attempt is made to update the &#8216;dbcharset&#8217; setting in the <code>textpattern/config.php</code> file to &#8216;utf8&#8217;. Should this fail due to lack of permissions, you will be asked to make this change yourself. Do not skip this step, otherwise you will experience irreparable data corruption so severe that you will cry like a baby (you made backups, right?), much to the amusement of onlookers.</p>\n\n<p>How to use this plugin:</p>\n\n<ol>\n	<li><strong><span class=\"caps\">MAKE</span> <span class=\"caps\">BACKUPS</span> <span class=\"caps\">FIRST</span>!</strong> (not just the <span class=\"caps\">TXP</span> tables, but the entire database)</li>\n	<li>Make sure you have MySQL version 4.1 or higher installed.</li>\n	<li>Activate the plugin.</li>\n	<li><a href=\"?event=rvm_latin1_to_utf8\">Click here</a> and follow the instructions on the screen.</li>\n</ol>', '\nif (txpinterface == \'admin\' and gps(\'event\') == \'rvm_latin1_to_utf8\')\n{\n  add_privs(\'rvm_latin1_to_utf8\', \'1\');\n  register_tab(\'extensions\', \'rvm_latin1_to_utf8\', \'latin1_to_utf8\');\n  register_callback(\'rvm_latin1_to_utf8\', \'rvm_latin1_to_utf8\');\n}\n\nfunction rvm_latin1_to_utf8()\n{\n  global $txpcfg, $DB;\n\n  $debug = FALSE;\n\n  require_privs(\'rvm_latin1_to_utf8\');\n\n  pagetop(\'\');\n\n  echo \'<div style=\"margin-left: 50px\">\';\n\n  if (version_compare(mysqli_get_server_info($DB->link), \'4.1.0\', \'<\'))\n  {\n    echo graf(\'The rvm_latin1_to_utf8 plugin has been removed, because your MySQL version is lower than 4.1.\');\n    safe_delete(\'txp_plugin\', \"name = \'rvm_latin1_to_utf8\'\");\n    rvm_latin1_to_utf8_end();\n  }\n\n  # standard TXP tables\n  $txptables = array(\n    \'textpattern\',\n    \'txp_category\',\n    \'txp_css\',\n    \'txp_discuss\',\n    \'txp_discuss_ipban\',\n    \'txp_discuss_nonce\',\n    \'txp_file\',\n    \'txp_form\',\n    \'txp_image\',\n    \'txp_lang\',\n    \'txp_link\',\n    \'txp_log\',\n    \'txp_page\',\n    \'txp_plugin\',\n    \'txp_prefs\',\n    \'txp_priv\',\n    \'txp_section\',\n    \'txp_token\',\n    \'txp_users\'\n  );\n\n  # find tables that match the TXP table prefix, mark standard and selected ones.\n  $mytables = ps(\'mytables\', array()) ? ps(\'mytables\') : array();\n  $unknown  = FALSE;\n\n  if ($rs = safe_query(\"SHOW TABLES LIKE \'\".addcslashes(doSlash(PFX), \'%_\').\"%\'\"))\n  {\n    while ($row = mysqli_fetch_row($rs))\n    {\n      $table = substr($row[0], strlen(PFX));\n\n      if (in_array($table, $txptables) or in_array($table, $mytables))\n      {\n        $tables[] = $table;\n      }\n      else\n      {\n        $unknown = TRUE;\n      }\n\n      $inputs[] =\n        \'<label>\'.\n          \'<input type=\"checkbox\" name=\"mytables[]\" value=\"\'.htmlspecialchars($table).\'\" \'.\n            (in_array($table, $txptables) ? \'disabled=\"disabled\" checked=\"checked\"\' : \'\').\' />\'.\n          htmlspecialchars(PFX.$table).\n        \'</label>\';\n    }\n  }\n  else\n  {\n    echo graf(\'Hmmm... strange, I cannot find any tables that match your TXP table prefix\');\n  }\n\n  # ask user what to do with tables we\'re not sure about.\n  if ($unknown and !ps(\'continue\'))\n  {\n    echo\n      graf(\'Below, a list of tables is shown that may be part of your Textpattern install.\').\n      graf(\'Some of these tables may have been created by plugins you have installed.<br />\n        In that case, check the tables that these plugins have added.<br />\n        Be careful not to check tables that are not related to this Textpattern install!\').\n      graf(\'The standard Textpattern tables are already checked.\').\n      form(\n        join(\'<br />\', $inputs).\n        graf(\n          eInput(\'rvm_latin1_to_utf8\').\'<br />\'.\n          fInput(\'submit\', \'continue\', \'Continue\', \'publish\')\n        )\n      );\n\n    rvm_latin1_to_utf8_end();\n  }\n\n  echo graf(\'Updating Textpattern tables...\');\n\n  # loop through tables\n  foreach ($tables as $table)\n  {\n    $indexes = array();\n    $drop    = array();\n    $create  = array();\n    $bin     = array();\n    $utf8    = array();\n    $cols    = array();\n\n    # prepare alter statements for columns\n    $columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx($table));\n\n    if ($columns) foreach ($columns as $column)\n    {\n      extract($column);\n\n      if (preg_match(\'/^(char|varchar|tinytext|text|mediumtext|longtext|enum|set)\\b/\', $Type))\n      {\n        $Null = ($Null == \'YES\' ? \' NULL\' : \' NOT NULL\');\n\n        $begin = \'MODIFY `\'.doSlash($Field).\'` \'.$Type.\' CHARACTER SET \';\n        $end   = $Null.\" DEFAULT \'\".doSlash($Default).\"\'\";\n\n        $bin[]  = $begin.\'binary\'.$end;\n        $utf8[] = $begin.\'utf8 COLLATE utf8_general_ci\'.$end;\n        $cols[] = $Field;\n      }\n    }\n\n    # analyse index structures\n    $rows = getRows(\'SHOW INDEX FROM \'.safe_pfx($table));\n\n    if ($rows) foreach ($rows as $row)\n    {\n      extract($row);\n\n      if ($Key_name == \'PRIMARY\' or !in_array($Column_name, $cols))\n      {\n        continue;\n      }\n\n      $Key_name = \'`\'.$Key_name.\'`\';\n\n      $indexes[$Key_name][\'unique\'] = ($Non_unique ? \'\' : \' UNIQUE\');\n      $indexes[$Key_name][\'fulltext\'] = ($Index_type == \'FULLTEXT\' ? \' FULLTEXT\' : \'\');\n      $indexes[$Key_name][\'columns\'][$Seq_in_index] = \'`\'.$Column_name.\'`\'.($Sub_part ? \' (\'.$Sub_part.\')\' : \'\');\n    }\n\n    # prepare index drop/create statements\n    foreach ($indexes as $index => $structure)\n    {\n      extract($structure);\n\n      $drop[]   = \'DROP INDEX \'.$index;\n      $create[] = \'ADD\'.$unique.$fulltext.\' INDEX \'.$index.\' (\'.join(\', \', $columns).\')\';\n    }\n\n    $success = TRUE;\n\n    # alter the table + indexes\n    if ($drop)\n    {\n      $success *= safe_alter($table, join(\', \', $drop), $debug);\n    }\n\n    if ($bin and $success)\n    {\n      $success *= safe_alter($table, join(\', \', $bin), $debug);\n\n      if ($success)\n      {\n        $success *= safe_alter($table, join(\', \', $utf8).\', DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n      }\n    }\n\n    if ($create and $success)\n    {\n      $success *= safe_alter($table, join(\', \', $create), $debug);\n    }\n  }\n\n  # while we\'re here, might as well optimize the tables\n  safe_query(\"OPTIMIZE TABLE \".join(\', \', array_map(\'safe_pfx\', $tables)), $debug);\n\n  if ($success)\n  {\n    echo graf(\'Successfully completed.\');\n\n    $file   = txpath.\'/config.php\';\n    $config = file_get_contents($file);\n    $config = str_replace(\'?>\', n.\'# rvm_latin1_to_utf8 plugin added the following line to ensure a correct dbcharset\'.n.\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\'\'.n.\'?>\', $config);\n\n    if ($txpcfg[\'dbcharset\'] == \'utf8\'\n      or is_writable($file)\n      and $handle = fopen($file, \'w\')\n      and fwrite($handle, $config) !== FALSE\n      and fclose($handle))\n    {\n      echo graf(\'The rvm_latin1_to_utf8 plugin has been automatically de-installed.\');\n      safe_delete(\'txp_plugin\', \"name = \'rvm_latin1_to_utf8\'\");\n    }\n    else\n    {\n      echo graf(\'<b>WARNING: textpattern/config.php could not be updated automatically. Please update your textpattern/config.php file to contain <code>$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\';</code></b> and deinstall the rvm_latin1_to_utf8 plugin manually\');\n    }\n  }\n  else\n  {\n    echo graf(\'Due to one or more errors the tables could not be updated correctly. You may have to restore a backup.\');\n  }\n\n  rvm_latin1_to_utf8_end();\n}\n\nfunction rvm_latin1_to_utf8_end()\n{\n  echo \'</div>\';\n  end_page();\n  exit();\n}\n', '\nif (txpinterface == \'admin\' and gps(\'event\') == \'rvm_latin1_to_utf8\')\n{\n  add_privs(\'rvm_latin1_to_utf8\', \'1\');\n  register_tab(\'extensions\', \'rvm_latin1_to_utf8\', \'latin1_to_utf8\');\n  register_callback(\'rvm_latin1_to_utf8\', \'rvm_latin1_to_utf8\');\n}\n\nfunction rvm_latin1_to_utf8()\n{\n  global $txpcfg, $DB;\n\n  $debug = FALSE;\n\n  require_privs(\'rvm_latin1_to_utf8\');\n\n  pagetop(\'\');\n\n  echo \'<div style=\"margin-left: 50px\">\';\n\n  if (version_compare(mysqli_get_server_info($DB->link), \'4.1.0\', \'<\'))\n  {\n    echo graf(\'The rvm_latin1_to_utf8 plugin has been removed, because your MySQL version is lower than 4.1.\');\n    safe_delete(\'txp_plugin\', \"name = \'rvm_latin1_to_utf8\'\");\n    rvm_latin1_to_utf8_end();\n  }\n\n  # standard TXP tables\n  $txptables = array(\n    \'textpattern\',\n    \'txp_category\',\n    \'txp_css\',\n    \'txp_discuss\',\n    \'txp_discuss_ipban\',\n    \'txp_discuss_nonce\',\n    \'txp_file\',\n    \'txp_form\',\n    \'txp_image\',\n    \'txp_lang\',\n    \'txp_link\',\n    \'txp_log\',\n    \'txp_page\',\n    \'txp_plugin\',\n    \'txp_prefs\',\n    \'txp_priv\',\n    \'txp_section\',\n    \'txp_token\',\n    \'txp_users\'\n  );\n\n  # find tables that match the TXP table prefix, mark standard and selected ones.\n  $mytables = ps(\'mytables\', array()) ? ps(\'mytables\') : array();\n  $unknown  = FALSE;\n\n  if ($rs = safe_query(\"SHOW TABLES LIKE \'\".addcslashes(doSlash(PFX), \'%_\').\"%\'\"))\n  {\n    while ($row = mysqli_fetch_row($rs))\n    {\n      $table = substr($row[0], strlen(PFX));\n\n      if (in_array($table, $txptables) or in_array($table, $mytables))\n      {\n        $tables[] = $table;\n      }\n      else\n      {\n        $unknown = TRUE;\n      }\n\n      $inputs[] =\n        \'<label>\'.\n          \'<input type=\"checkbox\" name=\"mytables[]\" value=\"\'.htmlspecialchars($table).\'\" \'.\n            (in_array($table, $txptables) ? \'disabled=\"disabled\" checked=\"checked\"\' : \'\').\' />\'.\n          htmlspecialchars(PFX.$table).\n        \'</label>\';\n    }\n  }\n  else\n  {\n    echo graf(\'Hmmm... strange, I cannot find any tables that match your TXP table prefix\');\n  }\n\n  # ask user what to do with tables we\'re not sure about.\n  if ($unknown and !ps(\'continue\'))\n  {\n    echo\n      graf(\'Below, a list of tables is shown that may be part of your Textpattern install.\').\n      graf(\'Some of these tables may have been created by plugins you have installed.<br />\n        In that case, check the tables that these plugins have added.<br />\n        Be careful not to check tables that are not related to this Textpattern install!\').\n      graf(\'The standard Textpattern tables are already checked.\').\n      form(\n        join(\'<br />\', $inputs).\n        graf(\n          eInput(\'rvm_latin1_to_utf8\').\'<br />\'.\n          fInput(\'submit\', \'continue\', \'Continue\', \'publish\')\n        )\n      );\n\n    rvm_latin1_to_utf8_end();\n  }\n\n  echo graf(\'Updating Textpattern tables...\');\n\n  # loop through tables\n  foreach ($tables as $table)\n  {\n    $indexes = array();\n    $drop    = array();\n    $create  = array();\n    $bin     = array();\n    $utf8    = array();\n    $cols    = array();\n\n    # prepare alter statements for columns\n    $columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx($table));\n\n    if ($columns) foreach ($columns as $column)\n    {\n      extract($column);\n\n      if (preg_match(\'/^(char|varchar|tinytext|text|mediumtext|longtext|enum|set)\\b/\', $Type))\n      {\n        $Null = ($Null == \'YES\' ? \' NULL\' : \' NOT NULL\');\n\n        $begin = \'MODIFY `\'.doSlash($Field).\'` \'.$Type.\' CHARACTER SET \';\n        $end   = $Null.\" DEFAULT \'\".doSlash($Default).\"\'\";\n\n        $bin[]  = $begin.\'binary\'.$end;\n        $utf8[] = $begin.\'utf8 COLLATE utf8_general_ci\'.$end;\n        $cols[] = $Field;\n      }\n    }\n\n    # analyse index structures\n    $rows = getRows(\'SHOW INDEX FROM \'.safe_pfx($table));\n\n    if ($rows) foreach ($rows as $row)\n    {\n      extract($row);\n\n      if ($Key_name == \'PRIMARY\' or !in_array($Column_name, $cols))\n      {\n        continue;\n      }\n\n      $Key_name = \'`\'.$Key_name.\'`\';\n\n      $indexes[$Key_name][\'unique\'] = ($Non_unique ? \'\' : \' UNIQUE\');\n      $indexes[$Key_name][\'fulltext\'] = ($Index_type == \'FULLTEXT\' ? \' FULLTEXT\' : \'\');\n      $indexes[$Key_name][\'columns\'][$Seq_in_index] = \'`\'.$Column_name.\'`\'.($Sub_part ? \' (\'.$Sub_part.\')\' : \'\');\n    }\n\n    # prepare index drop/create statements\n    foreach ($indexes as $index => $structure)\n    {\n      extract($structure);\n\n      $drop[]   = \'DROP INDEX \'.$index;\n      $create[] = \'ADD\'.$unique.$fulltext.\' INDEX \'.$index.\' (\'.join(\', \', $columns).\')\';\n    }\n\n    $success = TRUE;\n\n    # alter the table + indexes\n    if ($drop)\n    {\n      $success *= safe_alter($table, join(\', \', $drop), $debug);\n    }\n\n    if ($bin and $success)\n    {\n      $success *= safe_alter($table, join(\', \', $bin), $debug);\n\n      if ($success)\n      {\n        $success *= safe_alter($table, join(\', \', $utf8).\', DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n      }\n    }\n\n    if ($create and $success)\n    {\n      $success *= safe_alter($table, join(\', \', $create), $debug);\n    }\n  }\n\n  # while we\'re here, might as well optimize the tables\n  safe_query(\"OPTIMIZE TABLE \".join(\', \', array_map(\'safe_pfx\', $tables)), $debug);\n\n  if ($success)\n  {\n    echo graf(\'Successfully completed.\');\n\n    $file   = txpath.\'/config.php\';\n    $config = file_get_contents($file);\n    $config = str_replace(\'?>\', n.\'# rvm_latin1_to_utf8 plugin added the following line to ensure a correct dbcharset\'.n.\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\'\'.n.\'?>\', $config);\n\n    if ($txpcfg[\'dbcharset\'] == \'utf8\'\n      or is_writable($file)\n      and $handle = fopen($file, \'w\')\n      and fwrite($handle, $config) !== FALSE\n      and fclose($handle))\n    {\n      echo graf(\'The rvm_latin1_to_utf8 plugin has been automatically de-installed.\');\n      safe_delete(\'txp_plugin\', \"name = \'rvm_latin1_to_utf8\'\");\n    }\n    else\n    {\n      echo graf(\'<b>WARNING: textpattern/config.php could not be updated automatically. Please update your textpattern/config.php file to contain <code>$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\';</code></b> and deinstall the rvm_latin1_to_utf8 plugin manually\');\n    }\n  }\n  else\n  {\n    echo graf(\'Due to one or more errors the tables could not be updated correctly. You may have to restore a backup.\');\n  }\n\n  rvm_latin1_to_utf8_end();\n}\n\nfunction rvm_latin1_to_utf8_end()\n{\n  echo \'</div>\';\n  end_page();\n  exit();\n}\n', 'fdb4db72916ef3b1eb5a14dc46b34212', '', '', 1, 5, 0),
('rvm_counter', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.2', 'Auto-incrementing counter(s)', ' <style>\n   li code {font-weight: bold;}\n   pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\n   h1, h2, h3, h3 code {font-family: sans-serif; font-weight: bold;}\n   h1, h2, h3 {margin-left: -1em;}\n   h2, h3 {margin-top: 2em;}\n   h1 {font-size: 3em;}\n   h2 {font-size: 2em;}\n   h3 {font-size: 1.5em;}\n   li a code {font-weight: normal;}\n   .required, .warning {color:red;}\n </style>\n\n<h1>rvm_counter</h1>\n\n<p>This plugin provides the following tag(s):<br />\n<ul>\n	<li><a href=\"#rvm_counter\"> <code>&lt;txp:rvm_counter /&gt;</code> </a></li><br />\n</ul></p>\n\n\n<h3 id=\"rvm_counter\"><code>&lt;txp:rvm_counter /&gt;</code></h3>\n\n<p>Returns a number that is incremented each time this tag is called. Counter values are stored in the txp_discuss_nonce table without conflicting with comment nonces.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code> <br />\nExplicit name (max 20 chars) for this counter to differentiate it from another counter. Default is an empty string.</li>\n	<li><code>offset=&quot;integer&quot;</code> <br />\nPositive or negative offset for the counter. This is added to the actual counter value before returning it. Default is 0.</li>\n	<li><code>reset=&quot;integer&quot;</code> <br />\nReset the counter to the specified value upon the next call of this counter tag. In most cases using the &#8216;offset&#8217; attribute is easier.</li>\n	<li><code>step=&quot;integer&quot;</code> <br />\nPositive or negative counter step size. Default is set to &#8216;1&#8217;, meaning the counter is increased by 1 each time it is called.</li>\n	<li><code>width=&quot;integer&quot;</code> <br />\nMinimum width of the counter when displayed. Small counter values are zero-padded (left side) to reach the specified minimum width. Default is unset.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Default, nameless counter:</p>\n\n<pre><code>&lt;txp:rvm_counter /&gt;</code></pre>\n\n<p>A counter named &#8216;example&#8217;:</p>\n\n<pre><code>&lt;txp:rvm_counter name=&quot;example&quot; /&gt;</code></pre>\n\n<p>Reset the &#8216;example&#8217; counter to 10. Note that you have to remove the reset attribute after the counter is reset:</p>\n\n<pre><code>&lt;txp:rvm_counter name=&quot;example&quot; reset=&quot;10&quot; /&gt;</code></pre>\n\n<p>Use an offset of -10. Suppose the counter is currently at 25, then it would display 15:</p>\n\n<pre><code>&lt;txp:rvm_counter offset=&quot;-10&quot; /&gt;</code></pre>\n\n<p>Add left side zero-padding to make the counter value 5 digits wide. If the counter is set to 25, this would display 00025:</p>\n\n<pre><code>&lt;txp:rvm_counter width=&quot;5&quot; /&gt;</code></pre>\n\n<p>Display the current counter value, without changing it:</p>\n\n<pre><code>&lt;txp:rvm_counter step=&quot;0&quot; /&gt;</code></pre>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_counter\');\n}\n\n\nfunction rvm_counter($atts)\n{\n  extract(lAtts(array(\n    \'name\'   => \'\',\n    \'offset\' => \'0\',\n    \'reset\'  => \'\',\n    \'step\'   => \'1\',\n    \'width\'  => \'1\'\n  ), $atts));\n\n  $name  = doSlash(substr(\'rvm_counter_\'.$name, 0, 32));\n  $count = safe_field(\'secret\', \'txp_discuss_nonce\', \"nonce = \'\".$name.\"\'\");\n\n  if ($count === FALSE)\n  {\n    $count = ($reset !== \'\') ? intval($reset) : intval($step);\n    safe_insert(\'txp_discuss_nonce\', \"nonce = \'\".$name.\"\', issue_time = \'9999-12-31\', secret = \'\".$count.\"\'\");\n  }\n  else\n  {\n    $count = ($reset !== \'\') ? intval($reset) : $count + intval($step);\n    safe_update(\'txp_discuss_nonce\', \"secret = \'\".$count.\"\'\", \"nonce = \'\".$name.\"\'\");\n  }\n\n  return sprintf(\'%0\'.intval($width).\'d\', $count + intval($offset));\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_counter\');\n}\n\n\nfunction rvm_counter($atts)\n{\n  extract(lAtts(array(\n    \'name\'   => \'\',\n    \'offset\' => \'0\',\n    \'reset\'  => \'\',\n    \'step\'   => \'1\',\n    \'width\'  => \'1\'\n  ), $atts));\n\n  $name  = doSlash(substr(\'rvm_counter_\'.$name, 0, 32));\n  $count = safe_field(\'secret\', \'txp_discuss_nonce\', \"nonce = \'\".$name.\"\'\");\n\n  if ($count === FALSE)\n  {\n    $count = ($reset !== \'\') ? intval($reset) : intval($step);\n    safe_insert(\'txp_discuss_nonce\', \"nonce = \'\".$name.\"\', issue_time = \'9999-12-31\', secret = \'\".$count.\"\'\");\n  }\n  else\n  {\n    $count = ($reset !== \'\') ? intval($reset) : $count + intval($step);\n    safe_update(\'txp_discuss_nonce\', \"secret = \'\".$count.\"\'\", \"nonce = \'\".$name.\"\'\");\n  }\n\n  return sprintf(\'%0\'.intval($width).\'d\', $count + intval($offset));\n}\n', '984080fca0887d52125ce24a90dad9e5', '', '', 0, 5, 0),
('rvm_css', 0, 'Ruud van Melick', 'http://vanmelick.com/', '1.2', 'Static CSS caching', '<p>This plugin automatically stores a copy of your style sheet(s) as a static file. Static files are served several orders of magnitude faster than <span class=\"caps\">PHP</span> files (200 times faster, in my own tests on a FastCGI-based server).</p>\n\n<p>Post-installation steps:<br />\n<ol>\n	<li>Visit the <a href=\"index.php?event=prefs&amp;step=advanced_prefs\">advanced preferences</a> and make sure the &#8220;Style directory&#8221; preference contains the directory where you want to store the static <span class=\"caps\">CSS</span> files. This path is always a relative path (to the directory of your root textpattern installation). You should make sure that <span class=\"caps\">PHP</span> is able to write to that directory.</li>\n	<li>Activate the plugin.</li><br />\n</ol></p>\n\n<p>The <code>&lt;txp:rvm_css /&gt;</code> tag supplied by this plugin has the exact same attributes as the built-in <code>&lt;txp:css /&gt;</code> tag and can be used as a drop-in replacement, but you shouldn&#8217;t have to use it, because this plugin overrides the default behaviour of the <code>&lt;txp:css&gt;</code> tag.</p>\n\n<p>Note: because not all characters are allowed in filenames, avoid using non-alphanumeric characters in style sheet names.</p>\n\n<p>If you wish to use <a href=\"http://lesscss.org/\"><span class=\"caps\">LESS</span></a> syntax in your stylesheets, you need to do the following (this could be implemented as a plugin):<br />\n<ol>\n	<li>Download the file <a href=\"https://raw.github.com/leafo/lessphp/master/lessc.inc.php\">lessc.inc.php</a> (version 0.3.5 or higher) from the <a href=\"http://leafo.net/lessphp/\">lessphp</a> website and upload it to your website.</li>\n	<li>Edit your config.php file and add this just below the &#8216;$txpcfg&#8217; configuration lines: <code>require &#39;/path/to/lessc.inc.php&#39;;</code></li><br />\n</ol></p>\n\n<p>Things you should know when using <span class=\"caps\">LESS</span> syntax:<br />\n<ol>\n	<li>Remember to save each style sheet anew each time you update the lesssc.inc.php file.</li>\n	<li>If your <span class=\"caps\">LESS</span> code cannot be parsed, an error will be shown at the bottom of css admin tab and the static css file will not be updated.</li>\n	<li>When specifying only the filename in an &#8220;@import &#8216;style.less&#8217;&#8221; statement, the plugin assumes it&#8217;s in the &#8220;Style directory&#8221;. Do not leave out the &#8216;.less&#8217; extension!</li><br />\n</ol></p>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'rvm_css\')\n    ->register(\'rvm_css\', \'css\');\n}\n\nif (txpinterface == \'admin\')\n{\n  register_callback(\'rvm_css_sync\', \'css\', \'css_save\');\n  register_callback(\'rvm_css_sync\', \'css\', \'css_save_posted\');\n  register_callback(\'rvm_css_sync\', \'txp.css\', \'import\');\n  register_callback(\'rvm_css_sync\', \'plugin_lifecycle.rvm_css\', \'enabled\');\n  register_callback(\'rvm_css_setup\', \'plugin_lifecycle.rvm_css\', \'installed\');\n  register_callback(\'rvm_css_cleanup\', \'plugin_lifecycle.rvm_css\', \'deleted\');\n}\n\n\nfunction rvm_css($atts)\n{\n  global $doctype, $pretext;\n\n  extract(lAtts(array(\n    \'format\' => \'url\',\n    \'media\'  => \'screen\',\n    \'name\'   => $pretext[\'css\'],\n    \'rel\'    => \'stylesheet\',\n    \'theme\'  => isset($pretext[\'skin\']) ? $pretext[\'skin\'] : \'\',\n    \'title\'  => \'\',\n  ), $atts));\n\n  if ($name === \'\')\n  {\n      $name = \'default\';\n  }\n\n  if ($format === \'link\' and strpos($name, \',\') !== false)\n  {\n    $names = do_list($name);\n    $css = \'\';\n\n    foreach ($names as $name)\n    {\n      $atts[\'name\'] = $name;\n      $css .= rvm_css($atts);\n    }\n\n    return $css;\n  }\n\n  if ($theme)\n  {\n    $skindir = strtolower(sanitizeForUrl($theme)).\'/\';\n  }\n\n  $file = get_pref(\'rvm_css_dir\').\'/\'.$skindir.strtolower(sanitizeForUrl($name)).\'.css\';\n\n  if (empty(get_pref(\'rvm_css_dir\')) or\n    !is_readable(get_pref(\'path_to_site\').\'/\'.$file) and\n    (!rvm_css_sync() or !is_readable(get_pref(\'path_to_site\').\'/\'.$file))\n  )\n  {\n    $atts[\'name\'] = $name;\n\n    return css($atts);\n  }\n\n\n  if ($format == \'link\') {\n    return tag_void(\'link\', array(\n      \'rel\'   => $rel,\n      \'type\'  => $doctype != \'html5\' ? \'text/css\' : \'\',\n      \'media\' => $media,\n      \'title\' => $title,\n      \'href\'  => hu.$file,\n    ));\n  }\n\n  return hu.$file;\n}\n\n\nfunction rvm_css_sync()\n{\n  if (!get_pref(\'rvm_css_dir\'))\n  {\n    return false;\n  }\n\n  $basedir = get_pref(\'path_to_site\').\'/\'.get_pref(\'rvm_css_dir\').\'/\';\n\n  if (!file_exists($basedir))\n  {\n    mkdir($basedir, 0755);\n  }\n  elseif (!is_writable($basedir))\n  {\n    return false;\n  }\n\n  if ($rs = safe_rows_start(\'*\', \'txp_css\', \'1=1\'))\n  {\n    while ($row = nextRow($rs))\n    {\n      extract($row);\n\n      if (preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\', $css))\n      {\n        $css = base64_decode($css);\n      }\n\n      if (!empty($skin))\n      {\n        $skin = strtolower(sanitizeForUrl($skin));\n\n        if (!file_exists($basedir.$skin))\n        {\n          mkdir($basedir.$skin, 0755);\n        }\n        elseif (!is_writable($basedir.$skin))\n        {\n          continue;\n        }\n\n        $skindir = $skin.\'/\';\n      }\n      else\n      {\n        $skindir = \'\';\n      }\n\n      $basefile = $basedir.$skindir.strtolower(sanitizeForUrl($name));\n      $cssfile  = $basefile.\'.css\';\n\n      if (file_exists($cssfile) and !is_writable($cssfile))\n      {\n        continue;\n      }\n\n      if (class_exists(\'lessc\'))\n      {\n        $handle = fopen($file.\'.less\', \'wb\');\n        fwrite($handle, $css);\n        fclose($handle);\n        chmod($file.\'.less\', 0644);\n\n        $less = new lessc();\n        $less->setFormatter(\'compressed\');\n        $less->setImportDir(get_pref(\'path_to_site\').\'/\'.get_pref(\'rvm_css_dir\').\'/\');\n\n        try\n        {\n          $css  = $less->parse($css);\n        }\n        catch (Exception $ex)\n        {\n          error_log(\"lessphp fatal error: \".$ex->getMessage());\n          return false;\n        }\n      }\n\n      file_put_contents($cssfile, $css) and chmod($cssfile, 0644);\n    }\n  }\n\n  return true;\n}\n\n\nfunction rvm_css_setup()\n{\n  if (!get_pref(\'rvm_css_dir\'))\n  {\n    set_pref(\'rvm_css_dir\', \'css\', \'admin\', \'1\', \'text_input\', \'20\');\n  }\n}\n\n\nfunction rvm_css_cleanup()\n{\n    safe_delete(\'txp_prefs\', \"name=\'rvm_css_dir\'\");\n    safe_delete(\'txp_lang\', \"owner=\'rvm_css\'\");\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'rvm_css\')\n    ->register(\'rvm_css\', \'css\');\n}\n\nif (txpinterface == \'admin\')\n{\n  register_callback(\'rvm_css_sync\', \'css\', \'css_save\');\n  register_callback(\'rvm_css_sync\', \'css\', \'css_save_posted\');\n  register_callback(\'rvm_css_sync\', \'txp.css\', \'import\');\n  register_callback(\'rvm_css_sync\', \'plugin_lifecycle.rvm_css\', \'enabled\');\n  register_callback(\'rvm_css_setup\', \'plugin_lifecycle.rvm_css\', \'installed\');\n  register_callback(\'rvm_css_cleanup\', \'plugin_lifecycle.rvm_css\', \'deleted\');\n}\n\n\nfunction rvm_css($atts)\n{\n  global $doctype, $pretext;\n\n  extract(lAtts(array(\n    \'format\' => \'url\',\n    \'media\'  => \'screen\',\n    \'name\'   => $pretext[\'css\'],\n    \'rel\'    => \'stylesheet\',\n    \'theme\'  => isset($pretext[\'skin\']) ? $pretext[\'skin\'] : \'\',\n    \'title\'  => \'\',\n  ), $atts));\n\n  if ($name === \'\')\n  {\n      $name = \'default\';\n  }\n\n  if ($format === \'link\' and strpos($name, \',\') !== false)\n  {\n    $names = do_list($name);\n    $css = \'\';\n\n    foreach ($names as $name)\n    {\n      $atts[\'name\'] = $name;\n      $css .= rvm_css($atts);\n    }\n\n    return $css;\n  }\n\n  if ($theme)\n  {\n    $skindir = strtolower(sanitizeForUrl($theme)).\'/\';\n  }\n\n  $file = get_pref(\'rvm_css_dir\').\'/\'.$skindir.strtolower(sanitizeForUrl($name)).\'.css\';\n\n  if (empty(get_pref(\'rvm_css_dir\')) or\n    !is_readable(get_pref(\'path_to_site\').\'/\'.$file) and\n    (!rvm_css_sync() or !is_readable(get_pref(\'path_to_site\').\'/\'.$file))\n  )\n  {\n    $atts[\'name\'] = $name;\n\n    return css($atts);\n  }\n\n\n  if ($format == \'link\') {\n    return tag_void(\'link\', array(\n      \'rel\'   => $rel,\n      \'type\'  => $doctype != \'html5\' ? \'text/css\' : \'\',\n      \'media\' => $media,\n      \'title\' => $title,\n      \'href\'  => hu.$file,\n    ));\n  }\n\n  return hu.$file;\n}\n\n\nfunction rvm_css_sync()\n{\n  if (!get_pref(\'rvm_css_dir\'))\n  {\n    return false;\n  }\n\n  $basedir = get_pref(\'path_to_site\').\'/\'.get_pref(\'rvm_css_dir\').\'/\';\n\n  if (!file_exists($basedir))\n  {\n    mkdir($basedir, 0755);\n  }\n  elseif (!is_writable($basedir))\n  {\n    return false;\n  }\n\n  if ($rs = safe_rows_start(\'*\', \'txp_css\', \'1=1\'))\n  {\n    while ($row = nextRow($rs))\n    {\n      extract($row);\n\n      if (preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\', $css))\n      {\n        $css = base64_decode($css);\n      }\n\n      if (!empty($skin))\n      {\n        $skin = strtolower(sanitizeForUrl($skin));\n\n        if (!file_exists($basedir.$skin))\n        {\n          mkdir($basedir.$skin, 0755);\n        }\n        elseif (!is_writable($basedir.$skin))\n        {\n          continue;\n        }\n\n        $skindir = $skin.\'/\';\n      }\n      else\n      {\n        $skindir = \'\';\n      }\n\n      $basefile = $basedir.$skindir.strtolower(sanitizeForUrl($name));\n      $cssfile  = $basefile.\'.css\';\n\n      if (file_exists($cssfile) and !is_writable($cssfile))\n      {\n        continue;\n      }\n\n      if (class_exists(\'lessc\'))\n      {\n        $handle = fopen($file.\'.less\', \'wb\');\n        fwrite($handle, $css);\n        fclose($handle);\n        chmod($file.\'.less\', 0644);\n\n        $less = new lessc();\n        $less->setFormatter(\'compressed\');\n        $less->setImportDir(get_pref(\'path_to_site\').\'/\'.get_pref(\'rvm_css_dir\').\'/\');\n\n        try\n        {\n          $css  = $less->parse($css);\n        }\n        catch (Exception $ex)\n        {\n          error_log(\"lessphp fatal error: \".$ex->getMessage());\n          return false;\n        }\n      }\n\n      file_put_contents($cssfile, $css) and chmod($cssfile, 0644);\n    }\n  }\n\n  return true;\n}\n\n\nfunction rvm_css_setup()\n{\n  if (!get_pref(\'rvm_css_dir\'))\n  {\n    set_pref(\'rvm_css_dir\', \'css\', \'admin\', \'1\', \'text_input\', \'20\');\n  }\n}\n\n\nfunction rvm_css_cleanup()\n{\n    safe_delete(\'txp_prefs\', \"name=\'rvm_css_dir\'\");\n    safe_delete(\'txp_lang\', \"owner=\'rvm_css\'\");\n}\n', '14fd7801511f6d059587b2d07e736a1e', '#@prefs\n#@owner rvm_css\nrvm_css_dir => Stylesheet directory\n#@language en-gb\nrvm_css_dir => Stylesheet directory\n#@language nl-nl\nrvm_css_dir => Stylesheet-map\n#@language de-de\nrvm_css_dir => Stilvorlageverzeichnis\n#@language fr-fr\nrvm_css_dir => Répertoire des style', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rvm_inherit', 0, 'Ruud van Melick', 'http://vanmelick.com/txp/', '0.4', 'Template inheritance', '<p><style>\n   li code {font-weight: bold;}\n   pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\n   h1, h2, h3, h3 code {font-family: sans-serif; font-weight: bold;}\n   h1, h2, h3 {margin-left: -1em;}\n   h2, h3 {margin-top: 2em;}\n   li a code {font-weight: normal;}\n   code {border: 0px; background: none;}<br />\n</style></p>\n\n\n<h1 id=\"top\">rvm_inherit</h1>\n\n<p>This plugin makes it possible to use template inheritance.</p>\n\n<p>Please reports bugs and problems with this plugin in <a href=\"http://forum.textpattern.com/viewtopic.php?id=45552\">this forum thread</a></p>\n\n\n<h2 id=\"contents\">Contents</h2>\n\n<ul>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#rvm_inherit\"><code>&lt;txp:rvm_inherit&gt;</code></a></li>\n		<li><a href=\"#rvm_block\"><code>&lt;txp:rvm_block&gt;</code></a></li>\n		<li><a href=\"#rvm_child\"><code>&lt;txp:rvm_child&gt;</code></a></li>\n		<li><a href=\"#rvm_parent\"><code>&lt;txp:rvm_parent&gt;</code></a></li>\n	</ul></li>\n	<li><a href=\"#start\">Getting started</a></li>\n	<li><a href=\"#nesting\">Nesting blocks</a></li>\n	<li><a href=\"#multiple\">Rendering a block multiple times</a></li>\n	<li><a href=\"#child\">Children</a></li>\n	<li><a href=\"#parent\">Parents</a></li>\n</ul>\n\n\n<h3 id=\"rvm_inherit\"><code>&lt;txp:rvm_inherit /&gt;</code></h3>\n\n<p>Must be used as a container tag. Should contain only <code>&lt;txp:rvm_block&gt;</code>, <code>&lt;txp:rvm_child&gt;</code> and/or <code>&lt;txp:rvm_parent&gt;</code> tags, although those tags can of course contain other <span class=\"caps\">TXP</span> tags.</p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>form=&quot;text&quot;</code><br />\nName of an existing <span class=\"caps\">TXP</span> form you wish to inherit.</li>\n	<li><code>page=&quot;text&quot;</code><br />\nName of an existing <span class=\"caps\">TXP</span> page you wish to inherit.</li>\n</ul>\n\n<p>The <code>page</code> and <code>form</code> attributes are mutually exclusive. If neither is set it, <code>page</code> is set to &#8216;default&#8217;;</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n<h3 id=\"rvm_block\"><code>&lt;txp:rvm_block /&gt;</code></h3>\n\n<p>Can be used as a self-closing tag and as a container tag.</p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code><br />\nIdentifier of a block in the inheritance chain. If neither <code>child</code> nor <code>parent</code> is set, <code>name</code> defaults to an empty string. In other cases, an attempt is made to guess the correct name depending on the context where this tag is used.</li>\n	<li><code>child=&quot;integer&quot;</code><br />\nIf set to <code>1</code> it references the contents of the child block in the inheritance chain. If set to <code>2</code> it references the contents of the grandchild&#8230; etc.</li>\n	<li><code>parent=&quot;integer&quot;</code><br />\nIf set to <code>1</code> it references the contents of the parent block in the inheritance chain. If set to <code>2</code> it references the contents of the grandparent&#8230; etc.</li>\n</ul>\n\n<p>The <code>child</code> and <code>parent</code> attributes are mutually exclusive; at most only one of them may be set.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n<h3 id=\"rvm_child\"><code>&lt;txp:rvm_child /&gt;</code></h3>\n\n<p>Should only be used as a self-closing tag and is merely shorter way to write <code>&lt;txp:rvm_block child=&quot;1&quot;/&gt;</code></p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code><br />\nIdentifier of a block in the inheritance chain. If not set, an attempt is made to guess the correct name depending on the context where this tag is used (or it defaults to an empty string).</li>\n</ul>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n<h3 id=\"rvm_child\"><code>&lt;txp:rvm_parent /&gt;</code></h3>\n\n<p>Should only be used as a self-closing tag and is merely shorter way to write <code>&lt;txp:rvm_block parent=&quot;1&quot;/&gt;</code></p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code><br />\nIdentifier of a block in the inheritance chain. If not set, an attempt is made to guess the correct name depending on the context where this tag is used (or it defaults to an empty string).</li>\n</ul>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"start\">Getting started</h2>\n\n<p>Using template inheritance, two or more templates can organize themselves into an inheritance chain. These templates can be <span class=\"caps\">TXP</span> pages or <span class=\"caps\">TXP</span> forms. You can mix forms and pages in an inheritance chain. This plugin treats them as if they were the same.</p>\n\n<p>In the examples given here I&#8217;ll only use <span class=\"caps\">TXP</span> pages. These examples were borrowed from <a href=\"http://docs.makotemplates.org/en/latest/inheritance.html\">Mako</a>, which inspired me to write this plugin.</p>\n\n<p>Suppose you have a <span class=\"caps\">TXP</span> page &#8216;default&#8217; that contains:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            &lt;rvm_block name=&quot;header&quot; /&gt;\n        &lt;/div&gt;\n\n        &lt;txp:rvm_block&gt;\n\n        &lt;div class=&quot;footer&quot;&gt;\n            &lt;txp:rvm_block name=&quot;footer&quot;&gt;\n                this is the footer\n            &lt;/txp:rvm_block&gt;\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>And you have an inheriting <span class=\"caps\">TXP</span> page &#8216;daughter&#8217; which contains:</p>\n\n<pre><code>&lt;txp:rvm_inherit page=&quot;default&quot;&gt;\n    &lt;txp:rvm_block name=&quot;header&quot;&gt;\n        this is some header content\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block&gt;\n        this is the body content.\n    &lt;/txp:rvm_block&gt;\n&lt;/txp:rvm_inherit&gt;</code></pre>\n\n<p>The result, when using that &#8216;daughter&#8217; template, is:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            this is some header content\n        &lt;/div&gt;\n\n        this is the body content.\n\n        &lt;div class=&quot;footer&quot;&gt;\n            this is the footer\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>In this example the body and header content of the &#8216;daughter&#8217; page override the corresponding parts in the &#8216;default&#8217; page. Nothing was specified for the &#8216;footer&#8217; block in the &#8216;daughter&#8217; template, so that remains as it was in the &#8216;default&#8217; template.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"nesting\">Nesting blocks</h2>\n\n<p>The named blocks defined in an inherited template can also be nested within other blocks. The name given to each block is globally accessible via any inheriting template. We can add a new block title to our header block in the &#8216;default&#8217; template:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            &lt;txp:rvm_block name=&quot;header&quot;&gt;\n                &lt;h2&gt;\n                    &lt;txp:rvm_block name=&quot;title&quot; /&gt;\n                &lt;/h2&gt;\n            &lt;/txp:rvm_block&gt;\n        &lt;/div&gt;\n\n        &lt;txp:rvm_block /&gt;\n\n        &lt;div class=&quot;footer&quot;&gt;\n            &lt;txp:rvm_block name=&quot;footer&quot;&gt;\n                this is the footer\n            &lt;/txp:rvm_block&gt;\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>The inheriting template &#8216;daughter&#8217; can name either or both of header and title, separately or nested themselves:</p>\n\n<pre><code>&lt;txp:rvm_inherit page=&quot;default&quot;&gt;\n    &lt;txp:rvm_block name=&quot;header&quot;&gt;\n        this is some header content\n        &lt;txp:rvm_parent /&gt;\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block name=&quot;title&quot;&gt;\n        this is the title\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block&gt;\n        this is the body content.\n    &lt;txp:rvm_block&gt;\n&lt;/txp:rvm_inherit&gt;</code></pre>\n\n<p>The result would be:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            this is come header content\n            &lt;h2&gt;\n                this is the title\n            &lt;/h2&gt;\n        &lt;/div&gt;\n\n        this is the body content.\n\n        &lt;div class=&quot;footer&quot;&gt;\n            this is the footer\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>Note when we overrode header, we added an extra <code>&lt;txp:rvm_parent /&gt;</code> in order to invoke the parent’s header block in addition to our own. That’s described in more detail <a href=\"#parent\">below</a>.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"multiple\">Rendering a named block multiple times</h2>\n\n<p>You can use multiple blocks with the same name within one template:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;head&gt;\n        &lt;title&gt;&lt;txp:rvm_block name=&quot;title&quot; /&gt;&lt;/title&gt;\n    &lt;/head&gt;\n    &lt;body&gt;\n    &lt;txp:rvm_block name=&quot;header&quot;&gt;\n        &lt;h2&gt;&lt;txp:rvm_block name=&quot;title&quot; /&gt;&lt;/h2&gt;\n    &lt;/txp:rvm_block&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>Where above an inheriting template can define <code>&lt;txp:rvm_block name=&quot;title&quot;&gt;</code> just once, and it will be used in the base template both in the <code>&lt;title&gt;</code> section as well as the <code>&lt;h2&gt;</code><br />\nthis is the body content.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"child\">Children</h2>\n\n<p>Sometimes you have an inheritance chain that spans more than two templates. Or maybe you don’t, but you’d like to build your system such that extra inherited templates can be inserted in the middle of a chain where they would be smoothly integrated. If each template wants to define its layout just within its main body, you can’t just call <code>&lt;txp:block /&gt;</code> to get at the inheriting template’s body, since that is only the topmost body. To get at the body of the inheriting template, you use <code>&lt;txp:rvm_child /&gt;</code>.</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            &lt;txp:rvm_block name=&quot;header&quot; /&gt;\n        &lt;/div&gt;\n\n        &lt;txp:rvm_child /&gt;\n\n        &lt;div class=&quot;footer&quot;&gt;\n            &lt;txp:rvm_block name=&quot;footer&quot;&gt;\n                this is the footer\n            &lt;/txp:rvm_block&gt;\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>Lets also add an intermediate template called &#8216;daughter&#8217; which inherits from the &#8216;default&#8217; page:</p>\n\n<pre><code>&lt;txp:rvm_inherit page=&quot;default&quot;/&gt;\n    &lt;ul&gt;\n        &lt;txp:rvm_block name=&quot;toolbar&quot;&gt;\n            &lt;li&gt;selection 1&lt;/li&gt;\n            &lt;li&gt;selection 2&lt;/li&gt;\n            &lt;li&gt;selection 3&lt;/li&gt;\n        &lt;/txp:rvm_block&gt;\n    &lt;/ul&gt;\n    &lt;div class=&quot;mainlayout&quot;&gt;\n        &lt;txp:rvm_child /&gt;\n    &lt;/div&gt;\n&lt;/txp:rvm_inherit&gt;</code></pre>\n\n<p>And finally create a &#8216;granddaughter&#8217; template to inherit from &#8216;daughter&#8217;:</p>\n\n<pre><code>&lt;txp:rvm_inherit page=&quot;daughter&quot;/&gt;\n    &lt;txp:rvm_block name=&quot;header&quot;&gt;\n        this is some header content\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block&gt;\n        this is the body content.\n    &lt;/txp:rvm_block&gt;\n&lt;/txp:rvm_inherit&gt;</code></pre>\n\n<p>In this setup, each call to <code>&lt;txp:rvm_child /&gt;</code> will render the body of the next template in the inheritance chain (which can be written as &#8216;granddaughter&#8217; -&gt; &#8216;daughter&#8217; -&gt; &#8216;default&#8217;). The output we get would be:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            this is some header content\n        &lt;/div&gt;\n\n        &lt;ul&gt;\n            &lt;li&gt;selection 1&lt;/li&gt;\n            &lt;li&gt;selection 2&lt;/li&gt;\n            &lt;li&gt;selection 3&lt;/li&gt;\n        &lt;/ul&gt;\n\n        &lt;div class=&quot;mainlayout&quot;&gt;\n        this is the body content.\n        &lt;/div&gt;\n\n        &lt;div class=&quot;footer&quot;&gt;\n            this is the footer\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>So above, we have the <code>&lt;html&gt;</code>, <code>&lt;body&gt;</code> and header/footer layout of the &#8216;default&#8217; page, we have the <code>&lt;ul&gt;</code> and mainlayout section of the &#8216;daughter&#8217; page, and the main body of the &#8216;granddaughter&#8217; page as well as its overridden &#8216;header&#8217; block. The &#8216;daughter&#8217; template is inserted into the middle of the chain without &#8216;default&#8217; template having to change anything. Without <code>&lt;txp:rvm_child&gt;</code>, only the main body of the &#8216;granddaughter&#8217; template could be used; there would be no way to call the &#8216;daughter&#8217; body content.</p>\n\n<p><a href=\"#top\">Back to top</a></p>\n\n\n<h2 id=\"parent\">Parents</h2>\n\n<p>Lets now look at parents; the opposite of children. The parent template is the one immediately preceding the current template. Blocks can call upon their overridden versions. This is not as hard as it sounds. Lets modify the &#8216;granddaughter&#8217; template to augment the list of selections provided by the &#8216;toolbar&#8217; block in the &#8216;daughter&#8217; template:</p>\n\n<pre><code>&lt;txp:rvm_inherit page=&quot;daughter&quot; /&gt;\n    &lt;txp:rvm_block name=&quot;header&quot;&gt;\n        this is some header content\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block name=&quot;toolbar&quot;&gt;\n        &lt;txp:rvm_parent /&gt;\n        &lt;li&gt;selection 4&lt;/li&gt;\n        &lt;li&gt;selection 5&lt;/li&gt;\n    &lt;/txp:rvm_block&gt;\n\n    &lt;txp:rvm_block&gt;\n        this is the body content.\n    &lt;/txp:rvm_block&gt;\n&lt;/txp:rvm_inherit&gt;</code></pre>\n\n<p>Above, we implemented a &#8216;toolbar&#8217; block, which is meant to override the definition of toolbar within the inherited template &#8216;granddaughter&#8217;. However, since we want the content from that of &#8216;daughter&#8217; as well, we call it via the <code>&lt;txp:rvm_parent&gt;</code> tag whenever we want it’s content, in this case before we add our own selections. So the output for the whole thing is now:</p>\n\n<pre><code>&lt;html&gt;\n    &lt;body&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            this is some header content\n        &lt;/div&gt;\n\n        &lt;ul&gt;\n            &lt;li&gt;selection 1&lt;/li&gt;\n            &lt;li&gt;selection 2&lt;/li&gt;\n            &lt;li&gt;selection 3&lt;/li&gt;\n            &lt;li&gt;selection 4&lt;/li&gt;\n            &lt;li&gt;selection 5&lt;/li&gt;\n        &lt;/ul&gt;\n\n        &lt;div class=&quot;mainlayout&quot;&gt;\n        this is the body content.\n        &lt;/div&gt;\n\n        &lt;div class=&quot;footer&quot;&gt;\n            this is the footer\n        &lt;/div&gt;\n    &lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<p>and you’re now a template inheritance ninja!</p>\n\n<p><a href=\"#top\">Back to top</a></p>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'rvm_inherit\')\n		->register(\'rvm_block\')\n		->register(\'rvm_child\')\n		->register(\'rvm_parent\');\n}\n\nfunction rvm_inherit($atts, $thing = null)\n{\n	static $stack = array();\n\n	extract(lAtts(array(\n		\'page\' => false,\n		\'form\' => false\n	), $atts));\n\n	$out = \'\';\n	rvm_block(false);\n\n	if ($thing !== null)\n	{\n		parse($thing);\n	}\n	else\n	{\n		trigger_error(\'rvm_inherit should be used as a container tag\');\n	}\n\n	rvm_block(true);\n\n	if ($page === false and $form === false)\n	{\n		$page = \'default\';\n	}\n\n	if ($page !== false)\n	{\n		if (in_array($page, $stack, true))\n		{\n			trigger_error(\'circular page reference: \' . array_pop($stack) . \' calls \' . $page);\n		}\n		else\n		{\n			$stack[] = $page;\n\n			if (is_callable(\'fetch_page\'))\n			{\n				$page = fetch_page($page);\n			}\n			else\n			{\n				$page = safe_field(\'user_html\',\'txp_page\',\"name=\'\".doSlash($page).\"\'\");\n			}\n\n			$out = ($page === false ? \'\' : parse($page));\n		}\n	}\n	else\n	{\n		$out = parse_form($form);\n	}\n\n	rvm_block(false);\n	return $out;\n}\n\nfunction rvm_block($atts, $thing = null)\n{\n	static $parse = true;\n	static $chain = array();\n	static $children = array();\n	static $parents = array();\n	static $queue = array();\n\n	if (is_bool($atts))\n	{\n		$parse = $atts;\n		return;\n	}\n\n	extract(lAtts(array(\n		\'name\' => false,\n		\'child\' => false,\n		\'parent\' => false\n	), $atts));\n\n	$out = \'\';\n\n	if ($child or $parent)\n	{\n		if ($thing !== null)\n		{\n			trigger_error(\'ignoring container contents because child or parent attribute is specified\');\n		}\n\n		if ($name === false)\n		{\n		 	if (!empty($queue))\n		 	{\n				$name = end($queue);\n			}\n			elseif ($parent)\n			{\n				trigger_error(\'use of parent attribute in a standalone block tag is not allowed\');\n			}\n		}\n	}\n\n	if ($name === false)\n	{\n		$name = \'\';\n	}\n\n	if ($child !== false)\n	{\n		if (assert_int($child) and $child > 0)\n		{\n			if (!isset($children[$name]))\n			{\n				$children[$name] = count($chain[$name]);\n			}\n\n			$children[$name] -= $child;\n\n			if (isset($chain[$name]) and $children[$name] >= 0)\n			{\n				$thing = $chain[$name][$children[$name]];\n				$out = ($thing === null ? \'\' : parse($thing));\n			}\n		}\n	}\n	elseif ($parent !== false)\n	{\n		if (assert_int($parent) and $parent > 0)\n		{\n			if (!isset($parents[$name]))\n			{\n				$parents[$name] = 0;\n			}\n\n			$parents[$name] += $parent;\n\n			if (isset($chain[$name]) and $parents[$name] < count($chain[$name]))\n			{\n				$thing = $chain[$name][$parents[$name]];\n				$out = ($thing === null ? \'\' : parse($thing));\n			}\n			else\n			{\n				trigger_error(\'missing parent\');\n			}\n		}\n	}\n	else\n	{\n		$chain[$name][] = $thing;\n\n		if ($parse)\n		{\n			if (in_array($name, $queue))\n			{\n				trigger_error(\'do not nest blocks with the same name, unless it is a parent/child construct.\');\n			}\n			else\n			{\n				$queue[] = $name;\n				$thing = $chain[$name][0];\n				$out = ($thing === null ? \'\' : parse($thing));\n				array_pop($queue);\n			}\n\n			array_pop($chain[$name]); # needed in case a template contains the same block more then once.\n		}\n	}\n\n	return $out;\n}\n\nfunction rvm_child($atts, $thing = null)\n{\n	$atts[\'child\'] = 1;\n	return rvm_block($atts);\n}\n\nfunction rvm_parent($atts, $thing = null)\n{\n	$atts[\'parent\'] = 1;\n	return rvm_block($atts);\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'rvm_inherit\')\n		->register(\'rvm_block\')\n		->register(\'rvm_child\')\n		->register(\'rvm_parent\');\n}\n\nfunction rvm_inherit($atts, $thing = null)\n{\n	static $stack = array();\n\n	extract(lAtts(array(\n		\'page\' => false,\n		\'form\' => false\n	), $atts));\n\n	$out = \'\';\n	rvm_block(false);\n\n	if ($thing !== null)\n	{\n		parse($thing);\n	}\n	else\n	{\n		trigger_error(\'rvm_inherit should be used as a container tag\');\n	}\n\n	rvm_block(true);\n\n	if ($page === false and $form === false)\n	{\n		$page = \'default\';\n	}\n\n	if ($page !== false)\n	{\n		if (in_array($page, $stack, true))\n		{\n			trigger_error(\'circular page reference: \' . array_pop($stack) . \' calls \' . $page);\n		}\n		else\n		{\n			$stack[] = $page;\n\n			if (is_callable(\'fetch_page\'))\n			{\n				$page = fetch_page($page);\n			}\n			else\n			{\n				$page = safe_field(\'user_html\',\'txp_page\',\"name=\'\".doSlash($page).\"\'\");\n			}\n\n			$out = ($page === false ? \'\' : parse($page));\n		}\n	}\n	else\n	{\n		$out = parse_form($form);\n	}\n\n	rvm_block(false);\n	return $out;\n}\n\nfunction rvm_block($atts, $thing = null)\n{\n	static $parse = true;\n	static $chain = array();\n	static $children = array();\n	static $parents = array();\n	static $queue = array();\n\n	if (is_bool($atts))\n	{\n		$parse = $atts;\n		return;\n	}\n\n	extract(lAtts(array(\n		\'name\' => false,\n		\'child\' => false,\n		\'parent\' => false\n	), $atts));\n\n	$out = \'\';\n\n	if ($child or $parent)\n	{\n		if ($thing !== null)\n		{\n			trigger_error(\'ignoring container contents because child or parent attribute is specified\');\n		}\n\n		if ($name === false)\n		{\n		 	if (!empty($queue))\n		 	{\n				$name = end($queue);\n			}\n			elseif ($parent)\n			{\n				trigger_error(\'use of parent attribute in a standalone block tag is not allowed\');\n			}\n		}\n	}\n\n	if ($name === false)\n	{\n		$name = \'\';\n	}\n\n	if ($child !== false)\n	{\n		if (assert_int($child) and $child > 0)\n		{\n			if (!isset($children[$name]))\n			{\n				$children[$name] = count($chain[$name]);\n			}\n\n			$children[$name] -= $child;\n\n			if (isset($chain[$name]) and $children[$name] >= 0)\n			{\n				$thing = $chain[$name][$children[$name]];\n				$out = ($thing === null ? \'\' : parse($thing));\n			}\n		}\n	}\n	elseif ($parent !== false)\n	{\n		if (assert_int($parent) and $parent > 0)\n		{\n			if (!isset($parents[$name]))\n			{\n				$parents[$name] = 0;\n			}\n\n			$parents[$name] += $parent;\n\n			if (isset($chain[$name]) and $parents[$name] < count($chain[$name]))\n			{\n				$thing = $chain[$name][$parents[$name]];\n				$out = ($thing === null ? \'\' : parse($thing));\n			}\n			else\n			{\n				trigger_error(\'missing parent\');\n			}\n		}\n	}\n	else\n	{\n		$chain[$name][] = $thing;\n\n		if ($parse)\n		{\n			if (in_array($name, $queue))\n			{\n				trigger_error(\'do not nest blocks with the same name, unless it is a parent/child construct.\');\n			}\n			else\n			{\n				$queue[] = $name;\n				$thing = $chain[$name][0];\n				$out = ($thing === null ? \'\' : parse($thing));\n				array_pop($queue);\n			}\n\n			array_pop($chain[$name]); # needed in case a template contains the same block more then once.\n		}\n	}\n\n	return $out;\n}\n\nfunction rvm_child($atts, $thing = null)\n{\n	$atts[\'child\'] = 1;\n	return rvm_block($atts);\n}\n\nfunction rvm_parent($atts, $thing = null)\n{\n	$atts[\'parent\'] = 1;\n	return rvm_block($atts);\n}\n', 'f390e1e8e1be2745180175a3b18fc450', '', '', 0, 5, 0),
('rvm_if_this_article', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.2', 'conditional tag for detecting the current article', '<p>This plugin provides the <code>&lt;txp:rvm_if_this_article /&gt;</code> tag which compares the currently processed article (for example in article_custom) with the ID of the article page it&#8217;s used on. Example use would be for a list of articles (using article_custom) on an article page, where you want the current article to be rendered differently (not linked, bold, not at all, whatever) in the custom list of articles.</p>\n\n<pre><code>&lt;txp:rvm_if_this_article&gt; ... &lt;txp:else /&gt; ... &lt;/txp:rvm_if_this_article&gt;</code></pre>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_if_this_article\');\n}\n\nfunction rvm_if_this_article($atts, $thing)\n{\n  global $pretext;\n\n  return parse(EvalElse($thing, $pretext[\'id\'] == article_id(array()) ));\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_if_this_article\');\n}\n\nfunction rvm_if_this_article($atts, $thing)\n{\n  global $pretext;\n\n  return parse(EvalElse($thing, $pretext[\'id\'] == article_id(array()) ));\n}\n', '95513135b995eba436c1e5b5d6e13220', '', '', 0, 5, 0),
('rvm_mail_comments', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.1', 'Mail all comments to dedicated moderator(s)', '<p>Before enabling the plugin, edit the plugin so the $mod_name and $mod_email variables contain the name and email address of the dedicated moderator. Example:\n  <pre>\n    $mod_name = &#39;John Doe&#39;;\n    $mod_email = &#39;john@example.com&#39;;\n  </pre></p>\n\n<p>If there are multiple dedicated moderators, use a generic name and a comma-separated list of email addresses:\n  <pre>\n    $mod_name = &#39;Moderators&#39;;\n    $mod_email = &#39;moderator@example.com,anothermoderator@example.com&#39;;\n  </pre></p>\n\n<p><a href=\"http://forum.textpattern.com/viewtopic.php?id=19087\">More info</a></p>', '\nregister_callback(\'rvm_mail_comment\',\'comment.save\');\n\nfunction rvm_mail_comment($foo, $bar)\n{\n	global $sitename;\n\n	$mod_name = \'Moderator name\';\n	$mod_email = \'moderator_email@example.com\';\n\n	$row = getRow(\"SHOW TABLE STATUS LIKE \'\".PFX.\"txp_discuss\'\");\n	$discussid = $row[\'Auto_increment\'];\n\n	extract(getComment());\n	$parentid = assert_int($parentid);\n	$discussid = assert_int($discussid);\n	$article = safe_row(\"Section, Posted, ID, url_title, AuthorID, Title\", \"textpattern\", \"ID = $parentid\");\n	extract($article);\n\n	$out = gTxt(\'greeting\').\" $mod_name,\\r\\n\\r\\n\";\n	$out .= str_replace(\'{title}\',$Title,gTxt(\'comment_recorded\')).\"\\r\\n\";\n	$out .= permlinkurl_id($parentid).\"\\r\\n\";\n	$out .= hu.\'textpattern/index.php?event=discuss&step=discuss_edit&discussid=\'.$discussid.\"\\r\\n\";\n	$out .= \"\\r\\n\";\n	$out .= gTxt(\'comment_name\').\": $name\\r\\n\";\n	$out .= gTxt(\'comment_email\').\": $email\\r\\n\";\n	$out .= gTxt(\'comment_web\').\": $web\\r\\n\";\n	$out .= gTxt(\'comment_comment\').\": $message\";\n\n	$subject = strtr(gTxt(\'comment_received\'),array(\'{site}\' => $sitename, \'{title}\' => $Title));\n\n	$success = txpMail($mod_email, $subject, $out, $email);\n}', '\nregister_callback(\'rvm_mail_comment\',\'comment.save\');\n\nfunction rvm_mail_comment($foo, $bar)\n{\n	global $sitename;\n\n	$mod_name = \'Moderator name\';\n	$mod_email = \'moderator_email@example.com\';\n\n	$row = getRow(\"SHOW TABLE STATUS LIKE \'\".PFX.\"txp_discuss\'\");\n	$discussid = $row[\'Auto_increment\'];\n\n	extract(getComment());\n	$parentid = assert_int($parentid);\n	$discussid = assert_int($discussid);\n	$article = safe_row(\"Section, Posted, ID, url_title, AuthorID, Title\", \"textpattern\", \"ID = $parentid\");\n	extract($article);\n\n	$out = gTxt(\'greeting\').\" $mod_name,\\r\\n\\r\\n\";\n	$out .= str_replace(\'{title}\',$Title,gTxt(\'comment_recorded\')).\"\\r\\n\";\n	$out .= permlinkurl_id($parentid).\"\\r\\n\";\n	$out .= hu.\'textpattern/index.php?event=discuss&step=discuss_edit&discussid=\'.$discussid.\"\\r\\n\";\n	$out .= \"\\r\\n\";\n	$out .= gTxt(\'comment_name\').\": $name\\r\\n\";\n	$out .= gTxt(\'comment_email\').\": $email\\r\\n\";\n	$out .= gTxt(\'comment_web\').\": $web\\r\\n\";\n	$out .= gTxt(\'comment_comment\').\": $message\";\n\n	$subject = strtr(gTxt(\'comment_received\'),array(\'{site}\' => $sitename, \'{title}\' => $Title));\n\n	$success = txpMail($mod_email, $subject, $out, $email);\n}', '26d547c31ac4a79522aed8c5a1d0de0c', '', '', 0, 5, 0),
('rvm_maintenance', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.5', 'Maintenance Mode', '<h1>Maintenance Mode</h1>\n\n<p>This plugin, when activated, will show a maintenance page to all visitors who are not logged in on the admin side of Textpattern.</p>\n\n<p>After enabling the plugin, click the <em>View Site</em> tab. This will allow you to see what your website will look like to visitors once the maintenance mode is deactivated.</p>\n\n<p>There are two ways to customize the maintenance mode error page<sup class=\"footnote\" id=\"fnrev8269406885d37768cb8d62-1\"><a href=\"#fn8269406885d37768cb8d62-1\">1</a></sup>:<br />\n<ol>\n	<li>modify the <em>error_default</em> template page.</li>\n	<li>create a new template page called <em>error_503</em> (recommended!)</li><br />\n</ol></p>\n\n<p>To disable maintenance mode, simply deactivate the plugin. This will make your entire site viewable to all visitors.</p>\n\n<p>This plugin requires Textpattern 4.0.7 or higher.</p>\n\n<p class=\"footnote\" id=\"fn8269406885d37768cb8d62-1\"><sup>1</sup> Maintenance mode uses a <em>503 Service Unavailable</em> <span class=\"caps\">HTTP</span> status header.</p>', '\nregister_callback(\'rvm_maintenance_init\', \'pretext\');\n\nfunction rvm_maintenance_init()\n{\n  if (txpinterface == \'public\' and !gps(\'txpcleantest\') and !is_logged_in())\n  {\n    $_GET = $_POST = $_REQUEST = array();\n    register_callback(\'rvm_maintenance\', \'pretext_end\');\n  }\n}\n\nfunction rvm_maintenance()\n{\n    txp_die(\'Site maintenance in progress. Please check back later.\', 503);\n}\n', '\nregister_callback(\'rvm_maintenance_init\', \'pretext\');\n\nfunction rvm_maintenance_init()\n{\n  if (txpinterface == \'public\' and !gps(\'txpcleantest\') and !is_logged_in())\n  {\n    $_GET = $_POST = $_REQUEST = array();\n    register_callback(\'rvm_maintenance\', \'pretext_end\');\n  }\n}\n\nfunction rvm_maintenance()\n{\n    txp_die(\'Site maintenance in progress. Please check back later.\', 503);\n}\n', '543f28e4542434c60c0b680ae6764a3b', '', '', 0, 5, 0),
('rvm_plugin_diff', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.3', 'Find changes made to installed plugins', '<p>In the extensions tab, you&#8217;ll find a new tab called &#8216;Plugin Diff&#8217;, which you can use to:</p>\n\n<ul>\n	<li>Find what changes were made to installed plugins.</li>\n	<li>Download those changes in unified diff format. The resulting patch uses <span class=\"caps\">UNIX</span>-style (LF) line endings, which you may have to convert if you&#8217;re developing on a different platform (Windows = <span class=\"caps\">CRLF</span>, Mac = CR).</li>\n	<li>See the code with line numbers at the beginning of each line (makes finding errors easier).</li>\n</ul>', 'if (@txpinterface == \'admin\') {\n  add_privs(\'rvm_plugin_diff\', \'1,2\');\n  register_tab(\'extensions\', \'rvm_plugin_diff\', \'Plugin Diff\');\n  register_callback(\'rvm_plugin_diff\', \'rvm_plugin_diff\');\n}\n\n/**\n  Diff routine uses some parts of the code found at this address:\n  http://www.holomind.de/phpnet/diff2.src.php\n  Copyright (C) 2003  Daniel Unterberger <diff.phpnet@holomind.de>\n  Copyright (C) 2005  Nils Knappmeier (next version)\n  License: GNU General Public License version 2 or later\n  http://www.gnu.org/licenses/gpl.html\n**/\n\nfunction rvm_plugin_diff($event, $step) {\n  pagetop(\'Plugin Diff\');\n\n  $name = gps(\'name\');\n  $nospace = gps(\'nospace\') ? 1 : 0;\n  $context = is_numeric(gps(\'context\')) ? intval(gps(\'context\')) : \'5\';\n\n  $plugins = array();\n  $mplugins = safe_column(\'name\', \'txp_plugin\', \'code != code_restore\');\n  $oplugins = safe_column(\'name\', \'txp_plugin\', \'code = code_restore\');\n  asort($oplugins); asort($mplugins);\n  if ($mplugins) {\n    $plugins[] = \' \';\n    if ($oplugins) $plugins[] = \' ----- MODIFIED ----- \';\n    foreach($mplugins as $plugin) $plugins[] = $plugin;\n  }\n  if ($oplugins) {\n    $plugins[] = \' \';\n    if ($mplugins) $plugins[] = \' ----- ORIGINAL ----- \';\n    foreach($oplugins as $plugin) $plugins[] = $plugin;\n  }\n  if (isset($plugins[$name])) $name = $plugins[$name];\n\n  echo \'<div style=\"border:1px solid #ccc;background:#f7f7f7;padding:1em;margin-left:2em;float:left;\">\';\n  echo form(\n    \'<p>Plugin \'.selectInput(\'name\', $plugins, $name, 0).\'</p>\'.\n    \'<p>Show <input type=\"text\" name=\"context\" maxlength=\"4\" size=\"3\" value=\"\'.$context.\'\" class=\"edit\" /> lines around each change</p>\'.\n    \'<p>\'.checkbox(\'nospace\',\'ignore\',$nospace).\' Ignore white space changes</p>\'.\n    eInput(\'rvm_plugin_diff\').\n    fInput(\'submit\',\'\',\'Show Changes\',\'publish\')\n  );\n  echo \'</div>\';\n  echo \'<br style=\"clear:both\" />\';\n\n  $rs = safe_row(\'code,code_restore,name,version\', \'txp_plugin\', \"name=\'\".safe_escape($name).\"\'\");\n  if (!$rs) return;\n  extract($rs);\n  unset($rs);\n\n  # force Unix-style line breaks\n  $code_restore = str_replace(\"\\r\\n\", n, $code_restore);\n  $code_restore = str_replace(\"\\r\", n, $code_restore);\n  $code = str_replace(\"\\r\\n\", n, $code);\n  $code = str_replace(\"\\r\", n, $code);\n\n  # split the source text into arrays of lines\n  $t1 = explode(n,trim($code_restore));\n  $t2 = explode(n,trim($code));\n  unset($code_restore,$code);\n\n  # build a reverse-index array using the line as key and line number as value\n  foreach($t1 as $i=>$x) $r1[$x][] = $i;\n  foreach($t2 as $i=>$x) $r2[$x][] = $i;\n\n  $a1=0; $a2=0;   # start at beginning of each list\n  $actions = array();\n  $is_diff = false;\n\n  # walk this loop until we reach the end of one of the lists\n  while ($a1<count($t1) && $a2<count($t2)) {\n    # if we have a common element, save it and go to the next\n    if ($nospace ? trim($t1[$a1])==trim($t2[$a2]) : $t1[$a1]==$t2[$a2])\n      { $actions[]=0; $a1++; $a2++; continue; }\n    # otherwise, find the shortest move (Manhattan-distance) from the\n    # current location\n    $is_diff = true;\n    $best1=count($t1); $best2=count($t2);\n    $s1=$a1; $s2=$a2;\n    while($s1+$s2 < $best1+$best2) {\n      $d=-1;\n      foreach((array)@$r1[$t2[$s2]] as $n)\n        if ($n>=$s1) { $d=$n; break; }\n      if ($d>=$s1 and $d+$s2 < $best1+$best2)\n        { $best1=$d; $best2=$s2; }\n      $d=-1;\n      foreach((array)@$r2[$t1[$s1]] as $n)\n        if ($n>=$s2) { $d=$n; break; }\n      if ($d>=$s2 and $s1+$d < $best1+$best2)\n        { $best1=$s1; $best2=$d; }\n      $s1++; $s2++;\n    }\n    while ($a1<$best1) { $actions[]=1; $a1++; }  # deleted elements\n    while ($a2<$best2) { $actions[]=2; $a2++; }  # added elements\n  }\n  unset($r1,$r2);\n\n  # we\'ve reached the end of one list, now walk to the end of the other\n  while($a1<count($t1)) { $actions[]=1; $a1++; }  # deleted elements\n  while($a2<count($t2)) { $actions[]=2; $a2++; }  # added elements\n\n  # create $lines array with all needed info for further processing\n  $i1=$i2=0;\n  $lines = array();\n  foreach($actions as $act) {\n    $line = ($act==1 ? $t1[$i1] : $t2[$i2]);\n    $line_old = ($act==2 ? 0 : ++$i1);\n    $line_new = ($act==1 ? 0 : ++$i2);\n    $lines[] = array($line_old, $line_new, $line, 2);\n  }\n\n  # remember how much space the line numbers will need\n  $n1 = strlen(count($t1)); if ($n1 < 3) $n1 = 3; $n1++;\n  $n2 = strlen(count($t2)); if ($n2 < 3) $n2 = 3; $n2++;\n  unset($t1,$t2,$actions);\n\n  # mark lines that are needed to provide context\n  if ($is_diff) {\n    if (trim($context) != \'\') {\n      $count = 0;\n      foreach($lines as $nr => $line) {\n        if ($line[0] and $line[1]) {\n          if ($count) $count--;\n          else $lines[$nr][3]--;\n        }\n        else $count = $context;\n      }\n      $count = 0;\n      for($nr = count($lines)-1; $nr >= 0; $nr--) {\n        if ($lines[$nr][0] and $lines[$nr][1]) {\n          if ($count) $count--;\n          else $lines[$nr][3]--;\n        }\n        else $count = $context;\n      }\n    }\n\n    # unified diff\n    if (gps(\'format\')) {\n      $udiff = \'\';\n      $diff = array(0,0,0,0);\n      foreach($lines as $line) {\n        if (!$line[3]) {\n          if ($out) {\n            $udiff .= $diff[4].$out;\n            $out = \'\';\n            $diff = array(0,0,0,0);\n          }\n        }\n        else {\n          if (!$diff[0]) $diff[0] = $line[0];\n          if (!$diff[1]) $diff[1] = $line[1];\n          if ($line[0]) $diff[2] = $line[0];\n          if ($line[1]) $diff[3] = $line[1];\n          $diff[4] = \'@@ -\'.$diff[0].\',\'.($diff[2]+1-$diff[0]).\' +\'.$diff[1].\',\'.($diff[3]+1-$diff[1]).\' @@\'.n;\n          $out .= (!$line[1] ? \'-\' : (!$line[0] ? \'+\' : \' \')) . $line[2] . n;\n        }\n      }\n      if ($out) $udiff .= $diff[4].$out;\n\n      # send unified diff file\n      if ($udiff) {\n        $plugin = $name.\'-\'.$version;\n        $time = strftime(\' %Y-%m-%d %H:%M:%S.000000000 %z\',time());\n        $udiff = \'--- \'.$plugin.\'.orig\'.$time.n.\'+++ \'.$plugin.\'.new\'.$time.n.$udiff;\n        ob_clean();\n        header(\'Content-Description: Plugin Diff\');\n        header(\'Content-Type: text/plain;charset=utf-8\');\n        header(\'Content-Length: \' . strlen($udiff));\n        header(\'Content-Disposition: attachment; filename=\"\' . $plugin . \'.diff\"\');\n        echo $udiff;\n        ob_flush();\n        flush();\n        exit;\n      }\n    }\n\n    # add inline diff markers\n    $ins = $del = array();\n    foreach($lines as $nr => $line) {\n      if ($line[0] and $line[1]) {\n        if ($del and count($del) == count($ins)) {\n          while($delnr = array_shift($del) and $insnr = array_shift($ins)) {\n            $begin = 0; $end = 0;\n            $delstr = $lines[$delnr][2];\n            $insstr = $lines[$insnr][2];\n            $max = min(strlen($insstr),strlen($delstr));\n            while($begin <= $max and substr($delstr,$begin,1) == substr($insstr,$begin,1)) $begin++;\n            while($end <= $max and substr($delstr,-$end-1,1) == substr($insstr,-$end-1,1)) $end++;\n            if (!$begin and !$end) continue;\n            foreach(array($delnr,$insnr) as $linenr) {\n              $str = $lines[$linenr][2];\n              $len = strlen($str) - $begin - $end;\n              if ($len < 0) { $end += $len; $len = 0; }\n              $lines[$linenr][2] = substr($str,0,$begin).\"\\r\".substr($str,$begin,$len).\"\\r\".($end?substr($str,-$end):\'\');\n            }\n          }\n        }\n        $ins = $del = array();\n      }\n      else {\n        if ($line[0]) $del[] = $nr;\n        else $ins[] = $nr;\n      }\n    }\n  }\n\n  # download link\n  if ($is_diff) echo \'<p style=\"margin-left:3em\"><a href=\"?event=rvm_plugin_diff\'.a.\'format=diff\'.a.\'name=\'.urlencode($name).a.\'context=\'.$context.a.\'nospace=\'.$nospace.\'\">Download in <strong>unified diff</strong> format</a></p>\';\n\n  # color diff\n  $lstyle = \' style=\"color: #999; border-right:1px solid #ccc;padding:0em .5em 0em 0em;\"\';\n  echo \'<pre>\';\n  if ($is_diff) {\n    echo \"<span$lstyle>\".str_pad(\'Old\', $n1, \' \', STR_PAD_LEFT).\'</span>\';\n    echo \"<span$lstyle>\".str_pad(\'New\', $n2, \' \', STR_PAD_LEFT).\'</span>\'.n;\n  }\n  $skip = false;\n  foreach($lines as $line) {\n    if (!$line[3]) {\n      # empty space between diff parts\n      if (!$skip) echo \'<div style=\"background:#f7f7f7;height:2em;border:1px solid #ccc\";border-right:none;>&nbsp;</div>\';\n      $skip = true;\n      continue;\n    }\n    else $skip = false;\n    if ($is_diff) echo \"<span$lstyle>\".str_pad(($line[0] ? $line[0] : \'\'),$n1,\' \',STR_PAD_LEFT).\'</span>\';\n    echo \"<span$lstyle>\".str_pad(($line[1] ? $line[1] : \'\'),$n2,\' \',STR_PAD_LEFT).\'</span>\';\n    if (!$line[1]) echo \'<span style=\"background: #fdd;\">\';\n    if (!$line[0]) echo \'<span style=\"background: #dfd;\">\';\n    $str = str_replace(\"\\t\",\'  \',htmlspecialchars($line[2]));\n    if (!$line[1] or !$line[0]) {\n      # color inline diff\n      $color = $line[0] ? \'#eaa\' : \'#9e9\';\n      $str = preg_replace(\'/\\r(.*)\\r/\',\'<span style=\"background:\'.$color.\'\">${1}</span>\',$str);\n    }\n    echo \' \'.$str;\n    if (!$line[0] or !$line[1]) echo \'</span>\';\n    echo n;\n  }\n  echo \'</pre>\';\n}', 'if (@txpinterface == \'admin\') {\n  add_privs(\'rvm_plugin_diff\', \'1,2\');\n  register_tab(\'extensions\', \'rvm_plugin_diff\', \'Plugin Diff\');\n  register_callback(\'rvm_plugin_diff\', \'rvm_plugin_diff\');\n}\n\n/**\n  Diff routine uses some parts of the code found at this address:\n  http://www.holomind.de/phpnet/diff2.src.php\n  Copyright (C) 2003  Daniel Unterberger <diff.phpnet@holomind.de>\n  Copyright (C) 2005  Nils Knappmeier (next version)\n  License: GNU General Public License version 2 or later\n  http://www.gnu.org/licenses/gpl.html\n**/\n\nfunction rvm_plugin_diff($event, $step) {\n  pagetop(\'Plugin Diff\');\n\n  $name = gps(\'name\');\n  $nospace = gps(\'nospace\') ? 1 : 0;\n  $context = is_numeric(gps(\'context\')) ? intval(gps(\'context\')) : \'5\';\n\n  $plugins = array();\n  $mplugins = safe_column(\'name\', \'txp_plugin\', \'code != code_restore\');\n  $oplugins = safe_column(\'name\', \'txp_plugin\', \'code = code_restore\');\n  asort($oplugins); asort($mplugins);\n  if ($mplugins) {\n    $plugins[] = \' \';\n    if ($oplugins) $plugins[] = \' ----- MODIFIED ----- \';\n    foreach($mplugins as $plugin) $plugins[] = $plugin;\n  }\n  if ($oplugins) {\n    $plugins[] = \' \';\n    if ($mplugins) $plugins[] = \' ----- ORIGINAL ----- \';\n    foreach($oplugins as $plugin) $plugins[] = $plugin;\n  }\n  if (isset($plugins[$name])) $name = $plugins[$name];\n\n  echo \'<div style=\"border:1px solid #ccc;background:#f7f7f7;padding:1em;margin-left:2em;float:left;\">\';\n  echo form(\n    \'<p>Plugin \'.selectInput(\'name\', $plugins, $name, 0).\'</p>\'.\n    \'<p>Show <input type=\"text\" name=\"context\" maxlength=\"4\" size=\"3\" value=\"\'.$context.\'\" class=\"edit\" /> lines around each change</p>\'.\n    \'<p>\'.checkbox(\'nospace\',\'ignore\',$nospace).\' Ignore white space changes</p>\'.\n    eInput(\'rvm_plugin_diff\').\n    fInput(\'submit\',\'\',\'Show Changes\',\'publish\')\n  );\n  echo \'</div>\';\n  echo \'<br style=\"clear:both\" />\';\n\n  $rs = safe_row(\'code,code_restore,name,version\', \'txp_plugin\', \"name=\'\".safe_escape($name).\"\'\");\n  if (!$rs) return;\n  extract($rs);\n  unset($rs);\n\n  # force Unix-style line breaks\n  $code_restore = str_replace(\"\\r\\n\", n, $code_restore);\n  $code_restore = str_replace(\"\\r\", n, $code_restore);\n  $code = str_replace(\"\\r\\n\", n, $code);\n  $code = str_replace(\"\\r\", n, $code);\n\n  # split the source text into arrays of lines\n  $t1 = explode(n,trim($code_restore));\n  $t2 = explode(n,trim($code));\n  unset($code_restore,$code);\n\n  # build a reverse-index array using the line as key and line number as value\n  foreach($t1 as $i=>$x) $r1[$x][] = $i;\n  foreach($t2 as $i=>$x) $r2[$x][] = $i;\n\n  $a1=0; $a2=0;   # start at beginning of each list\n  $actions = array();\n  $is_diff = false;\n\n  # walk this loop until we reach the end of one of the lists\n  while ($a1<count($t1) && $a2<count($t2)) {\n    # if we have a common element, save it and go to the next\n    if ($nospace ? trim($t1[$a1])==trim($t2[$a2]) : $t1[$a1]==$t2[$a2])\n      { $actions[]=0; $a1++; $a2++; continue; }\n    # otherwise, find the shortest move (Manhattan-distance) from the\n    # current location\n    $is_diff = true;\n    $best1=count($t1); $best2=count($t2);\n    $s1=$a1; $s2=$a2;\n    while($s1+$s2 < $best1+$best2) {\n      $d=-1;\n      foreach((array)@$r1[$t2[$s2]] as $n)\n        if ($n>=$s1) { $d=$n; break; }\n      if ($d>=$s1 and $d+$s2 < $best1+$best2)\n        { $best1=$d; $best2=$s2; }\n      $d=-1;\n      foreach((array)@$r2[$t1[$s1]] as $n)\n        if ($n>=$s2) { $d=$n; break; }\n      if ($d>=$s2 and $s1+$d < $best1+$best2)\n        { $best1=$s1; $best2=$d; }\n      $s1++; $s2++;\n    }\n    while ($a1<$best1) { $actions[]=1; $a1++; }  # deleted elements\n    while ($a2<$best2) { $actions[]=2; $a2++; }  # added elements\n  }\n  unset($r1,$r2);\n\n  # we\'ve reached the end of one list, now walk to the end of the other\n  while($a1<count($t1)) { $actions[]=1; $a1++; }  # deleted elements\n  while($a2<count($t2)) { $actions[]=2; $a2++; }  # added elements\n\n  # create $lines array with all needed info for further processing\n  $i1=$i2=0;\n  $lines = array();\n  foreach($actions as $act) {\n    $line = ($act==1 ? $t1[$i1] : $t2[$i2]);\n    $line_old = ($act==2 ? 0 : ++$i1);\n    $line_new = ($act==1 ? 0 : ++$i2);\n    $lines[] = array($line_old, $line_new, $line, 2);\n  }\n\n  # remember how much space the line numbers will need\n  $n1 = strlen(count($t1)); if ($n1 < 3) $n1 = 3; $n1++;\n  $n2 = strlen(count($t2)); if ($n2 < 3) $n2 = 3; $n2++;\n  unset($t1,$t2,$actions);\n\n  # mark lines that are needed to provide context\n  if ($is_diff) {\n    if (trim($context) != \'\') {\n      $count = 0;\n      foreach($lines as $nr => $line) {\n        if ($line[0] and $line[1]) {\n          if ($count) $count--;\n          else $lines[$nr][3]--;\n        }\n        else $count = $context;\n      }\n      $count = 0;\n      for($nr = count($lines)-1; $nr >= 0; $nr--) {\n        if ($lines[$nr][0] and $lines[$nr][1]) {\n          if ($count) $count--;\n          else $lines[$nr][3]--;\n        }\n        else $count = $context;\n      }\n    }\n\n    # unified diff\n    if (gps(\'format\')) {\n      $udiff = \'\';\n      $diff = array(0,0,0,0);\n      foreach($lines as $line) {\n        if (!$line[3]) {\n          if ($out) {\n            $udiff .= $diff[4].$out;\n            $out = \'\';\n            $diff = array(0,0,0,0);\n          }\n        }\n        else {\n          if (!$diff[0]) $diff[0] = $line[0];\n          if (!$diff[1]) $diff[1] = $line[1];\n          if ($line[0]) $diff[2] = $line[0];\n          if ($line[1]) $diff[3] = $line[1];\n          $diff[4] = \'@@ -\'.$diff[0].\',\'.($diff[2]+1-$diff[0]).\' +\'.$diff[1].\',\'.($diff[3]+1-$diff[1]).\' @@\'.n;\n          $out .= (!$line[1] ? \'-\' : (!$line[0] ? \'+\' : \' \')) . $line[2] . n;\n        }\n      }\n      if ($out) $udiff .= $diff[4].$out;\n\n      # send unified diff file\n      if ($udiff) {\n        $plugin = $name.\'-\'.$version;\n        $time = strftime(\' %Y-%m-%d %H:%M:%S.000000000 %z\',time());\n        $udiff = \'--- \'.$plugin.\'.orig\'.$time.n.\'+++ \'.$plugin.\'.new\'.$time.n.$udiff;\n        ob_clean();\n        header(\'Content-Description: Plugin Diff\');\n        header(\'Content-Type: text/plain;charset=utf-8\');\n        header(\'Content-Length: \' . strlen($udiff));\n        header(\'Content-Disposition: attachment; filename=\"\' . $plugin . \'.diff\"\');\n        echo $udiff;\n        ob_flush();\n        flush();\n        exit;\n      }\n    }\n\n    # add inline diff markers\n    $ins = $del = array();\n    foreach($lines as $nr => $line) {\n      if ($line[0] and $line[1]) {\n        if ($del and count($del) == count($ins)) {\n          while($delnr = array_shift($del) and $insnr = array_shift($ins)) {\n            $begin = 0; $end = 0;\n            $delstr = $lines[$delnr][2];\n            $insstr = $lines[$insnr][2];\n            $max = min(strlen($insstr),strlen($delstr));\n            while($begin <= $max and substr($delstr,$begin,1) == substr($insstr,$begin,1)) $begin++;\n            while($end <= $max and substr($delstr,-$end-1,1) == substr($insstr,-$end-1,1)) $end++;\n            if (!$begin and !$end) continue;\n            foreach(array($delnr,$insnr) as $linenr) {\n              $str = $lines[$linenr][2];\n              $len = strlen($str) - $begin - $end;\n              if ($len < 0) { $end += $len; $len = 0; }\n              $lines[$linenr][2] = substr($str,0,$begin).\"\\r\".substr($str,$begin,$len).\"\\r\".($end?substr($str,-$end):\'\');\n            }\n          }\n        }\n        $ins = $del = array();\n      }\n      else {\n        if ($line[0]) $del[] = $nr;\n        else $ins[] = $nr;\n      }\n    }\n  }\n\n  # download link\n  if ($is_diff) echo \'<p style=\"margin-left:3em\"><a href=\"?event=rvm_plugin_diff\'.a.\'format=diff\'.a.\'name=\'.urlencode($name).a.\'context=\'.$context.a.\'nospace=\'.$nospace.\'\">Download in <strong>unified diff</strong> format</a></p>\';\n\n  # color diff\n  $lstyle = \' style=\"color: #999; border-right:1px solid #ccc;padding:0em .5em 0em 0em;\"\';\n  echo \'<pre>\';\n  if ($is_diff) {\n    echo \"<span$lstyle>\".str_pad(\'Old\', $n1, \' \', STR_PAD_LEFT).\'</span>\';\n    echo \"<span$lstyle>\".str_pad(\'New\', $n2, \' \', STR_PAD_LEFT).\'</span>\'.n;\n  }\n  $skip = false;\n  foreach($lines as $line) {\n    if (!$line[3]) {\n      # empty space between diff parts\n      if (!$skip) echo \'<div style=\"background:#f7f7f7;height:2em;border:1px solid #ccc\";border-right:none;>&nbsp;</div>\';\n      $skip = true;\n      continue;\n    }\n    else $skip = false;\n    if ($is_diff) echo \"<span$lstyle>\".str_pad(($line[0] ? $line[0] : \'\'),$n1,\' \',STR_PAD_LEFT).\'</span>\';\n    echo \"<span$lstyle>\".str_pad(($line[1] ? $line[1] : \'\'),$n2,\' \',STR_PAD_LEFT).\'</span>\';\n    if (!$line[1]) echo \'<span style=\"background: #fdd;\">\';\n    if (!$line[0]) echo \'<span style=\"background: #dfd;\">\';\n    $str = str_replace(\"\\t\",\'  \',htmlspecialchars($line[2]));\n    if (!$line[1] or !$line[0]) {\n      # color inline diff\n      $color = $line[0] ? \'#eaa\' : \'#9e9\';\n      $str = preg_replace(\'/\\r(.*)\\r/\',\'<span style=\"background:\'.$color.\'\">${1}</span>\',$str);\n    }\n    echo \' \'.$str;\n    if (!$line[0] or !$line[1]) echo \'</span>\';\n    echo n;\n  }\n  echo \'</pre>\';\n}', '064262d7a61ce4b0f139ffec3f1ebfb5', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rvm_privileged', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.5', 'Use TXP admin user privileges on public side', ' <style>\n   li code {font-weight: bold;}\n   pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\n   h1, h2, h3, h3 code {font-family: sans-serif; font-weight: bold;}\n   h1, h2, h3 {margin-left: -1em;}\n   h2, h3 {margin-top: 2em;}\n   h1 {font-size: 3em;}\n   h2 {font-size: 2em;}\n   h3 {font-size: 1.5em;}\n   li a code {font-weight: normal;}\n   .required, .warning {color:red;}\n </style>\n\n<h1>Privileged</h1>\n\n<p>Requires <span class=\"caps\">TXP</span> 4.0.6 or higher.</p>\n\n<p>This plugin provides the following tags:<br />\n<ul>\n	<li><a href=\"#rvm_privileged\"> <code>&lt;txp:rvm_privileged /&gt;</code> </a></li>\n	<li><a href=\"#rvm_if_privileged\"> <code>&lt;txp:rvm_if_privileged /&gt;</code> </a></li>\n	<li><a href=\"#rvm_privileged_user\"> <code>&lt;txp:rvm_privileged_user /&gt;</code> </a></li><br />\n</ul></p>\n\n\n<h3 id=\"rvm_privileged\"><code>&lt;txp:rvm_privileged /&gt;</code></h3>\n\n<p>When a non-privileged user visits a page containing this tag, a <span class=\"caps\">HTTP</span> 403 Forbidden error is shown.</p>\n\n<p>The error page can be customized in two ways:<br />\n<ol>\n	<li>modify the <code>error_default</code> template page.</li>\n	<li>create a new template page called <code>error_403</code> (recommended!)</li><br />\n</ol></p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;comma-separated values&quot;</code> <br />\nList of allowed <span class=\"caps\">TXP</span> login names. Default is to accept any logged in user.</li>\n	<li><code>level=&quot;comma-separated values&quot;</code> <br />\nList of allowed Privilege levels<sup class=\"footnote\" id=\"fnrev9264895595d37772dcae4e-1\"><a href=\"#fn9264895595d37772dcae4e-1\">1</a></sup>. Default is to accept logged in users regardless of privilege level.</li>\n</ul>\n\n<h4>Example</h4>\n\n<p>Allow access to user &#8216;ruud&#8217; and anyone with privilege levels 2,3 and 5:</p>\n\n<pre><code>&lt;txp:rvm_privileged name=&quot;ruud&quot; level=&quot;2,3,5&quot; /&gt;</code></pre>\n\n\n<h3 id=\"rvm_if_privileged\"><code>&lt;txp:rvm_if_privileged /&gt;</code></h3>\n\n<p>Can be used to display different content to privileged users.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;comma-separated values&quot;</code> <br />\nList of allowed <span class=\"caps\">TXP</span> login names. Default is to accept any logged in user.</li>\n	<li><code>level=&quot;comma-separated values&quot;</code> <br />\nList of allowed Privilege levels<sup class=\"footnote\"><a href=\"#fn9264895595d37772dcae4e-1\">1</a></sup>. Default is to accept logged in users regardless of privilege level.</li>\n</ul>\n\n<h4>Example</h4>\n\n<pre><code>&lt;txp:rvm_if_privileged&gt;\n  You are logged in!\n&lt;txp:else /&gt;\n  You are NOT logged in!\n&lt;/txp:rvm_if_privileged&gt;</code></pre>\n\n\n<h3 id=\"rvm_privileged_user\"><code>&lt;txp:rvm_privileged_user /&gt;</code></h3>\n\n<p>Display various properties of the logged in user.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>type=&quot;name|email|privilege|level&quot;</code> <br />\n Select one of: login name (name), email address (email), privilege (privilege) or numeric privilege (level). By default the RealName of the logged in user is shown.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<p>Show the real name of the logged in user:</p>\n\n<pre><code>&lt;txp:rvm_privileged_user /&gt;</code></pre>\n\n<p>Show the email address of the logged in user:</p>\n\n<pre><code>&lt;txp:rvm_privileged_user type=&quot;email&quot;&gt;</code></pre>\n\n<h3>Footnotes</h3>\n\n<p class=\"footnote\" id=\"fn9264895595d37772dcae4e-1\"><sup>1</sup> Privilege levels:<br />\n<ol>\n	<li>Publisher</li>\n	<li>Managing Editor</li>\n	<li>Copy Editor</li>\n	<li>Staff Writer</li>\n	<li>Freelancer</li>\n	<li>Designer</li><br />\n</ol></p>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'rvm_privileged\')\n    ->register(\'rvm_if_privileged\')\n    ->register(\'rvm_privileged_user\');\n}\n\n\nfunction rvm_privileged($atts)\n{\n  extract(lAtts(array(\n    \'name\' => \'\',\n    \'level\'  => \'\'\n  ), $atts));\n\n  if (!rvm_is_privileged($name, $level))\n  {\n    ob_clean();\n    txp_die(\'Forbidden\', 403);\n  }\n}\n\n\nfunction rvm_if_privileged($atts, $thing = \'\')\n{\n  extract(lAtts(array(\n    \'name\' => \'\',\n    \'level\' => \'\'\n  ), $atts));\n\n  return parse(EvalElse($thing, rvm_is_privileged($name, $level)));\n}\n\n\nfunction rvm_is_privileged($name, $level)\n{\n  static $ili = 0;\n\n  if ($ili === 0) $ili = is_logged_in();\n\n  if (!$ili) return FALSE;\n\n  $isname  = ($name and in_array($ili[\'name\'], do_list($name)));\n  $islevel = ($level and in_array($ili[\'privs\'], do_list($level)));\n\n  if ($name)\n  {\n    if ($level)\n    {\n      return ($isname and $islevel);\n    }\n    else\n    {\n      return $isname;\n    }\n  }\n  elseif($level)\n  {\n    return $islevel;\n  }\n  else\n  {\n    return TRUE;\n  }\n}\n\n\nfunction rvm_privileged_user($atts)\n{\n  extract(lAtts(array(\n    \'type\' => \'RealName\'\n  ), $atts));\n\n  static $ili = 0;\n\n  if ($ili === 0) $ili = is_logged_in();\n\n  $types = array(\'RealName\', \'name\', \'email\', \'privilege\', \'level\');\n\n  if (!$ili or !in_array($type, $types)) return;\n\n  $levels = array(\'none\', \'publisher\', \'managing editor\', \'copy editor\', \'staff writer\', \'freelancer\', \'designer\');\n\n  switch ($type)\n  {\n    case \'privilege\': $out = $levels[$ili[\'privs\']]; break;\n    case \'level\'    : $out = $ili[\'privs\']; break;\n    default         : $out = $ili[$type];\n  }\n\n  return htmlspecialchars($out);\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'rvm_privileged\')\n    ->register(\'rvm_if_privileged\')\n    ->register(\'rvm_privileged_user\');\n}\n\n\nfunction rvm_privileged($atts)\n{\n  extract(lAtts(array(\n    \'name\' => \'\',\n    \'level\'  => \'\'\n  ), $atts));\n\n  if (!rvm_is_privileged($name, $level))\n  {\n    ob_clean();\n    txp_die(\'Forbidden\', 403);\n  }\n}\n\n\nfunction rvm_if_privileged($atts, $thing = \'\')\n{\n  extract(lAtts(array(\n    \'name\' => \'\',\n    \'level\' => \'\'\n  ), $atts));\n\n  return parse(EvalElse($thing, rvm_is_privileged($name, $level)));\n}\n\n\nfunction rvm_is_privileged($name, $level)\n{\n  static $ili = 0;\n\n  if ($ili === 0) $ili = is_logged_in();\n\n  if (!$ili) return FALSE;\n\n  $isname  = ($name and in_array($ili[\'name\'], do_list($name)));\n  $islevel = ($level and in_array($ili[\'privs\'], do_list($level)));\n\n  if ($name)\n  {\n    if ($level)\n    {\n      return ($isname and $islevel);\n    }\n    else\n    {\n      return $isname;\n    }\n  }\n  elseif($level)\n  {\n    return $islevel;\n  }\n  else\n  {\n    return TRUE;\n  }\n}\n\n\nfunction rvm_privileged_user($atts)\n{\n  extract(lAtts(array(\n    \'type\' => \'RealName\'\n  ), $atts));\n\n  static $ili = 0;\n\n  if ($ili === 0) $ili = is_logged_in();\n\n  $types = array(\'RealName\', \'name\', \'email\', \'privilege\', \'level\');\n\n  if (!$ili or !in_array($type, $types)) return;\n\n  $levels = array(\'none\', \'publisher\', \'managing editor\', \'copy editor\', \'staff writer\', \'freelancer\', \'designer\');\n\n  switch ($type)\n  {\n    case \'privilege\': $out = $levels[$ili[\'privs\']]; break;\n    case \'level\'    : $out = $ili[\'privs\']; break;\n    default         : $out = $ili[$type];\n  }\n\n  return htmlspecialchars($out);\n}\n', '1bd74ef8a9bc571195d4fbbaa6232c3a', '', '', 0, 5, 0),
('rvm_substr', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.4', 'Cut XHTML at a specified character limit', '<p>This plugin allows you to cut string of valid <span class=\"caps\">XTHML</span> at a specified character limit (<span class=\"caps\">UTF</span>-8 and <span class=\"caps\">HTML</span>-escape-code aware) and then add a trailing ellipsis. The end result is still valid <span class=\"caps\">XHTML</span>.</p>\n\n<p><strong>Example:</strong></p>\n\n<pre><code>&lt;txp:rvm_substr length=&quot;8&quot;&gt;&lt;txp:body /&gt;&lt;/txp:rvm_substr&gt;</code></pre>\n\n<p>will output &#8220;long str&#8230;&#8221;, assuming the body contains &#8216;long string&#8217;.</p>\n\n<p><strong>Attributes:</strong></p>\n\n<ul>\n	<li>length=&#8220;integer&#8221;<br />\nThe length of the string you want returned, not counting the length of the trailing text and tags. Defaults to 10.</li>\n</ul>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_substr\');\n}\n\n\nfunction rvm_substr($atts, $thing)\n{\n  extract(lAtts(array(\n    \'length\' => 10,\n  ), $atts));\n\n  assert_int($length);\n\n  $thing    = parse($thing);\n  $parsed   = preg_split(\'/(<[^>]*>)/\', $thing, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n  $ellipsis = FALSE;\n  $istag    = FALSE;\n  $tags     = array();\n  $total    = 0;\n  $out      = \'\';\n\n  foreach ($parsed as $chunk)\n  {\n    if ($total === $length)\n    {\n      $ellipsis = TRUE;\n      break;\n    }\n\n    if ($istag)\n    {\n      if (substr($chunk, 1, 1) === \'/\')\n      {\n        array_pop($tags);\n      }\n      elseif (substr($chunk, -2, 1) !== \'/\')\n      {\n        array_push($tags, $chunk);\n      }\n\n      $out .= $chunk;\n    }\n    else\n    {\n      if (trim($chunk) !== \'\')\n      {\n        $len = preg_match_all(\'/(?:[^&]|&[^;]+;)/us\', $chunk, $dummy);\n\n        if ($total + $len <= $length)\n        {\n          $out   .= $chunk;\n          $total += $len;\n        }\n        else\n        {\n          preg_match(\'/^(?:[^&]|&[^;]+;){\'.($length-$total).\'}/us\', $chunk, $match);\n          $out  .= $match[0];\n          $total = $length;\n          $ellipsis = TRUE;\n        }\n      }\n      else\n      {\n        $out .= $chunk;\n      }\n    }\n\n    $istag = !$istag;\n  }\n\n  $out = rtrim($out) . ($ellipsis ? \'…\' : \'\');\n\n  foreach(array_reverse($tags) as $tag)\n  {\n    preg_match(\'/^<(\\w+)/s\', $tag, $match);\n    $out .= \'</\'.$match[1].\'>\';\n  }\n\n  return $out;\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'rvm_substr\');\n}\n\n\nfunction rvm_substr($atts, $thing)\n{\n  extract(lAtts(array(\n    \'length\' => 10,\n  ), $atts));\n\n  assert_int($length);\n\n  $thing    = parse($thing);\n  $parsed   = preg_split(\'/(<[^>]*>)/\', $thing, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n  $ellipsis = FALSE;\n  $istag    = FALSE;\n  $tags     = array();\n  $total    = 0;\n  $out      = \'\';\n\n  foreach ($parsed as $chunk)\n  {\n    if ($total === $length)\n    {\n      $ellipsis = TRUE;\n      break;\n    }\n\n    if ($istag)\n    {\n      if (substr($chunk, 1, 1) === \'/\')\n      {\n        array_pop($tags);\n      }\n      elseif (substr($chunk, -2, 1) !== \'/\')\n      {\n        array_push($tags, $chunk);\n      }\n\n      $out .= $chunk;\n    }\n    else\n    {\n      if (trim($chunk) !== \'\')\n      {\n        $len = preg_match_all(\'/(?:[^&]|&[^;]+;)/us\', $chunk, $dummy);\n\n        if ($total + $len <= $length)\n        {\n          $out   .= $chunk;\n          $total += $len;\n        }\n        else\n        {\n          preg_match(\'/^(?:[^&]|&[^;]+;){\'.($length-$total).\'}/us\', $chunk, $match);\n          $out  .= $match[0];\n          $total = $length;\n          $ellipsis = TRUE;\n        }\n      }\n      else\n      {\n        $out .= $chunk;\n      }\n    }\n\n    $istag = !$istag;\n  }\n\n  $out = rtrim($out) . ($ellipsis ? \'…\' : \'\');\n\n  foreach(array_reverse($tags) as $tag)\n  {\n    preg_match(\'/^<(\\w+)/s\', $tag, $match);\n    $out .= \'</\'.$match[1].\'>\';\n  }\n\n  return $out;\n}\n', 'bb4e2c2e377ca39a6ff2b72d8bd6a9ee', '', '', 0, 5, 0),
('rvm_utf8_to_utf8mb4', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.2', 'Alter pre-MySQL 5.5.3 tables to use full UTF-8 charset (utf8mb4 instead of utf8)', '<h1>utf8 to utf8mb4</h1>\n\n<p><strong><span class=\"caps\">WARNING</span>: carefully read this documentation and <span class=\"caps\">BACKUP</span> <span class=\"caps\">YOUR</span> <span class=\"caps\">DATABASE</span> before activating/using this plugin.</strong></p>\n\n<p>Textpattern always uses &#8216;utf8&#8217; to store data in the database, but depending on which MySQL version you used when installing Textpattern, the character set of the tables and columns differs:</p>\n\n<ol>\n	<li>MySQL versions older than 4.1: the character set of the tables created by Textpattern is typically &#8216;latin1&#8217;.</li>\n	<li>MySQL versions 4.1 and higher: the tables are set to use the &#8216;utf8&#8217; character set, which matches the actual character set of the data stored in those tables, but doesn&#8217;t allow 4-byte <span class=\"caps\">UTF</span>-8 characters.</li>\n	<li>MySQL versions 5.5.3 and higher + <span class=\"caps\">TXP</span> 4.6 and higher: the tables are set to use the &#8216;utf8mb4&#8217; character set, which allows the full range of <span class=\"caps\">UTF</span>-8 characters to be used, including emojis.</li>\n</ol>\n\n<p>Having the tables/columns themselves use the same character set as the data stored in them has some advantages when performing searches or sorting in Textpattern, especially if you use characters outside US-<span class=\"caps\">ASCII</span>.</p>\n\n<p>If you upgrade your Textpattern version to 4.6 or higher + MySQL version from 5.5.3 or higher, the table/column character sets are not automatically converted to &#8216;utf8mb4&#8217;, so they stay &#8216;latin1&#8217; or &#8216;utf8&#8217; while the data stored in those tables is still &#8216;utf8&#8217;.</p>\n\n<p>To switch from &#8216;latin1&#8217; to &#8216;utf8&#8217;, use the rvm_latin1_to_utf8 plugin before using this one.<br />\nTo switch from &#8216;utf8&#8217; to &#8216;utf8mb4&#8217;, this plugin can be used.</p>\n\n<p>This plugin assumes the current charset for tables and columns is set to &#8216;utf8&#8217; and changes that to &#8216;utf8mb4&#8217;. This can be done in <span class=\"caps\">TXP</span> 4.6 or higher, because the relevant indexes have already been changed to allow for the extra byte space per character as required by &#8216;utf8mb4&#8217;. Since the old &#8216;utf8&#8217; columns couldn&#8217;t contain 4-byte <span class=\"caps\">UTF</span>-8 characters, no data is lost when changing to utf8mb4.</p>\n\n<p>Afterwards an attempt is made to update the &#8216;dbcharset&#8217; setting in the <code>textpattern/config.php</code> file to &#8216;utf8mb4&#8217;. Should this fail due to lack of permissions, you will be asked to make this change yourself. Do not skip this step, otherwise you will experience irreparable data corruption so severe that you will cry like a baby (you made backups, right?), much to the amusement of onlookers.</p>\n\n<p>How to use this plugin:</p>\n\n<ol>\n	<li><strong><span class=\"caps\">MAKE</span> <span class=\"caps\">BACKUPS</span> <span class=\"caps\">FIRST</span>!</strong> (not just the <span class=\"caps\">TXP</span> tables, but the entire database)</li>\n	<li>Make sure you have MySQL version 5.5.3 or higher installed.</li>\n	<li>Upgrade to at least Textpattern 4.6 or higher.</li>\n	<li>Activate the plugin.</li>\n	<li><a href=\"?event=rvm_utf8_to_utf8mb4\">Click here</a> and follow the instructions on the screen.</li>\n</ol>', '\nif (txpinterface == \'admin\' and gps(\'event\') == \'rvm_utf8_to_utf8mb4\')\n{\n  add_privs(\'rvm_utf8_to_utf8mb4\', \'1\');\n  register_tab(\'extensions\', \'rvm_utf8_to_utf8mb4\', \'utf8_to_utf8mb4\');\n  register_callback(\'rvm_utf8_to_utf8mb4\', \'rvm_utf8_to_utf8mb4\');\n}\n\nfunction rvm_utf8_to_utf8mb4()\n{\n  global $txpcfg, $DB, $dbversion;\n\n  $debug = FALSE;\n  $error = FALSE;\n\n  require_privs(\'rvm_utf8_to_utf8mb4\');\n  pagetop(\'\');\n\n  echo \'<div style=\"margin-left: 50px\">\';\n\n  if (mysqli_get_server_version($DB->link) < 50503) {\n    $error = \'MySQL version is lower than 5.5.3\';\n  } else {\n    if (false !== strpos(mysqli_get_client_info($DB->link), \'mysqlnd\')) {\n      // mysqlnd 5.0.9+ required\n      if (mysqli_get_client_version($mylink) < 50009) {\n        $error = \'mysqlnd client version is below 5.0.9\';\n      }\n    } else {\n      // libmysqlclient 5.5.3+ required\n      if (mysqli_get_client_version($DB->link) < 50503) {\n        $error = \'libmysqlclient version is below 5.5.3\';\n      }\n    }\n  }\n\n  if (version_compare($dbversion, \'4.6.0\', \'<\')) {\n    $error = \'Textpattern database version is below 4.6.0. Upgrade Textpattern first\';\n  }\n\n  if ($txpcfg[\'dbcharset\'] == \'latin1\') {\n    $error = \'dbcharset is latin1, so you have to use the rvm_latin1_to_utf8 plugin first\';\n  }\n\n  if ($error)\n  {\n    echo graf(\'The rvm_utf8_to_utf8mb4 plugin has been removed, because your \'.$error);\n    safe_delete(\'txp_plugin\', \"name = \'rvm_utf8_to_utf8mb4\'\");\n    rvm_utf8_to_utf8mb4_end();\n  }\n\n  # standard TXP tables\n  $txptables = array(\n    \'textpattern\',\n    \'txp_category\',\n    \'txp_css\',\n    \'txp_discuss\',\n    \'txp_discuss_ipban\',\n    \'txp_discuss_nonce\',\n    \'txp_file\',\n    \'txp_form\',\n    \'txp_image\',\n    \'txp_lang\',\n    \'txp_link\',\n    \'txp_log\',\n    \'txp_page\',\n    \'txp_plugin\',\n    \'txp_prefs\',\n    \'txp_section\',\n    \'txp_token\',\n    \'txp_users\'\n  );\n\n  # find tables that match the TXP table prefix, mark standard and selected ones.\n  $mytables = ps(\'mytables\', array()) ? ps(\'mytables\') : array();\n  $unknown  = FALSE;\n\n  if ($rs = safe_query(\"SHOW TABLES LIKE \'\".addcslashes(doSlash(PFX), \'%_\').\"%\'\"))\n  {\n    while ($row = mysqli_fetch_row($rs))\n    {\n      $table = substr($row[0], strlen(PFX));\n\n      if (in_array($table, $txptables) or in_array($table, $mytables))\n      {\n        $tables[] = $table;\n      }\n      else\n      {\n        $unknown = TRUE;\n      }\n\n      $inputs[] =\n        \'<label>\'.\n          \'<input type=\"checkbox\" name=\"mytables[]\" value=\"\'.htmlspecialchars($table).\'\" \'.\n            (in_array($table, $txptables) ? \'disabled=\"disabled\" checked=\"checked\"\' : \'\').\' />\'.\n          htmlspecialchars(PFX.$table).\n        \'</label>\';\n    }\n  }\n  else\n  {\n    echo graf(\'Hmmm... strange, I cannot find any tables that match your TXP table prefix\');\n  }\n\n  # ask user what to do with tables we\'re not sure about.\n  if ($unknown and !ps(\'continue\'))\n  {\n    echo\n      graf(\'Below, a list of tables is shown that may be part of your Textpattern install.\').\n      graf(\'Some of these tables may have been created by plugins you have installed.<br />\n        In that case, check the tables that these plugins have added.<br />\n        Be careful not to check tables that are not related to this Textpattern install!\').\n      graf(\'The standard Textpattern tables are already checked.\').\n      form(\n        join(\'<br />\', $inputs).\n        graf(\n          eInput(\'rvm_utf8_to_utf8mb4\').\'<br />\'.\n          fInput(\'submit\', \'continue\', \'Continue\', \'publish\')\n        )\n      );\n\n    rvm_utf8_to_utf8mb4_end();\n  }\n\n  echo graf(\'Updating Textpattern tables...\');\n\n  # loop through tables\n  foreach ($tables as $table)\n  {\n    $utf8mb4 = array();\n\n    # prepare alter statements for columns\n    $columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx($table));\n\n    if ($columns) foreach ($columns as $column)\n    {\n      extract($column);\n\n      if (preg_match(\'/^(char|varchar|tinytext|text|mediumtext|longtext|enum|set)\\b/\', $Type))\n      {\n        $utf8mb4[] = \'MODIFY `\'.doSlash($Field).\'` \'.$Type.\'\n          CHARACTER SET utf8mb4\n          COLLATE utf8mb4_unicode_ci\'.\n          ($Null == \'YES\' ? \' NULL\' : \' NOT NULL\').\n          ($Default == \'NULL\'\n            ? ((preg_match(\'/text/\', $Type) || $Null == \'NO\') ? \'\' : \'DEFAULT NULL\')\n            : \" DEFAULT \'\".doSlash($Default).\"\'\"\n          );\n      }\n    }\n\n    $success = TRUE;\n\n    # alter the table\n    $success *= safe_alter($table, join(\', \', $utf8mb4).\', DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci\', $debug);\n  }\n\n  # while we\'re here, might as well optimize the tables\n  safe_query(\"OPTIMIZE TABLE \".join(\', \', array_map(\'safe_pfx\', $tables)), $debug);\n\n  if ($success)\n  {\n    echo graf(\'Successfully completed.\');\n\n    $file   = txpath.\'/config.php\';\n    $config = file_get_contents($file);\n    $config = str_replace(\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\'\'.n, \'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\';\'.n, $config); # fix missing ; on line added by in rvm_latin1_to_utf8\n    $config = str_replace(\'?>\', n.\'# rvm_utf8_to_utf8mb4 plugin added the following line to ensure a correct dbcharset\'.n.\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8mb4\\\';\'.n.\'?>\', $config);\n\n    if ($txpcfg[\'dbcharset\'] == \'utf8mb4\'\n      or is_writable($file)\n      and $handle = fopen($file, \'w\')\n      and fwrite($handle, $config) !== FALSE\n      and fclose($handle))\n    {\n      echo graf(\'The rvm_utf8_to_utf8mb4 plugin has been automatically de-installed.\');\n      safe_delete(\'txp_plugin\', \"name = \'rvm_utf8_to_utf8mb4\'\");\n    }\n    else\n    {\n      echo graf(\'<b>WARNING: textpattern/config.php could not be updated automatically. Please update your textpattern/config.php file to contain <code>$txpcfg[\\\'dbcharset\\\'] = \\\'utf8mb4\\\';</code></b> and deinstall the rvm_utf8_to_utf8mb4 plugin manually\');\n    }\n  }\n  else\n  {\n    echo graf(\'Due to one or more errors the tables could not be updated correctly. You may have to restore a backup.\');\n  }\n\n  rvm_utf8_to_utf8mb4_end();\n}\n\nfunction rvm_utf8_to_utf8mb4_end()\n{\n  echo \'</div>\';\n  end_page();\n  exit();\n}\n', '\nif (txpinterface == \'admin\' and gps(\'event\') == \'rvm_utf8_to_utf8mb4\')\n{\n  add_privs(\'rvm_utf8_to_utf8mb4\', \'1\');\n  register_tab(\'extensions\', \'rvm_utf8_to_utf8mb4\', \'utf8_to_utf8mb4\');\n  register_callback(\'rvm_utf8_to_utf8mb4\', \'rvm_utf8_to_utf8mb4\');\n}\n\nfunction rvm_utf8_to_utf8mb4()\n{\n  global $txpcfg, $DB, $dbversion;\n\n  $debug = FALSE;\n  $error = FALSE;\n\n  require_privs(\'rvm_utf8_to_utf8mb4\');\n  pagetop(\'\');\n\n  echo \'<div style=\"margin-left: 50px\">\';\n\n  if (mysqli_get_server_version($DB->link) < 50503) {\n    $error = \'MySQL version is lower than 5.5.3\';\n  } else {\n    if (false !== strpos(mysqli_get_client_info($DB->link), \'mysqlnd\')) {\n      // mysqlnd 5.0.9+ required\n      if (mysqli_get_client_version($mylink) < 50009) {\n        $error = \'mysqlnd client version is below 5.0.9\';\n      }\n    } else {\n      // libmysqlclient 5.5.3+ required\n      if (mysqli_get_client_version($DB->link) < 50503) {\n        $error = \'libmysqlclient version is below 5.5.3\';\n      }\n    }\n  }\n\n  if (version_compare($dbversion, \'4.6.0\', \'<\')) {\n    $error = \'Textpattern database version is below 4.6.0. Upgrade Textpattern first\';\n  }\n\n  if ($txpcfg[\'dbcharset\'] == \'latin1\') {\n    $error = \'dbcharset is latin1, so you have to use the rvm_latin1_to_utf8 plugin first\';\n  }\n\n  if ($error)\n  {\n    echo graf(\'The rvm_utf8_to_utf8mb4 plugin has been removed, because your \'.$error);\n    safe_delete(\'txp_plugin\', \"name = \'rvm_utf8_to_utf8mb4\'\");\n    rvm_utf8_to_utf8mb4_end();\n  }\n\n  # standard TXP tables\n  $txptables = array(\n    \'textpattern\',\n    \'txp_category\',\n    \'txp_css\',\n    \'txp_discuss\',\n    \'txp_discuss_ipban\',\n    \'txp_discuss_nonce\',\n    \'txp_file\',\n    \'txp_form\',\n    \'txp_image\',\n    \'txp_lang\',\n    \'txp_link\',\n    \'txp_log\',\n    \'txp_page\',\n    \'txp_plugin\',\n    \'txp_prefs\',\n    \'txp_section\',\n    \'txp_token\',\n    \'txp_users\'\n  );\n\n  # find tables that match the TXP table prefix, mark standard and selected ones.\n  $mytables = ps(\'mytables\', array()) ? ps(\'mytables\') : array();\n  $unknown  = FALSE;\n\n  if ($rs = safe_query(\"SHOW TABLES LIKE \'\".addcslashes(doSlash(PFX), \'%_\').\"%\'\"))\n  {\n    while ($row = mysqli_fetch_row($rs))\n    {\n      $table = substr($row[0], strlen(PFX));\n\n      if (in_array($table, $txptables) or in_array($table, $mytables))\n      {\n        $tables[] = $table;\n      }\n      else\n      {\n        $unknown = TRUE;\n      }\n\n      $inputs[] =\n        \'<label>\'.\n          \'<input type=\"checkbox\" name=\"mytables[]\" value=\"\'.htmlspecialchars($table).\'\" \'.\n            (in_array($table, $txptables) ? \'disabled=\"disabled\" checked=\"checked\"\' : \'\').\' />\'.\n          htmlspecialchars(PFX.$table).\n        \'</label>\';\n    }\n  }\n  else\n  {\n    echo graf(\'Hmmm... strange, I cannot find any tables that match your TXP table prefix\');\n  }\n\n  # ask user what to do with tables we\'re not sure about.\n  if ($unknown and !ps(\'continue\'))\n  {\n    echo\n      graf(\'Below, a list of tables is shown that may be part of your Textpattern install.\').\n      graf(\'Some of these tables may have been created by plugins you have installed.<br />\n        In that case, check the tables that these plugins have added.<br />\n        Be careful not to check tables that are not related to this Textpattern install!\').\n      graf(\'The standard Textpattern tables are already checked.\').\n      form(\n        join(\'<br />\', $inputs).\n        graf(\n          eInput(\'rvm_utf8_to_utf8mb4\').\'<br />\'.\n          fInput(\'submit\', \'continue\', \'Continue\', \'publish\')\n        )\n      );\n\n    rvm_utf8_to_utf8mb4_end();\n  }\n\n  echo graf(\'Updating Textpattern tables...\');\n\n  # loop through tables\n  foreach ($tables as $table)\n  {\n    $utf8mb4 = array();\n\n    # prepare alter statements for columns\n    $columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx($table));\n\n    if ($columns) foreach ($columns as $column)\n    {\n      extract($column);\n\n      if (preg_match(\'/^(char|varchar|tinytext|text|mediumtext|longtext|enum|set)\\b/\', $Type))\n      {\n        $utf8mb4[] = \'MODIFY `\'.doSlash($Field).\'` \'.$Type.\'\n          CHARACTER SET utf8mb4\n          COLLATE utf8mb4_unicode_ci\'.\n          ($Null == \'YES\' ? \' NULL\' : \' NOT NULL\').\n          ($Default == \'NULL\'\n            ? ((preg_match(\'/text/\', $Type) || $Null == \'NO\') ? \'\' : \'DEFAULT NULL\')\n            : \" DEFAULT \'\".doSlash($Default).\"\'\"\n          );\n      }\n    }\n\n    $success = TRUE;\n\n    # alter the table\n    $success *= safe_alter($table, join(\', \', $utf8mb4).\', DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci\', $debug);\n  }\n\n  # while we\'re here, might as well optimize the tables\n  safe_query(\"OPTIMIZE TABLE \".join(\', \', array_map(\'safe_pfx\', $tables)), $debug);\n\n  if ($success)\n  {\n    echo graf(\'Successfully completed.\');\n\n    $file   = txpath.\'/config.php\';\n    $config = file_get_contents($file);\n    $config = str_replace(\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\'\'.n, \'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8\\\';\'.n, $config); # fix missing ; on line added by in rvm_latin1_to_utf8\n    $config = str_replace(\'?>\', n.\'# rvm_utf8_to_utf8mb4 plugin added the following line to ensure a correct dbcharset\'.n.\'$txpcfg[\\\'dbcharset\\\'] = \\\'utf8mb4\\\';\'.n.\'?>\', $config);\n\n    if ($txpcfg[\'dbcharset\'] == \'utf8mb4\'\n      or is_writable($file)\n      and $handle = fopen($file, \'w\')\n      and fwrite($handle, $config) !== FALSE\n      and fclose($handle))\n    {\n      echo graf(\'The rvm_utf8_to_utf8mb4 plugin has been automatically de-installed.\');\n      safe_delete(\'txp_plugin\', \"name = \'rvm_utf8_to_utf8mb4\'\");\n    }\n    else\n    {\n      echo graf(\'<b>WARNING: textpattern/config.php could not be updated automatically. Please update your textpattern/config.php file to contain <code>$txpcfg[\\\'dbcharset\\\'] = \\\'utf8mb4\\\';</code></b> and deinstall the rvm_utf8_to_utf8mb4 plugin manually\');\n    }\n  }\n  else\n  {\n    echo graf(\'Due to one or more errors the tables could not be updated correctly. You may have to restore a backup.\');\n  }\n\n  rvm_utf8_to_utf8mb4_end();\n}\n\nfunction rvm_utf8_to_utf8mb4_end()\n{\n  echo \'</div>\';\n  end_page();\n  exit();\n}\n', 'abe29161571fc692e1053d02c66a01e2', '', '', 1, 5, 0),
('zem_contact_lang', 0, 'TXP Community', 'http://forum.textpattern.com/viewtopic.php?id=21144', '4.0.3.6', 'Language plug-in for Zem Contact Reborn', '<div style=\"text-align:center;font-weight:bold;font-size:24px;text-decoration:underline;\">Zem Contact Lang</div>\n\n<p>This is a separate language plug-in for use with Zem Contact Reborn. Both plug-ins need to be installed and activated in order to work properly.</p>\n\n<p>Separating the language in this way will enable non-english users to update the main plug-in without affecting their &#8220;localisation&#8221;.</p>\n\n<div id=\"local\" style=\"text-align:center;font-weight:bold;font-size:24px;text-decoration:underline;\">Localisation</div>\n\n<p>Throughout the <code>zem_contact_reborn</code> plug-in, use has been made of a separate <code>gTxt</code> function which you can see in this plug-in&#8217;s code by clicking on the &#8220;Edit&#8221; button.</p>\n\n<p>If you are using the plug-in for a non-english site you can make use of this to localise text outputs for your preferred language.</p>\n\n<p>You should only edit text that appears after the <code>=&gt;</code> sign.</p>\n\n<p>If you have a dual-language site and the languages use separate &#8220;sections&#8221;, you can use the <txp:if_section> tag to enable different translations. An example of this usage is shown in the <strong><a href=\"http://forum.textpattern.com/viewtopic.php?id=13416\">forum thread</a></strong>. Our thanks to Els (doggiez) for this example.</p>\n\n <br />\n', '\nfunction zem_contact_gTxt($what, $var1 = \'\', $var2 = \'\')\n{\n	$lang = array(\n		\'checkbox\'		=> \'Checkbox\',\n		\'contact\'		=> \'Contact\',\n		\'email\'			=> \'Email\',\n		\'email_subject\'		=> \"$var1 > Inquiry\",\n		\'email_thanks\'		=> \'Thank you, your message has been sent.\',\n		\'field_missing\'		=> \"Required field, &#8220;<strong>$var1</strong>&#8221;, is missing.\",\n		\'form_expired\'		=> \'The form has expired, please try again.\',\n		\'form_used\'		=> \'The form was already submitted, please fill out a new form.\',\n		\'general_inquiry\'	=> \'General inquiry\',\n		\'invalid_email\'		=> \"&#8220;<strong>$var1</strong>&#8221; is not a valid email address.\",\n		\'invalid_host\'		=> \"&#8220;<strong>$var1</strong>&#8221; is not a valid email host.\",\n		\'invalid_utf8\'		=> \"&#8220;<strong>$var1</strong>&#8221; contains invalid UTF-8 characters.\",\n		\'invalid_value\'		=> \"Invalid value for &#8220;<strong>$var1</strong>&#8221;, &#8220;<strong>$var2</strong>&#8221; is not one of the available options.\",\n		\'mail_sorry\'		=> \'Sorry, unable to send email.\',\n		\'message\'		=> \'Message\',\n		\'min_warning\'		=> \"&#8220;<strong>$var1</strong>&#8221; must contain at least $var2 characters.\",\n		\'max_warning\'		=> \"&#8220;<strong>$var1</strong>&#8221; must not contain more than $var2 characters.\",\n		\'name\'			=> \'Name\',\n		\'option\'		=> \'Option\',\n		\'radio\'			=> \'Radio\',\n		\'recipient\'		=> \'Recipient\',\n		\'refresh\'		=> \'Follow this link if the page does not refresh automatically.\',\n		\'secret\'		=> \'Secret\',\n		\'send\'			=> \'Send\',\n		\'send_article\'		=> \'Send article\',\n		\'spam\'			=> \'We do not accept spam, thank you!\',\n		\'text\'			=> \'Text\',\n		\'to_missing\'		=> \'&#8220;<strong>To</strong>&#8221; email address is missing.\',\n		\'version\'		=> \'4.0.3.6\'\n	);\n\n	return $lang[$what];\n}\n', '\nfunction zem_contact_gTxt($what, $var1 = \'\', $var2 = \'\')\n{\n	$lang = array(\n		\'checkbox\'		=> \'Checkbox\',\n		\'contact\'		=> \'Contact\',\n		\'email\'			=> \'Email\',\n		\'email_subject\'		=> \"$var1 > Inquiry\",\n		\'email_thanks\'		=> \'Thank you, your message has been sent.\',\n		\'field_missing\'		=> \"Required field, &#8220;<strong>$var1</strong>&#8221;, is missing.\",\n		\'form_expired\'		=> \'The form has expired, please try again.\',\n		\'form_used\'		=> \'The form was already submitted, please fill out a new form.\',\n		\'general_inquiry\'	=> \'General inquiry\',\n		\'invalid_email\'		=> \"&#8220;<strong>$var1</strong>&#8221; is not a valid email address.\",\n		\'invalid_host\'		=> \"&#8220;<strong>$var1</strong>&#8221; is not a valid email host.\",\n		\'invalid_utf8\'		=> \"&#8220;<strong>$var1</strong>&#8221; contains invalid UTF-8 characters.\",\n		\'invalid_value\'		=> \"Invalid value for &#8220;<strong>$var1</strong>&#8221;, &#8220;<strong>$var2</strong>&#8221; is not one of the available options.\",\n		\'mail_sorry\'		=> \'Sorry, unable to send email.\',\n		\'message\'		=> \'Message\',\n		\'min_warning\'		=> \"&#8220;<strong>$var1</strong>&#8221; must contain at least $var2 characters.\",\n		\'max_warning\'		=> \"&#8220;<strong>$var1</strong>&#8221; must not contain more than $var2 characters.\",\n		\'name\'			=> \'Name\',\n		\'option\'		=> \'Option\',\n		\'radio\'			=> \'Radio\',\n		\'recipient\'		=> \'Recipient\',\n		\'refresh\'		=> \'Follow this link if the page does not refresh automatically.\',\n		\'secret\'		=> \'Secret\',\n		\'send\'			=> \'Send\',\n		\'send_article\'		=> \'Send article\',\n		\'spam\'			=> \'We do not accept spam, thank you!\',\n		\'text\'			=> \'Text\',\n		\'to_missing\'		=> \'&#8220;<strong>To</strong>&#8221; email address is missing.\',\n		\'version\'		=> \'4.0.3.6\'\n	);\n\n	return $lang[$what];\n}\n', '552fe2e408499f9fd92ab620fa0e15f6', '', '', 0, 5, 0),
('sab_substr', 0, 'Ruud van Melick', 'http://vanmelick.com/', '0.5', 'Cut plain text at a specified character limit', '<p>This is a simple plugin to cut a string of plain text (not (X)HTML!) at a specified character limit (<span class=\"caps\">UTF</span>-8 aware) and then add a trailing string (i.e. ellipsis).</p>\n\n<p>You can use it to take an article title (or any other non-html escaped text) and trim it down to a specified character limit then add an ellipses. Possibly used for article navigation (prev/next) or an article list where you want to keep the titles short.</p>\n\n<p><strong>Example:</strong></p>\n\n<pre><code>&lt;txp:sab_substr limit=&quot;10&quot; trail=&quot;...&quot;&gt;&lt;txp:title /&gt;&lt;/txp:sab_substr&gt;</code></pre>\n\n<p>will output &#8220;long string&#8230;&#8221;, assuming the title is &#8216;long string title&#8217;.</p>\n\n<p><strong>Attributes:</strong></p>\n\n<ul>\n	<li>trail=&#8220;html text&#8221;<br />\nThe string you want appended to the result. It will <span class=\"caps\">NOT</span> be appended <span class=\"caps\">UNLESS</span> the content was trimmed. Defaults to &#8216;&#8230;&#8217; (ellipsis).</li>\n	<li>limit=&#8220;integer&#8221;<br />\nThe length of the string you want returned, not counting the length of the trailing text. Defaults to 10.</li>\n</ul>\n\n<p>PS. This plugin was originally released by Steve from bluecamel.org, but when it was no longer available, I wrote it anew from scratch to be a drop-in replacement.</p>', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'sab_substr\');\n}\n\n\nfunction sab_substr($atts, $thing)\n{\n  extract(lAtts(array(\n    \'limit\' => 10,\n    \'trail\' => \'&#8230;\'\n  ), $atts));\n\n  assert_int($limit);\n\n  $thing = parse($thing);\n\n  if (strlen(utf8_decode($thing)) > $limit)\n  {\n     $thing = preg_replace(\'/^(.{\'.$limit.\'}).*$/us\', \'$1\', $thing);\n     return htmlspecialchars($thing).$trail;\n  }\n\n  else\n  {\n     return htmlspecialchars($thing);\n  }\n}\n', '\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'sab_substr\');\n}\n\n\nfunction sab_substr($atts, $thing)\n{\n  extract(lAtts(array(\n    \'limit\' => 10,\n    \'trail\' => \'&#8230;\'\n  ), $atts));\n\n  assert_int($limit);\n\n  $thing = parse($thing);\n\n  if (strlen(utf8_decode($thing)) > $limit)\n  {\n     $thing = preg_replace(\'/^(.{\'.$limit.\'}).*$/us\', \'$1\', $thing);\n     return htmlspecialchars($thing).$trail;\n  }\n\n  else\n  {\n     return htmlspecialchars($thing);\n  }\n}\n', 'c5e34930cfaae67a8c23ae88659615e2', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_frontauth', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.9.7', 'Manage backend connections from frontend', '<div id=\"cbe-plugin-help\">\n<h1>cbe_frontauth</h1>\n\n<p>This client-side plugin lets your users (or you) manage backend connection from frontend, i.e. connect and disconnect as they (you) would do from backend.\n<br />You can thus make things visible and open actions for connected users only.</p>\n<p>Developed and tested with Textpattern 4.4.1, then 4.5-beta.</p>\n<p class=\"readme\">Please read the first <a href=\"#quick-start\">Quick start</a> paragraph to avoid (as much as possible) unexpected behaviors.</p>\n<p>A few examples (in french) can be found in the <a href=\"http://www.clairebrione.com/demo-cbe_frontauth\">demonstration page</a>.</p>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#tags-list\">Tags list</a></li>\n  <li><a href=\"#notations\">Notations</a></li>\n  <li><a href=\"#quick-start\">Quick start</a></li>\n  <li><a href=\"#individual-elements\">Take control on individual elements</a></li>\n  <li><a href=\"#additional-tags\">Additional and special tags</a></li>\n  <li><a href=\"#callbacks\">Callbacks</a></li>\n  <li><a href=\"#how-to\">How-to: ideas and snippets</a></li>\n  <li><a href=\"#advanced-usage\">Advanced usage</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>automatically generate a <a href=\"#login-logout-box\">login/logout box</a>...</li>\n  <li>... or independent <a href=\"#login-area\">login</a> or <a href=\"#logout-area\">logout</a> forms and links</li>\n  <li>choose if th user must connect with his/her <a href=\"#login-method\">username, or email address, or one of both</a></li>\n  <li>show/hide content depending on whether a user is connected or not (see <a href=\"#protect-parts\">protect parts of a page</a>)</li>\n  <li>set <a href=\"#automatic-redirect\">automatic redirections</a> after login and/or logout</li>\n  <li>optional: define your own default values for <a href=\"#setting-invites\">login/logout invites</a> and <a href=\"#setting-labels\">button/link labels</a>, <a href=\"#login-method\">define login method</a></li>\n  <li>override these values anywhere in the page if you need to</li>\n  <li>also provides <a href=\"#additional-tags\">additional tags</a> to ease scripter\'s life</li>\n  <li>and <a href=\"#callbacks\">hooks for callback functions</a> primarily in order to reset or change user\'s password</li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n<p>Download from <a href=\"\nAlso available on \"GitHub\":https://github.com/ClaireBrione/cbe_frontauth\" target=\"_blank\">GitHub</a>, <a href=\"http://textpattern.org/plugins/1234/cbe_frontauth\" target=\"_blank\">textpattern resources</a> or the <a href=\"http://www.clairebrione.com/cbe_frontauth\" target=\"_blank\">plugin page</a>.</p>\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36552\">forum thread</a> for support.</p>\n\n<h2 id=\"tags-list\">Tags list</h2>\n\n<p>Alphabetically:\n<br /><a href=\"#advanced-usage\">cbe_frontauth</a>\n<br /><a href=\"#path-backend\">cbe_frontauth_backend</a>\n<br /><a href=\"#login-logout-box\">cbe_frontauth_box</a>\n<br /><a href=\"#edit-article\">cbe_frontauth_edit_article</a>\n<br /><a href=\"#protect-parts\">cbe_frontauth_if_connected</a>\n<br /><a href=\"#setting-invites\">cbe_frontauth_invite</a>\n<br /><a href=\"#setting-labels\">cbe_frontauth_label</a>\n<br /><a href=\"#link-generation\">cbe_frontauth_link</a>\n<br /><a href=\"#protect-parts\">cbe_frontauth_if_logged</a>\n<br /><a href=\"#login-area\">cbe_frontauth_login</a>\n<br /><a href=\"#login-method\">cbe_frontauth_loginwith</a>\n<br /><a href=\"#form-elements\">cbe_frontauth_logname</a>\n<br /><a href=\"#logout-area\">cbe_frontauth_logout</a>\n<br /><a href=\"#form-elements\">cbe_frontauth_password</a>\n<br /><a href=\"#protect-parts\">cbe_frontauth_protect</a>\n<br /><a href=\"#automatic-redirect\">cbe_frontauth_redirect</a>\n<br /><a href=\"#form-elements\">cbe_frontauth_reset</a>\n<br /><a href=\"#form-elements\">cbe_frontauth_stay</a>\n<br /><a href=\"#form-elements\">cbe_frontauth_submit</a>\n<br /><a href=\"#user-info\">cbe_frontauth_whois</a>\n</p>\n\n<h2 id=\"notations\">Notations</h2>\n\n<p><code>Tags and examples are presented with this typography</code> (fixed width).</p>\n<p>Possible values for attributes are separated by a \"<code> | </code>\" (pipe).</p>\n<p><b>Bold</b> means default value.</p>\n<p>\"<code>...</code>\" (ellipsis) is to be replaced by any custom value, usually a string.</p>\n<p>Attributes surrounded by \"<code>[</code>\" and \"<code>]</code>\" (square brackets) are optional.</p>\n\n<h2 id=\"quick-start\">Quick start</h2>\n\n<p>Message strings are customisable by editing them in the function _cbe_fa_lang(). When possible, their default values are pulled from the language table. In most cases, you won\'t have to edit them as they are already localised.</p>\n\n<p class=\"readme\">What you have to know and care about :</p>\n<ul>\n  <li>The login/logout mechanism relies on cookies. A cookie is attached to one, and only one, subdomain.</li>\n  <li><code>http://domain.tld</code> and <code>http://www.domain.tld</code> are <b>different</b> subdomains, even if you present the same content through both URLs.</li>\n</ul>\n<p>=> You will have to choose which base URL you want to use (with or without www) and stick to it along all the navigation. This is also a good practice to avoid duplicate content.</p>\n\n<p>Here is how to:</p>\n<p>1. Plugin load order: as it handles redirections, it has to be loaded before any other plugin. Set by default to <b>4</b>, adjust according to your needs.</p>\n<p>2. Admin > Preferences : give (or verify) your site URL and save.</p>\n<p>3. Edit the <code>.htaccess</code> file located at the root of your site, and append as closer as possible to <code>RewriteEngine On</code> (replace <code>domain.tld</code> with your actual URL):</p>\n\n<p>EITHER, with www</p>\n<pre><code>RewriteCond %{HTTP_HOST} !^www\\.domain\\.tld$ [NC]\nRewriteRule ^(.*) http://www.domain.tld/$1 [QSA,R=301,L]</code></pre>\n\n<p>OR, without www</p>\n<pre><code>RewriteCond %{HTTP_HOST} ^www\\.domain\\.tld$ [NC]\nRewriteRule ^(.*) http://domain.tld/$1 [QSA,R=301,L]</code></pre>\n\n<p><span class=\"accent\">It\'s time now to start using the plugin</span>: <a href=\"#login-logout-box\">allow users to login and logout</a>, <a href=\"#automatic-redirect\">redirecting them</a> (or not) after login and/or logout, <a href=\"#protect-parts\">serve them special content</a>, <a href=\"#individual-elements\">the rest is up to you</a>.</p>\n\n<p><code>wraptag</code>, <code>class</code>, <code>break</code> and <code>breakclass</code> are supported by every tag and both default to <b>unset</b>.</p>\n\n<h3 id=\"login-logout-box\">Login/logout box: &lt;txp:cbe_frontauth_box /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_box\n  [ login_invite=\"<b>Connect to textpattern</b> | ...\"\n    logout_invite=\"<b>none</b> | ...\"\n    tag_invite=\"...\"\n    login_label=\"...\"\n    logout_label=\"...\"\n    logout_type=\"<b>button</b> | link\"\n    tag_error=\"<b>span</b>\" class_error=\"<b>cbe_fa_error</b>\"\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ] /&gt;</code></pre>\n\n<p>Displays\n<br />- simple login form if not connected\n<br />- \"connected as {login name}\" and a logout button or link if connected\n</p>\n\n<p>If login fails, a basic message will appear just before the login form. You can customise its wrapping tag and class.</p>\n\n<p>If you don\'t want \"connected as\" message, use as a container tag and put a blank or <a href=\"#box-ideas\">anything else</a> in between:</p>\n<pre><code>&lt;txp:cbe_frontauth_box&gt; &lt;/txp:cbe_frontauth_box&gt;</code></pre>\n\n<h3 id=\"protect-parts\">Protect parts of a page: &lt;txp:cbe_frontauth_protect /&gt;, &lt;txp:cbe_frontauth_if_logged /&gt; and &lt;txp:cbe_frontauth_if_connected /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_protect\n  [ name=\"<b>none</b> | comma-separated values\"\n    level=\"<b>none</b> | comma-separated values\"\n    link=\"<b>none</b> | url\"\n    linklabel=\"<b>none</b> | anchor\"\n    target=\"<b>_self</b> | _blank\"\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ]&gt;\n  What to protect\n&lt;txp:else /&gt;\n  What to display if not connected\n&lt;/txp:cbe_frontauth_protect&gt;</code></pre>\n<p>Synonyms: <code>&lt;txp:cbe_frontauth_if_connected /&gt;</code> <code>&lt;txp:cbe_frontauth_if_logged /&gt;</code> if you find one of these forms more convenient</p>\n\n<p>If connected, you can automatically add a link to click to go somewhere. This link will show first (before any other content).\n<br />You do this using the attributes <code>link</code>, <code>linklabel</code>, optionally <code>target</code> (\"_self\" opens the link in the same window/tab, \"_blank\" in a new window/tab).\n</p>\n\n<p>If you want to display the link anywhere else, or display more than one link, or conditionally show a link, prefer <a href=\"#link-generation\">&lt;txp:cbe_frontauth_link /&gt;</a></p>\n\n<h3 id=\"login-method\">Login method &lt;txp:cbe_frontauth_loginwith /&gt;</h3>\n\n<p>What to use as login name : username (as textpattern usually does), email, or auto for automatic detection.</p>\n<p><span class=\"readme\">Caution if using email login method</span> : textpattern doesn\'t check for duplicate email addresses upon user creation. If someone tries to log in using such an address, it will fail.</p>\n\n<pre><code>&lt;txp:cbe_frontauth_loginwith\n    value=\"<b>auto</b> | username | email\" /&gt;</code></pre>\n\n<h3 id=\"automatic-redirect\">Automatic redirect: &lt;txp:cbe_frontauth_redirect /&gt;</h3>\n\n<p>User will be automatically redirected after successful login and/or logout.\n<br />Use this tag before any other cbe_frontauth_* as it sets redirection(s) for the whole page.</p>\n\n<pre><code>&lt;txp:cbe_frontauth_redirect\n    for=\"login | logout | login,logout\"\n    value=\"after_login_url | after_logout_url | after_login_url,after_logout_url\" /&gt;</code></pre>\n\n<p>In other words and in details:</p>\n<p><code>&lt;txp:cbe_frontauth_redirect for=\"login\" value=\"after_login_url\" /&gt;</code>\n<br />sets automatic redirection after login</p>\n<p><code>&lt;txp:cbe_frontauth_redirect for=\"logout\" value=\"after_logout_url\" /&gt;</code>\n<br />sets automatic redirection after logout</p>\n<p><code>&lt;txp:cbe_frontauth_redirect for=\"login\" value=\"after_login_url\" /&gt;\n&lt;txp:cbe_frontauth_redirect for=\"logout\" value=\"after_logout_url\" /&gt;</code>\n<br />sets automatic redirection for both</p>\n<p><code>&lt;txp:cbe_frontauth_redirect for=\"login,logout\" value=\"after_login_url,after_logout_url\" /&gt;</code>\n<br />sets automatic redirection for both too</p>\n\n<h3 id=\"setting-invites\">Setting invites globally for the whole page: &lt;txp:cbe_frontauth_invite /&gt;</h3>\n\n<p>Works the same way <a href=\"#automatic-redirect\">as above</a>:</p>\n<p><pre><code>&lt;txp:cbe_frontauth_invite for=\"...\" value=\"...\" /&gt;</code></pre></p>\n<p>Combinations: <code>login, logout (or logged), tag</code></p>\n<p>Synonym: <code>logged</code> for logout, if you find this form more convenient. <span class=\"accent\">As synonyms they are mutually exclusive</span> and if both used <code>logout</code> will take precedence.</p>\n<p>Can be overridden by any tag that has <code>invite</code> as attribute.</p>\n\n<p>Example:</p>\n<pre><code>&lt;txp:cbe_frontauth_invite for=\"login,logout,tag\" invite=\"Please login,You can logout here,h2\" /&gt;\n&lt;txp:cbe_frontauth_box /&gt;\n  ... Your page here ...\n  ... and in the footer, for example ...\n&lt;txp:cbe_frontauth_login invite=\"Say hello !\" tag_invite=\"span\" /&gt;\n</code></pre>\n\n<h3 id=\"setting-labels\">Setting button and link labels globally for the whole page: &lt;txp:cbe_frontauth_label /&gt;</h3>\n\n<p>Works the same way <a href=\"#automatic-redirect\">as above</a> too:</p>\n<p><pre><code>&lt;txp:cbe_frontauth_label for=\"...\" value=\"...\" /&gt;</code></pre></p>\n<p>Combinations: <code>login, logout</code></p>\n<p>Can be overridden by any tag that has <code>label</code> as attribute.</p>\n\n<h2 id=\"individual-elements\">Take control on individual elements</h2>\n\n<h3 id=\"login-area\">Login area: &lt;txp:cbe_frontauth_login /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_login\n  [ invite=\"<b>Connect to textpattern</b> | ...\"\n    tag_invite=\"<b>none</b> | ...\"\n    ( {label=\"<b>Login</b>|...\" show_stay=\"<b>0</b>|1\" show_reset=\"0|<b>1</b>\"} | form=\"<b>none</b>|form name\" )\n    tag_error=\"<b>span</b>\" class_error=\"<b>cbe_fa_error</b>\"\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ] /&gt;\n\n&lt;txp:cbe_frontauth_login\n  [ invite=\"<b>Connect to textpattern</b> | ...\"\n    tag_invite=\"<b>none</b> | ...\"\n    tag_error=\"<b>span</b>\" class_error=\"<b>cbe_fa_error</b>\"\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ]&gt;\n   form elements\n&lt;/txp:cbe_frontauth_login&gt;</code></pre>\n\n<p>If login fails, a basic message will appear just before the login form. You can customise its wrapping tag and class.</p>\n\n<p id=\"form-elements\" >Where <code>form elements</code> are:</p>\n<pre><code>&lt;txp:cbe_frontauth_logname [label=\"<b>Name</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;\n&lt;txp:cbe_frontauth_password [label=\"<b>Password</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;\n&lt;txp:cbe_frontauth_stay [label=\"<b>Stay connected with this browser</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;\n&lt;txp:cbe_frontauth_reset [label=\"<b>Password forgotten ?</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;\n&lt;txp:cbe_frontauth_submit [label=\"<b>Login</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;</code></pre>\n\n<h3 id=\"logout-area\">Logout area: &lt;txp:cbe_frontauth_logout /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_logout\n  [ invite=\"<b>none</b>|...\"\n    tag_invite=\"<b>none</b>|...\"\n    ( {label=\"<b>Logout</b>|...\" type=\"<b>button</b>|link\" show_change=\"0|<b>1</b>} | form=\"<b>none</b>|form name\")\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ] /&gt;\n\n&lt;txp:cbe_frontauth_logout\n  [ invite=\"<b>none</b>|...\"\n    tag_invite=\"<b>none</b>|...\"\n    wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" ]&gt;\n   form elements\n&lt;/txp:cbe_frontauth_logout&gt;</code></pre>\n\n<p>Where <code>form elements</code> are:</p>\n<pre><code>&lt;txp:cbe_frontauth_submit type=\"logout\" [label=\"<b>Logout</b>|...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;\n&lt;txp:cbe_frontauth_link link=\"logout=1\" target=\"_get\" [label=\"...\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;</code></pre>\n\n<h2 id=\"additional-tags\">Additional and special tags</h2>\n\n<h3 id=\"user-info\">Connected user information: &lt;txp:cbe_frontauth_whois /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_whois [type=\"[<b>name</b>][, RealName][, email][, privs][, last_access]\" format=\"<b>as set in preferences</b>|since|rfc822|iso8601|w3cdtf|strftime() string value\" wraptag=\"...\" break=\"...\" class=\"...\" breakclass=\"...\"] /&gt;</code></pre>\n<p><code>format</code> applies to <code>last_access</code> if present.</p>\n\n<h3 id=\"path-backend\">Path to Textpattern backend: &lt;txp:cbe_frontauth_backend /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_backend /&gt;</code></pre>\n\n<p>Returns path to textpattern root (in most cases /textpattern/index.php).</p>\n\n<h3 id=\"edit-article\">Direct button or link to edit current article (write article)</h3>\n\n<p>In an individual article form or enclosed in <code>&lt;txp:if_individual_article&gt; &lt;/txp:if_individual_article&gt;</code>:</p>\n\n<pre><code>&lt;txp:cbe_frontauth_if_connected&gt;\n    &lt;txp:cbe_frontauth_edit_article label=\"<b>edit</b>|...\"  type=\"<b>button</b>|link\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\" /&gt;\n&lt;/txp:cbe_frontauth_if_connected&gt;</code></pre>\n\n<p><span class=\"accent\">Why use a button rather than a link ?</span> Answer: as it is enclosed in an HTML form, it allows to go to the edit page without showing parameters in the URL.</p>\n\n<h3 id=\"link-generation\">Link generation: &lt;txp:cbe_frontauth_link /&gt;</h3>\n\n<pre><code>&lt;txp:cbe_frontauth_link label=\"...\" link=\"...\" [target=\"<b>_self</b>|_blank|_get\" wraptag=\"...\" class=\"...\" break=\"...\" breakclass=\"...\"] /&gt;</code></pre>\n\n<p><code>class</code> applies to the anchor if there is no <code>wraptag</code> supplied.</p>\n<p><code>_get</code> will add <code>link</code> to the current URL, for example:</p>\n<p>URL : http://www.example.com/page\n<br /><code>&lt;txp:cbe_frontauth_link label=\"Logout\" link=\"logout=1\" target=\"_get\" /&gt;</code>\n<br />URL Result : http://www.example.com/page?logout=1</p>\n\n<h2 id=\"callbacks\">Callbacks</h2>\n<p>They have been introduced to hook cbe_frontauth\'s companion, <a href=\"/cbe_members\">cbe_members</a> (see details in the table below).</p>\n<table class=\"list\">\n  <tr> <th>Event</th> <th>Step</th> <th>What it is</th> </tr>\n  <tr> <td><code>cbefrontauth.reset_password</code></td> <td><code>cbe_fa_before_login</code></td> <td>Triggered before showing login form, when resetting password is in progress.<br />If cbe_members is installed, displays here the \"reset password\" form, or performs the actual reset if the form is successfully filled in.</td> </tr>\n  <tr> <td><code>cbefrontauth.reset_password</code></td> <td><code>cbe_fa_after_login</code></td> <td>Triggered after showing login form.<br />If cbe_members is installed, displays a link to the \"reset password\" form.</td> </tr>\n  <tr> <td><code>cbefrontauth.change_password</code></td> <td><code>cbe_fa_before_logout</code></td> <td>Triggered before showing logout form, when changing password is in progress.<br />If cbe_members is installed, displays the \"change password\" form, or performs the actual change if the form is successfully filled in.</td> </tr>\n  <tr> <td><code>cbefrontauth.change_password</code></td> <td><code>cbe_fa_after_logout</code></td> <td>Triggered after showing logout form.<br />If cbe_members is installed, displays a link to the \"change password\" form.</td> </tr>\n</table>\n\n<h2 id=\"how-to\">How-to: ideas and snippets</h2>\n\n<h3 id=\"box-ideas\">For login/logout box</h3>\n\n<p>Replace the standard message with something else:</p>\n<pre><code>&lt;txp:cbe_frontauth_box&gt;Welcome !&lt;/txp:cbe_frontauth_box&gt;</code></pre>\n\n<p>Or even:</p>\n<pre><code>&lt;txp:cbe_frontauth_box&gt;Greetings &lt;txp:cbe_frontauth_whois type=\"RealName\" /&gt; !&lt;/txp:cbe_frontauth_box&gt;</code></pre>\n\n<h3 id=\"invites-ideas\">For invites</h3>\n<pre><code>&lt;txp:cbe_frontauth_invite for=\"logged\" value=\'&lt;txp:cbe_frontauth_whois type=\"RealName\" /&gt;\' /&gt;</code></pre>\n<p>Note: if a user is connected, the login invite doesn\'t show and the logout invite takes its place. So we could use <code>for=\"logout\"</code> as well.</p>\n\n<h3 id=\"greeting-message\">A greeting message</h3>\n<pre><code>Greetings &lt;txp:cbe_frontauth_if_connected&gt;&lt;txp:cbe_frontauth_whois type=\"RealName\" /&gt;&lt;txp:else /&gt;dear User&lt;/txp:cbe_frontauth_if_connected&gt; !</code></pre>\n\n<h2 id=\"advanced-usage\">Advanced usage</h2>\n\n<p class=\"readme\">As previous tags should cover majority\'s needs, you don\'t have to read this section if you already achieved what you wanted to.</p>\n\n<p>This is the programmer\'s corner: it describes attributes for the main function that is called by almost every public tag discussed above.</p>\n\n<p>Here are the parameters for the main function:</p>\n<pre><code>&lt;txp:cbe_frontauth&gt;\n  What to do/display if connected\n&lt;txp:else /&gt;\n  What to do/display if not connected\n&lt;/txp:cbe_frontauth&gt;</code></pre>\n\n<p>form (\'\') or thing = what to display if logged in\n<br />tag_invite (\'\') = HTML tag enclosing the label, without brackets\n<br />\n<br />show_login (1) = whether to display or not a login form, appears only if not logged in\n<br />- login_invite (\'login_to_textpattern\') = invite to login\n<br />- login_form (\'\') = form to build your own HTML login form with txp:cbe_frontauth_login, or txp:cbe_frontauth_logname, cbe_frontauth_password, cbe_frontauth_stay, cbe_frontauth_reset, cbe_frontauth_submit. If not used, a default HTML form is displayed\n<br />- login_label (\'log_in_button\') = label for the login form\n<br />- login_with (auto) = whether to use username, or email, or auto detection as user logon\n<br />- login_redir (\'\') = go immediately to path after successful login\n<br />- show_stay (0) = used in the generic login form, whether to display or not a checkbox to stay logged in\n<br />- show_reset (1) = used in the generic login form, whether to display or not a link to reset password\n<br />\n<br />show_logout (1) = whether to display or not a default button to log out, appears only if logged in\n<br />- logout_invite (\'\') = invite to logout\n<br />- logout_form (\'\') = form to build your own HTML logout form, or your own link\n<br />- logout_label (as set in lang pack) = label for the logout button\n<br />- logout_type (\'button\'), other type is \'link\'\n<br />- logout_redir (\'\') = go immediately to path after logout\n<br />- show_change (1) = used in the generic logout form, whether to display or not a link to change password\n<br />\n<br />link (\'\') = a page to go to if connected\n<br />- linklabel (\'\') = text anchor for link\n<br />- target (_self) = _self _blank or _get, whether to open the link in the same window (or tab), or in a new one, or to generate an URL with address link as GET parameter. Works only with hyperlink (not login_redir, not logout_redir)\n<br />\n<br />Checking users and privileges :\n<br />- name (\'\') = list of names to check\n<br />- level (\'\') = list of privilege levels to check\n<br />\n<br />Presentational attributes :\n<br />- wraptag (\'\'), class (\'\')\n<br />\n<br />init = Special attribute for internal use only and documented only for people who want to know :)\n <br /> Whether to set (\'1\') or get (\'0\') global settings for redirections (login_redir, logout_redir), invites (login_invite, logout_invite, tag_invite), labels (login_label, logout_label), login type (login_with) or user\'s informations. Immediately returns and doesn\'t display anything.\n <br /> value = setting to set or get, string or array.\n</p>\n\n<h2 id=\"changelog\">Changelog</h2>\n<ul>\n  <li>20 Nov 15 - v0.9.7 - <a href=\"http://forum.textpattern.com/viewtopic.php?pid=296720#p296720\">Fix this</a></li>\n  <li>07 Apr 14 - v0.9.6 - Error when passing presentational attributes from cbe_frontauth_edit_article to cbe_frontauth_link</li>\n  <li>04 Apr 14 - v0.9.5 - Missing last access storage</li>\n  <li>27 Mar 13 - v0.9.4\n  <br />Missing initialization for cbe_frontauth_whois\n  <br />Error message when login fails\n  <br />Local language strings\n  </li>\n  <li>22 Mar 12 - v0.9.3 - Doc typo for cbe_frontauth_invite</li>\n  <li>?? ??? 12 - v0.9.2 - ??</li>\n  <li>22 Mar 12 - v0.9.1 - fixed missing attributes (show_login and show_change) for cbe_frontauth_box</li>\n  <li>21 Mar 12 - v 0.9 - Callback hooks: ability to ask for password reset if not connected, for password change if connected</li>\n  <li>10 Jan 12 - v 0.8 - Introduces &lt;txp:cbe_frontauth_loginwith /&gt;, <a href=\"http://forum.textpattern.com/viewtopic.php?pid=256632#p256632\">idea comes from another demand in the textpattern forum</a>.\n  </li>\n  <li>05 Jan 12 - v0.7.1 - Documentation addenda</li>\n  <li>06 Aug 11 - v0.7-beta\n  <br /> * Introduces &lt;txp:cbe_frontauth_edit_article /&gt\n  <br /> * CSRF protection ready\n  <br /> * Documentation improvements\n  </li>\n  <li>29 Jul 11 - v0.6-beta\n  <br /> * Optimizations to avoid multiple calls to database when retrieving user\'s informations\n  <br /> * Added name and privilege controls à la <a href=\"http://vanmelick.com/txp/\">&lt;txp:rvm_if_privileged /&gt;</a>\n  <br /> * Minor changes to documentation\n  </li>\n  <li>27 Jul 11 - v0.5-beta- First public beta release</li>\n  <li>26 Jul 11 - v0.4-beta- Restricted beta release</li>\n  <li>24 Jul 11 - v0.3-dev - Restricted development release</li>\n  <li>23 Jul 11 - v0.2-dev - Restricted development release</li>\n  <li>22 Jul 11 - v0.1-dev - Restricted development release</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#cbe-plugin-help .readme {\n  color: red;\n  font-weight: bold;\n}\n#cbe-plugin-help .accent {\n  font-weight: bold;\n}\n#cbe-plugin-help .list th{\n	text-align:left;\n	color:#fff;\n	background:#555;\n	padding:6px 16px 6px 4px;\n	border-right:1px solid #999;\n	font-weight:normal;\n}\n#cbe-plugin-help .list th a{\n	color:#fff;\n	display:block;\n}\n#cbe-plugin-help .list th h2{\n	margin:0;\n	font-size:11px;\n}\n#cbe-plugin-help .list td{\n	padding:6px 4px;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n** cbe_frontauth\r\n** Client-side textpattern plugin\r\n** Connect (and disconnect) from frontend to backend\r\n** Establishes bidirectional links between article display and edition\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1-dev - 22 Jul 2011 - Restricted development release\r\n** 0.2-dev - 23 Jul 2011 - Restricted development release\r\n** 0.3-dev - 24 Jul 2011 - Restricted development release\r\n** 0.4-beta- 26 Jul 2011 - Restricted beta release\r\n** 0.5-beta- 27 Jul 2011 - First public beta release\r\n** 0.6-beta- 29 Jul 2011 - Optimizations to avoid multiple calls to database\r\n**                           when retrieving user\'s informations\r\n**                         Added name and privilege controls\r\n**                           à la <txp:rvm_if_privileged /> (http://vanmelick.com/txp/)\r\n**                         Minor changes to documentation\r\n** 0.7-beta- 06 Aug 2011 - Introduces <txp:cbe_frontauth_edit_article />\r\n**                         CSRF protection ready\r\n**                         Documentation improvements\r\n** 0.7.1   - 05 Jan 2012 - Documentation addenda\r\n** 0.8     - 10 Jan 2012 - Introduces <txp:cbe_frontauth_loginwith />\r\n**                             http://forum.textpattern.com/viewtopic.php?pid=256632#p256632\r\n**                         txp:cbe_frontauth_loginwith (*auto*|username|email)\r\n** 0.9     - 21 Mar 2012 - Added callback hooks (reset and change password)\r\n** 0.9.1   - 22 Mar 2012 - Fixed missing attributes (show_login and show_change) for cbe_frontauth_box\r\n** 0.9.2   - ?? ??? 2012 - ??\r\n** 0.9.3   - 22 Aug 2012 - Doc typo for cbe_frontauth_invite\r\n** 0.9.4   - 27 Mar 2013 - Missing initialization for cbe_frontauth_whois\r\n**                         Error message when login fails\r\n**                         Local language strings\r\n** 0.9.5   - 04 Apr 2014 - Missing last access storage\r\n** 0.9.6   - 07 Apr 2014 - Error when passing presentational attributes from cbe_frontauth_edit_article to cbe_frontauth_link\r\n** 0.9.7   - 20 Nov 2015 - fix: http://forum.textpattern.com/viewtopic.php?pid=296720#p296720\r\n**\r\n** TODO\r\n** - break, breakclass -> in progress, full tests needed\r\n** - enhence error messages ?\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Local language strings, possible customisation here\r\n **\r\n **************************************************/\r\nfunction _cbe_fa_lang()\r\n{\r\n    return( array( \'login_failed\'         => \"Login failed\"\r\n                 , \'login_to_textpattern\' => gTxt( \'login_to_textpattern\' )\r\n                 , \'name\'                 => gTxt( \'name\' )\r\n                 , \'password\'             => gTxt( \'password\' )\r\n                 , \'log_in_button\'        => gTxt( \'log_in_button\' )\r\n                 , \'stay_logged_in\'       => gTxt( \'stay_logged_in\' )\r\n                 , \'logout\'               => gTxt( \'logout\' )\r\n                 , \'edit\'                 => gTxt( \'edit\' )\r\n                 , \'change_password\'      => gTxt( \'change_password\' )\r\n                 , \'password_reset\'       => gTxt( \'password_reset\' ) \r\n                 )\r\n          ) ;\r\n}\r\n/**************************************************\r\n **\r\n ** Don\'t edit further\r\n **\r\n **************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n/* == Shortcuts for cbe_frontauth() == */\r\n\r\n// -- Global init for redirection after login and/or logout\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_redirect( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'redir\' ) ) ;\r\n}\r\n\r\n// -- Global init for login/logout invites\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_invite( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'invite\' ) ) ;\r\n}\r\n\r\n// -- Global init for login/logout buttons/link labels\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_label( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'label\' ) ) ;\r\n}\r\n\r\n// -- Global init for login with user name, email, or automatic detection\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_loginwith( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'with\' ) ) ;\r\n}\r\n\r\n// -- Login / Logout box\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_box( $atts, $thing = \'\' )\r\n{\r\n    $public_atts = lAtts( array( \'login_invite\'  => _cbe_fa_gTxt( \'login_to_textpattern\' )\r\n                               , \'logout_invite\' => \'\'\r\n                               , \'show_change\'   => \'1\'\r\n                               , \'show_reset\'    => \'1\'\r\n                               , \'tag_invite\'    => \'\'\r\n                               , \'login_label\'   => _cbe_fa_gTxt( \'log_in_button\' )\r\n                               , \'logout_label\'  => _cbe_fa_gTxt( \'logout\' )\r\n                               , \'logout_type\'   => \'button\'\r\n                               , \'tag_error\'     => \'span\'\r\n                               , \'class_error\'   => \'cbe_fa_error\'\r\n                               )\r\n                           + _cbe_fa_format()\r\n                         , $atts ) ;\r\n\r\n    return( cbe_frontauth( $public_atts\r\n                          , $thing ? $thing : \'<p><txp:text item=\"logged_in_as\" /> <txp:cbe_frontauth_whois wraptag=\"span\" class=\"user\"/></p>\'\r\n                          ) ) ;\r\n\r\n}\r\n\r\n// -- Standalone login form\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_login( $atts, $thing = \'\' )\r\n{\r\n    return( _cbe_fa_inout_process( \'login\', $atts, $thing ) ) ;\r\n}\r\n\r\n// -- Standalone logout form / link\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_logout( $atts, $thing = \'\' )\r\n{\r\n    return( _cbe_fa_inout_process( \'logout\', $atts, $thing ) ) ;\r\n}\r\n\r\n// -- Protect parts from non-connected viewers\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_protect( $atts, $thing )\r\n{\r\n    $public_atts = lAtts( array( \'link\'      => \'\'\r\n                               , \'linklabel\' => \'\'\r\n                               , \'target\'    => \'_self\'\r\n                               , \'name\'      => \'\'\r\n                               , \'level\'     => \'\'\r\n                               )\r\n                         + _cbe_fa_format()\r\n                         , $atts ) ;\r\n\r\n    if( $public_atts[\'target\'] == \'_get\' )\r\n        $public_atts[\'target\'] = \'_self\' ;\r\n\r\n    return( cbe_frontauth( array( \'login_invite\' => \'\' , \'logout_invite\' => \'\'\r\n                                , \'show_login\'   => \'0\', \'show_logout\'   => \'0\'\r\n                                , \'show_reset\'   => \'0\', \'show_change\'   => \'0\' ) + $public_atts\r\n                         , $thing\r\n                         ) ) ;\r\n}\r\nfunction cbe_frontauth_if_logged( $atts, $thing )\r\n{\r\n    return( cbe_frontauth_protect( $atts, $thing ) ) ;\r\n}\r\nfunction cbe_frontauth_if_connected( $atts, $thing )\r\n{\r\n    return( cbe_frontauth_protect( $atts, $thing ) ) ;\r\n}\r\n\r\n/* == Elements == */\r\n\r\n// -- Generates input field for name\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_logname( $atts, $defvalue=null )\r\n{\r\n    return( _cbe_fa_identity( \'name\', $atts, $defvalue ) ) ;\r\n}\r\n\r\n// -- Generates input field for password\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_password( $atts, $defvalue=null )\r\n{\r\n    return( _cbe_fa_identity( \'password\', $atts, $defvalue ) ) ;\r\n}\r\n\r\n// -- Generates checkbox for stay (connected on this browser)\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_stay( $atts )\r\n{\r\n    extract( lAtts( array ( \'label\' => _cbe_fa_gTxt( \'stay_logged_in\' )\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out  = checkbox(\'p_stay\', 1, cs(\'txp_login\'), \'\', \'stay\') ;\r\n    $out .= \'<label for=\"stay\">\'.$label.\'</label>\' ;\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Generates submit button\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_submit( $atts )\r\n{\r\n    $public_atts = lAtts( array ( \'label\' => \'\'\r\n                                , \'type\'  => \'login\'\r\n                                )\r\n                        + _cbe_fa_format()\r\n                        , $atts\r\n                        ) ;\r\n\r\n    return( _cbe_fa_button( $public_atts ) ) ;\r\n}\r\n\r\n// -- Displays connected user\'s informations\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_whois( $atts )\r\n{\r\n    extract( lAtts( array ( \'type\'       => \'name\'\r\n                          , \'format\'     => \'\'\r\n                          )\r\n                    + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $types = do_list( $type ) ;\r\n    $whois = cbe_frontauth( array( \'init\' => \'0\', \'value\' => $types ) ) ;\r\n\r\n    if( isset( $whois[\'last_access\'] ) )\r\n    {\r\n        global $dateformat ;\r\n        $whois[\'last_access\'] = safe_strftime( $format ? $format : $dateformat, strtotime( $whois[\'last_access\'] ) ) ;\r\n    }\r\n\r\n    return( doWrap( $whois, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\n/* == Off-topic, but useful == */\r\n\r\n// -- Generates a link, normal or with a GET parameter\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_link( $atts )\r\n{   // $class applies to anchor if no $wraptag supplied\r\n    extract( lAtts( array ( \'label\'  => \'\'\r\n                          , \'link\'   => \'\'\r\n                          , \'target\' => \'_self\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $link = doStripTags( $link ) ;\r\n    $out = _cbe_fa_link( compact( \'link\', \'target\' ) ) ;\r\n    $out = href( $label, $out\r\n               , (($target !== \'_get\')  ? \' target=\"\'.$target.\'\"\' : \'\')\r\n               . ((!$wraptag && $class) ? \' class=\"\'.$class.\'\"\'   : \'\') ) ;\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Returns path to textpattern backend\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_backend()\r\n{\r\n//            . substr(strrchr(txpath, \"/\"), 1)\r\n    return( preg_replace(\'|//$|\',\'/\', rhu.\'/\')\r\n            . substr(strrchr(txpath, DS), 1)\r\n            . \'/index.php\'\r\n           ) ;\r\n}\r\n\r\n// -- Returns button (standalone) or link to edit current article\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_edit_article( $atts )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n\r\n    extract( lAtts( array ( \'label\' => _cbe_fa_gTxt( \'edit\' )\r\n                          , \'type\'  => \'button\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $path_parts = array( \'event\'      => \'article\'\r\n                       , \'step\'       => \'edit\'\r\n                       , \'ID\'         => $thisarticle[\'thisid\']\r\n                       ) ;\r\n\r\n    if( $type == \'button\' )\r\n    {\r\n        $out = array() ;\r\n\r\n        foreach( $path_parts as $part => $value )\r\n            $out[] = hInput( $part, $value ) ;\r\n\r\n        $out[] = cbe_frontauth_submit( array( \'label\' => $label, \'type\' => \'\', \'class\' =>\'publish\' ) ) ;\r\n\r\n         return( _cbe_fa_form( array( \'statements\' => join( n, $out ) ) ) ) ;\r\n    }\r\n    elseif( $type == \'link\' )\r\n    {\r\n        $path_parts[ \'_txp_token\' ] = form_token() ;\r\n        array_walk( $path_parts, create_function( \'&$v, $k\', \'$v = $k.\"=\".$v ;\' ) ) ;\r\n        $link = cbe_frontauth_backend() . \'?\' . join( \'&\', $path_parts ) ;\r\n\r\n        return( cbe_frontauth_link( compact( \'link\', \'label\'\r\n                                           , array_keys( _cbe_fa_format() )\r\n                                           )\r\n                                  )\r\n              ) ;\r\n    }\r\n    else\r\n        return ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Utilities (kinda private functions)\r\n **\r\n **************************************************/\r\n\r\n// -- Gets and returns local lang strings (txp admin + plugin specifics)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_gTxt( $text, $atts = array() )\r\n{\r\n    static $aTexts = array() ;\r\n    if( ! $aTexts )\r\n        $aTexts = _cbe_fa_lang() ;\r\n\r\n    return( isset( $aTexts[ $text ] ) ? strtr( $aTexts[ $text ], $atts ) : gTxt( $text ) ) ;\r\n}\r\n\r\n// -- Common presentational attributes\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_format()\r\n{\r\n    return( array( \'wraptag\'    => \'\'\r\n                 , \'class\'      => \'\'\r\n                 , \'break\'      => \'\'\r\n                 , \'breakclass\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\n// -- Global initialisations (redirect, invite, label, loginwith)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_init( $atts, $type )\r\n{\r\n    extract( lAtts( array ( \'for\' => \'\', \'value\' => \'\' ), $atts ) ) ;\r\n    if( $for === \'\' )\r\n        $for = \'login\' ;\r\n    $init_for = do_list( $for ) ;\r\n\r\n    if( ($index=array_search( \'logged\', $init_for )) !== false )\r\n        $init_for[ $index ] = \'logout\' ;\r\n\r\n    array_walk( $init_for, create_function( \'&$v, $k, $p\', \'$v = $v.\"_\".$p ;\'), $type ) ;\r\n\r\n    if( ($init_list = @array_combine( $init_for, do_list( $value ) )) === false )\r\n        return ;\r\n\r\n    cbe_frontauth( array( \'init\' => \'1\' ) + $init_list ) ;\r\n    return ;\r\n}\r\n\r\n// -- Retrieve user\'s info, if connected \r\n// -- textpattern/lib/txp_misc.php - is_logged_in() as a starting point\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_logged_in( &$user, $txp_user = null )\r\n{\r\n    if( $txp_user !== null )\r\n        $name = $txp_user ;\r\n    elseif( !($name = substr(cs(\'txp_login_public\'), 10)) )\r\n    {\r\n        $user[ \'name\' ] = false ;\r\n        return( false ) ;\r\n    }\r\n\r\n    $rs = safe_row(\'nonce, name, RealName, email, privs, last_access\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n    if( $rs && ($txp_user !== null || substr(md5($rs[\'nonce\']), -10) === substr(cs(\'txp_login_public\'), 0, 10) ) )\r\n    {\r\n        unset( $rs[ \'nonce\' ] ) ;\r\n        $user = $rs ;\r\n        return( true ) ;\r\n    }\r\n    else\r\n    {\r\n        $user[ \'name\' ] = false ;\r\n        return( false ) ;\r\n    }\r\n}\r\n\r\n// -- Checks current user against required privileges\r\n// -- Thanks to Ruud Van Melick\'s rvm_privileged (http://vanmelick.com/txp/)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_privileged( $r_name, $r_level, $u_name, $u_level )\r\n{\r\n    $chk_name  = !$r_name  || in_array( $u_name , do_list( $r_name  ) ) ;\r\n    $chk_level = !$r_level || in_array( $u_level, do_list( $r_level ) ) ;\r\n    return( $chk_name || $chk_level ) ;\r\n}\r\n\r\n// -- Generates input field for name or password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_identity( $field, $atts, $value=null )\r\n{\r\n    extract( lAtts( array ( \'label\'     => _cbe_fa_gTxt( $field )\r\n                          , \'label_sfx\' => \'\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $$field = \'\' ;\r\n    if( $field == \'name\' && cs(\'cbe_frontauth_login\') != \'\' )\r\n        list($name) = explode( \',\', cs(\'cbe_frontauth_login\') ) ;\r\n\r\n    $out = array() ;\r\n    $out[] = \'<label for=\"\'.$field.$label_sfx.\'\">\'.$label.\'</label>\' ;\r\n    $out[] =  fInput( ($field == \'name\')    ? \'text\'     : \'password\'\r\n                    ,(($field == \'name\')    ? \'p_userid\' : \'p_password\') . $label_sfx\r\n                    , ($field == \'name\')    ? $name      : ($value !== null ? $value : \'\')\r\n                    , (!$wraptag && $class) ? $class     : \'\'\r\n                    , \'\', \'\', \'\', \'\', $field.$label_sfx ) ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\n// -- Prepare call to cbe_frontauth() for login/logout form/link\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_inout_process( $inout, $atts, $thing = \'\' )\r\n{\r\n    $plus_atts = ($inout == \'logout\' )\r\n               ? array( \'type\'        => \'button\'\r\n                      , \'show_change\' => \'1\'      )\r\n               : array( \'show_stay\'   => \'0\'\r\n                      , \'show_reset\'  => \'1\'      ) ;\r\n    $public_atts = lAtts( array ( \'invite\'     => _cbe_fa_gTxt( ($inout == \'login\') ? \'login_to_textpattern\' \r\n                                                                                    : \'\' )\r\n                                , \'tag_invite\' => \'\'\r\n                                , \'label\'      => _cbe_fa_gTxt( ($inout == \'login\') ? \'log_in_button\'\r\n                                                                                    : \'logout\' )\r\n                                , \'form\'       => \'\'\r\n                                , \'tag_error\'  => \'span\', \'class_error\' => \'cbe_fa_error\'\r\n                                )\r\n                        + $plus_atts + _cbe_fa_format()\r\n                        , $atts ) ;\r\n\r\n    if( isset( $public_atts[\'invite\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_invite\'] = $public_atts[\'invite\'] ;\r\n        unset( $public_atts[\'invite\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'label\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_label\'] = $public_atts[\'label\'] ;\r\n        unset( $public_atts[\'label\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'form\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_form\'] = $public_atts[\'form\'] ;\r\n        unset( $public_atts[\'form\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'type\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_type\'] = $public_atts[\'type\'] ;\r\n        unset( $public_atts[\'type\'] ) ;\r\n    }\r\n    if( $thing )\r\n        $public_atts[$inout.\'_form\'] = $thing ;\r\n\r\n    $show = ($inout == \'login\') ? \'logout\' : \'login\' ;\r\n    return( cbe_frontauth( array( \'show_\'.$show => \'0\' ) + $public_atts ) ) ;\r\n}\r\n\r\n// -- Encloses statements in a submit form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_form( $atts )\r\n{\r\n    extract( lAtts( array( \'statements\' => \'\'\r\n                         , \'action\'     => cbe_frontauth_backend()\r\n                         , \'method\'     => \'post\'\r\n                         )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    if( ! $statements )\r\n        return ;\r\n\r\n    return( \'<form action=\"\'.$action.\'\" method=\"\'.$method.\'\">\'\r\n            .n. $statements\r\n            .n. \'</form>\' ) ;\r\n}\r\n\r\n// -- Generates a button (primary purpose : login/logout button)\r\n// -- Extended to \'edit\' (just in case) - 0.7\r\n// -- Note: providing a label and setting type to blank works too\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_button( $atts )\r\n{\r\n    extract( $atts ) ; // \'label\', \'type\', \'wraptag\', class\'\r\n\r\n    if( ! $label and ! ($label = cbe_frontauth( array( \'init\' => \'0\', \'value\' => $type.\'_label\' ) )) )\r\n        $label = _cbe_fa_gTxt( ($type == \'logout\' || $type == \'edit\' ) ? $type : \'log_in_button\' ) ;\r\n\r\n    $out = fInput( \'submit\', \'\', $label, (!$wraptag && $class) ? $class : \'\' ) ;\r\n \r\n    if( $type == \'logout\' )\r\n        $out .= hInput( \'p_logout\', \'1\' ) ;\r\n    elseif( $type == \'edit\' )\r\n        $out .= tInput() ;\r\n\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Generates a link (primary purpose : logout link)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_link( $atts )\r\n{\r\n    extract( $atts ) ; // \'link\', \'target\'\r\n    \r\n    if( $target == \'_get\' )\r\n    {\r\n        $uri = serverSet( \'REQUEST_URI\'  ) ;\r\n        $qus = serverSet( \'QUERY_STRING\' ) ;\r\n\r\n        $len_uri = strlen( $uri ) ;\r\n        $len_qus = strlen( $qus ) ;\r\n\r\n        $uri = ($len_qus > 0) ? substr( $uri, 0, $len_uri-$len_qus-1 ) : $uri ;\r\n        $qus = $qus . ($len_qus > 0 ? \'&\' : \'\') . $link ;\r\n\r\n        $out = (substr( $uri, -1 ) !== \'?\' ) ? ($uri.\'?\'.$qus) : ($uri.$qus) ;\r\n    }\r\n    else\r\n    {\r\n        $out = $link ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}\r\n\r\n// -- Generates login/logout form or logout link\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_inout( $atts )\r\n{\r\n    extract( $atts ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $form )\r\n        $out[] = ($f=@fetch_form( $form )) ? parse( $f ) : parse( $form ) ; // label takes precedence here\r\n    else\r\n    {\r\n        if( isset( $show_stay ) )\r\n        {   // login\r\n\r\n            $out[] = cbe_frontauth_logname(  array( \'class\' => \'edit\')\r\n                                          +  compact( \'break\', \'breakclass\' ) ) ;\r\n            $out[] = cbe_frontauth_password( array( \'class\' => \'edit\')\r\n                                           + compact( \'break\', \'breakclass\' ) ) ;\r\n            if( $show_stay )\r\n                $out[] = cbe_frontauth_stay( array() ) ;\r\n\r\n            $out[] = cbe_frontauth_submit( array( \'label\' => $label, \'class\' => \'publish\' ) ) ;\r\n\r\n        }\r\n        else\r\n        {   // logout\r\n            $out[] = ($type == \'button\')\r\n                   ? cbe_frontauth_submit( array( \'label\' => $label, \'type\' => \'logout\'\r\n                                                , \'class\' => $class ? $class : \'publish\' ) )\r\n                   : cbe_frontauth_link( array( \'label\' => $label, \'link\' => \'logout=1\', \'target\' => \'_get\'\r\n                                              , \'class\' => $class ? $class : \'publish\' ) ) ;\r\n        }\r\n    }\r\n\r\n//    $out = join( n, $out ) ;\r\n    $out = doWrap( $out, $wraptag, $break, \'\', $breakclass ) ;\r\n    return( (isset( $type ) && $type==\'link\')\r\n            ? $out\r\n            : _cbe_fa_form( array( \'statements\' => $out, \'action\' => page_url( array() ) ) ) ) ;\r\n}\r\n\r\n/* == Backbone == */\r\n\r\n// -- Cookie mechanism - from textpattern/include/txp_auth.php - doTxpValidate()\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_auth( $redir, $p_logout, $p_userid=\'\', $p_password=\'\', $p_stay=\'\' )\r\n{\r\n    defined(\'LOGIN_COOKIE_HTTP_ONLY\') || define(\'LOGIN_COOKIE_HTTP_ONLY\', true);\r\n    $hash  = md5(uniqid(mt_rand(), TRUE));\r\n    $nonce = md5($p_userid.pack(\'H*\',$hash));\r\n    $pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\') ;\r\n    $adm_path = $pub_path . substr(strrchr(txpath, DS), 1) . \'/\' ;\r\n\r\n    if( $p_logout )\r\n    {\r\n        $log_name = false ;\r\n\r\n        safe_update( \'txp_users\'\r\n                   , \"nonce = \'\".doSlash($hash).\"\'\"\r\n                   , \"name = \'\".doSlash($p_userid).\"\'\"\r\n                   ) ;\r\n\r\n        setcookie( \'txp_login\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $adm_path\r\n                 ) ;\r\n\r\n        setcookie( \'txp_login_public\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $pub_path\r\n                 ) ;\r\n\r\n        setcookie( \'cbe_frontauth_login\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $pub_path\r\n                 ) ;\r\n    }\r\n//    elseif( ($log_name = txp_validate( $p_userid, $p_password, false )) !== false )\r\n    elseif( ($log_name = txp_validate( $p_userid, $p_password )) !== false )\r\n    {\r\n        safe_update( \'txp_users\'\r\n                   , \"nonce = \'\".doSlash($nonce).\"\'\"\r\n                   , \"name = \'\".doSlash($p_userid).\"\'\"\r\n                   ) ;\r\n\r\n        setcookie( \'txp_login\'\r\n                 , $p_userid.\',\'.$hash\r\n                 , ($p_stay ? time()+3600*24*365 : 0)\r\n                 , $adm_path\r\n                 , null\r\n                 , null\r\n                 , LOGIN_COOKIE_HTTP_ONLY\r\n                 ) ;\r\n\r\n        setcookie( \'txp_login_public\'\r\n                 , substr(md5($nonce), -10).$p_userid\r\n                 , ($p_stay ? time()+3600*24*30 : 0)\r\n                 , $pub_path\r\n                 ) ;\r\n\r\n        if( $p_stay )\r\n            setcookie( \'cbe_frontauth_login\'\r\n                     , $p_userid.\',\'.$hash\r\n                     , time()+3600*24*365\r\n                     , $pub_path\r\n                     ) ;\r\n    }\r\n\r\n    if( $redir && ( $p_logout || $log_name !== false ) )\r\n    {\r\n        header( \"Location:$redir\" ) ;\r\n        exit ;\r\n    }\r\n\r\n    return( $log_name ) ;\r\n}\r\n\r\n// -- Get the job done\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth( $atts, $thing = null )\r\n{\r\n    include_once( txpath.\'/include/txp_auth.php\' ) ;\r\n    global $txp_user ;\r\n    static $inits = array( \'login_invite\' => \'\' , \'logout_invite\' => \'\' , \'tag_invite\' => \'\'\r\n                         , \'login_label\'  => \'\' , \'logout_label\'  => \'\'\r\n                         , \'login_redir\'  => \'\' , \'logout_redir\'  => \'\'\r\n                         , \'login_with\'   => \'\'\r\n                         ) ;\r\n    static $cbe_fa_user = array( \'name\'  => false , \'RealName\'    => \'\' , \'email\' => \'\'\r\n                               , \'privs\' => \'\'    , \'last_access\' => \'\'\r\n                               ) ;\r\n\r\n    if( isset( $atts[\'init\'] ) )\r\n    {\r\n        if( $atts[\'init\'] )\r\n        {\r\n            unset( $atts[\'init\'] ) ;\r\n\r\n            foreach( $atts as $param => $value )\r\n                $inits[$param] = $value ;\r\n\r\n            return ;\r\n        }\r\n        else\r\n        {\r\n            if( is_array( $atts[ \'value\' ] ) )\r\n            {\r\n                $whois = array() ;\r\n                if( ! $cbe_fa_user[ \'name\' ] ) _cbe_fa_logged_in( $cbe_fa_user ) ;\r\n                foreach( $atts[ \'value\' ] as $type )\r\n                    $whois[ $type ] = $cbe_fa_user[ $type ] ;\r\n                \r\n                return( $whois ) ;\r\n            }\r\n            else\r\n                return( isset( $inits[ $atts[ \'value\' ] ] ) ? $inits[ $atts[ \'value\' ] ] : \'\' ) ;\r\n        }\r\n    }\r\n\r\n    $def_atts = array( \'form\'          => \'\'\r\n                     , \'tag_invite\'    => \'\'\r\n                     , \'show_login\'    => \'1\'\r\n                     , \'login_invite\'  => _cbe_fa_gTxt( \'login_to_textpattern\' )\r\n                     , \'login_form\'    => \'\'\r\n                     , \'login_label\'   => _cbe_fa_gTxt( \'log_in_button\' )\r\n                     , \'login_with\'    => \'auto\'\r\n                     , \'login_redir\'   => \'\'\r\n                     , \'show_logout\'   => \'1\'\r\n                     , \'logout_invite\' => \'\'\r\n                     , \'logout_form\'   => \'\'\r\n                     , \'logout_label\'  => _cbe_fa_gTxt( \'logout\' )\r\n                     , \'logout_type\'   => \'button\'\r\n                     , \'logout_redir\'  => \'\'\r\n                     , \'show_stay\'     => \'0\'\r\n                     , \'show_reset\'    => \'1\'\r\n                     , \'show_change\'   => \'1\'\r\n                     , \'link\'          => \'\'\r\n                     , \'linklabel\'     => \'\'\r\n                     , \'target\'        => \'_self\'\r\n                     , \'name\'          => \'\'\r\n                     , \'level\'         => \'\'\r\n                     , \'tag_error\'     => \'\'\r\n                     , \'class_error\'   => \'\'\r\n                     ) ;\r\n\r\n    $ini_atts = array() ;\r\n    foreach( $inits as $param => $value )\r\n    {   /* Inits take precedence on default values */\r\n        if( !isset( $atts[$param] ) || $atts[$param] === $def_atts[$param] )\r\n            $ini_atts[$param] = $value ;\r\n    }\r\n\r\n    extract( lAtts( $def_atts + _cbe_fa_format(), array_merge( $atts, array_filter( $ini_atts ) ) ) ) ;\r\n\r\n    extract( psa( array( \'p_userid\', \'p_password\', \'p_stay\', \'p_reset\', \'p_logout\', \'p_change\' ) ) ) ;\r\n    $logout = gps( \'logout\' ) ;\r\n    $p_logout = $p_logout || $logout ;\r\n    $reset = gps( \'reset\' ) ;\r\n    $p_reset = $p_reset || $reset ;\r\n    $change = gps( \'change\' ) ;\r\n    $p_change = $p_change || $change ;\r\n\r\n    if( $p_userid && $p_password )\r\n    {\r\n        $username = ($login_with == \'auto\') ? safe_count( \'txp_users\', \"name=\'$p_userid\'\" ) : 0 ;\r\n\r\n        if( $username == 0 && $login_with != \'username\' )\r\n        { // Email probably given, retrieve user name if possible\r\n            $p_userid = safe_rows( \'name\', \'txp_users\', \"email=\'$p_userid\'\" ) ;\r\n            $p_userid = (count( $p_userid ) == 1) ? $p_userid[ 0 ][ \'name\' ] : \'\' ;\r\n        }\r\n\r\n        $login_redir = ($login_redir===\'link\') ? $link : $login_redir ;\r\n        $login_failed = ($txp_user = _cbe_fa_auth( $login_redir, 0, $p_userid, $p_password, $p_stay )) === false ;\r\n        _cbe_fa_logged_in( $cbe_fa_user, $txp_user ) ;\r\n    }\r\n    elseif( $p_logout )\r\n    {\r\n        if( $logout && !$logout_redir )\r\n            $logout_redir = preg_replace( \"/[?&]logout=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n\r\n        $txp_user = _cbe_fa_auth( $logout_redir, 1 ) ;\r\n        _cbe_fa_logged_in( $cbe_fa_user, false ) ;\r\n    }\r\n    else\r\n        $txp_user = _cbe_fa_logged_in( $cbe_fa_user ) ? $cbe_fa_user[ \'name\' ] : false ;\r\n\r\n    $out = array() ;\r\n    $invite = \'\' ;\r\n    $part_0 = EvalElse( $thing, 0 ) ;\r\n    $part_1 = EvalElse( $thing, 1 ) ;\r\n    if( $txp_user === false )\r\n    {\r\n        $out[] = parse( $part_0 ) ;\r\n\r\n        if( $show_login )\r\n        {\r\n            if( $p_reset )\r\n            {   // Resetting password in progress\r\n                $invite = _cbe_fa_gTxt( \'password_reset\' ) ;\r\n                $out[]  = callback_event( \'cbefrontauth.reset_password\', \'cbe_fa_before_login\', 0\r\n                                        , ps(\'step\') ? array( \'p_userid\'   => $p_userid\r\n                                                            , \'login_with\' => $login_with\r\n                                                            , \'tag_error\'  => $tag_error\r\n                                                            , \'class_error\' => $class_error )\r\n                                                     : null ) ;\r\n            }\r\n            else\r\n            {   // We are not resetting the password at the moment, display login form\r\n                if( isset( $login_failed ) && $login_failed )\r\n                    $out[] = doTag( _cbe_fa_gTxt( \'login_failed\' ), $tag_error, $class_error ) ;\r\n                $invite = $login_invite ;\r\n                $out[]  = _cbe_fa_inout( array( \'label\'      => $login_label\r\n                                              , \'form\'       => $login_form\r\n                                              , \'show_stay\'  => $show_stay\r\n                                              , \'show_reset\' => $show_reset\r\n                                              ) + compact( \'wraptag\', \'class\', \'break\', \'breakclass\' ) ) ;\r\n                if( $show_reset )\r\n                    $out[] = callback_event( \'cbefrontauth.reset_password\', \'cbe_fa_after_login\' ) ;\r\n            }\r\n        }\r\n    }\r\n    else\r\n    {\r\n        if( (!$name && !$level)\r\n            ||\r\n            _cbe_fa_privileged( $name, $level, $cbe_fa_user[ \'name\' ], $cbe_fa_user[ \'privs\' ] )\r\n          )\r\n        {\r\n            if( $link )\r\n                $out[] = cbe_frontauth_link( array( \'label\' => $linklabel ) + compact( \'link\', \'target\' ) ) ;\r\n\r\n            if( $thing )\r\n                $out[] = parse( $part_1 ) ;\r\n            elseif( $form )\r\n                $out[] = parse_form( $form ) ;\r\n        }\r\n        else\r\n            $out[] = parse( $part_0 ) ;\r\n\r\n        if( $show_logout )\r\n        {\r\n            if( $p_change )\r\n            {   // Changing password in progress\r\n                $invite = _cbe_fa_gTxt( \'change_password\' ) ;\r\n                $out[]  = callback_event( \'cbefrontauth.change_password\', \'cbe_fa_before_logout\', 0\r\n                                        , ps(\'step\') ? array( \'p_userid\'     => $txp_user\r\n                                                            , \'p_password\'   => $p_password\r\n                                                            , \'p_password_1\'\r\n                                                               => strip_tags( ps( \'p_password_1\' ) )\r\n                                                            , \'p_password_2\'\r\n                                                               => strip_tags( ps( \'p_password_2\' ) )\r\n                                                            , \'tag_error\'    => $tag_error\r\n                                                            , \'class_error\'  => $class_error )\r\n                                                     : null ) ;\r\n            }\r\n            else\r\n            {   // We are not changing the password at the moment, display logout form\r\n                $invite = $logout_invite ;\r\n                $out[] = _cbe_fa_inout( array( \'label\'       => $logout_label\r\n                                             , \'form\'        => $logout_form\r\n                                             , \'type\'        => $logout_type\r\n                                             , \'show_change\' => $show_change\r\n                                             , \'p_change\'    => $p_change\r\n                                             , \'tag_error\'   => $tag_error\r\n                                             , \'class_error\' => $class_error\r\n                                             ) + compact( \'wraptag\', \'class\', \'break\', \'breakclass\' ) ) ;\r\n                if( $show_change )\r\n                    $out[] = callback_event( \'cbefrontauth.change_password\', \'cbe_fa_after_logout\' ) ;\r\n            }\r\n        }\r\n    }\r\n\r\n//    return( doLabel( $invite, $tag_invite ) . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n    return( doLabel( $invite, $tag_invite ) . doWrap( $out, $wraptag, \'\', $class ) ) ;\r\n}\r\n', '/*\r\n** cbe_frontauth\r\n** Client-side textpattern plugin\r\n** Connect (and disconnect) from frontend to backend\r\n** Establishes bidirectional links between article display and edition\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1-dev - 22 Jul 2011 - Restricted development release\r\n** 0.2-dev - 23 Jul 2011 - Restricted development release\r\n** 0.3-dev - 24 Jul 2011 - Restricted development release\r\n** 0.4-beta- 26 Jul 2011 - Restricted beta release\r\n** 0.5-beta- 27 Jul 2011 - First public beta release\r\n** 0.6-beta- 29 Jul 2011 - Optimizations to avoid multiple calls to database\r\n**                           when retrieving user\'s informations\r\n**                         Added name and privilege controls\r\n**                           à la <txp:rvm_if_privileged /> (http://vanmelick.com/txp/)\r\n**                         Minor changes to documentation\r\n** 0.7-beta- 06 Aug 2011 - Introduces <txp:cbe_frontauth_edit_article />\r\n**                         CSRF protection ready\r\n**                         Documentation improvements\r\n** 0.7.1   - 05 Jan 2012 - Documentation addenda\r\n** 0.8     - 10 Jan 2012 - Introduces <txp:cbe_frontauth_loginwith />\r\n**                             http://forum.textpattern.com/viewtopic.php?pid=256632#p256632\r\n**                         txp:cbe_frontauth_loginwith (*auto*|username|email)\r\n** 0.9     - 21 Mar 2012 - Added callback hooks (reset and change password)\r\n** 0.9.1   - 22 Mar 2012 - Fixed missing attributes (show_login and show_change) for cbe_frontauth_box\r\n** 0.9.2   - ?? ??? 2012 - ??\r\n** 0.9.3   - 22 Aug 2012 - Doc typo for cbe_frontauth_invite\r\n** 0.9.4   - 27 Mar 2013 - Missing initialization for cbe_frontauth_whois\r\n**                         Error message when login fails\r\n**                         Local language strings\r\n** 0.9.5   - 04 Apr 2014 - Missing last access storage\r\n** 0.9.6   - 07 Apr 2014 - Error when passing presentational attributes from cbe_frontauth_edit_article to cbe_frontauth_link\r\n** 0.9.7   - 20 Nov 2015 - fix: http://forum.textpattern.com/viewtopic.php?pid=296720#p296720\r\n**\r\n** TODO\r\n** - break, breakclass -> in progress, full tests needed\r\n** - enhence error messages ?\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Local language strings, possible customisation here\r\n **\r\n **************************************************/\r\nfunction _cbe_fa_lang()\r\n{\r\n    return( array( \'login_failed\'         => \"Login failed\"\r\n                 , \'login_to_textpattern\' => gTxt( \'login_to_textpattern\' )\r\n                 , \'name\'                 => gTxt( \'name\' )\r\n                 , \'password\'             => gTxt( \'password\' )\r\n                 , \'log_in_button\'        => gTxt( \'log_in_button\' )\r\n                 , \'stay_logged_in\'       => gTxt( \'stay_logged_in\' )\r\n                 , \'logout\'               => gTxt( \'logout\' )\r\n                 , \'edit\'                 => gTxt( \'edit\' )\r\n                 , \'change_password\'      => gTxt( \'change_password\' )\r\n                 , \'password_reset\'       => gTxt( \'password_reset\' ) \r\n                 )\r\n          ) ;\r\n}\r\n/**************************************************\r\n **\r\n ** Don\'t edit further\r\n **\r\n **************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n/* == Shortcuts for cbe_frontauth() == */\r\n\r\n// -- Global init for redirection after login and/or logout\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_redirect( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'redir\' ) ) ;\r\n}\r\n\r\n// -- Global init for login/logout invites\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_invite( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'invite\' ) ) ;\r\n}\r\n\r\n// -- Global init for login/logout buttons/link labels\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_label( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'label\' ) ) ;\r\n}\r\n\r\n// -- Global init for login with user name, email, or automatic detection\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_loginwith( $atts )\r\n{\r\n    return( _cbe_fa_init( $atts, \'with\' ) ) ;\r\n}\r\n\r\n// -- Login / Logout box\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_box( $atts, $thing = \'\' )\r\n{\r\n    $public_atts = lAtts( array( \'login_invite\'  => _cbe_fa_gTxt( \'login_to_textpattern\' )\r\n                               , \'logout_invite\' => \'\'\r\n                               , \'show_change\'   => \'1\'\r\n                               , \'show_reset\'    => \'1\'\r\n                               , \'tag_invite\'    => \'\'\r\n                               , \'login_label\'   => _cbe_fa_gTxt( \'log_in_button\' )\r\n                               , \'logout_label\'  => _cbe_fa_gTxt( \'logout\' )\r\n                               , \'logout_type\'   => \'button\'\r\n                               , \'tag_error\'     => \'span\'\r\n                               , \'class_error\'   => \'cbe_fa_error\'\r\n                               )\r\n                           + _cbe_fa_format()\r\n                         , $atts ) ;\r\n\r\n    return( cbe_frontauth( $public_atts\r\n                          , $thing ? $thing : \'<p><txp:text item=\"logged_in_as\" /> <txp:cbe_frontauth_whois wraptag=\"span\" class=\"user\"/></p>\'\r\n                          ) ) ;\r\n\r\n}\r\n\r\n// -- Standalone login form\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_login( $atts, $thing = \'\' )\r\n{\r\n    return( _cbe_fa_inout_process( \'login\', $atts, $thing ) ) ;\r\n}\r\n\r\n// -- Standalone logout form / link\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_logout( $atts, $thing = \'\' )\r\n{\r\n    return( _cbe_fa_inout_process( \'logout\', $atts, $thing ) ) ;\r\n}\r\n\r\n// -- Protect parts from non-connected viewers\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_protect( $atts, $thing )\r\n{\r\n    $public_atts = lAtts( array( \'link\'      => \'\'\r\n                               , \'linklabel\' => \'\'\r\n                               , \'target\'    => \'_self\'\r\n                               , \'name\'      => \'\'\r\n                               , \'level\'     => \'\'\r\n                               )\r\n                         + _cbe_fa_format()\r\n                         , $atts ) ;\r\n\r\n    if( $public_atts[\'target\'] == \'_get\' )\r\n        $public_atts[\'target\'] = \'_self\' ;\r\n\r\n    return( cbe_frontauth( array( \'login_invite\' => \'\' , \'logout_invite\' => \'\'\r\n                                , \'show_login\'   => \'0\', \'show_logout\'   => \'0\'\r\n                                , \'show_reset\'   => \'0\', \'show_change\'   => \'0\' ) + $public_atts\r\n                         , $thing\r\n                         ) ) ;\r\n}\r\nfunction cbe_frontauth_if_logged( $atts, $thing )\r\n{\r\n    return( cbe_frontauth_protect( $atts, $thing ) ) ;\r\n}\r\nfunction cbe_frontauth_if_connected( $atts, $thing )\r\n{\r\n    return( cbe_frontauth_protect( $atts, $thing ) ) ;\r\n}\r\n\r\n/* == Elements == */\r\n\r\n// -- Generates input field for name\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_logname( $atts, $defvalue=null )\r\n{\r\n    return( _cbe_fa_identity( \'name\', $atts, $defvalue ) ) ;\r\n}\r\n\r\n// -- Generates input field for password\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_password( $atts, $defvalue=null )\r\n{\r\n    return( _cbe_fa_identity( \'password\', $atts, $defvalue ) ) ;\r\n}\r\n\r\n// -- Generates checkbox for stay (connected on this browser)\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_stay( $atts )\r\n{\r\n    extract( lAtts( array ( \'label\' => _cbe_fa_gTxt( \'stay_logged_in\' )\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out  = checkbox(\'p_stay\', 1, cs(\'txp_login\'), \'\', \'stay\') ;\r\n    $out .= \'<label for=\"stay\">\'.$label.\'</label>\' ;\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Generates submit button\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_submit( $atts )\r\n{\r\n    $public_atts = lAtts( array ( \'label\' => \'\'\r\n                                , \'type\'  => \'login\'\r\n                                )\r\n                        + _cbe_fa_format()\r\n                        , $atts\r\n                        ) ;\r\n\r\n    return( _cbe_fa_button( $public_atts ) ) ;\r\n}\r\n\r\n// -- Displays connected user\'s informations\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_whois( $atts )\r\n{\r\n    extract( lAtts( array ( \'type\'       => \'name\'\r\n                          , \'format\'     => \'\'\r\n                          )\r\n                    + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $types = do_list( $type ) ;\r\n    $whois = cbe_frontauth( array( \'init\' => \'0\', \'value\' => $types ) ) ;\r\n\r\n    if( isset( $whois[\'last_access\'] ) )\r\n    {\r\n        global $dateformat ;\r\n        $whois[\'last_access\'] = safe_strftime( $format ? $format : $dateformat, strtotime( $whois[\'last_access\'] ) ) ;\r\n    }\r\n\r\n    return( doWrap( $whois, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\n/* == Off-topic, but useful == */\r\n\r\n// -- Generates a link, normal or with a GET parameter\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_link( $atts )\r\n{   // $class applies to anchor if no $wraptag supplied\r\n    extract( lAtts( array ( \'label\'  => \'\'\r\n                          , \'link\'   => \'\'\r\n                          , \'target\' => \'_self\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $link = doStripTags( $link ) ;\r\n    $out = _cbe_fa_link( compact( \'link\', \'target\' ) ) ;\r\n    $out = href( $label, $out\r\n               , (($target !== \'_get\')  ? \' target=\"\'.$target.\'\"\' : \'\')\r\n               . ((!$wraptag && $class) ? \' class=\"\'.$class.\'\"\'   : \'\') ) ;\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Returns path to textpattern backend\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_backend()\r\n{\r\n//            . substr(strrchr(txpath, \"/\"), 1)\r\n    return( preg_replace(\'|//$|\',\'/\', rhu.\'/\')\r\n            . substr(strrchr(txpath, DS), 1)\r\n            . \'/index.php\'\r\n           ) ;\r\n}\r\n\r\n// -- Returns button (standalone) or link to edit current article\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth_edit_article( $atts )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n\r\n    extract( lAtts( array ( \'label\' => _cbe_fa_gTxt( \'edit\' )\r\n                          , \'type\'  => \'button\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $path_parts = array( \'event\'      => \'article\'\r\n                       , \'step\'       => \'edit\'\r\n                       , \'ID\'         => $thisarticle[\'thisid\']\r\n                       ) ;\r\n\r\n    if( $type == \'button\' )\r\n    {\r\n        $out = array() ;\r\n\r\n        foreach( $path_parts as $part => $value )\r\n            $out[] = hInput( $part, $value ) ;\r\n\r\n        $out[] = cbe_frontauth_submit( array( \'label\' => $label, \'type\' => \'\', \'class\' =>\'publish\' ) ) ;\r\n\r\n         return( _cbe_fa_form( array( \'statements\' => join( n, $out ) ) ) ) ;\r\n    }\r\n    elseif( $type == \'link\' )\r\n    {\r\n        $path_parts[ \'_txp_token\' ] = form_token() ;\r\n        array_walk( $path_parts, create_function( \'&$v, $k\', \'$v = $k.\"=\".$v ;\' ) ) ;\r\n        $link = cbe_frontauth_backend() . \'?\' . join( \'&\', $path_parts ) ;\r\n\r\n        return( cbe_frontauth_link( compact( \'link\', \'label\'\r\n                                           , array_keys( _cbe_fa_format() )\r\n                                           )\r\n                                  )\r\n              ) ;\r\n    }\r\n    else\r\n        return ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Utilities (kinda private functions)\r\n **\r\n **************************************************/\r\n\r\n// -- Gets and returns local lang strings (txp admin + plugin specifics)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_gTxt( $text, $atts = array() )\r\n{\r\n    static $aTexts = array() ;\r\n    if( ! $aTexts )\r\n        $aTexts = _cbe_fa_lang() ;\r\n\r\n    return( isset( $aTexts[ $text ] ) ? strtr( $aTexts[ $text ], $atts ) : gTxt( $text ) ) ;\r\n}\r\n\r\n// -- Common presentational attributes\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_format()\r\n{\r\n    return( array( \'wraptag\'    => \'\'\r\n                 , \'class\'      => \'\'\r\n                 , \'break\'      => \'\'\r\n                 , \'breakclass\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\n// -- Global initialisations (redirect, invite, label, loginwith)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_init( $atts, $type )\r\n{\r\n    extract( lAtts( array ( \'for\' => \'\', \'value\' => \'\' ), $atts ) ) ;\r\n    if( $for === \'\' )\r\n        $for = \'login\' ;\r\n    $init_for = do_list( $for ) ;\r\n\r\n    if( ($index=array_search( \'logged\', $init_for )) !== false )\r\n        $init_for[ $index ] = \'logout\' ;\r\n\r\n    array_walk( $init_for, create_function( \'&$v, $k, $p\', \'$v = $v.\"_\".$p ;\'), $type ) ;\r\n\r\n    if( ($init_list = @array_combine( $init_for, do_list( $value ) )) === false )\r\n        return ;\r\n\r\n    cbe_frontauth( array( \'init\' => \'1\' ) + $init_list ) ;\r\n    return ;\r\n}\r\n\r\n// -- Retrieve user\'s info, if connected \r\n// -- textpattern/lib/txp_misc.php - is_logged_in() as a starting point\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_logged_in( &$user, $txp_user = null )\r\n{\r\n    if( $txp_user !== null )\r\n        $name = $txp_user ;\r\n    elseif( !($name = substr(cs(\'txp_login_public\'), 10)) )\r\n    {\r\n        $user[ \'name\' ] = false ;\r\n        return( false ) ;\r\n    }\r\n\r\n    $rs = safe_row(\'nonce, name, RealName, email, privs, last_access\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n    if( $rs && ($txp_user !== null || substr(md5($rs[\'nonce\']), -10) === substr(cs(\'txp_login_public\'), 0, 10) ) )\r\n    {\r\n        unset( $rs[ \'nonce\' ] ) ;\r\n        $user = $rs ;\r\n        return( true ) ;\r\n    }\r\n    else\r\n    {\r\n        $user[ \'name\' ] = false ;\r\n        return( false ) ;\r\n    }\r\n}\r\n\r\n// -- Checks current user against required privileges\r\n// -- Thanks to Ruud Van Melick\'s rvm_privileged (http://vanmelick.com/txp/)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_privileged( $r_name, $r_level, $u_name, $u_level )\r\n{\r\n    $chk_name  = !$r_name  || in_array( $u_name , do_list( $r_name  ) ) ;\r\n    $chk_level = !$r_level || in_array( $u_level, do_list( $r_level ) ) ;\r\n    return( $chk_name || $chk_level ) ;\r\n}\r\n\r\n// -- Generates input field for name or password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_identity( $field, $atts, $value=null )\r\n{\r\n    extract( lAtts( array ( \'label\'     => _cbe_fa_gTxt( $field )\r\n                          , \'label_sfx\' => \'\'\r\n                          )\r\n                  + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $$field = \'\' ;\r\n    if( $field == \'name\' && cs(\'cbe_frontauth_login\') != \'\' )\r\n        list($name) = explode( \',\', cs(\'cbe_frontauth_login\') ) ;\r\n\r\n    $out = array() ;\r\n    $out[] = \'<label for=\"\'.$field.$label_sfx.\'\">\'.$label.\'</label>\' ;\r\n    $out[] =  fInput( ($field == \'name\')    ? \'text\'     : \'password\'\r\n                    ,(($field == \'name\')    ? \'p_userid\' : \'p_password\') . $label_sfx\r\n                    , ($field == \'name\')    ? $name      : ($value !== null ? $value : \'\')\r\n                    , (!$wraptag && $class) ? $class     : \'\'\r\n                    , \'\', \'\', \'\', \'\', $field.$label_sfx ) ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\n// -- Prepare call to cbe_frontauth() for login/logout form/link\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_inout_process( $inout, $atts, $thing = \'\' )\r\n{\r\n    $plus_atts = ($inout == \'logout\' )\r\n               ? array( \'type\'        => \'button\'\r\n                      , \'show_change\' => \'1\'      )\r\n               : array( \'show_stay\'   => \'0\'\r\n                      , \'show_reset\'  => \'1\'      ) ;\r\n    $public_atts = lAtts( array ( \'invite\'     => _cbe_fa_gTxt( ($inout == \'login\') ? \'login_to_textpattern\' \r\n                                                                                    : \'\' )\r\n                                , \'tag_invite\' => \'\'\r\n                                , \'label\'      => _cbe_fa_gTxt( ($inout == \'login\') ? \'log_in_button\'\r\n                                                                                    : \'logout\' )\r\n                                , \'form\'       => \'\'\r\n                                , \'tag_error\'  => \'span\', \'class_error\' => \'cbe_fa_error\'\r\n                                )\r\n                        + $plus_atts + _cbe_fa_format()\r\n                        , $atts ) ;\r\n\r\n    if( isset( $public_atts[\'invite\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_invite\'] = $public_atts[\'invite\'] ;\r\n        unset( $public_atts[\'invite\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'label\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_label\'] = $public_atts[\'label\'] ;\r\n        unset( $public_atts[\'label\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'form\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_form\'] = $public_atts[\'form\'] ;\r\n        unset( $public_atts[\'form\'] ) ;\r\n    }\r\n    if( isset( $public_atts[\'type\'] ) )\r\n    {\r\n        $public_atts[$inout.\'_type\'] = $public_atts[\'type\'] ;\r\n        unset( $public_atts[\'type\'] ) ;\r\n    }\r\n    if( $thing )\r\n        $public_atts[$inout.\'_form\'] = $thing ;\r\n\r\n    $show = ($inout == \'login\') ? \'logout\' : \'login\' ;\r\n    return( cbe_frontauth( array( \'show_\'.$show => \'0\' ) + $public_atts ) ) ;\r\n}\r\n\r\n// -- Encloses statements in a submit form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_form( $atts )\r\n{\r\n    extract( lAtts( array( \'statements\' => \'\'\r\n                         , \'action\'     => cbe_frontauth_backend()\r\n                         , \'method\'     => \'post\'\r\n                         )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    if( ! $statements )\r\n        return ;\r\n\r\n    return( \'<form action=\"\'.$action.\'\" method=\"\'.$method.\'\">\'\r\n            .n. $statements\r\n            .n. \'</form>\' ) ;\r\n}\r\n\r\n// -- Generates a button (primary purpose : login/logout button)\r\n// -- Extended to \'edit\' (just in case) - 0.7\r\n// -- Note: providing a label and setting type to blank works too\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_button( $atts )\r\n{\r\n    extract( $atts ) ; // \'label\', \'type\', \'wraptag\', class\'\r\n\r\n    if( ! $label and ! ($label = cbe_frontauth( array( \'init\' => \'0\', \'value\' => $type.\'_label\' ) )) )\r\n        $label = _cbe_fa_gTxt( ($type == \'logout\' || $type == \'edit\' ) ? $type : \'log_in_button\' ) ;\r\n\r\n    $out = fInput( \'submit\', \'\', $label, (!$wraptag && $class) ? $class : \'\' ) ;\r\n \r\n    if( $type == \'logout\' )\r\n        $out .= hInput( \'p_logout\', \'1\' ) ;\r\n    elseif( $type == \'edit\' )\r\n        $out .= tInput() ;\r\n\r\n    return( doTag( $out, $wraptag, $class ) ) ;\r\n}\r\n\r\n// -- Generates a link (primary purpose : logout link)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_link( $atts )\r\n{\r\n    extract( $atts ) ; // \'link\', \'target\'\r\n    \r\n    if( $target == \'_get\' )\r\n    {\r\n        $uri = serverSet( \'REQUEST_URI\'  ) ;\r\n        $qus = serverSet( \'QUERY_STRING\' ) ;\r\n\r\n        $len_uri = strlen( $uri ) ;\r\n        $len_qus = strlen( $qus ) ;\r\n\r\n        $uri = ($len_qus > 0) ? substr( $uri, 0, $len_uri-$len_qus-1 ) : $uri ;\r\n        $qus = $qus . ($len_qus > 0 ? \'&\' : \'\') . $link ;\r\n\r\n        $out = (substr( $uri, -1 ) !== \'?\' ) ? ($uri.\'?\'.$qus) : ($uri.$qus) ;\r\n    }\r\n    else\r\n    {\r\n        $out = $link ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}\r\n\r\n// -- Generates login/logout form or logout link\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_inout( $atts )\r\n{\r\n    extract( $atts ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $form )\r\n        $out[] = ($f=@fetch_form( $form )) ? parse( $f ) : parse( $form ) ; // label takes precedence here\r\n    else\r\n    {\r\n        if( isset( $show_stay ) )\r\n        {   // login\r\n\r\n            $out[] = cbe_frontauth_logname(  array( \'class\' => \'edit\')\r\n                                          +  compact( \'break\', \'breakclass\' ) ) ;\r\n            $out[] = cbe_frontauth_password( array( \'class\' => \'edit\')\r\n                                           + compact( \'break\', \'breakclass\' ) ) ;\r\n            if( $show_stay )\r\n                $out[] = cbe_frontauth_stay( array() ) ;\r\n\r\n            $out[] = cbe_frontauth_submit( array( \'label\' => $label, \'class\' => \'publish\' ) ) ;\r\n\r\n        }\r\n        else\r\n        {   // logout\r\n            $out[] = ($type == \'button\')\r\n                   ? cbe_frontauth_submit( array( \'label\' => $label, \'type\' => \'logout\'\r\n                                                , \'class\' => $class ? $class : \'publish\' ) )\r\n                   : cbe_frontauth_link( array( \'label\' => $label, \'link\' => \'logout=1\', \'target\' => \'_get\'\r\n                                              , \'class\' => $class ? $class : \'publish\' ) ) ;\r\n        }\r\n    }\r\n\r\n//    $out = join( n, $out ) ;\r\n    $out = doWrap( $out, $wraptag, $break, \'\', $breakclass ) ;\r\n    return( (isset( $type ) && $type==\'link\')\r\n            ? $out\r\n            : _cbe_fa_form( array( \'statements\' => $out, \'action\' => page_url( array() ) ) ) ) ;\r\n}\r\n\r\n/* == Backbone == */\r\n\r\n// -- Cookie mechanism - from textpattern/include/txp_auth.php - doTxpValidate()\r\n// -------------------------------------------------------------------\r\nfunction _cbe_fa_auth( $redir, $p_logout, $p_userid=\'\', $p_password=\'\', $p_stay=\'\' )\r\n{\r\n    defined(\'LOGIN_COOKIE_HTTP_ONLY\') || define(\'LOGIN_COOKIE_HTTP_ONLY\', true);\r\n    $hash  = md5(uniqid(mt_rand(), TRUE));\r\n    $nonce = md5($p_userid.pack(\'H*\',$hash));\r\n    $pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\') ;\r\n    $adm_path = $pub_path . substr(strrchr(txpath, DS), 1) . \'/\' ;\r\n\r\n    if( $p_logout )\r\n    {\r\n        $log_name = false ;\r\n\r\n        safe_update( \'txp_users\'\r\n                   , \"nonce = \'\".doSlash($hash).\"\'\"\r\n                   , \"name = \'\".doSlash($p_userid).\"\'\"\r\n                   ) ;\r\n\r\n        setcookie( \'txp_login\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $adm_path\r\n                 ) ;\r\n\r\n        setcookie( \'txp_login_public\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $pub_path\r\n                 ) ;\r\n\r\n        setcookie( \'cbe_frontauth_login\'\r\n                 , \'\'\r\n                 , time()-3600\r\n                 , $pub_path\r\n                 ) ;\r\n    }\r\n//    elseif( ($log_name = txp_validate( $p_userid, $p_password, false )) !== false )\r\n    elseif( ($log_name = txp_validate( $p_userid, $p_password )) !== false )\r\n    {\r\n        safe_update( \'txp_users\'\r\n                   , \"nonce = \'\".doSlash($nonce).\"\'\"\r\n                   , \"name = \'\".doSlash($p_userid).\"\'\"\r\n                   ) ;\r\n\r\n        setcookie( \'txp_login\'\r\n                 , $p_userid.\',\'.$hash\r\n                 , ($p_stay ? time()+3600*24*365 : 0)\r\n                 , $adm_path\r\n                 , null\r\n                 , null\r\n                 , LOGIN_COOKIE_HTTP_ONLY\r\n                 ) ;\r\n\r\n        setcookie( \'txp_login_public\'\r\n                 , substr(md5($nonce), -10).$p_userid\r\n                 , ($p_stay ? time()+3600*24*30 : 0)\r\n                 , $pub_path\r\n                 ) ;\r\n\r\n        if( $p_stay )\r\n            setcookie( \'cbe_frontauth_login\'\r\n                     , $p_userid.\',\'.$hash\r\n                     , time()+3600*24*365\r\n                     , $pub_path\r\n                     ) ;\r\n    }\r\n\r\n    if( $redir && ( $p_logout || $log_name !== false ) )\r\n    {\r\n        header( \"Location:$redir\" ) ;\r\n        exit ;\r\n    }\r\n\r\n    return( $log_name ) ;\r\n}\r\n\r\n// -- Get the job done\r\n// -------------------------------------------------------------------\r\nfunction cbe_frontauth( $atts, $thing = null )\r\n{\r\n    include_once( txpath.\'/include/txp_auth.php\' ) ;\r\n    global $txp_user ;\r\n    static $inits = array( \'login_invite\' => \'\' , \'logout_invite\' => \'\' , \'tag_invite\' => \'\'\r\n                         , \'login_label\'  => \'\' , \'logout_label\'  => \'\'\r\n                         , \'login_redir\'  => \'\' , \'logout_redir\'  => \'\'\r\n                         , \'login_with\'   => \'\'\r\n                         ) ;\r\n    static $cbe_fa_user = array( \'name\'  => false , \'RealName\'    => \'\' , \'email\' => \'\'\r\n                               , \'privs\' => \'\'    , \'last_access\' => \'\'\r\n                               ) ;\r\n\r\n    if( isset( $atts[\'init\'] ) )\r\n    {\r\n        if( $atts[\'init\'] )\r\n        {\r\n            unset( $atts[\'init\'] ) ;\r\n\r\n            foreach( $atts as $param => $value )\r\n                $inits[$param] = $value ;\r\n\r\n            return ;\r\n        }\r\n        else\r\n        {\r\n            if( is_array( $atts[ \'value\' ] ) )\r\n            {\r\n                $whois = array() ;\r\n                if( ! $cbe_fa_user[ \'name\' ] ) _cbe_fa_logged_in( $cbe_fa_user ) ;\r\n                foreach( $atts[ \'value\' ] as $type )\r\n                    $whois[ $type ] = $cbe_fa_user[ $type ] ;\r\n                \r\n                return( $whois ) ;\r\n            }\r\n            else\r\n                return( isset( $inits[ $atts[ \'value\' ] ] ) ? $inits[ $atts[ \'value\' ] ] : \'\' ) ;\r\n        }\r\n    }\r\n\r\n    $def_atts = array( \'form\'          => \'\'\r\n                     , \'tag_invite\'    => \'\'\r\n                     , \'show_login\'    => \'1\'\r\n                     , \'login_invite\'  => _cbe_fa_gTxt( \'login_to_textpattern\' )\r\n                     , \'login_form\'    => \'\'\r\n                     , \'login_label\'   => _cbe_fa_gTxt( \'log_in_button\' )\r\n                     , \'login_with\'    => \'auto\'\r\n                     , \'login_redir\'   => \'\'\r\n                     , \'show_logout\'   => \'1\'\r\n                     , \'logout_invite\' => \'\'\r\n                     , \'logout_form\'   => \'\'\r\n                     , \'logout_label\'  => _cbe_fa_gTxt( \'logout\' )\r\n                     , \'logout_type\'   => \'button\'\r\n                     , \'logout_redir\'  => \'\'\r\n                     , \'show_stay\'     => \'0\'\r\n                     , \'show_reset\'    => \'1\'\r\n                     , \'show_change\'   => \'1\'\r\n                     , \'link\'          => \'\'\r\n                     , \'linklabel\'     => \'\'\r\n                     , \'target\'        => \'_self\'\r\n                     , \'name\'          => \'\'\r\n                     , \'level\'         => \'\'\r\n                     , \'tag_error\'     => \'\'\r\n                     , \'class_error\'   => \'\'\r\n                     ) ;\r\n\r\n    $ini_atts = array() ;\r\n    foreach( $inits as $param => $value )\r\n    {   /* Inits take precedence on default values */\r\n        if( !isset( $atts[$param] ) || $atts[$param] === $def_atts[$param] )\r\n            $ini_atts[$param] = $value ;\r\n    }\r\n\r\n    extract( lAtts( $def_atts + _cbe_fa_format(), array_merge( $atts, array_filter( $ini_atts ) ) ) ) ;\r\n\r\n    extract( psa( array( \'p_userid\', \'p_password\', \'p_stay\', \'p_reset\', \'p_logout\', \'p_change\' ) ) ) ;\r\n    $logout = gps( \'logout\' ) ;\r\n    $p_logout = $p_logout || $logout ;\r\n    $reset = gps( \'reset\' ) ;\r\n    $p_reset = $p_reset || $reset ;\r\n    $change = gps( \'change\' ) ;\r\n    $p_change = $p_change || $change ;\r\n\r\n    if( $p_userid && $p_password )\r\n    {\r\n        $username = ($login_with == \'auto\') ? safe_count( \'txp_users\', \"name=\'$p_userid\'\" ) : 0 ;\r\n\r\n        if( $username == 0 && $login_with != \'username\' )\r\n        { // Email probably given, retrieve user name if possible\r\n            $p_userid = safe_rows( \'name\', \'txp_users\', \"email=\'$p_userid\'\" ) ;\r\n            $p_userid = (count( $p_userid ) == 1) ? $p_userid[ 0 ][ \'name\' ] : \'\' ;\r\n        }\r\n\r\n        $login_redir = ($login_redir===\'link\') ? $link : $login_redir ;\r\n        $login_failed = ($txp_user = _cbe_fa_auth( $login_redir, 0, $p_userid, $p_password, $p_stay )) === false ;\r\n        _cbe_fa_logged_in( $cbe_fa_user, $txp_user ) ;\r\n    }\r\n    elseif( $p_logout )\r\n    {\r\n        if( $logout && !$logout_redir )\r\n            $logout_redir = preg_replace( \"/[?&]logout=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n\r\n        $txp_user = _cbe_fa_auth( $logout_redir, 1 ) ;\r\n        _cbe_fa_logged_in( $cbe_fa_user, false ) ;\r\n    }\r\n    else\r\n        $txp_user = _cbe_fa_logged_in( $cbe_fa_user ) ? $cbe_fa_user[ \'name\' ] : false ;\r\n\r\n    $out = array() ;\r\n    $invite = \'\' ;\r\n    $part_0 = EvalElse( $thing, 0 ) ;\r\n    $part_1 = EvalElse( $thing, 1 ) ;\r\n    if( $txp_user === false )\r\n    {\r\n        $out[] = parse( $part_0 ) ;\r\n\r\n        if( $show_login )\r\n        {\r\n            if( $p_reset )\r\n            {   // Resetting password in progress\r\n                $invite = _cbe_fa_gTxt( \'password_reset\' ) ;\r\n                $out[]  = callback_event( \'cbefrontauth.reset_password\', \'cbe_fa_before_login\', 0\r\n                                        , ps(\'step\') ? array( \'p_userid\'   => $p_userid\r\n                                                            , \'login_with\' => $login_with\r\n                                                            , \'tag_error\'  => $tag_error\r\n                                                            , \'class_error\' => $class_error )\r\n                                                     : null ) ;\r\n            }\r\n            else\r\n            {   // We are not resetting the password at the moment, display login form\r\n                if( isset( $login_failed ) && $login_failed )\r\n                    $out[] = doTag( _cbe_fa_gTxt( \'login_failed\' ), $tag_error, $class_error ) ;\r\n                $invite = $login_invite ;\r\n                $out[]  = _cbe_fa_inout( array( \'label\'      => $login_label\r\n                                              , \'form\'       => $login_form\r\n                                              , \'show_stay\'  => $show_stay\r\n                                              , \'show_reset\' => $show_reset\r\n                                              ) + compact( \'wraptag\', \'class\', \'break\', \'breakclass\' ) ) ;\r\n                if( $show_reset )\r\n                    $out[] = callback_event( \'cbefrontauth.reset_password\', \'cbe_fa_after_login\' ) ;\r\n            }\r\n        }\r\n    }\r\n    else\r\n    {\r\n        if( (!$name && !$level)\r\n            ||\r\n            _cbe_fa_privileged( $name, $level, $cbe_fa_user[ \'name\' ], $cbe_fa_user[ \'privs\' ] )\r\n          )\r\n        {\r\n            if( $link )\r\n                $out[] = cbe_frontauth_link( array( \'label\' => $linklabel ) + compact( \'link\', \'target\' ) ) ;\r\n\r\n            if( $thing )\r\n                $out[] = parse( $part_1 ) ;\r\n            elseif( $form )\r\n                $out[] = parse_form( $form ) ;\r\n        }\r\n        else\r\n            $out[] = parse( $part_0 ) ;\r\n\r\n        if( $show_logout )\r\n        {\r\n            if( $p_change )\r\n            {   // Changing password in progress\r\n                $invite = _cbe_fa_gTxt( \'change_password\' ) ;\r\n                $out[]  = callback_event( \'cbefrontauth.change_password\', \'cbe_fa_before_logout\', 0\r\n                                        , ps(\'step\') ? array( \'p_userid\'     => $txp_user\r\n                                                            , \'p_password\'   => $p_password\r\n                                                            , \'p_password_1\'\r\n                                                               => strip_tags( ps( \'p_password_1\' ) )\r\n                                                            , \'p_password_2\'\r\n                                                               => strip_tags( ps( \'p_password_2\' ) )\r\n                                                            , \'tag_error\'    => $tag_error\r\n                                                            , \'class_error\'  => $class_error )\r\n                                                     : null ) ;\r\n            }\r\n            else\r\n            {   // We are not changing the password at the moment, display logout form\r\n                $invite = $logout_invite ;\r\n                $out[] = _cbe_fa_inout( array( \'label\'       => $logout_label\r\n                                             , \'form\'        => $logout_form\r\n                                             , \'type\'        => $logout_type\r\n                                             , \'show_change\' => $show_change\r\n                                             , \'p_change\'    => $p_change\r\n                                             , \'tag_error\'   => $tag_error\r\n                                             , \'class_error\' => $class_error\r\n                                             ) + compact( \'wraptag\', \'class\', \'break\', \'breakclass\' ) ) ;\r\n                if( $show_change )\r\n                    $out[] = callback_event( \'cbefrontauth.change_password\', \'cbe_fa_after_logout\' ) ;\r\n            }\r\n        }\r\n    }\r\n\r\n//    return( doLabel( $invite, $tag_invite ) . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n    return( doLabel( $invite, $tag_invite ) . doWrap( $out, $wraptag, \'\', $class ) ) ;\r\n}\r\n', '027ac0b23a21771af89f0714c8f23456', '', '', 0, 4, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_members', 0, 'Claire Brione', 'http://www.clairebrione.com/', 'a.1.2', 'cbe_frontauth companion: users accounts management (change and reset passwords)', '<div id=\"cbe-plugin-help\">\r\n\r\n<h1>cbe_members</h1>\r\n\r\n<p>It\'s a client-side plugin.\r\n<br />It currently is a companion for <code>cbe_frontauth</code> that adds the reset and change password features.</p>\r\n<p>Fully plug-and-play: just install and activate, and you\'re done !</p>\r\n<p>Developed and tested with Textpattern 4.4.1</p>\r\n<p class=\"readme\">Requires cbe_frontauth 0.9 and above</p>\r\n\r\n<h2>Table of contents</h2>\r\n<ul>\r\n  <li><a href=\"#features\">Features</a></li>\r\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\r\n  <li><a href=\"#tags-list\">Tags list</a></li>\r\n  <li><a href=\"#changelog\">Changelog</a></li>\r\n</ul>\r\n\r\n<h2 id=\"features\">Features</h2>\r\n<ul>\r\n<li><b>Change password</b>: displays a form (old password, new password twice), controls datas, if correct changes the password and mails it</li>\r\n<li><b>Reset password</b>: displays a form (user\'s name or email depending on <code>cbe_frontauth</code>\'s parameters), controls datas, if correct resets the password and mails it</li>\r\n<li><b>Error messages</b>: can be edited in the first lines of plugin code</li>\r\n</ul>\r\n\r\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\r\n\r\n<p>Download from <a href=\"http://textpattern.org/plugins/1250/cbe_members\">textpattern resources</a> or the <a href=\"http://www.clairebrione.com/cbe_members\">plugin page</a>.</p>\r\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\r\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=37760\">forum thread</a> for support.</p>\r\n\r\n<h2 id=\"tags-list\">Tags list</h2>\r\n\r\n<p>(Later)</p>\r\n\r\n<h2 id=\"changelog\">Changelog</h2>\r\n<ul>\r\n  <li>28 Mar 12 -v a.1.2- Fix: missing include for password generation</li>\r\n  <li>21 Mar 12 - v a.1 - Alpha: reset and change password</li>\r\n</ul>\r\n\r\n</div>\n<!-- --- BEGIN PLUGIN CSS --- -->\n<style type=\"text/css\">\r\n#cbe-plugin-help .readme {\r\n  color: red;\r\n  font-weight: bold;\r\n}\r\n#cbe-plugin-help .accent {\r\n  font-weight: bold;\r\n}\r\n#cbe-plugin-help .list th{\r\n	text-align:left;\r\n	color:#fff;\r\n	background:#555;\r\n	padding:6px 16px 6px 4px;\r\n	border-right:1px solid #999;\r\n	font-weight:normal;\r\n}\r\n#cbe-plugin-help .list th a{\r\n	color:#fff;\r\n	display:block;\r\n}\r\n#cbe-plugin-help .list th h2{\r\n	margin:0;\r\n	font-size:11px;\r\n}\r\n#cbe-plugin-help .list td{\r\n	padding:6px 4px;\r\n}\r\n</style>\n<!-- --- END PLUGIN CSS --- -->', '/*\r\n** cbe_members\r\n** Client-side textpattern plugin\r\n** Frontside user self management\r\n** Intended to be plugged into cbe_frontauth\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** a.1 - 21 Mar 2012 - Alpha : reset and change password\r\n** a.1.2-28 Mar 2012 - Fix: missing include for password generation\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Local lang values, possible customisation here\r\n **\r\n **************************************************/\r\n\r\nfunction _cbe_mb_lang()\r\n{\r\n    return( array( \'all_fields_required\'    => \"Merci de remplir tous les champs\"\r\n                 , \'new_password_error\'     => \"Les valeurs du nouveau mot de passe ne correspondent pas\"\r\n                 , \'old_password_error\'     => \"Ancien mot de passe invalide\"\r\n                 , \'passwords_must_be_diff\' => \"Ancien et nouveau mots de passe doivent être différents\"\r\n                 , \'password_too_short\'     => \"Nouveau mot de passe trop court (6 caractères minimum)\"\r\n                 , \'register\'               => \"Envoyer\"\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/* =========================== Stop editing =========================== */\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n// -- Self registration\r\n// -------------------------------------------------------------------\r\nfunction cbe_members( $atts, $thing = \'\' )\r\n{\r\n    /* ... (to be continued) ... */\r\n}\r\n\r\n// -------------------------------------------------------------------\r\n// -- Member management utilities\r\n// -------------------------------------------------------------------\r\n\r\n// -- Generates fields for register form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_fInput( $field, $type, $data = null )\r\n{\r\n    $out = array() ;\r\n    if( $data !== null )\r\n        extract( $data ) ;\r\n\r\n    if( $type !== \'submit\' )\r\n        $out[] = \'<label for=\"\'. $field .\'\">\'. _cbe_mb_gTxt( $field ) .\'</label>\' ;\r\n    $out[] = fInput( $type\r\n                   , $field\r\n                   , isset( $$field ) ? $$field : \'\'\r\n                   , \'\', \'\', \'\', \'\', \'\', $field ) ;\r\n    return( join( n, $out ) ) ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Functions to be plugged into cbe_frontauth\r\n **\r\n **************************************************/\r\n\r\nregister_callback( \'cbe_members_reset_password\' , \'cbefrontauth.reset_password\'  ) ;\r\nregister_callback( \'cbe_members_change_password\', \'cbefrontauth.change_password\' ) ;\r\n\r\n// -- Reset password\r\n// -- Available steps :\r\n// -- cbe_fa_before_login\r\n// -- cbe_fa_after_login\r\n// -------------------------------------------------------------------\r\nfunction cbe_members_reset_password( $event, $step, $data=null )\r\n{\r\n    global $prefs ;\r\n\r\n    if( $step == \'cbe_fa_before_login\' )\r\n    {\r\n        if( $data === null )\r\n        {\r\n            return( _cbe_mb_passwdcr_form( \'reset\', $step ) ) ;\r\n        }\r\n        else\r\n        {\r\n            extract( $data ) ;\r\n            //        ^ contains $p_userid, $login_with, $tag_error, $class_error\r\n\r\n            $return_url = PROTOCOL.$prefs[\'siteurl\']\r\n                        . preg_replace( \"/[?&]reset=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n            $out = array() ;\r\n            extract( _cbe_mb_get_author( $login_with, $p_userid ) ) ;\r\n            //        ^contains $newpass, $name, $email\r\n\r\n            $out[] = $newpass ? _cbe_mb_set_author_pass( $newpass, $name, $email, $return_url, $p_userid )\r\n                              : doTag( _cbe_mb_gTxt( \'unknown_author\', array( \'{name}\' => $p_userid ) )\r\n                                     , $tag_error, $class_error ) ;\r\n            $out[] = cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( \'revert\' )\r\n                                              , \'link\'  => $return_url\r\n                                              , \'target\' => \'_self\', \'wraptag\' => \'p\' ) ) ;\r\n            return( join( n, $out ) ) ;\r\n        }\r\n    }\r\n    elseif( $step == \'cbe_fa_after_login\' )\r\n    {\r\n        return( _cbe_mb_passwdcr_link( \'reset\', array( \'label\' => _cbe_mb_gTxt( \'password_forgotten\' ) ) ) ) ;\r\n    }\r\n}\r\n\r\n// -- Change password\r\n// -- Available steps :\r\n// -- cbe_fa_before_logout\r\n// -- cbe_fa_after_logout\r\n// -------------------------------------------------------------------\r\nfunction cbe_members_change_password( $event, $step, $data=null )\r\n{\r\n    global $prefs ;\r\n\r\n    if( $step == \'cbe_fa_before_logout\' )\r\n    {\r\n        if( $data === null )\r\n        {\r\n            return( _cbe_mb_passwdcr_form( \'change\', $step ) ) ;\r\n        }\r\n        else\r\n        {\r\n            extract( $data ) ;\r\n            //       ^ contains $p_userid, $p_password, $p_password_1, $p_password_2, $tag_error, $class_error\r\n\r\n            $controls = array( \'all_fields_required\'\r\n                                => \"empty( \\$p_password ) || empty( \\$p_password_1 ) || empty( \\$p_password_2 )\"\r\n                             , \'old_password_error\'\r\n                                => \"txp_validate( \\$p_userid, \\$p_password, false ) === false\"\r\n                             , \'new_password_error\'\r\n                                => \"\\$p_password_1 !== \\$p_password_2\"\r\n                             , \'passwords_must_be_diff\'\r\n                                => \"\\$p_password_1 === \\$p_password\"\r\n                             , \'password_too_short\'\r\n                                => \"strlen( \\$p_password_1 ) < 6\"\r\n                             ) ;\r\n\r\n            $iserror = false ;\r\n            $out = array() ;\r\n\r\n            foreach( $controls as $err => $cond )\r\n            {\r\n                if( eval( \"return( $cond ) ;\" ) )\r\n                {\r\n                    $iserror = true ;\r\n                    $out[] = _cbe_mb_message( $err, $tag_error, $class_error ) ;\r\n                    break ;\r\n                }\r\n            }\r\n\r\n            if( ! $iserror )\r\n            {\r\n                $return_url = PROTOCOL.$prefs[\'siteurl\']\r\n                            . preg_replace( \"/[?&]change=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n\r\n                $out[] = _cbe_mb_set_author_pass( $p_password_1, $p_userid\r\n                                                , fetch( \'email\', \'txp_users\', \'name\', $p_userid )\r\n                                                , $return_url ) ;\r\n                $out[] = cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( \'revert\' )\r\n                                                  , \'link\'  => $return_url\r\n                                                  , \'target\' => \'_self\', \'wraptag\' => \'p\' ) ) ;\r\n            }\r\n\r\n            if( $iserror )\r\n                $out[] =  _cbe_mb_passwdcr_form( \'change\', $step, compact( array_keys( $data ) ) ) ;\r\n\r\n            return( join( n, $out ) ) ;\r\n        }\r\n    }\r\n    elseif( $step == \'cbe_fa_after_logout\' )\r\n    {\r\n        return( _cbe_mb_passwdcr_link( \'change\', array( \'label\' => _cbe_mb_gTxt( \'change_password\' ) ) ) ) ;\r\n    }\r\n}\r\n\r\n// -------------------------------------------------------------------\r\n// -- Callbacks and general utilities\r\n// -------------------------------------------------------------------\r\n\r\n// -- Gets and returns admin lang strings\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_gTxt( $text, $atts = array() )\r\n{\r\n    static $aTexts = array() ;\r\n    if( ! $aTexts )\r\n        $aTexts = load_lang_event( \'admin\' ) + _cbe_mb_lang() ;\r\n\r\n    if( ($sText = gTxt( $text )) === $text && isset( $aTexts[ $text ] ) )\r\n    {\r\n        $sText = strtr( $aTexts[ $text ], $atts ) ;\r\n    }\r\n\r\n    return( $sText ) ;\r\n}\r\n\r\n// -- Returns taggified message\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_message( $text, $tag, $class )\r\n{\r\n    return( doTag( _cbe_mb_gTxt( $text ), $tag, $class ) ) ;\r\n}\r\n\r\n// -- Generates link for resetting or changing password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_passwdcr_link( $type, $atts )\r\n{\r\n    extract( lAtts( array ( \'label\' => \'\' )\r\n                    + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    return( cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( $type==\'reset\' ? \'password_forgotten\'\r\n                                                                               : \'change_password\' )\r\n                                     , \'link\' => $type.\'=1\', \'target\' => \'_get\'\r\n                                     , \'wraptag\' => \'p\', \'class\' => $class ? $class : \'\' ) ) ) ;\r\n}\r\n\r\n// -- Wrap form fields in form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_form( $contents, $action, $step = null, $method = null )\r\n{\r\n    return( \'<form action=\"\'. $action .\'\" method=\"\'. ($method !== null ? $method : \'post\') .\'\">\'\r\n            .n. $contents\r\n            . ($step !== null ? n . sInput( $step ) : \'\')\r\n            .n. \'</form>\' ) ;\r\n}\r\n\r\n// -- Generates form for resetting or changing password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_passwdcr_form( $type, $step, $data=null )\r\n{\r\n    $out = array() ;\r\n    if( $data !== null )\r\n        extract( $data ) ;\r\n\r\n    if( $type == \'reset\' )\r\n        $out[] = cbe_frontauth_logname( array( \'class\' => \'edit\') ) ;\r\n    else\r\n    { // old pass, new pass, new pass\r\n        $label_new = _cbe_mb_gTxt( \'new_password\' ) ;\r\n        $out[] = cbe_frontauth_password( array( \'class\' => \'edit\')\r\n                                       , isset( $p_password ) ? $p_password : null ) ;\r\n        $out[] = _cbe_fa_identity( \'new_password\', array( \'label\' => $label_new, \'label_sfx\' => \'_1\' )\r\n                                 , isset( $p_password_1 ) ? $p_password_1 : null ) ;\r\n        $out[] = _cbe_fa_identity( \'new_password\', array( \'label\' => $label_new, \'label_sfx\' => \'_2\' )\r\n                                 , isset( $p_password_2 ) ? $p_password_2 : null ) ;\r\n    }\r\n\r\n    $out[] = cbe_frontauth_submit( array( \'label\' => _cbe_mb_gTxt( $type==\'reset\' ? \'password_reset\'\r\n                                                                                  : \'change_password\' )\r\n                                        , \'class\' => \'publish\' ) ) ;\r\n\r\n    return( _cbe_mb_form( join( n, $out ), page_url( array() ), $step ) ) ;\r\n}\r\n\r\n// -- Mails new password (changed or reset)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_get_author( $login_with, $name )\r\n{\r\n    include_once( txpath.\'/lib/txplib_admin.php\' ) ;\r\n\r\n    $usermail = safe_rows( \'name, email\', \'txp_users\'\r\n                         , ($login_with != \'email\' ? \"name\" : \"email\") .\"=\'$name\'\" ) ;\r\n\r\n    if( count( $usermail ) != 1 && $login_with == \'auto\' )\r\n        $usermail = safe_rows( \'name, email\', \'txp_users\', \"email=\'$name\'\" ) ;\r\n\r\n    if( count( $usermail ) == 1 )\r\n        return( array( \'name\'    => $usermail[0][ \'name\'  ]\r\n                     , \'email\'   => $usermail[0][ \'email\' ]\r\n                     , \'newpass\' => generate_password( 6 )\r\n                     ) ) ;\r\n\r\n    return( array( \'name\' => \'\', \'email\' => \'\', \'newpass\' => \'\' ) ) ;\r\n}\r\n\r\n// -- Mails new password (changed or reset)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_send_new_password( $pass, $email, $name, $return_url, $givenid )\r\n{\r\n    global $prefs, $txp_user, $sitename ;\r\n    $curr_user = $txp_user ;\r\n\r\n    $message = _cbe_mb_gTxt(\'greeting\').\' \'.$givenid.\',\'.\r\n        n.n._cbe_mb_gTxt(\'your_password_is\').\': \'.$pass.\r\n        n.n._cbe_mb_gTxt(\'log_in_at\').\': \'.$return_url ;\r\n\r\n    $txp_user = $name ; // txpMail() recherche RealName à partir de name\r\n    $out = txpMail($email, \"[$sitename] \"._cbe_mb_gTxt(\'your_new_password\'), $message);\r\n    $txp_user = $curr_user ;\r\n    return( $out ) ;\r\n}\r\n\r\n// -- Resets/changes password and mails it\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_set_author_pass( $new_pass, $name, $email, $return_url, $givenid=null )\r\n{\r\n    $hash = doSlash(txp_hash_password($new_pass));\r\n    $givenid = ($givenid == null ? $name : $givenid) ;\r\n\r\n    if( safe_update( \'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($name).\"\'\" ) )\r\n        $out = _cbe_mb_gTxt( _cbe_mb_send_new_password( $new_pass, $email, $name, $return_url, $givenid )\r\n                           ? \'password_sent_to\'\r\n                           : \'could_not_mail\' )\r\n             . \' \' . $givenid ;\r\n\r\n    else\r\n        $out = _cbe_mb_gTxt(\'could_not_update_author\').\' \'.htmlspecialchars( $givenid ) ;\r\n    return( $out ) ;\r\n}\r\n', '/*\r\n** cbe_members\r\n** Client-side textpattern plugin\r\n** Frontside user self management\r\n** Intended to be plugged into cbe_frontauth\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** a.1 - 21 Mar 2012 - Alpha : reset and change password\r\n** a.1.2-28 Mar 2012 - Fix: missing include for password generation\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Local lang values, possible customisation here\r\n **\r\n **************************************************/\r\n\r\nfunction _cbe_mb_lang()\r\n{\r\n    return( array( \'all_fields_required\'    => \"Merci de remplir tous les champs\"\r\n                 , \'new_password_error\'     => \"Les valeurs du nouveau mot de passe ne correspondent pas\"\r\n                 , \'old_password_error\'     => \"Ancien mot de passe invalide\"\r\n                 , \'passwords_must_be_diff\' => \"Ancien et nouveau mots de passe doivent être différents\"\r\n                 , \'password_too_short\'     => \"Nouveau mot de passe trop court (6 caractères minimum)\"\r\n                 , \'register\'               => \"Envoyer\"\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/* =========================== Stop editing =========================== */\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n// -- Self registration\r\n// -------------------------------------------------------------------\r\nfunction cbe_members( $atts, $thing = \'\' )\r\n{\r\n    /* ... (to be continued) ... */\r\n}\r\n\r\n// -------------------------------------------------------------------\r\n// -- Member management utilities\r\n// -------------------------------------------------------------------\r\n\r\n// -- Generates fields for register form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_fInput( $field, $type, $data = null )\r\n{\r\n    $out = array() ;\r\n    if( $data !== null )\r\n        extract( $data ) ;\r\n\r\n    if( $type !== \'submit\' )\r\n        $out[] = \'<label for=\"\'. $field .\'\">\'. _cbe_mb_gTxt( $field ) .\'</label>\' ;\r\n    $out[] = fInput( $type\r\n                   , $field\r\n                   , isset( $$field ) ? $$field : \'\'\r\n                   , \'\', \'\', \'\', \'\', \'\', $field ) ;\r\n    return( join( n, $out ) ) ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Functions to be plugged into cbe_frontauth\r\n **\r\n **************************************************/\r\n\r\nregister_callback( \'cbe_members_reset_password\' , \'cbefrontauth.reset_password\'  ) ;\r\nregister_callback( \'cbe_members_change_password\', \'cbefrontauth.change_password\' ) ;\r\n\r\n// -- Reset password\r\n// -- Available steps :\r\n// -- cbe_fa_before_login\r\n// -- cbe_fa_after_login\r\n// -------------------------------------------------------------------\r\nfunction cbe_members_reset_password( $event, $step, $data=null )\r\n{\r\n    global $prefs ;\r\n\r\n    if( $step == \'cbe_fa_before_login\' )\r\n    {\r\n        if( $data === null )\r\n        {\r\n            return( _cbe_mb_passwdcr_form( \'reset\', $step ) ) ;\r\n        }\r\n        else\r\n        {\r\n            extract( $data ) ;\r\n            //        ^ contains $p_userid, $login_with, $tag_error, $class_error\r\n\r\n            $return_url = PROTOCOL.$prefs[\'siteurl\']\r\n                        . preg_replace( \"/[?&]reset=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n            $out = array() ;\r\n            extract( _cbe_mb_get_author( $login_with, $p_userid ) ) ;\r\n            //        ^contains $newpass, $name, $email\r\n\r\n            $out[] = $newpass ? _cbe_mb_set_author_pass( $newpass, $name, $email, $return_url, $p_userid )\r\n                              : doTag( _cbe_mb_gTxt( \'unknown_author\', array( \'{name}\' => $p_userid ) )\r\n                                     , $tag_error, $class_error ) ;\r\n            $out[] = cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( \'revert\' )\r\n                                              , \'link\'  => $return_url\r\n                                              , \'target\' => \'_self\', \'wraptag\' => \'p\' ) ) ;\r\n            return( join( n, $out ) ) ;\r\n        }\r\n    }\r\n    elseif( $step == \'cbe_fa_after_login\' )\r\n    {\r\n        return( _cbe_mb_passwdcr_link( \'reset\', array( \'label\' => _cbe_mb_gTxt( \'password_forgotten\' ) ) ) ) ;\r\n    }\r\n}\r\n\r\n// -- Change password\r\n// -- Available steps :\r\n// -- cbe_fa_before_logout\r\n// -- cbe_fa_after_logout\r\n// -------------------------------------------------------------------\r\nfunction cbe_members_change_password( $event, $step, $data=null )\r\n{\r\n    global $prefs ;\r\n\r\n    if( $step == \'cbe_fa_before_logout\' )\r\n    {\r\n        if( $data === null )\r\n        {\r\n            return( _cbe_mb_passwdcr_form( \'change\', $step ) ) ;\r\n        }\r\n        else\r\n        {\r\n            extract( $data ) ;\r\n            //       ^ contains $p_userid, $p_password, $p_password_1, $p_password_2, $tag_error, $class_error\r\n\r\n            $controls = array( \'all_fields_required\'\r\n                                => \"empty( \\$p_password ) || empty( \\$p_password_1 ) || empty( \\$p_password_2 )\"\r\n                             , \'old_password_error\'\r\n                                => \"txp_validate( \\$p_userid, \\$p_password, false ) === false\"\r\n                             , \'new_password_error\'\r\n                                => \"\\$p_password_1 !== \\$p_password_2\"\r\n                             , \'passwords_must_be_diff\'\r\n                                => \"\\$p_password_1 === \\$p_password\"\r\n                             , \'password_too_short\'\r\n                                => \"strlen( \\$p_password_1 ) < 6\"\r\n                             ) ;\r\n\r\n            $iserror = false ;\r\n            $out = array() ;\r\n\r\n            foreach( $controls as $err => $cond )\r\n            {\r\n                if( eval( \"return( $cond ) ;\" ) )\r\n                {\r\n                    $iserror = true ;\r\n                    $out[] = _cbe_mb_message( $err, $tag_error, $class_error ) ;\r\n                    break ;\r\n                }\r\n            }\r\n\r\n            if( ! $iserror )\r\n            {\r\n                $return_url = PROTOCOL.$prefs[\'siteurl\']\r\n                            . preg_replace( \"/[?&]change=1/\", \"\", serverSet(\'REQUEST_URI\') ) ;\r\n\r\n                $out[] = _cbe_mb_set_author_pass( $p_password_1, $p_userid\r\n                                                , fetch( \'email\', \'txp_users\', \'name\', $p_userid )\r\n                                                , $return_url ) ;\r\n                $out[] = cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( \'revert\' )\r\n                                                  , \'link\'  => $return_url\r\n                                                  , \'target\' => \'_self\', \'wraptag\' => \'p\' ) ) ;\r\n            }\r\n\r\n            if( $iserror )\r\n                $out[] =  _cbe_mb_passwdcr_form( \'change\', $step, compact( array_keys( $data ) ) ) ;\r\n\r\n            return( join( n, $out ) ) ;\r\n        }\r\n    }\r\n    elseif( $step == \'cbe_fa_after_logout\' )\r\n    {\r\n        return( _cbe_mb_passwdcr_link( \'change\', array( \'label\' => _cbe_mb_gTxt( \'change_password\' ) ) ) ) ;\r\n    }\r\n}\r\n\r\n// -------------------------------------------------------------------\r\n// -- Callbacks and general utilities\r\n// -------------------------------------------------------------------\r\n\r\n// -- Gets and returns admin lang strings\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_gTxt( $text, $atts = array() )\r\n{\r\n    static $aTexts = array() ;\r\n    if( ! $aTexts )\r\n        $aTexts = load_lang_event( \'admin\' ) + _cbe_mb_lang() ;\r\n\r\n    if( ($sText = gTxt( $text )) === $text && isset( $aTexts[ $text ] ) )\r\n    {\r\n        $sText = strtr( $aTexts[ $text ], $atts ) ;\r\n    }\r\n\r\n    return( $sText ) ;\r\n}\r\n\r\n// -- Returns taggified message\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_message( $text, $tag, $class )\r\n{\r\n    return( doTag( _cbe_mb_gTxt( $text ), $tag, $class ) ) ;\r\n}\r\n\r\n// -- Generates link for resetting or changing password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_passwdcr_link( $type, $atts )\r\n{\r\n    extract( lAtts( array ( \'label\' => \'\' )\r\n                    + _cbe_fa_format()\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    return( cbe_frontauth_link( array( \'label\' => _cbe_mb_gTxt( $type==\'reset\' ? \'password_forgotten\'\r\n                                                                               : \'change_password\' )\r\n                                     , \'link\' => $type.\'=1\', \'target\' => \'_get\'\r\n                                     , \'wraptag\' => \'p\', \'class\' => $class ? $class : \'\' ) ) ) ;\r\n}\r\n\r\n// -- Wrap form fields in form\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_form( $contents, $action, $step = null, $method = null )\r\n{\r\n    return( \'<form action=\"\'. $action .\'\" method=\"\'. ($method !== null ? $method : \'post\') .\'\">\'\r\n            .n. $contents\r\n            . ($step !== null ? n . sInput( $step ) : \'\')\r\n            .n. \'</form>\' ) ;\r\n}\r\n\r\n// -- Generates form for resetting or changing password\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_passwdcr_form( $type, $step, $data=null )\r\n{\r\n    $out = array() ;\r\n    if( $data !== null )\r\n        extract( $data ) ;\r\n\r\n    if( $type == \'reset\' )\r\n        $out[] = cbe_frontauth_logname( array( \'class\' => \'edit\') ) ;\r\n    else\r\n    { // old pass, new pass, new pass\r\n        $label_new = _cbe_mb_gTxt( \'new_password\' ) ;\r\n        $out[] = cbe_frontauth_password( array( \'class\' => \'edit\')\r\n                                       , isset( $p_password ) ? $p_password : null ) ;\r\n        $out[] = _cbe_fa_identity( \'new_password\', array( \'label\' => $label_new, \'label_sfx\' => \'_1\' )\r\n                                 , isset( $p_password_1 ) ? $p_password_1 : null ) ;\r\n        $out[] = _cbe_fa_identity( \'new_password\', array( \'label\' => $label_new, \'label_sfx\' => \'_2\' )\r\n                                 , isset( $p_password_2 ) ? $p_password_2 : null ) ;\r\n    }\r\n\r\n    $out[] = cbe_frontauth_submit( array( \'label\' => _cbe_mb_gTxt( $type==\'reset\' ? \'password_reset\'\r\n                                                                                  : \'change_password\' )\r\n                                        , \'class\' => \'publish\' ) ) ;\r\n\r\n    return( _cbe_mb_form( join( n, $out ), page_url( array() ), $step ) ) ;\r\n}\r\n\r\n// -- Mails new password (changed or reset)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_get_author( $login_with, $name )\r\n{\r\n    include_once( txpath.\'/lib/txplib_admin.php\' ) ;\r\n\r\n    $usermail = safe_rows( \'name, email\', \'txp_users\'\r\n                         , ($login_with != \'email\' ? \"name\" : \"email\") .\"=\'$name\'\" ) ;\r\n\r\n    if( count( $usermail ) != 1 && $login_with == \'auto\' )\r\n        $usermail = safe_rows( \'name, email\', \'txp_users\', \"email=\'$name\'\" ) ;\r\n\r\n    if( count( $usermail ) == 1 )\r\n        return( array( \'name\'    => $usermail[0][ \'name\'  ]\r\n                     , \'email\'   => $usermail[0][ \'email\' ]\r\n                     , \'newpass\' => generate_password( 6 )\r\n                     ) ) ;\r\n\r\n    return( array( \'name\' => \'\', \'email\' => \'\', \'newpass\' => \'\' ) ) ;\r\n}\r\n\r\n// -- Mails new password (changed or reset)\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_send_new_password( $pass, $email, $name, $return_url, $givenid )\r\n{\r\n    global $prefs, $txp_user, $sitename ;\r\n    $curr_user = $txp_user ;\r\n\r\n    $message = _cbe_mb_gTxt(\'greeting\').\' \'.$givenid.\',\'.\r\n        n.n._cbe_mb_gTxt(\'your_password_is\').\': \'.$pass.\r\n        n.n._cbe_mb_gTxt(\'log_in_at\').\': \'.$return_url ;\r\n\r\n    $txp_user = $name ; // txpMail() recherche RealName à partir de name\r\n    $out = txpMail($email, \"[$sitename] \"._cbe_mb_gTxt(\'your_new_password\'), $message);\r\n    $txp_user = $curr_user ;\r\n    return( $out ) ;\r\n}\r\n\r\n// -- Resets/changes password and mails it\r\n// -------------------------------------------------------------------\r\nfunction _cbe_mb_set_author_pass( $new_pass, $name, $email, $return_url, $givenid=null )\r\n{\r\n    $hash = doSlash(txp_hash_password($new_pass));\r\n    $givenid = ($givenid == null ? $name : $givenid) ;\r\n\r\n    if( safe_update( \'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($name).\"\'\" ) )\r\n        $out = _cbe_mb_gTxt( _cbe_mb_send_new_password( $new_pass, $email, $name, $return_url, $givenid )\r\n                           ? \'password_sent_to\'\r\n                           : \'could_not_mail\' )\r\n             . \' \' . $givenid ;\r\n\r\n    else\r\n        $out = _cbe_mb_gTxt(\'could_not_update_author\').\' \'.htmlspecialchars( $givenid ) ;\r\n    return( $out ) ;\r\n}\r\n', '38e106e042d2f34102c638a5037ee703', '', '', 0, 5, 0),
('cbe_output_form', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.1', 'output_form with parameters', '<div id=\"cbe-plugin-help\">\n<h1>cbe_output_form</h1>\n\n<p>This plugin somehow responds to a question in a long time buried in the depths of the forum: <a href=\"http://forum.textpattern.com/viewtopic.php?id=17998\">Passing variables through \"txp:output_form\"</a>.</p>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#tags-list\">Tags list</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>enhanced <code>txp:output_form</code> with parameters</li>\n  <li>automatically clears parameters after use, unless you specifiy not to</li>\n  <li>supports <code>&lt;txp:yield /&gt;</code></li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n<p>Download from <a href=\"http://textpattern.org/plugins/1259/cbe_output_form\">textpattern resources</a> or the <a href=\"http://www.clairebrione.com/cbe_output_form\">plugin page</a>.</p>\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=38124\">forum thread</a> for announcements and support.</p>\n\n<h2 id=\"tags-list\">Tags list</h2>\n<p>Alphabetically:\n<br /><a href=\"#usage\">cbe_output_form</a>\n</p>\n\n<h3 id=\"usage\">&lt;txp:cbe_output_form /&gt;</h3>\n\n<p>Single tag:\n<pre><code>&lt;txp:cbe_output_form form=\"form_name\" [ clearparams=\"{<b>1</b>|0}\" param_1=\"first value\" param_2=\"second value\" ... param_n=\"nth value\" ] /&gt;</code></pre></p>\n\n<p>Container tag:\n<pre><code>&lt;txp:cbe_output_form form=\"form_name\" [ clearparams=\"{<b>1</b>|0}\" param_1=\"first value\" param_2=\"second value\" ... param_n=\"nth value\" ]&gt;\n    ... Contained statements ...\n&lt;/txp:cbe_output_form&gt;</code></pre></p>\n\n<p>Note: <code>param_1, param_2, param_n</code> are for the example only. Name your parameters (as many as you need) as you want.</p>\n\n<p class=\"accent\">What it does</p>\n<p>Works like <code>&lt;txp:output_form /&gt;</code>, plus sets variables <code>foo</code> and <code>bar</code>.\n<br />These values can be retrieved in <code>form_name</code> using <code>&lt;txp:variable name=\"param_x\" /&gt;</code>.\n<br />Finally, passed parameters are cleared, unless you specifiy <code>clearparams=\"0\"</code>.</p>\n\n<h2 id=\"changelog\">Changelog</h2>\n<ul>\n  <li>v 0.1 - 03 Jun 2012 - Initial release</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#cbe-plugin-help .readme {\n  color: red;\n  font-weight: bold;\n}\n#cbe-plugin-help .accent {\n  font-weight: bold;\n}\n#cbe-plugin-help .list th{\n	text-align:left;\n	color:#fff;\n	background:#555;\n	padding:6px 16px 6px 4px;\n	border-right:1px solid #999;\n	font-weight:normal;\n}\n#cbe-plugin-help .list th a{\n	color:#fff;\n	display:block;\n}\n#cbe-plugin-help .list th h2{\n	margin:0;\n	font-size:11px;\n}\n#cbe-plugin-help .list td{\n	padding:6px 4px;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n** cbe_output_form\r\n** Client-side textpattern plugin\r\n** Pass variables through output_form\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 03 Jun 2012 - Initial release\r\n*/\r\n\r\n/**************************************************\r\n **\r\n ** Utilities\r\n **\r\n **************************************************/\r\n\r\n// -- Mandatory attributes\r\n// -------------------------------------------------------------------\r\nfunction _cbe_of_mandatts()\r\n{\r\n    return( array( \'form\'        => \'\'\r\n                 , \'clearparams\' => \'1\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n// -- <txp:cbe_output_form form=\"form_name\" [clearparams=\"{1|0}\" param1=\"value 1\" ... paramn=\"valuen\"] />\r\n// -------------------------------------------------------------------\r\nfunction cbe_output_form( $atts, $thing = NULL )\r\n{\r\n    global $variable ;\r\n    extract( lAtts( _cbe_of_mandatts(), $atts, 0 ) ) ;\r\n\r\n    if( ! $form )\r\n    {\r\n        trigger_error( gTxt( \'form_not_specified\' ) ) ;\r\n        return ;\r\n    }\r\n\r\n    $aParams = array_diff( array_keys( $atts ), array_keys( _cbe_of_mandatts() ) ) ;\r\n    foreach( array_values( $aParams ) as $param )\r\n        $variable[ $param ] = $atts[ $param ];\r\n\r\n    $out = output_form( array( \'form\' => $form ), $thing ) ;\r\n\r\n    if( $clearparams === \'1\' )\r\n    {\r\n        foreach( array_values( $aParams ) as $param )\r\n            unset( $variable[ $param ] ) ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}', '/*\r\n** cbe_output_form\r\n** Client-side textpattern plugin\r\n** Pass variables through output_form\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 03 Jun 2012 - Initial release\r\n*/\r\n\r\n/**************************************************\r\n **\r\n ** Utilities\r\n **\r\n **************************************************/\r\n\r\n// -- Mandatory attributes\r\n// -------------------------------------------------------------------\r\nfunction _cbe_of_mandatts()\r\n{\r\n    return( array( \'form\'        => \'\'\r\n                 , \'clearparams\' => \'1\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n// -- <txp:cbe_output_form form=\"form_name\" [clearparams=\"{1|0}\" param1=\"value 1\" ... paramn=\"valuen\"] />\r\n// -------------------------------------------------------------------\r\nfunction cbe_output_form( $atts, $thing = NULL )\r\n{\r\n    global $variable ;\r\n    extract( lAtts( _cbe_of_mandatts(), $atts, 0 ) ) ;\r\n\r\n    if( ! $form )\r\n    {\r\n        trigger_error( gTxt( \'form_not_specified\' ) ) ;\r\n        return ;\r\n    }\r\n\r\n    $aParams = array_diff( array_keys( $atts ), array_keys( _cbe_of_mandatts() ) ) ;\r\n    foreach( array_values( $aParams ) as $param )\r\n        $variable[ $param ] = $atts[ $param ];\r\n\r\n    $out = output_form( array( \'form\' => $form ), $thing ) ;\r\n\r\n    if( $clearparams === \'1\' )\r\n    {\r\n        foreach( array_values( $aParams ) as $param )\r\n            unset( $variable[ $param ] ) ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}', 'fdac502732587c4c8d499a2d2b40fa02', '', '', 0, 5, 0),
('cbe_swap_body_excerpt', 0, 'Claire Brione', 'http://www.clairebrione.com', '1.0.1', 'Swaps body and excerpt fields in Write tab', '<div id=\"cbe-plugin-help\">\n\n<h1>cbe_swap_body_excerpt</h1>\n\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"paypal\" id=\"donate-form\">\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\">\n<input type=\"hidden\" name=\"hosted_button_id\" value=\"GEK5C58W9DDB4\">\n<p>The primary purpose of this <strong>admin plugin</strong> is to swap body and excerpt fields in the Write tab.</p>\n<p class=\"txp-buttons\">It is completely free of charge, though you may think it (or any other of my plugins) <a rel=\"nofollow\" onclick=\"$(\'#donate-form\').submit();\" href=\"javascript:void(0)\">deserves a donation</a>.</p>\n<img alt=\"\" border=\"0\" src=\"https://www.paypalobjects.com/fr_FR/i/scr/pixel.gif\" width=\"1\" height=\"1\">\n</form>\n\n<h2>Table of contents</h2>\n\n<ul>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#requirements\">Plugin requirements</a></li>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#how-to\">How to...</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>Displays the \'Excerpt\' field before the \'Body\'</li>\n  <li>Optionally adds some content before these two fields by replacing the original \'Body\' markup with your own (see <a href=\"#body-replace\">how to do that</a>)</li>\n</ul>\n\n<h2 id=\"requirements\">Plugin requirements</h2>\n\n<p>Developed and tested with Textpattern 4.5.4 and 4.5.5. Should work with every version from 4.2.0 (since pluggable_ui for articles).</p>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n\n<ul>\n  <li>Download from <a href=\"http://textpattern.org/plugins/1287/cbe_swap_body_excerpt\">textpattern resources</a> or the <a href=\"http://www.clairebrione.com/cbe_swap_body_excerpt\">plugin page</a>.</li>\n  <li>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</li>\n  <li>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=40558\">forum thread</a> for support.</li>\n</ul>\n\n\n<h2 id=\"how-to\">How to...</h2>\n\n<h3 id=\"body-replace\">Replace body markup</h3>\n\n<p>Create an <b>admin plugin</b> to hook to cbe_swapbe.replace_body event.<br />\nCopy/paste the following lines, replace <code><b>abc_your_function_name</b></code> with the proper name and fill in the blanks:</p>\n\n<pre><code>register_callback( \'<b>abc_your_function_name</b>\' , \'cbe_swapbe.body_replace\'  ) ;\n\n/**\n * <b>abc_your_function_name</b>\n * Uses the space left by the former \'Body\' field\n * after Body and Excerpt have been swapped with cbe_swap_body_excerpt\n *\n * @param   string $event event on which the function fires\n * @return  string        replacement html markup\n */\nfunction <b>abc_your_function_name</b>( $event )\n{\n    $body_replacement  = <b>__your__code__here__</b> ;\n    $body_replacement .= <b>__more__code__if__needed__</b> ;\n    return( $body_replacement ) ;\n}\n</code></pre>\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<ul>\n  <li>1.0   - 23 Jan 2014 - Initial release</li>\n  <li>1.0.1 - 24 Jan 2014 - Fixed: nothing displays if articles don\'t use excerpts</li>\n</ul>\n\n</div>', '/**\r\n * cbe_swap_body_excerpt - Swaps body and excerpt fields in Write tab\r\n *\r\n * 1.0   - 23 Jan 2014 - Initial release\r\n * 1.0.1 - 24 Jan 2014 - Fixed: nothing displays if articles don\'t use excerpts\r\n *\r\n * @author  Claire Brione\r\n * @link    http://www.clairebrione.com/\r\n * @version 1.0.1\r\n */\r\n\r\n/* =========================== Constants ============================ */\r\n// -- Primitives\r\ndefine( \'CBE_SBE_VERSION\', \'1.0.1\'                 ) ;   // Current version\r\ndefine( \'CBE_SBE_NAME\'   , \'cbe_swap_body_excerpt\' ) ;   // Plugin name\r\ndefine( \'CBE_SBE_EVENT\'  , \'cbe_swapbe\'            ) ;   // This event\'s name (12 chars max)\r\n// -- Declensions\r\ndefine( \'CBE_SBE_SPFX\', CBE_SBE_EVENT.\'_\' ) ;            // Internal short prefix\r\ndefine( \'CBE_SBE_LPFX\', CBE_SBE_NAME.\'_\'  ) ;            // Internal long prefix\r\n\r\nif( @txpinterface == \'admin\' ) {\r\n\r\n/* ========================== Hook the plugin ========================= */\r\n\r\n  global $articles_use_excerpts ;\r\n  register_callback( CBE_SBE_SPFX.\'lifecycle\', \'plugin_lifecycle.\'.CBE_SBE_NAME ) ;\r\n  if( $articles_use_excerpts ) {\r\n      register_callback( CBE_SBE_SPFX.\'do\'       , \'article_ui\', \'body\'    ) ;\r\n      register_callback( CBE_SBE_SPFX.\'do\'       , \'article_ui\', \'excerpt\' ) ;\r\n  }\r\n\r\n/* =================== Plugin\'s lifecycle related =================== */\r\n\r\n/**\r\n * _cbe_swapbe_prefs_setup - Admin-side: plugin prefs creation or update\r\n *\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_prefs_setup()\r\n  {\r\n      set_pref( CBE_SBE_LPFX.\'version\', CBE_SBE_VERSION, CBE_SBE_EVENT , PREF_HIDDEN, \'\' ) ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * _cbe_swapbe_init - Admin-side: plugin table creation, prefs insertion\r\n *\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_init()\r\n  {\r\n      _cbe_swapbe_prefs_setup() ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * _cbe_swapbe_reinit - Admin-side: plugin table update, prefs update\r\n *\r\n * @param   string $registered_version before plugin update\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_reinit( $registered_version )\r\n  {\r\n      _cbe_swapbe_prefs_setup() ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * cbe_swapbe_lifecycle_installed - Admin-side: fires when plugin is installed\r\n *\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle_installed()\r\n  {\r\n      $registered_version = get_pref( CBE_SBE_LPFX.\'version\', \'none\' ) ;\r\n\r\n      if( $registered_version === CBE_SBE_VERSION )\r\n          return ;\r\n      elseif( $registered_version === \'none\' )\r\n          _cbe_swapbe_init() ;\r\n      else\r\n          _cbe_swapbe_reinit( $registered_version ) ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * cbe_swapbe_lifecycle_deleted - Admin-side: fires when plugin is deleted\r\n *\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle_deleted()\r\n  {\r\n      safe_delete( \'txp_prefs\', \"event=\'\".CBE_SBE_EVENT.\"\'\" ) ;\r\n\r\n      return ;\r\n  }\r\n/**\r\n * cbe_swapbe_lifecycle - Admin-side: plugin lifecycle\r\n *\r\n * @param   string $event admin event\r\n * @param   string $step  admin step\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle( $event = \'\', $step = \'\' )\r\n  {\r\n      $func = CBE_SBE_SPFX.\"lifecycle_$step\" ;\r\n\r\n      return( is_callable( $func ) ? $func() : \'\' ) ;\r\n  }\r\n\r\n/* ===================== Tab \'Content\' ====================== */\r\n/**\r\n * cbe_swapbe_do - Admin-side: swaps body and excerpt fields in Write tab\r\n *\r\n * @param   string $pui_event pluggable_ui event\r\n * @param   string $pui_step  pluggable_ui step\r\n * @param   string $data      partial html markup\r\n * @param   array  $rs        record set\r\n * @return  string            modified partial html markup\r\n */\r\n  function cbe_swapbe_do( $pui_event, $pui_step, $data, $rs )\r\n  {\r\n      static $cbe_sbe_field ;\r\n\r\n      if( $pui_step === \'excerpt\' ) {\r\n\r\n          return( $data.$cbe_sbe_field ) ;\r\n\r\n      } elseif( $pui_step === \'body\' ) {\r\n\r\n          $cbe_sbe_field = $data ;\r\n          $body_replace  = \'\' ;\r\n          $body_replace .= callback_event( CBE_SBE_EVENT.\'.body_replace\' ) ;\r\n          return( $body_replace !== \'\' ? $body_replace : \' \' ) ;\r\n\r\n      }\r\n\r\n      return( \'\' ) ;\r\n  }\r\n\r\n} // @txpinterface == \'admin\'\r\n\r\nelseif( defined( \'txpinterface\' ) ) {\r\n\r\n  return ;\r\n\r\n} // @txpinterface == \'public|css\'\r\n\r\nelse {\r\n\r\n  return ;\r\n\r\n} // txpinterface undef', '/**\r\n * cbe_swap_body_excerpt - Swaps body and excerpt fields in Write tab\r\n *\r\n * 1.0   - 23 Jan 2014 - Initial release\r\n * 1.0.1 - 24 Jan 2014 - Fixed: nothing displays if articles don\'t use excerpts\r\n *\r\n * @author  Claire Brione\r\n * @link    http://www.clairebrione.com/\r\n * @version 1.0.1\r\n */\r\n\r\n/* =========================== Constants ============================ */\r\n// -- Primitives\r\ndefine( \'CBE_SBE_VERSION\', \'1.0.1\'                 ) ;   // Current version\r\ndefine( \'CBE_SBE_NAME\'   , \'cbe_swap_body_excerpt\' ) ;   // Plugin name\r\ndefine( \'CBE_SBE_EVENT\'  , \'cbe_swapbe\'            ) ;   // This event\'s name (12 chars max)\r\n// -- Declensions\r\ndefine( \'CBE_SBE_SPFX\', CBE_SBE_EVENT.\'_\' ) ;            // Internal short prefix\r\ndefine( \'CBE_SBE_LPFX\', CBE_SBE_NAME.\'_\'  ) ;            // Internal long prefix\r\n\r\nif( @txpinterface == \'admin\' ) {\r\n\r\n/* ========================== Hook the plugin ========================= */\r\n\r\n  global $articles_use_excerpts ;\r\n  register_callback( CBE_SBE_SPFX.\'lifecycle\', \'plugin_lifecycle.\'.CBE_SBE_NAME ) ;\r\n  if( $articles_use_excerpts ) {\r\n      register_callback( CBE_SBE_SPFX.\'do\'       , \'article_ui\', \'body\'    ) ;\r\n      register_callback( CBE_SBE_SPFX.\'do\'       , \'article_ui\', \'excerpt\' ) ;\r\n  }\r\n\r\n/* =================== Plugin\'s lifecycle related =================== */\r\n\r\n/**\r\n * _cbe_swapbe_prefs_setup - Admin-side: plugin prefs creation or update\r\n *\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_prefs_setup()\r\n  {\r\n      set_pref( CBE_SBE_LPFX.\'version\', CBE_SBE_VERSION, CBE_SBE_EVENT , PREF_HIDDEN, \'\' ) ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * _cbe_swapbe_init - Admin-side: plugin table creation, prefs insertion\r\n *\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_init()\r\n  {\r\n      _cbe_swapbe_prefs_setup() ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * _cbe_swapbe_reinit - Admin-side: plugin table update, prefs update\r\n *\r\n * @param   string $registered_version before plugin update\r\n * @return  void\r\n * @access private\r\n */\r\n  function _cbe_swapbe_reinit( $registered_version )\r\n  {\r\n      _cbe_swapbe_prefs_setup() ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * cbe_swapbe_lifecycle_installed - Admin-side: fires when plugin is installed\r\n *\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle_installed()\r\n  {\r\n      $registered_version = get_pref( CBE_SBE_LPFX.\'version\', \'none\' ) ;\r\n\r\n      if( $registered_version === CBE_SBE_VERSION )\r\n          return ;\r\n      elseif( $registered_version === \'none\' )\r\n          _cbe_swapbe_init() ;\r\n      else\r\n          _cbe_swapbe_reinit( $registered_version ) ;\r\n\r\n      return ;\r\n  }\r\n\r\n/**\r\n * cbe_swapbe_lifecycle_deleted - Admin-side: fires when plugin is deleted\r\n *\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle_deleted()\r\n  {\r\n      safe_delete( \'txp_prefs\', \"event=\'\".CBE_SBE_EVENT.\"\'\" ) ;\r\n\r\n      return ;\r\n  }\r\n/**\r\n * cbe_swapbe_lifecycle - Admin-side: plugin lifecycle\r\n *\r\n * @param   string $event admin event\r\n * @param   string $step  admin step\r\n * @return  void\r\n */\r\n  function cbe_swapbe_lifecycle( $event = \'\', $step = \'\' )\r\n  {\r\n      $func = CBE_SBE_SPFX.\"lifecycle_$step\" ;\r\n\r\n      return( is_callable( $func ) ? $func() : \'\' ) ;\r\n  }\r\n\r\n/* ===================== Tab \'Content\' ====================== */\r\n/**\r\n * cbe_swapbe_do - Admin-side: swaps body and excerpt fields in Write tab\r\n *\r\n * @param   string $pui_event pluggable_ui event\r\n * @param   string $pui_step  pluggable_ui step\r\n * @param   string $data      partial html markup\r\n * @param   array  $rs        record set\r\n * @return  string            modified partial html markup\r\n */\r\n  function cbe_swapbe_do( $pui_event, $pui_step, $data, $rs )\r\n  {\r\n      static $cbe_sbe_field ;\r\n\r\n      if( $pui_step === \'excerpt\' ) {\r\n\r\n          return( $data.$cbe_sbe_field ) ;\r\n\r\n      } elseif( $pui_step === \'body\' ) {\r\n\r\n          $cbe_sbe_field = $data ;\r\n          $body_replace  = \'\' ;\r\n          $body_replace .= callback_event( CBE_SBE_EVENT.\'.body_replace\' ) ;\r\n          return( $body_replace !== \'\' ? $body_replace : \' \' ) ;\r\n\r\n      }\r\n\r\n      return( \'\' ) ;\r\n  }\r\n\r\n} // @txpinterface == \'admin\'\r\n\r\nelseif( defined( \'txpinterface\' ) ) {\r\n\r\n  return ;\r\n\r\n} // @txpinterface == \'public|css\'\r\n\r\nelse {\r\n\r\n  return ;\r\n\r\n} // txpinterface undef', '4266883e4452972f7751d5d877a0e9e5', '', '', 3, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_if_prod_status', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.2', 'Outputs or does something for a given production status. Automatically sets noindex directive if site is not live.', '<div id=\"cbe-plugin-help\">\n<h1>cbe_if_prod_status</h1>\n\n<p>This plugin comes from a short story. I was given an URL as indices with the following comment : \"I didn\'t make it clickable because I don\'t want it to be indexed, it is still being tested\".\n<br />So, why not automatically prevent indexation if the site not yet live ?</p>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#tags-list\">Tags</a></li>\n  <li><a href=\"#examples\">Examples</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n<ul>\n  <li>Automatically adds directive for robots in <code>&lt;head&gt;</code> section to prevent indexing and links following on non-live sites</li>\n  <li>Take action depending on production status (live, debug or testing)</li>\n  <li>&lt;txp:else /&gt; support</li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n<p>Download from <a href=\"http://textpattern.org/plugins/1262/cbe_if_prod_status\">textpattern resources</a> or the <a href=\"http://www.clairebrione.com/cbe_if_prod_status\">plugin page</a>.</p>\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=38318\">forum thread</a> for support.</p>\n\n<h2 id=\"tags-list\">Tags</h2>\n\n<h3>&lt;cbe_if_prod_status&gt;</h3>\n\n<pre><code>&lt;txp:cbe_if_prod_status\n  [value=\"live|debug|testing\"]&gt;\n    ... do what must be done ...\n&lt;txp:else /&gt;\n    ... otherwise, do that ...\n&lt;/txp:cbe_if_prod_status&gt;</code></pre>\n\n<p>Default for <code>value</code> : \"debug, testing\".</p>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3>Track errors when in non-live mode</h3>\n<pre><code>&lt;txp:cbe_if_prod_status&gt;\n    &lt;p&gt;You are here&lt;/p&gt;\n&lt;/txp:cbe_if_prod_status&gt;</code></pre>\n\n<h3>Collect statistics in live mode only</h3>\n<pre><code>&lt;txp:cbe_if_prod_status value=\"live\"&gt;\n    &lt;script type=\"text/javascript\" src=\"analytics.js\"&gt;&lt;/script&gt;\n&lt;/txp:cbe_if_prod_status&gt;</code></pre>\n\n<h2 id=\"changelog\">Changelog</h2>\n<ul>\n  <li>16 Jul 12 - v0.1 - Initial release</li>\n  <li>28 Jan 13 - v0.1.1 - Fix: didn\'t output anything if site status == \'live\'</li>\n  <li> 22 Jul 13 - v0.2 - <a href=\"http://forum.textpattern.com/viewtopic.php?pid=274178#p274178\">Neatly rewritten by Jukka Svahn (Gocom)</a></li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#cbe-plugin-help .readme {\n  color: red;\n  font-weight: bold;\n}\n#cbe-plugin-help .accent {\n  font-weight: bold;\n}\n#cbe-plugin-help .list th{\n	text-align:left;\n	color:#fff;\n	background:#555;\n	padding:6px 16px 6px 4px;\n	border-right:1px solid #999;\n	font-weight:normal;\n}\n#cbe-plugin-help .list th a{\n	color:#fff;\n	display:block;\n}\n#cbe-plugin-help .list th h2{\n	margin:0;\n	font-size:11px;\n}\n#cbe-plugin-help .list td{\n	padding:6px 4px;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n** cbe_if_prod_status\r\n** Client-side textpattern plugin\r\n** Automatically sets noindex directive if site is not live\r\n** Outputs or does something for a given production status, with <txp:else /> support\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1   - 16 Jul 2012 - Initial release\r\n** 0.1.1 - 28 Jan 2013 - Fix: didn\'t output anything if site status == \'live\'\r\n** 0.2   - 22 Jul 2013 - Neatly rewritten by Jukka Svahn (Gocom) http://forum.textpattern.com/viewtopic.php?pid=274178#p274178\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Automatism\r\n **\r\n **************************************************/\r\n\r\n/**\r\n * Registers robots meta injector when site is in testing or debugging mode.\r\n */\r\nif (get_pref(\'production_status\') !== \'live\')\r\n{\r\n    register_callback( \'cbe_meta_prod_status\', \'textpattern_end\' );\r\n}\r\n\r\n/**\r\n * Replaces the current output buffer contents.\r\n */\r\nfunction cbe_meta_prod_status()\r\n{\r\n    if ($ob = ob_get_contents())\r\n    {\r\n        ob_clean();\r\n        echo str_replace(\'<head>\', \'<head>\'.n.\'<meta name=\"robots\" content=\"noindex, nofollow\" />\', $ob);\r\n    }\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n/**\r\n * Tests the production status.\r\n *\r\n * @param array  $atts  Attributes\r\n * @param string $thing Contained statement\r\n */\r\nfunction cbe_if_prod_status( $atts, $thing )\r\n{\r\n    extract(lAtts(array(\r\n         \'value\' => \'debug, testing\',\r\n    ), $atts));\r\n\r\n    return parse( EvalElse( $thing, in_list( get_pref( \'production_status\' ), $value ) ) ) ;\r\n}\r\n', '/*\r\n** cbe_if_prod_status\r\n** Client-side textpattern plugin\r\n** Automatically sets noindex directive if site is not live\r\n** Outputs or does something for a given production status, with <txp:else /> support\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1   - 16 Jul 2012 - Initial release\r\n** 0.1.1 - 28 Jan 2013 - Fix: didn\'t output anything if site status == \'live\'\r\n** 0.2   - 22 Jul 2013 - Neatly rewritten by Jukka Svahn (Gocom) http://forum.textpattern.com/viewtopic.php?pid=274178#p274178\r\n**\r\n ************************************************************************/\r\n\r\n/**************************************************\r\n **\r\n ** Automatism\r\n **\r\n **************************************************/\r\n\r\n/**\r\n * Registers robots meta injector when site is in testing or debugging mode.\r\n */\r\nif (get_pref(\'production_status\') !== \'live\')\r\n{\r\n    register_callback( \'cbe_meta_prod_status\', \'textpattern_end\' );\r\n}\r\n\r\n/**\r\n * Replaces the current output buffer contents.\r\n */\r\nfunction cbe_meta_prod_status()\r\n{\r\n    if ($ob = ob_get_contents())\r\n    {\r\n        ob_clean();\r\n        echo str_replace(\'<head>\', \'<head>\'.n.\'<meta name=\"robots\" content=\"noindex, nofollow\" />\', $ob);\r\n    }\r\n}\r\n\r\n/**************************************************\r\n **\r\n ** Available tags\r\n **\r\n **************************************************/\r\n\r\n/**\r\n * Tests the production status.\r\n *\r\n * @param array  $atts  Attributes\r\n * @param string $thing Contained statement\r\n */\r\nfunction cbe_if_prod_status( $atts, $thing )\r\n{\r\n    extract(lAtts(array(\r\n         \'value\' => \'debug, testing\',\r\n    ), $atts));\r\n\r\n    return parse( EvalElse( $thing, in_list( get_pref( \'production_status\' ), $value ) ) ) ;\r\n}\r\n', 'bd236c1e8c8822efa2204a5e0238c7c3', '', '', 0, 5, 0),
('cbe_keywords', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.2', 'A keywords usage: counts and lists, tagclouds, hashtags', '<div id=\"cbe-plugin-help\">\n<h1>cbe_keywords</h1>\n\n<p>Developed with a tagcloud in mind.</p>\n<p>Idea inspired by <a href=\"http://textpattern.org/plugins/584/tru_tags\">tru_tags</a> and <a href=\"http://textpattern.org/plugins/523/chh_keywords\">chh_keywords</a>, pieces of code grabbed almost everywhere, borrowed from many plugins developers, you know who you are ... thank you !</p>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#tags-and-attributes\">Tags and attributes</a></li>\n  <li><a href=\"#examples\">Examples</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>get a simple list of all keywords used (<a href=\"#ex-basic\">example</a>)</li>\n  <li>get a list with number of occurences for each keyword (<a href=\"#ex-topten\">example</a>)</li>\n  <li>build a tag cloud (<a href=\"#ex-tagcloud\">example</a>)</li>\n  <li>build a formatted list ready to pass as a parameter to a script (<a href=\"#ex-flash\">example</a>)</li>\n  <li>get articles that share same keywords with the current one (<a href=\"#ex-related\">example</a>)</li>\n  <li>turn article keywords into hashtags (<a href=\"#ex-hashtags\">example</a>)</li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n<p>Download from the <a href=\"http://www.clairebrione.com/cbe_keywords\">plugin page</a>.</p>\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36455\">forum thread</a> for support.</p>\n\n<h2 id=\"tags-and-attributes\">Tags and attributes</h2>\n\n<p>All tags share the common presentational attributes <code>wraptag</code>, <code>break</code>, <code>class</code>, <code>breakclass</code>. Default values are unset. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross-reference\">Attributes Cross-reference</a> for further information.</p>\n\n<ul>\n  <li><a href=\"#tag-all-keywords\">&lt;txp:cbe_all_keywords /&gt;</a></li>\n  <li><a href=\"#tag-keyword\">&lt;txp:cbe_keyword /&gt;</a></li>\n  <li><a href=\"#tag-keyword-count\">&lt;txp:cbe_keyword_count /&gt;</a></li>\n  <li><a href=\"#tag-keyword-ratio\">&lt;txp:cbe_keyword_ratio /&gt;</a></li>\n  <li><a href=\"#tag-keywords-related\">&lt;txp:cbe_keywords_related /&gt;</a></li>\n  <li><a href=\"#tag-keywords-hashtags\">&lt;txp:cbe_keywords_hashtags /&gt;</a></li>\n</ul>\n\n<h3 id=\"tag-all-keywords\">&lt;txp:cbe_all_keywords /&gt;</h3>\n\n<p>Get a list of all keywords.</p>\n<p>Can be used in a page or form template, either as a single or container tag.</p>\n\n<table class=\"list\">\n  <tr>\n    <th>Attribute</th> <th>Description and values (default underlined)</th> <th>Example</th>\n  </tr>\n  <tr>\n    <td><code>status</code></td>\n    <td>Search keywords in articles having the specified status\n    <br /><b><u>live</u></b>: selects live articles only\n    <br /><b>sticky</b>: selects sticky articles only\n    <br /><b>all</b>: selects live and sticky articles</td>\n    <td><code>status=\"all\"</code></td>\n  </tr>\n  <tr>\n    <td><code>time</code></td>\n    <td>Search keywords in articles posted in the specified timeframe\n    <br /><b><u>past</u></b>: selects past articles only\n    <br /><b>future</b>: selects future articles only\n    <br /><b>any</b>: selects past and future articles\n    <br />note: selecting expired article or not depends on admin preferences</td>\n    <td><code>time=\"any\"</code></td>\n  </tr>\n  <tr>\n    <td><code>limit</code></td>\n    <td>Number of keywords to process/display\n    <br /><b><u>unset</u></b>: all keywords\n    <br /><b>integer</b>: only this specified number</td>\n    <td><code>limit=\"10\"</code></td>\n  </tr>\n  <tr>\n    <td><code>sortby</code></td>\n    <td>How to sort resulting list\n    <br /><b><u>unset</u></b>: no sorting\n    <br /><b>alpha</b>: sort by alphabetical order\n    <br /><b>weight</b>: sort by frequency</td>\n    <td><code>sortby=\"weight\"</code></td>\n  </tr>\n  <tr>\n    <td><code>sortdir</code></td>\n    <td>If sorting the list, direction to use\n    <br /><b><u>asc</u></b>: ascending order\n    <br /><b>desc</b>: descending order</td>\n    <td><code>sortdir=\"desc\"</code></td>\n  </tr>\n  <tr>\n    <td><code>section</code></td>\n    <td>Restrict to the (list of) section(s) provided. Can be used in conjunction with the category attribute\n    <br /><b><u>unset</u></b>: all sections\n    <br /><b>string</b>: a section name\n    <br /><b>string</b>: a comma-separated list of section names</td>\n    <td><code>section=\"my-section\"</code></td>\n  </tr>\n  <tr>\n    <td><code>category</code></td>\n    <td>Restrict to the (list of) category(ies) provided. Can be used in conjunction with the section attribute\n    <br /><b><u>unset</u></b>: all categories\n    <br /><b>string</b>: a category name\n    <br /><b>string</b>: a comma-separated list of category names</td>\n    <td><code>category=\"my-cat\"</code></td>\n  </tr>\n  <tr>\n    <td><code>form</code></td>\n    <td>Form used to process each keyword\n    <br /><b><u>unset</u></b>: no output\n    <br /><b>string</b>: a form name</td>\n    <td><code>form=\"keyw_form\"</code></td>\n  </tr>\n  <tr>\n    <td><code>minfontsize</code></td>\n    <td>For a weighted list or a tagcloud: the minimum font size to use (see <a href=\"examples\">examples</a> below)\n    <br /><b><u>false</u></b>: no calculation needed\n    <br /><b>integer</b>: number representing the minimum character size</td>\n    <td><code>minfontsize=\"12\"</code></td>\n  </tr>\n  <tr>\n    <td><code>maxfontsize</code></td>\n    <td>For a weighted list or a tagcloud: the maximum font size to use (see <a href=\"examples\">examples</a> below)\n    <br />><b><u>false</u></b>: no calculation needed\n    <br /><b>integer</b>: number representing the maximum character size</td>\n    <td><code>maxfontsize=\"24\"</code></td>\n  </tr>\n  <tr>\n    <td><code>label</code></td>\n    <td>Label to prepend to the output\n    <br /><b><u>unset</u></b>: no label\n    <br />><b>string</b>: text for the label</td>\n    <td><code>label=\"Tags\"</code></td>\n  </tr>\n  <tr>\n    <td><code>labeltag</code></td>\n    <td>Tag wrapping the label\n    <br /><b><u>unset</u></b>: no tag\n    <br /><b>string</b>: a tag without brackets</td>\n    <td><code>labeltag=\"h2\"</code></td>\n  </tr>\n</table>\n\n<h3 id=\"tag-keyword\">&lt;txp:cbe_keyword /&gt;</h3>\n\n<p>Value of the current keyword.</p>\n<p>Used as a single tag.</p>\n<p>Attribute:\n<br /><code>escape</code>: convert characters to HTML entities - values:</p>\n<ul>\n  <li><b><u>unset</u></b>: use UTF8 character set</li>\n  <li><b>html</b>: use ISO-8859-1 character set only</li>\n  <li><b>htmlspecial</b>: convert only special characters (&amp;, \", \', &lt;, &gt;)</li>\n</ul>\n\n<h3 id=\"tag-keyword-count\">&lt;txp:cbe_keyword_count /&gt;</h3>\n\n<p>How many times the current keyword is being used.</p>\n<p>Attributes: no other than the common presentational attributes.</p>\n\n<h3 id=\"tag-keyword-ratio\">&lt;txp:cbe_keyword_ratio /&gt;</h3>\n\n<p>How many times the current keyword is being used amongst all the other keywords.</p>\n<p>Attributes: no other than the common presentational attributes.</p>\n\n<h3 id=\"tag-keywords-related\">&lt;txp:cbe_keywords_related /&gt;</h3>\n\n<p>Works as <code>&lt;txp:related_articles /&gt;</code>, but on a keywords basis.</p>\n\n<table class=\"list\">\n  <tr>\n    <th>Attribute</th> <th>Description and values (default underlined)</th> <th>Example</th>\n  </tr>\n  <tr>\n    <td><code>status</code></td>\n    <td>Restrict to articles having the specified status\n    <br /><b><u>live</u></b>: selects live articles only\n    <br /><b>sticky</b>: selects sticky articles only\n    <br /><b>all</b>: selects live and sticky articles</td>\n    <td><code>status=\"all\"</code></td>\n  </tr>\n  <tr>\n    <td><code>time</code></td>\n    <td>Restrict to articles posted in the specified timeframe\n    <br /><b><u>past</u></b>: selects past articles only\n    <br /><b>future</b>: selects future articles only\n    <br /><b>any</b>: selects past and future articles\n    <br /><b>newer</b>: selects only articles newer than the one displayed\n    <br /><b>older</b>: selects only articles older than the one displayed\n    <br />note: selecting expired article or not depends on admin preferences</td>\n    <td><code>time=\"any\"</code></td>\n  </tr>\n  <tr>\n    <td><code>limit</code></td>\n    <td>Number of articles to retrieve\n    <br /><b><u>default</u></b>: 10</td>\n    <td><code>limit=\"5\"</code></td>\n  </tr>\n  <tr>\n    <td><code>sort</code></td>\n    <td>How to sort resulting list\n    <br /><b><u>default</u></b>: Posted desc\n    <br />Each field in the \"textpattern\"−table can be used as a sort key</td>\n    <td><code>sort=\"Title asc\"</code></td>\n  </tr>\n  <tr>\n    <td><code>section</code></td>\n    <td>Restrict to the (list of) section(s) provided. Can be used in conjunction with the category attribute\n    <br /><b><u>unset</u></b>: all sections\n    <br /><b>string</b>: a section name\n    <br /><b>string</b>: a comma-separated list of section names</td>\n    <td><code>section=\"my-section\"</code></td>\n  </tr>\n  <tr>\n    <td><code>category</code></td>\n    <td>Restrict to the (list of) category(ies) provided. Can be used in conjunction with the section attribute\n    <br /><b><u>unset</u></b>: all categories\n    <br /><b>string</b>: a category name\n    <br /><b>string</b>: a comma-separated list of category names</td>\n    <td><code>category=\"my-cat\"</code></td>\n  </tr>\n  <tr>\n    <td><code>form</code></td>\n    <td>Form used to process each article\n    <br /><b><u>unset</u></b>: no output\n    <br /><b>string</b>: a form name</td>\n    <td><code>form=\"keywrel_form\"</code></td>\n  </tr>\n  <tr>\n    <td><code>label</code></td>\n    <td>Label to prepend to the output\n    <br /><b><u>unset</u></b>: no label\n    <br /><b>string</b>: text for the label</td>\n    <td><code>label=\"Related\"</code></td>\n  </tr>\n  <tr>\n    <td><code>labeltag</code></td>\n    <td>Tag wrapping the label\n    <br /><b><u>unset</u></b>: no tag\n    <br /><b>string</b>: a tag without brackets</td>\n    <td><code>labeltag=\"h2\"</code></td>\n  </tr>\n  <tr>\n    <td><code>no_widow</code></td>\n    <td>Whether to inhibit line breaks in titles before the last word\n    <br /><b><u>default</u></b>: according to Advanced Preferences\n    <br /><b>boolean</b>: <code>0</code> : authorize linebreak, <code>1</code> : inhibits linebreak</td>\n    <td><code>no_widow=\"1\"</code></td>\n  </tr>\n</table>\n\n<h3 id=\"tag-keywords-hashtags\">&lt;txp:cbe_keywords_hashtags /&gt;</h3>\n\n<p>keyword1, keyword2, keyword3 become #keyword1 #keyword2 #keyword3</p>\n<p>Attributes: the common presentational attributes, plus<br />\n<code>limit</code>: limits the output to the specified number, can be a negative value to return the <i>n last</i> keywords.</p>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3 id=\"ex-basic\">Basic usage</h3>\n<pre><code>&lt;txp:cbe_all_keywords break=\" \" /&gt;</code></pre>\n\n<p>outputs a single line containing all keywords.</p>\n\n<h3 id=\"ex-tagcloud\">Text tagcloud</h3>\n\n<pre><code>&lt;txp:cbe_all_keywords minfontsize=\"12\" maxfontsize=\"24\" sortby=\"alpha\" sortdir=\"asc\" label=\"Tagcloud\" labeltag=\"h2\" wraptag=\"div\" class=\"tagcloud\"&gt;\n  &lt;span style=\"font-size: &lt;txp:cbe_keyword_ratio /&gt;px;\"&gt;&lt;txp:cbe_keyword /&gt;&lt;/span&gt;\n&lt;/txp:cbe_all_keywords&gt;</code></pre>\n\n<p>Try with <code>minfontsize=\"100\" maxfontsize=\"200\"</code> and <code>style=\"font-size: &lt;txp:cbe_keyword_ratio /&gt;%;\"</code></p>\n\n<h3 id=\"ex-flash\">Flash tagcloud</h3>\n\n<p>This is how to construct the parameter to pass to <a href=\"http://wordpress.org/extend/plugins/wp-cumulus/\">WP-Cumulus</a>, using the internal textpattern search engine, and assuming you have the flash file available:</p>\n\n<ol>\n  <li>Create a form, name it for example param_cumulus and put the following code (in one single line):<br />\n<pre><code>&lt;txp:cbe_all_keywords minfontsize=\"8\" maxfontsize=\"22\"&gt;&lt;a href=\'&lt;txp:site_url /&gt;?q=&lt;txp:cbe_keyword escape=\"html\" /&gt;\' style=\'font-size:&lt;txp:cbe_keyword_ratio /&gt;px;\'&gt;&lt;txp:cbe_keyword /&gt;&lt;/a&gt;&lt;/txp:cbe_all_keywords&gt;</code></pre>\n  </li>\n  <li>In the script, pass the paramaters this way:\n<pre><code>&lt;script type=\"text/javascript\"&gt;\n...\n    so.addVariable(\"tagcloud\", \"&lt;tags&gt;&lt;txp:output_form form=\"param_cumulus\" /&gt;&lt;/tags&gt;\");\n...\n&lt;/script&gt;</code></pre>\n  </li>\n</ol>\n\n<h3 id=\"ex-topten\">Top 10 keywords</h3>\n\n<pre><code>&lt;txp:cbe_all_keywords limit=\"10\" sortby=\"weight\" sortdir=\"desc\" wraptag=\"table\" class=\"allkw\"&gt;\n&lt;tr&gt;\n  &lt;txp:cbe_keyword wraptag=\"td\" class=\"kw\" /&gt;\n  &lt;txp:cbe_keyword_count wraptag=\"td\" class=\"count\" /&gt;\n  &lt;txp:cbe_keyword_ratio wraptag=\"td\" class=\"ratio\" /&gt;\n&lt;/tr&gt;\n&lt;/txp:cbe_all_keywords&gt;</code></pre>\n\n<h3 id=\"ex-related\">Related articles</h3>\n\n<p>Titles of the (at most) five latest related articles :</p>\n\n<pre><code>&lt;txp:cbe_keywords_related limit=\"5\" break=\"br\" wraptag=\"div\"&gt;\n  &lt;txp:title /&gt;\n&lt;/txp:cbe_keywords_related&gt;</code></pre>\n\n<p>Links to the (at most) ten related articles older than the current one, and published far in the past :</p>\n\n<pre><code>&lt;txp:cbe_keywords_related time=\"older\" sort=\"Posted asc\" /&gt;</code></pre>\n\n<p>Links to the (at most) ten related articles that were immediately published after the current one :</p>\n\n<pre><code>&lt;txp:cbe_keywords_related time=\"newer\" sort=\"Posted asc\" /&gt;</code></pre>\n\n<h3 id=\"ex-hashtags\">Hashtags</h3>\n\n<pre><code>&lt;txp:cbe_keywords_hashtags wraptag=\"p\" break=\"span\" class=\"hashtags\" breakclass=\"tag\" /></code></pre>\n\n<p>will generate something like this :</p>\n\n<pre><code>&lt;p class=\"hashtags\"&gt;\n	&lt;span class=\"tag\"&gt;#textpattern&lt;/span&gt;\n	&lt;span class=\"tag\"&gt;#javascript&lt;/span&gt;\n	&lt;span class=\"tag\"&gt;#jquery&lt;/span&gt;\n&lt;/p&gt;</code></pre>\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<ul>\n  <li>04 Apr 13 - v0.2 - Added values <code>time=\'newer | older\'</code> (than displayed article) for <code>&lt;txp:cbe_keywords_related /&gt;</code>\n  <li>11 Jul 11 - v0.1 - Initial release\n</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n.list th{\n	text-align:left;\n	color:#fff;\n	background:#555;\n	padding:6px 16px 6px 4px;\n	border-right:1px solid #999;\n	font-weight:normal;\n}\n.list th a{\n	color:#fff;\n	display:block;\n}\n.list th h2{\n	margin:0;\n	font-size:11px;\n}\n.list td{\n	padding:6px 4px;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n**\r\n** cbe_keywords v0.2\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 11 Jul 2011 - Initial release\r\n** 0.2 - 04 Apr 2013 - <txp:cbe_keywords_related /> : new values (\'older\' and \'newer\') for \'time\' attribute\r\n**\r\n ************************************************************************/\r\n\r\n/*\r\n * preliminary functions\r\n */\r\nfunction cbe_allkw_args()\r\n{\r\n    return( array( \'status\'      => \'live\'\r\n                 , \'time\'        => \'past\'\r\n                 , \'limit\'       => \'\'\r\n                 , \'sortby\'      => \'\'\r\n                 , \'sortdir\'     => \'asc\'\r\n                 , \'section\'     => \'\'\r\n                 , \'category\'    => \'\'\r\n                 , \'form\'        => \'\'\r\n                 , \'minfontsize\' => \'false\'\r\n                 , \'maxfontsize\' => \'false\'\r\n                 , \'label\'       => \'\'\r\n                 , \'labeltag\'    => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_onekw_args()\r\n{\r\n    return( array( \'escape\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_kw_format()\r\n{\r\n    return( array( \'wraptag\'    => \'\'\r\n                 , \'break\'      => \'\'\r\n                 , \'class\'      => \'\'\r\n                 , \'breakclass\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_relkw_args()\r\n{\r\n    global $prefs ;\r\n    return( array( \'status\'   => \'live\'\r\n                 , \'time\'     => \'past\'\r\n                 , \'form\'     => \'\'\r\n                 , \'label\'    => \'\'\r\n                 , \'labeltag\' => \'\'\r\n                 , \'limit\'    => 10\r\n                 , \'category\' => \'\'\r\n                 , \'no_widow\' => @$prefs[\'title_no_widow\']\r\n                 , \'section\'  => \'\'\r\n                 , \'sort\'     => \'Posted desc\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_all_keywords_options()\r\n{\r\n    return( cbe_allkw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_keyword_options()\r\n{\r\n    return( cbe_onekw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_keywords_related_options()\r\n{\r\n    return( cbe_relkw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_sql_where( $category, $section, $status, $time )\r\n{\r\n    $category = join(\"\',\'\", doSlash(do_list($category)));\r\n    $category = !$category ? \"\" : \"AND (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\r\n    $section  = !$section  ? \"\" : \"AND Section IN (\'\".join( \"\',\'\", doSlash(do_list($section)) ).\"\')\" ;\r\n    $qstatus  = array( \'live\'   => \"AND Status=\'4\'\"\r\n                     , \'sticky\' => \"AND Status=\'5\'\"\r\n                     , \'all\'    => \"AND Status>=\'4\'\"\r\n                     ) ;\r\n    $qtime    = array( \'past\'   => \"AND Posted <= now()\"\r\n                     , \'future\' => \"AND Posted > now()\"\r\n                     , \'older\'  => \"AND Posted < \'\\$cposted\'\"\r\n                     , \'newer\'  => \"AND Posted > \'\\$cposted\'\"\r\n                     , \'any\'    => \"\"\r\n                     ) ;\r\n\r\n    global $publish_expired_articles;\r\n    $expires = $publish_expired_articles\r\n               ? \"\"\r\n               : \"AND (Expires > now() OR Expires = \".NULLDATETIME.\")\" ;\r\n\r\n    return( array( \'status\'   => $qstatus[$status]\r\n                 , \'time\'     => $qtime[$time]\r\n                 , \'expires\'  => $expires\r\n                 , \'section\'  => $section\r\n                 , \'category\' => $category\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/****************************************************\r\n * things begin here\r\n */\r\nfunction cbe_all_keywords( $atts, $thing = \'\' )\r\n{   /* Retrieve a list of all keywords used in all articles, parse statement if necessary */\r\n    $cbe_kw_globals = array( \'minfontsize\', \'maxfontsize\', \'kwminused\', \'kwmaxused\', \'distribution\', \'proportions\', \'name\' ) ;\r\n    foreach( $cbe_kw_globals as $cbe_kw_global )\r\n        global $$cbe_kw_global ;\r\n\r\n    extract( lAtts( cbe_all_keywords_options(), $atts ) ) ;\r\n    \r\n    $bEvaluate = ($time === \'older\' || $time === \'newer\') ;\r\n    extract( cbe_sql_where( $category, $section, $status, $time ) ) ;\r\n    if( $bEvaluate )\r\n    {\r\n        $cposted = \"now()\" ;\r\n        eval( \"\\$time = \\\"$time\\\";\" );\r\n    }\r\n\r\n    $rs = safe_rows( \"Keywords\"\r\n                   , \"textpattern\"\r\n                   , \"Keywords != \'\' $status $time $expires $section $category ORDER BY rand()\"\r\n                   ) ;\r\n\r\n    if( !count( $rs ) )\r\n        return ;\r\n\r\n    $allkw = array() ;\r\n    foreach( $rs as $kwlist )\r\n        $allkw = array_merge( $allkw, explode( \",\", $kwlist[ \'Keywords\' ] ) ) ;\r\n\r\n    $distribution = array_count_values( $allkw ) ;  // array( (str)\"value $allkw\" => (int)# appears\r\n    $kwmaxused    = max( $distribution ) ;          // (int) most used keywords appear # time\r\n    $kwminused    = min( $distribution ) ;          // (int) less used keywords appear # time\r\n\r\n    if( $sortby == \'alpha\' )\r\n        { if( $sortdir == \'asc\' ) ksort( $distribution ) ; else krsort( $distribution ) ; }\r\n    elseif( $sortby == \'weight\' )\r\n        { if( $sortdir == \'asc\' ) asort( $distribution ) ; else arsort( $distribution ) ; }\r\n \r\n    if( $limit != \'\' )\r\n    {\r\n        $allkw        = array_slice( $allkw       , 0, $limit, true ) ;\r\n        $distribution = array_slice( $distribution, 0, $limit, true ) ;\r\n    }\r\n\r\n    $proportions  = array_map( create_function( \'$a\'\r\n                                              , \'global $kwmaxused; return( $a / $kwmaxused );\'\r\n                                              )\r\n                             , $distribution\r\n                             ) ;                    // array( (str)\"value $allkw\" => (float)%appears ) ;\r\n\r\n    if( empty( $thing ) && empty( $form ) )\r\n    {\r\n        $out = array_keys( $distribution ) ;\r\n    }\r\n    else\r\n    {\r\n        foreach( $distribution as $name => $v )\r\n        {\r\n            $out[] =  $thing ? parse( $thing ) : parse_form( $form ) ;\r\n        }\r\n    }\r\n\r\n    foreach( $cbe_kw_globals as $cbe_kw_global )\r\n        unset( $$cbe_kw_global ) ;\r\n\r\n    return( doLabel( $label, $labeltag )\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword_count( $atts )\r\n{   /* How many times does the current keyword appear across the site ? */\r\n    global $distribution, $name ;\r\n \r\n    extract( lAtts( cbe_kw_format(), $atts ) ) ;\r\n    return( doWrap( array( $distribution[ $name ] ), $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword_ratio( $atts )\r\n{   /* In what proportion is the current keyword used across the site ? Sets fontsize if required */\r\n    global $name, $proportions, $distribution, $minfontsize, $maxfontsize, $kwminused, $kwmaxused ;\r\n    extract( lAtts( cbe_kw_format(), $atts ) ) ;\r\n\r\n    if( $minfontsize == \'false\' && $maxfontsize == \'false\' )\r\n        $out[] = number_format( $proportions[ $name ], 2, \'.\', \'\' ) ;\r\n    elseif( $minfontsize != \'false\' && $maxfontsize != \'false\' && $kwmaxused != $kwminused )\r\n        $out[] = number_format( $minfontsize + ( $distribution[ $name ] - 1 ) * ( ($maxfontsize - $minfontsize) / ( $kwmaxused - $kwminused) ), 2, \'.\', \'\' ) ;\r\n    else\r\n        $out[] = ($charsize = number_format( $maxfontsize * $proportions[ $name ], 2, \'.\', \'\' )) < $minfontsize\r\n                 ? $minfontsize\r\n                 : $charsize ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword( $atts )\r\n{  /* Returns the current keyword (litterally) */\r\n    global $name ;\r\n    extract( lAtts( cbe_keyword_options(), $atts ) ) ;\r\n\r\n    if( $escape )\r\n    {\r\n        if( $escape == \'html\'        ) $out[] = htmlentities( $name ) ;\r\n        if( $escape == \'htmlspecial\' ) $out[] = htmlspecialchars( $name ) ;\r\n    }\r\n    else\r\n        $out[] = htmlentities( $name, ENT_COMPAT, \'UTF-8\' ) ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keywords_related( $atts, $thing = \'\' )\r\n{   /* article_related based on keywords */\r\n    global $thisarticle, $prefs ;\r\n\r\n    assert_article() ;\r\n    extract( lAtts( cbe_keywords_related_options(), $atts ) ) ;\r\n\r\n    $bEvaluate = ($time === \'older\' || $time === \'newer\') ;\r\n    extract( cbe_sql_where( $category, $section, $status, $time ) ) ;\r\n\r\n    $id = \"ID != \" . intval( $thisarticle[\'thisid\'] ) ;\r\n    if( $bEvaluate )\r\n    {\r\n        $cposted = date( \'Y-m-d H:i:s\', $thisarticle[\'posted\'] ) ;\r\n        eval( \"\\$time = \\\"$time\\\";\" );\r\n    }\r\n\r\n    $keys = doSlash( do_list( $thisarticle[\'keywords\'] ) ) ;\r\n    foreach( $keys as $key )\r\n    {\r\n        $keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\r\n    }\r\n    $keywords = \" AND (\" . join( \' OR \', $keyparts ) . \")\" ;\r\n    \r\n    $sort = doSlash( $sort ) ;\r\n    $limit = \" limit 0,\" . intval( $limit ) ;\r\n\r\n    $rs = safe_rows_start( \"*, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\"\r\n                         , \"textpattern\"\r\n                         , \"$id $keywords $status $time $expires $section $category ORDER BY $sort $limit\"\r\n                         ) ;\r\n\r\n    if( !count( $rs ) )\r\n        return ;\r\n\r\n    $out = array() ;\r\n    $old_article = $thisarticle ;\r\n    \r\n    while ($a = nextRow($rs))\r\n    {\r\n        $a[\'Title\'] = ($no_widow) ? noWidow(escape_title($a[\'Title\'])) : escape_title($a[\'Title\']);\r\n        $a[\'uPosted\'] = $a[\'posted\'] ;\r\n\r\n        if (empty($form) && empty($thing))\r\n        {\r\n            $out[] = href($a[\'Title\'], permlinkurl($a));\r\n        }\r\n        else\r\n        {\r\n            populateArticleData($a);\r\n            $out[] = ($thing) ?  parse($thing) : parse_form($form);\r\n        }\r\n    }\r\n\r\n    $thisarticle = $old_article ;\r\n    if( !count( $out ) )\r\n        return ;\r\n\r\n    return( doLabel( $label, $labeltag )\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keywords_hashtags( $atts, $thing = \'\' )\r\n{   /* Twitter dedicated : turns keywords into hashtags */\r\n    global $thisarticle, $prefs ;\r\n\r\n    assert_article() ;\r\n    extract( lAtts( array(\'limit\' => \'\') + cbe_kw_format(), $atts ) ) ;\r\n\r\n    if( $thisarticle[\'keywords\']!== \'\' )\r\n    {\r\n        $keys = doSlash( do_list( $thisarticle[\'keywords\'] ) ) ;\r\n        if( $limit != \'\' )\r\n            $keys = array_slice( $keys, 0, $limit ) ;\r\n        foreach( $keys as $key )\r\n        {\r\n            $out[] = \'#\'.$key ;\r\n        }\r\n        return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n    }\r\n\r\n    return ;\r\n}', '/*\r\n**\r\n** cbe_keywords v0.2\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 11 Jul 2011 - Initial release\r\n** 0.2 - 04 Apr 2013 - <txp:cbe_keywords_related /> : new values (\'older\' and \'newer\') for \'time\' attribute\r\n**\r\n ************************************************************************/\r\n\r\n/*\r\n * preliminary functions\r\n */\r\nfunction cbe_allkw_args()\r\n{\r\n    return( array( \'status\'      => \'live\'\r\n                 , \'time\'        => \'past\'\r\n                 , \'limit\'       => \'\'\r\n                 , \'sortby\'      => \'\'\r\n                 , \'sortdir\'     => \'asc\'\r\n                 , \'section\'     => \'\'\r\n                 , \'category\'    => \'\'\r\n                 , \'form\'        => \'\'\r\n                 , \'minfontsize\' => \'false\'\r\n                 , \'maxfontsize\' => \'false\'\r\n                 , \'label\'       => \'\'\r\n                 , \'labeltag\'    => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_onekw_args()\r\n{\r\n    return( array( \'escape\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_kw_format()\r\n{\r\n    return( array( \'wraptag\'    => \'\'\r\n                 , \'break\'      => \'\'\r\n                 , \'class\'      => \'\'\r\n                 , \'breakclass\' => \'\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_relkw_args()\r\n{\r\n    global $prefs ;\r\n    return( array( \'status\'   => \'live\'\r\n                 , \'time\'     => \'past\'\r\n                 , \'form\'     => \'\'\r\n                 , \'label\'    => \'\'\r\n                 , \'labeltag\' => \'\'\r\n                 , \'limit\'    => 10\r\n                 , \'category\' => \'\'\r\n                 , \'no_widow\' => @$prefs[\'title_no_widow\']\r\n                 , \'section\'  => \'\'\r\n                 , \'sort\'     => \'Posted desc\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_all_keywords_options()\r\n{\r\n    return( cbe_allkw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_keyword_options()\r\n{\r\n    return( cbe_onekw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_keywords_related_options()\r\n{\r\n    return( cbe_relkw_args()+cbe_kw_format() ) ;\r\n}\r\n\r\nfunction cbe_sql_where( $category, $section, $status, $time )\r\n{\r\n    $category = join(\"\',\'\", doSlash(do_list($category)));\r\n    $category = !$category ? \"\" : \"AND (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\r\n    $section  = !$section  ? \"\" : \"AND Section IN (\'\".join( \"\',\'\", doSlash(do_list($section)) ).\"\')\" ;\r\n    $qstatus  = array( \'live\'   => \"AND Status=\'4\'\"\r\n                     , \'sticky\' => \"AND Status=\'5\'\"\r\n                     , \'all\'    => \"AND Status>=\'4\'\"\r\n                     ) ;\r\n    $qtime    = array( \'past\'   => \"AND Posted <= now()\"\r\n                     , \'future\' => \"AND Posted > now()\"\r\n                     , \'older\'  => \"AND Posted < \'\\$cposted\'\"\r\n                     , \'newer\'  => \"AND Posted > \'\\$cposted\'\"\r\n                     , \'any\'    => \"\"\r\n                     ) ;\r\n\r\n    global $publish_expired_articles;\r\n    $expires = $publish_expired_articles\r\n               ? \"\"\r\n               : \"AND (Expires > now() OR Expires = \".NULLDATETIME.\")\" ;\r\n\r\n    return( array( \'status\'   => $qstatus[$status]\r\n                 , \'time\'     => $qtime[$time]\r\n                 , \'expires\'  => $expires\r\n                 , \'section\'  => $section\r\n                 , \'category\' => $category\r\n                 )\r\n          ) ;\r\n}\r\n\r\n/****************************************************\r\n * things begin here\r\n */\r\nfunction cbe_all_keywords( $atts, $thing = \'\' )\r\n{   /* Retrieve a list of all keywords used in all articles, parse statement if necessary */\r\n    $cbe_kw_globals = array( \'minfontsize\', \'maxfontsize\', \'kwminused\', \'kwmaxused\', \'distribution\', \'proportions\', \'name\' ) ;\r\n    foreach( $cbe_kw_globals as $cbe_kw_global )\r\n        global $$cbe_kw_global ;\r\n\r\n    extract( lAtts( cbe_all_keywords_options(), $atts ) ) ;\r\n    \r\n    $bEvaluate = ($time === \'older\' || $time === \'newer\') ;\r\n    extract( cbe_sql_where( $category, $section, $status, $time ) ) ;\r\n    if( $bEvaluate )\r\n    {\r\n        $cposted = \"now()\" ;\r\n        eval( \"\\$time = \\\"$time\\\";\" );\r\n    }\r\n\r\n    $rs = safe_rows( \"Keywords\"\r\n                   , \"textpattern\"\r\n                   , \"Keywords != \'\' $status $time $expires $section $category ORDER BY rand()\"\r\n                   ) ;\r\n\r\n    if( !count( $rs ) )\r\n        return ;\r\n\r\n    $allkw = array() ;\r\n    foreach( $rs as $kwlist )\r\n        $allkw = array_merge( $allkw, explode( \",\", $kwlist[ \'Keywords\' ] ) ) ;\r\n\r\n    $distribution = array_count_values( $allkw ) ;  // array( (str)\"value $allkw\" => (int)# appears\r\n    $kwmaxused    = max( $distribution ) ;          // (int) most used keywords appear # time\r\n    $kwminused    = min( $distribution ) ;          // (int) less used keywords appear # time\r\n\r\n    if( $sortby == \'alpha\' )\r\n        { if( $sortdir == \'asc\' ) ksort( $distribution ) ; else krsort( $distribution ) ; }\r\n    elseif( $sortby == \'weight\' )\r\n        { if( $sortdir == \'asc\' ) asort( $distribution ) ; else arsort( $distribution ) ; }\r\n \r\n    if( $limit != \'\' )\r\n    {\r\n        $allkw        = array_slice( $allkw       , 0, $limit, true ) ;\r\n        $distribution = array_slice( $distribution, 0, $limit, true ) ;\r\n    }\r\n\r\n    $proportions  = array_map( create_function( \'$a\'\r\n                                              , \'global $kwmaxused; return( $a / $kwmaxused );\'\r\n                                              )\r\n                             , $distribution\r\n                             ) ;                    // array( (str)\"value $allkw\" => (float)%appears ) ;\r\n\r\n    if( empty( $thing ) && empty( $form ) )\r\n    {\r\n        $out = array_keys( $distribution ) ;\r\n    }\r\n    else\r\n    {\r\n        foreach( $distribution as $name => $v )\r\n        {\r\n            $out[] =  $thing ? parse( $thing ) : parse_form( $form ) ;\r\n        }\r\n    }\r\n\r\n    foreach( $cbe_kw_globals as $cbe_kw_global )\r\n        unset( $$cbe_kw_global ) ;\r\n\r\n    return( doLabel( $label, $labeltag )\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword_count( $atts )\r\n{   /* How many times does the current keyword appear across the site ? */\r\n    global $distribution, $name ;\r\n \r\n    extract( lAtts( cbe_kw_format(), $atts ) ) ;\r\n    return( doWrap( array( $distribution[ $name ] ), $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword_ratio( $atts )\r\n{   /* In what proportion is the current keyword used across the site ? Sets fontsize if required */\r\n    global $name, $proportions, $distribution, $minfontsize, $maxfontsize, $kwminused, $kwmaxused ;\r\n    extract( lAtts( cbe_kw_format(), $atts ) ) ;\r\n\r\n    if( $minfontsize == \'false\' && $maxfontsize == \'false\' )\r\n        $out[] = number_format( $proportions[ $name ], 2, \'.\', \'\' ) ;\r\n    elseif( $minfontsize != \'false\' && $maxfontsize != \'false\' && $kwmaxused != $kwminused )\r\n        $out[] = number_format( $minfontsize + ( $distribution[ $name ] - 1 ) * ( ($maxfontsize - $minfontsize) / ( $kwmaxused - $kwminused) ), 2, \'.\', \'\' ) ;\r\n    else\r\n        $out[] = ($charsize = number_format( $maxfontsize * $proportions[ $name ], 2, \'.\', \'\' )) < $minfontsize\r\n                 ? $minfontsize\r\n                 : $charsize ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keyword( $atts )\r\n{  /* Returns the current keyword (litterally) */\r\n    global $name ;\r\n    extract( lAtts( cbe_keyword_options(), $atts ) ) ;\r\n\r\n    if( $escape )\r\n    {\r\n        if( $escape == \'html\'        ) $out[] = htmlentities( $name ) ;\r\n        if( $escape == \'htmlspecial\' ) $out[] = htmlspecialchars( $name ) ;\r\n    }\r\n    else\r\n        $out[] = htmlentities( $name, ENT_COMPAT, \'UTF-8\' ) ;\r\n\r\n    return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keywords_related( $atts, $thing = \'\' )\r\n{   /* article_related based on keywords */\r\n    global $thisarticle, $prefs ;\r\n\r\n    assert_article() ;\r\n    extract( lAtts( cbe_keywords_related_options(), $atts ) ) ;\r\n\r\n    $bEvaluate = ($time === \'older\' || $time === \'newer\') ;\r\n    extract( cbe_sql_where( $category, $section, $status, $time ) ) ;\r\n\r\n    $id = \"ID != \" . intval( $thisarticle[\'thisid\'] ) ;\r\n    if( $bEvaluate )\r\n    {\r\n        $cposted = date( \'Y-m-d H:i:s\', $thisarticle[\'posted\'] ) ;\r\n        eval( \"\\$time = \\\"$time\\\";\" );\r\n    }\r\n\r\n    $keys = doSlash( do_list( $thisarticle[\'keywords\'] ) ) ;\r\n    foreach( $keys as $key )\r\n    {\r\n        $keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\r\n    }\r\n    $keywords = \" AND (\" . join( \' OR \', $keyparts ) . \")\" ;\r\n    \r\n    $sort = doSlash( $sort ) ;\r\n    $limit = \" limit 0,\" . intval( $limit ) ;\r\n\r\n    $rs = safe_rows_start( \"*, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\"\r\n                         , \"textpattern\"\r\n                         , \"$id $keywords $status $time $expires $section $category ORDER BY $sort $limit\"\r\n                         ) ;\r\n\r\n    if( !count( $rs ) )\r\n        return ;\r\n\r\n    $out = array() ;\r\n    $old_article = $thisarticle ;\r\n    \r\n    while ($a = nextRow($rs))\r\n    {\r\n        $a[\'Title\'] = ($no_widow) ? noWidow(escape_title($a[\'Title\'])) : escape_title($a[\'Title\']);\r\n        $a[\'uPosted\'] = $a[\'posted\'] ;\r\n\r\n        if (empty($form) && empty($thing))\r\n        {\r\n            $out[] = href($a[\'Title\'], permlinkurl($a));\r\n        }\r\n        else\r\n        {\r\n            populateArticleData($a);\r\n            $out[] = ($thing) ?  parse($thing) : parse_form($form);\r\n        }\r\n    }\r\n\r\n    $thisarticle = $old_article ;\r\n    if( !count( $out ) )\r\n        return ;\r\n\r\n    return( doLabel( $label, $labeltag )\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n\r\nfunction cbe_keywords_hashtags( $atts, $thing = \'\' )\r\n{   /* Twitter dedicated : turns keywords into hashtags */\r\n    global $thisarticle, $prefs ;\r\n\r\n    assert_article() ;\r\n    extract( lAtts( array(\'limit\' => \'\') + cbe_kw_format(), $atts ) ) ;\r\n\r\n    if( $thisarticle[\'keywords\']!== \'\' )\r\n    {\r\n        $keys = doSlash( do_list( $thisarticle[\'keywords\'] ) ) ;\r\n        if( $limit != \'\' )\r\n            $keys = array_slice( $keys, 0, $limit ) ;\r\n        foreach( $keys as $key )\r\n        {\r\n            $out[] = \'#\'.$key ;\r\n        }\r\n        return( doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n    }\r\n\r\n    return ;\r\n}', '42ae5aea9bd6b5795e371e64081f3da6', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_helpful', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.6', 'Visitors can rate articles (based on wlk_helpful)', '<div id=\"cbe-plugin-help\">\r\n<h1>cbe_helpful</h1>\r\n\r\n<h2>Table of contents</h2>\r\n<ul>\r\n  <li><a href=\"#credits\">Credits</a></li>\r\n  <li><a href=\"#required\">Prerequisites</a></li>\r\n  <li><a href=\"#features\">Features</a></li>\r\n  <li><a href=\"#dl-supp\">Download and support</a></li>\r\n  <li><a href=\"#inst-setup\">Installation and setup</a></li>\r\n  <li><a href=\"#adm-usage\">Admin side usage</a></li>\r\n  <li><a href=\"#front-usage\">Client side usage</a></li>\r\n  <li><a href=\"#examples\">Examples</a></li>\r\n  <li><a href=\"#changelog\">Changelog</a></li>\r\n</ul>\r\n\r\n<h2 id=\"credits\">Credits</h2>\r\n<ul>\r\n  <li><a href=\"http://walkerhamilton.com/\"><b>Walker Hamilton</b></a>, for wlk_helpful on which this plugin is based</li>\r\n  <li><a href=\"http://rahforum.biz/\"><b>Jukka Svahn</b></a>, for rah_external_output (v1.0 minimum) on which the plugin heavily relies on</li>\r\n  <li><a href=\"http://txp-fr.net/\"><b>Pat64</b></a>, who took time to test, feedback, test, feedback, test ... feedback ... and so on</li>\r\n</ul>\r\n\r\n<h2 id=\"required\">Prerequisites</h2>\r\n<p>This plugin requires</p>\r\n<ul>\r\n  <li>the <a href=\"http://jquery.com\">jQuery library</a></li>\r\n  <li>the plugin <a href=\"http://rahforum.biz/plugins/rah_external_output\">rah_external_output v1.0 minimum</a></li>\r\n</ul>\r\n\r\n<h2 id=\"features\">Features</h2>\r\n<ul>\r\n  <li>just install and activate, no need to worry about additional scripts</li>\r\n  <li>automatically retrieves data from wlk_helpful if installed and active</li>\r\n  <li>set time threshold between two votes having the same IP</li>\r\n  <li>define your own presentation</li>\r\n</ul>\r\n\r\n<h2 id=\"#dl-supp\">Download and support</h2>\r\n<p>Download from the <a href=\"http://www.clairebrione.com/cbe_helpful\">plugin page</a>.</p>\r\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36435\">forum thread</a> for support.</p>\r\n\r\n<h2 id=\"inst-setup\">Installation and setup</h2>\r\n<p><span style=\"color: red; font-weight: bold;\">rah_external_output v1.0 minimum must be installed and active</span> otherwise cbe_helpful won\'t be activated and you will receive a message stating that you have to install/activate rah_external_output.</p>\r\n<p>Copy/paste in the Admin > Plugins tab to install.</p>\r\n<p>Setup is automatic at activation.</p>\r\n\r\n<h2 id=\"adm-usage\">Admin side usage</h2>\r\nOptions are available from the plugins pane, or go to Extensions > Article Ratings.\r\n\r\n<h2 id=\"front-usage\">Client side usage</h2>\r\n<p style=\"color: red; font-weight: bold;\">The jQuery library must be loaded, anywhere in the page.</p>\r\n\r\n<ul>\r\n  <li><a href=\"#tag-widget\">&lt;txp:cbe_helpful /&gt;</a></li>\r\n  <li><a href=\"#tag-label\">&lt;txp:cbe_helpful_label /&gt;</a></li>\r\n  <li><a href=\"#tag-score\">&lt;txp:cbe_helpful_score /&gt;</a></li>\r\n  <li><a href=\"#tag-yesno\">&lt;txp:cbe_helpful_yes /&gt;</a></li>\r\n  <li><a href=\"#tag-yesno\">&lt;txp:cbe_helpful_no /&gt;</a></li>\r\n  <li><a href=\"#tag-yesno\">&lt;txp:cbe_helpful_total_votes /&gt;</a></li>\r\n  <li><a href=\"#tag-list\">&lt;txp:cbe_helpful_list /&gt;</a></li>\r\n</ul>\r\n\r\n<h3 id=\"tag-widget\">Vote widget: &lt;txp:cbe_helpful /&gt;</h3>\r\n<p>Just put it in an <b>individual article</b> where you want it to appear. Can be used either as a single tag or as a pair.</p>\r\n\r\n<table class=\"list\">\r\n  <tr>\r\n    <th>Attribute</th> <th>Description and values</th> <th>Example</th>\r\n  </tr>\r\n  <tr>\r\n    <td><code>label</code></td>\r\n    <td>Label to prepend to the output.<br />Default : as defined in admin side</td>\r\n    <td><code>label=\"Rate it !\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>labeltag</code></td>\r\n    <td>Tag wrapping the label without brackets.<br />Default: <code>h2</code></td>\r\n    <td><code>labeltag=\"h3\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>labelclass</code></td>\r\n    <td>(X)HTML class attribute to apply to <code>labeltag</code>.<br />Default: unset</td>\r\n    <td><code>labelclass=\"widget-title\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>wraptag</code></td>\r\n    <td>Tag wrapping the widget without brackets. Effective only if used with the <code>form</code> attribute or as a pair.<br />Default: <code>div</code></td>\r\n    <td><code>wraptag=\"ul\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>class</code></td>\r\n    <td>(X)HTML class attribute to apply to <code>wraptag</code><br />Default: unset</td>\r\n    <td><code>class=\"widget\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>showonly</code></td>\r\n    <td>Whether to authorize voting or not. Use it, for example, to just display counts in an article list context.<br />Values: 0, 1<br />Default: <code>0</code></td>\r\n    <td><code>showonly=\"1\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>form</code></td>\r\n    <td>Form used to display the widget.<br />Default: unset</td>\r\n    <td><code>form=\"ballot\"</code></td>\r\n  </tr>\r\n</table>\r\n\r\n<p>Used as a single tag, outputs the minimal markup needed as an unordered list.</p>\r\n<p>Used as a pair or with the <code>form</code> attribute, lets you customize the way it is displayed with the following tags :</p>\r\n\r\n<h3 id=\"tag-label\">&lt;txp:cbe_helpful_label /&gt;</h3>\r\n<p>Displays the widget label as defined in admin side. Can also be used as a standalone tag (i.e. outside <code>&lt;txp:cbe_helpful&gt;</code>).</p>\r\n\r\n<table class=\"list\">\r\n  <tr>\r\n    <th>Attribute</th> <th>Description and values</th> <th>Example</th>\r\n  </tr>\r\n  <tr>\r\n    <td><code>wraptag</code></td>\r\n    <td>Tag wrapping the widget without brackets.<br />Default: unset</td>\r\n    <td><code>wraptag=\"h2\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>class</code></td>\r\n    <td>Class for the label.<br />Default: unset</td>\r\n    <td><code>class=\"widget\"</code></td>\r\n  </tr>\r\n</table>\r\n\r\n<h3 id=\"tag-score\">&lt;txp:cbe_helpful_score /&gt;</h3>\r\n<p>Displays the score formatted as specified in admin side : absolute value, ratio or percentage of positive votes</p>\r\n<p>Can be used as a standalone tag or enclosed in <code>cbe_helpful</code> or <code>cbe_helpful_list</code>.</p>\r\n\r\n<table class=\"list\">\r\n  <tr>\r\n    <th>Attribute</th> <th>Description and values</th> <th>Example</th>\r\n  </tr>\r\n  <tr>\r\n    <td><code>wraptag</code></td>\r\n    <td>Tag wrapping the score without brackets, can not be blank.<br />Default: span</td>\r\n    <td><code>wraptag=\"p\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>class</code></td>\r\n    <td>Class for styling.<br />Default: unset</td>\r\n    <td><code>class=\"score\"</code></td>\r\n  </tr>\r\n</table>\r\n\r\n<h3 id=\"tag-yesno\">&lt;txp:cbe_helpful_yes /&gt;, &lt;txp:cbe_helpful_no /&gt;, &lt;txp:cbe_helpful_total_votes /&gt;</h3>\r\n<p>Displays number of positive [resp. negative, total] votes, for &lt;txp:cbe_helpful_yes /&gt; and &lt;txp:cbe_helpful_no /&gt; only a link to click if the vote if possible.</p>\r\n<p>Must be enclosed in <code>cbe_helpful</code> or <code>cbe_helpful_list</code>.</p>\r\n<p>In a list based on time (see <a href=\"#tag-list\">&lt;txp:cbe_helpful_list /&gt;</a>), the displayed values are for the considered period only.</p>\r\n\r\n<table class=\"list\">\r\n  <tr>\r\n    <th>Attribute</th> <th>Description and values</th> <th>Example</th>\r\n  </tr>\r\n  <tr>\r\n    <td><code>wraptag</code></td>\r\n    <td>Tag wrapping the number without brackets, can not be blank.<br />Default: span</td>\r\n    <td><code>wraptag=\"p\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>class</code></td>\r\n    <td>Class used to style the number.<br />Default: unset</td>\r\n    <td><code>class=\"voices\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>voteclass</code></td>\r\n    <td>Class used to style the link.<br />Default: unset</td>\r\n    <td><code>class=\"vote\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>showonly</code></td>\r\n    <td><span class=\"highlight\">txp:cbe_helpful_yes and txp:cbe_helpful_no only !</span><br />Like in <code>txp:cbe_helpful</code>: whether to authorize voting or not. Use it, for example, to just display counts in an article list context.<br />Default: <code>0</code></td>\r\n    <td>See below</td>\r\n  </tr>\r\n    <td><code>labelshowonly</code></td>\r\n    <td><span class=\"highlight\">txp:cbe_helpful_yes and txp:cbe_helpful_no only.</span><br />If set AND no voting authorized (<code>showonly=\"1\"</code>): label to display instead of the one defined in preferences.<br /><span class=\"highlight\">txp:cbe_helpful_total_votes only.</span><br />If set: label to display instead of the one defined in preferences.<br />Default: unset</td>\r\n    <td><code>&lt;txp:cbe_helpful_yes showonly=\"1\" labelshowonly=\"people found this helpfulful\" /&gt;</code></td>\r\n  </tr>\r\n</table>\r\n\r\n<h3 id=\"tag-list\">Tops and flops: &lt;txp:cbe_helpful_list /&gt;</h3>\r\n<p>Can be used either as a single tag or as a pair.</p>\r\n<p>Displays an ordered list of article titles selected and sorted as follows (default is the titles of the 5 best rated articles):</p>\r\n\r\n<table class=\"list\">\r\n  <tr>\r\n    <th>Attribute</th> <th>Description and values</th> <th>Example</th>\r\n  </tr>\r\n  <tr>\r\n    <td><code>label</code></td>\r\n    <td>Label to prepend to the output.<br />Default : unset</td>\r\n    <td><code>label=\"Ratings\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>labeltag</code></td>\r\n    <td>Tag wrapping the label without brackets.<br />Default: <code>h2</code></td>\r\n    <td><code>labeltag=\"h3\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>labelclass</code></td>\r\n    <td>(X)HTML class attribute to apply to <code>labeltag</code>.<br />Default: unset</td>\r\n    <td><code>labelclass=\"summary-title\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>wraptag</code></td>\r\n    <td>HTML tag wrapping the resulting list.<br />Default: <code>ol</code></td>\r\n    <td><code>wraptag=\"p\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>break</code></td>\r\n    <td>HTML tag separating elements of the output.<br />Default: <code>li</code></td>\r\n    <td><code>break=\"br\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>class</code></td>\r\n    <td>Class for the list.<br />Default: cbe_helpful_list</td>\r\n    <td><code>class=\"parade\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>breakclass</code></td>\r\n    <td>Class for the HTML break tag.<br />Default: unset</td>\r\n    <td><code>breakclass=\"element\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>form</code></td>\r\n    <td>Form used to display some article details (standard article tags, or cbe_helpful tags).<br />Default: unset</td>\r\n    <td><code>form=\"prize_list\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>limit</code></td>\r\n    <td>Number of items to display.<br />Default: 5</td>\r\n    <td><code>limit=\"10\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>which</code></td>\r\n    <td>Whether to display the best ratings (top) or the worst (anything else).<br />Default: top</td>\r\n    <td><code>which=\"flop\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>order</code></td>\r\n    <td>How to sort the list, ascending or descending order.<br />Default: asc</td>\r\n    <td><code>order=\"desc\"</code></td>\r\n  </tr>\r\n  <tr>\r\n    <td><code>period</code></td>\r\n    <td>Liste articles that have been rated in a given time interval.<br />Possible values: today (or this day), yesterday (or last day), this week, last week, this month, last month, this year, last year<br />Default: unset (all time)</td>\r\n    <td><code>period=\"this month\"</code></td>\r\n  </tr>\r\n</table>\r\n\r\n<h2 id=\"examples\">Examples</h2>\r\n\r\n<h3>Simple widget</h3>\r\n<pre><code>&lt;txp:cbe_helpful /&gt;</code></pre>\r\n\r\n<h3>More elaborate widget</h3>\r\n<pre><code>&lt;txp:cbe_helpful labeltag=\"h2\">\r\n  &lt;p&gt;&lt;txp:cbe_helpful_score /&gt; people found this article useful. Do you ?&lt;/p&gt;\r\n  &lt;p&gt;\r\n  &lt;txp:cbe_helpful_yes class=\"green\" /&gt;\r\n  &lt;txp:cbe_helpful_no class=\"red\" /&gt;\r\n  &lt;/p&gt;\r\n&lt;/txp:cbe_helpful&gt;</code></pre>\r\n\r\n<h3>Detailed Top 10</h3>\r\n<pre><code>&lt;txp:cbe_helpful_list label=\"Top 10\" labeltag=\"h2\" limit=\"10\"&gt;\r\n  &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\r\n  &lt;txp:excerpt /&gt;\r\n&lt;/txp:cbe_helpful_list&gt;</code></pre>\r\n<p>or</p>\r\n<pre><code>&lt;txp:cbe_helpful_list limit=\"10\" form=\"topten\" /&gt;</code></pre>\r\n<p>with the accurate \"topten\" form.</p>\r\n\r\n<h3>Flop5 beginning with the worst</h3>\r\n<pre><code>&lt;txp:cbe_helpful_list which=\"flop\" order=\"asc\" /&gt;</code></pre>\r\n\r\n<h3>Display only count in article list context</h3>\r\n<p>In your article form:</p>\r\n<pre><code>&lt;txp:if_article_list&gt;\r\n  &lt;txp:cbe_helpful showonly=\"1\"&gt;\r\n    &lt;txp:cbe_helpful_yes labelshowonly=\"people find this useful\" /&gt;\r\n    &lt;txp:cbe_helpful_no labelshowonly=\"people find this useless\" /&gt;\r\n  &lt;/txp:cbe_helpful&gt;\r\n&lt;txp:else /&gt;\r\n  &lt;txp:cbe_helpful&gt;\r\n    &lt;txp:cbe_helpful_yes /&gt;\r\n    &lt;txp:cbe_helpful_no /&gt;\r\n  &lt;/txp:cbe_helpful&gt;\r\n&lt;/txp:if_article_list&gt;</code></pre>\r\n\r\n<h2 id=\"changelog\">Changelog</h2>\r\n<ul>\r\n  <li>30 Apr 13 - v0.6\r\n  <ul>\r\n    <li><a href=\"http://forum.textpattern.com/viewtopic.php?pid=266842#p266842\">display only (not clickable) the separate yes and no numbers in an article-list context</a> - thx <a href=\"http://forum.textpattern.com/profile.php?id=9986\">douglgm</a>. Added attributes:<br />showonly (cbe_helpful, cbe_helpful_yes, cbe_helpful_no)<br />labelshowonly (cbe_helpful_yes, cbe_helpful_no, cbe_helpful_score, cbe_helpful_total_votes)</li>\r\n    <li>Added too: labelclass (cbe_helpful, cbe_helpful_list)</li>\r\n    <li>Fix attempt: <a href=\"http://forum.textpattern.com/viewtopic.php?pid=267963#p267963\">markup as spotted here</a> - thx <a href=\"http://forum.textpattern.com/profile.php?id=14664\">Susanne</a></li>\r\n  </ul>\r\n  </li>\r\n  <li>18 Jul 12 -v0.5.1- Fixed: <a href=\"http://forum.textpattern.com/viewtopic.php?pid=263308#p263308\">path for /?rah_external_output=cbe_helpful_{js|ajax}</a> - thx <a href=\"http://forum.textpattern.com/profile.php?id=9986\">douglgm</a></li>\r\n  <li>16 Jul 12 - v0.5 - Now supports rah_external_output v1.0 new db structure. Previous structure is no longer supported. Plugin not activated if an error occurs on rah_external_output controls. Checks if all tables are present and restore them if not.</li>\r\n  <li>30 May 12 - v0.4 - <a href=\"http://forum.textpattern.com/viewtopic.php?pid=260946#p260946\">Total votes</a> - thx <a href=\"http://forum.textpattern.com/profile.php?id=6813\">milosevic</a>, <a href=\"http://forum.textpattern.com/viewtopic.php?pid=257887#p257887\">list based on time</a> - thx <a href=\"http://forum.textpattern.com/profile.php?id=5751\">tye</a>, fixed: miscalculation in cbe_helpful_list when sorting on score</li>\r\n  <li>04 Jan 12 - v0.3 - fixed: <a href=\"http://forum.textpattern.com/viewtopic.php?pid=256318#p256318\">unwanted markup if just displaying score</a>, thx milosevic; fixed: cbe_helpful_label not working if standalone; fixed: avoid multiple calls to _cbe_helpful_foot when displaying a list; JS code slightly revisited</li>\r\n  <li>19 Jul 11 -v0.2.1- fixed: non-valid html output for article id and unit - thx <a href=\"http://forum.textpattern.com/profile.php?id=7660\">Pat64</a></li>\r\n  <li>13 Jul 11 - v0.2 - cbe_helpful_list can now display cbe_helpful_score, cbe_helpful_yes, cbe_helpful_no; fixed: bug in list sorting when using ratio or percent display; repopulate table prefs with defaults if damaged or destroyed</li>\r\n  <li>12 Jul 11 -v0.1.1- Added control on value returned by getRows (cbe_helpful_list)\r\n  <li>08 Jul 11 - v0.1 - Initial release</li>\r\n</ul>\r\n\r\n</div>\n<!-- --- BEGIN PLUGIN CSS --- -->\n<style type=\"text/css\">\r\n.list th{\r\n	text-align:left;\r\n	color:#fff;\r\n	background:#555;\r\n	padding:6px 16px 6px 4px;\r\n	border-right:1px solid #999;\r\n	font-weight:normal;\r\n}\r\n.list th a{\r\n	color:#fff;\r\n	display:block;\r\n}\r\n.list th h2{\r\n	margin:0;\r\n	font-size:11px;\r\n}\r\n.list td{\r\n	padding:6px 4px;\r\n}\r\n</style>\n<!-- --- END PLUGIN CSS --- -->', '/*\r\n** cbe_helpful\r\n** Let your readers rate your articles\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 08 Jul 2011 - Initial release\r\n** 0.1.1 12 Jul 2011 - Added control on value returned by getRows (cbe_helpful_list), thx Pat64\r\n** 0.2 - 13 Jul 2011 - cbe_helpful_list can now display cbe_helpful_score, cbe_helpful_yes, cbe_helpful_no\r\n**                     Fixed: bug in list sorting when using ratio or percent display\r\n**                     Repopulate table prefs with defaults if damaged or destroyed\r\n** 0.2.1 19 Jul 2011 - Fixed: non-valid html output for article id and unit, thx Pat64\r\n** 0.3 - 04 Jan 2012 - Fixed: unwanted markup if just displaying score, thx milosevic\r\n**                            (http://forum.textpattern.com/viewtopic.php?pid=256318#p256318)\r\n**                     Fixed: cbe_helpful_label not working if standalone\r\n**                     Fixed: avoid multiple calls to _cbe_helpful_foot when displaying a list\r\n**                     JS code slightly revisited\r\n** 0.4 - 30 May 2012 - Total votes http://forum.textpattern.com/viewtopic.php?pid=260946#p260946\r\n**                     List based on time http://forum.textpattern.com/viewtopic.php?pid=257887#p257887\r\n**                     Fixed: miscalculation in cbe_helpful_list when sorting on score\r\n** 0.5 - 16 Jul 2012 - rah_external_output v1.0: new db structure. Previous structure is no longer supported\r\n**                     Plugin not activated if an error occurs on rah_external_output controls\r\n**                     Checks if all tables are present and restore them if not\r\n** 0.5.1 18 Jul 2012 - Fixed: path for /?rah_external_output=cbe_helpful_{js|ajax}, thx douglgm\r\n**                     (http://forum.textpattern.com/viewtopic.php?pid=263308#p263308)\r\n** 0.6 - 30 Apr 2013 - display only (not clickable) the separate yes and no numbers in an article-list context, thx douglgm\r\n**                     (http://forum.textpattern.com/viewtopic.php?pid=266842#p266842)\r\n**                     Added: attributes showonly (cbe_helpful, cbe_helpful_yes, cbe_helpful_no),\r\n**                                       labelshowonly (cbe_helpful_yes, cbe_helpful_no, cbe_helpful_score, cbe_helpful_total_votes)\r\n**                     Added too:        labelclass (cbe_helpful, cbe_helpful_list),\r\n**                     Fix attempt: markup, thx Susanne (http://forum.textpattern.com/viewtopic.php?pid=267963#p267963)\r\n**\r\n ************************************************************************/\r\n\r\n/* ============== Possible admin-side customisation here ============== */\r\nfunction cbe_helpful_labels_prefs()\r\n{\r\n    return( array( \'label\'      => \"Widget label\"\r\n                 , \'score\'      => \"Rate label\"\r\n                 , \'yes\'        => \"Positive vote label\"\r\n                 , \'no\'         => \"Negative vote label\"\r\n                 , \'votes\'      => \"Total votes label\"\r\n                 , \'yestooltip\' => \"Positive vote tooltip\"\r\n                 , \'notooltip\'  => \"Negative vote tooltip\"\r\n                 , \'finalrate\'  => \"Rate unit: value (simple), ratio or percentage of positive votes\"\r\n                 , \'ipcheck\'    => \"Interval allowed between votes for an IP (hours - 0 if not used)\"\r\n                 )\r\n          ) ;\r\n}\r\n/* =========================== Stop editing =========================== */\r\n\r\nif( @txpinterface == \'admin\' )\r\n{\r\n    add_privs( \'cbe_helpful_admin\', \'1,2\' ) ;\r\n    add_privs( \'plugin_prefs.cbe_helpful\', \'1,2\' );\r\n    register_tab( \'extensions\', \'cbe_helpful_admin\', \'Article Ratings\' ) ;\r\n    register_callback( \'cbe_helpful_admin\', \'plugin_prefs.cbe_helpful\');\r\n    register_callback( \'cbe_helpful_admin\', \'cbe_helpful_admin\' ) ;\r\n    register_callback( \'cbe_helpful_head\',\'admin_side\',\'head_end\') ;\r\n    register_callback( \'cbe_helpful_lifecycle\', \'plugin_lifecycle.cbe_helpful\' ) ;\r\n}\r\n\r\n/* ==================== Both sides ==================== */\r\n\r\nfunction cbe_helpful_def_prefs()\r\n{\r\n    return( array( \'label\'      => \'Helpful ?\'\r\n                 , \'score\'      => \'Score: \'\r\n                 , \'yes\'        => \'Yes\'\r\n                 , \'no\'         => \'No\'\r\n                 , \'votes\'      => \'Total votes\'\r\n                 , \'yestooltip\' => \'\'\r\n                 , \'notooltip\'  => \'\'\r\n                 , \'finalrate\'  => \'simple\'\r\n                 , \'ipcheck\'    => \'24\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_helpful_get_prefs()\r\n{\r\n    $out = array();\r\n    $sortorder = join( \",\", doArray( array_keys( cbe_helpful_def_prefs() ), \'doQuote\' ) ) ;\r\n    $rs = safe_rows( \"name,value\", \"cbe_helpful_prefs\", \"1=1 ORDER BY field( `name`, $sortorder )\" );\r\n		\r\n    foreach($rs as $row)\r\n      $out[ $row[\'name\'] ] = $row[\'value\'] ;\r\n			\r\n    return( $out ) ;\r\n}\r\n\r\n/* ==================== Admin side ==================== */\r\n\r\nfunction cbe_helpful_head()\r\n{\r\n    global $event ;\r\n    if( $event != \'cbe_helpful_admin\' && $event != \'plugin_prefs.cbe_helpful\' )\r\n        return ;\r\n\r\n    echo <<<EOF\r\n        <style type=\"text/css\">\r\n        table.txp-list { width:auto; margin:0 auto }\r\n        #txp-content table#list { width:auto !important }\r\n        form div { margin-top:1.5em; text-align:center }\r\n        td.td-label {\r\n            text-align: right ;\r\n        }\r\n        </style>\r\nEOF;\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_hlp_create_tables( &$hlp_prefs = null )\r\n{\r\n    safe_query( \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_votes\').\' (\r\n        `id` int(11) NOT NULL AUTO_INCREMENT,\r\n        `textpattern_id` int(11) NOT NULL,\r\n        `plus` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `minus` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `ip` varchar(15) NOT NULL DEFAULT \\\'000.000.000.000\\\',\r\n        `dvote` datetime NOT NULL DEFAULT \\\'0000-00-00 00:00:00\\\',\r\n        PRIMARY KEY (`id`)\r\n        )\' ) ;\r\n    safe_query( \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_counts\').\' (\r\n        `textpattern_id` int(11) NOT NULL,\r\n        `count` int(11) NOT NULL DEFAULT \\\'0\\\',\r\n        `pluses` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `minuses` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        PRIMARY KEY (`textpattern_id`)\r\n        )\' )  ;\r\n    safe_query(\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_prefs\').\' (\r\n        `name` VARCHAR(255) NOT NULL,\r\n        `value` LONGTEXT NOT NULL,\r\n        PRIMARY KEY(`name`)\r\n        )\' );\r\n\r\n    /* Populate preference table from defaults */\r\n    foreach( cbe_helpful_def_prefs() as $name => $value )\r\n    {\r\n        if( safe_count( \'cbe_helpful_prefs\', \"name=\'$name\'\" ) == 0 )\r\n        {\r\n            safe_insert(\'cbe_helpful_prefs\', \"name=\'$name\', value=\'$value\'\" ) ;\r\n            if( $hlp_prefs !== null )\r\n                $hlp_prefs[$name] = $value ;\r\n        }\r\n    }\r\n}\r\n\r\nfunction cbe_hlp_prefs_update( &$hlp_prefs, $updates )\r\n{\r\n    extract( $updates ) ;\r\n    foreach( $hlp_prefs as $name => $value )\r\n    {\r\n        if( $$name !== $value )\r\n        {\r\n            safe_upsert( \'cbe_helpful_prefs\', \"`value`=\'\".doSlash($$name).\"\'\"\r\n                                            , \"`name`=\'\".doSlash($name).\"\'\" ) ;\r\n            $hlp_prefs[$name] = $$name ;\r\n        }\r\n    }\r\n}\r\n\r\nfunction cbe_helpful_admin()\r\n{\r\n    global $event, $step ;\r\n    require_privs( \'cbe_helpful_admin\' ) ;\r\n    @$hlp_prefs = cbe_helpful_get_prefs() ;\r\n    $message = \"\" ;\r\n    $arrUnit = array( \'simple\', \'ratio\', \'percent\' ) ;\r\n\r\n    if( $step == \'save\' )\r\n    {\r\n        $updates = psa( array_keys( $hlp_prefs ) ) ;\r\n        cbe_hlp_prefs_update( $hlp_prefs, $updates ) ;\r\n        $message = gTxt( \'preferences_saved\' ) ;\r\n    }\r\n    elseif( $step == \'restore\' )\r\n    {\r\n        cbe_hlp_create_tables( $hlp_prefs ) ;\r\n        $message = \"Preferences restored\" ;\r\n    }\r\n\r\n    $error = array() ;\r\n    if( @getThing(\'describe `\'.safe_pfx(\'cbe_helpful_votes\').\'`\')  === false )\r\n        $error[] = \"missing \" . safe_pfx(\'cbe_helpful_votes\') ;\r\n    if( @getThing(\'describe `\'.safe_pfx(\'cbe_helpful_counts\').\'`\')  === false )\r\n        $error[] = \"missing \" . safe_pfx(\'cbe_helpful_counts\') ;\r\n\r\n    foreach( array_keys( cbe_helpful_def_prefs() ) as $pref )\r\n    {\r\n        if( ! array_key_exists( $pref, $hlp_prefs ) )\r\n        {\r\n            $error[] .= gTxt( \'missing_prefs\' ) ;\r\n            break ;\r\n        }\r\n    }\r\n\r\n    if( count( $error ) )\r\n    {\r\n        $out  = startTable( \'list\', \'\', \'txp-list\' ) ;\r\n        $out .= tr( tda( form( fInput( \'submit\', \'plugin\' , \'Back to plugins\', \'smallerbox\')\r\n                             . eInput( \'plugin\' ) ), \' style=\"text-align: right;\"\' )\r\n                  . td( form( fInput( \'submit\', \'restore\', \'Restore defaults\'   , \'smallerbox\')\r\n                            . sInput( \'restore\' ) . eInput( $event ) ) )\r\n                  ) ;\r\n        pagetop( \"Preferences\", gTxt( \'broken_plugin\' ) . \": \" . join( \", \", $error ) ) ;\r\n        $out .= endTable() ;\r\n        echo( $out ) ;\r\n        $error = array() ;\r\n        return ;\r\n    }\r\n\r\n    $labels = cbe_helpful_labels_prefs() ;\r\n    pagetop( \"cbe_helpful Preferences\", $message ) ;\r\n    $out  = tag( \'cbe_helpful Preferences\', \'h2\' ) ;\r\n    $out .= startTable( \'list\', \'\', \'txp-list\' ) ;\r\n    foreach( $hlp_prefs as $name => $value )\r\n    {\r\n        if( $name === \'finalrate\' )\r\n            $form_field = selectInput( $name, array_combine( $arrUnit, $arrUnit ), $value, false, \'\', $name ) ;\r\n        else\r\n            $form_field = fInput( \'text\', $name, $value, \'edit\', \'\', \'\', \'\', \'\', $name ) ;\r\n\r\n        $out .= tr( td( $labels[$name], \'\', \'td-label\' ).td( $form_field ) ) ;\r\n    }\r\n    $out .= endTable() ;\r\n    $out .= tag( fInput( \'submit\', \'submit\', gTxt(\'save\'), \'smallerbox\').sInput( \'save\' ).eInput( $event ), \'div\' ) ;\r\n    echo( form( $out ) ) ;\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_installed()\r\n{\r\n    /* Update version in txp_prefs */\r\n    set_pref( \'cbe_helpful_version\', \'0.6\', \'cbe_helpful\', PREF_HIDDEN, \'\' ) ;\r\n\r\n    /* AJAX snippets */\r\n    $code = base64_decode( \"OyBDb250ZW50LXR5cGU6IHRleHQvaHRtbFxyXG48dHhwOnBocD5cclxuICBleHRyYWN0KCBwc2EoIGFycmF5KCAnJ2FydGljbGVfaWQnJywgJyd1cF9kb3duJycsICcndW5pdCcnICkgKSApO1xyXG4gICR1cCA9ICRkb3duID0gMCA7XHJcblxyXG4gIGlmKCAkYXJ0aWNsZV9pZCAhPT0gJycnJyAmJiAkdXBfZG93biAhPT0gJycnJyAmJiBpc3NldCggJF9TRVJWRVJbJydSRU1PVEVfQUREUicnXSApIClcclxuICB7XHJcbiAgICAkJHVwX2Rvd24gPSAxIDtcclxuICAgICRpcCA9ICRfU0VSVkVSWycnUkVNT1RFX0FERFInJ10gO1xyXG4gICAgJGR2b3RlID0gZGF0ZSggJydZLW0tZCBIOmk6cycnICkgO1xyXG5cclxuICAgIHNhZmVfaW5zZXJ0KCAnJ2NiZV9oZWxwZnVsX3ZvdGVzJycsICJ0ZXh0cGF0dGVybl9pZD0nJyRhcnRpY2xlX2lkJycsIHBsdXM9JyckdXAnJywgbWludXM9JyckZG93bicnLCBpcD0nJyRpcCcnLCBkdm90ZT0nJyRkdm90ZScnIiApIDtcclxuICAgIHNhZmVfdXBzZXJ0KCAnJ2NiZV9oZWxwZnVsX2NvdW50cycnLCAiY291bnQ9Y291bnQrJHVwLSRkb3duLCBwbHVzZXM9cGx1c2VzKyR1cCwgbWludXNlcz1taW51c2VzKyRkb3duIiwgInRleHRwYXR0ZXJuX2lkPScnJGFydGljbGVfaWQnJyIgKSA7XHJcblxyXG4gICAgZXh0cmFjdCggc2FmZV9yb3coICcnY291bnQsIHBsdXNlcywgbWludXNlcycnLCAnJ2NiZV9oZWxwZnVsX2NvdW50cycnLCAidGV4dHBhdHRlcm5faWQ9JyckYXJ0aWNsZV9pZCcnIiApICkgO1xyXG4gICAgaWYoICR1bml0ID09PSAnJ3NpbXBsZScnIClcclxuICAgICAgICAkc2NvcmUgPSAkY291bnQgO1xyXG4gICAgZWxzZVxyXG4gICAge1xyXG4gICAgICAgICRzY29yZSA9ICRwbHVzZXMvKCRwbHVzZXMrJG1pbnVzZXMpIDtcclxuICAgICAgICAkc2NvcmUgPSAoJHVuaXQgPT09ICcncmF0aW8nJykgPyBzcHJpbnRmKCAiJTQuMmYiICAgLCByb3VuZCggJHNjb3JlLCAyICkgKVxyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgOiBzcHJpbnRmKCAiJTUuMmYgJSUiLCByb3VuZCggMTAwLjAqJHNjb3JlLCAyICkgKSA7XHJcbiAgICB9XHJcblxyXG4gICAgZWNobyBqc29uX2VuY29kZSggYXJyYXkoICcncGx1cycnID0+ICR1cCwgJydtaW51cycnID0+ICRkb3duLCAnJ3Njb3JlJycgPT4gJHNjb3JlICkgKSA7XHJcbiAgfVxyXG48L3R4cDpwaHA+\" ) ;\r\n    $js = base64_decode( \"OyBDb250ZW50LXR5cGU6IHRleHQvamF2YXNjcmlwdFxyXG4kKGRvY3VtZW50KS5yZWFkeShmdW5jdGlvbigpe2Z1bmN0aW9uIGEoYSxiKXt2YXIgYz1hLnBhcmVudHMoIi5oZWxwZnVscmF0ZXIiKTt2YXIgZD1hLnBhcmVudCgiLnBsdXMiKS5jaGlsZHJlbigiLnRoZWNvdW50Iik7dmFyIGU9YS5wYXJlbnQoIi5taW51cyIpLmNoaWxkcmVuKCIudGhlY291bnQiKTt2YXIgZj1jLmZpbmQoIi5obHBfdm90ZXMiKTt1bml0PWMuY2hpbGRyZW4oIi51bml0IikuaHRtbCgpO2FydGljbGVfaWQ9Yy5jaGlsZHJlbigiLnRoaXNpZCIpLmh0bWwoKTtwbHVzZXM9ZC5odG1sKCk7bWludXNlcz1lLmh0bWwoKTt2b3Rlcz1mLmh0bWwoKTskLnBvc3QoIi8/cmFoX2V4dGVybmFsX291dHB1dD1jYmVfaGVscGZ1bF9hamF4Iix7YXJ0aWNsZV9pZDphcnRpY2xlX2lkLHVwX2Rvd246Yix1bml0OnVuaXR9LGZ1bmN0aW9uKGcpe2QuaHRtbChwYXJzZUludChwbHVzZXMpK2cucGx1cyk7ZS5odG1sKHBhcnNlSW50KG1pbnVzZXMpK2cubWludXMpO2MuZmluZCgiLnZhbHVlIikuaHRtbChnLnNjb3JlKTtmLmh0bWwocGFyc2VJbnQodm90ZXMpKzEpO2EuY2hpbGRyZW4oIi5kaXN0ZXh0IikucmVtb3ZlKCk7aWYoYj09InVwIil7YS5wYXJlbnQoKS5zaWJsaW5ncygiLm1pbnVzIikuZmluZCgiLmRpc3RleHQiKS5yZW1vdmUoKX1lbHNle2EucGFyZW50KCkuc2libGluZ3MoIi5wbHVzIikuZmluZCgiLmRpc3RleHQiKS5yZW1vdmUoKX19LCJqc29uIil9JCgiLmhlbHBmdWxwbHVzIikuY2xpY2soZnVuY3Rpb24oKXthKCQodGhpcyksInVwIik7cmV0dXJuIGZhbHNlfSk7JCgiLmhlbHBmdWxtaW51cyIpLmNsaWNrKGZ1bmN0aW9uKCl7YSgkKHRoaXMpLCJkb3duIik7cmV0dXJuIGZhbHNlfSl9KQ==\" ) ;\r\n    $js = str_replace( \"/?rah_external_output\", hu.\"?rah_external_output\", $js ) ;\r\n    safe_upsert( \'txp_form\'\r\n               , \"`Form`=\'$code\'\r\n               ,  `type`=\'misc\'\"\r\n               , \"`name`=\'rah_eo_cbe_helpful_ajax\'\"\r\n               ) ;\r\n    safe_upsert( \'txp_form\'\r\n               , \"`Form`=\'$js\'\r\n               ,  `type`=\'misc\'\"\r\n               , \"`name`=\'rah_eo_cbe_helpful_js\'\"\r\n               ) ;\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_enabled()\r\n{\r\n    @$hlp_prefs = cbe_helpful_get_prefs() ;\r\n\r\n    $rs = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'rah_external_output\'\") ;\r\n    if( !$rs || version_compare( $rs[\'version\'], \"1.0\" ) < 0 )\r\n    {\r\n        safe_update( \'txp_plugin\', \"status = 0\", \"name = \'cbe_helpful\'\" ) ;\r\n        return( \"Plugin NON initialisé : rah_external_output v1.0 MINIMUM requis\" ) ;\r\n    }\r\n\r\n    /* Create tables */\r\n    cbe_hlp_create_tables() ;\r\n\r\n    /* If first install, populate data tables from wlk_helpful if active */\r\n    if( ! isset( $hlp_prefs[\'ipcheck\'] )\r\n       && safe_row( \"version\",\"txp_plugin\",\"status = 1 AND name=\'wlk_helpful\'\" )\r\n       && mysql_num_rows( safe_query( \"show tables like \'%wlk%\'\" ) )\r\n      )\r\n    {\r\n        $dmigr = date( \'Y-m-d H:i:s\' ) ;\r\n        $rs = safe_rows_start( \'*\', \'txp_wlk_helpful\', \'NOT (`plus` < 1 AND `minus` < 1) ORDER BY `id` ASC\' );\r\n        if( count( $rs ) )\r\n        {\r\n            while( $a = nextRow( $rs ) )\r\n            {\r\n                extract( $a ) ;\r\n                safe_insert( \"cbe_helpful_votes\"\r\n                           , \"`textpattern_id`=\'$textpattern_id\'\r\n                           , `plus`=\'$plus\'\r\n                           , `minus`=\'$minus\'\r\n                           , `ip`=\'$ip\'\r\n                           , `dvote`=\'$dmigr\'\"\r\n                           ) ;\r\n            }\r\n        }\r\n\r\n        safe_query( \"INSERT INTO \".safe_pfx(\'cbe_helpful_counts\').\r\n                    \" SELECT `textpattern_id`, sum(`plus`)-sum(`minus`) as count, sum(`plus`) as pluses, sum(`minus`) as minuses\r\n                    FROM \".safe_pfx(\'cbe_helpful_votes\').\" WHERE 1 GROUP BY `textpattern_id`\"\r\n                  ) ;\r\n    }\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_deleted()\r\n{\r\n    safe_delete( \'txp_form\', \"`name`=\'rah_eo_cbe_helpful_ajax\'\" ) ;\r\n    safe_delete( \'txp_form\', \"`name`=\'rah_eo_cbe_helpful_js\'\" ) ;\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_votes\')  );\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_counts\') );\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_prefs\')  );\r\n    safe_delete( \'txp_prefs\', \"name=\'cbe_helpful_version\'\" );\r\n}\r\n\r\nfunction cbe_helpful_lifecycle( $event = \'\', $step = \'\' )\r\n{\r\n    $func = \"cbe_helpful_$step\" ;\r\n    if( is_callable( $func ) )\r\n        return( $func() ) ;\r\n}\r\n\r\n/* ==================== Client side ==================== */\r\n\r\n/**\r\n ** Utilities\r\n **/\r\n\r\nfunction _cbe_helpful_computescore( $pluses, $minuses )\r\n{\r\n    global $hlp_prefs ;\r\n\r\n    /* ATTENTION s\'il y a du changement ici: le script ajax doit aussi être revu de la même façon */\r\n    if( ($unit=$hlp_prefs[\'finalrate\']) === \'simple\' )\r\n        $score = $pluses - $minuses ;\r\n    else\r\n    {\r\n        $score = ( ($votes=$pluses+$minuses) > 0 ) ? $pluses/$votes : 0 ;\r\n        $score = ($unit === \'ratio\') ? sprintf( \"%4.2f\"   , round( $score, 2 ) )\r\n                                     : sprintf( \"%5.2f %%\", round( 100.0*$score, 2 ) ) ;\r\n    }\r\n\r\n    return( $score ) ;\r\n}\r\n\r\nfunction _cbe_helpful_getrates( $art_id )\r\n{\r\n    global $hlp_prefs ;\r\n\r\n    $count = $pluses = $minuses = \"0\" ;\r\n    $ip    = $_SERVER[\'REMOTE_ADDR\'] ;\r\n\r\n    $canvote = safe_count( \'cbe_helpful_votes\', \"textpattern_id=\'$art_id\' AND ip=\'$ip\' AND ADDDATE(dvote, INTERVAL {$hlp_prefs[\'ipcheck\']} HOUR) > NOW()\" ) == 0 ;\r\n\r\n    if( count( $scores = safe_row( \'count, pluses, minuses\'\r\n                                 , \'cbe_helpful_counts\', \"textpattern_id=\'$art_id\'\" ) ) )\r\n        extract( $scores );\r\n\r\n    $score = _cbe_helpful_computescore( $pluses, $minuses ) ;\r\n\r\n    return( compact( \"canvote\", \"count\", \"pluses\", \"minuses\", \"score\" ) ) ;\r\n}\r\n\r\nfunction _cbe_helpful_yesno( $atts, $switch )\r\n{\r\n    global $hlp_prefs, $pluses, $minuses, $canvote, $showonly, $bIslist ;\r\n\r\n    $votable = ( !$showonly && is_bool($switch) ) ? $canvote : false ;\r\n\r\n    $jsclass     = $votable ? ( $switch ? \"plus\"        : \"minus\" ) : \"\" ;\r\n    $jsvoteclass = $votable ? ( $switch ? \"helpfulplus\" : \"helpfulminus\" ) : \"\" ;\r\n    $jscount     = $votable ? \"thecount\" : \"\" ;\r\n    $jstext      = $votable ? \"distext\" : \"\" ;\r\n    extract( lAtts( array ( \'wraptag\'       => \'\'\r\n                          , \'class\'         => \'\'\r\n                          , \'voteclass\'     => \'\'\r\n                          , \'showonly\'      => isset( $showonly ) ? $showonly : \'0\'\r\n                          , \'labelshowonly\' => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $votable )\r\n    {   /* clickable yes/no */\r\n        $voteclass = $voteclass != \'\' ? \"$jsvoteclass $voteclass\" : $jsvoteclass ;\r\n        $switch = $switch ? \'yes\' : \'no\' ;\r\n        $out[] = doWrap( array( $switch==\'yes\' ? $pluses : $minuses ), \"span\", \'\', $jscount ) ;\r\n        $out[] = href( doTag( $hlp_prefs[$switch], \"span\", $jstext ), \'#\'\r\n                            , \' title=\"\'.$hlp_prefs[$switch.\'tooltip\'].\'\" class=\"\'.$voteclass.\'\"\' ) ;\r\n    }\r\n    elseif( ! is_bool( $switch ) )\r\n    {   /* total_votes */\r\n        $out[] = doWrap( array( $pluses + $minuses ), \"span\", \'\', \"hlp_$switch\" ) ;\r\n        if( $showonly )\r\n            $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[$switch]), \'span\' ) ;\r\n        else\r\n            $out[] = doTag( ($hlp_prefs[$switch] ? $hlp_prefs[$switch] : $labelshowonly), \'span\' ) ;\r\n    }\r\n    else\r\n    {   /* non-clickable yes/no */\r\n        $out[] = $switch ? $pluses : $minuses ;\r\n        if( $showonly )\r\n            $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[$switch]), \"span\" ) ;\r\n    }\r\n    /* == Note: $labelshowonly & $hlp_prefs[$switch] are a fallback for each other == */\r\n\r\n    return( doWrap( $out, $wraptag ? $wraptag : ( (isset( $bIslist ) && $bIslist) ? \'li\' : \'span\' ), \' \'\r\n                  , $class != \'\' ? \"$jsclass $class\" : $jsclass\r\n                  ) ) ;\r\n}\r\n\r\nfunction _cbe_helpful_foot( $html )\r\n{   /* Put specific javascript just before </body> */\r\n    $js = \'<script type=\"text/javascript\" src=\"\'.hu.\'?rah_external_output=cbe_helpful_js\"></script>\' ;\r\n    $pattern = \'/(?=(<\\/body>))/si\';\r\n    $html = preg_replace( $pattern, $js, $html );\r\n    return $html;\r\n}\r\n\r\n/**\r\n ** Available tags\r\n **/\r\n\r\nfunction cbe_helpful( $atts, $thing = \'\' )\r\n{   /* Vote widget */\r\n    static $ajax_module = false ;\r\n    assert_article() ;\r\n    if( ! $ajax_module )\r\n    {\r\n//        ob_start( array( &$this, \'_cbe_helpful_foot\' ) ) ;\r\n        ob_start( \'_cbe_helpful_foot\' ) ;\r\n        $ajax_module = true ;\r\n    }\r\n    global $thisarticle ;\r\n    $cbe_hlp_globals = array( \'hlp_prefs\', \'score\', \'pluses\', \'minuses\', \'canvote\', \'showonly\', \'bIslist\' ) ;\r\n    foreach( $cbe_hlp_globals as $cbe_hlp_global )\r\n        global $$cbe_hlp_global ;\r\n\r\n\r\n    $jsclass = \"helpfulrater\" ;\r\n    $jsunit  = \"unit\" ;\r\n    $jsthid  = \"thisid\" ;\r\n    $hlp_prefs = cbe_helpful_get_prefs() ;\r\n\r\n    extract( lAtts( array ( \'label\'      => $hlp_prefs[\'label\']\r\n                          , \'labeltag\'   => \'h2\'\r\n                          , \'labelclass\' => \'\'\r\n                          , \'wraptag\'    => \'div\'\r\n                          , \'class\'      => \'\'\r\n                          , \'showonly\'   => \'0\'\r\n                          , \'form\'       => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $art_id = $thisarticle[ \'thisid\' ] ;\r\n\r\n    extract( _cbe_helpful_getrates( $art_id ) ) ;\r\n    $class = $class != \'\' ? \"$jsclass $class\" : $jsclass ;\r\n\r\n    $parsed = array() ;\r\n\r\n    if (empty($form) && empty($thing))\r\n    {\r\n        $parsed[] = doTag( $art_id,                 \'li\', $jsthid, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = doTag( $hlp_prefs[\'finalrate\'], \'li\', $jsunit, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = cbe_helpful_score( array( \'wraptag\'=>\'li\' ) ) ;\r\n        $parsed[] = cbe_helpful_label( array( \'class\'=>\'helpful\', \'wraptag\'=>\'li\' ) ) ;\r\n        $parsed[] = cbe_helpful_yes( array( \'wraptag\'=>\'li\', \'showonly\'=>$showonly ) ) ;\r\n        $parsed[] = cbe_helpful_no ( array( \'wraptag\'=>\'li\', \'showonly\'=>$showonly ) ) ;\r\n        $parsed[] = cbe_helpful_total_votes( array( \'wraptag\'=>\'li\' ) ) ;\r\n        $out = doWrap( $parsed, \'ul\', \'\', $class ) ;\r\n    }\r\n    else\r\n    {\r\n        $bIslist  = ($wraptag == \'ul\' || $wraptag == \'ol\') ;\r\n        $parsed[] = doTag( $art_id,                 $bIslist ? \'li\' : \'span\', $jsthid, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = doTag( $hlp_prefs[\'finalrate\'], $bIslist ? \'li\' : \'span\', $jsunit, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = parse( $thing ? $thing : $form ) ;\r\n        $out = ($label ? doTag( $label, $labeltag, $labelclass ) : \'\')\r\n             . doWrap( $parsed, $wraptag ? $wraptag : \'div\', \'\', $class ) ;\r\n    }\r\n\r\n    foreach( $cbe_hlp_globals as $cbe_hlp_global ) {\r\n        unset( $$cbe_hlp_global ) ;\r\n        unset( $GLOBALS[$cbe_hlp_global] ) ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}\r\n\r\nfunction cbe_helpful_label( $atts )\r\n{\r\n    global $hlp_prefs ;\r\n    extract( lAtts( array ( \'wraptag\'    => \'\'\r\n                          , \'class\'      => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    if( $hlp_prefs === NULL )\r\n        $hlp_prefs = cbe_helpful_get_prefs() ;\r\n    return( doTag( $hlp_prefs[\'label\'], $wraptag, $class ) ) ;\r\n}\r\n\r\nfunction cbe_helpful_score( $atts )\r\n{\r\n    global $hlp_prefs, $score, $canvote, $showonly, $bIslist ;\r\n\r\n    if( $score === NULL )\r\n    {\r\n        assert_article() ;\r\n        ob_start( array( &$this, \'_cbe_helpful_foot\' ) ) ;\r\n        global $thisarticle ;\r\n        $hlp_prefs = cbe_helpful_get_prefs() ;\r\n        extract( _cbe_helpful_getrates( $thisarticle[ \'thisid\' ] ) ) ;\r\n        $temp_score = $score ;\r\n        $score = NULL ;\r\n    }\r\n\r\n    $jsclass = $canvote ? \"value\" : \"\" ;\r\n    extract( lAtts( array ( \'wraptag\'       => \'\'\r\n                          , \'class\'         => \'\'\r\n                          , \'labelshowonly\' => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $showonly )\r\n        $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[\'score\']), \'span\' ) ;\r\n    else\r\n        $out[] = doTag( ($hlp_prefs[\'score\'] ? $hlp_prefs[\'score\'] : $labelshowonly), \'span\' ) ;\r\n    /* == Note: $labelshowonly & $hlp_prefs[\'score\'] are a fallback for each other == */\r\n\r\n    $out[] = doWrap( array( $score !== NULL ? $score : $temp_score )\r\n                   , \'span\', \'\'\r\n                   , $jsclass, \'\'\r\n                   , \" title=\\\"{$hlp_prefs[\'score\']}\\\"\"\r\n                   ) ;\r\n\r\n    return( doWrap( $out, (isset( $bIslist ) && $bIslist) ? \'li\' : $wraptag, \'\', $class ) ) ;\r\n}\r\n\r\nfunction cbe_helpful_yes( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, true ) ) ;\r\n}\r\nfunction cbe_helpful_no( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, false ) ) ;\r\n}\r\nfunction cbe_helpful_total_votes( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, \"votes\" ) ) ;\r\n}\r\n\r\n/* Tops and flops */\r\nfunction cbe_helpful_list( $atts, $thing = \'\' )\r\n{\r\n    global $thisarticle, $prefs, $hlp_prefs, $score, $pluses, $minuses, $canvote, $showonly, $bIslist ;\r\n    extract( lAtts( array ( \'label\'      => \'\'\r\n                          , \'labeltag\'   => \'h2\'\r\n                          , \'labelclass\' => \'\'\r\n                          , \'wraptag\'    => \'ol\'\r\n                          , \'break\'      => \'li\'\r\n                          , \'class\'      => __FUNCTION__\r\n                          , \'breakclass\' => \'\'\r\n                          , \'form\'       => \'\'\r\n                          , \'which\'      => \'top\'\r\n                          , \'order\'      => \'desc\'\r\n                          , \'limit\'      => \'5\'\r\n                          , \'period\'     => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $hlp_prefs = cbe_helpful_get_prefs() ;\r\n    $timelimit = \'\' ;\r\n    $sortorder = $which==\'top\' ? \'desc\' : \'asc\' ;\r\n    $sortwhat  = $hlp_prefs[\'finalrate\'] === \'simple\'\r\n               ? \"helpful.count $sortorder, helpful.pluses $sortorder\"\r\n               : \"pct $sortorder, helpful.pluses $sortorder\" ;\r\n\r\n    if( $period !== \'\' )\r\n    {\r\n        $period = str_replace( array( \"today\", \"yesterday\" ), array( \"this day\", \"last day\"), $period ) ;\r\n        preg_match( \'/(?P<when>\\w+) (?P<what>\\w+)/\', $period, $matches ) ;\r\n        $aWhat = array( \'year\'  => \"1/1\"\r\n                      , \'month\' => \"midnight first day of\"\r\n                      , \'week\'  => \"midnight\"\r\n                      , \'day\'   => \"midnight\"\r\n                      ) ;\r\n        $aWhen = array( \'this\' => array( \'beg\' => \"this\", \'end\' => \"next\" )\r\n                      , \'last\' => array( \'beg\' => \"last\", \'end\' => \"this\" )\r\n                      ) ;\r\n        if( isset( $aWhat[ $matches[ \'what\' ] ] ) && isset( $aWhen[ $matches[ \'when\' ] ] ) )\r\n        {\r\n            $timefrom = date( \"Y-m-d H:i:s\", strtotime( \"{$aWhat[ $matches[ \'what\' ] ]} {$aWhen[ $matches[ \'when\' ] ][ \'beg\' ]} {$matches[ \'what\' ]}\" ) ) ;\r\n            $timeto = date( \"Y-m-d H:i:s\", strtotime( \"-1 second {$aWhat[ $matches[ \'what\' ] ]} {$aWhen[ $matches[ \'when\' ] ][ \'end\' ]} {$matches[ \'what\' ]}\" ) ) ;\r\n            $timelimit = \" WHERE helpful.dvote BETWEEN \'$timefrom\' AND \'$timeto\'\" ;\r\n            $sqlRows = \'SELECT *, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, sum(helpful.plus) AS pluses, sum(helpful.minus) AS minuses, sum(helpful.plus)-sum(helpful.minus) AS count, sum(helpful.plus)/(sum(helpful.plus)+sum(helpful.minus)) AS pct FROM \'.safe_pfx(\'textpattern\').\' AS txp JOIN \'.safe_pfx(\'cbe_helpful_votes\').\' AS helpful ON txp.ID=helpful.textpattern_id\'.$timelimit.\' GROUP BY helpful.textpattern_id ORDER BY \'.str_replace(\"helpful.\", \"\", $sortwhat).\' LIMIT 0, \'.intval($limit) ;\r\n        }\r\n        else\r\n            trigger_error( \"Bad period format\" ) ;\r\n    }\r\n    else\r\n        $sqlRows = \'SELECT *, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, helpful.pluses/(helpful.pluses+helpful.minuses) AS pct FROM \'.safe_pfx(\'textpattern\').\' AS txp JOIN \'.safe_pfx(\'cbe_helpful_counts\').\' AS helpful ON txp.ID=helpful.textpattern_id ORDER BY \'.$sortwhat.\' LIMIT 0, \'.intval($limit) ;\r\n\r\n    $rs = isset( $sqlRows ) ? getRows( $sqlRows ) : false ;\r\n    if( $rs === false || !count( $rs ) )\r\n        return ;\r\n\r\n    if( $sortorder != $order )\r\n        $rs = array_reverse( $rs ) ;\r\n\r\n    $out = array() ;\r\n    $old_article  = $thisarticle ;\r\n    $old_score    = $score ;\r\n    $old_pluses   = $pluses ;\r\n    $old_minuses  = $minuses ;\r\n    $old_canvote  = $canvote ;\r\n    $old_showonly = $showonly ;\r\n\r\n    $canvote  = false ;\r\n    $showonly = true  ;\r\n    \r\n    foreach( $rs as $a )\r\n    {\r\n        $a[\'Title\'] = ($prefs[\'title_no_widow\']) ? noWidow(escape_title($a[\'Title\']))\r\n                                                 : escape_title($a[\'Title\']);\r\n        $a[\'uPosted\'] = $a[\'posted\'] ;\r\n\r\n        if (empty($form) && empty($thing))\r\n        {\r\n            $out[] = href($a[\'Title\'], permlinkurl($a));\r\n        }\r\n        else\r\n        {\r\n            populateArticleData($a);\r\n            $pluses  = $a[ \'pluses\' ] ;\r\n            $minuses = $a[ \'minuses\' ] ;\r\n            $score = _cbe_helpful_computescore( $pluses, $minuses ) ;\r\n            $out[] = ($thing) ?  parse($thing) : parse_form($form);\r\n        }\r\n    }\r\n\r\n    $thisarticle = $old_article ;\r\n    $score       = $old_score ;\r\n    $pluses      = $old_pluses ;\r\n    $minuses     = $old_minuses ;\r\n    $canvote     = $old_canvote ;\r\n    $showonly    = $old_showonly ;\r\n\r\n    if( !count( $out ) )\r\n        return ;\r\n\r\n    return( ($label ? doTag( $label, $labeltag, $labelclass ) : \'\')\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n', '/*\r\n** cbe_helpful\r\n** Let your readers rate your articles\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 08 Jul 2011 - Initial release\r\n** 0.1.1 12 Jul 2011 - Added control on value returned by getRows (cbe_helpful_list), thx Pat64\r\n** 0.2 - 13 Jul 2011 - cbe_helpful_list can now display cbe_helpful_score, cbe_helpful_yes, cbe_helpful_no\r\n**                     Fixed: bug in list sorting when using ratio or percent display\r\n**                     Repopulate table prefs with defaults if damaged or destroyed\r\n** 0.2.1 19 Jul 2011 - Fixed: non-valid html output for article id and unit, thx Pat64\r\n** 0.3 - 04 Jan 2012 - Fixed: unwanted markup if just displaying score, thx milosevic\r\n**                            (http://forum.textpattern.com/viewtopic.php?pid=256318#p256318)\r\n**                     Fixed: cbe_helpful_label not working if standalone\r\n**                     Fixed: avoid multiple calls to _cbe_helpful_foot when displaying a list\r\n**                     JS code slightly revisited\r\n** 0.4 - 30 May 2012 - Total votes http://forum.textpattern.com/viewtopic.php?pid=260946#p260946\r\n**                     List based on time http://forum.textpattern.com/viewtopic.php?pid=257887#p257887\r\n**                     Fixed: miscalculation in cbe_helpful_list when sorting on score\r\n** 0.5 - 16 Jul 2012 - rah_external_output v1.0: new db structure. Previous structure is no longer supported\r\n**                     Plugin not activated if an error occurs on rah_external_output controls\r\n**                     Checks if all tables are present and restore them if not\r\n** 0.5.1 18 Jul 2012 - Fixed: path for /?rah_external_output=cbe_helpful_{js|ajax}, thx douglgm\r\n**                     (http://forum.textpattern.com/viewtopic.php?pid=263308#p263308)\r\n** 0.6 - 30 Apr 2013 - display only (not clickable) the separate yes and no numbers in an article-list context, thx douglgm\r\n**                     (http://forum.textpattern.com/viewtopic.php?pid=266842#p266842)\r\n**                     Added: attributes showonly (cbe_helpful, cbe_helpful_yes, cbe_helpful_no),\r\n**                                       labelshowonly (cbe_helpful_yes, cbe_helpful_no, cbe_helpful_score, cbe_helpful_total_votes)\r\n**                     Added too:        labelclass (cbe_helpful, cbe_helpful_list),\r\n**                     Fix attempt: markup, thx Susanne (http://forum.textpattern.com/viewtopic.php?pid=267963#p267963)\r\n**\r\n ************************************************************************/\r\n\r\n/* ============== Possible admin-side customisation here ============== */\r\nfunction cbe_helpful_labels_prefs()\r\n{\r\n    return( array( \'label\'      => \"Widget label\"\r\n                 , \'score\'      => \"Rate label\"\r\n                 , \'yes\'        => \"Positive vote label\"\r\n                 , \'no\'         => \"Negative vote label\"\r\n                 , \'votes\'      => \"Total votes label\"\r\n                 , \'yestooltip\' => \"Positive vote tooltip\"\r\n                 , \'notooltip\'  => \"Negative vote tooltip\"\r\n                 , \'finalrate\'  => \"Rate unit: value (simple), ratio or percentage of positive votes\"\r\n                 , \'ipcheck\'    => \"Interval allowed between votes for an IP (hours - 0 if not used)\"\r\n                 )\r\n          ) ;\r\n}\r\n/* =========================== Stop editing =========================== */\r\n\r\nif( @txpinterface == \'admin\' )\r\n{\r\n    add_privs( \'cbe_helpful_admin\', \'1,2\' ) ;\r\n    add_privs( \'plugin_prefs.cbe_helpful\', \'1,2\' );\r\n    register_tab( \'extensions\', \'cbe_helpful_admin\', \'Article Ratings\' ) ;\r\n    register_callback( \'cbe_helpful_admin\', \'plugin_prefs.cbe_helpful\');\r\n    register_callback( \'cbe_helpful_admin\', \'cbe_helpful_admin\' ) ;\r\n    register_callback( \'cbe_helpful_head\',\'admin_side\',\'head_end\') ;\r\n    register_callback( \'cbe_helpful_lifecycle\', \'plugin_lifecycle.cbe_helpful\' ) ;\r\n}\r\n\r\n/* ==================== Both sides ==================== */\r\n\r\nfunction cbe_helpful_def_prefs()\r\n{\r\n    return( array( \'label\'      => \'Helpful ?\'\r\n                 , \'score\'      => \'Score: \'\r\n                 , \'yes\'        => \'Yes\'\r\n                 , \'no\'         => \'No\'\r\n                 , \'votes\'      => \'Total votes\'\r\n                 , \'yestooltip\' => \'\'\r\n                 , \'notooltip\'  => \'\'\r\n                 , \'finalrate\'  => \'simple\'\r\n                 , \'ipcheck\'    => \'24\'\r\n                 )\r\n          ) ;\r\n}\r\n\r\nfunction cbe_helpful_get_prefs()\r\n{\r\n    $out = array();\r\n    $sortorder = join( \",\", doArray( array_keys( cbe_helpful_def_prefs() ), \'doQuote\' ) ) ;\r\n    $rs = safe_rows( \"name,value\", \"cbe_helpful_prefs\", \"1=1 ORDER BY field( `name`, $sortorder )\" );\r\n		\r\n    foreach($rs as $row)\r\n      $out[ $row[\'name\'] ] = $row[\'value\'] ;\r\n			\r\n    return( $out ) ;\r\n}\r\n\r\n/* ==================== Admin side ==================== */\r\n\r\nfunction cbe_helpful_head()\r\n{\r\n    global $event ;\r\n    if( $event != \'cbe_helpful_admin\' && $event != \'plugin_prefs.cbe_helpful\' )\r\n        return ;\r\n\r\n    echo <<<EOF\r\n        <style type=\"text/css\">\r\n        table.txp-list { width:auto; margin:0 auto }\r\n        #txp-content table#list { width:auto !important }\r\n        form div { margin-top:1.5em; text-align:center }\r\n        td.td-label {\r\n            text-align: right ;\r\n        }\r\n        </style>\r\nEOF;\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_hlp_create_tables( &$hlp_prefs = null )\r\n{\r\n    safe_query( \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_votes\').\' (\r\n        `id` int(11) NOT NULL AUTO_INCREMENT,\r\n        `textpattern_id` int(11) NOT NULL,\r\n        `plus` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `minus` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `ip` varchar(15) NOT NULL DEFAULT \\\'000.000.000.000\\\',\r\n        `dvote` datetime NOT NULL DEFAULT \\\'0000-00-00 00:00:00\\\',\r\n        PRIMARY KEY (`id`)\r\n        )\' ) ;\r\n    safe_query( \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_counts\').\' (\r\n        `textpattern_id` int(11) NOT NULL,\r\n        `count` int(11) NOT NULL DEFAULT \\\'0\\\',\r\n        `pluses` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        `minuses` smallint(6) NOT NULL DEFAULT \\\'0\\\',\r\n        PRIMARY KEY (`textpattern_id`)\r\n        )\' )  ;\r\n    safe_query(\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'cbe_helpful_prefs\').\' (\r\n        `name` VARCHAR(255) NOT NULL,\r\n        `value` LONGTEXT NOT NULL,\r\n        PRIMARY KEY(`name`)\r\n        )\' );\r\n\r\n    /* Populate preference table from defaults */\r\n    foreach( cbe_helpful_def_prefs() as $name => $value )\r\n    {\r\n        if( safe_count( \'cbe_helpful_prefs\', \"name=\'$name\'\" ) == 0 )\r\n        {\r\n            safe_insert(\'cbe_helpful_prefs\', \"name=\'$name\', value=\'$value\'\" ) ;\r\n            if( $hlp_prefs !== null )\r\n                $hlp_prefs[$name] = $value ;\r\n        }\r\n    }\r\n}\r\n\r\nfunction cbe_hlp_prefs_update( &$hlp_prefs, $updates )\r\n{\r\n    extract( $updates ) ;\r\n    foreach( $hlp_prefs as $name => $value )\r\n    {\r\n        if( $$name !== $value )\r\n        {\r\n            safe_upsert( \'cbe_helpful_prefs\', \"`value`=\'\".doSlash($$name).\"\'\"\r\n                                            , \"`name`=\'\".doSlash($name).\"\'\" ) ;\r\n            $hlp_prefs[$name] = $$name ;\r\n        }\r\n    }\r\n}\r\n\r\nfunction cbe_helpful_admin()\r\n{\r\n    global $event, $step ;\r\n    require_privs( \'cbe_helpful_admin\' ) ;\r\n    @$hlp_prefs = cbe_helpful_get_prefs() ;\r\n    $message = \"\" ;\r\n    $arrUnit = array( \'simple\', \'ratio\', \'percent\' ) ;\r\n\r\n    if( $step == \'save\' )\r\n    {\r\n        $updates = psa( array_keys( $hlp_prefs ) ) ;\r\n        cbe_hlp_prefs_update( $hlp_prefs, $updates ) ;\r\n        $message = gTxt( \'preferences_saved\' ) ;\r\n    }\r\n    elseif( $step == \'restore\' )\r\n    {\r\n        cbe_hlp_create_tables( $hlp_prefs ) ;\r\n        $message = \"Preferences restored\" ;\r\n    }\r\n\r\n    $error = array() ;\r\n    if( @getThing(\'describe `\'.safe_pfx(\'cbe_helpful_votes\').\'`\')  === false )\r\n        $error[] = \"missing \" . safe_pfx(\'cbe_helpful_votes\') ;\r\n    if( @getThing(\'describe `\'.safe_pfx(\'cbe_helpful_counts\').\'`\')  === false )\r\n        $error[] = \"missing \" . safe_pfx(\'cbe_helpful_counts\') ;\r\n\r\n    foreach( array_keys( cbe_helpful_def_prefs() ) as $pref )\r\n    {\r\n        if( ! array_key_exists( $pref, $hlp_prefs ) )\r\n        {\r\n            $error[] .= gTxt( \'missing_prefs\' ) ;\r\n            break ;\r\n        }\r\n    }\r\n\r\n    if( count( $error ) )\r\n    {\r\n        $out  = startTable( \'list\', \'\', \'txp-list\' ) ;\r\n        $out .= tr( tda( form( fInput( \'submit\', \'plugin\' , \'Back to plugins\', \'smallerbox\')\r\n                             . eInput( \'plugin\' ) ), \' style=\"text-align: right;\"\' )\r\n                  . td( form( fInput( \'submit\', \'restore\', \'Restore defaults\'   , \'smallerbox\')\r\n                            . sInput( \'restore\' ) . eInput( $event ) ) )\r\n                  ) ;\r\n        pagetop( \"Preferences\", gTxt( \'broken_plugin\' ) . \": \" . join( \", \", $error ) ) ;\r\n        $out .= endTable() ;\r\n        echo( $out ) ;\r\n        $error = array() ;\r\n        return ;\r\n    }\r\n\r\n    $labels = cbe_helpful_labels_prefs() ;\r\n    pagetop( \"cbe_helpful Preferences\", $message ) ;\r\n    $out  = tag( \'cbe_helpful Preferences\', \'h2\' ) ;\r\n    $out .= startTable( \'list\', \'\', \'txp-list\' ) ;\r\n    foreach( $hlp_prefs as $name => $value )\r\n    {\r\n        if( $name === \'finalrate\' )\r\n            $form_field = selectInput( $name, array_combine( $arrUnit, $arrUnit ), $value, false, \'\', $name ) ;\r\n        else\r\n            $form_field = fInput( \'text\', $name, $value, \'edit\', \'\', \'\', \'\', \'\', $name ) ;\r\n\r\n        $out .= tr( td( $labels[$name], \'\', \'td-label\' ).td( $form_field ) ) ;\r\n    }\r\n    $out .= endTable() ;\r\n    $out .= tag( fInput( \'submit\', \'submit\', gTxt(\'save\'), \'smallerbox\').sInput( \'save\' ).eInput( $event ), \'div\' ) ;\r\n    echo( form( $out ) ) ;\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_installed()\r\n{\r\n    /* Update version in txp_prefs */\r\n    set_pref( \'cbe_helpful_version\', \'0.6\', \'cbe_helpful\', PREF_HIDDEN, \'\' ) ;\r\n\r\n    /* AJAX snippets */\r\n    $code = base64_decode( \"OyBDb250ZW50LXR5cGU6IHRleHQvaHRtbFxyXG48dHhwOnBocD5cclxuICBleHRyYWN0KCBwc2EoIGFycmF5KCAnJ2FydGljbGVfaWQnJywgJyd1cF9kb3duJycsICcndW5pdCcnICkgKSApO1xyXG4gICR1cCA9ICRkb3duID0gMCA7XHJcblxyXG4gIGlmKCAkYXJ0aWNsZV9pZCAhPT0gJycnJyAmJiAkdXBfZG93biAhPT0gJycnJyAmJiBpc3NldCggJF9TRVJWRVJbJydSRU1PVEVfQUREUicnXSApIClcclxuICB7XHJcbiAgICAkJHVwX2Rvd24gPSAxIDtcclxuICAgICRpcCA9ICRfU0VSVkVSWycnUkVNT1RFX0FERFInJ10gO1xyXG4gICAgJGR2b3RlID0gZGF0ZSggJydZLW0tZCBIOmk6cycnICkgO1xyXG5cclxuICAgIHNhZmVfaW5zZXJ0KCAnJ2NiZV9oZWxwZnVsX3ZvdGVzJycsICJ0ZXh0cGF0dGVybl9pZD0nJyRhcnRpY2xlX2lkJycsIHBsdXM9JyckdXAnJywgbWludXM9JyckZG93bicnLCBpcD0nJyRpcCcnLCBkdm90ZT0nJyRkdm90ZScnIiApIDtcclxuICAgIHNhZmVfdXBzZXJ0KCAnJ2NiZV9oZWxwZnVsX2NvdW50cycnLCAiY291bnQ9Y291bnQrJHVwLSRkb3duLCBwbHVzZXM9cGx1c2VzKyR1cCwgbWludXNlcz1taW51c2VzKyRkb3duIiwgInRleHRwYXR0ZXJuX2lkPScnJGFydGljbGVfaWQnJyIgKSA7XHJcblxyXG4gICAgZXh0cmFjdCggc2FmZV9yb3coICcnY291bnQsIHBsdXNlcywgbWludXNlcycnLCAnJ2NiZV9oZWxwZnVsX2NvdW50cycnLCAidGV4dHBhdHRlcm5faWQ9JyckYXJ0aWNsZV9pZCcnIiApICkgO1xyXG4gICAgaWYoICR1bml0ID09PSAnJ3NpbXBsZScnIClcclxuICAgICAgICAkc2NvcmUgPSAkY291bnQgO1xyXG4gICAgZWxzZVxyXG4gICAge1xyXG4gICAgICAgICRzY29yZSA9ICRwbHVzZXMvKCRwbHVzZXMrJG1pbnVzZXMpIDtcclxuICAgICAgICAkc2NvcmUgPSAoJHVuaXQgPT09ICcncmF0aW8nJykgPyBzcHJpbnRmKCAiJTQuMmYiICAgLCByb3VuZCggJHNjb3JlLCAyICkgKVxyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgOiBzcHJpbnRmKCAiJTUuMmYgJSUiLCByb3VuZCggMTAwLjAqJHNjb3JlLCAyICkgKSA7XHJcbiAgICB9XHJcblxyXG4gICAgZWNobyBqc29uX2VuY29kZSggYXJyYXkoICcncGx1cycnID0+ICR1cCwgJydtaW51cycnID0+ICRkb3duLCAnJ3Njb3JlJycgPT4gJHNjb3JlICkgKSA7XHJcbiAgfVxyXG48L3R4cDpwaHA+\" ) ;\r\n    $js = base64_decode( \"OyBDb250ZW50LXR5cGU6IHRleHQvamF2YXNjcmlwdFxyXG4kKGRvY3VtZW50KS5yZWFkeShmdW5jdGlvbigpe2Z1bmN0aW9uIGEoYSxiKXt2YXIgYz1hLnBhcmVudHMoIi5oZWxwZnVscmF0ZXIiKTt2YXIgZD1hLnBhcmVudCgiLnBsdXMiKS5jaGlsZHJlbigiLnRoZWNvdW50Iik7dmFyIGU9YS5wYXJlbnQoIi5taW51cyIpLmNoaWxkcmVuKCIudGhlY291bnQiKTt2YXIgZj1jLmZpbmQoIi5obHBfdm90ZXMiKTt1bml0PWMuY2hpbGRyZW4oIi51bml0IikuaHRtbCgpO2FydGljbGVfaWQ9Yy5jaGlsZHJlbigiLnRoaXNpZCIpLmh0bWwoKTtwbHVzZXM9ZC5odG1sKCk7bWludXNlcz1lLmh0bWwoKTt2b3Rlcz1mLmh0bWwoKTskLnBvc3QoIi8/cmFoX2V4dGVybmFsX291dHB1dD1jYmVfaGVscGZ1bF9hamF4Iix7YXJ0aWNsZV9pZDphcnRpY2xlX2lkLHVwX2Rvd246Yix1bml0OnVuaXR9LGZ1bmN0aW9uKGcpe2QuaHRtbChwYXJzZUludChwbHVzZXMpK2cucGx1cyk7ZS5odG1sKHBhcnNlSW50KG1pbnVzZXMpK2cubWludXMpO2MuZmluZCgiLnZhbHVlIikuaHRtbChnLnNjb3JlKTtmLmh0bWwocGFyc2VJbnQodm90ZXMpKzEpO2EuY2hpbGRyZW4oIi5kaXN0ZXh0IikucmVtb3ZlKCk7aWYoYj09InVwIil7YS5wYXJlbnQoKS5zaWJsaW5ncygiLm1pbnVzIikuZmluZCgiLmRpc3RleHQiKS5yZW1vdmUoKX1lbHNle2EucGFyZW50KCkuc2libGluZ3MoIi5wbHVzIikuZmluZCgiLmRpc3RleHQiKS5yZW1vdmUoKX19LCJqc29uIil9JCgiLmhlbHBmdWxwbHVzIikuY2xpY2soZnVuY3Rpb24oKXthKCQodGhpcyksInVwIik7cmV0dXJuIGZhbHNlfSk7JCgiLmhlbHBmdWxtaW51cyIpLmNsaWNrKGZ1bmN0aW9uKCl7YSgkKHRoaXMpLCJkb3duIik7cmV0dXJuIGZhbHNlfSl9KQ==\" ) ;\r\n    $js = str_replace( \"/?rah_external_output\", hu.\"?rah_external_output\", $js ) ;\r\n    safe_upsert( \'txp_form\'\r\n               , \"`Form`=\'$code\'\r\n               ,  `type`=\'misc\'\"\r\n               , \"`name`=\'rah_eo_cbe_helpful_ajax\'\"\r\n               ) ;\r\n    safe_upsert( \'txp_form\'\r\n               , \"`Form`=\'$js\'\r\n               ,  `type`=\'misc\'\"\r\n               , \"`name`=\'rah_eo_cbe_helpful_js\'\"\r\n               ) ;\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_enabled()\r\n{\r\n    @$hlp_prefs = cbe_helpful_get_prefs() ;\r\n\r\n    $rs = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'rah_external_output\'\") ;\r\n    if( !$rs || version_compare( $rs[\'version\'], \"1.0\" ) < 0 )\r\n    {\r\n        safe_update( \'txp_plugin\', \"status = 0\", \"name = \'cbe_helpful\'\" ) ;\r\n        return( \"Plugin NON initialisé : rah_external_output v1.0 MINIMUM requis\" ) ;\r\n    }\r\n\r\n    /* Create tables */\r\n    cbe_hlp_create_tables() ;\r\n\r\n    /* If first install, populate data tables from wlk_helpful if active */\r\n    if( ! isset( $hlp_prefs[\'ipcheck\'] )\r\n       && safe_row( \"version\",\"txp_plugin\",\"status = 1 AND name=\'wlk_helpful\'\" )\r\n       && mysql_num_rows( safe_query( \"show tables like \'%wlk%\'\" ) )\r\n      )\r\n    {\r\n        $dmigr = date( \'Y-m-d H:i:s\' ) ;\r\n        $rs = safe_rows_start( \'*\', \'txp_wlk_helpful\', \'NOT (`plus` < 1 AND `minus` < 1) ORDER BY `id` ASC\' );\r\n        if( count( $rs ) )\r\n        {\r\n            while( $a = nextRow( $rs ) )\r\n            {\r\n                extract( $a ) ;\r\n                safe_insert( \"cbe_helpful_votes\"\r\n                           , \"`textpattern_id`=\'$textpattern_id\'\r\n                           , `plus`=\'$plus\'\r\n                           , `minus`=\'$minus\'\r\n                           , `ip`=\'$ip\'\r\n                           , `dvote`=\'$dmigr\'\"\r\n                           ) ;\r\n            }\r\n        }\r\n\r\n        safe_query( \"INSERT INTO \".safe_pfx(\'cbe_helpful_counts\').\r\n                    \" SELECT `textpattern_id`, sum(`plus`)-sum(`minus`) as count, sum(`plus`) as pluses, sum(`minus`) as minuses\r\n                    FROM \".safe_pfx(\'cbe_helpful_votes\').\" WHERE 1 GROUP BY `textpattern_id`\"\r\n                  ) ;\r\n    }\r\n\r\n    return ;\r\n}\r\n\r\nfunction cbe_helpful_deleted()\r\n{\r\n    safe_delete( \'txp_form\', \"`name`=\'rah_eo_cbe_helpful_ajax\'\" ) ;\r\n    safe_delete( \'txp_form\', \"`name`=\'rah_eo_cbe_helpful_js\'\" ) ;\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_votes\')  );\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_counts\') );\r\n    safe_query( \'DROP TABLE IF EXISTS \' . safe_pfx(\'cbe_helpful_prefs\')  );\r\n    safe_delete( \'txp_prefs\', \"name=\'cbe_helpful_version\'\" );\r\n}\r\n\r\nfunction cbe_helpful_lifecycle( $event = \'\', $step = \'\' )\r\n{\r\n    $func = \"cbe_helpful_$step\" ;\r\n    if( is_callable( $func ) )\r\n        return( $func() ) ;\r\n}\r\n\r\n/* ==================== Client side ==================== */\r\n\r\n/**\r\n ** Utilities\r\n **/\r\n\r\nfunction _cbe_helpful_computescore( $pluses, $minuses )\r\n{\r\n    global $hlp_prefs ;\r\n\r\n    /* ATTENTION s\'il y a du changement ici: le script ajax doit aussi être revu de la même façon */\r\n    if( ($unit=$hlp_prefs[\'finalrate\']) === \'simple\' )\r\n        $score = $pluses - $minuses ;\r\n    else\r\n    {\r\n        $score = ( ($votes=$pluses+$minuses) > 0 ) ? $pluses/$votes : 0 ;\r\n        $score = ($unit === \'ratio\') ? sprintf( \"%4.2f\"   , round( $score, 2 ) )\r\n                                     : sprintf( \"%5.2f %%\", round( 100.0*$score, 2 ) ) ;\r\n    }\r\n\r\n    return( $score ) ;\r\n}\r\n\r\nfunction _cbe_helpful_getrates( $art_id )\r\n{\r\n    global $hlp_prefs ;\r\n\r\n    $count = $pluses = $minuses = \"0\" ;\r\n    $ip    = $_SERVER[\'REMOTE_ADDR\'] ;\r\n\r\n    $canvote = safe_count( \'cbe_helpful_votes\', \"textpattern_id=\'$art_id\' AND ip=\'$ip\' AND ADDDATE(dvote, INTERVAL {$hlp_prefs[\'ipcheck\']} HOUR) > NOW()\" ) == 0 ;\r\n\r\n    if( count( $scores = safe_row( \'count, pluses, minuses\'\r\n                                 , \'cbe_helpful_counts\', \"textpattern_id=\'$art_id\'\" ) ) )\r\n        extract( $scores );\r\n\r\n    $score = _cbe_helpful_computescore( $pluses, $minuses ) ;\r\n\r\n    return( compact( \"canvote\", \"count\", \"pluses\", \"minuses\", \"score\" ) ) ;\r\n}\r\n\r\nfunction _cbe_helpful_yesno( $atts, $switch )\r\n{\r\n    global $hlp_prefs, $pluses, $minuses, $canvote, $showonly, $bIslist ;\r\n\r\n    $votable = ( !$showonly && is_bool($switch) ) ? $canvote : false ;\r\n\r\n    $jsclass     = $votable ? ( $switch ? \"plus\"        : \"minus\" ) : \"\" ;\r\n    $jsvoteclass = $votable ? ( $switch ? \"helpfulplus\" : \"helpfulminus\" ) : \"\" ;\r\n    $jscount     = $votable ? \"thecount\" : \"\" ;\r\n    $jstext      = $votable ? \"distext\" : \"\" ;\r\n    extract( lAtts( array ( \'wraptag\'       => \'\'\r\n                          , \'class\'         => \'\'\r\n                          , \'voteclass\'     => \'\'\r\n                          , \'showonly\'      => isset( $showonly ) ? $showonly : \'0\'\r\n                          , \'labelshowonly\' => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $votable )\r\n    {   /* clickable yes/no */\r\n        $voteclass = $voteclass != \'\' ? \"$jsvoteclass $voteclass\" : $jsvoteclass ;\r\n        $switch = $switch ? \'yes\' : \'no\' ;\r\n        $out[] = doWrap( array( $switch==\'yes\' ? $pluses : $minuses ), \"span\", \'\', $jscount ) ;\r\n        $out[] = href( doTag( $hlp_prefs[$switch], \"span\", $jstext ), \'#\'\r\n                            , \' title=\"\'.$hlp_prefs[$switch.\'tooltip\'].\'\" class=\"\'.$voteclass.\'\"\' ) ;\r\n    }\r\n    elseif( ! is_bool( $switch ) )\r\n    {   /* total_votes */\r\n        $out[] = doWrap( array( $pluses + $minuses ), \"span\", \'\', \"hlp_$switch\" ) ;\r\n        if( $showonly )\r\n            $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[$switch]), \'span\' ) ;\r\n        else\r\n            $out[] = doTag( ($hlp_prefs[$switch] ? $hlp_prefs[$switch] : $labelshowonly), \'span\' ) ;\r\n    }\r\n    else\r\n    {   /* non-clickable yes/no */\r\n        $out[] = $switch ? $pluses : $minuses ;\r\n        if( $showonly )\r\n            $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[$switch]), \"span\" ) ;\r\n    }\r\n    /* == Note: $labelshowonly & $hlp_prefs[$switch] are a fallback for each other == */\r\n\r\n    return( doWrap( $out, $wraptag ? $wraptag : ( (isset( $bIslist ) && $bIslist) ? \'li\' : \'span\' ), \' \'\r\n                  , $class != \'\' ? \"$jsclass $class\" : $jsclass\r\n                  ) ) ;\r\n}\r\n\r\nfunction _cbe_helpful_foot( $html )\r\n{   /* Put specific javascript just before </body> */\r\n    $js = \'<script type=\"text/javascript\" src=\"\'.hu.\'?rah_external_output=cbe_helpful_js\"></script>\' ;\r\n    $pattern = \'/(?=(<\\/body>))/si\';\r\n    $html = preg_replace( $pattern, $js, $html );\r\n    return $html;\r\n}\r\n\r\n/**\r\n ** Available tags\r\n **/\r\n\r\nfunction cbe_helpful( $atts, $thing = \'\' )\r\n{   /* Vote widget */\r\n    static $ajax_module = false ;\r\n    assert_article() ;\r\n    if( ! $ajax_module )\r\n    {\r\n//        ob_start( array( &$this, \'_cbe_helpful_foot\' ) ) ;\r\n        ob_start( \'_cbe_helpful_foot\' ) ;\r\n        $ajax_module = true ;\r\n    }\r\n    global $thisarticle ;\r\n    $cbe_hlp_globals = array( \'hlp_prefs\', \'score\', \'pluses\', \'minuses\', \'canvote\', \'showonly\', \'bIslist\' ) ;\r\n    foreach( $cbe_hlp_globals as $cbe_hlp_global )\r\n        global $$cbe_hlp_global ;\r\n\r\n\r\n    $jsclass = \"helpfulrater\" ;\r\n    $jsunit  = \"unit\" ;\r\n    $jsthid  = \"thisid\" ;\r\n    $hlp_prefs = cbe_helpful_get_prefs() ;\r\n\r\n    extract( lAtts( array ( \'label\'      => $hlp_prefs[\'label\']\r\n                          , \'labeltag\'   => \'h2\'\r\n                          , \'labelclass\' => \'\'\r\n                          , \'wraptag\'    => \'div\'\r\n                          , \'class\'      => \'\'\r\n                          , \'showonly\'   => \'0\'\r\n                          , \'form\'       => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $art_id = $thisarticle[ \'thisid\' ] ;\r\n\r\n    extract( _cbe_helpful_getrates( $art_id ) ) ;\r\n    $class = $class != \'\' ? \"$jsclass $class\" : $jsclass ;\r\n\r\n    $parsed = array() ;\r\n\r\n    if (empty($form) && empty($thing))\r\n    {\r\n        $parsed[] = doTag( $art_id,                 \'li\', $jsthid, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = doTag( $hlp_prefs[\'finalrate\'], \'li\', $jsunit, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = cbe_helpful_score( array( \'wraptag\'=>\'li\' ) ) ;\r\n        $parsed[] = cbe_helpful_label( array( \'class\'=>\'helpful\', \'wraptag\'=>\'li\' ) ) ;\r\n        $parsed[] = cbe_helpful_yes( array( \'wraptag\'=>\'li\', \'showonly\'=>$showonly ) ) ;\r\n        $parsed[] = cbe_helpful_no ( array( \'wraptag\'=>\'li\', \'showonly\'=>$showonly ) ) ;\r\n        $parsed[] = cbe_helpful_total_votes( array( \'wraptag\'=>\'li\' ) ) ;\r\n        $out = doWrap( $parsed, \'ul\', \'\', $class ) ;\r\n    }\r\n    else\r\n    {\r\n        $bIslist  = ($wraptag == \'ul\' || $wraptag == \'ol\') ;\r\n        $parsed[] = doTag( $art_id,                 $bIslist ? \'li\' : \'span\', $jsthid, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = doTag( $hlp_prefs[\'finalrate\'], $bIslist ? \'li\' : \'span\', $jsunit, \' style=\"display:none;\"\' ) ;\r\n        $parsed[] = parse( $thing ? $thing : $form ) ;\r\n        $out = ($label ? doTag( $label, $labeltag, $labelclass ) : \'\')\r\n             . doWrap( $parsed, $wraptag ? $wraptag : \'div\', \'\', $class ) ;\r\n    }\r\n\r\n    foreach( $cbe_hlp_globals as $cbe_hlp_global ) {\r\n        unset( $$cbe_hlp_global ) ;\r\n        unset( $GLOBALS[$cbe_hlp_global] ) ;\r\n    }\r\n\r\n    return( $out ) ;\r\n}\r\n\r\nfunction cbe_helpful_label( $atts )\r\n{\r\n    global $hlp_prefs ;\r\n    extract( lAtts( array ( \'wraptag\'    => \'\'\r\n                          , \'class\'      => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    if( $hlp_prefs === NULL )\r\n        $hlp_prefs = cbe_helpful_get_prefs() ;\r\n    return( doTag( $hlp_prefs[\'label\'], $wraptag, $class ) ) ;\r\n}\r\n\r\nfunction cbe_helpful_score( $atts )\r\n{\r\n    global $hlp_prefs, $score, $canvote, $showonly, $bIslist ;\r\n\r\n    if( $score === NULL )\r\n    {\r\n        assert_article() ;\r\n        ob_start( array( &$this, \'_cbe_helpful_foot\' ) ) ;\r\n        global $thisarticle ;\r\n        $hlp_prefs = cbe_helpful_get_prefs() ;\r\n        extract( _cbe_helpful_getrates( $thisarticle[ \'thisid\' ] ) ) ;\r\n        $temp_score = $score ;\r\n        $score = NULL ;\r\n    }\r\n\r\n    $jsclass = $canvote ? \"value\" : \"\" ;\r\n    extract( lAtts( array ( \'wraptag\'       => \'\'\r\n                          , \'class\'         => \'\'\r\n                          , \'labelshowonly\' => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $out = array() ;\r\n\r\n    if( $showonly )\r\n        $out[] = doTag( ($labelshowonly ? $labelshowonly : $hlp_prefs[\'score\']), \'span\' ) ;\r\n    else\r\n        $out[] = doTag( ($hlp_prefs[\'score\'] ? $hlp_prefs[\'score\'] : $labelshowonly), \'span\' ) ;\r\n    /* == Note: $labelshowonly & $hlp_prefs[\'score\'] are a fallback for each other == */\r\n\r\n    $out[] = doWrap( array( $score !== NULL ? $score : $temp_score )\r\n                   , \'span\', \'\'\r\n                   , $jsclass, \'\'\r\n                   , \" title=\\\"{$hlp_prefs[\'score\']}\\\"\"\r\n                   ) ;\r\n\r\n    return( doWrap( $out, (isset( $bIslist ) && $bIslist) ? \'li\' : $wraptag, \'\', $class ) ) ;\r\n}\r\n\r\nfunction cbe_helpful_yes( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, true ) ) ;\r\n}\r\nfunction cbe_helpful_no( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, false ) ) ;\r\n}\r\nfunction cbe_helpful_total_votes( $atts )\r\n{\r\n    return( _cbe_helpful_yesno( $atts, \"votes\" ) ) ;\r\n}\r\n\r\n/* Tops and flops */\r\nfunction cbe_helpful_list( $atts, $thing = \'\' )\r\n{\r\n    global $thisarticle, $prefs, $hlp_prefs, $score, $pluses, $minuses, $canvote, $showonly, $bIslist ;\r\n    extract( lAtts( array ( \'label\'      => \'\'\r\n                          , \'labeltag\'   => \'h2\'\r\n                          , \'labelclass\' => \'\'\r\n                          , \'wraptag\'    => \'ol\'\r\n                          , \'break\'      => \'li\'\r\n                          , \'class\'      => __FUNCTION__\r\n                          , \'breakclass\' => \'\'\r\n                          , \'form\'       => \'\'\r\n                          , \'which\'      => \'top\'\r\n                          , \'order\'      => \'desc\'\r\n                          , \'limit\'      => \'5\'\r\n                          , \'period\'     => \'\'\r\n                          )\r\n                  , $atts\r\n                  )\r\n           ) ;\r\n\r\n    $hlp_prefs = cbe_helpful_get_prefs() ;\r\n    $timelimit = \'\' ;\r\n    $sortorder = $which==\'top\' ? \'desc\' : \'asc\' ;\r\n    $sortwhat  = $hlp_prefs[\'finalrate\'] === \'simple\'\r\n               ? \"helpful.count $sortorder, helpful.pluses $sortorder\"\r\n               : \"pct $sortorder, helpful.pluses $sortorder\" ;\r\n\r\n    if( $period !== \'\' )\r\n    {\r\n        $period = str_replace( array( \"today\", \"yesterday\" ), array( \"this day\", \"last day\"), $period ) ;\r\n        preg_match( \'/(?P<when>\\w+) (?P<what>\\w+)/\', $period, $matches ) ;\r\n        $aWhat = array( \'year\'  => \"1/1\"\r\n                      , \'month\' => \"midnight first day of\"\r\n                      , \'week\'  => \"midnight\"\r\n                      , \'day\'   => \"midnight\"\r\n                      ) ;\r\n        $aWhen = array( \'this\' => array( \'beg\' => \"this\", \'end\' => \"next\" )\r\n                      , \'last\' => array( \'beg\' => \"last\", \'end\' => \"this\" )\r\n                      ) ;\r\n        if( isset( $aWhat[ $matches[ \'what\' ] ] ) && isset( $aWhen[ $matches[ \'when\' ] ] ) )\r\n        {\r\n            $timefrom = date( \"Y-m-d H:i:s\", strtotime( \"{$aWhat[ $matches[ \'what\' ] ]} {$aWhen[ $matches[ \'when\' ] ][ \'beg\' ]} {$matches[ \'what\' ]}\" ) ) ;\r\n            $timeto = date( \"Y-m-d H:i:s\", strtotime( \"-1 second {$aWhat[ $matches[ \'what\' ] ]} {$aWhen[ $matches[ \'when\' ] ][ \'end\' ]} {$matches[ \'what\' ]}\" ) ) ;\r\n            $timelimit = \" WHERE helpful.dvote BETWEEN \'$timefrom\' AND \'$timeto\'\" ;\r\n            $sqlRows = \'SELECT *, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, sum(helpful.plus) AS pluses, sum(helpful.minus) AS minuses, sum(helpful.plus)-sum(helpful.minus) AS count, sum(helpful.plus)/(sum(helpful.plus)+sum(helpful.minus)) AS pct FROM \'.safe_pfx(\'textpattern\').\' AS txp JOIN \'.safe_pfx(\'cbe_helpful_votes\').\' AS helpful ON txp.ID=helpful.textpattern_id\'.$timelimit.\' GROUP BY helpful.textpattern_id ORDER BY \'.str_replace(\"helpful.\", \"\", $sortwhat).\' LIMIT 0, \'.intval($limit) ;\r\n        }\r\n        else\r\n            trigger_error( \"Bad period format\" ) ;\r\n    }\r\n    else\r\n        $sqlRows = \'SELECT *, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, helpful.pluses/(helpful.pluses+helpful.minuses) AS pct FROM \'.safe_pfx(\'textpattern\').\' AS txp JOIN \'.safe_pfx(\'cbe_helpful_counts\').\' AS helpful ON txp.ID=helpful.textpattern_id ORDER BY \'.$sortwhat.\' LIMIT 0, \'.intval($limit) ;\r\n\r\n    $rs = isset( $sqlRows ) ? getRows( $sqlRows ) : false ;\r\n    if( $rs === false || !count( $rs ) )\r\n        return ;\r\n\r\n    if( $sortorder != $order )\r\n        $rs = array_reverse( $rs ) ;\r\n\r\n    $out = array() ;\r\n    $old_article  = $thisarticle ;\r\n    $old_score    = $score ;\r\n    $old_pluses   = $pluses ;\r\n    $old_minuses  = $minuses ;\r\n    $old_canvote  = $canvote ;\r\n    $old_showonly = $showonly ;\r\n\r\n    $canvote  = false ;\r\n    $showonly = true  ;\r\n    \r\n    foreach( $rs as $a )\r\n    {\r\n        $a[\'Title\'] = ($prefs[\'title_no_widow\']) ? noWidow(escape_title($a[\'Title\']))\r\n                                                 : escape_title($a[\'Title\']);\r\n        $a[\'uPosted\'] = $a[\'posted\'] ;\r\n\r\n        if (empty($form) && empty($thing))\r\n        {\r\n            $out[] = href($a[\'Title\'], permlinkurl($a));\r\n        }\r\n        else\r\n        {\r\n            populateArticleData($a);\r\n            $pluses  = $a[ \'pluses\' ] ;\r\n            $minuses = $a[ \'minuses\' ] ;\r\n            $score = _cbe_helpful_computescore( $pluses, $minuses ) ;\r\n            $out[] = ($thing) ?  parse($thing) : parse_form($form);\r\n        }\r\n    }\r\n\r\n    $thisarticle = $old_article ;\r\n    $score       = $old_score ;\r\n    $pluses      = $old_pluses ;\r\n    $minuses     = $old_minuses ;\r\n    $canvote     = $old_canvote ;\r\n    $showonly    = $old_showonly ;\r\n\r\n    if( !count( $out ) )\r\n        return ;\r\n\r\n    return( ($label ? doTag( $label, $labeltag, $labelclass ) : \'\')\r\n          . doWrap( $out, $wraptag, $break, $class, $breakclass ) ) ;\r\n}\r\n', '61eac35bab8296b68eed81c600288054', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_if_variable', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.2', 'if_variable for a list of values', '<div id=\"cbe-plugin-help\">\n<h1>cbe_if_variable</h1>\n<p>Evaluate your variables in a if_custom_fields manner.</p>\n<p>Provide two additionnal tags: <code>cbe_if_variable_exists</code> and <code>cbe_if_variable_empty</code></p>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#tags-and-attributes\">Tags and attributes</a></li>\n  <li><a href=\"#examples\">Examples</a></li>\n  <li><a href=\"#summary\">Summary</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n\n<p>Download from the <a href=\"http://www.clairebrione.com/cbe_if_variable\">plugin page</a>.</p>\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36368\">forum thread</a> for support.</p>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>verify the variable among a separated list of values (<a href=\"#ex-basic\">example</a>) ...</li>\n  <li>... or a single value like <code>&lt;txp:if_variable /&gt;</code></li>\n  <li>the delimiter can be changed (<a href=\"#ex-delim\">example</a>)</li>\n  <li>the variable can also hold a separated list, thus operates like <code>&lt;txp:if_custom_field /&gt;</code></li>\n  <li>supports <code>&lt;txp:else /&gt;</code></li>\n</ul>\n\n<h2 id=\"tags-and-attributes\">Tags and attributes</h2>\n\n<h3>&lt;txp:cbe_if_variable_exists&gt; ... &lt;/txp:cbe_if_variable_exists&gt;</h3>\n<p>Used as a container. Checks for existence only.</p>\n\n<table class=\"list\">\n  <tr>\n    <th>Attribute (<b>required</b>)</th> <th>Description and values</th> <th>Example</th>\n  </tr>\n  <tr>\n    <td><code><b>name</b></code></td>\n    <td>variable name</td>\n    <td><code>name=\"myvar\"</code></td>\n  </tr>\n</table>\n\n<h3>&lt;txp:cbe_if_variable_empty&gt; ... &lt;/txp:cbe_if_variable_empty&gt;</h3>\n<p>Used as a container. Checks for emptiness. For a relevant result the variable must exist though the plugin won\'t warn if not: it will just return <code>false</code>.</p>\n\n<table class=\"list\">\n  <tr>\n    <th>Attribute (<b>required</b>)</th> <th>Description and values</th> <th>Example</th>\n  </tr>\n  <tr>\n    <td><code><b>name</b></code></td>\n    <td>variable name</td>\n    <td><code>name=\"myvar\"</code></td>\n  </tr>\n</table>\n\n<h3>&lt;txp:cbe_if_variable&gt; ... &lt;/txp:cbe_if_variable&gt;</h3>\n<p>Used as a container. Checks existence, non emptiness, and values.</p>\n\n<table class=\"list\">\n  <tr>\n    <th>Attribute (bold if <b>required</b>)</th> <th>Description and values</th> <th>Example</th>\n  </tr>\n  <tr>\n    <td><code><b>name</b></code></td>\n    <td>variable name</td>\n    <td><code>name=\"myvar\"</code></td>\n  </tr>\n  <tr>\n    <td><code>separator</code></td>\n    <td>separator used in the variable if you want to consider it as a list of values. Defaults to <code>delim</code>.</td>\n    <td><code>separator=\"/\"</code></td>\n  </tr>\n  <tr>\n    <td><code>value</code></td>\n    <td>list of values to look into.<br />If ommitted, ignores all other parameters and returns true if the variable is defined and has a value (i.e. returns false if not defined or is empty).</td>\n    <td><code>value=\"val1,val2,val3\"</code></td>\n  </tr>\n  <tr>\n    <td><code>delim</code></td>\n    <td>delimiter for the list of values, defaults to comma (<code>,</code>)</td>\n    <td><code>delim=\"|\"</code></td>\n  </tr>\n  <tr>\n    <td><code>match</code></td>\n    <td>how to compare, defaults to <code>any</code>\n        <br /><b>any</b>: checks that at least one of the variable\'s term is present at any place in <code>value</code>\n        <br /><b>all</b>: checks that each variable\'s term appears at any place in <code>value</code> (this is nonreciprocical)\n        <br /><b>pattern</b>: try to match the variable\'s value with the regular expression given in <code>value</code>. <code>delim</code> and <code>separator</code> are ignored.\n        </td>\n    <td><code>match=\"all\"</code></td>\n  </tr>\n</table>\n\n<h2 id=\"examples\">Examples</h2>\n\n<ul>\n  <li><a href=\"#ex-basic\">Basic usage</a></li>\n  <li><a href=\"#ex-delim\">Change the delimiter</a></li>\n  <li><a href=\"#ex-practical\">Practical application</a></li>\n</ul>\n\n<h3 id=\"ex-basic\">Basic usage</h3>\n<pre><code>&lt;txp:cbe_if_variable name=\"myvar\" value=\"one,two,three\"&gt;\n  &lt;p&gt;Variable contains one, or two, or three&lt;/p&gt;\n&lt;txp:else /&gt;\n  &lt;p>Variable contains another value&lt;/p&gt;\n&lt;/txp:cbe_if_variable&gt;</code></pre>\n\n<p>... should be self explanatory.</p>\n\n<h3 id=\"ex-delim\">Change the delimiter</h3>\n<pre><code>&lt;txp:cbe_if_variable name=\"myvar\" value=\"four|five|six\" delim=\"|\"&gt;\n  &lt;p&gt;Variable contains four, or five, or six&lt;/p&gt;\n&lt;txp:else /&gt;\n  &lt;p>Variable contains another value&lt;/p&gt;\n&lt;/txp:cbe_if_variable&gt;</code></pre>\n\n<p>... should be obvious too.</p>\n\n<h3 id=\"ex-practical\">Practical application</h3>\n<pre><code>&lt;txp:variable name=\"public-sections\" value=\",about,contact\" /&gt;\n&lt;txp:article&gt;\n  &lt;txp:variable name=\"article-section\" value=\'&lt;txp:section /&gt;\' /&gt;\n  &lt;txp:cbe_if_variable name=\"article-section\" value=\'&lt;txp:variable name=\"public-sections\" /&gt;\'&gt;\n    ... display the article ...\n  &lt;txp:else /&gt;\n    ... display something else, or do nothing ...\n  &lt;/txp:cbe_if_variable&gt;\n&lt;/txp:article&gt;</code></pre>\n\n<p>Display an article if it belongs to an authorized section.</p>\n\n<h2 id=\"summary\">Summary</h2>\n<p>Here is what the three tags return in various situations (simplified syntax) :</p>\n\n<pre><code><b>(nothing)</b>\ncbe_if_variable_exists name=\"myvar\" .. FALSE\ncbe_if_variable_empty name=\"myvar\" ... FALSE\ncbe_if_variable name=\"myvar\" ......... FALSE\n\n===================\n\n<b>&lt;txp:variable name=\"myvar\"&gt;&lt;/txp:variable&gt; or &lt;txp:variable name=\"myvar\" value=\"\" /&gt;</b>\ncbe_if_variable_exists name=\"myvar\" ............. TRUE\ncbe_if_variable_empty name=\"myvar\" .............. TRUE\ncbe_if_variable name=\"myvar\" .................... FALSE\ncbe_if_variable name=\"myvar\" value=\"\" ........... TRUE\ncbe_if_variable name=\"myvar\" value=\"whatever\" ... FALSE\ncbe_if_variable name=\"myvar\" value=\",whatever\" .. TRUE\n\n===================\n\n<b>&lt;txp:variable name=\"myvar\" value=\"something\" /&gt;</b>\ncbe_if_variable_exists name=\"myvar\" .. TRUE\ncbe_if_variable_empty name=\"myvar\" ... FALSE\ncbe_if_variable name=\"myvar\" ......... TRUE\n                                                match = \"any\"     \"all\"\ncbe_if_variable name=\"myvar\" value=\"\" ................. FALSE ... FALSE\ncbe_if_variable name=\"myvar\" value=\"something\" ........ TRUE .... TRUE\ncbe_if_variable name=\"myvar\" value=\"something,other\" .. TRUE .... TRUE\ncbe_if_variable name=\"myvar\" value=\"anything,other\" ... FALSE ... FALSE\n\n===================\n\n<b>&lt;txp:variable name=\"myvar\" value=\"other, something\" /&gt;</b>\n                                                match = \"any\"     \"all\"\ncbe_if_variable name=\"myvar\" value=\"\" ................. FALSE ... FALSE\ncbe_if_variable name=\"myvar\" value=\"something\" ........ TRUE .... FALSE\nbut : cbe_if_variable name=\"myvar\" value=\"something\" separator=\"|\" is FALSE\ncbe_if_variable name=\"myvar\" value=\"something,other\" .. TRUE .... TRUE\ncbe_if_variable name=\"myvar\" value=\"other\" ............ TRUE .... FALSE\ncbe_if_variable name=\"myvar\" value=\"anything,other\" ... TRUE .... FALSE\n\n===================\n\n<b>&lt;txp:variable name=\"phrase\" value=\"here, there and everywhere\" /&gt;</b>\ncbe_if_variable name=\"phrase\" value=\"here, there and everywhere\" ........... TRUE\ncbe_if_variable name=\"phrase\" value=\"here, there and everywhere\" delim=\"\" .. TRUE\ncbe_if_variable name=\"phrase\" value=\"here there and everywhere\" ............ FALSE\ncbe_if_variable name=\"phrase\" value=\"here there and everywhere\" delim=\"\" ... FALSE\ncbe_if_variable name=\"phrase\" value=\"here there and everywhere\" delim=\"/\" .. FALSE\ncbe_if_variable name=\"phrase\" value=\"there\" match=\"pattern\" ................ TRUE\ncbe_if_variable name=\"phrase\" value=\"^there\" match=\"pattern\" ............... FALSE\n</code></pre>\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<ul>\n  <li>01 Jul 11 - v0.2 -\n<br />\"match\" attribute added, thanks <a href=\"http://forum.textpattern.com/profile.php?id=15147\">sacripant</a> !\n<br />don\'t reinvent the wheel and use existing code\n<br />added examples\n</li>\n  <li>30 Jun 11 - v0.1.1 - Example fix</li>\n  <li>29 Jun 11 - v0.1 - Initial release</li>\n</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n.list th{\n	text-align:left;\n	color:#fff;\n	background:#555;\n	padding:6px 16px 6px 4px;\n	border-right:1px solid #999;\n	font-weight:normal;\n}\n.list th a{\n	color:#fff;\n	display:block;\n}\n.list th h2{\n	margin:0;\n	font-size:11px;\n}\n.list td{\n	padding:6px 4px;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n** cbe_if_variable v0.2\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 29 Jun 2011 - Initial release\r\n** 0.1.1 30 Jun 2011 - Example fix\r\n** 0.2 - 01 Jul 2011 - \"match\" attribute added, thanks sacripant !\r\n**                     don\'t reinvent the wheel and use existing code\r\n**                     added examples\r\n**\r\n ************************************************************************/\r\n\r\n/*\r\n** tool functions\r\n*/\r\nfunction _cbe_assert_variable( $name )\r\n{\r\n    if( empty( $name ) )\r\n    {\r\n        trigger_error(gTxt(\'variable_name_empty\'));\r\n        return( false ) ;\r\n    }\r\n\r\n    return( true ) ;\r\n}\r\n\r\nfunction _cbe_do_array( $val, $sep )\r\n{\r\n    return( $sep !==\'\' ? do_list( $val, $sep ) : array( trim( $val ) ) ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_any( $params )\r\n{   /* ANY : search at least one \"needle\" term in \"value\" at any place */\r\n    extract( $params ) ;\r\n    return( count( array_intersect( _cbe_do_array( $needle, $separator )\r\n                                  , _cbe_do_array( $value , $delim )\r\n                                  ) ) > 0 ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_all( $params )\r\n{   /* ALL : search all \"needle\" terms in \"value\" at any place, nonreciprocical */\r\n    extract( $params ) ;\r\n    $aNeedles = _cbe_do_array( $needle, $separator ) ;\r\n    return( count( array_intersect( $aNeedles, _cbe_do_array( $value, $delim ) ) ) == count( $aNeedles ) ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_pattern( $params )\r\n{   /* PATTERN (regexp) : try to match \"needle\" term with \"value\" regexp */\r\n    /* from publish/taghandlers.php if_custom_field() */\r\n    extract( $params ) ;\r\n    $dlmPool = array(\'/\', \'@\', \'#\', \'~\', \'`\', \'|\', \'!\', \'%\');\r\n    $dlm = array_merge(array_diff($dlmPool, preg_split(\'//\', $value, -1)));\r\n    $dlm = (count($dlm) > 0) ? $dlm[0].$value.$dlm[0] : $value;\r\n    return( preg_match($dlm, $needle) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable\r\n** false if the variable doesn\'t exist (unset) or has no value (empty) or is none of the values provided\r\n** true in other cases,\r\n**             acts like if_variable or if_custom_field depending on matching type and comparated values\r\n*/\r\nfunction cbe_if_variable($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    $options = \'all, any, pattern\' ;\r\n\r\n    extract(lAtts(array(\r\n        \'name\'      => \'\',\r\n        \'value\'     => \'\',\r\n        \'delim\'     => \',\',\r\n        \'match\'     => \'any\',\r\n        \'separator\' => \'delim\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    $separator = ($separator == \'delim\' ) ? $delim : $separator ;\r\n\r\n    if( isset( $variable[$name] ) )\r\n    {\r\n        if( ! isset( $atts[\'value\'] ) )\r\n            $x = ! empty( $variable[$name] ) ;\r\n        else\r\n        {\r\n            if( ! _cbe_ifvar_match_any( array( \'needle\' => $match, \'value\' => $options, \'delim\' => \',\', \'separator\' => \'\' ) ) )\r\n            {\r\n                trigger_error(gTxt(\'invalid_attribute_value\', array(\'{match}\' => $match)));\r\n                return( false ) ;\r\n            }\r\n\r\n            $needle = $variable[$name] ;\r\n            $x = call_user_func( \'_cbe_ifvar_match_\'.$match\r\n                                     , $match == \'pattern\'\r\n                                     ? compact( \"needle\", \"value\" )\r\n                                     : compact( \"needle\", \"value\", \"separator\", \"delim\" )\r\n                                     ) ;\r\n        }\r\n    }\r\n    else\r\n        $x = false ;\r\n\r\n    return parse( EvalElse( $thing, $x ) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable_empty\r\n** true if a variable is defined AND has a no value\r\n*/\r\nfunction cbe_if_variable_empty($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    extract(lAtts(array(\r\n        \'name\'    => \'\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    return parse( EvalElse( $thing, isset( $variable[$name] ) && empty( $variable[$name] ) ) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable_exists\r\n** true if a variable isn\'t defined\r\n*/\r\nfunction cbe_if_variable_exists($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    extract(lAtts(array(\r\n        \'name\'    => \'\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    return parse( EvalElse( $thing, isset( $variable[$name] ) ) ) ;\r\n}\r\n', '/*\r\n** cbe_if_variable v0.2\r\n** Claire Brione - http://www.clairebrione.com/\r\n**\r\n** 0.1 - 29 Jun 2011 - Initial release\r\n** 0.1.1 30 Jun 2011 - Example fix\r\n** 0.2 - 01 Jul 2011 - \"match\" attribute added, thanks sacripant !\r\n**                     don\'t reinvent the wheel and use existing code\r\n**                     added examples\r\n**\r\n ************************************************************************/\r\n\r\n/*\r\n** tool functions\r\n*/\r\nfunction _cbe_assert_variable( $name )\r\n{\r\n    if( empty( $name ) )\r\n    {\r\n        trigger_error(gTxt(\'variable_name_empty\'));\r\n        return( false ) ;\r\n    }\r\n\r\n    return( true ) ;\r\n}\r\n\r\nfunction _cbe_do_array( $val, $sep )\r\n{\r\n    return( $sep !==\'\' ? do_list( $val, $sep ) : array( trim( $val ) ) ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_any( $params )\r\n{   /* ANY : search at least one \"needle\" term in \"value\" at any place */\r\n    extract( $params ) ;\r\n    return( count( array_intersect( _cbe_do_array( $needle, $separator )\r\n                                  , _cbe_do_array( $value , $delim )\r\n                                  ) ) > 0 ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_all( $params )\r\n{   /* ALL : search all \"needle\" terms in \"value\" at any place, nonreciprocical */\r\n    extract( $params ) ;\r\n    $aNeedles = _cbe_do_array( $needle, $separator ) ;\r\n    return( count( array_intersect( $aNeedles, _cbe_do_array( $value, $delim ) ) ) == count( $aNeedles ) ) ;\r\n}\r\n\r\nfunction _cbe_ifvar_match_pattern( $params )\r\n{   /* PATTERN (regexp) : try to match \"needle\" term with \"value\" regexp */\r\n    /* from publish/taghandlers.php if_custom_field() */\r\n    extract( $params ) ;\r\n    $dlmPool = array(\'/\', \'@\', \'#\', \'~\', \'`\', \'|\', \'!\', \'%\');\r\n    $dlm = array_merge(array_diff($dlmPool, preg_split(\'//\', $value, -1)));\r\n    $dlm = (count($dlm) > 0) ? $dlm[0].$value.$dlm[0] : $value;\r\n    return( preg_match($dlm, $needle) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable\r\n** false if the variable doesn\'t exist (unset) or has no value (empty) or is none of the values provided\r\n** true in other cases,\r\n**             acts like if_variable or if_custom_field depending on matching type and comparated values\r\n*/\r\nfunction cbe_if_variable($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    $options = \'all, any, pattern\' ;\r\n\r\n    extract(lAtts(array(\r\n        \'name\'      => \'\',\r\n        \'value\'     => \'\',\r\n        \'delim\'     => \',\',\r\n        \'match\'     => \'any\',\r\n        \'separator\' => \'delim\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    $separator = ($separator == \'delim\' ) ? $delim : $separator ;\r\n\r\n    if( isset( $variable[$name] ) )\r\n    {\r\n        if( ! isset( $atts[\'value\'] ) )\r\n            $x = ! empty( $variable[$name] ) ;\r\n        else\r\n        {\r\n            if( ! _cbe_ifvar_match_any( array( \'needle\' => $match, \'value\' => $options, \'delim\' => \',\', \'separator\' => \'\' ) ) )\r\n            {\r\n                trigger_error(gTxt(\'invalid_attribute_value\', array(\'{match}\' => $match)));\r\n                return( false ) ;\r\n            }\r\n\r\n            $needle = $variable[$name] ;\r\n            $x = call_user_func( \'_cbe_ifvar_match_\'.$match\r\n                                     , $match == \'pattern\'\r\n                                     ? compact( \"needle\", \"value\" )\r\n                                     : compact( \"needle\", \"value\", \"separator\", \"delim\" )\r\n                                     ) ;\r\n        }\r\n    }\r\n    else\r\n        $x = false ;\r\n\r\n    return parse( EvalElse( $thing, $x ) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable_empty\r\n** true if a variable is defined AND has a no value\r\n*/\r\nfunction cbe_if_variable_empty($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    extract(lAtts(array(\r\n        \'name\'    => \'\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    return parse( EvalElse( $thing, isset( $variable[$name] ) && empty( $variable[$name] ) ) ) ;\r\n}\r\n\r\n/*\r\n** cbe_if_variable_exists\r\n** true if a variable isn\'t defined\r\n*/\r\nfunction cbe_if_variable_exists($atts, $thing = NULL)\r\n{\r\n    global $variable;\r\n    extract(lAtts(array(\r\n        \'name\'    => \'\'\r\n    ), $atts));\r\n\r\n    if( ! _cbe_assert_variable( $name ) )\r\n        return ;\r\n\r\n    return parse( EvalElse( $thing, isset( $variable[$name] ) ) ) ;\r\n}\r\n', 'becdd73c0fd14d4cd8c4886c3dd83c1e', '', '', 0, 5, 0),
('cbe_if_article_released', 0, 'Claire Brione', 'http://www.clairebrione.com/', '0.1', 'Tests if an article is already released, or will be in the future', '<div id=\"cbe-plugin-help\">\n<h1>cbe_if_article_released</h1>\n\n<h2>Table of contents</h2>\n<ul>\n  <li><a href=\"#dl-install-supp\">Download, installation, support</a></li>\n  <li><a href=\"#features\">Features</a></li>\n  <li><a href=\"#tags-and-attributes\">Tags and attributes</a></li>\n  <li><a href=\"#examples\">Examples</a></li>\n  <li><a href=\"#changelog\">Changelog</a></li>\n</ul>\n\n<h2 id=\"dl-install-supp\">Download, installation, support</h2>\n\n<p>Download from the <a href=\"http://www.clairebrione.com/cbe_if_article_released\">plugin page</a>.</p>\n\n<p>Copy/paste in the Admin > Plugins tab to install or uninstall, activate or desactivate.</p>\n\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36344\">forum thread</a> for support.</p>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n  <li>support for <code>&lt;txp:else /&gt;</code></li>\n</ul>\n\n<h2 id=\"tags-and-attributes\">Tags and attributes</h2>\n<p>Two tags that are opposite to each other.</p>\n<p>Always used as a container tag, in an individual article or article list context.</p>\n\n<h3>&lt;txp:cbe_if_article_released&gt; ... &lt;/txp:cbe_if_article_released&gt;</h3>\n<p>Evaluates to <code>true</code> if the article\'s publishing date is past.</p>\n\n<h3>&lt;txp:cbe_if_article_future&gt; ... &lt;/txp:cbe_if_article_future&gt;</h3>\n<p>Evaluates to <code>true</code> if the article\'s publishing date is future.</p>\n\n<h2 id=\"examples\">Examples</h2>\n\n<p>Caution : the plugin doesn\'t care about the article\'s status (live, sticky) or expiration.</p>\n\n<h3 id=\"ex-released\">Released article</h3>\n<pre><code>&lt;txp:cbe_if_article_released&gt;\n  &lt;txp:output_form form=\"comments_display\" /&gt;\n&lt;/txp:cbe_if_article_released&gt;</code></pre>\n\n<p>Shows comments and comment form (assuming that <code>comments_display</code> is the default one) only if the article is released.</p>\n\n<h3 id=\"ex-future\">Future article</h3>\n<pre><code>&lt;txp:cbe_if_article_future&gt;\n  &lt;p&gt;Comments are not yet opened.&lt;/p&gt;\n&lt;txp:else /&gt;\n  &lt;txp:output_form form=\"comments_display\" /&gt;\n&lt;/txp:cbe_if_article_future&gt;</code></pre>\n\n<p>Shows a message if the article is not yet published, otherwise shows comments and comment form (still assuming that <code>comments_display</code> is the default one).</p>\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<ul>\n  <li>24 Jun 11 - v0.1 - Initial release\n</ul>\n\n</div>', '/****\r\n *\r\n * cbe_if_article_released v0.1\r\n * Claire Brione - http://www.clairebrione.com/\r\n *\r\n ****/\r\n\r\n/*\r\n * cbe_if_article_released\r\n * true if an article\'s publish date is past\r\n */\r\nfunction cbe_if_article_released( $atts, $thing = NULL )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n    return( parse( EvalElse( $thing, $thisarticle[ \'posted\'] <= time() ) ) ) ;\r\n}\r\n\r\n/*\r\n * cbe_if_article_future\r\n * true if an article\'s publish date is future\r\n */\r\nfunction cbe_if_article_future( $atts, $thing = NULL )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n    return( parse( EvalElse( $thing, $thisarticle[ \'posted\'] > time() ) ) ) ;\r\n}', '/****\r\n *\r\n * cbe_if_article_released v0.1\r\n * Claire Brione - http://www.clairebrione.com/\r\n *\r\n ****/\r\n\r\n/*\r\n * cbe_if_article_released\r\n * true if an article\'s publish date is past\r\n */\r\nfunction cbe_if_article_released( $atts, $thing = NULL )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n    return( parse( EvalElse( $thing, $thisarticle[ \'posted\'] <= time() ) ) ) ;\r\n}\r\n\r\n/*\r\n * cbe_if_article_future\r\n * true if an article\'s publish date is future\r\n */\r\nfunction cbe_if_article_future( $atts, $thing = NULL )\r\n{\r\n    global $thisarticle ;\r\n    assert_article() ;\r\n    return( parse( EvalElse( $thing, $thisarticle[ \'posted\'] > time() ) ) ) ;\r\n}', 'fd5622c8f54ab17a4115dfabc772c000', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('arc_meta', 0, 'Andy Carter', 'http://andy-carter.com/', '2.0.1-beta.1', 'Title and Meta tags', '	<h1>arc_meta</h1>\n\n	<h2>Usage</h2>\n\n	<p>All of the following tags should be used within your templates&#8217; <code>&lt;head&gt;</code> tag.</p>\n\n	<h3>arc_meta_title</h3>\n\n	<p>Outputs an <span class=\"caps\">SEO</span> friendly <code>&lt;title&gt;</code> tag.</p>\n\n<pre><code>&lt;txp:arc_meta_title /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<p>The <code>arc_meta_title</code> tag attributes override the defaults. To set the default patterns used for the <code>&lt;title&gt;</code> tag goto the plugin&#8217;s options page.</p>\n\n	<ul>\n		<li>title &#8212; use this to override the title tag&#8217;s content</li>\n		<li>article_title &#8212; sets the pattern for article page titles</li>\n		<li>comment_title &#8212; sets the pattern for comment page titles</li>\n		<li>search_title &#8212; sets the pattern for search results page titles</li>\n		<li>category_title &#8212; sets the pattern for category page titles</li>\n		<li>section_title &#8212; sets the pattern for section page titles</li>\n		<li>homepage_title &#8212; sets the pattern for the homepage title</li>\n		<li>type &#8212; override the content type for the title</li>\n	</ul>\n\n	<h4>Title Tokens</h4>\n\n	<p>To set a pattern for a page title you can use the following tokens:-</p>\n\n	<ul>\n		<li><code>%a</code> &#8212; article title, can only be used on article and comment pages</li>\n		<li><code>%s</code> &#8212; section name, can be used on article and section pages</li>\n		<li><code>%c</code> &#8212; category name, can be used on category pages</li>\n		<li><code>%n</code> &#8212; site&#8217;s name, can be used on all pages</li>\n		<li><code>%t</code> &#8212; site&#8217;s slogan, can be used on all pages</li>\n		<li><code>%q</code> &#8212; search query, can be used on search results page</li>\n	</ul>\n\n	<p>For example, you could define the pattern for an article page as:-</p>\n\n<pre><code>%a | %n (%t)\n</code></pre>\n\n	<p>This would output something like:-</p>\n\n<pre><code>&lt;title&gt;Welcome to Your Site! | My site (My pithy slogan)&lt;/title&gt;\n</code></pre>\n\n	<h4>Examples</h4>\n\n	<h5>Example Using the <code>section_title</code> Attribute</h5>\n\n<pre><code>&lt;txp:arc_meta_title section_title=&#39;%n / %s&#39; /&gt;\n</code></pre>\n\n	<p>Outputs on an &#8216;Articles&#8217; section page:-</p>\n\n<pre><code>&lt;title&gt;My site / Articles&lt;/title&gt;\n</code></pre>\n\n	<h5>Example Using the <code>title</code> Attribute</h5>\n\n<pre><code>&lt;txp:arc_meta_title title=&#39;Hello World&#39; /&gt;\n</code></pre>\n\n	<p>Outputs:-</p>\n\n<pre><code>&lt;title&gt;Hello World&lt;/title&gt;\n</code></pre>\n\n	<h3>arc_meta_description</h3>\n\n	<p>Outputs a meta description tag when a description has been set.</p>\n\n<pre><code>&lt;txp:arc_meta_description /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>description &#8212; overrides the description set using arc_meta&#8217;s description field on the article Write page or section/category edit page</li>\n		<li>type &#8212; override the content type for the meta description</li>\n	</ul>\n\n	<h3>arc_meta_robots</h3>\n\n	<p>Outputs a meta robots tag when robots have been set. When a site is in testing or debugging mode it will automatically output a &#8216;noindex, nofollow&#8217; tag with your actual setting as a comment immediately after the tag for reference.</p>\n\n<pre><code>&lt;txp:arc_meta_robots /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>robots &#8212; overrides the robots instructions set using the meta robots field on the article Write page or section/category edit page</li>\n		<li>type &#8212; override the content type for the robots meta data</li>\n	</ul>\n\n	<h3>arc_meta_keywords</h3>\n\n	<p>Outputs a meta keywords tag when keywords have been set (only works for articles).</p>\n\n<pre><code>&lt;txp:arc_meta_keywords /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>keywords &#8212; overrides the keywords set using the meta keywords field on the article Write page</li>\n	</ul>\n\n	<h3>arc_meta_canonical</h3>\n\n	<p>Specify the page&#8217;s canonical <span class=\"caps\">URL</span>. See Google&#8217;s <a href=\"http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html\">Specify Your Canonical</a> for an explanation.</p>\n\n<pre><code>&lt;txp:arc_meta_canonical /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>url &#8212; overrides the default <span class=\"caps\">URL</span></li>\n	</ul>\n\n	<h3>arc_meta_open_graph</h3>\n\n	<p>Outputs meta tags for using Facebook Open Graph.</p>\n\n<pre><code>&lt;txp:arc_meta_open_graph /&gt;\n</code></pre>\n\n	<p>Just including the above tag in your templates&#8217; <code>&lt;head&gt;</code> tag will output tags for the following:-</p>\n\n	<ul>\n		<li>og:site_name &#8212; your site name from your preferences</li>\n		<li>og:title &#8212; the article&#8217;s title, section name or category name</li>\n		<li>og:description &#8212; your page&#8217;s meta description</li>\n		<li>og:url &#8212; your page&#8217;s canonical <span class=\"caps\">URL</span></li>\n		<li>og:image &#8212; the article&#8217;s image</li>\n	</ul>\n\n	<p>You can override the default values of any of these by passing a value to one of the tag&#8217;s attributes defined below. To disable one of the tags just pass an empty attribute value (<i>e.g.</i> <code>title=&#39;&#39;</code>).</p>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>site_name</li>\n		<li>title</li>\n		<li>description</li>\n		<li>url</li>\n		<li>image</li>\n	</ul>\n\n	<h3>arc_meta_twitter_card</h3>\n\n	<p>Outputs meta tags for using Twitter Cards.</p>\n\n<pre><code>&lt;txp:arc_meta_twitter_card /&gt;\n</code></pre>\n\n	<p>Just including the above tag in your templates&#8217; <code>&lt;head&gt;</code> tag will output tags for the following:-</p>\n\n	<ul>\n		<li>twitter:card &#8212; &#8216;summary&#8217; by default, can also be set to &#8216;photo&#8217; for image content or &#8216;player&#8217; for videos</li>\n		<li>twitter:title &#8212; the article&#8217;s title, section name or category name</li>\n		<li>twitter:description &#8212; your page&#8217;s meta description</li>\n		<li>twitter:url &#8212; your page&#8217;s canonical <span class=\"caps\">URL</span></li>\n		<li>twitter:image &#8212; the article&#8217;s image</li>\n	</ul>\n\n	<p>You can override the default values of any of these by passing a value to one of the tag&#8217;s attributes defined below.</p>\n\n	<p>To start using Twitter cards you will need to authorise them for your domain by <a href=\"https://dev.twitter.com/docs/cards/validation/validator\">validating and applying</a> on the Twitter website. You will need to supply Twitter with a few details including the <span class=\"caps\">URL</span> of a page with a complete Twitter card. It can take several days for Twitter to authorise your site.</p>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>card</li>\n		<li>title</li>\n		<li>description</li>\n		<li>url</li>\n		<li>image</li>\n	</ul>\n\n	<h3>arc_meta_organization</h3>\n\n	<p>Outputs Knowledge Graph social profiles script tag for an organisation.</p>\n\n<pre><code>&lt;txp:arc_meta_organization /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>name &#8212; Organisation&#8217;s name</li>\n		<li>logo &#8212; <span class=\"caps\">URL</span> to organisation&#8217;s logo (required, will throw an error if not set)</li>\n		<li>facebook &#8212; <span class=\"caps\">URL</span> to Facebook page</li>\n		<li>gplus &#8212; <span class=\"caps\">URL</span> to Google+ page</li>\n		<li>twitter &#8212; <span class=\"caps\">URL</span> to Twitter account</li>\n	</ul>\n\n	<h3>arc_meta_person</h3>\n\n	<p>Outputs Knowledge Graph social profiles script tag for a person.</p>\n\n<pre><code>&lt;txp:arc_meta_person /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li>name &#8212; Person&#8217;s name</li>\n		<li>logo &#8212; <span class=\"caps\">URL</span> to person&#8217;s picture</li>\n		<li>facebook &#8212; <span class=\"caps\">URL</span> to Facebook page</li>\n		<li>gplus &#8212; <span class=\"caps\">URL</span> to Google+ page</li>\n		<li>twitter &#8212; <span class=\"caps\">URL</span> to Twitter account</li>\n	</ul>\n\n	<h2>Admin</h2>\n\n	<h3>Article Write Page</h3>\n\n	<p>The plugin will add <em>title</em> and <em>description</em> fields to the <em>Meta</em> options. Use these to set your article&#8217;s meta data.</p>\n\n	<p>If you set a title here it will be used in place of the article&#8217;s title when replacing the <code>%a</code> token for the <code>arc_meta_title</code> tag.</p>\n\n	<p>The description will be used for the <code>arc_meta_description</code> tag and for the descriptions used by the <code>arc_meta_open_graph</code> and <code>arc_meta_twitter_card</code> tags.</p>\n\n	<h3>Section and Category Pages</h3>\n\n	<p>The plugin will add <em>meta title</em> and <em>meta description</em> fields to the section and category edit forms.</p>\n\n	<p>If you set a title here it will be used in place of the section&#8217;s/category&#8217;s name when replacing the <code>%s</code>/<code>%c</code> token for the <code>arc_meta_title</code> tag. When editing the homepage section the <em>meta title</em> will replace any pattern defined for the page title.</p>\n\n	<p>The description will be used for the <code>arc_meta_description</code> tag and for the descriptions used by the <code>arc_meta_open_graph</code> and <code>arc_meta_twitter_card</code> tags.</p>\n\n	<h3>Plugin Options</h3>\n\n	<p>From the plugin&#8217;s options page you can set the default patterns used for the arc_meta_title tag. These can all be overridden when the tag is included in your page templates, but it may be easier to set the default patterns if you want to change the ones that come with the plugin when installed.</p>', 'global $prefs, $txpcfg, $arc_meta;\n\n// Register tags.\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'arc_meta_title\')\n    ->register(\'arc_meta_description\')\n    ->register(\'arc_meta_canonical\')\n    ->register(\'arc_meta_robots\')\n    ->register(\'arc_meta_keywords\')\n    ->register(\'arc_meta_open_graph\')\n    ->register(\'arc_meta_twitter_card\')\n    ->register(\'arc_meta_organization\')\n    ->register(\'arc_meta_person\');\n\nregister_callback(\'_arc_meta_install\', \'plugin_lifecycle.arc_meta\', \'installed\');\nregister_callback(\'_arc_meta_uninstall\', \'plugin_lifecycle.arc_meta\', \'deleted\');\nregister_callback(\'arc_meta_options\', \'plugin_prefs.arc_meta\');\nadd_privs(\'plugin_prefs.arc_meta\', \'1,2\');\nadd_privs(\'prefs.arc_meta\', \'1,2\');\n\n\nfunction arc_meta_title($atts)\n{\n    global $parentid, $thisarticle, $id, $q, $c, $context, $s, $sitename, $prefs;\n\n    extract(lAtts(array(\n        \'separator\' => \' | \',\n        \'title\' => null,\n        \'article_title\' => $prefs[\'arc_meta_article_title\'],\n        \'comment_title\' => $prefs[\'arc_meta_comment_title\'],\n        \'search_title\' => $prefs[\'arc_meta_search_title\'],\n        \'category_title\' => $prefs[\'arc_meta_category_title\'],\n        \'section_title\' => $prefs[\'arc_meta_section_title\'],\n        \'section_category_title\' => $prefs[\'arc_meta_section_category_title\'],\n        \'homepage_title\' => $prefs[\'arc_meta_homepage_title\'],\n        \'type\' => null\n    ), $atts));\n\n    if ($title === null) {\n        $meta = _arc_meta($type);\n\n        // Determine the title type.\n        if ($type === null) {\n            if (!empty($parent_id) || !empty($thisarticle[\'title\'])) {\n                $type = \'comments\';\n            } elseif ($q) {\n                $type = \'search\';\n            } elseif ($c) {\n                $type = \'category\';\n            } elseif ($s and $s != \'default\') {\n                $type = \'section\';\n            } else {\n                $type = \'article\';\n            }\n        }\n\n        $tokens = array(\n            \'_%n_\' => txpspecialchars($sitename),\n            \'_%t_\' => txpspecialchars($prefs[\'site_slogan\'])\n        );\n\n        if ($type === \'comments\') {\n            $tokens[\'_%a_\'] = empty($meta[\'title\']) ? escape_title($thisarticle[\'title\']) : $meta[\'title\'];\n            $tokens[\'_%s_\'] = txpspecialchars(fetch_section_title($thisarticle[\'section\']));\n            $pattern = !empty($parent_id) ? $comment_title : $article_title;\n        } elseif ($type === \'search\') {\n            $tokens[\'_%q_\'] = txpspecialchars($q);\n            $pattern = $search_title;\n        } elseif ($type === \'category\') {\n            $tokens[\'_%c_\'] = $meta[\'title\'] ?: txpspecialchars(fetch_category_title($c, $context));\n            if ($s and $s != \'default\') {\n                $tokens[\'_%s_\'] = txpspecialchars(fetch_section_title($s));\n                $pattern = $section_category_title;\n            } else {\n                $pattern = $category_title;\n            }\n        } elseif ($type === \'section\') {\n            $tokens[\'_%s_\'] = $meta[\'title\'] ?: txpspecialchars(fetch_section_title($s));\n            $pattern = $section_title;\n        } else {\n            $pattern = $meta[\'title\'] ?: $homepage_title;\n        }\n\n        $title = preg_replace(array_keys($tokens), array_values($tokens), $pattern);\n    }\n\n    $html = tag($title, \'title\');\n\n    return $html;\n}\n\nfunction arc_meta_canonical($atts)\n{\n    extract(lAtts(array(\n        \'url\' => null\n    ), $atts));\n\n    $url = $url !== null ? $url : _arc_meta_url();\n\n    $html = \"<link rel=\\\"canonical\\\" href=\\\"$url\\\" />\";\n\n    return $html;\n}\n\nfunction arc_meta_description($atts)\n{\n    extract(lAtts(array(\n        \'description\' => null,\n        \'type\' => null\n    ), $atts));\n\n    if ($description === null) {\n        $meta = _arc_meta($type);\n        $description = !empty($meta[\'description\']) ? txpspecialchars($meta[\'description\'], ENT_QUOTES) : _arc_meta_description($type);\n    }\n\n    if ($description) {\n        $description = trim($description);\n        return \"<meta name=\\\"description\\\" content=\\\"$description\\\" />\";\n    }\n\n    return \'\';\n}\n\nfunction arc_meta_robots($atts)\n{\n    extract(lAtts(array(\n        \'robots\' => null,\n        \'type\' => null\n    ), $atts));\n\n    if ($robots === null) {\n        $meta = _arc_meta($type);\n        $robots = !empty($meta[\'robots\']) ? $meta[\'robots\'] : null;\n    }\n\n    $out = \'\';\n\n    if (get_pref(\'production_status\') != \'live\') {\n        $out .= \"<meta name=\\\"robots\\\" content=\\\"noindex, nofollow\\\" />\";\n        $out .= $robots ? \"<!-- $robots -->\" : null;\n    } elseif ($robots) {\n        $out .= \"<meta name=\\\"robots\\\" content=\\\"$robots\\\" />\";\n    }\n\n    return $out;\n}\n\nfunction arc_meta_keywords($atts)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'keywords\' => null\n    ), $atts));\n\n    $keywords = $keywords===null && isset($thisarticle[\'keywords\']) ? $thisarticle[\'keywords\'] : null;\n\n    if ($keywords) {\n        $keywords = txpspecialchars($keywords);\n        return \"<meta name=\\\"keywords\\\" content=\\\"$keywords\\\" />\";\n    }\n\n    return \'\';\n\n}\n\nfunction arc_meta_open_graph($atts)\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    extract(lAtts(array(\n        \'site_name\' => $prefs[\'sitename\'],\n        \'title\' => null,\n        \'description\' => null,\n        \'url\' => null,\n        \'image\' => null\n    ), $atts));\n\n    $title = $title===null ? _arc_meta_title() : $title;\n    $description = $description===null ? _arc_meta_description() : $description;\n    $url = $url===null ? _arc_meta_url() : $url;\n    $image = $image===null ? _arc_meta_image() : $image;\n\n    $html = \'\';\n    if ($site_name) {\n        $html .= \"<meta property=\\\"og:site_name\\\" content=\\\"$site_name\\\" />\";\n    }\n    if ($title) {\n        $html .= \"<meta property=\\\"og:title\\\" content=\\\"$title\\\" />\";\n    }\n    if ($description) {\n        $html .= \"<meta property=\\\"og:description\\\" content=\\\"$description\\\" />\";\n    }\n    if ($url) {\n        $html .= \"<meta property=\\\"og:url\\\" content=\\\"$url\\\" />\";\n    }\n    if ($image) {\n        $html .= \"<meta property=\\\"og:image\\\" content=\\\"$image\\\" />\";\n    }\n\n    return $html;\n}\n\nfunction arc_meta_twitter_card($atts)\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    extract(lAtts(array(\n        \'card\' => \'summary\',\n        \'title\' => null,\n        \'description\' => null,\n        \'url\' => null,\n        \'image\' => null\n    ), $atts));\n\n    $title = $title===null ? _arc_meta_title() : $title;\n    $description = $description===null ? _arc_meta_description() : $description;\n    $url = $url===null ? _arc_meta_url() : $url;\n    $image = $image===null ? _arc_meta_image() : $image;\n\n    $html = \"<meta name=\\\"twitter:card\\\" content=\\\"$card\\\" />\";\n    $html .= \"<meta name=\\\"twitter:title\\\" content=\\\"$title\\\" />\";\n    $html .= \"<meta name=\\\"twitter:description\\\" content=\\\"$description\\\" />\";\n\n    if ($url) {\n        $html .= \"<meta name=\\\"twitter:url\\\" content=\\\"$url\\\" />\";\n    }\n    if ($image) {\n        $html .= \"<meta name=\\\"twitter:image:src\\\" content=\\\"$image\\\" />\";\n    }\n\n    return $html;\n\n}\n\nfunction arc_meta_organization($atts)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => $prefs[\'sitename\'],\n        \'logo\' => null,\n        \'facebook\' => null,\n        \'gplus\' => null,\n        \'twitter\' => null\n    ), $atts));\n\n    if (empty($logo)) {\n        trigger_error(\'arc_meta_organization missing logo attribute\', E_USER_WARNING);\n    }\n\n    $data = array(\n        \'@context\' => \'http://schema.org\',\n        \'@type\' => \'Organization\',\n        \'name\' => $name,\n        \'logo\' => $logo,\n        \'url\' => hu\n    );\n\n    $sameAs = array();\n    if (!empty($facebook)) {\n        $sameAs[] = $facebook;\n    }\n    if (!empty($gplus)) {\n        $sameAs[] = $gplus;\n    }\n    if (!empty($twitter)) {\n        $sameAs[] = $twitter;\n    }\n\n    if (!empty($sameAs)) {\n        $data[\'sameAs\'] = $sameAs;\n    }\n\n    return \'<script type=\"application/ld+json\">\' . str_replace(\'\\\\/\', \'/\', json_encode($data)) . \'</script>\';\n\n}\n\nfunction arc_meta_person($atts)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => $prefs[\'sitename\'],\n        \'logo\' => null,\n        \'facebook\' => null,\n        \'gplus\' => null,\n        \'twitter\' => null\n    ), $atts));\n\n    $data = array(\n        \'@context\' => \'http://schema.org\',\n        \'@type\' => \'Person\',\n        \'name\' => $name,\n        \'url\' => hu\n    );\n\n    $sameAs = array();\n    if (!empty($facebook)) {\n        $sameAs[] = $facebook;\n    }\n    if (!empty($gplus)) {\n        $sameAs[] = $gplus;\n    }\n    if (!empty($twitter)) {\n        $sameAs[] = $twitter;\n    }\n\n    if (!empty($sameAs)) {\n        $data[\'sameAs\'] = $sameAs;\n    }\n\n    return \'<script type=\"application/ld+json\">\' . str_replace(\'\\\\/\', \'/\', json_encode($data)) . \'</script>\';\n\n}\n\nfunction _arc_meta_title()\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    if (!empty($thisarticle[\'thisid\'])) {\n        $title = txpspecialchars($thisarticle[\'title\']);\n    } elseif (!empty($s) and $s != \'default\') {\n        $title = txpspecialchars(fetch_section_title($s));\n    } elseif (!empty($c)) {\n        $title = txpspecialchars(fetch_category_title($c));\n    } else {\n        $title = txpspecialchars($prefs[\'sitename\']);\n    }\n\n    return $title;\n\n}\n\nfunction _arc_meta_url()\n{\n    global $thisarticle, $s, $c;\n\n    if (!empty($thisarticle[\'thisid\'])) {\n        $url = permlinkurl($thisarticle);\n    } elseif (!empty($s) and $s != \'default\') {\n        $url = pagelinkurl(array(\'s\' => $s));\n    } elseif (!empty($c)) {\n        $url = pagelinkurl(array(\'c\' => $c));\n    } else {\n        $url = hu;\n    }\n    return $url;\n}\n\nfunction _arc_meta_image()\n{\n    global $thisarticle;\n\n    $image = $thisarticle[\'article_image\'];\n\n    if (intval($image)) {\n        if ($rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($image))) {\n            $image = imagesrcurl($rs[\'id\'], $rs[\'ext\']);\n        } else {\n            $image = null;\n        }\n\n    } else {\n        $meta = _arc_meta();\n        if (!empty($meta[\'image\']) && $rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($meta[\'image\']))) {\n            $image = imagesrcurl($rs[\'id\'], $rs[\'ext\']);\n        } else {\n            $image = null;\n        }\n\n    }\n\n    return $image;\n}\n\n/**\n * Fetch meta description from the given (or automatic) context.\n *\n * Category context may be refined by specifying the content type as well\n * after a dot. e.g. category.image to check image context category.\n *\n * @param string $type Flavour of meta content to fetch (section, category, article)\n */\nfunction _arc_meta_description($type = null)\n{\n    global $thisarticle;\n\n    $metaDescription = getMetaDescription($type);\n\n    if (!empty($metaDescription)) {\n        $description = txpspecialchars($metaDescription);\n    } elseif (!empty($thisarticle[\'excerpt\'])) {\n        $description = strip_tags($thisarticle[\'excerpt\']);\n        $description = substr($description, 0, 200);\n        $description = txpspecialchars($description);\n    } elseif (!empty($thisarticle[\'body\'])) {\n        $description = strip_tags($thisarticle[\'body\']);\n        $description = substr($description, 0, 200);\n        $description = txpspecialchars($description);\n    } else {\n        $description = null;\n    }\n\n    return $description;\n}\n\n/**\n * Fetches meta data for the current context.\n * @param  string  $type   Context\n * @param  int     $typeId Context\'s primary key\n * @param  boolean $admin  Pass true if called from the admin area\n * @return array           Meta data\n */\nfunction _arc_meta($type = null, $typeId = null, $admin = false)\n{\n    global $thisarticle, $thiscategory, $thissection, $s, $c, $arc_meta;\n\n    if (empty($arc_meta)) {\n        $arc_meta = array(\n            \'id\' => null,\n            \'title\' => null,\n            \'description\' => null,\n            \'image\' => null,\n            \'robots\' => null\n        );\n\n        if ($admin === true && $typeId === null) {\n            return $arc_meta;\n        }\n\n        if (empty($type)) {\n            if (!empty($thisarticle[\'thisid\'])) {\n                $typeId = $typeId ?: $thisarticle[\'thisid\'];\n                $type = \'article\';\n            } elseif (!empty($c)) {\n                $typeId = $typeId ?: $c;\n                $type = \'category\';\n            } elseif (!empty($s)) {\n                $typeId = $typeId ?: $s;\n                $type = \'section\';\n            }\n\n        } elseif ($typeId === null) {\n            if (strpos($type, \'category\') === 0) {\n                $type = \'category\';\n                $typeId = $thiscategory ? $thiscategory[\'name\'] : $c;\n            } elseif ($type === \'section\') {\n                $typeId = $thissection[\'name\'] ? $thissection[\'name\'] : $s;\n            } elseif ($type === \'article\') {\n                assert_article();\n                $typeId = $thisarticle ? $thisarticle[\'thisid\'] : null;\n            }\n\n        }\n\n        if (empty($type) || empty($typeId)) {\n            if (!empty($thisarticle[\'thisid\'])) {\n                $typeId = $thisarticle[\'thisid\'];\n                $type = \'article\';\n            } elseif (!empty($c)) {\n                $typeId = $c;\n                $type = \'category\';\n            } elseif (!empty($s)) {\n                $typeId = $s;\n                $type = \'section\';\n            }\n\n        }\n\n        if (!empty($typeId) && !empty($type)) {\n            $meta = safe_row(\'*\', \'arc_meta\', \"type_id=\'$typeId\' AND type=\'$type\'\");\n            $arc_meta = array_merge($arc_meta, $meta);\n            return $arc_meta;\n        }\n\n    }\n\n    return $arc_meta;\n}\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'_arc_meta_article_meta\', \'article_ui\', \'url_title\');\n    register_callback(\'_arc_meta_article_meta_save\', \'ping\');\n    register_callback(\'_arc_meta_article_meta_save\', \'article_saved\');\n    register_callback(\'_arc_meta_article_meta_save\', \'article_posted\');\n\n    register_callback(\'_arc_meta_section_meta\', \'section_ui\', \'extend_detail_form\');\n    register_callback(\'_arc_meta_section_meta_save\', \'section\', \'section_save\');\n\n    register_callback(\'_arc_meta_category_meta\', \'category_ui\', \'extend_detail_form\');\n    register_callback(\'_arc_meta_category_meta_save\', \'category\', \'cat_article_save\');\n\n    if (!empty($prefs[\'arc_meta_section_tab\'])) {\n        add_privs(\'arc_meta_section_tab\', \'1,2,3,4\');\n        register_tab($prefs[\'arc_meta_section_tab\'], \'arc_meta_section_tab\', \'Sections Meta Data\');\n        register_callback(\'arc_meta_section_tab\', \'arc_meta_section_tab\');\n    }\n}\n\nfunction _arc_meta_install()\n{\n    $sql = \"CREATE TABLE IF NOT EXISTS \" . PFX . \"arc_meta (\n        `id` int(11) NOT NULL AUTO_INCREMENT,\n        `type` varchar(8) NOT NULL,\n        `type_id` varchar(128) NOT NULL,\n        `title` varchar(250) DEFAULT NULL,\n        `override_title` tinyint(1) DEFAULT NULL,\n        `robots` varchar(45) DEFAULT NULL,\n        PRIMARY KEY (`id`)\n        ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;\";\n\n    if (!safe_query($sql)) {\n        return \'Error - unable to create arc_meta table\';\n    }\n\n    $dbTable = getThings(\'DESCRIBE \' . safe_pfx(\'arc_meta\'));\n\n    if (!in_array(\'robots\', $dbTable)) {\n        safe_alter(\'arc_meta\', \'ADD robots VARCHAR(45)\');\n    }\n\n    if (!in_array(\'image\', $dbTable)) {\n        safe_alter(\'arc_meta\', \'ADD image INT(11) DEFAULT NULL\');\n        // Increased size of title and description columns.\n        safe_alter(\'arc_meta\', \'CHANGE title title VARCHAR(250) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL\');\n    }\n\n    // Upgrade plugin to 2.x.\n    if (in_array(\'description\', $dbTable)) {\n        // Copy meta description data to main TXP tables.\n        safe_query(\'UPDATE \' . safe_pfx(\'textpattern\') . \', \' . safe_pfx(\'arc_meta\') . \' SET textpattern.description = arc_meta.description WHERE textpattern.ID = arc_meta.type_id AND arc_meta.type = \\\'article\\\' AND arc_meta.description IS NOT NULL AND textpattern.description = \\\'\\\'\');\n        safe_query(\'UPDATE \' . safe_pfx(\'txp_category\') . \', \' . safe_pfx(\'arc_meta\') . \' SET txp_category.description = arc_meta.description WHERE txp_category.name = arc_meta.type_id AND arc_meta.type = \\\'category\\\' AND arc_meta.description IS NOT NULL AND txp_category.description = \\\'\\\'\');\n        safe_query(\'UPDATE \' . safe_pfx(\'txp_section\') . \', \' . safe_pfx(\'arc_meta\') . \' SET txp_section.description = arc_meta.description WHERE txp_section.name = arc_meta.type_id AND arc_meta.type = \\\'section\\\' AND arc_meta.description IS NOT NULL AND txp_section.description = \\\'\\\'\');\n        // Drop old meta description column.\n        safe_alter(\'arc_meta\', \'DROP COLUMN description\');\n    }\n\n    // Setup the plugin preferences.\n    _arc_meta_install_prefs();\n\n    return;\n}\n\n/**\n * Setup the plugin preferences if they have not yet been set.\n */\nfunction _arc_meta_install_prefs()\n{\n    if (!isset($prefs[\'arc_meta_homepage_title\'])) {\n        set_pref(\'arc_meta_homepage_title\', \'%n | %t\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 10);\n    }\n    if (!isset($prefs[\'arc_meta_article_title\'])) {\n        set_pref(\'arc_meta_article_title\', \'%a | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 20);\n    }\n    if (!isset($prefs[\'arc_meta_comment_title\'])) {\n        set_pref(\'arc_meta_comment_title\', gTxt(\'comments_on\').\' %a | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 30);\n    }\n    if (!isset($prefs[\'arc_meta_search_title\'])) {\n        set_pref(\'arc_meta_search_title\', gTxt(\'search_results\') . \': \' . \'%q | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 40);\n    }\n    if (!isset($prefs[\'arc_meta_category_title\'])) {\n        set_pref(\'arc_meta_category_title\', \'%c | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 50);\n    }\n    if (!isset($prefs[\'arc_meta_section_title\'])) {\n        set_pref(\'arc_meta_section_title\', \'%s | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 60);\n    }\n    if (!isset($prefs[\'arc_meta_section_category_title\'])) {\n        set_pref(\'arc_meta_section_category_title\', \'%c - %s | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 70);\n    }\n    if (!isset($prefs[\'arc_meta_section_tab\'])) {\n        set_pref(\'arc_meta_section_tab\', \'content\', \'arc_meta\', PREF_PLUGIN, \'_arc_meta_section_tab_select\', 80);\n    }\n    return;\n}\n\nfunction _arc_meta_section_tab_select($name, $val)\n{\n    $vals = array(\n        \'content\'=> gtxt(\'arc_meta_section_tab_content\'),\n        \'extensions\'=> gtxt(\'arc_meta_section_tab_extensions\'),\n        \'hidden\'=> gtxt(\'arc_meta_section_tab_hidden\')\n    );\n    return selectInput($name, $vals, $val);\n}\n\nfunction _arc_meta_uninstall()\n{\n    $sql = \"DROP TABLE IF EXISTS \".PFX.\"arc_meta;\";\n    if (!safe_query($sql)) {\n        return \'Error - unable to delete arc_meta table\';\n    }\n\n    $sql = \"DELETE FROM  \".PFX.\"txp_prefs WHERE event=\'arc_meta\';\";\n    if (!safe_query($sql)) {\n        return \'Error - unable to delete arc_meta preferences\';\n    }\n\n    safe_delete(\'txp_lang\', \"owner LIKE \'arc\\_meta\'\");\n\n    return;\n}\n\nfunction arc_meta_section_tab($event, $step)\n{\n    switch ($step) {\n        case \'edit\':\n            arc_meta_section_edit();\n            break;\n\n        case \'save\':\n            _arc_meta_section_meta_save($event, $step);\n            // Fall through to section list.\n\n        default:\n            arc_meta_section_list();\n            break;\n    }\n}\n\nfunction arc_meta_section_list()\n{\n    global $event;\n\n    pagetop(\'Section Meta Data\');\n\n    $html = \'<h1 class=\"txp-heading\">Section Meta Data</h1>\';\n\n    $rs = safe_query(\n        \'SELECT sections.*, arc_meta.title AS meta_title FROM \' . safe_pfx(\'txp_section\') . \' sections LEFT JOIN \' . safe_pfx(\'arc_meta\') . \' arc_meta ON arc_meta.type = \"section\" AND arc_meta.type_id = sections.name WHERE 1=1 ORDER BY CASE WHEN sections.name = \"default\" THEN 1 ELSE 2 END, sections.name ASC\'\n    );\n\n    if ($rs) {\n        $html .= n . \'<div id=\"\' . $event . \'_container\" class=\"txp-container\">\';\n\n        $html .= n . \'<div class=\"txp-listtables\">\' . n\n                . n . startTable(\'\', \'\', \'txp-list\')\n                . n . \'<thead>\'\n                . n . tr(hCell(\'Name\') . hCell(\'Title\') . hCell(gtxt(\'arc_meta_title\')) . hCell(\'Manage\'))\n            . n . \'</thead>\';\n\n        while ($row = nextRow($rs)) {\n            $editLink = href(\n                gTxt(\'edit\'),\n                \'?event=arc_meta_section_tab&amp;step=edit&amp;name=\' . $row[\'name\']\n            );\n            $html .= n . tr(\n                n . td($row[\'name\']) . td($row[\'title\']) . td($row[\'meta_title\']) . td($editLink)\n            );\n        }\n\n        $html .= n . endTable();\n\n        $html .= n . \'</div>\';\n\n    }\n\n    echo $html;\n}\n\nfunction arc_meta_section_edit()\n{\n    global $event;\n\n    $name = gps(\'name\');\n\n    $rs = safe_query(\n        \'SELECT sections.title AS section, sections.description, arc_meta.* FROM \' . safe_pfx(\'txp_section\') . \' sections LEFT JOIN \' . safe_pfx(\'arc_meta\') . \' arc_meta ON arc_meta.type = \"section\" AND arc_meta.type_id = sections.name WHERE sections.name=\"\' . doSlash($name) . \'\"\'\n    );\n\n    $meta = nextRow($rs);\n\n    pagetop(\'Section Meta Data\');\n\n    $form = \'\';\n\n    $form .= \'<div class=\"txp-edit\">\';\n    $form .= hed(\'Edit Section Meta Data\', 2);\n\n    // We include the section title as a disabled field for the user\'s\n    // reference.\n    $form .= \'<div class=\"txp-form-field edit-section-section\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(\'Section\', \'label\', \' for=\"section\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'section\', $meta[\'section\'], \'\', \'\', \'\', \'32\', \'\', \'section\', true) . \'</div>\';\n    $form .= \'</div>\';\n\n    // Meta data fields\n    $form .= hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= hInput(\'name\', $name);\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field txp-form-field-textarea edit-section-arc_meta_description\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_description\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . text_area(\'arc_meta_description\', null, null, $meta[\'description\'], \'arc_meta_description\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    $form .= \'<p class=\"txp-edit-actions\">\' . sLink(\'arc_meta_section_tab\', \'\', gTxt(\'cancel\'), \'txp-button\') . sInput(\'save\') . eInput($event) . fInput(\'submit\', \'save\', gTxt(\'Save\'), \'publish\') . \'</p>\';\n\n    $form .= \'</div>\';\n\n    $html = \'<div id=\"\' . $event . \'_container\" class=\"txp-container\">\' . form($form, \'\', \'\', \'post\', \'edit-form\') . \'</div>\';\n\n    echo $html;\n}\n\nfunction arc_meta_options()\n{\n    $link = \'?event=prefs#prefs_group_arc_meta\';\n    header(\'Location: \' . $link);\n}\n\nfunction _arc_meta_article_meta($event, $step, $data, $rs)\n{\n    // Get the article meta data.\n    $articleId = !empty($rs[\'ID\']) ? $rs[\'ID\'] : null;\n    $meta = _arc_meta(\'article\', $articleId, true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \"<p class=\'arc_meta_title\'>\";\n    $form .= tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'<br />\';\n    $form .= fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\');\n    $form .= \"</p>\";\n    $form .= \"<p class=\'edit-category-arc_meta_robots\'>\";\n    $form .= tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'<br />\';\n    $form .= selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\');\n    $form .= \'</p>\';\n\n    return $form . $data;\n}\n\nfunction _arc_meta_section_meta($event, $step, $data, $rs)\n{\n    // Get the section meta data.\n    $sectionName = !empty($rs[\'name\']) ? $rs[\'name\'] : null;\n    $meta = _arc_meta(\'section\', $sectionName, true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    return $data . $form;\n}\n\nfunction _arc_meta_category_meta($event, $step, $data, $rs)\n{\n    // Make sure that this is an article category (we don\'t support other\n    // category types).\n    if ($rs[\'type\']!=\'article\') {\n        return $data;\n    }\n\n    // Get the existing meta data for this category.\n    $meta = _arc_meta(\'category\', $rs[\'name\'], true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    return $data . $form;\n}\n\nfunction _arc_meta_article_meta_save($event, $step)\n{\n    $articleId = empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\'];\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'article\',\n        \'type_id\' => $articleId,\n        \'title\' => doSlash($metaTitle),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n}\n\nfunction _arc_meta_section_meta_save($event, $step)\n{\n    $sectionName = gps(\'name\');\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaDescription = gps(\'arc_meta_description\');\n    $metaImage = gps(\'arc_meta_image\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'section\',\n        \'type_id\' => $sectionName,\n        \'title\' => doSlash($metaTitle),\n        \'image\' => intval($metaImage),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    if (empty($values[\'image\'])) {\n        unset($values[\'image\']);\n        $sql[] = \"image = NULL\";\n    }\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaImage) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n\n    // Update the meta description.\n    $metaDescription = doSlash($metaDescription);\n    safe_update(\'txp_section\', \"description = \'$metaDescription\'\", \"name=\'$sectionName\'\");\n}\n\nfunction _arc_meta_category_meta_save($event, $step)\n{\n    $categoryName = gps(\'name\');\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaImage = gps(\'arc_meta_image\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'category\',\n        \'type_id\' => $categoryName,\n        \'title\' => doSlash($metaTitle),\n        \'image\' => intval($metaImage),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaDescription) || !empty($metaImage) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n}\n\nfunction _arc_meta_robots()\n{\n    return array(\n        \'index, follow\' => \'index, follow\',\n        \'index, nofollow\' => \'index, nofollow\',\n        \'noindex, follow\' => \'noindex, follow\',\n        \'noindex, nofollow\' => \'noindex, nofollow\'\n    );\n}\n', 'global $prefs, $txpcfg, $arc_meta;\n\n// Register tags.\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'arc_meta_title\')\n    ->register(\'arc_meta_description\')\n    ->register(\'arc_meta_canonical\')\n    ->register(\'arc_meta_robots\')\n    ->register(\'arc_meta_keywords\')\n    ->register(\'arc_meta_open_graph\')\n    ->register(\'arc_meta_twitter_card\')\n    ->register(\'arc_meta_organization\')\n    ->register(\'arc_meta_person\');\n\nregister_callback(\'_arc_meta_install\', \'plugin_lifecycle.arc_meta\', \'installed\');\nregister_callback(\'_arc_meta_uninstall\', \'plugin_lifecycle.arc_meta\', \'deleted\');\nregister_callback(\'arc_meta_options\', \'plugin_prefs.arc_meta\');\nadd_privs(\'plugin_prefs.arc_meta\', \'1,2\');\nadd_privs(\'prefs.arc_meta\', \'1,2\');\n\n\nfunction arc_meta_title($atts)\n{\n    global $parentid, $thisarticle, $id, $q, $c, $context, $s, $sitename, $prefs;\n\n    extract(lAtts(array(\n        \'separator\' => \' | \',\n        \'title\' => null,\n        \'article_title\' => $prefs[\'arc_meta_article_title\'],\n        \'comment_title\' => $prefs[\'arc_meta_comment_title\'],\n        \'search_title\' => $prefs[\'arc_meta_search_title\'],\n        \'category_title\' => $prefs[\'arc_meta_category_title\'],\n        \'section_title\' => $prefs[\'arc_meta_section_title\'],\n        \'section_category_title\' => $prefs[\'arc_meta_section_category_title\'],\n        \'homepage_title\' => $prefs[\'arc_meta_homepage_title\'],\n        \'type\' => null\n    ), $atts));\n\n    if ($title === null) {\n        $meta = _arc_meta($type);\n\n        // Determine the title type.\n        if ($type === null) {\n            if (!empty($parent_id) || !empty($thisarticle[\'title\'])) {\n                $type = \'comments\';\n            } elseif ($q) {\n                $type = \'search\';\n            } elseif ($c) {\n                $type = \'category\';\n            } elseif ($s and $s != \'default\') {\n                $type = \'section\';\n            } else {\n                $type = \'article\';\n            }\n        }\n\n        $tokens = array(\n            \'_%n_\' => txpspecialchars($sitename),\n            \'_%t_\' => txpspecialchars($prefs[\'site_slogan\'])\n        );\n\n        if ($type === \'comments\') {\n            $tokens[\'_%a_\'] = empty($meta[\'title\']) ? escape_title($thisarticle[\'title\']) : $meta[\'title\'];\n            $tokens[\'_%s_\'] = txpspecialchars(fetch_section_title($thisarticle[\'section\']));\n            $pattern = !empty($parent_id) ? $comment_title : $article_title;\n        } elseif ($type === \'search\') {\n            $tokens[\'_%q_\'] = txpspecialchars($q);\n            $pattern = $search_title;\n        } elseif ($type === \'category\') {\n            $tokens[\'_%c_\'] = $meta[\'title\'] ?: txpspecialchars(fetch_category_title($c, $context));\n            if ($s and $s != \'default\') {\n                $tokens[\'_%s_\'] = txpspecialchars(fetch_section_title($s));\n                $pattern = $section_category_title;\n            } else {\n                $pattern = $category_title;\n            }\n        } elseif ($type === \'section\') {\n            $tokens[\'_%s_\'] = $meta[\'title\'] ?: txpspecialchars(fetch_section_title($s));\n            $pattern = $section_title;\n        } else {\n            $pattern = $meta[\'title\'] ?: $homepage_title;\n        }\n\n        $title = preg_replace(array_keys($tokens), array_values($tokens), $pattern);\n    }\n\n    $html = tag($title, \'title\');\n\n    return $html;\n}\n\nfunction arc_meta_canonical($atts)\n{\n    extract(lAtts(array(\n        \'url\' => null\n    ), $atts));\n\n    $url = $url !== null ? $url : _arc_meta_url();\n\n    $html = \"<link rel=\\\"canonical\\\" href=\\\"$url\\\" />\";\n\n    return $html;\n}\n\nfunction arc_meta_description($atts)\n{\n    extract(lAtts(array(\n        \'description\' => null,\n        \'type\' => null\n    ), $atts));\n\n    if ($description === null) {\n        $meta = _arc_meta($type);\n        $description = !empty($meta[\'description\']) ? txpspecialchars($meta[\'description\'], ENT_QUOTES) : _arc_meta_description($type);\n    }\n\n    if ($description) {\n        $description = trim($description);\n        return \"<meta name=\\\"description\\\" content=\\\"$description\\\" />\";\n    }\n\n    return \'\';\n}\n\nfunction arc_meta_robots($atts)\n{\n    extract(lAtts(array(\n        \'robots\' => null,\n        \'type\' => null\n    ), $atts));\n\n    if ($robots === null) {\n        $meta = _arc_meta($type);\n        $robots = !empty($meta[\'robots\']) ? $meta[\'robots\'] : null;\n    }\n\n    $out = \'\';\n\n    if (get_pref(\'production_status\') != \'live\') {\n        $out .= \"<meta name=\\\"robots\\\" content=\\\"noindex, nofollow\\\" />\";\n        $out .= $robots ? \"<!-- $robots -->\" : null;\n    } elseif ($robots) {\n        $out .= \"<meta name=\\\"robots\\\" content=\\\"$robots\\\" />\";\n    }\n\n    return $out;\n}\n\nfunction arc_meta_keywords($atts)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'keywords\' => null\n    ), $atts));\n\n    $keywords = $keywords===null && isset($thisarticle[\'keywords\']) ? $thisarticle[\'keywords\'] : null;\n\n    if ($keywords) {\n        $keywords = txpspecialchars($keywords);\n        return \"<meta name=\\\"keywords\\\" content=\\\"$keywords\\\" />\";\n    }\n\n    return \'\';\n\n}\n\nfunction arc_meta_open_graph($atts)\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    extract(lAtts(array(\n        \'site_name\' => $prefs[\'sitename\'],\n        \'title\' => null,\n        \'description\' => null,\n        \'url\' => null,\n        \'image\' => null\n    ), $atts));\n\n    $title = $title===null ? _arc_meta_title() : $title;\n    $description = $description===null ? _arc_meta_description() : $description;\n    $url = $url===null ? _arc_meta_url() : $url;\n    $image = $image===null ? _arc_meta_image() : $image;\n\n    $html = \'\';\n    if ($site_name) {\n        $html .= \"<meta property=\\\"og:site_name\\\" content=\\\"$site_name\\\" />\";\n    }\n    if ($title) {\n        $html .= \"<meta property=\\\"og:title\\\" content=\\\"$title\\\" />\";\n    }\n    if ($description) {\n        $html .= \"<meta property=\\\"og:description\\\" content=\\\"$description\\\" />\";\n    }\n    if ($url) {\n        $html .= \"<meta property=\\\"og:url\\\" content=\\\"$url\\\" />\";\n    }\n    if ($image) {\n        $html .= \"<meta property=\\\"og:image\\\" content=\\\"$image\\\" />\";\n    }\n\n    return $html;\n}\n\nfunction arc_meta_twitter_card($atts)\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    extract(lAtts(array(\n        \'card\' => \'summary\',\n        \'title\' => null,\n        \'description\' => null,\n        \'url\' => null,\n        \'image\' => null\n    ), $atts));\n\n    $title = $title===null ? _arc_meta_title() : $title;\n    $description = $description===null ? _arc_meta_description() : $description;\n    $url = $url===null ? _arc_meta_url() : $url;\n    $image = $image===null ? _arc_meta_image() : $image;\n\n    $html = \"<meta name=\\\"twitter:card\\\" content=\\\"$card\\\" />\";\n    $html .= \"<meta name=\\\"twitter:title\\\" content=\\\"$title\\\" />\";\n    $html .= \"<meta name=\\\"twitter:description\\\" content=\\\"$description\\\" />\";\n\n    if ($url) {\n        $html .= \"<meta name=\\\"twitter:url\\\" content=\\\"$url\\\" />\";\n    }\n    if ($image) {\n        $html .= \"<meta name=\\\"twitter:image:src\\\" content=\\\"$image\\\" />\";\n    }\n\n    return $html;\n\n}\n\nfunction arc_meta_organization($atts)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => $prefs[\'sitename\'],\n        \'logo\' => null,\n        \'facebook\' => null,\n        \'gplus\' => null,\n        \'twitter\' => null\n    ), $atts));\n\n    if (empty($logo)) {\n        trigger_error(\'arc_meta_organization missing logo attribute\', E_USER_WARNING);\n    }\n\n    $data = array(\n        \'@context\' => \'http://schema.org\',\n        \'@type\' => \'Organization\',\n        \'name\' => $name,\n        \'logo\' => $logo,\n        \'url\' => hu\n    );\n\n    $sameAs = array();\n    if (!empty($facebook)) {\n        $sameAs[] = $facebook;\n    }\n    if (!empty($gplus)) {\n        $sameAs[] = $gplus;\n    }\n    if (!empty($twitter)) {\n        $sameAs[] = $twitter;\n    }\n\n    if (!empty($sameAs)) {\n        $data[\'sameAs\'] = $sameAs;\n    }\n\n    return \'<script type=\"application/ld+json\">\' . str_replace(\'\\\\/\', \'/\', json_encode($data)) . \'</script>\';\n\n}\n\nfunction arc_meta_person($atts)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => $prefs[\'sitename\'],\n        \'logo\' => null,\n        \'facebook\' => null,\n        \'gplus\' => null,\n        \'twitter\' => null\n    ), $atts));\n\n    $data = array(\n        \'@context\' => \'http://schema.org\',\n        \'@type\' => \'Person\',\n        \'name\' => $name,\n        \'url\' => hu\n    );\n\n    $sameAs = array();\n    if (!empty($facebook)) {\n        $sameAs[] = $facebook;\n    }\n    if (!empty($gplus)) {\n        $sameAs[] = $gplus;\n    }\n    if (!empty($twitter)) {\n        $sameAs[] = $twitter;\n    }\n\n    if (!empty($sameAs)) {\n        $data[\'sameAs\'] = $sameAs;\n    }\n\n    return \'<script type=\"application/ld+json\">\' . str_replace(\'\\\\/\', \'/\', json_encode($data)) . \'</script>\';\n\n}\n\nfunction _arc_meta_title()\n{\n    global $thisarticle, $prefs, $s, $c;\n\n    if (!empty($thisarticle[\'thisid\'])) {\n        $title = txpspecialchars($thisarticle[\'title\']);\n    } elseif (!empty($s) and $s != \'default\') {\n        $title = txpspecialchars(fetch_section_title($s));\n    } elseif (!empty($c)) {\n        $title = txpspecialchars(fetch_category_title($c));\n    } else {\n        $title = txpspecialchars($prefs[\'sitename\']);\n    }\n\n    return $title;\n\n}\n\nfunction _arc_meta_url()\n{\n    global $thisarticle, $s, $c;\n\n    if (!empty($thisarticle[\'thisid\'])) {\n        $url = permlinkurl($thisarticle);\n    } elseif (!empty($s) and $s != \'default\') {\n        $url = pagelinkurl(array(\'s\' => $s));\n    } elseif (!empty($c)) {\n        $url = pagelinkurl(array(\'c\' => $c));\n    } else {\n        $url = hu;\n    }\n    return $url;\n}\n\nfunction _arc_meta_image()\n{\n    global $thisarticle;\n\n    $image = $thisarticle[\'article_image\'];\n\n    if (intval($image)) {\n        if ($rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($image))) {\n            $image = imagesrcurl($rs[\'id\'], $rs[\'ext\']);\n        } else {\n            $image = null;\n        }\n\n    } else {\n        $meta = _arc_meta();\n        if (!empty($meta[\'image\']) && $rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($meta[\'image\']))) {\n            $image = imagesrcurl($rs[\'id\'], $rs[\'ext\']);\n        } else {\n            $image = null;\n        }\n\n    }\n\n    return $image;\n}\n\n/**\n * Fetch meta description from the given (or automatic) context.\n *\n * Category context may be refined by specifying the content type as well\n * after a dot. e.g. category.image to check image context category.\n *\n * @param string $type Flavour of meta content to fetch (section, category, article)\n */\nfunction _arc_meta_description($type = null)\n{\n    global $thisarticle;\n\n    $metaDescription = getMetaDescription($type);\n\n    if (!empty($metaDescription)) {\n        $description = txpspecialchars($metaDescription);\n    } elseif (!empty($thisarticle[\'excerpt\'])) {\n        $description = strip_tags($thisarticle[\'excerpt\']);\n        $description = substr($description, 0, 200);\n        $description = txpspecialchars($description);\n    } elseif (!empty($thisarticle[\'body\'])) {\n        $description = strip_tags($thisarticle[\'body\']);\n        $description = substr($description, 0, 200);\n        $description = txpspecialchars($description);\n    } else {\n        $description = null;\n    }\n\n    return $description;\n}\n\n/**\n * Fetches meta data for the current context.\n * @param  string  $type   Context\n * @param  int     $typeId Context\'s primary key\n * @param  boolean $admin  Pass true if called from the admin area\n * @return array           Meta data\n */\nfunction _arc_meta($type = null, $typeId = null, $admin = false)\n{\n    global $thisarticle, $thiscategory, $thissection, $s, $c, $arc_meta;\n\n    if (empty($arc_meta)) {\n        $arc_meta = array(\n            \'id\' => null,\n            \'title\' => null,\n            \'description\' => null,\n            \'image\' => null,\n            \'robots\' => null\n        );\n\n        if ($admin === true && $typeId === null) {\n            return $arc_meta;\n        }\n\n        if (empty($type)) {\n            if (!empty($thisarticle[\'thisid\'])) {\n                $typeId = $typeId ?: $thisarticle[\'thisid\'];\n                $type = \'article\';\n            } elseif (!empty($c)) {\n                $typeId = $typeId ?: $c;\n                $type = \'category\';\n            } elseif (!empty($s)) {\n                $typeId = $typeId ?: $s;\n                $type = \'section\';\n            }\n\n        } elseif ($typeId === null) {\n            if (strpos($type, \'category\') === 0) {\n                $type = \'category\';\n                $typeId = $thiscategory ? $thiscategory[\'name\'] : $c;\n            } elseif ($type === \'section\') {\n                $typeId = $thissection[\'name\'] ? $thissection[\'name\'] : $s;\n            } elseif ($type === \'article\') {\n                assert_article();\n                $typeId = $thisarticle ? $thisarticle[\'thisid\'] : null;\n            }\n\n        }\n\n        if (empty($type) || empty($typeId)) {\n            if (!empty($thisarticle[\'thisid\'])) {\n                $typeId = $thisarticle[\'thisid\'];\n                $type = \'article\';\n            } elseif (!empty($c)) {\n                $typeId = $c;\n                $type = \'category\';\n            } elseif (!empty($s)) {\n                $typeId = $s;\n                $type = \'section\';\n            }\n\n        }\n\n        if (!empty($typeId) && !empty($type)) {\n            $meta = safe_row(\'*\', \'arc_meta\', \"type_id=\'$typeId\' AND type=\'$type\'\");\n            $arc_meta = array_merge($arc_meta, $meta);\n            return $arc_meta;\n        }\n\n    }\n\n    return $arc_meta;\n}\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'_arc_meta_article_meta\', \'article_ui\', \'url_title\');\n    register_callback(\'_arc_meta_article_meta_save\', \'ping\');\n    register_callback(\'_arc_meta_article_meta_save\', \'article_saved\');\n    register_callback(\'_arc_meta_article_meta_save\', \'article_posted\');\n\n    register_callback(\'_arc_meta_section_meta\', \'section_ui\', \'extend_detail_form\');\n    register_callback(\'_arc_meta_section_meta_save\', \'section\', \'section_save\');\n\n    register_callback(\'_arc_meta_category_meta\', \'category_ui\', \'extend_detail_form\');\n    register_callback(\'_arc_meta_category_meta_save\', \'category\', \'cat_article_save\');\n\n    if (!empty($prefs[\'arc_meta_section_tab\'])) {\n        add_privs(\'arc_meta_section_tab\', \'1,2,3,4\');\n        register_tab($prefs[\'arc_meta_section_tab\'], \'arc_meta_section_tab\', \'Sections Meta Data\');\n        register_callback(\'arc_meta_section_tab\', \'arc_meta_section_tab\');\n    }\n}\n\nfunction _arc_meta_install()\n{\n    $sql = \"CREATE TABLE IF NOT EXISTS \" . PFX . \"arc_meta (\n        `id` int(11) NOT NULL AUTO_INCREMENT,\n        `type` varchar(8) NOT NULL,\n        `type_id` varchar(128) NOT NULL,\n        `title` varchar(250) DEFAULT NULL,\n        `override_title` tinyint(1) DEFAULT NULL,\n        `robots` varchar(45) DEFAULT NULL,\n        PRIMARY KEY (`id`)\n        ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;\";\n\n    if (!safe_query($sql)) {\n        return \'Error - unable to create arc_meta table\';\n    }\n\n    $dbTable = getThings(\'DESCRIBE \' . safe_pfx(\'arc_meta\'));\n\n    if (!in_array(\'robots\', $dbTable)) {\n        safe_alter(\'arc_meta\', \'ADD robots VARCHAR(45)\');\n    }\n\n    if (!in_array(\'image\', $dbTable)) {\n        safe_alter(\'arc_meta\', \'ADD image INT(11) DEFAULT NULL\');\n        // Increased size of title and description columns.\n        safe_alter(\'arc_meta\', \'CHANGE title title VARCHAR(250) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL\');\n    }\n\n    // Upgrade plugin to 2.x.\n    if (in_array(\'description\', $dbTable)) {\n        // Copy meta description data to main TXP tables.\n        safe_query(\'UPDATE \' . safe_pfx(\'textpattern\') . \', \' . safe_pfx(\'arc_meta\') . \' SET textpattern.description = arc_meta.description WHERE textpattern.ID = arc_meta.type_id AND arc_meta.type = \\\'article\\\' AND arc_meta.description IS NOT NULL AND textpattern.description = \\\'\\\'\');\n        safe_query(\'UPDATE \' . safe_pfx(\'txp_category\') . \', \' . safe_pfx(\'arc_meta\') . \' SET txp_category.description = arc_meta.description WHERE txp_category.name = arc_meta.type_id AND arc_meta.type = \\\'category\\\' AND arc_meta.description IS NOT NULL AND txp_category.description = \\\'\\\'\');\n        safe_query(\'UPDATE \' . safe_pfx(\'txp_section\') . \', \' . safe_pfx(\'arc_meta\') . \' SET txp_section.description = arc_meta.description WHERE txp_section.name = arc_meta.type_id AND arc_meta.type = \\\'section\\\' AND arc_meta.description IS NOT NULL AND txp_section.description = \\\'\\\'\');\n        // Drop old meta description column.\n        safe_alter(\'arc_meta\', \'DROP COLUMN description\');\n    }\n\n    // Setup the plugin preferences.\n    _arc_meta_install_prefs();\n\n    return;\n}\n\n/**\n * Setup the plugin preferences if they have not yet been set.\n */\nfunction _arc_meta_install_prefs()\n{\n    if (!isset($prefs[\'arc_meta_homepage_title\'])) {\n        set_pref(\'arc_meta_homepage_title\', \'%n | %t\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 10);\n    }\n    if (!isset($prefs[\'arc_meta_article_title\'])) {\n        set_pref(\'arc_meta_article_title\', \'%a | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 20);\n    }\n    if (!isset($prefs[\'arc_meta_comment_title\'])) {\n        set_pref(\'arc_meta_comment_title\', gTxt(\'comments_on\').\' %a | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 30);\n    }\n    if (!isset($prefs[\'arc_meta_search_title\'])) {\n        set_pref(\'arc_meta_search_title\', gTxt(\'search_results\') . \': \' . \'%q | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 40);\n    }\n    if (!isset($prefs[\'arc_meta_category_title\'])) {\n        set_pref(\'arc_meta_category_title\', \'%c | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 50);\n    }\n    if (!isset($prefs[\'arc_meta_section_title\'])) {\n        set_pref(\'arc_meta_section_title\', \'%s | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 60);\n    }\n    if (!isset($prefs[\'arc_meta_section_category_title\'])) {\n        set_pref(\'arc_meta_section_category_title\', \'%c - %s | %n\', \'arc_meta\', PREF_PLUGIN, \'text_input\', 70);\n    }\n    if (!isset($prefs[\'arc_meta_section_tab\'])) {\n        set_pref(\'arc_meta_section_tab\', \'content\', \'arc_meta\', PREF_PLUGIN, \'_arc_meta_section_tab_select\', 80);\n    }\n    return;\n}\n\nfunction _arc_meta_section_tab_select($name, $val)\n{\n    $vals = array(\n        \'content\'=> gtxt(\'arc_meta_section_tab_content\'),\n        \'extensions\'=> gtxt(\'arc_meta_section_tab_extensions\'),\n        \'hidden\'=> gtxt(\'arc_meta_section_tab_hidden\')\n    );\n    return selectInput($name, $vals, $val);\n}\n\nfunction _arc_meta_uninstall()\n{\n    $sql = \"DROP TABLE IF EXISTS \".PFX.\"arc_meta;\";\n    if (!safe_query($sql)) {\n        return \'Error - unable to delete arc_meta table\';\n    }\n\n    $sql = \"DELETE FROM  \".PFX.\"txp_prefs WHERE event=\'arc_meta\';\";\n    if (!safe_query($sql)) {\n        return \'Error - unable to delete arc_meta preferences\';\n    }\n\n    safe_delete(\'txp_lang\', \"owner LIKE \'arc\\_meta\'\");\n\n    return;\n}\n\nfunction arc_meta_section_tab($event, $step)\n{\n    switch ($step) {\n        case \'edit\':\n            arc_meta_section_edit();\n            break;\n\n        case \'save\':\n            _arc_meta_section_meta_save($event, $step);\n            // Fall through to section list.\n\n        default:\n            arc_meta_section_list();\n            break;\n    }\n}\n\nfunction arc_meta_section_list()\n{\n    global $event;\n\n    pagetop(\'Section Meta Data\');\n\n    $html = \'<h1 class=\"txp-heading\">Section Meta Data</h1>\';\n\n    $rs = safe_query(\n        \'SELECT sections.*, arc_meta.title AS meta_title FROM \' . safe_pfx(\'txp_section\') . \' sections LEFT JOIN \' . safe_pfx(\'arc_meta\') . \' arc_meta ON arc_meta.type = \"section\" AND arc_meta.type_id = sections.name WHERE 1=1 ORDER BY CASE WHEN sections.name = \"default\" THEN 1 ELSE 2 END, sections.name ASC\'\n    );\n\n    if ($rs) {\n        $html .= n . \'<div id=\"\' . $event . \'_container\" class=\"txp-container\">\';\n\n        $html .= n . \'<div class=\"txp-listtables\">\' . n\n                . n . startTable(\'\', \'\', \'txp-list\')\n                . n . \'<thead>\'\n                . n . tr(hCell(\'Name\') . hCell(\'Title\') . hCell(gtxt(\'arc_meta_title\')) . hCell(\'Manage\'))\n            . n . \'</thead>\';\n\n        while ($row = nextRow($rs)) {\n            $editLink = href(\n                gTxt(\'edit\'),\n                \'?event=arc_meta_section_tab&amp;step=edit&amp;name=\' . $row[\'name\']\n            );\n            $html .= n . tr(\n                n . td($row[\'name\']) . td($row[\'title\']) . td($row[\'meta_title\']) . td($editLink)\n            );\n        }\n\n        $html .= n . endTable();\n\n        $html .= n . \'</div>\';\n\n    }\n\n    echo $html;\n}\n\nfunction arc_meta_section_edit()\n{\n    global $event;\n\n    $name = gps(\'name\');\n\n    $rs = safe_query(\n        \'SELECT sections.title AS section, sections.description, arc_meta.* FROM \' . safe_pfx(\'txp_section\') . \' sections LEFT JOIN \' . safe_pfx(\'arc_meta\') . \' arc_meta ON arc_meta.type = \"section\" AND arc_meta.type_id = sections.name WHERE sections.name=\"\' . doSlash($name) . \'\"\'\n    );\n\n    $meta = nextRow($rs);\n\n    pagetop(\'Section Meta Data\');\n\n    $form = \'\';\n\n    $form .= \'<div class=\"txp-edit\">\';\n    $form .= hed(\'Edit Section Meta Data\', 2);\n\n    // We include the section title as a disabled field for the user\'s\n    // reference.\n    $form .= \'<div class=\"txp-form-field edit-section-section\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(\'Section\', \'label\', \' for=\"section\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'section\', $meta[\'section\'], \'\', \'\', \'\', \'32\', \'\', \'section\', true) . \'</div>\';\n    $form .= \'</div>\';\n\n    // Meta data fields\n    $form .= hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= hInput(\'name\', $name);\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field txp-form-field-textarea edit-section-arc_meta_description\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_description\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . text_area(\'arc_meta_description\', null, null, $meta[\'description\'], \'arc_meta_description\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    $form .= \'<p class=\"txp-edit-actions\">\' . sLink(\'arc_meta_section_tab\', \'\', gTxt(\'cancel\'), \'txp-button\') . sInput(\'save\') . eInput($event) . fInput(\'submit\', \'save\', gTxt(\'Save\'), \'publish\') . \'</p>\';\n\n    $form .= \'</div>\';\n\n    $html = \'<div id=\"\' . $event . \'_container\" class=\"txp-container\">\' . form($form, \'\', \'\', \'post\', \'edit-form\') . \'</div>\';\n\n    echo $html;\n}\n\nfunction arc_meta_options()\n{\n    $link = \'?event=prefs#prefs_group_arc_meta\';\n    header(\'Location: \' . $link);\n}\n\nfunction _arc_meta_article_meta($event, $step, $data, $rs)\n{\n    // Get the article meta data.\n    $articleId = !empty($rs[\'ID\']) ? $rs[\'ID\'] : null;\n    $meta = _arc_meta(\'article\', $articleId, true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \"<p class=\'arc_meta_title\'>\";\n    $form .= tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'<br />\';\n    $form .= fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\');\n    $form .= \"</p>\";\n    $form .= \"<p class=\'edit-category-arc_meta_robots\'>\";\n    $form .= tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'<br />\';\n    $form .= selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\');\n    $form .= \'</p>\';\n\n    return $form . $data;\n}\n\nfunction _arc_meta_section_meta($event, $step, $data, $rs)\n{\n    // Get the section meta data.\n    $sectionName = !empty($rs[\'name\']) ? $rs[\'name\'] : null;\n    $meta = _arc_meta(\'section\', $sectionName, true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-section-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    return $data . $form;\n}\n\nfunction _arc_meta_category_meta($event, $step, $data, $rs)\n{\n    // Make sure that this is an article category (we don\'t support other\n    // category types).\n    if ($rs[\'type\']!=\'article\') {\n        return $data;\n    }\n\n    // Get the existing meta data for this category.\n    $meta = _arc_meta(\'category\', $rs[\'name\'], true);\n\n    $form = hInput(\'arc_meta_id\', $meta[\'id\']);\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_title\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_title\'), \'label\', \' for=\"arc_meta_title\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'text\', \'arc_meta_title\', $meta[\'title\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_title\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_image\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_image\'), \'label\', \' for=\"arc_meta_image\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . fInput(\'number\', \'arc_meta_image\', $meta[\'image\'], \'\', \'\', \'\', \'32\', \'\', \'arc_meta_image\') . \'</div>\';\n    $form .= \'</div>\';\n    $form .= \'<div class=\"txp-form-field edit-category-arc_meta_robots\">\';\n    $form .= \'<div class=\"txp-form-field-label\">\' . tag(gtxt(\'arc_meta_robots\'), \'label\', \' for=\"arc_meta_description\"\') . \'</div>\';\n    $form .= \'<div class=\"txp-form-field-value\">\' . selectInput(\'arc_meta_robots\', _arc_meta_robots(), $meta[\'robots\'], \'arc_meta_robots\') . \'</div>\';\n    $form .= \'</div>\';\n\n    return $data . $form;\n}\n\nfunction _arc_meta_article_meta_save($event, $step)\n{\n    $articleId = empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\'];\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'article\',\n        \'type_id\' => $articleId,\n        \'title\' => doSlash($metaTitle),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n}\n\nfunction _arc_meta_section_meta_save($event, $step)\n{\n    $sectionName = gps(\'name\');\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaDescription = gps(\'arc_meta_description\');\n    $metaImage = gps(\'arc_meta_image\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'section\',\n        \'type_id\' => $sectionName,\n        \'title\' => doSlash($metaTitle),\n        \'image\' => intval($metaImage),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    if (empty($values[\'image\'])) {\n        unset($values[\'image\']);\n        $sql[] = \"image = NULL\";\n    }\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaImage) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n\n    // Update the meta description.\n    $metaDescription = doSlash($metaDescription);\n    safe_update(\'txp_section\', \"description = \'$metaDescription\'\", \"name=\'$sectionName\'\");\n}\n\nfunction _arc_meta_category_meta_save($event, $step)\n{\n    $categoryName = gps(\'name\');\n\n    $metaId = gps(\'arc_meta_id\');\n    $metaTitle = gps(\'arc_meta_title\');\n    $metaImage = gps(\'arc_meta_image\');\n    $metaRobots = gps(\'arc_meta_robots\');\n\n    $values = array(\n        \'type\' => \'category\',\n        \'type_id\' => $categoryName,\n        \'title\' => doSlash($metaTitle),\n        \'image\' => intval($metaImage),\n        \'robots\' => doSlash($metaRobots)\n    );\n\n    foreach ($values as $key => $value) {\n        $sql[] = \"$key = \'$value\'\";\n    }\n    $sql = implode(\', \', $sql);\n\n    if ($metaId) {\n        // Update existing meta data.\n        safe_update(\'arc_meta\', $sql, \"id=$metaId\");\n\n    } elseif (!empty($metaTitle) || !empty($metaDescription) || !empty($metaImage) || !empty($metaRobots)) {\n        // Create new meta data only if there is data to be saved.\n        safe_insert(\'arc_meta\', $sql);\n\n    }\n}\n\nfunction _arc_meta_robots()\n{\n    return array(\n        \'index, follow\' => \'index, follow\',\n        \'index, nofollow\' => \'index, nofollow\',\n        \'noindex, follow\' => \'noindex, follow\',\n        \'noindex, nofollow\' => \'noindex, nofollow\'\n    );\n}\n', '1a1ea7dbf4ad86c51020dd4637071e3f', '#@admin\n#@language en-gb\narc_meta => Metadata (arc_meta)\narc_meta_title => Meta title\narc_meta_description => Meta description\narc_meta_image => Meta image\narc_meta_robots => Meta robots\narc_meta => arc_meta\narc_meta_article_title => Article page titles pattern\narc_meta_comment_title => Comment page titles pattern\narc_meta_search_title => Search page titles pattern\narc_meta_category_title => Category titles pattern\narc_meta_section_title => Section titles pattern\narc_meta_section_category_title => Section category titles pattern\narc_meta_homepage_title => Homepage title pattern\narc_meta_section_tab => Additional panel location for sections meta data edit\narc_meta_section_tab_content => Content\narc_meta_section_tab_extensions => Extensions\narc_meta_section_tab_hidden => Hidden\n#@language fr-fr\narc_meta => Métadonnées (arc_meta)\narc_meta_title => Méta-titre\narc_meta_description => Méta-description\narc_meta_image => Méta-image\narc_meta_robots => Méta-robots\narc_meta_article_title => Méta-titre des articles\narc_meta_comment_title => Méta-titre des commentaires\narc_meta_search_title => Méta-titre des résultats de recherche\narc_meta_category_title => Méta-titre des catgéories\narc_meta_section_title => Méta-titre des sections\narc_meta_section_category_title => Méta-titre des catégories d\'une section\narc_meta_homepage_title => Méta-titre de la page d\'accueil\narc_meta_section_tab => Panneau d\'édition des métadonnées de sections\narc_meta_section_tab_content => Contenu\narc_meta_section_tab_extensions => Extensions\narc_meta_section_tab_hidden => Caché', '', 5, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('ied_plugin_composer', 1, 'Yura Linnyk / Stef Dawson / Steve Dickinson', 'http://stefdawson.com/', '1.1.1', 'Create, publish and edit plugins from within Textpattern CMS', '<h1>Plugin composer</h1>\n\n<p>Creates a new page under the <i>Extensions</i> tab where you can edit and export plugins that are already installed in Textpattern, as well as create or upload new plugins created by the community.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Create, edit, upload or install plugin code and documentation, then publish your wares in the standard Textpattern plugin format.</li>\n	<li>Full support for the official zem_tpl.php plugin template and the modified &#8220;code first&#8221; version by net-carver, which also incorporates a &#8216;style&#8217; segment for your help text (although it should be rarely needed).</li>\n	<li>Optional syntax checker to protect the site / admin side from bad code.</li>\n	<li>Export plugins in a variety of formats, allowing you to switch between editing in the plugin composer or your favourite editing program. You can export:\n	<ul>\n		<li>a standard BASE64-encoded text file.</li>\n		<li>a gzipped version (useful for large plugins).</li>\n		<li>a php file in the standard template format.</li>\n		<li>Textpacks on their own &#8212; any combination of languages.</li>\n	</ul></li>\n	<li>Support for all plugin types: Library, Public, and Admin (with or without <span class=\"caps\">AJAX</span>).</li>\n	<li>Specify a recommended plugin load order if your plugin needs special powers.</li>\n	<li>Documentation can be written in Textile or <span class=\"caps\">HTML</span>.</li>\n	<li>Take advantage of the <a href=\"http://forum.textpattern.com/viewtopic.php?id=13089\">TinyMCE <span class=\"caps\">WYSIWYG</span> editor</a> for the help section, or a variety of javascript syntax highlighters / editors for code. See the <a href=\"#ied_plugin_setup\">setup</a> section for more on the available editors.</li>\n	<li>Built-in Textile help viewer (thanks to net-carver&#8217;s Plugin Help Viewer) to allow you to preview your Help text during development.</li>\n	<li>Set a code &#8220;restore point&#8221; and roll back to that point if things go sideways. Also useful for returning plugin source code to its as-installed state.</li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p class=\"important\">Requires Textpattern 4.6.0+</p>\n\n<p>Download the plugin (v0.5 and above) from <a href=\"http://stefdawson.com/ied_plugin_composer\">stefdawson.com</a>, paste the code into Textpattern&#8217;s <em>Admin-&gt;Plugins</em> page, install and enable the plugin.</p>\n\n<p>The default preferences are automatically created when you install the plugin or visit the setup screen, by clicking the <strong>Setup</strong> button in the top right corner of the <em>Extensions-&gt;Plugin composer</em> panel. See the <a href=\"#ied_plugin_setup\">setup section</a> for details.</p>\n\n<p>To remove the plugin composer (noooo!) simply delete it as normal from the <em>Admin-&gt;Plugins</em> panel. All the preferences will automatically be removed as well. <span class=\"caps\">NOTE</span>: deleting the plugin from the plugin composer window itself <strong>will not</strong> delete the preferences unless you have set the plugin to respond to &#8216;delete&#8217; lifecycle events.</p>\n\n<h2 id=\"ied_plugin_list\">List panel</h2>\n\n<p>At the top of the main page is a collapsible area labelled <code>Installation</code>. This is dealt with in the <a href=\"#ied_plugin_create\">Creating plugins</a> section; the rest of the page lists all installed plugins. The columns are:</p>\n\n<dl>\n	<dt><strong>Plugin</strong></dt>\n	<dd>Click the plugin name to <a href=\"#ied_plugin_edit\">edit</a> it. If the plugin has preferences associated with it, you will also see an <strong>[Options]</strong> link.</dd>\n	<dt><strong>Author</strong></dt>\n	<dd>If available, click the author to visit their site.</dd>\n	<dt><strong>Version (Modified)</strong></dt>\n	<dd>Shows the current version number and whether the plugin has been modified from when it was either created or installed. If it has, you can click the version number to restore the plugin code to its installed state or last <a href=\"#ied_plugin_restore\">restore point</a>.</dd>\n	<dt><strong>Description</strong></dt>\n	<dd>A brief one-line overview of what the plugin does.</dd>\n	<dt><strong>Publish</strong></dt>\n	<dd>Three links\n	<dl>\n		<dd><em>Publish</em> : exports the plugin as a .txt file for distribution to other Textpattern users.</dd>\n		<dd><em>Zip</em> : exports the plugin as a compressed (gzipped) .txt file for distribution.</dd>\n		<dd><em>Help</em> : displays the (textile processed) plugin documentation, if there is any.</dd>\n	</dl></dd>\n	<dd>All these tasks (and more) can be performed from the <a href=\"#ied_plugin_edit\">edit panel</a>.</dd>\n	<dt><strong>Order</strong></dt>\n	<dd>The recommended plugin load order from 1 (loaded first) to 9 (loaded last). 5 is default.</dd>\n	<dd>Note that if using the plugin cache directory this feature is <em>only available if the <code>$plugin[&#39;order&#39;]</code> string is in the template to begin with.</em> So if your load order keeps returning to &#8216;5&#8217;, edit your template directly to add that string, or paste your plugin into the relevant parts of the <a href=\"https://github.com/textpattern/textpattern-plugin-template/blob/master/zem_plugin.php\">empty plugin template</a> and upload it.</dd>\n	<dt><strong>Active</strong></dt>\n	<dd>Enable or disable a plugin by clicking entries in this column &#8212; this will only trigger any enabled/disabled lifecycle event in your plugin if you have set the plugin to do so.</dd>\n</dl>\n\n<p>Use the multi-edit checkboxes (you can shift click them to select more than one at a time) to delete plugins or make mass changes. Deletion will only trigger the &#8216;deleted&#8217; lifecycle event if you have told the composer to allow plugins to respond to lifecycle events.</p>\n\n<p>If you are using the plugin_cache_dir (<em>Admin-&gt;Advanced Prefs</em>), any plugins in the standard template format uploaded to this directory will be available immediately for editing and testing. You can edit and publish the plugins in the same manner as the regular, installed plugins above, with the following exceptions:</p>\n\n<ul>\n	<li>There is no concept of &#8216;modified&#8217; or of restore points as the files always represent the most up-to-date version. Use your own external versioning.</li>\n	<li>Cached plugins are &#8220;always on&#8221; and do not need to be installed. To deactivate the plugin the file must be removed from the plugin cache directory.</li>\n	<li>It follows that the lifecycle notification events (installed, deleted, enabled, disabled) are not triggered from plugins in the cache directory.</li>\n	<li>The recommended load order cannot be changed from the list panel; it can only be altered via the <a href=\"#ied_plugin_edit\">edit panel</a>. The specified load order is only written to the file when the plugin is exported. Load order is alphabetical for all plugins in the plugin_cache directory.</li>\n</ul>\n\n<h2 id=\"ied_plugin_create\">Creating, installing and naming plugins</h2>\n\n<p>There are a few ways of creating plugins from the composer&#8217;s list panel. Expand the &#8216;Installation&#8217; twisty at the top of the composer&#8217;s list panel to see them all.</p>\n\n<p>The first is to create a blank, empty plugin; use the first text box for this and the <strong>Create new plugin</strong> button. Points to note:</p>\n\n<ul>\n	<li>If you use a standard plugin name (e.g. <code>abc_my_plugin</code>) it will be created in the database.</li>\n	<li>If you add <code>.php</code> to the plugin name, it will be created in the plugin cache directory in the standard template format.</li>\n	<li>Avoid specifying the version of the plugin in the name &#8212; you should use the <b>Rename file</b> or <b>Export</b> options in the <a href=\"#ied_plugin_edit\">edit panel</a> to alter the filename into your preferred filename format (see <a href=\"#ied_plugin_setup\">setup</a>).</li>\n</ul>\n\n<p>The second method of creating a plugin is to upload one using the Browse/upload box. Please note:</p>\n\n<ul>\n	<li>You can upload code as either:\n	<ul>\n		<li>a standard plugin base64 .txt file.</li>\n		<li>a standard plugin template (either code-first or help-first, complete with <span class=\"caps\">CSS</span> areas).</li>\n		<li>a <span class=\"caps\">PHP</span> file containing raw code only.</li>\n	</ul></li>\n	<li>The plugin will always be installed in the database when using this method (upload template plugins manually via <span class=\"caps\">FTP</span> or create a new one with a .php extension and paste the code into it if you want it in the plugin cache).</li>\n	<li>If the plugin exists it will be updated with your new info.</li>\n	<li>This is the only place you can upload raw <span class=\"caps\">PHP</span> with <code>&lt;?php ?&gt;</code> markers. Everywhere else you should use a full template.</li>\n	<li>If you add a plugin Name in the box above the upload widget before clicking <strong>Upload</strong>, your plugin will take that name.</li>\n</ul>\n\n<p>You may also install a plugin just like on the <em>Admin-&gt;Plugins</em> panel by copying and pasting the contents of a standard plugin .txt file into the Install textarea and hitting the <strong>Install</strong> button. Note there are a few differences from the core&#8217;s installer:</p>\n\n<ul>\n	<li>There is no preview step; the plugin is installed immediately, so be sure you trust the code!</li>\n	<li>You can choose whether the plugin is allowed to run its &#8216;install&#8217; lifecycle event by using the radio buttons that appear.</li>\n	<li>You can elect to have the plugin auto-enable itself by selecting the appropriate radio button entry prior to clicking <em>Install</em>.</li>\n	<li>The radio buttons take on the default settings from the <a href=\"#ied_plugin_setup\">setup panel</a>.</li>\n</ul>\n\n<p>By convention, every plugin should be created with a three-letter prefix, an underscore, then the name of the plugin. You are free to choose your own three-letter prefix (usually your initials) subject to <a href=\"http://docs.textpattern.io/development/plugin-developer-prefixes\">avoiding ones already taken by other plugin authors</a> so people can get to know your work. Plus, it groups your plugins together in the <a href=\"http://textpattern.org/plugins\">plugin respository</a>. All functions, variables (including <span class=\"caps\">DOM</span> nodes), <span class=\"caps\">CSS</span> classes, Textpack strings and anything else you inject into the global scope should be prefixed by <strong>at least</strong> your three letter code; preferably the whole plugin name (or abridged version thereof) to avoid namespace clashes with your own and other plugins.</p>\n\n<h2 id=\"ied_plugin_edit\">Edit panel</h2>\n\n<p>This panel is where you craft your masterpiece. It is divided into various sections and boxes. The <em>Panels</em> area allows you to switch between the various parts of the plugin. Each plugin has:</p>\n\n<ul>\n	<li>Meta content such as name, version, and flags.</li>\n	<li><span class=\"caps\">PHP</span> code.</li>\n	<li>Textpack strings.</li>\n	<li>Documentation / styling.</li>\n</ul>\n\n<p>You edit each part separately to reduce on-screen clutter, and when you switch subpanels your selection is remembered so that panel appears when editing other plugins. Hitting <strong>Save</strong> commits changes from the whole page &#8212; including subpanels that aren&#8217;t currently visible.</p>\n\n<h3>Meta subpanel</h3>\n\n<dl>\n	<dt><strong>Name</strong></dt>\n	<dd>The name of your plugin, which you can change at any time. Note that if you are editing a file from the plugin cache directory and the <code>$plugin[&#39;name&#39;]</code> row is commented out in the file, you will not be able to change the plugin name; it will always be the name of the file.</dd>\n	<dt><strong>Version</strong></dt>\n	<dd>The current version of your plugin. You are free to choose your own versioning convention.</dd>\n	<dd>To the right of these boxes will be some optional items that appear depending on the current mode of operation:\n	<dl>\n		<dd><strong>Restore point</strong> (in-database plugins only): selecting this checkbox will (upon clicking the <strong>Save</strong> button) store the current code as a baseline to which you may &#8220;roll back&#8221; to at a later date. See <a href=\"#ied_plugin_restore\">restore points</a>.</dd>\n		<dd><strong>Rename file</strong> (cache_dir plugins only): by default, when you save a file in the plugin cache directory, it is overwritten with your changes. Once a plugin is released you would normally keep a copy, taken from the plugin_cache_dir, for safekeeping. If you then subsequently modify the plugin and increase the version number, you may wish to alter the filename as well. Checking this box will (upon clicking the <strong>Save</strong> button) rename the file in the plugin cache directory to reflect the current version number. See the <a href=\"#ied_plugin_setup\">setup panel</a> for details on customising the filename format.</dd>\n		<dd><strong>Enable</strong> (in-database plugins only): switch the plugin on or off after Save.</dd>\n		<dd><strong>(file name)</strong> (cache_dir plugins only) : the current filename you are editing.</dd>\n	</dl></dd>\n	<dt><strong>Description</strong></dt>\n	<dd>Very brief one-liner describing your plugin&#8217;s core function / reason for existence. 255 characters maximum.</dd>\n	<dt><strong>Author</strong> :</dt>\n	<dd>You!</dd>\n	<dt><strong>Website</strong></dt>\n	<dd>Your home page or plugin page. Will be hyperlinked to your Author name in the <a href=\"#ied_plugin_list\">list panel</a>.</dd>\n	<dt><strong>Plugin type</strong></dt>\n	<dd>Choose one of the types that best fits the intended use of your plugin. If you choose &#8216;Public&#8217; and try to access the admin side in your code, a warning will be issued when the plugin is saved so you can choose a more appropriate type.</dd>\n	<dt><strong>Flags</strong></dt>\n	<dd>Choose which plugin flags are to be associated with the plugin:\n	<dl>\n		<dd><strong>Has prefs</strong> : check this to indicate your plugin responds to the <em>plugin_prefs.your_plugin_name</em> event.</dd>\n		<dd><strong>Event notify</strong> : check this to indicate your plugin responds to the <em>plugin_lifecycle.your_plugin_name</em> event/steps.</dd>\n	</dl></dd>\n	<dt><strong>Load order</strong></dt>\n	<dd>Choose the recommended order in which you think your plugin should be loaded by users. Most of the time, the default of &#8216;5&#8217; is fine, but for special cases where your plugin has to set up an environment or has to wait for other plugins to load first, you might require one of the numbers either side. Be aware that this is a <em>recommendation</em> and is overridable by the site administrator. If the plugin is already deployed on a site, the load order that is already set will be used regardless of the setting of this value; only new installations will be set to this value by default.</dd>\n</dl>\n\n<h3>Plugin code</h3>\n\n<p>Your plugin code goes here. Write your masterpiece and either click the main <strong>Save</strong> button (which saves everything: code, help, meta data, etc) or if you are simply updating just the code portion, use the <strong>Save code</strong> button at the top-right of the subpanel for rapid background saving. During this save process, the textarea dims to show it is working, though you can still continue to type if you wish. When the textarea returns to full visibility, it means the save process is complete. Your code is (optionally) run through a rudimentary syntax checker when using the <strong>Save Code</strong> button and any error is highlighted. <span class=\"warning\">If a syntax error is detected, your plugin is not saved</span>.</p>\n\n<p>Plugins are limited to 16Mb of code so there is also a character countdown just below the edit box. If you start approaching the limit(!), it might be worth considering splitting your plugin into a few parts or working for Micro$oft, where code bloat is acceptable.</p>\n\n<p>In Firefox, Chrome and IE10+ you can use the <em>Jump to line:</em> textbox. Enter a line number and press Enter to jump to that line in the code. In other browsers, ymmv.</p>\n\n<h3>Textpack strings</h3>\n\n<p>Before Textpattern 4.3.0, any time you displayed a plugin-specific string to the user it made sense to write your own <code>gTxt()</code> function which packaged up the strings into one area of your plugin. While convenient, this meant that anyone who wanted to use your plugin in another lanugage had to edit the plugin code and rewrite your strings into the target language, which caused upgrade hassles.</p>\n\n<p>This is no longer an issue, as Textpattern now has Textpacks: redistributable text files that contain language strings for direct insertion into the database.</p>\n\n<p class=\"warning\">A word of caution: the Textpack area works a little differently to the rest of the edit panel: most changes happen live as you type.</p>\n\n<p>Before you begin you need to define a textpack prefix for the plugin. This is usually your three letter plugin prefix plus some unique identifier with which all strings in use by the plugin will begin. For example, the plugin composer uses <code>ied_plugin</code> (though it could have used <code>ied_pcomp</code> or <code>ied_pc</code>, etc). Note that using just your three letter prefix is probably not wise because your own future plugins might require a similarly-named replacement and the strings would clash. Of course, you might want to take advantage of this feature!</p>\n\n<p>Once you enter the prefix and your cursor leaves the box, the composer will store the prefix and search the plugin code immediately for any references to such prefixed strings inside any function call with <code>gTxt</code> in it. Any it does find will be listed and you can immediately begin entering your replacement text in the currently selected language. Whenever the cursor leaves a text box its contents is saved directly to the database.</p>\n\n<p>Textpack strings can be used on the Admin side, the Public site, or both. Choose the most appropriate location from the dropdown against each string.</p>\n\n<p>If you create or rename a gTxt string in the code, when your cursor leaves the textarea the new string(s) will be created for you in the textpack area at the top of the list. Note however they are not written to the database until you supply a replacement string.</p>\n\n<p>During the process of creating/renaming replacement strings, if it orphans another string then the orphan will be highlighted and an [x] button will appear next to it. If you wish to copy the old content out of the box and paste it into your renamed string, now is the time to do so. Once you&#8217;re sure you no longer need the string, hit the [x] button to immediately delete it from the database. It will be removed from <strong>all</strong> languages.</p>\n\n<p>Please note:</p>\n\n<ul>\n	<li>if you programmatically refer to strings (e.g. by concatenating string parts together to form the gTxt string name, or iterating over a loop and using variable substitution) then they will <em>not</em> be automatically be detected. You will have to use the &#8216;+&#8217; button to add such strings manually.</li>\n	<li>any programmatically-derived Textpack strings, or ones not inside a gTxt() function will show up with an [x] button. You should of course not delete these strings!</li>\n	<li>after you do a full save (i.e. not just a <strong>Save Code</strong>) all strings that are defined for the plugin will be displayed. Any derived strings will still be highlighted as potential orphans.</li>\n	<li>switching language from the select list will immediately load strings for that language.</li>\n	<li>clicking <strong>Load</strong> does the same task as switching language and is there in case you only have one language installed or wish to refresh the strings in the current language.</li>\n</ul>\n\n<h4>Importing strings from the current plugin</h4>\n\n<p>The success of the automatic find facility relies on two things:</p>\n\n<ul>\n	<li>that the strings are all prefixed.</li>\n	<li>your plugin&#8217;s Type is one of the <span class=\"caps\">AJAX</span> types (4 or 5).</li>\n</ul>\n\n<p>If you&#8217;re converting an old plugin to the Textpack methodology then you may have hundreds of strings that would be a bind to copy one by one. The composer can try to help you out, but it only works if you have a function or method in your plugin where the strings are replaced and returned. If you don&#8217;t have that, now might be the time to do so to save yourself some effort! It doesn&#8217;t have to be referenced in the code, it just has to exist and return a string for a given name.</p>\n\n<p>Once you have that in place, here are some steps you can go through to convert your plugin and grab all the necessary strings en masse:</p>\n\n<ol>\n	<li>inside your plugin&#8217;s gTxt() function, ensure that all keys are prefixed with your nominated plugin prefix.</li>\n	<li>globally replace any reference to <code>abc_plugin_gTxt(&#39;</code> with <code>abc_plugin_gTxt(&#39;abc_prefix_</code>, if not already.</li>\n	<li>save the plugin from the composer window.</li>\n	<li>enter the Textpack prefix if it&#8217;s not already set.</li>\n	<li>give the name of your gTxt function, e.g. <code>ied_plugin_gTxt</code> in the <i>Load strings from function</i> box. If your <code>gTxt()</code> method is inside a class, specify <code>class_name::method_name</code> instead.</li>\n	<li>hit &#8216;Go&#8217;.</li>\n</ol>\n\n<p>If the composer can execute your chosen function then it&#8217;ll do so and return the strings as defined inside the function, populate each Textpack string and save it for you in the database automatically. Once that&#8217;s done and all strings are populated you can then:</p>\n\n<ol>\n	<li>globally replace any calls to <code>abc_plugin_gTxt</code> with the core <code>gTxt</code> in your plugin code.</li>\n	<li>delete your abc_plugin_gTxt function.</li>\n</ol>\n\n<p>Textpack strings are also written to the file of any plugin you are editing in the cache_dir when you Save the plugin, as long as you are using a recent template that has the <code>$plugin[&#39;textpack&#39;]</code> string in it.</p>\n\n<p><strong>Switching language</strong></p>\n\n<p>If at any time you want to see the installed textpack strings in other languages, simply use the select list to choose one. Any defined strings will be loaded into the textpack fields. You&#8217;ll see a counter whizzing up to show you how far it&#8217;s gone. As a translation aid, the equivalent string in your nominated default language (see <a href=\"#ied_plugin_setup\">setup</a>) will be displayed as you hover over the textpack entry. You can choose to translate strings yourself or you can defer translation to other members of the community after the plugin is published. Textpacks can be linked to your textpattern.org plugin page by contributors and installed at any time from Textpattern&#8217;s <em>Admin-&gt;Languages</em> tab.</p>\n\n<h3>Plugin help</h3>\n\n<p>Documentation for detailing the plugin usage. Can (probably should!) be written using <a href=\"http://textpattern.com/textile-sandbox\">Textile</a>. There are some <a href=\"http://docs.textpattern.io/development/plugin-user-help-guidelines\">documentation guidelines</a> that serve as a good starting point. Note that the character countdown here is only approximate because when your plugin is saved and the help is converted to <span class=\"caps\">HTML</span>, it usually takes up more space than Textile; please check that your help file renders correctly when your plugin is exported.</p>\n\n<p>The <em>Style</em> box is for any <span class=\"caps\">CSS</span> style rules you wish to apply to your documentation, although you should not need this with modern admin themes. You are encouraged to reuse the admin&#8217;s core <span class=\"caps\">CSS</span> rules as often as possible, but if there isn&#8217;t one that suits, it&#8217;s best to target your documentation specifically by surrounding the entire Plugin Help section with something like: <code>&lt;div id=&quot;abc_help&quot;&gt;h1. Docs go here...&lt;/div&gt;</code>.</p>\n\n<p>Note that both the Plugin Help and Style are governed by a size limit. Since they are both stored in the same 64KB field, the size is shared between them. Styles are <em>not</em> passed through the Textile processor and you don&#8217;t need to add the <code>&lt;style&gt;</code> tags; the composer will do that for you (but see the <a href=\"#ied_plugin_notes\">notes</a>).</p>\n\n<p>Click the <strong>[Docs]</strong> link alongside the Plugin help heading to show the documentation as users will see it (i.e. Textile processed into <span class=\"caps\">HTML</span>). Any help text over 64KB in length is truncated so you can verify that all your documentation fits into the users&#8217; destination database.</p>\n\n\n<h3>Distribution</h3>\n\n<p>Once you have saved your plugin, there is a section that allows you to export your plugin in a variety of formats. A language select box governs how Textpacks are dealt with. If you choose the first (empty) item then no Textpack information is included in the exported file. The only exception is when you save Textpacks directly: the first item is a shortcut for &#8216;all languages&#8217; to save you having to select them all.</p>\n\n<ul>\n	<li><strong>Plugin code for distribution</strong> : is a direct copy &#8216;n&#8217; paste area that contains your entire plugin + docs. You can take this entire area and paste it into the &#8216;Install plugin&#8217; box. Since this adds to the time it takes to do a full save of the plugin, this portion is optional and can be enabled from the <a href=\"#ied_plugin_setup\">setup panel</a>.</li>\n	<li><strong>Export as abc_myplugin.txt</strong> : converts the plugin help to <span class=\"caps\">HTML</span> and saves the plugin to your computer as a redistributable text file for other Textpattern users to install. If you have chosen any Textpack languages from the adjacent select list, they will be bundled with your plugin and thus installed automatically if the language matches the one in use by the administrator who installs the plugin.</li>\n	<li><strong>Export as abc_myplugin.txt (compressed)</strong> : converts the plugin help to <span class=\"caps\">HTML</span> and saves the plugin as a redistributable, gzipped text file for other Textpattern users to install. Useful for large plugins or to offer an alternative for people who have stringent size limits imposed by their host. Textpack strings are bundled too if you have chosen at least one language from the select list.</li>\n	<li><strong>Export as abc_myplugin.php</strong> : saves the plugin in a Textpattern standard template format. Useful for keeping the plugin for yourself &#8212; complete with Textiled help markup &#8212; so it can be later edited and re-issued / updated or shared with other developers who have the plugin composer or zem_tpl.php compiler. Textpack strings are bundled as part of the template if you choose.</li>\n	<li><strong>Export Textpack(s)</strong> : choose one or more languages from the select list to gather all your plugin&#8217;s Textpack strings and download them as a redistributable text file for other textpattern users to install or modify. You (or anyone else) will (one day) be able to upload links to Textpacks on textpattern.org so they are available from a central location.</li>\n</ul>\n\n<p>Note that when exporting as a standard plugin, the Textile processor attempts to decide if you have used Textile or not; it simply looks for a textiled header (<code>h1.</code> through <code>h6.</code>). Running pure <span class=\"caps\">HTML</span> through Textile may occasionally cause encoding issues depending on the original character set used, so it is always best to try and stick to Textile as the documentation system.</p>\n\n<h2 id=\"ied_plugin_restore\">Restore points</h2>\n\n<p>When a plugin is installed, a copy is kept in the database. If the plugin code (not help text) is subsequently edited, the plugin is considered &#8220;modified&#8221;; indicated in the plugin composer&#8217;s <a href=\"#ied_plugin_list\">list panel</a>. Sometimes you may wish to revert any changes back to the as-installed state.</p>\n\n<p>Any time a plugin is marked as &#8216;modified&#8217;, the version number becomes clickable from the <a href=\"#ied_plugin_list\">list panel</a>. Clicking it (and confirming you are sure) will wipe out any changes you made and return the plugin to its installed state.</p>\n\n<p>During the editing process of your own plugins, it may be that at certain times you wish to put a stake in the ground and say &#8220;this is my current baseline that I might want to return to later&#8221;. Perhaps you are about to make some major edits or try something experimental and want an easy fallback mechanism. That&#8217;s where the &#8220;Restore point&#8221; checkbox comes in.</p>\n\n<p>By checking the box when you fully save your plugin, the current code will become your new rollback point and the plugin will no longer be marked &#8220;modified&#8221;. Any changes made beforehand will not be recoverable so you will have to rely on your own backups if you wish to go back further. Any edits you make after creating a restore point can be undone by visiting the <a href=\"#ied_plugin_list\">list panel</a> and clicking the version number next to your plugin. Currently, only one rollback point can be stored in the database. Note that the &#8220;Restore point&#8221; checkbox is ignored during saving of meta data only, since you are not saving any code.</p>\n\n<h2 id=\"ied_plugin_setup\">Setup panel</h2>\n\n<p>Clicking Setup from the main <a href=\"#ied_plugin_list\">list panel</a> allows access to the plugin settings.</p>\n\n<dl>\n	<dt><strong>Plugin editor</strong></dt>\n	<dd>You may choose to edit the code using a 3rd party syntax highlighter. Current support is for EditArea, CodeMirror and CodePress.</dd>\n	<dd>Choose the system here, and specify the <acronym title=\"s\"><span class=\"caps\">URL</span></acronym> of the scripts and stylesheets in the box beneath. If your chosen editor requires more than one file to be loaded then list them all, separated by commas. If any of the files are stylesheets, prefix the name with <code>css:</code> so the plugin knows to insert a <code>&lt;link rel=&quot;stylesheet&quot; ...&gt;</code> instead of a <code>&lt;script&gt;</code> tag.</dd>\n	<dd>If you want to supply any configuration options to the editor, type them in the <strong>Plugin editor configuration options</strong> textarea. Write them as name:value pairs separated by comma, exactly as you would if initialisaing the script according to their documentation.</dd>\n	<dt><strong>Plugin editor width</strong></dt>\n	<dd>The size of the textareas on the <a href=\"#ied_plugin_edit\">edit panel</a>. Set to suit your screen resolution. Include the units (px, em, %, etc) if you wish; it&#8217;ll default to pixels if you omit them.</dd>\n	<dt><strong>Help editor</strong></dt>\n	<dd>You may choose to edit the help manually via Textile or use TinyMCE for a more <span class=\"caps\">WYSIWYG</span> experience.</dd>\n	<dd>In a similar manner to the plugin code editor you need to tell the composer the <span class=\"caps\">URL</span> to the javascript file. You can also supply configuration options. The defaults should give you a head start on how to use them: consult the TinyMCE documentation for more.</dd>\n	<dt><strong>Optional interface elements</strong></dt>\n	<dd>Choose which parts of the <a href=\"#ied_plugin_edit\">edit panel</a> you want to be visible in the interface.</dd>\n	<dt><strong>Perform lifecycle actions on</strong></dt>\n	<dd>When plugins are installed, enabled, disabled or deleted, plugins can run code to perform installation or cleanup actions. Choose which ones you wish to permit when installing, enabling/disabling or deleting plugins from the composer&#8217;s <a href=\"#ied_plugin_list\">list panel</a>.</dd>\n	<dd>The value of the &#8216;install&#8217; checkbox chosen here becomes the default option in the <em>Perform post-install actions</em> radio button in the <em>Installation</em> subpanel.</dd>\n	<dt><strong>Auto-enable plugins on install</strong></dt>\n	<dd>Whether to allow plugins to automatically switch on after installation, or to retain their previous status.</dd>\n	<dd>The setting chosen here becomes the default radio button selection under the <em>Install</em> textarea on the <a href=\"#ied_plugin_list\">list panel</a>.</dd>\n	<dt><strong>Syntax check on code save</strong></dt>\n	<dd>Whether to run the code through the syntax checker or not when you use the <strong>Save Code</strong> button. Highly recommended to save your admin side from breakage during development.</dd>\n	<dt><strong>Textpack language list</strong></dt>\n	<dd>Whether to only allow editing of strings from the currently installed languages, or all available Textpattern languages.</dd>\n	<dt><strong>Default Textpack language</strong></dt>\n	<dd>The primary language for your plugin strings (default = any). If you choose a language that is not installed (and you&#8217;re limiting the language list to only those installed) the default language will revert to your current admin-side language.</dd>\n	<dd>Note that if you set a language here, when you export Textpacks or build plugins, that will be the language expected to be installed in a user&#8217;s Textpattern. If you leave it at &#8216;any&#8217;, the language marker is omitted from the Textpack and thus the strings bundled with the plugin (which may be in English) will install into whichever language is the user&#8217;s default (which may be something other than English). By forcing the default language, you force the language marker in the plugin. Thus if your users don&#8217;t have that language installed (e.g. someone only has nl-nl installed and not English) and they install your plugin, it will look at the installed language (nl-nl), compare it to the one in the plugin (en-gb), find it doesn&#8217;t match and will skip the Textpack installation. This will leave the user&#8217;s interface with lots of ugly <code>abc_plugin_some_item</code> strings instead of the actual translated content.</dd>\n	<dd>This could be handy if you are distributing Textpacks separately and have a good stock of them, or have loads of Textpacks bundled with the plugin, but for 95% of cases it is best to leave the default textpack language at <code>Any</code> so users of your plugin are guaranteed to get some translated strings, even if they are not in their &#8216;local&#8217; language. They can then at least translate them and make the Textpack available to other users.</dd>\n	<dt><strong><span class=\"caps\">PHP</span> export order</strong></dt>\n	<dd>When saving your plugin in the standard template format, this governs whether you prefer the code block to be at the top of the file and the help block below, or vice versa.</dd>\n	<dt><strong>Export plugin filename format</strong></dt>\n	<dt><strong>Export compressed filename format</strong></dt>\n	<dt><strong>Export template filename format</strong></dt>\n	<dt><strong>Textpack filename format</strong></dt>\n	<dd>These define the format of the filenames when you export plugins/Textpacks. The first is for when you export standard <span class=\"caps\">BASE</span>-64 plugins; the second is for compressed plugins; the third is for exporting a standard <span class=\"caps\">PHP</span> template, and finally for exporting Textpacks.</dd>\n	<dd>Wherever you type <code>{name}</code>, the plugin name will appear. Similarly, <code>{version}</code> will be replaced with the current plugin version number. And <code>{lang}</code> will be replaced with either 1) the chosen language code, like en-gb; 2) &#8216;all&#8217; if you chose to export all textpacks as one file; 3) an abbreviated list of countries to which the languages in the pack apply if you choose to export more than one, e.g. <code>en+fi+nl+fr+de</code>.</dd>\n	<dd>You can type anything you like in these boxes, but it&#8217;s more useful to include the replacement strings somewhere in each box so you don&#8217;t get name / version clashes. For example, if you don&#8217;t like the fact that zipped plugins are exported as <code>pfx_my_plugin_v0.1_zip.txt</code>, you can change it. Perhaps you may prefer <code>pfx_my_plugin-compressed-0.1.txt</code>. In which case, set the 2nd box to <code>{name}-compressed-{version}.txt</code>.</dd>\n	<dd>Note the extension should usually be specified so your system/browser knows the file&#8217;s type when it is exported, but it&#8217;s not mandatory as the <span class=\"caps\">MIME</span> type is given, so (good) browsers should read that.</dd>\n	<dt><strong>Cache Textiled help path</strong></dt>\n	<dd>If you wish to take advantage of help cacheing, put the path to a temporary directory in the box. Empty the box if you prefer saves and exports to be slower!</dd>\n	<dd>Defaults to Textpattern&#8217;s temporary directory.</dd>\n</dl>\n\n<h2 id=\"ied_plugin_notes\">Notes / known issues</h2>\n\n<ol>\n	<li>When a plugin is saved to the plugin_cache_dir, if you have not put <code>&lt;style&gt;</code> markers in your <span class=\"caps\">CSS</span> block they will be added for you in the text area but <em>not</em> saved in the actual template until you save it again (exporting as a <span class=\"caps\">PHP</span> file is unaffected). So if you are in the habit of manually downloading the file from your <span class=\"caps\">FTP</span> client immediately after a save, just save the plugin again to be sure.</li>\n	<li>Loading Textpack strings from a gTxt function won&#8217;t work unless you (perhaps temporarily) switch your plugin to one of the <span class=\"caps\">AJAX</span> types.</li>\n</ol>\n\n<h2>Writing a plugin</h2>\n\n<p>You should be aware of the <a href=\"http://forum.textpattern.com/viewtopic.php?id=9881\">Plugin Author Resources</a> topic on the Textpattern Support Forum, and you might also want to have a look at the tutorials and guides for <a href=\"http://docs.textpattern.io/development/\">Plugin development</a> in the Textpattern documentation.</p>\n\n<p>Happy plugin authoring :-)</p>\n\n<h2>Authors</h2>\n\n<p>Original plugin: Yura Linnyk<br />\nModifications (v0.5+): <a href=\"http://stefdawson.com/commentForm\">Stef Dawson</a><br />\nA touch of class: Steve Dickinson<br />\nPlus help from a plethora of forum contributors too numerous to mention. You know who you are, thank you ;-)</p>', '// <?php\n/**\n * ied_plugin_composer\n *\n * A Textpattern CMS plugin for writing, editing and sharing plugins\n *  -> Create and edit admin-side or public plugins\n *  -> Supports plugin lifecycle events and prefs\n *  -> Supports Textpacks\n *  -> Optional syntax checker on save\n *\n * @author Yura Linnyk\n * @author Stef Dawson\n * @author Steve Dickinson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * Use href() (from 4.6.+) for anchor creation to avoid double-encoded ampersands\n//  * Figure out why syntax checker doesn\'t jump to line number sometimes (AJAX fails with error but it\'s not handled)\n//  * Show which langs have installed strings in the distribution section so the correct langs in the select list can be chosen\n//  * Find out why uploading PHP files sometiems throws an error even though it succeeds\n//  * jQuery on editor dropdowns in setup\n//  * phpdoc\n\nif (txpinterface === \'admin\') {\n    new ied_pc();\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'ied_plugin_list\')\n            ->register(\'ied_plugin_info\')\n            ->register(\'ied_plugin_textpacks\')\n            ->register(\'ied_plugin_download\')\n            ->register(\'ied_plugin_download_link\');\n    }\n\n    register_callback(\'ied_plugin_download\', \'pretext\');\n\n    /*\n     * Public tag: List plugins, filtered by name or prefix.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_list($atts = array(), $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'from\'       => \'database\', // database, cache (or both)\n            \'name\'       => \'\', // List of plugin names to return\n            \'prefix\'     => \'\', // Plugin prefixes\n            \'exclude\'    => \'\', // names to exclude from the list\n            \'type\'       => \'\', // 0-5 or comma-separated combos thereof\n            \'wraptag\'    => \'\',\n            \'class\'      => \'\',\n            \'break\'      => \'\',\n            \'breakclass\' => \'\',\n            \'html_id\'    => \'\',\n            \'form\'       => \'\',\n        ),$atts));\n\n        $thing = (empty($form)) ? ((empty($thing)) ? \'<txp:ied_plugin_info item=\"name\" />\' : $thing) : fetch_form($form);\n\n        $location = do_list($from);\n        $names = do_list($name);\n        $prefixes = do_list($prefix);\n        $excludes = do_list($exclude);\n\n        if (in_array(\'database\', $location)) {\n            $sql = array();\n            $sql[] = \'1\';\n            if ($name) {\n                $sql[] = \"name IN (\'\".implode(\"\',\'\", doSlash($names)).\"\')\";\n            }\n            if ($prefix) {\n                $sqlor = array();\n                foreach ($prefixes as $pfx) {\n                    $sqlor[] = \"name LIKE \'\".doSlash($pfx).\"%\'\";\n                }\n                $sql[] = \'(\' . implode(\' OR \', $sqlor) . \')\';\n            }\n            if ($exclude) {\n                $sql[] = \"name NOT IN (\'\".implode(\"\',\'\", doSlash($excludes)).\"\')\";\n            }\n\n            $rs = safe_rows(\'*\', \'txp_plugin\', implode(\' AND \', $sql) . \' ORDER BY name\');\n        }\n\n        // TODO: Add the meta data from matching plugins in the cache directory\n        if (in_array(\'cache\', $location)) {\n\n\n        }\n\n        $out = array();\n        $ied_pd_saved = $ied_plugin_data;\n\n        foreach ($rs as $row) {\n            $ied_plugin_data = $row;\n            $ied_plugin_data[\'help_unstyled\'] = preg_replace(\'/\\<\\!\\-\\- \\*\\*\\* BEGIN PLUGIN CSS.+END PLUGIN CSS \\*\\*\\* \\-\\-\\>/s\', \'\', $row[\'help\']);\n            $out[] = parse($thing);\n            $ied_plugin_data = array();\n        }\n\n        $ied_plugin_data = $ied_pd_saved;\n\n        return ($wraptag) ? doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id) : implode($break, $out);\n    }\n\n    /**\n     * Public tag: Display plugin data for form/container usage.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_info($atts, $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'item\'    => \'\',\n            \'wraptag\' => \'\',\n            \'break\'   => \'\',\n            \'class\'   => \'\',\n            \'debug\'   => 0,\n        ), $atts));\n\n        $pdata = is_array($ied_plugin_data) ? $ied_plugin_data : array();\n\n        if ($debug) {\n            echo \'++ AVAILABLE INFO ++\';\n            dmp($pdata);\n        }\n\n        $items = do_list($item);\n        $out = array();\n\n        foreach ($items as $it) {\n            if (isset($pdata[$it])) {\n                $out[] = $pdata[$it];\n            }\n        }\n\n        return doWrap($out, $wraptag, $break, $class);\n    }\n\n    /**\n     * Public tag: List of available textpack information.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_textpacks($atts, $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'name\'     => \'\',\n            \'filename\' => \'\',\n            \'lang\'     => \'IED_ALL\',\n            \'wraptag\'  => \'\',\n            \'break\'    => \'\',\n            \'class\'    => \'\',\n            \'form\'     => \'\',\n        ), $atts));\n\n        if (!$name && !$filename) {\n            return;\n        }\n\n        if ($name) {\n            $theName = $name;\n        } elseif ($filename) {\n            $theName = $filename;\n        }\n\n        $thing = (empty($form)) ? ((empty($thing)) ? \'<txp:ied_plugin_info item=\"lang\" />\' : $thing) : fetch_form($form);\n\n        $langs = array();\n        $tp_prefixes = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\'));\n\n        if (isset($tp_prefixes[$theName])) {\n            $strings = $this->textpack_grab($lang, $tp_prefixes[$theName]);\n            foreach ($strings as $row) {\n                if (array_search($row[\'lang\'], $langs) === false) {\n                    $langs[] = $row[\'lang\'];\n                }\n            }\n        }\n        $out = array();\n        $ied_pd_saved = $ied_plugin_data;\n        $idx = 0;\n        $num_langs = count($langs);\n\n        foreach ($langs as $row) {\n            $ied_plugin_data[\'lang\'] = $row;\n            $ied_plugin_data[\'first_lang\'] = (($idx === 0) ? 1 : 0);\n            $ied_plugin_data[\'last_lang\'] = (($idx === $num_langs - 1) ? 1 : 0);\n            $out[] = parse($thing);\n            $ied_plugin_data[\'lang\'] = $ied_plugin_data[\'first_lang\'] = $ied_plugin_data[\'last_lang\'] = \'\';\n            $idx++;\n        }\n        $ied_plugin_data = $ied_pd_saved;\n\n        return doWrap($out, $wraptag, $break, $class);\n    }\n\n    /**\n     * Public tag: Download a plugin.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_download_link($atts, $thing = null)\n    {\n        extract(lAtts(array(\n            \'type\'     => \'compressed\', // uncompressed, compressed, template, textpack\n            \'name\'     => \'\',\n            \'filename\' => \'\',\n            \'label\'    => \'Download\',\n            \'class\'    => \'\',\n            \'lang\'     => \'IED_ALL\',\n            \'form\'     => \'\',\n            \'escape\'   => \'html\',\n        ), $atts));\n\n        if (!$name && !$filename) {\n            return;\n        }\n\n        $amp = ($escape === \'html\') ? \'&amp;\' : \'&\';\n\n        if ($name) {\n            $theName = $amp.\'name=\'.urlencode($name);\n        } elseif ($filename) {\n            $theName = $amp.\'filename=\'.urlencode($filename);\n        }\n\n        $theClass = \'\';\n        if ($class) {\n            $theClass = \' class=\"\'.$class.\'\"\';\n        }\n        $langopt = \'\';\n        if ($lang) {\n            $langs = do_list($lang);\n            $langopt = $amp.\'lang=\'.implode(\',\', $langs);\n        }\n\n        $linkName = (empty($form)) ? ((empty($thing)) ? $label : parse($thing)) : parse_form($form);\n\n        if ($type === \'compressed\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=zip\'.$langopt, $theClass);\n        } elseif ($type === \'uncompressed\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=txt\'.$langopt, $theClass);\n        } elseif ($type === \'template\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=php\'.$langopt, $theClass);\n        } elseif ($type === \'textpack\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=textpack\'.$langopt, $theClass);\n        }\n    }\n\n    /**\n     * Handles downloading plugin content.\n     */\n    function ied_plugin_download()\n    {\n        if (gps(\'ied_plugin_download\')) {\n            $type = gps(\'type\');\n            switch ($type) {\n                case \'zip\':\n                case \'txt\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_file();\n                    break;\n                case \'php\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_php_file();\n                    break;\n                case \'textpack\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_textpack();\n                    break;\n            }\n        }\n    }\n}\n\n/**\n * Plugin composer admin interface.\n */\nclass ied_pc\n{\n    /**\n     * Shared information across methods.\n     *\n     * @var array\n     */\n    protected $ied_plugin_globals = array();\n\n    /**\n     * The plugin\'s event as registered in Txp.\n     *\n     * @var string\n     */\n    protected $ied_pc_event = \'ied_plugin_composer\';\n\n    /**\n     * Constructor to set up callbacks and environment.\n     */\n    public function __construct()\n    {\n        $this->ied_plugin_globals = array(\n            \'css_start\' => \'<!--\',\n            \'css_end\'   => \'-->\',\n            \'dlm_start\' => \'#\',\n            \'dlm_end\'   => \'\',\n            \'start\'     => \' --- BEGIN PLUGIN SECTION ---\',\n            \'end\'       => \' --- END PLUGIN SECTION ---\',\n            \'size_help\' => \'63535\',\n            \'size_css\'  => \'2000\',\n            \'size_code\' => \'16777215\',\n        );\n\n        add_privs($this->ied_pc_event, \'1,2\');\n        add_privs(\'plugin_prefs.\' . $this->ied_pc_event, \'1,2\');\n\n        register_tab(\'extensions\', $this->ied_pc_event, gTxt(\'ied_plugin_composer\'));\n        register_callback(array($this, \'plugin_composer\'), $this->ied_pc_event);\n        register_callback(array($this, \'setup\'), \'plugin_prefs.\' . $this->ied_pc_event);\n        register_callback(array($this, \'welcome\'), \'plugin_lifecycle.\' . $this->ied_pc_event);\n        register_callback(array($this, \'inject_css\'), \'admin_side\', \'head_end\');\n    }\n\n    /**\n     * CSS definitions: hopefully kind to themers.\n     *\n     * @return string Style rules\n     */\n    protected function get_style_rules()\n    {\n        $ied_pc_styles = array(\n            \'ied_plugin\' => \'\n.ied_label { margin:0 0.2em 0 0.6em;}\n.ied_plugin_link { float:right; }\n.ied_plugin_info_bar { text-align:right; }\n#ied_plugin_jumpToLine { width:4em; margin:0 1em 0 0.4em; }\n.ied_editForm { width:{edwidth}; margin:0 auto; }\n.ied_subdue { color:gray; padding:1px 2px 2px 1px; }\n#ied_plugin_tp_controls input[type=\"text\"] { width:16%; }\n#options_group_pack ul { list-style-type:none; }\n#options_group_pack ul label { margin:0 8px 0 0; }\n#options_group_pack ul input { width:450px; }\n#options_group_pack li { margin-bottom:.2em }\n.distribution-code { height:4.2em!important; }\n.ied_plugin_edit_toolbar { text-align:right; width:95%; display:inline-block; margin:-2em 0 0 0; }\n#ied_plugin_msgpop { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }\n#ied_plugin_msgpop .publish { float:right; }\n    \',\n        );\n\n        return $ied_pc_styles;\n    }\n\n    /**\n     * Inject style rules into the &lt;head&gt; of the page.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     * @return string      Style rules, or nothing if not the correct $event\n     */\n    public function inject_css($evt, $stp)\n    {\n        global $event;\n\n        if ($event === $this->ied_pc_event) {\n            $ied_plugin_prefs = $this->get_prefs();\n            $ied_plugin_styles = $this->get_style_rules();\n\n            // Possible variable replacements.\n            $edwidth = get_pref(\'ied_plugin_editor_width\', $ied_plugin_prefs[\'ied_plugin_editor_width\'][\'default\']);\n            $stylefind = array(\n                \'{edwidth}\',\n            );\n            $stylereps = array(\n                $edwidth,\n            );\n\n            echo \'<style type=\"text/css\">\' . str_replace($stylefind, $stylereps, $ied_plugin_styles[\'ied_plugin\']) . \'</style>\';\n        }\n\n        return;\n    }\n\n    /**\n     * Plugin jumpoff point.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     */\n    public function plugin_composer($evt, $stp)\n    {\n        $available_steps = array(\n            \'code_save\'        => true,\n            \'create\'           => true,\n            \'edit\'             => false,\n            \'generate_phpdoc\'  => true,\n            \'help\'             => true,\n            \'help_viewer\'      => false,\n            \'install\'          => true,\n            \'lang_set\'         => true,\n            \'table\'            => false,\n            \'meta_save\'        => true,\n            \'multi_edit\'       => true,\n            \'prefs\'            => false,\n            \'restore\'          => true,\n            \'save\'             => true,\n            \'save_as_file\'     => true,\n            \'save_as_php_file\' => true,\n            \'save_as_textpack\' => true,\n            \'set_tp_prefix\'    => true,\n            \'switch_status\'    => true,\n            \'textpack_del\'     => true,\n            \'textpack_get\'     => true,\n            \'textpack_load\'    => true,\n            \'textpack_save\'    => true,\n            \'upload\'           => true,\n            \'save_pane_state\'  => true,\n        );\n\n        if (!$stp or !bouncer($stp, $available_steps)) {\n            $stp = \'table\';\n        }\n\n        $this->$stp();\n    }\n\n    /**\n     * Lifecycle handling, post-install / delete.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     * @return string      Success/failure message\n     */\n    public function welcome($evt, $stp)\n    {\n        $msg = \'\';\n\n        switch ($stp) {\n            case \'installed\':\n                $this->prefs_update();\n                $msg = \'Thanks for installing the plugin composer. Happy authoring :-)\';\n                break;\n            case \'deleted\':\n                $this->prefs_remove(0);\n                break;\n        }\n\n        return $msg;\n    }\n\n    /**\n     * Table of plugins in both database and file system cache.\n     *\n     * @param  string $message Flash message to display success/error\n     * @return string          HTML\n     */\n    public function table($message = \'\')\n    {\n        pagetop(gTxt(\'ied_plugin_composer\'), $message);\n\n        require_privs(\'ied_plugin_composer\');\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n        $checked = in_array(\'installed\', $lc_opts);\n        $auto_en = get_pref(\'ied_plugin_auto_enable\');\n        $pcd = get_pref(\'plugin_cache_dir\');\n\n        $aeRadio[] = \'<p class=\"ied_plugin_radioset ied_plugin_autoenable txp-layout-2col\">\';\n        $aeRadio[] = \'<label class=\"ied_label\">\' . gTxt(\'ied_plugin_auto_enable\') . \'</label>\';\n        $aeRadio[] = radioset($ied_plugin_prefs[\'ied_plugin_auto_enable\'][\'content\'], \'ied_plugin_autoenable\', $auto_en);\n        $aeRadio[] = \'</p>\';\n\n        $ioRadio[] = \'<p class=\"ied_plugin_radioset ied_plugin_installopts txp-layout-2col\" hidden>\';\n        $ioRadio[] = \'<label class=\"ied_label\">\' . gTxt(\'ied_plugin_run_install\') . \'</label>\';\n        $ioRadio[] = yesnoradio(\'ied_plugin_installopts\', $checked);\n        $ioRadio[] = \'</p>\';\n\n        extract(gpsa(array(\'sort\', \'dir\')));\n\n        if ($sort === \'\') $sort = get_pref(\'ied_plugin_sort_column\', \'name\');\n        if ($dir === \'\') $dir = get_pref(\'ied_plugin_sort_dir\', \'asc\');\n        $dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n        if (!in_array($sort, array(\'name\', \'status\', \'author\', \'version\', \'load_order\'))) $sort = \'name\';\n\n        $sort_sql = $sort.\' \'.$dir;\n\n        set_pref(\'ied_plugin_sort_column\', $sort, \'ied_plugin\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n        set_pref(\'ied_plugin_sort_dir\', $dir, \'ied_plugin\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n\n        $switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n        // Top control-panel part of screen\n        echo \'<div class=\"txp-layout\">\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            n. \'<h1 class=\"txp-heading\">\'.gTxt(\'ied_plugin_composer\').sp.$this->anchor($this->ied_pc_event, \'help_viewer\', \'?\', array(\'name\' => \'ied_plugin_composer\'), array(\'class\' => \'pophelp\')).\'</h1>\'.\n            n. \'</div>\'.\n            n. \'<div id=\"ied_plugin_control\" class=\"txp-layout-2col\">\'.\n            n. sLink($this->ied_pc_event, \'prefs\', gTxt(\'ied_plugin_setup\'), \'ied_plugin_link\').\n            n. \'</div>\'.\n            n. \'<div class=\"txp-layout-1col\">\'.\n            n. \'<div class=\"summary-details clear\"><h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_cpanel_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#ied_plugin_cpanel\">\' . gTxt(\'ied_plugin_cpanel_legend\') . \'</a></h3><div id=\"ied_plugin_cpanel\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_cpanel_visible\') ? \'block\' : \'none\').\'\">\'.\n            n. \'<form class=\"ied_plugin_form\" enctype=\"multipart/form-data\" action=\"index.php\" method=\"post\">\'.\n            n. implode(n, $aeRadio).\n            n. implode(n, $ioRadio).\n            n. \'<p class=\"clear\">\'.\n            n. \'<label for=\"ied_plugin_newname\" class=\"ied_label\">\'.gTxt(\'name\').\'</label>\'.\n            n. fInput(\'text\', \'name\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', \'ied_plugin_newname\', false, false, \'Create a new plugin\').\n            n. fInput(\'submit\', \'plugin_create\', gTxt(\'create\')).\n            n. \'</p>\'.\n            n. \'<p>\'.\n            n. \'<label for=\"ied_plugin_file\" class=\"ied_label\">\'.gTxt(\'ied_plugin_upload_php\').\'</label>\'.\n            n. fInput(\'file\', \'thefile\', \'\', \'\', \'\', \'\', \'\', \'\', \'ied_plugin_file\').\n            n. fInput(\'submit\', \'plugin_upload\', gTxt(\'upload\')).\n            n. \'</p>\'.\n            n. \'<p>\'.\n            n. \'<label for=\"ied_plugin64\" class=\"ied_label\">\'.gTxt(\'ied_plugin_install_txt\').\'</label>\'.\n            n. text_area(\'plugin64\', \'\', \'\', \'\', \'ied_plugin64\').\n            n. fInput(\'submit\', \'plugin_install\', gTxt(\'install\')).\n            n. \'</p>\'.\n            n. eInput($this->ied_pc_event).\n            n. sInput(\'create\').\n            n. hInput(\'MAX_FILE_SIZE\', 1000000).\n            n. tInput().\n            n. \'</form>\'.\n            n. \'</div>\'.\n            n. \'</div>\'.\n            n. \'</div>\';\n\n        // Main plugin list\n        $rs = safe_rows(\'*\', \'txp_plugin\', \'1=1 ORDER BY \'.$sort_sql);\n\n        if ($rs) {\n            echo \'<div class=\"txp-layout-1col\">\'.\n                n. \'<div class=\"summary-details\">\'.\n                n. \'<form action=\"index.php\" id=\"ied_plugin_db_form\" method=\"post\">\'.\n                n. \'<h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_dbplugs_visible\') ? \' expanded\' : \'\').\'\">\'.\n                n. \'<a href=\"#ied_plugin_dbplugs\">\' . gTxt(\'ied_plugin_dbplugs_legend\') . \'</a>\'.\n                n. \'</h3>\'.\n                n. \'<div id=\"ied_plugin_dbplugs\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_dbplugs_visible\') ? \'block\' : \'none\').\'\">\'.\n            n. \'<div class=\"txp-listtables\">\'.\n            n. startTable(\'\', \'\', \'txp-list\').\n            n.\'<thead>\'.\n            n. tr(\n                n.hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\n                .n.column_head(\'plugin\', \'name\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'name\' == $sort) ? \"$dir \" : \'\').\'name\')\n                .n.column_head(\'author\', \'author\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'author\' == $sort) ? \"$dir \" : \'\').\'author\')\n                .n.column_head(gTxt(\'version\').\' (\'.gTxt(\'plugin_modified\').\')\', \'version\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'version\' == $sort) ? \"$dir \" : \'\').\'version\')\n                .n.hCell(gTxt(\'description\'), \'\', \' class=\"description\"\')\n                .n.hCell(gTxt(\'manage\'), \'\', \' class=\"manage\"\')\n                .n.column_head(\'active\', \'status\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'status\' == $sort) ? \"$dir \" : \'\').\'status\')\n            ).\n            n. \'</thead>\'.\n            n. \'<tbody>\';\n\n            foreach ($rs as $row) {\n                extract($row);\n                $ename = $this->anchor($this->ied_pc_event, \'edit\', $name, array(\'name\' => $name));\n                $hlink = ($help) ? $this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), array(\'name\' => $name)) : gTxt(\'none\');\n                $fnames = $this->get_name($name, $version);\n                $pubtag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'publish\'), array(\'name\' => $name), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0]))));\n                $pubztag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_compress\'), array(\'name\' => $name, \'type\' => \'zip\'), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[1]))));\n                $modified = (strtolower($code) != (strtolower($code_restore)));\n                $plugpref = ($flags & PLUGIN_HAS_PREFS) ? (sp.$this->anchor(\'plugin_prefs.\'.urlencode($name), \'\', \'[\'.gTxt(\'plugin_prefs\').\']\', array(\'name\' => $name), array(\'class\' => \'plugin_prefs\'.( ($status) ? \'\' : \' empty\'))) ) : \'\';\n\n                echo tr(\n                    n.td(\n                        fInput(\'checkbox\', \'selected[]\', $name)\n                    ,\'\', \'multi-edit\')\n                    .n.td($ename.$plugpref)\n                    .n.td(( ($author_uri) ? \'<a href=\"\'.txpspecialchars($author_uri).\'\">\'.txpspecialchars($author).\'</a>\' : txpspecialchars($author)))\n                    .n.td(( ($modified) ? $this->anchor($this->ied_pc_event, \'restore\', $version, array(\'name\' => $name), array(\'title\' => gTxt(\'ied_plugin_restore_help\'), \'onclick\' => \'return verify(\\\'\'.gTxt(\'ied_plugin_restore_verify\', array(\'{name}\' => $name)).\'\\\');\')) : $version) . (($modified) ? sp.\'(\'.gTxt(\'yes\').\')\' : \'\'))\n                    .n.td(txpspecialchars($description))\n                    .n.td($pubtag .sp. \'&#124;\' .sp. $pubztag .sp. \'&#124;\' .sp. $hlink)\n                    .n.td($this->status_link($status,$name,yes_no($status)))\n                );\n                unset($name,$page);\n            }\n            echo n. \'</tbody>\'.\n                n. endTable().\n                n. \'</div>\'.\n                n. \'</div>\'.\n                n. tInput().\n                $this->multiedit_form(\'db\', \'\', $sort, $dir, \'\', \'\').\n                n. \'</form>\'.\n                n. \'</div>\'.\n                n. \'</div>\';\n        }\n\n        if (!empty($pcd) && file_exists($pcd)) {\n            $filenames = array();\n\n            $directory = dir($pcd);\n\n            while ($file = $directory->read()) {\n                if ($file != \".\" && $file != \"..\") {\n                    $fileaddr = $pcd . DS . $file;\n                    if (!is_dir($fileaddr)) {\n                        $filenames[]=$file;\n                    }\n                }\n            }\n            $directory->close();\n            ($filenames)?natcasesort($filenames):\'\';\n\n            $out = array();\n\n            foreach ($filenames as $filename) {\n                $parts = explode (\'.\',$filename);\n                $fileext = array_pop($parts);\n\n                if ($fileext==\'php\') {\n                    $basename = basename($filename);\n                    $plugin = $this->read_file($pcd . DS . $filename);\n                    $hlink = ($plugin[\'help\']) ? $this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), array(\'filename\' => $filename)) : gTxt(\'none\');\n                    $efile = $this->anchor($this->ied_pc_event, \'edit\', $plugin[\'name\'], array(\'filename\' => $filename));\n                    $fnames = $this->get_name($plugin[\'name\'], $plugin[\'version\']);\n                    $plugpref = (($plugin[\'flags\'] & PLUGIN_HAS_PREFS)) ? \' \'.$this->anchor(\'plugin_prefs.\'.urlencode($plugin[\'name\']), \'\', \' [\'.gTxt(\'plugin_prefs\').\']\') : \'\';\n                    $pubtag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'publish\'), array(\'filename\' => $filename), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0]))));\n                    $pubztag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_compress\'), array(\'filename\' => $filename, \'type\' => \'zip\'), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[1]))));\n\n                    $out[] = tr(\n                        n.td(\n                            fInput(\'checkbox\', \'selected-cache[]\', $filename)\n                        ,\'\', \'multi-edit\')\n                        .n.td(\n                            tag($filename,\'div\',\' class=\"ied_subdue\"\')\n                            .(isset($plugin[\'name\']) ? $efile.$plugpref.\'<br />\' : \'\').\' \'\n                        )\n                        .n.td(\n                            ( isset($plugin[\'author_uri\']) ? \'<a href=\"\'.$plugin[\'author_uri\'].\'\">\' : \'\' ) .\n                            ( isset($plugin[\'author\']) ? $plugin[\'author\'] : \'&nbsp;\' ).\n                            ( isset($plugin[\'author_uri\']) ? \'</a>\' : \'\' )\n                        )\n                        .n.td(\n                            (isset($plugin[\'version\']) ? $plugin[\'version\'] : \'&nbsp;\')\n                        )\n                        .n.td(\n                            (isset($plugin[\'description\']) ? $plugin[\'description\'] : \'&nbsp;\')\n                        )\n                        .n.td(\n                            (isset($plugin[\'name\']) ? $pubtag .sp. \'&#124;\' .sp. $pubztag\n                                : tag(\'&nbsp;\', \'span\')\n                            )\n                            .sp. \'&#124;\' .sp. $hlink\n                        )\n                    );\n                }\n            }\n\n            if ($out) {\n                echo \'<div class=\"txp-layout-1col\">\'.\n                    n. \'<div class=\"summary-details\">\'.\n                    n. \'<form action=\"index.php\" id=\"ied_plugin_cache_form\" method=\"post\">\'.\n                        n. \'<h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_cacheplugs_visible\') ? \' expanded\' : \'\').\'\">\'.\n                        n. \'<a href=\"#ied_plugin_cacheplugs\">\' . gTxt(\'ied_plugin_cacheplugs_legend\') . \'</a>\'.\n                        n. \'</h3>\'.\n                        n. \'<div id=\"ied_plugin_cacheplugs\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_cacheplugs_visible\') ? \'block\' : \'none\').\'\">\'.\n                    n. \'<div class=\"txp-listtables ied_plugin_cacheplugs\">\'.\n                    n.startTable(\'\', \'\', \'txp-list\').\n                    n. \'<thead>\'.\n                    n. tr(\n                        n.hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\n                        .n.hCell(gTxt(\'plugin\'), \'\', \' class=\"name\"\')\n                        .n.hCell(gTxt(\'author\'), \'\', \' class=\"author\"\')\n                        .n.hCell(gTxt(\'version\') . \' (\'.gTxt(\'plugin_modified\').\')\', \'\', \' class=\"version\"\')\n                        .n.hCell(gTxt(\'description\'), \'\', \' class=\"description\"\')\n                        .n.hCell(gTxt(\'manage\'), \'\', \' class=\"manage\"\')\n                    ).\n                    n. \'</thead>\'.\n                    n. \'<tbody>\'.\n                    n. implode(n, $out).\n                    n. \'</tbody>\'.\n                    n. endTable().\n                    n. \'</div>\'.\n                    n. tInput().\n                    $this->multiedit_form(\'cache\', \'\', $sort, $dir, \'\', \'\').\n                    n. \'</form>\'.\n                    n. \'</div>\'.\n                    n. \'</div>\'.\n                    n. \'</div>\';\n            }\n        }\n\n        echo \'</div>\'.\n            n. script_js( <<<EOJS\n$(document).ready(function () {\n    $(\'#ied_plugin_db_form\').txpMultiEditForm({\n        \'checkbox\' : \'input[name=\"selected[]\"][type=checkbox]\'\n    });\n\n    $(\'#ied_plugin_cache_form\').txpMultiEditForm({\n        \'checkbox\' : \'input[name=\"selected-cache[]\"][type=checkbox]\'\n    });\n\n    var ied_io = $(\'.ied_plugin_installopts\');\n\n    $(\'#ied_plugin_file\').change(function() {\n        var fn = $(this).val();\n\n        if (fn.substr(fn.lastIndexOf(\'.\') + 1) === \'txt\') {\n            ied_io.show();\n        } else {\n            ied_io.hide();\n        }\n    });\n\n    $(\'#ied_plugin64\').on(\'blur, keyup\', function() {\n        if ($(this).val()) {\n            ied_io.show();\n        } else {\n            ied_io.hide();\n        }\n    });\n});\nEOJS\n                    );\n\n        // Show/hide \"Options\" link by setting the appropriate class on the plugin\'s &lt;tr&gt;\n        echo script_js(<<<EOJS\ntextpattern.Relay.register(\'txpAsyncHref.success\', function (event, data) {\n    jQuery(data.this).closest(\'tr\').find(\'a.plugin_prefs\').toggleClass(\'empty\');\n});\nEOJS\n        );\n    }\n\n    /**\n     * Render a multi-edit dropdown list of actions.\n     *\n     * @param  string $flavour       Whether the dropdown is for file-based plugins or those in the DB\n     * @param  string $page          Current page of results (unused)\n     * @param  string $sort          Current sort column name\n     * @param  string $dir           Current sort direction (asc or desc)\n     * @param  string $crit          Current filter criteria (unused)\n     * @param  string $search_method Column name upon which the search is being performed (unused)\n     * @return HTML\n     */\n    protected function multiedit_form($flavour, $page, $sort, $dir, $crit, $search_method)\n    {\n        $orders = selectInput(\'order\', array(1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9), 5, false);\n        $stati = selectInput(\'switch_status\', array(\'toggle\' => gTxt(\'ied_plugin_toggle\'), \'on\' => gTxt(\'on\'), \'off\' => gTxt(\'off\')), \'toggle\', false);\n        $lifecycles = selectInput(\'ied_lc_event\', array(\n            \'installed\'         => gTxt(\'ied_plugin_lbl_lc_install\'),\n            \'enabled\'           => gTxt(\'ied_plugin_lbl_lc_enable\'),\n            \'installed,enabled\' => gTxt(\'ied_plugin_lbl_lc_instable\'),\n            \'disabled\'          => gTxt(\'ied_plugin_lbl_lc_disable\'),\n            \'deleted\'           => gTxt(\'ied_plugin_lbl_lc_delete\'),\n            \'disabled,deleted\'  => gTxt(\'ied_plugin_lbl_lc_disdel\'),\n        ));\n\n        if ($flavour === \'db\') {\n            $methods = array(\n                \'changestatus\' => array(\'label\' => gTxt(\'changestatus\'), \'html\' => $stati),\n                \'changeorder\'  => array(\'label\' => gTxt(\'changeorder\'), \'html\' => $orders),\n                \'lifecycle\'    => array(\'label\' => gTxt(\'ied_plugin_lifecycle\'), \'html\' => $lifecycles),\n                \'delete\'       => gTxt(\'delete\'),\n            );\n        } else {\n            $methods = array(\n                \'lifecycle\' => array(\'label\' => gTxt(\'ied_plugin_lifecycle\'), \'html\' => $lifecycles),\n                \'textpack\'  => array(\'label\' => gTxt(\'ied_plugin_install_textpack\')),\n                \'delete\'    => gTxt(\'delete\'),\n            );\n        }\n\n        return multi_edit($methods, $this->ied_pc_event, \'multi_edit\', $page, $sort, $dir, $crit, $search_method);\n    }\n\n    /**\n     * Handle multi-edit actions.\n     */\n    public function multi_edit()\n    {\n        $selected = ps(\'selected\');\n        $selected_cache = ps(\'selected-cache\');\n        $method = assert_string(ps(\'edit_method\'));\n\n        if ($selected && is_array($selected)) {\n            $where = \"name IN (\'\".implode(\"\',\'\", doSlash($selected)).\"\')\";\n            $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n\n            switch ($method) {\n                case \'delete\':\n                    foreach ($selected as $name) {\n                        if (safe_field(\'flags\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) {\n                            load_plugin($name, true);\n                            if (in_array(\'disabled\', $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", \'disabled\');\n                            }\n                            if (in_array(\'deleted\', $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", \'deleted\');\n                            }\n                        }\n                    }\n                    safe_delete(\'txp_plugin\', $where);\n                    break;\n\n                case \'changestatus\':\n                    switch (ps(\'switch_status\')) {\n                        case \'on\':\n                            $newstat = \'1\';\n                            break;\n                        case \'off\':\n                            $newstat = \'0\';\n                            break;\n                        case \'toggle\':\n                        default:\n                            $newstat = \'(1-status)\';\n                            break;\n                    }\n\n                    foreach ($selected as $name) {\n                        if (safe_field(\'flags\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) {\n                            $status = safe_field(\'status\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\");\n                            $status = $status ? \'disabled\' : \'enabled\';\n                            load_plugin($name, true);\n                            if (in_array($status, $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", $status);\n                            }\n                        }\n                    }\n\n                    safe_update(\'txp_plugin\', \'status = \'.$newstat, $where);\n                    break;\n\n                case \'changeorder\':\n                    $order = min(max(intval(ps(\'order\')), 1), 9);\n                    safe_update(\'txp_plugin\', \'load_order = \'.$order, $where);\n                    break;\n\n\n                case \'lifecycle\':\n                    $lc_evs = do_list(ps(\'ied_lc_event\'));\n                    foreach ($selected as $name) {\n                        foreach ($lc_evs as $lc_ev) {\n                            callback_event(\"plugin_lifecycle.$name\", $lc_ev);\n                        }\n                    }\n                    break;\n            }\n        } else {\n            $selected = array();\n        }\n\n        if ($selected_cache && is_array($selected_cache)) {\n            $pcd = get_pref(\'plugin_cache_dir\');\n\n            switch ($method) {\n                case \'delete\':\n                    foreach ($selected_cache as $name) {\n                        $filenames = array();\n                        $dir = dir($pcd);\n\n                        while ($file = $dir->read()) {\n                            if ($file != \".\" && $file != \"..\" && in_array($file, $selected_cache)) {\n                                $fileaddr = $pcd . DS . $file;\n\n                                if (!is_dir($fileaddr)) {\n                                    unlink($fileaddr);\n                                }\n                            }\n                        }\n\n                        $dir->close();\n                    }\n                    break;\n\n                case \'lifecycle\':\n                    $lc_evs = do_list(ps(\'ied_lc_event\'));\n                    foreach ($selected_cache as $name) {\n                        $name = str_replace(\'.php\', \'\', $name);\n                        foreach ($lc_evs as $lc_ev) {\n                            callback_event(\"plugin_lifecycle.$name\", $lc_ev);\n                        }\n                    }\n                    break;\n\n                case \'textpack\':\n                    // Read the textpack from the .php file and call install_textpack()\n                    $textpack = array();\n                    foreach ($selected_cache as $name) {\n                        $fileaddr = $pcd . DS . $name;\n\n                        $contents = file($fileaddr);\n                        $in_tp = $in_comment = false;\n                        foreach ($contents as $row) {\n                            if (strpos($row, \'/**\') === 0) {\n                                $in_comment = true;\n                            }\n                            if (strpos($row, \'**/\') === 0) {\n                                $in_comment = false;\n                            }\n                            if (strpos($row, \'EOT;\') !== false) {\n                                break;\n                            }\n                            if ($in_tp === true) {\n                                $textpack[] = trim($row);\n                            }\n                            if (!$in_comment && strpos($row, \'$plugin[\\\'textpack\\\']\') !== false) {\n                                $in_tp = true;\n                      }\n                   }\n                    }\n\n                    $done = install_textpack(implode(n, $textpack));\n                break;\n            }\n        } else {\n            $selected_cache = array();\n        }\n\n        $message = \'\';\n\n        if ($selected || $selected_cache) {\n            if ($method === \'delete\') {\n                $message = gTxt(\'plugin_deleted\', array(\'{name}\' => implode(\', \', array_merge($selected, $selected_cache))));\n            } elseif ($method === \'lifecycle\') {\n                $message = gTxt(\'ied_plugin_lc_fired\', array(\'{name}\' => implode(\', \', array_merge($selected, $selected_cache)), \'{event}\' => implode(\',\', $lc_evs)));\n            } elseif ($method === \'textpack\') {\n                $message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $done));\n            } else {\n                $message = gTxt(\'plugin_updated\', array(\'{name}\' => implode(\', \', $selected)));\n            }\n        }\n        $this->table($message);\n    }\n\n    /**\n     * Render a hyperlink to another plugin composer method.\n     *\n     * @param  string $evt      Textpattern event destination\n     * @param  string $stp      Textpattern step (action) destination\n     * @param  string $linktext Text for the hyperlink\n     * @param  array  $nv       Name-value tuples to add to the URL\n     * @param  array  $atts     Name-value tuples to add to the anchor tag\n     * @return HTML\n     */\n    protected function anchor($evt, $stp, $linktext, $nv = array(), $atts = array())\n    {\n        $nv[\'_txp_token\'] = form_token();\n        $atts[\'href\'] = \'?event=\' . $evt . ($stp ? \'&step=\' . $stp : \'\') . \'&\' . http_build_query($nv);\n\n        $attribs = \'\';\n\n        foreach ($atts as $n => $v) {\n            $attribs .= \' \' . txpspecialchars($n) . \'=\"\' . txpspecialchars($v) . \'\"\';\n        }\n\n        return tag($linktext, \'a\', $attribs);\n    }\n\n    /**\n     * Render an asynchronous anchor tag for toggling plugin status.\n     *\n     * @param  string $status   Current plugin status (0=disabled, 1=enabled)\n     * @param  string $name     Plugin name to toggle\n     * @param  string $linktext Link text to display on the anchor\n     * @return string           HTML\n     */\n    protected function status_link($status, $name, $linktext)\n    {\n        return asyncHref($linktext, array(\'step\' => \'switch_status\', \'thing\' => $name),\' title=\"\'.($status==1 ? gTxt(\'disable\') : gTxt(\'enable\')).\'\"\' );\n    }\n\n    /**\n     * Toggle the status of the given plugin (params sent via POST)\n     *\n     * @return string New status\n     */\n    public function switch_status()\n    {\n        extract(array_map(\'assert_string\', gpsa(array(\'thing\', \'value\'))));\n        $change = ($value == gTxt(\'yes\')) ? 0 : 1;\n\n        safe_update(\'txp_plugin\', \"status = $change\", \"name = \'\".doSlash($thing).\"\'\");\n\n        $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n        $stp = $change ? \'enabled\' : \'disabled\';\n\n        if (in_array($stp, $lc_opts) && (safe_field(\'flags\', \'txp_plugin\', \"name=\'\".doSlash($thing).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) ) {\n            load_plugin($thing, true);\n            $message = callback_event(\"plugin_lifecycle.$thing\", $stp);\n        }\n\n        echo gTxt($change ? \'yes\' : \'no\');\n    }\n\n    /**\n     * Reset the given plugin to its last restore point.\n     */\n    public function restore()\n    {\n        $name = doSlash(gps(\'name\'));\n        safe_update(\"txp_plugin\", \"code = code_restore\", \"name=\'$name\'\");\n        $this->table(gTxt(\'ied_plugin_restored\', array(\'{name}\' => $name)));\n    }\n\n    /**\n     * Edit the selected plugin data.\n     *\n     * @param  string $message Flash message to display after action\n     * @param  string $newfile Filename if plugin being edited is in the filesystem\n     */\n    public function edit($message = \'\', $newfile = \'\')\n    {\n        $newname = trim(gps(\'newname\'));\n        $filename = gps(\'filename\');\n        $editfile = $filename ? 1 : 0;\n        $name = empty($newname) ? gps(\'name\') : $newname;\n        $name = ($newfile) ? $newfile : (($filename) ? $filename : $name);\n\n        pagetop(gTxt(\'ied_plugin_editing\', array(\'{name}\' => txpspecialchars($name))), $message);\n        require_privs(\'ied_plugin_composer\');\n\n        echo $this->insert_editors();\n\n        if (!$editfile) {\n            $rs = safe_row(\"author, author_uri, version, description, code, help, status, type, load_order, flags\", \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } else {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.$name);\n            $filename = $name;\n            $name = explode (\'.\', $name);\n            $fileext = array_pop($name);\n            $name = implode($name);\n            extract($plugin);\n            $status = ($fileext==\'php\')? 1: 0;\n        }\n\n        $ifel = get_pref(\'ied_plugin_interface_elems\');\n        $distblock = (strpos($ifel, \'distribution\') !== false);\n        $styleblock = (strpos($ifel, \'style\') !== false);\n        $distribution = \'\';\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n\n        if ($distblock) {\n            $plugin[\'name\'] = $name;\n            $plugin[\'author\'] = $author;\n            $plugin[\'author_uri\'] = $author_uri;\n            $plugin[\'version\'] = $version;\n            $plugin[\'description\'] = $description;\n            $plugin[\'help\'] = $this->textile($name, $help, $css, $start_css, $end_css);\n            $plugin[\'code\'] = $code;\n            $plugin[\'type\'] = $type;\n            $plugin[\'order\'] = $load_order;\n            $plugin[\'flags\'] = $flags;\n            $plugin[\'md5\'] = md5( $plugin[\'code\'] );\n            $distribution = \'<textarea name=\"distribution\" class=\"distribution-code\" rows=\"1\" onclick=\"this.select()\">\'.base64_encode(serialize($plugin)).\'</textarea>\';\n        }\n\n        for ($i = 1; $i <= 9; $i++) $orders[$i] = $i;\n\n        $tp_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\', 1));\n        $tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : \'\';\n\n        $fnames = $this->get_name($name, $version);\n        $namedLink = ($filename) ? array(\'filename\' => $filename) : array(\'name\' => $name);\n        $zippedLink = array_merge($namedLink, array(\'type\' => \'zip\'));\n\n        $slink = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0])), $namedLink);\n        $sziplink = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_export_zip\', array(\'{name}\' => $fnames[1])), $zippedLink);\n        $sphplink = $this->anchor($this->ied_pc_event, \'save_as_php_file\', gTxt(\'ied_plugin_save_as\', array(\'{name}\' => $fnames[2])), $namedLink);\n        $stxtlink = $this->anchor($this->ied_pc_event, \'save_as_textpack\', gTxt(\'ied_plugin_export_textpack\'), $namedLink);\n        $vhelplinkfull = ($help) ? \'[ \' .$this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), $namedLink) . \' ]\' : \'\';\n\n        $msgpop = \'<div id=\"ied_plugin_msgpop\"><input type=\"button\" class=\"publish\" value=\"\'.gTxt(\'ok\').\'\" onclick=\"ied_plugin_toggle_msgpop(\\\'0\\\');\" /><h2>\'.gTxt(\'ied_plugin_msgpop_lbl\').\'</h2><span class=\"ied_plugin_msgpop_content\"></span></div>\';\n\n        $newname = fInput(\'text\', \'newname\', $name, \'\', \'\', \'\', INPUT_REGULAR);\n        $author_widget = fInput(\'text\', \'author\', $author, \'\', \'\', \'\', INPUT_REGULAR);\n        $author_uri_widget = fInput(\'text\', \'author_uri\', $author_uri, \'\', \'\', \'\', INPUT_REGULAR);\n        $version_widget = fInput(\'text\', \'version\', $version, \'input-small\', \'\', \'\',INPUT_SMALL) .sp. (($editfile) ? checkbox(\'rename_file\', \'1\', 0, \'\',\'rename_file\') . \' <label for=\"rename_file\">\'.gTxt(\'ied_plugin_rename_file\').\'</label>\' : checkbox(\'restore_point\', \'1\', 0, \'\',\'restore_point\') . \' <label for=\"restore_point\">\'.gTxt(\'ied_plugin_restore_point\').\'</label>\');\n        $description_widget = fInput(\'text\', \'description\', $description, \'input-xlarge\', \'\', \'\', INPUT_REGULAR);\n        $codeblock = \'<textarea name=\"code\" id=\"plugin_editor\" rows=\"\'.INPUT_REGULAR.\'\" class=\"code codepress php\" maxlength=\"\'.$this->ied_plugin_globals[\'size_code\'].\'\">\'.txpspecialchars($code).\'</textarea><div class=\"ied_plugin_info_bar\"><span>\'.gTxt(\'ied_plugin_jump_to_line\').\'</span><input type=\"text\" id=\"ied_plugin_jumpToLine\" size=\"5\" maxlength=\"6\" /><span class=\"ied_plugin_charsRemain\"></span></div>\';\n        $help_widget = \'<textarea name=\"help\" id=\"plugin_help\" rows=\"\'.INPUT_REGULAR.\'\" class=\"mceEditor\" maxlength=\"\'.$this->ied_plugin_globals[\'size_help\'].\'\">\'.txpspecialchars($help).\'</textarea><div class=\"ied_plugin_info_bar\"><span class=\"ied_plugin_charsRemain\"></span></div>\';\n        $css_widget = ($styleblock) ? \'<textarea name=\"css\" id=\"plugin_help_css\" rows=\"\'.INPUT_MEDIUM.\'\" class=\"code\" maxlength=\"\'.$this->ied_plugin_globals[\'size_css\'].\'\">\'.txpspecialchars($css).\'</textarea><div class=\"ied_plugin_info_bar\"><span class=\"ied_plugin_charsRemain\"></span></div>\' : \'\';\n        $plugstatus = (!$editfile) ? sp.sp.checkbox(\'status\',1,$status, \'\',\'status\'). \' <label for=\"status\">\'.gTxt(\'ied_plugin_enable\').\'</label>\' : \'\';\n\n        $plugtypes = array(\n            \'0\' => gTxt(\'ied_plugin_type_0\'),\n            \'1\' => gTxt(\'ied_plugin_type_1\'),\n            \'2\' => gTxt(\'ied_plugin_type_2\'),\n            \'3\' => gTxt(\'ied_plugin_type_3\'),\n            \'4\' => gTxt(\'ied_plugin_type_4\'),\n            \'5\' => gTxt(\'ied_plugin_type_5\'),\n        );\n        $plugtype = selectInput(\'type\', $plugtypes, $type, false, \'\', \'type\');\n\n        $plugorder = selectInput(\'load_order\', $orders, $load_order, 0, 0);\n        $flaglist = checkbox(\n                \'flags[]\',\n                PLUGIN_HAS_PREFS,\n                (($flags & PLUGIN_HAS_PREFS) ? 1 : 0),\n                \'\',\n                \'ied_plugin_flag_has_prefs\'\n            )\n            .n. \'<label for=\"ied_plugin_flag_has_prefs\">\' . gTxt(\'ied_plugin_flag_has_prefs\') . \'</label>\'\n            .n. checkbox(\n                \'flags[]\',\n                PLUGIN_LIFECYCLE_NOTIFY,\n                (($flags & PLUGIN_LIFECYCLE_NOTIFY) ? 1 : 0),\n                \'\',\n                \'ied_plugin_flag_lifecycle_notify\'\n            )\n            .n. \'<label for=\"ied_plugin_flag_lifecycle_notify\">\' . gTxt(\'ied_plugin_flag_lifecycle_notify\') . \'</label>\';\n\n        $sub = graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\', \'\', \'\', \'\', \'\', \'ied_editSave\'), array(\'class\' => \'txp-save\'));\n        $codesub = (!$editfile) ? \'<a class=\"navlink\" name=\"ied_plugin_code_save\" id=\"ied_plugin_code_save\">\' . gTxt(\'ied_plugin_code_save\') . \'</a>\' : \'\';\n        $metasub = (!$editfile) ? \'<a class=\"navlink\" name=\"ied_plugin_meta_save\" id=\"ied_plugin_meta_save\">\' . gTxt(\'ied_plugin_meta_save\') . \'</a>\' : \'\';\n\n        // Language info. ied_visible_langs is the user\'s choice of which ones they want to see available.\n        // ied_available_langs is the list of actual, currently-installed langs.\n        $theLang = get_pref(\'language\');\n        $string_count = ($tp_pfx) ? safe_rows(\'lang, count(*) as count\', \'txp_lang\', \"name like \'\".$tp_pfx.\"%\' group by lang\") : array();\n        $ied_listlangs = get_pref(\'ied_plugin_lang_choose\', \'installed\');\n        $ied_visible_langs = self::lang_list($ied_listlangs);\n        $ied_available_langs = ($ied_listlangs == \'installed\') ? $ied_visible_langs : self::lang_list(\'installed\');\n        $dflt_lang = get_pref(\'ied_plugin_lang_default\', $theLang);\n        $dflt_lang = array_key_exists($dflt_lang, $ied_visible_langs) ? $dflt_lang : $theLang;\n        $dflt_lang_string_count = 0;\n\n        foreach ($string_count as $str_totals) {\n            if (isset($ied_visible_langs[$str_totals[\'lang\']])) {\n                $ied_visible_langs[$str_totals[\'lang\']] .= \' [\'.$str_totals[\'count\'].\']\';\n                if ($str_totals[\'lang\'] === $dflt_lang) {\n                    $dflt_lang_string_count = $str_totals[\'count\'];\n                }\n            }\n        }\n        $langsel = selectInput(\'ied_plugin_tp_lang\', $ied_visible_langs, $dflt_lang, \'\', \'\', \'ied_plugin_tp_lang\')\n            .fInput(\'button\', \'ied_plugin_tp_refresh\', gTxt(\'ied_plugin_load\'), \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_refresh\');\n\n        $preselected = do_list(get_pref(\'ied_plugin_lang_selected\', \'\'));\n\n        $op_langs[] = \'<select name=\"ied_plugin_tp_oplangs\" id=\"ied_plugin_tp_oplangs\" multiple=\"multiple\"><option value=\"\"></option>\';\n        foreach ($ied_available_langs as $langcode => $alang) {\n            $sel = in_array($langcode, $preselected) ? \' selected=\"selected\"\' : \'\';\n            $op_langs[] = \'<option value=\"\'.$langcode.\'\"\'.$sel.\'>\'.$alang.\'</option>\';\n        }\n        $op_langs[] = \'</select>\';\n\n        $tp_strings = array();\n        $tp_rows = $this->textpack_grab($dflt_lang, $tp_pfx);\n        foreach ($tp_rows as $tp_string) {\n            $apsel = selectInput(\'ied_plugin_tp_event\', array(\'admin\' => gTxt(\'admin\'), \'public\' => gTxt(\'public\'), \'common\' => gTxt(\'both\')), ($tp_string[\'event\'] == \'public\' ? \'public\' : ($tp_string[\'event\'] == \'common\' ? \'common\' : \'admin\')) );\n            $tp_strings[] = \'<li>\'.fInput(\'text\', \'textpack_\'.$tp_string[\'name\'], $tp_string[\'data\']).\' \'.$apsel.\' <label>\'.$tp_string[\'name\'].\'</label>\'.\'</li>\';\n        }\n\n\n        $err_prefix = gTxt(\'ied_plugin_syntax_err\');\n        $codesave_ok = gTxt(\'ied_plugin_code_saved\');\n        $phpdoc = \'\';\n\n        // TODO\n        $classFinder = \'/class[\\s\\n]+(\\w+)[\\s\\n]*\\{?(function[\\s\\n]+(\\w+)[\\s\\n]*\\(.*\\)[\\s\\n]*\\{?)*/\';\n        $functionFinder = \'/function[\\s\\n]+(\\w+)[\\s\\n]*\\(.*\\)[\\s\\n]*\\{?/\';\n        preg_match_all($functionFinder, $code , $functionArray);\n        // selectInput requires key and value to be the same\n    //dmp($functionArray);\n        $fnArray = array();\n    /*\n        foreach ($functionArray[1] as $key => $val) {\n            $fnArray[$val] = $functionArray[1][$key];\n        }\n        $phpdoc = selectInput(\'ied_plugin_to_phpdoc\', $fnArray, \'\', false, \'\', \'ied_plugin_to_phpdoc\')\n            .\'<a class=\"navlink\" name=\"ied_plugin_btn_phpdoc\" id=\"ied_plugin_btn_phpdoc\">\' . gTxt(\'ied_plugin_php_doc\') . \'</a>\';\n    */\n        $phpdoc = \'\';\n        $tabCount = $tabActive = 0;\n        $selectedTab = get_pref(\'pane_\' . $this->ied_pc_event . \'_visible\');\n        $tokenSuffix = $this->ied_pc_event . form_token() . get_pref(\'blog_uid\');\n        $panelSet = array(\n            0 => \'meta\',\n            1 => \'code\',\n            2 => \'pack\',\n            3 => \'docs\',\n            );\n\n        $switcherList = array();\n\n        foreach ($panelSet as $tabId => $panel) {\n            $switcherList[] = \'<li><a href=\"#options_group_\'.$panel.\'\" data-txp-pane=\"\'.$panel.\'\" data-txp-token=\"\' . md5($panel.$tokenSuffix).\'\">\' . gTxt(\'ied_plugin_\'.$panel.\'_legend\') . \'</a></li>\';\n            if ($panel === $selectedTab) {\n                $tabActive = $tabCount;\n            }\n\n            $tabCount++;\n        }\n\n        echo\n            n. form(\n                \'<div class=\"txp-layout\">\'\n                .n. \'<div class=\"txp-layout-2col\">\'\n                .n. hed(gTxt(\'ied_plugin_edit\', array(\'{name}\' => $name, \'{version}\' => $version)), 1, array(\'class\' => \'txp-heading\'))\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-2col\">\'\n                .n. sLink($this->ied_pc_event, \'\', gTxt(\'ied_plugin_list\'), \'ied_plugin_link\')\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-4col-alt\" id=\"ied_edit_switcher\" role=\"region\">\'\n                .n. \'<section class=\"txp-details\" id=\"all_options\" aria-labelledby=\"all_options-label\">\'\n                .n. \'<h3 id=\"all_options-label\">Panels</h3>\'\n                .n. \'<div role=\"group\">\'\n                .n. \'<ul class=\"switcher-list\">\'\n                .implode(n, $switcherList)\n                .n. \'</ul>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n                .n. $sub\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_dist\" aria-labelledby=\"options_group_dist-label\">\'\n                .n. hed(gTxt(\'ied_plugin_utils_legend\'), 2, array(\'id\' => \'options_group_dist-label\'))\n                .n. graf(gTxt(\'ied_plugin_utils_legend_extra\'), array(\'class\' => \'warning\'))\n                .n. (($distblock) ? \'<div>\' . $distribution . \'</div>\' : \'\')\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"ied_plugin_tp_oplangs\">\' . gTxt(\'ied_plugin_lang_choose\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . implode(n, $op_langs) . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<ul>\'\n                .n. \'<li>\' . $slink . \'</li>\'\n                .n. \'<li>\' . $sziplink . \'</li>\'\n                .n. \'<li>\' . $stxtlink . \'</li>\'\n                .n. \'<li>\' . $sphplink . \'</li>\'\n                .n. \'</ul>\'\n                .n. \'</section>\'\n                .n. \'</div>\'\n\n                .n. \'<div class=\"txp-layout-4col-3span\" id=\"ied_edit_content\" role=\"region\">\'\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_meta\" aria-labelledby=\"options_group_meta-label\">\'\n                .n. hed(gTxt(\'ied_plugin_meta_legend\'), 2, array(\'id\' => \'options_group_meta-label\'))\n                .n. \'<span class=\"ied_plugin_edit_toolbar\">\' . $metasub . \'</span>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"newname\">\' . gTxt(\'name\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $newname . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"version\">\' . gTxt(\'version\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. $version_widget\n                .n. $plugstatus\n                .n. (($filename) ? tag(sp.sp.\'(\'.$filename.\')\',\'span\',\' style=\"color:gray;\"\').hInput(\'filename\', $filename) : \'\')\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"description\">\' . gTxt(\'description\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $description_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"author\">\' . gTxt(\'author\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $author_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"author_uri\">\' . gTxt(\'website\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $author_uri_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"type\">\' . gTxt(\'ied_plugin_type\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $plugtype . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_flags\')  . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $flaglist . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_load_order\') . gTxt(\'ied_plugin_load_order_help\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $plugorder . \'</div>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_code\" aria-labelledby=\"options_group_code-label\">\'\n                .n. hed(gTxt(\'ied_plugin_code_legend\'), 2, array(\'id\' => \'options_group_code-label\'))\n                .n. \'<span class=\"ied_plugin_edit_toolbar\">\' . $msgpop . $phpdoc . sp . $codesub . \'</span>\'\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"plugin_editor\">PHP</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $codeblock . \'</div>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_pack\" aria-labelledby=\"options_group_pack-label\">\'\n                .n. hed(gTxt(\'ied_plugin_pack_legend\'), 2, array(\'id\' => \'options_group_pack-label\'))\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"ied_plugin_tp_prefix\">\' . gTxt(\'ied_plugin_tp_prefix\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. fInput(\'text\', \'ied_plugin_tp_prefix\', $tp_pfx, \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_prefix\')\n                .n. $langsel\n                .n. fInput(\'hidden\', \'ied_plugin_tp_lang_dflt\', $dflt_lang, \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_lang_dflt\')\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_tp_populate\')  . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. fInput(\'text\', \'ied_plugin_tp_populate\', \'\', \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_populate\')\n                .n. \'<button id=\"ied_plugin_tp_load\">\' . gTxt(\'go\') . \'</button>\'\n                .n. \'<span id=\"ied_plugin_tp_load_count\"></span>\'\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<a href=\"#\" id=\"ied_plugin_add_string\" class=\"txp-button\">+</a>\'\n                .n. \'<ul>\'\n                .n. implode(n, $tp_strings)\n                .n. \'</ul>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_docs\" aria-labelledby=\"options_group_docs-label\">\'\n                .n. hed(gTxt(\'ied_plugin_docs_legend\') . n . $vhelplinkfull, 2, array(\'id\' => \'options_group_docs-label\'))\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"plugin_help\">\' . gTxt(\'text\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $help_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. (($styleblock)\n                        ? \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                            . \'<div class=\"txp-form-field-label\"><label for=\"plugin_help_css\">\' . gTxt(\'css\') . \'</label></div>\'\n                            . \'<div class=\"txp-form-field-value\">\' . $css_widget . \'</div>\'\n                            . \'</div>\'\n                        : \'\')\n                .n. \'</section>\'\n                .n. \'</div>\'\n                .n. sInput(\'save\')\n                .n. eInput($this->ied_pc_event)\n                .n. hInput(\'name\',$name)\n            , \'\', \'\', \'post\', \'ied-edit-form\').\n            script_js(<<<EOJS\nvar selectedTab = \'{$tabActive}\';\nvar iedPluginGroup = $(\'.ied-edit-form\');\nvar iedPluginTabs = iedPluginGroup.find(\'.switcher-list li\');\n\niedPluginGroup.tabs({active: selectedTab}).removeClass(\'ui-widget ui-widget-content ui-corner-all\').addClass(\'ui-tabs-vertical\');\niedPluginGroup.find(\'.switcher-list\').removeClass(\'ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all\');\niedPluginTabs.removeClass(\'ui-state-default ui-corner-top\');\niedPluginGroup.find(\'.txp-prefs-group\').removeClass(\'ui-widget-content ui-corner-bottom\');\n\niedPluginTabs.on(\'click\', \'a\', function(ev) {\n    var me = $(this);\n    sendAsyncEvent({\n            event   : \'pane\',\n            step    : \'tabVisible\',\n            pane    : me.data(\'txp-pane\'),\n            origin  : textpattern.event,\n            token   : me.data(\'txp-token\')\n        });\n});\n\nvar ied_plugin_tp_total = 0;\n\njQuery.fn.selectRange = function(start, end) {\n    return this.each(function() {\n        if (this.setSelectionRange) {\n            this.focus();\n            this.setSelectionRange(start, end);\n        } else if (this.createTextRange) {\n            var range = this.createTextRange();\n            range.collapse(true);\n            range.moveEnd(\'character\', end);\n            range.moveStart(\'character\', start);\n            range.select();\n        }\n    });\n};\n\nArray.prototype.unique = function() {\n    var r = new Array();\n    o:for (var i = 0, n = this.length; i < n; i++) {\n        for (var x = 0, y = r.length; x < y; x++) {\n            if (r[x]==this[i]) {\n                continue o;\n            }\n        }\n        r[r.length] = this[i];\n    }\n\n    return r;\n};\n\nfunction ied_goToLine()\n{\n    var line = parseInt(jQuery(\'#ied_plugin_jumpToLine\').val());\n    var ied_ed = jQuery(\'#plugin_editor\');\n    var ied_edd = document.getElementById(\'plugin_editor\'); // Dunno how to convert a jQuery obj back to DOM\n    var lines = ied_ed.val().split(\'\\\\n\');\n    var numchars = 0;\n    var count = 0;\n    var findstr = \'\';\n    jQuery.each(lines, function () {\n        count++;\n        if (count >= line) {\n            findstr = this;\n\n\n            return false;\n        }\n        numchars += (this.length)+2; // Don\'t ask. +2 is something to do with line endings I think\n    });\n\n    // Find the line containing the string we found. Start counting from the line before.\n    // Those pesky line endings come into play again so we need to subtract the number\n    // of lines found from the start character position *shrug*\n    start = ied_ed.val().indexOf(findstr, numchars-count);\n    start = (findstr == \'\') ? start+1 : start;\n    end = start+findstr.length;\n    ied_ed.selectRange(end-1, end);\n\n    if (document.createEvent) {\n        var ied_theCode = ied_ed.val().charCodeAt(end-1);\n        if (window.KeyEvent) {\n            var ev = document.createEvent(\'KeyEvents\');\n            ev.initKeyEvent(\'keypress\', false, true, window, false, false, false, false, 0, ied_theCode);\n        } else {\n            var ev = document.createEvent(\'UIEvents\');\n            ev.initUIEvent(\'keypress\', false, true, window, 0);\n            ev.keyCode = ied_theCode;\n        }\n        ied_edd.dispatchEvent(ev); // cause scroll to cursor by replacing last char with itself\n    }\n    ied_ed.selectRange(start, end);\n\n    return false;\n}\nfunction ied_plugin_toggle_msgpop(state)\n{\n    var obj = jQuery(\"#ied_plugin_msgpop\");\n    if (state != undefined) {\n        if (state == 1) {\n            obj.show(\'normal\');\n        } else {\n            obj.hide(\'normal\');\n        }\n    } else {\n        obj.toggle(\'normal\');\n    }\n}\nfunction ied_plugin_rtrim(str, chars)\n{\n    chars = chars || \"\\s\";\n\n    return str.replace(new RegExp(\"[\" + chars + \"]+$\", \"g\"), \"\");\n}\nfunction ied_plugin_update_tp_count()\n{\n    var tp_count = tp_warns = tp_has_content = 0;\n\n    jQuery(\'#options_group_pack ul li\').each(function() {\n        var self = jQuery(this);\n        tp_count++;\n        if (self.find(\'input\').val() !== \'\') {\n            tp_has_content++;\n        }\n        if (self.find(\'label\').hasClass(\'warning\')) {\n            tp_warns++;\n        }\n    });\n    jQuery(\'#ied_plugin_tp_count\').empty().append(\'(\' +tp_count+ \' | \' +tp_has_content+ \' | \' +tp_warns+ \')\');\n    jQuery(\'#ied_plugin_tp_lang option\').find(\':selected\').data(\'string-count\', tp_has_content);\n\n    // Update the global var for use when loading strings\n    ied_plugin_tp_total = tp_count;\n}\n\njQuery(function () {\n    curh = getCookie(\'ied_plugin_edheight\');\n    curh = (curh == null) ? \'480\' : curh;\n    jQuery(\'textarea[maxlength]\').keyup(function () {\n        var max = parseInt(jQuery(this).attr(\'maxlength\'));\n        if (jQuery(this).val().length > max) {\n            jQuery(this).val(jQuery(this).val().substr(0, jQuery(this).attr(\'maxlength\')));\n        }\n        jQuery(this).parent().find(\'.ied_plugin_charsRemain\').html(\'Chars remaining: \'+ (max - jQuery(this).val().length));\n    });\n    jQuery(\'textarea[maxlength]\').keyup();\n    jQuery(\'#ied_plugin_jumpToLine\').keydown(function (e) {\n        var code = (e.keyCode ? e.keyCode : e.which);\n        if (code == 13) {\n            e.preventDefault();\n            e.stopPropagation();\n            ied_goToLine();\n\n\n            return false;\n        }\n    });\n\n    // Store the prefix\n    jQuery(\'#ied_plugin_tp_prefix\').blur(function () {\n        var pfx = jQuery(this).val();\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'set_tp_prefix\',\n            plugin: \'{$name}\',\n            prefix: pfx\n        });\n    });\n\n    // Find all occurrences of gTxt(\'something\')\n    jQuery(\'#plugin_editor, #ied_plugin_tp_prefix\').blur(function () {\n        var ied_tp_pfx = jQuery(\'#ied_plugin_tp_prefix\').val();\n        if (ied_tp_pfx != \'\') {\n            var ied_gtxt_re = /gTxt\\([\\\'\\\"]([a-zA-Z0-9_]*?)[\\\"\\\'][,\\)]/gi;\n            var ied_tp_ta = jQuery(\'#plugin_editor\').val().replace(/\\s*/g,\'\'); // Strip spaces to make lookups easier\n            var ied_tp_items = ied_tp_ta.match(ied_gtxt_re);\n\n            // if JS RegExp captured parenthical expressions in global searches or it was easy to inject variables\n            // into new RegExp() calls, this loop could be avoided\n            var ied_tp_used = [];\n            for (var idx = 0; idx < ied_tp_items.length; idx++) {\n                var pos = ied_tp_items[idx].lastIndexOf(\"\'\");\n                pos = (pos == -1) ? ied_tp_items[idx].lastIndexOf(\'\"\') : pos;\n                tpstr = ied_tp_items[idx].substr(6,pos-6);\n                if (tpstr.indexOf(ied_tp_pfx) == 0) {\n                    ied_tp_used[ied_tp_used.length] = tpstr;\n                }\n            }\n\n            ied_tp_used = ied_tp_used.unique();\n\n            // List of all current textpack strings in use (as of last Save operation)\n            var ied_tp_curr = [];\n            jQuery(\'#options_group_pack ul label\').each(function () {\n                ied_tp_curr[ied_tp_curr.length] = jQuery(this).text();\n            });\n\n            // Iterate over current array and check if each name is in the used textpack item list.\n            // If it is, remove it from the final list.\n            for (var idx = 0; idx < ied_tp_curr.length; idx++) {\n                if ((pos = jQuery.inArray(ied_tp_curr[idx], ied_tp_used)) > -1) {\n                    ied_tp_used.splice(pos, 1);\n                    jQuery(\'#options_group_pack ul label:contains(\'+ied_tp_curr[idx]+\')\').toggleClass(\'warning\', false).next(\".destroy\").remove();\n                } else {\n                    setclass = 1;\n                    jQuery(\'#options_group_pack ul label:contains(\'+ied_tp_curr[idx]+\')\').toggleClass(\'warning\', true).next(\".destroy\").remove().end().after(\'<a href=\"#\" class=\"destroy\"><span class=\"ui-icon ui-icon-close\">Delete</span></a>\');\n                }\n            }\n            // For each remaining item that has been used, add an input box\n            // TODO: i18n the select options\n            for (var idx = 0; idx < ied_tp_used.length; idx++) {\n                jQuery(\'#options_group_pack ul\').prepend(\'<li><input type=\"text\" name=\"textpack_\'+ied_tp_used[idx]+\'\" value=\"\" /> <select name=\"ied_plugin_tp_event\"><option value=\"admin\">Admin</option><option value=\"public\">Public</option><option value=\"common\">Both</option></select> <label>\'+ied_tp_used[idx]+\'</label></li>\');\n            }\n            ied_plugin_update_tp_count();\n        }\n    }).blur();\n\n    // Handle adding new strings manually\n    jQuery(\'#ied_plugin_add_string\').click(function (ev) {\n        jQuery(\'#options_group_pack ul\').before(\'<div id=\"ied_plugin_new_container\"><label>\'+jQuery(\'#ied_plugin_tp_prefix\').val()+\'_<input type=\"text\" name=\"ied_plugin_tp_newname\" id=\"ied_plugin_tp_newname\" value=\"\" /></label></div>\');\n        jQuery(\'#ied_plugin_tp_newname\').focus();\n        ev.preventDefault();\n    });\n    jQuery(document).on(\'blur\', \'#ied_plugin_tp_newname\', function () {\n        var newname = ied_plugin_rtrim(jQuery(\'#ied_plugin_tp_prefix\').val()+\'_\'+jQuery(\'#ied_plugin_tp_newname\').val(), \'_\');\n        var newok = true;\n        jQuery(\'#options_group_pack ul li label\').each(function () {\n            if (jQuery(this).text() == newname) {\n                jQuery(\'#ied_plugin_tp_newname\').css(\'color\', \'#E00\');\n                newok = false;\n            }\n        });\n        // TODO: i18n select option text\n        if (newok) {\n            jQuery(\'#options_group_pack ul\').prepend(\'<li><input type=\"text\" name=\"textpack_\'+newname+\'\" value=\"\" /> <select name=\"ied_plugin_tp_event\"><option value=\"admin\">Admin</option><option value=\"public\">Public</option><option value=\"common\">Both</option></select> <label>\'+newname+\'</label></li>\');\n            jQuery(\'#ied_plugin_new_container\').remove();\n            jQuery(\'input[name=\"textpack_\'+newname+\'\"]\').focus();\n        }\n        ied_plugin_update_tp_count();\n    });\n\n    // Initialise the generic AJAX error handler\n    jQuery(\'.ied_editForm\').ajaxError(function (event, request, settings) {\n        var xhr = jQuery(request.responseText);\n\n        // phpdoc generation barfed\n        if (settings.data.indexOf(\'step=ied_plugin_generate_phpdoc\') > -1) {\n            var msgContent = jQuery(\"#ied_plugin_msgpop .ied_plugin_msgpop_content\");\n            status = xhr.find(\'http-status\').attr(\'value\')\n            if (status == \'200 OK\') {\n                msgContent.append(xhr.find(\'ied_plugin_phpdoc\').attr(\'value\'));\n            } else if (status == \'501 Not Implemented\') {\n                msgContent.append(xhr.find(\'error_msg\').attr(\'value\'));\n            }\n            ied_plugin_toggle_msgpop(\'1\');\n        }\n\n        // code save barfed\n        if (settings.data.indexOf(\'step=ied_plugin_code_save\') > -1) {\n            var msg = xhr.find(\'ied_plugin_msg\').attr(\'value\');\n            var line = xhr.find(\'ied_plugin_err_line\').attr(\'value\');\n            jQuery(\'#ied_plugin_jumpToLine\').val(line);\n            ied_goToLine();\n            var codeobj = jQuery(\"#plugin_editor\");\n            codeobj.css({\'opacity\': \'.75\'}); // Reduced opacity as a visual cue that something\'s wrong\n            eval(msg); // bleurgh!\n        }\n    });\n\n    // Handle \'x\' button\n    jQuery(document).on(\'click\', \'.destroy\', function (event) {\n        var elem = jQuery(this).prev(\'label\');\n        var tp_lbl = elem.text();\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'textpack_del\',\n            ied_tp_lbl: tp_lbl\n        });\n\n        elem.parent().remove();\n        event.preventDefault();\n        ied_plugin_update_tp_count();\n    });\n\n    // Save textpack string to database\n    function ied_plugin_tp_save(event)\n    {\n        var elem = jQuery(this);\n        var isSel = elem.is(\'select\');\n        var tp_lbl = elem.nextAll(\'label\').text();\n        var tp_str = (isSel) ? elem.prevAll(\'input\').val() : elem.val();\n        var tp_ev = (isSel) ? elem.val() : elem.nextAll(\'select\').val();\n        var tp_evt = (tp_ev==\'public\' || tp_ev==\'common\') ? tp_ev : jQuery(\'#ied_plugin_tp_prefix\').val();\n        var tp_lng = jQuery(\'#ied_plugin_tp_lang\').val();\n        var tp_own = jQuery(\'#ied_plugin_tp_prefix\').val();\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'textpack_save\',\n            ied_tp_evt: tp_evt,\n            ied_tp_lbl: tp_lbl,\n            ied_tp_lng: tp_lng,\n            ied_tp_str: tp_str,\n            ied_tp_own: tp_own\n        });\n    }\n\n    // Handle saving textpack string\n    jQuery(document).on(\'blur\', \'#options_group_pack ul li input\', ied_plugin_tp_save);\n    jQuery(document).on(\'change\', \'#options_group_pack ul li select\', ied_plugin_tp_save);\n\n    // Handle language change.\n    jQuery(\"#ied_plugin_tp_lang\").change(function (event) {\n        jQuery(\'#ied_plugin_tp_load_count\').empty().show();\n\n        var tp_lng = jQuery(this).val();\n        var tp_dflt = jQuery(\'#ied_plugin_tp_lang_dflt\').val();\n        var sel = \'#options_group_pack ul li\';\n        var numStrings = sel.length;\n        var numFetched = 0;\n\n        jQuery(sel).each(function () {\n            var obj = jQuery(this);\n            var tp_lbl = obj.find(\'label\').text();\n            var tp_dest = obj.find(\'input\');\n\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'textpack_get\',\n                ied_tp_lbl: tp_lbl,\n                ied_tp_lng: tp_lng,\n                ied_tp_dflt: tp_dflt\n            }, function (data) {\n                numFetched++;\n                var theVal = data.ied_plugin_tp_string;\n                var xl8str = data.ied_plugin_tp_dflt;\n\n                tp_dest.val(theVal);\n\n                if (xl8str == undefined || xl8str == \'\') {\n                    obj.removeAttr(\'title\');\n                } else {\n                    obj.attr(\'title\', xl8str);\n                }\n                if (numFetched < ied_plugin_tp_total) {\n                    jQuery(\'#ied_plugin_tp_load_count\').text(numFetched + \'/\' + ied_plugin_tp_total);\n                } else {\n                    jQuery(\'#ied_plugin_tp_load_count\').text(\'OK\').hide(\'slow\');\n                    ied_plugin_update_tp_count();\n                }\n            },\n            \'json\');\n        });\n    });\n\n    // Current language refresh.\n    jQuery(\'#ied_plugin_tp_refresh\').click(function () {\n        // Trigger the change event\n        jQuery(\"#ied_plugin_tp_lang\").change();\n    });\n\n    // Load textpack strings from plugin\'s custom gTxt()\n    jQuery(\"#ied_plugin_tp_load\").click(function (event) {\n        var ied_fn = jQuery(\"#ied_plugin_tp_populate\").val();\n\n\n        jQuery(\'#options_group_pack ul li\').each(function () {\n            var obj = jQuery(this);\n            var tp_lbl = obj.find(\'label\').text();\n            var tp_dest = obj.find(\'input\');\n\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'textpack_load\',\n                ied_tp_fn: ied_fn,\n                ied_tp_lbl: tp_lbl\n            }, function (data) {\n                // Paste the returned string into the input box and save it by invoking blur()\n                tp_dest.val(data.ied_plugin_tp_string).blur();\n            },\n            \'json\');\n        });\n        event.preventDefault();\n    });\n\n    // Handle saving metadata.\n    jQuery(\"#ied_plugin_meta_save\").click(function(event) {\n        var msgarea = jQuery(\"#ied_plugin_messages\");\n        msgarea.empty();\n        var metaobj = jQuery(\"#options_group_meta\");\n        var datablock = {};\n\n        jQuery(metaobj).find(\'input[type=\"text\"], select\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\');\n            datablock[objid] = obj.val();\n        });\n\n        jQuery(metaobj).find(\'input[type=\"radio\"], input[type=\"checkbox\"]\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\').replace(/\\[\\]/, \'\');\n            if (typeof datablock[objid] == \'undefined\') {\n                datablock[objid] = [];\n            }\n            if (obj.prop(\'checked\') === true) {\n                datablock[objid].push(obj.val());\n            }\n        });\n\n        jQuery(metaobj).find(\'select\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\');\n            if (obj.prop(\'selected\') === true) {\n                datablock[objid] = obj.val();\n            }\n        });\n        var plugin = \'{$name}\';\n\n        metaobj.css(\'opacity\', \'0.4\');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'meta_save\',\n            plugin: plugin,\n            data: datablock\n        }, function(data) {\n            metaobj.css({\'opacity\': \'1\'});\n            var msg = jQuery(data).find(\'ied_plugin_msg\').attr(\'value\');\n            eval(msg); // yuk!\n        });\n\n        event.preventDefault();\n    });\n\n    // Handle saving code.\n    jQuery(\"#ied_plugin_code_save\").click(function (event) {\n        var msgarea = jQuery(\"#ied_plugin_messages\");\n        msgarea.empty();\n        var codeobj = jQuery(\"#plugin_editor\");\n        var codeblock = codeobj.val();\n        var plugin = \'{$name}\';\n\n        codeobj.css(\'opacity\', \'0.4\');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'code_save\',\n            plugin: plugin,\n            codeblock: codeblock\n        }, function (data) {\n            codeobj.css({\'opacity\': \'1\'});\n            var msg = jQuery(data).find(\'ied_plugin_msg\').attr(\'value\');\n            eval(msg); // yuk!\n        });\n\n        event.preventDefault();\n    });\n\n    // Handle generating phpdoc\n    jQuery(\"#ied_plugin_btn_phpdoc\").click(function (event) {\n        var msgarea = jQuery(\"#ied_plugin_msgpop\");\n        var msgContent = jQuery(\"#ied_plugin_msgpop .ied_plugin_msgpop_content\");\n        msgContent.empty();\n        var fnobj = jQuery(\"#ied_plugin_to_phpdoc\");\n        var fn = fnobj.val();\n        var plugin = \'{$name}\';\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'generate_phpdoc\',\n            plugin: plugin,\n            fn: fn\n        }, function (data) {\n            msgContent.append(jQuery(data).find(\'ied_plugin_phpdoc\').attr(\'value\'));\n            ied_plugin_toggle_msgpop(\'1\');\n        });\n\n        event.preventDefault();\n    });\n\n    jQuery(\'#ied_plugin_tp_oplangs\').change(function () {\n        sel = jQuery(\'#ied_plugin_tp_oplangs option:selected\').map(function () { return this.value }).get().join(\', \');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'lang_set\',\n            ied_tp_langsel: sel\n        });\n    });\n\n});\nEOJS\n            )\n        .n. \'</div>\';\n    }\n\n    /**\n     * Save the plugin to the DB or filesystem.\n     */\n    public function save()\n    {\n        extract(doSlash(gpsa(array(\'name\',\'newname\',\'filename\',\'code\',\'author\',\'author_uri\',\'version\',\'description\',\'help\',\'css\',\'status\',\'type\',\'load_order\',\'rename_file\',\'restore_point\',\'ied_plugin_tp_prefix\'))));\n        $flags = gps(\'flags\');\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n        $extraMsg = $newfilename = $msg1 = $msg2 = \'\';\n        $newname = trim($newname);\n        if ($flags) {\n            $flagout = 0;\n            foreach ($flags as $flag) {\n                $flagout |= $flag;\n            }\n            $flags = $flagout;\n        }\n\n        if (empty($newname)) {\n            $msg1 = gTxt(\'ied_plugin_name_first\');\n            $msgType = E_ERROR;\n        } else {\n            if (empty($filename)) {\n                $hout = array();\n                $hout[0] = $help;\n                if ($css) { $hout[1] = n.$start_css.n.$css.n.$end_css; }\n                safe_update(\n                    \'txp_plugin\',\n                    \"name=\'\".$newname.\"\',\n                    status = \".intval($status).\",\n                    type = \".intval($type).\",\n                    author = \'\".$author.\"\',\n                    author_uri = \'\".$author_uri.\"\',\n                    version = \'\".$version.\"\',\n                    description = \'\".$description.\"\',\n                    help = \'\".implode(\'\',$hout).\"\',\n                    code = \'\".$code.\"\',\n                    flags = \".intval($flags).\",\n                    \".(($restore_point == 1)? \"code_restore = \'\".$code.\"\',\" : \'\').\"\n                    load_order = \".$load_order,\n                    \"name = \'\".$name.\"\'\");\n                $msg1 = gTxt(\'plugin_saved\', array(\'{name}\' => $newname));\n                $msgType = \'\';\n            } else {\n                $pcd = get_pref(\'plugin_cache_dir\');\n                $dir = $pcd.DS;\n\n                if (empty($pcd)) {\n                    $filecontent = \'\';\n                    $msg2 = gTxt(\'ied_plugin_cache_not_set\');\n                } else {\n                    $ioo = get_pref(\'ied_plugin_output_order\');\n                    $oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;\n                    $textpack = $this->textpack_build($newname);\n                    $helpchunk = $this->build_template(\'help\', array($help, \' \')); // Use a space to force a CSS hunk in the file\n                    $codechunk = $this->build_template(\'code\', $code);\n                    $filecontent = $this->build_template(\"preamble\")\n                        .$this->build_template(\"name\", $newname)\n                        .$this->build_template(\"html_help\")\n                        .$this->build_template(\"version\", $version)\n                        .$this->build_template(\"author\", $author)\n                        .$this->build_template(\"author_uri\", $author_uri)\n                        .$this->build_template(\"description\", $description)\n                        .$this->build_template(\"load_order\", $load_order)\n                        .$this->build_template(\"type\", $type)\n                        .$this->build_template(\"flags\", $flags)\n                        .$this->build_template(\"textpack\", $textpack)\n                        .$this->build_template(\"include\")\n                        .(($oporder == 0) ? $codechunk : $helpchunk)\n                        .(($oporder == 1) ? $codechunk : $helpchunk)\n                        .$this->build_template(\"postamble\");\n                    $msgType = \'\';\n                }\n\n                $filecontent = $this->make_array($filecontent);\n                $metavars = array(\"name\" => \"$newname\",\n                                        \"version\" => \"$version\",\n                                        \"author\" => \"$author\",\n                                        \"author_uri\" => \"$author_uri\",\n                                        \"description\" => \"$description\",\n                                        \"type\" => \"$type\",\n                                        \"flags\" => \"$flags\",\n                                        \"order\" => \"$load_order\",\n                                );\n                $hunkvars = array(\"CODE\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$code)), // newline workaround\n                            \"HELP\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$help)),\n                            \"CSS\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$css)),\n                                );\n                foreach ($metavars as $varname => $value) {\n                    for ($idx = 0; $idx < count($filecontent); $idx++) {\n                        if (strpos($filecontent[$idx], \'$plugin[\\\'\'.$varname.\'\\\']\') === 0) {\n                            $filecontent[$idx] = \'$plugin[\\\'\'.$varname.\'\\\'] = \'.doQuote($value).\';\';\n                            break;\n                        }\n                    }\n                }\n                foreach ($hunkvars as $varname => $hunk) {\n                    list ($start_delim, $end_delim) = $this->make_markers($varname, $this->ied_plugin_globals[\'dlm_start\'], $this->ied_plugin_globals[\'dlm_end\']);\n                    $start = array_search($start_delim, $filecontent) + 1;\n                    $end = array_search($end_delim, $filecontent);\n                    if (is_numeric($start) && is_numeric($end) && $end >= $start) {\n                        array_splice($filecontent, $start, $end-$start, $hunk);\n                    }\n                }\n                $filecontent = implode(\"\\n\", $filecontent);\n\n                if ($filecontent) {\n                    $fh = fopen($dir.$filename, \'w+\');\n                    fwrite($fh, $filecontent);\n                    fclose($fh);\n                }\n                $msg1 = (empty($msg1)) ? gTxt(\'plugin_saved\', array(\'{name}\' => $newname)) : $msg1;\n                $msgType = \'\';\n\n                // Make new file if required\n                if ($rename_file == 1) {\n                    $fnames = $this->get_name($newname, $version);\n                    $newfilename = $fnames[2];\n                    $res = rename($dir.$filename, $dir.$newfilename);\n                    $extraMsg = ($res) ? gTxt(\'ied_plugin_renamed\') : gTxt(\'ied_plugin_rename_failed\');\n                }\n            }\n\n            // Store the plugin textpack prefix\n            $this->set_tp_prefix($newname, $ied_plugin_tp_prefix);\n        }\n        if ($msg2) {\n            $this->table($msg2);\n        } else {\n            // Check the plugin type matches the code used.\n            $extraMsg .= ($this->admin_check($code, $type)) ? \'\' : strong(gTxt(\'ied_plugin_check_type\'));\n            $message = $msg1.$extraMsg;\n            $this->edit(array($message, $msgType), $newfilename);\n        }\n    }\n\n    /**\n     * Export the plugin as a distributable txt file.\n     */\n    public function save_as_file()\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $rs = safe_row(\'description, author, author_uri, version, code, help, type, load_order, flags\', \'txp_plugin\', \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $zip = gps(\'type\');\n        if (gps(\'trim\')==1) {\n            $code=explode(\"\\r\\n\",$code);\n            $code=array_map(\'trim\',$code);\n            $code=implode(\"\\r\\n\",$code);\n        }\n\n        // Get any textpack strings\n        $textpack = $this->textpack_build($name);\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n        $fnames = $this->get_name($name, $version);\n\n        header(\'Content-type: text/plain\');\n        header(\'Content-Disposition: attachment; filename=\' . (($zip === \'zip\') ? $fnames[1] : $fnames[0]));\n\n        $types = array(\'Public\' , \'Admin/Public\' , \'Library\' , \'Admin\', \'Admin/AJAX\', \'Admin/Public/AJAX\'); // No gTxt() because the template is English\n        $plugin[\'name\'] = $name;\n        $plugin[\'author\'] = $author;\n        $plugin[\'author_uri\'] = $author_uri;\n        $plugin[\'version\'] = $version;\n        $plugin[\'description\'] = $description;\n        $plugin[\'code\'] = $code;\n        $plugin[\'type\'] = $type;\n        $plugin[\'order\'] = $load_order;\n        $plugin[\'flags\'] = $flags;\n    //  $plugin[\'allow_html_help\'] = true;\n    //  $plugin[\'help_raw\'] = $help.$start_css.$css.$end_css;\n        $plugin[\'help\'] = $this->textile($name, $help, $css, $start_css, $end_css);\n        $plugin[\'md5\'] = md5( $plugin[\'code\'] );\n        if ($textpack) {\n            $plugin[\'textpack\'] = $textpack;\n        }\n\n        echo \'# Name: \'.$name.\' v\'.$version.\' \'.(($zip === \'zip\') ? \"(compressed)\" : \"\").\'\n# Type: \'.$types[$type].\' plugin\n# \'.$description.\'\n# Author: \'.$author.\'\n# URL: \'.$author_uri.\'\n# Recommended load order: \'.$load_order.\'\n\n# .....................................................................\n# This is a plugin for Textpattern CMS - http://textpattern.com/\n# To install: textpattern > admin > plugins\n# Paste the following text into the \\\'Install plugin\\\' box:\n# .....................................................................\n\n\'.(($zip === \'zip\') ? chunk_split(base64_encode(gzencode(serialize($plugin))), 72) : chunk_split(base64_encode(serialize($plugin)), 72));\n\n        die();\n    }\n\n    /**\n     * Export the plugin as a plugin template PHP file.\n     */\n    public function save_as_php_file()\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $rs = safe_row(\"description, author, author_uri, version, code, help, type, load_order, flags\", \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin=$this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $ioo = get_pref(\'ied_plugin_output_order\');\n        $oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;\n        $fnames = $this->get_name($name, $version);\n\n        header(\'Content-type: text/php\');\n        header(\'Content-Disposition: attachment; filename=\' .$fnames[2]);\n\n        $textpack = $this->textpack_build($name);\n        $helpchunk = $this->build_template(\'help\', array($help, $css));\n        $codechunk = $this->build_template(\'code\', str_replace(\"\\r\\n\",\"\\n\",$code));\n\n        echo $this->build_template(\'preamble\').\n            $this->build_template(\'name\', $name).\n            $this->build_template(\'html_help\').\n            $this->build_template(\'version\', $version).\n            $this->build_template(\'author\', $author).\n            $this->build_template(\'author_uri\', $author_uri).\n            $this->build_template(\'description\', $description).\n            $this->build_template(\'load_order\', $load_order).\n            $this->build_template(\'type\', $type).\n            $this->build_template(\'flags\', $flags).\n            $this->build_template(\'textpack\', $textpack).\n            $this->build_template(\'include\').\n            (($oporder == 0) ? $codechunk : $helpchunk).\n            (($oporder == 1) ? $codechunk : $helpchunk).\n            $this->build_template(\'postamble\');\n\n        die();\n    }\n\n    /**\n     * Export only the textpacks from the plugin.\n     */\n    public function save_as_textpack()\n    {\n        if (gps(\'name\')) {\n            $name = $base_name = gps(\'name\');\n            $version = safe_field(\'version\', \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n        } elseif (gps(\'filename\')) {\n            $name = gps(\'filename\');\n            $base_name = str_replace(\'.php\', \'\', $name);\n            $plugin=$this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            $version = $plugin[\'version\'];\n        }\n\n        $langs = gps(\'lang\');\n        if (!$langs) {\n            $langs = get_pref(\'ied_plugin_lang_selected\', \'\');\n        }\n\n        $force = 0;\n        if ($langs == \'\') {\n            $langstr = \'all\';\n            $force = 1;\n        } else {\n            $langlist = do_list($langs);\n            if (count($langlist) == 1) {\n                $langstr = $langlist[0];\n            } else {\n                $country_codes = array();\n                foreach ($langlist as $ln) {\n                    $lparts = do_list($ln, \'-\');\n                    $country_codes[] = $lparts[0];\n                }\n                $langstr = implode(\'+\', array_unique($country_codes));\n            }\n        }\n        $textpack = $this->textpack_build($base_name, $force);\n        $fnames = $this->get_name($name, $version, $langstr);\n\n        header(\'Content-type: text/html; charset=UTF-8\');\n        header(\'Content-Disposition: attachment; filename=\' . $fnames[3]);\n        echo $textpack;\n        die();\n    }\n\n    /**\n     * Create a plugin in standard template format.\n     */\n    public function build_template($prt, $val=\'\')\n    {\n        $css = $help = \'\';\n        if (is_array($val)) {\n            $help = $val[0];\n            $css = isset($val[1]) ? $val[1] : \'\';\n            $val = \'\';\n        }\n        $template = array(\n            \"preamble\" => \'<?php\'.n.n\n                            .\'// This is a PLUGIN TEMPLATE for Textpattern CMS.\'.n.n\n                            .\'// Copy this file to a new name like abc_myplugin.php.  Edit the code, then\'.n\n                            .\'// run this file at the command line to produce a plugin for distribution:\'.n\n                            .\'// $ php abc_myplugin.php > abc_myplugin-0.1.txt\'.n.n,\n            \"name\" => \'// Plugin name is optional.  If unset, it will be extracted from the current\'.n\n                            .\'// file name. Plugin names should start with a three letter prefix which is\'.n\n                            .\'// unique and reserved for each plugin author (\"abc\" is just an example).\'.n\n                            .\'// Uncomment and edit this line to override:\'.n\n                            .\'$plugin[\\\'name\\\'] = \'.doQuote(doSlash($val)).\';\'.n.n,\n            \"html_help\" => \'// Allow raw HTML help, as opposed to Textile.\'.n\n                            .\'// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\'.n\n                            .\'// 1 = Plugin help is in raw HTML.  Not recommended.\'.n\n                            .\'# $plugin[\\\'allow_html_help\\\'] = 1;\'.n.n,\n            \"version\" => \'$plugin[\\\'version\\\'] = \'.doQuote($val).\';\'.n,\n            \"flags\" => \'// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\'.n\n                            .\'// Use an appropriately OR-ed combination of these flags.\'.n\n                            .\'// The four high-order bits 0xf000 are available for this plugin\\\'s private use\'.n\n                            .\'if (!defined(\\\'PLUGIN_HAS_PREFS\\\')) define(\\\'PLUGIN_HAS_PREFS\\\', 0x0001); // This plugin wants to receive \"plugin_prefs.{$plugin[\\\'name\\\']}\" events\'.n\n                            .\'if (!defined(\\\'PLUGIN_LIFECYCLE_NOTIFY\\\')) define(\\\'PLUGIN_LIFECYCLE_NOTIFY\\\', 0x0002); // This plugin wants to receive \"plugin_lifecycle.{$plugin[\\\'name\\\']}\" events\'.n.n\n                            .\'$plugin[\\\'flags\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"textpack\" => \'// Plugin \\\'textpack\\\' is optional. It provides i18n strings to be used in conjunction with gTxt().\'.n\n                            .\'// Syntax:\'.n\n                            .\'// ## arbitrary comment\'.n\n                            .\'// #@event\'.n\n                            .\'// #@language ISO-LANGUAGE-CODE\'.n\n                            .\'// abc_string_name => Localized String\'.n.n\n                            .(($val)\n                                ? \'$plugin[\\\'textpack\\\'] = <<<EOT\'.n\n                                    .$val.n\n                                    .\'EOT;\'.n.n\n                                : \'/** Uncomment me, if you need a textpack\'.n\n                                    .\'$plugin[\\\'textpack\\\'] = <<< EOT\'.n\n                                    .\'#@admin\'.n\n                                    .\'#@language en-gb\'.n\n                                    .\'abc_sample_string => Sample String\'.n\n                                    .\'abc_one_more => One more\'.n\n                                    .\'#@language de-de\'.n\n                                    .\'abc_sample_string => Beispieltext\'.n\n                                    .\'abc_one_more => Noch einer\'.n\n                                    .\'EOT;\'.n\n                                    .\'**/\'.n\n                                    .\'// End of textpack\'.n.n),\n            \"author\" => \'$plugin[\\\'author\\\'] = \'.doQuote(doSlash($val)).\';\'.n,\n            \"author_uri\" => \'$plugin[\\\'author_uri\\\'] = \'.doQuote(doSlash($val)).\';\'.n,\n            \"description\" => \'$plugin[\\\'description\\\'] = \'.doQuote(doSlash($val)).\';\'.n.n,\n            \"load_order\" => \'// Plugin load order:\'.n\n                            .\'// The default value of 5 would fit most plugins, while for instance comment\'.n\n                            .\'// spam evaluators or URL redirectors would probably want to run earlier\'.n\n                            .\'// (1...4) to prepare the environment for everything else that follows.\'.n\n                            .\'// Values 6...9 should be considered for plugins which would work late.\'.n\n                            .\'// This order is user-overrideable.\'.n\n                            .\'$plugin[\\\'order\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"type\" => \'// Plugin \\\'type\\\' defines where the plugin is loaded\'.n\n                            .\'// 0 = public              : only on the public side of the website (default)\'.n\n                            .\'// 1 = public+admin        : on both the public and admin side\'.n\n                            .\'// 2 = library             : only when include_plugin() or require_plugin() is called\'.n\n                            .\'// 3 = admin               : only on the admin side (no AJAX)\'.n\n                            .\'// 4 = admin+ajax          : only on the admin side (AJAX supported)\'.n\n                            .\'// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\'.n\n                            .\'$plugin[\\\'type\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"include\" => \'if (!defined(\\\'txpinterface\\\'))\'.n\n                            .\'        @include_once(\\\'zem_tpl.php\\\');\'.n.n,\n            \"help\" => \'if (0) {\'.n\n                            .\'?>\'.n\n                            . (($css) ? \'<!--\'.n\n                            .\'# --- BEGIN PLUGIN CSS ---\'.n\n                            .$css.n\n                            .\'# --- END PLUGIN CSS ---\'.n\n                            .\'-->\'.n : \'\')\n                            .\'<!--\'.n\n                            .\'# --- BEGIN PLUGIN HELP ---\'.n\n                            .$help.n\n                            .\'# --- END PLUGIN HELP ---\'.n\n                            .\'-->\'.n\n                            .\'<?php\'.n\n                            .\'}\'.n,\n            \"code\" => \'# --- BEGIN PLUGIN CODE ---\'.n\n                            .$val.n\n                            .\'# --- END PLUGIN CODE ---\'.n,\n            \"postamble\" => \'?>\',\n        );\n\n        return (array_key_exists($prt, $template) ? $template[$prt] : \'\');\n    }\n\n    /**\n     * Create / upload / install a new plugin.\n     */\n    public function create()\n    {\n        extract(doSlash(gpsa(array(\'name\', \'plugin_create\', \'plugin_upload\', \'plugin_install\', \'ied_plugin_autoenable\'))));\n\n        if ($plugin_create) {\n            $fname = \'\';\n            if ($name == \'\') {\n                $this->table(array(gTxt(\'ied_plugin_name_first\'), E_ERROR));\n\n\n                return;\n            }\n            if (strpos($name, \".php\") !== false) {\n                $fname = $name;\n                $name = basename($name, \".php\");\n            }\n            $exists = fetch(\'name\', \'txp_plugin\', \'name\', $name);\n\n            // MySQL defaults to set status=1 so we need to explicitly override this if this\n            // is a fresh installation\n            $state = ($ied_plugin_autoenable==1) ? 1 : 0;\n\n\n            if (!$exists) {\n                if ($fname) {\n                    // Put plugin in cache dir by faking a POST submission and saving\n                    $_POST[\'filename\'] = $fname;\n                    $_POST[\'newname\'] = $name;\n                    $_POST[\'status\'] = 1; // Cache_dir plugins are always on so this setting is ignored\n                    $_POST[\'type\'] = 0;\n                    $_POST[\'version\'] = \'0.1\';\n                    $_POST[\'load_order\'] = 5;\n                    $_POST[\'flags\'] = 0;\n                    $this->save();\n                } else {\n                    safe_insert(\"txp_plugin\", \"\n                        name=\'$name\',\n                        status=$state,\n                        type=0,\n                        version=\'0.1\',\n                        load_order=5,\n                        flags=0,\n                        description=\'\',\n                        help=\'\',\n                        code=\'\',\n                        code_restore=\'\'\n                    \");\n                    $this->edit(gTxt(\'ied_plugin_edit_new\'), $name);\n                }\n            } else {\n                $this->table(array(gTxt(\'plugin\').\' <strong>\'.$name.\'</strong> \'.gTxt(\'already_exists\'), E_ERROR));\n            }\n\n        } elseif ($plugin_upload) {\n            list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n\n            if (!$_FILES[\'thefile\'][\'tmp_name\']) {\n                $this->table(array(gTxt(\'ied_plugin_choose_file\'), E_ERROR));\n\n                return;\n            }\n\n            $info = explode (\'.\',$_FILES[\'thefile\'][\'name\']);\n            $lastpart = count($info)-1;\n            $ext = $info[$lastpart];\n\n            if ($ext == \'php\') {\n                $plugin = $this->read_file($_FILES[\'thefile\'][\'tmp_name\']);\n//  $newname = (empty($plugin[\'name\'])) ? basename($_FILES[\'thefile\'][\'name\'], \'.php\') : $plugin[\'name\'];\n                $newname = ($name) ? $name : doSlash($plugin[\'name\']);\n\n                if (empty($plugin[\'code\'])) {\n                    $plugin[\'code\'] = file_get_contents($_FILES[\'thefile\'][\'tmp_name\']);\n                }\n\n                extract(doSlash($plugin));\n\n                $md5 = md5($code);\n                $version = ($version) ? $version : \"0.1\";\n                $help = $help.(($css) ? n.$start_css.n.$css.n.$end_css : \'\');\n                $exists = fetch(\'name\', \'txp_plugin\', \'name\', $newname);\n\n                // MySQL defaults to set status=1 so we need to explicitly override this if this\n                // is a fresh installation\n                $state = ($ied_plugin_autoenable==1) ? \'status=1,\' : ($exists && ($ied_plugin_autoenable==2) ? \'\' : \'status=0,\');\n\n                if ($exists) {\n                    // Note: status omitted so it retains its value\n                    $rs = safe_update(\n                        \"txp_plugin\",\n                        \"$state\n                        type = \".intval($type).\",\n                        author = \'$author\',\n                        author_uri = \'$author_uri\',\n                        version = \'$version\',\n                        description = \'$description\',\n                        help = \'$help\',\n                        code = \'$code\',\n                        code_restore = \'$code\',\n                        code_md5 = \'$md5\',\n                        flags = \".intval($flags).\",\n                        load_order = \".intval($load_order),\n                        \"name = \'$newname\'\"\n                    );\n                } else {\n                    $rs = safe_insert(\n                        \"txp_plugin\",\n                        \"name = \'$newname\',\n                        $state\n                        type = \".intval($type).\",\n                        author = \'$author\',\n                        author_uri = \'$author_uri\',\n                        version = \'$version\',\n                        description = \'$description\',\n                        help = \'$help\',\n                        code = \'$code\',\n                        code_restore = \'$code\',\n                        code_md5 = \'$md5\',\n                        flags = \".intval($flags).\",\n                        load_order = \".intval($load_order)\n                    );\n                }\n\n                $msg = ($exists) ? gTxt(\'ied_plugin_updated\', array(\'{name}\' => $newname)) : gTxt(\'ied_plugin_uploaded\', array(\'{name}\' => $newname));\n                $this->table($msg, $newname);\n\n            } elseif ($ext === \'txt\') {\n                $plugin64 = file_get_contents($_FILES[\'thefile\'][\'tmp_name\']);\n                $ret = $this->install($plugin64);\n\n                if ($ret[\'err\'] == \'\') {\n                    $this->table($ret[\'msg\'], $ret[\'nam\']);\n              } else {\n                    $this->table(array($ret[\'msg\'], $ret[\'err\']));\n                }\n            }\n\n        } elseif ($plugin_install) {\n            $ret = $this->install(ps(\'plugin64\'));\n            if ($ret[\'err\'] == \'\') {\n                $this->table($ret[\'msg\'], $ret[\'nam\']);\n          } else {\n                $this->table(array($ret[\'msg\'], $ret[\'err\']));\n            }\n        }\n    }\n\n    /**\n     * Install plugin from distributable txt file.\n     *\n     * Frankensteined shamelessly from include/txp_plugin.php\n     *\n     * @param  string $plugin The raw base64-encoded/gzipped plugin block\n     */\n    public function install($plugin = \'\')\n    {\n        if (strpos($plugin, \'$plugin=\\\'\') !== false) {\n            @ini_set(\'pcre.backtrack_limit\', \'1000000\');\n            $plugin = preg_replace(\'@.*\\$plugin=\\\'([\\w=+/]+)\\\'.*@s\', \'$1\', $plugin);\n        }\n\n        $plugin = preg_replace(\'/^#.*$/m\', \'\', $plugin);\n\n        if (trim($plugin)) {\n            $plugin = base64_decode($plugin);\n\n            if (strncmp($plugin, \"\\x1F\\x8B\", 2) === 0) {\n                $plugin = gzinflate(substr($plugin, 10));\n            }\n\n            if ($plugin = @unserialize($plugin)) {\n                if (is_array($plugin)) {\n                    extract($plugin);\n\n                    $type  = empty($type)  ? 0 : min(max(intval($type), 0), 5);\n                    $order = empty($order) ? 5 : min(max(intval($order), 1), 9);\n                    $flags = empty($flags) ? 0 : intval($flags);\n\n                    $exists = fetch(\'name\', \'txp_plugin\', \'name\', $name);\n\n                    if (isset($help_raw) && empty($plugin[\'allow_html_help\'])) {\n                        // Default: help is in Textile format.\n                        include_once txpath.\'/lib/classTextile.php\';\n                        $textile = new Textile();\n                        $help = $textile->TextileRestricted($help_raw, 0, 0);\n                    }\n\n                    // MySQL defaults to set status=1 so we need to explicitly override this if this\n                    // is a fresh installation.\n                    $ied_plugin_autoenable = ps(\'ied_plugin_autoenable\');\n                    $state = ($ied_plugin_autoenable==1) ? \'status=1,\' : ($exists && ($ied_plugin_autoenable==2) ? \'\' : \'status=0,\');\n\n                    if ($exists) {\n                        $rs = safe_update(\n                           \"txp_plugin\",\n                            \"$state\n                            type         = $type,\n                            author       = \'\".doSlash($author).\"\',\n                            author_uri   = \'\".doSlash($author_uri).\"\',\n                            version      = \'\".doSlash($version).\"\',\n                            description  = \'\".doSlash($description).\"\',\n                            help         = \'\".doSlash($help).\"\',\n                            code         = \'\".doSlash($code).\"\',\n                            code_restore = \'\".doSlash($code).\"\',\n                            code_md5     = \'\".doSlash($md5).\"\',\n                            flags        = $flags\",\n                            \"name        = \'\".doSlash($name).\"\'\"\n                        );\n\n                    } else {\n\n                        $rs = safe_insert(\n                           \"txp_plugin\",\n                           \"name         = \'\".doSlash($name).\"\',\n                            $state\n                            type         = $type,\n                            author       = \'\".doSlash($author).\"\',\n                            author_uri   = \'\".doSlash($author_uri).\"\',\n                            version      = \'\".doSlash($version).\"\',\n                            description  = \'\".doSlash($description).\"\',\n                            help         = \'\".doSlash($help).\"\',\n                            code         = \'\".doSlash($code).\"\',\n                            code_restore = \'\".doSlash($code).\"\',\n                            code_md5     = \'\".doSlash($md5).\"\',\n                            load_order   = \'\".$order.\"\',\n                            flags        = $flags\"\n                        );\n                    }\n\n                    if ($rs and $code) {\n                        if (!empty($textpack)) {\n                            install_textpack($textpack, false);\n                        }\n\n                        if (ps(\'ied_plugin_installopts\') && ($flags & PLUGIN_LIFECYCLE_NOTIFY) ) {\n                            load_plugin($name, true);\n                            $message = callback_event(\"plugin_lifecycle.$name\", \'installed\');\n                        }\n\n                        if (empty($message)) $message = gTxt(\'plugin_installed\', array(\'{name}\' => $name));\n\n                        return array(\'msg\' => $message, \'err\' => \'\', \'nam\' => $name);\n                    } else {\n                        return array(\'msg\' => gTxt(\'plugin_install_failed\', array(\'{name}\' => $name)), \'err\' => E_ERROR);\n                    }\n                }\n            }\n        }\n\n        return array(\'msg\' => gTxt(\'bad_plugin_code\'), \'err\' => E_ERROR);\n    }\n\n    /**\n     * Store the state of the togglable panes.\n     */\n    public function save_pane_state()\n    {\n        $panes = array(\'ied_plugin_tp_strings\', \'ied_plugin_code\', \'ied_plugin_docs\', \'ied_plugin_meta\', \'ied_plugin_utils\', \'ied_plugin_cpanel\', \'ied_plugin_dbplugs\', \'ied_plugin_cacheplugs\');\n        $pane = gps(\'pane\');\n        if (in_array($pane, $panes)) {\n            set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'ied_plugin\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n            send_xml_response();\n        } else {\n            send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n        }\n    }\n\n    /**\n     * Get plugin (file)name information.\n     *\n     * Returns an array of (file)names;\n     *  1) the standard plugin\n     *  2) the compressed plugin\n     *  3) the PHP template\n     *  4) the textpack\n     */\n    public function get_name($name, $version = \'\', $lang=\'\')\n    {\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $op = get_pref(\'ied_plugin_output_sfile\');\n        $opc = get_pref(\'ied_plugin_output_sfilec\');\n        $opp = get_pref(\'ied_plugin_output_sfilep\');\n        $opt = get_pref(\'ied_plugin_output_sfilet\');\n\n        $out = array(\n            ( (empty($op)) ? $ied_plugin_prefs[\'ied_plugin_output_sfile\'][\'default\'] : $op ),\n            ( (empty($opc)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilec\'][\'default\'] : $opc ),\n            ( (empty($opp)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilep\'][\'default\'] : $opp ),\n            ( (empty($opt)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilet\'][\'default\'] : $opt ),\n        );\n\n        $from = array(\'{name}\', \'{version}\', \'{lang}\');\n        $to = array($name, $version, $lang);\n\n        foreach ($out as $fidx => $fname) {\n            $fname = str_replace($from, $to, $fname);\n            $out[$fidx] = sanitizeForFile($fname);\n        }\n\n        return $out;\n    }\n\n    /**\n     * Parse a plugin in Standard Textpattern Template format.\n     *\n     * @param  string $filepath Full path to the file to be read\n     */\n    public function read_file($filepath)\n    {\n        $content = file($filepath);\n        $justfile = basename($filepath);\n        $parts = explode (\'.\',$justfile);\n        $ext = array_pop($parts);\n        $ext = ($ext==$justfile) ? \'\' : \'.\'.$ext; // Only assign an extension if one exists\n        $source_lines = count($content);\n        $commentblock = false;\n        $in_textpack = false;\n        $metadata = array(\n                            \'name\'        => \'\',\n                            \'version\'     => \'\',\n                            \'revision\'    => \'\',\n                            \'author\'      => \'\',\n                            \'author_uri\'  => \'\',\n                            \'description\' => \'\',\n                            \'order\'       => \'5\',\n                            \'type\'        => \'\',\n                            \'flags\'       => \'\',\n                            \'textpack\'    => \'\',\n                        );\n        for ($idx=0; $idx < $source_lines; $idx++) {\n            $content[$idx] = rtrim($content[$idx]);\n\n            // Bomb out if we reach the end of the definition area\n            if (strpos( $content[$idx], \'if (!defined(\\\'txpinterface\\\')\' ) === 0) {\n                break;\n            }\n            if (strpos($content[$idx], \'/*\') === 0) {\n                $commentblock = true;\n                continue;\n            }\n            if ( (strpos($content[$idx], \'*/\') === 0) || (strpos($content[$idx], \'**/\') === 0) ) {\n                $commentblock = false;\n                continue;\n            }\n\n            if (strpos($content[$idx], \'EOT;\') === 0) {\n                $in_textpack = false;\n                continue;\n            }\n\n            if ($in_textpack) {\n                $metadata[\'textpack\'] .= $content[$idx].n;\n            }\n\n            if (!$commentblock && strpos($content[$idx], \'$plugin[\') === 0) {\n                // Found a plugin variable so extract it\n                $parts = explode(\" = \", $content[$idx]);\n                $parts[0] = str_replace(\"\'\", \"\", $parts[0]); // Make the match easier!\n                $semicolon = strpos($parts[1], \';\');\n\n                if ($semicolon !== false) {\n                    $parts[1] = substr($parts[1], 0, $semicolon); // Ditto\n                }\n\n                preg_match(\"/plugin\\[(.*)\\]/\", $parts[0], $var); // Extract just the variable name\n\n                if (is_numeric($parts[1])) {\n                    $parts[1] = \"\'\".$parts[1].\"\'\";\n                }\n\n                if (strpos($parts[1], \'<<<\') === false) {\n                    preg_match(\"/.*\'(.*)\'.*/\", $parts[1], $val); // Remove anything outside the quotes (e.g. $revision)\n\n                    if (empty($val)) {\n                        // Try unquoted - may be a constant or two.\n                        preg_match(\"/(.*)/\", $parts[1], $val);\n\n                        if (strtoupper($val[1]) == $val[1]) {\n                            // It\'s a constant so get its value\n                            $val[1] = 0;\n                            $constants = do_list($parts[1], \'|\');\n\n                            foreach ($constants as $constant) {\n                                $val[1] |= (defined($constant)) ? constant($constant) : 0;\n                            }\n                        }\n                    }\n                }\n\n                if ($var[1] === \'revision\' && isset($val[1]) && !empty($val[1])) {\n                    $revparts = explode(\' \', trim($val[1], \'$ \'));\n                    $val[1] = $revparts[count($revparts)-1];\n                    $val[1] = (empty($val[1])) ? \'\' : \'.\' .$val[1];\n                }\n\n                if ($var[1] === \'textpack\') {\n                    $in_textpack = true;\n                    continue;\n                }\n                $metadata[$var[1]] = $val[1];\n            }\n        }\n\n        $metadata[\'name\'] = (empty($metadata[\'name\'])) ? basename($justfile, $ext) : $metadata[\'name\'];\n        $metadata[\'load_order\'] = $metadata[\'order\'];\n        $metadata[\'version\'] .= $metadata[\'revision\'];\n        $metadata[\'help\'] = $this->extract_hunk($content, \'HELP\');\n        $metadata[\'css\']  = $this->extract_hunk($content, \'CSS\' );\n        $metadata[\'code\'] = $this->extract_hunk($content, \'CODE\');\n\n        return $metadata;\n    }\n\n    /**\n     * Pull out a chunk of text from the Standard Template Format.\n     *\n     * Ripped and modded from net-carver\'s zem_tpl template.\n     *\n     * @param  [type]  $content [description]\n     * @param  [type]  $hunk    [description]\n     * @param  string  $cmnt    [description]\n     * @param  boolean $delete  [description]\n     * @return [type]           [description]\n     */\n    public function extract_hunk($content, $hunk, $cmnt=\"#\", $delete=false)\n    {\n        $dlm = explode(\"|\", $cmnt);\n        $dlmStart = $dlm[0];\n        $dlmEnd = (isset($dlm[1])) ? $dlm[1] : \'\';\n        $lines = $this->make_array($content);\n\n        list ($start_delim, $end_delim) = $this->make_markers($hunk, $dlmStart, $dlmEnd);\n        $start = array_search($start_delim, $lines) + 1;\n        $end = array_search($end_delim, $lines);\n\n        // Kludge to get round the delimiter change in v0.83\n        if ($hunk == \'CSS\' && $end === false) {\n            $start_delim = str_replace(\'---\', \'***\', $start_delim);\n            $end_delim = str_replace(\'---\', \'***\', $end_delim);\n            $start = array_search($start_delim, $lines) + 1;\n            $end = array_search($end_delim, $lines);\n        }\n\n        $extracted = array();\n        if (count($lines) > 0 && is_numeric($start) && is_numeric($end) && $end > $start) {\n            $extracted = array_slice($lines, $start, $end-$start);\n            $lineNum = count($extracted)-1;\n            while ($lineNum > 0 && trim($extracted[$lineNum--]) == \"\") {\n                array_pop($extracted);\n            }\n            while (count($extracted) > 0 && trim($extracted[0]) == \"\") {\n                array_shift($extracted);\n            }\n            if ($delete) {\n                $chopped = array_splice($lines, $start-1, $end-$start+2, \"\");\n            }\n        }\n        $remains = $lines;\n        // Check if the CSS section has <style> tags around it; add them if not\n        if ($hunk == \"CSS\") {\n            $numrows = count($extracted);\n            if ($numrows > 1) {\n                if (strpos($extracted[$numrows-1], \'</style>\') === false) {\n                    $extracted[] = \'</style>\';\n                }\n                if (strpos($extracted[0], \'<style \') === false) {\n                    array_unshift($extracted, \'<style type=\"text/css\">\');\n                }\n            }\n        }\n        if ($delete) {\n            return array(trim(implode(\"\\n\", $extracted)), trim(implode(\"\\n\", $remains)));\n        } else {\n            return trim(implode(\"\\n\", $extracted));\n        }\n    }\n\n    /**\n     * Create a trimmed array from a multi-line document.\n     *\n     * @param  [type] $arr [description]\n     * @return [type]      [description]\n     */\n    public function make_array($arr)\n    {\n        if (!is_array($arr)) {\n            $arr = explode(\"\\n\", $arr);\n        }\n\n        $source_lines = count($arr);\n\n        for ($idx=0; $idx < $source_lines; $idx++) {\n            $arr[$idx] = rtrim($arr[$idx]);\n        }\n\n        return $arr;\n    }\n\n    /**\n     * Create start and end markers for the given hunk.\n     *\n     * @param  [type] $hunk  [description]\n     * @param  [type] $start [description]\n     * @param  [type] $end   [description]\n     * @return [type]        [description]\n     */\n    public function make_markers($hunk, $start, $end)\n    {\n        $smarker = $start . str_replace(\"SECTION\", $hunk, $this->ied_plugin_globals[\'start\']) . (($end)?\' \'.$end:\'\');\n        $emarker = $start . str_replace(\"SECTION\", $hunk, $this->ied_plugin_globals[\'end\']) . (($end)?\' \'.$end:\'\');\n\n        return (array($smarker, $emarker));\n    }\n\n    /**\n     * Check if the code smells like an admin-side plugin.\n     *\n     * Ripped and modded from net-carver\'s zem_tpl template.\n     *\n     * @param  string $codeblock The source code\n     * @param  string $type      The plugin\'s type\n     * @return bool              true if the code is a public-side plugin\n     *                           false if it seems to be admin-side\n     */\n    public function admin_check($codeblock, $type)\n    {\n        // Short circuit since we\'re only interested in public plugins.\n        if ($type != \'0\' || strpos($codeblock, \'txpinterface\') === false) {\n            return true;\n        }\n\n        // This is several orders of magnitude quicker than a single preg_match\n        // as plugin size increases.\n        $cb = str_replace(array(\'\\t\', \' \'), \'\', $codeblock);\n        $cb = str_replace(\'===\', \'==\', $cb);\n\n        if (strpos($cb, \'txpinterface==\\\"admin\') ||\n            strpos($cb, \"txpinterface==\\\'admin\") ||\n            strpos($cb, \'admin\\\"==txpinterface\') ||\n            strpos($cb, \"admin\\\'==txpinterface\") ||\n            strpos($cb, \'admin\\\"==@txpinterface\') ||\n            strpos($cb, \"admin\\\'==@txpinterface\")) {\n\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Textile the passed help doc and style info.\n     *\n     * Cache the content if the prefs indicate to do so.\n     *\n     * @param  [type] $name      [description]\n     * @param  [type] $help      [description]\n     * @param  [type] $css       [description]\n     * @param  [type] $start_dlm [description]\n     * @param  [type] $end_dlm   [description]\n     * @return [type]            [description]\n     */\n    public function textile($name, $help, $css, $start_dlm, $end_dlm)\n    {\n        $tmpdir = $hlpfile = \'\';\n        $changed = true;\n        $out = array();\n        $iotc = get_pref(\'ied_plugin_output_tmpcache\');\n\n        if (isset($iotc) && !empty($iotc) && is_writable($iotc)) {\n            $tmpdir = $iotc;\n            $cache = build_file_path($tmpdir, \'ied_plugin_composer.cache\');\n            if (!file_exists($cache)) {\n                touch($cache); // Create file if it doesn\'t exist\n            }\n            $plugs = parse_ini_file($cache);\n\n            $old_md5 = (is_array($plugs) && array_key_exists($name, $plugs)) ? $plugs[$name] : \'\';\n            $curr_md5 = md5($help);\n            $hlpfile = build_file_path($tmpdir, $name.\'_help.txtl\');\n            if ($old_md5 == $curr_md5) {\n                $out[0] = file_get_contents($hlpfile);\n                $changed = false;\n            } else {\n                $plugs[$name] = $curr_md5;\n                $fd = fopen($cache, \"w\");\n                foreach ($plugs as $idx => $val) {\n                    fwrite($fd, $idx.\' = \'.$val.n);\n                }\n                fclose($fd);\n            }\n        }\n\n        if (!$out) {\n            @include_once txpath.\'/lib/classTextile.php\';\n            $out[0] = $help;\n            if (class_exists(\'Textile\')) {\n                // Try and be a little more intelligent about applying textile or not\n                $re = \'/h[0-6](\\(.*\\))?\\./\';\n                if (preg_match($re, $help)) {\n                    $textile = new Textile();\n                    $out[0] = $textile->TextileThis($help);\n                }\n            }\n        }\n\n        if ($tmpdir && $changed) {\n            if (!file_exists($hlpfile)) {\n                touch($hlpfile);\n            }\n            $fd = fopen($hlpfile, \"w\");\n            fwrite($fd, $out[0]);\n            fclose($fd);\n        }\n\n        // Replace the triple \'---\' in the delimiters so the help file can validate\n        $start_dlm = str_replace(\'---\', \'***\', $start_dlm);\n        $end_dlm = str_replace(\'---\', \'***\', $end_dlm);\n\n        $out[1] = ($css) ? \"\\n$start_dlm\\n$css\\n$end_dlm\\n\" : \'\';\n\n        return substr(implode(\'\', $out), 0, $this->ied_plugin_globals[\'size_help\']+$this->ied_plugin_globals[\'size_css\']);\n    }\n\n    /**\n     * Inject any necessary javascript on the page for code helpers.\n     */\n    public function insert_editors()\n    {\n        $out = array();\n        $ed = array();\n        $ced = get_pref(\'ied_plugin_editor\');\n        $hed = get_pref(\'ied_plugin_help_editor\');\n        $cop = get_pref(\'ied_plugin_editor_options\');\n        $hop = get_pref(\'ied_plugin_help_editor_options\');\n        $ed[$ced] = get_pref(\'ied_plugin_editor_path\');\n        $ed[$hed] = get_pref(\'ied_plugin_help_editor_path\');\n\n        $cop = $cop ? \',\'.$cop : \'\';\n        $hop = $hop ? \',\'.$hop : \'\';\n\n        foreach ($ed as $editor => $editor_locs) {\n            $jsop = array();\n            $locs = do_list($editor_locs);\n            foreach ($locs as $loc) {\n                if (strpos($loc, \'css:\') !== false) {\n                    $jsop[] = \'<link rel=\"stylesheet\" href=\"\'.substr($loc, 4).\'\">\';\n                } else {\n                    $jsop[] = \'<script type=\"javascript\" src=\"\'.$loc.\'\"></script>\';\n                }\n            }\n\n            $jsop = implode(n, $jsop);\n\n            switch ($editor) {\n                case \"tiny_mce\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\ntinyMCE.init({\n    mode : \"specific_textareas\",\n    editor_selector : \"mceEditor\"\n    {$hop}\n});\n</script>;\nEOJS;\n                break;\n                case \"edit_area\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\n// initialisation\neditAreaLoader.init({\n    id: \"plugin_editor\",\n    syntax: \"php\"\n    {$cop}\n});\n</script>\nEOJS;\n                break;\n                case \"codemirror\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\nmyTextArea = document.getElementById(\"plugin_editor\");\nvar ied_pc_editor = CodeMirror.fromTextArea(myTextArea, {\n    value: myTextArea.value\n    {$cop}\n});\n</script>\nEOJS;\n                break;\n                case \"codepress\":\n                    $out[] = $jsop;\n                break;\n            }\n        }\n\n        return implode(\"\\n\", $out);\n    }\n\n    /**\n     * Show plugin help documentation.\n     *\n     * If raw, will Textile first.\n     *\n     * @param  string $message [description]\n     * @return [type]          [description]\n     */\n    public function help_viewer($message = \'\')\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $help = ($name) ? fetch(\'help\',\'txp_plugin\',\'name\',$name) : \'\';\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $out = \'\';\n        if (empty($help)) {\n            $out = gTxt(\'ied_plugin_help_not_available\');\n        } else {\n            list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n            $out = $this->textile($name, $help, $css, $start_css, $end_css);\n        }\n\n        pagetop(gTxt(\'ied_plugin_view_help\', array(\'{name}\' => $name)), $message);\n        echo n. \'<div id=\"plugin_container\" class=\"txp-container txp-view\">\'.\n            n. \'<div class=\"text-column\">\' . $out . \'</div>\'.\n            n. \'</div>\';\n    }\n\n    /**\n     * Wrap the passed widget with standard markup.\n     *\n     * @param  string $widget The widget to wrap\n     * @param  string $class  The CSS class to apply to the wrapper\n     * @return string         HTML\n     */\n    public function wrap_widget($widget, $class = \'txp-form-field-value\')\n    {\n        return \'<div class=\"\' . $class . \'\">\'.$widget.\'</div>\';\n    }\n\n    /**\n     * Preferences stub with correct signature for lifecycle callback.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     */\n    public function setup($evt = \'\', $stp = \'\')\n    {\n        $this->prefs();\n    }\n\n    /**\n     * Display the setup / prefs panel.\n     *\n     * @param  string $message Any feedback error / info message to disply\n     * @return string          HTML\n     */\n    public function prefs($message = \'\')\n    {\n        require_privs(\'plugin_prefs.\'.$this->ied_pc_event);\n\n        if (ps(\'ied_plugin_pref_save\')) {\n            $this->prefs_update();\n            $message = gTxt(\'preferences_saved\');\n        }\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        pagetop(gTxt(\'ied_plugin_lbl_setup\'), $message);\n\n        $btnSave = fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\');\n\n        echo \'<div class=\"txp-layout\">\'\n            .n. \'<div class=\"txp-layout-2col\">\'\n            .n. \'<h1 class=\"txp-heading\">\' . gTxt(\'ied_plugin_lbl_setup\') . \'</h1>\'\n            .n. \'</div>\'\n            .n. \'<div class=\"txp-layout-2col\">\'\n            .n. sLink($this->ied_pc_event, \'\', gTxt(\'ied_plugin_list\'), \'ied_plugin_link\')\n            .n. \'</div>\'\n            .n. script_js(<<<EOJS\nvar ied_plugin_path_re = new RegExp(\"^.*[/\\\\]\", \"g\")\nfunction ied_plugin_prefswap(selID, selValue)\n{\n    var id = selID+\'_path\';\n    var nuval = ((basename($(\"#\"+id).val()) == selValue) ? $(\"#\"+id).val() : dirname($(\"#\"+id).val())+selValue);\n\n    if ($(\"#\"+selID)[0].selectedIndex == 0) {\n        $(\"#\"+id).attr(\"disabled\", true);\n    } else {\n        $(\"#\"+id).attr(\"disabled\", false);\n        $(\"#\"+id).val(nuval);\n    }\n}\nfunction basename(path, suffix)\n{\n    return path.replace(ied_plugin_path_re, \'\');\n}\nfunction dirname(path)\n{\n    return path.match(ied_plugin_path_re);\n}\njQuery(function () {\n    jQuery(\".ied_plugin_setup select option:selected\").each(function (obj) {\n        var item = jQuery(this);\n        ied_plugin_prefswap(item.parent().attr(\'id\'), item.val());\n    });\n});\nEOJS\n            );\n\n        $out = array();\n        $out[] = n.\'<div class=\"txp-layout-1col\">\';\n        $out[] = \'<form name=\"ied_pc_prefs\" id=\"ied_pc_prefs\" class=\"txp-prefs-group\" action=\"index.php\" method=\"post\">\';\n        $out[] = eInput($this->ied_pc_event).sInput(\'prefs\');\n\n        $last_grp = \'\';\n\n        foreach ($ied_plugin_prefs as $idx => $prefobj) {\n            if ($last_grp != $prefobj[\'group\']) {\n                $out[] = hed(gTxt($prefobj[\'group\']), 2);\n            }\n            $last_grp = $prefobj[\'group\'];\n            $subout = array();\n            $label = \'<div class=\"txp-form-field-label\">\'\n                    .\'<label>\'.gTxt($idx).\'</label>\'\n                    .\'</div>\';\n            $val = get_pref($idx, $prefobj[\'default\'], 1);\n            $vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'txp-form-field\';\n            switch ($prefobj[\'html\']) {\n                case \'text_input\':\n                    $subout[] = $this->wrap_widget(fInput(\'text\', $idx, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $idx));\n                break;\n                case \'textarea\':\n                    $subout[] = $this->wrap_widget(text_area($idx, \'\', \'\', $val, $idx));\n                    $vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'txp-form-field-textarea\';\n                break;\n                case \'yesnoradio\':\n                    $subout[] = $this->wrap_widget(yesnoRadio($idx, $val));\n                break;\n                case \'radioset\':\n                    $subout[] = $this->wrap_widget(radioSet($prefobj[\'content\'], $idx, $val));\n                break;\n                case \'checkboxset\':\n                    $vals = do_list($val);\n                    $lclout = array();\n                    $cb_count = 0;\n\n                    foreach ($prefobj[\'content\'] as $cb => $val) {\n                        $checked = in_array($cb, $vals);\n                        $cbId = txpspecialchars($idx.\'-\'.$cb_count);\n                        $lclout[] = checkbox($idx . \'[]\', $cb, $checked, \'\', $cbId) . n . \'<label for=\"\' . $cbId . \'\">\' . gTxt($val) . \'</label>\';\n                        $cb_count++;\n                    }\n\n                    $subout[] = $this->wrap_widget(implode(n, $lclout));\n                break;\n                case \'selectlist\':\n                    $subout[] = $this->wrap_widget(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]));\n                break;\n                default:\n                    if (is_callable($prefobj[\'html\'])) {\n                        $subout[] = $this->wrap_widget($prefobj[\'html\']($idx, $val));\n                    }\n                break;\n            }\n            $out[] = tag($label . n.implode(n ,$subout), \'div\', ($vis ? \' class=\"\'.$vis.\'\"\' : \'\'));\n        }\n        $out[] = graf(fInput(\'submit\', \'ied_plugin_pref_save\', gTxt(\'save\'), \'publish\'), array(\'class\' => \'txp-save\'));\n        $out[] = tInput();\n        $out[] = \'</form></div></div>\';\n\n        echo implode(n, $out);\n    }\n\n    /**\n     * Save plugin prefs from setup panel.\n     */\n    public function prefs_update()\n    {\n        global $prefs;\n\n        $ied_plugin_prefs = $this->get_prefs();\n        $saved = ps(\'ied_plugin_pref_save\');\n\n        // Loop through each plugin setting and make sure it\'s in the prefs table, as follows:\n        // 1) if the value has been POSTed from the prefs panel, set the passed value\n        // 2) if the value exists in the prefs (i.e. run on upgrade), set the existing value\n        // 3) if neither exist, set the plugin default value\n        foreach ($ied_plugin_prefs as $key => $prefobj) {\n            $val = ($saved || isset($_POST[$key])) ? ps($key) : ((isset($prefs[$key])) ? $prefs[$key] : $prefobj[\'default\']);\n            $val = (is_array($val)) ? implode(\', \', $val) : $val;\n            set_pref($key, $val, \'ied_plugin\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\n        }\n    }\n\n    /**\n     * Delete plugin prefs.\n     *\n     * @param  integer $showpane Whether to display the panel afterwards\n     */\n    public function prefs_remove($showpane = 1)\n    {\n        safe_delete(\'txp_prefs\', \"name like \'ied_plugin_%\'\");\n\n        if ($showpane) {\n            $message = gTxt(\'ied_plugin_prefs_deleted\');\n            $this->prefs($message);\n        }\n    }\n\n    /**\n     * Generate list of available languages.\n     *\n     * @param  string $flavour If \'installed\' then show only those available, else \'all\'\n     * @return array\n     */\n    public static function lang_list($flavour = \'installed\')\n    {\n        $ied_langs = array();\n\n        if ($flavour === \'installed\') {\n            // Self-join to get all the installed langs and language strings in one step.\n//            $installed_langs = safe_query(\'select t1.lang, t2.data from \'.PFX.\'txp_lang as t1, \'.PFX.\'txp_lang as t2 WHERE t1.lang = t2.name GROUP BY lang\');\n            $ied_langs = safe_column(\'lang\', \'txp_lang\', \'1=1 GROUP BY lang\');\n        } else {\n            // Grab all available langs from the RPC server.\n            require_once txpath.\'/lib/IXRClass.php\';\n\n            $client = new IXR_Client(RPC_SERVER);\n\n            // Get items from RPC.\n            @set_time_limit(5);\n\n            if ($client->query(\'tups.listLanguages\', get_pref(\'blog_uid\'))) {\n                $response = $client->getResponse();\n\n                foreach ($response as $language) {\n                    $ied_langs[] = $language[\'language\'];\n                }\n            }\n        }\n\n        // Build the select list array.\n        $langlist = array();\n\n        foreach ($ied_langs as $ied_lang) {\n            $langlist[$ied_lang] = gTxt($ied_lang);\n        }\n\n        return $langlist;\n    }\n\n    /**\n     * Build textpack from strings in the database.\n     *\n     * @param  string  $name      Plugin name\n     * @param  integer $force_all Whether to build for the current langue (0) or all installed languages (1)\n     * @return string             The Textpack\n     */\n    public function textpack_build($name, $force_all = 0)\n    {\n        $fetch_lang = null;\n\n        if ($force_all === 0) {\n            $fetch_lang = gps(\'lang\');\n        }\n\n        if (!$fetch_lang) {\n            $fetch_lang = ($force_all === 1)\n                ? implode(\',\', array_keys(self::lang_list(\'installed\')))\n                : get_pref(\'ied_plugin_lang_selected\', \'\');\n        }\n\n        $tpout = array();\n\n        if ($fetch_lang) {\n            $chosen_lang = get_pref(\'ied_plugin_lang_default\', \'\');\n\n            // Guard against situations when the chosen default lang is \'any\'.\n            $dflt_lang = ($chosen_lang === \'\') ? get_pref(\'language\') : $chosen_lang;\n\n            $tp_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\', 1));\n            $tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : \'\';\n            $tp_rows = $this->textpack_grab($fetch_lang, $tp_pfx);\n\n            if ($tp_rows) {\n                $ctr = 0;\n                $prevlang = \'\';\n\n                // Go through all the languages and put the default language at the start of the array.\n                foreach ($tp_rows as $row) {\n                    // Add the event marker\n                    $theEvent = in_array($row[\'event\'], array(\'public\', \'common\')) ? $row[\'event\'] : $tp_pfx;\n\n                    if ($prevlang != $row[\'lang\']) {\n                        $ctr++;\n                    }\n\n                    $idx = ($row[\'lang\'] == $dflt_lang) ? 0 : $ctr;\n                    $tplang[$idx][$theEvent][$row[\'lang\']][$row[\'name\']] = $row[\'data\'];\n                    $prevlang = $row[\'lang\'];\n                    $prevevent = $row[\'event\'];\n                }\n\n                // Make sure default language is actually first.\n                ksort($tplang);\n\n                // Build the final textpack array with language markers.\n                // Note the marker for the default language may (should!) be omitted if the author wants\n                // the strings to be installed regardless of language on destination server.\n                // If a specific language is set and the user does not have that language\n                // installed, the strings would not be inserted.\n                $prevevent = \'\';\n\n                foreach ($tplang as $idx => $langblock) {\n                    foreach ($langblock as $ev => $codeblock) {\n                        $tpheader = array();\n                        $tpstrings = array();\n\n                        if ($prevevent != $ev) {\n                            $tpheader[] = \'#@\' . $ev;\n                        }\n\n                        foreach ($codeblock as $code => $data) {\n                            if (($idx == 0 && $chosen_lang) || ($idx > 0)) {\n                                $tpheader[] = \'#@language \' . $code;\n                            }\n\n                            foreach ($data as $key => $val) {\n                                // Don\'t output empty strings.\n                                if ($val) {\n                                    $tpstrings[] = $key . \' => \' . $val;\n                                }\n                            }\n\n                            if ($tpstrings) {\n                                $tpout = array_merge($tpout, $tpheader, $tpstrings);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        return implode(n, $tpout);\n    }\n\n    /**\n     * Read textpack strings with the given prefix from the database.\n     *\n     * @param  string $lang   Language of strings to fetch\n     * @param  string $prefix Prefix to find\n     * @return array          Record set of matching strings\n     */\n    public function textpack_grab($lang, $prefix)\n    {\n        if ($lang === \'IED_ALL\') {\n            $lang_query = \'\';\n        } else {\n            $lang = (empty($lang)) ? get_pref(\'language\', \'en-gb\') : $lang;\n            $langs = quote_list(do_list($lang));\n            $lang_query = \"lang IN (\".implode(\', \', $langs).\") AND \";\n        }\n\n        return ($prefix) ? safe_rows(\'name, data, lang, event\', \'txp_lang\', $lang_query.\"name LIKE \'\".doSlash($prefix).\"%\' ORDER BY event,lang,name\") : array();\n    }\n\n    /**\n     * Set the current Textpack language from which to display strings in the interface.\n     *\n     * Requires POST variable:\n     *  param  string ied_tp_langsel  The language name to set\n     */\n    public function lang_set()\n    {\n        $sel = doSlash(gps(\'ied_tp_langsel\'));\n        set_pref(\'ied_plugin_lang_selected\', $sel, \'ied_plugin\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n    }\n\n    /**\n     * Store the plugin textpack prefix.\n     *\n     * @param string $plugname Plugin name to store (if omitted, tries GET/POST for \'plugin\')\n     * @param string $pfx      Prefix to store (if omitted, tries GET/POST for \'prefix\')\n     */\n    public function set_tp_prefix($plugname = \'\', $pfx = \'\')\n    {\n        $plugname = ($plugname) ? $plugname : gps(\'plugin\');\n        $pfx = ($pfx) ? $pfx : gps(\'prefix\');\n\n        if ($pfx) {\n            $curr_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\'));\n            $curr_pfx[$plugname] = $pfx;\n            set_pref(\'ied_plugin_tp_prefix\', serialize($curr_pfx), \'ied_plugin\', PREF_HIDDEN, \'text_input\');\n        }\n    }\n\n    /**\n     * AJAX: Return a Textpack string from a (type 4 or 5) plugin gTxt() function/method.\n     *\n     * Given a function name (usually \'abc_plugin_gTxt\') it calls the function\n     * to fetch the given string from the plugin. By calling this repeatedly\n     * or each string, an entire Textpack can be built from strings that were\n     * previously hard-coded in old plugins.\n     *\n     * Note the destination plugin MUST be of type 4 or 5 in order for the\n     * function to be callable so you may have to (temporarily) alter the\n     * plugin type before using this feature.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_fn  The function to call\n     *  param  string ied_tp_lbl The language label (key)\n     * @return array             JSON structure containing the string\'s value\n     * @todo sanitize $fn\n     */\n    public function textpack_load()\n    {\n        $fn = doSlash(gps(\'ied_tp_fn\'));\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $ret = \'\';\n\n        $fnparts = do_list($fn, \'::\');\n        if (count($fnparts) == 2) {\n            // Callable class -> method\n            $fobj = array($fnparts[0], $fnparts[1]);\n        } else {\n            $fobj = $fnparts[0];\n        }\n        if (is_callable($fobj)) {\n            $ret = call_user_func_array($fobj, array($lbl));\n        }\n\n        if ($ret) {\n            echo json_encode(array(\'ied_plugin_tp_string\' => $ret));\n        }\n    }\n\n    /**\n     * AJAX: Delete a Textpack string from the database.\n     *\n     * Requires POST variable:\n     *  param  string ied_tp_lbl The language label (key) to delete\n     */\n    public function textpack_del()\n    {\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n\n        $ret = safe_delete(\'txp_lang\', \"name=\'$lbl\'\");\n    }\n\n    /**\n     * AJAX: save a Textpack string to the database.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_lbl The language label (key)\n     *  param  string ied_tp_str The data value to store\n     *  param  string ied_tp_lng Desired language of string\n     *  param  string ied_tp_evt Event (group) that the string belongs in\n     */\n    public function textpack_save()\n    {\n        global $DB;\n\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $str = doSlash(gps(\'ied_tp_str\'));\n        $lng = doSlash(gps(\'ied_tp_lng\'));\n        $evt = doSlash(gps(\'ied_tp_evt\'));\n        $own = doSlash(gps(\'ied_tp_own\'));\n\n        $where = \"name=\'$lbl\' AND lang=\'$lng\'\";\n        $ret = safe_update(\'txp_lang\', \"data=\'$str\', event=\'$evt\', owner=\'$own\'\", $where);\n\n        if ($ret && (mysqli_affected_rows($DB->link) || safe_count(\'txp_lang\', $where))) {\n            // Update OK: do nothing else.\n        } else {\n            $ret = safe_insert(\'txp_lang\', \"name=\'$lbl\', lang=\'$lng\', event=\'$evt\', owner=\'$own\', data=\'$str\'\");\n        }\n    }\n\n    /**\n     * AJAX: Fetch a Textpack string from the database.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_lbl  The language label (key)\n     *  param  string ied_tp_lng  Language of string to fetch\n     *  param  string ied_tp_dflt Default language to fetch string if main lang missing\n     * @return array              JSON response\n     */\n    public function textpack_get()\n    {\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $lng = doSlash(gps(\'ied_tp_lng\'));\n        $dflt = doSlash(gps(\'ied_tp_dflt\'));\n\n        $rs = safe_rows(\'lang, data\', \'txp_lang\', \"name=\'$lbl\' AND (lang=\'$lng\' OR lang=\'$dflt\')\");\n        $out = array();\n\n        foreach ($rs as $row) {\n            if (($row[\'lang\'] == $dflt) && ($lng != $dflt)) {\n                $out[\'ied_plugin_tp_dflt\'] = $row[\'data\'];\n            } else {\n                $out[\'ied_plugin_tp_string\'] = $row[\'data\'];\n            }\n        }\n\n        echo json_encode($out);\n    }\n\n    /**\n     * AJAX: Save just the plugin code, optionally checking syntax.\n     *\n     * Requires POST variables:\n     *  param  string plugin    Name of plugin to save against\n     *  param  string codeblock PHP code\n     * @return array            XML response\n     */\n    public function code_save()\n    {\n        global $theme;\n\n        $syntax_check = get_pref(\'ied_plugin_syntax_check\');\n\n        $plug = doSlash(ps(\'plugin\'));\n        $code = ps(\'codeblock\');\n\n        $ret = ($syntax_check) ? $this->check_syntax_err($code) : false;\n        $msg = \'\';\n\n        if ($ret === false) {\n            $ret = @safe_update(\'txp_plugin\', \"code=\'\".doSlash($code).\"\'\", \"name=\'$plug\'\");\n            if ($ret) {\n                $msg = $theme->announce_async(gTxt(\'ied_plugin_code_saved\'));\n            } else {\n                $msg = $theme->announce_async(array(gTxt(\'ied_plugin_code_saved_fail\'), E_ERROR));\n            }\n            send_xml_response(array(\'ied_plugin_msg\' => $msg));\n        } else {\n            $msg = $theme->announce_async(array(htmlentities($ret[0], ENT_QUOTES), E_ERROR));\n            send_xml_response(array(\'http-status\' => \'412 Precondition Failed\', \'ied_plugin_msg\' => $msg, \'ied_plugin_err_line\' => $ret[1]));\n        }\n    }\n\n    /**\n     * AJAX: Save just the plugin metadata.\n     *\n     * Requires POST variables:\n     *  param  string plugin Name of plugin to save against\n     *  param  array  data   Name-value pairs to store\n     * @return array         XML response\n     */\n    public function meta_save() {\n        global $theme;\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $plug = doSlash(ps(\'plugin\'));\n        $data = ps(\'data\');\n        $msg = \'\';\n        $meta = array();\n\n        // Defaults required because HTML forms don\'t post unchecked items.\n        $set = array(\n            \'flags\'  => \"flags=\'0\'\",\n            \'status\' => \"status=\'0\'\",\n            );\n\n        foreach ($data as $key => $value) {\n            switch ($key) {\n                case \'description\':\n                case \'version\':\n                case \'author\':\n                case \'author_uri\':\n                case \'type\':\n                case \'load_order\':\n                    $set[$key] = $key . \"=\'\".doSlash($value).\"\'\";\n                    break;\n                case \'newname\':\n                    $val = doSlash($value);\n                    $set[\'name\'] = \"name=\'\".$val.\"\'\";\n                    $meta[\'name\'] = $val;\n                    break;\n                case \'status\':\n                case \'flags\':\n                    $part = array();\n\n                    foreach ($value as $item) {\n                        if (!isset($part[$key])) {\n                            $part[$key] = \'\';\n                        }\n                        $part[$key] += $item;\n                    }\n\n                    foreach ($part as $idx => $val) {\n                        $set[$idx] = $idx . \"=\'\" . doSlash($val).\"\'\";\n                    }\n\n                    break;\n            }\n        }\n\n        $update = implode(\',\', $set);\n        $ret = safe_update(\'txp_plugin\', $update, \"name=\'$plug\'\");\n\n        if ($ret) {\n            $msg = $theme->announce_async(gTxt(\'ied_plugin_meta_saved\'));\n        } else {\n            $msg = $theme->announce_async(array(gTxt(\'ied_plugin_meta_saved_fail\'), E_ERROR));\n        }\n\n        send_xml_response(array(\'ied_plugin_msg\' => $msg));\n    }\n\n    /**\n     * Check the syntax of some PHP code.\n     *\n     * Mostly from a comment in http://php.net/manual/en/function.php-check-syntax.php\n     *\n     * @param  string $code  PHP code to check.\n     * @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.\n     */\n    public function check_syntax_err($code)\n    {\n        if (!defined(\'CR\')) define(\'CR\',chr(13));\n        if (!defined(\'LF\')) define(\'LF\',chr(10));\n\n        $braces=0;\n        $inString=0;\n\n        foreach (token_get_all(\'<?php \' . $code) as $token) {\n            if (is_array($token)) {\n                switch ($token[0]) {\n                    case T_CURLY_OPEN:\n                    case T_DOLLAR_OPEN_CURLY_BRACES:\n                    case T_START_HEREDOC: ++$inString; break;\n                    case T_END_HEREDOC:   --$inString; break;\n                }\n            } elseif ($inString & 1) {\n                switch ($token) {\n                    case \'`\': case \'\\\'\':\n                    case \'\"\': --$inString; break;\n                }\n            } else {\n                switch ($token) {\n                    case \'`\': case \'\\\'\':\n                    case \'\"\': ++$inString; break;\n                    case \'{\': ++$braces; break;\n                    case \'}\':\n                        if ($inString) {\n                            --$inString;\n                        } else {\n                            --$braces;\n                            if ($braces < 0) break 2;\n                        }\n                        break;\n                }\n            }\n        }\n        $inString = @ini_set(\'log_errors\', false);\n        $token = @ini_set(\'display_errors\', true);\n        ob_start();\n        $braces || $code = \"if (0) {{$code}\\n}\";\n        if (eval($code) === false) {\n            if ($braces) {\n                $braces = PHP_INT_MAX;\n            } else {\n                false !== strpos($code,CR) && $code = str_replace(CR,LF,str_replace(CRLF,LF,$code));\n                $braces = substr_count($code,LF);\n            }\n            $code = ob_get_clean();\n            $code = strip_tags($code);\n            if (preg_match(\"\'syntax error, (.+) in .+ on line (\\d+)$\'s\", $code, $code)) {\n                $code[2] = (int) $code[2];\n                $code = $code[2] <= $braces\n                    ? array($code[1], $code[2])\n                    : array(\'unexpected $end\' . substr($code[1], 14), $braces);\n            } else $code = array(\'syntax error\', 0);\n        } else {\n            ob_end_clean();\n            $code = false;\n        }\n        @ini_set(\'display_errors\', $token);\n        @ini_set(\'log_errors\', $inString);\n\n        return $code;\n    }\n\n    // Reflection utility for phpdoc generation\n    public function reflunction_factory($callback)\n    {\n        if (is_array($callback)) {\n            // must be a class method\n            list($class, $method) = $callback;\n\n\n            return new ReflectionMethod($class, $method);\n        }\n\n        // class::method syntax\n        if (is_string($callback) && strpos($callback, \"::\") !== false) {\n            list($class, $method) = explode(\"::\", $callback);\n\n            return new ReflectionMethod($class, $method);\n        }\n\n        // objects as functions (PHP 5.3+)\n        if (version_compare(PHP_VERSION, \"5.3.0\", \">=\") && method_exists($callback, \"__invoke\")) {\n            return new ReflectionMethod($callback, \"__invoke\");\n        }\n\n        // assume it\'s a function\n        return new ReflectionFunction($callback);\n    }\n\n    public function generate_phpdoc()\n    {\n        $plug = gps(\'plugin\');\n        $obj = gps(\'fn\');\n        $ret = load_plugin($plug);\n        if ($ret) {\n            try {\n                $obj = $this->reflunction_factory($obj);\n                $name = $obj->getName();\n                $doc = $obj->getDocComment();\n\n                if ($doc) {\n                    //TODO: make list of allowed docblock tags a pref\n                    //TODO: parse existing doc block and append any new vars/params\n                    preg_match_all(\'/\\s*\\*\\s*\\@(abstract|access|author|category|copyright|deprecated|example|final|filesource|global|ignore|internal|license|link|method|name|package|param|property|return|see|since|static|staticvar|subpackage|todo|tutorial|uses|var|version)\\s+(\\w+)\\s+(\\$\\w+(?::\\w+|->\\w+)*)\\s+(\\[(\\&|\\+|abstract|final|private|protected|public|static)\\])*\\s+(.*)/\', $doc, $matches, PREG_SET_ORDER);\n\n                    $tags = array();\n                    // Reshuffle the matches to index on [tag][var]\n                    foreach ($matches as $idx => $data) {\n                        $ful = isset($data[0]) ? $data[0] : \'\';\n                        $tag = isset($data[1]) ? $data[1] : \'\';\n                        $typ = isset($data[2]) ? $data[2] : \'\';\n                        $var = isset($data[3]) ? $data[3] : \'\';\n                        $mod = isset($data[4]) ? $data[4] : \'\';\n                        $tags[$tag][$var] = array(\n                            \'type\' => $typ,\n                            \'full\' => $ful,\n                            \'mod\'  => $mod,\n                        );\n                    }\n                }\n\n                $fntype = array(\'meta\' => array(), \'type\' => array());\n\n                if (method_exists($obj, \'isConstructor\')) {\n                    // Dealing with a class method\n                    if ($obj->isConstructor()) {\n                        $fntype[\'meta\'][] = \' [c]\';\n                    }\n                    if ($obj->isPublic()) {\n                        $fntype[\'mod\'][] = \'[public]\';\n                    }\n                    if ($obj->isPrivate()) {\n                        $fntype[\'mod\'][] = \'[private]\';\n                    }\n                    if ($obj->isProtected()) {\n                        $fntype[\'mod\'][] = \'[protected]\';\n                    }\n                    if ($obj->isAbstract()) {\n                        $fntype[\'mod\'][] = \'[abstract]\';\n                    }\n                    if ($obj->isFinal()) {\n                        $fntype[\'mod\'][] = \'[final]\';\n                    }\n                    if ($obj->isStatic()) {\n                        $fntype[\'mod\'][] = \'[static]\';\n                    }\n                }\n\n                $params = $obj->getParameters();\n                $param_list = array();\n                if ($params != null) {\n                    foreach ($params as $param) {\n                        $has_dflt = false;\n                        $type = \'string\';\n\n                        // Crude type checker\n                        if ($param->isDefaultValueAvailable()) {\n                            $has_dflt = true;\n                            $dflt = $param->getDefaultValue();\n\n                            if (is_bool($dflt)) {\n                                $type = \'boolean\';\n                                $dflt = $dflt ? \'true\' : \'false\';\n                            } elseif (is_null($dflt)) {\n                                $dflt = \'NULL\';\n                            } elseif ($dflt == (string) (int) $dflt) {\n                                $type = \'integer\';\n                            } elseif ($dflt == (string) (float) $dflt) {\n                                $type = \'float\';\n                            } elseif (is_numeric($dflt)) {\n                                $type = \'number\';\n                            } elseif (is_array($dflt)) {\n                                $type = \'array\';\n                            }\n\n                            if ( ($type == \'string\' || $type == \'array\') && empty($dflt) ) {\n                                $dflt = \'empty\';\n                            } elseif ($type == \'string\' && $dflt != \'NULL\') {\n                                $dflt = \'\"\'.$dflt.\'\"\';\n                            }\n                        }\n\n                        $item = \'@param\'.t.$type.t.\'$\'.$param->getName();\n                        if ($param->isPassedByReference()) {\n                            $item .= \' [&]\';\n                        }\n                        if ($param->allowsNull()) {\n                            $item .= \' [+]\';\n                        }\n                        $item .= ($has_dflt) ? t.\'(Default: \' .$dflt. \')\' : t;\n                        $item .= t.\'Param description\';\n                        $param_list[] = $item;\n                    }\n                }\n\n                $final = \'<pre>\';\n                $final .= \"/**\".br;\n                $final .= \" * $name\" . ((!empty($fntype[\'meta\'])) ? $fntype[\'meta\'] : \'\') .t. (($fnType[\'mod\']) ? implode(\' \', $fnType[\'mod\']).\' \' : \'\') . \"Description\".br;\n                $final .= \" *\".br;\n                $final .= \" * Summary goes here\".br;\n                $final .= \" *\".br;\n                foreach ($param_list as $item) {\n                    $final .= \" * \". $item.br;\n                }\n                $final .= \" */</pre>\".br;\n\n                send_xml_response(array(\'ied_plugin_phpdoc\' => $final));\n            } catch (Exception $ex) {\n                send_xml_response( array( \'http-status\' => \'501 Not Implemented\', \'error_msg\' => $ex->getMessage() . gTxt(\'ied_plugin_fn_not_exist\') ) );\n            }\n        } else {\n            send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n        }\n\n    /*  $classInterfaces=$reflection->getInterfaces();\n        //get information about the interfaces\n        if ($classInterfaces != null) {\n        fwrite($hf,\"\\n</i>\\t</td></tr>\\n\\t<tr>\".\n        \"<td align=\\\"center\\\" colspan=\\\"0\\\">\".\n        \"<font face=\\\"arial\\\" size=\\\"2\\\"\".\n        \" color=\\\"purple\\\">Implemented\".\n        \" interfaces:</td><td align=\\\"center\\\"\".\n        \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n        \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n        \"</font></td><td align=\\\"center\\\"\".\n        \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n        \" size=\\\"2\\\" color=\\\"black\\\"><b>\".\n        \"Description</b></font></td></tr>\\n\");\n         foreach ($classInterfaces as $in) {\n            fwrite($hf,\"\\t<tr><td></td><td align=\".\n            \"\\\"center\\\">\");\n            fwrite($hf,$in->getName());\n            fwrite($hf,\"</td><td align=\\\"center\\\"\".\n            \" colspan=\\\"3\\\"><i>\\n\");\n            fwrite($hf,$in->getDocComment());\n            fwrite($hf,\"</i></td></tr>\\n\");\n            }\n         }\n\n        //get the superclass information\n        $superclass=$reflection->getParentClass();\n        if ($superclass != null) {\n            fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n             \"colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n             \" size=\\\"2\\\" color=\\\"purple\\\">\".\n             \"The superclass is:</td><td\".\n             \" align=\\\"center\\\" colspan=\\\"4\\\">\".\n             \"<font face=\\\"arial\\\" size=\\\"2\\\"\".\n             \" color=\\\"black\\\"><b>\\n\");\n            fwrite($hf,$superclass->getName());\n            fwrite($hf,\"</font></td></tr>\\n\");\n            }\n\n        //get information about the constants\n        $constants=$reflection->getConstants();\n        if ($constants != null) {\n          $constantsNumber=count($constants);\n          fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"purple\\\">\".\n          \"Constants:</td><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n          \"</font></td><td align=\\\"center\\\"\".\n          \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Value</b>\".\n          \"</font></td></tr>\\n\");\n          foreach ($constants as $keys=>$value) {\n            fwrite($hf,\"\\t<tr><td></td><td\".\n            \" align=\\\"center\\\">\");\n            fwrite($hf,$keys);\n            fwrite($hf,\"</td><td align=\\\"center\\\"\".\n            \" colspan=\\\"3\\\">\");\n            fwrite($hf,$value);\n            fwrite($hf,\"</td></tr>\\n\");\n            }\n          }\n\n        //get information about properties\n        $properties=$reflection->getProperties();\n        if ($properties != null) {\n          fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"purple\\\">\".\n          \"Properties:</td><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n          \"</font></td><td align=\\\"center\\\"\".\n          \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>\".\n          \"Modifiers</b></font></td></tr>\\n\");\n          foreach ($properties as $in) {\n             fwrite($hf,\"\\t<tr><td></td><td\".\n             \" align=\\\"center\\\" colspan=\\\"0\\\">\");\n             fwrite($hf,$in->getName());\n             fwrite($hf,\"</td><td align=\\\"center\\\"\".\n             \" colspan=\\\"3\\\">\");\n\n             if ($in->isPublic()) { fwrite($hf,\"[public]\"); }\n             if ($in->isPrivate()) { fwrite($hf,\"[private]\"); }\n             if ($in->isProtected()) { fwrite($hf,\"[protected]\"); }\n             if ($in->isStatic()) { fwrite($hf,\"[static]\"); }\n             fwrite($hf,\"</td></tr>\\n\");\n             }\n          }\n    */\n\n    }\n\n    // ------------------------\n    // Settings for the plugin\n    public function get_prefs()\n    {\n        $tempdir = get_pref(\'tempdir\');\n\n        $ied_pc_prefs = array(\n            \'ied_plugin_editor\' => array(\n                \'html\'     => \'ied_plugin_code_editors\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 10,\n                \'default\'  => \'none\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_path\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 20,\n                \'default\'  => hu.\'js/\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_options\' => array(\n                \'html\'     => \'textarea\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 30,\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_width\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 40,\n                \'default\'  => \'90%\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor\' => array(\n                \'html\'     => \'ied_plugin_help_editors\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 50,\n                \'default\'  => \'textile\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor_path\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 60,\n                \'default\'  => hu.\'js/\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor_options\' => array(\n                \'html\'     => \'textarea\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 70,\n                \'default\'  => \'theme_advanced_toolbar_location : \"top\",\n    theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n    theme_advanced_buttons2 : \"link,unlink,separator,ibrowser,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n    theme_advanced_buttons3 : \"\"\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_interface_elems\' => array(\n                \'html\'     => \'checkboxset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 80,\n                \'content\'  => array(\'distribution\' => \'ied_plugin_if_el_dist\', \'style\' => \'ied_plugin_if_el_style\'),\n                \'default\'  => \'style\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_lifecycle_options\' => array(\n                \'html\'     => \'checkboxset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 90,\n                \'content\'  => array(\'installed\' => \'ied_plugin_lbl_lc_install\', \'enabled\' => \'ied_plugin_lbl_lc_enable\', \'disabled\' => \'ied_plugin_lbl_lc_disable\', \'deleted\' => \'ied_plugin_lbl_lc_delete\'),\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_auto_enable\' => array(\n                \'html\'     => \'radioset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 100,\n                \'content\'  => array(\'0\' => gTxt(\'no\'), \'1\' => gTxt(\'yes\'), \'2\' => gTxt(\'ied_plugin_same\')),\n                \'default\'  => \'2\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_syntax_check\' => array(\n                \'html\'     => \'yesnoradio\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 110,\n                \'default\'  => \'1\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_lang_choose\' => array(\n                \'html\'     => \'ied_plugin_lang_options\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 120,\n                \'default\'  => \'installed\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_lang_default\' => array(\n                \'html\'     => \'ied_plugin_lang_default\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 130,\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_order\' => array(\n                \'html\'     => \'radioset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 140,\n                \'content\'  => array(\'0\' => gTxt(\'ied_plugin_lbl_op_code_first\'), \'1\' => gTxt(\'ied_plugin_lbl_op_help_first\')),\n                \'default\'  => \'0\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfile\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 150,\n                \'default\'  => \'{name}_v{version}.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilec\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 160,\n                \'default\'  => \'{name}_v{version}_zip.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilep\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 170,\n                \'default\'  => \'{name}_v{version}.php\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilet\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 180,\n                \'default\'  => \'{name}_v{version}_{lang}_textpack.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_tmpcache\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 190,\n                \'default\'  => $tempdir,\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n        );\n\n        return $ied_pc_prefs;\n    }\n}\n\n/**\n * Add prefs callbacks to global scope, since create_pref() can\'t take\n * array/object syntax.\n *\n * @see  http://forum.textpattern.com/viewtopic.php?pid=298188#p298188\n */\nif (txpinterface === \'admin\') {\n    /**\n     * List of supported javascript syntax highlighter / code editors.\n     *\n     * NB: no i18n since these are the names of the projects.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_code_editors($name, $val = \'\')\n    {\n        $eds[\'none\'] = gTxt(\'none\');\n        $eds[\'edit_area\'] = \'EditArea\';\n        $eds[\'codemirror\'] = \'CodeMirror\';\n        $eds[\'codepress\'] = \'CodePress\';\n\n        return selectInput($name, $eds, $val, false);\n    }\n\n    /**\n     * List of supported javascript help editors.\n     *\n     * NB: no i18n since these are the names of the projects.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_help_editors($name, $val = \'\')\n    {\n        $eds[\'textilee\'] = \'Textile\';\n        $eds[\'tiny_mce\'] = \'TinyMCE\';\n\n        return selectInput($name, $eds, $val, false);\n    }\n\n    /**\n     * List of language options.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_lang_options($name, $val = \'\')\n    {\n        $lngs[\'installed\'] = gTxt(\'ied_plugin_langs_installed\');\n        $lngs[\'all\'] = gTxt(\'ied_plugin_langs_all\');\n\n        return selectInput($name, $lngs, $val, false);\n    }\n\n    /**\n     * Complete list of supported languages.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_lang_default($name, $val = \'\')\n    {\n        $langs = array_merge(array(\'\' => gTxt(\'ied_plugin_any\')), ied_pc::lang_list(\'all\'));\n\n        return selectInput($name, $langs, $val, false);\n    }\n}', '// <?php\n/**\n * ied_plugin_composer\n *\n * A Textpattern CMS plugin for writing, editing and sharing plugins\n *  -> Create and edit admin-side or public plugins\n *  -> Supports plugin lifecycle events and prefs\n *  -> Supports Textpacks\n *  -> Optional syntax checker on save\n *\n * @author Yura Linnyk\n * @author Stef Dawson\n * @author Steve Dickinson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * Use href() (from 4.6.+) for anchor creation to avoid double-encoded ampersands\n//  * Figure out why syntax checker doesn\'t jump to line number sometimes (AJAX fails with error but it\'s not handled)\n//  * Show which langs have installed strings in the distribution section so the correct langs in the select list can be chosen\n//  * Find out why uploading PHP files sometiems throws an error even though it succeeds\n//  * jQuery on editor dropdowns in setup\n//  * phpdoc\n\nif (txpinterface === \'admin\') {\n    new ied_pc();\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'ied_plugin_list\')\n            ->register(\'ied_plugin_info\')\n            ->register(\'ied_plugin_textpacks\')\n            ->register(\'ied_plugin_download\')\n            ->register(\'ied_plugin_download_link\');\n    }\n\n    register_callback(\'ied_plugin_download\', \'pretext\');\n\n    /*\n     * Public tag: List plugins, filtered by name or prefix.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_list($atts = array(), $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'from\'       => \'database\', // database, cache (or both)\n            \'name\'       => \'\', // List of plugin names to return\n            \'prefix\'     => \'\', // Plugin prefixes\n            \'exclude\'    => \'\', // names to exclude from the list\n            \'type\'       => \'\', // 0-5 or comma-separated combos thereof\n            \'wraptag\'    => \'\',\n            \'class\'      => \'\',\n            \'break\'      => \'\',\n            \'breakclass\' => \'\',\n            \'html_id\'    => \'\',\n            \'form\'       => \'\',\n        ),$atts));\n\n        $thing = (empty($form)) ? ((empty($thing)) ? \'<txp:ied_plugin_info item=\"name\" />\' : $thing) : fetch_form($form);\n\n        $location = do_list($from);\n        $names = do_list($name);\n        $prefixes = do_list($prefix);\n        $excludes = do_list($exclude);\n\n        if (in_array(\'database\', $location)) {\n            $sql = array();\n            $sql[] = \'1\';\n            if ($name) {\n                $sql[] = \"name IN (\'\".implode(\"\',\'\", doSlash($names)).\"\')\";\n            }\n            if ($prefix) {\n                $sqlor = array();\n                foreach ($prefixes as $pfx) {\n                    $sqlor[] = \"name LIKE \'\".doSlash($pfx).\"%\'\";\n                }\n                $sql[] = \'(\' . implode(\' OR \', $sqlor) . \')\';\n            }\n            if ($exclude) {\n                $sql[] = \"name NOT IN (\'\".implode(\"\',\'\", doSlash($excludes)).\"\')\";\n            }\n\n            $rs = safe_rows(\'*\', \'txp_plugin\', implode(\' AND \', $sql) . \' ORDER BY name\');\n        }\n\n        // TODO: Add the meta data from matching plugins in the cache directory\n        if (in_array(\'cache\', $location)) {\n\n\n        }\n\n        $out = array();\n        $ied_pd_saved = $ied_plugin_data;\n\n        foreach ($rs as $row) {\n            $ied_plugin_data = $row;\n            $ied_plugin_data[\'help_unstyled\'] = preg_replace(\'/\\<\\!\\-\\- \\*\\*\\* BEGIN PLUGIN CSS.+END PLUGIN CSS \\*\\*\\* \\-\\-\\>/s\', \'\', $row[\'help\']);\n            $out[] = parse($thing);\n            $ied_plugin_data = array();\n        }\n\n        $ied_plugin_data = $ied_pd_saved;\n\n        return ($wraptag) ? doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id) : implode($break, $out);\n    }\n\n    /**\n     * Public tag: Display plugin data for form/container usage.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_info($atts, $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'item\'    => \'\',\n            \'wraptag\' => \'\',\n            \'break\'   => \'\',\n            \'class\'   => \'\',\n            \'debug\'   => 0,\n        ), $atts));\n\n        $pdata = is_array($ied_plugin_data) ? $ied_plugin_data : array();\n\n        if ($debug) {\n            echo \'++ AVAILABLE INFO ++\';\n            dmp($pdata);\n        }\n\n        $items = do_list($item);\n        $out = array();\n\n        foreach ($items as $it) {\n            if (isset($pdata[$it])) {\n                $out[] = $pdata[$it];\n            }\n        }\n\n        return doWrap($out, $wraptag, $break, $class);\n    }\n\n    /**\n     * Public tag: List of available textpack information.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_textpacks($atts, $thing = null)\n    {\n        global $ied_plugin_data;\n\n        extract(lAtts(array(\n            \'name\'     => \'\',\n            \'filename\' => \'\',\n            \'lang\'     => \'IED_ALL\',\n            \'wraptag\'  => \'\',\n            \'break\'    => \'\',\n            \'class\'    => \'\',\n            \'form\'     => \'\',\n        ), $atts));\n\n        if (!$name && !$filename) {\n            return;\n        }\n\n        if ($name) {\n            $theName = $name;\n        } elseif ($filename) {\n            $theName = $filename;\n        }\n\n        $thing = (empty($form)) ? ((empty($thing)) ? \'<txp:ied_plugin_info item=\"lang\" />\' : $thing) : fetch_form($form);\n\n        $langs = array();\n        $tp_prefixes = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\'));\n\n        if (isset($tp_prefixes[$theName])) {\n            $strings = $this->textpack_grab($lang, $tp_prefixes[$theName]);\n            foreach ($strings as $row) {\n                if (array_search($row[\'lang\'], $langs) === false) {\n                    $langs[] = $row[\'lang\'];\n                }\n            }\n        }\n        $out = array();\n        $ied_pd_saved = $ied_plugin_data;\n        $idx = 0;\n        $num_langs = count($langs);\n\n        foreach ($langs as $row) {\n            $ied_plugin_data[\'lang\'] = $row;\n            $ied_plugin_data[\'first_lang\'] = (($idx === 0) ? 1 : 0);\n            $ied_plugin_data[\'last_lang\'] = (($idx === $num_langs - 1) ? 1 : 0);\n            $out[] = parse($thing);\n            $ied_plugin_data[\'lang\'] = $ied_plugin_data[\'first_lang\'] = $ied_plugin_data[\'last_lang\'] = \'\';\n            $idx++;\n        }\n        $ied_plugin_data = $ied_pd_saved;\n\n        return doWrap($out, $wraptag, $break, $class);\n    }\n\n    /**\n     * Public tag: Download a plugin.\n     *\n     * @param array  $atts  Tag attributes\n     * @param string $thing Tag container content\n     */\n    function ied_plugin_download_link($atts, $thing = null)\n    {\n        extract(lAtts(array(\n            \'type\'     => \'compressed\', // uncompressed, compressed, template, textpack\n            \'name\'     => \'\',\n            \'filename\' => \'\',\n            \'label\'    => \'Download\',\n            \'class\'    => \'\',\n            \'lang\'     => \'IED_ALL\',\n            \'form\'     => \'\',\n            \'escape\'   => \'html\',\n        ), $atts));\n\n        if (!$name && !$filename) {\n            return;\n        }\n\n        $amp = ($escape === \'html\') ? \'&amp;\' : \'&\';\n\n        if ($name) {\n            $theName = $amp.\'name=\'.urlencode($name);\n        } elseif ($filename) {\n            $theName = $amp.\'filename=\'.urlencode($filename);\n        }\n\n        $theClass = \'\';\n        if ($class) {\n            $theClass = \' class=\"\'.$class.\'\"\';\n        }\n        $langopt = \'\';\n        if ($lang) {\n            $langs = do_list($lang);\n            $langopt = $amp.\'lang=\'.implode(\',\', $langs);\n        }\n\n        $linkName = (empty($form)) ? ((empty($thing)) ? $label : parse($thing)) : parse_form($form);\n\n        if ($type === \'compressed\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=zip\'.$langopt, $theClass);\n        } elseif ($type === \'uncompressed\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=txt\'.$langopt, $theClass);\n        } elseif ($type === \'template\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=php\'.$langopt, $theClass);\n        } elseif ($type === \'textpack\') {\n            return href($linkName, \'?ied_plugin_download=1\'.$theName.$amp.\'type=textpack\'.$langopt, $theClass);\n        }\n    }\n\n    /**\n     * Handles downloading plugin content.\n     */\n    function ied_plugin_download()\n    {\n        if (gps(\'ied_plugin_download\')) {\n            $type = gps(\'type\');\n            switch ($type) {\n                case \'zip\':\n                case \'txt\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_file();\n                    break;\n                case \'php\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_php_file();\n                    break;\n                case \'textpack\':\n                    $ied_pc = new ied_pc();\n                    $ied_pc->save_as_textpack();\n                    break;\n            }\n        }\n    }\n}\n\n/**\n * Plugin composer admin interface.\n */\nclass ied_pc\n{\n    /**\n     * Shared information across methods.\n     *\n     * @var array\n     */\n    protected $ied_plugin_globals = array();\n\n    /**\n     * The plugin\'s event as registered in Txp.\n     *\n     * @var string\n     */\n    protected $ied_pc_event = \'ied_plugin_composer\';\n\n    /**\n     * Constructor to set up callbacks and environment.\n     */\n    public function __construct()\n    {\n        $this->ied_plugin_globals = array(\n            \'css_start\' => \'<!--\',\n            \'css_end\'   => \'-->\',\n            \'dlm_start\' => \'#\',\n            \'dlm_end\'   => \'\',\n            \'start\'     => \' --- BEGIN PLUGIN SECTION ---\',\n            \'end\'       => \' --- END PLUGIN SECTION ---\',\n            \'size_help\' => \'63535\',\n            \'size_css\'  => \'2000\',\n            \'size_code\' => \'16777215\',\n        );\n\n        add_privs($this->ied_pc_event, \'1,2\');\n        add_privs(\'plugin_prefs.\' . $this->ied_pc_event, \'1,2\');\n\n        register_tab(\'extensions\', $this->ied_pc_event, gTxt(\'ied_plugin_composer\'));\n        register_callback(array($this, \'plugin_composer\'), $this->ied_pc_event);\n        register_callback(array($this, \'setup\'), \'plugin_prefs.\' . $this->ied_pc_event);\n        register_callback(array($this, \'welcome\'), \'plugin_lifecycle.\' . $this->ied_pc_event);\n        register_callback(array($this, \'inject_css\'), \'admin_side\', \'head_end\');\n    }\n\n    /**\n     * CSS definitions: hopefully kind to themers.\n     *\n     * @return string Style rules\n     */\n    protected function get_style_rules()\n    {\n        $ied_pc_styles = array(\n            \'ied_plugin\' => \'\n.ied_label { margin:0 0.2em 0 0.6em;}\n.ied_plugin_link { float:right; }\n.ied_plugin_info_bar { text-align:right; }\n#ied_plugin_jumpToLine { width:4em; margin:0 1em 0 0.4em; }\n.ied_editForm { width:{edwidth}; margin:0 auto; }\n.ied_subdue { color:gray; padding:1px 2px 2px 1px; }\n#ied_plugin_tp_controls input[type=\"text\"] { width:16%; }\n#options_group_pack ul { list-style-type:none; }\n#options_group_pack ul label { margin:0 8px 0 0; }\n#options_group_pack ul input { width:450px; }\n#options_group_pack li { margin-bottom:.2em }\n.distribution-code { height:4.2em!important; }\n.ied_plugin_edit_toolbar { text-align:right; width:95%; display:inline-block; margin:-2em 0 0 0; }\n#ied_plugin_msgpop { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }\n#ied_plugin_msgpop .publish { float:right; }\n    \',\n        );\n\n        return $ied_pc_styles;\n    }\n\n    /**\n     * Inject style rules into the &lt;head&gt; of the page.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     * @return string      Style rules, or nothing if not the correct $event\n     */\n    public function inject_css($evt, $stp)\n    {\n        global $event;\n\n        if ($event === $this->ied_pc_event) {\n            $ied_plugin_prefs = $this->get_prefs();\n            $ied_plugin_styles = $this->get_style_rules();\n\n            // Possible variable replacements.\n            $edwidth = get_pref(\'ied_plugin_editor_width\', $ied_plugin_prefs[\'ied_plugin_editor_width\'][\'default\']);\n            $stylefind = array(\n                \'{edwidth}\',\n            );\n            $stylereps = array(\n                $edwidth,\n            );\n\n            echo \'<style type=\"text/css\">\' . str_replace($stylefind, $stylereps, $ied_plugin_styles[\'ied_plugin\']) . \'</style>\';\n        }\n\n        return;\n    }\n\n    /**\n     * Plugin jumpoff point.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     */\n    public function plugin_composer($evt, $stp)\n    {\n        $available_steps = array(\n            \'code_save\'        => true,\n            \'create\'           => true,\n            \'edit\'             => false,\n            \'generate_phpdoc\'  => true,\n            \'help\'             => true,\n            \'help_viewer\'      => false,\n            \'install\'          => true,\n            \'lang_set\'         => true,\n            \'table\'            => false,\n            \'meta_save\'        => true,\n            \'multi_edit\'       => true,\n            \'prefs\'            => false,\n            \'restore\'          => true,\n            \'save\'             => true,\n            \'save_as_file\'     => true,\n            \'save_as_php_file\' => true,\n            \'save_as_textpack\' => true,\n            \'set_tp_prefix\'    => true,\n            \'switch_status\'    => true,\n            \'textpack_del\'     => true,\n            \'textpack_get\'     => true,\n            \'textpack_load\'    => true,\n            \'textpack_save\'    => true,\n            \'upload\'           => true,\n            \'save_pane_state\'  => true,\n        );\n\n        if (!$stp or !bouncer($stp, $available_steps)) {\n            $stp = \'table\';\n        }\n\n        $this->$stp();\n    }\n\n    /**\n     * Lifecycle handling, post-install / delete.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     * @return string      Success/failure message\n     */\n    public function welcome($evt, $stp)\n    {\n        $msg = \'\';\n\n        switch ($stp) {\n            case \'installed\':\n                $this->prefs_update();\n                $msg = \'Thanks for installing the plugin composer. Happy authoring :-)\';\n                break;\n            case \'deleted\':\n                $this->prefs_remove(0);\n                break;\n        }\n\n        return $msg;\n    }\n\n    /**\n     * Table of plugins in both database and file system cache.\n     *\n     * @param  string $message Flash message to display success/error\n     * @return string          HTML\n     */\n    public function table($message = \'\')\n    {\n        pagetop(gTxt(\'ied_plugin_composer\'), $message);\n\n        require_privs(\'ied_plugin_composer\');\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n        $checked = in_array(\'installed\', $lc_opts);\n        $auto_en = get_pref(\'ied_plugin_auto_enable\');\n        $pcd = get_pref(\'plugin_cache_dir\');\n\n        $aeRadio[] = \'<p class=\"ied_plugin_radioset ied_plugin_autoenable txp-layout-2col\">\';\n        $aeRadio[] = \'<label class=\"ied_label\">\' . gTxt(\'ied_plugin_auto_enable\') . \'</label>\';\n        $aeRadio[] = radioset($ied_plugin_prefs[\'ied_plugin_auto_enable\'][\'content\'], \'ied_plugin_autoenable\', $auto_en);\n        $aeRadio[] = \'</p>\';\n\n        $ioRadio[] = \'<p class=\"ied_plugin_radioset ied_plugin_installopts txp-layout-2col\" hidden>\';\n        $ioRadio[] = \'<label class=\"ied_label\">\' . gTxt(\'ied_plugin_run_install\') . \'</label>\';\n        $ioRadio[] = yesnoradio(\'ied_plugin_installopts\', $checked);\n        $ioRadio[] = \'</p>\';\n\n        extract(gpsa(array(\'sort\', \'dir\')));\n\n        if ($sort === \'\') $sort = get_pref(\'ied_plugin_sort_column\', \'name\');\n        if ($dir === \'\') $dir = get_pref(\'ied_plugin_sort_dir\', \'asc\');\n        $dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n        if (!in_array($sort, array(\'name\', \'status\', \'author\', \'version\', \'load_order\'))) $sort = \'name\';\n\n        $sort_sql = $sort.\' \'.$dir;\n\n        set_pref(\'ied_plugin_sort_column\', $sort, \'ied_plugin\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n        set_pref(\'ied_plugin_sort_dir\', $dir, \'ied_plugin\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n\n        $switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n        // Top control-panel part of screen\n        echo \'<div class=\"txp-layout\">\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            n. \'<h1 class=\"txp-heading\">\'.gTxt(\'ied_plugin_composer\').sp.$this->anchor($this->ied_pc_event, \'help_viewer\', \'?\', array(\'name\' => \'ied_plugin_composer\'), array(\'class\' => \'pophelp\')).\'</h1>\'.\n            n. \'</div>\'.\n            n. \'<div id=\"ied_plugin_control\" class=\"txp-layout-2col\">\'.\n            n. sLink($this->ied_pc_event, \'prefs\', gTxt(\'ied_plugin_setup\'), \'ied_plugin_link\').\n            n. \'</div>\'.\n            n. \'<div class=\"txp-layout-1col\">\'.\n            n. \'<div class=\"summary-details clear\"><h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_cpanel_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#ied_plugin_cpanel\">\' . gTxt(\'ied_plugin_cpanel_legend\') . \'</a></h3><div id=\"ied_plugin_cpanel\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_cpanel_visible\') ? \'block\' : \'none\').\'\">\'.\n            n. \'<form class=\"ied_plugin_form\" enctype=\"multipart/form-data\" action=\"index.php\" method=\"post\">\'.\n            n. implode(n, $aeRadio).\n            n. implode(n, $ioRadio).\n            n. \'<p class=\"clear\">\'.\n            n. \'<label for=\"ied_plugin_newname\" class=\"ied_label\">\'.gTxt(\'name\').\'</label>\'.\n            n. fInput(\'text\', \'name\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', \'ied_plugin_newname\', false, false, \'Create a new plugin\').\n            n. fInput(\'submit\', \'plugin_create\', gTxt(\'create\')).\n            n. \'</p>\'.\n            n. \'<p>\'.\n            n. \'<label for=\"ied_plugin_file\" class=\"ied_label\">\'.gTxt(\'ied_plugin_upload_php\').\'</label>\'.\n            n. fInput(\'file\', \'thefile\', \'\', \'\', \'\', \'\', \'\', \'\', \'ied_plugin_file\').\n            n. fInput(\'submit\', \'plugin_upload\', gTxt(\'upload\')).\n            n. \'</p>\'.\n            n. \'<p>\'.\n            n. \'<label for=\"ied_plugin64\" class=\"ied_label\">\'.gTxt(\'ied_plugin_install_txt\').\'</label>\'.\n            n. text_area(\'plugin64\', \'\', \'\', \'\', \'ied_plugin64\').\n            n. fInput(\'submit\', \'plugin_install\', gTxt(\'install\')).\n            n. \'</p>\'.\n            n. eInput($this->ied_pc_event).\n            n. sInput(\'create\').\n            n. hInput(\'MAX_FILE_SIZE\', 1000000).\n            n. tInput().\n            n. \'</form>\'.\n            n. \'</div>\'.\n            n. \'</div>\'.\n            n. \'</div>\';\n\n        // Main plugin list\n        $rs = safe_rows(\'*\', \'txp_plugin\', \'1=1 ORDER BY \'.$sort_sql);\n\n        if ($rs) {\n            echo \'<div class=\"txp-layout-1col\">\'.\n                n. \'<div class=\"summary-details\">\'.\n                n. \'<form action=\"index.php\" id=\"ied_plugin_db_form\" method=\"post\">\'.\n                n. \'<h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_dbplugs_visible\') ? \' expanded\' : \'\').\'\">\'.\n                n. \'<a href=\"#ied_plugin_dbplugs\">\' . gTxt(\'ied_plugin_dbplugs_legend\') . \'</a>\'.\n                n. \'</h3>\'.\n                n. \'<div id=\"ied_plugin_dbplugs\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_dbplugs_visible\') ? \'block\' : \'none\').\'\">\'.\n            n. \'<div class=\"txp-listtables\">\'.\n            n. startTable(\'\', \'\', \'txp-list\').\n            n.\'<thead>\'.\n            n. tr(\n                n.hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\n                .n.column_head(\'plugin\', \'name\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'name\' == $sort) ? \"$dir \" : \'\').\'name\')\n                .n.column_head(\'author\', \'author\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'author\' == $sort) ? \"$dir \" : \'\').\'author\')\n                .n.column_head(gTxt(\'version\').\' (\'.gTxt(\'plugin_modified\').\')\', \'version\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'version\' == $sort) ? \"$dir \" : \'\').\'version\')\n                .n.hCell(gTxt(\'description\'), \'\', \' class=\"description\"\')\n                .n.hCell(gTxt(\'manage\'), \'\', \' class=\"manage\"\')\n                .n.column_head(\'active\', \'status\', \'ied_plugin_composer\', true, $switch_dir, \'\', \'\', ((\'status\' == $sort) ? \"$dir \" : \'\').\'status\')\n            ).\n            n. \'</thead>\'.\n            n. \'<tbody>\';\n\n            foreach ($rs as $row) {\n                extract($row);\n                $ename = $this->anchor($this->ied_pc_event, \'edit\', $name, array(\'name\' => $name));\n                $hlink = ($help) ? $this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), array(\'name\' => $name)) : gTxt(\'none\');\n                $fnames = $this->get_name($name, $version);\n                $pubtag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'publish\'), array(\'name\' => $name), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0]))));\n                $pubztag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_compress\'), array(\'name\' => $name, \'type\' => \'zip\'), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[1]))));\n                $modified = (strtolower($code) != (strtolower($code_restore)));\n                $plugpref = ($flags & PLUGIN_HAS_PREFS) ? (sp.$this->anchor(\'plugin_prefs.\'.urlencode($name), \'\', \'[\'.gTxt(\'plugin_prefs\').\']\', array(\'name\' => $name), array(\'class\' => \'plugin_prefs\'.( ($status) ? \'\' : \' empty\'))) ) : \'\';\n\n                echo tr(\n                    n.td(\n                        fInput(\'checkbox\', \'selected[]\', $name)\n                    ,\'\', \'multi-edit\')\n                    .n.td($ename.$plugpref)\n                    .n.td(( ($author_uri) ? \'<a href=\"\'.txpspecialchars($author_uri).\'\">\'.txpspecialchars($author).\'</a>\' : txpspecialchars($author)))\n                    .n.td(( ($modified) ? $this->anchor($this->ied_pc_event, \'restore\', $version, array(\'name\' => $name), array(\'title\' => gTxt(\'ied_plugin_restore_help\'), \'onclick\' => \'return verify(\\\'\'.gTxt(\'ied_plugin_restore_verify\', array(\'{name}\' => $name)).\'\\\');\')) : $version) . (($modified) ? sp.\'(\'.gTxt(\'yes\').\')\' : \'\'))\n                    .n.td(txpspecialchars($description))\n                    .n.td($pubtag .sp. \'&#124;\' .sp. $pubztag .sp. \'&#124;\' .sp. $hlink)\n                    .n.td($this->status_link($status,$name,yes_no($status)))\n                );\n                unset($name,$page);\n            }\n            echo n. \'</tbody>\'.\n                n. endTable().\n                n. \'</div>\'.\n                n. \'</div>\'.\n                n. tInput().\n                $this->multiedit_form(\'db\', \'\', $sort, $dir, \'\', \'\').\n                n. \'</form>\'.\n                n. \'</div>\'.\n                n. \'</div>\';\n        }\n\n        if (!empty($pcd) && file_exists($pcd)) {\n            $filenames = array();\n\n            $directory = dir($pcd);\n\n            while ($file = $directory->read()) {\n                if ($file != \".\" && $file != \"..\") {\n                    $fileaddr = $pcd . DS . $file;\n                    if (!is_dir($fileaddr)) {\n                        $filenames[]=$file;\n                    }\n                }\n            }\n            $directory->close();\n            ($filenames)?natcasesort($filenames):\'\';\n\n            $out = array();\n\n            foreach ($filenames as $filename) {\n                $parts = explode (\'.\',$filename);\n                $fileext = array_pop($parts);\n\n                if ($fileext==\'php\') {\n                    $basename = basename($filename);\n                    $plugin = $this->read_file($pcd . DS . $filename);\n                    $hlink = ($plugin[\'help\']) ? $this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), array(\'filename\' => $filename)) : gTxt(\'none\');\n                    $efile = $this->anchor($this->ied_pc_event, \'edit\', $plugin[\'name\'], array(\'filename\' => $filename));\n                    $fnames = $this->get_name($plugin[\'name\'], $plugin[\'version\']);\n                    $plugpref = (($plugin[\'flags\'] & PLUGIN_HAS_PREFS)) ? \' \'.$this->anchor(\'plugin_prefs.\'.urlencode($plugin[\'name\']), \'\', \' [\'.gTxt(\'plugin_prefs\').\']\') : \'\';\n                    $pubtag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'publish\'), array(\'filename\' => $filename), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0]))));\n                    $pubztag = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_compress\'), array(\'filename\' => $filename, \'type\' => \'zip\'), array(\'title\' => gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[1]))));\n\n                    $out[] = tr(\n                        n.td(\n                            fInput(\'checkbox\', \'selected-cache[]\', $filename)\n                        ,\'\', \'multi-edit\')\n                        .n.td(\n                            tag($filename,\'div\',\' class=\"ied_subdue\"\')\n                            .(isset($plugin[\'name\']) ? $efile.$plugpref.\'<br />\' : \'\').\' \'\n                        )\n                        .n.td(\n                            ( isset($plugin[\'author_uri\']) ? \'<a href=\"\'.$plugin[\'author_uri\'].\'\">\' : \'\' ) .\n                            ( isset($plugin[\'author\']) ? $plugin[\'author\'] : \'&nbsp;\' ).\n                            ( isset($plugin[\'author_uri\']) ? \'</a>\' : \'\' )\n                        )\n                        .n.td(\n                            (isset($plugin[\'version\']) ? $plugin[\'version\'] : \'&nbsp;\')\n                        )\n                        .n.td(\n                            (isset($plugin[\'description\']) ? $plugin[\'description\'] : \'&nbsp;\')\n                        )\n                        .n.td(\n                            (isset($plugin[\'name\']) ? $pubtag .sp. \'&#124;\' .sp. $pubztag\n                                : tag(\'&nbsp;\', \'span\')\n                            )\n                            .sp. \'&#124;\' .sp. $hlink\n                        )\n                    );\n                }\n            }\n\n            if ($out) {\n                echo \'<div class=\"txp-layout-1col\">\'.\n                    n. \'<div class=\"summary-details\">\'.\n                    n. \'<form action=\"index.php\" id=\"ied_plugin_cache_form\" method=\"post\">\'.\n                        n. \'<h3 class=\"lever txp-summary\'.(get_pref(\'pane_ied_plugin_cacheplugs_visible\') ? \' expanded\' : \'\').\'\">\'.\n                        n. \'<a href=\"#ied_plugin_cacheplugs\">\' . gTxt(\'ied_plugin_cacheplugs_legend\') . \'</a>\'.\n                        n. \'</h3>\'.\n                        n. \'<div id=\"ied_plugin_cacheplugs\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_ied_plugin_cacheplugs_visible\') ? \'block\' : \'none\').\'\">\'.\n                    n. \'<div class=\"txp-listtables ied_plugin_cacheplugs\">\'.\n                    n.startTable(\'\', \'\', \'txp-list\').\n                    n. \'<thead>\'.\n                    n. tr(\n                        n.hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\n                        .n.hCell(gTxt(\'plugin\'), \'\', \' class=\"name\"\')\n                        .n.hCell(gTxt(\'author\'), \'\', \' class=\"author\"\')\n                        .n.hCell(gTxt(\'version\') . \' (\'.gTxt(\'plugin_modified\').\')\', \'\', \' class=\"version\"\')\n                        .n.hCell(gTxt(\'description\'), \'\', \' class=\"description\"\')\n                        .n.hCell(gTxt(\'manage\'), \'\', \' class=\"manage\"\')\n                    ).\n                    n. \'</thead>\'.\n                    n. \'<tbody>\'.\n                    n. implode(n, $out).\n                    n. \'</tbody>\'.\n                    n. endTable().\n                    n. \'</div>\'.\n                    n. tInput().\n                    $this->multiedit_form(\'cache\', \'\', $sort, $dir, \'\', \'\').\n                    n. \'</form>\'.\n                    n. \'</div>\'.\n                    n. \'</div>\'.\n                    n. \'</div>\';\n            }\n        }\n\n        echo \'</div>\'.\n            n. script_js( <<<EOJS\n$(document).ready(function () {\n    $(\'#ied_plugin_db_form\').txpMultiEditForm({\n        \'checkbox\' : \'input[name=\"selected[]\"][type=checkbox]\'\n    });\n\n    $(\'#ied_plugin_cache_form\').txpMultiEditForm({\n        \'checkbox\' : \'input[name=\"selected-cache[]\"][type=checkbox]\'\n    });\n\n    var ied_io = $(\'.ied_plugin_installopts\');\n\n    $(\'#ied_plugin_file\').change(function() {\n        var fn = $(this).val();\n\n        if (fn.substr(fn.lastIndexOf(\'.\') + 1) === \'txt\') {\n            ied_io.show();\n        } else {\n            ied_io.hide();\n        }\n    });\n\n    $(\'#ied_plugin64\').on(\'blur, keyup\', function() {\n        if ($(this).val()) {\n            ied_io.show();\n        } else {\n            ied_io.hide();\n        }\n    });\n});\nEOJS\n                    );\n\n        // Show/hide \"Options\" link by setting the appropriate class on the plugin\'s &lt;tr&gt;\n        echo script_js(<<<EOJS\ntextpattern.Relay.register(\'txpAsyncHref.success\', function (event, data) {\n    jQuery(data.this).closest(\'tr\').find(\'a.plugin_prefs\').toggleClass(\'empty\');\n});\nEOJS\n        );\n    }\n\n    /**\n     * Render a multi-edit dropdown list of actions.\n     *\n     * @param  string $flavour       Whether the dropdown is for file-based plugins or those in the DB\n     * @param  string $page          Current page of results (unused)\n     * @param  string $sort          Current sort column name\n     * @param  string $dir           Current sort direction (asc or desc)\n     * @param  string $crit          Current filter criteria (unused)\n     * @param  string $search_method Column name upon which the search is being performed (unused)\n     * @return HTML\n     */\n    protected function multiedit_form($flavour, $page, $sort, $dir, $crit, $search_method)\n    {\n        $orders = selectInput(\'order\', array(1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9), 5, false);\n        $stati = selectInput(\'switch_status\', array(\'toggle\' => gTxt(\'ied_plugin_toggle\'), \'on\' => gTxt(\'on\'), \'off\' => gTxt(\'off\')), \'toggle\', false);\n        $lifecycles = selectInput(\'ied_lc_event\', array(\n            \'installed\'         => gTxt(\'ied_plugin_lbl_lc_install\'),\n            \'enabled\'           => gTxt(\'ied_plugin_lbl_lc_enable\'),\n            \'installed,enabled\' => gTxt(\'ied_plugin_lbl_lc_instable\'),\n            \'disabled\'          => gTxt(\'ied_plugin_lbl_lc_disable\'),\n            \'deleted\'           => gTxt(\'ied_plugin_lbl_lc_delete\'),\n            \'disabled,deleted\'  => gTxt(\'ied_plugin_lbl_lc_disdel\'),\n        ));\n\n        if ($flavour === \'db\') {\n            $methods = array(\n                \'changestatus\' => array(\'label\' => gTxt(\'changestatus\'), \'html\' => $stati),\n                \'changeorder\'  => array(\'label\' => gTxt(\'changeorder\'), \'html\' => $orders),\n                \'lifecycle\'    => array(\'label\' => gTxt(\'ied_plugin_lifecycle\'), \'html\' => $lifecycles),\n                \'delete\'       => gTxt(\'delete\'),\n            );\n        } else {\n            $methods = array(\n                \'lifecycle\' => array(\'label\' => gTxt(\'ied_plugin_lifecycle\'), \'html\' => $lifecycles),\n                \'textpack\'  => array(\'label\' => gTxt(\'ied_plugin_install_textpack\')),\n                \'delete\'    => gTxt(\'delete\'),\n            );\n        }\n\n        return multi_edit($methods, $this->ied_pc_event, \'multi_edit\', $page, $sort, $dir, $crit, $search_method);\n    }\n\n    /**\n     * Handle multi-edit actions.\n     */\n    public function multi_edit()\n    {\n        $selected = ps(\'selected\');\n        $selected_cache = ps(\'selected-cache\');\n        $method = assert_string(ps(\'edit_method\'));\n\n        if ($selected && is_array($selected)) {\n            $where = \"name IN (\'\".implode(\"\',\'\", doSlash($selected)).\"\')\";\n            $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n\n            switch ($method) {\n                case \'delete\':\n                    foreach ($selected as $name) {\n                        if (safe_field(\'flags\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) {\n                            load_plugin($name, true);\n                            if (in_array(\'disabled\', $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", \'disabled\');\n                            }\n                            if (in_array(\'deleted\', $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", \'deleted\');\n                            }\n                        }\n                    }\n                    safe_delete(\'txp_plugin\', $where);\n                    break;\n\n                case \'changestatus\':\n                    switch (ps(\'switch_status\')) {\n                        case \'on\':\n                            $newstat = \'1\';\n                            break;\n                        case \'off\':\n                            $newstat = \'0\';\n                            break;\n                        case \'toggle\':\n                        default:\n                            $newstat = \'(1-status)\';\n                            break;\n                    }\n\n                    foreach ($selected as $name) {\n                        if (safe_field(\'flags\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) {\n                            $status = safe_field(\'status\', \'txp_plugin\', \"name =\'\".doSlash($name).\"\'\");\n                            $status = $status ? \'disabled\' : \'enabled\';\n                            load_plugin($name, true);\n                            if (in_array($status, $lc_opts)) {\n                                callback_event(\"plugin_lifecycle.$name\", $status);\n                            }\n                        }\n                    }\n\n                    safe_update(\'txp_plugin\', \'status = \'.$newstat, $where);\n                    break;\n\n                case \'changeorder\':\n                    $order = min(max(intval(ps(\'order\')), 1), 9);\n                    safe_update(\'txp_plugin\', \'load_order = \'.$order, $where);\n                    break;\n\n\n                case \'lifecycle\':\n                    $lc_evs = do_list(ps(\'ied_lc_event\'));\n                    foreach ($selected as $name) {\n                        foreach ($lc_evs as $lc_ev) {\n                            callback_event(\"plugin_lifecycle.$name\", $lc_ev);\n                        }\n                    }\n                    break;\n            }\n        } else {\n            $selected = array();\n        }\n\n        if ($selected_cache && is_array($selected_cache)) {\n            $pcd = get_pref(\'plugin_cache_dir\');\n\n            switch ($method) {\n                case \'delete\':\n                    foreach ($selected_cache as $name) {\n                        $filenames = array();\n                        $dir = dir($pcd);\n\n                        while ($file = $dir->read()) {\n                            if ($file != \".\" && $file != \"..\" && in_array($file, $selected_cache)) {\n                                $fileaddr = $pcd . DS . $file;\n\n                                if (!is_dir($fileaddr)) {\n                                    unlink($fileaddr);\n                                }\n                            }\n                        }\n\n                        $dir->close();\n                    }\n                    break;\n\n                case \'lifecycle\':\n                    $lc_evs = do_list(ps(\'ied_lc_event\'));\n                    foreach ($selected_cache as $name) {\n                        $name = str_replace(\'.php\', \'\', $name);\n                        foreach ($lc_evs as $lc_ev) {\n                            callback_event(\"plugin_lifecycle.$name\", $lc_ev);\n                        }\n                    }\n                    break;\n\n                case \'textpack\':\n                    // Read the textpack from the .php file and call install_textpack()\n                    $textpack = array();\n                    foreach ($selected_cache as $name) {\n                        $fileaddr = $pcd . DS . $name;\n\n                        $contents = file($fileaddr);\n                        $in_tp = $in_comment = false;\n                        foreach ($contents as $row) {\n                            if (strpos($row, \'/**\') === 0) {\n                                $in_comment = true;\n                            }\n                            if (strpos($row, \'**/\') === 0) {\n                                $in_comment = false;\n                            }\n                            if (strpos($row, \'EOT;\') !== false) {\n                                break;\n                            }\n                            if ($in_tp === true) {\n                                $textpack[] = trim($row);\n                            }\n                            if (!$in_comment && strpos($row, \'$plugin[\\\'textpack\\\']\') !== false) {\n                                $in_tp = true;\n                      }\n                   }\n                    }\n\n                    $done = install_textpack(implode(n, $textpack));\n                break;\n            }\n        } else {\n            $selected_cache = array();\n        }\n\n        $message = \'\';\n\n        if ($selected || $selected_cache) {\n            if ($method === \'delete\') {\n                $message = gTxt(\'plugin_deleted\', array(\'{name}\' => implode(\', \', array_merge($selected, $selected_cache))));\n            } elseif ($method === \'lifecycle\') {\n                $message = gTxt(\'ied_plugin_lc_fired\', array(\'{name}\' => implode(\', \', array_merge($selected, $selected_cache)), \'{event}\' => implode(\',\', $lc_evs)));\n            } elseif ($method === \'textpack\') {\n                $message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $done));\n            } else {\n                $message = gTxt(\'plugin_updated\', array(\'{name}\' => implode(\', \', $selected)));\n            }\n        }\n        $this->table($message);\n    }\n\n    /**\n     * Render a hyperlink to another plugin composer method.\n     *\n     * @param  string $evt      Textpattern event destination\n     * @param  string $stp      Textpattern step (action) destination\n     * @param  string $linktext Text for the hyperlink\n     * @param  array  $nv       Name-value tuples to add to the URL\n     * @param  array  $atts     Name-value tuples to add to the anchor tag\n     * @return HTML\n     */\n    protected function anchor($evt, $stp, $linktext, $nv = array(), $atts = array())\n    {\n        $nv[\'_txp_token\'] = form_token();\n        $atts[\'href\'] = \'?event=\' . $evt . ($stp ? \'&step=\' . $stp : \'\') . \'&\' . http_build_query($nv);\n\n        $attribs = \'\';\n\n        foreach ($atts as $n => $v) {\n            $attribs .= \' \' . txpspecialchars($n) . \'=\"\' . txpspecialchars($v) . \'\"\';\n        }\n\n        return tag($linktext, \'a\', $attribs);\n    }\n\n    /**\n     * Render an asynchronous anchor tag for toggling plugin status.\n     *\n     * @param  string $status   Current plugin status (0=disabled, 1=enabled)\n     * @param  string $name     Plugin name to toggle\n     * @param  string $linktext Link text to display on the anchor\n     * @return string           HTML\n     */\n    protected function status_link($status, $name, $linktext)\n    {\n        return asyncHref($linktext, array(\'step\' => \'switch_status\', \'thing\' => $name),\' title=\"\'.($status==1 ? gTxt(\'disable\') : gTxt(\'enable\')).\'\"\' );\n    }\n\n    /**\n     * Toggle the status of the given plugin (params sent via POST)\n     *\n     * @return string New status\n     */\n    public function switch_status()\n    {\n        extract(array_map(\'assert_string\', gpsa(array(\'thing\', \'value\'))));\n        $change = ($value == gTxt(\'yes\')) ? 0 : 1;\n\n        safe_update(\'txp_plugin\', \"status = $change\", \"name = \'\".doSlash($thing).\"\'\");\n\n        $lc_opts = do_list(get_pref(\'ied_plugin_lifecycle_options\'));\n        $stp = $change ? \'enabled\' : \'disabled\';\n\n        if (in_array($stp, $lc_opts) && (safe_field(\'flags\', \'txp_plugin\', \"name=\'\".doSlash($thing).\"\'\") & PLUGIN_LIFECYCLE_NOTIFY) ) {\n            load_plugin($thing, true);\n            $message = callback_event(\"plugin_lifecycle.$thing\", $stp);\n        }\n\n        echo gTxt($change ? \'yes\' : \'no\');\n    }\n\n    /**\n     * Reset the given plugin to its last restore point.\n     */\n    public function restore()\n    {\n        $name = doSlash(gps(\'name\'));\n        safe_update(\"txp_plugin\", \"code = code_restore\", \"name=\'$name\'\");\n        $this->table(gTxt(\'ied_plugin_restored\', array(\'{name}\' => $name)));\n    }\n\n    /**\n     * Edit the selected plugin data.\n     *\n     * @param  string $message Flash message to display after action\n     * @param  string $newfile Filename if plugin being edited is in the filesystem\n     */\n    public function edit($message = \'\', $newfile = \'\')\n    {\n        $newname = trim(gps(\'newname\'));\n        $filename = gps(\'filename\');\n        $editfile = $filename ? 1 : 0;\n        $name = empty($newname) ? gps(\'name\') : $newname;\n        $name = ($newfile) ? $newfile : (($filename) ? $filename : $name);\n\n        pagetop(gTxt(\'ied_plugin_editing\', array(\'{name}\' => txpspecialchars($name))), $message);\n        require_privs(\'ied_plugin_composer\');\n\n        echo $this->insert_editors();\n\n        if (!$editfile) {\n            $rs = safe_row(\"author, author_uri, version, description, code, help, status, type, load_order, flags\", \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } else {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.$name);\n            $filename = $name;\n            $name = explode (\'.\', $name);\n            $fileext = array_pop($name);\n            $name = implode($name);\n            extract($plugin);\n            $status = ($fileext==\'php\')? 1: 0;\n        }\n\n        $ifel = get_pref(\'ied_plugin_interface_elems\');\n        $distblock = (strpos($ifel, \'distribution\') !== false);\n        $styleblock = (strpos($ifel, \'style\') !== false);\n        $distribution = \'\';\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n\n        if ($distblock) {\n            $plugin[\'name\'] = $name;\n            $plugin[\'author\'] = $author;\n            $plugin[\'author_uri\'] = $author_uri;\n            $plugin[\'version\'] = $version;\n            $plugin[\'description\'] = $description;\n            $plugin[\'help\'] = $this->textile($name, $help, $css, $start_css, $end_css);\n            $plugin[\'code\'] = $code;\n            $plugin[\'type\'] = $type;\n            $plugin[\'order\'] = $load_order;\n            $plugin[\'flags\'] = $flags;\n            $plugin[\'md5\'] = md5( $plugin[\'code\'] );\n            $distribution = \'<textarea name=\"distribution\" class=\"distribution-code\" rows=\"1\" onclick=\"this.select()\">\'.base64_encode(serialize($plugin)).\'</textarea>\';\n        }\n\n        for ($i = 1; $i <= 9; $i++) $orders[$i] = $i;\n\n        $tp_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\', 1));\n        $tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : \'\';\n\n        $fnames = $this->get_name($name, $version);\n        $namedLink = ($filename) ? array(\'filename\' => $filename) : array(\'name\' => $name);\n        $zippedLink = array_merge($namedLink, array(\'type\' => \'zip\'));\n\n        $slink = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_export\', array(\'{name}\' => $fnames[0])), $namedLink);\n        $sziplink = $this->anchor($this->ied_pc_event, \'save_as_file\', gTxt(\'ied_plugin_export_zip\', array(\'{name}\' => $fnames[1])), $zippedLink);\n        $sphplink = $this->anchor($this->ied_pc_event, \'save_as_php_file\', gTxt(\'ied_plugin_save_as\', array(\'{name}\' => $fnames[2])), $namedLink);\n        $stxtlink = $this->anchor($this->ied_pc_event, \'save_as_textpack\', gTxt(\'ied_plugin_export_textpack\'), $namedLink);\n        $vhelplinkfull = ($help) ? \'[ \' .$this->anchor($this->ied_pc_event, \'help_viewer\', gTxt(\'ied_plugin_docs\'), $namedLink) . \' ]\' : \'\';\n\n        $msgpop = \'<div id=\"ied_plugin_msgpop\"><input type=\"button\" class=\"publish\" value=\"\'.gTxt(\'ok\').\'\" onclick=\"ied_plugin_toggle_msgpop(\\\'0\\\');\" /><h2>\'.gTxt(\'ied_plugin_msgpop_lbl\').\'</h2><span class=\"ied_plugin_msgpop_content\"></span></div>\';\n\n        $newname = fInput(\'text\', \'newname\', $name, \'\', \'\', \'\', INPUT_REGULAR);\n        $author_widget = fInput(\'text\', \'author\', $author, \'\', \'\', \'\', INPUT_REGULAR);\n        $author_uri_widget = fInput(\'text\', \'author_uri\', $author_uri, \'\', \'\', \'\', INPUT_REGULAR);\n        $version_widget = fInput(\'text\', \'version\', $version, \'input-small\', \'\', \'\',INPUT_SMALL) .sp. (($editfile) ? checkbox(\'rename_file\', \'1\', 0, \'\',\'rename_file\') . \' <label for=\"rename_file\">\'.gTxt(\'ied_plugin_rename_file\').\'</label>\' : checkbox(\'restore_point\', \'1\', 0, \'\',\'restore_point\') . \' <label for=\"restore_point\">\'.gTxt(\'ied_plugin_restore_point\').\'</label>\');\n        $description_widget = fInput(\'text\', \'description\', $description, \'input-xlarge\', \'\', \'\', INPUT_REGULAR);\n        $codeblock = \'<textarea name=\"code\" id=\"plugin_editor\" rows=\"\'.INPUT_REGULAR.\'\" class=\"code codepress php\" maxlength=\"\'.$this->ied_plugin_globals[\'size_code\'].\'\">\'.txpspecialchars($code).\'</textarea><div class=\"ied_plugin_info_bar\"><span>\'.gTxt(\'ied_plugin_jump_to_line\').\'</span><input type=\"text\" id=\"ied_plugin_jumpToLine\" size=\"5\" maxlength=\"6\" /><span class=\"ied_plugin_charsRemain\"></span></div>\';\n        $help_widget = \'<textarea name=\"help\" id=\"plugin_help\" rows=\"\'.INPUT_REGULAR.\'\" class=\"mceEditor\" maxlength=\"\'.$this->ied_plugin_globals[\'size_help\'].\'\">\'.txpspecialchars($help).\'</textarea><div class=\"ied_plugin_info_bar\"><span class=\"ied_plugin_charsRemain\"></span></div>\';\n        $css_widget = ($styleblock) ? \'<textarea name=\"css\" id=\"plugin_help_css\" rows=\"\'.INPUT_MEDIUM.\'\" class=\"code\" maxlength=\"\'.$this->ied_plugin_globals[\'size_css\'].\'\">\'.txpspecialchars($css).\'</textarea><div class=\"ied_plugin_info_bar\"><span class=\"ied_plugin_charsRemain\"></span></div>\' : \'\';\n        $plugstatus = (!$editfile) ? sp.sp.checkbox(\'status\',1,$status, \'\',\'status\'). \' <label for=\"status\">\'.gTxt(\'ied_plugin_enable\').\'</label>\' : \'\';\n\n        $plugtypes = array(\n            \'0\' => gTxt(\'ied_plugin_type_0\'),\n            \'1\' => gTxt(\'ied_plugin_type_1\'),\n            \'2\' => gTxt(\'ied_plugin_type_2\'),\n            \'3\' => gTxt(\'ied_plugin_type_3\'),\n            \'4\' => gTxt(\'ied_plugin_type_4\'),\n            \'5\' => gTxt(\'ied_plugin_type_5\'),\n        );\n        $plugtype = selectInput(\'type\', $plugtypes, $type, false, \'\', \'type\');\n\n        $plugorder = selectInput(\'load_order\', $orders, $load_order, 0, 0);\n        $flaglist = checkbox(\n                \'flags[]\',\n                PLUGIN_HAS_PREFS,\n                (($flags & PLUGIN_HAS_PREFS) ? 1 : 0),\n                \'\',\n                \'ied_plugin_flag_has_prefs\'\n            )\n            .n. \'<label for=\"ied_plugin_flag_has_prefs\">\' . gTxt(\'ied_plugin_flag_has_prefs\') . \'</label>\'\n            .n. checkbox(\n                \'flags[]\',\n                PLUGIN_LIFECYCLE_NOTIFY,\n                (($flags & PLUGIN_LIFECYCLE_NOTIFY) ? 1 : 0),\n                \'\',\n                \'ied_plugin_flag_lifecycle_notify\'\n            )\n            .n. \'<label for=\"ied_plugin_flag_lifecycle_notify\">\' . gTxt(\'ied_plugin_flag_lifecycle_notify\') . \'</label>\';\n\n        $sub = graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\', \'\', \'\', \'\', \'\', \'ied_editSave\'), array(\'class\' => \'txp-save\'));\n        $codesub = (!$editfile) ? \'<a class=\"navlink\" name=\"ied_plugin_code_save\" id=\"ied_plugin_code_save\">\' . gTxt(\'ied_plugin_code_save\') . \'</a>\' : \'\';\n        $metasub = (!$editfile) ? \'<a class=\"navlink\" name=\"ied_plugin_meta_save\" id=\"ied_plugin_meta_save\">\' . gTxt(\'ied_plugin_meta_save\') . \'</a>\' : \'\';\n\n        // Language info. ied_visible_langs is the user\'s choice of which ones they want to see available.\n        // ied_available_langs is the list of actual, currently-installed langs.\n        $theLang = get_pref(\'language\');\n        $string_count = ($tp_pfx) ? safe_rows(\'lang, count(*) as count\', \'txp_lang\', \"name like \'\".$tp_pfx.\"%\' group by lang\") : array();\n        $ied_listlangs = get_pref(\'ied_plugin_lang_choose\', \'installed\');\n        $ied_visible_langs = self::lang_list($ied_listlangs);\n        $ied_available_langs = ($ied_listlangs == \'installed\') ? $ied_visible_langs : self::lang_list(\'installed\');\n        $dflt_lang = get_pref(\'ied_plugin_lang_default\', $theLang);\n        $dflt_lang = array_key_exists($dflt_lang, $ied_visible_langs) ? $dflt_lang : $theLang;\n        $dflt_lang_string_count = 0;\n\n        foreach ($string_count as $str_totals) {\n            if (isset($ied_visible_langs[$str_totals[\'lang\']])) {\n                $ied_visible_langs[$str_totals[\'lang\']] .= \' [\'.$str_totals[\'count\'].\']\';\n                if ($str_totals[\'lang\'] === $dflt_lang) {\n                    $dflt_lang_string_count = $str_totals[\'count\'];\n                }\n            }\n        }\n        $langsel = selectInput(\'ied_plugin_tp_lang\', $ied_visible_langs, $dflt_lang, \'\', \'\', \'ied_plugin_tp_lang\')\n            .fInput(\'button\', \'ied_plugin_tp_refresh\', gTxt(\'ied_plugin_load\'), \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_refresh\');\n\n        $preselected = do_list(get_pref(\'ied_plugin_lang_selected\', \'\'));\n\n        $op_langs[] = \'<select name=\"ied_plugin_tp_oplangs\" id=\"ied_plugin_tp_oplangs\" multiple=\"multiple\"><option value=\"\"></option>\';\n        foreach ($ied_available_langs as $langcode => $alang) {\n            $sel = in_array($langcode, $preselected) ? \' selected=\"selected\"\' : \'\';\n            $op_langs[] = \'<option value=\"\'.$langcode.\'\"\'.$sel.\'>\'.$alang.\'</option>\';\n        }\n        $op_langs[] = \'</select>\';\n\n        $tp_strings = array();\n        $tp_rows = $this->textpack_grab($dflt_lang, $tp_pfx);\n        foreach ($tp_rows as $tp_string) {\n            $apsel = selectInput(\'ied_plugin_tp_event\', array(\'admin\' => gTxt(\'admin\'), \'public\' => gTxt(\'public\'), \'common\' => gTxt(\'both\')), ($tp_string[\'event\'] == \'public\' ? \'public\' : ($tp_string[\'event\'] == \'common\' ? \'common\' : \'admin\')) );\n            $tp_strings[] = \'<li>\'.fInput(\'text\', \'textpack_\'.$tp_string[\'name\'], $tp_string[\'data\']).\' \'.$apsel.\' <label>\'.$tp_string[\'name\'].\'</label>\'.\'</li>\';\n        }\n\n\n        $err_prefix = gTxt(\'ied_plugin_syntax_err\');\n        $codesave_ok = gTxt(\'ied_plugin_code_saved\');\n        $phpdoc = \'\';\n\n        // TODO\n        $classFinder = \'/class[\\s\\n]+(\\w+)[\\s\\n]*\\{?(function[\\s\\n]+(\\w+)[\\s\\n]*\\(.*\\)[\\s\\n]*\\{?)*/\';\n        $functionFinder = \'/function[\\s\\n]+(\\w+)[\\s\\n]*\\(.*\\)[\\s\\n]*\\{?/\';\n        preg_match_all($functionFinder, $code , $functionArray);\n        // selectInput requires key and value to be the same\n    //dmp($functionArray);\n        $fnArray = array();\n    /*\n        foreach ($functionArray[1] as $key => $val) {\n            $fnArray[$val] = $functionArray[1][$key];\n        }\n        $phpdoc = selectInput(\'ied_plugin_to_phpdoc\', $fnArray, \'\', false, \'\', \'ied_plugin_to_phpdoc\')\n            .\'<a class=\"navlink\" name=\"ied_plugin_btn_phpdoc\" id=\"ied_plugin_btn_phpdoc\">\' . gTxt(\'ied_plugin_php_doc\') . \'</a>\';\n    */\n        $phpdoc = \'\';\n        $tabCount = $tabActive = 0;\n        $selectedTab = get_pref(\'pane_\' . $this->ied_pc_event . \'_visible\');\n        $tokenSuffix = $this->ied_pc_event . form_token() . get_pref(\'blog_uid\');\n        $panelSet = array(\n            0 => \'meta\',\n            1 => \'code\',\n            2 => \'pack\',\n            3 => \'docs\',\n            );\n\n        $switcherList = array();\n\n        foreach ($panelSet as $tabId => $panel) {\n            $switcherList[] = \'<li><a href=\"#options_group_\'.$panel.\'\" data-txp-pane=\"\'.$panel.\'\" data-txp-token=\"\' . md5($panel.$tokenSuffix).\'\">\' . gTxt(\'ied_plugin_\'.$panel.\'_legend\') . \'</a></li>\';\n            if ($panel === $selectedTab) {\n                $tabActive = $tabCount;\n            }\n\n            $tabCount++;\n        }\n\n        echo\n            n. form(\n                \'<div class=\"txp-layout\">\'\n                .n. \'<div class=\"txp-layout-2col\">\'\n                .n. hed(gTxt(\'ied_plugin_edit\', array(\'{name}\' => $name, \'{version}\' => $version)), 1, array(\'class\' => \'txp-heading\'))\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-2col\">\'\n                .n. sLink($this->ied_pc_event, \'\', gTxt(\'ied_plugin_list\'), \'ied_plugin_link\')\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-4col-alt\" id=\"ied_edit_switcher\" role=\"region\">\'\n                .n. \'<section class=\"txp-details\" id=\"all_options\" aria-labelledby=\"all_options-label\">\'\n                .n. \'<h3 id=\"all_options-label\">Panels</h3>\'\n                .n. \'<div role=\"group\">\'\n                .n. \'<ul class=\"switcher-list\">\'\n                .implode(n, $switcherList)\n                .n. \'</ul>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n                .n. $sub\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_dist\" aria-labelledby=\"options_group_dist-label\">\'\n                .n. hed(gTxt(\'ied_plugin_utils_legend\'), 2, array(\'id\' => \'options_group_dist-label\'))\n                .n. graf(gTxt(\'ied_plugin_utils_legend_extra\'), array(\'class\' => \'warning\'))\n                .n. (($distblock) ? \'<div>\' . $distribution . \'</div>\' : \'\')\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"ied_plugin_tp_oplangs\">\' . gTxt(\'ied_plugin_lang_choose\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . implode(n, $op_langs) . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<ul>\'\n                .n. \'<li>\' . $slink . \'</li>\'\n                .n. \'<li>\' . $sziplink . \'</li>\'\n                .n. \'<li>\' . $stxtlink . \'</li>\'\n                .n. \'<li>\' . $sphplink . \'</li>\'\n                .n. \'</ul>\'\n                .n. \'</section>\'\n                .n. \'</div>\'\n\n                .n. \'<div class=\"txp-layout-4col-3span\" id=\"ied_edit_content\" role=\"region\">\'\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_meta\" aria-labelledby=\"options_group_meta-label\">\'\n                .n. hed(gTxt(\'ied_plugin_meta_legend\'), 2, array(\'id\' => \'options_group_meta-label\'))\n                .n. \'<span class=\"ied_plugin_edit_toolbar\">\' . $metasub . \'</span>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"newname\">\' . gTxt(\'name\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $newname . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"version\">\' . gTxt(\'version\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. $version_widget\n                .n. $plugstatus\n                .n. (($filename) ? tag(sp.sp.\'(\'.$filename.\')\',\'span\',\' style=\"color:gray;\"\').hInput(\'filename\', $filename) : \'\')\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"description\">\' . gTxt(\'description\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $description_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"author\">\' . gTxt(\'author\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $author_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"author_uri\">\' . gTxt(\'website\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $author_uri_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"type\">\' . gTxt(\'ied_plugin_type\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $plugtype . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_flags\')  . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $flaglist . \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_load_order\') . gTxt(\'ied_plugin_load_order_help\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $plugorder . \'</div>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_code\" aria-labelledby=\"options_group_code-label\">\'\n                .n. hed(gTxt(\'ied_plugin_code_legend\'), 2, array(\'id\' => \'options_group_code-label\'))\n                .n. \'<span class=\"ied_plugin_edit_toolbar\">\' . $msgpop . $phpdoc . sp . $codesub . \'</span>\'\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"plugin_editor\">PHP</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $codeblock . \'</div>\'\n                .n. \'</div>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_pack\" aria-labelledby=\"options_group_pack-label\">\'\n                .n. hed(gTxt(\'ied_plugin_pack_legend\'), 2, array(\'id\' => \'options_group_pack-label\'))\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"ied_plugin_tp_prefix\">\' . gTxt(\'ied_plugin_tp_prefix\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. fInput(\'text\', \'ied_plugin_tp_prefix\', $tp_pfx, \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_prefix\')\n                .n. $langsel\n                .n. fInput(\'hidden\', \'ied_plugin_tp_lang_dflt\', $dflt_lang, \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_lang_dflt\')\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-form-field\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label>\' . gTxt(\'ied_plugin_tp_populate\')  . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\'\n                .n. fInput(\'text\', \'ied_plugin_tp_populate\', \'\', \'\', \'\', \'\', \'\', \'\', \'ied_plugin_tp_populate\')\n                .n. \'<button id=\"ied_plugin_tp_load\">\' . gTxt(\'go\') . \'</button>\'\n                .n. \'<span id=\"ied_plugin_tp_load_count\"></span>\'\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<a href=\"#\" id=\"ied_plugin_add_string\" class=\"txp-button\">+</a>\'\n                .n. \'<ul>\'\n                .n. implode(n, $tp_strings)\n                .n. \'</ul>\'\n                .n. \'</section>\'\n\n                .n. \'<section class=\"txp-prefs-group\" id=\"options_group_docs\" aria-labelledby=\"options_group_docs-label\">\'\n                .n. hed(gTxt(\'ied_plugin_docs_legend\') . n . $vhelplinkfull, 2, array(\'id\' => \'options_group_docs-label\'))\n                .n. \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                .n. \'<div class=\"txp-form-field-label\"><label for=\"plugin_help\">\' . gTxt(\'text\') . \'</label></div>\'\n                .n. \'<div class=\"txp-form-field-value\">\' . $help_widget . \'</div>\'\n                .n. \'</div>\'\n                .n. (($styleblock)\n                        ? \'<div class=\"txp-form-field txp-form-field-textarea\">\'\n                            . \'<div class=\"txp-form-field-label\"><label for=\"plugin_help_css\">\' . gTxt(\'css\') . \'</label></div>\'\n                            . \'<div class=\"txp-form-field-value\">\' . $css_widget . \'</div>\'\n                            . \'</div>\'\n                        : \'\')\n                .n. \'</section>\'\n                .n. \'</div>\'\n                .n. sInput(\'save\')\n                .n. eInput($this->ied_pc_event)\n                .n. hInput(\'name\',$name)\n            , \'\', \'\', \'post\', \'ied-edit-form\').\n            script_js(<<<EOJS\nvar selectedTab = \'{$tabActive}\';\nvar iedPluginGroup = $(\'.ied-edit-form\');\nvar iedPluginTabs = iedPluginGroup.find(\'.switcher-list li\');\n\niedPluginGroup.tabs({active: selectedTab}).removeClass(\'ui-widget ui-widget-content ui-corner-all\').addClass(\'ui-tabs-vertical\');\niedPluginGroup.find(\'.switcher-list\').removeClass(\'ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all\');\niedPluginTabs.removeClass(\'ui-state-default ui-corner-top\');\niedPluginGroup.find(\'.txp-prefs-group\').removeClass(\'ui-widget-content ui-corner-bottom\');\n\niedPluginTabs.on(\'click\', \'a\', function(ev) {\n    var me = $(this);\n    sendAsyncEvent({\n            event   : \'pane\',\n            step    : \'tabVisible\',\n            pane    : me.data(\'txp-pane\'),\n            origin  : textpattern.event,\n            token   : me.data(\'txp-token\')\n        });\n});\n\nvar ied_plugin_tp_total = 0;\n\njQuery.fn.selectRange = function(start, end) {\n    return this.each(function() {\n        if (this.setSelectionRange) {\n            this.focus();\n            this.setSelectionRange(start, end);\n        } else if (this.createTextRange) {\n            var range = this.createTextRange();\n            range.collapse(true);\n            range.moveEnd(\'character\', end);\n            range.moveStart(\'character\', start);\n            range.select();\n        }\n    });\n};\n\nArray.prototype.unique = function() {\n    var r = new Array();\n    o:for (var i = 0, n = this.length; i < n; i++) {\n        for (var x = 0, y = r.length; x < y; x++) {\n            if (r[x]==this[i]) {\n                continue o;\n            }\n        }\n        r[r.length] = this[i];\n    }\n\n    return r;\n};\n\nfunction ied_goToLine()\n{\n    var line = parseInt(jQuery(\'#ied_plugin_jumpToLine\').val());\n    var ied_ed = jQuery(\'#plugin_editor\');\n    var ied_edd = document.getElementById(\'plugin_editor\'); // Dunno how to convert a jQuery obj back to DOM\n    var lines = ied_ed.val().split(\'\\\\n\');\n    var numchars = 0;\n    var count = 0;\n    var findstr = \'\';\n    jQuery.each(lines, function () {\n        count++;\n        if (count >= line) {\n            findstr = this;\n\n\n            return false;\n        }\n        numchars += (this.length)+2; // Don\'t ask. +2 is something to do with line endings I think\n    });\n\n    // Find the line containing the string we found. Start counting from the line before.\n    // Those pesky line endings come into play again so we need to subtract the number\n    // of lines found from the start character position *shrug*\n    start = ied_ed.val().indexOf(findstr, numchars-count);\n    start = (findstr == \'\') ? start+1 : start;\n    end = start+findstr.length;\n    ied_ed.selectRange(end-1, end);\n\n    if (document.createEvent) {\n        var ied_theCode = ied_ed.val().charCodeAt(end-1);\n        if (window.KeyEvent) {\n            var ev = document.createEvent(\'KeyEvents\');\n            ev.initKeyEvent(\'keypress\', false, true, window, false, false, false, false, 0, ied_theCode);\n        } else {\n            var ev = document.createEvent(\'UIEvents\');\n            ev.initUIEvent(\'keypress\', false, true, window, 0);\n            ev.keyCode = ied_theCode;\n        }\n        ied_edd.dispatchEvent(ev); // cause scroll to cursor by replacing last char with itself\n    }\n    ied_ed.selectRange(start, end);\n\n    return false;\n}\nfunction ied_plugin_toggle_msgpop(state)\n{\n    var obj = jQuery(\"#ied_plugin_msgpop\");\n    if (state != undefined) {\n        if (state == 1) {\n            obj.show(\'normal\');\n        } else {\n            obj.hide(\'normal\');\n        }\n    } else {\n        obj.toggle(\'normal\');\n    }\n}\nfunction ied_plugin_rtrim(str, chars)\n{\n    chars = chars || \"\\s\";\n\n    return str.replace(new RegExp(\"[\" + chars + \"]+$\", \"g\"), \"\");\n}\nfunction ied_plugin_update_tp_count()\n{\n    var tp_count = tp_warns = tp_has_content = 0;\n\n    jQuery(\'#options_group_pack ul li\').each(function() {\n        var self = jQuery(this);\n        tp_count++;\n        if (self.find(\'input\').val() !== \'\') {\n            tp_has_content++;\n        }\n        if (self.find(\'label\').hasClass(\'warning\')) {\n            tp_warns++;\n        }\n    });\n    jQuery(\'#ied_plugin_tp_count\').empty().append(\'(\' +tp_count+ \' | \' +tp_has_content+ \' | \' +tp_warns+ \')\');\n    jQuery(\'#ied_plugin_tp_lang option\').find(\':selected\').data(\'string-count\', tp_has_content);\n\n    // Update the global var for use when loading strings\n    ied_plugin_tp_total = tp_count;\n}\n\njQuery(function () {\n    curh = getCookie(\'ied_plugin_edheight\');\n    curh = (curh == null) ? \'480\' : curh;\n    jQuery(\'textarea[maxlength]\').keyup(function () {\n        var max = parseInt(jQuery(this).attr(\'maxlength\'));\n        if (jQuery(this).val().length > max) {\n            jQuery(this).val(jQuery(this).val().substr(0, jQuery(this).attr(\'maxlength\')));\n        }\n        jQuery(this).parent().find(\'.ied_plugin_charsRemain\').html(\'Chars remaining: \'+ (max - jQuery(this).val().length));\n    });\n    jQuery(\'textarea[maxlength]\').keyup();\n    jQuery(\'#ied_plugin_jumpToLine\').keydown(function (e) {\n        var code = (e.keyCode ? e.keyCode : e.which);\n        if (code == 13) {\n            e.preventDefault();\n            e.stopPropagation();\n            ied_goToLine();\n\n\n            return false;\n        }\n    });\n\n    // Store the prefix\n    jQuery(\'#ied_plugin_tp_prefix\').blur(function () {\n        var pfx = jQuery(this).val();\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'set_tp_prefix\',\n            plugin: \'{$name}\',\n            prefix: pfx\n        });\n    });\n\n    // Find all occurrences of gTxt(\'something\')\n    jQuery(\'#plugin_editor, #ied_plugin_tp_prefix\').blur(function () {\n        var ied_tp_pfx = jQuery(\'#ied_plugin_tp_prefix\').val();\n        if (ied_tp_pfx != \'\') {\n            var ied_gtxt_re = /gTxt\\([\\\'\\\"]([a-zA-Z0-9_]*?)[\\\"\\\'][,\\)]/gi;\n            var ied_tp_ta = jQuery(\'#plugin_editor\').val().replace(/\\s*/g,\'\'); // Strip spaces to make lookups easier\n            var ied_tp_items = ied_tp_ta.match(ied_gtxt_re);\n\n            // if JS RegExp captured parenthical expressions in global searches or it was easy to inject variables\n            // into new RegExp() calls, this loop could be avoided\n            var ied_tp_used = [];\n            for (var idx = 0; idx < ied_tp_items.length; idx++) {\n                var pos = ied_tp_items[idx].lastIndexOf(\"\'\");\n                pos = (pos == -1) ? ied_tp_items[idx].lastIndexOf(\'\"\') : pos;\n                tpstr = ied_tp_items[idx].substr(6,pos-6);\n                if (tpstr.indexOf(ied_tp_pfx) == 0) {\n                    ied_tp_used[ied_tp_used.length] = tpstr;\n                }\n            }\n\n            ied_tp_used = ied_tp_used.unique();\n\n            // List of all current textpack strings in use (as of last Save operation)\n            var ied_tp_curr = [];\n            jQuery(\'#options_group_pack ul label\').each(function () {\n                ied_tp_curr[ied_tp_curr.length] = jQuery(this).text();\n            });\n\n            // Iterate over current array and check if each name is in the used textpack item list.\n            // If it is, remove it from the final list.\n            for (var idx = 0; idx < ied_tp_curr.length; idx++) {\n                if ((pos = jQuery.inArray(ied_tp_curr[idx], ied_tp_used)) > -1) {\n                    ied_tp_used.splice(pos, 1);\n                    jQuery(\'#options_group_pack ul label:contains(\'+ied_tp_curr[idx]+\')\').toggleClass(\'warning\', false).next(\".destroy\").remove();\n                } else {\n                    setclass = 1;\n                    jQuery(\'#options_group_pack ul label:contains(\'+ied_tp_curr[idx]+\')\').toggleClass(\'warning\', true).next(\".destroy\").remove().end().after(\'<a href=\"#\" class=\"destroy\"><span class=\"ui-icon ui-icon-close\">Delete</span></a>\');\n                }\n            }\n            // For each remaining item that has been used, add an input box\n            // TODO: i18n the select options\n            for (var idx = 0; idx < ied_tp_used.length; idx++) {\n                jQuery(\'#options_group_pack ul\').prepend(\'<li><input type=\"text\" name=\"textpack_\'+ied_tp_used[idx]+\'\" value=\"\" /> <select name=\"ied_plugin_tp_event\"><option value=\"admin\">Admin</option><option value=\"public\">Public</option><option value=\"common\">Both</option></select> <label>\'+ied_tp_used[idx]+\'</label></li>\');\n            }\n            ied_plugin_update_tp_count();\n        }\n    }).blur();\n\n    // Handle adding new strings manually\n    jQuery(\'#ied_plugin_add_string\').click(function (ev) {\n        jQuery(\'#options_group_pack ul\').before(\'<div id=\"ied_plugin_new_container\"><label>\'+jQuery(\'#ied_plugin_tp_prefix\').val()+\'_<input type=\"text\" name=\"ied_plugin_tp_newname\" id=\"ied_plugin_tp_newname\" value=\"\" /></label></div>\');\n        jQuery(\'#ied_plugin_tp_newname\').focus();\n        ev.preventDefault();\n    });\n    jQuery(document).on(\'blur\', \'#ied_plugin_tp_newname\', function () {\n        var newname = ied_plugin_rtrim(jQuery(\'#ied_plugin_tp_prefix\').val()+\'_\'+jQuery(\'#ied_plugin_tp_newname\').val(), \'_\');\n        var newok = true;\n        jQuery(\'#options_group_pack ul li label\').each(function () {\n            if (jQuery(this).text() == newname) {\n                jQuery(\'#ied_plugin_tp_newname\').css(\'color\', \'#E00\');\n                newok = false;\n            }\n        });\n        // TODO: i18n select option text\n        if (newok) {\n            jQuery(\'#options_group_pack ul\').prepend(\'<li><input type=\"text\" name=\"textpack_\'+newname+\'\" value=\"\" /> <select name=\"ied_plugin_tp_event\"><option value=\"admin\">Admin</option><option value=\"public\">Public</option><option value=\"common\">Both</option></select> <label>\'+newname+\'</label></li>\');\n            jQuery(\'#ied_plugin_new_container\').remove();\n            jQuery(\'input[name=\"textpack_\'+newname+\'\"]\').focus();\n        }\n        ied_plugin_update_tp_count();\n    });\n\n    // Initialise the generic AJAX error handler\n    jQuery(\'.ied_editForm\').ajaxError(function (event, request, settings) {\n        var xhr = jQuery(request.responseText);\n\n        // phpdoc generation barfed\n        if (settings.data.indexOf(\'step=ied_plugin_generate_phpdoc\') > -1) {\n            var msgContent = jQuery(\"#ied_plugin_msgpop .ied_plugin_msgpop_content\");\n            status = xhr.find(\'http-status\').attr(\'value\')\n            if (status == \'200 OK\') {\n                msgContent.append(xhr.find(\'ied_plugin_phpdoc\').attr(\'value\'));\n            } else if (status == \'501 Not Implemented\') {\n                msgContent.append(xhr.find(\'error_msg\').attr(\'value\'));\n            }\n            ied_plugin_toggle_msgpop(\'1\');\n        }\n\n        // code save barfed\n        if (settings.data.indexOf(\'step=ied_plugin_code_save\') > -1) {\n            var msg = xhr.find(\'ied_plugin_msg\').attr(\'value\');\n            var line = xhr.find(\'ied_plugin_err_line\').attr(\'value\');\n            jQuery(\'#ied_plugin_jumpToLine\').val(line);\n            ied_goToLine();\n            var codeobj = jQuery(\"#plugin_editor\");\n            codeobj.css({\'opacity\': \'.75\'}); // Reduced opacity as a visual cue that something\'s wrong\n            eval(msg); // bleurgh!\n        }\n    });\n\n    // Handle \'x\' button\n    jQuery(document).on(\'click\', \'.destroy\', function (event) {\n        var elem = jQuery(this).prev(\'label\');\n        var tp_lbl = elem.text();\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'textpack_del\',\n            ied_tp_lbl: tp_lbl\n        });\n\n        elem.parent().remove();\n        event.preventDefault();\n        ied_plugin_update_tp_count();\n    });\n\n    // Save textpack string to database\n    function ied_plugin_tp_save(event)\n    {\n        var elem = jQuery(this);\n        var isSel = elem.is(\'select\');\n        var tp_lbl = elem.nextAll(\'label\').text();\n        var tp_str = (isSel) ? elem.prevAll(\'input\').val() : elem.val();\n        var tp_ev = (isSel) ? elem.val() : elem.nextAll(\'select\').val();\n        var tp_evt = (tp_ev==\'public\' || tp_ev==\'common\') ? tp_ev : jQuery(\'#ied_plugin_tp_prefix\').val();\n        var tp_lng = jQuery(\'#ied_plugin_tp_lang\').val();\n        var tp_own = jQuery(\'#ied_plugin_tp_prefix\').val();\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'textpack_save\',\n            ied_tp_evt: tp_evt,\n            ied_tp_lbl: tp_lbl,\n            ied_tp_lng: tp_lng,\n            ied_tp_str: tp_str,\n            ied_tp_own: tp_own\n        });\n    }\n\n    // Handle saving textpack string\n    jQuery(document).on(\'blur\', \'#options_group_pack ul li input\', ied_plugin_tp_save);\n    jQuery(document).on(\'change\', \'#options_group_pack ul li select\', ied_plugin_tp_save);\n\n    // Handle language change.\n    jQuery(\"#ied_plugin_tp_lang\").change(function (event) {\n        jQuery(\'#ied_plugin_tp_load_count\').empty().show();\n\n        var tp_lng = jQuery(this).val();\n        var tp_dflt = jQuery(\'#ied_plugin_tp_lang_dflt\').val();\n        var sel = \'#options_group_pack ul li\';\n        var numStrings = sel.length;\n        var numFetched = 0;\n\n        jQuery(sel).each(function () {\n            var obj = jQuery(this);\n            var tp_lbl = obj.find(\'label\').text();\n            var tp_dest = obj.find(\'input\');\n\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'textpack_get\',\n                ied_tp_lbl: tp_lbl,\n                ied_tp_lng: tp_lng,\n                ied_tp_dflt: tp_dflt\n            }, function (data) {\n                numFetched++;\n                var theVal = data.ied_plugin_tp_string;\n                var xl8str = data.ied_plugin_tp_dflt;\n\n                tp_dest.val(theVal);\n\n                if (xl8str == undefined || xl8str == \'\') {\n                    obj.removeAttr(\'title\');\n                } else {\n                    obj.attr(\'title\', xl8str);\n                }\n                if (numFetched < ied_plugin_tp_total) {\n                    jQuery(\'#ied_plugin_tp_load_count\').text(numFetched + \'/\' + ied_plugin_tp_total);\n                } else {\n                    jQuery(\'#ied_plugin_tp_load_count\').text(\'OK\').hide(\'slow\');\n                    ied_plugin_update_tp_count();\n                }\n            },\n            \'json\');\n        });\n    });\n\n    // Current language refresh.\n    jQuery(\'#ied_plugin_tp_refresh\').click(function () {\n        // Trigger the change event\n        jQuery(\"#ied_plugin_tp_lang\").change();\n    });\n\n    // Load textpack strings from plugin\'s custom gTxt()\n    jQuery(\"#ied_plugin_tp_load\").click(function (event) {\n        var ied_fn = jQuery(\"#ied_plugin_tp_populate\").val();\n\n\n        jQuery(\'#options_group_pack ul li\').each(function () {\n            var obj = jQuery(this);\n            var tp_lbl = obj.find(\'label\').text();\n            var tp_dest = obj.find(\'input\');\n\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'textpack_load\',\n                ied_tp_fn: ied_fn,\n                ied_tp_lbl: tp_lbl\n            }, function (data) {\n                // Paste the returned string into the input box and save it by invoking blur()\n                tp_dest.val(data.ied_plugin_tp_string).blur();\n            },\n            \'json\');\n        });\n        event.preventDefault();\n    });\n\n    // Handle saving metadata.\n    jQuery(\"#ied_plugin_meta_save\").click(function(event) {\n        var msgarea = jQuery(\"#ied_plugin_messages\");\n        msgarea.empty();\n        var metaobj = jQuery(\"#options_group_meta\");\n        var datablock = {};\n\n        jQuery(metaobj).find(\'input[type=\"text\"], select\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\');\n            datablock[objid] = obj.val();\n        });\n\n        jQuery(metaobj).find(\'input[type=\"radio\"], input[type=\"checkbox\"]\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\').replace(/\\[\\]/, \'\');\n            if (typeof datablock[objid] == \'undefined\') {\n                datablock[objid] = [];\n            }\n            if (obj.prop(\'checked\') === true) {\n                datablock[objid].push(obj.val());\n            }\n        });\n\n        jQuery(metaobj).find(\'select\').each(function() {\n            var obj = jQuery(this);\n            var objid = obj.attr(\'name\');\n            if (obj.prop(\'selected\') === true) {\n                datablock[objid] = obj.val();\n            }\n        });\n        var plugin = \'{$name}\';\n\n        metaobj.css(\'opacity\', \'0.4\');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'meta_save\',\n            plugin: plugin,\n            data: datablock\n        }, function(data) {\n            metaobj.css({\'opacity\': \'1\'});\n            var msg = jQuery(data).find(\'ied_plugin_msg\').attr(\'value\');\n            eval(msg); // yuk!\n        });\n\n        event.preventDefault();\n    });\n\n    // Handle saving code.\n    jQuery(\"#ied_plugin_code_save\").click(function (event) {\n        var msgarea = jQuery(\"#ied_plugin_messages\");\n        msgarea.empty();\n        var codeobj = jQuery(\"#plugin_editor\");\n        var codeblock = codeobj.val();\n        var plugin = \'{$name}\';\n\n        codeobj.css(\'opacity\', \'0.4\');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'code_save\',\n            plugin: plugin,\n            codeblock: codeblock\n        }, function (data) {\n            codeobj.css({\'opacity\': \'1\'});\n            var msg = jQuery(data).find(\'ied_plugin_msg\').attr(\'value\');\n            eval(msg); // yuk!\n        });\n\n        event.preventDefault();\n    });\n\n    // Handle generating phpdoc\n    jQuery(\"#ied_plugin_btn_phpdoc\").click(function (event) {\n        var msgarea = jQuery(\"#ied_plugin_msgpop\");\n        var msgContent = jQuery(\"#ied_plugin_msgpop .ied_plugin_msgpop_content\");\n        msgContent.empty();\n        var fnobj = jQuery(\"#ied_plugin_to_phpdoc\");\n        var fn = fnobj.val();\n        var plugin = \'{$name}\';\n\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'generate_phpdoc\',\n            plugin: plugin,\n            fn: fn\n        }, function (data) {\n            msgContent.append(jQuery(data).find(\'ied_plugin_phpdoc\').attr(\'value\'));\n            ied_plugin_toggle_msgpop(\'1\');\n        });\n\n        event.preventDefault();\n    });\n\n    jQuery(\'#ied_plugin_tp_oplangs\').change(function () {\n        sel = jQuery(\'#ied_plugin_tp_oplangs option:selected\').map(function () { return this.value }).get().join(\', \');\n        sendAsyncEvent(\n        {\n            event: textpattern.event,\n            step: \'lang_set\',\n            ied_tp_langsel: sel\n        });\n    });\n\n});\nEOJS\n            )\n        .n. \'</div>\';\n    }\n\n    /**\n     * Save the plugin to the DB or filesystem.\n     */\n    public function save()\n    {\n        extract(doSlash(gpsa(array(\'name\',\'newname\',\'filename\',\'code\',\'author\',\'author_uri\',\'version\',\'description\',\'help\',\'css\',\'status\',\'type\',\'load_order\',\'rename_file\',\'restore_point\',\'ied_plugin_tp_prefix\'))));\n        $flags = gps(\'flags\');\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n        $extraMsg = $newfilename = $msg1 = $msg2 = \'\';\n        $newname = trim($newname);\n        if ($flags) {\n            $flagout = 0;\n            foreach ($flags as $flag) {\n                $flagout |= $flag;\n            }\n            $flags = $flagout;\n        }\n\n        if (empty($newname)) {\n            $msg1 = gTxt(\'ied_plugin_name_first\');\n            $msgType = E_ERROR;\n        } else {\n            if (empty($filename)) {\n                $hout = array();\n                $hout[0] = $help;\n                if ($css) { $hout[1] = n.$start_css.n.$css.n.$end_css; }\n                safe_update(\n                    \'txp_plugin\',\n                    \"name=\'\".$newname.\"\',\n                    status = \".intval($status).\",\n                    type = \".intval($type).\",\n                    author = \'\".$author.\"\',\n                    author_uri = \'\".$author_uri.\"\',\n                    version = \'\".$version.\"\',\n                    description = \'\".$description.\"\',\n                    help = \'\".implode(\'\',$hout).\"\',\n                    code = \'\".$code.\"\',\n                    flags = \".intval($flags).\",\n                    \".(($restore_point == 1)? \"code_restore = \'\".$code.\"\',\" : \'\').\"\n                    load_order = \".$load_order,\n                    \"name = \'\".$name.\"\'\");\n                $msg1 = gTxt(\'plugin_saved\', array(\'{name}\' => $newname));\n                $msgType = \'\';\n            } else {\n                $pcd = get_pref(\'plugin_cache_dir\');\n                $dir = $pcd.DS;\n\n                if (empty($pcd)) {\n                    $filecontent = \'\';\n                    $msg2 = gTxt(\'ied_plugin_cache_not_set\');\n                } else {\n                    $ioo = get_pref(\'ied_plugin_output_order\');\n                    $oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;\n                    $textpack = $this->textpack_build($newname);\n                    $helpchunk = $this->build_template(\'help\', array($help, \' \')); // Use a space to force a CSS hunk in the file\n                    $codechunk = $this->build_template(\'code\', $code);\n                    $filecontent = $this->build_template(\"preamble\")\n                        .$this->build_template(\"name\", $newname)\n                        .$this->build_template(\"html_help\")\n                        .$this->build_template(\"version\", $version)\n                        .$this->build_template(\"author\", $author)\n                        .$this->build_template(\"author_uri\", $author_uri)\n                        .$this->build_template(\"description\", $description)\n                        .$this->build_template(\"load_order\", $load_order)\n                        .$this->build_template(\"type\", $type)\n                        .$this->build_template(\"flags\", $flags)\n                        .$this->build_template(\"textpack\", $textpack)\n                        .$this->build_template(\"include\")\n                        .(($oporder == 0) ? $codechunk : $helpchunk)\n                        .(($oporder == 1) ? $codechunk : $helpchunk)\n                        .$this->build_template(\"postamble\");\n                    $msgType = \'\';\n                }\n\n                $filecontent = $this->make_array($filecontent);\n                $metavars = array(\"name\" => \"$newname\",\n                                        \"version\" => \"$version\",\n                                        \"author\" => \"$author\",\n                                        \"author_uri\" => \"$author_uri\",\n                                        \"description\" => \"$description\",\n                                        \"type\" => \"$type\",\n                                        \"flags\" => \"$flags\",\n                                        \"order\" => \"$load_order\",\n                                );\n                $hunkvars = array(\"CODE\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$code)), // newline workaround\n                            \"HELP\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$help)),\n                            \"CSS\" => doStrip(str_replace(\'\\r\\n\',\'\n\',$css)),\n                                );\n                foreach ($metavars as $varname => $value) {\n                    for ($idx = 0; $idx < count($filecontent); $idx++) {\n                        if (strpos($filecontent[$idx], \'$plugin[\\\'\'.$varname.\'\\\']\') === 0) {\n                            $filecontent[$idx] = \'$plugin[\\\'\'.$varname.\'\\\'] = \'.doQuote($value).\';\';\n                            break;\n                        }\n                    }\n                }\n                foreach ($hunkvars as $varname => $hunk) {\n                    list ($start_delim, $end_delim) = $this->make_markers($varname, $this->ied_plugin_globals[\'dlm_start\'], $this->ied_plugin_globals[\'dlm_end\']);\n                    $start = array_search($start_delim, $filecontent) + 1;\n                    $end = array_search($end_delim, $filecontent);\n                    if (is_numeric($start) && is_numeric($end) && $end >= $start) {\n                        array_splice($filecontent, $start, $end-$start, $hunk);\n                    }\n                }\n                $filecontent = implode(\"\\n\", $filecontent);\n\n                if ($filecontent) {\n                    $fh = fopen($dir.$filename, \'w+\');\n                    fwrite($fh, $filecontent);\n                    fclose($fh);\n                }\n                $msg1 = (empty($msg1)) ? gTxt(\'plugin_saved\', array(\'{name}\' => $newname)) : $msg1;\n                $msgType = \'\';\n\n                // Make new file if required\n                if ($rename_file == 1) {\n                    $fnames = $this->get_name($newname, $version);\n                    $newfilename = $fnames[2];\n                    $res = rename($dir.$filename, $dir.$newfilename);\n                    $extraMsg = ($res) ? gTxt(\'ied_plugin_renamed\') : gTxt(\'ied_plugin_rename_failed\');\n                }\n            }\n\n            // Store the plugin textpack prefix\n            $this->set_tp_prefix($newname, $ied_plugin_tp_prefix);\n        }\n        if ($msg2) {\n            $this->table($msg2);\n        } else {\n            // Check the plugin type matches the code used.\n            $extraMsg .= ($this->admin_check($code, $type)) ? \'\' : strong(gTxt(\'ied_plugin_check_type\'));\n            $message = $msg1.$extraMsg;\n            $this->edit(array($message, $msgType), $newfilename);\n        }\n    }\n\n    /**\n     * Export the plugin as a distributable txt file.\n     */\n    public function save_as_file()\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $rs = safe_row(\'description, author, author_uri, version, code, help, type, load_order, flags\', \'txp_plugin\', \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $zip = gps(\'type\');\n        if (gps(\'trim\')==1) {\n            $code=explode(\"\\r\\n\",$code);\n            $code=array_map(\'trim\',$code);\n            $code=implode(\"\\r\\n\",$code);\n        }\n\n        // Get any textpack strings\n        $textpack = $this->textpack_build($name);\n\n        list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n        $fnames = $this->get_name($name, $version);\n\n        header(\'Content-type: text/plain\');\n        header(\'Content-Disposition: attachment; filename=\' . (($zip === \'zip\') ? $fnames[1] : $fnames[0]));\n\n        $types = array(\'Public\' , \'Admin/Public\' , \'Library\' , \'Admin\', \'Admin/AJAX\', \'Admin/Public/AJAX\'); // No gTxt() because the template is English\n        $plugin[\'name\'] = $name;\n        $plugin[\'author\'] = $author;\n        $plugin[\'author_uri\'] = $author_uri;\n        $plugin[\'version\'] = $version;\n        $plugin[\'description\'] = $description;\n        $plugin[\'code\'] = $code;\n        $plugin[\'type\'] = $type;\n        $plugin[\'order\'] = $load_order;\n        $plugin[\'flags\'] = $flags;\n    //  $plugin[\'allow_html_help\'] = true;\n    //  $plugin[\'help_raw\'] = $help.$start_css.$css.$end_css;\n        $plugin[\'help\'] = $this->textile($name, $help, $css, $start_css, $end_css);\n        $plugin[\'md5\'] = md5( $plugin[\'code\'] );\n        if ($textpack) {\n            $plugin[\'textpack\'] = $textpack;\n        }\n\n        echo \'# Name: \'.$name.\' v\'.$version.\' \'.(($zip === \'zip\') ? \"(compressed)\" : \"\").\'\n# Type: \'.$types[$type].\' plugin\n# \'.$description.\'\n# Author: \'.$author.\'\n# URL: \'.$author_uri.\'\n# Recommended load order: \'.$load_order.\'\n\n# .....................................................................\n# This is a plugin for Textpattern CMS - http://textpattern.com/\n# To install: textpattern > admin > plugins\n# Paste the following text into the \\\'Install plugin\\\' box:\n# .....................................................................\n\n\'.(($zip === \'zip\') ? chunk_split(base64_encode(gzencode(serialize($plugin))), 72) : chunk_split(base64_encode(serialize($plugin)), 72));\n\n        die();\n    }\n\n    /**\n     * Export the plugin as a plugin template PHP file.\n     */\n    public function save_as_php_file()\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $rs = safe_row(\"description, author, author_uri, version, code, help, type, load_order, flags\", \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n            extract($rs);\n\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin=$this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $ioo = get_pref(\'ied_plugin_output_order\');\n        $oporder = (isset($ioo) && is_numeric($ioo)) ? $ioo : 0;\n        $fnames = $this->get_name($name, $version);\n\n        header(\'Content-type: text/php\');\n        header(\'Content-Disposition: attachment; filename=\' .$fnames[2]);\n\n        $textpack = $this->textpack_build($name);\n        $helpchunk = $this->build_template(\'help\', array($help, $css));\n        $codechunk = $this->build_template(\'code\', str_replace(\"\\r\\n\",\"\\n\",$code));\n\n        echo $this->build_template(\'preamble\').\n            $this->build_template(\'name\', $name).\n            $this->build_template(\'html_help\').\n            $this->build_template(\'version\', $version).\n            $this->build_template(\'author\', $author).\n            $this->build_template(\'author_uri\', $author_uri).\n            $this->build_template(\'description\', $description).\n            $this->build_template(\'load_order\', $load_order).\n            $this->build_template(\'type\', $type).\n            $this->build_template(\'flags\', $flags).\n            $this->build_template(\'textpack\', $textpack).\n            $this->build_template(\'include\').\n            (($oporder == 0) ? $codechunk : $helpchunk).\n            (($oporder == 1) ? $codechunk : $helpchunk).\n            $this->build_template(\'postamble\');\n\n        die();\n    }\n\n    /**\n     * Export only the textpacks from the plugin.\n     */\n    public function save_as_textpack()\n    {\n        if (gps(\'name\')) {\n            $name = $base_name = gps(\'name\');\n            $version = safe_field(\'version\', \"txp_plugin\", \"name=\'\".doSlash($name).\"\'\");\n        } elseif (gps(\'filename\')) {\n            $name = gps(\'filename\');\n            $base_name = str_replace(\'.php\', \'\', $name);\n            $plugin=$this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            $version = $plugin[\'version\'];\n        }\n\n        $langs = gps(\'lang\');\n        if (!$langs) {\n            $langs = get_pref(\'ied_plugin_lang_selected\', \'\');\n        }\n\n        $force = 0;\n        if ($langs == \'\') {\n            $langstr = \'all\';\n            $force = 1;\n        } else {\n            $langlist = do_list($langs);\n            if (count($langlist) == 1) {\n                $langstr = $langlist[0];\n            } else {\n                $country_codes = array();\n                foreach ($langlist as $ln) {\n                    $lparts = do_list($ln, \'-\');\n                    $country_codes[] = $lparts[0];\n                }\n                $langstr = implode(\'+\', array_unique($country_codes));\n            }\n        }\n        $textpack = $this->textpack_build($base_name, $force);\n        $fnames = $this->get_name($name, $version, $langstr);\n\n        header(\'Content-type: text/html; charset=UTF-8\');\n        header(\'Content-Disposition: attachment; filename=\' . $fnames[3]);\n        echo $textpack;\n        die();\n    }\n\n    /**\n     * Create a plugin in standard template format.\n     */\n    public function build_template($prt, $val=\'\')\n    {\n        $css = $help = \'\';\n        if (is_array($val)) {\n            $help = $val[0];\n            $css = isset($val[1]) ? $val[1] : \'\';\n            $val = \'\';\n        }\n        $template = array(\n            \"preamble\" => \'<?php\'.n.n\n                            .\'// This is a PLUGIN TEMPLATE for Textpattern CMS.\'.n.n\n                            .\'// Copy this file to a new name like abc_myplugin.php.  Edit the code, then\'.n\n                            .\'// run this file at the command line to produce a plugin for distribution:\'.n\n                            .\'// $ php abc_myplugin.php > abc_myplugin-0.1.txt\'.n.n,\n            \"name\" => \'// Plugin name is optional.  If unset, it will be extracted from the current\'.n\n                            .\'// file name. Plugin names should start with a three letter prefix which is\'.n\n                            .\'// unique and reserved for each plugin author (\"abc\" is just an example).\'.n\n                            .\'// Uncomment and edit this line to override:\'.n\n                            .\'$plugin[\\\'name\\\'] = \'.doQuote(doSlash($val)).\';\'.n.n,\n            \"html_help\" => \'// Allow raw HTML help, as opposed to Textile.\'.n\n                            .\'// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\'.n\n                            .\'// 1 = Plugin help is in raw HTML.  Not recommended.\'.n\n                            .\'# $plugin[\\\'allow_html_help\\\'] = 1;\'.n.n,\n            \"version\" => \'$plugin[\\\'version\\\'] = \'.doQuote($val).\';\'.n,\n            \"flags\" => \'// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\'.n\n                            .\'// Use an appropriately OR-ed combination of these flags.\'.n\n                            .\'// The four high-order bits 0xf000 are available for this plugin\\\'s private use\'.n\n                            .\'if (!defined(\\\'PLUGIN_HAS_PREFS\\\')) define(\\\'PLUGIN_HAS_PREFS\\\', 0x0001); // This plugin wants to receive \"plugin_prefs.{$plugin[\\\'name\\\']}\" events\'.n\n                            .\'if (!defined(\\\'PLUGIN_LIFECYCLE_NOTIFY\\\')) define(\\\'PLUGIN_LIFECYCLE_NOTIFY\\\', 0x0002); // This plugin wants to receive \"plugin_lifecycle.{$plugin[\\\'name\\\']}\" events\'.n.n\n                            .\'$plugin[\\\'flags\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"textpack\" => \'// Plugin \\\'textpack\\\' is optional. It provides i18n strings to be used in conjunction with gTxt().\'.n\n                            .\'// Syntax:\'.n\n                            .\'// ## arbitrary comment\'.n\n                            .\'// #@event\'.n\n                            .\'// #@language ISO-LANGUAGE-CODE\'.n\n                            .\'// abc_string_name => Localized String\'.n.n\n                            .(($val)\n                                ? \'$plugin[\\\'textpack\\\'] = <<<EOT\'.n\n                                    .$val.n\n                                    .\'EOT;\'.n.n\n                                : \'/** Uncomment me, if you need a textpack\'.n\n                                    .\'$plugin[\\\'textpack\\\'] = <<< EOT\'.n\n                                    .\'#@admin\'.n\n                                    .\'#@language en-gb\'.n\n                                    .\'abc_sample_string => Sample String\'.n\n                                    .\'abc_one_more => One more\'.n\n                                    .\'#@language de-de\'.n\n                                    .\'abc_sample_string => Beispieltext\'.n\n                                    .\'abc_one_more => Noch einer\'.n\n                                    .\'EOT;\'.n\n                                    .\'**/\'.n\n                                    .\'// End of textpack\'.n.n),\n            \"author\" => \'$plugin[\\\'author\\\'] = \'.doQuote(doSlash($val)).\';\'.n,\n            \"author_uri\" => \'$plugin[\\\'author_uri\\\'] = \'.doQuote(doSlash($val)).\';\'.n,\n            \"description\" => \'$plugin[\\\'description\\\'] = \'.doQuote(doSlash($val)).\';\'.n.n,\n            \"load_order\" => \'// Plugin load order:\'.n\n                            .\'// The default value of 5 would fit most plugins, while for instance comment\'.n\n                            .\'// spam evaluators or URL redirectors would probably want to run earlier\'.n\n                            .\'// (1...4) to prepare the environment for everything else that follows.\'.n\n                            .\'// Values 6...9 should be considered for plugins which would work late.\'.n\n                            .\'// This order is user-overrideable.\'.n\n                            .\'$plugin[\\\'order\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"type\" => \'// Plugin \\\'type\\\' defines where the plugin is loaded\'.n\n                            .\'// 0 = public              : only on the public side of the website (default)\'.n\n                            .\'// 1 = public+admin        : on both the public and admin side\'.n\n                            .\'// 2 = library             : only when include_plugin() or require_plugin() is called\'.n\n                            .\'// 3 = admin               : only on the admin side (no AJAX)\'.n\n                            .\'// 4 = admin+ajax          : only on the admin side (AJAX supported)\'.n\n                            .\'// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\'.n\n                            .\'$plugin[\\\'type\\\'] = \'.doQuote($val).\';\'.n.n,\n            \"include\" => \'if (!defined(\\\'txpinterface\\\'))\'.n\n                            .\'        @include_once(\\\'zem_tpl.php\\\');\'.n.n,\n            \"help\" => \'if (0) {\'.n\n                            .\'?>\'.n\n                            . (($css) ? \'<!--\'.n\n                            .\'# --- BEGIN PLUGIN CSS ---\'.n\n                            .$css.n\n                            .\'# --- END PLUGIN CSS ---\'.n\n                            .\'-->\'.n : \'\')\n                            .\'<!--\'.n\n                            .\'# --- BEGIN PLUGIN HELP ---\'.n\n                            .$help.n\n                            .\'# --- END PLUGIN HELP ---\'.n\n                            .\'-->\'.n\n                            .\'<?php\'.n\n                            .\'}\'.n,\n            \"code\" => \'# --- BEGIN PLUGIN CODE ---\'.n\n                            .$val.n\n                            .\'# --- END PLUGIN CODE ---\'.n,\n            \"postamble\" => \'?>\',\n        );\n\n        return (array_key_exists($prt, $template) ? $template[$prt] : \'\');\n    }\n\n    /**\n     * Create / upload / install a new plugin.\n     */\n    public function create()\n    {\n        extract(doSlash(gpsa(array(\'name\', \'plugin_create\', \'plugin_upload\', \'plugin_install\', \'ied_plugin_autoenable\'))));\n\n        if ($plugin_create) {\n            $fname = \'\';\n            if ($name == \'\') {\n                $this->table(array(gTxt(\'ied_plugin_name_first\'), E_ERROR));\n\n\n                return;\n            }\n            if (strpos($name, \".php\") !== false) {\n                $fname = $name;\n                $name = basename($name, \".php\");\n            }\n            $exists = fetch(\'name\', \'txp_plugin\', \'name\', $name);\n\n            // MySQL defaults to set status=1 so we need to explicitly override this if this\n            // is a fresh installation\n            $state = ($ied_plugin_autoenable==1) ? 1 : 0;\n\n\n            if (!$exists) {\n                if ($fname) {\n                    // Put plugin in cache dir by faking a POST submission and saving\n                    $_POST[\'filename\'] = $fname;\n                    $_POST[\'newname\'] = $name;\n                    $_POST[\'status\'] = 1; // Cache_dir plugins are always on so this setting is ignored\n                    $_POST[\'type\'] = 0;\n                    $_POST[\'version\'] = \'0.1\';\n                    $_POST[\'load_order\'] = 5;\n                    $_POST[\'flags\'] = 0;\n                    $this->save();\n                } else {\n                    safe_insert(\"txp_plugin\", \"\n                        name=\'$name\',\n                        status=$state,\n                        type=0,\n                        version=\'0.1\',\n                        load_order=5,\n                        flags=0,\n                        description=\'\',\n                        help=\'\',\n                        code=\'\',\n                        code_restore=\'\'\n                    \");\n                    $this->edit(gTxt(\'ied_plugin_edit_new\'), $name);\n                }\n            } else {\n                $this->table(array(gTxt(\'plugin\').\' <strong>\'.$name.\'</strong> \'.gTxt(\'already_exists\'), E_ERROR));\n            }\n\n        } elseif ($plugin_upload) {\n            list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n\n            if (!$_FILES[\'thefile\'][\'tmp_name\']) {\n                $this->table(array(gTxt(\'ied_plugin_choose_file\'), E_ERROR));\n\n                return;\n            }\n\n            $info = explode (\'.\',$_FILES[\'thefile\'][\'name\']);\n            $lastpart = count($info)-1;\n            $ext = $info[$lastpart];\n\n            if ($ext == \'php\') {\n                $plugin = $this->read_file($_FILES[\'thefile\'][\'tmp_name\']);\n//  $newname = (empty($plugin[\'name\'])) ? basename($_FILES[\'thefile\'][\'name\'], \'.php\') : $plugin[\'name\'];\n                $newname = ($name) ? $name : doSlash($plugin[\'name\']);\n\n                if (empty($plugin[\'code\'])) {\n                    $plugin[\'code\'] = file_get_contents($_FILES[\'thefile\'][\'tmp_name\']);\n                }\n\n                extract(doSlash($plugin));\n\n                $md5 = md5($code);\n                $version = ($version) ? $version : \"0.1\";\n                $help = $help.(($css) ? n.$start_css.n.$css.n.$end_css : \'\');\n                $exists = fetch(\'name\', \'txp_plugin\', \'name\', $newname);\n\n                // MySQL defaults to set status=1 so we need to explicitly override this if this\n                // is a fresh installation\n                $state = ($ied_plugin_autoenable==1) ? \'status=1,\' : ($exists && ($ied_plugin_autoenable==2) ? \'\' : \'status=0,\');\n\n                if ($exists) {\n                    // Note: status omitted so it retains its value\n                    $rs = safe_update(\n                        \"txp_plugin\",\n                        \"$state\n                        type = \".intval($type).\",\n                        author = \'$author\',\n                        author_uri = \'$author_uri\',\n                        version = \'$version\',\n                        description = \'$description\',\n                        help = \'$help\',\n                        code = \'$code\',\n                        code_restore = \'$code\',\n                        code_md5 = \'$md5\',\n                        flags = \".intval($flags).\",\n                        load_order = \".intval($load_order),\n                        \"name = \'$newname\'\"\n                    );\n                } else {\n                    $rs = safe_insert(\n                        \"txp_plugin\",\n                        \"name = \'$newname\',\n                        $state\n                        type = \".intval($type).\",\n                        author = \'$author\',\n                        author_uri = \'$author_uri\',\n                        version = \'$version\',\n                        description = \'$description\',\n                        help = \'$help\',\n                        code = \'$code\',\n                        code_restore = \'$code\',\n                        code_md5 = \'$md5\',\n                        flags = \".intval($flags).\",\n                        load_order = \".intval($load_order)\n                    );\n                }\n\n                $msg = ($exists) ? gTxt(\'ied_plugin_updated\', array(\'{name}\' => $newname)) : gTxt(\'ied_plugin_uploaded\', array(\'{name}\' => $newname));\n                $this->table($msg, $newname);\n\n            } elseif ($ext === \'txt\') {\n                $plugin64 = file_get_contents($_FILES[\'thefile\'][\'tmp_name\']);\n                $ret = $this->install($plugin64);\n\n                if ($ret[\'err\'] == \'\') {\n                    $this->table($ret[\'msg\'], $ret[\'nam\']);\n              } else {\n                    $this->table(array($ret[\'msg\'], $ret[\'err\']));\n                }\n            }\n\n        } elseif ($plugin_install) {\n            $ret = $this->install(ps(\'plugin64\'));\n            if ($ret[\'err\'] == \'\') {\n                $this->table($ret[\'msg\'], $ret[\'nam\']);\n          } else {\n                $this->table(array($ret[\'msg\'], $ret[\'err\']));\n            }\n        }\n    }\n\n    /**\n     * Install plugin from distributable txt file.\n     *\n     * Frankensteined shamelessly from include/txp_plugin.php\n     *\n     * @param  string $plugin The raw base64-encoded/gzipped plugin block\n     */\n    public function install($plugin = \'\')\n    {\n        if (strpos($plugin, \'$plugin=\\\'\') !== false) {\n            @ini_set(\'pcre.backtrack_limit\', \'1000000\');\n            $plugin = preg_replace(\'@.*\\$plugin=\\\'([\\w=+/]+)\\\'.*@s\', \'$1\', $plugin);\n        }\n\n        $plugin = preg_replace(\'/^#.*$/m\', \'\', $plugin);\n\n        if (trim($plugin)) {\n            $plugin = base64_decode($plugin);\n\n            if (strncmp($plugin, \"\\x1F\\x8B\", 2) === 0) {\n                $plugin = gzinflate(substr($plugin, 10));\n            }\n\n            if ($plugin = @unserialize($plugin)) {\n                if (is_array($plugin)) {\n                    extract($plugin);\n\n                    $type  = empty($type)  ? 0 : min(max(intval($type), 0), 5);\n                    $order = empty($order) ? 5 : min(max(intval($order), 1), 9);\n                    $flags = empty($flags) ? 0 : intval($flags);\n\n                    $exists = fetch(\'name\', \'txp_plugin\', \'name\', $name);\n\n                    if (isset($help_raw) && empty($plugin[\'allow_html_help\'])) {\n                        // Default: help is in Textile format.\n                        include_once txpath.\'/lib/classTextile.php\';\n                        $textile = new Textile();\n                        $help = $textile->TextileRestricted($help_raw, 0, 0);\n                    }\n\n                    // MySQL defaults to set status=1 so we need to explicitly override this if this\n                    // is a fresh installation.\n                    $ied_plugin_autoenable = ps(\'ied_plugin_autoenable\');\n                    $state = ($ied_plugin_autoenable==1) ? \'status=1,\' : ($exists && ($ied_plugin_autoenable==2) ? \'\' : \'status=0,\');\n\n                    if ($exists) {\n                        $rs = safe_update(\n                           \"txp_plugin\",\n                            \"$state\n                            type         = $type,\n                            author       = \'\".doSlash($author).\"\',\n                            author_uri   = \'\".doSlash($author_uri).\"\',\n                            version      = \'\".doSlash($version).\"\',\n                            description  = \'\".doSlash($description).\"\',\n                            help         = \'\".doSlash($help).\"\',\n                            code         = \'\".doSlash($code).\"\',\n                            code_restore = \'\".doSlash($code).\"\',\n                            code_md5     = \'\".doSlash($md5).\"\',\n                            flags        = $flags\",\n                            \"name        = \'\".doSlash($name).\"\'\"\n                        );\n\n                    } else {\n\n                        $rs = safe_insert(\n                           \"txp_plugin\",\n                           \"name         = \'\".doSlash($name).\"\',\n                            $state\n                            type         = $type,\n                            author       = \'\".doSlash($author).\"\',\n                            author_uri   = \'\".doSlash($author_uri).\"\',\n                            version      = \'\".doSlash($version).\"\',\n                            description  = \'\".doSlash($description).\"\',\n                            help         = \'\".doSlash($help).\"\',\n                            code         = \'\".doSlash($code).\"\',\n                            code_restore = \'\".doSlash($code).\"\',\n                            code_md5     = \'\".doSlash($md5).\"\',\n                            load_order   = \'\".$order.\"\',\n                            flags        = $flags\"\n                        );\n                    }\n\n                    if ($rs and $code) {\n                        if (!empty($textpack)) {\n                            install_textpack($textpack, false);\n                        }\n\n                        if (ps(\'ied_plugin_installopts\') && ($flags & PLUGIN_LIFECYCLE_NOTIFY) ) {\n                            load_plugin($name, true);\n                            $message = callback_event(\"plugin_lifecycle.$name\", \'installed\');\n                        }\n\n                        if (empty($message)) $message = gTxt(\'plugin_installed\', array(\'{name}\' => $name));\n\n                        return array(\'msg\' => $message, \'err\' => \'\', \'nam\' => $name);\n                    } else {\n                        return array(\'msg\' => gTxt(\'plugin_install_failed\', array(\'{name}\' => $name)), \'err\' => E_ERROR);\n                    }\n                }\n            }\n        }\n\n        return array(\'msg\' => gTxt(\'bad_plugin_code\'), \'err\' => E_ERROR);\n    }\n\n    /**\n     * Store the state of the togglable panes.\n     */\n    public function save_pane_state()\n    {\n        $panes = array(\'ied_plugin_tp_strings\', \'ied_plugin_code\', \'ied_plugin_docs\', \'ied_plugin_meta\', \'ied_plugin_utils\', \'ied_plugin_cpanel\', \'ied_plugin_dbplugs\', \'ied_plugin_cacheplugs\');\n        $pane = gps(\'pane\');\n        if (in_array($pane, $panes)) {\n            set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'ied_plugin\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n            send_xml_response();\n        } else {\n            send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n        }\n    }\n\n    /**\n     * Get plugin (file)name information.\n     *\n     * Returns an array of (file)names;\n     *  1) the standard plugin\n     *  2) the compressed plugin\n     *  3) the PHP template\n     *  4) the textpack\n     */\n    public function get_name($name, $version = \'\', $lang=\'\')\n    {\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $op = get_pref(\'ied_plugin_output_sfile\');\n        $opc = get_pref(\'ied_plugin_output_sfilec\');\n        $opp = get_pref(\'ied_plugin_output_sfilep\');\n        $opt = get_pref(\'ied_plugin_output_sfilet\');\n\n        $out = array(\n            ( (empty($op)) ? $ied_plugin_prefs[\'ied_plugin_output_sfile\'][\'default\'] : $op ),\n            ( (empty($opc)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilec\'][\'default\'] : $opc ),\n            ( (empty($opp)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilep\'][\'default\'] : $opp ),\n            ( (empty($opt)) ? $ied_plugin_prefs[\'ied_plugin_output_sfilet\'][\'default\'] : $opt ),\n        );\n\n        $from = array(\'{name}\', \'{version}\', \'{lang}\');\n        $to = array($name, $version, $lang);\n\n        foreach ($out as $fidx => $fname) {\n            $fname = str_replace($from, $to, $fname);\n            $out[$fidx] = sanitizeForFile($fname);\n        }\n\n        return $out;\n    }\n\n    /**\n     * Parse a plugin in Standard Textpattern Template format.\n     *\n     * @param  string $filepath Full path to the file to be read\n     */\n    public function read_file($filepath)\n    {\n        $content = file($filepath);\n        $justfile = basename($filepath);\n        $parts = explode (\'.\',$justfile);\n        $ext = array_pop($parts);\n        $ext = ($ext==$justfile) ? \'\' : \'.\'.$ext; // Only assign an extension if one exists\n        $source_lines = count($content);\n        $commentblock = false;\n        $in_textpack = false;\n        $metadata = array(\n                            \'name\'        => \'\',\n                            \'version\'     => \'\',\n                            \'revision\'    => \'\',\n                            \'author\'      => \'\',\n                            \'author_uri\'  => \'\',\n                            \'description\' => \'\',\n                            \'order\'       => \'5\',\n                            \'type\'        => \'\',\n                            \'flags\'       => \'\',\n                            \'textpack\'    => \'\',\n                        );\n        for ($idx=0; $idx < $source_lines; $idx++) {\n            $content[$idx] = rtrim($content[$idx]);\n\n            // Bomb out if we reach the end of the definition area\n            if (strpos( $content[$idx], \'if (!defined(\\\'txpinterface\\\')\' ) === 0) {\n                break;\n            }\n            if (strpos($content[$idx], \'/*\') === 0) {\n                $commentblock = true;\n                continue;\n            }\n            if ( (strpos($content[$idx], \'*/\') === 0) || (strpos($content[$idx], \'**/\') === 0) ) {\n                $commentblock = false;\n                continue;\n            }\n\n            if (strpos($content[$idx], \'EOT;\') === 0) {\n                $in_textpack = false;\n                continue;\n            }\n\n            if ($in_textpack) {\n                $metadata[\'textpack\'] .= $content[$idx].n;\n            }\n\n            if (!$commentblock && strpos($content[$idx], \'$plugin[\') === 0) {\n                // Found a plugin variable so extract it\n                $parts = explode(\" = \", $content[$idx]);\n                $parts[0] = str_replace(\"\'\", \"\", $parts[0]); // Make the match easier!\n                $semicolon = strpos($parts[1], \';\');\n\n                if ($semicolon !== false) {\n                    $parts[1] = substr($parts[1], 0, $semicolon); // Ditto\n                }\n\n                preg_match(\"/plugin\\[(.*)\\]/\", $parts[0], $var); // Extract just the variable name\n\n                if (is_numeric($parts[1])) {\n                    $parts[1] = \"\'\".$parts[1].\"\'\";\n                }\n\n                if (strpos($parts[1], \'<<<\') === false) {\n                    preg_match(\"/.*\'(.*)\'.*/\", $parts[1], $val); // Remove anything outside the quotes (e.g. $revision)\n\n                    if (empty($val)) {\n                        // Try unquoted - may be a constant or two.\n                        preg_match(\"/(.*)/\", $parts[1], $val);\n\n                        if (strtoupper($val[1]) == $val[1]) {\n                            // It\'s a constant so get its value\n                            $val[1] = 0;\n                            $constants = do_list($parts[1], \'|\');\n\n                            foreach ($constants as $constant) {\n                                $val[1] |= (defined($constant)) ? constant($constant) : 0;\n                            }\n                        }\n                    }\n                }\n\n                if ($var[1] === \'revision\' && isset($val[1]) && !empty($val[1])) {\n                    $revparts = explode(\' \', trim($val[1], \'$ \'));\n                    $val[1] = $revparts[count($revparts)-1];\n                    $val[1] = (empty($val[1])) ? \'\' : \'.\' .$val[1];\n                }\n\n                if ($var[1] === \'textpack\') {\n                    $in_textpack = true;\n                    continue;\n                }\n                $metadata[$var[1]] = $val[1];\n            }\n        }\n\n        $metadata[\'name\'] = (empty($metadata[\'name\'])) ? basename($justfile, $ext) : $metadata[\'name\'];\n        $metadata[\'load_order\'] = $metadata[\'order\'];\n        $metadata[\'version\'] .= $metadata[\'revision\'];\n        $metadata[\'help\'] = $this->extract_hunk($content, \'HELP\');\n        $metadata[\'css\']  = $this->extract_hunk($content, \'CSS\' );\n        $metadata[\'code\'] = $this->extract_hunk($content, \'CODE\');\n\n        return $metadata;\n    }\n\n    /**\n     * Pull out a chunk of text from the Standard Template Format.\n     *\n     * Ripped and modded from net-carver\'s zem_tpl template.\n     *\n     * @param  [type]  $content [description]\n     * @param  [type]  $hunk    [description]\n     * @param  string  $cmnt    [description]\n     * @param  boolean $delete  [description]\n     * @return [type]           [description]\n     */\n    public function extract_hunk($content, $hunk, $cmnt=\"#\", $delete=false)\n    {\n        $dlm = explode(\"|\", $cmnt);\n        $dlmStart = $dlm[0];\n        $dlmEnd = (isset($dlm[1])) ? $dlm[1] : \'\';\n        $lines = $this->make_array($content);\n\n        list ($start_delim, $end_delim) = $this->make_markers($hunk, $dlmStart, $dlmEnd);\n        $start = array_search($start_delim, $lines) + 1;\n        $end = array_search($end_delim, $lines);\n\n        // Kludge to get round the delimiter change in v0.83\n        if ($hunk == \'CSS\' && $end === false) {\n            $start_delim = str_replace(\'---\', \'***\', $start_delim);\n            $end_delim = str_replace(\'---\', \'***\', $end_delim);\n            $start = array_search($start_delim, $lines) + 1;\n            $end = array_search($end_delim, $lines);\n        }\n\n        $extracted = array();\n        if (count($lines) > 0 && is_numeric($start) && is_numeric($end) && $end > $start) {\n            $extracted = array_slice($lines, $start, $end-$start);\n            $lineNum = count($extracted)-1;\n            while ($lineNum > 0 && trim($extracted[$lineNum--]) == \"\") {\n                array_pop($extracted);\n            }\n            while (count($extracted) > 0 && trim($extracted[0]) == \"\") {\n                array_shift($extracted);\n            }\n            if ($delete) {\n                $chopped = array_splice($lines, $start-1, $end-$start+2, \"\");\n            }\n        }\n        $remains = $lines;\n        // Check if the CSS section has <style> tags around it; add them if not\n        if ($hunk == \"CSS\") {\n            $numrows = count($extracted);\n            if ($numrows > 1) {\n                if (strpos($extracted[$numrows-1], \'</style>\') === false) {\n                    $extracted[] = \'</style>\';\n                }\n                if (strpos($extracted[0], \'<style \') === false) {\n                    array_unshift($extracted, \'<style type=\"text/css\">\');\n                }\n            }\n        }\n        if ($delete) {\n            return array(trim(implode(\"\\n\", $extracted)), trim(implode(\"\\n\", $remains)));\n        } else {\n            return trim(implode(\"\\n\", $extracted));\n        }\n    }\n\n    /**\n     * Create a trimmed array from a multi-line document.\n     *\n     * @param  [type] $arr [description]\n     * @return [type]      [description]\n     */\n    public function make_array($arr)\n    {\n        if (!is_array($arr)) {\n            $arr = explode(\"\\n\", $arr);\n        }\n\n        $source_lines = count($arr);\n\n        for ($idx=0; $idx < $source_lines; $idx++) {\n            $arr[$idx] = rtrim($arr[$idx]);\n        }\n\n        return $arr;\n    }\n\n    /**\n     * Create start and end markers for the given hunk.\n     *\n     * @param  [type] $hunk  [description]\n     * @param  [type] $start [description]\n     * @param  [type] $end   [description]\n     * @return [type]        [description]\n     */\n    public function make_markers($hunk, $start, $end)\n    {\n        $smarker = $start . str_replace(\"SECTION\", $hunk, $this->ied_plugin_globals[\'start\']) . (($end)?\' \'.$end:\'\');\n        $emarker = $start . str_replace(\"SECTION\", $hunk, $this->ied_plugin_globals[\'end\']) . (($end)?\' \'.$end:\'\');\n\n        return (array($smarker, $emarker));\n    }\n\n    /**\n     * Check if the code smells like an admin-side plugin.\n     *\n     * Ripped and modded from net-carver\'s zem_tpl template.\n     *\n     * @param  string $codeblock The source code\n     * @param  string $type      The plugin\'s type\n     * @return bool              true if the code is a public-side plugin\n     *                           false if it seems to be admin-side\n     */\n    public function admin_check($codeblock, $type)\n    {\n        // Short circuit since we\'re only interested in public plugins.\n        if ($type != \'0\' || strpos($codeblock, \'txpinterface\') === false) {\n            return true;\n        }\n\n        // This is several orders of magnitude quicker than a single preg_match\n        // as plugin size increases.\n        $cb = str_replace(array(\'\\t\', \' \'), \'\', $codeblock);\n        $cb = str_replace(\'===\', \'==\', $cb);\n\n        if (strpos($cb, \'txpinterface==\\\"admin\') ||\n            strpos($cb, \"txpinterface==\\\'admin\") ||\n            strpos($cb, \'admin\\\"==txpinterface\') ||\n            strpos($cb, \"admin\\\'==txpinterface\") ||\n            strpos($cb, \'admin\\\"==@txpinterface\') ||\n            strpos($cb, \"admin\\\'==@txpinterface\")) {\n\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Textile the passed help doc and style info.\n     *\n     * Cache the content if the prefs indicate to do so.\n     *\n     * @param  [type] $name      [description]\n     * @param  [type] $help      [description]\n     * @param  [type] $css       [description]\n     * @param  [type] $start_dlm [description]\n     * @param  [type] $end_dlm   [description]\n     * @return [type]            [description]\n     */\n    public function textile($name, $help, $css, $start_dlm, $end_dlm)\n    {\n        $tmpdir = $hlpfile = \'\';\n        $changed = true;\n        $out = array();\n        $iotc = get_pref(\'ied_plugin_output_tmpcache\');\n\n        if (isset($iotc) && !empty($iotc) && is_writable($iotc)) {\n            $tmpdir = $iotc;\n            $cache = build_file_path($tmpdir, \'ied_plugin_composer.cache\');\n            if (!file_exists($cache)) {\n                touch($cache); // Create file if it doesn\'t exist\n            }\n            $plugs = parse_ini_file($cache);\n\n            $old_md5 = (is_array($plugs) && array_key_exists($name, $plugs)) ? $plugs[$name] : \'\';\n            $curr_md5 = md5($help);\n            $hlpfile = build_file_path($tmpdir, $name.\'_help.txtl\');\n            if ($old_md5 == $curr_md5) {\n                $out[0] = file_get_contents($hlpfile);\n                $changed = false;\n            } else {\n                $plugs[$name] = $curr_md5;\n                $fd = fopen($cache, \"w\");\n                foreach ($plugs as $idx => $val) {\n                    fwrite($fd, $idx.\' = \'.$val.n);\n                }\n                fclose($fd);\n            }\n        }\n\n        if (!$out) {\n            @include_once txpath.\'/lib/classTextile.php\';\n            $out[0] = $help;\n            if (class_exists(\'Textile\')) {\n                // Try and be a little more intelligent about applying textile or not\n                $re = \'/h[0-6](\\(.*\\))?\\./\';\n                if (preg_match($re, $help)) {\n                    $textile = new Textile();\n                    $out[0] = $textile->TextileThis($help);\n                }\n            }\n        }\n\n        if ($tmpdir && $changed) {\n            if (!file_exists($hlpfile)) {\n                touch($hlpfile);\n            }\n            $fd = fopen($hlpfile, \"w\");\n            fwrite($fd, $out[0]);\n            fclose($fd);\n        }\n\n        // Replace the triple \'---\' in the delimiters so the help file can validate\n        $start_dlm = str_replace(\'---\', \'***\', $start_dlm);\n        $end_dlm = str_replace(\'---\', \'***\', $end_dlm);\n\n        $out[1] = ($css) ? \"\\n$start_dlm\\n$css\\n$end_dlm\\n\" : \'\';\n\n        return substr(implode(\'\', $out), 0, $this->ied_plugin_globals[\'size_help\']+$this->ied_plugin_globals[\'size_css\']);\n    }\n\n    /**\n     * Inject any necessary javascript on the page for code helpers.\n     */\n    public function insert_editors()\n    {\n        $out = array();\n        $ed = array();\n        $ced = get_pref(\'ied_plugin_editor\');\n        $hed = get_pref(\'ied_plugin_help_editor\');\n        $cop = get_pref(\'ied_plugin_editor_options\');\n        $hop = get_pref(\'ied_plugin_help_editor_options\');\n        $ed[$ced] = get_pref(\'ied_plugin_editor_path\');\n        $ed[$hed] = get_pref(\'ied_plugin_help_editor_path\');\n\n        $cop = $cop ? \',\'.$cop : \'\';\n        $hop = $hop ? \',\'.$hop : \'\';\n\n        foreach ($ed as $editor => $editor_locs) {\n            $jsop = array();\n            $locs = do_list($editor_locs);\n            foreach ($locs as $loc) {\n                if (strpos($loc, \'css:\') !== false) {\n                    $jsop[] = \'<link rel=\"stylesheet\" href=\"\'.substr($loc, 4).\'\">\';\n                } else {\n                    $jsop[] = \'<script type=\"javascript\" src=\"\'.$loc.\'\"></script>\';\n                }\n            }\n\n            $jsop = implode(n, $jsop);\n\n            switch ($editor) {\n                case \"tiny_mce\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\ntinyMCE.init({\n    mode : \"specific_textareas\",\n    editor_selector : \"mceEditor\"\n    {$hop}\n});\n</script>;\nEOJS;\n                break;\n                case \"edit_area\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\n// initialisation\neditAreaLoader.init({\n    id: \"plugin_editor\",\n    syntax: \"php\"\n    {$cop}\n});\n</script>\nEOJS;\n                break;\n                case \"codemirror\":\n                    $out[] = <<<EOJS\n{$jsop}\n<script type=\"text/javascript\">\nmyTextArea = document.getElementById(\"plugin_editor\");\nvar ied_pc_editor = CodeMirror.fromTextArea(myTextArea, {\n    value: myTextArea.value\n    {$cop}\n});\n</script>\nEOJS;\n                break;\n                case \"codepress\":\n                    $out[] = $jsop;\n                break;\n            }\n        }\n\n        return implode(\"\\n\", $out);\n    }\n\n    /**\n     * Show plugin help documentation.\n     *\n     * If raw, will Textile first.\n     *\n     * @param  string $message [description]\n     * @return [type]          [description]\n     */\n    public function help_viewer($message = \'\')\n    {\n        if (gps(\'name\')) {\n            $name = gps(\'name\');\n            $help = ($name) ? fetch(\'help\',\'txp_plugin\',\'name\',$name) : \'\';\n            list($css,$help) = ($help) ? $this->extract_hunk($help, \"CSS\", \"<!--|-->\", true) : array(\'\',$help);\n        } elseif (gps(\'filename\')) {\n            $plugin = $this->read_file(get_pref(\'plugin_cache_dir\').DS.gps(\'filename\'));\n            extract($plugin);\n        }\n\n        $out = \'\';\n        if (empty($help)) {\n            $out = gTxt(\'ied_plugin_help_not_available\');\n        } else {\n            list ($start_css, $end_css) = $this->make_markers(\"CSS\", $this->ied_plugin_globals[\'css_start\'], $this->ied_plugin_globals[\'css_end\']);\n            $out = $this->textile($name, $help, $css, $start_css, $end_css);\n        }\n\n        pagetop(gTxt(\'ied_plugin_view_help\', array(\'{name}\' => $name)), $message);\n        echo n. \'<div id=\"plugin_container\" class=\"txp-container txp-view\">\'.\n            n. \'<div class=\"text-column\">\' . $out . \'</div>\'.\n            n. \'</div>\';\n    }\n\n    /**\n     * Wrap the passed widget with standard markup.\n     *\n     * @param  string $widget The widget to wrap\n     * @param  string $class  The CSS class to apply to the wrapper\n     * @return string         HTML\n     */\n    public function wrap_widget($widget, $class = \'txp-form-field-value\')\n    {\n        return \'<div class=\"\' . $class . \'\">\'.$widget.\'</div>\';\n    }\n\n    /**\n     * Preferences stub with correct signature for lifecycle callback.\n     *\n     * @param  string $evt Textpattern event\n     * @param  string $stp Textpattern step (action)\n     */\n    public function setup($evt = \'\', $stp = \'\')\n    {\n        $this->prefs();\n    }\n\n    /**\n     * Display the setup / prefs panel.\n     *\n     * @param  string $message Any feedback error / info message to disply\n     * @return string          HTML\n     */\n    public function prefs($message = \'\')\n    {\n        require_privs(\'plugin_prefs.\'.$this->ied_pc_event);\n\n        if (ps(\'ied_plugin_pref_save\')) {\n            $this->prefs_update();\n            $message = gTxt(\'preferences_saved\');\n        }\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        pagetop(gTxt(\'ied_plugin_lbl_setup\'), $message);\n\n        $btnSave = fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\');\n\n        echo \'<div class=\"txp-layout\">\'\n            .n. \'<div class=\"txp-layout-2col\">\'\n            .n. \'<h1 class=\"txp-heading\">\' . gTxt(\'ied_plugin_lbl_setup\') . \'</h1>\'\n            .n. \'</div>\'\n            .n. \'<div class=\"txp-layout-2col\">\'\n            .n. sLink($this->ied_pc_event, \'\', gTxt(\'ied_plugin_list\'), \'ied_plugin_link\')\n            .n. \'</div>\'\n            .n. script_js(<<<EOJS\nvar ied_plugin_path_re = new RegExp(\"^.*[/\\\\]\", \"g\")\nfunction ied_plugin_prefswap(selID, selValue)\n{\n    var id = selID+\'_path\';\n    var nuval = ((basename($(\"#\"+id).val()) == selValue) ? $(\"#\"+id).val() : dirname($(\"#\"+id).val())+selValue);\n\n    if ($(\"#\"+selID)[0].selectedIndex == 0) {\n        $(\"#\"+id).attr(\"disabled\", true);\n    } else {\n        $(\"#\"+id).attr(\"disabled\", false);\n        $(\"#\"+id).val(nuval);\n    }\n}\nfunction basename(path, suffix)\n{\n    return path.replace(ied_plugin_path_re, \'\');\n}\nfunction dirname(path)\n{\n    return path.match(ied_plugin_path_re);\n}\njQuery(function () {\n    jQuery(\".ied_plugin_setup select option:selected\").each(function (obj) {\n        var item = jQuery(this);\n        ied_plugin_prefswap(item.parent().attr(\'id\'), item.val());\n    });\n});\nEOJS\n            );\n\n        $out = array();\n        $out[] = n.\'<div class=\"txp-layout-1col\">\';\n        $out[] = \'<form name=\"ied_pc_prefs\" id=\"ied_pc_prefs\" class=\"txp-prefs-group\" action=\"index.php\" method=\"post\">\';\n        $out[] = eInput($this->ied_pc_event).sInput(\'prefs\');\n\n        $last_grp = \'\';\n\n        foreach ($ied_plugin_prefs as $idx => $prefobj) {\n            if ($last_grp != $prefobj[\'group\']) {\n                $out[] = hed(gTxt($prefobj[\'group\']), 2);\n            }\n            $last_grp = $prefobj[\'group\'];\n            $subout = array();\n            $label = \'<div class=\"txp-form-field-label\">\'\n                    .\'<label>\'.gTxt($idx).\'</label>\'\n                    .\'</div>\';\n            $val = get_pref($idx, $prefobj[\'default\'], 1);\n            $vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'txp-form-field\';\n            switch ($prefobj[\'html\']) {\n                case \'text_input\':\n                    $subout[] = $this->wrap_widget(fInput(\'text\', $idx, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $idx));\n                break;\n                case \'textarea\':\n                    $subout[] = $this->wrap_widget(text_area($idx, \'\', \'\', $val, $idx));\n                    $vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'txp-form-field-textarea\';\n                break;\n                case \'yesnoradio\':\n                    $subout[] = $this->wrap_widget(yesnoRadio($idx, $val));\n                break;\n                case \'radioset\':\n                    $subout[] = $this->wrap_widget(radioSet($prefobj[\'content\'], $idx, $val));\n                break;\n                case \'checkboxset\':\n                    $vals = do_list($val);\n                    $lclout = array();\n                    $cb_count = 0;\n\n                    foreach ($prefobj[\'content\'] as $cb => $val) {\n                        $checked = in_array($cb, $vals);\n                        $cbId = txpspecialchars($idx.\'-\'.$cb_count);\n                        $lclout[] = checkbox($idx . \'[]\', $cb, $checked, \'\', $cbId) . n . \'<label for=\"\' . $cbId . \'\">\' . gTxt($val) . \'</label>\';\n                        $cb_count++;\n                    }\n\n                    $subout[] = $this->wrap_widget(implode(n, $lclout));\n                break;\n                case \'selectlist\':\n                    $subout[] = $this->wrap_widget(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]));\n                break;\n                default:\n                    if (is_callable($prefobj[\'html\'])) {\n                        $subout[] = $this->wrap_widget($prefobj[\'html\']($idx, $val));\n                    }\n                break;\n            }\n            $out[] = tag($label . n.implode(n ,$subout), \'div\', ($vis ? \' class=\"\'.$vis.\'\"\' : \'\'));\n        }\n        $out[] = graf(fInput(\'submit\', \'ied_plugin_pref_save\', gTxt(\'save\'), \'publish\'), array(\'class\' => \'txp-save\'));\n        $out[] = tInput();\n        $out[] = \'</form></div></div>\';\n\n        echo implode(n, $out);\n    }\n\n    /**\n     * Save plugin prefs from setup panel.\n     */\n    public function prefs_update()\n    {\n        global $prefs;\n\n        $ied_plugin_prefs = $this->get_prefs();\n        $saved = ps(\'ied_plugin_pref_save\');\n\n        // Loop through each plugin setting and make sure it\'s in the prefs table, as follows:\n        // 1) if the value has been POSTed from the prefs panel, set the passed value\n        // 2) if the value exists in the prefs (i.e. run on upgrade), set the existing value\n        // 3) if neither exist, set the plugin default value\n        foreach ($ied_plugin_prefs as $key => $prefobj) {\n            $val = ($saved || isset($_POST[$key])) ? ps($key) : ((isset($prefs[$key])) ? $prefs[$key] : $prefobj[\'default\']);\n            $val = (is_array($val)) ? implode(\', \', $val) : $val;\n            set_pref($key, $val, \'ied_plugin\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\n        }\n    }\n\n    /**\n     * Delete plugin prefs.\n     *\n     * @param  integer $showpane Whether to display the panel afterwards\n     */\n    public function prefs_remove($showpane = 1)\n    {\n        safe_delete(\'txp_prefs\', \"name like \'ied_plugin_%\'\");\n\n        if ($showpane) {\n            $message = gTxt(\'ied_plugin_prefs_deleted\');\n            $this->prefs($message);\n        }\n    }\n\n    /**\n     * Generate list of available languages.\n     *\n     * @param  string $flavour If \'installed\' then show only those available, else \'all\'\n     * @return array\n     */\n    public static function lang_list($flavour = \'installed\')\n    {\n        $ied_langs = array();\n\n        if ($flavour === \'installed\') {\n            // Self-join to get all the installed langs and language strings in one step.\n//            $installed_langs = safe_query(\'select t1.lang, t2.data from \'.PFX.\'txp_lang as t1, \'.PFX.\'txp_lang as t2 WHERE t1.lang = t2.name GROUP BY lang\');\n            $ied_langs = safe_column(\'lang\', \'txp_lang\', \'1=1 GROUP BY lang\');\n        } else {\n            // Grab all available langs from the RPC server.\n            require_once txpath.\'/lib/IXRClass.php\';\n\n            $client = new IXR_Client(RPC_SERVER);\n\n            // Get items from RPC.\n            @set_time_limit(5);\n\n            if ($client->query(\'tups.listLanguages\', get_pref(\'blog_uid\'))) {\n                $response = $client->getResponse();\n\n                foreach ($response as $language) {\n                    $ied_langs[] = $language[\'language\'];\n                }\n            }\n        }\n\n        // Build the select list array.\n        $langlist = array();\n\n        foreach ($ied_langs as $ied_lang) {\n            $langlist[$ied_lang] = gTxt($ied_lang);\n        }\n\n        return $langlist;\n    }\n\n    /**\n     * Build textpack from strings in the database.\n     *\n     * @param  string  $name      Plugin name\n     * @param  integer $force_all Whether to build for the current langue (0) or all installed languages (1)\n     * @return string             The Textpack\n     */\n    public function textpack_build($name, $force_all = 0)\n    {\n        $fetch_lang = null;\n\n        if ($force_all === 0) {\n            $fetch_lang = gps(\'lang\');\n        }\n\n        if (!$fetch_lang) {\n            $fetch_lang = ($force_all === 1)\n                ? implode(\',\', array_keys(self::lang_list(\'installed\')))\n                : get_pref(\'ied_plugin_lang_selected\', \'\');\n        }\n\n        $tpout = array();\n\n        if ($fetch_lang) {\n            $chosen_lang = get_pref(\'ied_plugin_lang_default\', \'\');\n\n            // Guard against situations when the chosen default lang is \'any\'.\n            $dflt_lang = ($chosen_lang === \'\') ? get_pref(\'language\') : $chosen_lang;\n\n            $tp_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\', \'\', 1));\n            $tp_pfx = isset($tp_pfx[$name]) ? $tp_pfx[$name] : \'\';\n            $tp_rows = $this->textpack_grab($fetch_lang, $tp_pfx);\n\n            if ($tp_rows) {\n                $ctr = 0;\n                $prevlang = \'\';\n\n                // Go through all the languages and put the default language at the start of the array.\n                foreach ($tp_rows as $row) {\n                    // Add the event marker\n                    $theEvent = in_array($row[\'event\'], array(\'public\', \'common\')) ? $row[\'event\'] : $tp_pfx;\n\n                    if ($prevlang != $row[\'lang\']) {\n                        $ctr++;\n                    }\n\n                    $idx = ($row[\'lang\'] == $dflt_lang) ? 0 : $ctr;\n                    $tplang[$idx][$theEvent][$row[\'lang\']][$row[\'name\']] = $row[\'data\'];\n                    $prevlang = $row[\'lang\'];\n                    $prevevent = $row[\'event\'];\n                }\n\n                // Make sure default language is actually first.\n                ksort($tplang);\n\n                // Build the final textpack array with language markers.\n                // Note the marker for the default language may (should!) be omitted if the author wants\n                // the strings to be installed regardless of language on destination server.\n                // If a specific language is set and the user does not have that language\n                // installed, the strings would not be inserted.\n                $prevevent = \'\';\n\n                foreach ($tplang as $idx => $langblock) {\n                    foreach ($langblock as $ev => $codeblock) {\n                        $tpheader = array();\n                        $tpstrings = array();\n\n                        if ($prevevent != $ev) {\n                            $tpheader[] = \'#@\' . $ev;\n                        }\n\n                        foreach ($codeblock as $code => $data) {\n                            if (($idx == 0 && $chosen_lang) || ($idx > 0)) {\n                                $tpheader[] = \'#@language \' . $code;\n                            }\n\n                            foreach ($data as $key => $val) {\n                                // Don\'t output empty strings.\n                                if ($val) {\n                                    $tpstrings[] = $key . \' => \' . $val;\n                                }\n                            }\n\n                            if ($tpstrings) {\n                                $tpout = array_merge($tpout, $tpheader, $tpstrings);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        return implode(n, $tpout);\n    }\n\n    /**\n     * Read textpack strings with the given prefix from the database.\n     *\n     * @param  string $lang   Language of strings to fetch\n     * @param  string $prefix Prefix to find\n     * @return array          Record set of matching strings\n     */\n    public function textpack_grab($lang, $prefix)\n    {\n        if ($lang === \'IED_ALL\') {\n            $lang_query = \'\';\n        } else {\n            $lang = (empty($lang)) ? get_pref(\'language\', \'en-gb\') : $lang;\n            $langs = quote_list(do_list($lang));\n            $lang_query = \"lang IN (\".implode(\', \', $langs).\") AND \";\n        }\n\n        return ($prefix) ? safe_rows(\'name, data, lang, event\', \'txp_lang\', $lang_query.\"name LIKE \'\".doSlash($prefix).\"%\' ORDER BY event,lang,name\") : array();\n    }\n\n    /**\n     * Set the current Textpack language from which to display strings in the interface.\n     *\n     * Requires POST variable:\n     *  param  string ied_tp_langsel  The language name to set\n     */\n    public function lang_set()\n    {\n        $sel = doSlash(gps(\'ied_tp_langsel\'));\n        set_pref(\'ied_plugin_lang_selected\', $sel, \'ied_plugin\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n    }\n\n    /**\n     * Store the plugin textpack prefix.\n     *\n     * @param string $plugname Plugin name to store (if omitted, tries GET/POST for \'plugin\')\n     * @param string $pfx      Prefix to store (if omitted, tries GET/POST for \'prefix\')\n     */\n    public function set_tp_prefix($plugname = \'\', $pfx = \'\')\n    {\n        $plugname = ($plugname) ? $plugname : gps(\'plugin\');\n        $pfx = ($pfx) ? $pfx : gps(\'prefix\');\n\n        if ($pfx) {\n            $curr_pfx = unserialize(get_pref(\'ied_plugin_tp_prefix\'));\n            $curr_pfx[$plugname] = $pfx;\n            set_pref(\'ied_plugin_tp_prefix\', serialize($curr_pfx), \'ied_plugin\', PREF_HIDDEN, \'text_input\');\n        }\n    }\n\n    /**\n     * AJAX: Return a Textpack string from a (type 4 or 5) plugin gTxt() function/method.\n     *\n     * Given a function name (usually \'abc_plugin_gTxt\') it calls the function\n     * to fetch the given string from the plugin. By calling this repeatedly\n     * or each string, an entire Textpack can be built from strings that were\n     * previously hard-coded in old plugins.\n     *\n     * Note the destination plugin MUST be of type 4 or 5 in order for the\n     * function to be callable so you may have to (temporarily) alter the\n     * plugin type before using this feature.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_fn  The function to call\n     *  param  string ied_tp_lbl The language label (key)\n     * @return array             JSON structure containing the string\'s value\n     * @todo sanitize $fn\n     */\n    public function textpack_load()\n    {\n        $fn = doSlash(gps(\'ied_tp_fn\'));\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $ret = \'\';\n\n        $fnparts = do_list($fn, \'::\');\n        if (count($fnparts) == 2) {\n            // Callable class -> method\n            $fobj = array($fnparts[0], $fnparts[1]);\n        } else {\n            $fobj = $fnparts[0];\n        }\n        if (is_callable($fobj)) {\n            $ret = call_user_func_array($fobj, array($lbl));\n        }\n\n        if ($ret) {\n            echo json_encode(array(\'ied_plugin_tp_string\' => $ret));\n        }\n    }\n\n    /**\n     * AJAX: Delete a Textpack string from the database.\n     *\n     * Requires POST variable:\n     *  param  string ied_tp_lbl The language label (key) to delete\n     */\n    public function textpack_del()\n    {\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n\n        $ret = safe_delete(\'txp_lang\', \"name=\'$lbl\'\");\n    }\n\n    /**\n     * AJAX: save a Textpack string to the database.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_lbl The language label (key)\n     *  param  string ied_tp_str The data value to store\n     *  param  string ied_tp_lng Desired language of string\n     *  param  string ied_tp_evt Event (group) that the string belongs in\n     */\n    public function textpack_save()\n    {\n        global $DB;\n\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $str = doSlash(gps(\'ied_tp_str\'));\n        $lng = doSlash(gps(\'ied_tp_lng\'));\n        $evt = doSlash(gps(\'ied_tp_evt\'));\n        $own = doSlash(gps(\'ied_tp_own\'));\n\n        $where = \"name=\'$lbl\' AND lang=\'$lng\'\";\n        $ret = safe_update(\'txp_lang\', \"data=\'$str\', event=\'$evt\', owner=\'$own\'\", $where);\n\n        if ($ret && (mysqli_affected_rows($DB->link) || safe_count(\'txp_lang\', $where))) {\n            // Update OK: do nothing else.\n        } else {\n            $ret = safe_insert(\'txp_lang\', \"name=\'$lbl\', lang=\'$lng\', event=\'$evt\', owner=\'$own\', data=\'$str\'\");\n        }\n    }\n\n    /**\n     * AJAX: Fetch a Textpack string from the database.\n     *\n     * Requires POST variables:\n     *  param  string ied_tp_lbl  The language label (key)\n     *  param  string ied_tp_lng  Language of string to fetch\n     *  param  string ied_tp_dflt Default language to fetch string if main lang missing\n     * @return array              JSON response\n     */\n    public function textpack_get()\n    {\n        $lbl = doSlash(gps(\'ied_tp_lbl\'));\n        $lng = doSlash(gps(\'ied_tp_lng\'));\n        $dflt = doSlash(gps(\'ied_tp_dflt\'));\n\n        $rs = safe_rows(\'lang, data\', \'txp_lang\', \"name=\'$lbl\' AND (lang=\'$lng\' OR lang=\'$dflt\')\");\n        $out = array();\n\n        foreach ($rs as $row) {\n            if (($row[\'lang\'] == $dflt) && ($lng != $dflt)) {\n                $out[\'ied_plugin_tp_dflt\'] = $row[\'data\'];\n            } else {\n                $out[\'ied_plugin_tp_string\'] = $row[\'data\'];\n            }\n        }\n\n        echo json_encode($out);\n    }\n\n    /**\n     * AJAX: Save just the plugin code, optionally checking syntax.\n     *\n     * Requires POST variables:\n     *  param  string plugin    Name of plugin to save against\n     *  param  string codeblock PHP code\n     * @return array            XML response\n     */\n    public function code_save()\n    {\n        global $theme;\n\n        $syntax_check = get_pref(\'ied_plugin_syntax_check\');\n\n        $plug = doSlash(ps(\'plugin\'));\n        $code = ps(\'codeblock\');\n\n        $ret = ($syntax_check) ? $this->check_syntax_err($code) : false;\n        $msg = \'\';\n\n        if ($ret === false) {\n            $ret = @safe_update(\'txp_plugin\', \"code=\'\".doSlash($code).\"\'\", \"name=\'$plug\'\");\n            if ($ret) {\n                $msg = $theme->announce_async(gTxt(\'ied_plugin_code_saved\'));\n            } else {\n                $msg = $theme->announce_async(array(gTxt(\'ied_plugin_code_saved_fail\'), E_ERROR));\n            }\n            send_xml_response(array(\'ied_plugin_msg\' => $msg));\n        } else {\n            $msg = $theme->announce_async(array(htmlentities($ret[0], ENT_QUOTES), E_ERROR));\n            send_xml_response(array(\'http-status\' => \'412 Precondition Failed\', \'ied_plugin_msg\' => $msg, \'ied_plugin_err_line\' => $ret[1]));\n        }\n    }\n\n    /**\n     * AJAX: Save just the plugin metadata.\n     *\n     * Requires POST variables:\n     *  param  string plugin Name of plugin to save against\n     *  param  array  data   Name-value pairs to store\n     * @return array         XML response\n     */\n    public function meta_save() {\n        global $theme;\n\n        $ied_plugin_prefs = $this->get_prefs();\n\n        $plug = doSlash(ps(\'plugin\'));\n        $data = ps(\'data\');\n        $msg = \'\';\n        $meta = array();\n\n        // Defaults required because HTML forms don\'t post unchecked items.\n        $set = array(\n            \'flags\'  => \"flags=\'0\'\",\n            \'status\' => \"status=\'0\'\",\n            );\n\n        foreach ($data as $key => $value) {\n            switch ($key) {\n                case \'description\':\n                case \'version\':\n                case \'author\':\n                case \'author_uri\':\n                case \'type\':\n                case \'load_order\':\n                    $set[$key] = $key . \"=\'\".doSlash($value).\"\'\";\n                    break;\n                case \'newname\':\n                    $val = doSlash($value);\n                    $set[\'name\'] = \"name=\'\".$val.\"\'\";\n                    $meta[\'name\'] = $val;\n                    break;\n                case \'status\':\n                case \'flags\':\n                    $part = array();\n\n                    foreach ($value as $item) {\n                        if (!isset($part[$key])) {\n                            $part[$key] = \'\';\n                        }\n                        $part[$key] += $item;\n                    }\n\n                    foreach ($part as $idx => $val) {\n                        $set[$idx] = $idx . \"=\'\" . doSlash($val).\"\'\";\n                    }\n\n                    break;\n            }\n        }\n\n        $update = implode(\',\', $set);\n        $ret = safe_update(\'txp_plugin\', $update, \"name=\'$plug\'\");\n\n        if ($ret) {\n            $msg = $theme->announce_async(gTxt(\'ied_plugin_meta_saved\'));\n        } else {\n            $msg = $theme->announce_async(array(gTxt(\'ied_plugin_meta_saved_fail\'), E_ERROR));\n        }\n\n        send_xml_response(array(\'ied_plugin_msg\' => $msg));\n    }\n\n    /**\n     * Check the syntax of some PHP code.\n     *\n     * Mostly from a comment in http://php.net/manual/en/function.php-check-syntax.php\n     *\n     * @param  string $code  PHP code to check.\n     * @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.\n     */\n    public function check_syntax_err($code)\n    {\n        if (!defined(\'CR\')) define(\'CR\',chr(13));\n        if (!defined(\'LF\')) define(\'LF\',chr(10));\n\n        $braces=0;\n        $inString=0;\n\n        foreach (token_get_all(\'<?php \' . $code) as $token) {\n            if (is_array($token)) {\n                switch ($token[0]) {\n                    case T_CURLY_OPEN:\n                    case T_DOLLAR_OPEN_CURLY_BRACES:\n                    case T_START_HEREDOC: ++$inString; break;\n                    case T_END_HEREDOC:   --$inString; break;\n                }\n            } elseif ($inString & 1) {\n                switch ($token) {\n                    case \'`\': case \'\\\'\':\n                    case \'\"\': --$inString; break;\n                }\n            } else {\n                switch ($token) {\n                    case \'`\': case \'\\\'\':\n                    case \'\"\': ++$inString; break;\n                    case \'{\': ++$braces; break;\n                    case \'}\':\n                        if ($inString) {\n                            --$inString;\n                        } else {\n                            --$braces;\n                            if ($braces < 0) break 2;\n                        }\n                        break;\n                }\n            }\n        }\n        $inString = @ini_set(\'log_errors\', false);\n        $token = @ini_set(\'display_errors\', true);\n        ob_start();\n        $braces || $code = \"if (0) {{$code}\\n}\";\n        if (eval($code) === false) {\n            if ($braces) {\n                $braces = PHP_INT_MAX;\n            } else {\n                false !== strpos($code,CR) && $code = str_replace(CR,LF,str_replace(CRLF,LF,$code));\n                $braces = substr_count($code,LF);\n            }\n            $code = ob_get_clean();\n            $code = strip_tags($code);\n            if (preg_match(\"\'syntax error, (.+) in .+ on line (\\d+)$\'s\", $code, $code)) {\n                $code[2] = (int) $code[2];\n                $code = $code[2] <= $braces\n                    ? array($code[1], $code[2])\n                    : array(\'unexpected $end\' . substr($code[1], 14), $braces);\n            } else $code = array(\'syntax error\', 0);\n        } else {\n            ob_end_clean();\n            $code = false;\n        }\n        @ini_set(\'display_errors\', $token);\n        @ini_set(\'log_errors\', $inString);\n\n        return $code;\n    }\n\n    // Reflection utility for phpdoc generation\n    public function reflunction_factory($callback)\n    {\n        if (is_array($callback)) {\n            // must be a class method\n            list($class, $method) = $callback;\n\n\n            return new ReflectionMethod($class, $method);\n        }\n\n        // class::method syntax\n        if (is_string($callback) && strpos($callback, \"::\") !== false) {\n            list($class, $method) = explode(\"::\", $callback);\n\n            return new ReflectionMethod($class, $method);\n        }\n\n        // objects as functions (PHP 5.3+)\n        if (version_compare(PHP_VERSION, \"5.3.0\", \">=\") && method_exists($callback, \"__invoke\")) {\n            return new ReflectionMethod($callback, \"__invoke\");\n        }\n\n        // assume it\'s a function\n        return new ReflectionFunction($callback);\n    }\n\n    public function generate_phpdoc()\n    {\n        $plug = gps(\'plugin\');\n        $obj = gps(\'fn\');\n        $ret = load_plugin($plug);\n        if ($ret) {\n            try {\n                $obj = $this->reflunction_factory($obj);\n                $name = $obj->getName();\n                $doc = $obj->getDocComment();\n\n                if ($doc) {\n                    //TODO: make list of allowed docblock tags a pref\n                    //TODO: parse existing doc block and append any new vars/params\n                    preg_match_all(\'/\\s*\\*\\s*\\@(abstract|access|author|category|copyright|deprecated|example|final|filesource|global|ignore|internal|license|link|method|name|package|param|property|return|see|since|static|staticvar|subpackage|todo|tutorial|uses|var|version)\\s+(\\w+)\\s+(\\$\\w+(?::\\w+|->\\w+)*)\\s+(\\[(\\&|\\+|abstract|final|private|protected|public|static)\\])*\\s+(.*)/\', $doc, $matches, PREG_SET_ORDER);\n\n                    $tags = array();\n                    // Reshuffle the matches to index on [tag][var]\n                    foreach ($matches as $idx => $data) {\n                        $ful = isset($data[0]) ? $data[0] : \'\';\n                        $tag = isset($data[1]) ? $data[1] : \'\';\n                        $typ = isset($data[2]) ? $data[2] : \'\';\n                        $var = isset($data[3]) ? $data[3] : \'\';\n                        $mod = isset($data[4]) ? $data[4] : \'\';\n                        $tags[$tag][$var] = array(\n                            \'type\' => $typ,\n                            \'full\' => $ful,\n                            \'mod\'  => $mod,\n                        );\n                    }\n                }\n\n                $fntype = array(\'meta\' => array(), \'type\' => array());\n\n                if (method_exists($obj, \'isConstructor\')) {\n                    // Dealing with a class method\n                    if ($obj->isConstructor()) {\n                        $fntype[\'meta\'][] = \' [c]\';\n                    }\n                    if ($obj->isPublic()) {\n                        $fntype[\'mod\'][] = \'[public]\';\n                    }\n                    if ($obj->isPrivate()) {\n                        $fntype[\'mod\'][] = \'[private]\';\n                    }\n                    if ($obj->isProtected()) {\n                        $fntype[\'mod\'][] = \'[protected]\';\n                    }\n                    if ($obj->isAbstract()) {\n                        $fntype[\'mod\'][] = \'[abstract]\';\n                    }\n                    if ($obj->isFinal()) {\n                        $fntype[\'mod\'][] = \'[final]\';\n                    }\n                    if ($obj->isStatic()) {\n                        $fntype[\'mod\'][] = \'[static]\';\n                    }\n                }\n\n                $params = $obj->getParameters();\n                $param_list = array();\n                if ($params != null) {\n                    foreach ($params as $param) {\n                        $has_dflt = false;\n                        $type = \'string\';\n\n                        // Crude type checker\n                        if ($param->isDefaultValueAvailable()) {\n                            $has_dflt = true;\n                            $dflt = $param->getDefaultValue();\n\n                            if (is_bool($dflt)) {\n                                $type = \'boolean\';\n                                $dflt = $dflt ? \'true\' : \'false\';\n                            } elseif (is_null($dflt)) {\n                                $dflt = \'NULL\';\n                            } elseif ($dflt == (string) (int) $dflt) {\n                                $type = \'integer\';\n                            } elseif ($dflt == (string) (float) $dflt) {\n                                $type = \'float\';\n                            } elseif (is_numeric($dflt)) {\n                                $type = \'number\';\n                            } elseif (is_array($dflt)) {\n                                $type = \'array\';\n                            }\n\n                            if ( ($type == \'string\' || $type == \'array\') && empty($dflt) ) {\n                                $dflt = \'empty\';\n                            } elseif ($type == \'string\' && $dflt != \'NULL\') {\n                                $dflt = \'\"\'.$dflt.\'\"\';\n                            }\n                        }\n\n                        $item = \'@param\'.t.$type.t.\'$\'.$param->getName();\n                        if ($param->isPassedByReference()) {\n                            $item .= \' [&]\';\n                        }\n                        if ($param->allowsNull()) {\n                            $item .= \' [+]\';\n                        }\n                        $item .= ($has_dflt) ? t.\'(Default: \' .$dflt. \')\' : t;\n                        $item .= t.\'Param description\';\n                        $param_list[] = $item;\n                    }\n                }\n\n                $final = \'<pre>\';\n                $final .= \"/**\".br;\n                $final .= \" * $name\" . ((!empty($fntype[\'meta\'])) ? $fntype[\'meta\'] : \'\') .t. (($fnType[\'mod\']) ? implode(\' \', $fnType[\'mod\']).\' \' : \'\') . \"Description\".br;\n                $final .= \" *\".br;\n                $final .= \" * Summary goes here\".br;\n                $final .= \" *\".br;\n                foreach ($param_list as $item) {\n                    $final .= \" * \". $item.br;\n                }\n                $final .= \" */</pre>\".br;\n\n                send_xml_response(array(\'ied_plugin_phpdoc\' => $final));\n            } catch (Exception $ex) {\n                send_xml_response( array( \'http-status\' => \'501 Not Implemented\', \'error_msg\' => $ex->getMessage() . gTxt(\'ied_plugin_fn_not_exist\') ) );\n            }\n        } else {\n            send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n        }\n\n    /*  $classInterfaces=$reflection->getInterfaces();\n        //get information about the interfaces\n        if ($classInterfaces != null) {\n        fwrite($hf,\"\\n</i>\\t</td></tr>\\n\\t<tr>\".\n        \"<td align=\\\"center\\\" colspan=\\\"0\\\">\".\n        \"<font face=\\\"arial\\\" size=\\\"2\\\"\".\n        \" color=\\\"purple\\\">Implemented\".\n        \" interfaces:</td><td align=\\\"center\\\"\".\n        \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n        \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n        \"</font></td><td align=\\\"center\\\"\".\n        \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n        \" size=\\\"2\\\" color=\\\"black\\\"><b>\".\n        \"Description</b></font></td></tr>\\n\");\n         foreach ($classInterfaces as $in) {\n            fwrite($hf,\"\\t<tr><td></td><td align=\".\n            \"\\\"center\\\">\");\n            fwrite($hf,$in->getName());\n            fwrite($hf,\"</td><td align=\\\"center\\\"\".\n            \" colspan=\\\"3\\\"><i>\\n\");\n            fwrite($hf,$in->getDocComment());\n            fwrite($hf,\"</i></td></tr>\\n\");\n            }\n         }\n\n        //get the superclass information\n        $superclass=$reflection->getParentClass();\n        if ($superclass != null) {\n            fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n             \"colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n             \" size=\\\"2\\\" color=\\\"purple\\\">\".\n             \"The superclass is:</td><td\".\n             \" align=\\\"center\\\" colspan=\\\"4\\\">\".\n             \"<font face=\\\"arial\\\" size=\\\"2\\\"\".\n             \" color=\\\"black\\\"><b>\\n\");\n            fwrite($hf,$superclass->getName());\n            fwrite($hf,\"</font></td></tr>\\n\");\n            }\n\n        //get information about the constants\n        $constants=$reflection->getConstants();\n        if ($constants != null) {\n          $constantsNumber=count($constants);\n          fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"purple\\\">\".\n          \"Constants:</td><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n          \"</font></td><td align=\\\"center\\\"\".\n          \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Value</b>\".\n          \"</font></td></tr>\\n\");\n          foreach ($constants as $keys=>$value) {\n            fwrite($hf,\"\\t<tr><td></td><td\".\n            \" align=\\\"center\\\">\");\n            fwrite($hf,$keys);\n            fwrite($hf,\"</td><td align=\\\"center\\\"\".\n            \" colspan=\\\"3\\\">\");\n            fwrite($hf,$value);\n            fwrite($hf,\"</td></tr>\\n\");\n            }\n          }\n\n        //get information about properties\n        $properties=$reflection->getProperties();\n        if ($properties != null) {\n          fwrite($hf,\"\\t<tr><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"purple\\\">\".\n          \"Properties:</td><td align=\\\"center\\\"\".\n          \" colspan=\\\"0\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>Name</b>\".\n          \"</font></td><td align=\\\"center\\\"\".\n          \" colspan=\\\"3\\\"><font face=\\\"arial\\\"\".\n          \" size=\\\"2\\\" color=\\\"black\\\"><b>\".\n          \"Modifiers</b></font></td></tr>\\n\");\n          foreach ($properties as $in) {\n             fwrite($hf,\"\\t<tr><td></td><td\".\n             \" align=\\\"center\\\" colspan=\\\"0\\\">\");\n             fwrite($hf,$in->getName());\n             fwrite($hf,\"</td><td align=\\\"center\\\"\".\n             \" colspan=\\\"3\\\">\");\n\n             if ($in->isPublic()) { fwrite($hf,\"[public]\"); }\n             if ($in->isPrivate()) { fwrite($hf,\"[private]\"); }\n             if ($in->isProtected()) { fwrite($hf,\"[protected]\"); }\n             if ($in->isStatic()) { fwrite($hf,\"[static]\"); }\n             fwrite($hf,\"</td></tr>\\n\");\n             }\n          }\n    */\n\n    }\n\n    // ------------------------\n    // Settings for the plugin\n    public function get_prefs()\n    {\n        $tempdir = get_pref(\'tempdir\');\n\n        $ied_pc_prefs = array(\n            \'ied_plugin_editor\' => array(\n                \'html\'     => \'ied_plugin_code_editors\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 10,\n                \'default\'  => \'none\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_path\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 20,\n                \'default\'  => hu.\'js/\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_options\' => array(\n                \'html\'     => \'textarea\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 30,\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_editor_width\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 40,\n                \'default\'  => \'90%\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor\' => array(\n                \'html\'     => \'ied_plugin_help_editors\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 50,\n                \'default\'  => \'textile\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor_path\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 60,\n                \'default\'  => hu.\'js/\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_help_editor_options\' => array(\n                \'html\'     => \'textarea\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 70,\n                \'default\'  => \'theme_advanced_toolbar_location : \"top\",\n    theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n    theme_advanced_buttons2 : \"link,unlink,separator,ibrowser,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n    theme_advanced_buttons3 : \"\"\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_interface_elems\' => array(\n                \'html\'     => \'checkboxset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 80,\n                \'content\'  => array(\'distribution\' => \'ied_plugin_if_el_dist\', \'style\' => \'ied_plugin_if_el_style\'),\n                \'default\'  => \'style\',\n                \'group\'    => \'ied_plugin_if_settings\',\n            ),\n            \'ied_plugin_lifecycle_options\' => array(\n                \'html\'     => \'checkboxset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 90,\n                \'content\'  => array(\'installed\' => \'ied_plugin_lbl_lc_install\', \'enabled\' => \'ied_plugin_lbl_lc_enable\', \'disabled\' => \'ied_plugin_lbl_lc_disable\', \'deleted\' => \'ied_plugin_lbl_lc_delete\'),\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_auto_enable\' => array(\n                \'html\'     => \'radioset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 100,\n                \'content\'  => array(\'0\' => gTxt(\'no\'), \'1\' => gTxt(\'yes\'), \'2\' => gTxt(\'ied_plugin_same\')),\n                \'default\'  => \'2\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_syntax_check\' => array(\n                \'html\'     => \'yesnoradio\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 110,\n                \'default\'  => \'1\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_lang_choose\' => array(\n                \'html\'     => \'ied_plugin_lang_options\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 120,\n                \'default\'  => \'installed\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_lang_default\' => array(\n                \'html\'     => \'ied_plugin_lang_default\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 130,\n                \'default\'  => \'\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_order\' => array(\n                \'html\'     => \'radioset\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 140,\n                \'content\'  => array(\'0\' => gTxt(\'ied_plugin_lbl_op_code_first\'), \'1\' => gTxt(\'ied_plugin_lbl_op_help_first\')),\n                \'default\'  => \'0\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfile\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 150,\n                \'default\'  => \'{name}_v{version}.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilec\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 160,\n                \'default\'  => \'{name}_v{version}_zip.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilep\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 170,\n                \'default\'  => \'{name}_v{version}.php\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_sfilet\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 180,\n                \'default\'  => \'{name}_v{version}_{lang}_textpack.txt\',\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n            \'ied_plugin_output_tmpcache\' => array(\n                \'html\'     => \'text_input\',\n                \'type\'     => PREF_HIDDEN,\n                \'position\' => 190,\n                \'default\'  => $tempdir,\n                \'group\'    => \'ied_plugin_prefs\',\n            ),\n        );\n\n        return $ied_pc_prefs;\n    }\n}\n\n/**\n * Add prefs callbacks to global scope, since create_pref() can\'t take\n * array/object syntax.\n *\n * @see  http://forum.textpattern.com/viewtopic.php?pid=298188#p298188\n */\nif (txpinterface === \'admin\') {\n    /**\n     * List of supported javascript syntax highlighter / code editors.\n     *\n     * NB: no i18n since these are the names of the projects.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_code_editors($name, $val = \'\')\n    {\n        $eds[\'none\'] = gTxt(\'none\');\n        $eds[\'edit_area\'] = \'EditArea\';\n        $eds[\'codemirror\'] = \'CodeMirror\';\n        $eds[\'codepress\'] = \'CodePress\';\n\n        return selectInput($name, $eds, $val, false);\n    }\n\n    /**\n     * List of supported javascript help editors.\n     *\n     * NB: no i18n since these are the names of the projects.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_help_editors($name, $val = \'\')\n    {\n        $eds[\'textilee\'] = \'Textile\';\n        $eds[\'tiny_mce\'] = \'TinyMCE\';\n\n        return selectInput($name, $eds, $val, false);\n    }\n\n    /**\n     * List of language options.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_lang_options($name, $val = \'\')\n    {\n        $lngs[\'installed\'] = gTxt(\'ied_plugin_langs_installed\');\n        $lngs[\'all\'] = gTxt(\'ied_plugin_langs_all\');\n\n        return selectInput($name, $lngs, $val, false);\n    }\n\n    /**\n     * Complete list of supported languages.\n     *\n     * @param  string $name Preference name\n     * @param  string $val  Current preference value\n     * @return string       HTML\n     */\n    function ied_plugin_lang_default($name, $val = \'\')\n    {\n        $langs = array_merge(array(\'\' => gTxt(\'ied_plugin_any\')), ied_pc::lang_list(\'all\'));\n\n        return selectInput($name, $langs, $val, false);\n    }\n}', 'b231440274c0cbfb5adf63712ad4beca', '#@ied_plugin\nied_plugin_any => Any\nied_plugin_auto_enable => Auto-enable plugins on install\nied_plugin_cacheplugs_legend => Plugins in cache directory\nied_plugin_cache_not_set => Advanced pref \"plugin cache directory\" not defined\nied_plugin_check_type => Check your plugin type!\nied_plugin_choose_file => Please choose a file first\nied_plugin_code_dist => Plugin code for distribution\nied_plugin_code_legend => Plugin code\nied_plugin_code_save => Save code\nied_plugin_code_saved => Code saved\nied_plugin_code_saved_fail => Code saving failed\nied_plugin_composer => Plugin composer\nied_plugin_compress => Zip\nied_plugin_cpanel_legend => Installation\nied_plugin_create_new => Create new plugin\nied_plugin_dbplugs_legend => Plugins in database\nied_plugin_docs => Docs\nied_plugin_docs_legend => Plugin help\nied_plugin_edit => Edit: {name} v{version}\nied_plugin_editing => Editing {name} in Plugin Composer\nied_plugin_editor => Plugin editor\nied_plugin_editor_options => Plugin editor configuration options\nied_plugin_editor_path => Plugin editor URL\nied_plugin_editor_width => Plugin editor width\nied_plugin_edit_new => Edit your new plugin\nied_plugin_enable => Enabled\nied_plugin_export => Export as {name}\nied_plugin_export_textpack => Export textpack(s)\nied_plugin_export_zip => Export as {name} (compressed)\nied_plugin_flags => Flags\nied_plugin_flag_has_prefs => Has prefs\nied_plugin_flag_lifecycle_notify => Event notify\nied_plugin_fn_not_exist => (the function is probably javascript or mangled PHP)\nied_plugin_help_editor => Help editor\nied_plugin_help_editor_options => Help editor configuration options\nied_plugin_help_editor_path => Help editor URL\nied_plugin_help_not_available => Plugin help not available\nied_plugin_if_el_dist => Code for distribution\nied_plugin_if_el_style => Help style block\nied_plugin_if_settings => Interface settings\nied_plugin_install_textpack => Install file\'s Textpack\nied_plugin_install_txt => Install from .txt file\nied_plugin_interface_elems => Optional interface elements\nied_plugin_jump_to_line => Jump to line:\nied_plugin_langs_all => All available\nied_plugin_langs_installed => Only installed\nied_plugin_lang_choose => Textpack language list\nied_plugin_lang_default => Default textpack language\nied_plugin_lbl_lc_delete => Delete\nied_plugin_lbl_lc_disable => Disable\nied_plugin_lbl_lc_disdel => Disable+Delete\nied_plugin_lbl_lc_enable => Enable\nied_plugin_lbl_lc_instable => Install+Enable\nied_plugin_lbl_lc_install => Install\nied_plugin_lbl_op_code_first => Code first\nied_plugin_lbl_op_help_first => Help first\nied_plugin_lbl_setup => Plugin composer setup\nied_plugin_lc_fired => Lifecycle event {event} fired for {name}\nied_plugin_lifecycle => Fire lifecycle event\nied_plugin_lifecycle_options => Perform lifecycle actions on\nied_plugin_list => Plugin list\nied_plugin_load => Load\nied_plugin_load_order => Load order\nied_plugin_load_order_help => (1=first > 5=normal > 9=last)\nied_plugin_meta_legend => Meta information\nied_plugin_meta_save => Save meta\nied_plugin_meta_saved => Meta info saved\nied_plugin_meta_saved_fail => Meta info not saved\nied_plugin_msgpop_lbl => phpdoc block\nied_plugin_name_first => Please name the plugin before creating it\nied_plugin_output_order => PHP export order\nied_plugin_output_sfile => Export plugin filename format\nied_plugin_output_sfilec => Export compressed filename format\nied_plugin_output_sfilep => Plugin template filename format\nied_plugin_output_sfilet => Textpack filename format\nied_plugin_output_tmpcache => Cache Textiled help path\nied_plugin_pack_legend => Textpack strings\nied_plugin_php_doc => Make phpdoc\nied_plugin_prefs => Preferences\nied_plugin_prefs_deleted => Preferences deleted\nied_plugin_renamed => (renamed)\nied_plugin_rename_failed => (rename failed)\nied_plugin_rename_file => Rename file\nied_plugin_restored => Plugin {name} restored.\nied_plugin_restore_help => Roll back the plugin code to the most recent restore point\nied_plugin_restore_point => Restore point\nied_plugin_restore_verify => Are you sure you want to rollback the {name} source code to its last restore point?\nied_plugin_run_install => Perform post-install actions\nied_plugin_same => Same\nied_plugin_save_as => Save as {name}\nied_plugin_setup => Setup\nied_plugin_syntax_check => Syntax check on code save\nied_plugin_syntax_err => Syntax error\nied_plugin_toggle => Toggle\nied_plugin_tp_populate => Load strings from function\nied_plugin_tp_prefix => Textpack prefix\nied_plugin_type => Plugin type\nied_plugin_type_0 => Public\nied_plugin_type_1 => Admin + Public\nied_plugin_type_2 => Library\nied_plugin_type_3 => Admin only\nied_plugin_type_4 => Admin (+AJAX)\nied_plugin_type_5 => Admin + Public (+AJAX)\nied_plugin_updated => Plugin {name} updated\nied_plugin_uploaded => Plugin {name} uploaded\nied_plugin_upload_php => Upload plugin\nied_plugin_utils_legend => Distribution\nied_plugin_utils_legend_extra => Only for use after saving\nied_plugin_view_help => Help: {name}\n#@ied_plugin\n#@language fr-fr\nied_plugin_any => Tous\nied_plugin_auto_enable => Activer les plugins dès leur installation\nied_plugin_cacheplugs_legend => Les Plugins sont chargés depuis le cache\nied_plugin_cache_not_set => Le paramètre \"chemin du cache des plugins\" des préférences avancées n\'est pas renseigné\nied_plugin_check_type => Renseignez le type de votre plugin !\nied_plugin_choose_file => Veuillez choisir d\'abord un fichier\nied_plugin_code_dist => Code de distribution du plugin\nied_plugin_code_legend => Code source du plugin\nied_plugin_code_save => Sauvegarder le code\nied_plugin_code_saved => Code sauvegardé\nied_plugin_code_saved_fail => Échec de sauvegarde du code\nied_plugin_composer => Plugin composer\nied_plugin_compress => Zip\nied_plugin_cpanel_legend => Installation\nied_plugin_create_new => Créer un nouveau plugin\nied_plugin_dbplugs_legend => Plugins disponibles\nied_plugin_docs => Docs\nied_plugin_docs_legend => Aide du plugin\nied_plugin_edit => Éditer : {name} v{version}\nied_plugin_editing => Édition de {name} depuis Plugin Composer\nied_plugin_editor => Éditeur de plugins\nied_plugin_editor_options => Options de configuration de l\'éditeur de plugins\nied_plugin_editor_path => URL de l\'éditeur de plugins\nied_plugin_editor_width => Taille (largeur) de l\'éditeur de plugin\nied_plugin_edit_new => Éditer votre nouveau plugin\nied_plugin_enable => Activé\nied_plugin_export => Exporter sous {name}\nied_plugin_export_textpack => Exporter le(s) textpack(s)\nied_plugin_export_zip => Exporter sous {name} (compressé)\nied_plugin_flags => Flags\nied_plugin_flag_has_prefs => A les préfs\nied_plugin_flag_lifecycle_notify => Notification d\'événement\nied_plugin_fn_not_exist => (la fonction est probablement en javascript ou contient du PHP corrompu)\nied_plugin_help_editor => Éditeur de l\'aide\nied_plugin_help_editor_options => Configuration pour l\'éditeur de l\'aide\nied_plugin_help_editor_path => URL pour l\'éditeur de l\'aide\nied_plugin_help_not_available => Aide du plugin non disponible\nied_plugin_if_el_dist => Code de distribution\nied_plugin_if_el_style => Bloc CSS pour l\'aide\nied_plugin_if_settings => Paramètres de l\'interface\nied_plugin_install_txt => Installer depuis un fichier .txt\nied_plugin_interface_elems => Éléments optionels de l\'interface\nied_plugin_jump_to_line => Aller à la ligne :\nied_plugin_langs_all => Toutes disponibles\nied_plugin_langs_installed => Seulement installées\nied_plugin_lang_choose => Liste des traductions (Textpack)\nied_plugin_lang_default => Langue par défaut (Textpack)\nied_plugin_lbl_lc_delete => Supprimer\nied_plugin_lbl_lc_disable => Désactiver\nied_plugin_lbl_lc_enable => Activer\nied_plugin_lbl_lc_install => Installer\nied_plugin_lbl_op_code_first => Code au début\nied_plugin_lbl_op_help_first => Aide au début\nied_plugin_lbl_setup => Configuration de Plugin composer\nied_plugin_lifecycle_options => Réaliser les actions \"lifecycle\" sur\nied_plugin_load => Charger\nied_plugin_load_order => Ordre de chargement\nied_plugin_load_order_help => (1=premier > 5=normal > 9=dernier)\nied_plugin_meta_legend => Meta information\nied_plugin_msgpop_lbl => Bloc phpdoc\nied_plugin_name_first => Veuillez nommer le plugin avant de pouvoir le créer\nied_plugin_output_order => Orde d\'exportation du source PHP\nied_plugin_output_sfile => Noms d\'exportation des fichiers plugin\nied_plugin_output_sfilec => Noms des fichiers compressés\nied_plugin_output_sfilep => Noms des fichiers Plugin\nied_plugin_output_sfilet => Noms des fichiers Textpack\nied_plugin_output_tmpcache => Chemin du cache de l\'aide sous Textile\nied_plugin_pack_legend => Chaînes du Textpack\nied_plugin_php_doc => Créer un phpdoc\nied_plugin_prefs => Préférences\nied_plugin_prefs_deleted => Préférences supprimées\nied_plugin_renamed => (renomé)\nied_plugin_rename_failed => (échec de renomage)\nied_plugin_rename_file => Renomer le fichier\nied_plugin_restored => Plugin {name} restauré.\nied_plugin_restore_help => Restituer le code du plugin à partir de son dernier point de restauration\nied_plugin_restore_point => Point de restauration\nied_plugin_restore_verify => Êtes-vous sûr de vouloir restituer le code source de {name} à partir de son dernier point de restauration ?\nied_plugin_run_install => Effectuer les actions de post-installation\nied_plugin_same => Nom\nied_plugin_save_as => Sauver sous {name}\nied_plugin_setup => Configuration\nied_plugin_syntax_check => Vérifier la syntaxe du code avant sauvegarde\nied_plugin_syntax_err => Erreur de syntaxe\nied_plugin_tp_populate => Essayer de charger les chaînes depuis la fonction\nied_plugin_tp_prefix => Préfixe du Textpack\nied_plugin_type => Type de plugin\nied_plugin_type_0 => Public\nied_plugin_type_1 => Admin + Public\nied_plugin_type_2 => Librarie\nied_plugin_type_3 => Admin seul\nied_plugin_type_4 => Admin (+AJAX)\nied_plugin_type_5 => Admin + Public (+AJAX)\nied_plugin_updated => Plugin {name} mis à jour\nied_plugin_uploaded => Plugin {name} téléchargé\nied_plugin_upload_php => Télécharger un plugin\nied_plugin_utils_legend => Distribution\nied_plugin_utils_legend_extra => Effectif après sauvegarde\nied_plugin_view_help => Aide de : {name}', '', 5, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_admin_noindex', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Each page in /textpattern/ folder emits \"noindex, nofollow\" header.', '', 'if (@txpinterface === \'admin\') {\r\nheader(\'X-Robots-Tag: noindex, nofollow, noarchive, nosnippet, noimageindex\', true);\r\n}', 'if (@txpinterface === \'admin\') {\r\nheader(\'X-Robots-Tag: noindex, nofollow, noarchive, nosnippet, noimageindex\', true);\r\n}', 'e04765d833cdf6db8e5047053cd7a885', '', '', 3, 1, 0),
('pat_speeder', 0, 'Patrick LEFEVRE', 'http://pat-speeder.cara-tm.com', '0.7.5', 'Speed up your TXP website', '<div id=\"pat-speeder-help\"><h1><b>Speed up your TXP websites now!</b></h1><p>Did you ever seen the source code of the Google main html page? For speed reasons, Google serves its home page into one line of code. The benefit is a server bandwidth gain. So what don\'t we make the same for ours TXP websites?</p><p>Just activate this plugin and your page templates <del>and database css files</del> (not in this version) will be rendered into one line of code.</p><p><b>Warning</b>: This plugin seems not to be compatible with some flash audio players (to be confirmed). <br />Furthermore, for a long time I thought to preserve paddings before and after each links (\"a\" tags) and I added a space between all HTML code within the compressed copy in order to keep visual appearances unchanged. But all these extra spaces are not parts of natural and well written text contents. It incombe you to add them before and after! So keep that in mind; and let\'s write good contents and still let\'s gain 1.03% in compression (spaces between <code>&lt;a&gt;</code> tags will be removed).</p><h2><b>Test results</b></h2><p>This plugin gives a benefit rendition between 5% (for precompressed pages) and 6% (normal pages) according to Ruud\'s observations.</p><h2>Usage</h2><p>At the very top of your page templates add this tag:</p><p><code>&lt;txp:pat_speeder /&gt;</code></p><h2><b>Attributes</b></h2><p>This plugin accepts three attributes:</p><p><code>enable</code> boolean: set \"boolean\" to 1 activate the plugin. Set to 0 disabled it. Default 1 (yes).</p><p><code>gzip</code> boolean (optional): server side gzip compression. Default: 1 (true).</p><p><code>code</code> (optional): a comma separated list of tags name on which do not apply compression. Default: <code>script,code,pre</code></p><h2><b>Example</b></h2><p><code>&lt;txp:pat_speeder enable=\"0\" /&gt;&lt;!DOCTYPE ...</code></p><p>In this case, pat_speeder is\'nt enable into this current page template.</p><h2><b>Changelog</b></h2><p>First realize: v0.1 on 22 Jul 2009<br />Second realize: v0.2 on 26 Jul 2009. Adding some code submitted by Rudd.<br />Third realize: v0.3 on 28 Jul 2009. Mask html comments and css comments but preserve IE conditional comments.<br />Intermediate pre final realize: v0.3.a on Jul 2009. The array() had been changed by a stronger regex.<br />Final realize: v0.3.b on 30 Jul 2009. Support for &lt;pre&gt; tags. <br /><em>Non intrusive realize</em>: v0.4 on 29 Jul 2009. Add \"enable\" attribute.<br />Confidential Versions : v0.4a &amp; v0.4b for tests only. <br />\nBoilerplate/HTML5 capable: <b>v0.4c</b> on 06 Jul 2011. Add support in regex for \"HTML5/Boilerplate\" doctype declaration method. <br />v0.4.d on 18th august 2013. Minor changes for better support of pre &amp; code statements. <br />v0.5 on 25th March 2014. Corrects removing space between some tags. <br />v0.6 on 27th March 2014. Corrects errors. Remove HTML comments. <br />v0.7 on 26th May 2015. Add server side compression. Add \"gzip\" attribute. Better new line removing server system independant. <br />v0.7.1. on 30th May 2015. Remove extra spaces between all tags. <br/>v0.7.5 on 22th June 2018. Improvement; keep google bot comments.</p></p><p>See also the <a href=\"http://forum.textpattern.com/viewtopic.php?id=31345\"><strong>Forum Thread</strong></a> for requests or help</p><h2><b>Additional Optimization Advices</b></h2><p><b>Never need with version 0.7: keep it here for information</b>.</p><p>If you want to add more speed on your websites, you can take advantage on your server\'s gzip compression for all your other files (css, javascript, php, images) - never mind where they are located - with signifiant gains up to 80%. In your <code>.htaccess</code> file, just add this lines of code:</p><p><pre><code>&lt;IfModule mod_gzip.c&gt;\n    mod_gzip_on       Yes\n    mod_gzip_dechunk  Yes\n    mod_gzip_item_include file      \\.(html?|txt|css|js|php|pl)$\n    mod_gzip_item_include handler   ^cgi-script$\n    mod_gzip_item_include mime      ^text/.*\n    mod_gzip_item_include mime      ^application/x-javascript.*\n    mod_gzip_item_exclude mime      ^image/.*\n    mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*\n&lt;/IfModule&gt;</code></pre></p><h2><b>Acknowledgments</b></h2><p>Special thanks to <a href=\"http://vanmelick.com/txp/\" target=\"_blank\"><b>Ruud</b></a> for his good advices.</p></div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#pat-speeder-help p{text-align:justify}#pat-speeder-help pre{text-align:left !important;background:#fcfba1}</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * @name	  pat_speeder\r\n * @description	  Display page source on one line of code\r\n * @link 	  http://pat-speeder.cara-tm.com\r\n * @author	  Patrick LEFEVRE\r\n * @author_email  <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @type:         Public\r\n * @prefs:        no prefs\r\n * @order:        5\r\n * @version:      0.7.5\r\n * @license:      GPLv2\r\n */\r\n\r\n/**\r\n * This plugin tag registry\r\n *\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag_Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_speeder\');\r\n}\r\n\r\n\r\n/**\r\n * \r\n * \r\n */\r\nif (txpinterface == \'admin\') {\r\n\r\n	global $pat_speeder_gTxt;\r\n\r\n	register_callback(\'pat_speeder_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'pat_speeder_cleanup\', \'plugin_lifecycle.pat_speeder\', \'deleted\');\r\n\r\n	// Default plugin Textpack.\r\n	$pat_speeder_gTxt = array(\r\n		\'pat_speeder_enable\' => \'Activate pat_speeder?\',\r\n		\'pat_speeder_gzip\' => \'Active Gzip compression for pat_speeder?\',\r\n		\'pat_speeder_tags\' => \'Tags protection from pat_speeder\',\r\n	);\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin tag with attributes\r\n *\r\n * @param  array    Tag attributes\r\n * @return boolean  Call for main function\r\n */\r\nfunction pat_speeder($atts)\r\n{\r\n\r\n	global $prefs;\r\n\r\n	extract(lAtts(array(\r\n		\'enable\' => true,\r\n		\'gzip\'   => $prefs[\'pat_speeder_gzip\'],\r\n		\'code\'   => $prefs[\'pat_speeder_tags\'],\r\n	),$atts));\r\n\r\n	if ( $enable || ($prefs[\'pat_speeder_enable\'] && $enable) ) {\r\n		ob_start(function($buffer) use ($gzip, $code) {\r\n			return _pat_speeder_go($buffer, $gzip, $code);\r\n		});\r\n	}\r\n\r\n}\r\n\r\n/**\r\n * Main function\r\n * @param string $buffer\r\n * @return string HTML compressed content\r\n */\r\n\r\nfunction _pat_speeder_go($buffer, $gzip, $code)\r\n{\r\n	$codes = str_replace(\',\', \'|\', $code);\r\n\r\n	// remove uncessary elements from the source document\r\n	$buffer = preg_replace(\'/(?imx)(?>[^\\S ]\\s*|\\s{2,})(?=(?:(?:[^<]++|<(?!\\/?(?:textarea|\'.$codes.\')\\b))*+)(?:<(?>textarea|\'.$codes.\')\\b| \\z))/u\', \' \', $buffer);\r\n	// remove all comments except google ones\r\n	$buffer = preg_replace(\'/<!--([^<|\\[|>|go{2}gleo]).*?-->/s\', \'\', $buffer);\r\n\r\n	// server side compression if available\r\n	if( $gzip && isset($_SERVER[\'HTTP_ACCEPT_ENCODING\']) ) {\r\n		$encoding = $_SERVER[\'HTTP_ACCEPT_ENCODING\'];\r\n		if( function_exists(\'gzencode\') && preg_match(\'/gzip/i\', $encoding) ) {\r\n			header (\'Content-Encoding: gzip\');\r\n			$buffer = gzencode($buffer);\r\n		} elseif( function_exists(\'gzdeflate\') && preg_match(\'/deflate/i\', $encoding) ) {\r\n			header (\'Content-Encoding: deflate\');\r\n			$buffer = gzdeflate($buffer);\r\n		}\r\n	}\r\n\r\n\r\n	return $buffer;\r\n}\r\n\r\n\r\n/**\r\n * i18n from adi_plugins. Tks ;)\r\n * @param   $phrase   $atts\r\n */\r\nfunction pat_speeder_gTxt($phrase, $atts = array()) {\r\n// will check installed language strings before embedded English strings - to pick up Textpack\r\n// - for TXP standard strings gTxt() & pat_speeder_gTxt() are functionally equivalent\r\n	global $pat_speeder_gTxt;\r\n\r\n	if (strpos(gTxt($phrase, $atts), $phrase) !== FALSE) { // no TXP translation found\r\n		if (array_key_exists($phrase, $pat_speeder_gTxt)) // translation found\r\n			return strtr($pat_speeder_gTxt[$phrase], $atts);\r\n		else // last resort\r\n			return $phrase;\r\n		}\r\n	else // TXP translation\r\n		return gTxt($phrase, $atts);\r\n}\r\n\r\n\r\n/**\r\n * Plugin prefs.\r\n *\r\n * @param  \r\n * @return Insert this plugin prefs into \'txp_prefs\' table.\r\n */\r\nfunction pat_speeder_prefs()\r\n{\r\n\r\n	global $textarray, $pat_speeder_gTxt;\r\n\r\n	$textarray[\'pat_speeder_enable\'] = gTxt(\'pat_speeder_enable\');\r\n	$textarray[\'pat_speeder_gzip\'] = gTxt(\'pat_speeder_gzip\');\r\n	$textarray[\'pat_speeder_tags\'] = gTxt(\'pat_speeder_tags\');\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_enable\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_enable\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=24\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_gzip\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_gzip\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=25\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_tags\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_tags\', val=\'script,svg,pre,code\', type=1, event=\'admin\', html=\'text_input\', position=26\");\r\n\r\n	safe_repair(\'txp_prefs\');\r\n\r\n}\r\n\r\n\r\n/**\r\n * Delete plugin prefs & language strings.\r\n *\r\n * @param\r\n * @return Delete this plugin prefs.\r\n */\r\nfunction pat_speeder_cleanup()\r\n{\r\n\r\n	// Array of tables & rows to be removed\r\n	$els = array(\'txp_prefs\' => \'pat_speeder\', \'txp_lang\' => \'pat_speeder\');\r\n\r\n	// Process actions\r\n	foreach ($els as $table => $row) {\r\n		safe_delete($table, \"name LIKE \'\".str_replace(\'_\', \'\\_\', $row).\"\\_%\'\");\r\n		safe_repair($table);\r\n	}\r\n\r\n}\r\n', '/**\r\n * @name	  pat_speeder\r\n * @description	  Display page source on one line of code\r\n * @link 	  http://pat-speeder.cara-tm.com\r\n * @author	  Patrick LEFEVRE\r\n * @author_email  <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @type:         Public\r\n * @prefs:        no prefs\r\n * @order:        5\r\n * @version:      0.7.5\r\n * @license:      GPLv2\r\n */\r\n\r\n/**\r\n * This plugin tag registry\r\n *\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag_Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_speeder\');\r\n}\r\n\r\n\r\n/**\r\n * \r\n * \r\n */\r\nif (txpinterface == \'admin\') {\r\n\r\n	global $pat_speeder_gTxt;\r\n\r\n	register_callback(\'pat_speeder_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'pat_speeder_cleanup\', \'plugin_lifecycle.pat_speeder\', \'deleted\');\r\n\r\n	// Default plugin Textpack.\r\n	$pat_speeder_gTxt = array(\r\n		\'pat_speeder_enable\' => \'Activate pat_speeder?\',\r\n		\'pat_speeder_gzip\' => \'Active Gzip compression for pat_speeder?\',\r\n		\'pat_speeder_tags\' => \'Tags protection from pat_speeder\',\r\n	);\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin tag with attributes\r\n *\r\n * @param  array    Tag attributes\r\n * @return boolean  Call for main function\r\n */\r\nfunction pat_speeder($atts)\r\n{\r\n\r\n	global $prefs;\r\n\r\n	extract(lAtts(array(\r\n		\'enable\' => true,\r\n		\'gzip\'   => $prefs[\'pat_speeder_gzip\'],\r\n		\'code\'   => $prefs[\'pat_speeder_tags\'],\r\n	),$atts));\r\n\r\n	if ( $enable || ($prefs[\'pat_speeder_enable\'] && $enable) ) {\r\n		ob_start(function($buffer) use ($gzip, $code) {\r\n			return _pat_speeder_go($buffer, $gzip, $code);\r\n		});\r\n	}\r\n\r\n}\r\n\r\n/**\r\n * Main function\r\n * @param string $buffer\r\n * @return string HTML compressed content\r\n */\r\n\r\nfunction _pat_speeder_go($buffer, $gzip, $code)\r\n{\r\n	$codes = str_replace(\',\', \'|\', $code);\r\n\r\n	// remove uncessary elements from the source document\r\n	$buffer = preg_replace(\'/(?imx)(?>[^\\S ]\\s*|\\s{2,})(?=(?:(?:[^<]++|<(?!\\/?(?:textarea|\'.$codes.\')\\b))*+)(?:<(?>textarea|\'.$codes.\')\\b| \\z))/u\', \' \', $buffer);\r\n	// remove all comments except google ones\r\n	$buffer = preg_replace(\'/<!--([^<|\\[|>|go{2}gleo]).*?-->/s\', \'\', $buffer);\r\n\r\n	// server side compression if available\r\n	if( $gzip && isset($_SERVER[\'HTTP_ACCEPT_ENCODING\']) ) {\r\n		$encoding = $_SERVER[\'HTTP_ACCEPT_ENCODING\'];\r\n		if( function_exists(\'gzencode\') && preg_match(\'/gzip/i\', $encoding) ) {\r\n			header (\'Content-Encoding: gzip\');\r\n			$buffer = gzencode($buffer);\r\n		} elseif( function_exists(\'gzdeflate\') && preg_match(\'/deflate/i\', $encoding) ) {\r\n			header (\'Content-Encoding: deflate\');\r\n			$buffer = gzdeflate($buffer);\r\n		}\r\n	}\r\n\r\n\r\n	return $buffer;\r\n}\r\n\r\n\r\n/**\r\n * i18n from adi_plugins. Tks ;)\r\n * @param   $phrase   $atts\r\n */\r\nfunction pat_speeder_gTxt($phrase, $atts = array()) {\r\n// will check installed language strings before embedded English strings - to pick up Textpack\r\n// - for TXP standard strings gTxt() & pat_speeder_gTxt() are functionally equivalent\r\n	global $pat_speeder_gTxt;\r\n\r\n	if (strpos(gTxt($phrase, $atts), $phrase) !== FALSE) { // no TXP translation found\r\n		if (array_key_exists($phrase, $pat_speeder_gTxt)) // translation found\r\n			return strtr($pat_speeder_gTxt[$phrase], $atts);\r\n		else // last resort\r\n			return $phrase;\r\n		}\r\n	else // TXP translation\r\n		return gTxt($phrase, $atts);\r\n}\r\n\r\n\r\n/**\r\n * Plugin prefs.\r\n *\r\n * @param  \r\n * @return Insert this plugin prefs into \'txp_prefs\' table.\r\n */\r\nfunction pat_speeder_prefs()\r\n{\r\n\r\n	global $textarray, $pat_speeder_gTxt;\r\n\r\n	$textarray[\'pat_speeder_enable\'] = gTxt(\'pat_speeder_enable\');\r\n	$textarray[\'pat_speeder_gzip\'] = gTxt(\'pat_speeder_gzip\');\r\n	$textarray[\'pat_speeder_tags\'] = gTxt(\'pat_speeder_tags\');\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_enable\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_enable\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=24\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_gzip\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_gzip\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=25\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_speeder_tags\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_speeder_tags\', val=\'script,svg,pre,code\', type=1, event=\'admin\', html=\'text_input\', position=26\");\r\n\r\n	safe_repair(\'txp_prefs\');\r\n\r\n}\r\n\r\n\r\n/**\r\n * Delete plugin prefs & language strings.\r\n *\r\n * @param\r\n * @return Delete this plugin prefs.\r\n */\r\nfunction pat_speeder_cleanup()\r\n{\r\n\r\n	// Array of tables & rows to be removed\r\n	$els = array(\'txp_prefs\' => \'pat_speeder\', \'txp_lang\' => \'pat_speeder\');\r\n\r\n	// Process actions\r\n	foreach ($els as $table => $row) {\r\n		safe_delete($table, \"name LIKE \'\".str_replace(\'_\', \'\\_\', $row).\"\\_%\'\");\r\n		safe_repair($table);\r\n	}\r\n\r\n}\r\n', '6ecd8780771025828bc0fbe9ba8cea6f', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_admin_themes', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.40', 'Admin-side theme manager and editor', '	<h1 id=\"top\">smd_admin_themes</h1>\n\n	<p>Install, manage, edit and switch admin-side themes then share them with the community.</p>\n\n	<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>Create and Edit admin-side themes</li>\n		<li>Employ images, <span class=\"caps\">CSS</span>, JS, <span class=\"caps\">HTML</span>/PHP (for changing masthead, header &amp; footer) in your theme</li>\n		<li>Use constants in your <span class=\"caps\">CSS</span> files</li>\n		<li>Base your theme on an existing theme to tweak it to your taste</li>\n		<li>Export a theme package for distribution to the community in either .tar, .tgz, .bz2 or .zip format (requires <a href=\"http://stefdawson.com/smd_crunchers\">smd_crunchers</a> plugin)</li>\n		<li>Import / install other themes from the <a href=\"http://textgarden.org/administration-themes\">community repository</a></li>\n		<li>Administrator can set a default theme for all users, or enable per-user / per-role themes</li>\n	</ul>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p class=\"warning\">Requires Textpattern 4.5.0+ and <a href=\"http://stefdawson.com/smd_crunchers\">smd_crunchers</a> plugin for import/export</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1096/smd_admin_themes\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Textpattern <i>Admin-&gt;Plugins</i> pane, install and enable the plugin. A new tab labelled <i>Extensions-&gt;Admin themes</i> is created. <strong>Only publishers will see this tab</strong>.</p>\n\n	<p>The plugin should automatically install the preferences for you. Click &#8216;Preferences&#8217; from the <i>Extensions-&gt;Admin themes</i> tab or <i>Options</i> from the <i>Admin-&gt;Plugins</i> tab to alter, delete or reinstall them.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=30952\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete the plugin from the <em>Admin-&gt;Plugins</em> page. The preferences will automatically be removed. Please be aware that removing the plugin does <strong>not remove any themes you have installed, nor does it remove per-user theme preferences</strong>. Per-user prefs will simply be ignored when the plugin is deleted.</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>On the <i>Extensions-&gt;Admin themes</i> tab is a list or grid containing installed themes. Out of the box, there will be three available: <i>classic</i>, <i>hive</i> and <i>remora</i>. <strong>It&#8217;s advisable not to change these themes</strong> so you can always go back to them if everything goes sideways. Plus it&#8217;s a very useful starting point from which to <a href=\"#clone\">base</a> a <a href=\"#new\">new theme</a>.</p>\n\n	<p>In the list layout, the columns are:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">Theme</span></dt>\n		<dd>The name of the theme and (optional) thumbnail assigned to it. Click to <a href=\"#switch\">activate</a> the theme.</dd>\n		<dd>If the theme is based on another user&#8217;s theme, this dependency will be shown in parentheses after the theme name.</dd>\n		<dt><span class=\"atnm\">Author</span></dt>\n		<dd>You! Linked to your web site if you supply it in your <a href=\"#about\">Manifest</a>.</dd>\n		<dt><span class=\"atnm\">Version</span></dt>\n		<dd>Theme version number in <code>major.minor.subrev</code> notation. If a newer version of a theme is available from Textgarden, the version number will be hyperlinked to the theme on Textgarden so you may download/install it.</dd>\n		<dd>The versions are checked, at most, once every 24 hours to avoid hammering the server.</dd>\n		<dt><span class=\"atnm\">Description</span></dt>\n		<dd>Brief one- or two-liner explaining your theme. If you have offered a help <span class=\"caps\">URL</span> in your manifest, it will be linked here as well.</dd>\n		<dt><span class=\"atnm\">Actions</span></dt>\n		<dd>Actions to perform on each theme:\n	<dl>\n		<dt><span class=\"atnm\">[New theme]</span></dt>\n		<dd><a href=\"#new\">Create</a> a new blank, empty folder ready for your theme. No spaces / odd characters are allowed in the name and it must not start with a number.</dd>\n		<dt><span class=\"atnm\">[Edit]</span></dt>\n		<dd><a href=\"#edit_theme\">Edit</a> the various details, files and content within the theme.</dd>\n		<dt><span class=\"atnm\">[Base]</span></dt>\n		<dd><a href=\"#clone\">Extend</a> one of the existing themes to make your own changes under a new name.</dd>\n		<dt><span class=\"atnm\">[Export]</span></dt>\n		<dd><a href=\"#export\">Save</a> the theme package to your hard drive for distribution/backup in your chosen compression format.</dd>\n		<dt><span class=\"atnm\">[Delete]</span></dt>\n		<dd>Completely <a href=\"#delete\">remove</a> the theme. Not available to the core themes, for obvious reasons! Also not available if a theme has been used as a basis for another theme in the current site.</dd>\n	</dl></dd>\n	</dl>\n\n	<p>The currently selected theme is shaded and the current default theme that is in effect is highlighted. The grid view has parts of the above information, laid out slightly differently.</p>\n\n	<h2 id=\"switch\">Switching themes</h2>\n\n	<p>As the administrator, you can play God over everyone else&#8217;s themes. You are free to install as many as you wish and switch between them without affecting any other logins. Nobody else will even know you have installed the plugin.</p>\n\n	<p>If, however, you visit the <a href=\"#setup\">Preferences</a> page you can configure the themes that others use. There are three mechanisms to choose from:</p>\n\n	<ul>\n		<li>Force one theme for all users (except yourself)</li>\n		<li>Force one theme for each user privilege level (except yourself)</li>\n		<li>Allow all users (except yourself) to pick their favourite theme from a shortlist you define</li>\n	</ul>\n\n	<p>At all times, you can choose any theme you please for your own use. This allows trialling, tweaking and testing of themes before unleashing them on your user base.</p>\n\n	<h2 id=\"setup\">Theme preferences</h2>\n\n	<ul>\n		<li><span class=\"atnm\">Case-sensitive theme list</span> : When set to <code>Yes</code>, upper-case theme names take precedence over lower case theme names and will be listed first. For example, 3 theme called &#8216;Angelic&#8217;, &#8216;barbaric&#8217; and &#8216;Cohesive&#8217; will be ordered: <code>Angelic, Cohesive, barbaric</code>. Set to <code>No</code> to mix upper and lower-case themes together (order is then <code>Angelic, barbaric, Cohesive</code> ). The core themes are always listed first.</li>\n		<li><span class=\"atnm\">Export compression type</span> : <span class=\"warning\">Only if <a href=\"http://stefdawson.com/smd_crunchers\">smd_crunchers</a> is installed</span> Depending on your version of <span class=\"caps\">PHP</span> and which modules have been compiled into it, you will see a list of compression formats here. Choose one that suits you as the default in which to export your theme packages. You may choose from <em>Tar</em>, <em>GZip</em> (actually tar+gzip), <em>Bzip2</em> (tar+bzip), or <em>Zip</em> (the default).</li>\n		<li><span class=\"atnm\">Layout method</span> : Choose from a List View (useful if you prefer to hide or use small thumbs) or a Grid View of installed themes. This setting applies to both admin and user theme selection tabs. Default: Grid View</li>\n		<li><span class=\"atnm\">Thumb dimensions</span> : Set the thumbnail dimensions in the theme list. Set either dimension to 0 to turn off thumbnail display. Default: 260 &#215; 150px</li>\n		<li><span class=\"atnm\">Filename format</span> : Define the format of the exported theme. You can use <code>{theme}</code> and <code>{version}</code> replacement variables in this field. For example, <code>{theme}_v{version}</code>. Default: <code>{theme}</code></li>\n		<li><span class=\"atnm\">Max theme file size</span> : Any (compressed) theme that exceeds this file size will not be imported. Default: <code>512000</code></li>\n		<li><span class=\"atnm\">Default theme</span> : Set the theme for all other users. If using &#8216;One theme for everyone&#8217; the theme chosen here is forced upon all users. If using any of the other mechanisms, this theme is a fallback should something go pear-shaped.</li>\n		<li><span class=\"atnm\">Theme system</span> : As detailed in <a href=\"#switch\">Switching themes</a>, you may choose the way the Textpattern interface is presented to other users:\n	<ul>\n		<li><span class=\"atnm\">One theme for all</span> : uses the Default theme for all users; they have no choice in the matter</li>\n		<li><span class=\"atnm\">One theme per privilege level</span> : allows you to assign one or more privilege levels to a theme. Choose a theme from the left-hand list and then shift/ctrl-click the privilege levels to assign them. Flick between themes in the left column to see which ones are set for various user levels, or hover over a privilege level to see a tooltip. Only one level can be assigned to a theme so if you click a level that has already been assigned to another theme, it will be removed from its original assignment. Note you must Save the changes for them to take effect</li>\n		<li><span class=\"atnm\">One theme per user</span> : define a shortlist from the available installed themes to offer to all users. A new panel will become available to other users under <em>Admin</em> that is essentially a cut-down version of the <em>Admin themes</em> tab. Users can switch between any one of the themes you have permitted.</li>\n	</ul></li>\n	</ul>\n\n	<p>Remember to click Save to apply the changes. Clicking any other panel will undo any modifications you may have made.</p>\n\n	<h2 id=\"browse\">Browse themes</h2>\n\n	<p>Click this button to be taken to the central admin theme repository where you may browse themes and download them ready for installation.</p>\n\n	<h2 id=\"import\">Install a theme</h2>\n\n	<p class=\"warning\">Only available if <a href=\"http://stefdawson.com/smd_crunchers\">smd_crunchers</a> is installed.</p>\n\n	<p>Locate a theme package on your hard drive or paste a <span class=\"caps\">URL</span> to a package in the box at the top of the page. Hit <strong>Upload</strong> and it will be installed. If it already exists it will be overwritten. Note that you may receive a warning if the theme is packaged using a compression algorithm that is not built into your version of <span class=\"caps\">PHP</span>. A list of supported compression types is given below the upload box.</p>\n\n	<h2 id=\"new\">Create a new (empty) theme</h2>\n\n	<p>Click <strong>Create new theme</strong> and a box will slide down allowing you to give your theme a name.</p>\n\n	<p>No spaces or odd characters are permitted, because the name you enter is the name of the folder that will be created in the <em>theme</em> folder and the name of your theme&#8217;s class. If you do manage to use odd characters, the name will be &#8220;dumbed down&#8221; for you.</p>\n\n	<p>In order to avoid name clashes with other people&#8217;s themes, it is recommended that all themes you design begin with a unique 3-letter prefix of your own choosing, plus an underscore &#8212; exactly like plugins. Thus, smd_BlueMeanie might well be the name of a theme. When you have named your theme, click <b>[Go]</b> to create a folder in which it can reside.</p>\n\n	<p>Once the name is set, the only ways to rename it are to:</p>\n\n	<ol>\n		<li>edit the theme&#8217;s main <code>.php</code> filename from the plugin&#8217;s interface. The folder will be automatically renamed to match.</li>\n		<li>manually rename the folder and <code>.php</code> filename.</li>\n	</ol>\n\n	<p>In both cases, you <strong>must</strong> also alter the theme&#8217;s class name inside the <code>.php</code> file itself.</p>\n\n	<h2 id=\"clone\">Extend an existing theme</h2>\n\n	<p>Click <b>[Base]</b> to create a new theme based on another. A box will slide down so you may enter the name of your copy.</p>\n\n	<p>Observe the <a href=\"#new\">naming convention</a>, then click <b>[Go]</b> to create the new theme.</p>\n\n	<p><span class=\"warning\">Please remember, your theme will be dependent on the chosen theme and <strong>both</strong> must be present on a user&#8217;s system for the theme to work</span>. It is worth ensuring that users of your theme are made aware of this dependency to avoid unnecessary frustration; perhaps add a link to the theme&#8217;s base, mention the dependency in the Help link and/or in the theme&#8217;s article on Textgarden. You will not be able to delete any theme from the list that has such a dependency, otherwise your admin side will stop working.</p>\n\n	<h2 id=\"export\">Export a theme</h2>\n\n	<p class=\"warning\">Only available if <a href=\"http://stefdawson.com/smd_crunchers\">smd_crunchers</a> is installed.</p>\n\n	<p>If you wish to back up a theme or share it with others, click <b>[Export]</b>. A box will slide down offering you a choice of compression formats; the default is the format you chose from the <a href=\"#setup\">Preferences</a> page.</p>\n\n	<p>When you have chosen the appropriate format, click <b>[Go]</b> to export the theme and save it to your computer. All related files in the theme will be packaged together into a single download file for convenience.</p>\n\n	<p>The filename is governed by the <em>Filename format</em> preference.</p>\n\n	<h2 id=\"delete\">Delete a theme</h2>\n\n	<p>Hit this (and confirm you are sure) to remove the entire theme folder and contents from the file system. <span class=\"warning\">This is not undoable, so be careful</span>.</p>\n\n	<h2 id=\"edit_theme\">Edit a theme</h2>\n\n	<p>A theme is a collection of files. The files all reside in their own self-contained folder under <code>textpattern/theme</code>. The name of the folder is the name of your theme, hence the reason it cannot contain any obscure characters that would be invalid in the filesystem or <span class=\"caps\">PHP</span> variables.</p>\n\n	<p>When you click <b>[Edit]</b> you will be taken to a screen that allows you to make changes to the theme. The Edit screen is broken into two sub-panels, as follows:</p>\n\n	<h3 id=\"edit_content\">Content</h3>\n\n	<p>This is where you edit the contents of the files in your theme. The small edit box at the top is the filename and the large text area is its content. Click <b>Save</b> to commit changes. You may freely rename any file you are editing.</p>\n\n	<p class=\"warning\">Important: if you select a file, edit it, rename it, and click <b>Save</b> you will overwrite whatever was in the previous file &#8212; even if you empty the contents of the textarea in an effort to make a &#8220;new&#8221; one. If you wish to create a new file, you must click the <b>New file</b> button to ensure you are not editing an existing file by mistake.</p>\n\n	<p>When creating a new file, another box will appear allowing you to specify the folder for the file. Although files can be renamed, folders cannot. Once a file is inside a folder it cannot be moved to a different folder from the user interface: you will have to use your <span class=\"caps\">FTP</span> program to move files or rename folders.</p>\n\n	<p>It is also worth noting that file permissions vary from host to host. If you try and save a theme file and receive a yukky warning message about not being able to write the file, try increasing the permissions on the theme folder and all files beneath it.</p>\n\n	<p>The theme files are distributed in Textpattern with <code>644</code> permissions which should be fine for a standard host. Some shared hosting companies &#8212; and these are usually the ones that tell you to set your <code>files</code> and <code>images</code> folders to 777 in order to make them writable by Textpattern &#8212; run Apache/PHP in a different way which might require higher permissions in order to write to the theme folder. This is unfortunately outside the scope of the plugin so you will have to experiment if it causes you problems.</p>\n\n	<h3>File handling</h3>\n\n	<p>Alongside the editing area is where theme files are managed. Firstly is a select list of all installed themes; the current theme being edited is selected. Changing the theme here will immediately allow you to edit another theme. This is very useful if you are copying and pasting files from one theme to another and saves you having to go back to the theme list first.</p>\n\n	<p>Beneath that are two buttons:</p>\n\n	<ul>\n		<li><span class=\"atnm\">New file</span> : Create a new, empty file in your theme folder. Nothing is actually created until you hit <b>Save</b> but using this button ensures you begin with a clean slate and don&#8217;t accidentally overwrite the previous file you were editing</li>\n		<li><span class=\"atnm\">Theme list</span> : A shortcut back to the main theme list (equivalent to clicking the <em>Admin themes</em> menu item)</li>\n	</ul>\n\n	<p>Next is an upload area allowing you to upload new files and images to your theme. You can browse for as many files to upload as you wish &#8212; subject to the maximum file size restriction as set in your <i>Advanced Preferences</i> &#8212; and (optionally) store them in the nominated folder. These can be any of the supported file types, including images.</p>\n\n	<p>Below those buttons are a bunch of collapsible sections containing all the files that make up your theme. Click:</p>\n\n	<ul>\n		<li>a section heading to toggle the group of files visible/hidden</li>\n		<li>a filename to load it into the text area for editing</li>\n		<li>a regular (binary) image filename to preview it: svg files are previewed separately while editing them in the text area</li>\n	</ul>\n\n	<p>Note that other binary files may not be edited and are prevented from being clicked.</p>\n\n	<p>You can use the checkboxes alongside each file to select files for deletion. Use the &#8216;With selected&#8230;&#8217; box at the bottom of the list for this task. You may shift-click multiple files to select a range.</p>\n\n	<h4 id=\"edit_img\">Images</h4>\n\n	<p class=\"warning\">Images must have a lower-case file extension or they will not show up in the list (although they will be uploaded). Supported extensions are <code>.jpg</code>, <code>.jpeg</code>, <code>.gif</code>, <code>.png</code> and <code>.svg</code>.</p>\n\n	<p>Upload any images that your theme uses. Note the best way to refer to image <span class=\"caps\">URL</span>s in stylesheets is with a relative path:</p>\n\n<pre><code>.myStyle {\n   background: url(my-image.png);\n}\n</code></pre>\n\n	<p>Each theme can also contain an optional thumbnail. This is an image of 260&#215;150px in size, called <code>screenshot.png</code> (or <code>.jpg</code>, <code>.gif</code>, etc) in your root theme folder. Note it will be squashed / stretched to fit your thumbnail dimensions set in the plugin prefs.</p>\n\n	<p>If you select an <span class=\"caps\">SVG</span> image it will be opened up in the editor just like a regular file. You may preview it by clicking the <strong>Preview</strong> link alongside the filename input box. All other image files are previewed by clicking their name in the file list.</p>\n\n	<h4 id=\"edit_css\">Stylesheets</h4>\n\n	<p class=\"warning\">Files can be any valid filename but <span class=\"caps\">MUST</span> end in <code>.css</code> or <code>.scss</code> (i.e. Sass files)</p>\n\n	<p>The meat and potatoes of your theme. Add as many sheets as you like and load them from your <span class=\"caps\">PHP</span> file(s).</p>\n\n	<h4 id=\"edit_ssc\">Styleplates</h4>\n\n	<p class=\"warning\">Files can be any valid filename but <span class=\"caps\">MUST</span> end in <code>.ssc</code></p>\n\n	<p>A special version of a stylesheet that allows you to define constants throughout your sheet. Write your stylesheet in exactly the same way as normal, but where you find yourself, for example, using the same color value repeatedly or the same rules block, you can save yourself typing by using constants:</p>\n\n<pre class=\"prettyprint\"><code>@branding: #123456;\n#header {\n  color: @branding;\n}\nh2 {\n  color: @branding;\n}\n</code></pre>\n\n	<p>You can also specify whole rule blocks like this (<em>Note the colon after the block name</em>) :</p>\n\n<pre><code>@my_block: {\n  margin:0 20px;\n  border:3px solid #999;\n  font-size:120%;\n}\n#header {\n  color: @branding;\n  position:relative;\n  @my_block;\n}\nh2 {\n  @my_block;\n}\n</code></pre>\n\n	<p>If you put all your constants at the top of the <code>.ssc</code> file they will be removed from the generated css file.</p>\n\n	<p>When you save a <code>.ssc</code> file, a corresponding-named <code>.css</code> file will also be created. The two will be kept in sync as far as practicable (even if you rename the original file). When it comes to deletion though, they are treated separately. This allows you to either keep the <code>.ssc</code> file in the distribution archive or remove it prior to export.</p>\n\n	<h4 id=\"edit_js\">Javascript</h4>\n\n	<p class=\"warning\">Files can be any valid filename but <span class=\"caps\">MUST</span> end in <code>.js</code></p>\n\n	<p>Any trickery you may wish to employ in your theme can be written in these files. Remember that jQuery is loaded by default on the admin side so you can play with that.</p>\n\n	<h4 id=\"edit_php\"><span class=\"caps\">PHP</span></h4>\n\n	<p class=\"warning\">One file must be named <strong>exactly</strong> the same as your theme, i.e. <code>smd_BlueMeanie.php</code>. Any other files must end in <code>.php</code></p>\n\n	<p>Consult the <a href=\"http://textpattern.net/wiki/index.php?title=Creating_an_Admin_Side_Theme\">Admin Side theme guide</a> on Textbook for the rules on making themes. Note that corrupt themes are detected to the best of the plugin&#8217;s ability and you may not export, base or switch to any theme that is broken.</p>\n\n	<p>If you rename the theme&#8217;s core <span class=\"caps\">PHP</span> file (i.e. the one with the same name as the theme folder itself) the plugin will attempt to rename the folder as well; essentially renaming the theme. This should prevent a theme from becoming uneditable, but it is up to you to keep the class name in sync with the file name. If at any time you go back to the theme list/grid and find a theme exists but has no information associated with it, chances are it&#8217;s because the class name doesn&#8217;t match the theme&#8217;s folder name. Just hit <b>[Edit]</b> and alter the <span class=\"caps\">PHP</span> file to bring everything back on track.</p>\n\n	<h4 id=\"edit_js\">Text / Textile docs</h4>\n\n	<p>Any files with a <code>.txt</code> or <code>.textile</code> extension are supported. Textile files can be previewed by clicking the <strong>Preview</strong> link alongside the filename input box. Note that when the main textarea is edited, the preview becomes unavailable because the file must be saved to update it first.</p>\n\n	<h4 id=\"edit_other\">Others</h4>\n\n	<p>Any files that are not of the above supported types appear here. If you do try to create a file that is not supported, a warning will be issued. You must hit <b>Save</b> again to confirm you really wish to create the file.</p>\n\n	<h4 id=\"edit_folder\">Folders</h4>\n\n	<p>Any folders that your theme is using will be listed here. If you click the &#8216;x&#8217; alongside any one, it <strong>and all its contents</strong> will be deleted. By contrast, if you delete all files from a folder using the regular &#8216;With selected&#8217; mechanism, the folder itself will remain in the filesystem (although it won&#8217;t be shown) in this folder list.</p>\n\n	<h3 id=\"about\">Manifest</h3>\n\n	<p>In your main <span class=\"caps\">PHP</span> file is a function called <code>manifest()</code> which contains info about you and your theme. Edit the values here to stamp your mark on the theme. Note that if your description contains apostrophes they must be <em>escaped</em> by preceding them with a single backslash <code>\\</code>. e.g.</p>\n\n<pre><code>&#39;description&#39; =&gt; &#39;Don\\&#39;t fear the reaper!&#39;,\n</code></pre>\n\n	<h2 id=\"peruser\">Per-user theme selection</h2>\n\n	<p>If an administrator chooses to allow per-user themes, a new panel appears under the Admin menu item of all users (except administrators). This simply displays all available themes &#8212; ones that the administrator has explicitly allowed.</p>\n\n	<p>Clicking a theme name or thumbnail will immediately switch to that theme until the user chooses another or the administrator changes theme system.</p>\n\n	<h2 id=\"author\">Author and credits</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n	<p>This plugin would not have existed without the dedication, ideas and genius of Robert Wetzlmayr for the theme support. Plus of course the army of beta testers, ideas hamsters and tireless theme advocates, most notably Stuart Butcher, Dale Chapman, Dave DeSandro, Mike Dennis and Phil Wareham.</p>\n\n	<p>Kudos and thanks to you all.</p>\n\n	<h2 id=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>12 Jun 2009 | 0.1 | Initial public beta</li>\n		<li>03 Sep 2009 | 0.11 | Improved Textgarden theme feed and links ; improved file support (both thanks thebombsite) ; made global theme more obvious ; altered prefs panel a bit ; added <code>.ssc</code> support ; ignored .svn subdirs (all thanks pieman) ; improved warning messages ; misc fixes and housekeeping</li>\n		<li>02 Nov 2009 | 0.12 | Reduced theme feed clutter in the prefs table</li>\n		<li>04 Nov 2009 | 0.13 | Removed ereg_replace to conform with PHP5.3+ (thanks thebombsite) ; changed &#8216;global&#8217; to &#8216;default&#8217; ; fixed rogue untranslated string</li>\n		<li>09 Nov 2009 | 0.14 | Moved <span class=\"caps\">URL</span>s in line with Textgarden layout alterations (thanks for the headsup thebombsite) ; made feeds more robust in the event Textgarden is down / under maintenance (thanks mlarino)</li>\n		<li>09 Feb 2010 | 0.2 | Native support for zip importing and exporting (almost) irrespective of your <span class=\"caps\">PHP</span> environment ; zip is now the default system ; better notification of supported compression types and more robust rejection if something is missing</li>\n		<li>10 Feb 2010 | 0.21 | Fixed bzip2 warning (thanks thebombsite) ; fixed recursive mkdir problem with bzip2 files (thanks <span class=\"caps\">PHP</span> manual comments) ; fixed rogue temp file left behind with zip import</li>\n		<li>15 Feb 2010 | 0.22 | When renaming the core theme <span class=\"caps\">PHP</span> file, automatically rename folder too (thanks floodfish)</li>\n		<li>28 Feb 2010 | 0.23 | Rudimentary validation of zip file contents (thanks Tuts_and_Tipps); fixed export notification</li>\n		<li>06 Aug 2010 | 0.24 | Fixed <code>fsockopen()</code> and undefined index warnings if not connected to the Internet / prefs not installed</li>\n		<li>01 Nov 2010 | 0.25 | Better theme integration (colours, etc) ; fixed duplicate page renders on edit screen and added filename format option (both thanks maverick)</li>\n		<li>21 Feb 2011 | 0.26 | Fixed import bug if more than one dot in filename (thanks philwareham)</li>\n		<li><del>08 Jun 2011 | 0.27 | Fixed erroneous stripping of slashes on save ; added txp path pref (thanks maniqui)</del> <strong><span class=\"caps\">VERSION</span> <span class=\"caps\">REVOKED</span></strong></li>\n		<li>28 Sep 2011 | 0.28 | Fixed erroneous stripping of slashes on save</li>\n		<li>29 Jan 2012 | 0.30 | Moved (de)compression algorithms to smd_crunchers: now a required library for this plugin if you wish to import/export</li>\n		<li>29 Jan 2012 | 0.31 | D&#8217;oh, moved <code>smd_mkdir_recursive()</code> to smd_crunchers too</li>\n		<li>11 Sep 2012 | 0.40 | Updated for Txp 4.5.0 (thanks philwareham) ; better folder support ; improved file and image handling ; supports sass, svg, and textile types ; preview facility added ; multi-file uploads supported ; placeholder image added for themes without screenshots (thanks philwareham)</li>\n	</ul>', '/**\n * smd_admin_themes\n *\n * A Textpattern CMS plugin for creating, editing, testing, installing and distributing admin-side themes\n *  -> Employ images, CSS, JS, HTML/PHP in your theme\n *  -> Use constants in your CSS files\n *  -> Base your theme on an existing theme to tweak it to your taste\n *  -> Export a theme package for distribution to the community in either .tar, .tgz, .bz2 or .zip format (requires smd_crunchers plugin)\n *  -> Import / install other themes from the \"community repository\":http://textgarden.org/administration-themes\n *  -> Administrator can set a default theme for all users, or enable per-user / per-role themes\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * Verify that files can\'t be uploaded/created outside of the theme\'s dir\n//  * \'empty_info\' message shouldn\'t fire for bad file upload\n// TOFIX:\n//  * rawurlencode/decode for files with spaces in them. Currently can\'t edit contents of file with spaces in it as they get encoded as %20. If encoded, with each save they get %2520 added and it gets longer and longer....\n\nif (@txpinterface == \'admin\') {\n	// Silence warnings because, although the plugin requires smd_crunchers for the\n	// import/export features, it\'ll function in a reduced capacity without it\n	@require_plugin(\'smd_crunchers\');\n\n	global $smd_at_event, $smd_at_fullev, $smd_at_adm_event, $smd_at_privs,\n		$smd_core_themes, $prefs, $smd_at_feedurl, $smd_at_theme_repo;\n\n	$smd_at_event = \'smd_at\';\n	$smd_at_fullev = \'smd_admin_themes\';\n	$smd_at_adm_event = \'smd_admat\';\n	$smd_at_privs = \'1\';\n	$smd_at_feedurl = \'http://textgarden.org/admin-themes-feed\';\n	$smd_at_theme_repo = \'http://textgarden.org/administration-themes\';\n\n	$smd_core_themes = array(\'classic\', \'hive\', \'remora\');\n\n	add_privs($smd_at_event, $smd_at_privs);\n	add_privs(\'plugin_prefs.\'.$smd_at_fullev, $smd_at_privs);\n	register_tab(\"extensions\", $smd_at_event, gTxt(\'smd_at_tab_name\'));\n	register_callback(\'smd_at_options\', \'plugin_prefs.\'.$smd_at_fullev);\n	register_callback(\'smd_at_welcome\', \'plugin_lifecycle.\'.$smd_at_fullev);\n	register_callback(\'smd_at\', $smd_at_event);\n	register_callback(\'smd_at_per\', \'admin_side\', \'theme_name\');\n	register_callback(\'smd_at_link\', \'prefs_ui\', \'theme_name\');\n	register_callback(\'smd_at_inject_css\', \'admin_side\', \'head_end\');\n\n	if (isset($prefs[\'smd_at_system\']) && $prefs[\'smd_at_system\'] == 2) {\n		add_privs($smd_at_adm_event, \'2,3,4,5,6\');\n		register_tab(\"admin\", $smd_at_adm_event, gTxt(\'smd_at_skinner\'));\n		register_callback(\'smd_admat\', $smd_at_adm_event);\n	}\n}\n\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers\nfunction smd_at_get_style_rules() {\n	$smd_at_styles = array(\n		\'smd_at\' =>\'\n.smd_at_update { font-weight:bold; color:#900; }\n#smd_at_images { margin:0px 6px -4px 0; }\n.smd_skin_filetypes { margin:12px 0; }\nsmd_fakebtn { color:#555; cursor:pointer; }\n\'\n	);\n\n	return $smd_at_styles;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_inject_css($evt, $stp) {\n	global $smd_at_event, $event;\n\n	if ($event == $smd_at_event) {\n		$smd_at_styles = smd_at_get_style_rules();\n		echo \'<style type=\"text/css\">\', $smd_at_styles[\'smd_at\'], \'</style>\';\n	}\n\n	return;\n}\n\n// ------------------------\n// Plugin jumpoff point\nfunction smd_at($evt, $stp) {\n	$available_steps = array(\n		\'smd_at_clone\'         => true,\n		\'smd_at_delete\'        => true,\n		\'smd_at_delete_folder\' => true,\n		\'smd_at_edit\'          => false,\n		\'smd_at_multi_edit\'    => true,\n		\'smd_at_export\'        => true,\n		\'smd_at_import\'        => true,\n		\'smd_at_newfile\'       => false,\n		\'smd_at_prefs_install\' => false,\n		\'smd_at_prefs_remove\'  => false,\n		\'smd_at_prefs_update\'  => true,\n		\'smd_at_preview\'       => true,\n		\'smd_at_rename\'        => true,\n		\'smd_at_save\'          => true,\n		\'smd_at_setup\'         => false,\n		\'smd_at_switch\'        => true,\n		\'smd_at_upload\'        => true,\n		\'smd_at_change_pageby\' => true,\n		\'save_pane_state\'      => true,\n	);\n\n	if ($stp == \'save_pane_state\') {\n		smd_at_save_pane_state();\n	} else if ($stp && bouncer($stp, $available_steps)) {\n		$stp();\n	} else {\n		smd_at_list();\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_welcome($event, $step) {\n	$msg = \'\';\n	switch ($step) {\n		case \'installed\':\n			smd_at_prefs_install(0);\n			break;\n		case \'deleted\':\n			smd_at_prefs_remove(0);\n			break;\n	}\n	return $msg;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_link() {\n	global $smd_at_event;\n	return href(gTxt(\'smd_at_manage_lbl\'), \'?event=\'.$smd_at_event);\n}\n\n// ------------------------\nfunction smd_at_list($message=\'\') {\n	global $smd_at_event, $prefs, $theme, $smd_core_themes, $smd_at_fullev, $smd_at_feedurl, $smd_at_theme_repo;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n\n	extract(gpsa(array(\'step\', \'page\', \'crit\', \'search_method\', \'skin\')));\n	$message = ($message) ? $message : gps(\'message\');\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$stylerule = ($layout==0) ? \'list\' : \'grid\';\n	$tw = $at_prefs[\'smd_at_tw\'] + 10;\n	$th = $at_prefs[\'smd_at_th\'] + 80;\n	$helpLink = \"?event=plugin&step=plugin_help&name=$smd_at_fullev#usage\";\n\n	$pageby = (gps(\'qty\')) ? gps(\'qty\') : ((cs(\'smd_at_pageby\')) ? cs(\'smd_at_pageby\') : 15);\n	$max_theme_size = $at_prefs[\'smd_at_max_theme_size\'];\n\n	$curr_skin = (smd_at_exists($theme->name)) ? $theme->name : \'classic\';\n	$skin_list = smd_at_read_skins();\n	$crushers = smd_at_crush_options(\'compress\');\n	$uncrushers = smd_at_crush_options(\'decompress\');\n	$bases = array();\n\n	// Get a list of ancestor themes (i.e. dependencies)\n	foreach ($skin_list as $skina) {\n		$manifest = smd_at_read_skinfo($skina);\n		if ($manifest) {\n			if ($manifest[\'based_on\']) {\n				$bases[] = $manifest[\'based_on\'];\n			}\n		}\n	}\n	$bases = array_unique($bases);\n\n	pagetop(gTxt(\'smd_at_manage_lbl\'),$message);\n\n	// Handle paging\n	$total = count($skin_list);\n	$limit = max(@$pageby, 15);\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\n	$skin_list = array_slice($skin_list, $offset, $limit);\n\n	$qs = array(\n		\"event\" => $smd_at_event,\n		\"page\" => $page,\n	);\n\n	echo script_js(<<<EOJS\nfunction smd_getcrush(skin) {\n	skinHandle = \"crush_\"+skin;\n	jQuery(\'.smd_popup\').each(function() {\n		box = jQuery(this);\n		if (box.attr(\"id\") == skinHandle) {\n			if (box.css(\"display\") == \"none\") {\n				box.slideDown(\'fast\');\n			} else {\n				box.slideUp(\'fast\');\n			}\n		} else {\n			box.slideUp(\'fast\');\n		}\n	});\n	jQuery(\'#\'+skinHandle+\' a\').click(function() {\n		jQuery(this).attr(\"href\", \"?event={$smd_at_event}&skin=\"+skin+\"&step=smd_at_export&crush=\"+jQuery(\'#\'+skinHandle+\' input[type=\\\'radio\\\']:checked\').val()+\"&page=\"+{$page}+\"&_txp_token=\"+textpattern._txp_token);\n	});\n	return false;\n}\n\nfunction smd_getval(skin, step) {\n	skinHandle = \"pop_\"+skin;\n	jQuery(\'.smd_popup\').each(function() {\n		box = jQuery(this);\n		if (box.attr(\"id\") == skinHandle) {\n			if (box.css(\"display\") == \"none\") {\n				box.slideDown(\'fast\');\n				jQuery(\'#\'+skinHandle+\' input\').focus();\n			} else {\n				box.slideUp(\'fast\');\n			}\n		} else {\n			box.slideUp(\'fast\');\n		}\n	});\n	jQuery(\'#\'+skinHandle+\' a\').click(function() {\n		jQuery(this).attr(\"href\", \"?event={$smd_at_event}&skin=\"+((skin==\'new\')? \'\' : skin)+\"&step=\"+step+\"&new_skin=\"+jQuery(\'#\'+skinHandle+\' input\').val()+\"&page=\"+{$page}+\"&_txp_token=\"+textpattern._txp_token);\n	});\n\n	jQuery(\'#\'+skinHandle+\' input\').keyup(function () {\n		this.value = this.value.replace(/[^a-zA-Z0-9_]/g,\'\');\n		while (this.value.length > 0 && this.value.substring(0,1).match(/[0-9]/)) {\n			this.value = this.value.substring(1);\n		}\n	});\n\n	return false;\n}\nEOJS\n	);\n\n	// List the available skins\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_at_tab_name\'), \'</h1>\',\n		n, \'<div id=\"smd_control\" class=\"txp-control-panel\">\',\n		n, (($uncrushers) ? upload_form(gTxt(\'smd_at_install_skin\'), \'\', \'smd_at_import\', $smd_at_event, \'\', $max_theme_size).n.\'<p><small>\'.gTxt(\'smd_at_supported_import\', array(\'{types}\' => (($uncrushers) ? join(\', \', $uncrushers) : gTxt(\'none\')) )).\'</small></p>\' : \'\').\n		n, \'<p class=\"txp-buttons\">\',\n			n, \'<a href=\"?event=\', $smd_at_event, a, \'step=smd_at_setup\">\', gTxt(\'smd_at_setup\'), \'</a>\',\n			n, gTxt(\'smd_at_help_link\', array(\'{link}\' => $helpLink)),\n			n, \'<a href=\"\'.$smd_at_theme_repo.\'\">\', gTxt(\'smd_at_find_theme\'), \'</a>\',\n			n, \'<a href=\"#\" onclick=\"return smd_getval(\\\'new\\\', \\\'smd_at_clone\\\');\">\'.gTxt(\'smd_at_new\').\'</a>\',\n		n, \'</p>\',\n		n, \'<p id=\"pop_new\" class=\"smd_popup empty\">\',\n			n, \'<span>\', gTxt(\'smd_at_new_skin\'), \'</span>\', n, \'<input type=\"text\" name=\"smd_text_name\" value=\"\" size=\"\', INPUT_REGULAR, \'\" /> <a href=\"#\">[\', gTxt(\'smd_at_confirm\'), \']</a>\',\n		n, \'</p>\',\n		n, \'</div>\';\n\n	echo \'<div class=\"txp-container\">\',\n		\'<form method=\"post\" name=\"longform\" action=\"\', join_qs($qs), \'\">\';\n\n	$hdrow = hed(gTxt(\'smd_at_list_title\'), 2);\n	if ($layout==\"0\") {\n		echo \'<div class=\"txp-listtables\">\',\n			startTable(\'\',\'\',\'txp-list\',5),\n			tr(\n				tda($hdrow, \' colspan=\"7\"\')\n			),\n			assHead(\n				gTxt(\'smd_at_skin_gbl\'),\n				\'author\',\n				gTxt(\'version\'),\n				\'description\',\n				gTxt(\'smd_at_actions\')\n			);\n	} else {\n		echo $hdrow, \'<div class=\"txp-grid\">\';\n	}\n	if( is_callable(\'fsockopen\') )\n		$transport = \'fsock\';\n	elseif( is_callable(\'curl_init\') ) {\n		$transport = \'curl\';\n	} else {\n		$transport = \'\';\n	}\n\n	$feed = \'\';\n	switch ($transport) {\n		case \'curl\':\n			$c = curl_init();\n			curl_setopt($c, CURLOPT_URL, $smd_at_feedurl);\n			curl_setopt($c, CURLOPT_REFERER, hu);\n			curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\n			curl_setopt($c, CURLOPT_VERBOSE, false);\n			curl_setopt($c, CURLOPT_TIMEOUT, 10);\n			$feed = curl_exec($c);\n			break;\n		case \'fsock\':\n			$url = parse_url($smd_at_feedurl);\n			$fp = @fsockopen ($url[\'host\'], 80, $errno, $errstr, 5);\n			if ($fp) {\n				$qry = \'GET \'.$smd_at_feedurl;\n				$qry .= \" HTTP/1.0\\r\\n\\r\\n\";\n\n				fputs($fp, $qry);\n				stream_set_timeout($fp, 10);\n				$info = stream_get_meta_data($fp);\n\n				$hdrs = true;\n				while ((!feof($fp)) && (!$info[\'timed_out\'])) {\n					$line = fgets($fp, 8192);\n					$line = preg_replace(\"[\\r\\n]\", \"\", $line);\n					if ($hdrs == false) {\n						$feed .= $line.\"\\n\";\n					}\n					if (strlen($line) == 0) $hdrs = false;\n				}\n				if ($info[\'timed_out\']) {\n					$feed = \'\';\n				}\n				fclose($fp);\n			} else {\n				$feed = \'\';\n			}\n			break;\n		default:\n			$feed = \'\';\n	}\n\n	$smd_at_vlast = \'\';\n	$feeds = array();\n	if (isset($prefs[\'smd_at_versions\']) && isset($prefs[\'smd_at_vlast\'])) {\n		if (time() - $prefs[\'smd_at_vlast\'] < 86400) {\n			// Last updated within the last day, so read the cached version numbers\n			$feeds = unserialize($prefs[\'smd_at_versions\']);\n		}\n	}\n	if (!$feeds || gps(\'read_versions\')) {\n		$feeds = smd_at_feed($feed);\n		$installed_feed = array();\n		$feeds = (is_array($feeds)) ? $feeds : array();\n\n		// Only record the version info from themes that are installed\n		foreach($skin_list as $skin_name) {\n			$feed_skin_name = str_replace(\'_\', \'-\', $skin_name); // Unsanitize the url_title\n			if (array_key_exists($feed_skin_name, $feeds)) {\n				$installed_feed[$feed_skin_name] = $feeds[$feed_skin_name];\n			}\n		}\n		set_pref(\'smd_at_vlast\', time(), $smd_at_event, PREF_HIDDEN, \'text_input\');\n		set_pref(\'smd_at_versions\', serialize($installed_feed), $smd_at_event, PREF_HIDDEN, \'text_input\');\n	}\n\n	foreach ($skin_list as $skin_name) {\n		// Read each manifest and set up the labels\n		$skinfo = smd_at_read_skinfo($skin_name);\n		$feed_skin_name = str_replace(\'_\', \'-\', $skin_name); // Unsanitize for url_title matching\n\n		$thumbnail = smd_at_get_thumb($skinfo, 1);\n		$this_skin = ($skinfo) ? $skinfo[\'dname\'] : $skin_name;\n		$is_gbl = ($skin_name == $at_prefs[\'smd_at_global_skin\']) ? gTxt(\'smd_at_is_global\') : \'\';\n		$switch_link = \'?event=\'.$smd_at_event.a.\'step=smd_at_switch\'.a.\'nextstep=smd_at_list\'.a.\'skin=\'.$skin_name.a.\'page=\'.$page.a.\'_txp_token=\'.form_token();\n		$skintext = $this_skin .n. $is_gbl .n. (($skinfo && $skinfo[\'based_on\']) ? n.gTxt(\'smd_at_based_on\', array(\'{skin}\' => $skinfo[\'based_on\'])) : \'\');\n		$thumblock = ($layout==0)\n						? \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.$skintext.(($thumbnail) ? br.$thumbnail : \'\').\'</a>\'\n						: \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.(($thumbnail) ? $thumbnail : gTxt(\'smd_at_apply_skin\')).\'</a>\';\n		$authblock = (($skinfo && strpos($skinfo[\'author_uri\'], \"http://\") === 0) ? \'<a href=\"\'.$skinfo[\'author_uri\'].\'\">\'.$skinfo[\'author\'].\'</a>\' : (($skinfo) ? $skinfo[\'author\'] : \'-\'));\n		$actblock = \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_edit\'.a.\'skin=\'.$skin_name.\'\">[\'.gTxt(\'edit\').\']</a>\'\n			. sp .(($skinfo) ? \'<a href=\"#\" onclick=\"return smd_getval(\\\'\'.$skin_name.\'\\\', \\\'smd_at_clone\\\');\">[\'.gTxt(\'smd_at_clone\').\']</a>\' : \'\')\n			. sp .(($skinfo && $crushers) ? \'<a href=\"#\" onclick=\"return smd_getcrush(\\\'\'.$skin_name.\'\\\');\">[\'.gTxt(\'smd_at_export\').\']</a>\' : \'\')\n			. sp .((in_array($skin_name, $smd_core_themes) || in_array($skin_name, $bases)) ? \'\' : \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_delete\'.a.\'skin=\'.$skin_name.a.\'page=\'.$page.a.\'_txp_token=\'.form_token().\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_at_delete_confirm\', array(\'{skin}\' => $skin_name)).\'\\\');\">[\'.gTxt(\'delete\').\']</a>\');\n		$cloneblock = \'<p id=\"pop_\'.$skin_name.\'\" class=\"smd_popup empty\"><span>\'.gTxt(\'smd_at_new_cloneskin\').\'</span>\'.n.\'<input type=\"text\" name=\"smd_text_name\" value=\"\" size=\"\'.INPUT_MEDIUM.\'\" class=\"input-medium\" />\'.n.\'<a href=\"#\">[\'.strong(gTxt(\'smd_at_confirm\')).\']</a></p>\';\n		$crushblock = (($crushers) ? \'<p id=\"crush_\'.$skin_name.\'\" class=\"smd_popup empty\"><span>\'.gTxt(\'smd_at_crush_format\').\'</span>\'.radioSet($crushers, $skin_name.\'_crush\', $at_prefs[\'smd_at_crush\']).n.\'<a href=\"#\">[\'.strong(gTxt(\'smd_at_confirm\')).\']</a></p>\' : \'\');\n		$verblock = ($skinfo) ? ((isset($feeds[$feed_skin_name]) && isset($feeds[$feed_skin_name][\'summary\']) && version_compare($feeds[$feed_skin_name][\'summary\'],$skinfo[\'version\'])===1) ? \'<a href=\"\'.$feeds[$feed_skin_name][\'link\'].\'\" class=\"smd_at_update\">\'.gTxt(\'smd_at_version\').$skinfo[\'version\'].\'</a>\' : gTxt(\'smd_at_version\').$skinfo[\'version\']) : \'\';\n\n		if ($layout==0) {\n			echo tr(\n				n.td($thumblock)\n				.n.td($authblock)\n				.n.td($verblock)\n				.n.td((($skinfo)? $skinfo[\'description\'] : \'-\').(($skinfo && $skinfo[\'help\']) ? \' \'.href(\'[\'.gTxt(\'help\').\']\', $skinfo[\'help\']) : \'\'))\n				.n.td($actblock .n. $cloneblock .n. $crushblock)\n			, (($skin_name == $curr_skin) ? \' class=\"highlight\"\' : \'\')\n			);\n		} else {\n			echo \'<div class=\"txp-grid-cell\', (($skin_name == $curr_skin) ? \' highlight\' : \'\'), \'\">\',\n					n, hed($skintext, 3),\n					n, \'<p>\', $thumblock, \'</p>\',\n					n, \'<p>\', $verblock, n, gTxt(\'smd_at_by\'), n, $authblock, \'</p>\',\n					n, \'<p>\', $actblock, \'</p>\',\n					n, $cloneblock,\n					n, $crushblock,\n				n, \'</div>\';\n		}\n	}\n\n	if ($layout==\"0\") {\n		echo n, endTable();\n	}\n\n	echo n, \'</div>\',\n		n, tInput(),\n		n, \'</form>\',\n		n, \'<div id=\"list_navigation\" class=\"txp-navigation\">\',\n		n, nav_form($smd_at_event, $page, $numPages, NULL, NULL, NULL, NULL),\n		n, pageby_form($smd_at_event, $pageby),\n		n, \'</div>\',\n		n, \'</div>\';\n}\n\nfunction smd_at_feed($feed) {\n	global $feeds;\n\n	$feed=preg_replace(\"/>\".\"[[:space:]]+\".\"</i\", \"><\", $feed); // Kill whitespace in feed\n	$xmlparser = xml_parser_create();\n	xml_set_element_handler($xmlparser, \"smd_at_feed_start_tag\", \"smd_at_feed_end_tag\");\n	xml_set_character_data_handler($xmlparser, \"smd_at_feed_tag_contents\");\n	xml_parse($xmlparser, $feed);\n	xml_parser_free($xmlparser);\n\n	return $feeds;\n}\n\nfunction smd_at_feed_start_tag($parser, $name, $attribs) {\n	global $smd_at_feedin, $smd_at_feedtag;\n	if ($name == \"ENTRY\") {\n		$smd_at_feedin = true;\n	}\n	if ($smd_at_feedin) {\n		$smd_at_feedtag = $name;\n	}\n}\nfunction smd_at_feed_end_tag($parser, $name) {\n	global $smd_at_feedin, $smd_at_feedtag, $smd_at_feedname;\n	if ($name == \"ENTRY\") {\n		$smd_at_feedin = false;\n		$smd_at_feedname = \'\';\n	}\n}\n\nfunction smd_at_feed_tag_contents($parser, $data) {\n	global $smd_at_feedin, $smd_at_feedtag, $smd_at_feedname, $feeds;\n	if ($smd_at_feedin) {\n		if ($smd_at_feedtag == \"NAME\") {\n			$feeds[$data] = \'\';\n			$smd_at_feedname = $data;\n		}\n		if (isset($feeds[$smd_at_feedname])) {\n			if ($smd_at_feedtag == \"TITLE\" || $smd_at_feedtag == \"SUMMARY\" || $smd_at_feedtag == \"UPDATED\" || $smd_at_feedtag == \"ID\" || $smd_at_feedtag == \"LINK\") {\n				$feeds[$smd_at_feedname][strtolower($smd_at_feedtag)] = $data;\n			}\n		}\n	}\n}\n\n// ------------------------\nfunction smd_at_valid_types() {\n	$editable = array(\'ssc\', \'scss\', \'css\', \'js\', \'php\', \'txt\', \'textile\');\n	$imagable = array(\'jpg\', \'jpeg\', \'gif\', \'png\', \'svg\');\n	return array($editable, $imagable);\n}\n\n// ------------------------\nfunction smd_at_edit($message=\'\') {\n	global $smd_at_event, $file_max_upload_size, $step;\n\n	extract(doSlash(gpsa(array(\'skin\', \'new_skin\', \'new_skin_dir\', \'file\', \'dir\'))));\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n	$layout = $at_prefs[\'smd_at_layout\'];\n\n	list($editable, $imagable) = smd_at_valid_types();\n\n	$skinfo = smd_at_read_skinfo($skin);\n	$file = (empty($new_skin)) ? $file : $new_skin;\n	$dir = (empty($new_skin_dir)) ? $dir : $new_skin_dir;\n	$files = smd_at_listdir(THEME.$skin);\n	sort($files);\n\n	// Split the list into its directory groups\n	$allfiles = array();\n	foreach ($files as $currfile) {\n		$file_info = pathinfo($currfile);\n		// Ignore any rogue Subversion files\n		if (strpos($file_info[\'dirname\'], \'/.svn\') === false) {\n			$file_type = strtolower((isset($file_info[\'extension\'])) ? $file_info[\'extension\'] : \'other\');\n			$allfiles[$file_type][] = $currfile;\n		}\n	}\n	if (gps(\'smd_at_edits\')) {\n		$contents = str_replace(\'\\r\\n\',\'\n\',gps(\'smd_at_edits\')); // newline workaround\n	} else {\n		$contents = \'\';\n		if ($file && file_exists(THEME.$skin.DS.$dir.DS.$file)) {\n			$contents = file(THEME.$skin.DS.$dir.DS.$file);\n			$contents = join(\"\", $contents);\n		}\n	}\n\n	$info = explode (\'.\',$file);\n	$lastpart = count($info)-1;\n	$ext = strtolower(trim($info[$lastpart]));\n	$is_textile = ($ext == \'textile\');\n	$is_svg = ($ext == \'svg\');\n\n	// Begin rendering the page\n	pagetop(gTxt(\'smd_at_edit_lbl\', array(\'{theme}\' => $skin)), $message);\n\n	$skin_list = smd_at_read_skins();\n\n	// selectInput needs both index and value to be the same in this case\n	$skinsel = array();\n	foreach($skin_list as $key1 => $value1) {\n		$skinsel[$value1] = $skin_list[$key1];\n   }\n	$skinChange = \'<form name=\"skinchange\" action=\"index.php\">\'\n		. eInput($smd_at_event)\n		. sInput(\'smd_at_edit\')\n		. tInput()\n		. selectInput(\'skin\', $skinsel, $skin, 0, 1)\n		. \'</form>\';\n\n	$fileList = array();\n	$fileList[] = \'<div id=\"content_switcher\">\'\n		.n. hed(gTxt(\'smd_at_skin_files\'),2)\n		.n. $skinChange\n		.n. \'<p class=\"txp-buttons\">\'\n		.n. \'<a class=\"navlink\" href=\"?event=\'.$smd_at_event.a.\'step=smd_at_newfile\'.a.\'skin=\'.$skin.\'\">\'.gTxt(\'smd_at_new_file\').\'</a>\'\n		.n. \'<a class=\"navlink\" href=\"?event=\'.$smd_at_event.\'\">\'.gTxt(\'smd_at_all_themes\').\'</a>\'\n		.n. \'</p>\'\n		.n. \'<form action=\"index.php\" method=\"post\" enctype=\"multipart/form-data\">\'\n		.n. hInput(\'MAX_FILE_SIZE\', $file_max_upload_size)\n		.n. hInput(\'id\', $skin)\n		.n. eInput($smd_at_event)\n		.n. sInput(\'smd_at_upload\')\n		.n. tInput()\n		.n. \'<input type=\"file\" name=\"smd_at_file[]\" multiple=\"true\" />\'.sp.fInput(\'submit\', \'\', gTxt(\'upload\'))\n		.n. graf(\'<label for=\"smd_at_folder\">\'.gTxt(\'smd_at_folder\').\'</label>\' .n. fInput(\'text\', \'smd_at_folder\', ps(\'smd_at_folder\'), \'\', \'\', \'\', INPUT_MEDIUM, \'\', \'smd_at_folder\'))\n		.n. \'</form>\'\n//		.n. upload_form(gTxt(\'upload_file\'), \'\', \'smd_at_upload\', $smd_at_event, $skin, $file_max_upload_size, \'\', \'\')\n		.n. \'<form id=\"smd_at_allfiles\" action=\"index.php\" method=\"post\">\';\n\n	$dirs = $dirout = $out = array();\n	$methods = array(\n		\'delete\'     => gTxt(\'delete\'),\n	);\n\n	foreach ($allfiles as $ftype => $filerec) {\n		foreach ($filerec as $idx => $currfile) {\n			$justfile = basename($currfile);\n			$dirs[] = $theDir = dirname($currfile);\n			$justdir = trim(str_replace(THEME.$skin, \'\', $theDir), DS);\n			$parts = explode(\'.\', $justfile);\n			$basefile = array_shift($parts);\n			$showfile = ($file == $justfile) ? strong($justfile) : $justfile;\n			$is_img = smd_at_is_image($currfile);\n			$is_bin = smd_at_is_binary($currfile);\n\n			$edit = $is_img ? \'<a href=\"\'.$currfile.\'\" title=\"\'.gTxt(\'smd_at_preview_image\').\'\">\'.$showfile.\'</a>\' : ($is_bin ? $showfile : \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_edit\'.a.\'skin=\'.$skin.a.\'file=\'.$justfile.a.\'dir=\'.$justdir.\'\">\'.$showfile.\'</a>\');\n			$modbox = ($basefile == $skin)\n				?	\'\'\n				:	\'<input type=\"checkbox\" name=\"selected_files[]\" value=\"\'.$justdir.DS.$justfile.\'\" />\';\n\n			$group_name = in_array($ftype, $imagable) ? \'image_files\' : (in_array($ftype, $editable) ? $ftype : \'other\');\n\n			$out[$group_name][] = \'<li>\'.n.\'<span class=\"smd-at-list-action\">\'.$modbox.\'</span><span class=\"smd-at-list-name\">\'.$edit.\'</span></li>\';\n		}\n	}\n\n	$combined = array_merge($editable, array(\'image_files\', \'other\'));\n	foreach($combined as $group_name) {\n		if (isset($out[$group_name])) {\n			$visipref = \'smd_at_pane_\'.$group_name.\'_visible\';\n			$fileList[] = \'<div class=\"summary-details \'.$group_name.\'\"><h3 class=\"lever\'.(get_pref($visipref) ? \' expanded\' : \'\').\'\"><a href=\"#\'.$group_name.\'\">\'.gTxt(\'smd_at_\'.$group_name).\'</a></h3>\'.n.\n				\'<div id=\"\'.$group_name.\'\" class=\"toggle skin-list\" style=\"display:\'.(get_pref($visipref) ? \'block\' : \'none\').\'\">\'.n.\n				\'<ul class=\"plain-list\">\'.n;\n			$fileList[] = join(n, $out[$group_name]);\n			$fileList[] = \'</ul></div></div>\';\n		}\n	}\n\n	$fileList[] = multi_edit($methods, $smd_at_event, \'smd_at_multi_edit\', \'\', \'\', \'\', $skin);\n	$fileList[] = tInput().n.\'</form>\';\n\n	$dirs = array_unique($dirs);\n	unset($dirs[array_search(THEME.$skin, $dirs)]);\n	foreach ($dirs as $thisdir) {\n		$dirout[] = \'<tr><td class=\"smd-at-list-name\">\'.str_replace(THEME.$skin.\'/\', \'\', $thisdir).\'</td><td>\'.dLink($smd_at_event, \'smd_at_delete_folder\', \'delfolder\', $thisdir, 1, \'skin\', $skin).\'</td></tr>\';\n	}\n\n	$folderList = ($dirout) ? \'<div class=\"summary-details folders\"><h3 class=\"lever\'.(get_pref(\'smd_at_pane_folders_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#folders\">\'.gTxt(\'smd_at_folders\').\'</a></h3>\'.n.\n		\'<div id=\"folders\" class=\"toggle skin-list\" style=\"display:\'.(get_pref(\'smd_at_pane_folders_visible\') ? \'block\' : \'none\').\'\">\'.n.\n		\'<table>\'.\n		join(n, $dirout) . \'</table></div></div>\' : \'\';\n\n	$qs = array(\n		\"event\" => $smd_at_event,\n	);\n	$qsVars = \"index.php\".join_qs($qs);\n\n	// Render the rest of the page\n	echo startTable(\'\', \'\', \'txp-columntable\'),\n	tr(\n		td(\n			hed(gTxt(\'smd_at_edit_lbl\', array(\'{theme}\' => (($skinfo) ? $skinfo[\'dname\'] : $skin))), 1).\n			form(\n				n.graf(\n					\'<label for=\"new_skin\">\' . gTxt(\'smd_at_file_name\') . \'</label>\'\n					.n.\'<input type=\"text\" id=\"new_skin\" name=\"new_skin\" value=\"\'.txpspecialchars($file).\'\" size=\"\'.INPUT_REGULAR.\'\" />\'\n					.n.($is_textile ? \'<a class=\"smd_at_preview\" href=\"?event=\' . $smd_at_event .a. \'step=smd_at_preview\' .a. \'skin=\'.$skin.a.\'dir=\'.$dir.a.\'file=\'.$file.a.\'type=textile\'.a.\'_txp_token=\'.form_token().\'\">\'.gTxt(\'preview\').\'</a>\' : \'\')\n					.n.($is_svg ? \'<a class=\"smd_at_preview\" href=\"\'. THEME . $skin.\'/\' . (($dir) ? $dir.\'/\' : \'\') . $file .\'\">\'.gTxt(\'preview\').\'</a>\' : \'\')\n				)\n				.($step == \'smd_at_newfile\'\n					? n.graf(\n						\'<label for=\"new_skin_dir\">\' . gTxt(\'smd_at_folder_name\') . \'</label>\'\n						.n.\'<input type=\"text\" id=\"new_skin_dir\" name=\"new_skin_dir\" value=\"\'.txpspecialchars($dir).\'\" size=\"\'.INPUT_REGULAR.\'\" />\'\n					)\n					: \'\'\n				)\n				.n.graf(\n					\'<textarea id=\"smd_at_edits\" class=\"code\" name=\"smd_at_edits\" cols=\"\'.INPUT_XLARGE.\'\" rows=\"\'.INPUT_REGULAR.\'\">\'.txpspecialchars($contents).\'</textarea>\'\n					)\n				.n.graf(\n					fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\')\n				)\n				.n.eInput($smd_at_event)\n				.n.sInput(\'smd_at_save\')\n				.n.hInput(\'skin\',$skin)\n				.n.hInput(\'file\',$file)\n				.n.hInput(\'dir\',$dir)\n			)\n		, \'\', \'column\').\n		n.tdtl(join(n, $fileList).n.$folderList.n.\'</div>\', \' class=\"column\"\')\n	),\n	endTable().\n	script_js( <<<EOS\n		jQuery(document).ready(function() {\n			jQuery(\'#smd_at_allfiles\').txpMultiEditForm({\n				\'checkbox\' : \'input[name=\"selected_files[]\"][type=checkbox]\',\n				\'row\' : \'.plain-list li, .smd-at-list-name\',\n				\'highlighted\' : \'.plain-list li\'\n			});\n		});\n		jQuery(\'#smd_at_edits\').keyup(function() {\n			jQuery(\'.smd_at_preview\').hide();\n		});\nEOS\n	);\n}\n\n// ------------------------\n// Just clear out the var so the save function treats the content as a new file\nfunction smd_at_newfile() {\n	$_POST[\'file\'] = \'\';\n	$message=\'\';\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_preview() {\n	extract(doSlash(gpsa(array(\'skin\', \'file\', \'dir\', \'type\'))));\n\n	$fp = $skin.DS.(($dir) ? $dir.DS : \'\').$file;\n\n	$path = THEME.$fp;\n	$out = \'\';\n\n	switch ($type) {\n		case \'textile\':\n			@include_once txpath.\'/lib/classTextile.php\';\n\n			if (class_exists(\'Textile\') && file_exists($path)) {\n				$textile = new Textile();\n				$contents = file_get_contents($path);\n				$out = $textile->TextileThis($contents);\n			}\n		break;\n	}\n\n	pagetop(gTxt(\'smd_at_preview\', array(\'{name}\' => $fp)));\n	echo n,\'<div id=\"smd_at_container\" class=\"txp-container txp-view\">\',\n		n,\'<div class=\"text-column\">\' . $out . \'</div>\',\n		n,\'</div>\';\n\n	//TODO: back to smd_at_edit() button?\n}\n\n// ------------------------\n// Do the first 512 bytes contain a disproportionate quantity of non-ascii chars?\n// Hacked from http://stackoverflow.com/questions/3872877/how-to-check-if-uploaded-file-is-binary-file, thanks\nfunction smd_at_is_binary($path) {\n	if (file_exists($path)) {\n		if (!is_file($path)) return 0;\n\n		$fh  = fopen($path, \"r\");\n		$blk = fread($fh, 512);\n		fclose($fh);\n		clearstatcache();\n\n		return (\n			0 or substr_count($blk, \"\\x00\") > 0\n		);\n	}\n	return 0;\n}\n\n// ------------------------\n// Hacked from http://www.binarytides.com/blog/php-best-way-to-check-if-file-is-an-image/, thanks\nfunction smd_at_is_image($path) {\n	// Silence warnings in case file is not readable due to spaces or something\n	$a = @getimagesize($path);\n	$image_type = $a[2];\n\n	if(in_array($image_type , array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) {\n		return true;\n	}\n	return false;\n}\n\n// ------------------------\nfunction smd_at_clone() {\n	global $txp_user;\n\n	extract(doSlash(gpsa(array(\'skin\', \'new_skin\'))));\n	$msg=\'\';\n\n	$new_skin = trim($new_skin);\n	$new_skin = rawurlencode($new_skin);\n\n	if (empty($new_skin)) {\n		$msg = array(gTxt(\'smd_at_name_empty\'), E_WARNING);\n	}\n\n	// Check the new name isn\'t already in use\n	$themes = smd_at_read_skins();\n\n	if (in_array($new_skin, $themes)) {\n		$msg = array(gTxt(\'smd_at_skin_exists\', array(\'{name}\' => $new_skin)), E_ERROR);\n	}\n\n	// No errors, so make a new dir and create the PHP file\n	if ($msg==\'\') {\n		$content = \'\';\n		$destdir = THEME.$new_skin.DS;\n		$res = mkdir($destdir);\n		$based = \'\';\n\n		if ($skin) {\n			// Based on an existing theme. No need for any functions other than manifest()\n			$based = \"theme::based_on(\'\".$skin.\"\');\";\n			$extends = $skin.\'_theme\';\n			$srcss = THEME.$skin.DS.\'textpattern.css\';\n			if (file_exists($srcss) && filesize($srcss) > 0) {\n				$import = join(\'\', file($srcss));\n			} else {\n				$import = \'@import url(\"../classic/textpattern.css\");\';\n			}\n			$content = \'{\';\n		} else {\n			// Brand spanking new theme. Clone \'classic\' functions then add new manifest()\n			$extends = \'theme\';\n			$import = \'\';\n			$src = txpath.DS.THEME.\'classic\'.DS.\'classic.php\';\n			$grab = false;\n			$fp = file($src);\n\n			foreach($fp as $line) {\n				$line = rtrim($line);\n				if (strpos($line, \'function manifest()\') !== false) {\n					$grab = false; // unnecessary?\n					break;\n				}\n				if ($grab) {\n					$content .= $line.n;\n				}\n				if (strpos($line, \'class classic_theme extends theme\') !== false) {\n					$grab = true;\n				}\n			}\n		}\n\n		// manifest fields\n		$manuser = get_author_name($txp_user);\n		$mantitle = ucwords($new_skin);\n\n		// Write the new PHP file\n		$buf = <<<EOF\n<?php\n\nif (!defined(\'txpinterface\')) die(\'txpinterface is undefined.\');\n\n{$based}\n\nclass {$new_skin}_theme extends {$extends}\n{$content}\n\n	function manifest()\n	{\n		return array(\n			\'title\'	 		=> \'{$mantitle}\',\n			\'author\' 		=> \'{$manuser}\',\n			\'author_uri\' 	=> \'\',\n			\'version\' 		=> \'1.0\',\n			\'description\' 	=> \'\',\n			\'help\' 			=> \'\',\n		);\n	}\n}\n?>\nEOF;\n\n		$fp = fopen($destdir.$new_skin.\'.php\', \"wb\");\n		fwrite($fp, $buf);\n		fclose($fp);\n\n		// Create the CSS file\n		$fp = fopen($destdir.\'textpattern.css\', \"wb\");\n		fwrite($fp, $import);\n		fclose($fp);\n\n		if ($skin) {\n			$msg = gTxt(\'smd_at_skin_cloned\', array(\'{name}\' => $new_skin));;\n		} else {\n			$msg = gTxt(\'smd_at_skin_created\', array(\'{name}\' => $new_skin));;\n		}\n\n//	$msg = gTxt(\'skin_not_created\', array(\'{name}\' => $new_skin));;\n\n	}\n\n	smd_at_list($msg);\n}\n\n// ------------------------\nfunction smd_at_crush_options($type=\'compress\') {\n	global $plugins;\n\n	$smd_crushers = array();\n\n	if (is_array($plugins) && in_array(\'smd_crunchers\', $plugins)) {\n		$crunchers = smd_crunch_capabilities($type);\n		foreach (array(\'tar\', \'gzip\', \'zip\', \'bzip2\') as $cm) {\n			if (in_array($cm, $crunchers)) {\n				$smd_crushers[$cm] = gTxt(\'smd_at_c_\'.$cm);\n			}\n		}\n	}\n\n	return $smd_crushers;\n}\n\n// ------------------------\nfunction smd_at_export() {\n	extract(doSlash(gpsa(array(\'skin\',\'crush\'))));\n\n	$at_prefs = smd_at_get_prefs();\n\n	$message=\'\';\n	chdir(THEME);\n	$skinfo = smd_at_read_skinfo($skin);\n	$reps = array(\n		\'{theme}\' => $skin,\n		\'{version}\' => $skinfo[\'version\'],\n	);\n	$out = strtr($at_prefs[\'smd_at_filename_format\'], $reps);\n\n	// Check the passed compression format is valid\n	$crushers = smd_at_crush_options(\'compress\');\n	if ($crush==\'\' || $crush==\'undefined\' || !array_key_exists($crush, $crushers)) {\n		$msg = array(gTxt(\'smd_at_unsupported_compressiontype\', array(\'{crush}\' => $crush)), E_WARNING);\n		smd_at_list($message);\n		return;\n	}\n\n	switch($crush) {\n		case \'zip\':\n			$zip = new smd_crunch_zip_file($out.\'.zip\');\n			break;\n		case \'bzip2\':\n			$zip = new smd_crunch_bzip_file($out.\'.tbz2\');\n			break;\n		case \'tar\':\n			$zip = new smd_crunch_tar_file($out.\'.tar\');\n			break;\n		case \'gzip\':\n			$zip = new smd_crunch_gzip_file($out.\'.tgz\');\n 			break;\n	}\n\n	$zip->set_options(array(\'basedir\' => txpath.DS.THEME, \'overwrite\' => 1, \'inmemory\' => 1));\n	$zip->add_files($skin.DS.\'*.*\');\n	$zip->exclude_files($skin.DS.\'*.svn*\');\n	$zip->create_archive();\n	$zip->download_file();\n}\n\n// ------------------------\nfunction smd_at_import() {\n	$name = $_FILES[\'thefile\'][\'name\'];\n	$file = get_uploaded_file($_FILES[\'thefile\'][\'tmp_name\'], txpath.DS.THEME.basename($_FILES[\'thefile\'][\'tmp_name\']));\n	$basedir = txpath.DS.THEME;\n	$uncrushers = smd_at_crush_options(\'decompress\');\n	$message=\'\';\n	$info = explode (\'.\',$name);\n	$lastpart = count($info)-1;\n	$ext = $info[$lastpart];\n\n	if ($name && $file) {\n		if ($ext == \'zip\' && array_key_exists(\'zip\', $uncrushers)) {\n			$zip = new smd_crunch_dUnzip2($file);\n//			$zip->debug = true;\n			$zip->getList();\n			$validArchive = smd_at_check_archive($zip->compressedList, $ext);\n			if ($validArchive) {\n				$zip->unzipAll($basedir, \'\', true, 0755);\n				$zip->close();\n				$message = gTxt(\'smd_at_import_ok\');\n			} else {\n				$message = array(gTxt(\'smd_at_import_failed\'), E_ERROR);\n			}\n		} else if ($ext == \'tgz\' && array_key_exists(\'gzip\', $uncrushers)) {\n			$zip = new smd_crunch_gzip_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else if ($ext == \'tbz2\' && array_key_exists(\'bzip2\', $uncrushers)) {\n			$zip = new smd_crunch_bzip_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else if ($ext == \'tar\') {\n			$zip = new smd_crunch_tar_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else {\n			$message = array(gTxt(\'smd_at_unsupported_compressiontype\', array(\'{crush}\' => $ext)), E_WARNING);\n		}\n\n		// Remove the temporary file\n		unlink($file);\n	} else {\n		$message = array(gTxt(\'smd_at_empty_info\'), E_WARNING);\n	}\n\n	smd_at_list($message);\n}\n\n// Check the directory structure of the archive for various items/attributes.\n// Each time one of the conditions is met, the vcnt is incrememented.\n// Once the whole archive has been read, if the vcnt matches the expectedCounts for\n// that type, the archive is valid.\nfunction smd_at_check_archive($struct, $type=\'zip\') {\n	$expectedCounts = array(\'zip\' => 1, \'tgz\' => 0, \'tbz2\' => 0, \'tar\' => 0);\n	switch ($type) {\n		case \"zip\":\n			$vcnt = 0;\n			foreach ($struct as $entry => $info) {\n				$parts = pathinfo($info[\'file_name\']);\n				if (isset($parts[\'extension\']) && $parts[\'extension\'] == \'php\') {\n					$dirbits = explode(DS, $parts[\'dirname\']);\n					$numdirs = count($dirbits);\n					if (($parts[\'filename\'] == $dirbits[$numdirs-1]) && $numdirs==1) {\n						$vcnt++;\n					}\n				}\n			}\n			break;\n	}\n\n	return ($expectedCounts[$type] == $vcnt) ? true : false;\n}\n\n// ------------------------\nfunction smd_at_sanitize_folder($text) {\n	// Remove control characters and \" * \\ : < > ? |\n	$text = preg_replace(\'/[\\x00-\\x1f\\x22\\x2a\\x3a\\x3c\\x3e\\x3f\\x5c\\x7c\\x7f]+/\', \'\', $text);\n	// Remove duplicate dots and any leading or trailing dots/spaces\n	$text = preg_replace(\'/[.]{2,}/\', \'.\', trim($text, \'. \'));\n	// Remove any leading or trailing slash\n	$text = trim($text, \'/\');\n	return $text;\n}\n\n// ------------------------\nfunction smd_at_rrmdir($dir) {\n	foreach(glob($dir . \'/*\') as $file) {\n		if(is_dir($file))\n			smd_at_rrmdir($file);\n		else\n			unlink($file);\n	}\n	rmdir($dir);\n}\n\n// ------------------------\nfunction smd_at_upload() {\n	global $file_max_upload_size;\n\n	extract(doSlash(gpsa(array(\'id\', \'smd_at_folder\'))));\n\n	$_POST[\'skin\'] = $skin = $id;\n	$folder = smd_at_sanitize_folder($smd_at_folder);\n\n	$filelist = $_FILES[\'smd_at_file\'][\'name\'];\n	$name = $pos = NULL;\n	$message=\'\';\n	$up_count = $fail_count = 0;\n	$uploaded = array();\n\n	$skinfo = smd_at_read_skinfo($skin);\n	$new_skin = $skinfo[\'name\'];\n\n	if ($new_skin) {\n		$newpath = $skinfo[\'path\'] . (($folder) ? $folder.\'/\' : \'\');\n\n		if (!file_exists($newpath)) {\n			mkdir($newpath, 0755, true);\n		}\n\n		foreach ($filelist as $pos => $name) {\n			if ($name == \'\') continue;\n\n			$file = get_uploaded_file($_FILES[\'smd_at_file\'][\'tmp_name\'][$pos]);\n\n			// Skip files that are too big\n			$size = filesize($file);\n			if ($file_max_upload_size < $size) {\n				unlink($file);\n				$fail_count++;\n				continue;\n			}\n\n			if ($name && $file) {\n				if(shift_uploaded_file($file, $newpath.$name)) {\n					chmod($newpath.$name, 0755);\n					$up_count++;\n					$uploaded[] = $name;\n				} else {\n					unlink($file);\n					$fail_count++;\n				}\n			}\n		}\n		$message = gTxt(\'smd_at_upload_report\', array(\'{num_success}\' => $up_count, \'{num_fail}\' => $fail_count, \'{uploaded}\' => join(\', \', $uploaded)));\n	} else {\n		$message = array(gTxt(\'smd_at_empty_info\'), E_WARNING);\n	}\n\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_delete() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	extract(doSlash(gpsa(array(\'skin\'))));\n	$message=\'\';\n\n	if (in_array($skin, $smd_core_themes)) {\n		$message = array(gTxt(\'smd_at_core_theme\', array(\'{skin}\' => $skin)), E_ERROR);\n		smd_at_list($message);\n	} else {\n		$skindir = THEME.$skin;\n		if (!smd_rmdir_recursive($skindir)) {\n			$message = array(gTxt(\'smd_at_delete_failed\'), E_ERROR);\n		} else {\n			$message = gTxt(\'smd_at_skin_deleted\', array(\'{name}\' => $skin));\n		}\n\n		// Handle situations if current skin has been deleted\n		if (!file_exists(txpath.DS.THEME.$skin)) {\n			$at_prefs = smd_at_get_prefs();\n\n			// Reset the global skin in case it\'s been deleted\n			$gbl_skin = $at_prefs[\'smd_at_global_skin\'];\n			$gbl_skin = ($gbl_skin != \'\' && file_exists(txpath.DS.THEME.$gbl_skin)) ? $gbl_skin : \'classic\';\n			set_pref(\'smd_at_global_skin\', $gbl_skin, $smd_at_event, PREF_HIDDEN, \'text_input\');\n			set_pref(\'theme_name\', $gbl_skin);\n\n			$skin = get_pref(\'smd_skin\');\n			if (!file_exists(txpath.DS.THEME.$skin)) {\n				$skin = $gbl_skin;\n			}\n			set_pref(\'smd_skin\', $skin, \'smd_at\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n		}\n		$_GET[\'skin\'] = $skin;\n		$_GET[\'nextstep\'] = \'smd_at_list\';\n		smd_at_switch($message);\n	}\n}\n\n// ------------------------\nfunction smd_at_delete_file($skin, $delfile) {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	$ret = false;\n	$path = THEME.$skin.DS.$delfile;\n	if (file_exists($path) && !in_array($skin, $smd_core_themes)) {\n		$ret = unlink($path);\n	}\n	return $ret;\n}\n\n// ------------------------\nfunction smd_at_delete_folder() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	extract(doSlash(gpsa(array(\'skin\', \'delfolder\'))));\n	$message = \'\';\n\n	$_POST[\'skin\'] = $skin;\n\n	if (file_exists($delfolder) && !in_array($skin, $smd_core_themes)) {\n		smd_at_rrmdir($delfolder);\n		$message = gTxt(\'smd_at_folder_deleted\', array(\'{name}\' => $delfolder));\n	}\n\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_multi_edit() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	$message = \'\';\n	$method = ps(\'edit_method\');\n	$files = ps(\'selected_files\');\n	$skin = ps(\'crit\'); // bit of a hack, but multi_edit() has a fixed param set\n	$affected = array();\n	$_POST[\'skin\'] = $skin;\n\n	if ($files and is_array($files)) {\n		if ($method == \'delete\') {\n			foreach ($files as $name) {\n				if (smd_at_delete_file($skin, $name)) {\n					$affected[] = $name;\n				}\n			}\n\n			if ($affected) {\n				$message = gTxt(\'smd_at_files_deleted\', array(\'{list}\' => join(\', \', $affected)));\n			}\n\n			smd_at_edit($message);\n		}\n	} else {\n		smd_at_edit();\n	}\n}\n\n// ------------------------\nfunction smd_at_save() {\n	extract(doSlash(gpsa(array(\'skin\',\'new_skin\',\'new_skin_dir\',\'file\',\'dir\',\'smd_at_edits\'))));\n\n	list($editable, $imagable) = smd_at_valid_types();\n	$message = $extraMsg = \'\';\n	$file = trim($file);\n	$new_skin = trim($new_skin);\n	$new_skin_dir = trim(trim($new_skin_dir), DS);\n	$msglev = 0;\n\n	// New file\n	if (!$file && $new_skin) {\n		$file = $new_skin;\n		$dir = $new_skin_dir;\n		$ext = array_pop(explode (\'.\',$file));\n		if (strtolower($file) != \'readme\' && ($ext == \'\' || !in_array($ext, $editable))) {\n			$message = gTxt(\'smd_at_unsupported_filetype\').gTxt(\'smd_at_unsupported_fudge\');\n			$msglev = E_WARNING;\n			$_POST[\'file\'] = $file;\n			$_POST[\'dir\'] = $dir;\n		}\n	}\n\n	if ($message==\'\') {\n		$fname = sanitizeForFile($file);\n		$new_skin = rawurlencode($new_skin);\n		$_POST[\'new_skin\'] = $new_skin;\n\n		$smd_at_edits = doStrip(str_replace(\'\\r\\n\',\'\n\',$smd_at_edits)); // newline workaround\n\n		if ($fname) {\n			$filepath = THEME.$skin.DS.$dir.DS;\n			if (!file_exists($filepath.$fname)) {\n				if (!file_exists($filepath)) {\n					mkdir($filepath, 0755, true);\n				}\n\n				$ret = touch($filepath.$fname);\n				if ($ret === false) {\n					$message = gTxt(\'smd_at_mkdir_failed\', array(\'{name}\' => $fname));\n				}\n			}\n\n			// Assuming no errors so far...\n			if ($message == \'\') {\n				$fh = fopen($filepath.$fname, \'wb\');\n				fwrite($fh, $smd_at_edits);\n				fclose($fh);\n\n				if ($fname != $new_skin) {\n					// Rename the file\n					$res = rename($filepath.$fname, $filepath.$new_skin);\n\n					// If the renamed file is the theme\'s PHP file, try to rename the main skin dir too\n					$filebits = explode(\'.\', $new_skin);\n					$oldfilebits = explode(\'.\', $fname);\n					$ext = array_pop($filebits);\n					if ($res && $ext == \'php\' && ($oldfilebits[0] == $skin)) {\n						$new_filepath = THEME.$filebits[0].DS;\n						$ren = rename($filepath, $new_filepath);\n						$filepath = ($ren) ? $new_filepath : $filepath;\n						$_POST[\'skin\'] = $filebits[0];\n					}\n					$extraMsg = ($res) ? gTxt(\'smd_at_renamed\', array(\'{name}\' => $new_skin)) : gTxt(\'smd_at_rename_failed\');\n				}\n\n				// Process any css replacements\n				$filebits = explode (\'.\',(($fname==$new_skin)? $fname : $new_skin));\n				$cssfile = $filebits[0];\n				$ext = array_pop($filebits);\n				if ($ext == \'ssc\') {\n					$replacements = array();\n					$num_rep1 = preg_match_all(\'/(\\@[A-Za-z0-9_]+):\\s*(.*);/\', $smd_at_edits, $matches1);\n					$num_rep2 = preg_match_all(\'/(\\@[A-Za-z0-9_]+):\\s*\\{(.*?)\\}/s\', $smd_at_edits, $matches2);\n\n					// Simple replacements\n					foreach($matches1 as $idx => $reparr) {\n						if ($idx == 0) {\n							// Remove the var definitions\n							foreach ($reparr as $rep) {\n								$smd_at_edits = str_replace($rep, \'\',$smd_at_edits);\n							}\n						}\n						if ($idx == 1) {\n							foreach ($reparr as $jdx => $rep) {\n								$replacements[$rep] = $matches1[2][$jdx];\n							}\n						}\n					}\n\n					// Multi-line replacements\n					foreach($matches2 as $idx => $reparr) {\n						if ($idx == 0) {\n							// Remove the var definitions\n							foreach ($reparr as $rep) {\n								$smd_at_edits = str_replace($rep, \'\',$smd_at_edits);\n							}\n						}\n						if ($idx == 1) {\n							foreach ($reparr as $jdx => $rep) {\n								$replacements[$rep.\';\'] = trim($matches2[2][$jdx]);\n							}\n						}\n					}\n\n					// Generate and write the new css file\n					$smd_at_edits = trim(strtr($smd_at_edits, $replacements));\n					$fh = fopen($filepath.$cssfile.\'.css\', \'wb\');\n					fwrite($fh, $smd_at_edits);\n					fclose($fh);\n					if ($fname != $new_skin) {\n						$oldfname = explode (\'.\', $fname);\n						$oldfname = $oldfname[0].\'.css\';\n						$res = @unlink($filepath.$oldfname);\n					}\n				}\n\n				$message = gTxt(\'smd_at_file_saved\', array(\'{name}\' => $fname)).br.$extraMsg;\n			}\n		}\n	}\n	smd_at_edit(array($message, $msglev));\n}\n\n\n// ------------------------\n// PREFS\n// ------------------------\n// Get current pref value or default for each known pref\nfunction smd_at_get_prefs($force=0) {\n	global $prefs;\n\n	$smd_at_prefs = array(\n		\'smd_at_case_sort\' => array(\n			\'html\'     => \'yesnoradio\',\n			\'position\' => 10,\n			\'default\'  => \'1\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_crush\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 20,\n			\'default\'  => \'zip\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_tw\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 30,\n			\'default\'  => \'260\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_th\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 40,\n			\'default\'  => \'150\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_filename_format\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 60,\n			\'default\'  => \'{theme}\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_max_theme_size\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 70,\n			\'default\'  => (500 * 1024),\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_layout\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 80,\n			\'default\'  => \'1\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_system\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 90,\n			\'default\'  => \'0\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_global_skin\' => array(\n			\'html\'     => \'selectlist\',\n			\'position\' => 100,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_group_list\' => array(\n			\'html\'     => \'list\',\n			\'position\' => 110,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_user_list\' => array(\n			\'html\'     => \'list\',\n			\'position\' => 120,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n	);\n\n	$at_prefs = array();\n	foreach ($smd_at_prefs as $key => $prefarr) {\n		$at_prefs[$key] = get_pref($key, \'\', $force);\n		if ($at_prefs[$key] == \'\') {\n			$at_prefs[$key] = $prefarr[\'default\'];\n		}\n	}\n	return $at_prefs;\n}\n\n// ------------------------\nfunction smd_at_options($event, $step, $message=\'\') {\n	smd_at_setup($message);\n}\n\n// ------------------------\nfunction smd_at_setup($message=\'\') {\n	global $smd_at_event, $theme, $smd_core_themes;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs(1);\n\n	pagetop(gTxt(\'smd_at_prefs_title\'),$message);\n\n	$levels = get_groups();\n	unset($levels[1]); // Publishers get special privs apart from the masses\n	$numLevs = count($levels);\n\n	// Split the user skin list\n	$uskins = explode(\',\', $at_prefs[\'smd_at_user_list\']);\n\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$clrBtn = \' [<span id=\"smd_clr\" class=\"smd_fakebtn\">\'.gTxt(\'smd_at_clear\').\'</span>]\';\n	$btnSet = fInput(\'submit\', \'submit\', gTxt(\'smd_at_set\'), \'publish\');\n	$btnSave = graf(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'));\n	$btnList = \'<p class=\"txp-buttons\"><a class=\"navlink\" href=\"?event=\'.$smd_at_event.\'\">\'.gTxt(\'smd_at_all_themes\').\'</a></p>\';\n	$radBtns = array(\n		gTxt(\'smd_at_per_site\'),\n		gTxt(\'smd_at_per_group\'),\n		gTxt(\'smd_at_per_user\'),\n	);\n\n	$skin_list = smd_at_read_skins();\n\n	// selectInput needs both index and value to be the same in this case\n	$skinsel = array();\n	foreach($skin_list as $key1 => $value1) {\n		if (smd_at_exists($value1)) {\n			$skinsel[$value1] = $skin_list[$key1];\n		}\n	}\n	$gbl_skin = ($at_prefs[\'smd_at_global_skin\'] != \'\') ? $at_prefs[\'smd_at_global_skin\'] : $theme->name;\n	$crushers = smd_at_crush_options(\'compress\');\n\n	echo n, \'<div class=\"plugin-column\">\',\n		n, \'<h2>\', strong(gTxt(\'smd_at_prefs_title\')), \'</h2>\',\n		n, $btnList,\n		n, \'<form method=\"post\" action=\"?event=\', $smd_at_event, a, \'step=smd_at_prefs_update\" onsubmit=\"return smd_presub();\">\',\n		n, inputLabel(\'smd_at_case_sort\', yesnoRadio(\'smd_at_case_sort\', $at_prefs[\'smd_at_case_sort\']), \'smd_at_case_sort\');\n\n	if ($crushers) {\n		echo n, inputLabel(\'smd_at_crush\', radioSet($crushers, \'smd_at_crush\', $at_prefs[\'smd_at_crush\']), \'smd_at_crush_type\');\n	}\n\n	echo n, inputLabel(\'smd_at_layout\', radioSet(array(gTxt(\'smd_at_layout_list\'), gTxt(\'smd_at_layout_grid\')), \'smd_at_layout\', $at_prefs[\'smd_at_layout\']), \'smd_at_layout\'),\n		n, inputLabel(\'smd_at_thumbsize\', fInput(\'text\', \'smd_at_tw\', $at_prefs[\'smd_at_tw\'],\'input-xsmall\',\'\',\'\',4).n.gTxt(\'smd_at_times\').n.fInput(\'text\', \'smd_at_th\', $at_prefs[\'smd_at_th\'],\'input-xsmall\',\'\',\'\',4), \'smd_at_thumbsize\'),\n		n, inputLabel(\'smd_at_filename_format\', fInput(\'text\', \'smd_at_filename_format\', $at_prefs[\'smd_at_filename_format\']), \'smd_at_filename_format\'),\n		n, inputLabel(\'smd_at_max_theme_size\', fInput(\'text\', \'smd_at_max_theme_size\', $at_prefs[\'smd_at_max_theme_size\']), \'smd_at_max_theme_size\'),\n		n, inputLabel(\'smd_at_global_skin\', selectInput(\'smd_at_global_skin\', $skinsel, $gbl_skin, 0, 0, \'smd_at_global_skin\'), \'smd_at_global_skin\'),\n		n, inputLabel(\'smd_at_skin_system\', gTxt(\'smd_at_one_theme\').n.radioSet($radBtns, \'smd_at_system\', $at_prefs[\'smd_at_system\']), \'smd_at_skin_system\');\n\n	// Option 1\n	$priv_list = \'<select id=\"smd_at_privs\" name=\"smd_at_privs[]\" class=\"list\" size=\"\'.$numLevs.\'\" multiple=\"multiple\">\';\n	foreach ($levels as $levid => $levname) {\n		$priv_list .= \'<option value=\"\'.$levid.\'\">\'.$levname.\'</option>\';\n	}\n	$priv_list .= \'</select>\';\n\n	// Note the hidden skin group box; jQuery keeps track of any list changes and keeps it updated\n	$sela = \'<span class=\"edit-label\"><label>\'. gTxt(\'smd_at_skin_groups\').\'</label></span>\';\n	$selb = hInput(\'smd_at_group_list\', $at_prefs[\'smd_at_group_list\']);\n	$selb .= \'<select id=\"smd_at_grps\" name=\"smd_at_grps\" class=\"list\" size=\"\'.$numLevs.\'\">\';\n	foreach ($skin_list as $askin) {\n		if (smd_at_exists($askin)) {\n			$selb .= \'<option value=\"\'.$askin.\'\">\'.$askin.\'</option>\';\n		}\n	}\n	$selb .= \'</select>\';\n	echo n, graf($sela.\'<span class=\"edit-value\">\'.$selb.$priv_list.\'</span>\', \' class=\"smd_at_sel1\"\');\n\n	// Option 2\n	$sela = gTxt(\'smd_at_allowed_skins\');\n	$selb = \'<select id=\"smd_at_user_list\" name=\"smd_at_user_list[]\" class=\"list\" size=\"8\" multiple=\"multiple\">\';\n	foreach ($skin_list as $askin) {\n		if (smd_at_exists($askin)) {\n			$selb .= \'<option value=\"\'.$askin.\'\"\'.((in_array($askin, $uskins)) ? \' selected=\"selected\"\' : \'\').\'>\'.$askin.\'</option>\';\n		}\n	}\n	$selb .= \'</select>\'.$clrBtn;\n\n	echo graf($sela.\'<span class=\"edit-value\">\'.$selb.\'</span>\', \' class=\"smd_at_sel2\"\'),\n		n, $btnSave,\n		n, tInput(),\n		n, \'</form>\',\n		n, \'</div>\';\n\n	echo script_js(<<<EOJS\n// Handle show/hide of pref widgets based on radio selection\nfunction smd_at_prefswap(selValue) {\n	for (idx=0; idx < 3; idx++) {\n		if (idx==selValue) {\n			jQuery(\".smd_at_sel\"+idx).show();\n		} else {\n			jQuery(\".smd_at_sel\"+idx).hide();\n		}\n	}\n}\n\njQuery(function() {\n	jQuery(\"input[name=\'smd_at_system\']\").change(function() {\n		smd_at_prefswap(this.value);\n	});\n\n	jQuery(\'#smd_clr\').click(function() {\n		jQuery(\'#smd_at_user_list option\').prop(\"selected\", \"\");\n	});\n\n	jQuery(\"#smd_at_grps\").change(function() {\n		selskin = jQuery(this).val();\n\n		// Clear the old privs list and grab the current list from the hidden field\n		jQuery(\"#smd_at_privs option\").prop(\"selected\",false);\n		var grplist = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		var grps = grplist.split(\',\');\n		var cnt = grps.length;\n		for (var idx = 0; idx < cnt; idx++) {\n			privs = grps[idx].split(\':\');\n			skinid = privs.shift();\n			if (skinid == selskin) {\n				numPrivs = privs.length;\n				for (jdx = 0; jdx < numPrivs; jdx++) {\n					jQuery(\"#smd_at_privs option[value=\'\"+privs[jdx]+\"\']\").prop(\"selected\", true);\n				}\n			}\n		}\n	});\n\n	jQuery(\"#smd_at_privs\").change(function() {\n		selskin = jQuery(\"#smd_at_grps option:selected\").val();\n		var out = [];\n		var privs = [];\n		privs.push(selskin);\n		jQuery(\"#smd_at_privs option:selected\").each(function() {\n			privs.push(jQuery(this).val());\n		});\n		if (privs.length == 1) {\n			privs.pop();\n		} else {\n			out.push(privs.join(\":\"));\n		}\n		var hidlist = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		var prevlist = hidlist.split(\',\');\n		var prevcnt = prevlist.length;\n\n		jQuery(\"#smd_at_grps option\").each(function() {\n			currskin = jQuery(this).val();\n			if (currskin != selskin) {\n				for (var idx = 0; idx < prevcnt; idx++) {\n					prevprivs = prevlist[idx].split(\':\');\n					skinid = prevprivs.shift();\n					// Generate the priv list for the currently selected skin, removing any duplicate privs set elsewhere\n					if (skinid == currskin) {\n						tmpout = [];\n						tmpout.push(skinid);\n						for (thispriv in prevprivs) {\n							if (jQuery.inArray(prevprivs[thispriv], privs) < 0) {\n								tmpout.push(prevprivs[thispriv]);\n							}\n						}\n						if (tmpout.length > 1) {\n							out.push(tmpout.join(\":\"));\n						}\n					}\n				}\n			}\n		});\n		jQuery(\"input[name=\'smd_at_group_list\']\").val(out);\n	});\n\n	// Display a tooltip showing the currently-assigned skin when hovering over a privilege level\n	jQuery(\"#smd_at_privs option\").hover(function() {\n		hovitem = jQuery(this).val();\n		grps = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		items = grps.split(\",\");\n		for (idx = 0; idx < items.length; idx++) {\n			skinz = items[idx].split(\":\");\n			for (jdx = 1; jdx < skinz.length; jdx++) {\n				if (skinz[jdx] == hovitem) {\n					jQuery(this).attr(\"title\", skinz[0]);\n					break;\n				}\n			}\n		}\n	},\n	function() {\n		jQuery(this).attr(\"title\", \"\");\n	});\n	smd_at_prefswap(jQuery(\"input[name=\'smd_at_system\']:checked\").val());\n	jQuery(\"#smd_at_privs\").change();\n});\nEOJS\n	);\n}\n\n// ------------------------\n// Split a multi-char preference value into an array of keys/values.\n// Note: value is passed in and not read directly from the prefs array in this function - intentionally\nfunction smd_at_pref_explode($val) {\n	$order = array_values(array(gTxt(\'smd_at_c_gzip\'),gTxt(\'smd_at_c_bzip2\'),gTxt(\'smd_at_c_zip\')));\n	$onoff = array_values(preg_split(\'//\', $val, -1, PREG_SPLIT_NO_EMPTY));\n	$out = array();\n	foreach($order as $key1 => $value1) {\n		$out[(string)$value1] = $onoff[$key1];\n	}\n	return $out;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_install($showpane=\'1\') {\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	foreach ($at_prefs as $pref => $dflt) {\n		if (!fetch(\'name\',\'txp_prefs\',\'name\',$pref)) {\n			$id = safe_insert(\'txp_prefs\',\'prefs_id=1, name=\'.doQuote(doSlash($pref)).\', val=\'.doQuote(doSlash($dflt)).\', event=\"smd_at\", type=\'.PREF_HIDDEN);\n		}\n	}\n	if ($showpane) {\n		$message = gTxt(\'smd_at_prefs_installed\');\n		smd_at_setup($message);\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_remove($showpane=\'1\') {\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	foreach ($at_prefs as $pref => $dflt) {\n		if (fetch(\'name\',\'txp_prefs\',\'name\',$pref)) {\n			$id = safe_delete(\'txp_prefs\',\'name=\'.doQuote(doSlash($pref)));\n		}\n	}\n	if ($showpane) {\n		$message = gTxt(\'smd_at_prefs_deleted\');\n		smd_at_setup($message);\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_update() {\n	global $smd_plugin_prefs, $smd_at_event;\n\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	$post = array();\n\n	foreach ($at_prefs as $pref => $prefobj) {\n		$post[$pref] = ps($pref);\n	}\n\n	// Handle list options separately and only update them if they\'re set\n	// (they\'ve already been set in the foreach above, so need unsetting)\n	if (ps(\'smd_at_user_list\')) {\n		$post[\'smd_at_user_list\'] = join(\',\', ps(\'smd_at_user_list\'));\n	} else {\n		$post[\'smd_at_user_list\'] = \'\';\n	}\n	if (ps(\'smd_at_group_list\')) {\n		$post[\'smd_at_group_list\'] = ps(\'smd_at_group_list\');\n	} else {\n		unset($post[\'smd_at_group_list\']);\n	}\n	if (ps(\'smd_at_crush\')) {\n		$post[\'smd_at_crush\'] = ps(\'smd_at_crush\');\n	} else {\n		unset($post[\'smd_at_crush\']);\n	}\n\n	// Set the system-wide theme to the global theme\n	if ($post[\'smd_at_global_skin\']) {\n		set_pref(\'theme_name\', $post[\'smd_at_global_skin\']);\n	}\n\n	foreach ($at_prefs as $pref => $dflt) {\n		if (isset($post[$pref])) {\n			set_pref($pref, $post[$pref], $smd_at_event, PREF_HIDDEN);\n		}\n	}\n	$message = gTxt(\'preferences_saved\');\n	smd_at_setup($message);\n}\n\n// ------------------------\n// UTILITY FUNCTIONS\n// ------------------------\nfunction smd_at_get_privs($user=\'\') {\n	global $txp_user;\n	$user = ($user) ? $user : $txp_user;\n	$privs = safe_field(\"privs\", \"txp_users\", \"name=\'\".doSlash($user).\"\'\");\n	return $privs;\n}\n\n// ------------------------\nfunction smd_at_exists($name) {\n	$instance = theme::factory($name);\n	return $instance;\n}\n\n// If per-user skin support is OFF, the global skin wins.\n// One exception: if the user is an admin, allow the current skin to prevail so they may switch skins with impunity\n// and not affect the global skin\nfunction smd_at_per() {\n	global $smd_at_privs, $theme, $txp_user;\n\n	$at_prefs = smd_at_get_prefs();\n	$skinSys = $at_prefs[\'smd_at_system\'];\n	$privs = smd_at_get_privs();\n	$gbl_skin = ($at_prefs[\'smd_at_global_skin\'] != \'\') ? $at_prefs[\'smd_at_global_skin\'] : \'\';\n	$gbl_skin = (smd_at_exists($gbl_skin)) ? $gbl_skin : \'classic\';\n\n	switch ($skinSys) {\n		case 0:\n			// Global forced skin (except admins)\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				return $gbl_skin;\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n		case 1:\n			// Group-level forced skins\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				$forceSkins = ($at_prefs[\'smd_at_group_list\'] != \'\') ? $at_prefs[\'smd_at_group_list\'] : (($gbl_skin) ? $gbl_skin : $theme->name);\n\n				// Split the group skin list\n				$gskins = array();\n				$gopts = explode(\',\', $forceSkins);\n				foreach ($gopts as $grpdef) {\n					if ($grpdef == \'\') continue;\n					$privgrp = explode(\":\", $grpdef);\n					$skinid = array_shift($privgrp);\n					if (in_array($privs, $privgrp)) {\n						return (smd_at_exists($skinid)) ? $skinid : \'classic\';;\n					}\n				}\n				return ($gbl_skin) ? $gbl_skin : $theme->name;\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n		case 2:\n			// Per-user skin. If user is non-admin, check the skin is in the allowed list\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				$validSkins = ($at_prefs[\'smd_at_user_list\']) ? $at_prefs[\'smd_at_user_list\'] : (($gbl_skin) ? $gbl_skin : $theme->name);\n				$validSkins = do_list($validSkins);\n				$the_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (in_array($the_skin, $validSkins) && smd_at_exists($the_skin)) ? $the_skin : \'\';\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n	}\n}\n\n// ------------------------\n// Read and return the manifest, if it exists and add a few other niceties to the array for later\nfunction smd_at_read_skinfo($skin) {\n	global $smd_core_themes;\n\n	$this_theme = theme::factory($skin);\n	$skinfo = array();\n	if ($this_theme) {\n		$skinfo = $this_theme->manifest();\n		$skinfo[\'dname\'] = empty($skinfo[\'title\']) ? ucwords($this_theme->name) : $skinfo[\'title\'];\n		$skinfo[\'name\'] = $this_theme->name;\n		$skinfo[\'path\'] = $this_theme->url;\n		$skinfo[\'phpfile\'] = $this_theme->path($skin);\n		$skinfo[\'based_on\'] = \'\';\n		$contents = file_get_contents($skinfo[\'phpfile\']);\n		if (($pos = strpos($contents, \'theme::based_on\')) !== false) {\n			$begpos = strpos($contents, \"\'\", $pos)+1;\n			$endpos = strpos($contents, \"\'\", $begpos);\n			$base = substr($contents, $begpos, $endpos-$begpos);\n			$skinfo[\'based_on\'] = (in_array($base, $smd_core_themes)) ? \'\' : $base;\n		}\n		unset($this_theme);\n	}\n	return $skinfo;\n}\n\n// ------------------------\n// Grab the thumbnail filename if it exists, optionally return a formatted img tag\nfunction smd_at_get_thumb($skin, $img=0) {\n	$at_prefs = smd_at_get_prefs();\n	$tw = $at_prefs[\'smd_at_tw\'];\n	$th = $at_prefs[\'smd_at_th\'];\n	$use_thumb = ($tw == 0 || $th == 0) ? false : true;\n	$dflt_thumb = \'data:image/png;base64, \';\n\n	// An array means the manifest has already been read and is being passed into this function\n	if (!is_array($skin)) {\n		$skin = smd_at_read_skinfo($skin);\n	}\n	if ($skin) {\n		$skindir = $skin[\'path\'];\n		$thumbname = glob($skindir.\'screenshot.*\');\n		return ($img && $use_thumb) ? \'<img class=\"content-image\" src=\"\'.(($thumbname && file_exists($thumbname[0])) ? $thumbname[0] : $dflt_thumb).\'\" alt=\"\'.((isset($skin[\'name\'])) ? $skin[\'name\'] : \'\').\' thumbnail\" width=\"\'.$tw.\'\" height=\"\'.$th.\'\" />\' : \'\';\n	} else {\n		return \'\';\n	}\n}\n\n// ------------------------\nfunction smd_at_read_skins() {\n	$skin_list = theme::names();\n	$skin_list = smd_at_sort($skin_list);\n	return $skin_list;\n}\n\n// ------------------------\n// Sort the skins, keeping core themes at the top\nfunction smd_at_sort($skin_list) {\n	global $prefs, $smd_core_themes;\n\n	$caseSense = isset($prefs[\'smd_at_case_sort\']) ? $prefs[\'smd_at_case_sort\'] : 1;\n\n	if($caseSense) {\n		natsort($skin_list);\n	} else {\n		natcasesort($skin_list);\n	}\n	$dflt_found = array();\n	foreach ($skin_list as $skin_idx => $skin_name) {\n		if (in_array($skin_name, $smd_core_themes)) {\n			unset($skin_list[$skin_idx]);\n			$dflt_found[] = $skin_name;\n		}\n	}\n\n	$dflts = array_reverse($dflt_found); // So they go in alphabetical order\n	foreach ($dflts as $coretheme) {\n		array_unshift($skin_list, $coretheme);\n	}\n\n	return $skin_list;\n}\n\n// ------------------------\nfunction smd_at_listdir($start_dir=\'.\') {\n	$files = array();\n\n	if (is_dir($start_dir)) {\n		$fh = opendir($start_dir);\n		while (($file = readdir($fh)) !== false) {\n			// loop through the files, skipping . and .., and recursing if necessary\n			if (strcmp($file, \'.\')==0 || strcmp($file, \'..\')==0) continue;\n			$filepath = $start_dir . \'/\' . $file;\n			if ( is_dir($filepath) ) {\n				$files = array_merge($files, smd_at_listdir($filepath));\n			} else {\n				array_push($files, $filepath);\n			}\n		}\n		closedir($fh);\n	} else {\n\n		// false if the function was called with an invalid non-directory argument\n		$files = false;\n	}\n\n	return $files;\n}\n\n// ------------------------\n// Recursive rmdir() courtesy of the php manual user comments\nfunction smd_rmdir_recursive($filepath) {\n	if (is_dir($filepath) && !is_link($filepath)) {\n		if ($dh = opendir($filepath)) {\n			while (($sf = readdir($dh)) !== false) {\n				if ($sf == \'.\' || $sf == \'..\') continue;\n				if (!smd_rmdir_recursive($filepath.\'/\'.$sf)) {\n					break; // Doh, can\'t delete\n				}\n			}\n			closedir($dh);\n		}\n		return rmdir($filepath);\n	}\n	return unlink($filepath);\n}\n\n// ------------------------\n// From PHP manual comments. Thanks puremango\nfunction smd_at_is_dir($dir) {\n	// bypasses open_basedir restrictions of is_dir and fileperms\n	$tmp_cmd = `ls -dl $dir`;\n	$dir_flag = $tmp_cmd[0];\n	if($dir_flag!=\"d\") {\n		// not d; use next char (first char might be \'s\' and is still directory)\n		$dir_flag = $tmp_cmd[1];\n	}\n	return ($dir_flag==\"d\");\n}\n\n// ------------------------\nfunction smd_at_change_pageby() {\n	setcookie(\'smd_at_pageby\', gps(\'qty\'));\n	smd_at_list();\n}\n\n// ------------------------\nfunction smd_at_switch($message=\'\') {\n	global $smd_at_privs;\n\n	extract(doSlash(gpsa(array(\'skin\', \'event\', \'nextstep\'))));\n	$url = \"?event=\".$event.\"&step=\".$nextstep;\n\n	if ($skin && smd_at_exists($skin)) {\n		set_pref(\'smd_skin\', $skin, $event, PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n		$url .= \'&message=\' . (($message) ? $message : gTxt(\'smd_at_skin_switched\', array(\'{skin}\' => $skin)));\n	} else {\n		$url .= \'&message=\' . $message;\n	}\n\n	// Since the headers have been sent, double-declutch in order to show the skin this first time\n	echo <<<EOS\n<script type=\"text/javascript\">\nwindow.location.href=\"{$url}\";\n</script>\n<noscript>\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\n</noscript>\nEOS;\n	exit;\n}\n\n// All-user skin chooser\nfunction smd_admat($event, $step) {\n	if(!$step or !in_array($step, array(\n			\'smd_at_switch\',\n		))) {\n		smd_at_chooser(\'\');\n	} else $step();\n}\n\n// ------------------------\nfunction smd_at_chooser($message=\'\') {\n	global $prefs, $smd_at_adm_event, $theme;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$tw = $at_prefs[\'smd_at_tw\'] + 10;\n	$th = $at_prefs[\'smd_at_th\'] + 80;\n\n	$message = ($message) ? $message : gps(\'message\');\n	pagetop(gTxt(\'smd_at_skinner\'),$message);\n\n	$curr_skin = $theme->name;\n\n	$allowed = \'\';\n	if (isset($prefs[\'smd_at_user_list\'])) {\n		$allowed = explode(\",\",$prefs[\'smd_at_user_list\']);\n		foreach ($allowed as $idx => $valid) {\n			if ($valid == \'\') {\n				unset($allowed[$idx]);\n			}\n		}\n	}\n	$allowed = smd_at_sort($allowed);\n	$hdrow = hed(gTxt(\'smd_at_avail_title\'), 2);\n\n	echo \'<div class=\"txp-container\">\';\n\n	if ($layout==0) {\n		echo \'<div class=\"txp-listtables\">\',\n			startTable(\'\', \'\', \'txp-list\'),\n			tr(\n				tda($hdrow, \' colspan=\"3\"\')\n			),\n			assHead(\n				gTxt(\'smd_at_skin_gbl\'),\n				\'author\',\n				\'description\'\n			);\n	} else {\n		echo $hdrow, \'<div class=\"txp-grid\">\';\n	}\n	foreach ($allowed as $skin_name) {\n		$skinfo = smd_at_read_skinfo($skin_name);\n		if ($skinfo) {\n			$thumbnail = smd_at_get_thumb($skinfo, 1);\n			$switch_link = \'?event=\'.$smd_at_adm_event.a.\'step=smd_at_switch\'.a.\'nextstep=smd_admat\'.a.\'skin=\'.$skin_name;\n			$thumblock = ($layout==0)\n						? \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.$skinfo[\'dname\'].(($thumbnail) ? br.$thumbnail : \'\').\'</a>\'\n						: \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.(($thumbnail) ? $thumbnail : gTxt(\'smd_at_apply_skin\')).\'</a>\';\n			$authblock = (strpos($skinfo[\'author_uri\'], \"http://\") === 0) ? \'<a href=\"\'.$skinfo[\'author_uri\'].\'\">\'.$skinfo[\'author\'].\'</a>\' : $skinfo[\'author\'];\n\n			if ($layout==0) {\n				echo tr(\n					n.td($thumblock)\n					.n.td($authblock)\n					.n.td($skinfo[\'description\'])\n					, (($skin_name == $curr_skin) ? \' class=\"highlight\"\' : \'\')\n				);\n			} else {\n				echo n, \'<div class=\"txp-grid-cell\'.(($skin_name == $curr_skin) ? \' highlight\' : \'\').\'\">\',\n						n, hed($skinfo[\'dname\'], 3),\n						n, \'<p>\', $thumblock, \'</p>\',\n						n, \'<p>\', n, gTxt(\'smd_at_by\'), n, $authblock.\'</p>\',\n					n, \'</div>\';\n			}\n		}\n	}\n	if ($layout==0) {\n		echo n, endTable(),\n			n, \'</div>\';\n	} else {\n		echo n, \'</div>\';\n	}\n	echo n, \'</div>\';\n}\n\n// -------------------------------------------------------------\nfunction smd_at_save_pane_state() {\n	list($p1, $p2) = smd_at_valid_types();\n\n	$panes = array_merge($p1, $p2, array(\'folders\', \'image_files\', \'other\'));\n	$pane = gps(\'pane\');\n\n	if (in_array($pane, $panes))\n	{\n		set_pref(\"smd_at_pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_at\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n		send_xml_response();\n	} else {\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n	}\n}', '/**\n * smd_admin_themes\n *\n * A Textpattern CMS plugin for creating, editing, testing, installing and distributing admin-side themes\n *  -> Employ images, CSS, JS, HTML/PHP in your theme\n *  -> Use constants in your CSS files\n *  -> Base your theme on an existing theme to tweak it to your taste\n *  -> Export a theme package for distribution to the community in either .tar, .tgz, .bz2 or .zip format (requires smd_crunchers plugin)\n *  -> Import / install other themes from the \"community repository\":http://textgarden.org/administration-themes\n *  -> Administrator can set a default theme for all users, or enable per-user / per-role themes\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * Verify that files can\'t be uploaded/created outside of the theme\'s dir\n//  * \'empty_info\' message shouldn\'t fire for bad file upload\n// TOFIX:\n//  * rawurlencode/decode for files with spaces in them. Currently can\'t edit contents of file with spaces in it as they get encoded as %20. If encoded, with each save they get %2520 added and it gets longer and longer....\n\nif (@txpinterface == \'admin\') {\n	// Silence warnings because, although the plugin requires smd_crunchers for the\n	// import/export features, it\'ll function in a reduced capacity without it\n	@require_plugin(\'smd_crunchers\');\n\n	global $smd_at_event, $smd_at_fullev, $smd_at_adm_event, $smd_at_privs,\n		$smd_core_themes, $prefs, $smd_at_feedurl, $smd_at_theme_repo;\n\n	$smd_at_event = \'smd_at\';\n	$smd_at_fullev = \'smd_admin_themes\';\n	$smd_at_adm_event = \'smd_admat\';\n	$smd_at_privs = \'1\';\n	$smd_at_feedurl = \'http://textgarden.org/admin-themes-feed\';\n	$smd_at_theme_repo = \'http://textgarden.org/administration-themes\';\n\n	$smd_core_themes = array(\'classic\', \'hive\', \'remora\');\n\n	add_privs($smd_at_event, $smd_at_privs);\n	add_privs(\'plugin_prefs.\'.$smd_at_fullev, $smd_at_privs);\n	register_tab(\"extensions\", $smd_at_event, gTxt(\'smd_at_tab_name\'));\n	register_callback(\'smd_at_options\', \'plugin_prefs.\'.$smd_at_fullev);\n	register_callback(\'smd_at_welcome\', \'plugin_lifecycle.\'.$smd_at_fullev);\n	register_callback(\'smd_at\', $smd_at_event);\n	register_callback(\'smd_at_per\', \'admin_side\', \'theme_name\');\n	register_callback(\'smd_at_link\', \'prefs_ui\', \'theme_name\');\n	register_callback(\'smd_at_inject_css\', \'admin_side\', \'head_end\');\n\n	if (isset($prefs[\'smd_at_system\']) && $prefs[\'smd_at_system\'] == 2) {\n		add_privs($smd_at_adm_event, \'2,3,4,5,6\');\n		register_tab(\"admin\", $smd_at_adm_event, gTxt(\'smd_at_skinner\'));\n		register_callback(\'smd_admat\', $smd_at_adm_event);\n	}\n}\n\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers\nfunction smd_at_get_style_rules() {\n	$smd_at_styles = array(\n		\'smd_at\' =>\'\n.smd_at_update { font-weight:bold; color:#900; }\n#smd_at_images { margin:0px 6px -4px 0; }\n.smd_skin_filetypes { margin:12px 0; }\nsmd_fakebtn { color:#555; cursor:pointer; }\n\'\n	);\n\n	return $smd_at_styles;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_inject_css($evt, $stp) {\n	global $smd_at_event, $event;\n\n	if ($event == $smd_at_event) {\n		$smd_at_styles = smd_at_get_style_rules();\n		echo \'<style type=\"text/css\">\', $smd_at_styles[\'smd_at\'], \'</style>\';\n	}\n\n	return;\n}\n\n// ------------------------\n// Plugin jumpoff point\nfunction smd_at($evt, $stp) {\n	$available_steps = array(\n		\'smd_at_clone\'         => true,\n		\'smd_at_delete\'        => true,\n		\'smd_at_delete_folder\' => true,\n		\'smd_at_edit\'          => false,\n		\'smd_at_multi_edit\'    => true,\n		\'smd_at_export\'        => true,\n		\'smd_at_import\'        => true,\n		\'smd_at_newfile\'       => false,\n		\'smd_at_prefs_install\' => false,\n		\'smd_at_prefs_remove\'  => false,\n		\'smd_at_prefs_update\'  => true,\n		\'smd_at_preview\'       => true,\n		\'smd_at_rename\'        => true,\n		\'smd_at_save\'          => true,\n		\'smd_at_setup\'         => false,\n		\'smd_at_switch\'        => true,\n		\'smd_at_upload\'        => true,\n		\'smd_at_change_pageby\' => true,\n		\'save_pane_state\'      => true,\n	);\n\n	if ($stp == \'save_pane_state\') {\n		smd_at_save_pane_state();\n	} else if ($stp && bouncer($stp, $available_steps)) {\n		$stp();\n	} else {\n		smd_at_list();\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_welcome($event, $step) {\n	$msg = \'\';\n	switch ($step) {\n		case \'installed\':\n			smd_at_prefs_install(0);\n			break;\n		case \'deleted\':\n			smd_at_prefs_remove(0);\n			break;\n	}\n	return $msg;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_link() {\n	global $smd_at_event;\n	return href(gTxt(\'smd_at_manage_lbl\'), \'?event=\'.$smd_at_event);\n}\n\n// ------------------------\nfunction smd_at_list($message=\'\') {\n	global $smd_at_event, $prefs, $theme, $smd_core_themes, $smd_at_fullev, $smd_at_feedurl, $smd_at_theme_repo;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n\n	extract(gpsa(array(\'step\', \'page\', \'crit\', \'search_method\', \'skin\')));\n	$message = ($message) ? $message : gps(\'message\');\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$stylerule = ($layout==0) ? \'list\' : \'grid\';\n	$tw = $at_prefs[\'smd_at_tw\'] + 10;\n	$th = $at_prefs[\'smd_at_th\'] + 80;\n	$helpLink = \"?event=plugin&step=plugin_help&name=$smd_at_fullev#usage\";\n\n	$pageby = (gps(\'qty\')) ? gps(\'qty\') : ((cs(\'smd_at_pageby\')) ? cs(\'smd_at_pageby\') : 15);\n	$max_theme_size = $at_prefs[\'smd_at_max_theme_size\'];\n\n	$curr_skin = (smd_at_exists($theme->name)) ? $theme->name : \'classic\';\n	$skin_list = smd_at_read_skins();\n	$crushers = smd_at_crush_options(\'compress\');\n	$uncrushers = smd_at_crush_options(\'decompress\');\n	$bases = array();\n\n	// Get a list of ancestor themes (i.e. dependencies)\n	foreach ($skin_list as $skina) {\n		$manifest = smd_at_read_skinfo($skina);\n		if ($manifest) {\n			if ($manifest[\'based_on\']) {\n				$bases[] = $manifest[\'based_on\'];\n			}\n		}\n	}\n	$bases = array_unique($bases);\n\n	pagetop(gTxt(\'smd_at_manage_lbl\'),$message);\n\n	// Handle paging\n	$total = count($skin_list);\n	$limit = max(@$pageby, 15);\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\n	$skin_list = array_slice($skin_list, $offset, $limit);\n\n	$qs = array(\n		\"event\" => $smd_at_event,\n		\"page\" => $page,\n	);\n\n	echo script_js(<<<EOJS\nfunction smd_getcrush(skin) {\n	skinHandle = \"crush_\"+skin;\n	jQuery(\'.smd_popup\').each(function() {\n		box = jQuery(this);\n		if (box.attr(\"id\") == skinHandle) {\n			if (box.css(\"display\") == \"none\") {\n				box.slideDown(\'fast\');\n			} else {\n				box.slideUp(\'fast\');\n			}\n		} else {\n			box.slideUp(\'fast\');\n		}\n	});\n	jQuery(\'#\'+skinHandle+\' a\').click(function() {\n		jQuery(this).attr(\"href\", \"?event={$smd_at_event}&skin=\"+skin+\"&step=smd_at_export&crush=\"+jQuery(\'#\'+skinHandle+\' input[type=\\\'radio\\\']:checked\').val()+\"&page=\"+{$page}+\"&_txp_token=\"+textpattern._txp_token);\n	});\n	return false;\n}\n\nfunction smd_getval(skin, step) {\n	skinHandle = \"pop_\"+skin;\n	jQuery(\'.smd_popup\').each(function() {\n		box = jQuery(this);\n		if (box.attr(\"id\") == skinHandle) {\n			if (box.css(\"display\") == \"none\") {\n				box.slideDown(\'fast\');\n				jQuery(\'#\'+skinHandle+\' input\').focus();\n			} else {\n				box.slideUp(\'fast\');\n			}\n		} else {\n			box.slideUp(\'fast\');\n		}\n	});\n	jQuery(\'#\'+skinHandle+\' a\').click(function() {\n		jQuery(this).attr(\"href\", \"?event={$smd_at_event}&skin=\"+((skin==\'new\')? \'\' : skin)+\"&step=\"+step+\"&new_skin=\"+jQuery(\'#\'+skinHandle+\' input\').val()+\"&page=\"+{$page}+\"&_txp_token=\"+textpattern._txp_token);\n	});\n\n	jQuery(\'#\'+skinHandle+\' input\').keyup(function () {\n		this.value = this.value.replace(/[^a-zA-Z0-9_]/g,\'\');\n		while (this.value.length > 0 && this.value.substring(0,1).match(/[0-9]/)) {\n			this.value = this.value.substring(1);\n		}\n	});\n\n	return false;\n}\nEOJS\n	);\n\n	// List the available skins\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_at_tab_name\'), \'</h1>\',\n		n, \'<div id=\"smd_control\" class=\"txp-control-panel\">\',\n		n, (($uncrushers) ? upload_form(gTxt(\'smd_at_install_skin\'), \'\', \'smd_at_import\', $smd_at_event, \'\', $max_theme_size).n.\'<p><small>\'.gTxt(\'smd_at_supported_import\', array(\'{types}\' => (($uncrushers) ? join(\', \', $uncrushers) : gTxt(\'none\')) )).\'</small></p>\' : \'\').\n		n, \'<p class=\"txp-buttons\">\',\n			n, \'<a href=\"?event=\', $smd_at_event, a, \'step=smd_at_setup\">\', gTxt(\'smd_at_setup\'), \'</a>\',\n			n, gTxt(\'smd_at_help_link\', array(\'{link}\' => $helpLink)),\n			n, \'<a href=\"\'.$smd_at_theme_repo.\'\">\', gTxt(\'smd_at_find_theme\'), \'</a>\',\n			n, \'<a href=\"#\" onclick=\"return smd_getval(\\\'new\\\', \\\'smd_at_clone\\\');\">\'.gTxt(\'smd_at_new\').\'</a>\',\n		n, \'</p>\',\n		n, \'<p id=\"pop_new\" class=\"smd_popup empty\">\',\n			n, \'<span>\', gTxt(\'smd_at_new_skin\'), \'</span>\', n, \'<input type=\"text\" name=\"smd_text_name\" value=\"\" size=\"\', INPUT_REGULAR, \'\" /> <a href=\"#\">[\', gTxt(\'smd_at_confirm\'), \']</a>\',\n		n, \'</p>\',\n		n, \'</div>\';\n\n	echo \'<div class=\"txp-container\">\',\n		\'<form method=\"post\" name=\"longform\" action=\"\', join_qs($qs), \'\">\';\n\n	$hdrow = hed(gTxt(\'smd_at_list_title\'), 2);\n	if ($layout==\"0\") {\n		echo \'<div class=\"txp-listtables\">\',\n			startTable(\'\',\'\',\'txp-list\',5),\n			tr(\n				tda($hdrow, \' colspan=\"7\"\')\n			),\n			assHead(\n				gTxt(\'smd_at_skin_gbl\'),\n				\'author\',\n				gTxt(\'version\'),\n				\'description\',\n				gTxt(\'smd_at_actions\')\n			);\n	} else {\n		echo $hdrow, \'<div class=\"txp-grid\">\';\n	}\n	if( is_callable(\'fsockopen\') )\n		$transport = \'fsock\';\n	elseif( is_callable(\'curl_init\') ) {\n		$transport = \'curl\';\n	} else {\n		$transport = \'\';\n	}\n\n	$feed = \'\';\n	switch ($transport) {\n		case \'curl\':\n			$c = curl_init();\n			curl_setopt($c, CURLOPT_URL, $smd_at_feedurl);\n			curl_setopt($c, CURLOPT_REFERER, hu);\n			curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\n			curl_setopt($c, CURLOPT_VERBOSE, false);\n			curl_setopt($c, CURLOPT_TIMEOUT, 10);\n			$feed = curl_exec($c);\n			break;\n		case \'fsock\':\n			$url = parse_url($smd_at_feedurl);\n			$fp = @fsockopen ($url[\'host\'], 80, $errno, $errstr, 5);\n			if ($fp) {\n				$qry = \'GET \'.$smd_at_feedurl;\n				$qry .= \" HTTP/1.0\\r\\n\\r\\n\";\n\n				fputs($fp, $qry);\n				stream_set_timeout($fp, 10);\n				$info = stream_get_meta_data($fp);\n\n				$hdrs = true;\n				while ((!feof($fp)) && (!$info[\'timed_out\'])) {\n					$line = fgets($fp, 8192);\n					$line = preg_replace(\"[\\r\\n]\", \"\", $line);\n					if ($hdrs == false) {\n						$feed .= $line.\"\\n\";\n					}\n					if (strlen($line) == 0) $hdrs = false;\n				}\n				if ($info[\'timed_out\']) {\n					$feed = \'\';\n				}\n				fclose($fp);\n			} else {\n				$feed = \'\';\n			}\n			break;\n		default:\n			$feed = \'\';\n	}\n\n	$smd_at_vlast = \'\';\n	$feeds = array();\n	if (isset($prefs[\'smd_at_versions\']) && isset($prefs[\'smd_at_vlast\'])) {\n		if (time() - $prefs[\'smd_at_vlast\'] < 86400) {\n			// Last updated within the last day, so read the cached version numbers\n			$feeds = unserialize($prefs[\'smd_at_versions\']);\n		}\n	}\n	if (!$feeds || gps(\'read_versions\')) {\n		$feeds = smd_at_feed($feed);\n		$installed_feed = array();\n		$feeds = (is_array($feeds)) ? $feeds : array();\n\n		// Only record the version info from themes that are installed\n		foreach($skin_list as $skin_name) {\n			$feed_skin_name = str_replace(\'_\', \'-\', $skin_name); // Unsanitize the url_title\n			if (array_key_exists($feed_skin_name, $feeds)) {\n				$installed_feed[$feed_skin_name] = $feeds[$feed_skin_name];\n			}\n		}\n		set_pref(\'smd_at_vlast\', time(), $smd_at_event, PREF_HIDDEN, \'text_input\');\n		set_pref(\'smd_at_versions\', serialize($installed_feed), $smd_at_event, PREF_HIDDEN, \'text_input\');\n	}\n\n	foreach ($skin_list as $skin_name) {\n		// Read each manifest and set up the labels\n		$skinfo = smd_at_read_skinfo($skin_name);\n		$feed_skin_name = str_replace(\'_\', \'-\', $skin_name); // Unsanitize for url_title matching\n\n		$thumbnail = smd_at_get_thumb($skinfo, 1);\n		$this_skin = ($skinfo) ? $skinfo[\'dname\'] : $skin_name;\n		$is_gbl = ($skin_name == $at_prefs[\'smd_at_global_skin\']) ? gTxt(\'smd_at_is_global\') : \'\';\n		$switch_link = \'?event=\'.$smd_at_event.a.\'step=smd_at_switch\'.a.\'nextstep=smd_at_list\'.a.\'skin=\'.$skin_name.a.\'page=\'.$page.a.\'_txp_token=\'.form_token();\n		$skintext = $this_skin .n. $is_gbl .n. (($skinfo && $skinfo[\'based_on\']) ? n.gTxt(\'smd_at_based_on\', array(\'{skin}\' => $skinfo[\'based_on\'])) : \'\');\n		$thumblock = ($layout==0)\n						? \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.$skintext.(($thumbnail) ? br.$thumbnail : \'\').\'</a>\'\n						: \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.(($thumbnail) ? $thumbnail : gTxt(\'smd_at_apply_skin\')).\'</a>\';\n		$authblock = (($skinfo && strpos($skinfo[\'author_uri\'], \"http://\") === 0) ? \'<a href=\"\'.$skinfo[\'author_uri\'].\'\">\'.$skinfo[\'author\'].\'</a>\' : (($skinfo) ? $skinfo[\'author\'] : \'-\'));\n		$actblock = \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_edit\'.a.\'skin=\'.$skin_name.\'\">[\'.gTxt(\'edit\').\']</a>\'\n			. sp .(($skinfo) ? \'<a href=\"#\" onclick=\"return smd_getval(\\\'\'.$skin_name.\'\\\', \\\'smd_at_clone\\\');\">[\'.gTxt(\'smd_at_clone\').\']</a>\' : \'\')\n			. sp .(($skinfo && $crushers) ? \'<a href=\"#\" onclick=\"return smd_getcrush(\\\'\'.$skin_name.\'\\\');\">[\'.gTxt(\'smd_at_export\').\']</a>\' : \'\')\n			. sp .((in_array($skin_name, $smd_core_themes) || in_array($skin_name, $bases)) ? \'\' : \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_delete\'.a.\'skin=\'.$skin_name.a.\'page=\'.$page.a.\'_txp_token=\'.form_token().\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_at_delete_confirm\', array(\'{skin}\' => $skin_name)).\'\\\');\">[\'.gTxt(\'delete\').\']</a>\');\n		$cloneblock = \'<p id=\"pop_\'.$skin_name.\'\" class=\"smd_popup empty\"><span>\'.gTxt(\'smd_at_new_cloneskin\').\'</span>\'.n.\'<input type=\"text\" name=\"smd_text_name\" value=\"\" size=\"\'.INPUT_MEDIUM.\'\" class=\"input-medium\" />\'.n.\'<a href=\"#\">[\'.strong(gTxt(\'smd_at_confirm\')).\']</a></p>\';\n		$crushblock = (($crushers) ? \'<p id=\"crush_\'.$skin_name.\'\" class=\"smd_popup empty\"><span>\'.gTxt(\'smd_at_crush_format\').\'</span>\'.radioSet($crushers, $skin_name.\'_crush\', $at_prefs[\'smd_at_crush\']).n.\'<a href=\"#\">[\'.strong(gTxt(\'smd_at_confirm\')).\']</a></p>\' : \'\');\n		$verblock = ($skinfo) ? ((isset($feeds[$feed_skin_name]) && isset($feeds[$feed_skin_name][\'summary\']) && version_compare($feeds[$feed_skin_name][\'summary\'],$skinfo[\'version\'])===1) ? \'<a href=\"\'.$feeds[$feed_skin_name][\'link\'].\'\" class=\"smd_at_update\">\'.gTxt(\'smd_at_version\').$skinfo[\'version\'].\'</a>\' : gTxt(\'smd_at_version\').$skinfo[\'version\']) : \'\';\n\n		if ($layout==0) {\n			echo tr(\n				n.td($thumblock)\n				.n.td($authblock)\n				.n.td($verblock)\n				.n.td((($skinfo)? $skinfo[\'description\'] : \'-\').(($skinfo && $skinfo[\'help\']) ? \' \'.href(\'[\'.gTxt(\'help\').\']\', $skinfo[\'help\']) : \'\'))\n				.n.td($actblock .n. $cloneblock .n. $crushblock)\n			, (($skin_name == $curr_skin) ? \' class=\"highlight\"\' : \'\')\n			);\n		} else {\n			echo \'<div class=\"txp-grid-cell\', (($skin_name == $curr_skin) ? \' highlight\' : \'\'), \'\">\',\n					n, hed($skintext, 3),\n					n, \'<p>\', $thumblock, \'</p>\',\n					n, \'<p>\', $verblock, n, gTxt(\'smd_at_by\'), n, $authblock, \'</p>\',\n					n, \'<p>\', $actblock, \'</p>\',\n					n, $cloneblock,\n					n, $crushblock,\n				n, \'</div>\';\n		}\n	}\n\n	if ($layout==\"0\") {\n		echo n, endTable();\n	}\n\n	echo n, \'</div>\',\n		n, tInput(),\n		n, \'</form>\',\n		n, \'<div id=\"list_navigation\" class=\"txp-navigation\">\',\n		n, nav_form($smd_at_event, $page, $numPages, NULL, NULL, NULL, NULL),\n		n, pageby_form($smd_at_event, $pageby),\n		n, \'</div>\',\n		n, \'</div>\';\n}\n\nfunction smd_at_feed($feed) {\n	global $feeds;\n\n	$feed=preg_replace(\"/>\".\"[[:space:]]+\".\"</i\", \"><\", $feed); // Kill whitespace in feed\n	$xmlparser = xml_parser_create();\n	xml_set_element_handler($xmlparser, \"smd_at_feed_start_tag\", \"smd_at_feed_end_tag\");\n	xml_set_character_data_handler($xmlparser, \"smd_at_feed_tag_contents\");\n	xml_parse($xmlparser, $feed);\n	xml_parser_free($xmlparser);\n\n	return $feeds;\n}\n\nfunction smd_at_feed_start_tag($parser, $name, $attribs) {\n	global $smd_at_feedin, $smd_at_feedtag;\n	if ($name == \"ENTRY\") {\n		$smd_at_feedin = true;\n	}\n	if ($smd_at_feedin) {\n		$smd_at_feedtag = $name;\n	}\n}\nfunction smd_at_feed_end_tag($parser, $name) {\n	global $smd_at_feedin, $smd_at_feedtag, $smd_at_feedname;\n	if ($name == \"ENTRY\") {\n		$smd_at_feedin = false;\n		$smd_at_feedname = \'\';\n	}\n}\n\nfunction smd_at_feed_tag_contents($parser, $data) {\n	global $smd_at_feedin, $smd_at_feedtag, $smd_at_feedname, $feeds;\n	if ($smd_at_feedin) {\n		if ($smd_at_feedtag == \"NAME\") {\n			$feeds[$data] = \'\';\n			$smd_at_feedname = $data;\n		}\n		if (isset($feeds[$smd_at_feedname])) {\n			if ($smd_at_feedtag == \"TITLE\" || $smd_at_feedtag == \"SUMMARY\" || $smd_at_feedtag == \"UPDATED\" || $smd_at_feedtag == \"ID\" || $smd_at_feedtag == \"LINK\") {\n				$feeds[$smd_at_feedname][strtolower($smd_at_feedtag)] = $data;\n			}\n		}\n	}\n}\n\n// ------------------------\nfunction smd_at_valid_types() {\n	$editable = array(\'ssc\', \'scss\', \'css\', \'js\', \'php\', \'txt\', \'textile\');\n	$imagable = array(\'jpg\', \'jpeg\', \'gif\', \'png\', \'svg\');\n	return array($editable, $imagable);\n}\n\n// ------------------------\nfunction smd_at_edit($message=\'\') {\n	global $smd_at_event, $file_max_upload_size, $step;\n\n	extract(doSlash(gpsa(array(\'skin\', \'new_skin\', \'new_skin_dir\', \'file\', \'dir\'))));\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n	$layout = $at_prefs[\'smd_at_layout\'];\n\n	list($editable, $imagable) = smd_at_valid_types();\n\n	$skinfo = smd_at_read_skinfo($skin);\n	$file = (empty($new_skin)) ? $file : $new_skin;\n	$dir = (empty($new_skin_dir)) ? $dir : $new_skin_dir;\n	$files = smd_at_listdir(THEME.$skin);\n	sort($files);\n\n	// Split the list into its directory groups\n	$allfiles = array();\n	foreach ($files as $currfile) {\n		$file_info = pathinfo($currfile);\n		// Ignore any rogue Subversion files\n		if (strpos($file_info[\'dirname\'], \'/.svn\') === false) {\n			$file_type = strtolower((isset($file_info[\'extension\'])) ? $file_info[\'extension\'] : \'other\');\n			$allfiles[$file_type][] = $currfile;\n		}\n	}\n	if (gps(\'smd_at_edits\')) {\n		$contents = str_replace(\'\\r\\n\',\'\n\',gps(\'smd_at_edits\')); // newline workaround\n	} else {\n		$contents = \'\';\n		if ($file && file_exists(THEME.$skin.DS.$dir.DS.$file)) {\n			$contents = file(THEME.$skin.DS.$dir.DS.$file);\n			$contents = join(\"\", $contents);\n		}\n	}\n\n	$info = explode (\'.\',$file);\n	$lastpart = count($info)-1;\n	$ext = strtolower(trim($info[$lastpart]));\n	$is_textile = ($ext == \'textile\');\n	$is_svg = ($ext == \'svg\');\n\n	// Begin rendering the page\n	pagetop(gTxt(\'smd_at_edit_lbl\', array(\'{theme}\' => $skin)), $message);\n\n	$skin_list = smd_at_read_skins();\n\n	// selectInput needs both index and value to be the same in this case\n	$skinsel = array();\n	foreach($skin_list as $key1 => $value1) {\n		$skinsel[$value1] = $skin_list[$key1];\n   }\n	$skinChange = \'<form name=\"skinchange\" action=\"index.php\">\'\n		. eInput($smd_at_event)\n		. sInput(\'smd_at_edit\')\n		. tInput()\n		. selectInput(\'skin\', $skinsel, $skin, 0, 1)\n		. \'</form>\';\n\n	$fileList = array();\n	$fileList[] = \'<div id=\"content_switcher\">\'\n		.n. hed(gTxt(\'smd_at_skin_files\'),2)\n		.n. $skinChange\n		.n. \'<p class=\"txp-buttons\">\'\n		.n. \'<a class=\"navlink\" href=\"?event=\'.$smd_at_event.a.\'step=smd_at_newfile\'.a.\'skin=\'.$skin.\'\">\'.gTxt(\'smd_at_new_file\').\'</a>\'\n		.n. \'<a class=\"navlink\" href=\"?event=\'.$smd_at_event.\'\">\'.gTxt(\'smd_at_all_themes\').\'</a>\'\n		.n. \'</p>\'\n		.n. \'<form action=\"index.php\" method=\"post\" enctype=\"multipart/form-data\">\'\n		.n. hInput(\'MAX_FILE_SIZE\', $file_max_upload_size)\n		.n. hInput(\'id\', $skin)\n		.n. eInput($smd_at_event)\n		.n. sInput(\'smd_at_upload\')\n		.n. tInput()\n		.n. \'<input type=\"file\" name=\"smd_at_file[]\" multiple=\"true\" />\'.sp.fInput(\'submit\', \'\', gTxt(\'upload\'))\n		.n. graf(\'<label for=\"smd_at_folder\">\'.gTxt(\'smd_at_folder\').\'</label>\' .n. fInput(\'text\', \'smd_at_folder\', ps(\'smd_at_folder\'), \'\', \'\', \'\', INPUT_MEDIUM, \'\', \'smd_at_folder\'))\n		.n. \'</form>\'\n//		.n. upload_form(gTxt(\'upload_file\'), \'\', \'smd_at_upload\', $smd_at_event, $skin, $file_max_upload_size, \'\', \'\')\n		.n. \'<form id=\"smd_at_allfiles\" action=\"index.php\" method=\"post\">\';\n\n	$dirs = $dirout = $out = array();\n	$methods = array(\n		\'delete\'     => gTxt(\'delete\'),\n	);\n\n	foreach ($allfiles as $ftype => $filerec) {\n		foreach ($filerec as $idx => $currfile) {\n			$justfile = basename($currfile);\n			$dirs[] = $theDir = dirname($currfile);\n			$justdir = trim(str_replace(THEME.$skin, \'\', $theDir), DS);\n			$parts = explode(\'.\', $justfile);\n			$basefile = array_shift($parts);\n			$showfile = ($file == $justfile) ? strong($justfile) : $justfile;\n			$is_img = smd_at_is_image($currfile);\n			$is_bin = smd_at_is_binary($currfile);\n\n			$edit = $is_img ? \'<a href=\"\'.$currfile.\'\" title=\"\'.gTxt(\'smd_at_preview_image\').\'\">\'.$showfile.\'</a>\' : ($is_bin ? $showfile : \'<a href=\"?event=\'.$smd_at_event.a.\'step=smd_at_edit\'.a.\'skin=\'.$skin.a.\'file=\'.$justfile.a.\'dir=\'.$justdir.\'\">\'.$showfile.\'</a>\');\n			$modbox = ($basefile == $skin)\n				?	\'\'\n				:	\'<input type=\"checkbox\" name=\"selected_files[]\" value=\"\'.$justdir.DS.$justfile.\'\" />\';\n\n			$group_name = in_array($ftype, $imagable) ? \'image_files\' : (in_array($ftype, $editable) ? $ftype : \'other\');\n\n			$out[$group_name][] = \'<li>\'.n.\'<span class=\"smd-at-list-action\">\'.$modbox.\'</span><span class=\"smd-at-list-name\">\'.$edit.\'</span></li>\';\n		}\n	}\n\n	$combined = array_merge($editable, array(\'image_files\', \'other\'));\n	foreach($combined as $group_name) {\n		if (isset($out[$group_name])) {\n			$visipref = \'smd_at_pane_\'.$group_name.\'_visible\';\n			$fileList[] = \'<div class=\"summary-details \'.$group_name.\'\"><h3 class=\"lever\'.(get_pref($visipref) ? \' expanded\' : \'\').\'\"><a href=\"#\'.$group_name.\'\">\'.gTxt(\'smd_at_\'.$group_name).\'</a></h3>\'.n.\n				\'<div id=\"\'.$group_name.\'\" class=\"toggle skin-list\" style=\"display:\'.(get_pref($visipref) ? \'block\' : \'none\').\'\">\'.n.\n				\'<ul class=\"plain-list\">\'.n;\n			$fileList[] = join(n, $out[$group_name]);\n			$fileList[] = \'</ul></div></div>\';\n		}\n	}\n\n	$fileList[] = multi_edit($methods, $smd_at_event, \'smd_at_multi_edit\', \'\', \'\', \'\', $skin);\n	$fileList[] = tInput().n.\'</form>\';\n\n	$dirs = array_unique($dirs);\n	unset($dirs[array_search(THEME.$skin, $dirs)]);\n	foreach ($dirs as $thisdir) {\n		$dirout[] = \'<tr><td class=\"smd-at-list-name\">\'.str_replace(THEME.$skin.\'/\', \'\', $thisdir).\'</td><td>\'.dLink($smd_at_event, \'smd_at_delete_folder\', \'delfolder\', $thisdir, 1, \'skin\', $skin).\'</td></tr>\';\n	}\n\n	$folderList = ($dirout) ? \'<div class=\"summary-details folders\"><h3 class=\"lever\'.(get_pref(\'smd_at_pane_folders_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#folders\">\'.gTxt(\'smd_at_folders\').\'</a></h3>\'.n.\n		\'<div id=\"folders\" class=\"toggle skin-list\" style=\"display:\'.(get_pref(\'smd_at_pane_folders_visible\') ? \'block\' : \'none\').\'\">\'.n.\n		\'<table>\'.\n		join(n, $dirout) . \'</table></div></div>\' : \'\';\n\n	$qs = array(\n		\"event\" => $smd_at_event,\n	);\n	$qsVars = \"index.php\".join_qs($qs);\n\n	// Render the rest of the page\n	echo startTable(\'\', \'\', \'txp-columntable\'),\n	tr(\n		td(\n			hed(gTxt(\'smd_at_edit_lbl\', array(\'{theme}\' => (($skinfo) ? $skinfo[\'dname\'] : $skin))), 1).\n			form(\n				n.graf(\n					\'<label for=\"new_skin\">\' . gTxt(\'smd_at_file_name\') . \'</label>\'\n					.n.\'<input type=\"text\" id=\"new_skin\" name=\"new_skin\" value=\"\'.txpspecialchars($file).\'\" size=\"\'.INPUT_REGULAR.\'\" />\'\n					.n.($is_textile ? \'<a class=\"smd_at_preview\" href=\"?event=\' . $smd_at_event .a. \'step=smd_at_preview\' .a. \'skin=\'.$skin.a.\'dir=\'.$dir.a.\'file=\'.$file.a.\'type=textile\'.a.\'_txp_token=\'.form_token().\'\">\'.gTxt(\'preview\').\'</a>\' : \'\')\n					.n.($is_svg ? \'<a class=\"smd_at_preview\" href=\"\'. THEME . $skin.\'/\' . (($dir) ? $dir.\'/\' : \'\') . $file .\'\">\'.gTxt(\'preview\').\'</a>\' : \'\')\n				)\n				.($step == \'smd_at_newfile\'\n					? n.graf(\n						\'<label for=\"new_skin_dir\">\' . gTxt(\'smd_at_folder_name\') . \'</label>\'\n						.n.\'<input type=\"text\" id=\"new_skin_dir\" name=\"new_skin_dir\" value=\"\'.txpspecialchars($dir).\'\" size=\"\'.INPUT_REGULAR.\'\" />\'\n					)\n					: \'\'\n				)\n				.n.graf(\n					\'<textarea id=\"smd_at_edits\" class=\"code\" name=\"smd_at_edits\" cols=\"\'.INPUT_XLARGE.\'\" rows=\"\'.INPUT_REGULAR.\'\">\'.txpspecialchars($contents).\'</textarea>\'\n					)\n				.n.graf(\n					fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\')\n				)\n				.n.eInput($smd_at_event)\n				.n.sInput(\'smd_at_save\')\n				.n.hInput(\'skin\',$skin)\n				.n.hInput(\'file\',$file)\n				.n.hInput(\'dir\',$dir)\n			)\n		, \'\', \'column\').\n		n.tdtl(join(n, $fileList).n.$folderList.n.\'</div>\', \' class=\"column\"\')\n	),\n	endTable().\n	script_js( <<<EOS\n		jQuery(document).ready(function() {\n			jQuery(\'#smd_at_allfiles\').txpMultiEditForm({\n				\'checkbox\' : \'input[name=\"selected_files[]\"][type=checkbox]\',\n				\'row\' : \'.plain-list li, .smd-at-list-name\',\n				\'highlighted\' : \'.plain-list li\'\n			});\n		});\n		jQuery(\'#smd_at_edits\').keyup(function() {\n			jQuery(\'.smd_at_preview\').hide();\n		});\nEOS\n	);\n}\n\n// ------------------------\n// Just clear out the var so the save function treats the content as a new file\nfunction smd_at_newfile() {\n	$_POST[\'file\'] = \'\';\n	$message=\'\';\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_preview() {\n	extract(doSlash(gpsa(array(\'skin\', \'file\', \'dir\', \'type\'))));\n\n	$fp = $skin.DS.(($dir) ? $dir.DS : \'\').$file;\n\n	$path = THEME.$fp;\n	$out = \'\';\n\n	switch ($type) {\n		case \'textile\':\n			@include_once txpath.\'/lib/classTextile.php\';\n\n			if (class_exists(\'Textile\') && file_exists($path)) {\n				$textile = new Textile();\n				$contents = file_get_contents($path);\n				$out = $textile->TextileThis($contents);\n			}\n		break;\n	}\n\n	pagetop(gTxt(\'smd_at_preview\', array(\'{name}\' => $fp)));\n	echo n,\'<div id=\"smd_at_container\" class=\"txp-container txp-view\">\',\n		n,\'<div class=\"text-column\">\' . $out . \'</div>\',\n		n,\'</div>\';\n\n	//TODO: back to smd_at_edit() button?\n}\n\n// ------------------------\n// Do the first 512 bytes contain a disproportionate quantity of non-ascii chars?\n// Hacked from http://stackoverflow.com/questions/3872877/how-to-check-if-uploaded-file-is-binary-file, thanks\nfunction smd_at_is_binary($path) {\n	if (file_exists($path)) {\n		if (!is_file($path)) return 0;\n\n		$fh  = fopen($path, \"r\");\n		$blk = fread($fh, 512);\n		fclose($fh);\n		clearstatcache();\n\n		return (\n			0 or substr_count($blk, \"\\x00\") > 0\n		);\n	}\n	return 0;\n}\n\n// ------------------------\n// Hacked from http://www.binarytides.com/blog/php-best-way-to-check-if-file-is-an-image/, thanks\nfunction smd_at_is_image($path) {\n	// Silence warnings in case file is not readable due to spaces or something\n	$a = @getimagesize($path);\n	$image_type = $a[2];\n\n	if(in_array($image_type , array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG))) {\n		return true;\n	}\n	return false;\n}\n\n// ------------------------\nfunction smd_at_clone() {\n	global $txp_user;\n\n	extract(doSlash(gpsa(array(\'skin\', \'new_skin\'))));\n	$msg=\'\';\n\n	$new_skin = trim($new_skin);\n	$new_skin = rawurlencode($new_skin);\n\n	if (empty($new_skin)) {\n		$msg = array(gTxt(\'smd_at_name_empty\'), E_WARNING);\n	}\n\n	// Check the new name isn\'t already in use\n	$themes = smd_at_read_skins();\n\n	if (in_array($new_skin, $themes)) {\n		$msg = array(gTxt(\'smd_at_skin_exists\', array(\'{name}\' => $new_skin)), E_ERROR);\n	}\n\n	// No errors, so make a new dir and create the PHP file\n	if ($msg==\'\') {\n		$content = \'\';\n		$destdir = THEME.$new_skin.DS;\n		$res = mkdir($destdir);\n		$based = \'\';\n\n		if ($skin) {\n			// Based on an existing theme. No need for any functions other than manifest()\n			$based = \"theme::based_on(\'\".$skin.\"\');\";\n			$extends = $skin.\'_theme\';\n			$srcss = THEME.$skin.DS.\'textpattern.css\';\n			if (file_exists($srcss) && filesize($srcss) > 0) {\n				$import = join(\'\', file($srcss));\n			} else {\n				$import = \'@import url(\"../classic/textpattern.css\");\';\n			}\n			$content = \'{\';\n		} else {\n			// Brand spanking new theme. Clone \'classic\' functions then add new manifest()\n			$extends = \'theme\';\n			$import = \'\';\n			$src = txpath.DS.THEME.\'classic\'.DS.\'classic.php\';\n			$grab = false;\n			$fp = file($src);\n\n			foreach($fp as $line) {\n				$line = rtrim($line);\n				if (strpos($line, \'function manifest()\') !== false) {\n					$grab = false; // unnecessary?\n					break;\n				}\n				if ($grab) {\n					$content .= $line.n;\n				}\n				if (strpos($line, \'class classic_theme extends theme\') !== false) {\n					$grab = true;\n				}\n			}\n		}\n\n		// manifest fields\n		$manuser = get_author_name($txp_user);\n		$mantitle = ucwords($new_skin);\n\n		// Write the new PHP file\n		$buf = <<<EOF\n<?php\n\nif (!defined(\'txpinterface\')) die(\'txpinterface is undefined.\');\n\n{$based}\n\nclass {$new_skin}_theme extends {$extends}\n{$content}\n\n	function manifest()\n	{\n		return array(\n			\'title\'	 		=> \'{$mantitle}\',\n			\'author\' 		=> \'{$manuser}\',\n			\'author_uri\' 	=> \'\',\n			\'version\' 		=> \'1.0\',\n			\'description\' 	=> \'\',\n			\'help\' 			=> \'\',\n		);\n	}\n}\n?>\nEOF;\n\n		$fp = fopen($destdir.$new_skin.\'.php\', \"wb\");\n		fwrite($fp, $buf);\n		fclose($fp);\n\n		// Create the CSS file\n		$fp = fopen($destdir.\'textpattern.css\', \"wb\");\n		fwrite($fp, $import);\n		fclose($fp);\n\n		if ($skin) {\n			$msg = gTxt(\'smd_at_skin_cloned\', array(\'{name}\' => $new_skin));;\n		} else {\n			$msg = gTxt(\'smd_at_skin_created\', array(\'{name}\' => $new_skin));;\n		}\n\n//	$msg = gTxt(\'skin_not_created\', array(\'{name}\' => $new_skin));;\n\n	}\n\n	smd_at_list($msg);\n}\n\n// ------------------------\nfunction smd_at_crush_options($type=\'compress\') {\n	global $plugins;\n\n	$smd_crushers = array();\n\n	if (is_array($plugins) && in_array(\'smd_crunchers\', $plugins)) {\n		$crunchers = smd_crunch_capabilities($type);\n		foreach (array(\'tar\', \'gzip\', \'zip\', \'bzip2\') as $cm) {\n			if (in_array($cm, $crunchers)) {\n				$smd_crushers[$cm] = gTxt(\'smd_at_c_\'.$cm);\n			}\n		}\n	}\n\n	return $smd_crushers;\n}\n\n// ------------------------\nfunction smd_at_export() {\n	extract(doSlash(gpsa(array(\'skin\',\'crush\'))));\n\n	$at_prefs = smd_at_get_prefs();\n\n	$message=\'\';\n	chdir(THEME);\n	$skinfo = smd_at_read_skinfo($skin);\n	$reps = array(\n		\'{theme}\' => $skin,\n		\'{version}\' => $skinfo[\'version\'],\n	);\n	$out = strtr($at_prefs[\'smd_at_filename_format\'], $reps);\n\n	// Check the passed compression format is valid\n	$crushers = smd_at_crush_options(\'compress\');\n	if ($crush==\'\' || $crush==\'undefined\' || !array_key_exists($crush, $crushers)) {\n		$msg = array(gTxt(\'smd_at_unsupported_compressiontype\', array(\'{crush}\' => $crush)), E_WARNING);\n		smd_at_list($message);\n		return;\n	}\n\n	switch($crush) {\n		case \'zip\':\n			$zip = new smd_crunch_zip_file($out.\'.zip\');\n			break;\n		case \'bzip2\':\n			$zip = new smd_crunch_bzip_file($out.\'.tbz2\');\n			break;\n		case \'tar\':\n			$zip = new smd_crunch_tar_file($out.\'.tar\');\n			break;\n		case \'gzip\':\n			$zip = new smd_crunch_gzip_file($out.\'.tgz\');\n 			break;\n	}\n\n	$zip->set_options(array(\'basedir\' => txpath.DS.THEME, \'overwrite\' => 1, \'inmemory\' => 1));\n	$zip->add_files($skin.DS.\'*.*\');\n	$zip->exclude_files($skin.DS.\'*.svn*\');\n	$zip->create_archive();\n	$zip->download_file();\n}\n\n// ------------------------\nfunction smd_at_import() {\n	$name = $_FILES[\'thefile\'][\'name\'];\n	$file = get_uploaded_file($_FILES[\'thefile\'][\'tmp_name\'], txpath.DS.THEME.basename($_FILES[\'thefile\'][\'tmp_name\']));\n	$basedir = txpath.DS.THEME;\n	$uncrushers = smd_at_crush_options(\'decompress\');\n	$message=\'\';\n	$info = explode (\'.\',$name);\n	$lastpart = count($info)-1;\n	$ext = $info[$lastpart];\n\n	if ($name && $file) {\n		if ($ext == \'zip\' && array_key_exists(\'zip\', $uncrushers)) {\n			$zip = new smd_crunch_dUnzip2($file);\n//			$zip->debug = true;\n			$zip->getList();\n			$validArchive = smd_at_check_archive($zip->compressedList, $ext);\n			if ($validArchive) {\n				$zip->unzipAll($basedir, \'\', true, 0755);\n				$zip->close();\n				$message = gTxt(\'smd_at_import_ok\');\n			} else {\n				$message = array(gTxt(\'smd_at_import_failed\'), E_ERROR);\n			}\n		} else if ($ext == \'tgz\' && array_key_exists(\'gzip\', $uncrushers)) {\n			$zip = new smd_crunch_gzip_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else if ($ext == \'tbz2\' && array_key_exists(\'bzip2\', $uncrushers)) {\n			$zip = new smd_crunch_bzip_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else if ($ext == \'tar\') {\n			$zip = new smd_crunch_tar_file(basename($file));\n			$zip->set_options(array(\'basedir\' => $basedir, \'overwrite\' => 1));\n			$zip->extract_files();\n			$message = gTxt(\'smd_at_import_ok\');\n		} else {\n			$message = array(gTxt(\'smd_at_unsupported_compressiontype\', array(\'{crush}\' => $ext)), E_WARNING);\n		}\n\n		// Remove the temporary file\n		unlink($file);\n	} else {\n		$message = array(gTxt(\'smd_at_empty_info\'), E_WARNING);\n	}\n\n	smd_at_list($message);\n}\n\n// Check the directory structure of the archive for various items/attributes.\n// Each time one of the conditions is met, the vcnt is incrememented.\n// Once the whole archive has been read, if the vcnt matches the expectedCounts for\n// that type, the archive is valid.\nfunction smd_at_check_archive($struct, $type=\'zip\') {\n	$expectedCounts = array(\'zip\' => 1, \'tgz\' => 0, \'tbz2\' => 0, \'tar\' => 0);\n	switch ($type) {\n		case \"zip\":\n			$vcnt = 0;\n			foreach ($struct as $entry => $info) {\n				$parts = pathinfo($info[\'file_name\']);\n				if (isset($parts[\'extension\']) && $parts[\'extension\'] == \'php\') {\n					$dirbits = explode(DS, $parts[\'dirname\']);\n					$numdirs = count($dirbits);\n					if (($parts[\'filename\'] == $dirbits[$numdirs-1]) && $numdirs==1) {\n						$vcnt++;\n					}\n				}\n			}\n			break;\n	}\n\n	return ($expectedCounts[$type] == $vcnt) ? true : false;\n}\n\n// ------------------------\nfunction smd_at_sanitize_folder($text) {\n	// Remove control characters and \" * \\ : < > ? |\n	$text = preg_replace(\'/[\\x00-\\x1f\\x22\\x2a\\x3a\\x3c\\x3e\\x3f\\x5c\\x7c\\x7f]+/\', \'\', $text);\n	// Remove duplicate dots and any leading or trailing dots/spaces\n	$text = preg_replace(\'/[.]{2,}/\', \'.\', trim($text, \'. \'));\n	// Remove any leading or trailing slash\n	$text = trim($text, \'/\');\n	return $text;\n}\n\n// ------------------------\nfunction smd_at_rrmdir($dir) {\n	foreach(glob($dir . \'/*\') as $file) {\n		if(is_dir($file))\n			smd_at_rrmdir($file);\n		else\n			unlink($file);\n	}\n	rmdir($dir);\n}\n\n// ------------------------\nfunction smd_at_upload() {\n	global $file_max_upload_size;\n\n	extract(doSlash(gpsa(array(\'id\', \'smd_at_folder\'))));\n\n	$_POST[\'skin\'] = $skin = $id;\n	$folder = smd_at_sanitize_folder($smd_at_folder);\n\n	$filelist = $_FILES[\'smd_at_file\'][\'name\'];\n	$name = $pos = NULL;\n	$message=\'\';\n	$up_count = $fail_count = 0;\n	$uploaded = array();\n\n	$skinfo = smd_at_read_skinfo($skin);\n	$new_skin = $skinfo[\'name\'];\n\n	if ($new_skin) {\n		$newpath = $skinfo[\'path\'] . (($folder) ? $folder.\'/\' : \'\');\n\n		if (!file_exists($newpath)) {\n			mkdir($newpath, 0755, true);\n		}\n\n		foreach ($filelist as $pos => $name) {\n			if ($name == \'\') continue;\n\n			$file = get_uploaded_file($_FILES[\'smd_at_file\'][\'tmp_name\'][$pos]);\n\n			// Skip files that are too big\n			$size = filesize($file);\n			if ($file_max_upload_size < $size) {\n				unlink($file);\n				$fail_count++;\n				continue;\n			}\n\n			if ($name && $file) {\n				if(shift_uploaded_file($file, $newpath.$name)) {\n					chmod($newpath.$name, 0755);\n					$up_count++;\n					$uploaded[] = $name;\n				} else {\n					unlink($file);\n					$fail_count++;\n				}\n			}\n		}\n		$message = gTxt(\'smd_at_upload_report\', array(\'{num_success}\' => $up_count, \'{num_fail}\' => $fail_count, \'{uploaded}\' => join(\', \', $uploaded)));\n	} else {\n		$message = array(gTxt(\'smd_at_empty_info\'), E_WARNING);\n	}\n\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_delete() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	extract(doSlash(gpsa(array(\'skin\'))));\n	$message=\'\';\n\n	if (in_array($skin, $smd_core_themes)) {\n		$message = array(gTxt(\'smd_at_core_theme\', array(\'{skin}\' => $skin)), E_ERROR);\n		smd_at_list($message);\n	} else {\n		$skindir = THEME.$skin;\n		if (!smd_rmdir_recursive($skindir)) {\n			$message = array(gTxt(\'smd_at_delete_failed\'), E_ERROR);\n		} else {\n			$message = gTxt(\'smd_at_skin_deleted\', array(\'{name}\' => $skin));\n		}\n\n		// Handle situations if current skin has been deleted\n		if (!file_exists(txpath.DS.THEME.$skin)) {\n			$at_prefs = smd_at_get_prefs();\n\n			// Reset the global skin in case it\'s been deleted\n			$gbl_skin = $at_prefs[\'smd_at_global_skin\'];\n			$gbl_skin = ($gbl_skin != \'\' && file_exists(txpath.DS.THEME.$gbl_skin)) ? $gbl_skin : \'classic\';\n			set_pref(\'smd_at_global_skin\', $gbl_skin, $smd_at_event, PREF_HIDDEN, \'text_input\');\n			set_pref(\'theme_name\', $gbl_skin);\n\n			$skin = get_pref(\'smd_skin\');\n			if (!file_exists(txpath.DS.THEME.$skin)) {\n				$skin = $gbl_skin;\n			}\n			set_pref(\'smd_skin\', $skin, \'smd_at\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n		}\n		$_GET[\'skin\'] = $skin;\n		$_GET[\'nextstep\'] = \'smd_at_list\';\n		smd_at_switch($message);\n	}\n}\n\n// ------------------------\nfunction smd_at_delete_file($skin, $delfile) {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	$ret = false;\n	$path = THEME.$skin.DS.$delfile;\n	if (file_exists($path) && !in_array($skin, $smd_core_themes)) {\n		$ret = unlink($path);\n	}\n	return $ret;\n}\n\n// ------------------------\nfunction smd_at_delete_folder() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	extract(doSlash(gpsa(array(\'skin\', \'delfolder\'))));\n	$message = \'\';\n\n	$_POST[\'skin\'] = $skin;\n\n	if (file_exists($delfolder) && !in_array($skin, $smd_core_themes)) {\n		smd_at_rrmdir($delfolder);\n		$message = gTxt(\'smd_at_folder_deleted\', array(\'{name}\' => $delfolder));\n	}\n\n	smd_at_edit($message);\n}\n\n// ------------------------\nfunction smd_at_multi_edit() {\n	global $smd_core_themes, $smd_at_event;\n\n	if (!has_privs($smd_at_event)) {\n		smd_at_edit();\n	}\n\n	$message = \'\';\n	$method = ps(\'edit_method\');\n	$files = ps(\'selected_files\');\n	$skin = ps(\'crit\'); // bit of a hack, but multi_edit() has a fixed param set\n	$affected = array();\n	$_POST[\'skin\'] = $skin;\n\n	if ($files and is_array($files)) {\n		if ($method == \'delete\') {\n			foreach ($files as $name) {\n				if (smd_at_delete_file($skin, $name)) {\n					$affected[] = $name;\n				}\n			}\n\n			if ($affected) {\n				$message = gTxt(\'smd_at_files_deleted\', array(\'{list}\' => join(\', \', $affected)));\n			}\n\n			smd_at_edit($message);\n		}\n	} else {\n		smd_at_edit();\n	}\n}\n\n// ------------------------\nfunction smd_at_save() {\n	extract(doSlash(gpsa(array(\'skin\',\'new_skin\',\'new_skin_dir\',\'file\',\'dir\',\'smd_at_edits\'))));\n\n	list($editable, $imagable) = smd_at_valid_types();\n	$message = $extraMsg = \'\';\n	$file = trim($file);\n	$new_skin = trim($new_skin);\n	$new_skin_dir = trim(trim($new_skin_dir), DS);\n	$msglev = 0;\n\n	// New file\n	if (!$file && $new_skin) {\n		$file = $new_skin;\n		$dir = $new_skin_dir;\n		$ext = array_pop(explode (\'.\',$file));\n		if (strtolower($file) != \'readme\' && ($ext == \'\' || !in_array($ext, $editable))) {\n			$message = gTxt(\'smd_at_unsupported_filetype\').gTxt(\'smd_at_unsupported_fudge\');\n			$msglev = E_WARNING;\n			$_POST[\'file\'] = $file;\n			$_POST[\'dir\'] = $dir;\n		}\n	}\n\n	if ($message==\'\') {\n		$fname = sanitizeForFile($file);\n		$new_skin = rawurlencode($new_skin);\n		$_POST[\'new_skin\'] = $new_skin;\n\n		$smd_at_edits = doStrip(str_replace(\'\\r\\n\',\'\n\',$smd_at_edits)); // newline workaround\n\n		if ($fname) {\n			$filepath = THEME.$skin.DS.$dir.DS;\n			if (!file_exists($filepath.$fname)) {\n				if (!file_exists($filepath)) {\n					mkdir($filepath, 0755, true);\n				}\n\n				$ret = touch($filepath.$fname);\n				if ($ret === false) {\n					$message = gTxt(\'smd_at_mkdir_failed\', array(\'{name}\' => $fname));\n				}\n			}\n\n			// Assuming no errors so far...\n			if ($message == \'\') {\n				$fh = fopen($filepath.$fname, \'wb\');\n				fwrite($fh, $smd_at_edits);\n				fclose($fh);\n\n				if ($fname != $new_skin) {\n					// Rename the file\n					$res = rename($filepath.$fname, $filepath.$new_skin);\n\n					// If the renamed file is the theme\'s PHP file, try to rename the main skin dir too\n					$filebits = explode(\'.\', $new_skin);\n					$oldfilebits = explode(\'.\', $fname);\n					$ext = array_pop($filebits);\n					if ($res && $ext == \'php\' && ($oldfilebits[0] == $skin)) {\n						$new_filepath = THEME.$filebits[0].DS;\n						$ren = rename($filepath, $new_filepath);\n						$filepath = ($ren) ? $new_filepath : $filepath;\n						$_POST[\'skin\'] = $filebits[0];\n					}\n					$extraMsg = ($res) ? gTxt(\'smd_at_renamed\', array(\'{name}\' => $new_skin)) : gTxt(\'smd_at_rename_failed\');\n				}\n\n				// Process any css replacements\n				$filebits = explode (\'.\',(($fname==$new_skin)? $fname : $new_skin));\n				$cssfile = $filebits[0];\n				$ext = array_pop($filebits);\n				if ($ext == \'ssc\') {\n					$replacements = array();\n					$num_rep1 = preg_match_all(\'/(\\@[A-Za-z0-9_]+):\\s*(.*);/\', $smd_at_edits, $matches1);\n					$num_rep2 = preg_match_all(\'/(\\@[A-Za-z0-9_]+):\\s*\\{(.*?)\\}/s\', $smd_at_edits, $matches2);\n\n					// Simple replacements\n					foreach($matches1 as $idx => $reparr) {\n						if ($idx == 0) {\n							// Remove the var definitions\n							foreach ($reparr as $rep) {\n								$smd_at_edits = str_replace($rep, \'\',$smd_at_edits);\n							}\n						}\n						if ($idx == 1) {\n							foreach ($reparr as $jdx => $rep) {\n								$replacements[$rep] = $matches1[2][$jdx];\n							}\n						}\n					}\n\n					// Multi-line replacements\n					foreach($matches2 as $idx => $reparr) {\n						if ($idx == 0) {\n							// Remove the var definitions\n							foreach ($reparr as $rep) {\n								$smd_at_edits = str_replace($rep, \'\',$smd_at_edits);\n							}\n						}\n						if ($idx == 1) {\n							foreach ($reparr as $jdx => $rep) {\n								$replacements[$rep.\';\'] = trim($matches2[2][$jdx]);\n							}\n						}\n					}\n\n					// Generate and write the new css file\n					$smd_at_edits = trim(strtr($smd_at_edits, $replacements));\n					$fh = fopen($filepath.$cssfile.\'.css\', \'wb\');\n					fwrite($fh, $smd_at_edits);\n					fclose($fh);\n					if ($fname != $new_skin) {\n						$oldfname = explode (\'.\', $fname);\n						$oldfname = $oldfname[0].\'.css\';\n						$res = @unlink($filepath.$oldfname);\n					}\n				}\n\n				$message = gTxt(\'smd_at_file_saved\', array(\'{name}\' => $fname)).br.$extraMsg;\n			}\n		}\n	}\n	smd_at_edit(array($message, $msglev));\n}\n\n\n// ------------------------\n// PREFS\n// ------------------------\n// Get current pref value or default for each known pref\nfunction smd_at_get_prefs($force=0) {\n	global $prefs;\n\n	$smd_at_prefs = array(\n		\'smd_at_case_sort\' => array(\n			\'html\'     => \'yesnoradio\',\n			\'position\' => 10,\n			\'default\'  => \'1\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_crush\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 20,\n			\'default\'  => \'zip\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_tw\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 30,\n			\'default\'  => \'260\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_th\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 40,\n			\'default\'  => \'150\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_filename_format\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 60,\n			\'default\'  => \'{theme}\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_max_theme_size\' => array(\n			\'html\'     => \'text_input\',\n			\'position\' => 70,\n			\'default\'  => (500 * 1024),\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_layout\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 80,\n			\'default\'  => \'1\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_system\' => array(\n			\'html\'     => \'radioset\',\n			\'position\' => 90,\n			\'default\'  => \'0\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_global_skin\' => array(\n			\'html\'     => \'selectlist\',\n			\'position\' => 100,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_group_list\' => array(\n			\'html\'     => \'list\',\n			\'position\' => 110,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n		\'smd_at_user_list\' => array(\n			\'html\'     => \'list\',\n			\'position\' => 120,\n			\'default\'  => \'\',\n			\'group\'    => \'smd_at_settings\',\n		),\n	);\n\n	$at_prefs = array();\n	foreach ($smd_at_prefs as $key => $prefarr) {\n		$at_prefs[$key] = get_pref($key, \'\', $force);\n		if ($at_prefs[$key] == \'\') {\n			$at_prefs[$key] = $prefarr[\'default\'];\n		}\n	}\n	return $at_prefs;\n}\n\n// ------------------------\nfunction smd_at_options($event, $step, $message=\'\') {\n	smd_at_setup($message);\n}\n\n// ------------------------\nfunction smd_at_setup($message=\'\') {\n	global $smd_at_event, $theme, $smd_core_themes;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs(1);\n\n	pagetop(gTxt(\'smd_at_prefs_title\'),$message);\n\n	$levels = get_groups();\n	unset($levels[1]); // Publishers get special privs apart from the masses\n	$numLevs = count($levels);\n\n	// Split the user skin list\n	$uskins = explode(\',\', $at_prefs[\'smd_at_user_list\']);\n\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$clrBtn = \' [<span id=\"smd_clr\" class=\"smd_fakebtn\">\'.gTxt(\'smd_at_clear\').\'</span>]\';\n	$btnSet = fInput(\'submit\', \'submit\', gTxt(\'smd_at_set\'), \'publish\');\n	$btnSave = graf(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'));\n	$btnList = \'<p class=\"txp-buttons\"><a class=\"navlink\" href=\"?event=\'.$smd_at_event.\'\">\'.gTxt(\'smd_at_all_themes\').\'</a></p>\';\n	$radBtns = array(\n		gTxt(\'smd_at_per_site\'),\n		gTxt(\'smd_at_per_group\'),\n		gTxt(\'smd_at_per_user\'),\n	);\n\n	$skin_list = smd_at_read_skins();\n\n	// selectInput needs both index and value to be the same in this case\n	$skinsel = array();\n	foreach($skin_list as $key1 => $value1) {\n		if (smd_at_exists($value1)) {\n			$skinsel[$value1] = $skin_list[$key1];\n		}\n	}\n	$gbl_skin = ($at_prefs[\'smd_at_global_skin\'] != \'\') ? $at_prefs[\'smd_at_global_skin\'] : $theme->name;\n	$crushers = smd_at_crush_options(\'compress\');\n\n	echo n, \'<div class=\"plugin-column\">\',\n		n, \'<h2>\', strong(gTxt(\'smd_at_prefs_title\')), \'</h2>\',\n		n, $btnList,\n		n, \'<form method=\"post\" action=\"?event=\', $smd_at_event, a, \'step=smd_at_prefs_update\" onsubmit=\"return smd_presub();\">\',\n		n, inputLabel(\'smd_at_case_sort\', yesnoRadio(\'smd_at_case_sort\', $at_prefs[\'smd_at_case_sort\']), \'smd_at_case_sort\');\n\n	if ($crushers) {\n		echo n, inputLabel(\'smd_at_crush\', radioSet($crushers, \'smd_at_crush\', $at_prefs[\'smd_at_crush\']), \'smd_at_crush_type\');\n	}\n\n	echo n, inputLabel(\'smd_at_layout\', radioSet(array(gTxt(\'smd_at_layout_list\'), gTxt(\'smd_at_layout_grid\')), \'smd_at_layout\', $at_prefs[\'smd_at_layout\']), \'smd_at_layout\'),\n		n, inputLabel(\'smd_at_thumbsize\', fInput(\'text\', \'smd_at_tw\', $at_prefs[\'smd_at_tw\'],\'input-xsmall\',\'\',\'\',4).n.gTxt(\'smd_at_times\').n.fInput(\'text\', \'smd_at_th\', $at_prefs[\'smd_at_th\'],\'input-xsmall\',\'\',\'\',4), \'smd_at_thumbsize\'),\n		n, inputLabel(\'smd_at_filename_format\', fInput(\'text\', \'smd_at_filename_format\', $at_prefs[\'smd_at_filename_format\']), \'smd_at_filename_format\'),\n		n, inputLabel(\'smd_at_max_theme_size\', fInput(\'text\', \'smd_at_max_theme_size\', $at_prefs[\'smd_at_max_theme_size\']), \'smd_at_max_theme_size\'),\n		n, inputLabel(\'smd_at_global_skin\', selectInput(\'smd_at_global_skin\', $skinsel, $gbl_skin, 0, 0, \'smd_at_global_skin\'), \'smd_at_global_skin\'),\n		n, inputLabel(\'smd_at_skin_system\', gTxt(\'smd_at_one_theme\').n.radioSet($radBtns, \'smd_at_system\', $at_prefs[\'smd_at_system\']), \'smd_at_skin_system\');\n\n	// Option 1\n	$priv_list = \'<select id=\"smd_at_privs\" name=\"smd_at_privs[]\" class=\"list\" size=\"\'.$numLevs.\'\" multiple=\"multiple\">\';\n	foreach ($levels as $levid => $levname) {\n		$priv_list .= \'<option value=\"\'.$levid.\'\">\'.$levname.\'</option>\';\n	}\n	$priv_list .= \'</select>\';\n\n	// Note the hidden skin group box; jQuery keeps track of any list changes and keeps it updated\n	$sela = \'<span class=\"edit-label\"><label>\'. gTxt(\'smd_at_skin_groups\').\'</label></span>\';\n	$selb = hInput(\'smd_at_group_list\', $at_prefs[\'smd_at_group_list\']);\n	$selb .= \'<select id=\"smd_at_grps\" name=\"smd_at_grps\" class=\"list\" size=\"\'.$numLevs.\'\">\';\n	foreach ($skin_list as $askin) {\n		if (smd_at_exists($askin)) {\n			$selb .= \'<option value=\"\'.$askin.\'\">\'.$askin.\'</option>\';\n		}\n	}\n	$selb .= \'</select>\';\n	echo n, graf($sela.\'<span class=\"edit-value\">\'.$selb.$priv_list.\'</span>\', \' class=\"smd_at_sel1\"\');\n\n	// Option 2\n	$sela = gTxt(\'smd_at_allowed_skins\');\n	$selb = \'<select id=\"smd_at_user_list\" name=\"smd_at_user_list[]\" class=\"list\" size=\"8\" multiple=\"multiple\">\';\n	foreach ($skin_list as $askin) {\n		if (smd_at_exists($askin)) {\n			$selb .= \'<option value=\"\'.$askin.\'\"\'.((in_array($askin, $uskins)) ? \' selected=\"selected\"\' : \'\').\'>\'.$askin.\'</option>\';\n		}\n	}\n	$selb .= \'</select>\'.$clrBtn;\n\n	echo graf($sela.\'<span class=\"edit-value\">\'.$selb.\'</span>\', \' class=\"smd_at_sel2\"\'),\n		n, $btnSave,\n		n, tInput(),\n		n, \'</form>\',\n		n, \'</div>\';\n\n	echo script_js(<<<EOJS\n// Handle show/hide of pref widgets based on radio selection\nfunction smd_at_prefswap(selValue) {\n	for (idx=0; idx < 3; idx++) {\n		if (idx==selValue) {\n			jQuery(\".smd_at_sel\"+idx).show();\n		} else {\n			jQuery(\".smd_at_sel\"+idx).hide();\n		}\n	}\n}\n\njQuery(function() {\n	jQuery(\"input[name=\'smd_at_system\']\").change(function() {\n		smd_at_prefswap(this.value);\n	});\n\n	jQuery(\'#smd_clr\').click(function() {\n		jQuery(\'#smd_at_user_list option\').prop(\"selected\", \"\");\n	});\n\n	jQuery(\"#smd_at_grps\").change(function() {\n		selskin = jQuery(this).val();\n\n		// Clear the old privs list and grab the current list from the hidden field\n		jQuery(\"#smd_at_privs option\").prop(\"selected\",false);\n		var grplist = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		var grps = grplist.split(\',\');\n		var cnt = grps.length;\n		for (var idx = 0; idx < cnt; idx++) {\n			privs = grps[idx].split(\':\');\n			skinid = privs.shift();\n			if (skinid == selskin) {\n				numPrivs = privs.length;\n				for (jdx = 0; jdx < numPrivs; jdx++) {\n					jQuery(\"#smd_at_privs option[value=\'\"+privs[jdx]+\"\']\").prop(\"selected\", true);\n				}\n			}\n		}\n	});\n\n	jQuery(\"#smd_at_privs\").change(function() {\n		selskin = jQuery(\"#smd_at_grps option:selected\").val();\n		var out = [];\n		var privs = [];\n		privs.push(selskin);\n		jQuery(\"#smd_at_privs option:selected\").each(function() {\n			privs.push(jQuery(this).val());\n		});\n		if (privs.length == 1) {\n			privs.pop();\n		} else {\n			out.push(privs.join(\":\"));\n		}\n		var hidlist = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		var prevlist = hidlist.split(\',\');\n		var prevcnt = prevlist.length;\n\n		jQuery(\"#smd_at_grps option\").each(function() {\n			currskin = jQuery(this).val();\n			if (currskin != selskin) {\n				for (var idx = 0; idx < prevcnt; idx++) {\n					prevprivs = prevlist[idx].split(\':\');\n					skinid = prevprivs.shift();\n					// Generate the priv list for the currently selected skin, removing any duplicate privs set elsewhere\n					if (skinid == currskin) {\n						tmpout = [];\n						tmpout.push(skinid);\n						for (thispriv in prevprivs) {\n							if (jQuery.inArray(prevprivs[thispriv], privs) < 0) {\n								tmpout.push(prevprivs[thispriv]);\n							}\n						}\n						if (tmpout.length > 1) {\n							out.push(tmpout.join(\":\"));\n						}\n					}\n				}\n			}\n		});\n		jQuery(\"input[name=\'smd_at_group_list\']\").val(out);\n	});\n\n	// Display a tooltip showing the currently-assigned skin when hovering over a privilege level\n	jQuery(\"#smd_at_privs option\").hover(function() {\n		hovitem = jQuery(this).val();\n		grps = jQuery(\"input[name=\'smd_at_group_list\']\").val();\n		items = grps.split(\",\");\n		for (idx = 0; idx < items.length; idx++) {\n			skinz = items[idx].split(\":\");\n			for (jdx = 1; jdx < skinz.length; jdx++) {\n				if (skinz[jdx] == hovitem) {\n					jQuery(this).attr(\"title\", skinz[0]);\n					break;\n				}\n			}\n		}\n	},\n	function() {\n		jQuery(this).attr(\"title\", \"\");\n	});\n	smd_at_prefswap(jQuery(\"input[name=\'smd_at_system\']:checked\").val());\n	jQuery(\"#smd_at_privs\").change();\n});\nEOJS\n	);\n}\n\n// ------------------------\n// Split a multi-char preference value into an array of keys/values.\n// Note: value is passed in and not read directly from the prefs array in this function - intentionally\nfunction smd_at_pref_explode($val) {\n	$order = array_values(array(gTxt(\'smd_at_c_gzip\'),gTxt(\'smd_at_c_bzip2\'),gTxt(\'smd_at_c_zip\')));\n	$onoff = array_values(preg_split(\'//\', $val, -1, PREG_SPLIT_NO_EMPTY));\n	$out = array();\n	foreach($order as $key1 => $value1) {\n		$out[(string)$value1] = $onoff[$key1];\n	}\n	return $out;\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_install($showpane=\'1\') {\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	foreach ($at_prefs as $pref => $dflt) {\n		if (!fetch(\'name\',\'txp_prefs\',\'name\',$pref)) {\n			$id = safe_insert(\'txp_prefs\',\'prefs_id=1, name=\'.doQuote(doSlash($pref)).\', val=\'.doQuote(doSlash($dflt)).\', event=\"smd_at\", type=\'.PREF_HIDDEN);\n		}\n	}\n	if ($showpane) {\n		$message = gTxt(\'smd_at_prefs_installed\');\n		smd_at_setup($message);\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_remove($showpane=\'1\') {\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	foreach ($at_prefs as $pref => $dflt) {\n		if (fetch(\'name\',\'txp_prefs\',\'name\',$pref)) {\n			$id = safe_delete(\'txp_prefs\',\'name=\'.doQuote(doSlash($pref)));\n		}\n	}\n	if ($showpane) {\n		$message = gTxt(\'smd_at_prefs_deleted\');\n		smd_at_setup($message);\n	}\n}\n\n// -------------------------------------------------------------\nfunction smd_at_prefs_update() {\n	global $smd_plugin_prefs, $smd_at_event;\n\n	$at_prefs = smd_at_get_prefs();\n\n	$message = \'\';\n	$post = array();\n\n	foreach ($at_prefs as $pref => $prefobj) {\n		$post[$pref] = ps($pref);\n	}\n\n	// Handle list options separately and only update them if they\'re set\n	// (they\'ve already been set in the foreach above, so need unsetting)\n	if (ps(\'smd_at_user_list\')) {\n		$post[\'smd_at_user_list\'] = join(\',\', ps(\'smd_at_user_list\'));\n	} else {\n		$post[\'smd_at_user_list\'] = \'\';\n	}\n	if (ps(\'smd_at_group_list\')) {\n		$post[\'smd_at_group_list\'] = ps(\'smd_at_group_list\');\n	} else {\n		unset($post[\'smd_at_group_list\']);\n	}\n	if (ps(\'smd_at_crush\')) {\n		$post[\'smd_at_crush\'] = ps(\'smd_at_crush\');\n	} else {\n		unset($post[\'smd_at_crush\']);\n	}\n\n	// Set the system-wide theme to the global theme\n	if ($post[\'smd_at_global_skin\']) {\n		set_pref(\'theme_name\', $post[\'smd_at_global_skin\']);\n	}\n\n	foreach ($at_prefs as $pref => $dflt) {\n		if (isset($post[$pref])) {\n			set_pref($pref, $post[$pref], $smd_at_event, PREF_HIDDEN);\n		}\n	}\n	$message = gTxt(\'preferences_saved\');\n	smd_at_setup($message);\n}\n\n// ------------------------\n// UTILITY FUNCTIONS\n// ------------------------\nfunction smd_at_get_privs($user=\'\') {\n	global $txp_user;\n	$user = ($user) ? $user : $txp_user;\n	$privs = safe_field(\"privs\", \"txp_users\", \"name=\'\".doSlash($user).\"\'\");\n	return $privs;\n}\n\n// ------------------------\nfunction smd_at_exists($name) {\n	$instance = theme::factory($name);\n	return $instance;\n}\n\n// If per-user skin support is OFF, the global skin wins.\n// One exception: if the user is an admin, allow the current skin to prevail so they may switch skins with impunity\n// and not affect the global skin\nfunction smd_at_per() {\n	global $smd_at_privs, $theme, $txp_user;\n\n	$at_prefs = smd_at_get_prefs();\n	$skinSys = $at_prefs[\'smd_at_system\'];\n	$privs = smd_at_get_privs();\n	$gbl_skin = ($at_prefs[\'smd_at_global_skin\'] != \'\') ? $at_prefs[\'smd_at_global_skin\'] : \'\';\n	$gbl_skin = (smd_at_exists($gbl_skin)) ? $gbl_skin : \'classic\';\n\n	switch ($skinSys) {\n		case 0:\n			// Global forced skin (except admins)\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				return $gbl_skin;\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n		case 1:\n			// Group-level forced skins\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				$forceSkins = ($at_prefs[\'smd_at_group_list\'] != \'\') ? $at_prefs[\'smd_at_group_list\'] : (($gbl_skin) ? $gbl_skin : $theme->name);\n\n				// Split the group skin list\n				$gskins = array();\n				$gopts = explode(\',\', $forceSkins);\n				foreach ($gopts as $grpdef) {\n					if ($grpdef == \'\') continue;\n					$privgrp = explode(\":\", $grpdef);\n					$skinid = array_shift($privgrp);\n					if (in_array($privs, $privgrp)) {\n						return (smd_at_exists($skinid)) ? $skinid : \'classic\';;\n					}\n				}\n				return ($gbl_skin) ? $gbl_skin : $theme->name;\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n		case 2:\n			// Per-user skin. If user is non-admin, check the skin is in the allowed list\n			if (!in_array($privs, explode(\',\', $smd_at_privs))) {\n				$validSkins = ($at_prefs[\'smd_at_user_list\']) ? $at_prefs[\'smd_at_user_list\'] : (($gbl_skin) ? $gbl_skin : $theme->name);\n				$validSkins = do_list($validSkins);\n				$the_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (in_array($the_skin, $validSkins) && smd_at_exists($the_skin)) ? $the_skin : \'\';\n			} else {\n				// Admins\n				$adm_skin = get_pref(\'smd_skin\', (($gbl_skin) ? $gbl_skin : $theme->name));\n				return (smd_at_exists($adm_skin)) ? $adm_skin : \'classic\';\n			}\n			break;\n	}\n}\n\n// ------------------------\n// Read and return the manifest, if it exists and add a few other niceties to the array for later\nfunction smd_at_read_skinfo($skin) {\n	global $smd_core_themes;\n\n	$this_theme = theme::factory($skin);\n	$skinfo = array();\n	if ($this_theme) {\n		$skinfo = $this_theme->manifest();\n		$skinfo[\'dname\'] = empty($skinfo[\'title\']) ? ucwords($this_theme->name) : $skinfo[\'title\'];\n		$skinfo[\'name\'] = $this_theme->name;\n		$skinfo[\'path\'] = $this_theme->url;\n		$skinfo[\'phpfile\'] = $this_theme->path($skin);\n		$skinfo[\'based_on\'] = \'\';\n		$contents = file_get_contents($skinfo[\'phpfile\']);\n		if (($pos = strpos($contents, \'theme::based_on\')) !== false) {\n			$begpos = strpos($contents, \"\'\", $pos)+1;\n			$endpos = strpos($contents, \"\'\", $begpos);\n			$base = substr($contents, $begpos, $endpos-$begpos);\n			$skinfo[\'based_on\'] = (in_array($base, $smd_core_themes)) ? \'\' : $base;\n		}\n		unset($this_theme);\n	}\n	return $skinfo;\n}\n\n// ------------------------\n// Grab the thumbnail filename if it exists, optionally return a formatted img tag\nfunction smd_at_get_thumb($skin, $img=0) {\n	$at_prefs = smd_at_get_prefs();\n	$tw = $at_prefs[\'smd_at_tw\'];\n	$th = $at_prefs[\'smd_at_th\'];\n	$use_thumb = ($tw == 0 || $th == 0) ? false : true;\n	$dflt_thumb = \'data:image/png;base64, \';\n\n	// An array means the manifest has already been read and is being passed into this function\n	if (!is_array($skin)) {\n		$skin = smd_at_read_skinfo($skin);\n	}\n	if ($skin) {\n		$skindir = $skin[\'path\'];\n		$thumbname = glob($skindir.\'screenshot.*\');\n		return ($img && $use_thumb) ? \'<img class=\"content-image\" src=\"\'.(($thumbname && file_exists($thumbname[0])) ? $thumbname[0] : $dflt_thumb).\'\" alt=\"\'.((isset($skin[\'name\'])) ? $skin[\'name\'] : \'\').\' thumbnail\" width=\"\'.$tw.\'\" height=\"\'.$th.\'\" />\' : \'\';\n	} else {\n		return \'\';\n	}\n}\n\n// ------------------------\nfunction smd_at_read_skins() {\n	$skin_list = theme::names();\n	$skin_list = smd_at_sort($skin_list);\n	return $skin_list;\n}\n\n// ------------------------\n// Sort the skins, keeping core themes at the top\nfunction smd_at_sort($skin_list) {\n	global $prefs, $smd_core_themes;\n\n	$caseSense = isset($prefs[\'smd_at_case_sort\']) ? $prefs[\'smd_at_case_sort\'] : 1;\n\n	if($caseSense) {\n		natsort($skin_list);\n	} else {\n		natcasesort($skin_list);\n	}\n	$dflt_found = array();\n	foreach ($skin_list as $skin_idx => $skin_name) {\n		if (in_array($skin_name, $smd_core_themes)) {\n			unset($skin_list[$skin_idx]);\n			$dflt_found[] = $skin_name;\n		}\n	}\n\n	$dflts = array_reverse($dflt_found); // So they go in alphabetical order\n	foreach ($dflts as $coretheme) {\n		array_unshift($skin_list, $coretheme);\n	}\n\n	return $skin_list;\n}\n\n// ------------------------\nfunction smd_at_listdir($start_dir=\'.\') {\n	$files = array();\n\n	if (is_dir($start_dir)) {\n		$fh = opendir($start_dir);\n		while (($file = readdir($fh)) !== false) {\n			// loop through the files, skipping . and .., and recursing if necessary\n			if (strcmp($file, \'.\')==0 || strcmp($file, \'..\')==0) continue;\n			$filepath = $start_dir . \'/\' . $file;\n			if ( is_dir($filepath) ) {\n				$files = array_merge($files, smd_at_listdir($filepath));\n			} else {\n				array_push($files, $filepath);\n			}\n		}\n		closedir($fh);\n	} else {\n\n		// false if the function was called with an invalid non-directory argument\n		$files = false;\n	}\n\n	return $files;\n}\n\n// ------------------------\n// Recursive rmdir() courtesy of the php manual user comments\nfunction smd_rmdir_recursive($filepath) {\n	if (is_dir($filepath) && !is_link($filepath)) {\n		if ($dh = opendir($filepath)) {\n			while (($sf = readdir($dh)) !== false) {\n				if ($sf == \'.\' || $sf == \'..\') continue;\n				if (!smd_rmdir_recursive($filepath.\'/\'.$sf)) {\n					break; // Doh, can\'t delete\n				}\n			}\n			closedir($dh);\n		}\n		return rmdir($filepath);\n	}\n	return unlink($filepath);\n}\n\n// ------------------------\n// From PHP manual comments. Thanks puremango\nfunction smd_at_is_dir($dir) {\n	// bypasses open_basedir restrictions of is_dir and fileperms\n	$tmp_cmd = `ls -dl $dir`;\n	$dir_flag = $tmp_cmd[0];\n	if($dir_flag!=\"d\") {\n		// not d; use next char (first char might be \'s\' and is still directory)\n		$dir_flag = $tmp_cmd[1];\n	}\n	return ($dir_flag==\"d\");\n}\n\n// ------------------------\nfunction smd_at_change_pageby() {\n	setcookie(\'smd_at_pageby\', gps(\'qty\'));\n	smd_at_list();\n}\n\n// ------------------------\nfunction smd_at_switch($message=\'\') {\n	global $smd_at_privs;\n\n	extract(doSlash(gpsa(array(\'skin\', \'event\', \'nextstep\'))));\n	$url = \"?event=\".$event.\"&step=\".$nextstep;\n\n	if ($skin && smd_at_exists($skin)) {\n		set_pref(\'smd_skin\', $skin, $event, PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n		$url .= \'&message=\' . (($message) ? $message : gTxt(\'smd_at_skin_switched\', array(\'{skin}\' => $skin)));\n	} else {\n		$url .= \'&message=\' . $message;\n	}\n\n	// Since the headers have been sent, double-declutch in order to show the skin this first time\n	echo <<<EOS\n<script type=\"text/javascript\">\nwindow.location.href=\"{$url}\";\n</script>\n<noscript>\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\n</noscript>\nEOS;\n	exit;\n}\n\n// All-user skin chooser\nfunction smd_admat($event, $step) {\n	if(!$step or !in_array($step, array(\n			\'smd_at_switch\',\n		))) {\n		smd_at_chooser(\'\');\n	} else $step();\n}\n\n// ------------------------\nfunction smd_at_chooser($message=\'\') {\n	global $prefs, $smd_at_adm_event, $theme;\n\n	$smd_at_styles = smd_at_get_style_rules();\n	$at_prefs = smd_at_get_prefs();\n	$layout = $at_prefs[\'smd_at_layout\'];\n	$tw = $at_prefs[\'smd_at_tw\'] + 10;\n	$th = $at_prefs[\'smd_at_th\'] + 80;\n\n	$message = ($message) ? $message : gps(\'message\');\n	pagetop(gTxt(\'smd_at_skinner\'),$message);\n\n	$curr_skin = $theme->name;\n\n	$allowed = \'\';\n	if (isset($prefs[\'smd_at_user_list\'])) {\n		$allowed = explode(\",\",$prefs[\'smd_at_user_list\']);\n		foreach ($allowed as $idx => $valid) {\n			if ($valid == \'\') {\n				unset($allowed[$idx]);\n			}\n		}\n	}\n	$allowed = smd_at_sort($allowed);\n	$hdrow = hed(gTxt(\'smd_at_avail_title\'), 2);\n\n	echo \'<div class=\"txp-container\">\';\n\n	if ($layout==0) {\n		echo \'<div class=\"txp-listtables\">\',\n			startTable(\'\', \'\', \'txp-list\'),\n			tr(\n				tda($hdrow, \' colspan=\"3\"\')\n			),\n			assHead(\n				gTxt(\'smd_at_skin_gbl\'),\n				\'author\',\n				\'description\'\n			);\n	} else {\n		echo $hdrow, \'<div class=\"txp-grid\">\';\n	}\n	foreach ($allowed as $skin_name) {\n		$skinfo = smd_at_read_skinfo($skin_name);\n		if ($skinfo) {\n			$thumbnail = smd_at_get_thumb($skinfo, 1);\n			$switch_link = \'?event=\'.$smd_at_adm_event.a.\'step=smd_at_switch\'.a.\'nextstep=smd_admat\'.a.\'skin=\'.$skin_name;\n			$thumblock = ($layout==0)\n						? \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.$skinfo[\'dname\'].(($thumbnail) ? br.$thumbnail : \'\').\'</a>\'\n						: \'<a href=\"\'.$switch_link.\'\" title=\"\'.gTxt(\'smd_at_apply_skin\').\'\">\'.(($thumbnail) ? $thumbnail : gTxt(\'smd_at_apply_skin\')).\'</a>\';\n			$authblock = (strpos($skinfo[\'author_uri\'], \"http://\") === 0) ? \'<a href=\"\'.$skinfo[\'author_uri\'].\'\">\'.$skinfo[\'author\'].\'</a>\' : $skinfo[\'author\'];\n\n			if ($layout==0) {\n				echo tr(\n					n.td($thumblock)\n					.n.td($authblock)\n					.n.td($skinfo[\'description\'])\n					, (($skin_name == $curr_skin) ? \' class=\"highlight\"\' : \'\')\n				);\n			} else {\n				echo n, \'<div class=\"txp-grid-cell\'.(($skin_name == $curr_skin) ? \' highlight\' : \'\').\'\">\',\n						n, hed($skinfo[\'dname\'], 3),\n						n, \'<p>\', $thumblock, \'</p>\',\n						n, \'<p>\', n, gTxt(\'smd_at_by\'), n, $authblock.\'</p>\',\n					n, \'</div>\';\n			}\n		}\n	}\n	if ($layout==0) {\n		echo n, endTable(),\n			n, \'</div>\';\n	} else {\n		echo n, \'</div>\';\n	}\n	echo n, \'</div>\';\n}\n\n// -------------------------------------------------------------\nfunction smd_at_save_pane_state() {\n	list($p1, $p2) = smd_at_valid_types();\n\n	$panes = array_merge($p1, $p2, array(\'folders\', \'image_files\', \'other\'));\n	$pane = gps(\'pane\');\n\n	if (in_array($pane, $panes))\n	{\n		set_pref(\"smd_at_pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_at\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n		send_xml_response();\n	} else {\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n	}\n}', '06a0a9d56c1914d43a2dfacb32d77457', '#@smd_at\nsmd_at_actions => Actions\nsmd_at_allowed_skins => Allowed themes\nsmd_at_all_themes => All themes\nsmd_at_apply_skin => Apply this theme\nsmd_at_avail_title => Available themes\nsmd_at_based_on => (based on {skin})\nsmd_at_by => by\nsmd_at_case_sort => Case-sensitive theme list\nsmd_at_clear => none\nsmd_at_clone => Base\nsmd_at_confirm => Go\nsmd_at_core_theme => Cannot delete {skin}: core theme\nsmd_at_core_theme_file => Cannot delete files from {skin}: core theme\nsmd_at_crush_format => Format\nsmd_at_crush_type => Export compression type\nsmd_at_css => Stylesheet\nsmd_at_c_bzip2 => BZip\nsmd_at_c_gzip => GZip\nsmd_at_c_tar => Tar\nsmd_at_c_zip => Zip\nsmd_at_delete_confirm => Really delete theme {skin}?\nsmd_at_delete_failed => Delete failed. Try removing it manually\nsmd_at_edit_lbl => Edit {theme} theme\nsmd_at_empty_info => You should choose a file first\nsmd_at_export => Export\nsmd_at_filename_format => Filename format\nsmd_at_files_deleted => Deleted: {list}. Note that empty folders may need removing manually\nsmd_at_file_name => File name\nsmd_at_file_not_deleted => File {name} <strong>not</strong> deleted\nsmd_at_file_saved => File {name} saved\nsmd_at_find_theme => Browse themes\nsmd_at_folder => ... to folder\nsmd_at_folders => Folder\nsmd_at_folder_deleted => Folder {name} deleted\nsmd_at_folder_name => Folder\nsmd_at_global_skin => Default theme\nsmd_at_help_link => <a href=\"{link}\">Help</a>\nsmd_at_image_files => Image\nsmd_at_import_failed => Installation failed :-( Check the archive structure\nsmd_at_import_ok => Installation successful.\nsmd_at_install_skin => Install theme\nsmd_at_is_global => (default)\nsmd_at_js => Javascript\nsmd_at_layout => Layout method\nsmd_at_layout_grid => Grid\nsmd_at_layout_list => List\nsmd_at_list_title => Installed themes\nsmd_at_manage_lbl => Theme manager\nsmd_at_max_theme_size => Max theme file size (bytes)\nsmd_at_mkdir_failed => Cannot create directory for file {name}. Try manually creating it.\nsmd_at_name_empty => You should give your theme a name\nsmd_at_new => Create new theme\nsmd_at_new_cloneskin => Name of copy\nsmd_at_new_file => New file\nsmd_at_new_skin => Theme name\nsmd_at_one_theme => One theme\nsmd_at_other => Other files\nsmd_at_other_files => Others\nsmd_at_per_group => per priv level\nsmd_at_per_site => for all\nsmd_at_per_user => per user\nsmd_at_php => PHP\nsmd_at_prefs_deleted => Preferences deleted\nsmd_at_prefs_installed => Preferences installed\nsmd_at_prefs_title => Admin theme preferences\nsmd_at_preview => Preview of {name}\nsmd_at_preview_image => View image\nsmd_at_renamed => Renamed to {name}\nsmd_at_rename_failed => Rename failed\nsmd_at_scss => SCSS\nsmd_at_set => Set\nsmd_at_setup => Preferences\nsmd_at_skinner => Theme\nsmd_at_skin_cloned => Theme extended as {name}\nsmd_at_skin_created => Theme {name} created\nsmd_at_skin_deleted => Theme {name} deleted\nsmd_at_skin_exists => Theme {name} already exists\nsmd_at_skin_files => Theme files\nsmd_at_skin_gbl => Theme\nsmd_at_skin_groups => Group themes\nsmd_at_skin_switched => Theme switched to {skin}\nsmd_at_skin_system => Theme system\nsmd_at_ssc => Styleplate\nsmd_at_supported_import => Supported import types: {types}\nsmd_at_tab_name => Admin themes\nsmd_at_textile => Textile\nsmd_at_thumbsize => Thumb dimensions\nsmd_at_times => x\nsmd_at_txt => Text\nsmd_at_unsupported_compressiontype => {crush} is not a supported compression format in this installation\nsmd_at_unsupported_filetype => File is not of a supported type\nsmd_at_unsupported_fudge => : hit Save again to store anyway\nsmd_at_upload_report => Uploaded {num_success} files(s) {uploaded}. Failed: {num_fail}\nsmd_at_version => v', '', 4, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_cache', 0, 'Oleg Loukianov', 'www.iut-fbleau.fr/projet/etc/', '0.2.8', 'Events-driven cache', '<h1>etc_cache</h1>\n\n<p><a href=\"https://github.com/etc-plugins/etc_cache/releases\">Download</a> | <a href=\"https://packagist.org/packages/etc-plugins/etc_cache\">Packagist</a></p>\n\n<p>This Textpattern plugin provides an events-driven cache solution for Textpattern <span class=\"caps\">CMS</span>.</p>\n\n<p>Textpattern is fast, but when you have thousands of articles, processing the whole list (say, for creating a sitemap) can become time consuming. It&#8217;s a good idea (unless you publish an article every minute) to cache the processed result. Naturally, the cached block must be updated when content/an article is added/deleted. Most caching plugins trigger this update when the corresponding page is visited after the site update &#8211; this has, however, two inconveniences:</p>\n\n<ol>\n	<li>The first visitor has to wait while the expired block is processed and cached again</li>\n	<li>Every site modification, even irrelevant to the cached content, yields the cache update</li>\n</ol>\n\n<p>That’s what etc_cache is aimed to solve.</p>\n\n<h2>Installing</h2>\n\n<p>Using <a href=\"https://getcomposer.org\">Composer</a>:</p>\n\n<pre><code>$ composer require etc-plugins/etc_cache:*</code></pre>\n\n<p>Or download the latest version of the plugin from <a href=\"https://github.com/etc-plugins/etc_cache/releases\">the GitHub project page</a>, paste the code into the Textpattern Plugins administration panel, install and enable the plugin. Visit the <a href=\"https://forum.textpattern.io/viewtopic.php?id=47702\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern 4.6.0 or newer.</li>\n</ul>\n\n<h2>Tags</h2>\n\n<p>The basic usage is:</p>\n\n<pre><code>&lt;txp:etc_cache id=&quot;heavycode&quot;&gt;\n    ...heavy...\n&lt;/txp:etc_cache&gt;</code></pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>id=&quot;id name&quot;</code><br />\nA unique identifier name for this cached item.</li>\n	<li><code>reset=&quot;value&quot;</code><br />\nSee reset information below.</li>\n	<li><code>time=&quot;value&quot;</code><br />\nSee time information below.</li>\n</ul>\n\n<p>The code will be processed and cached until the site is updated. On site update, the plugin (if configured so) will ping the <span class=\"caps\">URL</span> containing this block, triggering the cache refresh. Hence, the cache will always stay up to date, without penalizing site visitors.</p>\n\n<p>To configure automatic cache updates, visit Extensions region Cache administration panel and edit reset field of each block. The possible values are:</p>\n\n<ul>\n	<li>(empty): update client-side only when expired, regardless the site updates</li>\n	<li><code>1</code>: (default) update client-side if expired or the site was updated</li>\n	<li>a list of events like <code>article_posted, article_saved</code> or <code>SQL LIKE</code> pattern like <code>article%</code>: update server-side when a matching event is fired.</li>\n</ul>\n\n<p>The value <code>%</code> thus means &#8216;auto-update on each site update&#8217;, but will act as <code>1</code> client-side too.</p>\n\n<p>You can be more specific with cache reset criteria. Say, if you need a block to be reset only if the article 3 is updated, set:</p>\n\n<pre><code>reset: article_saved\nfilter: {&quot;article_saved&quot;:{&quot;ID&quot;:3}}</code></pre>\n\n<p>You can also pass a <code>reset</code> attribute directly to etc_cache:</p>\n\n<pre><code>&lt;txp:etc_cache id=&quot;archive&quot; reset=&quot;article%&quot;&gt;\n    ...heavy code building an articles archive...\n&lt;/txp:etc_cache&gt;</code></pre>\n\n<p>If needed, one can pass a <code>time</code> attribute to etc_cache:</p>\n\n<pre><code>&lt;txp:etc_cache id=&quot;dailycode&quot; time=&quot;+1 day&quot;&gt;\n    ...daily code...\n&lt;/txp:etc_cache&gt;</code></pre>\n\n<p>A positive (relative) value of time will indicate that the cache (even a fresh one) must be reset on site update.</p>\n\n<p>An absolute value like <code>time=&#39;&lt;txp:modified format=&quot;%F %T&quot; gmt=&quot;1&quot; /&gt; +1 month&#39;</code> will mean &#8216;cache it if not modified since one month&#8217;.</p>\n\n<p>A negative value will not observe site updates, for example (<code>-900</code> seconds is equiavlent to 15 minutes):</p>\n\n<p><txp:etc_cache id=\"api-feed\" time=\"-900\">\n    &#8230;feed code&#8230;<br />\n</txp:etc_cache></p>\n\n<h2>Authors/credits</h2>\n\n<p>Written by <a href=\"http://www.iut-fbleau.fr/projet/etc/\">Oleg Loukianov</a>. Many thanks to <a href=\"https://github.com/etc-plugins/etc_cache/graphs/contributors\">all additional contributors</a>.</p>', 'Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_cache\');\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'etc_cache_install\', \'plugin_lifecycle.etc_cache\');\n    new etc_Cache();\n} elseif (serverSet(\'HTTP_USER_AGENT\') == \'etc_cache\') {\n    global $nolog;\n    $nolog = true;\n}\n\n// -------------------------------------------------------------\n\nfunction etc_cache($atts, $thing = null)\n{\n    global $prefs, $pretext;\n    static $lastmod = null, $times = array();\n\n    extract(lAtts(array(\n        \'id\'    => !empty($atts[\'form\']) ? $atts[\'form\'] : null,\n        \'form\'  => \'\',\n        \'reset\' => null,\n        \'time\'  => true\n    ), $atts));\n\n    if (empty($id)) {\n        trigger_error(gTxt(\'form_not_specified\'));\n\n        return;\n    }\n\n    $id = doSlash($id);\n    $now = time();\n\n    if (!isset($lastmod)) {\n        $lastmod = strtotime($prefs[\'lastmod\']);\n    }\n\n    if (empty($time)) {\n        $update = 0;\n    } elseif ($time === true) {\n        $update = $lastmod;\n    } elseif (!isset($times[$time = (string)$time])) {\n        $update = is_numeric($time) ? $now + (float)$time : (int)strtotime($time, $now);\n\n        if ($update > $now) {\n            if (!is_numeric($time)) {\n                $date = date_parse($time);\n\n                foreach (array(\'year\', \'month\', \'day\', \'hour\', \'minute\', \'second\', \'fraction\') as $key) {\n                    if ($date[$key] !== false) {\n                        $update = -1;\n                        break;\n                    }\n                }\n            }\n        }\n\n        $times[$time] = $update;\n    } else {\n        $update = $times[$time];\n    }\n\n    $cached = $update >= 0 ? safe_row(\'reset, UNIX_TIMESTAMP(time) AS time, url, text\', \'etc_cache\', \"id = \'$id\' AND time IS NOT NULL\") : false;\n    $onlastmod = $cached && ($time === true || $update > $now) && ($cached[\'reset\'] == \'%\' || $cached[\'reset\'] == \'1\');\n\n    if ($update > $now) {\n        $update = $onlastmod ? max(2*$now - $update, $lastmod) : 2*$now - $update;\n//    } elseif ($time !== true) {\n//        if (!isset($reset) && !isset($cached[\'reset\'])) $reset = \'\';\n    } elseif ($cached && !$onlastmod && $time === true) {\n        $update = 0;\n    }\n\n    $parse = !$cached || $update < 0 || $cached[\'time\'] < $update;\n\n    if ($parse) {\n        $out = $form ? parse_form($form, $thing) : parse($thing);\n\n        if ($update >= 0) {\n            $renew = ($reset || !isset($cached[\'url\']) ? \"url=\'\".doSlash($pretext[\'req\']).\"\',\" : \'\')\n                .(isset($reset) && $reset !== true ? \"reset=\'\".doSlash($reset).\"\',\" : \'\');\n\n            safe_upsert(\'etc_cache\', \"time = NOW(), $renew text = \'\".doSlash($out).\"\'\", \"id = \'$id\'\");\n        }\n    } else {\n        $out = $cached[\'text\'];\n    }\n\n    return $out;\n}\n\n/********** Admin class ***********/\n\nclass etc_Cache\n{\npublic function __construct()\n{\n	add_privs(\'etc_cache\', \'1,2\');\n	register_tab(\'extensions\', \'etc_cache\', gTxt(\'etc_cache_tab\'));\n	register_callback(array($this, \'update\'), \'site.update\');\n	register_callback(array($this, \'tab\'), \'etc_cache\');\n}\n\npublic function update($event, $step = \'\', $rs = null)\n{\n    $urls = $ids = array();\n    $safe_step = $step ? doSlash($step) : \'\';\n    $filter = \"\'$safe_step\' LIKE reset\".($step ? \" OR FIND_IN_SET(\'$safe_step\', reset)\" : \'\');\n\n    foreach (safe_rows(\'id, url, filter\', \'etc_cache\', \"url > \'\' AND reset > \'\' AND ($filter)\") as $cache) {\n        if ($step && !empty($cache[\'filter\'])) {\n            $items = json_decode($cache[\'filter\'], true);\n\n            if (isset($items[$step]) && is_array($items[$step])) {\n                foreach ($items[$step] as $field => $value) {\n                    if (isset($rs[$field]) && !in_array($rs[$field], (array)$value)) {\n                        continue 2;\n                    }\n                }\n            }\n        }\n\n        $ids[] = $cache[\'id\'];\n\n        if (!in_array($cache[\'url\'], $urls)) {\n            $urls[] = $cache[\'url\'];\n        }\n    }\n\n    if ($ids) {\n        safe_update(\'etc_cache\', \'time = NULL\', \"id IN (\".implode(\',\', quote_list($ids)).\")\");\n        $this->ping($urls);\n        safe_delete(\'etc_cache\', \"time IS NULL AND url > \'\' AND reset > \'\' AND ($filter)\");\n        safe_optimize(\'etc_cache\');\n    }\n}\n\nprivate function ping ($urls = array()) {\n	static $context = null;\n	if (empty($urls)) return;\n	if (!isset($context)) {\n		$context = stream_context_create(array(\'http\'=>array(\n			\'method\'=>\'GET\',\n			\'header\'=>\"Cache-Control: max-age=0, no-store, no-cache\\r\\n\" .\n				\"Pragma: no-cache\\r\\n\" .\n				\"User-Agent: etc-cache\\r\\n\"\n			)\n		));\n	}\n\n	foreach ((array)$urls as $url) {\n		$url = preg_match(\'/^\\s*https?:/\', $url) ? $url : hu.ltrim($url, \'/ \');\n		file_get_contents($url, false, $context);\n	}\n}\n\n/*************** admin *****************/\n\npublic function tab($event, $step) {\n	global $prefs;\n\n	$qid = quote_list($id = gps(\'id\'));\n\n	if($step == \'save\' && bouncer($step, array(\'save\'=>true))) switch(gps(\'save\')) {\n		case gTxt(\'save\') : if ($id) safe_upsert(\'etc_cache\',\n			\"reset=\'\".doSlash(gps(\'reset\')).\"\',\n			url=\'\".doSlash(gps(\'url\')).\"\',\n			filter=\'\".doSlash(gps(\'filter\')).\"\'\",\n			\"id=$qid\");\n		break;\n		case gTxt(\'delete\') : safe_delete(\'etc_cache\', $id ? \"id=$qid\" : \'1\');\n		break;\n		case gTxt(\'update\') : safe_update(\'etc_cache\', \'time=NULL\', $id ? \"id=$qid\" : \'1\');\n		if ($id && $url = safe_field(\'url\', \'etc_cache\', \"id=$qid\")) {\n			$this->ping($url);\n		}\n		break;\n	}\n\n	$rs = safe_rows(\'id, time, url, reset, filter, IF(LENGTH(text)>512, CONCAT(LEFT(text, 504), \" ...\"), text) AS text\', \'etc_cache\', \'1 ORDER BY time DESC\');\n	$now = date_create(\'now\');\n\n	pagetop(\"etc_cache\");\n\n    echo n.\'<div class=\"txp-layout\">\'.\n        n.tag(\n            hed(gTxt(\'etc_cache_heading\'), 1, array(\'class\' => \'txp-heading\')),\n            \'div\', array(\'class\' => \'txp-layout-1col\')\n        );\n\n	echo n.tag_start(\'div\', array(\n            \'class\' => \'txp-layout-1col\',\n            \'id\'    => $event.\'_container\',\n        ));\n\n    if ($rs) {\n        echo n.tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n            n.tag_start(\'table\', array(\'class\' => \'txp-list--no-options\')).\n            n.tag_start(\'thead\').\n            tr(\n                n.\'<th>\'.dLink(\'etc_cache\', \'save\', \'save\', \'Delete\').n.\'</th>\'.\n                n.\'<th>ID</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_cached_at\').\'</th>\'.\n                n.\'<th>URL</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_reset\').\'</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_filter\').\'</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_actions\').\'</th>\'\n            ).\n            n.tag_end(\'thead\').\n            n.tag_start(\'tbody\');\n\n    	foreach($rs as $row) {\n    		extract($row);\n    		$class = \'date\'.($reset == \'%\' && $time < $prefs[\'lastmod\'] ? \' warning\' : \'\');\n    		$datetime = date_create($time);\n    		$diff = $datetime->diff($now);\n    		$days = $diff->format(\'%a\');\n    		$diff = (!$days ? \'\' : \"$days day\".($days == 1 ? \'\' : \'s\'). \' \').$diff->format(\'%H:%I hours old\');\n\n    		echo n.\'<form method=\"post\" action=\"?event=etc_cache\">\'.\n                n.\'<tr>\'.\n    		    n.\'<td>\'.\n                n.tag(\n                    span(gTxt(\'delete\'), array(\'class\' => \'ui-icon ui-icon-close\')),\n                    \'button\',\n                    array(\n                        \'name\'      => \'save\',\n                        \'value\'      => \'Delete\',\n                        \'class\'      => \'destroy\',\n                        \'type\'       => \'submit\',\n                        \'title\'      => gTxt(\'delete\'),\n                        \'aria-label\' => gTxt(\'delete\'),\n                    )\n                ).\'</td>\'.\n                n.\'<td title=\"\'.doSpecial($text).\'\">\'.doSpecial($id).\'</td>\'.\n                n.\'<td class=\"\'.$class.\'\">\'.doSpecial($time).\' <small>(\'.$diff.\')</small></td>\'.\n                n.\'<td>\'.fInput(\'text\', \'url\', $url, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n                n.\'<td>\'.fInput(\'text\', \'reset\', $reset, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n                n.\'<td>\'.fInput(\'text\', \'filter\', $filter, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n    		    n.\'<td>\'.fInput(\'submit\', \'save\', gTxt(\'update\')).fInput(\'submit\', \'save\', gTxt(\'save\')).n.\'</td>\'.\n    		    sInput(\'save\').\n                hInput(\'id\', $id).\n                tInput().\n    		    n.\'</tr>\'.n.\'</form>\';\n    	}\n\n        echo n.tag_end(\'tbody\').\n            n.tag_end(\'table\').\n            n.tag_end(\'div\');\n    } else {\n        echo graf(\n            span(null, array(\'class\' => \'ui-icon ui-icon-info\')).\' \'.\n            gTxt(\'etc_cache_no_cached_items\'),\n            array(\'class\' => \'alert-block information\')\n        );\n    }\n\n    echo n.tag_end(\'div\'). // End of .txp-layout-1col.\n        n.\'</div>\'; // End of .txp-layout.\n}\n//------- end class\n}\n\n/*************** install *****************/\n\nfunction etc_cache_install($event=\'\', $step=\'\')\n{\n	if($step == \'deleted\') {\n		safe_delete(\'txp_prefs\', \"name LIKE \'etc\\_cache\\_%\'\");\n		safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'etc_cache\'));\n		return;\n	}\n	if($step == \'enabled\') {\n		$qc=\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'etc_cache\').\" (\";\n		$qc.= <<<EOF\n			`id` varchar(64) NOT NULL,\n			`url` varchar(255) DEFAULT NULL,\n			`time` DATETIME DEFAULT NULL,\n			`reset` varchar(255) DEFAULT \'1\',\n			`filter` varchar(767) DEFAULT NULL,\n			`text` mediumtext,\n			PRIMARY KEY (`id`)\n			) ENGINE=MyISAM CHARACTER SET=utf8 ;\nEOF;\n		safe_query($qc);\n		return;\n	}\n}', 'Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_cache\');\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'etc_cache_install\', \'plugin_lifecycle.etc_cache\');\n    new etc_Cache();\n} elseif (serverSet(\'HTTP_USER_AGENT\') == \'etc_cache\') {\n    global $nolog;\n    $nolog = true;\n}\n\n// -------------------------------------------------------------\n\nfunction etc_cache($atts, $thing = null)\n{\n    global $prefs, $pretext;\n    static $lastmod = null, $times = array();\n\n    extract(lAtts(array(\n        \'id\'    => !empty($atts[\'form\']) ? $atts[\'form\'] : null,\n        \'form\'  => \'\',\n        \'reset\' => null,\n        \'time\'  => true\n    ), $atts));\n\n    if (empty($id)) {\n        trigger_error(gTxt(\'form_not_specified\'));\n\n        return;\n    }\n\n    $id = doSlash($id);\n    $now = time();\n\n    if (!isset($lastmod)) {\n        $lastmod = strtotime($prefs[\'lastmod\']);\n    }\n\n    if (empty($time)) {\n        $update = 0;\n    } elseif ($time === true) {\n        $update = $lastmod;\n    } elseif (!isset($times[$time = (string)$time])) {\n        $update = is_numeric($time) ? $now + (float)$time : (int)strtotime($time, $now);\n\n        if ($update > $now) {\n            if (!is_numeric($time)) {\n                $date = date_parse($time);\n\n                foreach (array(\'year\', \'month\', \'day\', \'hour\', \'minute\', \'second\', \'fraction\') as $key) {\n                    if ($date[$key] !== false) {\n                        $update = -1;\n                        break;\n                    }\n                }\n            }\n        }\n\n        $times[$time] = $update;\n    } else {\n        $update = $times[$time];\n    }\n\n    $cached = $update >= 0 ? safe_row(\'reset, UNIX_TIMESTAMP(time) AS time, url, text\', \'etc_cache\', \"id = \'$id\' AND time IS NOT NULL\") : false;\n    $onlastmod = $cached && ($time === true || $update > $now) && ($cached[\'reset\'] == \'%\' || $cached[\'reset\'] == \'1\');\n\n    if ($update > $now) {\n        $update = $onlastmod ? max(2*$now - $update, $lastmod) : 2*$now - $update;\n//    } elseif ($time !== true) {\n//        if (!isset($reset) && !isset($cached[\'reset\'])) $reset = \'\';\n    } elseif ($cached && !$onlastmod && $time === true) {\n        $update = 0;\n    }\n\n    $parse = !$cached || $update < 0 || $cached[\'time\'] < $update;\n\n    if ($parse) {\n        $out = $form ? parse_form($form, $thing) : parse($thing);\n\n        if ($update >= 0) {\n            $renew = ($reset || !isset($cached[\'url\']) ? \"url=\'\".doSlash($pretext[\'req\']).\"\',\" : \'\')\n                .(isset($reset) && $reset !== true ? \"reset=\'\".doSlash($reset).\"\',\" : \'\');\n\n            safe_upsert(\'etc_cache\', \"time = NOW(), $renew text = \'\".doSlash($out).\"\'\", \"id = \'$id\'\");\n        }\n    } else {\n        $out = $cached[\'text\'];\n    }\n\n    return $out;\n}\n\n/********** Admin class ***********/\n\nclass etc_Cache\n{\npublic function __construct()\n{\n	add_privs(\'etc_cache\', \'1,2\');\n	register_tab(\'extensions\', \'etc_cache\', gTxt(\'etc_cache_tab\'));\n	register_callback(array($this, \'update\'), \'site.update\');\n	register_callback(array($this, \'tab\'), \'etc_cache\');\n}\n\npublic function update($event, $step = \'\', $rs = null)\n{\n    $urls = $ids = array();\n    $safe_step = $step ? doSlash($step) : \'\';\n    $filter = \"\'$safe_step\' LIKE reset\".($step ? \" OR FIND_IN_SET(\'$safe_step\', reset)\" : \'\');\n\n    foreach (safe_rows(\'id, url, filter\', \'etc_cache\', \"url > \'\' AND reset > \'\' AND ($filter)\") as $cache) {\n        if ($step && !empty($cache[\'filter\'])) {\n            $items = json_decode($cache[\'filter\'], true);\n\n            if (isset($items[$step]) && is_array($items[$step])) {\n                foreach ($items[$step] as $field => $value) {\n                    if (isset($rs[$field]) && !in_array($rs[$field], (array)$value)) {\n                        continue 2;\n                    }\n                }\n            }\n        }\n\n        $ids[] = $cache[\'id\'];\n\n        if (!in_array($cache[\'url\'], $urls)) {\n            $urls[] = $cache[\'url\'];\n        }\n    }\n\n    if ($ids) {\n        safe_update(\'etc_cache\', \'time = NULL\', \"id IN (\".implode(\',\', quote_list($ids)).\")\");\n        $this->ping($urls);\n        safe_delete(\'etc_cache\', \"time IS NULL AND url > \'\' AND reset > \'\' AND ($filter)\");\n        safe_optimize(\'etc_cache\');\n    }\n}\n\nprivate function ping ($urls = array()) {\n	static $context = null;\n	if (empty($urls)) return;\n	if (!isset($context)) {\n		$context = stream_context_create(array(\'http\'=>array(\n			\'method\'=>\'GET\',\n			\'header\'=>\"Cache-Control: max-age=0, no-store, no-cache\\r\\n\" .\n				\"Pragma: no-cache\\r\\n\" .\n				\"User-Agent: etc-cache\\r\\n\"\n			)\n		));\n	}\n\n	foreach ((array)$urls as $url) {\n		$url = preg_match(\'/^\\s*https?:/\', $url) ? $url : hu.ltrim($url, \'/ \');\n		file_get_contents($url, false, $context);\n	}\n}\n\n/*************** admin *****************/\n\npublic function tab($event, $step) {\n	global $prefs;\n\n	$qid = quote_list($id = gps(\'id\'));\n\n	if($step == \'save\' && bouncer($step, array(\'save\'=>true))) switch(gps(\'save\')) {\n		case gTxt(\'save\') : if ($id) safe_upsert(\'etc_cache\',\n			\"reset=\'\".doSlash(gps(\'reset\')).\"\',\n			url=\'\".doSlash(gps(\'url\')).\"\',\n			filter=\'\".doSlash(gps(\'filter\')).\"\'\",\n			\"id=$qid\");\n		break;\n		case gTxt(\'delete\') : safe_delete(\'etc_cache\', $id ? \"id=$qid\" : \'1\');\n		break;\n		case gTxt(\'update\') : safe_update(\'etc_cache\', \'time=NULL\', $id ? \"id=$qid\" : \'1\');\n		if ($id && $url = safe_field(\'url\', \'etc_cache\', \"id=$qid\")) {\n			$this->ping($url);\n		}\n		break;\n	}\n\n	$rs = safe_rows(\'id, time, url, reset, filter, IF(LENGTH(text)>512, CONCAT(LEFT(text, 504), \" ...\"), text) AS text\', \'etc_cache\', \'1 ORDER BY time DESC\');\n	$now = date_create(\'now\');\n\n	pagetop(\"etc_cache\");\n\n    echo n.\'<div class=\"txp-layout\">\'.\n        n.tag(\n            hed(gTxt(\'etc_cache_heading\'), 1, array(\'class\' => \'txp-heading\')),\n            \'div\', array(\'class\' => \'txp-layout-1col\')\n        );\n\n	echo n.tag_start(\'div\', array(\n            \'class\' => \'txp-layout-1col\',\n            \'id\'    => $event.\'_container\',\n        ));\n\n    if ($rs) {\n        echo n.tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n            n.tag_start(\'table\', array(\'class\' => \'txp-list--no-options\')).\n            n.tag_start(\'thead\').\n            tr(\n                n.\'<th>\'.dLink(\'etc_cache\', \'save\', \'save\', \'Delete\').n.\'</th>\'.\n                n.\'<th>ID</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_cached_at\').\'</th>\'.\n                n.\'<th>URL</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_reset\').\'</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_filter\').\'</th>\'.\n                n.\'<th>\'.gTxt(\'etc_cache_actions\').\'</th>\'\n            ).\n            n.tag_end(\'thead\').\n            n.tag_start(\'tbody\');\n\n    	foreach($rs as $row) {\n    		extract($row);\n    		$class = \'date\'.($reset == \'%\' && $time < $prefs[\'lastmod\'] ? \' warning\' : \'\');\n    		$datetime = date_create($time);\n    		$diff = $datetime->diff($now);\n    		$days = $diff->format(\'%a\');\n    		$diff = (!$days ? \'\' : \"$days day\".($days == 1 ? \'\' : \'s\'). \' \').$diff->format(\'%H:%I hours old\');\n\n    		echo n.\'<form method=\"post\" action=\"?event=etc_cache\">\'.\n                n.\'<tr>\'.\n    		    n.\'<td>\'.\n                n.tag(\n                    span(gTxt(\'delete\'), array(\'class\' => \'ui-icon ui-icon-close\')),\n                    \'button\',\n                    array(\n                        \'name\'      => \'save\',\n                        \'value\'      => \'Delete\',\n                        \'class\'      => \'destroy\',\n                        \'type\'       => \'submit\',\n                        \'title\'      => gTxt(\'delete\'),\n                        \'aria-label\' => gTxt(\'delete\'),\n                    )\n                ).\'</td>\'.\n                n.\'<td title=\"\'.doSpecial($text).\'\">\'.doSpecial($id).\'</td>\'.\n                n.\'<td class=\"\'.$class.\'\">\'.doSpecial($time).\' <small>(\'.$diff.\')</small></td>\'.\n                n.\'<td>\'.fInput(\'text\', \'url\', $url, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n                n.\'<td>\'.fInput(\'text\', \'reset\', $reset, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n                n.\'<td>\'.fInput(\'text\', \'filter\', $filter, \'\', \'\', \'\', INPUT_MEDIUM).n.\'</td>\'.\n    		    n.\'<td>\'.fInput(\'submit\', \'save\', gTxt(\'update\')).fInput(\'submit\', \'save\', gTxt(\'save\')).n.\'</td>\'.\n    		    sInput(\'save\').\n                hInput(\'id\', $id).\n                tInput().\n    		    n.\'</tr>\'.n.\'</form>\';\n    	}\n\n        echo n.tag_end(\'tbody\').\n            n.tag_end(\'table\').\n            n.tag_end(\'div\');\n    } else {\n        echo graf(\n            span(null, array(\'class\' => \'ui-icon ui-icon-info\')).\' \'.\n            gTxt(\'etc_cache_no_cached_items\'),\n            array(\'class\' => \'alert-block information\')\n        );\n    }\n\n    echo n.tag_end(\'div\'). // End of .txp-layout-1col.\n        n.\'</div>\'; // End of .txp-layout.\n}\n//------- end class\n}\n\n/*************** install *****************/\n\nfunction etc_cache_install($event=\'\', $step=\'\')\n{\n	if($step == \'deleted\') {\n		safe_delete(\'txp_prefs\', \"name LIKE \'etc\\_cache\\_%\'\");\n		safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'etc_cache\'));\n		return;\n	}\n	if($step == \'enabled\') {\n		$qc=\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'etc_cache\').\" (\";\n		$qc.= <<<EOF\n			`id` varchar(64) NOT NULL,\n			`url` varchar(255) DEFAULT NULL,\n			`time` DATETIME DEFAULT NULL,\n			`reset` varchar(255) DEFAULT \'1\',\n			`filter` varchar(767) DEFAULT NULL,\n			`text` mediumtext,\n			PRIMARY KEY (`id`)\n			) ENGINE=MyISAM CHARACTER SET=utf8 ;\nEOF;\n		safe_query($qc);\n		return;\n	}\n}', '379fc35378ab3f2d527478d491adfdfe', '#@etc_cache\netc_cache_actions => Actions\netc_cache_cached_at => Cached at\netc_cache_filter => Filter\netc_cache_heading => Cached items\netc_cache_no_cached_items => No cached items recorded.\netc_cache_reset => Reset\netc_cache_tab => Cache\n', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_crunchers', 1, 'Stef Dawson / Devin Doucette / Alexandre Tedeschi', 'http://stefdawson.com/', '0.11', 'Library of compression algorithms', '<div id=\"smd_help\">\n\n	<h1>smd_crunchers</h1>\n\n	<p class=\"important\">Requires PHP5+</p>\n\n	<p>Library of compression / decompression algorithms mostly written by other people.</p>\n\n	<p>After installation, it enables anyone to employ bi-directional Tar, Zip, Gzip and Bzip2 mechanisms, some of which would ordinarily rely on particular components being compiled into <span class=\"caps\">PHP</span>.</p>\n\n	<p>While certain core libraries are still required to be compiled into <span class=\"caps\">PHP</span> (e.g. zlib), this plugin mitigates the need for any further extensions like <span class=\"caps\">PECL</span> / <span class=\"caps\">PEAR</span> to offer zip capabilities. In limited server setups or places where users don&#8217;t have access to configuration files, this is rather handy.</p>\n\n	<h2>Usage</h2>\n\n	<dl>\n		<dt>Function <code>smd_crunch_capabilities( type )</code></dt>\n		<dd>Find out which compression / decompression facilities are available on the current server setup. The <code>type</code> argument can be either:\n	<dl>\n		<dd><code>compress</code>: to determine which compression facilities are available</dd>\n		<dd><code>decompress</code>: to determine which decompression facilities are available</dd>\n	</dl></dd>\n		<dd>Default: <code>compress</code></dd>\n		<dd>Returns: an array containing zero or more of:\n	<dl>\n		<dd><code>tar</code></dd>\n		<dd><code>zip</code></dd>\n		<dd><code>gzip</code></dd>\n		<dd><code>bzip2</code></dd>\n	</dl></dd>\n		<dt>Function <code>smd_mkdir_recursive( path, mode )</code></dt>\n		<dd>Create a directory tree representing <code>path</code>, and set the optional <code>mode</code> permissions (Default: 0755)</dd>\n		<dt>Class smd_crunch_archive</dt>\n		<dd>Base class for all main archiving / unarchiving facilities, <strong>excluding unzip</strong>.</dd>\n		<dt>Class smd_crunch_tar_file</dt>\n		<dd>Specific class for handling (de)compression of .tar files</dd>\n		<dt>Class smd_crunch_gzip_file</dt>\n		<dd>Specific class for handling (de)compression of.gz and .tgz files</dd>\n		<dt>Class smd_crunch_bzip_file</dt>\n		<dd>Specific class for handling (de)compression of.bz2 and .tbz2 files</dd>\n		<dt>Class smd_crunch_zip_file</dt>\n		<dd>Specific class for handling compression of.zip files</dd>\n		<dt>Class smd_crunch_dUnzip2</dt>\n		<dd>Specific class for handling decompression of.zip files</dd>\n		<dt>Class smd_crunch_dZip</dt>\n		<dd>Alternative methods for compressing .zip files</dd>\n	</dl>\n\n	<h2>Example 1: compress all files in a folder as zip</h2>\n\n	<p>But exclude any <code>.svn</code> directories in the process. Then immediately serve the file as a downloadable item <code>file.zip</code>.</p>\n\n<pre class=\"block\"><code class=\"block\">$arc = new smd_crunch_zip_file(&#39;file.zip&#39;);\n$arc-&gt;set_options(array(\n     &#39;basedir&#39;   =&gt; &#39;/path/to/files&#39;,\n     &#39;overwrite&#39; =&gt; 1,\n     &#39;inmemory&#39;  =&gt; 1\n   ));\n$arc-&gt;add_files(&#39;/path/to/files/*.*&#39;);\n$arc-&gt;exclude_files(&#39;/path/to/files/*.svn*&#39;);\n$arc-&gt;create_archive();\n$arc-&gt;download_file();\n</code></pre>\n\n	<h2>Example 2: decompress gzip archive</h2>\n\n	<p>This is the same generic procedure for all supported compressed types.</p>\n\n<pre class=\"block\"><code class=\"block\">$fullpath = &#39;/path/to/file.gz&#39;;\n$arc = new smd_crunch_gzip_file(basename($fullpath));\n$arc-&gt;set_options(array(\n     &#39;basedir&#39;   =&gt; &#39;/path/to/export/folder&#39;,\n     &#39;overwrite&#39; =&gt; 1\n   ));\n$arc-&gt;extract_files();\n</code></pre>\n\n	<h2>Example 3: decompress zip archive</h2>\n\n<pre class=\"block\"><code class=\"block\">$fullpath = &#39;/path/to/file.zip&#39;;\n$zip = new smd_crunch_dUnzip2($fullpath);\n$filelist = $zip-&gt;getList();\nforeach($filelist as $fn =&gt; $zippedFile) {\n   // Do file size/type validation here?\n   echo &quot;$fn ($zippedFile[&#39;uncompressed_size&#39;] bytes)&lt;br&gt;&quot;;\n}\n$zip-&gt;unzipAll(&#39;/path/to/export/folder&#39;);\n$zip-&gt;close();\n</code></pre>\n\n	<h2>Author / credits</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n	<p>Libraries from <a href=\"http://phpclasses.org/\"><span class=\"caps\">PHP</span> Classes</a> by Devin Doucette and Alexandre Tedeschi. Many thanks to those authors for awesome coding skillz.</p>\n\n	<h2>Changelog</h2>\n\n	<ul>\n		<li>29 Jan 2012 | v0.10 | Initial release</li>\n		<li>29 Jan 2012 | v0.11 | Added <code>smd_mkdir_recursive()</code></li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_crunchers\r\n *\r\n * A Textpattern CMS plugin library for standard compression/decompression algorithms\r\n *  -> zip/unzip\r\n *  -> tar\r\n *  -> gzip\r\n *  -> bzip2\r\n *\r\n * @author Devin Doucette\r\n * @author Alexandre Tedeschi\r\n * @author Stef Dawson\r\n * @link   http://shaw.ca/\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// ------------------------\r\nfunction smd_crunch_capabilities($type=\'compress\') {\r\n	$smd_crunchers = array();\r\n\r\n	// Bi-directional class\r\n	if (class_exists(\'smd_crunch_tar_file\')) {\r\n		$smd_crunchers[] = \'tar\';\r\n	}\r\n\r\n	if ($type == \'compress\') {\r\n		// Available compressors\r\n		if (function_exists(\'gzcompress\') && function_exists(\'gzopen\') && function_exists(\'gzwrite\')) {\r\n			if (class_exists(\'smd_crunch_gzip_file\')) {\r\n				$smd_crunchers[] = \'gzip\';\r\n			}\r\n			if (class_exists(\'smd_crunch_zip_file\')) {\r\n				$smd_crunchers[] = \'zip\';\r\n			}\r\n		}\r\n		if (function_exists(\'bzcompress\') && function_exists(\'bzopen\') && function_exists(\'bzwrite\')) {\r\n			if (class_exists(\'smd_crunch_bzip_file\')) {\r\n				$smd_crunchers[] = \'bzip2\';\r\n			}\r\n		}\r\n	} else {\r\n		// Available decompressors\r\n		if (function_exists(\'gzinflate\')) {\r\n			if (class_exists(\'smd_crunch_gzip_file\')) {\r\n				$smd_crunchers[] = \'gzip\';\r\n			}\r\n			if (class_exists(\'smd_crunch_dUnzip2\')) {\r\n				$smd_crunchers[] = \'zip\';\r\n			}\r\n		}\r\n		if (function_exists(\'bzdecompress\')) {\r\n			if (class_exists(\'smd_crunch_bzip_file\')) {\r\n				$smd_crunchers[] = \'bzip2\';\r\n			}\r\n		}\r\n	}\r\n	return $smd_crunchers;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_mkdir_recursive($path, $mode = 0755) {\r\n	if(@mkdir($path, $mode) or file_exists($path)) return true;\r\n	return (smd_mkdir_recursive(dirname($path), $mode) and mkdir($path, $mode));\r\n}\r\n\r\n/*--------------------------------------------------\r\n | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.1\r\n | By Devin Doucette\r\n | Copyright (c) 2005 Devin Doucette\r\n | Email: darksnoopy@shaw.ca\r\n +--------------------------------------------------\r\n | Email bugs/suggestions to darksnoopy@shaw.ca\r\n +--------------------------------------------------\r\n | This script has been created and released under\r\n | the GNU GPL and is free to use and redistribute\r\n | only if this copyright statement is not removed\r\n +--------------------------------------------------*/\r\n\r\nclass smd_crunch_archive\r\n{\r\n	function smd_crunch_archive($name)\r\n	{\r\n		$this->options = array (\r\n			\'basedir\' => \".\",\r\n			\'name\' => $name,\r\n			\'prepend\' => \"\",\r\n			\'inmemory\' => 0,\r\n			\'overwrite\' => 0,\r\n			\'recurse\' => 1,\r\n			\'storepaths\' => 1,\r\n			\'followlinks\' => 0,\r\n			\'level\' => 3,\r\n			\'method\' => 1,\r\n			\'sfx\' => \"\",\r\n			\'type\' => \"\",\r\n			\'comment\' => \"\"\r\n		);\r\n		$this->files = array ();\r\n		$this->exclude = array ();\r\n		$this->storeonly = array ();\r\n		$this->error = array ();\r\n	}\r\n\r\n	function set_options($options)\r\n	{\r\n		foreach ($options as $key => $value)\r\n			$this->options[$key] = $value;\r\n		if (!empty ($this->options[\'basedir\']))\r\n		{\r\n			$this->options[\'basedir\'] = str_replace(\"\\\\\", \"/\", $this->options[\'basedir\']);\r\n			$this->options[\'basedir\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'basedir\']);\r\n			$this->options[\'basedir\'] = preg_replace(\"/\\/$/\", \"\", $this->options[\'basedir\']);\r\n		}\r\n		if (!empty ($this->options[\'name\']))\r\n		{\r\n			$this->options[\'name\'] = str_replace(\"\\\\\", \"/\", $this->options[\'name\']);\r\n			$this->options[\'name\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'name\']);\r\n		}\r\n		if (!empty ($this->options[\'prepend\']))\r\n		{\r\n			$this->options[\'prepend\'] = str_replace(\"\\\\\", \"/\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/^(\\.*\\/+)+/\", \"\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/\\/$/\", \"\", $this->options[\'prepend\']) . \"/\";\r\n		}\r\n	}\r\n\r\n	function create_archive()\r\n	{\r\n		$this->make_list();\r\n\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($this->options[\'overwrite\'] == 0 && file_exists($this->options[\'name\'] . ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\" ? \".tmp\" : \"\")))\r\n			{\r\n				$this->error[] = \"File {$this->options[\'name\']} already exists.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n			else if ($this->archive = @fopen($this->options[\'name\'] . ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\" ? \".tmp\" : \"\"), \"wb+\"))\r\n				chdir($pwd);\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = \"\";\r\n\r\n		switch ($this->options[\'type\'])\r\n		{\r\n		case \"zip\":\r\n			if (!$this->create_zip())\r\n			{\r\n				$this->error[] = \"Could not create zip file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"bzip\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n			if (!$this->create_bzip())\r\n			{\r\n				$this->error[] = \"Could not create bzip2 file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"gzip\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n			if (!$this->create_gzip())\r\n			{\r\n				$this->error[] = \"Could not create gzip file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"tar\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n		}\r\n\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			fclose($this->archive);\r\n			if ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\")\r\n				unlink($this->options[\'basedir\'] . \"/\" . $this->options[\'name\'] . \".tmp\");\r\n		}\r\n	}\r\n\r\n	function add_data($data)\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n			fwrite($this->archive, $data);\r\n		else\r\n			$this->archive .= $data;\r\n	}\r\n\r\n	function make_list()\r\n	{\r\n		if (!empty ($this->exclude))\r\n			foreach ($this->files as $key => $value)\r\n				foreach ($this->exclude as $current)\r\n					if ($value[\'name\'] == $current[\'name\'])\r\n						unset ($this->files[$key]);\r\n		if (!empty ($this->storeonly))\r\n			foreach ($this->files as $key => $value)\r\n				foreach ($this->storeonly as $current)\r\n					if ($value[\'name\'] == $current[\'name\'])\r\n						$this->files[$key][\'method\'] = 0;\r\n		unset ($this->exclude, $this->storeonly);\r\n	}\r\n\r\n	function add_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->files[] = $current;\r\n	}\r\n\r\n	function exclude_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->exclude[] = $current;\r\n	}\r\n\r\n	function store_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->storeonly[] = $current;\r\n	}\r\n\r\n	function list_files($list)\r\n	{\r\n		if (!is_array ($list))\r\n		{\r\n			$temp = $list;\r\n			$list = array ($temp);\r\n			unset ($temp);\r\n		}\r\n\r\n		$files = array ();\r\n\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($list as $current)\r\n		{\r\n			$current = str_replace(\"\\\\\", \"/\", $current);\r\n			$current = preg_replace(\"/\\/+/\", \"/\", $current);\r\n			$current = preg_replace(\"/\\/$/\", \"\", $current);\r\n			if (strstr($current, \"*\"))\r\n			{\r\n				$regex = preg_replace(\"/([\\\\\\^\\$\\.\\[\\]\\|\\(\\)\\?\\+\\{\\}\\/])/\", \"\\\\\\\\\\\\1\", $current);\r\n				$regex = str_replace(\"*\", \".*\", $regex);\r\n				$dir = strstr($current, \"/\") ? substr($current, 0, strrpos($current, \"/\")) : \".\";\r\n				$temp = $this->parse_dir($dir);\r\n				foreach ($temp as $current2)\r\n					if (preg_match(\"/^{$regex}$/i\", $current2[\'name\']))\r\n						$files[] = $current2;\r\n				unset ($regex, $dir, $temp, $current);\r\n			}\r\n			else if (@is_dir($current))\r\n			{\r\n				$temp = $this->parse_dir($current);\r\n				foreach ($temp as $file)\r\n					$files[] = $file;\r\n				unset ($temp, $file);\r\n			}\r\n			else if (@file_exists($current))\r\n				$files[] = array (\'name\' => $current, \'name2\' => $this->options[\'prepend\'] .\r\n					preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($current, \"/\")) ?\r\n					substr($current, strrpos($current, \"/\") + 1) : $current),\r\n					\'type\' => @is_link($current) && $this->options[\'followlinks\'] == 0 ? 2 : 0,\r\n					\'ext\' => substr($current, strrpos($current, \".\")), \'stat\' => stat($current));\r\n		}\r\n\r\n		chdir($pwd);\r\n\r\n		unset ($current, $pwd);\r\n\r\n		usort($files, array (\"smd_crunch_archive\", \"sort_files\"));\r\n\r\n		return $files;\r\n	}\r\n\r\n	function parse_dir($dirname)\r\n	{\r\n		if ($this->options[\'storepaths\'] == 1 && !preg_match(\"/^(\\.+\\/*)+$/\", $dirname))\r\n			$files = array (array (\'name\' => $dirname, \'name2\' => $this->options[\'prepend\'] .\r\n				preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($dirname, \"/\")) ?\r\n				substr($dirname, strrpos($dirname, \"/\") + 1) : $dirname), \'type\' => 5, \'stat\' => stat($dirname)));\r\n		else\r\n			$files = array ();\r\n		$dir = @opendir($dirname);\r\n\r\n		while ($file = @readdir($dir))\r\n		{\r\n			$fullname = $dirname . \"/\" . $file;\r\n			if ($file == \".\" || $file == \"..\")\r\n				continue;\r\n			else if (@is_dir($fullname))\r\n			{\r\n				if (empty ($this->options[\'recurse\']))\r\n					continue;\r\n				$temp = $this->parse_dir($fullname);\r\n				foreach ($temp as $file2)\r\n					$files[] = $file2;\r\n			}\r\n			else if (@file_exists($fullname))\r\n				$files[] = array (\'name\' => $fullname, \'name2\' => $this->options[\'prepend\'] .\r\n					preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($fullname, \"/\")) ?\r\n					substr($fullname, strrpos($fullname, \"/\") + 1) : $fullname),\r\n					\'type\' => @is_link($fullname) && $this->options[\'followlinks\'] == 0 ? 2 : 0,\r\n					\'ext\' => substr($file, strrpos($file, \".\")), \'stat\' => stat($fullname));\r\n		}\r\n\r\n		@closedir($dir);\r\n\r\n		return $files;\r\n	}\r\n\r\n	function sort_files($a, $b)\r\n	{\r\n		if ($a[\'type\'] != $b[\'type\'])\r\n			if ($a[\'type\'] == 5 || $b[\'type\'] == 2)\r\n				return -1;\r\n			else if ($a[\'type\'] == 2 || $b[\'type\'] == 5)\r\n				return 1;\r\n		else if ($a[\'type\'] == 5)\r\n			return strcmp(strtolower($a[\'name\']), strtolower($b[\'name\']));\r\n		else if ($a[\'ext\'] != $b[\'ext\'])\r\n			return strcmp($a[\'ext\'], $b[\'ext\']);\r\n		else if ($a[\'stat\'][7] != $b[\'stat\'][7])\r\n			return $a[\'stat\'][7] > $b[\'stat\'][7] ? -1 : 1;\r\n		else\r\n			return strcmp(strtolower($a[\'name\']), strtolower($b[\'name\']));\r\n		return 0;\r\n	}\r\n\r\n	function download_file()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$this->error[] = \"Can only use download_file() if archive is in memory. Redirect to file otherwise, it is faster.\";\r\n			return;\r\n		}\r\n		switch ($this->options[\'type\'])\r\n		{\r\n		case \"zip\":\r\n			header(\"Content-Type: application/zip\");\r\n			break;\r\n		case \"bzip\":\r\n			header(\"Content-Type: application/x-bzip2\");\r\n			break;\r\n		case \"gzip\":\r\n			header(\"Content-Type: application/x-gzip\");\r\n			break;\r\n		case \"tar\":\r\n			header(\"Content-Type: application/x-tar\");\r\n		}\r\n		$header = \"Content-Disposition: attachment; filename=\\\"\";\r\n		$header .= strstr($this->options[\'name\'], \"/\") ? substr($this->options[\'name\'], strrpos($this->options[\'name\'], \"/\") + 1) : $this->options[\'name\'];\r\n		$header .= \"\\\"\";\r\n		header($header);\r\n		header(\"Content-Length: \" . strlen($this->archive));\r\n		header(\"Content-Transfer-Encoding: binary\");\r\n		header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\r\n		header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\r\n		print($this->archive);\r\n	}\r\n}\r\n\r\nclass smd_crunch_tar_file extends smd_crunch_archive\r\n{\r\n	function smd_crunch_tar_file($name)\r\n	{\r\n		$this->smd_crunch_archive($name);\r\n		$this->options[\'type\'] = \"tar\";\r\n	}\r\n\r\n	function create_tar()\r\n	{\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($this->files as $current)\r\n		{\r\n			if ($current[\'name\'] == $this->options[\'name\'])\r\n				continue;\r\n			if (strlen($current[\'name2\']) > 99)\r\n			{\r\n				$path = substr($current[\'name2\'], 0, strpos($current[\'name2\'], \"/\", strlen($current[\'name2\']) - 100) + 1);\r\n				$current[\'name2\'] = substr($current[\'name2\'], strlen($path));\r\n				if (strlen($path) > 154 || strlen($current[\'name2\']) > 99)\r\n				{\r\n					$this->error[] = \"Could not add {$path}{$current[\'name2\']} to archive because the filename is too long.\";\r\n					continue;\r\n				}\r\n			}\r\n			$block = pack(\"a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12\", $current[\'name2\'], sprintf(\"%07o\",\r\n				$current[\'stat\'][2]), sprintf(\"%07o\", $current[\'stat\'][4]), sprintf(\"%07o\", $current[\'stat\'][5]),\r\n				sprintf(\"%011o\", $current[\'type\'] == 2 ? 0 : $current[\'stat\'][7]), sprintf(\"%011o\", $current[\'stat\'][9]),\r\n				\"        \", $current[\'type\'], $current[\'type\'] == 2 ? @readlink($current[\'name\']) : \"\", \"ustar \", \" \",\r\n				\"Unknown\", \"Unknown\", \"\", \"\", !empty ($path) ? $path : \"\", \"\");\r\n\r\n			$checksum = 0;\r\n			for ($i = 0; $i < 512; $i++)\r\n				$checksum += ord(substr($block, $i, 1));\r\n			$checksum = pack(\"a8\", sprintf(\"%07o\", $checksum));\r\n			$block = substr_replace($block, $checksum, 148, 8);\r\n\r\n			if ($current[\'type\'] == 2 || $current[\'stat\'][7] == 0)\r\n				$this->add_data($block);\r\n			else if ($fp = @fopen($current[\'name\'], \"rb\"))\r\n			{\r\n				$this->add_data($block);\r\n				while ($temp = fread($fp, 1048576))\r\n					$this->add_data($temp);\r\n				if ($current[\'stat\'][7] % 512 > 0)\r\n				{\r\n					$temp = \"\";\r\n					for ($i = 0; $i < 512 - $current[\'stat\'][7] % 512; $i++)\r\n						$temp .= \"\\0\";\r\n					$this->add_data($temp);\r\n				}\r\n				fclose($fp);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open file {$current[\'name\']} for reading. It was not added.\";\r\n		}\r\n\r\n		$this->add_data(pack(\"a1024\", \"\"));\r\n\r\n		chdir($pwd);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function extract_files()\r\n	{\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		if ($fp = $this->open_archive())\r\n		{\r\n			if ($this->options[\'inmemory\'] == 1)\r\n				$this->files = array ();\r\n\r\n			while ($block = fread($fp, 512))\r\n			{\r\n				$temp = unpack(\"a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp\", $block);\r\n				$file = array (\r\n					\'name\' => $temp[\'prefix\'] . $temp[\'name\'],\r\n					\'stat\' => array (\r\n						2 => $temp[\'mode\'],\r\n						4 => octdec($temp[\'uid\']),\r\n						5 => octdec($temp[\'gid\']),\r\n						7 => octdec($temp[\'size\']),\r\n						9 => octdec($temp[\'mtime\']),\r\n					),\r\n					\'checksum\' => octdec($temp[\'checksum\']),\r\n					\'type\' => $temp[\'type\'],\r\n					\'magic\' => $temp[\'magic\'],\r\n				);\r\n\r\n				if ($file[\'checksum\'] == 0x00000000)\r\n					break;\r\n				else if (substr($file[\'magic\'], 0, 5) != \"ustar\")\r\n				{\r\n					$this->error[] = \"This script does not support extracting this type of tar file.\";\r\n					break;\r\n				}\r\n				$block = substr_replace($block, \"        \", 148, 8);\r\n				$checksum = 0;\r\n				for ($i = 0; $i < 512; $i++)\r\n					$checksum += ord(substr($block, $i, 1));\r\n				if ($file[\'checksum\'] != $checksum)\r\n					$this->error[] = \"Could not extract from {$this->options[\'name\']}, it is corrupt.\";\r\n\r\n				// Added: create dir if required\r\n				if (strpos($file[\'name\'], \'..\') === false) {\r\n					$path_parts = pathinfo($file[\'name\']);\r\n					if (!file_exists($file[\'name\'])) {\r\n						$ret = smd_mkdir_recursive(((isset($path_parts[\'extension\'])) ? dirname($this->options[\'basedir\'].DS.$file[\'name\']) : $this->options[\'basedir\'].DS.$file[\'name\']));\r\n					}\r\n				}\r\n\r\n				if ($this->options[\'inmemory\'] == 1)\r\n				{\r\n					$file[\'data\'] = fread($fp, $file[\'stat\'][7]);\r\n					fread($fp, (512 - $file[\'stat\'][7] % 512) == 512 ? 0 : (512 - $file[\'stat\'][7] % 512));\r\n					unset ($file[\'checksum\'], $file[\'magic\']);\r\n					$this->files[] = $file;\r\n				}\r\n				else if ($file[\'type\'] == 5)\r\n				{\r\n					if (!is_dir($file[\'name\']))\r\n						mkdir($file[\'name\'], $file[\'stat\'][2]);\r\n				}\r\n				else if ($this->options[\'overwrite\'] == 0 && file_exists($file[\'name\']))\r\n				{\r\n					$this->error[] = \"{$file[\'name\']} already exists.\";\r\n					continue;\r\n				}\r\n				else if ($file[\'type\'] == 2)\r\n				{\r\n					symlink($temp[\'symlink\'], $file[\'name\']);\r\n					chmod($file[\'name\'], $file[\'stat\'][2]);\r\n				}\r\n				else if ($new = fopen($file[\'name\'], \"wb\"))\r\n				{\r\n					fwrite($new, fread($fp, $file[\'stat\'][7]));\r\n					fread($fp, (512 - $file[\'stat\'][7] % 512) == 512 ? 0 : (512 - $file[\'stat\'][7] % 512));\r\n					fclose($new);\r\n					@chmod($file[\'name\'], 0644); // Hard-coded for now because the stupid 0100644 value in $file[\'stat\'][2] confuses the OS/PHP\r\n				}\r\n				else\r\n				{\r\n					$this->error[] = \"Could not open {$file[\'name\']} for writing.\";\r\n					continue;\r\n				}\r\n				// Remove owner and group setting since there is almost 0 likelihood the files will have the same user/group across systems\r\n				// In other words, leave owner/group at the Apache default -- which is what we want\r\n//				chown($file[\'name\'], $file[\'stat\'][4]);\r\n//				chgrp($file[\'name\'], $file[\'stat\'][5]);\r\n				@touch($file[\'name\'], $file[\'stat\'][9]);\r\n				unset ($file);\r\n			}\r\n		}\r\n		else\r\n			$this->error[] = \"Could not open file {$this->options[\'name\']}\";\r\n\r\n		chdir($pwd);\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @fopen($this->options[\'name\'], \"rb\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_gzip_file extends smd_crunch_tar_file\r\n{\r\n	function smd_crunch_gzip_file($name)\r\n	{\r\n		$this->smd_crunch_tar_file($name);\r\n		$this->options[\'type\'] = \"gzip\";\r\n	}\r\n\r\n	function create_gzip()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($fp = gzopen($this->options[\'name\'], \"wb{$this->options[\'level\']}\"))\r\n			{\r\n				fseek($this->archive, 0);\r\n				while ($temp = fread($this->archive, 1048576))\r\n					gzwrite($fp, $temp);\r\n				gzclose($fp);\r\n				chdir($pwd);\r\n			}\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = gzencode($this->archive, $this->options[\'level\']);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @gzopen($this->options[\'name\'], \"rb\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_bzip_file extends smd_crunch_tar_file\r\n{\r\n	function smd_crunch_bzip_file($name)\r\n	{\r\n		$this->smd_crunch_tar_file($name);\r\n		$this->options[\'type\'] = \"bzip\";\r\n	}\r\n\r\n	function create_bzip()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($fp = bzopen($this->options[\'name\'], \"wb\"))\r\n			{\r\n				fseek($this->archive, 0);\r\n				while ($temp = fread($this->archive, 1048576))\r\n					bzwrite($fp, $temp);\r\n				bzclose($fp);\r\n				chdir($pwd);\r\n			}\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = bzcompress($this->archive, $this->options[\'level\']);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @bzopen($this->options[\'name\'], \"r\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_zip_file extends smd_crunch_archive\r\n{\r\n	function smd_crunch_zip_file($name)\r\n	{\r\n		$this->smd_crunch_archive($name);\r\n		$this->options[\'type\'] = \"zip\";\r\n	}\r\n\r\n	function create_zip()\r\n	{\r\n		$files = 0;\r\n		$offset = 0;\r\n		$central = \"\";\r\n\r\n		if (!empty ($this->options[\'sfx\']))\r\n			if ($fp = @fopen($this->options[\'sfx\'], \"rb\"))\r\n			{\r\n				$temp = fread($fp, filesize($this->options[\'sfx\']));\r\n				fclose($fp);\r\n				$this->add_data($temp);\r\n				$offset += strlen($temp);\r\n				unset ($temp);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open sfx module from {$this->options[\'sfx\']}.\";\r\n\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($this->files as $current)\r\n		{\r\n			if ($current[\'name\'] == $this->options[\'name\'])\r\n				continue;\r\n\r\n			$timedate = explode(\" \", date(\"Y n j G i s\", $current[\'stat\'][9]));\r\n			$timedate = ($timedate[0] - 1980 << 25) | ($timedate[1] << 21) | ($timedate[2] << 16) |\r\n				($timedate[3] << 11) | ($timedate[4] << 5) | ($timedate[5]);\r\n\r\n			$block = pack(\"VvvvV\", 0x04034b50, 0x000A, 0x0000, (isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate);\r\n\r\n			if ($current[\'stat\'][7] == 0 && $current[\'type\'] == 5)\r\n			{\r\n				$block .= pack(\"VVVvv\", 0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']) + 1, 0x0000);\r\n				$block .= $current[\'name2\'] . \"/\";\r\n				$this->add_data($block);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']) + 1, 0x0000, 0x0000, 0x0000, 0x0000, $current[\'type\'] == 5 ? 0x00000010 : 0x00000000, $offset);\r\n				$central .= $current[\'name2\'] . \"/\";\r\n				$files++;\r\n				$offset += (31 + strlen($current[\'name2\']));\r\n			}\r\n			else if ($current[\'stat\'][7] == 0)\r\n			{\r\n				$block .= pack(\"VVVvv\", 0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']), 0x0000);\r\n				$block .= $current[\'name2\'];\r\n				$this->add_data($block);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']), 0x0000, 0x0000, 0x0000, 0x0000, $current[\'type\'] == 5 ? 0x00000010 : 0x00000000, $offset);\r\n				$central .= $current[\'name2\'];\r\n				$files++;\r\n				$offset += (30 + strlen($current[\'name2\']));\r\n			}\r\n			else if ($fp = @fopen($current[\'name\'], \"rb\"))\r\n			{\r\n				$temp = fread($fp, $current[\'stat\'][7]);\r\n				fclose($fp);\r\n				$crc32 = crc32($temp);\r\n				if (!isset($current[\'method\']) && $this->options[\'method\'] == 1)\r\n				{\r\n					$temp = gzcompress($temp, $this->options[\'level\']);\r\n					$size = strlen($temp) - 6;\r\n					$temp = substr($temp, 2, $size);\r\n				}\r\n				else\r\n					$size = strlen($temp);\r\n				$block .= pack(\"VVVvv\", $crc32, $size, $current[\'stat\'][7], strlen($current[\'name2\']), 0x0000);\r\n				$block .= $current[\'name2\'];\r\n				$this->add_data($block);\r\n				$this->add_data($temp);\r\n				unset ($temp);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					$crc32, $size, $current[\'stat\'][7], strlen($current[\'name2\']), 0x0000, 0x0000, 0x0000, 0x0000, 0x00000000, $offset);\r\n				$central .= $current[\'name2\'];\r\n				$files++;\r\n				$offset += (30 + strlen($current[\'name2\']) + $size);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open file {$current[\'name\']} for reading. It was not added.\";\r\n		}\r\n\r\n		$this->add_data($central);\r\n\r\n		$this->add_data(pack(\"VvvvvVVv\", 0x06054b50, 0x0000, 0x0000, $files, $files, strlen($central), $offset,\r\n			!empty ($this->options[\'comment\']) ? strlen($this->options[\'comment\']) : 0x0000));\r\n\r\n		if (!empty ($this->options[\'comment\']))\r\n			$this->add_data($this->options[\'comment\']);\r\n\r\n		chdir($pwd);\r\n\r\n		return 1;\r\n	}\r\n}\r\n\r\n// 19/08/2011 (v2.663)\r\n// - unzipAll was using double slashes (path//filename) to save files. (thanks to Karen Peyton).\r\n// 09/08/2010 (v2.662)\r\n// - unzipAll parameters fully reviewed and fixed. Thanks Ronny Dreschler and Conor Mac Aoidh.\r\n// 12/05/2010 (v2.661)\r\n// - Fixed E_STRICT notice: \"Only variables should be passed by reference\". Thanks Erik W.\r\n// 24/03/2010 (v2.66)\r\n// - Fixed bug inside unzipAll when dirname is \".\" (thanks to Thorsten Groth)\r\n// - Added character \"´\" to the string conversion table (ex: caixa d´água)\r\n// 27/02/2010\r\n// - Removed PHP4 support (file_put_contents redeclaration).\r\n// 04/12/2009 (v2.65)\r\n// * Added character translation to decode accents and/or special characters.\r\n// 10/11/2009\r\n// * Some security added to avoid malicious ZIP files (relative dirs)\r\n// * unzipAll() will output by default to same folder of the caller script\r\n// 25/09/2009\r\n// - Code optimization to reduce memory usage (uncompress(&$contents))\r\n// 12/07/2009 (2.62)\r\n// - Debug messages are shown only when explicit.\r\n// - New method: getLastError()\r\n\r\n##############################################################\r\n# Class dUnzip2 v2.663\r\n#\r\n#  Author: Alexandre Tedeschi (d)\r\n#  E-Mail: alexandrebr at gmail dot com\r\n#  Londrina - PR / Brazil\r\n#\r\n#  Objective:\r\n#    This class allows programmer to easily unzip files on the fly.\r\n#\r\n#  Requirements:\r\n#    This class requires extension ZLib Enabled. It is default\r\n#    for most site hosts around the world, and for the PHP Win32 dist.\r\n#\r\n#  To do:\r\n#   * Error handling\r\n#   * Write a PHP-Side gzinflate, to completely avoid any external extensions\r\n#   * Write other decompress algorithms\r\n#\r\n#  Methods:\r\n#  * dUnzip2($filename)         - Constructor - Opens $filename\r\n#  * getList([$stopOnFile])     - Retrieve the file list\r\n#  * getExtraInfo($zipfilename) - Retrieve more information about compressed file\r\n#  * getZipInfo([$entry])       - Retrieve ZIP file details.\r\n#  * unzip($zipfilename, [$outfilename, [$applyChmod]]) - Unzip file\r\n#  * unzipAll([$outDir, [$zipDir, [$maintainStructure, [$applyChmod]]]])\r\n#  * close()                    - Close file handler, but keep the list\r\n#  * __destroy()                - Close file handler and release memory\r\n#\r\n#  If you modify this class, or have any ideas to improve it, please contact me!\r\n#  You are allowed to redistribute this class, if you keep my name and contact e-mail on it.\r\n#\r\n#  PLEASE! IF YOU USE THIS CLASS IN ANY OF YOUR PROJECTS, PLEASE LET ME KNOW!\r\n#  If you have problems using it, don\'t think twice before contacting me!\r\n#\r\n##############################################################\r\n\r\nclass smd_crunch_dUnzip2{\r\n	Function getVersion(){\r\n		return \"2.663\";\r\n	}\r\n	// Public\r\n	var $fileName;\r\n	var $lastError;\r\n	var $compressedList; // You will problably use only this one!\r\n	var $centralDirList; // Central dir list... It\'s a kind of \'extra attributes\' for a set of files\r\n	var $endOfCentral;   // End of central dir, contains ZIP Comments\r\n	var $debug;\r\n\r\n	// Private\r\n	var $fh;\r\n	var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n	var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n	var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n\r\n	// Public\r\n	Function smd_crunch_dUnzip2($fileName){\r\n		$this->fileName       = $fileName;\r\n		$this->compressedList = \r\n		$this->centralDirList = \r\n		$this->endOfCentral   = Array();\r\n	}\r\n	\r\n	Function getList($stopOnFile=false){\r\n		if(sizeof($this->compressedList)){\r\n			$this->debugMsg(1, \"Returning already loaded file list.\");\r\n			return $this->compressedList;\r\n		}\r\n		\r\n		// Open file, and set file handler\r\n		$fh = fopen($this->fileName, \"r\");\r\n		$this->fh = &$fh;\r\n		if(!$fh){\r\n			$this->debugMsg(2, \"Failed to load file.\");\r\n			return false;\r\n		}\r\n		\r\n		$this->debugMsg(1, \"Loading list from \'End of Central Dir\' index list...\");\r\n		if(!$this->_loadFileListByEOF($fh, $stopOnFile)){\r\n			$this->debugMsg(1, \"Failed! Trying to load list looking for signatures...\");\r\n			if(!$this->_loadFileListBySignatures($fh, $stopOnFile)){\r\n				$this->debugMsg(1, \"Failed! Could not find any valid header.\");\r\n				$this->debugMsg(2, \"ZIP File is corrupted or empty\");\r\n				return false;\r\n			}\r\n		}\r\n		\r\n		if($this->debug){\r\n			#------- Debug compressedList\r\n			$kkk = 0;\r\n			echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n			foreach($this->compressedList as $fileName=>$item){\r\n				if(!$kkk && $kkk=1){\r\n					echo \"<tr style=\'background: #ADA\'>\";\r\n					foreach($item as $fieldName=>$value)\r\n						echo \"<td>$fieldName</td>\";\r\n					echo \'</tr>\';\r\n				}\r\n				echo \"<tr style=\'background: #CFC\'>\";\r\n				foreach($item as $fieldName=>$value){\r\n					if($fieldName == \'lastmod_datetime\')\r\n						echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n					else\r\n						echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n				}\r\n				echo \"</tr>\";\r\n			}\r\n			echo \"</table>\";\r\n			\r\n			#------- Debug centralDirList\r\n			$kkk = 0;\r\n			if(sizeof($this->centralDirList)){\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n				foreach($this->centralDirList as $fileName=>$item){\r\n					if(!$kkk && $kkk=1){\r\n						echo \"<tr style=\'background: #AAD\'>\";\r\n						foreach($item as $fieldName=>$value)\r\n							echo \"<td>$fieldName</td>\";\r\n						echo \'</tr>\';\r\n					}\r\n					echo \"<tr style=\'background: #CCF\'>\";\r\n					foreach($item as $fieldName=>$value){\r\n						if($fieldName == \'lastmod_datetime\')\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n						else\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n					}\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n			}\r\n		\r\n			#------- Debug endOfCentral\r\n			$kkk = 0;\r\n			if(sizeof($this->endOfCentral)){\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana\' style=\'border: 1px solid #000\'>\";\r\n				echo \"<tr style=\'background: #DAA\'><td colspan=\'2\'>dUnzip - End of file</td></tr>\";\r\n				foreach($this->endOfCentral as $field=>$value){\r\n					echo \"<tr>\";\r\n					echo \"<td style=\'background: #FCC\'>$field</td>\";\r\n					echo \"<td style=\'background: #FDD\'>$value</td>\";\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n			}\r\n		}\r\n		\r\n		return $this->compressedList;\r\n	}\r\n	Function getExtraInfo($compressedFileName){\r\n		return\r\n			isset($this->centralDirList[$compressedFileName])?\r\n			$this->centralDirList[$compressedFileName]:\r\n			false;\r\n	}\r\n	Function getZipInfo($detail=false){\r\n		return $detail?\r\n			$this->endOfCentral[$detail]:\r\n			$this->endOfCentral;\r\n	}\r\n	\r\n	Function unzip($compressedFileName, $targetFileName=false, $applyChmod=0777){\r\n		if(!sizeof($this->compressedList)){\r\n			$this->debugMsg(1, \"Trying to unzip before loading file list... Loading it!\");\r\n			$this->getList(false, $compressedFileName);\r\n		}\r\n		\r\n		$fdetails = &$this->compressedList[$compressedFileName];\r\n		if(!isset($this->compressedList[$compressedFileName])){\r\n			$this->debugMsg(2, \"File \'<b>$compressedFileName</b>\' is not compressed in the zip.\");\r\n			return false;\r\n		}\r\n		if(substr($compressedFileName, -1) == \"/\"){\r\n			$this->debugMsg(2, \"Trying to unzip a folder name \'<b>$compressedFileName</b>\'.\");\r\n			return false;\r\n		}\r\n		if(!$fdetails[\'uncompressed_size\']){\r\n			$this->debugMsg(1, \"File \'<b>$compressedFileName</b>\' is empty.\");\r\n			return $targetFileName?\r\n				file_put_contents($targetFileName, \"\"):\r\n				\"\";\r\n		}\r\n		\r\n		fseek($this->fh, $fdetails[\'contents-startOffset\']);\r\n		$toUncompress = fread($this->fh, $fdetails[\'compressed_size\']);\r\n		$ret = $this->uncompress(\r\n				$toUncompress,\r\n				$fdetails[\'compression_method\'],\r\n				$fdetails[\'uncompressed_size\'],\r\n				$targetFileName\r\n			);\r\n		unset($toUncompress);\r\n		if($applyChmod && $targetFileName)\r\n			chmod($targetFileName, 0777);\r\n\r\n		return $ret;\r\n	}\r\n	Function unzipAll($targetDir=false, $baseDir=\"\", $maintainStructure=true, $applyChmod=0777){\r\n		if($targetDir === false)\r\n			$targetDir = dirname($_SERVER[\'SCRIPT_FILENAME\']).\"/\";\r\n		\r\n		if(substr($targetDir, -1) == \"/\")\r\n			$targetDir = substr($targetDir, 0, -1);\r\n		\r\n		$lista = $this->getList();\r\n		if(sizeof($lista)) foreach($lista as $fileName=>$trash){\r\n			$dirname  = dirname($fileName);\r\n			$outDN    = \"$targetDir/$dirname\";\r\n			\r\n			if(substr($dirname, 0, strlen($baseDir)) != $baseDir)\r\n				continue;\r\n			\r\n			if(!is_dir($outDN) && $maintainStructure){\r\n				$str = \"\";\r\n				$folders = explode(\"/\", $dirname);\r\n				foreach($folders as $folder){\r\n					$str = $str?\"$str/$folder\":$folder;\r\n					if(!is_dir(\"$targetDir/$str\")){\r\n						$this->debugMsg(1, \"Creating folder: $targetDir/$str\");\r\n						mkdir(\"$targetDir/$str\");\r\n						if($applyChmod)\r\n							chmod(\"$targetDir/$str\", $applyChmod);\r\n					}\r\n				}\r\n			}\r\n			if(substr($fileName, -1, 1) == \"/\")\r\n				continue;\r\n			\r\n			$maintainStructure?\r\n				$this->unzip($fileName, \"$targetDir/$fileName\", $applyChmod):\r\n				$this->unzip($fileName, \"$targetDir/\".basename($fileName), $applyChmod);\r\n		}\r\n	}\r\n	\r\n	Function close(){     // Free the file resource\r\n		if($this->fh)\r\n			fclose($this->fh);\r\n	}\r\n	Function __destroy(){ \r\n		$this->close();\r\n	}\r\n	\r\n	// Private (you should NOT call these methods):\r\n	Function uncompress(&$content, $mode, $uncompressedSize, $targetFileName=false){\r\n		switch($mode){\r\n			case 0:\r\n				// Not compressed\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, $content):\r\n					$content;\r\n			case 1:\r\n				$this->debugMsg(2, \"Shrunk mode is not supported... yet?\");\r\n				return false;\r\n			case 2:\r\n			case 3:\r\n			case 4:\r\n			case 5:\r\n				$this->debugMsg(2, \"Compression factor \".($mode-1).\" is not supported... yet?\");\r\n				return false;\r\n			case 6:\r\n				$this->debugMsg(2, \"Implode is not supported... yet?\");\r\n				return false;\r\n			case 7:\r\n				$this->debugMsg(2, \"Tokenizing compression algorithm is not supported... yet?\");\r\n				return false;\r\n			case 8:\r\n				// Deflate\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, gzinflate($content, $uncompressedSize)):\r\n					gzinflate($content, $uncompressedSize);\r\n			case 9:\r\n				$this->debugMsg(2, \"Enhanced Deflating is not supported... yet?\");\r\n				return false;\r\n			case 10:\r\n				$this->debugMsg(2, \"PKWARE Date Compression Library Impoloding is not supported... yet?\");\r\n				return false;\r\n			case 12:\r\n				// Bzip2\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, bzdecompress($content)):\r\n					bzdecompress($content);\r\n			case 18:\r\n				$this->debugMsg(2, \"IBM TERSE is not supported... yet?\");\r\n				return false;\r\n			default:\r\n				$this->debugMsg(2, \"Unknown uncompress method: $mode\");\r\n				return false;\r\n		}\r\n	}\r\n	Function debugMsg($level, $string){\r\n		if($this->debug){\r\n			if($level == 1)\r\n				echo \"<b style=\'color: #777\'>dUnzip2:</b> $string<br>\";\r\n			\r\n			if($level == 2)\r\n				echo \"<b style=\'color: #F00\'>dUnzip2:</b> $string<br>\";\r\n		}\r\n		$this->lastError = $string;\r\n	}\r\n	Function getLastError(){\r\n		return $this->lastError;\r\n	}\r\n	\r\n	Function _loadFileListByEOF(&$fh, $stopOnFile=false){\r\n		// Check if there\'s a valid Central Dir signature.\r\n		// Let\'s consider a file comment smaller than 1024 characters...\r\n		// Actually, it length can be 65536.. But we\'re not going to support it.\r\n		\r\n		for($x = 0; $x < 1024; $x++){\r\n			fseek($fh, -22-$x, SEEK_END);\r\n			\r\n			$signature = fread($fh, 4);\r\n			if($signature == $this->dirSignatureE){\r\n				// If found EOF Central Dir\r\n				$eodir[\'disk_number_this\']   = unpack(\"v\", fread($fh, 2)); // number of this disk\r\n				$eodir[\'disk_number\']        = unpack(\"v\", fread($fh, 2)); // number of the disk with the start of the central directory\r\n				$eodir[\'total_entries_this\'] = unpack(\"v\", fread($fh, 2)); // total number of entries in the central dir on this disk\r\n				$eodir[\'total_entries\']      = unpack(\"v\", fread($fh, 2)); // total number of entries in\r\n				$eodir[\'size_of_cd\']         = unpack(\"V\", fread($fh, 4)); // size of the central directory\r\n				$eodir[\'offset_start_cd\']    = unpack(\"V\", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number\r\n				$zipFileCommentLenght        = unpack(\"v\", fread($fh, 2)); // zipfile comment length\r\n				$eodir[\'zipfile_comment\']    = $zipFileCommentLenght[1]?fread($fh, $zipFileCommentLenght[1]):\'\'; // zipfile comment\r\n				$this->endOfCentral = Array(\r\n					\'disk_number_this\'=>$eodir[\'disk_number_this\'][1],\r\n					\'disk_number\'=>$eodir[\'disk_number\'][1],\r\n					\'total_entries_this\'=>$eodir[\'total_entries_this\'][1],\r\n					\'total_entries\'=>$eodir[\'total_entries\'][1],\r\n					\'size_of_cd\'=>$eodir[\'size_of_cd\'][1],\r\n					\'offset_start_cd\'=>$eodir[\'offset_start_cd\'][1],\r\n					\'zipfile_comment\'=>$eodir[\'zipfile_comment\'],\r\n				);\r\n				\r\n				// Then, load file list\r\n				fseek($fh, $this->endOfCentral[\'offset_start_cd\']);\r\n				$signature = fread($fh, 4);\r\n				\r\n				while($signature == $this->dirSignature){\r\n					$dir[\'version_madeby\']      = unpack(\"v\", fread($fh, 2)); // version made by\r\n					$dir[\'version_needed\']      = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n					$dir[\'general_bit_flag\']    = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n					$dir[\'compression_method\']  = unpack(\"v\", fread($fh, 2)); // compression method\r\n					$dir[\'lastmod_time\']        = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n					$dir[\'lastmod_date\']        = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n					$dir[\'crc-32\']              = fread($fh, 4);              // crc-32\r\n					$dir[\'compressed_size\']     = unpack(\"V\", fread($fh, 4)); // compressed size\r\n					$dir[\'uncompressed_size\']   = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n					$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n					$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n					$fileCommentLength          = unpack(\"v\", fread($fh, 2)); // file comment length\r\n					$dir[\'disk_number_start\']   = unpack(\"v\", fread($fh, 2)); // disk number start\r\n					$dir[\'internal_attributes\'] = unpack(\"v\", fread($fh, 2)); // internal file attributes-byte1\r\n					$dir[\'external_attributes1\']= unpack(\"v\", fread($fh, 2)); // external file attributes-byte2\r\n					$dir[\'external_attributes2\']= unpack(\"v\", fread($fh, 2)); // external file attributes\r\n					$dir[\'relative_offset\']     = unpack(\"V\", fread($fh, 4)); // relative offset of local header\r\n					$dir[\'file_name\']           = fread($fh, $fileNameLength[1]);                             // filename\r\n					$dir[\'extra_field\']         = $extraFieldLength[1] ?fread($fh, $extraFieldLength[1]) :\'\'; // extra field\r\n					$dir[\'file_comment\']        = $fileCommentLength[1]?fread($fh, $fileCommentLength[1]):\'\'; // file comment			\r\n					\r\n					// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n					$BINlastmod_date = str_pad(decbin($dir[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n					$BINlastmod_time = str_pad(decbin($dir[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n					$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n					$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n					$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n					$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n					$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n					$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));	\r\n					\r\n					// Some protection agains attacks...\r\n					$dir[\'file_name\']     = $this->_decodeFilename($dir[\'file_name\']);\r\n					if(!$dir[\'file_name\'] = $this->_protect($dir[\'file_name\']))\r\n						continue;\r\n					\r\n					$this->centralDirList[$dir[\'file_name\']] = Array(\r\n						\'version_madeby\'=>$dir[\'version_madeby\'][1],\r\n						\'version_needed\'=>$dir[\'version_needed\'][1],\r\n						\'general_bit_flag\'=>str_pad(decbin($dir[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n						\'compression_method\'=>$dir[\'compression_method\'][1],\r\n						\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n						\'crc-32\'            =>str_pad(dechex(ord($dir[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n						\'compressed_size\'=>$dir[\'compressed_size\'][1],\r\n						\'uncompressed_size\'=>$dir[\'uncompressed_size\'][1],\r\n						\'disk_number_start\'=>$dir[\'disk_number_start\'][1],\r\n						\'internal_attributes\'=>$dir[\'internal_attributes\'][1],\r\n						\'external_attributes1\'=>$dir[\'external_attributes1\'][1],\r\n						\'external_attributes2\'=>$dir[\'external_attributes2\'][1],\r\n						\'relative_offset\'=>$dir[\'relative_offset\'][1],\r\n						\'file_name\'=>$dir[\'file_name\'],\r\n						\'extra_field\'=>$dir[\'extra_field\'],\r\n						\'file_comment\'=>$dir[\'file_comment\'],\r\n					);\r\n					$signature = fread($fh, 4);\r\n				}\r\n				\r\n				// If loaded centralDirs, then try to identify the offsetPosition of the compressed data.\r\n				if($this->centralDirList) foreach($this->centralDirList as $filename=>$details){\r\n					$i = $this->_getFileHeaderInformation($fh, $details[\'relative_offset\']);\r\n					$this->compressedList[$filename][\'file_name\']          = $filename;\r\n					$this->compressedList[$filename][\'compression_method\'] = $details[\'compression_method\'];\r\n					$this->compressedList[$filename][\'version_needed\']     = $details[\'version_needed\'];\r\n					$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n					$this->compressedList[$filename][\'crc-32\']             = $details[\'crc-32\'];\r\n					$this->compressedList[$filename][\'compressed_size\']    = $details[\'compressed_size\'];\r\n					$this->compressedList[$filename][\'uncompressed_size\']  = $details[\'uncompressed_size\'];\r\n					$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n					$this->compressedList[$filename][\'extra_field\']        = $i[\'extra_field\'];\r\n					$this->compressedList[$filename][\'contents-startOffset\']=$i[\'contents-startOffset\'];\r\n					if(strtolower($stopOnFile) == strtolower($filename))\r\n						break;\r\n				}\r\n				return true;\r\n			}\r\n		}\r\n		return false;\r\n	}\r\n	Function _loadFileListBySignatures(&$fh, $stopOnFile=false){\r\n		fseek($fh, 0);\r\n		\r\n		$return = false;\r\n		for(;;){\r\n			$details = $this->_getFileHeaderInformation($fh);\r\n			if(!$details){\r\n				$this->debugMsg(1, \"Invalid signature. Trying to verify if is old style Data Descriptor...\");\r\n				fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again)\r\n				$details = $this->_getFileHeaderInformation($fh);\r\n			}\r\n			if(!$details){\r\n				$this->debugMsg(1, \"Still invalid signature. Probably reached the end of the file.\");\r\n				break;\r\n			}\r\n			$filename = $details[\'file_name\'];\r\n			$this->compressedList[$filename] = $details;\r\n			$return = true;\r\n			if(strtolower($stopOnFile) == strtolower($filename))\r\n				break;\r\n		}\r\n		\r\n		return $return;\r\n	}\r\n	Function _getFileHeaderInformation(&$fh, $startOffset=false){\r\n		if($startOffset !== false)\r\n			fseek($fh, $startOffset);\r\n		\r\n		$signature = fread($fh, 4);\r\n		if($signature == $this->zipSignature){\r\n			# $this->debugMsg(1, \"Zip Signature!\");\r\n			\r\n			// Get information about the zipped file\r\n			$file[\'version_needed\']     = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n			$file[\'general_bit_flag\']   = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n			$file[\'compression_method\'] = unpack(\"v\", fread($fh, 2)); // compression method\r\n			$file[\'lastmod_time\']       = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n			$file[\'lastmod_date\']       = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n			$file[\'crc-32\']             = fread($fh, 4);              // crc-32\r\n			$file[\'compressed_size\']    = unpack(\"V\", fread($fh, 4)); // compressed size\r\n			$file[\'uncompressed_size\']  = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n			$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n			$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n			$file[\'file_name\']          = fread($fh, $fileNameLength[1]); // filename\r\n			$file[\'extra_field\']        = $extraFieldLength[1]?fread($fh, $extraFieldLength[1]):\'\'; // extra field\r\n			$file[\'contents-startOffset\']= ftell($fh);\r\n			\r\n			// Bypass the whole compressed contents, and look for the next file\r\n			fseek($fh, $file[\'compressed_size\'][1], SEEK_CUR);\r\n			\r\n			// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n			$BINlastmod_date = str_pad(decbin($file[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n			$BINlastmod_time = str_pad(decbin($file[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n			$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n			$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n			$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n			$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n			$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n			\r\n			// Some protection agains attacks...\r\n			$file[\'file_name\']     = $this->_decodeFilename($file[\'file_name\']);\r\n			if(!$file[\'file_name\'] = $this->_protect($file[\'file_name\']))\r\n				return false;\r\n			\r\n			// Mount file table\r\n			$i = Array(\r\n				\'file_name\'         =>$file[\'file_name\'],\r\n				\'compression_method\'=>$file[\'compression_method\'][1],\r\n				\'version_needed\'    =>$file[\'version_needed\'][1],\r\n				\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n				\'crc-32\'            =>str_pad(dechex(ord($file[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n				\'compressed_size\'   =>$file[\'compressed_size\'][1],\r\n				\'uncompressed_size\' =>$file[\'uncompressed_size\'][1],\r\n				\'extra_field\'       =>$file[\'extra_field\'],\r\n				\'general_bit_flag\'  =>str_pad(decbin($file[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n				\'contents-startOffset\'=>$file[\'contents-startOffset\']\r\n			);\r\n			return $i;\r\n		}\r\n		return false;\r\n	}\r\n	\r\n	Function _decodeFilename($filename){\r\n		$from = \"\\xb7\\xb5\\xb6\\xc7\\x8e\\x8f\\x92\\x80\\xd4\\x90\\xd2\\xd3\\xde\\xd6\\xd7\\xd8\\xd1\\xa5\\xe3\\xe0\".\r\n		        \"\\xe2\\xe5\\x99\\x9d\\xeb\\xe9\\xea\\x9a\\xed\\xe8\\xe1\\x85\\xa0\\x83\\xc6\\x84\\x86\\x91\\x87\\x8a\".\r\n				\"\\x82\\x88\\x89\\x8d\\xa1\\x8c\\x8b\\xd0\\xa4\\x95\\xa2\\x93\\xe4\\x94\\x9b\\x97\\xa3\\x96\\xec\\xe7\".\r\n				\"\\x98ï\";\r\n		$to   = \"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýþÿ´\";\r\n\r\n		return strtr($filename, $from, $to);\r\n	}\r\n	Function _protect($fullPath){\r\n		// Known hack-attacks (filename like):\r\n		//   /home/usr\r\n		//   ../../home/usr\r\n		//   folder/../../../home/usr\r\n		//   sample/(x0)../home/usr\r\n		\r\n		$fullPath = strtr($fullPath, \":*<>|\\\"\\x0\\\\\", \"......./\");\r\n		while($fullPath[0] == \"/\")\r\n			$fullPath = substr($fullPath, 1);\r\n		\r\n		if(substr($fullPath, -1) == \"/\"){\r\n			$base     = \'\';\r\n			$fullPath = substr($fullPath, 0, -1);\r\n		}\r\n		else{\r\n			$base     = basename($fullPath);\r\n			$fullPath = dirname($fullPath);\r\n		}\r\n		\r\n		$parts   = explode(\"/\", $fullPath);\r\n		$lastIdx = false;\r\n		foreach($parts as $idx=>$part){\r\n			if($part == \".\")\r\n				unset($parts[$idx]);\r\n			elseif($part == \"..\"){\r\n				unset($parts[$idx]);\r\n				if($lastIdx !== false){\r\n					unset($parts[$lastIdx]);\r\n				}\r\n			}\r\n			elseif($part === \'\'){\r\n				unset($parts[$idx]);\r\n			}\r\n			else{\r\n				$lastIdx = $idx;\r\n			}\r\n		}\r\n		\r\n		$fullPath = sizeof($parts)?implode(\"/\", $parts).\"/\":\"\";\r\n		return $fullPath.$base;\r\n	}\r\n}\r\n\r\nclass smd_crunch_dZip{\r\n	var $filename;\r\n	var $overwrite;\r\n\r\n	var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n	var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n	var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n	var $files_count  = 0;\r\n	var $fh;\r\n\r\n	Function smd_crunch_dZip($filename, $overwrite=true){\r\n		$this->filename  = $filename;\r\n		$this->overwrite = $overwrite;\r\n	}\r\n	Function addDir($dirname, $fileComments=\'\'){\r\n		if(substr($dirname, -1) != \'/\')\r\n			$dirname .= \'/\';\r\n		$this->addFile(false, $dirname, $fileComments);\r\n	}\r\n	Function addFile($filename, $cfilename, $fileComments=\'\', $data=false){\r\n		if(!($fh = &$this->fh))\r\n			$fh = fopen($this->filename, $this->overwrite?\'wb\':\'a+b\');\r\n		\r\n		// $filename can be a local file OR the data wich will be compressed\r\n		if(substr($cfilename, -1)==\'/\'){\r\n			$details[\'uncsize\'] = 0;\r\n			$data = \'\';\r\n		}\r\n		elseif(file_exists($filename)){\r\n			$details[\'uncsize\'] = filesize($filename);\r\n			$data = file_get_contents($filename);\r\n		}\r\n		elseif($filename){\r\n			echo \"<b>Cannot add $filename. File not found</b><br>\";\r\n			return false;\r\n		}\r\n		else{\r\n			$details[\'uncsize\'] = strlen($filename);\r\n			// DATA is given.. use it! :|\r\n		}\r\n\r\n		// if data to compress is too small, just store it\r\n		if($details[\'uncsize\'] < 256){\r\n			$details[\'comsize\'] = $details[\'uncsize\'];\r\n			$details[\'vneeded\'] = 10;\r\n			$details[\'cmethod\'] = 0;\r\n			$zdata = &$data;\r\n		}\r\n		else{ // otherwise, compress it\r\n			$zdata = gzcompress($data);\r\n			$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)\r\n			$details[\'comsize\'] = strlen($zdata);\r\n			$details[\'vneeded\'] = 10;\r\n			$details[\'cmethod\'] = 8;\r\n		}\r\n		\r\n		$details[\'bitflag\'] = 0;\r\n		$details[\'crc_32\']  = crc32($data);\r\n		\r\n		// Convert date and time to DOS Format, and set then\r\n		$lastmod_timeS  = str_pad(decbin(date(\'s\')>=32?date(\'s\')-32:date(\'s\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_timeM  = str_pad(decbin(date(\'i\')), 6, \'0\', STR_PAD_LEFT);\r\n		$lastmod_timeH  = str_pad(decbin(date(\'H\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateD  = str_pad(decbin(date(\'d\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateM  = str_pad(decbin(date(\'m\')), 4, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateY  = str_pad(decbin(date(\'Y\')-1980), 7, \'0\', STR_PAD_LEFT);\r\n		\r\n		# echo \"ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (\".date(\"s H H\").\")\\n\";\r\n		# echo \"ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (\".date(\"d m Y\").\")\\n\";\r\n		$details[\'modtime\'] = bindec(\"$lastmod_timeH$lastmod_timeM$lastmod_timeS\");\r\n		$details[\'moddate\'] = bindec(\"$lastmod_dateY$lastmod_dateM$lastmod_dateD\");\r\n		\r\n		$details[\'offset\'] = ftell($fh);\r\n		fwrite($fh, $this->zipSignature);\r\n		fwrite($fh, pack(\'s\', $details[\'vneeded\'])); // version_needed\r\n		fwrite($fh, pack(\'s\', $details[\'bitflag\'])); // general_bit_flag\r\n		fwrite($fh, pack(\'s\', $details[\'cmethod\'])); // compression_method\r\n		fwrite($fh, pack(\'s\', $details[\'modtime\'])); // lastmod_time\r\n		fwrite($fh, pack(\'s\', $details[\'moddate\'])); // lastmod_date\r\n		fwrite($fh, pack(\'V\', $details[\'crc_32\']));  // crc-32\r\n		fwrite($fh, pack(\'I\', $details[\'comsize\'])); // compressed_size\r\n		fwrite($fh, pack(\'I\', $details[\'uncsize\'])); // uncompressed_size\r\n		fwrite($fh, pack(\'s\', strlen($cfilename)));   // file_name_length\r\n		fwrite($fh, pack(\'s\', 0));  // extra_field_length\r\n		fwrite($fh, $cfilename);    // file_name\r\n		// ignoring extra_field\r\n		fwrite($fh, $zdata);\r\n		\r\n		// Append it to central dir\r\n		$details[\'external_attributes\']  = (substr($cfilename, -1)==\'/\'&&!$zdata)?16:32; // Directory or file name\r\n		$details[\'comments\']             = $fileComments;\r\n		$this->appendCentralDir($cfilename, $details);\r\n		$this->files_count++;\r\n	}\r\n	Function setExtra($filename, $property, $value){\r\n		$this->centraldirs[$filename][$property] = $value;\r\n	}\r\n	Function save($zipComments=\'\'){\r\n		if(!($fh = &$this->fh))\r\n			$fh = fopen($this->filename, $this->overwrite?\'w\':\'a+\');\r\n		\r\n		$cdrec = \"\";\r\n		foreach($this->centraldirs as $filename=>$cd){\r\n			$cdrec .= $this->dirSignature;\r\n			$cdrec .= \"\\x0\\x0\";                  // version made by\r\n			$cdrec .= pack(\'v\', $cd[\'vneeded\']); // version needed to extract\r\n			$cdrec .= \"\\x0\\x0\";                  // general bit flag\r\n			$cdrec .= pack(\'v\', $cd[\'cmethod\']); // compression method\r\n			$cdrec .= pack(\'v\', $cd[\'modtime\']); // lastmod time\r\n			$cdrec .= pack(\'v\', $cd[\'moddate\']); // lastmod date\r\n			$cdrec .= pack(\'V\', $cd[\'crc_32\']);  // crc32\r\n			$cdrec .= pack(\'V\', $cd[\'comsize\']); // compressed filesize\r\n			$cdrec .= pack(\'V\', $cd[\'uncsize\']); // uncompressed filesize\r\n			$cdrec .= pack(\'v\', strlen($filename)); // file comment length\r\n			$cdrec .= pack(\'v\', 0);                // extra field length\r\n			$cdrec .= pack(\'v\', strlen($cd[\'comments\'])); // file comment length\r\n			$cdrec .= pack(\'v\', 0); // disk number start\r\n			$cdrec .= pack(\'v\', 0); // internal file attributes\r\n			$cdrec .= pack(\'V\', $cd[\'external_attributes\']); // internal file attributes\r\n			$cdrec .= pack(\'V\', $cd[\'offset\']); // relative offset of local header\r\n			$cdrec .= $filename;\r\n			$cdrec .= $cd[\'comments\'];\r\n		}\r\n		$before_cd = ftell($fh);\r\n		fwrite($fh, $cdrec);\r\n\r\n		// end of central dir\r\n		fwrite($fh, $this->dirSignatureE);\r\n		fwrite($fh, pack(\'v\', 0)); // number of this disk\r\n		fwrite($fh, pack(\'v\', 0)); // number of the disk with the start of the central directory\r\n		fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries \"on this disk\" \r\n		fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries overall \r\n		fwrite($fh, pack(\'V\', strlen($cdrec)));     // size of central dir \r\n		fwrite($fh, pack(\'V\', $before_cd));         // offset to start of central dir\r\n		fwrite($fh, pack(\'v\', strlen($zipComments))); // .zip file comment length\r\n		fwrite($fh, $zipComments);\r\n		\r\n		fclose($fh);\r\n	}\r\n	\r\n	// Private\r\n	Function appendCentralDir($filename,$properties){\r\n		$this->centraldirs[$filename] = $properties;\r\n	}\r\n}\r\n', '/**\r\n * smd_crunchers\r\n *\r\n * A Textpattern CMS plugin library for standard compression/decompression algorithms\r\n *  -> zip/unzip\r\n *  -> tar\r\n *  -> gzip\r\n *  -> bzip2\r\n *\r\n * @author Devin Doucette\r\n * @author Alexandre Tedeschi\r\n * @author Stef Dawson\r\n * @link   http://shaw.ca/\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// ------------------------\r\nfunction smd_crunch_capabilities($type=\'compress\') {\r\n	$smd_crunchers = array();\r\n\r\n	// Bi-directional class\r\n	if (class_exists(\'smd_crunch_tar_file\')) {\r\n		$smd_crunchers[] = \'tar\';\r\n	}\r\n\r\n	if ($type == \'compress\') {\r\n		// Available compressors\r\n		if (function_exists(\'gzcompress\') && function_exists(\'gzopen\') && function_exists(\'gzwrite\')) {\r\n			if (class_exists(\'smd_crunch_gzip_file\')) {\r\n				$smd_crunchers[] = \'gzip\';\r\n			}\r\n			if (class_exists(\'smd_crunch_zip_file\')) {\r\n				$smd_crunchers[] = \'zip\';\r\n			}\r\n		}\r\n		if (function_exists(\'bzcompress\') && function_exists(\'bzopen\') && function_exists(\'bzwrite\')) {\r\n			if (class_exists(\'smd_crunch_bzip_file\')) {\r\n				$smd_crunchers[] = \'bzip2\';\r\n			}\r\n		}\r\n	} else {\r\n		// Available decompressors\r\n		if (function_exists(\'gzinflate\')) {\r\n			if (class_exists(\'smd_crunch_gzip_file\')) {\r\n				$smd_crunchers[] = \'gzip\';\r\n			}\r\n			if (class_exists(\'smd_crunch_dUnzip2\')) {\r\n				$smd_crunchers[] = \'zip\';\r\n			}\r\n		}\r\n		if (function_exists(\'bzdecompress\')) {\r\n			if (class_exists(\'smd_crunch_bzip_file\')) {\r\n				$smd_crunchers[] = \'bzip2\';\r\n			}\r\n		}\r\n	}\r\n	return $smd_crunchers;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_mkdir_recursive($path, $mode = 0755) {\r\n	if(@mkdir($path, $mode) or file_exists($path)) return true;\r\n	return (smd_mkdir_recursive(dirname($path), $mode) and mkdir($path, $mode));\r\n}\r\n\r\n/*--------------------------------------------------\r\n | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.1\r\n | By Devin Doucette\r\n | Copyright (c) 2005 Devin Doucette\r\n | Email: darksnoopy@shaw.ca\r\n +--------------------------------------------------\r\n | Email bugs/suggestions to darksnoopy@shaw.ca\r\n +--------------------------------------------------\r\n | This script has been created and released under\r\n | the GNU GPL and is free to use and redistribute\r\n | only if this copyright statement is not removed\r\n +--------------------------------------------------*/\r\n\r\nclass smd_crunch_archive\r\n{\r\n	function smd_crunch_archive($name)\r\n	{\r\n		$this->options = array (\r\n			\'basedir\' => \".\",\r\n			\'name\' => $name,\r\n			\'prepend\' => \"\",\r\n			\'inmemory\' => 0,\r\n			\'overwrite\' => 0,\r\n			\'recurse\' => 1,\r\n			\'storepaths\' => 1,\r\n			\'followlinks\' => 0,\r\n			\'level\' => 3,\r\n			\'method\' => 1,\r\n			\'sfx\' => \"\",\r\n			\'type\' => \"\",\r\n			\'comment\' => \"\"\r\n		);\r\n		$this->files = array ();\r\n		$this->exclude = array ();\r\n		$this->storeonly = array ();\r\n		$this->error = array ();\r\n	}\r\n\r\n	function set_options($options)\r\n	{\r\n		foreach ($options as $key => $value)\r\n			$this->options[$key] = $value;\r\n		if (!empty ($this->options[\'basedir\']))\r\n		{\r\n			$this->options[\'basedir\'] = str_replace(\"\\\\\", \"/\", $this->options[\'basedir\']);\r\n			$this->options[\'basedir\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'basedir\']);\r\n			$this->options[\'basedir\'] = preg_replace(\"/\\/$/\", \"\", $this->options[\'basedir\']);\r\n		}\r\n		if (!empty ($this->options[\'name\']))\r\n		{\r\n			$this->options[\'name\'] = str_replace(\"\\\\\", \"/\", $this->options[\'name\']);\r\n			$this->options[\'name\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'name\']);\r\n		}\r\n		if (!empty ($this->options[\'prepend\']))\r\n		{\r\n			$this->options[\'prepend\'] = str_replace(\"\\\\\", \"/\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/^(\\.*\\/+)+/\", \"\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/\\/+/\", \"/\", $this->options[\'prepend\']);\r\n			$this->options[\'prepend\'] = preg_replace(\"/\\/$/\", \"\", $this->options[\'prepend\']) . \"/\";\r\n		}\r\n	}\r\n\r\n	function create_archive()\r\n	{\r\n		$this->make_list();\r\n\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($this->options[\'overwrite\'] == 0 && file_exists($this->options[\'name\'] . ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\" ? \".tmp\" : \"\")))\r\n			{\r\n				$this->error[] = \"File {$this->options[\'name\']} already exists.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n			else if ($this->archive = @fopen($this->options[\'name\'] . ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\" ? \".tmp\" : \"\"), \"wb+\"))\r\n				chdir($pwd);\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = \"\";\r\n\r\n		switch ($this->options[\'type\'])\r\n		{\r\n		case \"zip\":\r\n			if (!$this->create_zip())\r\n			{\r\n				$this->error[] = \"Could not create zip file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"bzip\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n			if (!$this->create_bzip())\r\n			{\r\n				$this->error[] = \"Could not create bzip2 file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"gzip\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n			if (!$this->create_gzip())\r\n			{\r\n				$this->error[] = \"Could not create gzip file.\";\r\n				return 0;\r\n			}\r\n			break;\r\n		case \"tar\":\r\n			if (!$this->create_tar())\r\n			{\r\n				$this->error[] = \"Could not create tar file.\";\r\n				return 0;\r\n			}\r\n		}\r\n\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			fclose($this->archive);\r\n			if ($this->options[\'type\'] == \"gzip\" || $this->options[\'type\'] == \"bzip\")\r\n				unlink($this->options[\'basedir\'] . \"/\" . $this->options[\'name\'] . \".tmp\");\r\n		}\r\n	}\r\n\r\n	function add_data($data)\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n			fwrite($this->archive, $data);\r\n		else\r\n			$this->archive .= $data;\r\n	}\r\n\r\n	function make_list()\r\n	{\r\n		if (!empty ($this->exclude))\r\n			foreach ($this->files as $key => $value)\r\n				foreach ($this->exclude as $current)\r\n					if ($value[\'name\'] == $current[\'name\'])\r\n						unset ($this->files[$key]);\r\n		if (!empty ($this->storeonly))\r\n			foreach ($this->files as $key => $value)\r\n				foreach ($this->storeonly as $current)\r\n					if ($value[\'name\'] == $current[\'name\'])\r\n						$this->files[$key][\'method\'] = 0;\r\n		unset ($this->exclude, $this->storeonly);\r\n	}\r\n\r\n	function add_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->files[] = $current;\r\n	}\r\n\r\n	function exclude_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->exclude[] = $current;\r\n	}\r\n\r\n	function store_files($list)\r\n	{\r\n		$temp = $this->list_files($list);\r\n		foreach ($temp as $current)\r\n			$this->storeonly[] = $current;\r\n	}\r\n\r\n	function list_files($list)\r\n	{\r\n		if (!is_array ($list))\r\n		{\r\n			$temp = $list;\r\n			$list = array ($temp);\r\n			unset ($temp);\r\n		}\r\n\r\n		$files = array ();\r\n\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($list as $current)\r\n		{\r\n			$current = str_replace(\"\\\\\", \"/\", $current);\r\n			$current = preg_replace(\"/\\/+/\", \"/\", $current);\r\n			$current = preg_replace(\"/\\/$/\", \"\", $current);\r\n			if (strstr($current, \"*\"))\r\n			{\r\n				$regex = preg_replace(\"/([\\\\\\^\\$\\.\\[\\]\\|\\(\\)\\?\\+\\{\\}\\/])/\", \"\\\\\\\\\\\\1\", $current);\r\n				$regex = str_replace(\"*\", \".*\", $regex);\r\n				$dir = strstr($current, \"/\") ? substr($current, 0, strrpos($current, \"/\")) : \".\";\r\n				$temp = $this->parse_dir($dir);\r\n				foreach ($temp as $current2)\r\n					if (preg_match(\"/^{$regex}$/i\", $current2[\'name\']))\r\n						$files[] = $current2;\r\n				unset ($regex, $dir, $temp, $current);\r\n			}\r\n			else if (@is_dir($current))\r\n			{\r\n				$temp = $this->parse_dir($current);\r\n				foreach ($temp as $file)\r\n					$files[] = $file;\r\n				unset ($temp, $file);\r\n			}\r\n			else if (@file_exists($current))\r\n				$files[] = array (\'name\' => $current, \'name2\' => $this->options[\'prepend\'] .\r\n					preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($current, \"/\")) ?\r\n					substr($current, strrpos($current, \"/\") + 1) : $current),\r\n					\'type\' => @is_link($current) && $this->options[\'followlinks\'] == 0 ? 2 : 0,\r\n					\'ext\' => substr($current, strrpos($current, \".\")), \'stat\' => stat($current));\r\n		}\r\n\r\n		chdir($pwd);\r\n\r\n		unset ($current, $pwd);\r\n\r\n		usort($files, array (\"smd_crunch_archive\", \"sort_files\"));\r\n\r\n		return $files;\r\n	}\r\n\r\n	function parse_dir($dirname)\r\n	{\r\n		if ($this->options[\'storepaths\'] == 1 && !preg_match(\"/^(\\.+\\/*)+$/\", $dirname))\r\n			$files = array (array (\'name\' => $dirname, \'name2\' => $this->options[\'prepend\'] .\r\n				preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($dirname, \"/\")) ?\r\n				substr($dirname, strrpos($dirname, \"/\") + 1) : $dirname), \'type\' => 5, \'stat\' => stat($dirname)));\r\n		else\r\n			$files = array ();\r\n		$dir = @opendir($dirname);\r\n\r\n		while ($file = @readdir($dir))\r\n		{\r\n			$fullname = $dirname . \"/\" . $file;\r\n			if ($file == \".\" || $file == \"..\")\r\n				continue;\r\n			else if (@is_dir($fullname))\r\n			{\r\n				if (empty ($this->options[\'recurse\']))\r\n					continue;\r\n				$temp = $this->parse_dir($fullname);\r\n				foreach ($temp as $file2)\r\n					$files[] = $file2;\r\n			}\r\n			else if (@file_exists($fullname))\r\n				$files[] = array (\'name\' => $fullname, \'name2\' => $this->options[\'prepend\'] .\r\n					preg_replace(\"/(\\.+\\/+)+/\", \"\", ($this->options[\'storepaths\'] == 0 && strstr($fullname, \"/\")) ?\r\n					substr($fullname, strrpos($fullname, \"/\") + 1) : $fullname),\r\n					\'type\' => @is_link($fullname) && $this->options[\'followlinks\'] == 0 ? 2 : 0,\r\n					\'ext\' => substr($file, strrpos($file, \".\")), \'stat\' => stat($fullname));\r\n		}\r\n\r\n		@closedir($dir);\r\n\r\n		return $files;\r\n	}\r\n\r\n	function sort_files($a, $b)\r\n	{\r\n		if ($a[\'type\'] != $b[\'type\'])\r\n			if ($a[\'type\'] == 5 || $b[\'type\'] == 2)\r\n				return -1;\r\n			else if ($a[\'type\'] == 2 || $b[\'type\'] == 5)\r\n				return 1;\r\n		else if ($a[\'type\'] == 5)\r\n			return strcmp(strtolower($a[\'name\']), strtolower($b[\'name\']));\r\n		else if ($a[\'ext\'] != $b[\'ext\'])\r\n			return strcmp($a[\'ext\'], $b[\'ext\']);\r\n		else if ($a[\'stat\'][7] != $b[\'stat\'][7])\r\n			return $a[\'stat\'][7] > $b[\'stat\'][7] ? -1 : 1;\r\n		else\r\n			return strcmp(strtolower($a[\'name\']), strtolower($b[\'name\']));\r\n		return 0;\r\n	}\r\n\r\n	function download_file()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$this->error[] = \"Can only use download_file() if archive is in memory. Redirect to file otherwise, it is faster.\";\r\n			return;\r\n		}\r\n		switch ($this->options[\'type\'])\r\n		{\r\n		case \"zip\":\r\n			header(\"Content-Type: application/zip\");\r\n			break;\r\n		case \"bzip\":\r\n			header(\"Content-Type: application/x-bzip2\");\r\n			break;\r\n		case \"gzip\":\r\n			header(\"Content-Type: application/x-gzip\");\r\n			break;\r\n		case \"tar\":\r\n			header(\"Content-Type: application/x-tar\");\r\n		}\r\n		$header = \"Content-Disposition: attachment; filename=\\\"\";\r\n		$header .= strstr($this->options[\'name\'], \"/\") ? substr($this->options[\'name\'], strrpos($this->options[\'name\'], \"/\") + 1) : $this->options[\'name\'];\r\n		$header .= \"\\\"\";\r\n		header($header);\r\n		header(\"Content-Length: \" . strlen($this->archive));\r\n		header(\"Content-Transfer-Encoding: binary\");\r\n		header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\r\n		header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\r\n		print($this->archive);\r\n	}\r\n}\r\n\r\nclass smd_crunch_tar_file extends smd_crunch_archive\r\n{\r\n	function smd_crunch_tar_file($name)\r\n	{\r\n		$this->smd_crunch_archive($name);\r\n		$this->options[\'type\'] = \"tar\";\r\n	}\r\n\r\n	function create_tar()\r\n	{\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($this->files as $current)\r\n		{\r\n			if ($current[\'name\'] == $this->options[\'name\'])\r\n				continue;\r\n			if (strlen($current[\'name2\']) > 99)\r\n			{\r\n				$path = substr($current[\'name2\'], 0, strpos($current[\'name2\'], \"/\", strlen($current[\'name2\']) - 100) + 1);\r\n				$current[\'name2\'] = substr($current[\'name2\'], strlen($path));\r\n				if (strlen($path) > 154 || strlen($current[\'name2\']) > 99)\r\n				{\r\n					$this->error[] = \"Could not add {$path}{$current[\'name2\']} to archive because the filename is too long.\";\r\n					continue;\r\n				}\r\n			}\r\n			$block = pack(\"a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12\", $current[\'name2\'], sprintf(\"%07o\",\r\n				$current[\'stat\'][2]), sprintf(\"%07o\", $current[\'stat\'][4]), sprintf(\"%07o\", $current[\'stat\'][5]),\r\n				sprintf(\"%011o\", $current[\'type\'] == 2 ? 0 : $current[\'stat\'][7]), sprintf(\"%011o\", $current[\'stat\'][9]),\r\n				\"        \", $current[\'type\'], $current[\'type\'] == 2 ? @readlink($current[\'name\']) : \"\", \"ustar \", \" \",\r\n				\"Unknown\", \"Unknown\", \"\", \"\", !empty ($path) ? $path : \"\", \"\");\r\n\r\n			$checksum = 0;\r\n			for ($i = 0; $i < 512; $i++)\r\n				$checksum += ord(substr($block, $i, 1));\r\n			$checksum = pack(\"a8\", sprintf(\"%07o\", $checksum));\r\n			$block = substr_replace($block, $checksum, 148, 8);\r\n\r\n			if ($current[\'type\'] == 2 || $current[\'stat\'][7] == 0)\r\n				$this->add_data($block);\r\n			else if ($fp = @fopen($current[\'name\'], \"rb\"))\r\n			{\r\n				$this->add_data($block);\r\n				while ($temp = fread($fp, 1048576))\r\n					$this->add_data($temp);\r\n				if ($current[\'stat\'][7] % 512 > 0)\r\n				{\r\n					$temp = \"\";\r\n					for ($i = 0; $i < 512 - $current[\'stat\'][7] % 512; $i++)\r\n						$temp .= \"\\0\";\r\n					$this->add_data($temp);\r\n				}\r\n				fclose($fp);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open file {$current[\'name\']} for reading. It was not added.\";\r\n		}\r\n\r\n		$this->add_data(pack(\"a1024\", \"\"));\r\n\r\n		chdir($pwd);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function extract_files()\r\n	{\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		if ($fp = $this->open_archive())\r\n		{\r\n			if ($this->options[\'inmemory\'] == 1)\r\n				$this->files = array ();\r\n\r\n			while ($block = fread($fp, 512))\r\n			{\r\n				$temp = unpack(\"a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp\", $block);\r\n				$file = array (\r\n					\'name\' => $temp[\'prefix\'] . $temp[\'name\'],\r\n					\'stat\' => array (\r\n						2 => $temp[\'mode\'],\r\n						4 => octdec($temp[\'uid\']),\r\n						5 => octdec($temp[\'gid\']),\r\n						7 => octdec($temp[\'size\']),\r\n						9 => octdec($temp[\'mtime\']),\r\n					),\r\n					\'checksum\' => octdec($temp[\'checksum\']),\r\n					\'type\' => $temp[\'type\'],\r\n					\'magic\' => $temp[\'magic\'],\r\n				);\r\n\r\n				if ($file[\'checksum\'] == 0x00000000)\r\n					break;\r\n				else if (substr($file[\'magic\'], 0, 5) != \"ustar\")\r\n				{\r\n					$this->error[] = \"This script does not support extracting this type of tar file.\";\r\n					break;\r\n				}\r\n				$block = substr_replace($block, \"        \", 148, 8);\r\n				$checksum = 0;\r\n				for ($i = 0; $i < 512; $i++)\r\n					$checksum += ord(substr($block, $i, 1));\r\n				if ($file[\'checksum\'] != $checksum)\r\n					$this->error[] = \"Could not extract from {$this->options[\'name\']}, it is corrupt.\";\r\n\r\n				// Added: create dir if required\r\n				if (strpos($file[\'name\'], \'..\') === false) {\r\n					$path_parts = pathinfo($file[\'name\']);\r\n					if (!file_exists($file[\'name\'])) {\r\n						$ret = smd_mkdir_recursive(((isset($path_parts[\'extension\'])) ? dirname($this->options[\'basedir\'].DS.$file[\'name\']) : $this->options[\'basedir\'].DS.$file[\'name\']));\r\n					}\r\n				}\r\n\r\n				if ($this->options[\'inmemory\'] == 1)\r\n				{\r\n					$file[\'data\'] = fread($fp, $file[\'stat\'][7]);\r\n					fread($fp, (512 - $file[\'stat\'][7] % 512) == 512 ? 0 : (512 - $file[\'stat\'][7] % 512));\r\n					unset ($file[\'checksum\'], $file[\'magic\']);\r\n					$this->files[] = $file;\r\n				}\r\n				else if ($file[\'type\'] == 5)\r\n				{\r\n					if (!is_dir($file[\'name\']))\r\n						mkdir($file[\'name\'], $file[\'stat\'][2]);\r\n				}\r\n				else if ($this->options[\'overwrite\'] == 0 && file_exists($file[\'name\']))\r\n				{\r\n					$this->error[] = \"{$file[\'name\']} already exists.\";\r\n					continue;\r\n				}\r\n				else if ($file[\'type\'] == 2)\r\n				{\r\n					symlink($temp[\'symlink\'], $file[\'name\']);\r\n					chmod($file[\'name\'], $file[\'stat\'][2]);\r\n				}\r\n				else if ($new = fopen($file[\'name\'], \"wb\"))\r\n				{\r\n					fwrite($new, fread($fp, $file[\'stat\'][7]));\r\n					fread($fp, (512 - $file[\'stat\'][7] % 512) == 512 ? 0 : (512 - $file[\'stat\'][7] % 512));\r\n					fclose($new);\r\n					@chmod($file[\'name\'], 0644); // Hard-coded for now because the stupid 0100644 value in $file[\'stat\'][2] confuses the OS/PHP\r\n				}\r\n				else\r\n				{\r\n					$this->error[] = \"Could not open {$file[\'name\']} for writing.\";\r\n					continue;\r\n				}\r\n				// Remove owner and group setting since there is almost 0 likelihood the files will have the same user/group across systems\r\n				// In other words, leave owner/group at the Apache default -- which is what we want\r\n//				chown($file[\'name\'], $file[\'stat\'][4]);\r\n//				chgrp($file[\'name\'], $file[\'stat\'][5]);\r\n				@touch($file[\'name\'], $file[\'stat\'][9]);\r\n				unset ($file);\r\n			}\r\n		}\r\n		else\r\n			$this->error[] = \"Could not open file {$this->options[\'name\']}\";\r\n\r\n		chdir($pwd);\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @fopen($this->options[\'name\'], \"rb\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_gzip_file extends smd_crunch_tar_file\r\n{\r\n	function smd_crunch_gzip_file($name)\r\n	{\r\n		$this->smd_crunch_tar_file($name);\r\n		$this->options[\'type\'] = \"gzip\";\r\n	}\r\n\r\n	function create_gzip()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($fp = gzopen($this->options[\'name\'], \"wb{$this->options[\'level\']}\"))\r\n			{\r\n				fseek($this->archive, 0);\r\n				while ($temp = fread($this->archive, 1048576))\r\n					gzwrite($fp, $temp);\r\n				gzclose($fp);\r\n				chdir($pwd);\r\n			}\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = gzencode($this->archive, $this->options[\'level\']);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @gzopen($this->options[\'name\'], \"rb\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_bzip_file extends smd_crunch_tar_file\r\n{\r\n	function smd_crunch_bzip_file($name)\r\n	{\r\n		$this->smd_crunch_tar_file($name);\r\n		$this->options[\'type\'] = \"bzip\";\r\n	}\r\n\r\n	function create_bzip()\r\n	{\r\n		if ($this->options[\'inmemory\'] == 0)\r\n		{\r\n			$pwd = getcwd();\r\n			chdir($this->options[\'basedir\']);\r\n			if ($fp = bzopen($this->options[\'name\'], \"wb\"))\r\n			{\r\n				fseek($this->archive, 0);\r\n				while ($temp = fread($this->archive, 1048576))\r\n					bzwrite($fp, $temp);\r\n				bzclose($fp);\r\n				chdir($pwd);\r\n			}\r\n			else\r\n			{\r\n				$this->error[] = \"Could not open {$this->options[\'name\']} for writing.\";\r\n				chdir($pwd);\r\n				return 0;\r\n			}\r\n		}\r\n		else\r\n			$this->archive = bzcompress($this->archive, $this->options[\'level\']);\r\n\r\n		return 1;\r\n	}\r\n\r\n	function open_archive()\r\n	{\r\n		return @bzopen($this->options[\'name\'], \"r\");\r\n	}\r\n}\r\n\r\nclass smd_crunch_zip_file extends smd_crunch_archive\r\n{\r\n	function smd_crunch_zip_file($name)\r\n	{\r\n		$this->smd_crunch_archive($name);\r\n		$this->options[\'type\'] = \"zip\";\r\n	}\r\n\r\n	function create_zip()\r\n	{\r\n		$files = 0;\r\n		$offset = 0;\r\n		$central = \"\";\r\n\r\n		if (!empty ($this->options[\'sfx\']))\r\n			if ($fp = @fopen($this->options[\'sfx\'], \"rb\"))\r\n			{\r\n				$temp = fread($fp, filesize($this->options[\'sfx\']));\r\n				fclose($fp);\r\n				$this->add_data($temp);\r\n				$offset += strlen($temp);\r\n				unset ($temp);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open sfx module from {$this->options[\'sfx\']}.\";\r\n\r\n		$pwd = getcwd();\r\n		chdir($this->options[\'basedir\']);\r\n\r\n		foreach ($this->files as $current)\r\n		{\r\n			if ($current[\'name\'] == $this->options[\'name\'])\r\n				continue;\r\n\r\n			$timedate = explode(\" \", date(\"Y n j G i s\", $current[\'stat\'][9]));\r\n			$timedate = ($timedate[0] - 1980 << 25) | ($timedate[1] << 21) | ($timedate[2] << 16) |\r\n				($timedate[3] << 11) | ($timedate[4] << 5) | ($timedate[5]);\r\n\r\n			$block = pack(\"VvvvV\", 0x04034b50, 0x000A, 0x0000, (isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate);\r\n\r\n			if ($current[\'stat\'][7] == 0 && $current[\'type\'] == 5)\r\n			{\r\n				$block .= pack(\"VVVvv\", 0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']) + 1, 0x0000);\r\n				$block .= $current[\'name2\'] . \"/\";\r\n				$this->add_data($block);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']) + 1, 0x0000, 0x0000, 0x0000, 0x0000, $current[\'type\'] == 5 ? 0x00000010 : 0x00000000, $offset);\r\n				$central .= $current[\'name2\'] . \"/\";\r\n				$files++;\r\n				$offset += (31 + strlen($current[\'name2\']));\r\n			}\r\n			else if ($current[\'stat\'][7] == 0)\r\n			{\r\n				$block .= pack(\"VVVvv\", 0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']), 0x0000);\r\n				$block .= $current[\'name2\'];\r\n				$this->add_data($block);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					0x00000000, 0x00000000, 0x00000000, strlen($current[\'name2\']), 0x0000, 0x0000, 0x0000, 0x0000, $current[\'type\'] == 5 ? 0x00000010 : 0x00000000, $offset);\r\n				$central .= $current[\'name2\'];\r\n				$files++;\r\n				$offset += (30 + strlen($current[\'name2\']));\r\n			}\r\n			else if ($fp = @fopen($current[\'name\'], \"rb\"))\r\n			{\r\n				$temp = fread($fp, $current[\'stat\'][7]);\r\n				fclose($fp);\r\n				$crc32 = crc32($temp);\r\n				if (!isset($current[\'method\']) && $this->options[\'method\'] == 1)\r\n				{\r\n					$temp = gzcompress($temp, $this->options[\'level\']);\r\n					$size = strlen($temp) - 6;\r\n					$temp = substr($temp, 2, $size);\r\n				}\r\n				else\r\n					$size = strlen($temp);\r\n				$block .= pack(\"VVVvv\", $crc32, $size, $current[\'stat\'][7], strlen($current[\'name2\']), 0x0000);\r\n				$block .= $current[\'name2\'];\r\n				$this->add_data($block);\r\n				$this->add_data($temp);\r\n				unset ($temp);\r\n				$central .= pack(\"VvvvvVVVVvvvvvVV\", 0x02014b50, 0x0014, $this->options[\'method\'] == 0 ? 0x0000 : 0x000A, 0x0000,\r\n					(isset($current[\'method\']) || $this->options[\'method\'] == 0) ? 0x0000 : 0x0008, $timedate,\r\n					$crc32, $size, $current[\'stat\'][7], strlen($current[\'name2\']), 0x0000, 0x0000, 0x0000, 0x0000, 0x00000000, $offset);\r\n				$central .= $current[\'name2\'];\r\n				$files++;\r\n				$offset += (30 + strlen($current[\'name2\']) + $size);\r\n			}\r\n			else\r\n				$this->error[] = \"Could not open file {$current[\'name\']} for reading. It was not added.\";\r\n		}\r\n\r\n		$this->add_data($central);\r\n\r\n		$this->add_data(pack(\"VvvvvVVv\", 0x06054b50, 0x0000, 0x0000, $files, $files, strlen($central), $offset,\r\n			!empty ($this->options[\'comment\']) ? strlen($this->options[\'comment\']) : 0x0000));\r\n\r\n		if (!empty ($this->options[\'comment\']))\r\n			$this->add_data($this->options[\'comment\']);\r\n\r\n		chdir($pwd);\r\n\r\n		return 1;\r\n	}\r\n}\r\n\r\n// 19/08/2011 (v2.663)\r\n// - unzipAll was using double slashes (path//filename) to save files. (thanks to Karen Peyton).\r\n// 09/08/2010 (v2.662)\r\n// - unzipAll parameters fully reviewed and fixed. Thanks Ronny Dreschler and Conor Mac Aoidh.\r\n// 12/05/2010 (v2.661)\r\n// - Fixed E_STRICT notice: \"Only variables should be passed by reference\". Thanks Erik W.\r\n// 24/03/2010 (v2.66)\r\n// - Fixed bug inside unzipAll when dirname is \".\" (thanks to Thorsten Groth)\r\n// - Added character \"´\" to the string conversion table (ex: caixa d´água)\r\n// 27/02/2010\r\n// - Removed PHP4 support (file_put_contents redeclaration).\r\n// 04/12/2009 (v2.65)\r\n// * Added character translation to decode accents and/or special characters.\r\n// 10/11/2009\r\n// * Some security added to avoid malicious ZIP files (relative dirs)\r\n// * unzipAll() will output by default to same folder of the caller script\r\n// 25/09/2009\r\n// - Code optimization to reduce memory usage (uncompress(&$contents))\r\n// 12/07/2009 (2.62)\r\n// - Debug messages are shown only when explicit.\r\n// - New method: getLastError()\r\n\r\n##############################################################\r\n# Class dUnzip2 v2.663\r\n#\r\n#  Author: Alexandre Tedeschi (d)\r\n#  E-Mail: alexandrebr at gmail dot com\r\n#  Londrina - PR / Brazil\r\n#\r\n#  Objective:\r\n#    This class allows programmer to easily unzip files on the fly.\r\n#\r\n#  Requirements:\r\n#    This class requires extension ZLib Enabled. It is default\r\n#    for most site hosts around the world, and for the PHP Win32 dist.\r\n#\r\n#  To do:\r\n#   * Error handling\r\n#   * Write a PHP-Side gzinflate, to completely avoid any external extensions\r\n#   * Write other decompress algorithms\r\n#\r\n#  Methods:\r\n#  * dUnzip2($filename)         - Constructor - Opens $filename\r\n#  * getList([$stopOnFile])     - Retrieve the file list\r\n#  * getExtraInfo($zipfilename) - Retrieve more information about compressed file\r\n#  * getZipInfo([$entry])       - Retrieve ZIP file details.\r\n#  * unzip($zipfilename, [$outfilename, [$applyChmod]]) - Unzip file\r\n#  * unzipAll([$outDir, [$zipDir, [$maintainStructure, [$applyChmod]]]])\r\n#  * close()                    - Close file handler, but keep the list\r\n#  * __destroy()                - Close file handler and release memory\r\n#\r\n#  If you modify this class, or have any ideas to improve it, please contact me!\r\n#  You are allowed to redistribute this class, if you keep my name and contact e-mail on it.\r\n#\r\n#  PLEASE! IF YOU USE THIS CLASS IN ANY OF YOUR PROJECTS, PLEASE LET ME KNOW!\r\n#  If you have problems using it, don\'t think twice before contacting me!\r\n#\r\n##############################################################\r\n\r\nclass smd_crunch_dUnzip2{\r\n	Function getVersion(){\r\n		return \"2.663\";\r\n	}\r\n	// Public\r\n	var $fileName;\r\n	var $lastError;\r\n	var $compressedList; // You will problably use only this one!\r\n	var $centralDirList; // Central dir list... It\'s a kind of \'extra attributes\' for a set of files\r\n	var $endOfCentral;   // End of central dir, contains ZIP Comments\r\n	var $debug;\r\n\r\n	// Private\r\n	var $fh;\r\n	var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n	var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n	var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n\r\n	// Public\r\n	Function smd_crunch_dUnzip2($fileName){\r\n		$this->fileName       = $fileName;\r\n		$this->compressedList = \r\n		$this->centralDirList = \r\n		$this->endOfCentral   = Array();\r\n	}\r\n	\r\n	Function getList($stopOnFile=false){\r\n		if(sizeof($this->compressedList)){\r\n			$this->debugMsg(1, \"Returning already loaded file list.\");\r\n			return $this->compressedList;\r\n		}\r\n		\r\n		// Open file, and set file handler\r\n		$fh = fopen($this->fileName, \"r\");\r\n		$this->fh = &$fh;\r\n		if(!$fh){\r\n			$this->debugMsg(2, \"Failed to load file.\");\r\n			return false;\r\n		}\r\n		\r\n		$this->debugMsg(1, \"Loading list from \'End of Central Dir\' index list...\");\r\n		if(!$this->_loadFileListByEOF($fh, $stopOnFile)){\r\n			$this->debugMsg(1, \"Failed! Trying to load list looking for signatures...\");\r\n			if(!$this->_loadFileListBySignatures($fh, $stopOnFile)){\r\n				$this->debugMsg(1, \"Failed! Could not find any valid header.\");\r\n				$this->debugMsg(2, \"ZIP File is corrupted or empty\");\r\n				return false;\r\n			}\r\n		}\r\n		\r\n		if($this->debug){\r\n			#------- Debug compressedList\r\n			$kkk = 0;\r\n			echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n			foreach($this->compressedList as $fileName=>$item){\r\n				if(!$kkk && $kkk=1){\r\n					echo \"<tr style=\'background: #ADA\'>\";\r\n					foreach($item as $fieldName=>$value)\r\n						echo \"<td>$fieldName</td>\";\r\n					echo \'</tr>\';\r\n				}\r\n				echo \"<tr style=\'background: #CFC\'>\";\r\n				foreach($item as $fieldName=>$value){\r\n					if($fieldName == \'lastmod_datetime\')\r\n						echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n					else\r\n						echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n				}\r\n				echo \"</tr>\";\r\n			}\r\n			echo \"</table>\";\r\n			\r\n			#------- Debug centralDirList\r\n			$kkk = 0;\r\n			if(sizeof($this->centralDirList)){\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n				foreach($this->centralDirList as $fileName=>$item){\r\n					if(!$kkk && $kkk=1){\r\n						echo \"<tr style=\'background: #AAD\'>\";\r\n						foreach($item as $fieldName=>$value)\r\n							echo \"<td>$fieldName</td>\";\r\n						echo \'</tr>\';\r\n					}\r\n					echo \"<tr style=\'background: #CCF\'>\";\r\n					foreach($item as $fieldName=>$value){\r\n						if($fieldName == \'lastmod_datetime\')\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n						else\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n					}\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n			}\r\n		\r\n			#------- Debug endOfCentral\r\n			$kkk = 0;\r\n			if(sizeof($this->endOfCentral)){\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana\' style=\'border: 1px solid #000\'>\";\r\n				echo \"<tr style=\'background: #DAA\'><td colspan=\'2\'>dUnzip - End of file</td></tr>\";\r\n				foreach($this->endOfCentral as $field=>$value){\r\n					echo \"<tr>\";\r\n					echo \"<td style=\'background: #FCC\'>$field</td>\";\r\n					echo \"<td style=\'background: #FDD\'>$value</td>\";\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n			}\r\n		}\r\n		\r\n		return $this->compressedList;\r\n	}\r\n	Function getExtraInfo($compressedFileName){\r\n		return\r\n			isset($this->centralDirList[$compressedFileName])?\r\n			$this->centralDirList[$compressedFileName]:\r\n			false;\r\n	}\r\n	Function getZipInfo($detail=false){\r\n		return $detail?\r\n			$this->endOfCentral[$detail]:\r\n			$this->endOfCentral;\r\n	}\r\n	\r\n	Function unzip($compressedFileName, $targetFileName=false, $applyChmod=0777){\r\n		if(!sizeof($this->compressedList)){\r\n			$this->debugMsg(1, \"Trying to unzip before loading file list... Loading it!\");\r\n			$this->getList(false, $compressedFileName);\r\n		}\r\n		\r\n		$fdetails = &$this->compressedList[$compressedFileName];\r\n		if(!isset($this->compressedList[$compressedFileName])){\r\n			$this->debugMsg(2, \"File \'<b>$compressedFileName</b>\' is not compressed in the zip.\");\r\n			return false;\r\n		}\r\n		if(substr($compressedFileName, -1) == \"/\"){\r\n			$this->debugMsg(2, \"Trying to unzip a folder name \'<b>$compressedFileName</b>\'.\");\r\n			return false;\r\n		}\r\n		if(!$fdetails[\'uncompressed_size\']){\r\n			$this->debugMsg(1, \"File \'<b>$compressedFileName</b>\' is empty.\");\r\n			return $targetFileName?\r\n				file_put_contents($targetFileName, \"\"):\r\n				\"\";\r\n		}\r\n		\r\n		fseek($this->fh, $fdetails[\'contents-startOffset\']);\r\n		$toUncompress = fread($this->fh, $fdetails[\'compressed_size\']);\r\n		$ret = $this->uncompress(\r\n				$toUncompress,\r\n				$fdetails[\'compression_method\'],\r\n				$fdetails[\'uncompressed_size\'],\r\n				$targetFileName\r\n			);\r\n		unset($toUncompress);\r\n		if($applyChmod && $targetFileName)\r\n			chmod($targetFileName, 0777);\r\n\r\n		return $ret;\r\n	}\r\n	Function unzipAll($targetDir=false, $baseDir=\"\", $maintainStructure=true, $applyChmod=0777){\r\n		if($targetDir === false)\r\n			$targetDir = dirname($_SERVER[\'SCRIPT_FILENAME\']).\"/\";\r\n		\r\n		if(substr($targetDir, -1) == \"/\")\r\n			$targetDir = substr($targetDir, 0, -1);\r\n		\r\n		$lista = $this->getList();\r\n		if(sizeof($lista)) foreach($lista as $fileName=>$trash){\r\n			$dirname  = dirname($fileName);\r\n			$outDN    = \"$targetDir/$dirname\";\r\n			\r\n			if(substr($dirname, 0, strlen($baseDir)) != $baseDir)\r\n				continue;\r\n			\r\n			if(!is_dir($outDN) && $maintainStructure){\r\n				$str = \"\";\r\n				$folders = explode(\"/\", $dirname);\r\n				foreach($folders as $folder){\r\n					$str = $str?\"$str/$folder\":$folder;\r\n					if(!is_dir(\"$targetDir/$str\")){\r\n						$this->debugMsg(1, \"Creating folder: $targetDir/$str\");\r\n						mkdir(\"$targetDir/$str\");\r\n						if($applyChmod)\r\n							chmod(\"$targetDir/$str\", $applyChmod);\r\n					}\r\n				}\r\n			}\r\n			if(substr($fileName, -1, 1) == \"/\")\r\n				continue;\r\n			\r\n			$maintainStructure?\r\n				$this->unzip($fileName, \"$targetDir/$fileName\", $applyChmod):\r\n				$this->unzip($fileName, \"$targetDir/\".basename($fileName), $applyChmod);\r\n		}\r\n	}\r\n	\r\n	Function close(){     // Free the file resource\r\n		if($this->fh)\r\n			fclose($this->fh);\r\n	}\r\n	Function __destroy(){ \r\n		$this->close();\r\n	}\r\n	\r\n	// Private (you should NOT call these methods):\r\n	Function uncompress(&$content, $mode, $uncompressedSize, $targetFileName=false){\r\n		switch($mode){\r\n			case 0:\r\n				// Not compressed\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, $content):\r\n					$content;\r\n			case 1:\r\n				$this->debugMsg(2, \"Shrunk mode is not supported... yet?\");\r\n				return false;\r\n			case 2:\r\n			case 3:\r\n			case 4:\r\n			case 5:\r\n				$this->debugMsg(2, \"Compression factor \".($mode-1).\" is not supported... yet?\");\r\n				return false;\r\n			case 6:\r\n				$this->debugMsg(2, \"Implode is not supported... yet?\");\r\n				return false;\r\n			case 7:\r\n				$this->debugMsg(2, \"Tokenizing compression algorithm is not supported... yet?\");\r\n				return false;\r\n			case 8:\r\n				// Deflate\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, gzinflate($content, $uncompressedSize)):\r\n					gzinflate($content, $uncompressedSize);\r\n			case 9:\r\n				$this->debugMsg(2, \"Enhanced Deflating is not supported... yet?\");\r\n				return false;\r\n			case 10:\r\n				$this->debugMsg(2, \"PKWARE Date Compression Library Impoloding is not supported... yet?\");\r\n				return false;\r\n			case 12:\r\n				// Bzip2\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, bzdecompress($content)):\r\n					bzdecompress($content);\r\n			case 18:\r\n				$this->debugMsg(2, \"IBM TERSE is not supported... yet?\");\r\n				return false;\r\n			default:\r\n				$this->debugMsg(2, \"Unknown uncompress method: $mode\");\r\n				return false;\r\n		}\r\n	}\r\n	Function debugMsg($level, $string){\r\n		if($this->debug){\r\n			if($level == 1)\r\n				echo \"<b style=\'color: #777\'>dUnzip2:</b> $string<br>\";\r\n			\r\n			if($level == 2)\r\n				echo \"<b style=\'color: #F00\'>dUnzip2:</b> $string<br>\";\r\n		}\r\n		$this->lastError = $string;\r\n	}\r\n	Function getLastError(){\r\n		return $this->lastError;\r\n	}\r\n	\r\n	Function _loadFileListByEOF(&$fh, $stopOnFile=false){\r\n		// Check if there\'s a valid Central Dir signature.\r\n		// Let\'s consider a file comment smaller than 1024 characters...\r\n		// Actually, it length can be 65536.. But we\'re not going to support it.\r\n		\r\n		for($x = 0; $x < 1024; $x++){\r\n			fseek($fh, -22-$x, SEEK_END);\r\n			\r\n			$signature = fread($fh, 4);\r\n			if($signature == $this->dirSignatureE){\r\n				// If found EOF Central Dir\r\n				$eodir[\'disk_number_this\']   = unpack(\"v\", fread($fh, 2)); // number of this disk\r\n				$eodir[\'disk_number\']        = unpack(\"v\", fread($fh, 2)); // number of the disk with the start of the central directory\r\n				$eodir[\'total_entries_this\'] = unpack(\"v\", fread($fh, 2)); // total number of entries in the central dir on this disk\r\n				$eodir[\'total_entries\']      = unpack(\"v\", fread($fh, 2)); // total number of entries in\r\n				$eodir[\'size_of_cd\']         = unpack(\"V\", fread($fh, 4)); // size of the central directory\r\n				$eodir[\'offset_start_cd\']    = unpack(\"V\", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number\r\n				$zipFileCommentLenght        = unpack(\"v\", fread($fh, 2)); // zipfile comment length\r\n				$eodir[\'zipfile_comment\']    = $zipFileCommentLenght[1]?fread($fh, $zipFileCommentLenght[1]):\'\'; // zipfile comment\r\n				$this->endOfCentral = Array(\r\n					\'disk_number_this\'=>$eodir[\'disk_number_this\'][1],\r\n					\'disk_number\'=>$eodir[\'disk_number\'][1],\r\n					\'total_entries_this\'=>$eodir[\'total_entries_this\'][1],\r\n					\'total_entries\'=>$eodir[\'total_entries\'][1],\r\n					\'size_of_cd\'=>$eodir[\'size_of_cd\'][1],\r\n					\'offset_start_cd\'=>$eodir[\'offset_start_cd\'][1],\r\n					\'zipfile_comment\'=>$eodir[\'zipfile_comment\'],\r\n				);\r\n				\r\n				// Then, load file list\r\n				fseek($fh, $this->endOfCentral[\'offset_start_cd\']);\r\n				$signature = fread($fh, 4);\r\n				\r\n				while($signature == $this->dirSignature){\r\n					$dir[\'version_madeby\']      = unpack(\"v\", fread($fh, 2)); // version made by\r\n					$dir[\'version_needed\']      = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n					$dir[\'general_bit_flag\']    = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n					$dir[\'compression_method\']  = unpack(\"v\", fread($fh, 2)); // compression method\r\n					$dir[\'lastmod_time\']        = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n					$dir[\'lastmod_date\']        = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n					$dir[\'crc-32\']              = fread($fh, 4);              // crc-32\r\n					$dir[\'compressed_size\']     = unpack(\"V\", fread($fh, 4)); // compressed size\r\n					$dir[\'uncompressed_size\']   = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n					$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n					$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n					$fileCommentLength          = unpack(\"v\", fread($fh, 2)); // file comment length\r\n					$dir[\'disk_number_start\']   = unpack(\"v\", fread($fh, 2)); // disk number start\r\n					$dir[\'internal_attributes\'] = unpack(\"v\", fread($fh, 2)); // internal file attributes-byte1\r\n					$dir[\'external_attributes1\']= unpack(\"v\", fread($fh, 2)); // external file attributes-byte2\r\n					$dir[\'external_attributes2\']= unpack(\"v\", fread($fh, 2)); // external file attributes\r\n					$dir[\'relative_offset\']     = unpack(\"V\", fread($fh, 4)); // relative offset of local header\r\n					$dir[\'file_name\']           = fread($fh, $fileNameLength[1]);                             // filename\r\n					$dir[\'extra_field\']         = $extraFieldLength[1] ?fread($fh, $extraFieldLength[1]) :\'\'; // extra field\r\n					$dir[\'file_comment\']        = $fileCommentLength[1]?fread($fh, $fileCommentLength[1]):\'\'; // file comment			\r\n					\r\n					// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n					$BINlastmod_date = str_pad(decbin($dir[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n					$BINlastmod_time = str_pad(decbin($dir[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n					$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n					$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n					$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n					$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n					$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n					$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));	\r\n					\r\n					// Some protection agains attacks...\r\n					$dir[\'file_name\']     = $this->_decodeFilename($dir[\'file_name\']);\r\n					if(!$dir[\'file_name\'] = $this->_protect($dir[\'file_name\']))\r\n						continue;\r\n					\r\n					$this->centralDirList[$dir[\'file_name\']] = Array(\r\n						\'version_madeby\'=>$dir[\'version_madeby\'][1],\r\n						\'version_needed\'=>$dir[\'version_needed\'][1],\r\n						\'general_bit_flag\'=>str_pad(decbin($dir[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n						\'compression_method\'=>$dir[\'compression_method\'][1],\r\n						\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n						\'crc-32\'            =>str_pad(dechex(ord($dir[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n											  str_pad(dechex(ord($dir[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n						\'compressed_size\'=>$dir[\'compressed_size\'][1],\r\n						\'uncompressed_size\'=>$dir[\'uncompressed_size\'][1],\r\n						\'disk_number_start\'=>$dir[\'disk_number_start\'][1],\r\n						\'internal_attributes\'=>$dir[\'internal_attributes\'][1],\r\n						\'external_attributes1\'=>$dir[\'external_attributes1\'][1],\r\n						\'external_attributes2\'=>$dir[\'external_attributes2\'][1],\r\n						\'relative_offset\'=>$dir[\'relative_offset\'][1],\r\n						\'file_name\'=>$dir[\'file_name\'],\r\n						\'extra_field\'=>$dir[\'extra_field\'],\r\n						\'file_comment\'=>$dir[\'file_comment\'],\r\n					);\r\n					$signature = fread($fh, 4);\r\n				}\r\n				\r\n				// If loaded centralDirs, then try to identify the offsetPosition of the compressed data.\r\n				if($this->centralDirList) foreach($this->centralDirList as $filename=>$details){\r\n					$i = $this->_getFileHeaderInformation($fh, $details[\'relative_offset\']);\r\n					$this->compressedList[$filename][\'file_name\']          = $filename;\r\n					$this->compressedList[$filename][\'compression_method\'] = $details[\'compression_method\'];\r\n					$this->compressedList[$filename][\'version_needed\']     = $details[\'version_needed\'];\r\n					$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n					$this->compressedList[$filename][\'crc-32\']             = $details[\'crc-32\'];\r\n					$this->compressedList[$filename][\'compressed_size\']    = $details[\'compressed_size\'];\r\n					$this->compressedList[$filename][\'uncompressed_size\']  = $details[\'uncompressed_size\'];\r\n					$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n					$this->compressedList[$filename][\'extra_field\']        = $i[\'extra_field\'];\r\n					$this->compressedList[$filename][\'contents-startOffset\']=$i[\'contents-startOffset\'];\r\n					if(strtolower($stopOnFile) == strtolower($filename))\r\n						break;\r\n				}\r\n				return true;\r\n			}\r\n		}\r\n		return false;\r\n	}\r\n	Function _loadFileListBySignatures(&$fh, $stopOnFile=false){\r\n		fseek($fh, 0);\r\n		\r\n		$return = false;\r\n		for(;;){\r\n			$details = $this->_getFileHeaderInformation($fh);\r\n			if(!$details){\r\n				$this->debugMsg(1, \"Invalid signature. Trying to verify if is old style Data Descriptor...\");\r\n				fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again)\r\n				$details = $this->_getFileHeaderInformation($fh);\r\n			}\r\n			if(!$details){\r\n				$this->debugMsg(1, \"Still invalid signature. Probably reached the end of the file.\");\r\n				break;\r\n			}\r\n			$filename = $details[\'file_name\'];\r\n			$this->compressedList[$filename] = $details;\r\n			$return = true;\r\n			if(strtolower($stopOnFile) == strtolower($filename))\r\n				break;\r\n		}\r\n		\r\n		return $return;\r\n	}\r\n	Function _getFileHeaderInformation(&$fh, $startOffset=false){\r\n		if($startOffset !== false)\r\n			fseek($fh, $startOffset);\r\n		\r\n		$signature = fread($fh, 4);\r\n		if($signature == $this->zipSignature){\r\n			# $this->debugMsg(1, \"Zip Signature!\");\r\n			\r\n			// Get information about the zipped file\r\n			$file[\'version_needed\']     = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n			$file[\'general_bit_flag\']   = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n			$file[\'compression_method\'] = unpack(\"v\", fread($fh, 2)); // compression method\r\n			$file[\'lastmod_time\']       = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n			$file[\'lastmod_date\']       = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n			$file[\'crc-32\']             = fread($fh, 4);              // crc-32\r\n			$file[\'compressed_size\']    = unpack(\"V\", fread($fh, 4)); // compressed size\r\n			$file[\'uncompressed_size\']  = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n			$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n			$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n			$file[\'file_name\']          = fread($fh, $fileNameLength[1]); // filename\r\n			$file[\'extra_field\']        = $extraFieldLength[1]?fread($fh, $extraFieldLength[1]):\'\'; // extra field\r\n			$file[\'contents-startOffset\']= ftell($fh);\r\n			\r\n			// Bypass the whole compressed contents, and look for the next file\r\n			fseek($fh, $file[\'compressed_size\'][1], SEEK_CUR);\r\n			\r\n			// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n			$BINlastmod_date = str_pad(decbin($file[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n			$BINlastmod_time = str_pad(decbin($file[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n			$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n			$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n			$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n			$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n			$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n			\r\n			// Some protection agains attacks...\r\n			$file[\'file_name\']     = $this->_decodeFilename($file[\'file_name\']);\r\n			if(!$file[\'file_name\'] = $this->_protect($file[\'file_name\']))\r\n				return false;\r\n			\r\n			// Mount file table\r\n			$i = Array(\r\n				\'file_name\'         =>$file[\'file_name\'],\r\n				\'compression_method\'=>$file[\'compression_method\'][1],\r\n				\'version_needed\'    =>$file[\'version_needed\'][1],\r\n				\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n				\'crc-32\'            =>str_pad(dechex(ord($file[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n									  str_pad(dechex(ord($file[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n				\'compressed_size\'   =>$file[\'compressed_size\'][1],\r\n				\'uncompressed_size\' =>$file[\'uncompressed_size\'][1],\r\n				\'extra_field\'       =>$file[\'extra_field\'],\r\n				\'general_bit_flag\'  =>str_pad(decbin($file[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n				\'contents-startOffset\'=>$file[\'contents-startOffset\']\r\n			);\r\n			return $i;\r\n		}\r\n		return false;\r\n	}\r\n	\r\n	Function _decodeFilename($filename){\r\n		$from = \"\\xb7\\xb5\\xb6\\xc7\\x8e\\x8f\\x92\\x80\\xd4\\x90\\xd2\\xd3\\xde\\xd6\\xd7\\xd8\\xd1\\xa5\\xe3\\xe0\".\r\n		        \"\\xe2\\xe5\\x99\\x9d\\xeb\\xe9\\xea\\x9a\\xed\\xe8\\xe1\\x85\\xa0\\x83\\xc6\\x84\\x86\\x91\\x87\\x8a\".\r\n				\"\\x82\\x88\\x89\\x8d\\xa1\\x8c\\x8b\\xd0\\xa4\\x95\\xa2\\x93\\xe4\\x94\\x9b\\x97\\xa3\\x96\\xec\\xe7\".\r\n				\"\\x98ï\";\r\n		$to   = \"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýþÿ´\";\r\n\r\n		return strtr($filename, $from, $to);\r\n	}\r\n	Function _protect($fullPath){\r\n		// Known hack-attacks (filename like):\r\n		//   /home/usr\r\n		//   ../../home/usr\r\n		//   folder/../../../home/usr\r\n		//   sample/(x0)../home/usr\r\n		\r\n		$fullPath = strtr($fullPath, \":*<>|\\\"\\x0\\\\\", \"......./\");\r\n		while($fullPath[0] == \"/\")\r\n			$fullPath = substr($fullPath, 1);\r\n		\r\n		if(substr($fullPath, -1) == \"/\"){\r\n			$base     = \'\';\r\n			$fullPath = substr($fullPath, 0, -1);\r\n		}\r\n		else{\r\n			$base     = basename($fullPath);\r\n			$fullPath = dirname($fullPath);\r\n		}\r\n		\r\n		$parts   = explode(\"/\", $fullPath);\r\n		$lastIdx = false;\r\n		foreach($parts as $idx=>$part){\r\n			if($part == \".\")\r\n				unset($parts[$idx]);\r\n			elseif($part == \"..\"){\r\n				unset($parts[$idx]);\r\n				if($lastIdx !== false){\r\n					unset($parts[$lastIdx]);\r\n				}\r\n			}\r\n			elseif($part === \'\'){\r\n				unset($parts[$idx]);\r\n			}\r\n			else{\r\n				$lastIdx = $idx;\r\n			}\r\n		}\r\n		\r\n		$fullPath = sizeof($parts)?implode(\"/\", $parts).\"/\":\"\";\r\n		return $fullPath.$base;\r\n	}\r\n}\r\n\r\nclass smd_crunch_dZip{\r\n	var $filename;\r\n	var $overwrite;\r\n\r\n	var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n	var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n	var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n	var $files_count  = 0;\r\n	var $fh;\r\n\r\n	Function smd_crunch_dZip($filename, $overwrite=true){\r\n		$this->filename  = $filename;\r\n		$this->overwrite = $overwrite;\r\n	}\r\n	Function addDir($dirname, $fileComments=\'\'){\r\n		if(substr($dirname, -1) != \'/\')\r\n			$dirname .= \'/\';\r\n		$this->addFile(false, $dirname, $fileComments);\r\n	}\r\n	Function addFile($filename, $cfilename, $fileComments=\'\', $data=false){\r\n		if(!($fh = &$this->fh))\r\n			$fh = fopen($this->filename, $this->overwrite?\'wb\':\'a+b\');\r\n		\r\n		// $filename can be a local file OR the data wich will be compressed\r\n		if(substr($cfilename, -1)==\'/\'){\r\n			$details[\'uncsize\'] = 0;\r\n			$data = \'\';\r\n		}\r\n		elseif(file_exists($filename)){\r\n			$details[\'uncsize\'] = filesize($filename);\r\n			$data = file_get_contents($filename);\r\n		}\r\n		elseif($filename){\r\n			echo \"<b>Cannot add $filename. File not found</b><br>\";\r\n			return false;\r\n		}\r\n		else{\r\n			$details[\'uncsize\'] = strlen($filename);\r\n			// DATA is given.. use it! :|\r\n		}\r\n\r\n		// if data to compress is too small, just store it\r\n		if($details[\'uncsize\'] < 256){\r\n			$details[\'comsize\'] = $details[\'uncsize\'];\r\n			$details[\'vneeded\'] = 10;\r\n			$details[\'cmethod\'] = 0;\r\n			$zdata = &$data;\r\n		}\r\n		else{ // otherwise, compress it\r\n			$zdata = gzcompress($data);\r\n			$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)\r\n			$details[\'comsize\'] = strlen($zdata);\r\n			$details[\'vneeded\'] = 10;\r\n			$details[\'cmethod\'] = 8;\r\n		}\r\n		\r\n		$details[\'bitflag\'] = 0;\r\n		$details[\'crc_32\']  = crc32($data);\r\n		\r\n		// Convert date and time to DOS Format, and set then\r\n		$lastmod_timeS  = str_pad(decbin(date(\'s\')>=32?date(\'s\')-32:date(\'s\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_timeM  = str_pad(decbin(date(\'i\')), 6, \'0\', STR_PAD_LEFT);\r\n		$lastmod_timeH  = str_pad(decbin(date(\'H\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateD  = str_pad(decbin(date(\'d\')), 5, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateM  = str_pad(decbin(date(\'m\')), 4, \'0\', STR_PAD_LEFT);\r\n		$lastmod_dateY  = str_pad(decbin(date(\'Y\')-1980), 7, \'0\', STR_PAD_LEFT);\r\n		\r\n		# echo \"ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (\".date(\"s H H\").\")\\n\";\r\n		# echo \"ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (\".date(\"d m Y\").\")\\n\";\r\n		$details[\'modtime\'] = bindec(\"$lastmod_timeH$lastmod_timeM$lastmod_timeS\");\r\n		$details[\'moddate\'] = bindec(\"$lastmod_dateY$lastmod_dateM$lastmod_dateD\");\r\n		\r\n		$details[\'offset\'] = ftell($fh);\r\n		fwrite($fh, $this->zipSignature);\r\n		fwrite($fh, pack(\'s\', $details[\'vneeded\'])); // version_needed\r\n		fwrite($fh, pack(\'s\', $details[\'bitflag\'])); // general_bit_flag\r\n		fwrite($fh, pack(\'s\', $details[\'cmethod\'])); // compression_method\r\n		fwrite($fh, pack(\'s\', $details[\'modtime\'])); // lastmod_time\r\n		fwrite($fh, pack(\'s\', $details[\'moddate\'])); // lastmod_date\r\n		fwrite($fh, pack(\'V\', $details[\'crc_32\']));  // crc-32\r\n		fwrite($fh, pack(\'I\', $details[\'comsize\'])); // compressed_size\r\n		fwrite($fh, pack(\'I\', $details[\'uncsize\'])); // uncompressed_size\r\n		fwrite($fh, pack(\'s\', strlen($cfilename)));   // file_name_length\r\n		fwrite($fh, pack(\'s\', 0));  // extra_field_length\r\n		fwrite($fh, $cfilename);    // file_name\r\n		// ignoring extra_field\r\n		fwrite($fh, $zdata);\r\n		\r\n		// Append it to central dir\r\n		$details[\'external_attributes\']  = (substr($cfilename, -1)==\'/\'&&!$zdata)?16:32; // Directory or file name\r\n		$details[\'comments\']             = $fileComments;\r\n		$this->appendCentralDir($cfilename, $details);\r\n		$this->files_count++;\r\n	}\r\n	Function setExtra($filename, $property, $value){\r\n		$this->centraldirs[$filename][$property] = $value;\r\n	}\r\n	Function save($zipComments=\'\'){\r\n		if(!($fh = &$this->fh))\r\n			$fh = fopen($this->filename, $this->overwrite?\'w\':\'a+\');\r\n		\r\n		$cdrec = \"\";\r\n		foreach($this->centraldirs as $filename=>$cd){\r\n			$cdrec .= $this->dirSignature;\r\n			$cdrec .= \"\\x0\\x0\";                  // version made by\r\n			$cdrec .= pack(\'v\', $cd[\'vneeded\']); // version needed to extract\r\n			$cdrec .= \"\\x0\\x0\";                  // general bit flag\r\n			$cdrec .= pack(\'v\', $cd[\'cmethod\']); // compression method\r\n			$cdrec .= pack(\'v\', $cd[\'modtime\']); // lastmod time\r\n			$cdrec .= pack(\'v\', $cd[\'moddate\']); // lastmod date\r\n			$cdrec .= pack(\'V\', $cd[\'crc_32\']);  // crc32\r\n			$cdrec .= pack(\'V\', $cd[\'comsize\']); // compressed filesize\r\n			$cdrec .= pack(\'V\', $cd[\'uncsize\']); // uncompressed filesize\r\n			$cdrec .= pack(\'v\', strlen($filename)); // file comment length\r\n			$cdrec .= pack(\'v\', 0);                // extra field length\r\n			$cdrec .= pack(\'v\', strlen($cd[\'comments\'])); // file comment length\r\n			$cdrec .= pack(\'v\', 0); // disk number start\r\n			$cdrec .= pack(\'v\', 0); // internal file attributes\r\n			$cdrec .= pack(\'V\', $cd[\'external_attributes\']); // internal file attributes\r\n			$cdrec .= pack(\'V\', $cd[\'offset\']); // relative offset of local header\r\n			$cdrec .= $filename;\r\n			$cdrec .= $cd[\'comments\'];\r\n		}\r\n		$before_cd = ftell($fh);\r\n		fwrite($fh, $cdrec);\r\n\r\n		// end of central dir\r\n		fwrite($fh, $this->dirSignatureE);\r\n		fwrite($fh, pack(\'v\', 0)); // number of this disk\r\n		fwrite($fh, pack(\'v\', 0)); // number of the disk with the start of the central directory\r\n		fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries \"on this disk\" \r\n		fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries overall \r\n		fwrite($fh, pack(\'V\', strlen($cdrec)));     // size of central dir \r\n		fwrite($fh, pack(\'V\', $before_cd));         // offset to start of central dir\r\n		fwrite($fh, pack(\'v\', strlen($zipComments))); // .zip file comment length\r\n		fwrite($fh, $zipComments);\r\n		\r\n		fclose($fh);\r\n	}\r\n	\r\n	// Private\r\n	Function appendCentralDir($filename,$properties){\r\n		$this->centraldirs[$filename] = $properties;\r\n	}\r\n}\r\n', '3b429f40e2c3a03c76f63a02d06fce61', '', '', 2, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('tom_image_grid', 0, 'Thomas Jund', 'http://sacripant.fr', '0.4.2', 'An optionnal grid display for images tab', '<h1>tom_image_grid 0.4</h1>\n\n<p><em>tom_image_grid</em> is a plugin for Textpattern 4.6.* <span class=\"caps\">CMS</span>. It allows a more compact display (as a grid) of the images list.</p>\n\n<p>The plugin adds two buttons<br />\n<img alt=\"\" src=\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0iZ3JpZCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzMzMiPg0KICAgIDxyZWN0IHg9IjMiICB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjMiICB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KPC9nPg0KPC9zdmc+DQo=\" /> <img alt=\"\" src=\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0ibGluZSI+DQogICAgPHJlY3QgeD0iMSIgeT0iMTUiIHdpZHRoPSIzIiBoZWlnaHQ9IjMiLz4NCiAgICA8cmVjdCB4PSIxIiB5PSI5IiAgd2lkdGg9IjMiIGhlaWdodD0iMyIvPg0KICAgIDxyZWN0IHg9IjEiIHk9IjMiICB3aWR0aD0iMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMTUiIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iOSIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMyIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQo8L2c+DQo8L3N2Zz4NCg==\" /><br />\nin the Image Tab of your back-office allowing you to switch from the standard display to a more compact grid display.</p>', 'if (@txpinterface == \'admin\') {\n    register_callback( \'tom_image_grid_markup\', \'image_ui\', \'extend_controls\');\n    register_callback( \'tom_image_grid_css\', \"image\");\n    register_callback( \'tom_image_grid_js\', \"image\");\n}\n\nfunction tom_image_grid_markup($event, $step, $default)\n{\n    $out = <<<HTML\n\n<form id=\"tom_ig_options\">\n    <label><input type=\"radio\" name=\"tom_ig_option\" id=\"tom_ig_option--grid\" /><span class=\"tom_ig_icon-grid\">Grid</span></label>\n    <label><input type=\"radio\" name=\"tom_ig_option\" id=\"tom_ig_option--line\" /><span class=\"tom_ig_icon-line\">Line</span></label>\n</form>\nHTML;\n    echo $default.$out;\n}\n\nfunction tom_image_grid_css()\n{\n    $sortby = gTxt(\'sort_by\');\n    $out = <<<CSS\n\n<style type=\"text/css\">\n\n/*\n** CSS for tom_image_grid plugin\n** By Sacripant — Thomas Jund\n** http://sacripant.fr\n*/\n\n.tom_ig .txp-list * {\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n.tom_ig .txp-listtables {\n    display: block;\n}\n\n.tom_ig#page-image .txp-list thead th {\n    /*display: none;*/\n    display: inline-block;\n    position: relative;\n    width: auto;\n\n}\n.tom_ig#page-image .txp-list thead th.images_detail {\n    display: none !important;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-multi-edit {\n    margin-right: 1em;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-multi-edit:after {\n    content: attr(title);\n    padding-left: .33em;\n}\n\n.tom_ig#page-image .txp-list thead th.txp-list-col-id {\n    margin-left: 3.6em;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-id:before {\n    content: \"Sort by\";\n    display: block;\n    padding: inherit;\n    padding-left: 0;\n    padding-right: .33em;\n\n    position: absolute;\n    width: 3.6em;\n    right: 100%;\n    top: 0;\n\n    text-align: right;\n\n    font-weight: normal;\n}\n\n\n.tom_ig#page-image .txp-list tbody tr {\n    display: inline-block;\n    /*vertical-align: mid;*/\n    width: 15em;\n    margin: .66em 1em 0 0\n}\n\n.tom_ig#page-image .txp-list tbody td,\n.tom_ig#page-image .txp-list tbody th {\n    display: block;\n    padding: 0.16em 0.33em;\n    text-overflow: ellipsis;\n    overflow: hidden;\n    white-space: nowrap;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-multi-edit {\n    float: right;\n    width: auto;\n}\n.tom_ig#page-image .txp-list td.txp-list-col-multi-edit input {\n    margin-bottom: 0;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail {\n    height: 12em;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail img {\n    max-width: 100%;\n    max-height: 100%;\n    height: auto;\n    width: auto;\n}\n\n/* No thumbnail display */\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail:not(.has-thumbnail) {\n    display: block;\n    background-color: #eee;\n    text-align: center;\n\n    height: 100%;\n    width: 100%;\n    border-width: 5.25em 5.5em;\n    border-style: solid;\n    border-top-color: #eee;\n    border-bottom-color: #eee;\n    border-left-color: #ddd;\n    border-right-color: #ddd;\n}\n\n\n\n@media only screen and (max-width: 480px) {\n    .tom_ig#page-image .txp-list tbody tr {\n        width: 140px;\n        margin: .66em .33em 0 0;\n    }\n\n    .tom_ig#page-image .txp-list td.thumbnail {\n        height: 112px;\n    }\n\n    .tom_ig .tom_ig--noThumb {\n        border-width: 45px 40px;\n    }\n\n\n}\n\n/*buttons\n----------*/\n\n#tom_ig_options input {\n    display: none;\n}\n\n#tom_ig_options label {\n    padding: .16em;\n    display: inline-block;\n    border: 1px solid transparent;\n    width: 20px;\n    height: 20px;\n    overflow: hidden;\n}\n#tom_ig_options label.selected {\n    border-color: #aaa;\n}\n#tom_ig_options label:not(.selected):hover {\n    cursor: pointer;\n    background-color: #eee;\n}\n.tom_ig_icon-grid:before {\n    content: url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0iZ3JpZCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzMzMiPg0KICAgIDxyZWN0IHg9IjMiICB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjMiICB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KPC9nPg0KPC9zdmc+DQo=\");\n}\n.tom_ig_icon-line:before {\n    content: url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0ibGluZSI+DQogICAgPHJlY3QgeD0iMSIgeT0iMTUiIHdpZHRoPSIzIiBoZWlnaHQ9IjMiLz4NCiAgICA8cmVjdCB4PSIxIiB5PSI5IiAgd2lkdGg9IjMiIGhlaWdodD0iMyIvPg0KICAgIDxyZWN0IHg9IjEiIHk9IjMiICB3aWR0aD0iMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMTUiIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iOSIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMyIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQo8L2c+DQo8L3N2Zz4NCg==\");\n}\n\n</style>\nCSS;\n    echo $out;\n}\n\nfunction tom_image_grid_js()\n{\n    $out = <<<JS\n\n<script type=\"text/javascript\">\n/*\n** JS for tom_image_grid plugin\n** By Sacripant — Thomas Jund\n** http://sacripant.fr\n*/\n\n$(function() {\n    var imageDisplayOption = $(\"#tom_ig_options\")\n    ,   radios = $(\'input\', imageDisplayOption)\n    ,   listtables = $(\'#images_form .txp-listtables\')\n    /*,   imagesTr = $(\'tr\', listtables)*/\n    ,   cookie = \'tom_image_grid\'\n    ,   cookieValue = getCookie(cookie)\n    ;\n\n\n    // Add radios in tab\n    imageDisplayOption.prependTo(listtables);\n\n\n    // Change Layout when radio checked\n    radios.change(function(event)\n    {\n        if ( this.id === \"tom_ig_option--grid\")\n        {\n            $(\'body\').addClass(\'tom_ig\');\n            setCookie(cookie, \'tom_ig_option--grid\');\n        }\n        else\n        {\n            $(\'body\').removeClass(\'tom_ig\');\n            setCookie(cookie, \'tom_ig_option--line\')\n        }\n\n        radios.parent().removeClass(\'selected\');\n        $(this).parent().addClass(\'selected\');\n    });\n\n    // Check & create cookie\n    if ( cookieValue )\n    {\n        var targetRadio = document.getElementById(cookieValue);\n    }\n    else\n    {\n        var targetRadio = document.getElementById(\'tom_ig_option--line\');\n        setCookie(cookie, \'tom_ig_option--line\')\n    };\n\n    $(targetRadio).prop(\'checked\', true).trigger(\"change\");\n});\n</script>\n\nJS;\n    echo $out;\n}', 'if (@txpinterface == \'admin\') {\n    register_callback( \'tom_image_grid_markup\', \'image_ui\', \'extend_controls\');\n    register_callback( \'tom_image_grid_css\', \"image\");\n    register_callback( \'tom_image_grid_js\', \"image\");\n}\n\nfunction tom_image_grid_markup($event, $step, $default)\n{\n    $out = <<<HTML\n\n<form id=\"tom_ig_options\">\n    <label><input type=\"radio\" name=\"tom_ig_option\" id=\"tom_ig_option--grid\" /><span class=\"tom_ig_icon-grid\">Grid</span></label>\n    <label><input type=\"radio\" name=\"tom_ig_option\" id=\"tom_ig_option--line\" /><span class=\"tom_ig_icon-line\">Line</span></label>\n</form>\nHTML;\n    echo $default.$out;\n}\n\nfunction tom_image_grid_css()\n{\n    $sortby = gTxt(\'sort_by\');\n    $out = <<<CSS\n\n<style type=\"text/css\">\n\n/*\n** CSS for tom_image_grid plugin\n** By Sacripant — Thomas Jund\n** http://sacripant.fr\n*/\n\n.tom_ig .txp-list * {\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n}\n.tom_ig .txp-listtables {\n    display: block;\n}\n\n.tom_ig#page-image .txp-list thead th {\n    /*display: none;*/\n    display: inline-block;\n    position: relative;\n    width: auto;\n\n}\n.tom_ig#page-image .txp-list thead th.images_detail {\n    display: none !important;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-multi-edit {\n    margin-right: 1em;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-multi-edit:after {\n    content: attr(title);\n    padding-left: .33em;\n}\n\n.tom_ig#page-image .txp-list thead th.txp-list-col-id {\n    margin-left: 3.6em;\n}\n.tom_ig#page-image .txp-list thead th.txp-list-col-id:before {\n    content: \"Sort by\";\n    display: block;\n    padding: inherit;\n    padding-left: 0;\n    padding-right: .33em;\n\n    position: absolute;\n    width: 3.6em;\n    right: 100%;\n    top: 0;\n\n    text-align: right;\n\n    font-weight: normal;\n}\n\n\n.tom_ig#page-image .txp-list tbody tr {\n    display: inline-block;\n    /*vertical-align: mid;*/\n    width: 15em;\n    margin: .66em 1em 0 0\n}\n\n.tom_ig#page-image .txp-list tbody td,\n.tom_ig#page-image .txp-list tbody th {\n    display: block;\n    padding: 0.16em 0.33em;\n    text-overflow: ellipsis;\n    overflow: hidden;\n    white-space: nowrap;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-multi-edit {\n    float: right;\n    width: auto;\n}\n.tom_ig#page-image .txp-list td.txp-list-col-multi-edit input {\n    margin-bottom: 0;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail {\n    height: 12em;\n}\n\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail img {\n    max-width: 100%;\n    max-height: 100%;\n    height: auto;\n    width: auto;\n}\n\n/* No thumbnail display */\n.tom_ig#page-image .txp-list td.txp-list-col-thumbnail:not(.has-thumbnail) {\n    display: block;\n    background-color: #eee;\n    text-align: center;\n\n    height: 100%;\n    width: 100%;\n    border-width: 5.25em 5.5em;\n    border-style: solid;\n    border-top-color: #eee;\n    border-bottom-color: #eee;\n    border-left-color: #ddd;\n    border-right-color: #ddd;\n}\n\n\n\n@media only screen and (max-width: 480px) {\n    .tom_ig#page-image .txp-list tbody tr {\n        width: 140px;\n        margin: .66em .33em 0 0;\n    }\n\n    .tom_ig#page-image .txp-list td.thumbnail {\n        height: 112px;\n    }\n\n    .tom_ig .tom_ig--noThumb {\n        border-width: 45px 40px;\n    }\n\n\n}\n\n/*buttons\n----------*/\n\n#tom_ig_options input {\n    display: none;\n}\n\n#tom_ig_options label {\n    padding: .16em;\n    display: inline-block;\n    border: 1px solid transparent;\n    width: 20px;\n    height: 20px;\n    overflow: hidden;\n}\n#tom_ig_options label.selected {\n    border-color: #aaa;\n}\n#tom_ig_options label:not(.selected):hover {\n    cursor: pointer;\n    background-color: #eee;\n}\n.tom_ig_icon-grid:before {\n    content: url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0iZ3JpZCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzMzMiPg0KICAgIDxyZWN0IHg9IjMiICB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjMiICB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIxMiIgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KICAgIDxyZWN0IHg9IjEyIiB5PSIzIiAgd2lkdGg9IjYiIGhlaWdodD0iNiIvPg0KPC9nPg0KPC9zdmc+DQo=\");\n}\n.tom_ig_icon-line:before {\n    content: url(\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQo8ZyBpZD0ibGluZSI+DQogICAgPHJlY3QgeD0iMSIgeT0iMTUiIHdpZHRoPSIzIiBoZWlnaHQ9IjMiLz4NCiAgICA8cmVjdCB4PSIxIiB5PSI5IiAgd2lkdGg9IjMiIGhlaWdodD0iMyIvPg0KICAgIDxyZWN0IHg9IjEiIHk9IjMiICB3aWR0aD0iMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMTUiIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iOSIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQogICAgPHJlY3QgeD0iNiIgeT0iMyIgIHdpZHRoPSIxMyIgaGVpZ2h0PSIzIi8+DQo8L2c+DQo8L3N2Zz4NCg==\");\n}\n\n</style>\nCSS;\n    echo $out;\n}\n\nfunction tom_image_grid_js()\n{\n    $out = <<<JS\n\n<script type=\"text/javascript\">\n/*\n** JS for tom_image_grid plugin\n** By Sacripant — Thomas Jund\n** http://sacripant.fr\n*/\n\n$(function() {\n    var imageDisplayOption = $(\"#tom_ig_options\")\n    ,   radios = $(\'input\', imageDisplayOption)\n    ,   listtables = $(\'#images_form .txp-listtables\')\n    /*,   imagesTr = $(\'tr\', listtables)*/\n    ,   cookie = \'tom_image_grid\'\n    ,   cookieValue = getCookie(cookie)\n    ;\n\n\n    // Add radios in tab\n    imageDisplayOption.prependTo(listtables);\n\n\n    // Change Layout when radio checked\n    radios.change(function(event)\n    {\n        if ( this.id === \"tom_ig_option--grid\")\n        {\n            $(\'body\').addClass(\'tom_ig\');\n            setCookie(cookie, \'tom_ig_option--grid\');\n        }\n        else\n        {\n            $(\'body\').removeClass(\'tom_ig\');\n            setCookie(cookie, \'tom_ig_option--line\')\n        }\n\n        radios.parent().removeClass(\'selected\');\n        $(this).parent().addClass(\'selected\');\n    });\n\n    // Check & create cookie\n    if ( cookieValue )\n    {\n        var targetRadio = document.getElementById(cookieValue);\n    }\n    else\n    {\n        var targetRadio = document.getElementById(\'tom_ig_option--line\');\n        setCookie(cookie, \'tom_ig_option--line\')\n    };\n\n    $(targetRadio).prop(\'checked\', true).trigger(\"change\");\n});\n</script>\n\nJS;\n    echo $out;\n}', 'b1e6a93ac5517f08a74c0fe15a9e46d3', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_macro', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.41', 'Define custom macros/virtual Textpattern tags for your site', '<h1 id=\"smd_top\">smd_macro</h1>\n\n<p>Build your own virtual Textpattern tags from combinations of built-in tags and plugins &#8212; even <span class=\"caps\">PHP</span>. Thus you can ease your workflow or offer your clients simpler syntax for doing powerful things in their Pages, Forms, and Articles.</p>\n\n<h2 id=\"smd_feat\">Features:</h2>\n\n<ul>\n	<li>Define macros &#8212; custom, virtual Txp tags &#8212; with any number of attributes</li>\n	<li>Construct a definition block &#8212; like a Txp Form &#8212; that contains the tags / code that make up your macro</li>\n	<li>Inject replacement values (read from your attributes) into macro definitions at runtime</li>\n	<li>Import / export macros for archive / sharing with the community</li>\n	<li>Macros can be single or containers</li>\n</ul>\n\n<h2>Installation / uninstallation</h2>\n\n<p class=\"information\">Requires Textpattern 4.5.2+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1213/smd_macro\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp <em>Admin -&gt; Plugins</em> pane, install and enable the plugin. To uninstall, delete from the <em>Admin -&gt; Plugins</em> page.</p>\n\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35772\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<h2 id=\"smd_usage\">Usage</h2>\n\n<p>Visit the <em>Content -&gt; Macros</em> tab. When there are macros defined, the dropdown at the top-right of the panel contains a list of all macros that have been defined. Choose one to load it into the boxes below for editing.</p>\n\n<p>The boxes are:</p>\n\n<ul>\n	<li><span class=\"atnm\">Name</span> : name of your macro/virtual tag<sup class=\"footnote\" id=\"fnrev55340768858831bfe392d1-1\"><a href=\"#fn55340768858831bfe392d1-1\">1</a></sup>. Click the adjacent [x] button to delete it.</li>\n	<li><span class=\"atnm\">Description</span> : optional description for your own documentation &#8212; not used anywhere else at present.</li>\n	<li><span class=\"atnm\">Attributes</span> : specify up to three values for each attribute<sup class=\"footnote\" id=\"fnrev55340768858831bfe392d1-2\"><a href=\"#fn55340768858831bfe392d1-2\">2</a></sup>. Click the [+] button to create more. Clear the <em>name</em> box before Saving to delete that attribute. The three values are:\n	<ul>\n		<li><span class=\"atnm\">Name</span> : the name of your attribute that clients will use in your tag. Must be a valid <span class=\"caps\">ASCII</span> name.</li>\n		<li><span class=\"atnm\">Default</span> : the default value that this attribute will take on if not supplied. Leave empty for <code>&quot;&quot;</code>.</li>\n		<li><span class=\"atnm\">Replacement</span> : the name of the <code>{replacement}</code> that this attribute will occupy in your definitions. Specify it <em>without</em> the <code>{}</code> brackets. If this entry is omitted, the attribute&#8217;s <code>Name</code> will be used.</li>\n	</ul></li>\n	<li><span class=\"atnm\">Macro definition</span> : your tag code here. At any point you can insert the name of any of your defined attribute replacement values inside <code>{}</code> marks to have the plugin inject the attribute into your markup when the client uses the tag. See <a href=\"#smd_examples\">examples</a> for more.</li>\n</ul>\n\n<p class=\"footnote\" id=\"fn55340768858831bfe392d1-1\"><sup>1</sup> <span class=\"caps\">IMPORTANT</span>: your tag <strong><span class=\"caps\">MUST</span></strong> adhere to the following conventions:</p>\n\n<ul>\n	<li>an <span class=\"caps\">ASCII</span> name &#8212; no foreign characters, hyphens or anything other than alphanumeric characters and underscores. In addition it may <em>not</em> begin with a number.</li>\n	<li>double check before saving that your tag does not have the same name as an existing Txp or <span class=\"caps\">PHP</span> function. If it does, your site <span class=\"caps\">WILL</span> blow up with nasty errors. The best way to avoid this is to make sure you prefix your tags with a three-letter prefix, just as you would if this was your own plugin.</li>\n</ul>\n\n<p>The plugin tries to shield you from invalid names as far as possible and will usually detect internal <span class=\"caps\">PHP</span>/Txp function name clashes, admin-side plugin clashes as well as invalid/previously defined macro names. But it cannot legislate for functions that are going to become available on your public site, i.e. tags/functions added by public-side plugins. Please exercise caution when naming your macros.</p>\n\n<p class=\"footnote\" id=\"fn55340768858831bfe392d1-2\"><sup>2</sup> Make sure you avoid the pipe symbol <code>|</code> in your attribute names / defaults / replacements as this character is used internally by the plugin.</p>\n\n<h3>Container macros</h3>\n\n<p>You may use your new macros as container tags. In your macro definition, specify the special replacement tag <code>{smd_container}</code> where you want the contained content to go. If the container is empty, nothing is output <em>unless</em> you define an attribute named <code>smd_container</code> and give it some Default content. In that case, your <code>{smd_container}</code> replacement tag will take on the default value assigned to that attribute. Note that the <em>Replacement name</em> doesn&#8217;t matter and can be omitted: the replacement is <em>always</em> going to be <code>{smd_container}</code>.</p>\n\n<p>Note this means that if you add an attribute <code>smd_container=&quot;some value&quot;</code> to your macro when it&#8217;s used in your page, it does the same thing as using the container. This could be construed a feature.</p>\n\n<h2 id=\"smd_import\">Importing macros</h2>\n\n<p>You can import macros that have been previously exported from the plugin. Click the <em>Import</em> link and choose the <code>smd_macros.ini</code> file that you have been given (the file name itself doesn&#8217;t actually matter). If you want to overwrite any macros of the same name that already exist in your system, check the <em>Force overwrite</em> box. Otherwise, any macros in the import file that already exist will be skipped.</p>\n\n<p>Note that the plugin makes some checks on your system and will try and bail out gracefully if the necessary permissions are not set (it temporarily uploads files to Txp&#8217;s temp directory, as defined in <em>Advanced Prefs</em>). It also imposes a soft limit of roughly 15KB on the size of the imported file, mostly for reasons of speed. If you find yourself requiring to import large numbers of macros in one file, you can increase this limit by creating the following global system preference (smd_prefalizer can help):</p>\n\n<ul>\n	<li><span class=\"atnm\">Pref name</span>: smd_macro_import_filesize</li>\n	<li><span class=\"atnm\">Value</span> : maximum file size (in bytes)</li>\n	<li><span class=\"atnm\">Visibility</span> : Hidden</li>\n	<li><span class=\"atnm\">Event</span> : smd_macro</li>\n</ul>\n\n<p>Alternatively, split your macros into a few smaller files and import them separately.</p>\n\n<h2 id=\"smd_export\">Exporting macros</h2>\n\n<p>At any time you can export one or more macros to a file for distribution or backup. Just click the <em>Export</em> link and choose one or more macros from the list that appears. Click <em>Go</em> and then save the resulting file to your filesystem. The current macro that is being edited (if any) is selected by default.</p>\n\n<h2 id=\"smd_clone\">Cloning macros</h2>\n\n<p>While a macro is loaded into the edit boxes you can clone it. Click the <em>Clone</em> button, supply a name for the clone and hit <em>Go</em>. Provided the name isn&#8217;t invalid or in use, your new clone will be created and brought into the edit area for immediate editing.</p>\n\n<p>Note that the clone will be created from the chosen macro as it was last saved to the database. If you have made unsaved edits to the macro in the edit boxes at the time you clone the macro, they will be lost.</p>\n\n<h2 id=\"smd_examples\">Examples</h2>\n\n<h3 id=\"smd_eg1\">Example 1: image gallery</h3>\n\n<ul>\n	<li><span class=\"atnm\">Macro name</span> : my_gallery</li>\n	<li><span class=\"atnm\">Attributes (default / replacement)</span> : category (empty / img_cat)</li>\n</ul>\n\n<pre class=\"block\"><code>&lt;txp:images category=&quot;{img_cat}&quot;&gt;\n   &lt;txp:image /&gt;\n   &lt;div class=&quot;img_info&quot;&gt;&lt;txp:image_info /&gt; by &lt;txp:image_author /&gt;&lt;/div&gt;\n&lt;/txp:images&gt;</code></pre>\n\n<p>Thus, <code>&lt;txp:my_gallery category=&quot;animals&quot; /&gt;</code> will render an image gallery from the given category, complete with caption and author info. Note that no default value is given so if the <code>category</code> attribute is not supplied, no gallery will be produced.</p>\n\n<h3 id=\"smd_eg2\">Example 2: interface to smd_query</h3>\n\n<ul>\n	<li><span class=\"atnm\">Macro name</span> : my_col_from_table</li>\n	<li><span class=\"atnm\">Attributes (default / replacement)</span> : column (ID / col), table (textpattern)</li>\n</ul>\n\n<pre class=\"block\"><code>&lt;p&gt;The contents of the {col} column\nfrom the {table} table is:&lt;/p&gt;\n&lt;txp:smd_query column=&quot;{col}&quot; table=&quot;{table}&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n   {{col}}\n&lt;/txp:smd_query&gt;</code></pre>\n\n<p>Anybody using <code>&lt;txp:my_col_from_table column=&quot;Title&quot; /&gt;</code> would see a list of all article titles. If they added <code>table=&quot;txp_file&quot;</code> they&#8217;d see all titles from all uploaded files. Things to note:</p>\n\n<ul>\n	<li>No replacement variable specified for the <code>table</code> attribute, therefore it takes on the name of the attribute itself.</li>\n	<li>The double set of <code>{{}}</code> in the smd_query container is required because you first want the macro to replace <code>{col}</code> with the value from your macro&#8217;s <code>column</code> attribute, i.e. if you&#8217;d chosen <code>column=&quot;Posted&quot;</code> then <code>{{col}}</code> becomes <code>{Posted}</code>. After that, smd_query searches for <code>{Posted}</code> and replaces it with the contents of the &#8216;Posted&#8217; column for each row in the table.</li>\n</ul>\n\n<h2>Author / credits</h2>\n\n<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Spawned from an idea by jpdupont, with thanks. Also big props to the beta test team: primarily jpdupont, pieman, mrdale, maverick, maruchan and jakob.</p>', '/**\n * smd_macro\n *\n * A Textpattern CMS plugin for creating new tags:\n *  -> Define new <txp:tags> to do any task you like -- lightbox effect, comment scheme...\n *  -> Add arbitrary numbers of attributes and set defaults if you wish\n *  -> Import / export your creations to share new tags with the community\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n * @todo   Allow apostrophes / tags-in-tags in container code\n */\n\nif (txpinterface === \'admin\') {\n    global $smd_macro_event;\n    $smd_macro_event = \'smd_macro\';\n\n    add_privs($smd_macro_event,\'1,2\');\n    register_tab(\'content\', $smd_macro_event, gTxt(\'smd_macro_tab_name\'));\n    register_callback(\'smd_macro_dispatcher\', \'smd_macro\');\n    register_callback(\'smd_macro_upload_form\', $smd_macro_event.\'_ui\', \'inputlabel.\'.$smd_macro_event.\'-upload\');\n    register_callback(\'smd_macro_welcome\', \'plugin_lifecycle.smd_macro\');\n}\n\nif (!defined(\'SMD_MACRO\')) {\n    define(\"SMD_MACRO\", \'smd_macro\');\n}\n\n// Bootstrap the function insertion\nregister_callback(\'smd_macro_boot\', \'pretext\');\n\n// ********************\n// ADMIN SIDE INTERFACE\n// ********************\n\n// ------------------------\nfunction smd_macro_dispatcher($evt, $stp)\n{\n    global $smd_macro_event;\n\n    $available_steps = array(\n        \'smd_macro_table_install\' => true,\n        \'smd_macro_table_remove\'  => true,\n        \'smd_macro_prefsave\'      => true,\n        \'smd_macro_save\'          => true,\n        \'smd_macro_delete\'        => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = $smd_macro_event;\n    }\n\n    $stp();\n}\n\n// ------------------------\nfunction smd_macro_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_macro_table_install(0);\n            $msg = \'Go go gadget macro!\';\n            break;\n        case \'deleted\':\n            smd_macro_table_remove(0);\n            break;\n    }\n\n    return $msg;\n}\n\n// ------------------------\nfunction smd_macro($msg = \'\')\n{\n    global $smd_macro_event;\n\n    if (!smd_macro_table_exist(1)) {\n        smd_macro_table_install(0);\n    }\n\n    $export_list = gps(\'smd_macro_export\');\n    $max_file_size = get_pref(\'smd_macro_import_filesize\', 15 * 1024);\n\n    if (gps(\'step\') == \'smd_macro_export\' && $export_list) {\n        // Generate an associative array that is translated to an ini file\n        $out = array();\n        $rows = safe_rows(\'*\', SMD_MACRO, \"macro_name IN (\'\".join(\"\',\'\", doSlash($export_list)).\"\')\");\n\n        foreach ($rows as $row) {\n            $atts = unserialize($row[\'attributes\']);\n            $outatts = array();\n\n            foreach ($atts as $att => $vals) {\n                $outatts[] = utf8_decode(join(\'|\', array($att, $vals[\'default\'], $vals[\'rep\'])));\n            }\n\n            $out[$row[\'macro_name\']] = array(\n                \'description\' => $row[\'description\'],\n                \'attributes\' => $outatts,\n                \'definition\' => $row[\'definition\'],\n            );\n        }\n\n        $content = smd_macro_create_ini($out, array(\'definition\' => \'base64_encode\'));\n        header(\"Content-Type: text/plain; charset=utf-8\");\n        header(\"Content-Disposition: attachment; filename=\\\"smd_macros.ini\\\"\");\n        header(\"Content-Length: \" . mb_strlen($content));\n        header(\"Content-Transfer-Encoding: binary\");\n        header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\n        header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\n        print($content);\n        exit;\n\n    } elseif (gps(\'step\') == \'smd_macro_import\') {\n        $overwrite = gps(\'smd_macro_import_overwrite\');\n\n        $file = @get_uploaded_file($_FILES[\'thefile\'][\'tmp_name\'], get_pref(\'tempdir\').DS.basename($_FILES[\'thefile\'][\'tmp_name\']));\n\n        if ($file === false) {\n            $msg = array(gTxt(\'smd_macro_cannot_import\'), E_WARNING);\n        } else {\n            $size = filesize($file);\n\n            if ($max_file_size < $size) {\n                $msg = array(gTxt(\'smd_macro_file_size\'), E_WARNING);\n            } else {\n                $ini = @parse_ini_file($file, true);\n\n                if ($ini) {\n                    $done = array(\'ok\' => array(), \'nok\' => array(), \'skip\' => array());\n\n                    foreach ($ini as $key => $val) {\n                        $atts = array();\n                        $def = $desc = \'\';\n\n                        if (isset($val[\'attributes\'])) {\n                            foreach ($val[\'attributes\'] as $attlist) {\n                                list($nam, $dflt, $rep) = explode(\'|\', utf8_encode($attlist));\n                                $atts[strtolower(str_replace(\'-\', \'_\', sanitizeForUrl($nam)))] = array(\n                                    \'default\' => $dflt,\n                                    \'rep\' => $rep\n                                );\n                            }\n                        }\n\n                        if (isset($val[\'description\'])) {\n                            $desc = $val[\'description\'];\n                        }\n\n                        if (isset($val[\'definition\'])) {\n                            $def = base64_decode($val[\'definition\']);\n                        }\n\n                        $result = (string)smd_macro_save_direct($key, $desc, $atts, $def, $overwrite);\n                        switch ($result) {\n                            case \'SMD_MACRO_BAD_NAME\':\n                            case \'SMD_MACRO_CLASH\':\n                                $done[\'nok\'][] = $key;\n                                break;\n                            case \'SMD_MACRO_SKIP\':\n                                $done[\'skip\'][] = $key;\n                                break;\n                            default:\n                                $done[\'ok\'][] = $key;\n                                break;\n                        }\n                    }\n\n                    $msg = (($done[\'ok\']) ? gTxt(\'smd_macro_imported\') . join(\', \', $done[\'ok\']).br : \'\')\n                        . (($done[\'skip\']) ? gTxt(\'smd_macro_skipped\') . join(\', \', $done[\'skip\']).br : \'\')\n                        . (($done[\'nok\']) ? gTxt(\'smd_macro_not_imported\') . join(\', \', $done[\'nok\']) : \'\');\n                } else {\n                    $msg = array(gTxt(\'smd_macro_invalid_ini\'), E_WARNING);\n                }\n            }\n            unlink($file);\n        }\n    } elseif (gps(\'step\') == \'smd_macro_clone\') {\n        $source = safe_row(\'*\', SMD_MACRO, \"macro_name=\'\".doSlash(gps(\'smd_macro_name\')).\"\'\");\n        $name = gps(\'smd_macro_new_name\');\n        $desc = $source[\'description\'];\n        $def = $source[\'definition\'];\n        $atts = $source[\'attributes\'];\n\n        $result = (string)smd_macro_save_direct($name, $desc, $atts, $def, false);\n\n        switch($result) {\n            case \'SMD_MACRO_BAD_NAME\':\n                $msg = array(gTxt(\'smd_macro_invalid\'), E_WARNING);\n                break;\n            case \'SMD_MACRO_CLASH\':\n            case \'SMD_MACRO_SKIP\':\n                $msg = array(gTxt(\'smd_macro_exists\'), E_WARNING);\n                break;\n            default:\n                $msg = gTxt(\'smd_macro_created\');\n                $_POST[\'smd_macro_name\'] = $name; // Inject the new macro into the post stream so it\'s selected by default\n                break;\n        }\n    }\n\n    pagetop(gTxt(\'smd_macro_tab_name\'), $msg);\n\n    $macros = safe_rows(\'*\', SMD_MACRO, \'1=1 ORDER BY macro_name\');\n    $macro_name = $macro_new_name = gps(\'smd_macro_name\');\n    $macro_description = $macro_def = $macro_code = \'\';\n    $macro_atts = array();\n\n    // Build the select list if possible\n    $macrolist = array();\n\n    foreach ($macros as $idx => $macro) {\n        $macrolist[$macro[\'macro_name\']] = $macro[\'macro_name\'];\n\n        if ($macro[\'macro_name\'] == $macro_name) {\n            $macro_new_name = $macro[\'macro_name\'];\n            $macro_description = $macro[\'description\'];\n            $macro_atts = $macro[\'attributes\'] ? unserialize($macro[\'attributes\']) : array();\n            $macro_def = $macro[\'definition\'];\n        }\n    }\n\n    $macroSelector = ($macrolist) ? selectInput(\'smd_macro_name\', $macrolist, $macro_name, true, 1) : \'\';\n    $multi_macsel = \'\';\n\n    if ($macrolist) {\n        $multi_macsel = \'<select name=\"smd_macro_export[]\" id=\"smd_macro_export_list\" size=\"8\" class=\"list\" multiple=\"multiple\">\'.n;\n        foreach ($macrolist as $key => $val) {\n            $multi_macsel .= \'<option value=\"\'.$key.\'\"\' . (($macro_name==$key) ? \' selected=\"selected\"\' : \'\') .\'>\'.$val.\'</option>\'.n;\n        }\n        $multi_macsel .= \'</select>\';\n    }\n\n    $editcell = ($macrolist) ? \'<label>\'.gTxt(\'smd_macro_choose\').\'</label>\'.n.$macroSelector : \'\';\n\n    // Edit form\n    echo n, \'<h1 class=\"txp-heading\">\', gTxt(\'smd_macro_tab_name\'), \'</h1>\',\n        n, \'<div id=\"smd_macro_control\" class=\"txp-control-panel\">\',\n        n, \'<form id=\"smd_macro_select\" action=\"index.php\" method=\"post\">\',\n        n, \'<p id=\"smd_macro_select\">\',\n        n, $editcell,\n        n, eInput($smd_macro_event),\n        n, \'</form>\',\n\n        // Import / export form\n        n, \'<a id=\"smd_macro_import\" href=\"#\">\'.gTxt(\'smd_macro_import\').\'</a>\',\n        n, (($multi_macsel)\n            ? \' / \' . \'<a id=\"smd_macro_export\" href=\"#\">\'.gTxt(\'smd_macro_export\').\'</a>\'\n                . (($macro_name) ? \' / \' . \'<a id=\"smd_macro_clone\" href=\"#\">\'.gTxt(\'smd_macro_clone\').\'</a>\' : \'\')\n            : \'\'),\n        n, \'<div id=\"smd_macro_import_holder\" class=\"smd_hidden\">\',\n        n, upload_form(gTxt(\'smd_macro_upload\'), \'\', \'smd_macro_import\', $smd_macro_event, \'\'),\n        n, \'</div>\',\n        n, (($multi_macsel)\n                ? \'<div id=\"smd_macro_export_holder\" class=\"smd_hidden\"><form id=\"smd_macro_export_form\" action=\"index.php\" method=\"post\">\'\n                    . $multi_macsel . br\n                    . fInput(\'submit\', \'smd_macro_export_go\', gTxt(\'go\'), \'\', \'\', \'smd_macro_export_close();\')\n                    . eInput($smd_macro_event)\n                    . sInput(\'smd_macro_export\')\n                    . \'</form></div>\'\n                    . \'<div id=\"smd_macro_clone_holder\" class=\"smd_hidden\"><form id=\"smd_macro_clone_form\" action=\"index.php\" method=\"post\">\'\n                    . hInput(\'smd_macro_name\', $macro_name)\n                    . fInput(\'text\', \'smd_macro_new_name\', \'\')\n                    . fInput(\'submit\', \'smd_macro_clone_go\', gTxt(\'go\'))\n                    . eInput($smd_macro_event)\n                    . sInput(\'smd_macro_clone\')\n                    . tInput()\n                    . \'</form></div>\'\n                : \'\'\n            ),\n        n, \'</div>\';\n\n    echo n, \'<div class=\"txp-container\">\',\n        n, \'<form id=\"smd_macro_form\" action=\"index.php\" method=\"post\">\',\n        n, startTable(),\n        n, tr(\n            fLabelCell(\'name\', \'\', \'smd_macro_new_name\')\n            .n.td(\n                fInput(\'text\', \'smd_macro_new_name\', $macro_new_name).fInput(\'hidden\', \'smd_macro_name\', $macro_name)\n                .(($macro_name == \'\') ? \'\' : sp.\'<a href=\"?event=\'.$smd_macro_event. a.\'step=smd_macro_delete\' . a . \'smd_macro_name=\'.urlencode($macro_name). a . \'_txp_token=\'.form_token() . \'\" onclick=\"return confirm(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\');\">[x]</a>\')\n            )\n        ),\n        n, tr(\n            fLabelCell(\'description\', \'\', \'smd_macro_description\')\n            .n.td(\n                fInput(\'text\', \'smd_macro_description\', $macro_description, \'\', \'\', \'\', \'66\')\n            )\n        );\n\n    $attlist = array(\'<table id=\"smd_macro_attlist\"><thead><tr><th>\'.gTxt(\'name\').\'</th><th>\'.gTxt(\'default\').\'</th><th>\'.gTxt(\'smd_macro_repname\').\'</th></tr></thead><tbody>\');\n    $macro_atts = empty($macro_atts) ? array(\'\' => array(\'default\' => \'\', \'rep\' => \'\')) : $macro_atts;\n\n    foreach ($macro_atts as $attname => $att) {\n        $attlist[] = \'<tr class=\"smd_macro_att\">\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attname[]\', $attname). \'</td>\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attdflt[]\', $att[\'default\']). \'</td>\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attrep[]\', $att[\'rep\']). \'</td>\'\n            .\'</tr>\';\n    }\n    $attlist[] = \'</tbody></table>\';\n\n    $atts = join(n, $attlist);\n    echo n, tr(\n        tda(\'<label>\'.gTxt(\'smd_macro_attributes\').\'</label>\'.n.\'<a href=\"#\" id=\"smd_macro_att_add\" title=\"\'.gTxt(\'smd_macro_att_clone_help\').\'\">\'.gTxt(\'smd_macro_att_clone\').\'</a>\')\n        .tda($atts, \' id=\"smd_macro_att\"\')\n        ),\n        n, tr(\n            tda(\'<label>\'.gTxt(\'smd_macro_tag_definition\').\'</label>\')\n            .n.tda(\n                text_area(\'smd_macro_definition\', 250, 700, $macro_def)\n            )\n        ),\n        n, tr(td(\'&nbsp;\').eInput($smd_macro_event).sInput(\'smd_macro_save\').td(fInput(\'submit\', \'save\', gTxt(\'save\')))),\n        n, tInput(),\n        n, endTable(),\n        n, \'</form>\',\n        n, \'</div>\',\n        n, script_js(<<<EOJS\njQuery(function() {\n    jQuery(\'#smd_macro_att_add\').click(function(ev) {\n        var obj = jQuery(\'#smd_macro_attlist tbody\');\n        var elems = jQuery(\'.smd_macro_att\');\n\n        // Add the row, empty it and focus. Can\'t do this in any fewer jQuery() calls for some reason\n        obj.append(obj.children().eq(0).clone());\n        obj.children().last().find(\'input:text\').val(\'\');\n        obj.children().last().find(\'input:text\').first().focus();\n        ev.preventDefault();\n    });\n\n    // Import link\n    jQuery(\'#smd_macro_import\').click(function(ev) {\n        jQuery(\'#smd_macro_export_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_import_holder\').toggle(\'normal\');\n    });\n\n    // Export link\n    jQuery(\'#smd_macro_export\').click(function(ev) {\n        jQuery(\'#smd_macro_import_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_export_holder\').toggle(\'normal\');\n    });\n\n    // Clone link\n    jQuery(\'#smd_macro_clone\').click(function(ev) {\n        jQuery(\'#smd_macro_import_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_export_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').toggle(\'normal\').find(\"input[name=\'smd_macro_new_name\']\").focus();\n    });\n});\nfunction smd_macro_export_close() {\n    jQuery(\"#smd_macro_export\").click();\n}\nEOJS\n        ),\n        <<<EOCSS\n<style type=\"text/css\">\n.smd_hidden {\n    display:none;\n}\n</style>\nEOCSS;\n}\n\n// ------------------------\n// Add an overwrite checkbox to the upload form\nfunction smd_macro_upload_form($evt, $stp, $data, $args)\n{\n    return $data .\n        checkbox(\'smd_macro_import_overwrite\', \'1\', false, 0, \'smd_macro_import_overwrite\') .\n        \'<label for=\"smd_macro_import_overwrite\">\' . gTxt(\'smd_macro_overwrite\') . \'</label>\';\n}\n\n// ------------------------\n// Takes function params; assumes attribute sanitization done already\nfunction smd_macro_save_direct($name = \'\', $desc = \'\', $atts = array(), $def = \'\', $overwrite = false)\n{\n    global $smd_macro_event;\n\n    $smd_macro_new_name = sanitizeForUrl($name);\n    $smd_macro_description = doSlash($desc);\n    $smd_macro_definition = $def;\n\n    $ret = \'\';\n\n    if (smd_macro_valid($smd_macro_new_name)) {\n        $code = doSlash(smd_macro_build($smd_macro_new_name, $atts, $smd_macro_definition));\n\n        $smd_macro_definition = doSlash($smd_macro_definition);\n        $smd_macro_atts = is_array($atts) ? serialize($atts) : $atts;\n\n        if (smd_macro_table_exist()) {\n            // Check if this macro name clashes with a built-in PHP/Txp function\n            $exists = smd_macro_exists($smd_macro_new_name, false);\n\n            if ($exists === false) {\n                $indb = safe_field(\'macro_name\', SMD_MACRO, \"macro_name=\'$smd_macro_new_name\'\");\n\n                if ($indb) {\n                    if ($overwrite) {\n                        // Update\n                        $ret = safe_update(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\", \"macro_name=\'$smd_macro_new_name\'\");\n                    } else {\n                        $ret = \'SMD_MACRO_SKIP\';\n                    }\n                } else {\n                    // Insert\n                    $ret = safe_insert(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\");\n                }\n            } else {\n                $ret = \'SMD_MACRO_CLASH\';\n            }\n        }\n    } else {\n        $ret = \'SMD_MACRO_BAD_NAME\';\n    }\n\n    // $ret may be empty only if macro table not installed\n    return $ret;\n}\n\n// ------------------------\n// Takes URL params; sanitizes attributes locally\nfunction smd_macro_save()\n{\n    global $smd_macro_event;\n\n    extract(doSlash(gpsa(array(\n        \'smd_macro_name\',\n        \'smd_macro_new_name\',\n        \'smd_macro_description\',\n    ))));\n\n    $msg = \'\';\n    $smd_macro_new_name = sanitizeForUrl($smd_macro_new_name);\n\n    if (smd_macro_valid($smd_macro_new_name)) {\n        $att_name = gps(\'smd_macro_attname\');\n        $att_dflt = gps(\'smd_macro_attdflt\');\n        $att_rep = gps(\'smd_macro_attrep\');\n\n        // Don\'t doSlash() this yet\n        $smd_macro_definition = gps(\'smd_macro_definition\');\n\n        $atts = array();\n        foreach($att_name as $idx => $att) {\n            if ($att == \'\') continue;\n            $atts[strtolower(str_replace(\'-\', \'_\', sanitizeForUrl($att)))] = array(\n                \'default\' => $att_dflt[$idx],\n                \'rep\' => $att_rep[$idx]\n            );\n        }\n\n        $code = doSlash(smd_macro_build($smd_macro_new_name, $atts, $smd_macro_definition));\n        $smd_macro_definition = doSlash($smd_macro_definition);\n        $smd_macro_atts = serialize($atts);\n\n        if (smd_macro_table_exist()) {\n            $exists = ($smd_macro_name != $smd_macro_new_name) && smd_macro_exists($smd_macro_new_name);\n\n            if ($exists === false) {\n                // Inject the new name so it\'s selected in the dropdown if renamed\n                $_POST[\'smd_macro_name\'] = $smd_macro_new_name;\n\n                if ($smd_macro_name == \'\') {\n                    // Insert\n                    $ret = safe_insert(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\");\n                    $msg = gTxt(\'smd_macro_created\');\n                } else {\n                    // Update\n                    $ret = safe_update(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\", \"macro_name=\'$smd_macro_name\'\");\n                    $msg = gTxt(\'smd_macro_saved\');\n                }\n            } else {\n                $msg = array(gTxt(\'smd_macro_exists\'), E_WARNING);\n            }\n        }\n    } else {\n        $msg = array(gTxt(\'smd_macro_invalid\'), E_WARNING);\n    }\n    smd_macro($msg);\n}\n\n// ------------------------\nfunction smd_macro_delete()\n{\n    global $smd_macro_event;\n\n    $macro_name = doSlash(gps(\'smd_macro_name\'));\n\n    $ret = safe_delete(SMD_MACRO, \"macro_name=\'$macro_name\'\");\n    $msg = gTxt(\'smd_macro_deleted\');\n\n    $_GET[\'smd_macro_name\'] = \'\';\n\n    smd_macro($msg);\n}\n\n// ------------------------\n// Check the macro doesn\'t exist and also that it doesn\'t clash with an internal PHP/Txp function\nfunction smd_macro_exists($macro, $check_db = true)\n{\n    $ret = ($check_db) ? safe_field(\'macro_name\', SMD_MACRO, \"macro_name=\'$macro\'\") : false;\n\n    if ($ret === false) {\n        $fns = get_defined_functions();\n        foreach ($fns as $flist) {\n            if ($ret === false) {\n                $ret = in_array($macro, $flist);\n            }\n        }\n    }\n    return $ret;\n}\n\n// ------------------------\n// Check the macro name is a valid PHP function name\nfunction smd_macro_valid($macro)\n{\n    return is_callable($macro, true) && preg_match(\'/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/\', $macro);\n}\n\n// ------------------------\nfunction smd_macro_build($macro, $args, $def)\n{\n    $arglist = $reps = array();\n    $lAtts = \'\';\n\n    // Validate macro name and bail if it\'d cause problems\n    // Build arg list and replacements array.\n    // Replacements are prefixed with the name of the macro for clash minimisation\n    $args = is_array($args) ? $args : unserialize($args);\n\n    foreach ($args as $arg => $vals) {\n        $arglist[] = \"\'$arg\' => \'\".$vals[\'default\'].\"\'\";\n        $rep = empty($vals[\'rep\']) ? $arg : $vals[\'rep\'];\n        $reps[] = \'\"{\' . $rep . \'}\" => \"$\'.$macro.\'_\'.$arg.\'\"\';\n    }\n\n    $reps[] = \'\"{smd_container}\" => \"$\'.$macro.\'_smd_container\"\';\n    $arg_string = join(\',\'.n, $arglist);\n    $rep_string = join(\',\'.n, $reps);\n    $def = doSlash($def);\n\n    // Only create lAtts if there\'s at least one attribute defined\n    if ($arg_string) {\n        $lAtts = <<<EOATT\nextract(lAtts(array(\n        {$arg_string}\n    ),\\$atts), EXTR_PREFIX_ALL, \'{$macro}\');\nEOATT;\n    }\n\n$full_macro = <<< EOFN\nfunction {$macro}(\\$atts, \\$thing = NULL) {\n    {$lAtts}\n    \\${$macro}_smd_container = (\\$thing)? \\$thing : (isset(\\${$macro}_smd_container) ? \\${$macro}_smd_container : \'\');\n    \\$out = \'\';\n    \\$out = strtr(stripslashes(\'{$def}\'), array({$rep_string}));\n    return parse(\\$out);\n}\nEOFN;\n\n    return $full_macro;\n}\n\n// ------------------------\n// Create ini file contents and return it.\n// $special is an array of keynames and a function to apply to them (e.g. \'definition\' => \'base64_encode\')\nfunction smd_macro_create_ini($arr, $special=array())\n{\n    $content = \'\';\n\n    foreach ($arr as $key => $elem) {\n        $content .= \"[\".$key.\"]\\n\";\n\n        foreach ($elem as $subkey => $subelem) {\n            $subelem = (array_key_exists($subkey, $special)) ? doArray($subelem, $special[$subkey]) : $subelem;\n\n            if (is_array($subelem)) {\n                $num = count($subelem);\n\n                for ($idx = 0; $idx < $num; $idx++) {\n                    $content .= $subkey . \"[] = \\\"\" . $subelem[$idx] . \"\\\"\\n\";\n                }\n            } elseif ($subelem == \"\") {\n                $content .= $subkey . \" = \\n\";\n            } else {\n                $content .= $subkey . \" = \\\"\" . $subelem . \"\\\"\\n\";\n            }\n        }\n    }\n\n    return $content;\n}\n\n// ------------------------\n// Add macro table if not already installed\nfunction smd_macro_table_install($showpane = \'1\')\n{\n    global $DB;\n\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_MACRO.\"` (\n        `macro_name` varchar(32) NOT NULL default \'\',\n        `description` varchar(255) NULL default \'\' COLLATE utf8_general_ci,\n        `attributes` text NULL COLLATE utf8_general_ci,\n        `definition` mediumtext NULL COLLATE utf8_general_ci,\n        `code` mediumtext NULL,\n        PRIMARY KEY (`macro_name`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8\";\n\n    if (gps(\'debug\')) {\n        dmp($sql);\n    }\n\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    $flds = getThings(\'describe `\'.PFX.SMD_MACRO.\'`\');\n\n    if (!in_array(\'definition\',$flds)) {\n        safe_alter(SMD_MACRO, \"add `definition` mediumtext NULL default \'\' after `attributes`\");\n    }\n\n    // Upgrade table collation if necessary\n    $ret = @safe_field(\"COLLATION_NAME\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_MACRO.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'description\'\");\n\n    if ($ret != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_MACRO, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n\n    // Spit out results\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        if ($showpane) {\n            $msg = gTxt(\'smd_macro_tbl_installed\');\n            smd_macro($msg);\n        }\n    } else {\n        if ($showpane) {\n            $msg = gTxt(\'smd_macro_tbl_not_installed\');\n            smd_macro($msg);\n        }\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_macro_table_remove()\n{\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n\n    if (smd_macro_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_MACRO. \"; \";\n        if (gps(\'debug\')) {\n            dmp($sql);\n        }\n\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_macro_tbl_removed\');\n    } else {\n        $msg = gTxt(\'smd_macro_tbl_not_removed\');\n        smd_macro($msg);\n    }\n}\n\n// ------------------------\nfunction smd_macro_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_MACRO => 5);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n\n        return ($out===0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_MACRO.\" HAS \".count(@safe_show(\'columns\', SMD_MACRO)).\" COLUMNS;\";\n        }\n\n        return(@safe_show(\'columns\', SMD_MACRO));\n    }\n}\n\n// *********************\n// PUBLIC SIDE INTERFACE\n// *********************\n\n// ------------------------\n// Extracts all prebuilt macros (essentially, new tags) and injects them into the global scope so they can be called\nfunction smd_macro_boot()\n{\n    $full_macros = array();\n    $toRegister = array();\n    $reg = \'\';\n\n    $rs = safe_rows(\'*\', SMD_MACRO, \'1=1\');\n\n    foreach ($rs as $row) {\n        $toRegister[] = \"->register(\'\" . txpspecialchars($row[\'macro_name\']) . \"\')\";\n        $full_macros[] = str_replace(\'\\r\\n\',\'\n\', $row[\'code\']); // yukky newline workaround\n    }\n\n    if (count($toRegister) > 0) {\n        $reg = \"if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n            Txp::get(\'\\Textpattern\\Tag\\Registry\')\"\n                . (implode(n, $toRegister))\n                .\";\n        }\";\n    }\n\n    $macros = $reg . n . implode(n, $full_macros);\n\n    // Inject the virtual tags into the global scope\n    eval($macros);\n}', '/**\n * smd_macro\n *\n * A Textpattern CMS plugin for creating new tags:\n *  -> Define new <txp:tags> to do any task you like -- lightbox effect, comment scheme...\n *  -> Add arbitrary numbers of attributes and set defaults if you wish\n *  -> Import / export your creations to share new tags with the community\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n * @todo   Allow apostrophes / tags-in-tags in container code\n */\n\nif (txpinterface === \'admin\') {\n    global $smd_macro_event;\n    $smd_macro_event = \'smd_macro\';\n\n    add_privs($smd_macro_event,\'1,2\');\n    register_tab(\'content\', $smd_macro_event, gTxt(\'smd_macro_tab_name\'));\n    register_callback(\'smd_macro_dispatcher\', \'smd_macro\');\n    register_callback(\'smd_macro_upload_form\', $smd_macro_event.\'_ui\', \'inputlabel.\'.$smd_macro_event.\'-upload\');\n    register_callback(\'smd_macro_welcome\', \'plugin_lifecycle.smd_macro\');\n}\n\nif (!defined(\'SMD_MACRO\')) {\n    define(\"SMD_MACRO\", \'smd_macro\');\n}\n\n// Bootstrap the function insertion\nregister_callback(\'smd_macro_boot\', \'pretext\');\n\n// ********************\n// ADMIN SIDE INTERFACE\n// ********************\n\n// ------------------------\nfunction smd_macro_dispatcher($evt, $stp)\n{\n    global $smd_macro_event;\n\n    $available_steps = array(\n        \'smd_macro_table_install\' => true,\n        \'smd_macro_table_remove\'  => true,\n        \'smd_macro_prefsave\'      => true,\n        \'smd_macro_save\'          => true,\n        \'smd_macro_delete\'        => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = $smd_macro_event;\n    }\n\n    $stp();\n}\n\n// ------------------------\nfunction smd_macro_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_macro_table_install(0);\n            $msg = \'Go go gadget macro!\';\n            break;\n        case \'deleted\':\n            smd_macro_table_remove(0);\n            break;\n    }\n\n    return $msg;\n}\n\n// ------------------------\nfunction smd_macro($msg = \'\')\n{\n    global $smd_macro_event;\n\n    if (!smd_macro_table_exist(1)) {\n        smd_macro_table_install(0);\n    }\n\n    $export_list = gps(\'smd_macro_export\');\n    $max_file_size = get_pref(\'smd_macro_import_filesize\', 15 * 1024);\n\n    if (gps(\'step\') == \'smd_macro_export\' && $export_list) {\n        // Generate an associative array that is translated to an ini file\n        $out = array();\n        $rows = safe_rows(\'*\', SMD_MACRO, \"macro_name IN (\'\".join(\"\',\'\", doSlash($export_list)).\"\')\");\n\n        foreach ($rows as $row) {\n            $atts = unserialize($row[\'attributes\']);\n            $outatts = array();\n\n            foreach ($atts as $att => $vals) {\n                $outatts[] = utf8_decode(join(\'|\', array($att, $vals[\'default\'], $vals[\'rep\'])));\n            }\n\n            $out[$row[\'macro_name\']] = array(\n                \'description\' => $row[\'description\'],\n                \'attributes\' => $outatts,\n                \'definition\' => $row[\'definition\'],\n            );\n        }\n\n        $content = smd_macro_create_ini($out, array(\'definition\' => \'base64_encode\'));\n        header(\"Content-Type: text/plain; charset=utf-8\");\n        header(\"Content-Disposition: attachment; filename=\\\"smd_macros.ini\\\"\");\n        header(\"Content-Length: \" . mb_strlen($content));\n        header(\"Content-Transfer-Encoding: binary\");\n        header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\n        header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\n        print($content);\n        exit;\n\n    } elseif (gps(\'step\') == \'smd_macro_import\') {\n        $overwrite = gps(\'smd_macro_import_overwrite\');\n\n        $file = @get_uploaded_file($_FILES[\'thefile\'][\'tmp_name\'], get_pref(\'tempdir\').DS.basename($_FILES[\'thefile\'][\'tmp_name\']));\n\n        if ($file === false) {\n            $msg = array(gTxt(\'smd_macro_cannot_import\'), E_WARNING);\n        } else {\n            $size = filesize($file);\n\n            if ($max_file_size < $size) {\n                $msg = array(gTxt(\'smd_macro_file_size\'), E_WARNING);\n            } else {\n                $ini = @parse_ini_file($file, true);\n\n                if ($ini) {\n                    $done = array(\'ok\' => array(), \'nok\' => array(), \'skip\' => array());\n\n                    foreach ($ini as $key => $val) {\n                        $atts = array();\n                        $def = $desc = \'\';\n\n                        if (isset($val[\'attributes\'])) {\n                            foreach ($val[\'attributes\'] as $attlist) {\n                                list($nam, $dflt, $rep) = explode(\'|\', utf8_encode($attlist));\n                                $atts[strtolower(str_replace(\'-\', \'_\', sanitizeForUrl($nam)))] = array(\n                                    \'default\' => $dflt,\n                                    \'rep\' => $rep\n                                );\n                            }\n                        }\n\n                        if (isset($val[\'description\'])) {\n                            $desc = $val[\'description\'];\n                        }\n\n                        if (isset($val[\'definition\'])) {\n                            $def = base64_decode($val[\'definition\']);\n                        }\n\n                        $result = (string)smd_macro_save_direct($key, $desc, $atts, $def, $overwrite);\n                        switch ($result) {\n                            case \'SMD_MACRO_BAD_NAME\':\n                            case \'SMD_MACRO_CLASH\':\n                                $done[\'nok\'][] = $key;\n                                break;\n                            case \'SMD_MACRO_SKIP\':\n                                $done[\'skip\'][] = $key;\n                                break;\n                            default:\n                                $done[\'ok\'][] = $key;\n                                break;\n                        }\n                    }\n\n                    $msg = (($done[\'ok\']) ? gTxt(\'smd_macro_imported\') . join(\', \', $done[\'ok\']).br : \'\')\n                        . (($done[\'skip\']) ? gTxt(\'smd_macro_skipped\') . join(\', \', $done[\'skip\']).br : \'\')\n                        . (($done[\'nok\']) ? gTxt(\'smd_macro_not_imported\') . join(\', \', $done[\'nok\']) : \'\');\n                } else {\n                    $msg = array(gTxt(\'smd_macro_invalid_ini\'), E_WARNING);\n                }\n            }\n            unlink($file);\n        }\n    } elseif (gps(\'step\') == \'smd_macro_clone\') {\n        $source = safe_row(\'*\', SMD_MACRO, \"macro_name=\'\".doSlash(gps(\'smd_macro_name\')).\"\'\");\n        $name = gps(\'smd_macro_new_name\');\n        $desc = $source[\'description\'];\n        $def = $source[\'definition\'];\n        $atts = $source[\'attributes\'];\n\n        $result = (string)smd_macro_save_direct($name, $desc, $atts, $def, false);\n\n        switch($result) {\n            case \'SMD_MACRO_BAD_NAME\':\n                $msg = array(gTxt(\'smd_macro_invalid\'), E_WARNING);\n                break;\n            case \'SMD_MACRO_CLASH\':\n            case \'SMD_MACRO_SKIP\':\n                $msg = array(gTxt(\'smd_macro_exists\'), E_WARNING);\n                break;\n            default:\n                $msg = gTxt(\'smd_macro_created\');\n                $_POST[\'smd_macro_name\'] = $name; // Inject the new macro into the post stream so it\'s selected by default\n                break;\n        }\n    }\n\n    pagetop(gTxt(\'smd_macro_tab_name\'), $msg);\n\n    $macros = safe_rows(\'*\', SMD_MACRO, \'1=1 ORDER BY macro_name\');\n    $macro_name = $macro_new_name = gps(\'smd_macro_name\');\n    $macro_description = $macro_def = $macro_code = \'\';\n    $macro_atts = array();\n\n    // Build the select list if possible\n    $macrolist = array();\n\n    foreach ($macros as $idx => $macro) {\n        $macrolist[$macro[\'macro_name\']] = $macro[\'macro_name\'];\n\n        if ($macro[\'macro_name\'] == $macro_name) {\n            $macro_new_name = $macro[\'macro_name\'];\n            $macro_description = $macro[\'description\'];\n            $macro_atts = $macro[\'attributes\'] ? unserialize($macro[\'attributes\']) : array();\n            $macro_def = $macro[\'definition\'];\n        }\n    }\n\n    $macroSelector = ($macrolist) ? selectInput(\'smd_macro_name\', $macrolist, $macro_name, true, 1) : \'\';\n    $multi_macsel = \'\';\n\n    if ($macrolist) {\n        $multi_macsel = \'<select name=\"smd_macro_export[]\" id=\"smd_macro_export_list\" size=\"8\" class=\"list\" multiple=\"multiple\">\'.n;\n        foreach ($macrolist as $key => $val) {\n            $multi_macsel .= \'<option value=\"\'.$key.\'\"\' . (($macro_name==$key) ? \' selected=\"selected\"\' : \'\') .\'>\'.$val.\'</option>\'.n;\n        }\n        $multi_macsel .= \'</select>\';\n    }\n\n    $editcell = ($macrolist) ? \'<label>\'.gTxt(\'smd_macro_choose\').\'</label>\'.n.$macroSelector : \'\';\n\n    // Edit form\n    echo n, \'<h1 class=\"txp-heading\">\', gTxt(\'smd_macro_tab_name\'), \'</h1>\',\n        n, \'<div id=\"smd_macro_control\" class=\"txp-control-panel\">\',\n        n, \'<form id=\"smd_macro_select\" action=\"index.php\" method=\"post\">\',\n        n, \'<p id=\"smd_macro_select\">\',\n        n, $editcell,\n        n, eInput($smd_macro_event),\n        n, \'</form>\',\n\n        // Import / export form\n        n, \'<a id=\"smd_macro_import\" href=\"#\">\'.gTxt(\'smd_macro_import\').\'</a>\',\n        n, (($multi_macsel)\n            ? \' / \' . \'<a id=\"smd_macro_export\" href=\"#\">\'.gTxt(\'smd_macro_export\').\'</a>\'\n                . (($macro_name) ? \' / \' . \'<a id=\"smd_macro_clone\" href=\"#\">\'.gTxt(\'smd_macro_clone\').\'</a>\' : \'\')\n            : \'\'),\n        n, \'<div id=\"smd_macro_import_holder\" class=\"smd_hidden\">\',\n        n, upload_form(gTxt(\'smd_macro_upload\'), \'\', \'smd_macro_import\', $smd_macro_event, \'\'),\n        n, \'</div>\',\n        n, (($multi_macsel)\n                ? \'<div id=\"smd_macro_export_holder\" class=\"smd_hidden\"><form id=\"smd_macro_export_form\" action=\"index.php\" method=\"post\">\'\n                    . $multi_macsel . br\n                    . fInput(\'submit\', \'smd_macro_export_go\', gTxt(\'go\'), \'\', \'\', \'smd_macro_export_close();\')\n                    . eInput($smd_macro_event)\n                    . sInput(\'smd_macro_export\')\n                    . \'</form></div>\'\n                    . \'<div id=\"smd_macro_clone_holder\" class=\"smd_hidden\"><form id=\"smd_macro_clone_form\" action=\"index.php\" method=\"post\">\'\n                    . hInput(\'smd_macro_name\', $macro_name)\n                    . fInput(\'text\', \'smd_macro_new_name\', \'\')\n                    . fInput(\'submit\', \'smd_macro_clone_go\', gTxt(\'go\'))\n                    . eInput($smd_macro_event)\n                    . sInput(\'smd_macro_clone\')\n                    . tInput()\n                    . \'</form></div>\'\n                : \'\'\n            ),\n        n, \'</div>\';\n\n    echo n, \'<div class=\"txp-container\">\',\n        n, \'<form id=\"smd_macro_form\" action=\"index.php\" method=\"post\">\',\n        n, startTable(),\n        n, tr(\n            fLabelCell(\'name\', \'\', \'smd_macro_new_name\')\n            .n.td(\n                fInput(\'text\', \'smd_macro_new_name\', $macro_new_name).fInput(\'hidden\', \'smd_macro_name\', $macro_name)\n                .(($macro_name == \'\') ? \'\' : sp.\'<a href=\"?event=\'.$smd_macro_event. a.\'step=smd_macro_delete\' . a . \'smd_macro_name=\'.urlencode($macro_name). a . \'_txp_token=\'.form_token() . \'\" onclick=\"return confirm(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\');\">[x]</a>\')\n            )\n        ),\n        n, tr(\n            fLabelCell(\'description\', \'\', \'smd_macro_description\')\n            .n.td(\n                fInput(\'text\', \'smd_macro_description\', $macro_description, \'\', \'\', \'\', \'66\')\n            )\n        );\n\n    $attlist = array(\'<table id=\"smd_macro_attlist\"><thead><tr><th>\'.gTxt(\'name\').\'</th><th>\'.gTxt(\'default\').\'</th><th>\'.gTxt(\'smd_macro_repname\').\'</th></tr></thead><tbody>\');\n    $macro_atts = empty($macro_atts) ? array(\'\' => array(\'default\' => \'\', \'rep\' => \'\')) : $macro_atts;\n\n    foreach ($macro_atts as $attname => $att) {\n        $attlist[] = \'<tr class=\"smd_macro_att\">\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attname[]\', $attname). \'</td>\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attdflt[]\', $att[\'default\']). \'</td>\'\n            .\'<td>\'.fInput(\'text\', \'smd_macro_attrep[]\', $att[\'rep\']). \'</td>\'\n            .\'</tr>\';\n    }\n    $attlist[] = \'</tbody></table>\';\n\n    $atts = join(n, $attlist);\n    echo n, tr(\n        tda(\'<label>\'.gTxt(\'smd_macro_attributes\').\'</label>\'.n.\'<a href=\"#\" id=\"smd_macro_att_add\" title=\"\'.gTxt(\'smd_macro_att_clone_help\').\'\">\'.gTxt(\'smd_macro_att_clone\').\'</a>\')\n        .tda($atts, \' id=\"smd_macro_att\"\')\n        ),\n        n, tr(\n            tda(\'<label>\'.gTxt(\'smd_macro_tag_definition\').\'</label>\')\n            .n.tda(\n                text_area(\'smd_macro_definition\', 250, 700, $macro_def)\n            )\n        ),\n        n, tr(td(\'&nbsp;\').eInput($smd_macro_event).sInput(\'smd_macro_save\').td(fInput(\'submit\', \'save\', gTxt(\'save\')))),\n        n, tInput(),\n        n, endTable(),\n        n, \'</form>\',\n        n, \'</div>\',\n        n, script_js(<<<EOJS\njQuery(function() {\n    jQuery(\'#smd_macro_att_add\').click(function(ev) {\n        var obj = jQuery(\'#smd_macro_attlist tbody\');\n        var elems = jQuery(\'.smd_macro_att\');\n\n        // Add the row, empty it and focus. Can\'t do this in any fewer jQuery() calls for some reason\n        obj.append(obj.children().eq(0).clone());\n        obj.children().last().find(\'input:text\').val(\'\');\n        obj.children().last().find(\'input:text\').first().focus();\n        ev.preventDefault();\n    });\n\n    // Import link\n    jQuery(\'#smd_macro_import\').click(function(ev) {\n        jQuery(\'#smd_macro_export_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_import_holder\').toggle(\'normal\');\n    });\n\n    // Export link\n    jQuery(\'#smd_macro_export\').click(function(ev) {\n        jQuery(\'#smd_macro_import_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_export_holder\').toggle(\'normal\');\n    });\n\n    // Clone link\n    jQuery(\'#smd_macro_clone\').click(function(ev) {\n        jQuery(\'#smd_macro_import_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_export_holder\').hide(\'normal\');\n        jQuery(\'#smd_macro_clone_holder\').toggle(\'normal\').find(\"input[name=\'smd_macro_new_name\']\").focus();\n    });\n});\nfunction smd_macro_export_close() {\n    jQuery(\"#smd_macro_export\").click();\n}\nEOJS\n        ),\n        <<<EOCSS\n<style type=\"text/css\">\n.smd_hidden {\n    display:none;\n}\n</style>\nEOCSS;\n}\n\n// ------------------------\n// Add an overwrite checkbox to the upload form\nfunction smd_macro_upload_form($evt, $stp, $data, $args)\n{\n    return $data .\n        checkbox(\'smd_macro_import_overwrite\', \'1\', false, 0, \'smd_macro_import_overwrite\') .\n        \'<label for=\"smd_macro_import_overwrite\">\' . gTxt(\'smd_macro_overwrite\') . \'</label>\';\n}\n\n// ------------------------\n// Takes function params; assumes attribute sanitization done already\nfunction smd_macro_save_direct($name = \'\', $desc = \'\', $atts = array(), $def = \'\', $overwrite = false)\n{\n    global $smd_macro_event;\n\n    $smd_macro_new_name = sanitizeForUrl($name);\n    $smd_macro_description = doSlash($desc);\n    $smd_macro_definition = $def;\n\n    $ret = \'\';\n\n    if (smd_macro_valid($smd_macro_new_name)) {\n        $code = doSlash(smd_macro_build($smd_macro_new_name, $atts, $smd_macro_definition));\n\n        $smd_macro_definition = doSlash($smd_macro_definition);\n        $smd_macro_atts = is_array($atts) ? serialize($atts) : $atts;\n\n        if (smd_macro_table_exist()) {\n            // Check if this macro name clashes with a built-in PHP/Txp function\n            $exists = smd_macro_exists($smd_macro_new_name, false);\n\n            if ($exists === false) {\n                $indb = safe_field(\'macro_name\', SMD_MACRO, \"macro_name=\'$smd_macro_new_name\'\");\n\n                if ($indb) {\n                    if ($overwrite) {\n                        // Update\n                        $ret = safe_update(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\", \"macro_name=\'$smd_macro_new_name\'\");\n                    } else {\n                        $ret = \'SMD_MACRO_SKIP\';\n                    }\n                } else {\n                    // Insert\n                    $ret = safe_insert(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\");\n                }\n            } else {\n                $ret = \'SMD_MACRO_CLASH\';\n            }\n        }\n    } else {\n        $ret = \'SMD_MACRO_BAD_NAME\';\n    }\n\n    // $ret may be empty only if macro table not installed\n    return $ret;\n}\n\n// ------------------------\n// Takes URL params; sanitizes attributes locally\nfunction smd_macro_save()\n{\n    global $smd_macro_event;\n\n    extract(doSlash(gpsa(array(\n        \'smd_macro_name\',\n        \'smd_macro_new_name\',\n        \'smd_macro_description\',\n    ))));\n\n    $msg = \'\';\n    $smd_macro_new_name = sanitizeForUrl($smd_macro_new_name);\n\n    if (smd_macro_valid($smd_macro_new_name)) {\n        $att_name = gps(\'smd_macro_attname\');\n        $att_dflt = gps(\'smd_macro_attdflt\');\n        $att_rep = gps(\'smd_macro_attrep\');\n\n        // Don\'t doSlash() this yet\n        $smd_macro_definition = gps(\'smd_macro_definition\');\n\n        $atts = array();\n        foreach($att_name as $idx => $att) {\n            if ($att == \'\') continue;\n            $atts[strtolower(str_replace(\'-\', \'_\', sanitizeForUrl($att)))] = array(\n                \'default\' => $att_dflt[$idx],\n                \'rep\' => $att_rep[$idx]\n            );\n        }\n\n        $code = doSlash(smd_macro_build($smd_macro_new_name, $atts, $smd_macro_definition));\n        $smd_macro_definition = doSlash($smd_macro_definition);\n        $smd_macro_atts = serialize($atts);\n\n        if (smd_macro_table_exist()) {\n            $exists = ($smd_macro_name != $smd_macro_new_name) && smd_macro_exists($smd_macro_new_name);\n\n            if ($exists === false) {\n                // Inject the new name so it\'s selected in the dropdown if renamed\n                $_POST[\'smd_macro_name\'] = $smd_macro_new_name;\n\n                if ($smd_macro_name == \'\') {\n                    // Insert\n                    $ret = safe_insert(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\");\n                    $msg = gTxt(\'smd_macro_created\');\n                } else {\n                    // Update\n                    $ret = safe_update(SMD_MACRO, \"macro_name=\'$smd_macro_new_name\', description=\'$smd_macro_description\', attributes=\'$smd_macro_atts\', definition=\'$smd_macro_definition\', code=\'$code\'\", \"macro_name=\'$smd_macro_name\'\");\n                    $msg = gTxt(\'smd_macro_saved\');\n                }\n            } else {\n                $msg = array(gTxt(\'smd_macro_exists\'), E_WARNING);\n            }\n        }\n    } else {\n        $msg = array(gTxt(\'smd_macro_invalid\'), E_WARNING);\n    }\n    smd_macro($msg);\n}\n\n// ------------------------\nfunction smd_macro_delete()\n{\n    global $smd_macro_event;\n\n    $macro_name = doSlash(gps(\'smd_macro_name\'));\n\n    $ret = safe_delete(SMD_MACRO, \"macro_name=\'$macro_name\'\");\n    $msg = gTxt(\'smd_macro_deleted\');\n\n    $_GET[\'smd_macro_name\'] = \'\';\n\n    smd_macro($msg);\n}\n\n// ------------------------\n// Check the macro doesn\'t exist and also that it doesn\'t clash with an internal PHP/Txp function\nfunction smd_macro_exists($macro, $check_db = true)\n{\n    $ret = ($check_db) ? safe_field(\'macro_name\', SMD_MACRO, \"macro_name=\'$macro\'\") : false;\n\n    if ($ret === false) {\n        $fns = get_defined_functions();\n        foreach ($fns as $flist) {\n            if ($ret === false) {\n                $ret = in_array($macro, $flist);\n            }\n        }\n    }\n    return $ret;\n}\n\n// ------------------------\n// Check the macro name is a valid PHP function name\nfunction smd_macro_valid($macro)\n{\n    return is_callable($macro, true) && preg_match(\'/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/\', $macro);\n}\n\n// ------------------------\nfunction smd_macro_build($macro, $args, $def)\n{\n    $arglist = $reps = array();\n    $lAtts = \'\';\n\n    // Validate macro name and bail if it\'d cause problems\n    // Build arg list and replacements array.\n    // Replacements are prefixed with the name of the macro for clash minimisation\n    $args = is_array($args) ? $args : unserialize($args);\n\n    foreach ($args as $arg => $vals) {\n        $arglist[] = \"\'$arg\' => \'\".$vals[\'default\'].\"\'\";\n        $rep = empty($vals[\'rep\']) ? $arg : $vals[\'rep\'];\n        $reps[] = \'\"{\' . $rep . \'}\" => \"$\'.$macro.\'_\'.$arg.\'\"\';\n    }\n\n    $reps[] = \'\"{smd_container}\" => \"$\'.$macro.\'_smd_container\"\';\n    $arg_string = join(\',\'.n, $arglist);\n    $rep_string = join(\',\'.n, $reps);\n    $def = doSlash($def);\n\n    // Only create lAtts if there\'s at least one attribute defined\n    if ($arg_string) {\n        $lAtts = <<<EOATT\nextract(lAtts(array(\n        {$arg_string}\n    ),\\$atts), EXTR_PREFIX_ALL, \'{$macro}\');\nEOATT;\n    }\n\n$full_macro = <<< EOFN\nfunction {$macro}(\\$atts, \\$thing = NULL) {\n    {$lAtts}\n    \\${$macro}_smd_container = (\\$thing)? \\$thing : (isset(\\${$macro}_smd_container) ? \\${$macro}_smd_container : \'\');\n    \\$out = \'\';\n    \\$out = strtr(stripslashes(\'{$def}\'), array({$rep_string}));\n    return parse(\\$out);\n}\nEOFN;\n\n    return $full_macro;\n}\n\n// ------------------------\n// Create ini file contents and return it.\n// $special is an array of keynames and a function to apply to them (e.g. \'definition\' => \'base64_encode\')\nfunction smd_macro_create_ini($arr, $special=array())\n{\n    $content = \'\';\n\n    foreach ($arr as $key => $elem) {\n        $content .= \"[\".$key.\"]\\n\";\n\n        foreach ($elem as $subkey => $subelem) {\n            $subelem = (array_key_exists($subkey, $special)) ? doArray($subelem, $special[$subkey]) : $subelem;\n\n            if (is_array($subelem)) {\n                $num = count($subelem);\n\n                for ($idx = 0; $idx < $num; $idx++) {\n                    $content .= $subkey . \"[] = \\\"\" . $subelem[$idx] . \"\\\"\\n\";\n                }\n            } elseif ($subelem == \"\") {\n                $content .= $subkey . \" = \\n\";\n            } else {\n                $content .= $subkey . \" = \\\"\" . $subelem . \"\\\"\\n\";\n            }\n        }\n    }\n\n    return $content;\n}\n\n// ------------------------\n// Add macro table if not already installed\nfunction smd_macro_table_install($showpane = \'1\')\n{\n    global $DB;\n\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_MACRO.\"` (\n        `macro_name` varchar(32) NOT NULL default \'\',\n        `description` varchar(255) NULL default \'\' COLLATE utf8_general_ci,\n        `attributes` text NULL COLLATE utf8_general_ci,\n        `definition` mediumtext NULL COLLATE utf8_general_ci,\n        `code` mediumtext NULL,\n        PRIMARY KEY (`macro_name`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8\";\n\n    if (gps(\'debug\')) {\n        dmp($sql);\n    }\n\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    $flds = getThings(\'describe `\'.PFX.SMD_MACRO.\'`\');\n\n    if (!in_array(\'definition\',$flds)) {\n        safe_alter(SMD_MACRO, \"add `definition` mediumtext NULL default \'\' after `attributes`\");\n    }\n\n    // Upgrade table collation if necessary\n    $ret = @safe_field(\"COLLATION_NAME\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_MACRO.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'description\'\");\n\n    if ($ret != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_MACRO, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n\n    // Spit out results\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        if ($showpane) {\n            $msg = gTxt(\'smd_macro_tbl_installed\');\n            smd_macro($msg);\n        }\n    } else {\n        if ($showpane) {\n            $msg = gTxt(\'smd_macro_tbl_not_installed\');\n            smd_macro($msg);\n        }\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_macro_table_remove()\n{\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n\n    if (smd_macro_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_MACRO. \"; \";\n        if (gps(\'debug\')) {\n            dmp($sql);\n        }\n\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_macro_tbl_removed\');\n    } else {\n        $msg = gTxt(\'smd_macro_tbl_not_removed\');\n        smd_macro($msg);\n    }\n}\n\n// ------------------------\nfunction smd_macro_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_MACRO => 5);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n\n        return ($out===0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_MACRO.\" HAS \".count(@safe_show(\'columns\', SMD_MACRO)).\" COLUMNS;\";\n        }\n\n        return(@safe_show(\'columns\', SMD_MACRO));\n    }\n}\n\n// *********************\n// PUBLIC SIDE INTERFACE\n// *********************\n\n// ------------------------\n// Extracts all prebuilt macros (essentially, new tags) and injects them into the global scope so they can be called\nfunction smd_macro_boot()\n{\n    $full_macros = array();\n    $toRegister = array();\n    $reg = \'\';\n\n    $rs = safe_rows(\'*\', SMD_MACRO, \'1=1\');\n\n    foreach ($rs as $row) {\n        $toRegister[] = \"->register(\'\" . txpspecialchars($row[\'macro_name\']) . \"\')\";\n        $full_macros[] = str_replace(\'\\r\\n\',\'\n\', $row[\'code\']); // yukky newline workaround\n    }\n\n    if (count($toRegister) > 0) {\n        $reg = \"if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n            Txp::get(\'\\Textpattern\\Tag\\Registry\')\"\n                . (implode(n, $toRegister))\n                .\";\n        }\";\n    }\n\n    $macros = $reg . n . implode(n, $full_macros);\n\n    // Inject the virtual tags into the global scope\n    eval($macros);\n}', '03826061198f49e92b6f18ce5715a263', '#@smd_macro\n#@language de\nsmd_macro_attributes => Attribute\nsmd_macro_att_clone => [+]\nsmd_macro_att_clone_help => Ein Attribut hinzufügen\nsmd_macro_cannot_import => Das Makro kann nicht importiert werden. Bitte überprüfen Sie die Berechtigungen der Ordner und die Einstellungen Ihres Servers.\nsmd_macro_choose => Makro zum Bearbeiten auswählen\nsmd_macro_clone => Duplizieren\nsmd_macro_created => Makro erstellt\nsmd_macro_deleted => Makro gelöscht\nsmd_macro_exists => Der Name des Makros existiert bereits oder ist inkompatibel\nsmd_macro_export => Exportieren\nsmd_macro_file_size => Die Makrodatei ist zu groß\nsmd_macro_import => Importieren\nsmd_macro_imported => Importiert:\nsmd_macro_invalid => Der Name des Makros ist ungültig\nsmd_macro_invalid_ini => Die Makrodatei ist nicht im erwarteten Format\nsmd_macro_not_imported => Nicht importiert:\nsmd_macro_overwrite => Überschreiben erzwingen\nsmd_macro_repname => Ersatzname\nsmd_macro_saved => Makro gespeichert\nsmd_macro_skipped => Übersprungen:\nsmd_macro_tab_name => Makros\nsmd_macro_tag_definition => Makrodefinition\nsmd_macro_tbl_installed => Tabelle installiert\nsmd_macro_tbl_not_installed => Tabelle nicht installiert\nsmd_macro_tbl_not_removed => Tabelle nicht entfernt\nsmd_macro_tbl_removed => Tabelle entfernt\nsmd_macro_upload => Makrodatei\n#@smd_macro\n#@language es\nsmd_macro_attributes => Atributos\nsmd_macro_att_clone => [+]\nsmd_macro_att_clone_help => Añadir un atributo\nsmd_macro_cannot_import => El macro no puede ser importado. Por favor, examine los permisos de carpeta y la configuración de su servidor.\nsmd_macro_choose => Seleccione el macro para editar\nsmd_macro_clone => Duplicar\nsmd_macro_created => Macro creado\nsmd_macro_deleted => Macro eliminado\nsmd_macro_exists => El nombre del macro ya existe o es incompatible\nsmd_macro_export => Exportar\nsmd_macro_file_size => El archivo del macro es demasiado grande\nsmd_macro_import => Importar\nsmd_macro_imported => Importado:\nsmd_macro_invalid => El nombre del macro no es válido\nsmd_macro_invalid_ini => El archivo del macro no tiene el formato esperado\nsmd_macro_not_imported => No importado:\nsmd_macro_overwrite => Forzar sobrescribir\nsmd_macro_repname => Nombre de reemplazo\nsmd_macro_saved => Macro guardado\nsmd_macro_skipped => Saltado:\nsmd_macro_tab_name => Macros\nsmd_macro_tag_definition => Definición del macro\nsmd_macro_tbl_installed => Tabla instalada\nsmd_macro_tbl_not_installed => Tabla no instalada\nsmd_macro_tbl_not_removed => Tabla no eliminada\nsmd_macro_tbl_removed => Tabla eliminada\nsmd_macro_upload => Archivo del macro', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_gallery', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.62', 'A(nother) thumbnail gallery/slideshow creator with unreal flexibility', '<div id=\"smd_help\">\n\n	<h1 id=\"top\">smd_gallery</h1>\n\n	<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>Supports all current and (probably) future gallery scripts including thumbpop, rollover, tooltip, lightbox, litebox, slimbox, moonbox, thickbox, slideshow, fancyzoom, etc</li>\n		<li>Pull images from the <span class=\"caps\">TXP</span> database that match categories, IDs, author, extension or particular dimensions / aspect ratios</li>\n		<li>Use article fields to hold lists of image categories/IDs for article-specific galleries</li>\n		<li>Multi-level sub-category support</li>\n		<li>Gallery layout is defined in a <span class=\"caps\">TXP</span> form/container</li>\n		<li>Direct-access pagination is supported and the layout of paging features can be controlled by a second form</li>\n		<li>Uses a tag-replacement system like the wet_for_each_image plugin</li>\n		<li><span class=\"caps\">MLP</span> support</li>\n	</ul>\n\n	<h2 id=\"author\">Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. For other software by me, or to make a donation, see the <a href=\"http://stefdawson.com/sw\">software page</a>.</p>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p class=\"required\">Requires <span class=\"caps\">TXP</span> 4.2.0+ and smd_lib v0.36+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/957/smd_gallery\">textpattern.org</a>, or the software page above, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=26482\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To remove the plugin, simply delete it from the Admin-&gt;Plugins tab. If no other plugins are using smd_lib you may safely delete that too.</p>\n\n	<h2 id=\"usage\">Overview</h2>\n\n	<p>The general idea is that this plugin acts as a flexible image filter; nothing more. The plugin options govern which images are pulled out of the database and they are sorted into whatever order you like.</p>\n\n	<p>After that, each image &#8212; one by one &#8212; has all its attributes passed through the container or <code>form</code> of your choice; you simply choose to display whichever image attributes you like in this form. This means any image attributes can be &#8220;injected&#8221; directly into any markup; it could be <span class=\"caps\">TXP</span> tags, it could be content, it could be javascript, or <span class=\"caps\">CSS</span>; anything.</p>\n\n	<p>Pagination works the same way, allowing you to specify how users can step through your image galleries.</p>\n\n	<p>There is also a special &#8220;collate&#8221; mode that allows you to collect all image attributes together into a series of big long lists <em>without</em> calling the form every time. The form/container is then only processed once at the end.</p>\n\n	<p>Between these two modes you should be able to realise any gallerying solution; now and in future.</p>\n\n	<h2 class=\"tag \" id=\"smd_gallery\">smd_gallery</h2>\n\n	<p class=\"tag-summary\">Place one or more smd_gallery tags in an article, page or form, and supply any of the following options to configure it. The attributes are grouped by function to make it easier on your eyes / sanity.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<h4>Filters</h4>\n\n	<ul>\n		<li><span class=\"atnm\">id</span> : list of image IDs to display. May be integers or ranges, for example <code>id=&quot;5, 42, 8-11&quot;</code> would grab images 5, 8, 9, 10, 11 and 42. Use &#8216;!&#8217; in front of a number or range to exclude those images. Use &#8216;?&#8217; and the name of a <span class=\"caps\">TXP</span> field to read the list of image IDs from that field, e.g. <code>id=?article_image&quot;</code></li>\n		<li><span class=\"atnm\">name</span> : list of image names to display. You can either specify exact matches, e.g. <code>name=&quot;lion.jpg, tiger.jpg&quot;</code> or regular expression matches by appending <code>:regex</code> to terms. For example, <code>name=&quot;lion:regex&quot;</code> would match lion.jpg, lioness.jpg and megalion.jpg. Alternatively, <code>name=&quot;^lion:regex&quot;</code> would only match images that start with &#8216;lion&#8217;. Use &#8216;!&#8217; in front of names to exclude those images. Use &#8216;?&#8217; and the name of a <span class=\"caps\">TXP</span> field to read the list of image names/regexes from that field, e.g. <code>name=?filenames&quot;</code> to read them from a custom field of that name.<div class=\"break\">If you want to negate a regular expression, things may get tricky if you are using meta characters; in this case it is better to set your regular expression up in, say, a txp:variable (or an article field) and use <code>name=&quot;!var_name:regex&quot;</code>.</div><div class=\"break\">You can mix exact matches and regex matches in the same <code>name</code> attribute if you wish. The colon can be altered with <code>paramdelim</code> &#8212; this is sometimes necessary if you are using character classes such as <code>[[:digit:]]</code> in your regular expression.</div></li>\n		<li><span class=\"atnm\">category</span> : list of image categories to display. You may specify fixed-name categories (e.g. &#8220;animals&#8221;) or a&#8217;?&#8217; followed by an article field name. e.g. <code>category=&quot;animals, art-?thisid&quot;</code> would grab all images from category &#8220;animals&#8221; and category &#8220;art-42&#8221; (assuming the current article has an id of 42). Use &#8216;!&#8217; to <em>exclude</em> the given category or field.<div class=\"break\">Fields may themselves contain lists of categories. The &#8216;?&#8217; field names may be combined (e.g. ?s-?article_image) but be aware that custom field names may contain spaces if <code>strictfields=&quot;0&quot;</code></div></li>\n		<li><span class=\"atnm\">sublevel</span> : an integer that specifies how many &#8216;levels&#8217; down to traverse each matching category. <code>sublevel=&quot;0&quot;</code> (the default) is only the top-level; 1 = top-level plus the 1st sub-cat; 2 = top-level plus the next 2 sub-cats, and so on. <code>sublevel=&quot;all&quot;</code> is all sub-cats</li>\n		<li><span class=\"atnm\">author</span> : filter images by this list of author login names</li>\n		<li><span class=\"atnm\">extension</span> : filter images by this list of file extensions (without the leading dot)</li>\n		<li><span class=\"atnm\">aspect</span> : filter images by this list of aspect ratios. Choose from any of <code>portrait</code>, <code>landscape</code> or <code>square</code></li>\n		<li><span class=\"atnm\">dimensions</span> : filter images matching the given dimensions. Specify up to two values. The first is the width and the second is the height. If you wish to specify minimum or maximum dimensions, add <code>&gt;</code>, <code>&gt;=</code>, <code>&lt;</code> or <code>&lt;=</code> before the value. For example, <code>dimensions=&quot;&gt;=1024, &gt;=768&quot;</code> would only show images greater than or equal to 1024 &#215; 768 pixels</li>\n		<li><span class=\"atnm\">sort</span> : re-order the images by <code>category</code> (the default), <code>name</code>, <code>date</code>, <code>id</code> (the default if using an id), file <code>ext</code>ension, <code>alt</code>, <code>caption</code>, <code>author</code>, <code>width</code>, <code>height</code>, <code>thumb_width</code>, <code>thumb_height</code>, <code>hasthumb</code> in either <code>asc</code>ending or <code>desc</code>ending order. Two special cases exist: <code>fixed</code> maintains the order you specified in the id and/or category attribute, while <code>random</code> shuffles the order.<div class=\"break\">Note that if <code>random</code> appears as the first item in the list, random images will be pulled from the database and they will then be sorted according to the order listed after it. If <code>random</code> appears anywhere else in the list, any sort items before it will determine the order of data pulled from the database, and the results will then be ordered randomly. In the 2nd case, any items listed after &#8216;random&#8217; will be ignored, but <code>fixed</code> is still honoured</div></li>\n	</ul>\n\n	<h4>Limits / offsets / sizes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">limit</span> : the maximum number of images to show <em>per page</em>. 0 (the default) or omitted = unlimited</li>\n		<li><span class=\"atnm\">maxlimit</span> : the absolute maximum number of image to pull from the database. Must be greater than 0</li>\n		<li><span class=\"atnm\">offset</span> : skip this many images and start displaying them thereafter</li>\n		<li><span class=\"atnm\">thumblimit</span> : the maximum number of thumbnails to show. 0 or omitted = all thumbs. Note that this differs from limit; you can launch a 20-image gallery but only show 1 thumbnail on the page. Be aware that if you are showing multiple categories from a single tag, you will see <code>thumblimit</code> thumbnails <em>at each change in category</em>. If you don&#8217;t want this, set <code>onchange</code> to something else. See the <code>{object}</code> <a href=\"#rep_gallery\">replacement tag</a></li>\n		<li><span class=\"atnm\">grouplimit</span> : the maximum number of images to put on the page, <em>per group</em>. A group is considered any set of images (e.g. all images from one category) so this can be used to only display N images at each change in category. A concrete example is displaying a thumbnail that launches a gallery showing the 5 most recent works from each author. Without <code>thumblimit=&quot;1&quot; grouplimit=&quot;5&quot;</code> the number of images in the group would be unlimited. See <a href=\"#eg12\">example 12</a> for more</li>\n		<li><span class=\"atnm\">thumboffset</span> : skip this many thumbs before displaying <code>thumblimit</code> thumbnails</li>\n		<li><span class=\"atnm\">thumbsize</span> : takes one or two comma-separated numbers to specify width and/or height of all your gallery thumbnails. The default is in pixels if you don&#8217;t specify units. Missing out one or the other parameter (i.e. having an &#8220;empty&#8221; parameter before or after the comma) will only set one of the dimensions. Use a single value to make square thumbs. If you omit either or both, the corresponding value will be read from the database</li>\n		<li><span class=\"atnm\">imgsize</span> : identical to <code>thumbsize</code> but for the full size images</li>\n	</ul>\n\n	<h4>Forms and paging</h4>\n\n	<ul>\n		<li><span class=\"atnm\">form</span> : the <span class=\"caps\">TXP</span> form used to layout your matching images. Without this attribute the plugin acts as a container tag. Without a container, the plugin displays each full-size image, hyperlinked</li>\n		<li><span class=\"atnm\">pageform</span> : the <span class=\"caps\">TXP</span> form used to specify the layout of your paging navigation</li>\n		<li><span class=\"atnm\">paging</span> : switch paging on without having to specify a pageform. When this is used, all <a href=\"#rep_paging\">page replacement tags</a> become available in the <a href=\"#rep_gallery\">gallery replacement tags</a> as well</li>\n		<li><span class=\"atnm\">pagepos</span> : the position of the paging block. Options are <code>below</code> (the default), <code>above</code>, or both of them separated by <code>delim</code></li>\n		<li><span class=\"atnm\">objectform</span> : the <span class=\"caps\">TXP</span> form used to specify the layout of your <code>{object}</code> replacement tag. You can use all the standard <a href=\"#rep_gallery\">gallery replacement tags</a> with the exception of <code>{object}</code> itself! Uses a hyperlinked image if no form is specified</li>\n		<li><span class=\"atnm\">countform</span> : list of <span class=\"caps\">TXP</span> forms to use when certain numbered images are reached. See <a href=\"#countform\">count forms</a> and <a href=\"#eg10\">example 10</a>. This has no effect in collate mode</li>\n		<li><span class=\"atnm\">countpos</span> : default position of the <code>countform</code>s. Either <code>above</code> (the default), <code>below</code>, or both of them separated by <code>delim</code>. Can be overridden on a per-form basis</li>\n	</ul>\n\n	<h4>Grouping images</h4>\n\n	<ul>\n		<li><span class=\"atnm\">onchange</span> : a list of attributes to &#8220;watch&#8221;. It tells the plugin that you want to be notified when items in this list change compared to the previous image. Under normal circumstances this should be the same as your <code>sort</code> parameter or you may get some freaky results! It defaults to <code>category</code>.<div class=\"break\">This is very useful for splitting galleries at every change in category and displaying the category title above each group. Items you watch are added to the list of replacement tags and are available by putting &#8220;onchange:&#8221; in front of their name (e.g. <code>{onchange:category}</code> &#8212; see <a href=\"#eg6\">example 6</a> for more). The colon delimiter can be changed with <code>paramdelim</code>.</div><div class=\"break\">You can also elect to defer processing of onchange events to a form. To do so, specify a form after your watch item like this <code>category_title:my_form</code>. In my_form you can put any markup and replacement tags. See <a href=\"#eg11\">example 11</a> for more.</div><div class=\"break\">Another way of using this attribute is with the special prefix <code>smd_every:n</code> which will trigger a group change every Nth image. Note it can&#8217;t be used to watch specific things in this mode; it is only used to trigger <code>grouptag</code> changes</div></li>\n		<li><span class=\"atnm\">onchangewraptag</span> : the (X)HTML tag to surround any <code>onchange</code> elements you are watching, e.g. <code>onchangewraptag=&quot;h3&quot;</code></li>\n		<li><span class=\"atnm\">onchangeclass</span> : the <span class=\"caps\">CSS</span> class name tro apply to the wraptag of any onchange elements you are watching</li>\n		<li><span class=\"atnm\">grouptag</span> : the (X)HTML tag to make available as a replacement so you can wrap groups of elements at each <code>onchange</code>. Default: unset. See <a href=\"#eg6\">examples 6</a> for more on the <code>grouptagstart</code> / <code>grouptagend</code> replacement tags</li>\n		<li><span class=\"atnm\">groupclass</span> : the <span class=\"caps\">CSS</span> class name applied to any <code>grouptag</code></li>\n	</ul>\n\n	<h4>Tag/class/formatting attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag to surround the entire gallery, e.g. <code>wraptag=&quot;div&quot;</code></li>\n		<li><span class=\"atnm\">break</span> : the (X)HTML tag to surround each thumbnail</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> class name applied to the <code>wraptag</code></li>\n		<li><span class=\"atnm\">breakclass</span> : the <span class=\"caps\">CSS</span> class name applied to the <code>break</code> elements</li>\n		<li><span class=\"atnm\">html_id</span> : the name of an <span class=\"caps\">HTML</span> <code>id</code> attribute to apply to the wraptag</li>\n		<li><span class=\"atnm\">navwraptag</span> : the (X)HTML tag to surround the entire <code>pageform</code>, e.g. <code>wraptag=&quot;div&quot;</code>. Default: <code>ul</code></li>\n		<li><span class=\"atnm\">navbreak</span> : the (X)HTML tag to surround each item within the <code>pageform</code>. Default: <code>li</code></li>\n		<li><span class=\"atnm\">navclass</span> : the <span class=\"caps\">CSS</span> class name of the navigation wraptag. Default: <code>smd_gallery_nav</code></li>\n		<li><span class=\"atnm\">emptyclass</span> : the <span class=\"caps\">CSS</span> class name of the navigation items that are <em>not</em> links. Default: <code>empty</code></li>\n		<li><span class=\"atnm\">format</span> (<span class=\"required\">only if using <code>{date}</code></span>) : allows you to change the date format used. Can take any <a href=\"http://uk.php.net/strftime\">strftime()</a> values. Defaults to the date system as set in the site&#8217;s prefs</li>\n		<li><span class=\"atnm\">lang</span> (<span class=\"required\">only if using <code>{date}</code></span>) : sets the language code of the date format</li>\n		<li><span class=\"atnm\">gmt</span> (<span class=\"required\">only if using <code>{date}</code></span>) : returns either local time according to the set time zone preferences (0), or <span class=\"caps\">GMT</span> time (1). Default: 0</li>\n	</ul>\n\n	<h4>Plugin customisation</h4>\n\n	<ul>\n		<li><span class=\"atnm\">delim</span> : change the delimiter for specifying attribute lists. Default is comma (,)</li>\n		<li><span class=\"atnm\">paramdelim</span> : change the delimiter for specifying inter-parameter attributes. Default is colon (:)</li>\n		<li><span class=\"atnm\">collate</span> : switch the plugin into <a href=\"#collate_mode\">collate mode</a></li>\n		<li><span class=\"atnm\">collatedelim</span> : change the delimiter between each collated item. Default is comma (,)</li>\n		<li><span class=\"atnm\">combo</span> : specify your own <a href=\"#combos\">custom replacement tags</a></li>\n		<li><span class=\"atnm\">escape</span> : list of replacement variable names to &#8216;escape&#8217;, in other words convert any <span class=\"caps\">HTML</span> tags to their character entity equivalents. Set to <code>escape=&quot;&quot;</code> to turn this feature off. You can escape your own combos. Default: <code>alt, title, caption</code></li>\n		<li><span class=\"atnm\">strictfields</span> : when using &#8216;?&#8217; or &#8216;!&#8217; fields, spaces are allowed in field names. Set <code>strictfields=&quot;1&quot;</code> to forbid spaces. Default: 0</li>\n		<li><span class=\"atnm\">galleryid</span> : under normal circumstances you should never need this. Each gallery is assigned a unique ID based on the supplied attributes so you may use more than one gallery on a page and navigate them independently. In the unlikely event that two identical IDs are generated you can override the ID for a gallery by using this attribute.<div class=\"break\">Internally the plugin generates a 32-character hash string for each tag, then picks a 4-char code from this string (it takes every 6th character by default). You can supply a unique fixed string (e.g. <code>galleryid=&quot;MyGal&quot;</code>) or give it a question mark and 2 numbers separated by a colon (e.g. <code>galleryid=&quot;?5:4&quot;</code>). The first number is the length of the final string to generate, the second number is how many characters to skip in the 32-char string each time it picks one.</div><div class=\"break\">In this case it generates a 5-character string from the characters it finds at positions 1,5,9,13, and 17. If you omit the 2nd number (e.g. <code>galleryid=&quot;?6&quot;</code>) it will assume it&#8217;s an increment of 1</div></li>\n		<li><span class=\"atnm\">thumb</span> : under normal operation, thumbnails use the &#8216;t&#8217; image in your <code>images</code> directory and the full size pictures don&#8217;t. If you wish to redefine this you may specify up to two values separated by a colon. The first indicates the state of the full size image, and the second is the state of the thumbnail:\n	<ul>\n		<li>thumb=0:1 &#8212; fullsize = regular image : thumbnail = thumb</li>\n		<li>thumb=1:0 &#8212; fullsize = thumb : thumbnail = regular image</li>\n		<li>thumb=0:0 (or thumb=0) &#8212; fullsize = regular image : thumbnail = regular image</li>\n		<li>thumb=1:1 (or thumb=1) &#8212; fullsize = thumb : thumbnail = thumb</li>\n	</ul></li>\n		<li><span class=\"atnm\">text</span> : display the given text instead of thumbnails. The text can contain <code>?</code> article fields</li>\n	</ul>\n\n	<h4>Special attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">columns</span> : give a comma-separated list of additional columns you would like to pull from the database. Each item you specify is split into three parts: <code>table.column:replacement name</code>. The only mandatory part is the replacement name, since <code>table</code> defaults to txp_image, and the <code>column</code> defaults to the same name as the replacement you specify.<div class=\"break\">For example, if you have installed the cno_image_order plugin you could use <code>columns=&quot;position&quot;</code>, since the plugin adds the &#8216;position&#8217; column to txp_image. You could then sort by <code>position</code> and also display the image&#8217;s <code>{position}</code>. If you wished to rename the variable to, say, <code>imgpos</code> you would use <code>columns=&quot;position:imgpos&quot;</code>. Note that if you specify the table name you must also include the table prefix if you have used one.</div></li>\n		<li><span class=\"atnm\">directory</span> : (<span class=\"required\">experimental</span>) read images from a list of directories (or <span class=\"caps\">URL</span>s) instead of the <span class=\"caps\">TXP</span> database</li>\n		<li><span class=\"atnm\">match</span> : limit the images and directories chosen in <code>directory</code> mode. Give a list of words to include or !exclude and any matching files or directories will be chosen or ignored, respectively. The match terms are automatically assumed to be &#8220;wild&#8221; so any part of the file name / path will trigger the match</li>\n		<li><span class=\"atnm\">debug</span> : turn on diagnostics. Varying levels of verbosity from 0 (none) to 4 (a lot)</li>\n	</ul>\n\n	<h2 id=\"gallery_form\">Gallery form</h2>\n\n	<p>The <code>form</code> is where you design what every thumbnail will look like on the screen. You do this by specifying <span class=\"caps\">HTML</span> markup or <span class=\"caps\">TXP</span> tags as normal. In key places where you want, for example, an image ID / alt tag / image width to be displayed, you specify special replacement tags, detailed below. These are replaced for each image in turn, thus building up the gallery.</p>\n\n	<p>In collate mode, the image details are instead made into lists. The form is only processed once and the replacement tags output the entire list.</p>\n\n	<h3 class=\"atts \" id=\"rep_gallery\">Replacement tags: gallery</h3>\n\n	<ul>\n		<li><span class=\"atnm\">{id}</span> : image ID (or, in directory mode, the filename without its extension)</li>\n		<li><span class=\"atnm\">{name}</span> : full image filename as defined in the database, usually including extension</li>\n		<li><span class=\"atnm\">{ext}</span> : file extension, including dot</li>\n		<li><span class=\"atnm\">{category}</span> : image category name</li>\n		<li><span class=\"atnm\">{category_title}</span> : image category title</li>\n		<li><span class=\"atnm\">{author}</span> : who uploaded the image</li>\n		<li><span class=\"atnm\">{url}</span> : <span class=\"caps\">URL</span> of the fullsize image</li>\n		<li><span class=\"atnm\">{thumburl}</span> : <span class=\"caps\">URL</span> of the thumbnail</li>\n		<li><span class=\"atnm\">{imagepath}</span> : directory the image is in</li>\n		<li><span class=\"atnm\">{imagedef}</span> : just the file name part of the image (e.g. 5.jpg)</li>\n		<li><span class=\"atnm\">{thumbdef}</span> : just the file name part of the thumbnail (e.g. 5t.jpg)</li>\n		<li><span class=\"atnm\">{alt}</span> : alt text</li>\n		<li><span class=\"atnm\">{title} or {caption}</span> : caption</li>\n		<li><span class=\"atnm\">{date}</span> : image upload date</li>\n		<li><span class=\"atnm\">{width}</span> : image width</li>\n		<li><span class=\"atnm\">{height}</span> : image height</li>\n		<li><span class=\"atnm\">{wunits}</span> : image width units (e.g. px, %, em, etc.)</li>\n		<li><span class=\"atnm\">{hunits}</span> : image height units</li>\n		<li><span class=\"atnm\">{thumbwidth}</span> : thumbnail width</li>\n		<li><span class=\"atnm\">{thumbheight}</span> : thumbnail height</li>\n		<li><span class=\"atnm\">{thumbwunits}</span> : thumbnail width units (e.g. px, %, em, etc.)</li>\n		<li><span class=\"atnm\">{thumbhunits}</span> : thumbnail height units</li>\n		<li><span class=\"atnm\">{hasthumb}</span> : if the image has an associated thumbnail or not</li>\n		<li><span class=\"atnm\">{galleryid}</span> : gallery id that you or the plugin assigned</li>\n		<li><span class=\"atnm\">{object}</span> : a special item containing either the <code>text</code> OR the image tag of the corresponding thumbnail, but <em>only</em> if <code>thumblimit</code> allows it. e.g. if <code>thumblimit=&quot;1&quot;</code> this variable is only set for the first thumbnail in the list; every other time it is empty</li>\n		<li><span class=\"atnm\">{counter}</span> : image counter, starting from 1 and incrementing each time</li>\n		<li><span class=\"atnm\">{index}</span> : index counter (zero-based &#8212; probably not much use)</li>\n		<li><span class=\"atnm\">{thumbcounter}</span> : thumbnail image counter, starting from 1. Note it is &#8216;reset&#8217; whenever an element given in <code>onchange</code> differs from the previous item</li>\n		<li><span class=\"atnm\">{thumbindex}</span> : index counter (zero-based &#8212; probably not much use). It is reset like <code>{thumbcounter}</code></li>\n		<li><span class=\"atnm\">{totalimages}</span> : total number of images matched on this page</li>\n		<li><span class=\"atnm\">{maximages}</span> : absolute maximum number of images across all pages</li>\n	</ul>\n\n	<p>In addition, if either <code>paging=&quot;1&quot;</code> or <code>pageform</code> have been specified, all the <a href=\"#rep_paging\">paging replacement tags</a> are available too. If those aren&#8217;t enough, you can <a href=\"#combos\">create your own</a> by grouping combinations of the above, optionally with article variables such as <code>?excerpt</code>, <code>?keywords</code>, <code>?article_image</code>, etc.</p>\n\n	<p>Other tags are added depending if you are watching for <code>onchange</code> elements, or if you have specified a <code>grouptag</code>. If you added <code>onchange=&quot;author, category&quot;</code> then every time the author or category changed compared to the previous image, one or two new replacement tags would become available; namely <code>{onchange:author}</code> and <code>{onchange:category)</code>. Using onchange with <code>smd_every</code> does <em>not</em> add a new replacement tag as it is merely used to count.</p>\n\n	<p>If you used <code>grouptag=&quot;div&quot;</code> as well, you would be given two new replacement tags labelled <code>grouptagstart</code> and <code>grouptagend</code>. See <a href=\"#eg6\">example 6</a> for details.</p>\n\n	<h2 id=\"paging_form\">Paging form</h2>\n\n	<p>Add gallery navigation to your page. Works in a similar manner to the <a href=\"#gallery_form\">gallery form</a>: a series of replacement tags govern which gallery navigation items are available. You could choose to only show next/prev. Or perhaps 1st page/last page. Or numbered page links.</p>\n\n	<h3 class=\"atts \" id=\"rep_paging\">Replacement tags: paging</h3>\n\n	<ul>\n		<li><span class=\"atnm\">{navfirst}</span> : first page (default label: &#8220;First&#8221; / <span class=\"caps\">MLP</span>)</li>\n		<li><span class=\"atnm\">{navprev}</span> : previous page (default label: &#8220;&laquo;&#8221; / <span class=\"caps\">MLP</span>)</li>\n		<li><span class=\"atnm\">{navearlier}</span> : any page numbers before the current page</li>\n		<li><span class=\"atnm\">{navthisurl}</span> : current page with a <span class=\"caps\">URL</span> link to itself</li>\n		<li><span class=\"atnm\">{navthis}</span> : current page without a <span class=\"caps\">URL</span> link</li>\n		<li><span class=\"atnm\">{navlater}</span> : any page numbers after the current page</li>\n		<li><span class=\"atnm\">{navnext}</span> : next page (default label: &#8220;&raquo;&#8221; / <span class=\"caps\">MLP</span>)</li>\n		<li><span class=\"atnm\">{navlast}</span> : last page (default label: &#8220;Last&#8221; / <span class=\"caps\">MLP</span>)</li>\n		<li><span class=\"atnm\">{navfirstpageurl}</span> : raw <span class=\"caps\">URL</span> to first page</li>\n		<li><span class=\"atnm\">{navprevpageurl}</span> : raw <span class=\"caps\">URL</span> to previous page</li>\n		<li><span class=\"atnm\">{navthispageurl}</span> : raw <span class=\"caps\">URL</span> to current page</li>\n		<li><span class=\"atnm\">{navnextpageurl}</span> : raw <span class=\"caps\">URL</span> to next page</li>\n		<li><span class=\"atnm\">{navlastpageurl}</span> : raw <span class=\"caps\">URL</span> to last page</li>\n		<li><span class=\"atnm\">{totalimages}</span> : total number of images matched on the current page</li>\n		<li><span class=\"atnm\">{totalpages}</span> : total number of pages in this collection</li>\n		<li><span class=\"atnm\">{maximages}</span> : absolute maximum number of images across all pages</li>\n	</ul>\n\n	<p>The current page has a fixed class of &#8216;current&#8217; and each item is, by default, a <code>&lt;li&gt;</code> for easy styling. Custom labels are defined directly in the form itself; for example to label the link that takes users to the first page of the gallery as &#8220;1st&#8221;, use:</p>\n\n	<p><code>{navfirst:1st}</code></p>\n\n	<p>With this syntax you can label navfirst, navlast, navprev, navnext and navthis (the &#8216;:&#8217; delimiter in this case is specified by <code>paramdelim</code>). You can use this mechanism to read <span class=\"caps\">MLP</span> snippets, e.g:</p>\n\n	<p><code>{navfirst:##firstLabel##}</code></p>\n\n	<p>You are not limited to text labels. Images could be used:</p>\n\n	<p><code>{navlast: &lt;img src=&quot;images/last.png&quot; /&gt;}</code></p>\n\n	<p>You can override the number of items displayed by navearlier and navlater by specifying a colon and a number. So to only show 2 items to the left of the current page and 3 items to the right, use <code>{navearlier:2} {navlater:3}</code> in your form. If you were on page 9 of 15, {navearlier:4} {navlater:3} would show <code>&lt;&lt; 5 6 7 8 9 10 11 12 &gt;&gt;</code></p>\n\n	<p>Currently <code>{navearlier}</code> and <code>{navlater}</code> only display arabic numerals. Any ideas on how to offer other character sets greatly appreciated.</p>\n\n	<h2 id=\"countform\">Count forms</h2>\n\n	<p>Sometimes it&#8217;s handy to be able to take action only when certain images are reached; for example:</p>\n\n	<ul>\n		<li>only display something on the first or last image</li>\n		<li>start a new <span class=\"caps\">HTML</span> &#8216;row&#8217; every 3rd image</li>\n		<li>break your gallery up with an advert</li>\n	</ul>\n\n	<p>This can be done with smd_if but since it is such a common feature, the plugin has the ability built in. All replacement tags are available within your countforms so you can do some pretty neat stuff. It is best explained by example:</p>\n\n	<ul>\n		<li><code>countform=&quot;1:gal_head&quot;</code> : this would output the form <em>gal_head</em> only when the first image is displayed</li>\n		<li><code>countform=&quot;{totalimages}:gal_foot&quot;</code> : this would output the form <em>gal_foot</em> only when the last image is displayed</li>\n		<li><code>countform=&quot;1:gal_head, {totalimages}:gal_foot&quot;</code> : do both of the above</li>\n		<li><code>countform=&quot;1:gal_head, {totalimages}:gal_foot:smd_below&quot;</code> : the same as the previous example, but put the contents below the last image (by default, all output appears above the given image)</li>\n		<li><code>countform=&quot;4:9:smd_below:gal_ad&quot;</code> : output <em>gal_ad</em> after the 4th and 9th images only</li>\n		<li><code>countform=&quot;4-9:smd_below:gal_ad&quot;</code> : output <em>gal_ad</em> after the 4th, 5th, 6th, 7th, 8th and 9th images only</li>\n		<li><code>countform=&quot;smd_every:3:gal_row&quot;</code> : output <em>gal_row</em> before every 3rd image</li>\n		<li><code>countform=&quot;smd_every:2:!8:gal_row&quot;</code> : output <em>gal_row</em> before every even image but not on the 8th image</li>\n		<li><code>countform=&quot;:gal_row&quot;</code> : use an empty first value to output <em>gal_row</em> before every image. This may seem useless, but it means you can use two, three, four&#8230; forms (or any number of forms and the container) simultaneously to iterate over the same gallery. Or use the same form at different frequencies. Get creative!</li>\n		<li><code>countform=&quot;:!5:gal_row:smd_below&quot;</code> : again, missing out the first value will output gal_row after every image, but this time skipping the 5th image</li>\n	</ul>\n\n	<p>The <code>,</code> and <code>:</code> can be overridden with the <code>delim</code> and <code>paramdelim</code> attributes, respectively.</p>\n\n	<p>There are a few rules when using the countform:</p>\n\n	<ul>\n		<li>numbers must appear before the form name</li>\n		<li>the form name is usually the last item in the colon-separated list, though you can put <code>smd_above</code> or <code>smd_below</code> last if you wish</li>\n		<li><code>smd_every</code> only affects the value that <em>immediately</em> follows it, e.g. <code>countform=&quot;smd_every:5:7:12:my_ad&quot;</code> will output <em>my_ad</em> at every 5th image, as well as <em>only</em> on the 7th and 12th images. Thus the sequence would be 5, 7, 10, 12, 15, 20, 25, 30&#8230;</li>\n		<li>if using <code>smd_every</code>, it <strong>must</strong> appear as the first item. For example <code>countform=&quot;4:the_fourth, 6:smd_every:5:my_ad&quot;</code> is invalid, but <code>countform=&quot;4:the_fourth, smd_every:5:6:my_ad&quot;</code> is OK</li>\n	</ul>\n\n	<p>See <a href=\"#eg10\">example 10</a> for more.</p>\n\n	<h2 id=\"combos\">Combos</h2>\n\n	<p>If the built-in <a href=\"#rep_gallery\">replacement tags</a> aren&#8217;t enough for your needs or you find yourself repeating the same combination and want to save yourself some typing, you can define your own. The syntax is:</p>\n\n	<p><code>combo=&quot;newtag1:definition, newtag2:definition, ...&quot;</code></p>\n\n	<p>&#8216;definition&#8217; can be any fixed text, any existing replacement tag, or any &#8216;?&#8217; article variable so you can get really wild here, for example:</p>\n\n	<p><code>combo=&quot;imgin:found in ?s [tagged with ?keywords. Photo by {author}]&quot;</code></p>\n\n	<p>Once defined, <code>{imgin}</code> is available to your gallery <code>form</code> and might be replaced with &#8220;found in zoo [tagged with zebra, africa, stripes. Photo by Bloke]&#8221;. $pretext is also available for those geeks who know what&#8217;s in it (temporarily use <code>debug=&quot;4&quot;</code> to view all available article variables).</p>\n\n	<p>One thing to watch out for is that the space character isn&#8217;t necessarily a delimiter for <code>?</code> variables (because custom fields can have spaces in them). So <code>combo=&quot;doesnotwork:?s in ?category1&quot;</code> will look for a variable labelled &#8220;s in&#8221; and one called &#8220;category1&#8221;. Only one of those exists! Clever use of parentheses or other punctuation can work round this. Or use <code>strictfields=&quot;0&quot;</code> to turn off the use of spaces in custom field names.</p>\n\n	<h2 id=\"collate_mode\">Collate mode</h2>\n\n	<p>If you want to get <em>really</em> funky you can put the plugin into a 2nd mode called &#8220;collation mode&#8221;. This means your chosen form is <strong>not</strong> executed for every image; it is only used once at the very end of the list, after all the images have been internally &#8220;collected&#8221;. In effect, the plugin builds up a giant array of all matching images ready for you to output them all in one go. This is very handy for doing slideshows and is best explained by example:</p>\n\n	<p><code>collate=&quot;1&quot;</code></p>\n\n	<p>switches collation mode on. If the plugin matched 4 images and you were to use the replacement tag <code>{name}</code> in your form like this:</p>\n\n<pre class=\"block\"><code class=\"block\">The matching images: {name}\n</code></pre>\n\n	<p>you might get this:</p>\n\n<pre class=\"block\"><code class=\"block\">The matching images: heron.jpg,penguin.jpg,emu.jpg,puffin.jpg\n</code></pre>\n\n	<p>Sometimes it&#8217;s useful to be able to put quotes around each item so you can tell collate mode to do that instead:</p>\n\n	<p><code>collate=&quot;quote:{name}&quot;</code></p>\n\n	<p>You would then get:</p>\n\n<pre class=\"block\"><code class=\"block\">The matching images: &quot;heron.jpg&quot;,&quot;penguin.jpg&quot;,&quot;emu.jpg&quot;,&quot;puffin.jpg&quot;\n</code></pre>\n\n	<p>The delimiter (a comma in this case) can be overridden with the <code>delim</code> attribute. You may use <code>sglquote</code> to wrap single quotes round the items, or &#8220;dblquote&#8221; as a synonym for &#8220;quote&#8221; if you prefer. You can quote more than one thing at once by specifying the items as a delimited list:</p>\n\n	<p><code>collate=&quot;quote:{name}:{alt}:{imgin}&quot;</code></p>\n\n	<p>Yes, you can use combos! The delimiter used between items can be overriden with the <code>collatedelim</code> attribute.</p>\n\n	<p>The second special feature of collation mode is that you do not have to always output the entire list. You can grab individual entities from within the internal array by using the &#8216;#&#8217; notation thus:</p>\n\n<pre class=\"block\"><code class=\"block\">{name} might output &quot;heron.jpg&quot;,&quot;penguin.jpg&quot;,&quot;emu.jpg&quot;,&quot;puffin.jpg&quot; (as before)\n{name#1} would only output heron.jpg\n{name#3} would only output emu.jpg\n</code></pre>\n\n	<p>Note that when pulling out individual entries they <strong>do not</strong> get quotes added to them, regardless of whether you used <code>quote</code> or not. This is because it is a single item so you can easily put the quotes in the form itself (viz: <code>&quot;{name#2}&quot;</code>)</p>\n\n	<p>Messing around with pulling out individual items and lists can yield some very interesting results and allows some completely wacky galleries to be generated. Try combining smd_gallery tags with <code>&lt;txp:if_different&gt;</code> for a real mind scramble :-)</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<p>The examples all use the classic zoo website. Let&#8217;s get some pictures out of the database&#8230;</p>\n\n	<h3 id=\"eg1\">Example 1: simple / lightboxed gallery</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;animals&quot; sublevel=&quot;all&quot; form=&quot;gallery&quot; /&gt;\n</code></pre>\n\n	<p>That pulls out all pictures from the animals category, including all sub-categories, and passes them to the form called &#8216;gallery&#8217; for processing. The form could be:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;img class=&quot;animage&quot; src=&quot;{url}&quot; alt=&quot;{alt}&quot; /&gt;\n&lt;div class=&quot;acaption&quot;&gt;{title}&lt;/div&gt;\n</code></pre>\n\n	<p>That means, for every matched image, substitute its <span class=\"caps\">URL</span>, alt and title tags into the relevant places. Thus you get a series of images on the page, which you can style accordingly.</p>\n\n	<p>For a more fancy output using, say, slimbox, you could load the relevant libraries and styles in the <code>&lt;head&gt;</code> of your page:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;script type=&quot;text/javascript&quot; src=&quot;/js/mootools.js&quot;&gt;&lt;/script&gt;\n&lt;script type=&quot;text/javascript&quot; src=&quot;/js/slimbox.js&quot;&gt;&lt;/script&gt;\n&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/css/slimbox.css&quot; /&gt;\n</code></pre>\n\n	<p>Then your &#8216;gallery&#8217; form could be:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;a rel=&quot;lightbox-{category}&quot; href=&quot;{url}&quot;\n     title=&quot;A picture of {caption}&quot;&gt;\n &lt;txp:thumbnail id=&quot;{id}&quot; /&gt;\n&lt;/a&gt;\n</code></pre>\n\n	<p>Voila! Using the same smd_gallery tag you have changed the way the gallery renders from a simple list to a full-featured slimbox. With virtually the same form you can switch the output to thickbox, lightbox, litebox, moonbox or whatever-you-like-box depending on which libraries you load. Follow the instructions for your favourite effect and plug it into smd_gallery.</p>\n\n	<h3 id=\"eg2\">Example 2: launch from one image</h3>\n\n	<p>A common request is to be able to &#8220;launch&#8221; a slimbox gallery from a single image or text link. Using a single image:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;animals&quot;\n     sublevel=&quot;all&quot;\n     form=&quot;gallery&quot; thumblimit=&quot;1&quot; /&gt;\n</code></pre>\n\n	<p>The form will use the special <code>{object}</code> replacement tag so it only displays the thumbnail if the value of thumblimit has <strong>not</strong> been reached:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;a rel=&quot;lightbox-smd&quot; href=&quot;{url}&quot;\n     title=&quot;A picture of {title}&quot;&gt;\n{object}\n&lt;/a&gt;\n</code></pre>\n\n	<p>Change the start image by specifying <code>thumboffset</code> &#8212; note this will also start the lightbox effect from the image you choose. If you want to start on a specific image in position 1, use the <code>id</code> attribute to bring it to the top of the list, or change the sort order.</p>\n\n	<p>If you wanted to launch the gallery from a text link, add something like <code>text=&quot;Click to start lightbox&quot;</code>.</p>\n\n	<h3 id=\"eg3\">Example 3: popup gallery</h3>\n\n	<p>To achieve an effect similar to rss_thumbpop, add an image and caption placeholder outside the call to smd_gallery, then include your smd_gallery tag as usual. This time we are only interested in all the pictures from the categories up to two levels below both &#8220;animals&#8221;, plus whichever categories are listed in <code>my_custom_field</code> and the images with IDs 25, 38, 39, 40 and 42:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;img id=&quot;fullsize&quot; src=&quot;/images/empty.jpg&quot; /&gt;\n&lt;span id=&quot;fullcaption&quot;&gt;&lt;/span&gt;\n&lt;txp:smd_gallery sublevel=&quot;2&quot;\n     category=&quot;animals, ?my_custom_field&quot;\n     id=&quot;25, 42, 38-40&quot; form=&quot;gallery&quot; /&gt;\n</code></pre>\n\n	<p>The gallery form looks scary but is only a thumbnail image surrounded by a simple anchor tag with two actions assigned to it: a mouseover to copy the picture and caption into the placeholder; and a mouseout to remove them:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;a href=&quot;{url}&quot; title=&quot;{title}&quot;\n     onmouseover=&quot;document.getElementById(&#39;fullsize&#39;).src = this.href;\n     document.getElementById(&#39;fullcaption&#39;).innerHTML = this.title; return false;&quot;\n     onmouseout=&quot;document.getElementById(&#39;fullsize&#39;).src = &#39;/images/17.jpg&#39;;\n     document.getElementById(&#39;fullcaption&#39;).innerHTML = &#39;&#39;; return false;&quot;&gt;\n  &lt;img src=&quot;{thumburl}&quot; alt=&quot;{alt}&quot; /&gt;\n&lt;/a&gt;\n</code></pre>\n\n	<p>Note there are much neater ways of doing this (e.g. with jQuery) and it&#8217;s not recommended to use this form in a live site, but it gives a flavour of what is possible.</p>\n\n	<h3 id=\"eg4\">Example 4: slideshow</h3>\n\n	<p>This one uses collate mode but is not much more difficult to understand. In your <code>&lt;head&gt;</code> section:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;script type=&quot;text/javascript&quot; src=&quot;/js/mootools.js&quot;&gt;&lt;/script&gt;\n&lt;script type=&quot;text/javascript&quot; src=&quot;/js/slideshow.js&quot;&gt;&lt;/script&gt;\n</code></pre>\n\n	<p>The tag:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery\n  category=&quot;birds, fish&quot; form=&quot;gallery&quot;\n  combo=&quot;imglist:{id}{ext}&quot;\n  collate=&quot;quote:{imglist}:{alt}&quot; /&gt;\n</code></pre>\n\n	<p>That pulls all pictures from the birds and fish categories. Although technically unnecessary, a combo has been defined to show how it can be used. This combo is called <code>imglist</code> and is defined as the image&#8217;s ID number plus its extension. We use this in the collate attribute and tell it to put double quotes round every item in &#8216;imglist&#8217; and &#8216;alt&#8217;. Then we can put these values into the necessary javascript in the <code>gallery</code> form to generate the slideshow:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;div class=&quot;slideshow&quot; id=&quot;slideshow1&quot;&gt;\n &lt;img src=&quot;&lt;txp:site_url /&gt;images/{id#1}.jpg&quot;\n     alt=&quot;{alt#1}&quot; width=&quot;{width#1}&quot; height=&quot;{height#1}&quot; /&gt;\n&lt;/div&gt;\n</code>\n<code class=\"block\">&lt;script type=&quot;text/javascript&quot;&gt;\nmyShow1 = new Slideshow(&quot;slideshow1&quot;,\n  { hu: &quot;&lt;txp:site_url /&gt;images/&quot;,\n    images: [{imglist}],\n    captions: [{alt}],\n    classes: [&quot;prev&quot;,&quot;next&quot;,&quot;active&quot;],\n    type: &quot;fade&quot;\n  });\n&lt;/script&gt;\n</code></pre>\n\n	<p>Note that the initial portion (in the div and img tags) uses the &#8216;#&#8217; notation because we only want to display one image to &#8220;start&#8221; the slideshow. In this case we&#8217;re using the first image in the list, but you can change the #1 for #42 if you wanted to show the forty-second image instead! The part in the script tag uses the full, quoted lists because that&#8217;s the format the javascript requires.</p>\n\n	<h3 id=\"eg5\">Example 5: pagination</h3>\n\n	<p>To add paging to a gallery you use the <code>limit</code> attribute to show a number of thumbs per page. For a paged thumbpop gallery that shows five thumbs per page:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;img id=&quot;fullsize&quot; src=&quot;/images/empty.jpg&quot; /&gt;\n&lt;span id=&quot;fullcaption&quot;&gt;&lt;/span&gt;\n&lt;txp:smd_gallery category=&quot;animals&quot;\n  form=&quot;gallery&quot; pageform=&quot;gallery_nav&quot;\n  limit=&quot;5&quot; /&gt;\n</code></pre>\n\n	<p>The &#8216;gallery&#8217; form is identical to the one in <a href=\"#eg4\">example 4</a>, while your gallery_nav form might look like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;div class=&quot;total&quot;&gt;# of pics: {totalimages}&lt;/div&gt;\n     {navfirst} {navprev}\n     {navearlier} {navthis} {navlater}\n     {navnext} {navlast}\n</code></pre>\n\n	<p>That might render something similar to this below the thumbs:</p>\n\n<pre class=\"block\"><code class=\"block\"># of pics: 38\n     First &lt; 1 2 3 4 5 6 7 8 &gt; Last\n</code></pre>\n\n	<h3 id=\"eg6\">Example 6: onchange</h3>\n\n	<p>If you want to simulate multiple galleries from one smd_gallery tag you can use <code>onchange</code> to &#8220;watch&#8221; for when certain attributes of images change and insert special tags <em>only when something changes</em>. In this regard it acts like a souped-up version of <span class=\"caps\">TXP</span>&#8217;s built-in <code>&lt;txp:if_different&gt;</code> tag.</p>\n\n	<p>You can watch the usual things like <code>category</code>, <code>author</code>, <code>width</code>, <code>height</code>, <code>ext</code>ension and so on:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery\n     category=&quot;birds, fish&quot; form=&quot;gallery&quot;\n     onchange=&quot;category_title&quot;\n     onchangewraptag=&quot;h3&quot; /&gt;\n</code></pre>\n\n	<p>Since the plugin normally sorts by category, this will have the effect of adding one replacement tag <code>{onchange:category_title}</code> at the very beginning of the thumb list and also when the category switches from &#8216;birds&#8217; to &#8216;fish&#8217;. Thus your gallery form could be:</p>\n\n<pre class=\"block\"><code class=\"block\">{onchange:category_title}\n&lt;a href=&quot;{url}&quot; rel=&quot;lightbox-{category}&quot;&gt;\n{object}\n&lt;/a&gt;\n</code></pre>\n\n	<p>That would make two lightboxable sets of thumbnails with a heading 3 above each group containing the category title.</p>\n\n	<p>Note that if you add <code>thumblimit=&quot;1&quot;</code> to the smd_gallery tag you can show just one thumbnail in each group, with headings above each thumb. Visitors clicking the thumb will launch the lightbox and be able to browse the relevant category. If you want to make sure that they can only browse six images in each category set, add <code>grouplimit=&quot;6&quot;</code>.</p>\n\n	<p>An extra pair of replacement tags are available if you wish to wrap the objects themselves to group them. Use the <code>grouptag</code> and <code>groupclass</code> attributes to set it up, then two new replacement tags called <code>grouptagstart</code> and <code>grouptagend</code> become available. They are conditional and are only populated when the attributes you are watching/sorting by change. Use it like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery id=&quot;?article_image&quot;\n     category=&quot;birds, fish&quot; form=&quot;gallery&quot;\n     onchange=&quot;category_title&quot;\n     onchangewraptag=&quot;h3&quot;\n     grouptag=&quot;div&quot; groupclass=&quot;box&quot; /&gt;\n</code></pre>\n\n<pre class=\"block\"><code class=\"block\">{onchange:category_title}\n{grouptagstart}\n&lt;a href=&quot;{url}&quot; rel=&quot;lightbox-{category}&quot;&gt;\n{object}\n&lt;/a&gt;\n{grouptagend}\n</code></pre>\n\n	<p>Note that you can move the <code>grouptagstart</code> to surround the <code>onchange</code>d title if you wish.</p>\n\n	<h3 id=\"eg7\">Example 7: smd_every</h3>\n\n	<p>You can also trigger an onchange event every nth image:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;frontpage&quot;\n     limit=&quot;9&quot; sort=&quot;random&quot;\n     onchange=&quot;smd_every:3&quot;\n     grouptag=&quot;ul&quot; groupclass=&quot;slideshow&quot;&gt;\n{grouptagstart}\n   &lt;li&gt;\n      &lt;a href=&quot;&lt;txp:permlink id=&quot;{alt}&quot; /&gt;&quot;&gt;\n         &lt;img src=&quot;{thumburl}&quot; alt=&quot;&quot; /&gt;\n         &lt;span&gt;{title}&lt;/span&gt;\n      &lt;/a&gt;\n   &lt;/li&gt;\n{grouptagend}\n&lt;/txp:smd_gallery&gt;\n</code></pre>\n\n	<h3 id=\"eg8\">Example 8: uncategorized images</h3>\n\n	<p>Sometimes you might wish to extract images that do not have a category assigned. You need to use the special value <code>SMD_EMPTY</code> for this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;SMD_EMPTY, shoes&quot; /&gt;\n</code></pre>\n\n	<p>You can also negate the logic so you can extract all images that have some (any) category assigned to them:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;!SMD_EMPTY&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"eg9\">Example 9: {object} and objectform</h3>\n\n	<p>The default <code>{object}</code> outputs a simple hyperlinked thumbnail. If you want something a bit more exotic, like launching a shadowbox effect from a single thumbnail that also has the caption beneath it, you need to customise the <code>{object}</code> via the <code>objectform</code> attribute. Put a list of image IDs in your article image field and then:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery thumblimit=&quot;1&quot;\n     id=&quot;?article_image&quot; wraptag=&quot;div&quot;\n     class=&quot;thumbs&quot; objectform=&quot;my_obj&quot;\n     combo=&quot;artid:?thisid&quot;&gt;\n  &lt;a rel=&quot;shadowbox[{artid}];\n     options={counterType:&#39;skip&#39;,\n     continuous:true,\n     animate:false,\n     animSequence:&#39;sync&#39;}&quot;\n     href=&quot;{url}&quot;\n     title=&quot;{alt}&quot;&gt;{object}&lt;/a&gt;\n&lt;/txp:smd_gallery&gt;\n</code></pre>\n\n	<p>And in <code>my_obj</code>, you could put:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:thumbnail id=&quot;{id}&quot; /&gt;&lt;br /&gt;{title}\n</code></pre>\n\n	<p>Notice how the replacement tags still work in the <code>objectform</code>? This allows you to build up a complete layout for the launchable thumbnail and have it only rendered once on the page. The rest of the container in this example renders all the &#8220;hidden&#8221; images in the shadowbox gallery.</p>\n\n	<h3 id=\"eg10\">Example 10: <code>countform</code></h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery category=&quot;animals&quot;\n     countform=&quot;1:gal_a:smd_above,\n       {totalimages}:gal_c,\n       smd_every:3:!{totalimages}:gal_b&quot;\n     countpos=&quot;below&quot;&gt;\n&lt;txp:thumbnail id=&quot;{id}&quot; /&gt;\n&lt;/txp:smd_gallery&gt;\n</code></pre>\n\n	<p>Note that <code>countpos</code> is set to <code>below</code> in this example so all output appears <em>after</em> the given images. If you made the following three forms, your tag would split the gallery into &#8216;rows&#8217; of 3 images:</p>\n\n	<p>gal_a: <code>&lt;div class=&quot;newrow&quot;&gt;</code><br />\ngal_b: <code>&lt;/div&gt;&lt;div class=&quot;newrow&quot;&gt;</code><br />\ngal_c: <code>&lt;/div&gt;</code></p>\n\n	<p>Breaking the <code>countform</code> attribute down into its components, you can see that:</p>\n\n	<ul>\n		<li><code>1:gal_a:smd_above</code> adds the &#8216;newrow&#8217; div above the first image</li>\n		<li><code>{totalimages}:gal_c</code> adds the closing div below the last image on this page</li>\n		<li><code>smd_every:3:!{totalimages}:gal_b</code> adds a closing and then an opening div after every 3rd image, <em>except</em> on the last image. If we didn&#8217;t put the exception in, and you had 12 thumbnails in your gallery (which is divisible by 3!) you would see the contents of gal2 after the last image as well</li>\n	</ul>\n\n	<h3 id=\"eg11\">Example 11: Using an onchange form</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery\n     category=&quot;birds, fish&quot;\n     onchange=&quot;category_title:oc_form&quot;&gt;\n{onchange:category_title}\n&lt;a href=&quot;{url}&quot; rel=&quot;lightbox-{category}&quot;&gt;\n{object}\n&lt;/a&gt;\n&lt;/txp:smd_gallery&gt;\n</code></pre>\n\n	<p>and in form <code>oc_form</code>:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;div id=&quot;{category}&quot;\n   class=&quot;category&quot;&gt;{category_title}&lt;/div&gt;\n</code></pre>\n\n	<h3 id=\"eg12\">Example 12: Portfolio landing page</h3>\n\n	<p>Here we will render a gallery for each artist on a gallery site. Arrange your image categories so they are children of a parent image category:</p>\n\n<pre class=\"block\"><code class=\"block\">portfolio\n  |--&gt; monet\n  |--&gt; van_gogh\n  |--&gt; turner\n  |--&gt; dali\n  |--&gt; ...\n</code></pre>\n\n	<p>Then of course, upload your images and assign them to the appropriate artist category.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_gallery\n     category=&quot;portfolio&quot; sublevel=&quot;1&quot;\n     onchange=&quot;category_title&quot;\n     onchangewraptag=&quot;h3&quot; thumblimit=&quot;1&quot;\n     grouplimit=&quot;5&quot; sort=&quot;category asc, date desc&quot;\n     objectform=&quot;full_link&quot;&gt;\n{onchange:category_title}\n&lt;a href=&quot;{url}&quot; rel=&quot;lightbox-{category}&quot;&gt;\n{object}\n&lt;/a&gt;\n&lt;/txp:smd_gallery&gt;\n</code></pre>\n\n	<p>In Form <code>full_link</code>:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:thumbnail id=&quot;{id}&quot; /&gt;\n&lt;span&gt;\n   See &lt;a href=&quot;/artists/{category}&quot;&gt;all images by {category_title}&lt;/a&gt;.\n&lt;/span&gt;\n</code></pre>\n\n	<p>That will grab each artist category and display a thumbnail of the most recent work submitted by each author under their own heading. When you click the thumbnail it launches the lightbox gallery and allows you to navigate through the five most recent pictures submitted. Beneath the thumbnail is a link to an individual article that could show that artists&#8217;s complete works and a bio.</p>\n\n	<h2 id=\"changelog\">Changelog</h2>\n\n	<p>All available versions and changes are listed here.</p>\n\n	<p><strong>Beta</strong></p>\n\n	<ul>\n		<li>14 Feb 08 | 0.1 | Initial release &#8211; Happy Valentines Day :-)</li>\n		<li>19 Feb 08 | 0.2 | Added {galleryid} (thanks rloaderro) and some skeleton docs</li>\n	</ul>\n\n	<p><strong>Official</strong></p>\n\n	<ul>\n		<li>11 Mar 08 | 0.3 | Official public release. Fixed silly empty field bug; added {counter}, {index} and the totals (all thanks jakob)</li>\n		<li>15 Mar 08 | 0.31 | Fixed a couple of undefined indexes and added {category_title} (thanks jstubbs)</li>\n		<li>17 Mar 08 | 0.32 | Fixed another undefined index and removed bogus call to smd_lib (both thanks secundar)</li>\n		<li>21 Mar 08 | 0.33 | Did v0.31 properly, including table prefix (thanks nardo)</li>\n		<li>01 Apr 08 | 0.34 | Fixed <span class=\"caps\">SQL</span> parenthesis bug ; added emptyclass and {maximages} (thanks the_ghost)</li>\n		<li>08 Apr 08 | 0.4 | Changed thumb to allow thumb/fullsize swapping ; Added thumbchange, thumbchangewraptag, thumbchangeclass, {thumbindex}, {thumbcounter} and the {changed:*} replacement tags (thanks fuls)</li>\n		<li>26 May 08 | 0.41 | Fixed Directory separator (thanks cuda) ; added raw page <span class=\"caps\">URL</span>s to the nav replacement array and made the nav array available in the standard replacements array (thanks visualpeople)</li>\n		<li>27 May 08 | 0.42 | Added pagepos and made page blocks independent of thumb block (thanks duchamp)</li>\n		<li>10 Jun 08 | 0.43 | Fixed sort=&#8220;fixed&#8221; when using limit (thanks robin746) ; added per-group onchange wrapping and allowed fixed sorting of categories (thanks jstubbs) ; deprecated thumbchange* and renamed as onchange* ; fixed $idx counter problem</li>\n		<li>10 Jun 08 | 0.44 | Attempt #2 at per-group onchange wrapping (thanks again jstubbs)</li>\n		<li>14 Jul 08 | 0.45 | Fixed lack of output when not using categories (thanks robin746)</li>\n		<li>16 Nov 08 | 0.46 | Added collatedelim (thanks curiouz) ; fixed (I hope) the category / id bug from 0.44 and 0.45 (thanks robin746/nardo)</li>\n		<li>13 Feb 09 | 0.5 | Requires smd_lib ; added <span class=\"caps\">MLP</span> support (thanks net-carver) ; added support for displaying uncategorised images (thanks xzamen) ; &#8216;?&#8217; vars expanded to link / image / file / <code>&lt;txp:variable /&gt;</code> and <span class=\"caps\">URL</span> variable ; new attributes: <code>escape</code>, <code>html_id</code>, <code>format</code>, <code>gmt</code>, <code>lang</code> (thanks maniqui), <code>strictfields</code> ; improved <code>{object}</code> support and added <code>objectform</code> (thanks tye/rvturnage) ; <code>{title}</code> == <code>{caption}</code> at last! ; removed <code>thumbchange</code>* attributes ; improved support for table extensions (via the <code>columns</code> attribute: thanks RedFox) ; added <code>smd_every</code> syntax to <code>onchange</code> (thanks jakob) ; fixed offset bug (thanks feragnoli)</li>\n		<li>01 Mar 09 | 0.51 | Added <code>countform</code> and <code>countpos</code> ; fixed <code>{hasthumb}</code> warning</li>\n		<li>16 Nov 09 | 0.52 | <span class=\"caps\">TXP</span> 4.2.0 only ; added database thumbnail height and width support ; added <code>aspect</code>, <code>dimensions</code>, <code>author</code>, <code>extension</code> and <code>imgsize</code> attributes ; fixed quote parsing in <code>escape</code> attribute and warning when using <code>fixed</code> sorting (both thanks maniqui)</li>\n		<li>01 Jan 10 | 0.53 | Added onchange form capability (thanks MattD)</li>\n		<li>01 Feb 10 | 0.54 | Multiple images not displayed if multiple categories exist with the same name and different title</li>\n		<li>01 Mar 10 | 0.55 | Added <code>name</code> filtering</li>\n		<li>10 Mar 10 | 0.6 | Simplified query to eliminate errors with uncategorised images being omitted from the results; allowed countform ranges ; added <code>grouplimit</code></li>\n		<li>03 Apr 10 | 0.61 | Added <code>breakclass</code> (thanks the_ghost)</li>\n		<li>06 Apr 10 | 0.62 | Fixed <code>{onchange:category_title}</code> and <code>{grouptagend}</code> (thanks jan)</li>\n	</ul>\n\n	<h2 id=\"credits\">Credits</h2>\n\n	<p>This plugin wouldn&#8217;t have existed without the awesome mind of Robert Wetzlmayr. His wet_for_each_image plugin is true genius; this simply stands on the shoulders of that idea and rolls in a souped up version of the thumbnail generation engine from smd_slimbox. To that end, the plugin is also indebted to anyone who has posted features or bug reports to me or the smd_slimbox thread. You&#8217;re all marvellous.</p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help a:link, #smd_help a:visited { color: #00c; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #079; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n#smd_help ul { list-style-type:square; }\n#smd_help .required {color:red;}\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'require_plugin(\'smd_lib\');\r\n\r\n// MLP support\r\nglobal $smd_gallLang;\r\n$smd_gallStr = array(\r\n	\'navfirst\' => \'First\',\r\n	\'navlast\' => \'Last\',\r\n	\'navprev\' => \'&laquo;\',\r\n	\'navnext\' => \'&raquo;\',\r\n);\r\n$smd_gallLang = new smd_MLP(\'smd_gallery\', \'smd_gall\', $smd_gallStr);\r\n\r\nfunction smd_gallery($atts, $thing=\'\') {\r\n	global $pretext, $thisarticle, $img_dir, $dateformat, $smd_gallLang, $l10n_language;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'columns\' => \'\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'collatedelim\' => \',\',\r\n		\'strictfields\' => \'0\',\r\n		\'form\' => \'\',\r\n		\'countform\' => \'\',\r\n		\'countpos\' => \'above\',\r\n		\'id\' => \'\',\r\n		\'name\' => \'\',\r\n		\'category\' => \'\',\r\n		\'aspect\' => \'\',\r\n		\'author\' => \'\',\r\n		\'dimensions\' => \'\',\r\n		\'extension\' => \'\',\r\n		\'directory\' => \'\',\r\n		\'match\' => \'\',\r\n		\'sublevel\' => \'0\',\r\n		\'escape\' => \'alt, title, caption\',\r\n		\'sort\' => \'\',\r\n		\'offset\' => \'0\',\r\n		\'limit\' => \'0\',\r\n		\'maxlimit\' => \'99999\',\r\n		\'grouplimit\' => \'99999\',\r\n		\'thumblimit\' => \'99999\',\r\n		\'thumboffset\' => \'0\',\r\n		\'thumb\' => \'0:1\',\r\n		\'thumbsize\' => \'\',\r\n		\'imgsize\' => \'\',\r\n		\'onchange\' => \'category\',\r\n		\'onchangewraptag\' => \'\',\r\n		\'onchangeclass\' => \'\',\r\n		\'objectform\' => \'\',\r\n		\'grouptag\' => \'\',\r\n		\'groupclass\' => \'\',\r\n		\'text\' => \'\',\r\n		\'format\' => $dateformat,\r\n		\'gmt\' => \'\',\r\n		\'lang\' => \'\',\r\n		\'collate\' => \'\',\r\n		\'combo\' => \'\',\r\n		\'paging\' => \'0\',\r\n		\'pageform\' => \'\',\r\n		\'pagepos\' => \'below\',\r\n		\'navwraptag\' => \'ul\',\r\n		\'navbreak\' => \'li\',\r\n		\'navclass\' => \'smd_gallery_nav\',\r\n		\'emptyclass\' => \'empty\',\r\n		\'galleryid\' => \'?4:6\',\r\n		\'wraptag\' => \'\',\r\n		\'html_id\' => \'\',\r\n		\'break\' => \'\',\r\n		\'class\' => \'\',\r\n		\'breakclass\' => \'\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Category relies on whether id has been specified or not.\r\n	// lAtts() won\'t let us handle this logic so it has to be done manually\r\n	$attemptMatch = (empty($category) && empty($id) && empty($name)) ? false : true;\r\n	$category = isset($category) ? doSlash($category) : (($id==\'\') ? $pretext[\'c\']: \'\');\r\n	$sort = ($sort) ? $sort : ( ($id) ? \'id\' : \'category\' );\r\n\r\n	// ** LOOKUP TABLES\r\n	// $fieldMap holds database column as key, and replacement identifier as value.\r\n	// Additional cols copied to $extraMap for iteration while creating replacements.\r\n	// Use array_search to find if value exists and return correct key for use in db calls/array refs.\r\n	$fieldMap = array();\r\n	$extraMap = array();\r\n	$coreCols = array(\"name\", \"id\", \"alt\", \"caption\", \"category\", \"author\", \"date\", \"ext\", \"w:width\", \"h:height\", \"thumb_w:thumb_width\", \"thumb_h:thumb_height\", \"thumbnail:hasthumb\");\r\n	$mlpFields = array(\"alt\", \"caption\"); // Manually create the MLP column names, since using getRows() later does not. Category title also has the same treatment\r\n	$thislang = (isset($l10n_language[\'long\'])) ? $l10n_language[\'long\'] : LANG;\r\n	$dflt_tbl = PFX.\"txp_image\";\r\n	$columns = do_list($columns);\r\n	foreach (array_merge($coreCols, $columns) as $colinfo) {\r\n		$colbit = do_list($colinfo, \":\");\r\n		$coltbl = do_list($colbit[0], \".\");\r\n\r\n		if ($colbit[0] == \'\') continue;\r\n\r\n		if (count($coltbl) > 1) {\r\n			$tbl = $coltbl[0];\r\n			$colname = $coltbl[1];\r\n		} else {\r\n			$tbl = $dflt_tbl;\r\n			$colname = $coltbl[0];\r\n		}\r\n		$colid = (count($colbit) > 1) ? $colbit[1] : $colname;\r\n		$fieldMap[(($tbl)?$tbl.\'.\':\'\').( (isset($l10n_language[\'long\']) && in_array($colname, $mlpFields)) ? _l10n_make_field_name($colname, $thislang). \' AS \'.$colname  : $colname )] = $colid;\r\n		if (!in_array($colinfo, $coreCols)) {\r\n			$extraMap[$colname] = $colid;\r\n		}\r\n	}\r\n	$fieldMap = array_unique($fieldMap);\r\n\r\n	// Handle forms and set a default tag if none specified\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$thing = (empty($thing)) ? \'<a href=\"{url}\" alt=\"{alt}\"><txp:image id=\"{id}\" /></a>\' : $thing;\r\n	$objectform = (empty($objectform)) ? \'\' : fetch_form($objectform);\r\n\r\n	// Counting\r\n	$countPosAllowed = array(\"above\", \"below\");\r\n	$countposns = array();\r\n	$counting = 0;\r\n	if ($countform) {\r\n		$counting = 1;\r\n		$countform = do_list($countform, $delim);\r\n		$countpos = str_replace(\'smd_\', \'\', $countpos); // For convenience\r\n		$countpos = do_list($countpos, $delim);\r\n		foreach ($countpos as $countitem) {\r\n			$countposns[] = (in_array($countitem, $countPosAllowed)) ? $countitem : $countPosAllowed[0];\r\n		}\r\n	}\r\n\r\n	// Paging init\r\n	$pagePosAllowed = array(\"below\", \"above\");\r\n	$pageout = array();\r\n	if ($pageform) {\r\n		$paging = 1;\r\n		$pageform = fetch_form($pageform);\r\n		$pagepos = str_replace(\'smd_\', \'\', $pagepos);\r\n		$pagepos = do_list($pagepos, $delim);\r\n		foreach ($pagepos as $pageitem) {\r\n			$pageout[] = (in_array($pageitem, $pagePosAllowed)) ? $pageitem : $pagePosAllowed[0];\r\n		}\r\n	}\r\n\r\n	// onchange verification\r\n	$onchange = do_list($onchange, $delim);\r\n	$onchange = ($onchange[0] == \"\") ? array() : $onchange;\r\n	$resetOpts = $resetForms = array();\r\n	foreach ($onchange as $resetOpt) {\r\n		$resetParts = do_list($resetOpt, $paramdelim);\r\n\r\n		if (count($resetParts) > 1) {\r\n			if ($resetParts[0] == \'smd_every\') {\r\n				$resetOpts[$resetParts[0].$paramdelim.$resetParts[1]] = \"\";\r\n			} else {\r\n				$resetForms[$resetParts[0]] = $resetParts[1];\r\n				$resetOpts[$resetParts[0]] = \"\";\r\n			}\r\n		} else {\r\n			$resetOpts[$resetParts[0]] = \"\";\r\n		}\r\n	}\r\n\r\n	// Escaping\r\n	$spc = ($strictfields) ? 0 : 1;\r\n	$escapes = do_list($escape, $delim);\r\n	foreach ($escapes as $idx => $val) {\r\n		if ($val == \'\') {\r\n			unset($escapes[$idx]);\r\n		}\r\n	}\r\n\r\n	// The gallery identifier is used for keeping track of next/prev thumbnail page links\r\n	$gallID = \'\';\r\n	if ($galleryid == \'\' || substr($galleryid,0,1) == \"?\") {\r\n		// Generate a (fairly likely) unique ID for this gallery instance\r\n		$gallTmp = md5($category.$id.$directory.$match.$sublevel.$sort.$limit.$maxlimit);\r\n		list($gallLen, $gallSkip) = explode(\":\", substr($galleryid,1));\r\n		$gallLen =  (empty($gallLen)) ? \"4\" : $gallLen;\r\n		$gallSkip =  (empty($gallSkip)) ? \"1\" : $gallSkip;\r\n		for ($idx = 0, $cnt = 0; $cnt < $gallLen; $cnt++, $idx = (($idx+$gallSkip) % strlen($gallTmp))) {\r\n			$gallID .= $gallTmp[$idx];\r\n		}\r\n	} else {\r\n		$gallID = $galleryid;\r\n	}\r\n	$gallID = \"smd_glry_\".$gallID; // Avoid (already incredibly unlikely) namespace clashes\r\n\r\n	// Set up the variables from the thumb option.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$thumbOpts = do_list($thumb, \":\");\r\n	if (count($thumbOpts) == 1) {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = \"t\";\r\n	} else {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = ($thumbOpts[1] == 1) ? \"t\" : \"\";\r\n	}\r\n\r\n	// The manner of obtaining files differs if using directory vs category/id\r\n	$rs = array();\r\n	if ($directory) {\r\n		$rs = smd_doDirectory($directory, 0, $sublevel, $match, $delim, $spc);\r\n		list($allImages, $numPages, $thumPage, $offset) = smd_getPage($rs, $gallID, $limit, $offset, $paging);\r\n		$dirlimit = ($limit==0) ? $maxlimit : $limit;\r\n		$postOrder = array();\r\n		$fixedOrder = 0;\r\n		$randsort = false;\r\n	} else {\r\n		// Process categories/IDs and make up the crux of the where clause.\r\n		// Clause order = IDs OR cats AND !cats AND !IDs\r\n		$clause[] = \'1=1\';\r\n		$clauseOR = array();\r\n\r\n		// Surround each entry in the ID list with quotes and make an SQL \"in\" list\r\n		$subinfo = ($sublevel) ? \"image:\".$sublevel : \'\';\r\n		list($idinc, $idexc) = smd_doList($id, true, \'\', true, $delim, $spc);\r\n		list($naminc, $namexc) = smd_doList($name, false, \'\', false, $delim, $spc);\r\n		list($catinc, $catexc) = smd_doList($category, false, $subinfo, true, $delim, $spc);\r\n		if ($idinc) {\r\n			$clauseOR[] = PFX.\'txp_image.id IN (\'.implode(\",\", $idinc).\')\';\r\n		}\r\n		if ($naminc) {\r\n			$namlist = array(\'regex\' => array(), \'exact\' => array());\r\n			foreach ($naminc as $theName) {\r\n				$namebits = do_list($theName, $paramdelim);\r\n				$namebits[1] = (isset($namebits[1]) && array_key_exists($namebits[1], $namlist)) ? $namebits[1] : \'exact\';\r\n				$namlist[$namebits[1]][] = $namebits[0];\r\n			}\r\n			if ($namlist[\'exact\']) {\r\n				$clauseOR[] = PFX.\"txp_image.name IN (\'\".implode(\"\',\'\", $namlist[\'exact\']).\"\')\";\r\n			}\r\n			foreach ($namlist[\'regex\'] as $re) {\r\n				$clauseOR[] = PFX.\"txp_image.name REGEXP \'\".preg_quote($re).\"\'\";\r\n			}\r\n		}\r\n		if ($catinc) {\r\n			$clauseOR[] = PFX.\'txp_image.category IN (\'.implode(\",\", $catinc).\')\';\r\n		}\r\n		if (!empty($clauseOR)) {\r\n			$clause[] = \' (\'.implode(\" OR \", $clauseOR).\') \';\r\n		}\r\n\r\n		$clause = implode(\" AND \", $clause);\r\n		$clause .= ($catexc) ? \' AND \'.PFX.\'txp_image.category NOT IN (\'.implode(\",\", $catexc).\')\' : \'\';\r\n		$clause .= ($idexc) ? \' AND \'.PFX.\'txp_image.id NOT IN (\'.implode(\",\", $idexc).\')\' : \'\';\r\n\r\n		if ($namexc) {\r\n			$namlist = array(\'regex\' => array(), \'exact\' => array());\r\n			foreach ($namexc as $theName) {\r\n				$namebits = do_list($theName, $paramdelim);\r\n				$namebits[1] = (isset($namebits[1]) && array_key_exists($namebits[1], $namlist)) ? $namebits[1] : \'exact\';\r\n				$namlist[$namebits[1]][] = $namebits[0];\r\n			}\r\n			if ($namlist[\'exact\']) {\r\n				$clause .= \" AND \".PFX.\"txp_image.name NOT IN (\'\".implode(\"\',\'\", $namlist[\'exact\']).\"\')\";\r\n			}\r\n			foreach ($namlist[\'regex\'] as $re) {\r\n				$clause .= \" AND \".PFX.\"txp_image.name NOT REGEXP \'\".preg_quote($re).\"\'\";\r\n			}\r\n		}\r\n\r\n		// If a category/id option was chosen and they matched nothing, use a bogus category that should not exist.\r\n		// This ensures that the plugin does the expected thing of matching nothing in the event it finds nothing.\r\n		if ($clause == \"1=1\" && $attemptMatch) {\r\n	  		$clause = PFX.\'txp_image.category = \"smd_\' .mt_rand(). \'\"\';\r\n		}\r\n\r\n		$clause = \'(\'.str_replace(\'SMD_EMPTY\', \'\', $clause).\')\';\r\n\r\n		if ($debug > 1) {\r\n			echo \"++ INCLUDED/EXCLUDED IDs, NAMEs AND CATs ++\";\r\n			if ($idinc) dmp($idinc);\r\n			if ($idexc) dmp($idexc);\r\n			if ($naminc) dmp($naminc);\r\n			if ($namexc) dmp($namexc);\r\n			if ($catinc) dmp($catinc);\r\n			if ($catexc) dmp($catexc);\r\n		}\r\n\r\n		// Filter by aspect ratio\r\n		if ($aspect) {\r\n			$aspects = do_list($aspect, $delim);\r\n\r\n			// Compare fullsize or thumbnail aspect ratios\r\n			$fldmod = ($fullmod) == \'t\' ? \'thumb_\' : \'\';\r\n			$wfld = PFX.\'txp_image.\'.$fldmod.\'w\';\r\n			$hfld = PFX.\'txp_image.\'.$fldmod.\'h\';\r\n\r\n			$clauseOR = array();\r\n			foreach ($aspects as $ratio) {\r\n				switch($ratio) {\r\n					case \'portrait\':\r\n						$clauseOR[] = $hfld. \' > \'. $wfld;\r\n						break;\r\n					case \'landscape\':\r\n						$clauseOR[] = $wfld. \' > \'. $hfld;\r\n						break;\r\n					case \'square\':\r\n						$clauseOR[] = $hfld. \' = \'. $wfld;\r\n						break;\r\n				}\r\n			}\r\n			if (!empty($clauseOR)) {\r\n				$clause .= \' AND \'.$wfld.\' > 0 AND \'.$hfld.\' > 0 AND (\'.implode(\" OR \", $clauseOR).\') \';\r\n			}\r\n		}\r\n\r\n		// Filter by author\r\n		if ($author) {\r\n			$authors = do_list($author, $delim);\r\n			$clause .= \" AND \" . PFX.\"txp_image.author IN (\'\".implode(\"\',\'\", $authors).\"\')\";\r\n		}\r\n\r\n		// Filter by extension\r\n		if ($extension) {\r\n			$extensions = do_list($extension, $delim);\r\n			foreach ($extensions as $idx => $ext) {\r\n				$extensions[$idx] = \".\".$ext;\r\n			}\r\n			$clause .= \" AND \" . PFX.\"txp_image.ext IN (\'\".implode(\"\',\'\", $extensions).\"\')\";\r\n		}\r\n\r\n		// Filter by dimensions\r\n		if ($dimensions) {\r\n			$dimAllow = array(\'=\', \'>\', \'<\', \'>=\', \'<=\');\r\n			$dims = do_list($dimensions, $delim);\r\n\r\n			// Compare fullsize or thumbnail aspect ratios\r\n			$fldmod = ($fullmod) == \'t\' ? \'thumb_\' : \'\';\r\n			$wfld = PFX.\'txp_image.\'.$fldmod.\'w\';\r\n			$hfld = PFX.\'txp_image.\'.$fldmod.\'h\';\r\n\r\n			$wdim = $dims[0];\r\n			$hdim = (isset($dims[1])) ? $dims[1] : \'\';\r\n\r\n			$wdimval = preg_replace(\'/[^0-9]/\', \'\', $wdim);\r\n			$hdimval = preg_replace(\'/[^0-9]/\', \'\', $hdim);\r\n			$wdimop = preg_replace(\'/[0-9]/\', \'\', $wdim);\r\n			$wdimop = in_array($wdimop, $dimAllow) ? $wdimop : \'=\';\r\n			$hdimop = preg_replace(\'/[0-9]/\', \'\', $hdim);\r\n			$hdimop = in_array($hdimop, $dimAllow) ? $hdimop : \'=\';\r\n			$clause .= (($wdim) ? \' AND \' . $wfld . $wdimop . $wdimval : \'\') . (($hdim) ? \' AND \' . $hfld . $hdimop . $hdimval : \'\');\r\n		}\r\n\r\n		// Convert the sort into a valid sorting hierarchy.\r\n		// Note that random is a special case and the following rules apply:\r\n		//  1) Anything appearing before random is passed to the database query for ordering\r\n		//  2) As soon as random is encountered, the rest of the sort string is ignored.\r\n		//     The resultset will be sorted randomly instead\r\n		//  3) If random appears first in the list, the data is retrieved randomly then the\r\n		//     remaining options sort the resultset\r\n		$clauseSort = array();\r\n		$postOrder = array();\r\n		$rule = \"sql\";\r\n		$postPrefix = \"SORT_\";\r\n		$fixedOrder = $randsort = false;\r\n		$sort = do_list($sort, $delim);\r\n		for ($idx = 0; $idx < count($sort); $idx++) {\r\n			$sort_dir = explode(\' \', $sort[$idx]);\r\n			$direction = $colRef = \'\';\r\n			if (count($sort_dir) <= 1) {\r\n				$sort_dir[1] = \"asc\";\r\n			}\r\n			if ($rule == \"sql\") {\r\n				$direction = ($sort_dir[1] == \"desc\") ? \' desc\' : \' asc\';\r\n			} else {\r\n				$direction = ($sort_dir[1] == \"desc\") ? $postPrefix.\'DESC\' : $postPrefix.\'ASC\';\r\n			}\r\n\r\n			switch ($sort_dir[0]) {\r\n				case \'random\':\r\n					$rule = \"post\";\r\n					if ($idx == 0) {\r\n						$clauseSort[] = \'rand()\';\r\n					} else {\r\n						$randsort = true;\r\n					}\r\n					break;\r\n				case \'fixed\':\r\n					// \'fixed\' only works on ids or categories\r\n					$fixedOrder = ($id == \'\' && $category == \'\') ? false : true;\r\n					break;\r\n				default:\r\n					$colRef = array_search($sort_dir[0], $fieldMap);\r\n					$colName = $fieldMap[$colRef];\r\n					break;\r\n			}\r\n			if ($rule == \"sql\") {\r\n				if ($colRef != \'\') {\r\n					$clauseSort[] = $colRef.$direction;\r\n				}\r\n			} else {\r\n				if (!$randsort) {\r\n					if ($colRef != \'\') {\r\n						$postOrder[] = array(\"col\" => $colName, \"sort\" => $direction);\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		if (count($clauseSort) > 0) {\r\n			$clause .= \' ORDER BY \' . implode(\",\",$clauseSort);\r\n		}\r\n		if ($debug > 0) {\r\n			echo \"++ WHERE CLAUSE ++\";\r\n			dmp($clause);\r\n		}\r\n		// The database fields required\r\n		$fields = implode(\",\", array_keys($fieldMap));\r\n		$cTitle = (isset($l10n_language[\'long\'])) ? _l10n_make_field_name(\'title\', $thislang) : \'title\';\r\n\r\n		$rs = getRows(\"\r\n			SELECT DISTINCT $fields\r\n			FROM \".PFX.\"txp_image\r\n			WHERE $clause\r\n			LIMIT 0, $maxlimit\r\n		\", $debug);\r\n		list($allImages, $numPages, $thumPage, $offset) = smd_getPage($rs, $gallID, $limit, $offset, $paging);\r\n	}\r\n\r\n\r\n	// Is post-ordering required? Do it\r\n	if ($randsort) {\r\n		shuffle($rs);\r\n	} else if (count($postOrder) > 0) {\r\n		// Translate the rows into columns that can be sorted\r\n		foreach($rs as $key => $row) {\r\n			foreach ($row as $identifier => $item) {\r\n				$varname = \"col_\".$identifier;\r\n				${$varname}[$key] = $item;\r\n			}\r\n		}\r\n\r\n		// Make up an array_multisort arg list and execute it.\r\n		// The necessary evil() is because we don\'t know how many cols there will be in the array\r\n		for ($idx = 0; $idx < count($postOrder); $idx++) {\r\n			$sortargs[] = \'$col_\'.$postOrder[$idx][\'col\'];\r\n			$sortargs[] = $postOrder[$idx][\'sort\'];\r\n		}\r\n		$sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $rs);\';\r\n		eval($sortit);\r\n	}\r\n\r\n	// Is fixed-order output desired? If so, re-order the $rs\r\n	if ($fixedOrder) {\r\n		$orderedRS = Array();\r\n		$ignoreList = Array();\r\n		$remainingRS = Array();\r\n\r\n		// Suck out the fixed items first. IDs then categories\r\n		for ($idx = 0; $idx < count($idinc); $idx++) {\r\n			foreach($rs as $row) {\r\n				if ($id && doQuote($row[\'id\']) == $idinc[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n					break;\r\n				}\r\n			}\r\n		}\r\n		for ($idx = 0; $idx < count($catinc); $idx++) {\r\n			foreach($rs as $row) {\r\n				if ($category && doQuote($row[\'category\']) == $catinc[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n				}\r\n			}\r\n		}\r\n		// Tack on the remaining rows\r\n		if ($rs) {\r\n			foreach($rs as $row) {\r\n				if (!in_array($row[\'id\'], $ignoreList)) {\r\n					$remainingRS[] = $row;\r\n				}\r\n			}\r\n		}\r\n		$rs = array_merge($orderedRS, $remainingRS);\r\n	}\r\n\r\n	// Grab the portion of the record set actually required to iterate over\r\n	if ($rs) {\r\n		$rs = array_slice($rs, $offset, (($directory) ? $dirlimit : (($limit==0) ? $maxlimit : $limit)));\r\n	}\r\n\r\n	// Prepare fixed thumbsizes\r\n	$thumbWidth = $thumbHeight = \'\';\r\n	$thumbWUnits = $thumbHUnits = \'\';\r\n	if ($thumbsize) {\r\n		$thumbSizes = do_list($thumbsize, $delim);\r\n		$units = preg_replace(\'/[0-9]/\', \'\', $thumbSizes[0]);\r\n		if (count($thumbSizes) == 1) {\r\n			$thumbWidth = $thumbHeight = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[0]);\r\n			$thumbWUnits = $thumbHUnits = ($units == \'\') ? $thumbWUnits : $units;\r\n		} else {\r\n			$thumbWidth = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[0]);\r\n			$thumbHeight = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[1]);\r\n			$thumbWUnits = ($units == \'\' && $thumbWidth != \'\') ? $thumbWUnits : $units;\r\n			$units = preg_replace(\'/[0-9]/\', \'\', $thumbSizes[1]);\r\n			$thumbHUnits = ($units == \'\' && $thumbHeight != \'\') ? $thumbHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Prepare fixed image sizes\r\n	$imageWidth = $imageHeight = \'\';\r\n	$imageWUnits = $imageHUnits = \'\';\r\n	if ($imgsize) {\r\n		$imageSizes = do_list($imgsize, $delim);\r\n		$units = preg_replace(\'/[0-9]/\', \'\', $imageSizes[0]);\r\n		if (count($imageSizes) == 1) {\r\n			$imageWidth = $imageHeight = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[0]);\r\n			$imageWUnits = $imageHUnits = ($units == \'\') ? $imageWUnits : $units;\r\n		} else {\r\n			$imageWidth = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[0]);\r\n			$imageHeight = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[1]);\r\n			$imageWUnits = ($units == \'\' && $imageWidth != \'\') ? $imageWUnits : $units;\r\n			$units = preg_replace(\'/[0-9]/\', \'\', $imageSizes[1]);\r\n			$imageHUnits = ($units == \'\' && $imageHeight != \'\') ? $imageHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Collation defers parsing the form for each returned item.\r\n	// Instead it outputs the data as a list at the end (items may be individually quoted or extracted if required)\r\n	//TODO : Add ALWAYS switch as a shortcut to specify that all matching items (regardless of #1, #2, #3 etc) are to be quoted\r\n	if ($collate) {\r\n		$dblQuote = $sglQuote = array(\'SMDNONE\');\r\n		$collateOpts = do_list($collate, $delim);\r\n		foreach ($collateOpts as $option) {\r\n			$indexes = do_list($option, $paramdelim);\r\n			$colType = array_shift($indexes);\r\n			// Supplying \'quote\' on its own will empty the relevant array, implying \"ALL\" fields are to be quoted\r\n			switch ($colType) {\r\n				case \"sglquote\":\r\n					$sglQuote = $indexes;\r\n					break;\r\n				case \"dblquote\":\r\n				case \"quote\":\r\n					$dblQuote = $indexes;\r\n					break;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Combos are user-defined combinations of replacement modifiers (e.g. idext: {id}{ext}, ...\r\n	$combos = array();\r\n	if ($combo) {\r\n		$combo = do_list($combo, $delim);\r\n		foreach ($combo as $newDef) {\r\n			list($defParts, $unwanted) = smd_doList($newDef, false, \'\', false, $paramdelim, $spc);\r\n			$defName = \"{\".array_shift($defParts).\"}\";\r\n			$combos[$defName] = $defParts[0];\r\n		}\r\n	}\r\n	if ($debug > 1) {\r\n		echo \"++ COMBOs ++\";\r\n		dmp($combos);\r\n	}\r\n	// Construct the output\r\n	$thumbout = array();\r\n	$groupout = array();\r\n	$navout = array();\r\n	$out = array();\r\n	$collations = array();\r\n	$chIdentifier = \"onchange\";\r\n\r\n	if ($debug > 2) {\r\n		echo \"++ RECORD SET ++\";\r\n		dmp($rs);\r\n	}\r\n	if ($debug > 3) {\r\n		echo \"++ AVAILABLE \'?\' VARS ++\";\r\n		if ($pretext) dmp($pretext);\r\n		if ($thisarticle) dmp($thisarticle);\r\n	}\r\n\r\n	if($rs) {\r\n		$idx = 0;\r\n		$ctr = 1;\r\n		$thumbIdx = 0;\r\n		$thumbCtr = 1;\r\n		$numImages = count($rs);\r\n		$repagements = array();\r\n\r\n		// Set up the paging features if required\r\n		if (($limit > 0) && $paging) {\r\n			$next = ($numPages > 1 && $thumPage != $numPages);\r\n			$prev = ($numPages > 1 && $thumPage > 1);\r\n			$prevPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage-1);\r\n			$nextPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage+1);\r\n			$thisPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage);\r\n			$firstPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, 1);\r\n			$lastPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $numPages);\r\n\r\n			// Labels can be specified in the replacement tag after a paramdelim.\r\n			// \'earlier\' and \'later\' don\'t have labels. Anything specified after paramdelim is the max number of pages to show; empty = all pages\r\n			$label = array(\r\n					\'{navfirst}\' => $smd_gallLang->gTxt(\'navfirst\'),\r\n					\'{navprev}\' => $smd_gallLang->gTxt(\'navprev\'),\r\n					\'{navearlier}\' => \'\',\r\n					\'{navthis}\' => $thumPage,\r\n					\'{navthisurl}\' => $thumPage,\r\n					\'{navlater}\' => \'\',\r\n					\'{navnext}\' => $smd_gallLang->gTxt(\'navnext\'),\r\n					\'{navlast}\' => $smd_gallLang->gTxt(\'navlast\'),\r\n				);\r\n\r\n			$labelRE = \'/\\{(nav[a-z]+)\'.$paramdelim.\'(.+?)\\}/\';\r\n			$numLabels = preg_match_all($labelRE, $pageform, $labels);\r\n\r\n			for ($labelCtr = 0; $labelCtr < $numLabels; $labelCtr++) {\r\n				$fieldname = \'{\'.$labels[1][$labelCtr].\'}\';\r\n				$label[$fieldname] = $labels[2][$labelCtr];\r\n				$pageform = str_replace($labels[0][$labelCtr], \'{\'.$labels[1][$labelCtr].\'}\', $pageform);\r\n			}\r\n\r\n			if ($debug > 1) {\r\n				echo \"++ PAGING LABELS ++\";\r\n				dmp($label);\r\n			}\r\n\r\n			// Generate \'earlier\' and \'later\' lists\r\n			$numEarlier = intval($label[\'{navearlier}\']);\r\n			$numLater = intval($label[\'{navlater}\']);\r\n			$numEarlier = ($numEarlier && ($thumPage-$numEarlier > 1)) ? $numEarlier : $thumPage-1;\r\n			$numLater = ($numLater && ($thumPage+$numLater < $numPages)) ? $numLater : $numPages-$thumPage;\r\n			$navEarlier = $navLater = array();\r\n			for ($cod = $thumPage-$numEarlier; $cod < $thumPage; $cod++) {\r\n				$navEarlier[] = \'<a href=\"\'.smd_addQSVar($pretext[\'request_uri\'], $gallID, $cod).\'\">\'.$cod.\'</a>\';\r\n			}\r\n			for ($cod = $thumPage+1; $cod <= $thumPage+$numLater; $cod++) {\r\n				$navLater[] = \'<a href=\"\'.smd_addQSVar($pretext[\'request_uri\'], $gallID, $cod).\'\">\'.$cod.\'</a>\';\r\n			}\r\n			$navEarlier = doWrap($navEarlier, \'\', $navbreak, \'\');\r\n			$navLater = doWrap($navLater, \'\', $navbreak, \'\');\r\n\r\n			// Make up the repagements array for pageform\r\n			$repagements = array(\r\n					\'{navfirst}\' => (($prev) ? doTag(\'<a href=\"\'.$firstPage.\'\">\'.$label[\'{navfirst}\'].\'</a>\', $navbreak) : doTag($label[\'{navfirst}\'], $navbreak, $emptyclass)),\r\n					\'{navprev}\' => (($prev) ? doTag(\'<a href=\"\'.$prevPage.\'\">\'.$label[\'{navprev}\'].\'</a>\', $navbreak) : doTag($label[\'{navprev}\'], $navbreak, $emptyclass)),\r\n					\'{navearlier}\' => $navEarlier,\r\n					\'{navthis}\' => doTag($label[\'{navthis}\'], $navbreak, \'current \'.$emptyclass),\r\n					\'{navthisurl}\' => doTag(\'<a href=\"\'.$thisPage.\'\">\'.$label[\'{navthisurl}\'].\'</a>\', $navbreak, \'current\'),\r\n					\'{navlater}\' => $navLater,\r\n					\'{navnext}\' => (($next) ? doTag(\'<a href=\"\'.$nextPage.\'\">\'.$label[\'{navnext}\'].\'</a>\', $navbreak) : doTag($label[\'{navnext}\'], $navbreak, $emptyclass)),\r\n					\'{navlast}\' => (($next) ? doTag(\'<a href=\"\'.$lastPage.\'\">\'.$label[\'{navlast}\'].\'</a>\', $navbreak) : doTag($label[\'{navlast}\'], $navbreak, $emptyclass)),\r\n					\'{navthispageurl}\' => $thisPage,\r\n					\'{navnextpageurl}\' => (($next) ? $nextPage : \'\'),\r\n					\'{navprevpageurl}\' => (($prev) ? $prevPage : \'\'),\r\n					\'{navfirstpageurl}\' => $firstPage,\r\n					\'{navlastpageurl}\' => $lastPage,\r\n					\'{totalimages}\' => $numImages,\r\n					\'{totalpages}\' => $numPages,\r\n					\'{maximages}\' => $allImages,\r\n				);\r\n\r\n				// Perform escaping here, just prior to output\r\n				if ($escapes) {\r\n					foreach ($repagements as $defName => $defVal) {\r\n						if (in_array(trim($defName, \"{}\"), $escapes)) {\r\n							$repagements[$defName] = htmlspecialchars($defVal, ENT_QUOTES);\r\n						}\r\n					}\r\n				}\r\n\r\n				// Perform the substitutions\r\n				$navout[] = parse(strtr($pageform, $repagements));\r\n		}\r\n\r\n		// For every image in the gallery...\r\n		foreach($rs as $key => $row) {\r\n			if ($directory) {\r\n				$urlPath = $row[\'path\'].\'/\';\r\n			} else {\r\n				$urlPath = hu.$img_dir.\'/\';\r\n				$row[\'category_title\'] = fetch_category_title($row[\'category\'], \'image\');\r\n			}\r\n			$theThumb = $urlPath . $row[\'id\'] . $thumbmod . $row[\'ext\'];\r\n			$fullSize = $urlPath . $row[\'id\'] . $fullmod .$row[\'ext\'];\r\n\r\n			// Do we need to reset the thumb counter this time?\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === 0) {\r\n					$iter = explode($paramdelim, $resetOpt);\r\n					$iter = (count($iter) > 1) ? $iter[1] : 0;\r\n					if ($thumbIdx % $iter == 0) {\r\n						$thumbIdx = 0;\r\n						$thumbCtr = 1;\r\n					}\r\n				} else if ($row[$resetOpt] != $resetVal) {\r\n					$thumbIdx = 0;\r\n					$thumbCtr = 1;\r\n					break;\r\n				}\r\n			}\r\n\r\n			// Override thumbsize / imgsize if required\r\n			if ($thumbsize) {\r\n				$theThumbWidth = empty($thumbWidth) ? $row[\'thumb_w\'] : $thumbWidth;\r\n				$theThumbHeight = empty($thumbHeight) ? $row[\'thumb_h\'] : $thumbHeight;\r\n				$theThumbWUnits = $thumbWUnits;\r\n				$theThumbHUnits = $thumbHUnits;\r\n			} else {\r\n				$theThumbWidth = $row[\'thumb_w\'];\r\n				$theThumbHeight = $row[\'thumb_h\'];\r\n				$theThumbWUnits = $theThumbHUnits = \'\';\r\n			}\r\n\r\n			if ($imgsize) {\r\n				$theImageWidth = empty($imageWidth) ? $row[\'w\'] : $imageWidth;\r\n				$theImageHeight = empty($imageHeight) ? $row[\'h\'] : $imageHeight;\r\n				$theImageWUnits = $imageWUnits;\r\n				$theImageHUnits = $imageHUnits;\r\n			} else {\r\n				$theImageWidth = $row[\'w\'];\r\n				$theImageHeight = $row[\'h\'];\r\n				$theImageWUnits = $theImageHUnits = \'\';\r\n			}\r\n\r\n			// Construct the replacement array\r\n			$replacements = array(\r\n					\'{id}\' => $row[\'id\'],\r\n					\'{name}\' => $row[\'name\'],\r\n					\'{ext}\' => $row[\'ext\'],\r\n					\'{category}\' => $row[\'category\'],\r\n					\'{category_title}\' => ((isset($row[\'category_title\']) && $row[\'category_title\'] !== false) ? $row[\'category_title\'] : \'\'),\r\n					\'{author}\' => $row[\'author\'],\r\n					\'{hasthumb}\' => (isset($row[\'thumbnail\']) ? $row[\'thumbnail\'] : 0),\r\n					\'{url}\' => $fullSize,\r\n					\'{thumburl}\' => $theThumb,\r\n					\'{imagepath}\' => $urlPath,\r\n					\'{imagedef}\' => basename($fullSize),\r\n					\'{thumbdef}\' => basename($theThumb),\r\n					\'{alt}\' => $row[\'alt\'],\r\n					\'{title}\' => $row[\'caption\'],\r\n					\'{caption}\' => $row[\'caption\'],\r\n					\'{thumbwidth}\' => $theThumbWidth,\r\n					\'{thumbheight}\' => $theThumbHeight,\r\n					\'{thumbwunits}\' => $theThumbWUnits,\r\n					\'{thumbhunits}\' => $theThumbHUnits,\r\n					\'{width}\' => $theImageWidth,\r\n					\'{height}\' => $theImageHeight,\r\n					\'{wunits}\' => $theImageWUnits,\r\n					\'{hunits}\' => $theImageHUnits,\r\n					\'{date}\' => (($format) ? safe_strftime($format, strtotime($row[\'date\']), $gmt, $lang) : $row[\'date\']),\r\n					\'{galleryid}\' => $gallID,\r\n					\'{index}\' => $idx,\r\n					\'{counter}\' => $ctr,\r\n					\'{thumbindex}\' => $thumbIdx,\r\n					\'{thumbcounter}\' => $thumbCtr,\r\n					\'{totalimages}\' => $numImages,\r\n					\'{maximages}\' => $allImages,\r\n				);\r\n\r\n			// Add any custom columns\r\n			foreach ($extraMap as $eCol => $eVal) {\r\n				$replacements[\'{\'.$eVal.\'}\'] = $row[$eCol];\r\n			}\r\n\r\n			// Append any changed items that are required\r\n			$endtag = false;\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === 0) {\r\n					$iter = explode($paramdelim, $resetOpt);\r\n					$iter = (count($iter) > 1) ? $iter[1] : 0;\r\n					if ($thumbCtr % $iter === 0 || $ctr == $numImages) {\r\n						$endtag = true;\r\n					}\r\n				} else {\r\n					$replacements[\'{\'.$chIdentifier.$paramdelim.$resetOpt.\'}\'] = ($thumbIdx == 0) ? ((isset($resetForms[$resetOpt])) ? strtr(parse_form($resetForms[$resetOpt]), $replacements) : doTag($row[$resetOpt], $onchangewraptag, $onchangeclass)) : \'\';\r\n					// Do a sneaky (yuk) lookahead. Would love to find a better way to do this\r\n					if (isset($rs[$key+1])) {\r\n						// category_title is not available on the next row (yet) so populate it if required... bleurgh!\r\n						if ($resetOpt == \'category_title\') {\r\n							$rs[$key+1][$resetOpt] = (isset($rs[$key+1][\'category\']) && !empty($rs[$key+1][\'category\'])) ? fetch_category_title($rs[$key+1][\'category\'], \'image\') : \'\';\r\n						}\r\n						if(isset($rs[$key+1][$resetOpt]) && ($rs[$key+1][$resetOpt] != $row[$resetOpt])) {\r\n							$endtag = true;\r\n						}\r\n					} else {\r\n						$endtag = true; // Last image\r\n					}\r\n				}\r\n			}\r\n\r\n			$replacements[\'{grouptagend}\'] = ($grouptag && $endtag) ? \'</\' . $grouptag . \'>\' : \'\';\r\n			$replacements[\'{grouptagstart}\'] = ($grouptag && $thumbIdx == 0) ? \'<\' . $grouptag . (($groupclass) ? \' class=\"\'.$groupclass.\'\"\' : \'\') . \'>\' : \'\';\r\n\r\n			// Append any combos\r\n			foreach ($combos as $defName => $newDef) {\r\n				if (!array_key_exists($defName, $replacements)) {\r\n					$replacements[$defName] = strtr($newDef, $replacements);\r\n				}\r\n			}\r\n\r\n			// Solos are items in the output form that require details from a specific row. Useful only in collation\r\n			// mode, they are added to the replacements array on an as-needed basis to save space/time\r\n			$soloRE = \'/\\{([a-z0-9]+)#\'.$ctr.\'\\}/\';\r\n			$numSolos = preg_match_all($soloRE, $thing, $solos);\r\n			for ($soloCtr = 0; $soloCtr < $numSolos; $soloCtr++) {\r\n				$fieldname = \'{\'.$solos[1][$soloCtr].\'#\'.$ctr.\'}\';\r\n				$grabfield = \'{\'.$solos[1][$soloCtr].\'}\';\r\n				$replacements[$fieldname] = $replacements[$grabfield];\r\n			}\r\n\r\n			// Perform escaping on the replacements.\r\n			// Note the repagements have already been escaped so when/if they are combined with the replacements,\r\n			// the escaping is not done twice\r\n			if ($escapes) {\r\n				foreach ($replacements as $defName => $defVal) {\r\n					if (in_array(trim($defName, \"{}\"), $escapes)) {\r\n						$replacements[$defName] = htmlspecialchars($defVal, ENT_QUOTES);\r\n					}\r\n				}\r\n			}\r\n\r\n			// Add the navigation items if they\'re defined\r\n			if (count($repagements) > 0) {\r\n				$replacements = array_merge($repagements, $replacements);\r\n			}\r\n\r\n			// Is a thumb/text object required this time?\r\n			$object = \'\';\r\n			if ($thumbIdx >= $thumboffset && $thumbIdx < $thumboffset+$thumblimit) {\r\n				$object = ($objectform) ? parse(strtr($objectform, $replacements)) : (($text) ? $text : \'<img src=\"\'.$theThumb.\'\"\' .(($row[\'alt\']) ? \' alt=\"\'.$row[\'alt\'].\'\"\' : \'\') .(($theThumbWidth) ? \' width=\"\'.$theThumbWidth.$theThumbWUnits.\'\"\' : \'\') .(($theThumbHeight) ? \' height=\"\'.$theThumbHeight.$theThumbHUnits.\'\"\' : \'\') .\' />\');\r\n			}\r\n			$replacements[\'{object}\'] = $object;\r\n\r\n			if ($debug > 0) {\r\n				echo \"++ REPLACEMENTS : IMAGE \".$ctr.\" ++\";\r\n				dmp($replacements);\r\n			}\r\n\r\n			// Check if countform is to be called\r\n			$countpre = $countpost = array();\r\n			if ($counting) {\r\n				foreach ($countform as $countlist) {\r\n					$posover = array();\r\n					$countlist = strtr($countlist, $replacements); // Replace any {} vars with their real values\r\n					$countlist = do_list($countlist, $paramdelim);\r\n					if ($dpos=array_search(\'smd_above\', $countlist)) {\r\n						$posover[] = \"above\";\r\n						unset($countlist[$dpos]);\r\n					}\r\n					if ($dpos=array_search(\'smd_below\', $countlist)) {\r\n						$posover[] = \"below\";\r\n						unset($countlist[$dpos]);\r\n					}\r\n					$countlist = array_merge($countlist); // re-index\r\n\r\n					$posover = (empty($posover)) ? $countposns : $posover;\r\n					$divby = false;\r\n					if ($countlist[0] == \'smd_every\') {\r\n						$divby = true;\r\n						array_shift($countlist);\r\n					}\r\n					$numforms = count($countlist);\r\n					$callform = fetch_form($countlist[$numforms-1]);\r\n					unset($countlist[$numforms-1]);\r\n					$orlogic = $andlogic = array();\r\n\r\n					// Check if any value is a range and expand it if so\r\n					$fullcountlist = array();\r\n					foreach ($countlist as $custcount) {\r\n						if (preg_match(\'/^(\\d+)\\-(\\d+)$/\', $custcount)) {\r\n							list($lo, $hi) = explode(\"-\", $custcount, 2);\r\n							$fullcountlist += range($lo, $hi);\r\n						} else {\r\n							$fullcountlist[] = $custcount;\r\n						}\r\n					}\r\n\r\n					$countlist = $fullcountlist;\r\n					foreach ($countlist as $custcount) {\r\n						$showval = (strpos($custcount, \"!\") === 0) ? false : true;// Negative logic required\r\n						$custcount = str_replace(\'!\', \'\', $custcount);\r\n						if ($showval) {\r\n							$orlogic[] = empty($custcount) || (($divby) ? (($ctr % $custcount === 0) ? true : false) : (($custcount == $ctr) ? true : false));\r\n						} else {\r\n							$andlogic[] = ($custcount == $ctr) ? false : true;\r\n						}\r\n					}\r\n					if (in_array(\'1\', $orlogic) && !in_array(\'0\', $andlogic)) {\r\n						if (in_array(\"above\", $posover)) {\r\n							$countpre[] = parse(strtr($callform, $replacements));\r\n						}\r\n						if (in_array(\"below\", $posover)) {\r\n							$countpost[] = parse(strtr($callform, $replacements));\r\n						}\r\n					}\r\n            }\r\n			}\r\n\r\n			if ($debug > 0 && ($countpre || $countpost)) {\r\n				echo \"++ COUNT FORM TRIGGERED ++\";\r\n				if ($countpre) {\r\n					dmp($countpre);\r\n				}\r\n				if ($countpost) {\r\n					dmp($countpost);\r\n				}\r\n			}\r\n\r\n			// In collate mode the form is only parsed at the end: build a collosal multi-dimension array of all items here\r\n			if ($collate) {\r\n				foreach ($replacements as $defName => $defVal) {\r\n					$collations[$defName][] = $defVal;\r\n				}\r\n			} else {\r\n				if ($counting) {\r\n					$thumbout = array_merge($thumbout, $countpre);\r\n				}\r\n\r\n				if ($thumbIdx < $grouplimit) {\r\n					$thumbout[] = parse(strtr($thing, $replacements));\r\n				}\r\n				if ($counting) {\r\n					$thumbout = array_merge($thumbout, $countpost);\r\n				}\r\n			}\r\n\r\n			// Keep track of the current values so we know if they change next time\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === false) {\r\n					$resetOpts[$resetOpt] = $row[$resetOpt];\r\n				} else {\r\n					$resetOpts[$resetOpt] = $thumbCtr;\r\n				}\r\n			}\r\n			$thumbIdx++;\r\n			$thumbCtr++;\r\n			$idx++;\r\n			$ctr++;\r\n		}\r\n		if ($collate) {\r\n			foreach ($collations as $item => $list) {\r\n				// Quote the lists if required\r\n				$list = (empty($dblQuote) || in_array($item, $dblQuote)) ? doArray($list, \'smd_doDblQuote\') : $list;\r\n				$list = (empty($sglQuote) || in_array($item, $sglQuote)) ? doArray($list, \'doQuote\') : $list;\r\n				$collations[$item] = implode($collatedelim, $list);\r\n			}\r\n			if ($debug > 0) {\r\n				echo \"++ COLLATIONS ++\";\r\n				dmp($collations);\r\n			}\r\n			$thumbout[] = parse(strtr($thing, $collations));\r\n		}\r\n\r\n		// Tack on the paging from earlier if it\'s to go afterwards\r\n		if (($limit > 0) && $paging) {\r\n			if ($debug > 0) {\r\n				echo \"++ PAGING REPLACEMENTS ++\";\r\n				dmp($repagements);\r\n			}\r\n		}\r\n	}\r\n\r\n	// Add the paging to the output if it\'s to go above the thumbs\r\n	if (($limit > 0) && $paging) {\r\n		if (in_array(\"above\", $pageout)) {\r\n			$out[] = doWrap($navout, $navwraptag, \'\', $navclass);\r\n		}\r\n	}\r\n\r\n	$out[] = doWrap($thumbout, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n\r\n	if (($limit > 0) && $paging) {\r\n		if (in_array(\"below\", $pageout)) {\r\n			$out[] = doWrap($navout, $navwraptag, \'\', $navclass);\r\n		}\r\n	}\r\n\r\n	if ($debug > 2) {\r\n		echo \"++ FINAL OUTPUT ++\";\r\n		dmp($out);\r\n	}\r\n\r\n	return join(\" \", $out);\r\n}\r\n\r\n// Work out which page this gallery is on and return the relevant info\r\nfunction smd_getPage($rs, $gallID, $limit, $offset, $paging) {\r\n	$numThums = count($rs) - $offset;\r\n	if (($limit > 0) && $paging) {\r\n		$numPages = ($numThums > 0) ? ceil($numThums/$limit) : 1;\r\n		$thumPage = (!gps($gallID)) ? 1 : gps($gallID);\r\n		$offset = (($thumPage - 1) * $limit) + $offset;\r\n	} else {\r\n		// No paging required\r\n		$numPages = 1;\r\n		$thumPage = 1;\r\n	}\r\n	return array($numThums, $numPages, $thumPage, $offset);\r\n}\r\n\r\n// Read the given delimited directories and get all files that match.\r\n// Go down as many subdir levels as given in the $sub parameter\r\nfunction smd_doDirectory($lst, $lvl=0, $sub=0, $match=\"\", $dlm=\",\", $spc=1, $ctr=0) {\r\n	list($dirinc, $direxc) = smd_doList($lst, false, $sub, false, $dlm, $spc);\r\n	list($matchinc, $matchexc) = smd_doList($match, false, \'\', false, $dlm, $spc);\r\n	$matchinc = \".*\" .(($matchinc) ? \"(\" .implode(\"|\",$matchinc).\")+.*\" : \"\");\r\n	$matchexc = ($matchexc) ? \".*(\" .implode(\"|\",$matchexc).\")+.*\" : \"smd_glry_\".mt_rand();\r\n\r\n	$sub = (empty($sub)) ? 0 : $sub;\r\n	$sub = (strtolower($sub)==\"all\") ? 99999 : $sub;\r\n	$fileList = array();\r\n	foreach ($dirinc as $dir) {\r\n		if ($handle = @opendir($dir)) {\r\n			while (($file = @readdir($handle)) !== false) {\r\n				$fullpath = $dir.DS.$file;\r\n				if (preg_match(\"/\".$matchinc.\"/\", $fullpath) && !preg_match(\"/\".$matchexc.\"/\", $fullpath)) {\r\n					if (is_dir($fullpath)) {\r\n						if (($file == \".\") || ($file== \"..\") || (in_array($file, $direxc)) || ($lvl >= $sub)) {\r\n							continue;\r\n						} else {\r\n							// Recurse the subdir. Note that the \'!\' is temporarily added back to each direxc item\r\n							$tmp = $direxc; // Don\'t overwrite the original array\r\n							array_unshift($tmp, $fullpath);\r\n							$subFiles = smd_doDirectory(implode($dlm.\"!\", $tmp), $lvl+1, $sub, $match, $dlm, $spc, $ctr);\r\n							$fileList = array_merge($fileList, $subFiles);\r\n							$ctr += count($subFiles);\r\n							continue;\r\n						}\r\n					} else {\r\n						$filestat = stat($fullpath);\r\n						$imgsize = getimagesize($fullpath);\r\n						$fileList[$ctr][\'path\'] = $dir;\r\n						$fileList[$ctr][\'name\'] = basename($fullpath);\r\n						$fileList[$ctr][\'id\'] = preg_replace(\'/\\..+$/\', \'\', $file);\r\n						$fileList[$ctr][\'alt\'] = \'\';\r\n						$fileList[$ctr][\'caption\'] = \'\';\r\n						$parts = explode(DS, $dir);\r\n						$fileList[$ctr][\'category\'] = (count($parts) > 1) ? end($parts) : \'\';\r\n						$fileList[$ctr][\'author\'] = strval($filestat[\'uid\']);\r\n						$fileList[$ctr][\'date\'] = strftime(\"%Y-%m-%d %H:%M:%S\", $filestat[\'ctime\']);\r\n						$parts = explode(\".\", $file);\r\n						$fileList[$ctr][\'ext\'] = (count($parts) > 1) ? \".\".end($parts) : \'\';\r\n						$fileList[$ctr][\'w\'] = (is_array($imgsize)) ? strval($imgsize[0]) : \'\';\r\n						$fileList[$ctr][\'h\'] = (is_array($imgsize)) ? strval($imgsize[1]) : \'\';\r\n					}\r\n					$ctr++;\r\n				}\r\n			}\r\n		}\r\n		if ($handle) {\r\n			@closedir($handle);\r\n		}\r\n	}\r\n	return $fileList;\r\n}', 'require_plugin(\'smd_lib\');\r\n\r\n// MLP support\r\nglobal $smd_gallLang;\r\n$smd_gallStr = array(\r\n	\'navfirst\' => \'First\',\r\n	\'navlast\' => \'Last\',\r\n	\'navprev\' => \'&laquo;\',\r\n	\'navnext\' => \'&raquo;\',\r\n);\r\n$smd_gallLang = new smd_MLP(\'smd_gallery\', \'smd_gall\', $smd_gallStr);\r\n\r\nfunction smd_gallery($atts, $thing=\'\') {\r\n	global $pretext, $thisarticle, $img_dir, $dateformat, $smd_gallLang, $l10n_language;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'columns\' => \'\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'collatedelim\' => \',\',\r\n		\'strictfields\' => \'0\',\r\n		\'form\' => \'\',\r\n		\'countform\' => \'\',\r\n		\'countpos\' => \'above\',\r\n		\'id\' => \'\',\r\n		\'name\' => \'\',\r\n		\'category\' => \'\',\r\n		\'aspect\' => \'\',\r\n		\'author\' => \'\',\r\n		\'dimensions\' => \'\',\r\n		\'extension\' => \'\',\r\n		\'directory\' => \'\',\r\n		\'match\' => \'\',\r\n		\'sublevel\' => \'0\',\r\n		\'escape\' => \'alt, title, caption\',\r\n		\'sort\' => \'\',\r\n		\'offset\' => \'0\',\r\n		\'limit\' => \'0\',\r\n		\'maxlimit\' => \'99999\',\r\n		\'grouplimit\' => \'99999\',\r\n		\'thumblimit\' => \'99999\',\r\n		\'thumboffset\' => \'0\',\r\n		\'thumb\' => \'0:1\',\r\n		\'thumbsize\' => \'\',\r\n		\'imgsize\' => \'\',\r\n		\'onchange\' => \'category\',\r\n		\'onchangewraptag\' => \'\',\r\n		\'onchangeclass\' => \'\',\r\n		\'objectform\' => \'\',\r\n		\'grouptag\' => \'\',\r\n		\'groupclass\' => \'\',\r\n		\'text\' => \'\',\r\n		\'format\' => $dateformat,\r\n		\'gmt\' => \'\',\r\n		\'lang\' => \'\',\r\n		\'collate\' => \'\',\r\n		\'combo\' => \'\',\r\n		\'paging\' => \'0\',\r\n		\'pageform\' => \'\',\r\n		\'pagepos\' => \'below\',\r\n		\'navwraptag\' => \'ul\',\r\n		\'navbreak\' => \'li\',\r\n		\'navclass\' => \'smd_gallery_nav\',\r\n		\'emptyclass\' => \'empty\',\r\n		\'galleryid\' => \'?4:6\',\r\n		\'wraptag\' => \'\',\r\n		\'html_id\' => \'\',\r\n		\'break\' => \'\',\r\n		\'class\' => \'\',\r\n		\'breakclass\' => \'\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Category relies on whether id has been specified or not.\r\n	// lAtts() won\'t let us handle this logic so it has to be done manually\r\n	$attemptMatch = (empty($category) && empty($id) && empty($name)) ? false : true;\r\n	$category = isset($category) ? doSlash($category) : (($id==\'\') ? $pretext[\'c\']: \'\');\r\n	$sort = ($sort) ? $sort : ( ($id) ? \'id\' : \'category\' );\r\n\r\n	// ** LOOKUP TABLES\r\n	// $fieldMap holds database column as key, and replacement identifier as value.\r\n	// Additional cols copied to $extraMap for iteration while creating replacements.\r\n	// Use array_search to find if value exists and return correct key for use in db calls/array refs.\r\n	$fieldMap = array();\r\n	$extraMap = array();\r\n	$coreCols = array(\"name\", \"id\", \"alt\", \"caption\", \"category\", \"author\", \"date\", \"ext\", \"w:width\", \"h:height\", \"thumb_w:thumb_width\", \"thumb_h:thumb_height\", \"thumbnail:hasthumb\");\r\n	$mlpFields = array(\"alt\", \"caption\"); // Manually create the MLP column names, since using getRows() later does not. Category title also has the same treatment\r\n	$thislang = (isset($l10n_language[\'long\'])) ? $l10n_language[\'long\'] : LANG;\r\n	$dflt_tbl = PFX.\"txp_image\";\r\n	$columns = do_list($columns);\r\n	foreach (array_merge($coreCols, $columns) as $colinfo) {\r\n		$colbit = do_list($colinfo, \":\");\r\n		$coltbl = do_list($colbit[0], \".\");\r\n\r\n		if ($colbit[0] == \'\') continue;\r\n\r\n		if (count($coltbl) > 1) {\r\n			$tbl = $coltbl[0];\r\n			$colname = $coltbl[1];\r\n		} else {\r\n			$tbl = $dflt_tbl;\r\n			$colname = $coltbl[0];\r\n		}\r\n		$colid = (count($colbit) > 1) ? $colbit[1] : $colname;\r\n		$fieldMap[(($tbl)?$tbl.\'.\':\'\').( (isset($l10n_language[\'long\']) && in_array($colname, $mlpFields)) ? _l10n_make_field_name($colname, $thislang). \' AS \'.$colname  : $colname )] = $colid;\r\n		if (!in_array($colinfo, $coreCols)) {\r\n			$extraMap[$colname] = $colid;\r\n		}\r\n	}\r\n	$fieldMap = array_unique($fieldMap);\r\n\r\n	// Handle forms and set a default tag if none specified\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$thing = (empty($thing)) ? \'<a href=\"{url}\" alt=\"{alt}\"><txp:image id=\"{id}\" /></a>\' : $thing;\r\n	$objectform = (empty($objectform)) ? \'\' : fetch_form($objectform);\r\n\r\n	// Counting\r\n	$countPosAllowed = array(\"above\", \"below\");\r\n	$countposns = array();\r\n	$counting = 0;\r\n	if ($countform) {\r\n		$counting = 1;\r\n		$countform = do_list($countform, $delim);\r\n		$countpos = str_replace(\'smd_\', \'\', $countpos); // For convenience\r\n		$countpos = do_list($countpos, $delim);\r\n		foreach ($countpos as $countitem) {\r\n			$countposns[] = (in_array($countitem, $countPosAllowed)) ? $countitem : $countPosAllowed[0];\r\n		}\r\n	}\r\n\r\n	// Paging init\r\n	$pagePosAllowed = array(\"below\", \"above\");\r\n	$pageout = array();\r\n	if ($pageform) {\r\n		$paging = 1;\r\n		$pageform = fetch_form($pageform);\r\n		$pagepos = str_replace(\'smd_\', \'\', $pagepos);\r\n		$pagepos = do_list($pagepos, $delim);\r\n		foreach ($pagepos as $pageitem) {\r\n			$pageout[] = (in_array($pageitem, $pagePosAllowed)) ? $pageitem : $pagePosAllowed[0];\r\n		}\r\n	}\r\n\r\n	// onchange verification\r\n	$onchange = do_list($onchange, $delim);\r\n	$onchange = ($onchange[0] == \"\") ? array() : $onchange;\r\n	$resetOpts = $resetForms = array();\r\n	foreach ($onchange as $resetOpt) {\r\n		$resetParts = do_list($resetOpt, $paramdelim);\r\n\r\n		if (count($resetParts) > 1) {\r\n			if ($resetParts[0] == \'smd_every\') {\r\n				$resetOpts[$resetParts[0].$paramdelim.$resetParts[1]] = \"\";\r\n			} else {\r\n				$resetForms[$resetParts[0]] = $resetParts[1];\r\n				$resetOpts[$resetParts[0]] = \"\";\r\n			}\r\n		} else {\r\n			$resetOpts[$resetParts[0]] = \"\";\r\n		}\r\n	}\r\n\r\n	// Escaping\r\n	$spc = ($strictfields) ? 0 : 1;\r\n	$escapes = do_list($escape, $delim);\r\n	foreach ($escapes as $idx => $val) {\r\n		if ($val == \'\') {\r\n			unset($escapes[$idx]);\r\n		}\r\n	}\r\n\r\n	// The gallery identifier is used for keeping track of next/prev thumbnail page links\r\n	$gallID = \'\';\r\n	if ($galleryid == \'\' || substr($galleryid,0,1) == \"?\") {\r\n		// Generate a (fairly likely) unique ID for this gallery instance\r\n		$gallTmp = md5($category.$id.$directory.$match.$sublevel.$sort.$limit.$maxlimit);\r\n		list($gallLen, $gallSkip) = explode(\":\", substr($galleryid,1));\r\n		$gallLen =  (empty($gallLen)) ? \"4\" : $gallLen;\r\n		$gallSkip =  (empty($gallSkip)) ? \"1\" : $gallSkip;\r\n		for ($idx = 0, $cnt = 0; $cnt < $gallLen; $cnt++, $idx = (($idx+$gallSkip) % strlen($gallTmp))) {\r\n			$gallID .= $gallTmp[$idx];\r\n		}\r\n	} else {\r\n		$gallID = $galleryid;\r\n	}\r\n	$gallID = \"smd_glry_\".$gallID; // Avoid (already incredibly unlikely) namespace clashes\r\n\r\n	// Set up the variables from the thumb option.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$thumbOpts = do_list($thumb, \":\");\r\n	if (count($thumbOpts) == 1) {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = \"t\";\r\n	} else {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = ($thumbOpts[1] == 1) ? \"t\" : \"\";\r\n	}\r\n\r\n	// The manner of obtaining files differs if using directory vs category/id\r\n	$rs = array();\r\n	if ($directory) {\r\n		$rs = smd_doDirectory($directory, 0, $sublevel, $match, $delim, $spc);\r\n		list($allImages, $numPages, $thumPage, $offset) = smd_getPage($rs, $gallID, $limit, $offset, $paging);\r\n		$dirlimit = ($limit==0) ? $maxlimit : $limit;\r\n		$postOrder = array();\r\n		$fixedOrder = 0;\r\n		$randsort = false;\r\n	} else {\r\n		// Process categories/IDs and make up the crux of the where clause.\r\n		// Clause order = IDs OR cats AND !cats AND !IDs\r\n		$clause[] = \'1=1\';\r\n		$clauseOR = array();\r\n\r\n		// Surround each entry in the ID list with quotes and make an SQL \"in\" list\r\n		$subinfo = ($sublevel) ? \"image:\".$sublevel : \'\';\r\n		list($idinc, $idexc) = smd_doList($id, true, \'\', true, $delim, $spc);\r\n		list($naminc, $namexc) = smd_doList($name, false, \'\', false, $delim, $spc);\r\n		list($catinc, $catexc) = smd_doList($category, false, $subinfo, true, $delim, $spc);\r\n		if ($idinc) {\r\n			$clauseOR[] = PFX.\'txp_image.id IN (\'.implode(\",\", $idinc).\')\';\r\n		}\r\n		if ($naminc) {\r\n			$namlist = array(\'regex\' => array(), \'exact\' => array());\r\n			foreach ($naminc as $theName) {\r\n				$namebits = do_list($theName, $paramdelim);\r\n				$namebits[1] = (isset($namebits[1]) && array_key_exists($namebits[1], $namlist)) ? $namebits[1] : \'exact\';\r\n				$namlist[$namebits[1]][] = $namebits[0];\r\n			}\r\n			if ($namlist[\'exact\']) {\r\n				$clauseOR[] = PFX.\"txp_image.name IN (\'\".implode(\"\',\'\", $namlist[\'exact\']).\"\')\";\r\n			}\r\n			foreach ($namlist[\'regex\'] as $re) {\r\n				$clauseOR[] = PFX.\"txp_image.name REGEXP \'\".preg_quote($re).\"\'\";\r\n			}\r\n		}\r\n		if ($catinc) {\r\n			$clauseOR[] = PFX.\'txp_image.category IN (\'.implode(\",\", $catinc).\')\';\r\n		}\r\n		if (!empty($clauseOR)) {\r\n			$clause[] = \' (\'.implode(\" OR \", $clauseOR).\') \';\r\n		}\r\n\r\n		$clause = implode(\" AND \", $clause);\r\n		$clause .= ($catexc) ? \' AND \'.PFX.\'txp_image.category NOT IN (\'.implode(\",\", $catexc).\')\' : \'\';\r\n		$clause .= ($idexc) ? \' AND \'.PFX.\'txp_image.id NOT IN (\'.implode(\",\", $idexc).\')\' : \'\';\r\n\r\n		if ($namexc) {\r\n			$namlist = array(\'regex\' => array(), \'exact\' => array());\r\n			foreach ($namexc as $theName) {\r\n				$namebits = do_list($theName, $paramdelim);\r\n				$namebits[1] = (isset($namebits[1]) && array_key_exists($namebits[1], $namlist)) ? $namebits[1] : \'exact\';\r\n				$namlist[$namebits[1]][] = $namebits[0];\r\n			}\r\n			if ($namlist[\'exact\']) {\r\n				$clause .= \" AND \".PFX.\"txp_image.name NOT IN (\'\".implode(\"\',\'\", $namlist[\'exact\']).\"\')\";\r\n			}\r\n			foreach ($namlist[\'regex\'] as $re) {\r\n				$clause .= \" AND \".PFX.\"txp_image.name NOT REGEXP \'\".preg_quote($re).\"\'\";\r\n			}\r\n		}\r\n\r\n		// If a category/id option was chosen and they matched nothing, use a bogus category that should not exist.\r\n		// This ensures that the plugin does the expected thing of matching nothing in the event it finds nothing.\r\n		if ($clause == \"1=1\" && $attemptMatch) {\r\n	  		$clause = PFX.\'txp_image.category = \"smd_\' .mt_rand(). \'\"\';\r\n		}\r\n\r\n		$clause = \'(\'.str_replace(\'SMD_EMPTY\', \'\', $clause).\')\';\r\n\r\n		if ($debug > 1) {\r\n			echo \"++ INCLUDED/EXCLUDED IDs, NAMEs AND CATs ++\";\r\n			if ($idinc) dmp($idinc);\r\n			if ($idexc) dmp($idexc);\r\n			if ($naminc) dmp($naminc);\r\n			if ($namexc) dmp($namexc);\r\n			if ($catinc) dmp($catinc);\r\n			if ($catexc) dmp($catexc);\r\n		}\r\n\r\n		// Filter by aspect ratio\r\n		if ($aspect) {\r\n			$aspects = do_list($aspect, $delim);\r\n\r\n			// Compare fullsize or thumbnail aspect ratios\r\n			$fldmod = ($fullmod) == \'t\' ? \'thumb_\' : \'\';\r\n			$wfld = PFX.\'txp_image.\'.$fldmod.\'w\';\r\n			$hfld = PFX.\'txp_image.\'.$fldmod.\'h\';\r\n\r\n			$clauseOR = array();\r\n			foreach ($aspects as $ratio) {\r\n				switch($ratio) {\r\n					case \'portrait\':\r\n						$clauseOR[] = $hfld. \' > \'. $wfld;\r\n						break;\r\n					case \'landscape\':\r\n						$clauseOR[] = $wfld. \' > \'. $hfld;\r\n						break;\r\n					case \'square\':\r\n						$clauseOR[] = $hfld. \' = \'. $wfld;\r\n						break;\r\n				}\r\n			}\r\n			if (!empty($clauseOR)) {\r\n				$clause .= \' AND \'.$wfld.\' > 0 AND \'.$hfld.\' > 0 AND (\'.implode(\" OR \", $clauseOR).\') \';\r\n			}\r\n		}\r\n\r\n		// Filter by author\r\n		if ($author) {\r\n			$authors = do_list($author, $delim);\r\n			$clause .= \" AND \" . PFX.\"txp_image.author IN (\'\".implode(\"\',\'\", $authors).\"\')\";\r\n		}\r\n\r\n		// Filter by extension\r\n		if ($extension) {\r\n			$extensions = do_list($extension, $delim);\r\n			foreach ($extensions as $idx => $ext) {\r\n				$extensions[$idx] = \".\".$ext;\r\n			}\r\n			$clause .= \" AND \" . PFX.\"txp_image.ext IN (\'\".implode(\"\',\'\", $extensions).\"\')\";\r\n		}\r\n\r\n		// Filter by dimensions\r\n		if ($dimensions) {\r\n			$dimAllow = array(\'=\', \'>\', \'<\', \'>=\', \'<=\');\r\n			$dims = do_list($dimensions, $delim);\r\n\r\n			// Compare fullsize or thumbnail aspect ratios\r\n			$fldmod = ($fullmod) == \'t\' ? \'thumb_\' : \'\';\r\n			$wfld = PFX.\'txp_image.\'.$fldmod.\'w\';\r\n			$hfld = PFX.\'txp_image.\'.$fldmod.\'h\';\r\n\r\n			$wdim = $dims[0];\r\n			$hdim = (isset($dims[1])) ? $dims[1] : \'\';\r\n\r\n			$wdimval = preg_replace(\'/[^0-9]/\', \'\', $wdim);\r\n			$hdimval = preg_replace(\'/[^0-9]/\', \'\', $hdim);\r\n			$wdimop = preg_replace(\'/[0-9]/\', \'\', $wdim);\r\n			$wdimop = in_array($wdimop, $dimAllow) ? $wdimop : \'=\';\r\n			$hdimop = preg_replace(\'/[0-9]/\', \'\', $hdim);\r\n			$hdimop = in_array($hdimop, $dimAllow) ? $hdimop : \'=\';\r\n			$clause .= (($wdim) ? \' AND \' . $wfld . $wdimop . $wdimval : \'\') . (($hdim) ? \' AND \' . $hfld . $hdimop . $hdimval : \'\');\r\n		}\r\n\r\n		// Convert the sort into a valid sorting hierarchy.\r\n		// Note that random is a special case and the following rules apply:\r\n		//  1) Anything appearing before random is passed to the database query for ordering\r\n		//  2) As soon as random is encountered, the rest of the sort string is ignored.\r\n		//     The resultset will be sorted randomly instead\r\n		//  3) If random appears first in the list, the data is retrieved randomly then the\r\n		//     remaining options sort the resultset\r\n		$clauseSort = array();\r\n		$postOrder = array();\r\n		$rule = \"sql\";\r\n		$postPrefix = \"SORT_\";\r\n		$fixedOrder = $randsort = false;\r\n		$sort = do_list($sort, $delim);\r\n		for ($idx = 0; $idx < count($sort); $idx++) {\r\n			$sort_dir = explode(\' \', $sort[$idx]);\r\n			$direction = $colRef = \'\';\r\n			if (count($sort_dir) <= 1) {\r\n				$sort_dir[1] = \"asc\";\r\n			}\r\n			if ($rule == \"sql\") {\r\n				$direction = ($sort_dir[1] == \"desc\") ? \' desc\' : \' asc\';\r\n			} else {\r\n				$direction = ($sort_dir[1] == \"desc\") ? $postPrefix.\'DESC\' : $postPrefix.\'ASC\';\r\n			}\r\n\r\n			switch ($sort_dir[0]) {\r\n				case \'random\':\r\n					$rule = \"post\";\r\n					if ($idx == 0) {\r\n						$clauseSort[] = \'rand()\';\r\n					} else {\r\n						$randsort = true;\r\n					}\r\n					break;\r\n				case \'fixed\':\r\n					// \'fixed\' only works on ids or categories\r\n					$fixedOrder = ($id == \'\' && $category == \'\') ? false : true;\r\n					break;\r\n				default:\r\n					$colRef = array_search($sort_dir[0], $fieldMap);\r\n					$colName = $fieldMap[$colRef];\r\n					break;\r\n			}\r\n			if ($rule == \"sql\") {\r\n				if ($colRef != \'\') {\r\n					$clauseSort[] = $colRef.$direction;\r\n				}\r\n			} else {\r\n				if (!$randsort) {\r\n					if ($colRef != \'\') {\r\n						$postOrder[] = array(\"col\" => $colName, \"sort\" => $direction);\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		if (count($clauseSort) > 0) {\r\n			$clause .= \' ORDER BY \' . implode(\",\",$clauseSort);\r\n		}\r\n		if ($debug > 0) {\r\n			echo \"++ WHERE CLAUSE ++\";\r\n			dmp($clause);\r\n		}\r\n		// The database fields required\r\n		$fields = implode(\",\", array_keys($fieldMap));\r\n		$cTitle = (isset($l10n_language[\'long\'])) ? _l10n_make_field_name(\'title\', $thislang) : \'title\';\r\n\r\n		$rs = getRows(\"\r\n			SELECT DISTINCT $fields\r\n			FROM \".PFX.\"txp_image\r\n			WHERE $clause\r\n			LIMIT 0, $maxlimit\r\n		\", $debug);\r\n		list($allImages, $numPages, $thumPage, $offset) = smd_getPage($rs, $gallID, $limit, $offset, $paging);\r\n	}\r\n\r\n\r\n	// Is post-ordering required? Do it\r\n	if ($randsort) {\r\n		shuffle($rs);\r\n	} else if (count($postOrder) > 0) {\r\n		// Translate the rows into columns that can be sorted\r\n		foreach($rs as $key => $row) {\r\n			foreach ($row as $identifier => $item) {\r\n				$varname = \"col_\".$identifier;\r\n				${$varname}[$key] = $item;\r\n			}\r\n		}\r\n\r\n		// Make up an array_multisort arg list and execute it.\r\n		// The necessary evil() is because we don\'t know how many cols there will be in the array\r\n		for ($idx = 0; $idx < count($postOrder); $idx++) {\r\n			$sortargs[] = \'$col_\'.$postOrder[$idx][\'col\'];\r\n			$sortargs[] = $postOrder[$idx][\'sort\'];\r\n		}\r\n		$sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $rs);\';\r\n		eval($sortit);\r\n	}\r\n\r\n	// Is fixed-order output desired? If so, re-order the $rs\r\n	if ($fixedOrder) {\r\n		$orderedRS = Array();\r\n		$ignoreList = Array();\r\n		$remainingRS = Array();\r\n\r\n		// Suck out the fixed items first. IDs then categories\r\n		for ($idx = 0; $idx < count($idinc); $idx++) {\r\n			foreach($rs as $row) {\r\n				if ($id && doQuote($row[\'id\']) == $idinc[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n					break;\r\n				}\r\n			}\r\n		}\r\n		for ($idx = 0; $idx < count($catinc); $idx++) {\r\n			foreach($rs as $row) {\r\n				if ($category && doQuote($row[\'category\']) == $catinc[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n				}\r\n			}\r\n		}\r\n		// Tack on the remaining rows\r\n		if ($rs) {\r\n			foreach($rs as $row) {\r\n				if (!in_array($row[\'id\'], $ignoreList)) {\r\n					$remainingRS[] = $row;\r\n				}\r\n			}\r\n		}\r\n		$rs = array_merge($orderedRS, $remainingRS);\r\n	}\r\n\r\n	// Grab the portion of the record set actually required to iterate over\r\n	if ($rs) {\r\n		$rs = array_slice($rs, $offset, (($directory) ? $dirlimit : (($limit==0) ? $maxlimit : $limit)));\r\n	}\r\n\r\n	// Prepare fixed thumbsizes\r\n	$thumbWidth = $thumbHeight = \'\';\r\n	$thumbWUnits = $thumbHUnits = \'\';\r\n	if ($thumbsize) {\r\n		$thumbSizes = do_list($thumbsize, $delim);\r\n		$units = preg_replace(\'/[0-9]/\', \'\', $thumbSizes[0]);\r\n		if (count($thumbSizes) == 1) {\r\n			$thumbWidth = $thumbHeight = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[0]);\r\n			$thumbWUnits = $thumbHUnits = ($units == \'\') ? $thumbWUnits : $units;\r\n		} else {\r\n			$thumbWidth = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[0]);\r\n			$thumbHeight = preg_replace(\'/[^0-9]/\', \'\', $thumbSizes[1]);\r\n			$thumbWUnits = ($units == \'\' && $thumbWidth != \'\') ? $thumbWUnits : $units;\r\n			$units = preg_replace(\'/[0-9]/\', \'\', $thumbSizes[1]);\r\n			$thumbHUnits = ($units == \'\' && $thumbHeight != \'\') ? $thumbHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Prepare fixed image sizes\r\n	$imageWidth = $imageHeight = \'\';\r\n	$imageWUnits = $imageHUnits = \'\';\r\n	if ($imgsize) {\r\n		$imageSizes = do_list($imgsize, $delim);\r\n		$units = preg_replace(\'/[0-9]/\', \'\', $imageSizes[0]);\r\n		if (count($imageSizes) == 1) {\r\n			$imageWidth = $imageHeight = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[0]);\r\n			$imageWUnits = $imageHUnits = ($units == \'\') ? $imageWUnits : $units;\r\n		} else {\r\n			$imageWidth = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[0]);\r\n			$imageHeight = preg_replace(\'/[^0-9]/\', \'\', $imageSizes[1]);\r\n			$imageWUnits = ($units == \'\' && $imageWidth != \'\') ? $imageWUnits : $units;\r\n			$units = preg_replace(\'/[0-9]/\', \'\', $imageSizes[1]);\r\n			$imageHUnits = ($units == \'\' && $imageHeight != \'\') ? $imageHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Collation defers parsing the form for each returned item.\r\n	// Instead it outputs the data as a list at the end (items may be individually quoted or extracted if required)\r\n	//TODO : Add ALWAYS switch as a shortcut to specify that all matching items (regardless of #1, #2, #3 etc) are to be quoted\r\n	if ($collate) {\r\n		$dblQuote = $sglQuote = array(\'SMDNONE\');\r\n		$collateOpts = do_list($collate, $delim);\r\n		foreach ($collateOpts as $option) {\r\n			$indexes = do_list($option, $paramdelim);\r\n			$colType = array_shift($indexes);\r\n			// Supplying \'quote\' on its own will empty the relevant array, implying \"ALL\" fields are to be quoted\r\n			switch ($colType) {\r\n				case \"sglquote\":\r\n					$sglQuote = $indexes;\r\n					break;\r\n				case \"dblquote\":\r\n				case \"quote\":\r\n					$dblQuote = $indexes;\r\n					break;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Combos are user-defined combinations of replacement modifiers (e.g. idext: {id}{ext}, ...\r\n	$combos = array();\r\n	if ($combo) {\r\n		$combo = do_list($combo, $delim);\r\n		foreach ($combo as $newDef) {\r\n			list($defParts, $unwanted) = smd_doList($newDef, false, \'\', false, $paramdelim, $spc);\r\n			$defName = \"{\".array_shift($defParts).\"}\";\r\n			$combos[$defName] = $defParts[0];\r\n		}\r\n	}\r\n	if ($debug > 1) {\r\n		echo \"++ COMBOs ++\";\r\n		dmp($combos);\r\n	}\r\n	// Construct the output\r\n	$thumbout = array();\r\n	$groupout = array();\r\n	$navout = array();\r\n	$out = array();\r\n	$collations = array();\r\n	$chIdentifier = \"onchange\";\r\n\r\n	if ($debug > 2) {\r\n		echo \"++ RECORD SET ++\";\r\n		dmp($rs);\r\n	}\r\n	if ($debug > 3) {\r\n		echo \"++ AVAILABLE \'?\' VARS ++\";\r\n		if ($pretext) dmp($pretext);\r\n		if ($thisarticle) dmp($thisarticle);\r\n	}\r\n\r\n	if($rs) {\r\n		$idx = 0;\r\n		$ctr = 1;\r\n		$thumbIdx = 0;\r\n		$thumbCtr = 1;\r\n		$numImages = count($rs);\r\n		$repagements = array();\r\n\r\n		// Set up the paging features if required\r\n		if (($limit > 0) && $paging) {\r\n			$next = ($numPages > 1 && $thumPage != $numPages);\r\n			$prev = ($numPages > 1 && $thumPage > 1);\r\n			$prevPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage-1);\r\n			$nextPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage+1);\r\n			$thisPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumPage);\r\n			$firstPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, 1);\r\n			$lastPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $numPages);\r\n\r\n			// Labels can be specified in the replacement tag after a paramdelim.\r\n			// \'earlier\' and \'later\' don\'t have labels. Anything specified after paramdelim is the max number of pages to show; empty = all pages\r\n			$label = array(\r\n					\'{navfirst}\' => $smd_gallLang->gTxt(\'navfirst\'),\r\n					\'{navprev}\' => $smd_gallLang->gTxt(\'navprev\'),\r\n					\'{navearlier}\' => \'\',\r\n					\'{navthis}\' => $thumPage,\r\n					\'{navthisurl}\' => $thumPage,\r\n					\'{navlater}\' => \'\',\r\n					\'{navnext}\' => $smd_gallLang->gTxt(\'navnext\'),\r\n					\'{navlast}\' => $smd_gallLang->gTxt(\'navlast\'),\r\n				);\r\n\r\n			$labelRE = \'/\\{(nav[a-z]+)\'.$paramdelim.\'(.+?)\\}/\';\r\n			$numLabels = preg_match_all($labelRE, $pageform, $labels);\r\n\r\n			for ($labelCtr = 0; $labelCtr < $numLabels; $labelCtr++) {\r\n				$fieldname = \'{\'.$labels[1][$labelCtr].\'}\';\r\n				$label[$fieldname] = $labels[2][$labelCtr];\r\n				$pageform = str_replace($labels[0][$labelCtr], \'{\'.$labels[1][$labelCtr].\'}\', $pageform);\r\n			}\r\n\r\n			if ($debug > 1) {\r\n				echo \"++ PAGING LABELS ++\";\r\n				dmp($label);\r\n			}\r\n\r\n			// Generate \'earlier\' and \'later\' lists\r\n			$numEarlier = intval($label[\'{navearlier}\']);\r\n			$numLater = intval($label[\'{navlater}\']);\r\n			$numEarlier = ($numEarlier && ($thumPage-$numEarlier > 1)) ? $numEarlier : $thumPage-1;\r\n			$numLater = ($numLater && ($thumPage+$numLater < $numPages)) ? $numLater : $numPages-$thumPage;\r\n			$navEarlier = $navLater = array();\r\n			for ($cod = $thumPage-$numEarlier; $cod < $thumPage; $cod++) {\r\n				$navEarlier[] = \'<a href=\"\'.smd_addQSVar($pretext[\'request_uri\'], $gallID, $cod).\'\">\'.$cod.\'</a>\';\r\n			}\r\n			for ($cod = $thumPage+1; $cod <= $thumPage+$numLater; $cod++) {\r\n				$navLater[] = \'<a href=\"\'.smd_addQSVar($pretext[\'request_uri\'], $gallID, $cod).\'\">\'.$cod.\'</a>\';\r\n			}\r\n			$navEarlier = doWrap($navEarlier, \'\', $navbreak, \'\');\r\n			$navLater = doWrap($navLater, \'\', $navbreak, \'\');\r\n\r\n			// Make up the repagements array for pageform\r\n			$repagements = array(\r\n					\'{navfirst}\' => (($prev) ? doTag(\'<a href=\"\'.$firstPage.\'\">\'.$label[\'{navfirst}\'].\'</a>\', $navbreak) : doTag($label[\'{navfirst}\'], $navbreak, $emptyclass)),\r\n					\'{navprev}\' => (($prev) ? doTag(\'<a href=\"\'.$prevPage.\'\">\'.$label[\'{navprev}\'].\'</a>\', $navbreak) : doTag($label[\'{navprev}\'], $navbreak, $emptyclass)),\r\n					\'{navearlier}\' => $navEarlier,\r\n					\'{navthis}\' => doTag($label[\'{navthis}\'], $navbreak, \'current \'.$emptyclass),\r\n					\'{navthisurl}\' => doTag(\'<a href=\"\'.$thisPage.\'\">\'.$label[\'{navthisurl}\'].\'</a>\', $navbreak, \'current\'),\r\n					\'{navlater}\' => $navLater,\r\n					\'{navnext}\' => (($next) ? doTag(\'<a href=\"\'.$nextPage.\'\">\'.$label[\'{navnext}\'].\'</a>\', $navbreak) : doTag($label[\'{navnext}\'], $navbreak, $emptyclass)),\r\n					\'{navlast}\' => (($next) ? doTag(\'<a href=\"\'.$lastPage.\'\">\'.$label[\'{navlast}\'].\'</a>\', $navbreak) : doTag($label[\'{navlast}\'], $navbreak, $emptyclass)),\r\n					\'{navthispageurl}\' => $thisPage,\r\n					\'{navnextpageurl}\' => (($next) ? $nextPage : \'\'),\r\n					\'{navprevpageurl}\' => (($prev) ? $prevPage : \'\'),\r\n					\'{navfirstpageurl}\' => $firstPage,\r\n					\'{navlastpageurl}\' => $lastPage,\r\n					\'{totalimages}\' => $numImages,\r\n					\'{totalpages}\' => $numPages,\r\n					\'{maximages}\' => $allImages,\r\n				);\r\n\r\n				// Perform escaping here, just prior to output\r\n				if ($escapes) {\r\n					foreach ($repagements as $defName => $defVal) {\r\n						if (in_array(trim($defName, \"{}\"), $escapes)) {\r\n							$repagements[$defName] = htmlspecialchars($defVal, ENT_QUOTES);\r\n						}\r\n					}\r\n				}\r\n\r\n				// Perform the substitutions\r\n				$navout[] = parse(strtr($pageform, $repagements));\r\n		}\r\n\r\n		// For every image in the gallery...\r\n		foreach($rs as $key => $row) {\r\n			if ($directory) {\r\n				$urlPath = $row[\'path\'].\'/\';\r\n			} else {\r\n				$urlPath = hu.$img_dir.\'/\';\r\n				$row[\'category_title\'] = fetch_category_title($row[\'category\'], \'image\');\r\n			}\r\n			$theThumb = $urlPath . $row[\'id\'] . $thumbmod . $row[\'ext\'];\r\n			$fullSize = $urlPath . $row[\'id\'] . $fullmod .$row[\'ext\'];\r\n\r\n			// Do we need to reset the thumb counter this time?\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === 0) {\r\n					$iter = explode($paramdelim, $resetOpt);\r\n					$iter = (count($iter) > 1) ? $iter[1] : 0;\r\n					if ($thumbIdx % $iter == 0) {\r\n						$thumbIdx = 0;\r\n						$thumbCtr = 1;\r\n					}\r\n				} else if ($row[$resetOpt] != $resetVal) {\r\n					$thumbIdx = 0;\r\n					$thumbCtr = 1;\r\n					break;\r\n				}\r\n			}\r\n\r\n			// Override thumbsize / imgsize if required\r\n			if ($thumbsize) {\r\n				$theThumbWidth = empty($thumbWidth) ? $row[\'thumb_w\'] : $thumbWidth;\r\n				$theThumbHeight = empty($thumbHeight) ? $row[\'thumb_h\'] : $thumbHeight;\r\n				$theThumbWUnits = $thumbWUnits;\r\n				$theThumbHUnits = $thumbHUnits;\r\n			} else {\r\n				$theThumbWidth = $row[\'thumb_w\'];\r\n				$theThumbHeight = $row[\'thumb_h\'];\r\n				$theThumbWUnits = $theThumbHUnits = \'\';\r\n			}\r\n\r\n			if ($imgsize) {\r\n				$theImageWidth = empty($imageWidth) ? $row[\'w\'] : $imageWidth;\r\n				$theImageHeight = empty($imageHeight) ? $row[\'h\'] : $imageHeight;\r\n				$theImageWUnits = $imageWUnits;\r\n				$theImageHUnits = $imageHUnits;\r\n			} else {\r\n				$theImageWidth = $row[\'w\'];\r\n				$theImageHeight = $row[\'h\'];\r\n				$theImageWUnits = $theImageHUnits = \'\';\r\n			}\r\n\r\n			// Construct the replacement array\r\n			$replacements = array(\r\n					\'{id}\' => $row[\'id\'],\r\n					\'{name}\' => $row[\'name\'],\r\n					\'{ext}\' => $row[\'ext\'],\r\n					\'{category}\' => $row[\'category\'],\r\n					\'{category_title}\' => ((isset($row[\'category_title\']) && $row[\'category_title\'] !== false) ? $row[\'category_title\'] : \'\'),\r\n					\'{author}\' => $row[\'author\'],\r\n					\'{hasthumb}\' => (isset($row[\'thumbnail\']) ? $row[\'thumbnail\'] : 0),\r\n					\'{url}\' => $fullSize,\r\n					\'{thumburl}\' => $theThumb,\r\n					\'{imagepath}\' => $urlPath,\r\n					\'{imagedef}\' => basename($fullSize),\r\n					\'{thumbdef}\' => basename($theThumb),\r\n					\'{alt}\' => $row[\'alt\'],\r\n					\'{title}\' => $row[\'caption\'],\r\n					\'{caption}\' => $row[\'caption\'],\r\n					\'{thumbwidth}\' => $theThumbWidth,\r\n					\'{thumbheight}\' => $theThumbHeight,\r\n					\'{thumbwunits}\' => $theThumbWUnits,\r\n					\'{thumbhunits}\' => $theThumbHUnits,\r\n					\'{width}\' => $theImageWidth,\r\n					\'{height}\' => $theImageHeight,\r\n					\'{wunits}\' => $theImageWUnits,\r\n					\'{hunits}\' => $theImageHUnits,\r\n					\'{date}\' => (($format) ? safe_strftime($format, strtotime($row[\'date\']), $gmt, $lang) : $row[\'date\']),\r\n					\'{galleryid}\' => $gallID,\r\n					\'{index}\' => $idx,\r\n					\'{counter}\' => $ctr,\r\n					\'{thumbindex}\' => $thumbIdx,\r\n					\'{thumbcounter}\' => $thumbCtr,\r\n					\'{totalimages}\' => $numImages,\r\n					\'{maximages}\' => $allImages,\r\n				);\r\n\r\n			// Add any custom columns\r\n			foreach ($extraMap as $eCol => $eVal) {\r\n				$replacements[\'{\'.$eVal.\'}\'] = $row[$eCol];\r\n			}\r\n\r\n			// Append any changed items that are required\r\n			$endtag = false;\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === 0) {\r\n					$iter = explode($paramdelim, $resetOpt);\r\n					$iter = (count($iter) > 1) ? $iter[1] : 0;\r\n					if ($thumbCtr % $iter === 0 || $ctr == $numImages) {\r\n						$endtag = true;\r\n					}\r\n				} else {\r\n					$replacements[\'{\'.$chIdentifier.$paramdelim.$resetOpt.\'}\'] = ($thumbIdx == 0) ? ((isset($resetForms[$resetOpt])) ? strtr(parse_form($resetForms[$resetOpt]), $replacements) : doTag($row[$resetOpt], $onchangewraptag, $onchangeclass)) : \'\';\r\n					// Do a sneaky (yuk) lookahead. Would love to find a better way to do this\r\n					if (isset($rs[$key+1])) {\r\n						// category_title is not available on the next row (yet) so populate it if required... bleurgh!\r\n						if ($resetOpt == \'category_title\') {\r\n							$rs[$key+1][$resetOpt] = (isset($rs[$key+1][\'category\']) && !empty($rs[$key+1][\'category\'])) ? fetch_category_title($rs[$key+1][\'category\'], \'image\') : \'\';\r\n						}\r\n						if(isset($rs[$key+1][$resetOpt]) && ($rs[$key+1][$resetOpt] != $row[$resetOpt])) {\r\n							$endtag = true;\r\n						}\r\n					} else {\r\n						$endtag = true; // Last image\r\n					}\r\n				}\r\n			}\r\n\r\n			$replacements[\'{grouptagend}\'] = ($grouptag && $endtag) ? \'</\' . $grouptag . \'>\' : \'\';\r\n			$replacements[\'{grouptagstart}\'] = ($grouptag && $thumbIdx == 0) ? \'<\' . $grouptag . (($groupclass) ? \' class=\"\'.$groupclass.\'\"\' : \'\') . \'>\' : \'\';\r\n\r\n			// Append any combos\r\n			foreach ($combos as $defName => $newDef) {\r\n				if (!array_key_exists($defName, $replacements)) {\r\n					$replacements[$defName] = strtr($newDef, $replacements);\r\n				}\r\n			}\r\n\r\n			// Solos are items in the output form that require details from a specific row. Useful only in collation\r\n			// mode, they are added to the replacements array on an as-needed basis to save space/time\r\n			$soloRE = \'/\\{([a-z0-9]+)#\'.$ctr.\'\\}/\';\r\n			$numSolos = preg_match_all($soloRE, $thing, $solos);\r\n			for ($soloCtr = 0; $soloCtr < $numSolos; $soloCtr++) {\r\n				$fieldname = \'{\'.$solos[1][$soloCtr].\'#\'.$ctr.\'}\';\r\n				$grabfield = \'{\'.$solos[1][$soloCtr].\'}\';\r\n				$replacements[$fieldname] = $replacements[$grabfield];\r\n			}\r\n\r\n			// Perform escaping on the replacements.\r\n			// Note the repagements have already been escaped so when/if they are combined with the replacements,\r\n			// the escaping is not done twice\r\n			if ($escapes) {\r\n				foreach ($replacements as $defName => $defVal) {\r\n					if (in_array(trim($defName, \"{}\"), $escapes)) {\r\n						$replacements[$defName] = htmlspecialchars($defVal, ENT_QUOTES);\r\n					}\r\n				}\r\n			}\r\n\r\n			// Add the navigation items if they\'re defined\r\n			if (count($repagements) > 0) {\r\n				$replacements = array_merge($repagements, $replacements);\r\n			}\r\n\r\n			// Is a thumb/text object required this time?\r\n			$object = \'\';\r\n			if ($thumbIdx >= $thumboffset && $thumbIdx < $thumboffset+$thumblimit) {\r\n				$object = ($objectform) ? parse(strtr($objectform, $replacements)) : (($text) ? $text : \'<img src=\"\'.$theThumb.\'\"\' .(($row[\'alt\']) ? \' alt=\"\'.$row[\'alt\'].\'\"\' : \'\') .(($theThumbWidth) ? \' width=\"\'.$theThumbWidth.$theThumbWUnits.\'\"\' : \'\') .(($theThumbHeight) ? \' height=\"\'.$theThumbHeight.$theThumbHUnits.\'\"\' : \'\') .\' />\');\r\n			}\r\n			$replacements[\'{object}\'] = $object;\r\n\r\n			if ($debug > 0) {\r\n				echo \"++ REPLACEMENTS : IMAGE \".$ctr.\" ++\";\r\n				dmp($replacements);\r\n			}\r\n\r\n			// Check if countform is to be called\r\n			$countpre = $countpost = array();\r\n			if ($counting) {\r\n				foreach ($countform as $countlist) {\r\n					$posover = array();\r\n					$countlist = strtr($countlist, $replacements); // Replace any {} vars with their real values\r\n					$countlist = do_list($countlist, $paramdelim);\r\n					if ($dpos=array_search(\'smd_above\', $countlist)) {\r\n						$posover[] = \"above\";\r\n						unset($countlist[$dpos]);\r\n					}\r\n					if ($dpos=array_search(\'smd_below\', $countlist)) {\r\n						$posover[] = \"below\";\r\n						unset($countlist[$dpos]);\r\n					}\r\n					$countlist = array_merge($countlist); // re-index\r\n\r\n					$posover = (empty($posover)) ? $countposns : $posover;\r\n					$divby = false;\r\n					if ($countlist[0] == \'smd_every\') {\r\n						$divby = true;\r\n						array_shift($countlist);\r\n					}\r\n					$numforms = count($countlist);\r\n					$callform = fetch_form($countlist[$numforms-1]);\r\n					unset($countlist[$numforms-1]);\r\n					$orlogic = $andlogic = array();\r\n\r\n					// Check if any value is a range and expand it if so\r\n					$fullcountlist = array();\r\n					foreach ($countlist as $custcount) {\r\n						if (preg_match(\'/^(\\d+)\\-(\\d+)$/\', $custcount)) {\r\n							list($lo, $hi) = explode(\"-\", $custcount, 2);\r\n							$fullcountlist += range($lo, $hi);\r\n						} else {\r\n							$fullcountlist[] = $custcount;\r\n						}\r\n					}\r\n\r\n					$countlist = $fullcountlist;\r\n					foreach ($countlist as $custcount) {\r\n						$showval = (strpos($custcount, \"!\") === 0) ? false : true;// Negative logic required\r\n						$custcount = str_replace(\'!\', \'\', $custcount);\r\n						if ($showval) {\r\n							$orlogic[] = empty($custcount) || (($divby) ? (($ctr % $custcount === 0) ? true : false) : (($custcount == $ctr) ? true : false));\r\n						} else {\r\n							$andlogic[] = ($custcount == $ctr) ? false : true;\r\n						}\r\n					}\r\n					if (in_array(\'1\', $orlogic) && !in_array(\'0\', $andlogic)) {\r\n						if (in_array(\"above\", $posover)) {\r\n							$countpre[] = parse(strtr($callform, $replacements));\r\n						}\r\n						if (in_array(\"below\", $posover)) {\r\n							$countpost[] = parse(strtr($callform, $replacements));\r\n						}\r\n					}\r\n            }\r\n			}\r\n\r\n			if ($debug > 0 && ($countpre || $countpost)) {\r\n				echo \"++ COUNT FORM TRIGGERED ++\";\r\n				if ($countpre) {\r\n					dmp($countpre);\r\n				}\r\n				if ($countpost) {\r\n					dmp($countpost);\r\n				}\r\n			}\r\n\r\n			// In collate mode the form is only parsed at the end: build a collosal multi-dimension array of all items here\r\n			if ($collate) {\r\n				foreach ($replacements as $defName => $defVal) {\r\n					$collations[$defName][] = $defVal;\r\n				}\r\n			} else {\r\n				if ($counting) {\r\n					$thumbout = array_merge($thumbout, $countpre);\r\n				}\r\n\r\n				if ($thumbIdx < $grouplimit) {\r\n					$thumbout[] = parse(strtr($thing, $replacements));\r\n				}\r\n				if ($counting) {\r\n					$thumbout = array_merge($thumbout, $countpost);\r\n				}\r\n			}\r\n\r\n			// Keep track of the current values so we know if they change next time\r\n			foreach ($resetOpts as $resetOpt => $resetVal) {\r\n				if (strpos($resetOpt, \'smd_every\') === false) {\r\n					$resetOpts[$resetOpt] = $row[$resetOpt];\r\n				} else {\r\n					$resetOpts[$resetOpt] = $thumbCtr;\r\n				}\r\n			}\r\n			$thumbIdx++;\r\n			$thumbCtr++;\r\n			$idx++;\r\n			$ctr++;\r\n		}\r\n		if ($collate) {\r\n			foreach ($collations as $item => $list) {\r\n				// Quote the lists if required\r\n				$list = (empty($dblQuote) || in_array($item, $dblQuote)) ? doArray($list, \'smd_doDblQuote\') : $list;\r\n				$list = (empty($sglQuote) || in_array($item, $sglQuote)) ? doArray($list, \'doQuote\') : $list;\r\n				$collations[$item] = implode($collatedelim, $list);\r\n			}\r\n			if ($debug > 0) {\r\n				echo \"++ COLLATIONS ++\";\r\n				dmp($collations);\r\n			}\r\n			$thumbout[] = parse(strtr($thing, $collations));\r\n		}\r\n\r\n		// Tack on the paging from earlier if it\'s to go afterwards\r\n		if (($limit > 0) && $paging) {\r\n			if ($debug > 0) {\r\n				echo \"++ PAGING REPLACEMENTS ++\";\r\n				dmp($repagements);\r\n			}\r\n		}\r\n	}\r\n\r\n	// Add the paging to the output if it\'s to go above the thumbs\r\n	if (($limit > 0) && $paging) {\r\n		if (in_array(\"above\", $pageout)) {\r\n			$out[] = doWrap($navout, $navwraptag, \'\', $navclass);\r\n		}\r\n	}\r\n\r\n	$out[] = doWrap($thumbout, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n\r\n	if (($limit > 0) && $paging) {\r\n		if (in_array(\"below\", $pageout)) {\r\n			$out[] = doWrap($navout, $navwraptag, \'\', $navclass);\r\n		}\r\n	}\r\n\r\n	if ($debug > 2) {\r\n		echo \"++ FINAL OUTPUT ++\";\r\n		dmp($out);\r\n	}\r\n\r\n	return join(\" \", $out);\r\n}\r\n\r\n// Work out which page this gallery is on and return the relevant info\r\nfunction smd_getPage($rs, $gallID, $limit, $offset, $paging) {\r\n	$numThums = count($rs) - $offset;\r\n	if (($limit > 0) && $paging) {\r\n		$numPages = ($numThums > 0) ? ceil($numThums/$limit) : 1;\r\n		$thumPage = (!gps($gallID)) ? 1 : gps($gallID);\r\n		$offset = (($thumPage - 1) * $limit) + $offset;\r\n	} else {\r\n		// No paging required\r\n		$numPages = 1;\r\n		$thumPage = 1;\r\n	}\r\n	return array($numThums, $numPages, $thumPage, $offset);\r\n}\r\n\r\n// Read the given delimited directories and get all files that match.\r\n// Go down as many subdir levels as given in the $sub parameter\r\nfunction smd_doDirectory($lst, $lvl=0, $sub=0, $match=\"\", $dlm=\",\", $spc=1, $ctr=0) {\r\n	list($dirinc, $direxc) = smd_doList($lst, false, $sub, false, $dlm, $spc);\r\n	list($matchinc, $matchexc) = smd_doList($match, false, \'\', false, $dlm, $spc);\r\n	$matchinc = \".*\" .(($matchinc) ? \"(\" .implode(\"|\",$matchinc).\")+.*\" : \"\");\r\n	$matchexc = ($matchexc) ? \".*(\" .implode(\"|\",$matchexc).\")+.*\" : \"smd_glry_\".mt_rand();\r\n\r\n	$sub = (empty($sub)) ? 0 : $sub;\r\n	$sub = (strtolower($sub)==\"all\") ? 99999 : $sub;\r\n	$fileList = array();\r\n	foreach ($dirinc as $dir) {\r\n		if ($handle = @opendir($dir)) {\r\n			while (($file = @readdir($handle)) !== false) {\r\n				$fullpath = $dir.DS.$file;\r\n				if (preg_match(\"/\".$matchinc.\"/\", $fullpath) && !preg_match(\"/\".$matchexc.\"/\", $fullpath)) {\r\n					if (is_dir($fullpath)) {\r\n						if (($file == \".\") || ($file== \"..\") || (in_array($file, $direxc)) || ($lvl >= $sub)) {\r\n							continue;\r\n						} else {\r\n							// Recurse the subdir. Note that the \'!\' is temporarily added back to each direxc item\r\n							$tmp = $direxc; // Don\'t overwrite the original array\r\n							array_unshift($tmp, $fullpath);\r\n							$subFiles = smd_doDirectory(implode($dlm.\"!\", $tmp), $lvl+1, $sub, $match, $dlm, $spc, $ctr);\r\n							$fileList = array_merge($fileList, $subFiles);\r\n							$ctr += count($subFiles);\r\n							continue;\r\n						}\r\n					} else {\r\n						$filestat = stat($fullpath);\r\n						$imgsize = getimagesize($fullpath);\r\n						$fileList[$ctr][\'path\'] = $dir;\r\n						$fileList[$ctr][\'name\'] = basename($fullpath);\r\n						$fileList[$ctr][\'id\'] = preg_replace(\'/\\..+$/\', \'\', $file);\r\n						$fileList[$ctr][\'alt\'] = \'\';\r\n						$fileList[$ctr][\'caption\'] = \'\';\r\n						$parts = explode(DS, $dir);\r\n						$fileList[$ctr][\'category\'] = (count($parts) > 1) ? end($parts) : \'\';\r\n						$fileList[$ctr][\'author\'] = strval($filestat[\'uid\']);\r\n						$fileList[$ctr][\'date\'] = strftime(\"%Y-%m-%d %H:%M:%S\", $filestat[\'ctime\']);\r\n						$parts = explode(\".\", $file);\r\n						$fileList[$ctr][\'ext\'] = (count($parts) > 1) ? \".\".end($parts) : \'\';\r\n						$fileList[$ctr][\'w\'] = (is_array($imgsize)) ? strval($imgsize[0]) : \'\';\r\n						$fileList[$ctr][\'h\'] = (is_array($imgsize)) ? strval($imgsize[1]) : \'\';\r\n					}\r\n					$ctr++;\r\n				}\r\n			}\r\n		}\r\n		if ($handle) {\r\n			@closedir($handle);\r\n		}\r\n	}\r\n	return $fileList;\r\n}', '9f5b0ecb470a10096da367b8c656aa9e', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_lib', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.37', 'Shared function library used by smd_ plugins and others.', '<h1>smd_ plugin library</h1>\n\n<p>Offers no public textpattern tags. It is simply a shared library of common functions used by smd_ plugins.</p>\n\n<h2>Function Reference</h2>\n\n<p><strong>smd_addQSVar</strong><br />\n<strong>smd_removeQSVar</strong></p>\n\n<p>Add or remove a query string variable to the given <span class=\"caps\">URL</span>, taking into account any existing variables that may be in the <span class=\"caps\">URL</span> already. ‘Add’ takes three arguments, ‘Remove’ just takes the first two:</p>\n\n<ol>\n	<li>The <span class=\"caps\">URL</span> string to add to/remove from</li>\n	<li>The id of the querystring (the bit before the = sign)</li>\n	<li>The value of the new querystring (the bit after the = sign)</li>\n</ol>\n\n<p>e.g. <code>smd_addQSVar($thisarticle[\'url_title\'], \'tpg\', 15);</code> would add <code>tpg=15</code> to the current article&#8217;s <span class=\"caps\">URL</span>. If there are no other variables currently in the <span class=\"caps\">URL</span>, it is added with a question mark, otherwise it is appended with an ampersand.</p>\n\n<h3>smd_doList</h3>\n\n<p>Return an expanded list of items with the following properties:</p>\n\n<ol>\n	<li>Anything containing ‘?’ or ‘!’ is checked for a match with a <span class=\"caps\">TXP</span> field (<code>&lt;txp:variable /&gt;</code>, image, file, link, global article, url <span class=\"caps\">POST</span>/GET/SERVER, or individual article, in that order)</li>\n	<li>Any ranges of items are expanded (e.g. 4-7 =&gt; 4,5,6,7) if the <code>rng</code> option permits it</li>\n	<li><span class=\"caps\">TXP</span> fields may themselves be lists or ranges</li>\n	<li>Anything that is not a <span class=\"caps\">TXP</span> field is used verbatim</li>\n	<li>The items are returned as 2 lists; inclusion and exclusion</li>\n</ol>\n\n<p>Args ( [*] = mandatory ):</p>\n\n<ol>\n	<li>[*] lst = the list as a delimited string</li>\n	<li>rng = whether to allow ranges or not (bool). Default = true</li>\n	<li>sub = the type of subcategory to traverse (image, file, link, article, none=”“) and how many levels to go down (e.g. image:2). Default = ‘’</li>\n	<li>qte = whether to quote each item in the array or not (bool). Default = true</li>\n	<li>dlm = the delimiter (string). Default = “,”</li>\n	<li>lax = Whether to be lax or strict about what characters constitute a field; primarily whether spaces are allowed in, say, custom fields. Default = “1”</li>\n</ol>\n\n<h3>smd_getOpts</h3>\n\n<p>Deprecated as it is mostly superseded by smd_doList; this one is clunkier but has $idprefix so it remains for now. It searches the passed string for predetermined sequences of characters and, if that sequence is in the given $allowed array, replaces it as follows:</p>\n\n<ul>\n	<li>?c = current global category (!c = not current category)</li>\n	<li>?s = current section (!s = not current section)</li>\n	<li>?t = current article title (!t = not current title)</li>\n	<li>?id = current article ID, prepended with $idprefix (!id = not current ID)</li>\n	<li>?q = current query term (!q = not current query term)</li>\n	<li>?field = contents of the current article&#8217;s field (could be a comma-separated list)</li>\n	<li>!field = not the contents of the current article&#8217;s field (could be a comma-separated list)</li>\n</ul>\n\n<p>Integer ranges (e.g. 1-5) will be expanded into their individual values if the $allowRange option is true; anything else is returned verbatim. It outputs two arrays: the 1st contains items for inclusion, the 2nd contains items for exclusion.</p>\n\n<p>Args ( [*] = mandatory ):</p>\n\n<ol>\n	<li>[*] The string to search for matches</li>\n	<li>[*] An array containing shortcuts that are “allowed” to be found in the string (?c, ?s, ?t, ?field etc)</li>\n	<li>The prefix for ?id strings</li>\n	<li>Boolean indicating whether to allow range expansion or not</li>\n	<li>RegEx string to split options at (see smd_split)</li>\n	<li>preg_split option (see smd_split)</li>\n</ol>\n\n<h3>smd_split</h3>\n\n<p>Returns an array of items from a string of (usually) comma-separated values. If any values contain ranges of numbers like 1-5 that need ‘expanding’ first (and $allowRange is true), they are dealt with. Takes the following arguments ( [*] = mandatory args) :</p>\n\n<ol>\n	<li>[*] The string to split</li>\n	<li>Boolean indicating whether to allow range expansion or not (i.e. 1-5 becomes 1,2,3,4,5)</li>\n	<li>The regular expression character classes to match. If a full RegEx starting and ending with ‘/’ characters is supplied, the expression is used verbatim. Without the ‘/’ characters, the expression is treated as a list of character classes to find. Defaults to “/(,|,\\s)+/” which is a comma, or comma and a whitespace character.</li>\n	<li>preg_split option constant as defined in the php manual</li>\n</ol>\n\n<h3>smd_MLP</h3>\n\n<p>Instantiate one of these to handle <span class=\"caps\">MLP</span> in your plugin like this:</p>\n\n<p>1) Declare a unique global variable, e.g. global $myPlug<br />\n2) Define your default string replacement array (doesn&#8217;t need to be global), e.g:</p>\n\n<p>$myStrings = array (“msg1” =&gt; “This is message 1”, “msg2” =&gt; “This is message 2”);</p>\n\n<p>3) Create an <span class=\"caps\">MLP</span> handler:</p>\n\n<p>$myPlug = new smd_MLP(“plugin_name”, “plugin_prefix”, $myStrings);</p>\n\n<p>4) That&#8217;s it! There are two optional args to smd_MLP: a) the default (full) language to use, e.g “da-dk”. Defaults to “en-gb”. b) the interface the strings are for. Choose from “public” (the default), “admin” or “common”</p>\n\n<p>5) To use a replacement string in your code: a) Make sure to import the unique global variable: e.g. global $myPlug; b) Call $myPlug-&gt;gTxt(“messageID”); [ e.g. $myPlug-&gt;gTxt(“msg1”) ] c) If you want to replace any args in your message string, pass an associative array as the 2nd arg to gTxt()</p>\n\n<h3>smd_doDblQuote</h3>\n\n<p>Alternative to the core&#8217;s doQuote(). This one dbl-quotes instead of sgl-quotes</p>\n\n<h3>smd_pregPos</h3>\n\n<p>Lifted from one of the comments in the <span class=\"caps\">PHP</span> manual, this just looks for a RegEx string within another, returning the matches it finds and the position of the first match.</p>\n\n<h3>array_combine</h3>\n\n<p>PHP4 equivalent of the standard PHP5 function, lifted from php.net</p>\n\n<h3>htmlspecialchars_decode</h3>\n\n<p>PHP4 equivalent of the standard PHP5 function, lifted from php.net</p>', '// Software engineers look away now unless you wish to be horrified by the amount\r\n// of coupling and cohesion in these functions. I\'m warning you... it\'s not pretty\r\nif (!class_exists(\'smd_MLP\')) {\r\n    class smd_MLP\r\n    {\r\n        var $smd_strings;\r\n        var $smd_owner;\r\n        var $smd_prefix;\r\n        var $smd_lang;\r\n        var $smd_event;\r\n\r\n        function __construct($plug, $prefx, $strarray, $lng = \'en-gb\', $ev = \'public\')\r\n        {\r\n            $this->smd_owner = $plug;\r\n            $this->smd_prefix = $prefx;\r\n            $this->smd_strings = $strarray;\r\n            $this->smd_lang = $lng;\r\n            $this->smd_event = $ev;\r\n            register_callback(array(&$this, \'smd_Callback\'), \'l10n.enumerate_strings\');\r\n        }\r\n\r\n        function smd_Callback($event = \'l10n.enumerate_strings\', $step = \'\', $pre = 0)\r\n        {\r\n            $r = array(\r\n                \'owner\' => $this->smd_owner,\r\n                \'prefix\' => $this->smd_prefix,\r\n                \'lang\' => $this->smd_lang,\r\n                \'event\' => $this->smd_event,\r\n                \'strings\' => $this->smd_strings,\r\n            );\r\n            return $r;\r\n        }\r\n        // Generic lookup\r\n        //  $what = key to look up\r\n        //  $args = any arguments the key is expecting for replacement\r\n        function gTxt($what, $args = array())\r\n        {\r\n            global $textarray;\r\n\r\n            // Prepare the prefixed key for use\r\n            $key = $this->smd_prefix . \'-\' . $what;\r\n            $key = strtolower($key);\r\n\r\n            // Grab from the global textarray (possibly edited by MLP) if we can\r\n            if(isset($textarray[$key])) {\r\n                $str = $textarray[$key];\r\n            } else {\r\n                // The string isn\'t in the localised textarray so fallback to using\r\n                // the (non prefixed) string array in the plugin\r\n                $key = strtolower($what);\r\n                $str = (isset($this->smd_strings[$key])) ? $this->smd_strings[$key] : $what;\r\n            }\r\n            // Perform substitutions\r\n            if(!empty($args)) {\r\n                $str = strtr($str, $args);\r\n            }\r\n\r\n            return $str;\r\n        }\r\n    }\r\n}\r\n\r\nif (!function_exists(\"smd_doList\")) {\r\n    function smd_doList($lst, $rng = true, $sub = \"\", $qte = true, $dlm = \",\", $lax = true)\r\n    {\r\n        global $thisarticle, $thisimage, $thisfile, $thislink, $pretext, $variable;\r\n\r\n        $inc = $exc = array();\r\n        $lst = do_list($lst, $dlm);\r\n\r\n        // Sometimes pesky Unicode is not compiled in. Detect if so and fall back to ASCII\r\n        if (!@preg_match(\'/\\pL/u\', \'a\')) {\r\n            $modRE = ($lax) ? \'/(\\?|\\!)([A-Za-z0-9_\\- ]+)/\' : \'/(\\?|\\!)([A-Za-z0-9_\\-]+)/\';\r\n        } else {\r\n            $modRE = ($lax) ? \'/(\\?|\\!)([\\p{L}\\p{N}\\p{Pc}\\p{Pd}\\p{Zs}]+)/\' : \'/(\\?|\\!)([\\p{L}\\p{N}\\p{Pc}\\p{Pd}]+)/\';\r\n        }\r\n\r\n        foreach ($lst as $item) {\r\n            $mod = 0; // 0 = include, 1 = exclude\r\n            $numMods = preg_match_all($modRE, $item, $mods);\r\n\r\n            for ($modCtr = 0; $modCtr < $numMods; $modCtr++) {\r\n                // mod \"type\" is governed by the first one found only. i.e. if \"article-?c!s\" was used in one field\r\n                // it would be an \"include\" of the word \"article-\" plus the category and section concatenated\r\n                $mod = ($mods[1][0] === \"!\") ? 1 : 0;\r\n                $modChar = $mods[1][$modCtr];\r\n                $modItem = trim($mods[2][$modCtr]);\r\n                $lowitem = strtolower($modItem);\r\n\r\n                if (isset($variable[$lowitem])) {\r\n                    $item = str_replace($modChar.$modItem, $variable[$lowitem], $item);\r\n                } else if (isset($thisimage[$lowitem])) {\r\n                    $item = str_replace($modChar.$modItem, $thisimage[$lowitem], $item);\r\n                } else if (isset($thisfile[$lowitem])) {\r\n                    $item = str_replace($modChar.$modItem, $thisfile[$lowitem], $item);\r\n                } else if (isset($thislink[$lowitem])) {\r\n                    $item = str_replace($modChar.$modItem, $thislink[$lowitem], $item);\r\n                } else if (array_key_exists($lowitem, $pretext)) {\r\n                    $item = str_replace($modChar.$modItem, $pretext[$lowitem], $item);\r\n                } else if (isset($_POST[$modItem])) {\r\n                    $item = str_replace($modChar.$modItem, $_POST[$modItem], $item);\r\n                } else if (isset($_GET[$modItem])) {\r\n                    $item = str_replace($modChar.$modItem, $_GET[$modItem], $item);\r\n                } else if (isset($_SERVER[$modItem])) {\r\n                    $item = str_replace($modChar.$modItem, $_SERVER[$modItem], $item);\r\n                } else if (isset($thisarticle[$lowitem])) {\r\n                    $item = str_replace($modChar.$modItem, $thisarticle[$lowitem], $item);\r\n                } else {\r\n                    $item = str_replace($modChar.$modItem, $modItem, $item);\r\n                }\r\n            }\r\n\r\n            // Handle ranges of values\r\n            $sitem = do_list($item, $dlm);\r\n\r\n            foreach ($sitem as $idx => $elem) {\r\n                if ($rng && preg_match(\'/^(\\d+)\\-(\\d+)$/\', $elem)) {\r\n                    list($lo, $hi) = explode(\"-\", $elem, 2);\r\n                    $sitem[$idx] = implode($dlm, range($lo, $hi));\r\n                }\r\n            }\r\n\r\n            $item = implode($dlm, $sitem);\r\n\r\n            // Item may be empty; ignore it if so\r\n            if ($item) {\r\n                $item = do_list($item, $dlm);\r\n\r\n                // Handle sub-categories\r\n                if ($sub) {\r\n                    list($subtype, $level) = explode(\":\", $sub);\r\n                    $level = (empty($level)) ? 0 : $level;\r\n                    $level = (strtolower($level)==\"all\") ? 99999 : $level;\r\n                    $outitems = array();\r\n\r\n                    foreach ($item as $cat) {\r\n                        $cats = getTree(doslash($cat), $subtype);\r\n                        foreach ($cats as $jdx => $val) {\r\n                            if ($cats[$jdx][\'level\'] <= $level) {\r\n                                $outitems[] = $cats[$jdx][\'name\'];\r\n                            }\r\n                        }\r\n                    }\r\n                    $item = $outitems;\r\n                }\r\n\r\n                // Quote if asked\r\n                $item = ($qte) ? doArray($item, \'doQuote\') : $item;\r\n\r\n                if ($mod === 0) {\r\n                    $inc = array_unique(array_merge($inc, $item));\r\n                } else {\r\n                    $exc = array_unique(array_merge($exc, $item));\r\n                }\r\n            }\r\n        }\r\n\r\n        return array($inc, $exc);\r\n    }\r\n}\r\n\r\n// Split a string on a pattern and allow integer ranges to be expanded\r\nif (!function_exists(\"smd_split\")) {\r\n    function smd_split($str, $allowRange = true, $splitat = \"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY)\r\n    {\r\n        $retarr = array();\r\n\r\n        if ((substr($splitat,0,1) == \"/\") && (substr($splitat, strlen($splitat)-1, 1) == \"/\")) {\r\n            $pat = $splitat;\r\n        } else {\r\n            $pat = \'/[\'.$splitat.\']+/\';\r\n        }\r\n\r\n        $elems = preg_split($pat, $str, -1, $pregopt);\r\n\r\n        foreach ($elems as $item) {\r\n            $item = trim($item);\r\n            $negate = false;\r\n\r\n            // Does the item start with a negation character\r\n            if (substr($item,0,1) === \"!\") {\r\n                $negate = true;\r\n                $item = substr($item,1);\r\n            }\r\n\r\n            // Is the item an integer list range\r\n            if ($allowRange && preg_match(\'/^(\\d+)\\-(\\d+)$/\', $item)) {\r\n                list($lo, $hi) = explode(\"-\", $item, 2);\r\n                $rng = range($lo, $hi);\r\n\r\n                // Reapply the negation if necessary\r\n                for($idx = 0; $idx < count($rng); $idx++) {\r\n                    $rng[$idx] = (($negate) ? \"!\" : \"\") . $rng[$idx];\r\n                }\r\n\r\n                $retarr = array_merge($retarr, $rng);\r\n            } else {\r\n                $retarr[] = (($negate) ? \"!\" : \"\") . $item;\r\n            }\r\n        }\r\n\r\n        return $retarr;\r\n    }\r\n}\r\n\r\nif (!function_exists(\"smd_doDblQuote\")) {\r\n    function smd_doDblQuote($val)\r\n    {\r\n        return \'\"\'.$val.\'\"\';\r\n    }\r\n}\r\n\r\nif (!function_exists(\"smd_removeQSVar\")) {\r\n    function smd_removeQSVar($url, $key)\r\n    {\r\n        $url = preg_replace(\'/(.*)(\\?|&)\' . $key . \'=[^&]+?(&)(.*)/i\', \'$1$2$4\', $url . \'&\');\r\n        $url = substr($url, 0, -1);\r\n\r\n        return ($url);\r\n    }\r\n}\r\n\r\nif (!function_exists(\"smd_addQSVar\")) {\r\n    function smd_addQSVar($url, $key, $value)\r\n    {\r\n        $url = smd_removeQSVar($url, $key);\r\n\r\n        if (strpos($url, \'?\') === false) {\r\n            return ($url . \'?\' . $key . \'=\' . $value);\r\n        } else {\r\n            return ($url . \'&\' . $key . \'=\' . $value);\r\n        }\r\n    }\r\n}\r\n\r\n// DEPRECATED: for backwards compatibility only\r\nif (!function_exists(\"smd_getSubCats\")) {\r\n    function smd_getSubCats($parent,$cattype)\r\n    {\r\n        return getTree($parent,$cattype); //getTree() or getTreePath()??\r\n    }\r\n}\r\n\r\n// DEPRECATED: for backwards compatibility only\r\nif (!function_exists(\"smd_getOpts\")) {\r\n    function smd_getOpts($str, $allowed, $idprefix = \"\", $allowRange = true, $splitat = \"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY)\r\n    {\r\n        global $pretext, $thisarticle;\r\n\r\n        $out = array();\r\n        $notout = array();\r\n        $matches = smd_split($str, $allowRange, $splitat, $pregopt);\r\n\r\n        // An array that tells the loop what to do with each of the valid strings\r\n        //  arg1: type (1=exact match, 2=anywhere within string, 3=custom field)\r\n        //  arg2: prefix, if any\r\n        //  arg3: variable to substitute\r\n        //  arg4: extra check, if applicable\r\n        //  arg5: store in the in/exclude list (1=include; 2=exclude)\r\n        $opt = array(\r\n            \"?c\" => array(2, \"\", $pretext[\'c\'], \"1\", 1),\r\n            \"!c\" => array(2, \"\", $pretext[\'c\'], \"1\", 2),\r\n            \"?s\" => array(2, \"\", $pretext[\'s\'], \"1\", 1),\r\n            \"!s\" => array(2, \"\", $pretext[\'s\'], \"1\", 2),\r\n            \"?q\" => array(2, \"\", $pretext[\'q\'], \"1\", 1),\r\n            \"!q\" => array(2, \"\", $pretext[\'q\'], \"1\", 2),\r\n            \"?t\" => array(2, \"\", $thisarticle[\'url_title\'], \'$thisarticle!=NULL\', 1),\r\n            \"!t\" => array(2, \"\", $thisarticle[\'url_title\'], \'$thisarticle!=NULL\', 2),\r\n            \"?id\" => array(1, $idprefix, $pretext[\'id\'], \'$thisarticle!=NULL\', 1),\r\n            \"!id\" => array(1, $idprefix, $pretext[\'id\'], \'$thisarticle!=NULL\', 2),\r\n            \"?\" => array(3, \"\", \"\", \'$thisarticle!=NULL\', 1),\r\n            \"!\" => array(3, \"\", \"\", \'$thisarticle!=NULL\', 2),\r\n        );\r\n\r\n        for ($idx = 0; $idx < count($matches); $idx++) {\r\n            $matched = false;\r\n            $thismatch = $matches[$idx];\r\n\r\n            foreach ($opt as $var => $args) {\r\n                $opvar = ($args[4] == 1) ? \"out\" : \"notout\";\r\n\r\n                switch ($args[0]) {\r\n                    case 1:\r\n                        if (($thismatch === $var) && in_array($var,$allowed)) {\r\n                            $matched = true;\r\n\r\n                            if ($args[2] != \"\" && $args[3]) {\r\n                                $rep = str_replace($var, $args[1].$args[2], $thismatch);\r\n\r\n                                if (!in_array($rep, ${$opvar})) {\r\n                                    ${$opvar}[] = $rep;\r\n                                }\r\n                            }\r\n                        }\r\n                        break;\r\n                    case 2:\r\n                        $pat = \'/\\\\\'.$var.\'$|\\\\\'.$var.\'[^A-Za-z0-9]/\';\r\n\r\n                        if ((is_int(smd_pregPos($pat, $thismatch, $fnd))) && in_array($var,$allowed)) {\r\n                            $matched = true;\r\n\r\n                            if ($args[2] != \"\" && $args[3]) {\r\n                                $rep = str_replace($var, $args[1].$args[2], $thismatch);\r\n\r\n                                if (!in_array($rep, ${$opvar})) {\r\n                                    ${$opvar}[] = $rep;\r\n                                }\r\n                            }\r\n                        }\r\n                        break;\r\n                    case 3:\r\n                        $len = strlen($var);\r\n\r\n                        if ((substr($thismatch,0,$len) === $var) && in_array($var.\"field\",$allowed)) {\r\n                            $matched = true;\r\n                            // Use the given field name; which may be a comma-separated sublist.\r\n                            // Split off the field name from the question mark\r\n                            $fieldname = substr($thismatch,$len);\r\n\r\n                            if (($args[3]) && (isset($thisarticle[strtolower($fieldname)]))) {\r\n                                $fieldContents = $thisarticle[strtolower($fieldname)];\r\n                            } else {\r\n                                $fieldContents = $fieldname;\r\n                            }\r\n\r\n                            if (!empty($fieldContents)) {\r\n                                $subout = smd_split(strip_tags($fieldContents), $allowRange, $splitat, $pregopt);\r\n\r\n                                foreach ($subout as $subname) {\r\n                                    if (!in_array($subname, ${$opvar})) {\r\n                                        ${$opvar}[] = $subname;\r\n                                    }\r\n                                }\r\n                            }\r\n                        }\r\n                        break;\r\n                }\r\n\r\n                if ($matched) {\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (!$matched) {\r\n                // Assign the variable verbatim\r\n                if (!in_array($thismatch, $out)) {\r\n                    $out[] = $thismatch;\r\n                }\r\n            }\r\n        }\r\n\r\n        return array($out,$notout);\r\n    }\r\n}\r\n\r\n// Stolen from php.net: strpos page comments...\r\nif (!function_exists(\"smd_pregPos\")) {\r\nfunction smd_pregPos($sPattern, $sSubject, &$FoundString, $iOffset = 0)\r\n{\r\n    $FoundString = null;\r\n\r\n    if (preg_match($sPattern, $sSubject, $aMatches, PREG_OFFSET_CAPTURE, $iOffset) > 0) {\r\n        $FoundString = $aMatches[0][0];\r\n\r\n        return $aMatches[0][1];\r\n    } else {\r\n        return false;\r\n    }\r\n}\r\n}\r\n\r\n//... and array_combine...\r\nif (!function_exists(\"array_combine\")) {\r\n    function array_combine($arr1,$arr2)\r\n    {\r\n        $out = array();\r\n\r\n        foreach($arr1 as $key1 => $value1) {\r\n            $out[$value1] = $arr2[$key1];\r\n        }\r\n\r\n        return $out;\r\n    }\r\n}\r\n\r\n//... and htmlspecialchars_decode\r\nif (!function_exists(\"htmlspecialchars_decode\")) {\r\n    function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)\r\n    {\r\n        return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));\r\n    }\r\n}', '// Software engineers look away now unless you wish to be horrified by the amount\n// of coupling and cohesion in these functions. I\'m warning you... it\'s not pretty\nif (!class_exists(\'smd_MLP\')) {\n    class smd_MLP\n    {\n        var $smd_strings;\n        var $smd_owner;\n        var $smd_prefix;\n        var $smd_lang;\n        var $smd_event;\n\n        function __construct($plug, $prefx, $strarray, $lng = \'en-gb\', $ev = \'public\')\n        {\n            $this->smd_owner = $plug;\n            $this->smd_prefix = $prefx;\n            $this->smd_strings = $strarray;\n            $this->smd_lang = $lng;\n            $this->smd_event = $ev;\n            register_callback(array(&$this, \'smd_Callback\'), \'l10n.enumerate_strings\');\n        }\n\n        function smd_Callback($event = \'l10n.enumerate_strings\', $step = \'\', $pre = 0)\n        {\n            $r = array(\n                \'owner\' => $this->smd_owner,\n                \'prefix\' => $this->smd_prefix,\n                \'lang\' => $this->smd_lang,\n                \'event\' => $this->smd_event,\n                \'strings\' => $this->smd_strings,\n            );\n            return $r;\n        }\n        // Generic lookup\n        //  $what = key to look up\n        //  $args = any arguments the key is expecting for replacement\n        function gTxt($what, $args = array())\n        {\n            global $textarray;\n\n            // Prepare the prefixed key for use\n            $key = $this->smd_prefix . \'-\' . $what;\n            $key = strtolower($key);\n\n            // Grab from the global textarray (possibly edited by MLP) if we can\n            if(isset($textarray[$key])) {\n                $str = $textarray[$key];\n            } else {\n                // The string isn\'t in the localised textarray so fallback to using\n                // the (non prefixed) string array in the plugin\n                $key = strtolower($what);\n                $str = (isset($this->smd_strings[$key])) ? $this->smd_strings[$key] : $what;\n            }\n            // Perform substitutions\n            if(!empty($args)) {\n                $str = strtr($str, $args);\n            }\n\n            return $str;\n        }\n    }\n}\n\nif (!function_exists(\"smd_doList\")) {\n    function smd_doList($lst, $rng = true, $sub = \"\", $qte = true, $dlm = \",\", $lax = true)\n    {\n        global $thisarticle, $thisimage, $thisfile, $thislink, $pretext, $variable;\n\n        $inc = $exc = array();\n        $lst = do_list($lst, $dlm);\n\n        // Sometimes pesky Unicode is not compiled in. Detect if so and fall back to ASCII\n        if (!@preg_match(\'/\\pL/u\', \'a\')) {\n            $modRE = ($lax) ? \'/(\\?|\\!)([A-Za-z0-9_\\- ]+)/\' : \'/(\\?|\\!)([A-Za-z0-9_\\-]+)/\';\n        } else {\n            $modRE = ($lax) ? \'/(\\?|\\!)([\\p{L}\\p{N}\\p{Pc}\\p{Pd}\\p{Zs}]+)/\' : \'/(\\?|\\!)([\\p{L}\\p{N}\\p{Pc}\\p{Pd}]+)/\';\n        }\n\n        foreach ($lst as $item) {\n            $mod = 0; // 0 = include, 1 = exclude\n            $numMods = preg_match_all($modRE, $item, $mods);\n\n            for ($modCtr = 0; $modCtr < $numMods; $modCtr++) {\n                // mod \"type\" is governed by the first one found only. i.e. if \"article-?c!s\" was used in one field\n                // it would be an \"include\" of the word \"article-\" plus the category and section concatenated\n                $mod = ($mods[1][0] === \"!\") ? 1 : 0;\n                $modChar = $mods[1][$modCtr];\n                $modItem = trim($mods[2][$modCtr]);\n                $lowitem = strtolower($modItem);\n\n                if (isset($variable[$lowitem])) {\n                    $item = str_replace($modChar.$modItem, $variable[$lowitem], $item);\n                } else if (isset($thisimage[$lowitem])) {\n                    $item = str_replace($modChar.$modItem, $thisimage[$lowitem], $item);\n                } else if (isset($thisfile[$lowitem])) {\n                    $item = str_replace($modChar.$modItem, $thisfile[$lowitem], $item);\n                } else if (isset($thislink[$lowitem])) {\n                    $item = str_replace($modChar.$modItem, $thislink[$lowitem], $item);\n                } else if (array_key_exists($lowitem, $pretext)) {\n                    $item = str_replace($modChar.$modItem, $pretext[$lowitem], $item);\n                } else if (isset($_POST[$modItem])) {\n                    $item = str_replace($modChar.$modItem, $_POST[$modItem], $item);\n                } else if (isset($_GET[$modItem])) {\n                    $item = str_replace($modChar.$modItem, $_GET[$modItem], $item);\n                } else if (isset($_SERVER[$modItem])) {\n                    $item = str_replace($modChar.$modItem, $_SERVER[$modItem], $item);\n                } else if (isset($thisarticle[$lowitem])) {\n                    $item = str_replace($modChar.$modItem, $thisarticle[$lowitem], $item);\n                } else {\n                    $item = str_replace($modChar.$modItem, $modItem, $item);\n                }\n            }\n\n            // Handle ranges of values\n            $sitem = do_list($item, $dlm);\n\n            foreach ($sitem as $idx => $elem) {\n                if ($rng && preg_match(\'/^(\\d+)\\-(\\d+)$/\', $elem)) {\n                    list($lo, $hi) = explode(\"-\", $elem, 2);\n                    $sitem[$idx] = implode($dlm, range($lo, $hi));\n                }\n            }\n\n            $item = implode($dlm, $sitem);\n\n            // Item may be empty; ignore it if so\n            if ($item) {\n                $item = do_list($item, $dlm);\n\n                // Handle sub-categories\n                if ($sub) {\n                    list($subtype, $level) = explode(\":\", $sub);\n                    $level = (empty($level)) ? 0 : $level;\n                    $level = (strtolower($level)==\"all\") ? 99999 : $level;\n                    $outitems = array();\n\n                    foreach ($item as $cat) {\n                        $cats = getTree(doslash($cat), $subtype);\n                        foreach ($cats as $jdx => $val) {\n                            if ($cats[$jdx][\'level\'] <= $level) {\n                                $outitems[] = $cats[$jdx][\'name\'];\n                            }\n                        }\n                    }\n                    $item = $outitems;\n                }\n\n                // Quote if asked\n                $item = ($qte) ? doArray($item, \'doQuote\') : $item;\n\n                if ($mod === 0) {\n                    $inc = array_unique(array_merge($inc, $item));\n                } else {\n                    $exc = array_unique(array_merge($exc, $item));\n                }\n            }\n        }\n\n        return array($inc, $exc);\n    }\n}\n\n// Split a string on a pattern and allow integer ranges to be expanded\nif (!function_exists(\"smd_split\")) {\n    function smd_split($str, $allowRange = true, $splitat = \"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY)\n    {\n        $retarr = array();\n\n        if ((substr($splitat,0,1) == \"/\") && (substr($splitat, strlen($splitat)-1, 1) == \"/\")) {\n            $pat = $splitat;\n        } else {\n            $pat = \'/[\'.$splitat.\']+/\';\n        }\n\n        $elems = preg_split($pat, $str, -1, $pregopt);\n\n        foreach ($elems as $item) {\n            $item = trim($item);\n            $negate = false;\n\n            // Does the item start with a negation character\n            if (substr($item,0,1) === \"!\") {\n                $negate = true;\n                $item = substr($item,1);\n            }\n\n            // Is the item an integer list range\n            if ($allowRange && preg_match(\'/^(\\d+)\\-(\\d+)$/\', $item)) {\n                list($lo, $hi) = explode(\"-\", $item, 2);\n                $rng = range($lo, $hi);\n\n                // Reapply the negation if necessary\n                for($idx = 0; $idx < count($rng); $idx++) {\n                    $rng[$idx] = (($negate) ? \"!\" : \"\") . $rng[$idx];\n                }\n\n                $retarr = array_merge($retarr, $rng);\n            } else {\n                $retarr[] = (($negate) ? \"!\" : \"\") . $item;\n            }\n        }\n\n        return $retarr;\n    }\n}\n\nif (!function_exists(\"smd_doDblQuote\")) {\n    function smd_doDblQuote($val)\n    {\n        return \'\"\'.$val.\'\"\';\n    }\n}\n\nif (!function_exists(\"smd_removeQSVar\")) {\n    function smd_removeQSVar($url, $key)\n    {\n        $url = preg_replace(\'/(.*)(\\?|&)\' . $key . \'=[^&]+?(&)(.*)/i\', \'$1$2$4\', $url . \'&\');\n        $url = substr($url, 0, -1);\n\n        return ($url);\n    }\n}\n\nif (!function_exists(\"smd_addQSVar\")) {\n    function smd_addQSVar($url, $key, $value)\n    {\n        $url = smd_removeQSVar($url, $key);\n\n        if (strpos($url, \'?\') === false) {\n            return ($url . \'?\' . $key . \'=\' . $value);\n        } else {\n            return ($url . \'&\' . $key . \'=\' . $value);\n        }\n    }\n}\n\n// DEPRECATED: for backwards compatibility only\nif (!function_exists(\"smd_getSubCats\")) {\n    function smd_getSubCats($parent,$cattype)\n    {\n        return getTree($parent,$cattype); //getTree() or getTreePath()??\n    }\n}\n\n// DEPRECATED: for backwards compatibility only\nif (!function_exists(\"smd_getOpts\")) {\n    function smd_getOpts($str, $allowed, $idprefix = \"\", $allowRange = true, $splitat = \"/(,|,\\s)+/\", $pregopt = PREG_SPLIT_NO_EMPTY)\n    {\n        global $pretext, $thisarticle;\n\n        $out = array();\n        $notout = array();\n        $matches = smd_split($str, $allowRange, $splitat, $pregopt);\n\n        // An array that tells the loop what to do with each of the valid strings\n        //  arg1: type (1=exact match, 2=anywhere within string, 3=custom field)\n        //  arg2: prefix, if any\n        //  arg3: variable to substitute\n        //  arg4: extra check, if applicable\n        //  arg5: store in the in/exclude list (1=include; 2=exclude)\n        $opt = array(\n            \"?c\" => array(2, \"\", $pretext[\'c\'], \"1\", 1),\n            \"!c\" => array(2, \"\", $pretext[\'c\'], \"1\", 2),\n            \"?s\" => array(2, \"\", $pretext[\'s\'], \"1\", 1),\n            \"!s\" => array(2, \"\", $pretext[\'s\'], \"1\", 2),\n            \"?q\" => array(2, \"\", $pretext[\'q\'], \"1\", 1),\n            \"!q\" => array(2, \"\", $pretext[\'q\'], \"1\", 2),\n            \"?t\" => array(2, \"\", $thisarticle[\'url_title\'], \'$thisarticle!=NULL\', 1),\n            \"!t\" => array(2, \"\", $thisarticle[\'url_title\'], \'$thisarticle!=NULL\', 2),\n            \"?id\" => array(1, $idprefix, $pretext[\'id\'], \'$thisarticle!=NULL\', 1),\n            \"!id\" => array(1, $idprefix, $pretext[\'id\'], \'$thisarticle!=NULL\', 2),\n            \"?\" => array(3, \"\", \"\", \'$thisarticle!=NULL\', 1),\n            \"!\" => array(3, \"\", \"\", \'$thisarticle!=NULL\', 2),\n        );\n\n        for ($idx = 0; $idx < count($matches); $idx++) {\n            $matched = false;\n            $thismatch = $matches[$idx];\n\n            foreach ($opt as $var => $args) {\n                $opvar = ($args[4] == 1) ? \"out\" : \"notout\";\n\n                switch ($args[0]) {\n                    case 1:\n                        if (($thismatch === $var) && in_array($var,$allowed)) {\n                            $matched = true;\n\n                            if ($args[2] != \"\" && $args[3]) {\n                                $rep = str_replace($var, $args[1].$args[2], $thismatch);\n\n                                if (!in_array($rep, ${$opvar})) {\n                                    ${$opvar}[] = $rep;\n                                }\n                            }\n                        }\n                        break;\n                    case 2:\n                        $pat = \'/\\\\\'.$var.\'$|\\\\\'.$var.\'[^A-Za-z0-9]/\';\n\n                        if ((is_int(smd_pregPos($pat, $thismatch, $fnd))) && in_array($var,$allowed)) {\n                            $matched = true;\n\n                            if ($args[2] != \"\" && $args[3]) {\n                                $rep = str_replace($var, $args[1].$args[2], $thismatch);\n\n                                if (!in_array($rep, ${$opvar})) {\n                                    ${$opvar}[] = $rep;\n                                }\n                            }\n                        }\n                        break;\n                    case 3:\n                        $len = strlen($var);\n\n                        if ((substr($thismatch,0,$len) === $var) && in_array($var.\"field\",$allowed)) {\n                            $matched = true;\n                            // Use the given field name; which may be a comma-separated sublist.\n                            // Split off the field name from the question mark\n                            $fieldname = substr($thismatch,$len);\n\n                            if (($args[3]) && (isset($thisarticle[strtolower($fieldname)]))) {\n                                $fieldContents = $thisarticle[strtolower($fieldname)];\n                            } else {\n                                $fieldContents = $fieldname;\n                            }\n\n                            if (!empty($fieldContents)) {\n                                $subout = smd_split(strip_tags($fieldContents), $allowRange, $splitat, $pregopt);\n\n                                foreach ($subout as $subname) {\n                                    if (!in_array($subname, ${$opvar})) {\n                                        ${$opvar}[] = $subname;\n                                    }\n                                }\n                            }\n                        }\n                        break;\n                }\n\n                if ($matched) {\n                    break;\n                }\n            }\n\n            if (!$matched) {\n                // Assign the variable verbatim\n                if (!in_array($thismatch, $out)) {\n                    $out[] = $thismatch;\n                }\n            }\n        }\n\n        return array($out,$notout);\n    }\n}\n\n// Stolen from php.net: strpos page comments...\nif (!function_exists(\"smd_pregPos\")) {\nfunction smd_pregPos($sPattern, $sSubject, &$FoundString, $iOffset = 0)\n{\n    $FoundString = null;\n\n    if (preg_match($sPattern, $sSubject, $aMatches, PREG_OFFSET_CAPTURE, $iOffset) > 0) {\n        $FoundString = $aMatches[0][0];\n\n        return $aMatches[0][1];\n    } else {\n        return false;\n    }\n}\n}\n\n//... and array_combine...\nif (!function_exists(\"array_combine\")) {\n    function array_combine($arr1,$arr2)\n    {\n        $out = array();\n\n        foreach($arr1 as $key1 => $value1) {\n            $out[$value1] = $arr2[$key1];\n        }\n\n        return $out;\n    }\n}\n\n//... and htmlspecialchars_decode\nif (!function_exists(\"htmlspecialchars_decode\")) {\n    function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)\n    {\n        return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));\n    }\n}', '12214ccd3798bc1ee0ba63d189744ff3', '', '', 2, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_calendar', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.56', 'Calendar / event / schedule system with events as Textpattern articles', '<h1>smd_calendar</h1>\n\n<p>Render a calendar with one or more articles as events on each day. Useful for gig guides, “what&#8217;s on” or scheduling apps.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Full-size / mini <a href=\"https://stefdawson.com/#smdcal\" rel=\"nofollow\">calendar</a> by month, with <span class=\"caps\">ISO</span> week support</li>\n	<li>Nav: next/prev month or month/year dropdown. Year range can be from first/last article or +/- N years</li>\n	<li>One article = one event, native to <span class=\"caps\">TXP</span>: posted date = the date it appears in the calendar (overrideable)</li>\n	<li>Filter events by cat / section / author / status / time / expiry</li>\n	<li>Specify event frequency in custom field (1 week / 10 days / 3 months / etc)</li>\n	<li>Custom fields for in/exclusions (dates on which an event is (re)scheduled/cancelled/omitted)</li>\n	<li>Multi-day spanned events based on article expiry</li>\n	<li>Display future, expired or sticky events</li>\n	<li>Holidays per-calendar</li>\n	<li>Pass each event to a form/container. Spanned and/or recurring events can be sent to a separate form. Cell format also customisable</li>\n	<li><a href=\"https://stefdawson.com/#ifcal\" rel=\"nofollow\">Conditional</a> tests for flags and dates so you can build your own logic</li>\n	<li>Table-, row-, cell-, and event-level classes for indicating different scenarios</li>\n	<li>Tags to <a href=\"https://stefdawson.com/#artev\" rel=\"nofollow\">display recurring events</a>; event/calendar <a href=\"https://stefdawson.com/#calinfo\" rel=\"nofollow\">characteristics</a>; or the <a href=\"https://stefdawson.com/#calnow\" rel=\"nofollow\">current date/time</a></li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p>Requires Textpattern 4.4.1+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1052/smd_calendar\" rel=\"nofollow\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\" rel=\"nofollow\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin-&gt;Plugins pane, install and enable the plugin. Create any needed <a href=\"https://stefdawson.com/#fldatts\" rel=\"nofollow\">custom fields</a>. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=29375\" rel=\"nofollow\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>To uninstall, delete from the Admin-&gt;Plugins page.</p>\n\n<h2>Naming convention</h2>\n\n<p>The core of the plugin is the smd_calendar tag, which renders a standard calendar. Each cell contains one or more events that occur on that day. Before diving into the tags here are the basics:</p>\n\n<ul>\n	<li>*calid% : you may put more than one calendar on a page: each can be uniquely referenced with a calendar ID so they may be controlled independently</li>\n	<li>*event% : an article. Any article written in the given section(s) will appear on the calendar as long as its expiry hasn&#8217;t been met. More than one event may appear in each cell</li>\n	<li>*event flag% : events can either be <a href=\"https://stefdawson.com/#events\" rel=\"nofollow\">standard</a>, they can <a href=\"https://stefdawson.com/#recur\" rel=\"nofollow\">recur</a>, they can span <a href=\"https://stefdawson.com/#multi\" rel=\"nofollow\">multiple</a> days, dates can be <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">unscheduled or omitted</a> from the calendar, or <a href=\"https://stefdawson.com/#extra\" rel=\"nofollow\">extra</a> dates can be added</li>\n	<li>*cell flag% : a series of flags are used to label each cell according to what it contains. Cells can either be empty (i.e. no date: the filler cells at the start/end of the month), they can be a regular day (no flag), they can contain an event, or may be a <a href=\"https://stefdawson.com/#holidays\" rel=\"nofollow\">holiday</a></li>\n</ul>\n\n<p>Flags provide information <em>about</em> the cell / event  &#8212; and can be tested with the conditional tag &#8212; while the corresponding class names are just there for your <span class=\"caps\">CSS</span> use.</p>\n\n<p>When assigned as classes, flags <strong>always</strong> take the ‘class’ prefix (i.e. the 1st item given in smd_calendar&#8217;s <code>classprefixes</code> attribute) &#8212; whether they appear at the <em>event</em> or <em>cell</em> levels. The only classes that take the optional 2nd ‘event’ prefix are the fields you specify in the <code>eventclasses</code> attribute.</p>\n\n<h2>Standard cells and events</h2>\n\n<p>The following cumulative naming rules apply:</p>\n\n<ul>\n	<li>Normal (day) cells don&#8217;t have a class assigned to them unless you specify one with <code>cellclass</code></li>\n	<li>Any cell that contains an event (of any type) is flagged as an <code>event</code></li>\n	<li>Any cell that falls on a holiday is given the flag <code>hols</code></li>\n	<li>The current day is given the flag <code>today</code></li>\n</ul>\n\n<p>A single event (aka article) with a Posted date will be flagged <code>standard</code>, unless it recurs or spans more than one day.</p>\n\n<h2>Recurring events</h2>\n\n<p>If you have nominated a field as your <code>stepfield</code> you may enter a comma-separated list of frequencies at which the event is to repeat. The format of the repetition is <strong>number interval</strong>, e.g. <code>4 weeks</code> or <code>10 days</code> or <code>6 months</code>. The plugin will do its best to figure out what you mean. See <a href=\"http://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html\" rel=\"nofollow\">date input formats</a> for more.</p>\n\n<p>A few points:</p>\n\n<ul>\n	<li><code>second tuesday</code> will show an event <em>every</em> 2nd Tuesday from the start date (i.e. fortnightly)</li>\n	<li><code>second tuesday ?month ?year</code> will substitute the current month and year in first, and then calculate the date, resulting in the event only recurring on the 2nd Tuesday of each month. You <strong>must</strong> use both <code>?month</code> and <code>?year</code> if you choose this type of date or your event won&#8217;t appear</li>\n	<li><code>20 ?month</code> will show the event on the 20th of every month</li>\n	<li>things like <code>first thursday</code> or <code>last friday</code> or <code>this tuesday</code> don&#8217;t need the <code>?month</code> or <code>?year</code> because they can <em>only</em> occur monthly, and will only show the event on the given day of the month</li>\n</ul>\n\n<p>If you can find a use for it, you may specify multiple frequencies, for example <code>3 days, 1 week</code>. That means you would see the event on the 1st day, then the 3rd, 6th, 7th, 9th, 12th, 14th, 15th, 18th, 21st, 24th&#8230; days thereafter. Note that the event only occurs once on the 21st day even though the two frequencies clash.</p>\n\n<p>Recur rules:</p>\n\n<ul>\n	<li>The repetition will continue forever<sup class=\"footnote\" id=\"fnrev16087328445c48e2688f7c4-1\"><a href=\"#fn16087328445c48e2688f7c4-1\">1</a></sup> unless you specify an expiry time</li>\n	<li>The very first event of a recurring set will be flagged with <code>recurfirst</code></li>\n	<li>Each following cell that contains a recurring event will be flagged with <code>recur</code></li>\n	<li>Both flags are applied to the event as classes using the class prefix</li>\n	<li>Repeated events may be <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">cancelled or omitted</a> on a per-date basis</li>\n</ul>\n\n<p class=\"footnote\" id=\"fn16087328445c48e2688f7c4-1\"><sup>1</sup> ‘forever’ is limited to 99999 recurrences per event, or 274 years’ worth of daily events. Either <span class=\"caps\">UNIX</span> will run out of dates or <span class=\"caps\">PHP</span> will run out of memory long before you reach this limit :-)</p>\n\n<h2>Multi-day (spanned) events</h2>\n\n<p>Any event that has:</p>\n\n<ul>\n	<li>a start date</li>\n	<li>an expiry date that is a day or more later than the start date, and</li>\n	<li><em>does not</em> have any repetition in its <code>stepfield</code></li>\n</ul>\n\n<p>is flagged as a <code>multi</code>. By default the first event is displayed in full and each ‘continuation’ event is shown only as a right arrow in subsequent cells. Events may span months or years and the plugin figures everything out using flag rules:</p>\n\n<ul>\n	<li>The first cell of a spanned set is <code>multifirst</code></li>\n	<li>The last cell is <code>multilast</code></li>\n	<li>Every other cell is a <code>multi</code> <em>except</em> if the event rolls over into the next month. In that case, the entry on the 1st of the month is a <code>multiprev</code> to indicate it belongs to a previous event</li>\n	<li>If the 1st day of the month is the last day of the event, the <code>multiprev</code> is dropped in favour of <code>multilast</code></li>\n</ul>\n\n<p>Switch off spanning completely with <code>showspanned=&quot;0&quot;</code>. Events that have an expiry then become standard events.</p>\n\n<p>‘Continuation’ cells may be processed with a separate form (<code>spanform</code>). If you choose not to use a <code>spanform</code>, the standard <code>form</code> or container will be used and you will have to distinguish between the different multi flags yourself using the <a href=\"https://stefdawson.com/#ifcal\" rel=\"nofollow\">conditional tag</a>.</p>\n\n<p>You can also <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">cancel or omit</a> days of a spanned event in the same manner as you do with <a href=\"https://stefdawson.com/#recur\" rel=\"nofollow\">recurring events</a>, except you cannot cancel the first day; you should move the event start date and apologise!</p>\n\n<h2>Cancelling and omitting events</h2>\n\n<p>Plans change and you may find that a gig has to be cancelled. Perhaps you advertise a weekly boot fair but the field is waterlogged one week. No problem: nominate a <code>skipfield</code> in your <code>smd_calendar</code> tag and enter the date of the cancelled event.</p>\n\n<p>Or you might run a theatre web site that has a three-week production performance on weeknights only. Instead of setting up three separate events &#8212; one for each week &#8212; nominate an <code>omitfield</code> and list the dates on which the performance does not air. Omitted dates will not, under any circumstances, appear on the calendar and they override cancelled dates/holidays. If <code>cellplus</code> mode is used, you will however see a cell flag labelled <code>omit</code> (plus class prefix) in case you do wish to style such cells.</p>\n\n<p>To specify the dates in either field, use any acceptable date format, but derivatives of dd-monthname-yyyy or monthname-dd-yyyy are the most unambiguous to avoid problems (e.g. is <code>1/5</code> Jan 5th or May 1st). You can specify as many cancellations or omissions as you like; comma-separate each one. You may also specify ranges of dates to omit/cancel by using the notation <code>start date =&gt; end date</code>.</p>\n\n<p>By default, cancelled events will not appear on the calendar. If you use <code>showskipped=&quot;1&quot;</code> the event will appear in the cell as normal and the <code>cancel</code> flag will apply to the event (and cell if you choose) so you may detect/style it.</p>\n\n<h2>Extra dates</h2>\n\n<p>When events are <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">cancelled</a> you may elect to reschedule the event on a different date instead of having to create a new event with identical details. Use the <code>extrafield</code> to simply add it to the calendar on its new day. You might also use this feature if you have a <a href=\"https://stefdawson.com/#recur\" rel=\"nofollow\">recurring</a> event on the 1st of every month but for some reason you have to move one of the events a day or two. Simply cancel the offending date and add the new date using <code>extrafield</code>.</p>\n\n<p>The list of dates can be in any standard date format (including date ranges using <code>start =&gt; end</code>). They will be added to the calendar and flagged as <code>extra</code>. Also see the <code>extrastrict</code> attribute.</p>\n\n<p>If your original event spans more than one day, you may elect to schedule the entire block again. Add a ‘+’ after any date you wish to repeat in its entirety, e.g. <code>2009-Mar-12+, 2009-Jun-18+, 2009-Feb-19</code> would copy the entire event block to March and June, but only the 1st date to Feb 19th. Events/cells are flagged as both <code>extra</code> and <code>multi</code> where applicable.</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>if you schedule an extra event on a day that already contains the same event (perhaps on a spanned or recurring date) you may see two identical events on the same day. Try and avoid this, unless it&#8217;s your intended behaviour</li>\n	<li>date ranges and the ‘+’ syntax are mutually exlcusive on any date (e.g. <code>2009-Mar-10+ =&gt; 2009-Mar-15</code> is illegal)</li>\n</ul>\n\n<h2>Holidays</h2>\n\n<p>Public holidays need not be a nuisance to your events. Give the plugin a holiday list and it&#8217;ll make sure any recurring or multi events are not scheduled on those days. <a href=\"https://stefdawson.com/#events\" rel=\"nofollow\">Standard</a> one-off events are permitted by default because you might want to organise a special event on that day, though you can forbid those too if you wish.</p>\n\n<p>The list of dates you specify can be entered directly in a string in the <code>holidays</code> attribute or in a <code>&lt;txp:variable /&gt;</code>:</p>\n\n<pre><code>&lt;txp:variable name=&quot;nat_hols&quot;\n     value=&quot;Dec 25, 26 Dec, 31/Dec, Jan-1,\n        May 4 2009, 2009-08-31&quot; /&gt;\n&lt;txp:smd_calendar holidays=&quot;txpvar:nat_hols&quot; /&gt;</code></pre>\n\n<p>That list shows some of the variety and breadth of formats the plugin allows. There are more. Note that the events without a year occur on the same date every year, whereas the ones with a year will only occur on that specific date.</p>\n\n<p>Once your dates are defined you can control which events are allowed to fall on those dates via <code>holidayflags</code>. Combining that attribute, <code>showspanned</code>, <code>showskipped</code> and the forms &#8212; along with the conditional tag &#8212; can give a wide variety of ways to display events and cells.</p>\n\n<p>Holiday cells are given the flag <code>hols</code>, and any event that is not specifically permitted by the <code>holidayflags</code> is automatically assigned a <code>cancel</code> flag if it falls on one of the days. Omitted dates will, however, cause the <code>cancel</code> flag to be removed.</p>\n\n<h2>Tag: <code>&lt;txp:smd_calendar&gt;</code></h2>\n\n<p>Put this tag wherever you want your calendar to appear. Use the following attributes to control its output. The default value is unset unless stated otherwise.</p>\n\n<h3>Display attributes</h3>\n\n<dl>\n	<dt><strong>size</strong></dt>\n	<dd>Calendar size. Options:\n	<dl>\n		<dd>large</dd>\n		<dd>small</dd>\n	</dl></dd>\n	<dd>The small is more geared towards a minical, although functionally there <a href=\"https://stefdawson.com/#sizediff\" rel=\"nofollow\">isn&#8217;t much between them</a></dd>\n	<dd>Default: large</dd>\n	<dt><strong>firstday</strong></dt>\n	<dd>First day of the week to show in the calendar. 0=Sunday, 1=Monday &#8230; 6=Saturday</dd>\n	<dd>Default: 0</dd>\n	<dt><strong>dayformat</strong></dt>\n	<dd>Way in which day names are rendered. Options:\n	<dl>\n		<dd><span class=\"caps\">ABBR</span> shows abbreviated day names; Mon, Tue, Wed, etc</dd>\n		<dd><span class=\"caps\">FULL</span> uses full names</dd>\n		<dd>Any valid <a href=\"http://uk2.php.net/strftime\" rel=\"nofollow\">strftime()</a> codes. Locale-specific names are returned</dd>\n		<dd>A comma-separated list of custom day names surrounded by <code>{}</code> brackets</dd>\n	</dl></dd>\n	<dd>Example, for two-letter German weekdays: <code>dayformat=&quot;{So,Mo,Di,Mi,Do,Fr,Sa}&quot;</code>. The first day in the list <strong>must</strong> represent Sunday or things will break</dd>\n	<dd>Default: <span class=\"caps\">ABBR</span></dd>\n	<dt><strong>monthformat</strong></dt>\n	<dd>Way in which the month names are rendered. Options:\n	<dl>\n		<dd><span class=\"caps\">FULL</span> shows full month names</dd>\n		<dd><span class=\"caps\">ABBR</span> uses abbreviated names</dd>\n		<dd>Any valid strftime() codes. Locale-specific names are returned</dd>\n		<dd>A comma-separated list of custom month names surrounded by <code>{}</code> brackets</dd>\n	</dl></dd>\n	<dd>Example, for single-letter month names: <code>monthformat=&quot;{J,F,M,A,M,J,J,A,S,O,N,D}&quot;</code>. The first month in the list <strong>must</strong> represent January or things will break</dd>\n	<dd>Default: <span class=\"caps\">FULL</span></dd>\n	<dt><strong>select</strong></dt>\n	<dd>Use a select dropdown for rapid access to weeks, months or years instead of fixed names. Choose one or more of:\n	<dl>\n		<dd>week</dd>\n		<dd>month</dd>\n		<dd>year</dd>\n	</dl></dd>\n	<dd>Note: week and month are mutually exclusive</dd>\n	<dd>You may also specify up to two extra arguments, separated by <code>:</code> chars. These add text in front of and behind the week/month/year, respectively</dd>\n	<dd>Example: <code>select=&quot;week:WEEK#, year:&lt;:&gt;&quot;</code> displays a select list with entries like this: <code>WEEK#15 &lt;2009&gt;</code></dd>\n	<dt><strong>selectbtn</strong></dt>\n	<dd>Add a dedicated submit button to week/month/year select lists. Specify the text you wish to appear on the button. It will have the <span class=\"caps\">CSS</span> class name <code>smd_cal_input</code></dd>\n	<dd>Default: unset (i.e. auto-submit on select list change)</dd>\n	<dt><strong>isoweeks</strong></dt>\n	<dd>Show <span class=\"caps\">ISO</span> week numbers as a column at the start of the calendar. Any text in this attribute enables the feature, and becomes the heading of the <span class=\"caps\">ISO</span> week column</dd>\n	<dd>You may change the default week number in each cell by adding a comma and some text; whatever you enter will be put in each <span class=\"caps\">ISO</span> week cell. Use the following replacement codes in your markup to insert the relevant info:\n	<dl>\n		<dd>{week}</dd>\n		<dd>{month}</dd>\n		<dd>{year}</dd>\n		<dd>{isoyear}</dd>\n	</dl></dd>\n	<dd>Example: <code>isoweeks=&quot;wk, #{week}&quot;</code> will put ‘wk’ at the top of the column and something like <code>#24</code>, <code>#25</code>, <code>#26</code>&#8230; beneath it. If the first item is omitted, there will be no column heading</dd>\n	<dd>Note that if this feature is enabled, <code>firstday</code> will be forced to start on a Monday as governed by the <span class=\"caps\">ISO</span> specification</dd>\n	<dt><strong>navarrow</strong></dt>\n	<dd>Comma-separated pair of items you want to appear as prev/next arrows in the calendar</dd>\n	<dd>Default: <code>&amp;#60;, &amp;#62;</code></dd>\n	<dt><strong>caption</strong></dt>\n	<dd>Add a caption to the calendar</dd>\n	<dt><strong>summary</strong></dt>\n	<dd>Add a summary to the calendar</dd>\n</dl>\n\n<h3>Filter attributes</h3>\n\n<dl>\n	<dt><strong>time</strong></dt>\n	<dd>Which events to display. Options:\n	<dl>\n		<dd>any</dd>\n		<dd>past</dd>\n		<dd>future</dd>\n		<dd>today</dd>\n	</dl></dd>\n	<dd>Default: any</dd>\n	<dt><strong>expired</strong></dt>\n	<dd>Hide or show expired events. Options:\n	<dl>\n		<dd>unset (use the <span class=\"caps\">TXP</span> Preference ‘Publish expired articles’)</dd>\n		<dd>0 (hide)</dd>\n		<dd>1 (show)</dd>\n	</dl></dd>\n	<dt><strong>status</strong></dt>\n	<dd>Events in this status list are published on the calendar. Options:\n	<dl>\n		<dd>live</dd>\n		<dd>sticky</dd>\n	</dl></dd>\n	<dd>Default: live</dd>\n	<dt><strong>category</strong></dt>\n	<dd>Filter events by this list of categories. Use <code>SMD_UNCAT</code> for uncategorised events</dd>\n	<dt><strong>subcats</strong></dt>\n	<dd>Consider sub-categories. Choose a numeric nesting level to consider, or the word <code>all</code></dd>\n	<dt><strong>section</strong></dt>\n	<dd>Filter events by this list of sections</dd>\n	<dt><strong>author</strong></dt>\n	<dd>Filter events by this list of author login names</dd>\n	<dt><strong>realname</strong></dt>\n	<dd>Filter events by this list of author Real Names (note: may add one extra query)</dd>\n	<dt><strong>showall</strong></dt>\n	<dd>If your calendar appears on the front page of your site and you have <em>not</em> used the <code>section</code> attribute, then:\n	<dl>\n		<dd>0: only shows events from sections marked with <code>On front page</code></dd>\n		<dd>1: shows all events from all sections</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>month</strong></dt>\n	<dd>Start the calendar on this month from 1 (Jan) to 12 (Dec). Normal calendar navigation overrides this value</dd>\n	<dd>If <code>static</code> is used, this month becomes the only one you may view</dd>\n	<dd>If unset, and no <code>m=</code> value appears on the <span class=\"caps\">URL</span> line, the current month is used</dd>\n	<dd><strong>week</strong></dd>\n	<dd>Start the calendar during the month containing this <span class=\"caps\">ISO</span> week (from 1 to 53)</dd>\n	<dd>Ignored if <code>static</code> or one of the calendar navigation controls is used</dd>\n	<dd>If a <code>w=</code> value appears on the <span class=\"caps\">URL</span> line, the given week overrides any month value</dd>\n	<dt><strong>year</strong></dt>\n	<dd>Start the calendar at this 4-digit year. Normal calendar navigation overrides this value</dd>\n	<dd>If <code>static</code> is used, this year becomes the only one you may view</dd>\n	<dd>If unset, and no <code>y=</code> value appears on the <span class=\"caps\">URL</span> line, the current year is used</dd>\n	<dt><strong>static</strong></dt>\n	<dd>Force the calendar to be fixed to one month/year (i.e. no navigation). Month and year decided by attributes <code>month</code> and <code>year</code> or, if omitted, the current date will be used</dd>\n</dl>\n\n<h3>Form attributes</h3>\n\n<dl>\n	<dt><strong>form</strong></dt>\n	<dd>Use the given <span class=\"caps\">TXP</span> form to process each event</dd>\n	<dd>If the smd_calendar tag is used as a container it will be used in preference to the form</dd>\n	<dd>If neither are used, a default is used (Large: hyperlinked article title, Small: empty event)</dd>\n	<dt><strong>spanform</strong></dt>\n	<dd>Display spanned events differently to standard events; they usually use the same <code>form</code>/container</dd>\n	<dd>If neither are specified, a right-arrow will be used to indicate continuation of the previous day&#8217;s event</dd>\n	<dd>Note: the first day of a spanned set is <em>not</em> passed to the <code>spanform</code>; only continuation cells are passed to it</dd>\n	<dt><strong>recurform</strong></dt>\n	<dd>Display recurring events differently to standard events</dd>\n	<dd>Note: the first event of a recurring set is <em>not</em> passed to <code>recurform</code></dd>\n	<dt><strong>cellform</strong> (<span>only on large calendars</span>)</dt>\n	<dd>Use if you wish to build each cell entirely from scratch</dd>\n	<dd>There are some <a href=\"https://stefdawson.com/#cellform\" rel=\"nofollow\">replacement variables</a> you can use to insert dynamic pieces of information in your cells</dd>\n	<dd>Note: you cannot use <span class=\"caps\">TXP</span> article or plugin tags in the Form</dd>\n	<dt><strong>headerform</strong></dt>\n	<dd>Use if you wish to build the header yourself</dd>\n	<dd>There are some <a href=\"https://stefdawson.com/#hdrform\" rel=\"nofollow\">replacement variables</a> you can use to insert dynamic pieces of information in your header</dd>\n</dl>\n\n<h3>Field-based attributes (custom fields or other article fields)</h3>\n\n<dl>\n	<dt><strong>stepfield</strong></dt>\n	<dd>ID of a field within which an event may be told to repeat</dd>\n	<dd>Note: it is the field&#8217;s ID <em>not its name</em>, so for custom fields you must use <code>custom_1</code> or <code>custom_2</code> etc</dd>\n	<dd>Without this attribute, no <a href=\"https://stefdawson.com/#recur\" rel=\"nofollow\">recurring events</a> may be defined</dd>\n	<dt><strong>omitfield</strong></dt>\n	<dd>ID (not name) of a field that contains a list of dates on which this event is to be <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">omitted</a></dd>\n	<dt><strong>skipfield</strong></dt>\n	<dd>ID (not name) of a field that contains a list of dates on which this event is <a href=\"https://stefdawson.com/#cancel\" rel=\"nofollow\">cancelled</a></dd>\n	<dt><strong>extrafield</strong></dt>\n	<dd>ID (not name) of a field from which a list of <a href=\"https://stefdawson.com/#extra\" rel=\"nofollow\">additional event dates</a> may be given; the same event details will be copied to the new day(s)</dd>\n	<dt><strong>extrastrict</strong></dt>\n	<dd>Control visibility of extra dates on the calendar. Options:\n	<dl>\n		<dd>0: <code>extrafield</code> dates automatically appear on the calendar</dd>\n		<dd>1: restrict new dates from appearing after the event&#8217;s expiry date</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>showskipped</strong></dt>\n	<dd>Control visibility of cancelled events on the calendar. Options:\n	<dl>\n		<dd>0 (hide)</dd>\n		<dd>1 (show)</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>showspanned</strong></dt>\n	<dd>Control visibility of spanned events on the calendar. Options:\n	<dl>\n		<dd>0 (hide)</dd>\n		<dd>1 (show)</dd>\n	</dl></dd>\n	<dd>Default: 1</dd>\n	<dt><strong>datefields</strong></dt>\n	<dd>IDs (not names) of up to two fields from which posted and expiry date stamps may be read. Comma-separate the field names; the first will be used as the Posted date and the second as the Expiry</dd>\n	<dd>If either is omitted or mangled, the article&#8217;s “real” posted/expiry date will be used instead</dd>\n	<dd>If the expiry date occurs before the start date, your datefields will be ignored and a warning will be issued</dd>\n</dl>\n\n<h3>Config attributes</h3>\n\n<dl>\n	<dt><strong>holidays</strong></dt>\n	<dd>List of dates that are decreed as <a href=\"https://stefdawson.com/#holidays\" rel=\"nofollow\">holidays</a></dd>\n	<dd>May be deferred to a <code>&lt;txp:variable /&gt;</code>, in which case define your list in a named variable and use <code>holidays=&quot;txpvar:my_var_name&quot;</code> to read them into the plugin</dd>\n	<dt><strong>holidayflags</strong></dt>\n	<dd>Permit certain event flags to be scheduled on holidays. List one or more of:\n	<dl>\n		<dd>standard</dd>\n		<dd>recur</dd>\n		<dd>multi</dd>\n	</dl></dd>\n	<dd>Default: <code>standard</code></dd>\n	<dt><strong>id</strong></dt>\n	<dd><span class=\"caps\">HTML</span> ID to apply to the table that holds the calendar. This becomes the value of the <span class=\"caps\">URL</span> variable <code>calid</code></dd>\n	<dd>Use this if you have more than one calendar on a page and wish to control them separately via the <span class=\"caps\">URL</span> vars</dd>\n	<dt><strong>navid</strong></dt>\n	<dd><span class=\"caps\">HTML</span> ID to apply to the prev/next/month/year navigation form</dd>\n	<dt><strong>yearwidth</strong></dt>\n	<dd>A comma-separated list that specifies how many years your calendar spans. Visitors will not be permitted to navigate (next/prev) the calendar outside this range. Options:\n	<dl>\n		<dd>0: use the earliest (posted) event as the earliest year, and the latest (modified or posted, whichever is greater) event as the latest year</dd>\n		<dd>any other single number expands the range equally in past &amp; future directions</dd>\n		<dd>any pair of numbers subtracts the first from the earliest event and adds the second number of whole years to the latest event</dd>\n		<dd>adding <code>c</code> to either value causes the current year to be used instead of the earliest or latest event</dd>\n	</dl></dd>\n	<dd>Example: <code>yearwidth=&quot;2,4c&quot;</code> subtracts 2 years from the earliest event and adds 4 whole years to today&#8217;s date</dd>\n	<dd>Default: 0</dd>\n	<dt><strong>remap</strong></dt>\n	<dd>When dealing with multiple calendars on a page it is often beneficial to use different names for <code>w=</code>, <code>m=</code> or <code>y=</code> in the <span class=\"caps\">URL</span> so you can navigate calendars individually. This attribute enables you to rename the w, m, &amp; y variable to any name specified after a colon (:)</dd>\n	<dd>Example: <code>remap=&quot;w:wk, y:yr&quot;</code></dd>\n	<dt><strong>linkposted</strong> (<span>only on small calendars</span>)</dt>\n	<dd>Each cell that contains an event flag of the given type(s) will have its day number linked to the event&#8217;s true start date instead of the cell&#8217;s date. This allows you to always link to a valid article/event</dd>\n	<dd>Note that if more than one event occurs in the cell, the link will only be to the first event the plugin finds</dd>\n	<dd>Example: if you have a weekly event that starts on the 20th December 2008, setting <code>linkposted=&quot;recur&quot;</code> will cause the link to be <code>date=2008-12-20</code> every week. Without linkposted, the dates would be 2008-12-20, 2008-12-27, 2009-01-03, and so on</dd>\n	<dd>Default: <code>recur, multi, multiprev, multilast</code> (i.e. any recurring or spanned event)</dd>\n	<dt><strong>maintain</strong></dt>\n	<dd>Keep track of this comma-separated list of variables in the <span class=\"caps\">URL</span> when navigating the calendar using the next/prev or month/year select lists. If you wish to maintain state yourself or do something exotic, empty this attribute first to avoid weirdness. Options:\n	<dl>\n		<dd>calid</dd>\n		<dd>section</dd>\n		<dd>category</dd>\n		<dd>author</dd>\n		<dd>article</dd>\n		<dd>date</dd>\n		<dd>pg</dd>\n		<dd>any other <span class=\"caps\">URL</span> variable of your choosing</dd>\n	</dl></dd>\n	<dd>Example: use <code>maintain=&quot;section, article, calid&quot;</code> if you have an individual article page with a calendar in a sidebar, so the currently viewed article will remain in view when changing date</dd>\n	<dd>Default: calid</dd>\n	<dt><strong>nameval</strong></dt>\n	<dd>Add your own name/value pairs to the calendar&#8217;s <span class=\"caps\">URL</span>.</dd>\n	<dd>Example: <code>nameval=&quot;tracker=mycal, keep=1&quot;</code> would add <code>?tracker=mycal&amp;keep=1</code> to the <span class=\"caps\">URL</span>. Useful if you want to <code>maintain</code> some values which you can&#8217;t add to the <span class=\"caps\">URL</span> on page load</dd>\n	<dt><strong>event_delim</strong></dt>\n	<dd>Delimiter between each event.</dd>\n	<dd>Default: comma</dd>\n</dl>\n\n<h3>Style attributes</h3>\n\n<dl>\n	<dt><strong>classlevels</strong></dt>\n	<dd>Each flagged event can be given a <span class=\"caps\">CSS</span> class based on its flag name(s). Classes can be applied to events, cells, or both. You may also promote (i.e. copy) all event classes that occur in a day to the cell itself so you can style the cell based on the events it contains. Options:\n	<dl>\n		<dd>event</dd>\n		<dd>cell</dd>\n		<dd>cellplus (for copying unique event classes to the containing cell)</dd>\n	</dl></dd>\n	<dd>Note you should not use <code>cell</code> and <code>cellplus</code> together because the latter overrides the former</dd>\n	<dd>Default: cell, event</dd>\n	<dt><strong>classprefixes</strong></dt>\n	<dd>Comma-separated list of up to two prefixes to apply to your class names. The first prefix is applied to cell-level classes (and flags) and the second prefix is applied to event classes (see <code>eventclasses</code>)</dd>\n	<dd>If you only specify one prefix, it will be used for both. If you use <code>classprefixes=&quot;&quot;</code> then no prefixes will be used at all</dd>\n	<dd>Default: smd_cal_, smd_cal_ev_</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Class name of the calendar table itself</dd>\n	<dd>Default: unset</dd>\n	<dt><strong>rowclass</strong></dt>\n	<dd>Class name of each table row</dd>\n	<dd>Default: unset</dd>\n	<dt><strong>cellclass</strong></dt>\n	<dd>Class name of each table cell</dd>\n	<dd>Default: unset</dd>\n	<dt><strong>emptyclass</strong></dt>\n	<dd>Class name of any cells that don&#8217;t contain a day number (i.e. the blank cells at the start &amp; end of a month)</dd>\n	<dd>Default: empty</dd>\n	<dt><strong>isoweekclass</strong></dt>\n	<dd>Class name of each cell containing an <span class=\"caps\">ISO</span> week</dd>\n	<dd>Default: week</dd>\n	<dt><strong>navclass</strong></dt>\n	<dd>Class name of the prev/next month nav arrows. If a comma-separated list is used, the first item will be the name of the class of the previous month, the 2nd item of the next month. If a single value is used, both class names will be the same</dd>\n	<dd>Default: navprev, navnext</dd>\n	<dt><strong>myclass</strong></dt>\n	<dd>Class name of both the month and year in the calendar header (either the <code>&lt;span&gt;</code> or <code>&lt;select&gt;</code> tags). If <code>mywraptag</code> is used, the class is applied to the wraptag instead</dd>\n	<dd>Default: unset</dd>\n	<dt><strong>eventclasses</strong></dt>\n	<dd>Comma-separated list of items to add as classes to each event. Each are prefixed with the event prefix</dd>\n	<dd>Example: <code>eventclasses=&quot;ID, AuthorID, custom_5&quot;</code> would add three classes to each event corresponding to the event&#8217;s ID, its author (login) name and the contents of custom_5</dd>\n	<dd>If you use <code>cellplus</code>, these classes will be copied to the cell level. Some special names exist: <code>category</code> adds both Category1 and Category2 (if set); <code>gcat</code> will add the current ‘global’ category (if filtering by category); <code>author</code> adds the author ID (if filtering by author); <code>section</code> adds the current section</dd>\n	<dd>Default: category</dd>\n	<dt><strong>eventwraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag, without brackets, to wrap each event with</dd>\n	<dd>Default: span</dd>\n	<dt><strong>mywraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag, without brackets, to wrap around <em>both</em> month + year dropdown select lists and submit button</dd>\n	<dd>Default: unset</dd>\n</dl>\n\n<h3>Using a <code>cellform</code> with replacement variables</h3>\n\n<p>If you don&#8217;t like the layout of the default cell, you can do it yourself with the <code>cellform</code> attribute. The cells are generated <em>last</em>, so by the time the plugin reaches this attribute, all events have already been processed by any of your forms/containers. Thus you can&#8217;t use <span class=\"caps\">TXP</span> article or plugin tags.</p>\n\n<p>To build your own cells you often need information such as the events that fall on a particular day; or the week, month or day numbers, etc. So you may also insert any of the following replacements to have the relevant value inserted among your markup:</p>\n\n<ul>\n	<li>{day} / {dayzeros} : day of the week (1-31 / 01-31)</li>\n	<li>{weekday} : weekday in the local language, or from your <code>dayformat</code> list</li>\n	<li>{weekdayfull} or {weekdayabbr} : weekday in the local language</li>\n	<li>{week} : <span class=\"caps\">ISO</span> week number (01-53)</li>\n	<li>{month} / {monthzeros} : month number (1-12 / 01-12)</li>\n	<li>{monthname} : month name in the local language, or from your <code>monthformat</code> list</li>\n	<li>{monthnamefull} or {monthnameabbr} : month name in the local language</li>\n	<li>{year} : 4-digit year</li>\n	<li>{shortyear} : 2-digit year</li>\n	<li>{isoyear} : 4 digit <span class=\"caps\">ISO</span> year</li>\n	<li>{shortisoyear} : 2 digit <span class=\"caps\">ISO</span> year</li>\n	<li>{evid} : event (article) ID</li>\n	<li>{events} : all events for the day</li>\n	<li>{standard} : only standard events</li>\n	<li>{recurfirst} / {recur} : various recurring events</li>\n	<li>{allrecur} : all recurring events for the day</li>\n	<li>{multifirst} / {multi} / {multiprev} / {multilast} : various multi events</li>\n	<li>{allmulti} : all multi events for the day</li>\n	<li>{cancel} : only cancelled events</li>\n	<li>{extra} : only extra events</li>\n</ul>\n\n<h3>Using a <code>headerform</code></h3>\n\n<p>You can create your own header if you wish and employ any of the following replacements in the markup:</p>\n\n<ul>\n	<li>{firstday} : current weekday (as a number from 0 to 6)</li>\n	<li>{daynames} : comma-separated day names</li>\n	<li>{isoweekhead} : <span class=\"caps\">ISO</span> week heading</li>\n	<li>{week} : <span class=\"caps\">ISO</span> week</li>\n	<li>{month} : month</li>\n	<li>{year} : year</li>\n	<li>{isoyear} : <span class=\"caps\">ISO</span> year</li>\n</ul>\n\n<h3>Differences between large and small calendars</h3>\n\n<ul>\n	<li><code>cellform</code> cannot be used on a small calendar</li>\n	<li>By default, no event descriptions are placed in the small calendar. You can add them yourself if you wish using a form/container</li>\n	<li>The only thing rendered in a small calendar cell is the hyperlinked date and any flags so you can style the boxes</li>\n	<li><code>classlevels</code> are ignored: everything is automatically assigned at the cell level (i.e. <code>cellplus</code> is set)</li>\n	<li><code>eventclasses</code> are still honoured if you wish to use a form to process them yourself</li>\n	<li>The small calendar outputs year-month-day-title or messy permlinks only</li>\n</ul>\n\n<h2>Tag: <code>&lt;txp:smd_if_cal&gt;</code></h2>\n\n<p>This conditional tag allows you &#8212; inside your container/forms  &#8212; to test certain conditions of the current event/cell. For enhanced conditional checking (perhaps in conjunction with <a href=\"https://stefdawson.com/#calnow\" rel=\"nofollow\">smd_cal_now</a>), consider the smd_if plugin. The default value is unset unless stated otherwise.</p>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><strong>flag</strong></dt>\n	<dd>The cell or event flag(s) you want to test, each separated by a comma. List one or more of:\n	<dl>\n		<dd>event</dd>\n		<dd>standard</dd>\n		<dd>recurfirst</dd>\n		<dd>recur</dd>\n		<dd>multifirst</dd>\n		<dd>multi</dd>\n		<dd>multilast</dd>\n		<dd>multiprev</dd>\n		<dd>cancel</dd>\n		<dd>omit</dd>\n		<dd>extra</dd>\n		<dd>hols</dd>\n		<dd>today</dd>\n		<dd><span class=\"caps\">SMD</span>_ANY (will trigger if the cell or event contains any of the above)</dd>\n	</dl></dd>\n	<dt><strong>calid</strong></dt>\n	<dd>The calendar ID you wish to check for a match</dd>\n	<dt><strong>year</strong></dt>\n	<dd>The year the current cell falls in</dd>\n	<dt><strong>isoyear</strong></dt>\n	<dd>The <span class=\"caps\">ISO</span> year the current cell falls in</dd>\n	<dt><strong>month</strong></dt>\n	<dd>The month number (1-12) that the current cell falls in</dd>\n	<dt><strong>week</strong></dt>\n	<dd>The <span class=\"caps\">ISO</span> week number that the current cell falls in</dd>\n	<dt><strong>day</strong></dt>\n	<dd>The day number the current cell falls in</dd>\n	<dt><strong>logic</strong></dt>\n	<dd>Method of combining the nominated tests. Options:\n	<dl>\n		<dd>or: tag will trigger if at least one of the tests is true</dd>\n		<dd>and: tag will only trigger if all the tests are true</dd>\n	</dl></dd>\n	<dd>Default: or</dd>\n</dl>\n\n<p>‘And’ logic is useful for checking if the cell is of a certain type <span class=\"caps\">AND</span> is later than the 15th of the month, for example.</p>\n\n<p>Rudimentary comparators can be applied to the <code>(iso)year</code>, <code>month</code>, <code>week</code> and <code>day</code> attributes. Normally the value you supply will be tested for an exact match but if you prefix it with one of the following character sequences then the behaviour changes:</p>\n\n<ul>\n	<li><code>&gt;</code> tests if attribute is greater than the given value (e.g. <code>year=&quot;&gt;2008&quot;</code>)</li>\n	<li><code>&gt;=</code> tests if attribute is greater than or equal to the given value (e.g. <code>month=&quot;&gt;=7&quot;</code>)</li>\n	<li><code>&lt;</code> tests if attribute is less than the given value</li>\n	<li><code>&lt;=</code> tests if attribute is less than or equal to the given value</li>\n	<li><code>!</code> tests if attribute is <em>not</em> the given value (e.g. <code>day=&quot;!15&quot;</code>)</li>\n</ul>\n\n<h2>Tag: <code>&lt;txp:smd_cal_info&gt;</code></h2>\n\n<p>Inside your smd_calendar container/forms, use this tag to output certain information about the current event.</p>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><strong>type</strong></dt>\n	<dd>Comma-separated list of types of information you want to display. Options:\n	<dl>\n		<dd>flag</dd>\n		<dd>calid</dd>\n		<dd>(iso)year</dd>\n		<dd>month</dd>\n		<dd>week</dd>\n		<dd>day</dd>\n		<dd>s (current section)</dd>\n		<dd>category</dd>\n		<dd>author</dd>\n		<dd>realname</dd>\n		<dd>article (id of the currently viewed article)</dd>\n		<dd>any other article variable such as <code>section</code> (the current article&#8217;s section), <code>authorid</code>, <code>article_image</code>, etc</dd>\n	</dl></dd>\n	<dd>If using the <code>html</code> attribute, you may optionally specify the name you want the variable to appear as in the <span class=\"caps\">URL</span> string. The variables all take on sensible defaults (e.g. ‘section’ becomes <code>?s=&lt;section name&gt;</code>, ‘category1’ becomes <code>?c=&lt;category1 name&gt;</code>, etc).</dd>\n	<dd>Example: <code>&lt;txp:smd_cal_info type=&quot;catgeory:the_cat&quot; html=&quot;1&quot; /&gt;</code> means you would see <code>?the_cat=&lt;category1 name&gt;</code> in the <span class=\"caps\">URL</span></dd>\n	<dd>Default: flag</dd>\n	<dt><strong>join</strong></dt>\n	<dd>The characters you want to use to separate each item you asked for. Note it is the characters <em>between</em> each item so the very first entry will <strong>not</strong> have the <code>join</code> in front of it (see <code>join_prefix</code>)</dd>\n	<dd>Default: a space</dd>\n	<dt><strong>join_prefix</strong></dt>\n	<dd>The string you want to put in front of the first item in the returned list. If you do not specify this attribute it tries to be clever:\n	<dl>\n		<dd>If using <code>type=&quot;flag&quot;</code> the join_prefix is set to the same as <code>join</code>. Thus with <code>join=&quot; cal_&quot;</code> you might get <code> cal_multi cal_today cal_hols</code></dd>\n		<dd>If using <code>html=&quot;1&quot;</code> the join_prefix is set to a question mark, thus: <code>type=&quot;month,year,category&quot; html=&quot;1&quot;</code> might render <code>?m=12&amp;y=2008&amp;c=gigs</code>, which can be put straight on the end of an anchor</dd>\n	</dl></dd>\n	<dd>Default: <code>SMD_AUTO</code></dd>\n	<dt><strong>html</strong></dt>\n	<dd>Control in which format the information is returned. Options:\n	<dl>\n		<dd>0: return items verbatim</dd>\n		<dd>1: return items as a <span class=\"caps\">URL</span> parameter string. This is useful if you are building your own content inside each cell via a <code>form</code> and wish to maintain the current search environment. If you allow people to filter events by category or author you can use this to return the ‘current’ state of certain variables so you can pass them to the next page and maintain state</dd>\n	</dl></dd>\n	<dd>Note: Setting this attribute to 1 overrides the <code>join</code> attribute and sets it to an ampersand</dd>\n	<dd>Default: 0</dd>\n	<dt><strong>escape</strong></dt>\n	<dd>Escape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> for page validation purposes. Use <code>escape=&quot;&quot;</code> to turn this off</dd>\n	<dd>Default: <code>html</code></dd>\n</dl>\n\n<h2>Tag: <code>&lt;txp:smd_cal_class&gt;</code></h2>\n\n<p>Inside your smd_calendat container/forms, use this tag to add a list of classes to the current cell/event. Very useful if building cells yourself because inside a <a href=\"https://stefdawson.com/#ifcal\" rel=\"nofollow\">conditional</a> tag you could add particular class names based on some value in a cell.</p>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><strong>name</strong></dt>\n	<dd>Comma-separated list of classnames to add to the current cell/event. These are <strong>not</strong> subject to any <code>classprefixes</code> so will always appear exactly as you write them</dd>\n	<dd>Default: unset</dd>\n</dl>\n\n<h2>Tag: <code>&lt;txp:smd_cal_now&gt;</code></h2>\n\n<p>Return the current date/time, formatted however you please. Useful for extracting parts of the current system timestamp to compare things via other conditional plugins or the <code>&lt;txp:smd_if_cal&gt;</code> tag.</p>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><strong>format</strong></dt>\n	<dd>The way you want the date/time represented. Use any valid <a href=\"http://uk2.php.net/strftime\" rel=\"nofollow\">strftime()</a> string. Has full Windows support, even for those values where strftime() indicates otherwise</dd>\n	<dd>Default: the date format set in Basic Preferences.</dd>\n	<dt><strong>now</strong></dt>\n	<dd>If you don&#8217;t want the time to be ‘now’ you can state what time ‘now’ is! Use any standard date/time format</dd>\n	<dd>You may also use the codes <code>?day</code>, <code>?month</code> or <code>?year</code> in your time string which will do one of two things:\n	<dl>\n		<dd>replace the codes with the <span class=\"caps\">URL</span> parameters <code>d=</code>, <code>m=</code> or <code>y=</code>, if they are being used</dd>\n		<dd>use the current day, month or year (i.e. the parts of today&#8217;s date)</dd>\n	</dl></dd>\n	<dd>Default: now! (the time at which you call the tag)</dd>\n	<dt><strong>offset</strong></dt>\n	<dd>An offset into the future that you wish to apply to <code>now</code></dd>\n	<dd>Example: <code>2 months</code>. See <a href=\"https://stefdawson.com/#eg6\" rel=\"nofollow\">Example 6</a> for a practical application of this attribute</dd>\n	<dt><strong>gmt</strong></dt>\n	<dd>Return either:\n	<dl>\n		<dd>0: local time according to the time zone set in Basic Prefs</dd>\n		<dd>1: <span class=\"caps\">GMT</span> time</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>lang</strong></dt>\n	<dd>An <span class=\"caps\">ISO</span> language code that formats time strings suitable for the specified language (or locale) as defined by <a href=\"http://en.wikipedia.org/wiki/ISO_639\" rel=\"nofollow\"><span class=\"caps\">ISO</span> 639</a></dd>\n	<dd>Default: unset (i.e. use the value as stated in <span class=\"caps\">TXP</span> prefs)</dd>\n</dl>\n\n<h2>Tag: <code>&lt;txp:smd_article_event&gt;</code></h2>\n\n<p>When you create recurring events, they really only exist once as a single article; the repetition is a trick. Thus the built-in article tags only show the single, real articles.</p>\n\n<p>This tag &#8212; similar in function to <code>&lt;txp:article_custom /&gt;</code> &#8212; allows you to list recurring articles as if they were ‘real’ articles in the database. <span>They don&#8217;t become real articles, they are just listed as such</span>.</p>\n\n<p>Inside the tag&#8217;s <code>form</code> or container you can use all existing article tags to display any information you like about each ‘virtual’ article. The default value is unset unless stated otherwise.</p>\n\n<h3>Identical attributes to <a href=\"https://stefdawson.com/#smdcal\" rel=\"nofollow\">smd_calendar</a>:</h3>\n\n<dl>\n	<dt><strong>stepfield</strong></dt>\n	<dt><strong>skipfield</strong></dt>\n	<dt><strong>omitfield</strong></dt>\n	<dt><strong>extrafield</strong></dt>\n	<dt><strong>datefields</strong></dt>\n	<dt><strong>section</strong></dt>\n	<dt><strong>category</strong></dt>\n	<dt><strong>author</strong></dt>\n	<dt><strong>realname</strong></dt>\n	<dt><strong>status</strong></dt>\n	<dt><strong>time</strong></dt>\n	<dt><strong>expired</strong></dt>\n</dl>\n\n<h3>Other attributes</h3>\n\n<dl>\n	<dt><strong>id</strong></dt>\n	<dd>Restrict events to this list of article IDs</dd>\n	<dt><strong>custom</strong></dt>\n	<dd>Specify your own comma-separated list of custom field clauses to the query. Separate each field from its (optional) operator and clause with a colon (alterable via <code>param_delim</code>).</dd>\n	<dd>Example: <code>custom=&quot;custom_3:like:Lion%, custom_6:Chessington&quot;</code> would add <code>AND custom_3 like \'Lion%\' AND custom_6=\'Chessington\'</code> to the database query.</dd>\n	<dt><strong>param_delim</strong></dt>\n	<dd>Alter the separator character(s) between field-clause items in the <code>custom</code> attribute.</dd>\n	<dd>Default: colon (:)</dd>\n	<dt><strong>type</strong></dt>\n	<dd>Comma-separated list of event types to display. Options:\n	<dl>\n		<dd>standard</dd>\n		<dd>recur</dd>\n		<dd>multi</dd>\n	</dl></dd>\n	<dd>Default: standard, recur, multi</dd>\n	<dt><strong>allspanned</strong></dt>\n	<dd>Control display of spanned events:</dd>\n	<dd>0: any event that has a start date in the past will be omitted from the list</dd>\n	<dd>1: display remaining days from spanned events that began in the past</dd>\n	<dd>Example: use <code>allspanned=&quot;1&quot;</code> if you are listing remaining performance dates from a Broadway show&#8217;s schedule that started some months ago.</dd>\n	<dd>Default: 0</dd>\n	<dt><strong>month</strong></dt>\n	<dd>Only show events that occur in the given <span class=\"caps\">YYYY</span>-mm</dd>\n	<dt><strong>from</strong></dt>\n	<dd>Only show events with Posted dated beginning after this start date. Can be any valid date format</dd>\n	<dt><strong>to</strong></dt>\n	<dd>Only show events with Posted dates up to this end date. Can be any valid date format</dd>\n	<dt><strong>sort</strong></dt>\n	<dd>Order the events by this column (case-sensitive) and sort direction (asc or desc)</dd>\n	<dd>Default: Posted asc</dd>\n	<dt><strong>form</strong></dt>\n	<dd>Pass each matching event to the given <span class=\"caps\">TXP</span> form. Note that using a container overrides this attribute, and if you specify neither a form nor container, you will see a list of article Posted dates</dd>\n	<dt><strong>paging</strong></dt>\n	<dd>Unlike article_custom, events from the smd_article_event tag may be paged using <code>&lt;txp:older /&gt;</code> and <code>&lt;txp:newer /&gt;</code>. But if you wish to show an event list on the same page as a standard article list, the older/newer tags will navigate both lists simultaneously. Under this circumstance you may need to turn paging off (0). Options:\n	<dl>\n		<dd>0 (off)</dd>\n		<dd>1 (on)</dd>\n	</dl></dd>\n	<dd>Default: 1</dd>\n	<dt><strong>offset</strong></dt>\n	<dd>Begin displaying events from this numeric position, instead of from the start of the list of events</dd>\n	<dd>Default: 0</dd>\n	<dt><strong>limit</strong></dt>\n	<dd>Inly show this many events maximum <strong>per page</strong>, i.e. the number of events to display, whether they come from one ‘real’ article or many</dd>\n	<dd>Default: 10</dd>\n	<dt><strong>eventlimit</strong></dt>\n	<dd>Only show this many events maximum <strong>per event</strong></dd>\n	<dd>Example: if you have a weekly repeated event that lasts for four months and you set <code>eventlimit=&quot;6&quot;</code> you will only see a maximum of 6 events from every article containing repetition. The range (start and end date) is determined by other plugin attributes</dd>\n	<dd>Default: 10</dd>\n	<dt><strong>pageby</strong></dt>\n	<dd>Esoteric paging feature, identical to <code>&lt;txp:article /&gt;</code></dd>\n	<dd>Default: same as <code>limit</code></dd>\n	<dt><strong>pgonly</strong></dt>\n	<dd>Set to 1 to perform the paging action without displaying anything. Probably useless</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>The <span class=\"caps\">HTML</span> tag, without brackets, to wrap the list in</dd>\n	<dt><strong>break</strong></dt>\n	<dd>The <span class=\"caps\">HTML</span> tag to separate each item with</dd>\n	<dt><strong>class</strong></dt>\n	<dd>The <span class=\"caps\">CSS</span> class to apply to the <code>wraptag</code></dd>\n</dl>\n\n<h3>The smd_article_event tag process</h3>\n\n<p>It is worth noting that this tag executes in 3-phases:</p>\n\n<ol>\n	<li>Pre-filter: all events that match <code>type</code>, <code>category</code>, <code>section</code>, <code>author</code>, <code>status</code>, <code>id</code>, and <code>expired</code> are extracted</li>\n	<li>Time-filter: any “time-based” attributes are then applied to the above list. At this point, any <code>extrafield</code>, <code>stepfield</code>, <code>omitfield</code>, or <code>skipfield</code> are calculated to find repeated dates (up to as many as <code>eventlimit</code> allows or the calculation exceeds the event&#8217;s expiry time). The attributes <code>time</code>, <code>month</code>, <code>from</code>, and <code>to</code> are used to refine the filtration here</li>\n	<li>Output: whatever the previous phases have left behind is subject to any <code>paging</code>, <code>offset</code> and <code>limit</code> you may have specified, then wrapped and displayed</li>\n</ol>\n\n<h2>Tag: <code>&lt;txp:smd_event_info&gt;</code></h2>\n\n<p>Identical tag to <a href=\"https://stefdawson.com/#calinfo\" rel=\"nofollow\">&lt;txp:smd_calinfo /&gt;</a> but for use inside <code>&lt;txp:smd_article_event /&gt;</code>.</p>\n\n<h2>Examples</h2>\n\n<h3>Example 1: basic calendar</h3>\n\n<p>You have the entire arsenal of <span class=\"caps\">TXP</span> tags available to you in a calendar. Thus you can set the article&#8217;s start and end dates to the same day and set the start and end times to indicate the start and end of the event. You can then use standard <code>&lt;txp:posted /&gt;</code> or <code>&lt;txp:expires /&gt;</code> tags with various <code>format</code> strings to render the event&#8217;s criteria.</p>\n\n<p>Similarly you can use any other <span class=\"caps\">TXP</span> tags to show as much or as little detail as you like in the calendar cell.</p>\n\n<pre><code>&lt;txp:smd_calendar section=&quot;events&quot;&gt;\n&lt;div&gt;\n   Event: &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;br /&gt;&lt;txp:excerpt /&gt;\n&lt;/div&gt;\n&lt;div class=&quot;evtime&quot;&gt;\n   Start: &lt;txp:posted format=&quot;%H:%M&quot; /&gt;\n&lt;/div&gt;\n&lt;div class=&quot;evtime&quot;&gt;\n   End: &lt;txp:expires format=&quot;%H:%M&quot; /&gt;\n&lt;/div&gt;\n&lt;/txp:smd_calendar&gt;</code></pre>\n\n<h3>Example 2: conditional calendar</h3>\n\n<p>Using the conditional tag you can take action if certain events contain particular flags. This example also shows a completely useless manner of employing <code>&lt;txp:smd_cal_now /&gt;</code>.</p>\n\n<pre><code>Time is: &lt;txp:smd_cal_now format=&quot;%T&quot; /&gt;\n&lt;txp:smd_calendar form=&quot;evform&quot;\n     stepfield=&quot;custom_3&quot; skipfield=&quot;custom_6&quot;\n     spanform=&quot;multis&quot; /&gt;</code></pre>\n\n<p>In form <code>evform</code>:</p>\n\n<pre><code>&lt;txp:smd_if_cal flag=&quot;recur&quot;&gt;\n  &lt;txp:permlink&gt;(RECUR)&lt;/txp:permlink&gt;\n&lt;txp:else /&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;txp:smd_if_cal flag=&quot;multifirst&quot;&gt;\n      &lt;span class=&quot;right&quot;&gt;&amp;laquo;--&lt;/span&gt;\n   &lt;/txp:smd_if_cal&gt;\n   &lt;txp:smd_if_cal flag=&quot;recurfirst&quot;&gt;\n      &lt;span&gt;One of many...&lt;/span&gt;\n   &lt;/txp:smd_if_cal&gt;\n&lt;/txp:smd_if_cal&gt;</code></pre>\n\n<p>And in form <code>multis</code>:</p>\n\n<pre><code>&lt;txp:smd_if_cal flag=&quot;multi, multiprev&quot;&gt;\n   &lt;txp:permlink&gt;--&amp;raquo;--&lt;/txp:permlink&gt;\n&lt;/txp:smd_if_cal&gt;\n&lt;txp:smd_if_cal flag=&quot;multilast&quot;&gt;\n   &lt;txp:permlink&gt;\n      &lt;span class=&quot;left&quot;&gt;--&amp;raquo;&lt;/span&gt; END &lt;txp:title /&gt;\n   &lt;/txp:permlink&gt;\n&lt;/txp:smd_if_cal&gt;</code></pre>\n\n<p>Notice that <code>multifirst</code> is tested inside the same form as standard events. This is because only <em>continuation spanned cells</em> are passed to the <code>spanform</code>; the first event of a spanned group is just like any standard event. Similarly, if you had been using <code>recurform</code> the first event of the recurring set would be processed in the usual form/container and every subsequent event would be passed to the dedicated form.</p>\n\n<h3>Example 3: classes</h3>\n\n<p>You could use the calendar tags to output various pieces of flag information to build your own class names. This example also demonstrates the <code>html</code> attribute of <code>&lt;txp:smd_cal_info /&gt;</code> to build up a query string that is passed along with an event&#8217;s <code>category1</code> when a visitor clicks the anchor.</p>\n\n<p>This allows your site visitors to filter events by category while retaining the ability to show the calendar for the current month/year and section they are viewing instead of dropping back to the current month/year like other calendar systems often do.</p>\n\n<pre><code>&lt;txp:smd_calendar isoweeks=&quot;WEEK#&quot;\n     yearwidth=&quot;0,2&quot; select=&quot;year, month&quot;\n     stepfield=&quot;custom_1&quot; skipfield=&quot;custom_2&quot;\n     showskipped=&quot;1&quot; expired=&quot;1&quot;&gt;\n   &lt;span class=&quot;&lt;txp:smd_cal_info join=&quot; cal_&quot; /&gt;&quot;&gt;\n      &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n      &lt;a href=&quot;?c=&lt;txp:category1\n         /&gt;&amp;&lt;txp:smd_cal_info type=&quot;s, year,\n         month, calid&quot; html=&quot;1&quot; join_prefix=&quot;&quot;\n         /&gt;&quot;&gt;&lt;txp:category1 title=&quot;1&quot; /&gt;&lt;/a&gt;\n   &lt;/span&gt;\n&lt;/txp:smd_calendar&gt;</code></pre>\n\n<p>What is also useful about the <code>&lt;txp:smd_cal_info /&gt;</code> tag is that if a particular value is not set it will <em>not</em> be included in the output.</p>\n\n<h3>Example 4: upcoming events</h3>\n\n<p>List the next 5 upcoming &#8212; recurring &#8212; events, plus any standard and spanned events, formatting them as a definition list.</p>\n\n<pre><code>&lt;h2&gt;Upcoming Events&lt;/h2&gt;\n&lt;txp:smd_article_event stepfield=&quot;custom_1&quot;\n     wraptag=&quot;dl&quot; time=&quot;future&quot; eventlimit=&quot;5&quot;&gt;\n   &lt;txp:if_different&gt;\n     &lt;dt&gt;&lt;txp:posted format=&quot;%B %Y&quot; /&gt;&lt;/dt&gt;\n   &lt;/txp:if_different&gt;\n   &lt;dd&gt;\n      &lt;txp:permlink&gt;&lt;txp:title/&gt;&lt;/txp:permlink&gt;\n      &lt;txp:posted /&gt;\n   &lt;/dd&gt;\n&lt;/txp:smd_article_event&gt;</code></pre>\n\n<p>If you add pagination tags you can flip through all the events; they will be displayed 10 at a time on each page (if you want to keep track of which page you are on as you flip through a calendar that is in a sidebar, add <code>pg</code> to the <code>maintain</code> attribute in your calendar).</p>\n\n<p>Note the hyperlinked title shown here will jump to the ‘real’ article (the first date in the recurring set) when clicked, not to an article with a date matching the recurrence.</p>\n\n<p>If you wanted to allow people to book an event, try this in your hyperlinked individual article:</p>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n &lt;txp:article limit=&quot;1&quot;&gt;\n  &lt;h3&gt;&lt;txp:title /&gt;&lt;/h3&gt;\n  &lt;txp:body /&gt;\n  &lt;p&gt;Please choose a date to book:&lt;/p&gt;\n  &lt;select&gt;\n  &lt;txp:smd_article_event stepfield=&quot;custom_1&quot;\n    type=&quot;recur&quot; id=\'&lt;txp:article_id /&gt;\'&gt;\n    &lt;option\n     value=\'&lt;txp:posted format=&quot;%G-%m-%d&quot;/&gt;\'&gt;\n      &lt;txp:posted format=&quot;%m %d, $G&quot; /&gt;\n    &lt;/option&gt;\n  &lt;/txp:smd_article_event&gt;\n  &lt;/select&gt;\n &lt;/txp:article&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>With some cunning you could even add the ‘virtual’ date that they chose in your original smd_article_event list and pass it as a <span class=\"caps\">URL</span> variable to your individual article where you could read the value and pre-select the date in the select list for the vistor.</p>\n\n<h3>Example 5: iCal synchronisation</h3>\n\n<p>How about being able to output your events in iCal format so other people can sync their calendars to yours? Put this in a new Page template in its own Section:</p>\n\n<pre><code>BEGIN:VCALENDAR\nVERSION:2.0\nX-WR-CALNAME:Gigs Calendar\nPRODID:-//Apple Computer, Inc//iCal 1.5//EN\nX-WR-TIMEZONE:Europe/London\n&lt;txp:smd_article_event form=&quot;icsitem&quot; time=&quot;any&quot;\nsection=&quot;gigs&quot; limit=&quot;1000&quot;&gt;\n BEGIN:VEVENT\n DTSTART:&lt;txp:posted format=&quot;%Y%m%dT%H%i%s&quot; /&gt;\n DTEND:&lt;txp:expires format=&quot;%Y%m%dT%H%i%s&quot; /&gt;\n SUMMARY:&lt;txp:title /&gt;\n END:VEVENT\n&lt;/txp:smd_article_event&gt;\nEND:VCALENDAR</code></pre>\n\n<p>That will output an iCal-formatted gig list (repeated or otherwise). If you got freaky with it and added some conditional logic inside the template you could even read in <span class=\"caps\">URL</span> variables and plug them in. Thus you could link to it directly off the calendar itself, pass in the section, category or event info and have a customised iCal stream pumped out of Textpattern.</p>\n\n<p>Thanks to woof for bringing the original <a href=\"http://de-online.co.uk/2006/05/05/textpattern-and-ical\" rel=\"nofollow\">David Emery</a> article to my attention.</p>\n\n<h3>Example 6: redefining now</h3>\n\n<p>Using the <code>now</code> and <code>offset</code> attributes of <code>&lt;txp:smd_cal_now /&gt;</code> you can effectively set ‘now’ to be any time you like and make calculations based on a particular date.</p>\n\n<p>Plugging the <code>?month</code> and <code>?year</code> codes in allows you to make <code>&lt;txp:smd_article_event /&gt;</code> track the calendar. So you can automatically show only the events that occur in the month the visitor is browsing via the calendar:</p>\n\n<pre><code>&lt;txp:smd_calendar stepfield=&quot;custom_1&quot; /&gt;\n&lt;h2&gt;Events this month&lt;/h2&gt;\n&lt;txp:smd_article_event stepfield=&quot;custom_1&quot;\n     from=\'&lt;txp:smd_cal_now now=&quot;01-?month-?year&quot; /&gt;\'\n     to=\'&lt;txp:smd_cal_now now=&quot;?month-?year&quot;\n        offset=&quot;1 month&quot; /&gt;\' time=&quot;any&quot; wraptag=&quot;ul&quot;&gt;\n&lt;li&gt;&lt;txp:permlink&gt;&lt;b&gt;&lt;txp:title/&gt;&lt;/b&gt;&lt;/txp:permlink&gt;&lt;/li&gt;\n&lt;/txp:smd_article_event&gt;</code></pre>\n\n<h2>Author / credits</h2>\n\n<p><a href=\"http://stefdawson.com/contact\" rel=\"nofollow\">Stef Dawson</a>. Jointly funded by generous donators: mrdale, woof, jakob, renobird and joebaich. Originally based on mdp_calendar. All props to the original author, and of course the class upon which the calendar is based.</p>', '/**\n * smd_calendar\n *\n * A Textpattern CMS plugin for complete monthly event and calendar management\n *  -> Originally based on mdp_calendar - thanks Marshall!\n *  -> Full-size / mini calendar by month, with ISO week support\n *  -> Txp articles are events. Future, past, and today\'s events are supported. Multi-day (spanned) events are based on article expiry\n *  -> Next/prev month/year select list with customisable start/end years\n *  -> Filter events by cat / section / author / status / time / expiry\n *  -> Specify event frequency in custom field (1 week / 10 days / 3 months / etc)\n *  -> Optionally (re)schedule / cancel / omit event dates\n *  -> Customisable output for events and cells using multiple forms/container and classes\n *  -> Conditional tags for building custom logic\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * allow table header to be removed / restyled completely (the month/week dropdown & nav icons)\n//     -- a form (navform?) for the header row with access to all vars such as which month is being displayed?\n//     -- tools to allow the header to be generated from components and laid out in any manner?\n//  * allow URL vars to be passed as POST (to bypass gbp_permlinks)\n//  * add custom rows to the table (header, footer) -- header could be used to replace the current nav/dropdowns\n//  * div-based calendar layout?\n// TOFIX:\n//  * Expiry dates on extra+ allspanned dates in smd_article_event (and calendar?). They currently \'creep\' a day for every day of a spanned event\n//  * Ranges in stepfields (http://forum.textpattern.com/viewtopic.php?pid=254395#p254395 and http://forum.textpattern.com/viewtopic.php?pid=255617#p255617)\n\nif ($date = gps(\'date\')) {\n    $_GET[\'month\'] = $date;\n}\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'smd_calendar\')\n        ->register(\'smd_cal_info\')\n        ->register(\'smd_cal_now\')\n        ->register(\'smd_cal_class\')\n        ->register(\'smd_if_cal\')\n        ->register(\'smd_article_event\')\n        ->register(\'smd_event_info\')\n        ->register(\'smd_event_duration\')\n        ->register(\'smd_cal_minilink\')\n        ->register(\'smd_cal_iso_week\')\n        ->register(\'smd_cal_reformat_win\')\n        ->register(\'smd_cal_in_array\')\n        ->register(\'smd_cal_include_event\')\n        ->register(\'smd_expand_daterange\');\n}\n\nfunction smd_calendar($atts, $thing = null)\n{\n    global $pretext, $thisarticle, $variable, $prefs, $smd_cal_flag, $smd_date, $smd_calinfo, $smd_cal_ucls;\n\n    extract(lAtts(array(\n        \'time\'          => \'any\',\n        \'size\'          => \'large\',\n        \'expired\'       => \'\',\n        \'category\'      => null,\n        \'subcats\'       => \'\',\n        \'section\'       => \'\',\n        \'author\'        => \'\',\n        \'realname\'      => \'\',\n        \'status\'        => \'live\',\n        \'showall\'       => \'0\',\n        \'static\'        => \'\',\n        \'form\'          => \'\',\n        \'spanform\'      => \'SMD_SAME\',\n        \'recurform\'     => \'SMD_SAME\',\n        \'cellform\'      => \'\',\n        \'headerform\'    => \'\',\n        \'stepfield\'     => \'\',\n        \'skipfield\'     => \'\',\n        \'omitfield\'     => \'\',\n        \'extrafield\'    => \'\',\n        \'extrastrict\'   => \'0\',\n        \'datefields\'    => \'\',\n        \'showskipped\'   => \'0\',\n        \'showspanned\'   => \'1\',\n        \'holidays\'      => \'\',\n        \'holidayflags\'  => \'standard\',\n        \'classlevels\'   => \'cell, event\',\n        \'linkposted\'    => \'recur, multi, multiprev, multilast\',\n        \'classprefixes\' => \'smd_cal_, smd_cal_ev_\',\n        \'class\'         => \'\',\n        \'rowclass\'      => \'\',\n        \'cellclass\'     => \'\',\n        \'emptyclass\'    => \'empty\',\n        \'isoweekclass\'  => \'week\',\n        \'navclass\'      => \'navprev, navnext\',\n        \'navarrow\'      => \'&#60;, &#62;\',\n        \'navid\'         => \'\',\n        \'eventclasses\'  => \'category\',\n        \'eventwraptag\'  => \'span\',\n        \'select\'        => \'\',\n        \'selectbtn\'     => \'\',\n        \'myclass\'       => \'\',\n        \'mywraptag\'     => \'\',\n        \'caption\'       => \'\',\n        \'summary\'       => \'\',\n        \'id\'            => \'\',\n        \'week\'          => \'\',\n        \'month\'         => \'\',\n        \'year\'          => \'\',\n        \'remap\'         => \'\',\n        \'yearwidth\'     => \'0\',\n        \'isoweeks\'      => \'\',\n        \'dayformat\'     => \'ABBR\',\n        \'monthformat\'   => \'FULL\',\n        \'firstday\'      => 0,\n        \'maintain\'      => \'calid\',\n        \'nameval\'       => \'\',\n        \'event_delim\'   => \',\',\n        \'gmt\'           => 0,\n        \'lang\'          => \'\',\n        \'debug\'         => 0,\n    ), $atts));\n\n    $size = (in_array($size, array(\'small\', \'large\'))) ? $size : \'large\';\n    $status = ($status) ? $status : \'live\'; // in case status is empty\n    $firstday = ($isoweeks == \'\') ? $firstday : 1;\n    $spanform = ($spanform == \'SMD_SAME\') ? $form : $spanform;\n    $recurform = ($recurform == \'SMD_SAME\') ? $form : $recurform;\n    $cellform = (empty($cellform)) ? \'\' : fetch_form($cellform);\n    $headerform = (empty($headerform)) ? \'\' : fetch_form($headerform);\n    $frontpage = ($section==\'\' && $pretext[\'s\']==\'default\') ? true : false;\n\n    // Set up the class prefixes\n    $clevs = do_list($classlevels);\n    $cls = do_list($classprefixes);\n    $cls_pfx = $evc_pfx = $cls[0];\n    if (count($cls) > 1){\n        $evc_pfx = $cls[1];\n    }\n\n    // Set up the nav class(es)\n    $maintain = do_list($maintain);\n    $navarrow = do_list($navarrow);\n    $navparr = $navarrow[0];\n    $navnarr = (count($navarrow) > 1) ? $navarrow[1] : $navarrow[0];\n    $navclass = do_list($navclass);\n    $navpclass = $navclass[0];\n    $navnclass = (count($navclass) > 1) ? $navclass[1] : $navclass[0];\n\n    // Filters\n    $fopts = array();\n    $catSQL = $secSQL = $authSQL = $fpSQL = \'\';\n    if($category !== null) {\n        $uncats = false;\n        $allcats = do_list($category);\n        if (($pos = array_search(\'SMD_UNCAT\', $allcats)) !== false) {\n            $uncats = true;\n            unset($allcats[$pos]);\n            $category = join(\',\', $allcats);\n        }\n        $fopts[\'c\'] = $category; // TODO: Can fopts take a list? Should it include subcats?\n        $subcats = (empty($subcats)) ? 0 : ((strtolower($subcats)==\"all\") ? 99999 : intval($subcats));\n        if ($subcats) {\n            $outcats = array();\n            foreach ($allcats as $cat) {\n                $cats = getTree(doslash($cat), \'article\');\n                foreach ($cats as $jdx => $val) {\n                    if ($cats[$jdx][\'level\'] <= $subcats) {\n                        $outcats[] = $cats[$jdx][\'name\'];\n                    }\n                }\n            }\n            $allcats = $outcats;\n        }\n        $catSQL = doQuote(join(\"\',\'\", doSlash($allcats)));\n        $catSQL = ($uncats ? \" AND (Category1 = \'\' AND Category2 = \'\')\" : \'\') .\n            ($uncats && $allcats ? \" OR \" : ($allcats ? \" AND \" : \'\')) .\n            ($allcats ? \"( Category1 IN (\".$catSQL.\") OR Category2 IN (\".$catSQL.\") ) \" : \'\');\n    }\n    if($section) {\n        $secs = do_list($section);\n        $smd_calinfo[\'s\'] = $secs[0];\n        $secSQL = doQuote(join(\"\',\'\", doSlash($secs)));\n        $secSQL = \" AND Section IN (\".$secSQL.\") \";\n    }\n    if($realname) {\n        $authors = safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doArray(do_list($realname), \'urldecode\'))) .\')\' );\n        $author = join(\',\', $authors);\n    }\n    if($author) {\n        $fopts[\'author\'] = htmlentities(gps(\'author\'));\n        $authSQL = doQuote(join(\"\',\'\", doSlash(do_list($author))));\n        $authSQL = \" AND AuthorID IN (\".$authSQL.\") \";\n    }\n    if ($frontpage && !$showall) {\n        $fpSQL = filterFrontPage();\n    }\n    $smd_calinfo[\'artid\'] = $thisarticle[\'thisid\'];\n    $smd_calinfo[\'artitle\'] = $thisarticle[\'url_title\'];\n    $nameval = do_list($nameval);\n    foreach ($nameval as $nv) {\n        $nv = explode(\"=\", $nv);\n        if ($nv[0]) {\n            $fopts[$nv[0]] = ((isset($nv[1])) ? $nv[1] : \'\');\n        }\n    }\n    $status = do_list($status);\n    $stati = array();\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } else if (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n    $stati = \" Status IN (\".join(\',\', $stati).\")\";\n\n    $expired = ($expired) ? $expired : $prefs[\'publish_expired_articles\'];\n    $expired = (($expired) ? \'\' : \' AND (now() <= Expires OR Expires IS NULL)\');\n    $eventclasses = do_list($eventclasses);\n    $holidayflags = do_list($holidayflags);\n    $linkposted = do_list($linkposted);\n    $datefields = do_list($datefields);\n\n    // Work out the first and last posts to determine the year range - probably a better way of doing this than 3 queries\n    $filt = $stati . (($category !== null) ? $catSQL : \'\') . (($section) ? $secSQL : \'\') . (($author) ? $authSQL : \'\') . $fpSQL;\n    $earliest = safe_field(\'unix_timestamp(Posted) AS uPosted\', \'textpattern\', $filt .\' ORDER BY Posted ASC LIMIT 0, 1\', $debug);\n    $lp = safe_field(\'unix_timestamp(Posted) AS uPosted\', \'textpattern\', $filt .\' ORDER BY Posted DESC LIMIT 0, 1\', $debug);\n    $lm = safe_field(\'unix_timestamp(LastMod) AS uLastMod\', \'textpattern\', $filt .\' ORDER BY LastMod DESC LIMIT 0, 1\', $debug);\n    $latest = ($time==\"past\") ? time() : (($lp > $lm) ? $lp : $lm);\n\n    $yearwidth = do_list($yearwidth);\n    $yearwidth[0] = (empty($yearwidth[0])) ? 0 : $yearwidth[0];\n    if (count($yearwidth) == 1) {\n        $yearwidth[1] = $yearwidth[0];\n    }\n    $usenow = array(false,false);\n    foreach ($yearwidth as $yridx => $yritem) {\n        if (strpos($yritem,\"c\") !== false) {\n            $yearwidth[$yridx] = intval($yritem);\n            $usenow[$yridx] = true;\n        }\n    }\n\n    // Remap w/m/y to other vars if required\n    $remap = do_list($remap);\n    $dmap = array(\"y\" => \"y\", \"m\" => \"m\", \"w\" => \"w\");\n    foreach ($remap as $dpair) {\n        $dpair = do_list($dpair, \':\');\n        $dmap[$dpair[0]] = (isset($dpair[1])) ? $dpair[1] : $dpair[0];\n    }\n    $earliest = date(\"Y\", strtotime(\"-\".$yearwidth[0].\" year\", ( (empty($earliest) || $usenow[0]==true) ? time() : $earliest) ) );\n    $latest = date(\"Y\", strtotime(\"+\".$yearwidth[1].\" year\", ( (empty($latest) || $usenow[1]==true) ? time() : $latest) ) );\n\n    // Check the URL for current date and calendar target info\n    $in_calid = gps(\'calid\');\n    $in_year = (gps($dmap[\"y\"]) and is_numeric(gps($dmap[\"y\"]))) ? (int)gps($dmap[\"y\"]) : \'\';\n    $in_month = (gps($dmap[\"m\"]) and is_numeric(gps($dmap[\"m\"]))) ? (int)gps($dmap[\"m\"]) : \'\';\n    $in_week = (gps($dmap[\"w\"]) and is_numeric(gps($dmap[\"w\"]))) ? (int)gps($dmap[\"w\"]) : \'\';\n\n    if($static) { // if we\'re static w/o any supplied vars, use the current date\n        if(!$year) { $year = safe_strftime(\'%Y\'); }\n        if(!$month) { $month = safe_strftime(\'%m\'); }\n    } else { // otherwise use current date only if there\'s nothing else\n        if( $id == $in_calid ) { // use incoming\n            $year = ($in_year) ? $in_year : (($year) ? $year : safe_strftime(\'%Y\'));\n            $month = ($in_month) ? $in_month : (($month) ? $month : safe_strftime(\'%m\'));\n            // If week is used, adjust month so it encompasses the given week\n            $week = $in_week;\n            if ($week) {\n                $month = safe_strftime(\"%m\", strtotime($year.\"W\".str_pad($week, 2, \'0\', STR_PAD_LEFT))); // Get the month from the week\n            }\n        } else { // use current\n            if(!$year) { $year = safe_strftime(\'%Y\'); }\n            if(!$month) { $month = safe_strftime(\'%m\'); }\n            if($week) { $month = safe_strftime(\"%m\", strtotime($year.\"W\".str_pad($week, 2, \'0\', STR_PAD_LEFT))); }\n        }\n    }\n    $smd_calinfo[\'id\'] = ($in_calid) ? $in_calid : $id;\n    $smd_date[\'y\'] = $year; $smd_date[\'m\'] = $month; // $week/day/isoyear are set per event later\n\n    $ts_first = mktime(0, 0, 0, $month, 1, $year);\n    $ts_last = mktime(23, 59, 59, $month, date(\'t\',$ts_first), $year);\n    $ts_lastoff = $ts_last - tz_offset($ts_last);\n    if ($debug) {\n        echo \"++ THIS MONTH\'S CALENDAR [ start stamp // end date // end stamp // end date // tz offset (end) ] ++\";\n        dmp($ts_first, date(\'Y-m-d H:i:s\', $ts_first), $ts_last, date(\'Y-m-d H:i:s\', $ts_last), $ts_lastoff);\n    }\n    $extrasql = $catSQL . $secSQL . $authSQL . $fpSQL;\n\n    switch($time) {\n        case \"any\" : break;\n        case \"future\" : $extrasql .= \" AND Posted > now()\"; break;\n        default : $extrasql .= \" AND Posted < now()\"; break; // The past\n    }\n\n    // Holidays are global \'exclusions\', either defined directly or in a txp:variable\n    $holidays = do_list($holidays);\n    $txphols = do_list($holidays[0], \":\");\n    if ($txphols[0] == \"txpvar\") {\n        $holidays = do_list($variable[$txphols[1]]);\n    }\n    // Force each holiday to a known format. Holidays without years use current year\n    foreach ($holidays as $key => $val) {\n        if (empty($val)) continue;\n        $numparts = preg_match(\'/^([\\d\\w]+).?([\\d\\w]+).?([\\d\\w]+)?$/\', $val, $parts);\n\n        if ($numparts) {\n            if (count($parts) == 3) {\n                $parts[3] = $year;\n         }\n            $val = str_pad($parts[1], 2, \'0\', STR_PAD_LEFT).\'-\'.str_pad($parts[2], 2, \'0\', STR_PAD_LEFT).\'-\'.$parts[3];\n        }\n        $holidays[$key] = date(\"d-M-Y\", safe_strtotime($val));\n    }\n\n    if ($debug > 0 && !empty($holidays) && $holidays[0] != \'\') {\n        echo \"++ HOLIDAYS ++ \";\n        dmp($holidays);\n    }\n\n    // Get all matching articles in (and before) this month\n    $events = array();\n    $uposted_field = (empty($datefields[0])) ? \'uPosted\' : \"UNIX_TIMESTAMP($datefields[0])\";\n    $sql2 = $stati . \" HAVING $uposted_field <= \".$ts_lastoff. $expired . $extrasql .\" ORDER BY Posted ASC\";\n    $grabCols = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\';\n    $evlist = safe_rows($grabCols, \'textpattern\', $sql2, $debug);\n    article_push();\n\n    // If any events recur and fall within the current month, add those as well\n    // If any dates are to be excluded, the entry is skipped UNLESS showskipped indicates otherwise\n    foreach ($evlist as $row) {\n        $idx = 0; // In case the 1st day of the month is a continuation of an event from the end of the previous month\n        $start = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row[\'uPosted\'] + tz_offset($row[\'uPosted\']);\n        $start_date = date(\"Y-m-d\", $start); // For recurring/spanned events on a minical, this is the event the cell links to\n        $real_end = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\'] + tz_offset($row[\'uExpires\']));\n\n        // If end < start the user-specified dates cannot be trusted\n        if ($real_end != 0 && $real_end <= $start) {\n            $start = $row[\'uPosted\'] + tz_offset($row[\'uPosted\']);\n            $real_end = $row[\'uExpires\'] + tz_offset($row[\'uExpires\']);\n            trigger_error(\'Expiry cannot be before start date in \"\'.$row[\'Title\'].\'\": ignored\', E_USER_WARNING);\n        }\n\n        $end = ($real_end != 0 && $real_end < $ts_last) ? $real_end : $ts_last;\n        $real_diff = ($real_end==0) ? 0 : $real_end - $start;\n        $real_end_month = ($real_end==0) ? 0 : date(\'m\', $real_end);\n        $real_end_year = ($real_end==0) ? 0 : date(\'Y\', $real_end);\n        $fake_diff = strtotime(date(\"Y-M-d\", $real_end) . \" 23:59:59\");\n        $diff = ($real_end==0) ? 0 : $fake_diff - $start;\n        $smd_cal_flag = array();\n        $smd_cal_ucls = array();\n\n        $ev_month = date(\'m\', $start);\n        $ev_year = date(\'Y\', $start);\n        $ev_hr = date(\'H\', $start);\n        $ev_mn = date(\'i\', $start);\n        $ev_sc = date(\'s\', $start);\n\n        if ($debug > 1) {\n            echo \'++ EVENT START // END // (if non-zero) REAL END ++\';\n            dmp(date(\'d-M-Y H:i:s\', $start) .\' // \'. date(\'d-M-Y H:i:s\', $end) .\' // \'. ( ($real_end == 0) ? \'\' : date(\'d-M-Y H:i:s\', $real_end) ));\n            dmp($row[\'Title\']);\n            if ($debug > 2) {\n                dmp($row);\n            }\n        }\n\n        $multi = (($end > $start) && ($real_end > $start) && ($real_end > $ts_first) && (date(\"d-m-Y\", $real_end) != date(\"d-m-Y\", $start))) ? true : false;\n        $recur = (empty($row[$stepfield])) ? false : true;\n        $hol_hit = in_array(date(\"d-M-Y\", $start), $holidays);\n        $evclasses = array();\n        foreach ($eventclasses as $evcls) {\n            switch ($evcls) {\n                case \"\":\n                    break;\n                case \"gcat\":\n                    if (isset($pretext[\'c\']) && !empty($pretext[\'c\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'c\'];\n                    }\n                    break;\n                case \"category\":\n                    if (isset($row[\'Category1\']) && !empty($row[\'Category1\'])) {\n                        $evclasses[] = $evc_pfx.$row[\'Category1\'];\n                    }\n                    if (isset($row[\'Category2\']) && !empty($row[\'Category2\'])) {\n                        $evclasses[] = $evc_pfx.$row[\'Category2\'];\n                    }\n                    break;\n                case \"section\":\n                    if (isset($pretext[\'s\']) && !empty($pretext[\'s\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'s\'];\n                    }\n                    break;\n                case \"author\":\n                    if (isset($pretext[\'author\']) && !empty($pretext[\'author\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'author\'];\n                    }\n                    break;\n                default:\n                    if (isset($row[$evcls]) && !empty($row[$evcls])) {\n                        $evclasses[] = $evc_pfx.$row[$evcls];\n                    }\n                    break;\n            }\n        }\n        $ignore = array();\n        $omit = array();\n        $cflag = array();\n\n        if ($debug > 1 && $evclasses) {\n            echo \'++ EVENT CLASSES ++\';\n            dmp($evclasses);\n        }\n\n        // Events that start or are added this month\n        if (($start < $end) && ($start > $ts_first)) {\n            populateArticleData($row);\n\n            // a standard event or start of a multi\n            if ($showspanned && $multi && !$recur) {\n                $smd_cal_flag[] = \'multifirst\';\n            }\n            if ($recur) {\n                $smd_cal_flag[] = \'recurfirst\';\n            }\n            if (!$smd_cal_flag) {\n                $smd_cal_flag[] = \'standard\';\n            }\n            if ( ( $hol_hit && !in_array(\'multi\',$holidayflags) && in_array(\'multifirst\',$smd_cal_flag) ) || ( $hol_hit && !in_array(\'standard\',$holidayflags) && in_array(\'standard\',$smd_cal_flag) ) ) {\n                $smd_cal_flag[] = \'cancel\';\n            }\n            foreach ($smd_cal_flag as $item) {\n                $cflag[] = $cls_pfx.$item;\n            }\n\n            $idx = $smd_date[\'d\'] = (int)strftime(\'%d\', $start);\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $start), $start);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $start), $start);\n            $use_posted = in_array(\'standard\', $linkposted);\n\n            $op = ($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href($row[\'Title\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n            $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n            $smd_cal_flag = array();\n            $cflag = array();\n            $smd_cal_ucls = array();\n            $use_posted = \'\';\n        }\n\n        // Generate a skip array for this event\n        if ($skipfield && $row[$skipfield] != \'\') {\n            $ignores = do_list($row[$skipfield]);\n            foreach ($ignores as $val) {\n                $igrng = smd_expand_daterange($val, $start, $end);\n                foreach ($igrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval); // Force each date to a known format\n                }\n            }\n        }\n        // Generate an omit array for this event\n        if ($omitfield && $row[$omitfield] != \'\') {\n            $omits = do_list($row[$omitfield]);\n            foreach ($omits as $val) {\n                $omrng = smd_expand_daterange($val, $start, $end);\n                foreach ($omrng as $theval) {\n                    $omit[] = date(\"d-M-Y\", $theval);\n                }\n            }\n        }\n        if ($debug > 1 && ($ignore || $omit)) {\n            echo \'++ OMITTED DATES ++\';\n            dmp($omit);\n            echo \'++ CANCELLED DATES ++\';\n            dmp($ignore);\n        }\n        // Calculate the date offsets and check recurring events that fall within the month of interest\n        if ($stepfield && $row[$stepfield] != \'\') {\n            $freq = do_list($row[$stepfield]);\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($freq as $interval) {\n                $max_loop = 99999; // Yuk, but practically limitless\n                $origerval = $interval;\n                $interval = str_replace(\"?month\", date(\'F\', mktime(0,0,0,$month,1)), $interval);\n                $interval = str_replace(\"?year\", $year, $interval);\n                if (strpos($interval, \"last\") === 0) {\n                    $interval = date(\"l, F jS Y\", strtotime( $interval, mktime(12, 0, 0, date(\"n\", mktime(0,0,0,$month,1,$year))+1, 1, $year) ));\n                    $max_loop = 1;\n                } else if (strpos($interval, \"first\") === 0) {\n                    $interval = date(\"l, F jS Y\", strtotime( $interval, mktime(12, 0, 0, (($month>1) ? $month-1 : 12), date(\"t\", mktime(0,0,0,$month-1,1,(($month==1) ? $year-1: $year))), (($month==1) ? $year-1: $year)) ));\n                    $max_loop = 1;\n                } else if (strpos($interval, \"this\") === 0) {\n                    $max_loop = 1;\n                }\n                $ts_loop = 0;\n                $ts_curr = $start;\n                if (strpos($origerval, \"?month\") || strpos($origerval, \"?year\")) {\n                    $max_loop = 1;\n            }\n\n//              $rng = smd_expand_daterange($interval);\n//dmp($interval, $rng);\n                while ($ts_curr < $end && $ts_loop < $max_loop) {\n                    if ($max_loop == 1) {\n                        $ts_curr = strtotime($interval);\n                        $ts_curr = ($ts_curr < $start || $ts_curr > $end) ? $start : $ts_curr;\n                    } else {\n                        $ts_curr = strtotime($interval, $ts_curr);\n                    }\n                    if ($ts_curr === false) {\n                        $ts_loop++;\n                        break;\n                    } else {\n                        if ($debug > 1) {\n                            dmp(\"INTERVAL: \". date(\'d-M-Y H:i:s\', $ts_curr+$stampoff));\n                        }\n                        if ($ts_curr < $end && $ts_curr >= $ts_first && $ts_curr != $start) {\n                            // A recurring event. Check it isn\'t a holiday or to be ignored\n                            populateArticleData($row);\n                            $op = \'\';\n                            $idx = (int)strftime(\'%d\', $ts_curr);\n                            $smd_cal_flag[] = \'recur\';\n                            $thisdate = date(\"d-M-Y\", $ts_curr);\n                            $omit_me = in_array($thisdate, $omit);\n                            $show_me = !in_array($thisdate, $ignore);\n                            $hol_hit = in_array($thisdate, $holidays);\n                            $show_hol = ($hol_hit && !in_array(\'recur\',$holidayflags) ) ? false : true;\n                            $use_posted = smd_cal_in_array(array(\'recur\', \'recurfirst\'), $linkposted);\n\n                            if ( $omit_me ) {\n                                $smd_cal_flag[] = \'omit\';\n                            }\n                            if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                                $smd_cal_flag[] = \'cancel\';\n                            }\n                            foreach ($smd_cal_flag as $item) {\n                                $cflag[] = $cls_pfx.$item;\n                            }\n\n                            // Create the events that appear in the cell but only if they\'ve not appeared before, or are to be ignored/omitted\n                            if (!$omit_me) {\n                                if (($show_me && $show_hol) || $showskipped) {\n                                    $smd_date[\'d\'] = $idx;\n                                    $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $ts_curr), $ts_curr);\n                                    $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $ts_curr), $ts_curr);\n                                    $op = ($recurform) ? parse_form($recurform) : (($thing) ? parse($thing) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href($row[\'Title\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n                                }\n                            }\n                            $used = array();\n                            if (isset($events[$idx]) && $events[$idx] != NULL) {\n                                foreach ($events[$idx] as $ev) {\n                                    $used[] = $ev[\'ev\'];\n                                }\n                            }\n                            if (isset($events[$idx]) && $events[$idx] == NULL || !in_array($op, $used)) {\n                                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n                            }\n                            $smd_cal_flag = array();\n                            $cflag = array();\n                            $smd_cal_ucls = array();\n                            $use_posted = \'\';\n                        }\n                        $ts_loop++;\n                    }\n                }\n            }\n        } else if ($showspanned && $multi) {\n            // Non-recurring events may span more than one date but they must still respect ignored dates and holidays\n            populateArticleData($row);\n            $lastday = (int)strftime(\'%d\', $end);\n            $real_lastday = (int)strftime(\'%d\', $real_end);\n            while (++$idx <= $lastday) {\n                $op = \'\';\n                $multiflag = (($year==$real_end_year) && ($month==$real_end_month) && ($idx==$real_lastday)) ? \'multilast\' : (($idx==1) ? \'multiprev\' : \'multi\');\n                $smd_cal_flag[] = $multiflag;\n                $thistime = mktime(0, 0, 0, $month, $idx, $year);\n                $thisdate = date(\"d-M-Y\", $thistime);\n                $omit_me = in_array($thisdate, $omit);\n                $show_me = !in_array($thisdate, $ignore);\n                $hol_hit = in_array($thisdate, $holidays);\n                $show_hol = ($hol_hit && !in_array(\'multi\',$holidayflags) ) ? false : true;\n                $use_posted = smd_cal_in_array(array(\'multi\', \'multifirst\', \'multilast\', \'multiprev\'), $linkposted);\n                if ( $omit_me ) {\n                    $smd_cal_flag[] = \'omit\';\n                }\n                if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                    $smd_cal_flag[] = \'cancel\';\n                }\n                foreach ($smd_cal_flag as $item) {\n                    $cflag[] = $cls_pfx.$item;\n                }\n                // Create the spanned event that appears in the cell\n                if (!$omit_me) {\n                    if ( ($show_me && $show_hol) || $showskipped) {\n                        $smd_date[\'d\'] = $idx;\n                        $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $thistime), $thistime);\n                        $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $thistime), $thistime);\n                        $op = ($spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href(\'&rarr;\', permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n                    }\n                }\n                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n                $smd_cal_flag = array();\n                $cflag = array();\n                $smd_cal_ucls = array();\n                $use_posted = \'\';\n            }\n        }\n        // Add any extra dates for this event that are within the current month\n        if ($extrafield && $row[$extrafield] != \'\') {\n            $xtra = do_list($row[$extrafield]);\n            $ev_hr = date(\'H\', $start);\n            $ev_mn = date(\'i\', $start);\n            $ev_sc = date(\'s\', $start);\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($xtra as $val) {\n                if (strpos($val, \"+\") === false) {\n                    $exrng = smd_expand_daterange($val);\n                    $val = date(\"Y-m-d\", $exrng[0]);\n                    $spidth = count($exrng);\n                    $spex = 0;\n                } else {\n                    $chk = $showspanned && !$recur;\n                    $spidth = $chk ? ceil($diff / (60*60*24)) : 1; // days between dates\n                    $val = rtrim($val, \'+\');\n                    $spex = $chk ? 1 : 0;\n                }\n\n                for ($jdx = 1; $jdx <= $spidth; $jdx++) {\n                    $tm = safe_strtotime($val . (($jdx==1) ? \'\' : \'+\'.($jdx-1).\' days\'));\n                    if ($diff > 0 && $jdx == 1) {\n                        $expstamp = $tm+$stampoff+$real_diff;\n                    }\n                    $idx = $smd_date[\'d\'] = (int)strftime(\'%d\', $tm);\n                    $dt = date(\"Y-m-d\", $tm);\n                    $lst = ($extrastrict) ? $end : $ts_last;\n                    if ($tm < $lst && $tm >= $ts_first) {\n                        $fakerow = $row;\n                        $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff);\n                        $fakerow[\'uPosted\'] = $tm+$stampoff;\n                        if ($diff>0) {\n                            $fakerow[\'Expires\'] = date(\"Y-m-d H:i:s\", $expstamp);\n                            $fakerow[\'uExpires\'] = $expstamp;\n                        }\n\n                        populateArticleData($fakerow);\n                        $smd_cal_flag[] = \'extra\';\n                        $cflag[] = $cls_pfx.\'extra\';\n                        $omit_me = false;\n                        $show_me = $show_hol = true;\n                        if ($spex) {\n                            $multiflag = ($jdx==1) ? \'multifirst\' : (($jdx==$spidth) ? \'multilast\' : (($idx==1) ? \'multiprev\' : \'multi\'));\n                            $thisdate = date(\"d-M-Y\", $tm);\n                            $omit_me = in_array($thisdate, $omit);\n                            $show_me = !in_array($thisdate, $ignore);\n                            $hol_hit = in_array($thisdate, $holidays);\n                            $show_hol = ($hol_hit && !in_array(\'multi\',$holidayflags) ) ? false : true;\n                            $use_posted = in_array(\'extra\', $linkposted);\n                            if ($omit_me) {\n                                $smd_cal_flag[] = \'omit\';\n                            }\n                            if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                                $smd_cal_flag[] = \'cancel\';\n                            }\n                            $smd_cal_flag[] = $multiflag;\n                            $cflag[] = $cls_pfx.$multiflag;\n                        }\n                        if (!$omit_me) {\n                            if ( ($show_me && $show_hol) || $showskipped) {\n                                $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $tm), $tm);\n                                $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $tm), $tm);\n                                $op = ($spex && $spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href((($spex && $jdx>1) ? \'&rarr;\' : $row[\'Title\']), permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) ));\n                                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $dt);\n                                $smd_cal_flag = array();\n                                $cflag = array();\n                                $smd_cal_ucls = array();\n                                $use_posted = \'\';\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    article_pop();\n\n    if ($debug > 1 && $events) {\n        echo \'++ ALL EVENTS ++\';\n        dmp($events);\n    }\n\n    // Generate the calendar\n    $calendar = new SMD_Calendar($size, $year, $month, $events, $section, $category, $debug);\n    $calendar->setWeek($week);\n    $calendar->setFirstDayOfWeek($firstday);\n    $calendar->setGMT($gmt);\n    $calendar->setLang($lang);\n    $calendar->setClassLevels($clevs);\n    $calendar->setClassPrefix($cls_pfx);\n    $calendar->setEventWraptag($eventwraptag);\n    $calendar->setCellForm($cellform);\n    $calendar->setHdrForm($headerform);\n    $calendar->setMYWraptag($mywraptag);\n    $calendar->setSummary($summary);\n    $calendar->setCaption($caption);\n    $calendar->setTableID($id);\n    $calendar->setTableClass($class);\n    $calendar->setRowClass($rowclass);\n    $calendar->setCellClass($cellclass);\n    $calendar->setEmptyClass($emptyclass);\n    $calendar->setISOWeekClass($isoweekclass);\n    $calendar->setNavInfo($navpclass,$navnclass,$navparr,$navnarr,$navid);\n    $calendar->setNavKeep($maintain);\n    $calendar->setMYClass($myclass);\n    $calendar->setNameFormat($dayformat, \"d\");\n    $calendar->setNameFormat($monthformat, \"m\");\n    $calendar->setRemap($dmap);\n    $calendar->setShowISOWeek($isoweeks);\n    $calendar->setEYear($earliest);\n    $calendar->setLYear($latest);\n    $calendar->setFilterOpts($fopts);\n    $calendar->setDelim($event_delim);\n    $calendar->setHolidays($holidays);\n    $calendar->setSelectors(do_list($select), $selectbtn);\n\n    return $calendar->display($static);\n}\n\nclass SMD_Calendar extends SMD_Raw_Calendar\n{\n    // Override Constructor\n    // Permits multiple events to show per day\n    var $section = \'\';\n    var $category = \'\';\n    var $size = \'\';\n    var $debug = 0;\n    var $events = array();\n\n    public function __construct($size, $year, $month, $events, $section, $category, $debug = 0)\n    {\n        $this->debug = $debug;\n        $this->section = $section;\n        $this->category = $category;\n        $this->events = $events;\n        $this->size = $size;\n        parent::__construct($year,$month,$debug);\n    }\n\n    // Override dspDayCell to display stuff right\n    function dspDayCell($theday)\n    {\n        global $smd_cal_flag, $smd_calinfo, $smd_cal_ucls, $smd_date, $permlink_mode;\n\n        $smd_cal_flag = array();\n        $smd_cal_ucls = array();\n        $tdclass = array();\n        $hasarticle = isset($this->events[$theday]);\n        $now = time() + tz_offset();\n\n        $thedate = mktime(0, 0, 0, $this->month, $theday, $this->year);\n        $hol_hit = in_array(date(\"d-M-Y\", $thedate), $this->holidays);\n        if ($hasarticle) {\n            $smd_cal_flag[] = \'event\';\n        }\n        if ($hol_hit) {\n            $smd_cal_flag[] = \'hols\';\n        }\n        $cflag = array();\n        foreach ($smd_cal_flag as $item) {\n            $cflag[] = $this->cls_pfx.$item;\n        }\n\n        if ($this->cellclass) {\n            $tdclass[] = $this->cellclass;\n        }\n        $tdclass = array_merge($tdclass, $cflag);\n        $runningclass = (in_array(\"cell\", $this->cls_lev) || in_array(\"cellplus\", $this->cls_lev)) ? $tdclass : array();\n\n        if($this->year == date(\'Y\',$now) and $this->month == date(\'n\',$now) and $theday == date(\'j\',$now) ) {\n            $smd_cal_flag[] = \'today\';\n            $runningclass[] = $this->cls_pfx.\'today\';\n        }\n\n        $out = array();\n        $flags = array();\n        $evid = array();\n        $fout = array(\'standard\'=>array(),\'recur\'=>array(),\'recurfirst\'=>array(),\'multifirst\'=>array(),\'multi\'=>array(),\'multiprev\'=>array(),\'multilast\'=>array(),\'cancel\'=>array(),\'extra\'=>array());\n        if (empty($this->cellform) && $this->size == \'large\') {\n            $out[] = hed($theday,4);\n        }\n\n        $evcnt = 0;\n        if( isset($this->events[$theday]) ) {\n            $days_events = $this->events[$theday];\n            foreach($days_events as $ev) {\n                $evclass = $ev[\'classes\'];\n                $evid[] = $ev[\'evid\'];\n                $flags = array_merge($flags, $ev[\'flag\']);\n                if (in_array(\"cellplus\", $this->cls_lev)) {\n                    $runningclass = array_merge($runningclass, $evclass);\n                }\n                $cls = ($evclass && in_array(\"event\", $this->cls_lev)) ? \' class=\"\'.join(\' \', $evclass).\'\"\' : \'\';\n                $op = ($this->evwraptag) ? tag($ev[\'ev\'], $this->evwraptag, $cls) : $ev[\'ev\'];\n                foreach ($ev[\'flag\'] as $flev) {\n                    $fout[$flev][] = $op;\n                }\n                $out[] = $op;\n                $evcnt++;\n                if ($this->size == \'small\' && $evcnt == 1) {\n                    break;\n                }\n            }\n        } elseif ($this->size == \'small\') {\n            $out[] = hed($theday,4);\n        }\n\n        // Amalgamate the event-level classes and cell-level classes if required\n        $runningclass = array_unique($runningclass);\n        if (in_array(\"cellplus\", $this->cls_lev)) {\n            $smd_cal_flag = array_merge($smd_cal_flag, $flags);\n        }\n\n        if ($this->cellform) {\n            $thistime = mktime(0, 0, 0, $this->month, $theday, $this->year);\n            $smd_calinfo[\'id\'] = $this->tableID;\n            $smd_date[\'y\'] = $this->year;\n            $smd_date[\'m\'] = $this->month;\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $thistime), $thistime);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $thistime), $thistime);\n            $smd_date[\'d\'] = $theday;\n            $reps = array(\n                \'{evid}\' => join($this->event_delim, $evid),\n                \'{standard}\' => join(\'\',$fout[\'standard\']),\n                \'{recur}\' => join(\'\',$fout[\'recur\']),\n                \'{recurfirst}\' => join(\'\',$fout[\'recurfirst\']),\n                \'{allrecur}\' => join(\'\',array_merge($fout[\'recur\'], $fout[\'recurfirst\'])),\n                \'{multifirst}\' => join(\'\',$fout[\'multifirst\']),\n                \'{multiprev}\' => join(\'\',$fout[\'multiprev\']),\n                \'{multi}\' => join(\'\',$fout[\'multilast\']),\n                \'{multilast}\' => join(\'\',$fout[\'multilast\']),\n                \'{allmulti}\' => join(\'\',array_merge($fout[\'multifirst\'],$fout[\'multi\'],$fout[\'multiprev\'],$fout[\'multilast\'])),\n                \'{cancel}\' => join(\'\',$fout[\'cancel\']),\n                \'{extra}\' => join(\'\',$fout[\'extra\']),\n                \'{events}\' => join(\'\',$out),\n                \'{numevents}\' => $evcnt,\n                \'{day}\' => $theday,\n                \'{dayzeros}\' => str_pad($theday, 2, \'0\', STR_PAD_LEFT),\n                \'{weekday}\' => ((is_array($this->dayNameFmt)) ? $this->dayNames[date(\'w\',$thistime)] : strftime($this->dayNameFmt, $thistime)),\n                \'{weekdayabbr}\' => strftime(\'%a\', $thistime),\n                \'{weekdayfull}\' => strftime(\'%A\', $thistime),\n                \'{week}\' => $smd_date[\'w\'],\n                \'{month}\' => $this->month,\n                \'{monthzeros}\' => str_pad($this->month, 2, \'0\', STR_PAD_LEFT),\n                \'{monthname}\' => ((is_array($this->mthNameFmt)) ? $this->mthNames[date(\'n\',$thistime)] : strftime($this->mthNameFmt, $thistime)),\n                \'{monthnameabbr}\' => strftime(\'%b\', $thistime),\n                \'{monthnamefull}\' => strftime(\'%B\', $thistime),\n                \'{year}\' => $this->year,\n                \'{shortyear}\' => strftime(\'%y\', $thistime),\n                \'{isoyear}\' => $smd_date[\'iy\'],\n                \'{shortisoyear}\' => strftime(smd_cal_reformat_win(\'%g\', $thistime), $thistime),\n            );\n            $cellout = parse(strtr($this->cellform, $reps));\n            $carray = array_merge($runningclass, $smd_cal_ucls);\n            $smd_cal_ucls = array();\n\n            return doTag($cellout,\'td\',join(\' \',$carray));\n        } else {\n            return doTag(join(\'\',$out),\'td\',join(\' \',$runningclass));\n        }\n    }\n\n    function display($static = false)\n    {\n        $sum = ($this->tblSummary) ? \' summary=\"\'.$this->tblSummary.\'\"\' : \'\';\n        $id = ($this->tableID) ? \' id=\"\'.$this->tableID.\'\"\' : \'\';\n        $c[] = ($this->tblCaption) ? \'<caption>\'.$this->tblCaption.\'</caption>\' : \'\';\n        $c[] = \'<thead>\';\n        $c[] = $this->dspHeader($static);\n        $c[] = $this->dspDayNames();\n        $c[] = \'</thead>\';\n        $c[] = $this->dspDayCells();\n\n        return doTag(join(\'\',$c),\'table\',$this->tableclass,$sum.$id);\n    }\n\n    function dspHeader($static)\n    {\n        global $pretext, $smd_calinfo, $permlink_mode;\n\n        $currmo = $this->month;\n        $curryr = $this->year;\n        $navpclass = $this->getNavInfo(\"pc\");\n        $navnclass = $this->getNavInfo(\"nc\");\n        $navparrow = $this->getNavInfo(\"pa\");\n        $navnarrow = $this->getNavInfo(\"na\");\n        $navid = $this->getNavInfo(\"id\");\n        $navpclass = ($navpclass) ? \' class=\"\'.$navpclass.\'\"\' : \'\';\n        $navnclass = ($navnclass) ? \' class=\"\'.$navnclass.\'\"\' : \'\';\n        $fopts = $this->fopts;\n\n        $sec = (isset($smd_calinfo[\'s\']) && !empty($smd_calinfo[\'s\'])) ? $smd_calinfo[\'s\'] : \'\';\n        foreach ($this->maintain as $col) {\n            switch ($col) {\n                case \"section\":\n                    if ($pretext[\'s\'] && $permlink_mode != \'year_month_day_title\') {\n                        $fopts = array(\'s\' => $pretext[\'s\']) + $fopts;\n                    }\n                    break;\n                case \"article\":\n                    if ($pretext[\'id\']) {\n                        $fopts = array(\'id\' => $pretext[\'id\']) + $fopts;\n                    }\n                    break;\n                case \"category\":\n                    if ($pretext[\'c\']) {\n                        $fopts = array(\'c\' => $pretext[\'c\']) + $fopts;\n                    }\n                    break;\n                case \"author\":\n                    if (gps(\'author\')) {\n                        $fopts = array(\'author\' => gps(\'author\')) + $fopts;\n                    }\n                    break;\n                case \"date\":\n                    if (gps(\'date\')) {\n                        $fopts = array(\'date\' => gps(\'date\')) + $fopts;\n                    }\n                    break;\n                case \"pg\":\n                    if ($pretext[\'pg\']) {\n                        $fopts = array(\'pg\' => $pretext[\'pg\']) + $fopts;\n                    }\n                    break;\n                case \"calid\":\n                    if ($this->tableID) {\n                        $fopts = array(\'calid\' => $this->tableID) + $fopts;\n                    }\n                    break;\n                default:\n                    if (gps($col)) {\n                        $fopts = array($col => gps($col)) + $fopts;\n                    }\n                    break;\n            }\n        }\n\n        $fopts = array_unique($fopts);\n        $filters = array();\n        $filterHid = array();\n        if (!$static) {\n            foreach($fopts as $key => $val) {\n                $filters[] = $key.\'=\'.$val;\n                $filterHid[] = hInput($key, $val);\n            }\n        }\n\n        // Week select list\n        if ($this->useSelector(\'week\') && !$static) {\n            $currwk = ($this->week) ? $this->week : date(\'W\', safe_strtotime($curryr.\"-\".$currmo.\"-1 12:00\"));\n            for ( $idx = 1; $idx <= 53; $idx++ ) {\n                $tagatts = \' value=\"\'.$idx.\'\"\';\n                if ( $idx == $currwk ) $tagatts .= \' selected=\"selected\"\';\n                $optiontags[] = doTag($this->selpfx[\'week\'].str_pad($idx, 2, \'0\', STR_PAD_LEFT).$this->selsfx[\'week\'], \'option\', \'\', $tagatts);\n            }\n            $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'w\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                . (($this->useSelector(\'year\')) ? \'\' : hInput($this->remap[\'y\'], $curryr));\n            $optiontags = array(); // Blank out\n        }\n\n        // Month select list - note mktime has the day forced to 1. If not you get\n        // bizarre repeated month names on the 31st of some months :-\\\n        if (!$this->useSelector(\'week\')) {\n            if ($this->useSelector(\'month\') && !$static) {\n                for ( $idx = 1; $idx <= 12; $idx++ ) {\n                    $tagatts = \' value=\"\'.$idx.\'\"\';\n                    if ( $idx == $currmo ) $tagatts .= \' selected=\"selected\"\';\n                    $optiontags[] = doTag($this->selpfx[\'month\'].((is_array($this->mthNameFmt)) ? $this->mthNames[date(\'n\',mktime(12,0,0,$idx,1))] : safe_strftime($this->mthNameFmt, mktime(12,0,0,$idx,1) )).$this->selsfx[\'month\'], \'option\', \'\', $tagatts);\n                }\n                $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'m\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                    . (($this->useSelector(\'year\')) ? \'\' : hInput($this->remap[\'y\'], $curryr));\n                $optiontags = array(); // Blank out\n            } else {\n                $selector[] = doTag($this->getMonthName(), \'span\', (($this->mywraptag) ? \'\' : $this->myclass));\n            }\n        }\n\n        // Year select list\n        $y0 = $this->eyr;\n        $y1 = $this->lyr;\n        if ($this->useSelector(\'year\') && ($y0 != $y1) && !$static) {\n            for ( $idx = $y0; $idx <= $y1; $idx++ ) {\n                $tagatts = \' value=\"\'.$idx.\'\"\';\n                if ( $idx == $curryr ) $tagatts .= \' selected=\"selected\"\';\n                $optiontags[] = doTag($this->selpfx[\'year\'].$idx.$this->selsfx[\'year\'], \'option\', \'\', $tagatts);\n            }\n            $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'y\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                    . (($this->useSelector(\'month\') || $this->useSelector(\'week\')) ? \'\' : hInput($this->remap[\'m\'], $currmo));\n        } else {\n            $selector[] = doTag($curryr, \'span\', (($this->mywraptag) ? \'\' : $this->myclass));\n        }\n\n        $request = serverSet(\'REQUEST_URI\');\n        $redirect = serverSet(\'REDIRECT_URL\');\n        if (!empty($redirect) && ($request != $redirect) && is_callable(\'_l10n_set_browse_language\')) {\n            // MLP in da house: use the redirect URL instead\n            $request = $redirect;\n        }\n        $urlp = parse_url($request);\n        $action = $urlp[\'path\'];\n\n        if ($permlink_mode == \'messy\') {\n            $out = makeOut(\'id\',\'s\',\'c\',\'q\',\'pg\',\'p\',\'month\');\n            foreach($out as $key => $val) {\n                if ($val) {\n                    $filters[] = $key.\'=\'.$val;\n                    $filterHid[] = hInput($key, $val);\n                }\n            }\n        }\n        $filterHid = array_unique($filterHid);\n        $filters = array_unique($filters);\n\n        $extras = \'\';\n        if (!$static && ( $this->useSelector(\'month\') || $this->useSelector(\'year\') )) {\n            if ($this->selbtn) {\n                $extras .= doTag(\'\', \'input\', \'smd_cal_input\', \' type=\"submit\" value=\"\'.$this->selbtn.\'\"\');\n            }\n            $extras .= join(n, $filterHid);\n        }\n\n        $selector = \'<form action=\"\'.$action.\'\" method=\"get\"\'.(($navid) ? \' id=\"\'.$navid.\'\"\' : \'\').\'>\'.doTag(join(sp, $selector).$extras, $this->mywraptag, $this->myclass).\'</form>\';\n\n        $nav_back_link = $this->navigation($curryr, $currmo, \'-\', $filters, $urlp[\'path\']);\n        $nav_fwd_link  = $this->navigation($curryr, $currmo, \'+\', $filters, $urlp[\'path\']);\n\n        $nav_back = (!$static && $nav_back_link) ? \'<a href=\"\'.$nav_back_link.\'\"\'.$navpclass.\'>\'.$navparrow.\'</a>\' : \'&nbsp;\';\n        $nav_fwd  = (!$static && $nav_fwd_link) ? \'<a href=\"\'.$nav_fwd_link.\'\"\'.$navnclass.\'>\'.$navnarrow.\'</a>\' : \'&nbsp;\';\n\n        $c[] = doTag($nav_back,\'th\');\n        $c[] = \'<th colspan=\"\'.(($this->showISOWeek) ? 6 : 5).\'\">\'.$selector.\'</th>\';\n        $c[] = doTag($nav_fwd,\'th\');\n\n        return doTag(join(\'\',$c),\'tr\', \'smd_cal_navrow\');\n    }\n\n    function navigation($year, $month, $direction, $flt, $url = \'\')\n    {\n        global $permlink_mode;\n\n        if($direction == \'-\') {\n            if($month - 1 < 1) {\n                $month = 12;\n                $year -= 1;\n            } else {\n                $month -= 1;\n            }\n        } else {\n            if($month + 1 > 12) {\n                $month = 1;\n                $year += 1;\n            } else {\n                $month += 1;\n            }\n        }\n\n        // Abort if we\'re about to go out of range\n        if ($year < $this->eyr || $year > $this->lyr) {\n            return \'\';\n        }\n\n        $flt[] = $this->remap[\'m\'].\"=$month\";\n        $flt[] = $this->remap[\'y\'].\"=$year\";\n\n        return $url . \"?\" . join(a, $flt);\n    }\n}\n\n/**\n * Basic Calendar data and display\n * http://www.oscarm.org/static/pg/calendarClass/\n * @author Oscar Merida\n * @created Jan 18 2004\n */\nclass SMD_Raw_Calendar\n{\n    var $gmt = 1, $lang, $debug = 0;\n    var $year, $eyr, $lyr, $month, $week;\n    var $dayNameFmt, $mthNameFmt, $dayNames, $mthNames, $startDay, $endDay, $firstDayOfWeek = 0, $startOffset = 0;\n    var $selectors, $selbtn, $selpfx, $selsfx;\n    var $showISOWeek, $ISOWeekHead, $ISOWeekCell;\n    var $cls_lev, $cls_pfx, $fopts;\n    var $evwraptag, $mywraptag;\n    var $rowclass, $cellclass, $emptyclass, $isoclass, $myclass, $tableID, $tblSummary, $tblCaption;\n    var $navpclass, $navnclass, $navparrow, $navnarrow, $navid;\n    var $holidays, $cellform, $hdrform, $maintain, $remap;\n    var $event_delim;\n\n    /**\n     * Constructor\n     *\n     * @param integer, year\n     * @param integer, month\n     * @return object\n     */\n    public function __construct($yr, $mo, $debug = 0)\n    {\n        $this->setDebug($debug);\n        $this->setYear($yr);\n        $this->setMonth($mo);\n        $this->setClassPrefix(\'smd_cal_\');\n\n        $this->startTime = strtotime( \"$yr-$mo-01 00:00\" );\n        $this->startDay = date( \'D\', $this->startTime );\n        $this->endDay = date( \'t\', $this->startTime );\n        $this->endTime = strtotime( \"$yr-$mo-\".$this->endDay.\" 23:59:59\" );\n        if ($this->debug) {\n            echo \"++ THIS MONTH\'S RENDERED CALENDAR [ start stamp // end date // start day // end stamp // end date // end day number ] ++\";\n            dmp($this->startTime, date(\'Y-m-d H:i:s\', $this->startTime), $this->startDay, $this->endTime, date(\'Y-m-d H:i:s\', $this->endTime), $this->endDay);\n        }\n        $this->setNameFormat(\'%a\', \'d\');\n        $this->setNameFormat(\'%B\', \'m\');\n        $this->setFirstDayOfWeek(0);\n        $this->setShowISOWeek(\'\');\n        $this->setTableID(\'\');\n        $this->setTableClass(\'\');\n    }\n\n    function useSelector($val)\n    {\n        return in_array($val, $this->selectors);\n    }\n\n    function getDayName($day)\n    {\n        return ($this->dayNames[$day%7]);\n    }\n\n    function getMonthName()\n    {\n        if (is_array($this->mthNameFmt)) {\n            return $this->mthNames[date(\'n\',$this->startTime)];\n        } else {\n            return strftime($this->mthNameFmt, $this->startTime);\n        }\n    }\n\n    function getNavInfo($type)\n    {\n        $r = \'\';\n        switch ($type) {\n            case \"id\": $r = $this->navid; break;\n            case \"pc\": $r = $this->navpclass; break;\n            case \"nc\": $r = $this->navnclass; break;\n            case \"pa\": $r = $this->navparrow; break;\n            case \"na\": $r = $this->navnarrow; break;\n        }\n        return $r;\n    }\n\n    function setDebug($d)\n    {\n        $this->debug = $d;\n    }\n\n    function setGMT($b)\n    {\n        $this->gmt = $b;\n    }\n\n    function setLang($code)\n    {\n        $this->lang = $code;\n    }\n\n    function setSummary($txt)\n    {\n        $this->tblSummary = $txt;\n    }\n\n    function setCaption($txt)\n    {\n        $this->tblCaption = $txt;\n    }\n\n    function setCellForm($frm)\n    {\n        $this->cellform = $frm;\n    }\n\n    function setHdrForm($frm)\n    {\n        $this->hdrform = $frm;\n    }\n\n    function setTableID($id)\n    {\n        $this->tableID = $id;\n    }\n\n    function setYear($yr)\n    {\n        $this->year = $yr;\n    }\n\n    function setEYear($yr)\n    {\n        $this->eyr = $yr;\n    }\n\n    function setLYear($yr)\n    {\n        $this->lyr = $yr;\n    }\n\n    function setMonth($mth)\n    {\n        $this->month = (int)$mth;\n    }\n\n    function setWeek($wk)\n    {\n        if ($wk) {\n            $wk = str_pad($wk, 2, \'0\', STR_PAD_LEFT);\n            $this->week = $wk;\n            $this->month = safe_strftime(\"%m\", strtotime($this->year.\"W\".$wk));\n        }\n    }\n\n    function setNavKeep($ar)\n    {\n        $this->maintain = $ar;\n    }\n\n    function setShowISOWeek($val)\n    {\n        $this->showISOWeek = ($val) ? true : false;\n        if ($val) {\n            $val = do_list($val);\n            $this->ISOWeekHead = $val[0];\n            $this->ISOWeekCell = (isset($val[1])) ? $val[1] : \'{week}\';\n        }\n    }\n\n    function setRemap($map)\n    {\n        $this->remap = $map;\n    }\n\n    function setClassLevels($cls)\n    {\n        $this->cls_lev = $cls;\n    }\n\n    function setClassPrefix($cls)\n    {\n        $this->cls_pfx = $cls;\n    }\n\n    function setEventWraptag($wrap)\n    {\n        $this->evwraptag = $wrap;\n    }\n\n    function setMYWraptag($wrap)\n    {\n        $this->mywraptag = $wrap;\n    }\n\n    function setTableClass($cls)\n    {\n        $this->tableclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setRowClass($cls)\n    {\n        $this->rowclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setCellClass($cls)\n    {\n        $this->cellclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setEmptyClass($cls)\n    {\n        $this->emptyclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setISOWeekClass($cls)\n    {\n        $this->isoclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setDelim($dlm)\n    {\n        $this->event_delim = $dlm;\n    }\n\n    function setNavInfo($clsp, $clsn, $arrp, $arrn, $nid)\n    {\n        $this->navpclass = ($clsp) ? $this->cls_pfx.$clsp : \'\';\n        $this->navnclass = ($clsn) ? $this->cls_pfx.$clsn : \'\';\n        $this->navparrow = ($arrp) ? $arrp : \'\';\n        $this->navnarrow = ($arrn) ? $arrn : \'\';\n        $this->navid = ($nid) ? $this->cls_pfx.$nid : \'\';\n    }\n\n    function setMYClass($cls)\n    {\n        $this->myclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setFilterOpts($f)\n    {\n        $this->fopts = $f;\n    }\n\n    function setHolidays($hols)\n    {\n        $this->holidays = $hols;\n    }\n\n    function setSelectors($sel, $btn)\n    {\n        foreach ($sel as $idx => $item) {\n            $selparts = explode(\":\", $item);\n            $sel[$idx] = $selparts[0];\n            $this->selpfx[$selparts[0]] = (isset($selparts[1])) ? $selparts[1] : \'\';\n            $this->selsfx[$selparts[0]] = (isset($selparts[2])) ? $selparts[2] : \'\';\n        }\n        $this->selectors = $sel;\n        $this->selbtn = $btn;\n    }\n\n    function setFirstDayOfWeek($d)\n    {\n        $this->firstDayOfWeek = ((int)$d <= 6 and (int)$d >= 0) ? (int)$d : 0;\n        $this->startOffset = date(\'w\', $this->startTime) - $this->firstDayOfWeek;\n        if ( $this->startOffset < 0 ) {\n            $this->startOffset = 7 - abs($this->startOffset);\n        }\n    }\n\n    /**\n     *\n     * frm: any valid PHP strftime() string or ABBR/FULL\n     * typ: d to set day, m to set month format\n     */\n    function setNameFormat($frm, $typ = \"d\")\n    {\n        switch ($frm) {\n            case \"full\":\n            case \"FULL\":\n                $fmt = ($typ == \'d\') ? \"%A\" : \"%B\";\n                break;\n            case \"abbr\":\n            case \"ABBR\":\n                $fmt = ($typ == \'d\') ? \"%a\" : \"%b\";\n                break;\n            default:\n                if (strpos($frm, \'%\') === 0) {\n                    $fmt = $frm;\n                } else {\n                    $frm = trim($frm, \'{}\');\n                    $frm = do_list($frm);\n                    $fmt = $frm;\n                }\n                break;\n        }\n\n        if ($typ == \"d\") {\n            $this->dayNameFmt = $fmt;\n            $this->dayNames = array();\n\n            // This is done to make sure Sunday is always the first day of our array\n            $start = 0;\n            $end = $start + 7;\n            $sunday = strtotime(\'1970-Jan-04 12:00:00\');\n\n            for($i=$start; $i<$end; $i++) {\n                if (is_array($fmt)) {\n                    $this->dayNames[] = $fmt[$i-$start];\n                } else {\n                    $this->dayNames[] = ucfirst(strftime($fmt, ($sunday + (86400*$i))));\n                }\n            }\n        } else {\n            $this->mthNameFmt = $fmt;\n            $this->mthNames = array();\n            for ($i=0; $i<12; $i++) {\n                if (is_array($fmt)) {\n                    $this->mthNames[$i+1] = $fmt[$i];\n                }\n            }\n        }\n    }\n\n    /**\n     * Return markup for displaying the calendar.\n     *\n     * @return\n     * @public\n     */\n    function display()\n    {\n        $id = ($this->tableID) ? \' id=\"\'.$this->tableID.\'\"\' : \'\';\n        $c[] = \'<table\'.$id.\'>\';\n        $c[] = \'<thead>\' . $this->dspDayNames() . \'</thead>\';\n        $c[] = $this->dspDayCells();\n        $c[] = \'</table>\';\n\n        return join(\'\',$c);\n    }\n\n    /**\n     * Displays the row of day names.\n     *\n     * @return string\n     * @private\n     */\n    function dspDayNames()\n    {\n        if ($this->hdrform) {\n            $reps = array(\n                \'{firstday}\' => $this->firstDayOfWeek,\n                \'{daynames}\' => join(\',\', $this->dayNames),\n                \'{isoweekhead}\' => $this->ISOWeekHead,\n                \'{week}\' => date(\'W\', $this->startTime),\n                \'{month}\' => date(\'n\', $this->startTime),\n                \'{year}\' => date(\'Y\', $this->startTime),\n                \'{isoyear}\' => date(\'o\', $this->startTime),\n            );\n\n            return parse(strtr($this->hdrform, $reps));\n        } else {\n            $c[] = \'<tr class=\"smd_cal_daynames\">\';\n\n            $i = $this->firstDayOfWeek;\n            $j = 0; // count number of days displayed\n            $end = false;\n\n            if ($this->showISOWeek) {\n                $c[] = \"<th>\".$this->ISOWeekHead.\"</th>\";\n            }\n            for($j = 0; $j<=6; $j++, $i++) {\n                if($i == 7) { $i = 0; }\n                $c[] = \'<th>\'.$this->getDayName($i).\"</th>\";\n            }\n\n            $c[] = \'</tr>\';\n            return join(\'\',$c);\n        }\n    }\n\n    /**\n     * Displays all day cells for the month.\n     *\n     * @return string\n     * @private\n     */\n    function dspDayCells()\n    {\n        $i = 0; // cell counter\n        $emptyClass = $this->emptyclass;\n        $isoClass = $this->isoclass;\n        $rowClass = $this->rowclass;\n        $rowClass = ($rowClass) ? \' class=\"\'.$rowClass.\'\"\' : \'\';\n\n        $c[] = \'<tr\'.$rowClass.\'>\';\n\n        if ($this->showISOWeek) {\n            $reps = array(\n                \'{week}\' => date(\'W\', $this->startTime),\n                \'{month}\' => date(\'n\', $this->startTime),\n                \'{year}\' => date(\'Y\', $this->startTime),\n                \'{isoyear}\' => date(\'o\', $this->startTime),\n            );\n            $wkcell = strtr($this->ISOWeekCell, $reps);\n            $c[] = \'<td class=\"\'.$isoClass.\'\">\'.$wkcell.\'</td>\';\n        }\n        // first display empty cells based on what weekday the month starts in\n        for( $j=0; $j<$this->startOffset; $j++ )    {\n            $i++;\n            $c[] = \'<td class=\"\'.$emptyClass.\'\">&nbsp;</td>\';\n        } // end offset cells\n\n        // write out the rest of the days, at each sunday, start a new row.\n        for( $d=1; $d<=$this->endDay; $d++ ) {\n            $i++;\n            $c[] = $this->dspDayCell( $d );\n            if ( $i%7 == 0 ) { $c[] = \'</tr>\'; }\n            if ( $d<$this->endDay && $i%7 == 0 ) {\n                $c[] = \'<tr\'.$rowClass.\'>\';\n                if ($this->showISOWeek) {\n                    // **Not** using safe_strtotime() here to cater for an operating timezone that differs from the server timezone.\n                    // Probably should do this in other places too but no bugs have been filed yet so it can be done on a\n                    // case-by-case basis\n                    $theTime = strtotime($this->year.\"-\".$this->month.\"-\".(int)($d + 1) .\" 00:00\");\n                    $reps = array(\n                        \'{week}\' => date(\'W\', $theTime),\n                        \'{month}\' => date(\'n\', $theTime),\n                        \'{year}\' => date(\'Y\', $theTime),\n                        \'{isoyear}\' => date(\'o\', $theTime),\n                    );\n                    $wkcell = strtr($this->ISOWeekCell, $reps);\n                    $c[] = \'<td class=\"\'.$isoClass.\'\">\'.$wkcell.\'</td>\';\n                }\n            }\n        }\n        // fill in the final row\n        $left = 7 - ( $i%7 );\n        if ( $left < 7) {\n            for ( $j=0; $j<$left; $j++ )    {\n              $c[] = \'<td class=\"\'.$emptyClass.\'\">&nbsp;</td>\';\n            }\n            $c[] = \"\\n\\t</tr>\";\n        }\n        return \'<tbody>\' . join(\'\',$c) . \'</tbody>\';\n    }\n\n    /**\n     * Outputs the contents for a given day.\n     *\n     * @param integer, day\n     * @abstract\n     */\n    function dspDayCell($day)\n    {\n        return \'<td>\'.$day.\'</td>\';\n    }\n}\n\nfunction smd_cal_minilink($row, $day, $month, $year, $use_posted = false)\n{\n    global $permlink_mode;\n\n    $lang = \'\';\n    $request = serverSet(\'REQUEST_URI\');\n    $redirect = serverSet(\'REDIRECT_URL\');\n    if (!empty($redirect) && ($request != $redirect) && is_callable(\'_l10n_set_browse_language\')) {\n        // MLP in da house so extract the language currently in use -- is there an MLP-native method for this?\n        $reqparts = explode(\'/\', $request);\n        $redparts = explode(\'/\', $redirect);\n        $lang = join(\'\', array_diff($redparts, $reqparts)) . \'/\';\n    }\n\n    if( $permlink_mode == \'year_month_day_title\' ) {\n        $linkdate = ($use_posted) ? date(\'Y/m/d\', $row[\'uPosted\']) : $year.\'/\'.str_pad($month,2,\"0\",STR_PAD_LEFT).\'/\'.str_pad($day,2,\"0\",STR_PAD_LEFT);\n        $href = \' href=\"\'.hu.$lang.$linkdate.\'\"\';\n    } else {\n        $linkdate = ($use_posted) ? date(\'Y-m-d\', $row[\'uPosted\']) : $year.\'-\'.str_pad($month,2,\"0\",STR_PAD_LEFT).\'-\'.str_pad($day,2,\"0\",STR_PAD_LEFT);\n        $href = \' href=\"\'.hu.$lang.\'?date=\'.$linkdate;\n        if($row[\'Section\']) { $href = $href.a.\'s=\'.$row[\'Section\']; }\n//      if($category) { $href = $href.a.\'c=\'.$category; }\n        $href .= \'\"\';\n    }\n\n    return tag($day, \'a\', $href);\n}\n\n// Perform one of two types of test: a flag-based test, or an info-based test\nfunction smd_if_cal($atts, $thing)\n{\n    global $smd_cal_flag, $smd_calinfo, $smd_date;\n\n    extract(lAtts(array(\n        \'flag\'    => \'\',\n        \'calid\'   => \'\',\n        \'isoyear\' => \'\',\n        \'year\'    => \'\',\n        \'month\'   => \'\',\n        \'week\'    => \'\',\n        \'day\'     => \'\',\n        \'logic\'   => \'or\',\n        \'debug\'   => \'0\',\n    ), $atts));\n\n    $flag = do_list($flag);\n    $ctr = $num = 0;\n\n    if ($debug) {\n        dmp($atts);\n    }\n\n    if ($flag && $flag[0] != \'\') {\n        $num += count($flag);\n        foreach ($flag as $whatnot) {\n            if (empty($whatnot)) continue;\n            $ctr += (in_array($whatnot, $smd_cal_flag) || ($whatnot == \'SMD_ANY\' && !empty($smd_cal_flag))) ? 1 : 0;\n        }\n    }\n    if ($calid) {\n        $num++;\n        $ctr += ($smd_calinfo[\'id\'] === $calid) ? 1 : 0;\n    }\n    foreach (array(\"iy\" => \"isoyear\", \"y\" => \"year\", \"m\" => \"month\", \"w\" => \"week\", \"d\" => \"day\") as $idx => $test) {\n        $tester = $$test;\n        $compare = $smd_date[$idx];\n\n        if ($tester) {\n            $num++;\n            preg_match(\'/([!=<>]+)?([\\d]+)/\', $tester, $matches);\n            if ($debug) {\n                dmp(\"TEST IF: \". $compare. (($matches[1]) ? $matches[1] : \'=\') . $matches[2] );\n            }\n            switch ($matches[1]) {\n                case \"!\":\n                    $ctr += ($compare!=$matches[2]) ? 1 : 0;\n                    break;\n                case \">\":\n                    $ctr += ($compare>$matches[2]) ? 1 : 0;\n                    break;\n                case \">=\":\n                    $ctr += ($compare>=$matches[2]) ? 1 : 0;\n                    break;\n                case \"<\":\n                    $ctr += ($compare<$matches[2]) ? 1 : 0;\n                    break;\n                case \"<=\":\n                    $ctr += ($compare<=$matches[2]) ? 1 : 0;\n                    break;\n                default:\n                    $ctr += ($compare==$matches[2]) ? 1 : 0;\n                    break;\n            }\n        }\n    }\n    $result = (($ctr === $num && $logic == \"and\") || $ctr > 0 && $logic == \"or\") ? true : false;\n    return parse(EvalElse($thing, $result));\n}\n\n// Convenient wrapper for smd_cal_info use=\"event\"\nfunction smd_event_info($atts)\n{\n    $atts[\'use\'] = \'event\';\n    return smd_cal_info($atts);\n}\n\n// Grab additional information about the current event\nfunction smd_cal_info($atts)\n{\n    global $pretext, $thisarticle, $smd_cal_flag, $smd_calinfo, $smd_date, $smd_eventinfo;\n\n    extract(lAtts(array(\n        \'type\'        => \'flag\',\n        \'join\'        => \' \',\n        \'join_prefix\' => \'SMD_AUTO\',\n        \'html\'        => 0,\n        \'escape\'      => \'html\',\n        \'use\'         => \'cal\', // \'cal\' for calendar (uses $smd_calinfo) or \'event\' for event lists (uses $smd_eventinfo). Not publically alterable\n        \'debug\'       => 0,\n    ), $atts));\n\n    // Validate $use attribute\n    $use = (in_array($use, array(\'cal\', \'event\'))) ? $use : \'cal\';\n    $cal_global = ${\'smd_\'.$use.\'info\'};\n\n    if ($debug && $thisarticle) {\n        echo \'++ Event name ++\';\n        dmp($thisarticle[\'title\']);\n    }\n    if ($debug && $cal_global) {\n        echo \'++ Available \'.$use.\' info ++\';\n        dmp($cal_global);\n    }\n\n    if ($debug && $smd_date) {\n        echo \'++ Available date info ++\';\n        dmp($smd_date);\n    }\n\n    if ($debug && $smd_cal_flag) {\n        echo \'++ Available flag info ++\';\n        dmp($smd_cal_flag);\n    }\n\n    // Type: 0=date, 1=smd_cal/eventinfo, 2=pretext, 3=thisarticle, 4(or other)=user value\n    $map = array(\n        \'year\' => array(0, \'y\'),\n        \'isoyear\' => array(0, \'iy\', \'y\'),\n        \'month\' => array(0, \'m\'),\n        \'week\' => array(0, \'w\'),\n        \'day\' => array(0, \'d\'),\n        \'section\' => array(3, \'\', \'s\'),\n        \'category1\' => array(3, \'\', \'c\'),\n        \'category2\' => array(3, \'\', \'c\'),\n        \'thisid\' => array(3, \'thisid\', \'id\'),\n        \'article\' => array(1, \'artid\', \'id\'),\n        \'calid\' => array(1, \'id\', \'calid\'),\n        \'category\' => array(2, \'c\'),\n        \'realname\' => array(2, \'author\'),\n    );\n    $join = ($html) ? a : $join; // html mode forces ampersand join\n    $type = do_list($type);\n    $ret = array();\n    foreach ($type as $item) {\n        $pts = do_list($item, \':\');\n        $item = $pts[0];\n\n        if (empty($item)) continue;\n        // Default html id\n        $hid = (isset($map[$item])) ? ((isset($map[$item][2])) ? $map[$item][2] : $map[$item][1]) : $item;\n        // User-specified htmlid overrides it\n        $hid = (count($pts) > 1 && !empty($pts[1])) ? $pts[1] : $hid;\n        if ($item == \"flag\") {\n            $ret[] = (($join_prefix==\"SMD_AUTO\") ? $join : \'\').join($join, $smd_cal_flag);\n        } else if ($item == \"author\" || $item == \"realname\") {\n            $currauthor = ($thisarticle == NULL) ? \'\' : author(array());\n            if ($currauthor) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $currauthor;\n            }\n        } else if ($item == \"s\") {\n            $sec = (!empty($pretext[\'s\'])) ? $pretext[\'s\'] : ((isset($cal_global[\'s\']) && !empty($cal_global[\'s\'])) ? $cal_global[\'s\'] : \'\');\n            if ($sec) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $sec;\n            }\n        } else if (isset($map[$item])) {\n            $typ = $map[$item][0];\n            $idx = empty($map[$item][1]) ? $item : $map[$item][1];\n            switch ($typ) {\n                case 0:\n                    if ($smd_date[$idx]) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $smd_date[$idx];\n                    }\n                    break;\n                case 1:\n                    if (!empty($cal_global[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $cal_global[$idx];\n                    }\n                    break;\n                case 2:\n                    if (!empty($pretext[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $pretext[$idx];\n                    }\n                    break;\n                case 3:\n                    if ($thisarticle != NULL && isset($thisarticle[$idx]) && !empty($thisarticle[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $thisarticle[$idx];\n                    }\n                    break;\n            }\n        } else if (array_key_exists($item, $pretext)) {\n            if ($pretext[$item]) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $pretext[$item];\n            }\n        } else if (isset($cal_global[$item])) {\n            if (!empty($cal_global[$item])) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $cal_global[$item];\n            }\n        } else {\n            if ($thisarticle != NULL && isset($thisarticle[$item]) && !empty($thisarticle[$item])) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $thisarticle[$item];\n            }\n        }\n    }\n//    $ret = array_unique($ret);\n    $out = (($join_prefix==\"SMD_AUTO\") ? (($html) ? \'?\' : \'\') : $join_prefix).join($join, $ret);\n    return ($escape==\'html\') ? htmlspecialchars($out) : $out;\n}\n\n// Return a formatted timestamp, with optional \'time now\' override\nfunction smd_cal_now($atts)\n{\n    global $dateformat;\n\n    extract(lAtts(array(\n        \'format\' => $dateformat,\n        \'now\'    => \'\',\n        \'offset\' => \'\',\n        \'gmt\'    => \'\',\n        \'lang\'   => \'\',\n    ), $atts));\n\n    $theDay = (gps(\'d\') && is_numeric(gps(\'d\'))) ? (int)gps(\'d\') : safe_strftime(\'%d\');\n    $theMonth = (gps(\'m\') && is_numeric(gps(\'m\'))) ? (int)gps(\'m\') : safe_strftime(\'%m\');\n    $theYear = (gps(\'y\') && is_numeric(gps(\'y\'))) ? (int)gps(\'y\') : safe_strftime(\'%Y\');\n    if ($now) {\n        $now = str_replace(\"?month\", date(\'F\', mktime(12,0,0,$theMonth,$theDay,$theYear)), $now);\n        $now = str_replace(\"?year\", $theYear, $now);\n        $now = str_replace(\"?day\", $theDay, $now);\n        $now = is_numeric($now) ? $now : strtotime($now);\n    } else {\n        $now = time();\n    }\n\n    if ($offset) {\n        $now = strtotime($offset, $now);\n    }\n\n    $format = smd_cal_reformat_win($format, $now);\n    return safe_strftime($format, $now, $gmt, $lang);\n}\n\n// Set user-defined classes for a cell\nfunction smd_cal_class($atts)\n{\n    global $smd_cal_ucls;\n\n    extract(lAtts(array(\n        \'name\' => \'\',\n    ), $atts));\n\n    $name = do_list($name);\n    $smd_cal_ucls = array_merge($smd_cal_ucls, $name);\n}\n\n// <txp:article_custom /> replacement(ish) tag that understands how to handle recurring events\nfunction smd_article_event($atts, $thing = null)\n{\n    global $prefs, $pretext, $thispage, $thisarticle, $smd_eventinfo, $smd_cal_flag, $smd_date;\n\n    extract(lAtts(array(\n        \'time\'        => \'any\',\n        \'type\'        => \'standard,recur,multi\',\n        \'expired\'     => \'\',\n        \'id\'          => \'\',\n        \'category\'    => null,\n        \'section\'     => \'\',\n        \'author\'      => \'\',\n        \'realname\'    => \'\',\n        \'custom\'      => \'\',\n        \'status\'      => \'live\',\n        \'param_delim\' => \':\',\n        \'sort\'        => \'\',\n        \'form\'        => \'\',\n        \'stepfield\'   => \'\',\n        \'skipfield\'   => \'\',\n        \'omitfield\'   => \'\',\n        \'extrafield\'  => \'\',\n        \'allspanned\'  => \'0\',\n        \'datefields\'  => \'\',\n        \'month\'       => \'\',\n        \'from\'        => \'\',\n        \'to\'          => \'\',\n        \'offset\'      => 0,\n        \'limit\'       => \'10\',\n        \'eventlimit\'  => \'10\',\n        \'paging\'      => \'1\',\n        \'pageby\'      => \'\',\n        \'pgonly\'      => \'\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'class\'       => \'\',\n        \'debug\'       => 0,\n    ), $atts));\n\n    // Phase 1 filters\n    $filtSQL = array();\n    $subSQL = array();\n    if ($category !== null) {\n        $uncats = false;\n        $allcats = do_list($category);\n        if (($pos = array_search(\'SMD_UNCAT\', $allcats)) !== false) {\n            $uncats = true;\n            unset($allcats[$pos]);\n            $category = join(\',\', $allcats);\n        }\n        $tmp = doQuote(join(\"\',\'\", doSlash(do_list($category))));\n        $filtSQL[] = ($uncats ? \"(Category1 = \'\' AND Category2 = \'\')\" : \'\') .\n            ($uncats && $allcats ? \" OR \" : \'\') .\n            ($allcats ? \'( Category1 IN (\'.$tmp.\') OR Category2 IN (\'.$tmp.\') )\' : \'\');\n    }\n    if($section) {\n        $filtSQL[] = \'Section IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($section)))).\')\';\n    }\n    if($realname) {\n        $authors = safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doArray(do_list($realname), \'urldecode\'))) .\')\' );\n        $author = join(\',\', $authors);\n    }\n    if($author) {\n        $filtSQL[] = \'AuthorID IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($author)))).\')\';\n    }\n    if($id) {\n        $filtSQL[] = \'ID IN (\'.join(\',\', array_map(\'intval\', do_list($id))).\')\';\n    }\n    if($custom) {\n        $custs = do_list($custom);\n        $validOps = array(\'=\', \'!=\', \'>\', \'>=\', \'<\', \'<=\', \'like\', \'not\', \'not like\');\n        foreach ($custs as $set) {\n            if (strpos($set, $param_delim) !== false) {\n                $clauseOpts = do_list($set, $param_delim);\n                $fld = $clauseOpts[0];\n                $oper = ((count($clauseOpts) == 3) && (in_array(strtolower($clauseOpts[1]), $validOps))) ? $clauseOpts[1] : \'=\';\n                $clause = (count($clauseOpts) == 3) ? $clauseOpts[2] : ((count($clauseOpts) == 2) ? $clauseOpts[1] : \'\');\n                $filtSQL[] = $fld . \" $oper \" . doQuote(doSlash($clause));\n            }\n        }\n    }\n\n    $type = do_list($type);\n    $pageby = (empty($pageby) ? $limit : $pageby);\n\n    foreach ($type as $evtyp) {\n        switch($evtyp) {\n            case \'standard\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" = \'\' AND Expires IS NULL)\";\n                }\n                break;\n            case \'recur\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" != \'\')\";\n                }\n                break;\n            case \'multi\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" = \'\' AND Expires IS NOT NULL)\";\n                }\n                break;\n        }\n    }\n    if ($subSQL) {\n        $filtSQL[] = \'(\'.join(\' OR \', $subSQL).\')\';\n    }\n\n    $status = ($status) ? $status : \'live\'; // in case status has been emptied\n    $status = do_list($status);\n    $stati = array();\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } else if (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n    $filtSQL[] = \'Status IN (\'.doQuote(join(\"\',\'\", $stati)).\')\';\n\n    $expired = ($expired) ? $expired : $prefs[\'publish_expired_articles\'];\n    if (!$expired) {\n        $filtSQL[] = \'(now() <= Expires OR Expires IS NULL)\';\n    }\n\n    // Sorting rules: data is sorted once as it is extracted via SQL and then again after the fake dates have been inserted\n    $sort = (empty($sort)) ? \'Posted asc\' : $sort;\n    $sort = do_list($sort);\n    $sortPrefix = \"SORT_\";\n    $sortOrder = array();\n    for ($idx = 0; $idx < count($sort); $idx++) {\n        $sorties = explode(\' \', $sort[$idx]);\n        if (count($sorties) <= 1) {\n            $sorties[1] = \"asc\";\n        }\n        $sorties[1] = $sortPrefix.(($sorties[1] == \"desc\") ? \'DESC\' : \'ASC\');\n        $sortOrder[] = array(\"by\" => $sorties[0], \"dir\" => $sorties[1]);\n    }\n    $filtSQL = join(\' AND \', $filtSQL);\n    $filtSQL .= \' ORDER BY \'.join(\',\',doSlash($sort));\n\n    $grabCols = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\';\n    $evlist = safe_rows($grabCols, \'textpattern\', $filtSQL, $debug);\n\n    if ($debug>2) {\n        echo \"++ RECORD SET ++\";\n        dmp($evlist);\n    }\n    $all_evs = array();\n    $ev_tally = array();\n    $now = time() + tz_offset();\n\n    $eventlimit = do_list($eventlimit);\n    if (count($eventlimit) == 1) {\n        $eventlimit[1] = $eventlimit[0];\n    }\n\n    $datefields = do_list($datefields);\n\n    // Phase 2: expand any recurring dates and collate all events that fall within the alloted ranges\n    foreach ($evlist as $row) {\n        $ev_posted = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row[\'uPosted\']+tz_offset($row[\'uPosted\']);\n        $ev_expires = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\']+tz_offset($row[\'uExpires\']));\n\n        $skip = ($skipfield && $row[$skipfield] != \'\');\n        $omit = ($omitfield && $row[$omitfield] != \'\');\n        $recur = ($stepfield && $row[$stepfield] != \'\');\n        $extra = ($extrafield && $row[$extrafield] != \'\');\n        $multi = ($ev_expires > $ev_posted && (date(\"d-m-Y\", $ev_expires) != date(\"d-m-Y\", $ev_posted))) ? true : false;\n\n        // If end < start the user-specified dates cannot be trusted\n        if ($ev_expires != 0 && $ev_expires <= $ev_posted) {\n            $ev_posted = $row[\'uPosted\']+tz_offset($row[\'uPosted\']);\n            $ev_expires = (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\']+tz_offset($row[\'uExpires\']));\n            trigger_error(\'Expiry cannot be before start date in \"\'.$row[\'Title\'].\'\": ignored\', E_USER_WARNING);\n        }\n        if ($debug > 1) {\n            echo \'++ EVENT START // END ++\';\n            dmp($row[\'Title\']);\n            dmp($ev_posted, date(\'Y-m-d H:i:s\', $ev_posted), $ev_expires, date(\'Y-m-d H:i:s\', $ev_expires));\n        }\n\n        // Rewrite the start/end dates in case they are user-defined\n        $row[\'uPosted\'] = $ev_posted;\n        $row[\'Posted\'] = date(\"Y-m-d H:i:s\", $ev_posted);\n        $row[\'uExpires\'] = $ev_expires;\n        $row[\'Expires\'] = ($ev_expires==0) ? \'0000-00-00 00:00:00\' : date(\"Y-m-d H:i:s\", $ev_expires);\n\n        $diff = ($ev_expires == 0) ? 0 : $ev_expires - $ev_posted;\n        $ev_month = date(\'m\', $ev_posted);\n        $ev_year = date(\'Y\', $ev_posted);\n        $ev_hr = date(\'H\', $ev_posted);\n        $ev_mn = date(\'i\', $ev_posted);\n        $ev_sc = date(\'s\', $ev_posted);\n        $ignore = array();\n\n        // Generate a skip array for this event\n        if ($skip) {\n            $ignores = do_list($row[$skipfield]);\n            foreach ($ignores as $val) {\n                $igrng = smd_expand_daterange($val, $ev_posted, $ev_expires);\n                foreach ($igrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval); // Force each date to a known format\n                }\n            }\n        }\n        // Append any omitted events\n        if ($omit) {\n            $omits = do_list($row[$omitfield]);\n            foreach ($omits as $val) {\n                $omrng = smd_expand_daterange($val, $ev_posted, $ev_expires);\n                foreach ($omrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval);\n                }\n            }\n        }\n        if ($debug > 1 && $ignore) {\n            echo \'++ IGNORED DATES ++\';\n            dmp($ignore);\n        }\n\n        // Does the base event deserve to be in the results?\n        if (smd_include_event($ev_posted, $now, $ignore, $time, $from, $to, $month)) {\n            $all_evs[] = array(\'ev\' => $row, \'flags\' => ($multi ? array(\'multifirst\') : array(\'standard\')) );\n            $ev_tally[$row[\'uPosted\']] = (isset($ev_tally[$row[\'uPosted\']])) ? $ev_tally[$row[\'uPosted\']]+1 : 1;\n        }\n\n        // Add any extra dates for this event\n        if ($extra) {\n            $xtra = do_list($row[$extrafield]);\n            $xtras = array();\n\n            // Make up an array of all extra dates\n            foreach ($xtra as $val) {\n                if (strpos($val, \"+\") === false) {\n                    $exrng = smd_expand_daterange($val);\n                    $xtras[] = date(\"Y-m-d\", $exrng[0]);\n                    $spex = 0;\n                } else {\n                    $fake_diff = safe_strtotime(date(\"Y-M-d\", $ev_expires) . \" 23:59:59\");\n                    $fdiff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;\n                    $chk = $allspanned && $multi && !$recur;\n                    $spidth = $chk ? ceil($fdiff / (60*60*24)) : 1; // days between dates\n                    $val = rtrim($val, \'+\');\n                    for ($jdx = 1; $jdx <= $spidth; $jdx++) {\n                        $xtras[] = date(\"Y-m-d\", safe_strtotime($val . (($jdx==1) ? \'\' : \'+\'.($jdx-1).\' days\')));\n                    }\n                    $spex = $chk ? 1 : 0;\n                }\n            }\n\n            $xtras = array_unique($xtras);\n\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($xtras as $jdx => $val) {\n                $tm = strtotime($val);\n                $flags = array(\'extra\');\n\n                // No $ignore for additional events, as they always show up\n                if (smd_include_event($tm+$stampoff, $now, array(), $time, $from, $to, $month)) {\n                    $fakerow = $row;\n                    $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff);\n                    $fakerow[\'uPosted\'] = $tm+$stampoff;\n                    if ($diff > 0) {\n                        $fakerow[\'Expires\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff+$diff);\n                        $fakerow[\'uExpires\'] = $tm+$stampoff+$diff;\n                    }\n                    if ($spex) {\n                        $flags[] = ($jdx==0) ? \'multifirst\' : (($jdx==$spidth-1) ? \'multilast\' : \'multi\');\n                    }\n                    $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                    $ev_tally[$fakerow[\'uPosted\']] = (isset($ev_tally[$fakerow[\'uPosted\']])) ? $ev_tally[$fakerow[\'uPosted\']]+1 : 1;\n                }\n            }\n        }\n\n        if ($recur) {\n            $flags = array(\'recurfirst\');\n            $freq = do_list($row[$stepfield]);\n            $monthly = false;\n            $currmonth = $ev_month;\n            $curryear = $ev_year;\n            foreach ($freq as $interval) {\n                $fakerow = $row;\n                $cstamp = $ev_posted;\n                for($idx = 0; $idx < 99999; $idx++) {\n                    $lstamp = $cstamp;\n                    if ((isset($ev_tally[$row[\'uPosted\']]) && ($ev_tally[$row[\'uPosted\']] >= $eventlimit[0])) || ($to && $cstamp > safe_strtotime($to))) {\n                        break;\n                    }\n                    $ival = str_replace(\"?month\", date(\'F\', mktime(0,0,0,$currmonth,1)), $interval);\n                    $ival = str_replace(\"?year\", $curryear, $ival);\n\n                    if (strpos($ival, \"last\") === 0) {\n                        $ival = date(\"l, F jS Y\", strtotime( $ival, mktime(12, 0, 0, date(\"n\", mktime(0,0,0,$currmonth,1,$curryear))+1, 1, $curryear) ));\n                        $monthly = true;\n                    } else if (strpos($ival, \"first\") === 0) {\n                        $ival = date(\"l, F jS Y\", strtotime( $ival, mktime(12, 0, 0, (($currmonth>1) ? $currmonth-1 : 12), date(\"t\", mktime(0,0,0,$currmonth-1,1,(($currmonth==1) ? $curryear-1: $curryear)) ), (($currmonth==1) ? $curryear-1: $curryear)) ));\n                        $monthly = true;\n                    } else if (strpos($ival, \"this\") === 0) {\n                        $monthly = true;\n                    }\n                    if (strpos($interval, \"?month\") || strpos($interval, \"?year\")) {\n                        $monthly = true;\n                }\n\n                    if ($monthly) {\n                        $cstamp = strtotime($ival);\n                    } else {\n                        $cstamp = strtotime($ival, $cstamp);\n                    }\n\n                    // This kludge takes account of timestamps like \"last Thursday\" (of the month). The last \'whatever day\' of\n                    // a month can only be a maximum of 31 days before the last timestamp we saw, so check for that (+/- 10 mins)\n                    $diffstamp = $cstamp - $lstamp;\n                    if ($diffstamp < 0) {\n                        if ($diffstamp > -(60*60*24*31)+600) {\n                            $cstamp = false; // Some \'last weekday\' of the previous month\n                        } else {\n                            break; // PHP_INT_MAX exceeded\n                        }\n                    }\n                    if ($cstamp !== false) {\n                        if ($debug > 1) {\n                            dmp(\"INTERVAL: \". $cstamp . \' // \' .date(\'d-M-Y H:i:s\', $cstamp));\n                        }\n\n                        if (($cstamp < $ev_expires || $ev_expires == \'0\') && ($cstamp != $ev_posted)) {\n                            $show_me = smd_include_event($cstamp, $now, $ignore, $time, $from, $to, $month);\n                            if ($show_me) {\n                                $flags[] = \'recur\';\n                                $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $cstamp);\n                                $fakerow[\'uPosted\'] = $cstamp;\n                                $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                                $ev_tally[$row[\'uPosted\']] = (isset($ev_tally[$row[\'uPosted\']])) ? $ev_tally[$row[\'uPosted\']]+1 : 1;\n                                $flags = array(); // reset so recurfirst is removed\n                            }\n                        } else {\n                            break;\n                        }\n                    }\n                    // Increment the month/year ready for the next interval\n                    if ($monthly) {\n                        $curryear = ($currmonth==12) ? $curryear+1 : $curryear;\n                        $currmonth = ($currmonth==12) ? 1 : $currmonth+1;\n                    }\n                }\n                if ($debug>1) {\n                    if (isset($ev_tally[$row[\'uPosted\']])) {\n                        dmp(\"TALLY: \". $ev_tally[$row[\'uPosted\']]);\n                    }\n                }\n            }\n        } else if ($allspanned && date(\"Y-M-d\", $ev_expires) != date(\"Y-M-d\", $ev_posted)) {\n            $postdate = date(\"Y-M-d H:i:s\", $ev_posted);\n            $fake_diff = safe_strtotime(date(\"Y-M-d\", $ev_expires) . \" 23:59:59\");\n            $diff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;\n            $spidth = ceil($diff / (60*60*24)); // days between dates\n            for ($jdx = 1; $jdx < $spidth; $jdx++) {\n                $flags = array();\n                $tm = safe_strtotime($postdate.\'+\'.$jdx.\' days\');\n                $show_me = smd_include_event($tm, $now, $ignore, $time, $from, $to, $month);\n                if ($show_me) {\n                    $flags[] = ($jdx==$spidth-1) ? \'multilast\' : \'multi\';\n                    $fakerow = $row;\n                    $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm);\n                    $fakerow[\'uPosted\'] = $tm;\n                    $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                }\n            }\n        }\n    }\n\n    if ($debug>2) {\n        echo \"++ PRE-SORTED ++\";\n        dmp($all_evs);\n    }\n\n    // Make up an array_multisort arg list and execute it\n    foreach($all_evs as $key => $entry) {\n        $row = $entry[\'ev\'];\n        foreach ($row as $identifier => $item) {\n            $varname = \"col_\".$identifier;\n            ${$varname}[$key] = $item;\n        }\n    }\n    if(count($all_evs) > 0) {\n        for ($idx = 0; $idx < count($sortOrder); $idx++) {\n            $sortargs[] = \'$col_\'.$sortOrder[$idx][\'by\'];\n            $sortargs[] = $sortOrder[$idx][\'dir\'];\n        }\n        $sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $all_evs);\';\n        eval($sortit);\n    }\n\n    if ($debug>2) {\n        echo \"++ POST-SORTED ++\";\n        dmp($all_evs);\n    }\n\n    // Handle paging\n    if ($paging) {\n        $grand_total = count($all_evs);\n        $total = $grand_total - $offset;\n        $numPages = ceil($total/$pageby);\n        $pg = (!$pretext[\'pg\']) ? 1 : $pretext[\'pg\'];\n        $pgoffset = $offset + (($pg - 1) * $pageby);\n        // send paging info to txp:newer and txp:older\n        $pageout[\'pg\'] = $pg;\n        $pageout[\'numPages\'] = $numPages;\n        $pageout[\'s\'] = $pretext[\'s\'];\n        $pageout[\'c\'] = $pretext[\'c\'];\n        $pageout[\'grand_total\'] = $grand_total;\n        $pageout[\'total\'] = $total;\n\n        if (empty($thispage))\n            $thispage = $pageout;\n        if ($pgonly)\n            return;\n    } else {\n        $pgoffset = $offset;\n    }\n\n    // Phase 3: iterate over the new array obeying any offset/limit. Anything in the range gets populated and parsed\n    $out = array();\n    $ctr = 0;\n    article_push();\n    $lastposted = 0;\n    foreach ($all_evs as $idx => $entry) {\n        $smd_cal_flag = array();\n        $smd_date = array();\n        $smd_eventinfo = array();\n\n        if ($idx >= $pgoffset && $ctr < $limit) {\n            $row = $entry[\'ev\'];\n\n            $smd_cal_flag = $entry[\'flags\'];\n\n            $thisposted = date(\'Y-m-d\', $row[\'uPosted\']);\n            $nextposted = isset($all_evs[\'ev\'][$idx+1]) ? date(\'Y-m-d\', $all_evs[\'ev\'][$idx+1][\'uPosted\']) : 0;\n\n            // Adjust times so txp:posted/expires return correct stamps\n            $row[\'Posted\'] = $row[\'Posted\']-tz_offset(strtotime($row[\'Posted\']));\n            $row[\'uPosted\'] = $row[\'uPosted\']-tz_offset($row[\'uPosted\']);\n            $row[\'Expires\'] = ($row[\'uExpires\'] == 0) ? \'0000-00-00 00:00:00\' : $row[\'Expires\']-tz_offset(strtotime($row[\'Expires\']));\n            $row[\'uExpires\'] = ($row[\'uExpires\'] == 0) ? 0 : $row[\'uExpires\']-tz_offset($row[\'uExpires\']);\n\n            // Populate additional event information\n            $fakestamp = ($row[\'uExpires\'] == 0) ? 0 : strtotime(date(\"Y-M-d\", $row[\'uExpires\']) . \" 23:59:59\");\n            $smd_eventinfo[\'duration\'] = ($row[\'uExpires\'] == 0) ? 0 : $row[\'uExpires\'] - $row[\'uPosted\'];\n            $smd_eventinfo[\'durationdays\'] = ($fakestamp) ? ceil(($fakestamp - $row[\'uPosted\']) / (60*60*24)) : 0;\n            $smd_date[\'y\'] = strftime(\'%Y\', $row[\'uPosted\']);\n            $smd_date[\'m\'] = strftime(\'%m\', $row[\'uPosted\']);\n            $smd_date[\'d\'] = (int)strftime(\'%d\', $row[\'uPosted\']);\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $row[\'uPosted\']), $row[\'uPosted\']);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $row[\'uPosted\']), $row[\'uPosted\']);\n            if ($row[\'uExpires\'] == 0) {\n                $smd_date[\'expy\'] = $smd_date[\'expm\'] = $smd_date[\'expd\'] = $smd_date[\'expw\'] = $smd_date[\'expiy\'] = \'\';\n            } else {\n                $smd_date[\'expy\'] = strftime(\'%Y\', $row[\'uExpires\']);\n                $smd_date[\'expm\'] = strftime(\'%m\', $row[\'uExpires\']);\n                $smd_date[\'expd\'] = (int)strftime(\'%d\', $row[\'uExpires\']);\n                $smd_date[\'expw\'] = strftime(smd_cal_reformat_win(\'%V\', $row[\'uExpires\']), $row[\'uExpires\']);\n                $smd_date[\'expiy\'] = strftime(smd_cal_reformat_win(\'%G\', $row[\'uExpires\']), $row[\'uExpires\']);\n            }\n            populateArticleData($row);\n            $thisarticle[\'is_first\'] = ($thisposted != $lastposted);\n            $thisarticle[\'is_last\'] = ($thisposted != $nextposted);\n            $lastposted = $thisposted;\n            $out[] = ($thing) ? parse($thing) : (($form) ? parse_form($form) : href($row[\'Posted\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\') );\n            $ctr++;\n        }\n    }\n    article_pop();\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Try and output \"nice\" dates that read well across month/year boundaries.\n// For example: 28 May - 05 Jun 2011 or May 11-16 2011, depending on format\nfunction smd_event_duration($atts)\n{\n    extract(lAtts(array(\n        \'start\'     => posted(array(\'format\' => \'%s\')),\n        \'end\'       => expires(array(\'format\' => \'%s\')),\n        \'format\'    => \'%d %b %Y\',\n        \'separator\' => \' &ndash; \',\n        \'debug\'     => 0,\n    ), $atts));\n\n    // Extract the relevant portions of the format so we can muck about with them\n    preg_match_all(\'/\\%([dejbBmhgGyY])/\', $format, $matches);\n\n    $indexes = array(\'day\' => \'\', \'month\' => \'\', \'year\' => \'\');\n    foreach($matches[1] as $idx => $token) {\n        switch ($token) {\n            case \'d\':\n            case \'e\':\n            case \'j\':\n                $indexes[\'day\'] = $idx;\n                break;\n            case \'b\':\n            case \'B\':\n            case \'m\':\n            case \'h\':\n                $indexes[\'month\'] = $idx;\n                break;\n            case \'g\':\n            case \'G\':\n            case \'y\':\n            case \'Y\':\n                $indexes[\'year\'] = $idx;\n                break;\n        }\n    }\n\n    $day_first = ($indexes[\'day\'] < $indexes[\'month\']);\n    $year_first = ($indexes[\'year\'] == 0);\n    $has_year = false;\n\n    if ($end) {\n        if (strftime(\'%Y %m %d\', $start) == strftime(\'%Y %m %d\',$end)) {\n             // begin and end on same day\n             $s_format = \'\';\n             $e_format = $format;\n             $has_year = true;\n         } else {\n            if (strftime(\'%Y\', $start) == strftime(\'%Y\', $end)) {\n                // same year\n                if (strftime(\'%m\', $start) == strftime(\'%m\', $end)) {\n                    // and same month\n                    $re1 = ($day_first) ? \'/\\%[bBmhgGyY]/\' : \'/\\%[gGyY]/\';\n                    $re2 = ($day_first) ? \'/\\%[gGyY]/\' : \'/\\%[bBmhgGyY]/\';\n\n                    $s_format = trim(preg_replace($re1, \'\', $format));\n                    $e_format = ($re2) ? trim(preg_replace($re2, \'\', $format)) : $format;\n                } else {\n                    // not same month\n                    $s_format = $e_format = trim(preg_replace(\'/\\%[gGyY]/\', \'\', $format));\n                }\n            } else {\n                // different year\n                $s_format = $e_format = $format;\n                $has_year = true;\n            }\n            $s_format .= $separator;\n        }\n\n        // Add the year back in the correct position\n        $s_format = ($has_year) ? $s_format : (($year_first) ? $matches[0][$indexes[\'year\']] . \' \' . $s_format : $s_format);\n        $e_format = ($has_year) ? $e_format : (($year_first) ? $e_format : $e_format . \' \' . $matches[0][$indexes[\'year\']]);\n    } else {\n        $s_format = $format;\n        $e_format =\'\';\n    }\n\n    return (($s_format) ? strftime($s_format, $start) : \'\') . (($e_format) ? strftime($e_format, $end) : \'\');\n}\n\n// An unoptimized workaround when \"%V\" and \"%G\" fails (usually on Windows)\n// Algorithm adapted from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt with thanks.\n// All other shortcut algorithms failed edge cases\nfunction smd_cal_iso_week($format = \'%V\', $time = null)\n{\n    if (!$time) $time = time();\n\n    $yr = strftime(\"%Y\", $time);\n    $leap = ( ( ($yr % 4 == 0) && ($yr % 100 != 0) ) || $yr % 400 == 0 );\n    $leap_prev = ( ( (($yr-1) % 4 == 0) && (($yr-1) % 100 != 0) ) || ($yr-1) % 400 == 0 );\n    $day_of_year = strftime(\'%j\', $time);\n\n    // Find the weekday of Jan 1st in the given year\n    $yy = ($yr - 1) % 100;\n    $c = ($yr - 1) - $yy;\n    $g = $yy + ($yy / 4);\n    $jan1weekday = 1 + ((((($c / 100) % 4) * 5) + $g) % 7);\n\n    // Find weekday ( could use: $weekday = strftime(\'%u\', $time); )\n    $h = $day_of_year + ($jan1weekday - 1);\n    $weekday = 1 + ( ($h - 1) % 7);\n\n    // Find if $time falls in iso_year Y-1, iso_week 52 or 53\n    if (($day_of_year <= (8 - $jan1weekday)) && $jan1weekday > 4) {\n        $iso_year = $yr - 1;\n        if ($jan1weekday == 5 || ($jan1weekday == 6 && $leap_prev)) {\n            $iso_week = 53;\n        } else {\n            $iso_week = 52;\n        }\n   } else {\n        $iso_year = $yr;\n    }\n\n    // Find if $time falls in iso_year Y+1, iso_week 1\n    if ($iso_year == $yr) {\n        $idx = ($leap) ? 366 : 365;\n        if ( ($idx - $day_of_year) < (4 - $weekday) ) {\n            $iso_year = $yr + 1;\n            $iso_week = 1;\n        }\n    }\n\n    // Find if $time falls in iso_year Y, iso_week 1 thru 53\n    if ($iso_year == $yr) {\n        $jdx = $day_of_year + (7 - $weekday) + ($jan1weekday - 1);\n        $iso_week = $jdx / 7;\n        if ($jan1weekday > 4) {\n            $iso_week--;\n        }\n    }\n\n    // Replacement array\n    $reps = array(\n        \'%V\' => str_pad($iso_week, 2, \'0\', STR_PAD_LEFT),\n        \'%G\' => $iso_year,\n        \'%g\' => substr($iso_year, 2),\n    );\n    return strtr($format, $reps);\n}\n\n// Adapted from: http://php.net/manual/en/function.strftime.php\nfunction smd_cal_reformat_win($format, $ts = null)\n{\n    // Only Win platforms need apply\n    if (!IS_WIN) {\n        return $format;\n    }\n\n    if (!$ts) $ts = time();\n\n    $mapping = array(\n        \'%C\' => sprintf(\"%02d\", date(\"Y\", $ts) / 100),\n        \'%D\' => \'%m/%d/%y\',\n        \'%e\' => sprintf(\"%\' 2d\", date(\"j\", $ts)),\n        \'%F\' => \'%Y-%m-%d\',\n        \'%g\' => smd_cal_iso_week(\'%g\', $ts),\n        \'%G\' => smd_cal_iso_week(\'%G\', $ts),\n        \'%h\' => \'%b\',\n        \'%l\' => sprintf(\"%\' 2d\", date(\"g\", $ts)),\n        \'%n\' => \"\\n\",\n        \'%P\' => date(\'a\', $ts),\n        \'%r\' => date(\"h:i:s\", $ts) . \" %p\",\n        \'%R\' => date(\"H:i\", $ts),\n        \'%s\' => date(\'U\', $ts),\n        \'%t\' => \"\\t\",\n        \'%T\' => \'%H:%M:%S\',\n        \'%u\' => ($w = date(\"w\", $ts)) ? $w : 7,\n        \'%V\' => smd_cal_iso_week(\'%V\', $ts),\n    );\n    $format = str_replace(\n        array_keys($mapping),\n        array_values($mapping),\n        $format\n    );\n\n    return $format;\n}\n\n// Find if the haystack contains one of the values in needle. Is there a cleverer way to do this?\nfunction smd_cal_in_array($needle, $haystack)\n{\n    foreach ($haystack as $val) {\n        if (in_array($val, $needle)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n// Check the passed timestamp against every time restriction and return true if it passes them all\nfunction smd_include_event($ts, $now, $ign, $time, $from, $to, $month)\n{\n    $show = array();\n    $show[] = !in_array(date(\"d-M-Y\", $ts), $ign);\n    $time = do_list($time);\n    $showor = false;\n    foreach($time as $tm) {\n        switch($tm) {\n            case \"any\":\n                $showor = true;\n                break;\n            case \"future\":\n                $showor = $showor || (($ts > $now) ? true : false);\n                break;\n            case \"today\":\n                $showor = $showor || (($ts >= strtotime(date(\'Y-m-d 00:00:00\', $now)) && $ts <= strtotime(date(\'Y-m-d 23:59:59\', $now))) ? true : false);\n                break;\n            default :\n                $showor = $showor || (($ts < $now) ? true : false);\n                break;\n        }\n    }\n    $show[] = $showor;\n    if ($from) { $show[] = ($ts >= safe_strtotime($from)) ? true : false; }\n    if ($to) { $show[] = ($ts <= safe_strtotime($to)) ? true : false; }\n    if ($month) { $show[] = (date(\"Y-m\", $ts) == $month) ? true : false; }\n\n    return (!in_array(0, $show)) ? true : false;\n}\n\n// Convert date ranges like 24-Oct-08 => 5-Nov-08 to an array of discrete date entities\n// Also, weekday vals such as {Sun:Mon:Wed} would return those days between $start and $end\nfunction smd_expand_daterange($range, $start = \'\', $end = \'\', $fmt = \'%s\')\n{\n    $out = array();\n    $rng = do_list($range, \"=>\");\n\n    if (count($rng) > 1) {\n        // Range expansion\n        $diff = safe_strtotime($rng[1]) - safe_strtotime($rng[0]);\n        $diffdays = ceil($diff / (60*60*24)); // days between dates\n        for ($jdx = 0; $jdx <= $diffdays; $jdx++) {\n            $out[] = safe_strftime($fmt, safe_strtotime($rng[0] . (($jdx==0) ? \'\' : \'+\'.$jdx.\' days\')));\n        }\n    } else if ($start && $end && strpos($range, \'{\') === 0 && strpos($range, \'}\') === strlen($range)-1) {\n        // Day of week expansion\n        $days = do_list(trim($range,\'{}\'), \':\');\n        $diffdays = ceil(($end-$start) / (60*60*24));\n        for ($jdx = 0; $jdx <= $diffdays; $jdx++) {\n            $tm = $start + ($jdx*60*60*24);\n            if (in_array(date(\'D\', $tm), $days)) {\n                $out[] = safe_strftime($fmt, $tm);\n            }\n        }\n    } else {\n        // Single date\n        $out[] = safe_strftime($fmt, safe_strtotime($rng[0]));\n    }\n    return $out;\n}', '/**\n * smd_calendar\n *\n * A Textpattern CMS plugin for complete monthly event and calendar management\n *  -> Originally based on mdp_calendar - thanks Marshall!\n *  -> Full-size / mini calendar by month, with ISO week support\n *  -> Txp articles are events. Future, past, and today\'s events are supported. Multi-day (spanned) events are based on article expiry\n *  -> Next/prev month/year select list with customisable start/end years\n *  -> Filter events by cat / section / author / status / time / expiry\n *  -> Specify event frequency in custom field (1 week / 10 days / 3 months / etc)\n *  -> Optionally (re)schedule / cancel / omit event dates\n *  -> Customisable output for events and cells using multiple forms/container and classes\n *  -> Conditional tags for building custom logic\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\n// TODO:\n//  * allow table header to be removed / restyled completely (the month/week dropdown & nav icons)\n//     -- a form (navform?) for the header row with access to all vars such as which month is being displayed?\n//     -- tools to allow the header to be generated from components and laid out in any manner?\n//  * allow URL vars to be passed as POST (to bypass gbp_permlinks)\n//  * add custom rows to the table (header, footer) -- header could be used to replace the current nav/dropdowns\n//  * div-based calendar layout?\n// TOFIX:\n//  * Expiry dates on extra+ allspanned dates in smd_article_event (and calendar?). They currently \'creep\' a day for every day of a spanned event\n//  * Ranges in stepfields (http://forum.textpattern.com/viewtopic.php?pid=254395#p254395 and http://forum.textpattern.com/viewtopic.php?pid=255617#p255617)\n\nif ($date = gps(\'date\')) {\n    $_GET[\'month\'] = $date;\n}\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'smd_calendar\')\n        ->register(\'smd_cal_info\')\n        ->register(\'smd_cal_now\')\n        ->register(\'smd_cal_class\')\n        ->register(\'smd_if_cal\')\n        ->register(\'smd_article_event\')\n        ->register(\'smd_event_info\')\n        ->register(\'smd_event_duration\')\n        ->register(\'smd_cal_minilink\')\n        ->register(\'smd_cal_iso_week\')\n        ->register(\'smd_cal_reformat_win\')\n        ->register(\'smd_cal_in_array\')\n        ->register(\'smd_cal_include_event\')\n        ->register(\'smd_expand_daterange\');\n}\n\nfunction smd_calendar($atts, $thing = null)\n{\n    global $pretext, $thisarticle, $variable, $prefs, $smd_cal_flag, $smd_date, $smd_calinfo, $smd_cal_ucls;\n\n    extract(lAtts(array(\n        \'time\'          => \'any\',\n        \'size\'          => \'large\',\n        \'expired\'       => \'\',\n        \'category\'      => null,\n        \'subcats\'       => \'\',\n        \'section\'       => \'\',\n        \'author\'        => \'\',\n        \'realname\'      => \'\',\n        \'status\'        => \'live\',\n        \'showall\'       => \'0\',\n        \'static\'        => \'\',\n        \'form\'          => \'\',\n        \'spanform\'      => \'SMD_SAME\',\n        \'recurform\'     => \'SMD_SAME\',\n        \'cellform\'      => \'\',\n        \'headerform\'    => \'\',\n        \'stepfield\'     => \'\',\n        \'skipfield\'     => \'\',\n        \'omitfield\'     => \'\',\n        \'extrafield\'    => \'\',\n        \'extrastrict\'   => \'0\',\n        \'datefields\'    => \'\',\n        \'showskipped\'   => \'0\',\n        \'showspanned\'   => \'1\',\n        \'holidays\'      => \'\',\n        \'holidayflags\'  => \'standard\',\n        \'classlevels\'   => \'cell, event\',\n        \'linkposted\'    => \'recur, multi, multiprev, multilast\',\n        \'classprefixes\' => \'smd_cal_, smd_cal_ev_\',\n        \'class\'         => \'\',\n        \'rowclass\'      => \'\',\n        \'cellclass\'     => \'\',\n        \'emptyclass\'    => \'empty\',\n        \'isoweekclass\'  => \'week\',\n        \'navclass\'      => \'navprev, navnext\',\n        \'navarrow\'      => \'&#60;, &#62;\',\n        \'navid\'         => \'\',\n        \'eventclasses\'  => \'category\',\n        \'eventwraptag\'  => \'span\',\n        \'select\'        => \'\',\n        \'selectbtn\'     => \'\',\n        \'myclass\'       => \'\',\n        \'mywraptag\'     => \'\',\n        \'caption\'       => \'\',\n        \'summary\'       => \'\',\n        \'id\'            => \'\',\n        \'week\'          => \'\',\n        \'month\'         => \'\',\n        \'year\'          => \'\',\n        \'remap\'         => \'\',\n        \'yearwidth\'     => \'0\',\n        \'isoweeks\'      => \'\',\n        \'dayformat\'     => \'ABBR\',\n        \'monthformat\'   => \'FULL\',\n        \'firstday\'      => 0,\n        \'maintain\'      => \'calid\',\n        \'nameval\'       => \'\',\n        \'event_delim\'   => \',\',\n        \'gmt\'           => 0,\n        \'lang\'          => \'\',\n        \'debug\'         => 0,\n    ), $atts));\n\n    $size = (in_array($size, array(\'small\', \'large\'))) ? $size : \'large\';\n    $status = ($status) ? $status : \'live\'; // in case status is empty\n    $firstday = ($isoweeks == \'\') ? $firstday : 1;\n    $spanform = ($spanform == \'SMD_SAME\') ? $form : $spanform;\n    $recurform = ($recurform == \'SMD_SAME\') ? $form : $recurform;\n    $cellform = (empty($cellform)) ? \'\' : fetch_form($cellform);\n    $headerform = (empty($headerform)) ? \'\' : fetch_form($headerform);\n    $frontpage = ($section==\'\' && $pretext[\'s\']==\'default\') ? true : false;\n\n    // Set up the class prefixes\n    $clevs = do_list($classlevels);\n    $cls = do_list($classprefixes);\n    $cls_pfx = $evc_pfx = $cls[0];\n    if (count($cls) > 1){\n        $evc_pfx = $cls[1];\n    }\n\n    // Set up the nav class(es)\n    $maintain = do_list($maintain);\n    $navarrow = do_list($navarrow);\n    $navparr = $navarrow[0];\n    $navnarr = (count($navarrow) > 1) ? $navarrow[1] : $navarrow[0];\n    $navclass = do_list($navclass);\n    $navpclass = $navclass[0];\n    $navnclass = (count($navclass) > 1) ? $navclass[1] : $navclass[0];\n\n    // Filters\n    $fopts = array();\n    $catSQL = $secSQL = $authSQL = $fpSQL = \'\';\n    if($category !== null) {\n        $uncats = false;\n        $allcats = do_list($category);\n        if (($pos = array_search(\'SMD_UNCAT\', $allcats)) !== false) {\n            $uncats = true;\n            unset($allcats[$pos]);\n            $category = join(\',\', $allcats);\n        }\n        $fopts[\'c\'] = $category; // TODO: Can fopts take a list? Should it include subcats?\n        $subcats = (empty($subcats)) ? 0 : ((strtolower($subcats)==\"all\") ? 99999 : intval($subcats));\n        if ($subcats) {\n            $outcats = array();\n            foreach ($allcats as $cat) {\n                $cats = getTree(doslash($cat), \'article\');\n                foreach ($cats as $jdx => $val) {\n                    if ($cats[$jdx][\'level\'] <= $subcats) {\n                        $outcats[] = $cats[$jdx][\'name\'];\n                    }\n                }\n            }\n            $allcats = $outcats;\n        }\n        $catSQL = doQuote(join(\"\',\'\", doSlash($allcats)));\n        $catSQL = ($uncats ? \" AND (Category1 = \'\' AND Category2 = \'\')\" : \'\') .\n            ($uncats && $allcats ? \" OR \" : ($allcats ? \" AND \" : \'\')) .\n            ($allcats ? \"( Category1 IN (\".$catSQL.\") OR Category2 IN (\".$catSQL.\") ) \" : \'\');\n    }\n    if($section) {\n        $secs = do_list($section);\n        $smd_calinfo[\'s\'] = $secs[0];\n        $secSQL = doQuote(join(\"\',\'\", doSlash($secs)));\n        $secSQL = \" AND Section IN (\".$secSQL.\") \";\n    }\n    if($realname) {\n        $authors = safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doArray(do_list($realname), \'urldecode\'))) .\')\' );\n        $author = join(\',\', $authors);\n    }\n    if($author) {\n        $fopts[\'author\'] = htmlentities(gps(\'author\'));\n        $authSQL = doQuote(join(\"\',\'\", doSlash(do_list($author))));\n        $authSQL = \" AND AuthorID IN (\".$authSQL.\") \";\n    }\n    if ($frontpage && !$showall) {\n        $fpSQL = filterFrontPage();\n    }\n    $smd_calinfo[\'artid\'] = $thisarticle[\'thisid\'];\n    $smd_calinfo[\'artitle\'] = $thisarticle[\'url_title\'];\n    $nameval = do_list($nameval);\n    foreach ($nameval as $nv) {\n        $nv = explode(\"=\", $nv);\n        if ($nv[0]) {\n            $fopts[$nv[0]] = ((isset($nv[1])) ? $nv[1] : \'\');\n        }\n    }\n    $status = do_list($status);\n    $stati = array();\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } else if (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n    $stati = \" Status IN (\".join(\',\', $stati).\")\";\n\n    $expired = ($expired) ? $expired : $prefs[\'publish_expired_articles\'];\n    $expired = (($expired) ? \'\' : \' AND (now() <= Expires OR Expires IS NULL)\');\n    $eventclasses = do_list($eventclasses);\n    $holidayflags = do_list($holidayflags);\n    $linkposted = do_list($linkposted);\n    $datefields = do_list($datefields);\n\n    // Work out the first and last posts to determine the year range - probably a better way of doing this than 3 queries\n    $filt = $stati . (($category !== null) ? $catSQL : \'\') . (($section) ? $secSQL : \'\') . (($author) ? $authSQL : \'\') . $fpSQL;\n    $earliest = safe_field(\'unix_timestamp(Posted) AS uPosted\', \'textpattern\', $filt .\' ORDER BY Posted ASC LIMIT 0, 1\', $debug);\n    $lp = safe_field(\'unix_timestamp(Posted) AS uPosted\', \'textpattern\', $filt .\' ORDER BY Posted DESC LIMIT 0, 1\', $debug);\n    $lm = safe_field(\'unix_timestamp(LastMod) AS uLastMod\', \'textpattern\', $filt .\' ORDER BY LastMod DESC LIMIT 0, 1\', $debug);\n    $latest = ($time==\"past\") ? time() : (($lp > $lm) ? $lp : $lm);\n\n    $yearwidth = do_list($yearwidth);\n    $yearwidth[0] = (empty($yearwidth[0])) ? 0 : $yearwidth[0];\n    if (count($yearwidth) == 1) {\n        $yearwidth[1] = $yearwidth[0];\n    }\n    $usenow = array(false,false);\n    foreach ($yearwidth as $yridx => $yritem) {\n        if (strpos($yritem,\"c\") !== false) {\n            $yearwidth[$yridx] = intval($yritem);\n            $usenow[$yridx] = true;\n        }\n    }\n\n    // Remap w/m/y to other vars if required\n    $remap = do_list($remap);\n    $dmap = array(\"y\" => \"y\", \"m\" => \"m\", \"w\" => \"w\");\n    foreach ($remap as $dpair) {\n        $dpair = do_list($dpair, \':\');\n        $dmap[$dpair[0]] = (isset($dpair[1])) ? $dpair[1] : $dpair[0];\n    }\n    $earliest = date(\"Y\", strtotime(\"-\".$yearwidth[0].\" year\", ( (empty($earliest) || $usenow[0]==true) ? time() : $earliest) ) );\n    $latest = date(\"Y\", strtotime(\"+\".$yearwidth[1].\" year\", ( (empty($latest) || $usenow[1]==true) ? time() : $latest) ) );\n\n    // Check the URL for current date and calendar target info\n    $in_calid = gps(\'calid\');\n    $in_year = (gps($dmap[\"y\"]) and is_numeric(gps($dmap[\"y\"]))) ? (int)gps($dmap[\"y\"]) : \'\';\n    $in_month = (gps($dmap[\"m\"]) and is_numeric(gps($dmap[\"m\"]))) ? (int)gps($dmap[\"m\"]) : \'\';\n    $in_week = (gps($dmap[\"w\"]) and is_numeric(gps($dmap[\"w\"]))) ? (int)gps($dmap[\"w\"]) : \'\';\n\n    if($static) { // if we\'re static w/o any supplied vars, use the current date\n        if(!$year) { $year = safe_strftime(\'%Y\'); }\n        if(!$month) { $month = safe_strftime(\'%m\'); }\n    } else { // otherwise use current date only if there\'s nothing else\n        if( $id == $in_calid ) { // use incoming\n            $year = ($in_year) ? $in_year : (($year) ? $year : safe_strftime(\'%Y\'));\n            $month = ($in_month) ? $in_month : (($month) ? $month : safe_strftime(\'%m\'));\n            // If week is used, adjust month so it encompasses the given week\n            $week = $in_week;\n            if ($week) {\n                $month = safe_strftime(\"%m\", strtotime($year.\"W\".str_pad($week, 2, \'0\', STR_PAD_LEFT))); // Get the month from the week\n            }\n        } else { // use current\n            if(!$year) { $year = safe_strftime(\'%Y\'); }\n            if(!$month) { $month = safe_strftime(\'%m\'); }\n            if($week) { $month = safe_strftime(\"%m\", strtotime($year.\"W\".str_pad($week, 2, \'0\', STR_PAD_LEFT))); }\n        }\n    }\n    $smd_calinfo[\'id\'] = ($in_calid) ? $in_calid : $id;\n    $smd_date[\'y\'] = $year; $smd_date[\'m\'] = $month; // $week/day/isoyear are set per event later\n\n    $ts_first = mktime(0, 0, 0, $month, 1, $year);\n    $ts_last = mktime(23, 59, 59, $month, date(\'t\',$ts_first), $year);\n    $ts_lastoff = $ts_last - tz_offset($ts_last);\n    if ($debug) {\n        echo \"++ THIS MONTH\'S CALENDAR [ start stamp // end date // end stamp // end date // tz offset (end) ] ++\";\n        dmp($ts_first, date(\'Y-m-d H:i:s\', $ts_first), $ts_last, date(\'Y-m-d H:i:s\', $ts_last), $ts_lastoff);\n    }\n    $extrasql = $catSQL . $secSQL . $authSQL . $fpSQL;\n\n    switch($time) {\n        case \"any\" : break;\n        case \"future\" : $extrasql .= \" AND Posted > now()\"; break;\n        default : $extrasql .= \" AND Posted < now()\"; break; // The past\n    }\n\n    // Holidays are global \'exclusions\', either defined directly or in a txp:variable\n    $holidays = do_list($holidays);\n    $txphols = do_list($holidays[0], \":\");\n    if ($txphols[0] == \"txpvar\") {\n        $holidays = do_list($variable[$txphols[1]]);\n    }\n    // Force each holiday to a known format. Holidays without years use current year\n    foreach ($holidays as $key => $val) {\n        if (empty($val)) continue;\n        $numparts = preg_match(\'/^([\\d\\w]+).?([\\d\\w]+).?([\\d\\w]+)?$/\', $val, $parts);\n\n        if ($numparts) {\n            if (count($parts) == 3) {\n                $parts[3] = $year;\n         }\n            $val = str_pad($parts[1], 2, \'0\', STR_PAD_LEFT).\'-\'.str_pad($parts[2], 2, \'0\', STR_PAD_LEFT).\'-\'.$parts[3];\n        }\n        $holidays[$key] = date(\"d-M-Y\", safe_strtotime($val));\n    }\n\n    if ($debug > 0 && !empty($holidays) && $holidays[0] != \'\') {\n        echo \"++ HOLIDAYS ++ \";\n        dmp($holidays);\n    }\n\n    // Get all matching articles in (and before) this month\n    $events = array();\n    $uposted_field = (empty($datefields[0])) ? \'uPosted\' : \"UNIX_TIMESTAMP($datefields[0])\";\n    $sql2 = $stati . \" HAVING $uposted_field <= \".$ts_lastoff. $expired . $extrasql .\" ORDER BY Posted ASC\";\n    $grabCols = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\';\n    $evlist = safe_rows($grabCols, \'textpattern\', $sql2, $debug);\n    article_push();\n\n    // If any events recur and fall within the current month, add those as well\n    // If any dates are to be excluded, the entry is skipped UNLESS showskipped indicates otherwise\n    foreach ($evlist as $row) {\n        $idx = 0; // In case the 1st day of the month is a continuation of an event from the end of the previous month\n        $start = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row[\'uPosted\'] + tz_offset($row[\'uPosted\']);\n        $start_date = date(\"Y-m-d\", $start); // For recurring/spanned events on a minical, this is the event the cell links to\n        $real_end = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\'] + tz_offset($row[\'uExpires\']));\n\n        // If end < start the user-specified dates cannot be trusted\n        if ($real_end != 0 && $real_end <= $start) {\n            $start = $row[\'uPosted\'] + tz_offset($row[\'uPosted\']);\n            $real_end = $row[\'uExpires\'] + tz_offset($row[\'uExpires\']);\n            trigger_error(\'Expiry cannot be before start date in \"\'.$row[\'Title\'].\'\": ignored\', E_USER_WARNING);\n        }\n\n        $end = ($real_end != 0 && $real_end < $ts_last) ? $real_end : $ts_last;\n        $real_diff = ($real_end==0) ? 0 : $real_end - $start;\n        $real_end_month = ($real_end==0) ? 0 : date(\'m\', $real_end);\n        $real_end_year = ($real_end==0) ? 0 : date(\'Y\', $real_end);\n        $fake_diff = strtotime(date(\"Y-M-d\", $real_end) . \" 23:59:59\");\n        $diff = ($real_end==0) ? 0 : $fake_diff - $start;\n        $smd_cal_flag = array();\n        $smd_cal_ucls = array();\n\n        $ev_month = date(\'m\', $start);\n        $ev_year = date(\'Y\', $start);\n        $ev_hr = date(\'H\', $start);\n        $ev_mn = date(\'i\', $start);\n        $ev_sc = date(\'s\', $start);\n\n        if ($debug > 1) {\n            echo \'++ EVENT START // END // (if non-zero) REAL END ++\';\n            dmp(date(\'d-M-Y H:i:s\', $start) .\' // \'. date(\'d-M-Y H:i:s\', $end) .\' // \'. ( ($real_end == 0) ? \'\' : date(\'d-M-Y H:i:s\', $real_end) ));\n            dmp($row[\'Title\']);\n            if ($debug > 2) {\n                dmp($row);\n            }\n        }\n\n        $multi = (($end > $start) && ($real_end > $start) && ($real_end > $ts_first) && (date(\"d-m-Y\", $real_end) != date(\"d-m-Y\", $start))) ? true : false;\n        $recur = (empty($row[$stepfield])) ? false : true;\n        $hol_hit = in_array(date(\"d-M-Y\", $start), $holidays);\n        $evclasses = array();\n        foreach ($eventclasses as $evcls) {\n            switch ($evcls) {\n                case \"\":\n                    break;\n                case \"gcat\":\n                    if (isset($pretext[\'c\']) && !empty($pretext[\'c\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'c\'];\n                    }\n                    break;\n                case \"category\":\n                    if (isset($row[\'Category1\']) && !empty($row[\'Category1\'])) {\n                        $evclasses[] = $evc_pfx.$row[\'Category1\'];\n                    }\n                    if (isset($row[\'Category2\']) && !empty($row[\'Category2\'])) {\n                        $evclasses[] = $evc_pfx.$row[\'Category2\'];\n                    }\n                    break;\n                case \"section\":\n                    if (isset($pretext[\'s\']) && !empty($pretext[\'s\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'s\'];\n                    }\n                    break;\n                case \"author\":\n                    if (isset($pretext[\'author\']) && !empty($pretext[\'author\'])) {\n                        $evclasses[] = $evc_pfx.$pretext[\'author\'];\n                    }\n                    break;\n                default:\n                    if (isset($row[$evcls]) && !empty($row[$evcls])) {\n                        $evclasses[] = $evc_pfx.$row[$evcls];\n                    }\n                    break;\n            }\n        }\n        $ignore = array();\n        $omit = array();\n        $cflag = array();\n\n        if ($debug > 1 && $evclasses) {\n            echo \'++ EVENT CLASSES ++\';\n            dmp($evclasses);\n        }\n\n        // Events that start or are added this month\n        if (($start < $end) && ($start > $ts_first)) {\n            populateArticleData($row);\n\n            // a standard event or start of a multi\n            if ($showspanned && $multi && !$recur) {\n                $smd_cal_flag[] = \'multifirst\';\n            }\n            if ($recur) {\n                $smd_cal_flag[] = \'recurfirst\';\n            }\n            if (!$smd_cal_flag) {\n                $smd_cal_flag[] = \'standard\';\n            }\n            if ( ( $hol_hit && !in_array(\'multi\',$holidayflags) && in_array(\'multifirst\',$smd_cal_flag) ) || ( $hol_hit && !in_array(\'standard\',$holidayflags) && in_array(\'standard\',$smd_cal_flag) ) ) {\n                $smd_cal_flag[] = \'cancel\';\n            }\n            foreach ($smd_cal_flag as $item) {\n                $cflag[] = $cls_pfx.$item;\n            }\n\n            $idx = $smd_date[\'d\'] = (int)strftime(\'%d\', $start);\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $start), $start);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $start), $start);\n            $use_posted = in_array(\'standard\', $linkposted);\n\n            $op = ($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href($row[\'Title\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n            $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n            $smd_cal_flag = array();\n            $cflag = array();\n            $smd_cal_ucls = array();\n            $use_posted = \'\';\n        }\n\n        // Generate a skip array for this event\n        if ($skipfield && $row[$skipfield] != \'\') {\n            $ignores = do_list($row[$skipfield]);\n            foreach ($ignores as $val) {\n                $igrng = smd_expand_daterange($val, $start, $end);\n                foreach ($igrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval); // Force each date to a known format\n                }\n            }\n        }\n        // Generate an omit array for this event\n        if ($omitfield && $row[$omitfield] != \'\') {\n            $omits = do_list($row[$omitfield]);\n            foreach ($omits as $val) {\n                $omrng = smd_expand_daterange($val, $start, $end);\n                foreach ($omrng as $theval) {\n                    $omit[] = date(\"d-M-Y\", $theval);\n                }\n            }\n        }\n        if ($debug > 1 && ($ignore || $omit)) {\n            echo \'++ OMITTED DATES ++\';\n            dmp($omit);\n            echo \'++ CANCELLED DATES ++\';\n            dmp($ignore);\n        }\n        // Calculate the date offsets and check recurring events that fall within the month of interest\n        if ($stepfield && $row[$stepfield] != \'\') {\n            $freq = do_list($row[$stepfield]);\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($freq as $interval) {\n                $max_loop = 99999; // Yuk, but practically limitless\n                $origerval = $interval;\n                $interval = str_replace(\"?month\", date(\'F\', mktime(0,0,0,$month,1)), $interval);\n                $interval = str_replace(\"?year\", $year, $interval);\n                if (strpos($interval, \"last\") === 0) {\n                    $interval = date(\"l, F jS Y\", strtotime( $interval, mktime(12, 0, 0, date(\"n\", mktime(0,0,0,$month,1,$year))+1, 1, $year) ));\n                    $max_loop = 1;\n                } else if (strpos($interval, \"first\") === 0) {\n                    $interval = date(\"l, F jS Y\", strtotime( $interval, mktime(12, 0, 0, (($month>1) ? $month-1 : 12), date(\"t\", mktime(0,0,0,$month-1,1,(($month==1) ? $year-1: $year))), (($month==1) ? $year-1: $year)) ));\n                    $max_loop = 1;\n                } else if (strpos($interval, \"this\") === 0) {\n                    $max_loop = 1;\n                }\n                $ts_loop = 0;\n                $ts_curr = $start;\n                if (strpos($origerval, \"?month\") || strpos($origerval, \"?year\")) {\n                    $max_loop = 1;\n            }\n\n//              $rng = smd_expand_daterange($interval);\n//dmp($interval, $rng);\n                while ($ts_curr < $end && $ts_loop < $max_loop) {\n                    if ($max_loop == 1) {\n                        $ts_curr = strtotime($interval);\n                        $ts_curr = ($ts_curr < $start || $ts_curr > $end) ? $start : $ts_curr;\n                    } else {\n                        $ts_curr = strtotime($interval, $ts_curr);\n                    }\n                    if ($ts_curr === false) {\n                        $ts_loop++;\n                        break;\n                    } else {\n                        if ($debug > 1) {\n                            dmp(\"INTERVAL: \". date(\'d-M-Y H:i:s\', $ts_curr+$stampoff));\n                        }\n                        if ($ts_curr < $end && $ts_curr >= $ts_first && $ts_curr != $start) {\n                            // A recurring event. Check it isn\'t a holiday or to be ignored\n                            populateArticleData($row);\n                            $op = \'\';\n                            $idx = (int)strftime(\'%d\', $ts_curr);\n                            $smd_cal_flag[] = \'recur\';\n                            $thisdate = date(\"d-M-Y\", $ts_curr);\n                            $omit_me = in_array($thisdate, $omit);\n                            $show_me = !in_array($thisdate, $ignore);\n                            $hol_hit = in_array($thisdate, $holidays);\n                            $show_hol = ($hol_hit && !in_array(\'recur\',$holidayflags) ) ? false : true;\n                            $use_posted = smd_cal_in_array(array(\'recur\', \'recurfirst\'), $linkposted);\n\n                            if ( $omit_me ) {\n                                $smd_cal_flag[] = \'omit\';\n                            }\n                            if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                                $smd_cal_flag[] = \'cancel\';\n                            }\n                            foreach ($smd_cal_flag as $item) {\n                                $cflag[] = $cls_pfx.$item;\n                            }\n\n                            // Create the events that appear in the cell but only if they\'ve not appeared before, or are to be ignored/omitted\n                            if (!$omit_me) {\n                                if (($show_me && $show_hol) || $showskipped) {\n                                    $smd_date[\'d\'] = $idx;\n                                    $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $ts_curr), $ts_curr);\n                                    $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $ts_curr), $ts_curr);\n                                    $op = ($recurform) ? parse_form($recurform) : (($thing) ? parse($thing) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href($row[\'Title\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n                                }\n                            }\n                            $used = array();\n                            if (isset($events[$idx]) && $events[$idx] != NULL) {\n                                foreach ($events[$idx] as $ev) {\n                                    $used[] = $ev[\'ev\'];\n                                }\n                            }\n                            if (isset($events[$idx]) && $events[$idx] == NULL || !in_array($op, $used)) {\n                                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n                            }\n                            $smd_cal_flag = array();\n                            $cflag = array();\n                            $smd_cal_ucls = array();\n                            $use_posted = \'\';\n                        }\n                        $ts_loop++;\n                    }\n                }\n            }\n        } else if ($showspanned && $multi) {\n            // Non-recurring events may span more than one date but they must still respect ignored dates and holidays\n            populateArticleData($row);\n            $lastday = (int)strftime(\'%d\', $end);\n            $real_lastday = (int)strftime(\'%d\', $real_end);\n            while (++$idx <= $lastday) {\n                $op = \'\';\n                $multiflag = (($year==$real_end_year) && ($month==$real_end_month) && ($idx==$real_lastday)) ? \'multilast\' : (($idx==1) ? \'multiprev\' : \'multi\');\n                $smd_cal_flag[] = $multiflag;\n                $thistime = mktime(0, 0, 0, $month, $idx, $year);\n                $thisdate = date(\"d-M-Y\", $thistime);\n                $omit_me = in_array($thisdate, $omit);\n                $show_me = !in_array($thisdate, $ignore);\n                $hol_hit = in_array($thisdate, $holidays);\n                $show_hol = ($hol_hit && !in_array(\'multi\',$holidayflags) ) ? false : true;\n                $use_posted = smd_cal_in_array(array(\'multi\', \'multifirst\', \'multilast\', \'multiprev\'), $linkposted);\n                if ( $omit_me ) {\n                    $smd_cal_flag[] = \'omit\';\n                }\n                if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                    $smd_cal_flag[] = \'cancel\';\n                }\n                foreach ($smd_cal_flag as $item) {\n                    $cflag[] = $cls_pfx.$item;\n                }\n                // Create the spanned event that appears in the cell\n                if (!$omit_me) {\n                    if ( ($show_me && $show_hol) || $showskipped) {\n                        $smd_date[\'d\'] = $idx;\n                        $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $thistime), $thistime);\n                        $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $thistime), $thistime);\n                        $op = ($spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href(\'&rarr;\', permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) );\n                    }\n                }\n                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $start_date);\n                $smd_cal_flag = array();\n                $cflag = array();\n                $smd_cal_ucls = array();\n                $use_posted = \'\';\n            }\n        }\n        // Add any extra dates for this event that are within the current month\n        if ($extrafield && $row[$extrafield] != \'\') {\n            $xtra = do_list($row[$extrafield]);\n            $ev_hr = date(\'H\', $start);\n            $ev_mn = date(\'i\', $start);\n            $ev_sc = date(\'s\', $start);\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($xtra as $val) {\n                if (strpos($val, \"+\") === false) {\n                    $exrng = smd_expand_daterange($val);\n                    $val = date(\"Y-m-d\", $exrng[0]);\n                    $spidth = count($exrng);\n                    $spex = 0;\n                } else {\n                    $chk = $showspanned && !$recur;\n                    $spidth = $chk ? ceil($diff / (60*60*24)) : 1; // days between dates\n                    $val = rtrim($val, \'+\');\n                    $spex = $chk ? 1 : 0;\n                }\n\n                for ($jdx = 1; $jdx <= $spidth; $jdx++) {\n                    $tm = safe_strtotime($val . (($jdx==1) ? \'\' : \'+\'.($jdx-1).\' days\'));\n                    if ($diff > 0 && $jdx == 1) {\n                        $expstamp = $tm+$stampoff+$real_diff;\n                    }\n                    $idx = $smd_date[\'d\'] = (int)strftime(\'%d\', $tm);\n                    $dt = date(\"Y-m-d\", $tm);\n                    $lst = ($extrastrict) ? $end : $ts_last;\n                    if ($tm < $lst && $tm >= $ts_first) {\n                        $fakerow = $row;\n                        $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff);\n                        $fakerow[\'uPosted\'] = $tm+$stampoff;\n                        if ($diff>0) {\n                            $fakerow[\'Expires\'] = date(\"Y-m-d H:i:s\", $expstamp);\n                            $fakerow[\'uExpires\'] = $expstamp;\n                        }\n\n                        populateArticleData($fakerow);\n                        $smd_cal_flag[] = \'extra\';\n                        $cflag[] = $cls_pfx.\'extra\';\n                        $omit_me = false;\n                        $show_me = $show_hol = true;\n                        if ($spex) {\n                            $multiflag = ($jdx==1) ? \'multifirst\' : (($jdx==$spidth) ? \'multilast\' : (($idx==1) ? \'multiprev\' : \'multi\'));\n                            $thisdate = date(\"d-M-Y\", $tm);\n                            $omit_me = in_array($thisdate, $omit);\n                            $show_me = !in_array($thisdate, $ignore);\n                            $hol_hit = in_array($thisdate, $holidays);\n                            $show_hol = ($hol_hit && !in_array(\'multi\',$holidayflags) ) ? false : true;\n                            $use_posted = in_array(\'extra\', $linkposted);\n                            if ($omit_me) {\n                                $smd_cal_flag[] = \'omit\';\n                            }\n                            if ( (!$show_me || !$show_hol) && !$omit_me ) {\n                                $smd_cal_flag[] = \'cancel\';\n                            }\n                            $smd_cal_flag[] = $multiflag;\n                            $cflag[] = $cls_pfx.$multiflag;\n                        }\n                        if (!$omit_me) {\n                            if ( ($show_me && $show_hol) || $showskipped) {\n                                $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $tm), $tm);\n                                $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $tm), $tm);\n                                $op = ($spex && $spanform) ? parse_form($spanform) : (($thing) ? parse($thing) : (($form) ? parse_form($form) : (($size==\"small\") ? smd_cal_minilink($row, $idx, $month, $year, $use_posted) : href((($spex && $jdx>1) ? \'&rarr;\' : $row[\'Title\']), permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\')) ));\n                                $events[$idx][] = array(\'ev\' => $op, \'evid\' => $row[\'ID\'], \'flag\' => $smd_cal_flag, \'classes\' => array_merge($cflag, $smd_cal_ucls, $evclasses), \'posted\' => $dt);\n                                $smd_cal_flag = array();\n                                $cflag = array();\n                                $smd_cal_ucls = array();\n                                $use_posted = \'\';\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    article_pop();\n\n    if ($debug > 1 && $events) {\n        echo \'++ ALL EVENTS ++\';\n        dmp($events);\n    }\n\n    // Generate the calendar\n    $calendar = new SMD_Calendar($size, $year, $month, $events, $section, $category, $debug);\n    $calendar->setWeek($week);\n    $calendar->setFirstDayOfWeek($firstday);\n    $calendar->setGMT($gmt);\n    $calendar->setLang($lang);\n    $calendar->setClassLevels($clevs);\n    $calendar->setClassPrefix($cls_pfx);\n    $calendar->setEventWraptag($eventwraptag);\n    $calendar->setCellForm($cellform);\n    $calendar->setHdrForm($headerform);\n    $calendar->setMYWraptag($mywraptag);\n    $calendar->setSummary($summary);\n    $calendar->setCaption($caption);\n    $calendar->setTableID($id);\n    $calendar->setTableClass($class);\n    $calendar->setRowClass($rowclass);\n    $calendar->setCellClass($cellclass);\n    $calendar->setEmptyClass($emptyclass);\n    $calendar->setISOWeekClass($isoweekclass);\n    $calendar->setNavInfo($navpclass,$navnclass,$navparr,$navnarr,$navid);\n    $calendar->setNavKeep($maintain);\n    $calendar->setMYClass($myclass);\n    $calendar->setNameFormat($dayformat, \"d\");\n    $calendar->setNameFormat($monthformat, \"m\");\n    $calendar->setRemap($dmap);\n    $calendar->setShowISOWeek($isoweeks);\n    $calendar->setEYear($earliest);\n    $calendar->setLYear($latest);\n    $calendar->setFilterOpts($fopts);\n    $calendar->setDelim($event_delim);\n    $calendar->setHolidays($holidays);\n    $calendar->setSelectors(do_list($select), $selectbtn);\n\n    return $calendar->display($static);\n}\n\nclass SMD_Calendar extends SMD_Raw_Calendar\n{\n    // Override Constructor\n    // Permits multiple events to show per day\n    var $section = \'\';\n    var $category = \'\';\n    var $size = \'\';\n    var $debug = 0;\n    var $events = array();\n\n    public function __construct($size, $year, $month, $events, $section, $category, $debug = 0)\n    {\n        $this->debug = $debug;\n        $this->section = $section;\n        $this->category = $category;\n        $this->events = $events;\n        $this->size = $size;\n        parent::__construct($year,$month,$debug);\n    }\n\n    // Override dspDayCell to display stuff right\n    function dspDayCell($theday)\n    {\n        global $smd_cal_flag, $smd_calinfo, $smd_cal_ucls, $smd_date, $permlink_mode;\n\n        $smd_cal_flag = array();\n        $smd_cal_ucls = array();\n        $tdclass = array();\n        $hasarticle = isset($this->events[$theday]);\n        $now = time() + tz_offset();\n\n        $thedate = mktime(0, 0, 0, $this->month, $theday, $this->year);\n        $hol_hit = in_array(date(\"d-M-Y\", $thedate), $this->holidays);\n        if ($hasarticle) {\n            $smd_cal_flag[] = \'event\';\n        }\n        if ($hol_hit) {\n            $smd_cal_flag[] = \'hols\';\n        }\n        $cflag = array();\n        foreach ($smd_cal_flag as $item) {\n            $cflag[] = $this->cls_pfx.$item;\n        }\n\n        if ($this->cellclass) {\n            $tdclass[] = $this->cellclass;\n        }\n        $tdclass = array_merge($tdclass, $cflag);\n        $runningclass = (in_array(\"cell\", $this->cls_lev) || in_array(\"cellplus\", $this->cls_lev)) ? $tdclass : array();\n\n        if($this->year == date(\'Y\',$now) and $this->month == date(\'n\',$now) and $theday == date(\'j\',$now) ) {\n            $smd_cal_flag[] = \'today\';\n            $runningclass[] = $this->cls_pfx.\'today\';\n        }\n\n        $out = array();\n        $flags = array();\n        $evid = array();\n        $fout = array(\'standard\'=>array(),\'recur\'=>array(),\'recurfirst\'=>array(),\'multifirst\'=>array(),\'multi\'=>array(),\'multiprev\'=>array(),\'multilast\'=>array(),\'cancel\'=>array(),\'extra\'=>array());\n        if (empty($this->cellform) && $this->size == \'large\') {\n            $out[] = hed($theday,4);\n        }\n\n        $evcnt = 0;\n        if( isset($this->events[$theday]) ) {\n            $days_events = $this->events[$theday];\n            foreach($days_events as $ev) {\n                $evclass = $ev[\'classes\'];\n                $evid[] = $ev[\'evid\'];\n                $flags = array_merge($flags, $ev[\'flag\']);\n                if (in_array(\"cellplus\", $this->cls_lev)) {\n                    $runningclass = array_merge($runningclass, $evclass);\n                }\n                $cls = ($evclass && in_array(\"event\", $this->cls_lev)) ? \' class=\"\'.join(\' \', $evclass).\'\"\' : \'\';\n                $op = ($this->evwraptag) ? tag($ev[\'ev\'], $this->evwraptag, $cls) : $ev[\'ev\'];\n                foreach ($ev[\'flag\'] as $flev) {\n                    $fout[$flev][] = $op;\n                }\n                $out[] = $op;\n                $evcnt++;\n                if ($this->size == \'small\' && $evcnt == 1) {\n                    break;\n                }\n            }\n        } elseif ($this->size == \'small\') {\n            $out[] = hed($theday,4);\n        }\n\n        // Amalgamate the event-level classes and cell-level classes if required\n        $runningclass = array_unique($runningclass);\n        if (in_array(\"cellplus\", $this->cls_lev)) {\n            $smd_cal_flag = array_merge($smd_cal_flag, $flags);\n        }\n\n        if ($this->cellform) {\n            $thistime = mktime(0, 0, 0, $this->month, $theday, $this->year);\n            $smd_calinfo[\'id\'] = $this->tableID;\n            $smd_date[\'y\'] = $this->year;\n            $smd_date[\'m\'] = $this->month;\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $thistime), $thistime);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $thistime), $thistime);\n            $smd_date[\'d\'] = $theday;\n            $reps = array(\n                \'{evid}\' => join($this->event_delim, $evid),\n                \'{standard}\' => join(\'\',$fout[\'standard\']),\n                \'{recur}\' => join(\'\',$fout[\'recur\']),\n                \'{recurfirst}\' => join(\'\',$fout[\'recurfirst\']),\n                \'{allrecur}\' => join(\'\',array_merge($fout[\'recur\'], $fout[\'recurfirst\'])),\n                \'{multifirst}\' => join(\'\',$fout[\'multifirst\']),\n                \'{multiprev}\' => join(\'\',$fout[\'multiprev\']),\n                \'{multi}\' => join(\'\',$fout[\'multilast\']),\n                \'{multilast}\' => join(\'\',$fout[\'multilast\']),\n                \'{allmulti}\' => join(\'\',array_merge($fout[\'multifirst\'],$fout[\'multi\'],$fout[\'multiprev\'],$fout[\'multilast\'])),\n                \'{cancel}\' => join(\'\',$fout[\'cancel\']),\n                \'{extra}\' => join(\'\',$fout[\'extra\']),\n                \'{events}\' => join(\'\',$out),\n                \'{numevents}\' => $evcnt,\n                \'{day}\' => $theday,\n                \'{dayzeros}\' => str_pad($theday, 2, \'0\', STR_PAD_LEFT),\n                \'{weekday}\' => ((is_array($this->dayNameFmt)) ? $this->dayNames[date(\'w\',$thistime)] : strftime($this->dayNameFmt, $thistime)),\n                \'{weekdayabbr}\' => strftime(\'%a\', $thistime),\n                \'{weekdayfull}\' => strftime(\'%A\', $thistime),\n                \'{week}\' => $smd_date[\'w\'],\n                \'{month}\' => $this->month,\n                \'{monthzeros}\' => str_pad($this->month, 2, \'0\', STR_PAD_LEFT),\n                \'{monthname}\' => ((is_array($this->mthNameFmt)) ? $this->mthNames[date(\'n\',$thistime)] : strftime($this->mthNameFmt, $thistime)),\n                \'{monthnameabbr}\' => strftime(\'%b\', $thistime),\n                \'{monthnamefull}\' => strftime(\'%B\', $thistime),\n                \'{year}\' => $this->year,\n                \'{shortyear}\' => strftime(\'%y\', $thistime),\n                \'{isoyear}\' => $smd_date[\'iy\'],\n                \'{shortisoyear}\' => strftime(smd_cal_reformat_win(\'%g\', $thistime), $thistime),\n            );\n            $cellout = parse(strtr($this->cellform, $reps));\n            $carray = array_merge($runningclass, $smd_cal_ucls);\n            $smd_cal_ucls = array();\n\n            return doTag($cellout,\'td\',join(\' \',$carray));\n        } else {\n            return doTag(join(\'\',$out),\'td\',join(\' \',$runningclass));\n        }\n    }\n\n    function display($static = false)\n    {\n        $sum = ($this->tblSummary) ? \' summary=\"\'.$this->tblSummary.\'\"\' : \'\';\n        $id = ($this->tableID) ? \' id=\"\'.$this->tableID.\'\"\' : \'\';\n        $c[] = ($this->tblCaption) ? \'<caption>\'.$this->tblCaption.\'</caption>\' : \'\';\n        $c[] = \'<thead>\';\n        $c[] = $this->dspHeader($static);\n        $c[] = $this->dspDayNames();\n        $c[] = \'</thead>\';\n        $c[] = $this->dspDayCells();\n\n        return doTag(join(\'\',$c),\'table\',$this->tableclass,$sum.$id);\n    }\n\n    function dspHeader($static)\n    {\n        global $pretext, $smd_calinfo, $permlink_mode;\n\n        $currmo = $this->month;\n        $curryr = $this->year;\n        $navpclass = $this->getNavInfo(\"pc\");\n        $navnclass = $this->getNavInfo(\"nc\");\n        $navparrow = $this->getNavInfo(\"pa\");\n        $navnarrow = $this->getNavInfo(\"na\");\n        $navid = $this->getNavInfo(\"id\");\n        $navpclass = ($navpclass) ? \' class=\"\'.$navpclass.\'\"\' : \'\';\n        $navnclass = ($navnclass) ? \' class=\"\'.$navnclass.\'\"\' : \'\';\n        $fopts = $this->fopts;\n\n        $sec = (isset($smd_calinfo[\'s\']) && !empty($smd_calinfo[\'s\'])) ? $smd_calinfo[\'s\'] : \'\';\n        foreach ($this->maintain as $col) {\n            switch ($col) {\n                case \"section\":\n                    if ($pretext[\'s\'] && $permlink_mode != \'year_month_day_title\') {\n                        $fopts = array(\'s\' => $pretext[\'s\']) + $fopts;\n                    }\n                    break;\n                case \"article\":\n                    if ($pretext[\'id\']) {\n                        $fopts = array(\'id\' => $pretext[\'id\']) + $fopts;\n                    }\n                    break;\n                case \"category\":\n                    if ($pretext[\'c\']) {\n                        $fopts = array(\'c\' => $pretext[\'c\']) + $fopts;\n                    }\n                    break;\n                case \"author\":\n                    if (gps(\'author\')) {\n                        $fopts = array(\'author\' => gps(\'author\')) + $fopts;\n                    }\n                    break;\n                case \"date\":\n                    if (gps(\'date\')) {\n                        $fopts = array(\'date\' => gps(\'date\')) + $fopts;\n                    }\n                    break;\n                case \"pg\":\n                    if ($pretext[\'pg\']) {\n                        $fopts = array(\'pg\' => $pretext[\'pg\']) + $fopts;\n                    }\n                    break;\n                case \"calid\":\n                    if ($this->tableID) {\n                        $fopts = array(\'calid\' => $this->tableID) + $fopts;\n                    }\n                    break;\n                default:\n                    if (gps($col)) {\n                        $fopts = array($col => gps($col)) + $fopts;\n                    }\n                    break;\n            }\n        }\n\n        $fopts = array_unique($fopts);\n        $filters = array();\n        $filterHid = array();\n        if (!$static) {\n            foreach($fopts as $key => $val) {\n                $filters[] = $key.\'=\'.$val;\n                $filterHid[] = hInput($key, $val);\n            }\n        }\n\n        // Week select list\n        if ($this->useSelector(\'week\') && !$static) {\n            $currwk = ($this->week) ? $this->week : date(\'W\', safe_strtotime($curryr.\"-\".$currmo.\"-1 12:00\"));\n            for ( $idx = 1; $idx <= 53; $idx++ ) {\n                $tagatts = \' value=\"\'.$idx.\'\"\';\n                if ( $idx == $currwk ) $tagatts .= \' selected=\"selected\"\';\n                $optiontags[] = doTag($this->selpfx[\'week\'].str_pad($idx, 2, \'0\', STR_PAD_LEFT).$this->selsfx[\'week\'], \'option\', \'\', $tagatts);\n            }\n            $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'w\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                . (($this->useSelector(\'year\')) ? \'\' : hInput($this->remap[\'y\'], $curryr));\n            $optiontags = array(); // Blank out\n        }\n\n        // Month select list - note mktime has the day forced to 1. If not you get\n        // bizarre repeated month names on the 31st of some months :-\\\n        if (!$this->useSelector(\'week\')) {\n            if ($this->useSelector(\'month\') && !$static) {\n                for ( $idx = 1; $idx <= 12; $idx++ ) {\n                    $tagatts = \' value=\"\'.$idx.\'\"\';\n                    if ( $idx == $currmo ) $tagatts .= \' selected=\"selected\"\';\n                    $optiontags[] = doTag($this->selpfx[\'month\'].((is_array($this->mthNameFmt)) ? $this->mthNames[date(\'n\',mktime(12,0,0,$idx,1))] : safe_strftime($this->mthNameFmt, mktime(12,0,0,$idx,1) )).$this->selsfx[\'month\'], \'option\', \'\', $tagatts);\n                }\n                $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'m\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                    . (($this->useSelector(\'year\')) ? \'\' : hInput($this->remap[\'y\'], $curryr));\n                $optiontags = array(); // Blank out\n            } else {\n                $selector[] = doTag($this->getMonthName(), \'span\', (($this->mywraptag) ? \'\' : $this->myclass));\n            }\n        }\n\n        // Year select list\n        $y0 = $this->eyr;\n        $y1 = $this->lyr;\n        if ($this->useSelector(\'year\') && ($y0 != $y1) && !$static) {\n            for ( $idx = $y0; $idx <= $y1; $idx++ ) {\n                $tagatts = \' value=\"\'.$idx.\'\"\';\n                if ( $idx == $curryr ) $tagatts .= \' selected=\"selected\"\';\n                $optiontags[] = doTag($this->selpfx[\'year\'].$idx.$this->selsfx[\'year\'], \'option\', \'\', $tagatts);\n            }\n            $selector[] = doTag(join(n, $optiontags), \'select\', (($this->mywraptag) ? \'\' : $this->myclass), \' name=\"\'.$this->remap[\'y\'].\'\"\'.(($this->selbtn) ? \'\' : \' onchange=\"this.form.submit()\"\'), \'\')\n                    . (($this->useSelector(\'month\') || $this->useSelector(\'week\')) ? \'\' : hInput($this->remap[\'m\'], $currmo));\n        } else {\n            $selector[] = doTag($curryr, \'span\', (($this->mywraptag) ? \'\' : $this->myclass));\n        }\n\n        $request = serverSet(\'REQUEST_URI\');\n        $redirect = serverSet(\'REDIRECT_URL\');\n        if (!empty($redirect) && ($request != $redirect) && is_callable(\'_l10n_set_browse_language\')) {\n            // MLP in da house: use the redirect URL instead\n            $request = $redirect;\n        }\n        $urlp = parse_url($request);\n        $action = $urlp[\'path\'];\n\n        if ($permlink_mode == \'messy\') {\n            $out = makeOut(\'id\',\'s\',\'c\',\'q\',\'pg\',\'p\',\'month\');\n            foreach($out as $key => $val) {\n                if ($val) {\n                    $filters[] = $key.\'=\'.$val;\n                    $filterHid[] = hInput($key, $val);\n                }\n            }\n        }\n        $filterHid = array_unique($filterHid);\n        $filters = array_unique($filters);\n\n        $extras = \'\';\n        if (!$static && ( $this->useSelector(\'month\') || $this->useSelector(\'year\') )) {\n            if ($this->selbtn) {\n                $extras .= doTag(\'\', \'input\', \'smd_cal_input\', \' type=\"submit\" value=\"\'.$this->selbtn.\'\"\');\n            }\n            $extras .= join(n, $filterHid);\n        }\n\n        $selector = \'<form action=\"\'.$action.\'\" method=\"get\"\'.(($navid) ? \' id=\"\'.$navid.\'\"\' : \'\').\'>\'.doTag(join(sp, $selector).$extras, $this->mywraptag, $this->myclass).\'</form>\';\n\n        $nav_back_link = $this->navigation($curryr, $currmo, \'-\', $filters, $urlp[\'path\']);\n        $nav_fwd_link  = $this->navigation($curryr, $currmo, \'+\', $filters, $urlp[\'path\']);\n\n        $nav_back = (!$static && $nav_back_link) ? \'<a href=\"\'.$nav_back_link.\'\"\'.$navpclass.\'>\'.$navparrow.\'</a>\' : \'&nbsp;\';\n        $nav_fwd  = (!$static && $nav_fwd_link) ? \'<a href=\"\'.$nav_fwd_link.\'\"\'.$navnclass.\'>\'.$navnarrow.\'</a>\' : \'&nbsp;\';\n\n        $c[] = doTag($nav_back,\'th\');\n        $c[] = \'<th colspan=\"\'.(($this->showISOWeek) ? 6 : 5).\'\">\'.$selector.\'</th>\';\n        $c[] = doTag($nav_fwd,\'th\');\n\n        return doTag(join(\'\',$c),\'tr\', \'smd_cal_navrow\');\n    }\n\n    function navigation($year, $month, $direction, $flt, $url = \'\')\n    {\n        global $permlink_mode;\n\n        if($direction == \'-\') {\n            if($month - 1 < 1) {\n                $month = 12;\n                $year -= 1;\n            } else {\n                $month -= 1;\n            }\n        } else {\n            if($month + 1 > 12) {\n                $month = 1;\n                $year += 1;\n            } else {\n                $month += 1;\n            }\n        }\n\n        // Abort if we\'re about to go out of range\n        if ($year < $this->eyr || $year > $this->lyr) {\n            return \'\';\n        }\n\n        $flt[] = $this->remap[\'m\'].\"=$month\";\n        $flt[] = $this->remap[\'y\'].\"=$year\";\n\n        return $url . \"?\" . join(a, $flt);\n    }\n}\n\n/**\n * Basic Calendar data and display\n * http://www.oscarm.org/static/pg/calendarClass/\n * @author Oscar Merida\n * @created Jan 18 2004\n */\nclass SMD_Raw_Calendar\n{\n    var $gmt = 1, $lang, $debug = 0;\n    var $year, $eyr, $lyr, $month, $week;\n    var $dayNameFmt, $mthNameFmt, $dayNames, $mthNames, $startDay, $endDay, $firstDayOfWeek = 0, $startOffset = 0;\n    var $selectors, $selbtn, $selpfx, $selsfx;\n    var $showISOWeek, $ISOWeekHead, $ISOWeekCell;\n    var $cls_lev, $cls_pfx, $fopts;\n    var $evwraptag, $mywraptag;\n    var $rowclass, $cellclass, $emptyclass, $isoclass, $myclass, $tableID, $tblSummary, $tblCaption;\n    var $navpclass, $navnclass, $navparrow, $navnarrow, $navid;\n    var $holidays, $cellform, $hdrform, $maintain, $remap;\n    var $event_delim;\n\n    /**\n     * Constructor\n     *\n     * @param integer, year\n     * @param integer, month\n     * @return object\n     */\n    public function __construct($yr, $mo, $debug = 0)\n    {\n        $this->setDebug($debug);\n        $this->setYear($yr);\n        $this->setMonth($mo);\n        $this->setClassPrefix(\'smd_cal_\');\n\n        $this->startTime = strtotime( \"$yr-$mo-01 00:00\" );\n        $this->startDay = date( \'D\', $this->startTime );\n        $this->endDay = date( \'t\', $this->startTime );\n        $this->endTime = strtotime( \"$yr-$mo-\".$this->endDay.\" 23:59:59\" );\n        if ($this->debug) {\n            echo \"++ THIS MONTH\'S RENDERED CALENDAR [ start stamp // end date // start day // end stamp // end date // end day number ] ++\";\n            dmp($this->startTime, date(\'Y-m-d H:i:s\', $this->startTime), $this->startDay, $this->endTime, date(\'Y-m-d H:i:s\', $this->endTime), $this->endDay);\n        }\n        $this->setNameFormat(\'%a\', \'d\');\n        $this->setNameFormat(\'%B\', \'m\');\n        $this->setFirstDayOfWeek(0);\n        $this->setShowISOWeek(\'\');\n        $this->setTableID(\'\');\n        $this->setTableClass(\'\');\n    }\n\n    function useSelector($val)\n    {\n        return in_array($val, $this->selectors);\n    }\n\n    function getDayName($day)\n    {\n        return ($this->dayNames[$day%7]);\n    }\n\n    function getMonthName()\n    {\n        if (is_array($this->mthNameFmt)) {\n            return $this->mthNames[date(\'n\',$this->startTime)];\n        } else {\n            return strftime($this->mthNameFmt, $this->startTime);\n        }\n    }\n\n    function getNavInfo($type)\n    {\n        $r = \'\';\n        switch ($type) {\n            case \"id\": $r = $this->navid; break;\n            case \"pc\": $r = $this->navpclass; break;\n            case \"nc\": $r = $this->navnclass; break;\n            case \"pa\": $r = $this->navparrow; break;\n            case \"na\": $r = $this->navnarrow; break;\n        }\n        return $r;\n    }\n\n    function setDebug($d)\n    {\n        $this->debug = $d;\n    }\n\n    function setGMT($b)\n    {\n        $this->gmt = $b;\n    }\n\n    function setLang($code)\n    {\n        $this->lang = $code;\n    }\n\n    function setSummary($txt)\n    {\n        $this->tblSummary = $txt;\n    }\n\n    function setCaption($txt)\n    {\n        $this->tblCaption = $txt;\n    }\n\n    function setCellForm($frm)\n    {\n        $this->cellform = $frm;\n    }\n\n    function setHdrForm($frm)\n    {\n        $this->hdrform = $frm;\n    }\n\n    function setTableID($id)\n    {\n        $this->tableID = $id;\n    }\n\n    function setYear($yr)\n    {\n        $this->year = $yr;\n    }\n\n    function setEYear($yr)\n    {\n        $this->eyr = $yr;\n    }\n\n    function setLYear($yr)\n    {\n        $this->lyr = $yr;\n    }\n\n    function setMonth($mth)\n    {\n        $this->month = (int)$mth;\n    }\n\n    function setWeek($wk)\n    {\n        if ($wk) {\n            $wk = str_pad($wk, 2, \'0\', STR_PAD_LEFT);\n            $this->week = $wk;\n            $this->month = safe_strftime(\"%m\", strtotime($this->year.\"W\".$wk));\n        }\n    }\n\n    function setNavKeep($ar)\n    {\n        $this->maintain = $ar;\n    }\n\n    function setShowISOWeek($val)\n    {\n        $this->showISOWeek = ($val) ? true : false;\n        if ($val) {\n            $val = do_list($val);\n            $this->ISOWeekHead = $val[0];\n            $this->ISOWeekCell = (isset($val[1])) ? $val[1] : \'{week}\';\n        }\n    }\n\n    function setRemap($map)\n    {\n        $this->remap = $map;\n    }\n\n    function setClassLevels($cls)\n    {\n        $this->cls_lev = $cls;\n    }\n\n    function setClassPrefix($cls)\n    {\n        $this->cls_pfx = $cls;\n    }\n\n    function setEventWraptag($wrap)\n    {\n        $this->evwraptag = $wrap;\n    }\n\n    function setMYWraptag($wrap)\n    {\n        $this->mywraptag = $wrap;\n    }\n\n    function setTableClass($cls)\n    {\n        $this->tableclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setRowClass($cls)\n    {\n        $this->rowclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setCellClass($cls)\n    {\n        $this->cellclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setEmptyClass($cls)\n    {\n        $this->emptyclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setISOWeekClass($cls)\n    {\n        $this->isoclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setDelim($dlm)\n    {\n        $this->event_delim = $dlm;\n    }\n\n    function setNavInfo($clsp, $clsn, $arrp, $arrn, $nid)\n    {\n        $this->navpclass = ($clsp) ? $this->cls_pfx.$clsp : \'\';\n        $this->navnclass = ($clsn) ? $this->cls_pfx.$clsn : \'\';\n        $this->navparrow = ($arrp) ? $arrp : \'\';\n        $this->navnarrow = ($arrn) ? $arrn : \'\';\n        $this->navid = ($nid) ? $this->cls_pfx.$nid : \'\';\n    }\n\n    function setMYClass($cls)\n    {\n        $this->myclass = ($cls) ? $this->cls_pfx.$cls : \'\';\n    }\n\n    function setFilterOpts($f)\n    {\n        $this->fopts = $f;\n    }\n\n    function setHolidays($hols)\n    {\n        $this->holidays = $hols;\n    }\n\n    function setSelectors($sel, $btn)\n    {\n        foreach ($sel as $idx => $item) {\n            $selparts = explode(\":\", $item);\n            $sel[$idx] = $selparts[0];\n            $this->selpfx[$selparts[0]] = (isset($selparts[1])) ? $selparts[1] : \'\';\n            $this->selsfx[$selparts[0]] = (isset($selparts[2])) ? $selparts[2] : \'\';\n        }\n        $this->selectors = $sel;\n        $this->selbtn = $btn;\n    }\n\n    function setFirstDayOfWeek($d)\n    {\n        $this->firstDayOfWeek = ((int)$d <= 6 and (int)$d >= 0) ? (int)$d : 0;\n        $this->startOffset = date(\'w\', $this->startTime) - $this->firstDayOfWeek;\n        if ( $this->startOffset < 0 ) {\n            $this->startOffset = 7 - abs($this->startOffset);\n        }\n    }\n\n    /**\n     *\n     * frm: any valid PHP strftime() string or ABBR/FULL\n     * typ: d to set day, m to set month format\n     */\n    function setNameFormat($frm, $typ = \"d\")\n    {\n        switch ($frm) {\n            case \"full\":\n            case \"FULL\":\n                $fmt = ($typ == \'d\') ? \"%A\" : \"%B\";\n                break;\n            case \"abbr\":\n            case \"ABBR\":\n                $fmt = ($typ == \'d\') ? \"%a\" : \"%b\";\n                break;\n            default:\n                if (strpos($frm, \'%\') === 0) {\n                    $fmt = $frm;\n                } else {\n                    $frm = trim($frm, \'{}\');\n                    $frm = do_list($frm);\n                    $fmt = $frm;\n                }\n                break;\n        }\n\n        if ($typ == \"d\") {\n            $this->dayNameFmt = $fmt;\n            $this->dayNames = array();\n\n            // This is done to make sure Sunday is always the first day of our array\n            $start = 0;\n            $end = $start + 7;\n            $sunday = strtotime(\'1970-Jan-04 12:00:00\');\n\n            for($i=$start; $i<$end; $i++) {\n                if (is_array($fmt)) {\n                    $this->dayNames[] = $fmt[$i-$start];\n                } else {\n                    $this->dayNames[] = ucfirst(strftime($fmt, ($sunday + (86400*$i))));\n                }\n            }\n        } else {\n            $this->mthNameFmt = $fmt;\n            $this->mthNames = array();\n            for ($i=0; $i<12; $i++) {\n                if (is_array($fmt)) {\n                    $this->mthNames[$i+1] = $fmt[$i];\n                }\n            }\n        }\n    }\n\n    /**\n     * Return markup for displaying the calendar.\n     *\n     * @return\n     * @public\n     */\n    function display()\n    {\n        $id = ($this->tableID) ? \' id=\"\'.$this->tableID.\'\"\' : \'\';\n        $c[] = \'<table\'.$id.\'>\';\n        $c[] = \'<thead>\' . $this->dspDayNames() . \'</thead>\';\n        $c[] = $this->dspDayCells();\n        $c[] = \'</table>\';\n\n        return join(\'\',$c);\n    }\n\n    /**\n     * Displays the row of day names.\n     *\n     * @return string\n     * @private\n     */\n    function dspDayNames()\n    {\n        if ($this->hdrform) {\n            $reps = array(\n                \'{firstday}\' => $this->firstDayOfWeek,\n                \'{daynames}\' => join(\',\', $this->dayNames),\n                \'{isoweekhead}\' => $this->ISOWeekHead,\n                \'{week}\' => date(\'W\', $this->startTime),\n                \'{month}\' => date(\'n\', $this->startTime),\n                \'{year}\' => date(\'Y\', $this->startTime),\n                \'{isoyear}\' => date(\'o\', $this->startTime),\n            );\n\n            return parse(strtr($this->hdrform, $reps));\n        } else {\n            $c[] = \'<tr class=\"smd_cal_daynames\">\';\n\n            $i = $this->firstDayOfWeek;\n            $j = 0; // count number of days displayed\n            $end = false;\n\n            if ($this->showISOWeek) {\n                $c[] = \"<th>\".$this->ISOWeekHead.\"</th>\";\n            }\n            for($j = 0; $j<=6; $j++, $i++) {\n                if($i == 7) { $i = 0; }\n                $c[] = \'<th>\'.$this->getDayName($i).\"</th>\";\n            }\n\n            $c[] = \'</tr>\';\n            return join(\'\',$c);\n        }\n    }\n\n    /**\n     * Displays all day cells for the month.\n     *\n     * @return string\n     * @private\n     */\n    function dspDayCells()\n    {\n        $i = 0; // cell counter\n        $emptyClass = $this->emptyclass;\n        $isoClass = $this->isoclass;\n        $rowClass = $this->rowclass;\n        $rowClass = ($rowClass) ? \' class=\"\'.$rowClass.\'\"\' : \'\';\n\n        $c[] = \'<tr\'.$rowClass.\'>\';\n\n        if ($this->showISOWeek) {\n            $reps = array(\n                \'{week}\' => date(\'W\', $this->startTime),\n                \'{month}\' => date(\'n\', $this->startTime),\n                \'{year}\' => date(\'Y\', $this->startTime),\n                \'{isoyear}\' => date(\'o\', $this->startTime),\n            );\n            $wkcell = strtr($this->ISOWeekCell, $reps);\n            $c[] = \'<td class=\"\'.$isoClass.\'\">\'.$wkcell.\'</td>\';\n        }\n        // first display empty cells based on what weekday the month starts in\n        for( $j=0; $j<$this->startOffset; $j++ )    {\n            $i++;\n            $c[] = \'<td class=\"\'.$emptyClass.\'\">&nbsp;</td>\';\n        } // end offset cells\n\n        // write out the rest of the days, at each sunday, start a new row.\n        for( $d=1; $d<=$this->endDay; $d++ ) {\n            $i++;\n            $c[] = $this->dspDayCell( $d );\n            if ( $i%7 == 0 ) { $c[] = \'</tr>\'; }\n            if ( $d<$this->endDay && $i%7 == 0 ) {\n                $c[] = \'<tr\'.$rowClass.\'>\';\n                if ($this->showISOWeek) {\n                    // **Not** using safe_strtotime() here to cater for an operating timezone that differs from the server timezone.\n                    // Probably should do this in other places too but no bugs have been filed yet so it can be done on a\n                    // case-by-case basis\n                    $theTime = strtotime($this->year.\"-\".$this->month.\"-\".(int)($d + 1) .\" 00:00\");\n                    $reps = array(\n                        \'{week}\' => date(\'W\', $theTime),\n                        \'{month}\' => date(\'n\', $theTime),\n                        \'{year}\' => date(\'Y\', $theTime),\n                        \'{isoyear}\' => date(\'o\', $theTime),\n                    );\n                    $wkcell = strtr($this->ISOWeekCell, $reps);\n                    $c[] = \'<td class=\"\'.$isoClass.\'\">\'.$wkcell.\'</td>\';\n                }\n            }\n        }\n        // fill in the final row\n        $left = 7 - ( $i%7 );\n        if ( $left < 7) {\n            for ( $j=0; $j<$left; $j++ )    {\n              $c[] = \'<td class=\"\'.$emptyClass.\'\">&nbsp;</td>\';\n            }\n            $c[] = \"\\n\\t</tr>\";\n        }\n        return \'<tbody>\' . join(\'\',$c) . \'</tbody>\';\n    }\n\n    /**\n     * Outputs the contents for a given day.\n     *\n     * @param integer, day\n     * @abstract\n     */\n    function dspDayCell($day)\n    {\n        return \'<td>\'.$day.\'</td>\';\n    }\n}\n\nfunction smd_cal_minilink($row, $day, $month, $year, $use_posted = false)\n{\n    global $permlink_mode;\n\n    $lang = \'\';\n    $request = serverSet(\'REQUEST_URI\');\n    $redirect = serverSet(\'REDIRECT_URL\');\n    if (!empty($redirect) && ($request != $redirect) && is_callable(\'_l10n_set_browse_language\')) {\n        // MLP in da house so extract the language currently in use -- is there an MLP-native method for this?\n        $reqparts = explode(\'/\', $request);\n        $redparts = explode(\'/\', $redirect);\n        $lang = join(\'\', array_diff($redparts, $reqparts)) . \'/\';\n    }\n\n    if( $permlink_mode == \'year_month_day_title\' ) {\n        $linkdate = ($use_posted) ? date(\'Y/m/d\', $row[\'uPosted\']) : $year.\'/\'.str_pad($month,2,\"0\",STR_PAD_LEFT).\'/\'.str_pad($day,2,\"0\",STR_PAD_LEFT);\n        $href = \' href=\"\'.hu.$lang.$linkdate.\'\"\';\n    } else {\n        $linkdate = ($use_posted) ? date(\'Y-m-d\', $row[\'uPosted\']) : $year.\'-\'.str_pad($month,2,\"0\",STR_PAD_LEFT).\'-\'.str_pad($day,2,\"0\",STR_PAD_LEFT);\n        $href = \' href=\"\'.hu.$lang.\'?date=\'.$linkdate;\n        if($row[\'Section\']) { $href = $href.a.\'s=\'.$row[\'Section\']; }\n//      if($category) { $href = $href.a.\'c=\'.$category; }\n        $href .= \'\"\';\n    }\n\n    return tag($day, \'a\', $href);\n}\n\n// Perform one of two types of test: a flag-based test, or an info-based test\nfunction smd_if_cal($atts, $thing)\n{\n    global $smd_cal_flag, $smd_calinfo, $smd_date;\n\n    extract(lAtts(array(\n        \'flag\'    => \'\',\n        \'calid\'   => \'\',\n        \'isoyear\' => \'\',\n        \'year\'    => \'\',\n        \'month\'   => \'\',\n        \'week\'    => \'\',\n        \'day\'     => \'\',\n        \'logic\'   => \'or\',\n        \'debug\'   => \'0\',\n    ), $atts));\n\n    $flag = do_list($flag);\n    $ctr = $num = 0;\n\n    if ($debug) {\n        dmp($atts);\n    }\n\n    if ($flag && $flag[0] != \'\') {\n        $num += count($flag);\n        foreach ($flag as $whatnot) {\n            if (empty($whatnot)) continue;\n            $ctr += (in_array($whatnot, $smd_cal_flag) || ($whatnot == \'SMD_ANY\' && !empty($smd_cal_flag))) ? 1 : 0;\n        }\n    }\n    if ($calid) {\n        $num++;\n        $ctr += ($smd_calinfo[\'id\'] === $calid) ? 1 : 0;\n    }\n    foreach (array(\"iy\" => \"isoyear\", \"y\" => \"year\", \"m\" => \"month\", \"w\" => \"week\", \"d\" => \"day\") as $idx => $test) {\n        $tester = $$test;\n        $compare = $smd_date[$idx];\n\n        if ($tester) {\n            $num++;\n            preg_match(\'/([!=<>]+)?([\\d]+)/\', $tester, $matches);\n            if ($debug) {\n                dmp(\"TEST IF: \". $compare. (($matches[1]) ? $matches[1] : \'=\') . $matches[2] );\n            }\n            switch ($matches[1]) {\n                case \"!\":\n                    $ctr += ($compare!=$matches[2]) ? 1 : 0;\n                    break;\n                case \">\":\n                    $ctr += ($compare>$matches[2]) ? 1 : 0;\n                    break;\n                case \">=\":\n                    $ctr += ($compare>=$matches[2]) ? 1 : 0;\n                    break;\n                case \"<\":\n                    $ctr += ($compare<$matches[2]) ? 1 : 0;\n                    break;\n                case \"<=\":\n                    $ctr += ($compare<=$matches[2]) ? 1 : 0;\n                    break;\n                default:\n                    $ctr += ($compare==$matches[2]) ? 1 : 0;\n                    break;\n            }\n        }\n    }\n    $result = (($ctr === $num && $logic == \"and\") || $ctr > 0 && $logic == \"or\") ? true : false;\n    return parse(EvalElse($thing, $result));\n}\n\n// Convenient wrapper for smd_cal_info use=\"event\"\nfunction smd_event_info($atts)\n{\n    $atts[\'use\'] = \'event\';\n    return smd_cal_info($atts);\n}\n\n// Grab additional information about the current event\nfunction smd_cal_info($atts)\n{\n    global $pretext, $thisarticle, $smd_cal_flag, $smd_calinfo, $smd_date, $smd_eventinfo;\n\n    extract(lAtts(array(\n        \'type\'        => \'flag\',\n        \'join\'        => \' \',\n        \'join_prefix\' => \'SMD_AUTO\',\n        \'html\'        => 0,\n        \'escape\'      => \'html\',\n        \'use\'         => \'cal\', // \'cal\' for calendar (uses $smd_calinfo) or \'event\' for event lists (uses $smd_eventinfo). Not publically alterable\n        \'debug\'       => 0,\n    ), $atts));\n\n    // Validate $use attribute\n    $use = (in_array($use, array(\'cal\', \'event\'))) ? $use : \'cal\';\n    $cal_global = ${\'smd_\'.$use.\'info\'};\n\n    if ($debug && $thisarticle) {\n        echo \'++ Event name ++\';\n        dmp($thisarticle[\'title\']);\n    }\n    if ($debug && $cal_global) {\n        echo \'++ Available \'.$use.\' info ++\';\n        dmp($cal_global);\n    }\n\n    if ($debug && $smd_date) {\n        echo \'++ Available date info ++\';\n        dmp($smd_date);\n    }\n\n    if ($debug && $smd_cal_flag) {\n        echo \'++ Available flag info ++\';\n        dmp($smd_cal_flag);\n    }\n\n    // Type: 0=date, 1=smd_cal/eventinfo, 2=pretext, 3=thisarticle, 4(or other)=user value\n    $map = array(\n        \'year\' => array(0, \'y\'),\n        \'isoyear\' => array(0, \'iy\', \'y\'),\n        \'month\' => array(0, \'m\'),\n        \'week\' => array(0, \'w\'),\n        \'day\' => array(0, \'d\'),\n        \'section\' => array(3, \'\', \'s\'),\n        \'category1\' => array(3, \'\', \'c\'),\n        \'category2\' => array(3, \'\', \'c\'),\n        \'thisid\' => array(3, \'thisid\', \'id\'),\n        \'article\' => array(1, \'artid\', \'id\'),\n        \'calid\' => array(1, \'id\', \'calid\'),\n        \'category\' => array(2, \'c\'),\n        \'realname\' => array(2, \'author\'),\n    );\n    $join = ($html) ? a : $join; // html mode forces ampersand join\n    $type = do_list($type);\n    $ret = array();\n    foreach ($type as $item) {\n        $pts = do_list($item, \':\');\n        $item = $pts[0];\n\n        if (empty($item)) continue;\n        // Default html id\n        $hid = (isset($map[$item])) ? ((isset($map[$item][2])) ? $map[$item][2] : $map[$item][1]) : $item;\n        // User-specified htmlid overrides it\n        $hid = (count($pts) > 1 && !empty($pts[1])) ? $pts[1] : $hid;\n        if ($item == \"flag\") {\n            $ret[] = (($join_prefix==\"SMD_AUTO\") ? $join : \'\').join($join, $smd_cal_flag);\n        } else if ($item == \"author\" || $item == \"realname\") {\n            $currauthor = ($thisarticle == NULL) ? \'\' : author(array());\n            if ($currauthor) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $currauthor;\n            }\n        } else if ($item == \"s\") {\n            $sec = (!empty($pretext[\'s\'])) ? $pretext[\'s\'] : ((isset($cal_global[\'s\']) && !empty($cal_global[\'s\'])) ? $cal_global[\'s\'] : \'\');\n            if ($sec) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $sec;\n            }\n        } else if (isset($map[$item])) {\n            $typ = $map[$item][0];\n            $idx = empty($map[$item][1]) ? $item : $map[$item][1];\n            switch ($typ) {\n                case 0:\n                    if ($smd_date[$idx]) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $smd_date[$idx];\n                    }\n                    break;\n                case 1:\n                    if (!empty($cal_global[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $cal_global[$idx];\n                    }\n                    break;\n                case 2:\n                    if (!empty($pretext[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $pretext[$idx];\n                    }\n                    break;\n                case 3:\n                    if ($thisarticle != NULL && isset($thisarticle[$idx]) && !empty($thisarticle[$idx])) {\n                        $ret[] = (($html) ? $hid.\'=\' : \'\') . $thisarticle[$idx];\n                    }\n                    break;\n            }\n        } else if (array_key_exists($item, $pretext)) {\n            if ($pretext[$item]) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $pretext[$item];\n            }\n        } else if (isset($cal_global[$item])) {\n            if (!empty($cal_global[$item])) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $cal_global[$item];\n            }\n        } else {\n            if ($thisarticle != NULL && isset($thisarticle[$item]) && !empty($thisarticle[$item])) {\n                $ret[] = (($html) ? $hid.\'=\' : \'\') . $thisarticle[$item];\n            }\n        }\n    }\n//    $ret = array_unique($ret);\n    $out = (($join_prefix==\"SMD_AUTO\") ? (($html) ? \'?\' : \'\') : $join_prefix).join($join, $ret);\n    return ($escape==\'html\') ? htmlspecialchars($out) : $out;\n}\n\n// Return a formatted timestamp, with optional \'time now\' override\nfunction smd_cal_now($atts)\n{\n    global $dateformat;\n\n    extract(lAtts(array(\n        \'format\' => $dateformat,\n        \'now\'    => \'\',\n        \'offset\' => \'\',\n        \'gmt\'    => \'\',\n        \'lang\'   => \'\',\n    ), $atts));\n\n    $theDay = (gps(\'d\') && is_numeric(gps(\'d\'))) ? (int)gps(\'d\') : safe_strftime(\'%d\');\n    $theMonth = (gps(\'m\') && is_numeric(gps(\'m\'))) ? (int)gps(\'m\') : safe_strftime(\'%m\');\n    $theYear = (gps(\'y\') && is_numeric(gps(\'y\'))) ? (int)gps(\'y\') : safe_strftime(\'%Y\');\n    if ($now) {\n        $now = str_replace(\"?month\", date(\'F\', mktime(12,0,0,$theMonth,$theDay,$theYear)), $now);\n        $now = str_replace(\"?year\", $theYear, $now);\n        $now = str_replace(\"?day\", $theDay, $now);\n        $now = is_numeric($now) ? $now : strtotime($now);\n    } else {\n        $now = time();\n    }\n\n    if ($offset) {\n        $now = strtotime($offset, $now);\n    }\n\n    $format = smd_cal_reformat_win($format, $now);\n    return safe_strftime($format, $now, $gmt, $lang);\n}\n\n// Set user-defined classes for a cell\nfunction smd_cal_class($atts)\n{\n    global $smd_cal_ucls;\n\n    extract(lAtts(array(\n        \'name\' => \'\',\n    ), $atts));\n\n    $name = do_list($name);\n    $smd_cal_ucls = array_merge($smd_cal_ucls, $name);\n}\n\n// <txp:article_custom /> replacement(ish) tag that understands how to handle recurring events\nfunction smd_article_event($atts, $thing = null)\n{\n    global $prefs, $pretext, $thispage, $thisarticle, $smd_eventinfo, $smd_cal_flag, $smd_date;\n\n    extract(lAtts(array(\n        \'time\'        => \'any\',\n        \'type\'        => \'standard,recur,multi\',\n        \'expired\'     => \'\',\n        \'id\'          => \'\',\n        \'category\'    => null,\n        \'section\'     => \'\',\n        \'author\'      => \'\',\n        \'realname\'    => \'\',\n        \'custom\'      => \'\',\n        \'status\'      => \'live\',\n        \'param_delim\' => \':\',\n        \'sort\'        => \'\',\n        \'form\'        => \'\',\n        \'stepfield\'   => \'\',\n        \'skipfield\'   => \'\',\n        \'omitfield\'   => \'\',\n        \'extrafield\'  => \'\',\n        \'allspanned\'  => \'0\',\n        \'datefields\'  => \'\',\n        \'month\'       => \'\',\n        \'from\'        => \'\',\n        \'to\'          => \'\',\n        \'offset\'      => 0,\n        \'limit\'       => \'10\',\n        \'eventlimit\'  => \'10\',\n        \'paging\'      => \'1\',\n        \'pageby\'      => \'\',\n        \'pgonly\'      => \'\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'class\'       => \'\',\n        \'debug\'       => 0,\n    ), $atts));\n\n    // Phase 1 filters\n    $filtSQL = array();\n    $subSQL = array();\n    if ($category !== null) {\n        $uncats = false;\n        $allcats = do_list($category);\n        if (($pos = array_search(\'SMD_UNCAT\', $allcats)) !== false) {\n            $uncats = true;\n            unset($allcats[$pos]);\n            $category = join(\',\', $allcats);\n        }\n        $tmp = doQuote(join(\"\',\'\", doSlash(do_list($category))));\n        $filtSQL[] = ($uncats ? \"(Category1 = \'\' AND Category2 = \'\')\" : \'\') .\n            ($uncats && $allcats ? \" OR \" : \'\') .\n            ($allcats ? \'( Category1 IN (\'.$tmp.\') OR Category2 IN (\'.$tmp.\') )\' : \'\');\n    }\n    if($section) {\n        $filtSQL[] = \'Section IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($section)))).\')\';\n    }\n    if($realname) {\n        $authors = safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doArray(do_list($realname), \'urldecode\'))) .\')\' );\n        $author = join(\',\', $authors);\n    }\n    if($author) {\n        $filtSQL[] = \'AuthorID IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($author)))).\')\';\n    }\n    if($id) {\n        $filtSQL[] = \'ID IN (\'.join(\',\', array_map(\'intval\', do_list($id))).\')\';\n    }\n    if($custom) {\n        $custs = do_list($custom);\n        $validOps = array(\'=\', \'!=\', \'>\', \'>=\', \'<\', \'<=\', \'like\', \'not\', \'not like\');\n        foreach ($custs as $set) {\n            if (strpos($set, $param_delim) !== false) {\n                $clauseOpts = do_list($set, $param_delim);\n                $fld = $clauseOpts[0];\n                $oper = ((count($clauseOpts) == 3) && (in_array(strtolower($clauseOpts[1]), $validOps))) ? $clauseOpts[1] : \'=\';\n                $clause = (count($clauseOpts) == 3) ? $clauseOpts[2] : ((count($clauseOpts) == 2) ? $clauseOpts[1] : \'\');\n                $filtSQL[] = $fld . \" $oper \" . doQuote(doSlash($clause));\n            }\n        }\n    }\n\n    $type = do_list($type);\n    $pageby = (empty($pageby) ? $limit : $pageby);\n\n    foreach ($type as $evtyp) {\n        switch($evtyp) {\n            case \'standard\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" = \'\' AND Expires IS NULL)\";\n                }\n                break;\n            case \'recur\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" != \'\')\";\n                }\n                break;\n            case \'multi\':\n                if ($stepfield) {\n                    $subSQL[] = \"(\".$stepfield.\" = \'\' AND Expires IS NOT NULL)\";\n                }\n                break;\n        }\n    }\n    if ($subSQL) {\n        $filtSQL[] = \'(\'.join(\' OR \', $subSQL).\')\';\n    }\n\n    $status = ($status) ? $status : \'live\'; // in case status has been emptied\n    $status = do_list($status);\n    $stati = array();\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } else if (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n    $filtSQL[] = \'Status IN (\'.doQuote(join(\"\',\'\", $stati)).\')\';\n\n    $expired = ($expired) ? $expired : $prefs[\'publish_expired_articles\'];\n    if (!$expired) {\n        $filtSQL[] = \'(now() <= Expires OR Expires IS NULL)\';\n    }\n\n    // Sorting rules: data is sorted once as it is extracted via SQL and then again after the fake dates have been inserted\n    $sort = (empty($sort)) ? \'Posted asc\' : $sort;\n    $sort = do_list($sort);\n    $sortPrefix = \"SORT_\";\n    $sortOrder = array();\n    for ($idx = 0; $idx < count($sort); $idx++) {\n        $sorties = explode(\' \', $sort[$idx]);\n        if (count($sorties) <= 1) {\n            $sorties[1] = \"asc\";\n        }\n        $sorties[1] = $sortPrefix.(($sorties[1] == \"desc\") ? \'DESC\' : \'ASC\');\n        $sortOrder[] = array(\"by\" => $sorties[0], \"dir\" => $sorties[1]);\n    }\n    $filtSQL = join(\' AND \', $filtSQL);\n    $filtSQL .= \' ORDER BY \'.join(\',\',doSlash($sort));\n\n    $grabCols = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires\';\n    $evlist = safe_rows($grabCols, \'textpattern\', $filtSQL, $debug);\n\n    if ($debug>2) {\n        echo \"++ RECORD SET ++\";\n        dmp($evlist);\n    }\n    $all_evs = array();\n    $ev_tally = array();\n    $now = time() + tz_offset();\n\n    $eventlimit = do_list($eventlimit);\n    if (count($eventlimit) == 1) {\n        $eventlimit[1] = $eventlimit[0];\n    }\n\n    $datefields = do_list($datefields);\n\n    // Phase 2: expand any recurring dates and collate all events that fall within the alloted ranges\n    foreach ($evlist as $row) {\n        $ev_posted = (!empty($datefields[0]) && !empty($row[$datefields[0]]) && ($stdt = strtotime($row[$datefields[0]])) !== false) ? $stdt : $row[\'uPosted\']+tz_offset($row[\'uPosted\']);\n        $ev_expires = (isset($datefields[1]) && !empty($row[$datefields[1]]) && ($endt = strtotime($row[$datefields[1]])) !== false) ? $endt : (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\']+tz_offset($row[\'uExpires\']));\n\n        $skip = ($skipfield && $row[$skipfield] != \'\');\n        $omit = ($omitfield && $row[$omitfield] != \'\');\n        $recur = ($stepfield && $row[$stepfield] != \'\');\n        $extra = ($extrafield && $row[$extrafield] != \'\');\n        $multi = ($ev_expires > $ev_posted && (date(\"d-m-Y\", $ev_expires) != date(\"d-m-Y\", $ev_posted))) ? true : false;\n\n        // If end < start the user-specified dates cannot be trusted\n        if ($ev_expires != 0 && $ev_expires <= $ev_posted) {\n            $ev_posted = $row[\'uPosted\']+tz_offset($row[\'uPosted\']);\n            $ev_expires = (($row[\'uExpires\']==0) ? 0 : $row[\'uExpires\']+tz_offset($row[\'uExpires\']));\n            trigger_error(\'Expiry cannot be before start date in \"\'.$row[\'Title\'].\'\": ignored\', E_USER_WARNING);\n        }\n        if ($debug > 1) {\n            echo \'++ EVENT START // END ++\';\n            dmp($row[\'Title\']);\n            dmp($ev_posted, date(\'Y-m-d H:i:s\', $ev_posted), $ev_expires, date(\'Y-m-d H:i:s\', $ev_expires));\n        }\n\n        // Rewrite the start/end dates in case they are user-defined\n        $row[\'uPosted\'] = $ev_posted;\n        $row[\'Posted\'] = date(\"Y-m-d H:i:s\", $ev_posted);\n        $row[\'uExpires\'] = $ev_expires;\n        $row[\'Expires\'] = ($ev_expires==0) ? \'0000-00-00 00:00:00\' : date(\"Y-m-d H:i:s\", $ev_expires);\n\n        $diff = ($ev_expires == 0) ? 0 : $ev_expires - $ev_posted;\n        $ev_month = date(\'m\', $ev_posted);\n        $ev_year = date(\'Y\', $ev_posted);\n        $ev_hr = date(\'H\', $ev_posted);\n        $ev_mn = date(\'i\', $ev_posted);\n        $ev_sc = date(\'s\', $ev_posted);\n        $ignore = array();\n\n        // Generate a skip array for this event\n        if ($skip) {\n            $ignores = do_list($row[$skipfield]);\n            foreach ($ignores as $val) {\n                $igrng = smd_expand_daterange($val, $ev_posted, $ev_expires);\n                foreach ($igrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval); // Force each date to a known format\n                }\n            }\n        }\n        // Append any omitted events\n        if ($omit) {\n            $omits = do_list($row[$omitfield]);\n            foreach ($omits as $val) {\n                $omrng = smd_expand_daterange($val, $ev_posted, $ev_expires);\n                foreach ($omrng as $theval) {\n                    $ignore[] = date(\"d-M-Y\", $theval);\n                }\n            }\n        }\n        if ($debug > 1 && $ignore) {\n            echo \'++ IGNORED DATES ++\';\n            dmp($ignore);\n        }\n\n        // Does the base event deserve to be in the results?\n        if (smd_include_event($ev_posted, $now, $ignore, $time, $from, $to, $month)) {\n            $all_evs[] = array(\'ev\' => $row, \'flags\' => ($multi ? array(\'multifirst\') : array(\'standard\')) );\n            $ev_tally[$row[\'uPosted\']] = (isset($ev_tally[$row[\'uPosted\']])) ? $ev_tally[$row[\'uPosted\']]+1 : 1;\n        }\n\n        // Add any extra dates for this event\n        if ($extra) {\n            $xtra = do_list($row[$extrafield]);\n            $xtras = array();\n\n            // Make up an array of all extra dates\n            foreach ($xtra as $val) {\n                if (strpos($val, \"+\") === false) {\n                    $exrng = smd_expand_daterange($val);\n                    $xtras[] = date(\"Y-m-d\", $exrng[0]);\n                    $spex = 0;\n                } else {\n                    $fake_diff = safe_strtotime(date(\"Y-M-d\", $ev_expires) . \" 23:59:59\");\n                    $fdiff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;\n                    $chk = $allspanned && $multi && !$recur;\n                    $spidth = $chk ? ceil($fdiff / (60*60*24)) : 1; // days between dates\n                    $val = rtrim($val, \'+\');\n                    for ($jdx = 1; $jdx <= $spidth; $jdx++) {\n                        $xtras[] = date(\"Y-m-d\", safe_strtotime($val . (($jdx==1) ? \'\' : \'+\'.($jdx-1).\' days\')));\n                    }\n                    $spex = $chk ? 1 : 0;\n                }\n            }\n\n            $xtras = array_unique($xtras);\n\n            $stampoff = (int)(3600*$ev_hr) + (int)(60*$ev_mn) + (int)$ev_sc;\n            foreach ($xtras as $jdx => $val) {\n                $tm = strtotime($val);\n                $flags = array(\'extra\');\n\n                // No $ignore for additional events, as they always show up\n                if (smd_include_event($tm+$stampoff, $now, array(), $time, $from, $to, $month)) {\n                    $fakerow = $row;\n                    $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff);\n                    $fakerow[\'uPosted\'] = $tm+$stampoff;\n                    if ($diff > 0) {\n                        $fakerow[\'Expires\'] = date(\"Y-m-d H:i:s\", $tm+$stampoff+$diff);\n                        $fakerow[\'uExpires\'] = $tm+$stampoff+$diff;\n                    }\n                    if ($spex) {\n                        $flags[] = ($jdx==0) ? \'multifirst\' : (($jdx==$spidth-1) ? \'multilast\' : \'multi\');\n                    }\n                    $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                    $ev_tally[$fakerow[\'uPosted\']] = (isset($ev_tally[$fakerow[\'uPosted\']])) ? $ev_tally[$fakerow[\'uPosted\']]+1 : 1;\n                }\n            }\n        }\n\n        if ($recur) {\n            $flags = array(\'recurfirst\');\n            $freq = do_list($row[$stepfield]);\n            $monthly = false;\n            $currmonth = $ev_month;\n            $curryear = $ev_year;\n            foreach ($freq as $interval) {\n                $fakerow = $row;\n                $cstamp = $ev_posted;\n                for($idx = 0; $idx < 99999; $idx++) {\n                    $lstamp = $cstamp;\n                    if ((isset($ev_tally[$row[\'uPosted\']]) && ($ev_tally[$row[\'uPosted\']] >= $eventlimit[0])) || ($to && $cstamp > safe_strtotime($to))) {\n                        break;\n                    }\n                    $ival = str_replace(\"?month\", date(\'F\', mktime(0,0,0,$currmonth,1)), $interval);\n                    $ival = str_replace(\"?year\", $curryear, $ival);\n\n                    if (strpos($ival, \"last\") === 0) {\n                        $ival = date(\"l, F jS Y\", strtotime( $ival, mktime(12, 0, 0, date(\"n\", mktime(0,0,0,$currmonth,1,$curryear))+1, 1, $curryear) ));\n                        $monthly = true;\n                    } else if (strpos($ival, \"first\") === 0) {\n                        $ival = date(\"l, F jS Y\", strtotime( $ival, mktime(12, 0, 0, (($currmonth>1) ? $currmonth-1 : 12), date(\"t\", mktime(0,0,0,$currmonth-1,1,(($currmonth==1) ? $curryear-1: $curryear)) ), (($currmonth==1) ? $curryear-1: $curryear)) ));\n                        $monthly = true;\n                    } else if (strpos($ival, \"this\") === 0) {\n                        $monthly = true;\n                    }\n                    if (strpos($interval, \"?month\") || strpos($interval, \"?year\")) {\n                        $monthly = true;\n                }\n\n                    if ($monthly) {\n                        $cstamp = strtotime($ival);\n                    } else {\n                        $cstamp = strtotime($ival, $cstamp);\n                    }\n\n                    // This kludge takes account of timestamps like \"last Thursday\" (of the month). The last \'whatever day\' of\n                    // a month can only be a maximum of 31 days before the last timestamp we saw, so check for that (+/- 10 mins)\n                    $diffstamp = $cstamp - $lstamp;\n                    if ($diffstamp < 0) {\n                        if ($diffstamp > -(60*60*24*31)+600) {\n                            $cstamp = false; // Some \'last weekday\' of the previous month\n                        } else {\n                            break; // PHP_INT_MAX exceeded\n                        }\n                    }\n                    if ($cstamp !== false) {\n                        if ($debug > 1) {\n                            dmp(\"INTERVAL: \". $cstamp . \' // \' .date(\'d-M-Y H:i:s\', $cstamp));\n                        }\n\n                        if (($cstamp < $ev_expires || $ev_expires == \'0\') && ($cstamp != $ev_posted)) {\n                            $show_me = smd_include_event($cstamp, $now, $ignore, $time, $from, $to, $month);\n                            if ($show_me) {\n                                $flags[] = \'recur\';\n                                $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $cstamp);\n                                $fakerow[\'uPosted\'] = $cstamp;\n                                $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                                $ev_tally[$row[\'uPosted\']] = (isset($ev_tally[$row[\'uPosted\']])) ? $ev_tally[$row[\'uPosted\']]+1 : 1;\n                                $flags = array(); // reset so recurfirst is removed\n                            }\n                        } else {\n                            break;\n                        }\n                    }\n                    // Increment the month/year ready for the next interval\n                    if ($monthly) {\n                        $curryear = ($currmonth==12) ? $curryear+1 : $curryear;\n                        $currmonth = ($currmonth==12) ? 1 : $currmonth+1;\n                    }\n                }\n                if ($debug>1) {\n                    if (isset($ev_tally[$row[\'uPosted\']])) {\n                        dmp(\"TALLY: \". $ev_tally[$row[\'uPosted\']]);\n                    }\n                }\n            }\n        } else if ($allspanned && date(\"Y-M-d\", $ev_expires) != date(\"Y-M-d\", $ev_posted)) {\n            $postdate = date(\"Y-M-d H:i:s\", $ev_posted);\n            $fake_diff = safe_strtotime(date(\"Y-M-d\", $ev_expires) . \" 23:59:59\");\n            $diff = ($ev_expires==0) ? 0 : $fake_diff - $ev_posted;\n            $spidth = ceil($diff / (60*60*24)); // days between dates\n            for ($jdx = 1; $jdx < $spidth; $jdx++) {\n                $flags = array();\n                $tm = safe_strtotime($postdate.\'+\'.$jdx.\' days\');\n                $show_me = smd_include_event($tm, $now, $ignore, $time, $from, $to, $month);\n                if ($show_me) {\n                    $flags[] = ($jdx==$spidth-1) ? \'multilast\' : \'multi\';\n                    $fakerow = $row;\n                    $fakerow[\'Posted\'] = date(\"Y-m-d H:i:s\", $tm);\n                    $fakerow[\'uPosted\'] = $tm;\n                    $all_evs[] = array(\'ev\' => $fakerow, \'flags\' => $flags);\n                }\n            }\n        }\n    }\n\n    if ($debug>2) {\n        echo \"++ PRE-SORTED ++\";\n        dmp($all_evs);\n    }\n\n    // Make up an array_multisort arg list and execute it\n    foreach($all_evs as $key => $entry) {\n        $row = $entry[\'ev\'];\n        foreach ($row as $identifier => $item) {\n            $varname = \"col_\".$identifier;\n            ${$varname}[$key] = $item;\n        }\n    }\n    if(count($all_evs) > 0) {\n        for ($idx = 0; $idx < count($sortOrder); $idx++) {\n            $sortargs[] = \'$col_\'.$sortOrder[$idx][\'by\'];\n            $sortargs[] = $sortOrder[$idx][\'dir\'];\n        }\n        $sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $all_evs);\';\n        eval($sortit);\n    }\n\n    if ($debug>2) {\n        echo \"++ POST-SORTED ++\";\n        dmp($all_evs);\n    }\n\n    // Handle paging\n    if ($paging) {\n        $grand_total = count($all_evs);\n        $total = $grand_total - $offset;\n        $numPages = ceil($total/$pageby);\n        $pg = (!$pretext[\'pg\']) ? 1 : $pretext[\'pg\'];\n        $pgoffset = $offset + (($pg - 1) * $pageby);\n        // send paging info to txp:newer and txp:older\n        $pageout[\'pg\'] = $pg;\n        $pageout[\'numPages\'] = $numPages;\n        $pageout[\'s\'] = $pretext[\'s\'];\n        $pageout[\'c\'] = $pretext[\'c\'];\n        $pageout[\'grand_total\'] = $grand_total;\n        $pageout[\'total\'] = $total;\n\n        if (empty($thispage))\n            $thispage = $pageout;\n        if ($pgonly)\n            return;\n    } else {\n        $pgoffset = $offset;\n    }\n\n    // Phase 3: iterate over the new array obeying any offset/limit. Anything in the range gets populated and parsed\n    $out = array();\n    $ctr = 0;\n    article_push();\n    $lastposted = 0;\n    foreach ($all_evs as $idx => $entry) {\n        $smd_cal_flag = array();\n        $smd_date = array();\n        $smd_eventinfo = array();\n\n        if ($idx >= $pgoffset && $ctr < $limit) {\n            $row = $entry[\'ev\'];\n\n            $smd_cal_flag = $entry[\'flags\'];\n\n            $thisposted = date(\'Y-m-d\', $row[\'uPosted\']);\n            $nextposted = isset($all_evs[\'ev\'][$idx+1]) ? date(\'Y-m-d\', $all_evs[\'ev\'][$idx+1][\'uPosted\']) : 0;\n\n            // Adjust times so txp:posted/expires return correct stamps\n            $row[\'Posted\'] = $row[\'Posted\']-tz_offset(strtotime($row[\'Posted\']));\n            $row[\'uPosted\'] = $row[\'uPosted\']-tz_offset($row[\'uPosted\']);\n            $row[\'Expires\'] = ($row[\'uExpires\'] == 0) ? \'0000-00-00 00:00:00\' : $row[\'Expires\']-tz_offset(strtotime($row[\'Expires\']));\n            $row[\'uExpires\'] = ($row[\'uExpires\'] == 0) ? 0 : $row[\'uExpires\']-tz_offset($row[\'uExpires\']);\n\n            // Populate additional event information\n            $fakestamp = ($row[\'uExpires\'] == 0) ? 0 : strtotime(date(\"Y-M-d\", $row[\'uExpires\']) . \" 23:59:59\");\n            $smd_eventinfo[\'duration\'] = ($row[\'uExpires\'] == 0) ? 0 : $row[\'uExpires\'] - $row[\'uPosted\'];\n            $smd_eventinfo[\'durationdays\'] = ($fakestamp) ? ceil(($fakestamp - $row[\'uPosted\']) / (60*60*24)) : 0;\n            $smd_date[\'y\'] = strftime(\'%Y\', $row[\'uPosted\']);\n            $smd_date[\'m\'] = strftime(\'%m\', $row[\'uPosted\']);\n            $smd_date[\'d\'] = (int)strftime(\'%d\', $row[\'uPosted\']);\n            $smd_date[\'w\'] = strftime(smd_cal_reformat_win(\'%V\', $row[\'uPosted\']), $row[\'uPosted\']);\n            $smd_date[\'iy\'] = strftime(smd_cal_reformat_win(\'%G\', $row[\'uPosted\']), $row[\'uPosted\']);\n            if ($row[\'uExpires\'] == 0) {\n                $smd_date[\'expy\'] = $smd_date[\'expm\'] = $smd_date[\'expd\'] = $smd_date[\'expw\'] = $smd_date[\'expiy\'] = \'\';\n            } else {\n                $smd_date[\'expy\'] = strftime(\'%Y\', $row[\'uExpires\']);\n                $smd_date[\'expm\'] = strftime(\'%m\', $row[\'uExpires\']);\n                $smd_date[\'expd\'] = (int)strftime(\'%d\', $row[\'uExpires\']);\n                $smd_date[\'expw\'] = strftime(smd_cal_reformat_win(\'%V\', $row[\'uExpires\']), $row[\'uExpires\']);\n                $smd_date[\'expiy\'] = strftime(smd_cal_reformat_win(\'%G\', $row[\'uExpires\']), $row[\'uExpires\']);\n            }\n            populateArticleData($row);\n            $thisarticle[\'is_first\'] = ($thisposted != $lastposted);\n            $thisarticle[\'is_last\'] = ($thisposted != $nextposted);\n            $lastposted = $thisposted;\n            $out[] = ($thing) ? parse($thing) : (($form) ? parse_form($form) : href($row[\'Posted\'], permlinkurl($row), \' title=\"\'.$row[\'Title\'].\'\"\') );\n            $ctr++;\n        }\n    }\n    article_pop();\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Try and output \"nice\" dates that read well across month/year boundaries.\n// For example: 28 May - 05 Jun 2011 or May 11-16 2011, depending on format\nfunction smd_event_duration($atts)\n{\n    extract(lAtts(array(\n        \'start\'     => posted(array(\'format\' => \'%s\')),\n        \'end\'       => expires(array(\'format\' => \'%s\')),\n        \'format\'    => \'%d %b %Y\',\n        \'separator\' => \' &ndash; \',\n        \'debug\'     => 0,\n    ), $atts));\n\n    // Extract the relevant portions of the format so we can muck about with them\n    preg_match_all(\'/\\%([dejbBmhgGyY])/\', $format, $matches);\n\n    $indexes = array(\'day\' => \'\', \'month\' => \'\', \'year\' => \'\');\n    foreach($matches[1] as $idx => $token) {\n        switch ($token) {\n            case \'d\':\n            case \'e\':\n            case \'j\':\n                $indexes[\'day\'] = $idx;\n                break;\n            case \'b\':\n            case \'B\':\n            case \'m\':\n            case \'h\':\n                $indexes[\'month\'] = $idx;\n                break;\n            case \'g\':\n            case \'G\':\n            case \'y\':\n            case \'Y\':\n                $indexes[\'year\'] = $idx;\n                break;\n        }\n    }\n\n    $day_first = ($indexes[\'day\'] < $indexes[\'month\']);\n    $year_first = ($indexes[\'year\'] == 0);\n    $has_year = false;\n\n    if ($end) {\n        if (strftime(\'%Y %m %d\', $start) == strftime(\'%Y %m %d\',$end)) {\n             // begin and end on same day\n             $s_format = \'\';\n             $e_format = $format;\n             $has_year = true;\n         } else {\n            if (strftime(\'%Y\', $start) == strftime(\'%Y\', $end)) {\n                // same year\n                if (strftime(\'%m\', $start) == strftime(\'%m\', $end)) {\n                    // and same month\n                    $re1 = ($day_first) ? \'/\\%[bBmhgGyY]/\' : \'/\\%[gGyY]/\';\n                    $re2 = ($day_first) ? \'/\\%[gGyY]/\' : \'/\\%[bBmhgGyY]/\';\n\n                    $s_format = trim(preg_replace($re1, \'\', $format));\n                    $e_format = ($re2) ? trim(preg_replace($re2, \'\', $format)) : $format;\n                } else {\n                    // not same month\n                    $s_format = $e_format = trim(preg_replace(\'/\\%[gGyY]/\', \'\', $format));\n                }\n            } else {\n                // different year\n                $s_format = $e_format = $format;\n                $has_year = true;\n            }\n            $s_format .= $separator;\n        }\n\n        // Add the year back in the correct position\n        $s_format = ($has_year) ? $s_format : (($year_first) ? $matches[0][$indexes[\'year\']] . \' \' . $s_format : $s_format);\n        $e_format = ($has_year) ? $e_format : (($year_first) ? $e_format : $e_format . \' \' . $matches[0][$indexes[\'year\']]);\n    } else {\n        $s_format = $format;\n        $e_format =\'\';\n    }\n\n    return (($s_format) ? strftime($s_format, $start) : \'\') . (($e_format) ? strftime($e_format, $end) : \'\');\n}\n\n// An unoptimized workaround when \"%V\" and \"%G\" fails (usually on Windows)\n// Algorithm adapted from http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt with thanks.\n// All other shortcut algorithms failed edge cases\nfunction smd_cal_iso_week($format = \'%V\', $time = null)\n{\n    if (!$time) $time = time();\n\n    $yr = strftime(\"%Y\", $time);\n    $leap = ( ( ($yr % 4 == 0) && ($yr % 100 != 0) ) || $yr % 400 == 0 );\n    $leap_prev = ( ( (($yr-1) % 4 == 0) && (($yr-1) % 100 != 0) ) || ($yr-1) % 400 == 0 );\n    $day_of_year = strftime(\'%j\', $time);\n\n    // Find the weekday of Jan 1st in the given year\n    $yy = ($yr - 1) % 100;\n    $c = ($yr - 1) - $yy;\n    $g = $yy + ($yy / 4);\n    $jan1weekday = 1 + ((((($c / 100) % 4) * 5) + $g) % 7);\n\n    // Find weekday ( could use: $weekday = strftime(\'%u\', $time); )\n    $h = $day_of_year + ($jan1weekday - 1);\n    $weekday = 1 + ( ($h - 1) % 7);\n\n    // Find if $time falls in iso_year Y-1, iso_week 52 or 53\n    if (($day_of_year <= (8 - $jan1weekday)) && $jan1weekday > 4) {\n        $iso_year = $yr - 1;\n        if ($jan1weekday == 5 || ($jan1weekday == 6 && $leap_prev)) {\n            $iso_week = 53;\n        } else {\n            $iso_week = 52;\n        }\n   } else {\n        $iso_year = $yr;\n    }\n\n    // Find if $time falls in iso_year Y+1, iso_week 1\n    if ($iso_year == $yr) {\n        $idx = ($leap) ? 366 : 365;\n        if ( ($idx - $day_of_year) < (4 - $weekday) ) {\n            $iso_year = $yr + 1;\n            $iso_week = 1;\n        }\n    }\n\n    // Find if $time falls in iso_year Y, iso_week 1 thru 53\n    if ($iso_year == $yr) {\n        $jdx = $day_of_year + (7 - $weekday) + ($jan1weekday - 1);\n        $iso_week = $jdx / 7;\n        if ($jan1weekday > 4) {\n            $iso_week--;\n        }\n    }\n\n    // Replacement array\n    $reps = array(\n        \'%V\' => str_pad($iso_week, 2, \'0\', STR_PAD_LEFT),\n        \'%G\' => $iso_year,\n        \'%g\' => substr($iso_year, 2),\n    );\n    return strtr($format, $reps);\n}\n\n// Adapted from: http://php.net/manual/en/function.strftime.php\nfunction smd_cal_reformat_win($format, $ts = null)\n{\n    // Only Win platforms need apply\n    if (!IS_WIN) {\n        return $format;\n    }\n\n    if (!$ts) $ts = time();\n\n    $mapping = array(\n        \'%C\' => sprintf(\"%02d\", date(\"Y\", $ts) / 100),\n        \'%D\' => \'%m/%d/%y\',\n        \'%e\' => sprintf(\"%\' 2d\", date(\"j\", $ts)),\n        \'%F\' => \'%Y-%m-%d\',\n        \'%g\' => smd_cal_iso_week(\'%g\', $ts),\n        \'%G\' => smd_cal_iso_week(\'%G\', $ts),\n        \'%h\' => \'%b\',\n        \'%l\' => sprintf(\"%\' 2d\", date(\"g\", $ts)),\n        \'%n\' => \"\\n\",\n        \'%P\' => date(\'a\', $ts),\n        \'%r\' => date(\"h:i:s\", $ts) . \" %p\",\n        \'%R\' => date(\"H:i\", $ts),\n        \'%s\' => date(\'U\', $ts),\n        \'%t\' => \"\\t\",\n        \'%T\' => \'%H:%M:%S\',\n        \'%u\' => ($w = date(\"w\", $ts)) ? $w : 7,\n        \'%V\' => smd_cal_iso_week(\'%V\', $ts),\n    );\n    $format = str_replace(\n        array_keys($mapping),\n        array_values($mapping),\n        $format\n    );\n\n    return $format;\n}\n\n// Find if the haystack contains one of the values in needle. Is there a cleverer way to do this?\nfunction smd_cal_in_array($needle, $haystack)\n{\n    foreach ($haystack as $val) {\n        if (in_array($val, $needle)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n// Check the passed timestamp against every time restriction and return true if it passes them all\nfunction smd_include_event($ts, $now, $ign, $time, $from, $to, $month)\n{\n    $show = array();\n    $show[] = !in_array(date(\"d-M-Y\", $ts), $ign);\n    $time = do_list($time);\n    $showor = false;\n    foreach($time as $tm) {\n        switch($tm) {\n            case \"any\":\n                $showor = true;\n                break;\n            case \"future\":\n                $showor = $showor || (($ts > $now) ? true : false);\n                break;\n            case \"today\":\n                $showor = $showor || (($ts >= strtotime(date(\'Y-m-d 00:00:00\', $now)) && $ts <= strtotime(date(\'Y-m-d 23:59:59\', $now))) ? true : false);\n                break;\n            default :\n                $showor = $showor || (($ts < $now) ? true : false);\n                break;\n        }\n    }\n    $show[] = $showor;\n    if ($from) { $show[] = ($ts >= safe_strtotime($from)) ? true : false; }\n    if ($to) { $show[] = ($ts <= safe_strtotime($to)) ? true : false; }\n    if ($month) { $show[] = (date(\"Y-m\", $ts) == $month) ? true : false; }\n\n    return (!in_array(0, $show)) ? true : false;\n}\n\n// Convert date ranges like 24-Oct-08 => 5-Nov-08 to an array of discrete date entities\n// Also, weekday vals such as {Sun:Mon:Wed} would return those days between $start and $end\nfunction smd_expand_daterange($range, $start = \'\', $end = \'\', $fmt = \'%s\')\n{\n    $out = array();\n    $rng = do_list($range, \"=>\");\n\n    if (count($rng) > 1) {\n        // Range expansion\n        $diff = safe_strtotime($rng[1]) - safe_strtotime($rng[0]);\n        $diffdays = ceil($diff / (60*60*24)); // days between dates\n        for ($jdx = 0; $jdx <= $diffdays; $jdx++) {\n            $out[] = safe_strftime($fmt, safe_strtotime($rng[0] . (($jdx==0) ? \'\' : \'+\'.$jdx.\' days\')));\n        }\n    } else if ($start && $end && strpos($range, \'{\') === 0 && strpos($range, \'}\') === strlen($range)-1) {\n        // Day of week expansion\n        $days = do_list(trim($range,\'{}\'), \':\');\n        $diffdays = ceil(($end-$start) / (60*60*24));\n        for ($jdx = 0; $jdx <= $diffdays; $jdx++) {\n            $tm = $start + ($jdx*60*60*24);\n            if (in_array(date(\'D\', $tm), $days)) {\n                $out[] = safe_strftime($fmt, $tm);\n            }\n        }\n    } else {\n        // Single date\n        $out[] = safe_strftime($fmt, safe_strtotime($rng[0]));\n    }\n    return $out;\n}', '780c65c1a32cc9402d789bf549a48a78', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_random_text', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.14', 'Random text from file, database, string or TXP field/variable', '<div id=\"smd_help\">\n\n	<h1>smd_random_text</h1>\n\n	<p>Choose random text strings from a variety of sources. Could be used as a quote-of-the-day system, random tag lines, or something more elaborate.</p>\n\n	<h2>Features</h2>\n\n	<ul>\n		<li>Pull the item list from either:\n	<ul>\n		<li>a given delimited string</li>\n		<li>a file</li>\n		<li>a database table</li>\n		<li>a <code>&lt;txp:variable /&gt;</code></li>\n		<li>user-submitted form data</li>\n		<li>a Textpattern field</li>\n	</ul></li>\n		<li>Grab as many random items as you like from your chosen list</li>\n		<li>Pass each to a form/the container if you wish to format them</li>\n	</ul>\n\n	<h2>Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Combines ideas from dru_random_text by Drew McLellan and simple_random_text by Manuel Ebert, plus some sprinklings of my own. Many thanks to the authors of those works.</p>\n\n	<h2>Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1022/smd_random_text\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?pid=190675\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete from the <em>Admin -&gt; Plugins</em> page.</p>\n\n	<h2>Usage</h2>\n\n	<p>Use the tag in any page, form or article/file/image/link context.</p>\n\n	<h3 class=\"tag\">smd_random_text</h3>\n\n	<p class=\"tag-summary\">There are two mandatory attributes: <code>type</code> and <code>source</code>. The others are used to control how the information is extracted and/or presented.</p>\n\n	<h4 class=\"atts\">Mandatory attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm mand\">type</span> : where to look for the pool of text strings. Can be one of <code>string</code> (the default), <code>field</code>, <code>txpvar</code>, <code>urlvar</code>, <code>database</code>, or <code>file</code></li>\n		<li><span class=\"atnm mand\">source</span> : further refinement of where to find the strings using the following rules:\n	<ul>\n		<li><span>string</span> : a delimited list of text (e.g. <code>source=&quot;my|cracking|words|of|the|day|in a natty little|list&quot;</code>)</li>\n		<li><span>field</span> : the name of the Textpattern field to look in (e.g. <code>source=&quot;article_image&quot;</code> or <code>source=&quot;my_custom_field_name&quot;</code>)</li>\n		<li><span>txpvar</span> : the name of a <code>&lt;txp:variable /&gt;</code> that has been defined (e.g. <code>source=&quot;my_list&quot;</code>)</li>\n		<li><span>urlvar</span> : the name of a <span class=\"caps\">URL</span> variable (via <span class=\"caps\">GET</span> or <span class=\"caps\">POST</span>) that will be passed to the page (e.g. <code>source=&quot;property_list&quot;</code>)</li>\n		<li><span>database</span> : the table and column name, separated by <code>delim</code> (e.g. <code>source=&quot;txp_category|title&quot;</code>). You may specify more than one column from the same table by listing them separated by comma. For example <code>source=&quot;txp_discuss|message,name,discussid&quot;</code></li>\n		<li><span>file</span> : a file name relative to the root of your site, with or without leading slash (e.g. <code>source=&quot;files/my_strings.txt&quot;</code>)</li>\n	</ul></li>\n	</ul>\n\n	<h4 class=\"atts\">Formatting</h4>\n\n	<ul>\n		<li><span class=\"atnm\">limit</span> : the number of items to pull from the list. Defaults to 1. If you specify more than the available number of items in the pool, the entire pool will be used and presented in a random order</li>\n		<li><span class=\"atnm\">form</span> : the form to use to format the text. If not supplied, will try the container. If that&#8217;s not used either, the plugin will simply output the chosen text on its own</li>\n		<li><span class=\"atnm\">count</span> : whether to count <code>up</code> (the default) or <code>down</code> for each random piece of text</li>\n		<li><span class=\"atnm\">trim</span> : whether to remove whitespace from around each list item (1) or not (0). Default: 1</li>\n		<li><span class=\"atnm\">escape</span> : escape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> in any text items. Use <code>escape=&quot;&quot;</code> to turn it off. Default: <code>html</code></li>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag with which to wrap the chosen strings, e.g. <code>wraptag=&quot;ul&quot;</code>. Default: unset</li>\n		<li><span class=\"atnm\">break</span> : the (X)HTML tag to put between successive items of text, e.g. <code>break=&quot;li&quot;</code>. Default: unset</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> class to apply to the item wraptag, e.g. <code>class=&quot;random_strings&quot;</code>. Default: unset</li>\n	</ul>\n\n	<h4 class=\"atts\">Plugin configuration</h4>\n\n	<ul>\n		<li><span class=\"atnm\">delim</span> : the delimiter to use between words/attribute options. Default: pipe <code>|</code></li>\n		<li><span class=\"atnm\">file_delim</span> : the delimiter to use when parsing files. If each word or phrase is on its own line then the default newline (<code>\\n</code>) is sufficient. If you wish to have all your text strings on one line of the file delimited by some other character, set your own delimiter here. For more elaborate file structures, you may use regexes if you also set <code>file_delim_type=&quot;regex&quot;</code></li>\n		<li><span class=\"atnm\">file_delim_type</span> : choose from either <code>char</code> (the default) to tell the plugin that you are splitting your file using regular <span class=\"caps\">ASCII</span> characters in your <code>file_delim</code>, or <code>regex</code> to indicate you wish to use your own regular expression. Note there is no need to specify the regular expression delimiters as they will be chosen automatically based on the content of your regex</li>\n		<li><span class=\"atnm\">item_num</span> : show this particular item from the pool. If it doesn&#8217;t exist, you get the first item. But wait?! This is a random_text plugin, why would anybody want to specify which item to use? Two uses spring to mind: Firstly, if you have already randomized your items externally you might wish to always pick, say, the first item. Secondly &#8212; and far more subtly &#8212; if you wish to pull more than one item from any randomly chosen place, you can nest smd_random_text tags and make the inner tags always use the same row/position number as the randomly chosen outer tag. See <a href=\"#eg7\">example 7</a></li>\n		<li><span class=\"atnm\">var_prefix</span> : if you wish to nest smd_random_text tags then you may find that the replacement variable names clash. If that is the case you can alter the prefix to avoid this. Default: <code>smd_rnd_txt</code></li>\n		<li><span class=\"atnm\">shuffle</span> : perhaps a little pointless, this adds an extra layer of randomness to proceedings by shuffling the chosen items again. So if you had asked for 6 items in your list of 20 and the plugin had randomly chosen them in this order: <code>4, 17, 2, 5, 13, 11</code>, using <code>shuffle=&quot;1&quot;</code> might reorder them to <code>17, 5, 2, 11, 4, 13</code></li>\n		<li><span class=\"atnm\">debug</span> : set to 1 to show the pool, the file name (if applicable) and the chosen items. Use 2 to see what <code>field</code>s and replacements are available for your use</li>\n	</ul>\n\n	<h3>Replacement tags</h3>\n\n	<p>When using a form or container, use the following replacement tags to insert the relevant piece of information wherever you want it:</p>\n\n	<p><code>{smd_rnd_txt}</code> : the text string itself (if you have altered <code>var_prefix</code> then this replacement name is whatever you have chosen as your prefix)<br />\n<code>{smd_rnd_txt_rows}</code> : the number of items in total<br />\n<code>{smd_rnd_txt_thisrow}</code> : the current row number<br />\n<code>{smd_rnd_txt_thisindex}</code> : the current row index (zero-based)<br />\n<code>{smd_rnd_txt_chosen_item}</code> : the zero-based index number of the actual item selected by the plugin</p>\n\n	<p>In addition, if you have chosen to extract multiple columns from a database table, you will have discreet replacements available for each item suffixed by the column name. So if you had used <code>source=&quot;txp_category|name,title&quot;</code> you would have two extra replacements called <code>{smd_rnd_txt_name}</code> and <code>{smd_rnd_txt_title}</code>.</p>\n\n	<h2>Examples</h2>\n\n	<h3 id=\"eg1\">Example 1: Quote of the day</h3>\n\n	<p>Read quotes from a database. The table is called &#8220;quotes&#8221; and has already been added to the <span class=\"caps\">TXP</span> database (the connection must already be open). The column containing the quotes is called &#8220;text&#8221;:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;database&quot;\n     source=&quot;quotes|text&quot;&gt;\n  &lt;div class=&quot;qotd&quot;&gt;Quote of the day: &lt;br /&gt;{smd_rnd_txt}&lt;/div&gt;\n&lt;/txp:smd_random_text&gt;\n</code></pre>\n\n	<h3 id=\"eg2\">Example 2: Five random comments</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;database&quot;\n     source=&quot;txp_discuss|message,name&quot; limit=&quot;5&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n   &lt;p&gt;&lt;cite&gt;{smd_rnd_txt_name}&lt;/cite&gt; wrote:&lt;/p&gt;\n   &lt;blockquote&gt;\n   {smd_rnd_txt_message}\n   &lt;/blockquote&gt;\n&lt;/txp:smd_random_text&gt;\n</code></pre>\n\n	<h3 id=\"eg3\">Example 3: Top 10 words</h3>\n\n	<p>Top ten random silly words from a list in a file in the textpattern root folder, counting down from 10 to 1. Each word must be on a separate row of the file unless you alter the <code>file_delim</code> attribute; in which case the words must all appear on the first row of the file. If there aren&#8217;t 10 items in the file, the count begins from however high it can go.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;file&quot;\n     source=&quot;words.txt&quot; limit=&quot;10&quot;\n     form=&quot;wordout&quot; count=&quot;down&quot; /&gt;\n</code></pre>\n\n	<p>and in form <code>wordout</code>:</p>\n\n<pre class=\"block\"><code class=\"block\">#{smd_rnd_txt_thisrow} : {smd_rnd_txt} &lt;br /&gt;\n</code></pre>\n\n	<h3 id=\"eg4\">Example 4: Random e-mail address</h3>\n\n	<p>Use a <code>&lt;txp:variable /&gt;</code> to hold a list of random e-mail addresses from which one will be selected to be sent via a zem_contact_reborn form:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;emails&quot;\n     value=&quot;Aldous.Huxley|William.Shakespeare|John.Betjeman&quot; /&gt;\n&lt;txp:zem_contact\n     to=&#39;&lt;txp:smd_random_text type=&quot;txpvar&quot;\n     source=&quot;emails&quot; /&gt;@writers.com&#39;&gt;\n  // Other zem_contact options\n&lt;/txp:zem_contact&gt;\n</code></pre>\n\n	<h3 id=\"eg5\">Example 5: Random image selection</h3>\n\n	<p>Indirectly, not just for text! Choose two random image IDs from a comma-separated list given in the custom field labelled &#8220;my_images&#8221; and display them via the built-in <code>&lt;txp:image /&gt;</code> tag.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;field&quot;\n     source=&quot;my_images&quot; limit=&quot;2&quot; delim=&quot;,&quot;&gt;\n  &lt;txp:image id=&quot;{smd_rnd_txt}&quot; /&gt;\n&lt;/txp:smd_random_text&gt;\n</code></pre>\n\n	<h3 id=\"eg6\">Example 6: Item from a fact file</h3>\n\n	<p>Add this to your <code>files</code> form to display a random fact about each file from a pipe-delmiited list defined in the file&#8217;s <code>description</code> field:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;field&quot;\n     source=&quot;description&quot;&gt;\n&lt;br /&gt;FACT: {smd_rnd_txt}\n&lt;/txp:smd_random_text&gt;\n</code></pre>\n\n	<h3 id=\"eg7\">Example 7: Keeping items in sync</h3>\n\n	<p>Say you have a set of txp:variables that contain lists of related things:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;hp_forename&quot;&gt;\n  Harry | Hermione | Ron | Severus\n&lt;/txp:variable&gt;\n&lt;txp:variable name=&quot;hp_surname&quot;&gt;\n  Potter | Granger | Weasley | Snape\n&lt;/txp:variable&gt;\n&lt;txp:variable name=&quot;hp_age&quot;&gt;\n  14 | 16 | 15 | 130\n&lt;/txp:variable&gt;\n</code></pre>\n\n	<p>How would you ensure you retrieved the &#8216;column&#8217; that corresponds with the correct value for each character? Like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_random_text type=&quot;txpvar&quot;\n     source=&quot;hp_forename&quot; limit=&quot;2&quot; wraptag=&quot;ul&quot;\n     break=&quot;li&quot;&gt;\n   Character: {smd_rnd_txt}\n   &lt;txp:smd_random_text type=&quot;txpvar&quot;\n     source=&quot;hp_surname&quot;\n     item_num=&quot;{smd_rnd_txt_chosen_item}&quot; /&gt;\n   is &lt;txp:smd_random_text type=&quot;txpvar&quot;\n     source=&quot;hp_age&quot;\n     item_num=&quot;{smd_rnd_txt_chosen_item}&quot; /&gt;\n   years old\n&lt;/txp:smd_random_text&gt;\n</code></pre>\n\n	<p>As you can see, the first smd_random_text chooses two random character forenames from the first txp:variable. Then, for each of the chosen forenames, the second and third (inner) smd_random_text tags go and fetch <em>the same column</em> as the one just chosen. It uses <code>{smd_rnd_txt_chosen_item}</code> to force the plugin to read the correct column.</p>\n\n	<h2>Changelog</h2>\n\n	<ul>\n		<li>10 Oct 08 | 0.1 | Initial release</li>\n		<li>17 Mar 09 | 0.11 | Added <code>trim</code> and <code>escape</code></li>\n		<li>26 Jul 09 | 0.12 | Added <code>item_num</code>, <code>shuffle</code> and the replacement tag <code>{smd_rnd_txt_chosen_item}</code></li>\n		<li>28 Feb 11 | 0.13 | Permitted multiple database columns to be selected in one plugin call (thanks giampablo) ; added <code>var_prefix</code></li>\n		<li>03 May 11 | 0.14 | Added <code>file_delim_type</code> and permitted regexes in <code>file_delim</code> (thanks MyOtheHedgeFox)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 17px Georgia, sans-serif; }\n#smd_help h3 { font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em 0; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function smd_random_text($atts, $thing=NULL) {\r\n	global $txpcfg, $variable, $pretext, $thisarticle, $thisfile, $thislink, $thisimage;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'            => \'string\',\r\n		\'source\'          => \'hello world\',\r\n		\'delim\'           => \'|\',\r\n		\'file_delim\'      => \'\\n\',\r\n		\'file_delim_type\' => \'char\', // char or regex\r\n		\'out_delim\'       => \',\',\r\n		\'item_num\'        => \'\',\r\n		\'shuffle\'         => \'0\',\r\n		\'trim\'            => \'1\',\r\n		\'escape\'          => \'html\',\r\n		\'form\'            => \'\',\r\n		\'limit\'           => \'1\',\r\n		\'count\'           => \'up\',\r\n		\'wraptag\'         => \'\',\r\n		\'break\'           => \'\',\r\n		\'var_prefix\'      => \'smd_rnd_txt\',\r\n		\'class\'           => \'\',\r\n		\'debug\'           => \'0\',\r\n	), $atts));\r\n\r\n	$srcpt = explode($delim, $source);\r\n	$limit = ($limit < 1) ? 1 : $limit;\r\n	$from = array();\r\n	$items = array();\r\n	$multirow = 1;\r\n\r\n	switch ($type) {\r\n		case \"field\":\r\n			if (isset($thisfile[$srcpt[0]]) && $thisfile[$srcpt[0]] != \"\") {\r\n				$from = $thisfile[$srcpt[0]];\r\n			} else if (isset($thislink[$srcpt[0]]) && $thislink[$srcpt[0]] != \"\") {\r\n				$from = $thislink[$srcpt[0]];\r\n			} else if (isset($thisimage[$srcpt[0]]) && $thisimage[$srcpt[0]] != \"\") {\r\n				$from = $thisimage[$srcpt[0]];\r\n			} else if (isset($pretext[$srcpt[0]]) && $pretext[$srcpt[0]] != \"\") {\r\n				$from = $pretext[$srcpt[0]];\r\n			} else if (isset($thisarticle[$srcpt[0]]) && $thisarticle[$srcpt[0]] != \"\") {\r\n				$from = $thisarticle[$srcpt[0]];\r\n			}\r\n			if ($from) {\r\n				$from = explode($delim, $from);\r\n				$limit = (count($from) < $limit) ? count($from) : $limit;\r\n				$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			}\r\n			break;\r\n		case \"txpvar\":\r\n			$from = explode($delim, $variable[$srcpt[0]]);\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n		case \"urlvar\":\r\n			$from = explode($delim, gps($srcpt[0]));\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n		case \"database\":\r\n			$where = \"1=1 ORDER BY RAND() LIMIT \".$limit;\r\n			$from = safe_rows($srcpt[1], $srcpt[0], $where, $debug);\r\n			$multirow = count(explode(\',\', $srcpt[1]));\r\n			$limit = count($from);\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_keys($from);\r\n			break;\r\n		case \"file\":\r\n			$path = $txpcfg[\'doc_root\'].DS.ltrim($srcpt[0], DS);\r\n			if ($debug) {\r\n				echo \"++ FILE PATH ++\";\r\n				dmp($path);\r\n			}\r\n			if (file_exists($path)) {\r\n				if ($file_delim == \'\\n\') {\r\n					$from = doArray(file($path), \'rtrim\');\r\n				} else if ($file_delim_type == \'char\') {\r\n					$from = explode($file_delim, file_get_contents($path));\r\n				} else if ($file_delim_type == \'regex\') {					\r\n					// Cannot guarantee that a fixed delimiter won\'t break preg_match (and preg_quote doesn\'t help) so\r\n					// dynamically assign the delimiter based on the first entry in $dlmPool that is NOT in the file_delim attribute.\r\n					// This minimises (does not eliminate) the possibility of a TXP-initiated preg_match error, while still\r\n					// preserving errors outside TXP\'s control (e.g. mangled user-submitted PCRE pattern)\r\n					$dlmPool = array(\'/\', \'@\', \'#\', \'~\', \'`\', \'|\', \'!\', \'%\');\r\n					$dlm = array_merge(array_diff($dlmPool, preg_split(\'//\', $file_delim, -1)));\r\n					$dlm = (count($dlm) > 0) ? $dlm[0].$file_delim.$dlm[0] : $file_delim;\r\n					if ($debug) {\r\n						echo \"++ REGEX ++\";\r\n						dmp($dlm);\r\n					}\r\n					$from = preg_split($dlm, file_get_contents($path));\r\n				}\r\n				if ($debug > 3) {\r\n					echo \"++ SPLIT FILE CONTENTS ++\";\r\n					dmp($from);\r\n				}\r\n				$limit = (count($from) < $limit) ? count($from) : $limit;\r\n				$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			}\r\n			break;\r\n		case \"string\":\r\n		default:\r\n			$from = $srcpt;\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n	}\r\n\r\n	// Force array if it isn\'t already (some PHP functions return single values)\r\n	if (!is_array($items)) {\r\n		$items = array($items);\r\n	}\r\n\r\n	if ($shuffle) {\r\n		shuffle($items);\r\n	}\r\n\r\n	if ($multirow == 1) {\r\n		$from = ($trim) ? doArray($from, \'trim\') : $from;\r\n	}\r\n\r\n	if ($debug) {\r\n		echo \"++ ITEM POOL ++\";\r\n		dmp($from);\r\n		echo \"++ CHOSEN ITEMS ++\";\r\n		dmp($items);\r\n	}\r\n	if ($debug > 2) {\r\n		echo \"++ PRETEXT ++\";\r\n		dmp($pretext);\r\n		if (isset($thisfile)) {\r\n			echo \"++ THIS FILE ++\";\r\n			dmp($thisfile);\r\n		}\r\n		if (isset($thislink)) {\r\n			echo \"++ THIS LINK ++\";\r\n			dmp($thislink);\r\n		}\r\n		if (isset($thisimage)) {\r\n			echo \"++ THIS IMAGE ++\";\r\n			dmp($thisimage);\r\n		}\r\n		if (isset($thisarticle)) {\r\n			echo \"++ THIS ARTICLE ++\";\r\n			dmp($thisarticle);\r\n		}\r\n	}\r\n\r\n	// Find out how to plonk the output\r\n	$what = ($form) ? fetch_form($form) : (($thing) ? $thing : \'{\'.$var_prefix.\'}\');\r\n\r\n	// Pick the given $items keys out of the $from array and display them\r\n	$out = array();\r\n	$ctr = ($count==\"up\") ? 0 : $limit-1;\r\n	foreach ($items as $item) {\r\n		$replacement = array();\r\n		if (is_array($from[$item])) {\r\n			foreach ($from[$item] as $jdx => $val) {\r\n				$val = ($trim) ? trim($val) : $val;\r\n				$replacement[\'{\'.$var_prefix.\'_\'.$jdx.\'}\'] = ($escape == \'html\') ? htmlspecialchars($val) : $val;\r\n			}\r\n			$from[$item] = join($out_delim, $from[$item]);\r\n			$from[$item] = ($trim) ? trim($from[$item]) : $from[$item];\r\n		}\r\n		$replacement[\'{\'.$var_prefix.\'}\'] = ($escape == \'html\') ? htmlspecialchars($from[$item]) : $from[$item];\r\n		$replacement[\'{\'.$var_prefix.\'_rows}\'] = $limit;\r\n		$replacement[\'{\'.$var_prefix.\'_thisindex}\'] = $ctr;\r\n		$replacement[\'{\'.$var_prefix.\'_thisrow}\'] = $ctr + 1;\r\n		$replacement[\'{\'.$var_prefix.\'_chosen_item}\'] = $item;\r\n		if ($debug > 1) {\r\n			echo \"++ REPLACEMENTS ++\";\r\n			dmp($replacement);\r\n		}\r\n		$out[] = parse(strtr($what, $replacement));\r\n		$ctr = ($count==\"up\") ? $ctr+1 : $ctr-1;\r\n	}\r\n\r\n	return doWrap($out, $wraptag, $break, $class);\r\n}', 'function smd_random_text($atts, $thing=NULL) {\r\n	global $txpcfg, $variable, $pretext, $thisarticle, $thisfile, $thislink, $thisimage;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'            => \'string\',\r\n		\'source\'          => \'hello world\',\r\n		\'delim\'           => \'|\',\r\n		\'file_delim\'      => \'\\n\',\r\n		\'file_delim_type\' => \'char\', // char or regex\r\n		\'out_delim\'       => \',\',\r\n		\'item_num\'        => \'\',\r\n		\'shuffle\'         => \'0\',\r\n		\'trim\'            => \'1\',\r\n		\'escape\'          => \'html\',\r\n		\'form\'            => \'\',\r\n		\'limit\'           => \'1\',\r\n		\'count\'           => \'up\',\r\n		\'wraptag\'         => \'\',\r\n		\'break\'           => \'\',\r\n		\'var_prefix\'      => \'smd_rnd_txt\',\r\n		\'class\'           => \'\',\r\n		\'debug\'           => \'0\',\r\n	), $atts));\r\n\r\n	$srcpt = explode($delim, $source);\r\n	$limit = ($limit < 1) ? 1 : $limit;\r\n	$from = array();\r\n	$items = array();\r\n	$multirow = 1;\r\n\r\n	switch ($type) {\r\n		case \"field\":\r\n			if (isset($thisfile[$srcpt[0]]) && $thisfile[$srcpt[0]] != \"\") {\r\n				$from = $thisfile[$srcpt[0]];\r\n			} else if (isset($thislink[$srcpt[0]]) && $thislink[$srcpt[0]] != \"\") {\r\n				$from = $thislink[$srcpt[0]];\r\n			} else if (isset($thisimage[$srcpt[0]]) && $thisimage[$srcpt[0]] != \"\") {\r\n				$from = $thisimage[$srcpt[0]];\r\n			} else if (isset($pretext[$srcpt[0]]) && $pretext[$srcpt[0]] != \"\") {\r\n				$from = $pretext[$srcpt[0]];\r\n			} else if (isset($thisarticle[$srcpt[0]]) && $thisarticle[$srcpt[0]] != \"\") {\r\n				$from = $thisarticle[$srcpt[0]];\r\n			}\r\n			if ($from) {\r\n				$from = explode($delim, $from);\r\n				$limit = (count($from) < $limit) ? count($from) : $limit;\r\n				$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			}\r\n			break;\r\n		case \"txpvar\":\r\n			$from = explode($delim, $variable[$srcpt[0]]);\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n		case \"urlvar\":\r\n			$from = explode($delim, gps($srcpt[0]));\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n		case \"database\":\r\n			$where = \"1=1 ORDER BY RAND() LIMIT \".$limit;\r\n			$from = safe_rows($srcpt[1], $srcpt[0], $where, $debug);\r\n			$multirow = count(explode(\',\', $srcpt[1]));\r\n			$limit = count($from);\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_keys($from);\r\n			break;\r\n		case \"file\":\r\n			$path = $txpcfg[\'doc_root\'].DS.ltrim($srcpt[0], DS);\r\n			if ($debug) {\r\n				echo \"++ FILE PATH ++\";\r\n				dmp($path);\r\n			}\r\n			if (file_exists($path)) {\r\n				if ($file_delim == \'\\n\') {\r\n					$from = doArray(file($path), \'rtrim\');\r\n				} else if ($file_delim_type == \'char\') {\r\n					$from = explode($file_delim, file_get_contents($path));\r\n				} else if ($file_delim_type == \'regex\') {					\r\n					// Cannot guarantee that a fixed delimiter won\'t break preg_match (and preg_quote doesn\'t help) so\r\n					// dynamically assign the delimiter based on the first entry in $dlmPool that is NOT in the file_delim attribute.\r\n					// This minimises (does not eliminate) the possibility of a TXP-initiated preg_match error, while still\r\n					// preserving errors outside TXP\'s control (e.g. mangled user-submitted PCRE pattern)\r\n					$dlmPool = array(\'/\', \'@\', \'#\', \'~\', \'`\', \'|\', \'!\', \'%\');\r\n					$dlm = array_merge(array_diff($dlmPool, preg_split(\'//\', $file_delim, -1)));\r\n					$dlm = (count($dlm) > 0) ? $dlm[0].$file_delim.$dlm[0] : $file_delim;\r\n					if ($debug) {\r\n						echo \"++ REGEX ++\";\r\n						dmp($dlm);\r\n					}\r\n					$from = preg_split($dlm, file_get_contents($path));\r\n				}\r\n				if ($debug > 3) {\r\n					echo \"++ SPLIT FILE CONTENTS ++\";\r\n					dmp($from);\r\n				}\r\n				$limit = (count($from) < $limit) ? count($from) : $limit;\r\n				$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			}\r\n			break;\r\n		case \"string\":\r\n		default:\r\n			$from = $srcpt;\r\n			$limit = (count($from) < $limit) ? count($from) : $limit;\r\n			$items = ($item_num != \'\') ? ((count($from) >= $item_num) ? $item_num : 0) : array_rand($from, $limit);\r\n			break;\r\n	}\r\n\r\n	// Force array if it isn\'t already (some PHP functions return single values)\r\n	if (!is_array($items)) {\r\n		$items = array($items);\r\n	}\r\n\r\n	if ($shuffle) {\r\n		shuffle($items);\r\n	}\r\n\r\n	if ($multirow == 1) {\r\n		$from = ($trim) ? doArray($from, \'trim\') : $from;\r\n	}\r\n\r\n	if ($debug) {\r\n		echo \"++ ITEM POOL ++\";\r\n		dmp($from);\r\n		echo \"++ CHOSEN ITEMS ++\";\r\n		dmp($items);\r\n	}\r\n	if ($debug > 2) {\r\n		echo \"++ PRETEXT ++\";\r\n		dmp($pretext);\r\n		if (isset($thisfile)) {\r\n			echo \"++ THIS FILE ++\";\r\n			dmp($thisfile);\r\n		}\r\n		if (isset($thislink)) {\r\n			echo \"++ THIS LINK ++\";\r\n			dmp($thislink);\r\n		}\r\n		if (isset($thisimage)) {\r\n			echo \"++ THIS IMAGE ++\";\r\n			dmp($thisimage);\r\n		}\r\n		if (isset($thisarticle)) {\r\n			echo \"++ THIS ARTICLE ++\";\r\n			dmp($thisarticle);\r\n		}\r\n	}\r\n\r\n	// Find out how to plonk the output\r\n	$what = ($form) ? fetch_form($form) : (($thing) ? $thing : \'{\'.$var_prefix.\'}\');\r\n\r\n	// Pick the given $items keys out of the $from array and display them\r\n	$out = array();\r\n	$ctr = ($count==\"up\") ? 0 : $limit-1;\r\n	foreach ($items as $item) {\r\n		$replacement = array();\r\n		if (is_array($from[$item])) {\r\n			foreach ($from[$item] as $jdx => $val) {\r\n				$val = ($trim) ? trim($val) : $val;\r\n				$replacement[\'{\'.$var_prefix.\'_\'.$jdx.\'}\'] = ($escape == \'html\') ? htmlspecialchars($val) : $val;\r\n			}\r\n			$from[$item] = join($out_delim, $from[$item]);\r\n			$from[$item] = ($trim) ? trim($from[$item]) : $from[$item];\r\n		}\r\n		$replacement[\'{\'.$var_prefix.\'}\'] = ($escape == \'html\') ? htmlspecialchars($from[$item]) : $from[$item];\r\n		$replacement[\'{\'.$var_prefix.\'_rows}\'] = $limit;\r\n		$replacement[\'{\'.$var_prefix.\'_thisindex}\'] = $ctr;\r\n		$replacement[\'{\'.$var_prefix.\'_thisrow}\'] = $ctr + 1;\r\n		$replacement[\'{\'.$var_prefix.\'_chosen_item}\'] = $item;\r\n		if ($debug > 1) {\r\n			echo \"++ REPLACEMENTS ++\";\r\n			dmp($replacement);\r\n		}\r\n		$out[] = parse(strtr($what, $replacement));\r\n		$ctr = ($count==\"up\") ? $ctr+1 : $ctr-1;\r\n	}\r\n\r\n	return doWrap($out, $wraptag, $break, $class);\r\n}', '97fd3a884e49af7e7efdf18277f934b1', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_access_keys', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.11', 'Permit access to content for a certain time period/number of access attempts', '<div id=\"smd_help\">\n\n	<h1>smd_access_keys</h1>\n\n	<p>Permit access to downloads, pages or parts of pages for a certain configurable time period and/or for a defined number of access attempts. The access key is tamper-proof.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires <span class=\"caps\">TXP</span> 4.4.0+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1222/smd_access_keys\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> <em>Admin-&gt;Plugins</em> pane, install and enable the plugin. The table will be installed automatically unless you use the plugin from the cache directory; in which case, visit the <em>Extensions-&gt;Access keys</em> tab and click the <em>Install table</em> button before trying to use the plugin.</p>\n\n	<p>To uninstall the plugin, delete from the <em>Admin-&gt;Plugins</em> page. The access keys table will be deleted automatically.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35996\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<h2>Overview</h2>\n\n	<p>The plugin allows you to create keys for any <span class=\"caps\">URL</span> and you can then protect all or part of that <span class=\"caps\">URL</span> by either:</p>\n\n	<p>a) wrapping a tag around the parts you want protecting<br />\nb) putting a protection tag at the top of the page to protect its entirety<br />\nc) flagging the <span class=\"caps\">URL</span> as a file_download whereby it&#8217;s automatically restricted to only those in possession of the key</p>\n\n	<p>An access key is a long <span class=\"caps\">URL</span> that looks like this: <code>site.com/some/protected/url/&lt;trigger&gt;/&lt;token&gt;/&lt;timestamps&gt;.&lt;max_accesses&gt;</code>. Any deviation or alteration of the token results in rejection. The secret key used to protect the resources can be automatically generated by the plugin or supplied by you. A user-configurable salt is applied as well. For best results, let the plugin choose, because then no two secret keys/salts will ever be the same, even if they protect the exact same resource.</p>\n\n	<p>You can time-limit the access to the resource if you like &#8212; from the moment the key is generated or from some arbitrary point in the future &#8212; and/or you can limit the number of accesses (views/downloads) that resource can have. When the resource has expired, it&#8217;s gone.</p>\n\n	<p>Keys can be generated from the admin side interface (<em>Extensions-&gt;Access keys</em>) or via a public tag. The latter allows you to offer self-key generation, perhaps in response to a mem_self_register request or zem_contact_reborn form submission, or from an admin-side dashboard.</p>\n\n	<h2>Tag: <code>&lt;txp:smd_access_key /&gt;</code></h2>\n\n	<p>Generates an access token for a given <span class=\"caps\">URL</span>. Configure it using the following attributes:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">url</span></dt>\n		<dd>The <span class=\"caps\">URL</span> of the resource you want to protect. Can be either a fully qualified <span class=\"caps\">URL</span> or relative.</dd>\n		<dd>Default: the current page</dd>\n		<dt><span class=\"atnm\">trigger</span></dt>\n		<dd>This string is added to your url so the plugin knows it is protected content.</dd>\n		<dd>Default: <code>smd_akey</code></dd>\n		<dt><span class=\"atnm\">site_name</span></dt>\n		<dd>Whether to automatically add the site name (http://site.com) to the <span class=\"caps\">URL</span> or not. Choose from:\n	<dl>\n		<dd>0: leave the site out</dd>\n		<dd>1: add the site <span class=\"caps\">URL</span> (but only if its not in the <span class=\"caps\">URL</span> already)</dd>\n	</dl></dd>\n		<dd>Default: 1</dd>\n		<dt><span class=\"atnm\">section_mode</span></dt>\n		<dd>If you are trying to protect a section landing page (i.e. not an article) then you will likely receive a 404 error when trying to access <span class=\"caps\">URL</span>s that entirely use &#8216;/&#8217; as their separator. By setting <code>section_mode=&quot;1&quot;</code> you tell the plugin to separate the page <span class=\"caps\">URL</span> from the access key information with a &#8216;?&#8217; instead.</dd>\n		<dd>Note this means you cannot use the plugin with the messy mode <span class=\"caps\">URL</span> structure, nor can you pass any traditional <span class=\"caps\">URL</span> parameters to the page. You can, however, encode such parameters into the <code>extra</code> area of the key.</dd>\n		<dd>Default: 0</dd>\n		<dt><span class=\"atnm\">secret</span></dt>\n		<dd>A secret key used to unlock the page. Choose any text you wish here or leave blank to have the plugin choose a random number for you.</dd>\n		<dt><span class=\"atnm\">start</span></dt>\n		<dd>An English-formatted date stamp from when you want the resource to become available. It is often best to use <code>YYYY-MMM-DD HH:MM:SS</code> format as it is the least ambiguous.</dd>\n		<dd>Default: empty (i.e. now)</dd>\n		<dt><span class=\"atnm\">expires</span></dt>\n		<dd>An English-formatted date stamp at which you wish the resource to expire, OR an offset beginning with &#8216;+&#8217;, for example <code>expires=&quot;+24 hours&quot;</code></dd>\n		<dd>Default: empty (i.e. no key expiry: get the expiry information from the protected resource itself)</dd>\n		<dt><span class=\"atnm\">max</span></dt>\n		<dd>If you wish to limit the number of times this resource can be requested, set its value here.</dd>\n		<dd>Default: unset (i.e. unlimited)</dd>\n		<dt><span class=\"atnm\">extra</span></dt>\n		<dd>If you want to add any additional information to the <span class=\"caps\">URL</span> (after the token) then specify it here. If set, a slash will be added to the <span class=\"caps\">URL</span> and then your given content will be appended. It&#8217;s up to you to ensure it is suitably encoded. Note that you can add multiple items, separating each with a slash character if you wish. Additional values added using this attribute are directly accessible using the <code>&lt;txp:smd_access_info /&gt;</code> tag</dd>\n	</dl>\n\n	<p>A few words about this tag:</p>\n\n	<ol>\n		<li>The clock starts ticking the moment the token is generated (or at the time given in the <code>start</code> attribute)</li>\n		<li>You can pass the returned access link around as often as you like to as many people as you like and it&#8217;ll keep working until its expiry/access limit is met</li>\n		<li>Each time you create a new token (even for the same page) a new access counter is generated; any previously created tokens that still have available access attempts will continue to function until the limit is reached or they expire</li>\n		<li>Direct <span class=\"caps\">TXP</span> file downloads can be protected too. Just supply <code>trigger=&quot;file_download&quot;</code> and <code>url=&quot;/file_download/&lt;id&gt;/&lt;filename&gt;&quot;</code>. You will then not be permitted to access the site.com/file_download/<id> link without the access token being present. For this to be effective you should move your <code>/files</code> directory out of a web-accessible location or employ an .htaccess file that forbids bypassing the <code>site.com/files/&lt;filename&gt;</code> <span class=\"caps\">URL</span> format. Note that <span class=\"caps\">TXP</span> normally allows you to type anything after the <code>/id/</code> as a filename and still retrieve the file; smd_access_key will not: the filename must match exactly</li>\n		<li>If the <code>start</code> date is mangled in any way, the key will be generated &#8216;now&#8217;</li>\n		<li>If the <code>expires</code> is mangled in any way, no epxiry will be set</li>\n		<li>Your secret key is salted using a randomly generated salt. The default length of the salt is 8 characters but you can alter this via a <a href=\"#smd_akey_prefs\">pref</a></li>\n		<li>You may control the expiry time of <em>all</em> of your direct file_download <span class=\"caps\">URL</span>s by altering a <a href=\"#smd_akey_prefs\">pref</a></li>\n	</ol>\n\n	<h3>Example: Creating a key</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_key\n     url=&quot;/music/next-big-hit&quot; trigger=&quot;demo&quot; /&gt;\n</code></pre>\n\n	<p>This might generate an access-controlled <span class=\"caps\">URL</span> such as the following (newlines just for clarity):</p>\n\n<pre class=\"block\"><code class=\"block\">http://site.com/music/next-big-hit/demo/\n     42c531d13423eecaaab73a2df43a8b7c337a360a/4d9d12a5\n</code></pre>\n\n	<p>Send that link to your friend and she&#8217;ll be able to listen to your cool new demo song.</p>\n\n	<h2>Tag: <code>&lt;txp:smd_access_protect /&gt;</code></h2>\n\n	<p>Once you&#8217;ve generated a key, it&#8217;s time to protect all or part of your chosen <span class=\"caps\">URL</span>.</p>\n\n	<ul>\n		<li>To protect an entire page, put the <code>&lt;txp:smd_access_protect&gt;</code> tag right at the top, above your <span class=\"caps\">DOCTYPE</span></li>\n		<li>To only protect part of a page, wrap the protected content with the tag</li>\n		<li>File downloads are automatically protected if you have generated a key for them</li>\n	</ul>\n\n	<p>Configure the plugin with the following attributes:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">trigger</span></dt>\n		<dd>The same trigger you specified in your <code>&lt;txp:smd_access_key /&gt;</code> tag. You may specify a partial trigger if utilising <code>trigger_mode</code>.</dd>\n		<dd>Default: <code>smd_akey</code></dd>\n		<dt><span class=\"atnm\">trigger_mode</span></dt>\n		<dd>Usually you&#8217;ll want the trigger to match exactly, but you may wish to match some part of the trigger (for a practical example of this, see <a href=\"#smd_akey_eg3\">example 3</a>). Choose from:\n	<dl>\n		<dd><strong>exact</strong> : (default) incoming token&#8217;s trigger must exactly match the <code>trigger</code> attribute</dd>\n		<dd><strong>begins</strong> : incoming token&#8217;s trigger must begin with <code>trigger</code></dd>\n		<dd><strong>ends</strong> : incoming token&#8217;s trigger must end with <code>trigger</code></dd>\n		<dd><strong>contains</strong> : incoming token&#8217;s trigger must contain the text given in <code>trigger</code></dd>\n	</dl></dd>\n		<dd>Default: <code>exact</code></dd>\n		<dt><span class=\"atnm\">site_name</span></dt>\n		<dd>Add the site prefix to the <span class=\"caps\">URL</span> when trying to figure out if the resource is protected. This functions identically to the attribute in the <code>&lt;txp:smd_access_key /&gt;</code> tag and, ideally, they should match.</dd>\n		<dd>Default: 1 (yes)</dd>\n		<dt><span class=\"atnm\">section_mode</span></dt>\n		<dd>Works in an identical manner to the attribute in the <code>&lt;txp:smd_access_key /&gt;</code> tag and they should match.</dd>\n		<dd>Default: 0 (no)</dd>\n		<dt><span class=\"atnm\">force</span></dt>\n		<dd>Controls content availability. This is one method of putting the token generation tag and the protected content on the same page. Choose from:\n	<dl>\n		<dd>0: permit the resource to be directly accessible</dd>\n		<dd>1: the content is unavailable even if you <em>don&#8217;t</em> specify the access token in the <span class=\"caps\">URL</span> (e.g. if you directly visit <code>/music/next-big-hit</code>)</dd>\n	</dl></dd>\n		<dd>Default: 0</dd>\n		<dt><span class=\"atnm\">expires</span></dt>\n		<dd>The time, in seconds, after which the resource will cease to be available. Set to 0 for unlimited time.</dd>\n		<dd>If you have set an expiry in the key itself, this attribute is ignored when that key is presented.</dd>\n		<dd>Default: 3600 (1 hour)</dd>\n	</dl>\n\n	<p>(if you wish to log IP addresses for each access attempt you can set the <a href=\"#smd_akey_prefs\">pref</a>)</p>\n\n	<p>In our music example you&#8217;ll probably want to put the following tag at the top of the page:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_protect expires=&quot;86400&quot; trigger=&quot;demo&quot; force=&quot;1&quot; /&gt;\n</code></pre>\n\n	<p>So nobody can visit that page unless they have been given a valid access token <span class=\"caps\">URL</span>. The song will be available for listening for just one day from the moment the token is generated.</p>\n\n	<p>Alternatively, you might allow people to see the surrounding page and just wrap the mp3 itself with the tag:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_protect expires=&quot;86400&quot; trigger=&quot;demo&quot;&gt;\n  &lt;object type=&quot;/audio/mpeg&quot; data=&quot;/music/next-big-hit.mp3&quot; /&gt;\n&lt;/txp:smd_access_protect&gt;\n</code></pre>\n\n	<p>Note that you can protect as many parts of the same page as you like, differing only in the <code>trigger</code>. This allows you to unlock various parts of your pages to different people and set individual expiry times for each portion of the page, or set expiry times in the access tokens themselves.</p>\n\n	<p>Another option is is to elect to create the access key directly for the file_download itself. If you&#8217;re feeling paranoid you could do both :-)</p>\n\n	<h2>Tag: <code>&lt;txp:smd_if_access_error /&gt;</code></h2>\n\n	<p>Conditional tag that triggers the contained content if access was denied for some reason.  Supports <code>&lt;txp:else /&gt;</code>. Attributes:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">type</span></dt>\n		<dd>Comma-delimited list of one or more of the following access types. The corresponding <span class=\"caps\">HTTP</span> error status code thrown is given in parentheses:\n	<dl>\n		<dd><strong>smd_akey_err_bad_token</strong> (403) : if the access token in the <span class=\"caps\">URL</span> is too long/short</dd>\n		<dd><strong>smd_akey_err_expired</strong> (410) :  if the resource has passed its expiry</dd>\n		<dd><strong>smd_akey_err_forbidden</strong> (401) : triggered if the <code>force</code> attribute is set and direct access has been requested</dd>\n		<dd><strong>smd_akey_err_invalid_token</strong> (403) : the access key in the <span class=\"caps\">URL</span> doesn&#8217;t match the one used to protect the resource</dd>\n		<dd><strong>smd_akey_err_limit</strong> (410) : the number of permitted views (specified via the <code>max</code> attribute) has been exceeded</dd>\n		<dd><strong>smd_akey_err_missing_timestamp</strong> (403) : the timestamp portion of the access key is missing from the <span class=\"caps\">URL</span></dd>\n		<dd><strong>smd_akey_err_unavailable</strong> (410) : the resource isn&#8217;t yet available (the start time hasn&#8217;t been reached)</dd>\n		<dd><strong>smd_akey_err_unauthorized</strong> (401) : the resource isn&#8217;t one that has been protected by this access key</dd>\n	</dl></dd>\n		<dt><span class=\"atnm\">code</span></dt>\n		<dd>Comma-delimited list of one or more of the access codes given in the above list</dd>\n	</dl>\n\n	<p>Without any attributes, the tag triggers the contained content on any error. If you specify many <code>type</code>s then <em>any</em> of the listed types that match will return true. The same goes for <code>code</code>s. If you use the two in tandem then the error must match one of the types <span class=\"caps\">AND</span> one of the codes to return true.</p>\n\n	<h2>Tag: <code>&lt;txp:smd_access_error /&gt;</code></h2>\n\n	<p>Display an access error message/code. Typically used inside <code>&lt;txp:smd_if_access_error&gt;</code>. Attributes:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">item</span></dt>\n		<dd>Which error information to display. Comma-separate the values if you wish to use both. Choose from:\n	<dl>\n		<dd><code>message</code>: the error string</dd>\n		<dd><code>code</code>: the status code</dd>\n	</dl></dd>\n		<dd>Default: <code>message</code></dd>\n		<dt><span class=\"atnm\">message</span></dt>\n		<dd>Display this message instead of the plugin default message.</dd>\n		<dt><span class=\"atnm\">wraptag</span></dt>\n		<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the output.</dd>\n		<dt><span class=\"atnm\">class</span></dt>\n		<dd><span class=\"caps\">CSS</span> class name to apply to the wraptag.</dd>\n		<dt><span class=\"atnm\">html_id</span></dt>\n		<dd><span class=\"caps\">HTML</span> ID to apply to the wraptag.</dd>\n		<dt><span class=\"atnm\">break</span></dt>\n		<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around each item.</dd>\n		<dt><span class=\"atnm\">breakclass</span></dt>\n		<dd><span class=\"caps\">CSS</span> class name to apply to each break tag</dd>\n	</dl>\n\n	<h2>Tag: <code>&lt;txp:smd_access_info /&gt;</code></h2>\n\n	<p>Display access information from the current protected page. Attributes:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">item</span></dt>\n		<dd>One or more of the following items (comma-delimited):\n	<dl>\n		<dd><strong>page</strong> : the <span class=\"caps\">URL</span> that matches the current access key</dd>\n		<dd><strong>trigger</strong> : the trigger for the current key, as given in the <span class=\"caps\">URL</span></dd>\n		<dd><strong>maximum</strong> : the maximum number of access attempts allowed for this key</dd>\n		<dd><strong>accesses</strong> : the current number of access attempts that have been made for this key</dd>\n		<dd><strong>now</strong> : the raw <span class=\"caps\">UNIX</span> timestamp of the time the access attempt was made</dd>\n		<dd><strong>issued</strong> : the raw <span class=\"caps\">UNIX</span> timestamp of when the current key was created</dd>\n		<dd><strong>expires</strong> : the raw <span class=\"caps\">UNIX</span> timestamp of when the current key expires</dd>\n		<dd><strong>hextime</strong> : the <span class=\"caps\">UNIX</span> timestamp of when the current key was created, encoded as a hex string (as used in the <span class=\"caps\">URL</span>)</dd>\n		<dd><strong>ip</strong> : the IP address of the remote address making the request (if available)</dd>\n		<dd><strong>extra</strong> : the full string as given in the <code>extra</code> attribute</dd>\n		<dd><strong>extra_N</strong> (where N is an integer beginning at 1) : if you have specified many values in your <code>extra</code>, each separated with a slash, this displays the given individual item</dd>\n	</dl></dd>\n		<dd>Default: <code>page</code></dd>\n		<dt><span class=\"atnm\">escape</span></dt>\n		<dd>Whether to escape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> in the item. Set <code>escape=&quot;&quot;</code> to turn this off.</dd>\n		<dd>Defualt: <code>html</code></dd>\n		<dt><span class=\"atnm\">format</span></dt>\n		<dd>If displaying a time-related item (now, issued, or expires) you can format it using the <a href=\"http://php.net/manual/en/function.strftime.php\">strftime() formatting codes</a>.</dd>\n		<dd>Default: <code>%Y-%m-%d %H:%M:%S</code></dd>\n		<dt><span class=\"atnm\">wraptag</span></dt>\n		<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the output.</dd>\n		<dt><span class=\"atnm\">class</span> :<br />\n<span class=\"caps\">CSS</span> class name to apply to the wraptag.</dt>\n		<dt><span class=\"atnm\">html_id</span></dt>\n		<dd><span class=\"caps\">HTML</span> ID to apply to the wraptag.</dd>\n		<dt><span class=\"atnm\">break</span></dt>\n		<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around each item.</dd>\n		<dt><span class=\"atnm\">breakclass</span></dt>\n		<dd><span class=\"caps\">CSS</span> class name to apply to each break tag.</dd>\n	</dl>\n\n	<h2>Admin side interface</h2>\n\n	<p>Under <em>Extensions-&gt;Access keys</em> you will see a list of all keys that have been generated. You can sort the list (ascending or descending) by clicking the headings or you can filter them using the select list and text box at the top. The usual multi-edit tools/checkboxes are available to allow you to batch-delete keys.</p>\n\n	<p>If you want to manually create an access key, click <em>New key</em> and fill in as many of the text boxes as you wish. The only one that is mandatory is the <em>Page</em> box. Your access key will be generated and displayed in the message area for you to copy and paste.</p>\n\n	<p>A few things to note about the list:</p>\n\n	<ul>\n		<li>If you have elected to log IP addresses (see <a href=\"#smd_akey_prefs\">prefs</a>) the IPs of all key-based access attempts are logged. Further, if you have <span class=\"caps\">TXP</span> visitor logging enabled, the addresses in the &#8216;IP&#8217; column are hyperlinked to the Visitor Logs to show you all accesses by the clicked IP. Although the plugin can potentially hold over 1600 IP addresses the interface might crumble before you reach this limit so it&#8217;s best to try and limit the number of accesses per key to keep the number of attempts low and make the interface manageable</li>\n		<li>The Page is hyperlinked to the protected <span class=\"caps\">URL</span> but it does <em>not</em> attempt to access it with the key. It is just a convenience for you to check that the page is protected. If you want to access the page via its key you can do so from the Visitor Logs, assuming someone has tried at least once to access the resource &#8212; you can just click the relevant visitor log Page entry which contains the full access key <span class=\"caps\">URL</span></li>\n		<li>The number of accesses may exceed the maximum permitted. This is because attempts are logged all the time the resource is available (i.e. has not expired). Rest assured if the maximum access limit has been reached, visitors with valid keys will <span class=\"caps\">NOT</span> see the protected content, even though the counter will continue to log requests. You can use this information to check the <span class=\"caps\">TXP</span> Visitor Logs for IP addresses that try to access a resource after the limit has been reached. As soon as the resource expires, access counting ceases</li>\n		<li>The trigger for file downloads is empty. This is a side-effect of the way the plugin operates but it has a useful bonus: you can leave the trigger blank if your Page <span class=\"caps\">URL</span> contains <code>/file_download</code> (because the trigger itself is the presence of <code>file_download</code> in the <span class=\"caps\">URL</span>)</li>\n	</ul>\n\n	<h2 id=\"smd_akey_prefs\">Preferences</h2>\n\n	<p>The plugin exposes some global preference settings that govern its operation:</p>\n\n	<dl>\n		<dt><span class=\"atnm\">File download expiry time</span></dt>\n		<dd>Time, in seconds, after which your file downloads will expire, following generation of an access key.</dd>\n		<dd>Default: 3600 (1 hour)</dd>\n		<dt><span class=\"atnm\">Salt length</span></dt>\n		<dd>Number of characters to use as a salt in your secret key. The default is usually fine but if you want to alter this for greater/reduced security then do so. <span class=\"caps\">IMPORTANT</span>: if you change this value, all your existing access keys become instantly invalid.</dd>\n		<dd>Default: 8</dd>\n		<dt><span class=\"atnm\">Log IP addresses</span></dt>\n		<dd>Whether you wish the plugin to log the IP address of each visitor who tries to access a protected resource</dd>\n		<dd>Default: no</dd>\n	</dl>\n\n	<h2 id=\"smd_akey_eg1\">Example 1: Protect part of a page</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_protect\n     trigger=&quot;leaflets&quot; force=&quot;1&quot; expires=&quot;86400&quot;&gt;\n   &lt;p&gt;Your leaflet contents goes here&lt;/p&gt;\n&lt;txp:else /&gt;\n   &lt;txp:smd_if_access_error\n     type=&quot;smd_akey_err_forbidden&quot;&gt;\n      &lt;p&gt;Before you can download this item, you&#39;ll need\n     an access key.\n     &lt;a href=&quot;&lt;txp:smd_access_key max=&quot;4&quot;\n        trigger=&quot;leaflets&quot; /&gt;&quot;&gt;Here&#39;s one&lt;/a&gt;&lt;/p&gt;\n   &lt;txp:else /&gt;\n      &lt;txp:smd_access_error item=&quot;code, message&quot;\n          break=&quot;br&quot; /&gt;\n   &lt;/txp:smd_if_access_error&gt;\n&lt;/txp:smd_access_protect&gt;\n</code></pre>\n\n	<h2 id=\"smd_akey_eg2\">Example 2: Protect multiple resources with one tag</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_protect\n     trigger=&quot;leaflet, book&quot; expires=&quot;86400&quot;&gt;\n   &lt;p&gt;Your &lt;txp:smd_access_info item=&quot;trigger&quot; /&gt;\n          contents goes here&lt;/p&gt;\n&lt;txp:else /&gt;\n   &lt;txp:smd_if_access_error&gt;\n      &lt;txp:smd_access_error item=&quot;code, message&quot;\n          break=&quot;br&quot; /&gt;\n   &lt;/txp:smd_if_access_error&gt;\n&lt;/txp:smd_access_protect&gt;\n</code></pre>\n\n	<p>You can use smd_if, or assign the output of <code>&lt;txp:smd_access_info&gt;</code> to a <code>&lt;txp:variable&gt;</code> and use <code>&lt;txp:if_variable&gt;</code>, to perform conditional display of access key information.</p>\n\n	<h2 id=\"smd_akey_eg3\">Example 3: Issue a registration activation code</h2>\n\n	<p>In your registration e-mail form, you could put something like this:</p>\n\n<pre class=\"block\"><code class=\"block\">Thank you for registering with &lt;txp:site_name /&gt;.\nTo activate your account, please visit the following\nlink within the next 24 hours:\n&lt;txp:smd_access_key url=&quot;/account/activate/&quot; max=&quot;1&quot;\n     trigger=&#39;user_id.&lt;txp:author title=&quot;0&quot; /&gt;&#39; /&gt;\n</code></pre>\n\n	<p>Once the visitor has signed up they are e-mailed a unique access token to the <code>/account/activate</code> page. You can protect it like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_protect trigger=&quot;user_id.&quot;\n     trigger_mode=&quot;begins&quot; expires=&quot;86400&quot; force=&quot;1&quot;&gt;\n   &lt;txp:hide&gt; Activation actions could go here &lt;/txp:hide&gt;\n   &lt;p&gt;Congratulations, your account is activated.&lt;/p&gt;\n&lt;txp:else /&gt;\n   &lt;txp:smd_access_error /&gt;\n&lt;/txp:smd_access_protect&gt;\n</code></pre>\n\n	<p>If necessary, you could employ <code>str_replace()</code> (or <code>&lt;txp:rah_replace /&gt;</code>) to remove the <code>user_id.</code> and be left with the user name of the registered user name, suitable for display. Alternatively &#8212; and perhaps easier &#8212; you could pass the author as an <code>extra</code> parameter when the key is generated:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_access_key url=&quot;/account/activate/&quot; max=&quot;1&quot;\n     trigger=&quot;user_id&quot; extra=&#39;&lt;txp:author title=&quot;0&quot; /&gt;&#39; /&gt;\n</code></pre>\n\n	<p>This allows you to use the default <code>trigger_mode</code> and has the benefit that you can directly access the additional piece of information using <code>&lt;txp:smd_access_info item=&quot;extra_1&quot; /&gt;</code>.</p>\n\n	<p>The <code>extra</code> attribute is even more powerful if you pack more information into it: <code>extra=&#39;&lt;txp:author title=&quot;0&quot; /&gt;/&lt;txp:section /&gt;&#39;</code>. You can retrieve each piece individually:</p>\n\n	<ul>\n		<li><code>&lt;txp:smd_access_info item=&quot;extra&quot; /&gt;</code> =&gt; <code>sdawson/account</code></li>\n		<li><code>&lt;txp:smd_access_info item=&quot;extra_1&quot; /&gt;</code> =&gt; <code>sdawson</code></li>\n		<li><code>&lt;txp:smd_access_info item=&quot;extra_2&quot; /&gt;</code> =&gt; <code>account</code></li>\n	</ul>\n\n	<p>Note that you can only extract each piece individually if you use a slash as a separator in your <code>extra</code> attribute.</p>\n\n	<h2>Author</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n	<h2>Changelog</h2>\n\n	<ul>\n		<li>18 Apr 2011 | 0.10 | Initial release</li>\n		<li>30 Jan 2012 | 0.11 | Added <code>expires</code> to smd_access_key ; fixed no-criteria output ; plays nicely with gbp_permanent_links (thanks jakob) ; fixed subdir <span class=\"caps\">URL</span> error, added <code>section_mode</code> attribute and fixed <span class=\"caps\">URL</span> decoding (thanks sacripant)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_access_keys\r\n *\r\n * A Textpattern CMS plugin for secure tokenized access to resources:\r\n *  -> Time-based or access attempt limits\r\n *  -> Untamperable URL-based keys\r\n *  -> Optional IP logging\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n// TODO: Add shortcut to send a newly-generated admin-side key to a user? (dropdown of users + e-mail template in prefs?)\r\n//       Add auto-deletion of expired keys pref:\r\n//        -> File download keys can be deleted on any key access (expiry window is known via prefs)\r\n//        -> Other key accesses can only be deleted when that key is used\r\n//        -> Configurable grace period after expiry, before deletion\r\n//       Obfusctaed URLs (see http://radioartnet.net/11/2011/07/05/samuel-beckett-words-and-music/)\r\n//       Query an access key and separate it into its component parts for convenient testing\r\nif(@txpinterface == \'admin\') {\r\n	global $smd_akey_event, $smd_akey_styles;\r\n	$smd_akey_event = \'smd_akey\';\r\n\r\n	$smd_akey_styles = array(\r\n		\'list\' =>\r\n         \'.smd_hidden { display:none; }\',\r\n	);\r\n\r\n	add_privs($smd_akey_event, \'1\');\r\n	add_privs(\'plugin_prefs.smd_access_keys\', \'1\');\r\n	register_tab(\'extensions\', $smd_akey_event, smd_akey_gTxt(\'smd_akey_tab_name\'));\r\n	register_callback(\'smd_akey_dispatcher\', $smd_akey_event);\r\n	register_callback(\'smd_akey_welcome\', \'plugin_lifecycle.smd_access_keys\');\r\n	register_callback(\'smd_akey_prefs\', \'plugin_prefs.smd_access_keys\');\r\n}\r\n\r\nglobal $smd_akey_prefs;\r\n$smd_akey_prefs = array(\r\n	\'smd_akey_file_download_expires\' => array(\r\n		\'html\'     => \'text_input\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 10,\r\n		\'default\'  => \'3600\',\r\n	),\r\n	\'smd_akey_salt_length\' => array(\r\n		\'html\'     => \'text_input\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 20,\r\n		\'default\'  => \'8\',\r\n	),\r\n	\'smd_akey_log_ip\' => array(\r\n		\'html\'     => \'yesnoradio\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 30,\r\n		\'default\'  => \'0\',\r\n	),\r\n);\r\n\r\nif (!defined(\'SMD_AKEYS\')) define(\"SMD_AKEYS\", \'smd_akeys\');\r\n\r\nregister_callback(\'smd_access_protect_download\', \'file_download\');\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n// Jump off point for event/steps\r\nfunction smd_akey_dispatcher($evt, $stp) {\r\n	if(!$stp or !in_array($stp, array(\r\n			\'smd_akey_table_install\',\r\n			\'smd_akey_table_remove\',\r\n			\'smd_akey_create\',\r\n			\'smd_akey_prefs\',\r\n			\'smd_akey_prefsave\',\r\n			\'smd_akey_multi_edit\',\r\n			\'smd_akey_change_pageby\',\r\n		))) {\r\n		smd_akey(\'\');\r\n	} else $stp();\r\n}\r\n\r\n// Bootstrap when installed/deleted\r\nfunction smd_akey_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_akey_table_install(0);\r\n			$msg = \'Restrict your Txp world :-)\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_akey_table_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// Main admin interface\r\nfunction smd_akey($msg=\'\') {\r\n	global $smd_akey_event, $smd_akey_list_pageby, $smd_akey_styles, $logging, $smd_akey_prefs;\r\n\r\n	pagetop(smd_akey_gTxt(\'smd_akey_tab_name\'), $msg);\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n		if ($sort === \'\') $sort = get_pref(\'smd_akey_sort_column\', \'time\');\r\n		if ($dir === \'\') $dir = get_pref(\'smd_akey_sort_dir\', \'desc\');\r\n		$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\r\n\r\n		switch ($sort) {\r\n			case \'page\':\r\n				$sort_sql = \'page \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'triggah\':\r\n				$sort_sql = \'triggah \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'maximum\':\r\n				$sort_sql = \'maximum \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'accesses\':\r\n				$sort_sql = \'accesses \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'ip\':\r\n				$sort_sql = \'ip \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			default:\r\n				$sort = \'time\';\r\n				$sort_sql = \'time \'.$dir;\r\n			break;\r\n		}\r\n\r\n		set_pref(\'smd_akey_sort_column\', $sort, \'smd_akey\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n		set_pref(\'smd_akey_sort_dir\', $dir, \'smd_akey\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n\r\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n		$criteria = 1;\r\n\r\n		if ($search_method and $crit) {\r\n			$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\r\n			$critsql = array(\r\n				\'page\'     => \"page like \'%$crit_escaped%\'\",\r\n				\'triggah\'  => \"triggah like \'%$crit_escaped%\'\",\r\n				\'maximum\'  => \"maximum = \'$crit_escaped\'\",\r\n				\'accesses\' => \"accesses = \'$crit_escaped\'\",\r\n				\'ip\'       => \"ip like \'%$crit_escaped%\'\",\r\n			);\r\n\r\n			if (array_key_exists($search_method, $critsql)) {\r\n				$criteria = $critsql[$search_method];\r\n				$limit = 500;\r\n			} else {\r\n				$search_method = \'\';\r\n				$crit = \'\';\r\n			}\r\n		} else {\r\n			$search_method = \'\';\r\n			$crit = \'\';\r\n		}\r\n\r\n		$total = safe_count(SMD_AKEYS, \"$criteria\");\r\n\r\n		echo \'<div id=\"\'.$smd_akey_event.\'_control\" class=\"txp-control-panel\">\';\r\n\r\n		if ($total < 1) {\r\n			if ($criteria != 1) {\r\n				echo n.smd_akey_search_form($crit, $search_method).\r\n					n.graf(gTxt(\'no_results_found\'), \' class=\"indicator\"\').\'</div>\';\r\n					return;\r\n			}\r\n		}\r\n\r\n		$limit = max($smd_akey_list_pageby, 15);\r\n\r\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\r\n\r\n		echo n.smd_akey_search_form($crit, $search_method).\'</div>\';\r\n\r\n		// Retrieve the secret keyring table entries\r\n		$secring = safe_rows(\'*\', SMD_AKEYS, \"$criteria order by $sort_sql limit $offset, $limit\");\r\n\r\n		// Set up the buttons and column info\r\n		$newbtn = \'<a class=\"navlink\" href=\"#\" onclick=\"return smd_akey_togglenew();\">\'.smd_akey_gTxt(\'smd_akey_btn_new\').\'</a>\';\r\n		$prefbtn = \'<a class=\"navlink\" href=\"?event=\'.$smd_akey_event.a.\'step=smd_akey_prefs\">\'.smd_akey_gTxt(\'smd_akey_btn_pref\').\'</a>\';\r\n		$showip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\'], 1);\r\n\r\n		echo <<<EOC\r\n<script type=\"text/javascript\">\r\nfunction smd_akey_togglenew() {\r\n	box = jQuery(\"#smd_akey_create\");\r\n	if (box.css(\"display\") == \"none\") {\r\n		box.show();\r\n	} else {\r\n		box.hide();\r\n	}\r\n	jQuery(\"input.smd_focus\").focus();\r\n	return false;\r\n}\r\njQuery(function() {\r\n	jQuery(\"#smd_akey_add\").click(function () {\r\n		jQuery(\"#smd_akey_step\").val(\'smd_akey_create\');\r\n		jQuery(\"#smd_akey_form\").removeAttr(\'onsubmit\').submit();\r\n	});\r\n});\r\n</script>\r\nEOC;\r\n		// Inject styles\r\n		echo \'<style type=\"text/css\">\' . $smd_akey_styles[\'list\'] . \'</style>\';\r\n\r\n		// Access key list\r\n		echo n.\'<div id=\"\'.$smd_akey_event.\'_container\" class=\"txp-container txp-list\">\';\r\n		echo \'<form name=\"longform\" id=\"smd_akey_form\" action=\"index.php\" method=\"post\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\';\r\n		echo startTable(\'list\');\r\n		echo n.\'<thead>\'\r\n			.n.tr(tda($newbtn . sp . $prefbtn, \' class=\"noline\"\'))\r\n			.n.tr(\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_page\'), \'page\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'page\' == $sort) ? \"$dir \" : \'\').\'page\').\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_trigger\'), \'triggah\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'triggah\' == $sort) ? \"$dir \" : \'\')).\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_time\'), \'time\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'time\' == $sort) ? \"$dir \" : \'\').\'date time\').\r\n				n.hCell(gTxt(\'expires\'), \'expires\', \' class=\"date time\"\').\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_max\'), \'maximum\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'maximum\' == $sort) ? \"$dir \" : \'\')).\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_accesses\'), \'accesses\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'accesses\' == $sort) ? \"$dir \" : \'\')).\r\n				(($showip) ? n.column_head(\'IP\', \'ip\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'ip\' == $sort) ? \"$dir \" : \'\').\'ip\') : \'\').\r\n				n.hCell(\'\', \'\', \' class=\"multi-edit\"\')\r\n			).\r\n			n.\'</thead>\';\r\n\r\n		$multiOpts = array(\'smd_akey_delete\' => gTxt(\'delete\'));\r\n\r\n		echo \'<tfoot>\' . tr(tda(\r\n				select_buttons()\r\n				.n.selectInput(\'smd_akey_multi_edit\', $multiOpts, \'\', true)\r\n				.n.eInput($smd_akey_event)\r\n				.n.fInput(\'submit\', \'\', gTxt(\'go\'), \'smallerbox\')\r\n			,\' class=\"multi-edit\" colspan=\"\' . (($showip) ? 7 : 6) . \'\" style=\"text-align: right; border: none;\"\'));\r\n		echo \'</tfoot>\';\r\n		echo \'<tbody>\';\r\n\r\n		// New access key row\r\n		echo \'<tr id=\"smd_akey_create\" class=\"smd_hidden\">\';\r\n		echo td(fInput(\'hidden\', \'step\', \'smd_akey_multi_edit\', \'\', \'\', \'\', \'\', \'\', \'smd_akey_step\').fInput(\'text\', \'smd_akey_newpage\', \'\', \'smd_focus\', \'\', \'\', \'60\'))\r\n			.td(fInput(\'text\', \'smd_akey_triggah\', \'\'))\r\n			.td(fInput(\'text\', \'smd_akey_time\', safe_strftime(\'%Y-%m-%d %H:%M:%S\'), \'\', \'\', \'\', \'25\'))\r\n			.td(fInput(\'text\', \'smd_akey_expires\', \'\', \'\', \'\', \'\', \'25\'))\r\n			.td(fInput(\'text\', \'smd_akey_maximum\', \'\', \'\', \'\', \'\', \'5\'))\r\n			.td(\'&nbsp;\')\r\n			. (($showip) ? td(\'&nbsp;\') : \'\')\r\n			.td(fInput(\'submit\', \'smd_akey_add\', gTxt(\'add\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'smd_akey_add\'));\r\n		echo \'</tr>\';\r\n\r\n		// Remaining access keys\r\n		foreach ($secring as $secidx => $data) {\r\n			if ($showip) {\r\n				$ips = do_list($data[\'ip\'], \' \');\r\n				$iplist = array();\r\n				foreach ($ips as $ip) {\r\n					$iplist[] = ($logging == \'none\') ? $ip : eLink(\'log\', \'log_list\', \'search_method\', \'ip\', $ip, \'crit\', $ip);\r\n				}\r\n			}\r\n			$dkey = $data[\'page\'].\'|\'.$data[\'t_hex\'];\r\n			$timeparts = do_list($data[\'t_hex\'], \'-\');\r\n			$expiry = (isset($timeparts[1])) ? hexdec($timeparts[1]) : \'\';\r\n			echo tr(\r\n				td(\'<a href=\"\'.$data[\'page\'].\'\">\'.$data[\'page\'].\'</a>\', \'\', \'page\')\r\n				. td($data[\'triggah\'])\r\n				. td(safe_strftime(\'%Y-%m-%d %H:%M:%S\', $data[\'time\']), 85, \'date time\')\r\n				. td( (($expiry) ? safe_strftime(\'%Y-%m-%d %H:%M:%S\', $expiry) : \'-\'), 85, \'date time\')\r\n				. td($data[\'maximum\'])\r\n				. td($data[\'accesses\'])\r\n				. ( ($showip) ? td( trim(join(\' \', $iplist)), 20, \'ip\' ) : \'\' )\r\n				. td( fInput(\'checkbox\', \'selected[]\', $dkey, \'checkbox\'), \'\', \'multi-edit\')\r\n			);\r\n		}\r\n		echo \'</tbody>\';\r\n		echo endTable();\r\n		echo \'</form>\';\r\n\r\n		echo \'<div id=\"\'.$smd_akey_event.\'_navigation\" class=\"txp-navigation\">\'.\r\n			n.nav_form($smd_akey_event, $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit).\r\n\r\n			n.pageby_form($smd_akey_event, $smd_akey_list_pageby).\r\n			n.\'</div>\'.n.\'</div>\';\r\n\r\n	} else {\r\n		// Table not installed\r\n		$btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_akey_event.a.\'step=smd_akey_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', smd_akey_gTxt(\'smd_akey_tbl_install_lbl\'), \'smallerbox\').\'</form>\';\r\n		$btnStyle = \' style=\"border:0;height:25px\"\';\r\n		echo startTable(\'list\');\r\n		echo tr(tda(strong(smd_akey_gTxt(\'smd_akey_prefs_some_tbl\')).br.br\r\n				.smd_akey_gTxt(\'smd_akey_prefs_some_explain\').br.br\r\n				.smd_akey_gTxt(\'smd_akey_prefs_some_opts\'), \' colspan=\"2\"\')\r\n		);\r\n		echo tr(tda($btnInstall, $btnStyle));\r\n		echo endTable();\r\n	}\r\n}\r\n\r\n// Change and store qty-per-page value\r\nfunction smd_akey_change_pageby() {\r\n	event_change_pageby(\'smd_akey\');\r\n	smd_akey();\r\n}\r\n\r\n// The search dropdown list\r\nfunction smd_akey_search_form($crit, $method) {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	$doip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\'], 1);\r\n\r\n	$methods =	array(\r\n		\'page\'     => smd_akey_gTxt(\'smd_akey_page\'),\r\n		\'triggah\'  => smd_akey_gTxt(\'smd_akey_trigger\'),\r\n		\'maximum\'  => smd_akey_gTxt(\'smd_akey_max\'),\r\n		\'accesses\' => smd_akey_gTxt(\'smd_akey_accesses\'),\r\n	);\r\n\r\n	if ($doip) {\r\n		$methods[\'ip\'] = gTxt(\'IP\');\r\n	}\r\n\r\n	return search_form($smd_akey_event, \'\', $crit, $methods, $method, \'page\');\r\n}\r\n\r\n// Create a key from the admin side\'s \'New key\' button\r\nfunction smd_akey_create() {\r\n	extract(gpsa(array(\'smd_akey_newpage\', \'smd_akey_triggah\', \'smd_akey_time\', \'smd_akey_expires\', \'smd_akey_maximum\')));\r\n\r\n	if ($smd_akey_newpage) {\r\n		// Just call the public tag with the relevant options\r\n		$key = smd_access_key(\r\n			array(\r\n				\'url\'     => $smd_akey_newpage,\r\n				\'trigger\' => $smd_akey_triggah,\r\n				\'start\'   => $smd_akey_time,\r\n				\'expires\' => $smd_akey_expires,\r\n				\'max\'     => $smd_akey_maximum,\r\n			)\r\n		);\r\n		$msg = smd_akey_gTxt(\'smd_akey_generated\', array(\'{key}\' => $key));\r\n	} else {\r\n		$msg = array(smd_akey_gTxt(\'smd_akey_need_page\'), E_ERROR);\r\n	}\r\n	smd_akey($msg);\r\n}\r\n\r\n// Handle submission of the multi-edit dropdown options\r\nfunction smd_akey_multi_edit() {\r\n	$selected = gps(\'selected\');\r\n	$operation = gps(\'smd_akey_multi_edit\');\r\n	$del = 0;\r\n	$msg = \'\';\r\n\r\n	switch ($operation) {\r\n		case \'smd_akey_delete\':\r\n			if ($selected) {\r\n				foreach ($selected as $sel) {\r\n					$parts = explode(\'|\', $sel);\r\n					$ret = safe_delete(SMD_AKEYS, \"page = \'\" . $parts[0] . \"\' AND t_hex = \'\" . $parts[1] . \"\'\");\r\n					$del = ($ret) ? $del+1 : $del;\r\n				}\r\n				$msg = smd_akey_gTxt(\'smd_akey_deleted\', array(\'{deleted}\' => $del));\r\n			}\r\n		break;\r\n	}\r\n\r\n	smd_akey($msg);\r\n}\r\n\r\n// Display the prefs\r\nfunction smd_akey_prefs() {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	pagetop(smd_akey_gTxt(\'smd_akey_pref_legend\'));\r\n\r\n	$out = array();\r\n	$out[] = \'<form name=\"smd_akey_prefs\" id=\"smd_akey_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_akey_event).sInput(\'smd_akey_prefsave\');\r\n	$out[] = startTable(\'list\');\r\n	$out[] = tr(tdcs(strong(smd_akey_gTxt(\'smd_akey_pref_legend\')), 2));\r\n	foreach ($smd_akey_prefs as $idx => $prefobj) {\r\n		$subout = array();\r\n		$subout[] = tda(\'<label for=\"\'.$idx.\'\">\'.smd_akey_gTxt($idx).\'</label>\', \' class=\"noline\" style=\"text-align: right; vertical-align: middle;\"\');\r\n		$val = get_pref($idx, $prefobj[\'default\']);\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = fInputCell($idx, $val, \'\', \'\', \'\', $idx);\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = tda(yesnoRadio($idx, $val),\' class=\"noline\"\');\r\n			break;\r\n		}\r\n		$out[] = tr(join(n ,$subout));\r\n	}\r\n\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\'), \' class=\"noline\"\'));\r\n	$out[] = endTable();\r\n	$out[] = \'</form>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// Save the prefs\r\nfunction smd_akey_prefsave() {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	foreach ($smd_akey_prefs as $idx => $prefobj) {\r\n		$val = ps($idx);\r\n		set_pref($idx, $val, $smd_akey_event, $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n	}\r\n\r\n	$msg = smd_akey_gTxt(\'smd_akey_prefs_saved\');\r\n\r\n	smd_akey($msg);\r\n}\r\n\r\n// Add akey table if not already installed\r\nfunction smd_akey_table_install($showpane=\'1\') {\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n\r\n	// Use \'triggah\' and \'maximum\' because \'trigger\' and \'max\' are reserved words.\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_AKEYS.\"` (\r\n		`page` varchar(255) NOT NULL default \'\',\r\n		`t_hex` varchar(17) NOT NULL default \'\',\r\n		`time` int(14) NOT NULL default 0,\r\n		`secret` varchar(255) NOT NULL default \'\',\r\n		`triggah` varchar(255) NULL default \'\',\r\n		`maximum` int(11) NULL default 0,\r\n		`accesses` int(11) NULL default 0,\r\n		`ip` text NOT NULL default \'\',\r\n		PRIMARY KEY (`page`,`t_hex`)\r\n	) ENGINE=MyISAM\";\r\n\r\n	if(gps(\'debug\')) {\r\n		dmp($sql);\r\n	}\r\n	foreach ($sql as $qry) {\r\n		$ret = safe_query($qry);\r\n		if ($ret===false) {\r\n			$GLOBALS[\'txp_err_count\']++;\r\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n			echo \"<!--\\n $qry \\n-->\\n\";\r\n		}\r\n	}\r\n\r\n	// Spit out results\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		if ($showpane) {\r\n			$msg = smd_akey_gTxt(\'smd_akey_tbl_installed\');\r\n			smd_akey($msg);\r\n		}\r\n	} else {\r\n		if ($showpane) {\r\n			$msg = smd_akey_gTxt(\'smd_akey_tbl_not_installed\');\r\n			smd_akey($msg);\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_akey_table_remove() {\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_akey_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_AKEYS. \"; \";\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$msg = smd_akey_gTxt(\'smd_akey_tbl_removed\');\r\n	} else {\r\n		$msg = smd_akey_gTxt(\'smd_akey_tbl_not_removed\');\r\n		smd_akey($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_akey_table_exist($type=\'\') {\r\n	global $plugins_ver, $DB;\r\n\r\n	// Upgrade check\r\n	$ver = get_pref(\'smd_akey_installed_version\', \'\');\r\n	if (!$ver || $plugins_ver[\'smd_access_keys\'] != $ver) {\r\n		// Increase the size of the t_hex field to allow for expiry times\r\n		$ret = @safe_field(\"CHARACTER_MAXIMUM_LENGTH\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_AKEYS.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'t_hex\'\");\r\n		if ($ret != \'17\') {\r\n			safe_alter(SMD_AKEYS, \"CHANGE `t_hex` `t_hex` VARCHAR( 17 ) NOT NULL DEFAULT \'\'\");\r\n			set_pref(\'smd_akey_installed_version\', $plugins_ver[\'smd_access_keys\'], \'smd_akey\', PREF_HIDDEN, \'\', 0);\r\n		}\r\n	}\r\n\r\n	if ($type == \'1\') {\r\n		$tbls = array(SMD_AKEYS => 8);\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			if (count(@safe_show(\'columns\', $tbl)) == $cols) {\r\n				$out--;\r\n			}\r\n		}\r\n		return ($out===0) ? 1 : 0;\r\n	} else {\r\n		return(@safe_show(\'columns\', SMD_AKEYS));\r\n	}\r\n}\r\n\r\n//**********************\r\n// PUBLIC SIDE INTERFACE\r\n//**********************\r\nfunction smd_access_key($atts, $thing=NULL) {\r\n	global $smd_akey_prefs, $smd_akey_info;\r\n\r\n	// In case this tag is called from the admin side - needs parse()\r\n	include_once txpath.\'/publish.php\';\r\n\r\n	extract(lAtts(array(\r\n		\'secret\'       => \'\',\r\n		\'url\'          => \'\',\r\n		\'site_name\'    => \'1\',\r\n		\'section_mode\' => \'0\',\r\n		\'start\'        => \'\',\r\n		\'expires\'      => \'\',\r\n		\'trigger\'      => \'smd_akey\',\r\n		\'max\'          => \'\',\r\n		\'extra\'        => \'\',\r\n		\'form\'         => \'\',\r\n	),$atts));\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		$thing = (empty($form)) ? $thing : fetch_form($form);\r\n		$thing = (empty($thing)) ? \'<txp:smd_access_info item=\"key\" />\' : $thing;\r\n\r\n		$trigger = trim($trigger);\r\n		$trigger = ($trigger == \'file_download\') ? \'\' : $trigger;\r\n\r\n		$smd_akey_salt_length = get_pref(\'smd_akey_salt_length\', $smd_akey_prefs[\'smd_akey_salt_length\'][\'default\']);\r\n\r\n		// Without a URL, assume current page\r\n		$page = rtrim( (($url) ? $url : serverSet(\'REQUEST_URI\')), \'/\');\r\n\r\n		if ($site_name && (strpos($page, \'http\') !== 0)) {\r\n			// Can\'t use raw hu since it contains the subdir (as does the REQUEST_URI)\r\n			// so duplicate portions would occur in the generated URL\r\n			$urlparts = parse_url(hu);\r\n			$page = $urlparts[\'scheme\'] . \'://\' . $urlparts[\'host\'] . $page;\r\n		}\r\n\r\n		if (!$secret) {\r\n			$secret = uniqid(\'\', true);\r\n		}\r\n\r\n		$salt = substr(md5(uniqid(rand(), true)), 0, $smd_akey_salt_length);\r\n		$plen = strlen($page) % 32; // Because 32 is the size of an md5 string and we don\'t want to fall off the end\r\n\r\n		// Generate a timestamp. The clock starts ticking from this moment\r\n		$ts = ($start) ? safe_strtotime($start) : time();\r\n		$ts = ($ts === false) ? time() : $ts;\r\n		$t_hex = dechex($ts);\r\n\r\n		// Any expiry to add?\r\n		if ($expires) {\r\n			// Relative offset from the start time, or an absolute expiry?\r\n			$rel = (strpos($expires, \'+\') === 0) ? true : false;\r\n			if ($rel) {\r\n				$exp = safe_strtotime($expires, $ts);\r\n			} else {\r\n				$exp = safe_strtotime($expires);\r\n			}\r\n			if ($exp !== false) {\r\n				$t_hex .= \'-\' . dechex($exp);\r\n			}\r\n		}\r\n\r\n		// Update/insert the remaining data\r\n		$exists = safe_field(\'page\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\' AND t_hex=\'\".doSlash($t_hex).\"\'\");\r\n		$maxinfo = \'\';\r\n		if ($max) {\r\n			$maxinfo = \", maximum = \'\".doSlash($max).\"\', accesses = \'0\'\";\r\n		}\r\n		if ($exists) {\r\n			safe_update(SMD_AKEYS, \"triggah=\'\".doSlash($trigger).\"\', time=\'\".doSlash($ts).\"\', secret=\'\".doSlash($secret).\"\'\" . $maxinfo, \"page=\'\".doSlash($page).\"\' AND t_hex=\'\".doSlash($t_hex).\"\'\");\r\n		} else {\r\n			safe_insert(SMD_AKEYS, \"page=\'\".doSlash($page).\"\', t_hex=\'\".doSlash($t_hex).\"\', triggah=\'\".doSlash($trigger).\"\', secret=\'\".doSlash($secret).\"\', time=\'\".doSlash($ts).\"\'\" . $maxinfo);\r\n	   }\r\n\r\n		// Tack on max if applicable\r\n		$max_safe = $max;\r\n		$max = ($max) ? \'.\'.$max : \'\';\r\n\r\n		// And any extra\r\n		$extratok = ($extra) ? \'/\'.$extra : \'\';\r\n\r\n		// Create the raw token...\r\n		$token = md5($salt.$secret.$page.$trigger.$t_hex.$max.$extra);\r\n		// ... and insert the salt partway through\r\n		$salty_token = substr($token, 0, $plen) . $salt . substr($token, $plen);\r\n		$tokensep = ($section_mode) ? \'?\' : \'/\';\r\n		$key = $page . (($trigger) ? $tokensep . $trigger : \'\') . \'/\' . $salty_token . \'/\' . $t_hex . $max . $extratok;\r\n\r\n		$smd_akey_info = array(\r\n			\'ak_page\'      => $page,\r\n			\'ak_extra\'     => $extra,\r\n			\'ak_hextime\'   => $t_hex,\r\n			\'ak_issued\'    => $ts,\r\n			\'ak_now\'       => time(),\r\n			\'ak_expires\'   => ($expires) ? $exp : \'\',\r\n			\'ak_trigger\'   => $trigger,\r\n			\'ak_maximum\'   => $max_safe,\r\n			\'ak_separator\' => $tokensep,\r\n			\'ak_key\'       => $key,\r\n		);\r\n\r\n		return parse($thing);\r\n	} else {\r\n		trigger_error(smd_akey_gTxt(\'smd_akey_tbl_not_installed\'), E_USER_NOTICE);\r\n	}\r\n}\r\n\r\n// Protect a page for a given time limit from the moment the\r\n// access token has been generated. Embed this tag at the top\r\n// of the page you want to protect or wrap it around part of a\r\n// page you wish to protect. The unique URL to the resource\r\n// is generated by <txp:smd_access_key />\r\nfunction smd_access_protect($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode, $smd_akey_protected_info, $smd_akey_prefs, $permlink_mode, $plugins;\r\n\r\n	extract(lAtts(array(\r\n		\'trigger\'      => \'smd_akey\',\r\n		\'trigger_mode\' => \'exact\', // exact, begins, ends, contains\r\n		\'site_name\'    => \'1\',\r\n		\'section_mode\' => \'0\',\r\n		\'force\'        => \'0\',\r\n		\'expires\'      => \'3600\', // in seconds\r\n	),$atts));\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		$url = serverSet(\'REQUEST_URI\');\r\n\r\n		if ($site_name && (strpos($url, hu) === false)) {\r\n			$urlparts = parse_url(hu);\r\n			// Can\'t use raw hu since it contains the subdir (as does the REQUEST_URI)\r\n			// so duplicates would occur in the generated URL\r\n			$url = $urlparts[\'scheme\'] . \'://\' . $urlparts[\'host\'] . $url;\r\n		}\r\n\r\n		if ($section_mode == \'1\') {\r\n			$halves = explode(\'?\', $url);\r\n			$half1 = explode(\'/\', $halves[0]);\r\n			$half2 = (isset($halves[1])) ? explode(\'/\', $halves[1]) : array();\r\n\r\n			$parts = array_merge($half1, $half2);\r\n		} else {\r\n			$parts = explode(\'/\', $url);\r\n		}\r\n\r\n		trace_add(\'[smd_access_key URL elements: \' . join(\'|\', $parts).\']\');\r\n\r\n		// Look for one of the triggers in the URL and bomb out if we find it\r\n		$triggers = do_list($trigger);\r\n		$trigger = $triggers[0]; // Initialise to the first value in case no others are found\r\n\r\n		$trigoff = false;\r\n		foreach ($triggers as $trig) {\r\n			switch ($trigger_mode) {\r\n				case \'exact\':\r\n					$trigoff = array_search($trig, $parts);\r\n					$realTrig = $trig;\r\n				break;\r\n				case \'begins\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						if (strpos($part, $trig) === 0) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n				case \'ends\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						$re = \'/.+\'.preg_quote($trig).\'$/i\';\r\n						if (preg_match($re, $part) === 1) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n				case \'contains\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						$re = \'/.*\'.preg_quote($trig).\'.*$/i\';\r\n						if (preg_match($re, $part) === 1) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n			}\r\n			if ($trigoff !== false) {\r\n				// Found it so set the trigger to be the current item and jump out\r\n				$trigoff = ($trigger == \'file_download\') ? $trigoff + 2 : $trigoff;\r\n				$trigger = $realTrig;\r\n				break;\r\n			}\r\n		}\r\n\r\n		trace_add(\'[smd_access_key trigger: \' . $trigger . ($trigoff ? \' found at \' . $trigoff : \'\') . \']\');\r\n\r\n		$ret = false;\r\n		$smd_access_error = $smd_access_errcode = \'\';\r\n		$smd_akey_salt_length = get_pref(\'smd_akey_salt_length\', $smd_akey_prefs[\'smd_akey_salt_length\'][\'default\']);\r\n		$doip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\']);\r\n\r\n		if ($trigoff !== false) {\r\n			$tokidx = $trigoff + 1;\r\n			$timeidx = $trigoff + 2;\r\n			$extraidx = $trigoff + 3;\r\n\r\n			// OK, on a trigger page, so read the token from the URL\r\n			$tok = (isset($parts[$tokidx]) && strlen($parts[$tokidx]) == intval(32 + $smd_akey_salt_length)) ? $parts[$tokidx] : 0;\r\n\r\n			trace_add(\'[smd_access_key token: \' . $tok .\']\');\r\n\r\n			if ($tok) {\r\n				// The token is present, so read the timestamp from the URL\r\n				$t_hex = (isset($parts[$timeidx])) ? $parts[$timeidx] : 0;\r\n\r\n				// Is there a download limit? Extract it if so\r\n				$timeparts = do_list($t_hex, \'.\');\r\n				$max = (isset($timeparts[1])) ? $timeparts[1] : \'0\';\r\n				$maxtok = ($max) ? \'.\'.$max : \'\';\r\n				$t_hex = $timeparts[0];\r\n\r\n				// Any extra info?\r\n				$extras = (isset($parts[$extraidx])) ? array_slice($parts, $extraidx) : array();\r\n\r\n				// Recreate the original page URL, sans /trigger/token/time\r\n				if ($trigger == \'file_download\') {\r\n					$trigoff++;\r\n					$trigger = \'\';\r\n				}\r\n\r\n				// gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs\r\n				// so it requires an exception\r\n				$gbp_pl = (is_array($plugins) && in_array(\'gbp_permanent_links\', $plugins));\r\n\r\n				if ($permlink_mode == \'messy\' && !$gbp_pl) {\r\n					// Don\'t want a slash between site and start of query params\r\n					$page = rtrim(join(\'/\', array_slice($parts, 0, $trigoff-1)), \'/\') . $parts[$trigoff-1];\r\n				} else {\r\n					$page = rtrim(join(\'/\', array_slice($parts, 0, $trigoff)), \'/\');\r\n				}\r\n\r\n				// In case the URL contains non-ascii chars\r\n				$page = rawurldecode($page);\r\n\r\n				trace_add(\'[smd_access_key page | timestamp | max | extras: \' . join(\'|\', array($page, $t_hex, $max, $extras)) . \']\');\r\n\r\n				if ($t_hex) {\r\n					// The timestamp is present. Next, get the secret key\r\n					$secret = false;\r\n					$secring = safe_row(\'*\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\' AND t_hex = \'\".doSlash($t_hex).\"\'\");\r\n\r\n					if ($secring) {\r\n						$secret = $secring[\'secret\'];\r\n\r\n						// Extract the salt from the token\r\n						$plen = strlen($page) % 32;\r\n						$salt = substr($tok, $plen, $smd_akey_salt_length);\r\n						$tok = substr($tok, 0, $plen).substr($tok, $plen+$smd_akey_salt_length);\r\n						$ext = (($extras) ? urldecode(join(\'/\', $extras)) : \'\');\r\n\r\n						// Regenerate the original token...\r\n		 				$check_token = md5($salt.$secret.$page.$trigger.$t_hex.$maxtok.$ext);\r\n\r\n						trace_add(\'[smd_access_key reconstructed token: \' . $check_token . \']\');\r\n\r\n						// ... and compare it to the one in the URL\r\n						if ($check_token == $tok) {\r\n							// Token is valid. Now check if the page has expired\r\n\r\n							// Is there an explicit access key expiry? Extract that if so\r\n							$timeparts = do_list($t_hex, \'-\');\r\n							$t_exp = (isset($timeparts[1])) ? hexdec($timeparts[1]) : \'\';\r\n							$t_beg = $timeparts[0];\r\n\r\n							$t_dec = hexdec($t_beg);\r\n							$now = time();\r\n\r\n							// Has the resource become available yet?\r\n							if ($now < $t_dec) {\r\n								if ($thing == NULL) {\r\n									txp_die(smd_akey_gTxt(\'smd_akey_err_unavailable\'), 410);\r\n								} else {\r\n									$smd_access_error = \'smd_akey_err_unavailable\';\r\n									$smd_access_errcode = 410;\r\n								}\r\n							} else {\r\n								// Has token\'s expiry been reached, or is \'now\' greater than \'then\' (when token generated) + expiry period?\r\n								if ($t_exp) {\r\n									$tester = true;\r\n									$compare_to = $t_exp;\r\n								} else {\r\n									$tester = ($expires != 0);\r\n									$compare_to = $t_dec + $expires;\r\n								}\r\n\r\n								if ($tester && ($now > $compare_to)) {\r\n									if ($thing == NULL) {\r\n										txp_die(smd_akey_gTxt(\'smd_akey_err_expired\'), 410);\r\n									} else {\r\n										$smd_access_error = \'smd_akey_err_expired\';\r\n										$smd_access_errcode = 410;\r\n									}\r\n								} else {\r\n									// Check if the download limit has been exceeded\r\n									$vu_qty = $secring[\'accesses\'];\r\n									if ($max) {\r\n										if ($vu_qty < $max) {\r\n											$ret = true;\r\n										} else {\r\n											if ($thing == NULL) {\r\n												txp_die(smd_akey_gTxt(\'smd_akey_err_limit\'), 410);\r\n											} else {\r\n												$smd_access_error = \'smd_akey_err_limit\';\r\n												$smd_access_errcode = 410;\r\n											}\r\n										}\r\n									} else {\r\n										$ret = true;\r\n									}\r\n									// Increment the access counter\r\n									$vu_qty++;\r\n	\r\n									// Grab the IP and add it to the list of IPs so far\r\n									if ($doip) {\r\n										$ips = do_list($secring[\'ip\'], \' \');\r\n										$ip = remote_addr();\r\n										if (!in_array($ip, $ips)) {\r\n											$ips[] = $ip;\r\n										}\r\n										$ipup = \", ip=\'\".doSlash(trim(join(\' \', $ips))).\"\'\";\r\n									} else {\r\n										$ipup = \'\';\r\n									}\r\n									safe_update(SMD_AKEYS, \"accesses=\'\".doSlash($vu_qty).\"\'\" . $ipup, \"page=\'\".doSlash($page).\"\' AND t_hex = \'\".doSlash($t_hex).\"\'\");\r\n\r\n									// Load up the global array so <txp:smd_access_info> and <txp:if_smd_access_info> work\r\n									$smd_akey_protected_info = array(\r\n										\'page\'     => $secring[\'page\'],\r\n										\'hextime\'  => $secring[\'t_hex\'],\r\n										\'issued\'   => $secring[\'time\'],\r\n										\'now\'      => $now,\r\n										\'expires\'  => $compare_to,\r\n										\'trigger\'  => $secring[\'triggah\'],\r\n										\'maximum\'  => $secring[\'maximum\'],\r\n										\'accesses\' => $vu_qty,\r\n									);\r\n									if ($doip) {\r\n										$smd_akey_protected_info[\'ip\'] = $ip;\r\n									}\r\n									if ($extras) {\r\n										$smd_akey_protected_info[\'extra\'] = urldecode(join(\'/\', $extras));\r\n										foreach($extras as $idx => $extra) {\r\n											$smd_akey_protected_info[\'extra_\'.intval($idx+1)] = urldecode($extra);\r\n										}\r\n									}\r\n								}\r\n							}\r\n\r\n						} else {\r\n							if ($thing == NULL) {\r\n								txp_die(smd_akey_gTxt(\'smd_akey_err_invalid_token\'), 403);\r\n							} else {\r\n								$smd_access_error = \'smd_akey_err_invalid_token\';\r\n								$smd_access_errcode = 403;\r\n							}\r\n						}\r\n\r\n					} else {\r\n						if ($thing == NULL) {\r\n							txp_die(smd_akey_gTxt(\'smd_akey_err_unauthorized\'), 401);\r\n						} else {\r\n							$smd_access_error = \'smd_akey_err_unauthorized\';\r\n							$smd_access_errcode = 401;\r\n						}\r\n					}\r\n\r\n				} else {\r\n					if ($thing == NULL) {\r\n						txp_die(smd_akey_gTxt(\'smd_akey_err_missing_timestamp\'), 403);\r\n					} else {\r\n						$smd_access_error = \'smd_akey_err_missing_timestamp\';\r\n						$smd_access_errcode = 403;\r\n					}\r\n				}\r\n\r\n			} else {\r\n				if ($thing == NULL) {\r\n					txp_die(smd_akey_gTxt(\'smd_akey_err_bad_token\'), 403);\r\n				} else {\r\n					$smd_access_error = \'smd_akey_err_bad_token\';\r\n					$smd_access_errcode = 403;\r\n				}\r\n			}\r\n		} else {\r\n			// If we always want to forbid access to this page regardless if the trigger exists\r\n			if ($force) {\r\n				if ($thing == NULL) {\r\n					txp_die(smd_akey_gTxt(\'smd_akey_err_forbidden\'), 401);\r\n				} else {\r\n					$smd_access_error = \'smd_akey_err_forbidden\';\r\n					$smd_access_errcode = 401;\r\n				}\r\n			} else {\r\n				$ret = true;\r\n			}\r\n		}\r\n\r\n		if ($smd_access_error || $smd_access_errcode) {\r\n			trace_add(\'[smd_access_key error state: \' . $smd_access_errcode . \'|\' . $smd_access_error . \']\');\r\n		}\r\n\r\n		// If we reach this point it\'s because we\'re using a container\r\n		return parse(EvalElse($thing, $ret));\r\n	} else {\r\n		trigger_error(smd_akey_gTxt(\'smd_akey_tbl_not_installed\'), E_USER_NOTICE);\r\n	}\r\n}\r\n\r\n// Called just before a download is initiated\r\nfunction smd_access_protect_download($evt, $stp) {\r\n	global $smd_akey_prefs, $id, $file_error;\r\n\r\n	if (smd_akey_table_exist(1) && !isset($file_error)) {\r\n		$fileid = intval($id);\r\n\r\n		// In case the page was called with a bogus filename, get the \"true\" filename\r\n		// from the database and make up the valid URL\r\n		$real_file = safe_field(\"filename\", \"txp_file\", \"id=\".doSlash($fileid));\r\n		$page = filedownloadurl($fileid, $real_file);\r\n		$secring = safe_field(\'page\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\'\");\r\n\r\n		// Only want to protect pages that we\'ve generated tokens for\r\n		if ($secring) {\r\n			// Pass in a default expiry from the pref, but it can be overridden by the key\'s expiry\r\n			return smd_access_protect(\r\n				array(\r\n					\'trigger\' => \'file_download\',\r\n					\'force\'   => \'1\',\r\n					\'expires\' => get_pref(\'smd_akey_file_download_expires\', $smd_akey_prefs[\'smd_akey_file_download_expires\'][\'default\']),\r\n				)\r\n			);\r\n		}\r\n	}\r\n	// remote download not done - leave to Txp to handle error or \"local\" file download\r\n	return;\r\n}\r\n\r\n// Conditional tag for checking error status from smd_access_protect\r\nfunction smd_if_access_error($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'   => \'\',\r\n		\'code\'   => \'\',\r\n	),$atts));\r\n\r\n	$err = array();\r\n	$codes = do_list($code);\r\n	$types = do_list($type);\r\n\r\n	if ($smd_access_error) {\r\n		if ($code && $type) {\r\n			$err[\'code\'] = (in_array($smd_access_errcode, $codes)) ? true : false;\r\n			$err[\'msg\'] = (in_array($smd_access_error, $types)) ? true : false;\r\n		} else if ($code) {\r\n			$err[\'code\'] = (in_array($smd_access_errcode, $codes)) ? true : false;\r\n		} else if ($type) {\r\n			$err[\'msg\'] = (in_array($smd_access_error, $types)) ? true : false;\r\n		} else {\r\n			$err[\'msg\'] = true;\r\n		}\r\n	}\r\n\r\n	$out = in_array(false, $err) ? false : true; // AND logic\r\n\r\n	return parse(EvalElse($thing, $out));\r\n}\r\n\r\n// Display access error information\r\nfunction smd_access_error($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'    => \'message\',\r\n		\'message\' => \'\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'break\'      => \'\',\r\n		\'breakclass\' => \'\',\r\n	),$atts));\r\n\r\n	$out = array();\r\n	$items = do_list($item);\r\n\r\n	if ($smd_access_errcode && in_array(\'code\', $items)) {\r\n		$out[] = $smd_access_errcode;\r\n	}\r\n\r\n	if ($smd_access_error && in_array(\'message\', $items)) {\r\n		$out[] = ($message) ? $message : smd_akey_gTxt($smd_access_error);\r\n	}\r\n\r\n	if ($out) {\r\n		return doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n// Display access information for custom formatted messages\r\nfunction smd_access_info($atts, $thing=NULL) {\r\n	global $smd_akey_protected_info, $smd_akey_info;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'       => \'page\',\r\n		\'escape\'     => \'html\',\r\n		\'format\'     => \'%Y-%m-%d %H:%M:%S\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'break\'      => \'\',\r\n		\'breakclass\' => \'\',\r\n	),$atts));\r\n\r\n	$out = array();\r\n	$items = do_list($item);\r\n\r\n	foreach ($items as $idx) {\r\n		$ak_idx = \'ak_\'.$idx;\r\n		if ($smd_akey_protected_info && array_key_exists($idx, $smd_akey_protected_info)) {\r\n			$val = ($escape == \'html\') ? htmlspecialchars($smd_akey_protected_info[$idx]) : $smd_akey_protected_info[$idx];\r\n			if (in_array($idx, array(\'time\', \'now\', \'expires\')) && $format) {\r\n				$val = safe_strftime($format, $val);\r\n			}\r\n			$out[] = $val;\r\n		}\r\n		if ($smd_akey_info && array_key_exists($ak_idx, $smd_akey_info)) {\r\n			$val = ($escape == \'html\') ? htmlspecialchars($smd_akey_info[$ak_idx]) : $smd_akey_info[$ak_idx];\r\n			if (in_array($idx, array(\'time\', \'now\', \'expires\')) && $format) {\r\n				$val = safe_strftime($format, $val);\r\n			}\r\n			$out[] = $val;\r\n		}\r\n	}\r\n\r\n	if ($out) {\r\n		return doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n/**\r\n * smd_akey_gTxt	Convert strings for i18n purposes\r\n *\r\n * @param	string	$what [+] [private] [static]		Name of the item from the array to retrieve\r\n * @param	array	$atts [+]	(Default: Array)	Array of \'search\' => \'replacements\' to make\r\n */\r\nfunction smd_akey_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'en-gb\' => array(\r\n			\'smd_akey_accesses\' => \'Access attempts\',\r\n			\'smd_akey_btn_new\' => \'New key\',\r\n			\'smd_akey_btn_pref\' => \'Prefs\',\r\n			\'smd_akey_deleted\' => \'Keys deleted: {deleted}\',\r\n			\'smd_akey_err_bad_token\' => \'Missing or mangled access key\',\r\n			\'smd_akey_err_expired\' => \'Access expired\',\r\n			\'smd_akey_err_forbidden\' => \'Forbidden access\',\r\n			\'smd_akey_err_invalid_token\' => \'Invalid access key\',\r\n			\'smd_akey_err_missing_timestamp\' => \'Missing timestamp\',\r\n			\'smd_akey_err_unavailable\' => \'Not available\',\r\n			\'smd_akey_err_unauthorized\' => \'Unauthorized access\',\r\n			\'smd_akey_err_limit\' => \'Access limit reached\',\r\n			\'smd_akey_file_download_expires\' => \'File download expiry time (seconds)\',\r\n			\'smd_akey_generated\' => \'Access key: {key}\',\r\n			\'smd_akey_log_ip\' => \'Log IP addresses\',\r\n			\'smd_akey_max\' => \'Maximum\',\r\n			\'smd_akey_need_page\' => \'You need to enter a page URL\',\r\n			\'smd_akey_page\' => \'Page\',\r\n			\'smd_akey_pref_legend\' => \'Access key preferences\',\r\n			\'smd_akey_prefs_saved\' => \'Preferences saved\',\r\n			\'smd_akey_prefs_some_tbl\' => \'Not all table info available.\',\r\n			\'smd_akey_prefs_some_explain\' => \'This is either a new installation or a different version\'.br.\'of the plugin to one you had before.\',\r\n			\'smd_akey_prefs_some_opts\' => \'Click \"Install table\" to add or update the table\'.br.\'leaving all existing data untouched.\',\r\n			\'smd_akey_prefs_some_tbl\' => \'Not all table info available.\',\r\n			\'smd_akey_salt_length\' => \'Salt length (characters)\',\r\n			\'smd_akey_tab_name\' => \'Access keys\',\r\n			\'smd_akey_tbl_install_lbl\' => \'Install table\',\r\n			\'smd_akey_tbl_installed\' => \'Table installed\',\r\n			\'smd_akey_tbl_not_installed\' => \'Table not installed\',\r\n			\'smd_akey_tbl_removed\' => \'Table removed\',\r\n			\'smd_akey_tbl_not_removed\' => \'Table not removed\',\r\n			\'smd_akey_time\' => \'Issued\',\r\n			\'smd_akey_trigger\' => \'Trigger\',\r\n		),\r\n	);\r\n\r\n	$thislang = get_pref(\'language\', \'en-gb\');\r\n	$thislang = (isset($lang[$thislang][$what])) ? $thislang : \'en-gb\';\r\n	return strtr($lang[$thislang][$what], $atts);\r\n}', '/**\r\n * smd_access_keys\r\n *\r\n * A Textpattern CMS plugin for secure tokenized access to resources:\r\n *  -> Time-based or access attempt limits\r\n *  -> Untamperable URL-based keys\r\n *  -> Optional IP logging\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n// TODO: Add shortcut to send a newly-generated admin-side key to a user? (dropdown of users + e-mail template in prefs?)\r\n//       Add auto-deletion of expired keys pref:\r\n//        -> File download keys can be deleted on any key access (expiry window is known via prefs)\r\n//        -> Other key accesses can only be deleted when that key is used\r\n//        -> Configurable grace period after expiry, before deletion\r\n//       Obfusctaed URLs (see http://radioartnet.net/11/2011/07/05/samuel-beckett-words-and-music/)\r\n//       Query an access key and separate it into its component parts for convenient testing\r\nif(@txpinterface == \'admin\') {\r\n	global $smd_akey_event, $smd_akey_styles;\r\n	$smd_akey_event = \'smd_akey\';\r\n\r\n	$smd_akey_styles = array(\r\n		\'list\' =>\r\n         \'.smd_hidden { display:none; }\',\r\n	);\r\n\r\n	add_privs($smd_akey_event, \'1\');\r\n	add_privs(\'plugin_prefs.smd_access_keys\', \'1\');\r\n	register_tab(\'extensions\', $smd_akey_event, smd_akey_gTxt(\'smd_akey_tab_name\'));\r\n	register_callback(\'smd_akey_dispatcher\', $smd_akey_event);\r\n	register_callback(\'smd_akey_welcome\', \'plugin_lifecycle.smd_access_keys\');\r\n	register_callback(\'smd_akey_prefs\', \'plugin_prefs.smd_access_keys\');\r\n}\r\n\r\nglobal $smd_akey_prefs;\r\n$smd_akey_prefs = array(\r\n	\'smd_akey_file_download_expires\' => array(\r\n		\'html\'     => \'text_input\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 10,\r\n		\'default\'  => \'3600\',\r\n	),\r\n	\'smd_akey_salt_length\' => array(\r\n		\'html\'     => \'text_input\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 20,\r\n		\'default\'  => \'8\',\r\n	),\r\n	\'smd_akey_log_ip\' => array(\r\n		\'html\'     => \'yesnoradio\',\r\n		\'type\'     => PREF_HIDDEN,\r\n		\'position\' => 30,\r\n		\'default\'  => \'0\',\r\n	),\r\n);\r\n\r\nif (!defined(\'SMD_AKEYS\')) define(\"SMD_AKEYS\", \'smd_akeys\');\r\n\r\nregister_callback(\'smd_access_protect_download\', \'file_download\');\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n// Jump off point for event/steps\r\nfunction smd_akey_dispatcher($evt, $stp) {\r\n	if(!$stp or !in_array($stp, array(\r\n			\'smd_akey_table_install\',\r\n			\'smd_akey_table_remove\',\r\n			\'smd_akey_create\',\r\n			\'smd_akey_prefs\',\r\n			\'smd_akey_prefsave\',\r\n			\'smd_akey_multi_edit\',\r\n			\'smd_akey_change_pageby\',\r\n		))) {\r\n		smd_akey(\'\');\r\n	} else $stp();\r\n}\r\n\r\n// Bootstrap when installed/deleted\r\nfunction smd_akey_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_akey_table_install(0);\r\n			$msg = \'Restrict your Txp world :-)\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_akey_table_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// Main admin interface\r\nfunction smd_akey($msg=\'\') {\r\n	global $smd_akey_event, $smd_akey_list_pageby, $smd_akey_styles, $logging, $smd_akey_prefs;\r\n\r\n	pagetop(smd_akey_gTxt(\'smd_akey_tab_name\'), $msg);\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n		if ($sort === \'\') $sort = get_pref(\'smd_akey_sort_column\', \'time\');\r\n		if ($dir === \'\') $dir = get_pref(\'smd_akey_sort_dir\', \'desc\');\r\n		$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\r\n\r\n		switch ($sort) {\r\n			case \'page\':\r\n				$sort_sql = \'page \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'triggah\':\r\n				$sort_sql = \'triggah \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'maximum\':\r\n				$sort_sql = \'maximum \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'accesses\':\r\n				$sort_sql = \'accesses \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			case \'ip\':\r\n				$sort_sql = \'ip \'.$dir.\', time desc\';\r\n			break;\r\n\r\n			default:\r\n				$sort = \'time\';\r\n				$sort_sql = \'time \'.$dir;\r\n			break;\r\n		}\r\n\r\n		set_pref(\'smd_akey_sort_column\', $sort, \'smd_akey\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n		set_pref(\'smd_akey_sort_dir\', $dir, \'smd_akey\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n\r\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n		$criteria = 1;\r\n\r\n		if ($search_method and $crit) {\r\n			$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\r\n			$critsql = array(\r\n				\'page\'     => \"page like \'%$crit_escaped%\'\",\r\n				\'triggah\'  => \"triggah like \'%$crit_escaped%\'\",\r\n				\'maximum\'  => \"maximum = \'$crit_escaped\'\",\r\n				\'accesses\' => \"accesses = \'$crit_escaped\'\",\r\n				\'ip\'       => \"ip like \'%$crit_escaped%\'\",\r\n			);\r\n\r\n			if (array_key_exists($search_method, $critsql)) {\r\n				$criteria = $critsql[$search_method];\r\n				$limit = 500;\r\n			} else {\r\n				$search_method = \'\';\r\n				$crit = \'\';\r\n			}\r\n		} else {\r\n			$search_method = \'\';\r\n			$crit = \'\';\r\n		}\r\n\r\n		$total = safe_count(SMD_AKEYS, \"$criteria\");\r\n\r\n		echo \'<div id=\"\'.$smd_akey_event.\'_control\" class=\"txp-control-panel\">\';\r\n\r\n		if ($total < 1) {\r\n			if ($criteria != 1) {\r\n				echo n.smd_akey_search_form($crit, $search_method).\r\n					n.graf(gTxt(\'no_results_found\'), \' class=\"indicator\"\').\'</div>\';\r\n					return;\r\n			}\r\n		}\r\n\r\n		$limit = max($smd_akey_list_pageby, 15);\r\n\r\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\r\n\r\n		echo n.smd_akey_search_form($crit, $search_method).\'</div>\';\r\n\r\n		// Retrieve the secret keyring table entries\r\n		$secring = safe_rows(\'*\', SMD_AKEYS, \"$criteria order by $sort_sql limit $offset, $limit\");\r\n\r\n		// Set up the buttons and column info\r\n		$newbtn = \'<a class=\"navlink\" href=\"#\" onclick=\"return smd_akey_togglenew();\">\'.smd_akey_gTxt(\'smd_akey_btn_new\').\'</a>\';\r\n		$prefbtn = \'<a class=\"navlink\" href=\"?event=\'.$smd_akey_event.a.\'step=smd_akey_prefs\">\'.smd_akey_gTxt(\'smd_akey_btn_pref\').\'</a>\';\r\n		$showip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\'], 1);\r\n\r\n		echo <<<EOC\r\n<script type=\"text/javascript\">\r\nfunction smd_akey_togglenew() {\r\n	box = jQuery(\"#smd_akey_create\");\r\n	if (box.css(\"display\") == \"none\") {\r\n		box.show();\r\n	} else {\r\n		box.hide();\r\n	}\r\n	jQuery(\"input.smd_focus\").focus();\r\n	return false;\r\n}\r\njQuery(function() {\r\n	jQuery(\"#smd_akey_add\").click(function () {\r\n		jQuery(\"#smd_akey_step\").val(\'smd_akey_create\');\r\n		jQuery(\"#smd_akey_form\").removeAttr(\'onsubmit\').submit();\r\n	});\r\n});\r\n</script>\r\nEOC;\r\n		// Inject styles\r\n		echo \'<style type=\"text/css\">\' . $smd_akey_styles[\'list\'] . \'</style>\';\r\n\r\n		// Access key list\r\n		echo n.\'<div id=\"\'.$smd_akey_event.\'_container\" class=\"txp-container txp-list\">\';\r\n		echo \'<form name=\"longform\" id=\"smd_akey_form\" action=\"index.php\" method=\"post\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\';\r\n		echo startTable(\'list\');\r\n		echo n.\'<thead>\'\r\n			.n.tr(tda($newbtn . sp . $prefbtn, \' class=\"noline\"\'))\r\n			.n.tr(\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_page\'), \'page\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'page\' == $sort) ? \"$dir \" : \'\').\'page\').\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_trigger\'), \'triggah\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'triggah\' == $sort) ? \"$dir \" : \'\')).\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_time\'), \'time\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'time\' == $sort) ? \"$dir \" : \'\').\'date time\').\r\n				n.hCell(gTxt(\'expires\'), \'expires\', \' class=\"date time\"\').\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_max\'), \'maximum\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'maximum\' == $sort) ? \"$dir \" : \'\')).\r\n				n.column_head(smd_akey_gTxt(\'smd_akey_accesses\'), \'accesses\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'accesses\' == $sort) ? \"$dir \" : \'\')).\r\n				(($showip) ? n.column_head(\'IP\', \'ip\', $smd_akey_event, true, $switch_dir, $crit, $search_method, ((\'ip\' == $sort) ? \"$dir \" : \'\').\'ip\') : \'\').\r\n				n.hCell(\'\', \'\', \' class=\"multi-edit\"\')\r\n			).\r\n			n.\'</thead>\';\r\n\r\n		$multiOpts = array(\'smd_akey_delete\' => gTxt(\'delete\'));\r\n\r\n		echo \'<tfoot>\' . tr(tda(\r\n				select_buttons()\r\n				.n.selectInput(\'smd_akey_multi_edit\', $multiOpts, \'\', true)\r\n				.n.eInput($smd_akey_event)\r\n				.n.fInput(\'submit\', \'\', gTxt(\'go\'), \'smallerbox\')\r\n			,\' class=\"multi-edit\" colspan=\"\' . (($showip) ? 7 : 6) . \'\" style=\"text-align: right; border: none;\"\'));\r\n		echo \'</tfoot>\';\r\n		echo \'<tbody>\';\r\n\r\n		// New access key row\r\n		echo \'<tr id=\"smd_akey_create\" class=\"smd_hidden\">\';\r\n		echo td(fInput(\'hidden\', \'step\', \'smd_akey_multi_edit\', \'\', \'\', \'\', \'\', \'\', \'smd_akey_step\').fInput(\'text\', \'smd_akey_newpage\', \'\', \'smd_focus\', \'\', \'\', \'60\'))\r\n			.td(fInput(\'text\', \'smd_akey_triggah\', \'\'))\r\n			.td(fInput(\'text\', \'smd_akey_time\', safe_strftime(\'%Y-%m-%d %H:%M:%S\'), \'\', \'\', \'\', \'25\'))\r\n			.td(fInput(\'text\', \'smd_akey_expires\', \'\', \'\', \'\', \'\', \'25\'))\r\n			.td(fInput(\'text\', \'smd_akey_maximum\', \'\', \'\', \'\', \'\', \'5\'))\r\n			.td(\'&nbsp;\')\r\n			. (($showip) ? td(\'&nbsp;\') : \'\')\r\n			.td(fInput(\'submit\', \'smd_akey_add\', gTxt(\'add\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'smd_akey_add\'));\r\n		echo \'</tr>\';\r\n\r\n		// Remaining access keys\r\n		foreach ($secring as $secidx => $data) {\r\n			if ($showip) {\r\n				$ips = do_list($data[\'ip\'], \' \');\r\n				$iplist = array();\r\n				foreach ($ips as $ip) {\r\n					$iplist[] = ($logging == \'none\') ? $ip : eLink(\'log\', \'log_list\', \'search_method\', \'ip\', $ip, \'crit\', $ip);\r\n				}\r\n			}\r\n			$dkey = $data[\'page\'].\'|\'.$data[\'t_hex\'];\r\n			$timeparts = do_list($data[\'t_hex\'], \'-\');\r\n			$expiry = (isset($timeparts[1])) ? hexdec($timeparts[1]) : \'\';\r\n			echo tr(\r\n				td(\'<a href=\"\'.$data[\'page\'].\'\">\'.$data[\'page\'].\'</a>\', \'\', \'page\')\r\n				. td($data[\'triggah\'])\r\n				. td(safe_strftime(\'%Y-%m-%d %H:%M:%S\', $data[\'time\']), 85, \'date time\')\r\n				. td( (($expiry) ? safe_strftime(\'%Y-%m-%d %H:%M:%S\', $expiry) : \'-\'), 85, \'date time\')\r\n				. td($data[\'maximum\'])\r\n				. td($data[\'accesses\'])\r\n				. ( ($showip) ? td( trim(join(\' \', $iplist)), 20, \'ip\' ) : \'\' )\r\n				. td( fInput(\'checkbox\', \'selected[]\', $dkey, \'checkbox\'), \'\', \'multi-edit\')\r\n			);\r\n		}\r\n		echo \'</tbody>\';\r\n		echo endTable();\r\n		echo \'</form>\';\r\n\r\n		echo \'<div id=\"\'.$smd_akey_event.\'_navigation\" class=\"txp-navigation\">\'.\r\n			n.nav_form($smd_akey_event, $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit).\r\n\r\n			n.pageby_form($smd_akey_event, $smd_akey_list_pageby).\r\n			n.\'</div>\'.n.\'</div>\';\r\n\r\n	} else {\r\n		// Table not installed\r\n		$btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_akey_event.a.\'step=smd_akey_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', smd_akey_gTxt(\'smd_akey_tbl_install_lbl\'), \'smallerbox\').\'</form>\';\r\n		$btnStyle = \' style=\"border:0;height:25px\"\';\r\n		echo startTable(\'list\');\r\n		echo tr(tda(strong(smd_akey_gTxt(\'smd_akey_prefs_some_tbl\')).br.br\r\n				.smd_akey_gTxt(\'smd_akey_prefs_some_explain\').br.br\r\n				.smd_akey_gTxt(\'smd_akey_prefs_some_opts\'), \' colspan=\"2\"\')\r\n		);\r\n		echo tr(tda($btnInstall, $btnStyle));\r\n		echo endTable();\r\n	}\r\n}\r\n\r\n// Change and store qty-per-page value\r\nfunction smd_akey_change_pageby() {\r\n	event_change_pageby(\'smd_akey\');\r\n	smd_akey();\r\n}\r\n\r\n// The search dropdown list\r\nfunction smd_akey_search_form($crit, $method) {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	$doip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\'], 1);\r\n\r\n	$methods =	array(\r\n		\'page\'     => smd_akey_gTxt(\'smd_akey_page\'),\r\n		\'triggah\'  => smd_akey_gTxt(\'smd_akey_trigger\'),\r\n		\'maximum\'  => smd_akey_gTxt(\'smd_akey_max\'),\r\n		\'accesses\' => smd_akey_gTxt(\'smd_akey_accesses\'),\r\n	);\r\n\r\n	if ($doip) {\r\n		$methods[\'ip\'] = gTxt(\'IP\');\r\n	}\r\n\r\n	return search_form($smd_akey_event, \'\', $crit, $methods, $method, \'page\');\r\n}\r\n\r\n// Create a key from the admin side\'s \'New key\' button\r\nfunction smd_akey_create() {\r\n	extract(gpsa(array(\'smd_akey_newpage\', \'smd_akey_triggah\', \'smd_akey_time\', \'smd_akey_expires\', \'smd_akey_maximum\')));\r\n\r\n	if ($smd_akey_newpage) {\r\n		// Just call the public tag with the relevant options\r\n		$key = smd_access_key(\r\n			array(\r\n				\'url\'     => $smd_akey_newpage,\r\n				\'trigger\' => $smd_akey_triggah,\r\n				\'start\'   => $smd_akey_time,\r\n				\'expires\' => $smd_akey_expires,\r\n				\'max\'     => $smd_akey_maximum,\r\n			)\r\n		);\r\n		$msg = smd_akey_gTxt(\'smd_akey_generated\', array(\'{key}\' => $key));\r\n	} else {\r\n		$msg = array(smd_akey_gTxt(\'smd_akey_need_page\'), E_ERROR);\r\n	}\r\n	smd_akey($msg);\r\n}\r\n\r\n// Handle submission of the multi-edit dropdown options\r\nfunction smd_akey_multi_edit() {\r\n	$selected = gps(\'selected\');\r\n	$operation = gps(\'smd_akey_multi_edit\');\r\n	$del = 0;\r\n	$msg = \'\';\r\n\r\n	switch ($operation) {\r\n		case \'smd_akey_delete\':\r\n			if ($selected) {\r\n				foreach ($selected as $sel) {\r\n					$parts = explode(\'|\', $sel);\r\n					$ret = safe_delete(SMD_AKEYS, \"page = \'\" . $parts[0] . \"\' AND t_hex = \'\" . $parts[1] . \"\'\");\r\n					$del = ($ret) ? $del+1 : $del;\r\n				}\r\n				$msg = smd_akey_gTxt(\'smd_akey_deleted\', array(\'{deleted}\' => $del));\r\n			}\r\n		break;\r\n	}\r\n\r\n	smd_akey($msg);\r\n}\r\n\r\n// Display the prefs\r\nfunction smd_akey_prefs() {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	pagetop(smd_akey_gTxt(\'smd_akey_pref_legend\'));\r\n\r\n	$out = array();\r\n	$out[] = \'<form name=\"smd_akey_prefs\" id=\"smd_akey_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_akey_event).sInput(\'smd_akey_prefsave\');\r\n	$out[] = startTable(\'list\');\r\n	$out[] = tr(tdcs(strong(smd_akey_gTxt(\'smd_akey_pref_legend\')), 2));\r\n	foreach ($smd_akey_prefs as $idx => $prefobj) {\r\n		$subout = array();\r\n		$subout[] = tda(\'<label for=\"\'.$idx.\'\">\'.smd_akey_gTxt($idx).\'</label>\', \' class=\"noline\" style=\"text-align: right; vertical-align: middle;\"\');\r\n		$val = get_pref($idx, $prefobj[\'default\']);\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = fInputCell($idx, $val, \'\', \'\', \'\', $idx);\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = tda(yesnoRadio($idx, $val),\' class=\"noline\"\');\r\n			break;\r\n		}\r\n		$out[] = tr(join(n ,$subout));\r\n	}\r\n\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\'), \' class=\"noline\"\'));\r\n	$out[] = endTable();\r\n	$out[] = \'</form>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// Save the prefs\r\nfunction smd_akey_prefsave() {\r\n	global $smd_akey_event, $smd_akey_prefs;\r\n\r\n	foreach ($smd_akey_prefs as $idx => $prefobj) {\r\n		$val = ps($idx);\r\n		set_pref($idx, $val, $smd_akey_event, $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n	}\r\n\r\n	$msg = smd_akey_gTxt(\'smd_akey_prefs_saved\');\r\n\r\n	smd_akey($msg);\r\n}\r\n\r\n// Add akey table if not already installed\r\nfunction smd_akey_table_install($showpane=\'1\') {\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n\r\n	// Use \'triggah\' and \'maximum\' because \'trigger\' and \'max\' are reserved words.\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_AKEYS.\"` (\r\n		`page` varchar(255) NOT NULL default \'\',\r\n		`t_hex` varchar(17) NOT NULL default \'\',\r\n		`time` int(14) NOT NULL default 0,\r\n		`secret` varchar(255) NOT NULL default \'\',\r\n		`triggah` varchar(255) NULL default \'\',\r\n		`maximum` int(11) NULL default 0,\r\n		`accesses` int(11) NULL default 0,\r\n		`ip` text NOT NULL default \'\',\r\n		PRIMARY KEY (`page`,`t_hex`)\r\n	) ENGINE=MyISAM\";\r\n\r\n	if(gps(\'debug\')) {\r\n		dmp($sql);\r\n	}\r\n	foreach ($sql as $qry) {\r\n		$ret = safe_query($qry);\r\n		if ($ret===false) {\r\n			$GLOBALS[\'txp_err_count\']++;\r\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n			echo \"<!--\\n $qry \\n-->\\n\";\r\n		}\r\n	}\r\n\r\n	// Spit out results\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		if ($showpane) {\r\n			$msg = smd_akey_gTxt(\'smd_akey_tbl_installed\');\r\n			smd_akey($msg);\r\n		}\r\n	} else {\r\n		if ($showpane) {\r\n			$msg = smd_akey_gTxt(\'smd_akey_tbl_not_installed\');\r\n			smd_akey($msg);\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_akey_table_remove() {\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_akey_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_AKEYS. \"; \";\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$msg = smd_akey_gTxt(\'smd_akey_tbl_removed\');\r\n	} else {\r\n		$msg = smd_akey_gTxt(\'smd_akey_tbl_not_removed\');\r\n		smd_akey($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_akey_table_exist($type=\'\') {\r\n	global $plugins_ver, $DB;\r\n\r\n	// Upgrade check\r\n	$ver = get_pref(\'smd_akey_installed_version\', \'\');\r\n	if (!$ver || $plugins_ver[\'smd_access_keys\'] != $ver) {\r\n		// Increase the size of the t_hex field to allow for expiry times\r\n		$ret = @safe_field(\"CHARACTER_MAXIMUM_LENGTH\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_AKEYS.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'t_hex\'\");\r\n		if ($ret != \'17\') {\r\n			safe_alter(SMD_AKEYS, \"CHANGE `t_hex` `t_hex` VARCHAR( 17 ) NOT NULL DEFAULT \'\'\");\r\n			set_pref(\'smd_akey_installed_version\', $plugins_ver[\'smd_access_keys\'], \'smd_akey\', PREF_HIDDEN, \'\', 0);\r\n		}\r\n	}\r\n\r\n	if ($type == \'1\') {\r\n		$tbls = array(SMD_AKEYS => 8);\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			if (count(@safe_show(\'columns\', $tbl)) == $cols) {\r\n				$out--;\r\n			}\r\n		}\r\n		return ($out===0) ? 1 : 0;\r\n	} else {\r\n		return(@safe_show(\'columns\', SMD_AKEYS));\r\n	}\r\n}\r\n\r\n//**********************\r\n// PUBLIC SIDE INTERFACE\r\n//**********************\r\nfunction smd_access_key($atts, $thing=NULL) {\r\n	global $smd_akey_prefs, $smd_akey_info;\r\n\r\n	// In case this tag is called from the admin side - needs parse()\r\n	include_once txpath.\'/publish.php\';\r\n\r\n	extract(lAtts(array(\r\n		\'secret\'       => \'\',\r\n		\'url\'          => \'\',\r\n		\'site_name\'    => \'1\',\r\n		\'section_mode\' => \'0\',\r\n		\'start\'        => \'\',\r\n		\'expires\'      => \'\',\r\n		\'trigger\'      => \'smd_akey\',\r\n		\'max\'          => \'\',\r\n		\'extra\'        => \'\',\r\n		\'form\'         => \'\',\r\n	),$atts));\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		$thing = (empty($form)) ? $thing : fetch_form($form);\r\n		$thing = (empty($thing)) ? \'<txp:smd_access_info item=\"key\" />\' : $thing;\r\n\r\n		$trigger = trim($trigger);\r\n		$trigger = ($trigger == \'file_download\') ? \'\' : $trigger;\r\n\r\n		$smd_akey_salt_length = get_pref(\'smd_akey_salt_length\', $smd_akey_prefs[\'smd_akey_salt_length\'][\'default\']);\r\n\r\n		// Without a URL, assume current page\r\n		$page = rtrim( (($url) ? $url : serverSet(\'REQUEST_URI\')), \'/\');\r\n\r\n		if ($site_name && (strpos($page, \'http\') !== 0)) {\r\n			// Can\'t use raw hu since it contains the subdir (as does the REQUEST_URI)\r\n			// so duplicate portions would occur in the generated URL\r\n			$urlparts = parse_url(hu);\r\n			$page = $urlparts[\'scheme\'] . \'://\' . $urlparts[\'host\'] . $page;\r\n		}\r\n\r\n		if (!$secret) {\r\n			$secret = uniqid(\'\', true);\r\n		}\r\n\r\n		$salt = substr(md5(uniqid(rand(), true)), 0, $smd_akey_salt_length);\r\n		$plen = strlen($page) % 32; // Because 32 is the size of an md5 string and we don\'t want to fall off the end\r\n\r\n		// Generate a timestamp. The clock starts ticking from this moment\r\n		$ts = ($start) ? safe_strtotime($start) : time();\r\n		$ts = ($ts === false) ? time() : $ts;\r\n		$t_hex = dechex($ts);\r\n\r\n		// Any expiry to add?\r\n		if ($expires) {\r\n			// Relative offset from the start time, or an absolute expiry?\r\n			$rel = (strpos($expires, \'+\') === 0) ? true : false;\r\n			if ($rel) {\r\n				$exp = safe_strtotime($expires, $ts);\r\n			} else {\r\n				$exp = safe_strtotime($expires);\r\n			}\r\n			if ($exp !== false) {\r\n				$t_hex .= \'-\' . dechex($exp);\r\n			}\r\n		}\r\n\r\n		// Update/insert the remaining data\r\n		$exists = safe_field(\'page\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\' AND t_hex=\'\".doSlash($t_hex).\"\'\");\r\n		$maxinfo = \'\';\r\n		if ($max) {\r\n			$maxinfo = \", maximum = \'\".doSlash($max).\"\', accesses = \'0\'\";\r\n		}\r\n		if ($exists) {\r\n			safe_update(SMD_AKEYS, \"triggah=\'\".doSlash($trigger).\"\', time=\'\".doSlash($ts).\"\', secret=\'\".doSlash($secret).\"\'\" . $maxinfo, \"page=\'\".doSlash($page).\"\' AND t_hex=\'\".doSlash($t_hex).\"\'\");\r\n		} else {\r\n			safe_insert(SMD_AKEYS, \"page=\'\".doSlash($page).\"\', t_hex=\'\".doSlash($t_hex).\"\', triggah=\'\".doSlash($trigger).\"\', secret=\'\".doSlash($secret).\"\', time=\'\".doSlash($ts).\"\'\" . $maxinfo);\r\n	   }\r\n\r\n		// Tack on max if applicable\r\n		$max_safe = $max;\r\n		$max = ($max) ? \'.\'.$max : \'\';\r\n\r\n		// And any extra\r\n		$extratok = ($extra) ? \'/\'.$extra : \'\';\r\n\r\n		// Create the raw token...\r\n		$token = md5($salt.$secret.$page.$trigger.$t_hex.$max.$extra);\r\n		// ... and insert the salt partway through\r\n		$salty_token = substr($token, 0, $plen) . $salt . substr($token, $plen);\r\n		$tokensep = ($section_mode) ? \'?\' : \'/\';\r\n		$key = $page . (($trigger) ? $tokensep . $trigger : \'\') . \'/\' . $salty_token . \'/\' . $t_hex . $max . $extratok;\r\n\r\n		$smd_akey_info = array(\r\n			\'ak_page\'      => $page,\r\n			\'ak_extra\'     => $extra,\r\n			\'ak_hextime\'   => $t_hex,\r\n			\'ak_issued\'    => $ts,\r\n			\'ak_now\'       => time(),\r\n			\'ak_expires\'   => ($expires) ? $exp : \'\',\r\n			\'ak_trigger\'   => $trigger,\r\n			\'ak_maximum\'   => $max_safe,\r\n			\'ak_separator\' => $tokensep,\r\n			\'ak_key\'       => $key,\r\n		);\r\n\r\n		return parse($thing);\r\n	} else {\r\n		trigger_error(smd_akey_gTxt(\'smd_akey_tbl_not_installed\'), E_USER_NOTICE);\r\n	}\r\n}\r\n\r\n// Protect a page for a given time limit from the moment the\r\n// access token has been generated. Embed this tag at the top\r\n// of the page you want to protect or wrap it around part of a\r\n// page you wish to protect. The unique URL to the resource\r\n// is generated by <txp:smd_access_key />\r\nfunction smd_access_protect($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode, $smd_akey_protected_info, $smd_akey_prefs, $permlink_mode, $plugins;\r\n\r\n	extract(lAtts(array(\r\n		\'trigger\'      => \'smd_akey\',\r\n		\'trigger_mode\' => \'exact\', // exact, begins, ends, contains\r\n		\'site_name\'    => \'1\',\r\n		\'section_mode\' => \'0\',\r\n		\'force\'        => \'0\',\r\n		\'expires\'      => \'3600\', // in seconds\r\n	),$atts));\r\n\r\n	if (smd_akey_table_exist(1)) {\r\n		$url = serverSet(\'REQUEST_URI\');\r\n\r\n		if ($site_name && (strpos($url, hu) === false)) {\r\n			$urlparts = parse_url(hu);\r\n			// Can\'t use raw hu since it contains the subdir (as does the REQUEST_URI)\r\n			// so duplicates would occur in the generated URL\r\n			$url = $urlparts[\'scheme\'] . \'://\' . $urlparts[\'host\'] . $url;\r\n		}\r\n\r\n		if ($section_mode == \'1\') {\r\n			$halves = explode(\'?\', $url);\r\n			$half1 = explode(\'/\', $halves[0]);\r\n			$half2 = (isset($halves[1])) ? explode(\'/\', $halves[1]) : array();\r\n\r\n			$parts = array_merge($half1, $half2);\r\n		} else {\r\n			$parts = explode(\'/\', $url);\r\n		}\r\n\r\n		trace_add(\'[smd_access_key URL elements: \' . join(\'|\', $parts).\']\');\r\n\r\n		// Look for one of the triggers in the URL and bomb out if we find it\r\n		$triggers = do_list($trigger);\r\n		$trigger = $triggers[0]; // Initialise to the first value in case no others are found\r\n\r\n		$trigoff = false;\r\n		foreach ($triggers as $trig) {\r\n			switch ($trigger_mode) {\r\n				case \'exact\':\r\n					$trigoff = array_search($trig, $parts);\r\n					$realTrig = $trig;\r\n				break;\r\n				case \'begins\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						if (strpos($part, $trig) === 0) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n				case \'ends\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						$re = \'/.+\'.preg_quote($trig).\'$/i\';\r\n						if (preg_match($re, $part) === 1) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n				case \'contains\':\r\n					$count = 0;\r\n					foreach ($parts as $part) {\r\n						$re = \'/.*\'.preg_quote($trig).\'.*$/i\';\r\n						if (preg_match($re, $part) === 1) {\r\n							$trigoff = $count;\r\n							$realTrig = $part;\r\n							break;\r\n						}\r\n						$count++;\r\n					}\r\n				break;\r\n			}\r\n			if ($trigoff !== false) {\r\n				// Found it so set the trigger to be the current item and jump out\r\n				$trigoff = ($trigger == \'file_download\') ? $trigoff + 2 : $trigoff;\r\n				$trigger = $realTrig;\r\n				break;\r\n			}\r\n		}\r\n\r\n		trace_add(\'[smd_access_key trigger: \' . $trigger . ($trigoff ? \' found at \' . $trigoff : \'\') . \']\');\r\n\r\n		$ret = false;\r\n		$smd_access_error = $smd_access_errcode = \'\';\r\n		$smd_akey_salt_length = get_pref(\'smd_akey_salt_length\', $smd_akey_prefs[\'smd_akey_salt_length\'][\'default\']);\r\n		$doip = get_pref(\'smd_akey_log_ip\', $smd_akey_prefs[\'smd_akey_log_ip\'][\'default\']);\r\n\r\n		if ($trigoff !== false) {\r\n			$tokidx = $trigoff + 1;\r\n			$timeidx = $trigoff + 2;\r\n			$extraidx = $trigoff + 3;\r\n\r\n			// OK, on a trigger page, so read the token from the URL\r\n			$tok = (isset($parts[$tokidx]) && strlen($parts[$tokidx]) == intval(32 + $smd_akey_salt_length)) ? $parts[$tokidx] : 0;\r\n\r\n			trace_add(\'[smd_access_key token: \' . $tok .\']\');\r\n\r\n			if ($tok) {\r\n				// The token is present, so read the timestamp from the URL\r\n				$t_hex = (isset($parts[$timeidx])) ? $parts[$timeidx] : 0;\r\n\r\n				// Is there a download limit? Extract it if so\r\n				$timeparts = do_list($t_hex, \'.\');\r\n				$max = (isset($timeparts[1])) ? $timeparts[1] : \'0\';\r\n				$maxtok = ($max) ? \'.\'.$max : \'\';\r\n				$t_hex = $timeparts[0];\r\n\r\n				// Any extra info?\r\n				$extras = (isset($parts[$extraidx])) ? array_slice($parts, $extraidx) : array();\r\n\r\n				// Recreate the original page URL, sans /trigger/token/time\r\n				if ($trigger == \'file_download\') {\r\n					$trigoff++;\r\n					$trigger = \'\';\r\n				}\r\n\r\n				// gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs\r\n				// so it requires an exception\r\n				$gbp_pl = (is_array($plugins) && in_array(\'gbp_permanent_links\', $plugins));\r\n\r\n				if ($permlink_mode == \'messy\' && !$gbp_pl) {\r\n					// Don\'t want a slash between site and start of query params\r\n					$page = rtrim(join(\'/\', array_slice($parts, 0, $trigoff-1)), \'/\') . $parts[$trigoff-1];\r\n				} else {\r\n					$page = rtrim(join(\'/\', array_slice($parts, 0, $trigoff)), \'/\');\r\n				}\r\n\r\n				// In case the URL contains non-ascii chars\r\n				$page = rawurldecode($page);\r\n\r\n				trace_add(\'[smd_access_key page | timestamp | max | extras: \' . join(\'|\', array($page, $t_hex, $max, $extras)) . \']\');\r\n\r\n				if ($t_hex) {\r\n					// The timestamp is present. Next, get the secret key\r\n					$secret = false;\r\n					$secring = safe_row(\'*\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\' AND t_hex = \'\".doSlash($t_hex).\"\'\");\r\n\r\n					if ($secring) {\r\n						$secret = $secring[\'secret\'];\r\n\r\n						// Extract the salt from the token\r\n						$plen = strlen($page) % 32;\r\n						$salt = substr($tok, $plen, $smd_akey_salt_length);\r\n						$tok = substr($tok, 0, $plen).substr($tok, $plen+$smd_akey_salt_length);\r\n						$ext = (($extras) ? urldecode(join(\'/\', $extras)) : \'\');\r\n\r\n						// Regenerate the original token...\r\n		 				$check_token = md5($salt.$secret.$page.$trigger.$t_hex.$maxtok.$ext);\r\n\r\n						trace_add(\'[smd_access_key reconstructed token: \' . $check_token . \']\');\r\n\r\n						// ... and compare it to the one in the URL\r\n						if ($check_token == $tok) {\r\n							// Token is valid. Now check if the page has expired\r\n\r\n							// Is there an explicit access key expiry? Extract that if so\r\n							$timeparts = do_list($t_hex, \'-\');\r\n							$t_exp = (isset($timeparts[1])) ? hexdec($timeparts[1]) : \'\';\r\n							$t_beg = $timeparts[0];\r\n\r\n							$t_dec = hexdec($t_beg);\r\n							$now = time();\r\n\r\n							// Has the resource become available yet?\r\n							if ($now < $t_dec) {\r\n								if ($thing == NULL) {\r\n									txp_die(smd_akey_gTxt(\'smd_akey_err_unavailable\'), 410);\r\n								} else {\r\n									$smd_access_error = \'smd_akey_err_unavailable\';\r\n									$smd_access_errcode = 410;\r\n								}\r\n							} else {\r\n								// Has token\'s expiry been reached, or is \'now\' greater than \'then\' (when token generated) + expiry period?\r\n								if ($t_exp) {\r\n									$tester = true;\r\n									$compare_to = $t_exp;\r\n								} else {\r\n									$tester = ($expires != 0);\r\n									$compare_to = $t_dec + $expires;\r\n								}\r\n\r\n								if ($tester && ($now > $compare_to)) {\r\n									if ($thing == NULL) {\r\n										txp_die(smd_akey_gTxt(\'smd_akey_err_expired\'), 410);\r\n									} else {\r\n										$smd_access_error = \'smd_akey_err_expired\';\r\n										$smd_access_errcode = 410;\r\n									}\r\n								} else {\r\n									// Check if the download limit has been exceeded\r\n									$vu_qty = $secring[\'accesses\'];\r\n									if ($max) {\r\n										if ($vu_qty < $max) {\r\n											$ret = true;\r\n										} else {\r\n											if ($thing == NULL) {\r\n												txp_die(smd_akey_gTxt(\'smd_akey_err_limit\'), 410);\r\n											} else {\r\n												$smd_access_error = \'smd_akey_err_limit\';\r\n												$smd_access_errcode = 410;\r\n											}\r\n										}\r\n									} else {\r\n										$ret = true;\r\n									}\r\n									// Increment the access counter\r\n									$vu_qty++;\r\n	\r\n									// Grab the IP and add it to the list of IPs so far\r\n									if ($doip) {\r\n										$ips = do_list($secring[\'ip\'], \' \');\r\n										$ip = remote_addr();\r\n										if (!in_array($ip, $ips)) {\r\n											$ips[] = $ip;\r\n										}\r\n										$ipup = \", ip=\'\".doSlash(trim(join(\' \', $ips))).\"\'\";\r\n									} else {\r\n										$ipup = \'\';\r\n									}\r\n									safe_update(SMD_AKEYS, \"accesses=\'\".doSlash($vu_qty).\"\'\" . $ipup, \"page=\'\".doSlash($page).\"\' AND t_hex = \'\".doSlash($t_hex).\"\'\");\r\n\r\n									// Load up the global array so <txp:smd_access_info> and <txp:if_smd_access_info> work\r\n									$smd_akey_protected_info = array(\r\n										\'page\'     => $secring[\'page\'],\r\n										\'hextime\'  => $secring[\'t_hex\'],\r\n										\'issued\'   => $secring[\'time\'],\r\n										\'now\'      => $now,\r\n										\'expires\'  => $compare_to,\r\n										\'trigger\'  => $secring[\'triggah\'],\r\n										\'maximum\'  => $secring[\'maximum\'],\r\n										\'accesses\' => $vu_qty,\r\n									);\r\n									if ($doip) {\r\n										$smd_akey_protected_info[\'ip\'] = $ip;\r\n									}\r\n									if ($extras) {\r\n										$smd_akey_protected_info[\'extra\'] = urldecode(join(\'/\', $extras));\r\n										foreach($extras as $idx => $extra) {\r\n											$smd_akey_protected_info[\'extra_\'.intval($idx+1)] = urldecode($extra);\r\n										}\r\n									}\r\n								}\r\n							}\r\n\r\n						} else {\r\n							if ($thing == NULL) {\r\n								txp_die(smd_akey_gTxt(\'smd_akey_err_invalid_token\'), 403);\r\n							} else {\r\n								$smd_access_error = \'smd_akey_err_invalid_token\';\r\n								$smd_access_errcode = 403;\r\n							}\r\n						}\r\n\r\n					} else {\r\n						if ($thing == NULL) {\r\n							txp_die(smd_akey_gTxt(\'smd_akey_err_unauthorized\'), 401);\r\n						} else {\r\n							$smd_access_error = \'smd_akey_err_unauthorized\';\r\n							$smd_access_errcode = 401;\r\n						}\r\n					}\r\n\r\n				} else {\r\n					if ($thing == NULL) {\r\n						txp_die(smd_akey_gTxt(\'smd_akey_err_missing_timestamp\'), 403);\r\n					} else {\r\n						$smd_access_error = \'smd_akey_err_missing_timestamp\';\r\n						$smd_access_errcode = 403;\r\n					}\r\n				}\r\n\r\n			} else {\r\n				if ($thing == NULL) {\r\n					txp_die(smd_akey_gTxt(\'smd_akey_err_bad_token\'), 403);\r\n				} else {\r\n					$smd_access_error = \'smd_akey_err_bad_token\';\r\n					$smd_access_errcode = 403;\r\n				}\r\n			}\r\n		} else {\r\n			// If we always want to forbid access to this page regardless if the trigger exists\r\n			if ($force) {\r\n				if ($thing == NULL) {\r\n					txp_die(smd_akey_gTxt(\'smd_akey_err_forbidden\'), 401);\r\n				} else {\r\n					$smd_access_error = \'smd_akey_err_forbidden\';\r\n					$smd_access_errcode = 401;\r\n				}\r\n			} else {\r\n				$ret = true;\r\n			}\r\n		}\r\n\r\n		if ($smd_access_error || $smd_access_errcode) {\r\n			trace_add(\'[smd_access_key error state: \' . $smd_access_errcode . \'|\' . $smd_access_error . \']\');\r\n		}\r\n\r\n		// If we reach this point it\'s because we\'re using a container\r\n		return parse(EvalElse($thing, $ret));\r\n	} else {\r\n		trigger_error(smd_akey_gTxt(\'smd_akey_tbl_not_installed\'), E_USER_NOTICE);\r\n	}\r\n}\r\n\r\n// Called just before a download is initiated\r\nfunction smd_access_protect_download($evt, $stp) {\r\n	global $smd_akey_prefs, $id, $file_error;\r\n\r\n	if (smd_akey_table_exist(1) && !isset($file_error)) {\r\n		$fileid = intval($id);\r\n\r\n		// In case the page was called with a bogus filename, get the \"true\" filename\r\n		// from the database and make up the valid URL\r\n		$real_file = safe_field(\"filename\", \"txp_file\", \"id=\".doSlash($fileid));\r\n		$page = filedownloadurl($fileid, $real_file);\r\n		$secring = safe_field(\'page\', SMD_AKEYS, \"page=\'\".doSlash($page).\"\'\");\r\n\r\n		// Only want to protect pages that we\'ve generated tokens for\r\n		if ($secring) {\r\n			// Pass in a default expiry from the pref, but it can be overridden by the key\'s expiry\r\n			return smd_access_protect(\r\n				array(\r\n					\'trigger\' => \'file_download\',\r\n					\'force\'   => \'1\',\r\n					\'expires\' => get_pref(\'smd_akey_file_download_expires\', $smd_akey_prefs[\'smd_akey_file_download_expires\'][\'default\']),\r\n				)\r\n			);\r\n		}\r\n	}\r\n	// remote download not done - leave to Txp to handle error or \"local\" file download\r\n	return;\r\n}\r\n\r\n// Conditional tag for checking error status from smd_access_protect\r\nfunction smd_if_access_error($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode;\r\n\r\n	extract(lAtts(array(\r\n		\'type\'   => \'\',\r\n		\'code\'   => \'\',\r\n	),$atts));\r\n\r\n	$err = array();\r\n	$codes = do_list($code);\r\n	$types = do_list($type);\r\n\r\n	if ($smd_access_error) {\r\n		if ($code && $type) {\r\n			$err[\'code\'] = (in_array($smd_access_errcode, $codes)) ? true : false;\r\n			$err[\'msg\'] = (in_array($smd_access_error, $types)) ? true : false;\r\n		} else if ($code) {\r\n			$err[\'code\'] = (in_array($smd_access_errcode, $codes)) ? true : false;\r\n		} else if ($type) {\r\n			$err[\'msg\'] = (in_array($smd_access_error, $types)) ? true : false;\r\n		} else {\r\n			$err[\'msg\'] = true;\r\n		}\r\n	}\r\n\r\n	$out = in_array(false, $err) ? false : true; // AND logic\r\n\r\n	return parse(EvalElse($thing, $out));\r\n}\r\n\r\n// Display access error information\r\nfunction smd_access_error($atts, $thing=NULL) {\r\n	global $smd_access_error, $smd_access_errcode;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'    => \'message\',\r\n		\'message\' => \'\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'break\'      => \'\',\r\n		\'breakclass\' => \'\',\r\n	),$atts));\r\n\r\n	$out = array();\r\n	$items = do_list($item);\r\n\r\n	if ($smd_access_errcode && in_array(\'code\', $items)) {\r\n		$out[] = $smd_access_errcode;\r\n	}\r\n\r\n	if ($smd_access_error && in_array(\'message\', $items)) {\r\n		$out[] = ($message) ? $message : smd_akey_gTxt($smd_access_error);\r\n	}\r\n\r\n	if ($out) {\r\n		return doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n// Display access information for custom formatted messages\r\nfunction smd_access_info($atts, $thing=NULL) {\r\n	global $smd_akey_protected_info, $smd_akey_info;\r\n\r\n	extract(lAtts(array(\r\n		\'item\'       => \'page\',\r\n		\'escape\'     => \'html\',\r\n		\'format\'     => \'%Y-%m-%d %H:%M:%S\',\r\n		\'wraptag\'    => \'\',\r\n		\'class\'      => \'\',\r\n		\'html_id\'    => \'\',\r\n		\'break\'      => \'\',\r\n		\'breakclass\' => \'\',\r\n	),$atts));\r\n\r\n	$out = array();\r\n	$items = do_list($item);\r\n\r\n	foreach ($items as $idx) {\r\n		$ak_idx = \'ak_\'.$idx;\r\n		if ($smd_akey_protected_info && array_key_exists($idx, $smd_akey_protected_info)) {\r\n			$val = ($escape == \'html\') ? htmlspecialchars($smd_akey_protected_info[$idx]) : $smd_akey_protected_info[$idx];\r\n			if (in_array($idx, array(\'time\', \'now\', \'expires\')) && $format) {\r\n				$val = safe_strftime($format, $val);\r\n			}\r\n			$out[] = $val;\r\n		}\r\n		if ($smd_akey_info && array_key_exists($ak_idx, $smd_akey_info)) {\r\n			$val = ($escape == \'html\') ? htmlspecialchars($smd_akey_info[$ak_idx]) : $smd_akey_info[$ak_idx];\r\n			if (in_array($idx, array(\'time\', \'now\', \'expires\')) && $format) {\r\n				$val = safe_strftime($format, $val);\r\n			}\r\n			$out[] = $val;\r\n		}\r\n	}\r\n\r\n	if ($out) {\r\n		return doWrap($out, $wraptag, $break, $class, $breakclass, \'\', \'\', $html_id);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\n/**\r\n * smd_akey_gTxt	Convert strings for i18n purposes\r\n *\r\n * @param	string	$what [+] [private] [static]		Name of the item from the array to retrieve\r\n * @param	array	$atts [+]	(Default: Array)	Array of \'search\' => \'replacements\' to make\r\n */\r\nfunction smd_akey_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'en-gb\' => array(\r\n			\'smd_akey_accesses\' => \'Access attempts\',\r\n			\'smd_akey_btn_new\' => \'New key\',\r\n			\'smd_akey_btn_pref\' => \'Prefs\',\r\n			\'smd_akey_deleted\' => \'Keys deleted: {deleted}\',\r\n			\'smd_akey_err_bad_token\' => \'Missing or mangled access key\',\r\n			\'smd_akey_err_expired\' => \'Access expired\',\r\n			\'smd_akey_err_forbidden\' => \'Forbidden access\',\r\n			\'smd_akey_err_invalid_token\' => \'Invalid access key\',\r\n			\'smd_akey_err_missing_timestamp\' => \'Missing timestamp\',\r\n			\'smd_akey_err_unavailable\' => \'Not available\',\r\n			\'smd_akey_err_unauthorized\' => \'Unauthorized access\',\r\n			\'smd_akey_err_limit\' => \'Access limit reached\',\r\n			\'smd_akey_file_download_expires\' => \'File download expiry time (seconds)\',\r\n			\'smd_akey_generated\' => \'Access key: {key}\',\r\n			\'smd_akey_log_ip\' => \'Log IP addresses\',\r\n			\'smd_akey_max\' => \'Maximum\',\r\n			\'smd_akey_need_page\' => \'You need to enter a page URL\',\r\n			\'smd_akey_page\' => \'Page\',\r\n			\'smd_akey_pref_legend\' => \'Access key preferences\',\r\n			\'smd_akey_prefs_saved\' => \'Preferences saved\',\r\n			\'smd_akey_prefs_some_tbl\' => \'Not all table info available.\',\r\n			\'smd_akey_prefs_some_explain\' => \'This is either a new installation or a different version\'.br.\'of the plugin to one you had before.\',\r\n			\'smd_akey_prefs_some_opts\' => \'Click \"Install table\" to add or update the table\'.br.\'leaving all existing data untouched.\',\r\n			\'smd_akey_prefs_some_tbl\' => \'Not all table info available.\',\r\n			\'smd_akey_salt_length\' => \'Salt length (characters)\',\r\n			\'smd_akey_tab_name\' => \'Access keys\',\r\n			\'smd_akey_tbl_install_lbl\' => \'Install table\',\r\n			\'smd_akey_tbl_installed\' => \'Table installed\',\r\n			\'smd_akey_tbl_not_installed\' => \'Table not installed\',\r\n			\'smd_akey_tbl_removed\' => \'Table removed\',\r\n			\'smd_akey_tbl_not_removed\' => \'Table not removed\',\r\n			\'smd_akey_time\' => \'Issued\',\r\n			\'smd_akey_trigger\' => \'Trigger\',\r\n		),\r\n	);\r\n\r\n	$thislang = get_pref(\'language\', \'en-gb\');\r\n	$thislang = (isset($lang[$thislang][$what])) ? $thislang : \'en-gb\';\r\n	return strtr($lang[$thislang][$what], $atts);\r\n}', '2ef7a957bd67f460e69c7755429a843f', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_bio', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.50', 'Customisable user biographies / profile info.', '<h1>smd_bio</h1>\n\n<p>Configure additional user biographical information to be collected when authors are created, then show that info as bylines against your articles. Like custom fields for user info.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Define only the info you require to be collected about your users &#8212; totally customisable.</li>\n	<li>Info is entered/edited on the <em>Admin-&gt;Users</em> tab beneath the existing user info (i.e. Publishers only) but also integrates with smd_user_manager.</li>\n	<li>Choose from a variety of types of content: text, radio buttons, select lists, checkboxes, images&#8230;</li>\n	<li>Output any pieces of biographical info in your article flow in a variety of ways.</li>\n	<li>Image thumbnail preview/selection on the <em>Admin-&gt;Users</em> tab.</li>\n	<li>Optional <span class=\"caps\">CSS</span> to modify the layout of the <em>Admin-&gt;Users</em> tab.</li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p class=\"important\">Requires Textpattern 4.5+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1116/smd_bio\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Textpattern <em>Admin-&gt;Plugins</em> pane, install and enable the plugin. The plugin&#8217;s tables will be installed automatically. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=31496\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>When you visit the <em>Extensions-&gt;Bio config</em> page, the plugin&#8217;s tables will be checked and installed/upgraded automatically if not present already. This is a convenience for people who run the plugin from the cache directory.</p>\n\n<p>To uninstall, delete the plugin from the <em>Admin-&gt;Plugins</em> page. <strong>All additional user tables and bio information will be removed</strong> so be sure you have backed everything up first.</p>\n\n<h2 id=\"smd_bio_config\">Configuring bio fields</h2>\n\n<p>Visit the <em>Extensions-&gt;Bio config</em> tab. Add bio fields such as cell/work/fax numbers, department, mugshot, postal address, job title, whatever you wish. Just add one at a time and hit Save in between. The fields will be listed below the form, and can be sorted at will. Each field comprises:</p>\n\n<dl>\n	<dt><strong>Name</strong></dt>\n	<dd>An internal name with which you wish to refer to your bio field. Avoid characters such as spaces and &#8216;weird&#8217; characters here. <strong>This cannot be changed, once set</strong></dd>\n	<dt><strong>Title</strong></dt>\n	<dd>A human-friendly name for your bio field. This is displayed to the left of the input control on the <em>Admin-&gt;Users</em> panel, and as a label on your article pages.</dd>\n	<dd>If you omit the <strong>Name</strong> then this Title will be &#8220;dumbed down&#8221; to valid characters and used as the Name.</dd>\n	<dt><strong>Type</strong></dt>\n	<dd>The type of data you wish to collect. This will be the flavour of input control you see on the <em>Admin-&gt;Users</em> panel</dd>\n	<dt><strong>Column type</strong> (click &#8216;More&#8217; to toggle)</dt>\n	<dd>The database column type that is created. If you don&#8217;t know what this is, just accept the default, otherwise specify which type of data this column is to hold</dd>\n	<dd>Note that some types of biographical information (e.g. images) are forced to be of a certain column type</dd>\n	<dd><span class=\"caps\">IMPORTANT</span>: if you alter this after it has been created, any existing bio data in the field will be altered to suit and <strong>you may lose information</strong>. Please backup first</dd>\n	<dt><strong>Column size</strong> (click &#8216;More&#8217; to toggle)</dt>\n	<dd>Some column types &#8212; most notably the varchar types &#8212; require a column width (or display width) to be specified. Put a value here if you wish to use a size other than the default. If you don&#8217;t know what this is, just accept the default</dd>\n	<dd>Note that most column types do not require a size so if you do specify one it will be ignored</dd>\n	<dd><span class=\"caps\">IMPORTANT</span>: if you alter this after it has been created, any existing bio data in the field will be altered to suit and <strong>you may lose information</strong>. Please backup first</dd>\n	<dt><strong>Size</strong></dt>\n	<dd>The dimensions of the chosen input control. Takes one or two comma-separated values, the interpretation of which depends on the field <em>Type</em>:\n	<dl>\n		<dd>For text-based input fields, the first is the width of the box (in characters) and the second is the maximum number of characters a user can type (0 = leave at default)</dd>\n		<dd>For numeric-based input fields, the values are the minimum permitted value, the maximum permitted value, and the acceptable interval (step) that value can take</dd>\n		<dd>For textareas, it is the width and height of the box in characters, respectively</dd>\n		<dd>For images, the two values are the x and y dimensions of the image/thumbnail on the <em>Admin-&gt;Users</em> tab. If only one value is given, the image will be square. If either value is omitted, the image or thumbnail dimensions (as stored in the database) are used instead</dd>\n		<dd>For other types, the Size is currently unused</dd>\n	</dl></dd>\n	<dt><strong>Value</strong></dt>\n	<dd>Depends on the field <em>Type</em>:\n	<dl>\n		<dd>For text- and number-based fields, this is the default value that will be placed in the text box. You can use this to initialise an entry, or offer instructions like &#8220;Type your job description here&#8221;</dd>\n		<dd>For images, it can be used to specify the parent category name under which the desired images are stored. If omitted, all images are available in the image select list</dd>\n		<dd>For list, radio and checkbox types, this is used to configure the available options:\n		<dl>\n			<dd>Either put one option per line or use a comma to separate each option.</dd>\n			<dd>If you just list options, they will be used as labels exactly as you define them. When referring to them with the public tags, the field <em>names</em> will be all lower case, have &#8216;non-web-safe&#8217; characters removed, and will have spaces converted to underscores. See the <a href=\"#smd_bio_list_example\">example</a> for further details.</dd>\n			<dd>In select lists, you may put an empty option at the top if you wish by beginning the list with a comma.</dd>\n			<dd>In select lists, checkbox and radio sets you may also mark label(s) with <code>[*]</code> to indicate it is a default checked/selected value. Radio sets and single-select lists only permit one marker.</dd>\n		</dl></dd>\n		</dl></dd>\n	<dt><strong>Position</strong></dt>\n	<dd>The order in which the fields appear on the <i>Admin-&gt;Users</i> tab</dd>\n	<dd>You can use any alphanumeric system to sort, e.g <code>10, 20, 30,...</code> or <code>a, b, c...</code>. The lower the value the higher up the screen it will be</dd>\n</dl>\n\n<h3 id=\"smd_bio_list_example\">Defining your own lists</h3>\n\n<p>There are a few of ways to define your own names and/or labels for use in select lists, radio sets and checkbox groups:</p>\n\n<pre class=\"block\"><code>label_1\nlabel_2\nlabel_3\n...</code></pre>\n\n<p>or</p>\n\n<pre class=\"block\"><code>label_1, label_2, label_3, ...</code></pre>\n\n<p>or</p>\n\n<pre class=\"block\"><code>name_1 =&gt; label_1, name_2 =&gt; label_2, name_3...</code></pre>\n\n<p>(you may also put each name-label pair on a separate line if you wish). Here&#8217;s an example for a dropdown list of Departments:</p>\n\n<pre class=\"block\"><code>sales =&gt; Sales\nmktg =&gt; Marketing\neng =&gt; Engineering\nqual =&gt; Quality assurance\nit =&gt; Tech support</code></pre>\n\n<p>If you defined the list as above, your field names would be <code>sales</code>, <code>mktg</code>, <code>eng</code>, <code>qual</code> and <code>it</code>, respectively.</p>\n\n<p>If, however, you omit the field names, viz:</p>\n\n<pre class=\"block\"><code>Sales\nMarketing\nEngineering\nQuality assurance\nTech support</code></pre>\n\n<p>then you would refer to the fields with: <code>sales</code>, <code>marketing</code>, <code>engineering</code>, <code>quality_assurance</code>, and <code>tech_support</code>, respectively.</p>\n\n<p>For multiple select lists and checkbox groups you can optionally define some of the entries as defaults. For example in your <code>subscriptions</code> checkbox group:</p>\n\n<pre class=\"block\"><code>Kerrang\nFuture Music[*]\nNME\nSound on Sound[*]</code></pre>\n\n<p>The same system applies to single or multiple select lists and radio sets, although for single selects and radio sets, only one element may be starred (if you star more, only one of the defaults will prevail).</p>\n\n<p>There is one further method of entering data, and that is to call another <span class=\"caps\">PHP</span> function. Perhaps you want to offer a select list of all countries of the world. You could type them in or copy and paste them from the Internet as long as they were in the correct <code>key =&gt; value</code> or comma-separated format. Alternatively you could put this in the <em>Value</em> box:</p>\n\n<pre><code>SMD_BIO_FN|function_name|param_1|param_2|...</code></pre>\n\n<p>As long as that named function returns a standard <span class=\"caps\">PHP</span> array, the values and any keys returned will be injected into the <em>Value</em> box automatically.</p>\n\n<h2>Entering user data: <em>Admin-&gt;Users</em></h2>\n\n<p>Your configured fields will appear on the <em>Admin-&gt;Users</em> panel, beneath the usual crop of data input fields. Simply enter data in them and it will be saved along with the existing user data. Hit <em>Edit</em> and any configured info will be retrieved for editing.</p>\n\n<p>When choosing an image, you can either type its ID in the box or use the dropdown select list to choose an image. The chosen image will appear below the input controls.</p>\n\n<p>If you wish to alter the layout of the input controls, you may create a standard stylesheet in <em>Presentation-&gt;Style</em> and name it <code>smd_bio</code>. It will be loaded automatically when you visit the <em>Admin-&gt;Users</em> panel.</p>\n\n<p>To quickly view the extended bio information for a user, hover over the user&#8217;s login name link in the list; a tooltip will appear showing some of the extended bio. The data is fetched from the server when you first hover over the row so it may take a few seconds to load (and may require you to wiggle the mouse around a bit to get it to display, sorry!)</p>\n\n<h2>Entering user data: <em>Admin-&gt;User manager</em></h2>\n\n<p>If you have the smd_user_manager plugin installed, smd_bio will hook into that plugin. When you hover over the login name of an entry in the User list, detailed bio information is retrieved and displayed as a tooltip. Editing a user will also permit Bio information to be entered.</p>\n\n<h2>Displaying user bio info on your site</h2>\n\n<p>When you create a new <em>field</em> in the <a href=\"#smd_bio_config\">Bio Config</a> pane, it has various attributes like name, title, size, value, etc. The <code>name</code> is the key: that is how you refer to the field using the <code>field</code> or <code>fields</code> attributes in the various tags (below).</p>\n\n<p>Each field has a variety of pieces of data that you may display. These are known as <code>items</code> and the primary ones are:</p>\n\n<ol>\n	<li><code>value</code> : the current value of the field that has been selected / typed by the user in their profile.</li>\n	<li><code>title</code> : the human friendly title (a.k.a. <em>label</em>) that you have assigned to your field in the Bio Config screen &#8212; this is handy if you want to print out the title alongside the data value itself, e.g. Department: sales.</li>\n	<li><code>name</code> : the field&#8217;s key (shown in the &#8216;Name&#8217; column on the Bio Config panel). This is of less everyday use, but when building your own input screens for capturing bio data, it becomes handy so you can tell mem_form the name of the field it needs to store the bio data item in.</li>\n</ol>\n\n<p>There are other items useful for displaying the field type, various counters, or for diving deeper into the available options in lists, radio sets, checkboxes, etc, but the most important concept is that a <code>field</code> is your bio thingamybob (it&#8217;s Name / Key), and an <code>item</code> is the part of thingamybob you want to display: its value, title, name (a.k.a. key), type, default values, and so on.</p>\n\n<h2 id=\"smd_bio_info\">Tag: <code>&lt;txp:smd_bio_info&gt;</code></h2>\n\n<p>Use this tag to display pieces of info from a user&#8217;s biography. One or more of these tags can be employed depending on how you prefer to work. It may also be used as a container (or via the <code>form</code> attribute) to allow you to embed other Textpattern tags.</p>\n\n<p>This tag requires article context so you normally use it inside <code>&lt;txp:if_individual_article&gt;</code> tags. By default it will look up the author of the currently viewed article and display the given bio fields from that author.</p>\n\n<p>If you&#8217;re trying to display bio info in a sidebar or on a list page that does not necessarily have article context, you need to specify the author(s) that you wish to display bio info from. In v0.3x you did that with the <code>authors</code> attribute; in v0.4x you wrap your <code>&lt;txp:smd_bio_info&gt;</code> tag in an <a href=\"#smd_bio_author\">smd_bio_author</a> tag.</p>\n\n<p>Use the following attributes to tweak this tag&#8217;s output. The default value is unset unless otherwise noted:</p>\n\n<dl>\n	<dt><strong>fields</strong></dt>\n	<dd>List of bio field <em>names</em> you wish to display, in the order you wish to display them.</dd>\n	<dd>Note you can also display bio information from the standard Textpattern user table, i.e. you can use any of the following, (case sensitive) : <code>user_id</code>, <code>name</code> (login name), <code>RealName</code>, <code>email</code>, <code>privs</code>, <code>last_access</code></dd>\n	<dd>Default: unset (i.e. all fields)</dd>\n	<dt><strong>exclude</strong></dt>\n	<dd>List of bio field <em>names</em> you do <strong>not</strong> wish to display. This overrides <code>fields</code></dd>\n	<dt><strong>form</strong></dt>\n	<dd>The name of a Txp Form with which to process each record.</dd>\n	<dd>If not specified, the tag&#8217;s container will be used.</dd>\n	<dd>If the container is empty, default output is used (label and field contents).</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around each record.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Fixed <span class=\"caps\">CSS</span> class name to add to each record&#8217;s wraptag.</dd>\n	<dt><strong>break</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag to put between each field.</dd>\n	<dt><strong>breakclass</strong></dt>\n	<dd><span class=\"caps\">CSS</span> class name to add to each field&#8217;s wraptag.</dd>\n	<dd>The default is to automatically assign <code>smd_bio_*name_of_field*</code> (e.g. smd_bio_cell, smd_bio_dept, etc).</dd>\n	<dt><strong>labeltag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the field&#8217;s label.</dd>\n	<dt><strong>label</strong></dt>\n	<dd>Whether to display a label or not for each field. Choose from:\n	<dl>\n		<dd><strong>1</strong> : (default) Display the Title of the field</dd>\n		<dd><strong>0</strong> : Display the Name of the field</dd>\n		<dd><strong>some label</strong> : Display the given text as a label</dd>\n	</dl></dd>\n	</dl>\n:: &#8220;&#8221; (i.e. empty) : Do not display any label<br />\n<dl>\n	<dt><strong>show_empty</strong></dt>\n	<dd>Choose if you wish to hide (0) or show (1) any fields that have no data assigned to them.</dd>\n	<dd>Default: 0</dd>\n</dl>\n\n<h2 id=\"smd_bio_author\">Tag: <code>&lt;txp:smd_bio_author&gt;</code></h2>\n\n<p>Wrap this tag around <code>&lt;txp:smd_bio_info&gt;</code> tags to display information from more than one user. The contained content will be displayed for each author.</p>\n\n<dl>\n	<dt><strong>author</strong></dt>\n	<dd>Comma-separated list of author login names from which you wish to display info.</dd>\n	<dd>If omitted, the current (individual article) author will be used &#8212; functionally the same as if you just used <code>&lt;txp:smd_bio_info /&gt;</code> without the author wrapper tag.</dd>\n	<dd>You may specify any of your comma-separated entries as <code>SMD_PRIVS:</code> and then colon-separate the priv numbers. Any users with those matching level(s) will be displayed.</dd>\n	<dd>You may also use <code>SMD_ALL</code> to return all defined authors.</dd>\n	<dt><strong>sort</strong></dt>\n	<dd>Order the authors by the given comma-separated list of columns and sort directions. You can order the results by any of the built-in user columns (RealName, name, user_id, email, last_access, privs) or your own bio fields.</dd>\n	<dd>For the sort order you can choose from:\n	<dl>\n		<dd><strong>asc</strong>: ascending order</dd>\n		<dd><strong>desc</strong>: descending order</dd>\n	</dl></dd>\n	<dd>Default: <code>RealName asc</code></dd>\n	<dt><strong>form</strong></dt>\n	<dd>The name of a Txp Form with which to process each author.</dd>\n	<dd>If not specified, the tag&#8217;s container will be used.</dd>\n	<dd>If the container is empty, the name of the author is displayed.</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the entire output.</dd>\n	<dt><strong>class</strong></dt>\n	<dd><span class=\"caps\">CSS</span> class name to add to the wraptag.</dd>\n	<dt><strong>break</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around each author record.</dd>\n	<dt><strong>breakclass</strong></dt>\n	<dd><span class=\"caps\">CSS</span> class name to apply to each break tag.</dd>\n	<dt><strong>labeltag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the label.</dd>\n	<dt><strong>label</strong></dt>\n	<dd>The label text to display above all author info.</dd>\n</dl>\n\n<h2 id=\"smd_bio_data\">Tag: <code>&lt;txp:smd_bio_data&gt;</code> <sup>(formerly <a href=\"#smd_bio_repkeys\">replacement keys</a>)</sup></h2>\n\n<p>Inside your <a href=\"#smd_bio_info\">smd_bio_info</a> form or container you can display biographical information using this tag. The following attributes select which piece of information to display:</p>\n\n<dl>\n	<dt><strong>field</strong></dt>\n	<dd>The bio field from you wish to display some information (e.g. cell, phone, address, department, &#8230;).</dd>\n	<dt><strong>item</strong></dt>\n	<dd>Comma-separated list of actual piece(s) of information you need about the field. Choose from:\n	<dl>\n		<dd><strong>value</strong> : the field&#8217;s content. Functionally equivalent to <code>{smd_bio_*field*}</code>.</dd>\n		<dd><strong>name</strong> : the field&#8217;s name, as defined on the Bio Config tab. Equivalent to <code>{smd_bio_*field*_name}</code>.</dd>\n		<dd><strong>title</strong> : the field&#8217;s &#8220;human friendly&#8221; title, as defined on the Bio Config tab. Equivalent to <code>{smd_bio_*field*_title}</code>.</dd>\n		<dd><strong>type</strong> : the field&#8217;s data type (text, textarea, checkbox, select list, etc). Note that there is no distinction between a single checkbox and a group of them; they are all designated <code>checkbox</code>.</dd>\n		<dd><strong>default</strong> : the field&#8217;s pre-initialized, or default value. This is only set for non-list field types: list fields have individual &#8216;default_option_N&#8217; entries instead (see below).</dd>\n		<dd><strong>option_N</strong> : the name (key) of the Nth option in a list (select, radio, checkbox group).</dd>\n		<dd><strong>title_N</strong> : the title of the Nth option in a list.</dd>\n		<dd><strong>chosen_option_N</strong> : the name (key) of the Nth selected option in a list  (select, radio, checkbox group).</dd>\n		<dd><strong>chosen_title_N</strong> : the label of the Nth selected option in a list.</dd>\n		<dd><strong>default_option_N</strong> : the name (key) of the Nth default option</dd>\n		<dd><strong>option_count</strong> : the total number of list options.</dd>\n		<dd><strong>chosen_count</strong> : the total number of selected list entries.</dd>\n		<dd><strong>default_count</strong> : the total number of default list entries.</dd>\n		<dd><strong>iterate_option</strong> : the name of the current option being iterated (see <a href=\"#smd_bio_iterate\">smd_bio_iterate</a>).</dd>\n		<dd><strong>iterate_title</strong>: the human-friendly title of the current option being iterated.</dd>\n		<dd><strong>iterate_count</strong> : The option number count (starting from 1).</dd>\n		<dd><strong>iterate_is_default</strong> : if you are iterating over <code>all</code> options, this will be set to 1 if the option is one of the options that should be set by default if nothing has already been chosen. 0 otherwise.</dd>\n		<dd><strong>iterate_is_chosen</strong> : if you are iterating over <code>all</code> options, this will be set to either <code>checked</code> or <code>selected</code> (depending on the field&#8217;s type) if the current option is chosen.</dd>\n		<dd><strong>widget</strong> : an <span class=\"caps\">HTML</span> input control of the correct type for this field. Multi select options are prefixed with <code>ms_</code> and each checkbox value in a group is prefixed with <code>cb_</code> when submitted. Note also that &#8216;image&#8217; fields render a simple text input field because they only store a standard Textpattern image ID; if you want to do anything more elaborate you will have to roll it yourself. Use widgets inside one of the following constructs to allow updating of bio fields from the public site / dashboards:\n		<dl>\n			<dd><code>&lt;txp:mem_form type=&quot;smd_bio&quot;&gt;</code></dd>\n			<dd><code>&lt;txp:mem_self_register&gt;</code></dd>\n			<dd><code>&lt;txp:mem_self_user_edit&gt;</code></dd>\n		</dl></dd>\n		</dl></dd>\n	<dd>Default: <code>value</code></dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the entire output.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Fixed <span class=\"caps\">CSS</span> class name to add to the wraptag.</dd>\n	<dt><strong>break</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag or characters to put between each item.</dd>\n</dl>\n\n<p>If you wish to see an entire list of available data, add <code>debug=&quot;1&quot;</code> to the surrounding smd_bio_info tag.</p>\n\n<h3 id=\"smd_bio_repkeys\"><code>&lt;txp:smd_bio_info&gt;</code> replacement keys <sup>(<strong>deprecated</strong>)</sup></h3>\n\n<p>The <a href=\"#smd_bio_data\">smd_bio_data</a> tag will be able to fit all your bio display needs. There is, however, a legacy method of displaying data using <i>replacement keys</i>. These should be considered deprecated and their use discouraged in favour of the smd_bio_data tag. They may be removed in future versions of the plugin.</p>\n\n<p>The main replacement keys are:</p>\n\n<dl>\n	<dt><code>{smd_bio_*field*}</code></dt>\n	<dd>The value of the named field (e.g. <code>smd_bio_cell</code>, <code>smd_bio_department</code>, etc).</dd>\n	<dt><code>{smd_bio_*field*_name}</code></dt>\n	<dd>Sanitized name of the column corresponding to this named field.</dd>\n	<dt><code>{smd_bio_*field*_title}</code></dt>\n	<dd>Human-friendly title you assigned this named field.</dd>\n	<dt><code>{smd_bio_*field*_class}</code></dt>\n	<dd>Name of the class associated with this named field.</dd>\n</dl>\n\n<p>If you have elected to extract a list item such as radio, list or checkbox you will have some more replacement keys in the following format:</p>\n\n<dl>\n	<dt><code>{smd_bio_*field*_option_N}</code></dt>\n	<dd>The value of each named option in turn, where N starts at 1 and increments.</dd>\n	<dt><code>{smd_bio_*field*_title_N}</code></dt>\n	<dd>The value of each named option&#8217;s title in turn. Again, N starts at 1 and counts up for every option in your list.</dd>\n	<dt><code>{smd_bio_*field*_chosen_option_N}</code></dt>\n	<dd>The value of each selected option in turn, where N starts at 1 and increments.</dd>\n	<dd>For select and radio lists there will be only one; for checkbox groups there may be more.</dd>\n	<dt><code>{smd_bio_*field*_chosen_title_N}</code></dt>\n	<dd>The value of each selected option&#8217;s label in turn, where N starts at 1 and increments.</dd>\n	<dt><code>{smd_bio_*field*_option_count}</code></dt>\n	<dd>The total number of elements in the named list.</dd>\n	<dt><code>{smd_bio_*field*_chosen_count}</code></dt>\n	<dd>The total number of selected elements in the named list.</dd>\n</dl>\n\n<p>Further, if you are displaying just a single <code>field</code>, these replacements (backwards compatible with smd_bio v0.3x) are present:</p>\n\n<dl>\n	<dt><code>{smd_bio_info_item}</code></dt>\n	<dd>The value of the current field &#8212; Deprecated: use <code>{smd_bio_info_value}</code> instead.</dd>\n	<dt><code>{smd_bio_info_name}</code></dt>\n	<dd>The sanitized name of the column corresponding to the field.</dd>\n	<dt><code>{smd_bio_info_title}</code></dt>\n	<dd>The human-friendly title you assigned the field.</dd>\n	<dt><code>{smd_bio_info_itemclass}</code></dt>\n	<dd>The name of this field&#8217;s class &#8212; Deprecated: use <code>{smd_bio_info_class}</code> instead.</dd>\n</dl>\n\n<h2 id=\"smd_if_bio\">Tag: <code>&lt;txp:smd_if_bio&gt;</code></h2>\n\n<p>A simple conditional for testing a field / item. Must be used inside a <code>&lt;txp:smd_bio_info /&gt;</code> tag to test for the existence/value of one of your chosen bio items. Use smd_if for more complex conditional logic. Supports <code>&lt;txp:else /&gt;</code>.</p>\n\n<dl>\n	<dt><strong>field</strong></dt>\n	<dd>The bio field from you wish to check (e.g. department, preferred_contact, phone_number,&#8230;)</dd>\n	<dt><strong>item</strong></dt>\n	<dd>The actual piece of information you want to compare from the field. Choose from the same items as defined in the <code>item</code> attribute for the <a href=\"#smd_bio_data\">smd_bio_data</a> tag.</dd>\n	<dd>Default: <code>value</code></dd>\n	<dt><strong>value</strong></dt>\n	<dd>The value you wish to compare the field/item against.</dd>\n	<dd>If omitted, the tag will just check for the existence of the given field/item</dd>\n</dl>\n\n<h2 id=\"smd_if_bio_first_author\">Tag: <code>&lt;txp:smd_if_bio_first_author&gt;</code></h2>\n\n<p>Parses the container if the current author is the first in the list. Must be used inside a <code>&lt;txp:smd_bio_info /&gt;</code> tag, and supports <code>&lt;txp:else /&gt;</code>.</p>\n\n<h2 id=\"smd_if_bio_last_author\">Tag: <code>&lt;txp:smd_if_bio_last_author&gt;</code></h2>\n\n<p>Parses the container if the current author is the last in the list. Must be used inside a <code>&lt;txp:smd_bio_info /&gt;</code> tag, and supports <code>&lt;txp:else /&gt;</code>.</p>\n\n<h2 id=\"smd_bio_iterate\">Tag: <code>&lt;txp:smd_bio_iterate&gt;</code></h2>\n\n<p>Step through select list, checkbox, and radio sets with this tag, displaying info about each option as you go. Useful if you want to roll your own widgets or do some custom interaction.</p>\n\n<dl>\n	<dt><strong>field</strong></dt>\n	<dd>The bio field over which you wish to iterate (e.g. preferred_contact, subscription, favourite_rockstar, &#8230;). Must be a &#8216;list&#8217; or &#8216;group&#8217; field.</dd>\n	<dt><strong>using</strong></dt>\n	<dd>The type of info you want to iterate over. Choose from:\n	<dl>\n		<dd><code>chosen</code>: step over chosen (selected, checked) options.</dd>\n		<dd><code>default</code>: step over default (pre-selected) options.</dd>\n		<dd><code>all</code>: step over all items in the group, whether selected or not.</dd>\n	</dl></dd>\n	<dd>Default: <code>chosen</code></dd>\n	<dt><strong>display</strong></dt>\n	<dd>The piece of information you wish to output from the option. This attribute is ignored if you use a <code>form</code> or the container. Choose from:\n	<dl>\n		<dd><code>option</code>: the internal name of the option</dd>\n		<dd><code>title</code>: the option&#8217;s human-friendly title</dd>\n	</dl></dd>\n	<dd>Default: <code>title</code></dd>\n	<dt><strong>form</strong></dt>\n	<dd>The name of a Textpattern Form with which to process each option.</dd>\n	<dd>If not specified, the tag&#8217;s container will be used.</dd>\n	<dd>If the container is empty, default output is used (the option&#8217;s value).</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag (without angle brackets) to wrap around the group.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Fixed <span class=\"caps\">CSS</span> class name to add to the group&#8217;s wraptag.</dd>\n	<dt><strong>break</strong></dt>\n	<dd><span class=\"caps\">HTML</span> tag to put between each option.</dd>\n	<dt><strong>breakclass</strong></dt>\n	<dd><span class=\"caps\">CSS</span> class name to add to each option&#8217;s wraptag.</dd>\n	<dt><strong>limit</strong></dt>\n	<dd>The maximum number of options to iterate over.</dd>\n	<dd>Default: 0 (i.e. all of them)</dd>\n	<dt><strong>offset</strong></dt>\n	<dd>The number of options to skip before starting to display options.</dd>\n	<dd>Default: 0</dd>\n</dl>\n\n<p>See the <a href=\"#smd_bio_data\">smd_bio_data</a> tag for details of what you can display / test inside this tag&#8217;s container.</p>\n\n<h2 id=\"smd_bio_articles\">Tag: <code>&lt;txp:smd_bio_articles&gt;</code></h2>\n\n<p>A simple convenience wrapper for <code>&lt;txp:article_custom /&gt;</code> that sets the <code>author</code> attribute to the person who wrote the current article. If you specify an author, that person will be used instead. In all other regards, the tag functions identically to <a href=\"http://textpattern.net/wiki/index.php?title=article_custom\">article_custom</a> and can be used as a container if you wish.</p>\n\n<p class=\"important\"><span class=\"caps\">IMPORTANT</span>: take care when using this tag inside your default form. If you do not specify your own container or a dedicated <code>form</code>, you will receive a <em>circular reference error</em> from Textpattern as it tries to call the default form, which calls the default form, which calls the default form&#8230;</p>\n\n<h3 id=\"smd_bio_examples\">Examples</h3>\n\n<h2 id=\"smd_bio_eg1\">Example 1: List bio fields from author of current article</h2>\n\n<pre><code>&lt;txp:smd_bio_info\n     fields=&quot;jobtitle, extension, cell, department&quot;\n     labeltag=&quot;dt&quot; wraptag=&quot;dl&quot;\n     break=&quot;dd&quot; class=&quot;profile&quot; /&gt;</code></pre>\n\n<p>Shows the job title, work&#8217;s extension number, cell phone number and department of the current author, as a definition list with class <code>profile</code>.</p>\n\n<h2 id=\"smd_bio_eg2\">Example 2: List profiles for named + priv level users</h2>\n\n<pre><code>&lt;txp:smd_bio_author wraptag=&quot;div&quot; class=&quot;authors&quot;\n     author=&quot;mr_pub, SMD_PRIVS:4:3&quot;&gt;\n   &lt;txp:smd_bio_info\n     fields=&quot;name, RealName, department&quot;\n     labeltag=&quot;dt&quot; wraptag=&quot;dl&quot;\n     break=&quot;dd&quot; /&gt;\n&lt;/txp:smd_bio_author&gt;</code></pre>\n\n<p>Shows the name, real name and department of all <em>Copy Editors</em> (3) and <em>Staff Writers</em> (4) and the user &#8216;mr_pub&#8217;.</p>\n\n<h2 id=\"smd_bio_eg3\">Example 3: Using smd_bio_articles and smd_bio_info as a container</h2>\n\n<pre><code>&lt;txp:smd_bio_info fields=&quot;photo, department, RealName&quot;&gt;\n   &lt;txp:image id=&#39;&lt;txp:smd_bio_data field=&quot;photo&quot; /&gt;&#39; /&gt;\n   Recent articles by\n   &lt;a href=&quot;/desks/&lt;txp:smd_bio_data field=&quot;department&quot; /&gt;&quot;&gt;\n      &lt;txp:smd_bio_data field=&quot;RealName&quot; /&gt;\n   &lt;/a&gt;:\n&lt;/txp:smd_bio_info&gt;\n&lt;txp:smd_bio_articles limit=&quot;6&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n&lt;/txp:smd_bio_articles&gt;</code></pre>\n\n<p>Displays the author photo, the author&#8217;s RealName linked to the section that explains about the department to which she belongs, then lists the 6 most recent articles by her. Note the use of <code>&lt;txp:smd_bio_data /&gt;</code> to feed <code>&lt;txp:image /&gt;</code> with the ID of the selected photo.</p>\n\n<h2 id=\"smd_bio_eg4\">Example 4: checkboxes, lists and radios</h2>\n\n<pre><code>&lt;txp:smd_bio_author=&quot;SMD_PRIVS:5&quot;&gt;\n   &lt;txp:smd_bio_info fields=&quot;name, image, contact_by,\n      subscribed, department&quot;&gt;\n      &lt;a class=&quot;image&quot;\n        href=&quot;/blog/&lt;txp:smd_bio_data field=&quot;name&quot; /&gt;&quot;\n        title=&quot;browse other posts by this author&quot;&gt;\n         &lt;img class=&quot;thumb&quot;\n           src=&quot;/images/&lt;txp:smd_bio_data field=&quot;image&quot; /&gt;.jpg&quot; /&gt;\n      &lt;/a&gt;\n      &lt;div class=&quot;summary&quot;&gt;\n         &lt;h3&gt;Department&lt;/h3&gt;&lt;txp:smd_bio_data field=&quot;department&quot; /&gt;\n         &lt;h3&gt;Bio&lt;/h3&gt;&lt;txp:smd_bio_data field=&quot;profile&quot; /&gt;\n         &lt;h3&gt;Preferred contact method&lt;/h3&gt;&lt;txp:smd_bio_data field=&quot;contact_by&quot; /&gt;\n         &lt;h3&gt;Subscribed to&lt;/h3&gt;&lt;txp:smd_bio_data field=&quot;subscribed&quot; /&gt;\n      &lt;/div&gt;\n   &lt;/txp:smd_bio_info&gt;\n&lt;/txp:smd_bio_author&gt;</code></pre>\n\n<h2 id=\"smd_bio_eg5\">Example 5: telephone directory of users</h2>\n\n<p>If you have some bio fields such as surname, forename, department, phone, avatar, and so forth you could display a quick directory of all your users as follows. The snippet of <span class=\"caps\">PHP</span> is just a quick way of getting the first letter of the surname; you could be far more creative here and link to a full bio or filter by letter, and so on.</p>\n\n<pre><code>&lt;txp:smd_bio_author author=&quot;SMD_ALL&quot; sort=&quot;surname asc&quot;&gt;\n\n   &lt;txp:smd_bio_info wraptag=&quot;dl&quot;&gt;\n      &lt;txp:php&gt;\n         global $variable;\n         $variable[&#39;initial&#39;] = substr(\n            smd_bio_data(array(&#39;field&#39; =&gt; &#39;surname&#39;)),\n            0, 1);\n      &lt;/txp:php&gt;\n\n      &lt;txp:if_different&gt;&lt;dt&gt;&lt;txp:variable name=&quot;initial&quot; /&gt;&lt;/dt&gt;&lt;/txp:if_different&gt;\n\n      &lt;dd class=&quot;name&quot;&gt;\n         &lt;txp:smd_bio_data field=&quot;surname&quot; /&gt;,\n         &lt;txp:smd_bio_data field=&quot;forename&quot; /&gt;\n      &lt;/dd&gt;\n\n      &lt;txp:smd_if_bio field=&quot;department&quot;&gt;\n         &lt;txp:smd_bio_data field=&quot;department&quot;\n            item=&quot;title, value&quot; break=&quot;: &quot;  wraptag=&quot;dd&quot; /&gt;\n      &lt;/txp:smd_if_bio&gt;\n\n      &lt;txp:smd_if_bio field=&quot;phone&quot;&gt;\n         &lt;txp:smd_bio_data field=&quot;phone&quot;\n            item=&quot;title, value&quot; break=&quot;: &quot;  wraptag=&quot;dd&quot; /&gt;\n      &lt;/txp:smd_if_bio&gt;\n   &lt;/txp:smd_bio_info&gt;\n\n&lt;/txp:smd_bio_author&gt;</code></pre>\n\n<h2 id=\"smd_bio_eg6\">Example 6: updating a profile from the public side</h2>\n\n<p>With mem_form installed and some suitable privilege wrapper plugin such as rvm_privileged or cbe_frountauth you can present a public profile for your users to maintain. This example uses <code>&lt;txp:mem_form&gt;</code> with the <code>type=&quot;smd_bio&quot;</code> attribute but the plugin is equally at home within <code>&lt;txp:mem_self_register&gt;</code> (so you can capture extended bio information at sign-up time) or inside <code>&lt;txp:mem_self_user_edit_form&gt;</code>.</p>\n\n<pre><code>&lt;txp:mem_form type=&quot;smd_bio&quot;&gt;\n\n&lt;txp:smd_bio_info show_empty=&quot;1&quot;&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;name&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;RealName&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;email&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;avatar&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;mini_bio&quot; item=&quot;title, value&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;phone_home&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;phone_work&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n   &lt;br /&gt;&lt;txp:smd_bio_data field=&quot;marketing_preference&quot; item=&quot;title, widget&quot; break=&quot;: &quot; /&gt;\n&lt;/txp:smd_bio_info&gt;\n&lt;txp:mem_submit /&gt;\n\n&lt;/txp:mem_form&gt;</code></pre>\n\n<h2>Author / Credits</h2>\n\n<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. The plugin is a logical extension of pvc_users_info by Peter V. Cook (the smd_bio_articles tag is essentially the same as pvc_author_articles). Thanks also to pieman for setting the wheels in motion and net-carver for his inimitable knack of making things better.</p>\n\n<h2>Changelog</h2>\n\n<ul>\n	<li>23 Oct 2014 | 0.41 | Fixed array-to-string conversion in javascript comment (thanks aslsw66)</li>\n	<li>25 Feb 2013 | 0.40 | Improved performance and reduced server load by up to 90% (thanks jakob); plugin lifecycle aware; permitted configurable database column types/sizes and international characters; removed base64 css; added smd_bio_author, smd_bio_data, smd_bio_iterate, smd_if_bio, smd_if_bio_first_author and smd_if_bio_last_author tags; added <code>show_empty</code> attribute; deprecated <code>author</code>, <code>items</code> (now <code>fields</code>), <code>labelclass</code>, <code>itemwraptag</code> (now <code>break</code>) and <code>itemclass</code> (now <code>breakclass</code>); altered replacement key names; fixed and improved hover tooltips on <em>Admin-&gt;Users</em> tab; increased default varchar size to 255 (thanks hablablow); added multi-select lists and permitted checkboxes to be marked as default; added more field types for <span class=\"caps\">HTML</span> 5 widgets; enabled <code>SMD_BIO_FN|function|param|param|...</code> support when defining fields to call arbitrary functions; experimental support for item=&#8220;widget&#8221; to display an input control for the given field</li>\n	<li>08 Jun 2010 | 0.31 | Javascript only appears on admin tab (thanks redbot/Gocom)</li>\n	<li>31 Aug 2009 | 0.30 | Removed <code>item</code> attribute; fixed warning message if using single items; hidden pref <code>smd_bio_sanitize_name</code> forces sanitized login names</li>\n	<li>21 Aug 2009 | 0.20 | First public release; no image/thumb output; experimental <code>options</code> attribute removed; container/form accepts Txp tags; fixed textbox size limit (thanks MattD)</li>\n	<li>14 Jul 2009 | 0.10 | Initial (non-public) release</li>\n</ul>', '/**\n * smd_bio\n *\n * A Textpattern CMS plugin for storing additional biographical author data:\n *  -> Create checkboxes, select lists, radio buttons, text input, image, etc fields\n *  -> Data is captured and stored when a user is created\n *  -> Allow people to manage their own profiles via smd_user_manager\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n *\n * @todo Stop smd_bio_iterate from trashing the bio data for future fields of the same name.\n * @todo Attempt table repair if bio/bio_meta get out of sync.\n * @todo Ability to specify wildcards/matches for fields/authors in the client tag.\n * @todo Specify tooltip extended bio information in prefs (cf. what to do about touch devices).\n */\nif (txpinterface === \'admin\') {\n    add_privs(\'smd_bio\', \'1\');\n    register_tab(\'extensions\', \'smd_bio\', gTxt(\'smd_bio_admin_tab\'));\n    register_callback(\'smd_bio_dispatcher\', \'smd_bio\');\n    register_callback(\'smd_bio_fields\', \'author_ui\', \'extend_detail_form\');\n    register_callback(\'smd_bio_admin_js\', \'admin_side\', \'head_end\');\n\n    // Note these are all pre Txp\'s involvement\n    register_callback(\'smd_bio_save\', \'admin\', \'author_save\', 1);\n    register_callback(\'smd_bio_save\', \'admin\', \'author_save_new\', 1);\n    register_callback(\'smd_bio_delete\', \'admin\', \'admin_multi_edit\', 1);\n    register_callback(\'smd_bio_welcome\', \'plugin_lifecycle.smd_bio\');\n\n    // Doesn\'t hurt much to add these if the plugin\'s not installed.\n    // Since plugins are loaded in load_order and then alphabetical,\n    // at this point smd_user_manager doesn\'t \'exist\' (b < u)\n    register_callback(\'smd_bio_save\', \'smd_um\', \'smd_um_save\', 1);\n    register_callback(\'smd_bio_save\', \'smd_um\', \'smd_um_save_new\', 1);\n    register_callback(\'smd_bio_delete\', \'smd_um\', \'smd_um_multi_edit\', 1);\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_bio_author\')\n            ->register(\'smd_bio_info\')\n            ->register(\'smd_bio_data\')\n            ->register(\'smd_bio_iterate\')\n            ->register(\'smd_bio_articles\')\n            ->register(\'smd_if_bio\')\n            ->register(\'smd_if_bio_is\')\n            ->register(\'smd_if_bio_first_author\')\n            ->register(\'smd_if_bio_last_author\');\n    }\n}\n\nregister_callback(\'smd_bio_form_submit\', \'mem_form.submit\');\n\n// Intercept image and extended bio display on Admin->Users panel\n$smd_bio_step = gps(\'smd_bio_step\');\nif ($smd_bio_step == \'smd_bio_get_image\') {\n    smd_bio_get_image();\n}\nif ($smd_bio_step == \'smd_bio_get_ebio\') {\n    smd_bio_get_ebio();\n}\n\nif (!defined(\'SMD_BIO\')) {\n    define(\"SMD_BIO\", \'smd_bio\');\n}\nif (!defined(\'SMD_BIO_META\')) {\n    define(\"SMD_BIO_META\", \'smd_bio_meta\');\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_get_styles() {\n    $smd_bio_styles = array(\n        \'meta\' =>\n         \'.smd_bio_toggler { display:none; }\',\n        \'tooltip\' =>\n         \'#tooltip { position:absolute; border:1px solid #333; background:#f7f5d1; padding:10px 15px; opacity:.9; color:#333; display:none; max-width:60%; }\',\n    );\n    return $smd_bio_styles;\n}\n\n// -------------------------------------------------------------\n// Install/uninstall jumpoff point\nfunction smd_bio_welcome($evt, $stp) {\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_bio_table_install(0);\n            $msg = \'Pimp your users\';\n            break;\n        case \'deleted\':\n            smd_bio_table_remove(0);\n            break;\n    }\n    return $msg;\n}\n\n// ************************\n// BIO CONFIGURATION\n// ------------------------\nfunction smd_bio_dispatcher($evt, $stp) {\n    $available_steps = array(\n        \'smd_bio_config\'          => false,\n        \'smd_bio_table_install\'   => false,\n        \'smd_bio_table_remove\'    => false,\n        \'smd_bio_meta_add\'        => true,\n        \'smd_bio_multi_edit\'      => true,\n        \'smd_bio_meta_save\'       => true,\n        \'smd_bio_save_pane_state\' => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = \'smd_bio_config\';\n    }\n    $stp();\n}\n\n// ------------------------\n// The Extensions->Bio config panel, made up of two areas: the edit pane and the list pane\nfunction smd_bio_config($msg=\'\') {\n    smd_bio_table_install(0);\n\n    pagetop(gTxt(\'smd_bio_admin_tab\'), $msg);\n\n    echo n.\'<div id=\"smd_bio_container\" class=\"txp-container\">\'.\n        n.smd_bio_meta_edit().\n        n.smd_bio_meta_list().\n        n.\'</div>\';\n}\n\n// ------------------------\nfunction smd_bio_meta_edit() {\n    $smd_bio_types = smd_bio_get_types();\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n\n    $vars = array(\'step\', \'id\', \'title\', \'name\', \'type\', \'coltype\', \'colsize\', \'size\', \'val\', \'position\');\n    $rs = array();\n\n    extract(gpsa($vars));\n    $colsize = (int)$colsize;\n\n    if ($id && $step == \'meta_edit\') {\n        $id = assert_int($id);\n        $rs = safe_row(\'*\', SMD_BIO_META, \"id = $id\");\n        extract($rs);\n    }\n\n    if ($step == \'smd_bio_meta_save\' || $step == \'smd_bio_meta_add\' || $step == \'smd_bio_multi_edit\') {\n        foreach ($vars as $var) {\n            $$var = \'\';\n        }\n    }\n\n    $caption = gTxt(($step == \'meta_edit\') ? \'smd_bio_meta_edit\' : \'smd_bio_meta_add\');\n\n    // Make the name/val pairs for the type selectInput\n    $selv = array();\n    foreach ($smd_bio_types as $widx => $wval) {\n        $selv[$widx] = $wval[\'name\'];\n    }\n\n    // Make the name/val pairs for the coltype selectInput\n    foreach($smd_bio_coltypes as $ctype => $cdata) {\n        $coltypes[$ctype] = $cdata[\'title\'];\n    }\n\n    $toggleState = get_pref(\'pane_smd_bio_coltype_visible\') ? true : false;\n\n    return hed($caption, 1, \' class=\"txp-heading\"\').\n        form(\n            \'<div class=\"txp-edit\">\'.\n            inputLabel(\'name\', ($id && $step == \'meta_edit\' ? strong($name) : fInput(\'text\', \'name\', $name, \'\', \'\', \'\', \'\', \'\', \'name\')), \'name\').\n            inputLabel(\'title\', fInput(\'text\', \'title\', $title, \'\', \'\', \'\', \'\', \'\', \'title\'), \'title\').\n            inputLabel(\'smd_bio_widget_type\', selectInput(\'type\', $selv, $type, false, \'\', \'smd_bio_widget_type\') .sp. \'<a id=\"smd_bio_colgroup\" class=\"txp-summary lever\'.(($toggleState) ? \' expanded\' : \'\').\'\" href=\"#\">\'.gTxt(\'smd_bio_more\').\'</a>\', \'type\').\n            inputLabel(\'smd_bio_coltype\', selectInput(\'coltype\', $coltypes, $coltype, false, \'\', \'smd_bio_coltype\'), \'smd_bio_coltype\', \'\', \'txp-form-field smd_bio_coltype \'.(($toggleState) ? \'\' : \' smd_bio_toggler\')).\n            inputLabel(\'smd_bio_colsize\', fInput(\'number\', \'colsize\', $colsize, \'\', \'\', \'\', \'\', \'\', \'smd_bio_colsize\'), \'smd_bio_colsize\', \'\', \'txp-form-field smd_bio_coltype \'.(($toggleState) ? \'\' : \' smd_bio_toggler\')).\n            inputLabel(\'smd_bio_size\', fInput(\'text\', \'size\', $size, \'\', \'\', \'\', \'\', \'\', \'smd_bio_size\'), \'smd_bio_size\', \'smd_bio_size\').\n            inputLabel(\'smd_bio_value\', text_area(\'val\', \'100\', \'300\', $val, \'smd_bio_value\'), \'smd_bio_value\', \'smd_bio_val\').\n            inputLabel(\'smd_bio_position\', fInput(\'text\', \'position\', $position, \'\', \'\', \'\', \'\', \'\', \'smd_bio_position\'), \'smd_bio_position\').\n            graf(\n                fInput(\'submit\', \'save\', gTxt(\'save\'), \'publish\')\n            ).\n\n            eInput(\'smd_bio\').\n            ($id ? hInput(\'id\', $id).hInput(\'name\', $name).sInput(\'smd_bio_meta_save\') : sInput(\'smd_bio_meta_add\')).\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_text\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_number\" title=\"\'.gTxt(\'smd_bio_sizehelp_numrange\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_range\" title=\"\'.gTxt(\'smd_bio_sizehelp_numrange\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_list\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_multilist\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_radio\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_yesnoradio\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_checkbox\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_textarea\" title=\"\'.gTxt(\'smd_bio_sizehelp_textarea\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_image\" title=\"\'.gTxt(\'smd_bio_sizehelp_image\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_email\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_url\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_datetime\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_date\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_month\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_week\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_time\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_text\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_number\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_range\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_list\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_multilist\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_radio\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_yesnoradio\" title=\"\'.gTxt(\'smd_bio_valhelp_ynr\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_checkbox\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_textarea\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_image\" title=\"\'.gTxt(\'smd_bio_valhelp_image\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_email\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_url\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_datetime\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_date\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_month\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_week\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_time\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n        \'</div>\'\n    );\n}\n\n// ------------------------\nfunction smd_bio_meta_list() {\n\n    $smd_bio_types = smd_bio_get_types();\n\n    extract(gpsa(array(\'sort\', \'dir\', \'crit\', \'search_method\')));\n    if ($sort === \'\') $sort = get_pref(\'smd_bio_meta_sort_column\', \'name\');\n    if ($dir === \'\') $dir = get_pref(\'smd_bio_meta_sort_dir\', \'asc\');\n    $dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n    if (!in_array($sort, array(\'name\', \'title\', \'type\', \'size\', \'val\', \'position\'))) $sort = \'position\';\n\n    $sort_sql   = $sort.\' \'.$dir;\n\n    set_pref(\'smd_bio_meta_sort_column\', $sort, \'smd_bio\', 2, \'\', 0, PREF_PRIVATE);\n    set_pref(\'smd_bio_meta_sort_dir\', $dir, \'smd_bio\', 2, \'\', 0, PREF_PRIVATE);\n\n    $switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n    $rs = safe_rows_start(\'*\', SMD_BIO_META, \'1=1 ORDER BY \'.$sort_sql);\n    $out = array();\n\n    if ($rs) {\n        $out[] =\n        n. \'<form id=\"smd_bio_form\" action=\"index.php\" method=\"post\" name=\"longform\" class=\"multi_edit_form\">\'.\n\n        n.\'<div class=\"txp-listtables\">\'.\n        startTable(\'\', \'\', \'txp-list\').\n        n.\'<thead>\'.\n        tr(\n            hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' scope=\"col\" title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\').\n            column_head(\'name\', \'name\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'name\' == $sort) ? $dir : \'\').\n            column_head(\'title\', \'title\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'title\' == $sort) ? $dir : \'\').\n            column_head(\'type\', \'type\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'type\' == $sort) ? $dir : \'\').\n            column_head(gTxt(\'smd_bio_size\'), \'size\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'size\' == $sort) ? $dir : \'\').\n            column_head(\'value\', \'val\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'val\' == $sort) ? $dir : \'\').\n            column_head(gTxt(\'smd_bio_position\'), \'position\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'position\' == $sort) ? $dir : \'\')\n        ).\n        n.\'</thead>\'.\n        n.\'<tbody>\';\n\n        while ($a = nextRow($rs)) {\n            extract(doSpecial($a));\n\n            $out[] = tr(\n                td(fInput(\'checkbox\', \'selected[]\', $name)).\n                td(eLink(\'smd_bio\', \'meta_edit\', \'id\', $id, $name)).\n                td($title).\n                td($smd_bio_types[$type][\'name\']).\n                td($size).\n                td($val).\n                td($position)\n            );\n        }\n\n        $out[] = \'</tbody>\'.\n            endTable().\n            n.\'</div>\'.\n            smd_bio_multiedit_form(\'\', $sort, $dir, $crit, $search_method).\n            tInput().\n            \'</form>\';\n    }\n    return join(n, $out);\n}\n\n// ------------------------\nfunction smd_bio_multiedit_form($page, $sort, $dir, $crit, $search_method) {\n\n    $methods = array(\n        \'delete\' => gTxt(\'delete\'),\n    );\n\n    return multi_edit($methods, \'smd_bio\', \'smd_bio_multi_edit\', $page, $sort, $dir, $crit, $search_method);\n}\n\n// ------------------------\nfunction smd_bio_multi_edit()\n{\n    $selected = ps(\'selected\');\n\n    if (!$selected or !is_array($selected))\n    {\n        smd_bio();\n    }\n\n    $selected = array_map(\'assert_string\', $selected);\n    $method   = ps(\'edit_method\');\n    $changed  = array();\n    $key = \'\';\n\n    switch ($method)\n    {\n        case \'delete\':\n            return smd_bio_meta_del($selected);\n            break;\n\n        default:\n            $key = \'\';\n            $val = \'\';\n            break;\n    }\n\n    smd_bio();\n}\n\n// ------------------------\nfunction smd_bio_meta_make_list($val) {\n\n    if (strpos(strtolower($val), \'smd_bio_fn\') !== false) {\n        // Special function syntax so call the designated\n        // function to retrieve the value(s)\n        $params = do_list($val, \'|\');\n        array_shift($params); // Remove SMD_BIO_FN\n        $fn = array_shift($params);\n\n        $fnbits = do_list($fn, \'::\');\n        $fncall = (isset($fnbits[1])) ? array($fnbits[0], $fnbits[1]) : $fnbits[0];\n        $val = call_user_func_array($fncall, $params);\n\n        if (is_array($val)) {\n            // Fake a string list of name => val pairs\n            $out = array();\n            foreach ($val as $idx => $item) {\n                $out[] = str_replace(\',\', \'&#44;\', $idx . \' => \' . $item); // Preserve commas by encoding them\n            }\n            $val = join(\', \', $out);\n        }\n    }\n\n    // Parse the values\n    $wvals = do_list($val, \'\\r\\n\');\n    if (count($wvals) == 1) {\n        $wvals = do_list($val);\n    }\n    return join(\', \', doArray($wvals, \'trim\'));\n}\n\n// ------------------------\nfunction smd_bio_meta_save() {\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n    $smd_bio_unused = smd_bio_get_unused();\n\n    extract(doSlash(psa(array(\'id\', \'name\', \'title\', \'type\', \'coltype\', \'colsize\', \'size\', \'position\'))));\n    $val = ps(\'val\'); // Defer doSlash() until later\n\n    $id = assert_int($id);\n    $val = doSlash(smd_bio_meta_make_list($val));\n    $hasDefault = $smd_bio_coltypes[$coltype][\'has_default\'];\n\n    // Validate the input to ensure items are emptied for types where they\'re unused\n    foreach ($smd_bio_unused as $unused => $excludes) {\n        if (in_array($type, $excludes)) {\n            $$unused = \'\';\n        }\n    }\n\n    // Validate the input to ensure items that require a column size have one...\n    if (empty($colsize) && $smd_bio_coltypes[$coltype][\'size_req\'] !== false) {\n        $colsize = $smd_bio_coltypes[$coltype][\'size_req\'];\n    }\n\n    // ... and that ones that don\'t require one are removed\n    if ($smd_bio_coltypes[$coltype][\'size_req\'] === false) {\n        $colsize = null;\n    }\n\n    // Try to adjust column type/size if applicable\n    $rs = safe_alter(SMD_BIO, \"CHANGE `$name` `$name` $coltype\" . (($colsize) ? \"($colsize)\" : \'\') . \" NULL\" . (($hasDefault) ? \" DEFAULT NULL\" : \'\'));\n\n    if ($rs) {\n        $rs = safe_update(SMD_BIO_META, \"\n            title = \'$title\',\n            type = \'$type\',\n            size = \'$size\',\n            coltype = \'$coltype\',\n            \" . (($colsize) ? \"colsize = $colsize,\" : \'\') . \"\n            val = \'$val\',\n            position = \'$position\'\",\n            \"id = $id\"\n        );\n\n        if ($rs) {\n            $msg = gTxt(\'smd_bio_meta_updated\', array(\'{name}\' => $name));\n        } else {\n            $msg = array(gTxt(\'smd_bio_meta_update_partial\', array(\'{name}\' => $name)), E_WARNING);\n        }\n    } else {\n        $msg = array(gTxt(\'smd_bio_meta_update_failed\', array(\'{name}\' => $name)), E_WARNING);\n    }\n\n    smd_bio_config($msg);\n}\n\n// ------------------------\nfunction smd_bio_meta_add() {\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n    $smd_bio_unused = smd_bio_get_unused();\n\n    extract(doSlash(psa(array(\'name\', \'title\', \'type\', \'coltype\', \'colsize\', \'size\', \'position\'))));\n    $val = ps(\'val\'); // Defer doSlash() until later\n\n    // Use the title as name if it\'s omitted\n    if ($name === \'\' && $title !== \'\') {\n        $name = $title;\n    }\n\n    $name = smd_bio_sanitize($name);\n\n    if (!empty($name) && smd_bio_meta_check($name)) {\n        $size = (empty($size)) ? 25 : $size;\n        $hasDefault = $smd_bio_coltypes[$coltype][\'has_default\'];\n\n        // Validate the input to ensure items are emptied for types where they\'re unused\n        foreach ($smd_bio_unused as $unused => $excludes) {\n            if (in_array($type, $excludes)) {\n                $$unused = \'\';\n            }\n        }\n\n        // Validate the input to ensure items that require a column size have one...\n        if (empty($colsize) && $smd_bio_coltypes[$coltype][\'size_req\'] !== false) {\n            $colsize = $smd_bio_coltypes[$coltype][\'size_req\'];\n        }\n        // ... and that ones that don\'t require one are removed\n        if ($smd_bio_coltypes[$coltype][\'size_req\'] === false) {\n            $colsize = null;\n        }\n\n        $ret = safe_alter(SMD_BIO, \"ADD `$name` $coltype\" . (($colsize) ? \"($colsize)\" : \'\') . \" NULL\" . (($hasDefault) ? \" DEFAULT NULL\" : \'\'));\n        $val = doSlash(smd_bio_meta_make_list($val));\n\n        if ($ret) {\n            $rs = safe_insert(SMD_BIO_META, \"\n                name = \'$name\',\n                title = \'$title\',\n                type = \'$type\',\n                size = \'$size\',\n                coltype = \'$coltype\',\n                \" . (($colsize) ? \"colsize = $colsize,\" : \'\') . \"\n                val = \'$val\',\n                position = \'$position\'\n            \");\n\n            if ($rs) {\n                smd_bio_config(gTxt(\'smd_bio_meta_added\'));\n                return;\n            }\n        }\n    }\n\n    smd_bio_config(array(gTxt(\'smd_bio_meta_not_added\'), E_ERROR));\n}\n\n// ------------------------\nfunction smd_bio_meta_del($names) {\n\n    $changed = array();\n\n    $names  = $names ? array_map(\'assert_string\', $names) : array(assert_string(ps(\'name\')));\n    $message = \'\';\n\n    foreach ($names as $name) {\n        $exists = smd_bio_meta_check($name);\n        $ret = @safe_alter(SMD_BIO, \"DROP COLUMN `$name`\");\n        if ($ret || $exists) {\n            $ret = safe_delete(SMD_BIO_META, \"name=\'$name\'\");\n            $changed[] = $name;\n        }\n    }\n    smd_bio_config(gTxt(\'smd_bio_meta_deleted\', array(\'{affected}\' => join(\', \', $changed))));\n}\n\n// ------------------------\nfunction smd_bio_meta_check($col) {\n    $ucols = getThings(\'describe `\'.PFX.\'txp_users`\');\n    $bcols = getThings(\'describe `\'.PFX.SMD_BIO.\'`\');\n    $cols = array_merge($ucols, $bcols);\n    return (!in_array($col, $cols));\n}\n\n// ************************\n// ADMIN -> USERS PANEL\n// ------------------------\nfunction smd_bio_fields($evt, $stp, $mt, $data) {\n    global $smd_um_event, $txp_user;\n\n    $out = $vals = array();\n\n    $rstep = gps(\'step\');\n    if(smd_bio_table_exist()) {\n        if (in_array($rstep, array(\'\', \'author_edit\', \'smd_um_edit\', \'smd_um_save\', \'smd_um_change_pass\'))) {\n            extract(gpsa(array(\'user_id\')));\n\n            if (empty($user_id) && !has_privs(\'smd_um.usr.create\')) {\n                // This is a self-edit from smd_user_manager, thus the user_id has not been sent\n                $user_id = safe_field(\'user_id\',\'txp_users\',\"name = \'\".doSlash($txp_user).\"\'\");\n            }\n            // Shame we have to double de-clutch here but we can\'t index on user_id unfortunately (\'cos when\n            // inserting new users, the bio functions run _BEFORE_ an auto_increment ID has been generated by Txp)\n            $uname = safe_field(\'name\',\'txp_users\',\"user_id = \'\".doSlash($user_id).\"\'\");\n            $vals = safe_row(\'*\', SMD_BIO, \"user_ref=\'\".doSlash($uname).\"\'\");\n        }\n\n        $widgets = safe_rows(\'*\', SMD_BIO_META, \'1=1 ORDER BY position\');\n        $tdac_image = \' class=\"smd_bio_image_cell\"\';\n        foreach ($widgets as $widget) {\n            $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : $widget[\'val\'];\n            $title = ($widget[\'title\']) ? $widget[\'title\'] : $widget[\'name\'];\n            $sizeopts = do_list($widget[\'size\']);\n            $size1 = $sizeopts[0];\n            $size2 = (isset($sizeopts[1])) ? $sizeopts[1] : \'\';\n            $size3 = (isset($sizeopts[2])) ? $sizeopts[2] : \'\';\n            $name = \'smd_bio_\'.$widget[\'name\'];\n            switch($widget[\'type\']) {\n                case \'list\':\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $out[] = inputLabel(\n                        $name,\n                        selectInput($name, $selv, $dflt, false, \'\', $name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_select \'.$name\n                    );\n                    break;\n                case \'multilist\':\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $use_val = (isset($vals[$widget[\'name\']]) && ($vals[$widget[\'name\']] !== \'\' || $vals[$widget[\'name\']] !== null)) ? $val : $dflt; // Don\'t use defaults if this field has been previously saved\n                    $selectedVals = do_list($use_val);\n                    $items = array();\n                    $items[] = \'<select name=\"\'.$name.\'\" class=\"list multiple\" multiple=\"multiple\" onchange=\"smd_bio_multisel(\\\'\'.$name.\'\\\');\">\';\n                    foreach ($selv as $idx => $lbl) {\n                        // Not using selectInput() because it doesn\'t support multiples\n                        $items[] = \'<option value=\"ms_\'.$idx.\'\" \'.((in_array($idx, $selectedVals)) ? \' selected=\"selected\"\' : \'\') . \'>\' . $lbl . \'</option>\';\n                    }\n                    $items[] = \'</select>\';\n                    $out[] = inputLabel(\n                        $name,\n                        join(n,$items).fInput(\'hidden\',$name,$use_val,\'\',\'\',\'\',\'\',\'\',$name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_select \'.$name\n                    );\n                    break;\n                case \'radio\':\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $out[] = inputLabel(\n                        $name,\n                        radioSet($selv, $name, $dflt),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_radio \'.$name\n                    );\n                    break;\n                case \'yesnoradio\':\n                    $out[] = inputLabel(\n                        $name,\n                        yesnoRadio($name, $val),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_radio \'.$name\n                    );\n                    break;\n                case \'checkbox\':\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $use_val = (isset($vals[$widget[\'name\']]) && ($vals[$widget[\'name\']] !== \'\' || $vals[$widget[\'name\']] !== null)) ? $val : $dflt; // Don\'t use defaults if this field has been previously saved\n                    $checkedVals = do_list($use_val);\n                    $items = array();\n                    foreach ($selv as $idx => $lbl) {\n                        // Not using checkbox() because it doesn\'t support onclick in 4.5.x\n                        $items[] = \'<input type=\"checkbox\" name=\"cb_\'.$name.\'\" value=\"\'.$idx.\'\"\'.((in_array($idx, $checkedVals)) ? \' checked=\"checked\"\' : \'\') . \' class=\"checkbox\" onclick=\"smd_bio_checkbox(\\\'\'.$name.\'\\\');\" />\'.$lbl;\n                    }\n                    $out[] = inputLabel(\n                        $name,\n                        join(\'\', $items).fInput(\'hidden\',$name,$use_val,\'\',\'\',\'\',\'\',\'\',$name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_checkbox \'.$name\n                    );\n                    break;\n                case \'textarea\':\n                    // Not using text_area() because it doesn\'t have class attribute in 4.5.x\n                    $size1 = ($size1 == \'\' || $size1 == 0) ? 40 : $size1;\n                    $size2 = ($size2 == \'\' || $size2 == 0) ? 5 : $size2;\n                    $out[] = inputLabel(\n                        $name,\n                        \'<textarea id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"smd_bio_textarea\" cols=\"\'.$size1.\'\" rows=\"\'.$size2.\'\">\'.txpspecialchars($val).\'</textarea>\',\n                        $title,\n                        \'\',\n                        \'txp-form-field txp-form-field-textarea smd_bio_textarea \'.$name\n                    );\n                    break;\n                case \'image\':\n                    $parent = $widget[\'val\'];\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $where = ($parent) ? \"category=\'\".doSlash($parent).\"\'\" : \'1=1\';\n                    $tree = safe_rows(\'*\', \'txp_image\', $where. \' ORDER BY name\');\n                    $selv = array();\n                    foreach ($tree as $row) {\n                        $selv[$row[\'id\']] = $row[\'name\'];\n                    }\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"text\" value=\"\'.txpspecialchars($val).\'\" id=\"\'.$name.\'\" name=\"\'.$name.\'\" size=\'.INPUT_XSMALL.\' class=\"smd_bio_image_id input_xsmall\" />\'.selectInput($name.\'_list\', $selv, $val, true, \'\').\'<span class=\"smd_bio_image\"></span><span class=\"smd_bio_image_data\" title=\"\'.(($size1) ? $size1 : \'\').\',\'.(($size2) ? $size2 : $size1).\'\"></span>\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_image_cell \'.$name\n                    );\n                    break;\n                case \'number\':\n                case \'range\':\n                    $min = ($size1 == \'\') ? \'\' : \" min={$size1}\";\n                    $max = ($size2 == \'\') ? \'\' : \" max={$size2}\";\n                    $jmp = ($size3 == \'\') ? \'\' : \" step={$size3}\";\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"\'.$widget[\'type\'].\'\" value=\"\'.txpspecialchars($val).\'\" name=\"\'.$name.\'\" id=\"\'.$name.\'\"\'.$min.$max.$jmp.\'\" class=\"smd_bio_\'.$widget[\'type\'].\'\" />\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_\'.$widget[\'type\'].\' \'.$name\n                    );\n                    break;\n                case \'date\':\n                case \'month\':\n                case \'week\':\n                case \'time\':\n                case \'datetime\':\n                case \'email\':\n                case \'url\':\n                case \'text\':\n                    // Not using fInput() because it has no MAXLENGTH property and doesn\'t support HTML 5 elements\n                    $size1 = ($size1 == \'\' || $size1 == 0) ? 25 : $size1;\n                    $size2 = ($size2 == \'\' || $size2 == 0) ? $size1 : $size2;\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"\'.$widget[\'type\'].\'\" value=\"\'.txpspecialchars($val).\'\" name=\"\'.$name.\'\" id=\"\'.$name.\'\" size=\"\'.$size1.\'\" maxlength=\"\'.$size2.\'\" class=\"smd_bio_\'.$widget[\'type\'].\'\" />\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_\'.$widget[\'type\'].\' \'.$name\n                    );\n                    break;\n            }\n        }\n    }\n    return join(\'\', $out);\n}\n\n// ------------------------\n// Read a name/val array and remove any [*] marker which indicates a default item\nfunction smd_bio_get_default($list, $curr) {\n    $out = array();\n    $dflt = \'\';\n    $new_dflt = array();\n    foreach ($list as $item => $value) {\n        $value = str_replace(\'&#44;\', \',\', $value); // Revert encoded commas to real literals @see smd_bio_meta_make_list\n        if (($pos = strpos($value, \'[*]\')) !== false) {\n            $out[$item] = substr($value, 0, $pos);\n            $new_dflt[] = $item;\n        } else {\n            $out[$item] = $value;\n        }\n        if ($item == $curr) {\n            $dflt = $item;\n        }\n    }\n    $dflt = ($dflt) ? $dflt : join(\',\', $new_dflt);\n\n    return array($out, $dflt);\n}\n\n// ------------------------\n// AJAX calls\nfunction smd_bio_get_image() {\n    global $img_dir;\n\n    $id = gps(\'id\');\n    if ($id) {\n        $rs = safe_row(\'*\', \'txp_image\', \"id = \'\".doSlash($id).\"\'\");\n        extract($rs);\n        $out = array();\n        $out[\'thumb\'] = ($thumbnail) ? hu.$img_dir.\'/\'.$id.\'t\'.$ext : \'\';\n        $out[\'image\'] = hu.$img_dir.\'/\'.$id.$ext;\n        $out[\'w\'] = $w;\n        $out[\'h\'] = $h;\n        $out[\'thw\'] = ($thumb_w) ? $thumb_w : \'\';\n        $out[\'thh\'] = ($thumb_h) ? $thumb_h : \'\';\n        send_xml_response($out);\n    } else {\n        send_xml_response();\n    }\n    exit;\n}\n\n// ------------------------\n// IMPORTANT: constants NOT used for table names\nfunction smd_bio_get_ebio() {\n    include_once txpath.\'/publish/taghandlers.php\';\n\n    $id = gps(\'id\');\n    $core = array(\'user_ref\');\n    $rs = safe_row(\'*\', \'smd_bio\', \"user_ref = \'\".doSlash($id).\"\'\");\n    $meta = safe_rows(\'*\', \'smd_bio_meta\', \"1=1\");\n//  $include = array_merge($core, array(\'mug\', \'cell\', \'department\')); // TODO: get these from prefs/meta table\n    $allowed_types = array(\'text\', \'textarea\', \'email\', \'url\', \'date\', \'month\', \'week\', \'time\', \'datetime\', \'number\', \'range\');\n    $out = array();\n    foreach ($rs as $idx => $val) {\n//      if (in_array($idx, $include)) {\n            if (in_array($idx, $core)) {\n                $out[$idx] = doSlash($val);\n            } else {\n                foreach($meta as $row) {\n                    if ($row[\'name\'] == $idx) {\n                        if ($row[\'type\'] == \"image\") {\n                            // Crude str_replace() to remove javascript-breaking single quotes\n                            $out[$idx] = \'smd_image::\'.str_replace(\"\'\", \'\', thumbnail(array(\'id\'=> $val)));\n                        } else if (in_array($row[\'type\'], $allowed_types)) {\n                            $out[$idx] = $row[\'title\'].\'::\'.txpspecialchars(strip_tags($val), ENT_QUOTES);\n                        }\n                        break;\n                    }\n                }\n            }\n//      }\n    }\n    send_xml_response($out);\n    exit;\n}\n\n// Inject admin-side javascript\n// ------------------------\nfunction smd_bio_admin_js($evt, $stp) {\n    global $event, $step;\n\n    $smd_bio_styles = smd_bio_get_styles();\n\n    $runon = array(\n        \'admin\' => array(\n            \'evt\' => array(\'admin\', \'smd_um\'),\n            \'stp\' => array(\'\', \'smd_um\', \'smd_um_edit\', \'smd_um_edit\', \'smd_um_save\', \'smd_um_save_new\', \'smd_um_change_pass\', \'author_edit\', \'author_save\', \'author_save_new\'),\n        ),\n        \'bio\' => array(\n            \'evt\' => array(\'smd_bio\'),\n        ),\n    );\n\n    // ********\n    // js+css for Admin->Users tab\n    // ********\n    if (in_array($event, $runon[\'admin\'][\'evt\']) !== false && in_array($step, $runon[\'admin\'][\'stp\']) !== false) {\n        $css_custom = safe_field(\'css\', \'txp_css\', \"name=\'smd_bio\'\");\n        $css = \'<style type=\"text/css\">\' . $css_custom .n. $smd_bio_styles[\'tooltip\'] . \'</style>\';\n\n    echo <<<EOJS\n<script type=\"text/javascript\">\n//<![CDATA[\n\n// Concatenate checkbox options for storage\nfunction smd_bio_checkbox(dest) {\n    var out = [];\n    jQuery(\"#user_edit :checkbox\").each(function() {\n        var item = jQuery(this);\n        if (item.attr(\'name\').replace(\'cb_\',\'\') == dest) {\n            if (item.prop(\'checked\') == true) {\n                out.push(item.val());\n            }\n        }\n    });\n    jQuery(\'#\'+dest).val(out.join(\',\'));\n}\n// Concatenate multi select list options for storage\nfunction smd_bio_multisel(dest) {\n    var out = [];\n    jQuery(\"#user_edit select.multiple\").each(function() {\n        var item = jQuery(this);\n        if (item.attr(\'name\') == dest) {\n            // You\'re the one that I want, ooh ooh oooohhh\n            jQuery(item).children(\":selected\").each(function() {\n                out.push(jQuery(this).val().replace(\'ms_\',\'\'));\n            });\n        }\n    });\n    jQuery(\'#\'+dest).val(out.join(\',\'));\n}\njQuery(function() {\n    // Grab images from the server when the select/textbox change\n    jQuery(\".smd_bio_image_id\").blur(function() {\n        id = jQuery(this).attr(\'id\');\n        val = jQuery(this).val();\n        smd_bio_get_image(id, val);\n    }).blur();\n    jQuery(\".smd_bio_image_cell select\").change(function() {\n        id = jQuery(this).parent().find(\'.smd_bio_image_id\').attr(\'id\');\n        val = jQuery(this).val();\n        smd_bio_get_image(id, val);\n    });\n    function smd_bio_get_image(id, val) {\n        jQuery(\"#\" + id + \" ~ select option[value=\'\"+val+\"\']\").prop(\"selected\", true);\n        jQuery(\"#\" + id).val(val);\n        var dims = jQuery(\"#\" + id + \" ~ .smd_bio_image_data\").attr(\'title\');\n        var size = new Array();\n        if (dims) {\n            size = dims.split(\",\");\n        }\n        sendAsyncEvent({\n                event: textpattern.event,\n                smd_bio_step: \'smd_bio_get_image\',\n                id: val\n            }, function(data) {\n                data = jQuery(data);\n                if (data) {\n                    var full = 0;\n                    var imgLink = data.find(\'thumb\').attr(\'value\');\n                    if (imgLink == \'\') {\n                        full = 1;\n                        var imgLink = data.find(\'image\').attr(\'value\');\n                    }\n                    if (imgLink) {\n                        if (size[0] == \'\') {\n                            if (full == 1) {\n                                size[0] = data.find(\'w\').attr(\'value\');\n                            } else {\n                                size[0] = data.find(\'thw\').attr(\'value\');\n                            }\n                        }\n                        if (size[1] == \'\') {\n                            if (full == 1) {\n                                size[1] = data.find(\'h\').attr(\'value\');\n                            } else {\n                                size[1] = data.find(\'thh\').attr(\'value\');\n                            }\n                        }\n                        jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeIn().html(\'<img src=\"\'+imgLink+\'\" width=\"\'+size[0]+\'\" height=\"\'+size[1]+\'\" />\');\n                    } else {\n                        jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeOut().empty();\n                    }\n                } else {\n                    jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeOut().empty();\n                }\n            }\n        );\n    }\n\n    // Grab the extended info when hovering an author in the list\n    jQuery(\"#users_form tbody tr, #smd_um_form tbody tr\").hover(function(e) {\n        var tt = \'\';\n        var row = jQuery(this);\n        var hovItem = row.find(\".login-name\");\n\n        if (row.data(\'tooltip\') == undefined) {\n            var person = row.find(\".login-name\").text();\n\n            sendAsyncEvent({\n                    event: textpattern.event,\n                    smd_bio_step: \'smd_bio_get_ebio\',\n                    id: person\n                }, function(data) {\n                    data = jQuery(data);\n                        var entry = data.find(\'user_ref\');\n                        out = \'\';\n                        entry.nextAll().each(function(item) {\n                            node = jQuery(this).context.nodeName;\n                            if (node != \'http-status\') {\n                                vall = jQuery(this).attr(\'value\');\n                                if (vall) {\n                                    vsplit = vall.split(\'::\');\n                                    if (vsplit[0].indexOf(\'smd_image\') < 0) {\n                                        out += ((vsplit.length>1) ? vsplit[0] : node) + \': \';\n                                    }\n                                    vall = ((vsplit.length>1) ? vsplit[1] : vsplit[0]);\n                                    out += vall + \'<br/>\';\n                                } else {\n                                    out += node+\': \';\n                                }\n                            }\n                        });\n                        row.data(\'tooltip\', out);\n//                      hovItem.trigger(\'mouseover\'); // Trigger the hover state when the data is loaded\n                    }\n            );\n        }\n\n        xOffset = 30;\n        yOffset = 25;\n        hovItem.hover(function(e) {\n            var tt = jQuery(this).parent().data(\'tooltip\');\n            if (tt != \'\') {\n                jQuery(\"body\").append(\"<p id=\'tooltip\'>\"+ tt +\"</p>\");\n                jQuery(\"#tooltip\")\n                    .css(\"top\",(e.pageY - xOffset) + \"px\")\n                    .css(\"left\",(e.pageX + yOffset) + \"px\")\n                    .fadeIn(\"fast\");\n            }\n        },\n        function() {\n            jQuery(\"#tooltip\").remove();\n        });\n        hovItem.mousemove(function(e) {\n            jQuery(\"#tooltip\")\n                .css(\"top\",(e.pageY - xOffset) + \"px\")\n                .css(\"left\",(e.pageX + yOffset) + \"px\");\n        });\n    });\n});\n//]]>\n</script>\n{$css}\nEOJS;\n    }\n\n    // ********\n    // js for Extensions->Bio config tab\n    // ********\n    if (in_array($event, $runon[\'bio\'][\'evt\'])) {\n        $smd_bio_types = smd_bio_get_types();\n        $smd_bio_unused = smd_bio_get_unused();\n\n        $css = \'<style type=\"text/css\">\' . $smd_bio_styles[\'meta\'] . \'</style>\';\n        $js_unused = join(\',\', doArray($smd_bio_unused[\'size\'], \'doQuote\'));\n\n        foreach ($smd_bio_types as $type => $data) {\n            $type_json[] = \'smd_bio_types[\"\'.$type.\'\"] = { \"dflt_type\": \"\'.$data[\'coltype\'].\'\", \"dflt_size\": \"\'.$data[\'colsize\'].\'\", \"fixed\": \"\'.$data[\'fixed\'].\'\" };\';\n        }\n        $type_json = join(n, $type_json);\n\n        echo <<<EOJS\n<script type=\"text/javascript\">\n//<![CDATA[\nvar smd_bio_unused = [{$js_unused}];\nvar smd_bio_types = [];\nvar destColtype = \"#page-smd_bio select[name=\'coltype\']\";\nvar destColsize = \"#page-smd_bio input[name=\'colsize\']\";\n{$type_json}\n\njQuery(function() {\n    // Perform actions when the Type is changed\n    // Action #1: size box. Auto-update this when Edit clicked as well as when select list is altered\n    jQuery(\"#smd_bio_widget_type\").change(function() {\n        var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n        var destSize = \"#page-smd_bio input[name=\'size\']\";\n\n        // Grey out the Size box for those items that don\'t use it\n        if (jQuery.inArray(theType, smd_bio_unused) > -1) {\n            jQuery(destSize).prop(\"disabled\", true);\n            jQuery(destSize).parent().prev().css(\"color\", \'#999\');\n        } else {\n            jQuery(destSize).prop(\"disabled\", false);\n            jQuery(destSize).parent().prev().css(\"color\", \'\');\n        }\n    }).change();\n\n    // Action #2: coltype/colsize. Can\'t amalgamate these with the \'size\' onchange because\n    // this only changes when select list changed (i.e. not when Edit first clicked)\n    jQuery(\"#smd_bio_widget_type\").change(function() {\n        var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n\n        // Preselect the coltype + colsize based on the defaults\n        jQuery(destColtype + \" option[value=\'\"+smd_bio_types[theType].dflt_type+\"\']\").prop(\'selected\', true);\n        jQuery(destColsize).val(smd_bio_types[theType].dflt_size);\n\n        if (smd_bio_types[theType].fixed == \'\') {\n            jQuery(destColtype).prop(\"disabled\", false);\n            jQuery(destColsize).prop(\"disabled\", false);\n            jQuery(destColtype).parent().prev().css(\"color\", \'\');\n            jQuery(destColsize).parent().prev().css(\"color\", \'\');\n        } else {\n            jQuery(destColtype).prop(\"disabled\", true);\n            jQuery(destColsize).prop(\"disabled\", true);\n            jQuery(destColtype).parent().prev().css(\"color\", \'#999\');\n            jQuery(destColsize).parent().prev().css(\"color\", \'#999\');\n        }\n    });\n\n    // Force the auto-change if the type is of a fixed variety\n    theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n    if (smd_bio_types[theType].fixed != \'\') {\n        jQuery(\"#smd_bio_widget_type\").change();\n    }\n\n    // When clicking Save, enable the coltype/colsize boxes so the values are transmitted in the _POST array\n    jQuery(\"#page-smd_bio input[name=\'save\']\").click(function() {\n        jQuery(destColtype).prop(\"disabled\", false);\n        jQuery(destColsize).prop(\"disabled\", false);\n    });\n\n    // Pop up the help tooltips based on the current Type\n    jQuery(\".edit-smd-bio-size .pophelp\").hover(\n        function(e) {\n            var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n            spanid = \'span#smd_bio_size_help_\'+theType;\n            this.title = jQuery(spanid).attr(\"title\");\n        }, function(e) {\n            this.title = \'\';\n        });\n    jQuery(\".edit-smd-bio-value .pophelp\").hover(\n        function(e) {\n            var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n            spanid = \'span#smd_bio_val_help_\'+theType;\n            this.title = jQuery(spanid).attr(\"title\");\n        }, function(e) {\n            this.title = \'\';\n        });\n\n    // Handle opening/closing the coltype area\n    jQuery(\'#smd_bio_colgroup\').click(function(ev) {\n        ev.preventDefault();\n        jQuery(this).toggleClass(\'expanded\');\n        jQuery(\'.smd_bio_coltype\').toggle();\n\n        sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'smd_bio_save_pane_state\',\n                pane: \'coltype\',\n                visible: (jQuery(this).hasClass(\'expanded\'))\n            }\n        );\n    });\n});\n//]]>\n</script>\n{$css}\nEOJS;\n    }\n}\n\n// ------------------------\n// Make the name/val pairs for selectInput / radio / checkbox sets\nfunction smd_bio_splitval($val) {\n    $selv = array();\n\n    $wvals = preg_split(\"/[\\r\\n,]+/\", $val, -1, PREG_SPLIT_NO_EMPTY);\n    foreach ($wvals as $wval) {\n        $prts = explode(\'=>\', $wval);\n        if (count($prts) == 1) {\n            $prts[1] = $prts[0];\n            $prts[0] = smd_bio_sanitize($prts[0]);\n        }\n        $selv[trim($prts[0])] = trim($prts[1]);\n    }\n    return $selv;\n}\n\n// ------------------------\n// Super-sanitize the passed value so we can make variable names from the returned string\nfunction smd_bio_sanitize($val) {\n    return strtolower(str_replace(\"-\", \"_\", sanitizeForUrl($val)));\n}\n\n// ------------------------\nfunction smd_bio_save($evt, $stp) {\n    global $prefs;\n\n    if (smd_bio_table_exist()) {\n        $targetvars = array();\n        extract(doSlash(psa(array(\'privs\', \'name\', \'email\', \'RealName\', \'user_id\'))));\n        if (get_pref(\'smd_bio_sanitize_name\', 0) > 0) {\n            // Sanitize and pass the new name forward to the actual txp_user save routine\n            $name = strtolower(sanitizeForUrl($name));\n            $_POST[\'name\'] = $name;\n        }\n        $length = function_exists(\'mb_strlen\') ? mb_strlen($name, \'8bit\') : strlen($name);\n\n        if (($user_id || $name) and $length <= 64 and is_valid_email($email)) {\n            foreach ($_POST as $idx => $item) {\n                if (strpos($idx, \'smd_bio_\') === 0) {\n                    $targetvars[] = $idx;\n                }\n            }\n\n            // Double de-clutch again... dammit :-(\n            $user_id = gps(\'user_id\');\n            if ($user_id) {\n                $user_ref = safe_field(\'name\',\'txp_users\',\"user_id = \'$user_id\'\");\n            } else {\n                $user_ref = $name;\n            }\n            extract(gpsa($targetvars));\n            $bcols = getThings(\'describe `\'.PFX.SMD_BIO.\'`\');\n            $sqlSet = array();\n            foreach ($targetvars as $var) {\n                $colname = str_replace(\'smd_bio_\', \'\', $var);\n                if (in_array($colname, $bcols)) {\n                    $sqlSet[] = \"`$colname` = \'\".doSlash($$var).\"\'\";\n                }\n            }\n            if ($sqlSet) {\n                $rs = safe_upsert(SMD_BIO, join(\',\', $sqlSet), \"`user_ref` = \'\".doSlash($user_ref).\"\'\");\n            }\n        }\n    }\n}\n\n// ------------------------\nfunction smd_bio_delete($evt, $stp) {\n    global $txp_user;\n\n    if (smd_bio_table_exist()) {\n        // Since we are executing \'pre\' delete we need to unfortunately duplicate some of the checks\n        // from txp_admin.php so we minimise the opportunity to delete someone by mistake\n        $selected = ps(\'selected\');\n        $method = ps(\'edit_method\');\n        if (!$selected or !is_array($selected)) {\n            return;\n        }\n        if ($method != \'delete\') {\n            return;\n        }\n\n        $names = safe_column(\'name\', \'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($selected)).\"\') AND name != \'\".doSlash($txp_user).\"\'\");\n        if (!$names) return;\n\n        $assign_assets = ps(\'assign_assets\');\n        if ($assign_assets === \'\') {\n            return;\n        } elseif (in_array($assign_assets, $names)) {\n            return;\n        } else {\n            // All the checks passed -- do it\n            safe_delete(SMD_BIO, \"user_ref IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\");\n        }\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_save_pane_state() {\n    global $event;\n    $panes = array(\'coltype\');\n    $pane = gps(\'pane\');\n    if (in_array($pane, $panes)) {\n        set_pref(\"pane_smd_bio_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), $event, PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_form_submit() {\n    global $mem_form_type;\n\n    if (!in_array($mem_form_type, array(\'smd_bio\', \'mem_self_register\', \'mem_self_user_edit\'))) return;\n\n    $author = smd_bio_find_author();\n\n    if ($author) {\n        $core_cols = smd_bio_core_cols();\n        $meta_cols = safe_column(\'name\', SMD_BIO_META, \'1=1\');\n        $query_params = array(\n            \'core\' => array(\n                \'tbl\' => \'txp_users\',\n                \'ref\' => \'name\',\n            ),\n            \'bio\' => array(\n                \'tbl\' => SMD_BIO,\n                \'ref\' => \'user_ref\',\n            )\n        );\n\n        foreach (stripPost() as $key => $val) {\n            // Only care about smd_bio_ prefixed entries in $_POST\n            if (strpos($key, \'smd_bio_\') !== false) {\n                // Strip off the known prefix\n                $raw_key = str_replace(\'smd_bio_\', \'\', $key);\n                if (in_array($raw_key, $core_cols)) {\n                    $query_params[\'core\'][\'cols\'][] = doSlash($raw_key) . \'=\'. doQuote(doSlash($val));\n                } else if (in_array($raw_key, $meta_cols)) {\n                    $query_params[\'bio\'][\'cols\'][] = doSlash($raw_key) . \'=\'. doQuote(doSlash($val));\n                }\n            }\n        }\n\n        // If there are some cols set we\'re good to update/insert depending if $author exists or not\n        foreach ($query_params as $type => $data) {\n            if (isset($data[\'cols\'])) {\n                $params = join(\', \', $data[\'cols\']);\n                safe_upsert($data[\'tbl\'], $params, $data[\'ref\'] .\'=\'. doQuote(doSlash($author)));\n            }\n        }\n    }\n}\n\n// Try some of the usual suspects for locating authorship\nfunction smd_bio_find_author($author_in=\'\', $places=array(\'biotag\', \'txpuser\', \'ili\', \'profile\', \'selfreg\', \'list\', \'article\', \'image\', \'file\', \'link\') ) {\n    global $smd_bio_author, $txp_user, $mem_form_type, $mem_profile, $pretext, $thisarticle, $thisimage, $thisfile, $thislink;\n    static $smd_bio_ili = 0;\n\n    $places = is_array($places) ? $places : do_list($places);\n\n    // Check for any passed-in author first\n    $author = $author_in;\n\n    foreach ($places as $place) {\n        if ($author != \'\') break;\n        switch ($place) {\n            case \'biotag\':\n                // From smd_bio_author tag\n                $author = ($smd_bio_author != \'\') ? $smd_bio_author : $author;\n            break;\n            case \'txpuser\':\n                // From global user variable\n                $author = ($txp_user != \'\') ? $txp_user : $author;\n            break;\n            case \'ili\':\n                // From currently logged-in user\n                $smd_bio_ili = ($smd_bio_ili === 0) ? is_logged_in() : $smd_bio_ili;\n                if ($smd_bio_ili) {\n                    $author = $smd_bio_ili[\'name\'];\n                }\n            break;\n            case \'profile\':\n                // From current self-edit user profile\n                $author = (isset($mem_profile[\'name\'])) ? $mem_profile[\'name\'] : $author;\n            break;\n            case \'selfreg\':\n                // New author from mem_self_reg?\n                $aname = ps(\'name\');\n                if ($aname == \'\') {\n                    // New author from custom mem_form?\n                    $aname = ps(\'smd_bio_name\');\n                }\n                if ( ($mem_form_type == \'mem_self_register\' || $mem_form_type == \'smd_bio\') && ($aname != \'\') ) {\n                    // As long as $aname doesn\'t exist, let this new author in\n                    //TODO: Cache this?\n                    $exists = safe_row(\'*\', \'txp_user\', \"name=\'\".doSlash($aname).\"\'\");\n                    if (!$exists) {\n                        $author = $aname;\n                    }\n                }\n            break;\n            case \'list\':\n                $author = (isset($pretext[\'author\'])) ? $pretext[\'author\'] : $author;\n            break;\n            case \'article\':\n                $author = (isset($thisarticle[\'authorid\'])) ? $thisarticle[\'authorid\'] : $author;\n            break;\n            case \'image\':\n                $author = (isset($thisimage[\'author\'])) ? $thisimage[\'author\'] : $author;\n            break;\n            case \'file\':\n                $author = (isset($thisfile[\'author\'])) ? $thisfile[\'author\'] : $author;\n            break;\n            case \'link\':\n                $author = (isset($thislink[\'author\'])) ? $thislink[\'author\'] : $author;\n            break;\n        }\n    }\n\n    return $author;\n}\n\n// -------------------------------------------------------------\n// Return a list of core columns in the txp_users table.\n// This could be done programmatically but isn\'t: save a\n// query, save the world\nfunction smd_bio_core_cols() {\n    // The indices are the gTxt() names for the associated field\n    return array(\n        \'id\'         => \'user_id\',\n        \'name\'       => \'name\',\n        \'real_name\'  => \'RealName\',\n        \'email\'      => \'email\',\n        \'privileges\' => \'privs\',\n        \'date\'       => \'last_access\'\n    );\n}\n\n// -------------------------------------------------------------\n// In alphabetical order or sorting on the admin panel gets screwy\nfunction smd_bio_get_types() {\n    $smd_bio_types = array(\n        \'checkbox\' => array(\n                \'name\'    => \'Checkbox(es)\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'date\' => array(\n                \'name\'    => \'Date\',\n                \'coltype\' => \'date\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'datetime\' => array(\n                \'name\'    => \'Date/Time\',\n                \'coltype\' => \'datetime\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'email\' => array(\n                \'name\'    => \'E-mail\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 254,\n                \'fixed\'   => false,\n                ),\n        \'image\' => array(\n                \'name\'    => \'Image\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => true,\n                ),\n        \'list\' => array(\n                \'name\'    => \'Select list\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'month\' => array(\n                \'name\'    => \'Month\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'multilist\' => array(\n                \'name\'    => \'Multi select list\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'number\' => array(\n                \'name\'    => \'Number\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => false,\n                ),\n        \'radio\' => array(\n                \'name\'    => \'Radio set\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'range\' => array(\n                \'name\'    => \'Range\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => false,\n                ),\n        \'text\' => array(\n                \'name\'    => \'Text box\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'textarea\' => array(\n                \'name\'    => \'Text area\',\n                \'coltype\' => \'mediumtext\',\n                \'colsize\' => 4096,\n                \'fixed\'   => false,\n                ),\n        \'time\' => array(\n                \'name\'    => \'Time\',\n                \'coltype\' => \'time\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'url\' => array(\n                \'name\'    => \'URL\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'week\' => array(\n                \'name\'    => \'Week\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'yesnoradio\' => array(\n                \'name\'    => \'Yes/no radio\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n    );\n\n    return $smd_bio_types;\n}\n\n// -------------------------------------------------------------\n// In alphabetical order or sorting on the admin panel gets screwy\nfunction smd_bio_get_coltypes() {\n    $smd_bio_coltypes = array(\n        \'date\' => array(\n            \'title\'       => \'Date [YYYY-MM-DD]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'datetime\' => array(\n            \'title\'       => \'Date + time [YYYY-MM-DD HH:MN:SS]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'double\' => array(\n            \'title\'       => \'Double precision float\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'float\' => array(\n            \'title\'       => \'Floating point number\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'int\' => array(\n            \'title\'       => \'Integer [up to 4294967295]\',\n            \'size_req\'    => \'11\',\n            \'has_default\' => true,\n        ),\n        \'longtext\' => array(\n            \'title\'       => \'Long text [up to 4GB]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'mediumint\' => array(\n            \'title\'       => \'Medium integer [up to 16777215]\',\n            \'size_req\'    => \'8\',\n            \'has_default\' => true,\n        ),\n        \'mediumtext\' => array(\n            \'title\'       => \'Medium text [up to 16MB]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'smallint\' => array(\n            \'title\'       => \'Small integer [up to 65535]\',\n            \'size_req\'    => \'4\',\n            \'has_default\' => true,\n        ),\n        \'text\' => array(\n            \'title\'       => \'Text [up to 64KB chars]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'time\' => array(\n            \'title\'       => \'Time [HH:MN:SS]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'timestamp\' => array(\n            \'title\'       => \'Timestamp [seconds since UNIX epoch]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'tinyint\' => array(\n            \'title\'       => \'Tiny integer [up to 255]\',\n            \'size_req\'    => \'3\',\n            \'has_default\' => true,\n        ),\n        \'tinytext\' => array(\n            \'title\'       => \'Tiny text [up to 255 chars]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'varbinary\' => array(\n            \'title\'       => \'Binary varchar [size subject to max row size]\',\n            \'size_req\'    => \'255\',\n            \'has_default\' => true,\n        ),\n        \'varchar\' => array(\n            \'title\'       => \'Varchar [size subject to max row size]\',\n            \'size_req\'    => \'255\',\n            \'has_default\' => true,\n        ),\n    );\n\n    return $smd_bio_coltypes;\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_get_unused() {\n    $smd_bio_unused = array(\n        \'size\' => array(\'list\', \'multilist\', \'radio\', \'yesnoradio\', \'checkbox\'),\n    );\n\n    return $smd_bio_unused;\n}\n\n// ************************\n// TABLE MANAGEMENT\n// ------------------------\nfunction smd_bio_table_exist() {\n    static $smd_bio_table_ok = array();\n\n    if (isset($smd_bio_table_ok[\'meta\'])) {\n        return ($smd_bio_table_ok[\'meta\'] === $smd_bio_table_ok[\'field\']);\n    }\n\n    $meta = safe_count(SMD_BIO_META, \'1=1\');\n    $flds = @safe_show(\'columns\', SMD_BIO);\n\n    $smd_bio_table_ok[\'meta\'] = (int)$meta;\n    $smd_bio_table_ok[\'field\'] = (int)count($flds) - 1; // Subtract the user_ref column as it\'s always present\n\n    return($smd_bio_table_ok[\'meta\'] === $smd_bio_table_ok[\'field\']);\n}\n\n// ------------------------\nfunction smd_bio_table_install($showpane=1) {\n    global $DB;\n\n    $smd_bio_types = smd_bio_get_types();\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $msg = \'\';\n    $debug = gps(\'debug\');\n\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_BIO.\"` (\n        `user_ref` varchar(64) NOT NULL default \'\',\n        UNIQUE KEY `user_ref` (`user_ref`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8, PACK_KEYS=1\";\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_BIO_META.\"` (\n        `id` int(4) NOT NULL auto_increment,\n        `title` varchar(64) NULL default \'\' COLLATE utf8_general_ci,\n        `name` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `type` set(\".doQuote(join(\"\',\'\", array_keys($smd_bio_types))).\") NOT NULL default \'text\',\n        `size` varchar(10) NULL default 0,\n        `coltype` set(\".doQuote(join(\"\',\'\", array_keys($smd_bio_coltypes))).\") NOT NULL default \'varchar\',\n        `colsize` smallint(4) NULL default 0,\n        `val` text NULL COLLATE utf8_general_ci,\n        `position` varchar(16) NULL default \'\',\n        PRIMARY KEY (`id`),\n        UNIQUE KEY (`name`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8, AUTO_INCREMENT=1\";\n\n    if ($debug) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Handle upgrades from v0.3x to v0.40.\n    // Upgrade table collation if necessary\n    $ret = getRows(\"SHOW TABLE STATUS WHERE name IN (\'\".PFX.SMD_BIO.\"\', \'\".PFX.SMD_BIO_META.\"\')\");\n    if ($ret[0][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_BIO_META, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n    if ($ret[1][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_BIO, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n\n    // Alter the position field from int to varchar so positioning can be non-numeric\n    $ret = @safe_field(\"DATA_TYPE\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_BIO_META.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'position\'\");\n    if ($ret != \'varchar\') {\n        safe_alter(SMD_BIO_META, \"CHANGE `position` `position` VARCHAR( 16 ) NULL DEFAULT \'\'\", $debug);\n    }\n    // Add the coltype and colsize columns\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_BIO_META.\'`\');\n    if (!in_array(\'coltype\', $flds)) {\n        safe_alter(SMD_BIO_META, \"ADD `coltype` SET(\".doQuote(join(\"\',\'\", array_keys($smd_bio_coltypes))).\") NOT NULL default \'\' after `size`\", $debug);\n        safe_alter(SMD_BIO_META, \"ADD `colsize` SMALLINT(4) NULL default 0 after `coltype`\", $debug);\n    }\n    // Add multiple select list & date flavour support to the \'type\' set, and rename \'text_input\' to just \'text\'\n    $toChange = safe_column(\'id\', SMD_BIO_META, \'type=\"text_input\"\');\n    $fld = getRows(\"SHOW FULL COLUMNS FROM `\".PFX.SMD_BIO_META.\"` LIKE \'type\'\");\n    $ft = $fld[0][\'Type\'];\n    if ( (strpos($ft, \'multilist\') === false) || (strpos($ft, \'text_input\') !== false) || (strpos($ft, \'month\') === false) ) {\n        safe_alter(SMD_BIO_META, \"CHANGE `type` `type` SET(\".doQuote(join(\"\',\'\", array_keys($smd_bio_types))).\")\", $debug);\n    }\n    if ($toChange) {\n        safe_update(SMD_BIO_META, \'type=\"text\"\', \"id in (\'\". join(\"\',\'\", $toChange) .\"\')\", $debug);\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_bio_tbl_installed\');\n    } else {\n        $msg = gTxt(\'smd_bio_tbl_not_installed\');\n    }\n\n    if ($showpane) {\n        smd_bio_config($msg);\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_bio_table_remove($showpane=1) {\n    $ret = $msg = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_BIO. \"; \";\n    $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_BIO_META. \"; \";\n    if(gps(\'debug\')) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_bio_tbl_removed\');\n    } else {\n        $msg = gTxt(\'smd_bio_tbl_not_removed\');\n    }\n\n    if ($showpane) {\n        smd_bio_config($msg);\n    }\n}\n\n// ************************\n// PUBLIC-SIDE TAGS\n// ------------------------\n// Wrapper to permit bio info to be displayed for multiple authors\nfunction smd_bio_author($atts, $thing = null) {\n    global $smd_bio_author, $smd_bio_meta_data;\n\n    extract(lAtts(array(\n        \'author\'      => \'\',\n        \'form\'        => \'\',\n        \'sort\'        => \'RealName asc\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'breakclass\'  => \'\',\n        \'class\'       => \'\',\n        \'label\'       => \'\',\n        \'labeltag\'    => \'\',\n        \'auto_detect\' => \'profile, list, article, image, file, link\',\n    ), $atts));\n\n    $author = smd_bio_find_author($author, $auto_detect);\n    $author = do_list($author);\n    $authors = array();\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n\n    // Set up any sorting clause\n    if ($sort != \'\') {\n        $sort = do_list($sort);\n        $sortout = array();\n        foreach ($sort as $sitems) {\n            $sortbits = do_list($sitems, \' \');\n            $sort_col = $sortbits[0];\n            $sort_ord = (isset($sortbits[1]) && in_array($sortbits[1], array(\'asc\', \'desc\'))) ? $sortbits[1] : \'asc\';\n            if ($sort_col) {\n                $sortout[] = \'`\' . $sort_col . \'` \' . $sort_ord;\n            }\n        }\n        if ($sortout) {\n            $sort = \' ORDER BY \'.join(\',\', $sortout);\n        }\n    }\n\n    // Expand any priv levels\n    foreach ($author as $user) {\n        if (strpos(strtolower($user), \"smd_privs\") === 0) {\n            $aprivs = explode(\':\', $user);\n            array_shift($aprivs); // Remove smd_privs token from the array\n            $rows = safe_query(\'SELECT txpu.name FROM \' . PFX . \'txp_users AS txpu LEFT JOIN \' . PFX.SMD_BIO . \' AS smdb ON txpu.name = smdb.user_ref WHERE privs in (\' . doQuote(join(\"\',\'\", $aprivs)) . \')\'.$sort);\n            if ($rows) {\n                while ($a = nextRow($rows)) {\n                    $authors[] = $a[\'name\'];\n                }\n            }\n        } else if (strpos(strtolower($user), \"smd_all\") === 0) {\n            $rows = safe_query(\'SELECT txpu.name FROM \' . PFX . \'txp_users AS txpu LEFT JOIN \' . PFX.SMD_BIO . \' AS smdb ON txpu.name = smdb.user_ref WHERE 1=1\'.$sort);\n            if ($rows) {\n                while ($a = nextRow($rows)) {\n                    $authors[] = $a[\'name\'];\n                }\n            }\n        } else {\n            $authors[] = $user;\n        }\n    }\n\n    // Parse content for each author: inject current author into global\n    $out = array();\n    $author_count = count($authors) - 1;\n    foreach ($authors as $idx => $smd_bio_author) {\n        $smd_bio_meta_data[\'author\'][\'first\'] = ($idx === 0);\n        $smd_bio_meta_data[\'author\'][\'last\'] = ($idx === $author_count);\n        $toParse = (empty($thing)) ? $smd_bio_author : $thing;\n        $out[] = parse($toParse);\n    }\n\n    return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\n// Display biographical field data from a given user\'s profile\nfunction smd_bio_info($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    // Data cache\n    static $bio_info = array();\n    static $meta = array();\n    static $cmeta = array();\n    static $metacols = array();\n\n    extract(lAtts(array(\n        \'author\'      => \'\', // Deprecated: use smd_bio_author tag as a wrapper instead\n        \'fields\'      => \'SMD_ALL\',\n        \'items\'       => \'SMD_ALL\', // Deprecated: use fields instead\n        \'exclude\'     => \'\',\n        \'form\'        => \'\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'class\'       => \'\',\n        \'label\'       => \'1\',\n        \'labeltag\'    => \'\',\n        \'labelclass\'  => \'SMD_DFLT\', // Deprecated\n        \'itemwraptag\' => \'\', // Deprecated: use break\n        \'itemclass\'   => \'SMD_DFLT\', // Deprecated: use breakclass\n        \'breakclass\'  => \'SMD_DFLT\',\n        \'show_empty\'  => 0,\n        \'prefix\'      => \'smd_bio_\', // Only for replacement variables\n        \'auto_detect\' => \'biotag, list, article, image, file, link\',\n        \'debug\'       => 0,\n    ), $atts));\n\n    if (isset($atts[\'author\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'author\')) . \'. Use the smd_bio_author tag as a wrapper instead.\', E_USER_NOTICE);\n        unset($author);\n    }\n    if (isset($atts[\'items\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'items\')) . \'. Use fields instead.\', E_USER_NOTICE);\n        $fields = $items;\n        unset($items);\n    }\n    if (isset($atts[\'itemwraptag\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'itemwraptag\')) . \'. Use break instead.\', E_USER_NOTICE);\n        $break = $itemwraptag;\n        unset($itemwraptag);\n    }\n    if (isset($atts[\'itemclass\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'itemclass\')) . \'. Use breakclass instead.\', E_USER_NOTICE);\n        $breakclass = $itemclass;\n        unset($itemclass);\n    }\n    if (isset($atts[\'labelclass\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'labelclass\')), E_USER_NOTICE);\n        unset($labelclass);\n    }\n\n    $author = smd_bio_find_author(\'\', $auto_detect);\n\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n    $smd_bio_data = $smd_bio_option_data = array();\n    $bio_types = array_keys(smd_bio_get_types());\n\n    $fields = do_list($fields);\n    $exclude = do_list($exclude);\n    $final = \'\';\n\n    $coreCols = smd_bio_core_cols();\n    $list_types = array(\'checkbox\', \'radio\', \'yesnoradio\', \'list\', \'multilist\');\n    $mm_types = array(\'number\', \'range\');\n\n    if ($author) {\n        $meta = (empty($meta)) ? safe_rows(\'*\', SMD_BIO_META, \'1=1\') : $meta;\n        $cmeta = (empty($cmeta)) ? safe_show(\'columns\', \'txp_users\') : $cmeta;\n        $metacols = (empty($metacols)) ? safe_column(\'name\', SMD_BIO_META, \'1=1\') : $metacols;\n        $num = count($meta);\n\n        foreach($cmeta as $info) {\n            if (!in_array($info[\'Field\'], $coreCols)) continue;\n            $meta[$num][\'name\'] = $info[\'Field\'];\n            $tField = join(\'\', array_keys($coreCols, $info[\'Field\']));\n            $meta[$num][\'title\'] = ((gTxt($tField) == $tField) ? gTxt(\'smd_bio_\'.$tField) : gTxt($tField));\n            $length = (($off = strpos($info[\'Type\'], \'(\')) !== false) ? $off : strlen($info[\'Type\']); // Find the first open bracket or end of string\n            $cmtype = substr($info[\'Type\'], 0, $length);\n            $cmtype = in_array($cmtype, $bio_types)? $cmtype : \'text\';\n            $meta[$num][\'type\'] = $cmtype;\n            $num++;\n        }\n        if ($debug > 1) {\n            echo \'++ META DATA ++\';\n            dmp($meta);\n        }\n\n        // Exclusions override given fields\n        if ($exclude[0] != \'\' || in_array(\'SMD_ALL\', $fields)) {\n            $fields = $metacols;\n            $fields = array_merge($fields, $coreCols);\n        }\n\n        if (isset($bio_info[$author])) {\n            $cbio = $bio_info[$author][\'cbio\'];\n            $ebio = $bio_info[$author][\'ebio\'];\n        } else {\n            $cbio = $bio_info[$author][\'cbio\'] = safe_row(\'*\', \'txp_users\', \"name=\'\".doSlash($author).\"\'\");\n            $ebio = $bio_info[$author][\'ebio\'] = safe_row(\'*\', SMD_BIO, \"user_ref=\'\".doSlash($author).\"\'\");\n        }\n\n        $replacements = $out = $toParse = array();\n        $isSingle = ($thing === null) ? true : false;\n        $numFields = count($fields);\n\n        foreach ($fields as $iref => $whatnot) {\n            $idx = -1;\n            if (in_array($whatnot, $exclude)) continue;\n\n            // Find the meta row\n            foreach ($meta as $num => $data) {\n                if ($data[\'name\'] == $whatnot) {\n                    $idx = $num;\n                    break;\n                }\n            }\n\n            if ($idx > -1) {\n                if (in_array($whatnot, $coreCols)) {\n                    $field = isset($cbio[$whatnot]) ? $cbio[$whatnot] : \'\';\n                } else {\n                    $field = isset($ebio[$whatnot]) ? $ebio[$whatnot] : \'\';\n                }\n\n                $theName = $meta[$idx][\'name\'];\n                $theTitle = $meta[$idx][\'title\'];\n                $prefixedName = $prefix.$theName;\n                $fixedName = \'smd_bio_\'.$theName; // Used for widget names so we know the indices in the $_POST array\n                $theClass = ($breakclass==\'SMD_DFLT\') ? $prefixedName : (($breakclass) ? $breakclass : \'\');\n                $replacements[\'{\'.$prefixedName.\'_name}\'] = $theName;\n                $replacements[\'{\'.$prefixedName.\'_title}\'] = $theTitle;\n                $replacements[\'{\'.$prefixedName.\'_class}\'] = $theClass;\n                $replacements[\'{\'.$prefixedName.\'_type}\'] = $meta[$idx][\'type\'];\n                $smd_bio_data[$theName][\'name\'] = $theName;\n                $smd_bio_data[$theName][\'title\'] = $theTitle;\n                $smd_bio_data[$theName][\'class\'] = $theClass;\n                $smd_bio_data[$theName][\'type\'] = $meta[$idx][\'type\'];\n                if (!in_array($meta[$idx][\'type\'], $list_types) && isset($meta[$idx][\'val\'])) {\n                    $smd_bio_data[$theName][\'default\'] = $meta[$idx][\'val\'];\n                    $replacements[\'{\'.$prefixedName.\'_default}\'] = $meta[$idx][\'val\'];\n                }\n                $widget = \'\';\n\n                if ($field || $show_empty) {\n                    if (in_array($meta[$idx][\'type\'], $list_types)) {\n                        $field = do_list($field);\n                        $field = join(\', \',$field);\n                    }\n\n                    $fieldContent = ($meta[$idx][\'type\'] == \'textarea\') ? nl2br($field) : $field;\n                    $replacements[\'{\'.$prefixedName.\'}\'] = $fieldContent;\n                    $smd_bio_data[$theName][\'value\'] = $fieldContent;\n\n                    // For backwards compatibility(ish) with v0.3x\n                    if ($numFields == 1) {\n                        $replacements[\'{\'.$prefix.\'info_item}\'] = $fieldContent; // Deprecated, use info_value instead\n                        $replacements[\'{\'.$prefix.\'info_value}\'] = $fieldContent;\n                        $replacements[\'{\'.$prefix.\'info_name}\'] = $theName;\n                        $replacements[\'{\'.$prefix.\'info_title}\'] = $theTitle;\n                        $replacements[\'{\'.$prefix.\'info_itemclass}\'] = $theClass; // Deprecated: use info_class instead\n                        $replacements[\'{\'.$prefix.\'info_class}\'] = $theClass;\n                    }\n\n                    if (in_array($meta[$idx][\'type\'], $list_types)) {\n                        $chosens = do_list($field);\n                        $nv = smd_bio_splitval($meta[$idx][\'val\']);\n                        list($nv, $dflt) = smd_bio_get_default($nv, $field);\n                        $dflts = do_list($dflt);\n                        $listctr=1;\n                        $chosenctr=0;\n                        foreach($nv as $listitem => $listlabel) {\n                            $replacements[\'{\'.$prefixedName.\'_option_\'.$listctr.\'}\'] = $listitem;\n                            $replacements[\'{\'.$prefixedName.\'_title_\'.$listctr.\'}\'] = $listlabel;\n                            $smd_bio_data[$theName][\'option_\'.$listctr] = $listitem;\n                            $smd_bio_data[$theName][\'title_\'.$listctr] = $listlabel;\n                            if (in_array($listitem, $chosens)) {\n                                $chosenctr++;\n                                $replacements[\'{\'.$prefixedName.\'_chosen_option_\'.$chosenctr.\'}\'] = $listitem;\n                                $replacements[\'{\'.$prefixedName.\'_chosen_title_\'.$chosenctr.\'}\'] = $listlabel;\n                                $smd_bio_data[$theName][\'chosen_option_\'.$chosenctr] = $listitem;\n                                $smd_bio_data[$theName][\'chosen_title_\'.$chosenctr] = $listlabel;\n                            }\n                            $listctr++;\n                        }\n                        $dfltctr = 1;\n                        foreach($dflts as $dfltitem) {\n                            $replacements[\'{\'.$prefixedName.\'_default_option_\'.$dfltctr.\'}\'] = $dfltitem;\n                            $smd_bio_data[$theName][\'default_option_\'.$dfltctr] = $dfltitem;\n                            $dfltctr++;\n                        }\n\n                        // TODO: maybe hand-code all input types so they can have classes added\n                        switch ($meta[$idx][\'type\']) {\n                            case \'checkbox\':\n                                $citems = array();\n                                foreach ($nv as $idx => $lbl) {\n                                    $citems[] = checkbox($fixedName, \'cb_\'.$idx, (in_array($idx, $dflts) ? \'1\' : \'0\')) . $lbl;\n                                }\n                                $widget = join(n, $citems);\n                            break;\n                            case \'yesnoradio\':\n                                $widget = yesnoRadio($fixedName, $dflt);\n                            break;\n                            case \'radio\':\n                                $widget = radioSet($nv, $fixedName, $dflt);\n                            break;\n                            case \'list\':\n                                $widget = selectInput($fixedName, $nv, $dflt, false, \'\', $prefixedName);\n                            break;\n                            case \'multilist\':\n                                $mitems = array();\n                                $mitems[] = \'<select name=\"\'.$fixedName.\'\" class=\"list multiple \'.$theClass.\'\" multiple=\"multiple\">\';\n                                foreach ($nv as $idx => $lbl) {\n                                    // Not using selectInput() because it doesn\'t support multiples\n                                    $mitems[] = \'<option value=\"ms_\'.$idx.\'\" \'.((in_array($idx, $dflts)) ? \' selected=\"selected\"\' : \'\') . \'>\' . $lbl . \'</option>\';\n                                }\n                                $mitems[] = \'</select>\';\n                                $widget = join(n, $mitems);\n                            break;\n                        }\n\n                        $replacements[\'{\'.$prefixedName.\'_option_count}\'] = $listctr-1;\n                        $replacements[\'{\'.$prefixedName.\'_chosen_count}\'] = $chosenctr;\n                        $replacements[\'{\'.$prefixedName.\'_default_count}\'] = $dfltctr-1;\n                        $smd_bio_data[$theName][\'option_count\'] = $listctr-1;\n                        $smd_bio_data[$theName][\'chosen_count\'] = $chosenctr;\n                        $smd_bio_data[$theName][\'default_count\'] = $dfltctr-1;\n                    } else if (in_array($meta[$idx][\'type\'], $mm_types)) {\n                        $sizes = do_list($meta[$idx][\'size\']);\n                        $min = ($sizes[0] === \'\') ? \'\' : \' min=\"\' . $sizes[0] . \'\"\';\n                        $max = (isset($sizes[1]) && $sizes[1] !== \'\') ? \' max=\"\' . $sizes[1] . \'\"\' : \'\';\n                        $jmp = (isset($sizes[2]) && $sizes[2] !== \'\') ? \' step=\"\' . $sizes[2] . \'\"\': \'\';\n                        $cls = ($theClass) ? \' class=\"\'.$theClass.\'\"\' : \'\';\n                        $widget = \'<input type=\"\'.$meta[$idx][\'type\'].\'\" value=\"\'.$field.\'\" name=\"\'.$fixedName.\'\"\'.$cls.$min.$max.$jmp.\' />\';\n                    } else if ($meta[$idx][\'type\'] == \'textarea\') {\n                        $sizes = do_list($meta[$idx][\'size\']);\n                        $w = ($sizes[0] === \'\') ? \'\' : \' cols=\"\' . $sizes[0] . \'\"\';\n                        $h = (isset($sizes[1]) && $sizes[1] !== \'\') ? \' rows=\"\' . $sizes[1] . \'\"\': \'\';\n                        $cls = ($theClass) ? \' class=\"\'.$theClass.\'\"\' : \'\';\n                        // TODO: maybe use text_area() when 4.6.0 released as it may support classes\n                        $widget = \'<textarea name=\"\' . $fixedName . \'\" id=\"\' . $prefixedName . \'\"\'. $cls.$w.$h.\'>\'.$field.\'</textarea>\';\n                    } else if ($meta[$idx][\'type\'] == \'image\') {\n                        $widget = fInput(\'text\', $fixedName, $field, $theClass, \'\', \'\', \'\', \'\', $prefixedName);\n                    } else {\n                        $widget = fInput($meta[$idx][\'type\'], $fixedName, $field, $theClass, \'\', \'\', \'\', \'\', $prefixedName);\n                    }\n\n                    $smd_bio_data[$theName][\'widget\'] = $widget;\n                    $replacements[\'{\'.$prefixedName.\'_widget}\'] = $widget;\n\n                    // Without container/form, build up generic output\n                    if ($isSingle) {\n                        $taglab = (($label==1) ? $theTitle : (($label==\'\') ? \'\' : (($label) ? $label : $theName)));\n                        $toParse[] = doLabel($taglab, $labeltag) . (($break) ? doTag($field, $break, $theClass) : $field);\n                    }\n                }\n            }\n        }\n\n        if ($debug) {\n            echo \'++ BIO REPLACEMENTS FOR \' .$author. \' ++\';\n            dmp($replacements);\n        }\n\n        if (!$isSingle) {\n            $toParse[] = $thing;\n        }\n\n        $out[] = parse(strtr(join(n, $toParse), $replacements));\n        $final = doWrap($out, $wraptag, \'\', $class);\n    }\n    return $final;\n}\n\n// Output data\nfunction smd_bio_data($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'   => \'\',\n        \'item\'    => \'value\',\n        \'wraptag\' => \'\',\n        \'break\'   => \'\',\n        \'class\'   => \'\',\n        \'debug\'   => 0,\n    ), $atts));\n\n    $bdata = is_array($smd_bio_data) ? $smd_bio_data : array();\n    $idata = is_array($smd_bio_option_data) ? $smd_bio_option_data : array();\n    $datapool = array_merge($bdata, $idata);\n\n    if ($debug) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($datapool);\n    }\n\n    $items = do_list($item);\n    $out = array();\n    foreach ($items as $it) {\n        if (isset($datapool[$field][$it])) {\n            $out[] = $datapool[$field][$it];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Iterate over N multi-items\nfunction smd_bio_iterate($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'      => \'\',\n        \'using\'      => \'chosen\', // chosen, default, all\n        \'display\'    => \'title\', // option, title\n        \'prefix\'     => \'smd_bio_\', // Only for replacement variables\n        \'wraptag\'    => \'\',\n        \'class\'      => \'\',\n        \'break\'      => \'\',\n        \'form\'       => \'\',\n        \'limit\'      => 0,\n        \'offset\'     => 0,\n        \'debug\'      => 0,\n    ), $atts));\n\n    if ($debug > 1) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($smd_bio_data);\n    }\n\n    // Validation 1\n    if (!isset($smd_bio_data[$field])) {\n        return;\n    }\n\n    // Validation 2\n    $usingMap = array(\'all\' => \'\', \'chosen\' => \'chosen\', \'default\' => \'default\');\n    $using = isset($usingMap[$using]) ? $using : \'chosen\';\n\n    // Validation 3\n    $displayMap = array(\'option\' => \'option\', \'title\' => \'title\');\n    $display = isset($displayMap[$display]) ? $display : \'title\';\n\n    switch ($using) {\n        case \'chosen\':\n        case \'default\':\n            $countType = $using.\'_count\';\n            break;\n        case \'all\':\n        default:\n            $countType = \'option_count\';\n        break;\n    }\n\n    $out = $reps = $stash = array();\n\n    // Whip through the lists to gather the info for later.\n    if (isset($smd_bio_data[$field][\'option_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'option_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'option_\'.$idx];\n            $ttl = $smd_bio_data[$field][\'title_\'.$idx];\n            $stash[\'option\'][$opt] = $ttl;\n        }\n    }\n    if (isset($smd_bio_data[$field][\'chosen_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'chosen_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'chosen_option_\'.$idx];\n            $ttl = $smd_bio_data[$field][\'chosen_title_\'.$idx];\n            $stash[\'chosen\'][$opt] = $ttl;\n        }\n    }\n    if (isset($smd_bio_data[$field][\'default_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'default_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'default_option_\'.$idx];\n            $stash[\'default\'][] = $opt;\n        }\n    }\n\n    $checked_types = array(\'checkbox\', \'radio\', \'yesnoradio\');\n    $selected_types = array(\'list\', \'multilist\');\n\n    // Do the iteration for real now, taking limit and offset into account.\n    // Can only iterate over items that have a count.\n    if (isset($smd_bio_data[$field][$countType])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][$countType]; $idx++) {\n            if (( ($idx-1) >= $offset ) && ( $limit == 0 || (($idx-1) < $limit+$offset) )) {\n                $key = (($usingMap[$using]) ? $usingMap[$using]. \'_\' : \'\') . ($using === \'default\' && $display === \'title\'? \'option\' : $displayMap[$display]) . \'_\' . $idx;\n                $uval = $usingMap[$using] ? $usingMap[$using].\'_\' : \'\';\n                $opt = $smd_bio_data[$field][$uval . \'option_\'.$idx];\n\n                // No title in smd_bio_data for default, so fudge it\n                if ($using === \'default\') {\n                    $ttl = $stash[\'option\'][$opt];\n                } else {\n                    $ttl = $smd_bio_data[$field][$uval . \'title_\'.$idx];\n                }\n\n                $reps = array(\n                    \'{\'.$prefix.\'iterate_option}\' => $opt,\n                    \'{\'.$prefix.\'iterate_title}\' => $ttl,\n                    \'{\'.$prefix.\'iterate_count}\' => $idx,\n                );\n\n                // When iterating all options, indicate which are default and which are chosen\n                if ($using === \'all\') {\n                    if (isset($stash[\'default\']) && in_array($opt, $stash[\'default\'])) {\n                        $reps[\'{\'.$prefix.\'iterate_is_default}\'] = \'1\';\n                        $smd_bio_option_data[$field][\'iterate_is_default\'] = \'1\';\n                    } else {\n                        $reps[\'{\'.$prefix.\'iterate_is_default}\'] = \'0\';\n                        $smd_bio_option_data[$field][\'iterate_is_default\'] = \'0\';\n                    }\n\n                    if (isset($stash[\'chosen\']) && array_key_exists($opt, $stash[\'chosen\'])) {\n                        $flavour = $smd_bio_data[$field][\'type\'];\n                        $html_sel = (in_array($flavour, $checked_types)) ? \'checked\' : ((in_array($flavour, $selected_types)) ? \'selected\' : \'1\');\n                        $reps[\'{\'.$prefix.\'iterate_is_chosen}\'] = $html_sel;\n                        $smd_bio_option_data[$field][\'iterate_is_chosen\'] = $html_sel;\n                    } else {\n                        $reps[\'{\'.$prefix.\'iterate_is_chosen}\'] = \'\';\n                        $smd_bio_option_data[$field][\'iterate_is_chosen\'] = \'\';\n                    }\n                }\n\n                $smd_bio_option_data[$field][\'iterate_option\'] = $opt;\n                $smd_bio_option_data[$field][\'iterate_title\'] = $ttl;\n                $smd_bio_option_data[$field][\'iterate_count\'] = $idx;\n                if ($debug) {\n                    echo \'++ ITERATOR REPLACEMENTS ++\';\n                    dmp($reps);\n                }\n                $out[] = ($thing !== null) ? parse(strtr($thing, $reps)) : ( ($form !== \'\') ? parse_form(strtr($form, $reps)) : $smd_bio_data[$field][$key] );\n            }\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Convenience conditional to test a field/item. Use smd_if for more advanced conditional logic\nfunction smd_if_bio($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'          => \'\',\n        \'item\'           => \'value\',\n        \'value\'          => null,\n        \'case_sensitive\' => 1,\n        \'debug\'          => 0,\n    ), $atts));\n\n    $bdata = is_array($smd_bio_data) ? $smd_bio_data : array();\n    $idata = is_array($smd_bio_option_data) ? $smd_bio_option_data : array();\n    $datapool = array_merge($bdata, $idata);\n    if ($debug) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($datapool);\n    }\n\n    $result = false;\n\n    if (isset($datapool[$field][$item])) {\n        if ($value !== null) {\n            $cval = ($case_sensitive) ? $value : strtolower($value);\n            $citm = ($case_sensitive) ? $datapool[$field][$item] : strtolower($datapool[$field][$item]);\n            $result = ((string)$citm === (string)$cval);\n        } else {\n            $result = ($datapool[$field][$item] !== \'\');\n        }\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// Conditional to test bio meta data\nfunction smd_if_bio_is($atts, $thing = null) {\n    global $smd_bio_meta_data;\n\n    extract(lAtts(array(\n        \'type\'  => \'author\', // author, field\n        \'item\'  => \'first\', // first, last\n        \'debug\' => 0,\n    ), $atts));\n\n    $datapool = is_array($smd_bio_meta_data) ? $smd_bio_meta_data : array();\n    if ($debug) {\n        echo \'++ AVAILABLE BIO META DATA ++\';\n        dmp($datapool);\n    }\n\n    $result = false;\n\n    if (isset($datapool[$type][$item])) {\n        $result = $datapool[$type][$item];\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// Convenience conditional\nfunction smd_if_bio_first_author($atts, $thing = null) {\n    return smd_if_bio_is(array(\'type\' => \'author\', \'item\' => \'first\'), $thing);\n}\n// Convenience conditional\nfunction smd_if_bio_last_author($atts, $thing = null) {\n    return smd_if_bio_is(array(\'type\' => \'author\', \'item\' => \'last\'), $thing);\n}\n\n\n// ------------------------\n// A wrapper to article_custom that auto sets the user to the one specified\n// or the current article\'s author.\n// NOTE: lAtts() is NOT used because that limits the plugin attributes.\nfunction smd_bio_articles($atts, $thing = null) {\n    global $thisarticle;\n\n    $author = (isset($atts[\'author\'])) ? $atts[\'author\'] : (isset($thisarticle[\'authorid\']) ? $thisarticle[\'authorid\'] : \'\');\n    $atts[\'author\'] = $author;\n    return parseArticles($atts, \'1\', $thing);\n}', '/**\n * smd_bio\n *\n * A Textpattern CMS plugin for storing additional biographical author data:\n *  -> Create checkboxes, select lists, radio buttons, text input, image, etc fields\n *  -> Data is captured and stored when a user is created\n *  -> Allow people to manage their own profiles via smd_user_manager\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n *\n * @todo Stop smd_bio_iterate from trashing the bio data for future fields of the same name.\n * @todo Attempt table repair if bio/bio_meta get out of sync.\n * @todo Ability to specify wildcards/matches for fields/authors in the client tag.\n * @todo Specify tooltip extended bio information in prefs (cf. what to do about touch devices).\n */\nif (txpinterface === \'admin\') {\n    add_privs(\'smd_bio\', \'1\');\n    register_tab(\'extensions\', \'smd_bio\', gTxt(\'smd_bio_admin_tab\'));\n    register_callback(\'smd_bio_dispatcher\', \'smd_bio\');\n    register_callback(\'smd_bio_fields\', \'author_ui\', \'extend_detail_form\');\n    register_callback(\'smd_bio_admin_js\', \'admin_side\', \'head_end\');\n\n    // Note these are all pre Txp\'s involvement\n    register_callback(\'smd_bio_save\', \'admin\', \'author_save\', 1);\n    register_callback(\'smd_bio_save\', \'admin\', \'author_save_new\', 1);\n    register_callback(\'smd_bio_delete\', \'admin\', \'admin_multi_edit\', 1);\n    register_callback(\'smd_bio_welcome\', \'plugin_lifecycle.smd_bio\');\n\n    // Doesn\'t hurt much to add these if the plugin\'s not installed.\n    // Since plugins are loaded in load_order and then alphabetical,\n    // at this point smd_user_manager doesn\'t \'exist\' (b < u)\n    register_callback(\'smd_bio_save\', \'smd_um\', \'smd_um_save\', 1);\n    register_callback(\'smd_bio_save\', \'smd_um\', \'smd_um_save_new\', 1);\n    register_callback(\'smd_bio_delete\', \'smd_um\', \'smd_um_multi_edit\', 1);\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_bio_author\')\n            ->register(\'smd_bio_info\')\n            ->register(\'smd_bio_data\')\n            ->register(\'smd_bio_iterate\')\n            ->register(\'smd_bio_articles\')\n            ->register(\'smd_if_bio\')\n            ->register(\'smd_if_bio_is\')\n            ->register(\'smd_if_bio_first_author\')\n            ->register(\'smd_if_bio_last_author\');\n    }\n}\n\nregister_callback(\'smd_bio_form_submit\', \'mem_form.submit\');\n\n// Intercept image and extended bio display on Admin->Users panel\n$smd_bio_step = gps(\'smd_bio_step\');\nif ($smd_bio_step == \'smd_bio_get_image\') {\n    smd_bio_get_image();\n}\nif ($smd_bio_step == \'smd_bio_get_ebio\') {\n    smd_bio_get_ebio();\n}\n\nif (!defined(\'SMD_BIO\')) {\n    define(\"SMD_BIO\", \'smd_bio\');\n}\nif (!defined(\'SMD_BIO_META\')) {\n    define(\"SMD_BIO_META\", \'smd_bio_meta\');\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_get_styles() {\n    $smd_bio_styles = array(\n        \'meta\' =>\n         \'.smd_bio_toggler { display:none; }\',\n        \'tooltip\' =>\n         \'#tooltip { position:absolute; border:1px solid #333; background:#f7f5d1; padding:10px 15px; opacity:.9; color:#333; display:none; max-width:60%; }\',\n    );\n    return $smd_bio_styles;\n}\n\n// -------------------------------------------------------------\n// Install/uninstall jumpoff point\nfunction smd_bio_welcome($evt, $stp) {\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_bio_table_install(0);\n            $msg = \'Pimp your users\';\n            break;\n        case \'deleted\':\n            smd_bio_table_remove(0);\n            break;\n    }\n    return $msg;\n}\n\n// ************************\n// BIO CONFIGURATION\n// ------------------------\nfunction smd_bio_dispatcher($evt, $stp) {\n    $available_steps = array(\n        \'smd_bio_config\'          => false,\n        \'smd_bio_table_install\'   => false,\n        \'smd_bio_table_remove\'    => false,\n        \'smd_bio_meta_add\'        => true,\n        \'smd_bio_multi_edit\'      => true,\n        \'smd_bio_meta_save\'       => true,\n        \'smd_bio_save_pane_state\' => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = \'smd_bio_config\';\n    }\n    $stp();\n}\n\n// ------------------------\n// The Extensions->Bio config panel, made up of two areas: the edit pane and the list pane\nfunction smd_bio_config($msg=\'\') {\n    smd_bio_table_install(0);\n\n    pagetop(gTxt(\'smd_bio_admin_tab\'), $msg);\n\n    echo n.\'<div id=\"smd_bio_container\" class=\"txp-container\">\'.\n        n.smd_bio_meta_edit().\n        n.smd_bio_meta_list().\n        n.\'</div>\';\n}\n\n// ------------------------\nfunction smd_bio_meta_edit() {\n    $smd_bio_types = smd_bio_get_types();\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n\n    $vars = array(\'step\', \'id\', \'title\', \'name\', \'type\', \'coltype\', \'colsize\', \'size\', \'val\', \'position\');\n    $rs = array();\n\n    extract(gpsa($vars));\n    $colsize = (int)$colsize;\n\n    if ($id && $step == \'meta_edit\') {\n        $id = assert_int($id);\n        $rs = safe_row(\'*\', SMD_BIO_META, \"id = $id\");\n        extract($rs);\n    }\n\n    if ($step == \'smd_bio_meta_save\' || $step == \'smd_bio_meta_add\' || $step == \'smd_bio_multi_edit\') {\n        foreach ($vars as $var) {\n            $$var = \'\';\n        }\n    }\n\n    $caption = gTxt(($step == \'meta_edit\') ? \'smd_bio_meta_edit\' : \'smd_bio_meta_add\');\n\n    // Make the name/val pairs for the type selectInput\n    $selv = array();\n    foreach ($smd_bio_types as $widx => $wval) {\n        $selv[$widx] = $wval[\'name\'];\n    }\n\n    // Make the name/val pairs for the coltype selectInput\n    foreach($smd_bio_coltypes as $ctype => $cdata) {\n        $coltypes[$ctype] = $cdata[\'title\'];\n    }\n\n    $toggleState = get_pref(\'pane_smd_bio_coltype_visible\') ? true : false;\n\n    return hed($caption, 1, \' class=\"txp-heading\"\').\n        form(\n            \'<div class=\"txp-edit\">\'.\n            inputLabel(\'name\', ($id && $step == \'meta_edit\' ? strong($name) : fInput(\'text\', \'name\', $name, \'\', \'\', \'\', \'\', \'\', \'name\')), \'name\').\n            inputLabel(\'title\', fInput(\'text\', \'title\', $title, \'\', \'\', \'\', \'\', \'\', \'title\'), \'title\').\n            inputLabel(\'smd_bio_widget_type\', selectInput(\'type\', $selv, $type, false, \'\', \'smd_bio_widget_type\') .sp. \'<a id=\"smd_bio_colgroup\" class=\"txp-summary lever\'.(($toggleState) ? \' expanded\' : \'\').\'\" href=\"#\">\'.gTxt(\'smd_bio_more\').\'</a>\', \'type\').\n            inputLabel(\'smd_bio_coltype\', selectInput(\'coltype\', $coltypes, $coltype, false, \'\', \'smd_bio_coltype\'), \'smd_bio_coltype\', \'\', \'txp-form-field smd_bio_coltype \'.(($toggleState) ? \'\' : \' smd_bio_toggler\')).\n            inputLabel(\'smd_bio_colsize\', fInput(\'number\', \'colsize\', $colsize, \'\', \'\', \'\', \'\', \'\', \'smd_bio_colsize\'), \'smd_bio_colsize\', \'\', \'txp-form-field smd_bio_coltype \'.(($toggleState) ? \'\' : \' smd_bio_toggler\')).\n            inputLabel(\'smd_bio_size\', fInput(\'text\', \'size\', $size, \'\', \'\', \'\', \'\', \'\', \'smd_bio_size\'), \'smd_bio_size\', \'smd_bio_size\').\n            inputLabel(\'smd_bio_value\', text_area(\'val\', \'100\', \'300\', $val, \'smd_bio_value\'), \'smd_bio_value\', \'smd_bio_val\').\n            inputLabel(\'smd_bio_position\', fInput(\'text\', \'position\', $position, \'\', \'\', \'\', \'\', \'\', \'smd_bio_position\'), \'smd_bio_position\').\n            graf(\n                fInput(\'submit\', \'save\', gTxt(\'save\'), \'publish\')\n            ).\n\n            eInput(\'smd_bio\').\n            ($id ? hInput(\'id\', $id).hInput(\'name\', $name).sInput(\'smd_bio_meta_save\') : sInput(\'smd_bio_meta_add\')).\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_text\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_number\" title=\"\'.gTxt(\'smd_bio_sizehelp_numrange\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_range\" title=\"\'.gTxt(\'smd_bio_sizehelp_numrange\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_list\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_multilist\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_radio\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_yesnoradio\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_checkbox\" title=\"\'.gTxt(\'smd_bio_help_unused\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_textarea\" title=\"\'.gTxt(\'smd_bio_sizehelp_textarea\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_image\" title=\"\'.gTxt(\'smd_bio_sizehelp_image\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_email\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_url\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_datetime\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_date\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_month\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_week\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_size_help_time\" title=\"\'.gTxt(\'smd_bio_sizehelp_text\').\'\"\').\n\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_text\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_number\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_range\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_list\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_multilist\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_radio\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_yesnoradio\" title=\"\'.gTxt(\'smd_bio_valhelp_ynr\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_checkbox\" title=\"\'.gTxt(\'smd_bio_valhelp_lrc\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_textarea\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_image\" title=\"\'.gTxt(\'smd_bio_valhelp_image\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_email\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_url\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_datetime\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_date\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_month\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_week\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n            tag(\' \', \'span\', \' id=\"smd_bio_val_help_time\" title=\"\'.gTxt(\'smd_bio_valhelp_text\').\'\"\').\n        \'</div>\'\n    );\n}\n\n// ------------------------\nfunction smd_bio_meta_list() {\n\n    $smd_bio_types = smd_bio_get_types();\n\n    extract(gpsa(array(\'sort\', \'dir\', \'crit\', \'search_method\')));\n    if ($sort === \'\') $sort = get_pref(\'smd_bio_meta_sort_column\', \'name\');\n    if ($dir === \'\') $dir = get_pref(\'smd_bio_meta_sort_dir\', \'asc\');\n    $dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n    if (!in_array($sort, array(\'name\', \'title\', \'type\', \'size\', \'val\', \'position\'))) $sort = \'position\';\n\n    $sort_sql   = $sort.\' \'.$dir;\n\n    set_pref(\'smd_bio_meta_sort_column\', $sort, \'smd_bio\', 2, \'\', 0, PREF_PRIVATE);\n    set_pref(\'smd_bio_meta_sort_dir\', $dir, \'smd_bio\', 2, \'\', 0, PREF_PRIVATE);\n\n    $switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n    $rs = safe_rows_start(\'*\', SMD_BIO_META, \'1=1 ORDER BY \'.$sort_sql);\n    $out = array();\n\n    if ($rs) {\n        $out[] =\n        n. \'<form id=\"smd_bio_form\" action=\"index.php\" method=\"post\" name=\"longform\" class=\"multi_edit_form\">\'.\n\n        n.\'<div class=\"txp-listtables\">\'.\n        startTable(\'\', \'\', \'txp-list\').\n        n.\'<thead>\'.\n        tr(\n            hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' scope=\"col\" title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\').\n            column_head(\'name\', \'name\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'name\' == $sort) ? $dir : \'\').\n            column_head(\'title\', \'title\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'title\' == $sort) ? $dir : \'\').\n            column_head(\'type\', \'type\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'type\' == $sort) ? $dir : \'\').\n            column_head(gTxt(\'smd_bio_size\'), \'size\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'size\' == $sort) ? $dir : \'\').\n            column_head(\'value\', \'val\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'val\' == $sort) ? $dir : \'\').\n            column_head(gTxt(\'smd_bio_position\'), \'position\', \'smd_bio\', true, $switch_dir, \'\', \'\', (\'position\' == $sort) ? $dir : \'\')\n        ).\n        n.\'</thead>\'.\n        n.\'<tbody>\';\n\n        while ($a = nextRow($rs)) {\n            extract(doSpecial($a));\n\n            $out[] = tr(\n                td(fInput(\'checkbox\', \'selected[]\', $name)).\n                td(eLink(\'smd_bio\', \'meta_edit\', \'id\', $id, $name)).\n                td($title).\n                td($smd_bio_types[$type][\'name\']).\n                td($size).\n                td($val).\n                td($position)\n            );\n        }\n\n        $out[] = \'</tbody>\'.\n            endTable().\n            n.\'</div>\'.\n            smd_bio_multiedit_form(\'\', $sort, $dir, $crit, $search_method).\n            tInput().\n            \'</form>\';\n    }\n    return join(n, $out);\n}\n\n// ------------------------\nfunction smd_bio_multiedit_form($page, $sort, $dir, $crit, $search_method) {\n\n    $methods = array(\n        \'delete\' => gTxt(\'delete\'),\n    );\n\n    return multi_edit($methods, \'smd_bio\', \'smd_bio_multi_edit\', $page, $sort, $dir, $crit, $search_method);\n}\n\n// ------------------------\nfunction smd_bio_multi_edit()\n{\n    $selected = ps(\'selected\');\n\n    if (!$selected or !is_array($selected))\n    {\n        smd_bio();\n    }\n\n    $selected = array_map(\'assert_string\', $selected);\n    $method   = ps(\'edit_method\');\n    $changed  = array();\n    $key = \'\';\n\n    switch ($method)\n    {\n        case \'delete\':\n            return smd_bio_meta_del($selected);\n            break;\n\n        default:\n            $key = \'\';\n            $val = \'\';\n            break;\n    }\n\n    smd_bio();\n}\n\n// ------------------------\nfunction smd_bio_meta_make_list($val) {\n\n    if (strpos(strtolower($val), \'smd_bio_fn\') !== false) {\n        // Special function syntax so call the designated\n        // function to retrieve the value(s)\n        $params = do_list($val, \'|\');\n        array_shift($params); // Remove SMD_BIO_FN\n        $fn = array_shift($params);\n\n        $fnbits = do_list($fn, \'::\');\n        $fncall = (isset($fnbits[1])) ? array($fnbits[0], $fnbits[1]) : $fnbits[0];\n        $val = call_user_func_array($fncall, $params);\n\n        if (is_array($val)) {\n            // Fake a string list of name => val pairs\n            $out = array();\n            foreach ($val as $idx => $item) {\n                $out[] = str_replace(\',\', \'&#44;\', $idx . \' => \' . $item); // Preserve commas by encoding them\n            }\n            $val = join(\', \', $out);\n        }\n    }\n\n    // Parse the values\n    $wvals = do_list($val, \'\\r\\n\');\n    if (count($wvals) == 1) {\n        $wvals = do_list($val);\n    }\n    return join(\', \', doArray($wvals, \'trim\'));\n}\n\n// ------------------------\nfunction smd_bio_meta_save() {\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n    $smd_bio_unused = smd_bio_get_unused();\n\n    extract(doSlash(psa(array(\'id\', \'name\', \'title\', \'type\', \'coltype\', \'colsize\', \'size\', \'position\'))));\n    $val = ps(\'val\'); // Defer doSlash() until later\n\n    $id = assert_int($id);\n    $val = doSlash(smd_bio_meta_make_list($val));\n    $hasDefault = $smd_bio_coltypes[$coltype][\'has_default\'];\n\n    // Validate the input to ensure items are emptied for types where they\'re unused\n    foreach ($smd_bio_unused as $unused => $excludes) {\n        if (in_array($type, $excludes)) {\n            $$unused = \'\';\n        }\n    }\n\n    // Validate the input to ensure items that require a column size have one...\n    if (empty($colsize) && $smd_bio_coltypes[$coltype][\'size_req\'] !== false) {\n        $colsize = $smd_bio_coltypes[$coltype][\'size_req\'];\n    }\n\n    // ... and that ones that don\'t require one are removed\n    if ($smd_bio_coltypes[$coltype][\'size_req\'] === false) {\n        $colsize = null;\n    }\n\n    // Try to adjust column type/size if applicable\n    $rs = safe_alter(SMD_BIO, \"CHANGE `$name` `$name` $coltype\" . (($colsize) ? \"($colsize)\" : \'\') . \" NULL\" . (($hasDefault) ? \" DEFAULT NULL\" : \'\'));\n\n    if ($rs) {\n        $rs = safe_update(SMD_BIO_META, \"\n            title = \'$title\',\n            type = \'$type\',\n            size = \'$size\',\n            coltype = \'$coltype\',\n            \" . (($colsize) ? \"colsize = $colsize,\" : \'\') . \"\n            val = \'$val\',\n            position = \'$position\'\",\n            \"id = $id\"\n        );\n\n        if ($rs) {\n            $msg = gTxt(\'smd_bio_meta_updated\', array(\'{name}\' => $name));\n        } else {\n            $msg = array(gTxt(\'smd_bio_meta_update_partial\', array(\'{name}\' => $name)), E_WARNING);\n        }\n    } else {\n        $msg = array(gTxt(\'smd_bio_meta_update_failed\', array(\'{name}\' => $name)), E_WARNING);\n    }\n\n    smd_bio_config($msg);\n}\n\n// ------------------------\nfunction smd_bio_meta_add() {\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n    $smd_bio_unused = smd_bio_get_unused();\n\n    extract(doSlash(psa(array(\'name\', \'title\', \'type\', \'coltype\', \'colsize\', \'size\', \'position\'))));\n    $val = ps(\'val\'); // Defer doSlash() until later\n\n    // Use the title as name if it\'s omitted\n    if ($name === \'\' && $title !== \'\') {\n        $name = $title;\n    }\n\n    $name = smd_bio_sanitize($name);\n\n    if (!empty($name) && smd_bio_meta_check($name)) {\n        $size = (empty($size)) ? 25 : $size;\n        $hasDefault = $smd_bio_coltypes[$coltype][\'has_default\'];\n\n        // Validate the input to ensure items are emptied for types where they\'re unused\n        foreach ($smd_bio_unused as $unused => $excludes) {\n            if (in_array($type, $excludes)) {\n                $$unused = \'\';\n            }\n        }\n\n        // Validate the input to ensure items that require a column size have one...\n        if (empty($colsize) && $smd_bio_coltypes[$coltype][\'size_req\'] !== false) {\n            $colsize = $smd_bio_coltypes[$coltype][\'size_req\'];\n        }\n        // ... and that ones that don\'t require one are removed\n        if ($smd_bio_coltypes[$coltype][\'size_req\'] === false) {\n            $colsize = null;\n        }\n\n        $ret = safe_alter(SMD_BIO, \"ADD `$name` $coltype\" . (($colsize) ? \"($colsize)\" : \'\') . \" NULL\" . (($hasDefault) ? \" DEFAULT NULL\" : \'\'));\n        $val = doSlash(smd_bio_meta_make_list($val));\n\n        if ($ret) {\n            $rs = safe_insert(SMD_BIO_META, \"\n                name = \'$name\',\n                title = \'$title\',\n                type = \'$type\',\n                size = \'$size\',\n                coltype = \'$coltype\',\n                \" . (($colsize) ? \"colsize = $colsize,\" : \'\') . \"\n                val = \'$val\',\n                position = \'$position\'\n            \");\n\n            if ($rs) {\n                smd_bio_config(gTxt(\'smd_bio_meta_added\'));\n                return;\n            }\n        }\n    }\n\n    smd_bio_config(array(gTxt(\'smd_bio_meta_not_added\'), E_ERROR));\n}\n\n// ------------------------\nfunction smd_bio_meta_del($names) {\n\n    $changed = array();\n\n    $names  = $names ? array_map(\'assert_string\', $names) : array(assert_string(ps(\'name\')));\n    $message = \'\';\n\n    foreach ($names as $name) {\n        $exists = smd_bio_meta_check($name);\n        $ret = @safe_alter(SMD_BIO, \"DROP COLUMN `$name`\");\n        if ($ret || $exists) {\n            $ret = safe_delete(SMD_BIO_META, \"name=\'$name\'\");\n            $changed[] = $name;\n        }\n    }\n    smd_bio_config(gTxt(\'smd_bio_meta_deleted\', array(\'{affected}\' => join(\', \', $changed))));\n}\n\n// ------------------------\nfunction smd_bio_meta_check($col) {\n    $ucols = getThings(\'describe `\'.PFX.\'txp_users`\');\n    $bcols = getThings(\'describe `\'.PFX.SMD_BIO.\'`\');\n    $cols = array_merge($ucols, $bcols);\n    return (!in_array($col, $cols));\n}\n\n// ************************\n// ADMIN -> USERS PANEL\n// ------------------------\nfunction smd_bio_fields($evt, $stp, $mt, $data) {\n    global $smd_um_event, $txp_user;\n\n    $out = $vals = array();\n\n    $rstep = gps(\'step\');\n    if(smd_bio_table_exist()) {\n        if (in_array($rstep, array(\'\', \'author_edit\', \'smd_um_edit\', \'smd_um_save\', \'smd_um_change_pass\'))) {\n            extract(gpsa(array(\'user_id\')));\n\n            if (empty($user_id) && !has_privs(\'smd_um.usr.create\')) {\n                // This is a self-edit from smd_user_manager, thus the user_id has not been sent\n                $user_id = safe_field(\'user_id\',\'txp_users\',\"name = \'\".doSlash($txp_user).\"\'\");\n            }\n            // Shame we have to double de-clutch here but we can\'t index on user_id unfortunately (\'cos when\n            // inserting new users, the bio functions run _BEFORE_ an auto_increment ID has been generated by Txp)\n            $uname = safe_field(\'name\',\'txp_users\',\"user_id = \'\".doSlash($user_id).\"\'\");\n            $vals = safe_row(\'*\', SMD_BIO, \"user_ref=\'\".doSlash($uname).\"\'\");\n        }\n\n        $widgets = safe_rows(\'*\', SMD_BIO_META, \'1=1 ORDER BY position\');\n        $tdac_image = \' class=\"smd_bio_image_cell\"\';\n        foreach ($widgets as $widget) {\n            $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : $widget[\'val\'];\n            $title = ($widget[\'title\']) ? $widget[\'title\'] : $widget[\'name\'];\n            $sizeopts = do_list($widget[\'size\']);\n            $size1 = $sizeopts[0];\n            $size2 = (isset($sizeopts[1])) ? $sizeopts[1] : \'\';\n            $size3 = (isset($sizeopts[2])) ? $sizeopts[2] : \'\';\n            $name = \'smd_bio_\'.$widget[\'name\'];\n            switch($widget[\'type\']) {\n                case \'list\':\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $out[] = inputLabel(\n                        $name,\n                        selectInput($name, $selv, $dflt, false, \'\', $name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_select \'.$name\n                    );\n                    break;\n                case \'multilist\':\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $use_val = (isset($vals[$widget[\'name\']]) && ($vals[$widget[\'name\']] !== \'\' || $vals[$widget[\'name\']] !== null)) ? $val : $dflt; // Don\'t use defaults if this field has been previously saved\n                    $selectedVals = do_list($use_val);\n                    $items = array();\n                    $items[] = \'<select name=\"\'.$name.\'\" class=\"list multiple\" multiple=\"multiple\" onchange=\"smd_bio_multisel(\\\'\'.$name.\'\\\');\">\';\n                    foreach ($selv as $idx => $lbl) {\n                        // Not using selectInput() because it doesn\'t support multiples\n                        $items[] = \'<option value=\"ms_\'.$idx.\'\" \'.((in_array($idx, $selectedVals)) ? \' selected=\"selected\"\' : \'\') . \'>\' . $lbl . \'</option>\';\n                    }\n                    $items[] = \'</select>\';\n                    $out[] = inputLabel(\n                        $name,\n                        join(n,$items).fInput(\'hidden\',$name,$use_val,\'\',\'\',\'\',\'\',\'\',$name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_select \'.$name\n                    );\n                    break;\n                case \'radio\':\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $out[] = inputLabel(\n                        $name,\n                        radioSet($selv, $name, $dflt),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_radio \'.$name\n                    );\n                    break;\n                case \'yesnoradio\':\n                    $out[] = inputLabel(\n                        $name,\n                        yesnoRadio($name, $val),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_radio \'.$name\n                    );\n                    break;\n                case \'checkbox\':\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $selv = smd_bio_splitval($widget[\'val\']);\n                    list($selv, $dflt) = smd_bio_get_default($selv, $val);\n                    $use_val = (isset($vals[$widget[\'name\']]) && ($vals[$widget[\'name\']] !== \'\' || $vals[$widget[\'name\']] !== null)) ? $val : $dflt; // Don\'t use defaults if this field has been previously saved\n                    $checkedVals = do_list($use_val);\n                    $items = array();\n                    foreach ($selv as $idx => $lbl) {\n                        // Not using checkbox() because it doesn\'t support onclick in 4.5.x\n                        $items[] = \'<input type=\"checkbox\" name=\"cb_\'.$name.\'\" value=\"\'.$idx.\'\"\'.((in_array($idx, $checkedVals)) ? \' checked=\"checked\"\' : \'\') . \' class=\"checkbox\" onclick=\"smd_bio_checkbox(\\\'\'.$name.\'\\\');\" />\'.$lbl;\n                    }\n                    $out[] = inputLabel(\n                        $name,\n                        join(\'\', $items).fInput(\'hidden\',$name,$use_val,\'\',\'\',\'\',\'\',\'\',$name),\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_checkbox \'.$name\n                    );\n                    break;\n                case \'textarea\':\n                    // Not using text_area() because it doesn\'t have class attribute in 4.5.x\n                    $size1 = ($size1 == \'\' || $size1 == 0) ? 40 : $size1;\n                    $size2 = ($size2 == \'\' || $size2 == 0) ? 5 : $size2;\n                    $out[] = inputLabel(\n                        $name,\n                        \'<textarea id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"smd_bio_textarea\" cols=\"\'.$size1.\'\" rows=\"\'.$size2.\'\">\'.txpspecialchars($val).\'</textarea>\',\n                        $title,\n                        \'\',\n                        \'txp-form-field txp-form-field-textarea smd_bio_textarea \'.$name\n                    );\n                    break;\n                case \'image\':\n                    $parent = $widget[\'val\'];\n                    $val = ($vals && isset($vals[$widget[\'name\']])) ? $vals[$widget[\'name\']] : \'\';\n                    $where = ($parent) ? \"category=\'\".doSlash($parent).\"\'\" : \'1=1\';\n                    $tree = safe_rows(\'*\', \'txp_image\', $where. \' ORDER BY name\');\n                    $selv = array();\n                    foreach ($tree as $row) {\n                        $selv[$row[\'id\']] = $row[\'name\'];\n                    }\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"text\" value=\"\'.txpspecialchars($val).\'\" id=\"\'.$name.\'\" name=\"\'.$name.\'\" size=\'.INPUT_XSMALL.\' class=\"smd_bio_image_id input_xsmall\" />\'.selectInput($name.\'_list\', $selv, $val, true, \'\').\'<span class=\"smd_bio_image\"></span><span class=\"smd_bio_image_data\" title=\"\'.(($size1) ? $size1 : \'\').\',\'.(($size2) ? $size2 : $size1).\'\"></span>\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_image_cell \'.$name\n                    );\n                    break;\n                case \'number\':\n                case \'range\':\n                    $min = ($size1 == \'\') ? \'\' : \" min={$size1}\";\n                    $max = ($size2 == \'\') ? \'\' : \" max={$size2}\";\n                    $jmp = ($size3 == \'\') ? \'\' : \" step={$size3}\";\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"\'.$widget[\'type\'].\'\" value=\"\'.txpspecialchars($val).\'\" name=\"\'.$name.\'\" id=\"\'.$name.\'\"\'.$min.$max.$jmp.\'\" class=\"smd_bio_\'.$widget[\'type\'].\'\" />\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_\'.$widget[\'type\'].\' \'.$name\n                    );\n                    break;\n                case \'date\':\n                case \'month\':\n                case \'week\':\n                case \'time\':\n                case \'datetime\':\n                case \'email\':\n                case \'url\':\n                case \'text\':\n                    // Not using fInput() because it has no MAXLENGTH property and doesn\'t support HTML 5 elements\n                    $size1 = ($size1 == \'\' || $size1 == 0) ? 25 : $size1;\n                    $size2 = ($size2 == \'\' || $size2 == 0) ? $size1 : $size2;\n                    $out[] = inputLabel(\n                        $name,\n                        \'<input type=\"\'.$widget[\'type\'].\'\" value=\"\'.txpspecialchars($val).\'\" name=\"\'.$name.\'\" id=\"\'.$name.\'\" size=\"\'.$size1.\'\" maxlength=\"\'.$size2.\'\" class=\"smd_bio_\'.$widget[\'type\'].\'\" />\',\n                        $title,\n                        \'\',\n                        \'txp-form-field smd_bio_\'.$widget[\'type\'].\' \'.$name\n                    );\n                    break;\n            }\n        }\n    }\n    return join(\'\', $out);\n}\n\n// ------------------------\n// Read a name/val array and remove any [*] marker which indicates a default item\nfunction smd_bio_get_default($list, $curr) {\n    $out = array();\n    $dflt = \'\';\n    $new_dflt = array();\n    foreach ($list as $item => $value) {\n        $value = str_replace(\'&#44;\', \',\', $value); // Revert encoded commas to real literals @see smd_bio_meta_make_list\n        if (($pos = strpos($value, \'[*]\')) !== false) {\n            $out[$item] = substr($value, 0, $pos);\n            $new_dflt[] = $item;\n        } else {\n            $out[$item] = $value;\n        }\n        if ($item == $curr) {\n            $dflt = $item;\n        }\n    }\n    $dflt = ($dflt) ? $dflt : join(\',\', $new_dflt);\n\n    return array($out, $dflt);\n}\n\n// ------------------------\n// AJAX calls\nfunction smd_bio_get_image() {\n    global $img_dir;\n\n    $id = gps(\'id\');\n    if ($id) {\n        $rs = safe_row(\'*\', \'txp_image\', \"id = \'\".doSlash($id).\"\'\");\n        extract($rs);\n        $out = array();\n        $out[\'thumb\'] = ($thumbnail) ? hu.$img_dir.\'/\'.$id.\'t\'.$ext : \'\';\n        $out[\'image\'] = hu.$img_dir.\'/\'.$id.$ext;\n        $out[\'w\'] = $w;\n        $out[\'h\'] = $h;\n        $out[\'thw\'] = ($thumb_w) ? $thumb_w : \'\';\n        $out[\'thh\'] = ($thumb_h) ? $thumb_h : \'\';\n        send_xml_response($out);\n    } else {\n        send_xml_response();\n    }\n    exit;\n}\n\n// ------------------------\n// IMPORTANT: constants NOT used for table names\nfunction smd_bio_get_ebio() {\n    include_once txpath.\'/publish/taghandlers.php\';\n\n    $id = gps(\'id\');\n    $core = array(\'user_ref\');\n    $rs = safe_row(\'*\', \'smd_bio\', \"user_ref = \'\".doSlash($id).\"\'\");\n    $meta = safe_rows(\'*\', \'smd_bio_meta\', \"1=1\");\n//  $include = array_merge($core, array(\'mug\', \'cell\', \'department\')); // TODO: get these from prefs/meta table\n    $allowed_types = array(\'text\', \'textarea\', \'email\', \'url\', \'date\', \'month\', \'week\', \'time\', \'datetime\', \'number\', \'range\');\n    $out = array();\n    foreach ($rs as $idx => $val) {\n//      if (in_array($idx, $include)) {\n            if (in_array($idx, $core)) {\n                $out[$idx] = doSlash($val);\n            } else {\n                foreach($meta as $row) {\n                    if ($row[\'name\'] == $idx) {\n                        if ($row[\'type\'] == \"image\") {\n                            // Crude str_replace() to remove javascript-breaking single quotes\n                            $out[$idx] = \'smd_image::\'.str_replace(\"\'\", \'\', thumbnail(array(\'id\'=> $val)));\n                        } else if (in_array($row[\'type\'], $allowed_types)) {\n                            $out[$idx] = $row[\'title\'].\'::\'.txpspecialchars(strip_tags($val), ENT_QUOTES);\n                        }\n                        break;\n                    }\n                }\n            }\n//      }\n    }\n    send_xml_response($out);\n    exit;\n}\n\n// Inject admin-side javascript\n// ------------------------\nfunction smd_bio_admin_js($evt, $stp) {\n    global $event, $step;\n\n    $smd_bio_styles = smd_bio_get_styles();\n\n    $runon = array(\n        \'admin\' => array(\n            \'evt\' => array(\'admin\', \'smd_um\'),\n            \'stp\' => array(\'\', \'smd_um\', \'smd_um_edit\', \'smd_um_edit\', \'smd_um_save\', \'smd_um_save_new\', \'smd_um_change_pass\', \'author_edit\', \'author_save\', \'author_save_new\'),\n        ),\n        \'bio\' => array(\n            \'evt\' => array(\'smd_bio\'),\n        ),\n    );\n\n    // ********\n    // js+css for Admin->Users tab\n    // ********\n    if (in_array($event, $runon[\'admin\'][\'evt\']) !== false && in_array($step, $runon[\'admin\'][\'stp\']) !== false) {\n        $css_custom = safe_field(\'css\', \'txp_css\', \"name=\'smd_bio\'\");\n        $css = \'<style type=\"text/css\">\' . $css_custom .n. $smd_bio_styles[\'tooltip\'] . \'</style>\';\n\n    echo <<<EOJS\n<script type=\"text/javascript\">\n//<![CDATA[\n\n// Concatenate checkbox options for storage\nfunction smd_bio_checkbox(dest) {\n    var out = [];\n    jQuery(\"#user_edit :checkbox\").each(function() {\n        var item = jQuery(this);\n        if (item.attr(\'name\').replace(\'cb_\',\'\') == dest) {\n            if (item.prop(\'checked\') == true) {\n                out.push(item.val());\n            }\n        }\n    });\n    jQuery(\'#\'+dest).val(out.join(\',\'));\n}\n// Concatenate multi select list options for storage\nfunction smd_bio_multisel(dest) {\n    var out = [];\n    jQuery(\"#user_edit select.multiple\").each(function() {\n        var item = jQuery(this);\n        if (item.attr(\'name\') == dest) {\n            // You\'re the one that I want, ooh ooh oooohhh\n            jQuery(item).children(\":selected\").each(function() {\n                out.push(jQuery(this).val().replace(\'ms_\',\'\'));\n            });\n        }\n    });\n    jQuery(\'#\'+dest).val(out.join(\',\'));\n}\njQuery(function() {\n    // Grab images from the server when the select/textbox change\n    jQuery(\".smd_bio_image_id\").blur(function() {\n        id = jQuery(this).attr(\'id\');\n        val = jQuery(this).val();\n        smd_bio_get_image(id, val);\n    }).blur();\n    jQuery(\".smd_bio_image_cell select\").change(function() {\n        id = jQuery(this).parent().find(\'.smd_bio_image_id\').attr(\'id\');\n        val = jQuery(this).val();\n        smd_bio_get_image(id, val);\n    });\n    function smd_bio_get_image(id, val) {\n        jQuery(\"#\" + id + \" ~ select option[value=\'\"+val+\"\']\").prop(\"selected\", true);\n        jQuery(\"#\" + id).val(val);\n        var dims = jQuery(\"#\" + id + \" ~ .smd_bio_image_data\").attr(\'title\');\n        var size = new Array();\n        if (dims) {\n            size = dims.split(\",\");\n        }\n        sendAsyncEvent({\n                event: textpattern.event,\n                smd_bio_step: \'smd_bio_get_image\',\n                id: val\n            }, function(data) {\n                data = jQuery(data);\n                if (data) {\n                    var full = 0;\n                    var imgLink = data.find(\'thumb\').attr(\'value\');\n                    if (imgLink == \'\') {\n                        full = 1;\n                        var imgLink = data.find(\'image\').attr(\'value\');\n                    }\n                    if (imgLink) {\n                        if (size[0] == \'\') {\n                            if (full == 1) {\n                                size[0] = data.find(\'w\').attr(\'value\');\n                            } else {\n                                size[0] = data.find(\'thw\').attr(\'value\');\n                            }\n                        }\n                        if (size[1] == \'\') {\n                            if (full == 1) {\n                                size[1] = data.find(\'h\').attr(\'value\');\n                            } else {\n                                size[1] = data.find(\'thh\').attr(\'value\');\n                            }\n                        }\n                        jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeIn().html(\'<img src=\"\'+imgLink+\'\" width=\"\'+size[0]+\'\" height=\"\'+size[1]+\'\" />\');\n                    } else {\n                        jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeOut().empty();\n                    }\n                } else {\n                    jQuery(\"#\" + id + \" ~ .smd_bio_image\").fadeOut().empty();\n                }\n            }\n        );\n    }\n\n    // Grab the extended info when hovering an author in the list\n    jQuery(\"#users_form tbody tr, #smd_um_form tbody tr\").hover(function(e) {\n        var tt = \'\';\n        var row = jQuery(this);\n        var hovItem = row.find(\".login-name\");\n\n        if (row.data(\'tooltip\') == undefined) {\n            var person = row.find(\".login-name\").text();\n\n            sendAsyncEvent({\n                    event: textpattern.event,\n                    smd_bio_step: \'smd_bio_get_ebio\',\n                    id: person\n                }, function(data) {\n                    data = jQuery(data);\n                        var entry = data.find(\'user_ref\');\n                        out = \'\';\n                        entry.nextAll().each(function(item) {\n                            node = jQuery(this).context.nodeName;\n                            if (node != \'http-status\') {\n                                vall = jQuery(this).attr(\'value\');\n                                if (vall) {\n                                    vsplit = vall.split(\'::\');\n                                    if (vsplit[0].indexOf(\'smd_image\') < 0) {\n                                        out += ((vsplit.length>1) ? vsplit[0] : node) + \': \';\n                                    }\n                                    vall = ((vsplit.length>1) ? vsplit[1] : vsplit[0]);\n                                    out += vall + \'<br/>\';\n                                } else {\n                                    out += node+\': \';\n                                }\n                            }\n                        });\n                        row.data(\'tooltip\', out);\n//                      hovItem.trigger(\'mouseover\'); // Trigger the hover state when the data is loaded\n                    }\n            );\n        }\n\n        xOffset = 30;\n        yOffset = 25;\n        hovItem.hover(function(e) {\n            var tt = jQuery(this).parent().data(\'tooltip\');\n            if (tt != \'\') {\n                jQuery(\"body\").append(\"<p id=\'tooltip\'>\"+ tt +\"</p>\");\n                jQuery(\"#tooltip\")\n                    .css(\"top\",(e.pageY - xOffset) + \"px\")\n                    .css(\"left\",(e.pageX + yOffset) + \"px\")\n                    .fadeIn(\"fast\");\n            }\n        },\n        function() {\n            jQuery(\"#tooltip\").remove();\n        });\n        hovItem.mousemove(function(e) {\n            jQuery(\"#tooltip\")\n                .css(\"top\",(e.pageY - xOffset) + \"px\")\n                .css(\"left\",(e.pageX + yOffset) + \"px\");\n        });\n    });\n});\n//]]>\n</script>\n{$css}\nEOJS;\n    }\n\n    // ********\n    // js for Extensions->Bio config tab\n    // ********\n    if (in_array($event, $runon[\'bio\'][\'evt\'])) {\n        $smd_bio_types = smd_bio_get_types();\n        $smd_bio_unused = smd_bio_get_unused();\n\n        $css = \'<style type=\"text/css\">\' . $smd_bio_styles[\'meta\'] . \'</style>\';\n        $js_unused = join(\',\', doArray($smd_bio_unused[\'size\'], \'doQuote\'));\n\n        foreach ($smd_bio_types as $type => $data) {\n            $type_json[] = \'smd_bio_types[\"\'.$type.\'\"] = { \"dflt_type\": \"\'.$data[\'coltype\'].\'\", \"dflt_size\": \"\'.$data[\'colsize\'].\'\", \"fixed\": \"\'.$data[\'fixed\'].\'\" };\';\n        }\n        $type_json = join(n, $type_json);\n\n        echo <<<EOJS\n<script type=\"text/javascript\">\n//<![CDATA[\nvar smd_bio_unused = [{$js_unused}];\nvar smd_bio_types = [];\nvar destColtype = \"#page-smd_bio select[name=\'coltype\']\";\nvar destColsize = \"#page-smd_bio input[name=\'colsize\']\";\n{$type_json}\n\njQuery(function() {\n    // Perform actions when the Type is changed\n    // Action #1: size box. Auto-update this when Edit clicked as well as when select list is altered\n    jQuery(\"#smd_bio_widget_type\").change(function() {\n        var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n        var destSize = \"#page-smd_bio input[name=\'size\']\";\n\n        // Grey out the Size box for those items that don\'t use it\n        if (jQuery.inArray(theType, smd_bio_unused) > -1) {\n            jQuery(destSize).prop(\"disabled\", true);\n            jQuery(destSize).parent().prev().css(\"color\", \'#999\');\n        } else {\n            jQuery(destSize).prop(\"disabled\", false);\n            jQuery(destSize).parent().prev().css(\"color\", \'\');\n        }\n    }).change();\n\n    // Action #2: coltype/colsize. Can\'t amalgamate these with the \'size\' onchange because\n    // this only changes when select list changed (i.e. not when Edit first clicked)\n    jQuery(\"#smd_bio_widget_type\").change(function() {\n        var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n\n        // Preselect the coltype + colsize based on the defaults\n        jQuery(destColtype + \" option[value=\'\"+smd_bio_types[theType].dflt_type+\"\']\").prop(\'selected\', true);\n        jQuery(destColsize).val(smd_bio_types[theType].dflt_size);\n\n        if (smd_bio_types[theType].fixed == \'\') {\n            jQuery(destColtype).prop(\"disabled\", false);\n            jQuery(destColsize).prop(\"disabled\", false);\n            jQuery(destColtype).parent().prev().css(\"color\", \'\');\n            jQuery(destColsize).parent().prev().css(\"color\", \'\');\n        } else {\n            jQuery(destColtype).prop(\"disabled\", true);\n            jQuery(destColsize).prop(\"disabled\", true);\n            jQuery(destColtype).parent().prev().css(\"color\", \'#999\');\n            jQuery(destColsize).parent().prev().css(\"color\", \'#999\');\n        }\n    });\n\n    // Force the auto-change if the type is of a fixed variety\n    theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n    if (smd_bio_types[theType].fixed != \'\') {\n        jQuery(\"#smd_bio_widget_type\").change();\n    }\n\n    // When clicking Save, enable the coltype/colsize boxes so the values are transmitted in the _POST array\n    jQuery(\"#page-smd_bio input[name=\'save\']\").click(function() {\n        jQuery(destColtype).prop(\"disabled\", false);\n        jQuery(destColsize).prop(\"disabled\", false);\n    });\n\n    // Pop up the help tooltips based on the current Type\n    jQuery(\".edit-smd-bio-size .pophelp\").hover(\n        function(e) {\n            var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n            spanid = \'span#smd_bio_size_help_\'+theType;\n            this.title = jQuery(spanid).attr(\"title\");\n        }, function(e) {\n            this.title = \'\';\n        });\n    jQuery(\".edit-smd-bio-value .pophelp\").hover(\n        function(e) {\n            var theType = jQuery(\"#smd_bio_widget_type option:selected\").val();\n            spanid = \'span#smd_bio_val_help_\'+theType;\n            this.title = jQuery(spanid).attr(\"title\");\n        }, function(e) {\n            this.title = \'\';\n        });\n\n    // Handle opening/closing the coltype area\n    jQuery(\'#smd_bio_colgroup\').click(function(ev) {\n        ev.preventDefault();\n        jQuery(this).toggleClass(\'expanded\');\n        jQuery(\'.smd_bio_coltype\').toggle();\n\n        sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'smd_bio_save_pane_state\',\n                pane: \'coltype\',\n                visible: (jQuery(this).hasClass(\'expanded\'))\n            }\n        );\n    });\n});\n//]]>\n</script>\n{$css}\nEOJS;\n    }\n}\n\n// ------------------------\n// Make the name/val pairs for selectInput / radio / checkbox sets\nfunction smd_bio_splitval($val) {\n    $selv = array();\n\n    $wvals = preg_split(\"/[\\r\\n,]+/\", $val, -1, PREG_SPLIT_NO_EMPTY);\n    foreach ($wvals as $wval) {\n        $prts = explode(\'=>\', $wval);\n        if (count($prts) == 1) {\n            $prts[1] = $prts[0];\n            $prts[0] = smd_bio_sanitize($prts[0]);\n        }\n        $selv[trim($prts[0])] = trim($prts[1]);\n    }\n    return $selv;\n}\n\n// ------------------------\n// Super-sanitize the passed value so we can make variable names from the returned string\nfunction smd_bio_sanitize($val) {\n    return strtolower(str_replace(\"-\", \"_\", sanitizeForUrl($val)));\n}\n\n// ------------------------\nfunction smd_bio_save($evt, $stp) {\n    global $prefs;\n\n    if (smd_bio_table_exist()) {\n        $targetvars = array();\n        extract(doSlash(psa(array(\'privs\', \'name\', \'email\', \'RealName\', \'user_id\'))));\n        if (get_pref(\'smd_bio_sanitize_name\', 0) > 0) {\n            // Sanitize and pass the new name forward to the actual txp_user save routine\n            $name = strtolower(sanitizeForUrl($name));\n            $_POST[\'name\'] = $name;\n        }\n        $length = function_exists(\'mb_strlen\') ? mb_strlen($name, \'8bit\') : strlen($name);\n\n        if (($user_id || $name) and $length <= 64 and is_valid_email($email)) {\n            foreach ($_POST as $idx => $item) {\n                if (strpos($idx, \'smd_bio_\') === 0) {\n                    $targetvars[] = $idx;\n                }\n            }\n\n            // Double de-clutch again... dammit :-(\n            $user_id = gps(\'user_id\');\n            if ($user_id) {\n                $user_ref = safe_field(\'name\',\'txp_users\',\"user_id = \'$user_id\'\");\n            } else {\n                $user_ref = $name;\n            }\n            extract(gpsa($targetvars));\n            $bcols = getThings(\'describe `\'.PFX.SMD_BIO.\'`\');\n            $sqlSet = array();\n            foreach ($targetvars as $var) {\n                $colname = str_replace(\'smd_bio_\', \'\', $var);\n                if (in_array($colname, $bcols)) {\n                    $sqlSet[] = \"`$colname` = \'\".doSlash($$var).\"\'\";\n                }\n            }\n            if ($sqlSet) {\n                $rs = safe_upsert(SMD_BIO, join(\',\', $sqlSet), \"`user_ref` = \'\".doSlash($user_ref).\"\'\");\n            }\n        }\n    }\n}\n\n// ------------------------\nfunction smd_bio_delete($evt, $stp) {\n    global $txp_user;\n\n    if (smd_bio_table_exist()) {\n        // Since we are executing \'pre\' delete we need to unfortunately duplicate some of the checks\n        // from txp_admin.php so we minimise the opportunity to delete someone by mistake\n        $selected = ps(\'selected\');\n        $method = ps(\'edit_method\');\n        if (!$selected or !is_array($selected)) {\n            return;\n        }\n        if ($method != \'delete\') {\n            return;\n        }\n\n        $names = safe_column(\'name\', \'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($selected)).\"\') AND name != \'\".doSlash($txp_user).\"\'\");\n        if (!$names) return;\n\n        $assign_assets = ps(\'assign_assets\');\n        if ($assign_assets === \'\') {\n            return;\n        } elseif (in_array($assign_assets, $names)) {\n            return;\n        } else {\n            // All the checks passed -- do it\n            safe_delete(SMD_BIO, \"user_ref IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\");\n        }\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_save_pane_state() {\n    global $event;\n    $panes = array(\'coltype\');\n    $pane = gps(\'pane\');\n    if (in_array($pane, $panes)) {\n        set_pref(\"pane_smd_bio_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), $event, PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_form_submit() {\n    global $mem_form_type;\n\n    if (!in_array($mem_form_type, array(\'smd_bio\', \'mem_self_register\', \'mem_self_user_edit\'))) return;\n\n    $author = smd_bio_find_author();\n\n    if ($author) {\n        $core_cols = smd_bio_core_cols();\n        $meta_cols = safe_column(\'name\', SMD_BIO_META, \'1=1\');\n        $query_params = array(\n            \'core\' => array(\n                \'tbl\' => \'txp_users\',\n                \'ref\' => \'name\',\n            ),\n            \'bio\' => array(\n                \'tbl\' => SMD_BIO,\n                \'ref\' => \'user_ref\',\n            )\n        );\n\n        foreach (stripPost() as $key => $val) {\n            // Only care about smd_bio_ prefixed entries in $_POST\n            if (strpos($key, \'smd_bio_\') !== false) {\n                // Strip off the known prefix\n                $raw_key = str_replace(\'smd_bio_\', \'\', $key);\n                if (in_array($raw_key, $core_cols)) {\n                    $query_params[\'core\'][\'cols\'][] = doSlash($raw_key) . \'=\'. doQuote(doSlash($val));\n                } else if (in_array($raw_key, $meta_cols)) {\n                    $query_params[\'bio\'][\'cols\'][] = doSlash($raw_key) . \'=\'. doQuote(doSlash($val));\n                }\n            }\n        }\n\n        // If there are some cols set we\'re good to update/insert depending if $author exists or not\n        foreach ($query_params as $type => $data) {\n            if (isset($data[\'cols\'])) {\n                $params = join(\', \', $data[\'cols\']);\n                safe_upsert($data[\'tbl\'], $params, $data[\'ref\'] .\'=\'. doQuote(doSlash($author)));\n            }\n        }\n    }\n}\n\n// Try some of the usual suspects for locating authorship\nfunction smd_bio_find_author($author_in=\'\', $places=array(\'biotag\', \'txpuser\', \'ili\', \'profile\', \'selfreg\', \'list\', \'article\', \'image\', \'file\', \'link\') ) {\n    global $smd_bio_author, $txp_user, $mem_form_type, $mem_profile, $pretext, $thisarticle, $thisimage, $thisfile, $thislink;\n    static $smd_bio_ili = 0;\n\n    $places = is_array($places) ? $places : do_list($places);\n\n    // Check for any passed-in author first\n    $author = $author_in;\n\n    foreach ($places as $place) {\n        if ($author != \'\') break;\n        switch ($place) {\n            case \'biotag\':\n                // From smd_bio_author tag\n                $author = ($smd_bio_author != \'\') ? $smd_bio_author : $author;\n            break;\n            case \'txpuser\':\n                // From global user variable\n                $author = ($txp_user != \'\') ? $txp_user : $author;\n            break;\n            case \'ili\':\n                // From currently logged-in user\n                $smd_bio_ili = ($smd_bio_ili === 0) ? is_logged_in() : $smd_bio_ili;\n                if ($smd_bio_ili) {\n                    $author = $smd_bio_ili[\'name\'];\n                }\n            break;\n            case \'profile\':\n                // From current self-edit user profile\n                $author = (isset($mem_profile[\'name\'])) ? $mem_profile[\'name\'] : $author;\n            break;\n            case \'selfreg\':\n                // New author from mem_self_reg?\n                $aname = ps(\'name\');\n                if ($aname == \'\') {\n                    // New author from custom mem_form?\n                    $aname = ps(\'smd_bio_name\');\n                }\n                if ( ($mem_form_type == \'mem_self_register\' || $mem_form_type == \'smd_bio\') && ($aname != \'\') ) {\n                    // As long as $aname doesn\'t exist, let this new author in\n                    //TODO: Cache this?\n                    $exists = safe_row(\'*\', \'txp_user\', \"name=\'\".doSlash($aname).\"\'\");\n                    if (!$exists) {\n                        $author = $aname;\n                    }\n                }\n            break;\n            case \'list\':\n                $author = (isset($pretext[\'author\'])) ? $pretext[\'author\'] : $author;\n            break;\n            case \'article\':\n                $author = (isset($thisarticle[\'authorid\'])) ? $thisarticle[\'authorid\'] : $author;\n            break;\n            case \'image\':\n                $author = (isset($thisimage[\'author\'])) ? $thisimage[\'author\'] : $author;\n            break;\n            case \'file\':\n                $author = (isset($thisfile[\'author\'])) ? $thisfile[\'author\'] : $author;\n            break;\n            case \'link\':\n                $author = (isset($thislink[\'author\'])) ? $thislink[\'author\'] : $author;\n            break;\n        }\n    }\n\n    return $author;\n}\n\n// -------------------------------------------------------------\n// Return a list of core columns in the txp_users table.\n// This could be done programmatically but isn\'t: save a\n// query, save the world\nfunction smd_bio_core_cols() {\n    // The indices are the gTxt() names for the associated field\n    return array(\n        \'id\'         => \'user_id\',\n        \'name\'       => \'name\',\n        \'real_name\'  => \'RealName\',\n        \'email\'      => \'email\',\n        \'privileges\' => \'privs\',\n        \'date\'       => \'last_access\'\n    );\n}\n\n// -------------------------------------------------------------\n// In alphabetical order or sorting on the admin panel gets screwy\nfunction smd_bio_get_types() {\n    $smd_bio_types = array(\n        \'checkbox\' => array(\n                \'name\'    => \'Checkbox(es)\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'date\' => array(\n                \'name\'    => \'Date\',\n                \'coltype\' => \'date\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'datetime\' => array(\n                \'name\'    => \'Date/Time\',\n                \'coltype\' => \'datetime\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'email\' => array(\n                \'name\'    => \'E-mail\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 254,\n                \'fixed\'   => false,\n                ),\n        \'image\' => array(\n                \'name\'    => \'Image\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => true,\n                ),\n        \'list\' => array(\n                \'name\'    => \'Select list\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'month\' => array(\n                \'name\'    => \'Month\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'multilist\' => array(\n                \'name\'    => \'Multi select list\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'number\' => array(\n                \'name\'    => \'Number\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => false,\n                ),\n        \'radio\' => array(\n                \'name\'    => \'Radio set\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n        \'range\' => array(\n                \'name\'    => \'Range\',\n                \'coltype\' => \'int\',\n                \'colsize\' => 11,\n                \'fixed\'   => false,\n                ),\n        \'text\' => array(\n                \'name\'    => \'Text box\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'textarea\' => array(\n                \'name\'    => \'Text area\',\n                \'coltype\' => \'mediumtext\',\n                \'colsize\' => 4096,\n                \'fixed\'   => false,\n                ),\n        \'time\' => array(\n                \'name\'    => \'Time\',\n                \'coltype\' => \'time\',\n                \'colsize\' => null,\n                \'fixed\'   => false,\n                ),\n        \'url\' => array(\n                \'name\'    => \'URL\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'week\' => array(\n                \'name\'    => \'Week\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => false,\n                ),\n        \'yesnoradio\' => array(\n                \'name\'    => \'Yes/no radio\',\n                \'coltype\' => \'varchar\',\n                \'colsize\' => 255,\n                \'fixed\'   => true,\n                ),\n    );\n\n    return $smd_bio_types;\n}\n\n// -------------------------------------------------------------\n// In alphabetical order or sorting on the admin panel gets screwy\nfunction smd_bio_get_coltypes() {\n    $smd_bio_coltypes = array(\n        \'date\' => array(\n            \'title\'       => \'Date [YYYY-MM-DD]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'datetime\' => array(\n            \'title\'       => \'Date + time [YYYY-MM-DD HH:MN:SS]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'double\' => array(\n            \'title\'       => \'Double precision float\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'float\' => array(\n            \'title\'       => \'Floating point number\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'int\' => array(\n            \'title\'       => \'Integer [up to 4294967295]\',\n            \'size_req\'    => \'11\',\n            \'has_default\' => true,\n        ),\n        \'longtext\' => array(\n            \'title\'       => \'Long text [up to 4GB]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'mediumint\' => array(\n            \'title\'       => \'Medium integer [up to 16777215]\',\n            \'size_req\'    => \'8\',\n            \'has_default\' => true,\n        ),\n        \'mediumtext\' => array(\n            \'title\'       => \'Medium text [up to 16MB]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'smallint\' => array(\n            \'title\'       => \'Small integer [up to 65535]\',\n            \'size_req\'    => \'4\',\n            \'has_default\' => true,\n        ),\n        \'text\' => array(\n            \'title\'       => \'Text [up to 64KB chars]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'time\' => array(\n            \'title\'       => \'Time [HH:MN:SS]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'timestamp\' => array(\n            \'title\'       => \'Timestamp [seconds since UNIX epoch]\',\n            \'size_req\'    => false,\n            \'has_default\' => true,\n        ),\n        \'tinyint\' => array(\n            \'title\'       => \'Tiny integer [up to 255]\',\n            \'size_req\'    => \'3\',\n            \'has_default\' => true,\n        ),\n        \'tinytext\' => array(\n            \'title\'       => \'Tiny text [up to 255 chars]\',\n            \'size_req\'    => false,\n            \'has_default\' => false,\n        ),\n        \'varbinary\' => array(\n            \'title\'       => \'Binary varchar [size subject to max row size]\',\n            \'size_req\'    => \'255\',\n            \'has_default\' => true,\n        ),\n        \'varchar\' => array(\n            \'title\'       => \'Varchar [size subject to max row size]\',\n            \'size_req\'    => \'255\',\n            \'has_default\' => true,\n        ),\n    );\n\n    return $smd_bio_coltypes;\n}\n\n// -------------------------------------------------------------\nfunction smd_bio_get_unused() {\n    $smd_bio_unused = array(\n        \'size\' => array(\'list\', \'multilist\', \'radio\', \'yesnoradio\', \'checkbox\'),\n    );\n\n    return $smd_bio_unused;\n}\n\n// ************************\n// TABLE MANAGEMENT\n// ------------------------\nfunction smd_bio_table_exist() {\n    static $smd_bio_table_ok = array();\n\n    if (isset($smd_bio_table_ok[\'meta\'])) {\n        return ($smd_bio_table_ok[\'meta\'] === $smd_bio_table_ok[\'field\']);\n    }\n\n    $meta = safe_count(SMD_BIO_META, \'1=1\');\n    $flds = @safe_show(\'columns\', SMD_BIO);\n\n    $smd_bio_table_ok[\'meta\'] = (int)$meta;\n    $smd_bio_table_ok[\'field\'] = (int)count($flds) - 1; // Subtract the user_ref column as it\'s always present\n\n    return($smd_bio_table_ok[\'meta\'] === $smd_bio_table_ok[\'field\']);\n}\n\n// ------------------------\nfunction smd_bio_table_install($showpane=1) {\n    global $DB;\n\n    $smd_bio_types = smd_bio_get_types();\n    $smd_bio_coltypes = smd_bio_get_coltypes();\n\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $msg = \'\';\n    $debug = gps(\'debug\');\n\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_BIO.\"` (\n        `user_ref` varchar(64) NOT NULL default \'\',\n        UNIQUE KEY `user_ref` (`user_ref`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8, PACK_KEYS=1\";\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_BIO_META.\"` (\n        `id` int(4) NOT NULL auto_increment,\n        `title` varchar(64) NULL default \'\' COLLATE utf8_general_ci,\n        `name` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `type` set(\".doQuote(join(\"\',\'\", array_keys($smd_bio_types))).\") NOT NULL default \'text\',\n        `size` varchar(10) NULL default 0,\n        `coltype` set(\".doQuote(join(\"\',\'\", array_keys($smd_bio_coltypes))).\") NOT NULL default \'varchar\',\n        `colsize` smallint(4) NULL default 0,\n        `val` text NULL COLLATE utf8_general_ci,\n        `position` varchar(16) NULL default \'\',\n        PRIMARY KEY (`id`),\n        UNIQUE KEY (`name`)\n    ) ENGINE=MyISAM, CHARACTER SET=utf8, AUTO_INCREMENT=1\";\n\n    if ($debug) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Handle upgrades from v0.3x to v0.40.\n    // Upgrade table collation if necessary\n    $ret = getRows(\"SHOW TABLE STATUS WHERE name IN (\'\".PFX.SMD_BIO.\"\', \'\".PFX.SMD_BIO_META.\"\')\");\n    if ($ret[0][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_BIO_META, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n    if ($ret[1][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_BIO, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\');\n    }\n\n    // Alter the position field from int to varchar so positioning can be non-numeric\n    $ret = @safe_field(\"DATA_TYPE\", \"INFORMATION_SCHEMA.COLUMNS\", \"table_name = \'\".PFX.SMD_BIO_META.\"\' AND table_schema = \'\" . $DB->db . \"\' AND column_name = \'position\'\");\n    if ($ret != \'varchar\') {\n        safe_alter(SMD_BIO_META, \"CHANGE `position` `position` VARCHAR( 16 ) NULL DEFAULT \'\'\", $debug);\n    }\n    // Add the coltype and colsize columns\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_BIO_META.\'`\');\n    if (!in_array(\'coltype\', $flds)) {\n        safe_alter(SMD_BIO_META, \"ADD `coltype` SET(\".doQuote(join(\"\',\'\", array_keys($smd_bio_coltypes))).\") NOT NULL default \'\' after `size`\", $debug);\n        safe_alter(SMD_BIO_META, \"ADD `colsize` SMALLINT(4) NULL default 0 after `coltype`\", $debug);\n    }\n    // Add multiple select list & date flavour support to the \'type\' set, and rename \'text_input\' to just \'text\'\n    $toChange = safe_column(\'id\', SMD_BIO_META, \'type=\"text_input\"\');\n    $fld = getRows(\"SHOW FULL COLUMNS FROM `\".PFX.SMD_BIO_META.\"` LIKE \'type\'\");\n    $ft = $fld[0][\'Type\'];\n    if ( (strpos($ft, \'multilist\') === false) || (strpos($ft, \'text_input\') !== false) || (strpos($ft, \'month\') === false) ) {\n        safe_alter(SMD_BIO_META, \"CHANGE `type` `type` SET(\".doQuote(join(\"\',\'\", array_keys($smd_bio_types))).\")\", $debug);\n    }\n    if ($toChange) {\n        safe_update(SMD_BIO_META, \'type=\"text\"\', \"id in (\'\". join(\"\',\'\", $toChange) .\"\')\", $debug);\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_bio_tbl_installed\');\n    } else {\n        $msg = gTxt(\'smd_bio_tbl_not_installed\');\n    }\n\n    if ($showpane) {\n        smd_bio_config($msg);\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_bio_table_remove($showpane=1) {\n    $ret = $msg = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_BIO. \"; \";\n    $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_BIO_META. \"; \";\n    if(gps(\'debug\')) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $msg = gTxt(\'smd_bio_tbl_removed\');\n    } else {\n        $msg = gTxt(\'smd_bio_tbl_not_removed\');\n    }\n\n    if ($showpane) {\n        smd_bio_config($msg);\n    }\n}\n\n// ************************\n// PUBLIC-SIDE TAGS\n// ------------------------\n// Wrapper to permit bio info to be displayed for multiple authors\nfunction smd_bio_author($atts, $thing = null) {\n    global $smd_bio_author, $smd_bio_meta_data;\n\n    extract(lAtts(array(\n        \'author\'      => \'\',\n        \'form\'        => \'\',\n        \'sort\'        => \'RealName asc\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'breakclass\'  => \'\',\n        \'class\'       => \'\',\n        \'label\'       => \'\',\n        \'labeltag\'    => \'\',\n        \'auto_detect\' => \'profile, list, article, image, file, link\',\n    ), $atts));\n\n    $author = smd_bio_find_author($author, $auto_detect);\n    $author = do_list($author);\n    $authors = array();\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n\n    // Set up any sorting clause\n    if ($sort != \'\') {\n        $sort = do_list($sort);\n        $sortout = array();\n        foreach ($sort as $sitems) {\n            $sortbits = do_list($sitems, \' \');\n            $sort_col = $sortbits[0];\n            $sort_ord = (isset($sortbits[1]) && in_array($sortbits[1], array(\'asc\', \'desc\'))) ? $sortbits[1] : \'asc\';\n            if ($sort_col) {\n                $sortout[] = \'`\' . $sort_col . \'` \' . $sort_ord;\n            }\n        }\n        if ($sortout) {\n            $sort = \' ORDER BY \'.join(\',\', $sortout);\n        }\n    }\n\n    // Expand any priv levels\n    foreach ($author as $user) {\n        if (strpos(strtolower($user), \"smd_privs\") === 0) {\n            $aprivs = explode(\':\', $user);\n            array_shift($aprivs); // Remove smd_privs token from the array\n            $rows = safe_query(\'SELECT txpu.name FROM \' . PFX . \'txp_users AS txpu LEFT JOIN \' . PFX.SMD_BIO . \' AS smdb ON txpu.name = smdb.user_ref WHERE privs in (\' . doQuote(join(\"\',\'\", $aprivs)) . \')\'.$sort);\n            if ($rows) {\n                while ($a = nextRow($rows)) {\n                    $authors[] = $a[\'name\'];\n                }\n            }\n        } else if (strpos(strtolower($user), \"smd_all\") === 0) {\n            $rows = safe_query(\'SELECT txpu.name FROM \' . PFX . \'txp_users AS txpu LEFT JOIN \' . PFX.SMD_BIO . \' AS smdb ON txpu.name = smdb.user_ref WHERE 1=1\'.$sort);\n            if ($rows) {\n                while ($a = nextRow($rows)) {\n                    $authors[] = $a[\'name\'];\n                }\n            }\n        } else {\n            $authors[] = $user;\n        }\n    }\n\n    // Parse content for each author: inject current author into global\n    $out = array();\n    $author_count = count($authors) - 1;\n    foreach ($authors as $idx => $smd_bio_author) {\n        $smd_bio_meta_data[\'author\'][\'first\'] = ($idx === 0);\n        $smd_bio_meta_data[\'author\'][\'last\'] = ($idx === $author_count);\n        $toParse = (empty($thing)) ? $smd_bio_author : $thing;\n        $out[] = parse($toParse);\n    }\n\n    return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\n// Display biographical field data from a given user\'s profile\nfunction smd_bio_info($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    // Data cache\n    static $bio_info = array();\n    static $meta = array();\n    static $cmeta = array();\n    static $metacols = array();\n\n    extract(lAtts(array(\n        \'author\'      => \'\', // Deprecated: use smd_bio_author tag as a wrapper instead\n        \'fields\'      => \'SMD_ALL\',\n        \'items\'       => \'SMD_ALL\', // Deprecated: use fields instead\n        \'exclude\'     => \'\',\n        \'form\'        => \'\',\n        \'wraptag\'     => \'\',\n        \'break\'       => \'\',\n        \'class\'       => \'\',\n        \'label\'       => \'1\',\n        \'labeltag\'    => \'\',\n        \'labelclass\'  => \'SMD_DFLT\', // Deprecated\n        \'itemwraptag\' => \'\', // Deprecated: use break\n        \'itemclass\'   => \'SMD_DFLT\', // Deprecated: use breakclass\n        \'breakclass\'  => \'SMD_DFLT\',\n        \'show_empty\'  => 0,\n        \'prefix\'      => \'smd_bio_\', // Only for replacement variables\n        \'auto_detect\' => \'biotag, list, article, image, file, link\',\n        \'debug\'       => 0,\n    ), $atts));\n\n    if (isset($atts[\'author\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'author\')) . \'. Use the smd_bio_author tag as a wrapper instead.\', E_USER_NOTICE);\n        unset($author);\n    }\n    if (isset($atts[\'items\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'items\')) . \'. Use fields instead.\', E_USER_NOTICE);\n        $fields = $items;\n        unset($items);\n    }\n    if (isset($atts[\'itemwraptag\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'itemwraptag\')) . \'. Use break instead.\', E_USER_NOTICE);\n        $break = $itemwraptag;\n        unset($itemwraptag);\n    }\n    if (isset($atts[\'itemclass\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'itemclass\')) . \'. Use breakclass instead.\', E_USER_NOTICE);\n        $breakclass = $itemclass;\n        unset($itemclass);\n    }\n    if (isset($atts[\'labelclass\'])) {\n        trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'labelclass\')), E_USER_NOTICE);\n        unset($labelclass);\n    }\n\n    $author = smd_bio_find_author(\'\', $auto_detect);\n\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n    $smd_bio_data = $smd_bio_option_data = array();\n    $bio_types = array_keys(smd_bio_get_types());\n\n    $fields = do_list($fields);\n    $exclude = do_list($exclude);\n    $final = \'\';\n\n    $coreCols = smd_bio_core_cols();\n    $list_types = array(\'checkbox\', \'radio\', \'yesnoradio\', \'list\', \'multilist\');\n    $mm_types = array(\'number\', \'range\');\n\n    if ($author) {\n        $meta = (empty($meta)) ? safe_rows(\'*\', SMD_BIO_META, \'1=1\') : $meta;\n        $cmeta = (empty($cmeta)) ? safe_show(\'columns\', \'txp_users\') : $cmeta;\n        $metacols = (empty($metacols)) ? safe_column(\'name\', SMD_BIO_META, \'1=1\') : $metacols;\n        $num = count($meta);\n\n        foreach($cmeta as $info) {\n            if (!in_array($info[\'Field\'], $coreCols)) continue;\n            $meta[$num][\'name\'] = $info[\'Field\'];\n            $tField = join(\'\', array_keys($coreCols, $info[\'Field\']));\n            $meta[$num][\'title\'] = ((gTxt($tField) == $tField) ? gTxt(\'smd_bio_\'.$tField) : gTxt($tField));\n            $length = (($off = strpos($info[\'Type\'], \'(\')) !== false) ? $off : strlen($info[\'Type\']); // Find the first open bracket or end of string\n            $cmtype = substr($info[\'Type\'], 0, $length);\n            $cmtype = in_array($cmtype, $bio_types)? $cmtype : \'text\';\n            $meta[$num][\'type\'] = $cmtype;\n            $num++;\n        }\n        if ($debug > 1) {\n            echo \'++ META DATA ++\';\n            dmp($meta);\n        }\n\n        // Exclusions override given fields\n        if ($exclude[0] != \'\' || in_array(\'SMD_ALL\', $fields)) {\n            $fields = $metacols;\n            $fields = array_merge($fields, $coreCols);\n        }\n\n        if (isset($bio_info[$author])) {\n            $cbio = $bio_info[$author][\'cbio\'];\n            $ebio = $bio_info[$author][\'ebio\'];\n        } else {\n            $cbio = $bio_info[$author][\'cbio\'] = safe_row(\'*\', \'txp_users\', \"name=\'\".doSlash($author).\"\'\");\n            $ebio = $bio_info[$author][\'ebio\'] = safe_row(\'*\', SMD_BIO, \"user_ref=\'\".doSlash($author).\"\'\");\n        }\n\n        $replacements = $out = $toParse = array();\n        $isSingle = ($thing === null) ? true : false;\n        $numFields = count($fields);\n\n        foreach ($fields as $iref => $whatnot) {\n            $idx = -1;\n            if (in_array($whatnot, $exclude)) continue;\n\n            // Find the meta row\n            foreach ($meta as $num => $data) {\n                if ($data[\'name\'] == $whatnot) {\n                    $idx = $num;\n                    break;\n                }\n            }\n\n            if ($idx > -1) {\n                if (in_array($whatnot, $coreCols)) {\n                    $field = isset($cbio[$whatnot]) ? $cbio[$whatnot] : \'\';\n                } else {\n                    $field = isset($ebio[$whatnot]) ? $ebio[$whatnot] : \'\';\n                }\n\n                $theName = $meta[$idx][\'name\'];\n                $theTitle = $meta[$idx][\'title\'];\n                $prefixedName = $prefix.$theName;\n                $fixedName = \'smd_bio_\'.$theName; // Used for widget names so we know the indices in the $_POST array\n                $theClass = ($breakclass==\'SMD_DFLT\') ? $prefixedName : (($breakclass) ? $breakclass : \'\');\n                $replacements[\'{\'.$prefixedName.\'_name}\'] = $theName;\n                $replacements[\'{\'.$prefixedName.\'_title}\'] = $theTitle;\n                $replacements[\'{\'.$prefixedName.\'_class}\'] = $theClass;\n                $replacements[\'{\'.$prefixedName.\'_type}\'] = $meta[$idx][\'type\'];\n                $smd_bio_data[$theName][\'name\'] = $theName;\n                $smd_bio_data[$theName][\'title\'] = $theTitle;\n                $smd_bio_data[$theName][\'class\'] = $theClass;\n                $smd_bio_data[$theName][\'type\'] = $meta[$idx][\'type\'];\n                if (!in_array($meta[$idx][\'type\'], $list_types) && isset($meta[$idx][\'val\'])) {\n                    $smd_bio_data[$theName][\'default\'] = $meta[$idx][\'val\'];\n                    $replacements[\'{\'.$prefixedName.\'_default}\'] = $meta[$idx][\'val\'];\n                }\n                $widget = \'\';\n\n                if ($field || $show_empty) {\n                    if (in_array($meta[$idx][\'type\'], $list_types)) {\n                        $field = do_list($field);\n                        $field = join(\', \',$field);\n                    }\n\n                    $fieldContent = ($meta[$idx][\'type\'] == \'textarea\') ? nl2br($field) : $field;\n                    $replacements[\'{\'.$prefixedName.\'}\'] = $fieldContent;\n                    $smd_bio_data[$theName][\'value\'] = $fieldContent;\n\n                    // For backwards compatibility(ish) with v0.3x\n                    if ($numFields == 1) {\n                        $replacements[\'{\'.$prefix.\'info_item}\'] = $fieldContent; // Deprecated, use info_value instead\n                        $replacements[\'{\'.$prefix.\'info_value}\'] = $fieldContent;\n                        $replacements[\'{\'.$prefix.\'info_name}\'] = $theName;\n                        $replacements[\'{\'.$prefix.\'info_title}\'] = $theTitle;\n                        $replacements[\'{\'.$prefix.\'info_itemclass}\'] = $theClass; // Deprecated: use info_class instead\n                        $replacements[\'{\'.$prefix.\'info_class}\'] = $theClass;\n                    }\n\n                    if (in_array($meta[$idx][\'type\'], $list_types)) {\n                        $chosens = do_list($field);\n                        $nv = smd_bio_splitval($meta[$idx][\'val\']);\n                        list($nv, $dflt) = smd_bio_get_default($nv, $field);\n                        $dflts = do_list($dflt);\n                        $listctr=1;\n                        $chosenctr=0;\n                        foreach($nv as $listitem => $listlabel) {\n                            $replacements[\'{\'.$prefixedName.\'_option_\'.$listctr.\'}\'] = $listitem;\n                            $replacements[\'{\'.$prefixedName.\'_title_\'.$listctr.\'}\'] = $listlabel;\n                            $smd_bio_data[$theName][\'option_\'.$listctr] = $listitem;\n                            $smd_bio_data[$theName][\'title_\'.$listctr] = $listlabel;\n                            if (in_array($listitem, $chosens)) {\n                                $chosenctr++;\n                                $replacements[\'{\'.$prefixedName.\'_chosen_option_\'.$chosenctr.\'}\'] = $listitem;\n                                $replacements[\'{\'.$prefixedName.\'_chosen_title_\'.$chosenctr.\'}\'] = $listlabel;\n                                $smd_bio_data[$theName][\'chosen_option_\'.$chosenctr] = $listitem;\n                                $smd_bio_data[$theName][\'chosen_title_\'.$chosenctr] = $listlabel;\n                            }\n                            $listctr++;\n                        }\n                        $dfltctr = 1;\n                        foreach($dflts as $dfltitem) {\n                            $replacements[\'{\'.$prefixedName.\'_default_option_\'.$dfltctr.\'}\'] = $dfltitem;\n                            $smd_bio_data[$theName][\'default_option_\'.$dfltctr] = $dfltitem;\n                            $dfltctr++;\n                        }\n\n                        // TODO: maybe hand-code all input types so they can have classes added\n                        switch ($meta[$idx][\'type\']) {\n                            case \'checkbox\':\n                                $citems = array();\n                                foreach ($nv as $idx => $lbl) {\n                                    $citems[] = checkbox($fixedName, \'cb_\'.$idx, (in_array($idx, $dflts) ? \'1\' : \'0\')) . $lbl;\n                                }\n                                $widget = join(n, $citems);\n                            break;\n                            case \'yesnoradio\':\n                                $widget = yesnoRadio($fixedName, $dflt);\n                            break;\n                            case \'radio\':\n                                $widget = radioSet($nv, $fixedName, $dflt);\n                            break;\n                            case \'list\':\n                                $widget = selectInput($fixedName, $nv, $dflt, false, \'\', $prefixedName);\n                            break;\n                            case \'multilist\':\n                                $mitems = array();\n                                $mitems[] = \'<select name=\"\'.$fixedName.\'\" class=\"list multiple \'.$theClass.\'\" multiple=\"multiple\">\';\n                                foreach ($nv as $idx => $lbl) {\n                                    // Not using selectInput() because it doesn\'t support multiples\n                                    $mitems[] = \'<option value=\"ms_\'.$idx.\'\" \'.((in_array($idx, $dflts)) ? \' selected=\"selected\"\' : \'\') . \'>\' . $lbl . \'</option>\';\n                                }\n                                $mitems[] = \'</select>\';\n                                $widget = join(n, $mitems);\n                            break;\n                        }\n\n                        $replacements[\'{\'.$prefixedName.\'_option_count}\'] = $listctr-1;\n                        $replacements[\'{\'.$prefixedName.\'_chosen_count}\'] = $chosenctr;\n                        $replacements[\'{\'.$prefixedName.\'_default_count}\'] = $dfltctr-1;\n                        $smd_bio_data[$theName][\'option_count\'] = $listctr-1;\n                        $smd_bio_data[$theName][\'chosen_count\'] = $chosenctr;\n                        $smd_bio_data[$theName][\'default_count\'] = $dfltctr-1;\n                    } else if (in_array($meta[$idx][\'type\'], $mm_types)) {\n                        $sizes = do_list($meta[$idx][\'size\']);\n                        $min = ($sizes[0] === \'\') ? \'\' : \' min=\"\' . $sizes[0] . \'\"\';\n                        $max = (isset($sizes[1]) && $sizes[1] !== \'\') ? \' max=\"\' . $sizes[1] . \'\"\' : \'\';\n                        $jmp = (isset($sizes[2]) && $sizes[2] !== \'\') ? \' step=\"\' . $sizes[2] . \'\"\': \'\';\n                        $cls = ($theClass) ? \' class=\"\'.$theClass.\'\"\' : \'\';\n                        $widget = \'<input type=\"\'.$meta[$idx][\'type\'].\'\" value=\"\'.$field.\'\" name=\"\'.$fixedName.\'\"\'.$cls.$min.$max.$jmp.\' />\';\n                    } else if ($meta[$idx][\'type\'] == \'textarea\') {\n                        $sizes = do_list($meta[$idx][\'size\']);\n                        $w = ($sizes[0] === \'\') ? \'\' : \' cols=\"\' . $sizes[0] . \'\"\';\n                        $h = (isset($sizes[1]) && $sizes[1] !== \'\') ? \' rows=\"\' . $sizes[1] . \'\"\': \'\';\n                        $cls = ($theClass) ? \' class=\"\'.$theClass.\'\"\' : \'\';\n                        // TODO: maybe use text_area() when 4.6.0 released as it may support classes\n                        $widget = \'<textarea name=\"\' . $fixedName . \'\" id=\"\' . $prefixedName . \'\"\'. $cls.$w.$h.\'>\'.$field.\'</textarea>\';\n                    } else if ($meta[$idx][\'type\'] == \'image\') {\n                        $widget = fInput(\'text\', $fixedName, $field, $theClass, \'\', \'\', \'\', \'\', $prefixedName);\n                    } else {\n                        $widget = fInput($meta[$idx][\'type\'], $fixedName, $field, $theClass, \'\', \'\', \'\', \'\', $prefixedName);\n                    }\n\n                    $smd_bio_data[$theName][\'widget\'] = $widget;\n                    $replacements[\'{\'.$prefixedName.\'_widget}\'] = $widget;\n\n                    // Without container/form, build up generic output\n                    if ($isSingle) {\n                        $taglab = (($label==1) ? $theTitle : (($label==\'\') ? \'\' : (($label) ? $label : $theName)));\n                        $toParse[] = doLabel($taglab, $labeltag) . (($break) ? doTag($field, $break, $theClass) : $field);\n                    }\n                }\n            }\n        }\n\n        if ($debug) {\n            echo \'++ BIO REPLACEMENTS FOR \' .$author. \' ++\';\n            dmp($replacements);\n        }\n\n        if (!$isSingle) {\n            $toParse[] = $thing;\n        }\n\n        $out[] = parse(strtr(join(n, $toParse), $replacements));\n        $final = doWrap($out, $wraptag, \'\', $class);\n    }\n    return $final;\n}\n\n// Output data\nfunction smd_bio_data($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'   => \'\',\n        \'item\'    => \'value\',\n        \'wraptag\' => \'\',\n        \'break\'   => \'\',\n        \'class\'   => \'\',\n        \'debug\'   => 0,\n    ), $atts));\n\n    $bdata = is_array($smd_bio_data) ? $smd_bio_data : array();\n    $idata = is_array($smd_bio_option_data) ? $smd_bio_option_data : array();\n    $datapool = array_merge($bdata, $idata);\n\n    if ($debug) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($datapool);\n    }\n\n    $items = do_list($item);\n    $out = array();\n    foreach ($items as $it) {\n        if (isset($datapool[$field][$it])) {\n            $out[] = $datapool[$field][$it];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Iterate over N multi-items\nfunction smd_bio_iterate($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'      => \'\',\n        \'using\'      => \'chosen\', // chosen, default, all\n        \'display\'    => \'title\', // option, title\n        \'prefix\'     => \'smd_bio_\', // Only for replacement variables\n        \'wraptag\'    => \'\',\n        \'class\'      => \'\',\n        \'break\'      => \'\',\n        \'form\'       => \'\',\n        \'limit\'      => 0,\n        \'offset\'     => 0,\n        \'debug\'      => 0,\n    ), $atts));\n\n    if ($debug > 1) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($smd_bio_data);\n    }\n\n    // Validation 1\n    if (!isset($smd_bio_data[$field])) {\n        return;\n    }\n\n    // Validation 2\n    $usingMap = array(\'all\' => \'\', \'chosen\' => \'chosen\', \'default\' => \'default\');\n    $using = isset($usingMap[$using]) ? $using : \'chosen\';\n\n    // Validation 3\n    $displayMap = array(\'option\' => \'option\', \'title\' => \'title\');\n    $display = isset($displayMap[$display]) ? $display : \'title\';\n\n    switch ($using) {\n        case \'chosen\':\n        case \'default\':\n            $countType = $using.\'_count\';\n            break;\n        case \'all\':\n        default:\n            $countType = \'option_count\';\n        break;\n    }\n\n    $out = $reps = $stash = array();\n\n    // Whip through the lists to gather the info for later.\n    if (isset($smd_bio_data[$field][\'option_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'option_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'option_\'.$idx];\n            $ttl = $smd_bio_data[$field][\'title_\'.$idx];\n            $stash[\'option\'][$opt] = $ttl;\n        }\n    }\n    if (isset($smd_bio_data[$field][\'chosen_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'chosen_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'chosen_option_\'.$idx];\n            $ttl = $smd_bio_data[$field][\'chosen_title_\'.$idx];\n            $stash[\'chosen\'][$opt] = $ttl;\n        }\n    }\n    if (isset($smd_bio_data[$field][\'default_count\'])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][\'default_count\']; $idx++) {\n            $opt = $smd_bio_data[$field][\'default_option_\'.$idx];\n            $stash[\'default\'][] = $opt;\n        }\n    }\n\n    $checked_types = array(\'checkbox\', \'radio\', \'yesnoradio\');\n    $selected_types = array(\'list\', \'multilist\');\n\n    // Do the iteration for real now, taking limit and offset into account.\n    // Can only iterate over items that have a count.\n    if (isset($smd_bio_data[$field][$countType])) {\n        for ($idx = 1; $idx <= $smd_bio_data[$field][$countType]; $idx++) {\n            if (( ($idx-1) >= $offset ) && ( $limit == 0 || (($idx-1) < $limit+$offset) )) {\n                $key = (($usingMap[$using]) ? $usingMap[$using]. \'_\' : \'\') . ($using === \'default\' && $display === \'title\'? \'option\' : $displayMap[$display]) . \'_\' . $idx;\n                $uval = $usingMap[$using] ? $usingMap[$using].\'_\' : \'\';\n                $opt = $smd_bio_data[$field][$uval . \'option_\'.$idx];\n\n                // No title in smd_bio_data for default, so fudge it\n                if ($using === \'default\') {\n                    $ttl = $stash[\'option\'][$opt];\n                } else {\n                    $ttl = $smd_bio_data[$field][$uval . \'title_\'.$idx];\n                }\n\n                $reps = array(\n                    \'{\'.$prefix.\'iterate_option}\' => $opt,\n                    \'{\'.$prefix.\'iterate_title}\' => $ttl,\n                    \'{\'.$prefix.\'iterate_count}\' => $idx,\n                );\n\n                // When iterating all options, indicate which are default and which are chosen\n                if ($using === \'all\') {\n                    if (isset($stash[\'default\']) && in_array($opt, $stash[\'default\'])) {\n                        $reps[\'{\'.$prefix.\'iterate_is_default}\'] = \'1\';\n                        $smd_bio_option_data[$field][\'iterate_is_default\'] = \'1\';\n                    } else {\n                        $reps[\'{\'.$prefix.\'iterate_is_default}\'] = \'0\';\n                        $smd_bio_option_data[$field][\'iterate_is_default\'] = \'0\';\n                    }\n\n                    if (isset($stash[\'chosen\']) && array_key_exists($opt, $stash[\'chosen\'])) {\n                        $flavour = $smd_bio_data[$field][\'type\'];\n                        $html_sel = (in_array($flavour, $checked_types)) ? \'checked\' : ((in_array($flavour, $selected_types)) ? \'selected\' : \'1\');\n                        $reps[\'{\'.$prefix.\'iterate_is_chosen}\'] = $html_sel;\n                        $smd_bio_option_data[$field][\'iterate_is_chosen\'] = $html_sel;\n                    } else {\n                        $reps[\'{\'.$prefix.\'iterate_is_chosen}\'] = \'\';\n                        $smd_bio_option_data[$field][\'iterate_is_chosen\'] = \'\';\n                    }\n                }\n\n                $smd_bio_option_data[$field][\'iterate_option\'] = $opt;\n                $smd_bio_option_data[$field][\'iterate_title\'] = $ttl;\n                $smd_bio_option_data[$field][\'iterate_count\'] = $idx;\n                if ($debug) {\n                    echo \'++ ITERATOR REPLACEMENTS ++\';\n                    dmp($reps);\n                }\n                $out[] = ($thing !== null) ? parse(strtr($thing, $reps)) : ( ($form !== \'\') ? parse_form(strtr($form, $reps)) : $smd_bio_data[$field][$key] );\n            }\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}\n\n// Convenience conditional to test a field/item. Use smd_if for more advanced conditional logic\nfunction smd_if_bio($atts, $thing = null) {\n    global $smd_bio_data, $smd_bio_option_data;\n\n    extract(lAtts(array(\n        \'field\'          => \'\',\n        \'item\'           => \'value\',\n        \'value\'          => null,\n        \'case_sensitive\' => 1,\n        \'debug\'          => 0,\n    ), $atts));\n\n    $bdata = is_array($smd_bio_data) ? $smd_bio_data : array();\n    $idata = is_array($smd_bio_option_data) ? $smd_bio_option_data : array();\n    $datapool = array_merge($bdata, $idata);\n    if ($debug) {\n        echo \'++ AVAILABLE BIO DATA ++\';\n        dmp($datapool);\n    }\n\n    $result = false;\n\n    if (isset($datapool[$field][$item])) {\n        if ($value !== null) {\n            $cval = ($case_sensitive) ? $value : strtolower($value);\n            $citm = ($case_sensitive) ? $datapool[$field][$item] : strtolower($datapool[$field][$item]);\n            $result = ((string)$citm === (string)$cval);\n        } else {\n            $result = ($datapool[$field][$item] !== \'\');\n        }\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// Conditional to test bio meta data\nfunction smd_if_bio_is($atts, $thing = null) {\n    global $smd_bio_meta_data;\n\n    extract(lAtts(array(\n        \'type\'  => \'author\', // author, field\n        \'item\'  => \'first\', // first, last\n        \'debug\' => 0,\n    ), $atts));\n\n    $datapool = is_array($smd_bio_meta_data) ? $smd_bio_meta_data : array();\n    if ($debug) {\n        echo \'++ AVAILABLE BIO META DATA ++\';\n        dmp($datapool);\n    }\n\n    $result = false;\n\n    if (isset($datapool[$type][$item])) {\n        $result = $datapool[$type][$item];\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// Convenience conditional\nfunction smd_if_bio_first_author($atts, $thing = null) {\n    return smd_if_bio_is(array(\'type\' => \'author\', \'item\' => \'first\'), $thing);\n}\n// Convenience conditional\nfunction smd_if_bio_last_author($atts, $thing = null) {\n    return smd_if_bio_is(array(\'type\' => \'author\', \'item\' => \'last\'), $thing);\n}\n\n\n// ------------------------\n// A wrapper to article_custom that auto sets the user to the one specified\n// or the current article\'s author.\n// NOTE: lAtts() is NOT used because that limits the plugin attributes.\nfunction smd_bio_articles($atts, $thing = null) {\n    global $thisarticle;\n\n    $author = (isset($atts[\'author\'])) ? $atts[\'author\'] : (isset($thisarticle[\'authorid\']) ? $thisarticle[\'authorid\'] : \'\');\n    $atts[\'author\'] = $author;\n    return parseArticles($atts, \'1\', $thing);\n}', '8a56b8a3119c5707250d6d726066ede3', '', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_browse_by', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.11', 'Browse by Category (+article Section) in admin side', '<div id=\"smd_help\">\n\n	<h1>smd_browse_by</h1>\n\n	<p>Browse your articles/images/files/links via a quick dropdown menu of category (or in the case of articles: section as well). Just install, activate, done.</p>\n\n	<h2>Author / credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Original plugin by Steve Lam.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires <span class=\"caps\">TXP</span> 4.4.1+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1239/smd_browse_by\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> <em>Admin-&gt;Plugins</em> pane, install and enable the plugin.</p>\n\n	<p>To uninstall the plugin, delete from the <em>Admin-&gt;Plugins</em> page.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36784\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<h2>Configuration</h2>\n\n	<p>The plugin can be controlled with a few hidden preference values (the smd_prefalizer plugin can help here). Any prefs you create should have the following items set, in addition to the preference-specific name and value given later:</p>\n\n	<ul>\n		<li>Visibility: <strong>Hidden</strong></li>\n		<li>Event: <strong>smd_browse</strong></li>\n		<li>User: <em>your login name</em> if you wish the preference to only apply to your login, or leave it empty to apply it for all users</li>\n	</ul>\n\n	<p>The preferences are:</p>\n\n	<h3>Browse using</h3>\n\n	<p>By default you can browse by category on all main content types and additionally by section with articles. If you wish to only offer browsing on certain tabs, set this preference. Comma-separate any of the following values to build the interface to your choosing:</p>\n\n	<ul>\n		<li>Name: <strong>smd_browse_by</strong></li>\n		<li>Value:\n	<ul>\n		<li><em>empty</em> : none (!) : remove all browsable lists added by the plugin</li>\n		<li><strong><span class=\"caps\">SMD</span>_ALL</strong> : browse on every tab &#8212; this has the same effect as completely removing the preference value</li>\n		<li><strong>article</strong>: browse by category and section lists on the Articles tab</li>\n		<li><strong>article_cat</strong>: browse by category on the Articles tab</li>\n		<li><strong>article_sec</strong>: browse by section on the Articles tab</li>\n		<li><strong>image</strong>: browse by category on the Images tab</li>\n		<li><strong>file</strong>: browse by category on the Files tab</li>\n		<li><strong>link</strong>: browse by category on the Links tab</li>\n	</ul></li>\n	</ul>\n\n	<h3>Go button</h3>\n\n	<p>By default the select lists submit automatically when the entry is changed. If you wish to add a &#8216;Go&#8217; button next to the lists, set this preference:</p>\n\n	<ul>\n		<li>Name: <strong>smd_browse_by_go_button</strong></li>\n		<li>Value:\n	<ul>\n		<li>0 (auto-submit)</li>\n		<li>1 (Go button)</li>\n	</ul></li>\n	</ul>\n\n	<h2><span class=\"caps\">API</span></h2>\n\n	<p>If you wish to alter the output of any of the lists that appear on the screen you may do so by registering a callback to be notified when one or all of the select lists are displayed. Here are the relevant events and steps you can use:</p>\n\n	<ul>\n		<li>Event: <code>smd_browse_by_ui</code></li>\n		<li>Step: call your plugin at the following times:\n	<ul>\n		<li><em>empty</em> : all select lists</li>\n		<li><code>article_cat</code>: when the Article category list is displayed</li>\n		<li><code>article_sec</code>: when the Article section list is displayed</li>\n		<li><code>image</code>: when the Image category list is displayed</li>\n		<li><code>file</code>: when the File category list is displayed</li>\n		<li><code>link</code>: when the Link category list is displayed</li>\n	</ul></li>\n	</ul>\n\n	<p>An example:</p>\n\n<pre class=\"block\"><code class=\"block\">if (@txpinterface == &#39;admin&#39;) {\n   register_callback(&#39;my_custom_browser&#39;, &#39;smd_browse_by_ui&#39;, &#39;image&#39;);\n}\n</code>\n<code class=\"block\">function my_custom_browser($evt, $stp, $data, $rs, $val) {\n   // $evt is always &#39;smd_browse_by_ui&#39;\n   // $stp is the current step\n   //    (article_cat, article_sec, image, file, or link)\n   // $data holds the fully rendered default select list HTML\n   //    so you can search/replace/manipulate it\n   // $rs is the raw array containing the list of items\n   //    so you can iterate over them\n   // $val is the currently chosen value from the list\n   // if you return anything from this function,\n   //    that will be what is displayed in place of the\n   //    default select list\n}\n</code></pre>\n\n	<h2>Changelog</h2>\n\n	<ul>\n		<li>31 Aug 2011 | 0.10 | Initial release</li>\n		<li>04 Mar 2012 | 0.11 | Added hidden pref to control which dropdowns to use ; category tree now displayed (thanks pieman) ; pluggable_ui() implemented so other plugins can alter the lists (thanks tye) ; more consistent layout (thanks philwareham) ; sections ordered by title</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 17px Georgia, sans-serif; }\n#smd_help h3 { font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\21d2\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\n	register_callback(\"smd_browse_by_image\", \"image\");\r\n	register_callback(\"smd_browse_by_article\", \"list\");\r\n	register_callback(\"smd_browse_by_file\", \"file\");\r\n	register_callback(\"smd_browse_by_link\", \"link\");\r\n}\r\n\r\nfunction smd_browse_by_image($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'image\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'image\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_article($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	$showcat = (in_array(\'SMD_ALL\', $showit) || in_array(\'article\', $showit) || in_array(\'article_cat\', $showit));\r\n	$showsec = (in_array(\'SMD_ALL\', $showit) || in_array(\'article\', $showit) || in_array(\'article_sec\', $showit));\r\n\r\n	$smd_browse_by_form = (($showcat) ? form(smd_browse_dropdown(\'article_cat\', $evt), \'\', \'\', \'get\') : \'\')\r\n		. (($showsec) ? form(smd_browse_dropdown(\'article_sec\', $evt), \'\', \'\', \'get\') : \'\');\r\n\r\n	if ($showcat || $showsec) {\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_file($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'file\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'file\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_link($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'link\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'link\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\n// Common js to place each \'browse by\' above each area\'s search form\r\nfunction smd_browse_by_js($content) {\r\n	$out = doSlash($content);\r\n	$js = <<<EOC\r\n<script type=\"text/javascript\">\r\n	jQuery(document).ready(function() {\r\n		jQuery(\".search-form\").before(\'<div class=\"smd_browse_by\">{$out}</div>\');\r\n	});\r\n</script>\r\n<style type=\"text/css\">\r\n.smd_browse_by {\r\n	width:100%;\r\n	text-align:center;\r\n}\r\n.smd_browse_by form {\r\n	display:inline;\r\n}\r\n</style>\r\nEOC;\r\n	echo $js;\r\n}\r\n\r\nfunction smd_browse_dropdown($type, $evt) {\r\n	$browsable = array(\r\n		\'article_cat\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'article\' AND name != \'root\'\",\r\n			\'step\'  => \'list\',\r\n			\'meth\'  => \'categories\',\r\n		),\r\n		\'article_sec\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_sec\'),\r\n			\'table\' => \'txp_section\',\r\n			\'where\' => \"name != \'default\'\",\r\n			\'step\'  => \'list\',\r\n			\'meth\'  => \'section\',\r\n			\'sort\'  => \'Title asc\',\r\n		),\r\n		\'image\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'image\' AND name != \'root\'\",\r\n			\'step\'  => \'image_list\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n		\'file\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'file\' AND name != \'root\'\",\r\n			\'step\'  => \'file_list\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n		\'link\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'link\' AND name != \'root\'\",\r\n			\'step\'  => \'link_edit\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n	);\r\n\r\n	if (isset($browsable[$type])) {\r\n		$entry = $browsable[$type];\r\n		$sel = array();\r\n\r\n		if ($entry[\'meth\'] == \'section\') {\r\n			$sorder = ($entry[\'sort\']) ? \' ORDER BY \'.$entry[\'sort\'] : \'\';\r\n			$section_cat = safe_rows(\'name, title\', $entry[\'table\'], $entry[\'where\'].$sorder);\r\n			foreach ($section_cat as $row) {\r\n				$sel[] = array(\'name\' => $row[\'name\'], \'title\' => $row[\'title\']);\r\n			}\r\n		} else if ($entry[\'meth\'] == \'category\' || $entry[\'meth\'] == \'categories\') {\r\n			$thistype = str_replace(\'_cat\', \'\', $type);\r\n			$sel = getTree(\'root\', $thistype, \'1=1\', $entry[\'table\']);\r\n		}\r\n		if ($sel) {\r\n			$use_go = get_pref(\'smd_browse_by_go_button\', 0);\r\n			$val = gps(\'crit\');\r\n			return\r\n				graf($entry[\'by\']\r\n				. pluggable_ui(\'smd_browse_by_ui\', $type, smd_browse_select_input(\'crit\', $sel, $val, true, (($use_go) ? false : true)), $sel,  $val)\r\n				. (($use_go) ? fInput(\'submit\', \'\', gTxt(\'go\'), \'smallerbox\') : \'\')\r\n				. hInput(\'event\', $evt)\r\n				. hInput(\'step\', $entry[\'step\'])\r\n				. hInput(\'search_method\', $entry[\'meth\'])\r\n				);\r\n		}\r\n	}\r\n	return false;\r\n}\r\n\r\n// Frankensteined from the core\'s selectInput() and treeSelectInput()\r\nfunction smd_browse_select_input($select_name = \'\', $array = \'\', $value = \'\', $blank_first, $onchange, $select_id = \'\', $truncate = 0) {\r\n	$out = array();\r\n	$selected = false;\r\n	$level = 0;\r\n\r\n	foreach ($array as $a) {\r\n		if ($a[\'name\'] == \'root\') {\r\n			continue;\r\n		}\r\n\r\n		extract($a);\r\n\r\n		if ($name == $value) {\r\n			$sel = \' selected=\"selected\"\';\r\n			$selected = true;\r\n		} else {\r\n			$sel = \'\';\r\n		}\r\n\r\n		$sp = str_repeat(sp.sp, $level);\r\n\r\n		if (($truncate > 3) && (strlen(utf8_decode($title)) > $truncate)) {\r\n			$htmltitle = \' title=\"\'.htmlspecialchars($title).\'\"\';\r\n			$title = preg_replace(\'/^(.{0,\'.($truncate - 3).\'}).*$/su\',\'$1\',$title);\r\n			$hellip = \'&#8230;\';\r\n		} else {\r\n			$htmltitle = $hellip = \'\';\r\n		}\r\n\r\n		$out[] = n.t.\'<option value=\"\'.htmlspecialchars($name).\'\"\'.$htmltitle.$sel.\'>\'.$sp.htmlspecialchars($title).$hellip.\'</option>\';\r\n	}\r\n\r\n	return n.\'<select\'.( $select_id ? \' id=\"\'.$select_id.\'\"\' : \'\' ).\' name=\"\'.$select_name.\'\" class=\"list\"\'.\r\n		($onchange == 1 ? \' onchange=\"submit(this.form);\"\' : $onchange).\r\n		\'>\'.\r\n		($blank_first ? n.t.\'<option value=\"\"\'.($selected == false ? \' selected=\"selected\"\' : \'\').\'></option>\' : \'\').\r\n		( $out ? join(\'\', $out) : \'\').\r\n		n.\'</select>\';\r\n}\r\n\r\n// ------------------------\r\n// Plugin-specific replacement strings - localise as required\r\nfunction smd_browse_by_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'by_cat\' => \'Browse by Category:\',\r\n		\'by_sec\' => \'Browse by Section:\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', 'if (@txpinterface == \'admin\') {\r\n	register_callback(\"smd_browse_by_image\", \"image\");\r\n	register_callback(\"smd_browse_by_article\", \"list\");\r\n	register_callback(\"smd_browse_by_file\", \"file\");\r\n	register_callback(\"smd_browse_by_link\", \"link\");\r\n}\r\n\r\nfunction smd_browse_by_image($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'image\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'image\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_article($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	$showcat = (in_array(\'SMD_ALL\', $showit) || in_array(\'article\', $showit) || in_array(\'article_cat\', $showit));\r\n	$showsec = (in_array(\'SMD_ALL\', $showit) || in_array(\'article\', $showit) || in_array(\'article_sec\', $showit));\r\n\r\n	$smd_browse_by_form = (($showcat) ? form(smd_browse_dropdown(\'article_cat\', $evt), \'\', \'\', \'get\') : \'\')\r\n		. (($showsec) ? form(smd_browse_dropdown(\'article_sec\', $evt), \'\', \'\', \'get\') : \'\');\r\n\r\n	if ($showcat || $showsec) {\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_file($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'file\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'file\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\nfunction smd_browse_by_link($evt, $stp) {\r\n	$showit = do_list(get_pref(\'smd_browse_by\', \'SMD_ALL\'));\r\n	if (in_array(\'SMD_ALL\', $showit) || in_array(\'link\', $showit)) {\r\n		$smd_browse_by_form = form(smd_browse_dropdown(\'link\', $evt), \'\', \'\', \'get\');\r\n		smd_browse_by_js($smd_browse_by_form);\r\n	}\r\n}\r\n\r\n// Common js to place each \'browse by\' above each area\'s search form\r\nfunction smd_browse_by_js($content) {\r\n	$out = doSlash($content);\r\n	$js = <<<EOC\r\n<script type=\"text/javascript\">\r\n	jQuery(document).ready(function() {\r\n		jQuery(\".search-form\").before(\'<div class=\"smd_browse_by\">{$out}</div>\');\r\n	});\r\n</script>\r\n<style type=\"text/css\">\r\n.smd_browse_by {\r\n	width:100%;\r\n	text-align:center;\r\n}\r\n.smd_browse_by form {\r\n	display:inline;\r\n}\r\n</style>\r\nEOC;\r\n	echo $js;\r\n}\r\n\r\nfunction smd_browse_dropdown($type, $evt) {\r\n	$browsable = array(\r\n		\'article_cat\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'article\' AND name != \'root\'\",\r\n			\'step\'  => \'list\',\r\n			\'meth\'  => \'categories\',\r\n		),\r\n		\'article_sec\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_sec\'),\r\n			\'table\' => \'txp_section\',\r\n			\'where\' => \"name != \'default\'\",\r\n			\'step\'  => \'list\',\r\n			\'meth\'  => \'section\',\r\n			\'sort\'  => \'Title asc\',\r\n		),\r\n		\'image\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'image\' AND name != \'root\'\",\r\n			\'step\'  => \'image_list\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n		\'file\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'file\' AND name != \'root\'\",\r\n			\'step\'  => \'file_list\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n		\'link\' => array(\r\n			\'by\'    => smd_browse_by_gTxt(\'by_cat\'),\r\n			\'table\' => \'txp_category\',\r\n			\'where\' => \"type = \'link\' AND name != \'root\'\",\r\n			\'step\'  => \'link_edit\',\r\n			\'meth\'  => \'category\',\r\n		),\r\n	);\r\n\r\n	if (isset($browsable[$type])) {\r\n		$entry = $browsable[$type];\r\n		$sel = array();\r\n\r\n		if ($entry[\'meth\'] == \'section\') {\r\n			$sorder = ($entry[\'sort\']) ? \' ORDER BY \'.$entry[\'sort\'] : \'\';\r\n			$section_cat = safe_rows(\'name, title\', $entry[\'table\'], $entry[\'where\'].$sorder);\r\n			foreach ($section_cat as $row) {\r\n				$sel[] = array(\'name\' => $row[\'name\'], \'title\' => $row[\'title\']);\r\n			}\r\n		} else if ($entry[\'meth\'] == \'category\' || $entry[\'meth\'] == \'categories\') {\r\n			$thistype = str_replace(\'_cat\', \'\', $type);\r\n			$sel = getTree(\'root\', $thistype, \'1=1\', $entry[\'table\']);\r\n		}\r\n		if ($sel) {\r\n			$use_go = get_pref(\'smd_browse_by_go_button\', 0);\r\n			$val = gps(\'crit\');\r\n			return\r\n				graf($entry[\'by\']\r\n				. pluggable_ui(\'smd_browse_by_ui\', $type, smd_browse_select_input(\'crit\', $sel, $val, true, (($use_go) ? false : true)), $sel,  $val)\r\n				. (($use_go) ? fInput(\'submit\', \'\', gTxt(\'go\'), \'smallerbox\') : \'\')\r\n				. hInput(\'event\', $evt)\r\n				. hInput(\'step\', $entry[\'step\'])\r\n				. hInput(\'search_method\', $entry[\'meth\'])\r\n				);\r\n		}\r\n	}\r\n	return false;\r\n}\r\n\r\n// Frankensteined from the core\'s selectInput() and treeSelectInput()\r\nfunction smd_browse_select_input($select_name = \'\', $array = \'\', $value = \'\', $blank_first, $onchange, $select_id = \'\', $truncate = 0) {\r\n	$out = array();\r\n	$selected = false;\r\n	$level = 0;\r\n\r\n	foreach ($array as $a) {\r\n		if ($a[\'name\'] == \'root\') {\r\n			continue;\r\n		}\r\n\r\n		extract($a);\r\n\r\n		if ($name == $value) {\r\n			$sel = \' selected=\"selected\"\';\r\n			$selected = true;\r\n		} else {\r\n			$sel = \'\';\r\n		}\r\n\r\n		$sp = str_repeat(sp.sp, $level);\r\n\r\n		if (($truncate > 3) && (strlen(utf8_decode($title)) > $truncate)) {\r\n			$htmltitle = \' title=\"\'.htmlspecialchars($title).\'\"\';\r\n			$title = preg_replace(\'/^(.{0,\'.($truncate - 3).\'}).*$/su\',\'$1\',$title);\r\n			$hellip = \'&#8230;\';\r\n		} else {\r\n			$htmltitle = $hellip = \'\';\r\n		}\r\n\r\n		$out[] = n.t.\'<option value=\"\'.htmlspecialchars($name).\'\"\'.$htmltitle.$sel.\'>\'.$sp.htmlspecialchars($title).$hellip.\'</option>\';\r\n	}\r\n\r\n	return n.\'<select\'.( $select_id ? \' id=\"\'.$select_id.\'\"\' : \'\' ).\' name=\"\'.$select_name.\'\" class=\"list\"\'.\r\n		($onchange == 1 ? \' onchange=\"submit(this.form);\"\' : $onchange).\r\n		\'>\'.\r\n		($blank_first ? n.t.\'<option value=\"\"\'.($selected == false ? \' selected=\"selected\"\' : \'\').\'></option>\' : \'\').\r\n		( $out ? join(\'\', $out) : \'\').\r\n		n.\'</select>\';\r\n}\r\n\r\n// ------------------------\r\n// Plugin-specific replacement strings - localise as required\r\nfunction smd_browse_by_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'by_cat\' => \'Browse by Category:\',\r\n		\'by_sec\' => \'Browse by Section:\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', 'a3b4b94ee11fb4501b5d460a3b22b148', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_article_stats', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.40', 'Get article/excerpt statistics and display them to content editors and visitors', '<h1>smd_article_stats</h1>\n\n<p>Put this tag in your article form to display info about the current article. Tags are ignored in the calculation, as far as possible. Note that it only displays the actual number of words entered in the article itself &#8212; if some of the content is derived from other forms, it will not be included. The same goes for character counts: if there is markup or random content in the field you choose to tally, the plugin will make a best guess after it strips tags from the field.</p>\n\n<h2>Attributes</h2>\n\n<ul>\n	<li><strong>item</strong> : list of items you want to count. The most common ones are <code>body</code>, <code>excerpt</code> or <code>title</code> to get the number of words in the relevant fields. You can supply any article field you like here (e.g. custom field: <code>item=&quot;body, book precis, book author&quot;</code>). If you don&#8217;t specify anything then the fields used will be those selected from the prefs.</li>\n	<li><strong>type</strong> : flavour of information you wish to display. Either <code>word</code> (the default) or <code>char</code>.</li>\n	<li><strong>label</strong> : label text to output before the requested count.</li>\n	<li><strong>labeltag</strong> : <span class=\"caps\">HTML</span> tag without brackets to wrap around the label.</li>\n	<li><strong>wraptag</strong> : <span class=\"caps\">HTML</span> tag without brackets to wrap around the output.</li>\n	<li><strong>class</strong> : <span class=\"caps\">CSS</span> classname to apply to the wraptag (default: <code>smd_article_stats</code>).</li>\n</ul>\n\n<h2>Admin side</h2>\n\n<p>On the Write panel, the number of words/chars in the document are displayed in a stats panel. It is updated in real time as data is entered in the given fields (defined in the plugin preferences). The article ID can also be displayed; hyperlinked to the article itself if it&#8217;s live or sticky. Set the <em>Show article ID</em> preference accordingly.</p>\n\n<p>If you wish to move the panel to a different location on the Write panel, visit the Advanced prefs. You can then choose one of the following items from the list:</p>\n\n<ul>\n	<li><span>Above Sort and display</span> : (default location) above the &#8216;Sort and display&#8217; box.</li>\n	<li><span>Above Title</span> : above the Title box.</li>\n	<li><span>Above Textfilter</span> : above the Textfilter Help twisty.</li>\n	<li><span>Below Textfilter</span> : below the Textfilter Help twisty.</li>\n	<li><span>Below Excerpt</span> : Immediately beneath the Excerpt.</li>\n	<li><span>Below Author</span> : Beneath the author info (which is under the Excerpt). Note this position won&#8217;t appear for new articles until they are published.</li>\n	<li><span>None</span> : disable the panel.</li>\n</ul>\n\n<p>You may also customize which fields contribute to the count by altering the value in the <em>Word count fields and <span class=\"caps\">DOM</span> selectors</em> box. List the fields using the syntax <em>field</em> -&gt; <em><span class=\"caps\">DOM</span> selector</em> and separate each field with a comma. For example, to count words in the body, excerpt and custom 2 fields, set the preference to:</p>\n\n<pre><code>Body -&gt; #body, Excerpt -&gt; #excerpt, custom_2 -&gt; #custom-2</code></pre>\n\n<h2>Author / credits</h2>\n\n<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Thanks to both zem and iblastoff for the original works that this plugin borrows as its foundation.</p>\n\n<h2>Changelog</h2>\n\n<ul>\n	<li>17 Mar 2016 | 0.40 | For Txp 4.6.0+ ; Added character count support ; fixed the default <span class=\"caps\">DOM</span> anchors for new Write panel layout</li>\n	<li>21 Nov 2012 | 0.30 | For Txp 4.5.0+ ; added visible prefs ; made smd_article_stats tag take on defaults ; display of ID is optional</li>\n	<li>04 Apr 2012 | 0.24 | Expanded the available fields to anything in the article (thanks Teemu)</li>\n	<li>14 Feb 2012 | 0.23 | Fixed excerpt_below auto-update mechanism ; added author_below position</li>\n	<li>06 Jun 2011 | 0.22 | Added paragraph wrapper to fieldset for consistency with <span class=\"caps\">TXP</span>&#8217;s layout (thanks philwareham)</li>\n	<li>22 Feb 2010 | 0.21 | Prevented error message if step mangled</li>\n	<li>07 Nov 2009 | 0.20 | Improved counting to ignore tags and added real-time admin-side counter (both thanks speeke)</li>\n	<li>24 Aug 2009 | 0.10 | Initial release</li>\n</ul>', '/**\r\n * smd_article_stats\r\n *\r\n * A Textpattern CMS plugin for counting words/characters in article fields and\r\n * optionally displaying them to visitors.\r\n *  -> Choose which fields to count on the admin side.\r\n *  -> Customize where you want the count to be displayed.\r\n *  -> Shows ID of currently edited article.\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n * @todo TinyMCE -- accessing fields inside iframes?\r\n */\r\n\r\nif (txpinterface === \'admin\') {\r\n	$all_privs = array_keys(get_groups());\r\n	unset($all_privs[array_search(\'0\', $all_privs)]); // Remove \'none\'.\r\n	$all_joined = implode(\',\', $all_privs);\r\n\r\n	add_privs(\'smd_artstat_prefs\', $all_joined);\r\n	add_privs(\'prefs.smd_artstat\', $all_joined);\r\n	register_callback(\'smd_artstat_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'smd_article_info\', \'article\');\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n\r\n	foreach ($smd_ai_prefs as $key => $prefobj) {\r\n		register_callback(\'smd_article_info_pophelp\', \'admin_help\', $key);\r\n	}\r\n}\r\n\r\n/**\r\n * Public tag: display article statistics.\r\n *\r\n * @param  array  $atts  Tag attributes\r\n * @param  string $thing Tag container content\r\n * @return string        HTML\r\n */\r\nfunction smd_article_stats($atts, $thing = null)\r\n{\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'wraptag\'  => \'\',\r\n		\'class\'    => __FUNCTION__,\r\n		\'break\'    => \'\',\r\n		\'label\'    => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'item\'     => \'\',\r\n		\'type\'     => \'word\',\r\n	), $atts));\r\n\r\n	$out = array();\r\n\r\n	// item not specified? Use the array \'keys\' from the pref.\r\n	if (empty($item)) {\r\n		$fldList = do_list(get_pref(\'smd_artstat_fields\'));\r\n		$cfs = getCustomFields();\r\n\r\n		foreach ($fldList as $fld) {\r\n			$fldInfo = do_list($fld, \'->\');\r\n			$field = $fldInfo[0];\r\n\r\n			if (strpos($field, \'custom_\') !== false) {\r\n				$cfnum = str_replace(\'custom_\', \'\', $field);\r\n				if (array_key_exists($cfnum, $cfs)) {\r\n					$field = $cfs[$cfnum];\r\n				} else {\r\n					// Bogus CF: skip it\r\n					continue;\r\n				}\r\n			}\r\n\r\n			$item[] = strtolower($field);\r\n		}\r\n	} else {\r\n		$item = do_list($item);\r\n	}\r\n\r\n	$ret = smd_article_info_count($item, $thisarticle);\r\n	$out = ($type === \'char\') ? $ret[\'char\'] : $ret[\'word\'];\r\n\r\n	return doLabel($label, $labeltag) . doWrap($out, $wraptag, $break, $class);\r\n}\r\n\r\n/**\r\n * Admin-side info -- auto-updated via jQuery.\r\n *\r\n * @param  string $event Textpattern event (panel)\r\n * @param  string $step  Textpattern step (action)\r\n * @return string        HTML\r\n */\r\nfunction smd_article_info($event, $step)\r\n{\r\n	global $app_mode;\r\n\r\n	extract(gpsa(array(\'view\')));\r\n\r\n	include_once txpath.\'/publish/taghandlers.php\';\r\n\r\n	if(!$view || gps(\'save\') || gps(\'publish\')) {\r\n		$view = \'text\';\r\n	}\r\n\r\n	if ($view == \'text\') {\r\n		$screen_locs = array(\r\n			\'none\'                  => \'\',\r\n			\'excerpt_below\'         => \'jq|.excerpt|after\',\r\n			\'author_below\'          => \'jq|.author|after\',\r\n			\'sort_display_above\'    => \'jq|#txp-write-sort-groups|before\',\r\n			\'title_above\'           => \'jq|#main_content .title|prepend\',\r\n			\'textfilter_help_above\' => \'jq|#txp-textfilter-group|before\',\r\n			\'textfilter_help_below\' => \'jq|#txp-textfilter-group|after\',\r\n		);\r\n\r\n		// Check hidden pref and sanitize\r\n		$posn = get_pref(\'smd_artstat_pos\', \'sort_display_above\');\r\n		$posn = (array_key_exists($posn, $screen_locs)) ? $posn : \'sort_display_above\';\r\n		$show_words = get_pref(\'smd_artstat_show_word\', \'1\');\r\n		$show_chars = get_pref(\'smd_artstat_show_char\', \'1\');\r\n\r\n		$placer = explode(\'|\', $screen_locs[$posn]);\r\n		doArray($placer, \'escape_js\');\r\n\r\n		// Split and recombine to get rid of spaces.\r\n		// @todo Error detection if missing entries.\r\n		$fldList = do_list(get_pref(\'smd_artstat_fields\', \'Body -> #body, Excerpt -> #excerpt\'));\r\n		$fldAnchors = array(\'0\'); // Placeholder since Status isn\'t a countable field, but we need it later.\r\n		$db_fields = array(\'Status\');\r\n\r\n		foreach ($fldList as $fld) {\r\n			$fldInfo = do_list($fld, \'->\');\r\n			$db_fields[] = $fldInfo[0];\r\n\r\n			if (isset($fldInfo[1])) {\r\n				$fldAnchors[] = $fldInfo[1];\r\n			}\r\n		}\r\n\r\n		array_shift($fldAnchors); // Goodbye Status anchor.\r\n\r\n		$js_fields = escape_js(implode(\',\', $fldAnchors));\r\n		$js_array_fields = implode(\',\', doArray(doArray($fldAnchors, \'escape_js\'), \'doQuote\'));\r\n\r\n		$id = (empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\']);\r\n\r\n		if (empty($id)) {\r\n			$rs = $db_fields;\r\n		} else {\r\n			$rs = safe_row(implode(\',\', doArray($db_fields, \'doSlash\')), \'textpattern\', \'ID=\' . doSlash($id));\r\n		}\r\n\r\n		$idlink = (get_pref(\'smd_artstat_id\') === \'1\')\r\n			? (($id && in_array($rs[\'Status\'], array(STATUS_LIVE, STATUS_STICKY)))\r\n				? href($id, permlinkurl_id($id))\r\n				: $id)\r\n			: \'\';\r\n\r\n		$indiv = array(\r\n			\'word\' => array(),\r\n			\'char\' => array(),\r\n		);\r\n\r\n		$totals = array(\r\n			\'word\' => 0,\r\n			\'char\' => 0,\r\n		);\r\n\r\n		array_shift($db_fields); // Goodbye Status field.\r\n\r\n		$info = smd_article_info_count($db_fields, $rs);\r\n\r\n		foreach ($info as $type => $block) {\r\n			$counter = 0;\r\n\r\n			foreach ($block as $fld => $qty) {\r\n				$totals[$type] += $qty;\r\n				$indiv[$type][] = \'<span class=\"smd_article_\' . $type . \'_stats_\' . $counter . \'\">\' . $qty . \'</span>\';\r\n				$counter++;\r\n			}\r\n		}\r\n\r\n		gTxtScript(array(\'smd_artstat_word_singular\', \'smd_artstat_word_plural\'));\r\n		gTxtScript(array(\'smd_artstat_char_singular\', \'smd_artstat_char_plural\'));\r\n		$singstring = get_pref(\'smd_artstat_singular\', \'1\');\r\n		$singles = do_list($singstring);\r\n		$content = array();\r\n\r\n		if ($show_words) {\r\n			$content[] = \'<span class=\"smd_article_stats_wc\">\' . $totals[\'word\'] . \'</span> <span class=\"smd_article_stats_wd\">\' . (in_array($totals[\'word\'], $singles) ? gTxt(\'smd_artstat_word_singular\') : gTxt(\'smd_artstat_word_plural\')) . \'</span>\';\r\n\r\n			if (count($indiv[\'word\']) > 1) {\r\n				$content[] = \' ( \' . implode(\' / \', $indiv[\'word\']) . \' )\';\r\n			}\r\n		}\r\n\r\n		if ($show_chars) {\r\n			$content[] = ($content ? \' | \' : \'\') . \'<span class=\"smd_article_stats_cc\">\' . $totals[\'char\'] . \'</span> <span class=\"smd_article_stats_cd\">\' . (in_array($totals[\'char\'], $singles) ? gTxt(\'smd_artstat_char_singular\') : gTxt(\'smd_artstat_char_plural\')) . \'</span>\';\r\n\r\n			if (count($indiv[\'char\']) > 1) {\r\n				$content[] = \' ( \' . implode(\' / \', $indiv[\'char\']) . \' )\';\r\n			}\r\n		}\r\n\r\n		if ($idlink) {\r\n			$content[] = ($content ? \' | \' : \'\') . gTxt(\'id\') . n . $idlink;\r\n		}\r\n\r\n		$out1 = escape_js(\r\n			defined(\'PREF_PLUGIN\')\r\n				? wrapGroup(\'smd_artstat\', implode(n, $content), \'smd_artstat\')\r\n				: \'<fieldset><legend>\'.gTxt(\'smd_artstat_legend\').\'</legend><p>\' . implode(n, $content) . \'</p></fieldset>\'\r\n		);\r\n\r\n		$out2 = script_js(<<<EOJS\r\njQuery(function() {\r\n	var singlist = [{$singstring}];\r\n\r\n	jQuery(\"{$js_fields}\").keyup(function() {\r\n		var flds = [{$js_array_fields}];\r\n		var wds = 0;\r\n		var chs = 0;\r\n		var content = \'\';\r\n\r\n		for (idx = 0; idx < flds.length; idx++) {\r\n			if (jQuery(flds[idx]).length > 0) {\r\n				content = jQuery(flds[idx]).val();\r\n				content += (content.length > 0) ? \" \" : \"\";\r\n				content = content.replace(/(<([^>]+)>)/ig,\"\");\r\n\r\n				char_count = content.length-1;\r\n				word_count = content.split(/\\s+/).length-1;\r\n\r\n				jQuery(\".smd_article_word_stats_\"+idx).text(word_count);\r\n				jQuery(\".smd_article_char_stats_\"+idx).text(char_count);\r\n				wds += word_count;\r\n				chs += char_count;\r\n			}\r\n		}\r\n\r\n		jQuery(\".smd_article_stats_wc\").text(wds);\r\n		jQuery(\".smd_article_stats_cc\").text(chs);\r\n		jQuery(\".smd_article_stats_wd\").text(((jQuery.inArray(wds, singlist) > -1) ? textpattern.gTxt(\'smd_artstat_word_singular\') : textpattern.gTxt(\'smd_artstat_word_plural\')));\r\n		jQuery(\".smd_article_stats_cd\").text(((jQuery.inArray(chs, singlist) > -1) ? textpattern.gTxt(\'smd_artstat_char_singular\') : textpattern.gTxt(\'smd_artstat_char_plural\')));\r\n	}).keyup();\r\n});\r\nEOJS\r\n		);\r\n\r\n		if ($placer[0] === \'jq\' && $app_mode !== \'async\') {\r\n			echo \'<script type=\"text/javascript\">jQuery(function() { jQuery(\"\' . $placer[1] . \'\").\' . $placer[2] . \'(\\\'\' . $out1 . \'\\\'); });</script>\' . $out2;\r\n		}\r\n	}\r\n}\r\n\r\n/**\r\n * Get pophelp content from stefdawson.com\r\n *\r\n * @param  string $evt  Textpattern event (panel)\r\n * @param  string $stp  Textpattern step (action)\r\n * @param  string $ui   Default ui content\r\n * @param  array  $vars The variables in use for the current article\r\n * @return string       Help text\r\n */\r\nfunction smd_article_info_pophelp($evt, $stp, $ui, $vars)\r\n{\r\n	return str_replace(HELP_URL, \'http://stefdawson.com/downloads/support/\', $ui);\r\n}\r\n\r\n/**\r\n * Install prefs if they don\'t already exist.\r\n *\r\n * @param  string $evt Textpattern event (panel)\r\n * @param  string $stp Textpattern step (action)\r\n */\r\nfunction smd_artstat_prefs($evt, $stp)\r\n{\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n\r\n	foreach ($smd_ai_prefs as $key => $prefobj) {\r\n		if (get_pref($key) === \'\') {\r\n			set_pref($key, doSlash($prefobj[\'default\']), \'smd_artstat\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\'], $prefobj[\'visibility\']);\r\n		}\r\n	}\r\n}\r\n\r\n/**\r\n * Only render these prefs if enough privs exist.\r\n *\r\n * Restricted message otherwise.\r\n *\r\n * @param  string $key The preference key being displayed\r\n * @param  string $val The current preference value\r\n * @return string      HTML\r\n */\r\nfunction smd_artstat_restricted($key, $val)\r\n{\r\n	global $txp_user;\r\n	static $smd_artstat_privs = array();\r\n\r\n	if (array_key_exists($txp_user, $smd_artstat_privs)) {\r\n		$privs = $smd_artstat_privs[$txp_user];\r\n	} else {\r\n		$safe_user = doSlash($txp_user);\r\n		$privs = safe_field(\'privs\', \'txp_users\', \"name=\'$safe_user\'\");\r\n		$smd_artstat_privs[$txp_user] = $privs;\r\n	}\r\n\r\n    if ($privs === \'1\') {\r\n		return fInput(\'text\', $key, $val, \'\', \'\', \'\', INPUT_REGULAR);\r\n	} else {\r\n		return gTxt(\'smd_artstat_set_by_admin\');\r\n	}\r\n}\r\n\r\n/**\r\n * Render the position pref.\r\n *\r\n * @param  string $key The preference key being displayed\r\n * @param  string $val The current preference value\r\n * @return string      HTML\r\n */\r\nfunction smd_artstat_pos($key, $val)\r\n{\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n	$obj = $smd_ai_prefs[$key];\r\n	return selectInput($key, $obj[\'content\'], $val);\r\n}\r\n\r\n/**\r\n * Settings for the plugin.\r\n *\r\n * @return array  Preference set\r\n */\r\nfunction smd_article_info_prefs()\r\n{\r\n	$smd_ai_prefs = array(\r\n		\'smd_artstat_fields\' => array(\r\n			\'html\'       => \'smd_artstat_restricted\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 10,\r\n			\'default\'    => \'Body -> #body, Excerpt -> #excerpt\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_GLOBAL,\r\n		),\r\n		\'smd_artstat_pos\' => array(\r\n			\'html\'       => \'smd_artstat_pos\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 20,\r\n			\'content\'    => array(\r\n				\'none\'                  => gTxt(\'none\'),\r\n				\'excerpt_below\'         => gTxt(\'smd_artstat_pos_below_excerpt\'),\r\n				\'author_below\'          => gTxt(\'smd_artstat_pos_below_author\'),\r\n				\'sort_display_above\'    => gTxt(\'smd_artstat_pos_above_sort_display\'),\r\n				\'title_above\'           => gTxt(\'smd_artstat_pos_above_title\'),\r\n				\'textfilter_help_above\' => gTxt(\'smd_artstat_pos_above_textfilter\'),\r\n				\'textfilter_help_below\' => gTxt(\'smd_artstat_pos_below_textfilter\'),\r\n				),\r\n			\'default\'    => \'sort_display_above\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_PRIVATE,\r\n		),\r\n		\'smd_artstat_singular\' => array(\r\n			\'html\'       => \'smd_artstat_restricted\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 30,\r\n			\'default\'    => \'1\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_GLOBAL,\r\n		),\r\n        \'smd_artstat_show_word\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 40,\r\n            \'default\'    => \'1\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n        \'smd_artstat_show_char\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 50,\r\n            \'default\'    => \'0\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n        \'smd_artstat_id\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 60,\r\n            \'default\'    => \'0\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n	);\r\n\r\n	return $smd_ai_prefs;\r\n}\r\n\r\n/**\r\n * Library function to count words in the given field items.\r\n *\r\n * @param  string|array $item The field(s) to count\r\n * @param  array        $from The structure containing the data\r\n * @return array\r\n * @todo   Filter out common Textile markup somehow? But what about when multi textfilters hit the streets?\r\n */\r\nfunction smd_article_info_count($item, $from)\r\n{\r\n	$words = array();\r\n	$chars = array();\r\n	$notags = \'/(<([^>]+?)>)/\';\r\n\r\n	$item = is_array($item) ? $item : array($item);\r\n\r\n	foreach ($item as $whatnot) {\r\n		$content = (isset($from[$whatnot])) ? preg_replace($notags, \'\', trim($from[$whatnot])) . ((strlen($from[$whatnot])==0) ? \'\' : \' \') : \'\';\r\n\r\n		if ($content) {\r\n			if (!isset($words[$whatnot])) {\r\n				$words[$whatnot] = 0;\r\n			}\r\n\r\n			if (!isset($chars[$whatnot])) {\r\n				$chars[$whatnot] = 0;\r\n			}\r\n\r\n			$words[$whatnot] += preg_match_all(\'@\\s+@\', $content, $m);\r\n			$chars[$whatnot] += strlen($content) - 1;\r\n		}\r\n	}\r\n\r\n	return array(\'word\' => $words, \'char\' => $chars);\r\n}', '/**\r\n * smd_article_stats\r\n *\r\n * A Textpattern CMS plugin for counting words/characters in article fields and\r\n * optionally displaying them to visitors.\r\n *  -> Choose which fields to count on the admin side.\r\n *  -> Customize where you want the count to be displayed.\r\n *  -> Shows ID of currently edited article.\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n * @todo TinyMCE -- accessing fields inside iframes?\r\n */\r\n\r\nif (txpinterface === \'admin\') {\r\n	$all_privs = array_keys(get_groups());\r\n	unset($all_privs[array_search(\'0\', $all_privs)]); // Remove \'none\'.\r\n	$all_joined = implode(\',\', $all_privs);\r\n\r\n	add_privs(\'smd_artstat_prefs\', $all_joined);\r\n	add_privs(\'prefs.smd_artstat\', $all_joined);\r\n	register_callback(\'smd_artstat_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'smd_article_info\', \'article\');\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n\r\n	foreach ($smd_ai_prefs as $key => $prefobj) {\r\n		register_callback(\'smd_article_info_pophelp\', \'admin_help\', $key);\r\n	}\r\n}\r\n\r\n/**\r\n * Public tag: display article statistics.\r\n *\r\n * @param  array  $atts  Tag attributes\r\n * @param  string $thing Tag container content\r\n * @return string        HTML\r\n */\r\nfunction smd_article_stats($atts, $thing = null)\r\n{\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'wraptag\'  => \'\',\r\n		\'class\'    => __FUNCTION__,\r\n		\'break\'    => \'\',\r\n		\'label\'    => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'item\'     => \'\',\r\n		\'type\'     => \'word\',\r\n	), $atts));\r\n\r\n	$out = array();\r\n\r\n	// item not specified? Use the array \'keys\' from the pref.\r\n	if (empty($item)) {\r\n		$fldList = do_list(get_pref(\'smd_artstat_fields\'));\r\n		$cfs = getCustomFields();\r\n\r\n		foreach ($fldList as $fld) {\r\n			$fldInfo = do_list($fld, \'->\');\r\n			$field = $fldInfo[0];\r\n\r\n			if (strpos($field, \'custom_\') !== false) {\r\n				$cfnum = str_replace(\'custom_\', \'\', $field);\r\n				if (array_key_exists($cfnum, $cfs)) {\r\n					$field = $cfs[$cfnum];\r\n				} else {\r\n					// Bogus CF: skip it\r\n					continue;\r\n				}\r\n			}\r\n\r\n			$item[] = strtolower($field);\r\n		}\r\n	} else {\r\n		$item = do_list($item);\r\n	}\r\n\r\n	$ret = smd_article_info_count($item, $thisarticle);\r\n	$out = ($type === \'char\') ? $ret[\'char\'] : $ret[\'word\'];\r\n\r\n	return doLabel($label, $labeltag) . doWrap($out, $wraptag, $break, $class);\r\n}\r\n\r\n/**\r\n * Admin-side info -- auto-updated via jQuery.\r\n *\r\n * @param  string $event Textpattern event (panel)\r\n * @param  string $step  Textpattern step (action)\r\n * @return string        HTML\r\n */\r\nfunction smd_article_info($event, $step)\r\n{\r\n	global $app_mode;\r\n\r\n	extract(gpsa(array(\'view\')));\r\n\r\n	include_once txpath.\'/publish/taghandlers.php\';\r\n\r\n	if(!$view || gps(\'save\') || gps(\'publish\')) {\r\n		$view = \'text\';\r\n	}\r\n\r\n	if ($view == \'text\') {\r\n		$screen_locs = array(\r\n			\'none\'                  => \'\',\r\n			\'excerpt_below\'         => \'jq|.excerpt|after\',\r\n			\'author_below\'          => \'jq|.author|after\',\r\n			\'sort_display_above\'    => \'jq|#txp-write-sort-groups|before\',\r\n			\'title_above\'           => \'jq|#main_content .title|prepend\',\r\n			\'textfilter_help_above\' => \'jq|#txp-textfilter-group|before\',\r\n			\'textfilter_help_below\' => \'jq|#txp-textfilter-group|after\',\r\n		);\r\n\r\n		// Check hidden pref and sanitize\r\n		$posn = get_pref(\'smd_artstat_pos\', \'sort_display_above\');\r\n		$posn = (array_key_exists($posn, $screen_locs)) ? $posn : \'sort_display_above\';\r\n		$show_words = get_pref(\'smd_artstat_show_word\', \'1\');\r\n		$show_chars = get_pref(\'smd_artstat_show_char\', \'1\');\r\n\r\n		$placer = explode(\'|\', $screen_locs[$posn]);\r\n		doArray($placer, \'escape_js\');\r\n\r\n		// Split and recombine to get rid of spaces.\r\n		// @todo Error detection if missing entries.\r\n		$fldList = do_list(get_pref(\'smd_artstat_fields\', \'Body -> #body, Excerpt -> #excerpt\'));\r\n		$fldAnchors = array(\'0\'); // Placeholder since Status isn\'t a countable field, but we need it later.\r\n		$db_fields = array(\'Status\');\r\n\r\n		foreach ($fldList as $fld) {\r\n			$fldInfo = do_list($fld, \'->\');\r\n			$db_fields[] = $fldInfo[0];\r\n\r\n			if (isset($fldInfo[1])) {\r\n				$fldAnchors[] = $fldInfo[1];\r\n			}\r\n		}\r\n\r\n		array_shift($fldAnchors); // Goodbye Status anchor.\r\n\r\n		$js_fields = escape_js(implode(\',\', $fldAnchors));\r\n		$js_array_fields = implode(\',\', doArray(doArray($fldAnchors, \'escape_js\'), \'doQuote\'));\r\n\r\n		$id = (empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\']);\r\n\r\n		if (empty($id)) {\r\n			$rs = $db_fields;\r\n		} else {\r\n			$rs = safe_row(implode(\',\', doArray($db_fields, \'doSlash\')), \'textpattern\', \'ID=\' . doSlash($id));\r\n		}\r\n\r\n		$idlink = (get_pref(\'smd_artstat_id\') === \'1\')\r\n			? (($id && in_array($rs[\'Status\'], array(STATUS_LIVE, STATUS_STICKY)))\r\n				? href($id, permlinkurl_id($id))\r\n				: $id)\r\n			: \'\';\r\n\r\n		$indiv = array(\r\n			\'word\' => array(),\r\n			\'char\' => array(),\r\n		);\r\n\r\n		$totals = array(\r\n			\'word\' => 0,\r\n			\'char\' => 0,\r\n		);\r\n\r\n		array_shift($db_fields); // Goodbye Status field.\r\n\r\n		$info = smd_article_info_count($db_fields, $rs);\r\n\r\n		foreach ($info as $type => $block) {\r\n			$counter = 0;\r\n\r\n			foreach ($block as $fld => $qty) {\r\n				$totals[$type] += $qty;\r\n				$indiv[$type][] = \'<span class=\"smd_article_\' . $type . \'_stats_\' . $counter . \'\">\' . $qty . \'</span>\';\r\n				$counter++;\r\n			}\r\n		}\r\n\r\n		gTxtScript(array(\'smd_artstat_word_singular\', \'smd_artstat_word_plural\'));\r\n		gTxtScript(array(\'smd_artstat_char_singular\', \'smd_artstat_char_plural\'));\r\n		$singstring = get_pref(\'smd_artstat_singular\', \'1\');\r\n		$singles = do_list($singstring);\r\n		$content = array();\r\n\r\n		if ($show_words) {\r\n			$content[] = \'<span class=\"smd_article_stats_wc\">\' . $totals[\'word\'] . \'</span> <span class=\"smd_article_stats_wd\">\' . (in_array($totals[\'word\'], $singles) ? gTxt(\'smd_artstat_word_singular\') : gTxt(\'smd_artstat_word_plural\')) . \'</span>\';\r\n\r\n			if (count($indiv[\'word\']) > 1) {\r\n				$content[] = \' ( \' . implode(\' / \', $indiv[\'word\']) . \' )\';\r\n			}\r\n		}\r\n\r\n		if ($show_chars) {\r\n			$content[] = ($content ? \' | \' : \'\') . \'<span class=\"smd_article_stats_cc\">\' . $totals[\'char\'] . \'</span> <span class=\"smd_article_stats_cd\">\' . (in_array($totals[\'char\'], $singles) ? gTxt(\'smd_artstat_char_singular\') : gTxt(\'smd_artstat_char_plural\')) . \'</span>\';\r\n\r\n			if (count($indiv[\'char\']) > 1) {\r\n				$content[] = \' ( \' . implode(\' / \', $indiv[\'char\']) . \' )\';\r\n			}\r\n		}\r\n\r\n		if ($idlink) {\r\n			$content[] = ($content ? \' | \' : \'\') . gTxt(\'id\') . n . $idlink;\r\n		}\r\n\r\n		$out1 = escape_js(\r\n			defined(\'PREF_PLUGIN\')\r\n				? wrapGroup(\'smd_artstat\', implode(n, $content), \'smd_artstat\')\r\n				: \'<fieldset><legend>\'.gTxt(\'smd_artstat_legend\').\'</legend><p>\' . implode(n, $content) . \'</p></fieldset>\'\r\n		);\r\n\r\n		$out2 = script_js(<<<EOJS\r\njQuery(function() {\r\n	var singlist = [{$singstring}];\r\n\r\n	jQuery(\"{$js_fields}\").keyup(function() {\r\n		var flds = [{$js_array_fields}];\r\n		var wds = 0;\r\n		var chs = 0;\r\n		var content = \'\';\r\n\r\n		for (idx = 0; idx < flds.length; idx++) {\r\n			if (jQuery(flds[idx]).length > 0) {\r\n				content = jQuery(flds[idx]).val();\r\n				content += (content.length > 0) ? \" \" : \"\";\r\n				content = content.replace(/(<([^>]+)>)/ig,\"\");\r\n\r\n				char_count = content.length-1;\r\n				word_count = content.split(/\\s+/).length-1;\r\n\r\n				jQuery(\".smd_article_word_stats_\"+idx).text(word_count);\r\n				jQuery(\".smd_article_char_stats_\"+idx).text(char_count);\r\n				wds += word_count;\r\n				chs += char_count;\r\n			}\r\n		}\r\n\r\n		jQuery(\".smd_article_stats_wc\").text(wds);\r\n		jQuery(\".smd_article_stats_cc\").text(chs);\r\n		jQuery(\".smd_article_stats_wd\").text(((jQuery.inArray(wds, singlist) > -1) ? textpattern.gTxt(\'smd_artstat_word_singular\') : textpattern.gTxt(\'smd_artstat_word_plural\')));\r\n		jQuery(\".smd_article_stats_cd\").text(((jQuery.inArray(chs, singlist) > -1) ? textpattern.gTxt(\'smd_artstat_char_singular\') : textpattern.gTxt(\'smd_artstat_char_plural\')));\r\n	}).keyup();\r\n});\r\nEOJS\r\n		);\r\n\r\n		if ($placer[0] === \'jq\' && $app_mode !== \'async\') {\r\n			echo \'<script type=\"text/javascript\">jQuery(function() { jQuery(\"\' . $placer[1] . \'\").\' . $placer[2] . \'(\\\'\' . $out1 . \'\\\'); });</script>\' . $out2;\r\n		}\r\n	}\r\n}\r\n\r\n/**\r\n * Get pophelp content from stefdawson.com\r\n *\r\n * @param  string $evt  Textpattern event (panel)\r\n * @param  string $stp  Textpattern step (action)\r\n * @param  string $ui   Default ui content\r\n * @param  array  $vars The variables in use for the current article\r\n * @return string       Help text\r\n */\r\nfunction smd_article_info_pophelp($evt, $stp, $ui, $vars)\r\n{\r\n	return str_replace(HELP_URL, \'http://stefdawson.com/downloads/support/\', $ui);\r\n}\r\n\r\n/**\r\n * Install prefs if they don\'t already exist.\r\n *\r\n * @param  string $evt Textpattern event (panel)\r\n * @param  string $stp Textpattern step (action)\r\n */\r\nfunction smd_artstat_prefs($evt, $stp)\r\n{\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n\r\n	foreach ($smd_ai_prefs as $key => $prefobj) {\r\n		if (get_pref($key) === \'\') {\r\n			set_pref($key, doSlash($prefobj[\'default\']), \'smd_artstat\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\'], $prefobj[\'visibility\']);\r\n		}\r\n	}\r\n}\r\n\r\n/**\r\n * Only render these prefs if enough privs exist.\r\n *\r\n * Restricted message otherwise.\r\n *\r\n * @param  string $key The preference key being displayed\r\n * @param  string $val The current preference value\r\n * @return string      HTML\r\n */\r\nfunction smd_artstat_restricted($key, $val)\r\n{\r\n	global $txp_user;\r\n	static $smd_artstat_privs = array();\r\n\r\n	if (array_key_exists($txp_user, $smd_artstat_privs)) {\r\n		$privs = $smd_artstat_privs[$txp_user];\r\n	} else {\r\n		$safe_user = doSlash($txp_user);\r\n		$privs = safe_field(\'privs\', \'txp_users\', \"name=\'$safe_user\'\");\r\n		$smd_artstat_privs[$txp_user] = $privs;\r\n	}\r\n\r\n    if ($privs === \'1\') {\r\n		return fInput(\'text\', $key, $val, \'\', \'\', \'\', INPUT_REGULAR);\r\n	} else {\r\n		return gTxt(\'smd_artstat_set_by_admin\');\r\n	}\r\n}\r\n\r\n/**\r\n * Render the position pref.\r\n *\r\n * @param  string $key The preference key being displayed\r\n * @param  string $val The current preference value\r\n * @return string      HTML\r\n */\r\nfunction smd_artstat_pos($key, $val)\r\n{\r\n	$smd_ai_prefs = smd_article_info_prefs();\r\n	$obj = $smd_ai_prefs[$key];\r\n	return selectInput($key, $obj[\'content\'], $val);\r\n}\r\n\r\n/**\r\n * Settings for the plugin.\r\n *\r\n * @return array  Preference set\r\n */\r\nfunction smd_article_info_prefs()\r\n{\r\n	$smd_ai_prefs = array(\r\n		\'smd_artstat_fields\' => array(\r\n			\'html\'       => \'smd_artstat_restricted\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 10,\r\n			\'default\'    => \'Body -> #body, Excerpt -> #excerpt\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_GLOBAL,\r\n		),\r\n		\'smd_artstat_pos\' => array(\r\n			\'html\'       => \'smd_artstat_pos\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 20,\r\n			\'content\'    => array(\r\n				\'none\'                  => gTxt(\'none\'),\r\n				\'excerpt_below\'         => gTxt(\'smd_artstat_pos_below_excerpt\'),\r\n				\'author_below\'          => gTxt(\'smd_artstat_pos_below_author\'),\r\n				\'sort_display_above\'    => gTxt(\'smd_artstat_pos_above_sort_display\'),\r\n				\'title_above\'           => gTxt(\'smd_artstat_pos_above_title\'),\r\n				\'textfilter_help_above\' => gTxt(\'smd_artstat_pos_above_textfilter\'),\r\n				\'textfilter_help_below\' => gTxt(\'smd_artstat_pos_below_textfilter\'),\r\n				),\r\n			\'default\'    => \'sort_display_above\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_PRIVATE,\r\n		),\r\n		\'smd_artstat_singular\' => array(\r\n			\'html\'       => \'smd_artstat_restricted\',\r\n			\'type\'       => PREF_PLUGIN,\r\n			\'position\'   => 30,\r\n			\'default\'    => \'1\',\r\n			\'group\'      => \'smd_artstat_settings\',\r\n			\'visibility\' => PREF_GLOBAL,\r\n		),\r\n        \'smd_artstat_show_word\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 40,\r\n            \'default\'    => \'1\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n        \'smd_artstat_show_char\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 50,\r\n            \'default\'    => \'0\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n        \'smd_artstat_id\' => array(\r\n            \'html\'       => \'yesnoradio\',\r\n            \'type\'       => PREF_PLUGIN,\r\n            \'position\'   => 60,\r\n            \'default\'    => \'0\',\r\n            \'group\'      => \'smd_artstat_settings\',\r\n            \'visibility\' => PREF_PRIVATE,\r\n        ),\r\n	);\r\n\r\n	return $smd_ai_prefs;\r\n}\r\n\r\n/**\r\n * Library function to count words in the given field items.\r\n *\r\n * @param  string|array $item The field(s) to count\r\n * @param  array        $from The structure containing the data\r\n * @return array\r\n * @todo   Filter out common Textile markup somehow? But what about when multi textfilters hit the streets?\r\n */\r\nfunction smd_article_info_count($item, $from)\r\n{\r\n	$words = array();\r\n	$chars = array();\r\n	$notags = \'/(<([^>]+?)>)/\';\r\n\r\n	$item = is_array($item) ? $item : array($item);\r\n\r\n	foreach ($item as $whatnot) {\r\n		$content = (isset($from[$whatnot])) ? preg_replace($notags, \'\', trim($from[$whatnot])) . ((strlen($from[$whatnot])==0) ? \'\' : \' \') : \'\';\r\n\r\n		if ($content) {\r\n			if (!isset($words[$whatnot])) {\r\n				$words[$whatnot] = 0;\r\n			}\r\n\r\n			if (!isset($chars[$whatnot])) {\r\n				$chars[$whatnot] = 0;\r\n			}\r\n\r\n			$words[$whatnot] += preg_match_all(\'@\\s+@\', $content, $m);\r\n			$chars[$whatnot] += strlen($content) - 1;\r\n		}\r\n	}\r\n\r\n	return array(\'word\' => $words, \'char\' => $chars);\r\n}', 'ea18ef10409a16c084b27a1653b70f6b', '#@smd_artstat\n#@language fr\nsmd_artstat => Statistiques d\'article\nsmd_artstat_fields => Champs de décompte et sélecteur du DOM\nsmd_artstat_id => Afficher l\'ID de l\'article\nsmd_artstat_legend => Statistiques article\nsmd_artstat_pos => Emplacement dans la page\nsmd_artstat_pos_above_status => Au dessus des statuts\nsmd_artstat_pos_above_textfilter => Au dessus du sélecteur Aide Textile\nsmd_artstat_pos_above_textile => Au dessus de l\'aide Textile\nsmd_artstat_pos_above_title => Au dessus du titre\nsmd_artstat_pos_below_author => Sous le nom d\'auteur\nsmd_artstat_pos_below_excerpt => Sous le résumé\nsmd_artstat_pos_below_textfilter => En dessous du sélecteur Aide Textile\nsmd_artstat_pos_below_textile => En dessous de l\'aide Textile\nsmd_artstat_singular => Nombre pris au singulier\nsmd_artstat_word_plural => Mots\nsmd_artstat_word_singular => Mot', '', 5, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_featured', 0, 'Stef Dawson', 'https://stefdawson.com/', '0.6.0', 'Quickly label and display featured articles for your home / landing pages', '<h1>smd_featured</h1>\n\n<p>Pull one or more articles out of the regular article flow and pimp them &#8212; perhaps on a section landing page or your home page.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>One-click selection of articles to feature</li>\n	<li>Label your featured articles, allowing you to group them into areas like Featured, Teasers, etc</li>\n	<li>Add an optional title/description to each article so you can use that as copy, instead of something from the article itself (supports Textile and some <span class=\"caps\">TXP</span> tags)</li>\n	<li>Search your featured articles by name or label; filtering in real-time if you wish</li>\n	<li>Administrators can limit the articles available for pimpage from certain sections</li>\n	<li>Display information from the featured articles using regular <span class=\"caps\">TXP</span> article tags</li>\n	<li>Keeps track of which articles have been seen already on the page so later featured (or unfeatured) articles aren&#8217;t duplicated</li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p class=\"important\">Requires Textpattern 4.6.0+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1174/smd_featured\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=33938\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>When you install the plugin, the necessary table is automatically installed for you. There&#8217;s currently no facility for removing it during plugin operation, but it will be deleted if the plugin is deleted from your system. Note that if you put the plugin in the cache dir and therefore don&#8217;t go though the normal installation procedure, the table is <strong>not</strong> installed automatically; visit the <em>Content-&gt;Featured articles</em> tab and click the <em>Install table</em> button.</p>\n\n<p>To uninstall, simply delete the plugin from the <em>Admin-&gt;Plugins</em> page.</p>\n\n<p class=\"important\">Unfortunately, when upgrading from v0.3x to v0.40, your Textile preferences will be lost. Please reset them by visiting the <em>Featured articles</em> tab with an administrator account and adjusting the prefs.</p>\n\n<h2>Configuring featured articles</h2>\n\n<p>Visit the <em>Content-&gt;Featured articles</em> tab. A list of all articles (from the admin-selected sections) are presented in a grid.</p>\n\n<h3>To feature an article</h3>\n\n<p>Click its name. It will be highlighted and (if permitted by the administrator) an <code>[Edit]</code> button appears beside it. If you choose not to edit the article information, it will be featured but unlabelled.</p>\n\n<p>An arrow also appears in the lower right hand corner of the box. Click this to take you to the Write tab to view/edit the original article. The same link is available by clicking the article Title while editing an entry.</p>\n\n<p>In the lower left corner of the box, a position input field appears. You can type anything in this box to specify a sort order for the featured item; as soon as the cursor leaves the box the value is saved. You are free to use whichever sort scheme works for you &#8212; numbers, alphanumerics, squiggles, anything. This does not alter the order of the items in the grid (use the <em>Sort by</em> value in the plugin&#8217;s control panel for that) but allows you to order the items as they appear on your site. You may leave this box empty if you wish. Note that sorting is performed on a per-label basis so you may reuse sort values under different labels.</p>\n\n<p>Clicking <code>[Edit]</code> will display up to four input boxes, depending on the <a href=\"#smd_feat_prefs\">prefs</a>:</p>\n\n<ul>\n	<li><strong>Label</strong> : allows you to label (group) the article with others of a similar featured status. For example, you could label some articles to go in a &#8216;teasers&#8217; block on your home page by labelling each potential article with <code>Teaser</code>. Be careful: <strong>labels are case sensitive</strong>. To help, you may choose a label from the dropdown list of all currently defined labels or enter a new one.</li>\n	<li><strong>Title</strong> : allows you to store some Textile-aware title related to the article. This is different from the article&#8217;s actual Title and can be used for any purpose you see fit.</li>\n	<li><strong>Description</strong> : allows you to store some Textile-aware supporting information about the article. This could simply be additional information for your own use, or some copy that you may wish to display in your teasers block to entice people to read the article. It can be as simple or as complex as you like and can contain <span class=\"caps\">TXP</span> tags (if they make sense to use at that point &#8212; article tags cannot be used, for example, but <code>&lt;txp:site_url /&gt;</code> can).</li>\n	<li><strong>Position</strong> : allows you to store some positioning text (up to 16 characters) that governs the order this item appears on your site. This is the same value as in the little box in the lower left corner of the article cell.</li>\n</ul>\n\n<p>When you&#8217;re happy with your values, hit Save to store the entry. You can edit the information at any time by clicking the appropriate <code>[Edit]</code> button from the featured article list.</p>\n\n<h3>To unfeature an article</h3>\n\n<p>Click its name again. You may be offered a confirmation dialog box, in which case confirm that you are sure first. The article&#8217;s label, title and description are deleted, so be sure you really want to remove them, or have backed the information up somewhere.</p>\n\n<h3>Finding articles to feature</h3>\n\n<p>With large article lists it can become difficult to find the one you want. Above the grid is a control panel. Click it to reveal the plugin options.  On the left is the Article search area and on the right are the plugin preferences. If you are in <em>Display: All</em> mode, simply start typing in the <em>By name</em> box and your article list will be reduced as you type to only those that match. Type multiple words to find articles that contain any of the given words. To quickly clear the box, hit the <span class=\"caps\">ESC</span>ape key.</p>\n\n<p>Alternatively, you can use the select list to choose all articles with a particular label. The empty entry at the top of the list means &#8220;all articles&#8221;. If you have featured an article but don&#8217;t supply a label you can filter it by choosing the <code>[unlabelled]</code> item from the list. If you have labelled any articles, each label you have supplied will also be an option in the list. Remember that labels are case sensitive.</p>\n\n<p>Note that filtering by name or by label are mutually exclusive actions: selecting something from the dropdown will clear the text box, and typing something in the text box will reset the dropdown.</p>\n\n<p>Your search criteria are remembered between edit/list views so you won&#8217;t lose your place. If you are in <em>Display: Paginated</em> mode, the search works in exactly the same way but you have to hit Enter or click <em>Go</em> to submit the search criteria. This mode is handy for very long article lists or slow servers / computers.</p>\n\n<h3 id=\"smd_feat_prefs\">Plugin preferences</h3>\n\n<p>Users of the <em>Featured articles</em> tab also have a few preferences at their disposal which can be altered at will:</p>\n\n<ol>\n	<li><strong>Display</strong> : choose whether you wish to display all articles on one page (and use the live search to filter them) or choose a paginated view (and use the traditional search). The traditional search might be useful if you have a lot of articles (over 1000) because the live search might slow down significantly. Note that when you search for something in paginated mode, the results themselves aren&#8217;t paginated.</li>\n	<li><strong>Sort by</strong> : define the sort order of the article list.</li>\n	<li><strong>Box size</strong> : specify the <code>width</code> and <code>height</code> values in pixels of each cell in the grid. Default is <code>150x40</code>.</li>\n</ol>\n\n<p>Administrators can also alter the behaviour of the plugin by altering the following prefs:</p>\n\n<ol>\n	<li><strong>Permit entry of</strong>: choose which featured elements users are permitted to enter/alter. If you disable all the checkboxes, the [Edit] links next to featured articles will not appear.</li>\n	<li><strong>Apply textile to</strong>: choose the items to which you wish to apply Textile when you Edit and Save a featured article. <strong>Note this only affects featured articles saved from this point forward</strong>. Thus if you wish to apply a new Textile setting to existing articles you need to visit each one in turn by clicking <code>[Edit]</code> and then <em>Save</em>.</li>\n	<li><strong>Articles from sections</strong>: define a comma-separated list of section names. These will be the only sections from which articles are allowed to be featured. If you leave this box empty you or your users may specify <code>&amp;section_list=list,of,sections,...</code> in the smd_featured tab&#8217;s <span class=\"caps\">URL</span> to limit the list to only articles in the given sections. This is handy if you wish to direct people to the smd_featured tab from a link in another tab.</li>\n</ol>\n\n<p>In addition, if you create a hidden pref called <code>smd_featured_privs</code> you can limit the privilege levels of accounts that can access the <em>Featured articles</em> tab. Default: <code>1,2</code>. If using smd_prefalizer to create this pref, set the name and value as above and set the following additional parameters:</p>\n\n<ul>\n	<li><strong>type</strong> : hidden</li>\n	<li><strong>event</strong> : smd_featured</li>\n	<li><strong>input control</strong> : text_input</li>\n	<li><strong>user</strong> : / empty /</li>\n	<li><strong>position</strong>: 0</li>\n</ul>\n\n<h2>Displaying featured articles</h2>\n\n<p>Once you have chosen your featured articles, you need a way to display them:</p>\n\n<h3>Tag: <code>&lt;txp:smd_featured /&gt;</code></h3>\n\n<p>Acts a bit like the standard <code>&lt;txp:article /&gt;</code> tag to display featured articles. Attributes:</p>\n\n<ul>\n	<li><span class=\"atnm\">label</span> : display featured articles with this label. Specify <code>label=&quot;&quot;</code> to display unlabelled articles (which are <strong>not</strong> the same as unfeatured articles). If this attribute is omitted, all featured articles &#8212; irrespective of label &#8212; will be displayed.</li>\n	<li><span class=\"atnm\">labeltag</span> : the (X)HTML tag &#8212; without brackets &#8212; to apply to the label attribute. If this attribute is specified the label itself is displayed at the head of your featured article list. If you wish to display unlabelled articles you need to specify the <code>unlabel</code> attribute or you&#8217;ll see no label. Note that this tag differs from the <span class=\"caps\">TXP</span> convention: you will <em>only</em> see the label if you specify the <code>labeltag</code>. You can display the label inside your articles using the <code>&lt;txp:featured_info /&gt;</code> tag.</li>\n	<li><span class=\"atnm\">unlabel</span> : when using <code>label=&quot;&quot;</code> (to show unlabelled articles) this attribute specifies the label to use inside the <code>labeltag</code>. Default: &#8220;Featured&#8221;.</li>\n	<li><span class=\"atnm\">time</span> : limit the articles to those in the <code>past</code>, the <code>future</code> or <code>any</code>. Default: <code>past</code>.</li>\n	<li><span class=\"atnm\">status</span> : only display articles of the given status number(s). Default: <code>4,5</code> (a.k.a. live and sticky). Note that this attribute may be ignored if you are trying to view anything other live and sticky articles (since article_custom does the same).</li>\n	<li><span class=\"atnm\">section</span> : only display articles from the given section(s). If the section list is restricted using the plugin preferences, the sections used here must also be in that list.</li>\n	<li><span class=\"atnm\">history</span> : determines whether to remember previously seen articles and thus exclude them from future smd_featured / smd_unfeatured tags. If you are using the same featured tag multiple times on the same page and want it to always output the same list, set this attribute to 0. Default: 1.</li>\n	<li><span class=\"atnm\">form</span> : use this <span class=\"caps\">TXP</span> form to display each article, instead of using the container contents.</li>\n	<li><span class=\"atnm\">limit</span> : maximum number of featured articles to display. Default: 10.</li>\n	<li><span class=\"atnm\">sort</span> : order the featured articles by <code>id</code>, <code>label</code>, <code>Title</code>, <code>Posted</code>, and so on. Add <code>asc</code> or <code>desc</code> to the attribute to alter the direction of the sort. You may also use <code>rand()</code> to sort the articles randomly. Default: <code>feat_position asc, Posted desc</code>. The same list available for the <code>&lt;txp:article /&gt;</code> tag is usable here. You may also use:\n	<ul>\n		<li><strong>feat_position</strong> : your custom position</li>\n		<li><strong>feat_title</strong> : your featured title content</li>\n		<li><strong>description</strong> : your featured description content</li>\n	</ul></li>\n	<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag &#8212; without brackets &#8212; to wrap around the list of featured articles.</li>\n	<li><span class=\"atnm\">break</span> : the (X)HTML tag &#8212; without brackets &#8212; or text to put between each article.</li>\n	<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> class name to apply to the wraptag.</li>\n	<li><span class=\"atnm\">html_id</span> : a <span class=\"caps\">DOM</span> ID to apply to the wraptag.</li>\n</ul>\n\n<p>You may use the tag as a container and any tags you specify will be displayed in each featured article. You can use standard article tags such as <code>&lt;txp:title /&gt;</code>, <code>&lt;txp:excerpt /&gt;</code>, <code>&lt;txp:permlink /&gt;</code>, etc, although some articles are not displayable by Textpattern (e.g. hidden, pending and draft articles).</p>\n\n<p class=\"important\">Note that you can&#8217;t use <code>&lt;txp:smd_featured&gt;</code> inside an article or an Article Form. Doing so will trigger a warning because it&#8217;s the same as putting another article tag inside an article. Use the tag only in Pages, or in a Form that is never directly used to display article content.</p>\n\n<p>Each time you use <code>&lt;txp:smd_featured /&gt;</code> it makes a note of the articles it has displayed and will not duplicate articles in later tags. You can use <code>&lt;txp:else /&gt;</code> in your form / container to take action if the featured list is empty.</p>\n\n<h3>Tag: <code>&lt;txp:smd_featured_info /&gt;</code></h3>\n\n<p>Allows you to display the label or the description within your <code>&lt;txp:smd_featured /&gt;</code>  form/container.</p>\n\n<p>Has one attribute:</p>\n\n<ul>\n	<li><span class=\"atnm\">item</span> : the thing you want to display. Choose from:\n	<ul>\n		<li><strong>label</strong>: the label.</li>\n		<li><strong>title</strong>: the (possibly Textile-processed) title.</li>\n		<li><strong>description</strong>: the (possibly Textile-processed) description.</li>\n	</ul></li>\n	</ul>\n\n<h3>Tag: <code>&lt;txp:smd_unfeatured /&gt;</code></h3>\n\n<p>Use this tag if you wish to show the &#8216;remaining&#8217; articles &#8212; i.e. ones not already displayed by any preceding <code>&lt;txp:smd_featured /&gt;</code> tags. It acts very much like the built-in article_custom tag, and takes all the same attributes. Thus, by default, its context is &#8220;any articles from any section defined in the smd_featured plugin that have not been displayed already&#8221;. You can reduce this list by <code>section</code>, <code>time</code> or <code>status</code>.</p>\n\n<p>Unlike article_custom, <code>&lt;txp:smd_unfeatured /&gt;</code> supports pagination and <code>&lt;txp:else /&gt;</code> (for when your unfeatured list returns nothing). You can also specify the <code>history</code> attribute and it works exactly the same way as it does for <code>&lt;txp:smd_featured /&gt;</code>. Thus it can be a very useful, pageable direct replacement tag for article_custom if you use <code>history=&quot;0&quot;</code>.</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>to use pagination, remove any article tag and add the <code>pageby</code> attribute (and of course <code>limit</code>) to the smd_unfeatured tag. Note you may get a warning about no article tag on the page, but you can ignore it.</li>\n	<li>if you wish the <code>pageby</code> attribute to track any <code>limit</code> you set, specify <code>pageby=&quot;limit&quot;</code>.</li>\n	<li>if you have any other paging in effect on the page, unfeatured pagination will be ignored.</li>\n</ul>\n\n<h3>Tag: <code>&lt;txp:smd_if_featured&gt;</code></h3>\n\n<p>This tag allows you to detect if the current (individual) article is featured and/or has a particular label. You may also use this outside an article context by supplying a list of article IDs.</p>\n\n<p>Has two attributes:</p>\n\n<ul>\n	<li><span class=\"atnm\">id</span> : list of article IDs of which you wish to check the featured status. If omitted, defaults to the current article (if used inside a <code>&lt;txp:article /&gt;</code> container/form).</li>\n	<li><span class=\"atnm\">label</span> : list of labels to compare the given ID(s) with. Specify <code>label=&quot;&quot;</code> to check for unlabelled articles, or use an empty item if specifying a list, e.g. <code>label=&quot;, Featured, Teaser&quot;</code>. Remember that labels are case sensitive.</li>\n</ul>\n\n<p>If the article matches one of the given labels the contained content will be executed. If no label is given the article(s) will be checked if they are featured, irrespective of label.</p>\n\n<h2>Examples</h2>\n\n<h3 id=\"eg1\">Example 1: Complete directory of featured articles</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_featured labeltag=&quot;h2&quot; limit=&quot;20&quot;\n     wraptag=&quot;dl&quot; sort=&quot;label&quot;&gt;\n   &lt;txp:if_different&gt;\n      &lt;h2&gt;&lt;txp:smd_featured_info item=&quot;label&quot; /&gt;&lt;/h2&gt;\n   &lt;/txp:if_different&gt;\n   &lt;dt&gt;\n      &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;/dt&gt;\n   &lt;dd&gt;\n      &lt;txp:excerpt /&gt;\n   &lt;/dd&gt;\n&lt;/txp:smd_featured&gt;</code></pre>\n\n<h3 id=\"eg2\">Example 2: One featured article and six teasers on the home page</h3>\n\n<p>Select an article and label it <code>feature</code>. Select six others and label them <code>teasers</code>. Add some description text to your teaser articles.</p>\n\n<p>In your default Page, put this:</p>\n\n<pre class=\"block\"><code>&lt;txp:smd_featured label=&quot;feature&quot;\n     wraptag=&quot;div&quot; html_id=&quot;main_feature&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;txp:article_image class=&quot;feat_image&quot; /&gt;\n   &lt;p class=&quot;leadin&quot;&gt;&lt;txp:excerpt /&gt;&lt;/p&gt;\n&lt;/txp:smd_featured&gt;</code></pre>\n\n<pre class=\"block\"><code>&lt;txp:smd_featured label=&quot;teasers&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot; html_id=&quot;teaser_block&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;p class=&quot;leadin&quot;&gt;&lt;txp:excerpt /&gt;&lt;/p&gt;\n&lt;/txp:smd_featured&gt;</code></pre>\n\n<p>If you wanted to have some other articles beneath those, but didn&#8217;t want any duplicates to appear you could use:</p>\n\n<pre class=\"block\"><code>&lt;txp:smd_unfeatured form=&quot;my_list&quot; /&gt;</code></pre>\n\n<h3 id=\"eg3\">Example 3: Per-section landing page featured articles</h3>\n\n<p>If you had a zoo site with a section for primates, a section for mammals and a section for reptiles, when you set up your featured article list define their labels like this:</p>\n\n<ul>\n	<li>Featured primates</li>\n	<li>Featured mammals</li>\n	<li>Featured reptiles</li>\n</ul>\n\n<p>On your section landing page template you can then do this to display four articles from the relevant (current) section:</p>\n\n<pre class=\"block\"><code>&lt;h2&gt;Look who&#39;s here:&lt;/h2&gt;\n&lt;txp:smd_featured label=&#39;Featured &lt;txp:section /&gt;&#39;\n     section=&#39;&lt;txp:section /&gt;&#39; limit=&quot;4&quot;\n     wraptag=&quot;div&quot; html_id=&quot;feat_block&quot;&gt;\n   &lt;div class=&quot;pimped_article&quot;&gt;\n      &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n      &lt;txp:smd_featured_info item=&quot;description&quot; /&gt;\n   &lt;/div&gt;\n&lt;/txp:smd_featured&gt;</code></pre>\n\n<h3 id=\"eg4\">Example 4: Conditional check for featured-ness</h3>\n\n<p>If a site visitor has navigated away from your front page and is viewing an article, you may wish to highlight the fact the current article is a featured product. So, somewhere inside you article form add:</p>\n\n<pre class=\"block\"><code>&lt;txp:smd_if_featured&gt;\n   &lt;p class=&quot;hilight&quot;&gt;FEATURED PRODUCT&lt;/p&gt;\n&lt;/txp:smd_if_featured&gt;</code></pre>\n\n<p>Or if you wanted to only highlight the article if it was a featured or on special offer:</p>\n\n<pre class=\"block\"><code>&lt;txp:smd_if_featured label=&quot;Featured, Special&quot;&gt;\n   &lt;p class=&quot;hilight&quot;&gt;As featured on the front page!&lt;/p&gt;\n&lt;/txp:smd_if_featured&gt;</code></pre>\n\n<h2>Author / Credits</h2>\n\n<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a></p>', '/**\n * smd_featured\n *\n * A Textpattern CMS plugin for pimping your articles on landing pages.\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (txpinterface === \'admin\') {\n    global $smd_featured_event, $smd_featured_pref_privs;\n\n    $smd_featured_event = \'smd_featured\';\n    $smd_featured_privs = get_pref(\'smd_featured_privs\', \'1, 2\');\n    $smd_featured_pref_privs = array(\n        \'all\' => array(\n            \'smd_featured_display\',\n            \'smd_featured_box_size\',\n            \'smd_featured_sort\',\n        ),\n        \'1\' => array(\n            \'smd_featured_section_list\',\n            \'smd_featured_textile\',\n            \'smd_featured_show_ui\',\n        ),\n    );\n\n    add_privs($smd_featured_event, $smd_featured_privs);\n    register_tab(\"content\", $smd_featured_event, gTxt(\'smd_feat_tab_name\'));\n    register_callback(\'smd_featured_manage\', $smd_featured_event);\n    register_callback(\'smd_featured_welcome\', \'plugin_lifecycle.smd_featured\');\n    register_callback(\'smd_featured_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_featured\')\n            ->register(\'smd_featured_info\')\n            ->register(\'smd_unfeatured\')\n            ->register(\'smd_if_featured\');\n    }\n}\n\nif (!defined(\'SMD_FEAT\')) {\n    define(\"SMD_FEAT\", \'smd_featured\');\n}\n\n// **********\n// ADMIN SIDE\n// **********\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers\nfunction smd_featured_get_style_rules()\n{\n    $sizes = do_list(get_pref(\'smd_featured_box_size\', \'150x40\', 1), \'x\');\n    $sizes[0] = (isset($sizes[0]) && is_numeric($sizes[0])) ? $sizes[0] : \'150\';\n    $sizes[1] = (isset($sizes[1]) && is_numeric($sizes[1])) ? $sizes[1] : \'40\';\n    $stylekeys = array(\'{bw}\', \'{bh}\');\n    $stylevals = array($sizes[0], $sizes[1]);\n\n    $smd_featured_styles = array(\n        \'common\' => \'\n#smd_all h3 { margin:15px 0; }\n#smd_featured_cpanel select, #smd_featured_cpanel input[type=\"text\"] { margin-bottom:10px; }\n#smd_featured_cpanel input[type=\"text\"] { padding:3px; }\n#control input, #control select { margin:0; }\n.smd_hidden { display:none; }\n.smd_featured { background-color:#e2dfce; position:relative; }\n.smd_featured a { font-weight: bold; color:#80551e; }\n#smd_featured_cpanel form { border:1px solid #ccc; margin: 0.5rem 0; padding: 1rem; }\n.smd_featured_subpanel h4 { margin: 0 0 0.5rem }\n.smd_featured_table { margin:0 auto; text-align:center; }\n.smd_clear { clear:both; }\n.smd_featured_table div { display:inline-block; width:{bw}px; height:{bh}px; border:1px solid #aaa; padding:0.4em; overflow:hidden; }\n.smd_feat_edart { position:absolute; right:5px; bottom:3px; }\n.smd_feat_edpos { position:absolute; left:2px; bottom:2px; font-size:75%; width:7em;}\n#smd_feat_saveform { margin:1em; }\n#smd_feat_loading { color:#80551e; text-align:center; font-size:200%; font-style:italic; width:500px; height:100px; margin:0 auto; border:1px solid #777; background:#e2dfce;display:block }\n\',\n    );\n\n    return str_replace($stylekeys, $stylevals, $smd_featured_styles);\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_inject_css($evt, $stp)\n{\n    global $smd_featured_event, $event;\n\n    if ($event === $smd_featured_event) {\n        $smd_featured_styles = smd_featured_get_style_rules();\n        echo \'<style type=\"text/css\">\', $smd_featured_styles[\'common\'], \'</style>\';\n    }\n\n    return;\n}\n// ------------------------\nfunction smd_featured_manage($evt, $stp)\n{\n    if ($stp == \'save_pane_state\') {\n        smd_featured_save_pane_state();\n    } else {\n        if(!$stp or !in_array($stp, array(\n                \'smd_featured_table_install\',\n                \'smd_featured_table_remove\',\n                \'smd_featured_prefsave\',\n                \'smd_featured_save\',\n                \'smd_featured_store_pos\',\n                \'smd_featured_tagit\',\n                \'smd_featured_change_pageby\',\n            ))) {\n            smd_featured_list(\'\');\n        } else $stp();\n    }\n}\n\n// ------------------------\nfunction smd_featured_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_featured_table_install(0);\n            // Remove some of the per-user prefs on upgrade from v0.3x to v0.40\n            safe_delete (\'txp_prefs\', \"name IN (\'smd_featured_textile\') AND user_name != \'\'\");\n            $msg = \'Thanks for installing smd_featured. Please read the docs :-)\';\n            break;\n        case \'deleted\':\n            smd_featured_table_remove(0);\n            break;\n    }\n    return $msg;\n}\n\n// ------------------------\nfunction smd_featured_list($msg = \'\')\n{\n    global $smd_featured_event, $smd_featured_list_pageby, $txp_user, $smd_featured_pref_privs;\n\n    pagetop(gTxt(\'smd_feat_tab_name\'), $msg);\n    extract(gpsa(array(\'smd_feat_id\', \'smd_feat_label\', \'smd_feat_searchkeep\', \'smd_feat_filtkeep\', \'page\')));\n\n    if (smd_featured_table_exist(1)) {\n        smd_featured_table_upgrade();\n        $featlist = safe_rows(\'*\', SMD_FEAT, \'1=1\');\n        $featlist = empty($featlist) ? array() : $featlist;\n        $editname = $feat_label = $feat_title = $feat_desc = \'\';\n\n        $etypes = $ftypes = $flist = array();\n        $etypes[\'\'] = gTxt(\'smd_feat_unlabelled\');\n        $ftypes[\'smd_unlabelled\'] = gTxt(\'smd_feat_unlabelled\');\n        foreach ($featlist as $item) {\n            if (isset($item[\'label\']) && !empty($item[\'label\'])) {\n                $ftypes[$item[\'label\']] = $etypes[$item[\'label\']] = $item[\'label\'];\n            }\n            $flist[$item[\'feat_id\']] = array(\n                \'label\' => $item[\'label\'],\n                \'position\' => $item[\'feat_position\'],\n                \'title\' => $item[\'feat_title\'],\n                \'title_html\' => $item[\'feat_title_html\'],\n                \'description\' => $item[\'description\'],\n                \'desc_html\' => $item[\'desc_html\']\n            );\n        }\n\n        $featlist = $flist;\n\n        $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n        $rights = array_key_exists($privs, $smd_featured_pref_privs);\n\n        // Get additional filtering from hidden prefs\n        $where = array(\'1=1\');\n        $seclist = get_pref(\'smd_featured_section_list\', \'\', 1);\n        if (!$seclist) {\n            $seclist = gps(\'section_list\');\n        }\n        if ($seclist) {\n            $where[] = \"Section IN (\'\".join(\"\',\'\", do_list($seclist)).\"\')\";\n        }\n        $where[] = \"Status IN (4,5)\";\n\n        $orderby = get_pref(\'smd_featured_sort\', \'Posted desc\', 1);\n        $orderqry = ($orderby) ? \' ORDER BY \'.doSlash($orderby) : \'\';\n        $currOrder = explode(\' \', $orderby);\n        $currOrder[0] = (isset($currOrder[0])) ? $currOrder[0] : \'Posted\';\n        $currOrder[1] = (isset($currOrder[1])) ? $currOrder[1] : \'desc\';\n        $display = get_pref(\'smd_featured_display\', \'all\', 1);\n\n        // Generate the extra criteria if in list view\n        if ($display === \'paginated\') {\n            if ($smd_feat_searchkeep) {\n                $items = do_list($smd_feat_searchkeep, \' \');\n                $itlist = array();\n                foreach ($items as $item) {\n                    if (trim($item) != \'\') {\n                        $itlist[] = \"Title like \'%$item%\'\";\n                    }\n                }\n                $where[] = \'(\'.join(\' OR \', $itlist).\')\';\n            }\n            if ($smd_feat_filtkeep) {\n                $lbl = ($smd_feat_filtkeep == \'smd_unlabelled\') ? \'\' : $smd_feat_filtkeep;\n                $ids = safe_column(\'feat_id\', SMD_FEAT, \"BINARY label = \'$lbl\'\");\n                $where[] = \"ID IN (\'\".join(\"\',\'\", $ids).\"\')\";\n            }\n        }\n\n        $where = join(\' AND \', $where);\n        $total = safe_count(\'textpattern\', $where);\n        $do_pag = !$smd_feat_filtkeep && !$smd_feat_searchkeep;\n\n        $limit = ($display==\'paginated\' && $do_pag) ? max($smd_featured_list_pageby, 15) : 99999;\n        list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n        $rs = safe_rows(\'*\', \'textpattern\', $where. $orderqry . \" limit $offset, $limit\");\n        $out = array();\n\n        foreach ($rs as $row) {\n            $ftype = isset($featlist[$row[\'ID\']][\'label\']) ? $featlist[$row[\'ID\']][\'label\'] : \'\';\n            $out[] = array($row[\'ID\'], $row[\'Title\'], $ftype);\n            if ($smd_feat_id && $row[\'ID\'] == $smd_feat_id) {\n                $editname = $row[\'Title\'];\n                $feat_label = $featlist[$smd_feat_id][\'label\'];\n                $feat_title = $featlist[$smd_feat_id][\'title\'];\n                $feat_position = $featlist[$smd_feat_id][\'position\'];\n                $feat_desc = doStrip(str_replace(\'\\r\\n\',\'\n\', $featlist[$smd_feat_id][\'description\'])); // Hackish newline kludge\n            }\n            // Add the position to the most recent array entry\n            if (isset($featlist[$row[\'ID\']][\'position\'])) {\n                $out[count($out)-1][] = $featlist[$row[\'ID\']][\'position\'];\n            }\n        }\n\n        //TODO: i18n\n        $sortopts = array(\n            \'Posted\' => \'Posted\',\n            \'Expires\' => \'Expiry\',\n            \'LastMod\' => \'Modified\',\n            \'Title\' => \'Title\',\n            \'Section\' => \'Section\',\n            \'Category1\' => \'Category1\',\n            \'Category2\' => \'Category2\',\n        );\n        $sortdirs = array(\n            \'asc\' => \'Ascending\',\n            \'desc\' => \'Descending\',\n        );\n        $displayopts = array(\n            \'all\' => \'All\',\n            \'paginated\' => \'Paginated\',\n        );\n        $textileonoff = explode(\',\', get_pref(\'smd_featured_textile\', \'title,desc\', 1));\n        $showonoff = explode(\',\', get_pref(\'smd_featured_show_ui\', \'label,title,desc\', 1));\n        $txt_ttl = in_array(\'title\', $textileonoff);\n        $txt_desc = in_array(\'desc\', $textileonoff);\n        $show_lbl = in_array(\'label\', $showonoff);\n        $show_ttl = in_array(\'title\', $showonoff);\n        $show_desc = in_array(\'desc\', $showonoff);\n        $use_edit = ($show_lbl || $show_ttl || $show_desc) ? \'1\' : \'0\';\n\n        $sizes = do_list(get_pref(\'smd_featured_box_size\', \'150x40\', 1), \'x\');\n        $sizes[0] = (isset($sizes[0]) && is_numeric($sizes[0])) ? $sizes[0] : \'150\';\n        $sizes[1] = (isset($sizes[1]) && is_numeric($sizes[1])) ? $sizes[1] : \'40\';\n        $display_js = ($display==\'all\') ? 1 : 0;\n\n        echo n.\'<div id=\"smd_feat_loading\"><div id=\"smd_feat_loading_holder\">Loading...</div></div>\';\n        echo n.\'<div id=\"smd_container\" class=\"txp-container\" style=\"display:none;\">\';\n        echo n.\'<div id=\"smd_featured_control_panel\"><span class=\"txp-summary lever\'.(get_pref(\'pane_smd_featured_cpanel_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_featured_cpanel\">\'.gTxt(\'smd_feat_control_panel\').\'</a></span>\';\n        echo n.\'<div id=\"smd_featured_cpanel\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_featured_cpanel_visible\') ? \'block\' : \'none\').\'\">\';\n\n        echo n.\'<form id=\"smd_feat_prefs\" action=\"index.php\" method=\"post\">\';\n        echo n.\'<div id=\"smd_featured_prefs\" class=\"smd_featured_subpanel\"><h4>\'.gTxt(\'smd_feat_prefs\').\'</h4>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<label for=\"smd_feat_display\">\'.gTxt(\'smd_feat_pref_display\').\'</label>\'.\n            n.selectInput(\'smd_feat_display\', $displayopts, $display, \'\', \'\', \'smd_feat_display\');\n        echo n.\'</div>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<label for=\"smd_feat_sort\">\'.gTxt(\'smd_feat_pref_sort\').\'</label>\'.\n            n.selectInput(\'smd_feat_sort\', $sortopts, $currOrder[0], \'\', \'\', \'smd_feat_sort\').\n            n.selectInput(\'smd_feat_sortdir\', $sortdirs, $currOrder[1], \'\', \'\', \'smd_feat_sortdir\');\n        echo n.\'</div>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<div id=\"smd_feat_display_box\">\';\n        echo n.\'<label for=\"smd_feat_boxsize\">\'.gTxt(\'smd_feat_pref_boxsize\').\'</label>\'.\n            n.fInput(\'text\', \'smd_feat_box_x\', $sizes[0], \'\', \'\', \'\', \'3\', \'\', \'smd_feat_boxsize\').n.\n            gTxt(\'smd_feat_pref_boxsizeby\').n.fInput(\'text\', \'smd_feat_box_y\', $sizes[1], \'\', \'\', \'\', \'3\');\n        echo n.\'</div>\';\n\n        if ($rights) {\n            echo n.\'<div class=\"smd_featured_pref smd_feat_show_ui\">\';\n            echo n.\'<label for=\"smd_feat_show_ui\">\'.gTxt(\'smd_feat_show_ui\').\'</label>\';\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'label\', $show_lbl, \'\', \'smd_feat_show_label\').sp.gTxt(\'smd_feat_label\');\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'title\', $show_ttl, \'\', \'smd_feat_show_title\').sp.gTxt(\'smd_feat_title\');\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'desc\', $show_desc, \'\', \'smd_feat_show_desc\').sp.gTxt(\'smd_feat_desc\');\n            echo n.\'</div>\';\n\n            echo n.\'<div class=\"smd_featured_pref smd_feat_textile\">\';\n            echo n.\'<label for=\"smd_feat_textile\">\'.gTxt(\'smd_feat_textile\').\'</label>\';\n            echo n.checkbox(\'smd_feat_textile[]\', \'title\', $txt_ttl, \'\', \'smd_feat_textile_title\').sp.gTxt(\'smd_feat_title\');\n            echo n.checkbox(\'smd_feat_textile[]\', \'desc\', $txt_desc, \'\', \'smd_feat_textile_desc\').sp.gTxt(\'smd_feat_desc\');\n            echo n.\'</div>\';\n\n            echo n.\'<div class=\"smd_featured_pref smd_feat_section_list\">\';\n            echo n.\'<label for=\"smd_feat_section_list\">\'.gTxt(\'smd_feat_section_list\').\'</label>\';\n            echo n.fInput(\'text\', \'smd_feat_section_list\', $seclist, \'\', \'\', \'\', \'30\', \'\', \'smd_feat_section_list\');\n            echo n.\'</div>\';\n        }\n\n        echo n.\'</div>\';\n\n        echo n.eInput($smd_featured_event).sInput(\'smd_featured_prefsave\');\n        echo n.fInput(\'submit\', \'\', gTxt(\'save\'), \'smd_featured_save\');\n        echo n.\'</div>\';\n        echo n.\'</form>\';\n\n        echo n.\'<form id=\"smd_feat_filtform\" action=\"index.php\" method=\"post\" onsubmit=\"smd_featured_editkeep(0);return false;\">\';\n        echo n.\'<div id=\"smd_featured_filt\" class=\"smd_featured_subpanel\"><h4>\'.gTxt((($display==\'all\') ? \'smd_feat_search_live\' : \'smd_feat_search_standard\')).\'</h4>\';\n        echo n.\'<label for=\"smd_feat_search\">\'.gTxt(\'smd_feat_by_name\').\'</label>\'.n.fInput(\'text\', \'smd_feat_search\', $smd_feat_searchkeep, \'\', \'\', \'\', \'\', \'\', \'smd_feat_search\').\n            (($ftypes) ? n.\'<div id=\"smd_featured_bylabel\">\'.gTxt(\'smd_feat_by_label\').n.selectInput(\'smd_feat_filt\', $ftypes, $smd_feat_filtkeep, 1, \'\', \'smd_feat_filt\').\'</div>\' : \'\');\n        echo ($display==\'paginated\') ? n.fInput(\'submit\', \'\', gTxt(\'go\'), \'smd_featured_save\', \'\', \'return smd_featured_editkeep(0);\') : \'\';\n        echo n.eInput($smd_featured_event).n.sInput(\'smd_featured_list\');\n        echo n.\'</div>\';\n        echo n.\'</form>\';\n\n        echo n.\'</div>\';\n        echo n.\'</div>\';\n\n        echo n.\'<form id=\"smd_feat_editform\" action=\"index.php\" method=\"post\">\';\n        echo n.hInput(\'smd_feat_searchkeep\', \'\').\n                n.hInput(\'smd_feat_filtkeep\', \'\').\n                n.hInput(\'smd_feat_id\', \'\').\n                n.eInput($smd_featured_event).\n                n.sInput(\'smd_featured_list\');\n        echo n.\'</form>\';\n\n        echo n.\'<form id=\"smd_feat_saveform\" action=\"index.php\" method=\"post\" onsubmit=\"return smd_featured_savekeep();\">\';\n        echo n.startTable();\n        if ($smd_feat_id) {\n            echo n.tr(\n                n.td(\'&nbsp;\').tdcs(gTxt(\'edit\').sp.strong(eLink(\'article\', \'edit\', \'ID\', $smd_feat_id, $editname)), 2).\n                n.td(fInput(\'submit\', \'\', gTxt(\'save\'))).\n                n.hInput(\'smd_feat_searchkeep\', \'\').\n                n.hInput(\'smd_feat_filtkeep\', \'\').\n                n.hInput(\'smd_feat_id\', $smd_feat_id).\n                n.eInput($smd_featured_event).\n                n.sInput(\'smd_featured_save\')\n            );\n            if ($show_lbl) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'label\'), \'\', \'smd_feat_label\').\n                    n.td(\n                        n.fInput(\'text\', \'smd_feat_label\', $feat_label, \'\', \'\', \'\', \'\', \'\', \'smd_feat_label\').\n                        n.selectInput(\'smd_feat_labelchoose\', $etypes, $feat_label, 0, \'\', \'smd_feat_labelchoose\')\n                    )\n                );\n            }\n            if ($show_ttl) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'title\'), \'\', \'smd_feat_title\').\n                    n.td(\n                        n.fInput(\'text\', \'smd_feat_title\', $feat_title, \'\', \'\', \'\', \'\', \'\', \'smd_feat_title\')\n                    )\n                );\n            }\n            if ($show_desc) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'description\'), \'\', \'smd_feat_desc\').\n                    n.td(text_area(\'smd_feat_desc\', 80, 400, $feat_desc, \'smd_feat_desc\'))\n                );\n            }\n            echo n.tr(\n                n.fLabelCell(gTxt(\'smd_feat_position\'), \'\', \'smd_feat_position\').\n                n.td(fInput(\'text\', \'smd_feat_position\', $feat_position, \'\', \'\', \'\', \'\', \'\', \'smd_feat_position\'))\n            );\n        }\n        echo n.endTable();\n        echo n.\'</form>\';\n\n        $edbtn = small(\'[\'.gTxt(\'edit\').\']\');\n        $edtip = gTxt(\'smd_feat_lbl_edfeat\');\n        $edartbtn = \'&rarr;\';\n        $edarttip = gTxt(\'smd_feat_lbl_edart\');\n        $edpostip = gTxt(\'smd_feat_lbl_edpos\');\n        $unfeattip = gTxt(\'smd_feat_lbl_unfeature\');\n\n        if ($out) {\n            $rows = count($rs);\n            $tblout = array();\n            $atts = \' class=\"smd_featured_table smd_clear\" id=\"smd_\'.$display.\'\"\';\n\n            $tblout[] = hed(gTxt(\'smd_feat_manage_lbl\', array(), \'raw\'), 3, \' class=\"smd_clear\"\');\n            for ($idx = 0; $idx < $rows; $idx++) {\n                $isfeat = (isset($out[$idx]) && array_key_exists($out[$idx][0], $featlist));\n                $cellclass = $isfeat ? \' class=\"smd_featured\"\' : \'\';\n                $edlink = $isfeat && $use_edit ? \'<a class=\"smd_feat_edlink\" href=\"#\" onclick=\"return smd_featured_editkeep(\\\'\'.$out[$idx][0].\'\\\');\" title=\"\'.$edtip.\'\">\'.$edbtn.\'</a>\'.sp : \'\';\n                $edpos = $isfeat ? \'<input name=\"smd_featured_position\" class=\"smd_feat_edpos\" title=\"\'.$edpostip.\'\" value=\"\'.$out[$idx][3].\'\" onblur=\"return smd_featured_store_pos(this, \\\'\'.$out[$idx][0].\'\\\')\" />\' : \'\';\n                $edart = $isfeat ? \'<a class=\"smd_feat_edart\" href=\"?event=article&step=edit&ID=\'.$out[$idx][0].\'\" title=\"\'.$edarttip.\'\">\'.$edartbtn.\'</a>\' : \'\';\n                $rowdata = (isset($out[$idx])) ? \'<span name=\"smd_feat_name\" class=\"smd_hidden\">\'.$out[$idx][1].\'</span>\' : \'\';\n                $rowdata .= $isfeat ? \'<span name=\"smd_feat_label\" class=\"extra smd_hidden\">\'.$out[$idx][2].\'</span>\' : \'\';\n                $tblout[] = (isset($out[$idx])) ? \'<div\'.$cellclass.\'>\'.$edlink.\'<a href=\"#\" title=\"\'.(($isfeat) ? $unfeattip : \'\').\'\" onclick=\"return smd_featured_select(this, \\\'\'.$out[$idx][0].\'\\\')\">\'.$out[$idx][1].\'</a>\'.$edpos.$edart.$rowdata.\'</div>\' : \'<div></div>\';\n            }\n            echo tag(join(\"\",$tblout), \'div\', $atts);\n            echo \'<div class=\"smd_clear\"></div>\';\n            if ($display==\'paginated\' && $do_pag) {\n                echo n.\'<div id=\"\'.$smd_featured_event.\'_navigation\" class=\"txp-navigation\">\'.\n                    n.nav_form($smd_featured_event, $page, $numPages, \'\', \'\', \'\', \'\', $total, $limit).\n                    n.pageby_form($smd_featured_event, $smd_featured_list_pageby);\n                    n.\'</div>\';\n            }\n        }\n\n        $qs = array(\n            \"event\" => $smd_featured_event,\n        );\n\n        $qsVars = \"index.php\".join_qs($qs);\n        $verifyTxt = gTxt(\'smd_feat_unfeature_confirm\');\n\n        echo <<<EOJS\n<script type=\"text/javascript\">\nfunction smd_featured_select(obj, id) {\n    obj = jQuery(obj).parent();\n\n    // N.B. Negative logic used here because we\'re checking the class _before_ it\'s been toggled\n    var action = ((obj).hasClass(\"smd_featured\")) ? \'remove\' : \'add\';\n    if (action == \'remove\') {\n        if (\'1\' == {$use_edit}) {\n            var ret = confirm(\'{$verifyTxt}\');\n            if (ret == false) {\n                return false;\n            }\n        }\n    }\n    jQuery.post(\'{$qsVars}\', { step: \"smd_featured_tagit\", smd_feat_id: id, smd_feat_action: action },\n        function(data) {\n            obj.toggleClass(\'smd_featured\');\n            if (action == \'add\') {\n                obj.find(\'a\').attr(\'title\', \"{$unfeattip}\");\n                if (\'1\' == \'{$use_edit}\') {\n                    obj.prepend(\'<a class=\"smd_feat_edlink\" title=\"{$edtip}\" href=\"#\" onclick=\"return smd_featured_editkeep(\\\'\'+id+\'\\\')\">{$edbtn}</a>&nbsp;\');\n                }\n                obj.append(\'<input name=\"smd_featured_position\" class=\"smd_feat_edpos\" title=\"{$edpostip}\" value=\"\" onblur=\"return smd_featured_store_pos(this, \\\'\'+id+\'\\\')\" />\');\n                obj.append(\'<a class=\"smd_feat_edart\" title=\"{$edarttip}\" href=\"?event=article&step=edit&ID=\'+id+\'\">{$edartbtn}</a>\');\n                obj.append(\'<span name=\"smd_feat_label\" class=\"extra smd_hidden\"></span>\');\n                obj.find(\'input.smd_feat_edpos\').focus();\n            } else {\n                obj.find(\'a.smd_feat_edlink\').remove();\n                obj.find(\'input.smd_feat_edpos\').remove();\n                obj.find(\'a.smd_feat_edart\').remove();\n                obj.find(\'a\').attr(\'title\', \'\');\n                obj.find(\'span.extra\').remove();\n                if (jQuery(\"#smd_feat_search\").val() != \'\') jQuery(\"#smd_feat_search\").keyup();\n                if (jQuery(\"#smd_feat_filt\").val() != \'\') jQuery(\"#smd_feat_filt\").change();\n            }\n        }\n    );\n    return false;\n}\n\nfunction smd_featured_editkeep(id) {\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_searchkeep\']\").val(jQuery(\"#smd_feat_search\").val());\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_filtkeep\']\").val(jQuery(\"#smd_featured_bylabel #smd_feat_filt option:selected\").val());\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_id\']\").val(id);\n    jQuery(\"#smd_feat_editform\").submit();\n}\n\nfunction smd_featured_savekeep() {\n    jQuery(\"#smd_feat_saveform [name=\'smd_feat_searchkeep\']\").val(jQuery(\"#smd_feat_search\").val());\n    jQuery(\"#smd_feat_saveform [name=\'smd_feat_filtkeep\']\").val(jQuery(\"#smd_featured_bylabel #smd_feat_filt option:selected\").val());\n}\n\nfunction smd_feat_filter(selector, query, nam, csense, exact) {\n    if ({$display_js} == 1) {\n        var query = jQuery.trim(query);\n        csense = (csense) ? \"\" : \"i\";\n        query = query.replace(/ /gi, \'|\'); // add OR for regex query\n        if (exact) {\n            tmp = query.split(\'|\');\n            for (var idx = 0; idx < tmp.length; idx++) {\n                tmp[idx] = \'^\'+tmp[idx]+\'$\';\n            }\n            query = tmp.join(\'|\');\n        }\n        var re = new RegExp(query, csense);\n        jQuery(selector).each(function() {\n            sel = (typeof nam==\"undefined\" || nam==\'\') ? jQuery(this) : jQuery(this).find(\"span[name=\'\"+nam+\"\']\");\n            if (query == \'\') {\n                if (sel.length == 1 && sel.text() == \'\') {\n                    jQuery(this).show();\n                } else {\n                    jQuery(this).hide();\n                }\n            } else {\n                if (sel.text().search(re) < 0) {\n                    jQuery(this).hide();\n                } else {\n                    jQuery(this).show();\n                }\n            }\n        });\n    }\n}\n\nfunction smd_featured_store_pos(obj, id) {\n    var obj = jQuery(obj);\n    var pos = obj.val();\n\n    //TODO: feedback on success\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_featured_store_pos\',\n        smd_feat_id: id,\n        smd_feat_pos: pos\n    });\n}\n\njQuery(function() {\n    jQuery(\"#smd_feat_search\").keyup(function(event) {\n        jQuery(\"#smd_feat_filt\").val(\'0\'); // Clear the filter dropdown\n        // if esc is pressed or nothing is entered\n        if (event.keyCode == 27 || jQuery(this).val() == \'\') {\n            jQuery(this).val(\'\');\n            if ({$display_js} == 1) {\n                jQuery(\".smd_featured_table div:not(.tblhead)\").show();\n            }\n        } else {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', jQuery(this).val(), \'smd_feat_name\', 0, 0);\n        }\n    });\n    if (\'{$smd_feat_searchkeep}\' != \'\') {\n        jQuery(\"#smd_feat_search\").keyup();\n    }\n    jQuery(\"#smd_feat_filt\").change(function(event) {\n        jQuery(\"#smd_feat_search\").val(\'\'); // Empty the search box\n        if (jQuery(this).val() == \'\') {\n            if ({$display_js} == 1) {\n                jQuery(\'.smd_featured_table div:not(.tblhead)\').show();\n            }\n        } else if (jQuery(this).val() == \'smd_unlabelled\') {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', \'\', \'smd_feat_label\', 0, 0);\n        } else {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', jQuery(this).val(), \'smd_feat_label\', 1, 1);\n        }\n    });\n    if (\'{$smd_feat_filtkeep}\' != \'\') {\n        jQuery(\"#smd_feat_filt\").change();\n    }\n    if (\'{$smd_feat_id}\' != \'\') {\n        jQuery(\"#smd_feat_label\").focus();\n        jQuery(\"#smd_feat_labelchoose\").change(function(event) {\n            jQuery(\"#smd_feat_label\").val(jQuery(this).val());\n        });\n    }\n    jQuery(\"#smd_feat_loading\").hide();\n    jQuery(\"#smd_container\").show();\n});\n\n</script>\nEOJS;\n\n        echo \'</div>\';\n    } else {\n        // Table not installed\n        $btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_featured_event.a.\'step=smd_featured_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', gTxt(\'smd_feat_tbl_install_lbl\')).\'</form>\';\n        $btnStyle = \' style=\"border:0;height:25px\"\';\n        echo startTable();\n        echo tr(tda(strong(gTxt(\'smd_feat_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_feat_prefs_some_explain\').br.br\n                .gTxt(\'smd_feat_prefs_some_opts\'), \' colspan=\"2\"\')\n        );\n        echo tr(tda($btnInstall, $btnStyle));\n        echo endTable();\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_change_pageby()\n{\n    global $smd_featured_event;\n\n    event_change_pageby($smd_featured_event);\n    smd_featured_list();\n}\n\n// ------------------------\n// Update the passed record in the featured table\nfunction smd_featured_save()\n{\n    global $smd_featured_event;\n\n    extract(gpsa(array(\'smd_feat_id\', \'smd_feat_label\', \'smd_feat_title\', \'smd_feat_desc\', \'smd_feat_position\')));\n    assert_int($smd_feat_id);\n\n    $smd_feat_titletile = $smd_feat_title;\n    $smd_feat_desctile = $smd_feat_desc;\n    $smd_feat_label = doSlash($smd_feat_label);\n    $smd_feat_position = doSlash($smd_feat_position);\n\n    if (smd_featured_table_exist()) {\n        @include_once txpath.\'/lib/classTextile.php\';\n        @include_once txpath.\'/publish.php\';\n\n        $textileonoff = explode(\',\', get_pref(\'smd_featured_textile\', \'\'));\n        $txt_ttl = in_array(\'title\', $textileonoff);\n        $txt_desc = in_array(\'desc\', $textileonoff);\n\n        if (class_exists(\'Textile\')) {\n            $textile = new Textile();\n            $smd_feat_titletile = doSlash((($txt_ttl) ? $textile->TextileThis(parse($smd_feat_title)) : parse($smd_feat_title)));\n            $smd_feat_desctile = doSlash((($txt_desc) ? $textile->TextileThis(parse($smd_feat_desc)) : parse($smd_feat_desc)));\n        }\n\n        $smd_feat_title = doSlash($smd_feat_title);\n        $smd_feat_desc = doSlash($smd_feat_desc);\n        $ret = safe_upsert(SMD_FEAT, \"label=\'$smd_feat_label\', feat_position=\'$smd_feat_position\', feat_title=\'$smd_feat_title\', feat_title_html=\'$smd_feat_titletile\', description=\'$smd_feat_desc\', desc_html=\'$smd_feat_desctile\'\", \"feat_id=\'$smd_feat_id\'\");\n        unset($_POST[\'smd_feat_id\']);\n    }\n\n    smd_featured_list(gTxt(\'smd_feat_saved\'));\n}\n\n// ------------------------\n// Create an empty entry in the featured table or destroy it\nfunction smd_featured_tagit()\n{\n    global $smd_featured_event;\n    extract(doSlash(gpsa(array(\'smd_feat_id\', \'smd_feat_action\'))));\n\n    assert_int($smd_feat_id);\n\n    if ($smd_feat_action == \'add\') {\n        $ret = safe_upsert(SMD_FEAT, \"label=\'\'\", \"feat_id=\'$smd_feat_id\'\");\n    } else if ($smd_feat_action == \'remove\') {\n        $ret = safe_delete(SMD_FEAT, \"feat_id=\'$smd_feat_id\'\");\n    }\n}\n\n// -------------------------------------------------------------\n// Stash the position against the given featured item\nfunction smd_featured_store_pos()\n{\n    $id = gps(\'smd_feat_id\');\n    assert_int($id);\n\n    $id = doSlash($id);\n    $pos = doSlash(gps(\'smd_feat_pos\'));\n\n    $exists = safe_row(\'*\', SMD_FEAT, \"feat_id=$id\");\n    if ($exists) {\n        safe_update(SMD_FEAT, \"feat_position=\'$pos\'\", \"feat_id=$id\");\n        send_xml_response();\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_prefsave()\n{\n    global $smd_featured_pref_privs, $txp_user;\n\n    // Three different types of pref can be stored: see below for details\n    $stdprefs = array(\n        PREF_GLOBAL => array(\n            \'smd_featured_section_list\' => \'smd_feat_section_list\',\n        ),\n        PREF_PRIVATE => array(\n            \'smd_featured_display\' => \'smd_feat_display\',\n            \'smd_featured_section_list\' => \'smd_feat_section_list\',\n        )\n    );\n    $joinprefs = array(\n        PREF_PRIVATE => array(\n            // Index is the pref name; First item in array is the join string\n            \'smd_featured_box_size\' => array(\'x\', \'smd_feat_box_x\', \'smd_feat_box_y\'),\n            \'smd_featured_sort\' => array(\' \', \'smd_feat_sort\', \'smd_feat_sortdir\'),\n        )\n    );\n    $arrayprefs = array(\n        PREF_GLOBAL => array(\n            \'smd_featured_textile\' => \'smd_feat_textile\',\n            \'smd_featured_show_ui\' => \'smd_feat_show_ui\',\n        )\n    );\n\n    $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n    $rights = array_key_exists($privs, $smd_featured_pref_privs);\n    $perprefs = ($rights) ? $smd_featured_pref_privs[$privs] : array();\n    $preflist = array_merge($smd_featured_pref_privs[\'all\'], $perprefs);\n\n    // Standard prefs are just single widget values that are stored directly\n    foreach ($stdprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                set_pref(doSlash($key), doSlash(gps($val)), \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n\n    // Join prefs are discrete widget values (with different HTML names) that need combining into a single pref value\n    foreach ($joinprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                $joinstr = \'\';\n                $combined = array();\n                foreach ($val as $idx => $item) {\n                    if ($idx==0) {\n                        $joinstr = $item;\n                    } else {\n                        $combined[] = doSlash(gps($item));\n                    }\n                }\n                set_pref(doSlash($key), join($joinstr, $combined), \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n\n    // Array prefs are widget values from combined checkboxes under the same HTML name.\n    // They may not be presented as arrays if only one item is checked\n    foreach ($arrayprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                $joinstr = \',\';\n                $val = doSlash(gps($val));\n                $combined = join($joinstr, ((is_array($val)) ? $val : array($val)));\n                set_pref(doSlash($key), $combined, \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n    smd_featured_list(gTxt(\'preferences_saved\'));\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_save_pane_state()\n{\n    $panes = array(\'smd_featured_cpanel\');\n    $pane = gps(\'pane\');\n    if (in_array($pane, $panes))\n    {\n        set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_featured\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n// ------------------------\n// Add featured table if not already installed\nfunction smd_featured_table_install($showpane = \'1\')\n{\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_FEAT.\"` (\n        `feat_id`         int(8)       NOT NULL default 0,\n        `label`           varchar(32)  NULL     default \'\',\n        `feat_position`   varchar(16)  NULL     default \'\',\n        `feat_title`      varchar(255) NULL     default \'\',\n        `feat_title_html` varchar(255) NULL     default \'\',\n        `description`     varchar(255) NULL     default \'\',\n        `desc_html`       varchar(255) NULL     default \'\',\n        PRIMARY KEY (`feat_id`)\n    ) ENGINE=MyISAM\";\n\n    if (gps(\'debug\')) {\n        dmp($sql);\n    }\n\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Spit out results\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        if ($showpane) {\n            $message = gTxt(\'smd_feat_tbl_installed\');\n            smd_featured_list($message);\n        }\n    } else {\n        if ($showpane) {\n            $message = gTxt(\'smd_feat_tbl_not_installed\');\n            smd_featured_list($message);\n        }\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_featured_table_remove()\n{\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    if (smd_featured_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_FEAT. \"; \";\n        if(gps(\'debug\')) {\n            dmp($sql);\n        }\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_feat_tbl_removed\');\n    } else {\n        $message = gTxt(\'smd_feat_tbl_not_removed\');\n        smd_featured_list($message);\n    }\n}\n\n// ------------------------\n// Handle upgrades from previous versions.\nfunction smd_featured_table_upgrade()\n{\n    global $DB;\n\n    $varCharSize = (version_compare($DB->version, \'5.0.3\', \'>=\') ? \'16384\' : \'255\');\n    $colInfo = getRows(\'describe `\'.PFX.SMD_FEAT.\'`\');\n    $cols = array();\n    $descTypes = array();\n\n    foreach ($colInfo as $row) {\n        $cols[] = $row[\'Field\'];\n\n        if (in_array($row[\'Field\'], array(\'description\', \'desc_html\'))) {\n            $descTypes[$row[\'Field\']] = $row[\'Type\'];\n        }\n    }\n\n    if (!in_array(\'feat_id\', $cols)) {\n        safe_alter(SMD_FEAT, \"CHANGE `id` `feat_id` int( 8 ) NOT NULL DEFAULT \'0\'\");\n    }\n    if (!in_array(\'feat_title\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_title` varchar( 255 ) NULL DEFAULT \'\' AFTER `label`\");\n    }\n    if (!in_array(\'feat_title_html\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_title_html` varchar( 255 ) NULL DEFAULT \'\' AFTER `feat_title`\");\n    }\n    if (!in_array(\'feat_position\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_position` varchar ( 16 ) NULL DEFAULT \'\' AFTER `label`\");\n    }\n    if ($descTypes[\'description\'] === \'text\') {\n        safe_alter(SMD_FEAT, \"MODIFY `description` varchar (\" . $varCharSize . \") NULL\");\n    }\n    if ($descTypes[\'desc_html\'] === \'text\') {\n        safe_alter(SMD_FEAT, \"MODIFY `desc_html` varchar (\" . $varCharSize . \") NULL\");\n    }\n}\n\n// ------------------------\nfunction smd_featured_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_FEAT => 7);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n        return ($out === 0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_FEAT.\" HAS \".count(@safe_show(\'columns\', SMD_FEAT)).\" COLUMNS;\";\n        }\n        return (@safe_show(\'columns\', SMD_FEAT));\n    }\n}\n\n// ****************\n// PUBLIC SIDE TAGS\n// ****************\n// ------------------------\nfunction smd_featured($atts, $thing)\n{\n    global $smd_featured_info, $smd_prior_featured, $prefs;\n\n    extract(lAtts(array(\n        \'label\'    => \'\',\n        \'unlabel\'  => \'Featured\',\n        \'labeltag\' => \'\',\n        \'time\'     => \'past\',\n        \'status\'   => \'4,5\',\n        \'section\'  => \'\',\n        \'history\'  => \'1\',\n        \'limit\'    => \'10\',\n        \'sort\'     => \'feat_position asc, Posted desc\',\n        \'form\'     => \'\',\n        \'wraptag\'  => \'\',\n        \'break\'    => \'\',\n        \'class\'    => \'\',\n        \'html_id\'  => \'\',\n        \'debug\'    => \'\',\n    ),$atts));\n\n    $unlabel = trim($unlabel);\n    // Use isset() because unlabelled articles ($label=\"\") is a valid user option\n    if (isset($atts[\'label\'])) {\n        $label = trim($label);\n        if ($label) {\n            $where = \"BINARY label REGEXP \'\".$label.\"\'\";\n        } else {\n            $where = \"label=\'\'\";\n        }\n        unset($atts[\'label\']);\n    } else {\n        // If no label attribute has been given, treat this as \'all featured items\'\n        $ids = safe_column(\'feat_id\', SMD_FEAT, \'1=1\');\n        $ids = join(\"\',\'\", doSlash($ids));\n        $where = \"smdfeat.feat_id IN (\'$ids\')\";\n    }\n\n    // Exclude previously seen articles\n    if ($history && !empty($smd_prior_featured)) {\n        $where .= \' AND smdfeat.feat_id NOT IN(\' . join(\',\', $smd_prior_featured) . \')\';\n    }\n\n    // NOTE: time is left in the $atts array and passed to article_custom. Otherwise the default\n    // time value (past) will be used\n    if ($time) {\n        switch ($time) {\n            case \'any\':\n                break;\n            case \'future\':\n                $where .= \" AND Posted > now()\";\n                break;\n            default:\n                $where .= \" AND Posted <= now()\";\n        }\n        if (!$prefs[\'publish_expired_articles\']) {\n            $where .= \" AND (now() <= Expires OR Expires IS NULL)\";\n        }\n    }\n\n    if ($status) {\n        $where .= \' AND Status IN (\'.join(\',\', do_list($status)).\')\';\n        unset($atts[\'status\']);\n    }\n\n    if ($section) {\n        $where .= \" AND Section IN (\'\".join(\"\',\'\", do_list($section)).\"\')\";\n        unset($atts[\'section\']);\n    }\n\n    if ($sort) {\n        $where .=\' ORDER BY \'.$sort;\n        unset($atts[\'sort\']);\n    }\n\n    // Leave limit in the $atts array too so it doesn\'t default to article_custom\'s value if set here\n    if ($limit) {\n        $where .=\' LIMIT \'.$limit;\n    }\n\n    // Don\'t pass the remaining attributes we\'ve already handled onto the article_custom tag\n    unset(\n        $atts[\'label\'],\n        $atts[\'unlabel\'],\n        $atts[\'labeltag\'],\n        $atts[\'wraptag\'],\n        $atts[\'break\'],\n        $atts[\'class\'],\n        $atts[\'history\'],\n        $atts[\'html_id\'],\n        $atts[\'debug\']\n    );\n\n    if ($debug) {\n        echo \'++ WHERE ++\';\n        dmp($where);\n    }\n\n    $rs = getRows(\'SELECT *, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod FROM \'.PFX.\'textpattern AS txp LEFT JOIN \'.PFX.SMD_FEAT.\' AS smdfeat ON txp.ID=smdfeat.feat_id WHERE \'.$where, $debug);\n\n    if ($debug > 1 && $rs) {\n        echo \'++ RECORD SET ++\';\n        dmp($rs);\n    }\n\n    $truePart = EvalElse($thing, 1);\n    $falsePart = EvalElse($thing, 0);\n\n    $out = array();\n    if ($rs) {\n        foreach ($rs as $row) {\n            $smd_featured_info[\'label\'] = $row[\'label\'];\n            $smd_featured_info[\'position\'] = $row[\'feat_position\'];\n            $smd_featured_info[\'title\'] = $row[\'feat_title_html\'];\n            $smd_featured_info[\'description\'] = $row[\'desc_html\'];\n            $atts[\'id\'] = $row[\'ID\'];\n\n            $artout = article_custom($atts, $truePart);\n\n            if ($artout) {\n                $smd_prior_featured[] = $row[\'ID\'];\n                $out[] = $artout;\n            }\n        }\n    } else {\n        return parse($falsePart);\n    }\n    if ($out) {\n        return (($labeltag) ? doLabel( ( ($label == \'\') ? $unlabel : $label), $labeltag ) : \'\')\n            .doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n    }\n    return \'\';\n}\n\n// ------------------------\nfunction smd_unfeatured($atts, $thing)\n{\n    global $smd_prior_featured, $thispage, $pretext;\n\n    $time = (isset($atts[\'time\'])) ? $atts[\'time\'] : \'\';\n    $status = (isset($atts[\'status\'])) ? $atts[\'status\'] : 4;\n    $section = (isset($atts[\'section\'])) ? $atts[\'section\'] : \'\';\n    $history = (isset($atts[\'history\'])) ? $atts[\'history\'] : \'1\';\n    unset($atts[\'history\']);\n\n    $where = \"1=1\";\n\n    if ($time) {\n        switch ($time) {\n            case \'any\':\n                break;\n            case \'future\':\n                $where .= \" AND Posted > now()\";\n                break;\n            default:\n                $where .= \" AND Posted <= now()\";\n        }\n        if (!$prefs[\'publish_expired_articles\']) {\n            $where .= \" AND (now() <= Expires OR Expires IS NULL)\";\n        }\n    }\n\n    if ($status) {\n        $where .= \' AND Status IN (\'.join(\',\', do_list($status)).\')\';\n    }\n\n    if ($section) {\n        $where .= \" AND Section IN (\'\".join(\"\',\'\", do_list($section)).\"\')\";\n    }\n\n    // Exclude previously seen articles\n    if ($history && !empty($smd_prior_featured)) {\n        $where .= \' AND id NOT IN(\' . join(\',\', $smd_prior_featured) . \')\';\n    }\n\n    $offset = (isset($atts[\'offset\'])) ? $atts[\'offset\'] : 0;\n\n    if (isset($atts[\'limit\']) && isset($atts[\'pageby\'])) {\n        $limit = $atts[\'limit\'];\n        $pageby = $atts[\'pageby\'];\n        $pageby = ($pageby == \'limit\') ? $limit : $pageby;\n        $pg = gps(\'pg\');\n\n        $grand_total = safe_count(\'textpattern\',$where);\n        $total = $grand_total - $offset;\n        $numPages = ceil($total/$pageby);\n        $pg = (!$pg) ? 1 : $pg;\n        $pgoffset = $offset + (($pg - 1) * $pageby);\n        // send paging info to txp:newer and txp:older\n        $pageout[\'pg\']          = $pg;\n        $pageout[\'numPages\']    = $numPages;\n        $pageout[\'s\']           = $pretext[\'s\'];\n        $pageout[\'c\']           = $pretext[\'c\'];\n        $pageout[\'context\']     = \'article\';\n        $pageout[\'grand_total\'] = $grand_total;\n        $pageout[\'total\']       = $total;\n\n        if (empty($thispage)) {\n            $thispage = $pageout;\n        }\n    } else {\n        $pgoffset = $offset;\n    }\n\n    $truePart = EvalElse($thing, 1);\n    $falsePart = EvalElse($thing, 0);\n\n    $rs = safe_column(\'ID\', \'textpattern\', $where);\n    if ($rs) {\n        $atts[\'offset\'] = $pgoffset;\n        $atts[\'id\'] = join(\',\', $rs);\n        $out = article_custom($atts, $truePart);\n        return $out;\n    } else {\n        return parse($falsePart);\n    }\n}\n\n// ------------------------\nfunction smd_featured_info($atts)\n{\n    global $smd_featured_info;\n\n    extract(lAtts(array(\n        \'item\' => \'\',\n    ),$atts));\n\n    return (isset($smd_featured_info[$item]) ? $smd_featured_info[$item] : \'\');\n}\n\n// ------------------------\nfunction smd_if_featured($atts, $thing)\n{\n    global $smd_featured_info, $thisarticle;\n\n    extract(lAtts(array(\n        \'id\' => \'\',\n        \'label\' => \'\',\n    ),$atts));\n\n    if ($id) {\n        $id = join(\"\',\'\", do_list(doSlash($id)));\n    }\n\n    if (!$id && $thisarticle) {\n        $id = $thisarticle[\'thisid\'];\n    }\n\n    $where[] = \"feat_id IN (\'\".$id.\"\')\";\n\n    if (isset($atts[\'label\'])) {\n        $label = explode(\',\', doSlash(trim($label)));\n        $lblwhere = array();\n        foreach ($label as $lbl) {\n            $lblwhere[] = \"\'\".trim($lbl).\"\'\";\n        }\n        if ($lblwhere) {\n            $where[] = \'( BINARY label IN (\' . join(\',\', $lblwhere) . \') )\';\n        }\n    }\n\n    $ret = safe_row(\'feat_id\', SMD_FEAT, join(\' AND\', $where));\n\n    return parse(EvalElse($thing, $ret));\n}', '/**\n * smd_featured\n *\n * A Textpattern CMS plugin for pimping your articles on landing pages.\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (txpinterface === \'admin\') {\n    global $smd_featured_event, $smd_featured_pref_privs;\n\n    $smd_featured_event = \'smd_featured\';\n    $smd_featured_privs = get_pref(\'smd_featured_privs\', \'1, 2\');\n    $smd_featured_pref_privs = array(\n        \'all\' => array(\n            \'smd_featured_display\',\n            \'smd_featured_box_size\',\n            \'smd_featured_sort\',\n        ),\n        \'1\' => array(\n            \'smd_featured_section_list\',\n            \'smd_featured_textile\',\n            \'smd_featured_show_ui\',\n        ),\n    );\n\n    add_privs($smd_featured_event, $smd_featured_privs);\n    register_tab(\"content\", $smd_featured_event, gTxt(\'smd_feat_tab_name\'));\n    register_callback(\'smd_featured_manage\', $smd_featured_event);\n    register_callback(\'smd_featured_welcome\', \'plugin_lifecycle.smd_featured\');\n    register_callback(\'smd_featured_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_featured\')\n            ->register(\'smd_featured_info\')\n            ->register(\'smd_unfeatured\')\n            ->register(\'smd_if_featured\');\n    }\n}\n\nif (!defined(\'SMD_FEAT\')) {\n    define(\"SMD_FEAT\", \'smd_featured\');\n}\n\n// **********\n// ADMIN SIDE\n// **********\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers\nfunction smd_featured_get_style_rules()\n{\n    $sizes = do_list(get_pref(\'smd_featured_box_size\', \'150x40\', 1), \'x\');\n    $sizes[0] = (isset($sizes[0]) && is_numeric($sizes[0])) ? $sizes[0] : \'150\';\n    $sizes[1] = (isset($sizes[1]) && is_numeric($sizes[1])) ? $sizes[1] : \'40\';\n    $stylekeys = array(\'{bw}\', \'{bh}\');\n    $stylevals = array($sizes[0], $sizes[1]);\n\n    $smd_featured_styles = array(\n        \'common\' => \'\n#smd_all h3 { margin:15px 0; }\n#smd_featured_cpanel select, #smd_featured_cpanel input[type=\"text\"] { margin-bottom:10px; }\n#smd_featured_cpanel input[type=\"text\"] { padding:3px; }\n#control input, #control select { margin:0; }\n.smd_hidden { display:none; }\n.smd_featured { background-color:#e2dfce; position:relative; }\n.smd_featured a { font-weight: bold; color:#80551e; }\n#smd_featured_cpanel form { border:1px solid #ccc; margin: 0.5rem 0; padding: 1rem; }\n.smd_featured_subpanel h4 { margin: 0 0 0.5rem }\n.smd_featured_table { margin:0 auto; text-align:center; }\n.smd_clear { clear:both; }\n.smd_featured_table div { display:inline-block; width:{bw}px; height:{bh}px; border:1px solid #aaa; padding:0.4em; overflow:hidden; }\n.smd_feat_edart { position:absolute; right:5px; bottom:3px; }\n.smd_feat_edpos { position:absolute; left:2px; bottom:2px; font-size:75%; width:7em;}\n#smd_feat_saveform { margin:1em; }\n#smd_feat_loading { color:#80551e; text-align:center; font-size:200%; font-style:italic; width:500px; height:100px; margin:0 auto; border:1px solid #777; background:#e2dfce;display:block }\n\',\n    );\n\n    return str_replace($stylekeys, $stylevals, $smd_featured_styles);\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_inject_css($evt, $stp)\n{\n    global $smd_featured_event, $event;\n\n    if ($event === $smd_featured_event) {\n        $smd_featured_styles = smd_featured_get_style_rules();\n        echo \'<style type=\"text/css\">\', $smd_featured_styles[\'common\'], \'</style>\';\n    }\n\n    return;\n}\n// ------------------------\nfunction smd_featured_manage($evt, $stp)\n{\n    if ($stp == \'save_pane_state\') {\n        smd_featured_save_pane_state();\n    } else {\n        if(!$stp or !in_array($stp, array(\n                \'smd_featured_table_install\',\n                \'smd_featured_table_remove\',\n                \'smd_featured_prefsave\',\n                \'smd_featured_save\',\n                \'smd_featured_store_pos\',\n                \'smd_featured_tagit\',\n                \'smd_featured_change_pageby\',\n            ))) {\n            smd_featured_list(\'\');\n        } else $stp();\n    }\n}\n\n// ------------------------\nfunction smd_featured_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_featured_table_install(0);\n            // Remove some of the per-user prefs on upgrade from v0.3x to v0.40\n            safe_delete (\'txp_prefs\', \"name IN (\'smd_featured_textile\') AND user_name != \'\'\");\n            $msg = \'Thanks for installing smd_featured. Please read the docs :-)\';\n            break;\n        case \'deleted\':\n            smd_featured_table_remove(0);\n            break;\n    }\n    return $msg;\n}\n\n// ------------------------\nfunction smd_featured_list($msg = \'\')\n{\n    global $smd_featured_event, $smd_featured_list_pageby, $txp_user, $smd_featured_pref_privs;\n\n    pagetop(gTxt(\'smd_feat_tab_name\'), $msg);\n    extract(gpsa(array(\'smd_feat_id\', \'smd_feat_label\', \'smd_feat_searchkeep\', \'smd_feat_filtkeep\', \'page\')));\n\n    if (smd_featured_table_exist(1)) {\n        smd_featured_table_upgrade();\n        $featlist = safe_rows(\'*\', SMD_FEAT, \'1=1\');\n        $featlist = empty($featlist) ? array() : $featlist;\n        $editname = $feat_label = $feat_title = $feat_desc = \'\';\n\n        $etypes = $ftypes = $flist = array();\n        $etypes[\'\'] = gTxt(\'smd_feat_unlabelled\');\n        $ftypes[\'smd_unlabelled\'] = gTxt(\'smd_feat_unlabelled\');\n        foreach ($featlist as $item) {\n            if (isset($item[\'label\']) && !empty($item[\'label\'])) {\n                $ftypes[$item[\'label\']] = $etypes[$item[\'label\']] = $item[\'label\'];\n            }\n            $flist[$item[\'feat_id\']] = array(\n                \'label\' => $item[\'label\'],\n                \'position\' => $item[\'feat_position\'],\n                \'title\' => $item[\'feat_title\'],\n                \'title_html\' => $item[\'feat_title_html\'],\n                \'description\' => $item[\'description\'],\n                \'desc_html\' => $item[\'desc_html\']\n            );\n        }\n\n        $featlist = $flist;\n\n        $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n        $rights = array_key_exists($privs, $smd_featured_pref_privs);\n\n        // Get additional filtering from hidden prefs\n        $where = array(\'1=1\');\n        $seclist = get_pref(\'smd_featured_section_list\', \'\', 1);\n        if (!$seclist) {\n            $seclist = gps(\'section_list\');\n        }\n        if ($seclist) {\n            $where[] = \"Section IN (\'\".join(\"\',\'\", do_list($seclist)).\"\')\";\n        }\n        $where[] = \"Status IN (4,5)\";\n\n        $orderby = get_pref(\'smd_featured_sort\', \'Posted desc\', 1);\n        $orderqry = ($orderby) ? \' ORDER BY \'.doSlash($orderby) : \'\';\n        $currOrder = explode(\' \', $orderby);\n        $currOrder[0] = (isset($currOrder[0])) ? $currOrder[0] : \'Posted\';\n        $currOrder[1] = (isset($currOrder[1])) ? $currOrder[1] : \'desc\';\n        $display = get_pref(\'smd_featured_display\', \'all\', 1);\n\n        // Generate the extra criteria if in list view\n        if ($display === \'paginated\') {\n            if ($smd_feat_searchkeep) {\n                $items = do_list($smd_feat_searchkeep, \' \');\n                $itlist = array();\n                foreach ($items as $item) {\n                    if (trim($item) != \'\') {\n                        $itlist[] = \"Title like \'%$item%\'\";\n                    }\n                }\n                $where[] = \'(\'.join(\' OR \', $itlist).\')\';\n            }\n            if ($smd_feat_filtkeep) {\n                $lbl = ($smd_feat_filtkeep == \'smd_unlabelled\') ? \'\' : $smd_feat_filtkeep;\n                $ids = safe_column(\'feat_id\', SMD_FEAT, \"BINARY label = \'$lbl\'\");\n                $where[] = \"ID IN (\'\".join(\"\',\'\", $ids).\"\')\";\n            }\n        }\n\n        $where = join(\' AND \', $where);\n        $total = safe_count(\'textpattern\', $where);\n        $do_pag = !$smd_feat_filtkeep && !$smd_feat_searchkeep;\n\n        $limit = ($display==\'paginated\' && $do_pag) ? max($smd_featured_list_pageby, 15) : 99999;\n        list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n        $rs = safe_rows(\'*\', \'textpattern\', $where. $orderqry . \" limit $offset, $limit\");\n        $out = array();\n\n        foreach ($rs as $row) {\n            $ftype = isset($featlist[$row[\'ID\']][\'label\']) ? $featlist[$row[\'ID\']][\'label\'] : \'\';\n            $out[] = array($row[\'ID\'], $row[\'Title\'], $ftype);\n            if ($smd_feat_id && $row[\'ID\'] == $smd_feat_id) {\n                $editname = $row[\'Title\'];\n                $feat_label = $featlist[$smd_feat_id][\'label\'];\n                $feat_title = $featlist[$smd_feat_id][\'title\'];\n                $feat_position = $featlist[$smd_feat_id][\'position\'];\n                $feat_desc = doStrip(str_replace(\'\\r\\n\',\'\n\', $featlist[$smd_feat_id][\'description\'])); // Hackish newline kludge\n            }\n            // Add the position to the most recent array entry\n            if (isset($featlist[$row[\'ID\']][\'position\'])) {\n                $out[count($out)-1][] = $featlist[$row[\'ID\']][\'position\'];\n            }\n        }\n\n        //TODO: i18n\n        $sortopts = array(\n            \'Posted\' => \'Posted\',\n            \'Expires\' => \'Expiry\',\n            \'LastMod\' => \'Modified\',\n            \'Title\' => \'Title\',\n            \'Section\' => \'Section\',\n            \'Category1\' => \'Category1\',\n            \'Category2\' => \'Category2\',\n        );\n        $sortdirs = array(\n            \'asc\' => \'Ascending\',\n            \'desc\' => \'Descending\',\n        );\n        $displayopts = array(\n            \'all\' => \'All\',\n            \'paginated\' => \'Paginated\',\n        );\n        $textileonoff = explode(\',\', get_pref(\'smd_featured_textile\', \'title,desc\', 1));\n        $showonoff = explode(\',\', get_pref(\'smd_featured_show_ui\', \'label,title,desc\', 1));\n        $txt_ttl = in_array(\'title\', $textileonoff);\n        $txt_desc = in_array(\'desc\', $textileonoff);\n        $show_lbl = in_array(\'label\', $showonoff);\n        $show_ttl = in_array(\'title\', $showonoff);\n        $show_desc = in_array(\'desc\', $showonoff);\n        $use_edit = ($show_lbl || $show_ttl || $show_desc) ? \'1\' : \'0\';\n\n        $sizes = do_list(get_pref(\'smd_featured_box_size\', \'150x40\', 1), \'x\');\n        $sizes[0] = (isset($sizes[0]) && is_numeric($sizes[0])) ? $sizes[0] : \'150\';\n        $sizes[1] = (isset($sizes[1]) && is_numeric($sizes[1])) ? $sizes[1] : \'40\';\n        $display_js = ($display==\'all\') ? 1 : 0;\n\n        echo n.\'<div id=\"smd_feat_loading\"><div id=\"smd_feat_loading_holder\">Loading...</div></div>\';\n        echo n.\'<div id=\"smd_container\" class=\"txp-container\" style=\"display:none;\">\';\n        echo n.\'<div id=\"smd_featured_control_panel\"><span class=\"txp-summary lever\'.(get_pref(\'pane_smd_featured_cpanel_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_featured_cpanel\">\'.gTxt(\'smd_feat_control_panel\').\'</a></span>\';\n        echo n.\'<div id=\"smd_featured_cpanel\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_featured_cpanel_visible\') ? \'block\' : \'none\').\'\">\';\n\n        echo n.\'<form id=\"smd_feat_prefs\" action=\"index.php\" method=\"post\">\';\n        echo n.\'<div id=\"smd_featured_prefs\" class=\"smd_featured_subpanel\"><h4>\'.gTxt(\'smd_feat_prefs\').\'</h4>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<label for=\"smd_feat_display\">\'.gTxt(\'smd_feat_pref_display\').\'</label>\'.\n            n.selectInput(\'smd_feat_display\', $displayopts, $display, \'\', \'\', \'smd_feat_display\');\n        echo n.\'</div>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<label for=\"smd_feat_sort\">\'.gTxt(\'smd_feat_pref_sort\').\'</label>\'.\n            n.selectInput(\'smd_feat_sort\', $sortopts, $currOrder[0], \'\', \'\', \'smd_feat_sort\').\n            n.selectInput(\'smd_feat_sortdir\', $sortdirs, $currOrder[1], \'\', \'\', \'smd_feat_sortdir\');\n        echo n.\'</div>\';\n\n        echo n.\'<div class=\"smd_featured_pref\">\';\n        echo n.\'<div id=\"smd_feat_display_box\">\';\n        echo n.\'<label for=\"smd_feat_boxsize\">\'.gTxt(\'smd_feat_pref_boxsize\').\'</label>\'.\n            n.fInput(\'text\', \'smd_feat_box_x\', $sizes[0], \'\', \'\', \'\', \'3\', \'\', \'smd_feat_boxsize\').n.\n            gTxt(\'smd_feat_pref_boxsizeby\').n.fInput(\'text\', \'smd_feat_box_y\', $sizes[1], \'\', \'\', \'\', \'3\');\n        echo n.\'</div>\';\n\n        if ($rights) {\n            echo n.\'<div class=\"smd_featured_pref smd_feat_show_ui\">\';\n            echo n.\'<label for=\"smd_feat_show_ui\">\'.gTxt(\'smd_feat_show_ui\').\'</label>\';\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'label\', $show_lbl, \'\', \'smd_feat_show_label\').sp.gTxt(\'smd_feat_label\');\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'title\', $show_ttl, \'\', \'smd_feat_show_title\').sp.gTxt(\'smd_feat_title\');\n            echo n.checkbox(\'smd_feat_show_ui[]\', \'desc\', $show_desc, \'\', \'smd_feat_show_desc\').sp.gTxt(\'smd_feat_desc\');\n            echo n.\'</div>\';\n\n            echo n.\'<div class=\"smd_featured_pref smd_feat_textile\">\';\n            echo n.\'<label for=\"smd_feat_textile\">\'.gTxt(\'smd_feat_textile\').\'</label>\';\n            echo n.checkbox(\'smd_feat_textile[]\', \'title\', $txt_ttl, \'\', \'smd_feat_textile_title\').sp.gTxt(\'smd_feat_title\');\n            echo n.checkbox(\'smd_feat_textile[]\', \'desc\', $txt_desc, \'\', \'smd_feat_textile_desc\').sp.gTxt(\'smd_feat_desc\');\n            echo n.\'</div>\';\n\n            echo n.\'<div class=\"smd_featured_pref smd_feat_section_list\">\';\n            echo n.\'<label for=\"smd_feat_section_list\">\'.gTxt(\'smd_feat_section_list\').\'</label>\';\n            echo n.fInput(\'text\', \'smd_feat_section_list\', $seclist, \'\', \'\', \'\', \'30\', \'\', \'smd_feat_section_list\');\n            echo n.\'</div>\';\n        }\n\n        echo n.\'</div>\';\n\n        echo n.eInput($smd_featured_event).sInput(\'smd_featured_prefsave\');\n        echo n.fInput(\'submit\', \'\', gTxt(\'save\'), \'smd_featured_save\');\n        echo n.\'</div>\';\n        echo n.\'</form>\';\n\n        echo n.\'<form id=\"smd_feat_filtform\" action=\"index.php\" method=\"post\" onsubmit=\"smd_featured_editkeep(0);return false;\">\';\n        echo n.\'<div id=\"smd_featured_filt\" class=\"smd_featured_subpanel\"><h4>\'.gTxt((($display==\'all\') ? \'smd_feat_search_live\' : \'smd_feat_search_standard\')).\'</h4>\';\n        echo n.\'<label for=\"smd_feat_search\">\'.gTxt(\'smd_feat_by_name\').\'</label>\'.n.fInput(\'text\', \'smd_feat_search\', $smd_feat_searchkeep, \'\', \'\', \'\', \'\', \'\', \'smd_feat_search\').\n            (($ftypes) ? n.\'<div id=\"smd_featured_bylabel\">\'.gTxt(\'smd_feat_by_label\').n.selectInput(\'smd_feat_filt\', $ftypes, $smd_feat_filtkeep, 1, \'\', \'smd_feat_filt\').\'</div>\' : \'\');\n        echo ($display==\'paginated\') ? n.fInput(\'submit\', \'\', gTxt(\'go\'), \'smd_featured_save\', \'\', \'return smd_featured_editkeep(0);\') : \'\';\n        echo n.eInput($smd_featured_event).n.sInput(\'smd_featured_list\');\n        echo n.\'</div>\';\n        echo n.\'</form>\';\n\n        echo n.\'</div>\';\n        echo n.\'</div>\';\n\n        echo n.\'<form id=\"smd_feat_editform\" action=\"index.php\" method=\"post\">\';\n        echo n.hInput(\'smd_feat_searchkeep\', \'\').\n                n.hInput(\'smd_feat_filtkeep\', \'\').\n                n.hInput(\'smd_feat_id\', \'\').\n                n.eInput($smd_featured_event).\n                n.sInput(\'smd_featured_list\');\n        echo n.\'</form>\';\n\n        echo n.\'<form id=\"smd_feat_saveform\" action=\"index.php\" method=\"post\" onsubmit=\"return smd_featured_savekeep();\">\';\n        echo n.startTable();\n        if ($smd_feat_id) {\n            echo n.tr(\n                n.td(\'&nbsp;\').tdcs(gTxt(\'edit\').sp.strong(eLink(\'article\', \'edit\', \'ID\', $smd_feat_id, $editname)), 2).\n                n.td(fInput(\'submit\', \'\', gTxt(\'save\'))).\n                n.hInput(\'smd_feat_searchkeep\', \'\').\n                n.hInput(\'smd_feat_filtkeep\', \'\').\n                n.hInput(\'smd_feat_id\', $smd_feat_id).\n                n.eInput($smd_featured_event).\n                n.sInput(\'smd_featured_save\')\n            );\n            if ($show_lbl) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'label\'), \'\', \'smd_feat_label\').\n                    n.td(\n                        n.fInput(\'text\', \'smd_feat_label\', $feat_label, \'\', \'\', \'\', \'\', \'\', \'smd_feat_label\').\n                        n.selectInput(\'smd_feat_labelchoose\', $etypes, $feat_label, 0, \'\', \'smd_feat_labelchoose\')\n                    )\n                );\n            }\n            if ($show_ttl) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'title\'), \'\', \'smd_feat_title\').\n                    n.td(\n                        n.fInput(\'text\', \'smd_feat_title\', $feat_title, \'\', \'\', \'\', \'\', \'\', \'smd_feat_title\')\n                    )\n                );\n            }\n            if ($show_desc) {\n                echo n.tr(\n                    n.fLabelCell(gTxt(\'description\'), \'\', \'smd_feat_desc\').\n                    n.td(text_area(\'smd_feat_desc\', 80, 400, $feat_desc, \'smd_feat_desc\'))\n                );\n            }\n            echo n.tr(\n                n.fLabelCell(gTxt(\'smd_feat_position\'), \'\', \'smd_feat_position\').\n                n.td(fInput(\'text\', \'smd_feat_position\', $feat_position, \'\', \'\', \'\', \'\', \'\', \'smd_feat_position\'))\n            );\n        }\n        echo n.endTable();\n        echo n.\'</form>\';\n\n        $edbtn = small(\'[\'.gTxt(\'edit\').\']\');\n        $edtip = gTxt(\'smd_feat_lbl_edfeat\');\n        $edartbtn = \'&rarr;\';\n        $edarttip = gTxt(\'smd_feat_lbl_edart\');\n        $edpostip = gTxt(\'smd_feat_lbl_edpos\');\n        $unfeattip = gTxt(\'smd_feat_lbl_unfeature\');\n\n        if ($out) {\n            $rows = count($rs);\n            $tblout = array();\n            $atts = \' class=\"smd_featured_table smd_clear\" id=\"smd_\'.$display.\'\"\';\n\n            $tblout[] = hed(gTxt(\'smd_feat_manage_lbl\', array(), \'raw\'), 3, \' class=\"smd_clear\"\');\n            for ($idx = 0; $idx < $rows; $idx++) {\n                $isfeat = (isset($out[$idx]) && array_key_exists($out[$idx][0], $featlist));\n                $cellclass = $isfeat ? \' class=\"smd_featured\"\' : \'\';\n                $edlink = $isfeat && $use_edit ? \'<a class=\"smd_feat_edlink\" href=\"#\" onclick=\"return smd_featured_editkeep(\\\'\'.$out[$idx][0].\'\\\');\" title=\"\'.$edtip.\'\">\'.$edbtn.\'</a>\'.sp : \'\';\n                $edpos = $isfeat ? \'<input name=\"smd_featured_position\" class=\"smd_feat_edpos\" title=\"\'.$edpostip.\'\" value=\"\'.$out[$idx][3].\'\" onblur=\"return smd_featured_store_pos(this, \\\'\'.$out[$idx][0].\'\\\')\" />\' : \'\';\n                $edart = $isfeat ? \'<a class=\"smd_feat_edart\" href=\"?event=article&step=edit&ID=\'.$out[$idx][0].\'\" title=\"\'.$edarttip.\'\">\'.$edartbtn.\'</a>\' : \'\';\n                $rowdata = (isset($out[$idx])) ? \'<span name=\"smd_feat_name\" class=\"smd_hidden\">\'.$out[$idx][1].\'</span>\' : \'\';\n                $rowdata .= $isfeat ? \'<span name=\"smd_feat_label\" class=\"extra smd_hidden\">\'.$out[$idx][2].\'</span>\' : \'\';\n                $tblout[] = (isset($out[$idx])) ? \'<div\'.$cellclass.\'>\'.$edlink.\'<a href=\"#\" title=\"\'.(($isfeat) ? $unfeattip : \'\').\'\" onclick=\"return smd_featured_select(this, \\\'\'.$out[$idx][0].\'\\\')\">\'.$out[$idx][1].\'</a>\'.$edpos.$edart.$rowdata.\'</div>\' : \'<div></div>\';\n            }\n            echo tag(join(\"\",$tblout), \'div\', $atts);\n            echo \'<div class=\"smd_clear\"></div>\';\n            if ($display==\'paginated\' && $do_pag) {\n                echo n.\'<div id=\"\'.$smd_featured_event.\'_navigation\" class=\"txp-navigation\">\'.\n                    n.nav_form($smd_featured_event, $page, $numPages, \'\', \'\', \'\', \'\', $total, $limit).\n                    n.pageby_form($smd_featured_event, $smd_featured_list_pageby);\n                    n.\'</div>\';\n            }\n        }\n\n        $qs = array(\n            \"event\" => $smd_featured_event,\n        );\n\n        $qsVars = \"index.php\".join_qs($qs);\n        $verifyTxt = gTxt(\'smd_feat_unfeature_confirm\');\n\n        echo <<<EOJS\n<script type=\"text/javascript\">\nfunction smd_featured_select(obj, id) {\n    obj = jQuery(obj).parent();\n\n    // N.B. Negative logic used here because we\'re checking the class _before_ it\'s been toggled\n    var action = ((obj).hasClass(\"smd_featured\")) ? \'remove\' : \'add\';\n    if (action == \'remove\') {\n        if (\'1\' == {$use_edit}) {\n            var ret = confirm(\'{$verifyTxt}\');\n            if (ret == false) {\n                return false;\n            }\n        }\n    }\n    jQuery.post(\'{$qsVars}\', { step: \"smd_featured_tagit\", smd_feat_id: id, smd_feat_action: action },\n        function(data) {\n            obj.toggleClass(\'smd_featured\');\n            if (action == \'add\') {\n                obj.find(\'a\').attr(\'title\', \"{$unfeattip}\");\n                if (\'1\' == \'{$use_edit}\') {\n                    obj.prepend(\'<a class=\"smd_feat_edlink\" title=\"{$edtip}\" href=\"#\" onclick=\"return smd_featured_editkeep(\\\'\'+id+\'\\\')\">{$edbtn}</a>&nbsp;\');\n                }\n                obj.append(\'<input name=\"smd_featured_position\" class=\"smd_feat_edpos\" title=\"{$edpostip}\" value=\"\" onblur=\"return smd_featured_store_pos(this, \\\'\'+id+\'\\\')\" />\');\n                obj.append(\'<a class=\"smd_feat_edart\" title=\"{$edarttip}\" href=\"?event=article&step=edit&ID=\'+id+\'\">{$edartbtn}</a>\');\n                obj.append(\'<span name=\"smd_feat_label\" class=\"extra smd_hidden\"></span>\');\n                obj.find(\'input.smd_feat_edpos\').focus();\n            } else {\n                obj.find(\'a.smd_feat_edlink\').remove();\n                obj.find(\'input.smd_feat_edpos\').remove();\n                obj.find(\'a.smd_feat_edart\').remove();\n                obj.find(\'a\').attr(\'title\', \'\');\n                obj.find(\'span.extra\').remove();\n                if (jQuery(\"#smd_feat_search\").val() != \'\') jQuery(\"#smd_feat_search\").keyup();\n                if (jQuery(\"#smd_feat_filt\").val() != \'\') jQuery(\"#smd_feat_filt\").change();\n            }\n        }\n    );\n    return false;\n}\n\nfunction smd_featured_editkeep(id) {\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_searchkeep\']\").val(jQuery(\"#smd_feat_search\").val());\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_filtkeep\']\").val(jQuery(\"#smd_featured_bylabel #smd_feat_filt option:selected\").val());\n    jQuery(\"#smd_feat_editform [name=\'smd_feat_id\']\").val(id);\n    jQuery(\"#smd_feat_editform\").submit();\n}\n\nfunction smd_featured_savekeep() {\n    jQuery(\"#smd_feat_saveform [name=\'smd_feat_searchkeep\']\").val(jQuery(\"#smd_feat_search\").val());\n    jQuery(\"#smd_feat_saveform [name=\'smd_feat_filtkeep\']\").val(jQuery(\"#smd_featured_bylabel #smd_feat_filt option:selected\").val());\n}\n\nfunction smd_feat_filter(selector, query, nam, csense, exact) {\n    if ({$display_js} == 1) {\n        var query = jQuery.trim(query);\n        csense = (csense) ? \"\" : \"i\";\n        query = query.replace(/ /gi, \'|\'); // add OR for regex query\n        if (exact) {\n            tmp = query.split(\'|\');\n            for (var idx = 0; idx < tmp.length; idx++) {\n                tmp[idx] = \'^\'+tmp[idx]+\'$\';\n            }\n            query = tmp.join(\'|\');\n        }\n        var re = new RegExp(query, csense);\n        jQuery(selector).each(function() {\n            sel = (typeof nam==\"undefined\" || nam==\'\') ? jQuery(this) : jQuery(this).find(\"span[name=\'\"+nam+\"\']\");\n            if (query == \'\') {\n                if (sel.length == 1 && sel.text() == \'\') {\n                    jQuery(this).show();\n                } else {\n                    jQuery(this).hide();\n                }\n            } else {\n                if (sel.text().search(re) < 0) {\n                    jQuery(this).hide();\n                } else {\n                    jQuery(this).show();\n                }\n            }\n        });\n    }\n}\n\nfunction smd_featured_store_pos(obj, id) {\n    var obj = jQuery(obj);\n    var pos = obj.val();\n\n    //TODO: feedback on success\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_featured_store_pos\',\n        smd_feat_id: id,\n        smd_feat_pos: pos\n    });\n}\n\njQuery(function() {\n    jQuery(\"#smd_feat_search\").keyup(function(event) {\n        jQuery(\"#smd_feat_filt\").val(\'0\'); // Clear the filter dropdown\n        // if esc is pressed or nothing is entered\n        if (event.keyCode == 27 || jQuery(this).val() == \'\') {\n            jQuery(this).val(\'\');\n            if ({$display_js} == 1) {\n                jQuery(\".smd_featured_table div:not(.tblhead)\").show();\n            }\n        } else {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', jQuery(this).val(), \'smd_feat_name\', 0, 0);\n        }\n    });\n    if (\'{$smd_feat_searchkeep}\' != \'\') {\n        jQuery(\"#smd_feat_search\").keyup();\n    }\n    jQuery(\"#smd_feat_filt\").change(function(event) {\n        jQuery(\"#smd_feat_search\").val(\'\'); // Empty the search box\n        if (jQuery(this).val() == \'\') {\n            if ({$display_js} == 1) {\n                jQuery(\'.smd_featured_table div:not(.tblhead)\').show();\n            }\n        } else if (jQuery(this).val() == \'smd_unlabelled\') {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', \'\', \'smd_feat_label\', 0, 0);\n        } else {\n            smd_feat_filter(\'.smd_featured_table div:not(.tblhead)\', jQuery(this).val(), \'smd_feat_label\', 1, 1);\n        }\n    });\n    if (\'{$smd_feat_filtkeep}\' != \'\') {\n        jQuery(\"#smd_feat_filt\").change();\n    }\n    if (\'{$smd_feat_id}\' != \'\') {\n        jQuery(\"#smd_feat_label\").focus();\n        jQuery(\"#smd_feat_labelchoose\").change(function(event) {\n            jQuery(\"#smd_feat_label\").val(jQuery(this).val());\n        });\n    }\n    jQuery(\"#smd_feat_loading\").hide();\n    jQuery(\"#smd_container\").show();\n});\n\n</script>\nEOJS;\n\n        echo \'</div>\';\n    } else {\n        // Table not installed\n        $btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_featured_event.a.\'step=smd_featured_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', gTxt(\'smd_feat_tbl_install_lbl\')).\'</form>\';\n        $btnStyle = \' style=\"border:0;height:25px\"\';\n        echo startTable();\n        echo tr(tda(strong(gTxt(\'smd_feat_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_feat_prefs_some_explain\').br.br\n                .gTxt(\'smd_feat_prefs_some_opts\'), \' colspan=\"2\"\')\n        );\n        echo tr(tda($btnInstall, $btnStyle));\n        echo endTable();\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_change_pageby()\n{\n    global $smd_featured_event;\n\n    event_change_pageby($smd_featured_event);\n    smd_featured_list();\n}\n\n// ------------------------\n// Update the passed record in the featured table\nfunction smd_featured_save()\n{\n    global $smd_featured_event;\n\n    extract(gpsa(array(\'smd_feat_id\', \'smd_feat_label\', \'smd_feat_title\', \'smd_feat_desc\', \'smd_feat_position\')));\n    assert_int($smd_feat_id);\n\n    $smd_feat_titletile = $smd_feat_title;\n    $smd_feat_desctile = $smd_feat_desc;\n    $smd_feat_label = doSlash($smd_feat_label);\n    $smd_feat_position = doSlash($smd_feat_position);\n\n    if (smd_featured_table_exist()) {\n        @include_once txpath.\'/lib/classTextile.php\';\n        @include_once txpath.\'/publish.php\';\n\n        $textileonoff = explode(\',\', get_pref(\'smd_featured_textile\', \'\'));\n        $txt_ttl = in_array(\'title\', $textileonoff);\n        $txt_desc = in_array(\'desc\', $textileonoff);\n\n        if (class_exists(\'Textile\')) {\n            $textile = new Textile();\n            $smd_feat_titletile = doSlash((($txt_ttl) ? $textile->TextileThis(parse($smd_feat_title)) : parse($smd_feat_title)));\n            $smd_feat_desctile = doSlash((($txt_desc) ? $textile->TextileThis(parse($smd_feat_desc)) : parse($smd_feat_desc)));\n        }\n\n        $smd_feat_title = doSlash($smd_feat_title);\n        $smd_feat_desc = doSlash($smd_feat_desc);\n        $ret = safe_upsert(SMD_FEAT, \"label=\'$smd_feat_label\', feat_position=\'$smd_feat_position\', feat_title=\'$smd_feat_title\', feat_title_html=\'$smd_feat_titletile\', description=\'$smd_feat_desc\', desc_html=\'$smd_feat_desctile\'\", \"feat_id=\'$smd_feat_id\'\");\n        unset($_POST[\'smd_feat_id\']);\n    }\n\n    smd_featured_list(gTxt(\'smd_feat_saved\'));\n}\n\n// ------------------------\n// Create an empty entry in the featured table or destroy it\nfunction smd_featured_tagit()\n{\n    global $smd_featured_event;\n    extract(doSlash(gpsa(array(\'smd_feat_id\', \'smd_feat_action\'))));\n\n    assert_int($smd_feat_id);\n\n    if ($smd_feat_action == \'add\') {\n        $ret = safe_upsert(SMD_FEAT, \"label=\'\'\", \"feat_id=\'$smd_feat_id\'\");\n    } else if ($smd_feat_action == \'remove\') {\n        $ret = safe_delete(SMD_FEAT, \"feat_id=\'$smd_feat_id\'\");\n    }\n}\n\n// -------------------------------------------------------------\n// Stash the position against the given featured item\nfunction smd_featured_store_pos()\n{\n    $id = gps(\'smd_feat_id\');\n    assert_int($id);\n\n    $id = doSlash($id);\n    $pos = doSlash(gps(\'smd_feat_pos\'));\n\n    $exists = safe_row(\'*\', SMD_FEAT, \"feat_id=$id\");\n    if ($exists) {\n        safe_update(SMD_FEAT, \"feat_position=\'$pos\'\", \"feat_id=$id\");\n        send_xml_response();\n    }\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_prefsave()\n{\n    global $smd_featured_pref_privs, $txp_user;\n\n    // Three different types of pref can be stored: see below for details\n    $stdprefs = array(\n        PREF_GLOBAL => array(\n            \'smd_featured_section_list\' => \'smd_feat_section_list\',\n        ),\n        PREF_PRIVATE => array(\n            \'smd_featured_display\' => \'smd_feat_display\',\n            \'smd_featured_section_list\' => \'smd_feat_section_list\',\n        )\n    );\n    $joinprefs = array(\n        PREF_PRIVATE => array(\n            // Index is the pref name; First item in array is the join string\n            \'smd_featured_box_size\' => array(\'x\', \'smd_feat_box_x\', \'smd_feat_box_y\'),\n            \'smd_featured_sort\' => array(\' \', \'smd_feat_sort\', \'smd_feat_sortdir\'),\n        )\n    );\n    $arrayprefs = array(\n        PREF_GLOBAL => array(\n            \'smd_featured_textile\' => \'smd_feat_textile\',\n            \'smd_featured_show_ui\' => \'smd_feat_show_ui\',\n        )\n    );\n\n    $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n    $rights = array_key_exists($privs, $smd_featured_pref_privs);\n    $perprefs = ($rights) ? $smd_featured_pref_privs[$privs] : array();\n    $preflist = array_merge($smd_featured_pref_privs[\'all\'], $perprefs);\n\n    // Standard prefs are just single widget values that are stored directly\n    foreach ($stdprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                set_pref(doSlash($key), doSlash(gps($val)), \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n\n    // Join prefs are discrete widget values (with different HTML names) that need combining into a single pref value\n    foreach ($joinprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                $joinstr = \'\';\n                $combined = array();\n                foreach ($val as $idx => $item) {\n                    if ($idx==0) {\n                        $joinstr = $item;\n                    } else {\n                        $combined[] = doSlash(gps($item));\n                    }\n                }\n                set_pref(doSlash($key), join($joinstr, $combined), \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n\n    // Array prefs are widget values from combined checkboxes under the same HTML name.\n    // They may not be presented as arrays if only one item is checked\n    foreach ($arrayprefs as $type => $prfs) {\n        foreach ($prfs as $key => $val) {\n            if (in_array($key, $preflist)) {\n                $joinstr = \',\';\n                $val = doSlash(gps($val));\n                $combined = join($joinstr, ((is_array($val)) ? $val : array($val)));\n                set_pref(doSlash($key), $combined, \'smd_featured\', PREF_HIDDEN, \'text_input\', 0, $type);\n            }\n        }\n    }\n    smd_featured_list(gTxt(\'preferences_saved\'));\n}\n\n// -------------------------------------------------------------\nfunction smd_featured_save_pane_state()\n{\n    $panes = array(\'smd_featured_cpanel\');\n    $pane = gps(\'pane\');\n    if (in_array($pane, $panes))\n    {\n        set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_featured\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n// ------------------------\n// Add featured table if not already installed\nfunction smd_featured_table_install($showpane = \'1\')\n{\n    $GLOBALS[\'txp_err_count\'] = 0;\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_FEAT.\"` (\n        `feat_id`         int(8)       NOT NULL default 0,\n        `label`           varchar(32)  NULL     default \'\',\n        `feat_position`   varchar(16)  NULL     default \'\',\n        `feat_title`      varchar(255) NULL     default \'\',\n        `feat_title_html` varchar(255) NULL     default \'\',\n        `description`     varchar(255) NULL     default \'\',\n        `desc_html`       varchar(255) NULL     default \'\',\n        PRIMARY KEY (`feat_id`)\n    ) ENGINE=MyISAM\";\n\n    if (gps(\'debug\')) {\n        dmp($sql);\n    }\n\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Spit out results\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        if ($showpane) {\n            $message = gTxt(\'smd_feat_tbl_installed\');\n            smd_featured_list($message);\n        }\n    } else {\n        if ($showpane) {\n            $message = gTxt(\'smd_feat_tbl_not_installed\');\n            smd_featured_list($message);\n        }\n    }\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_featured_table_remove()\n{\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    if (smd_featured_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_FEAT. \"; \";\n        if(gps(\'debug\')) {\n            dmp($sql);\n        }\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_feat_tbl_removed\');\n    } else {\n        $message = gTxt(\'smd_feat_tbl_not_removed\');\n        smd_featured_list($message);\n    }\n}\n\n// ------------------------\n// Handle upgrades from previous versions.\nfunction smd_featured_table_upgrade()\n{\n    global $DB;\n\n    $varCharSize = (version_compare($DB->version, \'5.0.3\', \'>=\') ? \'16384\' : \'255\');\n    $colInfo = getRows(\'describe `\'.PFX.SMD_FEAT.\'`\');\n    $cols = array();\n    $descTypes = array();\n\n    foreach ($colInfo as $row) {\n        $cols[] = $row[\'Field\'];\n\n        if (in_array($row[\'Field\'], array(\'description\', \'desc_html\'))) {\n            $descTypes[$row[\'Field\']] = $row[\'Type\'];\n        }\n    }\n\n    if (!in_array(\'feat_id\', $cols)) {\n        safe_alter(SMD_FEAT, \"CHANGE `id` `feat_id` int( 8 ) NOT NULL DEFAULT \'0\'\");\n    }\n    if (!in_array(\'feat_title\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_title` varchar( 255 ) NULL DEFAULT \'\' AFTER `label`\");\n    }\n    if (!in_array(\'feat_title_html\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_title_html` varchar( 255 ) NULL DEFAULT \'\' AFTER `feat_title`\");\n    }\n    if (!in_array(\'feat_position\', $cols)) {\n        safe_alter(SMD_FEAT, \"ADD `feat_position` varchar ( 16 ) NULL DEFAULT \'\' AFTER `label`\");\n    }\n    if ($descTypes[\'description\'] === \'text\') {\n        safe_alter(SMD_FEAT, \"MODIFY `description` varchar (\" . $varCharSize . \") NULL\");\n    }\n    if ($descTypes[\'desc_html\'] === \'text\') {\n        safe_alter(SMD_FEAT, \"MODIFY `desc_html` varchar (\" . $varCharSize . \") NULL\");\n    }\n}\n\n// ------------------------\nfunction smd_featured_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_FEAT => 7);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n        return ($out === 0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_FEAT.\" HAS \".count(@safe_show(\'columns\', SMD_FEAT)).\" COLUMNS;\";\n        }\n        return (@safe_show(\'columns\', SMD_FEAT));\n    }\n}\n\n// ****************\n// PUBLIC SIDE TAGS\n// ****************\n// ------------------------\nfunction smd_featured($atts, $thing)\n{\n    global $smd_featured_info, $smd_prior_featured, $prefs;\n\n    extract(lAtts(array(\n        \'label\'    => \'\',\n        \'unlabel\'  => \'Featured\',\n        \'labeltag\' => \'\',\n        \'time\'     => \'past\',\n        \'status\'   => \'4,5\',\n        \'section\'  => \'\',\n        \'history\'  => \'1\',\n        \'limit\'    => \'10\',\n        \'sort\'     => \'feat_position asc, Posted desc\',\n        \'form\'     => \'\',\n        \'wraptag\'  => \'\',\n        \'break\'    => \'\',\n        \'class\'    => \'\',\n        \'html_id\'  => \'\',\n        \'debug\'    => \'\',\n    ),$atts));\n\n    $unlabel = trim($unlabel);\n    // Use isset() because unlabelled articles ($label=\"\") is a valid user option\n    if (isset($atts[\'label\'])) {\n        $label = trim($label);\n        if ($label) {\n            $where = \"BINARY label REGEXP \'\".$label.\"\'\";\n        } else {\n            $where = \"label=\'\'\";\n        }\n        unset($atts[\'label\']);\n    } else {\n        // If no label attribute has been given, treat this as \'all featured items\'\n        $ids = safe_column(\'feat_id\', SMD_FEAT, \'1=1\');\n        $ids = join(\"\',\'\", doSlash($ids));\n        $where = \"smdfeat.feat_id IN (\'$ids\')\";\n    }\n\n    // Exclude previously seen articles\n    if ($history && !empty($smd_prior_featured)) {\n        $where .= \' AND smdfeat.feat_id NOT IN(\' . join(\',\', $smd_prior_featured) . \')\';\n    }\n\n    // NOTE: time is left in the $atts array and passed to article_custom. Otherwise the default\n    // time value (past) will be used\n    if ($time) {\n        switch ($time) {\n            case \'any\':\n                break;\n            case \'future\':\n                $where .= \" AND Posted > now()\";\n                break;\n            default:\n                $where .= \" AND Posted <= now()\";\n        }\n        if (!$prefs[\'publish_expired_articles\']) {\n            $where .= \" AND (now() <= Expires OR Expires IS NULL)\";\n        }\n    }\n\n    if ($status) {\n        $where .= \' AND Status IN (\'.join(\',\', do_list($status)).\')\';\n        unset($atts[\'status\']);\n    }\n\n    if ($section) {\n        $where .= \" AND Section IN (\'\".join(\"\',\'\", do_list($section)).\"\')\";\n        unset($atts[\'section\']);\n    }\n\n    if ($sort) {\n        $where .=\' ORDER BY \'.$sort;\n        unset($atts[\'sort\']);\n    }\n\n    // Leave limit in the $atts array too so it doesn\'t default to article_custom\'s value if set here\n    if ($limit) {\n        $where .=\' LIMIT \'.$limit;\n    }\n\n    // Don\'t pass the remaining attributes we\'ve already handled onto the article_custom tag\n    unset(\n        $atts[\'label\'],\n        $atts[\'unlabel\'],\n        $atts[\'labeltag\'],\n        $atts[\'wraptag\'],\n        $atts[\'break\'],\n        $atts[\'class\'],\n        $atts[\'history\'],\n        $atts[\'html_id\'],\n        $atts[\'debug\']\n    );\n\n    if ($debug) {\n        echo \'++ WHERE ++\';\n        dmp($where);\n    }\n\n    $rs = getRows(\'SELECT *, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod FROM \'.PFX.\'textpattern AS txp LEFT JOIN \'.PFX.SMD_FEAT.\' AS smdfeat ON txp.ID=smdfeat.feat_id WHERE \'.$where, $debug);\n\n    if ($debug > 1 && $rs) {\n        echo \'++ RECORD SET ++\';\n        dmp($rs);\n    }\n\n    $truePart = EvalElse($thing, 1);\n    $falsePart = EvalElse($thing, 0);\n\n    $out = array();\n    if ($rs) {\n        foreach ($rs as $row) {\n            $smd_featured_info[\'label\'] = $row[\'label\'];\n            $smd_featured_info[\'position\'] = $row[\'feat_position\'];\n            $smd_featured_info[\'title\'] = $row[\'feat_title_html\'];\n            $smd_featured_info[\'description\'] = $row[\'desc_html\'];\n            $atts[\'id\'] = $row[\'ID\'];\n\n            $artout = article_custom($atts, $truePart);\n\n            if ($artout) {\n                $smd_prior_featured[] = $row[\'ID\'];\n                $out[] = $artout;\n            }\n        }\n    } else {\n        return parse($falsePart);\n    }\n    if ($out) {\n        return (($labeltag) ? doLabel( ( ($label == \'\') ? $unlabel : $label), $labeltag ) : \'\')\n            .doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n    }\n    return \'\';\n}\n\n// ------------------------\nfunction smd_unfeatured($atts, $thing)\n{\n    global $smd_prior_featured, $thispage, $pretext;\n\n    $time = (isset($atts[\'time\'])) ? $atts[\'time\'] : \'\';\n    $status = (isset($atts[\'status\'])) ? $atts[\'status\'] : 4;\n    $section = (isset($atts[\'section\'])) ? $atts[\'section\'] : \'\';\n    $history = (isset($atts[\'history\'])) ? $atts[\'history\'] : \'1\';\n    unset($atts[\'history\']);\n\n    $where = \"1=1\";\n\n    if ($time) {\n        switch ($time) {\n            case \'any\':\n                break;\n            case \'future\':\n                $where .= \" AND Posted > now()\";\n                break;\n            default:\n                $where .= \" AND Posted <= now()\";\n        }\n        if (!$prefs[\'publish_expired_articles\']) {\n            $where .= \" AND (now() <= Expires OR Expires IS NULL)\";\n        }\n    }\n\n    if ($status) {\n        $where .= \' AND Status IN (\'.join(\',\', do_list($status)).\')\';\n    }\n\n    if ($section) {\n        $where .= \" AND Section IN (\'\".join(\"\',\'\", do_list($section)).\"\')\";\n    }\n\n    // Exclude previously seen articles\n    if ($history && !empty($smd_prior_featured)) {\n        $where .= \' AND id NOT IN(\' . join(\',\', $smd_prior_featured) . \')\';\n    }\n\n    $offset = (isset($atts[\'offset\'])) ? $atts[\'offset\'] : 0;\n\n    if (isset($atts[\'limit\']) && isset($atts[\'pageby\'])) {\n        $limit = $atts[\'limit\'];\n        $pageby = $atts[\'pageby\'];\n        $pageby = ($pageby == \'limit\') ? $limit : $pageby;\n        $pg = gps(\'pg\');\n\n        $grand_total = safe_count(\'textpattern\',$where);\n        $total = $grand_total - $offset;\n        $numPages = ceil($total/$pageby);\n        $pg = (!$pg) ? 1 : $pg;\n        $pgoffset = $offset + (($pg - 1) * $pageby);\n        // send paging info to txp:newer and txp:older\n        $pageout[\'pg\']          = $pg;\n        $pageout[\'numPages\']    = $numPages;\n        $pageout[\'s\']           = $pretext[\'s\'];\n        $pageout[\'c\']           = $pretext[\'c\'];\n        $pageout[\'context\']     = \'article\';\n        $pageout[\'grand_total\'] = $grand_total;\n        $pageout[\'total\']       = $total;\n\n        if (empty($thispage)) {\n            $thispage = $pageout;\n        }\n    } else {\n        $pgoffset = $offset;\n    }\n\n    $truePart = EvalElse($thing, 1);\n    $falsePart = EvalElse($thing, 0);\n\n    $rs = safe_column(\'ID\', \'textpattern\', $where);\n    if ($rs) {\n        $atts[\'offset\'] = $pgoffset;\n        $atts[\'id\'] = join(\',\', $rs);\n        $out = article_custom($atts, $truePart);\n        return $out;\n    } else {\n        return parse($falsePart);\n    }\n}\n\n// ------------------------\nfunction smd_featured_info($atts)\n{\n    global $smd_featured_info;\n\n    extract(lAtts(array(\n        \'item\' => \'\',\n    ),$atts));\n\n    return (isset($smd_featured_info[$item]) ? $smd_featured_info[$item] : \'\');\n}\n\n// ------------------------\nfunction smd_if_featured($atts, $thing)\n{\n    global $smd_featured_info, $thisarticle;\n\n    extract(lAtts(array(\n        \'id\' => \'\',\n        \'label\' => \'\',\n    ),$atts));\n\n    if ($id) {\n        $id = join(\"\',\'\", do_list(doSlash($id)));\n    }\n\n    if (!$id && $thisarticle) {\n        $id = $thisarticle[\'thisid\'];\n    }\n\n    $where[] = \"feat_id IN (\'\".$id.\"\')\";\n\n    if (isset($atts[\'label\'])) {\n        $label = explode(\',\', doSlash(trim($label)));\n        $lblwhere = array();\n        foreach ($label as $lbl) {\n            $lblwhere[] = \"\'\".trim($lbl).\"\'\";\n        }\n        if ($lblwhere) {\n            $where[] = \'( BINARY label IN (\' . join(\',\', $lblwhere) . \') )\';\n        }\n    }\n\n    $ret = safe_row(\'feat_id\', SMD_FEAT, join(\' AND\', $where));\n\n    return parse(EvalElse($thing, $ret));\n}', '877ecfd589cbfa1ade8bc0ec112fad8c', '', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_if', 0, 'Stef Dawson', 'http://stefdawson.com/', '1.0.0', 'Generic multiple if condition tests', '<div id=\"smd_help\">\n\n	<h1>smd_if</h1>\n\n	<p>A generic &#8216;if condition&#8217; tester. Can test any field or variable in the current article, file, image, link, <span class=\"caps\">URL</span> var, <code>&lt;txp:variable /&gt;</code> or <span class=\"caps\">PHP</span> context for a variety of attributes and take action if <span class=\"caps\">TRUE</span> or <span class=\"caps\">FALSE</span>.</p>\n\n	<h2>Features</h2>\n\n	<ul>\n		<li>Supports most major article, file, image and link variables such as section, category, custom fields, id, query string, author, body, excerpt, yahde yahde, plus url vars, server vars, txp vars, php vars, and sub-category/parent checking</li>\n		<li>Tests include equality, inequality, less than, greater than, divisible by, empty, used, defined, begins, ends, contains, is numeric / alpha / alphanumeric / lowercase / uppercase, among others</li>\n		<li>Tests for more than one condition at once and applies either <span class=\"caps\">AND</span> logic (all tests must pass) or OR logic (any test must pass)</li>\n		<li>All tested fields and values are available to the container so you can display them</li>\n		<li>Custom regular-expression filters are available to help weed out bad data</li>\n		<li>Ugly and very dirty. Uses <span class=\"caps\">PHP</span>&#8217;s <code>eval()</code> command which most programmers concur should be renamed <code>evil()</code></li>\n	</ul>\n\n	<h2>Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/930/smd_if\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp <em>Admin-&gt;Plugins</em> pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=25357\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete from the <em>Admin-&gt;Plugins</em> page.</p>\n\n	<h2>Usage</h2>\n\n	<p>Use the tag in any page, form or article context. Can also be used inside file, image, or link lists to take action depending on attributes of the current item.</p>\n\n	<h3 class=\"tag\">smd_if</h3>\n\n	<p class=\"tag-summary\">At the place you wish to compare a field with another value, put this tag with at least the <code>field</code> attribute. Specify each field you wish to test as a series of comma-separated items &#8212; though the comma is overridable with <code>param_delim</code>. If the result of all the comparison tests is <span class=\"caps\">TRUE</span> the contained block will be executed. If the result is <span class=\"caps\">FALSE</span>, any <code>&lt;txp:else /&gt;</code> will be executed instead. Without any <code>&lt;txp:else /&gt;</code> block, nothing is output if the result is <span class=\"caps\">FALSE</span>.</p>\n\n	<p><strong>Attributes</strong></p>\n\n	<dl>\n		<dt><span class=\"atnm mand\">field</span></dt>\n		<dd>Comma-separated list of <a href=\"#field\">fields</a> to test.</dd>\n		<dt><span class=\"atnm\">operator</span></dt>\n		<dd>Comma-separated list of <a href=\"#operator\">operations</a> for comparison with the corresponding field (e.g. <code>eq</code>, <code>not</code>, <code>begins</code>, etc).</dd>\n		<dd>Default: <code>eq</code> for most tests, <code>contains</code> for parent tests</dd>\n		<dt><span class=\"atnm\">value</span></dt>\n		<dd>Comma-separated list of <a href=\"#value\">values</a> with which to compare the corresponding fields.</dd>\n		<dt><span class=\"atnm\">logic</span></dt>\n		<dd>How multiple tests are joined: Choose from:\n	<dl>\n		<dd><code>and</code>: all conditions must be met for a <span class=\"caps\">TRUE</span> result.</dd>\n		<dd><code>or</code>: any of the conditions that match will give a <span class=\"caps\">TRUE</span> result.</dd>\n	</dl></dd>\n		<dd>Default: <code>and</code></dd>\n		<dt><span class=\"atnm\">case_sensitive</span></dt>\n		<dd>Whether to perform case-sensitive comparisons. Note that if using <code>islower</code> or <code>isupper</code> in a comparison, case sensitivity will automatically be switched on while the test is taking place. Values:\n	<dl>\n		<dd>1 = yes</dd>\n		<dd>0 = no</dd>\n	</dl></dd>\n		<dd>Default: 0</dd>\n		<dt><span class=\"atnm\">filter</span></dt>\n		<dd>List of regular expressions with which to filter one or more fields/values. See <a href=\"#filtering\">filtering</a>.</dd>\n		<dt><span class=\"atnm\">replace</span></dt>\n		<dd>List of items with which to replace each of the matching filters.</dd>\n		<dt><span class=\"atnm\">filter_type</span></dt>\n		<dd>Limit the filter to certain types of test. Choose from any of:\n	<dl>\n		<dd><code>all</code></dd>\n		<dd><code>urlvar</code></dd>\n		<dd><code>postvar</code></dd>\n		<dd><code>svrvar</code></dd>\n		<dd><code>txpvar</code></dd>\n		<dd><code>phpvar</code></dd>\n		<dd>or any field name</dd>\n	</dl></dd>\n		<dd>Default: <code>all</code></dd>\n		<dt><span class=\"atnm\">filter_on</span></dt>\n		<dd>Apply the filter to either the <code>field</code>, the <code>value</code> or both. Please see the <a href=\"#fcaveat\">filtering caveat</a> below for important information about this attribute.</dd>\n		<dd>Default: <code>field</code></dd>\n		<dt><span class=\"atnm\">param_delim</span></dt>\n		<dd>Delimiter used between each field, operator or value in a multi-test. You normally only need to change this if you have used that character in the name of a custom field, for example.</dd>\n		<dd>Default:  comma (,)</dd>\n		<dt><span class=\"atnm\">mod_delim</span></dt>\n		<dd>Delimiter used when specifying field or operator modifiers for:\n	<dl>\n		<dd>urlvar, postvar, svrvar, txpvar</dd>\n		<dd>parent from <code>LVL</code> and <code>CAT</code></dd>\n		<dd>separating an operator from the <span class=\"caps\">NUM</span> or <span class=\"caps\">NOSPACE</span> modifiers</dd>\n	</dl></dd>\n		<dd>Default: colon (:)</dd>\n		<dt><span class=\"atnm\">list_delim</span></dt>\n		<dd>Delimiter used when specifying a list of values to check via the <code>in</code>, <code>notin</code>, <code>between</code> and <code>range</code> operators.</dd>\n		<dd>Default: forward-slash (/)</dd>\n		<dt><span class=\"atnm\">var_prefix</span></dt>\n		<dd>Prefix any replacement variable names with this string. If nesting smd_if tags, you will probably need to specify this on any inner smd_if tags to prevent the {replacement} variables clashing.</dd>\n		<dd>Default: <code>smd_if_</code></dd>\n	</dl>\n\n	<p>The lists are processed in order, i.e. the 1st field uses the 1st operator in the list and compares it to the 1st value; the 2nd field uses the 2nd operator and compares it to the 2nd value, and so on. Values should usually be specified with placeholders to maintain order: e.g. <code>value=&quot;, 4,, top&quot;</code></p>\n\n	<p>Note that, although the first three attributes are usually mandatory, if you happen to require the default operator for all your tests, you can safely omit <code>operator</code>. Similarly with values: if you are entirely testing the existence or type of variables, you can omit the <code>value</code> parameter if you wish. And if you are testing the same <code>field</code> again and again for differing conditions, you can list it just once as a convenient shortcut.</p>\n\n	<h3 class=\"atnm \" id=\"field\">field</h3>\n\n	<p>List of field locations to look at. A non-exhaustive list of some useful values are:</p>\n\n	<ul>\n		<li><code>s</code> (global section) or <code>section</code> (article section)</li>\n		<li><code>c</code> (global category) or <code>category1</code>, or <code>category2</code> if on an article page</li>\n		<li><code>authorid</code> or <code>author</code></li>\n		<li><code>id</code> (file/link/image/article ID). In an individual article context, use <code>thisid</code> instead</li>\n		<li><code>query</code> (the query string from the search form)</li>\n		<li><code>pg</code> (the current page number in lists)</li>\n		<li><code>month</code> (current month field from the address bar)</li>\n		<li><code>status</code> (document status: 200, 404, 403, etc)</li>\n		<li><code>page</code> (the Txp Page template used by this section)</li>\n		<li><code>next_id</code> / <code>prev_id</code> (ID of next/prev document if on article page)</li>\n		<li><code>next_title</code> / <code>prev_title</code>(Title of next/prev document if on article page)</li>\n		<li><code>next_utitle</code> / <code>prev_utitle</code> (url-title of next/prev document if on article page)</li>\n		<li><code>permlink_mode</code> (take action based on one of the messy/clean <span class=\"caps\">URL</span> schemes)</li>\n		<li><code>other article/file/link field</code> (e.g. body, excerpt, article_image, keywords, linkanme, filename, downloads, &#8230;)</li>\n		<li><code>my_custom_field_name</code> (any custom fields you have defined)</li>\n		<li><code>urlvar:var_name</code> (any variable in the address bar after the <code>?</code>)</li>\n		<li><code>postvar:var_name</code> (any variable posted from an <span class=\"caps\">HTML</span> form)</li>\n		<li><code>svrvar:var_name</code> (any standard server variable, e.g. <span class=\"caps\">HTTP</span>_USER_AGENT)</li>\n		<li><code>txpvar:my_var</code> (any Textpattern variable set with <code>&lt;txp:variable name=&quot;my_var&quot; /&gt;</code>)</li>\n		<li><code>phpvar:my_var</code> (any <span class=\"caps\">PHP</span> variable <em>in the global scope</em>)</li>\n		<li><code>parent:optional_modifiers</code> (whether the given category is a descendent of another category)</li>\n		<li><code>NULL</code> (useful when comparing arbitrary values for emptiness)</li>\n	</ul>\n\n	<p>If you specify a field name that does not exist, the text you use will be taken verbatim in most cases.</p>\n\n	<p>To avoid ambiguity you can prefix the field name with one of <code>pretext</code>, <code>article</code>, <code>image</code>, <code>link</code> or <code>file</code>, separating it from the field by <code>param_delim</code>.</p>\n\n	<p>If you wish to compare a field that might contain <span class=\"caps\">HTML</span> (e.g <code>body</code>), add the modifier <code>:NOTAGS</code> to the end of the field. It will have its <span class=\"caps\">HTML</span> and <span class=\"caps\">PHP</span> tags stripped from it and will also be trimmed to remove leading and trailing spaces. You may choose to solely remove spaces from both the start and end of any field by adding <code>:TRIM</code> to the end of the field.</p>\n\n	<p>If you suspect a field might contain <span class=\"caps\">HTML</span>ish input like <code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code> or quotes/apostrophes you can elect to convert them to entities like <code>&amp;lt;</code>, <code>&amp;quot;</code>, etc. Just specify the <code>:ESC</code> modifier to replace everything except apostrophes with its entity equivalent, or use <code>:ESCALL</code> to include apostrophes.</p>\n\n	<p>The special field <code>parent</code> checks the parent category for a match. Unlike the other field types, the default <a href=\"#operator\">operator</a> for parent is &#8216;contains&#8217;. This is because the entire tree is checked for a match, starting from the top of the tree down to the current category. Internally, the plugin makes up a &#8220;breadcrumb trail&#8221; of categories in the current branch, each separated by a space, so testing for equality would require putting them all in the <a href=\"#value\">value</a> parameter.</p>\n\n	<p>You are of course free to choose an alternative operator; <code>begins</code> is very useful for testing if the top level category matches the one given in the <code>value</code> field.</p>\n\n	<p>If you use <code>parent:LVLn</code>, the comparison will be restricted to that &#8220;level&#8221; of sub-category; LVL1 is the &#8220;top&#8221; level, LVL2 is the next sub-category level, and so on. When using these modifiers, the &#8216;eq&#8217; operator becomes more useful because you are comparing a single parent category.</p>\n\n	<p>If you wish to compare against the category&#8217;s title instead of its name, add the <code>:TTL</code> modifier. To test the number of children the given category has, specify <code>:KIDS</code>. Note that <span class=\"caps\">TTL</span> and <span class=\"caps\">KIDS</span> are mutually exclusive and if they are both employed, the last one used takes priority.</p>\n\n	<p>When using articles, you can further modify the behaviour of the parent using the <code>CATn</code> syntax (where &#8216;n&#8217; is 1 or 2). Specifying &#8220;parent&#8221; without <code>CATn</code> will use the global category (<code>?c=</code>). If you add <code>:CATn</code> it will instead compare the article&#8217;s category1 or category2 respectively.</p>\n\n	<p>You can use <span class=\"caps\">CAT</span>, <span class=\"caps\">LVL</span> and <span class=\"caps\">TTL</span>/KIDS in combination, independently or not at all. This allows comparisons such as &#8220;if the 2nd sub-category of category1 equals blahblah&#8221; or &#8220;if category2 is a child of blahblah&#8221;. See <a href=\"#eg4\">Example 4</a>.</p>\n\n	<p>One other special field is <code>NULL</code>. This is exactly what it says it is: empty. The reason for its inclusion is that sometimes you wish to test something that isn&#8217;t a true variable &#8212; e.g. a replacement variable from smd_vars or smd_each &#8212; to see if it&#8217;s empty or not.</p>\n\n	<p>If you were to put this:</p>\n\n	<p><code>&lt;txp:smd_if field=&quot;{result}&quot; operator=&quot;isempty&quot;&gt;</code></p>\n\n	<p>you would not get the result you expect (it&#8217;s pretty esoteric but it revolves around the fact that <code>&quot;&quot;</code> (as a variable name) is not empty, it&#8217;s invalid). To get round this you may use <span class=\"caps\">NULL</span> as a placeholder and move the thing you want to check into the <code>value</code> instead, e.g:</p>\n\n	<p><code>&lt;txp:smd_if field=&quot;NULL&quot; operator=&quot;eq&quot; value=&quot;{result}&quot;&gt;</code></p>\n\n	<p>will test the <span class=\"caps\">NULL</span> object (i.e. &#8216;emptiness&#8217;) to see if it&#8217;s equal to the <code>{result}</code> replacement variable. You can use similar logic to test for optional variables by swapping the field and value, like this:</p>\n\n	<p><code>&lt;txp:smd_if field=&quot;7&quot; operator=&quot;gt&quot; value=&quot;{result}&quot;&gt;</code></p>\n\n	<p>That would see if the replacement variable <code>{result}</code> was less than or equal to 7 (that&#8217;s not a typo, the logic is reversed in this case: it is interpreted as: &#8220;is 7 greater than {result}&#8221;, which is the same as &#8220;is {result} less than or equal to 7&#8221;!)</p>\n\n	<h3 class=\"atnm \" id=\"operator\">operator</h3>\n\n	<p>List of operators to apply, in order, to each field. Choose from:</p>\n\n	<ul>\n		<li><code>eq</code> Equal (the default for all except &#8216;parent&#8217;)</li>\n		<li><code>not</code> Not equal</li>\n		<li><code>lt</code> Less than</li>\n		<li><code>gt</code> Greater than</li>\n		<li><code>le</code> Less than or equal to</li>\n		<li><code>ge</code> Greater than or equal to</li>\n		<li><code>in</code> Field is one of a list of values (use <code>list_delim</code> to separate values)</li>\n		<li><code>notin</code> Field is not one of the given list of values (use <code>list_delim</code> to separate values)</li>\n		<li><code>divisible</code> Field is exactly divisible by the value</li>\n		<li><code>between</code> Field lies between the given values, exclusive (use <code>list_delim</code> to separate values)</li>\n		<li><code>range</code> Field is within the range of given values, inclusive (use <code>list_delim</code> to separate values)</li>\n		<li><code>begins</code> Field begins with a sequence of characters</li>\n		<li><code>contains</code> Field contains a sequence of characters (default for &#8216;parent&#8217;)</li>\n		<li><code>ends</code> Field ends with a sequence of characters</li>\n		<li><code>isempty</code> Field is empty (contains nothing)</li>\n		<li><code>isused</code> Field has some value</li>\n		<li><code>defined</code> Field is set (useful with urlvar variables)</li>\n		<li><code>undefined</code> Field is not set, or missing from the <span class=\"caps\">URL</span> line</li>\n		<li><code>isnum</code> Field is a number</li>\n		<li><code>isalpha</code> Field contains characters only</li>\n		<li><code>isalnum</code> Field contains alphanumeric characters only</li>\n		<li><code>islower</code> Every character in the field is lower case</li>\n		<li><code>isupper</code> Every character in the field is upper case</li>\n		<li><code>ispunct</code> Every character in the field is some punctuation mark</li>\n		<li><code>isspace</code> Every character in the field is a whitespace character (or tab, newline, etc)</li>\n	</ul>\n\n	<p>With the comparison operators (primarily gt, lt, ge, le) you may find odd behaviour when comparing numbers. For example, <code>urlvar:pic gt 6</code> will return <span class=\"caps\">TRUE</span> if <code>pic</code> is set to &#8220;fred&#8221;. This is because the word &#8220;fred&#8221; (or at least the &#8220;character f&#8221;) is greater in the <span class=\"caps\">ASCII</span> table than the &#8220;character 6&#8221;.</p>\n\n	<p>To circumvent this problem, you may append <code>:NUM</code> to the end of any of these operators to force the plugin to check that the values are integers.</p>\n\n	<p>If you wish to compare the length of a field, append <code>:LEN</code> to any of the numerical comparison operators. It doesn&#8217;t make sense to use both <code>:NUM</code> and <code>:LEN</code> together, so they are mutually exclusive.</p>\n\n	<p>If you wish to compare the quantity (count) of a list of items in a field, append <code>:COUNT</code> to any of the numerical comparison operators. Again, this option is mutually exclusive with all other modifiers.</p>\n\n	<p>There is a subtle difference between the operators <code>between</code> and <code>range</code>; the former does not include the endpoint values you specify whereas the latter does. For example, the value <code>10</code> is in the range <code>10/20</code> but it is <em>not</em> between <code>10/20</code>. The value <code>11</code>, however, satisfies both. You can use the <code>:NUM</code> modifier for these two operators, though most tests will work fine without it, and you may check if list <code>:COUNT</code> values lie within two endpoints. You can also compare non-integer values with these operators.</p>\n\n	<p>The <code>begins</code>, <code>ends</code> and <code>contains</code> operators, along with any of the <code>is</code> operators (except <code>isspace</code>), can take an extra parameter as well. Since they compare every character against the given behaviour, space characters can mess things up a bit. For example <code>field=&quot;custom1&quot; operator=&quot;islower&quot;</code> will fail if custom1 contains &#8220;this is a test&#8221;. Or comparing something to <code>body</code> can fail because the body often starts with a number of space characters. To circumvent this, add <code>:NOSPACE</code> to the operator which will remove all spaces from the string before testing it.</p>\n\n	<p>Note also that while <code>defined</code> and <code>undefined</code> differ semantically from <code>isused</code> and <code>isempty</code> (respectively), the way Txp assigns variables means that, for the most part, the terms are interchangeable. When dealing with urlvars, postvars and svrvars, the two sets of operators behave independently, as you would expect. See <a href=\"#eg5\">Example 5</a> for more. Neither <code>defined</code> nor <code>undefined</code> make sense with <code>parent</code>, so they are forbidden.</p>\n\n	<h3 class=\"atnm \" id=\"value\">value</h3>\n\n	<p>List of values to compare each field in turn to. Can be static values/text or the name of any Txp field, like those given in <a href=\"#field\">field</a> (except &#8220;parent&#8221;).</p>\n\n	<p>To distinguish a Txp field from static text, prefix the field name with <code>?</code>. For example: <code>value=&quot;title&quot;</code> will compare your chosen field against the word &#8220;title&#8221;, whereas <code>value=&quot;?title&quot;</code> will compare your field against the current article&#8217;s title.</p>\n\n	<p>If you wish to compare a value that might contain <span class=\"caps\">HTML</span> (e.g <code>?body</code>), add the modifier <code>:NOTAGS</code> to the end of the value. It will have any <span class=\"caps\">HTML</span> and <span class=\"caps\">PHP</span> tags stripped from it and will also be trimmed to remove leading and trailing spaces. You may choose to solely remove spaces from both the start and end of any value by adding <code>:TRIM</code> to the end of the value.</p>\n\n	<p>If you suspect a value might contain <span class=\"caps\">HTML</span>ish input like <code>&lt;</code>, <code>&gt;</code>, <code>&amp;</code> or quotes/apostrophes you can elect to convert them to entities like <code>&amp;gt;</code>, <code>&amp;quot;</code>, etc. Just specify the <code>:ESC</code> modifier to replace everything except apostrophes with its entity equivalent, or use <code>:ESCALL</code> to include apostrophes.</p>\n\n	<p>Note that you may find using double-quotes in fields gives unexpected results. They are best avoided, or worked around by using <code>contains</code> instead of <code>eq</code>.</p>\n\n	<h3>Replacement tags</h3>\n\n	<p>Every field or value that you refer to in your smd_if tag becomes available within the containing block so you can display its contents if you wish. Most of the time this is not much use but it can be very useful with the <code>in</code> operator or the <code>:LEN</code> modifier. For instance, if you have asked smd_if to test the <span class=\"caps\">URL</span> variable named &#8216;level&#8217; and told it to compare it to the custom field labelled &#8216;allowable_levels&#8217;, two tags become available which you can use within the containing block:</p>\n\n	<ul>\n		<li><code>{smd_if_level}</code> would display the value of the &#8216;level&#8217; <span class=\"caps\">URL</span> variable.</li>\n		<li><code>{smd_if_allowable_levels}</code> would display the contents of the current article&#8217;s custom field.</li>\n	</ul>\n\n	<p>By default the replacement tags are prefixed with <code>smd_if_</code> so they don&#8217;t clash with the ones in smd_gallery (for example, when using smd_if inside an smd_gallery tag). You can change this prefix with the <code>var_prefix</code> attribute.</p>\n\n	<p>If you are comparing a fixed-value field (such as <code>field=&quot;NULL&quot;</code> or <code>value=&quot;12&quot;</code> or smd_gallery&#8217;s <code>value=&quot;{category}&quot;</code>) the name of the replacement tags are <code>{smd_if_fieldN}</code> for fields and <code>{smd_if_valN}</code> for values, where N is the test number starting from 1.</p>\n\n	<p>If you use the multiple value options such as <code>in</code>, <code>between</code>, <code>range</code>, etc you will also see replacement tags of the following format: <code>{smd_if_valN_X}</code> where N is the value counter (as above) and X is an incrementing number; with one for each value in your list. For example, <code>value=&quot;10/20/30&quot;</code> sets:</p>\n\n	<ul>\n		<li><code>{ smd_if_val1_1 }</code> = 10</li>\n		<li><code>{ smd_if_val1_2 }</code> = 20</li>\n		<li><code>{ smd_if_val1_3 }</code> = 30</li>\n	</ul>\n\n	<p>There are also &#8216;length&#8217; replacement tags. Following a similar convention to above, these are prefixed with <code>smd_if_len_</code>. If you get stuck, temporarily switch <code>debug=&quot;1&quot;</code> on to see the replacements available and their associated names/values.</p>\n\n	<p>See <a href=\"#eg8\">Example 8</a> and <a href=\"#eg9\">9</a> for more.</p>\n\n	<h2 id=\"filtering\">Filtering</h2>\n\n	<p>All user input is tainted by default.</p>\n\n	<p>Any time you rely on someone to enter something, at least one person will invariably catch you out; either accidentally or maliciously. For this reason, smd_if supports powerful filtering rules so you can trap and remove suspect input. It already does some of this for you with the <span class=\"caps\">NOTAGS</span> modifier, but filtering gives another level of control above that.</p>\n\n	<p>Let&#8217;s say you are asking the user to enter a value and you need to compare it to a range. What if, instead of a number, they entered <code>fr3d</code> or <code>;rm -rf 1*;</code>. The plugin might just fall over (probably with an error, which is not very pleasant for users), or it might cause harm to the filesystem if a person trying to hack your site was skilled enough (it&#8217;s unlikely, but possible).</p>\n\n	<p>In both cases, filtering comes to the rescue. You can specify that you <em>expect</em> only digits from 0 to 9 in your fields and can tell the plugin to chop out anything that is not numeric. So in the first case above, all you would see if the user entered &#8216;fr3d&#8217; would be &#8216;3&#8217;.</p>\n\n	<p>Fortunately &#8212; and unfortunately &#8212; it&#8217;s built around <a href=\"http://www.regular-expressions.info/\">regular expressions</a> which are fiendishly powerful but can also be fiendishly tricky to learn if you are not familiar. They are worth learning.</p>\n\n	<p>Let&#8217;s dive in at the deep end and tell the plugin that, under no circumstances, must we allow any input that is non-numeric:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;highest&quot; value=&quot;100&quot; /&gt;\n&lt;txp:smd_if field=&quot;urlvar:low&quot; operator=&quot;ge, le&quot;\n     value=&quot;1, txpvar:highest&quot; filter=&quot;/[^0-9]+/&quot;&gt;\n   &lt;p&gt;{smd_if_low} is valid and is between\n   {smd_if_val1} and {smd_if_highest}.&lt;/p&gt;\n&lt;txp:else /&gt;\n   &lt;p&gt;Sorry, the value {smd_if_low} is not within the\n     range {smd_if_val1}-{smd_if_highest}.&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>[ Eagle-eyed people may notice that something similar can be achieved with the <code>:NUM</code> modifier. The difference here is that the replacement variables are also filtered, whereas with <code>:NUM</code> they contain the original (possibly invalid) input ]</p>\n\n	<p>Although out of the scope of this documentation, it&#8217;s worth just taking a moment to see what the filter is doing:</p>\n\n<pre><code>/[^0-9]+/\n</code></pre>\n\n	<ul>\n		<li>The forward slashes are start and end delimiters and should always be present (unless you know what you&#8217;re doing!)</li>\n		<li>The square brackets <code>[]</code> denote a character class, or group of characters. In this case they contain the range of digits 0 to 9</li>\n		<li>The circumflex <code>^</code> negates the class (i.e. non-digits)</li>\n		<li>The plus <code>+</code> means &#8216;one or more of the things I&#8217;ve just seen&#8217;</li>\n	</ul>\n\n	<p>So putting it all together, it reads &#8220;Find every occurrence of one or more non-digits&#8221;. Thus it looks at every field and finds anything non-numeric. Then it replaces whatever it finds with <code>&#39;&#39;</code>, i.e. nothing, nada, zip. Effectively, it deletes whatever non-digits it finds and leaves the good stuff (the numbers) behind.</p>\n\n	<h3 id=\"freplacing\">Replacing bad data</h3>\n\n	<p>For each matching filter there&#8217;s an equivalent <code>replace</code> string. By default this is set to <code>replace=&quot;&quot;</code> which means &#8220;replace whatever you find with nothing&#8221;; or in other words &#8220;delete everything that matches the filter&#8221;. You may elect to replace your filtered data with something else, say, <code>replace=&quot;txp&quot;</code>. So if someone entered &#8220;fr3d&#8221; you would see the replacement variable has a value &#8220;txp3txp&#8221; (the reason you only get one &#8216;txp&#8217; before the number is due to the expression being <em>greedy</em> and gobbling up as many characters as it can in a group before replacing them. See any regex tutorial for more on this topic).</p>\n\n	<p>Under normal circumstances you won&#8217;t want to mess with <code>replace</code> as it&#8217;ll do what you want with the default &#8216;delete&#8217; operation.</p>\n\n	<h3 id=\"foptions\">Filtering options</h3>\n\n	<p>By default, the plugin only looks at <code>field</code> data. If you wish to change that, use the <code>filter_in</code> attribute.</p>\n\n	<p>The plugin also applies the filter to all fields (<code>filter_type=&quot;all&quot;</code>). You may wish to only target a filter at url and server vars, in which case you would specify <code>filter_type=&quot;urlvar, svrvar&quot;</code>. Or maybe you wish to validate the article image field in case someone entered some rogue data there: <code>filter_type=&quot;article_image&quot;</code>.</p>\n\n	<h3 id=\"fcaveat\">Filtering caveat</h3>\n\n	<p>If you specify <code>filter_on=&quot;field, value&quot;</code> it is important to note that the <em>same filter</em> will be applied to each corresponding field and value. If your filter is too strict there&#8217;s a chance it may filter every character out of both <code>field</code> and <code>value</code>, thus if your test was for equality the test would return &#8216;true&#8217;. Here&#8217;s an example:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:comp1&quot; operator=&quot;eq&quot;\n     value=&quot;urlvar:comp2&quot; filter=&quot;/[^a-zA-Z]+/&quot;\n     filter_on=&quot;field, value&quot;&gt;\n   // I&#39;m NOT necessarily valid\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>If your user typed in the <span class=\"caps\">URL</span> <code>site.com/article?comp1=12345&amp;comp2=67890</code> the plugin would do the following:</p>\n\n	<ol>\n		<li>Filter comp1 and remove all non-letter characters</li>\n		<li>Filter comp2 and remove all non-letter characters</li>\n		<li>Look at comp1 (which is now empty) and comp2 (which is also empty), then compare them</li>\n	</ol>\n\n	<p>You can see what&#8217;s going to happen: the test result is going to be &#8216;true&#8217; because &#8220;nothing&#8221; does indeed equal &#8220;nothing&#8221;. So the act of the user entering two nonsensical, completely numeric strings of data has broken your logic.</p>\n\n	<p>For this reason, if you are filtering on both field and value, you should perform <em>additional</em> tests to see if either field / value is set at all. This is better:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:comp1, urlvar:comp1, urlvar:comp2&quot;\n     operator=&quot;eq, isused, isused&quot; value=&quot;urlvar:comp2&quot;\n     filter=&quot;/[^a-zA-Z]+/&quot;\n     filter_on=&quot;field, value&quot;&gt;\n   // I&#39;m now actually valid\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>So now, if your filter removes everything from both <span class=\"caps\">URL</span> vars, it still fails the &#8216;has the user entered anything at all&#8217; tests because as far as the plugin is concerned, the visitor has submitted rubbish.</p>\n\n	<h3 id=\"ffurther\">Going further</h3>\n\n	<p>The above examples all use a single filter. You can specify more than one filter and replacement if you wish, just comma-delimit them (unless you&#8217;ve overriden the <code>param_delim</code> of course).</p>\n\n	<p>When you specify more than one filter, they ignore the <code>filter_type</code> attribute because the filters are applied in order; one per test. If you wish to skip a particular field and not apply a filter, simply leave an empty comma as a placeholder, e.g. <code>filter=&quot;, /[^a-zA-Z0-9\\-]+/, , /[^0-9]+/&quot;</code> would apply the respective filters to the 2nd and 4th tests only.</p>\n\n	<h2 id=\"eg1\">Example 1: standard comparison</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;section, id&quot;\n     operator=&quot;begins, gt&quot;\n     value=&quot;lion, 12&quot;&gt;\n &lt;p&gt;The lion sleeps tonight&lt;/p&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Roooooarrrr! *CHOMP*&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>Checks if the current section name begins with the word &#8220;lion&#8221; and the article ID is greater than 12. Displays &#8220;The lion sleeps tonight&#8221; if both conditions are met or displays the text &#8220;Roooooarrrr! <strong><span class=\"caps\">CHOMP</span></strong>&#8221; if not.</p>\n\n	<h2 id=\"eg2\">Example 2: other types of field</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;summary, category1, urlvar:pic&quot;\n     operator=&quot;isused, eq, isnum&quot;\n     value=&quot;, animal ,&quot; &gt;\n  &lt;p&gt;All matched&lt;/p&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Match failed&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>Checks if the custom field labelled &#8220;summary&#8221; has some data in it, checks if category1 equals &#8220;animal&#8221; and tests if the urlvar <code>pic</code> is numeric (e.g. <code>?pic=5</code>).</p>\n\n	<p>If all these conditions are met the &#8220;All matched&#8221; message is displayed, else the &#8220;Match failed&#8221; message is shown. Note that <code>isused</code> and <code>isnum</code> don&#8217;t take arguments for <code>value</code> and their positions are held by empty commas (technically the last comma isn&#8217;t needed but it helps keep everything neat if you add further tests later on).</p>\n\n	<h2 id=\"eg3\">Example 3: using &#8216;or&#8217; logic</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;article_image, svrvar:HTTP_USER_AGENT&quot;\n     operator=&quot;eq, contains&quot;\n     value=&quot;urlvar:pic, Safari&quot;\n     logic=&quot;or&quot;&gt;\n &lt;p&gt;Come into my parlour&lt;/p&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Not today, thanks&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>Compares (for equality) the current article image id with the value of the url variable <code>pic</code> and checks if the value of the <span class=\"caps\">HTTP</span>_USER_AGENT string contains &#8220;Safari&#8221;. This example uses the &#8216;or&#8217; logic, hence if <em>either</em> condition is met the &#8216;come into my parlour&#8217; message is shown, otherwise the &#8216;not today&#8217; message is displayed.</p>\n\n	<h2 id=\"eg4\">Example 4: sub-category testing</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;parent:LVL2&quot;\n     operator=&quot;eq&quot;\n     value=&quot;mammal&quot;&gt;\n &lt;txp:article /&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Not today, thanks&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>On a category list page, this checks the 2nd sub-category of the tree to see if it equals &#8220;mammal&#8221;. If it does, the article is displayed; if not, the message is shown instead. Removing the <code>:LVL2</code> &#8212; which means you can also remove the operator parameter to force the comparison to be the default &#8220;contains&#8221; &#8212; checks if the current (global) category is a child of &#8216;mammal&#8217; at any nesting level.</p>\n\n	<p>Move the example into an article or article Form and change the field to <code>parent:CAT1</code> to see if the article&#8217;s category1 matches &#8216;mammal&#8217; at any level, or use <code>field=&quot;parent:CAT1:LVL2&quot;</code> to combine the checks.</p>\n\n	<h2 id=\"eg5\">Example 5: defined/undefined/isused/isempty</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:pic, urlvar:page&quot;\n     operator=&quot;gt:NUM, undefined&quot;\n     value=&quot;?article_image,&quot;&gt;\n &lt;p&gt;Yes please&lt;/p&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Not today, thanks&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>Tests if the url variable <code>pic</code> is strictly numerically greater than the value in the current article&#8217;s <code>article_image</code> field and that the url variable <code>page</code> is missing from the <span class=\"caps\">URL</span> address. Compare the outcome of this test with the other operators using the following table when testing the <code>page</code> urlvar:</p>\n\n	<table>\n		<tr>\n			<th><span class=\"caps\">URL</span> </th>\n			<th>defined </th>\n			<th>undefined </th>\n			<th>isused </th>\n			<th>isempty </th>\n		</tr>\n		<tr>\n			<td> index.php?pag </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n			<td> <span class=\"caps\">TRUE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n		</tr>\n		<tr>\n			<td> index.php?page= </td>\n			<td> <span class=\"caps\">TRUE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n			<td> <span class=\"caps\">TRUE</span> </td>\n		</tr>\n		<tr>\n			<td> index.php?page=4 </td>\n			<td> <span class=\"caps\">TRUE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n			<td> <span class=\"caps\">TRUE</span> </td>\n			<td> <span class=\"caps\">FALSE</span> </td>\n		</tr>\n	</table>\n\n	<h2 id=\"eg6\">Example 6: short circuiting the <code>field</code></h2>\n\n	<p>Put this inside your <code>plainlinks</code> form and execute a <code>&lt;txp:linklist /&gt;</code> from an article page/form:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;id&quot;\n     operator=&quot;ge:NUM, le:NUM&quot;\n     value=&quot;urlvar:min, urlvar:max&quot;&gt;\n  &lt;txp:linkdesctitle /&gt;&lt;br /&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>That will list only the links that have IDs between the <code>min</code> and <code>max</code> variables specified on the address bar. Notice that the id field is only listed once and each operator is applied to it in turn.</p>\n\n	<h2 id=\"eg7\">Example 7: alphanumeric testing</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:product_code&quot;\n     operator=&quot;isalnum&quot;&gt;\n  &lt;txp:output_form form=&quot;show_product&quot; /&gt;\n&lt;txp:else /&gt;\n &lt;p&gt;Invalid product code&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>Tests to see if the product_code <span class=\"caps\">URL</span> variable is alphanumeric and displays a form if so.</p>\n\n	<h2 id=\"eg8\">Example 8: displaying used values</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:sort_order&quot;\n     operator=&quot;in&quot;\n     value=&quot;id/price/size/colour&quot;&gt;\n  &lt;p&gt;Sorting values by {smd_if_sort_order}&lt;/p&gt;\n  // Do some stuff\n&lt;txp:else /&gt;\n  // Use a default sort, or show an error here\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>By using the replacement tag {smd_if_sort_order} you have plucked the value from the <span class=\"caps\">URL</span> bar and inserted it into the article. Useful when using the <code>in</code> or <code>notin</code> operators because, although you know that the field matched one of the values in your list, you would otherwise not know which one has been given on the address bar. If you specify the <code>debug</code> attribute in tags like these you can more easily see what replacements are available.</p>\n\n	<h2 id=\"eg9\">Example 9: using phpvar</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:php&gt;\nglobal $bodyex;\n$bodyex = excerpt(array()).body(array());\n&lt;/txp:php&gt;\n&lt;txp:smd_if field=&quot;phpvar:bodyex&quot;\n     operator=&quot;gt:LEN&quot;\n     value=&quot;300&quot;&gt;\n  &lt;p&gt;You are a big boy at {smd_if_len_bodyex}\n     characters long!&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>If put in an article Form (<span class=\"caps\">NOT</span> directly in an article or you&#8217;ll get an out of memory error!), this checks the excerpt and body and shows the message if the combined total length is more than 300 characters.</p>\n\n	<h2 id=\"eg10\">Example 10: <code>between</code> and <code>range</code></h2>\n\n	<p>Check that the given <span class=\"caps\">URL</span> params <code>day</code> and <code>month</code> are within reasonable tolerance. Note that it&#8217;s still possible to specify the 31st February so you may require extra checks in the container.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:day, urlvar:month&quot;\n     operator=&quot;between, between&quot; value=&quot;0/32, 0/13&quot;&gt;\n   // Likely a valid date supplied\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>This is functionally equivalent, and probably more obvious to anyone else reading the code:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_if field=&quot;urlvar:day, urlvar:month&quot;\n     operator=&quot;range, range&quot; value=&quot;1/31, 1/12&quot;&gt;\n   // Likely a valid date supplied\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>If you wanted to factor the year in and make sure that nobody used a year less than 1900 or greater than the current year, try this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:php&gt;\nglobal $thisyear;\n$thisyear = date(&quot;Y&quot;);\n&lt;/txp:php&gt;\n&lt;txp:smd_if field=&quot;urlvar:d, urlvar:m, urlvar:y&quot;\n     operator=&quot;range, range, range&quot;\n     value=&quot;1/31, 1/12, 1900/phpvar:thisyear&quot;&gt;\n   // Likely a valid date supplied\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<h2 id=\"eg11\">Example 11: reading multiple values from different places</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;ltuae&quot;&gt;42&lt;/txp:variable&gt;\n&lt;txp:smd_if field=&quot;urlvar:trigger&quot;\n     operator=&quot;in&quot; value=&quot;3/15/36/txpvar:ltuae/180/?secret&quot;&gt;\n   &lt;p&gt;You found one of the magic numbers&lt;/p&gt;\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<p>First of all we set up the Txp variable, then test the <span class=\"caps\">URL</span> variable <code>trigger</code> and see if it is one of the numbers listed in the <code>value</code> attribute. Note that we have specified the Txp variable as one of the numbers, and also the contents of the custom field called <code>secret</code>. Essentially, this builds up the value attribute from all the sources and tests the final result. So if secret held the number 94, this smd_if tag checks if trigger is one of 3, 15, 36, 42, 180 or 94 and displays the message if so. If <code>secret</code> instead contained <code>94/101/248</code> these three values would also be tested as part of the <code>in</code> operator.</p>\n\n	<h2 id=\"eg12\">Example 12: item counts</h2>\n\n	<p>This counts the number of items in <code>my_list</code> and tests them against the <code>value</code>. Note that <code>list_delim</code> is used between list items.</p>\n\n<pre><code>&lt;txp:variable name=&quot;my_list&quot;&gt;8 / 42 / 11 / 75 / 14&lt;/txp:variable&gt;\n&lt;txp:smd_if field=&quot;txpvar:my_list&quot; operator=&quot;gt:COUNT&quot; value=&quot;3&quot;&gt;\n   Yes, there are {smd_if_val1} or more values\n   (actually: {smd_if_count_my_list})\n&lt;txp:else /&gt;\n  There are fewer than {smd_if_val1} values in the list.\n&lt;/txp:smd_if&gt;\n</code></pre>\n\n	<h2>Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Based on an idea brewing in the back of my mind while hacking chs_if_urlvar.</p>\n\n	<h2 class=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>30 Dec 2007 | 0.10 | Initial release</li>\n		<li>30 Dec 2007 | 0.20 | Added parent category checking (thanks the_ghost)</li>\n		<li>02 Jan 2008 | 0.30 | Added defined/undefined and strict numeric comparisons</li>\n		<li>06 Jan 2008 | 0.40 | Added <code>?</code> notation to allow the value to read Txp fields; better quote support (both thanks NeilA)</li>\n		<li>06 Jan 2008 | 0.41 | Fixed lower case field names and undefined index error (thanks peterj)</li>\n		<li>14 Jan 2008 | 0.50 | Added case_sensitive option; made &#8216;contains&#8217; the default for &#8216;parent&#8217; tests; improved help (all thanks the_ghost); added delim options</li>\n		<li>15 Jan 2008 | 0.51 | Fixed defined/undefined syntax error; tightened isused/isempty to distinguish them from defined/undefined</li>\n		<li>25 May 2008 | 0.60 | Fixed &#8216;undefined index&#8217; errors (thanks redbot and the_ghost) ; added more pretext variables ; added more <code>is</code> checks (and the <span class=\"caps\">NOSPACE</span> modifier) ; allowed file and link tests (including parent categories)</li>\n		<li>26 May 2008 | 0.61 | Fixed stupid oversight in field name generation to allow arbitrary names instead of forcing $thisarticle (thanks to Joana Carvalho for leading me to this)</li>\n		<li>11 Jun 2008 | 0.62 | Fixed incorrect result if eval() is empty ; added <span class=\"caps\">NULL</span> field object</li>\n		<li>10 Sep 2008 | 0.70 | Fixed warning if empty custom field in value (thanks visualpeople) ; added txpvar support (thanks the_ghost) ; added thisimage support (for the future) ; added operators <code>in</code>, <code>notin</code> and the <code>list_delim</code> attribute; enabled replacement tags for matched variables</li>\n		<li>01 Oct 2008 | 0.71 | Fixed the fix for empty custom fields implemented in 0.7 (thanks mapu/visualpeople)</li>\n		<li>01 Oct 2008 | 0.72 | Added <code>:NOTAGS</code> (thanks mapu)</li>\n		<li>13 Oct 2008 | 0.73 | Added <code>:NOSPACE</code> to <code>begins</code>, <code>ends</code> and <code>contains</code> (thanks mapu), added phpvar support, <code>:LEN</code> modifier and length replacement tags (all thanks the_ghost)</li>\n		<li>13 Oct 2008 | 0.74 | Bug fix the smd_if_ names of vals and fields to avoid clashes. Now numerically indexed</li>\n		<li>02 Dec 2008 | 0.75 | Added <code>divisible</code> operator (thanks gomedia) ; allow short-circuit of fields (thanks redbot)</li>\n		<li>20 Mar 2009 | 0.76 | Added <code>postvar</code> field type (thanks kostas45)</li>\n		<li>22 Mar 2009 | 0.77 | Added <code>:TRIM</code> modifier (thanks gomedia)</li>\n		<li>05 Apr 2009 | 0.80 | Added filtering capability</li>\n		<li>26 Sep 2009 | 0.81 | Added parent <code>TTL</code> and <code>KIDS</code> modifiers (thanks photonomad) ; improved parent debug output</li>\n		<li>02 Mar 2010 | 0.82 | Added <code>between</code> and <code>range</code> (thanks speeke)</li>\n		<li>02 Mar 2010 | 0.90 | Internal code refactorisation ; allowed multiple values to be read from multiple sources (thanks speeke) ; enhanced replacement tags</li>\n		<li>28 Jan 2012 | 0.91 | Fixed pretext checks for section/category (thanks saccade) ; enabled explicit checks for pretext, file, link, image, and article ; added <code>var_prefix</code> to allow nesting of smd_if tags; added <code>:COUNT</code> modifier (thanks the_ghost) ; added <code>:ESC</code> and <code>:ESCALL</code> modifiers ; fixed checks for defined / undefined</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\n * smd_if\n *\n * A Textpattern CMS plugin for evaluating multiple conditional logic statements\n *  -> Test for (in)equality, less/greater than, divisible by, empty, used, defined, begins/ends, contains, in list, type...\n *  -> Supports and/or logic\n *  -> Data filtration options\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\nfunction smd_if($atts,$thing) {\n	global $thisarticle, $pretext, $thisfile, $thislink, $thisimage, $variable;\n\n	extract(lAtts(array(\n		\'field\'          => \'\',\n		\'operator\'       => \'\',\n		\'value\'          => \'\',\n		\'logic\'          => \'and\',\n		\'case_sensitive\' => \'0\',\n		\'filter\'         => \'\',\n		\'replace\'        => \'\',\n		\'filter_type\'    => \'all\',\n		\'filter_on\'      => \'field\',\n		\'param_delim\'    => \',\',\n		\'mod_delim\'      => \':\',\n		\'list_delim\'     => \'/\',\n		\'var_prefix\'     => \'smd_if_\',\n		\'debug\'          => \'0\',\n	), $atts));\n\n	// Special field names that refer to $pretext or elsewhere - everything else is assumed to\n	// exist in $thisarticle so custom fields can be used\n	$allPtxt = array(\n		\"id\"            => \'$pretext[\"id\"]\',\n		\"s\"             => \'$pretext[\"s\"]\',\n		\"c\"             => \'$pretext[\"c\"]\',\n		\"query\"         => \'$pretext[\"q\"]\',\n		\"pg\"            => \'$pretext[\"pg\"]\',\n		\"month\"         => \'$pretext[\"month\"]\',\n		\"author\"        => \'$pretext[\"author\"]\',\n		\"status\"        => \'$pretext[\"status\"]\',\n		\"page\"          => \'$pretext[\"page\"]\',\n		\"next_id\"       => \'$pretext[\"next_id\"]\',\n		\"next_title\"    => \'$pretext[\"next_title\"]\',\n		\"next_utitle\"   => \'$pretext[\"next_utitle\"]\',\n		\"prev_id\"       => \'$pretext[\"prev_id\"]\',\n		\"prev_title\"    => \'$pretext[\"prev_title\"]\',\n		\"prev_utitle\"   => \'$pretext[\"prev_utitle\"]\',\n		\"permlink_mode\" => \'$pretext[\"permlink_mode\"]\',\n	);\n\n	// Each entry has the operation to be eval()d later and a list of disallowed fields\n	$allOps = array(\n		\'eq\'        => array(\'isset(VARNAME) && CAST FIELD === CAST VALUE\', \'\'),\n		\'not\'       => array(\'isset(VARNAME) && CAST FIELD !== CAST VALUE\', \'\'),\n		\'gt\'        => array(\'isset(VARNAME) && CAST FIELD > CAST VALUE\', \'\'),\n		\'ge\'        => array(\'isset(VARNAME) && CAST FIELD >= CAST VALUE\', \'\'),\n		\'lt\'        => array(\'isset(VARNAME) && CAST FIELD < CAST VALUE\', \'\'),\n		\'le\'        => array(\'isset(VARNAME) && CAST FIELD <= CAST VALUE\', \'\'),\n		\'between\'   => array(\'isset(VARNAME) && ($tween=explode(\"\'.$list_delim.\'\", VALUE)) && CAST FIELD > $tween[0] && CAST FIELD < $tween[1]\', \'\'),\n		\'range\'     => array(\'isset(VARNAME) && ($rng=explode(\"\'.$list_delim.\'\", VALUE)) && CAST FIELD >= $rng[0] && CAST FIELD <= $rng[1]\', \'\'),\n		\'divisible\' => array(\'isset(VARNAME) && CAST FIELD % CAST VALUE === 0\', \'\'),\n		\'in\'        => array(\'isset(VARNAME) && in_array(FIELD, explode(\"\'.$list_delim.\'\", VALUE)) !== false\', \'\'),\n		\'notin\'     => array(\'isset(VARNAME) && in_array(FIELD, explode(\"\'.$list_delim.\'\", VALUE)) === false\', \'\'),\n		\'begins\'    => array(\'isset(VARNAME) && strpos(FIELD, VALUE) === 0\', \'\'),\n		\'contains\'  => array(\'isset(VARNAME) && strpos(FIELD, VALUE) !== false\', \'\'),\n		\'ends\'      => array(\'isset(VARNAME) && substr(FIELD, strlen(FIELD) - strlen(VALUE)) === VALUE\', \'\'),\n		\'defined\'   => array(\'isset(VARNAME)\', \'parent\'),\n		\'undefined\' => array(\'!isset(VARNAME)\', \'parent\'),\n		\'isempty\'   => array(\'isset(VARNAME) && FIELD == \"\"\', \'\'),\n		\'isused\'    => array(\'isset(VARNAME) && FIELD != \"\"\', \'\'),\n		\'isnum\'     => array(\'isset(VARNAME) && ctype_digit((string)FIELD)\', \'\'),\n		\'isalpha\'   => array(\'isset(VARNAME) && ctype_alpha((string)FIELD)\', \'\'),\n		\'isalnum\'   => array(\'isset(VARNAME) && ctype_alnum((string)FIELD)\', \'\'),\n		\'islower\'   => array(\'isset(VARNAME) && ctype_lower((string)FIELD)\', \'\'),\n		\'isupper\'   => array(\'isset(VARNAME) && ctype_upper((string)FIELD)\', \'\'),\n		\'ispunct\'   => array(\'isset(VARNAME) && ctype_punct((string)FIELD)\', \'\'),\n		\'isspace\'   => array(\'isset(VARNAME) && ctype_space((string)FIELD)\', \'\'),\n	);\n\n	$numericOps = \"gt, ge, lt, le, eq, not, divisible, range, between\";\n	$caseOps = \"islower, isupper\";\n	$spaceOps = \"isnum, isalpha, isalnum, islower, isupper, ispunct, begins, contains, ends\";\n	$fields = do_list($field, $param_delim);\n	$numFlds = count($fields);\n	$ops = do_list($operator, $param_delim);\n	$numOps = count($ops);\n	$vals = do_list($value, $param_delim);\n	$numVals = count($vals);\n	$parentCats = \'\'; // Placeholder for the concatenated list of category leaf nodes\n	$replacements = array();\n	$type = ($thisfile) ? \"file\" : (($thislink) ? \"link\" : (($thisimage) ? \"image\" : \"article\"));\n	$out = array();\n	$iterations = ($numFlds > $numOps) ? $numFlds : $numOps;\n	$filter = (!empty($filter)) ? do_list($filter, $param_delim) : array();\n	$replace = (!empty($replace)) ? do_list($replace, $param_delim) : array();\n	$numFilters = count($filter);\n	$numReplace = count($replace);\n	$filter_on = (!empty($filter_on)) ? do_list($filter_on, $param_delim) : array();\n	$filter_type = (!empty($filter_type)) ? do_list($filter_type, $param_delim) : array();\n	if ($debug > 1 && ($filter || $replace)) {\n		echo \"++ FILTERS / REPLACEMENTS ++\";\n		dmp($filter, $replace);\n	}\n\n	for ($idx = 0; $idx < $iterations; $idx++) {\n		$fld = ($idx < $numFlds) ? $fields[$idx] : $fields[0]; // Allow short-circuit\n		$fldParts = explode($mod_delim, $fld);\n		$val = ($idx < $numVals) ? $vals[$idx] : \'\';\n		$valList = explode($list_delim, $val);\n		$valRep = array();\n		foreach ($valList as $kdx => $theval) {\n			$valRep[$kdx] = explode($mod_delim, $theval);\n		}\n\n		$op = ($idx < $numOps && $ops[$idx] != \'\') ? $ops[$idx] : (($fldParts[0]==\"parent\") ? \"contains\" : \"eq\");\n		$opParts = explode($mod_delim, $op);\n		$op = (array_key_exists($opParts[0], $allOps)) ? $opParts[0] : \"eq\";\n		$cast = ((count($opParts) == 2) && ($opParts[1] === \"NUM\") && (in_list($op, $numericOps))) ? \'(int)\' : \'\';\n		$length = ((count($opParts) == 2) && ($opParts[1] === \"LEN\") && (in_list($op, $numericOps))) ? \'strlen(FIELD)\' : \'\';\n		// The cast to string is necessary to counteract the === in the eq operator.\n		// It doesn\'t impact anything else because string comparisons work fairly intuitively in PHP\n		// (e.g. 19 < 2 = false even though in terms of string order they\'d go 19, 2, 20, 21,...)\n		$count = ((count($opParts) == 2) && ($opParts[1] === \"COUNT\") && (in_list($op, $numericOps))) ? \'(string)count(explode(\"\'.$list_delim.\'\", FIELD))\' : \'\';\n		$killSpaces = ((count($opParts) == 2) && ($opParts[1] === \"NOSPACE\") && (in_list($op, $spaceOps))) ? true : false;\n		$stripFld = ((count($fldParts) > 1) && (in_array(\"NOTAGS\", $fldParts))) ? true : false;\n		$trimFld = ((count($fldParts) > 1) && (in_array(\"TRIM\", $fldParts))) ? true : false;\n		$escapeFld = ((count($fldParts) > 1) && (in_array(\"ESC\", $fldParts))) ? true : false;\n		$escapeAllFld = ((count($fldParts) > 1) && (in_array(\"ESCALL\", $fldParts))) ? true : false;\n		$case_sensitive = (in_list($op, $caseOps)) ? 1 : $case_sensitive;\n		$pat = ($idx < $numFilters) ? $filter[$idx] : (($filter) ? $filter[0] : \'\');\n		$rep = ($idx < $numReplace) ? $replace[$idx] : (($replace) ? $replace[0] : \'\');\n		if ($debug) {\n			echo \'TEST \'.($idx+1).n;\n			dmp($fldParts, $opParts, $valRep);\n		}\n		// Get the operator replacement code\n		$exclude = do_list($allOps[$op][1]);\n		$op = $allOps[$op][0];\n\n		// As long as the current operator allows this field...\n		if (!in_array($fldParts[0], $exclude)) {\n			// Make up the test field variable\n			if ($fldParts[0] == \'file\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thisfile[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisfile[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thisfile[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'link\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thislink[\"\'.$rfld.\'\"]\';\n			} else if (isset($thislink[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thislink[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'image\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thisimage[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisimage[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thisimage[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'pretext\') {\n				$rfld = $fldParts[1];\n				$fld = \'$pretext[\"\'.$rfld.\'\"]\';\n			} else if (array_key_exists($fldParts[0], $allPtxt)) {\n				$rfld = $fldParts[0];\n				$fld = $allPtxt[$rfld];\n			} else if ($fldParts[0] == \"parent\") {\n				$treeField = \'name\';\n				$level = \'\';\n				foreach ($fldParts as $part) {\n					if ($part == \"parent\") {\n						$theCat = ($thisfile) ? $thisfile[\'category\'] : (($thislink) ? $thislink[\'category\'] : (($thisimage) ? $thisimage[\'category\'] : $pretext[\'c\']));\n					} else if (strpos($part, \"CAT\") === 0) {\n						$theCat = $thisarticle[\"category\".substr($part, 3)];\n					} else if (strpos($part, \"LVL\") === 0) {\n						$level = substr($part, 3);\n					} else if (strpos($part, \"TTL\") === 0) {\n						$treeField = \'title\';\n					} else if (strpos($part, \"KIDS\") === 0) {\n						$treeField = \'children\';\n					}\n				}\n\n				$tree = getTreePath(doSlash($theCat), $type);\n				if ($debug && $tree) {\n					echo \"CATEGORY TREE:\";\n					dmp($tree);\n				}\n				$items = array();\n				foreach ($tree as $leaf) {\n					if ($leaf[\'name\'] == \"root\" || $leaf[\'name\'] == $theCat) {\n						continue;\n					} else if ($level == \'\' || $level == $leaf[\'level\']) {\n						$items[] = $leaf[$treeField];\n					}\n				}\n				$parentCats = implode(\" \", $items);\n				$rfld = sanitizeForUrl($parentCats);\n				if ($debug && $parentCats) {\n					echo \"++ PARENT INFO ++\";\n					dmp($parentCats);\n				}\n				$fld = \'$parentCats\';\n			} else if ($fldParts[0] == \"txpvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$variable[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"urlvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_GET[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"postvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_POST[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"svrvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_SERVER[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"phpvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$GLOBALS[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \'article\') {\n				$rfld = strtolower($fldParts[1]);\n				$fld = \'$thisarticle[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisarticle[$fldParts[0]])) {\n				$rfld = strtolower($fldParts[0]);\n				$fld = \'$thisarticle[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \"NULL\") {\n				$smd_if_var = \'\';\n				$fld = \'$smd_if_var\';\n				$rfld = \"NULL\";\n			} else {\n				$smd_if_var = $fldParts[0];\n				$fld = \'$smd_if_var\';\n				$rfld = \"field\".($idx*1+1);\n			}\n			$rlfld = $var_prefix.\"len_\".$rfld;\n			$rcfld = $var_prefix.\"count_\".$rfld;\n			$rfld = $var_prefix.$rfld;\n\n			// Take a copy of $fld to use in any isset() requests\n			$fldClean = $fld;\n\n			// Apply user-defined field filters\n			if ($killSpaces) {\n				$fld = \'preg_replace(\"/\\s+/\",\"\",\'.$fld.\')\';\n			}\n			if ($stripFld) {\n				$fld = \'trim(strip_tags(\'.$fld.\'))\';\n			}\n			if ($trimFld) {\n				$fld = \'trim(\'.$fld.\')\';\n			}\n			if ($escapeFld) {\n				$fld = \'htmlentities(\'.$fld.\')\';\n			}\n			if ($escapeAllFld) {\n				$fld = \'htmlentities(\'.$fld.\', ENT_QUOTES)\';\n			}\n			$do_ffilt = ($pat && in_array(\'field\', $filter_on) && (in_array($fldParts[0], $filter_type) || in_array(\'all\', $filter_type)) ) ? true : false;\n\n			// Find the real value to compare against (may be another field)\n			$valcnt = 1;\n			$vflds = array();\n			$core_vfld = \"val\".(($idx*1)+1);\n\n			foreach ($valRep as $jdx => $valParts) {\n				$stripVal = ((count($valParts) > 1) && (in_array(\"NOTAGS\", $valParts))) ? true : false;\n				$trimVal = ((count($valParts) > 1) && (in_array(\"TRIM\", $valParts))) ? true : false;\n				$escapeVal = ((count($valParts) > 1) && (in_array(\"ESC\", $valParts))) ? true : false;\n				$escapeAllVal = ((count($valParts) > 1) && (in_array(\"ESCALL\", $valParts))) ? true : false;\n				$numValParts = count($valParts);\n				if ($valParts[0] == \"urlvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_GET[$vfld]) && $_GET[$vfld] != \"\") ? \'$_GET[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"postvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_POST[$vfld]) && $_POST[$vfld] != \"\") ? \'$_POST[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"svrvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_SERVER[$vfld]) && $_SERVER[$vfld] != \"\") ? \'$_SERVER[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"txpvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($variable[$vfld]) && $variable[$vfld] != \"\") ? \'$variable[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"phpvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($GLOBALS[$vfld]) && $GLOBALS[$vfld] != \"\") ? \'$GLOBALS[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"file\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisfile[$vfld]) && $thisfile[$vfld] != \"\") ? \'$thisfile[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"link\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thislink[$vfld]) && $thislink[$vfld] != \"\") ? \'$thislink[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"image\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisimage[$vfld]) && $thisimage[$vfld] != \"\") ? \'$thisimage[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"pretext\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($pretext[$vfld]) && $pretext[$vfld] != \"\") ? \'$pretext[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"article\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisarticle[$vfld]) && $thisarticle[$vfld] != \"\") ? \'$thisarticle[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if (strpos($valParts[0], \"?\") === 0) {\n					$valParts[0] = substr(strtolower($valParts[0]), 1);\n					$vfld = $valParts[0];\n					if (isset($thisfile[$vfld]) && $thisfile[$vfld] != \"\") {\n						$val = \'$thisfile[\"\'.$vfld.\'\"]\';\n					} else if (isset($thislink[$vfld]) && $thislink[$vfld] != \"\") {\n						$val = \'$thislink[\"\'.$vfld.\'\"]\';\n					} else if (isset($thisimage[$vfld]) && $thisimage[$vfld] != \"\") {\n						$val = \'$thisimage[\"\'.$vfld.\'\"]\';\n					} else if (array_key_exists($vfld, $allPtxt) && $allPtxt[$vfld] != \"\") {\n						$val = $allPtxt[$vfld];\n					} else if (isset($thisarticle[$vfld]) && $thisarticle[$vfld] != \"\") {\n						$val = \'$thisarticle[\"\'.$vfld.\'\"]\';\n					} else {\n						$val = doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else {\n					$vfld = $core_vfld.\'_\'.$valcnt;\n					$val = doQuote(str_replace(\'\"\', \'\\\"\', $valParts[0]));\n				}\n\n				// Apply user-defined value filters\n				if ($stripVal) {\n					$val = \'trim(strip_tags(\'.$val.\'))\';\n				}\n				if ($trimVal) {\n					$val = \'trim(\'.$val.\')\';\n				}\n				if ($escapeVal) {\n					$val = \'htmlentities(\'.$val.\')\';\n				}\n				if ($escapeAllVal) {\n					$val = \'htmlentities(\'.$val.\', ENT_QUOTES)\';\n				}\n				$do_vfilt = ($pat && in_array(\'value\', $filter_on) && (in_array($valParts[0], $filter_type) || in_array(\'all\', $filter_type)) ) ? true : false;\n\n				// Replace the string parts by evaluating any variables...\n				$filt_fld = ($do_ffilt) ? \"preg_replace(\'$pat\', \'$rep\', $fld)\" : $fld;\n				$filt_val = ($do_vfilt) ? \"preg_replace(\'$pat\', \'$rep\', $val)\" : $val;\n				eval (\"\\$valRep[$jdx] = \".$filt_val.\";\");\n\n				// Only add sub-values to the replacements array if there\'s more than one sub-value\n				if (count($valRep) > 1) {\n					$vflds[$var_prefix.$vfld] = $valRep[$jdx];\n					$vflds[$var_prefix.\"len_\".$vfld] = strlen($valRep[$jdx]);\n				}\n\n				$valcnt++;\n			}\n\n			$joinedVals = join($list_delim, $valRep);\n			$smd_prefilter = doQuote($joinedVals);\n\n			// Add the combined operator for backwards compatibility with plugin v0.8x\n			$vflds[$var_prefix.$core_vfld] = $joinedVals;\n			$vflds[$var_prefix.\"len_\".$core_vfld] = strlen($joinedVals);\n\n			$cmd = str_replace(\"CAST\", $cast, $op);\n			$cmd = ($length) ? str_replace(\"FIELD\", $length, $cmd) : $cmd;\n			$cmd = ($count) ? str_replace(\"FIELD\", $count, $cmd) : $cmd;\n			$cmd = str_replace(\"FIELD\", (($case_sensitive) ? $filt_fld : \'strtolower(\'.$filt_fld.\')\'), $cmd);\n			$cmd = str_replace(\"VARNAME\", $fldClean, $cmd);\n			$cmd = str_replace(\"VALUE\", (($case_sensitive) ? \'VALUE\' : \'strtolower(VALUE)\'), $cmd);\n\n			// Value replacements have already been run through evil() so they can be assigned directly\n			foreach ($vflds as $valit => $valval) {\n				$replacements[\'{\'.$valit.\'}\'] = $valval;\n			}\n\n			// Field replacements need some eval() action...\n			$cmd = \"@\\$replacements[\'{\".$rfld.\"}\'] = \".$filt_fld.\"; \\n@\\$replacements[\'{\".$rlfld.\"}\'] = strlen(\".$filt_fld.\"); \\n@\\$replacements[\'{\".$rcfld.\"}\'] = count(explode(\'\".$list_delim.\"\', \".$filt_fld.\")); \\n\\$out[\".$idx.\"] = (\".str_replace(\"VALUE\", (($smd_prefilter===\"\'\'\" && strpos($op, \"strpos\") !== false) ? \"\' \'\" : $smd_prefilter), $cmd).\") ? \'true\' : \'false\';\";\n			if ($debug) {\n				dmp($cmd);\n			}\n			// ... and evaluate the expression\n			eval($cmd);\n		}\n	}\n	if ($debug) {\n		echo \"RESULT:\";\n		dmp($out);\n		echo \"REPLACEMENTS:\";\n		dmp($replacements);\n	}\n	if ($debug > 2) {\n		echo \"PRETEXT:\";\n		dmp($pretext);\n		echo \"THIS ARTICLE:\";\n		dmp($thisarticle);\n		echo \"THIS FILE:\";\n		dmp($thisfile);\n		echo \"THIS LINK:\";\n		dmp($thislink);\n		echo \"THIS IMAGE:\";\n		dmp($thisimage);\n	}\n	// Check logic\n	$result = ($out) ? true : false;\n	if (strtolower($logic) == \"and\" && in_array(\"false\", $out)) {\n		$result = false;\n	}\n	if (strtolower($logic) == \"or\" && !in_array(\"true\", $out)) {\n		$result = false;\n	}\n\n	return parse(strtr($thing, $replacements), $result);\n}', '/**\n * smd_if\n *\n * A Textpattern CMS plugin for evaluating multiple conditional logic statements\n *  -> Test for (in)equality, less/greater than, divisible by, empty, used, defined, begins/ends, contains, in list, type...\n *  -> Supports and/or logic\n *  -> Data filtration options\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\n\nfunction smd_if($atts,$thing) {\n	global $thisarticle, $pretext, $thisfile, $thislink, $thisimage, $variable;\n\n	extract(lAtts(array(\n		\'field\'          => \'\',\n		\'operator\'       => \'\',\n		\'value\'          => \'\',\n		\'logic\'          => \'and\',\n		\'case_sensitive\' => \'0\',\n		\'filter\'         => \'\',\n		\'replace\'        => \'\',\n		\'filter_type\'    => \'all\',\n		\'filter_on\'      => \'field\',\n		\'param_delim\'    => \',\',\n		\'mod_delim\'      => \':\',\n		\'list_delim\'     => \'/\',\n		\'var_prefix\'     => \'smd_if_\',\n		\'debug\'          => \'0\',\n	), $atts));\n\n	// Special field names that refer to $pretext or elsewhere - everything else is assumed to\n	// exist in $thisarticle so custom fields can be used\n	$allPtxt = array(\n		\"id\"            => \'$pretext[\"id\"]\',\n		\"s\"             => \'$pretext[\"s\"]\',\n		\"c\"             => \'$pretext[\"c\"]\',\n		\"query\"         => \'$pretext[\"q\"]\',\n		\"pg\"            => \'$pretext[\"pg\"]\',\n		\"month\"         => \'$pretext[\"month\"]\',\n		\"author\"        => \'$pretext[\"author\"]\',\n		\"status\"        => \'$pretext[\"status\"]\',\n		\"page\"          => \'$pretext[\"page\"]\',\n		\"next_id\"       => \'$pretext[\"next_id\"]\',\n		\"next_title\"    => \'$pretext[\"next_title\"]\',\n		\"next_utitle\"   => \'$pretext[\"next_utitle\"]\',\n		\"prev_id\"       => \'$pretext[\"prev_id\"]\',\n		\"prev_title\"    => \'$pretext[\"prev_title\"]\',\n		\"prev_utitle\"   => \'$pretext[\"prev_utitle\"]\',\n		\"permlink_mode\" => \'$pretext[\"permlink_mode\"]\',\n	);\n\n	// Each entry has the operation to be eval()d later and a list of disallowed fields\n	$allOps = array(\n		\'eq\'        => array(\'isset(VARNAME) && CAST FIELD === CAST VALUE\', \'\'),\n		\'not\'       => array(\'isset(VARNAME) && CAST FIELD !== CAST VALUE\', \'\'),\n		\'gt\'        => array(\'isset(VARNAME) && CAST FIELD > CAST VALUE\', \'\'),\n		\'ge\'        => array(\'isset(VARNAME) && CAST FIELD >= CAST VALUE\', \'\'),\n		\'lt\'        => array(\'isset(VARNAME) && CAST FIELD < CAST VALUE\', \'\'),\n		\'le\'        => array(\'isset(VARNAME) && CAST FIELD <= CAST VALUE\', \'\'),\n		\'between\'   => array(\'isset(VARNAME) && ($tween=explode(\"\'.$list_delim.\'\", VALUE)) && CAST FIELD > $tween[0] && CAST FIELD < $tween[1]\', \'\'),\n		\'range\'     => array(\'isset(VARNAME) && ($rng=explode(\"\'.$list_delim.\'\", VALUE)) && CAST FIELD >= $rng[0] && CAST FIELD <= $rng[1]\', \'\'),\n		\'divisible\' => array(\'isset(VARNAME) && CAST FIELD % CAST VALUE === 0\', \'\'),\n		\'in\'        => array(\'isset(VARNAME) && in_array(FIELD, explode(\"\'.$list_delim.\'\", VALUE)) !== false\', \'\'),\n		\'notin\'     => array(\'isset(VARNAME) && in_array(FIELD, explode(\"\'.$list_delim.\'\", VALUE)) === false\', \'\'),\n		\'begins\'    => array(\'isset(VARNAME) && strpos(FIELD, VALUE) === 0\', \'\'),\n		\'contains\'  => array(\'isset(VARNAME) && strpos(FIELD, VALUE) !== false\', \'\'),\n		\'ends\'      => array(\'isset(VARNAME) && substr(FIELD, strlen(FIELD) - strlen(VALUE)) === VALUE\', \'\'),\n		\'defined\'   => array(\'isset(VARNAME)\', \'parent\'),\n		\'undefined\' => array(\'!isset(VARNAME)\', \'parent\'),\n		\'isempty\'   => array(\'isset(VARNAME) && FIELD == \"\"\', \'\'),\n		\'isused\'    => array(\'isset(VARNAME) && FIELD != \"\"\', \'\'),\n		\'isnum\'     => array(\'isset(VARNAME) && ctype_digit((string)FIELD)\', \'\'),\n		\'isalpha\'   => array(\'isset(VARNAME) && ctype_alpha((string)FIELD)\', \'\'),\n		\'isalnum\'   => array(\'isset(VARNAME) && ctype_alnum((string)FIELD)\', \'\'),\n		\'islower\'   => array(\'isset(VARNAME) && ctype_lower((string)FIELD)\', \'\'),\n		\'isupper\'   => array(\'isset(VARNAME) && ctype_upper((string)FIELD)\', \'\'),\n		\'ispunct\'   => array(\'isset(VARNAME) && ctype_punct((string)FIELD)\', \'\'),\n		\'isspace\'   => array(\'isset(VARNAME) && ctype_space((string)FIELD)\', \'\'),\n	);\n\n	$numericOps = \"gt, ge, lt, le, eq, not, divisible, range, between\";\n	$caseOps = \"islower, isupper\";\n	$spaceOps = \"isnum, isalpha, isalnum, islower, isupper, ispunct, begins, contains, ends\";\n	$fields = do_list($field, $param_delim);\n	$numFlds = count($fields);\n	$ops = do_list($operator, $param_delim);\n	$numOps = count($ops);\n	$vals = do_list($value, $param_delim);\n	$numVals = count($vals);\n	$parentCats = \'\'; // Placeholder for the concatenated list of category leaf nodes\n	$replacements = array();\n	$type = ($thisfile) ? \"file\" : (($thislink) ? \"link\" : (($thisimage) ? \"image\" : \"article\"));\n	$out = array();\n	$iterations = ($numFlds > $numOps) ? $numFlds : $numOps;\n	$filter = (!empty($filter)) ? do_list($filter, $param_delim) : array();\n	$replace = (!empty($replace)) ? do_list($replace, $param_delim) : array();\n	$numFilters = count($filter);\n	$numReplace = count($replace);\n	$filter_on = (!empty($filter_on)) ? do_list($filter_on, $param_delim) : array();\n	$filter_type = (!empty($filter_type)) ? do_list($filter_type, $param_delim) : array();\n	if ($debug > 1 && ($filter || $replace)) {\n		echo \"++ FILTERS / REPLACEMENTS ++\";\n		dmp($filter, $replace);\n	}\n\n	for ($idx = 0; $idx < $iterations; $idx++) {\n		$fld = ($idx < $numFlds) ? $fields[$idx] : $fields[0]; // Allow short-circuit\n		$fldParts = explode($mod_delim, $fld);\n		$val = ($idx < $numVals) ? $vals[$idx] : \'\';\n		$valList = explode($list_delim, $val);\n		$valRep = array();\n		foreach ($valList as $kdx => $theval) {\n			$valRep[$kdx] = explode($mod_delim, $theval);\n		}\n\n		$op = ($idx < $numOps && $ops[$idx] != \'\') ? $ops[$idx] : (($fldParts[0]==\"parent\") ? \"contains\" : \"eq\");\n		$opParts = explode($mod_delim, $op);\n		$op = (array_key_exists($opParts[0], $allOps)) ? $opParts[0] : \"eq\";\n		$cast = ((count($opParts) == 2) && ($opParts[1] === \"NUM\") && (in_list($op, $numericOps))) ? \'(int)\' : \'\';\n		$length = ((count($opParts) == 2) && ($opParts[1] === \"LEN\") && (in_list($op, $numericOps))) ? \'strlen(FIELD)\' : \'\';\n		// The cast to string is necessary to counteract the === in the eq operator.\n		// It doesn\'t impact anything else because string comparisons work fairly intuitively in PHP\n		// (e.g. 19 < 2 = false even though in terms of string order they\'d go 19, 2, 20, 21,...)\n		$count = ((count($opParts) == 2) && ($opParts[1] === \"COUNT\") && (in_list($op, $numericOps))) ? \'(string)count(explode(\"\'.$list_delim.\'\", FIELD))\' : \'\';\n		$killSpaces = ((count($opParts) == 2) && ($opParts[1] === \"NOSPACE\") && (in_list($op, $spaceOps))) ? true : false;\n		$stripFld = ((count($fldParts) > 1) && (in_array(\"NOTAGS\", $fldParts))) ? true : false;\n		$trimFld = ((count($fldParts) > 1) && (in_array(\"TRIM\", $fldParts))) ? true : false;\n		$escapeFld = ((count($fldParts) > 1) && (in_array(\"ESC\", $fldParts))) ? true : false;\n		$escapeAllFld = ((count($fldParts) > 1) && (in_array(\"ESCALL\", $fldParts))) ? true : false;\n		$case_sensitive = (in_list($op, $caseOps)) ? 1 : $case_sensitive;\n		$pat = ($idx < $numFilters) ? $filter[$idx] : (($filter) ? $filter[0] : \'\');\n		$rep = ($idx < $numReplace) ? $replace[$idx] : (($replace) ? $replace[0] : \'\');\n		if ($debug) {\n			echo \'TEST \'.($idx+1).n;\n			dmp($fldParts, $opParts, $valRep);\n		}\n		// Get the operator replacement code\n		$exclude = do_list($allOps[$op][1]);\n		$op = $allOps[$op][0];\n\n		// As long as the current operator allows this field...\n		if (!in_array($fldParts[0], $exclude)) {\n			// Make up the test field variable\n			if ($fldParts[0] == \'file\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thisfile[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisfile[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thisfile[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'link\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thislink[\"\'.$rfld.\'\"]\';\n			} else if (isset($thislink[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thislink[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'image\') {\n				$rfld = $fldParts[1];\n				$fld = \'$thisimage[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisimage[$fldParts[0]])) {\n				$rfld = $fldParts[0];\n				$fld = \'$thisimage[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \'pretext\') {\n				$rfld = $fldParts[1];\n				$fld = \'$pretext[\"\'.$rfld.\'\"]\';\n			} else if (array_key_exists($fldParts[0], $allPtxt)) {\n				$rfld = $fldParts[0];\n				$fld = $allPtxt[$rfld];\n			} else if ($fldParts[0] == \"parent\") {\n				$treeField = \'name\';\n				$level = \'\';\n				foreach ($fldParts as $part) {\n					if ($part == \"parent\") {\n						$theCat = ($thisfile) ? $thisfile[\'category\'] : (($thislink) ? $thislink[\'category\'] : (($thisimage) ? $thisimage[\'category\'] : $pretext[\'c\']));\n					} else if (strpos($part, \"CAT\") === 0) {\n						$theCat = $thisarticle[\"category\".substr($part, 3)];\n					} else if (strpos($part, \"LVL\") === 0) {\n						$level = substr($part, 3);\n					} else if (strpos($part, \"TTL\") === 0) {\n						$treeField = \'title\';\n					} else if (strpos($part, \"KIDS\") === 0) {\n						$treeField = \'children\';\n					}\n				}\n\n				$tree = getTreePath(doSlash($theCat), $type);\n				if ($debug && $tree) {\n					echo \"CATEGORY TREE:\";\n					dmp($tree);\n				}\n				$items = array();\n				foreach ($tree as $leaf) {\n					if ($leaf[\'name\'] == \"root\" || $leaf[\'name\'] == $theCat) {\n						continue;\n					} else if ($level == \'\' || $level == $leaf[\'level\']) {\n						$items[] = $leaf[$treeField];\n					}\n				}\n				$parentCats = implode(\" \", $items);\n				$rfld = sanitizeForUrl($parentCats);\n				if ($debug && $parentCats) {\n					echo \"++ PARENT INFO ++\";\n					dmp($parentCats);\n				}\n				$fld = \'$parentCats\';\n			} else if ($fldParts[0] == \"txpvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$variable[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"urlvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_GET[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"postvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_POST[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"svrvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$_SERVER[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \"phpvar\") {\n				if (count($fldParts) > 1) {\n					$rfld = $fldParts[1];\n					$fld = \'$GLOBALS[\"\'.$rfld.\'\"]\';\n				}\n			} else if ($fldParts[0] == \'article\') {\n				$rfld = strtolower($fldParts[1]);\n				$fld = \'$thisarticle[\"\'.$rfld.\'\"]\';\n			} else if (isset($thisarticle[$fldParts[0]])) {\n				$rfld = strtolower($fldParts[0]);\n				$fld = \'$thisarticle[\"\'.$rfld.\'\"]\';\n			} else if ($fldParts[0] == \"NULL\") {\n				$smd_if_var = \'\';\n				$fld = \'$smd_if_var\';\n				$rfld = \"NULL\";\n			} else {\n				$smd_if_var = $fldParts[0];\n				$fld = \'$smd_if_var\';\n				$rfld = \"field\".($idx*1+1);\n			}\n			$rlfld = $var_prefix.\"len_\".$rfld;\n			$rcfld = $var_prefix.\"count_\".$rfld;\n			$rfld = $var_prefix.$rfld;\n\n			// Take a copy of $fld to use in any isset() requests\n			$fldClean = $fld;\n\n			// Apply user-defined field filters\n			if ($killSpaces) {\n				$fld = \'preg_replace(\"/\\s+/\",\"\",\'.$fld.\')\';\n			}\n			if ($stripFld) {\n				$fld = \'trim(strip_tags(\'.$fld.\'))\';\n			}\n			if ($trimFld) {\n				$fld = \'trim(\'.$fld.\')\';\n			}\n			if ($escapeFld) {\n				$fld = \'htmlentities(\'.$fld.\')\';\n			}\n			if ($escapeAllFld) {\n				$fld = \'htmlentities(\'.$fld.\', ENT_QUOTES)\';\n			}\n			$do_ffilt = ($pat && in_array(\'field\', $filter_on) && (in_array($fldParts[0], $filter_type) || in_array(\'all\', $filter_type)) ) ? true : false;\n\n			// Find the real value to compare against (may be another field)\n			$valcnt = 1;\n			$vflds = array();\n			$core_vfld = \"val\".(($idx*1)+1);\n\n			foreach ($valRep as $jdx => $valParts) {\n				$stripVal = ((count($valParts) > 1) && (in_array(\"NOTAGS\", $valParts))) ? true : false;\n				$trimVal = ((count($valParts) > 1) && (in_array(\"TRIM\", $valParts))) ? true : false;\n				$escapeVal = ((count($valParts) > 1) && (in_array(\"ESC\", $valParts))) ? true : false;\n				$escapeAllVal = ((count($valParts) > 1) && (in_array(\"ESCALL\", $valParts))) ? true : false;\n				$numValParts = count($valParts);\n				if ($valParts[0] == \"urlvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_GET[$vfld]) && $_GET[$vfld] != \"\") ? \'$_GET[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"postvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_POST[$vfld]) && $_POST[$vfld] != \"\") ? \'$_POST[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"svrvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($_SERVER[$vfld]) && $_SERVER[$vfld] != \"\") ? \'$_SERVER[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"txpvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($variable[$vfld]) && $variable[$vfld] != \"\") ? \'$variable[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"phpvar\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($GLOBALS[$vfld]) && $GLOBALS[$vfld] != \"\") ? \'$GLOBALS[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"file\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisfile[$vfld]) && $thisfile[$vfld] != \"\") ? \'$thisfile[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"link\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thislink[$vfld]) && $thislink[$vfld] != \"\") ? \'$thislink[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"image\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisimage[$vfld]) && $thisimage[$vfld] != \"\") ? \'$thisimage[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"pretext\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($pretext[$vfld]) && $pretext[$vfld] != \"\") ? \'$pretext[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if ($valParts[0] == \"article\") {\n					if ($numValParts > 1) {\n						$vfld = $valParts[1];\n						$val = (isset($thisarticle[$vfld]) && $thisarticle[$vfld] != \"\") ? \'$thisarticle[\"\'.$vfld.\'\"]\' : doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else if (strpos($valParts[0], \"?\") === 0) {\n					$valParts[0] = substr(strtolower($valParts[0]), 1);\n					$vfld = $valParts[0];\n					if (isset($thisfile[$vfld]) && $thisfile[$vfld] != \"\") {\n						$val = \'$thisfile[\"\'.$vfld.\'\"]\';\n					} else if (isset($thislink[$vfld]) && $thislink[$vfld] != \"\") {\n						$val = \'$thislink[\"\'.$vfld.\'\"]\';\n					} else if (isset($thisimage[$vfld]) && $thisimage[$vfld] != \"\") {\n						$val = \'$thisimage[\"\'.$vfld.\'\"]\';\n					} else if (array_key_exists($vfld, $allPtxt) && $allPtxt[$vfld] != \"\") {\n						$val = $allPtxt[$vfld];\n					} else if (isset($thisarticle[$vfld]) && $thisarticle[$vfld] != \"\") {\n						$val = \'$thisarticle[\"\'.$vfld.\'\"]\';\n					} else {\n						$val = doQuote(str_replace(\'\"\', \'\\\"\', $vfld));\n					}\n				} else {\n					$vfld = $core_vfld.\'_\'.$valcnt;\n					$val = doQuote(str_replace(\'\"\', \'\\\"\', $valParts[0]));\n				}\n\n				// Apply user-defined value filters\n				if ($stripVal) {\n					$val = \'trim(strip_tags(\'.$val.\'))\';\n				}\n				if ($trimVal) {\n					$val = \'trim(\'.$val.\')\';\n				}\n				if ($escapeVal) {\n					$val = \'htmlentities(\'.$val.\')\';\n				}\n				if ($escapeAllVal) {\n					$val = \'htmlentities(\'.$val.\', ENT_QUOTES)\';\n				}\n				$do_vfilt = ($pat && in_array(\'value\', $filter_on) && (in_array($valParts[0], $filter_type) || in_array(\'all\', $filter_type)) ) ? true : false;\n\n				// Replace the string parts by evaluating any variables...\n				$filt_fld = ($do_ffilt) ? \"preg_replace(\'$pat\', \'$rep\', $fld)\" : $fld;\n				$filt_val = ($do_vfilt) ? \"preg_replace(\'$pat\', \'$rep\', $val)\" : $val;\n				eval (\"\\$valRep[$jdx] = \".$filt_val.\";\");\n\n				// Only add sub-values to the replacements array if there\'s more than one sub-value\n				if (count($valRep) > 1) {\n					$vflds[$var_prefix.$vfld] = $valRep[$jdx];\n					$vflds[$var_prefix.\"len_\".$vfld] = strlen($valRep[$jdx]);\n				}\n\n				$valcnt++;\n			}\n\n			$joinedVals = join($list_delim, $valRep);\n			$smd_prefilter = doQuote($joinedVals);\n\n			// Add the combined operator for backwards compatibility with plugin v0.8x\n			$vflds[$var_prefix.$core_vfld] = $joinedVals;\n			$vflds[$var_prefix.\"len_\".$core_vfld] = strlen($joinedVals);\n\n			$cmd = str_replace(\"CAST\", $cast, $op);\n			$cmd = ($length) ? str_replace(\"FIELD\", $length, $cmd) : $cmd;\n			$cmd = ($count) ? str_replace(\"FIELD\", $count, $cmd) : $cmd;\n			$cmd = str_replace(\"FIELD\", (($case_sensitive) ? $filt_fld : \'strtolower(\'.$filt_fld.\')\'), $cmd);\n			$cmd = str_replace(\"VARNAME\", $fldClean, $cmd);\n			$cmd = str_replace(\"VALUE\", (($case_sensitive) ? \'VALUE\' : \'strtolower(VALUE)\'), $cmd);\n\n			// Value replacements have already been run through evil() so they can be assigned directly\n			foreach ($vflds as $valit => $valval) {\n				$replacements[\'{\'.$valit.\'}\'] = $valval;\n			}\n\n			// Field replacements need some eval() action...\n			$cmd = \"@\\$replacements[\'{\".$rfld.\"}\'] = \".$filt_fld.\"; \\n@\\$replacements[\'{\".$rlfld.\"}\'] = strlen(\".$filt_fld.\"); \\n@\\$replacements[\'{\".$rcfld.\"}\'] = count(explode(\'\".$list_delim.\"\', \".$filt_fld.\")); \\n\\$out[\".$idx.\"] = (\".str_replace(\"VALUE\", (($smd_prefilter===\"\'\'\" && strpos($op, \"strpos\") !== false) ? \"\' \'\" : $smd_prefilter), $cmd).\") ? \'true\' : \'false\';\";\n			if ($debug) {\n				dmp($cmd);\n			}\n			// ... and evaluate the expression\n			eval($cmd);\n		}\n	}\n	if ($debug) {\n		echo \"RESULT:\";\n		dmp($out);\n		echo \"REPLACEMENTS:\";\n		dmp($replacements);\n	}\n	if ($debug > 2) {\n		echo \"PRETEXT:\";\n		dmp($pretext);\n		echo \"THIS ARTICLE:\";\n		dmp($thisarticle);\n		echo \"THIS FILE:\";\n		dmp($thisfile);\n		echo \"THIS LINK:\";\n		dmp($thislink);\n		echo \"THIS IMAGE:\";\n		dmp($thisimage);\n	}\n	// Check logic\n	$result = ($out) ? true : false;\n	if (strtolower($logic) == \"and\" && in_array(\"false\", $out)) {\n		$result = false;\n	}\n	if (strtolower($logic) == \"or\" && !in_array(\"true\", $out)) {\n		$result = false;\n	}\n\n	return parse(strtr($thing, $replacements), $result);\n}', '51133d1670f49777d47091e54c48022f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_lately', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.31', 'Show recently viewed/popular articles', '<div id=\"smd_help\">\n\n<h1>smd_lately</h1>\n\n<p>List the most recent articles viewed by the current visitor or, alternatively, all visitors. Can also order by popularity (most viewed).</p>\n\n<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>List recently viewed articles: useful on product pages or in a shopping cart</li>\n		<li>Show a list of the most popular articles</li>\n		<li>Limit either list by section or arbitrary value (ip, host, page, referer) if you wish</li>\n		<li>Uses the visitor logs</li>\n		<li>Automatically filters out article list pages, category, author or search visits</li>\n	</ul>\n\n<h2 id=\"author\">Author / credits</h2>\n\n<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n<h2 id=\"install\">Installation / Uninstallation</h2>\n\n<p class=\"required\">Requires Logging to be set to <em>All hits</em> (will issue a warning if not)</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1110/smd_lately\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=31341\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n<h2 id=\"np\"><code>&lt;txp:smd_lately&gt;</code></h2>\n\n<p>Place this tag anywhere you wish to display a list of recently viewed articles. The tag may be used as a single tag (with an optional form) or as a container. Inside the form/container you can use any standard article tags &#8212; <code>&lt;txp:title /&gt;</code>, <code>&lt;txp:excerpt /&gt;</code>, <code>&lt;txp:posted /&gt;</code>, etc &#8212; to format the list however you like. Without either a form or a container, the plugin displays a hyperlinked title to the article.</p>\n\n<p>The following attributes can be used to modify the plugin&#8217;s output:</p>\n\n<h3 class=\"atts\">Attributes</h3>\n\n<h4>Log filtering and sort order attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">by</span> : use <code>by=&quot;&quot;</code> or <code>by=&quot;SMD_ALL&quot;</code> to view the most recent articles by all site visitors. By default (i.e. if the attribute is omitted) the plugin only shows the recent articles viewed by the current visitor</li>\n		<li><span class=\"atnm\">section</span> : choose to show articles only from this list of sections. Default: unset (i.e. from all sections). <span class=\"required\">Note that you must be using either messy mode or a permlink mode with <code>/section</code> in it to filter by section</span></li>\n		<li><span class=\"atnm\">show_current</span> : Display the article being viewed in the list: 1=Yes; 0=No. Ignored if in an article_list page. Default: 0</li>\n		<li><span class=\"atnm\">include</span> : ensure that log file entries that match this set of criteria are included in the list. This attribute takes up to 3 parameters, separated by <code>param_delim</code> (default is the colon).\n		<ul>\n			<li>First is the name of a field to match against. Choose from <code>ip</code> (the default), <code>host</code>, <code>page</code> or <code>refer</code></li>\n			<li>Second is an optional parameter to indicate whether the match is a regular expression, wild or an exact match. Use <code>regex</code> if you wish this match to be considered as a regular expression, specify <code>like</code> if you want to check if the text is a simple &#8216;wild&#8217; match (it&#8217;s similar to, but quicker than, a regex), or omit the parameter entirely if you want an &#8216;exact match&#8217; to be considered</li>\n			<li>Finally, specify the text you want to match with that field. For example, <code>include=&quot;host:like:www.domain.com&quot;</code> would only include results that had <code>www.domain.com</code> somewhere in their host name. Using <code>include=&quot;192.168.2.200&quot;</code> would show pages from any internal meddling you may have done on your local <span class=\"caps\">XAMPP</span> server. Note that if you omit parameters 1 and 2, the plugin uses the defaults</li>\n		</ul></li>\n		<li><span class=\"atnm\">exclude</span> : ensure that log file entries that match this set of criteria are <strong>not</strong> included in the list. Use this in the same manner as <code>include</code></li>\n		<li><span class=\"atnm\">from</span> : date / time stamp (written in English) of the <em>earliest</em> date to consider in the logs. You may specify <code>?month</code> and/or <code>?year</code> to have the plugin replace it with the current month/year. Default: unset</li>\n		<li><span class=\"atnm\">to</span> : date / time stamp (written in English) of the <em>most recent</em> date to consider in the logs. You may specify <code>?month</code> and/or <code>?year</code> to have the plugin replace it with the current month/year. Default: current time</li>\n		<li><span class=\"atnm\">within</span> : date offset (written in English) which specifies a time window in which you are interested. For example <code>within=&quot;36 hours&quot;</code> would show results from the last 36 hours. If you specify a <code>to</code> date, the value of <code>within</code> is subtracted from it. If you specify a <code>from</code> date, the value of <code>within</code> is <em>added</em> to it. If you specify both <code>from</code> and <code>to</code>, the offset is calculated relative to the <code>to</code> attribute. Default: unset</li>\n		<li><span class=\"atnm\">sort</span> : order the list by either <code>time</code>, or <code>popularity</code>. Add <code>asc</code> or <code>desc</code> to choose ascending or descending sort order. Default: <code>time desc</code></li>\n		<li><span class=\"atnm\">limit</span> : show this many items in the list. 0 = unlimited. Default: 10</li>\n	</ul>\n\n<h4>Article filter attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">status</span> : Only articles with one of these listed status values are displayed in the list. Default: 4 (<code>live</code>)</li>\n		<li><span class=\"atnm\">time</span> : If an article in the log has its posted timestamp in the time period indicated in this attribute, it will be displayed. Choose from <code>any</code>, <code>future</code> or <code>past</code> (the default). Leaving this at its default prevents any future-dated articles that you may be previewing from showing up in the list</li>\n	</ul>\n\n<h4>Display attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag, without its brackets, to wrap the list with. Default: unset</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> class name to apply to the wraptag. Default: <code>smd_lately</code></li>\n		<li><span class=\"atnm\">active_class</span> : the <span class=\"caps\">CSS</span> class name to set as the active class when <code>show_current=&quot;1&quot;</code> is used. Default: <code>active</code>. See <a href=\"#reps\">replacement variables</a> for details on how to actually insert this into your markup</li>\n		<li><span class=\"atnm\">break</span> : the (X)HTML tag, without its brackets, to wrap each item with. Default: <code>br</code></li>\n		<li><span class=\"atnm\">label</span> : the label text to add to the top of the list. Default: unset</li>\n		<li><span class=\"atnm\">labeltag</span> : the (X)HTML tag, without its brackets, to wrap the label with. Default: unset</li>\n	</ul>\n\n<h4>Plugin configuration attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">form</span> : if you prefer to use a form instead of the container to hold your markup and tags, specify it here. Default: unset</li>\n		<li><span class=\"atnm\">delim</span> : the delimiter to use between items in attribute lists (<code>section</code>, <code>include</code>, <code>exclude</code>, <code>status</code>). Default: comma</li>\n		<li><span class=\"atnm\">param_delim</span> : the delimiter to use between parameters inside an attribute (<code>include</code>, <code>exclude</code>). Default: colon</li>\n		<li><span class=\"atnm\">cache_time</span> : if set, the results are cached in a temporary file for the designated number of seconds. Subsequent calls to smd_lately (e.g. refreshing the page) will read the cached information instead of trawling the logs, thus cutting down on server load. After <code>cache_time</code> seconds have elapsed, the next page refresh will cause the information to be recalculated. Note that the file name of the cached data is of the form <code>smd_lately_data_ABCDEF</code> where <span class=\"caps\">ABCDEF</span> is a unique string that applies to this particular smd_lately tag. If you alter the attributes there is a good chance it will create a new temporary file and the plugin does not clean up after itself. For this reason it&#8217;s probably a good idea to play with the plugin and set your attributes up before setting <code>cache_time</code></li>\n	</ul>\n\n<h3 class=\"atts\" id=\"reps\">Replacement variables</h3>\n\n<p>In addition to regular <span class=\"caps\">TXP</span> article tags, you may employ any of the following codes in your form/container to display the corresponding value:</p>\n\n	<ul>\n		<li><span class=\"atnm\">{smd_lately_activeclass}</span> : the raw classname you specified in your <code>active_class</code> attribute: only set if current article matches</li>\n		<li><span class=\"atnm\">{smd_lately_active}</span> : a full <code> class=&quot;class_name&quot;</code> string: only set if current article matches</li>\n		<li><span class=\"atnm\">{smd_lately_count}</span> : the number of times the article has been accessed</li>\n		<li><span class=\"atnm\">{smd_lately_fulldate}</span> : the article&#8217;s last access date stamp</li>\n		<li><span class=\"atnm\">{smd_lately_date}</span> : the article&#8217;s last access date</li>\n		<li><span class=\"atnm\">{smd_lately_date_year}</span> : the article&#8217;s last access year</li>\n		<li><span class=\"atnm\">{smd_lately_date_month}</span> : the article&#8217;s last access month (number)</li>\n		<li><span class=\"atnm\">{smd_lately_date_monthname}</span> : the article&#8217;s last access month (full name)</li>\n		<li><span class=\"atnm\">{smd_lately_date_day}</span> : the article&#8217;s last access day (number)</li>\n		<li><span class=\"atnm\">{smd_lately_date_dayname}</span> : the article&#8217;s last access day (full name)</li>\n		<li><span class=\"atnm\">{smd_lately_time}</span> : the article&#8217;s last access time stamp</li>\n		<li><span class=\"atnm\">{smd_lately_time_hour}</span> : the article&#8217;s last access hour</li>\n		<li><span class=\"atnm\">{smd_lately_time_minute}</span> : the article&#8217;s last access minute</li>\n		<li><span class=\"atnm\">{smd_lately_time_second}</span> : the article&#8217;s last access second</li>\n	</ul>\n\n<h3 id=\"caveats\">Caveats</h3>\n\n<p>Since the plugin uses the <span class=\"caps\">TXP</span> logs you need to make sure they are being used (Admin-&gt;Basic Prefs). It also means that any visitors using an anonymising proxy will get spurious (or no) results. Their loss.</p>\n\n<h2 class=\"examples\">Examples</h2>\n\n<h3 id=\"eg1\">Example 1: recent article list for current visitor</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately /&gt;\n</code></pre>\n\n<h3 id=\"eg2\">Example 2: recent articles for all visitors</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately by=&quot;&quot; section=&quot;archive, about&quot; /&gt;\n</code></pre>\n\n<p>Only shows the articles viewed from the &#8216;archive&#8217; and &#8216;about&#8217; sections.</p>\n\n<h3 id=\"eg3\">Example 3: most popular articles across the site</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately by=&quot;&quot; sort=&quot;popularity&quot;  /&gt;\n</code></pre>\n\n<p>Without using the <code>by</code> attribute, this would give the most popular articles for the current visitor &#8212; probably not what you want!</p>\n\n<h3 id=\"eg4\">Example 4: tag as a container</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately by=&quot;&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot; limit=&quot;6&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt; [{smd_lately_count}]&lt;br /&gt;\n   &lt;txp:posted /&gt; by &lt;txp:author /&gt;\n&lt;/txp:smd_lately&gt;\n</code></pre>\n\n<p>Shows the 6 most recent articles accessed by any site visitor. The unordered list output contains a permlinked title, the date the article was posted and the article&#8217;s author.</p>\n\n<h3 id=\"eg5\">Example 5: filtering by host</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately by=&quot;&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot; limit=&quot;6&quot;\n     exclude=&quot;host:id3456-bt.custref.com, refer:&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt; [{smd_lately_count}]&lt;br /&gt;\n&lt;/txp:smd_lately&gt;\n</code></pre>\n\n<p>Shows only records that have some referer information (i.e. we are excluding entries where refer = empty) and those that have <span class=\"caps\">NOT</span> been accessed via the unique hostname given to you by your <span class=\"caps\">ISP</span> &#8212; assuming <code>id3456-bt.custref.com</code> shows up in the &#8216;host&#8217; column whenever you access a <span class=\"caps\">TXP</span> article.</p>\n\n<p>If you adjusted the exclude attribute to read <code>exclude=&quot;host:like:bt.custref&quot;</code> then any log entries with <code>bt.custref</code> in them would be excluded from consideration.</p>\n\n<h3 id=\"eg6\">Example 6: filtering by time range</h3>\n\n<pre class=\"block\"><code>&lt;txp:smd_lately by=&quot;&quot;\n     wraptag=&quot;ul&quot; break=&quot;li&quot;\n     from=&quot;?year-?month-01&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt; [{smd_lately_count}]&lt;br /&gt;\n&lt;/txp:smd_lately&gt;\n</code></pre>\n\n<p>Shows only the hits generated from the first day of the current month until now. The recent list will thus &#8216;reset&#8217; on the 1st of every month.</p>\n\n<p>If you removed the <code>from</code> attribute and used <code>within=&quot;30 days&quot;</code> you would instead see a &#8216;rolling&#8217; total of the hits in the previous 30 days (you could also use <code>within=&quot;1 month&quot;</code> to get roughly the same result).</p>\n\n<h3 id=\"changelog\">Changelog</h3>\n\n	<ul>\n		<li>23 Jul 09 | 0.10 | Initial release</li>\n		<li>24 Jul 09 | 0.11 | Made backwards compatible with <span class=\"caps\">TXP</span> 4.0.8</li>\n		<li>10 Aug 09 | 0.12 | Never assume what remains in the filtered <span class=\"caps\">URL</span> is actually an article</li>\n		<li>11 Sep 09 | 0.13 | Added replacement vars</li>\n		<li>19 Feb 10 | 0.20 | Added <code>status</code>, <code>time</code>, <code>include</code>, <code>exclude</code>, <code>delim</code>, and <code>param_delim</code> ; article list now filtered by <code>time=&quot;past&quot;</code> and <code>status=&quot;4&quot;</code> by default</li>\n		<li>10 May 10 | 0.21 | Added <code>from</code>, <code>to</code> and <code>within</code> (thanks the_ghost) ; added <code>like</code> matching ; removed some power-hungry <span class=\"caps\">REGEXP</span> operators in favour of <span class=\"caps\">LIKE</span> to improve performance</li>\n		<li>28 May 10 | 0.22 | Fixed default <code>class</code> attribute and removed trailing slash in log pages (thanks maniqui)</li>\n		<li>10 Sep 10 | 0.30 | Added <code>cache_time</code> (thanks pieman) ; now only uses two queries regardless of number of results ; improved messy <span class=\"caps\">URL</span> support ; fixed time display problems using <code>NOW()</code> and added <code>active_class</code> (both thanks jelle)</li>\n		<li>13 Mar 14 | 0.31 | Added filtering by method (thanks kees-b)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n#smd_help ul { list-style-type:square; }\n#smd_help .required {color:red;}\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_lately\r\n *\r\n * A Textpattern CMS plugin for displaying recent visitor stats, or page hits.\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\nfunction smd_lately($atts, $thing=NULL) {\r\n	global $prefs, $thisarticle, $permlink_mode;\r\n\r\n	// Check logging is on\r\n	if ( $prefs[\'logging\'] != \'all\') {\r\n		trigger_error(smd_rv_gTxt(\'logging_enabled\'), E_USER_NOTICE);\r\n		return;\r\n	}\r\n\r\n	extract(lAtts(array(\r\n		\'by\' => \'SMD_CURRENT\', // Default is the IP address of the current visitor. Can be empty for \'all\' visitors\r\n		\'section\' => \'\',\r\n		\'status\' => \'\',\r\n		\'time\' => \'past\',\r\n		\'include\' => \'\',\r\n		\'exclude\' => \'\',\r\n		\'within\' => \'\',\r\n		\'from\' => \'\',\r\n		\'to\' => \'\',\r\n		\'show_current\' => 0,\r\n		\'form\' => \'\',\r\n		\'limit\' => 10,\r\n		\'sort\' => \'time desc\',\r\n		\'wraptag\' => \'\',\r\n		\'break\' => \'br\',\r\n		\'class\' => __FUNCTION__,\r\n		\'active_class\' => \'active\',\r\n		\'label\' => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'delim\' => \',\',\r\n		\'param_delim\' => \':\',\r\n		\'silent\' => 0,\r\n		\'cache_time\' => \'0\', // in seconds\r\n		\'hashsize\' => \'6:5\',\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	// Make a unique hash value for this instance so the results can be cached in a file\r\n	$uniq = \'\';\r\n	$md5 = md5($by.$sort.$section.$within.$from.$to.$include.$exclude);\r\n	list($hashLen, $hashSkip) = explode(\':\', $hashsize);\r\n	for ($idx = 0, $cnt = 0; $cnt < $hashLen; $cnt++, $idx = (($idx+$hashSkip) % strlen($md5))) {\r\n		$uniq .= $md5[$idx];\r\n	}\r\n\r\n	$var_lastmod = \'smd_lately_lmod_\'.$uniq;\r\n	$var_data = find_temp_dir().DS.\'smd_lately_data_\'.$uniq;\r\n	$lastmod = get_pref($var_lastmod, 0);\r\n	$read_cache = (($cache_time > 0) && ((time() - $lastmod) < $cache_time)) ? true : false;\r\n\r\n	// Sanitize sort options\r\n	$sortBits = do_list($sort, \" \");\r\n	$sortBits[0] = (empty($sortBits[0])) ? \'time\' : $sortBits[0];\r\n	if (!isset($sortBits[1]) || !in_array($sortBits[1], array(\'desc\', \'asc\'))) {\r\n		$sortBits[1] = \'desc\';\r\n		$sort = join(\" \", $sortBits);\r\n	}\r\n\r\n	// Make a note of the current article if appropriate\r\n	if ($permlink_mode==\'messy\') {\r\n		$match_col = \'ID\';\r\n		$match_tacol = \'thisid\';\r\n		$match_prefix = \'id=\';\r\n	} else {\r\n		$match_col = $match_tacol = \'url_title\';\r\n		$match_prefix = \'\';\r\n	}\r\n	$curr_art = ($thisarticle) ? $thisarticle[$match_tacol] : \'\';\r\n\r\n	if ($read_cache) {\r\n		$rs = unserialize(file_get_contents($var_data));\r\n	} else {\r\n		// IP address clause\r\n		$ip = \'\';\r\n		if ($by == \'SMD_CURRENT\') {\r\n			if (function_exists(\'remote_addr\')) {\r\n				$ip = remote_addr();\r\n			} else {\r\n				$ip = serverSet(\'REMOTE_ADDR\');\r\n				if (($ip == \'127.0.0.1\' || $ip == serverSet(\'SERVER_ADDR\')) && serverSet(\'HTTP_X_FORWARDED_FOR\')) {\r\n					$ips = explode(\', \', serverSet(\'HTTP_X_FORWARDED_FOR\'));\r\n					$ip = $ips[0];\r\n				}\r\n			}\r\n			if ($ip) {\r\n				$ip = \" AND ip=\'\".doSlash($ip).\"\'\";\r\n			}\r\n		} else if ($by == \'SMD_ALL\') {\r\n			$by = \'\';\r\n		}\r\n\r\n		// Make sure we don\'t include the current article\r\n		// Note the regexp is anchored to the end with a $\r\n		$thisicle = \'\';\r\n		if (isset($thisarticle)) {\r\n			if (!$show_current) {\r\n				$urltitle = $thisarticle[$match_tacol];\r\n				$thisicle = ($urltitle) ? \" AND page NOT LIKE \'%$match_prefix$urltitle\'\" : \'\';\r\n			}\r\n		}\r\n\r\n		// Filter out article_list pages and other (un)desirables\r\n		$rules = array(\r\n			\"page NOT LIKE \'/\'\", // Goodbye front page\r\n			\"page NOT LIKE \'\'\",\r\n			\"page NOT LIKE \'%q=%\'\", // Goodbye searches\r\n			\"page NOT LIKE \'%c=%\'\", // Goodbye cat lists\r\n			\"page NOT LIKE \'%pg=%\'\", // Goodbye multi-page lists\r\n			\"page NOT LIKE \'%category=%\'\",\r\n			\"page NOT LIKE \'/category/%\'\",\r\n			\"page NOT LIKE \'%author=%\'\",\r\n			\"page NOT LIKE \'/author/%\'\",\r\n		);\r\n\r\n		if ($section) {\r\n			$section = do_list($section, $delim);\r\n			$subrule = array();\r\n			foreach ($section as $sec) {\r\n				if ($permlink_mode == \'messy\') {\r\n					$subrule[] = \"page LIKE \'%s=$sec%\'\";\r\n				} else {\r\n					$subrule[] = \"page LIKE \'/$sec%\' AND page NOT REGEXP \'^/$sec/?$\'\";\r\n				}\r\n			}\r\n			$subrule = \'(\' . join(\' OR \', $subrule) . \')\';\r\n			$rules[] = $subrule;\r\n		} else {\r\n			// Exclude any rows that just contain the section (i.e. article list pages)\r\n			$allSecs = safe_column(\'name\', \'txp_section\', \"1=1\", $debug);\r\n			$rules[] = \"page NOT REGEXP \'^/(\" . join(\"|\", $allSecs) . \")/?$\'\";\r\n		}\r\n\r\n		// Process any manual includes\r\n		if ($include) {\r\n			$include = do_list($include, $delim);\r\n			$subrules = array();\r\n			foreach ($include as $inc) {\r\n				$regex = $like = false;\r\n				$column = \'ip\';\r\n				$parts = do_list($inc, $param_delim);\r\n				$match = array_pop($parts);\r\n				foreach ($parts as $part) {\r\n					switch ($part) {\r\n						case \"ip\":\r\n						case \"host\":\r\n						case \"page\":\r\n						case \"refer\":\r\n						case \"method\":\r\n							$column = $part;\r\n							break;\r\n						case \"like\":\r\n							$like = true;\r\n							break;\r\n						case \"regex\":\r\n							$regex = true;\r\n							break;\r\n					}\r\n				}\r\n				$subrules[] = $regex ? \"$column REGEXP \'\".doSlash(preg_quote($match)).\"\'\" : (($like) ? \"$column LIKE \'%\".doSlash($match).\"%\'\" : \"$column = \'\".doSlash($match).\"\'\");\r\n			}\r\n			$rules[] = \'(\'.join(\' OR \', $subrules).\')\';\r\n		}\r\n\r\n		// Process any manual excludes\r\n		if ($exclude) {\r\n			$exclude = do_list($exclude, $delim);\r\n			foreach ($exclude as $exc) {\r\n				$regex = $like = false;\r\n				$column = \'ip\';\r\n				$parts = do_list($exc, $param_delim);\r\n				$match = array_pop($parts);\r\n				foreach ($parts as $part) {\r\n					switch ($part) {\r\n						case \"ip\":\r\n						case \"host\":\r\n						case \"page\":\r\n						case \"refer\":\r\n						case \"method\":\r\n							$column = $part;\r\n							break;\r\n						case \"like\":\r\n							$like = true;\r\n							break;\r\n						case \"regex\":\r\n							$regex = true;\r\n							break;\r\n					}\r\n				}\r\n				$rules[] = $regex ? \"$column NOT REGEXP \'\".doSlash(preg_quote($match)).\"\'\" : (($like) ? \"$column NOT LIKE \'%\".doSlash($match).\"%\'\" : \"$column != \'\".doSlash($match).\"\'\");\r\n			}\r\n		}\r\n\r\n		// Filter by time frame\r\n		$fromstamp = $tostamp = $diffstamp = 0;\r\n		$thismonth = date(\'F\', mktime(0,0,0,strftime(\'%m\'),1));\r\n		$thisyear = strftime(\'%Y\');\r\n		if ($from) {\r\n			$from = str_replace(\"?month\", $thismonth, $from);\r\n			$from = str_replace(\"?year\", $thisyear, $from);\r\n			$fromstamp = strtotime($from);\r\n			if ($fromstamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) > $fromstamp\";\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'from\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n		if ($to) {\r\n			$to = str_replace(\"?month\", $thismonth, $to);\r\n			$to = str_replace(\"?year\", $thisyear, $to);\r\n			$tostamp = strtotime($to);\r\n			if ($tostamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) < $tostamp\";\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'to\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n		if ($within) {\r\n			if ($tostamp) {\r\n				$refstamp = $tostamp;\r\n				$refdir = \'-\';\r\n			} else if ($fromstamp) {\r\n				$refstamp = $fromstamp;\r\n				$refdir = \'+\';\r\n			} else {\r\n				$refstamp = time();\r\n				$refdir = \'-\';\r\n			}\r\n\r\n			$diffstamp = strtotime($refdir.$within, $refstamp);\r\n			if ($diffstamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) \".(($refdir == \'-\') ? \'> \' : \'< \' ).$diffstamp;\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'within\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n\r\n		if ($debug) {\r\n			echo \"++ smd_lately RULES ++\";\r\n			dmp($rules);\r\n			if ($from || $to || $within) {\r\n				echo \"++ TIME WINDOW ++\";\r\n				if ($from) {\r\n					dmp(\"FROM: \" . date(\'Y-M-d H:i:s\', $fromstamp));\r\n				}\r\n				if ($within) {\r\n					dmp(\"WITHIN: \" . date(\'Y-M-d H:i:s\', $diffstamp), $within . (($refdir == \'-\') ? \" BEFORE TO DATE\" : \" AFTER FROM DATE\"));\r\n				}\r\n				if ($to) {\r\n					dmp(\"TO: \". date(\'Y-M-d H:i:s\', $tostamp));\r\n				} else {\r\n					dmp(\"TO: \". date(\'Y-M-d H:i:s\', time()));\r\n				}\r\n			}\r\n		}\r\n\r\n		$rules = \' AND \' . join(\' AND \', $rules);\r\n\r\n		$query = \'SELECT count(page) as popularity, page, MAX(time) as time FROM \'.PFX.\'txp_log WHERE 1=1\'.$ip.$thisicle.$rules.\' AND status = 200 GROUP BY page ORDER BY \'.$sort;\r\n		$rs = getRows($query, $debug);\r\n\r\n		// Store the current document in the cache and datestamp it\r\n		if ($cache_time > 0) {\r\n			if ($debug > 1) {\r\n				dmp(\'++ DATA CACHED to \'.$var_data.\' ++\');\r\n			}\r\n			set_pref($var_lastmod, time(), \'smd_lately\', PREF_HIDDEN, \'text_input\');\r\n			$fh = fopen($var_data, \'wb\');\r\n			fwrite($fh, serialize($rs));\r\n			fclose($fh);\r\n		}\r\n	}\r\n\r\n	if ($debug > 1) {\r\n		dmp($rs);\r\n	}\r\n\r\n	// Set up counters and create query params\r\n	$count = 0;\r\n	$out = array();\r\n\r\n	if ($status) {\r\n		$status = do_list($status, $delim);\r\n		$stati = array();\r\n		foreach ($status as $stat) {\r\n			if (empty($stat)) {\r\n				continue;\r\n			} else if (is_numeric($stat)) {\r\n				$stati[] = $stat;\r\n			} else {\r\n				$stati[] = getStatusNum($stat);\r\n			}\r\n		}\r\n		$status = \" AND Status IN (\".join(\',\', $stati).\")\";\r\n	}\r\n\r\n	switch($time) {\r\n		case \"\":\r\n		case \"any\" : $time = \"\"; break;\r\n		case \"future\" : $time = \" AND Posted > NOW()\"; break;\r\n		default : $time = \" AND Posted < NOW()\"; break;\r\n	}\r\n\r\n	// Process the result set\r\n	$articles = array();\r\n	if ($rs) {\r\n		// Loop until limit reached\r\n		foreach ($rs as $row) {\r\n			if ($limit > 0 && is_numeric($limit) && $count == $limit) break;\r\n			if ($permlink_mode == \'messy\') {\r\n				preg_match(\'@id=(\\d+)@\', $row[\'page\'], $matches);\r\n				$da_url = isset($matches[1]) ? \'id\'.$matches[1] :  \'\'; // Add \'id\' to trick array_multisort into believing its an associative array\r\n			} else {\r\n				$justurl = explode(\'?\',$row[\'page\']); // Strip off any query params\r\n				$urlpart = explode(\'/\', rtrim($justurl[0],\"/\"));\r\n				$da_url = $urlpart[count($urlpart)-1];\r\n			}\r\n			if ($da_url) {\r\n				if (isset($articles[$da_url])) {\r\n					$articles[$da_url][0] += $row[\'popularity\'];\r\n					if ($row[\'time\'] > $articles[$da_url][1]) {\r\n						$articles[$da_url][1] = $row[\'time\'];\r\n					}\r\n				} else {\r\n					$articles[$da_url] = array(\r\n						$row[\'popularity\'],\r\n						$row[\'time\'],\r\n					);\r\n					$count++;\r\n				}\r\n			}\r\n		}\r\n\r\n		if ($debug > 1) {\r\n			echo \'++ ORIGINAL LOG LIST ++\';\r\n			dmp($articles);\r\n		}\r\n\r\n		if ($articles) {\r\n			// If sorting by popularity, re-order the results in case they\'ve been subsequently aggregated\r\n			if($sortBits[0] == \'popularity\') {\r\n				foreach ($articles as $key => $row) {\r\n					$apop[$key]  = $row[0];\r\n					$atime[$key] = $row[1];\r\n				}\r\n				$dir = ($sortBits[1] == \'asc\') ? SORT_ASC : SORT_DESC;\r\n				array_multisort($apop, $dir, $atime, $dir, $articles);\r\n			}\r\n			if ($permlink_mode == \'messy\') {\r\n				// Strip off the fake \'id\' identifiers in the array keys\r\n				foreach ($articles as $key => $row) {\r\n					$tmp[substr($key, 2)] = $row;\r\n				}\r\n				$articles = $tmp;\r\n			}\r\n			if ($debug > 2) {\r\n				echo \'++ FINAL LOG LIST++\';\r\n				dmp($articles);\r\n			}\r\n			$darticles = safe_rows(\'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\', \'textpattern\', \"$match_col IN (\'\" . join(\"\',\'\", array_keys($articles)) . \"\')\".$status.$time, $debug);\r\n\r\n			// Refactor the article list keyed on url_title or ID (depending on permlink_mode)\r\n			$urlicles = array();\r\n			foreach ($darticles as $darticle) {\r\n				$urlicles[$darticle[$match_col]] = $darticle;\r\n			}\r\n\r\n			// Iterate over the _original_ article list (from the first query)\r\n			// and pluck out the relevant article contents\r\n			foreach ($articles as $urlTitle => $darticle) {\r\n				if (isset($urlicles[$urlTitle])) {\r\n					$theTime = strtotime($darticle[1]);\r\n					$aktiv = $thisarticle && $show_current && $urlTitle == $curr_art;\r\n					$replacements = array(\r\n						\"{smd_lately_active}\" => (($aktiv) ? \' class=\"\'.$active_class.\'\"\' : \'\'),\r\n						\"{smd_lately_activeclass}\" => (($aktiv) ? $active_class : \'\'),\r\n						\"{smd_lately_count}\" => $darticle[0],\r\n						\"{smd_lately_fulldate}\" => $darticle[1],\r\n						\"{smd_lately_date}\" => strftime(\"%F\", $theTime),\r\n						\"{smd_lately_date_year}\" => strftime(\"%Y\", $theTime),\r\n						\"{smd_lately_date_month}\" => strftime(\"%m\", $theTime),\r\n						\"{smd_lately_date_monthname}\" => strftime(\"%B\", $theTime),\r\n						\"{smd_lately_date_day}\" => strftime(\"%d\", $theTime),\r\n						\"{smd_lately_date_dayname}\" => strftime(\"%A\", $theTime),\r\n						\"{smd_lately_time}\" => strftime(\"%T\", $theTime),\r\n						\"{smd_lately_time_hour}\" => strftime(\"%H\", $theTime),\r\n						\"{smd_lately_time_minute}\" => strftime(\"%M\", $theTime),\r\n						\"{smd_lately_time_second}\" => strftime(\"%S\", $theTime),\r\n					);\r\n					article_push();\r\n					populateArticleData($urlicles[$urlTitle]);\r\n					$out[] = ($thing) ? parse(strtr($thing, $replacements)) : (($form) ? parse_form(strtr($form, $replacements)) : href( $urlicles[$urlTitle][\'Title\'], permlinkurl($urlicles[$urlTitle]), $replacements[\'{smd_lately_active}\'] ));\r\n					article_pop();\r\n				}\r\n			}\r\n		}\r\n	}\r\n	return ($out) ? doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class) : \'\';\r\n}\r\n\r\n// ------------------------\r\n// Plugin-specific replacement strings - localise as required\r\nfunction smd_rv_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'invalid_ts\' => \'Invalid date/time info in \"{where}\" attribute.\',\r\n		\'logging_enabled\' => \'Logging must be set to \"All hits\" in Basic Pefs.\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', '/**\r\n * smd_lately\r\n *\r\n * A Textpattern CMS plugin for displaying recent visitor stats, or page hits.\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\nfunction smd_lately($atts, $thing=NULL) {\r\n	global $prefs, $thisarticle, $permlink_mode;\r\n\r\n	// Check logging is on\r\n	if ( $prefs[\'logging\'] != \'all\') {\r\n		trigger_error(smd_rv_gTxt(\'logging_enabled\'), E_USER_NOTICE);\r\n		return;\r\n	}\r\n\r\n	extract(lAtts(array(\r\n		\'by\' => \'SMD_CURRENT\', // Default is the IP address of the current visitor. Can be empty for \'all\' visitors\r\n		\'section\' => \'\',\r\n		\'status\' => \'\',\r\n		\'time\' => \'past\',\r\n		\'include\' => \'\',\r\n		\'exclude\' => \'\',\r\n		\'within\' => \'\',\r\n		\'from\' => \'\',\r\n		\'to\' => \'\',\r\n		\'show_current\' => 0,\r\n		\'form\' => \'\',\r\n		\'limit\' => 10,\r\n		\'sort\' => \'time desc\',\r\n		\'wraptag\' => \'\',\r\n		\'break\' => \'br\',\r\n		\'class\' => __FUNCTION__,\r\n		\'active_class\' => \'active\',\r\n		\'label\' => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'delim\' => \',\',\r\n		\'param_delim\' => \':\',\r\n		\'silent\' => 0,\r\n		\'cache_time\' => \'0\', // in seconds\r\n		\'hashsize\' => \'6:5\',\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	// Make a unique hash value for this instance so the results can be cached in a file\r\n	$uniq = \'\';\r\n	$md5 = md5($by.$sort.$section.$within.$from.$to.$include.$exclude);\r\n	list($hashLen, $hashSkip) = explode(\':\', $hashsize);\r\n	for ($idx = 0, $cnt = 0; $cnt < $hashLen; $cnt++, $idx = (($idx+$hashSkip) % strlen($md5))) {\r\n		$uniq .= $md5[$idx];\r\n	}\r\n\r\n	$var_lastmod = \'smd_lately_lmod_\'.$uniq;\r\n	$var_data = find_temp_dir().DS.\'smd_lately_data_\'.$uniq;\r\n	$lastmod = get_pref($var_lastmod, 0);\r\n	$read_cache = (($cache_time > 0) && ((time() - $lastmod) < $cache_time)) ? true : false;\r\n\r\n	// Sanitize sort options\r\n	$sortBits = do_list($sort, \" \");\r\n	$sortBits[0] = (empty($sortBits[0])) ? \'time\' : $sortBits[0];\r\n	if (!isset($sortBits[1]) || !in_array($sortBits[1], array(\'desc\', \'asc\'))) {\r\n		$sortBits[1] = \'desc\';\r\n		$sort = join(\" \", $sortBits);\r\n	}\r\n\r\n	// Make a note of the current article if appropriate\r\n	if ($permlink_mode==\'messy\') {\r\n		$match_col = \'ID\';\r\n		$match_tacol = \'thisid\';\r\n		$match_prefix = \'id=\';\r\n	} else {\r\n		$match_col = $match_tacol = \'url_title\';\r\n		$match_prefix = \'\';\r\n	}\r\n	$curr_art = ($thisarticle) ? $thisarticle[$match_tacol] : \'\';\r\n\r\n	if ($read_cache) {\r\n		$rs = unserialize(file_get_contents($var_data));\r\n	} else {\r\n		// IP address clause\r\n		$ip = \'\';\r\n		if ($by == \'SMD_CURRENT\') {\r\n			if (function_exists(\'remote_addr\')) {\r\n				$ip = remote_addr();\r\n			} else {\r\n				$ip = serverSet(\'REMOTE_ADDR\');\r\n				if (($ip == \'127.0.0.1\' || $ip == serverSet(\'SERVER_ADDR\')) && serverSet(\'HTTP_X_FORWARDED_FOR\')) {\r\n					$ips = explode(\', \', serverSet(\'HTTP_X_FORWARDED_FOR\'));\r\n					$ip = $ips[0];\r\n				}\r\n			}\r\n			if ($ip) {\r\n				$ip = \" AND ip=\'\".doSlash($ip).\"\'\";\r\n			}\r\n		} else if ($by == \'SMD_ALL\') {\r\n			$by = \'\';\r\n		}\r\n\r\n		// Make sure we don\'t include the current article\r\n		// Note the regexp is anchored to the end with a $\r\n		$thisicle = \'\';\r\n		if (isset($thisarticle)) {\r\n			if (!$show_current) {\r\n				$urltitle = $thisarticle[$match_tacol];\r\n				$thisicle = ($urltitle) ? \" AND page NOT LIKE \'%$match_prefix$urltitle\'\" : \'\';\r\n			}\r\n		}\r\n\r\n		// Filter out article_list pages and other (un)desirables\r\n		$rules = array(\r\n			\"page NOT LIKE \'/\'\", // Goodbye front page\r\n			\"page NOT LIKE \'\'\",\r\n			\"page NOT LIKE \'%q=%\'\", // Goodbye searches\r\n			\"page NOT LIKE \'%c=%\'\", // Goodbye cat lists\r\n			\"page NOT LIKE \'%pg=%\'\", // Goodbye multi-page lists\r\n			\"page NOT LIKE \'%category=%\'\",\r\n			\"page NOT LIKE \'/category/%\'\",\r\n			\"page NOT LIKE \'%author=%\'\",\r\n			\"page NOT LIKE \'/author/%\'\",\r\n		);\r\n\r\n		if ($section) {\r\n			$section = do_list($section, $delim);\r\n			$subrule = array();\r\n			foreach ($section as $sec) {\r\n				if ($permlink_mode == \'messy\') {\r\n					$subrule[] = \"page LIKE \'%s=$sec%\'\";\r\n				} else {\r\n					$subrule[] = \"page LIKE \'/$sec%\' AND page NOT REGEXP \'^/$sec/?$\'\";\r\n				}\r\n			}\r\n			$subrule = \'(\' . join(\' OR \', $subrule) . \')\';\r\n			$rules[] = $subrule;\r\n		} else {\r\n			// Exclude any rows that just contain the section (i.e. article list pages)\r\n			$allSecs = safe_column(\'name\', \'txp_section\', \"1=1\", $debug);\r\n			$rules[] = \"page NOT REGEXP \'^/(\" . join(\"|\", $allSecs) . \")/?$\'\";\r\n		}\r\n\r\n		// Process any manual includes\r\n		if ($include) {\r\n			$include = do_list($include, $delim);\r\n			$subrules = array();\r\n			foreach ($include as $inc) {\r\n				$regex = $like = false;\r\n				$column = \'ip\';\r\n				$parts = do_list($inc, $param_delim);\r\n				$match = array_pop($parts);\r\n				foreach ($parts as $part) {\r\n					switch ($part) {\r\n						case \"ip\":\r\n						case \"host\":\r\n						case \"page\":\r\n						case \"refer\":\r\n						case \"method\":\r\n							$column = $part;\r\n							break;\r\n						case \"like\":\r\n							$like = true;\r\n							break;\r\n						case \"regex\":\r\n							$regex = true;\r\n							break;\r\n					}\r\n				}\r\n				$subrules[] = $regex ? \"$column REGEXP \'\".doSlash(preg_quote($match)).\"\'\" : (($like) ? \"$column LIKE \'%\".doSlash($match).\"%\'\" : \"$column = \'\".doSlash($match).\"\'\");\r\n			}\r\n			$rules[] = \'(\'.join(\' OR \', $subrules).\')\';\r\n		}\r\n\r\n		// Process any manual excludes\r\n		if ($exclude) {\r\n			$exclude = do_list($exclude, $delim);\r\n			foreach ($exclude as $exc) {\r\n				$regex = $like = false;\r\n				$column = \'ip\';\r\n				$parts = do_list($exc, $param_delim);\r\n				$match = array_pop($parts);\r\n				foreach ($parts as $part) {\r\n					switch ($part) {\r\n						case \"ip\":\r\n						case \"host\":\r\n						case \"page\":\r\n						case \"refer\":\r\n						case \"method\":\r\n							$column = $part;\r\n							break;\r\n						case \"like\":\r\n							$like = true;\r\n							break;\r\n						case \"regex\":\r\n							$regex = true;\r\n							break;\r\n					}\r\n				}\r\n				$rules[] = $regex ? \"$column NOT REGEXP \'\".doSlash(preg_quote($match)).\"\'\" : (($like) ? \"$column NOT LIKE \'%\".doSlash($match).\"%\'\" : \"$column != \'\".doSlash($match).\"\'\");\r\n			}\r\n		}\r\n\r\n		// Filter by time frame\r\n		$fromstamp = $tostamp = $diffstamp = 0;\r\n		$thismonth = date(\'F\', mktime(0,0,0,strftime(\'%m\'),1));\r\n		$thisyear = strftime(\'%Y\');\r\n		if ($from) {\r\n			$from = str_replace(\"?month\", $thismonth, $from);\r\n			$from = str_replace(\"?year\", $thisyear, $from);\r\n			$fromstamp = strtotime($from);\r\n			if ($fromstamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) > $fromstamp\";\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'from\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n		if ($to) {\r\n			$to = str_replace(\"?month\", $thismonth, $to);\r\n			$to = str_replace(\"?year\", $thisyear, $to);\r\n			$tostamp = strtotime($to);\r\n			if ($tostamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) < $tostamp\";\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'to\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n		if ($within) {\r\n			if ($tostamp) {\r\n				$refstamp = $tostamp;\r\n				$refdir = \'-\';\r\n			} else if ($fromstamp) {\r\n				$refstamp = $fromstamp;\r\n				$refdir = \'+\';\r\n			} else {\r\n				$refstamp = time();\r\n				$refdir = \'-\';\r\n			}\r\n\r\n			$diffstamp = strtotime($refdir.$within, $refstamp);\r\n			if ($diffstamp) {\r\n				$rules[] = \"UNIX_TIMESTAMP(time) \".(($refdir == \'-\') ? \'> \' : \'< \' ).$diffstamp;\r\n			} else {\r\n				if (!$silent) {\r\n					trigger_error(smd_rv_gTxt(\'invalid_ts\', array(\"{where}\" => \'within\')), E_USER_NOTICE);\r\n				}\r\n			}\r\n		}\r\n\r\n		if ($debug) {\r\n			echo \"++ smd_lately RULES ++\";\r\n			dmp($rules);\r\n			if ($from || $to || $within) {\r\n				echo \"++ TIME WINDOW ++\";\r\n				if ($from) {\r\n					dmp(\"FROM: \" . date(\'Y-M-d H:i:s\', $fromstamp));\r\n				}\r\n				if ($within) {\r\n					dmp(\"WITHIN: \" . date(\'Y-M-d H:i:s\', $diffstamp), $within . (($refdir == \'-\') ? \" BEFORE TO DATE\" : \" AFTER FROM DATE\"));\r\n				}\r\n				if ($to) {\r\n					dmp(\"TO: \". date(\'Y-M-d H:i:s\', $tostamp));\r\n				} else {\r\n					dmp(\"TO: \". date(\'Y-M-d H:i:s\', time()));\r\n				}\r\n			}\r\n		}\r\n\r\n		$rules = \' AND \' . join(\' AND \', $rules);\r\n\r\n		$query = \'SELECT count(page) as popularity, page, MAX(time) as time FROM \'.PFX.\'txp_log WHERE 1=1\'.$ip.$thisicle.$rules.\' AND status = 200 GROUP BY page ORDER BY \'.$sort;\r\n		$rs = getRows($query, $debug);\r\n\r\n		// Store the current document in the cache and datestamp it\r\n		if ($cache_time > 0) {\r\n			if ($debug > 1) {\r\n				dmp(\'++ DATA CACHED to \'.$var_data.\' ++\');\r\n			}\r\n			set_pref($var_lastmod, time(), \'smd_lately\', PREF_HIDDEN, \'text_input\');\r\n			$fh = fopen($var_data, \'wb\');\r\n			fwrite($fh, serialize($rs));\r\n			fclose($fh);\r\n		}\r\n	}\r\n\r\n	if ($debug > 1) {\r\n		dmp($rs);\r\n	}\r\n\r\n	// Set up counters and create query params\r\n	$count = 0;\r\n	$out = array();\r\n\r\n	if ($status) {\r\n		$status = do_list($status, $delim);\r\n		$stati = array();\r\n		foreach ($status as $stat) {\r\n			if (empty($stat)) {\r\n				continue;\r\n			} else if (is_numeric($stat)) {\r\n				$stati[] = $stat;\r\n			} else {\r\n				$stati[] = getStatusNum($stat);\r\n			}\r\n		}\r\n		$status = \" AND Status IN (\".join(\',\', $stati).\")\";\r\n	}\r\n\r\n	switch($time) {\r\n		case \"\":\r\n		case \"any\" : $time = \"\"; break;\r\n		case \"future\" : $time = \" AND Posted > NOW()\"; break;\r\n		default : $time = \" AND Posted < NOW()\"; break;\r\n	}\r\n\r\n	// Process the result set\r\n	$articles = array();\r\n	if ($rs) {\r\n		// Loop until limit reached\r\n		foreach ($rs as $row) {\r\n			if ($limit > 0 && is_numeric($limit) && $count == $limit) break;\r\n			if ($permlink_mode == \'messy\') {\r\n				preg_match(\'@id=(\\d+)@\', $row[\'page\'], $matches);\r\n				$da_url = isset($matches[1]) ? \'id\'.$matches[1] :  \'\'; // Add \'id\' to trick array_multisort into believing its an associative array\r\n			} else {\r\n				$justurl = explode(\'?\',$row[\'page\']); // Strip off any query params\r\n				$urlpart = explode(\'/\', rtrim($justurl[0],\"/\"));\r\n				$da_url = $urlpart[count($urlpart)-1];\r\n			}\r\n			if ($da_url) {\r\n				if (isset($articles[$da_url])) {\r\n					$articles[$da_url][0] += $row[\'popularity\'];\r\n					if ($row[\'time\'] > $articles[$da_url][1]) {\r\n						$articles[$da_url][1] = $row[\'time\'];\r\n					}\r\n				} else {\r\n					$articles[$da_url] = array(\r\n						$row[\'popularity\'],\r\n						$row[\'time\'],\r\n					);\r\n					$count++;\r\n				}\r\n			}\r\n		}\r\n\r\n		if ($debug > 1) {\r\n			echo \'++ ORIGINAL LOG LIST ++\';\r\n			dmp($articles);\r\n		}\r\n\r\n		if ($articles) {\r\n			// If sorting by popularity, re-order the results in case they\'ve been subsequently aggregated\r\n			if($sortBits[0] == \'popularity\') {\r\n				foreach ($articles as $key => $row) {\r\n					$apop[$key]  = $row[0];\r\n					$atime[$key] = $row[1];\r\n				}\r\n				$dir = ($sortBits[1] == \'asc\') ? SORT_ASC : SORT_DESC;\r\n				array_multisort($apop, $dir, $atime, $dir, $articles);\r\n			}\r\n			if ($permlink_mode == \'messy\') {\r\n				// Strip off the fake \'id\' identifiers in the array keys\r\n				foreach ($articles as $key => $row) {\r\n					$tmp[substr($key, 2)] = $row;\r\n				}\r\n				$articles = $tmp;\r\n			}\r\n			if ($debug > 2) {\r\n				echo \'++ FINAL LOG LIST++\';\r\n				dmp($articles);\r\n			}\r\n			$darticles = safe_rows(\'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\', \'textpattern\', \"$match_col IN (\'\" . join(\"\',\'\", array_keys($articles)) . \"\')\".$status.$time, $debug);\r\n\r\n			// Refactor the article list keyed on url_title or ID (depending on permlink_mode)\r\n			$urlicles = array();\r\n			foreach ($darticles as $darticle) {\r\n				$urlicles[$darticle[$match_col]] = $darticle;\r\n			}\r\n\r\n			// Iterate over the _original_ article list (from the first query)\r\n			// and pluck out the relevant article contents\r\n			foreach ($articles as $urlTitle => $darticle) {\r\n				if (isset($urlicles[$urlTitle])) {\r\n					$theTime = strtotime($darticle[1]);\r\n					$aktiv = $thisarticle && $show_current && $urlTitle == $curr_art;\r\n					$replacements = array(\r\n						\"{smd_lately_active}\" => (($aktiv) ? \' class=\"\'.$active_class.\'\"\' : \'\'),\r\n						\"{smd_lately_activeclass}\" => (($aktiv) ? $active_class : \'\'),\r\n						\"{smd_lately_count}\" => $darticle[0],\r\n						\"{smd_lately_fulldate}\" => $darticle[1],\r\n						\"{smd_lately_date}\" => strftime(\"%F\", $theTime),\r\n						\"{smd_lately_date_year}\" => strftime(\"%Y\", $theTime),\r\n						\"{smd_lately_date_month}\" => strftime(\"%m\", $theTime),\r\n						\"{smd_lately_date_monthname}\" => strftime(\"%B\", $theTime),\r\n						\"{smd_lately_date_day}\" => strftime(\"%d\", $theTime),\r\n						\"{smd_lately_date_dayname}\" => strftime(\"%A\", $theTime),\r\n						\"{smd_lately_time}\" => strftime(\"%T\", $theTime),\r\n						\"{smd_lately_time_hour}\" => strftime(\"%H\", $theTime),\r\n						\"{smd_lately_time_minute}\" => strftime(\"%M\", $theTime),\r\n						\"{smd_lately_time_second}\" => strftime(\"%S\", $theTime),\r\n					);\r\n					article_push();\r\n					populateArticleData($urlicles[$urlTitle]);\r\n					$out[] = ($thing) ? parse(strtr($thing, $replacements)) : (($form) ? parse_form(strtr($form, $replacements)) : href( $urlicles[$urlTitle][\'Title\'], permlinkurl($urlicles[$urlTitle]), $replacements[\'{smd_lately_active}\'] ));\r\n					article_pop();\r\n				}\r\n			}\r\n		}\r\n	}\r\n	return ($out) ? doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class) : \'\';\r\n}\r\n\r\n// ------------------------\r\n// Plugin-specific replacement strings - localise as required\r\nfunction smd_rv_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'invalid_ts\' => \'Invalid date/time info in \"{where}\" attribute.\',\r\n		\'logging_enabled\' => \'Logging must be set to \"All hits\" in Basic Pefs.\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', '9144215026d46f13423b606ce0dd110b', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_multi_choice', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.20', 'Switch / case construct for multiple conditional branches', '<div id=\"smd_help\">\n\n	<h1>smd_multi_choice</h1>\n\n	<p>A generic &#8216;if condition&#8217; tester that can take action if some article, file, link, <span class=\"caps\">URL</span>, <code>&lt;txp:variable /&gt;</code> or global <span class=\"caps\">PHP</span> variable matches one or more tests. The difference between this and smd_if is that you do not need to nest successive tests because it performs comparisons using a <code>switch... case</code> construct.</p>\n\n	<h2>Features</h2>\n\n	<ul>\n		<li>Supports all major article, file, and link variables such as section, category, custom fields, id, query string, author, body, excerpt, yahde yahde, plus checking url vars, server vars, txp and <span class=\"caps\">PHP</span> vars</li>\n		<li>Tests include equality, less than, greater than, divisible by, begins, ends, contains, and &#8216;character&#8217; tests such is isalpha, isnum, etc</li>\n		<li>Tests can either terminate when a match is found, or &#8220;fall through&#8221; to the next test (&#8220;<span class=\"caps\">AND</span>&#8221; logic)</li>\n		<li>Multiple tests can have the same outcome (&#8220;OR&#8221; logic)</li>\n		<li>The tested field &amp; matched value are <a href=\"#replacements\">available to the case container</a> so you can display them</li>\n		<li>Default condition in the event no test cases match</li>\n	</ul>\n\n	<h2>Author</h2>\n\n	<p><a href=\"http://stefdawson.com/commentForm\">Stef Dawson</a>.</p>\n\n	<h2>Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1077/smd_multi_choice\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=30376\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n	<h2>Usage</h2>\n\n	<p>The plugin gives two tags which must be used in tandem. Use <code>&lt;txp:smd_switch /&gt;</code> to tell the plugin which <code>item</code> you are interested in comparing against and then use one or more <code>&lt;txp:smd_case /&gt;</code> tags inside the switch to take action depending on the value.</p>\n\n	<p>Use the tags in any page, form or article context. Can also be used inside file, image (in the future) or link lists to take action depending on attributes of the current item.</p>\n\n	<h3 class=\"tag\">smd_switch</h3>\n\n	<p class=\"tag-summary\">At the place you wish to compare a field with a set of values, put this container tag.</p>\n\n	<h4 class=\"atts\">Attributes</h4>\n\n	<ul>\n		<li><span class=\"att-name mand\">item</span> : the thing you are interested in. It could be the result of another tag-in-tag, a fixed string(!), or a variable if you prefix the item with <code>?</code>. Example variables are <code>?article_image</code>, <code>?url_title</code>, <code>?my_txp_var</code>, <code>?my_url_var</code>, and so on</li>\n		<li><span class=\"att-name\">look_in</span> : list of places to look for the <code>item</code>. Default: <code>SMD_ALL</code> which checks all items in the following list, in order. You should never need to change this, but you may choose from any of the following and comma-separate each one you wish to consider:\n	<ul>\n		<li><span class=\"att-val\">txpvar</span> : a <code>&lt;txp:variable /&gt;</code></li>\n		<li><span class=\"att-val\">svrvar</span> : something in <code>$_SERVER</code></li>\n		<li><span class=\"att-val\">file</span> : variable from the current file in a file_download_list</li>\n		<li><span class=\"att-val\">image</span> : variable from the current image in an image_list</li>\n		<li><span class=\"att-val\">link</span> : variable from the current link in a linklist</li>\n		<li><span class=\"att-val\">gbl</span> : variable from the global article context (a.k.a. <code>$pretext</code>)</li>\n		<li><span class=\"att-val\">article</span> : variable from the current article</li>\n		<li><span class=\"att-val\">urlvar</span> : something in <code>$_GET</code> or <code>$_POST</code></li>\n		<li><span class=\"att-val\">phpvar</span> : something in the global scope</li>\n	</ul></li>\n		<li><span class=\"att-name\">leave_empty</span> : in most cases if a variable is not set you will want it to remain that way so your switch will do nothing. But you may decide that you want the variable name itself to become the <code>item</code>, i.e. if you used <code>item=&quot;?quantity&quot;</code> and there was no &#8216;quantity&#8217; variable anywhere, it would compare against the actual word &#8216;quantity&#8217;. This may be useful when specifying defaults</li>\n		<li><span class=\"att-name\">var_prefix</span> : if you are nesting smd_switch tags you&#8217;ll find that any inner <code>{smd_mc_value}</code> replacements will take on the values of the outer tag. For this reason you can specify a prefix that will be used in all replacements. Thus if you set <code>var_prefix=&quot;in_&quot;</code> on your inner smd_switch tag, you would use <code>{in_mc_value}</code> and <code>{in_mc_item}</code> in the inner container/form. Default: <code>smd_</code></li>\n		<li><span class=\"att-name\">debug</span> : set to 1 to turn on diagnostic info for all contained smd_case tags</li>\n	</ul>\n\n	<h3 class=\"tag\">smd_case</h3>\n\n	<p class=\"tag-summary\">Inside your <code>&lt;txp:smd_switch&gt;</code> container put one or more <code>&lt;txp:smd_case /&gt;</code> tags to test the <code>item</code> against each successive value. Case tags are usually containers that will execute the enclosed content if the <code>value</code> matches the <code>item</code>, but see <a href=\"#eg4\">example 4</a> for use as a self-closing tag.</p>\n\n	<h4 class=\"atts\">Attributes</h4>\n\n	<ul>\n		<li><span class=\"att-name\">value</span> : the value you want to check against. It can either be a fixed value or another field in the current context (see the <code>&lt;txp:smd_switch /&gt;</code> tag&#8217;s <code>item</code> attribute for info). Default: unset</li>\n		<li><span class=\"att-name\">type</span> : the type of test you want to perform:\n	<ul>\n		<li><span class=\"att-val\">eq</span> : Equality (the default)</li>\n		<li><span class=\"att-val\">gt</span> : <code>item</code> is greater than <code>value</code></li>\n		<li><span class=\"att-val\">ge</span> : <code>item</code> is greater than or equal to <code>value</code></li>\n		<li><span class=\"att-val\">lt</span> : <code>item</code> is less than <code>value</code></li>\n		<li><span class=\"att-val\">le</span> : <code>item</code> is less than or equal to <code>value</code></li>\n		<li><span class=\"att-val\">divisible</span> : <code>item</code> is wholly divisible by <code>value</code><sup id=\"fnrev1999442304c5744d2d079b\" class=\"footnote\"><a href=\"#fn1999442304c5744d2d079b\">1</a></sup></li>\n		<li><span class=\"att-val\">begins</span> : <code>item</code> starts with <code>value</code></li>\n		<li><span class=\"att-val\">contains</span> : <code>item</code> has <code>value</code> somewhere within its contents</li>\n		<li><span class=\"att-val\">ends</span> : <code>item</code> ends with <code>value</code></li>\n		<li><span class=\"att-val\">isnum</span> : <code>value</code> is made up entirely of digits</li>\n		<li><span class=\"att-val\">isalpha</span> : <code>value</code> is made up entirely of alphabetic characters</li>\n		<li><span class=\"att-val\">isalnum</span> : <code>value</code> is made up entirely of alphanumeric characters</li>\n		<li><span class=\"att-val\">islower</span> : <code>value</code> is made up entirely of lower case characters<sup id=\"fnrev17585721564c5744d2d0b84\" class=\"footnote\"><a href=\"#fn17585721564c5744d2d0b84\">2</a></sup></li>\n		<li><span class=\"att-val\">isupper</span> : <code>value</code> is made up entirely of upper case characters<sup class=\"footnote\"><a href=\"#fn17585721564c5744d2d0b84\">2</a></sup></li>\n		<li><span class=\"att-val\">ispunct</span> : <code>value</code> is made up entirely of punctuation characters</li>\n		<li><span class=\"att-val\">isspace</span> : <code>value</code> is made up entirely of &#8216;whitespace&#8217; characters</li>\n		<li><span class=\"att-val\">empty</span> : <code>item</code> is empty (i.e. has no content) &#8212; similar to <code>value=&quot;&quot;</code></li>\n	</ul></li>\n		<li><span class=\"att-name\">look_in</span> : exactly the same options as for smd_switch except they apply to the <code>value</code> attribute</li>\n		<li><span class=\"att-name\">leave_empty</span> : exactly the same behaviour for smd_switch except it applies to the <code>value</code> attribute</li>\n		<li><span class=\"att-name\">case_sensitive</span> : whether you wish to take case into account when compare <code>item</code> and <code>value</code>; 1=yes, 0=no. Default: 0</li>\n		<li><span class=\"att-name\">numeric</span> : whether you wish to compare the values numerically (1) instead of alpha-numerically (0). Useful when comparing numbers because &#8216;f&#8217; is actually greater than &#8216;1&#8217; in the <span class=\"caps\">ASCII</span> table. Default: 0. Note that a numeric test is implied if using <code>type=&quot;divisible&quot;</code></li>\n		<li><span class=\"att-name\">fallthru</span> : under normal circumstances, when one of the tests matches any further tests are ignored. Thus the <em>order of the tests is important</em>. Sometimes you may wish to take more than one action, e.g. if the item is greater than value1 then display something, and also display something else if it begins with value2. This type of &#8220;<span class=\"caps\">AND</span>&#8221; condition is achieved by setting <code>fallthru=&quot;1&quot;</code>. In the event the case matches this condition, the next case tag will still be checked. For &#8220;OR&#8221; conditions, see <a href=\"#eg4\">example 4</a></li>\n		<li><span class=\"att-name\">default</span> : inside any switch, one and only one of your case tags can be designated a &#8216;default&#8217; by setting this attribute to 1. This default branch will be taken if none of the preceding case tags match anything. It is usually the last tag in a switch. Default: 0</li>\n		<li><span class=\"att-name\">debug</span> : set to 1 to show diagnositc information for this case tag only. Default: 0</li>\n	</ul>\n\n	<p id=\"fn1999442304c5744d2d079b\" class=\"footnote\"><sup>1</sup> This test forces &#8216;numeric&#8217; on automtically. Also, 0 is regarded as <em>not</em> wholly divisible by any value</p>\n\n	<p id=\"fn17585721564c5744d2d0b84\" class=\"footnote\"><sup>2</sup> case_sensitive is automatically set to 1 for these tests</p>\n\n	<h2 id=\"replacements\">Replacement variables</h2>\n\n	<p>Some replacement variables are available within the <code>&lt;txp:smd_case&gt;</code> container so you may output the matched values directly, for example, in error messages:</p>\n\n	<ol>\n		<li><code>{smd_mc_item}</code> (or <code>{smd_switch}</code>): the value of the switch <code>item</code> being looked at</li>\n		<li><code>{smd_mc_value}</code> (or <code>{smd_case}</code>): the value of the case <code>value</code> that matched the <code>item</code></li>\n	</ol>\n\n	<p>When testing more than one value to produce a single outcome, the <em>most recent</em> value that matches is the one assigned to the replacement variable. Note that the <code>smd_</code> part is the default setting and may be changed with the <code>var_prefix</code> attribute.</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<h3 id=\"eg1\">Example 1</h3>\n\n	<p>On your error_default page you could put this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_switch item=&quot;?txp_error_code&quot;&gt;\n   &lt;txp:smd_case value=&quot;403&quot;&gt;\n      &lt;p&gt;Forbidden access. Code: 403&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;404&quot;&gt;\n      &lt;p&gt;Page not found. Code: 404&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;500&quot;&gt;\n      &lt;p&gt;Something went horribly wrong. Code: 500&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case default=&quot;1&quot;&gt;\n      &lt;p&gt;Whoops! Error code: {smd_mc_item} occurred&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n&lt;/txp:smd_switch&gt;\n</code></pre>\n\n	<h3 id=\"eg2\">Example 2</h3>\n\n	<p>Test some value obtained from public-side form input:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_switch item=&quot;?quantity&quot; look_in=&quot;urlvar&quot;&gt;\n   &lt;txp:smd_case value=&quot;2&quot; type=&quot;lt&quot;\n        numeric=&quot;1&quot;&gt;\n      &lt;p&gt;Value {smd_switch} is too small&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;20&quot; type=&quot;gt&quot;\n        numeric=&quot;1&quot;&gt;\n      &lt;p&gt;Value {smd_switch} is too large&lt;/p&gt;\n   &lt;/txp:smd_case&gt;\n&lt;/txp:smd_switch&gt;\n</code></pre>\n\n	<h3 id=\"eg3\">Example 3</h3>\n\n	<p>Building on <a href=\"#eg2\">example 2</a>, this runs the 2nd and 3rd tests regardless of whether the 2nd one returned true. It also adds a check to see if the <code>quantity</code> <span class=\"caps\">URL</span> variable is set at all and issues a warning of not (an empty <code>&lt;txp:smd_case&gt;</code> is the same as <code>&lt;txp:smd_case value=&quot;&quot;&gt;</code>)</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_switch item=&quot;?quantity&quot; look_in=&quot;urlvar&quot;&gt;\n   &lt;txp:smd_case&gt;\n      Please supply a quantity&lt;br /&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;2&quot; type=&quot;lt&quot;\n        numeric=&quot;1&quot;&gt;\n      Value {smd_switch} is too small&lt;br /&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;20&quot; type=&quot;gt&quot;\n        numeric=&quot;1&quot; fallthru=&quot;1&quot;&gt;\n      Value {smd_switch} is too large&lt;br /&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;100&quot; type=&quot;gt&quot;\n        numeric=&quot;1&quot;&gt;\n      ... and out of this world!&lt;br /&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case default=&quot;1&quot;&gt;\n      Everything&#39;s fine\n   &lt;/txp:smd_case&gt;\n&lt;/txp:smd_switch&gt;\n</code></pre>\n\n	<p>Thus, you see the following if you add these values to the address bar:</p>\n\n	<ul>\n		<li><code>?quantity=1</code> : Value 1 is too small</li>\n		<li><code>?quantity=15</code> : Everything&#8217;s fine</li>\n		<li><code>?quantity=25</code> : Value 25 is too large</li>\n		<li><code>?quantity=125</code> : Value 125 is too large&#8230; and out of this world!</li>\n	</ul>\n\n	<h3 id=\"eg4\">Example 4</h3>\n\n	<p>Sometimes you may want to perform the same action for a number of matches. For these instances you may use the <code>&lt;txp:smd_case /&gt;</code> tag in its Single form. List all the cases that you want to have the same outcome, then use the container of the last one to perform the action if any of the preceding cases matched.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_switch item=&quot;?custom1&quot;&gt;\n   &lt;txp:smd_case value=&quot;3&quot; type=&quot;divisible&quot; /&gt;\n   &lt;txp:smd_case value=&quot;7&quot; type=&quot;divisible&quot; /&gt;\n   &lt;txp:smd_case value=&quot;9&quot; type=&quot;divisible&quot;&gt;\n     {smd_switch} is divisible by 3, 7 or 9\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case value=&quot;5&quot; type=&quot;ends&quot; /&gt;\n   &lt;txp:smd_case value=&quot;0&quot; type=&quot;ends&quot;&gt;\n     Yay! Numbers ending in 5 or 0 win a teddy.\n   &lt;/txp:smd_case&gt;\n&lt;/txp:smd_switch&gt;\n</code></pre>\n\n	<p>If you add <code>fallthru=&quot;1&quot;</code> to the <code>value=&quot;9&quot;</code> case tag, both &#8216;divisible&#8217; and &#8216;teddy bear&#8217; sets of conditions would be tested and you&#8217;d see varying combinations of the output based on the contents of custom1.</p>\n\n	<h3 id=\"eg5\">Example 5</h3>\n\n	<p>This one checks that the <span class=\"caps\">URL</span> variable <code>qty</code> is within the range of a <code>stock</code> custom field. This one uses a nested <code>smd_switch</code> tag. The first (outer) switch checks if the value is numeric and then uses a second switch to compare its value to the stock level in the custom field. If the outer switch detects the value is not numeric, the default case fires to inform the visitor of the error.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_switch item=&quot;?qty&quot;&gt;\n   &lt;txp:smd_case type=&quot;isnum&quot;&gt;\n      &lt;txp:smd_switch item=&quot;?qty&quot; var_prefix=&quot;inner_&quot;&gt;\n         &lt;txp:smd_case value=&quot;0&quot;&gt;\n            Please select a quantity to order\n         &lt;/txp:smd_case&gt;\n         &lt;txp:smd_case value=&quot;?stock&quot; type=&quot;le&quot;&gt;\n            There are enough of those in stock. Place order?\n         &lt;/txp:smd_case&gt;\n         &lt;txp:smd_case default=&quot;1&quot; value=&quot;?stock&quot;&gt;\n           There are only {inner_mc_value} in stock. Please choose a lower number\n         &lt;/txp:smd_case&gt;\n      &lt;/txp:smd_switch&gt;\n   &lt;/txp:smd_case&gt;\n   &lt;txp:smd_case default=&quot;1&quot; value=&quot;?stock&quot;&gt;\n      Sorry, &quot;{smd_mc_item}&quot; is not valid input. Please choose a valid order quantity.\n   &lt;/txp:smd_case&gt;\n&lt;/txp:smd_switch&gt;\n</code></pre>\n\n	<p>So if someone enters <code>?qty=0</code> or <code>?qty=fred</code> then a special message is shown to prompt them to enter a valid number. If the <code>qty</code> variable is lower than the stock level of the current article then all is well. But if any other value is used, the <code>default</code> condition of the inner <code>smd_switch</code> kicks in and tells the visitor that there are not enough to satisfy their ravenous needs.</p>\n\n	<p>A couple of other things to note:</p>\n\n	<ul>\n		<li>the inner switch uses the <code>var_prefix</code> attribute so the variable can be displayed correctly to the visitor without clashing with the value in the outer switch. In this case the switch values happen to be the same so it doesn&#8217;t really matter, but if you were testing a separate variable then it might</li>\n		<li>the default cases both use <code>value=&quot;?stock&quot;</code> even though they would appear not to be strictly necessary. They are used so the replacement variables <code>{smd_mc_item}</code> and <code>{inner_mc_value}</code> can be inserted in the output. If you omit the <code>value</code> attribute then the replacements are not &#8216;primed&#8217; because the case tag does not know where to look for its value</li>\n	</ul>\n\n	<h2 id=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>10 Apr 09 | 0.10 | Initial release</li>\n		<li>01 Aug 10 | 0.20 | Added empty type (thanks gomedia) ; added <code>var_prefix</code> ; fixed default case firing unnecessarily ; fixed problem with multiple switch tags on a page</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.3 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help a:link, #smd_help a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .att-name { font-weight:bold; }\n#smd_help .att-val { font-style:italic;}\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:100%; text-align:center; padding-bottom:1em; border:1px solid #666;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function smd_switch($atts, $thing) {\r\n	global $smd_switch_item, $smd_switch_debug, $smd_case_true, $smd_multi_case, $smd_mc_val, $smd_bypass, $smd_var_prefix;\r\n\r\n	extract(lAtts(array(\r\n		\'item\' => \'\',\r\n		\'look_in\' => \'SMD_ALL\',\r\n		\'leave_empty\' => \'1\',\r\n		\'debug\' => \'0\',\r\n		\'var_prefix\' => \'smd_\',\r\n	), $atts));\r\n\r\n	if (!isset($atts[\'item\'])) {\r\n		trigger_error(\"smd_multi_choice requires an item to look at\");\r\n		return;\r\n	}\r\n\r\n	$smd_switch_item = smd_mc_parse($item, $look_in, $leave_empty);\r\n\r\n	// Initialize everything else in case previous smd_switch tags left any global residue\r\n	$smd_var_prefix = $var_prefix;\r\n	$smd_switch_debug = $debug;\r\n	$smd_bypass = $smd_case_true = 0;\r\n	$smd_multi_case = false;\r\n	$smd_mc_val = \'\';\r\n\r\n	return (parse($thing));\r\n}\r\n\r\nfunction smd_case($atts, $thing=NULL) {\r\n	global $smd_switch_item, $smd_switch_debug, $smd_case_true, $smd_multi_case, $smd_mc_val, $smd_bypass, $smd_var_prefix;\r\n\r\n	extract(lAtts(array(\r\n		\'value\' => \'\',\r\n		\'look_in\' => \'SMD_ALL\',\r\n		\'leave_empty\' => \'1\',\r\n		\'type\' => \'eq\',\r\n		\'case_sensitive\' => 0,\r\n		\'numeric\' => 0,\r\n		\'fallthru\' => 0,\r\n		\'default\' => 0,\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	$result = false;\r\n\r\n	// Validation\r\n	$cases = array(\'islower\', \'isupper\');\r\n	$case_sensitive = (in_array($type, $cases)) ? 1 : $case_sensitive;\r\n	$value = smd_mc_parse($value, $look_in, $leave_empty);\r\n	$value = ($case_sensitive) ? $value : strtolower($value);\r\n	$tester = ($case_sensitive) ? $smd_switch_item : strtolower($smd_switch_item);\r\n	$fallthru = (!$thing) ? 1 : $fallthru;\r\n\r\n	$replacements = array();\r\n	$replacements[\'{\'.$smd_var_prefix.\'switch}\'] = $replacements[\'{\'.$smd_var_prefix.\'mc_item}\'] = $tester;\r\n\r\n	if ($smd_bypass == 0) {\r\n		if ($debug || $smd_switch_debug) {\r\n			echo \"++ TEST $tester \" . (($default) ? \'DEFAULT\' : $type.\' \'.$value);\r\n		}\r\n		if ($default == 1 && !$smd_case_true) {\r\n			$result = true;\r\n		} else {\r\n			switch ($type) {\r\n				case \'eq\':\r\n					$result = ($numeric) ? (($tester === $value) ? true : false) : (($tester == $value) ? true : false);\r\n					break;\r\n				case \'gt\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester > (int)$value) ? true : false) : (($tester > $value) ? true : false);\r\n					break;\r\n				case \'ge\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester >= (int)$value) ? true : false) : (($tester >= $value) ? true : false);\r\n					break;\r\n				case \'lt\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester < (int)$value) ? true : false) : (($tester < $value) ? true : false);\r\n					break;\r\n				case \'le\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester <= (int)$value) ? true : false) : (($tester <= $value) ? true : false);\r\n					break;\r\n				case \'divisible\':\r\n					$result = (ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester > 0 && (int)$tester % (int)$value === 0) ? true : false;\r\n					break;\r\n				case \'begins\':\r\n					$result = (strpos($tester, $value) === 0) ? true : false;\r\n					break;\r\n				case \'contains\':\r\n					$result = (strpos($tester, $value) !== false) ? true : false;\r\n					break;\r\n				case \'ends\':\r\n					$result = (substr($tester, strlen($tester) - strlen($value)) === $value) ? true : false;\r\n					break;\r\n				case \'isnum\':\r\n					$result = (ctype_digit((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isalpha\':\r\n					$result = (ctype_alpha((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isalnum\':\r\n					$result = (ctype_alnum((string)$tester)) ? true : false;\r\n					break;\r\n				case \'islower\':\r\n					$result = (ctype_lower((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isupper\':\r\n					$result = (ctype_upper((string)$tester)) ? true : false;\r\n					break;\r\n				case \'ispunct\':\r\n					$result = (ctype_punct((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isspace\':\r\n					$result = (ctype_space((string)$tester)) ? true : false;\r\n					break;\r\n				case \'empty\':\r\n					$result = ($tester == \'\') ? true : false;\r\n					break;\r\n			}\r\n		}\r\n		$smd_case_true = ($result) ? true : $smd_case_true;\r\n		if ($debug || $smd_switch_debug) {\r\n			echo \' RESULT: \' . (($result) ? \'TRUE\' : \'FALSE\' ) . \'<br />\';\r\n		}\r\n\r\n		if (($result || $smd_multi_case) && !$fallthru) {\r\n			$smd_bypass = 1;\r\n		}\r\n		if ($thing) {\r\n			$ret = $result || $smd_multi_case;\r\n			$replacements[\'{\'.$smd_var_prefix.\'case}\'] = $replacements[\'{\'.$smd_var_prefix.\'mc_value}\'] = ($result) ? $value : $smd_mc_val;\r\n			if ($debug > 1 || $smd_switch_debug > 1) {\r\n				echo \'++ REPLACEMENTS ++\';\r\n				dmp($replacements);\r\n			}\r\n			$smd_multi_case = false;\r\n			return parse(EvalElse(strtr($thing, $replacements), $ret));\r\n		} else {\r\n			$smd_multi_case = ($result) ? true : $smd_multi_case;\r\n			$smd_mc_val = ($result) ? $value : $smd_mc_val;\r\n		}\r\n	} else {\r\n		return false;\r\n	}\r\n}\r\n\r\n// Look in the given $in places for a value from a \'?\' variable $item\r\nfunction smd_mc_parse($item, $in, $mt=\'1\') {\r\n	global $pretext, $thisarticle, $thisimage, $thisfile, $thislink, $variable;\r\n\r\n	$in = ($in == \"SMD_ALL\") ? \"txpvar, svrvar, image, file, link, gbl, article, urlvar, phpvar\" : $in;\r\n	$lookin = do_list($in);\r\n\r\n	$modRE = \'/(\\?)([A-Za-z0-9_\\- ]+)/\';\r\n	$numMods = preg_match_all($modRE, $item, $mods);\r\n\r\n	for ($modCtr = 0; $modCtr < $numMods; $modCtr++) {\r\n		$modChar = $mods[1][$modCtr];\r\n		$modItem = trim($mods[2][$modCtr]);\r\n		$lowitem = strtolower($modItem);\r\n\r\n		if (in_array(\"txpvar\", $lookin) && isset($variable[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $variable[$lowitem], $item);\r\n		} else if (in_array(\"svrvar\", $lookin) && serverSet($modItem) != \'\') {\r\n			$item = str_replace($modChar.$modItem, doSlash(serverSet($modItem)), $item);\r\n		} else if (in_array(\"image\", $lookin) && isset($thisimage[$lowitem]) && !empty($thisimage[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisimage[$lowitem], $item);\r\n		} else if (in_array(\"file\", $lookin) && isset($thisfile[$lowitem]) && !empty($thisfile[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisfile[$lowitem], $item);\r\n		} else if (in_array(\"link\", $lookin) && isset($thislink[$lowitem]) && !empty($thislink[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thislink[$lowitem], $item);\r\n		} else if (in_array(\"gbl\", $lookin) && array_key_exists($lowitem, $pretext) && !empty($pretext[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $pretext[$lowitem], $item);\r\n		} else if (in_array(\"article\", $lookin) && isset($thisarticle[$lowitem]) && !empty($thisarticle[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisarticle[$lowitem], $item);\r\n		} else if (in_array(\"urlvar\", $lookin) && gps($lowitem) != \'\') {\r\n			$item = str_replace($modChar.$modItem, doSlash(gps($lowitem)), $item);\r\n		} else if (in_array(\"phpvar\", $lookin) && isset($GLOBALS[$lowitem]) && !empty($GLOBALS[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $GLOBALS[$lowitem], $item);\r\n		} else if (!$mt) {\r\n			$item = str_replace($modChar.$modItem, $modItem, $item);\r\n		} else {\r\n			$item = \'\';\r\n		}\r\n	}\r\n	return $item;\r\n}', 'function smd_switch($atts, $thing) {\r\n	global $smd_switch_item, $smd_switch_debug, $smd_case_true, $smd_multi_case, $smd_mc_val, $smd_bypass, $smd_var_prefix;\r\n\r\n	extract(lAtts(array(\r\n		\'item\' => \'\',\r\n		\'look_in\' => \'SMD_ALL\',\r\n		\'leave_empty\' => \'1\',\r\n		\'debug\' => \'0\',\r\n		\'var_prefix\' => \'smd_\',\r\n	), $atts));\r\n\r\n	if (!isset($atts[\'item\'])) {\r\n		trigger_error(\"smd_multi_choice requires an item to look at\");\r\n		return;\r\n	}\r\n\r\n	$smd_switch_item = smd_mc_parse($item, $look_in, $leave_empty);\r\n\r\n	// Initialize everything else in case previous smd_switch tags left any global residue\r\n	$smd_var_prefix = $var_prefix;\r\n	$smd_switch_debug = $debug;\r\n	$smd_bypass = $smd_case_true = 0;\r\n	$smd_multi_case = false;\r\n	$smd_mc_val = \'\';\r\n\r\n	return (parse($thing));\r\n}\r\n\r\nfunction smd_case($atts, $thing=NULL) {\r\n	global $smd_switch_item, $smd_switch_debug, $smd_case_true, $smd_multi_case, $smd_mc_val, $smd_bypass, $smd_var_prefix;\r\n\r\n	extract(lAtts(array(\r\n		\'value\' => \'\',\r\n		\'look_in\' => \'SMD_ALL\',\r\n		\'leave_empty\' => \'1\',\r\n		\'type\' => \'eq\',\r\n		\'case_sensitive\' => 0,\r\n		\'numeric\' => 0,\r\n		\'fallthru\' => 0,\r\n		\'default\' => 0,\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	$result = false;\r\n\r\n	// Validation\r\n	$cases = array(\'islower\', \'isupper\');\r\n	$case_sensitive = (in_array($type, $cases)) ? 1 : $case_sensitive;\r\n	$value = smd_mc_parse($value, $look_in, $leave_empty);\r\n	$value = ($case_sensitive) ? $value : strtolower($value);\r\n	$tester = ($case_sensitive) ? $smd_switch_item : strtolower($smd_switch_item);\r\n	$fallthru = (!$thing) ? 1 : $fallthru;\r\n\r\n	$replacements = array();\r\n	$replacements[\'{\'.$smd_var_prefix.\'switch}\'] = $replacements[\'{\'.$smd_var_prefix.\'mc_item}\'] = $tester;\r\n\r\n	if ($smd_bypass == 0) {\r\n		if ($debug || $smd_switch_debug) {\r\n			echo \"++ TEST $tester \" . (($default) ? \'DEFAULT\' : $type.\' \'.$value);\r\n		}\r\n		if ($default == 1 && !$smd_case_true) {\r\n			$result = true;\r\n		} else {\r\n			switch ($type) {\r\n				case \'eq\':\r\n					$result = ($numeric) ? (($tester === $value) ? true : false) : (($tester == $value) ? true : false);\r\n					break;\r\n				case \'gt\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester > (int)$value) ? true : false) : (($tester > $value) ? true : false);\r\n					break;\r\n				case \'ge\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester >= (int)$value) ? true : false) : (($tester >= $value) ? true : false);\r\n					break;\r\n				case \'lt\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester < (int)$value) ? true : false) : (($tester < $value) ? true : false);\r\n					break;\r\n				case \'le\':\r\n					$result = ($numeric) ? ((ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester <= (int)$value) ? true : false) : (($tester <= $value) ? true : false);\r\n					break;\r\n				case \'divisible\':\r\n					$result = (ctype_digit((string)$tester) && ctype_digit((string)$value) && (int)$tester > 0 && (int)$tester % (int)$value === 0) ? true : false;\r\n					break;\r\n				case \'begins\':\r\n					$result = (strpos($tester, $value) === 0) ? true : false;\r\n					break;\r\n				case \'contains\':\r\n					$result = (strpos($tester, $value) !== false) ? true : false;\r\n					break;\r\n				case \'ends\':\r\n					$result = (substr($tester, strlen($tester) - strlen($value)) === $value) ? true : false;\r\n					break;\r\n				case \'isnum\':\r\n					$result = (ctype_digit((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isalpha\':\r\n					$result = (ctype_alpha((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isalnum\':\r\n					$result = (ctype_alnum((string)$tester)) ? true : false;\r\n					break;\r\n				case \'islower\':\r\n					$result = (ctype_lower((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isupper\':\r\n					$result = (ctype_upper((string)$tester)) ? true : false;\r\n					break;\r\n				case \'ispunct\':\r\n					$result = (ctype_punct((string)$tester)) ? true : false;\r\n					break;\r\n				case \'isspace\':\r\n					$result = (ctype_space((string)$tester)) ? true : false;\r\n					break;\r\n				case \'empty\':\r\n					$result = ($tester == \'\') ? true : false;\r\n					break;\r\n			}\r\n		}\r\n		$smd_case_true = ($result) ? true : $smd_case_true;\r\n		if ($debug || $smd_switch_debug) {\r\n			echo \' RESULT: \' . (($result) ? \'TRUE\' : \'FALSE\' ) . \'<br />\';\r\n		}\r\n\r\n		if (($result || $smd_multi_case) && !$fallthru) {\r\n			$smd_bypass = 1;\r\n		}\r\n		if ($thing) {\r\n			$ret = $result || $smd_multi_case;\r\n			$replacements[\'{\'.$smd_var_prefix.\'case}\'] = $replacements[\'{\'.$smd_var_prefix.\'mc_value}\'] = ($result) ? $value : $smd_mc_val;\r\n			if ($debug > 1 || $smd_switch_debug > 1) {\r\n				echo \'++ REPLACEMENTS ++\';\r\n				dmp($replacements);\r\n			}\r\n			$smd_multi_case = false;\r\n			return parse(EvalElse(strtr($thing, $replacements), $ret));\r\n		} else {\r\n			$smd_multi_case = ($result) ? true : $smd_multi_case;\r\n			$smd_mc_val = ($result) ? $value : $smd_mc_val;\r\n		}\r\n	} else {\r\n		return false;\r\n	}\r\n}\r\n\r\n// Look in the given $in places for a value from a \'?\' variable $item\r\nfunction smd_mc_parse($item, $in, $mt=\'1\') {\r\n	global $pretext, $thisarticle, $thisimage, $thisfile, $thislink, $variable;\r\n\r\n	$in = ($in == \"SMD_ALL\") ? \"txpvar, svrvar, image, file, link, gbl, article, urlvar, phpvar\" : $in;\r\n	$lookin = do_list($in);\r\n\r\n	$modRE = \'/(\\?)([A-Za-z0-9_\\- ]+)/\';\r\n	$numMods = preg_match_all($modRE, $item, $mods);\r\n\r\n	for ($modCtr = 0; $modCtr < $numMods; $modCtr++) {\r\n		$modChar = $mods[1][$modCtr];\r\n		$modItem = trim($mods[2][$modCtr]);\r\n		$lowitem = strtolower($modItem);\r\n\r\n		if (in_array(\"txpvar\", $lookin) && isset($variable[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $variable[$lowitem], $item);\r\n		} else if (in_array(\"svrvar\", $lookin) && serverSet($modItem) != \'\') {\r\n			$item = str_replace($modChar.$modItem, doSlash(serverSet($modItem)), $item);\r\n		} else if (in_array(\"image\", $lookin) && isset($thisimage[$lowitem]) && !empty($thisimage[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisimage[$lowitem], $item);\r\n		} else if (in_array(\"file\", $lookin) && isset($thisfile[$lowitem]) && !empty($thisfile[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisfile[$lowitem], $item);\r\n		} else if (in_array(\"link\", $lookin) && isset($thislink[$lowitem]) && !empty($thislink[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thislink[$lowitem], $item);\r\n		} else if (in_array(\"gbl\", $lookin) && array_key_exists($lowitem, $pretext) && !empty($pretext[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $pretext[$lowitem], $item);\r\n		} else if (in_array(\"article\", $lookin) && isset($thisarticle[$lowitem]) && !empty($thisarticle[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $thisarticle[$lowitem], $item);\r\n		} else if (in_array(\"urlvar\", $lookin) && gps($lowitem) != \'\') {\r\n			$item = str_replace($modChar.$modItem, doSlash(gps($lowitem)), $item);\r\n		} else if (in_array(\"phpvar\", $lookin) && isset($GLOBALS[$lowitem]) && !empty($GLOBALS[$lowitem])) {\r\n			$item = str_replace($modChar.$modItem, $GLOBALS[$lowitem], $item);\r\n		} else if (!$mt) {\r\n			$item = str_replace($modChar.$modItem, $modItem, $item);\r\n		} else {\r\n			$item = \'\';\r\n		}\r\n	}\r\n	return $item;\r\n}', '8542469c639800d371148bdf4338200c', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_pagicle', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.2', 'Automatic client-side pagination of articles using jQuery', '<div id=\"smd_help\">\n\n	<h1>smd_pagicle</h1>\n\n	<p>Automatically split long articles into bite-size hunks which visitors can navigate between using the handy pager. The whole article is loaded in one go; the various pages are simply switched into view via jQuery.</p>\n\n	<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>Choose where you split the page; either via some fixed code your authors can enter at chosen break points, or at particular elements on the page (e.g. at each <code>&lt;h2&gt;</code>)</li>\n		<li>Choose the jQuery animation effect you wish to apply between pages</li>\n		<li>Auto-focus (scroll) back to the top of the document at the start of each virtual page</li>\n		<li>Make header and footer elements (which are usually only shown on the top/bottom of the article, respectively) appear on every virtual page</li>\n	</ul>\n\n	<h2 id=\"author\">Author / credits</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1101/smd_pagicle\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=31084\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>Somewhere on an individual article Page put a call to <code>&lt;txp:smd_pagicle /&gt;</code>. Configure the plugin using the following attributes; items with a border round them are mandatory, although the defaults usually suffice:</p>\n\n	<ul>\n		<li><span class=\"atnm mand\">container</span> : ID of the <span class=\"caps\">DOM</span> wrapper element that holds your article content. Default: <code>#content</code></li>\n		<li><span class=\"atnm\">splitat</span> : what you would like to search for in your article. Each time the plugin finds one of these items, a new page will begin. It can either be some text that you embed within your article body (e.g. <code>{PageHere}</code>) or some other content that might appear inside an <span class=\"caps\">HTML</span> tag pair. Note that if this is used in tandem with <code>splitwraptag</code> then you should either specify the entire content inside the tag OR use regular expression notation to match part of it. See examples <a href=\"#eg3\">3</a> and <a href=\"#eg4\">4</a> for more. Default: unset<sup class=\"footnote\"><a href=\"#fn8550394694a433f7e87c4e\">1</a></sup></li>\n		<li><span class=\"atnm\">splitwraptag</span> : an (X)HTML tag without brackets to search for. Can work together with <code>splitat</code> or on its own to search for any particular set of elements inside your <code>container</code>. Default: <code>h2</code><sup class=\"footnote\"><a href=\"#fn8550394694a433f7e87c4e\">1</a></sup></li>\n		<li><span class=\"atnm\">delete</span> : whether you wish to remove the element at which you chose to <code>splitat</code> / <code>splitwraptag</code>. The default is automatic: for <code>splitat</code> (used on its own) it is set to 1, and for <code>splitwraptag</code> it is set to 0 to preserve the tag and its contents</li>\n		<li><span class=\"atnm\">header</span> : a reference to the <span class=\"caps\">DOM</span> element that contains your article title. This is so it can be repeated at the top of each virtual page. Default: <code>h1</code></li>\n		<li><span class=\"atnm\">footer</span> : a reference to the <span class=\"caps\">DOM</span> element that contains your article footer. Normally this would contain the article comments, or some related articles that would only be displayed at the very end of the article. If you wish to copy the footer content into the bottom of every virtual page, specify its <span class=\"caps\">DOM</span> reference here. Default: unset</li>\n		<li><span class=\"atnm\">animate</span> : list of jQuery name:value pairs to pass to the <a href=\"http://docs.jquery.com/Effects/animate\">animate()</a> function. Each time you click a page number, this animation effect is played. Default: <code>opacity:show</code><sup class=\"footnote\"><a href=\"#fn12149713304a433f7e8841e\">2</a></sup></li>\n		<li><span class=\"atnm\">focus</span> : a <span class=\"caps\">DOM</span> element reference to scroll back to, whenever a page number is clicked from the nav. Can take an optional scroll speed in milliseconds by specifying the speed value after a colon. Default: <code>body:400</code></li>\n		<li><span class=\"atnm\">addnavto</span> : <span class=\"caps\">DOM</span> element to which to add the page numbers navigation widget. The element you name here &#8212; it may be a single element ID or a class so you can add the widget to more than one element &#8212; must already be included on your page. Default: <code>#smd_pagnav</code><sup class=\"footnote\"><a href=\"#fn12149713304a433f7e8841e\">2</a></sup></li>\n		<li><span class=\"atnm\">navclass</span> : class to apply to the navigation container. Default: <code>smd_pages</code><sup class=\"footnote\"><a href=\"#fn12149713304a433f7e8841e\">2</a></sup></li>\n		<li><span class=\"atnm\">navactive</span> : class to indicate the currently selected page. Default: <code>smd_currpage</code><sup class=\"footnote\"><a href=\"#fn12149713304a433f7e8841e\">2</a></sup></li>\n		<li><span class=\"atnm\">navdisabled</span> : during the course of navigating pages, the next/prev links are sometimes not required. Since it is not possibe in <span class=\"caps\">HTML</span> to &#8216;disable&#8217; an anchor, this class is applied to the arrows so you may choose to style them differently. Default: <code>smd_disabled</code><sup class=\"footnote\"><a href=\"#fn12149713304a433f7e8841e\">2</a></sup></li>\n		<li><span class=\"atnm\">navdisplay</span> : which navigation elements to show. Default: <code>f p # n l a</code> Choose from:\n	<ul>\n		<li><strong>f</strong>: first page (<code>&amp;laquo;</code>)</li>\n		<li><strong>p</strong>: previous page (<code>&amp;lt;</code>)</li>\n		<li><strong>#</strong>: the page numbers</li>\n		<li><strong>n</strong>: next page (<code>&amp;gt;</code>)</li>\n		<li><strong>l</strong>: last page (<code>&amp;raquo;</code>)</li>\n		<li><strong>a</strong>: show entire document, i.e. turn off paging (<code>All</code>)</li>\n	</ul></li>\n		<li><span class=\"atnm\">navtext</span> : list of text values to use for each of the chosen items in <code>navdisplay</code>. Specify the item code (f, p, n, l or a) followed by a colon and then the text you want to appear in the nav, e.g. <code>navtext=&quot;f:First, l:Last&quot;</code>. Any items you do not specify will use the defaults as listed above</li>\n	</ul>\n\n	<p id=\"fn8550394694a433f7e87c4e\" class=\"footnote\"><sup>1</sup> at least one of either <code>split</code> or <code>splitwraptag</code> are required.</p>\n\n	<p id=\"fn12149713304a433f7e8841e\" class=\"footnote\"><sup>2</sup> if any of these attributes are unset (<code>=&quot;&quot;</code>) in the tag, they will be reset to their defaults again.</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<h3 id=\"eg1\">Example 1</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;div id=&quot;smd_pagnav&quot;&gt;&lt;/div&gt;\n&lt;txp:smd_pagicle container=&quot;#pagemain&quot;\n     footer=&quot;#navlocal&quot; /&gt;\n</code></pre>\n\n	<p>This example will:</p>\n\n	<ul>\n		<li>split the document at <code>&lt;h2&gt;</code> tags inside the <code>pagemain</code> container div</li>\n		<li>add the page navigation to the <code>smd_pagnav</code> div</li>\n		<li>copy the <code>#navlocal</code> element to the bottom of each page</li>\n	</ul>\n\n	<h3 id=\"eg2\">Example 2</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;div class=&quot;smd_pagination&quot;&gt;&lt;/div&gt;\n&lt;txp:article /&gt;\n&lt;div class=&quot;smd_pagination&quot;&gt;&lt;/div&gt;\n&lt;txp:smd_pagicle container=&quot;#pagemain&quot;\n     splitat=&quot;{newpage}&quot; splitwraptag=&quot;&quot;\n     addnavto=&quot;.smd_pagination&quot; /&gt;\n</code></pre>\n\n	<p>This example:</p>\n\n	<ul>\n		<li>starts a new page every time the body has the phrase <code>{newpage}</code> in it</li>\n		<li>adds the nav above and below the article by using a class name (note the <code>.</code> in <code>.smd_pagination</code> to indicate it is a class not an ID we are referring to)</li>\n	</ul>\n\n	<p>Also note that <code>splitwraptag</code> has been unset.</p>\n\n	<h3 id=\"eg3\">Example 3</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;div id=&quot;smd_pagnav&quot;&gt;&lt;/div&gt;\n&lt;txp:smd_pagicle container=&quot;#wrapper&quot;\n     splitat=&quot;Commentary&quot; splitwraptag=&quot;h3&quot;\n     focus=&quot;h1:1000&quot; /&gt;\n</code></pre>\n\n	<p>This example will:</p>\n\n	<ul>\n		<li>Only split the document wherever it finds <code>&lt;h3&gt;Commentary&lt;/h3&gt;</code> inside the <code>wrapper</code> div</li>\n		<li>Scroll up to the <code>&lt;h1&gt;</code> tag and take one second (1000 milliseconds) to do so each time a page number is clicked</li>\n	</ul>\n\n	<h3 id=\"eg4\">Example 4</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;div id=&quot;smd_pagnav&quot;&gt;&lt;/div&gt;\n&lt;txp:smd_pagicle container=&quot;#wrapper&quot;\n     splitat=&quot;.*red.*&quot; splitwraptag=&quot;h3&quot; /&gt;\n</code></pre>\n\n	<p>This example will split the document wherever it finds an <code>&lt;h3&gt;</code> tag that has the characters <code>red</code> inside them. The <code>.*</code> is a <a href=\"http://www.regular-expressions.info/\">regular expression</a> wildcard that matches any characters before and after &#8216;red&#8217;.</p>\n\n	<p>For example, <code>&lt;h3&gt;Blinkered vision&lt;/h3&gt;</code> and <code>&lt;h3&gt;Drederick Tatum&lt;/h3&gt;</code> will match, but <code>&lt;h3&gt;Here doggy&lt;/h3&gt;</code> will not.</p>\n\n	<h3 id=\"eg5\">Example 5</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;div id=&quot;smd_pagnav&quot;&gt;&lt;/div&gt;\n&lt;txp:smd_pagicle container=&quot;#wrapper&quot;\n     navtext=&quot;&quot;f:##first##, l:##last## /&gt;\n</code></pre>\n\n	<p>This example:</p>\n\n	<ul>\n		<li>Splits the article at <code>&lt;h2&gt;</code> tags</li>\n		<li>Adds the navigation to the <code>smd_pagnav</code> div</li>\n		<li>Uses <span class=\"caps\">MLP</span> snippets to render language-dependent entries for the &#8216;f&#8217; and &#8216;l&#8217; nav items</li>\n	</ul>\n\n	<h3 id=\"changes\">Changelog</h3>\n\n	<ul>\n		<li>23 Jun 09 | 0.1 | Initial release</li>\n		<li>24 Jun 09 | 0.2 | Refactored code and removed most of the <code>wrap</code> attributes ; fixed validation issues and visibility of pager in non-paged articles, added &#8216;all&#8217; and allowed <span class=\"caps\">MLP</span> snippet replacement (all thanks ultramega)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n#smd_help ul { list-style-type:square; }\n#smd_help .required {color:red;}\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</s\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function smd_pagicle($atts, $thing=\'\') {\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'container\' => \'#content\',\r\n		\'splitat\' => \'\',\r\n		\'splitwraptag\' => \'h2\',\r\n		\'header\' => \'h1\',\r\n		\'footer\' => \'\',\r\n		\'delete\' => \'SMD_AUTO\',\r\n		\'focus\' => \'body:400\',\r\n		\'animate\' => \'opacity:show\',\r\n		\'addnavto\' => \'#smd_pagnav\',\r\n		\'navclass\' => \'smd_pages\',\r\n		\'navactive\' => \'smd_currpage\',\r\n		\'navdisabled\' => \'smd_disabled\',\r\n		\'navdisplay\' => \'f p # n l a\',\r\n		\'navtext\' => \'\',\r\n	), $atts));\r\n\r\n	// Sanitize the options so we can guarantee that jQuery at least has _something_ to swing off\r\n	// This part simply reaffirms the defaults above\r\n	if ($splitwraptag && $splitat == \"\") {\r\n		$splitat = \".*\";\r\n	}\r\n\r\n	$navreps = array(\'f\' => \'&laquo;\', \'p\' => \'<\', \'n\' => \'>\', \'l\' => \'&raquo;\', \'a\' => \'All\');\r\n	$animate = ($animate) ? $animate : \'opacity:show\';\r\n	$delete = ($delete) ? $delete : \'SMD_AUTO\';\r\n	$addnavto = ($addnavto) ? $addnavto : \'#smd_pagnav\';\r\n	$navclass = ($navclass) ? $navclass : \'smd_pages\';\r\n	$navactive = ($navactive) ? $navactive : \'smd_currpage\';\r\n	$navdisabled = ($navdisabled) ? $navdisabled : \'smd_disabled\';\r\n	$navtext = do_list($navtext);\r\n	foreach ($navtext as $navit) {\r\n		$navit = explode(\":\", $navit);\r\n		if(array_key_exists($navit[0], $navreps) && isset($navit[1])) {\r\n			$navreps[$navit[0]] = $navit[1];\r\n		}\r\n	}\r\n\r\n	// Remove spaces between items in addnavto and make a comma-separated list\r\n	$addnavto = implode(\",\", do_list($addnavto));\r\n\r\n	// Optional items\r\n	$theclass = ($navclass) ? \' class=\"\'.$navclass.\'\"\' : \' class=\"smd_pages\"\';\r\n	$theactive = ($navclass && $navactive) ? \' class=\"\'.$navclass.\' \'.$navactive.\'\"\' : (($navactive) ? \' class=\"\'.$navactive.\'\"\' : \'\');\r\n	$swrap_beg = ($splitwraptag) ? \'<\'.$splitwraptag.\'>\' : \'\';\r\n	$swrap_end = ($splitwraptag) ? \'</\'.$splitwraptag.\'>\' : \'\';\r\n\r\n	// Automatic default\r\n	if ($delete == \"SMD_AUTO\") {\r\n		$delete = ($splitwraptag) ? 0 : 1;\r\n	}\r\n\r\n	// split the navdisplay into its characters for making a js array\r\n	$navitems = implode(\",\", doArray(preg_split(\'//\', $navdisplay, -1, PREG_SPLIT_NO_EMPTY), \'doQuote\'));\r\n\r\n	// Handle the focus element\r\n	$jfocus = \'\';\r\n	if ($focus) {\r\n		$focusItems = do_list($focus, \':\');\r\n		$jfocus = (isset($focusItems[0])) ? $focusItems[0] : \'body\';\r\n		$jfocustime = (isset($focusItems[1])) ? $focusItems[1] : \'400\';\r\n		if ($jfocus) {\r\n			$jfocus = \'var pos = jQuery(\"\' .$jfocus. \'\").offset();\';\r\n			$jfocus .= \'jQuery(\"html\").animate({ scrollTop: pos.top + \"px\" }, { duration: \' .(($jfocustime) ? $jfocustime : 400).\' });\';\r\n		}\r\n	}\r\n\r\n	// Handle the animation element\r\n	$janimate = do_list($animate);\r\n	$elem = array();\r\n	foreach ($janimate as $animopt) {\r\n		if (trim($animopt) == \'\') continue;\r\n		list($jobj, $jval) = do_list($animopt, \":\");\r\n		$elem[] = doArray($jobj, \'doQuote\'). \':\'. doArray($jval, \'doQuote\');\r\n	}\r\n	$janimate = ($elem) ? \"{\" . implode(\",\", $elem) . \"}\" : \'\';\r\n\r\n	// Since the regex \'captures\' the delimiter, the matches alternate between\r\n	// a block of content (evens) and the delimiter itself (odds)\r\n$out = <<<EOJS\r\n<script type=\"text/javascript\">\r\n //<![CDATA[\r\nvar smd_pag_num = 1;\r\nvar smd_pag_max = 1;\r\nvar smd_pages = new Array();\r\nvar smd_pag_hdr = \'\';\r\nvar smd_pag_ftr = \'\';\r\n\r\nfunction smd_pagicle() {\r\n	var paged = \'\';\r\n	var nav = \'\';\r\n	var idx = 0;\r\n	var ctr = 1;\r\n	var navitems = [{$navitems}];\r\n\r\n	if (\'{$header}\' != \'\') {\r\n		smd_pag_hdr = jQuery(\"{$header}\");\r\n	}\r\n	if (\'{$footer}\' != \'\') {\r\n		smd_pag_ftr = jQuery(\"{$footer}\");\r\n	}\r\n\r\n	// Stash the full page\r\n	smd_pages[0] = jQuery(\"{$container}\").html();\r\n\r\n	var reg = new RegExp(\"({$swrap_beg}{$splitat}{$swrap_end})\");\r\n	jQuery(\"{$container}\").each(function(content) {\r\n		smd_pageparts = jQuery(this).html().split(reg);\r\n		var capture = \'\';\r\n		jQuery(smd_pageparts).each(function(part) {\r\n			if (idx%2 == 1 && {$delete}==0) { // For odd parts that are not to be removed\r\n				capture = this;\r\n			}\r\n			if (idx%2 == 0) {\r\n				// Store the individual pages\r\n				smd_pages[ctr] = capture + this;\r\n				ctr++;\r\n			}\r\n			idx++;\r\n		});\r\n	});\r\n\r\n	smd_pag_max = ctr-1;\r\n\r\n	// Add the paging options if required\r\n	if (smd_pag_max>1) {\r\n		jQuery.each(navitems, function() {\r\n			switch(this.toString()) {\r\n				case \'a\':\r\n					nav += \'<a id=\"smd_all\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'all\\\');return(false);\">{$navreps[\'a\']}</a>\';\r\n					break;\r\n				case \'f\':\r\n					nav += \'<a id=\"smd_fst\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'1\\\');return(false);\">{$navreps[\'f\']}</a>\';\r\n					break;\r\n				case \'l\':\r\n					nav += \'<a id=\"smd_lst\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'\' +smd_pag_max+ \'\\\');return(false);\">{$navreps[\'l\']}</a>\';\r\n					break;\r\n				case \'p\':\r\n					nav += \'<a id=\"smd_prv\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'-1\\\');return(false);\">{$navreps[\'p\']}</a>\';\r\n					break;\r\n				case \'n\':\r\n					nav += \'<a id=\"smd_nxt\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'+1\\\');return(false);\">{$navreps[\'n\']}</a>\';\r\n					break;\r\n				case \'#\':\r\n					for (jdx = 1; jdx < ctr; jdx++) {\r\n						nav += \' <a href=\"#\"\' +((smd_pag_num==jdx) ? \'{$theactive}\' : \'{$theclass}\')+ \'\" onclick=\"smd_pag_show(\\\'\' +jdx+ \'\\\');return(false);\">\' +jdx+ \'</a>\';\r\n					}\r\n					break;\r\n				default:\r\n					nav += \' \';\r\n					break;\r\n			}\r\n		});\r\n		jQuery(\"{$addnavto}\").each(function() {\r\n			jQuery(this).append(nav);\r\n		});\r\n	}\r\n}\r\n\r\n// Display a particualr page\r\nfunction smd_pag_show(dir) {\r\n	if (dir==\"all\") {\r\n		jQuery(\"a.{$navclass}\").replaceWith(\'\');\r\n		smd_pag_num = 0;\r\n	} else {\r\n		re = /(\\+|\\-)?(\\d+)/;\r\n		matches = re.exec(dir);\r\n		if (matches[1] == \"+\" && smd_pag_num < smd_pag_max) {\r\n			smd_pag_num++;\r\n		}\r\n		if (matches[1] == \"-\" && smd_pag_num > 1) {\r\n			smd_pag_num--;\r\n		}\r\n		if (matches[1] == null) {\r\n			smd_pag_num = matches[2];\r\n		}\r\n	}\r\n\r\n	// Show the page\r\n	jQuery(\"{$container}\").hide();\r\n	jQuery(\"{$container}\").empty();\r\n	if (smd_pag_hdr && (smd_pag_num > 1)) {\r\n		jQuery(\"{$container}\").prepend(smd_pag_hdr);\r\n	}\r\n	jQuery(\"{$container}\").append(smd_pages[smd_pag_num]);\r\n	if (smd_pag_ftr && (smd_pag_num < smd_pag_max-1)) {\r\n		jQuery(\"{$container}\").append(smd_pag_ftr);\r\n	}\r\n	if (\"{$janimate}\" != \"\") {\r\n		jQuery(\"{$container}\").animate({$janimate});\r\n	}\r\n\r\n	if (smd_pag_num==1) {\r\n		jQuery(\"#smd_fst\").addClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_prv\").addClass(\"{$navdisabled}\");\r\n	} else {\r\n		jQuery(\"#smd_fst\").removeClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_prv\").removeClass(\"{$navdisabled}\");\r\n	}\r\n	if (smd_pag_num==smd_pag_max) {\r\n		jQuery(\"#smd_lst\").addClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_nxt\").addClass(\"{$navdisabled}\");\r\n	} else {\r\n		jQuery(\"#smd_lst\").removeClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_nxt\").removeClass(\"{$navdisabled}\");\r\n	}\r\n\r\n	// Set the current class\r\n	jQuery(\"a.{$navclass}\").each(function() {\r\n		jQuery(this).removeClass(\"{$navactive}\");\r\n		if (jQuery(this).text() == smd_pag_num) {\r\n			jQuery(this).addClass(\"{$navactive}\");\r\n		}\r\n	});\r\n\r\n	// And scroll to the top\r\n	{$jfocus}\r\n	return false;\r\n}\r\n\r\njQuery(function() {\r\n	smd_pagicle();\r\n	if (smd_pag_max>1) {\r\n		smd_pag_show(1)\r\n	}\r\n});\r\n //]]>\r\n</script>\r\nEOJS;\r\n	return $out;\r\n}', 'function smd_pagicle($atts, $thing=\'\') {\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'container\' => \'#content\',\r\n		\'splitat\' => \'\',\r\n		\'splitwraptag\' => \'h2\',\r\n		\'header\' => \'h1\',\r\n		\'footer\' => \'\',\r\n		\'delete\' => \'SMD_AUTO\',\r\n		\'focus\' => \'body:400\',\r\n		\'animate\' => \'opacity:show\',\r\n		\'addnavto\' => \'#smd_pagnav\',\r\n		\'navclass\' => \'smd_pages\',\r\n		\'navactive\' => \'smd_currpage\',\r\n		\'navdisabled\' => \'smd_disabled\',\r\n		\'navdisplay\' => \'f p # n l a\',\r\n		\'navtext\' => \'\',\r\n	), $atts));\r\n\r\n	// Sanitize the options so we can guarantee that jQuery at least has _something_ to swing off\r\n	// This part simply reaffirms the defaults above\r\n	if ($splitwraptag && $splitat == \"\") {\r\n		$splitat = \".*\";\r\n	}\r\n\r\n	$navreps = array(\'f\' => \'&laquo;\', \'p\' => \'<\', \'n\' => \'>\', \'l\' => \'&raquo;\', \'a\' => \'All\');\r\n	$animate = ($animate) ? $animate : \'opacity:show\';\r\n	$delete = ($delete) ? $delete : \'SMD_AUTO\';\r\n	$addnavto = ($addnavto) ? $addnavto : \'#smd_pagnav\';\r\n	$navclass = ($navclass) ? $navclass : \'smd_pages\';\r\n	$navactive = ($navactive) ? $navactive : \'smd_currpage\';\r\n	$navdisabled = ($navdisabled) ? $navdisabled : \'smd_disabled\';\r\n	$navtext = do_list($navtext);\r\n	foreach ($navtext as $navit) {\r\n		$navit = explode(\":\", $navit);\r\n		if(array_key_exists($navit[0], $navreps) && isset($navit[1])) {\r\n			$navreps[$navit[0]] = $navit[1];\r\n		}\r\n	}\r\n\r\n	// Remove spaces between items in addnavto and make a comma-separated list\r\n	$addnavto = implode(\",\", do_list($addnavto));\r\n\r\n	// Optional items\r\n	$theclass = ($navclass) ? \' class=\"\'.$navclass.\'\"\' : \' class=\"smd_pages\"\';\r\n	$theactive = ($navclass && $navactive) ? \' class=\"\'.$navclass.\' \'.$navactive.\'\"\' : (($navactive) ? \' class=\"\'.$navactive.\'\"\' : \'\');\r\n	$swrap_beg = ($splitwraptag) ? \'<\'.$splitwraptag.\'>\' : \'\';\r\n	$swrap_end = ($splitwraptag) ? \'</\'.$splitwraptag.\'>\' : \'\';\r\n\r\n	// Automatic default\r\n	if ($delete == \"SMD_AUTO\") {\r\n		$delete = ($splitwraptag) ? 0 : 1;\r\n	}\r\n\r\n	// split the navdisplay into its characters for making a js array\r\n	$navitems = implode(\",\", doArray(preg_split(\'//\', $navdisplay, -1, PREG_SPLIT_NO_EMPTY), \'doQuote\'));\r\n\r\n	// Handle the focus element\r\n	$jfocus = \'\';\r\n	if ($focus) {\r\n		$focusItems = do_list($focus, \':\');\r\n		$jfocus = (isset($focusItems[0])) ? $focusItems[0] : \'body\';\r\n		$jfocustime = (isset($focusItems[1])) ? $focusItems[1] : \'400\';\r\n		if ($jfocus) {\r\n			$jfocus = \'var pos = jQuery(\"\' .$jfocus. \'\").offset();\';\r\n			$jfocus .= \'jQuery(\"html\").animate({ scrollTop: pos.top + \"px\" }, { duration: \' .(($jfocustime) ? $jfocustime : 400).\' });\';\r\n		}\r\n	}\r\n\r\n	// Handle the animation element\r\n	$janimate = do_list($animate);\r\n	$elem = array();\r\n	foreach ($janimate as $animopt) {\r\n		if (trim($animopt) == \'\') continue;\r\n		list($jobj, $jval) = do_list($animopt, \":\");\r\n		$elem[] = doArray($jobj, \'doQuote\'). \':\'. doArray($jval, \'doQuote\');\r\n	}\r\n	$janimate = ($elem) ? \"{\" . implode(\",\", $elem) . \"}\" : \'\';\r\n\r\n	// Since the regex \'captures\' the delimiter, the matches alternate between\r\n	// a block of content (evens) and the delimiter itself (odds)\r\n$out = <<<EOJS\r\n<script type=\"text/javascript\">\r\n //<![CDATA[\r\nvar smd_pag_num = 1;\r\nvar smd_pag_max = 1;\r\nvar smd_pages = new Array();\r\nvar smd_pag_hdr = \'\';\r\nvar smd_pag_ftr = \'\';\r\n\r\nfunction smd_pagicle() {\r\n	var paged = \'\';\r\n	var nav = \'\';\r\n	var idx = 0;\r\n	var ctr = 1;\r\n	var navitems = [{$navitems}];\r\n\r\n	if (\'{$header}\' != \'\') {\r\n		smd_pag_hdr = jQuery(\"{$header}\");\r\n	}\r\n	if (\'{$footer}\' != \'\') {\r\n		smd_pag_ftr = jQuery(\"{$footer}\");\r\n	}\r\n\r\n	// Stash the full page\r\n	smd_pages[0] = jQuery(\"{$container}\").html();\r\n\r\n	var reg = new RegExp(\"({$swrap_beg}{$splitat}{$swrap_end})\");\r\n	jQuery(\"{$container}\").each(function(content) {\r\n		smd_pageparts = jQuery(this).html().split(reg);\r\n		var capture = \'\';\r\n		jQuery(smd_pageparts).each(function(part) {\r\n			if (idx%2 == 1 && {$delete}==0) { // For odd parts that are not to be removed\r\n				capture = this;\r\n			}\r\n			if (idx%2 == 0) {\r\n				// Store the individual pages\r\n				smd_pages[ctr] = capture + this;\r\n				ctr++;\r\n			}\r\n			idx++;\r\n		});\r\n	});\r\n\r\n	smd_pag_max = ctr-1;\r\n\r\n	// Add the paging options if required\r\n	if (smd_pag_max>1) {\r\n		jQuery.each(navitems, function() {\r\n			switch(this.toString()) {\r\n				case \'a\':\r\n					nav += \'<a id=\"smd_all\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'all\\\');return(false);\">{$navreps[\'a\']}</a>\';\r\n					break;\r\n				case \'f\':\r\n					nav += \'<a id=\"smd_fst\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'1\\\');return(false);\">{$navreps[\'f\']}</a>\';\r\n					break;\r\n				case \'l\':\r\n					nav += \'<a id=\"smd_lst\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'\' +smd_pag_max+ \'\\\');return(false);\">{$navreps[\'l\']}</a>\';\r\n					break;\r\n				case \'p\':\r\n					nav += \'<a id=\"smd_prv\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'-1\\\');return(false);\">{$navreps[\'p\']}</a>\';\r\n					break;\r\n				case \'n\':\r\n					nav += \'<a id=\"smd_nxt\" href=\"#\"{$theclass} onclick=\"smd_pag_show(\\\'+1\\\');return(false);\">{$navreps[\'n\']}</a>\';\r\n					break;\r\n				case \'#\':\r\n					for (jdx = 1; jdx < ctr; jdx++) {\r\n						nav += \' <a href=\"#\"\' +((smd_pag_num==jdx) ? \'{$theactive}\' : \'{$theclass}\')+ \'\" onclick=\"smd_pag_show(\\\'\' +jdx+ \'\\\');return(false);\">\' +jdx+ \'</a>\';\r\n					}\r\n					break;\r\n				default:\r\n					nav += \' \';\r\n					break;\r\n			}\r\n		});\r\n		jQuery(\"{$addnavto}\").each(function() {\r\n			jQuery(this).append(nav);\r\n		});\r\n	}\r\n}\r\n\r\n// Display a particualr page\r\nfunction smd_pag_show(dir) {\r\n	if (dir==\"all\") {\r\n		jQuery(\"a.{$navclass}\").replaceWith(\'\');\r\n		smd_pag_num = 0;\r\n	} else {\r\n		re = /(\\+|\\-)?(\\d+)/;\r\n		matches = re.exec(dir);\r\n		if (matches[1] == \"+\" && smd_pag_num < smd_pag_max) {\r\n			smd_pag_num++;\r\n		}\r\n		if (matches[1] == \"-\" && smd_pag_num > 1) {\r\n			smd_pag_num--;\r\n		}\r\n		if (matches[1] == null) {\r\n			smd_pag_num = matches[2];\r\n		}\r\n	}\r\n\r\n	// Show the page\r\n	jQuery(\"{$container}\").hide();\r\n	jQuery(\"{$container}\").empty();\r\n	if (smd_pag_hdr && (smd_pag_num > 1)) {\r\n		jQuery(\"{$container}\").prepend(smd_pag_hdr);\r\n	}\r\n	jQuery(\"{$container}\").append(smd_pages[smd_pag_num]);\r\n	if (smd_pag_ftr && (smd_pag_num < smd_pag_max-1)) {\r\n		jQuery(\"{$container}\").append(smd_pag_ftr);\r\n	}\r\n	if (\"{$janimate}\" != \"\") {\r\n		jQuery(\"{$container}\").animate({$janimate});\r\n	}\r\n\r\n	if (smd_pag_num==1) {\r\n		jQuery(\"#smd_fst\").addClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_prv\").addClass(\"{$navdisabled}\");\r\n	} else {\r\n		jQuery(\"#smd_fst\").removeClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_prv\").removeClass(\"{$navdisabled}\");\r\n	}\r\n	if (smd_pag_num==smd_pag_max) {\r\n		jQuery(\"#smd_lst\").addClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_nxt\").addClass(\"{$navdisabled}\");\r\n	} else {\r\n		jQuery(\"#smd_lst\").removeClass(\"{$navdisabled}\");\r\n		jQuery(\"#smd_nxt\").removeClass(\"{$navdisabled}\");\r\n	}\r\n\r\n	// Set the current class\r\n	jQuery(\"a.{$navclass}\").each(function() {\r\n		jQuery(this).removeClass(\"{$navactive}\");\r\n		if (jQuery(this).text() == smd_pag_num) {\r\n			jQuery(this).addClass(\"{$navactive}\");\r\n		}\r\n	});\r\n\r\n	// And scroll to the top\r\n	{$jfocus}\r\n	return false;\r\n}\r\n\r\njQuery(function() {\r\n	smd_pagicle();\r\n	if (smd_pag_max>1) {\r\n		smd_pag_show(1)\r\n	}\r\n});\r\n //]]>\r\n</script>\r\nEOJS;\r\n	return $out;\r\n}', '3a4b3436acbb623163a8a223285c90ab', '', '', 0, 5, 0),
('smd_parent', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.1', 'A txp:category/cat1/cat2 extension with parent options', '<div id=\"smd_help\">\n\n	<h1>smd_parent</h1>\n\n	<p>An extension of <code>&#60;txp:category /&#62;</code> that allows any parent element to be retrieved. Works with all article, link, image and file category types, takes exactly the same arguments as the conventional <code>category</code> tag plus the following attributes.</p>\n\n	<h4 class=\"att-name \">level</h4>\n\n	<p>The parent level to return. Defaults to &#8220;1&#8221; (i.e. the &#8220;topmost level&#8221;). Any integer returns that particular parent level; a negative number specifies an offset &#8220;back up the tree&#8221; from the current category</p>\n\n	<h4 class=\"att-name \">catnum</h4>\n\n	<p>By default the plugin always chooses the global category (<code>?c</code>) unless you override it with the <code>name</code> attribute. If you use a <code>1</code> or a <code>2</code> for the <code>catnum</code> option you tell the plugin to use the category1 or category2 value instead</p>\n\n	<h3 id=\"notes\">Notes</h3>\n\n	<ol>\n		<li>Using the <code>name</code> attribute <em>always</em> overrides any catnum or global category (even if it&#8217;s a typo!)</li>\n		<li>Using <code>catnum</code> forces <code>type</code> to &#8220;article&#8221; for obvious reasons and also forces the tag to be used in an article context. You will get a tag warning if this condition is not met, exactly as with <code>&#60;txp:category1 /&#62;</code> or <code>&#60;txp:category2 /&#62;</code></li>\n		<li>If at any time the parent level &#8220;falls off&#8221; the tree you will get nothing back</li>\n		<li>If you tell the plugin to return the current category you will also get nothing back (use <code>&#60;txp:category /&#62;</code> for that!)</li>\n	</ol>\n\n	<p>All other attributes <code>class</code>, <code>link</code>, <code>title</code>, <code>wraptag</code>, <code>name</code>, <code>section</code>, <code>this_section</code>, and <code>type</code> are honoured exactly as they are in <code>&#60;txp:category /&#62;</code>.</p>\n\n	<h2>Author</h2>\n\n	<p><a href=\"http://stefdawson.com/commentForm\">Stef Dawson</a>. Written for <span class=\"caps\">TXP</span> user Kjeld Duits and given to the community.</p>\n\n	<h2>Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/940/smd_parent\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&#62; Plugins pane, install and enable the plugin.</p>\n\n	<h2>Examples</h2>\n\n	<p>All examples use the following category structure (level numbers are in parentheses):</p>\n\n	<ul>\n		<li>(1) animal\n	<ul>\n		<li>(2) mammal\n	<ul>\n		<li>(3) tiger</li>\n		<li>(3) lion</li>\n		<li>(3) human\n	<ul>\n		<li>(4) male</li>\n		<li>(4) female</li>\n	</ul></li>\n		<li>(3) zebra</li>\n	</ul></li>\n		<li>(2) reptile\n	<ul>\n		<li>(3) crocodile</li>\n		<li>(3) alligator</li>\n	</ul></li>\n	</ul></li>\n	</ul>\n\n	<h3 id=\"eg1\">Example 1</h3>\n\n	<p>The following table shows the result of various tag attributes when you are in the global category &#8220;zebra&#8221;:</p>\n\n	<table>\n		<tr>\n			<th>Tag </th>\n			<th>Returns </th>\n			<th>Explanation </th>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent /&#62;</code> </td>\n			<td>animal</td>\n			<td>Default is top level (1) </td>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent level=&#34;2&#34; /&#62;</code> </td>\n			<td>mammal</td>\n			<td>Retrieves level 2 category in this branch</td>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent level=&#34;-1&#34; /&#62;</code> </td>\n			<td>mammal</td>\n			<td>Traverses one level &#8220;up&#8221; from where we are</td>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent level=&#34;42&#34; /&#62;</code> </td>\n			<td>&#160;</td>\n			<td>The name of a 1980s pop group, not a valid category level :-)</td>\n		</tr>\n	</table>\n\n	<h3 id=\"eg2\">Example 2</h3>\n\n	<p>When you are in an article with a category1 of &#8220;zebra&#8221; and a global category of &#8220;female&#8221;, the following table shows the result of various tag attributes:</p>\n\n	<table>\n		<tr>\n			<th>Tag </th>\n			<th>Returns </th>\n			<th>Explanation </th>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent level=&#34;-1&#34; catnum=&#34;1&#34; /&#62;</code> </td>\n			<td>mammal</td>\n			<td>One level up the tree</td>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent level=&#34;-3&#34; /&#62;</code> </td>\n			<td>animal</td>\n			<td>Three levels up the tree; omitting catnum uses the global category instead</td>\n		</tr>\n		<tr>\n			<td> <code>&#60;txp:smd_parent name=&#34;crocodile&#34; level=&#34;2&#34; catnum=&#34;1&#34; /&#62;</code> </td>\n			<td>reptile</td>\n			<td>Returns level 2; the <code>name</code> attribute overrides <em>all</em> categories</td>\n		</tr>\n	</table>\n\n	<h2 class=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>15 Jan 08 | <a href=\"http://stefdawson.com/downloads/smd_parent_v0.1.txt\">v0.1</a> | Initial release</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.3 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help a:link, #smd_help a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .att-name { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:100%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em;}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function smd_parent($atts, $thing=\'\') {\r\n	global $pretext, $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'catnum\' => \'\',\r\n		\'level\' => \'1\',\r\n		\'class\' => \'\',\r\n		\'link\' => 0,\r\n		\'name\' => \'\',\r\n		\'section\' => \'\',\r\n		\'this_section\' => 0,\r\n		\'title\' => 0,\r\n		\'type\' => \'article\',\r\n		\'wraptag\' => \'\',\r\n	), $atts));\r\n\r\n	// Set the relevant category; either named, global, cat1 or cat2\r\n	$category = ($name) ? $name : $pretext[\'c\'];\r\n	if ($catnum && !$name) {\r\n		assert_article();\r\n		$category = \"category\".$catnum;\r\n		$category = ($thisarticle[$category]) ? $thisarticle[$category] : \'\';\r\n		$type = \'article\';\r\n	}\r\n	$out = \'\';\r\n	if ($category) {\r\n		$tree = getTreePath(doSlash($category), $type);\r\n		$reqLevel = $currLevel = count($tree) - 1;\r\n		if (strpos($level, \"-\") === 0) {\r\n			$levelDiff = intval(substr($level, 1));\r\n			$reqLevel = (is_numeric($levelDiff)) ? $currLevel - $levelDiff : 0;\r\n		} else if (is_numeric($level)) {\r\n			$reqLevel = $level;\r\n		}\r\n		if ($reqLevel > 0 && $reqLevel < $currLevel) {\r\n			$category = $tree[$reqLevel][\'name\'];\r\n			$out = category(array(\r\n					\'class\' => $class,\r\n					\'link\' => $link,\r\n					\'title\' => $title,\r\n					\'section\' => $section,\r\n					\'this_section\' => $this_section,\r\n					\'wraptag\' => $wraptag,\r\n					\'name\' => $category,\r\n					\'type\' => $type,\r\n				) , $thing);\r\n		}\r\n	}\r\n	return $out;\r\n}', 'function smd_parent($atts, $thing=\'\') {\r\n	global $pretext, $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'catnum\' => \'\',\r\n		\'level\' => \'1\',\r\n		\'class\' => \'\',\r\n		\'link\' => 0,\r\n		\'name\' => \'\',\r\n		\'section\' => \'\',\r\n		\'this_section\' => 0,\r\n		\'title\' => 0,\r\n		\'type\' => \'article\',\r\n		\'wraptag\' => \'\',\r\n	), $atts));\r\n\r\n	// Set the relevant category; either named, global, cat1 or cat2\r\n	$category = ($name) ? $name : $pretext[\'c\'];\r\n	if ($catnum && !$name) {\r\n		assert_article();\r\n		$category = \"category\".$catnum;\r\n		$category = ($thisarticle[$category]) ? $thisarticle[$category] : \'\';\r\n		$type = \'article\';\r\n	}\r\n	$out = \'\';\r\n	if ($category) {\r\n		$tree = getTreePath(doSlash($category), $type);\r\n		$reqLevel = $currLevel = count($tree) - 1;\r\n		if (strpos($level, \"-\") === 0) {\r\n			$levelDiff = intval(substr($level, 1));\r\n			$reqLevel = (is_numeric($levelDiff)) ? $currLevel - $levelDiff : 0;\r\n		} else if (is_numeric($level)) {\r\n			$reqLevel = $level;\r\n		}\r\n		if ($reqLevel > 0 && $reqLevel < $currLevel) {\r\n			$category = $tree[$reqLevel][\'name\'];\r\n			$out = category(array(\r\n					\'class\' => $class,\r\n					\'link\' => $link,\r\n					\'title\' => $title,\r\n					\'section\' => $section,\r\n					\'this_section\' => $this_section,\r\n					\'wraptag\' => $wraptag,\r\n					\'name\' => $category,\r\n					\'type\' => $type,\r\n				) , $thing);\r\n		}\r\n	}\r\n	return $out;\r\n}', '538584b6ff5356a1fb5a487ef85ae0c5', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_prognostics', 0, 'Stef Dawson', 'https://stefdawson.com/', '0.5.0', 'Pro-active diagnostics that alarm when things have changed in Textpattern', '<h1>smd_prognostics</h1>\n\n<p>The <em>Admin -&gt; Diagnostics</em> panel is great if you remember to look at it from time to time. But if your server is compromised, how would you know about a sneaky Trojan or malware until your visitors complain or the search engines block your site?</p>\n\n<p>The answer is to use smd_prognostics to tell you when things have changed so you can take action immediately.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Monitor files and attacks in and around your Textpattern installation.</li>\n	<li>Automatically detect any changes and display/send notification to designated user(s).</li>\n	<li>Customise where and how often the checks are performed.</li>\n	<li>Acknowledge known alterations and send repeat alarms at configurable intervals if the situation remains unchecked.</li>\n</ul>\n\n<h2>Installation / uninstallation</h2>\n\n<p><strong>Requires Textpattern 4.7.2+</strong></p>\n\n<p>Download the plugin from either <a href=\"https://github.com/Bloke/smd_prognostics/releases\" rel=\"nofollow\">GitHub</a>, or the <a href=\"https://stefdawson.com/sw\" rel=\"nofollow\">software page</a>, paste the code into the Textpattern <em>Admin -&gt; Plugins</em> panel, install and enable the plugin. Note that it is recommended that the plugin runs as priority 1 so it can detect things before other plugins.</p>\n\n<p>To uninstall, delete from the <em>Admin -&gt; Plugins</em> panel. You may use smd_prefalizer to remove any <code>smd_prognostics_*</code> preferences that the plugin created.</p>\n\n<p>Visit the <a href=\"https://forum.textpattern.io/viewtopic.php?id=34938\" rel=\"nofollow\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<h2>Quick start: the three steps to detection</h2>\n\n<ol>\n	<li>Visit <em>Extensions -&gt; Prognostics</em>, alter the setup information to taste and <strong>make sure you <em>Save</em> it</strong></li>\n	<li>Click the Prognostic page&#8217;s <em>Files</em> button (ignore any warning about missing checksums for now: they&#8217;ve not been created yet) and choose the files you wish to monitor. <strong>Make sure you <em>Save</em> the changes</strong></li>\n	<li>Go about your business. If the filesystem changes you&#8217;ll be told about it. Please consider helping to <a href=\"https://stefdawson.com/#smd_proghelp_better\" rel=\"nofollow\">make the plugin better</a></li>\n</ol>\n\n<h2>Setup</h2>\n\n<p>The <em>Prognostic setup</em> page houses the following configuration options:</p>\n\n<h3>Monitoring options</h3>\n\n<dl>\n	<dt><span>Check files on public side clicks</span></dt>\n	<dd>Checks are always performed when people visit admin side tabs. You may also elect to check the files when visitors/bots browse your web site if you prefer (see <a href=\"https://stefdawson.com/#smd_proghelp_howitworks\" rel=\"nofollow\">how it works</a> for details).</dd>\n	<dt><span>Check files (at most) every</span></dt>\n	<dd>The plugin does not check the files every click because that would place unnecessary strain on the server. Instead you choose a configurable time &#8212; in seconds &#8212; after which the very next admin- or public-side click will trigger the check.</dd>\n	<dt><span>Check files between</span></dt>\n	<dd>Two times of day between which the files will be checked. You could set this to off-peak hours if you preferred to minimise the plugin&#8217;s impact on daytime visitors.</dd>\n	<dd>Default: <code>00:00 =&gt; 23:59</code></dd>\n	<dt><span>Check this many files each time</span></dt>\n	<dd>Instead of testing every single file each time &#8212; which will slow down your site / admin side &#8212; it is good practice to only check a subset of files every time the plugin is triggered.</dd>\n	<dt><span>Alarm on detection and every</span></dt>\n	<dd>When something has changed and it&#8217;s detected, an alarm action is always taken. But if the plugin continually bombarded you with information it would be annoying and not as useful. This setting allows you to configure the interval between repeat nags of the same message. Once a day (the default of 86400 seconds) by e-mail is usually enough unless you&#8217;re a masochist. Note that if more files are changed &#8212; even when the timeout period has yet to elapse &#8212; you will receive another alert.</dd>\n	<dd>You may specify two values (comma-separated) if you wish: the first is the time between notifications to the Textpattern admin side; the second is the time between e-mails. If you use only one value then it will be applied to both.</dd>\n	<dt><span>Notify via</span></dt>\n	<dd>Choose how you wish to be notified:\n	<dl>\n		<dd><em>Txp Interface</em> will display a message on the Textpattern admin side when a change is detected.</dd>\n		<dd><em>E-mail</em> will send out an e-mail about the changes.</dd>\n	</dl></dd>\n	<dt><span>Send e-mail to</span></dt>\n	<dd>Comma-separated list of e-mail address recipients. If this is empty no e-mails will be sent, even if you choose to be notified via e-mail.</dd>\n	<dt><span>Send forensics to</span></dt>\n	<dd>Comma-separated list of e-mail address recipients to receive forensics data (the nattily-titled <em>frognostics</em>). You can choose nobody if you wish to disable this feature, or send them to yourself, or <a href=\"https://stefdawson.com/#smd_proghelp_better\" rel=\"nofollow\">to me for analaysis</a>. <strong>Please note that some data is quite sensitive and may divulge paths to your files or site</strong>. If you prefer to keep stuff private by all means sanitize the forensics and send the edited highlights to me for analysis if you wish.</dd>\n</dl>\n\n<h3>Realtime options</h3>\n\n<dl>\n	<dt><span>Block request headers</span></dt>\n	<dd>If you enable this facility you can elect to block or allow certain types of speical <span class=\"caps\">HTTP</span> request. Any request of a checked type will be blocked and a message will be returned instead of the page. The core types <span class=\"caps\">GET</span>, <span class=\"caps\">POST</span>, <span class=\"caps\">HEAD</span> and <span class=\"caps\">OPTIONS</span> are automatically allowed because Textpattern requires them. The remaining three &#8212; <span class=\"caps\">TRACE</span>, <span class=\"caps\">PUT</span> and <span class=\"caps\">DELETE</span> &#8212; are specialist headers that aren&#8217;t used by the core but may be required by some plugins. Permit them if you wish by clearing the relevant check box(es), otherwise for maximum protection keep this feature switched on with all three types of header checked.</dd>\n	<dt><span>Protect against <span class=\"caps\">SQL</span> injection</span></dt>\n	<dd>Monitor incoming <span class=\"caps\">URL</span>s for <span class=\"caps\">SQL</span> injection attacks and deal with them in a variety of ways. When an attack is detected the request can be <code>Block</code>ed or you can send an <span class=\"caps\">HTTP</span> status header so you can deal with the attack on your error pages. If you type anything in the <code>with</code> box, the given message will be displayed on the page.</dd>\n	<dd>You may specify <code>txp_form:name-of-form</code> in the <code>with</code> box to hand off control to a custom form first before the result is thrown to the page. You could use this, for example, to mail off your own diagnostics or to make a decision on whether the <span class=\"caps\">SQL</span> attack was legitimate or not and either block it yourself or strip out anything dodgy and send the request on. If you wish to go further, there is a <a href=\"https://stefdawson.com/#smd_proghelp_callbacks\" rel=\"nofollow\">callback available</a>.</dd>\n	<dd>The sensitivity may be altered by raising the number; 1 is the most sensitive (i.e. 1 of the trigger words will trip the switch). You can control <span class=\"caps\">GET</span> and <span class=\"caps\">POST</span> sensitivity independently by putting two comma-separated values in the box. If you accept comments on your site it may be prudent to raise the <span class=\"caps\">POST</span> value (the second one), as any comment that contains words like <code>select</code> or <code>union</code> or even Textile&#8217;s <code>--</code> will trip the injector. A good trade-off betwen security and false alarms for a site with comments is <code>1, 3</code>. Raise further if your commenters regularly post code fragments or <span class=\"caps\">SQL</span> statements.</dd>\n	<dd>There is also the option to employ an <abbr title=\"Cross-Site Scripting\"><span class=\"caps\">XSS</span></abbr> shield which will encode all spurious characters to help prevent injection / cookie attacks. Note that because the comment preview step is always skipped (to avoid triggering on preview and annoying frognostic recipients), this may cause publicly submitted comments to not appear as previewed. Especially if angle brackets or tags are used in the comment text.</dd>\n	<dt><span>Send forensics for</span></dt>\n	<dd>E-mail frognostics info to the designated e-mail address(es) when either header violations, <span class=\"caps\">SQL</span> injections, or both are detected.</dd>\n</dl>\n\n<h3>Filesystem options</h3>\n\n<dl>\n	<dt><span>Check files for</span></dt>\n	<dd>Choose what type of alterations you wish to monitor: new files added, files deleted or files modified. By default, additions are not selected because it incurs a (tiny) extra processing penalty to the checks. Therefore if you want to monitor for additions, you need to opt-in.</dd>\n	<dt><span>File locations</span></dt>\n	<dd>Comma-separated list of directories from which you wish to be able to select files for monitoring. Choose only the top-level folder; everything beneath will automatically be selected. It defaults to your site root. If using a multi-site setup you may have to alter this.</dd>\n	<dt><span>Exclude folders</span></dt>\n	<dd>Since the list of files under your Textpattern installation may potentially be large you can designate some folders to be automatically excluded, which makes the file list shorter.</dd>\n	<dd>Default: <code>images, files, tmp</code>.</dd>\n	<dt><span>Ignore files</span></dt>\n	<dd>Some files, such as the error_log file on typical Linux hosts, pop up unannounced and you may periodically delete them. On development sites this can cause excessive alarms during debugging. To minimise this, you can list files here that you always wish to ignore. No paths are necessary: just supply a comma-separated list of file names. Wildcards (* and ?) are permissable.</dd>\n	<dd>Note that the prognostics checksums file is always treated as a special case so there&#8217;s no need to list it explicitly.</dd>\n</dl>\n\n<h3>Plugin options</h3>\n\n<dl>\n	<dt><span>Restrict prognostic config to</span></dt>\n	<dd>If you have to issue Publisher / Managing Editor privs to someone else as well as yourself but don&#8217;t want anyone messing with the plugin settings, put a list of permitted user names in this box. <span>Be careful. The names are case-sensitive and if you misspell a name you can lock yourself out of editing the settings!</span></dd>\n	<dt><span>Admin-side <span class=\"caps\">URL</span></span></dt>\n	<dd>The <span class=\"caps\">URL</span> of your ‘textpattern’ admin side. This is usually <code>http://site.com/textpattern</code> but if you are running <a href=\"https://stefdawson.com/#smd_proghelp_multisite\" rel=\"nofollow\">multi-site</a> or have otherwise altered the directory, you can specify it here.</dd>\n	<dt><span>Prognostics folder</span></dt>\n	<dd>The folder where the prognostics checksums file will reside. The directory must already exist and be writable, or your chosen location will be ignored. It is <strong>highly recommended</strong> that this folder be outside of your website document root, i.e. in a non-web-accessible location.</dd>\n	<dt><span>Unique prefix</span></dt>\n	<dd>Prefix the checksums file with this string. Useful to avoid files clashing if you are using the same (non-docroot) folder to store the checksums from more than one site.</dd>\n</dl>\n\n<h2>How it works</h2>\n\n<p>Ideally, the plugin would be a timed script that runs every N seconds. But that requires a particular piece of software (cron) on your host and there&#8217;s no guarantee it exists. Thus the only thing that can ‘trigger’ the plugin is Textpattern itself. The plugin runs every time you interact with the admin side, but if it checked all the files against all the checksums you have chosen on every click, your admin side would slow down a lot. The compromise is to specify a timeout value and a number of files to check. This is how it works:</p>\n\n<ol>\n	<li>When you click a tab or interact with the admin side, the plugin looks at when you <em>last</em> clicked something and asks “is it greater than N (seconds)?” where N is the value of <code>Check files (at most) every</code> on the Setup panel. It also checks if the current time of day is between the values specified in <code>Check files between</code>.</li>\n	<li>If so, it grabs as many files as you have specified in <code>Check this many files each time</code>, tests them and resets the timer. It keeps a note of how far it has worked through your list of files and continues where it left off next time it runs.</li>\n	<li>If the value of N has not yet been reached, it does nothing, thus saving you precious processor cycles</li>\n</ol>\n\n<p>All that is great if you&#8217;re continually using the admin side and producing clicks for the plugin to run. But once you hand over the site to your client, they may not use the admin side as often as you &#8212; if at all. Thus your files would rarely/never be checked. To counteract this, you can ask the plugin to respond to public side clicks as well so that any time a visitor (or bot) crawls your pages the plugin runs, checks if the timeout has been exceeded and, if so, checks some files and resets the timer (exactly as it does on the admin side).</p>\n\n<p>This will push the processing load onto your public site, but since it&#8217;s only run at most once every N seconds and you specify how many files to read each time, and at what times of day, you can control how much of an impact it has by matching your desire to know ‘instantly’ that something has changed versus the volume of traffic / visitors to the site and how often you want to delay their browsing.</p>\n\n<p>The good thing about being able to specify both the timeout, the time of day and the quantity of files is that you can determine if the plugin runs ‘little and often’ or takes more of your resources when it runs less frequently or during off-peak times. The recommendation is to run fairly frequently &#8212; between 10 and 60 seconds is good &#8212; and only processing a small number of files, say 20 or 30 each time.</p>\n\n<p>As an example, if you monitored every file in a stock Txp installation with a ten-second timeout and checked 30 files each time, then &#8212; assuming you had at least five clicks per minute &#8212; you would cycle through them in under a minute. And your visitors probably won&#8217;t notice the delay. If you have fewer than five clicks per minute then it will take longer to cycle through the files and the chance of catching changes as they happen reduces. But if you run too often (say, every one or two seconds) you are wasting your visitors’ time, your server&#8217;s resources and increasing the chance of the plugin misinforming you of alterations (it may still be partway through the previous run when the next occurs and trigger a warning that something isn&#8217;t right).</p>\n\n<p>The plugin is written to be as quick and efficient as it possibly can to avoid any delays; there may be <a href=\"https://stefdawson.com/#smd_proghelp_better\" rel=\"nofollow\">room for improvement</a> of course! Please be aware that no detection system is foolproof and there is no substitute for vigilance and <a href=\"https://stefdawson.com/blog/choosing-a-brilliant-password\" rel=\"nofollow\">good passwords</a> :-)</p>\n\n<h2>Monitoring files</h2>\n\n<p>Clicking the <em>Files</em> button takes you to a screen with a textarea that allows you to choose the files you wish to monitor. A count of the current number of monitored files is shown at the top, along with the number of files available in your chosen file location(s). The following files will be in the list:</p>\n\n<ol>\n	<li>all files in all subdirectories from all <code>File locations</code> you specified to monitor in the Setup screen</li>\n	<li><em>except</em> any files below the directories you chose to exclude</li>\n	<li><em>except</em> any file names you explicitly chose to ignore</li>\n	<li><em>except</em> the prognostics checksums file</li>\n</ol>\n\n<p>At first, no files will be selected so you will be monitoring nothing. Choose which files you wish to monitor and save the changes. The plugin will keep tabs on those files.</p>\n\n<p>If you have elected to watch for additions to the file system, any files in any of your chosen locations that are not currently in the list at the time you hit <em>Save</em> (regardless of whether you are monitoring them or not) will trigger an alarm. Go ahead and try it: add some files to your server or install a new theme or something and the plugin will tell you.</p>\n\n<h2>Acknowledging alarms</h2>\n\n<p>If one or more files are altered, deleted, or new files have been added (depending on which actions you have elected to monitor) the plugin will detect this when the next admin- or public-side click is received and the timeout cycle is exceeded. It will then take the action you chose. Note of course that you won&#8217;t see the admin-side warning if not logged in!</p>\n\n<p>The message delivered to you contains a link to the Acknowledgement page (which can also be reached by clicking the <em>Alarms</em> button from the Prognostics panel). You are presented with a list of all the filesystem alterations. If you know about the changes &#8212; e.g. you initiated them by uploading some new file version(s) &#8212; then you can acknowlegde this by placing a check mark next to the files you know contain legitimate alterations. When you select <em>Acknowledge</em> from the dropdown below the list, you signify to the plugin that you accept the changes. Thus the new file(s) become the baseline.</p>\n\n<p>If you are monitoring file system additions, the act of acknowledging the alarm indicates that you are happy for the new file(s) to be monitored. They will automatically be added to the list of Files to monitor (you can verify this by visiting the Prognostic panel&#8217;s <em>Files</em> list). If you know about some of the files (perhaps they are new additions) and you don&#8217;t want to monitor them, select the file(s) and choose <em>Ignore</em>. The alarm will be acknowledged but you will not be monitoring the file(s).</p>\n\n<p>If you choose not to acknowledge/ignore some or all of the files, you will continue to be nagged at your designated alarm frequency until you acknowledge the problems or fix the filesystem to its original state. Do bear in mind when configuring the frequency that alerting too often can alienate users and make an alarm situation less effective.</p>\n\n<h2>Forensics</h2>\n\n<p><strong>Before</strong> acknowledging alarms, you may choose to send off forensics information to a designated e-mail address. The following information is sent:</p>\n\n<ul>\n	<li>File information about each changed/added file, such as its size, modification time, and user / group ownership</li>\n	<li>The contents of the files themselves, <span class=\"caps\">BASE</span>-64 encoded in the body of the e-mail</li>\n	<li>The names &amp; paths of any deleted files</li>\n	<li>Any Textpattern log entries that occurred either side of the alarm condition (logging must be enabled in your <em>Admin -&gt; Preferences</em>)</li>\n</ul>\n\n<p>You can select which file information to send off via the check boxes. The ones you choose will remain selected for convenience after the forensics have been sent, so you may immediately <em>Acknowledge</em> the ones you notified.</p>\n\n<h2>Advice</h2>\n\n<p>Clicking the <em>Advice</em> button will run through a series of checks and offer suggestions on how to harden your installation. The most important one to follow is if you are warned about the prognostics checksums file being in docroot. It is <strong>highly recommended to move it somewhere non-web accessible</strong>.</p>\n\n<h2>Callbacks</h2>\n\n<p>The plugin has a callback of its own allowing you to perform custom <span class=\"caps\">SQL</span> injection tests. Hook your own code into it by raising a callback on the event <code>smd_frognostics</code> and step <code>sql_injection</code>. This callback fires on every page access if <code>Protect against SQL injection</code> is enabled.</p>\n\n<p>Your code can return:</p>\n\n<ul>\n	<li>-1 to assert “no injection detected at all and do not run the plugin&#8217;s <span class=\"caps\">SQL</span> checks”</li>\n	<li>0 to assert “no injection detected but run the plugin checks as well”</li>\n	<li>1 to assert “injection detected” (plugin checks are bypassed)</li>\n</ul>\n\n<p>In all cases, the plugin will continue to process forensics evidence and <span class=\"caps\">HTTP</span> redirection if the options are enabled. Bail out with an <code>exit()</code> or <code>txp_die()</code> if you wish the plugin to terminate instead.</p>\n\n<h2>Multi-site setup</h2>\n\n<p>If you are using Textpattern&#8217;s multi-site feature there are some additional things to note:</p>\n\n<ol>\n	<li>You should set smd_prognostics up on <em>each</em> domain you wish to protect to avoid overloading one domain with checking them all.</li>\n	<li>Choose just one of your domains as the ‘master’ which will monitor the Textpattern core files.</li>\n	<li>Consider moving all the domains in the <code>/sites</code> folder outside docroot.</li>\n	<li>Ensure you employ a <code>Unique prefix</code> or your checksums files will clash, causing odd behaviour. The recommended method is to use the <code>Prognostics folder</code> setting to move all checksum files to a central location &#8212; outside docroot &#8212; and use the same directory for all domains. The <code>Unique prefix</code> will then distinguish between them.</li>\n	<li>The <code>Admin-side URL</code> may well be wrong: you should probably change this on each domain.</li>\n</ol>\n\n<h2>Help make the plugin better</h2>\n\n<p>If smd_prognostics does detect some rogue elements in your filesystem, please consider sending the forensics to prognostics [at] stefdawson [dot] com with as much information as you possibly can. You can either do this automatically from within the plugin (see the setup page, though note the sensitivity of some of the data sent) or copy and paste relevant information into an e-mail. The sort of stuff that might be useful is:</p>\n\n<ul>\n	<li>Textpattern (and/or server) logs for the time of the attack.</li>\n	<li>The lines of the file(s) that were affected (or the files in their entirety).</li>\n	<li>The content of any additional lines added or altered in the files.</li>\n	<li>Your Textpattern, <span class=\"caps\">PHP</span>, MySQL and Apache versions.</li>\n	<li>Your hoster company name.</li>\n</ul>\n\n<p>and so on. Any and all information will be treated in the strictest confidence of course. Analysing this data may allow me to improve the plugin and perhaps close any security holes in Textpattern itself, or even help make the plugin more pro-active by detecting the various kinds of attack in progress and minimising damage to your file system. Thanks in advance for you help.</p>\n\n<h2>Author / credits</h2>\n\n<p>Written by <a href=\"https://stefdawson.com/contact\" rel=\"nofollow\">Stef Dawson</a>. Many thanks to Kevin Potts and Steve Dickinson for beta testing and feature enhancements. Also kudos to Hugo Sousa for the phProtector class.</p>', '/**\n * smd_prognostics\n *\n * A Textpattern CMS plugin for pro-active diagnostics\n *  -> Detect changes to the file system\n *  -> Acknowledge alarms (sent via e-mail or to the admin interface)\n *\n * @author Stef Dawson\n * @link   https://stefdawson.com/\n * @todo   Monitor PHP / MySQL version? (https://forum.textpattern.io/viewtopic.php?pid=260163#p260163)\n */\n\nglobal $smd_prognostics_event, $smd_prognostics_checksums, $smd_prognostics_sqlprot;\n\n$smd_prognostics_event = \'smd_prognostics\';\n$smd_prognostics_checksums = rtrim(get_pref(\'smd_prognostics_dir\', txpath, 1), DS) . DS . get_pref(\'smd_prognostics_prefix\', \'\').\'smd_prognostics_checksums.txt\';\n$hdron = get_pref(\'smd_prognostics_req_headers\', \'\');\n$smd_prognostics_sqlprot = new smd_prog_PhProtector(false);\n\nif (txpinterface === \'admin\') {\n    global $txp_user, $event, $step;\n\n    $smd_prognostics_privs = \'1,2\';\n    $smd_prognostics_suppress = gps(\'smd_prognostics_suppress\');\n\n    $privs = safe_field(\"privs\", \"txp_users\", \"name = \'\".doSlash($txp_user).\"\'\");\n    $users = get_pref(\'smd_prognostics_users\', \'\');\n    $allow = ($users) ? in_array($txp_user, do_list($users)) : true;\n\n    if ($allow) {\n        add_privs($smd_prognostics_event, $smd_prognostics_privs);\n        add_privs(\'plugin_prefs.\'.$smd_prognostics_event, $smd_prognostics_privs);\n        register_tab(\'extensions\', $smd_prognostics_event, gTxt(\'smd_prognostics\'));\n        register_callback(\'smd_prognostics_setup\', \'plugin_prefs.\'.$smd_prognostics_event);\n        register_callback(\'smd_prognostics_dispatcher\', $smd_prognostics_event);\n        register_callback(\'smd_prognostics_inject_css\', \'admin_side\', \'head_end\');\n    }\n\n    if ($hdron) {\n        register_callback(\'smd_prognostics_request_headers\', \'admin_side\', \'head_end\');\n    }\n\n    // Admin side callback for checking files\n    if (!$smd_prognostics_suppress && in_array($privs, do_list($smd_prognostics_privs))) {\n        register_callback(\'smd_prognostics\', \'admin_side\', \'main_content\');\n    }\n}\n\n// Public side callbacks\nif (strpos(get_pref(\'smd_prognostics_check_where\', \'\'), \'public\') !== false) {\n    register_callback(\'smd_prognostics\', \'pretext\');\n}\nif ($hdron) {\n    register_callback(\'smd_prognostics_request_headers\', \'pretext\');\n}\nif (strpos(get_pref(\'smd_prognostics_sql_inject\', \'smd_no\'), \'smd_no\') === false) {\n    register_callback(\'smd_prognostics_sql_inject\', \'pretext\');\n}\n\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers.\nfunction smd_prognostics_get_style_rules()\n{\n    $smd_prognostics_styles = array(\n        \'msg\' =>\n         \'.smd_prog_warn { margin:0 auto 10px; width:500px; background:#ffc; border:2px dashed red; padding:10px; color:#444; }\n          .smd_prog_warn a { font-weight:bold; color:#963; }\n          .smd_prog_warn span { font-weight:bold; }\n          .smd_prog_warn div { font-weight:bold; padding-bottom:10px; }\',\n        \'setup\' =>\n         \'.smd_label { text-align:right!important; vertical-align:middle; }\n          .smd_prog_btns form { display:inline; }\n          .smd_prognostics_setup h3 { margin:25px 0 0; }\',\n        \'advice\' =>\n         \'.smd_prog_advice { width:750px; }\n          #list.smd_prog_advice td { padding:5px; }\n          .smd_prog_btns { width:140px; }\',\n    );\n\n    return $smd_prognostics_styles;\n}\n\n// -------------------------------------------------------------\nfunction smd_prognostics_inject_css($evt, $stp)\n{\n    global $smd_prognostics_event, $event, $step;\n\n    if ($event == $smd_prognostics_event) {\n        $smd_prognostics_style = smd_prognostics_get_style_rules();\n\n        $styles = array();\n\n        switch ($step) {\n            case \'smd_prognostics_advice\':\n            $styles[] = \'advice\';\n            $styles[] = \'setup\';\n            break;\n        }\n\n        if ($styles) {\n            echo \'<style type=\"text/css\">\';\n            foreach ($styles as $style) {\n                echo $smd_prognostics_style[$style];\n            }\n            echo \'</style>\';\n        }\n    }\n\n    return;\n}\n\n// ------------------------\nfunction smd_prognostics_dispatcher($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_prognostics_files\'  => false,\n        \'smd_prognostics_setup\'  => false,\n        \'smd_prognostics_advice\' => false,\n        \'smd_prognostics_ack\'    => false,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = \'smd_prognostics_setup\';\n    }\n    $stp();\n}\n\n// Stub to call the verification code from the URL\nfunction smd_prognostics($evt, $stp)\n{\n    return smd_do_prognostics();\n}\n\n// Verify the file checksums\n// $mode = 0: normal / 1: no update (return arrays only) / 2: silent update\nfunction smd_do_prognostics($mode = 0)\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums;\n\n    $smd_prognostics_style = smd_prognostics_get_style_rules();\n\n    $now = time();\n    $nok = $miss = $added = $allfiles = $hashdown = $hashmod = array();\n    $timenow = date(\'H:i:s\', $now);\n    list($beg, $end) = do_list(get_pref(\'smd_prognostics_check_between\', \'|\'), \'|\'); // Default is pipe so we can guarantee two return vals\n    $tdiff = ($mode == 1 || ( ($timenow > $beg) && ($timenow < $end) && ($now - get_pref(\'smd_prognostics_lastcheck\', 0) > get_pref(\'smd_prognostics_check_freq\', 3600)) ) ) ? true : false;\n\n    // Parts shamelessly plagiarised from txp_diag\n    if ($tdiff) {\n        $sumhash = get_pref(\'smd_prognostics_sumhash\', NULL, ($mode==1 ? 1 : 0));\n        $lookat = get_pref(\'smd_prognostics_check_for\', \'\');\n        $adds = (strpos($lookat, \'add\') !== false);\n        $dels = (strpos($lookat, \'delete\') !== false);\n        $mods = (strpos($lookat, \'modify\') !== false);\n\n        if ($cs = @file($smd_prognostics_checksums)) {\n            $hash = md5(smd_prognostics_prep_file($smd_prognostics_checksums));\n            if ($hash != $sumhash) {\n                $hashmod[] = $smd_prognostics_checksums;\n            } else {\n                $ctr = 0;\n                $qty_per = (($qty = get_pref(\'smd_prognostics_check_qty\', \'\')) == \'\') ? count($cs) : $qty;\n                $so_far = get_pref(\'smd_prognostics_qty_so_far\', 0);\n                $until = $so_far + $qty_per;\n                foreach ($cs as $c) {\n                    if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                        list(,$file,$md5) = $m;\n                        if ($dels && !file_exists($file)) {\n                            $miss[] = $file;\n                        } else if ($mods && $md5 != \'NULL\') {\n                            // Check file_exists last as it\'s the slowest operation; allows PHP to short circuit conditionals faster\n                            if ( ( (($ctr >= $so_far) && ($ctr < $until) || $mode == 1) ) && file_exists($file) ) {\n                                $content = smd_prognostics_prep_file($file);\n                                if ((md5($content) != $md5) && ($file != $smd_prognostics_checksums)) {\n                                    $nok[] = $file;\n                                }\n                            }\n                            $ctr++;\n                        }\n                        $allfiles[] = $file;\n                    }\n                }\n\n                // Stash the story so far ready for next time the function is called\n                if ($mode != 1) {\n                    set_pref(\'smd_prognostics_qty_so_far\', (($until < $ctr) ? $until : 0), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n                if ($adds) {\n                    $filelist = smd_prognostics_readfiles();\n                    $added = array_diff($filelist, $allfiles);\n                }\n\n                if ($mode != 1) {\n                    set_pref(\'smd_prognostics_lastcheck\', $now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n        } else {\n            // File doesn\'t exist\n            $hashdown[] = $smd_prognostics_checksums;\n        }\n    }\n\n    // Assemble message\n    if ($nok || $miss || $added || $hashdown || $hashmod) {\n        if ($mode==1) {\n            return array($nok, $miss, $added, $hashdown, $hashmod);\n        } else {\n            $via = get_pref(\'smd_prognostics_notify_via\', \'\');\n            $detect = get_pref(\'smd_prognostics_lastdetect\', \'\');\n            $lasts = explode(\',\', get_pref(\'smd_prognostics_lastact\', 0));\n            $freqs = explode(\',\', get_pref(\'smd_prognostics_alarm_freq\', 86400));\n            if (!isset($lasts[1])) {\n                $lasts[1] = $lasts[0];\n            }\n            if (!isset($freqs[1])) {\n                $freqs[1] = $freqs[0];\n            }\n\n            $lastact_txp = ($mode < 2 && ($now - $lasts[0] > $freqs[0])) ? true : false;\n            $lastact_mail = ($mode < 2 && ($now - $lasts[1] > $freqs[1])) ? true : false;\n            $lastmsg = get_pref(\'smd_prognostics_lastmsg\', \'\');\n\n            $subject = gTxt(\'smd_prognostics_subject\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $msg = join(\'|\', array_merge($hashdown, $hashmod, $nok, $miss, $added));\n\n            if (txpinterface === \'admin\' && strpos($via, \'txp\') !== false) {\n                if ($lastact_txp || ($lastmsg != md5($msg))) {\n                    $txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir());\n                    $out = \'<style type=\"text/css\">\'.$smd_prognostics_style[\'msg\'].\'</style><div class=\"smd_prog_warn\"><div>\'.$subject.\'</div>\'.\n                        (($hashdown) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_hashdown\').\'</p><ul><li>\'.join(\'</li><li>\',$hashdown).\'</li></ul>\' : \'\').\n                        (($hashmod) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_hashmod\').\'</p><ul><li>\'.join(\'</li><li>\',$hashmod).\'</li></ul>\' : \'\').\n                        (($nok) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_nok\').\'</p><ul><li>\'.join(\'</li><li>\',$nok).\'</li></ul>\' : \'\').\n                        (($miss) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_miss\').\'</p><ul><li>\'.join(\'</li><li>\',$miss).\'</li></ul>\' : \'\').\n                        (($added) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_added\').\'</p><ul><li>\'.join(\'</li><li>\',$added).\'</li></ul>\' : \'\').\n                        \'<p><a href=\"\'.$txpdir.\'/index.php?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.\n                        gTxt(\'smd_prognostics_postamble\').\'</a></p></div>\';\n\n                    set_pref(\'smd_prognostics_lastact\', $now.\',\'.$lasts[1], \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                    echo $out;\n                }\n            }\n\n            if (strpos($via, \'email\') !== false) {\n                if ($lastact_mail || ($lastmsg != md5($msg))) {\n                    $to = get_pref(\'smd_prognostics_mailto\', \'\');\n                    if ($to) {\n                        $hdrs = smd_prognostics_header_info(\'smd_prognostics\');\n                        $body =\n                            (($hashdown) ? n.gTxt(\'smd_prognostics_preamble_hashdown\').n.n.join(n,$hashdown) : \'\').\n                            (($hashmod) ? n.gTxt(\'smd_prognostics_preamble_hashmod\').n.n.join(n,$hashmod) : \'\').\n                            (($nok) ? n.gTxt(\'smd_prognostics_preamble_nok\').n.n.join(n,$nok) : \'\').\n                            (($miss) ? n.n.gTxt(\'smd_prognostics_preamble_miss\').n.n.join(n,$miss) : \'\').\n                            (($added) ? n.n.gTxt(\'smd_prognostics_preamble_added\').n.n.join(n,$added) : \'\').\n                            n.n.\'<a href=\"\'.$hdrs[\'txpdir\'].\'/index.php?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.gTxt(\'smd_prognostics_postamble\').\'</a>\';\n                        mail($to, $subject, $body, $hdrs[\'headers\']);\n                    }\n                    set_pref(\'smd_prognostics_lastact\', $lasts[0].\',\'.$now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n\n            set_pref(\'smd_prognostics_lastmsg\', md5($msg), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n\n            if ($lastmsg != md5($msg)) {\n                if ($detect == \'\') {\n                    set_pref(\'smd_prognostics_lastdetect\', $now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                } else {\n                    $detect = explode(\',\',$detect);\n                    set_pref(\'smd_prognostics_lastdetect\', $detect[0].\',\'.$now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n        }\n    }\n}\n\n// ----------  Catch unexpected request headers\nfunction smd_prognostics_request_headers($evt, $stp) {\n    $block = explode(\'|\', get_pref(\'smd_prognostics_req_headers\', \'\'));\n    $hdr = serverSet(\'REQUEST_METHOD\');\n\n    if (in_array($hdr, $block)) {\n        $send = (strpos(get_pref(\'smd_prognostics_rt_forensics\'), \'hdr\') !== false);\n\n        // Send the forensics off if necessary\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to && $send) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body = n.gTxt(\'smd_prognostics_req_not_allowed\', array(\'{req}\' => $hdr)).n;\n            foreach ($_SERVER + $_REQUEST + $_ENV as $key => $var) {\n                $body .= n.$key.\': \'.$var;\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n\n        //TODO: offer alternative die mechanisms like SQL attacks?\n        exit(1);\n    }\n}\n\n// ----------  SQL injection detection\nfunction smd_prognostics_sql_inject() {\n    global $smd_prognostics_sqlprot, $permlink_mode, $DB;\n\n    // Determine comment preview step and ignore if so\n    $is_prevu = false;\n    $com = psa(array(\n        \'parentid\',\n        \'preview\',\n        \'backpage\',\n    ));\n    if ($com[\'preview\']) {\n        $urlparts = explode(\'/\', $com[\'backpage\']);\n        $num = ($permlink_mode == \'messy\') ? 1 : count($urlparts);\n        $artic = safe_field(\'id\',\'textpattern\', \"ID=\".doSlash($com[\'parentid\']).(($num > 1) ? \" AND url_title=\'\".doSlash($urlparts[$num-1]).\"\'\" : \'\'));\n        $is_prevu = ($artic) ? true : false;\n    }\n    if(!$is_prevu && $smd_prognostics_sqlprot->isMalicious()) {\n        $opts = do_list(get_pref(\'smd_prognostics_sql_inject\', \'|\'), \'|\');\n        $blok = (strpos($opts[0], \'smd_block\') !== false);\n        $send = (strpos(get_pref(\'smd_prognostics_rt_forensics\'), \'sql\') !== false);\n        $ver = (defined(\'txp_version\')) ? txp_version : get_pref(\'version\', \'\');\n\n        // Send the forensics off if necessary\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to && $send) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body = n.gTxt(\'smd_prognostics_preamble_sql_inject\').n;\n            $body .= (($ver) ? \'Txp: \' . $ver .n : \'\') . \'PHP: \' . phpversion() .n. \'MySQL: \' . mysqli_get_server_info($DB->link) .n. ((is_callable(\'apache_get_version\')) ? \'Apache: \' . apache_get_version().n : \'\');\n            foreach ($_SERVER + $_REQUEST + $_ENV as $key => $var) {\n                $body .= n.$key.\': \'.$var;\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n\n        if (isset($opts[1]) && !empty($opts[1])) {\n            $parts = do_list($opts[1], \':\');\n            if ($parts[0] == \'txp_form\') {\n                $msg = parse_form($parts[1]);\n            } else {\n                $msg = $opts[1];\n            }\n        } else {\n            $msg = \'\';\n        }\n\n        if ($blok) {\n            echo $msg;\n            exit(1);\n        } else {\n            txp_die($msg, $opts[0]);\n        }\n    }\n}\n\n// -----------------\n// Admin-side panels\n// -----------------\n// ----------  Alarm acknowledgement\nfunction smd_prognostics_ack($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums, $prefs, $DB;\n\n    $method = ps(\'edit_method\');\n    $submit = ($method == \'acknowledge\');\n    $ignore = ($method == \'ignore\');\n    $csi    = ($method == \'csi\');\n\n    $ack = gps(\'selected\');\n    $smd_prog_ack = (is_array($ack)) ? $ack : (($ack) ? array($ack) : array());\n\n    $out = array();\n\n    if ($submit || $ignore) {\n        if ($cs = @file($smd_prognostics_checksums)) {\n            foreach ($cs as $c) {\n                if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                    list(,$file,$md5) = $m;\n                    if (($key = array_search($file, $smd_prog_ack)) !== false) {\n                        if (file_exists($file)) {\n                            $content = smd_prognostics_prep_file($file);\n                            $out[] = $file.\': (\'.( ($ignore) ? \'NULL\' : md5($content) ).\')\';\n                        }\n                        // Remove files that already have checksums so we\'re left with additions\n                        unset($smd_prog_ack[$key]);\n                    } else {\n                        $out[] = trim($c);\n                    }\n                }\n            }\n\n            // Tack on any new files\n            foreach ($smd_prog_ack as $file) {\n                if ( ($file != $smd_prognostics_checksums) && file_exists($file) ) {\n                    $content = smd_prognostics_prep_file($file);\n                    $out[] = $file.\': (\'.( ($ignore) ? \'NULL\' : md5($content) ).\')\';\n                }\n            }\n\n            $fh = fopen($smd_prognostics_checksums, \"w\");\n            fwrite($fh, join(n, $out));\n            fclose($fh);\n            smd_prognostics_self_hash();\n            $msg = gTxt(\'smd_prognostics_acked\');\n        }\n    }\n\n    if ($csi && $smd_prog_ack) {\n         $msg = gTxt(\'smd_prognostics_csi_sent\');\n    } else if ($csi) {\n         $msg = gTxt(\'smd_prognostics_none_selected\');\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_ack\'), $msg);\n\n    list($nok, $miss, $added, $hashdown, $hashmod) = smd_do_prognostics(1);\n    $nok = is_array($nok) ? $nok : array();\n    $miss = is_array($miss) ? $miss : array();\n    $added = is_array($added) ? $added : array();\n    $hashmod = is_array($hashmod) ? $hashmod : array();\n    $errnum = count(array_merge($nok, $miss, $added, $hashmod));\n    $forensics = array();\n    $lform = \'Y-m-d H:i:s\';\n    $med = \'\';\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_ack\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'ack\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-ack-form\" name=\"longform\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.\n        n. \'<div class=\"txp-listtables\">\'.\n        n. startTable(\'\', \'\', \'txp-list\');\n\n    if ($errnum > 0) {\n        foreach($hashmod as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_hashmod\')).tda($naughty));\n        }\n\n        foreach($nok as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_changed\')).tda($naughty));\n\n            if ($sel) {\n                $fi = stat($naughty);\n                $forensics[\'nok\'][] = smd_prognostics_forensic_output($naughty, $fi);\n                $forensics[\'files\'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));\n            }\n        }\n\n        foreach($miss as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_missing\')).tda($naughty));\n\n            if ($sel) {\n                $forensics[\'miss\'][] = $naughty;\n            }\n        }\n\n        foreach($added as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_added\')).tda($naughty));\n\n            if ($sel) {\n                $fi = stat($naughty);\n                $forensics[\'added\'][] = smd_prognostics_forensic_output($naughty, $fi);\n                $forensics[\'files\'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));\n            }\n        }\n\n        if ($smd_prog_ack && $prefs[\'logging\'] != \'none\') {\n            $detect = explode(\',\',get_pref(\'smd_prognostics_lastdetect\', time()));\n\n            if (!isset($detect[1])) {\n                $detect[1] = $detect[0];\n            }\n\n            $rs = safe_rows(\'*\', \'txp_log\', \"time BETWEEN \'\" . date($lform, ($detect[0] - $prefs[\'smd_prognostics_check_freq\'])) . \"\' AND \'\" . date($lform, ($detect[1] + 5)) . \"\'\");\n\n            foreach ($rs as $row) {\n                $forensics[\'txp_log\'][] = join(\',\',$row);\n            }\n        }\n\n        $methods = array(\n            \'acknowledge\' => array(\'label\' => gTxt(\'smd_prognostics_btn_ackit\')),\n            \'ignore\'      => array(\'label\' => gTxt(\'smd_prognostics_btn_ignore\')),\n            \'csi\'         => array(\'label\' => gTxt(\'smd_prognostics_btn_csi\')),\n        );\n\n        $med = multi_edit($methods, $smd_prognostics_event, \'smd_prognostics_ack\');\n\n        if (get_pref(\'smd_prognostics_mailto_csi\', \'\') == \'\') {\n            unset($methods[\'csi\']);\n        }\n    } else {\n        echo tr(td(gTxt(\'smd_prognostics_no_alarms\')));\n        set_pref(\'smd_prognostics_lastdetect\', \'\', \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n    }\n    echo endTable().\n        \'</div>\'.\n        $med.\n        tInput().\n        \'</form>\'.\n        \'</div></div>\'.\n        script_js( <<<EOS\n            $(document).ready(function() {\n                $(\'.smd_prognostics-ack-form\').txpMultiEditForm({\n                    \'row\' : \'tr\',\n                    \'highlighted\' : \'tr\'\n                });\n            });\nEOS\n        );\n\n    if ($csi && $forensics) {\n        // Send the forensics off.\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body =\n                n.\'Txp: \' . txp_version .n. \'PHP: \' . phpversion() .n. \'MySQL: \' . mysqli_get_server_info($DB->link) .n. ((is_callable(\'apache_get_version\')) ? \'Apache: \' . apache_get_version().n : \'\').\n                ((isset($forensics[\'nok\'])) ? n.gTxt(\'smd_prognostics_preamble_nok\').n.join(n,$forensics[\'nok\']) : \'\').\n                ((isset($forensics[\'miss\'])) ? n.n.gTxt(\'smd_prognostics_preamble_miss\').n.join(n,$forensics[\'miss\']) : \'\').\n                ((isset($forensics[\'added\'])) ? n.n.gTxt(\'smd_prognostics_preamble_added\').n.join(n,$forensics[\'added\']) : \'\').\n                ((isset($forensics[\'txp_log\'])) ? n.n.gTxt(\'smd_prognostics_preamble_txplog\').n.join(n,$forensics[\'txp_log\']) : n.n.gTxt(\'smd_prognostics_no_log_entries\'));\n            if (isset($forensics[\'files\'])) {\n                $body .= n.n.gTxt(\'smd_prognostics_preamble_files\');\n                foreach ($forensics[\'files\'] as $fn => $content) {\n                    $body .= n.n.$fn.n.n.$content.n;\n                }\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n    }\n}\n\n// ---------- File management\nfunction smd_prognostics_files($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums;\n\n    extract(doSlash(gpsa(array(\'submit\'))));\n    $smd_prognostics_files = gps(\'smd_prognostics_files\');\n\n    if (!is_array($smd_prognostics_files)) {\n        $smd_prognostics_files = array();\n    }\n\n    $adds = (strpos(get_pref(\'smd_prognostics_check_for\'), \'add\') !== false);\n    $filelist = smd_prognostics_readfiles();\n    $allcount = count($filelist);\n\n    if ($submit) {\n        $outfile = array();\n        foreach ($smd_prognostics_files as $file) {\n            $content = smd_prognostics_prep_file($file);\n            $outfile[] = $file.\': (\'.md5($content).\')\';\n        }\n\n        if (is_writable(dirname($smd_prognostics_checksums))) {\n            $fh = @fopen($smd_prognostics_checksums, \"w\");\n            if ($fh) {\n                fwrite($fh, join(n, $outfile));\n\n                if ($adds) {\n                    $additions = array_diff($filelist, $smd_prognostics_files);\n                    $added = array();\n                    foreach ($additions as $addition) {\n                        $added[] = $addition.\': (NULL)\';\n                    }\n                    fwrite($fh, n.join(n, $added));\n                }\n                fclose($fh);\n                smd_prognostics_self_hash();\n                smd_do_prognostics(2); // Silently acknowledge all the files\n                set_pref(\'smd_prognostics_lastdetect\', \'\', \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                $msg = gTxt(\'smd_prognostics_files_updated\');\n            } else {\n                $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => $smd_prognostics_checksums)), E_WARNING);\n            }\n        } else {\n            $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => dirname($smd_prognostics_checksums))), E_WARNING);\n        }\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_files\'), $msg);\n\n    $smd_prognostics_files = array();\n\n    if ($cs = @file($smd_prognostics_checksums)) {\n        foreach ($cs as $c) {\n            if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                list(,$file,$md5) = $m;\n\n                if ($md5 != \'NULL\') {\n                    $smd_prognostics_files[] = $file;\n                }\n            }\n        }\n    }\n\n    $moncount = count($smd_prognostics_files);\n\n    if ($filelist) {\n        $filez = array();\n\n        foreach($filelist as $key => $val) {\n            $filez[$val] = $filelist[$key];\n        }\n\n        $filesel = smd_prognostics_multisel(\'smd_prognostics_files\', $filez, $smd_prognostics_files);\n    }\n\n    $showfiles = (!empty($filesel));\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_files\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'fil\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-files-form\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_files\">\'.\n        n. startTable(\'\', \'\', \'txp-list\').\n        n. tr(tdcs(gTxt(\'smd_prognostics_currmon\', array(\'{curr}\' => $moncount, \'{outof}\' => $allcount)) .(($showfiles) ? br.br. gTxt(\'smd_prognostics_monfiles_explain\') : \'\'), 1, 400)).\n        n. (($showfiles) ? tr(tda($filesel)) : tr(tda(gTxt(\'smd_prognostics_no_files\')))).\n        n. tr(tda(fInput(\'hidden\', \'smd_prognostics_suppress\', 1).fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'))).\n        n. endTable().\n        n. tInput().\n        n. \'</form>\'.\n        n. \'</div></div>\';\n}\n\n// ---------- Setup / prefs\nfunction smd_prognostics_setup($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums, $prefs;\n\n    $origloc = get_pref(\'smd_prognostics_listloc\', realpath(txpath.DS.\'..\'.DS).DS, 1);\n    $origexc = get_pref(\'smd_prognostics_excludir\', \'images, files, tmp\', 1);\n    $origdir = rtrim(get_pref(\'smd_prognostics_dir\', realpath(txpath), 1), DS);\n    $origpfx = get_pref(\'smd_prognostics_prefix\', \'\', 1);\n\n    $preflist = array(\n        \'smd_prognostics_check_freq\',\n        \'smd_prognostics_check_qty\',\n        \'smd_prognostics_alarm_freq\',\n        \'smd_prognostics_check_where\',\n        \'smd_prognostics_mailto\',\n        \'smd_prognostics_mailto_csi\',\n        \'smd_prognostics_users\',\n        \'smd_prognostics_dir\',\n        \'smd_prognostics_prefix\',\n        \'smd_prognostics_listloc\',\n        \'smd_prognostics_excludir\',\n        \'smd_prognostics_ignores\',\n        \'smd_prognostics_inject_sensitivity\',\n        \'smd_prognostics_xss\',\n        \'smd_prognostics_txpdir\',\n    );\n\n    $warnloc = \'\';\n    $notify = gps(\'smd_prognostics_notify_via\');\n    $chfor = gps(\'smd_prognostics_check_for\');\n    $reqs = gps(\'smd_prognostics_req_headers\');\n    $sqlin = gps(\'smd_prognostics_sql_inject\');\n    $tween = gps(\'smd_prognostics_check_between\');\n    $rtfor = gps(\'smd_prognostics_rt_forensics\');\n\n    // Only one of these valid status codes is allowed to be thrown\n    $throw_codes = array(\n        \'smd_no\' => gTxt(\'no\'),\n        \'smd_block\' => gTxt(\'smd_prognostics_block\'),\n        \'200\' => \'200 OK\',\n        \'301\' => \'301 Moved Permanently\',\n        \'302\' => \'302 Found\',\n        \'307\' => \'307 Temporary Redirect\',\n        \'401\' => \'401 Unauthorized\',\n        \'403\' => \'403 Forbidden\',\n        \'404\' => \'404 Not Found\',\n        \'410\' => \'410 Gone\',\n        \'414\' => \'414 Request-URI Too Long\',\n        \'500\' => \'500 Internal Server Error\',\n        \'501\' => \'501 Not Implemented\',\n    );\n\n    if (!is_array($tween)) {\n        $tween = array();\n    }\n\n    foreach ($tween as $idx => $val) {\n        if (empty($val)) {\n            $tween[$idx] = ($idx==0) ? \'00:00\' : \'23:59\';\n        } else {\n            $timeparts = do_list($val, \':\');\n            foreach ($timeparts as $num) {\n                if (!is_numeric($num)) {\n                    $tween[$idx] = ($idx==0) ? \'00:00\' : \'23:59\';\n                    break;\n                }\n            }\n        }\n    }\n\n    $smd_prognostics_notify_via = join(\'|\', ((is_array($notify)) ? $notify : array($notify)));\n    $smd_prognostics_check_for = join(\'|\', ((is_array($chfor)) ? $chfor : array($chfor)));\n    $smd_prognostics_req_headers = join(\'|\', ((is_array($reqs)) ? $reqs : array($reqs)));\n    $smd_prognostics_sql_inject = join(\'|\', ((is_array($sqlin)) ? $sqlin : array($sqlin)));\n    $smd_prognostics_check_between = join(\'|\', ((is_array($tween)) ? $tween : array($tween)));\n    $smd_prognostics_rt_forensics = join(\'|\', ((is_array($rtfor)) ? $rtfor : array($rtfor)));\n\n    // Grab the saved pref values and tack on the array item(s)\n    extract(doSlash(gpsa(array_merge(array(\'submit\'), $preflist))));\n    $preflist[] = \'smd_prognostics_notify_via\';\n    $preflist[] = \'smd_prognostics_check_for\';\n    $preflist[] = \'smd_prognostics_req_headers\';\n    $preflist[] = \'smd_prognostics_sql_inject\';\n    $preflist[] = \'smd_prognostics_check_between\';\n    $preflist[] = \'smd_prognostics_rt_forensics\';\n    $smd_prognostics_dir = rtrim($smd_prognostics_dir, DS);\n\n    if ($submit) {\n        if (($smd_prognostics_dir != $origdir) || ($smd_prognostics_prefix != $origpfx)) {\n            if (is_dir($smd_prognostics_dir) && is_writable($smd_prognostics_dir)) {\n                // Everything OK so do nothing for now\n            } else {\n                // Ignore new dir and reset it to what it was before\n                $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => $smd_prognostics_dir)), E_WARNING);\n                $smd_prognostics_dir = $origdir;\n            }\n\n            // Room for more config files as and when they are required\n            $filelist[] = $smd_prognostics_checksums;\n\n            foreach ($filelist as $file) {\n                $filename = $smd_prognostics_prefix.ltrim(basename($file), $origpfx);\n                rename($file, rtrim($smd_prognostics_dir, DS).DS.$filename);\n            }\n        }\n\n        // Write all the prefs\n        foreach ($preflist as $prefval) {\n            set_pref(doSlash($prefval), doSlash($$prefval), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n        }\n\n        if ( ($smd_prognostics_listloc != $origloc) || ($smd_prognostics_excludir != $origexc) ) {\n            $msg = array(gTxt(\'smd_prognostics_warn_loc\'), E_WARNING);\n        }\n\n        if (!$msg) {\n            $msg = gTxt(\'preferences_saved\');\n        }\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_setup\'), $msg);\n\n    $smd_prognostics_dir = get_pref(\'smd_prognostics_dir\', txpath, 1);\n    $smd_prognostics_prefix = get_pref(\'smd_prognostics_prefix\', \'\', 1);\n    $smd_prognostics_check_for = get_pref(\'smd_prognostics_check_for\', \'delete|modify\', 1);\n    $smd_prognostics_check_where = get_pref(\'smd_prognostics_check_where\', \'admin\', 1);\n    $smd_prognostics_check_freq = get_pref(\'smd_prognostics_check_freq\', 10, 1);\n    $smd_prognostics_check_between = get_pref(\'smd_prognostics_check_between\', \'00:00|23:59\', 1);\n    $smd_prognostics_check_qty = get_pref(\'smd_prognostics_check_qty\', \'30\', 1);\n    $smd_prognostics_alarm_freq = get_pref(\'smd_prognostics_alarm_freq\', 86400, 1);\n    $smd_prognostics_notify_via = get_pref(\'smd_prognostics_notify_via\', \'\', 1);\n    $smd_prognostics_mailto = get_pref(\'smd_prognostics_mailto\', \'\', 1);\n    $smd_prognostics_mailto_csi = get_pref(\'smd_prognostics_mailto_csi\', \'\', 1);\n    $smd_prognostics_users = get_pref(\'smd_prognostics_users\', \'\', 1);\n    $smd_prognostics_listloc = get_pref(\'smd_prognostics_listloc\', realpath(txpath.DS.\'..\'.DS).DS, 1);\n    $smd_prognostics_excludir = get_pref(\'smd_prognostics_excludir\', \'images, files, tmp\', 1);\n    $smd_prognostics_ignores = get_pref(\'smd_prognostics_ignores\', \'error_log\', 1);\n    $smd_prognostics_req_headers = get_pref(\'smd_prognostics_req_headers\', \'TRACE|PUT|DELETE\', 1);\n    $smd_prognostics_sql_inject = get_pref(\'smd_prognostics_sql_inject\', \'0|\', 1);\n    $smd_prognostics_inject_sensitivity = get_pref(\'smd_prognostics_inject_sensitivity\', 1, 1);\n    $smd_prognostics_xss = get_pref(\'smd_prognostics_xss\', 0, 1);\n    $smd_prognostics_rt_forensics = get_pref(\'smd_prognostics_rt_forensics\', \'1|1\', 1);\n    $smd_prognostics_txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir(), 1);\n\n    $adds = (strpos($smd_prognostics_check_for, \'add\') !== false);\n    $dels = (strpos($smd_prognostics_check_for, \'delete\') !== false);\n    $mods = (strpos($smd_prognostics_check_for, \'modify\') !== false);\n    $rh_trace = (strpos($smd_prognostics_req_headers, \'TRACE\') !== false);\n    $rh_put = (strpos($smd_prognostics_req_headers, \'PUT\') !== false);\n    $rh_del = (strpos($smd_prognostics_req_headers, \'DELETE\') !== false);\n    $rt_hdr = (strpos($smd_prognostics_rt_forensics, \'hdr\') !== false);\n    $rt_sql = (strpos($smd_prognostics_rt_forensics, \'sql\') !== false);\n    $tweens = do_list($smd_prognostics_check_between, \'|\');\n    $throws = do_list($smd_prognostics_sql_inject, \'|\');\n    $helpLink = \"?event=plugin\".a.\"step=plugin_help\".a.\"name=$smd_prognostics_event#smd_setup\";\n\n    //TODO: refactor with inputLabel()\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_setup\'), sp, gTxt(\'smd_prognostics_help_link\', array(\'{link}\' => $helpLink), \'raw\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'set\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-setup-form\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_setup\">\'.\n        n. startTable(\'\', \'\', \'smd_prognostics_setup\').\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_monopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_where\'), \' class=\"smd_label\"\').\n            tda(radioSet(\n                array(\n                    \'admin\' => gTxt(\'no\'),\n                    \'adminpublic\' => gTxt(\'yes\')\n                ), \'smd_prognostics_check_where\', $smd_prognostics_check_where), \' id=\"smd_prognostics_check_where\"\')\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_freq\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_check_freq\', $smd_prognostics_check_freq, \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_seconds\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_between\'), \' class=\"smd_label\"\').\n            tda(\n                fInput(\'text\', \'smd_prognostics_check_between[]\', $tweens[0], \'\',\'\',\'\',15).\n                gTxt(\'smd_prognostics_and\').\n                fInput(\'text\', \'smd_prognostics_check_between[]\', $tweens[1], \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_hms\')\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_qty\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_check_qty\', $smd_prognostics_check_qty, \'\',\'\',\'\',15))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_alarm_freq\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_alarm_freq\', $smd_prognostics_alarm_freq, \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_seconds\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_notify_via\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_notify_via[]\', \'txp\', strpos($smd_prognostics_notify_via, \'txp\') !== false) .\n                \'<label>\'.gTxt(\'smd_prognostics_notify_txp\').\'</label>\'.\n                checkbox(\'smd_prognostics_notify_via[]\', \'email\', strpos($smd_prognostics_notify_via, \'email\') !== false).\n                \'<label>\'.gTxt(\'smd_prognostics_notify_email\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_mailto\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_mailto\', $smd_prognostics_mailto, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_mailto_csi\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_mailto_csi\', $smd_prognostics_mailto_csi, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_realopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_req_headers\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_req_headers[]\', \'TRACE\', $rh_trace).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_trace\').\'</label>\'.\n                checkbox(\'smd_prognostics_req_headers[]\', \'PUT\', $rh_put).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_put\').\'</label>\'.\n                checkbox(\'smd_prognostics_req_headers[]\', \'DELETE\', $rh_del).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_del\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_sql_inject\'), \' class=\"smd_label\"\').\n            tda(\n                selectInput(\'smd_prognostics_sql_inject[]\', $throw_codes, $throws[0], 0).\n                gTxt(\'smd_prognostics_with\').\n                fInput(\'text\', \'smd_prognostics_sql_inject[]\', $throws[1], \'\',\'\',\'\',60).br.\n                gTxt(\'smd_prognostics_sensitivity\').\n                fInput(\'text\', \'smd_prognostics_inject_sensitivity\', $smd_prognostics_inject_sensitivity, \'\',\'\',\'\',5).sp.gTxt(\'smd_prognostics_sensitivity_explain\').br.\n                gTxt(\'smd_prognostics_xss\').\n                yesnoRadio(\'smd_prognostics_xss\', $smd_prognostics_xss).sp.gTxt(\'smd_prognostics_xss_explain\')\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_send_forensics\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_rt_forensics[]\', \'hdr\', $rt_hdr).\n                \'<label>\'.gTxt(\'smd_prognostics_rt_hdr\').\'</label>\'.\n                checkbox(\'smd_prognostics_rt_forensics[]\', \'sql\', $rt_sql).\n                \'<label>\'.gTxt(\'smd_prognostics_rt_sql\').\'</label>\'\n            )\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_fileopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_for\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_check_for[]\', \'add\', $adds) .\n                \'<label>\'.gTxt(\'smd_prognostics_ch_add\').\'</label>\'.\n                checkbox(\'smd_prognostics_check_for[]\', \'delete\', $dels).\n                \'<label>\'.gTxt(\'smd_prognostics_ch_del\').\'</label>\'.\n                checkbox(\'smd_prognostics_check_for[]\', \'modify\', $mods).\n                \'<label>\'.gTxt(\'smd_prognostics_ch_mod\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_listloc\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_listloc\', $smd_prognostics_listloc, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_excludir\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_excludir\', $smd_prognostics_excludir, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_ignores\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_ignores\', $smd_prognostics_ignores, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_plugopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_auth_users\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_users\', $smd_prognostics_users, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\').sp.gTxt(\'smd_prognostics_csense\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_txpdir\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_txpdir\', $smd_prognostics_txpdir, \'\',\'\',\'\',60))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_progloc\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_dir\', $smd_prognostics_dir, \'\',\'\',\'\',60))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_progpfx\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_prefix\', $smd_prognostics_prefix, \'\',\'\',\'\',60))\n        ).\n        n. tr(tda(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'))).\n        n. endTable().\n        n. tInput().\n        n. \'</form>\'.\n        n. \'</div>\'.\n        n. \'</div>\';\n}\n\n// ----------  Security advice\nfunction smd_prognostics_advice($msg = \'\')\n{\n    global $smd_prognostics_event, $prefs, $event, $DB;\n    require_once txpath.\'/lib/IXRClass.php\';\n\n    pagetop(gTxt(\'smd_prognostics_ttl_advice\'), $msg);\n\n    $checks = array();\n\n    // Prognostics dir in docroot?\n    if (strpos(get_pref(\'smd_prognostics_dir\', txpath), $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_prognostics\');\n    }\n\n    // Setup still exists?\n    if (@is_dir(txpath . DS. \'setup\')) {\n        $checks[] = gTxt(\'smd_prognostics_setup_exists\');\n    }\n\n    // RPC still exists?\n    if ( ($prefs[\'enable_xmlrpc_server\'] == 0) && (@is_dir(realpath(txpath.DS.\'..\'.DS. \'rpc\'))) ) {\n        $checks[] = gTxt(\'smd_prognostics_rpc_exists\');\n    }\n\n    // New Txp version/branch available?\n    include_once txpath.\'/include/txp_diag.php\';\n    $now = time();\n    $updateInfo = unserialize(get_pref(\'smd_prognostics_last_update_check\', \'\'));\n\n    if (!$updateInfo || ( $now > ($updateInfo[\'when\'] + (60*60*24)) )) {\n        $updates = checkUpdates();\n        $checks[] = $updateInfo[\'msg\'] = ($updates) ? gTxt($updates[\'msg\'], array(\'{version}\' => $updates[\'version\'])) : \'\';\n        $updateInfo[\'when\'] = $now;\n        set_pref(\'smd_prognostics_last_update_check\', serialize($updateInfo), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n    }\n\n    // Files dir in docroot?\n    if (strpos($prefs[\'file_base_path\'], $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_files\');\n    }\n\n    // Tmp dir in docroot?\n    if (strpos($prefs[\'tempdir\'], $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_tmp\');\n    }\n\n/*\n    // TODO: check what SHOW GRANTS returns and determine if it could be a security risk\n    $res = safe_query(\'SHOW GRANTS\');\n    if (numRows($res) > 0) {\n        $checks[] = gTxt(\'smd_prognostics_show_grants\');\n    }\n*/\n\n    // Does this MySQL user have FILES privs?\n    $randfile = $prefs[\'tempdir\'].DS.rand().time().\'.sql\';\n    $res = @safe_query(\'SELECT id INTO OUTFILE \"\'.$randfile.\'\" FIELDS TERMINATED BY \"\\t\" LINES TERMINATED BY \"\\n\" FROM txp_image WHERE 1 LIMIT 1\');\n    if (mysqli_error($DB->link) == \'\') {\n        $checks[] = gTxt(\'smd_prognostics_sql_file_privs\');\n        if (is_file($randfile)) {\n            @unlink($randfile);\n        }\n    }\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_advice\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'adv\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. startTable(\'\', \'\', \'smd_prognostics_advice\');\n\n    if ($checks) {\n        foreach($checks as $check) {\n            echo n. tr(tda($check));\n        }\n    } else {\n        echo n. tr(tda(gTxt(\'smd_prognostics_tight\')));\n    }\n\n    echo endTable(),\n        n. \'</div></div>\';\n}\n\n// ---------- Common buttons\nfunction smd_prognostics_button_bar($active = \'\')\n{\n    global $smd_prognostics_event;\n\n    $active = ($active) ? $active : \'setup\';\n\n    $btns = array(\n        \'ack\' => eLink($smd_prognostics_event, \'smd_prognostics_ack\', \'smd_prognostics_suppress\', \'1\', gTxt(\'smd_prognostics_pnl_ack\')),\n        \'adv\' => sLink($smd_prognostics_event, \'smd_prognostics_advice\', gTxt(\'smd_prognostics_pnl_advice\')),\n        \'fil\' => sLink($smd_prognostics_event, \'smd_prognostics_files\', gTxt(\'smd_prognostics_pnl_files\')),\n        \'set\' => sLink($smd_prognostics_event, \'smd_prognostics_setup\', gTxt(\'smd_prognostics_pnl_setup\')),\n    );\n\n    return graf(\n            (($active == \'set\') ? strong($btns[\'set\']) : $btns[\'set\'])\n            .n.(($active == \'fil\') ? strong($btns[\'fil\']) : $btns[\'fil\'])\n            .n.(($active == \'ack\') ? strong($btns[\'ack\']) : $btns[\'ack\'])\n            .n.(($active == \'adv\') ? strong($btns[\'adv\']) : $btns[\'adv\'])\n        , \' class=\"txp-buttons\"\');\n}\n\n// ---------- Hash the hashfile\nfunction smd_prognostics_self_hash() {\n    global $smd_prognostics_checksums;\n\n    if (file_exists($smd_prognostics_checksums)) {\n        $content = smd_prognostics_prep_file($smd_prognostics_checksums);\n        $hash = md5($content);\n    } else {\n        $hash = NULL;\n    }\n    set_pref(\'smd_prognostics_sumhash\', $hash, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n}\n\n// ---------- Compile list of files to monitor\nfunction smd_prognostics_readfiles() {\n    $filelist = array();\n\n    $smd_prognostics_listloc = get_pref(\'smd_prognostics_listloc\', \'\');\n    $excludes = smd_prognostics_ignore_list();\n\n    if ($smd_prognostics_listloc) {\n        foreach (do_list($smd_prognostics_listloc) as $loc) {\n            // NOTE: not using GLOB_BRACE to grab regular and dot files, since it\'s not always available cross-OS\n            $filelist = array_merge($filelist, smd_prognostics_rglob(\"*\", GLOB_MARK, $loc, $excludes), smd_prognostics_rglob(\".*\", GLOB_MARK, $loc, $excludes));\n        }\n    }\n    return $filelist;\n}\n\n// ---------- Prepare file contents for md5. Assumes file exists\n// Checks to see if large files are binary before wasting time stripping newlines and stuff\nfunction smd_prognostics_prep_file($file) {\n    $content = file_get_contents($file);\n    $stat = stat($file);\n    $dostrip = true;\n    // Perform the strip regardless on files < 1Kb as it has little impact\n    if ($stat[\'size\'] > 1024000) {\n        $part = substr($content, 0, 1536000); // First 1.5KB\n        if (strpos($part, 0x00) || (strpos($part, 0x0D) !== false) || (strpos($part, 0x0A) !== false)) {\n            // Likely a binary file: leave it be\n            $dostrip = false;\n        }\n    }\n    if ($dostrip) {\n        $content = str_replace(array(\"\\r\\n\", \"\\$HeadURL: http:\"), array(\"\\n\", \"\\$HeadURL: https:\"), $content);\n    }\n    return $content;\n}\n\n// ---------- Pre-compute excluded files and dirs, expanding wildcards in the process\nfunction smd_prognostics_ignore_list() {\n    global $smd_prognostics_checksums;\n\n    $excl = do_list(get_pref(\'smd_prognostics_excludir\', \'\'));\n    $ign = get_pref(\'smd_prognostics_ignores\', \'\');\n\n    // Add any files to permanently exclude here (NOT dirs: they\'re ignored automatically)\n    $ignarr = ($ign) ? do_list($ign) : array();\n\n    // Expand any wildcard filenames\n    $regarr = array();\n    foreach ($ignarr as $idx => $item) {\n        if ( (strpos($item, \'*\') !== false) || (strpos($item, \'?\') !== false) ) {\n            $regarr[] = str_replace( array(\"\\*\", \"\\?\"), array(\".*\", \".\"), preg_quote($item) );\n            unset($ignarr[$idx]); // Remove the wildcard filename from the ignore list\n        }\n    }\n    $regexclude = ($regarr) ? \'/^(\' . join(\'|\', $regarr) . \')$/\' : \'\';\n    $permexclude = array_merge(array(basename($smd_prognostics_checksums)), $ignarr);\n\n    return array($excl, $regexclude, $permexclude);\n}\n\n// Frankensteined from http://snipplr.com/view/16233/recursive-glob/\nfunction smd_prognostics_rglob($pattern, $flags=0, $path=\'\', $excludes = array(), $excl=array(), $ign=\'\') {\n    if (!$path && ($dir = dirname($pattern)) != \'.\') {\n        if ($dir == \'\\\\\' || $dir == DS) $dir = \'\';\n        return (array)smd_prognostics_rglob(basename($pattern), $flags, $dir . DS, $excludes);\n    }\n    $paths = glob($path . \'*\', GLOB_ONLYDIR | GLOB_NOSORT);\n    $files = glob($path . $pattern, $flags);\n\n    if (is_array($paths)) {\n        foreach ($paths as $p) {\n            $parts = explode(DS, $p);\n            $pinfo = array_pop($parts);\n            if (!in_array($pinfo, $excludes[0])) {\n                $files = array_merge((array)$files, (array)smd_prognostics_rglob($pattern, $flags, $p . DS, $excludes));\n                foreach($files as $idx => $theFile) {\n                    $parts = explode(DS, $theFile);\n                    $fex = array_pop($parts);\n                    $rex = ($excludes[1]) ? preg_match($excludes[1], $fex) : false;\n                    if($fex==\'\' || in_array($fex, $excludes[2]) || $rex) {\n                        unset($files[$idx]);\n                    }\n                }\n            }\n        }\n    }\n    return $files;\n}\n\n// Format forensic data for a file\nfunction smd_prognostics_forensic_output($file, $fi) {\n    global $prefs;\n\n    $dform = $prefs[\'dateformat\'];\n    $sep = n;\n    $out = \'\';\n\n    $out .= $sep . gTxt(\'smd_prognostics_stat_name\') . $file;\n    $out .= $sep . gTxt(\'smd_prognostics_stat_size\') . $fi[\'size\'];\n    $out .= $sep . gTxt(\'smd_prognostics_stat_mod\') . strftime($dform, $fi[\'mtime\']);\n    $out .= $sep . gTxt(\'smd_prognostics_stat_uid\') . $fi[\'uid\'];\n    $out .= $sep . gTxt(\'smd_prognostics_stat_gid\') . $fi[\'gid\'];\n\n    return $out;\n}\n\n// ---------- Does what it says on the tin\n// Assumes \'textpattern\' is the admin-side directory if server var not set. Must fix in core one day with true constant\nfunction smd_prognostics_guess_admin_dir() {\n    $admindir = trim(dirname($_SERVER[\'PHP_SELF\']), \'/\\\\\');\n    $admindir = (empty($admindir)) ? \'textpattern\' : $admindir;\n    return $admindir;\n}\n\n// ---------- Get common server info for mail headers, etc\nfunction smd_prognostics_header_info($fromname) {\n    $domainparts = do_list(doStrip(serverSet(\'SERVER_NAME\')), \'.\');\n    $numparts = count($domainparts);\n    $domain = $domainparts[$numparts-2] . \'.\' . $domainparts[$numparts-1];\n    $reply_to = \'noreply@\'.$domain;\n    $txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir());\n    $sep = IS_WIN ? \"\\r\\n\" : \"\\n\";\n    $headers = \"From: $fromname <$reply_to>\".\n        $sep.\'Reply-To: \'.$reply_to.\n        $sep.\'X-Mailer: Textpattern\'.\n        $sep.\'Content-Transfer-Encoding: 8bit\'.\n        $sep.\'Content-Type: text/plain; charset=\"UTF-8\"\'.\n        $sep;\n\n    $out = array(\n        \'domain\' => $domain,\n        \'reply_to\' => $reply_to,\n        \'txpdir\' => $txpdir,\n        \'sep\' => $sep,\n        \'headers\' => $headers,\n    );\n\n    return $out;\n}\n\n// Multi-file dropdown selection\nfunction smd_prognostics_multisel($selname=\'\', $tree=array(), $sel=array()) {\n    $out[] = \'<select id=\"\'.$selname.\'\" name=\"\'.$selname.\'[]\" class=\"list\" style=\"height:400px;\" multiple=\"multiple\">\';\n    foreach ($tree as $leaf) {\n        $selected=\'\';\n        if (in_array($leaf, $sel)) {\n            $selected = \' selected=\"selected\"\';\n        }\n\n        $out[] = t.\'<option\'.$selected.\'>\'.htmlspecialchars($leaf).\'</option>\'.n;\n    }\n    $out[] = \'</select>\';\n    return join(\'\',$out);\n}\n\n\n\n//****************************************************************\n// Web page      : http://code.google.com/p/phprotector\n// Autor                : Hugo Sousa        adamastor666@gmail.com\n// Date              : 2010-03-25\n// Version          : 0.3.1.1\n//\n//***************************************************************\nclass smd_prog_PhProtector {\n    var $SHOW_ERRORS;\n    var $do_xss;\n\n    public function __construct($show_errors) {\n        $this->SHOW_ERRORS=$show_errors;\n        if ($this->SHOW_ERRORS) {\n            error_reporting(E_ERROR | E_WARNING | E_PARSE);  //Show errors\n            ini_set(\'display_errors\', \"1\"); //display errors\n        } else {\n            ini_set(\'display_errors\', \"0\"); //display errors\n            ini_set(\'log_errors\', \"1\"); //log_errors\n        }\n        $this->do_xss = get_pref(\'smd_prognostics_xss\', 0);\n    }\n\n    /*\n    * Main function to be called in a index page that redirects to other pages\n    *\n    */\n    public function isMalicious() {\n        $sqli = 0;\n\n        $sqli = callback_event(\'smd_frognostics\', \'sql_injection\', false);\n\n        if (!$sqli) {\n            $num_bad_words1 = $this->CheckGet();\n            $num_bad_words2 = $this->CheckPost();\n\n            $thresh = explode(\',\', get_pref(\'smd_prognostics_inject_sensitivity\', 1));\n            if (!isset($thresh[1])) {\n                $thresh[1] = $thresh[0];\n            }\n            $thresh[0] = is_numeric($thresh[0]) ? $thresh[0] : 1;\n            $thresh[1] = is_numeric($thresh[1]) ? $thresh[1] : 1;\n\n            if ($num_bad_words1 >= $thresh[0]) {\n                $sqli = true;\n            }\n\n            if ($num_bad_words2 >= $thresh[1]) {\n                $sqli = true;\n            }\n        }\n\n        return (($sqli <= 0) ? false : $sqli);\n    }\n\n    //check for sql injection and XSS in Post variables\n    private function CheckPost() {\n        $num_bad_words = 0;\n\n        foreach($_POST as $campo => $input) {\n            // XSS PROTECTION\n            if ($this->do_xss) {\n                if (is_array($_POST[$campo])) {\n                    $_POST[$campo] = array_map(\'htmlentities\', $_POST[$campo], array_fill(0, count($_POST[$campo]), ENT_NOQUOTES) );\n                } else {\n                    $_POST[$campo]= htmlentities($_POST[$campo], ENT_NOQUOTES);\n                }\n            }\n            $num_bad_words = $num_bad_words + $this->wordExists($input); //SQL INJECTION\n        }\n\n        return $num_bad_words;\n    }\n\n    //check for sql injection and XSS in GET variables\n    private function CheckGet() {\n        $num_bad_words = 0;\n\n        foreach($_GET as $campo => $input) {\n            // XSS PROTECTION\n            if ($this->do_xss) {\n                if (is_array($_GET[$campo])) {\n                    $_GET[$campo] = array_map(\'htmlentities\', $_GET[$campo], array_fill(0, count($_GET[$campo]), ENT_NOQUOTES) );\n                } else {\n                    $_GET[$campo]= htmlentities($_GET[$campo], ENT_NOQUOTES);\n                }\n            }\n            $strinput = (is_array($input)) ? join(\'\', $input) : $input;\n            if($this->isIdInjection($campo, $strinput)){ //SQL ID INJECTION\n                $num_bad_words =    $num_bad_words + 0.5;\n            }\n\n            $num_bad_words = $num_bad_words + $this->wordExists($input); //SQL INJECTION\n        }\n\n        return $num_bad_words;\n    }\n\n    /**\n    *   return true if injection sql word is found.\n    *   The input is tested if is equal to a sql injection pattern\n    *   \\b[^a-z]*?drop[^a-z]*?\\b\n    *    http://www.pagecolumn.com/tool/regtest.htm\n    **//*        \"/*\",\"+\"               */\n    private function wordExists($input) {\n        $num_bad_words = 0;\n        $input = is_array($input) ? $input : array($input);\n\n        /*\n         WORD AFTER\n        */\n        $baddelim1 = \"[^a-z]*\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]+\";\n        $badwords= array(\"union\", \"select\", \"show\", \"insert\", \"update\", \"delete\", \"drop\", \"truncate\", \"create\", \"load_file\", \"exec\", \"#\", \"--\");\n        //\"/*\"\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*WA*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n\n        /*\n        BEFORE WORD\n        */\n        $baddelim1 = \"[^a-z]+\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]*\";\n        $badwords= array(\"@@version\", \"@@datadir\", \"user\", \"version\");\n\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*BW*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n\n        /*\n        BEFORE WORD AFTER\n        */\n        $baddelim1 = \"[^a-z]+\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]+\";\n        $badwords= array(\"benchmark\", \"--\", \"varchar\", \"convert\", \"char\", \"limit\", \"information_schema\",\"table_name\", \"from\", \"where\", \"order\");\n\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*B-W-A*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n//dmp($num_bad_words);\n        return $num_bad_words;\n    }\n\n    /**\n    *   return true if an ID is not really an ID\n    *\n    **/\n    private function isIDInjection($campo, $input) {\n        $reg=\"/^id/\";\n\n        if(preg_match($reg, $campo)) {\n            if(!$this->stringIsNumberNotZero($input)) {\n                return true;     // if is ID and NOT INTEGER or NULL -> SQL INJECTION!!\n            }\n        }\n\n        return false;\n    }\n\n    /**\n    *   return true if the string is a number (different from 0, the id could not be zero!)\n    *  TODO: check if *all* chars are zero and fail, e.g. id=0000\n    **/\n    private function stringIsNumberNotZero( $string ) {\n        if (empty($string)) {\n            return false;\n        }\n        return ctype_digit($string);\n    }\n} //end class', '/**\n * smd_prognostics\n *\n * A Textpattern CMS plugin for pro-active diagnostics\n *  -> Detect changes to the file system\n *  -> Acknowledge alarms (sent via e-mail or to the admin interface)\n *\n * @author Stef Dawson\n * @link   https://stefdawson.com/\n * @todo   Monitor PHP / MySQL version? (https://forum.textpattern.io/viewtopic.php?pid=260163#p260163)\n */\n\nglobal $smd_prognostics_event, $smd_prognostics_checksums, $smd_prognostics_sqlprot;\n\n$smd_prognostics_event = \'smd_prognostics\';\n$smd_prognostics_checksums = rtrim(get_pref(\'smd_prognostics_dir\', txpath, 1), DS) . DS . get_pref(\'smd_prognostics_prefix\', \'\').\'smd_prognostics_checksums.txt\';\n$hdron = get_pref(\'smd_prognostics_req_headers\', \'\');\n$smd_prognostics_sqlprot = new smd_prog_PhProtector(false);\n\nif (txpinterface === \'admin\') {\n    global $txp_user, $event, $step;\n\n    $smd_prognostics_privs = \'1,2\';\n    $smd_prognostics_suppress = gps(\'smd_prognostics_suppress\');\n\n    $privs = safe_field(\"privs\", \"txp_users\", \"name = \'\".doSlash($txp_user).\"\'\");\n    $users = get_pref(\'smd_prognostics_users\', \'\');\n    $allow = ($users) ? in_array($txp_user, do_list($users)) : true;\n\n    if ($allow) {\n        add_privs($smd_prognostics_event, $smd_prognostics_privs);\n        add_privs(\'plugin_prefs.\'.$smd_prognostics_event, $smd_prognostics_privs);\n        register_tab(\'extensions\', $smd_prognostics_event, gTxt(\'smd_prognostics\'));\n        register_callback(\'smd_prognostics_setup\', \'plugin_prefs.\'.$smd_prognostics_event);\n        register_callback(\'smd_prognostics_dispatcher\', $smd_prognostics_event);\n        register_callback(\'smd_prognostics_inject_css\', \'admin_side\', \'head_end\');\n    }\n\n    if ($hdron) {\n        register_callback(\'smd_prognostics_request_headers\', \'admin_side\', \'head_end\');\n    }\n\n    // Admin side callback for checking files\n    if (!$smd_prognostics_suppress && in_array($privs, do_list($smd_prognostics_privs))) {\n        register_callback(\'smd_prognostics\', \'admin_side\', \'main_content\');\n    }\n}\n\n// Public side callbacks\nif (strpos(get_pref(\'smd_prognostics_check_where\', \'\'), \'public\') !== false) {\n    register_callback(\'smd_prognostics\', \'pretext\');\n}\nif ($hdron) {\n    register_callback(\'smd_prognostics_request_headers\', \'pretext\');\n}\nif (strpos(get_pref(\'smd_prognostics_sql_inject\', \'smd_no\'), \'smd_no\') === false) {\n    register_callback(\'smd_prognostics_sql_inject\', \'pretext\');\n}\n\n// -------------------------------------------------------------\n// CSS definitions: hopefully kind to themers.\nfunction smd_prognostics_get_style_rules()\n{\n    $smd_prognostics_styles = array(\n        \'msg\' =>\n         \'.smd_prog_warn { margin:0 auto 10px; width:500px; background:#ffc; border:2px dashed red; padding:10px; color:#444; }\n          .smd_prog_warn a { font-weight:bold; color:#963; }\n          .smd_prog_warn span { font-weight:bold; }\n          .smd_prog_warn div { font-weight:bold; padding-bottom:10px; }\',\n        \'setup\' =>\n         \'.smd_label { text-align:right!important; vertical-align:middle; }\n          .smd_prog_btns form { display:inline; }\n          .smd_prognostics_setup h3 { margin:25px 0 0; }\',\n        \'advice\' =>\n         \'.smd_prog_advice { width:750px; }\n          #list.smd_prog_advice td { padding:5px; }\n          .smd_prog_btns { width:140px; }\',\n    );\n\n    return $smd_prognostics_styles;\n}\n\n// -------------------------------------------------------------\nfunction smd_prognostics_inject_css($evt, $stp)\n{\n    global $smd_prognostics_event, $event, $step;\n\n    if ($event == $smd_prognostics_event) {\n        $smd_prognostics_style = smd_prognostics_get_style_rules();\n\n        $styles = array();\n\n        switch ($step) {\n            case \'smd_prognostics_advice\':\n            $styles[] = \'advice\';\n            $styles[] = \'setup\';\n            break;\n        }\n\n        if ($styles) {\n            echo \'<style type=\"text/css\">\';\n            foreach ($styles as $style) {\n                echo $smd_prognostics_style[$style];\n            }\n            echo \'</style>\';\n        }\n    }\n\n    return;\n}\n\n// ------------------------\nfunction smd_prognostics_dispatcher($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_prognostics_files\'  => false,\n        \'smd_prognostics_setup\'  => false,\n        \'smd_prognostics_advice\' => false,\n        \'smd_prognostics_ack\'    => false,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $stp = \'smd_prognostics_setup\';\n    }\n    $stp();\n}\n\n// Stub to call the verification code from the URL\nfunction smd_prognostics($evt, $stp)\n{\n    return smd_do_prognostics();\n}\n\n// Verify the file checksums\n// $mode = 0: normal / 1: no update (return arrays only) / 2: silent update\nfunction smd_do_prognostics($mode = 0)\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums;\n\n    $smd_prognostics_style = smd_prognostics_get_style_rules();\n\n    $now = time();\n    $nok = $miss = $added = $allfiles = $hashdown = $hashmod = array();\n    $timenow = date(\'H:i:s\', $now);\n    list($beg, $end) = do_list(get_pref(\'smd_prognostics_check_between\', \'|\'), \'|\'); // Default is pipe so we can guarantee two return vals\n    $tdiff = ($mode == 1 || ( ($timenow > $beg) && ($timenow < $end) && ($now - get_pref(\'smd_prognostics_lastcheck\', 0) > get_pref(\'smd_prognostics_check_freq\', 3600)) ) ) ? true : false;\n\n    // Parts shamelessly plagiarised from txp_diag\n    if ($tdiff) {\n        $sumhash = get_pref(\'smd_prognostics_sumhash\', NULL, ($mode==1 ? 1 : 0));\n        $lookat = get_pref(\'smd_prognostics_check_for\', \'\');\n        $adds = (strpos($lookat, \'add\') !== false);\n        $dels = (strpos($lookat, \'delete\') !== false);\n        $mods = (strpos($lookat, \'modify\') !== false);\n\n        if ($cs = @file($smd_prognostics_checksums)) {\n            $hash = md5(smd_prognostics_prep_file($smd_prognostics_checksums));\n            if ($hash != $sumhash) {\n                $hashmod[] = $smd_prognostics_checksums;\n            } else {\n                $ctr = 0;\n                $qty_per = (($qty = get_pref(\'smd_prognostics_check_qty\', \'\')) == \'\') ? count($cs) : $qty;\n                $so_far = get_pref(\'smd_prognostics_qty_so_far\', 0);\n                $until = $so_far + $qty_per;\n                foreach ($cs as $c) {\n                    if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                        list(,$file,$md5) = $m;\n                        if ($dels && !file_exists($file)) {\n                            $miss[] = $file;\n                        } else if ($mods && $md5 != \'NULL\') {\n                            // Check file_exists last as it\'s the slowest operation; allows PHP to short circuit conditionals faster\n                            if ( ( (($ctr >= $so_far) && ($ctr < $until) || $mode == 1) ) && file_exists($file) ) {\n                                $content = smd_prognostics_prep_file($file);\n                                if ((md5($content) != $md5) && ($file != $smd_prognostics_checksums)) {\n                                    $nok[] = $file;\n                                }\n                            }\n                            $ctr++;\n                        }\n                        $allfiles[] = $file;\n                    }\n                }\n\n                // Stash the story so far ready for next time the function is called\n                if ($mode != 1) {\n                    set_pref(\'smd_prognostics_qty_so_far\', (($until < $ctr) ? $until : 0), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n                if ($adds) {\n                    $filelist = smd_prognostics_readfiles();\n                    $added = array_diff($filelist, $allfiles);\n                }\n\n                if ($mode != 1) {\n                    set_pref(\'smd_prognostics_lastcheck\', $now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n        } else {\n            // File doesn\'t exist\n            $hashdown[] = $smd_prognostics_checksums;\n        }\n    }\n\n    // Assemble message\n    if ($nok || $miss || $added || $hashdown || $hashmod) {\n        if ($mode==1) {\n            return array($nok, $miss, $added, $hashdown, $hashmod);\n        } else {\n            $via = get_pref(\'smd_prognostics_notify_via\', \'\');\n            $detect = get_pref(\'smd_prognostics_lastdetect\', \'\');\n            $lasts = explode(\',\', get_pref(\'smd_prognostics_lastact\', 0));\n            $freqs = explode(\',\', get_pref(\'smd_prognostics_alarm_freq\', 86400));\n            if (!isset($lasts[1])) {\n                $lasts[1] = $lasts[0];\n            }\n            if (!isset($freqs[1])) {\n                $freqs[1] = $freqs[0];\n            }\n\n            $lastact_txp = ($mode < 2 && ($now - $lasts[0] > $freqs[0])) ? true : false;\n            $lastact_mail = ($mode < 2 && ($now - $lasts[1] > $freqs[1])) ? true : false;\n            $lastmsg = get_pref(\'smd_prognostics_lastmsg\', \'\');\n\n            $subject = gTxt(\'smd_prognostics_subject\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $msg = join(\'|\', array_merge($hashdown, $hashmod, $nok, $miss, $added));\n\n            if (txpinterface === \'admin\' && strpos($via, \'txp\') !== false) {\n                if ($lastact_txp || ($lastmsg != md5($msg))) {\n                    $txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir());\n                    $out = \'<style type=\"text/css\">\'.$smd_prognostics_style[\'msg\'].\'</style><div class=\"smd_prog_warn\"><div>\'.$subject.\'</div>\'.\n                        (($hashdown) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_hashdown\').\'</p><ul><li>\'.join(\'</li><li>\',$hashdown).\'</li></ul>\' : \'\').\n                        (($hashmod) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_hashmod\').\'</p><ul><li>\'.join(\'</li><li>\',$hashmod).\'</li></ul>\' : \'\').\n                        (($nok) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_nok\').\'</p><ul><li>\'.join(\'</li><li>\',$nok).\'</li></ul>\' : \'\').\n                        (($miss) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_miss\').\'</p><ul><li>\'.join(\'</li><li>\',$miss).\'</li></ul>\' : \'\').\n                        (($added) ? \'<p>\'.gTxt(\'smd_prognostics_preamble_added\').\'</p><ul><li>\'.join(\'</li><li>\',$added).\'</li></ul>\' : \'\').\n                        \'<p><a href=\"\'.$txpdir.\'/index.php?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.\n                        gTxt(\'smd_prognostics_postamble\').\'</a></p></div>\';\n\n                    set_pref(\'smd_prognostics_lastact\', $now.\',\'.$lasts[1], \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                    echo $out;\n                }\n            }\n\n            if (strpos($via, \'email\') !== false) {\n                if ($lastact_mail || ($lastmsg != md5($msg))) {\n                    $to = get_pref(\'smd_prognostics_mailto\', \'\');\n                    if ($to) {\n                        $hdrs = smd_prognostics_header_info(\'smd_prognostics\');\n                        $body =\n                            (($hashdown) ? n.gTxt(\'smd_prognostics_preamble_hashdown\').n.n.join(n,$hashdown) : \'\').\n                            (($hashmod) ? n.gTxt(\'smd_prognostics_preamble_hashmod\').n.n.join(n,$hashmod) : \'\').\n                            (($nok) ? n.gTxt(\'smd_prognostics_preamble_nok\').n.n.join(n,$nok) : \'\').\n                            (($miss) ? n.n.gTxt(\'smd_prognostics_preamble_miss\').n.n.join(n,$miss) : \'\').\n                            (($added) ? n.n.gTxt(\'smd_prognostics_preamble_added\').n.n.join(n,$added) : \'\').\n                            n.n.\'<a href=\"\'.$hdrs[\'txpdir\'].\'/index.php?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.gTxt(\'smd_prognostics_postamble\').\'</a>\';\n                        mail($to, $subject, $body, $hdrs[\'headers\']);\n                    }\n                    set_pref(\'smd_prognostics_lastact\', $lasts[0].\',\'.$now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n\n            set_pref(\'smd_prognostics_lastmsg\', md5($msg), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n\n            if ($lastmsg != md5($msg)) {\n                if ($detect == \'\') {\n                    set_pref(\'smd_prognostics_lastdetect\', $now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                } else {\n                    $detect = explode(\',\',$detect);\n                    set_pref(\'smd_prognostics_lastdetect\', $detect[0].\',\'.$now, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                }\n            }\n        }\n    }\n}\n\n// ----------  Catch unexpected request headers\nfunction smd_prognostics_request_headers($evt, $stp) {\n    $block = explode(\'|\', get_pref(\'smd_prognostics_req_headers\', \'\'));\n    $hdr = serverSet(\'REQUEST_METHOD\');\n\n    if (in_array($hdr, $block)) {\n        $send = (strpos(get_pref(\'smd_prognostics_rt_forensics\'), \'hdr\') !== false);\n\n        // Send the forensics off if necessary\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to && $send) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body = n.gTxt(\'smd_prognostics_req_not_allowed\', array(\'{req}\' => $hdr)).n;\n            foreach ($_SERVER + $_REQUEST + $_ENV as $key => $var) {\n                $body .= n.$key.\': \'.$var;\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n\n        //TODO: offer alternative die mechanisms like SQL attacks?\n        exit(1);\n    }\n}\n\n// ----------  SQL injection detection\nfunction smd_prognostics_sql_inject() {\n    global $smd_prognostics_sqlprot, $permlink_mode, $DB;\n\n    // Determine comment preview step and ignore if so\n    $is_prevu = false;\n    $com = psa(array(\n        \'parentid\',\n        \'preview\',\n        \'backpage\',\n    ));\n    if ($com[\'preview\']) {\n        $urlparts = explode(\'/\', $com[\'backpage\']);\n        $num = ($permlink_mode == \'messy\') ? 1 : count($urlparts);\n        $artic = safe_field(\'id\',\'textpattern\', \"ID=\".doSlash($com[\'parentid\']).(($num > 1) ? \" AND url_title=\'\".doSlash($urlparts[$num-1]).\"\'\" : \'\'));\n        $is_prevu = ($artic) ? true : false;\n    }\n    if(!$is_prevu && $smd_prognostics_sqlprot->isMalicious()) {\n        $opts = do_list(get_pref(\'smd_prognostics_sql_inject\', \'|\'), \'|\');\n        $blok = (strpos($opts[0], \'smd_block\') !== false);\n        $send = (strpos(get_pref(\'smd_prognostics_rt_forensics\'), \'sql\') !== false);\n        $ver = (defined(\'txp_version\')) ? txp_version : get_pref(\'version\', \'\');\n\n        // Send the forensics off if necessary\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to && $send) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body = n.gTxt(\'smd_prognostics_preamble_sql_inject\').n;\n            $body .= (($ver) ? \'Txp: \' . $ver .n : \'\') . \'PHP: \' . phpversion() .n. \'MySQL: \' . mysqli_get_server_info($DB->link) .n. ((is_callable(\'apache_get_version\')) ? \'Apache: \' . apache_get_version().n : \'\');\n            foreach ($_SERVER + $_REQUEST + $_ENV as $key => $var) {\n                $body .= n.$key.\': \'.$var;\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n\n        if (isset($opts[1]) && !empty($opts[1])) {\n            $parts = do_list($opts[1], \':\');\n            if ($parts[0] == \'txp_form\') {\n                $msg = parse_form($parts[1]);\n            } else {\n                $msg = $opts[1];\n            }\n        } else {\n            $msg = \'\';\n        }\n\n        if ($blok) {\n            echo $msg;\n            exit(1);\n        } else {\n            txp_die($msg, $opts[0]);\n        }\n    }\n}\n\n// -----------------\n// Admin-side panels\n// -----------------\n// ----------  Alarm acknowledgement\nfunction smd_prognostics_ack($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums, $prefs, $DB;\n\n    $method = ps(\'edit_method\');\n    $submit = ($method == \'acknowledge\');\n    $ignore = ($method == \'ignore\');\n    $csi    = ($method == \'csi\');\n\n    $ack = gps(\'selected\');\n    $smd_prog_ack = (is_array($ack)) ? $ack : (($ack) ? array($ack) : array());\n\n    $out = array();\n\n    if ($submit || $ignore) {\n        if ($cs = @file($smd_prognostics_checksums)) {\n            foreach ($cs as $c) {\n                if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                    list(,$file,$md5) = $m;\n                    if (($key = array_search($file, $smd_prog_ack)) !== false) {\n                        if (file_exists($file)) {\n                            $content = smd_prognostics_prep_file($file);\n                            $out[] = $file.\': (\'.( ($ignore) ? \'NULL\' : md5($content) ).\')\';\n                        }\n                        // Remove files that already have checksums so we\'re left with additions\n                        unset($smd_prog_ack[$key]);\n                    } else {\n                        $out[] = trim($c);\n                    }\n                }\n            }\n\n            // Tack on any new files\n            foreach ($smd_prog_ack as $file) {\n                if ( ($file != $smd_prognostics_checksums) && file_exists($file) ) {\n                    $content = smd_prognostics_prep_file($file);\n                    $out[] = $file.\': (\'.( ($ignore) ? \'NULL\' : md5($content) ).\')\';\n                }\n            }\n\n            $fh = fopen($smd_prognostics_checksums, \"w\");\n            fwrite($fh, join(n, $out));\n            fclose($fh);\n            smd_prognostics_self_hash();\n            $msg = gTxt(\'smd_prognostics_acked\');\n        }\n    }\n\n    if ($csi && $smd_prog_ack) {\n         $msg = gTxt(\'smd_prognostics_csi_sent\');\n    } else if ($csi) {\n         $msg = gTxt(\'smd_prognostics_none_selected\');\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_ack\'), $msg);\n\n    list($nok, $miss, $added, $hashdown, $hashmod) = smd_do_prognostics(1);\n    $nok = is_array($nok) ? $nok : array();\n    $miss = is_array($miss) ? $miss : array();\n    $added = is_array($added) ? $added : array();\n    $hashmod = is_array($hashmod) ? $hashmod : array();\n    $errnum = count(array_merge($nok, $miss, $added, $hashmod));\n    $forensics = array();\n    $lform = \'Y-m-d H:i:s\';\n    $med = \'\';\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_ack\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'ack\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-ack-form\" name=\"longform\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_ack\'.a.\'smd_prognostics_suppress=1\">\'.\n        n. \'<div class=\"txp-listtables\">\'.\n        n. startTable(\'\', \'\', \'txp-list\');\n\n    if ($errnum > 0) {\n        foreach($hashmod as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_hashmod\')).tda($naughty));\n        }\n\n        foreach($nok as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_changed\')).tda($naughty));\n\n            if ($sel) {\n                $fi = stat($naughty);\n                $forensics[\'nok\'][] = smd_prognostics_forensic_output($naughty, $fi);\n                $forensics[\'files\'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));\n            }\n        }\n\n        foreach($miss as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_missing\')).tda($naughty));\n\n            if ($sel) {\n                $forensics[\'miss\'][] = $naughty;\n            }\n        }\n\n        foreach($added as $naughty) {\n            $sel = in_array($naughty, $smd_prog_ack);\n            echo tr(td(checkbox(\'selected[]\', $naughty, $sel), \'\', \'multi-edit\').tda(gTxt(\'smd_prognostics_lbl_added\')).tda($naughty));\n\n            if ($sel) {\n                $fi = stat($naughty);\n                $forensics[\'added\'][] = smd_prognostics_forensic_output($naughty, $fi);\n                $forensics[\'files\'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));\n            }\n        }\n\n        if ($smd_prog_ack && $prefs[\'logging\'] != \'none\') {\n            $detect = explode(\',\',get_pref(\'smd_prognostics_lastdetect\', time()));\n\n            if (!isset($detect[1])) {\n                $detect[1] = $detect[0];\n            }\n\n            $rs = safe_rows(\'*\', \'txp_log\', \"time BETWEEN \'\" . date($lform, ($detect[0] - $prefs[\'smd_prognostics_check_freq\'])) . \"\' AND \'\" . date($lform, ($detect[1] + 5)) . \"\'\");\n\n            foreach ($rs as $row) {\n                $forensics[\'txp_log\'][] = join(\',\',$row);\n            }\n        }\n\n        $methods = array(\n            \'acknowledge\' => array(\'label\' => gTxt(\'smd_prognostics_btn_ackit\')),\n            \'ignore\'      => array(\'label\' => gTxt(\'smd_prognostics_btn_ignore\')),\n            \'csi\'         => array(\'label\' => gTxt(\'smd_prognostics_btn_csi\')),\n        );\n\n        $med = multi_edit($methods, $smd_prognostics_event, \'smd_prognostics_ack\');\n\n        if (get_pref(\'smd_prognostics_mailto_csi\', \'\') == \'\') {\n            unset($methods[\'csi\']);\n        }\n    } else {\n        echo tr(td(gTxt(\'smd_prognostics_no_alarms\')));\n        set_pref(\'smd_prognostics_lastdetect\', \'\', \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n    }\n    echo endTable().\n        \'</div>\'.\n        $med.\n        tInput().\n        \'</form>\'.\n        \'</div></div>\'.\n        script_js( <<<EOS\n            $(document).ready(function() {\n                $(\'.smd_prognostics-ack-form\').txpMultiEditForm({\n                    \'row\' : \'tr\',\n                    \'highlighted\' : \'tr\'\n                });\n            });\nEOS\n        );\n\n    if ($csi && $forensics) {\n        // Send the forensics off.\n        $to = get_pref(\'smd_prognostics_mailto_csi\', \'\');\n        if ($to) {\n            $subject = gTxt(\'smd_prognostics_subject_csi\', array(\'{site}\' => get_pref(\'siteurl\')));\n            $hdrs = smd_prognostics_header_info(\'smd_frognostics\');\n            $body =\n                n.\'Txp: \' . txp_version .n. \'PHP: \' . phpversion() .n. \'MySQL: \' . mysqli_get_server_info($DB->link) .n. ((is_callable(\'apache_get_version\')) ? \'Apache: \' . apache_get_version().n : \'\').\n                ((isset($forensics[\'nok\'])) ? n.gTxt(\'smd_prognostics_preamble_nok\').n.join(n,$forensics[\'nok\']) : \'\').\n                ((isset($forensics[\'miss\'])) ? n.n.gTxt(\'smd_prognostics_preamble_miss\').n.join(n,$forensics[\'miss\']) : \'\').\n                ((isset($forensics[\'added\'])) ? n.n.gTxt(\'smd_prognostics_preamble_added\').n.join(n,$forensics[\'added\']) : \'\').\n                ((isset($forensics[\'txp_log\'])) ? n.n.gTxt(\'smd_prognostics_preamble_txplog\').n.join(n,$forensics[\'txp_log\']) : n.n.gTxt(\'smd_prognostics_no_log_entries\'));\n            if (isset($forensics[\'files\'])) {\n                $body .= n.n.gTxt(\'smd_prognostics_preamble_files\');\n                foreach ($forensics[\'files\'] as $fn => $content) {\n                    $body .= n.n.$fn.n.n.$content.n;\n                }\n            }\n\n            mail($to, $subject, $body, $hdrs[\'headers\']);\n        }\n    }\n}\n\n// ---------- File management\nfunction smd_prognostics_files($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums;\n\n    extract(doSlash(gpsa(array(\'submit\'))));\n    $smd_prognostics_files = gps(\'smd_prognostics_files\');\n\n    if (!is_array($smd_prognostics_files)) {\n        $smd_prognostics_files = array();\n    }\n\n    $adds = (strpos(get_pref(\'smd_prognostics_check_for\'), \'add\') !== false);\n    $filelist = smd_prognostics_readfiles();\n    $allcount = count($filelist);\n\n    if ($submit) {\n        $outfile = array();\n        foreach ($smd_prognostics_files as $file) {\n            $content = smd_prognostics_prep_file($file);\n            $outfile[] = $file.\': (\'.md5($content).\')\';\n        }\n\n        if (is_writable(dirname($smd_prognostics_checksums))) {\n            $fh = @fopen($smd_prognostics_checksums, \"w\");\n            if ($fh) {\n                fwrite($fh, join(n, $outfile));\n\n                if ($adds) {\n                    $additions = array_diff($filelist, $smd_prognostics_files);\n                    $added = array();\n                    foreach ($additions as $addition) {\n                        $added[] = $addition.\': (NULL)\';\n                    }\n                    fwrite($fh, n.join(n, $added));\n                }\n                fclose($fh);\n                smd_prognostics_self_hash();\n                smd_do_prognostics(2); // Silently acknowledge all the files\n                set_pref(\'smd_prognostics_lastdetect\', \'\', \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n                $msg = gTxt(\'smd_prognostics_files_updated\');\n            } else {\n                $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => $smd_prognostics_checksums)), E_WARNING);\n            }\n        } else {\n            $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => dirname($smd_prognostics_checksums))), E_WARNING);\n        }\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_files\'), $msg);\n\n    $smd_prognostics_files = array();\n\n    if ($cs = @file($smd_prognostics_checksums)) {\n        foreach ($cs as $c) {\n            if (preg_match(\'@^(\\S+): \\((.*)\\)$@\', trim($c), $m)) {\n                list(,$file,$md5) = $m;\n\n                if ($md5 != \'NULL\') {\n                    $smd_prognostics_files[] = $file;\n                }\n            }\n        }\n    }\n\n    $moncount = count($smd_prognostics_files);\n\n    if ($filelist) {\n        $filez = array();\n\n        foreach($filelist as $key => $val) {\n            $filez[$val] = $filelist[$key];\n        }\n\n        $filesel = smd_prognostics_multisel(\'smd_prognostics_files\', $filez, $smd_prognostics_files);\n    }\n\n    $showfiles = (!empty($filesel));\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_files\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'fil\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-files-form\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_files\">\'.\n        n. startTable(\'\', \'\', \'txp-list\').\n        n. tr(tdcs(gTxt(\'smd_prognostics_currmon\', array(\'{curr}\' => $moncount, \'{outof}\' => $allcount)) .(($showfiles) ? br.br. gTxt(\'smd_prognostics_monfiles_explain\') : \'\'), 1, 400)).\n        n. (($showfiles) ? tr(tda($filesel)) : tr(tda(gTxt(\'smd_prognostics_no_files\')))).\n        n. tr(tda(fInput(\'hidden\', \'smd_prognostics_suppress\', 1).fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'))).\n        n. endTable().\n        n. tInput().\n        n. \'</form>\'.\n        n. \'</div></div>\';\n}\n\n// ---------- Setup / prefs\nfunction smd_prognostics_setup($msg = \'\')\n{\n    global $smd_prognostics_event, $smd_prognostics_checksums, $prefs;\n\n    $origloc = get_pref(\'smd_prognostics_listloc\', realpath(txpath.DS.\'..\'.DS).DS, 1);\n    $origexc = get_pref(\'smd_prognostics_excludir\', \'images, files, tmp\', 1);\n    $origdir = rtrim(get_pref(\'smd_prognostics_dir\', realpath(txpath), 1), DS);\n    $origpfx = get_pref(\'smd_prognostics_prefix\', \'\', 1);\n\n    $preflist = array(\n        \'smd_prognostics_check_freq\',\n        \'smd_prognostics_check_qty\',\n        \'smd_prognostics_alarm_freq\',\n        \'smd_prognostics_check_where\',\n        \'smd_prognostics_mailto\',\n        \'smd_prognostics_mailto_csi\',\n        \'smd_prognostics_users\',\n        \'smd_prognostics_dir\',\n        \'smd_prognostics_prefix\',\n        \'smd_prognostics_listloc\',\n        \'smd_prognostics_excludir\',\n        \'smd_prognostics_ignores\',\n        \'smd_prognostics_inject_sensitivity\',\n        \'smd_prognostics_xss\',\n        \'smd_prognostics_txpdir\',\n    );\n\n    $warnloc = \'\';\n    $notify = gps(\'smd_prognostics_notify_via\');\n    $chfor = gps(\'smd_prognostics_check_for\');\n    $reqs = gps(\'smd_prognostics_req_headers\');\n    $sqlin = gps(\'smd_prognostics_sql_inject\');\n    $tween = gps(\'smd_prognostics_check_between\');\n    $rtfor = gps(\'smd_prognostics_rt_forensics\');\n\n    // Only one of these valid status codes is allowed to be thrown\n    $throw_codes = array(\n        \'smd_no\' => gTxt(\'no\'),\n        \'smd_block\' => gTxt(\'smd_prognostics_block\'),\n        \'200\' => \'200 OK\',\n        \'301\' => \'301 Moved Permanently\',\n        \'302\' => \'302 Found\',\n        \'307\' => \'307 Temporary Redirect\',\n        \'401\' => \'401 Unauthorized\',\n        \'403\' => \'403 Forbidden\',\n        \'404\' => \'404 Not Found\',\n        \'410\' => \'410 Gone\',\n        \'414\' => \'414 Request-URI Too Long\',\n        \'500\' => \'500 Internal Server Error\',\n        \'501\' => \'501 Not Implemented\',\n    );\n\n    if (!is_array($tween)) {\n        $tween = array();\n    }\n\n    foreach ($tween as $idx => $val) {\n        if (empty($val)) {\n            $tween[$idx] = ($idx==0) ? \'00:00\' : \'23:59\';\n        } else {\n            $timeparts = do_list($val, \':\');\n            foreach ($timeparts as $num) {\n                if (!is_numeric($num)) {\n                    $tween[$idx] = ($idx==0) ? \'00:00\' : \'23:59\';\n                    break;\n                }\n            }\n        }\n    }\n\n    $smd_prognostics_notify_via = join(\'|\', ((is_array($notify)) ? $notify : array($notify)));\n    $smd_prognostics_check_for = join(\'|\', ((is_array($chfor)) ? $chfor : array($chfor)));\n    $smd_prognostics_req_headers = join(\'|\', ((is_array($reqs)) ? $reqs : array($reqs)));\n    $smd_prognostics_sql_inject = join(\'|\', ((is_array($sqlin)) ? $sqlin : array($sqlin)));\n    $smd_prognostics_check_between = join(\'|\', ((is_array($tween)) ? $tween : array($tween)));\n    $smd_prognostics_rt_forensics = join(\'|\', ((is_array($rtfor)) ? $rtfor : array($rtfor)));\n\n    // Grab the saved pref values and tack on the array item(s)\n    extract(doSlash(gpsa(array_merge(array(\'submit\'), $preflist))));\n    $preflist[] = \'smd_prognostics_notify_via\';\n    $preflist[] = \'smd_prognostics_check_for\';\n    $preflist[] = \'smd_prognostics_req_headers\';\n    $preflist[] = \'smd_prognostics_sql_inject\';\n    $preflist[] = \'smd_prognostics_check_between\';\n    $preflist[] = \'smd_prognostics_rt_forensics\';\n    $smd_prognostics_dir = rtrim($smd_prognostics_dir, DS);\n\n    if ($submit) {\n        if (($smd_prognostics_dir != $origdir) || ($smd_prognostics_prefix != $origpfx)) {\n            if (is_dir($smd_prognostics_dir) && is_writable($smd_prognostics_dir)) {\n                // Everything OK so do nothing for now\n            } else {\n                // Ignore new dir and reset it to what it was before\n                $msg = array(gTxt(\'smd_prognostics_not_writable\', array(\'{location}\' => $smd_prognostics_dir)), E_WARNING);\n                $smd_prognostics_dir = $origdir;\n            }\n\n            // Room for more config files as and when they are required\n            $filelist[] = $smd_prognostics_checksums;\n\n            foreach ($filelist as $file) {\n                $filename = $smd_prognostics_prefix.ltrim(basename($file), $origpfx);\n                rename($file, rtrim($smd_prognostics_dir, DS).DS.$filename);\n            }\n        }\n\n        // Write all the prefs\n        foreach ($preflist as $prefval) {\n            set_pref(doSlash($prefval), doSlash($$prefval), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n        }\n\n        if ( ($smd_prognostics_listloc != $origloc) || ($smd_prognostics_excludir != $origexc) ) {\n            $msg = array(gTxt(\'smd_prognostics_warn_loc\'), E_WARNING);\n        }\n\n        if (!$msg) {\n            $msg = gTxt(\'preferences_saved\');\n        }\n    }\n\n    pagetop(gTxt(\'smd_prognostics_ttl_setup\'), $msg);\n\n    $smd_prognostics_dir = get_pref(\'smd_prognostics_dir\', txpath, 1);\n    $smd_prognostics_prefix = get_pref(\'smd_prognostics_prefix\', \'\', 1);\n    $smd_prognostics_check_for = get_pref(\'smd_prognostics_check_for\', \'delete|modify\', 1);\n    $smd_prognostics_check_where = get_pref(\'smd_prognostics_check_where\', \'admin\', 1);\n    $smd_prognostics_check_freq = get_pref(\'smd_prognostics_check_freq\', 10, 1);\n    $smd_prognostics_check_between = get_pref(\'smd_prognostics_check_between\', \'00:00|23:59\', 1);\n    $smd_prognostics_check_qty = get_pref(\'smd_prognostics_check_qty\', \'30\', 1);\n    $smd_prognostics_alarm_freq = get_pref(\'smd_prognostics_alarm_freq\', 86400, 1);\n    $smd_prognostics_notify_via = get_pref(\'smd_prognostics_notify_via\', \'\', 1);\n    $smd_prognostics_mailto = get_pref(\'smd_prognostics_mailto\', \'\', 1);\n    $smd_prognostics_mailto_csi = get_pref(\'smd_prognostics_mailto_csi\', \'\', 1);\n    $smd_prognostics_users = get_pref(\'smd_prognostics_users\', \'\', 1);\n    $smd_prognostics_listloc = get_pref(\'smd_prognostics_listloc\', realpath(txpath.DS.\'..\'.DS).DS, 1);\n    $smd_prognostics_excludir = get_pref(\'smd_prognostics_excludir\', \'images, files, tmp\', 1);\n    $smd_prognostics_ignores = get_pref(\'smd_prognostics_ignores\', \'error_log\', 1);\n    $smd_prognostics_req_headers = get_pref(\'smd_prognostics_req_headers\', \'TRACE|PUT|DELETE\', 1);\n    $smd_prognostics_sql_inject = get_pref(\'smd_prognostics_sql_inject\', \'0|\', 1);\n    $smd_prognostics_inject_sensitivity = get_pref(\'smd_prognostics_inject_sensitivity\', 1, 1);\n    $smd_prognostics_xss = get_pref(\'smd_prognostics_xss\', 0, 1);\n    $smd_prognostics_rt_forensics = get_pref(\'smd_prognostics_rt_forensics\', \'1|1\', 1);\n    $smd_prognostics_txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir(), 1);\n\n    $adds = (strpos($smd_prognostics_check_for, \'add\') !== false);\n    $dels = (strpos($smd_prognostics_check_for, \'delete\') !== false);\n    $mods = (strpos($smd_prognostics_check_for, \'modify\') !== false);\n    $rh_trace = (strpos($smd_prognostics_req_headers, \'TRACE\') !== false);\n    $rh_put = (strpos($smd_prognostics_req_headers, \'PUT\') !== false);\n    $rh_del = (strpos($smd_prognostics_req_headers, \'DELETE\') !== false);\n    $rt_hdr = (strpos($smd_prognostics_rt_forensics, \'hdr\') !== false);\n    $rt_sql = (strpos($smd_prognostics_rt_forensics, \'sql\') !== false);\n    $tweens = do_list($smd_prognostics_check_between, \'|\');\n    $throws = do_list($smd_prognostics_sql_inject, \'|\');\n    $helpLink = \"?event=plugin\".a.\"step=plugin_help\".a.\"name=$smd_prognostics_event#smd_setup\";\n\n    //TODO: refactor with inputLabel()\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_setup\'), sp, gTxt(\'smd_prognostics_help_link\', array(\'{link}\' => $helpLink), \'raw\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'set\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. \'<form class=\"smd_prognostics-setup-form\" method=\"post\" action=\"?event=\'.$smd_prognostics_event.a.\'step=smd_prognostics_setup\">\'.\n        n. startTable(\'\', \'\', \'smd_prognostics_setup\').\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_monopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_where\'), \' class=\"smd_label\"\').\n            tda(radioSet(\n                array(\n                    \'admin\' => gTxt(\'no\'),\n                    \'adminpublic\' => gTxt(\'yes\')\n                ), \'smd_prognostics_check_where\', $smd_prognostics_check_where), \' id=\"smd_prognostics_check_where\"\')\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_freq\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_check_freq\', $smd_prognostics_check_freq, \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_seconds\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_between\'), \' class=\"smd_label\"\').\n            tda(\n                fInput(\'text\', \'smd_prognostics_check_between[]\', $tweens[0], \'\',\'\',\'\',15).\n                gTxt(\'smd_prognostics_and\').\n                fInput(\'text\', \'smd_prognostics_check_between[]\', $tweens[1], \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_hms\')\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_qty\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_check_qty\', $smd_prognostics_check_qty, \'\',\'\',\'\',15))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_alarm_freq\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_alarm_freq\', $smd_prognostics_alarm_freq, \'\',\'\',\'\',15).sp.gTxt(\'smd_prognostics_seconds\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_notify_via\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_notify_via[]\', \'txp\', strpos($smd_prognostics_notify_via, \'txp\') !== false) .\n                \'<label>\'.gTxt(\'smd_prognostics_notify_txp\').\'</label>\'.\n                checkbox(\'smd_prognostics_notify_via[]\', \'email\', strpos($smd_prognostics_notify_via, \'email\') !== false).\n                \'<label>\'.gTxt(\'smd_prognostics_notify_email\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_mailto\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_mailto\', $smd_prognostics_mailto, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_mailto_csi\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_mailto_csi\', $smd_prognostics_mailto_csi, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_realopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_req_headers\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_req_headers[]\', \'TRACE\', $rh_trace).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_trace\').\'</label>\'.\n                checkbox(\'smd_prognostics_req_headers[]\', \'PUT\', $rh_put).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_put\').\'</label>\'.\n                checkbox(\'smd_prognostics_req_headers[]\', \'DELETE\', $rh_del).\n                \'<label>\'.gTxt(\'smd_prognostics_rh_del\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_sql_inject\'), \' class=\"smd_label\"\').\n            tda(\n                selectInput(\'smd_prognostics_sql_inject[]\', $throw_codes, $throws[0], 0).\n                gTxt(\'smd_prognostics_with\').\n                fInput(\'text\', \'smd_prognostics_sql_inject[]\', $throws[1], \'\',\'\',\'\',60).br.\n                gTxt(\'smd_prognostics_sensitivity\').\n                fInput(\'text\', \'smd_prognostics_inject_sensitivity\', $smd_prognostics_inject_sensitivity, \'\',\'\',\'\',5).sp.gTxt(\'smd_prognostics_sensitivity_explain\').br.\n                gTxt(\'smd_prognostics_xss\').\n                yesnoRadio(\'smd_prognostics_xss\', $smd_prognostics_xss).sp.gTxt(\'smd_prognostics_xss_explain\')\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_send_forensics\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_rt_forensics[]\', \'hdr\', $rt_hdr).\n                \'<label>\'.gTxt(\'smd_prognostics_rt_hdr\').\'</label>\'.\n                checkbox(\'smd_prognostics_rt_forensics[]\', \'sql\', $rt_sql).\n                \'<label>\'.gTxt(\'smd_prognostics_rt_sql\').\'</label>\'\n            )\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_fileopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_check_for\'), \' class=\"smd_label\"\').\n            tda(\n                checkbox(\'smd_prognostics_check_for[]\', \'add\', $adds) .\n                \'<label>\'.gTxt(\'smd_prognostics_ch_add\').\'</label>\'.\n                checkbox(\'smd_prognostics_check_for[]\', \'delete\', $dels).\n                \'<label>\'.gTxt(\'smd_prognostics_ch_del\').\'</label>\'.\n                checkbox(\'smd_prognostics_check_for[]\', \'modify\', $mods).\n                \'<label>\'.gTxt(\'smd_prognostics_ch_mod\').\'</label>\'\n            )\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_listloc\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_listloc\', $smd_prognostics_listloc, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_excludir\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_excludir\', $smd_prognostics_excludir, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_ignores\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_ignores\', $smd_prognostics_ignores, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\'))\n        ).\n        n. tr(tdcs(hed(gTxt(\'smd_prognostics_ttl_plugopts\'), 3), 2)).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_auth_users\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_users\', $smd_prognostics_users, \'\',\'\',\'\',60).sp.gTxt(\'smd_prognostics_csv\').sp.gTxt(\'smd_prognostics_csense\'))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_txpdir\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_txpdir\', $smd_prognostics_txpdir, \'\',\'\',\'\',60))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_progloc\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_dir\', $smd_prognostics_dir, \'\',\'\',\'\',60))\n        ).\n        n. tr(\n            tda(gTxt(\'smd_prognostics_progpfx\'), \' class=\"smd_label\"\').\n            tda(fInput(\'text\', \'smd_prognostics_prefix\', $smd_prognostics_prefix, \'\',\'\',\'\',60))\n        ).\n        n. tr(tda(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'))).\n        n. endTable().\n        n. tInput().\n        n. \'</form>\'.\n        n. \'</div>\'.\n        n. \'</div>\';\n}\n\n// ----------  Security advice\nfunction smd_prognostics_advice($msg = \'\')\n{\n    global $smd_prognostics_event, $prefs, $event, $DB;\n    require_once txpath.\'/lib/IXRClass.php\';\n\n    pagetop(gTxt(\'smd_prognostics_ttl_advice\'), $msg);\n\n    $checks = array();\n\n    // Prognostics dir in docroot?\n    if (strpos(get_pref(\'smd_prognostics_dir\', txpath), $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_prognostics\');\n    }\n\n    // Setup still exists?\n    if (@is_dir(txpath . DS. \'setup\')) {\n        $checks[] = gTxt(\'smd_prognostics_setup_exists\');\n    }\n\n    // RPC still exists?\n    if ( ($prefs[\'enable_xmlrpc_server\'] == 0) && (@is_dir(realpath(txpath.DS.\'..\'.DS. \'rpc\'))) ) {\n        $checks[] = gTxt(\'smd_prognostics_rpc_exists\');\n    }\n\n    // New Txp version/branch available?\n    include_once txpath.\'/include/txp_diag.php\';\n    $now = time();\n    $updateInfo = unserialize(get_pref(\'smd_prognostics_last_update_check\', \'\'));\n\n    if (!$updateInfo || ( $now > ($updateInfo[\'when\'] + (60*60*24)) )) {\n        $updates = checkUpdates();\n        $checks[] = $updateInfo[\'msg\'] = ($updates) ? gTxt($updates[\'msg\'], array(\'{version}\' => $updates[\'version\'])) : \'\';\n        $updateInfo[\'when\'] = $now;\n        set_pref(\'smd_prognostics_last_update_check\', serialize($updateInfo), \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n    }\n\n    // Files dir in docroot?\n    if (strpos($prefs[\'file_base_path\'], $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_files\');\n    }\n\n    // Tmp dir in docroot?\n    if (strpos($prefs[\'tempdir\'], $prefs[\'path_to_site\']) !== false) {\n        $checks[] = gTxt(\'smd_prognostics_docroot_tmp\');\n    }\n\n/*\n    // TODO: check what SHOW GRANTS returns and determine if it could be a security risk\n    $res = safe_query(\'SHOW GRANTS\');\n    if (numRows($res) > 0) {\n        $checks[] = gTxt(\'smd_prognostics_show_grants\');\n    }\n*/\n\n    // Does this MySQL user have FILES privs?\n    $randfile = $prefs[\'tempdir\'].DS.rand().time().\'.sql\';\n    $res = @safe_query(\'SELECT id INTO OUTFILE \"\'.$randfile.\'\" FIELDS TERMINATED BY \"\\t\" LINES TERMINATED BY \"\\n\" FROM txp_image WHERE 1 LIMIT 1\');\n    if (mysqli_error($DB->link) == \'\') {\n        $checks[] = gTxt(\'smd_prognostics_sql_file_privs\');\n        if (is_file($randfile)) {\n            @unlink($randfile);\n        }\n    }\n\n    echo n. \'<div class=\"txp-layout\">\'.\n        n. \'<div class=\"txp-layout-2col\">\'.\n        n. \'<h1 class=\"txp-heading\">\'.\n        n. gTxt(\'smd_prognostics_ttl_advice\').\n        n. \'</h1>\'.\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_control\" class=\"txp-layout-2col\">\'.\n        n. smd_prognostics_button_bar(\'adv\').\n        n. \'</div>\'.\n        n. \'<div id=\"\'.$smd_prognostics_event.\'_container\" class=\"txp-container\">\'.\n        n. startTable(\'\', \'\', \'smd_prognostics_advice\');\n\n    if ($checks) {\n        foreach($checks as $check) {\n            echo n. tr(tda($check));\n        }\n    } else {\n        echo n. tr(tda(gTxt(\'smd_prognostics_tight\')));\n    }\n\n    echo endTable(),\n        n. \'</div></div>\';\n}\n\n// ---------- Common buttons\nfunction smd_prognostics_button_bar($active = \'\')\n{\n    global $smd_prognostics_event;\n\n    $active = ($active) ? $active : \'setup\';\n\n    $btns = array(\n        \'ack\' => eLink($smd_prognostics_event, \'smd_prognostics_ack\', \'smd_prognostics_suppress\', \'1\', gTxt(\'smd_prognostics_pnl_ack\')),\n        \'adv\' => sLink($smd_prognostics_event, \'smd_prognostics_advice\', gTxt(\'smd_prognostics_pnl_advice\')),\n        \'fil\' => sLink($smd_prognostics_event, \'smd_prognostics_files\', gTxt(\'smd_prognostics_pnl_files\')),\n        \'set\' => sLink($smd_prognostics_event, \'smd_prognostics_setup\', gTxt(\'smd_prognostics_pnl_setup\')),\n    );\n\n    return graf(\n            (($active == \'set\') ? strong($btns[\'set\']) : $btns[\'set\'])\n            .n.(($active == \'fil\') ? strong($btns[\'fil\']) : $btns[\'fil\'])\n            .n.(($active == \'ack\') ? strong($btns[\'ack\']) : $btns[\'ack\'])\n            .n.(($active == \'adv\') ? strong($btns[\'adv\']) : $btns[\'adv\'])\n        , \' class=\"txp-buttons\"\');\n}\n\n// ---------- Hash the hashfile\nfunction smd_prognostics_self_hash() {\n    global $smd_prognostics_checksums;\n\n    if (file_exists($smd_prognostics_checksums)) {\n        $content = smd_prognostics_prep_file($smd_prognostics_checksums);\n        $hash = md5($content);\n    } else {\n        $hash = NULL;\n    }\n    set_pref(\'smd_prognostics_sumhash\', $hash, \'smd_prognos\', PREF_HIDDEN, \'text_input\');\n}\n\n// ---------- Compile list of files to monitor\nfunction smd_prognostics_readfiles() {\n    $filelist = array();\n\n    $smd_prognostics_listloc = get_pref(\'smd_prognostics_listloc\', \'\');\n    $excludes = smd_prognostics_ignore_list();\n\n    if ($smd_prognostics_listloc) {\n        foreach (do_list($smd_prognostics_listloc) as $loc) {\n            // NOTE: not using GLOB_BRACE to grab regular and dot files, since it\'s not always available cross-OS\n            $filelist = array_merge($filelist, smd_prognostics_rglob(\"*\", GLOB_MARK, $loc, $excludes), smd_prognostics_rglob(\".*\", GLOB_MARK, $loc, $excludes));\n        }\n    }\n    return $filelist;\n}\n\n// ---------- Prepare file contents for md5. Assumes file exists\n// Checks to see if large files are binary before wasting time stripping newlines and stuff\nfunction smd_prognostics_prep_file($file) {\n    $content = file_get_contents($file);\n    $stat = stat($file);\n    $dostrip = true;\n    // Perform the strip regardless on files < 1Kb as it has little impact\n    if ($stat[\'size\'] > 1024000) {\n        $part = substr($content, 0, 1536000); // First 1.5KB\n        if (strpos($part, 0x00) || (strpos($part, 0x0D) !== false) || (strpos($part, 0x0A) !== false)) {\n            // Likely a binary file: leave it be\n            $dostrip = false;\n        }\n    }\n    if ($dostrip) {\n        $content = str_replace(array(\"\\r\\n\", \"\\$HeadURL: http:\"), array(\"\\n\", \"\\$HeadURL: https:\"), $content);\n    }\n    return $content;\n}\n\n// ---------- Pre-compute excluded files and dirs, expanding wildcards in the process\nfunction smd_prognostics_ignore_list() {\n    global $smd_prognostics_checksums;\n\n    $excl = do_list(get_pref(\'smd_prognostics_excludir\', \'\'));\n    $ign = get_pref(\'smd_prognostics_ignores\', \'\');\n\n    // Add any files to permanently exclude here (NOT dirs: they\'re ignored automatically)\n    $ignarr = ($ign) ? do_list($ign) : array();\n\n    // Expand any wildcard filenames\n    $regarr = array();\n    foreach ($ignarr as $idx => $item) {\n        if ( (strpos($item, \'*\') !== false) || (strpos($item, \'?\') !== false) ) {\n            $regarr[] = str_replace( array(\"\\*\", \"\\?\"), array(\".*\", \".\"), preg_quote($item) );\n            unset($ignarr[$idx]); // Remove the wildcard filename from the ignore list\n        }\n    }\n    $regexclude = ($regarr) ? \'/^(\' . join(\'|\', $regarr) . \')$/\' : \'\';\n    $permexclude = array_merge(array(basename($smd_prognostics_checksums)), $ignarr);\n\n    return array($excl, $regexclude, $permexclude);\n}\n\n// Frankensteined from http://snipplr.com/view/16233/recursive-glob/\nfunction smd_prognostics_rglob($pattern, $flags=0, $path=\'\', $excludes = array(), $excl=array(), $ign=\'\') {\n    if (!$path && ($dir = dirname($pattern)) != \'.\') {\n        if ($dir == \'\\\\\' || $dir == DS) $dir = \'\';\n        return (array)smd_prognostics_rglob(basename($pattern), $flags, $dir . DS, $excludes);\n    }\n    $paths = glob($path . \'*\', GLOB_ONLYDIR | GLOB_NOSORT);\n    $files = glob($path . $pattern, $flags);\n\n    if (is_array($paths)) {\n        foreach ($paths as $p) {\n            $parts = explode(DS, $p);\n            $pinfo = array_pop($parts);\n            if (!in_array($pinfo, $excludes[0])) {\n                $files = array_merge((array)$files, (array)smd_prognostics_rglob($pattern, $flags, $p . DS, $excludes));\n                foreach($files as $idx => $theFile) {\n                    $parts = explode(DS, $theFile);\n                    $fex = array_pop($parts);\n                    $rex = ($excludes[1]) ? preg_match($excludes[1], $fex) : false;\n                    if($fex==\'\' || in_array($fex, $excludes[2]) || $rex) {\n                        unset($files[$idx]);\n                    }\n                }\n            }\n        }\n    }\n    return $files;\n}\n\n// Format forensic data for a file\nfunction smd_prognostics_forensic_output($file, $fi) {\n    global $prefs;\n\n    $dform = $prefs[\'dateformat\'];\n    $sep = n;\n    $out = \'\';\n\n    $out .= $sep . gTxt(\'smd_prognostics_stat_name\') . $file;\n    $out .= $sep . gTxt(\'smd_prognostics_stat_size\') . $fi[\'size\'];\n    $out .= $sep . gTxt(\'smd_prognostics_stat_mod\') . strftime($dform, $fi[\'mtime\']);\n    $out .= $sep . gTxt(\'smd_prognostics_stat_uid\') . $fi[\'uid\'];\n    $out .= $sep . gTxt(\'smd_prognostics_stat_gid\') . $fi[\'gid\'];\n\n    return $out;\n}\n\n// ---------- Does what it says on the tin\n// Assumes \'textpattern\' is the admin-side directory if server var not set. Must fix in core one day with true constant\nfunction smd_prognostics_guess_admin_dir() {\n    $admindir = trim(dirname($_SERVER[\'PHP_SELF\']), \'/\\\\\');\n    $admindir = (empty($admindir)) ? \'textpattern\' : $admindir;\n    return $admindir;\n}\n\n// ---------- Get common server info for mail headers, etc\nfunction smd_prognostics_header_info($fromname) {\n    $domainparts = do_list(doStrip(serverSet(\'SERVER_NAME\')), \'.\');\n    $numparts = count($domainparts);\n    $domain = $domainparts[$numparts-2] . \'.\' . $domainparts[$numparts-1];\n    $reply_to = \'noreply@\'.$domain;\n    $txpdir = get_pref(\'smd_prognostics_txpdir\', hu.smd_prognostics_guess_admin_dir());\n    $sep = IS_WIN ? \"\\r\\n\" : \"\\n\";\n    $headers = \"From: $fromname <$reply_to>\".\n        $sep.\'Reply-To: \'.$reply_to.\n        $sep.\'X-Mailer: Textpattern\'.\n        $sep.\'Content-Transfer-Encoding: 8bit\'.\n        $sep.\'Content-Type: text/plain; charset=\"UTF-8\"\'.\n        $sep;\n\n    $out = array(\n        \'domain\' => $domain,\n        \'reply_to\' => $reply_to,\n        \'txpdir\' => $txpdir,\n        \'sep\' => $sep,\n        \'headers\' => $headers,\n    );\n\n    return $out;\n}\n\n// Multi-file dropdown selection\nfunction smd_prognostics_multisel($selname=\'\', $tree=array(), $sel=array()) {\n    $out[] = \'<select id=\"\'.$selname.\'\" name=\"\'.$selname.\'[]\" class=\"list\" style=\"height:400px;\" multiple=\"multiple\">\';\n    foreach ($tree as $leaf) {\n        $selected=\'\';\n        if (in_array($leaf, $sel)) {\n            $selected = \' selected=\"selected\"\';\n        }\n\n        $out[] = t.\'<option\'.$selected.\'>\'.htmlspecialchars($leaf).\'</option>\'.n;\n    }\n    $out[] = \'</select>\';\n    return join(\'\',$out);\n}\n\n\n\n//****************************************************************\n// Web page      : http://code.google.com/p/phprotector\n// Autor                : Hugo Sousa        adamastor666@gmail.com\n// Date              : 2010-03-25\n// Version          : 0.3.1.1\n//\n//***************************************************************\nclass smd_prog_PhProtector {\n    var $SHOW_ERRORS;\n    var $do_xss;\n\n    public function __construct($show_errors) {\n        $this->SHOW_ERRORS=$show_errors;\n        if ($this->SHOW_ERRORS) {\n            error_reporting(E_ERROR | E_WARNING | E_PARSE);  //Show errors\n            ini_set(\'display_errors\', \"1\"); //display errors\n        } else {\n            ini_set(\'display_errors\', \"0\"); //display errors\n            ini_set(\'log_errors\', \"1\"); //log_errors\n        }\n        $this->do_xss = get_pref(\'smd_prognostics_xss\', 0);\n    }\n\n    /*\n    * Main function to be called in a index page that redirects to other pages\n    *\n    */\n    public function isMalicious() {\n        $sqli = 0;\n\n        $sqli = callback_event(\'smd_frognostics\', \'sql_injection\', false);\n\n        if (!$sqli) {\n            $num_bad_words1 = $this->CheckGet();\n            $num_bad_words2 = $this->CheckPost();\n\n            $thresh = explode(\',\', get_pref(\'smd_prognostics_inject_sensitivity\', 1));\n            if (!isset($thresh[1])) {\n                $thresh[1] = $thresh[0];\n            }\n            $thresh[0] = is_numeric($thresh[0]) ? $thresh[0] : 1;\n            $thresh[1] = is_numeric($thresh[1]) ? $thresh[1] : 1;\n\n            if ($num_bad_words1 >= $thresh[0]) {\n                $sqli = true;\n            }\n\n            if ($num_bad_words2 >= $thresh[1]) {\n                $sqli = true;\n            }\n        }\n\n        return (($sqli <= 0) ? false : $sqli);\n    }\n\n    //check for sql injection and XSS in Post variables\n    private function CheckPost() {\n        $num_bad_words = 0;\n\n        foreach($_POST as $campo => $input) {\n            // XSS PROTECTION\n            if ($this->do_xss) {\n                if (is_array($_POST[$campo])) {\n                    $_POST[$campo] = array_map(\'htmlentities\', $_POST[$campo], array_fill(0, count($_POST[$campo]), ENT_NOQUOTES) );\n                } else {\n                    $_POST[$campo]= htmlentities($_POST[$campo], ENT_NOQUOTES);\n                }\n            }\n            $num_bad_words = $num_bad_words + $this->wordExists($input); //SQL INJECTION\n        }\n\n        return $num_bad_words;\n    }\n\n    //check for sql injection and XSS in GET variables\n    private function CheckGet() {\n        $num_bad_words = 0;\n\n        foreach($_GET as $campo => $input) {\n            // XSS PROTECTION\n            if ($this->do_xss) {\n                if (is_array($_GET[$campo])) {\n                    $_GET[$campo] = array_map(\'htmlentities\', $_GET[$campo], array_fill(0, count($_GET[$campo]), ENT_NOQUOTES) );\n                } else {\n                    $_GET[$campo]= htmlentities($_GET[$campo], ENT_NOQUOTES);\n                }\n            }\n            $strinput = (is_array($input)) ? join(\'\', $input) : $input;\n            if($this->isIdInjection($campo, $strinput)){ //SQL ID INJECTION\n                $num_bad_words =    $num_bad_words + 0.5;\n            }\n\n            $num_bad_words = $num_bad_words + $this->wordExists($input); //SQL INJECTION\n        }\n\n        return $num_bad_words;\n    }\n\n    /**\n    *   return true if injection sql word is found.\n    *   The input is tested if is equal to a sql injection pattern\n    *   \\b[^a-z]*?drop[^a-z]*?\\b\n    *    http://www.pagecolumn.com/tool/regtest.htm\n    **//*        \"/*\",\"+\"               */\n    private function wordExists($input) {\n        $num_bad_words = 0;\n        $input = is_array($input) ? $input : array($input);\n\n        /*\n         WORD AFTER\n        */\n        $baddelim1 = \"[^a-z]*\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]+\";\n        $badwords= array(\"union\", \"select\", \"show\", \"insert\", \"update\", \"delete\", \"drop\", \"truncate\", \"create\", \"load_file\", \"exec\", \"#\", \"--\");\n        //\"/*\"\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*WA*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n\n        /*\n        BEFORE WORD\n        */\n        $baddelim1 = \"[^a-z]+\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]*\";\n        $badwords= array(\"@@version\", \"@@datadir\", \"user\", \"version\");\n\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*BW*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n\n        /*\n        BEFORE WORD AFTER\n        */\n        $baddelim1 = \"[^a-z]+\"; //the delim should be from \"a\" to \"b\" anything else is considered sql injection :)\n        $baddelim2 = \"[^a-z]+\";\n        $badwords= array(\"benchmark\", \"--\", \"varchar\", \"convert\", \"char\", \"limit\", \"information_schema\",\"table_name\", \"from\", \"where\", \"order\");\n\n        foreach($badwords as $badword) {\n            $expression = \"/\".$baddelim1.strtolower($badword).$baddelim2.\"/\";\n            foreach ($input as $wrd) {\n                if (preg_match ($expression, strtolower($wrd))) {\n//dmp(\'*B-W-A*\',$badword, $wrd);\n                    //die(\"sql injection!\");\n                    $num_bad_words++;\n                }\n            }\n        }\n//dmp($num_bad_words);\n        return $num_bad_words;\n    }\n\n    /**\n    *   return true if an ID is not really an ID\n    *\n    **/\n    private function isIDInjection($campo, $input) {\n        $reg=\"/^id/\";\n\n        if(preg_match($reg, $campo)) {\n            if(!$this->stringIsNumberNotZero($input)) {\n                return true;     // if is ID and NOT INTEGER or NULL -> SQL INJECTION!!\n            }\n        }\n\n        return false;\n    }\n\n    /**\n    *   return true if the string is a number (different from 0, the id could not be zero!)\n    *  TODO: check if *all* chars are zero and fail, e.g. id=0000\n    **/\n    private function stringIsNumberNotZero( $string ) {\n        if (empty($string)) {\n            return false;\n        }\n        return ctype_digit($string);\n    }\n} //end class', '232c20bdef47f1e6cfc1e9ef44783987', '#@smd_prognostics\nsmd_prognostics => Prognostics\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)\n#@smd_prognostics\n#@language de\nsmd_prognostics => Prognostics\n#@smd_prognostics\n#@language en-gb\nsmd_prognostics => Prognostics\n#@smd_prognostics\n#@language en-us\nsmd_prognostics => Prognostics\n#@smd_prognostics\n#@language es\nsmd_prognostics => Prognostics\n#@smd_prognostics\n#@language fr\nsmd_prognostics => Prognostics\n#@smd_prognostics\n#@language de\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)\n#@smd_prognostics\n#@language en-gb\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)\n#@smd_prognostics\n#@language en-us\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)\n#@smd_prognostics\n#@language es\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)\n#@smd_prognostics\n#@language fr\nsmd_prognostics_acked => Alarms acknowledged.\nsmd_prognostics_alarm_freq => Alarm on detection and every:\nsmd_prognostics_and => and\nsmd_prognostics_auth_users => Restrict prognostic config to:\nsmd_prognostics_block => Block\nsmd_prognostics_btn_ackit => Acknowledge\nsmd_prognostics_btn_csi => Send Forensics\nsmd_prognostics_btn_ignore => Ignore\nsmd_prognostics_check_between => Check files between:\nsmd_prognostics_check_for => Check files for:\nsmd_prognostics_check_freq => Check files (at most) every:\nsmd_prognostics_check_qty => Check this many files each time:\nsmd_prognostics_check_where => Check files on public side clicks:\nsmd_prognostics_ch_add => Additions\nsmd_prognostics_ch_del => Deletions\nsmd_prognostics_ch_mod => Modifications\nsmd_prognostics_csense => (case-sensitive)\nsmd_prognostics_csi_sent => Forensics data sent\nsmd_prognostics_csv => (comma-separated)\nsmd_prognostics_currmon => You are currently monitoring {curr} out of {outof} available files.\nsmd_prognostics_docroot_files => Consider moving the \"files\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > File directory path\" setting.\nsmd_prognostics_docroot_prognostics => Please change the \"Prognostics folder\" setting to a directory outside your site root folder.\nsmd_prognostics_docroot_tmp => Consider moving the \"tmp\" folder outside of your site root folder. You can do this via the \"Admin > Preferences > Admin > Temporary directory path\" setting.\nsmd_prognostics_excludir => Exclude folders:\nsmd_prognostics_files_updated => File list updated\nsmd_prognostics_help_link => (<a href=\"{link}\">Help</a>)\nsmd_prognostics_hms => (hrs:mins:secs)\nsmd_prognostics_ignores => Ignore files:\nsmd_prognostics_lbl_added => Added (not monitored)\nsmd_prognostics_lbl_changed => Changed\nsmd_prognostics_lbl_hashmod => COMPROMISED\nsmd_prognostics_lbl_missing => Missing\nsmd_prognostics_listloc => File locations:\nsmd_prognostics_mailto => Send e-mail to:\nsmd_prognostics_mailto_csi => Send forensics to:\nsmd_prognostics_monfiles_explain => Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.\nsmd_prognostics_none_selected => No files selected\nsmd_prognostics_notify_email => E-mail\nsmd_prognostics_notify_txp => Txp interface\nsmd_prognostics_notify_via => Notify via:\nsmd_prognostics_not_writable => {location} is not a directory or is not writable\nsmd_prognostics_no_alarms => No alarms to acknowledge at present.\nsmd_prognostics_no_files => No files to list. Check the plugin setup (and Save it).\nsmd_prognostics_no_log_entries => No log activity around the time of the differences.\nsmd_prognostics_pnl_ack => Alarms\nsmd_prognostics_pnl_advice => Advice\nsmd_prognostics_pnl_files => Files\nsmd_prognostics_pnl_setup => Setup\nsmd_prognostics_postamble => Acknowledge alarms\nsmd_prognostics_preamble_added => These files have been added:\nsmd_prognostics_preamble_files => File contents follows:\nsmd_prognostics_preamble_hashdown => WARNING! The checksums file is missing.\nsmd_prognostics_preamble_hashmod => WARNING! The checksums file has been altered:\nsmd_prognostics_preamble_miss => These files are missing:\nsmd_prognostics_preamble_nok => These files differ from their expected content:\nsmd_prognostics_preamble_sql_inject => Possible SQL injection detected\nsmd_prognostics_preamble_txplog => Log entries prior to this detection:\nsmd_prognostics_progloc => Prognostics folder:\nsmd_prognostics_progpfx => Unique prefix:\nsmd_prognostics_req_headers => Block request headers:\nsmd_prognostics_req_not_allowed => REQUEST_METHOD not allowed: {req}\nsmd_prognostics_rh_del => DELETE\nsmd_prognostics_rh_put => PUT\nsmd_prognostics_rh_trace => TRACE\nsmd_prognostics_rpc_exists => The rpc directory is not being used. Consider removing it.\nsmd_prognostics_rt_hdr => Header violations\nsmd_prognostics_rt_sql => SQL injections\nsmd_prognostics_seconds => (seconds)\nsmd_prognostics_send_forensics => Send forensics for:\nsmd_prognostics_sensitivity => Sensitivity threshold:\nsmd_prognostics_sensitivity_explain => (1=most sensitive)\nsmd_prognostics_setup_exists => The setup directory still exists. Please delete it.\nsmd_prognostics_show_grants => Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.\nsmd_prognostics_sql_file_privs => Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.\nsmd_prognostics_sql_inject => Protect against SQL injection:\nsmd_prognostics_stat_gid => Group ID:\nsmd_prognostics_stat_mod => Modified:\nsmd_prognostics_stat_name => Filename:\nsmd_prognostics_stat_size => Size:\nsmd_prognostics_stat_uid => User ID:\nsmd_prognostics_subject => Prognostics ({site})\nsmd_prognostics_subject_csi => Prognostics ({site}) forensics\nsmd_prognostics_tight => You run a pretty tight ship. Stay frosty.\nsmd_prognostics_ttl_ack => Prognostic alarm acknowledgement\nsmd_prognostics_ttl_advice => Prognostic advice\nsmd_prognostics_ttl_fileopts => Filesystem options\nsmd_prognostics_ttl_files => Prognostic file monitoring\nsmd_prognostics_ttl_monopts => Monitoring options\nsmd_prognostics_ttl_plugopts => Plugin options\nsmd_prognostics_ttl_realopts => Realtime options\nsmd_prognostics_ttl_setup => Prognostic setup\nsmd_prognostics_txpdir => Admin-side URL:\nsmd_prognostics_warn_loc => File locations changed. Update your <a href=\"index.php?event=smd_prognostics&#38;;step=smd_prognostics_files\">list of files</a> now\nsmd_prognostics_with => with:\nsmd_prognostics_xss => XSS shield\nsmd_prognostics_xss_explain => (may break Textiled comments)', '', 1, 1, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_pullquote', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.1a', 'Pulls text from articles for magazine-style quotes. Uses jQuery.', '<div id=\"smd_help\">\n<h1 id=\"top\">smd_pullquote</h1>\n<p>For more information on usage, bugs, features etc, please <a href=\"http://forum.textpattern.com/viewtopic.php?id=24476\">visit the forum thread</a>.</p>\n\n<p class=\"required\">Requires <a href=\"http://stefdawson.com/downloads/smd_lib_v0.3.txt\">smd_lib v0.3</a> to be installed and activated.<p>\n<p class=\"required\">Requires <a href=\"http://jquery.com/\">jQuery</a> to be available. From TXP v4.0.5 this is distributed by default.<p>\n\n<h2 id=\"contents\">Contents</h2>\n<ul>\n<li><a href=\"#overview\">Overview</a></li>\n<li><a href=\"#prereq\">Before you begin</a></li>\n<li><a href=\"#atts\">Attributes</a>\n<ul>\n<li><a href=\"#jquery\">jquery</a></li>\n<li><a href=\"#jqclashvar\">jq_clashvar</a></li>\n<li><a href=\"#searchclass\">search_class</a></li>\n<li><a href=\"#wraptag\">wraptag</a></li>\n<li><a href=\"#class\">class</a></li>\n<li><a href=\"#container\">container</a></li>\n<li><a href=\"#insertmethod\">insert_method</a></li>\n<li><a href=\"#limit\">limit</a></li>\n<li><a href=\"#random\">random</a></li>\n<li><a href=\"#hideorig\">hide_originals</a></li>\n<li><a href=\"#chopper\">chopper</a></li>\n<li><a href=\"#omissions\">omissions</a></li>\n<li><a href=\"#start\">start</a></li>\n<li><a href=\"#end\">end</a></li>\n<li><a href=\"#pos\">pos</a></li>\n</ul></li>\n<li><a href=\"#examples\">Examples</a></li>\n<li><a href=\"#styling\">Styling</a></li>\n</ul>\n\n<h2 id=\"overview\">Overview</h2>\n<p>Ever wanted to have those magazine-style pullquotes automatically generated from your article content? This is the plugin for you!</p>\n<p>Features:</p>\n<ul>\n<li>Using markup you tell the plugin which bits of your articles are to be displayed and styled separately: no need to handle quotes separately from content</li>\n<li>Words from the full body text can be cut from the quotes to make them shorter; omitted words are replaced with characters of your choice</li>\n<li>Display one or more quotes randomly from a page, optionally removing the originals</li>\n<li>Position the quotes anywhere you like via CSS, or use rudimentary maths to keep the quote near to its position on the page. See <a href=\"#pos\">pos</a>, <a href=\"#container\">container</a> and <a href=\"#insertmethod\">insert_method</a></li>\n<li>Key attributes covered by MLP for multi-lingual control over quotes</li>\n</ul>\n\n<h2 id=\"prereq\">Before you begin</h2>\n<p>The plugin relies heavily on javascript and <a href=\"http://jquery.com\">jQuery</a>. From TXP v4.0.5 jQuery is shipped and installed with the base package. You may need to upgrade the version that ships with textpattern to a later version since this plugin was developed with jQuery v1.2.1; the version that ships with TXP is an earlier version. Note that if you use other javascript libraries on the same page (e.g. mootools) you may need to tell the plugin which is which to avoid clashes. See <a href=\"#jquery\">jquery</a> and <a href=\"#jqclashvar\">jq_clashvar</a> for more.</p>\n<p>The general idea is that as you write your article, you wrap any killer quotes with some markup; usually a <code>&lt;span&gt;</code> element since they are \"invisible\" in the content. Give each quote the same class name (by default it\'s <code>pq</code> but can be changed with the <a href=\"#searchclass\">search_class</a> option) and your quotes will be copied from your document into separate elements ready to be positioned/styled.</p>\n<p>Absolute positioning works best but you may have success with floats, or even inline depending on your level of ingenuity.</p>\n\n<h2 id=\"atts\">Attributes</h2>\n<h3 id=\"jquery\"><code>jquery</code></h3>\n<p>For a default installation you should not need to change this. It specifies the path to the <code>jquery.js</code> library file. Defaults to <code>/textpattern/jquery.js</code>. Give it the full path to the library on your server or, if you have already loaded the library earlier on the page, specify <code>jquery=\"\"</code>.</p>\n\n<h3 id=\"jqclashvar\"><code>jq_clashvar</code></h3>\n<p>You should not need to change this unless you are using another javascript library such as mootools on the same page. To avoid the libraries clashing, jQuery provides a facility to identify itself uniquely to the javascript interpreter. Specify the unique variable you wish jquery to use here.</p>\n<p>By default it uses <code>jq</code>. If you have already set up the jquery <code>.noConflict()</code> variable elsewhere on the page, simply specify the variable name you used and it will continue to use it (in this case, it will not redefine the variable). If you specify a different name, or one that is previously undefined, the variable will be created and used for smd_pullquote.</p>\n\n<h3 id=\"searchclass\"><code>search_class</code></h3>\n<p>Once the page is displayed the plugin goes through the document and looks for all occurrences of content wrapped with an HTML element of a particular class. By default it\'s <code>pq</code>. If you don\'t like that, change it with this option.</p>\n<p>Let\'s assume your content is:\n\n<blockquote><p>The lion is a magnificent animal but don\'t be lulled into a false sense of security by its looks. Beneath the cuddly exterior lie teeth - big teeth - and &lt;span class=\"pq\"&gt;if it hasn\'t eaten for a while you could be next on the menu&lt;/span&gt;.</p></blockquote>\n\n<p>Using the <code>span</code> in that manner tells the plugin that the text \"if it hasn\'t eaten for a while you could be next on the menu\" should be \'pulled\' (copied) out of the text into its own element so you can style it.</p>\n<p>You can specify as many of these as you like, just give each one the same class. And don\'t nest them.</p>\n\n<h3 id=\"wraptag\"><code>wraptag</code></h3>\n<p>Every quote pulled from the document will be placed into separate elements, each wrapped in this tag. By default it is the &lt;q&gt; tag but you can specify anything you like here: <code>div</code>, <code>span</code>, <code>p</code>, whatever. See <a href=\"#start\">start</a> and <a href=\"#end\">end</a> for more.</p>\n\n<h3 id=\"class\"><code>class</code></h3>\n<p>Every quote pulled from the document is assigned the same class for easy styling. Specify it here. Default: <code>pQuote</code> (note the capital Q!)</p>\n\n<h3 id=\"container\"><code>container</code></h3>\n<p>All pullquotes are injected into the document after it is rendered to the page. Specify here a DOM element, id or class name where you want them to go. By default it is <code>body</code>.</p>\n<p>If you are specifying a raw html element, just give its name: e.g. <code>container=\"body\"</code>.</p>\n<p>If you wish the quotes to be inserted into an element with a specific id, use <code>container=\"#myID\"</code>.</p>\n<p>Similarly, if you want the quotes to be inserted into an element with a specific class, use <code>container=\".myClass\"</code>. The <a href=\"#insertmethod\">insert_method</a> attribute specifies exactly how the element is inserted.</p>\n<p>Note that if you choose a class and it exists in more than one place on the page, the quotes will be duplicated. This \'feature\' could be used as a special effect but it\'s unlikely to be of any practical use.</p>\n\n<h3 id=\"insertmethod\"><code>insert_method</code></h3>\n<p>Once you have chosen an element to contain your quotes, you can specify how you want them injected into that element. The default is <code>append</code> to add the quotes to the end of whatever is already there.</p>\n<h4>Options</h4>\n<ul>\n<li><code>append</code><br>\nthe default: inserts the quotes after whatever is already inside the element</li>\n<li><code>prepend</code><br>\ninserts the quotes before whatever is already inside the element</li>\n<li><code>after</code><br>\nadds the quotes immediately after the chosen element</li>\n<li><code>before</code><br>\nputs the quotes immediately in front of the chosen element</li>\n<li><code>replaceWith</code><br>\nreplaces the given element entirely by the quotes. This may have odd results if you have elected to display more than one quote. See <a href=\"#limit\">limit</a></li>\n</ul>\n\n<h3 id=\"limit\"><code>limit</code></h3>\n<p>Specify the maximum number of quotes you wish to have rendered to the page. By default it is empty (or \"0\") which means \"all of them\".</p>\n\n<h3 id=\"random\"><code>random</code></h3>\n<p>Set this on to randomly pick <a href=\"#limit\">limit</a> quotes from the page and display them.</p>\n<h4>Options</h4>\n<ul>\n<li><code>0</code><br>\nthe default: off, i.e. pick the quotes off the page in the order they appear</li>\n<li><code>1</code><br>\non: pick random quotes from the page and display them. The number of quotes chosen is governed by the limit option</li>\n</ul>\n\n<h3 id=\"hideorig\"><code>hide_originals</code></h3>\n<p>Set this to remove the original quotes from the source document and only leave the pullquotes themselves. It has a few levels of severity.</p>\n<h4>Options</h4>\n<ul>\n<li><code>0</code><br>\nthe default: original quotes are displayed in context</li>\n<li><code>1</code><br>\noriginal quotes are removed from the document body, leaving just the pullquotes behind, but <em>only</em> for those quotes that are actually used</li>\n<li><code>2</code><br>\nall original quotes are removed from the document body, irrespective of whether the quote was used. Note that in the latter two cases the whole quote is removed, irrespective of any words <a href=\"#chopper\">chopped out</a>.</li>\n</ul>\n\n<p>Using <code>hide_originals=\"2\"</code> might be useful for a simple \"quote of the day\" where you can load all the quotes onto the page and have the plugin choose one randomly.</p>\n\n<h3 id=\"chopper\"><code>chopper</code></h3>\n<p>Sometimes quotes are too long to make into pullquotes and abridged versions work better. This options specifies a character sequence to allow words to be omitted from quotes; any words between pairs of character sequences defined by the <code>chopper</code> attribute will be removed from the quote. The default character sequence is <code>~~</code> (two tildes).</p>\n<p>The missing text is replaced with the character sequence defined by <a href=\"#omissions\">omissions</a>.</p>\n<p>For example, specifying the sentence:\n\n<blockquote><p>I cannot help it~~; reason has nothing to do with it; ~~I love her against reason.</p></blockquote>\n\n<p>Would render this to the page:</p>\n\n<pre>&lt;q class=\"pQuote\"&gt;I cannot help it &hellip; I love her\n     against reason&lt;/q&gt;</pre>\n\n<p>You can specify as many pairs of characters within a pullquote as you like and the text between each set will be omitted. If you do not like the character sequence or are using that particular sequence within your body text, change it to anything you like with this option. Once each quote has been generated, the character sequence is removed from the document body so it doesn\'t clutter up your beautiful prose!</p>\n\n<h3 id=\"omissions\"><code>omissions</code></h3>\n<p>As shown in the <a href=\"#chopper\">example for the chopper option</a>, omitted characters are replaced with whatever character entity or sequence is defined in this attribute. By default it is the ellipsis (&amp;hellip; or \'&hellip;\').</p>\n<p>Along with a few of the other attributes, this one is Multi-Lingual Publishing pack (MLP) aware so you can specify a different character sequence per language.</p>\n\n<h3 id=\"start\"><code>start</code></h3>\n<p>The character sequence to add to the start of every quote. By default this is empty because good browsers render the HTML <code>q</code> element with language-specific quote characters automatically. Should you wish to <a href=\"#wraptag\">change the wraptag</a> however, <code>start</code> and its corresponding friend <a href=\"#end\">end</a> may be useful. Note that browsers that support the CSS2 <code>before</code> and <code>after</code> pseudo-elements can also add characters via the <code>content:</code> property. This option is MLP-aware.</p>\n\n<h3 id=\"end\"><code>end</code></h3>\n<p>Complements <a href=\"#start\">start</a> and specifies the character sequence to add to the end of every quote. By default this is empty because good browsers render the HTML <code>q</code> element with language-specific quote characters automatically. This option is MLP-aware.</p>\n\n<h3 id=\"pos\"><code>pos</code></h3>\n<p>Although positioning can be done entirely in CSS (and probably should), there are times when it is useful to specify positions from the plugin. One such case is where you want the quote to be pulled out alongside the place that the full quote appears in context. The pos attribute helps you achieve this.</p>\n<p>It takes a pair of arguments, separated by a comma. If the option is omitted or malformed, it is disabled and positioning is therefore completely under CSS control. Otherwise, specify x and y co-ordinates of where you wish the pullquotes to appear. This option is best explained by example:</p>\n<p>(note: it is assumed that the pQuote element is assigned <code>position:absolute;</code> via CSS. See <a href=\"#styling\">styling</a> for examples)</p>\n\n<table id=\"egtab\">\n<tr><th>pos=</th><th>result</th><th>remarks</th></tr>\n<tr><td>\"50, 200\"</td><td>all quotes appear at 50px from the left edge and 200px from the top edge of the <a href=\"#container\">container</a></td><td>without any units specified, \'px\' is assumed</td></tr>\n<tr><td>\"10%, 150\"</td><td>quotes appear 10% from the left edge and 150px from the top edge</td><td>Resizing the horizontal axis of the browser window moves the quotes</td></tr>\n<tr><td>\"40pt, y\"</td><td>quotes appear 40pts from the left edge and at the same vertical position as the place the original quote appears</td><td>some variation in y position sometimes occurs depending on the containing element</td></tr>\n<tr><td>\"x-80, y+50\"</td><td>quotes appear 80px to the left of the original quote and 50px below it</td><td>you can use + - * or / to perform simple maths on x and y co-ordinates</td></tr>\n</table>\n\n<p>You can use any valid CSS unit of length measurement, such as %, px, em, ex, pt, cm and so on.</p>\n\n<h2><a id=\"examples\"></a>Examples</h2>\n<p></p>\n\n<h3 id=\"eg1\"><code>Example 1</code></h3>\n<pre>&lt;txp:smd_pullquote /&gt;</pre>\n\n<p>Vanilla usage: just puts the javascript on the page, scans for all class definitions labelled \'pq\' and creates pullquotes in HTML \'q\' tags. Style to taste.</p>\n\n<h3 id=\"eg2\"><code>Example 2</code></h3>\n<pre>&lt;txp:smd_pullquote wraptag=\"div\" start=\"&amp;laquo;\"\n     end=\"&amp;raquo;\" class=\"myQuotes\" /&gt;</pre>\n\n<p>Wraps each quote in a <code>&lt;div&gt;</code> tag, prepends each quote with a left-angle quote and appends each quote with a right-angle quote symbol. Sets the class attribute of the div tags to <code>myQuotes</code></p>\n\n<h3 id=\"eg3\"><code>Example 3</code></h3>\n<pre>&lt;txp:smd_pullquote wraptag=\"p\" search_class=\"AQuote\"\n     chopper=\"@DLM@\" omissions=\".....\" /&gt;</pre>\n\n<p>Searches the document for elements with a class of <code>AQuote</code> and omits words between pairs of <code>@DLM@</code>. Each omitted set of words is replaced with a series of dots.</p>\n\n<blockquote><p>&lt;span class=\"AQuote\"&gt;I cannot help it@DLM@; reason has nothing to do with it; @DLM@I love her against reason.&lt;/span&gt;</p></blockquote>\n\n<p>generates a pullquote like this:</p>\n\n<blockquote><p>&lt;p class=\"pQuote\"&gt;I cannot help it..... I love her against reason.&lt;/p&gt;</p></blockquote>\n\n<h3 id=\"eg4\"><code>Example 4</code></h3>\n<pre>&lt;txp:smd_pullquote jquery=\"\" jq_clashvar=\"jQuery\"\n     container=\"#quoteHolder\" insert_method=\"prepend\" /&gt;</pre>\n\n<p>Tells the plugin not to load the jquery library (the assumption being that it is already loaded) and to use the noConflict() variable name \'jQuery\' (if it has not already been set up it will be assigned). The quotes are then told to attach themselves to the element with an id of <code>#quoteHolder</code> and they are to be placed immediately in front of any content that may already be in the element.</p>\n\n<h3 id=\"eg5\"><code>Example 5</code></h3>\n<pre>&lt;txp:smd_pullquote limit=\"1\" random=\"1\" pos=\"60, 230\" /&gt;</pre>\n\n<p>Only shows 1 quote on the page (the limit attribute), telling the plugin to choose a random quote from those defined in the source and place it at a position 60px from the left edge and 230px from the top edge of the containing element (the default <code>&lt;body&gt;</code> in this case).</p>\n<p>Using <code>limit=\"2\" random=\"1\"</code> instead would pull two random quotes from the text and put them in exactly the same spot on the page. See <a href=\"#styling\">styling</a> for ways to separate them.</p>\n<p>Using <code>limit=\"2\" random=\"0\"</code> (or omitting random) would pull the first two quotes from the text and put them in exactly the same spot on the page.</p>\n\n<h2><a id=\"styling\"></a>Styling</h2>\n<p>To style quotes when using the <a href=\"#pos\">pos</a> attribute you can use something like this:</p>\n\n<pre>\n.pQuote {\n	position:absolute;\n	margin:1em;\n	padding:.5em;\n	font-size:1.1em;\n	text-align:right;\n	font-style:italic;\n	color:#222;\n	line-height:1.35;\n	width:120px;\n}\n</pre>\n\n<p>Add <code>top:</code> and <code>left:</code> definitions if you want to fix the location and only intend having 1 quote per page, or prefer not to use the <code>pos</code> attribute.</p>\n\n<p>For multiple, fixed-position quotes you could use this type of styling:</p>\n<pre>\n.pQuote {\n	position:absolute;\n	top:50px;\n	left:100px;\n}\n.pQuote + .pQuote {\n	top:450px;\n	right:30px;\n}\n</pre>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help a:link, #smd_help a:visited { color: #00c; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #079; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n#smd_help ul { list-style-type:square; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '// v0.1   Initial release\r\n// v0.1a  Removed bogus leading slash on <script> tag. Thanks sgoodwin\r\n\r\nfunction smd_pullquote($atts) {\r\n	require_plugin(\'smd_lib\');\r\n\r\n	// MLP support\r\n	global $smd_pquoLang;\r\n	$smd_pquo_str = array(\r\n		\'pq_search_class\' => \'pq\',\r\n		\'pq_class\' => \'pQuote\',\r\n		\'pq_start\' => \'\',\r\n		\'pq_end\' => \'\',\r\n		\'pq_omit\' => \'&hellip;\',\r\n	);\r\n	$smd_pquoLang = new smd_MLP(\'smd_pullquote\', \'smd_pquo\', $smd_pquo_str);\r\n\r\n	// Process the args\r\n	$insertMethods = array(\"append\", \"prepend\", \"after\", \"before\", \"replaceWith\");\r\n	extract(lAtts(array(\r\n		\'jquery\'				=> \'/textpattern/jquery.js\',\r\n		\'jq_clashvar\'		=> \'jq\',\r\n		\'container\'			=> \'body\',\r\n		\'insert_method\'	=> $insertMethods[0],\r\n		\'search_class\'		=> $smd_pquoLang->gTxt(\"pq_search_class\"),\r\n		\'class\'				=> $smd_pquoLang->gTxt(\"pq_class\"),\r\n		\'wraptag\'			=> \'q\',\r\n		\'limit\'				=> \'0\',\r\n		\'random\'				=> \'0\',\r\n		\'chopper\'			=> \'~~\',\r\n		\'start\'				=> $smd_pquoLang->gTxt(\"pq_start\"),\r\n		\'end\'					=> $smd_pquoLang->gTxt(\"pq_end\"),\r\n		\'omissions\'			=> $smd_pquoLang->gTxt(\"pq_omit\"),\r\n		\'hide_originals\'	=> \'0\',\r\n		\'pos\'					=> \'\',\r\n	), $atts));\r\n\r\n	$ret = \"\";\r\n	if (!empty($jquery)) {\r\n		$ret .= \'<script type=\"text/javascript\" src=\"\'.$jquery.\'\"></script>\';\r\n	}\r\n	$ret .= \'<script type=\"text/javascript\">\'.n;\r\n	$ret .= \'<!--\'.n;\r\n\r\n	if (!empty($jq_clashvar)) {\r\n		$ret .= \'if (typeof $\'.$jq_clashvar.\' == \\\'undefined\\\') {\'.n;\r\n		$ret .= \' var $\'.$jq_clashvar.\' = jQuery.noConflict();\'.n;\r\n		$ret .= \'}\'.n;\r\n	}\r\n	$jq_clashvar = \"$\".$jq_clashvar;\r\n\r\n	// Check relevant passed args are reasonable\r\n	$limit = (trim($limit) == \"\") ? 0 : $limit;\r\n	$container = (trim($container) == \"\") ? \"body\" : $container;\r\n	$insert_method = (in_array($insert_method, $insertMethods)) ? $insert_method : $insertMethods[0];\r\n\r\n	// Write the javascript to the page.\r\n	// Callback for array.sort() to randomly order the array\r\n	$ret .= \'function randOrder() {\'.n;\r\n	$ret .= \' return (Math.round(Math.random())-0.5);\'.n;\r\n 	$ret .= \'}\'.n;\r\n\r\n	// Choose N numbers from a list of M (ranging from 0 - M-1).\r\n	// If typ = \"rnd\", choose them randomly; if typ=\"all\" generate a complete range.\r\n	// Original random algorithm by Donald Knuth.\r\n	$ret .= \'function genList(M,N,typ) {\'.n;\r\n	$ret .= \' ary = [];\'.n;\r\n	$ret .= \' for (idx=0; idx<M && N>0; ++idx) {\'.n;\r\n	$ret .= \'  if ((Math.floor(Math.random()*(M-idx)))<N || typ == \"all\") {\'.n;\r\n	$ret .= \'   ary.push(idx);\'.n;\r\n	$ret .= \'   N=N-1;\'.n;\r\n	$ret .= \'  }\'.n;\r\n	$ret .= \' }\'.n;\r\n	$ret .= \' return(ary);\'.n;\r\n	$ret .= \'}\'.n;\r\n\r\n	// Find each quote, grab the text, chop out any omissions, make the pullquote,\r\n	// then remove the chopper symbols from the original text on the page\r\n	$ret .= \'function smd_pullquote() {\'.n;\r\n	$ret .= \' var snipReg = /\'.$chopper.\'.+?\'.$chopper.\'/gi;\'.n;\r\n	$ret .= \' var cleaReg = /\'.$chopper.\'/gi;\'.n;\r\n	$ret .= \' var numQuotes = \'. $jq_clashvar.\'(\".\'.$search_class.\'\").length;\'.n;\r\n	$ret .= \' var quotes = [];\'.n;\r\n	$ret .= \' var quoteCtr = 0;\'.n;\r\n	$ret .= \' var quotePos = [];\'.n;\r\n	$ret .= \' var lim = (\' .$limit. \' > numQuotes || \' .$limit. \' == 0) ? numQuotes : \' .$limit. \';\'.n;\r\n	$ret .= ($random==0) ? \' chosen = genList(numQuotes, lim, \"all\");\'.n : \' chosen = genList(numQuotes, lim, \"rnd\");\'.n;\r\n	$ret .= ($random==0) ? \'\' : \' chosen.sort(randOrder)\'.n;\r\n	$ret .= \' \' .$jq_clashvar. \'(\".\'.$search_class.\'\").each(function() {\'.n;\r\n	$ret .= \'  var $this = \'.$jq_clashvar.\'(this);\'.n;\r\n	$ret .= \'  txt = $this.text();\'.n;\r\n	$ret .= \'  rep = txt.replace(snipReg, \"\'.$omissions.\'\");\'.n;\r\n	$ret .= \'  quotes.push(rep);\'.n;\r\n	$ret .= \'  quotePos.push($this.offset());\'.n;\r\n	$ret .= \'  $this.text(txt.replace(cleaReg, \"\"));\'.n;\r\n	if ($hide_originals == \"1\") {\r\n		$ret .= \'  (chosen.exists(quoteCtr)) ? $this.empty() : \"\";\'.n;\r\n	} else if ($hide_originals == \"2\") {\r\n		$ret .= \'  $this.empty();\'.n;\r\n	}\r\n	$ret .= \'  quoteCtr++;\'.n;\r\n	$ret .= \'  });\'.n;\r\n\r\n	// Insert the pullquotes into the page content in the chosen order\r\n	$ret .= \' for (var idx = 0; idx < chosen.length; idx++) {\'.n;\r\n	$ret .= \'  \' .$jq_clashvar. \'(\"\'.$container.\'\").\' .$insert_method. \'(\\\'<\'.$wraptag. (($class==\"\") ? \"\" : \' class=\"\'.$class.\'\"\') . \'>\' .$start. \'\\\'+quotes[chosen[idx]]+\\\'\' .$end. \'</\'.$wraptag.\'>\\\');\'.n;\r\n\r\n	// Calculate the position if applicable\r\n	$top = \"\";\r\n	$left = \"\";\r\n	$units = \"px\";\r\n	$expr = \"[em|ex|px|in|cm|mm|pt|pc|%]\";\r\n	$uexpr = \"[^em|ex|px|in|cm|mm|pt|pc|%]\";\r\n	if ($pos != \"\") {\r\n		$posParts = explode(\",\", $pos);\r\n		if (count($posParts) == 2) {\r\n			$lunits = trim(ereg_replace($uexpr, \"\", $posParts[0]));\r\n			$tunits = trim(ereg_replace($uexpr, \"\", $posParts[1]));\r\n			$left = str_replace(\"x\", \"quotePos[chosen[idx]].left\", trim(ereg_replace($expr, \"\", $posParts[0])));\r\n			$top = str_replace(\"y\", \"quotePos[chosen[idx]].top\", trim(ereg_replace($expr, \"\", $posParts[1])));\r\n		} else {\r\n			$pos = \"\";\r\n		}\r\n	}\r\n	$ret .= ($pos==\"\") ? \"\" : \'  \' .$jq_clashvar. \'(\".\' .$class. \':eq(\"+idx+\")\").css({top: \'.$top.\'+\"\'.(($tunits!=\"\") ? $tunits : $units ).\'\", left: \'.$left.\'+\"\'.(($lunits==\"\") ? $units : $lunits ).\'\"});\'.n;\r\n	$ret .= \' }\'.n;\r\n\r\n	$ret .= \'}\'.n;\r\n\r\n	// Run smd_pullquote on DOM ready\r\n	$ret .= $jq_clashvar.\'(function() {\'.n;\r\n	$ret .= \' smd_pullquote();\'.n;\r\n	$ret .= \'});\'.n;\r\n	$ret .= \'-->\'.n;\r\n	$ret .= \'</script>\'.n;\r\n\r\n	return $ret;\r\n}', '// v0.1   Initial release\r\n// v0.1a  Removed bogus leading slash on <script> tag. Thanks sgoodwin\r\n\r\nfunction smd_pullquote($atts) {\r\n	require_plugin(\'smd_lib\');\r\n\r\n	// MLP support\r\n	global $smd_pquoLang;\r\n	$smd_pquo_str = array(\r\n		\'pq_search_class\' => \'pq\',\r\n		\'pq_class\' => \'pQuote\',\r\n		\'pq_start\' => \'\',\r\n		\'pq_end\' => \'\',\r\n		\'pq_omit\' => \'&hellip;\',\r\n	);\r\n	$smd_pquoLang = new smd_MLP(\'smd_pullquote\', \'smd_pquo\', $smd_pquo_str);\r\n\r\n	// Process the args\r\n	$insertMethods = array(\"append\", \"prepend\", \"after\", \"before\", \"replaceWith\");\r\n	extract(lAtts(array(\r\n		\'jquery\'				=> \'/textpattern/jquery.js\',\r\n		\'jq_clashvar\'		=> \'jq\',\r\n		\'container\'			=> \'body\',\r\n		\'insert_method\'	=> $insertMethods[0],\r\n		\'search_class\'		=> $smd_pquoLang->gTxt(\"pq_search_class\"),\r\n		\'class\'				=> $smd_pquoLang->gTxt(\"pq_class\"),\r\n		\'wraptag\'			=> \'q\',\r\n		\'limit\'				=> \'0\',\r\n		\'random\'				=> \'0\',\r\n		\'chopper\'			=> \'~~\',\r\n		\'start\'				=> $smd_pquoLang->gTxt(\"pq_start\"),\r\n		\'end\'					=> $smd_pquoLang->gTxt(\"pq_end\"),\r\n		\'omissions\'			=> $smd_pquoLang->gTxt(\"pq_omit\"),\r\n		\'hide_originals\'	=> \'0\',\r\n		\'pos\'					=> \'\',\r\n	), $atts));\r\n\r\n	$ret = \"\";\r\n	if (!empty($jquery)) {\r\n		$ret .= \'<script type=\"text/javascript\" src=\"\'.$jquery.\'\"></script>\';\r\n	}\r\n	$ret .= \'<script type=\"text/javascript\">\'.n;\r\n	$ret .= \'<!--\'.n;\r\n\r\n	if (!empty($jq_clashvar)) {\r\n		$ret .= \'if (typeof $\'.$jq_clashvar.\' == \\\'undefined\\\') {\'.n;\r\n		$ret .= \' var $\'.$jq_clashvar.\' = jQuery.noConflict();\'.n;\r\n		$ret .= \'}\'.n;\r\n	}\r\n	$jq_clashvar = \"$\".$jq_clashvar;\r\n\r\n	// Check relevant passed args are reasonable\r\n	$limit = (trim($limit) == \"\") ? 0 : $limit;\r\n	$container = (trim($container) == \"\") ? \"body\" : $container;\r\n	$insert_method = (in_array($insert_method, $insertMethods)) ? $insert_method : $insertMethods[0];\r\n\r\n	// Write the javascript to the page.\r\n	// Callback for array.sort() to randomly order the array\r\n	$ret .= \'function randOrder() {\'.n;\r\n	$ret .= \' return (Math.round(Math.random())-0.5);\'.n;\r\n 	$ret .= \'}\'.n;\r\n\r\n	// Choose N numbers from a list of M (ranging from 0 - M-1).\r\n	// If typ = \"rnd\", choose them randomly; if typ=\"all\" generate a complete range.\r\n	// Original random algorithm by Donald Knuth.\r\n	$ret .= \'function genList(M,N,typ) {\'.n;\r\n	$ret .= \' ary = [];\'.n;\r\n	$ret .= \' for (idx=0; idx<M && N>0; ++idx) {\'.n;\r\n	$ret .= \'  if ((Math.floor(Math.random()*(M-idx)))<N || typ == \"all\") {\'.n;\r\n	$ret .= \'   ary.push(idx);\'.n;\r\n	$ret .= \'   N=N-1;\'.n;\r\n	$ret .= \'  }\'.n;\r\n	$ret .= \' }\'.n;\r\n	$ret .= \' return(ary);\'.n;\r\n	$ret .= \'}\'.n;\r\n\r\n	// Find each quote, grab the text, chop out any omissions, make the pullquote,\r\n	// then remove the chopper symbols from the original text on the page\r\n	$ret .= \'function smd_pullquote() {\'.n;\r\n	$ret .= \' var snipReg = /\'.$chopper.\'.+?\'.$chopper.\'/gi;\'.n;\r\n	$ret .= \' var cleaReg = /\'.$chopper.\'/gi;\'.n;\r\n	$ret .= \' var numQuotes = \'. $jq_clashvar.\'(\".\'.$search_class.\'\").length;\'.n;\r\n	$ret .= \' var quotes = [];\'.n;\r\n	$ret .= \' var quoteCtr = 0;\'.n;\r\n	$ret .= \' var quotePos = [];\'.n;\r\n	$ret .= \' var lim = (\' .$limit. \' > numQuotes || \' .$limit. \' == 0) ? numQuotes : \' .$limit. \';\'.n;\r\n	$ret .= ($random==0) ? \' chosen = genList(numQuotes, lim, \"all\");\'.n : \' chosen = genList(numQuotes, lim, \"rnd\");\'.n;\r\n	$ret .= ($random==0) ? \'\' : \' chosen.sort(randOrder)\'.n;\r\n	$ret .= \' \' .$jq_clashvar. \'(\".\'.$search_class.\'\").each(function() {\'.n;\r\n	$ret .= \'  var $this = \'.$jq_clashvar.\'(this);\'.n;\r\n	$ret .= \'  txt = $this.text();\'.n;\r\n	$ret .= \'  rep = txt.replace(snipReg, \"\'.$omissions.\'\");\'.n;\r\n	$ret .= \'  quotes.push(rep);\'.n;\r\n	$ret .= \'  quotePos.push($this.offset());\'.n;\r\n	$ret .= \'  $this.text(txt.replace(cleaReg, \"\"));\'.n;\r\n	if ($hide_originals == \"1\") {\r\n		$ret .= \'  (chosen.exists(quoteCtr)) ? $this.empty() : \"\";\'.n;\r\n	} else if ($hide_originals == \"2\") {\r\n		$ret .= \'  $this.empty();\'.n;\r\n	}\r\n	$ret .= \'  quoteCtr++;\'.n;\r\n	$ret .= \'  });\'.n;\r\n\r\n	// Insert the pullquotes into the page content in the chosen order\r\n	$ret .= \' for (var idx = 0; idx < chosen.length; idx++) {\'.n;\r\n	$ret .= \'  \' .$jq_clashvar. \'(\"\'.$container.\'\").\' .$insert_method. \'(\\\'<\'.$wraptag. (($class==\"\") ? \"\" : \' class=\"\'.$class.\'\"\') . \'>\' .$start. \'\\\'+quotes[chosen[idx]]+\\\'\' .$end. \'</\'.$wraptag.\'>\\\');\'.n;\r\n\r\n	// Calculate the position if applicable\r\n	$top = \"\";\r\n	$left = \"\";\r\n	$units = \"px\";\r\n	$expr = \"[em|ex|px|in|cm|mm|pt|pc|%]\";\r\n	$uexpr = \"[^em|ex|px|in|cm|mm|pt|pc|%]\";\r\n	if ($pos != \"\") {\r\n		$posParts = explode(\",\", $pos);\r\n		if (count($posParts) == 2) {\r\n			$lunits = trim(ereg_replace($uexpr, \"\", $posParts[0]));\r\n			$tunits = trim(ereg_replace($uexpr, \"\", $posParts[1]));\r\n			$left = str_replace(\"x\", \"quotePos[chosen[idx]].left\", trim(ereg_replace($expr, \"\", $posParts[0])));\r\n			$top = str_replace(\"y\", \"quotePos[chosen[idx]].top\", trim(ereg_replace($expr, \"\", $posParts[1])));\r\n		} else {\r\n			$pos = \"\";\r\n		}\r\n	}\r\n	$ret .= ($pos==\"\") ? \"\" : \'  \' .$jq_clashvar. \'(\".\' .$class. \':eq(\"+idx+\")\").css({top: \'.$top.\'+\"\'.(($tunits!=\"\") ? $tunits : $units ).\'\", left: \'.$left.\'+\"\'.(($lunits==\"\") ? $units : $lunits ).\'\"});\'.n;\r\n	$ret .= \' }\'.n;\r\n\r\n	$ret .= \'}\'.n;\r\n\r\n	// Run smd_pullquote on DOM ready\r\n	$ret .= $jq_clashvar.\'(function() {\'.n;\r\n	$ret .= \' smd_pullquote();\'.n;\r\n	$ret .= \'});\'.n;\r\n	$ret .= \'-->\'.n;\r\n	$ret .= \'</script>\'.n;\r\n\r\n	return $ret;\r\n}', '8248a4d69163e7ba7a10a0bef90fcade', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_horizon', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.1', 'Next/previous article without restrictions', '<div id=\"smd_help\">\n\n	<h1 id=\"top\">smd_horizon</h1>\n\n	<p>The existing tags <code>&lt;txp:next_title /&gt;</code>, <code>&lt;txp:link_to_next /&gt;</code> and their <code>prev</code> counterparts cease to function when they reach the first/last posted article in a section. If you have ever wanted to navigate off the <span class=\"caps\">TXP</span> grid, this plugin can help.</p>\n\n	<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>Ability to link to articles that are to be published in future; <code>&lt;txp:next/prev_title /&gt;</code> and <code>&lt;txp:link_to_next/prev&gt;</code> work beyond the existing Posted range</li>\n		<li>Detection of when you are about to &#8220;fall off the end&#8221; of a section or category, in either direction</li>\n		<li>Entire adjacent article contents available in the current article for your (mis)use so you can tease people with what&#8217;s coming next or use any article content as a link</li>\n		<li>Navigate seamlessly between many sections, categories or authors</li>\n	</ul>\n\n	<h2 id=\"author\">Author / credits</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>.</p>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p class=\"required\">Requires Textpattern 4.0.7+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1079/smd_horizon\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=30464\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n	<h2 id=\"np\">Tags: <code>&lt;txp:smd_next&gt;</code> / <code>&lt;txp:smd_prev&gt;</code></h2>\n\n	<p>Wrap either <code>&lt;txp:smd_next&gt;</code> or <code>&lt;txp:smd_prev&gt;</code> around existing <code>&lt;txp:link_to_next/prev /&gt;</code> and <code>&lt;txp:next/prev_title /&gt;</code> tags to be able to navigate multiple sections or future articles.</p>\n\n	<h3 class=\"atts \" id=\"npatts\">Attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">section</span> : navigate among articles in this list of sections. Default: current section</li>\n		<li><span class=\"atnm\">category</span> : navigate among articles having this list of categories. Default: current category</li>\n		<li><span class=\"atnm\">author</span> : navigate among articles by this list of author IDs. Default: current author</li>\n		<li><span class=\"atnm\">realname</span> : navigate among articles by this list of author Real Names. Default: unset. Note this adds one database query to the page so if you can possibly use <code>author</code> instead, do so</li>\n		<li><span class=\"atnm\">status</span> : take articles in this list of status into consideration. Use either the name (<code>live</code>, <code>hidden</code>, etc) or their equivalent numeric values. Default: <code>live</code>. Note: you still cannot actually <em>view</em> (i.e. navigate to) articles that are not Live or Sticky but you can see their contents from the current article (i.e. preview them: see <a href=\"#eg3\">example 3</a>)</li>\n		<li><span class=\"atnm\">time</span> : choose which timeframe your articles should be in. Either <code>any</code>, <code>future</code>, or <code>past</code>. Default: <code>any</code></li>\n		<li><span class=\"atnm\">datasort</span> : order the articles by these data items<sup class=\"footnote\"><a href=\"#fn172734724149eb64524a198\">1</a></sup>. You should not normally need this as it automatically sorts based on your section, category and author filters. Default: <code>section, category1, category2, author</code>. Note that unlike regular sort options this does <em>not</em> take <code>asc</code> or <code>desc</code>: the sort order is determined by whether you are using smd_prev or smd_next</li>\n		<li><span class=\"atnm\">timesort</span> : order articles by these time options<sup class=\"footnote\"><a href=\"#fn172734724149eb64524a198\">1</a></sup>. Can be any of <code>posted</code>, <code>lastmod</code> or <code>expires</code>. Default: <code>posted</code></li>\n		<li><span class=\"atnm\">form</span> : if you prefer to use a form instead of the container to hold your markup and tags, specify it here. Default: unset.</li>\n	</ul>\n\n	<p id=\"fn172734724149eb64524a198\" class=\"footnote\"><sup>1</sup> The reason there are two types of sort options is because the &#8216;datasort&#8217; is applied first. Thus, in the case of linking among multiple sections, articles are always ordered by date <em>within</em> a section. If this were not the case, your articles might be muddled up and it would be very difficult to know when you have reached the &#8216;end&#8217; of a section or category.</p>\n\n	<h2 id=\"linkto\">Tags: <code>&lt;txp:smd_link_to_next&gt;</code> / <code>&lt;txp:smd_link_to_prev&gt;</code></h2>\n\n	<p>A drop-in replacement for the built-in navigation tags, with a few additional attributes:</p>\n\n	<ul>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag, without its brackets, to wrap round the link, e.g. <code>wraptag=&quot;div&quot;</code>. If used as a Single tag, this attribute is ignored. Default: unset</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> class name to apply to the wraptag. If no wraptag is given, the class is applied directly to the link itself. If used as a Single tag, this attribute is ignored. Default: unset</li>\n		<li><span class=\"atnm\">urlvars</span> : a list of <span class=\"caps\">URL</span> variable names to add to the generated link. Default: unset</li>\n	</ul>\n\n	<p>The <span class=\"caps\">URL</span> variables may be derived from the current <span class=\"caps\">URL</span> line or may be set like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_link_to_next urlvars=&quot;c, id, myvar=12&quot;&gt;\n  &lt;txp:title /&gt;\n&lt;/txp:link_to_next&gt;\n</code></pre>\n\n	<p>Thus, <code>c</code> and <code>id</code> will be read from the <span class=\"caps\">URL</span> and passed forward, whereas <code>myvar</code> will be added to the <span class=\"caps\">URL</span> and initialized with a value of <code>12</code> if it does not already exist. If either <code>c</code> or <code>id</code> are missing they will not be passed in the link. If <code>myvar</code> changes the <span class=\"caps\">URL</span>&#8217;s value will persist, however if it is removed from the <span class=\"caps\">URL</span> it will be reinstated when you navigate to another article, and it will be reset to <code>12</code>.</p>\n\n	<p>Notes:</p>\n\n	<ul>\n		<li>the <span class=\"caps\">URL</span> variables and values are read from both <span class=\"caps\">GET</span> and <span class=\"caps\">POST</span> submissions; <span class=\"caps\">POST</span> overrides <span class=\"caps\">GET</span> if the names clash</li>\n		<li>you can use the shorthand <code>SMD_ALL</code> to read all current <span class=\"caps\">URL</span> variables</li>\n		<li>you may add <code>:ESCAPE</code> to any variable name (including <span class=\"caps\">SMD</span>_ALL) to have <span class=\"caps\">HTML</span> entities escaped</li>\n		<li>you may add <code>:FORCE</code> to any variable name (including <span class=\"caps\">SMD</span>_ALL) to include the variable in the link even if it is empty</li>\n		<li>you may add <code>:TAG_PRIORITY</code> to any variable name (including <span class=\"caps\">SMD</span>_ALL) so any values you may have given in the tag are used regardless if the same variable name exists in the <span class=\"caps\">URL</span>. Without this option, &#8216;<span class=\"caps\">URL</span> priority&#8217; is assumed, so if a variable of the same name exists and is used, your given value will be ignored. <span class=\"caps\">TAG</span>_PRIORITY is useful for making sure a variable exists, is initialized to a known value and remains at that value, even if the variable is altered by the visitor or removed from the <span class=\"caps\">URL</span></li>\n		<li>the above three modifiers can be used in combination if you wish</li>\n	</ul>\n\n	<h2 id=\"ifend\">Tag: <code>&lt;txp:smd_if_end&gt;</code></h2>\n\n	<p>Anything inside this conditional tag will only be displayed if the end of current postable articles has been reached. The tag can look for the &#8216;end&#8217; of a variety of things governed by the <code>type</code> attribute:</p>\n\n	<ul>\n		<li><span class=\"atnm\">type</span> : can be any of <code>list</code>, <code>category</code>, <code>category1</code>, <code>category2</code>, <code>author</code> or <code>section</code>. Default: <code>list</code></li>\n		<li><span class=\"atnm\">logic</span> : can be <code>or</code> which means that if <em>any</em> of the items reach their end the container will be triggered; or it could be <code>and</code> which means that <em>all</em> of the items have to end simultaneously before the container will fire</li>\n	</ul>\n\n	<p>Use this to take action and display something different if you wish to differentiate between &#8216;future&#8217; and &#8216;current&#8217; articles or if you have reached the end of a list of sections.</p>\n\n	<h2 id=\"ifstart\">Tag: <code>&lt;txp:smd_if_start&gt;</code></h2>\n\n	<p>Whatever is inside this conditional tag will only be displayed if the beginning of current postable articles has been reached. The tag can look for the &#8216;start&#8217; of a variety of things governed by the <code>type</code> attribute:</p>\n\n	<ul>\n		<li><span class=\"atnm\">type</span> : can be any of <code>list</code>, <code>category</code>, <code>category1</code>, <code>category2</code>, <code>author</code> or <code>section</code>. Default: <code>list</code></li>\n		<li><span class=\"atnm\">logic</span> : can be <code>or</code> which means that if <em>any</em> of the items have reached the beginning, the container will trigger; or it could be <code>and</code> which means that <em>all</em> of the items have to be at their respective start points simultaneously before the container will fire</li>\n	</ul>\n\n	<p>This could be used to offer &#8220;wraparound&#8221; navigation so if you click &#8220;previous&#8221; when you are at the first article you can perhaps take visitors to the last article, or maybe to the most recent article in another section.</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<p class=\"required\">Important: akin to the built-in tags, smd_horizon is limited to use in an individual article context and will either throw a warning or produce weird results if used in an article list.</p>\n\n	<h3 id=\"eg1\">Example 1: <span class=\"caps\">URL</span> var persistence and wrapping</h3>\n\n	<p>Can be used as a drop-in replacement for <code>&lt;txp:link_to_....&gt;</code> but with the extra ability to apply wraptag and class. Also, the <span class=\"caps\">URL</span> variable <code>uname</code> is passed along from article to article, if it is used in the <span class=\"caps\">URL</span>.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n  &lt;txp:smd_link_to_prev wraptag=&quot;div&quot;\n     class=&quot;nav_prev&quot; urlvars=&quot;uname&quot;&gt;&amp;#171;\n     &lt;txp:prev_title/&gt;&lt;/txp:smd_link_to_prev&gt;\n  &lt;txp:smd_link_to_next wraptag=&quot;div&quot;\n     class=&quot;nav_next&quot;\n     urlvars=&quot;uname&quot;&gt;&lt;txp:next_title/&gt;\n     &amp;#187;&lt;/txp:smd_link_to_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<h3 id=\"eg2\">Example 2: Navigating to future articles</h3>\n\n	<p>Enhance the standard link_to_next/prev tags by wrapping smd_next/smd_prev around them. With its default setting <code>time=&quot;any&quot;</code>, you can allow visitors to navigate to future articles either using the standard link_to_next/prev tags (as used in this example) or via the smd_link_to_next/prev tags.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev&gt;\n      &lt;txp:link_to_prev&gt;\n         &amp;#171; &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next&gt;\n      &lt;txp:link_to_next&gt;\n         &lt;txp:next_title/&gt; &amp;#187;\n      &lt;/txp:link_to_next&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<h3 id=\"eg3\">Example 3: Sneak peek of unpublished articles</h3>\n\n	<p>If navigating directly to future articles is not your idea of fun, how about offering a sneak preview of the next chapter of your book that you are serialising?</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:title /&gt;\n   &lt;txp:body /&gt;\n   &lt;txp:smd_if_end type=&quot;section&quot;&gt;\n      &lt;txp:smd_next&gt;\n         &lt;h3&gt;Coming up next week...&lt;/h3&gt;\n         &lt;txp:excerpt /&gt;\n      &lt;/txp:smd_next&gt;\n      &lt;txp:link_to_prev&gt;\n         &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n   &lt;txp:else /&gt;\n      &lt;txp:link_to_prev&gt;\n         &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n      &lt;txp:link_to_next&gt;\n         &lt;txp:next_title/&gt;\n      &lt;/txp:link_to_next&gt;\n   &lt;/txp:smd_if_end&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>Imagine you have each chapter as an article and have published, say, the first 3 chapters. You have written the 4th chapter but have set its posted date to next week. Your visitors can read and navigate through chapters 1, 2, and 3, as they normally would with any <span class=\"caps\">TXP</span> articles. When they reach Chapter 3, they are shown the excerpt from the unpublished article to whet their appetites.</p>\n\n	<p>One side-effect of <span class=\"caps\">TXP</span>&#8217;s content handling is that future articles can still be displayed in the browser if a visitor guesses the <span class=\"caps\">URL</span>. For serialised article titles such as <em>chapter-1</em>, <em>chapter-2</em>, and so on, it is not a great leap of faith for someone to gain access to your future articles.</p>\n\n	<p>With smd_horizon you can circumvent this. Set your Chapter 4, future article to the &#8216;hidden&#8217; status and add <code>status=&quot;live, hidden&quot;</code> to your <code>&lt;txp:smd_next&gt;</code> tag. You can still offer &#8216;previews&#8217; of the content.</p>\n\n	<h3 id=\"eg4\">Example 4: Make next/prev links using images</h3>\n\n	<p>Article content can be anything from the article; not just its excerpt. For example, category and custom field contents are all available. Say you published an online comic; you can even preview the article image if you wish.</p>\n\n	<p>With this example, article images are used as links to the next/previous article. It also maintains the <span class=\"caps\">URL</span> variables <code>m</code> and <code>y</code> so that a nearby calendar on the same page remains showing the month and year the visitor has chosen:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n  &lt;txp:smd_prev time=&quot;past&quot;&gt;\n    &lt;txp:smd_link_to_prev urlvars=&quot;m,y&quot;&gt;\n      &lt;txp:article_image thumbnail=&quot;1&quot; /&gt;\n    &lt;/txp:smd_link_to_prev&gt;\n  &lt;/txp:smd_prev&gt;\n  &lt;txp:smd_next time=&quot;past&quot;&gt;\n    &lt;txp:smd_link_to_next urlvars=&quot;m,y&quot;&gt;\n      &lt;txp:article_image thumbnail=&quot;1&quot; /&gt;\n    &lt;/txp:smd_link_to_next&gt;\n  &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>If you wanted to use the title as a fallback in case an article image wasn&#8217;t assigned, you can use <span class=\"caps\">TXP</span> 4.0.9&#8217;s <code>&lt;txp:if_article_image /&gt;</code> tag:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n  &lt;txp:smd_prev time=&quot;past&quot;&gt;\n    &lt;txp:smd_link_to_prev urlvars=&quot;m,y&quot;&gt;\n      &lt;txp:if_article_image&gt;\n         &lt;txp:article_image thumbnail=&quot;1&quot; /&gt;\n      &lt;txp:else /&gt;\n         &lt;txp:title /&gt;\n      &lt;/txp:if_article_image&gt;\n    &lt;/txp:smd_link_to_prev&gt;\n  &lt;/txp:smd_prev&gt;\n  &lt;txp:smd_next time=&quot;past&quot;&gt;\n    &lt;txp:smd_link_to_next urlvars=&quot;m,y&quot;&gt;\n      &lt;txp:if_article_image&gt;\n         &lt;txp:article_image thumbnail=&quot;1&quot; /&gt;\n      &lt;txp:else /&gt;\n         &lt;txp:title /&gt;\n      &lt;/txp:if_article_image&gt;\n    &lt;/txp:smd_link_to_next&gt;\n  &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<h3 id=\"eg5\">Example 5: Multiple section navigation</h3>\n\n	<p>Navigate over more than one section and take action when you reach either end of the list.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev section=&quot;articles, about&quot;&gt;\n      &lt;txp:smd_if_start type=&quot;list&quot;&gt;\n      The articles begin here\n      &lt;/txp:smd_if_start&gt;\n      &lt;txp:link_to_prev&gt;\n         &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next section=&quot;articles, about&quot;&gt;\n      &lt;txp:smd_if_end type=&quot;list&quot;&gt;\n      The end of the road\n      &lt;/txp:smd_if_end&gt;\n      &lt;txp:link_to_next&gt;\n         &lt;txp:next_title/&gt;\n      &lt;/txp:link_to_next&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>Notes:</p>\n\n	<ul>\n		<li> this is achieved by simply wrapping the standard link_to_next/prev tags with smd_next and smd_prev. No other trickery involved</li>\n		<li>if you are using smd_next/prev to iterate over a list of categories or authors, you can detect the end of those lists as well using the same <code>type=&quot;list&quot;</code> syntax. If you are iterating over more than one item at once the plugin has no way of knowing which of the &#8216;last&#8217; items it has reached (section, category, author&#8230;) so it&#8217;ll be the very last one that triggers it</li>\n	</ul>\n\n	<h3 id=\"eg6\">Example 6: At the end of each section, do&#8230;</h3>\n\n	<p>An extension of <a href=\"#eg5\">example 5</a>, this one detects when you reach the end of one of the sections in your list and displays an appropriate message to guide you onwards.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev section=&quot;articles, about&quot;&gt;\n      &lt;txp:smd_if_start type=&quot;section&quot;&gt;\n         &lt;txp:link_to_prev&gt;\n            Step back into &lt;txp:section /&gt;\n         &lt;/txp:link_to_prev&gt;\n      &lt;txp:else /&gt;\n         &lt;txp:link_to_prev&gt;\n            &lt;txp:prev_title/&gt;\n         &lt;/txp:link_to_prev&gt;\n      &lt;/txp:smd_if_start&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next section=&quot;articles, about&quot;&gt;\n      &lt;txp:smd_if_end type=&quot;section&quot;&gt;\n         &lt;txp:link_to_next&gt;\n            Move onwards to &lt;txp:section /&gt;\n         &lt;/txp:link_to_next&gt;\n      &lt;txp:else /&gt;\n         &lt;txp:link_to_next&gt;\n            &lt;txp:next_title/&gt;\n         &lt;/txp:link_to_next&gt;\n      &lt;/txp:smd_if_end&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<h3 id=\"eg7\">Example 7: Nesting smd_if_start/end</h3>\n\n	<p>A further extension of <a href=\"#eg6\">example 6</a>:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev section=\"articles, about, products\"&gt;\n      &lt;txp:smd_if_start type=\"list\"&gt;\n      The articles begin here\n      &lt;txp:else /&gt;\n         &lt;txp:smd_if_start type=\"section\"&gt;\n            (previous section)\n         &lt;/txp:smd_if_start&gt;\n      &lt;/txp:smd_if_start&gt;\n      &lt;txp:link_to_prev&gt;\n         &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next section=\"articles, about, products\"&gt;\n      &lt;txp:link_to_next&gt;\n         &lt;txp:next_title/&gt;\n      &lt;/txp:link_to_next&gt;\n      &lt;txp:smd_if_end type=\"list\"&gt;\n      The end of the road\n      &lt;txp:else /&gt;\n         &lt;txp:smd_if_end type=\"section\"&gt;\n             (next section)\n         &lt;/txp:smd_if_end&gt;\n      &lt;/txp:smd_if_end&gt;\n   &lt;/txp:smd_next>&lt;br /&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n	<p>Notice that:</p>\n\n	<ul>\n		<li>detection of the start/end of the list of sections occurs <em>first</em></li>\n		<li>if that fails, we check if we are at the end of a conventional section. If we did not do this nested inside the &#8216;else&#8217;, the &#8216;end of section&#8217; trigger would be true at the extremities of the section list as well</li>\n		<li>there is no conditional surrounding the link_to_next/prev because they will automatically return nothing when the end of the list is reached</li>\n	</ul>\n\n	<h3 id=\"eg8\">Example 8: At end of each category1, do&#8230;</h3>\n\n	<p>If you order your articles by category1 you can identify when the next (or previous) category is about to be reached. This example just shows a message when a different category1 is detected:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev&gt;\n      &lt;txp:link_to_prev&gt;\n         &amp;#171; &lt;txp:prev_title/&gt;\n      &lt;/txp:link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next&gt;\n      &lt;txp:link_to_next&gt;\n         &lt;txp:next_title/&gt; &amp;#187;\n      &lt;/txp:link_to_next&gt;\n      &lt;txp:smd_if_end type=&quot;category1&quot;&gt;\n         Next cat: &lt;txp:category1 link=&quot;1&quot; /&gt;\n      &lt;/txp:smd_if_end&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>Notes:</p>\n\n	<ul>\n		<li>Use <code>type=&quot;category1, category2&quot;</code> (or <code>type=&quot;category&quot;</code>) to take action if <em>either</em> category changes in the next article</li>\n		<li>Use <code>type=&quot;category1, category2&quot; logic=&quot;and&quot;</code> to take action if <em>both</em> categories change in the next article</li>\n		<li>You can use the shorthand <code>cat1</code> and <code>cat2</code> if you don&#8217;t like spelling out <code>category1</code> and <code>category2</code> every time :-)</li>\n	</ul>\n\n	<h3 id=\"eg9\">Example 9: Loop over an author&#8217;s articles</h3>\n\n	<p>No matter if an author has written stuff in multiple sections, you can iterate over them all using smd_link_to_next/prev.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;txp:smd_prev author=&quot;Bloke&quot; section=&quot;articles,about&quot;&gt;\n      &lt;txp:smd_link_to_prev&gt;\n         &lt;txp:title /&gt;\n      &lt;/txp:smd_link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next author=&quot;Bloke&quot; section=&quot;articles,about&quot;&gt;\n      &lt;txp:smd_link_to_next&gt;\n         &lt;txp:title /&gt;\n      &lt;/txp:smd_link_to_next&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>Instead of hard-coding the author name, if you wish to use the current author, replace <code>author=&quot;Bloke&quot;</code> with <code>realname=&#39;&lt;txp:author /&gt;&#39;</code>. Very useful in magazine-style sites for showing &#8216;next article by this author&#8217; links.</p>\n\n	<p>Note that this example does <em>not</em> work with <span class=\"caps\">TXP</span>&#8217;s built-in link_to_next/prev tags because they still &#8216;see&#8217; other articles in the same section, irrespective of author (and in fact category, so the same restriction applies there).</p>\n\n	<h3 id=\"eg10\">Example 10: Loop over many author&#8217;s articles</h3>\n\n	<p>And across multiple sections too:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;p&gt;Current article by: &lt;txp:author /&gt;&lt;/p&gt;\n   &lt;txp:smd_prev author=&quot;Stef, John&quot;\n     section=&quot;articles,products&quot;\n     datasort=&quot;author, section&quot;&gt;\n      &lt;txp:smd_link_to_prev&gt;\n         &lt;txp:title /&gt; (by &lt;txp:author /&gt;)\n      &lt;/txp:smd_link_to_prev&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next author=&quot;Stef, John&quot;\n     section=&quot;articles, products&quot;\n     datasort=&quot;author, section&quot;&gt;\n      &lt;txp:smd_link_to_next&gt;\n         &lt;txp:title /&gt; (by &lt;txp:author /&gt;)\n      &lt;/txp:smd_link_to_next&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>Notice that:</p>\n\n	<ul>\n		<li><code>datasort</code> is used to order by author first, then section</li>\n		<li>iterating over author lists <em>must</em> be done inside smd_next/prev tags</li>\n	</ul>\n\n	<h3 id=\"eg11\">Example 11: Detect when an author change occurs</h3>\n\n	<p>An extension of <a href=\"#eg10\">example 10</a> that shows a notification every time you are about to &#8220;step off&#8221; an author&#8217;s article list into the next or previous author:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:if_individual_article&gt;\n   &lt;p&gt;Current article by: &lt;txp:author /&gt;&lt;/p&gt;\n   &lt;txp:smd_prev author=&quot;Stef, Dale, Jakob&quot;\n     section=&quot;articles, products&quot;\n     datasort=&quot;author, section&quot;&gt;\n      &lt;txp:smd_link_to_prev&gt;\n         &lt;txp:title /&gt; (by &lt;txp:author /&gt;)\n      &lt;/txp:smd_link_to_prev&gt;\n      &lt;txp:smd_if_start type=&quot;author&quot;&gt;\n      (Previous author)\n      &lt;/txp:smd_if_start&gt;\n   &lt;/txp:smd_prev&gt;\n   &lt;txp:smd_next author=&quot;Stef, Dale, Jakob&quot;\n     section=&quot;articles, products&quot;\n     datasort=&quot;author, section&quot;&gt;\n      &lt;txp:smd_link_to_next&gt;\n         &lt;txp:title /&gt; (by &lt;txp:author /&gt;)\n      &lt;/txp:smd_link_to_next&gt;\n      &lt;txp:smd_if_end type=&quot;author&quot;&gt;\n      (Next author)\n      &lt;/txp:smd_if_end&gt;\n   &lt;/txp:smd_next&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n#smd_help ul { list-style-type:square; }\n#smd_help .required {color:red;}\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '// Public interfaces: convenience functions\r\nfunction smd_prev($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_nearest($atts, $thing);\r\n}\r\nfunction smd_next($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_nearest($atts, $thing);\r\n}\r\nfunction smd_link_to_prev($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_link_to($atts, $thing);\r\n}\r\nfunction smd_link_to_next($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_link_to($atts, $thing);\r\n}\r\nfunction smd_if_start($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_if_horizon($atts, $thing);\r\n}\r\nfunction smd_if_end($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_if_horizon($atts, $thing);\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_if_horizon($atts, $thing) {\r\n	global $pretext, $thisarticle, $thiscategory, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'type\' => \'list\',\r\n		\'logic\' => \'or\',\r\n		\'dir\' => \'next\',\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	$itout = array(); // For debug only\r\n	$type = do_list($type);\r\n	$out = array();\r\n	foreach ($type as $item) {\r\n		if ($debug) {\r\n			$itout[] = $item;\r\n		}\r\n		switch ($item) {\r\n			case \'list\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (empty($smd_last)) ? true : false;\r\n					} else {\r\n						$out[] = (empty($smd_first)) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'category\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && ($smd_last[\'category1\'] != $thisarticle[\'category1\'] || $smd_last[\'category2\'] != $thisarticle[\'category2\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && ($smd_first[\'category1\'] != $thisarticle[\'category1\'] || $smd_first[\'category2\'] != $thisarticle[\'category2\'])) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'author\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'author\'] != $thisarticle[\'authorid\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'author\'] != $thisarticle[\'authorid\']) ? true : false;\r\n					}\r\n				} else {\r\n					// Not possible since author lists are not permitted in TXP\r\n				}\r\n				break;\r\n			case \'cat1\':\r\n			case \'category1\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'category1\'] != $thisarticle[\'category1\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'category1\'] != $thisarticle[\'category1\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'cat2\':\r\n			case \'category2\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'category2\'] != $thisarticle[\'category2\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'category2\'] != $thisarticle[\'category2\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'section\':\r\n			default:\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'section\'] != $thisarticle[\'section\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'section\'] != $thisarticle[\'section\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = empty($pretext[\'next_id\']) ? true : false;\r\n					} else {\r\n						$out[] = empty($pretext[\'prev_id\']) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n		}\r\n	}\r\n	if ($debug) {\r\n		echo \'++ TEST RESULTS ++\';\r\n		dmp($itout);\r\n		dmp($out);\r\n	}\r\n	$res = ($out) ? true : false;\r\n	if (strtolower($logic) == \"and\" && in_array(false, $out)) {\r\n		$res = false;\r\n	}\r\n	if (strtolower($logic) == \"or\" && !in_array(true, $out)) {\r\n		$res = false;\r\n	}\r\n	if ($debug) {\r\n		echo \'++ FINAL RESULT ++\';\r\n		dmp($res);\r\n	}\r\n	return parse(EvalElse($thing, $res));\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_nearest($atts, $thing) {\r\n	global $pretext, $thisarticle, $thiscategory, $prefs, $next_id, $prev_id, $next_title, $prev_title, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'section\' => $pretext[\'s\'],\r\n		\'category\' => $pretext[\'c\'],\r\n		\'author\' => $pretext[\'author\'],\r\n		\'realname\' => \'\',\r\n		\'status\' => \'4\',\r\n		\'time\' => \'any\', // any, future, past\r\n		\'datasort\' => \'section, category1, category2, author\',\r\n		\'timesort\' => \'posted\',\r\n		\'form\' => \'\',\r\n		\'dir\' => \'next\', // Set by wrapper tags\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	extract($prefs);\r\n	$smd_in_nearest = true;\r\n\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$expired = ($publish_expired_articles) ? \'\' : \' AND (now() <= Expires or Expires = \'.NULLDATETIME.\')\';\r\n	$safe_name = safe_pfx(\'textpattern\');\r\n\r\n	// Filters\r\n	$catSQL = $secSQL = $authSQL = \'\';\r\n	if($category) {\r\n		$catSQL = doQuote(join(\"\',\'\", doSlash(do_list($category))));\r\n		$catSQL = \' AND ( Category1 IN (\'.$catSQL.\') OR Category2 IN (\'.$catSQL.\') ) \';\r\n	}\r\n	if($section) {\r\n		$secSQL = \' AND Section IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($section)))).\') \';\r\n	}\r\n	if($realname) {\r\n		$author = join(\',\', safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doSlash(doArray(do_list($realname), \'urldecode\')))) .\')\' ));\r\n	}\r\n	if($author) {\r\n		$authSQL = \' AND AuthorID IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($author)))).\') \';\r\n	}\r\n	$status = do_list($status);\r\n	$stati = array();\r\n	foreach ($status as $stat) {\r\n		if (empty($stat)) {\r\n			continue;\r\n		} else if (is_numeric($stat)) {\r\n			$stati[] = $stat;\r\n		} else {\r\n			$stati[] = getStatusNum($stat);\r\n		}\r\n	}\r\n	$statSQL = \'Status IN (\'.join(\',\', $stati).\')\';\r\n	$timeSQL = \'\';\r\n	switch($time) {\r\n		case \"any\" : break;\r\n		case \"future\" : $timeSQL = \" AND Posted > now()\"; break;\r\n		default : $timeSQL = \" AND Posted < now()\"; break; // The past\r\n	}\r\n\r\n	// Sort\r\n	$sorder = (($dir==\'next\') ? \' DESC\' : \' ASC\'); // Negative logic to avoid lookahead: the \"last\" row seen is always the one required\r\n	$orderby = array();\r\n	if ($datasort) {\r\n		$datasort = do_list($datasort);\r\n		foreach ($datasort as $item) {\r\n			switch($item) {\r\n				case \'section\':\r\n					if ($section) {\r\n						$orderby[] = \'Section\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category\':\r\n					if ($category) {\r\n						$orderby[] = \'Category1\'.$sorder;\r\n						$orderby[] = \'Category2\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category1\':\r\n					if ($category) {\r\n						$orderby[] = \'Category1\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category2\':\r\n					if ($category) {\r\n						$orderby[] = \'Category2\'.$sorder;\r\n					}\r\n					break;\r\n				case \'author\':\r\n					if ($author) {\r\n						$orderby[] = \'AuthorID\'.$sorder;\r\n					}\r\n					break;\r\n			}\r\n		}\r\n	}\r\n	if ($timesort) {\r\n		$timesort = do_list($timesort);\r\n		foreach ($timesort as $item) {\r\n			switch(strtolower($item)) {\r\n				case \'lastmod\':\r\n					$orderby[] = \'LastMod\'.$sorder;\r\n					break;\r\n				case \'expires\':\r\n					$orderby[] = \'Expires\'.$sorder;\r\n					break;\r\n				case \'posted\':\r\n				default:\r\n					$orderby[] = \'Posted\'.$sorder;\r\n					break;\r\n			}\r\n		}\r\n	}\r\n\r\n	$orderby = \' ORDER BY \' . join(\',\', $orderby);\r\n\r\n	// Do it\r\n	assert_article();\r\n	$rs = safe_rows(\'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\',\r\n	\'textpattern\',\r\n	$statSQL.\r\n		(($category) ? $catSQL : \'\').\r\n		(($section) ? $secSQL : \'\').\r\n		(($author) ? $authSQL : \'\').\r\n		$timeSQL.\r\n		$expired.\r\n		$orderby,\r\n	$debug);\r\n	if ($debug > 1 && $rs) {\r\n		echo \'++ RECORD SET ++\';\r\n		dmp($rs);\r\n	}\r\n	// Find the current article in the record set, then move to find next/prev\r\n	$last = $curr = $ctr = 1;\r\n\r\n	foreach ($rs as $row) {\r\n		if ($row[\'ID\'] == $thisarticle[\'thisid\']) {\r\n			$curr = $last;\r\n			break;\r\n		}\r\n		$last = $row; // Store current\r\n		$ctr++;\r\n	}\r\n\r\n	if ($curr !== 1) {\r\n		if ($dir==\'next\') {\r\n			$smd_last[\'position\'] = $ctr;\r\n			$smd_last[\'section\'] = $thisarticle[\'section\'];\r\n			$smd_last[\'psec\'] = $pretext[\'s\'];\r\n			$smd_last[\'pcat\'] = $pretext[\'c\'];\r\n			$smd_last[\'category1\'] = $thisarticle[\'category1\'];\r\n			$smd_last[\'category2\'] = $thisarticle[\'category2\'];\r\n			$smd_last[\'author\'] = $thisarticle[\'authorid\'];\r\n		} else {\r\n			$smd_first[\'position\'] = $ctr;\r\n			$smd_first[\'psec\'] = $pretext[\'s\'];\r\n			$smd_first[\'pcat\'] = $pretext[\'c\'];\r\n			$smd_first[\'section\'] = $thisarticle[\'section\'];\r\n			$smd_first[\'category1\'] = $thisarticle[\'category1\'];\r\n			$smd_first[\'category2\'] = $thisarticle[\'category2\'];\r\n			$smd_first[\'author\'] = $thisarticle[\'authorid\'];\r\n		}\r\n	} else {\r\n		if ($dir==\'next\') {\r\n			$smd_last = array();\r\n		} else {\r\n			$smd_first = array();\r\n		}\r\n	}\r\n\r\n	if ($debug) {\r\n		if ($dir==\'next\') {\r\n			echo \'++ MOST RECENT (NEXT) ++\';\r\n			dmp($smd_last);\r\n		} else {\r\n			echo \'++ MOST RECENT (PREV) ++\';\r\n			dmp($smd_first);\r\n		}\r\n	}\r\n\r\n	// Populate globals if the next/prev article exists\r\n	$out = \'\';\r\n	$saved = array();\r\n	if ($curr === 1) {\r\n		$out = parse($thing);\r\n	} else {\r\n		// Keep a note of where we were\r\n		article_push();\r\n		$saved[\'prev_id\'] = $prev_id;\r\n		$saved[\'next_id\'] = $next_id;\r\n		$saved[\'prev_title\'] = $prev_title;\r\n		$saved[\'next_title\'] = $next_title;\r\n\r\n		// Pretend we\'re in the new article, and fake the global vars\r\n		populateArticleData($curr);\r\n		$prev_id = ($dir==\'prev\') ? $curr[\'ID\'] : \'\';\r\n		$next_id = ($dir==\'next\') ? $curr[\'ID\'] : \'\';\r\n		$prev_title = ($dir==\'prev\') ? $curr[\'Title\'] : \'\';\r\n		$next_title = ($dir==\'next\') ? $curr[\'Title\'] : \'\';\r\n		$url = permlinkurl_id($curr[\'ID\']);\r\n		$thing = (empty($thing)) ? \'<a rel=\"\'.$dir.\'\" href=\"\'.$url.\'\" title=\"\'.$curr[\'Title\'].\'\">\'.$curr[\'Title\'].\'</a>\' : $thing;\r\n		$out = parse($thing);\r\n\r\n		// Restore everything\r\n		$prev_id = $saved[\'prev_id\'];\r\n		$next_id = $saved[\'next_id\'];\r\n		$prev_title = $saved[\'prev_title\'];\r\n		$next_title = $saved[\'next_title\'];\r\n		article_pop();\r\n	}\r\n	$smd_in_nearest = false;\r\n	return $out;\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_link_to($atts, $thing = NULL) {\r\n	global $next_id, $prev_id, $next_title, $prev_title, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'showalways\' => 0,\r\n		\'wraptag\' => \'\',\r\n		\'class\' => \'\',\r\n		\'urlvars\' => \'\',\r\n		\'dir\' => \'next\', // Set by wrapper tags\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Maintain any URL variables\r\n	$addArgs = array();\r\n	if($urlvars) {\r\n		$optencode = $optforce = $optpri = false;\r\n		if (strpos($urlvars, \'SMD_ALL\') === 0) {\r\n			// Determine if options are to be applied globally\r\n			$urlopts = do_list($urlvars, \':\');\r\n			$optencode = (in_array(\'ESCAPE\', $urlopts)) ? true : false;\r\n			$optforce = (in_array(\'FORCE\', $urlopts)) ? true : false;\r\n			$optpri = (in_array(\'TAG_PRIORITY\', $urlopts)) ? true : false;\r\n			// POST overrides GET if both exist\r\n			$urlvars = array_merge(array_keys($_GET), array_keys($_POST));\r\n		} else {\r\n			$urlvars = do_list($urlvars);\r\n		}\r\n		foreach ($urlvars as $urlvar) {\r\n			$urlopts = do_list($urlvar, \':\');\r\n			$encode = ($optencode || in_array(\'ESCAPE\', $urlopts)) ? true : false;\r\n			$force = ($optforce || in_array(\'FORCE\', $urlopts)) ? true : false;\r\n			$pri = ($optpri || in_array(\'TAG_PRIORITY\', $urlopts)) ? true : false;\r\n			$urlparts = do_list($urlopts[0], \'=\');\r\n			$var = $urlparts[0];\r\n			$val = gps($urlparts[0]);\r\n			if ($pri) {\r\n				$val = (isset($urlparts[1])) ? $urlparts[1] : gps($urlparts[0]);\r\n			} else {\r\n				if ($val==\'\' && isset($urlparts[1])) {\r\n					$val = $urlparts[1];\r\n				}\r\n			}\r\n			$val = ($encode) ? htmlentities($val) : $val;\r\n			if ($val !== \'\' || $force) {\r\n				$addArgs[] = $var.\'=\'.$val;\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($debug && $addArgs) {\r\n		echo \'++ URL VARS ++\';\r\n		dmp($addArgs);\r\n   }\r\n\r\n	if ($dir==\'next\' && (($smd_in_nearest) ? $smd_last : 1)) {\r\n		if ($next_id) {\r\n			$url = permlinkurl_id($next_id) . (($addArgs) ? \'?\'. join(a, $addArgs) : \'\');\r\n			if ($thing) {\r\n				$thing = parse($thing);\r\n				$next_title = escape_title($next_title);\r\n\r\n				return doWrap(array(\'<a rel=\"next\" href=\"\'.$url.\'\"\'. (($class && !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\r\n					($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\r\n					\'>\'.$thing.\'</a>\'), $wraptag, \'\', $class);\r\n			}\r\n			return $url;\r\n		} else {\r\n			return ($showalways) ? parse($thing) : \'\';\r\n		}\r\n	}\r\n	if ($dir==\'prev\' && (($smd_in_nearest) ? $smd_first : 1)) {\r\n		if ($prev_id) {\r\n			$url = permlinkurl_id($prev_id) . (($addArgs) ? \'?\'. join(a, $addArgs) : \'\');\r\n			if ($thing) {\r\n				$thing = parse($thing);\r\n				$prev_title = escape_title($prev_title);\r\n\r\n				return doWrap(array(\'<a rel=\"prev\" href=\"\'.$url.\'\"\'. (($class && !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\r\n					($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\r\n					\'>\'.$thing.\'</a>\'), $wraptag, \'\', $class);\r\n			}\r\n			return $url;\r\n		} else {\r\n			return ($showalways) ? parse($thing) : \'\';\r\n		}\r\n	}\r\n	return;\r\n}', '// Public interfaces: convenience functions\r\nfunction smd_prev($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_nearest($atts, $thing);\r\n}\r\nfunction smd_next($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_nearest($atts, $thing);\r\n}\r\nfunction smd_link_to_prev($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_link_to($atts, $thing);\r\n}\r\nfunction smd_link_to_next($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_link_to($atts, $thing);\r\n}\r\nfunction smd_if_start($atts, $thing) {\r\n	$atts[\'dir\'] = \'prev\';\r\n	return smd_if_horizon($atts, $thing);\r\n}\r\nfunction smd_if_end($atts, $thing) {\r\n	$atts[\'dir\'] = \'next\';\r\n	return smd_if_horizon($atts, $thing);\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_if_horizon($atts, $thing) {\r\n	global $pretext, $thisarticle, $thiscategory, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'type\' => \'list\',\r\n		\'logic\' => \'or\',\r\n		\'dir\' => \'next\',\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	$itout = array(); // For debug only\r\n	$type = do_list($type);\r\n	$out = array();\r\n	foreach ($type as $item) {\r\n		if ($debug) {\r\n			$itout[] = $item;\r\n		}\r\n		switch ($item) {\r\n			case \'list\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (empty($smd_last)) ? true : false;\r\n					} else {\r\n						$out[] = (empty($smd_first)) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'category\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && ($smd_last[\'category1\'] != $thisarticle[\'category1\'] || $smd_last[\'category2\'] != $thisarticle[\'category2\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && ($smd_first[\'category1\'] != $thisarticle[\'category1\'] || $smd_first[\'category2\'] != $thisarticle[\'category2\'])) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'author\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'author\'] != $thisarticle[\'authorid\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'author\'] != $thisarticle[\'authorid\']) ? true : false;\r\n					}\r\n				} else {\r\n					// Not possible since author lists are not permitted in TXP\r\n				}\r\n				break;\r\n			case \'cat1\':\r\n			case \'category1\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'category1\'] != $thisarticle[\'category1\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'category1\'] != $thisarticle[\'category1\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'cat2\':\r\n			case \'category2\':\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'category2\'] != $thisarticle[\'category2\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'category2\'] != $thisarticle[\'category2\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($thiscategory[\'is_last\'])) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($thiscategory[\'is_first\'])) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n			case \'section\':\r\n			default:\r\n				if ($smd_in_nearest) {\r\n					if ($dir == \'next\') {\r\n						$out[] = (!empty($smd_last) && $smd_last[\'section\'] != $thisarticle[\'section\']) ? true : false;\r\n					} else {\r\n						$out[] = (!empty($smd_first) && $smd_first[\'section\'] != $thisarticle[\'section\']) ? true : false;\r\n					}\r\n				} else {\r\n					if ($dir == \'next\') {\r\n						$out[] = empty($pretext[\'next_id\']) ? true : false;\r\n					} else {\r\n						$out[] = empty($pretext[\'prev_id\']) ? true : false;\r\n					}\r\n				}\r\n				break;\r\n		}\r\n	}\r\n	if ($debug) {\r\n		echo \'++ TEST RESULTS ++\';\r\n		dmp($itout);\r\n		dmp($out);\r\n	}\r\n	$res = ($out) ? true : false;\r\n	if (strtolower($logic) == \"and\" && in_array(false, $out)) {\r\n		$res = false;\r\n	}\r\n	if (strtolower($logic) == \"or\" && !in_array(true, $out)) {\r\n		$res = false;\r\n	}\r\n	if ($debug) {\r\n		echo \'++ FINAL RESULT ++\';\r\n		dmp($res);\r\n	}\r\n	return parse(EvalElse($thing, $res));\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_nearest($atts, $thing) {\r\n	global $pretext, $thisarticle, $thiscategory, $prefs, $next_id, $prev_id, $next_title, $prev_title, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'section\' => $pretext[\'s\'],\r\n		\'category\' => $pretext[\'c\'],\r\n		\'author\' => $pretext[\'author\'],\r\n		\'realname\' => \'\',\r\n		\'status\' => \'4\',\r\n		\'time\' => \'any\', // any, future, past\r\n		\'datasort\' => \'section, category1, category2, author\',\r\n		\'timesort\' => \'posted\',\r\n		\'form\' => \'\',\r\n		\'dir\' => \'next\', // Set by wrapper tags\r\n		\'debug\' => 0,\r\n	), $atts));\r\n\r\n	extract($prefs);\r\n	$smd_in_nearest = true;\r\n\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$expired = ($publish_expired_articles) ? \'\' : \' AND (now() <= Expires or Expires = \'.NULLDATETIME.\')\';\r\n	$safe_name = safe_pfx(\'textpattern\');\r\n\r\n	// Filters\r\n	$catSQL = $secSQL = $authSQL = \'\';\r\n	if($category) {\r\n		$catSQL = doQuote(join(\"\',\'\", doSlash(do_list($category))));\r\n		$catSQL = \' AND ( Category1 IN (\'.$catSQL.\') OR Category2 IN (\'.$catSQL.\') ) \';\r\n	}\r\n	if($section) {\r\n		$secSQL = \' AND Section IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($section)))).\') \';\r\n	}\r\n	if($realname) {\r\n		$author = join(\',\', safe_column(\'name\', \'txp_users\', \'RealName IN (\'. doQuote(join(\"\',\'\", doSlash(doArray(do_list($realname), \'urldecode\')))) .\')\' ));\r\n	}\r\n	if($author) {\r\n		$authSQL = \' AND AuthorID IN (\'.doQuote(join(\"\',\'\", doSlash(do_list($author)))).\') \';\r\n	}\r\n	$status = do_list($status);\r\n	$stati = array();\r\n	foreach ($status as $stat) {\r\n		if (empty($stat)) {\r\n			continue;\r\n		} else if (is_numeric($stat)) {\r\n			$stati[] = $stat;\r\n		} else {\r\n			$stati[] = getStatusNum($stat);\r\n		}\r\n	}\r\n	$statSQL = \'Status IN (\'.join(\',\', $stati).\')\';\r\n	$timeSQL = \'\';\r\n	switch($time) {\r\n		case \"any\" : break;\r\n		case \"future\" : $timeSQL = \" AND Posted > now()\"; break;\r\n		default : $timeSQL = \" AND Posted < now()\"; break; // The past\r\n	}\r\n\r\n	// Sort\r\n	$sorder = (($dir==\'next\') ? \' DESC\' : \' ASC\'); // Negative logic to avoid lookahead: the \"last\" row seen is always the one required\r\n	$orderby = array();\r\n	if ($datasort) {\r\n		$datasort = do_list($datasort);\r\n		foreach ($datasort as $item) {\r\n			switch($item) {\r\n				case \'section\':\r\n					if ($section) {\r\n						$orderby[] = \'Section\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category\':\r\n					if ($category) {\r\n						$orderby[] = \'Category1\'.$sorder;\r\n						$orderby[] = \'Category2\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category1\':\r\n					if ($category) {\r\n						$orderby[] = \'Category1\'.$sorder;\r\n					}\r\n					break;\r\n				case \'category2\':\r\n					if ($category) {\r\n						$orderby[] = \'Category2\'.$sorder;\r\n					}\r\n					break;\r\n				case \'author\':\r\n					if ($author) {\r\n						$orderby[] = \'AuthorID\'.$sorder;\r\n					}\r\n					break;\r\n			}\r\n		}\r\n	}\r\n	if ($timesort) {\r\n		$timesort = do_list($timesort);\r\n		foreach ($timesort as $item) {\r\n			switch(strtolower($item)) {\r\n				case \'lastmod\':\r\n					$orderby[] = \'LastMod\'.$sorder;\r\n					break;\r\n				case \'expires\':\r\n					$orderby[] = \'Expires\'.$sorder;\r\n					break;\r\n				case \'posted\':\r\n				default:\r\n					$orderby[] = \'Posted\'.$sorder;\r\n					break;\r\n			}\r\n		}\r\n	}\r\n\r\n	$orderby = \' ORDER BY \' . join(\',\', $orderby);\r\n\r\n	// Do it\r\n	assert_article();\r\n	$rs = safe_rows(\'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\',\r\n	\'textpattern\',\r\n	$statSQL.\r\n		(($category) ? $catSQL : \'\').\r\n		(($section) ? $secSQL : \'\').\r\n		(($author) ? $authSQL : \'\').\r\n		$timeSQL.\r\n		$expired.\r\n		$orderby,\r\n	$debug);\r\n	if ($debug > 1 && $rs) {\r\n		echo \'++ RECORD SET ++\';\r\n		dmp($rs);\r\n	}\r\n	// Find the current article in the record set, then move to find next/prev\r\n	$last = $curr = $ctr = 1;\r\n\r\n	foreach ($rs as $row) {\r\n		if ($row[\'ID\'] == $thisarticle[\'thisid\']) {\r\n			$curr = $last;\r\n			break;\r\n		}\r\n		$last = $row; // Store current\r\n		$ctr++;\r\n	}\r\n\r\n	if ($curr !== 1) {\r\n		if ($dir==\'next\') {\r\n			$smd_last[\'position\'] = $ctr;\r\n			$smd_last[\'section\'] = $thisarticle[\'section\'];\r\n			$smd_last[\'psec\'] = $pretext[\'s\'];\r\n			$smd_last[\'pcat\'] = $pretext[\'c\'];\r\n			$smd_last[\'category1\'] = $thisarticle[\'category1\'];\r\n			$smd_last[\'category2\'] = $thisarticle[\'category2\'];\r\n			$smd_last[\'author\'] = $thisarticle[\'authorid\'];\r\n		} else {\r\n			$smd_first[\'position\'] = $ctr;\r\n			$smd_first[\'psec\'] = $pretext[\'s\'];\r\n			$smd_first[\'pcat\'] = $pretext[\'c\'];\r\n			$smd_first[\'section\'] = $thisarticle[\'section\'];\r\n			$smd_first[\'category1\'] = $thisarticle[\'category1\'];\r\n			$smd_first[\'category2\'] = $thisarticle[\'category2\'];\r\n			$smd_first[\'author\'] = $thisarticle[\'authorid\'];\r\n		}\r\n	} else {\r\n		if ($dir==\'next\') {\r\n			$smd_last = array();\r\n		} else {\r\n			$smd_first = array();\r\n		}\r\n	}\r\n\r\n	if ($debug) {\r\n		if ($dir==\'next\') {\r\n			echo \'++ MOST RECENT (NEXT) ++\';\r\n			dmp($smd_last);\r\n		} else {\r\n			echo \'++ MOST RECENT (PREV) ++\';\r\n			dmp($smd_first);\r\n		}\r\n	}\r\n\r\n	// Populate globals if the next/prev article exists\r\n	$out = \'\';\r\n	$saved = array();\r\n	if ($curr === 1) {\r\n		$out = parse($thing);\r\n	} else {\r\n		// Keep a note of where we were\r\n		article_push();\r\n		$saved[\'prev_id\'] = $prev_id;\r\n		$saved[\'next_id\'] = $next_id;\r\n		$saved[\'prev_title\'] = $prev_title;\r\n		$saved[\'next_title\'] = $next_title;\r\n\r\n		// Pretend we\'re in the new article, and fake the global vars\r\n		populateArticleData($curr);\r\n		$prev_id = ($dir==\'prev\') ? $curr[\'ID\'] : \'\';\r\n		$next_id = ($dir==\'next\') ? $curr[\'ID\'] : \'\';\r\n		$prev_title = ($dir==\'prev\') ? $curr[\'Title\'] : \'\';\r\n		$next_title = ($dir==\'next\') ? $curr[\'Title\'] : \'\';\r\n		$url = permlinkurl_id($curr[\'ID\']);\r\n		$thing = (empty($thing)) ? \'<a rel=\"\'.$dir.\'\" href=\"\'.$url.\'\" title=\"\'.$curr[\'Title\'].\'\">\'.$curr[\'Title\'].\'</a>\' : $thing;\r\n		$out = parse($thing);\r\n\r\n		// Restore everything\r\n		$prev_id = $saved[\'prev_id\'];\r\n		$next_id = $saved[\'next_id\'];\r\n		$prev_title = $saved[\'prev_title\'];\r\n		$next_title = $saved[\'next_title\'];\r\n		article_pop();\r\n	}\r\n	$smd_in_nearest = false;\r\n	return $out;\r\n}\r\n\r\n// ****************************\r\n// Private function: not for public consumption\r\n// ****************************\r\nfunction smd_link_to($atts, $thing = NULL) {\r\n	global $next_id, $prev_id, $next_title, $prev_title, $smd_last, $smd_first, $smd_in_nearest;\r\n\r\n	extract(lAtts(array(\r\n		\'showalways\' => 0,\r\n		\'wraptag\' => \'\',\r\n		\'class\' => \'\',\r\n		\'urlvars\' => \'\',\r\n		\'dir\' => \'next\', // Set by wrapper tags\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Maintain any URL variables\r\n	$addArgs = array();\r\n	if($urlvars) {\r\n		$optencode = $optforce = $optpri = false;\r\n		if (strpos($urlvars, \'SMD_ALL\') === 0) {\r\n			// Determine if options are to be applied globally\r\n			$urlopts = do_list($urlvars, \':\');\r\n			$optencode = (in_array(\'ESCAPE\', $urlopts)) ? true : false;\r\n			$optforce = (in_array(\'FORCE\', $urlopts)) ? true : false;\r\n			$optpri = (in_array(\'TAG_PRIORITY\', $urlopts)) ? true : false;\r\n			// POST overrides GET if both exist\r\n			$urlvars = array_merge(array_keys($_GET), array_keys($_POST));\r\n		} else {\r\n			$urlvars = do_list($urlvars);\r\n		}\r\n		foreach ($urlvars as $urlvar) {\r\n			$urlopts = do_list($urlvar, \':\');\r\n			$encode = ($optencode || in_array(\'ESCAPE\', $urlopts)) ? true : false;\r\n			$force = ($optforce || in_array(\'FORCE\', $urlopts)) ? true : false;\r\n			$pri = ($optpri || in_array(\'TAG_PRIORITY\', $urlopts)) ? true : false;\r\n			$urlparts = do_list($urlopts[0], \'=\');\r\n			$var = $urlparts[0];\r\n			$val = gps($urlparts[0]);\r\n			if ($pri) {\r\n				$val = (isset($urlparts[1])) ? $urlparts[1] : gps($urlparts[0]);\r\n			} else {\r\n				if ($val==\'\' && isset($urlparts[1])) {\r\n					$val = $urlparts[1];\r\n				}\r\n			}\r\n			$val = ($encode) ? htmlentities($val) : $val;\r\n			if ($val !== \'\' || $force) {\r\n				$addArgs[] = $var.\'=\'.$val;\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($debug && $addArgs) {\r\n		echo \'++ URL VARS ++\';\r\n		dmp($addArgs);\r\n   }\r\n\r\n	if ($dir==\'next\' && (($smd_in_nearest) ? $smd_last : 1)) {\r\n		if ($next_id) {\r\n			$url = permlinkurl_id($next_id) . (($addArgs) ? \'?\'. join(a, $addArgs) : \'\');\r\n			if ($thing) {\r\n				$thing = parse($thing);\r\n				$next_title = escape_title($next_title);\r\n\r\n				return doWrap(array(\'<a rel=\"next\" href=\"\'.$url.\'\"\'. (($class && !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\r\n					($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\r\n					\'>\'.$thing.\'</a>\'), $wraptag, \'\', $class);\r\n			}\r\n			return $url;\r\n		} else {\r\n			return ($showalways) ? parse($thing) : \'\';\r\n		}\r\n	}\r\n	if ($dir==\'prev\' && (($smd_in_nearest) ? $smd_first : 1)) {\r\n		if ($prev_id) {\r\n			$url = permlinkurl_id($prev_id) . (($addArgs) ? \'?\'. join(a, $addArgs) : \'\');\r\n			if ($thing) {\r\n				$thing = parse($thing);\r\n				$prev_title = escape_title($prev_title);\r\n\r\n				return doWrap(array(\'<a rel=\"prev\" href=\"\'.$url.\'\"\'. (($class && !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\r\n					($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\r\n					\'>\'.$thing.\'</a>\'), $wraptag, \'\', $class);\r\n			}\r\n			return $url;\r\n		} else {\r\n			return ($showalways) ? parse($thing) : \'\';\r\n		}\r\n	}\r\n	return;\r\n}', '23034089370ad66d5a7dcf8b6ef46667', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_qr_code', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.32', 'Create QR codes on the fly', '<div id=\"smd_help\">\n\n	<h1>smd_qr_code</h1>\n\n	<p>Create Quick Response 2-D barcodes on the fly containing a variety of data: text, <span class=\"caps\">URL</span> links, contact details, geographical location data, wifi hotspot requests, or your own custom formats. Choose to embed the barcode directly on your page or offer it as a download.</p>\n\n	<h2>Author / credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Credits also go to the awesome Google Charts <span class=\"caps\">API</span>. Many thanks to all involved.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires <span class=\"caps\">TXP</span> 4.4.0+, <span class=\"caps\">PHP</span> 5 (with <span class=\"caps\">CURL</span>) and Internet access</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1219/smd_qr_code\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> <em>Admin -&gt; Plugins</em> pane, install and enable the plugin. To uninstall, delete from the <em>Admin -&gt; Plugins</em> page.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35886\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<h2>Usage: <code>&lt;txp:smd_qr_code /&gt;</code></h2>\n\n	<p>The plugin adds one tag, which you can use to embed a QR code on your page. It has a variety of attributes.</p>\n\n	<h3>Data attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">type</span> : the type of QR code to generate. Define your own, or use one of the built-in formats:\n	<ul>\n		<li><strong>text</strong> : the default</li>\n		<li><strong>link</strong> : a hyperlink. The <code>http[s]://</code> prefix is optional and will be added if omitted</li>\n		<li><strong>bookmark</strong></li>\n		<li><strong>sms</strong> : send a text message</li>\n		<li><strong>phone</strong></li>\n		<li><strong>email</strong></li>\n		<li><strong>contact</strong> : a <span class=\"caps\">MECARD</span></li>\n		<li><strong>vcard</strong> : a compact version of the full vcard spec</li>\n		<li><strong>geo</strong> : geographical location (decimal latitude, longitude and height above the earth&#8217;s surface)</li>\n		<li><strong>wifi</strong> : wifi hotspot login credentials (<span class=\"caps\">WEP</span> or <span class=\"caps\">WPA</span> protocols supported)</li>\n	</ul></li>\n		<li><span class=\"atnm\">data</span> : the information to put in the QR code. The data is dependent on the <code>type</code> &#8212; see <a href=\"#smd_qr_code_data_table\">the data table</a> for details on the quantity and order of information you put in this attribute for the built-in types. Delimit each piece of data with a pipe character (though this can be changed with the <code>delim</code> attribute, if required). For example, <code>type=&quot;bookmark&quot; data=&quot;20,000 Leagues under the Sea|www.jules-verne.co.uk/&quot;</code>. The limit of the data attribute is about 2KB</li>\n		<li><span class=\"atnm\">delim</span> : the delimiter to use between <code>data</code> items. Default: <code>|</code> (pipe)</li>\n		<li><span class=\"atnm\">escape</span> : Escape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code>. Use <code>escape=&quot;html&quot;</code> to switch this on. Default: unset</li>\n	</ul>\n\n	<h3>Output / display attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">format</span> : whether you wish to <code>view</code> the barcode inline (inside an <code>&lt;img&gt;</code> tag), force it to <code>download</code>, or output the <code>raw</code> link so you can craft your own use for it. Default: <code>view</code></li>\n		<li><span class=\"atnm\">width</span> : width of the code, in pixels. Defaut: 200</li>\n		<li><span class=\"atnm\">height</span> : height of the code, in pixels. Default: 200</li>\n		<li><span class=\"atnm\">quality</span> : the amount of error correction that the barcode can tolerate:\n	<ul>\n		<li><strong>L</strong> &#8212; Low: 7% (default)</li>\n		<li><strong>M</strong> &#8212; Medium: 15%</li>\n		<li><strong>Q</strong> &#8212; Quartile: 25%</li>\n		<li><strong>H</strong> &#8212; High: 30%</li>\n	</ul></li>\n		<li><span class=\"atnm\">margin</span> : the margin (in QR code &#8220;rows&#8221;) around the image. Default: 4. Note that, depending on the quality and size settings, some adjacent values don&#8217;t appear to &#8216;do&#8217; anything. For example, a 200&#215;200 &#8216;L&#8217; quality image looks the same whether you pick a margin of 0, 1 or 2. The margin steps up a size for 3, 4 and 5, then up again for 6-10, and so on. This is an idiosyncracy of Google&#8217;s Charts <span class=\"caps\">API</span> (or the QR code spec?), not a fault with the plugin</li>\n		<li><span class=\"atnm\">bgcol</span> : background colour as an <span class=\"caps\">RGB</span> hex code (without the #). Default: unset</li>\n		<li><span class=\"atnm\">alpha</span> : alpha transparency as a hex value from 00 (transparent) to FF (solid). Default: unset</li>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML tag &#8212; specified without angle brackets &#8212; to wrap around the output. Default: unset</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> classname to apply to the wraptag (if present) or the <code>&lt;img&gt;</code> tag if you have chosen <code>format=&quot;view&quot;</code>. Default: unset</li>\n		<li><span class=\"atnm\">html_id</span> : the <span class=\"caps\">HTML</span> id to apply to the wraptag (if present) or the <code>&lt;img&gt;</code> tag if you have chosen <code>format=&quot;view&quot;</code>. Default: unset</li>\n		<li><span class=\"atnm\">title</span> : Set the <code>&lt;img&gt;</code> tag&#8217;s title attribute (often used as a tooltip). Only of use when <code>format=&quot;view&quot;</code>. Default: unset</li>\n	</ul>\n\n	<h3>Validation attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">validate</span> : whether to validate all fields (1, the default), no fields (0) or the given list of data fields. The list is delimited by <code>validelim</code> (default: comma). Consult the <a href=\"#smd_qr_code_data_table\">data table</a> for details on the names of the fields you can use. After each field you may also add a <code>delim</code> character and then a regular expression to validate this field against, which will override the built-in regex. See <a href=\"#eg5\">example 5</a>. <div class=\"break\">If you specify one or two fields to validate &#8212; perhaps you just want to adjust a single regex to taste &#8212; you&#8217;ll find that the remaining fields are &#8220;switched off&#8221; and not validated. To save you having to list them all, you can use a shortcut: just specify <code>1</code> as one of your list items and the remaining fields will be included in the validation with their original regexes.</div><div class=\"break\">You may also copy regexes if you don&#8217;t wish to define them again. Specify the field as usual and then the special code <span class=\"caps\">SMD</span>_REGEX_name_of_field_to_copy.</div></li>\n		<li><span class=\"atnm\">validelim</span> : the delimiter to use between your list of items to <code>validate</code>. Default: <code>,</code> (comma)</li>\n	</ul>\n\n	<h3>Customisation attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">layout</span> : define your own custom layout system. Use <code>{name-of-variable}</code> in your definition to have the data value injected at runtime. See <a href=\"#smd_qr_code_custom_types\">custom types</a></li>\n		<li><span class=\"atnm\">transform</span> : define your own custom transformation to apply to particular data items. Specify <code>type.field|name-of-function|optional|args|...</code>. See <a href=\"#smd_qr_code_custom_transforms\">custom transforms</a></li>\n		<li><span class=\"atnm\">prefix</span> : prefix for new parameters that are created by your transformations. Default: <code>SMD_QR_</code></li>\n	</ul>\n\n	<p>The plugin checks that you supply all information for your chosen type and, if <code>validate</code> is set validates each field with a regular expression. Any deviation will result in an error being triggered.</p>\n\n	<p>Note that if you wish to designate an item as &#8216;empty&#8217; (i.e. you don&#8217;t want to supply that parameter) you can put in the special code <code>{smd_empty}</code>. It is not enough to simply leave it blank because the plugin expects all fields to be filled in with <em>something</em>. The QR code will still contain the field, it will just have no data assigned to it. If you don&#8217;t like the built-in formats, <a href=\"#smd_qr_code_custom_types\">create your own</a>.</p>\n\n	<p>The code should be cached by your browser for 24 hours.</p>\n\n	<h2 id=\"smd_qr_code_data_table\">Data table for default types</h2>\n\n	<p>Here are all the <code>type</code>s available and the fields they require in the <code>data</code> attribute, from left to right. The names used here are the names you refer to the fields when listing them in your <code>validate</code> attribute.</p>\n\n	<table>\n	<thead>\n		<tr>\n			<th>type </th>\n			<th colspan=\"5\">data attributes </th>\n		</tr>\n	</thead>\n	<tbody>\n		<tr>\n			<td> <strong>text</strong> </td>\n			<td> text </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>link</strong> </td>\n			<td> url </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>bookmark</strong> </td>\n			<td> title </td>\n			<td> url </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>sms</strong> </td>\n			<td> phone </td>\n			<td> text </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>phone</strong> </td>\n			<td> phone </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>email</strong> </td>\n			<td> email </td>\n			<td> subject </td>\n			<td> message </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>contact</strong> </td>\n			<td> name </td>\n			<td> address </td>\n			<td> phone </td>\n			<td> email </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>vcard</strong> </td>\n			<td> name </td>\n			<td> email </td>\n			<td> url </td>\n			<td> address </td>\n			<td> phone </td>\n		</tr>\n		<tr>\n			<td> <strong>geo</strong> </td>\n			<td> lat </td>\n			<td> lon </td>\n			<td> height </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n		<tr>\n			<td> <strong>wifi</strong> </td>\n			<td> wifi_protocol </td>\n			<td> ssid </td>\n			<td> pass </td>\n			<td> </td>\n			<td> </td>\n		</tr>\n	</tbody>\n	</table>\n\n	<h2 id=\"smd_qr_code_custom_types\">Custom types</h2>\n\n	<p>You may create your own custom data formats for your QR codes. As well as being able to designate a custom regular expression to validate the data value, you can also specify callbacks to transform the data value prior to its validation. This allows you to do things like tidy up links, add suffixes, sanitize data, etc.</p>\n\n	<p>Let&#8217;s say you don&#8217;t like the built-in vcard implementation and wanted to drop the <span class=\"caps\">ADR</span> property, while adding the <span class=\"caps\">CELL</span> property. You can define your own by using the following attributes (line breaks added only for clarity: they should not be in your definition) :</p>\n\n<pre class=\"block\"><code class=\"block\">type=&quot;my_vcard&quot; layout=&quot;BEGIN:VCARD\\r\\n\n     N:{SMD_QR_formatted_name}\\r\\n\n     FN:{name}\\r\\n\n     EMAIL:{email}\\r\\n\n     URL:{url}\\r\\n\n     TEL:{phone}\\r\\n\n     CELL:{cell}\\r\\n\n     END:VCARD&quot;\n</code></pre>\n\n	<p>A few things to note:</p>\n\n	<ul>\n		<li>You can name the <code>type</code> anything you like (even the same name as one of the existing types: your version will be used instead)</li>\n		<li>The vcard spec (certainly v3+) states that the <code>N:</code> property is mandatory. It&#8217;s merely a reformatted version of the <code>FN:</code> so, to save you having to add it manually, the plugin applies a custom transform called <code>splitrevjoin</code> (more info in the <a href=\"#smd_qr_code_custom_types\">custom transforms</a> section). Because it&#8217;s a computed field, the plugin designates it with an <code>SMD_QR_</code> prefix. If you wanted to flaunt the spec and omit this value, just remove that entry from your <code>layout</code></li>\n		<li>The <code>{cell}</code> replacement variable isn&#8217;t one of the built-in supported formats (i.e. one of the parameters listed in the <a href=\"#smd_qr_code_data_table\">data table</a>). To help you out, the plugin assigns a default regular expression &#8220;one or more characters&#8221; to any new parameters, but if you wish to tighten this you can specify your own regex via the <code>validate</code> attribute</li>\n		<li>Any <span class=\"caps\">URL</span>s are reformatted to begin with <code>http://</code> if you omit the prefix. This is another built-in transform which you can override or suppress if you prefer</li>\n	</ul>\n\n	<p>With this custom format in place you can specify a <code>data</code> attribute like this (again, newlines are for clarity only) :</p>\n\n<pre class=\"block\"><code class=\"block\">data=&quot;Stef Dawson\n     | some@mail.com\n     | stefdawson.com\n     | +44 (0)1234 567 890\n     | +44 (0)7777 098 765&quot;\n</code></pre>\n\n	<p>Your QR code would be generated with the above data in it. If you wanted to use the same format across a number of codes, you could define your format in a <code>&lt;txp:variable /&gt;</code> and use <span class=\"caps\">TXP</span>&#8217;s standard tag-in-attribute syntax to reuse it.</p>\n\n	<p>Since the new field &#8216;cell&#8217; will be validated using the most permissive regex and we already have a perfectly good expression defined for &#8216;phone&#8217; it&#8217;d be a shame to have to repeat ourselves. So you can do this:</p>\n\n<pre class=\"block\"><code class=\"block\">validate=&quot;1, cell:SMD_REGEX_PHONE&quot;\n</code></pre>\n\n	<p>to copy the &#8216;phone&#8217; regex over to the &#8216;cell&#8217; field. The &#8216;1&#8217; just makes sure that all the remaining fields are validated as normal. Without that, only the cell and any other fields you explicitly defined in the list would be validated. If you would like to see a list of the built-in regexes, use <code>debug=&quot;1&quot;</code> (or 2 for more verbose output).</p>\n\n	<h2 id=\"smd_qr_code_custom_transforms\">Custom transforms</h2>\n\n	<p>Before validation, each piece of data may be put through a mangle to reformat it. You could use this to sanitize data, prefix it, suffix it, fetch other data, or create new entries by twisting a given data parameter. The following default transforms are built-in:</p>\n\n	<ul>\n		<li><strong>linkify</strong> : prefix any <span class=\"caps\">URL</span> that does not begin with <code>http://</code></li>\n		<li><strong>splitrevjoin</strong> : takes someone&#8217;s name, splits it at space, reverses the order of the data, and then recombines it with a semicolon between each field. Thus <code>Stef Dawson</code> becomes <code>Dawson;Stef</code></li>\n	</ul>\n\n	<p>Transformations can be applied to any data parameter. You choose which by using the following syntax:</p>\n\n<pre class=\"block\"><code class=\"block\">transform=&quot;type.field|function_name|optional|attributes|...&quot;\n</code></pre>\n\n	<p>So to capitalize the vcard&#8217;s &#8216;name&#8217; you could use <span class=\"caps\">PHP</span>&#8217;s built-in function <code>strtoupper()</code> like this:</p>\n\n<pre class=\"block\"><code class=\"block\">transform=&quot;vcard.name|strtoupper&quot;\n</code></pre>\n\n	<p>You can use a shorthand &#8216;wildcard&#8217; notation if you wish to apply the same transform to the same parameter in different types (though admittedly, this has limited use). The plugin uses this with its default <span class=\"caps\">URL</span> transformation by using: <code>*.url|linkify</code>.</p>\n\n	<p>If you try the above though you will receive a plugin warning <code>Invalid or unregistered transform function</code>. That&#8217;s a security message because only registered functions are permitted to run transformations. To register a function, use Textpattern&#8217;s <code>register_callback()</code> function in some <span class=\"caps\">PHP</span>:</p>\n\n<pre class=\"block\"><code class=\"block\">register_callback(&#39;strtoupper&#39;, &#39;smd_qr_code&#39;, &#39;add_transform&#39;);\n</code></pre>\n\n	<p>You can of course use your own functions if you wish. The signature of your function should be:</p>\n\n	<ol>\n		<li>(string) Data value to transform</li>\n		<li>(array) Optional values that your transform needs</li>\n	</ol>\n\n	<p>The additional parameters are passed, in order, from the <code>transform</code> attribute. Note that you can <em>only</em> use functions (either your own, or <span class=\"caps\">PHP</span>/TXP functions) if they either take a single string or a string plus an array as arguments. Trying to use any more complicated functions or attributes of the wrong type will likely result in warnings or errors being thrown.</p>\n\n	<p>If your custom function returns a single string value, that will become the parameter&#8217;s new value. If, however, you return an array of idx =&gt; values then any idx that matches the name of the parameter will replace the original. Any additional idx values become new parameters, prefixed with <code>SMD_QR_</code>. Let&#8217;s look at the built-in <code>splitrevjoin()</code> function:</p>\n\n<pre class=\"block\"><code class=\"block\">public function splitrevjoin($item, $args=array()) {\n   // $args: 0 = variable name, 1 = split char, 2 = join char\n   if (count($args) == 3) {\n      $outval = join(\n           $args[2]\n           , array_reverse(\n              explode( $args[1], $item )\n           )\n      );\n      return array($args[0] =&gt; $outval);\n   } else {\n      return $item;\n   }\n}\n</code></pre>\n\n	<p>In this function, we have assumed that the transform&#8217;s parameters are:</p>\n\n	<ul>\n		<li><code>$item</code> : the data value</li>\n		<li><code>$args[0]</code> : the name of the new parameter</li>\n		<li><code>$args[1]</code> : the split character</li>\n		<li><code>$args[2]</code> : the rejoin character</li>\n	</ul>\n\n	<p>All these values are passed in like this: <code>vcard.name|splitrevjoin|formatted_name| |;</code> which means we&#8217;re telling the transform to process the <code>$item</code> and return a <em>new</em> parameter called <code>formatted_name</code> after splitting the <code>$item</code> at space and reconstituting the parts as a string, delimited by a semicolon.</p>\n\n	<p>If we&#8217;d returned <code>name</code> instead of <code>formatted_name</code>, the existing parameter would have been overwritten. Since the goal of this function is to reformat the person&#8217;s name into a form suitable for the vcard&#8217;s <code>N:</code> attribute, we&#8217;re leaving the original intact and making a new value.</p>\n\n	<p>Finally, to distinguish the <em>input</em> values from <em>computed</em> values such as this one, the plugin will automatically prefix all new parameters with <code>SMD_QR_</code>. Thus to use it in your layout, remember you need to specify <code>{SMD_QR_formatted_name}</code>. The prefix can be changed via the plugin&#8217;s <code>prefix</code> attribute.</p>\n\n	<h2>Examples</h2>\n\n	<h3 id=\"eg1\">Example 1: Bookmark permlinks</h3>\n\n	<p>In your default Form put a QR code alongside your article heading so people can bookmark the article by taking a picture with their smart phone:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;h1 class=&quot;entry-title&quot;&gt;\n   &lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n   &lt;txp:smd_qr_code type=&quot;bookmark&quot;\n     data=&#39;&lt;txp:title /&gt;|&lt;txp:permlink /&gt;&#39;\n     width=&quot;50&quot; height=&quot;50&quot;\n     quality=&quot;Q&quot; /&gt;\n&lt;/h1&gt;\n</code></pre>\n\n	<p>Notice that since the image size has been reduced to fit alongside the title, the quality (error detection/correction) has been increased to compensate.</p>\n\n	<h3 id=\"eg2\">Example 2: Author contact details</h3>\n\n	<p>If you have smd_bio installed you could add the following code to the &#8216;published by&#8217; segment of your articles. It will display a QR code for the contact details of the article&#8217;s author, thus visitors can scan the barcode and have the author&#8217;s <span class=\"caps\">MECARD</span> added directly to their address books:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;p class=&quot;published&quot;&gt;\n   &lt;txp:posted /&gt; by &lt;txp:author title=&quot;0&quot; /&gt;\n   &lt;txp:smd_qr_code type=&quot;contact&quot;\n     data=&#39;&lt;txp:author /&gt;\n        | &lt;txp:smd_bio_info items=&quot;address&quot; label=&quot;&quot; /&gt;\n        | &lt;txp:smd_bio_info items=&quot;phone&quot; label=&quot;&quot; /&gt;\n        | &lt;txp:smd_bio_info items=&quot;email&quot; label=&quot;&quot; /&gt; /&gt;&#39;\n     bgcol=&quot;bea7d0&quot; margin=&quot;1&quot; /&gt;\n&lt;/p&gt;\n</code></pre>\n\n	<p>This one has a natty, thin purple border too courtesy of the <code>bgcol</code> and <code>margin</code> attributes.</p>\n\n	<p>Alternatively, in <span class=\"caps\">VCARD</span> format:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable\n     name=&quot;address&quot;&gt;&lt;txp:smd_bio_info\n     items=&quot;address&quot; label=&quot;&quot; /&gt;&lt;/txp:variable&gt;\n&lt;p class=&quot;published&quot;&gt;\n   &lt;txp:posted /&gt; by &lt;txp:author title=&quot;0&quot; /&gt;\n   &lt;txp:smd_qr_code type=&quot;vcard&quot;\n     data=&#39;&lt;txp:author /&gt;\n        | &lt;txp:smd_bio_info items=&quot;email&quot; label=&quot;&quot; /&gt; /&gt;&#39;\n        | &lt;txp:smd_bio_info items=&quot;website&quot; label=&quot;&quot; /&gt;\n        | &lt;txp:if_variable\n               name=&quot;address&quot;\n               value=&quot;&quot;&gt;{smd_empty}&lt;txp:else /&gt;&lt;txp:variable\n               name=&quot;address&quot; /&gt;&lt;/txp:if_variable&gt;\n        | &lt;txp:smd_bio_info items=&quot;phone&quot; label=&quot;&quot; /&gt;\n     bgcol=&quot;bea7d0&quot; margin=&quot;1&quot; /&gt;\n&lt;/p&gt;\n</code></pre>\n\n	<p>Note in this case we have checked if the address is empty and supplied the <code>{smd_empty}</code> parameter as a convenience. You would probably enforce this upstream at data collection point, but this serves to demonstrate that you can defensively prevent smd_qr_code validation errors by employing similar techniques on certain optional fields.</p>\n\n	<h3 id=\"eg3\">Example 3: Geographic location</h3>\n\n	<p>Organising a flashmob on Mount Kilimanjaro? Make sure people can find you by publishing a QR code on your site:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_qr_code type=&quot;geo&quot;\n     data=&quot;-3.07583 | 37.35333 | 5895&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"eg4\">Example 4: Raw links</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;a href=&quot;&lt;txp:smd_qr_code\n     type=&quot;link&quot; data=&quot;myfabsite.com/vouchers&quot;\n     format=&quot;raw&quot; /&gt;&quot;&gt;Download your voucher&lt;/a&gt;\n</code></pre>\n\n	<p>While this one is just a boring <span class=\"caps\">URL</span> link there&#8217;s nothing to stop you generating a personal voucher code, perhaps after they have submitted their contact details via zem_contact_reborn, and tacking it onto the end of the <span class=\"caps\">URL</span>.</p>\n\n	<h3 id=\"eg5\">Example 5: Overriding regexes</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_qr_code\n     type=&quot;bookmark&quot; delim=&quot;#&quot;\n     validate=&quot;title, url#%^(file://|http://|ftp://).+$%&quot;\n     data=&quot;Bookmark me # ftp://stefdawson.com&quot; /&gt;\n</code></pre>\n\n	<p>A few things to note about this one:</p>\n\n	<ul>\n		<li>The delimiter has been changed because the regular expression uses pipe symbols in its definition</li>\n		<li>The title is still being validated, and will use the built-in regular expression</li>\n		<li>The &#8216;url&#8217; regex is going to be validated using the given regex <strong>including start/end delimiters</strong>. Normally we&#8217;d use a <code>/</code> but in this case we have chosen <code>%</code> as the start/end character because we&#8217;re using <code>/</code>s in the regex itself, which means we don&#8217;t have to escape them</li>\n	</ul>\n\n	<h2>Changelog</h2>\n\n	<p>31 Mar 11 | 0.10 | Initial public release<br />\n02 Apr 11 | 0.20 | Added <code>title</code> attribute for the <code>&lt;img&gt;</code> tag ; added vcard type (thanks gomedia) ; added <code>validate</code> and <code>validelim</code> to allow regex overrides ; fixed url regex ; improved error reporting ; permitted <code>debug</code> to aid in tracking down regex / validation errors<br />\n04 Apr 11 | 0.30 | Wrote more flexible, custom interface to Google Charts <span class=\"caps\">API</span> ; added <code>layout</code>, <code>transform</code> and <code>prefix</code> attributes ; improved <code>validate</code> attribute<br />\n05 Apr 11 | 0.31 | Double-quotes not single quotes for proper backslashing ; fixed callback warning when no plugin callbacks defined (both thanks gomedia)</p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 17px Georgia, sans-serif; }\n#smd_help h3 { font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help thead { border-bottom:double; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function smd_qr_code($atts, $thing=NULL) {\n	extract(lAtts(array(\n		\'type\'      => \'text\', // text/link/bookmark/sms/phone/email/contact/geo/wifi\n		\'data\'      => \'Hello world!\',\n		\'delim\'     => \'|\',\n		\'layout\'    => \'\',\n		\'transform\' => \'\',\n		\'validate\'  => \'1\', // 0=no, 1=yes, or \'validate,these,fields\' or \'validate, these|^[0-9. \\-]+$, fields\n		\'validelim\' => \',\',\n		\'format\'    => \'view\', // view/download/raw\n		\'prefix\'    => \'SMD_QR_\',\n		\'width\'     => \'200\',\n		\'height\'    => \'200\',\n		\'quality\'   => \'L\', // L/M/Q/H\n		\'margin\'    => \'4\',\n		\'bgcol\'     => \'\',\n		\'alpha\'     => \'\',\n		\'escape\'    => \'\',\n		\'title\'     => \'\',\n		\'class\'     => \'\',\n		\'html_id\'   => \'\',\n		\'wraptag\'   => \'\',\n		\'protocol\'  => \'https://\',\n		\'debug\'     => 0,\n	),$atts));\n\n	// Default layouts\n	$layouts = array(\n		\'text\'     => \"{text}\",\n		\'link\'     => \"{url}\",\n		\'bookmark\' => \"MEBKM:TITLE:{title};URL:{url};;\",\n		\'sms\'      => \"SMSTO:{phone}:{text}\",\n		\'phone\'    => \"TEL:{phone}\",\n		\'contact\'  => \"MECARD:N:{name};ADR:{address};TEL:{phone};EMAIL:{email};;\",\n		\'vcard\'    => \"BEGIN:VCARD\\r\\nN:{\".$prefix.\"formatted_name}\\r\\nFN:{name}\\r\\nEMAIL:{email}\\r\\nURL:{url}\\r\\nADR:{address}\\r\\nTEL:{phone}\\r\\nEND:VCARD\",\n		\'email\'    => \"MATMSG:TO:{email};SUB:{subject};BODY:{message};;\",\n		\'geo\'      => \"GEO:{lat},{lon},{height}\",\n		\'wifi\'     => \"WIFI:T:{wifi_protocol};S{ssid};{pass};;\",\n	);\n\n	// Default regexes\n	$re_url = \'@^(http://|https://).+@\';\n	$re_email = \"<^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$>\";\n	$re_phone = \'/^[0-9()\\-+ ]+$/\';\n	$re_msg = \'/^.+$/\';\n	$re_geo = \'/^[0-9.\\-]+$/\';\n	$re_digits = \'/^[0-9\\+\\-]+$/\';\n	$re_wifi_protocol = \'/^(WEP|WPA)$/\';\n\n	// regex array: can be overridden on a case by case basis by the validate attribute\n	$regexes = array(\n		\'text\' => $re_msg,\n		\'url\' => $re_url,\n		\'title\' => $re_msg,\n		\'phone\' => $re_phone,\n		\'name\' => $re_msg,\n		\'address\' => $re_msg,\n		\'email\' => $re_email,\n		\'subject\' => $re_msg,\n		\'message\' => $re_msg,\n		\'lat\' => $re_geo,\n		\'lon\' => $re_geo,\n		\'height\' => $re_digits,\n		\'wifi_protocol\' => $re_wifi_protocol,\n		\'ssid\' => $re_msg,\n		\'pass\' => $re_msg,\n	);\n\n	// Override / new layout\n	if ($layout) {\n		$layouts[$type] = str_replace(\'\\r\\n\', \'\n\', $layout); // Ugly newline hack\n	}\n\n	// Build the array of permitted types\n	// Each entry links the \'type\' attribute with an array of expected attributes.\n	// The regex to use for each can be looked up in the $regexes array\n	foreach($layouts as $lay => $def) {\n		preg_match_all(\'/\\{((?!\'.$prefix.\').*?)\\}/\', $def, $matches);\n		$allowed_types[$lay] = $matches[1];\n		// As a convenience, if any new fields are defined, set a default (wide-open) regex for each\n		// These default regexes can be overriden by the next code block\n		if ($layout) {\n			foreach ($matches[1] as $newfield) {\n				if(!isset($regexes[$newfield])) {\n					$regexes[$newfield] = $re_msg;\n				}\n			}\n		}\n	}\n\n	// Build the list of fields to validate and inject / overwrite any custom regexes\n	$validRest = false;\n	if ($validate == \'0\') {\n		$toValidate = array();\n	}\n	if ($validate == \'1\') {\n		$toValidate[] = \'SMD_ALL\';\n	} else {\n		$fields = do_list($validate, $validelim);\n		foreach ($fields as $fieldinfo) {\n			$fieldParts = explode($delim, $fieldinfo);\n			if ($fieldParts[0] == \'1\') {\n				$validRest = true;\n			} else {\n				$toValidate[] = $fieldParts[0];\n				if (isset($fieldParts[1])) {\n					if (strpos($fieldParts[1], \'SMD_REGEX_\') !== false) {\n						$copyFromField = strtolower(str_replace(\'SMD_REGEX_\', \'\', $fieldParts[1]));\n						if (isset($regexes[$copyFromField])) {\n							$regexes[$fieldParts[0]] = $regexes[$copyFromField];\n						}\n					} else {\n						$regexes[$fieldParts[0]] = $fieldParts[1];\n					}\n				}\n			}\n		}\n		if ($validRest && array_key_exists($type, $allowed_types)) {\n			foreach($allowed_types[$type] as $fld) {\n				if (!in_array($fld, $regexes)) {\n					$toValidate[] = $fld;\n				}\n			}\n		}\n	}\n\n	// Default transforms\n	$transforms = array(\n		\'*.url\'      => \'linkify\',\n		\'vcard.name\' => \'splitrevjoin|formatted_name| |;\',\n	);\n\n	// Add custom transform rules\n	// New methods must use register_callback(\'my_custom_transform\', \'smd_qr_code\', \'add_transform\');\n	// Function signature is: 1) data value to transform, 2) optional array of any other args packaged as name=>val entries\n	$transform = do_list($transform);\n	foreach ($transform as $xform) {\n		if (empty($xform)) continue;\n		$xformParams = do_list($xform, $delim);\n		$xformid = array_shift($xformParams);\n		$transforms[$xformid] = join($delim, $xformParams);\n	}\n\n	if ($debug) {\n		echo \'++ TRANSFORMATIONS ++\';\n		dmp($transforms);\n	}\n\n	if (array_key_exists($type, $allowed_types)) {\n		$qrc = new smd_qrcode($width, $height, $quality, $margin, $bgcol, $alpha, $escape, $protocol);\n		$params = smd_qr_code_extract($allowed_types[$type], $transforms, $regexes, $toValidate, $data, $delim, $type, $prefix, $debug);\n\n		if ($params === false) {\n			return \'\';\n		} else {\n			// Run the function that prepares the data object\n			if (array_key_exists($type, $layouts)) {\n				$qrc->get_data($layouts[$type], $params, $debug);\n			} else {\n				trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_layout\', array(\'{type}\' => $type)), E_USER_NOTICE);\n				return \'\';\n			}\n		}\n\n		if ($format == \'view\') {\n			$tribs = \'\';\n			if (!$wraptag) {\n				$tribs .= ($class) ? \' class=\"\'.$class.\'\"\' : \'\';\n				$tribs .= ($html_id) ? \' id=\"\'.$html_id.\'\"\' : \'\';\n				$tribs .= ($title) ? \' title=\"\'.$title.\'\"\' : \'\';\n			}\n\n			$content = \'<img src=\"\' . $qrc->get_link() . \'\"\'.$tribs.\' alt=\"QR code\" />\';\n\n			return ($wraptag) ? doTag($content, $wraptag, $class, \'\', $html_id) : $content;\n\n		} elseif ($format == \'download\') {\n			$file = $qrc->get_image();\n			$qrc->download_image($file);\n			return \'\';\n		} elseif ($format == \'raw\') {\n			$content = $qrc->get_link();\n			return ($wraptag) ? doTag($content, $wraptag, $class, \'\', $html_id) : $content;\n		}\n	} else {\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_type\', array(\'{type}\' => $type)), E_USER_NOTICE);\n		return \'\';\n	}\n}\n\n//------------------------\nfunction smd_qr_code_extract($permitted, $transforms, $regexList, $validateList, $data, $dlm, $tag, $prefix, $debug=false) {\n	$opts = do_list($data, $dlm);\n	$opts = (empty($opts[0])) ? array() : $opts;\n\n	$xformlist = new smd_qr_code_transforms();\n\n	$out = array();\n	$count = 0;\n\n	if ($debug) {\n		echo \'++ PERMITTED ITEMS / SUPPLIED ITEMS / REGEXES / FIELDS TO VALIDATE ++\';\n		dmp($permitted, $opts, $regexList, $validateList);\n	}\n\n	if (count($permitted) != count($opts)) {\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_attribute_mismatch\', array(\'{type}\' => $tag, \'{count}\' => count($opts), \'{expected}\' => count($permitted), \'{fields}\' => join(\', \', $permitted))), E_USER_NOTICE);\n		return false;\n	}\n\n	foreach ($permitted as $idx) {\n		if (isset($opts[$count])) {\n			$dataval = $opts[$count];\n			if ($dataval == \'{smd_empty}\') {\n				$out[$idx] = \'\';\n			} else {\n\n				// Perform any transformations\n				$apply_to = $tag.\'.\'.$idx;\n				$xkey = \'\';\n\n				if (array_key_exists($apply_to, $transforms)) {\n					$xkey = $apply_to;\n				} else if (array_key_exists(\'*.\'.$idx, $transforms)) {\n					$xkey = \'*.\'.$idx;\n				}\n\n				if ($xkey) {\n					$xform = $transforms[$xkey];\n					$xformVals = do_list($xkey, \'.\');\n					$xformArgs = explode($dlm, $xform);\n					$fn = array_shift($xformArgs);\n\n					if ($idx == $xformVals[1]) {\n						// Pass the remaining args to the nominated function...\n						$result = $xformlist->execute($fn, $dataval, $xformArgs);\n\n						// .. and either override the current value or add new ones\n						if (is_array($result)) {\n							foreach($result as $inarg => $inval) {\n								if ($inarg == $idx) {\n									$dataval = $inval;\n								} else {\n									// Inject new tags created by transforms directly into the outstream\n									$out[$prefix.$inarg] = $inval;\n								}\n							}\n						} else {\n							$dataval = $result;\n						}\n					}\n				}\n\n				// Perform validation if required\n				if (in_array(\'SMD_ALL\', $validateList) || in_array($idx, $validateList)) {\n					$re = isset($regexList[$idx]) ? $regexList[$idx] : \'\';\n\n					if ($re) {\n						if ($debug > 1) {\n							dmp(\'VALIDATING \' . $idx . \' using \' . $re);\n						}\n						if (preg_match($re, $dataval)) {\n							$out[$idx] = $dataval;\n						} else {\n							trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_attribute\', array(\'{attrib}\' => $idx, \'{tag}\' => $tag)), E_USER_NOTICE);\n							return false;\n						}\n					} else {\n						$out[$idx] = $dataval;\n						trigger_error(smd_qr_code_gTxt(\'smd_qr_code_no_regex\', array(\'{attrib}\' => $idx, \'{tag}\' => $tag)), E_USER_WARNING);\n					}\n				} else {\n					// Not validating, so store it verbatim\n					if ($debug > 1) {\n						dmp(\'NOT VALIDATING \' . $idx);\n					}\n					$out[$idx] = $dataval;\n				}\n			}\n		} else {\n			// TODO: erm...?\n		}\n		$count++;\n	}\n					\n	return $out;\n}\n\n//------------------------\nfunction smd_qr_code_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'smd_qr_code_attribute_mismatch\' => \"Number of parameters don\'t match in \'{type}\' QR code: given {count}, expecting {expected} (Fields: {fields})\",\n		\'smd_qr_code_invalid_attribute\' => \"Invalid attribute \'{attrib}\' supplied in \'{tag}\' QR code. Check the format and order of data items\",\n		\'smd_qr_code_invalid_layout\' => \"Invalid QR code layout \'{type}\'\",\n		\'smd_qr_code_invalid_transform\' => \"Invalid or unregistered transform function \'{fn}\' -- ignoring\",\n		\'smd_qr_code_invalid_type\' => \"Invalid QR code type \'{type}\'\",\n		\'smd_qr_code_no_regex\' => \"No regex found for \'{attrib}\' in \'{tag}\' QR code -- ignoring\",\n		\'smd_qr_code_too_large\' => \"QR code data request too large\",\n	);\n	return strtr($lang[$what], $atts);\n}\n\n//------------------------\nclass smd_qrcode\n{\n	private $api = \'\';\n	private $data;\n	private $dimensions = \'\';\n	private $quality = \'\';\n	private $colour = \'\';\n	private $escape = \'\';\n\n	// Constructor\n	public function smd_qrcode($width, $height, $qual, $margin, $bgcol, $alpha, $esc, $prot) {\n		$this->escape = $esc;\n		$this->api = $prot . \'chart.apis.google.com/chart\';\n\n		// Build API strings\n		$w = (is_numeric($width)) ? $width : 200;\n		$h = (is_numeric($height)) ? $height : 200;\n		$this->dimensions = $w.\'x\'.$h;\n\n		$colours = array();\n		if ($alpha) $colours[] = \'a,s,000000\'.substr($alpha, -2);\n		if ($bgcol) $colours[] = \'bg,s,\'.$bgcol;\n		$this->colour = ($colours) ? join(\'|\', $colours) : \'\';\n\n		$qual = strtoupper($qual);\n		$szopts[] = ($qual && in_array($qual, array(\'L\', \'M\', \'Q\', \'H\'))) ? $qual : \'L\';\n		if (is_numeric($margin)) $szopts[] = $margin;\n		$this->quality = join(\'|\', $szopts);\n	}\n\n	// Create the data object from the given layout, plugging in the replacement variables and applying any transformations\n	public function get_data($layout, $args, $debug=0) {\n		// Create the replacements\n		$reps = array();\n\n		foreach ($args as $arg => $val) {\n			$reps[\'{\'.$arg.\'}\'] = $val;\n		}\n\n		if ($debug) {\n			echo \'++ REPLACEMENTS ++\';\n			dmp($reps);\n		}\n\n		$this->data = strtr($layout, $reps);\n\n		// Barf if above roughly 2KB (the limit of the Charts API\'s GET request)\n		if (strlen($this->data) > 2000) {\n			trigger_error(smd_qr_code_gTxt(\'smd_qr_code_too_large\'), E_USER_NOTICE);\n		}\n	}\n\n	private function get_url_params() {\n		$params = array(\n			\'cht\' => \'qr\',\n			\'chs\' => $this->dimensions,\n			\'chld\' => $this->quality,\n			\'chl\' => (($this->escape == \'html\') ? urlencode($this->data) : $this->data),\n		);\n\n		// Colour is optional\n		if ($this->colour) {\n			$params[\'chf\'] = $this->colour;\n		}\n\n		return http_build_query($params, \'\', \'&amp;\');\n	}\n\n	// get image\n	public function get_image(){\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $this->api);\n		curl_setopt($ch, CURLOPT_POST, true);\n		curl_setopt($ch, CURLOPT_POSTFIELDS, $this->get_url_params());\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n		curl_setopt($ch, CURLOPT_HEADER, false);\n		curl_setopt($ch, CURLOPT_TIMEOUT, 30);\n\n		$response = curl_exec($ch);\n		curl_close($ch);\n		return $response;\n	}\n\n	// get link for image\n	public function get_link($follow=false){\n		return $this->api.\'?\'.$this->get_url_params();\n	}\n\n	// force image download\n	public function download_image($file){\n		header(\'Content-Description: File Transfer\');\n		header(\'Content-Type: image/png\');\n		header(\'Content-Disposition: attachment; filename=qr_code.png\');\n		header(\'Content-Transfer-Encoding: binary\');\n		header(\'Expires: 0\');\n		header(\'Cache-Control: must-revalidate, post-check=0, pre-check=0\');\n		header(\'Pragma: public\');\n		header(\'Content-Length: \' . filesize($file));\n		ob_clean();\n		flush();\n		echo $file;\n	}\n}\n\n// ***************\n// Transformations\n// ***************\n//------------------------\nclass smd_qr_code_transforms\n{\n	private $xforms = array(\n		\'linkify\',\n		\'splitrevjoin\',\n	);\n\n	// Constructor : register callbacks and built-in transforms\n	public function smd_qr_code_transforms() {\n		global $plugin_callback;\n		if (is_array($plugin_callback)) {\n			foreach ($plugin_callback as $cb) {\n				if ($cb[\'event\'] == \'smd_qr_code\' && $cb[\'step\'] == \'add_transform\') {\n					$this->xforms[] = $cb[\'function\'];\n				}\n			}\n		}\n	}\n\n	// Run a registered custom function. Local functions trump externals\n	public function execute($fn, $item, $args=array()) {\n		if (in_array($fn, $this->xforms)) {\n			if (method_exists($this, $fn)) {\n				return $this->$fn($item, $args);\n			} else if (is_callable($fn)) {\n				if (empty($args)) {\n					return $fn($item);\n				} else {\n					return $fn($item, $args);\n				}\n			}\n		}\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_transform\', array(\'{fn}\' => $fn)), E_USER_WARNING);\n		return $item;\n	}\n\n	// Prefix links\n	public function linkify($item, $args=array()) {\n		$outvar = (isset($args[0])) ? $args[0] : \'url\';\n\n		if (preg_match(\'/^http:\\/\\//\', $item) || preg_match(\'/^https:\\/\\//\', $item)) {\n			$outval = $item;\n		} else {\n			$outval = \"http://\".$item;\n		}\n		return array($outvar => $outval);\n	}\n\n	// Split a variable, reverse the elements and join them again\n	public function splitrevjoin($item, $args=array()) {\n		// $args: 0 = variable name, 1 = split char, 2 = join char\n		if (count($args) == 3) {\n			$outval = join($args[2], array_reverse(explode($args[1], $item)));\n			return array($args[0] => $outval);\n		} else {\n			return $item;\n		}\n	}\n}', 'function smd_qr_code($atts, $thing=NULL) {\n	extract(lAtts(array(\n		\'type\'      => \'text\', // text/link/bookmark/sms/phone/email/contact/geo/wifi\n		\'data\'      => \'Hello world!\',\n		\'delim\'     => \'|\',\n		\'layout\'    => \'\',\n		\'transform\' => \'\',\n		\'validate\'  => \'1\', // 0=no, 1=yes, or \'validate,these,fields\' or \'validate, these|^[0-9. \\-]+$, fields\n		\'validelim\' => \',\',\n		\'format\'    => \'view\', // view/download/raw\n		\'prefix\'    => \'SMD_QR_\',\n		\'width\'     => \'200\',\n		\'height\'    => \'200\',\n		\'quality\'   => \'L\', // L/M/Q/H\n		\'margin\'    => \'4\',\n		\'bgcol\'     => \'\',\n		\'alpha\'     => \'\',\n		\'escape\'    => \'\',\n		\'title\'     => \'\',\n		\'class\'     => \'\',\n		\'html_id\'   => \'\',\n		\'wraptag\'   => \'\',\n		\'protocol\'  => \'https://\',\n		\'debug\'     => 0,\n	),$atts));\n\n	// Default layouts\n	$layouts = array(\n		\'text\'     => \"{text}\",\n		\'link\'     => \"{url}\",\n		\'bookmark\' => \"MEBKM:TITLE:{title};URL:{url};;\",\n		\'sms\'      => \"SMSTO:{phone}:{text}\",\n		\'phone\'    => \"TEL:{phone}\",\n		\'contact\'  => \"MECARD:N:{name};ADR:{address};TEL:{phone};EMAIL:{email};;\",\n		\'vcard\'    => \"BEGIN:VCARD\\r\\nN:{\".$prefix.\"formatted_name}\\r\\nFN:{name}\\r\\nEMAIL:{email}\\r\\nURL:{url}\\r\\nADR:{address}\\r\\nTEL:{phone}\\r\\nEND:VCARD\",\n		\'email\'    => \"MATMSG:TO:{email};SUB:{subject};BODY:{message};;\",\n		\'geo\'      => \"GEO:{lat},{lon},{height}\",\n		\'wifi\'     => \"WIFI:T:{wifi_protocol};S{ssid};{pass};;\",\n	);\n\n	// Default regexes\n	$re_url = \'@^(http://|https://).+@\';\n	$re_email = \"<^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$>\";\n	$re_phone = \'/^[0-9()\\-+ ]+$/\';\n	$re_msg = \'/^.+$/\';\n	$re_geo = \'/^[0-9.\\-]+$/\';\n	$re_digits = \'/^[0-9\\+\\-]+$/\';\n	$re_wifi_protocol = \'/^(WEP|WPA)$/\';\n\n	// regex array: can be overridden on a case by case basis by the validate attribute\n	$regexes = array(\n		\'text\' => $re_msg,\n		\'url\' => $re_url,\n		\'title\' => $re_msg,\n		\'phone\' => $re_phone,\n		\'name\' => $re_msg,\n		\'address\' => $re_msg,\n		\'email\' => $re_email,\n		\'subject\' => $re_msg,\n		\'message\' => $re_msg,\n		\'lat\' => $re_geo,\n		\'lon\' => $re_geo,\n		\'height\' => $re_digits,\n		\'wifi_protocol\' => $re_wifi_protocol,\n		\'ssid\' => $re_msg,\n		\'pass\' => $re_msg,\n	);\n\n	// Override / new layout\n	if ($layout) {\n		$layouts[$type] = str_replace(\'\\r\\n\', \'\n\', $layout); // Ugly newline hack\n	}\n\n	// Build the array of permitted types\n	// Each entry links the \'type\' attribute with an array of expected attributes.\n	// The regex to use for each can be looked up in the $regexes array\n	foreach($layouts as $lay => $def) {\n		preg_match_all(\'/\\{((?!\'.$prefix.\').*?)\\}/\', $def, $matches);\n		$allowed_types[$lay] = $matches[1];\n		// As a convenience, if any new fields are defined, set a default (wide-open) regex for each\n		// These default regexes can be overriden by the next code block\n		if ($layout) {\n			foreach ($matches[1] as $newfield) {\n				if(!isset($regexes[$newfield])) {\n					$regexes[$newfield] = $re_msg;\n				}\n			}\n		}\n	}\n\n	// Build the list of fields to validate and inject / overwrite any custom regexes\n	$validRest = false;\n	if ($validate == \'0\') {\n		$toValidate = array();\n	}\n	if ($validate == \'1\') {\n		$toValidate[] = \'SMD_ALL\';\n	} else {\n		$fields = do_list($validate, $validelim);\n		foreach ($fields as $fieldinfo) {\n			$fieldParts = explode($delim, $fieldinfo);\n			if ($fieldParts[0] == \'1\') {\n				$validRest = true;\n			} else {\n				$toValidate[] = $fieldParts[0];\n				if (isset($fieldParts[1])) {\n					if (strpos($fieldParts[1], \'SMD_REGEX_\') !== false) {\n						$copyFromField = strtolower(str_replace(\'SMD_REGEX_\', \'\', $fieldParts[1]));\n						if (isset($regexes[$copyFromField])) {\n							$regexes[$fieldParts[0]] = $regexes[$copyFromField];\n						}\n					} else {\n						$regexes[$fieldParts[0]] = $fieldParts[1];\n					}\n				}\n			}\n		}\n		if ($validRest && array_key_exists($type, $allowed_types)) {\n			foreach($allowed_types[$type] as $fld) {\n				if (!in_array($fld, $regexes)) {\n					$toValidate[] = $fld;\n				}\n			}\n		}\n	}\n\n	// Default transforms\n	$transforms = array(\n		\'*.url\'      => \'linkify\',\n		\'vcard.name\' => \'splitrevjoin|formatted_name| |;\',\n	);\n\n	// Add custom transform rules\n	// New methods must use register_callback(\'my_custom_transform\', \'smd_qr_code\', \'add_transform\');\n	// Function signature is: 1) data value to transform, 2) optional array of any other args packaged as name=>val entries\n	$transform = do_list($transform);\n	foreach ($transform as $xform) {\n		if (empty($xform)) continue;\n		$xformParams = do_list($xform, $delim);\n		$xformid = array_shift($xformParams);\n		$transforms[$xformid] = join($delim, $xformParams);\n	}\n\n	if ($debug) {\n		echo \'++ TRANSFORMATIONS ++\';\n		dmp($transforms);\n	}\n\n	if (array_key_exists($type, $allowed_types)) {\n		$qrc = new smd_qrcode($width, $height, $quality, $margin, $bgcol, $alpha, $escape, $protocol);\n		$params = smd_qr_code_extract($allowed_types[$type], $transforms, $regexes, $toValidate, $data, $delim, $type, $prefix, $debug);\n\n		if ($params === false) {\n			return \'\';\n		} else {\n			// Run the function that prepares the data object\n			if (array_key_exists($type, $layouts)) {\n				$qrc->get_data($layouts[$type], $params, $debug);\n			} else {\n				trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_layout\', array(\'{type}\' => $type)), E_USER_NOTICE);\n				return \'\';\n			}\n		}\n\n		if ($format == \'view\') {\n			$tribs = \'\';\n			if (!$wraptag) {\n				$tribs .= ($class) ? \' class=\"\'.$class.\'\"\' : \'\';\n				$tribs .= ($html_id) ? \' id=\"\'.$html_id.\'\"\' : \'\';\n				$tribs .= ($title) ? \' title=\"\'.$title.\'\"\' : \'\';\n			}\n\n			$content = \'<img src=\"\' . $qrc->get_link() . \'\"\'.$tribs.\' alt=\"QR code\" />\';\n\n			return ($wraptag) ? doTag($content, $wraptag, $class, \'\', $html_id) : $content;\n\n		} elseif ($format == \'download\') {\n			$file = $qrc->get_image();\n			$qrc->download_image($file);\n			return \'\';\n		} elseif ($format == \'raw\') {\n			$content = $qrc->get_link();\n			return ($wraptag) ? doTag($content, $wraptag, $class, \'\', $html_id) : $content;\n		}\n	} else {\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_type\', array(\'{type}\' => $type)), E_USER_NOTICE);\n		return \'\';\n	}\n}\n\n//------------------------\nfunction smd_qr_code_extract($permitted, $transforms, $regexList, $validateList, $data, $dlm, $tag, $prefix, $debug=false) {\n	$opts = do_list($data, $dlm);\n	$opts = (empty($opts[0])) ? array() : $opts;\n\n	$xformlist = new smd_qr_code_transforms();\n\n	$out = array();\n	$count = 0;\n\n	if ($debug) {\n		echo \'++ PERMITTED ITEMS / SUPPLIED ITEMS / REGEXES / FIELDS TO VALIDATE ++\';\n		dmp($permitted, $opts, $regexList, $validateList);\n	}\n\n	if (count($permitted) != count($opts)) {\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_attribute_mismatch\', array(\'{type}\' => $tag, \'{count}\' => count($opts), \'{expected}\' => count($permitted), \'{fields}\' => join(\', \', $permitted))), E_USER_NOTICE);\n		return false;\n	}\n\n	foreach ($permitted as $idx) {\n		if (isset($opts[$count])) {\n			$dataval = $opts[$count];\n			if ($dataval == \'{smd_empty}\') {\n				$out[$idx] = \'\';\n			} else {\n\n				// Perform any transformations\n				$apply_to = $tag.\'.\'.$idx;\n				$xkey = \'\';\n\n				if (array_key_exists($apply_to, $transforms)) {\n					$xkey = $apply_to;\n				} else if (array_key_exists(\'*.\'.$idx, $transforms)) {\n					$xkey = \'*.\'.$idx;\n				}\n\n				if ($xkey) {\n					$xform = $transforms[$xkey];\n					$xformVals = do_list($xkey, \'.\');\n					$xformArgs = explode($dlm, $xform);\n					$fn = array_shift($xformArgs);\n\n					if ($idx == $xformVals[1]) {\n						// Pass the remaining args to the nominated function...\n						$result = $xformlist->execute($fn, $dataval, $xformArgs);\n\n						// .. and either override the current value or add new ones\n						if (is_array($result)) {\n							foreach($result as $inarg => $inval) {\n								if ($inarg == $idx) {\n									$dataval = $inval;\n								} else {\n									// Inject new tags created by transforms directly into the outstream\n									$out[$prefix.$inarg] = $inval;\n								}\n							}\n						} else {\n							$dataval = $result;\n						}\n					}\n				}\n\n				// Perform validation if required\n				if (in_array(\'SMD_ALL\', $validateList) || in_array($idx, $validateList)) {\n					$re = isset($regexList[$idx]) ? $regexList[$idx] : \'\';\n\n					if ($re) {\n						if ($debug > 1) {\n							dmp(\'VALIDATING \' . $idx . \' using \' . $re);\n						}\n						if (preg_match($re, $dataval)) {\n							$out[$idx] = $dataval;\n						} else {\n							trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_attribute\', array(\'{attrib}\' => $idx, \'{tag}\' => $tag)), E_USER_NOTICE);\n							return false;\n						}\n					} else {\n						$out[$idx] = $dataval;\n						trigger_error(smd_qr_code_gTxt(\'smd_qr_code_no_regex\', array(\'{attrib}\' => $idx, \'{tag}\' => $tag)), E_USER_WARNING);\n					}\n				} else {\n					// Not validating, so store it verbatim\n					if ($debug > 1) {\n						dmp(\'NOT VALIDATING \' . $idx);\n					}\n					$out[$idx] = $dataval;\n				}\n			}\n		} else {\n			// TODO: erm...?\n		}\n		$count++;\n	}\n					\n	return $out;\n}\n\n//------------------------\nfunction smd_qr_code_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'smd_qr_code_attribute_mismatch\' => \"Number of parameters don\'t match in \'{type}\' QR code: given {count}, expecting {expected} (Fields: {fields})\",\n		\'smd_qr_code_invalid_attribute\' => \"Invalid attribute \'{attrib}\' supplied in \'{tag}\' QR code. Check the format and order of data items\",\n		\'smd_qr_code_invalid_layout\' => \"Invalid QR code layout \'{type}\'\",\n		\'smd_qr_code_invalid_transform\' => \"Invalid or unregistered transform function \'{fn}\' -- ignoring\",\n		\'smd_qr_code_invalid_type\' => \"Invalid QR code type \'{type}\'\",\n		\'smd_qr_code_no_regex\' => \"No regex found for \'{attrib}\' in \'{tag}\' QR code -- ignoring\",\n		\'smd_qr_code_too_large\' => \"QR code data request too large\",\n	);\n	return strtr($lang[$what], $atts);\n}\n\n//------------------------\nclass smd_qrcode\n{\n	private $api = \'\';\n	private $data;\n	private $dimensions = \'\';\n	private $quality = \'\';\n	private $colour = \'\';\n	private $escape = \'\';\n\n	// Constructor\n	public function smd_qrcode($width, $height, $qual, $margin, $bgcol, $alpha, $esc, $prot) {\n		$this->escape = $esc;\n		$this->api = $prot . \'chart.apis.google.com/chart\';\n\n		// Build API strings\n		$w = (is_numeric($width)) ? $width : 200;\n		$h = (is_numeric($height)) ? $height : 200;\n		$this->dimensions = $w.\'x\'.$h;\n\n		$colours = array();\n		if ($alpha) $colours[] = \'a,s,000000\'.substr($alpha, -2);\n		if ($bgcol) $colours[] = \'bg,s,\'.$bgcol;\n		$this->colour = ($colours) ? join(\'|\', $colours) : \'\';\n\n		$qual = strtoupper($qual);\n		$szopts[] = ($qual && in_array($qual, array(\'L\', \'M\', \'Q\', \'H\'))) ? $qual : \'L\';\n		if (is_numeric($margin)) $szopts[] = $margin;\n		$this->quality = join(\'|\', $szopts);\n	}\n\n	// Create the data object from the given layout, plugging in the replacement variables and applying any transformations\n	public function get_data($layout, $args, $debug=0) {\n		// Create the replacements\n		$reps = array();\n\n		foreach ($args as $arg => $val) {\n			$reps[\'{\'.$arg.\'}\'] = $val;\n		}\n\n		if ($debug) {\n			echo \'++ REPLACEMENTS ++\';\n			dmp($reps);\n		}\n\n		$this->data = strtr($layout, $reps);\n\n		// Barf if above roughly 2KB (the limit of the Charts API\'s GET request)\n		if (strlen($this->data) > 2000) {\n			trigger_error(smd_qr_code_gTxt(\'smd_qr_code_too_large\'), E_USER_NOTICE);\n		}\n	}\n\n	private function get_url_params() {\n		$params = array(\n			\'cht\' => \'qr\',\n			\'chs\' => $this->dimensions,\n			\'chld\' => $this->quality,\n			\'chl\' => (($this->escape == \'html\') ? urlencode($this->data) : $this->data),\n		);\n\n		// Colour is optional\n		if ($this->colour) {\n			$params[\'chf\'] = $this->colour;\n		}\n\n		return http_build_query($params, \'\', \'&amp;\');\n	}\n\n	// get image\n	public function get_image(){\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $this->api);\n		curl_setopt($ch, CURLOPT_POST, true);\n		curl_setopt($ch, CURLOPT_POSTFIELDS, $this->get_url_params());\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n		curl_setopt($ch, CURLOPT_HEADER, false);\n		curl_setopt($ch, CURLOPT_TIMEOUT, 30);\n\n		$response = curl_exec($ch);\n		curl_close($ch);\n		return $response;\n	}\n\n	// get link for image\n	public function get_link($follow=false){\n		return $this->api.\'?\'.$this->get_url_params();\n	}\n\n	// force image download\n	public function download_image($file){\n		header(\'Content-Description: File Transfer\');\n		header(\'Content-Type: image/png\');\n		header(\'Content-Disposition: attachment; filename=qr_code.png\');\n		header(\'Content-Transfer-Encoding: binary\');\n		header(\'Expires: 0\');\n		header(\'Cache-Control: must-revalidate, post-check=0, pre-check=0\');\n		header(\'Pragma: public\');\n		header(\'Content-Length: \' . filesize($file));\n		ob_clean();\n		flush();\n		echo $file;\n	}\n}\n\n// ***************\n// Transformations\n// ***************\n//------------------------\nclass smd_qr_code_transforms\n{\n	private $xforms = array(\n		\'linkify\',\n		\'splitrevjoin\',\n	);\n\n	// Constructor : register callbacks and built-in transforms\n	public function smd_qr_code_transforms() {\n		global $plugin_callback;\n		if (is_array($plugin_callback)) {\n			foreach ($plugin_callback as $cb) {\n				if ($cb[\'event\'] == \'smd_qr_code\' && $cb[\'step\'] == \'add_transform\') {\n					$this->xforms[] = $cb[\'function\'];\n				}\n			}\n		}\n	}\n\n	// Run a registered custom function. Local functions trump externals\n	public function execute($fn, $item, $args=array()) {\n		if (in_array($fn, $this->xforms)) {\n			if (method_exists($this, $fn)) {\n				return $this->$fn($item, $args);\n			} else if (is_callable($fn)) {\n				if (empty($args)) {\n					return $fn($item);\n				} else {\n					return $fn($item, $args);\n				}\n			}\n		}\n		trigger_error(smd_qr_code_gTxt(\'smd_qr_code_invalid_transform\', array(\'{fn}\' => $fn)), E_USER_WARNING);\n		return $item;\n	}\n\n	// Prefix links\n	public function linkify($item, $args=array()) {\n		$outvar = (isset($args[0])) ? $args[0] : \'url\';\n\n		if (preg_match(\'/^http:\\/\\//\', $item) || preg_match(\'/^https:\\/\\//\', $item)) {\n			$outval = $item;\n		} else {\n			$outval = \"http://\".$item;\n		}\n		return array($outvar => $outval);\n	}\n\n	// Split a variable, reverse the elements and join them again\n	public function splitrevjoin($item, $args=array()) {\n		// $args: 0 = variable name, 1 = split char, 2 = join char\n		if (count($args) == 3) {\n			$outval = join($args[2], array_reverse(explode($args[1], $item)));\n			return array($args[0] => $outval);\n		} else {\n			return $item;\n		}\n	}\n}', 'cd5405dd1fc7b1b19b7207739042ae06', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_slimbox', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.34', 'Image gallery lightbox wrapper that can use Slimbox, Litebox, Moonbox, Thickbox, etc', '<div id=\"smd_help\">\n\n	<h1 id=\"top\">smd_slimbox</h1>\n\n	<h2 id=\"features\">Features</h2>\n\n	<ul>\n		<li>Generates a list of thumbnails that can be styled any way you like</li>\n		<li>Flexible way of extracting images from the <span class=\"caps\">TXP</span> database; arbitrary inclusion/exclusion of categories or image IDs</li>\n		<li>Lists of categories and IDs can be pulled from any field in the current article, including custom fields</li>\n		<li>Supports glz_custom_fields for even greater flexibility (currently requires a small mod to publish.php)</li>\n		<li>Subcategories: specify a parent and all child categories are automatically included</li>\n		<li>Show associated image text in various locations around the thumbnails</li>\n		<li>Paging and multiple galleries per page</li>\n		<li>Supports Slimbox, Lightbox, Litebox, Moonbox, Thickbox and perhaps some more besides. For a wider range, see <a href=\"http://textpattern.org/plugins/957/smd_gallery\">smd_gallery</a></li>\n	</ul>\n\n	<h2 id=\"author\">Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>&gt;. For other software by me, or to make a donation, see the <a href=\"http://stefdawson.com/sw\">software page</a>.</p>\n\n	<p>Originally based on <a href=\"http://www.digitalia.be/software/slimbox\">slimbox, a lightweight Lightbox clone</a> by Christophe Beyls. For more info or troubleshooting on the lightbox side of things (as opposed to the thumbnail generation side of things) <a href=\"http://www.freewebsfarms.com/forums/viewtopic.php?pid=3316#p3316\">see Christophe&#8217;s forum</a>.</p>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p class=\"important\">Requires <a href=\"http://stefdawson.com/smd_lib\">smd_lib</a> to be installed and activated.</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/741/smd_slimbox\">textpattern.org</a>, or the &#8220;software page:&#8220;http://stefdawson.com/sw, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin.</p>\n\n	<p>You will also need a javascript library to perform the lightbox effect, which you must load on your page as per the instructions for that library. Although the examples in this page are for Slimbox, the plugin will work with a number of freely-available javascript libraries &#8212; often without modification. Some libraries (e.g. Thickbox) simply require an additional class to be set on the image tags.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=20714\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To remove the plugin, simply delete it from the Admin-&gt;Plugins tab and remove your third party libraries.</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>The plugin options govern which thumbnails to use and how they are displayed. Once a thumb is clicked, control is handed over to mootools/jQuery/prototype to do the fancy work. Therefore, if you wish to configure the look of the &#8220;lightbox&#8221; part, you need to edit its javascript and/or associated <span class=\"caps\">CSS</span>.</p>\n\n	<p>Before you can use smd_slimbox you have to ensure that the page can read the external library files. You do this by putting <code>&lt;script&gt;</code> and <code>&lt;link&gt;</code> tags as usual in your page <code>&lt;head&gt;</code>.</p>\n\n	<h3 id=\"changes\">Changes from 0.2x versions</h3>\n\n	<p>Experienced users of smd_slimbox, please note these important changes to the plugin:</p>\n\n	<ul>\n		<li>After overwhelming support from the poll, <code>smd_slimbox_inc</code> is now removed. If you use it, a warning will be issued</li>\n		<li>Accordingly, <code>libcheck</code> has been removed</li>\n		<li><code>catlabel</code> has been removed. With more <code>?</code> and <code>!</code> <a href=\"#fields\">article field codes</a> now available, and the ability to combine them (e.g. ?s-?c) you can now perform the same function as <code>catlabel</code> by using <code>category=&quot;article-?id&quot;</code></li>\n		<li><code>subcats</code> improved. Now takes an integer &#8220;level&#8221; that specifies how far down the tree to go. For the same behaviour as before, use <code>subcats=&quot;all&quot;</code></li>\n		<li>showalt/showcaption/altclass/captionclass/textpos have been removed. Use <code>thumbtext</code>, <code>thumbtextwrap</code> and <code>thumbtextclass</code> instead</li>\n		<li><code>orderby</code> is now called <code>sort</code> in line with other <span class=\"caps\">TXP</span> tags, and takes a space-delimited direction modifier instead of the old colon delimiter. <code>orderby</code> is still supported for now and will be converted to the new <code>sort</code> tag but will issue a warning in production/debug modes</li>\n		<li><code>thumb</code> is now enhanced to allow you to specify whether the full-size image and/or the thumbnail are displayed using the &#8216;t&#8217; image or not. So you can now swap thumbnail and full-size if you wish, or make them show identical images</li>\n		<li><code>trigger</code> added. This defaults to &#8220;lightbox-&#8221; which is what slimbox uses in the <code>rel</code> attribute of the anchors. Other libraries may use other words to identify themselves, so this allows you to modify the string to suit</li>\n		<li><code>delim</code> added. If you don&#8217;t like using a comma as a delimiter, change it. Set <code>delim=&quot;|&quot;</code> if using checkboxes and multi-selects with glz_custom_fields</li>\n		<li><code>navwraptag</code> added so the navigation block can be wrapped in a different tag to each image</li>\n		<li><code>linkclass</code> added so you can specify a separate class for the image anchors if you wish</li>\n	</ul>\n\n	<h3 class=\"atts\" id=\"atts\">Attributes</h3>\n\n	<p>Without any options the plugin displays every image in your entire collection and won&#8217;t look very pretty (though you can help a bit by starting with the <a href=\"#style\"><span class=\"caps\">CSS</span></a>). The following options tame the plugin a little:</p>\n\n	<h4>Filters</h4>\n\n	<ul class=\"atts\">\n		<li><span class=\"atnm\">category</span> : a list of image categories. By default the list should be comma-separated but this can be changed with the <code>delim</code> option. This attribute may take any of the special <a href=\"#fields\">field codes</a> by giving its name after a <code>?</code> (to include that field) or <code>!</code> (to exclude that field)</li>\n		<li><span class=\"atnm\">imageid</span> : a list of image IDs. Takes exactly the same syntax as the <code>category</code> attribute</li>\n		<li><span class=\"atnm\">subcats</span> : how far &#8216;down&#8217; to traverse image sub-categories. Takes an integer value or &#8216;all&#8217;. 0 (the default) means no child categories; 1 uses the main categories and the first level of children; 2 uses the main categories and up to two child sub-categories; and so on</li>\n		<li><span class=\"atnm\">sort</span> : a list of ways to order the results; by default it is by category. Specify the sort order by adding <code>asc</code> or <code>desc</code> to any value, e.g. <code>sort=&quot;category, date desc&quot;</code> would sort by category first, then descending date. Takes any of the <a href=\"#image_fields\">image field codes</a> plus two special codes:<div class=\"break\"><code>fixed</code> forces thumbs to be output in the order given in <code>imageid</code>.</div><div class=\"break\"><code>random</code> acts slightly differently from the others: if it appears as the first item in the list, random images will be pulled from the database and they will then be sorted according to the options listed after it. However, if it appears anywhere else in the list, any sort items before it will determine the order of data pulled from the database, and the results will <em>then</em> be ordered randomly (in that case, any items listed after <code>random</code> will be ignored, though <code>fixed</code> is still honoured). See <a href=\"#eg2\">example 2</a> for more</div></li>\n	</ul>\n\n	<h4>Limits / sizes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">limit</span> : the number of image thumbnails to show per page. 0 (or omitted) means essentially unlimited</li>\n		<li><span class=\"atnm\">maxlimit</span> : the absolute maximum number of images to pull from the database. 0 (or omitted) means essentially unlimited</li>\n		<li><span class=\"atnm\">thumbsize</span> : either for special effects, or in order to validate against <span class=\"caps\">XHTML</span>, the image thumbnails should have <code>width</code> and <code>height</code> attributes. <span class=\"caps\">TXP</span> cannot supply this automatically, but this attribute will.<div class=\"break\">Simply specify up to two values separated by a comma (you can override this with <code>delim</code>) to define the width and height of your thumbnails (in pixels by default). For example: <code>thumbsize=&quot;40, 60&quot;</code> will set 40-pixel x 60-pixel thumbnails. Supplying only one value will set both width and height to the same value. Adding units after each value (&#8220;px&#8221;, &#8220;em&#8221;, &#8220;%&#8221; etc) will use those units instead of the default pixels.</div><div class=\"break\">To specify just the width, put the comma in but leave the second parameter empty (e.g. <code>thumbsize=&quot;2em,&quot;</code>). Similarly to specify the height only, leave the first value empty by starting with a comma (e.g. <code>thumbsize=&quot;,10%&quot;</code>)</div></li>\n	</ul>\n\n	<h4>Grouping / paging images</h4>\n\n	<ul>\n		<li><span class=\"atnm\">groupname</span> : when making multiple galleries or for special behaviours, this attribute allows you to logically &#8220;group&#8221; thumbnails. Once a visitor clicks a thumbnail and control is passed to the javascript code, navigation is via the Next/Prev links. Without supplying a <code>groupname</code>, <em>all</em> lightboxable images from <em>all</em> smd_slimbox tags will be included in the navigation. So if you had 3 slimbox tags on the page, each with 20 images in them, the lightbox would show &#8220;Image 1 of 60&#8221; and allow you to navigate among all of them.<div class=\"break\">On article list pages, this is often undesirable so to change this behaviour, give each slimbox tag a unique <code>groupname</code>; that will limit the navigation only to the group of images in that tag. <code>groupname</code> can be any text you like (it defaults to &#8220;smd&#8221;), or you can supply any of the <a href=\"#image_fields\">image field codes</a> to slice and dice the thumbnails any way you choose. See <a href=\"#eg4\">example 4</a> for more</div></li>\n		<li><span class=\"atnm\">trigger</span> : in order to indicate to the javascript library that images are to be &#8220;lightboxed&#8221; the &#8216;rel&#8217; attriute of anchor images is often used (thickbox is the exception and uses its own class). Since the method of indicating how to lightbox an image varies, this attribute can be tailored to match whichever javascript library you are using (see their instructions). By default, it is set to &#8220;lightbox-&#8221; which should cover most major lightbox gallery effects. If you wish to remove the <code>rel</code>, empty both <code>trigger</code> and <code>groupname</code> attributes</li>\n		<li><span class=\"atnm\">showpagelinks</span> : displays clickable next/previous links under the thumbnails, allowing you to navigate from page to page and only show <code>limit</code> thumbs per page. It is on (<code>1</code>) by default if limit is greater than 0. Use <code>showpagelinks=&quot;0&quot;</code> to turn it off</li>\n	</ul>\n\n	<h4>Adding text to images</h4>\n\n	<ul>\n		<li><span class=\"atnm\">imagecaption</span> : the text to display under the large lightbox image. It can be any fixed string (rather pointless!) or a combination of fixed text and <a href=\"#image_fields\">image field codes</a></li>\n		<li><span class=\"atnm\">thumbtext</span> : you can place text around each thumbnail. Like <code>imagecaption</code>, give any fixed text and/or <a href=\"#image_fields\">image field codes</a> to add it below every thumbnail (inside tag <code>thumbtextwrap</code> with a class of <code>thumbtextclass</code>). By default, text is placed <code>below</code> the image but can be placed in any of 5 positions, as given in the table below. <code>onchange</code> is very useful for applying headers when the category changes. See <a href=\"#eg6\">example 6</a>. Can take the following options:\n	<ul>\n		<li><code>above</code> : above the thumbnail, not clickable</li>\n		<li><code>below</code> : below the thumbnail, not clickable (default)</li>\n		<li><code>before</code> : immediately in front of the thumbnail, clickable as part of the link</li>\n		<li><code>after</code> : immediately following the thumbnail, clickable as part of the link</li>\n		<li><code>onchange</code> : above every thumbnail but <em>only</em> if its contents changes from the image before it</li>\n	</ul></li>\n		<li><span class=\"atnm\">thumbtextwrap</span> : (X)HTML tag to wrap around each thumbtext item. Do not specify the <code>&lt;</code> or <code>&gt;</code> characters. Default: span</li>\n		<li><span class=\"atnm\">thumbtextclass</span> : <span class=\"caps\">CSS</span> class to apply to every thumbtext item. Default: empty (i.e. no class)</li>\n	</ul>\n\n	<h4>Tag / class / formatting attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">wraptag</span> : (X)HTML tag to wrap each &#8216;cell&#8217; (effectively a thumbnail image) with. Do not specify the &lt; or &gt; characters. For a valid page, the only useful options are &#8220;div&#8221;, &#8220;li&#8221; or &#8220;p&#8221;. Default: empty (i.e. no tag)</li>\n		<li><span class=\"atnm\">cellclass</span> : <span class=\"caps\">CSS</span> class for each &#8216;cell&#8217; that displays a thumbnail. Default: empty (i.e. no class)</li>\n		<li><span class=\"atnm\">linkclass</span> : <span class=\"caps\">CSS</span> class for the anchor tag of each image. Default: empty (i.e. no class)</li>\n		<li><span class=\"atnm\">imgclass</span> : <span class=\"caps\">CSS</span> class for each img tag. Default: empty (i.e. no class)</li>\n		<li><span class=\"atnm\">navwraptag</span> : when <code>showpagelinks</code> is on, some next/prev links are placed under each gallery to page through <code>limit</code> thumbnails at a time. This attribute specifies the (X)HTML tag to wrap around the whole navigation block. Do not specify the &lt; or &gt; characters. Default: empty (i.e. no tag)</li>\n		<li><span class=\"atnm\">navclass</span> : <span class=\"caps\">CSS</span> class for the next/prev container. Default: empty (i.e. no class)</li>\n		<li><span class=\"atnm\">prevlabel</span> : Label for the &#8216;previous page&#8217; of pictures when using <code>showpagelinks</code>. Default: &#8220;Prev&#8221;. Customisable in different languages via <span class=\"caps\">MLP</span></li>\n		<li><span class=\"atnm\">nextlabel</span> : Label for the &#8216;next page&#8217; of pictures when using <code>showpagelinks</code>. Default: &#8220;Next&#8221;. Customisable in different languages via <span class=\"caps\">MLP</span></li>\n		<li><span class=\"atnm\">prevclass</span> : <span class=\"caps\">CSS</span> class for the &#8216;prev&#8217; link. Default: smd_slimbox_prev</li>\n		<li><span class=\"atnm\">nextclass</span> : <span class=\"caps\">CSS</span> class for the &#8216;next&#8217; link. Default: smd_slimbox_next</li>\n	</ul>\n\n	<h4>Plugin customisation</h4>\n\n	<ul>\n		<li><span class=\"atnm\">delim</span> : The delimiter to use between items in lists (<code>category</code>, <code>imageid</code>, <code>thumbtext</code> etc). If using glz_custom_fields &#8212; especially with checkboxes or a multi select &#8212; you should change <code>delim</code> to a pipe &#8216;|&#8217; character</li>\n		<li><span class=\"atnm\">paramdelim</span> : by default in the <code>thumbtext</code> attribute, you separate parameters from option names using the colon. In some case you may wish to use the colon in your string of text so you will need to set the delimiter to something else. It can be pretty much any string you like (e.g. <code>paramdelim=&quot;[[SQUID&quot;</code>), just make sure it doesn&#8217;t appear anywhere else in your text</li>\n		<li><span class=\"atnm\">galleryid</span> : if using <code>showpagelinks</code>, this is the string that is used to differentiate between multiple slimbox galleries on a single page. It allows paging to be controlled on a per-gallery basis, so clicking Next/Prev in one gallery won&#8217;t affect the thumbnails in other galleries on the same page. Each gallery on a page <em>must</em> have a unique <code>galleryid</code> in order for paging to work. The good news is you&#8217;ll probably never have to use this attribute because it works automatically for you. In the (very) unlikely event it generates two identical strings, you can use this option to override the default behaviour.<div class=\"break\">It takes either a fixed string (e.g. <code>galleryid=&quot;myGallery1&quot;</code>); or a &#8216;?&#8217; followed by up to two numbers separated by a colon (the colon can be changed with <code>paramdelim</code>). See <a href=\"#galleryid\">using galleryid</a> for more</div></li>\n		<li><span class=\"atnm\">thumb</span> : under normal operation, thumbnails use the &#8216;t&#8217; image in your <code>images</code> directory and the full size pictures don&#8217;t. If you wish to redefine this you may specify up to two values separated by a colon. The first indicates the state of the full size image, and the second is the state of the thumbnail:\n	<ul>\n		<li>thumb=0:1 &#8212; fullsize = regular image : thumbnail = thumb (the default)</li>\n		<li>thumb=1:0 &#8212; fullsize = thumb : thumbnail = regular image</li>\n		<li>thumb=0:0 (or thumb=0) &#8212; fullsize = regular image : thumbnail = regular image</li>\n		<li>thumb=1:1 (or thumb=1) &#8212; fullsize = thumb : thumbnail = thumb</li>\n	</ul></li>\n	</ul>\n\n	<h3 id=\"fields\">Article field codes</h3>\n\n	<p>Use these codes to grab items from the currently displayed article and inject them into smd_slimbox tag attributes. All of the tags can be combined with fixed text (e.g. <code>category=&quot;mycat-?article_image_?s&quot;</code> might choose images from the category &#8220;mycat-12_lion&#8221; if the current article was in section &#8216;lion&#8217; and had an article image of &#8216;12&#8217;). Note that any article or custom fields &#8212; including those utilising glz_custom_fields &#8212; may contain lists of items.</p>\n\n	<ul>\n		<li><code>?c</code> : use the current global (article) category</li>\n		<li><code>!c</code> : uses everything except the current global category</li>\n		<li><code>?s</code> : use the current <span class=\"caps\">TXP</span> section</li>\n		<li><code>!s</code> : uses everything except the current section</li>\n		<li><code>?t</code> : use the current article&#8217;s url-title</li>\n		<li><code>!t</code> : uses all url-titles except the current article (probably not much use!)</li>\n		<li><code>?id</code> : use the current article&#8217;s ID as the name of an image category. Useful to make a per-article image gallery (e.g. <code>category=&quot;article-?id&quot;</code>)</li>\n		<li><code>!id</code> : uses all image IDs except the current article</li>\n		<li><code>?fieldname</code> : looks in the given fieldname of the current article. Designed primarily for use with custom fields (e.g. <code>imageid=&quot;?custom1&quot;</code>), it can also be any other field from your article. Common fields are <code>?excerpt</code> @?category1@ <code>?keywords</code> @?article_image@ or <code>?authorid</code>.</li>\n	</ul>\n\n	<p>To negate any category selection, use <code>!category</code> or <code>!field_name</code>. If you list specific image categories (e.g. <code>category=&quot;!landscape, !birds&quot;</code>) those categories will be excluded. If you specify a field name such as <code>!keywords</code>, the contents of that field will be read and the list of image categories found there will be excluded.</p>\n\n	<p>Note that if you specify a field name and that field in the article is empty, the name will be treated as if it was an actual image category. So if you specified <code>category=&quot;?keywords&quot;</code> and the &#8216;keywords&#8217; field in the current article was empty, it would look for an image category named &#8220;keywords&#8221;.</p>\n\n	<p>Also note that, regardless of the order you list them, all &#8216;positives&#8217; will be processed first, then any exclusions applied.</p>\n\n	<h3 id=\"image_fields\">Image field codes</h3>\n\n	<p>Like <a href=\"#fields\">article field codes</a>, these can be specified to grab pieces of information from the current image. Note that the &#8216;negation&#8217; versions don&#8217;t make sense here so the codes are &#8216;include only&#8217;:</p>\n\n	<ul>\n		<li><code>?category</code> : image category</li>\n		<li><code>?caption</code> : image caption</li>\n		<li><code>?alt</code> : image alt text</li>\n		<li><code>?name</code> : image (usually file) name</li>\n		<li><code>?id</code> : image ID</li>\n		<li><code>?date</code> : image upload datestamp</li>\n		<li><code>?author</code> : image author (who uploaded it to <span class=\"caps\">TXP</span>)</li>\n		<li><code>?ext</code> : image file extension</li>\n		<li><code>?width</code> : image width in pixels</li>\n		<li><code>?height</code> : image height in pixels</li>\n		<li><code>?url</code> : the <span class=\"caps\">URL</span> of the full-size image &#8212; can only be used with <code>imagecaption</code> or <code>thumbtext</code></li>\n	</ul>\n\n	<p>For example, to show the alt text under the main image instead of the caption (which it otherwise uses by default) :</p>\n\n<pre class=\"block\"><code class=\"block\">imagecaption=&quot;?alt&quot;\n</code></pre>\n\n	<p>For the adventurous:</p>\n\n<pre class=\"block\"><code class=\"block\">imagecaption=&quot;?caption by ?author\n     [?ext picture: ?widthx?heightpx]&quot;\n</code></pre>\n\n	<p>That might show a caption such as &#8220;Lake Windermere at night by Stef [jpg picture: 800&#215;600px]&#8221;.</p>\n\n	<p>To put (above each thumbnail) the text &#8220;Filed in&#8221; and then the category name of the image, followed by its filename in square brackets:</p>\n\n<pre class=\"block\"><code class=\"block\">thumbtext=&quot;above: Filed in ?category [?name]&quot;&gt;\n</code></pre>\n\n	<p>You can combine definitions, each separated by a comma, for example:</p>\n\n<pre class=\"block\"><code class=\"block\">thumbtext=&quot;above:Filed in ?category [?name (?width x ?height)],\n     below:?author&quot;\n</code></pre>\n\n	<p>That would add the category, filename, width and height above the image and the author (the person who uploaded it) below the thumbnail. You can get as creative as you like.</p>\n\n	<p>Note that if you want to use a colon in the text itself, you will need to override the default and set it to any string of characters you prefer using the <code>paramdelim</code> option.</p>\n\n	<h3 id=\"galleryid\">Using <code>galleryid</code></h3>\n\n	<p>Internally, the plugin generates a unique (32-character) string based on the other attributes given. You can use two numbers in galleryid to control which characters from this string to use in your unique code. The first number is the length of the string to generate and the 2nd number is how many characters to skip each time it picks one from the string (wrapping back to the start of the string if it falls off the end).</p>\n\n	<p>An example: suppose the tag generated this string internally: <code>2cb5bbf8b0c2c028d8f234b4bd46fdde</code>. These are the results of various values:</p>\n\n	<table>\n		<tr>\n			<th>galleryid= </th>\n			<th>result </th>\n			<th>remarks </th>\n		</tr>\n		<tr>\n			<td> <code>&quot;&quot;</code> or <code>&quot;?4:6&quot;</code> </td>\n			<td> 2fcf </td>\n			<td> the default </td>\n		</tr>\n		<tr>\n			<td> <code>&quot;?8&quot;</code> </td>\n			<td> 2cb5bbf8 </td>\n			<td> the first 8 characters </td>\n		</tr>\n		<tr>\n			<td> <code>&quot;?7:3&quot;</code> </td>\n			<td> 25f0c8f </td>\n			<td> length 7, choosing every 3rd character </td>\n		</tr>\n		<tr>\n			<td> <code>&quot;?8:7&quot;</code> </td>\n			<td> 2824f5c8 </td>\n			<td> length 8, choosing every 7th character (wraps) </td>\n		</tr>\n	</table>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<h3 id=\"eg1\">Example 1: simple slimbox gallery</h3>\n\n	<p>Show all images in the landscape category, ten at a time:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox category=&quot;landscape&quot; limit=&quot;10&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"eg2\">Example 2: adding captions</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox category=&quot;dogs, birds&quot; limit=&quot;9&quot;\n     thumbtext=&quot;after:?caption&quot; wraptag=&quot;li&quot;\n     cellclass=&quot;cell&quot; /&gt;\n</code></pre>\n\n	<p>Show all images in the dogs and birds categories, 9 per page. Add the captions under the thumbnails and make them clickable too. Wrap each image in a <code>li</code> tag and give each a class of &#8216;cell&#8217; (implies you must manually add a corresponding <code>&lt;ul&gt;</code> tag around the plugin in order to maintain page validity).</p>\n\n	<p>If you added <code>sort=&quot;date desc, random&quot;</code> to the above tag, you would get the most recent 9 images on the first page, but shown in a random order. The next page would show the next most-recent 9 images, again sorted randomly within themselves.</p>\n\n	<p>However, switching the attributes to: <code>sort=&quot;random, date desc&quot;</code> would change the output. You would then get 9 random images on the first page, with the most recent ones of the group shown first. Clicking &#8216;Next&#8217; would show another 9 random images (some may be the same as page 1), again sorted in descending order of date. For this reason (unless you&#8217;re simply using the plugin to pretty up a page with random images) if you&#8217;re using random as the first argument to <code>sort</code>, you probably want to disable paging by setting <code>showpagelinks=&quot;0&quot;</code>.</p>\n\n	<h3 id=\"eg3\">Example 3: using <code>imageid</code></h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox imageid=&quot;48, 25, 4&quot;\n     category=&quot;dogs, ?custom1&quot; subcats=&quot;all&quot; limit=&quot;20&quot;\n     prevlabel=&quot;Back&quot; sort=&quot;name desc, category&quot; /&gt;\n</code></pre>\n\n	<p>Show images with IDs 48, 25 and 4 as well as all images in the dogs category and also pull the image category names from the current article&#8217;s custom1 field (if custom1 held <code>birds, landscapes</code> you&#8217;d get pictures from all three categories and all their sub-categories). The tag also limits the number of thumbs per page to 20, changes the previous page label to &#8220;Back&#8221; and sorts the images in descending order of name, then category.</p>\n\n	<h3 id=\"eg4\">Example 4: image negation</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox category=&quot;static, cat-?id&quot;\n     imageid=&quot;!article_image&quot; limit=&quot;6&quot; groupname=&quot;?c&quot;\n     showpagelinks=&quot;0&quot; /&gt;\n</code></pre>\n\n	<p>If the visitor is currently viewing article ID 67, show all images from the &#8216;static&#8217; category and the &#8216;cat-67&#8217; category. If any image in either of those categories is found to have an ID of the article image from the current article, that picture will be ignored. Only show 6 pictures and don&#8217;t show the next/prev links on the page.</p>\n\n	<p>Also, group the lightbox pictures by image category. This means that if someone clicks on a picture from the &#8220;static&#8221; category, the Slimbox next/prev links only navigate between those pictures. The user will have to close the lightbox and click a picture in the cat-67 category to show pictures from the other group.</p>\n\n	<p>On the surface the groupname option may seem a little superfluous but it can be useful to allow people to only flick through a subset of the pictures on each page, based on their category, file name, author, width/height, alt/caption, even ID if you want them to all be unique entities. You can also use a fixed value read from a field in the current article, e.g. <code>groupname=&quot;?custom2&quot;</code>. Useful when used in conjunction with <code>sort</code>.</p>\n\n	<h3 id=\"eg5\">Example 5: combining field variables</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox category=&quot;?c, ?custom1, !custom2, !static&quot;\n     limit=&quot;8&quot; maxlimit=&quot;32&quot; /&gt;\n</code></pre>\n\n	<p>A bit of a contrived example that shows how crazy you can get: This will show a maximum of 32 images from an image category with the same name as the current global category, plus all images from categories listed in the custom1 field of the current article but <em>excluding</em> images from categories listed in custom2 and also excluding the &#8216;static&#8217; category. Show 8 image thumbnails at a time (i.e. 4 pages).</p>\n\n	<h3 id=\"eg6\">Example 6: using <code>onchange</code></h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_slimbox category=&quot;?Photo country&quot; subcats=&quot;all&quot;\n     thumbtext=&quot;onchange: &lt;h4&gt;?category&lt;/h4&gt;&quot;\n     groupname=&quot;?c&quot; thumbtextwrap=&quot;&quot; /&gt;\n</code></pre>\n\n	<p>A work-saving tag! Pulls out all pictures from the category given in the custom field labelled &#8216;Photo country&#8217;, including any sub-categories. By default it sorts them by category, which is perfect. The clever bit is <code>thumbtext</code>, which tells the plugin to display the category name above the images but <em>only</em> when the category changes. The result is category headings (surrounded by h4 tags, without the default <code>thumbtextwrap</code> span) above each group of images. Note that if you want to include simple <span class=\"caps\">HTML</span> tags you can, but they must be encoded with their character code equivalents or <span class=\"caps\">TXP</span> gets mad.</p>\n\n	<p>The <code>groupname</code> option tells slimbox to only permit stepping through each image in each category. You cannot &#8216;fall off&#8217; the end of one category into the next when using the Next/Prev links in the lightbox.</p>\n\n	<p>If you wanted to show category headings that grouped pictures by author as well you could use:</p>\n\n<pre class=\"block\"><code class=\"block\">sort=&quot;category, author&quot;\n     thumbtext=&quot;onchange: &amp;amp;amp;lt;h4&amp;amp;amp;gt;?category\n     (by ?author)&amp;amp;amp;lt;/h4&amp;amp;amp;gt;&quot;\n</code></pre>\n\n	<p>Add a little bit of <span class=\"caps\">CSS</span> and your single slimbox tag can look like it&#8217;s creating multiple galleries, all automatically.</p>\n\n	<h2 id=\"style\">Styling with <span class=\"caps\">CSS</span></h2>\n\n	<p>There are (deliberately) very few layout attributes for the plugin because pretty much everything can be accessed from <span class=\"caps\">CSS</span> to lay your images out however you like them. Want to change the number of images per row? Change the width of the containing &lt;div&gt; and set the width of each thumbnail space to a particular size.</p>\n\n	<p>The following logical structure applies:</p>\n\n	<ul>\n		<li>An optional wraptag and cellclass around each &#8220;cell&#8221; (i.e. linked thumbnail)</li>\n		<li>A link element containing the thumbnail&#8217;s <code>&lt;img&gt;</code> tag with an optional imgclass</li>\n		<li>The thumbtext is a <span> with an optional thumbtextclass and can appear above, below or within the link element</li>\n		<li>The next/prev links are wrapped in the optional navwraptag container and can be given an optional navclass</li>\n		<li>The next link has a (default) class of &#8220;smd_slimbox_next&#8221; and prev has &#8220;smd_slimbox_prev&#8221;. These can be changed or removed if you wish</li>\n	</ul>\n\n	<p>Using <span class=\"caps\">CSS</span> selectors you can access every part of the structure. An example <span class=\"caps\">CSS</span> layout is given here to get you started (assumes a wraptag of &#8216;li&#8217;, a containing &#8216;ul&#8217; around the plugin tag with a class of <code>smd_slimbox</code> and <code>navclass=&quot;smd_slimbox_nav&quot;</code>). Paste it into your <span class=\"caps\">CSS</span> template and you&#8217;re good to go.</p>\n\n<pre class=\"block\"><code class=\"block\">.smd_slimbox {\n	float:left;\n	width:600px;\n	margin:1em 0;\n	clear:both;\n	list-style:none;\n}\n.smd_slimbox li {\n	width:150px;\n	height:150px;\n	float:left;\n	margin:1em auto;\n	text-align:center;\n}\n.smd_slimbox img {\n	margin:0 auto;\n}\n.smd_slimbox span {\n	float:left;\n	width:140px;\n	padding:2px 0 0 5px;\n}\n.smd_slimbox_nav {\n	clear:both;\n	width:500px;\n	padding:20px;\n}\n.smd_slimbox_prev {\n	float:left;\n}\n.smd_slimbox_next {\n	float:right;\n}\n</code></pre>\n\n	<h2 id=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>19 Jan 07 | 0.10 | Initial release</li>\n		<li>20 Jan 07 | 0.11 | Added !category (negation) support. Thanks Vitruvius</li>\n		<li>02 Feb 07 | 0.12 | Added imageid, with negation support (thanks wouaren) and textpos option (thanks lee)</li>\n		<li>06 Feb 07 | 0.20 | js and css can now be loaded elsewhere (thanks jstubbs). Added wraptag and changed defaults to make output less like tag-soup (thanks wouaren)</li>\n		<li>13 Feb 07 | 0.21 | Added orderby=&#8216;fixed&#8217; (thanks lee) and minor warnfix (thanks benjibot)</li>\n		<li>04 Mar 07 | 0.22 | Added <span class=\"caps\">MLP</span> support, subcategory support and extended the category/imageid options. Now requires <a href=\"http://stefdawson.com/downloads/smd_lib_v0.2.txt\">smd_lib</a> (included in the zip file for convenience but available separately as well)</li>\n		<li>04 Mar 07 | 0.23 | Fixed orderby when using random  (thanks mrdale) and added ability to override maxlimit</li>\n		<li>19 Mar 07 | 0.24 | Allowed smd_slimbox_inc to be ignored via <code>libcheck=&quot;0&quot;</code> (thanks papuass)</li>\n		<li>30 Jun 07 | 0.24b | Fixed paging in sub-directory installations (thanks ultramega and Qwest)</li>\n		<li>28 Jul 07 | 0.25 | Supercharged the groupname option (thanks [Axel])</li>\n		<li>31 Jul 07 | 0.25a | Maintenance upgrade only. No features, just bundled the latest slimbox/mootools library and fixed the help file</li>\n		<li>04 Aug 07 | 0.26 | Fixed paging to allow multiple slimbox tags per page to co-exist (thanks wheaticus)</li>\n		<li>22 Aug 07 | 0.27 | Added thumbnails-as-main-image option (thanks progre55)</li>\n		<li>30 Aug 07 | 0.28 | Added customisable captions under the main image (thanks Adam_V) and consolidated orderby (internal code)</li>\n		<li>14 Sep 07 | 0.29 | Added the thumbsize option to set thumbnail dimensions (thanks Simanek)</li>\n		<li>20 Sep 07 | 0.29a | Fixed undeclared index warnings (thanks Ambitiouslemon)</li>\n		<li>20 Oct 07 | 0.29b | Fixed the fix that broke in v0.24 (thanks Mr.Smith) and the &#8216;px&#8217; thumbsize issue (thanks robin746)</li>\n		<li>03 Nov 07 | 0.210 | Uses smd_lib v0.3 so integer ranges are now only allowed in imageid. Added thumbtext/thumbtextclass/paramdelim. Deprecated showalt/showcaption/altclass/captionclass/textpos</li>\n		<li>28 Nov 07 | 0.211 | Uses smd_lib v0.31. Fixed some warnings and added onchange heading support (thanks karl.spencer, decoderltd)</li>\n		<li>20 Jan 08 | 0.212 | Added thumbtextwrap (thanks jodeci) and <code>?url</code> (thanks markusfalk) ; fixed whitespace around &#8216;?&#8217; codes</li>\n		<li>01 Apr 08 | 0.30 | Uses <a href=\"http://stefdawson.com/smd_lib\">smd_lib</a>. Backported features and refactorisation from smd_gallery ; added trigger, delim, navwraptag and linkclass (thanks marios) ; changed subcats and thumb (thanks fangonk) ; deprecated smd_slimbox_inc ; removed catlabel, libcheck and the deprecated atts from v0.210 ; renamed orderby as sort ; Moonbox, Lightbox, Litebox, Imagebox and Thickbox (jQuery) now supported ; also works with glz_custom_fields (thanks progre55 and <a href=\"http://gerhardlazu.com/blog/51/glz_custom_fields-11-released\">gerhard</a>)</li>\n		<li>09 Jun 08 | 0.31 | Fixed sort when using limit and saved a query (thanks robin746)</li>\n		<li>19 Dec 08 | 0.32 | Fixed <span class=\"caps\">MLP</span> string bug</li>\n		<li>22 Feb 09 | 0.33 | Allowed rel removal (thanks <span class=\"caps\">OMGC</span>hris)</li>\n		<li>09 Feb 11 | 0.34 | Fixed <span class=\"caps\">PHP</span> 5.3+ compatibilty (ereg_replace) and <code>thumb</code> attribute (both thanks akurei77)</li>\n	</ul>\n\n	<h2 id=\"credits\">Credits</h2>\n\n	<p>This plugin originally plagiarised ideas from Rob Sable&#8217;s rss_thumbpop and has been built up from there with suggestions from various <span class=\"caps\">TXP</span>ers in the community. It also wouldn&#8217;t exist without the hard work of Christophe Beyls&#8217; original implementation of Slimbox. Many thanks to all who have helped craft this plugin, and of course to the awesome Textpattern.</p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 17px Georgia, sans-serif; }\n#smd_help h3 { font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em 0; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '// DEPRECATED\r\nfunction smd_slimbox_inc($atts) {\r\n	global $production_status;\r\n	if (in_array($production_status, array(\'debug\', \'testing\'))) {\r\n		trigger_error(\"smd_slimbox_inc is no longer supported. Please remove it and include the relevant javascript libraries manually on your page.\");\r\n	}\r\n	return;\r\n}\r\nrequire_plugin(\'smd_lib\');\r\n// MLP support\r\nglobal $smd_sboxLang;\r\n$smd_sboxStr = array(\r\n	\'next_lbl\' => \'Next\',\r\n	\'prev_lbl\' => \'Prev\',\r\n);\r\n$smd_sboxLang = new smd_MLP(\'smd_slimbox\', \'smd_sbox\', $smd_sboxStr);\r\n\r\nfunction smd_slimbox($atts, $thing=\'\') {\r\n	global $production_status, $pretext, $thisarticle, $img_dir, $smd_sboxLang;\r\n\r\n	// ** LOOKUP TABLES\r\n	// Holds database field as key index and plugin option names as value.\r\n	// Use array_search to find if value exists and return correct key for use in db calls.\r\n	$fieldMap = array(\r\n		\"name\" => \"name\",\r\n		\"id\" => \"id\",\r\n		\"alt\" => \"alt\",\r\n		\"caption\" => \"caption\",\r\n		\"category\" => \"category\",\r\n		\"author\" => \"author\",\r\n		\"date\" => \"date\",\r\n		\"ext\" => \"ext\",\r\n		\"w\" => \"width\",\r\n		\"h\" => \"height\",\r\n	);\r\n	$showLocs = array(\"below\",\"above\",\"after\",\"before\",\"onchange\");\r\n//	$classTypes = array_merge(array(\"group\",\"cell\",\"link\",\"img\",\"thumbtext\",\"nav\",\"prev\",\"next\"),$showLocs);\r\n//	$operands = array(\"changes\", \"contains\", \"eq\", \"lt\", \"gt\", \"lte\", \"gte\", \"not\");\r\n	$thumbholder = \"::SBOX_REPL::\";\r\n\r\n	$attemptMatch = (isset($atts[\'category\']) || isset($atts[\'imageid\'])) ? true : false;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'delim\'				=> \',\',\r\n		\'paramdelim\'		=> \':\',\r\n		\'imageid\'			=> \'\',\r\n		\'category\'			=> \'\',\r\n		\'subcats\'			=> \'0\',\r\n		\'trigger\'			=> \'lightbox-\',\r\n		\'groupname\'			=> \'smd\',\r\n		\'thumb\'				=> \'0:1\',\r\n		\'limit\'				=> \'0\',\r\n		\'maxlimit\'			=> \'99999\',\r\n		\'sort\'				=> \'category asc\', // was orderby\r\n		\'orderby\'			=> \'\', // DEPRECATED\r\n		\'showpagelinks\'	=> \'1\',\r\n		\'galleryid\'			=> \'?4:6\',\r\n		\'imagecaption\'		=> \'?caption\',\r\n		\'thumbsize\'			=> \'\',\r\n		\'thumbtext\'			=> \'\',\r\n		\'thumbtextwrap\'	=> \'span\',\r\n		\'thumbtextclass\'	=> \'\',\r\n		\'wraptag\'			=> \'\',\r\n		\'navwraptag\'		=> \'\',\r\n		\'prevlabel\'			=> $smd_sboxLang->gTxt(\'prev_lbl\'),\r\n		\'nextlabel\'			=> $smd_sboxLang->gTxt(\'next_lbl\'),\r\n		\'prevclass\'			=> \'smd_slimbox_prev\',\r\n		\'nextclass\'			=> \'smd_slimbox_next\',\r\n		\'navclass\'			=> \'\',\r\n		\'cellclass\'			=> \'\',\r\n		\'linkclass\'			=> \'\',\r\n		\'imgclass\'			=> \'\',\r\n		\'debug\'			=> \'0\',\r\n	), $atts));\r\n\r\n	// DEPRECATED: Convert orderby to the new sort. Remove in later versions\r\n	if ($orderby) {\r\n		$sort = str_replace (\":\", \" \", $orderby);\r\n		if (in_array($production_status, array(\'debug\', \'testing\'))) {\r\n			trigger_error(\"\'orderby\' is now renamed \'sort\'. Use a space instead of a colon to specify sort order (e.g. orderby=\'category:asc\' is now sort=\'category asc\').\");\r\n		}\r\n	}\r\n	// Category relies on whether imageid has been specified or not.\r\n	// lAtts() won\'t let us handle this logic so it has to be done manually\r\n	$category = isset($category) ? doSlash($category) : (($imageid==\"\") ? $pretext[\'c\']: \'\');\r\n\r\n	// The gallery identifier is used for keeping track of next/prev thumbpage links.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$gallID = \"\";\r\n	if ($galleryid == \"\" || substr($galleryid,0,1) == \"?\") {\r\n		// Generate a unique ID for this gallery instance\r\n		$gallTmp = md5($category.$imageid.$orderby.$limit.$maxlimit.$wraptag);\r\n		list($gallLen, $gallSkip) = explode(\":\", substr($galleryid,1));\r\n		$gallLen =  (empty($gallLen)) ? \"4\" : $gallLen;\r\n		$gallSkip =  (empty($gallSkip)) ? \"1\" : $gallSkip;\r\n		for ($idx = 0, $cnt = 0; $cnt < $gallLen; $cnt++, $idx = (($idx+$gallSkip) % strlen($gallTmp))) {\r\n			$gallID .= $gallTmp[$idx];\r\n		}\r\n	} else {\r\n		$gallID = $galleryid;\r\n	}\r\n\r\n	// Set up the variables from the thumb option.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$thumbOpts = do_list($thumb, \":\");\r\n	if (count($thumbOpts) == 1) {\r\n		$fullmod = $thumbmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n	} else {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = ($thumbOpts[1] == 1) ? \"t\" : \"\";\r\n	}\r\n\r\n	// Extract the given category/imageid lists and make up the beginnings of a WHERE clause\r\n	$clause[] = \'1=1\';\r\n	$clauseOR = array();\r\n	$subinfo = ($subcats) ? \"image:\".$subcats : \'\';\r\n	list($fullIDList, $notIDList) = smd_doList($imageid, true, \'\', true, $delim);\r\n	list($fullCatList, $notCatList) = smd_doList($category, false, $subinfo, true, $delim);\r\n	if ($fullIDList) {\r\n		$clauseOR[] = \'txp_image.id IN (\'.implode(\",\", $fullIDList).\')\';\r\n	}\r\n	if ($fullCatList) {\r\n		$clauseOR[] = \'txp_image.category IN (\'.implode(\",\", $fullCatList).\')\';\r\n	}\r\n	if (!empty($clauseOR)) {\r\n		$clause[] = implode(\" OR \", $clauseOR);\r\n	}\r\n\r\n	$clause = implode(\" AND \", $clause);\r\n	$clause .= ($notCatList) ? \' AND txp_image.category NOT IN (\'.implode(\",\", $notCatList).\')\' : \'\';\r\n	$clause .= ($notIDList) ? \' AND txp_image.id NOT IN (\'.implode(\",\", $notIDList).\')\' : \'\';\r\n\r\n	// If a category/imageid option was chosen and they matched nothing, use a bogus category that should not exist.\r\n	// This ensures that the plugin does the expected thing of matching nothing in the event it finds nothing.\r\n	if ($clause == \"1=1\" && $attemptMatch) {\r\n  		$clause = \'txp_image.category = \"smd_\' .mt_rand(). \'\"\';\r\n	}\r\n\r\n	// Convert the sort into a valid sorting hierarchy.\r\n	// Note that random is a special case and the following rules apply:\r\n	//  1) Anything appearing before random is passed to the database query for ordering\r\n	//  2) As soon as random is encountered, the rest of the sort string is ignored.\r\n	//     The resultset will be sorted randomly instead\r\n	//  3) If random appears first in the list, the data is retrieved randomly then the\r\n	//     remaining options sort the resultset\r\n	$clauseSort = array();\r\n	$postOrder = array();\r\n	$rule = \"sql\";\r\n	$postPrefix = \"SORT_\";\r\n	$fixedOrder = $randsort = false;\r\n	$sort = do_list($sort, $delim);\r\n	for ($idx = 0; $idx < count($sort); $idx++) {\r\n		$sort_dir = explode(\' \', $sort[$idx]);\r\n		$direction = $colRef = \'\';\r\n		if (count($sort_dir) <= 1) {\r\n			$sort_dir[1] = \"asc\";\r\n		}\r\n		if ($rule == \"sql\") {\r\n			$direction = ($sort_dir[1] == \"desc\") ? \' desc\' : \' asc\';\r\n		} else {\r\n			$direction = ($sort_dir[1] == \"desc\") ? $postPrefix.\'DESC\' : $postPrefix.\'ASC\';\r\n		}\r\n\r\n		switch ($sort_dir[0]) {\r\n			case \'random\':\r\n				$rule = \"post\";\r\n				if ($idx == 0) {\r\n					$clauseSort[] = \'rand()\';\r\n				} else {\r\n					$randsort = true;\r\n				}\r\n				break;\r\n			case \'fixed\':\r\n				// \'fixed\' only works on ids\r\n				$fixedOrder = ($imageid == \'\') ? false : true;\r\n				break;\r\n			default:\r\n				$colRef = array_search($sort_dir[0], $fieldMap);\r\n				break;\r\n		}\r\n		if ($rule == \"sql\") {\r\n			if ($colRef != \'\') {\r\n				$clauseSort[] = $colRef.$direction;\r\n			}\r\n		} else {\r\n			if (!$randsort) {\r\n				if ($colRef != \'\') {\r\n					$postOrder[] = array(\"col\" => $colRef, \"sort\" => $direction);\r\n				}\r\n			}\r\n		}\r\n	}\r\n\r\n	if (count($clauseSort) > 0) {\r\n		$clause .= \' ORDER BY \' . implode(\",\",$clauseSort);\r\n	}\r\n\r\n	// The database fields required\r\n	$fields = \'id,name,caption,alt,category,date,author,ext,w,h\';\r\n\r\n	// Handle paging\r\n	if (($limit > 0) && $showpagelinks) {\r\n		$rs = safe_rows($fields, \'txp_image\', $clause .\' LIMIT 0,\'.$maxlimit, $debug);\r\n		$numthum = count($rs);\r\n		$numPages = ($numthum > 0) ? ceil($numthum/$limit) : 1;\r\n		$thumbpage = (!gps($gallID)) ? 1 : gps($gallID);\r\n		$offset = ($thumbpage - 1) * $limit;\r\n	} else {\r\n		// No paging required\r\n		$numPages = 1;\r\n		$thumbpage = 1;\r\n		$rs = safe_rows($fields, \'txp_image\', $clause .\' LIMIT 0,\'. (($limit==0) ? $maxlimit : $limit), $debug);\r\n	}\r\n\r\n	// Is post-ordering required? Do it\r\n	if ($randsort) {\r\n		shuffle($rs);\r\n	} else if (count($postOrder) > 0) {\r\n		// Translate the rows into columns that can be sorted\r\n		foreach($rs as $key => $row) {\r\n			$col_id[$key] = $row[\'id\'];\r\n			$col_name[$key] = $row[\'name\'];\r\n			$col_caption[$key] = $row[\'caption\'];\r\n			$col_alt[$key] = $row[\'alt\'];\r\n			$col_category[$key] = $row[\'category\'];\r\n			$col_date[$key] = $row[\'date\'];\r\n			$col_ext[$key] = $row[\'ext\'];\r\n			$col_author[$key] = $row[\'author\'];\r\n			$col_w[$key] = $row[\'w\'];\r\n			$col_h[$key] = $row[\'h\'];\r\n		}\r\n		// Make up an array_multisort arg list and execute it\r\n		for ($idx = 0; $idx < count($postOrder); $idx++) {\r\n			$sortargs[] = \'$col_\'.$postOrder[$idx][\'col\'];\r\n			$sortargs[] = $postOrder[$idx][\'sort\'];\r\n		}\r\n		$sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $rs);\';\r\n		eval($sortit);\r\n	}\r\n\r\n	// Is fixed-order output desired? If so, re-order the $rs\r\n	if ($fixedOrder) {\r\n		$orderedRS = Array();\r\n		$ignoreList = Array();\r\n		$remainingRS = Array();\r\n		// Suck out the fixed items first\r\n		for ($idx = 0; $idx < count($fullIDList); $idx++) {\r\n			foreach($rs as $row) {\r\n				if (\"\'\".$row[\'id\'].\"\'\" == $fullIDList[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n				}\r\n			}\r\n		}\r\n		// Tack on the remaining rows\r\n		foreach($rs as $row) {\r\n			if (!in_array($row[\'id\'], $ignoreList)) {\r\n				$remainingRS[] = $row;\r\n			}\r\n		}\r\n		$rs = array_merge($orderedRS, $remainingRS);\r\n	}\r\n\r\n	if (($limit > 0) && $showpagelinks) {\r\n		$rs = array_slice($rs, $offset, $limit);\r\n	}\r\n\r\n	// Handle thumbsize\r\n	$thumbWidth = $thumbHeight = \'\';\r\n	$thumbWUnits = $thumbHUnits = \'\';\r\n	if ($thumbsize) {\r\n		$thumbSizes = do_list($thumbsize, $delim);\r\n		if (count($thumbSizes) == 1) {\r\n			$thumbWidth = $thumbHeight = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[0]);\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbWUnits = $thumbHUnits = ($units == \'\') ? $thumbWUnits : $units;\r\n		} else {\r\n			$thumbWidth = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbHeight = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[1]);\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbWUnits = ($units == \'\' && $thumbWidth != \'\') ? $thumbWUnits : $units;\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[1]);\r\n			$thumbHUnits = ($units == \'\' && $thumbHeight != \'\') ? $thumbHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Set up the thumbtext positions\r\n	$posArray = array();\r\n	$showopts = do_list($thumbtext, $delim);\r\n	foreach ($showopts as $item) {\r\n		$showpos = do_list($item, $paramdelim);\r\n		if (count($showpos) > 1 && in_array($showpos[0],$showLocs)) {\r\n			$posArray[$showpos[0]] = $showpos[1];\r\n		} else {\r\n			$posArray[$showLocs[0]] = ((count($showpos)>1) ? $showpos[1] : $showpos[0]);\r\n		}\r\n	}\r\n\r\n	// Construct the output\r\n	$fieldRE = \'/\\?(\'.implode(\'|\', $fieldMap).\'|url)/\';\r\n	$thumbtextPlate = (($thumbtextwrap) ? \'<\' . $thumbtextwrap . (($thumbtextclass != \"\") ? \' class=\"\' .$thumbtextclass. \'\"\' : \'\') . \'>\' : \'\') .$thumbholder. (($thumbtextwrap) ? \'</\' . $thumbtextwrap. \'>\' : \'\');\r\n	$outStr = \'\';\r\n\r\n	if ($debug) {\r\n		dmp($rs);\r\n	}\r\n\r\n	if($rs) {\r\n		$thumbtextCurr=\"\";\r\n		foreach($rs as $row) {\r\n			$theThumb = hu.$img_dir.\'/\' . $row[\'id\'] . $thumbmod . $row[\'ext\'];\r\n			$full = hu.$img_dir.\'/\' . $row[\'id\'] . $fullmod .$row[\'ext\'];\r\n			if ($groupname === \"?c\") {\r\n				$grp = $row[\'category\'];\r\n			} else if ($groupname === \"?id\") {\r\n				$grp = $row[\'id\'];\r\n			} else if ($groupname === \"?author\") {\r\n				$grp = $row[\'author\'];\r\n			} else if ($groupname === \"?date\") {\r\n				$grp = $row[\'date\'];\r\n			} else if ($groupname === \"?alt\") {\r\n				$grp = $row[\'alt\'];\r\n			} else if ($groupname === \"?caption\") {\r\n				$grp = $row[\'caption\'];\r\n			} else if ($groupname === \"?ext\") {\r\n				$grp = $row[\'ext\'];\r\n			} else if ($groupname === \"?name\") {\r\n				$grp = $row[\'name\'];\r\n			} else if ($groupname === \"?width\") {\r\n				$grp = $row[\'w\'];\r\n			} else if ($groupname === \"?height\") {\r\n				$grp = $row[\'h\'];\r\n			} else if (substr($groupname,0,1) === \"?\") {\r\n				// Split off the field name from the question mark\r\n				$fieldname = substr($groupname,1);\r\n				if (isset($thisarticle[$fieldname])) {\r\n					$grp = $thisarticle[$fieldname];\r\n				} else {\r\n					$grp = \'\';\r\n				}\r\n			} else {\r\n				$grp = $groupname;\r\n			}\r\n\r\n			// Handle imagecaption\r\n			$captionMsg = $imagecaption;\r\n			$matches = array();\r\n			$ctr = preg_match_all($fieldRE, $captionMsg, $matches, PREG_SET_ORDER);\r\n			for ($idx = 0; $idx < $ctr; $idx++) {\r\n				$fieldname = $matches[$idx][1];\r\n				$key = array_search($fieldname, $fieldMap);\r\n				if ($key) {\r\n					$rep = $row[$key];\r\n				} else if ($fieldname == \"url\") {\r\n					$rep = $full;\r\n				} else {\r\n					$rep = $fieldname;\r\n				}\r\n				$captionMsg = str_replace($matches[$idx][0], $rep, $captionMsg);\r\n			}\r\n\r\n			// Handle thumbtext\r\n			$thumbtextArray = array_combine($showLocs, array(\"\",\"\",\"\",\"\",\"\")); // Initialise every element\r\n			foreach ($posArray as $pos => $text) {\r\n				$matches = array();\r\n				$ctr = preg_match_all($fieldRE, $text, $matches, PREG_SET_ORDER);\r\n				for ($idx = 0; $idx < $ctr; $idx++) {\r\n					$fieldname = $matches[$idx][1];\r\n					$key = array_search($fieldname, $fieldMap);\r\n					if ($key) {\r\n						$rep = $row[$key];\r\n					} else if ($fieldname == \"url\") {\r\n						$rep = $full;\r\n					} else {\r\n						$rep = $fieldname;\r\n					}\r\n					$text = str_replace($matches[$idx][0], $rep, $text);\r\n				}\r\n				$thumbtextArray[$pos] = $text;\r\n			}\r\n\r\n			// Construct the link and put thumbtext in the appropriate positions\r\n			$outStr .= ($thumbtextArray[\"onchange\"] && $thumbtextArray[\"onchange\"] != $thumbtextCurr) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"onchange\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= ($wraptag != \"\") ? \'<\' .$wraptag. (($cellclass != \"\") ? \' class=\"\' .$cellclass. \'\"\' : \'\') . \'>\' : \'\';\r\n			$outStr .= ($thumbtextArray[\"above\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"above\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= \'<a href=\"\'.$full.\'\"\'.(($trigger || $grp) ? \' rel=\"\' .$trigger.$grp. \'\"\' : \'\').\' title=\"\' .$captionMsg. \'\"\' .(($linkclass != \"\") ? \' class=\"\' .$linkclass. \'\"\' : \'\'). \'>\' .(($thumbtextArray[\"before\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"before\"]), $thumbtextPlate) : \'\'). \'<img src=\"\' .$theThumb. \'\" alt=\"\' .$row[\'alt\']. \'\"\' . (($imgclass != \"\") ? \' class=\"\' .$imgclass. \'\"\' : \'\') . (($thumbWidth) ? \' width=\"\' . $thumbWidth.$thumbWUnits. \'\"\' : \'\') . (($thumbHeight) ? \' height=\"\' . $thumbHeight.$thumbHUnits. \'\"\' : \'\') . \' />\' .(($thumbtextArray[\"after\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"after\"]), $thumbtextPlate) : \'\'). \'</a>\'.n;\r\n			$outStr .= ($thumbtextArray[\"below\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"below\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= ($wraptag != \"\") ? \'</\' .$wraptag. \'>\' : \'\';\r\n			$outStr .= n;\r\n			$thumbtextCurr = $thumbtextArray[\"onchange\"];\r\n		}\r\n\r\n		// Add the paging features if required\r\n		if (($limit > 0) && $showpagelinks) {\r\n			$next = ($numPages > 1 && $thumbpage != $numPages);\r\n			$prev = ($numPages > 1 && $thumbpage > 1);\r\n\r\n			// Replace any paging info in the query string\r\n			$prevPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumbpage-1);\r\n			$nextPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumbpage+1);\r\n\r\n			$outStr .= ($navwraptag != \"\") ? \'<\' .$navwraptag. (($navclass != \"\") ? \' class=\"\' .$navclass. \'\"\' : \'\') . \'>\' : \'\';\r\n			$outStr .= ($prev) ? \'<a\' . (($prevclass != \"\") ? \' class=\"\' .$prevclass. \'\"\' : \'\') . \' href=\"\' .$prevPage. \'\">\' .$prevlabel. \'</a>\'.n : \'\';\r\n			$outStr .= ($next) ? \'<a\' . (($nextclass != \"\") ? \' class=\"\' .$nextclass. \'\"\' : \'\') . \' href=\"\' .$nextPage. \'\">\' .$nextlabel. \'</a>\'.n : \'\';\r\n			$outStr .= ($navwraptag != \"\") ? \'</\' .$navwraptag. \'>\' : \'\';\r\n			$outStr .= n;\r\n		}\r\n	}\r\n\r\n	return $outStr;\r\n}', '// DEPRECATED\r\nfunction smd_slimbox_inc($atts) {\r\n	global $production_status;\r\n	if (in_array($production_status, array(\'debug\', \'testing\'))) {\r\n		trigger_error(\"smd_slimbox_inc is no longer supported. Please remove it and include the relevant javascript libraries manually on your page.\");\r\n	}\r\n	return;\r\n}\r\nrequire_plugin(\'smd_lib\');\r\n// MLP support\r\nglobal $smd_sboxLang;\r\n$smd_sboxStr = array(\r\n	\'next_lbl\' => \'Next\',\r\n	\'prev_lbl\' => \'Prev\',\r\n);\r\n$smd_sboxLang = new smd_MLP(\'smd_slimbox\', \'smd_sbox\', $smd_sboxStr);\r\n\r\nfunction smd_slimbox($atts, $thing=\'\') {\r\n	global $production_status, $pretext, $thisarticle, $img_dir, $smd_sboxLang;\r\n\r\n	// ** LOOKUP TABLES\r\n	// Holds database field as key index and plugin option names as value.\r\n	// Use array_search to find if value exists and return correct key for use in db calls.\r\n	$fieldMap = array(\r\n		\"name\" => \"name\",\r\n		\"id\" => \"id\",\r\n		\"alt\" => \"alt\",\r\n		\"caption\" => \"caption\",\r\n		\"category\" => \"category\",\r\n		\"author\" => \"author\",\r\n		\"date\" => \"date\",\r\n		\"ext\" => \"ext\",\r\n		\"w\" => \"width\",\r\n		\"h\" => \"height\",\r\n	);\r\n	$showLocs = array(\"below\",\"above\",\"after\",\"before\",\"onchange\");\r\n//	$classTypes = array_merge(array(\"group\",\"cell\",\"link\",\"img\",\"thumbtext\",\"nav\",\"prev\",\"next\"),$showLocs);\r\n//	$operands = array(\"changes\", \"contains\", \"eq\", \"lt\", \"gt\", \"lte\", \"gte\", \"not\");\r\n	$thumbholder = \"::SBOX_REPL::\";\r\n\r\n	$attemptMatch = (isset($atts[\'category\']) || isset($atts[\'imageid\'])) ? true : false;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'delim\'				=> \',\',\r\n		\'paramdelim\'		=> \':\',\r\n		\'imageid\'			=> \'\',\r\n		\'category\'			=> \'\',\r\n		\'subcats\'			=> \'0\',\r\n		\'trigger\'			=> \'lightbox-\',\r\n		\'groupname\'			=> \'smd\',\r\n		\'thumb\'				=> \'0:1\',\r\n		\'limit\'				=> \'0\',\r\n		\'maxlimit\'			=> \'99999\',\r\n		\'sort\'				=> \'category asc\', // was orderby\r\n		\'orderby\'			=> \'\', // DEPRECATED\r\n		\'showpagelinks\'	=> \'1\',\r\n		\'galleryid\'			=> \'?4:6\',\r\n		\'imagecaption\'		=> \'?caption\',\r\n		\'thumbsize\'			=> \'\',\r\n		\'thumbtext\'			=> \'\',\r\n		\'thumbtextwrap\'	=> \'span\',\r\n		\'thumbtextclass\'	=> \'\',\r\n		\'wraptag\'			=> \'\',\r\n		\'navwraptag\'		=> \'\',\r\n		\'prevlabel\'			=> $smd_sboxLang->gTxt(\'prev_lbl\'),\r\n		\'nextlabel\'			=> $smd_sboxLang->gTxt(\'next_lbl\'),\r\n		\'prevclass\'			=> \'smd_slimbox_prev\',\r\n		\'nextclass\'			=> \'smd_slimbox_next\',\r\n		\'navclass\'			=> \'\',\r\n		\'cellclass\'			=> \'\',\r\n		\'linkclass\'			=> \'\',\r\n		\'imgclass\'			=> \'\',\r\n		\'debug\'			=> \'0\',\r\n	), $atts));\r\n\r\n	// DEPRECATED: Convert orderby to the new sort. Remove in later versions\r\n	if ($orderby) {\r\n		$sort = str_replace (\":\", \" \", $orderby);\r\n		if (in_array($production_status, array(\'debug\', \'testing\'))) {\r\n			trigger_error(\"\'orderby\' is now renamed \'sort\'. Use a space instead of a colon to specify sort order (e.g. orderby=\'category:asc\' is now sort=\'category asc\').\");\r\n		}\r\n	}\r\n	// Category relies on whether imageid has been specified or not.\r\n	// lAtts() won\'t let us handle this logic so it has to be done manually\r\n	$category = isset($category) ? doSlash($category) : (($imageid==\"\") ? $pretext[\'c\']: \'\');\r\n\r\n	// The gallery identifier is used for keeping track of next/prev thumbpage links.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$gallID = \"\";\r\n	if ($galleryid == \"\" || substr($galleryid,0,1) == \"?\") {\r\n		// Generate a unique ID for this gallery instance\r\n		$gallTmp = md5($category.$imageid.$orderby.$limit.$maxlimit.$wraptag);\r\n		list($gallLen, $gallSkip) = explode(\":\", substr($galleryid,1));\r\n		$gallLen =  (empty($gallLen)) ? \"4\" : $gallLen;\r\n		$gallSkip =  (empty($gallSkip)) ? \"1\" : $gallSkip;\r\n		for ($idx = 0, $cnt = 0; $cnt < $gallLen; $cnt++, $idx = (($idx+$gallSkip) % strlen($gallTmp))) {\r\n			$gallID .= $gallTmp[$idx];\r\n		}\r\n	} else {\r\n		$gallID = $galleryid;\r\n	}\r\n\r\n	// Set up the variables from the thumb option.\r\n	// Don\'t use paramdelim here or the default option breaks\r\n	$thumbOpts = do_list($thumb, \":\");\r\n	if (count($thumbOpts) == 1) {\r\n		$fullmod = $thumbmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n	} else {\r\n		$fullmod = ($thumbOpts[0] == 1) ? \"t\" : \"\";\r\n		$thumbmod = ($thumbOpts[1] == 1) ? \"t\" : \"\";\r\n	}\r\n\r\n	// Extract the given category/imageid lists and make up the beginnings of a WHERE clause\r\n	$clause[] = \'1=1\';\r\n	$clauseOR = array();\r\n	$subinfo = ($subcats) ? \"image:\".$subcats : \'\';\r\n	list($fullIDList, $notIDList) = smd_doList($imageid, true, \'\', true, $delim);\r\n	list($fullCatList, $notCatList) = smd_doList($category, false, $subinfo, true, $delim);\r\n	if ($fullIDList) {\r\n		$clauseOR[] = \'txp_image.id IN (\'.implode(\",\", $fullIDList).\')\';\r\n	}\r\n	if ($fullCatList) {\r\n		$clauseOR[] = \'txp_image.category IN (\'.implode(\",\", $fullCatList).\')\';\r\n	}\r\n	if (!empty($clauseOR)) {\r\n		$clause[] = implode(\" OR \", $clauseOR);\r\n	}\r\n\r\n	$clause = implode(\" AND \", $clause);\r\n	$clause .= ($notCatList) ? \' AND txp_image.category NOT IN (\'.implode(\",\", $notCatList).\')\' : \'\';\r\n	$clause .= ($notIDList) ? \' AND txp_image.id NOT IN (\'.implode(\",\", $notIDList).\')\' : \'\';\r\n\r\n	// If a category/imageid option was chosen and they matched nothing, use a bogus category that should not exist.\r\n	// This ensures that the plugin does the expected thing of matching nothing in the event it finds nothing.\r\n	if ($clause == \"1=1\" && $attemptMatch) {\r\n  		$clause = \'txp_image.category = \"smd_\' .mt_rand(). \'\"\';\r\n	}\r\n\r\n	// Convert the sort into a valid sorting hierarchy.\r\n	// Note that random is a special case and the following rules apply:\r\n	//  1) Anything appearing before random is passed to the database query for ordering\r\n	//  2) As soon as random is encountered, the rest of the sort string is ignored.\r\n	//     The resultset will be sorted randomly instead\r\n	//  3) If random appears first in the list, the data is retrieved randomly then the\r\n	//     remaining options sort the resultset\r\n	$clauseSort = array();\r\n	$postOrder = array();\r\n	$rule = \"sql\";\r\n	$postPrefix = \"SORT_\";\r\n	$fixedOrder = $randsort = false;\r\n	$sort = do_list($sort, $delim);\r\n	for ($idx = 0; $idx < count($sort); $idx++) {\r\n		$sort_dir = explode(\' \', $sort[$idx]);\r\n		$direction = $colRef = \'\';\r\n		if (count($sort_dir) <= 1) {\r\n			$sort_dir[1] = \"asc\";\r\n		}\r\n		if ($rule == \"sql\") {\r\n			$direction = ($sort_dir[1] == \"desc\") ? \' desc\' : \' asc\';\r\n		} else {\r\n			$direction = ($sort_dir[1] == \"desc\") ? $postPrefix.\'DESC\' : $postPrefix.\'ASC\';\r\n		}\r\n\r\n		switch ($sort_dir[0]) {\r\n			case \'random\':\r\n				$rule = \"post\";\r\n				if ($idx == 0) {\r\n					$clauseSort[] = \'rand()\';\r\n				} else {\r\n					$randsort = true;\r\n				}\r\n				break;\r\n			case \'fixed\':\r\n				// \'fixed\' only works on ids\r\n				$fixedOrder = ($imageid == \'\') ? false : true;\r\n				break;\r\n			default:\r\n				$colRef = array_search($sort_dir[0], $fieldMap);\r\n				break;\r\n		}\r\n		if ($rule == \"sql\") {\r\n			if ($colRef != \'\') {\r\n				$clauseSort[] = $colRef.$direction;\r\n			}\r\n		} else {\r\n			if (!$randsort) {\r\n				if ($colRef != \'\') {\r\n					$postOrder[] = array(\"col\" => $colRef, \"sort\" => $direction);\r\n				}\r\n			}\r\n		}\r\n	}\r\n\r\n	if (count($clauseSort) > 0) {\r\n		$clause .= \' ORDER BY \' . implode(\",\",$clauseSort);\r\n	}\r\n\r\n	// The database fields required\r\n	$fields = \'id,name,caption,alt,category,date,author,ext,w,h\';\r\n\r\n	// Handle paging\r\n	if (($limit > 0) && $showpagelinks) {\r\n		$rs = safe_rows($fields, \'txp_image\', $clause .\' LIMIT 0,\'.$maxlimit, $debug);\r\n		$numthum = count($rs);\r\n		$numPages = ($numthum > 0) ? ceil($numthum/$limit) : 1;\r\n		$thumbpage = (!gps($gallID)) ? 1 : gps($gallID);\r\n		$offset = ($thumbpage - 1) * $limit;\r\n	} else {\r\n		// No paging required\r\n		$numPages = 1;\r\n		$thumbpage = 1;\r\n		$rs = safe_rows($fields, \'txp_image\', $clause .\' LIMIT 0,\'. (($limit==0) ? $maxlimit : $limit), $debug);\r\n	}\r\n\r\n	// Is post-ordering required? Do it\r\n	if ($randsort) {\r\n		shuffle($rs);\r\n	} else if (count($postOrder) > 0) {\r\n		// Translate the rows into columns that can be sorted\r\n		foreach($rs as $key => $row) {\r\n			$col_id[$key] = $row[\'id\'];\r\n			$col_name[$key] = $row[\'name\'];\r\n			$col_caption[$key] = $row[\'caption\'];\r\n			$col_alt[$key] = $row[\'alt\'];\r\n			$col_category[$key] = $row[\'category\'];\r\n			$col_date[$key] = $row[\'date\'];\r\n			$col_ext[$key] = $row[\'ext\'];\r\n			$col_author[$key] = $row[\'author\'];\r\n			$col_w[$key] = $row[\'w\'];\r\n			$col_h[$key] = $row[\'h\'];\r\n		}\r\n		// Make up an array_multisort arg list and execute it\r\n		for ($idx = 0; $idx < count($postOrder); $idx++) {\r\n			$sortargs[] = \'$col_\'.$postOrder[$idx][\'col\'];\r\n			$sortargs[] = $postOrder[$idx][\'sort\'];\r\n		}\r\n		$sortit = \'array_multisort(\'.implode(\", \",$sortargs).\', $rs);\';\r\n		eval($sortit);\r\n	}\r\n\r\n	// Is fixed-order output desired? If so, re-order the $rs\r\n	if ($fixedOrder) {\r\n		$orderedRS = Array();\r\n		$ignoreList = Array();\r\n		$remainingRS = Array();\r\n		// Suck out the fixed items first\r\n		for ($idx = 0; $idx < count($fullIDList); $idx++) {\r\n			foreach($rs as $row) {\r\n				if (\"\'\".$row[\'id\'].\"\'\" == $fullIDList[$idx]) {\r\n					$orderedRS[] = $row;\r\n					$ignoreList[] = $row[\'id\'];\r\n				}\r\n			}\r\n		}\r\n		// Tack on the remaining rows\r\n		foreach($rs as $row) {\r\n			if (!in_array($row[\'id\'], $ignoreList)) {\r\n				$remainingRS[] = $row;\r\n			}\r\n		}\r\n		$rs = array_merge($orderedRS, $remainingRS);\r\n	}\r\n\r\n	if (($limit > 0) && $showpagelinks) {\r\n		$rs = array_slice($rs, $offset, $limit);\r\n	}\r\n\r\n	// Handle thumbsize\r\n	$thumbWidth = $thumbHeight = \'\';\r\n	$thumbWUnits = $thumbHUnits = \'\';\r\n	if ($thumbsize) {\r\n		$thumbSizes = do_list($thumbsize, $delim);\r\n		if (count($thumbSizes) == 1) {\r\n			$thumbWidth = $thumbHeight = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[0]);\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbWUnits = $thumbHUnits = ($units == \'\') ? $thumbWUnits : $units;\r\n		} else {\r\n			$thumbWidth = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbHeight = preg_replace(\"/[^0-9]/\", \'\', $thumbSizes[1]);\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[0]);\r\n			$thumbWUnits = ($units == \'\' && $thumbWidth != \'\') ? $thumbWUnits : $units;\r\n			$units = preg_replace(\"/[0-9]/\", \'\', $thumbSizes[1]);\r\n			$thumbHUnits = ($units == \'\' && $thumbHeight != \'\') ? $thumbHUnits : $units;\r\n		}\r\n	}\r\n\r\n	// Set up the thumbtext positions\r\n	$posArray = array();\r\n	$showopts = do_list($thumbtext, $delim);\r\n	foreach ($showopts as $item) {\r\n		$showpos = do_list($item, $paramdelim);\r\n		if (count($showpos) > 1 && in_array($showpos[0],$showLocs)) {\r\n			$posArray[$showpos[0]] = $showpos[1];\r\n		} else {\r\n			$posArray[$showLocs[0]] = ((count($showpos)>1) ? $showpos[1] : $showpos[0]);\r\n		}\r\n	}\r\n\r\n	// Construct the output\r\n	$fieldRE = \'/\\?(\'.implode(\'|\', $fieldMap).\'|url)/\';\r\n	$thumbtextPlate = (($thumbtextwrap) ? \'<\' . $thumbtextwrap . (($thumbtextclass != \"\") ? \' class=\"\' .$thumbtextclass. \'\"\' : \'\') . \'>\' : \'\') .$thumbholder. (($thumbtextwrap) ? \'</\' . $thumbtextwrap. \'>\' : \'\');\r\n	$outStr = \'\';\r\n\r\n	if ($debug) {\r\n		dmp($rs);\r\n	}\r\n\r\n	if($rs) {\r\n		$thumbtextCurr=\"\";\r\n		foreach($rs as $row) {\r\n			$theThumb = hu.$img_dir.\'/\' . $row[\'id\'] . $thumbmod . $row[\'ext\'];\r\n			$full = hu.$img_dir.\'/\' . $row[\'id\'] . $fullmod .$row[\'ext\'];\r\n			if ($groupname === \"?c\") {\r\n				$grp = $row[\'category\'];\r\n			} else if ($groupname === \"?id\") {\r\n				$grp = $row[\'id\'];\r\n			} else if ($groupname === \"?author\") {\r\n				$grp = $row[\'author\'];\r\n			} else if ($groupname === \"?date\") {\r\n				$grp = $row[\'date\'];\r\n			} else if ($groupname === \"?alt\") {\r\n				$grp = $row[\'alt\'];\r\n			} else if ($groupname === \"?caption\") {\r\n				$grp = $row[\'caption\'];\r\n			} else if ($groupname === \"?ext\") {\r\n				$grp = $row[\'ext\'];\r\n			} else if ($groupname === \"?name\") {\r\n				$grp = $row[\'name\'];\r\n			} else if ($groupname === \"?width\") {\r\n				$grp = $row[\'w\'];\r\n			} else if ($groupname === \"?height\") {\r\n				$grp = $row[\'h\'];\r\n			} else if (substr($groupname,0,1) === \"?\") {\r\n				// Split off the field name from the question mark\r\n				$fieldname = substr($groupname,1);\r\n				if (isset($thisarticle[$fieldname])) {\r\n					$grp = $thisarticle[$fieldname];\r\n				} else {\r\n					$grp = \'\';\r\n				}\r\n			} else {\r\n				$grp = $groupname;\r\n			}\r\n\r\n			// Handle imagecaption\r\n			$captionMsg = $imagecaption;\r\n			$matches = array();\r\n			$ctr = preg_match_all($fieldRE, $captionMsg, $matches, PREG_SET_ORDER);\r\n			for ($idx = 0; $idx < $ctr; $idx++) {\r\n				$fieldname = $matches[$idx][1];\r\n				$key = array_search($fieldname, $fieldMap);\r\n				if ($key) {\r\n					$rep = $row[$key];\r\n				} else if ($fieldname == \"url\") {\r\n					$rep = $full;\r\n				} else {\r\n					$rep = $fieldname;\r\n				}\r\n				$captionMsg = str_replace($matches[$idx][0], $rep, $captionMsg);\r\n			}\r\n\r\n			// Handle thumbtext\r\n			$thumbtextArray = array_combine($showLocs, array(\"\",\"\",\"\",\"\",\"\")); // Initialise every element\r\n			foreach ($posArray as $pos => $text) {\r\n				$matches = array();\r\n				$ctr = preg_match_all($fieldRE, $text, $matches, PREG_SET_ORDER);\r\n				for ($idx = 0; $idx < $ctr; $idx++) {\r\n					$fieldname = $matches[$idx][1];\r\n					$key = array_search($fieldname, $fieldMap);\r\n					if ($key) {\r\n						$rep = $row[$key];\r\n					} else if ($fieldname == \"url\") {\r\n						$rep = $full;\r\n					} else {\r\n						$rep = $fieldname;\r\n					}\r\n					$text = str_replace($matches[$idx][0], $rep, $text);\r\n				}\r\n				$thumbtextArray[$pos] = $text;\r\n			}\r\n\r\n			// Construct the link and put thumbtext in the appropriate positions\r\n			$outStr .= ($thumbtextArray[\"onchange\"] && $thumbtextArray[\"onchange\"] != $thumbtextCurr) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"onchange\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= ($wraptag != \"\") ? \'<\' .$wraptag. (($cellclass != \"\") ? \' class=\"\' .$cellclass. \'\"\' : \'\') . \'>\' : \'\';\r\n			$outStr .= ($thumbtextArray[\"above\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"above\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= \'<a href=\"\'.$full.\'\"\'.(($trigger || $grp) ? \' rel=\"\' .$trigger.$grp. \'\"\' : \'\').\' title=\"\' .$captionMsg. \'\"\' .(($linkclass != \"\") ? \' class=\"\' .$linkclass. \'\"\' : \'\'). \'>\' .(($thumbtextArray[\"before\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"before\"]), $thumbtextPlate) : \'\'). \'<img src=\"\' .$theThumb. \'\" alt=\"\' .$row[\'alt\']. \'\"\' . (($imgclass != \"\") ? \' class=\"\' .$imgclass. \'\"\' : \'\') . (($thumbWidth) ? \' width=\"\' . $thumbWidth.$thumbWUnits. \'\"\' : \'\') . (($thumbHeight) ? \' height=\"\' . $thumbHeight.$thumbHUnits. \'\"\' : \'\') . \' />\' .(($thumbtextArray[\"after\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"after\"]), $thumbtextPlate) : \'\'). \'</a>\'.n;\r\n			$outStr .= ($thumbtextArray[\"below\"]) ? str_replace($thumbholder, htmlspecialchars_decode($thumbtextArray[\"below\"]), $thumbtextPlate) : \'\';\r\n			$outStr .= ($wraptag != \"\") ? \'</\' .$wraptag. \'>\' : \'\';\r\n			$outStr .= n;\r\n			$thumbtextCurr = $thumbtextArray[\"onchange\"];\r\n		}\r\n\r\n		// Add the paging features if required\r\n		if (($limit > 0) && $showpagelinks) {\r\n			$next = ($numPages > 1 && $thumbpage != $numPages);\r\n			$prev = ($numPages > 1 && $thumbpage > 1);\r\n\r\n			// Replace any paging info in the query string\r\n			$prevPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumbpage-1);\r\n			$nextPage = smd_addQSVar($pretext[\'request_uri\'], $gallID, $thumbpage+1);\r\n\r\n			$outStr .= ($navwraptag != \"\") ? \'<\' .$navwraptag. (($navclass != \"\") ? \' class=\"\' .$navclass. \'\"\' : \'\') . \'>\' : \'\';\r\n			$outStr .= ($prev) ? \'<a\' . (($prevclass != \"\") ? \' class=\"\' .$prevclass. \'\"\' : \'\') . \' href=\"\' .$prevPage. \'\">\' .$prevlabel. \'</a>\'.n : \'\';\r\n			$outStr .= ($next) ? \'<a\' . (($nextclass != \"\") ? \' class=\"\' .$nextclass. \'\"\' : \'\') . \' href=\"\' .$nextPage. \'\">\' .$nextlabel. \'</a>\'.n : \'\';\r\n			$outStr .= ($navwraptag != \"\") ? \'</\' .$navwraptag. \'>\' : \'\';\r\n			$outStr .= n;\r\n		}\r\n	}\r\n\r\n	return $outStr;\r\n}', '0458fca6f041aac905583c4554c72681', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_style', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.30', 'Stylesheet switcher', '<div id=\"smd_help\">\n\n	<h1>smd_style</h1>\n\n	<p>Manage alternate stylesheets and (optionally) switch between them via Javascript. Alternatively, switch between <span class=\"caps\">CSS</span> class names on arbitrary objects to effect style changes.</p>\n\n	<p>Features:</p>\n\n	<ul>\n		<li>Provide a list of style sheet names derived from:\n	<ul>\n		<li>a given list</li>\n		<li>any article field</li>\n		<li>a <code>&lt;txp:variable /&gt;</code></li>\n		<li>a <span class=\"caps\">URL</span> variable</li>\n		<li>some combination of the above</li>\n	</ul></li>\n		<li>Use either the built-in stylesheet switching ability of modern browsers or the plugin&#8217;s javascript switchers</li>\n		<li>Current style stored in a cookie: can be instructed to load that style first on subsequent pages</li>\n		<li>Compatible with Txp stylesheets, &#8216;real&#8217; stylesheets and the rvm_css plugin</li>\n		<li>Optionally build your own stylesheet syntax via a form/container</li>\n		<li>Put Txp tags inside your stylesheets if you wish (requires Txp-style sheet processing, i.e real_sheets must be off)</li>\n	</ul>\n\n	<h2 class=\"author\">Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a></p>\n\n	<h2 class=\"install\">Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1024/smd_style\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=28592\">forum thread</a> for more info and to report the success (or otherwise) of this plugin.</p>\n\n	<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n	<h2 class=\"usage\">Usage</h2>\n\n	<p>There are two tags available: one for use in the <code>&lt;head&gt;</code> of your page to set up the alternate styles, and the other adds a javascript stylesheet switcher to the page, wherever you want it to appear.</p>\n\n	<h2 id=\"smd_style\">smd_style</h2>\n\n	<p class=\"tag-summary\">Somewhere in your <span class=\"caps\">HTML</span> <code>&lt;head&gt;</code> tag, add a call to <code>smd_style</code> to include a list of alternate stylesheets. Use the following attributes to customise the output:</p>\n\n	<h3 class=\"atts\">Attributes</h3>\n\n	<ul>\n		<li><span class=\"atnm\">sheets</span> : a comma-separated list of stylesheets to include. These are their Txp names as given on the Presentation-&gt;Styles tab. See <a href=\"#sheetnames\">sheet names</a> for more</li>\n		<li><span class=\"atnm\">form</span> : the Txp form in which to build your own <code>&lt;link&gt;</code> tags. See <a href=\"#replacements\">replacement tags</a>. If not specified (and no container is used) a standard <span class=\"caps\">HTML</span> link will be output</li>\n		<li><span class=\"atnm\">use_default</span> : the list of stylesheets given in the <code>sheets</code> parameter does not usually contain the &#8216;default&#8217; stylesheet associated with the section. This allows you to always include a &#8216;global&#8217; sheet with core rules in it and have the alternates override/fill in the gaps. If, however, you wish smd_style to output the default stylesheet so you don&#8217;t have to do it elsewhere, specify <code>use_default=&quot;1&quot;</code></li>\n		<li><span class=\"atnm\">promote_recent</span> : Determine what to do when the page loads and the visitor has already switched to an alternate stylesheet. This can be useful to try and help prevent &#8216;flashes of unstyled content&#8217; in good browsers. Can take one of three values:\n	<ul>\n		<li><code>0</code> : do nothing, i.e. sheets are always loaded in the given order</li>\n		<li><code>1</code> (the default) : any style sheet that has been chosen by the visitor will be loaded as soon as possible <strong>after</strong> any core sheets (i.e. sheets that you want to be non-alternate). Thus if you have 2 &#8216;core&#8217; sheets and the visitor chooses the alternate sheet that used to be #4 in the list, it will now be loaded 3rd</li>\n		<li><code>2</code> : any style sheet that has been chosen by the visitor will be loaded first, i.e. the chosen sheet will <em>always</em> be promoted to the #1 slot</li>\n	</ul></li>\n		<li><span class=\"atnm\">skip</span> : in the list of sheets, skip this number of sheets before starting to label them as &#8216;alternate&#8217;. The special default setting of <code>auto</code> behaves thus:\n	<ul>\n		<li>if <code>use_default=&quot;1&quot;</code>, the first sheet in the list will be skipped (i.e. will always be a true stylesheet). Equivalent to <code>skip=&quot;1&quot;</code></li>\n		<li>if <code>use_default=&quot;0&quot;</code> the first sheet in the list will be an alternate stylesheet (i.e. equivalent to <code>skip=&quot;0&quot;</code>)</li>\n	</ul></li>\n		<li><span class=\"atnm\">skip_titles</span> : in the list of sheets, skip this number of sheets before starting to add <code>title</code> attributes. The special default setting of <code>auto</code> works the same as for the <code>skip</code> attribute. Note that some combinations of skip/skip_title are not permitted by the <span class=\"caps\">HTML</span> specification so are disallowed. See <a href=\"#skipping\">skipping</a> for more details</li>\n		<li><span class=\"atnm\">show_empty</span> : by default, if any field (e.g. a custom field) has no value assigned to it and you try to read a sheet name, the field will be ignored. Setting this to <code>1</code> will include a stylesheet with the name of the field itself. See <a href=\"#eg5\">Example 5</a> for a practical application of this</li>\n		<li><span class=\"atnm\">real_sheets</span> : normally, standard <code>css.php?n=sheet_name</code> references are output. If you prefer &#8216;real&#8217; stylesheet filename references, set this attribute to 1. Note you will have to ensure that the stylesheet files really exist on your server in the given directory (see <code>sheets_dir</code>)</li>\n		<li><span class=\"atnm\">sheets_dir</span> : location (relative to the root of your textpattern installation) where your stylesheets are to be found if using <code>real_sheets=&quot;1&quot;</code>. If you have the rvm_css plugin installed it will default to the directory specified in Admin -&gt; Preferences -&gt; Advanced -&gt; Style directory</li>\n		<li><span class=\"atnm\">parse_tags</span> : if you wish to be able to put Txp tags inside your style sheets and have them parsed, set this attribute to 1. Note that this only works if <code>real_styles=&quot;0&quot;</code>  because there&#8217;s no way for Textpattern to intercept a direct call to a stylesheet. Using this feature also incurs a (small) time penalty as each sheet is parsed</li>\n		<li><span class=\"atnm\">delim</span> : the delimiter to use between <code>sheets</code>. Default is the comma (,)</li>\n		<li><span class=\"atnm\">paramdelim</span> : the delimiter to use between additional sheet information. Default is the colon (:)</li>\n	</ul>\n\n	<h3 id=\"sheetnames\">Sheet names</h3>\n\n	<p>When using the <code>sheets</code> attribute, if you precede any sheet name with a <code>?</code> the Txp environment will be searched for a matching location. For example, <code>sheets=&quot;core, ?custom3&quot;</code> would load core.css and then look in the custom3 field for a further list of stylesheets to load. The order in which the locations are searched is: 1) Article fields; 2) <code>&lt;txp:variable /&gt;</code>; 3) The <span class=\"caps\">URL</span>. If a <code>?</code> value is supplied and a suitable location cannot be found, the value you gave will be used verbatim.</p>\n\n	<p>You may also specify up to two additional pieces of information in each sheet definition: 1) the sheet&#8217;s &#8220;pretty name&#8221; that people will see, and 2) its media type (usually <code>screen</code>). To do this, separate the values with colons (unless changed via the <code>paramdelim</code> attribute) like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style use_default=&quot;1&quot;\n     sheets=&quot;core, cats:Cats of the world, printer:Print layout:print&quot; /&gt;\n</code></pre>\n\n	<p>That would output something similar to the following:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot;\n     title=&quot;default&quot; href=&quot;http://site.com/textpattern/css.php?n=default&quot; /&gt;\n&lt;link rel=&quot;alternate stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot;\n     title=&quot;core&quot; href=&quot;http://site.com/textpattern/css.php?n=core&quot; /&gt;\n&lt;link rel=&quot;alternate stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot;\n     title=&quot;Cats of the world&quot; href=&quot;http://site.com/textpattern/css.php?n=cats&quot; /&gt;\n&lt;link rel=&quot;alternate stylesheet&quot; type=&quot;text/css&quot; media=&quot;print&quot;\n     title=&quot;Print layout&quot; href=&quot;http://site.com/textpattern/css.php?n=printer&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"replacements\">Replacement tags</h3>\n\n	<p>If you don&#8217;t like the standard output or wish to fashion your own <code>&lt;link&gt;</code> tags then you can specify container content or a <code>form</code> with which to process the results. There are replacement variables you can employ within your layout to insert the relevant content for each stylesheet:</p>\n\n	<ul>\n		<li><code>{smd_style_name}</code> : the stylesheet name as given in the Txp Styles tab</li>\n		<li><code>{smd_style_url}</code> : the complete <span class=\"caps\">URL</span> (Txp-style or &#8216;real&#8217; <span class=\"caps\">URL</span> depending on the <code>real_sheets</code> attribute)</li>\n		<li><code>{smd_style_media}</code> : the media type of stylesheet (e.g. screen, print&#8230;)</li>\n		<li><code>{smd_style_rel}</code> : the stylesheet relationship (either &#8216;stylesheet&#8217; or &#8216;alternate stylesheet&#8217;)</li>\n		<li><code>{smd_style_title}</code> : the human-friendly title you have assigned to the stylesheet. If not specified, it defaults to the stylesheet name</li>\n		<li><code>{smd_style_counter}</code> : the current stylesheet number being processed</li>\n		<li><code>{smd_style_total}</code> : the total number of stylesheets in the list</li>\n	</ul>\n\n	<h3 id=\"skipping\">Skipping</h3>\n\n	<p>The <span class=\"caps\">HTML</span> specification allows for three types of stylesheet:</p>\n\n	<ul>\n		<li>Persistent : ones that are forced on the user unless they choose &#8220;No Styles&#8221; from their browser menu</li>\n		<li>Preferred : ones that are always loaded but can be swapped out for others</li>\n		<li>Alternate : ones that are not loaded by default but can be switched in and out freely by the user</li>\n	</ul>\n\n	<p>With a combination of <code>use_default</code>, <code>skip</code> and <code>skip_title</code> you can offer any of these types. Some combinations of <code>skip</code> and <code>skip_title</code> are not permitted because they would render illegal <span class=\"caps\">HTML</span> (e.g. the case when you have an alternate stylesheet without a title).</p>\n\n	<p>To try and make it a little easier to visualise, here is a table that shows which of the stylesheet flavours are served with varying values of these attributes. Assume you have two stylesheets labelled A and B. Sheet A will be set to:</p>\n\n	<table>\n		<tr>\n			<td>&nbsp; </td>\n			<td><strong>skip=0</strong> </td>\n			<td><strong>skip=1</strong> </td>\n		</tr>\n		<tr>\n			<td> <strong>skip_title=0</strong> </td>\n			<td> Alternate </td>\n			<td> Preferred </td>\n		</tr>\n		<tr>\n			<td> <strong>skip_title=1</strong> </td>\n			<td> (disallowed) Alternate </td>\n			<td> Persistent </td>\n		</tr>\n	</table>\n\n	<p>The same logic applies if using the <code>auto</code> parameter and/or the <code>use_default</code> attribute. The first sheet in the list will become one of those flavours of Stylesheet where the values of <code>skip</code> and <code>skip_title</code> intersect. If using higher values, the first few sheets become those particular flavours.</p>\n\n	<h2 id=\"smd_styleswitch\">smd_styleswitch</h2>\n\n	<p class=\"important\">Requires jQuery to be loaded on your page, before the call to smd_styleswitch. Tested with v1.7.</p>\n\n	<p>On its own the <a href=\"#smd_style\">smd_style</a> tag gives the visitor the ability to switch styles via the &#8216;View alternate stylesheet&#8217; facility of modern browsers. If you wish to offer them a way of instantaneously switching (and remembering) the chosen theme from within your content, use <code>smd_styleswitch</code> somewhere in the flow of your page to insert a stylesheet switcher.</p>\n\n	<p>Alternatively, you may decide not to bother with traditional stylesheet switching at all and may prefer to simply <a href=\"#mode2\">switch class names</a> on certain elements of the page to effect changes.</p>\n\n	<p>It can be customised as follows:</p>\n\n	<h3 id=\"mode1\">Mode 1: Stylesheet switcher</h3>\n\n	<h4 class=\"atts \" id=\"m1attributes\">Attributes</h4>\n\n	<ul>\n		<li><span class=\"atnm\">wraptag</span> : standard <span class=\"caps\">HTML</span> tag to wrap the entire switcher with. Default is <code>ul</code>. If you do not specify this attribute you <em>must</em> create your own container somewhere else in the page and tell the plugin its ID or class so it can find it</li>\n		<li><span class=\"atnm\">html_id</span> : <span class=\"caps\">HTML</span> ID to apply to the wraptag. Default is unset. If you have not specified a <code>wraptag</code>, you may give the ID of your own container so the switcher can be inserted</li>\n		<li><span class=\"atnm\">class</span> : <span class=\"caps\">CSS</span> class to apply to the wraptag. Default is <code>smd_switcher</code>. If you have not specified a <code>wraptag</code>, you may give the class name of your own container so the switcher can be inserted</li>\n		<li><span class=\"atnm\">break</span> : Default is <code>li</code>. Can be either:\n	<ul>\n		<li>a standard <span class=\"caps\">HTML</span> tag to wrap each stylesheet name and link with.</li>\n		<li>text to place between each element, such as <code>break=&quot;--&quot;</code> (see <code>break_is_tag</code>)</li>\n	</ul></li>\n		<li><span class=\"atnm\">break_is_tag</span> : if the <code>break</code> attribute is a tag, this should be set to 1 (which it is by default). If you wish to use something else between each stylesheet name (e.g. <code>break=&quot; | &quot;</code>) set this to 0</li>\n		<li><span class=\"atnm\">linkclass</span> : <span class=\"caps\">CSS</span> class to apply to each stylesheet link that is inserted. Default is <code>smd_styleswitch</code></li>\n		<li><span class=\"atnm\">linkloc</span> : the <span class=\"caps\">HTML</span> attribute to compare for a match. When you click a link, in order to determine which one has been clicked, some unique information must be present in each link. You might choose the anchor&#8217;s &#8216;name&#8217; attribute (in which case, use <code>linkloc=&quot;name&quot;</code>) or perhaps the &#8216;rel&#8217;. Default: <code>rel</code> (in this mode) or <code>name</code> in class switching mode</li>\n		<li><span class=\"atnm\">activeclass</span> : <span class=\"caps\">CSS</span> class to indicate the currently selected stylesheet link. Default is <code>smd_currstyle</code></li>\n		<li><span class=\"atnm\">alt_only</span> : if set to <code>1</code> will only offer stylesheets with a rel that includes &#8216;alternate&#8217; in the list. The default behaviour (0) is to list all stylesheets on the page that contain a &#8216;title&#8217; attribute</li>\n		<li><span class=\"atnm\">sort</span> : set to 1 (the default) to sort the stylesheets in alphabetical order. If set to 0, the most recently used stylesheet will be shown at the head of the list</li>\n		<li><span class=\"atnm\">case_sensitive</span> : whether the <code>sort</code> is case sensitive (1) or not (0). Default is 0</li>\n		<li><span class=\"atnm\">expiry</span> : number of days after which the <code>smd_style</code> cookie that holds the current style information remains valid. Default is <code>30</code>. Set to 0 to disable cookie storing</li>\n		<li><span class=\"atnm\">form</span> : if you prefer to make your own links instead of using the default anchor tags, either use the tag as a container or specify a Txp form with which to process each link. Default: unset</li>\n	</ul>\n\n	<p>The stylesheet switcher that is inserted at the given location consists of an anchor tag with a link to the new stylesheet. The text within the anchor is the stylesheet title (if supplied) or its name if not. Note that the switcher will pick up <em>all stylesheets that have a title attribute</em> so if any of your other stylesheets not controlled by the plugin have titles, they will be included as well. This could be considered a feature(!)</p>\n\n	<p>If you are a neatness buff and prefer that all javascript goes in the <code>&lt;head&gt;</code>, using <code>wraptag=&quot;&quot;</code> will allow you to insert the <code>&lt;txp:smd_styleswitch /&gt;</code> tag in the head of your document while locating your switching container elsewhere. Just tell the plugin either the <code>html_id</code> or <code>class</code> of your switch container and the switcher will be inserted there.</p>\n\n	<h3 id=\"mode2\">Mode 2: Class switcher</h3>\n\n	<p>The other way to use the tag is as a class changer. You may target any selector on the page and offer a list of class names that will be applied to that element when the relevant switch link is chosen.</p>\n\n	<h4 class=\"atts \" id=\"m2attributes\">Attributes</h4>\n\n	<p>Takes the same attributes as above, with the exception that <code>alt_only</code> is ignored. In addition:</p>\n\n	<ul>\n		<li><span class=\"atnm\">byclass</span> : this puts the plugin into &#8220;class switching mode&#8221;. It works like the <code>sheets</code> attribute of the smd_style tag. Specify a list of class names that users may choose between. If you follow the class name with a colon (:) you may specify a &#8220;friendly&#8221; name to show to people instead of the class name itself. The special class name <code>default</code> removes all classes that have been applied to the selector. See <a href=\"#eg7\">example 7</a>.</li>\n		<li><span class=\"atnm\">destination</span> : the <span class=\"caps\">DOM</span> element ID you wish to add classes to, e.g. <code>destination=&quot;#my_div&quot;</code> or <code>destination=&quot;.my_class&quot;</code>. Default: <code>body</code></li>\n		<li><span class=\"atnm\">clean</span> : if this is set to 1, anything inside your designated wraptag container will be removed before adding the switcher content. Default: 1</li>\n		<li><span class=\"atnm\">delim</span> : the delimiter to use between items in the <code>byclass</code> attribute. Default: comma</li>\n		<li><span class=\"atnm\">paramdelim</span> : the delimiter to use between class and name in the <code>byclass</code> attribute. Default: colon</li>\n	</ul>\n\n	<p>If you put more than one smd_styleswitch tag in this mode, each will function independently so you can add class combos to the same element (see <a href=\"#eg8\">example 8</a>). Very useful for specifying two different lists of classes: one for &#8220;screen&#8221; styles and one for &#8220;print styles&#8221; that operate independently.</p>\n\n	<p>But beware the following caveats when putting multiple smd_styleswitch tags on a single page. For hassle-free operation, you should:</p>\n\n	<ul>\n		<li>attach each tag to a unique element. In other words, change the <code>class</code> or <code>html_id</code> to something different in each tag</li>\n		<li>ensure that <code>linkclass</code> is also unique for each tag, otherwise you may get odd results when choosing a style</li>\n		<li>be aware that using the same classname twice (e.g. <code>default</code> in two tags) will not switch properly if you are using a <code>linkloc=&quot;id&quot;</code> because the plugin will try and assign the same name to two different IDs, which is illegal <span class=\"caps\">DOM</span> markup</li>\n	</ul>\n\n	<h2 id=\"gotchas\">Gotchas</h2>\n\n	<ul>\n		<li>Make sure jQuery can be found by the page, or smd_styleswitch will do nothing</li>\n		<li>If using certain wraptag elements (e.g. the default &#8220;ul&#8221;), the <span class=\"caps\">HTML</span> validator may complain that the element is &#8220;unfinished&#8221; because it does not render the javascript which inserts the remaining &#8216;li&#8217; tags. If this bothers you, set <code>wraptag=&quot;&quot;</code> and specify your own container, filling it with an empty child tag (e.g. <code>&lt;li&gt;&amp;nbsp;&lt;/li&gt;</code>). The plugin will empty the contents of the container before adding the links as long as the <code>clean=&quot;1&quot;</code> is used in the tag (which it does by default)</li>\n		<li>You must use one (or both) of either <code>class</code> or <code>html_id</code> attributes, otherwise the plugin will not be able to attach itself. the html_id is used in preference to <code>class</code> if both are used</li>\n	</ul>\n\n	<h2>Examples</h2>\n\n	<h3 id=\"eg1\">Example 1: alternate stylesheets</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style\n     sheets=&quot;yellow, green, brown, blue, pink, black&quot; /&gt;\n&lt;txp:smd_styleswitch wraptag=&quot;&quot; /&gt;\n</code></pre>\n\n	<p>Somewhere further down the page you would have to add:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;ul class=&quot;smd_switcher&quot;&gt;&lt;/ul&gt;\n</code></pre>\n\n	<p>That will add six alternate stylesheets to the page and add a switcher wherever your <code>&lt;ul&gt;</code> appears on the page. A few modifications are possible:</p>\n\n	<ul>\n		<li>with rvm_css installed, adding <code>real_sheets=&quot;1&quot;</code> to the <code>smd_style</code> tag would change the <span class=\"caps\">URL</span>s to &#8216;real&#8217; <span class=\"caps\">CSS</span> file paths</li>\n		<li>adding <code>use_default=&quot;1&quot;</code> will add the current section&#8217;s stylesheet to the list as well. If it happens to be the same as one of the ones in the list already, it will only be used once</li>\n		<li>adding <code>skip=&quot;2&quot;</code> will cause the &#8216;yellow&#8217; and &#8216;green&#8217; sheets to be static (non-alternate)</li>\n		<li>using <code>promote_recent</code> will change the load order as follows:\n	<ul>\n		<li><code>0</code> : load order will always be yellow, green, brown, blue, pink, black</li>\n		<li><code>1</code> : promote the chosen sheet up the list. With <code>use_default=&quot;1&quot; skip=&quot;2&quot;</code>, if the visitor chose &#8216;pink&#8217; as their theme and refreshed the page, the load order would be: yellow, pink, green, brown, blue, black (yellow and pink would be listed as &#8220;static&#8221; stylesheets)</li>\n		<li><code>2</code> : force whichever sheet was most recently chosen to be the first loaded. Again, if the visitor chose &#8216;pink&#8217; as their preferred sheet and refreshed the page, the load order would be pink, yellow, green, brown, blue, black (pink and yellow would be static sheets)</li>\n	</ul></li>\n	</ul>\n\n	<h3 id=\"eg2\">Example 2: using txp_variable</h3>\n\n	<p>To set up the names of the stylesheets you want to include in a <code>&lt;txp:variable /&gt;</code> try this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;alt_styles&quot;\n     value=&quot;girls:Girly theme, boys:Lads only&quot; /&gt;\n&lt;txp:smd_style sheets=&quot;?alt_styles&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"eg3\">Example 3: persistent sheets</h3>\n\n	<p>Grab the default sheet, the &#8220;fixed&#8221; sheet (and make them both &#8216;static&#8217;) then look in the custom field labelled <code>alt_styles</code> and the <span class=\"caps\">URL</span> variable <code>mytheme</code> for more sheet definitions.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style sheets=&quot;fixed, ?alt_styles, ?mytheme&quot;\n     use_default=&quot;1&quot; skip=&quot;2&quot; /&gt;\n</code></pre>\n\n	<p>Note that no distinction is made where to find the variables, they are just checked in order and the first one it finds that matches (if at all) will be used. Thus if you had a custom field labelled <code>alt_styles</code> and someone put <code>site.com/my_page?alt_styles=two,three,four</code> on the <span class=\"caps\">URL</span>, you would still have them read from the custom field because it is &#8216;higher&#8217; in the hierarchy. One caveat is that if the custom field is empty, the next place in the list is checked until all locations are exhausted, though you can ignore empty fields with <code>show_empty=&quot;0&quot;</code>.</p>\n\n	<h3 id=\"eg4\">Example 4: custom link layout</h3>\n\n	<p>For a non-<span class=\"caps\">HTML</span> <span class=\"caps\">DTD</span> you may need to drop the trailing <code>/</code> on the <code>&lt;link&gt;</code> elements, so you could do this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style sheets=&quot;blue, red, green&quot;&gt;\n &lt;link rel=&quot;{smd_style_rel}&quot; type=&quot;text/css&quot;\n     media=&quot;{smd_style_media}&quot; href=&quot;{smd_style_url}&quot;\n     title=&quot;{smd_style_title}&quot;&gt;\n&lt;/txp:smd_style&gt;\n</code></pre>\n\n	<h3 id=\"eg5\">Example 5: show_empty</h3>\n\n	<p>The <code>show_empty</code> attribute seems fairly pointless on the surface, but imagine this scenario: you set up a custom field called <code>default_style</code>. When authoring articles, people can insert the name of a stylesheet in there to style the page with. But if they leave it blank you could ensure the page renders with at least <em>some</em> default content by creating a stylesheet called &#8220;default_style&#8221; (i.e. the same name as the custom field) and using a tag like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style sheets=&quot;?default_style&quot;\n     show_empty=&quot;1&quot; /&gt;\n</code></pre>\n\n	<p>If the custom field is empty, the plugin converts the request to <code>&lt;txp:smd_style sheets=&quot;default_style&quot; /&gt;</code>.</p>\n\n	<h3 id=\"eg6\">Example 6: tag parsing</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_style sheets=&quot;first, second, third&quot;\n     parse_tags=&quot;1&quot; /&gt;\n</code></pre>\n\n	<p>If one of your stylesheets contained the following:</p>\n\n<pre class=\"block\"><code class=\"block\">.filler {\n   background:url(&lt;txp:site_url /&gt;images/bg.jpg);\n   color:red;\n}\n</code></pre>\n\n	<p>The Txp tag would be replaced with the contents before being served. This does have a performance penalty as the stylesheet is fetched and parsed, but can be very useful. You may use <code>&lt;txp:php&gt;&lt;/txp:php&gt;</code> tags in the stylesheet as long as the admin preference to allow page level <span class=\"caps\">PHP</span> is enabled. Many thanks to akokskis for some of the code from ako_cssParse that enables this feature.</p>\n\n	<h3 id=\"eg7\">Example 7: class switching</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_styleswitch\n     byclass=&quot;default:Normal, mini:Small,\n       maxi:Large&quot; class=&quot;switcher&quot; /&gt;\n</code></pre>\n\n	<p>That tag will show a three-way class switcher containing three anchors as an unordered list. When you click:</p>\n\n	<ul>\n		<li><em>Small</em> the <code>mini</code> class is added to the <code>&lt;body&gt;</code> tag</li>\n		<li><em>Large</em> the <code>maxi</code> class replaces the <code>mini</code> class</li>\n		<li><em>Normal</em> either <code>mini</code> or <code>maxi</code> are removed (whichever was in force at the time)</li>\n	</ul>\n\n	<p>Thus with appropriate rules in a stylesheet you can switch the font size of the document on the fly. For example:</p>\n\n<pre class=\"block\"><code class=\"block\">body {\n  font-size:1em;\n}\nbody.mini {\n  font-size:.8em;\n}\nbody.maxi {\n  font-size:1.3em;\n}\n</code></pre>\n\n	<p>If you loaded your stylesheet with <code>media=&quot;print&quot;</code> then the styles would only be applied to the printed version of the page.</p>\n\n	<h3 id=\"eg8\">Example 8: multiple class switchers</h3>\n\n	<p>Let&#8217;s add a second smd_styleswitch tag to the page, in addition to the one in <a href=\"#eg7\">example 7</a>.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_styleswitch\n     byclass=&quot;default:Black on white, yob:Yellow on blue,\n       gob:Green on black&quot; linkclass=&quot;smd_colours&quot; /&gt;\n</code></pre>\n\n	<p>Notice first that this one takes <code>linkclass</code>. This is so that the two switchers are uniquely addressable &#8212; the first used the default linkclass of <code>smd_styleswitch</code>. Without this, the two switchers will clash and things won&#8217;t work properly.</p>\n\n	<p>The second switcher will be rendered and the two work independently; visitors can choose one style from each switcher. So they might click:</p>\n\n	<ul>\n		<li><em>Large</em> and <em>Yellow on blue</em> : both <code>maxi</code> and <code>yob</code> classes would be added to the <code>&lt;body&gt;</code> tag</li>\n		<li><em>Large</em> and <em>Green on black</em> : <code>maxi</code> and <code>gob</code> classes would apply to the body</li>\n		<li><em>Normal</em> and <em>Green on black</em> : just the <code>gob</code> style is applied</li>\n		<li><em>Normal</em> and <em>Black on white</em> : both styles are removed. Note that any other styles you may have manually applied are left untouched</li>\n	</ul>\n\n	<p>Again, with suitable classes you can allow people to combine the look of a page to their tastes. By default, their preferences are saved in the <code>smd_style</code> cookie so each page will have the same style applied.</p>\n\n	<h2 class=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>12 Oct 2008 | 0.10 | Initial release</li>\n		<li>18 Oct 2008 | 0.20 | Added <code>parse_tags</code> (thanks akokskis / johnstephens) ; added <code>skip_title</code> ; fixed invalid link tags ; <code>real_sheets</code> now work without a form/container</li>\n		<li>17 Nov 2011 | 0.30 | smd_styleswitch can now switch classes too ; added <code>html_id</code> and <code>form</code> attributes ; cookie can be disabled ; jQuery 1.3 fix</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_style\r\n *\r\n * A Textpattern CMS plugin for switching stylesheets on websites\r\n *  -> Use the browser\'s switcher or the plugin\'s built-in jQuery switcher\r\n *  -> Cookie stores current style in use so it applies across pages\r\n *  -> Compatible with Txp sheets, file sheets, and rvm_css\r\n *  -> Ability to put Txp tags in your stylesheet (if using Txp\'s sheets)\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// Shamelessly plagiarised from ako_cssParse (thanks!)\r\nif (@txpinterface===\"css\" && gps(\'smd_styleparse\') == 1) {\r\n	global $prefs;\r\n	$sheet = (gps(\'n\'));\r\n	if ($sheet) {\r\n		$css = safe_field(\'css\',\'txp_css\',\"name=\'\".doSlash($sheet).\"\'\");\r\n		if ($css) {\r\n			$css = parse($css);\r\n			$css = ($prefs[\'allow_page_php_scripting\']) ? evalString($css) : $css;\r\n			header(\'Content-type: text/css\');\r\n			echo($css);\r\n			exit();\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_style($atts, $thing) {\r\n	global $pretext, $thisarticle, $prefs, $variable;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'sheets\' => \'\',\r\n		\'form\' => \'\',\r\n		\'use_default\' => \'0\', // 0=no; 1=yes as primary sheet\r\n		\'promote_recent\' => \'1\', // 0=retain order; 1=promote used sheets but honour core sheets; 2=promote at all costs\r\n		\'skip\' => \'auto\',\r\n		\'skip_title\' => \'auto\',\r\n		\'show_empty\' => \'0\',\r\n		\'real_sheets\' => \'0\', // 0=no; 1=yes\r\n		\'sheets_dir\' => @$prefs[\'rvm_css_dir\'],\r\n		\'parse_tags\' => \'0\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	$out = array();\r\n	$styles = do_list($sheets, $delim);\r\n	$expstyles = array();\r\n	foreach ($styles as $idx => $opt) {\r\n		if (strpos($opt, \"?\") === 0) {\r\n			$opt = substr(strtolower($opt), 1);\r\n			$tmpa = array();\r\n			if (isset($pretext[$opt]) && $pretext[$opt] != \"\") {\r\n				$tmpa = do_list($pretext[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($thisarticle[$opt]) && $thisarticle[$opt] != \"\") {\r\n				$tmpa = do_list($thisarticle[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($variable[$opt]) && $variable[$opt] != \"\") {\r\n				$tmpa = do_list($variable[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($_GET[$opt]) && $_GET[$opt] != \"\") {\r\n				$tmpa = do_list(gps($opt), $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if ($show_empty) {\r\n				$expstyles[] = $opt;\r\n			}\r\n		} else {\r\n			$expstyles[] = $opt;\r\n		}\r\n	}\r\n	$styles = $expstyles;\r\n\r\n	$skip = ($skip==\"auto\") ? $use_default : $skip;\r\n	$skip_title = ($skip_title==\"auto\") ? $use_default : $skip_title;\r\n	$skip_title = ($skip==0) ? 0 : $skip_title;\r\n	if ($use_default > 0) {\r\n		$default = safe_row(\'css\', \'txp_section\', \"name = \'\".doSlash($pretext[\'s\']).\"\'\", $debug);\r\n		array_unshift($styles, $default[\'css\']);\r\n	}\r\n\r\n	// If a previous style has been chosen, move it to the front of the list (if requested).\r\n	// This forces good browsers to \'flash\' less when changing pages\r\n	$cookiestyle = cs(\"smd_style\");\r\n	$cookiestyle = do_list($cookiestyle);\r\n	$cstyles = array();\r\n	foreach ($cookiestyle as $cstyle) {\r\n		$cparts = explode(\":\", $cstyle);\r\n		if (count($cparts) >= 2) {\r\n			$cstyles[$cparts[0]] = $cparts[1];\r\n      } else {\r\n			$cstyles[\"all\"] = $cparts[0];\r\n		}\r\n	}\r\n\r\n	if ($promote_recent && $cstyles) {\r\n		$rejig = array();\r\n		foreach ($styles as $idx => $style) {\r\n			if (($idx < $skip) && $promote_recent == 1) {\r\n				$rejig[] = $style;\r\n				continue;\r\n			}\r\n			if (in_array($style, $cstyles) !== false) {\r\n				$rejig[] = $style;\r\n				break;\r\n			}\r\n		}\r\n		$styles = array_merge($rejig, $styles);\r\n		$styles = array_unique($styles);\r\n	}\r\n\r\n	if ($debug) {\r\n		echo \"++ STYLES ++\";\r\n		dmp($styles);\r\n	}\r\n	$stylecnt = 1;\r\n	$num_styles = count($styles);\r\n	foreach ($styles as $style) {\r\n		$styleopts = do_list($style, $paramdelim);\r\n		$name = (count($styleopts) >= 1) ? $styleopts[0] : \'\';\r\n		$title = ($stylecnt > $skip || $stylecnt > $skip_title) ? ((count($styleopts) >= 2) ? $styleopts[1] : $name) : \'\';\r\n		$media = (count($styleopts) >= 3) ? $styleopts[2] : \'screen\';\r\n		$rel = (($stylecnt > $skip) ? \"alternate \" : \'\') . \"stylesheet\";\r\n		$url = hu. (($real_sheets && $sheets_dir) ? $sheets_dir . \'/\' .strtolower(sanitizeForUrl($name)). \'.css\' : \'textpattern/css.php?n=\'.$name . (($parse_tags) ? a.\'smd_styleparse=1\' : \'\'));\r\n\r\n		// Make up the replacement array\r\n		$replacements = array(\r\n				\'{smd_style_name}\' => $name,\r\n				\'{smd_style_url}\' => $url,\r\n				\'{smd_style_media}\' => $media,\r\n				\'{smd_style_rel}\' => $rel,\r\n				\'{smd_style_title}\' => $title,\r\n				\'{smd_style_counter}\' => $stylecnt,\r\n				\'{smd_style_total}\' => $num_styles,\r\n		);\r\n\r\n		if ($debug) {\r\n			echo \"++ REPLACEMENTS ++\";\r\n			dmp($replacements);\r\n		}\r\n\r\n		$out[] = ($thing) ? parse(strtr($thing, $replacements)) : (\r\n					($form) ? strtr(parse_form($form), $replacements) : (\r\n					($real_sheets || $parse_tags) ? \'<link rel=\"\'.$rel.\'\"\'. (($title) ? \' title=\"\'.$title.\'\"\' : \'\') .\' href=\"\'.$url.\'\" media=\"\'.$media.\'\" type=\"text/css\" />\' :\r\n					css(array(\r\n						\"format\" => \"link\",\r\n						\"media\" => $media,\r\n						\"name\" => $name,\r\n						\"rel\" => $rel,\r\n						\"title\" => $title,\r\n						))\r\n					));\r\n\r\n		$stylecnt++;\r\n	}\r\n\r\n	return join(\"\\n\", $out);\r\n}\r\n\r\n// ------------------------\r\n// Javascript style switcher\r\nfunction smd_styleswitch($atts, $thing) {\r\n	global $smd_style_loader;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'wraptag\' => \'ul\',\r\n		\'wrapadd\' => \'\', // Perhaps this should be automatic only\r\n		\'class\' => \'smd_switcher\',\r\n		\'html_id\' => \'\',\r\n		\'break\' => \'li\',\r\n		\'break_is_tag\' => \'1\',\r\n		\'selectlabel\' => \'\',\r\n		\'linkclass\' => \'smd_styleswitch\',\r\n		\'activeclass\' => \'smd_currstyle\',\r\n		\'alt_only\' => \'0\', // 0=No; 1=Yes, only show alternate stylesheets\r\n		\'form\' => \'\',\r\n		\'sort\' => \'1\', // 0=No; 1=Yes\r\n		\'case_sensitive\' => \'0\',\r\n		\'expiry\' => \'30\', // In days\r\n		\'byclass\' => \'\',\r\n		\'destination\' => \'body\',\r\n		\'linkloc\' => \'\',\r\n		\'clean\' => \'1\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Set default form info if none specified\r\n	$fixlist = ($byclass) ? 1 : 0;\r\n	$linkloc = ($linkloc) ? $linkloc : (($fixlist) ? \'name\' : \'rel\');\r\n	$thing = ($form) ? fetch_form($form) : $thing ;\r\n	$thing = (empty($thing))\r\n		? (($fixlist)\r\n			? (($wraptag==\'select\') ? \'<option value=\"{smd_style_name}\" class=\"{smd_style_linkclass}\">{smd_style_title}</option>\' : \'<a name=\"{smd_style_name}\" href=\"#\" class=\"{smd_style_linkclass} {smd_style_activeclass}\">{smd_style_title}</a>\')\r\n			: \'<a rel=\"{smd_style_title}\" href=\"{smd_style_url}\" class=\"{smd_style_linkclass} {smd_style_activeclass}\">{smd_style_title}</a>\'\r\n		)\r\n		: $thing;\r\n	$thing = str_replace(\"\\r\\n\", \"\", $thing);\r\n	if ($wraptag == \'select\') {\r\n		$break = (strpos($thing, \'</option>\')) ? \'\' : \'option\';\r\n		$break_is_tag = 1;\r\n	}\r\n\r\n	// Set up the js if using a fixed list of classes\r\n	$byclass = do_list($byclass, $delim);\r\n	$stylecnt = 1;\r\n	$num_styles = count($byclass);\r\n	$classlist = $oplist = array();\r\n\r\n	if ($wraptag==\'select\' && empty($wrapadd)) {\r\n		$wrapadd = <<<EOJS\r\njQuery(selector).change(function() {\r\n	self.smd_switchStylesheet(self.jqsel, jQuery(selector+\" option:selected\").val(), type, self.classlist, self.destination);\r\n});\r\nEOJS;\r\n	}\r\n\r\n	// Make sure there are always jQuery elements to swing off, thus avoiding js errors\r\n	$class = (trim($class)==\"\") ? \"smd_switcher\" : $class;\r\n	$html_id = trim($html_id);\r\n	$linkclass = (trim($linkclass)==\"\") ? \'smd_styleswitch\' : $linkclass;\r\n	$jqSel = ($html_id) ? \'#\'.$html_id : \'.\'.$linkclass;\r\n	$stylematch = (($alt_only) ? \'alternate \' : \'\').\'style\';\r\n	$break_is_tag = ($break==\"\") ? 0 : $break_is_tag;\r\n\r\n	$cookiestyle = cs(\"smd_style\");\r\n	$cookiestyle = do_list($cookiestyle);\r\n	$cstyles = array();\r\n	foreach ($cookiestyle as $cstyle) {\r\n		$cparts = explode(\":\", $cstyle);\r\n		if (count($cparts) >= 2) {\r\n			$cstyles[$cparts[0]] = $cparts[1];\r\n      } else {\r\n			$cstyles[\"all\"] = $cparts[0];\r\n		}\r\n	}\r\n\r\n	foreach ($byclass as $cls) {\r\n		if ($cls==\"\") continue;\r\n		$clsopts = do_list($cls, $paramdelim);\r\n		$name = (count($clsopts) >= 1) ? sanitizeForUrl($clsopts[0]) : \'\';\r\n		$title = (count($clsopts) >= 2) ? $clsopts[1] : $name;\r\n		$media = (count($clsopts) >= 3) ? $clsopts[2] : \'screen\';\r\n\r\n		if (isset($cstyles[$jqSel]) && $cstyles[$jqSel] == $name) {\r\n			$actCls = $activeclass;\r\n		} else {\r\n			$actCls = \'\';\r\n		}\r\n		// Make up the replacement array\r\n		$replacements = array(\r\n				\'{smd_style_name}\' => $name,\r\n				\'{smd_style_title}\' => addslashes($title),\r\n				\'{smd_style_linkclass}\' => $linkclass,\r\n				\'{smd_style_activeclass}\' => $actCls,\r\n				\'{smd_style_autosub}\' => \'\', // TODO: set up a call to smd_atyleswitch() that can be called from onchange/onclick/etc\r\n				\'{smd_style_counter}\' => $stylecnt,\r\n				\'{smd_style_total}\' => $num_styles,\r\n		);\r\n		$stylecnt++;\r\n		$classlist[] = \'smd_style_cls.addCls(\"\'.$name.\'\");\';\r\n		$oplist[] = \"smd_style_cls.addSheet(\'\".parse(strtr($thing, $replacements)).\"\');\";\r\n	}\r\n	$classlist = join(n, $classlist);\r\n	$oplist = join(n, $oplist);\r\n\r\n	$pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\');\r\n\r\n	$out = array();\r\n	if ($wraptag) {\r\n		$out[] = \'<\'.$wraptag.(($html_id) ? \' id=\"\'.$html_id.\'\"\' : \'\').\' class=\"\'.$class.\'\"></\'.$wraptag.\'>\';\r\n	}\r\n\r\n	$out[] = <<<EOJS\r\n<script type=\"text/javascript\">\r\n<!--\r\njQuery(function() {\r\nsmd_style_cls = new smd_ClassHandler(\"{$fixlist}\");\r\n{$classlist}\r\n{$oplist}\r\nsmd_style_cls.setBreak(\'{$break}\');\r\nsmd_style_cls.setBreakIsTag(\'{$break_is_tag}\');\r\nsmd_style_cls.setHtmlID(\'{$html_id}\');\r\nsmd_style_cls.setClassName(\'{$class}\');\r\nsmd_style_cls.setJQSel(\'{$jqSel}\');\r\nsmd_style_cls.setDestination(\'{$destination}\');\r\nsmd_style_cls.setLinkClass(\'{$linkclass}\');\r\nsmd_style_cls.setLinkLoc(\'{$linkloc}\');\r\nsmd_style_cls.setClean(\'{$clean}\');\r\nsmd_style_cls.setPath(\'{$pub_path}\');\r\nsmd_style_cls.setActiveClass(\'{$activeclass}\');\r\nsmd_style_cls.setSort(\'{$sort}\');\r\nsmd_style_cls.setCaseSensitive(\'{$case_sensitive}\');\r\nsmd_style_cls.setExpiry(\'{$expiry}\');\r\nsmd_style_cls.setStyleMatch(\'{$stylematch}\');\r\nsmd_style_cls.setThing(\'{$thing}\');\r\nsmd_style_cls.construct();\r\n});\r\n-->\r\n</script>\r\nEOJS;\r\n\r\n	if ($smd_style_loader == 0) {\r\n		$out[] = <<<EOJS\r\n<script type=\"text/javascript\" src=\"/textpattern/textpattern.js\"></script>\r\n<script type=\"text/javascript\">\r\n<!--\r\nvar smd_style_cls = \'\';\r\nfunction smd_ClassHandler(type) {\r\n	// Variables\r\n	this.classlist = [];\r\n	this.stylelist = [];\r\n	this.act_class = \'\';\r\n	this.brk = \'\';\r\n	this.brkistag = \'\';\r\n	this.htmlid = \'\';\r\n	this.classname = \'\';\r\n	this.jqsel = \'\';\r\n	this.destination = \'\';\r\n	this.linkclass = \'\';\r\n	this.linkloc = \'\';\r\n	this.clean = \'\';\r\n	this.path = \'.\';\r\n	this.activeclass = \'\';\r\n	this.wraptag = \'\';\r\n	this.sortit = \'\';\r\n	this.case_sensitive = \'\';\r\n	this.expiry = \'\';\r\n	this.stylematch = \'\';\r\n	this.thing = \'\';\r\n	var self = this;\r\n\r\n	// Methods\r\n	this.addCls = function addCls(val) { this.classlist.push(val); }\r\n	this.addSheet = function addSheet(val) { this.stylelist.push(val); }\r\n	this.setBreak = function setBreak(val) { this.brk = val; }\r\n	this.setBreakIsTag = function setBreakIsTag(val) { this.brkistag = val; }\r\n	this.setHtmlID = function setHtmlID(val) { this.htmlid = val; }\r\n	this.setClassName = function setClassName(val) { this.classname = val; }\r\n	this.setJQSel = function setJQSel(val) { this.jqsel = val; }\r\n	this.setLinkClass = function setLinkClass(val) { this.linkclass = val; }\r\n	this.setLinkLoc = function setLinkLoc(val) { this.linkloc = val; }\r\n	this.setClean = function setClean(val) { this.clean = val; }\r\n	this.setPath = function setPath(val) { this.path = val; }\r\n	this.setActiveClass = function setActiveClass(val) { this.activeclass = val; }\r\n	this.setDestination = function setDestination(val) { this.destination = val; }\r\n	this.setSort = function setSort(val) { this.sortit = val; }\r\n	this.setCaseSensitive = function setCaseSensitive(val) { this.case_sensitive = val; }\r\n	this.setExpiry = function setExpiry(val) { this.expiry = val; }\r\n	this.setStyleMatch = function setStyleMatch(val) { this.stylematch = val; }\r\n	this.setThing = function setThing(val) { this.thing = val; }\r\n	this.getClasses = function getClasses() { return this.classlist; }\r\n\r\n	this.sorter = function sorter() {\r\n		if (this.sortit==1) {\r\n			this.stylelist = (this.case_sensitive==0)\r\n				? this.stylelist.sort(function(a,b) {\r\n					var x = a.toLowerCase();\r\n					var y = b.toLowerCase();\r\n					return ((x < y) ? -1 : ((x > y) ? 1 : 0));\r\n					})\r\n				: this.stylelist.sort();\r\n		}\r\n	}\r\n	// Switch to the given stylesheet\r\n	this.smd_switchStylesheet = function smd_switchStylesheet(lcls, styleref, type, clslist, dest) {\r\n		if (lcls == self.jqsel) {\r\n			if (type == 1) {\r\n				jQuery.each(clslist, function(idx, cls) {\r\n					if (jQuery(dest).hasClass(cls)) {\r\n						jQuery(dest).removeClass(cls);\r\n					}\r\n				});\r\n				if (styleref != \"default\") {\r\n					jQuery(dest).addClass(styleref);\r\n				}\r\n			} else {\r\n				jQuery(\'link[rel*=style][title]\').each(function(id) {\r\n					this.disabled = true;\r\n					if (this.getAttribute(\'title\') == styleref) this.disabled = false;\r\n				});\r\n			}\r\n			if (this.expiry > 0) {\r\n				var cstyle = getCookie(\'smd_style\');\r\n				if (cstyle == null) {\r\n					cstyle = \'\';\r\n				}\r\n				cstyle = cstyle.split(\'^\');\r\n				var newstyle = self.jqsel+\':\'+styleref;\r\n				var found = false;\r\n\r\n				for (var idx = 0; idx < cstyle.length; idx++) {\r\n					if (cstyle[idx] == \'\') continue;\r\n					var thispair = cstyle[idx].split(\':\');\r\n					if (thispair[0] == self.jqsel) {\r\n						cstyle[idx] = newstyle;\r\n						found=true;\r\n						break;\r\n					}\r\n				}\r\n				if (found == false) {\r\n					cstyle.push(newstyle);\r\n				}\r\n\r\n				var date = new Date();\r\n				date.setTime(date.getTime() + (self.expiry*24*60*60*1000));\r\n				var expires = \'; expires=\' + date.toGMTString();\r\n				document.cookie = \'smd_style=\' + cstyle.join(\'^\') + expires + \'; path=\'+self.path;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Adapted from http://phpjs.org/functions/strtr:4965ce2e-29d4-48da-91f9-07ca86a786ee\r\n	this.smd_strtr = function smd_strtr(str, from, to) {\r\n		var idx = 0, lgth = 0;\r\n		if (typeof from === \'object\') {\r\n			for (fr in from) {\r\n				re = new RegExp(fr, \'g\');\r\n				str = str.replace(re, from[fr]);\r\n			}\r\n			return str;\r\n		}\r\n\r\n		lgth = to.length;\r\n		if (from.length < to.length) {\r\n			lgth = from.length;\r\n		}\r\n		for (idx = 0; idx < lgth; idx++) {\r\n			str = str.replace(from[idx], to[idx]);\r\n		}\r\n		return str;\r\n	}\r\n\r\n	this.autosub = function autosub(id) {\r\n		self.smd_switchStylesheet(self.jqsel, jQuery(this(id)).getAttribute(self.linkloc), type, self.classlist, self.destination);\r\n	}\r\n	// Constructor from here\r\n	this.construct = function construct() {\r\n		if (type == \"0\") {\r\n			// Generate the list of stylesheets\r\n			jQuery(\'link[rel*=\'+self.stylematch+\'][title]\').each(function(id) {\r\n				if (this.disabled == true) {\r\n					act_class = \'\';\r\n				} else {\r\n					act_class = \" \"+self.activeclass;\r\n				}\r\n				var reps = new Array();\r\n				reps = {\r\n					\"{smd_style_title}\" : this.getAttribute(\'title\'),\r\n					\"{smd_style_url}\" : this.getAttribute(\'href\'),\r\n					\"{smd_style_linkclass}\" : self.linkclass,\r\n					\"{smd_style_activeclass}\" : act_class\r\n				};\r\n\r\n				self.addSheet(self.smd_strtr(self.thing, reps));\r\n			});\r\n		}\r\n\r\n		self.sorter();\r\n\r\n		var selector = (self.htmlid==\"\") ? \".\"+self.classname : \"#\"+self.htmlid;\r\n		{$wrapadd}\r\n		if (self.clean==1) {\r\n			jQuery(selector).empty();\r\n		}\r\n		jQuery(selector).append(\r\n			((self.brkistag==0)\r\n				? self.stylelist.join(self.brk)\r\n				: \"<\"+self.brk+\">\"+self.stylelist.join(\"</\"+self.brk+\"><\"+self.brk+\">\")+\"</\"+self.brk+\">\"\r\n			)\r\n		);\r\n\r\n		jQuery(\'.\'+self.linkclass).click(function() {\r\n			jQuery(\'.\'+self.linkclass).each(function() {\r\n				jQuery(this).removeClass(self.activeclass);\r\n			});\r\n			self.smd_switchStylesheet(self.jqsel, this.getAttribute(self.linkloc), type, self.classlist, self.destination);\r\n			jQuery(this).addClass(self.activeclass);\r\n			return false;\r\n		});\r\n		if (this.expiry > 0) {\r\n			var currStyle = getCookie(\'smd_style\');\r\n			if (currStyle) {\r\n				currStyle = currStyle.split(\'^\');\r\n				for (var idx = 0; idx < currStyle.length; idx++) {\r\n					cstyle = currStyle[idx].split(\':\');\r\n					self.smd_switchStylesheet(cstyle[0], cstyle[1], type, self.getClasses(), self.destination);\r\n				}\r\n			}\r\n		}\r\n	}\r\n}\r\n-->\r\n</script>\r\nEOJS;\r\n		$smd_style_loader++;\r\n	}\r\n	return join(\"\\n\", $out);\r\n}', '/**\r\n * smd_style\r\n *\r\n * A Textpattern CMS plugin for switching stylesheets on websites\r\n *  -> Use the browser\'s switcher or the plugin\'s built-in jQuery switcher\r\n *  -> Cookie stores current style in use so it applies across pages\r\n *  -> Compatible with Txp sheets, file sheets, and rvm_css\r\n *  -> Ability to put Txp tags in your stylesheet (if using Txp\'s sheets)\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// Shamelessly plagiarised from ako_cssParse (thanks!)\r\nif (@txpinterface===\"css\" && gps(\'smd_styleparse\') == 1) {\r\n	global $prefs;\r\n	$sheet = (gps(\'n\'));\r\n	if ($sheet) {\r\n		$css = safe_field(\'css\',\'txp_css\',\"name=\'\".doSlash($sheet).\"\'\");\r\n		if ($css) {\r\n			$css = parse($css);\r\n			$css = ($prefs[\'allow_page_php_scripting\']) ? evalString($css) : $css;\r\n			header(\'Content-type: text/css\');\r\n			echo($css);\r\n			exit();\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_style($atts, $thing) {\r\n	global $pretext, $thisarticle, $prefs, $variable;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'sheets\' => \'\',\r\n		\'form\' => \'\',\r\n		\'use_default\' => \'0\', // 0=no; 1=yes as primary sheet\r\n		\'promote_recent\' => \'1\', // 0=retain order; 1=promote used sheets but honour core sheets; 2=promote at all costs\r\n		\'skip\' => \'auto\',\r\n		\'skip_title\' => \'auto\',\r\n		\'show_empty\' => \'0\',\r\n		\'real_sheets\' => \'0\', // 0=no; 1=yes\r\n		\'sheets_dir\' => @$prefs[\'rvm_css_dir\'],\r\n		\'parse_tags\' => \'0\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	$out = array();\r\n	$styles = do_list($sheets, $delim);\r\n	$expstyles = array();\r\n	foreach ($styles as $idx => $opt) {\r\n		if (strpos($opt, \"?\") === 0) {\r\n			$opt = substr(strtolower($opt), 1);\r\n			$tmpa = array();\r\n			if (isset($pretext[$opt]) && $pretext[$opt] != \"\") {\r\n				$tmpa = do_list($pretext[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($thisarticle[$opt]) && $thisarticle[$opt] != \"\") {\r\n				$tmpa = do_list($thisarticle[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($variable[$opt]) && $variable[$opt] != \"\") {\r\n				$tmpa = do_list($variable[$opt], $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if (isset($_GET[$opt]) && $_GET[$opt] != \"\") {\r\n				$tmpa = do_list(gps($opt), $delim);\r\n				$expstyles = array_merge($expstyles, $tmpa);\r\n			} else if ($show_empty) {\r\n				$expstyles[] = $opt;\r\n			}\r\n		} else {\r\n			$expstyles[] = $opt;\r\n		}\r\n	}\r\n	$styles = $expstyles;\r\n\r\n	$skip = ($skip==\"auto\") ? $use_default : $skip;\r\n	$skip_title = ($skip_title==\"auto\") ? $use_default : $skip_title;\r\n	$skip_title = ($skip==0) ? 0 : $skip_title;\r\n	if ($use_default > 0) {\r\n		$default = safe_row(\'css\', \'txp_section\', \"name = \'\".doSlash($pretext[\'s\']).\"\'\", $debug);\r\n		array_unshift($styles, $default[\'css\']);\r\n	}\r\n\r\n	// If a previous style has been chosen, move it to the front of the list (if requested).\r\n	// This forces good browsers to \'flash\' less when changing pages\r\n	$cookiestyle = cs(\"smd_style\");\r\n	$cookiestyle = do_list($cookiestyle);\r\n	$cstyles = array();\r\n	foreach ($cookiestyle as $cstyle) {\r\n		$cparts = explode(\":\", $cstyle);\r\n		if (count($cparts) >= 2) {\r\n			$cstyles[$cparts[0]] = $cparts[1];\r\n      } else {\r\n			$cstyles[\"all\"] = $cparts[0];\r\n		}\r\n	}\r\n\r\n	if ($promote_recent && $cstyles) {\r\n		$rejig = array();\r\n		foreach ($styles as $idx => $style) {\r\n			if (($idx < $skip) && $promote_recent == 1) {\r\n				$rejig[] = $style;\r\n				continue;\r\n			}\r\n			if (in_array($style, $cstyles) !== false) {\r\n				$rejig[] = $style;\r\n				break;\r\n			}\r\n		}\r\n		$styles = array_merge($rejig, $styles);\r\n		$styles = array_unique($styles);\r\n	}\r\n\r\n	if ($debug) {\r\n		echo \"++ STYLES ++\";\r\n		dmp($styles);\r\n	}\r\n	$stylecnt = 1;\r\n	$num_styles = count($styles);\r\n	foreach ($styles as $style) {\r\n		$styleopts = do_list($style, $paramdelim);\r\n		$name = (count($styleopts) >= 1) ? $styleopts[0] : \'\';\r\n		$title = ($stylecnt > $skip || $stylecnt > $skip_title) ? ((count($styleopts) >= 2) ? $styleopts[1] : $name) : \'\';\r\n		$media = (count($styleopts) >= 3) ? $styleopts[2] : \'screen\';\r\n		$rel = (($stylecnt > $skip) ? \"alternate \" : \'\') . \"stylesheet\";\r\n		$url = hu. (($real_sheets && $sheets_dir) ? $sheets_dir . \'/\' .strtolower(sanitizeForUrl($name)). \'.css\' : \'textpattern/css.php?n=\'.$name . (($parse_tags) ? a.\'smd_styleparse=1\' : \'\'));\r\n\r\n		// Make up the replacement array\r\n		$replacements = array(\r\n				\'{smd_style_name}\' => $name,\r\n				\'{smd_style_url}\' => $url,\r\n				\'{smd_style_media}\' => $media,\r\n				\'{smd_style_rel}\' => $rel,\r\n				\'{smd_style_title}\' => $title,\r\n				\'{smd_style_counter}\' => $stylecnt,\r\n				\'{smd_style_total}\' => $num_styles,\r\n		);\r\n\r\n		if ($debug) {\r\n			echo \"++ REPLACEMENTS ++\";\r\n			dmp($replacements);\r\n		}\r\n\r\n		$out[] = ($thing) ? parse(strtr($thing, $replacements)) : (\r\n					($form) ? strtr(parse_form($form), $replacements) : (\r\n					($real_sheets || $parse_tags) ? \'<link rel=\"\'.$rel.\'\"\'. (($title) ? \' title=\"\'.$title.\'\"\' : \'\') .\' href=\"\'.$url.\'\" media=\"\'.$media.\'\" type=\"text/css\" />\' :\r\n					css(array(\r\n						\"format\" => \"link\",\r\n						\"media\" => $media,\r\n						\"name\" => $name,\r\n						\"rel\" => $rel,\r\n						\"title\" => $title,\r\n						))\r\n					));\r\n\r\n		$stylecnt++;\r\n	}\r\n\r\n	return join(\"\\n\", $out);\r\n}\r\n\r\n// ------------------------\r\n// Javascript style switcher\r\nfunction smd_styleswitch($atts, $thing) {\r\n	global $smd_style_loader;\r\n\r\n	// Plugin options\r\n	extract(lAtts(array(\r\n		\'wraptag\' => \'ul\',\r\n		\'wrapadd\' => \'\', // Perhaps this should be automatic only\r\n		\'class\' => \'smd_switcher\',\r\n		\'html_id\' => \'\',\r\n		\'break\' => \'li\',\r\n		\'break_is_tag\' => \'1\',\r\n		\'selectlabel\' => \'\',\r\n		\'linkclass\' => \'smd_styleswitch\',\r\n		\'activeclass\' => \'smd_currstyle\',\r\n		\'alt_only\' => \'0\', // 0=No; 1=Yes, only show alternate stylesheets\r\n		\'form\' => \'\',\r\n		\'sort\' => \'1\', // 0=No; 1=Yes\r\n		\'case_sensitive\' => \'0\',\r\n		\'expiry\' => \'30\', // In days\r\n		\'byclass\' => \'\',\r\n		\'destination\' => \'body\',\r\n		\'linkloc\' => \'\',\r\n		\'clean\' => \'1\',\r\n		\'delim\' => \',\',\r\n		\'paramdelim\' => \':\',\r\n		\'debug\' => \'0\',\r\n	), $atts));\r\n\r\n	// Set default form info if none specified\r\n	$fixlist = ($byclass) ? 1 : 0;\r\n	$linkloc = ($linkloc) ? $linkloc : (($fixlist) ? \'name\' : \'rel\');\r\n	$thing = ($form) ? fetch_form($form) : $thing ;\r\n	$thing = (empty($thing))\r\n		? (($fixlist)\r\n			? (($wraptag==\'select\') ? \'<option value=\"{smd_style_name}\" class=\"{smd_style_linkclass}\">{smd_style_title}</option>\' : \'<a name=\"{smd_style_name}\" href=\"#\" class=\"{smd_style_linkclass} {smd_style_activeclass}\">{smd_style_title}</a>\')\r\n			: \'<a rel=\"{smd_style_title}\" href=\"{smd_style_url}\" class=\"{smd_style_linkclass} {smd_style_activeclass}\">{smd_style_title}</a>\'\r\n		)\r\n		: $thing;\r\n	$thing = str_replace(\"\\r\\n\", \"\", $thing);\r\n	if ($wraptag == \'select\') {\r\n		$break = (strpos($thing, \'</option>\')) ? \'\' : \'option\';\r\n		$break_is_tag = 1;\r\n	}\r\n\r\n	// Set up the js if using a fixed list of classes\r\n	$byclass = do_list($byclass, $delim);\r\n	$stylecnt = 1;\r\n	$num_styles = count($byclass);\r\n	$classlist = $oplist = array();\r\n\r\n	if ($wraptag==\'select\' && empty($wrapadd)) {\r\n		$wrapadd = <<<EOJS\r\njQuery(selector).change(function() {\r\n	self.smd_switchStylesheet(self.jqsel, jQuery(selector+\" option:selected\").val(), type, self.classlist, self.destination);\r\n});\r\nEOJS;\r\n	}\r\n\r\n	// Make sure there are always jQuery elements to swing off, thus avoiding js errors\r\n	$class = (trim($class)==\"\") ? \"smd_switcher\" : $class;\r\n	$html_id = trim($html_id);\r\n	$linkclass = (trim($linkclass)==\"\") ? \'smd_styleswitch\' : $linkclass;\r\n	$jqSel = ($html_id) ? \'#\'.$html_id : \'.\'.$linkclass;\r\n	$stylematch = (($alt_only) ? \'alternate \' : \'\').\'style\';\r\n	$break_is_tag = ($break==\"\") ? 0 : $break_is_tag;\r\n\r\n	$cookiestyle = cs(\"smd_style\");\r\n	$cookiestyle = do_list($cookiestyle);\r\n	$cstyles = array();\r\n	foreach ($cookiestyle as $cstyle) {\r\n		$cparts = explode(\":\", $cstyle);\r\n		if (count($cparts) >= 2) {\r\n			$cstyles[$cparts[0]] = $cparts[1];\r\n      } else {\r\n			$cstyles[\"all\"] = $cparts[0];\r\n		}\r\n	}\r\n\r\n	foreach ($byclass as $cls) {\r\n		if ($cls==\"\") continue;\r\n		$clsopts = do_list($cls, $paramdelim);\r\n		$name = (count($clsopts) >= 1) ? sanitizeForUrl($clsopts[0]) : \'\';\r\n		$title = (count($clsopts) >= 2) ? $clsopts[1] : $name;\r\n		$media = (count($clsopts) >= 3) ? $clsopts[2] : \'screen\';\r\n\r\n		if (isset($cstyles[$jqSel]) && $cstyles[$jqSel] == $name) {\r\n			$actCls = $activeclass;\r\n		} else {\r\n			$actCls = \'\';\r\n		}\r\n		// Make up the replacement array\r\n		$replacements = array(\r\n				\'{smd_style_name}\' => $name,\r\n				\'{smd_style_title}\' => addslashes($title),\r\n				\'{smd_style_linkclass}\' => $linkclass,\r\n				\'{smd_style_activeclass}\' => $actCls,\r\n				\'{smd_style_autosub}\' => \'\', // TODO: set up a call to smd_atyleswitch() that can be called from onchange/onclick/etc\r\n				\'{smd_style_counter}\' => $stylecnt,\r\n				\'{smd_style_total}\' => $num_styles,\r\n		);\r\n		$stylecnt++;\r\n		$classlist[] = \'smd_style_cls.addCls(\"\'.$name.\'\");\';\r\n		$oplist[] = \"smd_style_cls.addSheet(\'\".parse(strtr($thing, $replacements)).\"\');\";\r\n	}\r\n	$classlist = join(n, $classlist);\r\n	$oplist = join(n, $oplist);\r\n\r\n	$pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\');\r\n\r\n	$out = array();\r\n	if ($wraptag) {\r\n		$out[] = \'<\'.$wraptag.(($html_id) ? \' id=\"\'.$html_id.\'\"\' : \'\').\' class=\"\'.$class.\'\"></\'.$wraptag.\'>\';\r\n	}\r\n\r\n	$out[] = <<<EOJS\r\n<script type=\"text/javascript\">\r\n<!--\r\njQuery(function() {\r\nsmd_style_cls = new smd_ClassHandler(\"{$fixlist}\");\r\n{$classlist}\r\n{$oplist}\r\nsmd_style_cls.setBreak(\'{$break}\');\r\nsmd_style_cls.setBreakIsTag(\'{$break_is_tag}\');\r\nsmd_style_cls.setHtmlID(\'{$html_id}\');\r\nsmd_style_cls.setClassName(\'{$class}\');\r\nsmd_style_cls.setJQSel(\'{$jqSel}\');\r\nsmd_style_cls.setDestination(\'{$destination}\');\r\nsmd_style_cls.setLinkClass(\'{$linkclass}\');\r\nsmd_style_cls.setLinkLoc(\'{$linkloc}\');\r\nsmd_style_cls.setClean(\'{$clean}\');\r\nsmd_style_cls.setPath(\'{$pub_path}\');\r\nsmd_style_cls.setActiveClass(\'{$activeclass}\');\r\nsmd_style_cls.setSort(\'{$sort}\');\r\nsmd_style_cls.setCaseSensitive(\'{$case_sensitive}\');\r\nsmd_style_cls.setExpiry(\'{$expiry}\');\r\nsmd_style_cls.setStyleMatch(\'{$stylematch}\');\r\nsmd_style_cls.setThing(\'{$thing}\');\r\nsmd_style_cls.construct();\r\n});\r\n-->\r\n</script>\r\nEOJS;\r\n\r\n	if ($smd_style_loader == 0) {\r\n		$out[] = <<<EOJS\r\n<script type=\"text/javascript\" src=\"/textpattern/textpattern.js\"></script>\r\n<script type=\"text/javascript\">\r\n<!--\r\nvar smd_style_cls = \'\';\r\nfunction smd_ClassHandler(type) {\r\n	// Variables\r\n	this.classlist = [];\r\n	this.stylelist = [];\r\n	this.act_class = \'\';\r\n	this.brk = \'\';\r\n	this.brkistag = \'\';\r\n	this.htmlid = \'\';\r\n	this.classname = \'\';\r\n	this.jqsel = \'\';\r\n	this.destination = \'\';\r\n	this.linkclass = \'\';\r\n	this.linkloc = \'\';\r\n	this.clean = \'\';\r\n	this.path = \'.\';\r\n	this.activeclass = \'\';\r\n	this.wraptag = \'\';\r\n	this.sortit = \'\';\r\n	this.case_sensitive = \'\';\r\n	this.expiry = \'\';\r\n	this.stylematch = \'\';\r\n	this.thing = \'\';\r\n	var self = this;\r\n\r\n	// Methods\r\n	this.addCls = function addCls(val) { this.classlist.push(val); }\r\n	this.addSheet = function addSheet(val) { this.stylelist.push(val); }\r\n	this.setBreak = function setBreak(val) { this.brk = val; }\r\n	this.setBreakIsTag = function setBreakIsTag(val) { this.brkistag = val; }\r\n	this.setHtmlID = function setHtmlID(val) { this.htmlid = val; }\r\n	this.setClassName = function setClassName(val) { this.classname = val; }\r\n	this.setJQSel = function setJQSel(val) { this.jqsel = val; }\r\n	this.setLinkClass = function setLinkClass(val) { this.linkclass = val; }\r\n	this.setLinkLoc = function setLinkLoc(val) { this.linkloc = val; }\r\n	this.setClean = function setClean(val) { this.clean = val; }\r\n	this.setPath = function setPath(val) { this.path = val; }\r\n	this.setActiveClass = function setActiveClass(val) { this.activeclass = val; }\r\n	this.setDestination = function setDestination(val) { this.destination = val; }\r\n	this.setSort = function setSort(val) { this.sortit = val; }\r\n	this.setCaseSensitive = function setCaseSensitive(val) { this.case_sensitive = val; }\r\n	this.setExpiry = function setExpiry(val) { this.expiry = val; }\r\n	this.setStyleMatch = function setStyleMatch(val) { this.stylematch = val; }\r\n	this.setThing = function setThing(val) { this.thing = val; }\r\n	this.getClasses = function getClasses() { return this.classlist; }\r\n\r\n	this.sorter = function sorter() {\r\n		if (this.sortit==1) {\r\n			this.stylelist = (this.case_sensitive==0)\r\n				? this.stylelist.sort(function(a,b) {\r\n					var x = a.toLowerCase();\r\n					var y = b.toLowerCase();\r\n					return ((x < y) ? -1 : ((x > y) ? 1 : 0));\r\n					})\r\n				: this.stylelist.sort();\r\n		}\r\n	}\r\n	// Switch to the given stylesheet\r\n	this.smd_switchStylesheet = function smd_switchStylesheet(lcls, styleref, type, clslist, dest) {\r\n		if (lcls == self.jqsel) {\r\n			if (type == 1) {\r\n				jQuery.each(clslist, function(idx, cls) {\r\n					if (jQuery(dest).hasClass(cls)) {\r\n						jQuery(dest).removeClass(cls);\r\n					}\r\n				});\r\n				if (styleref != \"default\") {\r\n					jQuery(dest).addClass(styleref);\r\n				}\r\n			} else {\r\n				jQuery(\'link[rel*=style][title]\').each(function(id) {\r\n					this.disabled = true;\r\n					if (this.getAttribute(\'title\') == styleref) this.disabled = false;\r\n				});\r\n			}\r\n			if (this.expiry > 0) {\r\n				var cstyle = getCookie(\'smd_style\');\r\n				if (cstyle == null) {\r\n					cstyle = \'\';\r\n				}\r\n				cstyle = cstyle.split(\'^\');\r\n				var newstyle = self.jqsel+\':\'+styleref;\r\n				var found = false;\r\n\r\n				for (var idx = 0; idx < cstyle.length; idx++) {\r\n					if (cstyle[idx] == \'\') continue;\r\n					var thispair = cstyle[idx].split(\':\');\r\n					if (thispair[0] == self.jqsel) {\r\n						cstyle[idx] = newstyle;\r\n						found=true;\r\n						break;\r\n					}\r\n				}\r\n				if (found == false) {\r\n					cstyle.push(newstyle);\r\n				}\r\n\r\n				var date = new Date();\r\n				date.setTime(date.getTime() + (self.expiry*24*60*60*1000));\r\n				var expires = \'; expires=\' + date.toGMTString();\r\n				document.cookie = \'smd_style=\' + cstyle.join(\'^\') + expires + \'; path=\'+self.path;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Adapted from http://phpjs.org/functions/strtr:4965ce2e-29d4-48da-91f9-07ca86a786ee\r\n	this.smd_strtr = function smd_strtr(str, from, to) {\r\n		var idx = 0, lgth = 0;\r\n		if (typeof from === \'object\') {\r\n			for (fr in from) {\r\n				re = new RegExp(fr, \'g\');\r\n				str = str.replace(re, from[fr]);\r\n			}\r\n			return str;\r\n		}\r\n\r\n		lgth = to.length;\r\n		if (from.length < to.length) {\r\n			lgth = from.length;\r\n		}\r\n		for (idx = 0; idx < lgth; idx++) {\r\n			str = str.replace(from[idx], to[idx]);\r\n		}\r\n		return str;\r\n	}\r\n\r\n	this.autosub = function autosub(id) {\r\n		self.smd_switchStylesheet(self.jqsel, jQuery(this(id)).getAttribute(self.linkloc), type, self.classlist, self.destination);\r\n	}\r\n	// Constructor from here\r\n	this.construct = function construct() {\r\n		if (type == \"0\") {\r\n			// Generate the list of stylesheets\r\n			jQuery(\'link[rel*=\'+self.stylematch+\'][title]\').each(function(id) {\r\n				if (this.disabled == true) {\r\n					act_class = \'\';\r\n				} else {\r\n					act_class = \" \"+self.activeclass;\r\n				}\r\n				var reps = new Array();\r\n				reps = {\r\n					\"{smd_style_title}\" : this.getAttribute(\'title\'),\r\n					\"{smd_style_url}\" : this.getAttribute(\'href\'),\r\n					\"{smd_style_linkclass}\" : self.linkclass,\r\n					\"{smd_style_activeclass}\" : act_class\r\n				};\r\n\r\n				self.addSheet(self.smd_strtr(self.thing, reps));\r\n			});\r\n		}\r\n\r\n		self.sorter();\r\n\r\n		var selector = (self.htmlid==\"\") ? \".\"+self.classname : \"#\"+self.htmlid;\r\n		{$wrapadd}\r\n		if (self.clean==1) {\r\n			jQuery(selector).empty();\r\n		}\r\n		jQuery(selector).append(\r\n			((self.brkistag==0)\r\n				? self.stylelist.join(self.brk)\r\n				: \"<\"+self.brk+\">\"+self.stylelist.join(\"</\"+self.brk+\"><\"+self.brk+\">\")+\"</\"+self.brk+\">\"\r\n			)\r\n		);\r\n\r\n		jQuery(\'.\'+self.linkclass).click(function() {\r\n			jQuery(\'.\'+self.linkclass).each(function() {\r\n				jQuery(this).removeClass(self.activeclass);\r\n			});\r\n			self.smd_switchStylesheet(self.jqsel, this.getAttribute(self.linkloc), type, self.classlist, self.destination);\r\n			jQuery(this).addClass(self.activeclass);\r\n			return false;\r\n		});\r\n		if (this.expiry > 0) {\r\n			var currStyle = getCookie(\'smd_style\');\r\n			if (currStyle) {\r\n				currStyle = currStyle.split(\'^\');\r\n				for (var idx = 0; idx < currStyle.length; idx++) {\r\n					cstyle = currStyle[idx].split(\':\');\r\n					self.smd_switchStylesheet(cstyle[0], cstyle[1], type, self.getClasses(), self.destination);\r\n				}\r\n			}\r\n		}\r\n	}\r\n}\r\n-->\r\n</script>\r\nEOJS;\r\n		$smd_style_loader++;\r\n	}\r\n	return join(\"\\n\", $out);\r\n}', 'cbe9762024df1b3a22b7b05fa5d7c287', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_tabber', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.11', 'Add and manage TXP tabs', '<div id=\"smd_help\">\n\n	<h1 id=\"smd_tabber_top\">smd_tabber</h1>\n\n	<p>Create and manage your own <span class=\"caps\">TXP</span> tabs/sub-tabs, populating them with any content you wish for your users. Content and <span class=\"caps\">CSS</span> are controlled by regular Textpattern Pages/Stylesheets. Acts like a multi-user, multi-tab dashboard for your admin-side users.</p>\n\n	<h2 id=\"smd_tabber_feat\">Features:</h2>\n\n	<ul>\n		<li>Define new primary or secondary tabs in your menu hierarchy</li>\n		<li>Assign a Page and a Style to menu items</li>\n		<li>Two convenience tags allow you to add Edit links in your markup for quick access to edit the tab&#8217;s content</li>\n	</ul>\n\n	<h2>Author / credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Spawned from an idea by maverick, with thanks. Kudos to the feedback from the beta test team &#8212; maruchan and maverick &#8212; who put up with endless new versions a day until I got it right.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires <span class=\"caps\">TXP</span> 4.4.0+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1216/smd_tabber\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> <em>Admin -&gt; Plugins</em> pane, install and enable the plugin. To uninstall, delete from the <em>Admin -&gt; Plugins</em> page. The table containing the extra tab definitions will be removed but your tab Pages and Stylesheets will remain.</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35882\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<h2 id=\"smd_tabber_usage\">Usage</h2>\n\n	<p>Visit <em>Admin-&gt;Manage tabs</em>. When there are some custom tabs defined, the dropdown beneath the heading contains a list of all your tabs, grouped by their area. Choose one of the tabs to load it into the boxes below for editing. The boxes are:</p>\n\n	<ul>\n		<li><span class=\"atnm\">Tab name</span> : name of your tab as it appears to your users (case sensitive)</li>\n		<li><span class=\"atnm\">Sort order</span> : an optional value you can assign to this tab that dictates the position it will occupy in your tab sequence. Tabs are sorted by this value before being slotted into the menu</li>\n		<li><span class=\"atnm\">Assign to area</span> : select which primary-level tab (a.k.a. the &#8220;top row&#8221; in the Classic theme) your new tab will appear under. If you want to create a new one, type it in the adjacent box (case sensitive)</li>\n		<li><span class=\"atnm\">View privileges</span> : select the user privilege levels that are allowed to see this tab. Default: current level of logged-in user. Note that &#8216;none&#8217; is an option. This allows you to remove a menu from operation and &#8216;park&#8217; it while it is being created/edited, without fear of anyone being able to see what you are doing. When you are ready to put it into production, simply reassign it to a regular user level</li>\n		<li><span class=\"atnm\">Page template</span> : if you have defined at least one <span class=\"caps\">TXP</span> Page (in <em>Presentation-&gt;Pages</em>) with the designated prefix (<code>tabber_</code> by default) then you will see those pages listed here. Choose one to assign it to this tab. Click the adjacent <em>Create</em> or <em>Edit</em> link as a shortcut to <em>Presentation-&gt;Pages</em></li>\n		<li><span class=\"atnm\">Stylesheet</span> : if you have defined at least one <span class=\"caps\">TXP</span> Stylesheet (in <em>Presentation-&gt;Style</em>) with the designated prefix (<code>tabber_</code> by default) then you will see those sheets listed here. Choose one to assign it to this tab. Click the adjacent <em>Create</em> or <em>Edit</em> link as a shortcut to <em>Presentation-&gt;Style</em></li>\n	</ul>\n\n	<h2 id=\"smd_tabber_notes\">Interface notes</h2>\n\n	<h3>General</h3>\n\n	<ul>\n		<li>You need to reload the admin side after saving to see any changes</li>\n		<li>Area and Tab names are case sensitive: name them as you want your users to see the tabs</li>\n		<li>Publisher level accounts also have a <a href=\"#smd_tabber_prefs\">[Preferences]</a> link below the header</li>\n		<li>Using smd_faux_role is a good way to quickly switch user level so you can see the tab structures you have created</li>\n	</ul>\n\n	<h3>Tabs</h3>\n\n	<ul>\n		<li>Alongside the tab selector of any tab you are editing is a <em>View</em> link. Click to jump straight to the selected tab</li>\n		<li>Alongside the <em>Tab name</em> box of any tab you are editing is an [x] link. Click to delete the tab</li>\n		<li>The <em>Sort order</em> allows you to arbitrarily order the tabs without having to rely on inventive tab naming strategies. If all the sort boxes are left empty, the order is determined by the alphabetic order of the tab names</li>\n		<li>Tab names must be unique &#8212; even if they occupy different areas</li>\n	</ul>\n\n	<h3>Areas</h3>\n\n	<ul>\n		<li>Areas will appear to the right of the <em>Extensions</em> tab, in alphabetical order; they (currently) cannot be positioned. If you want something to stand out, attach it to the &#8216;start&#8217; tab</li>\n		<li>Areas <span class=\"caps\">MUST</span> have at least one sub-tab assigned to them to become visible</li>\n	</ul>\n\n	<h3>Pages and Styles</h3>\n\n	<ul>\n		<li>Pages and Styles must begin with your chosen prefix (Default: <code>tabber_</code>) to be selectable by the plugin. Change the prefix via the plugin&#8217;s <a href=\"#smd_tabber_prefs\">Preferences</a></li>\n		<li>If you view the content of a tab that has no Page assigned, a default page will be used which contains &#8216;Edit Page&#8217; and &#8216;Edit Style&#8217; links</li>\n		<li>Pages and Styles can contain <span class=\"caps\">TXP</span> tags and are parsed as if on the public side. But you don&#8217;t need a <span class=\"caps\">DTD</span>/head/body/footer element here because they are supplied by the admin interface: you are just filling in the content between the menu and the footer</li>\n		<li>If you are using tags that automatically detect their context, you will probably have to manually specify the context when using those tags in admin-side pages</li>\n	</ul>\n\n	<h2 id=\"smd_tabber_prefs\">Preferences</h2>\n\n	<p><strong>Permit tabs to be managed by</strong></p>\n\n	<p>By default the tab manager is only available to any user with a Publisher account. Sometimes you may want to strictly control who can or cannot add/edit tabs. If you wish to do this, select user accounts from the <em>Permit tabs to be managed by</em> list and hit Save. From that point on, only those explicit user accounts will be allowed access to the tab manager. <span class=\"important\">Be careful not to lock yourself out</span> :-)</p>\n\n	<p>Note that no restrictions are placed on who can edit the tab Pages and Styles: they are subject to <span class=\"caps\">TXP</span>&#8217;s usual permissions structure so if you want to restrict access to these elements, choose suitable accounts for your users and tie them to the plugin&#8217;s prefs.</p>\n\n	<p><strong>Page/Style prefix</strong></p>\n\n	<p>In order to be assignable to a tab, your custom pages / stylesheets must begin with a defined prefix. Set the prefix here. Default: <code>tabber_</code>.</p>\n\n	<p><strong>Parse depth</strong></p>\n\n	<p>Most of the time, <span class=\"caps\">TXP</span>&#8217;s parser takes care of nested tags nicely but in some rare instances you may have, say, <code>{replacement}</code> strings inside tags inside tags and the parser might not be replacing everything. In these cases you can increase the parse depth of smd_tabber so it can dive deeper into the nested tag tree. The default is one pass, but if you wish to increase it, do so using this value.</p>\n\n	<h2 id=\"smd_tabber_tags\">Public tags</h2>\n\n	<h3><code>&lt;txp:smd_tabber_edit_page /&gt;</code> and <code>&lt;txp:smd_tabber_edit_style /&gt;</code></h3>\n\n	<p>Renders a link to the <em>Presentation-&gt;Pages</em> or <em>Presentation-&gt;Style</em> tabs, respectively, with the current page/stylesheet loaded ready for editing. If the resource doesn&#8217;t yet exist the link will take you to an empty document. You must begin the name of your page/stylesheet with your chosen prefix (see <a href=\"#smd_tabber_prefs\">prefs</a>) for it to be picked up by the plugin.</p>\n\n	<p>Optional arguments:</p>\n\n	<ul>\n		<li><span class=\"atnm\">name</span>: override the default name. If you want to force the link to edit a particular Page/Stylesheet, specify it here, either with or without the prefix</li>\n		<li><span class=\"atnm\">title</span> : the title of the link. Default: <code>Edit page</code> / <code>Edit CSS</code>, respectively</li>\n		<li><span class=\"atnm\">wraptag</span> : the (X)HTML element (without angle brackets) to surround the link, e.g. <code>wraptag=&quot;span&quot;</code>. Without this attribute set, <code>class</code> and <code>html_id</code> will do nothing. Default: unset.</li>\n		<li><span class=\"atnm\">class</span> : the <span class=\"caps\">CSS</span> classname to apply to the wraptag. Default: unset</li>\n		<li><span class=\"atnm\">html_id</span> : the <span class=\"caps\">HTML</span> id attribute to apply to the wraptag. Default: unset</li>\n	</ul>\n\n	<h2 id=\"smd_tabber_dashboards\">Dashboards and compatibility</h2>\n\n	<p>There is nothing to stop you using other dashboard or menu management plugins with smd_tabber. But the open architecture of smd_tabber means that, with a bit of planning, you can probably replicate most dashboard/menu functionality and perhaps do even more with it. Here are some common things that other plugins provide and how smd_tabber can be used to deliver similar functionality:</p>\n\n	<ul>\n		<li>lum_user_menu\n	<ul>\n		<li>although not as point n&#8217; click, you could create a Page with a grid of shortcuts on it. You could define an smd_macro called &#8216;lum_cell&#8217; that could take parameters to specify the icon, destination <span class=\"caps\">URL</span>, etc. Calling that multiple times in your tab&#8217;s Page would render the menu grid. You could even wrap some cells in rvm_privileged tags to control who could see which icons</li>\n	</ul></li>\n		<li>jmd_dashboard / sed_dashboard / aro_myAdmin\n	<ul>\n		<li>you can add your own tabs under the start tab</li>\n		<li>you can recreate the edit link functionality via an smd_macro, rsx_frontend_edit, chh_admin_tags, rss_article_edit, or even a <span class=\"caps\">TXP</span> form with txp:yield</li>\n		<li>employ upm_textile for fine-grained Textile processing inside your Pages (it&#8217;s more robust than having to escape tags-in-tags with <code>notextile.</code> or <code>==</code>). Alternatively, write your content in Articles and import them into your Pages with txp:article_custom</li>\n	</ul></li>\n		<li>esq_admin_splash\n	<ul>\n		<li>again, smd_tabber can be told to reside on its own tab and you can write your help text in a tab Page with optional upm_textile support</li>\n	</ul></li>\n	</ul>\n\n	<p>If none of the above appeals, you can of course mix smd_tabber with other tab-making plugins but please be aware that each plugin does things in their own way and methods of manipulating menus vary greatly. Therefore, if you experience odd menu bar behaviour you may consider switching off various combinations of such plugins to track down what&#8217;s going on.</p>\n\n	<h2 id=\"smd_tabber_changelog\">Changelog</h2>\n\n	<p>31 Mar 11 | 0.10 | Initial public release<br />\n31 Mar 11 | 0.11 | Imported <code>$pretext</code> to prevent warnings when using article tags (thanks redbot) ; improved documentation clarity</p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 17px Georgia, sans-serif; }\n#smd_help h3 { font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (!defined(\'SMD_TABBER\')) {\n	define(\"SMD_TABBER\", \'smd_tabber\');\n}\nif(@txpinterface == \'admin\') {\n	global $textarray, $smd_tabber_event, $smd_tabber_styles, $txp_user, $smd_tabber_callstack, $smd_tabber_uprivs, $smd_tabber_prevs;\n\n	$smd_tabber_event = \'smd_tabber\';\n	$smd_tabber_prevs = \'1\';\n	$smd_tabber_styles = array(\n		\'edit\' => \'#smd_tabber_wrapper { margin:0 auto; width:520px; }\n.smd_tabber_equal { display:table; border-collapse:separate; margin:10px auto; border-spacing:8px; }\n.smd_tabber_row { display:table-row; }\n.smd_tabber_row div { display:table-cell; }\n.smd_tabber_row .smd_tabber_label { width:150px; text-align:right; padding:2px 0 0 0; }\n.smd_tabber_row .smd_tabber_value { width:350px; vertical-align:middle; }\n#smd_tabber_select { margin-bottom:-20px; }\n.smd_tabber_save { float:right; margin:10px 50px 0 0!important;}\',\n		\'prefs\' => \'.smd_label { text-align: right!important; vertical-align: top; }\n		\',\n	);\n	register_callback(\'smd_tabber_welcome\', \'plugin_lifecycle.\'.$smd_tabber_event);\n\n	$ulist = get_pref(\'smd_tabber_tab_privs\', \'\');\n	$allowed = ($ulist) ? explode(\',\', $ulist) : array();\n	$levs = ($allowed) ? \'1,2,3,4,5,6\' : \'1\';\n	if (empty($allowed) || in_array($txp_user, $allowed)) {\n		add_privs($smd_tabber_event, $levs);\n	}\n\n	// Grab this here so that the privs are known immediately after manual install\n	$smd_tabber_uprivs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n\n	register_tab(\'admin\', $smd_tabber_event, smd_tabber_gTxt(\'smd_tabber_tab_name\'));\n	register_callback(\'smd_tabber_dispatch\', $smd_tabber_event);\n\n	// Do the tabbing deed\n	if (smd_tabber_table_exist(1)) {\n		register_callback(\'smd_tabber_css_link\', \'admin_side\', \'head_end\');\n		$smd_tabs = safe_rows(\'*\', SMD_TABBER, \'1=1 ORDER BY area, sort_order, name\');\n\n		// Yuk but no other way to get these.\n		// NB: \'start\' missing on purpose as it has no privs by default, so needs them adding\n	 	$smd_areas = array(\'content\', \'presentation\', \'admin\', \'extensions\');\n\n		foreach ($smd_tabs as $idx => $tab) {\n			$name = $tab[\'name\'];\n			$area = $tab[\'area\'];\n			$areaname = strtolower(sanitizeForUrl($area));\n			$area_privname = \'tab.\' . $areaname;\n			$create_top = (!in_array($areaname, $smd_areas));\n			$tabname = strtolower(sanitizeForUrl($name));\n\n			$eprivs = explode(\',\', $tab[\'view_privs\']);\n			$rights = in_array($smd_tabber_uprivs, $eprivs);\n\n			if ($rights) {\n				if ($create_top) {\n					add_privs($area_privname, $smd_tabber_uprivs);\n					$smd_areas[] = $areaname;\n					if ($areaname != \'start\') {\n						$textarray[\'tab_\'.$areaname] = $area;\n					}\n				}\n\n				add_privs($tabname, $smd_tabber_uprivs);\n				register_tab($areaname, $tabname, $name);\n				register_callback(\'smd_tabber_render_tab\', $tabname);\n				$smd_tabber_callstack[$tabname] = array(\'name\' => $name, \'page\' => $tab[\'page\'], \'style\' => $tab[\'style\']);\n			}\n		}\n	}\n}\n\n// ------------------------\nfunction smd_tabber_render_tab($evt, $stp) {\n	global $smd_tabber_callstack, $pretext;\n\n	$tab_info = $smd_tabber_callstack[$evt];\n\n	// Allow multiple parse calls for any nested {replaced} content\n	$parse_depth = intval(get_pref(\'smd_tabber_parse_depth\', 1));\n\n	pagetop($tab_info[\'name\']);\n\n	$html = safe_field(\'user_html\', \'txp_page\', \"name=\'\".doSlash($tab_info[\'page\']).\"\'\");\n	if (!$html) {\n		$html = \'<txp:smd_tabber_edit_page />\'.n.\'<txp:smd_tabber_edit_style />\';\n	}\n\n	// Hand over control to the Page code\n	include_once txpath.\'/publish.php\';\n	for ($idx = 0; $idx < $parse_depth; $idx++) {\n		$html = parse($html);\n	}\n	echo $html;\n}\n\n// ------------------------\nfunction smd_tabber_dispatch($evt, $stp) {\n	if(!$stp or !in_array($stp, array(\n			\'smd_tabber_table_install\',\n			\'smd_tabber_table_remove\',\n			\'smd_tabber_css\',\n			\'smd_tabber_prefs\',\n			\'smd_tabber_prefsave\',\n			\'smd_tabber_save\',\n			\'smd_tabber_delete\',\n		))) {\n		smd_tabber(\'\');\n	} else $stp();\n}\n\n// ------------------------\nfunction smd_tabber_welcome($evt, $stp) {\n	$msg = \'\';\n	switch ($stp) {\n		case \'installed\':\n			smd_tabber_table_install(0);\n			$msg = \'Supertabs are go!\';\n			break;\n		case \'deleted\':\n			smd_tabber_table_remove(0);\n			break;\n	}\n	return $msg;\n}\n\n// ------------------------\nfunction smd_tabber_css() {\n	global $event;\n	$name = doSlash(gps(\'name\'));\n	$css = safe_field(\'css\', \'txp_css\', \"name=\'$name\'\");\n	unset($name);\n\n	if ($css) {\n		header(\'Content-type: text/css\');\n		echo $css;\n		exit();\n	}\n}\n\n// ------------------------\nfunction smd_tabber_css_link() {\n	global $event, $smd_tabber_event;\n	// Annoyingly, we need an extra test here in case the plugin has been deleted.\n	// This callback is registered before plugin deletion but by the time it runs the table is gone\n	if (smd_tabber_table_exist(1)) {\n		$smd_tab = safe_field(\'style\', SMD_TABBER, \"name = \'\".doSlash($event).\"\'\");\n		echo ($smd_tab) ? n.\'<link href=\"?event=\'.$smd_tabber_event.a.\'step=smd_tabber_css\'.a.\'name=\'.$smd_tab.\'\" rel=\"stylesheet\" type=\"text/css\" />\'.n : \'\';\n	}\n}\n\n// ------------------------\nfunction smd_tabber($msg=\'\') {\n	global $smd_tabber_event, $smd_tabber_uprivs, $smd_tabber_prevs, $smd_tabber_styles;\n\n	pagetop(smd_tabber_gTxt(\'smd_tabber_tab_name\'), $msg);\n	$pref_rights = in_array($smd_tabber_prevs, explode(\',\', $smd_tabber_uprivs));\n\n	if (smd_tabber_table_exist(1)) {\n		$tab_name = $tab_new_name = gps(\'smd_tabber_name\');\n		$area = $curr_page = $curr_style = $sort_order = \'\';\n		$view_privs = array();\n		$tablist = $smd_areas = array();\n		$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\', 1);\n\n		// Can\'t use the smd_tabs and smd_areas lists in the global scope \'cos they\'re stale / strtolower()ed\n		$smd_tabs = safe_rows(\'*\', SMD_TABBER, \'1=1 ORDER BY area, sort_order, name\');\n		if ($smd_tabs) {\n			foreach ($smd_tabs as $idx => $tab) {\n				$tablist[$tab[\'area\']][$tab[\'name\']] = $tab[\'name\'];\n				$smd_areas[$tab[\'area\']] = strtolower(sanitizeForUrl($tab[\'area\']));\n	\n				if ($tab[\'name\'] == $tab_name) {\n					$sort_order = $tab[\'sort_order\'];\n					$area = $tab[\'area\'];\n					$view_privs = explode(\',\', $tab[\'view_privs\']);\n					$curr_page = $tab[\'page\'];\n					$curr_style = $tab[\'style\'];\n				}\n			}\n		}\n\n		// Default to the current user level\'s privs for new items\n		if ($tab_name == \'\') {\n			$view_privs[] = $smd_tabber_uprivs;\n		}\n\n		// Build a select list of tab names, injecting optgroups above any areas\n		$optgroups = (count($tablist) > 1); // Only add optgroups if there are tabs in more than one area\n		$tabSelector = \'\';\n		if ($tablist) {\n			$tabSelector .= \'<select name=\"smd_tabber_name\" id=\"smd_tabber_name\" onchange=\"submit(this.form);\">\';\n			$tabSelector .= \'<option value=\"\">\' . smd_tabber_gTxt(\'smd_tabber_new_tab\') . \'</option>\';\n			$lastArea = \'\';\n			$inGroups = false; // Is set to true when the first area is reached\n			foreach ($tablist as $theArea => $theTabs) {\n				if ($optgroups && $lastArea != $theArea) {\n					$tabSelector .= ($inGroups) ? \'</optgroup>\' : \'\';\n					$tabSelector .= \'<optgroup label=\"\'.$theArea.\'\">\';\n					$inGroups = true;\n            }\n            foreach($theTabs as $theTab => $tabName) {\n	            $tabSelector .= \'<option value=\"\'.$theTab.\'\"\'.(($theTab == $tab_name) ? \' selected=\"selected\"\' : \'\').\'>\' . $tabName . \'</option>\';\n				}\n			}\n			$tabSelector .= (($optgroups) ? \'</optgroup>\' : \'\') . \'</select>\';\n		}\n\n		$areas = areas();\n		$areas = array_merge($areas, $smd_areas);\n		$area_list = array(\'\' => smd_tabber_gTxt(\'smd_tabber_new_area\'));\n		foreach ($areas as $idx => $alist) {\n			$key = array_search($idx, $smd_areas);\n			if ($key === false) {\n				$area_list[$idx] = $idx;\n			} else {\n				$area_list[$key] = $key;\n			}\n		}\n\n		$privs = get_groups();\n		$privsel = smd_tabber_multi_select(\'smd_tabber_view_privs\', $privs, $view_privs);\n\n		$pages = safe_column(\'name\', \'txp_page\', \"name like \'\".doSlash($tab_prefix).\"%\'\");\n		foreach ($pages as $idx => $page) {\n			$pages[$idx] = str_replace($tab_prefix, \'\', $page);\n		}\n		$styles = safe_column(\'name\', \'txp_css\', \"name like \'\".doSlash($tab_prefix).\"%\'\");\n		foreach ($styles as $idx => $style) {\n			$styles[$idx] = str_replace($tab_prefix, \'\', $style);\n		}\n\n		$editcell = (($smd_tabs)\n				? \'<div class=\"smd_tabber_label\">\'\n					. smd_tabber_gTxt(\'smd_tabber_choose\')\n					. \'</div><div class=\"smd_tabber_value\">\'\n					. $tabSelector\n					. (($tab_name != \'\')\n						? sp . eLink(strtolower(sanitizeForUrl($tab_name)), \'\', \'\',\'\', gTxt(\'View\'))\n						: \'\')\n					. eInput($smd_tabber_event)\n					.\'</div>\'\n				: \'\');\n\n		$pref_link = $pref_rights ? sp . eLink($smd_tabber_event, \'smd_tabber_prefs\', \'\', \'\', \'[\'.smd_tabber_gTxt(\'smd_tabber_prefs\').\']\') : \'\';\n\n		// Edit form\n		echo \'<style type=\"text/css\">\' . $smd_tabber_styles[\'edit\'] . \'</style>\';\n		echo \'<div id=\"smd_tabber_wrapper\">\';\n		echo hed(smd_tabber_gTxt(\'smd_tabber_heading\'), 2);\n		echo \'<div class=\"smd_tabber_preflink\">\' . $pref_link . \'</div>\';\n		echo \'<form id=\"smd_tabber_select\" action=\"index.php\" method=\"post\">\';\n		echo \'<div class=\"smd_tabber_equal\">\';\n		echo \'<div class=\"smd_tabber_row\">\' . $editcell . \'</div><!-- end row -->\';\n		echo \'</div></form>\';\n\n		echo \'<form name=\"smd_tabber_form\" id=\"smd_tabber_form\" action=\"index.php\" method=\"post\">\';\n		echo \'<div class=\"smd_tabber_equal\">\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_name\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'text\', \'smd_tabber_new_name\', $tab_new_name)\n				. hInput(\'smd_tabber_name\', $tab_name)\n				. (($tab_name == \'\')\n					? \'\'\n					: sp . \'<a href=\"?event=\'.$smd_tabber_event. a.\'step=smd_tabber_delete\' . a . \'smd_tabber_name=\'.urlencode($tab_name).\'\" class=\"smallerbox\" onclick=\"return confirm(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\');\">[x]</a>\'\n				)\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_sort_order\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'text\', \'smd_tabber_sort_order\', $sort_order)\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_area\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_area\', $area_list, $area)\n				. sp . fInput(\'text\', \'smd_tabber_new_area\', \'\')\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_view_privs\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. $privsel\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_page\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_page\', $pages, $curr_page, true)\n				. sp . (($curr_page) ? eLink(\'page\', \'\', \'name\', $curr_page, gTxt(\'edit\')) : eLink(\'page\', \'page_new\', \'\', \'\', gTxt(\'create\')) )\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_style\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_style\', $styles, $curr_style, true)\n				. sp. (($curr_style) ? eLink(\'css\', \'\', \'name\', $curr_style, gTxt(\'edit\')) : eLink(\'css\', \'pour\', \'\', \'\', gTxt(\'create\')) )\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">&nbsp;</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'submit\', \'submit\', gTxt(\'save\'), \'smd_tabber_save publish\')\n				. eInput($smd_tabber_event)\n				. sInput(\'smd_tabber_save\')\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n\n		echo \'</div><!-- end smd_tabber_equal -->\';\n		echo \'</form>\';\n		echo \'</div><!-- end smd_tabber_wrapper -->\';\n	} else {\n		// Table not installed\n		$btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_tabber_event.a.\'step=smd_tabber_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', smd_tabber_gTxt(\'smd_tabber_tbl_install_lbl\'), \'smallerbox\').\'</form>\';\n		$btnStyle = \' style=\"border:0;height:25px\"\';\n		echo startTable(\'list\');\n		echo tr(tda(strong(smd_tabber_gTxt(\'smd_tabber_prefs_some_tbl\')).br.br\n				.smd_tabber_gTxt(\'smd_tabber_prefs_some_explain\').br.br\n				.smd_tabber_gTxt(\'smd_tabber_prefs_some_opts\'), \' colspan=\"2\"\')\n		);\n		echo tr(tda($btnInstall, $btnStyle));\n		echo endTable();\n	}\n}\n\n// ------------------------\nfunction smd_tabber_save() {\n	extract(doSlash(gpsa(array(\n		\'smd_tabber_name\',\n		\'smd_tabber_new_name\',\n		\'smd_tabber_area\',\n		\'smd_tabber_new_area\',\n		\'smd_tabber_page\',\n		\'smd_tabber_style\',\n		\'smd_tabber_sort_order\',\n	))));\n\n	$vu = gps(\'smd_tabber_view_privs\');\n	$smd_tabber_view_privs = $vu ? doSlash(join(\',\', $vu)) : \'\';\n\n	$msg = \'\';\n	$theArea = ($smd_tabber_new_area == \'\') ? $smd_tabber_area : $smd_tabber_new_area;\n\n	if ($smd_tabber_new_name == \'\') {\n		$msg = array(smd_tabber_gTxt(\'smd_tabber_need_name\'), E_WARNING);\n	} else {\n		if ($theArea == \'\') {\n			$msg = array(smd_tabber_gTxt(\'smd_tabber_need_area\'), E_WARNING);\n		} else {\n			$exists = safe_field(\'name\', SMD_TABBER, \"name=\'$smd_tabber_new_name\'\");\n			$same = ($smd_tabber_name != $smd_tabber_new_name) && $exists;\n			if ($same == false) {\n				$_POST[\'smd_tabber_name\'] = $smd_tabber_new_name;\n				if ($smd_tabber_name == \'\') {\n					safe_insert(SMD_TABBER, \"name=\'$smd_tabber_new_name\', sort_order=\'$smd_tabber_sort_order\', area=\'\".doSlash($theArea).\"\', page=\'$smd_tabber_page\', style=\'$smd_tabber_style\', view_privs=\'$smd_tabber_view_privs\'\");\n					$msg = smd_tabber_gTxt(\'smd_tabber_created\');\n				} else {\n					safe_update(SMD_TABBER, \"name=\'$smd_tabber_new_name\', sort_order=\'$smd_tabber_sort_order\', area=\'\".doSlash($theArea).\"\', page=\'$smd_tabber_page\', style=\'$smd_tabber_style\', view_privs=\'$smd_tabber_view_privs\'\", \"name=\'$smd_tabber_name\'\");\n					$msg = smd_tabber_gTxt(\'smd_tabber_saved\');\n				}\n			} else {\n				$msg = array(smd_tabber_gTxt(\'smd_tabber_exists\'), E_WARNING);\n			}\n		}\n	}\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_delete() {\n	global $smd_tabber_event;\n\n	$name = doSlash(gps(\'smd_tabber_name\'));\n\n	$ret = safe_delete(SMD_TABBER, \"name=\'$name\'\");\n	$msg = smd_tabber_gTxt(\'smd_tabber_deleted\');\n\n	$_GET[\'smd_tabber_name\'] = \'\';\n\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_prefs($msg=\'\') {\n	global $smd_tabber_event, $smd_tabber_styles;\n\n	pagetop(smd_tabber_gTxt(\'smd_tabber_prefs_lbl\'), $msg);\n\n	$users = safe_rows(\'*\', \'txp_users\', \'1=1 ORDER BY RealName\');\n	$privs = array(\'\' => smd_tabber_gTxt(\'smd_tabber_all_pubs\'));\n	foreach ($users as $idx => $user) {\n		$privs[$user[\'name\']] = $user[\'RealName\'];\n	}\n\n	$curr_privs = explode(\',\', get_pref(\'smd_tabber_tab_privs\', \'\'));\n	$parse_depth = get_pref(\'smd_tabber_parse_depth\', \'1\');\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	echo \'<style type=\"text/css\">\' . $smd_tabber_styles[\'prefs\'] . \'</style>\';\n	echo \'<form action=\"index.php\" method=\"post\" name=\"smd_tabber_prefs_form\">\';\n	echo startTable(\'list\');\n	echo tr(tda(hed(smd_tabber_gTxt(\'smd_tabber_prefs_lbl\'), 2), \' colspan=\"3\"\'));\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_tab_privs\'), \' class=\"smd_label\"\')\n		. tda(smd_tabber_multi_select(\'smd_tabber_tab_privs\', $privs, $curr_privs, 10))\n	);\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_tab_prefix\'), \' class=\"smd_label\"\')\n		. tda(fInput(\'text\', \'smd_tabber_tab_prefix\', $tab_prefix))\n	);\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_parse_depth\'), \' class=\"smd_label\"\')\n		. tda(fInput(\'text\', \'smd_tabber_parse_depth\', $parse_depth))\n	);\n	echo tr(tda(eLink($smd_tabber_event, \'\', \'\', \'\', smd_tabber_gTxt(\'smd_tabber_cancel\')), \' class=\"noline\"\') . tda(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\'). eInput($smd_tabber_event).sInput(\'smd_tabber_prefsave\'), \' class=\"noline\"\'));\n	echo endTable();\n	echo \'</form>\';\n}\n\n// ------------------------\nfunction smd_tabber_prefsave() {\n	$depth = intval(gps(\'smd_tabber_parse_depth\'));\n	$prefix = gps(\'smd_tabber_tab_prefix\');\n	$items = gps(\'smd_tabber_tab_privs\');\n	$privs = ($items) ? join(\',\', $items) : \'\';\n	set_pref(\'smd_tabber_tab_privs\', $privs, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n	set_pref(\'smd_tabber_tab_prefix\', $prefix, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n	set_pref(\'smd_tabber_parse_depth\', $depth, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n\n	$msg = gTxt(\'preferences_saved\');\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_multi_select($name, $items, $sel=array(), $size=\'7\') {\n	$out = \'<select name=\"\'.$name.\'[]\" multiple=\"multiple\" size=\"\'.$size.\'\">\'.n;\n	foreach ($items as $idx => $item) {\n		$out .= \'<option value=\"\'.$idx.\'\"\'.((in_array($idx, $sel)) ? \' selected=\"selected\"\' : \'\').\'>\'.$item.\'</option>\'.n;\n	}\n	$out .= \'</select>\'.n;\n\n	return $out;\n}\n\n// ------------------------\n// Add tabber table if not already installed\nfunction smd_tabber_table_install($showpane=\'1\') {\n	$GLOBALS[\'txp_err_count\'] = 0;\n	$ret = \'\';\n	$sql = array();\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TABBER.\"` (\n		`name` varchar(32) NOT NULL default \'\' COLLATE utf8_general_ci,\n		`sort_order` varchar(32) NULL default \'\' COLLATE utf8_general_ci,\n		`area` varchar(32) NULL default \'\' COLLATE utf8_general_ci,\n		`view_privs` varchar(32) NOT NULL default \'\',\n		`page` varchar(32) NULL default \'\',\n		`style` varchar(32) NULL default \'\',\n		PRIMARY KEY (`name`)\n	) ENGINE=MyISAM\";\n\n	if(gps(\'debug\')) {\n		dmp($sql);\n	}\n	foreach ($sql as $qry) {\n		$ret = safe_query($qry);\n		if ($ret===false) {\n			$GLOBALS[\'txp_err_count\']++;\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n			echo \"<!--\\n $qry \\n-->\\n\";\n		}\n	}\n\n	// Upgrade: be kind to beta testers\n	$flds = getThings(\'describe `\'.PFX.SMD_TABBER.\'`\');\n	if (!in_array(\'sort_order\',$flds)) {\n		safe_alter(SMD_TABBER, \"add `sort_order` varchar(32) NULL default \'\' COLLATE utf8_general_ci after `name`\");\n	}\n\n	// Spit out results\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_installed\');\n			smd_tabber($msg);\n		}\n	} else {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_not_installed\');\n			smd_tabber($msg);\n		}\n	}\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_tabber_table_remove($showpane=\'1\') {\n	$ret = \'\';\n	$sql = array();\n	$GLOBALS[\'txp_err_count\'] = 0;\n	$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TABBER. \"; \";\n	if(gps(\'debug\')) {\n		dmp($sql);\n	}\n	foreach ($sql as $qry) {\n		$ret = safe_query($qry);\n		if ($ret===false) {\n			$GLOBALS[\'txp_err_count\']++;\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n			echo \"<!--\\n $qry \\n-->\\n\";\n		}\n	}\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_removed\');\n		}\n	} else {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_not_removed\');\n			smd_tabber($msg);\n		}\n	}\n}\n\n// ------------------------\nfunction smd_tabber_table_exist($all=\'\') {\n	if ($all) {\n		$tbls = array(SMD_TABBER => 6);\n		$out = count($tbls);\n		foreach ($tbls as $tbl => $cols) {\n			if (gps(\'debug\')) {\n				echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n			}\n			if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n				$out--;\n			}\n		}\n		return ($out===0) ? 1 : 0;\n	} else {\n		if (gps(\'debug\')) {\n			echo \"++ TABLE \".SMD_TABBER.\" HAS \".count(@safe_show(\'columns\', SMD_tabber)).\" COLUMNS;\";\n		}\n		return(@safe_show(\'columns\', SMD_TABBER));\n	}\n}\n\n// ***********\n// PUBLIC TAGS\n// ***********\n// ------------------------\nfunction smd_tabber_edit_page($atts, $thing=NULL) {\n	global $smd_tabber_callstack, $event;\n\n	extract(lAtts(array(\n		\'name\'    => $event,\n		\'title\'   => \'Edit page\',\n		\'class\'   => \'\',\n		\'html_id\' => \'\',\n		\'wraptag\' => \'\',\n	),$atts));\n\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	if (isset($atts[\'name\'])) {\n		$page = (strpos($name, $tab_prefix) !== false) ? $name : $tab_prefix.$name;\n	} else {\n		// Lookup the name of the page\n		$ev = (strpos($name, $tab_prefix) !== false) ? str_replace($tab_prefix, \'\', $name) : $name;\n		$page = (isset($smd_tabber_callstack[$ev])) ? $smd_tabber_callstack[$ev][\'page\'] : \'\';\n	}\n	$idx = \'name\';\n	$step = \'\';\n	if (!$page) {\n		$page = $idx = \'\';\n		$step = \'page_new\';\n	}\n\n	$lnk = eLink(\'page\', $step, $idx, $page, $title);\n	return ($wraptag) ? doTag($lnk, $wraptag, $class, \'\', $html_id) : $lnk;\n}\n\n// ------------------------\nfunction smd_tabber_edit_style($atts, $thing=NULL) {\n	global $smd_tabber_callstack, $event;\n\n	extract(lAtts(array(\n		\'name\'    => $event,\n		\'title\'   => \'Edit CSS\',\n		\'class\'   => \'\',\n		\'html_id\' => \'\',\n		\'wraptag\' => \'\',\n	),$atts));\n\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	if (isset($atts[\'name\'])) {\n		$css = (strpos($name, $tab_prefix) !== false) ? $name : $tab_prefix.$name;\n	} else {\n		// Lookup the name of the stylesheet\n		$ev = (strpos($name, $tab_prefix) !== false) ? str_replace($tab_prefix, \'\', $name) : $name;\n		$css = (isset($smd_tabber_callstack[$ev])) ? $smd_tabber_callstack[$ev][\'style\'] : \'\';\n	}\n\n	$idx = \'name\';\n	$step = \'\';\n	if (!$css) {\n		$css = $idx = \'\';\n		$step = \'pour\';\n	}\n\n	$lnk = eLink(\'css\', $step, $idx, $css, $title);\n	return ($wraptag) ? doTag($lnk, $wraptag, $class, \'\', $html_id) : $lnk;\n}\n\nfunction smd_tabber_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'smd_tabber_all_pubs\' => \'All publishers\',\n		\'smd_tabber_area\' => \'Assign to area:\',\n		\'smd_tabber_cancel\' => \'[Cancel]\',\n		\'smd_tabber_choose\' => \'Select tab to edit:\',\n		\'smd_tabber_created\' => \'Tab created. Refresh to see it\',\n		\'smd_tabber_deleted\' => \'Tab deleted. Refresh to see changes\',\n		\'smd_tabber_exists\' => \'Tab already exists\',\n		\'smd_tabber_heading\' => \'Manage additional TXP tabs\',\n		\'smd_tabber_name\' => \'Tab name:\',\n		\'smd_tabber_need_area\' => \'Please supply a location for the tab\',\n		\'smd_tabber_need_name\' => \'Please supply a tab name\',\n		\'smd_tabber_new_area\' => \'New area >>\',\n		\'smd_tabber_new_tab\' => \'New tab\',\n		\'smd_tabber_page\' => \'Page template:\',\n		\'smd_tabber_parse_depth\' => \'Parse depth\',\n		\'smd_tabber_prefs\' => \'Preferences\',\n		\'smd_tabber_prefs_lbl\' => \'Tabber preferences\',\n		\'smd_tabber_prefs_some_explain\' => \'This is either a new installation or a different version\'.br.\'of the plugin to one you had before.\',\n		\'smd_tabber_prefs_some_opts\' => \'Click \"Install table\" to add or update the table\'.br.\'leaving all existing data untouched.\',\n		\'smd_tabber_prefs_some_tbl\' => \'Not all table info available.\',\n		\'smd_tabber_view_privs\' => \'View privileges:\',\n		\'smd_tabber_saved\' => \'Tab saved. Refresh to see changes\',\n		\'smd_tabber_sort_order\' => \'Sort order:\',\n		\'smd_tabber_style\' => \'Stylesheet:\',\n		\'smd_tabber_tab_name\' => \'Manage tabs\',\n		\'smd_tabber_tab_prefix\' => \'Page/Style prefix\',\n		\'smd_tabber_tab_privs\' => \'Permit tabs to be managed by\',\n		\'smd_tabber_tbl_installed\' => \'Table installed\',\n		\'smd_tabber_tbl_install_lbl\' => \'Install table\',\n		\'smd_tabber_tbl_not_installed\' => \'Table NOT installed\',\n		\'smd_tabber_tbl_not_removed\' => \'Table NOT removed\',\n		\'smd_tabber_tbl_removed\' => \'Table removed\',\n	);\n	return strtr($lang[$what], $atts);\n}', 'if (!defined(\'SMD_TABBER\')) {\n	define(\"SMD_TABBER\", \'smd_tabber\');\n}\nif(@txpinterface == \'admin\') {\n	global $textarray, $smd_tabber_event, $smd_tabber_styles, $txp_user, $smd_tabber_callstack, $smd_tabber_uprivs, $smd_tabber_prevs;\n\n	$smd_tabber_event = \'smd_tabber\';\n	$smd_tabber_prevs = \'1\';\n	$smd_tabber_styles = array(\n		\'edit\' => \'#smd_tabber_wrapper { margin:0 auto; width:520px; }\n.smd_tabber_equal { display:table; border-collapse:separate; margin:10px auto; border-spacing:8px; }\n.smd_tabber_row { display:table-row; }\n.smd_tabber_row div { display:table-cell; }\n.smd_tabber_row .smd_tabber_label { width:150px; text-align:right; padding:2px 0 0 0; }\n.smd_tabber_row .smd_tabber_value { width:350px; vertical-align:middle; }\n#smd_tabber_select { margin-bottom:-20px; }\n.smd_tabber_save { float:right; margin:10px 50px 0 0!important;}\',\n		\'prefs\' => \'.smd_label { text-align: right!important; vertical-align: top; }\n		\',\n	);\n	register_callback(\'smd_tabber_welcome\', \'plugin_lifecycle.\'.$smd_tabber_event);\n\n	$ulist = get_pref(\'smd_tabber_tab_privs\', \'\');\n	$allowed = ($ulist) ? explode(\',\', $ulist) : array();\n	$levs = ($allowed) ? \'1,2,3,4,5,6\' : \'1\';\n	if (empty($allowed) || in_array($txp_user, $allowed)) {\n		add_privs($smd_tabber_event, $levs);\n	}\n\n	// Grab this here so that the privs are known immediately after manual install\n	$smd_tabber_uprivs = safe_field(\'privs\', \'txp_users\', \"name = \'\".doSlash($txp_user).\"\'\");\n\n	register_tab(\'admin\', $smd_tabber_event, smd_tabber_gTxt(\'smd_tabber_tab_name\'));\n	register_callback(\'smd_tabber_dispatch\', $smd_tabber_event);\n\n	// Do the tabbing deed\n	if (smd_tabber_table_exist(1)) {\n		register_callback(\'smd_tabber_css_link\', \'admin_side\', \'head_end\');\n		$smd_tabs = safe_rows(\'*\', SMD_TABBER, \'1=1 ORDER BY area, sort_order, name\');\n\n		// Yuk but no other way to get these.\n		// NB: \'start\' missing on purpose as it has no privs by default, so needs them adding\n	 	$smd_areas = array(\'content\', \'presentation\', \'admin\', \'extensions\');\n\n		foreach ($smd_tabs as $idx => $tab) {\n			$name = $tab[\'name\'];\n			$area = $tab[\'area\'];\n			$areaname = strtolower(sanitizeForUrl($area));\n			$area_privname = \'tab.\' . $areaname;\n			$create_top = (!in_array($areaname, $smd_areas));\n			$tabname = strtolower(sanitizeForUrl($name));\n\n			$eprivs = explode(\',\', $tab[\'view_privs\']);\n			$rights = in_array($smd_tabber_uprivs, $eprivs);\n\n			if ($rights) {\n				if ($create_top) {\n					add_privs($area_privname, $smd_tabber_uprivs);\n					$smd_areas[] = $areaname;\n					if ($areaname != \'start\') {\n						$textarray[\'tab_\'.$areaname] = $area;\n					}\n				}\n\n				add_privs($tabname, $smd_tabber_uprivs);\n				register_tab($areaname, $tabname, $name);\n				register_callback(\'smd_tabber_render_tab\', $tabname);\n				$smd_tabber_callstack[$tabname] = array(\'name\' => $name, \'page\' => $tab[\'page\'], \'style\' => $tab[\'style\']);\n			}\n		}\n	}\n}\n\n// ------------------------\nfunction smd_tabber_render_tab($evt, $stp) {\n	global $smd_tabber_callstack, $pretext;\n\n	$tab_info = $smd_tabber_callstack[$evt];\n\n	// Allow multiple parse calls for any nested {replaced} content\n	$parse_depth = intval(get_pref(\'smd_tabber_parse_depth\', 1));\n\n	pagetop($tab_info[\'name\']);\n\n	$html = safe_field(\'user_html\', \'txp_page\', \"name=\'\".doSlash($tab_info[\'page\']).\"\'\");\n	if (!$html) {\n		$html = \'<txp:smd_tabber_edit_page />\'.n.\'<txp:smd_tabber_edit_style />\';\n	}\n\n	// Hand over control to the Page code\n	include_once txpath.\'/publish.php\';\n	for ($idx = 0; $idx < $parse_depth; $idx++) {\n		$html = parse($html);\n	}\n	echo $html;\n}\n\n// ------------------------\nfunction smd_tabber_dispatch($evt, $stp) {\n	if(!$stp or !in_array($stp, array(\n			\'smd_tabber_table_install\',\n			\'smd_tabber_table_remove\',\n			\'smd_tabber_css\',\n			\'smd_tabber_prefs\',\n			\'smd_tabber_prefsave\',\n			\'smd_tabber_save\',\n			\'smd_tabber_delete\',\n		))) {\n		smd_tabber(\'\');\n	} else $stp();\n}\n\n// ------------------------\nfunction smd_tabber_welcome($evt, $stp) {\n	$msg = \'\';\n	switch ($stp) {\n		case \'installed\':\n			smd_tabber_table_install(0);\n			$msg = \'Supertabs are go!\';\n			break;\n		case \'deleted\':\n			smd_tabber_table_remove(0);\n			break;\n	}\n	return $msg;\n}\n\n// ------------------------\nfunction smd_tabber_css() {\n	global $event;\n	$name = doSlash(gps(\'name\'));\n	$css = safe_field(\'css\', \'txp_css\', \"name=\'$name\'\");\n	unset($name);\n\n	if ($css) {\n		header(\'Content-type: text/css\');\n		echo $css;\n		exit();\n	}\n}\n\n// ------------------------\nfunction smd_tabber_css_link() {\n	global $event, $smd_tabber_event;\n	// Annoyingly, we need an extra test here in case the plugin has been deleted.\n	// This callback is registered before plugin deletion but by the time it runs the table is gone\n	if (smd_tabber_table_exist(1)) {\n		$smd_tab = safe_field(\'style\', SMD_TABBER, \"name = \'\".doSlash($event).\"\'\");\n		echo ($smd_tab) ? n.\'<link href=\"?event=\'.$smd_tabber_event.a.\'step=smd_tabber_css\'.a.\'name=\'.$smd_tab.\'\" rel=\"stylesheet\" type=\"text/css\" />\'.n : \'\';\n	}\n}\n\n// ------------------------\nfunction smd_tabber($msg=\'\') {\n	global $smd_tabber_event, $smd_tabber_uprivs, $smd_tabber_prevs, $smd_tabber_styles;\n\n	pagetop(smd_tabber_gTxt(\'smd_tabber_tab_name\'), $msg);\n	$pref_rights = in_array($smd_tabber_prevs, explode(\',\', $smd_tabber_uprivs));\n\n	if (smd_tabber_table_exist(1)) {\n		$tab_name = $tab_new_name = gps(\'smd_tabber_name\');\n		$area = $curr_page = $curr_style = $sort_order = \'\';\n		$view_privs = array();\n		$tablist = $smd_areas = array();\n		$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\', 1);\n\n		// Can\'t use the smd_tabs and smd_areas lists in the global scope \'cos they\'re stale / strtolower()ed\n		$smd_tabs = safe_rows(\'*\', SMD_TABBER, \'1=1 ORDER BY area, sort_order, name\');\n		if ($smd_tabs) {\n			foreach ($smd_tabs as $idx => $tab) {\n				$tablist[$tab[\'area\']][$tab[\'name\']] = $tab[\'name\'];\n				$smd_areas[$tab[\'area\']] = strtolower(sanitizeForUrl($tab[\'area\']));\n	\n				if ($tab[\'name\'] == $tab_name) {\n					$sort_order = $tab[\'sort_order\'];\n					$area = $tab[\'area\'];\n					$view_privs = explode(\',\', $tab[\'view_privs\']);\n					$curr_page = $tab[\'page\'];\n					$curr_style = $tab[\'style\'];\n				}\n			}\n		}\n\n		// Default to the current user level\'s privs for new items\n		if ($tab_name == \'\') {\n			$view_privs[] = $smd_tabber_uprivs;\n		}\n\n		// Build a select list of tab names, injecting optgroups above any areas\n		$optgroups = (count($tablist) > 1); // Only add optgroups if there are tabs in more than one area\n		$tabSelector = \'\';\n		if ($tablist) {\n			$tabSelector .= \'<select name=\"smd_tabber_name\" id=\"smd_tabber_name\" onchange=\"submit(this.form);\">\';\n			$tabSelector .= \'<option value=\"\">\' . smd_tabber_gTxt(\'smd_tabber_new_tab\') . \'</option>\';\n			$lastArea = \'\';\n			$inGroups = false; // Is set to true when the first area is reached\n			foreach ($tablist as $theArea => $theTabs) {\n				if ($optgroups && $lastArea != $theArea) {\n					$tabSelector .= ($inGroups) ? \'</optgroup>\' : \'\';\n					$tabSelector .= \'<optgroup label=\"\'.$theArea.\'\">\';\n					$inGroups = true;\n            }\n            foreach($theTabs as $theTab => $tabName) {\n	            $tabSelector .= \'<option value=\"\'.$theTab.\'\"\'.(($theTab == $tab_name) ? \' selected=\"selected\"\' : \'\').\'>\' . $tabName . \'</option>\';\n				}\n			}\n			$tabSelector .= (($optgroups) ? \'</optgroup>\' : \'\') . \'</select>\';\n		}\n\n		$areas = areas();\n		$areas = array_merge($areas, $smd_areas);\n		$area_list = array(\'\' => smd_tabber_gTxt(\'smd_tabber_new_area\'));\n		foreach ($areas as $idx => $alist) {\n			$key = array_search($idx, $smd_areas);\n			if ($key === false) {\n				$area_list[$idx] = $idx;\n			} else {\n				$area_list[$key] = $key;\n			}\n		}\n\n		$privs = get_groups();\n		$privsel = smd_tabber_multi_select(\'smd_tabber_view_privs\', $privs, $view_privs);\n\n		$pages = safe_column(\'name\', \'txp_page\', \"name like \'\".doSlash($tab_prefix).\"%\'\");\n		foreach ($pages as $idx => $page) {\n			$pages[$idx] = str_replace($tab_prefix, \'\', $page);\n		}\n		$styles = safe_column(\'name\', \'txp_css\', \"name like \'\".doSlash($tab_prefix).\"%\'\");\n		foreach ($styles as $idx => $style) {\n			$styles[$idx] = str_replace($tab_prefix, \'\', $style);\n		}\n\n		$editcell = (($smd_tabs)\n				? \'<div class=\"smd_tabber_label\">\'\n					. smd_tabber_gTxt(\'smd_tabber_choose\')\n					. \'</div><div class=\"smd_tabber_value\">\'\n					. $tabSelector\n					. (($tab_name != \'\')\n						? sp . eLink(strtolower(sanitizeForUrl($tab_name)), \'\', \'\',\'\', gTxt(\'View\'))\n						: \'\')\n					. eInput($smd_tabber_event)\n					.\'</div>\'\n				: \'\');\n\n		$pref_link = $pref_rights ? sp . eLink($smd_tabber_event, \'smd_tabber_prefs\', \'\', \'\', \'[\'.smd_tabber_gTxt(\'smd_tabber_prefs\').\']\') : \'\';\n\n		// Edit form\n		echo \'<style type=\"text/css\">\' . $smd_tabber_styles[\'edit\'] . \'</style>\';\n		echo \'<div id=\"smd_tabber_wrapper\">\';\n		echo hed(smd_tabber_gTxt(\'smd_tabber_heading\'), 2);\n		echo \'<div class=\"smd_tabber_preflink\">\' . $pref_link . \'</div>\';\n		echo \'<form id=\"smd_tabber_select\" action=\"index.php\" method=\"post\">\';\n		echo \'<div class=\"smd_tabber_equal\">\';\n		echo \'<div class=\"smd_tabber_row\">\' . $editcell . \'</div><!-- end row -->\';\n		echo \'</div></form>\';\n\n		echo \'<form name=\"smd_tabber_form\" id=\"smd_tabber_form\" action=\"index.php\" method=\"post\">\';\n		echo \'<div class=\"smd_tabber_equal\">\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_name\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'text\', \'smd_tabber_new_name\', $tab_new_name)\n				. hInput(\'smd_tabber_name\', $tab_name)\n				. (($tab_name == \'\')\n					? \'\'\n					: sp . \'<a href=\"?event=\'.$smd_tabber_event. a.\'step=smd_tabber_delete\' . a . \'smd_tabber_name=\'.urlencode($tab_name).\'\" class=\"smallerbox\" onclick=\"return confirm(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\');\">[x]</a>\'\n				)\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_sort_order\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'text\', \'smd_tabber_sort_order\', $sort_order)\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_area\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_area\', $area_list, $area)\n				. sp . fInput(\'text\', \'smd_tabber_new_area\', \'\')\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_view_privs\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. $privsel\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_page\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_page\', $pages, $curr_page, true)\n				. sp . (($curr_page) ? eLink(\'page\', \'\', \'name\', $curr_page, gTxt(\'edit\')) : eLink(\'page\', \'page_new\', \'\', \'\', gTxt(\'create\')) )\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">\' . smd_tabber_gTxt(\'smd_tabber_style\') . \'</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. selectInput(\'smd_tabber_style\', $styles, $curr_style, true)\n				. sp. (($curr_style) ? eLink(\'css\', \'\', \'name\', $curr_style, gTxt(\'edit\')) : eLink(\'css\', \'pour\', \'\', \'\', gTxt(\'create\')) )\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n\n		echo \'<div class=\"smd_tabber_row\">\';\n		echo \'<div class=\"smd_tabber_label\">&nbsp;</div>\';\n		echo \'<div class=\"smd_tabber_value\">\'\n				. fInput(\'submit\', \'submit\', gTxt(\'save\'), \'smd_tabber_save publish\')\n				. eInput($smd_tabber_event)\n				. sInput(\'smd_tabber_save\')\n				. \'</div>\'\n				. \'</div><!-- end row -->\';\n\n		echo \'</div><!-- end smd_tabber_equal -->\';\n		echo \'</form>\';\n		echo \'</div><!-- end smd_tabber_wrapper -->\';\n	} else {\n		// Table not installed\n		$btnInstall = \'<form method=\"post\" action=\"?event=\'.$smd_tabber_event.a.\'step=smd_tabber_table_install\" style=\"display:inline\">\'.fInput(\'submit\', \'submit\', smd_tabber_gTxt(\'smd_tabber_tbl_install_lbl\'), \'smallerbox\').\'</form>\';\n		$btnStyle = \' style=\"border:0;height:25px\"\';\n		echo startTable(\'list\');\n		echo tr(tda(strong(smd_tabber_gTxt(\'smd_tabber_prefs_some_tbl\')).br.br\n				.smd_tabber_gTxt(\'smd_tabber_prefs_some_explain\').br.br\n				.smd_tabber_gTxt(\'smd_tabber_prefs_some_opts\'), \' colspan=\"2\"\')\n		);\n		echo tr(tda($btnInstall, $btnStyle));\n		echo endTable();\n	}\n}\n\n// ------------------------\nfunction smd_tabber_save() {\n	extract(doSlash(gpsa(array(\n		\'smd_tabber_name\',\n		\'smd_tabber_new_name\',\n		\'smd_tabber_area\',\n		\'smd_tabber_new_area\',\n		\'smd_tabber_page\',\n		\'smd_tabber_style\',\n		\'smd_tabber_sort_order\',\n	))));\n\n	$vu = gps(\'smd_tabber_view_privs\');\n	$smd_tabber_view_privs = $vu ? doSlash(join(\',\', $vu)) : \'\';\n\n	$msg = \'\';\n	$theArea = ($smd_tabber_new_area == \'\') ? $smd_tabber_area : $smd_tabber_new_area;\n\n	if ($smd_tabber_new_name == \'\') {\n		$msg = array(smd_tabber_gTxt(\'smd_tabber_need_name\'), E_WARNING);\n	} else {\n		if ($theArea == \'\') {\n			$msg = array(smd_tabber_gTxt(\'smd_tabber_need_area\'), E_WARNING);\n		} else {\n			$exists = safe_field(\'name\', SMD_TABBER, \"name=\'$smd_tabber_new_name\'\");\n			$same = ($smd_tabber_name != $smd_tabber_new_name) && $exists;\n			if ($same == false) {\n				$_POST[\'smd_tabber_name\'] = $smd_tabber_new_name;\n				if ($smd_tabber_name == \'\') {\n					safe_insert(SMD_TABBER, \"name=\'$smd_tabber_new_name\', sort_order=\'$smd_tabber_sort_order\', area=\'\".doSlash($theArea).\"\', page=\'$smd_tabber_page\', style=\'$smd_tabber_style\', view_privs=\'$smd_tabber_view_privs\'\");\n					$msg = smd_tabber_gTxt(\'smd_tabber_created\');\n				} else {\n					safe_update(SMD_TABBER, \"name=\'$smd_tabber_new_name\', sort_order=\'$smd_tabber_sort_order\', area=\'\".doSlash($theArea).\"\', page=\'$smd_tabber_page\', style=\'$smd_tabber_style\', view_privs=\'$smd_tabber_view_privs\'\", \"name=\'$smd_tabber_name\'\");\n					$msg = smd_tabber_gTxt(\'smd_tabber_saved\');\n				}\n			} else {\n				$msg = array(smd_tabber_gTxt(\'smd_tabber_exists\'), E_WARNING);\n			}\n		}\n	}\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_delete() {\n	global $smd_tabber_event;\n\n	$name = doSlash(gps(\'smd_tabber_name\'));\n\n	$ret = safe_delete(SMD_TABBER, \"name=\'$name\'\");\n	$msg = smd_tabber_gTxt(\'smd_tabber_deleted\');\n\n	$_GET[\'smd_tabber_name\'] = \'\';\n\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_prefs($msg=\'\') {\n	global $smd_tabber_event, $smd_tabber_styles;\n\n	pagetop(smd_tabber_gTxt(\'smd_tabber_prefs_lbl\'), $msg);\n\n	$users = safe_rows(\'*\', \'txp_users\', \'1=1 ORDER BY RealName\');\n	$privs = array(\'\' => smd_tabber_gTxt(\'smd_tabber_all_pubs\'));\n	foreach ($users as $idx => $user) {\n		$privs[$user[\'name\']] = $user[\'RealName\'];\n	}\n\n	$curr_privs = explode(\',\', get_pref(\'smd_tabber_tab_privs\', \'\'));\n	$parse_depth = get_pref(\'smd_tabber_parse_depth\', \'1\');\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	echo \'<style type=\"text/css\">\' . $smd_tabber_styles[\'prefs\'] . \'</style>\';\n	echo \'<form action=\"index.php\" method=\"post\" name=\"smd_tabber_prefs_form\">\';\n	echo startTable(\'list\');\n	echo tr(tda(hed(smd_tabber_gTxt(\'smd_tabber_prefs_lbl\'), 2), \' colspan=\"3\"\'));\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_tab_privs\'), \' class=\"smd_label\"\')\n		. tda(smd_tabber_multi_select(\'smd_tabber_tab_privs\', $privs, $curr_privs, 10))\n	);\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_tab_prefix\'), \' class=\"smd_label\"\')\n		. tda(fInput(\'text\', \'smd_tabber_tab_prefix\', $tab_prefix))\n	);\n	echo tr(\n		tda(smd_tabber_gTxt(\'smd_tabber_parse_depth\'), \' class=\"smd_label\"\')\n		. tda(fInput(\'text\', \'smd_tabber_parse_depth\', $parse_depth))\n	);\n	echo tr(tda(eLink($smd_tabber_event, \'\', \'\', \'\', smd_tabber_gTxt(\'smd_tabber_cancel\')), \' class=\"noline\"\') . tda(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\'). eInput($smd_tabber_event).sInput(\'smd_tabber_prefsave\'), \' class=\"noline\"\'));\n	echo endTable();\n	echo \'</form>\';\n}\n\n// ------------------------\nfunction smd_tabber_prefsave() {\n	$depth = intval(gps(\'smd_tabber_parse_depth\'));\n	$prefix = gps(\'smd_tabber_tab_prefix\');\n	$items = gps(\'smd_tabber_tab_privs\');\n	$privs = ($items) ? join(\',\', $items) : \'\';\n	set_pref(\'smd_tabber_tab_privs\', $privs, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n	set_pref(\'smd_tabber_tab_prefix\', $prefix, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n	set_pref(\'smd_tabber_parse_depth\', $depth, \'smd_tabber\', PREF_HIDDEN, \'text_input\');\n\n	$msg = gTxt(\'preferences_saved\');\n	smd_tabber($msg);\n}\n\n// ------------------------\nfunction smd_tabber_multi_select($name, $items, $sel=array(), $size=\'7\') {\n	$out = \'<select name=\"\'.$name.\'[]\" multiple=\"multiple\" size=\"\'.$size.\'\">\'.n;\n	foreach ($items as $idx => $item) {\n		$out .= \'<option value=\"\'.$idx.\'\"\'.((in_array($idx, $sel)) ? \' selected=\"selected\"\' : \'\').\'>\'.$item.\'</option>\'.n;\n	}\n	$out .= \'</select>\'.n;\n\n	return $out;\n}\n\n// ------------------------\n// Add tabber table if not already installed\nfunction smd_tabber_table_install($showpane=\'1\') {\n	$GLOBALS[\'txp_err_count\'] = 0;\n	$ret = \'\';\n	$sql = array();\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TABBER.\"` (\n		`name` varchar(32) NOT NULL default \'\' COLLATE utf8_general_ci,\n		`sort_order` varchar(32) NULL default \'\' COLLATE utf8_general_ci,\n		`area` varchar(32) NULL default \'\' COLLATE utf8_general_ci,\n		`view_privs` varchar(32) NOT NULL default \'\',\n		`page` varchar(32) NULL default \'\',\n		`style` varchar(32) NULL default \'\',\n		PRIMARY KEY (`name`)\n	) ENGINE=MyISAM\";\n\n	if(gps(\'debug\')) {\n		dmp($sql);\n	}\n	foreach ($sql as $qry) {\n		$ret = safe_query($qry);\n		if ($ret===false) {\n			$GLOBALS[\'txp_err_count\']++;\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n			echo \"<!--\\n $qry \\n-->\\n\";\n		}\n	}\n\n	// Upgrade: be kind to beta testers\n	$flds = getThings(\'describe `\'.PFX.SMD_TABBER.\'`\');\n	if (!in_array(\'sort_order\',$flds)) {\n		safe_alter(SMD_TABBER, \"add `sort_order` varchar(32) NULL default \'\' COLLATE utf8_general_ci after `name`\");\n	}\n\n	// Spit out results\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_installed\');\n			smd_tabber($msg);\n		}\n	} else {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_not_installed\');\n			smd_tabber($msg);\n		}\n	}\n}\n\n// ------------------------\n// Drop table if in database\nfunction smd_tabber_table_remove($showpane=\'1\') {\n	$ret = \'\';\n	$sql = array();\n	$GLOBALS[\'txp_err_count\'] = 0;\n	$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TABBER. \"; \";\n	if(gps(\'debug\')) {\n		dmp($sql);\n	}\n	foreach ($sql as $qry) {\n		$ret = safe_query($qry);\n		if ($ret===false) {\n			$GLOBALS[\'txp_err_count\']++;\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\n			echo \"<!--\\n $qry \\n-->\\n\";\n		}\n	}\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_removed\');\n		}\n	} else {\n		if ($showpane) {\n			$msg = smd_tabber_gTxt(\'smd_tabber_tbl_not_removed\');\n			smd_tabber($msg);\n		}\n	}\n}\n\n// ------------------------\nfunction smd_tabber_table_exist($all=\'\') {\n	if ($all) {\n		$tbls = array(SMD_TABBER => 6);\n		$out = count($tbls);\n		foreach ($tbls as $tbl => $cols) {\n			if (gps(\'debug\')) {\n				echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n			}\n			if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n				$out--;\n			}\n		}\n		return ($out===0) ? 1 : 0;\n	} else {\n		if (gps(\'debug\')) {\n			echo \"++ TABLE \".SMD_TABBER.\" HAS \".count(@safe_show(\'columns\', SMD_tabber)).\" COLUMNS;\";\n		}\n		return(@safe_show(\'columns\', SMD_TABBER));\n	}\n}\n\n// ***********\n// PUBLIC TAGS\n// ***********\n// ------------------------\nfunction smd_tabber_edit_page($atts, $thing=NULL) {\n	global $smd_tabber_callstack, $event;\n\n	extract(lAtts(array(\n		\'name\'    => $event,\n		\'title\'   => \'Edit page\',\n		\'class\'   => \'\',\n		\'html_id\' => \'\',\n		\'wraptag\' => \'\',\n	),$atts));\n\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	if (isset($atts[\'name\'])) {\n		$page = (strpos($name, $tab_prefix) !== false) ? $name : $tab_prefix.$name;\n	} else {\n		// Lookup the name of the page\n		$ev = (strpos($name, $tab_prefix) !== false) ? str_replace($tab_prefix, \'\', $name) : $name;\n		$page = (isset($smd_tabber_callstack[$ev])) ? $smd_tabber_callstack[$ev][\'page\'] : \'\';\n	}\n	$idx = \'name\';\n	$step = \'\';\n	if (!$page) {\n		$page = $idx = \'\';\n		$step = \'page_new\';\n	}\n\n	$lnk = eLink(\'page\', $step, $idx, $page, $title);\n	return ($wraptag) ? doTag($lnk, $wraptag, $class, \'\', $html_id) : $lnk;\n}\n\n// ------------------------\nfunction smd_tabber_edit_style($atts, $thing=NULL) {\n	global $smd_tabber_callstack, $event;\n\n	extract(lAtts(array(\n		\'name\'    => $event,\n		\'title\'   => \'Edit CSS\',\n		\'class\'   => \'\',\n		\'html_id\' => \'\',\n		\'wraptag\' => \'\',\n	),$atts));\n\n	$tab_prefix = get_pref(\'smd_tabber_tab_prefix\', \'tabber_\');\n\n	if (isset($atts[\'name\'])) {\n		$css = (strpos($name, $tab_prefix) !== false) ? $name : $tab_prefix.$name;\n	} else {\n		// Lookup the name of the stylesheet\n		$ev = (strpos($name, $tab_prefix) !== false) ? str_replace($tab_prefix, \'\', $name) : $name;\n		$css = (isset($smd_tabber_callstack[$ev])) ? $smd_tabber_callstack[$ev][\'style\'] : \'\';\n	}\n\n	$idx = \'name\';\n	$step = \'\';\n	if (!$css) {\n		$css = $idx = \'\';\n		$step = \'pour\';\n	}\n\n	$lnk = eLink(\'css\', $step, $idx, $css, $title);\n	return ($wraptag) ? doTag($lnk, $wraptag, $class, \'\', $html_id) : $lnk;\n}\n\nfunction smd_tabber_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'smd_tabber_all_pubs\' => \'All publishers\',\n		\'smd_tabber_area\' => \'Assign to area:\',\n		\'smd_tabber_cancel\' => \'[Cancel]\',\n		\'smd_tabber_choose\' => \'Select tab to edit:\',\n		\'smd_tabber_created\' => \'Tab created. Refresh to see it\',\n		\'smd_tabber_deleted\' => \'Tab deleted. Refresh to see changes\',\n		\'smd_tabber_exists\' => \'Tab already exists\',\n		\'smd_tabber_heading\' => \'Manage additional TXP tabs\',\n		\'smd_tabber_name\' => \'Tab name:\',\n		\'smd_tabber_need_area\' => \'Please supply a location for the tab\',\n		\'smd_tabber_need_name\' => \'Please supply a tab name\',\n		\'smd_tabber_new_area\' => \'New area >>\',\n		\'smd_tabber_new_tab\' => \'New tab\',\n		\'smd_tabber_page\' => \'Page template:\',\n		\'smd_tabber_parse_depth\' => \'Parse depth\',\n		\'smd_tabber_prefs\' => \'Preferences\',\n		\'smd_tabber_prefs_lbl\' => \'Tabber preferences\',\n		\'smd_tabber_prefs_some_explain\' => \'This is either a new installation or a different version\'.br.\'of the plugin to one you had before.\',\n		\'smd_tabber_prefs_some_opts\' => \'Click \"Install table\" to add or update the table\'.br.\'leaving all existing data untouched.\',\n		\'smd_tabber_prefs_some_tbl\' => \'Not all table info available.\',\n		\'smd_tabber_view_privs\' => \'View privileges:\',\n		\'smd_tabber_saved\' => \'Tab saved. Refresh to see changes\',\n		\'smd_tabber_sort_order\' => \'Sort order:\',\n		\'smd_tabber_style\' => \'Stylesheet:\',\n		\'smd_tabber_tab_name\' => \'Manage tabs\',\n		\'smd_tabber_tab_prefix\' => \'Page/Style prefix\',\n		\'smd_tabber_tab_privs\' => \'Permit tabs to be managed by\',\n		\'smd_tabber_tbl_installed\' => \'Table installed\',\n		\'smd_tabber_tbl_install_lbl\' => \'Install table\',\n		\'smd_tabber_tbl_not_installed\' => \'Table NOT installed\',\n		\'smd_tabber_tbl_not_removed\' => \'Table NOT removed\',\n		\'smd_tabber_tbl_removed\' => \'Table removed\',\n	);\n	return strtr($lang[$what], $atts);\n}', 'c9a34eb713aa1f0ba1520b7a326d902b', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_thumbnail', 0, 'Stef Dawson', 'https://stefdawson.com/', '0.4.0', 'Multiple image thumbnails of arbitrary dimensions', '<h1>smd_thumbnail</h1>\n\n<p><a href=\"https://github.com/Bloke/smd_thumbnail/releases\">Download</a> | <a href=\"https://packagist.org/packages/bloke/smd_thumbnail\">Packagist</a></p>\n\n<p>If you&#8217;re bored of one Textpattern thumbnail per image and don&#8217;t fancy using an auto-resizing script or relying on the browser to stretch your thumbnails, <code>smd_thumbnail</code> could be the answer! It allows you to create profiles for as many Textpattern thumbnail sizes as you like and will automatically create physical thumbnails at those dimensions for any/all of your images.</p>\n\n<p>Please report bugs and problems with this plugin at <a href=\"https://github.com/Bloke/smd_thumbnail/issues\">the GitHub project&#8217;s issues page</a>.</p>\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#features\">Features</a></li>\n	<li><a href=\"#install\">Upgrading and uninstallation</a></li>\n	<li><a href=\"#usage\">Usage</a></li>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#st\">smd_thumbnail tag</a></li>\n		<li><a href=\"#st_info\">smd_thumbnail_info tag</a></li>\n		<li><a href=\"#st_if\">smd_if_thumbnail tag</a></li>\n	</ul></li>\n	<li><a href=\"#history\">History</a></li>\n	<li><a href=\"#credits\">Authors/credits</a></li>\n</ul>\n\n<h2 id=\"features\">Features</h2>\n\n<ul>\n	<li>Assign any number of thumbnails to Textpattern&#8217;s images.</li>\n	<li>Batch assign thumbnails to existing images.</li>\n	<li>Set one thumbnail size as default.</li>\n	<li>Display any of the thumbnail sizes on your site.</li>\n	<li>Keep thumbs in sync with Textpattern&#8217;s thumbs if you wish.</li>\n	<li>Conditional thumbnail check available.</li>\n</ul>\n\n<h2 id=\"install\">Installing</h2>\n\n<p><strong>Requires Textpattern 4.6.0+</strong></p>\n\n<p>Download the plugin from either <a href=\"https://github.com/Bloke/smd_thumbnail/releases\">GitHub</a>, or the <a href=\"https://stefdawson.com/sw/plugins/smd_thumbnail\">software page</a>, paste the code into the Plugins administration panel, install and enable the plugin. Visit the <a href=\"https://forum.textpattern.io/viewtopic.php?id=34367\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>Note: When upgrading from v0.1x to v0.20, your preferences for &#8216;Keep Textpattern thumbnails in sync with default profile&#8217; will be removed. Please visit the &#8216;Setup&#8217; area to reinstate the behaviour you desire.</p>\n\n<p>Alternatively, this plugin can be installed using <a href=\"https://getcomposer.org\">Composer</a>:</p>\n\n<pre><code>$ composer require bloke/smd_thumbnail:*</code></pre>\n\n<h2 id=\"usage\">Usage</h2>\n\n<p>Visit the Images administration panel. Above the list of images is an area labelled <strong>Thumbnail profiles</strong>. Click it to reveal the control panel. You can toggle this panel open and closed by clicking the heading.</p>\n\n<p>You must define at least one profile to begin with, so click the <strong>New profile</strong> button. This will reveal edit boxes where you can enter:</p>\n\n<ul>\n	<li><strong>Name</strong>: some descriptive name for this thumbnail dimension. You are limited to usual non-weird characters in the name.</li>\n	<li><strong>Description</strong>: a brief summary of this thumbnail dimension, to jog your memory or inform other users of its purpose.</li>\n	<li><strong>Width</strong>: thumbnail width, in pixels. If left blank (or set to <code>0</code>) and height is specified, the width will be computed in proportion to the height.</li>\n	<li><strong>Height</strong>: thumbnail height, in pixels. If left blank (or set to <code>0</code>) and width is specified, the height will be computed in proportion to the width.</li>\n	<li><strong>Quality (%)</strong>: The quality of the resulting thumbnail. Generally only of use for <span class=\"caps\">JPEG</span> or 24-bit <span class=\"caps\">PNG</span> images. The higher the value the better the quality and the bigger the file size. Default: <code>75</code>.</li>\n	<li><strong>Crop</strong>: whether to crop the image when the thumbnail is created, or scale it to fit.</li>\n	<li><strong>Sharpen</strong>: whether to apply a sharpening filter to the thumbnail when it is created. Useful for very small images.</li>\n	<li><strong>Active</strong>: Enable/disable this thumbnail size so it will/won&#8217;t be automatically generated when the next image is uploaded. Click the checkbox to instantly switch the profile on/off. Click the &#8216;Active&#8217; word in the heading row to toggle the status of all profiles. Note that this setting governs how the <a href=\"#smd_thumb_create\">All sizes</a> dropdown entry interacts with the <em>Create</em> and <em>Delete</em> buttons.</li>\n	<li><strong>Default</strong>: set this thumbnail profile as the default. The default image will be displayed in the &#8216;Images&#8217; list and will also be used as the default size to be displayed using <code>&lt;txp:smd_thumbnail/&gt;</code>. If the option to <a href=\"#smd_thumb_prefs\">sync with Textpattern&#8217;s thumbs</a> is on, it will also become Textpattern&#8217;s standard (built-in) thumbnail size from this point forward.</li>\n</ul>\n\n<p>Once you&#8217;ve configured your profile, click &#8216;Save&#8217; to store it. You can always edit it again by clicking its name. Go ahead and create some more profiles for thumbnails of different sizes/parameters. If you wish to delete a profile, click the &#8216;x&#8217; button and confirm. <strong>All thumbnails associated with that profile will be deleted.</strong></p>\n\n<p>At this point, no thumbnails have been created. Let&#8217;s rectify that&#8230;</p>\n\n<h3>Creating thumbnails</h3>\n\n<p>Pick one of your images and click to edit it. Beneath the main picture you will see an area labelled &#8216;Create thumbnail&#8217;.</p>\n\n<p>Inside this area is a simple select list containing one entry for each thumbnail profile you have configured, plus the special entry &#8216;All sizes&#8217;. You can select one of these entries and click &#8216;Create&#8217; to create thumbnail(s) in the selected size or click &#8216;Delete&#8217; to remove the selected thumbnail(s).</p>\n\n<p>Once created, the thumbnails appear in the panel.</p>\n\n<p>If you select &#8216;All sizes&#8217; and click &#8216;Create&#8217;, you will get one thumbnail for each active profile. Similarly, if you click &#8216;Delete&#8217; any thumbnails stored against an active profile for the current image will be removed. By toggling certain thumbnail profiles on/off you can control which ones are created or deleted &#8211; by default:</p>\n\n<ul>\n	<li>When you upload a new image, thumbnails will be automatically created for any active profiles.</li>\n	<li>When you replace an existing image, thumbnails will only be automatically created for active profiles if thumbs <strong>do not</strong> already exist. This option can be altered with the <a href=\"#smd_thumb_prefs\">prefs</a>.</li>\n</ul>\n\n<p>Note that if you use Textpattern&#8217;s multi-edit tool to delete images, <strong>all</strong> corresponding thumbnails for the selected images (including inactive ones) will be removed.</p>\n\n<h3>Custom thumbnails</h3>\n\n<p>If you prefer to make your own thumbmails in an external program, you can still set up the profiles as normal but instead of clicking &#8216;Create&#8217; in the &#8216;Image edit&#8217; screen, you can pick a profile size from the dropdown and use the file picker facility &#8216;Browse&#8230;&#8217; to select a thumbnail from your hard drive. When you click &#8216;Upload&#8217; the image will be inserted against the selected profile.</p>\n\n<p>At any time you can click one of the thumbnails (or select a size from the dropdown) and choose for another image to replace the existing thumbnail &#8211; even ones that have been auto-created. Some things to note about this feature:</p>\n\n<ol>\n	<li>The thumbnail must be of the same file type as the original image or the upload will be ignored.</li>\n	<li>You can click a highlighted image to deselect it.</li>\n	<li>You cannot &#8216;Browse&#8230;&#8217; for an image (the file upload box is greyed out) unless you have chosen one profile.</li>\n</ol>\n\n<h3>Batch thumbnail creation</h3>\n\n<p>You can also create thumbnails en-masse. Click the &#8216;Tools&#8217; button (labelled &#8216;Setup&#8217; if you have sufficient rights) in the right hand corner of the smd_thumbnail control panel. From there you will see an area that allows you to create thumbnails for:</p>\n\n<ol>\n	<li>Only the selected images (the checkboxes next to each image).</li>\n	<li>All images of a particular category.</li>\n	<li>All images uploaded by a particular user.</li>\n	<li>All images.</li>\n</ol>\n\n<p>After selecting the desired option (and if you choose category or author, select one of the entries from the secondary list that apears), click &#8216;Create&#8217;. Once you confirm your action, the plugin will create thumbnails for every active profile for all images that match your criteria. This is a quick way to generate thumbs for your existing images. <strong>Note</strong> that if you choose the first (empty) category entry, then all images that have not been assigned a category are manipulated.</p>\n\n<p>Because of the nature of this tool &#8211; especially with large image pools &#8211; the process is done as a background task via <span class=\"caps\">AJAX</span>. After clicking the &#8216;Create&#8217; button (and confirming you are sure) a counter will appear in the top-right of the prefs panel showing how many images it has processed out of the total number of images in the set. When it&#8217;s done, if you then refresh your &#8216;Images&#8217; tab (e.g. click to the &#8216;Profiles&#8217; pane) you will see your chosen default thumbnails in the list &#8211; assuming you&#8217;ve set one as default.</p>\n\n<h3>Preferences</h3>\n\n<p>If you have sufficient privileges, in the lower portion of the &#8216;Setup&#8217; panel are some options that govern how thumbnails are created for all users. There is no &#8216;Save&#8217; action here: the checkmarks are stored in real-time and always reflect the current state.</p>\n\n<h4>Create smd_thumbnails from</h4>\n\n<p>Governs whether the smd_thumbnails are created from the full size image or its (Textpattern) thumbnail. If you have elected not to have Textpattern thumbnails created then it&#8217;ll probably not work very well! This preference applies:</p>\n\n<ol>\n	<li>When you use any of the batch tools to mass-produce smd_thumbnails.</li>\n	<li>If you upload a new image.</li>\n	<li>If you reupload a main image and &#8216;Recreate thumbnails on re-upload of main image&#8217; is checked.</li>\n</ol>\n\n<h4>Keep Textpattern thumbnails in sync with default profile</h4>\n\n<p><strong>This option only appears if you have set a profile as default.</strong></p>\n\n<p>Ordinarily, smd_thumbnail works independently of Textpattern&#8217;s thumbnails. If you disable the plugin you&#8217;ll see that everything goes back to how it was before you installed it. This is great because your hard-graft isn&#8217;t lost if you have uploaded your own thumbs. But, if you start creating thumbs with the plugin you will notice that Textpattern&#8217;s built in tags such as <code>&lt;txp:article_image thumbnail=&quot;1&quot; /&gt;</code> and <code>&lt;txp:thumbnail /&gt;</code> won&#8217;t work for newer thumbnails. This might be confusing so you can opt to have smd_thumbnail update your Textpattern thumbnails at the same time.</p>\n\n<p>By ticking either of the checkboxes, the plugin will track any changes <strong>to the default profile</strong> and mimic them with Textpattern&#8217;s thumbnails. This has a few effects worth highlighting&#8230;</p>\n\n<p><strong>When the &#8216;Creation&#8217; checkbox is ticked:</strong></p>\n\n<ol>\n	<li>If you batch create thumbs, upload a new image, or replace an existing image, a new Textpattern thumbnail will be created at the default size. Any existing thumbs will be overwritten.</li>\n	<li>If you change default profile, any Textpattern thumbnails that have been created will remain at the previous size. Only when you start creating, uploading or replacing images will the new thumbnail sizes be created.</li>\n</ol>\n\n<p><strong>When the &#8216;Deletion&#8217; checkbox is ticked:</strong></p>\n\n<ol>\n	<li>If you delete a single thumb that corresponds to the default profile, both the profile thumb and Textpattern&#8217;s thumbmail will be deleted.</li>\n	<li>If you delete an entire profile, all its thumbnails <strong>and all Textpattern&#8217;s thumbnails</strong> will be removed.</li>\n</ol>\n\n<p><strong>Further:</strong></p>\n\n<ol>\n	<li>If you have no default profile, the checkboxes disappear and the settings have no effect. Choose a default profile to reinstate the behaviour.</li>\n	<li>If you deactivate the default profile the checkboxes have no effect <strong>unless</strong> you delete the profile (since you&#8217;re deleting it, its status is &#8216;lost&#8217; and therefore the checkboxes function as normal). If there&#8217;s any doubt, uncheck the &#8216;Deletion&#8217; checkbox first!</li>\n</ol>\n\n<h4>Recreate thumbnails on re-upload of main image</h4>\n\n<p>When this checkbox is cleared and you <strong>replace</strong> a main image (from the &#8216;Image edit&#8217; screen) any thumbnails that have already been assigned to the image are left as they are &#8211; only missing thumbnails will be created from the main image. Conversely, if you set this checkbox, when you upload the replacement image <strong><span class=\"caps\">ALL</span> smd_thumbnails for active profiles will be (re)created from the main image.</strong></p>\n\n<p><strong>Note one important exception:</strong> Textpattern thumbnails are <strong><span class=\"caps\">NOT</span></strong> covered by this option &#8211; they are governed by the &#8216;Keep Textpattern thumbs in sync&#8217; &#8216;Creation&#8217; checkbox. Thus, if you upload a replacement image and the &#8216;Creation&#8217; checkbox is on, you will replace Textpattern&#8217;s thumbnail regardless of the setting of the &#8216;Recreate thumbnails&#8230;&#8217; option.</p>\n\n<h2 id=\"tags\">Tags</h2>\n\n<h3 id=\"st\">smd_thumbnail tag</h3>\n\n<pre><code>&lt;txp:smd_thumbnail /&gt;</code></pre>\n\n<p><strong>A direct replacement for the built in <a href=\"http://textpattern.net/wiki/index.php?title=thumbnail\">txp:thumbnail</a> tag with exactly the same functionality and attributes apart from the additional attributes listed below.</strong></p>\n\n<h4>Attributes (in addition to standard txp:thumbnail tag attributes)</h4>\n\n<ul>\n	<li><code>add_stamp=&quot;boolean&quot;</code><br />\nAdds the image file modification time to the end of the thumbnail&#8217;s <span class=\"caps\">URL</span>. Use <code>add_stamp=&quot;1&quot;</code> to switch this feature on. This helps prevent stale images, but may prevent browsers from cacheing the thumbnails properly, thus increasing bandwidth usage. Default: <code>0</code>.</li>\n	<li><code>class=&quot;class name&quot;</code><br />\n<span class=\"caps\">HTML</span> <code>class</code> to apply to the <code>wraptag</code> and/or <code>&lt;img&gt;</code> attribute value. If omitted, the name of the profile will be used as a <code>class</code> name for the <code>&lt;img&gt;</code> tag. If you specify a <code>wraptag</code> and omit the <code>class</code>, the profile name will be used as a <code>class</code> on both the container and the <code>&lt;img&gt;</code> tag.</li>\n	<li><code>display=&quot;value&quot;</code><br />\nBy default, this tag outputs a full <code>&lt;img&gt;</code> tag. If you just require the image <span class=\"caps\">URL</span> so you can make your own image tags, set <code>display=&quot;url&quot;</code>. Default: <code>thumbnail</code>.</li>\n	<li><code>force_size=&quot;value&quot;</code><br />\nUsually when you set one or other width/height to <code>0</code> in a profile, the browser scales the missing dimension automatically. It does this by omitting the <code>width=</code> or <code>height=</code> attribute in the <code>img</code> tag. This may cause visual artefacts as the page is rendered and the browser calculates the sizes. If you wish the plugin to add the actual dimension to the <code>&lt;img&gt;</code> tag (the size at the time the thumbnail was created), tell the plugin with this attribute. Choose one or both of <code>width</code> or <code>height</code>. Comma-separate as required. Default: unset.</li>\n	<li><code>form=&quot;form name&quot;</code><br />\nYou can construct your own <code>&lt;img&gt;</code> tags using the given form. If not specified, you may use the tag as a container.</li>\n	<li><code>type=&quot;value&quot;</code><br />\nUse this attribute to display thumbnails of the given profile name (e.g., <code>type=&quot;large&quot;</code>). If you do not specify this attribute, the default profile will be used. If there is no default profile you&#8217;ll see warning messages.</li>\n</ul>\n\n<p>The tag works inside <code>&lt;txp:images&gt;</code> or can be used standalone by specifying the <code>id</code> or <code>name</code> attribute. If using it as a container or with the <code>form</code> attribute you display the various pieces of thumbnail information using the <code>&lt;txp:smd_thumbnail_info&gt;</code> tag.</p>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:images&gt;\n    &lt;txp:smd_thumbnail type=&quot;big-size&quot; /&gt;\n&lt;/txp:images&gt;</code></pre>\n\n<p>Show all images that have been generated with the &#8216;big-size&#8217; smd_thumbnail profile.</p>\n\n<h5>Example 2: responsive images</h5>\n\n<p>See <a href=\"https://forum.textpattern.io/viewtopic.php?pid=288361#p288361\">responsive images post</a> in the Textpattern forum.</p>\n\n<h3 id=\"st_info\">smd_thumbnail_info tag</h3>\n\n<pre><code>&lt;txp:smd_thumbnail_info /&gt;</code></pre>\n\n<p>Display various information from the current <code>&lt;txp:smd_thumbnail&gt;</code> tag (in either its form or container).</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>class=&quot;class name&quot;</code><br />\n<span class=\"caps\">HTML</span> <code>class</code> to apply to the <code>wraptag</code> attribute value.</li>\n	<li><code>break=&quot;value&quot;</code><br />\nWhere value is an <span class=\"caps\">HTML</span> element, specified without brackets (e.g., <code>break=&quot;li&quot;</code>) to separate list items.</li>\n	<li><code>item=&quot;item value&quot;</code><br />\nList of things you wish to display. Choose from:\n	<ul>\n		<li><code>alt</code>: image alt text.</li>\n		<li><code>author</code>: image author.</li>\n		<li><code>category</code>: image category name.</li>\n		<li><code>category_title</code>: image category title.</li>\n		<li><code>class</code>: class applied to thumbnail (if not supplied, is same as type).</li>\n		<li><code>date</code> : raw datestamp of when the image was uploaded.</li>\n		<li><code>ext</code>: thumbnail file extension.</li>\n		<li><code>h</code>: thumbnail height (pixels).</li>\n		<li><code>html_h</code>: <span class=\"caps\">HTML</span>-formatted <code>height</code> attribute.</li>\n		<li><code>html_w</code>: <span class=\"caps\">HTML</span>-formatted <code>width</code> attribute.</li>\n		<li><code>id</code>: thumbnail ID.</li>\n		<li><code>name</code>: image name.</li>\n		<li><code>title</code>: image title/caption.</li>\n		<li><code>type</code>: thumbnail profile name.</li>\n		<li><code>url</code>: full image <span class=\"caps\">URL</span> of the thumbnail.</li>\n		<li><code>w</code>: thumbnail width (pixels).</li>\n	</ul></li>\n	<li><code>wraptag=&quot;element&quot;</code><br />\n<span class=\"caps\">HTML</span> element to wrap (markup) list block, specified without brackets (e.g., <code>wraptag=&quot;ul&quot;</code>).</li>\n</ul>\n\n<h5>Example</h5>\n\n<pre><code>&lt;txp:images&gt;\n    &lt;txp:smd_thumbnail type=&quot;big-size&quot;&gt;\n        &lt;meta itemprop=&quot;image&quot; content=&quot;&lt;txp:smd_thumbnail_info item=&quot;url&quot; /&gt;&quot;&gt;\n        &lt;meta itemprop=&quot;width&quot; content=&quot;&lt;txp:smd_thumbnail_info item=&quot;w&quot; /&gt;&quot;&gt;\n        &lt;meta itemprop=&quot;height&quot; content=&quot;&lt;txp:smd_thumbnail_info item=&quot;h&quot; /&gt;&quot;&gt;\n    &lt;/txp:smd_thumbnail&gt;\n&lt;/txp:images&gt;</code></pre>\n\n<h3 id=\"st_if\">smd_if_thumbnail tag</h3>\n\n<pre><code>&lt;txp:smd_if_thumbnail&gt;</code></pre>\n\n<p>A direct replacement for the built in <code>&lt;txp:if_thumbnail /&gt;</code> tag with exactly the same functionality.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>type</code>: use this attribute to check for thumbnails of the given profile name (e.g., <code>type=&quot;Medium&quot;</code>). If you do not specify this attribute, the default profile will be tested. If there is no default profile the tag will always render the <code>&lt;txp:else /&gt;</code> portion.</li>\n</ul>\n\n<h5>Example</h5>\n\n<pre><code>&lt;txp:images&gt;\n    &lt;txp:smd_if_thumbnail type=&quot;640w&quot;&gt;\n        &lt;img alt=&quot;&lt;txp:image_info type=&#39;alt&#39; /&gt;&quot;\n            src=&quot;&lt;txp:smd_thumbnail type=&#39;640w&#39; display=&#39;url&#39; /&gt;&quot;\n            srcset=&quot;&lt;txp:image_url link=&#39;0&#39; /&gt; 2x, &lt;txp:smd_thumbnail type=&#39;640w&#39; display=&#39;url&#39; /&gt; 1x&quot;&gt;\n    &lt;txp:else /&gt;\n        &lt;img alt=&quot;&lt;txp:image_info type=&#39;alt&#39; /&gt;&quot; src=&quot;&lt;txp:image_url link=&#39;0&#39; /&gt;&quot;&gt;\n    &lt;/txp:smd_if_thumbnail&gt;\n&lt;/txp:images&gt;</code></pre>\n\n<h2>How it works</h2>\n\n<p>For reference, when you create a profile a directory is created with that name inside your Textpattern <code>images</code> folder. Inside this folder you will find images of the format <code>id.ext</code>: where <code>id</code> and <code>ext</code> match the corresponding image IDs in the Textpattern database. That&#8217;s pretty much it!</p>\n\n<p>You can delete thumbnail files manually from any of these directories and the plugin will figure everything out. But it&#8217;s probably not advisable to delete the directories themselves &#8211; use the &#8216;Delete&#8217; buttons in the control panel for that.</p>\n\n<h2 id=\"history\">History</h2>\n\n<p>Please see the <a href=\"https://github.com/Bloke/smd_thumbnail/blob/master/CHANGELOG.textile\">changelog on GitHub</a>.</p>\n\n<h2 id=\"credits\">Authors/credits</h2>\n\n<p>Written by <a href=\"https://stefdawson.com/contact\">Stef Dawson</a>. Many thanks to <a href=\"https://github.com/Bloke/smd_thumbnail/graphs/contributors\">all additional contributors</a>. Special thanks also to the beta test crew who offered feature and workflow advice, especially thebombsite, jakob, jstubbs and maniqui.</p>', '/**\n * smd_thumbnail\n *\n * A Textpattern CMS plugin for managing multiple image thumbnails:\n *  -> Create unlimited thumbnail profiles of differing sizes for various site uses\n *  -> Thumbnail files are created in your image directory -- no realtime scaling\n *  -> Batch create / alter thumbnails\n *  -> Choose to sync the thumbnails with Textpattern\'s own thumbs\n *\n * @author Stef Dawson\n * @link   https://stefdawson.com/\n * TODO: Make Selections (lines 912-916) optional via prefs or something\n * TODO: Simplify AJAX response packets to speed things up\n * TODO: Interface elements for Profile creation don\'t get rendered if there are no images in pool\n */\nif (!defined(\'SMD_THUMB\')) {\n    define(\"SMD_THUMB\", \'smd_thumbnail\');\n}\nif (!defined(\'SMD_THUMB_ACTIVE\')) {\n    define(\"SMD_THUMB_ACTIVE\", 1);\n}\nif (!defined(\'SMD_THUMB_CROP\')) {\n    define(\"SMD_THUMB_CROP\", 2);\n}\nif (!defined(\'SMD_THUMB_SHARP\')) {\n    define(\"SMD_THUMB_SHARP\", 4);\n}\n\nif (txpinterface === \'admin\') {\n    global $smd_thumb_event, $smd_thumb_prevs;\n\n    $smd_thumb_event = \'smd_thumbnail\';\n    $smd_thumb_privs = \'1,2,3\'; // Plugin privs\n    $smd_thumb_prevs = array(\'1\'); // Privs for prefs\n\n    add_privs($smd_thumb_event, $smd_thumb_privs);\n    add_privs(\'smd_thumb_profiles\', $smd_thumb_privs);\n    register_callback(\'smd_thumb_welcome\', \'plugin_lifecycle.\'.$smd_thumb_event);\n    register_callback(\'smd_thumb_profiles\', \'image_ui\', \'extend_controls\');\n    register_callback(\'smd_thumbs\', \'image_ui\', \'thumbnail\');\n    register_callback(\'smd_thumb_edit\', \'image_ui\', \'thumbnail_edit\');\n    register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_image\');\n    register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_create\');\n    register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'image\');\n    register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'moderate\');\n    register_callback(\'smd_thumb_delete\', \'image_deleted\', \'image\');\n    register_callback(\'smd_thumb_create_one\', \'image\', \'smd_thumb_create_one\');\n    register_callback(\'smd_thumb_switch_active\', \'image\', \'smd_thumb_switch_active\');\n    register_callback(\'smd_thumb_switch_pref\', \'image\', \'smd_thumb_switch_pref\');\n    register_callback(\'smd_thumb_insert\', \'image\', \'smd_thumbnail_insert\', 1);\n    register_callback(\'smd_thumb_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    smd_thumb_set_impath();\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_thumbnail\')\n            ->register(\'smd_if_thumbnail\')\n            ->register(\'smd_thumbnail_info\');\n    }\n}\n\n/**\n * CSS definitions: hopefully kind to themers.\n */\nfunction smd_thumb_get_style_rules()\n{\n    $smd_thumb_styles = array(\n        \'smd_thumb\' =>\'\n.smd_selected { border-color: #0066ff; }\n.smd_hidden { display: none; }\n.smd_inactive td { opacity: 0.33; }\ninput.smd_thumbnail-create { margin: 0; }\n#smd_thumbs img { display: block; margin: 1em 0; cursor: pointer; }\n/* Legacy 4.6.x support */\n#smd_thumb_profiles { clear: both; }\n.txp-list--no-options { width: 100%; }\n.txp-details .txp-listtables { margin: 1em 0;}\n\'\n);\n\n    return $smd_thumb_styles;\n}\n\n/**\n * Inject the stylesheet rules into the matching panel.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_inject_css($evt, $stp)\n{\n    global $event;\n\n    if ($event === \'image\') {\n        $smd_thumb_styles = smd_thumb_get_style_rules();\n        echo \'<style type=\"text/css\">\', $smd_thumb_styles[\'smd_thumb\'], \'</style>\';\n    }\n\n    return;\n}\n\n/**\n * Kickstart the plugin after installation/activation/deletion.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_welcome($evt, $stp)\n{\n    switch ($stp) {\n        case \'installed\':\n            smd_thumb_table_install();\n            // Remove per-user prefs on upgrade from v0.1x to v0.20.\n            safe_delete (\'txp_prefs\', \"name IN (\'smd_thumb_txp_create\', \'smd_thumb_txp_delete\', \'smd_thumb_auto_replace\') AND user_name != \'\'\");\n            break;\n        case \'deleted\':\n            smd_thumb_table_remove();\n            break;\n    }\n\n    return;\n}\n\n/**\n * Display the designated default thumbnail on the list page.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumbs($evt, $stp, $dflt, $currimg)\n{\n    extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    if (smd_thumb_table_exist()) {\n        $default = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n        if ($default) {\n            $row = safe_row(\'*\', SMD_THUMB, \"name=\'\".sanitizeForUrl($default).\"\'\");\n\n            if ($row) {\n                $edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$currimg[\'id\'].a.\'sort=\'.$sort.\n                        a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n                $out = smd_thumb_img($row, $currimg, array(\'class\' => \'content-image \'.$default));\n\n                return ($out) ? href($out, $edit_url) : gTxt(\'no\');\n            } else {\n                return gTxt(\'no\');\n            }\n        } else {\n            return gTxt(\'no\');\n        }\n    }\n}\n\n/**\n * Don\'t want the \'create\' controls or thumbnail image as they\'re both handled in the edit portion of the screen.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumb_empty($evt, $stp, $dflt, $currimg)\n{\n    return \' \';\n}\n\n/**\n * Create a bunch of thumbnails en masse.\n *\n * With a lot of images in the database, doing it all in one hit would time out.\n * Thus it\'s done via ajax; one request per image.\n *\n * @param  string $type Flavour of thumbnail creation: by (usr), by (cat), by (sel)ection, or (all)\n * @param  string $lst  List of images to operate upon\n * @return [type]       [description]\n */\nfunction smd_thumb_create_group($type, $lst = \'\')\n{\n    switch ($type) {\n        case \'all\':\n            $where = \'1=1\';\n            break;\n        case \'sel\':\n            $where = ($lst) ? \'id IN (\'.doSlash($lst).\')\' : \'1=0\';\n            break;\n        case \'cat\':\n            $where = \"category in (\'\".doSlash($lst).\"\')\";\n            break;\n        case \'usr\':\n            $where = \"author in (\'\".doSlash($lst).\"\')\";\n            break;\n    }\n\n    $images = safe_column(\'id\', \'txp_image\', $where);\n    $count = count($images);\n\n    echo <<<EOJS\n<script type=\"text/javascript\">\nvar bctr = 0; var btot = {$count};\njQuery(function() {\n    jQuery(\"#smd_thumb_btot\").text(\"/\'.$count.\'\");\nEOJS;\n\n    foreach ($images as $img) {\n        echo <<<EOJS\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_create_one\',\n        smd_thumb_imgid: {$img}\n    }, smd_create_group_feedback);\nEOJS;\n    }\n    echo \'});\';\n    echo <<<EOJS\nfunction smd_create_group_feedback() {\n    bctr++;\n    jQuery(\'#smd_thumb_bcurr\').text(bctr);\n}\nEOJS;\n    echo \'</script>\';\n}\n\n/**\n * AJAX method to create one thumbnail for each active profile, from the passed ID.\n */\nfunction smd_thumb_create_one()\n{\n    $currimg = gps(\'smd_thumb_imgid\');\n    assert_int($currimg);\n\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \' . SMD_THUMB_ACTIVE);\n    $curr = safe_row(\'*\', \'txp_image\', \"id=\" . doSlash($currimg));\n\n    if ($rs) {\n        smd_thumb_make($rs, $curr, 1);\n        send_xml_response();\n    }\n}\n\n/**\n * AJAX method to set the active thumbnail profile.\n *\n * Requires GET/POST params:\n *  -> smd_thumb_profile TName of the profile to set as active\n */\nfunction smd_thumb_switch_active()\n{\n    $name = doSlash(gps(\'smd_thumb_profile\'));\n\n    if ($name) {\n        safe_update(SMD_THUMB, \'flags = flags ^ \' . SMD_THUMB_ACTIVE, \"name=\'$name\'\");\n        send_xml_response(array(\'smd_thumb_profile\' => $name));\n    }\n}\n\n/**\n * AJAX method to toggle the given preference name to the given state.\n *\n * Requires GET/POST params:\n *  -> smd_thumb_txptype Preference name (without plugin prefix)\n *  -> smd_thumb_state   Preference value to store\n */\nfunction smd_thumb_switch_pref()\n{\n    $name = doSlash(gps(\'smd_thumb_txptype\'));\n    $state = doSlash(gps(\'smd_thumb_state\'));\n\n    if ($name) {\n        set_pref(\'smd_thumb_\'.$name, $state, \'smd_thumb\', PREF_HIDDEN, \'text_input\');\n        send_xml_response();\n    }\n}\n\n/**\n * Wrapper to create thumbnail for active profiles from uploaded image.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n * @param  string $id  New image identifier. If omitted, will try GET/POST, then Textpattern\'s GLOBALS[\'ID\']\n */\nfunction smd_thumb_generate($evt, $stp, $id = \'\')\n{\n    // Catch situations where the plugin is installed without lifecycle on a\n    // new install where no images exist. Thus the first time the plugin is\n    // invoked is here when the first image is uploaded.\n    if (!smd_thumb_table_exist()) {\n        smd_thumb_table_install();\n    }\n\n    $id = ($id) ? $id : gps(\'id\');\n    $id = ($id) ? $id : $GLOBALS[\'ID\'];\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \' . SMD_THUMB_ACTIVE);\n\n    smd_thumb_make($rs, $id);\n}\n\n/**\n * Wrapper to delete a bunch of selected thumbnails.\n *\n * Uses GET/POST parameters:\n *  -> selected Comma-separated list of selected image identifiers\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_delete($evt, $stp)\n{\n    $ids = gps(\'selected\');\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1\');\n\n    $images = safe_rows(\'*\', \'txp_image\', \'id IN (\' . join(\',\',quote_list($ids)) . \')\');\n\n    foreach ($images as $img) {\n        smd_thumb_unmake($rs, $img);\n    }\n}\n\n/**\n * Create a thumbnail.\n *\n * @param  array  $rs       Record set containing image meta data\n * @param  int    $currimg  Identifier for the current image being operated upon\n * @param  bool   $force    Whether to always create the thumbnail, even if it exists\n * @return string           Feedback message\n */\nfunction smd_thumb_make($rs, $currimg, $force = 0)\n{\n    // Wrapper for wet_thumb to allow multiple thumbnails.\n    $msg = \'\';\n    smd_thumb_set_impath();\n\n    if (!class_exists(\'smd_thumb\')) {\n        class smd_thumb extends wet_thumb\n        {\n            var $m_ext;\n            var $m_id;\n            var $m_dir;\n            var $m_dflt;\n            var $width;\n            var $height;\n            var $force;\n\n            /**\n             * Constructor.\n             *\n             * @param int     $id         Unique image identifier\n             * @param string  $dir        Subdirectory in which to store the image\n             * @param array   $img_row    Image meta data to store\n             * @param boolean $is_default Whether the thumbnail is to become the default\n             * @param string  $pro_w      Thumbnail width\n             * @param string  $pro_h      Thumbnail height\n             * @param boolean $force      Whether to overwrite any previous thumbnail\n             */\n            public function __construct ($id, $dir, $img_row, $is_default = false, $pro_w = \'\', $pro_h = \'\', $force = 0)\n            {\n                $id = assert_int($id);\n\n                if ($img_row) {\n                    extract($img_row);\n                    $this->m_id = $id;\n                    $this->m_ext = $ext;\n                    $this->m_dir = $dir;\n                    $this->force = $force;\n                    $this->width = $pro_w;\n                    $this->height = $pro_h;\n                    $this->m_dflt = $is_default;\n                }\n\n                parent::__construct();\n            }\n\n            /**\n             * Store the thumbnail image on disk.\n             *\n             * @return bool\n             */\n            public function write_image()\n            {\n                if (!isset($this->m_ext)) {\n                    return false;\n                }\n\n                $autorep = get_pref(\'smd_thumb_auto_replace\', \'0\');\n                $recfrom = get_pref(\'smd_thumb_create_from\', \'full\');\n                $src_sz = ($recfrom === \'full\') ? \'\' : \'t\';\n                $infile = IMPATH . $this->m_id . $src_sz . $this->m_ext;\n                $outfile = IMPATH . $this->m_dir . DS . $this->m_id . $this->m_ext;\n\n                // If we\'re trying to create from Txp thumbnail but it doesn\'t exist,\n                // fall back to creating from full image.\n                if (!file_exists($infile) && $src_sz === \'t\') {\n                    $infile = IMPATH . $this->m_id . $this->m_ext;\n                }\n\n                if (!file_exists($outfile) || $autorep || $this->force) {\n                    // If this is the default profile and the pref indicates, write a Textpattern thumb too.\n                    if (($this->m_dflt === true) && (get_pref(\'smd_thumb_txp_create\', \'0\'))) {\n                        $txp_thumb = IMPATH . $this->m_id . \'t\' . $this->m_ext;\n\n                        if (parent::write($infile, $txp_thumb)) {\n                            safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $this->width, thumb_h = $this->height\", \'id = \' . $this->m_id);\n                            @chmod($outfile, 0644);\n                        }\n                    }\n\n                    if (parent::write ($infile, $outfile)) {\n                        @chmod($outfile, 0644);\n\n                        return true;\n                    }\n                }\n\n                return false;\n            }\n        }\n    }\n\n    // If passed only an ID, look up the rest of the image data.\n    if (!is_array($currimg)) {\n        assert_int($currimg);\n        $currimg = safe_row(\'*\', \'txp_image\', \'id=\' . $currimg);\n    }\n\n    // Create each thumbnail.\n    $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\n\n    foreach ($rs as $row) {\n        // Sanitize a little.\n        $width = (int) $row[\'width\'];\n        $height = (int) $row[\'height\'];\n\n        if ($width === 0) {\n            $width = \'\';\n        }\n\n        if ($height === 0) {\n            $height = \'\';\n        }\n\n        if ($width === \'\' && $height === \'\') {\n            continue;\n        }\n\n        $crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\n        $sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\n        $id = $currimg[\'id\'];\n        $is_dflt = ($row[\'name\'] === $pro_dflt) ? true : false;\n\n        $t = new smd_thumb($id, sanitizeForUrl($row[\'name\']), $currimg, $is_dflt, $width, $height, $force);\n        $t->extrapolate = true; // Allow bigger thumbs than original image.\n        $t->crop = ($crop === 1);\n        $t->sharpen = ($sharpen === 1);\n        $t->hint = \'0\';\n        $t->width = $width;\n        $t->height = $height;\n        $t->quality = $row[\'quality\'];\n\n        if ($t->write_image()) {\n            $msg = gTxt(\'thumbnail_saved\', array(\'{id}\' => $id));\n        } else {\n            $msg = array(gTxt(\'thumbnail_not_saved\', array(\'{id}\' => $id)), E_ERROR);\n        }\n    }\n\n    return $msg;\n}\n\n/**\n * Delete the passed set of thumbnails.\n *\n * @param  array  $rs      Sert of image identifiers to delete\n * @param  int    $currimg Current image being operated upon\n */\nfunction smd_thumb_unmake($rs, $currimg)\n{\n    $id = $currimg[\'id\'];\n    $ext = $currimg[\'ext\'];\n    $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\n    $txp_del = get_pref(\'smd_thumb_txp_delete\', \'0\');\n    smd_thumb_set_impath();\n\n    foreach ($rs as $row) {\n        $path = IMPATH . sanitizeForUrl($row[\'name\']) . DS . $id . $ext;\n\n        if (file_exists($path)) {\n            unlink($path);\n        }\n\n        // Also remove Txp\'s built-in thumb?\n        if (($row[\'name\'] === $pro_dflt) && ($txp_del == \'1\')) {\n            $path = IMPATH . $id . \'t\' . $ext;\n\n            if (file_exists($path)) {\n                safe_update(\'txp_image\', \"thumbnail = 0, thumb_w = 0, thumb_h = 0\", \'id = \' . $id);\n                unlink($path);\n            }\n        }\n    }\n\n    return \'\';\n}\n\n/**\n * Insert a thumbnail when a new file is uploaded.\n */\nfunction smd_thumb_insert()\n{\n    global $txpcfg, $txp_user, $page, $sort, $dir, $crit, $search_method;\n\n    smd_thumb_set_impath();\n    extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    include_once txpath.\'/lib/txplib_misc.php\';\n\n    extract($txpcfg);\n    $id = assert_int(gps(\'id\'));\n    $profile = gps(\'smd_thumb_profile\');\n    $thumb_ext = gps(\'smd_thumb_ext\');\n\n    $author = fetch(\'author\', \'txp_image\', \'id\', $id);\n\n    if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n        return;\n    }\n\n    $file = $_FILES[\'thefile\'][\'tmp_name\'];\n    $name = $_FILES[\'thefile\'][\'name\'];\n\n    $file = get_uploaded_file($file);\n\n    if (empty($file)) {\n        return;\n    }\n\n    list($w, $h, $extension) = getimagesize($file);\n\n    $valid_exts = array(\n        IMAGETYPE_GIF  => \'.gif\',\n        IMAGETYPE_JPEG => \'.jpg\',\n        IMAGETYPE_PNG  => \'.png\',\n    );\n\n    $ext = isset($valid_exts[$extension]) ? $valid_exts[$extension] : \'\';\n\n    if (($file !== false) && $profile && $ext) {\n        $newpath = IMPATH . sanitizeForUrl($profile) . DS . $id . $ext;\n        if (shift_uploaded_file($file, $newpath) === false) {\n            // Failed: do nothing.\n        } else {\n            @chmod($newpath, 0644);\n\n            // If the pref indicates, duplicate as a Textpattern thumb too.\n            if (get_pref(\'smd_thumb_txp_create\', \'0\') == 1) {\n                $txp_thumb = IMPATH . $id . \'t\' . $ext;\n\n                if (copy($newpath, $txp_thumb)) {\n                    safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $w, thumb_h = $h\", \'id = \' . $id);\n                    @chmod($txp_thumb, 0644);\n                }\n            }\n\n            $message = gTxt(\'image_uploaded\', array(\'{name}\' => $name));\n        }\n    }\n\n    // Since the headers have been sent, resort to JavaScript to refresh the page.\n    $urlPieces = array(\n        \'event\'         => \'image\',\n        \'step\'          => \'image_edit\',\n        \'id\'            => $id,\n        \'sort\'          => $sort,\n        \'dir\'           => $dir,\n        \'page\'          => $page,\n        \'search_method\' => $search_method,\n        \'crit\'          => $crit,\n    );\n\n    $url = html_entity_decode(join_qs($urlPieces));\n\n    echo <<<EOS\n<script type=\"text/javascript\">\nwindow.location.href=\"{$url}\";\n</script>\n<noscript>\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\n</noscript>\nEOS;\n    exit;\n}\n\n/**\n * pluggable_ui callback to render the additions to the image edit panel.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumb_edit($evt, $stp, $dflt, $currimg)\n{\n    global $step, $file_max_upload_size, $txp_user;\n\n    extract(gpsa(array(\n        \'id\',\n        \'page\',\n        \'sort\',\n        \'dir\',\n        \'crit\',\n        \'search_method\',\n        \'smd_thumbnail_size\',\n        \'smd_thumbnail_chosen_size\',\n        \'smd_thumbnail_delete\',\n        \'smd_step\'\n    )));\n\n    $id = ($id) ? $id : $GLOBALS[\'ID\'];\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    // Toggle profile panel.\n    if ($step === \'save_pane_state\') {\n        smd_thumbnail_save_pane_state();\n        return;\n    }\n\n    // Create/delete the selected thumbs depending on the button pressed.\n    if ($smd_step === \'smd_thumbnail_manage\') {\n        // Validate user.\n        $author = fetch(\'author\', \'txp_image\', \'id\', $id);\n\n        if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n            image_list(gTxt(\'restricted_area\'));\n\n            return;\n        }\n\n        // Grab the thumbnails to work on.\n        $where = ($smd_thumbnail_size === \'all\') ? \'1=1 AND flags & \' . SMD_THUMB_ACTIVE : \"name=\'\" . doSlash($smd_thumbnail_chosen_size) . \"\'\";\n        $rs = safe_rows(\'*\', SMD_THUMB, $where);\n\n        // Do it.\n        if ($smd_thumbnail_delete) {\n            $msg = smd_thumb_unmake($rs, $currimg);\n        } else {\n            $msg = smd_thumb_make($rs, $currimg, 1);\n        }\n    }\n\n    $ext = $currimg[\'ext\'];\n    echo script_js(<<<EOC\nfunction smd_thumb_selector(sel) {\n    var idx = 0;\nconsole.log(sel);\n    jQuery(\"#smd_thumbs img\").each(function() {\n        if (jQuery(this).hasClass(sel)) {\n            jQuery(this).toggleClass(\'smd_selected active\');\n            if (jQuery(this).hasClass(\'smd_selected\')) {\n                jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\n                jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', false);\n                jQuery(\"#smd_thumb_profile\").val(sel);\n                idx = jQuery(\"#smd_thumbnail_size option[value=\'\"+sel+\"\']\").index();\n                jQuery(\"#smd_thumbnail_chosen_size\").val(sel);\n            } else {\n                jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n                jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n                jQuery(\"#smd_thumb_profile\").val(\'\');\n                jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\n            }\n        } else {\n            jQuery(this).removeClass(\'smd_selected active\');\n        }\n    });\n    jQuery(\"#smd_thumbnail_size\").prop(\"selectedIndex\", idx);\n}\nfunction smd_thumb_select_changed() {\n    obj = jQuery(\"#smd_thumbnail_size\");\n    if (obj.attr(\"selectedIndex\") == 0) {\n        jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n        jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n        jQuery(\"#smd_thumb_profile\").val(\'\');\n        jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\n    } else {\n        jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\n        jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', false);\n        jQuery(\"#smd_thumb_profile\").val(obj.val());\n        jQuery(\"#smd_thumbnail_chosen_size\").val(obj.val());\n    }\n    jQuery(\"#smd_thumbs img\").each(function() {\n        if (jQuery(this).hasClass(obj.val())) {\n            jQuery(this).addClass(\'smd_selected active\');\n        } else {\n            jQuery(this).removeClass(\'smd_selected active\');\n        }\n    });\n}\njQuery(function() {\n    jQuery(\"#smd_thumbs img\").each(function() {\n        var prf = jQuery(this).data(\'profile\');\n        jQuery(this).click(function() {\n            smd_thumb_selector(prf);\n        });\n    });\n    jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n    jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n    jQuery(\".smd_thumbnail-upload\").prepend(\'<input type=\"hidden\" name=\"smd_thumb_imgid\" value=\"{$id}\" /><input type=\"hidden\" id=\"smd_thumb_profile\" name=\"smd_thumb_profile\" value=\"\" /><input type=\"hidden\" id=\"smd_thumb_ext\" name=\"smd_thumb_ext\" value=\"{$ext}\" />\');\n});\nEOC\n    );\n\n    // Add thumbnails and creation controls.\n    if (smd_thumb_table_exist()) {\n        $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\n\n        if ($rs) {\n            $profiles = array(\'all\' => gTxt(\'smd_thumb_all_sizes\'));\n            $thumbs[] = \'<div id=\"smd_thumbs\">\';\n\n            foreach ($rs as $row) {\n                if ($row[\'flags\'] & SMD_THUMB_ACTIVE) {\n                    $profiles[$row[\'name\']] = $row[\'name\'];\n                }\n\n                $thumbs[] = smd_thumb_img($row, $currimg, array(\n                    \'class\' => \'content-image \' . $row[\'name\'],\n                    \'data-profile\' => $row[\'name\'],\n                    ));\n            }\n\n            $thumbs[] = \'</div>\';\n\n            $qs = array(\n                \"event\"         => \'image\',\n                \"step\"          => \'image_edit\',\n                \"id\"            => $id,\n                \"page\"          => $page,\n                \"sort\"          => $sort,\n                \"dir\"           => $dir,\n                \"crit\"          => $crit,\n                \"search_method\" => $search_method,\n            );\n\n            $out[] = upload_form(gTxt(\'smd_thumb_upload\'), \'\', \'smd_thumbnail_insert\', \'image\', $id, $file_max_upload_size, \'smd_upload_thumbnail\', \'smd_thumbnail-upload\');\n            $out[] = \'<form name=\"smd_thumbnail_create\" method=\"post\" action=\"\'.join_qs($qs).\'\">\'.n.\'<p>\';\n            $out[] = fInput(\'hidden\', \'smd_step\', \'smd_thumbnail_manage\');\n            $out[] = fInput(\'hidden\', \'smd_thumbnail_chosen_size\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumbnail_chosen_size\');\n            $out[] = selectInput(\'smd_thumbnail_size\', $profiles, \'\', \'\', \' onchange=\"return smd_thumb_select_changed()\";\', \'smd_thumbnail_size\');\n            $out[] = fInput(\'submit\', \'\', gTxt(\'create\'), \'smd_thumbnail-create\');\n            $out[] = fInput(\'submit\', \'smd_thumbnail_delete\', gTxt(\'delete\'), \'smd_thumbnail-delete\');\n            $out[] = join(\'\', $thumbs);\n            $out[] = \'</p>\'.n.\'</form>\';\n\n            return join(n, $out);\n        }\n    }\n\n    return \' \';\n}\n\n/**\n * Create an &ltimg&gt tag to a thumbnail.\n *\n * @param  array  $row     Thumbnail information\n * @param  array  $currimg Corresponding image information\n * @param  array  $meta    Thumbnail meta info\n * @param  string $dsp     Textpattern Form that contains rendering information\n * @return string\n */\nfunction smd_thumb_img($row, $currimg, $meta = array(), $dsp = \'\')\n{\n    global $img_dir, $smd_thumb_data;\n\n    smd_thumb_set_impath();\n\n    $dir = sanitizeForUrl($row[\'name\']);\n    $id = $currimg[\'id\'];\n    $ext = $currimg[\'ext\'];\n    $alt = $currimg[\'alt\'];\n\n    // alt is a mandatory attribute so make sure it exists (even if it\'s \"\").\n    if (!isset($meta[\'alt\'])) {\n        $meta[\'alt\'] = $currimg[\'alt\'];\n    }\n\n    $path = IMPATH . $dir . DS . $id . $ext;\n\n    if (file_exists($path)) {\n        $extras = \'\';\n\n        if (isset($meta[\'forcew\']) || isset($meta[\'forceh\'])) {\n            $dims = getimagesize($path);\n\n            if (isset($meta[\'forcew\']) && !$row[\'width\']) {\n                $row[\'width\'] = $dims[0];\n            }\n\n            if (isset($meta[\'forceh\']) && !$row[\'height\']) {\n                $row[\'height\'] = $dims[1];\n            }\n\n            unset($meta[\'forcew\']);\n            unset($meta[\'forceh\']);\n        }\n\n        if (isset($meta[\'width\']) && $meta[\'width\'] !== \'\') {\n            $row[\'width\'] = $meta[\'width\'];\n        }\n\n        if (isset($meta[\'height\']) && $meta[\'height\'] !== \'\') {\n            $row[\'height\'] = $meta[\'height\'];\n        }\n\n        unset($meta[\'width\']);\n        unset($meta[\'height\']);\n\n        // \'empty\' includes zero dimensions which omits the attribute and allows the browser to scale.\n        $w = (!empty($row[\'width\'])) ? \' width=\"\'.$row[\'width\'].\'\"\': \'\';\n        $h = (!empty($row[\'height\'])) ? \' height=\"\'.$row[\'height\'].\'\"\': \'\';\n\n        $uDate = \'\';\n\n        if (!isset($meta[\'stamp\'])) {\n            $uDate = \'?\' . filemtime($path);\n        }\n\n        unset($meta[\'stamp\']);\n\n        if (!isset($meta[\'class\'])) {\n            $meta[\'class\'] = $dir;\n        }\n\n        foreach ($meta as $key => $val) {\n            // We need all atts for container tags, but only valid HTML atts should appear in the default <img> tag.\n            if (in_array($key, array(\'alt\', \'class\', \'title\')) || strpos($key, \'data-\') === 0) {\n                $extras .= \' \'.$key.\'=\"\'.$val.\'\"\';\n            }\n        }\n\n        if ($dsp) {\n            // Hand off to the form/container for formatting advice.\n            $smd_thumb_data = $meta;\n            $smd_thumb_data[\'id\'] = $id;\n            $smd_thumb_data[\'ext\'] = $ext;\n            $smd_thumb_data[\'alt\'] = $alt;\n            $smd_thumb_data[\'w\'] = $row[\'width\'];\n            $smd_thumb_data[\'h\'] = $row[\'height\'];\n            $smd_thumb_data[\'html_w\'] = $w;\n            $smd_thumb_data[\'html_h\'] = $h;\n            return parse($dsp);\n        } else {\n            return \'<img src=\"\'.ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext.$uDate.\'\"\'.$w.$h.$extras.\'>\';\n        }\n    }\n\n    return \'\';\n}\n\n/**\n * Callback to render profiles control area on Images panel.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Texpattern step (action)\n * @param  string $dflt    Default content HTML\n * @param  array  $imglist Record set of all images in the list\n * @return string          HTML\n */\nfunction smd_thumb_profiles($evt, $stp, $dflt, $imglist)\n{\n    global $smd_thumb_event, $step, $smd_thumb_prevs, $txp_user;\n\n    if (!has_privs(__FUNCTION__)) {\n        return;\n    }\n\n    extract(gpsa(array(\n        \'page\',\n        \'sort\',\n        \'dir\',\n        \'crit\',\n        \'search_method\',\n        \'smd_thumb_add\',\n        \'smd_thumb_cancel\',\n        \'smd_thumb_save\',\n        \'smd_thumb_name\',\n        \'smd_thumb_newname\',\n        \'smd_thumb_add_newname\',\n        \'smd_thumb_description\',\n        \'smd_thumb_add_description\',\n        \'smd_thumb_width\',\n        \'smd_thumb_add_width\',\n        \'smd_thumb_height\',\n        \'smd_thumb_add_height\',\n        \'smd_thumb_quality\',\n        \'smd_thumb_add_quality\',\n        \'smd_thumb_active\',\n        \'smd_thumb_active_new\',\n        \'smd_thumb_add_active_new\',\n        \'smd_thumb_crop\',\n        \'smd_thumb_add_crop\',\n        \'smd_thumb_sharpen\',\n        \'smd_thumb_add_sharpen\',\n        \'smd_thumb_default\',\n        \'smd_thumb_add_default\',\n        \'smd_thumb_selected\',\n        \'smd_thumb_cat_selected\',\n        \'smd_thumb_usr_selected\',\n        \'smd_thumb_group_type\',\n    )));\n\n    if ($smd_thumb_add) {\n        $smd_thumb_newname = $smd_thumb_add_newname;\n        $smd_thumb_description = $smd_thumb_add_description;\n        $smd_thumb_width = $smd_thumb_add_width;\n        $smd_thumb_height = $smd_thumb_add_height;\n        $smd_thumb_quality = $smd_thumb_add_quality;\n        $smd_thumb_active_new = $smd_thumb_add_active_new;\n        $smd_thumb_crop = $smd_thumb_add_crop;\n        $smd_thumb_sharpen = $smd_thumb_add_sharpen;\n        $smd_thumb_default = $smd_thumb_add_default;\n    }\n\n    // Sanitize.\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n    $quality = (is_numeric($smd_thumb_quality)) ? (($smd_thumb_quality < 0) ? 75 : (($smd_thumb_quality > 100) ? 75 : $smd_thumb_quality) ) : 75;\n    $width = (is_numeric($smd_thumb_width)) ? $smd_thumb_width : 0;\n    $height = (is_numeric($smd_thumb_height)) ? $smd_thumb_height : 0;\n    $newname = sanitizeForUrl($smd_thumb_newname);\n    $msg = \'\';\n\n    smd_thumb_set_impath();\n\n    // Table control.\n    if (gps(\'remove\')) {\n        smd_thumb_table_remove();\n    } else {\n        if (!smd_thumb_table_exist()) {\n            smd_thumb_table_install();\n        }\n    }\n\n    // Action to toggle profile panel.\n    if ($step === \'save_pane_state\') {\n        smd_thumbnail_save_pane_state();\n        return;\n    }\n\n    // Action to create thumbnails.\n    if ($step === \'smd_thumb_create_group\') {\n        switch ($smd_thumb_group_type) {\n            case \'all\':\n                smd_thumb_create_group(\'all\');\n                break;\n            case \'sel\':\n                smd_thumb_create_group(\'sel\', rtrim($smd_thumb_selected, \',\'));\n                break;\n            case \'cat\':\n                smd_thumb_create_group(\'cat\', $smd_thumb_cat_selected);\n                break;\n            case \'usr\':\n                smd_thumb_create_group(\'usr\', $smd_thumb_usr_selected);\n                break;\n        }\n\n        set_pref(\'pane_smd_thumb_group_type\', $smd_thumb_group_type, \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n        $step = \'smd_thumb_prefs\';\n    }\n\n    // Action to save profile.\n    if ($step === \'smd_thumb_profile_save\') {\n        if (smd_thumb_table_exist()) {\n            if ($smd_thumb_cancel) {\n                // Do nothing.\n            } elseif ($smd_thumb_add) {\n                // Create new profile.\n                $exists = safe_row(\'*\', SMD_THUMB, \"name=\'\" . doSlash($newname) . \"\'\");\n\n                if ($exists) {\n                    $msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\n                } else {\n                    if ($newname) {\n                        $flags = 0;\n                        $flags = ($smd_thumb_active_new) ? $flags | SMD_THUMB_ACTIVE : $flags;\n                        $flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\n                        $flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\n\n                        safe_insert(\n                            SMD_THUMB,\n                            \"name=\'\" . doSlash($newname) . \"\'\"\n                            . \", description=\'\" . doSlash($smd_thumb_description) . \"\'\"\n                            . \", width=\'\" . doSlash($width) . \"\'\"\n                            . \", height=\'\" . doSlash($height) . \"\'\"\n                            . \", quality=\'\" . doSlash($quality) . \"\'\"\n                            . \", flags=\'\" . doSlash($flags) . \"\'\"\n                        );\n\n                        // Alter the default thumbnail.\n                        if ($smd_thumb_default) {\n                            set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\n                        }\n\n                        // Create the thumbnail directory with the same permissions as\n                        // the parent (images) directory.\n                        // Requires much back-and-forth number conversion.\n                        @mkdir(IMPATH.$newname);\n                        @chmod(IMPATH.$newname, octdec(decoct(fileperms(IMPATH) & 0777)));\n                    }\n                }\n            } else {\n                // Update existing profile.\n                $name = sanitizeForUrl($smd_thumb_name);\n                $exists = safe_row(\'*\', SMD_THUMB, \"name=\'\" . doSlash($newname) . \"\'\");\n\n                if ($exists && strtolower($newname) !== strtolower($name)) {\n                    $msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\n                } else {\n                    if ($newname) {\n                        $flags = 0;\n                        $flags = ($smd_thumb_active) ? $flags | SMD_THUMB_ACTIVE : $flags;\n                        $flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\n                        $flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\n\n                        safe_update(\n                            SMD_THUMB,\n                            \"name=\'\" . doSlash($newname) . \"\'\"\n                            . \", description=\'\" . doSlash($smd_thumb_description) . \"\'\"\n                            . \", width=\'\" . doSlash($width) . \"\'\"\n                            . \", height=\'\" . doSlash($height) . \"\'\"\n                            . \", quality=\'\" . doSlash($quality) . \"\'\"\n                            . \", flags=\'\" . doSlash($flags) . \"\'\"\n                            , \"name=\'\" . doSlash($name) . \"\'\"\n                        );\n\n                        // Alter the default thumbnail.\n                        if ($smd_thumb_default) {\n                            set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\n                        } else {\n                            // Remove the default flag if this used to be the default.\n                            if (get_pref(\'smd_thumb_default_profile\') === doSlash($name)) {\n                                set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\n                            }\n                        }\n\n                        // Adjust the thumbnail directory if the name has changed.\n                        if ($newname != $name) {\n                            rename(IMPATH.$name, IMPATH.$newname);\n                        }\n                    }\n                }\n            }\n        } else {\n            $msg = gTxt(\'smd_thumb_tables_not_installed\');\n        }\n\n        $step = \'smd_thumb_profile\';\n    }\n\n    // Action to delete a profile.\n    if ($step === \'smd_thumb_profile_delete\') {\n        if (smd_thumb_table_exist()) {\n            $txp_del = get_pref(\'smd_thumb_txp_delete\');\n            $the_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            if (!empty($smd_thumb_name)) {\n                $name = sanitizeForUrl($smd_thumb_name);\n                $ret = safe_delete(SMD_THUMB, \"name=\'\" . doSlash($name) . \"\'\");\n\n                if ($ret) {\n                    smd_thumb_rmdir(IMPATH.$name);\n\n                    // Delete all Textpattern thumbs too?\n                    if (($name == $the_dflt) && ($txp_del == \'1\')) {\n                        $rs = safe_rows(\'id, ext\', \'txp_image\', \'thumbnail = 1\');\n\n                        foreach ($rs as $row) {\n                            $path = IMPATH . $row[\'id\'] . \'t\' . $row[\'ext\'];\n\n                            if (file_exists($path)) {\n                                unlink($path);\n                            }\n                        }\n\n                        safe_update(\'txp_image\', \'thumbnail = 0, thumb_w = 0, thumb_h = 0\', \'thumbnail = 1\');\n                    }\n\n                    // Remove the default flag if this used to be the default.\n                    if (get_pref(\'smd_thumb_default_profile\') === doSlash($name)) {\n                        set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\n                    }\n\n                    $msg = gTxt(\'smd_thumb_profile_deleted\', array(\'{name}\' => doSlash($name)));\n                }\n            }\n        }\n\n        $step = \'smd_thumb_profile\';\n    }\n\n    $qs = array(\n        \"event\"         => \'image\',\n        \"page\"          => $page,\n        \"sort\"          => $sort,\n        \"dir\"           => $dir,\n        \"crit\"          => $crit,\n        \"search_method\" => $search_method,\n    );\n\n    $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\" . doSlash($txp_user) . \"\'\");\n    $rights = in_array($privs, $smd_thumb_prevs) ? true : false;\n\n    // Action to show thumbnail prefs.\n    if ($step === \'smd_thumb_prefs\') {\n        if (smd_thumb_table_exist()) {\n            $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            // Subselect lists for type \'cat\' and \'usr\'.\n            $rs = getTree(\'root\', \'image\');\n            $allCats = $rs ? treeSelectInput(\'smd_thumb_cat_selected\', $rs, \'\') : \'\';\n            $allCats = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allCats))).\"<\\/span>\";\n            $rs = safe_column(\'name\', \'txp_users\', \"privs not in(0,6) order by name asc\");\n            $allUsrs = $rs ? selectInput(\'smd_thumb_usr_selected\', $rs, \'\', true) : \'\';\n            $allUsrs = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allUsrs))).\"<\\/span>\";\n\n            echo script_js(<<<EOC\nfunction smd_thumb_switch_pref(obj, name) {\n    if (name == \'create_from\') {\n        state = jQuery(obj).val();\n    } else {\n        state = (jQuery(obj).prop(\'checked\')) ? 1 : 0;\n    }\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_switch_pref\',\n        smd_thumb_txptype: name,\n        smd_thumb_state: state\n    });\n}\nfunction smd_thumb_copy_selected() {\n    jQuery(\"#images_form tbody .multi-edit input:checked, #images_form tbody .txp-list-col-multi-edit input:checked\").each(function() {\n        jQuery(\"#smd_thumb_selected\").val(jQuery(\"#smd_thumb_selected\").val() + jQuery(this).val() + \',\');\n    });\n}\nfunction smd_thumb_subsel(obj) {\n    obj = jQuery(obj);\n    dest = jQuery(\'#smd_thumb_subsel\');\n    item = obj.val();\n    switch (item) {\n        case \'all\':\n        case \'sel\':\n            dest.empty();\n            break;\n        case \'cat\':\n            dest.empty().prepend(\'{$allCats}\');\n            break;\n        case \'usr\':\n            dest.empty().prepend(\'{$allUsrs}\');\n            break;\n    }\n}\njQuery(function() {\n    jQuery(\'#smd_thumb_group_type\').change();\n});\nEOC\n        );\n\n            $btnPnl = \'<p><a href=\"?event=image\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\"><span class=\"ui-icon ui-icon-arrowreturnthick-1-w\"></span>  \'.gTxt(\'smd_thumb_btn_pnl\').\'</a></p>\';\n            $btnGrp = \'<button class=\"navlink\" type=\"submit\" onclick=\"smd_thumb_copy_selected(); return confirm(\\\'\'.gTxt(\'smd_thumb_create_group_confirm\').\'\\\');\">\'.gTxt(\'smd_thumb_all_thumbs\').\'</button>\';\n            $grpTypes = array(\n                \'sel\' => gTxt(\'smd_thumb_bysel\'),\n                \'cat\' => gTxt(\'category\'),\n                \'usr\' => gTxt(\'author\'),\n                \'all\' => gTxt(\'smd_thumb_byall\'),\n            );\n\n            $grpOpts = selectInput(\'smd_thumb_group_type\', $grpTypes, get_pref(\'pane_smd_thumb_group_type\', \'all\', 1), \'\', \' onchange=\"smd_thumb_subsel(this);\"\', \'smd_thumb_group_type\').\'<span id=\"smd_thumb_subsel\"></span> \';\n\n            $out[] = \'<section class=\"txp-details\" id=\"smd_thumb_profiles\">\';\n            $out[] = \'<h3 class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.(($rights) ? gTxt(\'smd_thumb_profile_preftool_heading\') : gTxt(\'smd_thumb_profile_tool_heading\')).\'</a></h3>\'.\n                n.\'<div class=\"toggle\" id=\"smd_thumbnail_profiles\" role=\"region\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\n            $out[] = $btnPnl;\n            $out[] = \'<div id=\"smd_thumb_batch\"><span id=\"smd_thumb_bcurr\"></span><span id=\"smd_thumb_btot\"></span></div>\';\n            $out[] = \'<form method=\"post\" name=\"smd_thumb_multi_edit\" id=\"smd_thumb_multi_edit\" action=\"\'.join_qs($qs).\'\">\';\n\n            $out[] = \'<p><label for=\"smd_thumb_group_type\">\'.gTxt(\'smd_thumb_batch_preamble\').\'</label>\'.$grpOpts.$btnGrp.\'</p>\';\n\n            $out[] = fInput(\'hidden\', \'smd_thumb_selected\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumb_selected\');\n            $out[] = eInput($smd_thumb_event);\n            $out[] = sInput(\'smd_thumb_create_group\');\n            $out[] = \'</form>\';\n\n            if ($rights) {\n                $out[] = \'<form class=\"txp-prefs-group\" method=\"post\" name=\"smd_thumb_prefs\" id=\"smd_thumb_prefs_form\" action=\"\'.join_qs($qs).\'\">\';\n\n                $txp_rf = get_pref(\'smd_thumb_create_from\', \'full\');\n                $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_create_from\');\n                $out[] = n.\'<label>\n                            <input class=\"radio\" type=\"radio\" name=\"smd_thumb_create_from\" value=\"full\"\'.\n                                (($txp_rf==\'full\') ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\">\'.\n                                n.gTxt(\'smd_thumb_txp_create_from_full\').\n                        \'</label>\'.\n                        n.\'<label>\n                            <input class=\"radio\" type=\"radio\" name=\"smd_thumb_create_from\" value=\"thumb\"\'.\n                                (($txp_rf==\'thumb\') ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\">\'.\n                                n.gTxt(\'smd_thumb_txp_create_from_thumb\').\n                        \'</label>\';\n                $out[] = \'</p>\';\n\n                if ($pro_dflt) {\n                    $txp_c = get_pref(\'smd_thumb_txp_create\');\n                    $txp_d = get_pref(\'smd_thumb_txp_delete\');\n                    $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_default_sync\');\n                    $out[] = n.\'<label>\n                        <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_txp_create\" value=\"1\"\'.\n                                (($txp_c) ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'txp_create\\\');\">\'.\n                                n.gTxt(\'smd_thumb_create\').\n                        \'</label>\'.\n                        n.\'<label>\n                            <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_txp_delete\" value=\"1\"\'.\n                                (($txp_d) ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'txp_delete\\\');\">\'.\n                                n.gTxt(\'smd_thumb_delete\').\n                        \'</label>\';\n                    $out[] = \'</p>\';\n                }\n\n                $txp_ar = get_pref(\'smd_thumb_auto_replace\');\n                $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_auto_replace\');\n                $out[] = n.\'<label>\n                        <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_auto_replace\" value=\"1\"\'.\n                            (($txp_ar) ? \' checked=\"checked\"\' : \'\').\n                            \' onchange=\"smd_thumb_switch_pref(this, \\\'auto_replace\\\');\">\'.\n                            n.gTxt(\'yes\').\n                    \'</label>\';\n                $out[] = \'</p>\';\n\n                $out[] = \'</form>\';\n            }\n            $out[] = \'</section>\';\n        }\n    // Action to show thumbnail profile list.\n    } else {\n        $editFocus = ($step === \'smd_thumb_profile_edit\') ? \'jQuery(\"#smd_thumb_profile_edited input[name=\\\'smd_thumb_newname\\\']\").focus();\' : \'\';\n\n        echo script_js(<<<EOC\nfunction smd_thumb_togglenew() {\n    box = jQuery(\"#smd_thumb_profile_create\");\n    box.toggleClass(\"smd_hidden smd_thumb_new_profile\");\n    jQuery(\"input.smd_focus\").focus();\n    return false;\n}\nfunction smd_thumb_switch_active(name) {\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_switch_active\',\n        smd_thumb_profile: name\n    }, smd_thumb_switch_active_done);\n}\nfunction smd_thumb_switch_active_done(data) {\n    var smd_thumb_profile = jQuery(\'#smd_thumb_profile_\'+jQuery(data).find(\'smd_thumb_profile\').attr(\'value\'));\n    smd_thumb_profile.toggleClass(\'smd_inactive\');\n    if (smd_thumb_profile.hasClass(\'smd_inactive\')) {\n        smd_thumb_profile.find(\'input:checkbox\').removeAttr(\'checked\');\n    } else {\n        smd_thumb_profile.find(\'input:checkbox\').attr(\'checked\', \'checked\');\n    }\n}\n\njQuery(function() {\n    jQuery(\'.smd_thumb_heading_active\').click(function() {\n        jQuery(\'.smd_thumb_row input:checkbox\').each(function() {\n            smd_thumb_switch_active(jQuery(this).parent().attr(\'class\'));\n        });\n    });\n    {$editFocus}\n});\nEOC\n        );\n\n        $btnNew = \'<p><a class=\"txp-button\" href=\"#\" onclick=\"return smd_thumb_togglenew();\">\'.gTxt(\'smd_thumb_new\').\'</a></p>\';\n        $btnPref = \'<p class=\"txp-list-options\"><a href=\"?event=image\'.a.\'step=smd_thumb_prefs\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\"><span class=\"ui-icon ui-icon-wrench\"></span> \'.(($rights) ? gTxt(\'smd_thumb_btn_tools_prefs\') : gTxt(\'smd_thumb_btn_tools\')).\'</a></p>\';\n        $btnCancel = fInput(\'submit\', \'smd_thumb_cancel\', gTxt(\'cancel\'));\n\n        $headings = \'<thead>\'.tr(\n            column_head(\'name\', \'name\', \'image\', false, \'asc\').\n            column_head(\'description\', \'description\', \'image\', false).\n            column_head(\'thumb_width\', \'width\', \'image\', false).\n            column_head(\'thumb_height\', \'height\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_quality\'), \'quality\', \'image\', false).\n            column_head(\'keep_square_pixels\', \'crop\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_sharpen\'), \'sharpen\', \'image\', false).\n            column_head(\'active\', \'active\', \'image\', false, \'\', \'\', \'\', \'smd_thumb_heading_active\').\n            column_head(\'default\', \'default\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_actions\'), \'actions\', \'image\', false)\n        ).n.\'</thead>\';\n\n        $out[] = \'<section class=\"txp-details\" id=\"smd_thumb_profiles\">\';\n        $out[] = \'<h3 class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.gTxt(\'smd_thumb_profile_heading\').\'</a></h3>\'.\n            n.\'<div class=\"toggle\" id=\"smd_thumbnail_profiles\" role=\"region\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\n\n        // Main list of profiles.\n        $out[] = \'<form method=\"post\" name=\"smd_thumb_profile_form\" id=\"smd_thumb_profile_form\" action=\"\'.join_qs($qs).\'\">\';\n        $out[] = tag_start(\'div\', array(\'class\' => \'txp-listtables\'));\n        $out[] = tag_start(\'table\', array(\'class\' => \'txp-list--no-options\'));\n        $out[] = $headings;\n        $out[] = tag_start(\'tbody\');\n\n        if (smd_thumb_table_exist()) {\n            $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\n            $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            // Row for each currently-defined profile.\n            foreach ($rs as $row) {\n                $link_edt = join_qs($qs).a.\'step=smd_thumb_profile_edit\'.a.\'smd_thumb_name=\'.$row[\'name\'];\n                $link_del = join_qs($qs).a.\'step=smd_thumb_profile_delete\'.a.\'smd_thumb_name=\'.$row[\'name\'];\n                $btnEdt = \'<a href=\"\'.$link_edt.\'\">[\' . gTxt(\'edit\') . \']</a>\';\n                $btnDel = \'<a href=\"\'.$link_del.\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])).\'\\\');\">[\' . gTxt(\'delete\') . \']</a>\';\n                $btnDel = href(gTxt(\'delete\'), array(\n                    \'event\'          => \'image\',\n                    \'step\'           => \'smd_thumb_profile_delete\',\n                    \'smd_thumb_name\' => $row[\'name\'],\n                    \'_txp_token\'     => form_token(),\n                    \'page\'           => $page,\n                    \'sort\'           => $sort,\n                    \'dir\'            => $dir,\n                    \'crit\'           => $crit,\n                    \'search_method\'  => $search_method,\n                ), array(\n                    \'class\'       => \'destroy ui-icon ui-icon-close\',\n                    \'title\'       => gTxt(\'delete\'),\n                    \'data-verify\' => gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])),\n                ));\n\n                $active = ($row[\'flags\'] & SMD_THUMB_ACTIVE) ? 1 : 0;\n                $crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\n                $sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\n\n                if ($step == \'smd_thumb_profile_edit\' && $row[\'name\'] == $smd_thumb_name) {\n                    $btnSave = fInput(\'submit\', \'smd_thumb_save\', gTxt(\'save\'));\n                    $out[] = tr(\n                        tda(hInput(\'smd_thumb_name\', $row[\'name\']).fInput(\'text\', \'smd_thumb_newname\', $row[\'name\']), array(\'data-th\' => gTxt(\'name\')), \'\', \'\', INPUT_MEDIUM).\n                        tda(fInput(\'text\', \'smd_thumb_description\', $row[\'description\'], \'\', \'\', \'\', INPUT_REGULAR), array(\'data-th\' => gTxt(\'description\'))).\n                        tda(fInput(\'text\', \'smd_thumb_width\', $row[\'width\'], \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_width\'))).\n                        tda(fInput(\'text\', \'smd_thumb_height\', $row[\'height\'], \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_height\'))).\n                        tda(fInput(\'text\', \'smd_thumb_quality\', $row[\'quality\'], \'\', \'\', \'\', 3), array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                        tda(checkbox(\'smd_thumb_crop\', \'1\', $crop), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                        tda(checkbox(\'smd_thumb_sharpen\', \'1\', $sharpen), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                        tda(checkbox(\'smd_thumb_active\', \'1\', $active), array(\'data-th\' => gTxt(\'active\'), \'class\' => $row[\'name\'])).\n                        tda(checkbox(\'smd_thumb_default\', \'1\', (($row[\'name\'] == $pro_dflt) ? 1 : 0)), array(\'data-th\' => gTxt(\'default\'))).\n                        tda($btnSave.$btnCancel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')))\n                    , \' id=\"smd_thumb_profile_edited\"\');\n                } else {\n                    $out[] = tr(\n                        tda(href($row[\'name\'], $link_edt), array(\'data-th\' => gTxt(\'name\'))).\n                        tda($row[\'description\'], array(\'data-th\' => gTxt(\'description\'))).\n                        tda($row[\'width\'], array(\'data-th\' => gTxt(\'thumb_width\'))).\n                        tda($row[\'height\'], array(\'data-th\' => gTxt(\'thumb_height\'))).\n                        tda($row[\'quality\'], array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                        tda(($crop) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                        tda(($sharpen) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                        tda(\'<input type=\"checkbox\" name=\"smd_thumb_active_\'.$row[\'name\'].\'\" value=\"1\"\'.(($active) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_active(\\\'\'.$row[\'name\'].\'\\\');\" />\', array(\'data-th\' => gTxt(\'active\'), \'class\' => $row[\'name\'])).\n                        tda(($row[\'name\'] == $pro_dflt) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'default\'))).\n                        tda($btnDel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')))\n                    , \' id=\"smd_thumb_profile_\'.$row[\'name\'].\'\" class=\"smd_thumb_row\' . (($active) ? \'\' : \' smd_inactive\').\'\"\');\n                }\n            }\n\n            // New Profile row.\n            $out[]= \'<tr id=\"smd_thumb_profile_create\" class=\"smd_hidden\">\';\n            $out[] = tda(sInput(\'smd_thumb_profile_save\').fInput(\'text\', \'smd_thumb_add_newname\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_name : \'\'), \'smd_focus\', \'\', \'\', INPUT_MEDIUM), array(\'data-th\' => gTxt(\'name\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_description\', (($step === \'smd_thumb_profile_save\') ? $description : \'\'), \'\', \'\', \'\', INPUT_REGULAR), array(\'data-th\' => gTxt(\'description\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_width\', (($step === \'smd_thumb_profile_save\') ? $width : \'\'), \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_width\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_height\', (($step === \'smd_thumb_profile_save\') ? $height : \'\'), \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_height\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_quality\', (($step === \'smd_thumb_profile_save\') ? $quality : \'\'), \'\', \'\', \'\', 3), array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                tda(checkbox(\'smd_thumb_add_crop\', \'1\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_crop : 0)), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                tda(checkbox(\'smd_thumb_add_sharpen\', \'1\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_sharpen : 0)), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                tda(checkbox(\'smd_thumb_add_active_new\', \'1\', 1), array(\'data-th\' => gTxt(\'active\'))).\n                tda(checkbox(\'smd_thumb_add_default\', \'1\', 0), array(\'data-th\' => gTxt(\'default\'))).\n                tda(fInput(\'submit\', \'smd_thumb_add\', gTxt(\'add\')).$btnCancel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')));\n            $out[]= \'</tr>\';\n\n        }\n\n        $out[] = tag_end(\'tbody\');\n        $out[] = tag_end(\'table\');\n        $out[] = tag_end(\'div\'); // End of .txp-listtables.\n        $out[] = $btnPref;\n        $out[] = $btnNew;\n        $out[] = \'</form>\';\n        $out[] = \'</section>\';\n    }\n\n    return join(n, $out);\n}\n\n/**\n * Useful function for other plugins to use to retrieve a list of current profiles.\n *\n * @param  integer $active Whether to only return active profiles (1) or all of them (0)\n * @param  string  $order  The order in which to return the list\n * @return array\n */\nfunction smd_thumb_get_profiles($active = 1, $order = \'name\')\n{\n    $allowedOrders = array(\'name\', \'width\', \'height\',\'quality\');\n    $order = in_array($order, $allowedOrders) ? $order : \'name\';\n    $where[] = \'1=1\';\n\n    if ($active) {\n        $where[] = \'flags & \' . SMD_THUMB_ACTIVE;\n    }\n\n    $rs = safe_rows(\'*\', SMD_THUMB, join(\' AND \', $where) . \' ORDER BY \'.$order);\n\n    return $rs;\n}\n\n/**\n * Delete a thumbnail directory and its contents.\n *\n * @param  string $dir Directory to remove.\n */\nfunction smd_thumb_rmdir($dir)\n{\n    if (is_dir($dir)) {\n        $objects = scandir($dir);\n\n        foreach ($objects as $object) {\n            if ($object != \".\" && $object != \"..\") {\n                if (filetype($dir.DS.$object) == \"dir\") smd_thumb_rmdir($dir.DS.$object); else unlink($dir.DS.$object);\n            }\n        }\n\n        reset($objects);\n        rmdir($dir);\n    }\n}\n\n/**\n * Set the global image path if not already set.\n */\nfunction smd_thumb_set_impath()\n{\n    global $path_to_site, $img_dir;\n\n    if (!defined(\'IMPATH\')) {\n        define(\'IMPATH\', $path_to_site . DS . $img_dir . DS);\n    }\n}\n\n/**\n * Add thumbnail table if not already installed.\n *\n * Flags: Bit 0=Active / 1=Crop / 2=Sharpen\n */\nfunction smd_thumb_table_install()\n{\n    global $DB;\n\n    $tableDef = \"\n        name        VARCHAR(32)  NOT NULL DEFAULT \'\',\n        description VARCHAR(255) NOT NULL DEFAULT \'\',\n        width       INT(4)       NOT NULL DEFAULT \'0\',\n        height      INT(4)       NOT NULL DEFAULT \'0\',\n        quality     TINYINT(2)   NULL     DEFAULT \'75\',\n        flags       TINYINT(2)   NOT NULL DEFAULT \'0\',\n        PRIMARY KEY (name)\n    \";\n\n    if (!safe_create(SMD_THUMB, $tableDef)) {\n        echo mysqli_error($DB->link);\n    }\n\n    // Upgrades.\n    // -> to v0.4.0: Add the description column.\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_THUMB.\'`\');\n\n    if (!in_array(\'description\', $flds)) {\n        safe_alter(SMD_THUMB, \"ADD `description` VARCHAR(255) NOT NULL DEFAULT \'\' AFTER `name`\");\n    }\n\n    // Backup the current thumb prefs.\n    $exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\n\n    if (!$exists) {\n        set_pref(\'smd_thumb_backup_w\', get_pref(\'thumb_w\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0);\n        set_pref(\'smd_thumb_backup_h\', get_pref(\'thumb_h\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 1);\n        set_pref(\'smd_thumb_backup_c\', get_pref(\'thumb_crop\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 2);\n\n        // Erase the current thumb prefs.\n        set_pref(\'thumb_w\', \'\', \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_h\', \'\', \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_crop\', \'\', \'image\', PREF_HIDDEN);\n    }\n}\n\n/**\n * Drop thumbnail table if in database.\n */\nfunction smd_thumb_table_remove()\n{\n    global $DB;\n\n    if (!safe_drop(SMD_THUMB)) {\n        echo mysqli_error($DB->link);\n    }\n\n    // Restore the original thumb prefs.\n    $exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\n\n    if ($exists) {\n        set_pref(\'thumb_w\', get_pref(\'smd_thumb_backup_w\'), \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_h\', get_pref(\'smd_thumb_backup_h\'), \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_crop\', get_pref(\'smd_thumb_backup_c\'), \'image\', PREF_HIDDEN);\n\n    }\n\n    // Erase the backup thumb prefs.\n    safe_delete(\'txp_prefs\',\n        \"name IN(\'smd_thumb_backup_w\', \'smd_thumb_backup_h\', \'smd_thumb_backup_c\', \'smd_thumb_default_profile\', \'pane_smd_thumbnail_profiles_visible\')\");\n}\n\n/**\n * Determine if the thumbnail table exists.\n *\n * @param  string $all [description]\n */\nfunction smd_thumb_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_THUMB => 6);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n\n        return ($out === 0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_THUMB.\" HAS \".count(@safe_show(\'columns\', SMD_THUMB)).\" COLUMNS;\";\n        }\n\n        return(@safe_show(\'columns\', SMD_THUMB));\n    }\n}\n\n/**\n * AJAX function to store the state of the current UI elements (which pane is visible).\n */\nfunction smd_thumbnail_save_pane_state()\n{\n    $panes = array(\'smd_thumbnail_profiles\');\n    $pane = gps(\'pane\');\n\n    if (in_array($pane, $panes)) {\n        set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_thumb\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n/**\n * Public tag: show a thumbnail.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n * @return string        HTML\n */\nfunction smd_thumbnail($atts, $thing = NULL)\n{\n    global $thisimage, $img_dir;\n\n    extract(lAtts(array(\n        \'type\'       => get_pref(\'smd_thumb_default_profile\', \'\'),\n        \'id\'         => \'\',\n        \'name\'       => \'\',\n        \'escape\'     => \'html\',\n        \'wraptag\'    => \'\',\n        \'class\'      => \'\',\n        \'html_id\'    => \'\',\n        \'style\'      => \'\',\n        \'link\'       => \'\',\n        \'link_rel\'   => \'\',\n        \'poplink\'    => 0,\n        \'add_stamp\'  => 0,\n        \'width\'      => \'\',\n        \'height\'     => \'\',\n        \'force_size\' => \'\',\n        \'display\'    => \'thumbnail\', // thumbnail (full img tag) or URL.\n        \'form\'       => \'\',\n    ), $atts));\n\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n\n    if ($name) {\n        $name = doSlash($name);\n        $rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\n    } elseif ($id) {\n        $id = (int) $id;\n        $rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\n    } elseif ($thisimage) {\n        $id = (int) $thisimage[\'id\'];\n        $rs = $thisimage;\n    } else {\n        trigger_error(gTxt(\'unknown_image\'));\n        return;\n    }\n\n    smd_thumb_set_impath();\n\n    if ($rs) {\n        extract($rs);\n        $thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\n\n        if ($thumbinfo) {\n            $dir = sanitizeForUrl($thumbinfo[\'name\']);\n            $path = IMPATH . $dir . DS . $id . $ext;\n\n            if (file_exists($path)) {\n                // Drop out if all we need to display is the thumb\'s URL.\n                if ($display === \'url\') {\n                    return ihu . $img_dir . \'/\' . $dir . \'/\' . $id . $ext;\n                }\n\n                if ($escape === \'html\') {\n                    $alt = htmlspecialchars($alt);\n                    $caption = htmlspecialchars($caption);\n                }\n\n                $meta[\'url\'] = ihu . $img_dir . \'/\' . $dir . \'/\' . $id . $ext;\n                $meta[\'alt\'] = $alt;\n                $meta[\'type\'] = $type;\n                $meta[\'width\'] = $width;\n                $meta[\'height\'] = $height;\n                $force_size = do_list($force_size);\n\n                if (in_array(\'width\', $force_size)) {\n                    $meta[\'forcew\'] = 1;\n                }\n\n                if (in_array(\'height\', $force_size)) {\n                    $meta[\'forceh\'] = 1;\n                }\n\n                // Negative logic since we want the stamp on by default for the admin side.\n                if ($add_stamp == 0) {\n                    $meta[\'stamp\'] = 1;\n                }\n\n                if ($name) {\n                    $meta[\'name\'] = $name;\n                }\n\n                if ($caption) {\n                    $meta[\'title\'] = $caption;\n                }\n// $meta[\'title\'] = gTxt(\'smd_thumb_image\') . $rs[\'w\'] . \" &#215; \" . $rs[\'h\'] . sp .  gTxt(\'smd_thumb_profile\') . $thumbinfo[\'width\'] . \" &#215; \" . $thumbinfo[\'height\'];\n\n                if ($category) {\n                    $meta[\'category\'] = $category;\n                    $meta[\'category_title\'] = fetch_category_title($category, \'image\');\n                }\n\n                if ($author) {\n                    $meta[\'author\'] = $author;\n                }\n\n                if ($date) {\n                    $meta[\'date\'] = $date;\n                }\n\n                if ($html_id && !$wraptag) {\n                    $meta[\'id\'] = $html_id;\n                }\n\n                if ($class && !$wraptag) {\n                    $meta[\'class\'] = $class;\n                } else {\n                    $class = $type;\n                }\n\n                if ($style) {\n                    $meta[\'style\'] = $style;\n                }\n\n                $out = smd_thumb_img($thumbinfo, $rs, $meta, $thing);\n\n                if ($link) {\n                    $out = href($out, imagesrcurl($id, $ext), (!empty($link_rel) ? \' rel=\"\' . $link_rel . \'\"\' : \'\') . \' title=\"\' . $caption . \'\"\');\n                } elseif ($poplink) {\n                    $out = \'<a href=\"\' . imagesrcurl($id, $ext) . \'\"\' .\n                        \' onclick=\"window.open(this.href, \\\'popupwindow\\\', \'.\n                        \'\\\'width=\' . $w . \', height=\' . $h . \', scrollbars, resizable\\\'); return false;\">\' . $out . \'</a>\';\n                }\n\n                return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\n            }\n        }\n    }\n\n    trigger_error(gTxt(\'unknown_image\'));\n}\n\n/**\n * Public tag: check thumbnail exists.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n */\nfunction smd_if_thumbnail($atts, $thing)\n{\n    global $thisimage;\n\n    assert_image();\n\n    smd_thumb_set_impath();\n\n    extract(lAtts(array(\n        \'type\' => get_pref(\'smd_thumb_default_profile\'),\n    ), $atts));\n\n    $thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\n    $ret = false;\n\n    if ($thumbinfo) {\n        $path = IMPATH . sanitizeForUrl($thumbinfo[\'name\']) . DS . $thisimage[\'id\'] . $thisimage[\'ext\'];\n        $ret = file_exists($path);\n    }\n\n    return parse(EvalElse($thing, $ret));\n}\n\n/**\n * Public tag: display selected thumbnail meta data.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n * @return string        HTML\n */\nfunction smd_thumbnail_info($atts, $thing = NULL)\n{\n    global $smd_thumb_data;\n\n    extract(lAtts(array(\n        \'item\'    => \'\',\n        \'wraptag\' => \'\',\n        \'break\'   => \'\',\n        \'class\'   => \'\',\n        \'debug\'   => 0,\n    ), $atts));\n\n    $tdata = is_array($smd_thumb_data) ? $smd_thumb_data : array();\n\n    if ($debug) {\n        echo \'++ AVAILABLE INFO ++\';\n        dmp($tdata);\n    }\n\n    $items = do_list($item);\n    $out = array();\n\n    foreach ($items as $it) {\n        if (isset($tdata[$it])) {\n            $out[] = $tdata[$it];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}', '/**\n * smd_thumbnail\n *\n * A Textpattern CMS plugin for managing multiple image thumbnails:\n *  -> Create unlimited thumbnail profiles of differing sizes for various site uses\n *  -> Thumbnail files are created in your image directory -- no realtime scaling\n *  -> Batch create / alter thumbnails\n *  -> Choose to sync the thumbnails with Textpattern\'s own thumbs\n *\n * @author Stef Dawson\n * @link   https://stefdawson.com/\n * TODO: Make Selections (lines 912-916) optional via prefs or something\n * TODO: Simplify AJAX response packets to speed things up\n * TODO: Interface elements for Profile creation don\'t get rendered if there are no images in pool\n */\nif (!defined(\'SMD_THUMB\')) {\n    define(\"SMD_THUMB\", \'smd_thumbnail\');\n}\nif (!defined(\'SMD_THUMB_ACTIVE\')) {\n    define(\"SMD_THUMB_ACTIVE\", 1);\n}\nif (!defined(\'SMD_THUMB_CROP\')) {\n    define(\"SMD_THUMB_CROP\", 2);\n}\nif (!defined(\'SMD_THUMB_SHARP\')) {\n    define(\"SMD_THUMB_SHARP\", 4);\n}\n\nif (txpinterface === \'admin\') {\n    global $smd_thumb_event, $smd_thumb_prevs;\n\n    $smd_thumb_event = \'smd_thumbnail\';\n    $smd_thumb_privs = \'1,2,3\'; // Plugin privs\n    $smd_thumb_prevs = array(\'1\'); // Privs for prefs\n\n    add_privs($smd_thumb_event, $smd_thumb_privs);\n    add_privs(\'smd_thumb_profiles\', $smd_thumb_privs);\n    register_callback(\'smd_thumb_welcome\', \'plugin_lifecycle.\'.$smd_thumb_event);\n    register_callback(\'smd_thumb_profiles\', \'image_ui\', \'extend_controls\');\n    register_callback(\'smd_thumbs\', \'image_ui\', \'thumbnail\');\n    register_callback(\'smd_thumb_edit\', \'image_ui\', \'thumbnail_edit\');\n    register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_image\');\n    register_callback(\'smd_thumb_empty\', \'image_ui\', \'thumbnail_create\');\n    register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'image\');\n    register_callback(\'smd_thumb_generate\', \'image_uploaded\', \'moderate\');\n    register_callback(\'smd_thumb_delete\', \'image_deleted\', \'image\');\n    register_callback(\'smd_thumb_create_one\', \'image\', \'smd_thumb_create_one\');\n    register_callback(\'smd_thumb_switch_active\', \'image\', \'smd_thumb_switch_active\');\n    register_callback(\'smd_thumb_switch_pref\', \'image\', \'smd_thumb_switch_pref\');\n    register_callback(\'smd_thumb_insert\', \'image\', \'smd_thumbnail_insert\', 1);\n    register_callback(\'smd_thumb_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    smd_thumb_set_impath();\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_thumbnail\')\n            ->register(\'smd_if_thumbnail\')\n            ->register(\'smd_thumbnail_info\');\n    }\n}\n\n/**\n * CSS definitions: hopefully kind to themers.\n */\nfunction smd_thumb_get_style_rules()\n{\n    $smd_thumb_styles = array(\n        \'smd_thumb\' =>\'\n.smd_selected { border-color: #0066ff; }\n.smd_hidden { display: none; }\n.smd_inactive td { opacity: 0.33; }\ninput.smd_thumbnail-create { margin: 0; }\n#smd_thumbs img { display: block; margin: 1em 0; cursor: pointer; }\n/* Legacy 4.6.x support */\n#smd_thumb_profiles { clear: both; }\n.txp-list--no-options { width: 100%; }\n.txp-details .txp-listtables { margin: 1em 0;}\n\'\n);\n\n    return $smd_thumb_styles;\n}\n\n/**\n * Inject the stylesheet rules into the matching panel.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_inject_css($evt, $stp)\n{\n    global $event;\n\n    if ($event === \'image\') {\n        $smd_thumb_styles = smd_thumb_get_style_rules();\n        echo \'<style type=\"text/css\">\', $smd_thumb_styles[\'smd_thumb\'], \'</style>\';\n    }\n\n    return;\n}\n\n/**\n * Kickstart the plugin after installation/activation/deletion.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_welcome($evt, $stp)\n{\n    switch ($stp) {\n        case \'installed\':\n            smd_thumb_table_install();\n            // Remove per-user prefs on upgrade from v0.1x to v0.20.\n            safe_delete (\'txp_prefs\', \"name IN (\'smd_thumb_txp_create\', \'smd_thumb_txp_delete\', \'smd_thumb_auto_replace\') AND user_name != \'\'\");\n            break;\n        case \'deleted\':\n            smd_thumb_table_remove();\n            break;\n    }\n\n    return;\n}\n\n/**\n * Display the designated default thumbnail on the list page.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumbs($evt, $stp, $dflt, $currimg)\n{\n    extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    if (smd_thumb_table_exist()) {\n        $default = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n        if ($default) {\n            $row = safe_row(\'*\', SMD_THUMB, \"name=\'\".sanitizeForUrl($default).\"\'\");\n\n            if ($row) {\n                $edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$currimg[\'id\'].a.\'sort=\'.$sort.\n                        a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n                $out = smd_thumb_img($row, $currimg, array(\'class\' => \'content-image \'.$default));\n\n                return ($out) ? href($out, $edit_url) : gTxt(\'no\');\n            } else {\n                return gTxt(\'no\');\n            }\n        } else {\n            return gTxt(\'no\');\n        }\n    }\n}\n\n/**\n * Don\'t want the \'create\' controls or thumbnail image as they\'re both handled in the edit portion of the screen.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumb_empty($evt, $stp, $dflt, $currimg)\n{\n    return \' \';\n}\n\n/**\n * Create a bunch of thumbnails en masse.\n *\n * With a lot of images in the database, doing it all in one hit would time out.\n * Thus it\'s done via ajax; one request per image.\n *\n * @param  string $type Flavour of thumbnail creation: by (usr), by (cat), by (sel)ection, or (all)\n * @param  string $lst  List of images to operate upon\n * @return [type]       [description]\n */\nfunction smd_thumb_create_group($type, $lst = \'\')\n{\n    switch ($type) {\n        case \'all\':\n            $where = \'1=1\';\n            break;\n        case \'sel\':\n            $where = ($lst) ? \'id IN (\'.doSlash($lst).\')\' : \'1=0\';\n            break;\n        case \'cat\':\n            $where = \"category in (\'\".doSlash($lst).\"\')\";\n            break;\n        case \'usr\':\n            $where = \"author in (\'\".doSlash($lst).\"\')\";\n            break;\n    }\n\n    $images = safe_column(\'id\', \'txp_image\', $where);\n    $count = count($images);\n\n    echo <<<EOJS\n<script type=\"text/javascript\">\nvar bctr = 0; var btot = {$count};\njQuery(function() {\n    jQuery(\"#smd_thumb_btot\").text(\"/\'.$count.\'\");\nEOJS;\n\n    foreach ($images as $img) {\n        echo <<<EOJS\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_create_one\',\n        smd_thumb_imgid: {$img}\n    }, smd_create_group_feedback);\nEOJS;\n    }\n    echo \'});\';\n    echo <<<EOJS\nfunction smd_create_group_feedback() {\n    bctr++;\n    jQuery(\'#smd_thumb_bcurr\').text(bctr);\n}\nEOJS;\n    echo \'</script>\';\n}\n\n/**\n * AJAX method to create one thumbnail for each active profile, from the passed ID.\n */\nfunction smd_thumb_create_one()\n{\n    $currimg = gps(\'smd_thumb_imgid\');\n    assert_int($currimg);\n\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \' . SMD_THUMB_ACTIVE);\n    $curr = safe_row(\'*\', \'txp_image\', \"id=\" . doSlash($currimg));\n\n    if ($rs) {\n        smd_thumb_make($rs, $curr, 1);\n        send_xml_response();\n    }\n}\n\n/**\n * AJAX method to set the active thumbnail profile.\n *\n * Requires GET/POST params:\n *  -> smd_thumb_profile TName of the profile to set as active\n */\nfunction smd_thumb_switch_active()\n{\n    $name = doSlash(gps(\'smd_thumb_profile\'));\n\n    if ($name) {\n        safe_update(SMD_THUMB, \'flags = flags ^ \' . SMD_THUMB_ACTIVE, \"name=\'$name\'\");\n        send_xml_response(array(\'smd_thumb_profile\' => $name));\n    }\n}\n\n/**\n * AJAX method to toggle the given preference name to the given state.\n *\n * Requires GET/POST params:\n *  -> smd_thumb_txptype Preference name (without plugin prefix)\n *  -> smd_thumb_state   Preference value to store\n */\nfunction smd_thumb_switch_pref()\n{\n    $name = doSlash(gps(\'smd_thumb_txptype\'));\n    $state = doSlash(gps(\'smd_thumb_state\'));\n\n    if ($name) {\n        set_pref(\'smd_thumb_\'.$name, $state, \'smd_thumb\', PREF_HIDDEN, \'text_input\');\n        send_xml_response();\n    }\n}\n\n/**\n * Wrapper to create thumbnail for active profiles from uploaded image.\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n * @param  string $id  New image identifier. If omitted, will try GET/POST, then Textpattern\'s GLOBALS[\'ID\']\n */\nfunction smd_thumb_generate($evt, $stp, $id = \'\')\n{\n    // Catch situations where the plugin is installed without lifecycle on a\n    // new install where no images exist. Thus the first time the plugin is\n    // invoked is here when the first image is uploaded.\n    if (!smd_thumb_table_exist()) {\n        smd_thumb_table_install();\n    }\n\n    $id = ($id) ? $id : gps(\'id\');\n    $id = ($id) ? $id : $GLOBALS[\'ID\'];\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 AND flags & \' . SMD_THUMB_ACTIVE);\n\n    smd_thumb_make($rs, $id);\n}\n\n/**\n * Wrapper to delete a bunch of selected thumbnails.\n *\n * Uses GET/POST parameters:\n *  -> selected Comma-separated list of selected image identifiers\n *\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_thumb_delete($evt, $stp)\n{\n    $ids = gps(\'selected\');\n    $rs = safe_rows(\'*\', SMD_THUMB, \'1=1\');\n\n    $images = safe_rows(\'*\', \'txp_image\', \'id IN (\' . join(\',\',quote_list($ids)) . \')\');\n\n    foreach ($images as $img) {\n        smd_thumb_unmake($rs, $img);\n    }\n}\n\n/**\n * Create a thumbnail.\n *\n * @param  array  $rs       Record set containing image meta data\n * @param  int    $currimg  Identifier for the current image being operated upon\n * @param  bool   $force    Whether to always create the thumbnail, even if it exists\n * @return string           Feedback message\n */\nfunction smd_thumb_make($rs, $currimg, $force = 0)\n{\n    // Wrapper for wet_thumb to allow multiple thumbnails.\n    $msg = \'\';\n    smd_thumb_set_impath();\n\n    if (!class_exists(\'smd_thumb\')) {\n        class smd_thumb extends wet_thumb\n        {\n            var $m_ext;\n            var $m_id;\n            var $m_dir;\n            var $m_dflt;\n            var $width;\n            var $height;\n            var $force;\n\n            /**\n             * Constructor.\n             *\n             * @param int     $id         Unique image identifier\n             * @param string  $dir        Subdirectory in which to store the image\n             * @param array   $img_row    Image meta data to store\n             * @param boolean $is_default Whether the thumbnail is to become the default\n             * @param string  $pro_w      Thumbnail width\n             * @param string  $pro_h      Thumbnail height\n             * @param boolean $force      Whether to overwrite any previous thumbnail\n             */\n            public function __construct ($id, $dir, $img_row, $is_default = false, $pro_w = \'\', $pro_h = \'\', $force = 0)\n            {\n                $id = assert_int($id);\n\n                if ($img_row) {\n                    extract($img_row);\n                    $this->m_id = $id;\n                    $this->m_ext = $ext;\n                    $this->m_dir = $dir;\n                    $this->force = $force;\n                    $this->width = $pro_w;\n                    $this->height = $pro_h;\n                    $this->m_dflt = $is_default;\n                }\n\n                parent::__construct();\n            }\n\n            /**\n             * Store the thumbnail image on disk.\n             *\n             * @return bool\n             */\n            public function write_image()\n            {\n                if (!isset($this->m_ext)) {\n                    return false;\n                }\n\n                $autorep = get_pref(\'smd_thumb_auto_replace\', \'0\');\n                $recfrom = get_pref(\'smd_thumb_create_from\', \'full\');\n                $src_sz = ($recfrom === \'full\') ? \'\' : \'t\';\n                $infile = IMPATH . $this->m_id . $src_sz . $this->m_ext;\n                $outfile = IMPATH . $this->m_dir . DS . $this->m_id . $this->m_ext;\n\n                // If we\'re trying to create from Txp thumbnail but it doesn\'t exist,\n                // fall back to creating from full image.\n                if (!file_exists($infile) && $src_sz === \'t\') {\n                    $infile = IMPATH . $this->m_id . $this->m_ext;\n                }\n\n                if (!file_exists($outfile) || $autorep || $this->force) {\n                    // If this is the default profile and the pref indicates, write a Textpattern thumb too.\n                    if (($this->m_dflt === true) && (get_pref(\'smd_thumb_txp_create\', \'0\'))) {\n                        $txp_thumb = IMPATH . $this->m_id . \'t\' . $this->m_ext;\n\n                        if (parent::write($infile, $txp_thumb)) {\n                            safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $this->width, thumb_h = $this->height\", \'id = \' . $this->m_id);\n                            @chmod($outfile, 0644);\n                        }\n                    }\n\n                    if (parent::write ($infile, $outfile)) {\n                        @chmod($outfile, 0644);\n\n                        return true;\n                    }\n                }\n\n                return false;\n            }\n        }\n    }\n\n    // If passed only an ID, look up the rest of the image data.\n    if (!is_array($currimg)) {\n        assert_int($currimg);\n        $currimg = safe_row(\'*\', \'txp_image\', \'id=\' . $currimg);\n    }\n\n    // Create each thumbnail.\n    $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\n\n    foreach ($rs as $row) {\n        // Sanitize a little.\n        $width = (int) $row[\'width\'];\n        $height = (int) $row[\'height\'];\n\n        if ($width === 0) {\n            $width = \'\';\n        }\n\n        if ($height === 0) {\n            $height = \'\';\n        }\n\n        if ($width === \'\' && $height === \'\') {\n            continue;\n        }\n\n        $crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\n        $sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\n        $id = $currimg[\'id\'];\n        $is_dflt = ($row[\'name\'] === $pro_dflt) ? true : false;\n\n        $t = new smd_thumb($id, sanitizeForUrl($row[\'name\']), $currimg, $is_dflt, $width, $height, $force);\n        $t->extrapolate = true; // Allow bigger thumbs than original image.\n        $t->crop = ($crop === 1);\n        $t->sharpen = ($sharpen === 1);\n        $t->hint = \'0\';\n        $t->width = $width;\n        $t->height = $height;\n        $t->quality = $row[\'quality\'];\n\n        if ($t->write_image()) {\n            $msg = gTxt(\'thumbnail_saved\', array(\'{id}\' => $id));\n        } else {\n            $msg = array(gTxt(\'thumbnail_not_saved\', array(\'{id}\' => $id)), E_ERROR);\n        }\n    }\n\n    return $msg;\n}\n\n/**\n * Delete the passed set of thumbnails.\n *\n * @param  array  $rs      Sert of image identifiers to delete\n * @param  int    $currimg Current image being operated upon\n */\nfunction smd_thumb_unmake($rs, $currimg)\n{\n    $id = $currimg[\'id\'];\n    $ext = $currimg[\'ext\'];\n    $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\');\n    $txp_del = get_pref(\'smd_thumb_txp_delete\', \'0\');\n    smd_thumb_set_impath();\n\n    foreach ($rs as $row) {\n        $path = IMPATH . sanitizeForUrl($row[\'name\']) . DS . $id . $ext;\n\n        if (file_exists($path)) {\n            unlink($path);\n        }\n\n        // Also remove Txp\'s built-in thumb?\n        if (($row[\'name\'] === $pro_dflt) && ($txp_del == \'1\')) {\n            $path = IMPATH . $id . \'t\' . $ext;\n\n            if (file_exists($path)) {\n                safe_update(\'txp_image\', \"thumbnail = 0, thumb_w = 0, thumb_h = 0\", \'id = \' . $id);\n                unlink($path);\n            }\n        }\n    }\n\n    return \'\';\n}\n\n/**\n * Insert a thumbnail when a new file is uploaded.\n */\nfunction smd_thumb_insert()\n{\n    global $txpcfg, $txp_user, $page, $sort, $dir, $crit, $search_method;\n\n    smd_thumb_set_impath();\n    extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    include_once txpath.\'/lib/txplib_misc.php\';\n\n    extract($txpcfg);\n    $id = assert_int(gps(\'id\'));\n    $profile = gps(\'smd_thumb_profile\');\n    $thumb_ext = gps(\'smd_thumb_ext\');\n\n    $author = fetch(\'author\', \'txp_image\', \'id\', $id);\n\n    if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n        return;\n    }\n\n    $file = $_FILES[\'thefile\'][\'tmp_name\'];\n    $name = $_FILES[\'thefile\'][\'name\'];\n\n    $file = get_uploaded_file($file);\n\n    if (empty($file)) {\n        return;\n    }\n\n    list($w, $h, $extension) = getimagesize($file);\n\n    $valid_exts = array(\n        IMAGETYPE_GIF  => \'.gif\',\n        IMAGETYPE_JPEG => \'.jpg\',\n        IMAGETYPE_PNG  => \'.png\',\n    );\n\n    $ext = isset($valid_exts[$extension]) ? $valid_exts[$extension] : \'\';\n\n    if (($file !== false) && $profile && $ext) {\n        $newpath = IMPATH . sanitizeForUrl($profile) . DS . $id . $ext;\n        if (shift_uploaded_file($file, $newpath) === false) {\n            // Failed: do nothing.\n        } else {\n            @chmod($newpath, 0644);\n\n            // If the pref indicates, duplicate as a Textpattern thumb too.\n            if (get_pref(\'smd_thumb_txp_create\', \'0\') == 1) {\n                $txp_thumb = IMPATH . $id . \'t\' . $ext;\n\n                if (copy($newpath, $txp_thumb)) {\n                    safe_update(\'txp_image\', \"thumbnail = 1, thumb_w = $w, thumb_h = $h\", \'id = \' . $id);\n                    @chmod($txp_thumb, 0644);\n                }\n            }\n\n            $message = gTxt(\'image_uploaded\', array(\'{name}\' => $name));\n        }\n    }\n\n    // Since the headers have been sent, resort to JavaScript to refresh the page.\n    $urlPieces = array(\n        \'event\'         => \'image\',\n        \'step\'          => \'image_edit\',\n        \'id\'            => $id,\n        \'sort\'          => $sort,\n        \'dir\'           => $dir,\n        \'page\'          => $page,\n        \'search_method\' => $search_method,\n        \'crit\'          => $crit,\n    );\n\n    $url = html_entity_decode(join_qs($urlPieces));\n\n    echo <<<EOS\n<script type=\"text/javascript\">\nwindow.location.href=\"{$url}\";\n</script>\n<noscript>\n<meta http-equiv=\"refresh\" content=\"0;url={$url}\" />\n</noscript>\nEOS;\n    exit;\n}\n\n/**\n * pluggable_ui callback to render the additions to the image edit panel.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Textpattern step (action)\n * @param  string $dflt    Default markup ready to render\n * @param  array  $currimg Current image information\n */\nfunction smd_thumb_edit($evt, $stp, $dflt, $currimg)\n{\n    global $step, $file_max_upload_size, $txp_user;\n\n    extract(gpsa(array(\n        \'id\',\n        \'page\',\n        \'sort\',\n        \'dir\',\n        \'crit\',\n        \'search_method\',\n        \'smd_thumbnail_size\',\n        \'smd_thumbnail_chosen_size\',\n        \'smd_thumbnail_delete\',\n        \'smd_step\'\n    )));\n\n    $id = ($id) ? $id : $GLOBALS[\'ID\'];\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n\n    // Toggle profile panel.\n    if ($step === \'save_pane_state\') {\n        smd_thumbnail_save_pane_state();\n        return;\n    }\n\n    // Create/delete the selected thumbs depending on the button pressed.\n    if ($smd_step === \'smd_thumbnail_manage\') {\n        // Validate user.\n        $author = fetch(\'author\', \'txp_image\', \'id\', $id);\n\n        if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n            image_list(gTxt(\'restricted_area\'));\n\n            return;\n        }\n\n        // Grab the thumbnails to work on.\n        $where = ($smd_thumbnail_size === \'all\') ? \'1=1 AND flags & \' . SMD_THUMB_ACTIVE : \"name=\'\" . doSlash($smd_thumbnail_chosen_size) . \"\'\";\n        $rs = safe_rows(\'*\', SMD_THUMB, $where);\n\n        // Do it.\n        if ($smd_thumbnail_delete) {\n            $msg = smd_thumb_unmake($rs, $currimg);\n        } else {\n            $msg = smd_thumb_make($rs, $currimg, 1);\n        }\n    }\n\n    $ext = $currimg[\'ext\'];\n    echo script_js(<<<EOC\nfunction smd_thumb_selector(sel) {\n    var idx = 0;\nconsole.log(sel);\n    jQuery(\"#smd_thumbs img\").each(function() {\n        if (jQuery(this).hasClass(sel)) {\n            jQuery(this).toggleClass(\'smd_selected active\');\n            if (jQuery(this).hasClass(\'smd_selected\')) {\n                jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\n                jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', false);\n                jQuery(\"#smd_thumb_profile\").val(sel);\n                idx = jQuery(\"#smd_thumbnail_size option[value=\'\"+sel+\"\']\").index();\n                jQuery(\"#smd_thumbnail_chosen_size\").val(sel);\n            } else {\n                jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n                jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n                jQuery(\"#smd_thumb_profile\").val(\'\');\n                jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\n            }\n        } else {\n            jQuery(this).removeClass(\'smd_selected active\');\n        }\n    });\n    jQuery(\"#smd_thumbnail_size\").prop(\"selectedIndex\", idx);\n}\nfunction smd_thumb_select_changed() {\n    obj = jQuery(\"#smd_thumbnail_size\");\n    if (obj.attr(\"selectedIndex\") == 0) {\n        jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n        jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n        jQuery(\"#smd_thumb_profile\").val(\'\');\n        jQuery(\"#smd_thumbnail_chosen_size\").val(\'\');\n    } else {\n        jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', false);\n        jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', false);\n        jQuery(\"#smd_thumb_profile\").val(obj.val());\n        jQuery(\"#smd_thumbnail_chosen_size\").val(obj.val());\n    }\n    jQuery(\"#smd_thumbs img\").each(function() {\n        if (jQuery(this).hasClass(obj.val())) {\n            jQuery(this).addClass(\'smd_selected active\');\n        } else {\n            jQuery(this).removeClass(\'smd_selected active\');\n        }\n    });\n}\njQuery(function() {\n    jQuery(\"#smd_thumbs img\").each(function() {\n        var prf = jQuery(this).data(\'profile\');\n        jQuery(this).click(function() {\n            smd_thumb_selector(prf);\n        });\n    });\n    jQuery(\"#smd_upload_thumbnail\").attr(\'disabled\', true);\n    jQuery(\".smd_thumbnail-upload input[type=submit]\").attr(\'disabled\', true);\n    jQuery(\".smd_thumbnail-upload\").prepend(\'<input type=\"hidden\" name=\"smd_thumb_imgid\" value=\"{$id}\" /><input type=\"hidden\" id=\"smd_thumb_profile\" name=\"smd_thumb_profile\" value=\"\" /><input type=\"hidden\" id=\"smd_thumb_ext\" name=\"smd_thumb_ext\" value=\"{$ext}\" />\');\n});\nEOC\n    );\n\n    // Add thumbnails and creation controls.\n    if (smd_thumb_table_exist()) {\n        $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\n\n        if ($rs) {\n            $profiles = array(\'all\' => gTxt(\'smd_thumb_all_sizes\'));\n            $thumbs[] = \'<div id=\"smd_thumbs\">\';\n\n            foreach ($rs as $row) {\n                if ($row[\'flags\'] & SMD_THUMB_ACTIVE) {\n                    $profiles[$row[\'name\']] = $row[\'name\'];\n                }\n\n                $thumbs[] = smd_thumb_img($row, $currimg, array(\n                    \'class\' => \'content-image \' . $row[\'name\'],\n                    \'data-profile\' => $row[\'name\'],\n                    ));\n            }\n\n            $thumbs[] = \'</div>\';\n\n            $qs = array(\n                \"event\"         => \'image\',\n                \"step\"          => \'image_edit\',\n                \"id\"            => $id,\n                \"page\"          => $page,\n                \"sort\"          => $sort,\n                \"dir\"           => $dir,\n                \"crit\"          => $crit,\n                \"search_method\" => $search_method,\n            );\n\n            $out[] = upload_form(gTxt(\'smd_thumb_upload\'), \'\', \'smd_thumbnail_insert\', \'image\', $id, $file_max_upload_size, \'smd_upload_thumbnail\', \'smd_thumbnail-upload\');\n            $out[] = \'<form name=\"smd_thumbnail_create\" method=\"post\" action=\"\'.join_qs($qs).\'\">\'.n.\'<p>\';\n            $out[] = fInput(\'hidden\', \'smd_step\', \'smd_thumbnail_manage\');\n            $out[] = fInput(\'hidden\', \'smd_thumbnail_chosen_size\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumbnail_chosen_size\');\n            $out[] = selectInput(\'smd_thumbnail_size\', $profiles, \'\', \'\', \' onchange=\"return smd_thumb_select_changed()\";\', \'smd_thumbnail_size\');\n            $out[] = fInput(\'submit\', \'\', gTxt(\'create\'), \'smd_thumbnail-create\');\n            $out[] = fInput(\'submit\', \'smd_thumbnail_delete\', gTxt(\'delete\'), \'smd_thumbnail-delete\');\n            $out[] = join(\'\', $thumbs);\n            $out[] = \'</p>\'.n.\'</form>\';\n\n            return join(n, $out);\n        }\n    }\n\n    return \' \';\n}\n\n/**\n * Create an &ltimg&gt tag to a thumbnail.\n *\n * @param  array  $row     Thumbnail information\n * @param  array  $currimg Corresponding image information\n * @param  array  $meta    Thumbnail meta info\n * @param  string $dsp     Textpattern Form that contains rendering information\n * @return string\n */\nfunction smd_thumb_img($row, $currimg, $meta = array(), $dsp = \'\')\n{\n    global $img_dir, $smd_thumb_data;\n\n    smd_thumb_set_impath();\n\n    $dir = sanitizeForUrl($row[\'name\']);\n    $id = $currimg[\'id\'];\n    $ext = $currimg[\'ext\'];\n    $alt = $currimg[\'alt\'];\n\n    // alt is a mandatory attribute so make sure it exists (even if it\'s \"\").\n    if (!isset($meta[\'alt\'])) {\n        $meta[\'alt\'] = $currimg[\'alt\'];\n    }\n\n    $path = IMPATH . $dir . DS . $id . $ext;\n\n    if (file_exists($path)) {\n        $extras = \'\';\n\n        if (isset($meta[\'forcew\']) || isset($meta[\'forceh\'])) {\n            $dims = getimagesize($path);\n\n            if (isset($meta[\'forcew\']) && !$row[\'width\']) {\n                $row[\'width\'] = $dims[0];\n            }\n\n            if (isset($meta[\'forceh\']) && !$row[\'height\']) {\n                $row[\'height\'] = $dims[1];\n            }\n\n            unset($meta[\'forcew\']);\n            unset($meta[\'forceh\']);\n        }\n\n        if (isset($meta[\'width\']) && $meta[\'width\'] !== \'\') {\n            $row[\'width\'] = $meta[\'width\'];\n        }\n\n        if (isset($meta[\'height\']) && $meta[\'height\'] !== \'\') {\n            $row[\'height\'] = $meta[\'height\'];\n        }\n\n        unset($meta[\'width\']);\n        unset($meta[\'height\']);\n\n        // \'empty\' includes zero dimensions which omits the attribute and allows the browser to scale.\n        $w = (!empty($row[\'width\'])) ? \' width=\"\'.$row[\'width\'].\'\"\': \'\';\n        $h = (!empty($row[\'height\'])) ? \' height=\"\'.$row[\'height\'].\'\"\': \'\';\n\n        $uDate = \'\';\n\n        if (!isset($meta[\'stamp\'])) {\n            $uDate = \'?\' . filemtime($path);\n        }\n\n        unset($meta[\'stamp\']);\n\n        if (!isset($meta[\'class\'])) {\n            $meta[\'class\'] = $dir;\n        }\n\n        foreach ($meta as $key => $val) {\n            // We need all atts for container tags, but only valid HTML atts should appear in the default <img> tag.\n            if (in_array($key, array(\'alt\', \'class\', \'title\')) || strpos($key, \'data-\') === 0) {\n                $extras .= \' \'.$key.\'=\"\'.$val.\'\"\';\n            }\n        }\n\n        if ($dsp) {\n            // Hand off to the form/container for formatting advice.\n            $smd_thumb_data = $meta;\n            $smd_thumb_data[\'id\'] = $id;\n            $smd_thumb_data[\'ext\'] = $ext;\n            $smd_thumb_data[\'alt\'] = $alt;\n            $smd_thumb_data[\'w\'] = $row[\'width\'];\n            $smd_thumb_data[\'h\'] = $row[\'height\'];\n            $smd_thumb_data[\'html_w\'] = $w;\n            $smd_thumb_data[\'html_h\'] = $h;\n            return parse($dsp);\n        } else {\n            return \'<img src=\"\'.ihu.$img_dir.\'/\'.$dir.\'/\'.$id.$ext.$uDate.\'\"\'.$w.$h.$extras.\'>\';\n        }\n    }\n\n    return \'\';\n}\n\n/**\n * Callback to render profiles control area on Images panel.\n *\n * @param  string $evt     Textpattern event (panel)\n * @param  string $stp     Texpattern step (action)\n * @param  string $dflt    Default content HTML\n * @param  array  $imglist Record set of all images in the list\n * @return string          HTML\n */\nfunction smd_thumb_profiles($evt, $stp, $dflt, $imglist)\n{\n    global $smd_thumb_event, $step, $smd_thumb_prevs, $txp_user;\n\n    if (!has_privs(__FUNCTION__)) {\n        return;\n    }\n\n    extract(gpsa(array(\n        \'page\',\n        \'sort\',\n        \'dir\',\n        \'crit\',\n        \'search_method\',\n        \'smd_thumb_add\',\n        \'smd_thumb_cancel\',\n        \'smd_thumb_save\',\n        \'smd_thumb_name\',\n        \'smd_thumb_newname\',\n        \'smd_thumb_add_newname\',\n        \'smd_thumb_description\',\n        \'smd_thumb_add_description\',\n        \'smd_thumb_width\',\n        \'smd_thumb_add_width\',\n        \'smd_thumb_height\',\n        \'smd_thumb_add_height\',\n        \'smd_thumb_quality\',\n        \'smd_thumb_add_quality\',\n        \'smd_thumb_active\',\n        \'smd_thumb_active_new\',\n        \'smd_thumb_add_active_new\',\n        \'smd_thumb_crop\',\n        \'smd_thumb_add_crop\',\n        \'smd_thumb_sharpen\',\n        \'smd_thumb_add_sharpen\',\n        \'smd_thumb_default\',\n        \'smd_thumb_add_default\',\n        \'smd_thumb_selected\',\n        \'smd_thumb_cat_selected\',\n        \'smd_thumb_usr_selected\',\n        \'smd_thumb_group_type\',\n    )));\n\n    if ($smd_thumb_add) {\n        $smd_thumb_newname = $smd_thumb_add_newname;\n        $smd_thumb_description = $smd_thumb_add_description;\n        $smd_thumb_width = $smd_thumb_add_width;\n        $smd_thumb_height = $smd_thumb_add_height;\n        $smd_thumb_quality = $smd_thumb_add_quality;\n        $smd_thumb_active_new = $smd_thumb_add_active_new;\n        $smd_thumb_crop = $smd_thumb_add_crop;\n        $smd_thumb_sharpen = $smd_thumb_add_sharpen;\n        $smd_thumb_default = $smd_thumb_add_default;\n    }\n\n    // Sanitize.\n    $search_method = (is_array($search_method)) ? implode(\',\', $search_method) : $search_method;\n    $quality = (is_numeric($smd_thumb_quality)) ? (($smd_thumb_quality < 0) ? 75 : (($smd_thumb_quality > 100) ? 75 : $smd_thumb_quality) ) : 75;\n    $width = (is_numeric($smd_thumb_width)) ? $smd_thumb_width : 0;\n    $height = (is_numeric($smd_thumb_height)) ? $smd_thumb_height : 0;\n    $newname = sanitizeForUrl($smd_thumb_newname);\n    $msg = \'\';\n\n    smd_thumb_set_impath();\n\n    // Table control.\n    if (gps(\'remove\')) {\n        smd_thumb_table_remove();\n    } else {\n        if (!smd_thumb_table_exist()) {\n            smd_thumb_table_install();\n        }\n    }\n\n    // Action to toggle profile panel.\n    if ($step === \'save_pane_state\') {\n        smd_thumbnail_save_pane_state();\n        return;\n    }\n\n    // Action to create thumbnails.\n    if ($step === \'smd_thumb_create_group\') {\n        switch ($smd_thumb_group_type) {\n            case \'all\':\n                smd_thumb_create_group(\'all\');\n                break;\n            case \'sel\':\n                smd_thumb_create_group(\'sel\', rtrim($smd_thumb_selected, \',\'));\n                break;\n            case \'cat\':\n                smd_thumb_create_group(\'cat\', $smd_thumb_cat_selected);\n                break;\n            case \'usr\':\n                smd_thumb_create_group(\'usr\', $smd_thumb_usr_selected);\n                break;\n        }\n\n        set_pref(\'pane_smd_thumb_group_type\', $smd_thumb_group_type, \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\n        $step = \'smd_thumb_prefs\';\n    }\n\n    // Action to save profile.\n    if ($step === \'smd_thumb_profile_save\') {\n        if (smd_thumb_table_exist()) {\n            if ($smd_thumb_cancel) {\n                // Do nothing.\n            } elseif ($smd_thumb_add) {\n                // Create new profile.\n                $exists = safe_row(\'*\', SMD_THUMB, \"name=\'\" . doSlash($newname) . \"\'\");\n\n                if ($exists) {\n                    $msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\n                } else {\n                    if ($newname) {\n                        $flags = 0;\n                        $flags = ($smd_thumb_active_new) ? $flags | SMD_THUMB_ACTIVE : $flags;\n                        $flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\n                        $flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\n\n                        safe_insert(\n                            SMD_THUMB,\n                            \"name=\'\" . doSlash($newname) . \"\'\"\n                            . \", description=\'\" . doSlash($smd_thumb_description) . \"\'\"\n                            . \", width=\'\" . doSlash($width) . \"\'\"\n                            . \", height=\'\" . doSlash($height) . \"\'\"\n                            . \", quality=\'\" . doSlash($quality) . \"\'\"\n                            . \", flags=\'\" . doSlash($flags) . \"\'\"\n                        );\n\n                        // Alter the default thumbnail.\n                        if ($smd_thumb_default) {\n                            set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\n                        }\n\n                        // Create the thumbnail directory with the same permissions as\n                        // the parent (images) directory.\n                        // Requires much back-and-forth number conversion.\n                        @mkdir(IMPATH.$newname);\n                        @chmod(IMPATH.$newname, octdec(decoct(fileperms(IMPATH) & 0777)));\n                    }\n                }\n            } else {\n                // Update existing profile.\n                $name = sanitizeForUrl($smd_thumb_name);\n                $exists = safe_row(\'*\', SMD_THUMB, \"name=\'\" . doSlash($newname) . \"\'\");\n\n                if ($exists && strtolower($newname) !== strtolower($name)) {\n                    $msg = gTxt(\'smd_thumb_profile_exists\', array(\'{name}\' => doSlash($newname)));\n                } else {\n                    if ($newname) {\n                        $flags = 0;\n                        $flags = ($smd_thumb_active) ? $flags | SMD_THUMB_ACTIVE : $flags;\n                        $flags = ($smd_thumb_crop) ? $flags | SMD_THUMB_CROP : $flags;\n                        $flags = ($smd_thumb_sharpen) ? $flags | SMD_THUMB_SHARP : $flags;\n\n                        safe_update(\n                            SMD_THUMB,\n                            \"name=\'\" . doSlash($newname) . \"\'\"\n                            . \", description=\'\" . doSlash($smd_thumb_description) . \"\'\"\n                            . \", width=\'\" . doSlash($width) . \"\'\"\n                            . \", height=\'\" . doSlash($height) . \"\'\"\n                            . \", quality=\'\" . doSlash($quality) . \"\'\"\n                            . \", flags=\'\" . doSlash($flags) . \"\'\"\n                            , \"name=\'\" . doSlash($name) . \"\'\"\n                        );\n\n                        // Alter the default thumbnail.\n                        if ($smd_thumb_default) {\n                            set_pref(\'smd_thumb_default_profile\', $newname, \'smd_thumb\', PREF_HIDDEN);\n                        } else {\n                            // Remove the default flag if this used to be the default.\n                            if (get_pref(\'smd_thumb_default_profile\') === doSlash($name)) {\n                                set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\n                            }\n                        }\n\n                        // Adjust the thumbnail directory if the name has changed.\n                        if ($newname != $name) {\n                            rename(IMPATH.$name, IMPATH.$newname);\n                        }\n                    }\n                }\n            }\n        } else {\n            $msg = gTxt(\'smd_thumb_tables_not_installed\');\n        }\n\n        $step = \'smd_thumb_profile\';\n    }\n\n    // Action to delete a profile.\n    if ($step === \'smd_thumb_profile_delete\') {\n        if (smd_thumb_table_exist()) {\n            $txp_del = get_pref(\'smd_thumb_txp_delete\');\n            $the_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            if (!empty($smd_thumb_name)) {\n                $name = sanitizeForUrl($smd_thumb_name);\n                $ret = safe_delete(SMD_THUMB, \"name=\'\" . doSlash($name) . \"\'\");\n\n                if ($ret) {\n                    smd_thumb_rmdir(IMPATH.$name);\n\n                    // Delete all Textpattern thumbs too?\n                    if (($name == $the_dflt) && ($txp_del == \'1\')) {\n                        $rs = safe_rows(\'id, ext\', \'txp_image\', \'thumbnail = 1\');\n\n                        foreach ($rs as $row) {\n                            $path = IMPATH . $row[\'id\'] . \'t\' . $row[\'ext\'];\n\n                            if (file_exists($path)) {\n                                unlink($path);\n                            }\n                        }\n\n                        safe_update(\'txp_image\', \'thumbnail = 0, thumb_w = 0, thumb_h = 0\', \'thumbnail = 1\');\n                    }\n\n                    // Remove the default flag if this used to be the default.\n                    if (get_pref(\'smd_thumb_default_profile\') === doSlash($name)) {\n                        set_pref(\'smd_thumb_default_profile\', \'\', \'smd_thumb\', PREF_HIDDEN);\n                    }\n\n                    $msg = gTxt(\'smd_thumb_profile_deleted\', array(\'{name}\' => doSlash($name)));\n                }\n            }\n        }\n\n        $step = \'smd_thumb_profile\';\n    }\n\n    $qs = array(\n        \"event\"         => \'image\',\n        \"page\"          => $page,\n        \"sort\"          => $sort,\n        \"dir\"           => $dir,\n        \"crit\"          => $crit,\n        \"search_method\" => $search_method,\n    );\n\n    $privs = safe_field(\'privs\', \'txp_users\', \"name = \'\" . doSlash($txp_user) . \"\'\");\n    $rights = in_array($privs, $smd_thumb_prevs) ? true : false;\n\n    // Action to show thumbnail prefs.\n    if ($step === \'smd_thumb_prefs\') {\n        if (smd_thumb_table_exist()) {\n            $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            // Subselect lists for type \'cat\' and \'usr\'.\n            $rs = getTree(\'root\', \'image\');\n            $allCats = $rs ? treeSelectInput(\'smd_thumb_cat_selected\', $rs, \'\') : \'\';\n            $allCats = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allCats))).\"<\\/span>\";\n            $rs = safe_column(\'name\', \'txp_users\', \"privs not in(0,6) order by name asc\");\n            $allUsrs = $rs ? selectInput(\'smd_thumb_usr_selected\', $rs, \'\', true) : \'\';\n            $allUsrs = \"<span>\".str_replace(array(\"\\n\", \'-\'), array(\'\', \'&#45;\'), str_replace(\'</\', \'<\\/\', addslashes($allUsrs))).\"<\\/span>\";\n\n            echo script_js(<<<EOC\nfunction smd_thumb_switch_pref(obj, name) {\n    if (name == \'create_from\') {\n        state = jQuery(obj).val();\n    } else {\n        state = (jQuery(obj).prop(\'checked\')) ? 1 : 0;\n    }\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_switch_pref\',\n        smd_thumb_txptype: name,\n        smd_thumb_state: state\n    });\n}\nfunction smd_thumb_copy_selected() {\n    jQuery(\"#images_form tbody .multi-edit input:checked, #images_form tbody .txp-list-col-multi-edit input:checked\").each(function() {\n        jQuery(\"#smd_thumb_selected\").val(jQuery(\"#smd_thumb_selected\").val() + jQuery(this).val() + \',\');\n    });\n}\nfunction smd_thumb_subsel(obj) {\n    obj = jQuery(obj);\n    dest = jQuery(\'#smd_thumb_subsel\');\n    item = obj.val();\n    switch (item) {\n        case \'all\':\n        case \'sel\':\n            dest.empty();\n            break;\n        case \'cat\':\n            dest.empty().prepend(\'{$allCats}\');\n            break;\n        case \'usr\':\n            dest.empty().prepend(\'{$allUsrs}\');\n            break;\n    }\n}\njQuery(function() {\n    jQuery(\'#smd_thumb_group_type\').change();\n});\nEOC\n        );\n\n            $btnPnl = \'<p><a href=\"?event=image\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\"><span class=\"ui-icon ui-icon-arrowreturnthick-1-w\"></span>  \'.gTxt(\'smd_thumb_btn_pnl\').\'</a></p>\';\n            $btnGrp = \'<button class=\"navlink\" type=\"submit\" onclick=\"smd_thumb_copy_selected(); return confirm(\\\'\'.gTxt(\'smd_thumb_create_group_confirm\').\'\\\');\">\'.gTxt(\'smd_thumb_all_thumbs\').\'</button>\';\n            $grpTypes = array(\n                \'sel\' => gTxt(\'smd_thumb_bysel\'),\n                \'cat\' => gTxt(\'category\'),\n                \'usr\' => gTxt(\'author\'),\n                \'all\' => gTxt(\'smd_thumb_byall\'),\n            );\n\n            $grpOpts = selectInput(\'smd_thumb_group_type\', $grpTypes, get_pref(\'pane_smd_thumb_group_type\', \'all\', 1), \'\', \' onchange=\"smd_thumb_subsel(this);\"\', \'smd_thumb_group_type\').\'<span id=\"smd_thumb_subsel\"></span> \';\n\n            $out[] = \'<section class=\"txp-details\" id=\"smd_thumb_profiles\">\';\n            $out[] = \'<h3 class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.(($rights) ? gTxt(\'smd_thumb_profile_preftool_heading\') : gTxt(\'smd_thumb_profile_tool_heading\')).\'</a></h3>\'.\n                n.\'<div class=\"toggle\" id=\"smd_thumbnail_profiles\" role=\"region\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\n            $out[] = $btnPnl;\n            $out[] = \'<div id=\"smd_thumb_batch\"><span id=\"smd_thumb_bcurr\"></span><span id=\"smd_thumb_btot\"></span></div>\';\n            $out[] = \'<form method=\"post\" name=\"smd_thumb_multi_edit\" id=\"smd_thumb_multi_edit\" action=\"\'.join_qs($qs).\'\">\';\n\n            $out[] = \'<p><label for=\"smd_thumb_group_type\">\'.gTxt(\'smd_thumb_batch_preamble\').\'</label>\'.$grpOpts.$btnGrp.\'</p>\';\n\n            $out[] = fInput(\'hidden\', \'smd_thumb_selected\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_thumb_selected\');\n            $out[] = eInput($smd_thumb_event);\n            $out[] = sInput(\'smd_thumb_create_group\');\n            $out[] = \'</form>\';\n\n            if ($rights) {\n                $out[] = \'<form class=\"txp-prefs-group\" method=\"post\" name=\"smd_thumb_prefs\" id=\"smd_thumb_prefs_form\" action=\"\'.join_qs($qs).\'\">\';\n\n                $txp_rf = get_pref(\'smd_thumb_create_from\', \'full\');\n                $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_create_from\');\n                $out[] = n.\'<label>\n                            <input class=\"radio\" type=\"radio\" name=\"smd_thumb_create_from\" value=\"full\"\'.\n                                (($txp_rf==\'full\') ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\">\'.\n                                n.gTxt(\'smd_thumb_txp_create_from_full\').\n                        \'</label>\'.\n                        n.\'<label>\n                            <input class=\"radio\" type=\"radio\" name=\"smd_thumb_create_from\" value=\"thumb\"\'.\n                                (($txp_rf==\'thumb\') ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'create_from\\\');\">\'.\n                                n.gTxt(\'smd_thumb_txp_create_from_thumb\').\n                        \'</label>\';\n                $out[] = \'</p>\';\n\n                if ($pro_dflt) {\n                    $txp_c = get_pref(\'smd_thumb_txp_create\');\n                    $txp_d = get_pref(\'smd_thumb_txp_delete\');\n                    $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_default_sync\');\n                    $out[] = n.\'<label>\n                        <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_txp_create\" value=\"1\"\'.\n                                (($txp_c) ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'txp_create\\\');\">\'.\n                                n.gTxt(\'smd_thumb_create\').\n                        \'</label>\'.\n                        n.\'<label>\n                            <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_txp_delete\" value=\"1\"\'.\n                                (($txp_d) ? \' checked=\"checked\"\' : \'\').\n                                \' onchange=\"smd_thumb_switch_pref(this, \\\'txp_delete\\\');\">\'.\n                                n.gTxt(\'smd_thumb_delete\').\n                        \'</label>\';\n                    $out[] = \'</p>\';\n                }\n\n                $txp_ar = get_pref(\'smd_thumb_auto_replace\');\n                $out[] = \'<p>\'.gTxt(\'smd_thumb_txp_auto_replace\');\n                $out[] = n.\'<label>\n                        <input class=\"checkbox\" type=\"checkbox\" name=\"smd_thumb_auto_replace\" value=\"1\"\'.\n                            (($txp_ar) ? \' checked=\"checked\"\' : \'\').\n                            \' onchange=\"smd_thumb_switch_pref(this, \\\'auto_replace\\\');\">\'.\n                            n.gTxt(\'yes\').\n                    \'</label>\';\n                $out[] = \'</p>\';\n\n                $out[] = \'</form>\';\n            }\n            $out[] = \'</section>\';\n        }\n    // Action to show thumbnail profile list.\n    } else {\n        $editFocus = ($step === \'smd_thumb_profile_edit\') ? \'jQuery(\"#smd_thumb_profile_edited input[name=\\\'smd_thumb_newname\\\']\").focus();\' : \'\';\n\n        echo script_js(<<<EOC\nfunction smd_thumb_togglenew() {\n    box = jQuery(\"#smd_thumb_profile_create\");\n    box.toggleClass(\"smd_hidden smd_thumb_new_profile\");\n    jQuery(\"input.smd_focus\").focus();\n    return false;\n}\nfunction smd_thumb_switch_active(name) {\n    sendAsyncEvent(\n    {\n        event: textpattern.event,\n        step: \'smd_thumb_switch_active\',\n        smd_thumb_profile: name\n    }, smd_thumb_switch_active_done);\n}\nfunction smd_thumb_switch_active_done(data) {\n    var smd_thumb_profile = jQuery(\'#smd_thumb_profile_\'+jQuery(data).find(\'smd_thumb_profile\').attr(\'value\'));\n    smd_thumb_profile.toggleClass(\'smd_inactive\');\n    if (smd_thumb_profile.hasClass(\'smd_inactive\')) {\n        smd_thumb_profile.find(\'input:checkbox\').removeAttr(\'checked\');\n    } else {\n        smd_thumb_profile.find(\'input:checkbox\').attr(\'checked\', \'checked\');\n    }\n}\n\njQuery(function() {\n    jQuery(\'.smd_thumb_heading_active\').click(function() {\n        jQuery(\'.smd_thumb_row input:checkbox\').each(function() {\n            smd_thumb_switch_active(jQuery(this).parent().attr(\'class\'));\n        });\n    });\n    {$editFocus}\n});\nEOC\n        );\n\n        $btnNew = \'<p><a class=\"txp-button\" href=\"#\" onclick=\"return smd_thumb_togglenew();\">\'.gTxt(\'smd_thumb_new\').\'</a></p>\';\n        $btnPref = \'<p class=\"txp-list-options\"><a href=\"?event=image\'.a.\'step=smd_thumb_prefs\'.a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit.\'\"><span class=\"ui-icon ui-icon-wrench\"></span> \'.(($rights) ? gTxt(\'smd_thumb_btn_tools_prefs\') : gTxt(\'smd_thumb_btn_tools\')).\'</a></p>\';\n        $btnCancel = fInput(\'submit\', \'smd_thumb_cancel\', gTxt(\'cancel\'));\n\n        $headings = \'<thead>\'.tr(\n            column_head(\'name\', \'name\', \'image\', false, \'asc\').\n            column_head(\'description\', \'description\', \'image\', false).\n            column_head(\'thumb_width\', \'width\', \'image\', false).\n            column_head(\'thumb_height\', \'height\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_quality\'), \'quality\', \'image\', false).\n            column_head(\'keep_square_pixels\', \'crop\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_sharpen\'), \'sharpen\', \'image\', false).\n            column_head(\'active\', \'active\', \'image\', false, \'\', \'\', \'\', \'smd_thumb_heading_active\').\n            column_head(\'default\', \'default\', \'image\', false).\n            column_head(gTxt(\'smd_thumb_actions\'), \'actions\', \'image\', false)\n        ).n.\'</thead>\';\n\n        $out[] = \'<section class=\"txp-details\" id=\"smd_thumb_profiles\">\';\n        $out[] = \'<h3 class=\"txp-summary lever\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_thumbnail_profiles\">\'.gTxt(\'smd_thumb_profile_heading\').\'</a></h3>\'.\n            n.\'<div class=\"toggle\" id=\"smd_thumbnail_profiles\" role=\"region\" style=\"display:\'.(get_pref(\'pane_smd_thumbnail_profiles_visible\') ? \'block\' : \'none\').\'\">\';\n\n        // Main list of profiles.\n        $out[] = \'<form method=\"post\" name=\"smd_thumb_profile_form\" id=\"smd_thumb_profile_form\" action=\"\'.join_qs($qs).\'\">\';\n        $out[] = tag_start(\'div\', array(\'class\' => \'txp-listtables\'));\n        $out[] = tag_start(\'table\', array(\'class\' => \'txp-list--no-options\'));\n        $out[] = $headings;\n        $out[] = tag_start(\'tbody\');\n\n        if (smd_thumb_table_exist()) {\n            $rs = safe_rows(\'*\', SMD_THUMB, \'1=1 ORDER BY name\');\n            $pro_dflt = get_pref(\'smd_thumb_default_profile\', \'\', 1);\n\n            // Row for each currently-defined profile.\n            foreach ($rs as $row) {\n                $link_edt = join_qs($qs).a.\'step=smd_thumb_profile_edit\'.a.\'smd_thumb_name=\'.$row[\'name\'];\n                $link_del = join_qs($qs).a.\'step=smd_thumb_profile_delete\'.a.\'smd_thumb_name=\'.$row[\'name\'];\n                $btnEdt = \'<a href=\"\'.$link_edt.\'\">[\' . gTxt(\'edit\') . \']</a>\';\n                $btnDel = \'<a href=\"\'.$link_del.\'\" onclick=\"return confirm(\\\'\'.gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])).\'\\\');\">[\' . gTxt(\'delete\') . \']</a>\';\n                $btnDel = href(gTxt(\'delete\'), array(\n                    \'event\'          => \'image\',\n                    \'step\'           => \'smd_thumb_profile_delete\',\n                    \'smd_thumb_name\' => $row[\'name\'],\n                    \'_txp_token\'     => form_token(),\n                    \'page\'           => $page,\n                    \'sort\'           => $sort,\n                    \'dir\'            => $dir,\n                    \'crit\'           => $crit,\n                    \'search_method\'  => $search_method,\n                ), array(\n                    \'class\'       => \'destroy ui-icon ui-icon-close\',\n                    \'title\'       => gTxt(\'delete\'),\n                    \'data-verify\' => gTxt(\'smd_thumb_delete_confirm\', array(\"{name}\" => $row[\'name\'])),\n                ));\n\n                $active = ($row[\'flags\'] & SMD_THUMB_ACTIVE) ? 1 : 0;\n                $crop = ($row[\'flags\'] & SMD_THUMB_CROP) ? 1 : 0;\n                $sharpen = ($row[\'flags\'] & SMD_THUMB_SHARP) ? 1 : 0;\n\n                if ($step == \'smd_thumb_profile_edit\' && $row[\'name\'] == $smd_thumb_name) {\n                    $btnSave = fInput(\'submit\', \'smd_thumb_save\', gTxt(\'save\'));\n                    $out[] = tr(\n                        tda(hInput(\'smd_thumb_name\', $row[\'name\']).fInput(\'text\', \'smd_thumb_newname\', $row[\'name\']), array(\'data-th\' => gTxt(\'name\')), \'\', \'\', INPUT_MEDIUM).\n                        tda(fInput(\'text\', \'smd_thumb_description\', $row[\'description\'], \'\', \'\', \'\', INPUT_REGULAR), array(\'data-th\' => gTxt(\'description\'))).\n                        tda(fInput(\'text\', \'smd_thumb_width\', $row[\'width\'], \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_width\'))).\n                        tda(fInput(\'text\', \'smd_thumb_height\', $row[\'height\'], \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_height\'))).\n                        tda(fInput(\'text\', \'smd_thumb_quality\', $row[\'quality\'], \'\', \'\', \'\', 3), array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                        tda(checkbox(\'smd_thumb_crop\', \'1\', $crop), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                        tda(checkbox(\'smd_thumb_sharpen\', \'1\', $sharpen), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                        tda(checkbox(\'smd_thumb_active\', \'1\', $active), array(\'data-th\' => gTxt(\'active\'), \'class\' => $row[\'name\'])).\n                        tda(checkbox(\'smd_thumb_default\', \'1\', (($row[\'name\'] == $pro_dflt) ? 1 : 0)), array(\'data-th\' => gTxt(\'default\'))).\n                        tda($btnSave.$btnCancel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')))\n                    , \' id=\"smd_thumb_profile_edited\"\');\n                } else {\n                    $out[] = tr(\n                        tda(href($row[\'name\'], $link_edt), array(\'data-th\' => gTxt(\'name\'))).\n                        tda($row[\'description\'], array(\'data-th\' => gTxt(\'description\'))).\n                        tda($row[\'width\'], array(\'data-th\' => gTxt(\'thumb_width\'))).\n                        tda($row[\'height\'], array(\'data-th\' => gTxt(\'thumb_height\'))).\n                        tda($row[\'quality\'], array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                        tda(($crop) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                        tda(($sharpen) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                        tda(\'<input type=\"checkbox\" name=\"smd_thumb_active_\'.$row[\'name\'].\'\" value=\"1\"\'.(($active) ? \' checked=\"checked\"\' : \'\'). \' class=\"checkbox\" onchange=\"smd_thumb_switch_active(\\\'\'.$row[\'name\'].\'\\\');\" />\', array(\'data-th\' => gTxt(\'active\'), \'class\' => $row[\'name\'])).\n                        tda(($row[\'name\'] == $pro_dflt) ? gTxt(\'yes\') : gTxt(\'no\'), array(\'data-th\' => gTxt(\'default\'))).\n                        tda($btnDel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')))\n                    , \' id=\"smd_thumb_profile_\'.$row[\'name\'].\'\" class=\"smd_thumb_row\' . (($active) ? \'\' : \' smd_inactive\').\'\"\');\n                }\n            }\n\n            // New Profile row.\n            $out[]= \'<tr id=\"smd_thumb_profile_create\" class=\"smd_hidden\">\';\n            $out[] = tda(sInput(\'smd_thumb_profile_save\').fInput(\'text\', \'smd_thumb_add_newname\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_name : \'\'), \'smd_focus\', \'\', \'\', INPUT_MEDIUM), array(\'data-th\' => gTxt(\'name\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_description\', (($step === \'smd_thumb_profile_save\') ? $description : \'\'), \'\', \'\', \'\', INPUT_REGULAR), array(\'data-th\' => gTxt(\'description\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_width\', (($step === \'smd_thumb_profile_save\') ? $width : \'\'), \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_width\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_height\', (($step === \'smd_thumb_profile_save\') ? $height : \'\'), \'\', \'\', \'\', INPUT_XSMALL), array(\'data-th\' => gTxt(\'thumb_height\'))).\n                tda(fInput(\'text\', \'smd_thumb_add_quality\', (($step === \'smd_thumb_profile_save\') ? $quality : \'\'), \'\', \'\', \'\', 3), array(\'data-th\' => gTxt(\'smd_thumb_quality\'))).\n                tda(checkbox(\'smd_thumb_add_crop\', \'1\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_crop : 0)), array(\'data-th\' => gTxt(\'keep_square_pixels\'))).\n                tda(checkbox(\'smd_thumb_add_sharpen\', \'1\', (($step === \'smd_thumb_profile_save\') ? $smd_thumb_sharpen : 0)), array(\'data-th\' => gTxt(\'smd_thumb_sharpen\'))).\n                tda(checkbox(\'smd_thumb_add_active_new\', \'1\', 1), array(\'data-th\' => gTxt(\'active\'))).\n                tda(checkbox(\'smd_thumb_add_default\', \'1\', 0), array(\'data-th\' => gTxt(\'default\'))).\n                tda(fInput(\'submit\', \'smd_thumb_add\', gTxt(\'add\')).$btnCancel, array(\'data-th\' => gTxt(\'smd_thumb_actions\')));\n            $out[]= \'</tr>\';\n\n        }\n\n        $out[] = tag_end(\'tbody\');\n        $out[] = tag_end(\'table\');\n        $out[] = tag_end(\'div\'); // End of .txp-listtables.\n        $out[] = $btnPref;\n        $out[] = $btnNew;\n        $out[] = \'</form>\';\n        $out[] = \'</section>\';\n    }\n\n    return join(n, $out);\n}\n\n/**\n * Useful function for other plugins to use to retrieve a list of current profiles.\n *\n * @param  integer $active Whether to only return active profiles (1) or all of them (0)\n * @param  string  $order  The order in which to return the list\n * @return array\n */\nfunction smd_thumb_get_profiles($active = 1, $order = \'name\')\n{\n    $allowedOrders = array(\'name\', \'width\', \'height\',\'quality\');\n    $order = in_array($order, $allowedOrders) ? $order : \'name\';\n    $where[] = \'1=1\';\n\n    if ($active) {\n        $where[] = \'flags & \' . SMD_THUMB_ACTIVE;\n    }\n\n    $rs = safe_rows(\'*\', SMD_THUMB, join(\' AND \', $where) . \' ORDER BY \'.$order);\n\n    return $rs;\n}\n\n/**\n * Delete a thumbnail directory and its contents.\n *\n * @param  string $dir Directory to remove.\n */\nfunction smd_thumb_rmdir($dir)\n{\n    if (is_dir($dir)) {\n        $objects = scandir($dir);\n\n        foreach ($objects as $object) {\n            if ($object != \".\" && $object != \"..\") {\n                if (filetype($dir.DS.$object) == \"dir\") smd_thumb_rmdir($dir.DS.$object); else unlink($dir.DS.$object);\n            }\n        }\n\n        reset($objects);\n        rmdir($dir);\n    }\n}\n\n/**\n * Set the global image path if not already set.\n */\nfunction smd_thumb_set_impath()\n{\n    global $path_to_site, $img_dir;\n\n    if (!defined(\'IMPATH\')) {\n        define(\'IMPATH\', $path_to_site . DS . $img_dir . DS);\n    }\n}\n\n/**\n * Add thumbnail table if not already installed.\n *\n * Flags: Bit 0=Active / 1=Crop / 2=Sharpen\n */\nfunction smd_thumb_table_install()\n{\n    global $DB;\n\n    $tableDef = \"\n        name        VARCHAR(32)  NOT NULL DEFAULT \'\',\n        description VARCHAR(255) NOT NULL DEFAULT \'\',\n        width       INT(4)       NOT NULL DEFAULT \'0\',\n        height      INT(4)       NOT NULL DEFAULT \'0\',\n        quality     TINYINT(2)   NULL     DEFAULT \'75\',\n        flags       TINYINT(2)   NOT NULL DEFAULT \'0\',\n        PRIMARY KEY (name)\n    \";\n\n    if (!safe_create(SMD_THUMB, $tableDef)) {\n        echo mysqli_error($DB->link);\n    }\n\n    // Upgrades.\n    // -> to v0.4.0: Add the description column.\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_THUMB.\'`\');\n\n    if (!in_array(\'description\', $flds)) {\n        safe_alter(SMD_THUMB, \"ADD `description` VARCHAR(255) NOT NULL DEFAULT \'\' AFTER `name`\");\n    }\n\n    // Backup the current thumb prefs.\n    $exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\n\n    if (!$exists) {\n        set_pref(\'smd_thumb_backup_w\', get_pref(\'thumb_w\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 0);\n        set_pref(\'smd_thumb_backup_h\', get_pref(\'thumb_h\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 1);\n        set_pref(\'smd_thumb_backup_c\', get_pref(\'thumb_crop\'), \'smd_thumb\', PREF_HIDDEN, \'text_input\', 2);\n\n        // Erase the current thumb prefs.\n        set_pref(\'thumb_w\', \'\', \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_h\', \'\', \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_crop\', \'\', \'image\', PREF_HIDDEN);\n    }\n}\n\n/**\n * Drop thumbnail table if in database.\n */\nfunction smd_thumb_table_remove()\n{\n    global $DB;\n\n    if (!safe_drop(SMD_THUMB)) {\n        echo mysqli_error($DB->link);\n    }\n\n    // Restore the original thumb prefs.\n    $exists = safe_row(\'*\', \'txp_prefs\', \"name=\'smd_thumb_backup_w\'\");\n\n    if ($exists) {\n        set_pref(\'thumb_w\', get_pref(\'smd_thumb_backup_w\'), \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_h\', get_pref(\'smd_thumb_backup_h\'), \'image\', PREF_HIDDEN);\n        set_pref(\'thumb_crop\', get_pref(\'smd_thumb_backup_c\'), \'image\', PREF_HIDDEN);\n\n    }\n\n    // Erase the backup thumb prefs.\n    safe_delete(\'txp_prefs\',\n        \"name IN(\'smd_thumb_backup_w\', \'smd_thumb_backup_h\', \'smd_thumb_backup_c\', \'smd_thumb_default_profile\', \'pane_smd_thumbnail_profiles_visible\')\");\n}\n\n/**\n * Determine if the thumbnail table exists.\n *\n * @param  string $all [description]\n */\nfunction smd_thumb_table_exist($all = \'\')\n{\n    if ($all) {\n        $tbls = array(SMD_THUMB => 6);\n        $out = count($tbls);\n\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \".$tbl.\" HAS \".count(@safe_show(\'columns\', $tbl)).\" COLUMNS; REQUIRES \".$cols.\" ++\".br;\n            }\n\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n\n        return ($out === 0) ? 1 : 0;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \".SMD_THUMB.\" HAS \".count(@safe_show(\'columns\', SMD_THUMB)).\" COLUMNS;\";\n        }\n\n        return(@safe_show(\'columns\', SMD_THUMB));\n    }\n}\n\n/**\n * AJAX function to store the state of the current UI elements (which pane is visible).\n */\nfunction smd_thumbnail_save_pane_state()\n{\n    $panes = array(\'smd_thumbnail_profiles\');\n    $pane = gps(\'pane\');\n\n    if (in_array($pane, $panes)) {\n        set_pref(\"pane_{$pane}_visible\", (gps(\'visible\') == \'true\' ? \'1\' : \'0\'), \'smd_thumb\', PREF_HIDDEN, \'yesnoradio\', 0, PREF_PRIVATE);\n        send_xml_response();\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n}\n\n/**\n * Public tag: show a thumbnail.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n * @return string        HTML\n */\nfunction smd_thumbnail($atts, $thing = NULL)\n{\n    global $thisimage, $img_dir;\n\n    extract(lAtts(array(\n        \'type\'       => get_pref(\'smd_thumb_default_profile\', \'\'),\n        \'id\'         => \'\',\n        \'name\'       => \'\',\n        \'escape\'     => \'html\',\n        \'wraptag\'    => \'\',\n        \'class\'      => \'\',\n        \'html_id\'    => \'\',\n        \'style\'      => \'\',\n        \'link\'       => \'\',\n        \'link_rel\'   => \'\',\n        \'poplink\'    => 0,\n        \'add_stamp\'  => 0,\n        \'width\'      => \'\',\n        \'height\'     => \'\',\n        \'force_size\' => \'\',\n        \'display\'    => \'thumbnail\', // thumbnail (full img tag) or URL.\n        \'form\'       => \'\',\n    ), $atts));\n\n    $thing = (empty($form)) ? $thing : fetch_form($form);\n\n    if ($name) {\n        $name = doSlash($name);\n        $rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\n    } elseif ($id) {\n        $id = (int) $id;\n        $rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\n    } elseif ($thisimage) {\n        $id = (int) $thisimage[\'id\'];\n        $rs = $thisimage;\n    } else {\n        trigger_error(gTxt(\'unknown_image\'));\n        return;\n    }\n\n    smd_thumb_set_impath();\n\n    if ($rs) {\n        extract($rs);\n        $thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\n\n        if ($thumbinfo) {\n            $dir = sanitizeForUrl($thumbinfo[\'name\']);\n            $path = IMPATH . $dir . DS . $id . $ext;\n\n            if (file_exists($path)) {\n                // Drop out if all we need to display is the thumb\'s URL.\n                if ($display === \'url\') {\n                    return ihu . $img_dir . \'/\' . $dir . \'/\' . $id . $ext;\n                }\n\n                if ($escape === \'html\') {\n                    $alt = htmlspecialchars($alt);\n                    $caption = htmlspecialchars($caption);\n                }\n\n                $meta[\'url\'] = ihu . $img_dir . \'/\' . $dir . \'/\' . $id . $ext;\n                $meta[\'alt\'] = $alt;\n                $meta[\'type\'] = $type;\n                $meta[\'width\'] = $width;\n                $meta[\'height\'] = $height;\n                $force_size = do_list($force_size);\n\n                if (in_array(\'width\', $force_size)) {\n                    $meta[\'forcew\'] = 1;\n                }\n\n                if (in_array(\'height\', $force_size)) {\n                    $meta[\'forceh\'] = 1;\n                }\n\n                // Negative logic since we want the stamp on by default for the admin side.\n                if ($add_stamp == 0) {\n                    $meta[\'stamp\'] = 1;\n                }\n\n                if ($name) {\n                    $meta[\'name\'] = $name;\n                }\n\n                if ($caption) {\n                    $meta[\'title\'] = $caption;\n                }\n// $meta[\'title\'] = gTxt(\'smd_thumb_image\') . $rs[\'w\'] . \" &#215; \" . $rs[\'h\'] . sp .  gTxt(\'smd_thumb_profile\') . $thumbinfo[\'width\'] . \" &#215; \" . $thumbinfo[\'height\'];\n\n                if ($category) {\n                    $meta[\'category\'] = $category;\n                    $meta[\'category_title\'] = fetch_category_title($category, \'image\');\n                }\n\n                if ($author) {\n                    $meta[\'author\'] = $author;\n                }\n\n                if ($date) {\n                    $meta[\'date\'] = $date;\n                }\n\n                if ($html_id && !$wraptag) {\n                    $meta[\'id\'] = $html_id;\n                }\n\n                if ($class && !$wraptag) {\n                    $meta[\'class\'] = $class;\n                } else {\n                    $class = $type;\n                }\n\n                if ($style) {\n                    $meta[\'style\'] = $style;\n                }\n\n                $out = smd_thumb_img($thumbinfo, $rs, $meta, $thing);\n\n                if ($link) {\n                    $out = href($out, imagesrcurl($id, $ext), (!empty($link_rel) ? \' rel=\"\' . $link_rel . \'\"\' : \'\') . \' title=\"\' . $caption . \'\"\');\n                } elseif ($poplink) {\n                    $out = \'<a href=\"\' . imagesrcurl($id, $ext) . \'\"\' .\n                        \' onclick=\"window.open(this.href, \\\'popupwindow\\\', \'.\n                        \'\\\'width=\' . $w . \', height=\' . $h . \', scrollbars, resizable\\\'); return false;\">\' . $out . \'</a>\';\n                }\n\n                return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\n            }\n        }\n    }\n\n    trigger_error(gTxt(\'unknown_image\'));\n}\n\n/**\n * Public tag: check thumbnail exists.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n */\nfunction smd_if_thumbnail($atts, $thing)\n{\n    global $thisimage;\n\n    assert_image();\n\n    smd_thumb_set_impath();\n\n    extract(lAtts(array(\n        \'type\' => get_pref(\'smd_thumb_default_profile\'),\n    ), $atts));\n\n    $thumbinfo = safe_row(\'*\', SMD_THUMB, \"name=\'\".doSlash($type).\"\'\");\n    $ret = false;\n\n    if ($thumbinfo) {\n        $path = IMPATH . sanitizeForUrl($thumbinfo[\'name\']) . DS . $thisimage[\'id\'] . $thisimage[\'ext\'];\n        $ret = file_exists($path);\n    }\n\n    return parse(EvalElse($thing, $ret));\n}\n\n/**\n * Public tag: display selected thumbnail meta data.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Container\n * @return string        HTML\n */\nfunction smd_thumbnail_info($atts, $thing = NULL)\n{\n    global $smd_thumb_data;\n\n    extract(lAtts(array(\n        \'item\'    => \'\',\n        \'wraptag\' => \'\',\n        \'break\'   => \'\',\n        \'class\'   => \'\',\n        \'debug\'   => 0,\n    ), $atts));\n\n    $tdata = is_array($smd_thumb_data) ? $smd_thumb_data : array();\n\n    if ($debug) {\n        echo \'++ AVAILABLE INFO ++\';\n        dmp($tdata);\n    }\n\n    $items = do_list($item);\n    $out = array();\n\n    foreach ($items as $it) {\n        if (isset($tdata[$it])) {\n            $out[] = $tdata[$it];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class);\n}', '897b69b591f972854f3fb428216f6029', '#@smd_thumb\n#@language fr\nsmd_thumb_actions => Actions\nsmd_thumb_all_sizes => Toutes les tailles\nsmd_thumb_all_thumbs => Créer\nsmd_thumb_batch_legend => Créer/Recréer des vignettes\nsmd_thumb_batch_preamble => Pour les profils actifs :\nsmd_thumb_btn_pnl => Profils\nsmd_thumb_btn_tools => Outils\nsmd_thumb_btn_tools_prefs => Outils/Préf.\nsmd_thumb_byAll => Tous\nsmd_thumb_byCat => Par catégorie\nsmd_thumb_bySel => Sélectionnées\nsmd_thumb_byUsr => Par auteur\nsmd_thumb_create => Création\nsmd_thumb_create_group_confirm => Créer les vignettes pour TOUS les profils existant ? Les précédentes vignettes seront écrasées.\nsmd_thumb_delete => Suppression\nsmd_thumb_delete_confirm => Voulez-vous vraiment supprimer le profil {name} ? Les vignettes de ce type seront TOUTES supprimées.\nsmd_thumb_image => Image=\nsmd_thumb_new => Nouveau profil\nsmd_thumb_prefs => Préférences\nsmd_thumb_profile => Profil=\nsmd_thumb_profile_deleted => Le profil \"{name}\" a été supprimé.\nsmd_thumb_profile_exists => Le profil \"{name}\" existe déjà.\nsmd_thumb_profile_heading => Profils de vignettes\nsmd_thumb_profile_preftool_heading => Outils et préférences du vignettage\nsmd_thumb_profile_tool_heading => Outils de vignettage\nsmd_thumb_quality => Qualité (%)\nsmd_thumb_sharpen => Rendre net\nsmd_thumb_tables_not_installed => Tables non installées : essayez de réinstaller le plugin.\nsmd_thumb_thumbnail_heading => Vignettes\nsmd_thumb_txp_auto_replace => Recréer les vignettes au téléchargement des images :\nsmd_thumb_txp_create => Créer\nsmd_thumb_txp_create_from => Créer les vignettes à partir de :\nsmd_thumb_txp_create_from_full => l\'image originale\nsmd_thumb_txp_create_from_thumb => la vignette\nsmd_thumb_txp_delete => Supprimer\nsmd_thumb_upload => Remplacer les vignettes sélectionnées', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_user_manager', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.21', 'Manage user accounts, groups and privileges', '<h1>smd_user_manager</h1>\n\n<p>Complete user / group / privs management. Features:</p>\n\n	<ul>\n		<li>Replaces <em>Admin-&gt;Users</em> tab</li>\n		<li>Add / edit / list users, with content counts alongside each user</li>\n		<li>Search, sort, or filter the users (standard Txp pagination result depths apply)</li>\n		<li>Quickly find accounts with certain characteristics (e.g. self-registered spam accounts with 0 articles)</li>\n		<li>Perform multi-edits: change privilege / reset pass / delete</li>\n		<li>All users can edit their own details and change their password</li>\n		<li>Create new user groups (a.k.a. roles) if the default six aren&#8217;t enough</li>\n		<li>Rename existing groups to more suitable names (you cannot delete them)</li>\n		<li>Modify Txp&#8217;s standard priv areas to alter what each user group can see/do</li>\n		<li>Add new priv areas (useful for custom code to save doing it in a plugin)</li>\n		<li>A &#8220;who&#8217;s online&#8221; indicator</li>\n		<li>Integrates with smd_bio (v0.40+) and smd_prognostics (v0.20+)</li>\n	</ul>\n\n<h2>Installation / uninstallation</h2>\n\n<p class=\"important\">Requires Txp 4.5.0+</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1229/smd_user_manager\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp <em>Admin-&gt;Plugins</em> pane, install and enable the plugin. The tables will be installed and populated automatically unless you use the plugin from the cache directory; in either case, visiting the <em>Admin-&gt;User manager</em> tab will install and populate them.</p>\n\n<p>To uninstall the plugin, first assign all your users to groups in Txp&#8217;s first six, then delete the plugin from the <em>Admin-&gt;Plugins</em> page. The tables will be deleted automatically. If you do not reassign users to those default groups, you may have users with &#8216;dangling&#8217; (i.e. no) privs. The outcome of what happens when such users log in is thus undefined: at the very least you&#8217;ll get admin-side errors thrown.</p>\n\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36558\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<h2>Admin side overview</h2>\n\n<p>Visit the <em>Admin-&gt;User manager</em> tab. The default view (for admins) is a list of users. There are buttons at the top of the screen: <i>Change password</i>, <a href=\"#smd_um_users\">Users</a>, <a href=\"#smd_um_groups\">Groups</a>, <a href=\"#smd_um_privs\">Privs</a>, and <a href=\"#smd_um_prefs\">Prefs</a>. Each of those displays an area for the management of that component.</p>\n\n<p>Please note: The plugin tries to remove the <em>Admin-&gt;Users</em> menu, but depending on your installed theme this may not be possible. If the menu item remains, it will perform the same function as clicking the <em>User manager</em> tab.</p>\n\n<h2 id=\"smd_um_users\">User management (<em>Users</em> button)</h2>\n\n<p>View / search the installed user base. Change the number of users you wish to see per page by using the &#8216;per page&#8217; dropdown below the list. The columns on display are:</p>\n\n	<ul>\n		<li>Login &#8212; user name</li>\n		<li>Real Name &#8212; full name</li>\n		<li>E-mail &#8212; e-mail address</li>\n		<li>Privileges &#8212; user group</li>\n		<li>Last Login &#8212; month &amp; year of last access to the Textpattern admin side</li>\n		<li>Articles &#8212; number of articles authored by this user</li>\n		<li>Images &#8212; number of images uploaded by this user</li>\n		<li>Files &#8212; number of files uploaded by this user</li>\n		<li>Links &#8212; number of links created by this user</li>\n	</ul>\n\n<p>Click <em>New user</em> (admins only) to create a new user account and automatically send a randomly-generated password to the nominated e-mail address.<br />\nClick a login name (privileges required) to edit the details about that user. If you have any smd_bio fields set up, they can also be edited (and hovering over the login name will retrieve some extended biographical info).<br />\nClick an e-mail address to launch your e-mail software to send a message to the user.<br />\nClick an article / image / file / link value to see the content owned by that user.</p>\n\n<p>You may sort the columns by clicking the headings. Click once to sort in ascending order, click again to sort in descending order. An arrow shows the sort direction. Your sort column and direction are remembered next time you visit the panel.</p>\n\n<p>You may also search users by login name, real name, e-mail address, privilege level (number or name), or content count. A few notes about searching:</p>\n\n	<ul>\n		<li>When searching privileges by name, only the <em>first matching</em> group will be returned &#8212; the privs are searched in order of their ID.</li>\n		<li>You may search privileges and content counts by specifying an exact value, for example Article count: 0 shows all accounts with no articles (perhaps a self-registered spammer or malicious user?)</li>\n		<li>You may also search these fields using greater-than or less-than symbols to find users with the matching number of assets. For example, Image count: <code>&gt;=50</code> shows all users with 50 images or more.</li>\n	</ul>\n\n<p>You can also perform multi-edits by selecting the rows to alter via the checkboxes and using the dropdown immediately below the list to make mass changes. The options are:</p>\n\n	<ul>\n		<li>Change privileges: set all selected users to the given privilege level. When you choose this option, a further dropdown appears to allow you to choose which level to apply.</li>\n		<li>Reset password: send a new password to all selected users.</li>\n		<li>Delete: remove all selected user accounts. A further dropdown appears with a list of user accounts in it: choose one to transfer over any content that belonged to the deleted user(s).</li>\n	</ul>\n\n<p>A common use is to find any users with 0 articles, and then use the multi-edit tool to delete them all.</p>\n\n<h2 id=\"smd_um_groups\">Group management (<em>Groups</em> button)</h2>\n\n<p>This panel allows you to alter the names of Txp&#8217;s built-in 6 groups to suit your application. It changes the Titles in the currently installed language only.</p>\n\n<p>You may also add a new group using the input controls and accompanying <em>Create</em> button at the top of the panel. If you choose to only enter a Title, a name will be automatically generated (lower case, with most non-<span class=\"caps\">ASCII</span> characters replaced by safe characters). Alternatively, you can type your own name, though please stick to &#8216;simple&#8217; letters and numbers to make things easy on yourself later. If you choose an existing group from the <em>based on</em> dropdown, the privs for that group will be copied across to your new group.</p>\n\n<p>Custom groups can be deleted using the &#8216;x&#8217; button alongside each. Any users that were assigned to that group will have their privileges reduced to &#8216;None&#8217;. Either visit the <em>Users</em> panel and alter their priv level to something suitable, or set their level to something else prior to deleting the group.</p>\n\n<p>If any group contains at least one user, the group ID values are hyperlinked back to the Users panel to show only those users in that group. This is handy for reassigning priv levels before deleting a group. If you hover over the priv ID, a tooltip will display the number of users assigned to that privilege group.</p>\n\n<h2 id=\"smd_um_privs\">Priv management (<em>Privs</em> button)</h2>\n\n<p>From this panel you may alter which user groups can access which parts of the admin side interface or perform certain types of action. You will see a list of &#8216;area&#8217; headings. Click one to expand it and see the privileges it contains; click it again to collapse it. The open/closed state of the areas is remembered next time you visit the panel.</p>\n\n<p>Each area has a row of checkboxes alongside it. If a checkbox is ticked in a column corresponding to a privilege group, that group has access to that feature of the interface. You may alter who sees what by changing the tick boxes and hitting one of the <em>Save</em> buttons that appear to the left of each area heading; all the buttons do the same thing: they save the entire list of privileges. You must confirm the action, because the change is immediate.</p>\n\n<p>You may change checkboxes in batches by selecting rows/columns and then using a keyboard shortcut to make changes to all highlighted checkboxes. Firstly, choose which checkboxes to apply your changes:</p>\n\n	<ol>\n		<li>click a column heading to highlight that entire Group</li>\n		<li>click a row heading to highlight that entire Priv set</li>\n		<li>click the top left-hand corner of the collapsible table to select all checkboxes</li>\n	</ol>\n\n<p>You can select multiple columns/rows from multiple areas if you wish. Clicking a row/column/corner heading again will toggle its status.</p>\n\n<p>When you are ready to make changes to the selected checkboxes, you can use the following keys:</p>\n\n	<ul>\n		<li><code>c</code> to check all selected boxes</li>\n		<li><code>u</code> to uncheck all selected boxes</li>\n		<li><code>t</code> to toggle the status of all selected boxes (checked become unchecked, and vice versa)</li>\n		<li><code>d</code> deselects all rows / columns you have highlighted</li>\n	</ul>\n\n<p>The area headings merely group the areas for convenience and make the page look less scary! Any plugins you have installed will be shown under an area heading of the author&#8217;s three-letter prefix. Note that altering privs here overrides the privs as set by the plugin so you can customise who sees what, <em>as long as the plugin in question is loaded before smd_user_manager</em>. Check the plugin load order values if things aren&#8217;t working as you expect.</p>\n\n<p>The &#8216;tab&#8217; area heading governs which user groups have access to the primary navigation areas (content, presentation, admin, extensions and any custom tabs such as those created by smd_tabber). Note that giving access to these tabs does not automatically give access to the secondary navigation elements; you must turn those on independently. By contrast, giving a user group access to a secondary navigation item <em>does</em> allow them to use that feature but they won&#8217;t be able to navigate to it using the primary navigation button unless they have also been given access to that tab.</p>\n\n<p>For example, if you grant the &#8216;page&#8217; privilege to Freelancers but don&#8217;t give them access to the Presentation tab, they could type <code>?event=page</code> in the <span class=\"caps\">URL</span> to get to the Pages panel but they would have no means of navigating there by clicking the mouse. Since the primary &#8216;Presentation&#8217; tab is missing, the secondary tabs are all missing too, even though some of them may be permitted for that group of users.</p>\n\n<p>If you wish to add your own priv area, type a new one in the box at the top of the panel and hit <em>Create</em>. Your area will be created under the appropriate heading, e.g. if you specified <code>smd_test</code> it would appear under the <code>smd</code> heading, or if you created a <code>file.trusted</code> privilege area, it would appear under the <code>file</code> heading. Core areas are normally specified by a dotted notation but you are free to choose any notation that makes sense to your application.</p>\n\n<p>Note that:</p>\n\n	<ol>\n		<li>some plugins may not show up in the list due to factors such as plugin load order or the mechanism by which the plugin is written.</li>\n		<li>the <code>[R]</code> column is a special Reset indicator. Checking any row in this column will ignore any privilege checkboxes you may have set or altered and will reset the corresponding privs to their defaults after you click Save.</li>\n		<li>by default you may not add <code>smd_um</code> privs to override the functionality if this plugin, although you can adjust who can edit what so be careful not to open the permissions too widely. A preference setting governs whether smd_um can have its own privs altered.</li>\n		<li>you can create priv areas that may be tested from the public side using the <a href=\"#smd_um_has_privs\">smd_um_has_privs</a> tag.</li>\n	</ol>\n\n<h2 id=\"smd_um_prefs\">Preference management (<em>Prefs</em> button)</h2>\n\n<p>Administrators can set global preferences that govern the behaviour of the plugin:</p>\n\n	<dl>\n		<dt><strong>Assume hierarchical groups</strong></dt>\n		<dd>Textpattern does not normally have the notion of escalating privilege levels. A Copy Editor is not <em>greater than</em> a Staff Writer, for example, they just have different permissions to fulfil the relevant role.</dd>\n		<dd>You may set this preference to <em>yes</em> to force Textpattern to treat your levels as a hierarchy, i.e. lower numbers are &#8220;more powerful&#8221; than higher numbers (with the exception of 0: None which is always &#8216;no privs&#8217;).</dd>\n		<dd>Once set, logged-in users may not create or edit any other users that are &#8216;above&#8217; their assigned privs, e.g. Copy Editors cannot modify Publisher or Managing Editor accounts. Further, it is not possible to alter your own privileges, nor can you create a user with a greater priv level than you possess.</dd>\n		<dd>Default: no</dd>\n		<dt><strong>Protected administrator group</strong></dt>\n		<dd>Nominate a group that you wish only administrators to be able to alter/create.</dd>\n		<dd>Once set, the nominated group is off-limits to all users apart from those already in the selected group. In other words, non-admin users cannot create, modify, alter (or mass-alter) any privilege setting using this group.</dd>\n		<dd>Can be used in tandem with or independently of the <em>Assume hierarchical groups</em> setting.</dd>\n		<dd>Default: 1 (a.k.a. Publishers)</dd>\n		<dt><strong>Maximum user search result limit</strong></dt>\n		<dd>Maximum number of search results to return when filtering the User list.</dd>\n		<dd>Default: 500</dd>\n		<dt><strong>Password length (characters)</strong></dt>\n		<dd>The number of characters in newly-generated passwords. The higher the better (up to a point).</dd>\n		<dd>Default: 12</dd>\n		<dt><strong>Activity timeout (seconds)</strong></dt>\n		<dd>Number of seconds within which someone has to perform an admin-side action to remain visible on the Active Users list. For high activity sites, you can lower this value and receive a more responsive (accurate) list, at the possible expense of more perceived fluctuations in user activity when the timeout is exceeded. Lengthening the value will keep online users in the list longer, even though they may not actually still be active.</dd>\n		<dd>Default: 60</dd>\n		<dt><strong>Allow smd_um privs to be altered</strong></dt>\n		<dd>Set this to <em>yes</em> to allow smd_um privs to be changed from the Privs panel.</dd>\n		<dd>Default: no</dd>\n	</dl>\n\n<h2 id=\"smd_um_non_admin\">Non-admin users</h2>\n\n<p>User accounts without privileges to list users will automatically be shown the Edit screen for their own user account. This is the only account that can be edited. If smd_bio is installed, additional biographical information may also be edited.</p>\n\n<p>Users may also change their password using the button at the top of the screen.</p>\n\n<h2 id=\"smd_um_active\">Active users</h2>\n\n<p>At the bottom of each smd_user_manager panel is a list of currently active (&#8220;online&#8221;) users. Each user is hyperlinked to the <a href=\"#smd_um_users\">Users</a> panel to show you the info about just that person, which is a convenient shortcut to find out about a logged-in author.</p>\n\n<p>An active user is one who has performed some admin-side action in the past N seconds, where N is defined in the <a href=\"#smd_um_prefs\">Activity Timeout plugin preference</a>.</p>\n\n<h2 id=\"smd_um_callbacks\">Developer callbacks</h2>\n\n<p>The plugin honours as many existing Textpattern callbacks found on the <i>Admin-&gt;Users</i> panel as it can. In addition, the following callbacks are defined:</p>\n\n<table class=\"txp-list\">\n		<tr>\n			<th>Event </th>\n			<th>Step </th>\n			<th>Pre </th>\n			<th>Payload </th>\n			<th>Comments </th>\n		</tr>\n		<tr>\n			<td> smd_user_manager </td>\n			<td> password.reset </td>\n			<td> 0 </td>\n			<td> array comprising user name, password and email address </td>\n			<td> Return some string from your callback function to bypass the internal send_new_password() call. The returned string will be announced in the UI. Returning an empty string (or null) will still trigger the default password reset behaviour </td>\n		</tr>\n</table>\n\n<h2>Public side tags</h2>\n\n<h3 id=\"smd_um_has_privs\">Tag: <code>&lt;txp:smd_um_has_privs&gt;</code></h3>\n\n<p>Use this conditional tag to check if the currently logged-in user has permissions to perform some action. If they have, the contained content is executed. Supports <code>&lt;txp:else /&gt;</code>.</p>\n\n<p>Note that the plugin does not have a public-side logging in/out facility, it merely allows you to test whether someone who has logged in via the admin side (or via a third party login plugin) has necessary privileges.</p>\n\n<p>Attributes:</p>\n\n	<dl>\n		<dt><span>name</span></dt>\n		<dd>Check the current logged-in user&#8217;s name against this list of names.</dd>\n		<dd><span class=\"caps\">IMPORTANT</span>: Case sensitive.</dd>\n		<dt><span>group</span></dt>\n		<dd>Check the current logged-in user&#8217;s group (privilege) level is in this list of numbers.</dd>\n		<dd>You can specify <code>&gt;</code> and <code>&lt;</code> symbols before any value to indicate you wish to check if the user has privileges higher or lower than a given number, respectively.</dd>\n		<dd>If checking priv values less than a number, 0 (a.k.a None) is <em>never</em> included: you must add it to the list explicitly if you wish to test for it.</dd>\n		<dd>Example: <code>group=&quot;11, &lt;4&quot;</code> means &#8220;does the user have privs of 11, 1, 2, or 3?&#8221;</dd>\n		<dt><span>area</span></dt>\n		<dd>Check the current logged-in user has the ability to access one of the given areas in this list.</dd>\n		<dd>Example: <code>area=&quot;sports_hall, chemistry_lab&quot;</code> would only perform the contained content if the logged-in user&#8217;s priv level was present in either the sports_hall or chemistry_lab priv areas.</dd>\n	</dl>\n\n<p>Notes:</p>\n\n	<ul>\n		<li>You can combine the attributes in any way: the contained content will only be executed if the logged-in user matches <strong>all</strong> the criteria.</li>\n		<li>Without any attributes, the contained content will be executed if anybody is logged in, regardless of their name, privs or assigned areas.</li>\n		<li>Again: login <code>name</code>s are case sensitive</li>\n	</ul>\n\n<h2>Author / credits</h2>\n\n<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Thanks to the beta test team jakob, mrdale, alanfluff, maverick, Destry, redbot, and rsilletti for their willingness to let my code loose on their servers.</p>\n\n<h2>Changelog</h2>\n\n	<ul>\n		<li>10 Nov 2013 | v0.21 | Only save privs if they differ</li>\n		<li>16 May 2013 | v0.20 | Updated for Txp 4.5.0+; added callback smd_user_manager &gt; password.reset to allow interception of reset messages (thanks MattE)</li>\n		<li>25 Jan 2012 | v0.12 | Fixed smd_um_has_privs multiple area check ; fixed prefs options from Plugins pane</li>\n		<li>03 Nov 2011 | v0.11 | Added smd_um_has_privs tag to check privs/areas ; added preference to allow editing of smd_um privs; fixed handling of users with privs=None</li>\n		<li>27 Jul 2011 | v0.10 | Initial public release</li>\n	</ul>', '//<?php\r\n/**\r\n * smd_user_manager\r\n *\r\n * A Textpattern CMS plugin for complete user administration:\r\n *  -> Search / filter / alter info on users (with asset counts)\r\n *  -> Create / alter groups (roles)\r\n *  -> Create / customise privs (areas)\r\n *  -> Online user list\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n//  -> Why does multi-edit fire twice? Is it still attached to the Admin->Users table?\r\n\r\nif (!defined(\'SMD_UM_PRIVS\')) {\r\n	define(\"SMD_UM_PRIVS\", \'smd_um_privs\');\r\n}\r\nif (!defined(\'SMD_UM_GROUPS\')) {\r\n	define(\"SMD_UM_GROUPS\", \'smd_um_groups\');\r\n}\r\n\r\nif(@txpinterface === \'admin\') {\r\n	global $smd_um_event, $txp_permissions, $txp_groups, $txp_user, $step;\r\n\r\n	$smd_um_event = \'smd_um\';\r\n\r\n	add_privs($smd_um_event.\'.usr.list\', \'1, 2, 3\');\r\n	add_privs($smd_um_event.\'.usr.edit\', \'1, 2\');\r\n	add_privs($smd_um_event.\'.usr.create\', \'1\');\r\n	add_privs($smd_um_event.\'.grp\', \'1\');\r\n	add_privs($smd_um_event.\'.prv\', \'1\');\r\n	add_privs($smd_um_event.\'.prf\', \'1\');\r\n	add_privs(\'plugin_prefs.smd_user_manager\', \'1\');\r\n	register_tab(\'admin\', $smd_um_event, gTxt(\'smd_um_tab_name\'));\r\n	register_callback(\'smd_um_silence\', \'admin\');\r\n	register_callback(\'smd_um_dispatcher\', $smd_um_event);\r\n	register_callback(\'smd_um_dispatcher\', $smd_um_event.\'.usr.edit.own\');\r\n	register_callback(\'smd_um_users_tab\', \'admin_side\', \'head_end\');\r\n	register_callback(\'smd_um_prefs\', \'plugin_prefs.smd_user_manager\');\r\n	register_callback(\'smd_um_welcome\', \'plugin_lifecycle.smd_user_manager\');\r\n	register_callback(\'smd_um_inject_css\', \'admin_side\', \'head_end\');\r\n\r\n	// Log the time of this access attempt\r\n	$curr_users = unserialize(get_pref(\'smd_um_current_users\', \'\'));\r\n	$curr_users[$txp_user] = time();\r\n	set_pref(\'smd_um_current_users\', serialize($curr_users), \'smd_um\', PREF_HIDDEN, \'\', 0);\r\n\r\n	// Merge in the groups only for now\r\n	smd_um_priv_merge(1, 0);\r\n	include_once txpath.\'/lib/txplib_admin.php\';\r\n\r\n	// Permit user self-editing\r\n	$smd_um_grps = array_keys(smd_um_get_groups(0));\r\n	unset($smd_um_grps[0]); // Remove None user\r\n	$allprivs = join(\',\',$smd_um_grps);\r\n	add_privs($smd_um_event, $allprivs); // Required to display anything at all on the User Manager tab\r\n	add_privs($smd_um_event.\'.usr.edit.own\', $allprivs);\r\n\r\n	// Now the privs are established for all admin steps so we can go ahead\r\n	// and merge in the changes. One caveat: if we\'re saving the privs we\r\n	// need to delay the database merge until after the resets have been applied,\r\n	// otherwise we won\'t know what the defaults (in admin_config.php) are\r\n	$do_privs = (($step === \'smd_um_privs\') && ps(\'smd_um_priv_save\')) ? 0 : 1;\r\n	smd_um_priv_merge(0, $do_privs);\r\n}\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n\r\n// -------------------------------------------------------------\r\n// CSS definitions: hopefully kind to themers\r\n// Includes a hack for Remora (#nav li ul) to prevent menu from disappearing under the\r\n// privs content. This only occurs because the .smd_um_privgroup class uses position:relative.\r\n// Needs workaround as it affects other plugins.\r\nfunction smd_um_get_style_rules() {\r\n	$smd_um_styles = array(\r\n		\'control-panel\' => \'\r\n.smd_um_privgroup { margin:10px auto; width:50%; position:relative; }\r\n.smd_um_privgroup h3 { text-align:left; font-weight:bold; }\r\n.smd_um_privsave { position:absolute; left:-55px; top:0; }\r\n.smd_um_active_users { margin:15px auto; width:80%; text-align:center; }\r\n.smd_um_selected { background-color:#e2dfce; }\r\n.smd_um_grp_name, .smd_um_prv_name, .smd_um_reset_col { cursor:pointer; }\r\n.smd_um_checkbox, .smd_um_prv_hdr { text-align:center!important; }\r\n#nav li ul { z-index:10000; }\r\n\',\r\n	);\r\n\r\n	return $smd_um_styles;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_um_inject_css($evt, $stp) {\r\n	global $smd_um_event, $event;\r\n\r\n	if ($event == $smd_um_event || $event == \'admin\') {\r\n		$smd_um_styles = smd_um_get_style_rules();\r\n\r\n		echo \'<style type=\"text/css\">\', $smd_um_styles[\'control-panel\'], \'</style>\';\r\n	}\r\n\r\n	return;\r\n}\r\n\r\n\r\n// -------------------------------------------------------------\r\n// Destroy the existing Admin->Users tab if we come from any smd_um-initiated step\r\nfunction smd_um_silence($evt, $stp) {\r\n	global $event, $smd_um_event;\r\n\r\n	$ignore = array(\r\n		\'author_list\',\r\n		\'author_edit\',\r\n		\'author_save\',\r\n		\'author_save_new\',\r\n		\'change_pass\',\r\n	);\r\n\r\n	$no_dispatch = array(\r\n		\'change_pass\',\r\n	);\r\n\r\n	if (!in_array($stp, $ignore)) {\r\n		ob_end_clean(); // Kill any existing Admin tab panel...\r\n		ob_start(); // ... and start again\r\n		$event = $smd_um_event;\r\n\r\n		if (!in_array($stp, $no_dispatch)) {\r\n			smd_um_dispatcher($event, $stp);\r\n		}\r\n	}\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// Plugin jump off point\r\nfunction smd_um_dispatcher($evt, $stp, $msg=\'\') {\r\n	global $smd_um_event, $txp_permissions, $txp_user;\r\n\r\n	$available_steps = array(\r\n		\'smd_um\'                  => false,\r\n		\'smd_um_edit\'             => false,\r\n		\'smd_um_save\'             => true,\r\n		\'smd_um_save_new\'         => true,\r\n		\'smd_um_groups\'           => false,\r\n		\'smd_um_privs\'            => false,\r\n		\'smd_um_prefs\'            => false,\r\n		\'smd_um_multi_edit\'       => true,\r\n		\'smd_um_change_pass\'      => true,\r\n		\'smd_um_change_pass_form\' => false,\r\n		\'smd_um_change_pageby\'    => true,\r\n		\'smd_um_table_install\'    => true,\r\n		\'smd_um_table_remove\'     => true,\r\n		\'save_pane_state\'         => true,\r\n	);\r\n\r\n	if (!has_privs($smd_um_event.\'.usr.list\')) {\r\n		$stp = ($stp) ? $stp : \'smd_um_edit\';\r\n		$uid = safe_field(\'user_id\', \'txp_users\', \"name=\'\" . doSlash($txp_user) . \"\'\");\r\n		if ($uid) {\r\n			// Inject this value so the edit step picks it up and edits only the current user.\r\n			// The edit/save steps will verify if the current user is the one trying to be edited\r\n			// to prevent people adding &user_id=N to the URL\r\n			$_POST[\'user_id\'] = $uid;\r\n		}\r\n   }\r\n\r\n	if ($stp == \'save_pane_state\') {\r\n		smd_um_save_pane_state();\r\n	} else if ($stp && bouncer($stp, $available_steps)) {\r\n		if ($msg) {\r\n			$stp($msg);\r\n		} else {\r\n			$stp();\r\n		}\r\n	} else {\r\n		$smd_um_event($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Try to hide the Admin->Users tab in the secondary nav via jQuery.\r\n// May fail with inventive DOM structures / themes\r\n// (note to self: campaign for improvement in this area because jQuery smells hackish)\r\nfunction smd_um_users_tab() {\r\n	global $event;\r\n\r\n	$userStr = gTxt(\'tab_site_admin\');\r\n	echo script_js(<<<EOJS\r\njQuery(function() {\r\n	jQuery(\"a[href=\'?event=admin\']\").each(function() {\r\n		me = jQuery(this);\r\n		if (me.text() == \"{$userStr}\") {\r\n			me.parent().hide();\r\n		}\r\n	});\r\n	if (\'{$event}\' == \'admin\') {\r\n		jQuery(\"a[href=\'?event=smd_um\']\").each(function() {\r\n			me = jQuery(this);\r\n			me.parent().removeClass(\'inactive\').addClass(\'active\'); // Hive, etc\r\n			me.removeClass(\'tabdown\').addClass(\'tabup\'); // Classic, etc\r\n		});\r\n	}\r\n});\r\nEOJS\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_um_table_install(0);\r\n			$msg = \'Super duper users!\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_um_table_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\n// Main user display list\r\nfunction smd_um($msg=\'\') {\r\n	global $step, $smd_um_event, $txp_user, $smd_um_list_pageby;\r\n\r\n	require_privs($smd_um_event.\'.usr.list\');\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if (!smd_um_table_exist(1)) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_usr_lbl\'), $msg);\r\n\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n	if ($sort === \'\') $sort = get_pref(\'smd_um_sort_column\', \'login\');\r\n	if ($dir === \'\') $dir = get_pref(\'smd_um_sort_dir\', \'desc\');\r\n	$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\r\n\r\n	switch ($sort) {\r\n		case \'real_name\':\r\n			$sort_sql = \'RealName \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'email\':\r\n			$sort_sql = \'email \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'privs\':\r\n			$sort_sql = \'privs \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'article_count\':\r\n			$sort_sql = \'article_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'image_count\':\r\n			$sort_sql = \'image_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'file_count\':\r\n			$sort_sql = \'file_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'link_count\':\r\n			$sort_sql = \'link_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'last_login\':\r\n			$sort_sql = \'last_access \'.$dir;\r\n		break;\r\n\r\n		default:\r\n			$sort = \'name\';\r\n			$sort_sql = \'name \'.$dir.\', last_access desc\';\r\n		break;\r\n	}\r\n\r\n	set_pref(\'smd_um_sort_column\', $sort, \'smd_um\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n	set_pref(\'smd_um_sort_dir\', $dir, \'smd_um\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n\r\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n	$criteria = 1;\r\n\r\n	$count_columns = array(\'article_count\', \'image_count\', \'file_count\', \'link_count\');\r\n\r\n	if ($search_method and $crit != \'\') {\r\n		$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\r\n\r\n		// Permit searching by privilege name (sort of)\r\n		if ($search_method == \'privileges\' && !is_numeric($crit_escaped)) {\r\n			$levels = get_groups();\r\n			foreach ($levels as $idx => $group) {\r\n				if (strpos(strtolower($group), strtolower($crit_escaped)) !== false) {\r\n					$crit_escaped = $idx;\r\n					break;\r\n				}\r\n			}\r\n		}\r\n\r\n		// Permit <, =, and > operators in count searches.\r\n		// nullcheck is not required for privs searches because the value is a true 0 (whereas in the computed\r\n		// columns it\'s empty/null)\r\n		$operator = \'=\';\r\n		$nullcheck = \'\';\r\n		if (in_array($search_method, $count_columns) || $search_method == \'privileges\') {\r\n			preg_match(\'/([<=>]+)?([0-9]+)/\', $crit_escaped, $matches);\r\n			$operator = (isset($matches[1]) && $matches[1] != \'\') ? $matches[1] : \'=\';\r\n			$crit_escaped = (isset($matches[2]) && $matches[2] != \'\') ? $matches[2] : $crit_escaped;\r\n			$char_one = substr($operator, 0, 1);\r\n			$char_two = substr($operator, 1, 1);\r\n			$nullcheck = ($char_one == \'<\' || ($char_one == \'>\' && $char_two == \'=\' && $crit_escaped == \'0\')) ? \' OR ISNULL(\'.$search_method.\')\' : \'\';\r\n		}\r\n\r\n		$critsql = array(\r\n			\'login_name\'    => \"name like \'%$crit_escaped%\'\",\r\n			\'real_name\'     => \"RealName like \'%$crit_escaped%\'\",\r\n			\'email\'         => \"email like \'%$crit_escaped%\'\",\r\n			\'privileges\'    => \"privs $operator \'$crit_escaped\'\",\r\n			\'article_count\' => \"article_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'image_count\'   => \"image_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'file_count\'    => \"file_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'link_count\'    => \"link_count $operator \'$crit_escaped\'$nullcheck\",\r\n		);\r\n\r\n		if (array_key_exists($search_method, $critsql)) {\r\n			$criteria = $critsql[$search_method];\r\n			$limit = get_pref(\'smd_um_max_search_limit\', $smd_um_prefs[\'smd_um_max_search_limit\'][\'default\']);\r\n		} else {\r\n			$search_method = \'\';\r\n			$crit = \'\';\r\n		}\r\n	} else {\r\n		$search_method = \'\';\r\n		$crit = \'\';\r\n	}\r\n\r\n	// Since the *_count columns are computed we need to some jiggery pokery here.\r\n	// Thus, if we\'re looking for counts of \'zero\' the actual search value should be isnull()\r\n	if (in_array($search_method, $count_columns) && $operator == \'=\' && $crit_escaped == \'0\') {\r\n		$criteria = \"ISNULL($search_method)\";\r\n	}\r\n\r\n	// The fields, joins and sub-queries that make up the real and computed columns\r\n	$fields = \'txu.user_id, txu.name, txu.RealName, txu.email, txu.privs, unix_timestamp(txu.last_access) as last_login, txp.total AS article_count, txi.total AS image_count, txf.total AS file_count, txl.total AS link_count\';\r\n	$clause = \' FROM \'.PFX.\'txp_users as txu\r\n		LEFT JOIN (SELECT AuthorID, count(ID) AS total FROM \'.PFX.\'textpattern GROUP BY AuthorID) AS txp ON txp.AuthorID = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_image GROUP BY author) AS txi ON txi.author = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_file GROUP BY author) AS txf ON txf.author = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_link GROUP BY author) AS txl ON txl.author = txu.name\';\r\n\r\n	// Perform a count on the relevant search item. Doing a count(*) is awkward due to the computed columns\r\n	// so a straight query is performed with a loop to increment the total. getThing() or getRows for some reason\r\n	// failed under certain conditions\r\n	$total = 0;\r\n\r\n	// Call this so plugins that hook into the step can play\r\n	$criteria .= callback_event(\'admin_criteria\', \'author_list\', 0, $criteria);\r\n\r\n	$totrs = safe_query(\'SELECT \'.$fields.$clause.\' HAVING \'.$criteria);\r\n	while ($row = nextRow($totrs)) {\r\n		$total++;\r\n	}\r\n\r\n	$btnbar = smd_um_buttons(\'usr\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_usr\'), \'</h1>\',\r\n		\'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\',\r\n		$btnbar;\r\n\r\n	if ($total < 1) {\r\n		if ($criteria != 1) {\r\n			echo n, smd_um_search_form($crit, $search_method),\r\n				n, graf(gTxt(\'no_results_found\'), \' class=\"indicator\"\'),\r\n				n, \'</div>\';\r\n		}\r\n		return;\r\n	}\r\n\r\n	$limit = max($smd_um_list_pageby, 15);\r\n\r\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\r\n\r\n	$use_multi_edit = ( has_privs($smd_um_event.\'.usr.edit\') && (safe_count(\'txp_users\', \'1=1\') > 1) );\r\n\r\n	echo n, smd_um_search_form($crit, $search_method), \'</div>\';\r\n\r\n	// Retrieve the user info and related counts\r\n	$rs = safe_query(\'SELECT \'.$fields.$clause.\' HAVING \'.$criteria.\' ORDER BY \'.$sort_sql.\' LIMIT \'.$offset.\', \'.$limit);\r\n\r\n	if ($rs) {\r\n		echo n, \'<div class=\"txp-container\">\',\r\n			n, \'<form action=\"index.php\" id=\"smd_um_form\" method=\"post\" name=\"longform\" class=\"multi_edit_form\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n			n, \'<div class=\"txp-listtables\">\',\r\n			n, startTable(\'\', \'\', \'txp-list\'),\r\n			n, \'<thead>\',\r\n			n, tr(\r\n				n. (($use_multi_edit)\r\n					? hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\r\n					: hCell(\'\', \'\', \' class=\"multi-edit\"\')\r\n				).\r\n				n. column_head(\'login_name\', \'name\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'name\' == $sort) ? \"$dir \" : \'\').\'name login-name\').\r\n				n. column_head(\'real_name\', \'real_name\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'real_name\' == $sort) ? \"$dir \" : \'\').\'name real-name\').\r\n				n. column_head(\'email\', \'email\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'email\' == $sort) ? \"$dir \" : \'\').\'email\').\r\n				n. column_head(\'privileges\', \'privs\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'privs\' == $sort) ? \"$dir \" : \'\').\'privs\').\r\n				n. column_head(\'last_login\', \'last_login\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'last_login\' == $sort) ? \"$dir \" : \'\').\'date last-login modified\').\r\n				n. column_head(gTxt(\'smd_um_article_count\'), \'article_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'article_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_image_count\'), \'image_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'image_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_file_count\'), \'file_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'file_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_link_count\'), \'link_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'link_count\' == $sort) ? \"$dir \" : \'\'))\r\n			),\r\n			n, \'</thead>\',\r\n			n, \'<tbody>\';\r\n\r\n		$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n		while ($row = nextRow($rs)) {\r\n			extract(doSpecial($row));\r\n\r\n			$permitted = smd_um_can_edit($curr_priv, $name, $privs);\r\n\r\n			echo tr(\r\n				td(((has_privs($smd_um_event.\'.usr.edit\') && $txp_user != $row[\'name\']) ? fInput(\'checkbox\', \'selected[]\', $row[\'name\'], \'checkbox\') : \'\'), \'\', \'multi-edit\').\r\n				td( (($permitted) ? eLink($smd_um_event, \'smd_um_edit\', \'user_id\', $user_id, $name) : $name), \'\', \'name login-name actions\').\r\n				td($RealName, \'\', \'name real-name\').\r\n				td(\'<a href=\"mailto:\'.$email.\'\">\'.$email.\'</a>\', \'\', \'email\').\r\n				td(smd_um_get_priv_level($privs), \'\', \'privs\').\r\n				td(($last_login ? safe_strftime(\'%b&#160;%Y\', $last_login) : \'\'), \'\', \'date last-login modified\').\r\n				td(($article_count) ? eLink(\'list\', \'list\', \'search_method\', \'author\', $article_count, \'crit\', $name) : \'0\').\r\n				td(($image_count) ? eLink(\'image\', \'image_list\', \'search_method\', \'author\', $image_count, \'crit\', $name) : \'0\').\r\n				td(($file_count) ? eLink(\'file\', \'file_list\', \'search_method\', \'author\', $file_count, \'crit\', $name) : \'0\').\r\n				td(($link_count) ? eLink(\'link\', \'link_edit\', \'search_method\', \'author\', $link_count, \'crit\', $name) : \'0\')\r\n			);\r\n		}\r\n\r\n		echo n, \'</tbody>\',\r\n			n, endTable(),\r\n			n, \'</div>\',\r\n			n, (($use_multi_edit) ? smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) : \'\'),\r\n			n, tInput(),\r\n			n, \'</form>\',\r\n			n, \'<div id=\"users_navigation\" class=\"txp-navigation\">\',\r\n			n, nav_form(\'smd_um\', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit),\r\n			n, pageby_form(\'smd_um\', $smd_um_list_pageby),\r\n			n, \'</div>\',\r\n			n, smd_um_active_users(),\r\n			n, \'</div>\';\r\n	}\r\n\r\n	// Call the Admin side\'s author_list routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_list\');\r\n}\r\n\r\n// Can this logged-in user edit the given user account?\r\nfunction smd_um_can_edit($curr_priv, $name, $privs) {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$permitted = false; // Assume no editing rights unlesss otherwise stated\r\n\r\n	$tiered = get_pref(\'smd_um_hierarchical_groups\', $smd_um_prefs[\'smd_um_hierarchical_groups\'][\'default\']);\r\n	$protected = get_pref(\'smd_um_admin_group\', $smd_um_prefs[\'smd_um_admin_group\'][\'default\']);\r\n\r\n	// For some reason checking ($privs != \'\') doesn\'t work *shrug*\r\n	if ( ($name != \'\') && ($privs == 0 || $privs) ) {\r\n		$permitted = has_privs($smd_um_event.\'.usr.edit.own\') && ($name == $txp_user);\r\n		$can_edit = has_privs($smd_um_event.\'.usr.edit\');\r\n		if ($can_edit) {\r\n			$permitted |= $can_edit;\r\n			if ($tiered) {\r\n				$permitted &= (($privs >= $curr_priv) || ($privs == 0));\r\n			}\r\n			if ($protected) {\r\n				$permitted &= (($privs != $protected) || ($curr_priv == $protected));\r\n			}\r\n		}\r\n	}\r\n\r\n	return $permitted;\r\n}\r\n\r\n// ------------------------\r\n// Edit a single User\r\nfunction smd_um_edit($msg=\'\') {\r\n	global $step, $txp_user, $smd_um_event;\r\n\r\n	$vars = array(\'user_id\', \'name\', \'RealName\', \'email\', \'privs\');\r\n	extract(gpsa($vars));\r\n\r\n	$rs = array();\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	if ($user_id) {\r\n		$user_id = assert_int($user_id);\r\n		$rs = safe_row(\'*\', \'txp_users\', \"user_id = $user_id\");\r\n		extract($rs);\r\n	} else if (!has_privs($smd_um_event.\'.usr.create\')) {\r\n		// If the current user doesn\'t have sufficient rights to create new users\r\n		// then this Edit request without user_id is a self-edit\r\n		$rs = safe_row(\'*\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n		extract($rs);\r\n	}\r\n\r\n	// Check for edit / creation rights\r\n	$permitted = ($user_id) ? smd_um_can_edit($curr_priv, $name, $privs) : has_privs($smd_um_event.\'.usr.create\');\r\n\r\n	if (!$permitted)\r\n		exit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.\r\n			gTxt(\'restricted_area\').\'</p>\');\r\n\r\n	$caption = gTxt(($user_id) ? \'edit_author\' : \'add_new_author\');\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_usr_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'usr\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', $caption, \'</h1>\',\r\n		n, \'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, \'</div>\',\r\n		n, \'<div id=\"\', $smd_um_event.\'_container\" class=\"txp-edit\">\',\r\n		n, form(\r\n			\'<div class=\"txp-edit\">\'.n.\r\n			inputLabel(\'login_name\', ($user_id ? strong($name) : fInput(\'text\', \'name\', $name, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'login_name\')), ($user_id ? \'\' : \'login_name\'), ($user_id ? \'\' : \'add_new_author\')).n.\r\n			inputLabel(\'real_name\', fInput(\'text\', \'RealName\', $RealName, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'real_name\'), \'real_name\').n.\r\n			inputLabel(\'login_email\', fInput(\'text\', \'email\', $email, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'login_email\'), \'email\').n.\r\n			inputLabel(\'privileges\', (($txp_user != $name) ? selectInput(\'privs\', smd_um_get_groups(1), $privs) : hInput(\'privs\', $privs).strong(smd_um_get_priv_level($privs))), ($user_id ? \'\' : \'privileges\'), \'about_privileges\').n.\r\n			pluggable_ui(\'author_ui\', \'extend_detail_form\', \'\', $rs).n.\r\n			graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')).\r\n			eInput($smd_um_event).\r\n			($user_id ? hInput(\'user_id\', $user_id).sInput(\'smd_um_save\') : sInput(\'smd_um_save_new\')).\r\n			\'</div>\'\r\n		, \'\', \'\', \'post\', \'edit-form\', \'\', \'user_edit\'),\r\n		\'</div>\';\r\n\r\n	// Call the Admin side\'s author_edit routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_edit\');\r\n}\r\n\r\n// ------------------------\r\n// Virtually cloned from Admin->Users\r\nfunction smd_um_save() {\r\n	global $event, $smd_um_event, $txp_user;\r\n\r\n	// Call the Admin side\'s author_save routine so other plugins can join the party\r\n	callback_event(\'admin\', \'author_save\');\r\n\r\n	extract(doSlash(psa(array(\'privs\', \'user_id\', \'RealName\', \'email\'))));\r\n	$privs   = assert_int($privs);\r\n	$user_id = assert_int($user_id);\r\n	$name = safe_field(\'name\', \'txp_users\', \"user_id = $user_id\");\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	// Check for hacking attempts\r\n	$permitted = smd_um_can_edit($curr_priv, $name, $privs);\r\n\r\n	if (!$permitted)\r\n		exit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.\r\n			gTxt(\'restricted_area\').\'</p>\');\r\n\r\n	if (!is_valid_email($email)) {\r\n		smd_um_edit(array(gTxt(\'email_required\'), E_ERROR));\r\n		return;\r\n	}\r\n\r\n	$rs = safe_update(\'txp_users\', \"\r\n		privs    = $privs,\r\n		RealName = \'$RealName\',\r\n		email    = \'$email\'\",\r\n		\"user_id = $user_id\"\r\n	);\r\n\r\n	if ($rs) {\r\n		$msg = gTxt(\'author_updated\', array(\'{name}\' => $RealName));\r\n	} else {\r\n		$msg = \'\';\r\n	}\r\n\r\n	smd_um_dispatcher($event, \'\', $msg);\r\n}\r\n\r\n// ------------------------\r\n// Virtually cloned from Admin->Users\r\nfunction smd_um_save_new() {\r\n	global $smd_um_event;\r\n\r\n	require_privs($smd_um_event.\'.usr.create\');\r\n\r\n	// Call the Admin side\'s author_save_new routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_save_new\');\r\n\r\n	extract(doSlash(psa(array(\'privs\', \'name\', \'email\', \'RealName\'))));\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n	$privs  = assert_int($privs);\r\n	$length = function_exists(\'mb_strlen\') ? mb_strlen($name, \'8bit\') : strlen($name);\r\n\r\n	if ($name and $length <= 64 and is_valid_email($email)) {\r\n		$exists = safe_field(\'name\', \'txp_users\', \"name = \'\" .$name. \"\'\");\r\n\r\n		if ($exists) {\r\n			smd_um_edit(array(gTxt(\'author_already_exists\', array(\'{name}\' => $name)), E_ERROR));\r\n			return;\r\n		}\r\n\r\n		$pass_len = get_pref(\'smd_um_pass_length\', $smd_um_prefs[\'smd_um_pass_length\'][\'default\']);\r\n		$password = generate_password($pass_len);\r\n		$hash = doSlash(txp_hash_password($password));\r\n		$nonce = doSlash(md5(uniqid(mt_rand(), TRUE)));\r\n\r\n		$rs = safe_insert(\'txp_users\', \"\r\n			privs    = $privs,\r\n			name     = \'$name\',\r\n			email    = \'$email\',\r\n			RealName = \'$RealName\',\r\n			nonce    = \'$nonce\',\r\n			pass     = \'$hash\'\r\n		\");\r\n\r\n		if ($rs) {\r\n			// TODO: consider cloning send_password() because only people with admin.edit can run it (i.e. Publishers)\r\n			send_password($RealName, $name, $email, $password);\r\n			$msg = gTxt(\'password_sent_to\').sp.$email;\r\n			$smd_um_event($msg);\r\n		} else {\r\n			$msg = array(gTxt(\'error_adding_new_author\'), E_ERROR);\r\n			smd_um_edit($msg);\r\n		}\r\n	} else {\r\n		$msg = array(gTxt(\'error_adding_new_author\'), E_ERROR);\r\n		smd_um_edit($msg);\r\n	}\r\n\r\n}\r\n\r\n// ------------------------\r\n// Group management panel\r\nfunction smd_um_groups($msg=\'\') {\r\n	global $smd_um_event, $txp_user, $txp_groups, $txp_permissions;\r\n\r\n	require_privs($smd_um_event.\'.grp\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	// Handle any form actions\r\n	if (ps(\'smd_um_group_save\')) {\r\n		$excluded = smd_um_get_groups(0);\r\n		$ids = ps(\'smd_um_group_id\');\r\n		$names = ps(\'smd_um_group_name\');\r\n		$titles = ps(\'smd_um_group_title\');\r\n\r\n		foreach($ids as $idx => $id) {\r\n			$title = $titles[$idx];\r\n			$name = strtolower(sanitizeForUrl($names[$idx]));\r\n			// Can\'t create duplicate types\r\n			if (!in_array($name, $excluded)) {\r\n				safe_update(SMD_UM_GROUPS, \"name=\'\" . doSlash($name) . \"\'\", \"id=\'\" . doSlash($id) . \"\'\");\r\n			}\r\n			smd_um_upsert_lang($title, $name);\r\n		}\r\n\r\n		$msg = gTxt(\'smd_um_grp_saved\');\r\n\r\n	} else if (ps(\'smd_um_group_add\')) {\r\n		$title = ps(\'smd_um_new_grp\');\r\n		$name = ps(\'smd_um_new_grp_name\');\r\n		$name = ($name == \'\') ? strtolower(sanitizeForUrl($title)) : $name;\r\n		if ($name) {\r\n			$exists = safe_field(\'id\', SMD_UM_GROUPS, \"name=\'\".doSlash($name).\"\'\");\r\n			if ($exists) {\r\n				$msg = array(gTxt(\'smd_um_grp_exists\', array(\'{id}\' => $exists)), E_USER_WARNING);\r\n			} else {\r\n				// It\'s not atomic but it\'ll do, given that:\r\n				//  a) normally only one person administers this plugin\r\n				//  b) groups are added one at a time\r\n				$curr_max = safe_field(\"MAX(id)\", SMD_UM_GROUPS, \'1=1\');\r\n				$new_priv = ($curr_max + 1);\r\n				safe_insert(SMD_UM_GROUPS, \"id=\'\" . $new_priv . \"\', name=\'\" . doSlash($name) . \"\'\");\r\n				smd_um_upsert_lang($title, $name);\r\n\r\n				$based_on = ps(\'smd_um_new_grp_based_on\');\r\n				if ($based_on != \'\') {\r\n					assert_int($based_on);\r\n					// Can\'t rely on the privs being in the database so resort to the (merged) array\r\n					foreach ($txp_permissions as $area => $privs) {\r\n						$privs = do_list($privs);\r\n						if (in_array($based_on, $privs)) {\r\n							safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($area) . \"\', priv=\'\" . doSlash($new_priv) . \"\'\");\r\n						}\r\n					}\r\n				}\r\n\r\n				$msg = gTxt(\'smd_um_grp_created\', array(\'{name}\' => $name));\r\n			}\r\n		} else {\r\n			$msg = array(gTxt(\'smd_um_name_required\'), E_ERROR);\r\n		}\r\n	} else if (ps(\'smd_um_group_del\')) {\r\n		$id = str_replace(\'smd_um_del_\', \'\', ps(\'smd_um_grp_del\'));\r\n		assert_int($id);\r\n\r\n		$affected_users = safe_column(\'user_id\', \'txp_users\', \"privs = \'\".doSlash($id).\"\'\");\r\n		if ($affected_users) {\r\n			// Set all orphaned users to no privs -- can always assign them a new group from the main screen later\r\n			$ret = safe_update(\'txp_users\', \"privs=0\", \"user_id IN (\'\". join(\"\',\'\", doSlash($affected_users)) .\"\')\");\r\n		}\r\n\r\n		// TODO: double check this isn\'t a core group?\r\n		$red = safe_delete(SMD_UM_GROUPS, \"id=\'\".doSlash($id).\"\'\");\r\n\r\n		if ($red) {\r\n			$ret = safe_delete(SMD_UM_PRIVS, \"priv=\'\".doSlash($id).\"\'\");\r\n			$msg = gTxt(\'smd_um_grp_deleted\') . ($affected_users ? gTxt(\'smd_um_grp_affected\', array(\'{num}\' => count($affected_users))) : \'\');\r\n		}\r\n	}\r\n\r\n	// Render the page\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_grp_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'grp\');\r\n	$grouplist = smd_um_get_groups(1);\r\n	unset($grouplist[0]); // Don\'t want None privs\r\n	$grouplist = selectInput(\'smd_um_new_grp_based_on\', $grouplist, \'\', true, \'\', \'smd_um_new_grp_based_on\');\r\n\r\n	// New group\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_grp\'), \'</h1>\',\r\n		n, \'<div id=\"\'.$smd_um_event.\'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, form(\r\n			graf(\r\n				\'<label for=\"smd_um_new_grp\">\' . gTxt(\'smd_um_grp_new\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_grp\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_grp\')\r\n				.n.\'<label for=\"smd_um_new_grp_name\">\' . gTxt(\'smd_um_grp_new_name\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_grp_name\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_grp_name\')\r\n				.n.\'<label for=\"smd_um_new_grp_based_on\">\' . gTxt(\'smd_um_based_on\') . \'</label>\'\r\n				.n.$grouplist\r\n				.n.fInput(\'submit\', \'smd_um_group_add\', gTxt(\'create\'))\r\n				.n.eInput($smd_um_event)\r\n				.n.sInput(\'smd_um_groups\')\r\n				)\r\n			, \'\',\'\',\'post\',\'search-form\'\r\n			),\r\n		n, \'</div>\';\r\n\r\n	// Retrieve the group info and user counts per privilege level\r\n	$fields = \'smdg.id, smdg.name, smdg.core, txu.total AS user_count\';\r\n	$clause = \' FROM \'.PFX.\'smd_um_groups AS smdg\r\n		LEFT JOIN (SELECT privs, count(privs) AS total FROM \'.PFX.\'txp_users GROUP BY privs) AS txu ON smdg.id = txu.privs\';\r\n\r\n	$rs = getRows(\'SELECT \' . $fields.$clause . \' ORDER BY id\');\r\n\r\n	if ($rs) {\r\n		echo n, \'<div class=\"plugin-column\">\',\r\n			n, \'<form action=\"index.php\" id=\"smd_um_grp_form\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n			n.\'<div class=\"txp-listtables\">\'.\r\n			n, startTable(\'\', \'\', \'txp-list\'),\r\n			n, \'<thead>\',\r\n			n, tr(\r\n				hCell(\'ID\', \'\', \' class=\"id\"\').\r\n				hCell(\'name\', \'\', \' class=\"name\"\').\r\n				hCell(\'title\', \'\', \' class=\"name\"\').\r\n				hCell(\'\', \'\', \'\')\r\n			),\r\n			n, \'</thead>\',\r\n			n, \'<tbody>\';\r\n\r\n		foreach ($rs as $row) {\r\n			extract(doSpecial($row));\r\n			$user_count = empty($user_count) ? 0 : $user_count;\r\n			$dLink = ($core) ? \'&nbsp;\' : fInput(\'submit\', \'smd_um_group_del\', \"×\", \'\', \'\', \'smd_um_presub(this)\', \'\', \'\', \'smd_um_del_\'.$id)\r\n				.eInput($smd_um_event)\r\n				.sInput(\'smd_um_groups\')\r\n				.tInput();\r\n			echo tr(\r\n				tda(\r\n					hInput(\'smd_um_group_id[]\', $id)\r\n					.(($user_count) ? eLink($smd_um_event, \'\', \'search_method\', \'privileges\', $id, \'crit\', $id) : $id)\r\n				, \' class=\"id\"\'\r\n					.(($user_count) ? \' title=\"\' . gTxt(\'smd_um_user_count\') . $user_count . \'\"\': \'\')\r\n				)\r\n				.td(fInput(\'text\', \'smd_um_group_name[]\', $name), \'\', \'name\')\r\n				.td(fInput(\'text\', \'smd_um_group_title[]\', gTxt($name)), \'\', \'name\')\r\n				.td($dLink)\r\n			);\r\n		}\r\n\r\n		echo n, \'</tbody>\',\r\n			n, endTable(),\r\n			n, \'</div>\',\r\n			n, graf(fInput(\'submit\', \'smd_um_group_save\', gTxt(\'save\'), \'publish\')),\r\n			n, fInput(\'hidden\', \'smd_um_grp_del\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_grp_del\'),\r\n			n, eInput($smd_um_event),\r\n			n, sInput(\'smd_um_groups\'),\r\n			n, tInput(),\r\n			n, \'</form>\',\r\n			n, smd_um_active_users(),\r\n			n, \'</div>\',\r\n			script_js(<<<EOJS\r\nfunction smd_um_presub(obj) {\r\n	jQuery(\'#smd_um_grp_del\').val(jQuery(obj).attr(\'id\'));\r\n}\r\nEOJS\r\n			);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Privs management panel\r\nfunction smd_um_privs($msg=\'\') {\r\n	global $smd_um_event, $txp_user, $txp_permissions;\r\n\r\n	require_privs($smd_um_event.\'.prv\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	if (ps(\'smd_um_priv_save\')) {\r\n		$areas = ps(\'smd_um_areas\');\r\n		foreach ($areas as $area) {\r\n			$ar_fakename = str_replace(\'.\', \'---\', $area);\r\n			$privs = ps($ar_fakename);\r\n			$area = strtolower(sanitizeForPage($area));\r\n			$safe_area = doSlash($area);\r\n\r\n			$current_privs = safe_column(\'priv\', SMD_UM_PRIVS, \"area=\'{$safe_area}\'\");\r\n			$default_privs = isset($txp_permissions[$area]) ? do_list($txp_permissions[$area]) : array();\r\n			$diff_added = array_diff($privs, $default_privs);\r\n			$diff_removed = array_diff($current_privs, $privs);\r\n\r\n			// Only alter privs if they differ from what\'s already stored\r\n			if ($diff_added || $diff_removed) {\r\n				// Delete the old area privs if they exist\r\n				safe_delete(SMD_UM_PRIVS, \"area=\'{$safe_area}\'\");\r\n\r\n				if (is_array($privs)) {\r\n					foreach ($privs as $priv) {\r\n						// Reset should always be first in the list since it\'s the first checkbox col\r\n						// If reset, don\'t add the privs again (thus they will be read from admin_config.php)\r\n						if ($priv == \'smd_um_reset\') {\r\n							break;\r\n						} else {\r\n							assert_int($priv);\r\n							safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($area) . \"\', priv=\'\" . doSlash($priv) . \"\'\");\r\n						}\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		// Merge the changes into the priv table\r\n		smd_um_priv_merge(0,1);\r\n		$msg = gTxt(\'smd_um_prv_saved\');\r\n	} else if (ps(\'smd_um_priv_add\')) {\r\n		$name = ps(\'smd_um_new_prv\');\r\n		$name = strtolower(sanitizeForPage($name));\r\n\r\n		if ($name) {\r\n			if (strpos($name, \'smd_um\') === 0) {\r\n				// Can\'t create privs for this plugin\r\n				$msg = array(gTxt(\'smd_um_prv_smd_um\'), E_USER_WARNING);\r\n			} else {\r\n				$exists = array_key_exists($name, $txp_permissions);\r\n				if ($exists) {\r\n					$msg = array(gTxt(\'smd_um_prv_exists\'), E_USER_WARNING);\r\n				} else {\r\n					safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($name) . \"\'\");\r\n\r\n					smd_um_priv_merge(0,1);\r\n					$msg = gTxt(\'smd_um_prv_created\', array(\'{area}\' => $name));\r\n				}\r\n			}\r\n		} else {\r\n			$msg = array(gTxt(\'smd_um_name_required\'), E_ERROR);\r\n		}\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_prv_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'prv\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_prv\'), \'</h1>\',\r\n		n, \'<div id=\"\'.$smd_um_event.\'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, form(\r\n			graf(\r\n				\'<label for=\"smd_um_new_prv\">\' . gTxt(\'smd_um_prv_new\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_prv\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_prv\')\r\n				.n.fInput(\'submit\', \'smd_um_priv_add\', gTxt(\'create\'))\r\n				.n.eInput($smd_um_event)\r\n				.n.sInput(\'smd_um_privs\')\r\n				)\r\n			, \'\',\'\',\'post\',\'search-form\'\r\n			),\r\n		n, \'</div>\';\r\n\r\n	$grouplist_name = smd_um_get_groups(0);\r\n	$grouplist_title = smd_um_get_groups(1);\r\n\r\n	unset($grouplist_name[0]); // Don\'t want None privs\r\n	unset($grouplist_title[0]); // Ditto\r\n\r\n	$curr_area = \'\';\r\n	$area_count = 0;\r\n	$thatts = \' class=\"smd_um_grp_name\" title=\"\' . gTxt(\'smd_um_sel_grp\') . \'\"\';\r\n	$headers = \'<thead>\'.tr(\r\n		hCell(\'\', \'\', \' class=\"smd_um_sel_area\" title=\"\' . gTxt(\'smd_um_sel_all\') . \'\"\')\r\n		.hCell(gTxt(\'smd_um_reset\'), \'\', \' class=\"smd_um_reset_col\" title=\"\' . gTxt(\'smd_um_sel_reset\') . \'\"\')\r\n		.hCell(join(\'</th><th\'.$thatts.\'>\', $grouplist_title), \'\', $thatts)\r\n	, \' class=\"smd_um_prv_hdr\"\'). \'</thead>\';\r\n\r\n	$viz = do_list(get_pref(\'pane_smd_um_priv_visible\'));\r\n\r\n	echo script_js(<<<EOJS\r\nfunction smd_um_presub(obj) {\r\n	jQuery(\'#smd_um_prv_del\').val(jQuery(obj).attr(\'id\'));\r\n}\r\n\r\njQuery.fn.smd_um_rowsel = function(idx) {\r\n	return jQuery(\'tr:nth-child(\'+(idx+1)+\') td.smd_um_checkbox\', this);\r\n}\r\njQuery.fn.smd_um_colsel = function(idx) {\r\n	return jQuery(\'tr td:nth-child(\'+(idx+1)+\')\', this);\r\n}\r\n\r\n// Affect all highlighted checkboxes on keypress\r\nfunction smd_um_toggleCheckbox(ev) {\r\n	key = ev.keyCode;\r\n	obj = jQuery(\'.smd_um_selected :checkbox\');\r\n	switch(key) {\r\n		case 67:\r\n			// (c)heck selected boxes\r\n			obj.prop(\'checked\', true);\r\n		break;\r\n		case 68:\r\n			// (d)eselect all selected rows/cols\r\n			jQuery(\'.smd_um_selected, .smd_um_rsel, .smd_um_csel\').removeClass(\'smd_um_selected smd_um_rsel smd_um_csel\');\r\n		break;\r\n\r\n		case 84:\r\n			// (t)oggle selected boxes\r\n			obj.each(function() {\r\n				cb = jQuery(this);\r\n				if (cb.prop(\'checked\') == true) {\r\n					cb.prop(\'checked\', false);\r\n				} else {\r\n					cb.prop(\'checked\', true);\r\n				}\r\n			});\r\n		break;\r\n		case 85:\r\n			// (u)ncheck selected boxes\r\n			obj.prop(\'checked\', false);\r\n		break;\r\n	}\r\n}\r\n\r\njQuery(function() {\r\n	jQuery(document).bind(\'keyup\', smd_um_toggleCheckbox);\r\n\r\n	// Row selector\r\n	jQuery(\'.smd_um_prv_name\').click(function() {\r\n		tr = jQuery(this).closest(\'tr\');\r\n		rownum = tr.index();\r\n		obj = jQuery(tr).parent().smd_um_rowsel(rownum);\r\n\r\n		// Can\'t use toggleClass because it would untoggle any cols that were already selected\r\n		if (jQuery(this).hasClass(\'smd_um_rsel\')) {\r\n			obj.removeClass(\'smd_um_selected\');\r\n		} else {\r\n			obj.addClass(\'smd_um_selected\');\r\n		}\r\n		jQuery(this).toggleClass(\'smd_um_rsel\');\r\n	});\r\n\r\n	// Column selector\r\n	jQuery(\'.smd_um_grp_name, .smd_um_reset_col\').click(function() {\r\n		colnum = jQuery(this).index();\r\n		tbody = jQuery(this).parent().parent().next(\'tbody\');\r\n		obj = jQuery(tbody).smd_um_colsel(colnum);\r\n\r\n		// Can\'t use toggleClass because it would untoggle any rows that were already selected\r\n		if (colnum > 0) {\r\n			if (jQuery(this).hasClass(\'smd_um_csel\')) {\r\n				obj.removeClass(\'smd_um_selected\');\r\n			} else {\r\n				obj.addClass(\'smd_um_selected\');\r\n			}\r\n		}\r\n		jQuery(this).toggleClass(\'smd_um_csel\');\r\n	});\r\n\r\n	// Whole table selector\r\n	jQuery(\'.smd_um_sel_area\').click(function() {\r\n		me = jQuery(this);\r\n		thead = me.parent().parent();\r\n		tbody = thead.next(\'tbody\');\r\n		tbody.toggleClass(\'smd_um_allsel\');\r\n		if (tbody.hasClass(\'smd_um_allsel\')) {\r\n			tbody.find(\'.smd_um_prv_name\').removeClass(\'smd_um_rsel\').click();\r\n			me.nextAll(\'.smd_um_grp_name\').addClass(\'smd_um_csel\');\r\n		} else {\r\n			tbody.find(\'.smd_um_prv_name\').addClass(\'smd_um_rsel\').click();\r\n			me.nextAll(\'.smd_um_grp_name\').removeClass(\'smd_um_csel\');\r\n		}\r\n	});\r\n\r\n});\r\nEOJS\r\n	);\r\n\r\n	echo n, \'<div class=\"txp-list\">\',\r\n		n, \'<form action=\"index.php\" id=\"smd_um_privilege_form\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n		n, eInput($smd_um_event).sInput(\'smd_um_privs\').tInput();\r\n\r\n	foreach ($txp_permissions as $area => $privs) {\r\n		$priv_list = do_list($privs);\r\n		$area_parts = do_list($area, \'.\');\r\n		if (preg_match(\'/^([A-Za-z0-9]{3,3})\\_/\', $area_parts[0], $matches)) {\r\n			// Plugin\r\n			$area_parts[0] = $matches[1];\r\n		}\r\n		// Start of a new area so close the previous one and start a new block\r\n		if ($curr_area != $area_parts[0]) {\r\n			if ($area_count > 0) {\r\n				echo \'</tbody>\' . endTable() . \'</div></div>\';\r\n			}\r\n			$area_head = gTxt($area_parts[0]);\r\n			$is_viz = in_array($area_parts[0], $viz);\r\n			$ref = \'smd_um_priv_\'.$area_parts[0];\r\n			echo n, \'<div class=\"smd_um_privgroup\"><h3 class=\"txp-summary lever\', ($is_viz ? \' expanded\' : \'\'), \'\"><a href=\"#\', $ref, \'\">\', $area_parts[0], (($area_parts[0] != $area_head) ? \' (\'. gTxt($area_parts[0]). \')\' : \'\'), \'</a></h3>\',\r\n				n, \'<div id=\"\', $ref, \'\" class=\"toggle\" style=\"display:\', ($is_viz ? \'block\' : \'none\'), \'\">\',\r\n				n, fInput(\'submit\', \'smd_um_priv_save\', gTxt(\'save\'), \'smd_um_privsave\'),\r\n				n, startTable(\'\', \'\', \'txp-list\'),\r\n				n, $headers,\r\n				n, \'<tbody>\';\r\n		}\r\n\r\n		$privboxes = array();\r\n		$safe_area = str_replace(\'.\', \'---\', $area).\'[]\';\r\n		foreach ($grouplist_name as $id => $priv) {\r\n			/// Dots aren\'t valid characters for a name so replace them now and swap them back upon Save\r\n			$privboxes[] = td(checkbox($safe_area, $id, (in_array($id, $priv_list))), \'\', \'smd_um_checkbox\');\r\n		}\r\n\r\n		echo tr(\r\n			tda($area.hInput(\'smd_um_areas[]\', $area), \' class=\"smd_um_prv_name\" title=\"\' . gTxt(\'smd_um_sel_prv\') . \'\"\')\r\n			.td(checkbox($safe_area, \'smd_um_reset\', 0), \'\', \'smd_um_resetbox\')\r\n			.join(n, $privboxes)\r\n		);\r\n		$curr_area = $area_parts[0];\r\n		$area_count++;\r\n	}\r\n\r\n	echo n, endTable(),\r\n		n, \'</div></div>\',\r\n		n, fInput(\'hidden\', \'smd_um_prv_del\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_prv_del\'),\r\n		n, \'</form>\',\r\n		n, smd_um_active_users(),\r\n		n, \'</div>\';\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_um_wrap_widget($widget) {\r\n	return \'<span class=\"edit-value\">\'.$widget.\'</span>\';\r\n}\r\n\r\n// ------------------------\r\n// Prefs management panel\r\nfunction smd_um_prefs($msg=\'\') {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	require_privs($smd_um_event.\'.prf\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if (ps(\'smd_um_pref_save\')) {\r\n		foreach ($smd_um_prefs as $idx => $prefobj) {\r\n			$val = ps($idx);\r\n			$val = (is_array($val)) ? join(\', \', $val) : $val;\r\n			set_pref($idx, doSlash($val), $smd_um_event, $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n		}\r\n\r\n		$msg = gTxt(\'preferences_saved\');\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_prf_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'prf\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_prf\'), \'</h1>\',\r\n		n, \'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\', $btnbar, \'</div>\';\r\n\r\n	$out = array();\r\n	$out[] = n.\'<div class=\"plugin-column\">\';\r\n	$out[] = \'<form name=\"smd_um_prefs\" id=\"smd_um_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_um_event).sInput(\'smd_um_prefs\');\r\n\r\n	$last_grp = \'\';\r\n	foreach ($smd_um_prefs as $idx => $prefobj) {\r\n		if ($last_grp != $prefobj[\'group\']) {\r\n			$out[] = hed(gTxt($prefobj[\'group\']), 2);\r\n		}\r\n		$last_grp = $prefobj[\'group\'];\r\n		$subout = array();\r\n		$label = \'<span class=\"edit-label\">\'\r\n				.\'<label>\'.gTxt($idx).\'</label>\'\r\n				.\'</span>\';\r\n		$val = get_pref($idx, $prefobj[\'default\'], 1);\r\n		$vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'\';\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = smd_um_wrap_widget(fInput(\'text\', $idx, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $idx));\r\n			break;\r\n			case \'textarea\':\r\n				$subout[] = text_area($idx, \'\', \'\', $val, $idx);\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = smd_um_wrap_widget(yesnoRadio($idx, $val));\r\n			break;\r\n			case \'radioset\':\r\n				$subout[] = smd_um_wrap_widget(radioSet($prefobj[\'content\'], $idx, $val));\r\n			break;\r\n			case \'checkboxset\':\r\n				$vals = do_list($val);\r\n				$lclout = array();\r\n				foreach ($prefobj[\'content\'] as $cb => $val) {\r\n					$checked = in_array($cb, $vals);\r\n					$lclout[] = checkbox($idx.\'[]\', $cb, $checked). \'<label>\' . gTxt($val) . \'</label>\';\r\n				}\r\n				$subout[] = smd_um_wrap_widget(join(n, $lclout));\r\n			break;\r\n			case \'selectlist\':\r\n				$subout[] = smd_um_wrap_widget(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]));\r\n			break;\r\n			default:\r\n				if ( strpos($prefobj[\'html\'], \'smd_um_\') !== false && is_callable($prefobj[\'html\']) ) {\r\n					$subout[] = smd_um_wrap_widget($prefobj[\'html\']($idx, $val));\r\n				}\r\n			break;\r\n		}\r\n		$out[] = graf($label . n.join(n ,$subout), ($vis ? \' class=\"\'.$vis.\'\"\' : \'\'));\r\n	}\r\n	$out[] = graf(fInput(\'submit\', \'smd_um_pref_save\', gTxt(\'save\'), \'publish\'));\r\n	$out[] = tInput();\r\n	$out[] = \'</form>\'.smd_um_active_users().\'</div>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// ------------------------\r\n// Common buttons for the interface\r\nfunction smd_um_buttons($curr=\'usr\') {\r\n	global $step, $smd_um_event;\r\n\r\n	$grp = has_privs($smd_um_event.\'.grp\');\r\n	$prf = has_privs($smd_um_event.\'.prf\');\r\n	$prv = has_privs($smd_um_event.\'.prv\');\r\n	$new = has_privs($smd_um_event.\'.usr.create\');\r\n	$usr = ($grp || $prf || $prv); // Don\'t show usr button if it\'s the only tab available\r\n\r\n	$btns = array (\r\n		\'new\' => ( ($new) ? sLink($smd_um_event, \'smd_um_edit\', gTxt(\'smd_um_new_user\'), \'navlink\') : \'\'),\r\n		\'grp\' => ( ($grp) ? sLink($smd_um_event, \'smd_um_groups\', gTxt(\'smd_um_grp_lbl\'), \'navlink\') : \'\'),\r\n		\'prf\' => ( ($prf) ? sLink($smd_um_event, \'smd_um_prefs\', gTxt(\'smd_um_prf_lbl\'), \'navlink\') : \'\'),\r\n		\'prv\' => ( ($prv) ? sLink($smd_um_event, \'smd_um_privs\', gTxt(\'smd_um_prv_lbl\'), \'navlink\') : \'\'),\r\n		\'usr\' => ( ($usr) ? sLink($smd_um_event, \'smd_um_list\', gTxt(\'smd_um_usr_lbl\'), \'navlink\') : \'\'),\r\n		\'chp\' => ( sLink($smd_um_event, \'smd_um_change_pass_form\', gTxt(\'change_password\'), \'navlink\')),\r\n	);\r\n\r\n	return graf(\r\n			(($curr == \'usr\') ? n. ($step === \'smd_um_edit\' ? \'\' : $btns[\'chp\']) .n. $btns[\'new\'] .n. strong($btns[\'usr\']) : n.$btns[\'usr\'])\r\n			.n.(($curr == \'grp\') ? strong($btns[\'grp\']) : $btns[\'grp\'])\r\n			.n.(($curr == \'prv\') ? strong($btns[\'prv\']) : $btns[\'prv\'])\r\n			.n.(($curr == \'prf\') ? strong($btns[\'prf\']) : $btns[\'prf\'])\r\n		, \' class=\"txp-buttons\"\');\r\n}\r\n\r\n// ------------------------\r\n// Alter the pageby quantity\r\nfunction smd_um_change_pageby() {\r\n	global $smd_um_event;\r\n	event_change_pageby(\'smd_um\');\r\n	$smd_um_event();\r\n}\r\n\r\n// ------------------------\r\n// The user panel search dropdown list\r\nfunction smd_um_search_form($crit, $method) {\r\n	global $smd_um_event;\r\n\r\n	$methods =	array(\r\n		\'login_name\'    => gTxt(\'login_name\'),\r\n		\'real_name\'     => gTxt(\'real_name\'),\r\n		\'email\'         => gTxt(\'email\'),\r\n		\'privileges\'    => gTxt(\'privileges\'),\r\n		\'article_count\' => gTxt(\'smd_um_article_count\'),\r\n		\'image_count\'   => gTxt(\'smd_um_image_count\'),\r\n		\'file_count\'    => gTxt(\'smd_um_file_count\'),\r\n		\'link_count\'    => gTxt(\'smd_um_link_count\'),\r\n	);\r\n\r\n	return search_form($smd_um_event, \'\', $crit, $methods, $method, \'login\');\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_get_priv_level($priv) {\r\n	$levels = get_groups();\r\n	return $levels[$priv];\r\n}\r\n\r\n// ------------------------\r\n// Merge/edit the groups & privs into the existing global arrays\r\nfunction smd_um_priv_merge($do_grp=1, $do_priv=1) {\r\n	global $txp_groups, $txp_permissions;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if ($do_grp && smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		$new_groups = safe_rows(\'id, name\', SMD_UM_GROUPS, \'1=1\');\r\n		foreach ($new_groups as $row) {\r\n			$txp_groups[$row[\'id\']] = $row[\'name\'];\r\n		}\r\n		ksort($txp_groups);\r\n	}\r\n	if ($do_priv && smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		$new_privs = safe_rows(\'area, GROUP_CONCAT(priv) AS privs\', SMD_UM_PRIVS, \'1=1 GROUP BY area ORDER BY area\');\r\n\r\n		// Allow this plugin\'s strings to be skipped if we don\'t want people upsetting the plugin\'s behaviour\r\n		$self_edit = get_pref(\'smd_um_self_alter\', $smd_um_prefs[\'smd_um_self_alter\'][\'default\']);\r\n		foreach ($new_privs as $row) {\r\n			if (strpos($row[\'area\'], \'smd_um\') === false || $self_edit) {\r\n				$txp_permissions[$row[\'area\']] = $row[\'privs\'];\r\n			}\r\n		}\r\n		ksort($txp_permissions);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Show who\'s currently online\r\nfunction smd_um_active_users() {\r\n	global $smd_um_event;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$curr_users = unserialize(get_pref(\'smd_um_current_users\', \'\', 1));\r\n	$timeout = get_pref(\'smd_um_active_timeout\', $smd_um_prefs[\'smd_um_active_timeout\'][\'default\']);\r\n	$online = array();\r\n	if (is_array($curr_users)) {\r\n		foreach ($curr_users as $user => $last_access) {\r\n			$still_active = strtotime(\"+$timeout seconds\", $last_access);\r\n			if ( ($still_active !== false) && ($still_active > time()) ) {\r\n				$online[] = eLink($smd_um_event, \'\', \'search_method\', \'login_name\', $user, \'crit\', $user);\r\n			}\r\n		}\r\n	}\r\n\r\n	return ($online) ? \'<div class=\"smd_um_active_users\">\' . gTxt(\'smd_um_active\') . join(\', \', $online) . \'</div>\' : \'\';\r\n}\r\n\r\n// ------------------------\r\n// Update any language string. Note this may leave orphan strings if the name is changed\r\nfunction smd_um_upsert_lang($title, $name=\'\') {\r\n	global $textarray;\r\n\r\n	$lang = get_pref(\'language\', \'en-gb\');\r\n	$name = (isset($name) && $name != \'\') ? $name : strtolower(sanitizeForUrl($title));\r\n	$table = \'txp_lang\';\r\n	$where = \"name=\'\" . doSlash($name) . \"\' AND lang=\'\" . doSlash($lang) . \"\'\";\r\n\r\n	// Try to update first\r\n	$ret = safe_update($table, \"data=\'\" . doSlash($title) . \"\'\", $where);\r\n	if ($ret && (mysql_affected_rows() || safe_count($table, $where))) {\r\n		// Do nothing -- record has been updated\r\n	} else {\r\n		safe_insert($table, \"event=\'admin\', name=\'\" . doSlash($name) . \"\', lang=\'\" . doSlash($lang) . \"\', data=\'\" . doSlash($title) . \"\'\");\r\n	}\r\n	// Update the global array on the page so gtxt() can get it immediately\r\n	$textarray[$name] = $title;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_get_groups($type=0) {\r\n	global $txp_groups, $txp_user;\r\n	static $permitted_users = array();\r\n\r\n	if (isset($permitted_users[$type])) {\r\n		return $permitted_users[$type];\r\n	}\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$levels = ($type) ? get_groups() : $txp_groups;\r\n	$tiered = get_pref(\'smd_um_hierarchical_groups\', $smd_um_prefs[\'smd_um_hierarchical_groups\'][\'default\']);\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	if (smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		$protected = get_pref(\'smd_um_admin_group\', $smd_um_prefs[\'smd_um_admin_group\'][\'default\']);\r\n		$grp = safe_rows(\'id, name\', SMD_UM_GROUPS, \'1=1\');\r\n		foreach ($grp as $row) {\r\n			if ($protected && ($row[\'id\'] == $protected) && ($curr_priv != $protected) ) {\r\n				unset($levels[$row[\'id\']]);\r\n			} else {\r\n				$levels[$row[\'id\']] = (($type) ? gTxt($row[\'name\']) : $row[\'name\']);\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($tiered) {\r\n		// Remove any privs higher than the current logged in user\r\n		foreach ($levels as $priv => $name) {\r\n			if ( ($priv == 0) || ($priv >= $curr_priv) ) {\r\n				$permitted_users[$type][$priv] = $name;\r\n			}\r\n		}\r\n	} else {\r\n		$permitted_users[$type] = $levels;\r\n	}\r\n\r\n	ksort($permitted_users[$type]);\r\n\r\n	return $permitted_users[$type];\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) {\r\n\r\n	$levels = smd_um_get_groups(1);\r\n	$privileges = selectInput(\'privs\', $levels, \'\', \'\', \'\', \'privileges\');\r\n\r\n	$rs = safe_column(\'name\', \'txp_users\', \'1=1\');\r\n	$assign_assets = $rs ? \'<label for=\"assign_assets\">\'.gTxt(\'assign_assets_to\').\'</label>\'.n.selectInput(\'assign_assets\', $rs, \'\', true, \'\', \'assign_assets\') : \'\';\r\n\r\n	$methods = array(\r\n		\'changeprivilege\' => array(\'label\' => gTxt(\'changeprivilege\'), \'html\' => $privileges),\r\n		\'resetpassword\'   => gTxt(\'resetpassword\'),\r\n		\'delete\'          => array(\'label\' => gTxt(\'delete\'), \'html\' => $assign_assets),\r\n	);\r\n\r\n	if (safe_count(\'txp_users\', \'1=1\') <= 1) unset($methods[\'delete\']);\r\n\r\n	return multi_edit($methods, \'smd_um\', \'smd_um_multi_edit\', $page, $sort, $dir, $crit, $search_method);\r\n}\r\n\r\n// ------------------------\r\n// Cloned and tweaked from Admin tab :-(\r\nfunction smd_um_multi_edit() {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	require_privs($smd_um_event.\'.usr.edit\');\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$selected = ps(\'selected\');\r\n	$method   = ps(\'edit_method\');\r\n	$changed  = array();\r\n\r\n	if (!$selected or !is_array($selected))\r\n	{\r\n		return $smd_um_event();\r\n	}\r\n\r\n	$names = safe_column(\'name\', \'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($selected)).\"\') AND name != \'\".doSlash($txp_user).\"\'\");\r\n\r\n	if (!$names) return $smd_um_event();\r\n\r\n	switch ($method) {\r\n		case \'delete\':\r\n			$assign_assets = ps(\'assign_assets\');\r\n			if ($assign_assets === \'\') {\r\n				$msg = array(\'must_reassign_assets\', E_ERROR);\r\n			} elseif (in_array($assign_assets, $names)) {\r\n				$msg = array(\'cannot_assign_assets_to_deletee\', E_ERROR);\r\n			} elseif (safe_delete(\'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\")) {\r\n				$changed = $names;\r\n				$assign_assets = doSlash($assign_assets);\r\n				$names = join(\"\',\'\", doSlash($names));\r\n\r\n				// Delete private prefs\r\n				safe_delete(\'txp_prefs\', \"user_name IN (\'$names\')\");\r\n\r\n				// Assign dangling assets to their new owner\r\n				$reassign = array(\r\n					\'textpattern\' => \'AuthorID\',\r\n					\'txp_file\' 	=> \'author\',\r\n					\'txp_image\' => \'author\',\r\n					\'txp_link\' 	=> \'author\',\r\n				);\r\n				foreach ($reassign as $table => $col) {\r\n					safe_update($table, \"$col=\'$assign_assets\'\", \"$col IN (\'$names\')\");\r\n				}\r\n				callback_event(\'authors_deleted\', \'\', 0, $changed);\r\n				$msg = \'author_deleted\';\r\n			}\r\n			break;\r\n		case \'changeprivilege\':\r\n			$levels = smd_um_get_groups(1);\r\n			$privilege = ps(\'privs\');\r\n\r\n			if (!isset($levels[$privilege])) return $smd_um_event();\r\n\r\n			if (safe_update(\'txp_users\', \'privs = \'.intval($privilege), \"name IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\")) {\r\n				$changed = $names;\r\n				$msg = \'author_updated\';\r\n			}\r\n			break;\r\n\r\n		case \'resetpassword\':\r\n			$failed  = array();\r\n			$pass_len = get_pref(\'smd_um_pass_length\', $smd_um_prefs[\'smd_um_pass_length\'][\'default\']);\r\n\r\n			foreach ($names as $name) {\r\n				$passwd = generate_password($pass_len);\r\n				$hash = doSlash(txp_hash_password($passwd));\r\n\r\n				if (safe_update(\'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($name).\"\'\")) {\r\n					$email = safe_field(\'email\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n					$cb_params = array(\r\n						\'name\'  => $name,\r\n						\'email\' => $email,\r\n						\'pass\'  => $passwd,\r\n					);\r\n\r\n					$msg = callback_event(\'smd_user_manager\', \'password.reset\', 0, $cb_params);\r\n					if (strlen($msg) === 0) {\r\n						if (send_new_password($passwd, $email, $name)) {\r\n							$changed[] = $name;\r\n							$msg = \'author_updated\';\r\n						} else {\r\n							$msg = (array(gTxt(\'could_not_mail\').\' \'.txpspecialchars($name), E_ERROR));\r\n						}\r\n					}\r\n				}\r\n			}\r\n			break;\r\n	}\r\n\r\n	if ($changed) {\r\n		return $smd_um_event(gTxt($msg, array(\'{name}\' => txpspecialchars(join(\', \', $changed)))));\r\n	}\r\n\r\n	$smd_um_event($msg);\r\n}\r\n\r\n// ------------------------\r\n// Mostly cloned from Admin->Users tab\r\nfunction smd_um_change_pass_form() {\r\n	global $smd_um_event;\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_chp_lbl\'), \'\');\r\n\r\n	echo form(\r\n		\'<div class=\"txp-edit\">\'.\r\n		hed(gTxt(\'change_password\'), 2).n.\r\n		inputLabel(\'new_pass\', fInput(\'password\', \'new_pass\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', \'new_pass\'), \'new_password\').n.\r\n		graf(checkbox(\'mail_password\', \'1\', true, \'\', \'mail_password\') .n. \'<label for=\"mail_password\">\'.gTxt(\'mail_it\').\'</label>\', \' class=\"edit-mail-password\"\').n.\r\n		graf(fInput(\'submit\', \'change_pass\', gTxt(\'submit\'), \'publish\')).\r\n		eInput($smd_um_event).\r\n		sInput(\'smd_um_change_pass\').\r\n		\'</div>\'\r\n	, \'\', \'\', \'post\', \'\', \'\', \'change_password\');\r\n}\r\n\r\n// ------------------------\r\n// Mostly cloned from Admin->Users tab\r\nfunction smd_um_change_pass() {\r\n	global $event, $step, $txp_user;\r\n\r\n	// Call the Admin side\'s change_pass routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'change_pass\');\r\n\r\n	extract(psa(array(\'new_pass\', \'mail_password\')));\r\n\r\n	if (empty($new_pass)) {\r\n		smd_um_dispatcher($event, \'\', array(gTxt(\'password_required\'), E_ERROR));\r\n		return;\r\n	}\r\n\r\n	$hash = doSlash(txp_hash_password($new_pass));\r\n	$rs = safe_update(\'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($txp_user).\"\'\");\r\n\r\n	if ($rs) {\r\n		$msg = gTxt(\'password_changed\');\r\n\r\n		if ($mail_password) {\r\n			$email = fetch(\'email\', \'txp_users\', \'name\', $txp_user);\r\n			send_new_password($new_pass, $email, $txp_user);\r\n			$msg .= sp.gTxt(\'and_mailed_to\').sp.$email;\r\n		} else {\r\n			echo comment(mysql_error());\r\n		}\r\n\r\n		$msg .= \'.\';\r\n	} else {\r\n		$msg = array(gTxt(\'smd_um_pass_change_error\'), E_ERROR);\r\n	}\r\n	smd_um_dispatcher($event, \'\', $msg);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_save_pane_state() {\r\n	global $smd_um_event;\r\n\r\n	$pane = str_replace(\'smd_um_priv_\', \'\', gps(\'pane\'));\r\n	$curr = do_list(get_pref(\'pane_smd_um_priv_visible\'));\r\n	$visible = gps(\'visible\');\r\n	if ($visible == \'true\') {\r\n		$curr[] = $pane;\r\n	} else {\r\n		$pos = array_search($pane, $curr);\r\n		if ($pos !== false) {\r\n			unset($curr[$pos]);\r\n		}\r\n	}\r\n\r\n	$curr = array_unique($curr);\r\n\r\n	set_pref(\"pane_smd_um_priv_visible\", (join(\',\', $curr)), $smd_um_event, PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\r\n	send_xml_response();\r\n}\r\n\r\n// ****************\r\n// TABLE MANAGEMENT\r\n// ****************\r\n// Add group/priv tables if not already installed\r\nfunction smd_um_table_install($showpane=\'1\') {\r\n	global $smd_um_event, $txp_groups, $txp_permissions;\r\n\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n\r\n	// In truth, this table should be normalised further, but for the sake\r\n	// of one row per priv level per area, it\'s quicker than a join, and\r\n	// using GROUP_CONCAT() gets the priv table as in admin.config.php\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_UM_PRIVS.\"` (\r\n		`area` varchar(127) NOT NULL default \'\',\r\n		`priv` smallint NOT NULL default 0,\r\n		PRIMARY KEY (`area`,`priv`)\r\n	) ENGINE=MyISAM\";\r\n\r\n	// id is NOT an auto_increment colummn because autoinc doesn\'t allow a 0 entry, which we need for \'None\'\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_UM_GROUPS.\"` (\r\n		`id` smallint(4) NOT NULL default 0,\r\n		`name` varchar(64) NOT NULL default \'\',\r\n		`core` bool NOT NULL default 0,\r\n		PRIMARY KEY (`id`)\r\n	) ENGINE=MyISAM PACK_KEYS=1\";\r\n\r\n	// Handle upgrades: be kind to beta testers\r\n	if (smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		$flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_UM_PRIVS.\'`\');\r\n		if (in_array(\'core\',$flds)) {\r\n			$sql[] = \"ALTER TABLE `\".PFX.SMD_UM_PRIVS.\"` DROP `core`\";\r\n		}\r\n	}\r\n\r\n	// Append initial value population to query stack if this is a new install\r\n	if (!smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		foreach ($txp_groups as $id => $name) {\r\n			$sql[] = \"INSERT INTO `\".PFX.SMD_UM_GROUPS.\"` VALUES (\'$id\', \'$name\', 1)\";\r\n		}\r\n	}\r\n	if (!smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		foreach ($txp_permissions as $area => $privs) {\r\n			$priv_list = do_list($privs);\r\n			foreach ($priv_list as $priv) {\r\n				$sql[] = \"INSERT INTO `\".PFX.SMD_UM_PRIVS.\"` VALUES (\'$area\', \'$priv\')\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if(gps(\'debug\')) {\r\n		dmp($sql);\r\n	}\r\n	foreach ($sql as $qry) {\r\n		$ret = safe_query($qry);\r\n		if ($ret===false) {\r\n			$GLOBALS[\'txp_err_count\']++;\r\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n			echo \"<!--\\n $qry \\n-->\\n\";\r\n		}\r\n	}\r\n\r\n	// Spit out results\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		if ($showpane) {\r\n			$msg = gTxt(\'smd_um_tbl_installed\');\r\n			$smd_um_event($msg);\r\n		}\r\n	} else {\r\n		if ($showpane) {\r\n			$msg = gTxt(\'smd_um_tbl_not_installed\');\r\n			$smd_um_event($msg);\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_um_table_remove() {\r\n	global $smd_um_event;\r\n\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_um_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_UM_PRIVS. \"; \";\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_UM_GROUPS. \"; \";\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$msg = gTxt(\'smd_um_tbl_removed\');\r\n	} else {\r\n		$msg = gTxt(\'smd_um_tbl_not_removed\');\r\n		$smd_um_event($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_table_exist($which=\'\') {\r\n	static $smd_um_installed = array();\r\n\r\n	// The number of expected cols in each table\r\n	$tbls = array(\r\n		SMD_UM_GROUPS => 3,\r\n		SMD_UM_PRIVS => 2,\r\n	);\r\n\r\n	if ($which && array_key_exists($which, $tbls) && isset($smd_um_installed[$which])) {\r\n		return ($smd_um_installed[$which] == $tbls[$which]);\r\n	}\r\n\r\n	if ($which == \'1\') {\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			$num = count(@safe_show(\'columns\', $tbl));\r\n			$smd_um_installed[$tbl] = $num;\r\n			$out -= ($tbls[$tbl] == $num) ? 1 : 0;\r\n		}\r\n		return ($out===0) ? 1 : 0;\r\n	} else if (array_key_exists($which, $tbls)) {\r\n		$num = count(@safe_show(\'columns\', $which));\r\n		$smd_um_installed[$which] = $num;\r\n		return ($smd_um_installed[$which] == $tbls[$which]);\r\n	}\r\n\r\n	return false;\r\n}\r\n\r\n\r\n//*****************\r\n// PUBLIC SIDE TAGS\r\n// Though we could load all $txp_permissions / $txp_groups to the public side for speed,\r\n// exposing permissions to the world is not such a hot idea. Therefore the privs are\r\n// fetched ad-hoc and cached\r\nfunction smd_um_has_privs($atts, $thing=NULL) {\r\n	global $txp_user;\r\n	static $smd_um_permissions;\r\n	static $smd_um_groups;\r\n	static $smd_ili = 0;\r\n\r\n	extract(lAtts(array(\r\n		\'name\'  => \'\',\r\n		\'group\' => \'\',\r\n		\'area\'  => \'\',\r\n		\'debug\' => 0,\r\n	),$atts));\r\n\r\n	$ret = false;\r\n	$smd_ili = ($smd_ili === 0) ? is_logged_in() : $smd_ili;\r\n\r\n	if ($smd_ili) {\r\n		$names = do_list($name);\r\n		$groups = do_list($group);\r\n		$areas = do_list($area);\r\n\r\n		// Handle > and < groups\r\n		$grplist = array();\r\n		foreach ($groups as $grp) {\r\n			if ( (strpos($grp, \'>\') === 0) || (strpos($grp, \'<\') === 0) ) {\r\n				if (!isset($smd_um_groups)) {\r\n					$smd_um_groups = safe_column(\'id\', SMD_UM_GROUPS, \'1=1 ORDER BY id\');\r\n				}\r\n				$val = substr($grp, 1);\r\n				// Pull out all groups higher than this one\r\n				if (substr($grp, 0, 1) == \'>\') {\r\n					foreach($smd_um_groups as $ug) {\r\n						if ($ug > $val) $grplist[] = $ug;\r\n					}\r\n				}\r\n				// Pull out all groups lower than this one\r\n				if (substr($grp, 0, 1) == \'<\') {\r\n					foreach($smd_um_groups as $ug) {\r\n						if (($ug < $val) && ($ug != \'0\')) $grplist[] = $ug;\r\n					}\r\n				}\r\n			} else {\r\n				$grplist[] = $grp;\r\n			}\r\n		}\r\n		$groups = array_unique($grplist);\r\n\r\n		if ($debug) {\r\n			echo \'++ LOGGED IN CREDENTIALS / PERMISSION AREAS / ALL GROUP IDs / NAME ATTR / GROUP ATTR / AREA ATTR ++\';\r\n			dmp($smd_ili, $smd_um_permissions, $smd_um_groups, $names, $groups, $areas);\r\n		}\r\n\r\n		$isname  = ($name && in_array($smd_ili[\'name\'], $names));\r\n		$isgroup = (($group != \'\') && in_array($smd_ili[\'privs\'], $groups));\r\n		$isarea = false;\r\n\r\n		// Build up a cached array of privs by area\r\n		if ($areas) {\r\n			// TODO: would be nice to do this in one query somehow\r\n			foreach ($areas as $place) {\r\n				if (!isset($smd_um_permissions[$place])) {\r\n					$prv = safe_field(\'GROUP_CONCAT(priv) AS privs\', SMD_UM_PRIVS, \"area = \'\" . doSlash($place) . \"\'\");\r\n					$smd_um_permissions[$place] = $prv;\r\n				}\r\n				$isarea = ( $isarea || (in_array($smd_ili[\'privs\'], do_list($smd_um_permissions[$place]))) );\r\n			}\r\n		}\r\n\r\n		if ($debug) {\r\n			echo \'++ TEST AGAINST NAME / GROUP / AREA ++\';\r\n			dmp($isname, $isgroup, $isarea);\r\n		}\r\n\r\n		// Compare the current logged in credentials against the relevant passed-in attribute combinations\r\n		if ($name) {\r\n			if ($group) {\r\n				if ($area) {\r\n					if ($debug) dmp(\'CHECK NAME AND GROUP AND AREA\');\r\n					$ret = ($isname && $isgroup && $isarea);\r\n				} else {\r\n					if ($debug) dmp(\'CHECK NAME AND GROUP\');\r\n					$ret = ($isname && $isgroup);\r\n				}\r\n			} else if ($area) {\r\n				if ($debug) dmp(\'CHECK NAME AND AREA\');\r\n				$ret = ($isname && $isarea);\r\n			} else {\r\n				if ($debug) dmp(\'CHECK NAME\');\r\n				$ret = $isname;\r\n			}\r\n		} elseif($group) {\r\n			if ($area) {\r\n				if ($debug) dmp(\'CHECK GROUP AND AREA\');\r\n				$ret = ($isgroup && $isarea);\r\n			} else {\r\n				if ($debug) dmp(\'CHECK GROUP\');\r\n				$ret = $isgroup;\r\n			}\r\n		} elseif($area) {\r\n			if ($debug) dmp(\'CHECK AREA\');\r\n			$ret = $isarea;\r\n		} else {\r\n			if ($debug) dmp(\'NO CHECKS (ANY USER)\');\r\n			$ret = true;\r\n		}\r\n	}\r\n\r\n	return parse(EvalElse($thing, $ret));\r\n}\r\n\r\n// ------------------------\r\n// Settings for the plugin\r\nfunction smd_um_get_prefs() {\r\n	global $prefs;\r\n\r\n	$smd_um_prefs = array(\r\n		\'smd_um_hierarchical_groups\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'0\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_admin_group\' => array(\r\n			\'html\'     => \'selectlist\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'content\'  => array(get_groups(), false),\r\n			\'default\'  => \'1\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_max_search_limit\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'500\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_pass_length\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'default\'  => \'12\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_active_timeout\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 50,\r\n			\'default\'  => \'60\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_self_alter\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'0\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n	);\r\n\r\n	return $smd_um_prefs;\r\n}', '//<?php\r\n/**\r\n * smd_user_manager\r\n *\r\n * A Textpattern CMS plugin for complete user administration:\r\n *  -> Search / filter / alter info on users (with asset counts)\r\n *  -> Create / alter groups (roles)\r\n *  -> Create / customise privs (areas)\r\n *  -> Online user list\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n//  -> Why does multi-edit fire twice? Is it still attached to the Admin->Users table?\r\n\r\nif (!defined(\'SMD_UM_PRIVS\')) {\r\n	define(\"SMD_UM_PRIVS\", \'smd_um_privs\');\r\n}\r\nif (!defined(\'SMD_UM_GROUPS\')) {\r\n	define(\"SMD_UM_GROUPS\", \'smd_um_groups\');\r\n}\r\n\r\nif(@txpinterface === \'admin\') {\r\n	global $smd_um_event, $txp_permissions, $txp_groups, $txp_user, $step;\r\n\r\n	$smd_um_event = \'smd_um\';\r\n\r\n	add_privs($smd_um_event.\'.usr.list\', \'1, 2, 3\');\r\n	add_privs($smd_um_event.\'.usr.edit\', \'1, 2\');\r\n	add_privs($smd_um_event.\'.usr.create\', \'1\');\r\n	add_privs($smd_um_event.\'.grp\', \'1\');\r\n	add_privs($smd_um_event.\'.prv\', \'1\');\r\n	add_privs($smd_um_event.\'.prf\', \'1\');\r\n	add_privs(\'plugin_prefs.smd_user_manager\', \'1\');\r\n	register_tab(\'admin\', $smd_um_event, gTxt(\'smd_um_tab_name\'));\r\n	register_callback(\'smd_um_silence\', \'admin\');\r\n	register_callback(\'smd_um_dispatcher\', $smd_um_event);\r\n	register_callback(\'smd_um_dispatcher\', $smd_um_event.\'.usr.edit.own\');\r\n	register_callback(\'smd_um_users_tab\', \'admin_side\', \'head_end\');\r\n	register_callback(\'smd_um_prefs\', \'plugin_prefs.smd_user_manager\');\r\n	register_callback(\'smd_um_welcome\', \'plugin_lifecycle.smd_user_manager\');\r\n	register_callback(\'smd_um_inject_css\', \'admin_side\', \'head_end\');\r\n\r\n	// Log the time of this access attempt\r\n	$curr_users = unserialize(get_pref(\'smd_um_current_users\', \'\'));\r\n	$curr_users[$txp_user] = time();\r\n	set_pref(\'smd_um_current_users\', serialize($curr_users), \'smd_um\', PREF_HIDDEN, \'\', 0);\r\n\r\n	// Merge in the groups only for now\r\n	smd_um_priv_merge(1, 0);\r\n	include_once txpath.\'/lib/txplib_admin.php\';\r\n\r\n	// Permit user self-editing\r\n	$smd_um_grps = array_keys(smd_um_get_groups(0));\r\n	unset($smd_um_grps[0]); // Remove None user\r\n	$allprivs = join(\',\',$smd_um_grps);\r\n	add_privs($smd_um_event, $allprivs); // Required to display anything at all on the User Manager tab\r\n	add_privs($smd_um_event.\'.usr.edit.own\', $allprivs);\r\n\r\n	// Now the privs are established for all admin steps so we can go ahead\r\n	// and merge in the changes. One caveat: if we\'re saving the privs we\r\n	// need to delay the database merge until after the resets have been applied,\r\n	// otherwise we won\'t know what the defaults (in admin_config.php) are\r\n	$do_privs = (($step === \'smd_um_privs\') && ps(\'smd_um_priv_save\')) ? 0 : 1;\r\n	smd_um_priv_merge(0, $do_privs);\r\n}\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n\r\n// -------------------------------------------------------------\r\n// CSS definitions: hopefully kind to themers\r\n// Includes a hack for Remora (#nav li ul) to prevent menu from disappearing under the\r\n// privs content. This only occurs because the .smd_um_privgroup class uses position:relative.\r\n// Needs workaround as it affects other plugins.\r\nfunction smd_um_get_style_rules() {\r\n	$smd_um_styles = array(\r\n		\'control-panel\' => \'\r\n.smd_um_privgroup { margin:10px auto; width:50%; position:relative; }\r\n.smd_um_privgroup h3 { text-align:left; font-weight:bold; }\r\n.smd_um_privsave { position:absolute; left:-55px; top:0; }\r\n.smd_um_active_users { margin:15px auto; width:80%; text-align:center; }\r\n.smd_um_selected { background-color:#e2dfce; }\r\n.smd_um_grp_name, .smd_um_prv_name, .smd_um_reset_col { cursor:pointer; }\r\n.smd_um_checkbox, .smd_um_prv_hdr { text-align:center!important; }\r\n#nav li ul { z-index:10000; }\r\n\',\r\n	);\r\n\r\n	return $smd_um_styles;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_um_inject_css($evt, $stp) {\r\n	global $smd_um_event, $event;\r\n\r\n	if ($event == $smd_um_event || $event == \'admin\') {\r\n		$smd_um_styles = smd_um_get_style_rules();\r\n\r\n		echo \'<style type=\"text/css\">\', $smd_um_styles[\'control-panel\'], \'</style>\';\r\n	}\r\n\r\n	return;\r\n}\r\n\r\n\r\n// -------------------------------------------------------------\r\n// Destroy the existing Admin->Users tab if we come from any smd_um-initiated step\r\nfunction smd_um_silence($evt, $stp) {\r\n	global $event, $smd_um_event;\r\n\r\n	$ignore = array(\r\n		\'author_list\',\r\n		\'author_edit\',\r\n		\'author_save\',\r\n		\'author_save_new\',\r\n		\'change_pass\',\r\n	);\r\n\r\n	$no_dispatch = array(\r\n		\'change_pass\',\r\n	);\r\n\r\n	if (!in_array($stp, $ignore)) {\r\n		ob_end_clean(); // Kill any existing Admin tab panel...\r\n		ob_start(); // ... and start again\r\n		$event = $smd_um_event;\r\n\r\n		if (!in_array($stp, $no_dispatch)) {\r\n			smd_um_dispatcher($event, $stp);\r\n		}\r\n	}\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// Plugin jump off point\r\nfunction smd_um_dispatcher($evt, $stp, $msg=\'\') {\r\n	global $smd_um_event, $txp_permissions, $txp_user;\r\n\r\n	$available_steps = array(\r\n		\'smd_um\'                  => false,\r\n		\'smd_um_edit\'             => false,\r\n		\'smd_um_save\'             => true,\r\n		\'smd_um_save_new\'         => true,\r\n		\'smd_um_groups\'           => false,\r\n		\'smd_um_privs\'            => false,\r\n		\'smd_um_prefs\'            => false,\r\n		\'smd_um_multi_edit\'       => true,\r\n		\'smd_um_change_pass\'      => true,\r\n		\'smd_um_change_pass_form\' => false,\r\n		\'smd_um_change_pageby\'    => true,\r\n		\'smd_um_table_install\'    => true,\r\n		\'smd_um_table_remove\'     => true,\r\n		\'save_pane_state\'         => true,\r\n	);\r\n\r\n	if (!has_privs($smd_um_event.\'.usr.list\')) {\r\n		$stp = ($stp) ? $stp : \'smd_um_edit\';\r\n		$uid = safe_field(\'user_id\', \'txp_users\', \"name=\'\" . doSlash($txp_user) . \"\'\");\r\n		if ($uid) {\r\n			// Inject this value so the edit step picks it up and edits only the current user.\r\n			// The edit/save steps will verify if the current user is the one trying to be edited\r\n			// to prevent people adding &user_id=N to the URL\r\n			$_POST[\'user_id\'] = $uid;\r\n		}\r\n   }\r\n\r\n	if ($stp == \'save_pane_state\') {\r\n		smd_um_save_pane_state();\r\n	} else if ($stp && bouncer($stp, $available_steps)) {\r\n		if ($msg) {\r\n			$stp($msg);\r\n		} else {\r\n			$stp();\r\n		}\r\n	} else {\r\n		$smd_um_event($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Try to hide the Admin->Users tab in the secondary nav via jQuery.\r\n// May fail with inventive DOM structures / themes\r\n// (note to self: campaign for improvement in this area because jQuery smells hackish)\r\nfunction smd_um_users_tab() {\r\n	global $event;\r\n\r\n	$userStr = gTxt(\'tab_site_admin\');\r\n	echo script_js(<<<EOJS\r\njQuery(function() {\r\n	jQuery(\"a[href=\'?event=admin\']\").each(function() {\r\n		me = jQuery(this);\r\n		if (me.text() == \"{$userStr}\") {\r\n			me.parent().hide();\r\n		}\r\n	});\r\n	if (\'{$event}\' == \'admin\') {\r\n		jQuery(\"a[href=\'?event=smd_um\']\").each(function() {\r\n			me = jQuery(this);\r\n			me.parent().removeClass(\'inactive\').addClass(\'active\'); // Hive, etc\r\n			me.removeClass(\'tabdown\').addClass(\'tabup\'); // Classic, etc\r\n		});\r\n	}\r\n});\r\nEOJS\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			smd_um_table_install(0);\r\n			$msg = \'Super duper users!\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_um_table_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\n// Main user display list\r\nfunction smd_um($msg=\'\') {\r\n	global $step, $smd_um_event, $txp_user, $smd_um_list_pageby;\r\n\r\n	require_privs($smd_um_event.\'.usr.list\');\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if (!smd_um_table_exist(1)) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_usr_lbl\'), $msg);\r\n\r\n	extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\r\n	if ($sort === \'\') $sort = get_pref(\'smd_um_sort_column\', \'login\');\r\n	if ($dir === \'\') $dir = get_pref(\'smd_um_sort_dir\', \'desc\');\r\n	$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\r\n\r\n	switch ($sort) {\r\n		case \'real_name\':\r\n			$sort_sql = \'RealName \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'email\':\r\n			$sort_sql = \'email \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'privs\':\r\n			$sort_sql = \'privs \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'article_count\':\r\n			$sort_sql = \'article_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'image_count\':\r\n			$sort_sql = \'image_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'file_count\':\r\n			$sort_sql = \'file_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'link_count\':\r\n			$sort_sql = \'link_count \'.$dir.\', last_access desc\';\r\n		break;\r\n\r\n		case \'last_login\':\r\n			$sort_sql = \'last_access \'.$dir;\r\n		break;\r\n\r\n		default:\r\n			$sort = \'name\';\r\n			$sort_sql = \'name \'.$dir.\', last_access desc\';\r\n		break;\r\n	}\r\n\r\n	set_pref(\'smd_um_sort_column\', $sort, \'smd_um\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n	set_pref(\'smd_um_sort_dir\', $dir, \'smd_um\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\r\n\r\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n	$criteria = 1;\r\n\r\n	$count_columns = array(\'article_count\', \'image_count\', \'file_count\', \'link_count\');\r\n\r\n	if ($search_method and $crit != \'\') {\r\n		$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\r\n\r\n		// Permit searching by privilege name (sort of)\r\n		if ($search_method == \'privileges\' && !is_numeric($crit_escaped)) {\r\n			$levels = get_groups();\r\n			foreach ($levels as $idx => $group) {\r\n				if (strpos(strtolower($group), strtolower($crit_escaped)) !== false) {\r\n					$crit_escaped = $idx;\r\n					break;\r\n				}\r\n			}\r\n		}\r\n\r\n		// Permit <, =, and > operators in count searches.\r\n		// nullcheck is not required for privs searches because the value is a true 0 (whereas in the computed\r\n		// columns it\'s empty/null)\r\n		$operator = \'=\';\r\n		$nullcheck = \'\';\r\n		if (in_array($search_method, $count_columns) || $search_method == \'privileges\') {\r\n			preg_match(\'/([<=>]+)?([0-9]+)/\', $crit_escaped, $matches);\r\n			$operator = (isset($matches[1]) && $matches[1] != \'\') ? $matches[1] : \'=\';\r\n			$crit_escaped = (isset($matches[2]) && $matches[2] != \'\') ? $matches[2] : $crit_escaped;\r\n			$char_one = substr($operator, 0, 1);\r\n			$char_two = substr($operator, 1, 1);\r\n			$nullcheck = ($char_one == \'<\' || ($char_one == \'>\' && $char_two == \'=\' && $crit_escaped == \'0\')) ? \' OR ISNULL(\'.$search_method.\')\' : \'\';\r\n		}\r\n\r\n		$critsql = array(\r\n			\'login_name\'    => \"name like \'%$crit_escaped%\'\",\r\n			\'real_name\'     => \"RealName like \'%$crit_escaped%\'\",\r\n			\'email\'         => \"email like \'%$crit_escaped%\'\",\r\n			\'privileges\'    => \"privs $operator \'$crit_escaped\'\",\r\n			\'article_count\' => \"article_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'image_count\'   => \"image_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'file_count\'    => \"file_count $operator \'$crit_escaped\'$nullcheck\",\r\n			\'link_count\'    => \"link_count $operator \'$crit_escaped\'$nullcheck\",\r\n		);\r\n\r\n		if (array_key_exists($search_method, $critsql)) {\r\n			$criteria = $critsql[$search_method];\r\n			$limit = get_pref(\'smd_um_max_search_limit\', $smd_um_prefs[\'smd_um_max_search_limit\'][\'default\']);\r\n		} else {\r\n			$search_method = \'\';\r\n			$crit = \'\';\r\n		}\r\n	} else {\r\n		$search_method = \'\';\r\n		$crit = \'\';\r\n	}\r\n\r\n	// Since the *_count columns are computed we need to some jiggery pokery here.\r\n	// Thus, if we\'re looking for counts of \'zero\' the actual search value should be isnull()\r\n	if (in_array($search_method, $count_columns) && $operator == \'=\' && $crit_escaped == \'0\') {\r\n		$criteria = \"ISNULL($search_method)\";\r\n	}\r\n\r\n	// The fields, joins and sub-queries that make up the real and computed columns\r\n	$fields = \'txu.user_id, txu.name, txu.RealName, txu.email, txu.privs, unix_timestamp(txu.last_access) as last_login, txp.total AS article_count, txi.total AS image_count, txf.total AS file_count, txl.total AS link_count\';\r\n	$clause = \' FROM \'.PFX.\'txp_users as txu\r\n		LEFT JOIN (SELECT AuthorID, count(ID) AS total FROM \'.PFX.\'textpattern GROUP BY AuthorID) AS txp ON txp.AuthorID = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_image GROUP BY author) AS txi ON txi.author = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_file GROUP BY author) AS txf ON txf.author = txu.name\r\n		LEFT JOIN (SELECT author, count(id) AS total FROM \'.PFX.\'txp_link GROUP BY author) AS txl ON txl.author = txu.name\';\r\n\r\n	// Perform a count on the relevant search item. Doing a count(*) is awkward due to the computed columns\r\n	// so a straight query is performed with a loop to increment the total. getThing() or getRows for some reason\r\n	// failed under certain conditions\r\n	$total = 0;\r\n\r\n	// Call this so plugins that hook into the step can play\r\n	$criteria .= callback_event(\'admin_criteria\', \'author_list\', 0, $criteria);\r\n\r\n	$totrs = safe_query(\'SELECT \'.$fields.$clause.\' HAVING \'.$criteria);\r\n	while ($row = nextRow($totrs)) {\r\n		$total++;\r\n	}\r\n\r\n	$btnbar = smd_um_buttons(\'usr\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_usr\'), \'</h1>\',\r\n		\'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\',\r\n		$btnbar;\r\n\r\n	if ($total < 1) {\r\n		if ($criteria != 1) {\r\n			echo n, smd_um_search_form($crit, $search_method),\r\n				n, graf(gTxt(\'no_results_found\'), \' class=\"indicator\"\'),\r\n				n, \'</div>\';\r\n		}\r\n		return;\r\n	}\r\n\r\n	$limit = max($smd_um_list_pageby, 15);\r\n\r\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\r\n\r\n	$use_multi_edit = ( has_privs($smd_um_event.\'.usr.edit\') && (safe_count(\'txp_users\', \'1=1\') > 1) );\r\n\r\n	echo n, smd_um_search_form($crit, $search_method), \'</div>\';\r\n\r\n	// Retrieve the user info and related counts\r\n	$rs = safe_query(\'SELECT \'.$fields.$clause.\' HAVING \'.$criteria.\' ORDER BY \'.$sort_sql.\' LIMIT \'.$offset.\', \'.$limit);\r\n\r\n	if ($rs) {\r\n		echo n, \'<div class=\"txp-container\">\',\r\n			n, \'<form action=\"index.php\" id=\"smd_um_form\" method=\"post\" name=\"longform\" class=\"multi_edit_form\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n			n, \'<div class=\"txp-listtables\">\',\r\n			n, startTable(\'\', \'\', \'txp-list\'),\r\n			n, \'<thead>\',\r\n			n, tr(\r\n				n. (($use_multi_edit)\r\n					? hCell(fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'), \'\', \' title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"\')\r\n					: hCell(\'\', \'\', \' class=\"multi-edit\"\')\r\n				).\r\n				n. column_head(\'login_name\', \'name\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'name\' == $sort) ? \"$dir \" : \'\').\'name login-name\').\r\n				n. column_head(\'real_name\', \'real_name\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'real_name\' == $sort) ? \"$dir \" : \'\').\'name real-name\').\r\n				n. column_head(\'email\', \'email\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'email\' == $sort) ? \"$dir \" : \'\').\'email\').\r\n				n. column_head(\'privileges\', \'privs\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'privs\' == $sort) ? \"$dir \" : \'\').\'privs\').\r\n				n. column_head(\'last_login\', \'last_login\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'last_login\' == $sort) ? \"$dir \" : \'\').\'date last-login modified\').\r\n				n. column_head(gTxt(\'smd_um_article_count\'), \'article_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'article_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_image_count\'), \'image_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'image_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_file_count\'), \'file_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'file_count\' == $sort) ? \"$dir \" : \'\')).\r\n				n. column_head(gTxt(\'smd_um_link_count\'), \'link_count\', \'smd_um\', true, $switch_dir, $crit, $search_method, ((\'link_count\' == $sort) ? \"$dir \" : \'\'))\r\n			),\r\n			n, \'</thead>\',\r\n			n, \'<tbody>\';\r\n\r\n		$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n		while ($row = nextRow($rs)) {\r\n			extract(doSpecial($row));\r\n\r\n			$permitted = smd_um_can_edit($curr_priv, $name, $privs);\r\n\r\n			echo tr(\r\n				td(((has_privs($smd_um_event.\'.usr.edit\') && $txp_user != $row[\'name\']) ? fInput(\'checkbox\', \'selected[]\', $row[\'name\'], \'checkbox\') : \'\'), \'\', \'multi-edit\').\r\n				td( (($permitted) ? eLink($smd_um_event, \'smd_um_edit\', \'user_id\', $user_id, $name) : $name), \'\', \'name login-name actions\').\r\n				td($RealName, \'\', \'name real-name\').\r\n				td(\'<a href=\"mailto:\'.$email.\'\">\'.$email.\'</a>\', \'\', \'email\').\r\n				td(smd_um_get_priv_level($privs), \'\', \'privs\').\r\n				td(($last_login ? safe_strftime(\'%b&#160;%Y\', $last_login) : \'\'), \'\', \'date last-login modified\').\r\n				td(($article_count) ? eLink(\'list\', \'list\', \'search_method\', \'author\', $article_count, \'crit\', $name) : \'0\').\r\n				td(($image_count) ? eLink(\'image\', \'image_list\', \'search_method\', \'author\', $image_count, \'crit\', $name) : \'0\').\r\n				td(($file_count) ? eLink(\'file\', \'file_list\', \'search_method\', \'author\', $file_count, \'crit\', $name) : \'0\').\r\n				td(($link_count) ? eLink(\'link\', \'link_edit\', \'search_method\', \'author\', $link_count, \'crit\', $name) : \'0\')\r\n			);\r\n		}\r\n\r\n		echo n, \'</tbody>\',\r\n			n, endTable(),\r\n			n, \'</div>\',\r\n			n, (($use_multi_edit) ? smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) : \'\'),\r\n			n, tInput(),\r\n			n, \'</form>\',\r\n			n, \'<div id=\"users_navigation\" class=\"txp-navigation\">\',\r\n			n, nav_form(\'smd_um\', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit),\r\n			n, pageby_form(\'smd_um\', $smd_um_list_pageby),\r\n			n, \'</div>\',\r\n			n, smd_um_active_users(),\r\n			n, \'</div>\';\r\n	}\r\n\r\n	// Call the Admin side\'s author_list routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_list\');\r\n}\r\n\r\n// Can this logged-in user edit the given user account?\r\nfunction smd_um_can_edit($curr_priv, $name, $privs) {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$permitted = false; // Assume no editing rights unlesss otherwise stated\r\n\r\n	$tiered = get_pref(\'smd_um_hierarchical_groups\', $smd_um_prefs[\'smd_um_hierarchical_groups\'][\'default\']);\r\n	$protected = get_pref(\'smd_um_admin_group\', $smd_um_prefs[\'smd_um_admin_group\'][\'default\']);\r\n\r\n	// For some reason checking ($privs != \'\') doesn\'t work *shrug*\r\n	if ( ($name != \'\') && ($privs == 0 || $privs) ) {\r\n		$permitted = has_privs($smd_um_event.\'.usr.edit.own\') && ($name == $txp_user);\r\n		$can_edit = has_privs($smd_um_event.\'.usr.edit\');\r\n		if ($can_edit) {\r\n			$permitted |= $can_edit;\r\n			if ($tiered) {\r\n				$permitted &= (($privs >= $curr_priv) || ($privs == 0));\r\n			}\r\n			if ($protected) {\r\n				$permitted &= (($privs != $protected) || ($curr_priv == $protected));\r\n			}\r\n		}\r\n	}\r\n\r\n	return $permitted;\r\n}\r\n\r\n// ------------------------\r\n// Edit a single User\r\nfunction smd_um_edit($msg=\'\') {\r\n	global $step, $txp_user, $smd_um_event;\r\n\r\n	$vars = array(\'user_id\', \'name\', \'RealName\', \'email\', \'privs\');\r\n	extract(gpsa($vars));\r\n\r\n	$rs = array();\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	if ($user_id) {\r\n		$user_id = assert_int($user_id);\r\n		$rs = safe_row(\'*\', \'txp_users\', \"user_id = $user_id\");\r\n		extract($rs);\r\n	} else if (!has_privs($smd_um_event.\'.usr.create\')) {\r\n		// If the current user doesn\'t have sufficient rights to create new users\r\n		// then this Edit request without user_id is a self-edit\r\n		$rs = safe_row(\'*\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n		extract($rs);\r\n	}\r\n\r\n	// Check for edit / creation rights\r\n	$permitted = ($user_id) ? smd_um_can_edit($curr_priv, $name, $privs) : has_privs($smd_um_event.\'.usr.create\');\r\n\r\n	if (!$permitted)\r\n		exit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.\r\n			gTxt(\'restricted_area\').\'</p>\');\r\n\r\n	$caption = gTxt(($user_id) ? \'edit_author\' : \'add_new_author\');\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_usr_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'usr\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', $caption, \'</h1>\',\r\n		n, \'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, \'</div>\',\r\n		n, \'<div id=\"\', $smd_um_event.\'_container\" class=\"txp-edit\">\',\r\n		n, form(\r\n			\'<div class=\"txp-edit\">\'.n.\r\n			inputLabel(\'login_name\', ($user_id ? strong($name) : fInput(\'text\', \'name\', $name, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'login_name\')), ($user_id ? \'\' : \'login_name\'), ($user_id ? \'\' : \'add_new_author\')).n.\r\n			inputLabel(\'real_name\', fInput(\'text\', \'RealName\', $RealName, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'real_name\'), \'real_name\').n.\r\n			inputLabel(\'login_email\', fInput(\'text\', \'email\', $email, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'login_email\'), \'email\').n.\r\n			inputLabel(\'privileges\', (($txp_user != $name) ? selectInput(\'privs\', smd_um_get_groups(1), $privs) : hInput(\'privs\', $privs).strong(smd_um_get_priv_level($privs))), ($user_id ? \'\' : \'privileges\'), \'about_privileges\').n.\r\n			pluggable_ui(\'author_ui\', \'extend_detail_form\', \'\', $rs).n.\r\n			graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')).\r\n			eInput($smd_um_event).\r\n			($user_id ? hInput(\'user_id\', $user_id).sInput(\'smd_um_save\') : sInput(\'smd_um_save_new\')).\r\n			\'</div>\'\r\n		, \'\', \'\', \'post\', \'edit-form\', \'\', \'user_edit\'),\r\n		\'</div>\';\r\n\r\n	// Call the Admin side\'s author_edit routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_edit\');\r\n}\r\n\r\n// ------------------------\r\n// Virtually cloned from Admin->Users\r\nfunction smd_um_save() {\r\n	global $event, $smd_um_event, $txp_user;\r\n\r\n	// Call the Admin side\'s author_save routine so other plugins can join the party\r\n	callback_event(\'admin\', \'author_save\');\r\n\r\n	extract(doSlash(psa(array(\'privs\', \'user_id\', \'RealName\', \'email\'))));\r\n	$privs   = assert_int($privs);\r\n	$user_id = assert_int($user_id);\r\n	$name = safe_field(\'name\', \'txp_users\', \"user_id = $user_id\");\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	// Check for hacking attempts\r\n	$permitted = smd_um_can_edit($curr_priv, $name, $privs);\r\n\r\n	if (!$permitted)\r\n		exit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.\r\n			gTxt(\'restricted_area\').\'</p>\');\r\n\r\n	if (!is_valid_email($email)) {\r\n		smd_um_edit(array(gTxt(\'email_required\'), E_ERROR));\r\n		return;\r\n	}\r\n\r\n	$rs = safe_update(\'txp_users\', \"\r\n		privs    = $privs,\r\n		RealName = \'$RealName\',\r\n		email    = \'$email\'\",\r\n		\"user_id = $user_id\"\r\n	);\r\n\r\n	if ($rs) {\r\n		$msg = gTxt(\'author_updated\', array(\'{name}\' => $RealName));\r\n	} else {\r\n		$msg = \'\';\r\n	}\r\n\r\n	smd_um_dispatcher($event, \'\', $msg);\r\n}\r\n\r\n// ------------------------\r\n// Virtually cloned from Admin->Users\r\nfunction smd_um_save_new() {\r\n	global $smd_um_event;\r\n\r\n	require_privs($smd_um_event.\'.usr.create\');\r\n\r\n	// Call the Admin side\'s author_save_new routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'author_save_new\');\r\n\r\n	extract(doSlash(psa(array(\'privs\', \'name\', \'email\', \'RealName\'))));\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n	$privs  = assert_int($privs);\r\n	$length = function_exists(\'mb_strlen\') ? mb_strlen($name, \'8bit\') : strlen($name);\r\n\r\n	if ($name and $length <= 64 and is_valid_email($email)) {\r\n		$exists = safe_field(\'name\', \'txp_users\', \"name = \'\" .$name. \"\'\");\r\n\r\n		if ($exists) {\r\n			smd_um_edit(array(gTxt(\'author_already_exists\', array(\'{name}\' => $name)), E_ERROR));\r\n			return;\r\n		}\r\n\r\n		$pass_len = get_pref(\'smd_um_pass_length\', $smd_um_prefs[\'smd_um_pass_length\'][\'default\']);\r\n		$password = generate_password($pass_len);\r\n		$hash = doSlash(txp_hash_password($password));\r\n		$nonce = doSlash(md5(uniqid(mt_rand(), TRUE)));\r\n\r\n		$rs = safe_insert(\'txp_users\', \"\r\n			privs    = $privs,\r\n			name     = \'$name\',\r\n			email    = \'$email\',\r\n			RealName = \'$RealName\',\r\n			nonce    = \'$nonce\',\r\n			pass     = \'$hash\'\r\n		\");\r\n\r\n		if ($rs) {\r\n			// TODO: consider cloning send_password() because only people with admin.edit can run it (i.e. Publishers)\r\n			send_password($RealName, $name, $email, $password);\r\n			$msg = gTxt(\'password_sent_to\').sp.$email;\r\n			$smd_um_event($msg);\r\n		} else {\r\n			$msg = array(gTxt(\'error_adding_new_author\'), E_ERROR);\r\n			smd_um_edit($msg);\r\n		}\r\n	} else {\r\n		$msg = array(gTxt(\'error_adding_new_author\'), E_ERROR);\r\n		smd_um_edit($msg);\r\n	}\r\n\r\n}\r\n\r\n// ------------------------\r\n// Group management panel\r\nfunction smd_um_groups($msg=\'\') {\r\n	global $smd_um_event, $txp_user, $txp_groups, $txp_permissions;\r\n\r\n	require_privs($smd_um_event.\'.grp\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	// Handle any form actions\r\n	if (ps(\'smd_um_group_save\')) {\r\n		$excluded = smd_um_get_groups(0);\r\n		$ids = ps(\'smd_um_group_id\');\r\n		$names = ps(\'smd_um_group_name\');\r\n		$titles = ps(\'smd_um_group_title\');\r\n\r\n		foreach($ids as $idx => $id) {\r\n			$title = $titles[$idx];\r\n			$name = strtolower(sanitizeForUrl($names[$idx]));\r\n			// Can\'t create duplicate types\r\n			if (!in_array($name, $excluded)) {\r\n				safe_update(SMD_UM_GROUPS, \"name=\'\" . doSlash($name) . \"\'\", \"id=\'\" . doSlash($id) . \"\'\");\r\n			}\r\n			smd_um_upsert_lang($title, $name);\r\n		}\r\n\r\n		$msg = gTxt(\'smd_um_grp_saved\');\r\n\r\n	} else if (ps(\'smd_um_group_add\')) {\r\n		$title = ps(\'smd_um_new_grp\');\r\n		$name = ps(\'smd_um_new_grp_name\');\r\n		$name = ($name == \'\') ? strtolower(sanitizeForUrl($title)) : $name;\r\n		if ($name) {\r\n			$exists = safe_field(\'id\', SMD_UM_GROUPS, \"name=\'\".doSlash($name).\"\'\");\r\n			if ($exists) {\r\n				$msg = array(gTxt(\'smd_um_grp_exists\', array(\'{id}\' => $exists)), E_USER_WARNING);\r\n			} else {\r\n				// It\'s not atomic but it\'ll do, given that:\r\n				//  a) normally only one person administers this plugin\r\n				//  b) groups are added one at a time\r\n				$curr_max = safe_field(\"MAX(id)\", SMD_UM_GROUPS, \'1=1\');\r\n				$new_priv = ($curr_max + 1);\r\n				safe_insert(SMD_UM_GROUPS, \"id=\'\" . $new_priv . \"\', name=\'\" . doSlash($name) . \"\'\");\r\n				smd_um_upsert_lang($title, $name);\r\n\r\n				$based_on = ps(\'smd_um_new_grp_based_on\');\r\n				if ($based_on != \'\') {\r\n					assert_int($based_on);\r\n					// Can\'t rely on the privs being in the database so resort to the (merged) array\r\n					foreach ($txp_permissions as $area => $privs) {\r\n						$privs = do_list($privs);\r\n						if (in_array($based_on, $privs)) {\r\n							safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($area) . \"\', priv=\'\" . doSlash($new_priv) . \"\'\");\r\n						}\r\n					}\r\n				}\r\n\r\n				$msg = gTxt(\'smd_um_grp_created\', array(\'{name}\' => $name));\r\n			}\r\n		} else {\r\n			$msg = array(gTxt(\'smd_um_name_required\'), E_ERROR);\r\n		}\r\n	} else if (ps(\'smd_um_group_del\')) {\r\n		$id = str_replace(\'smd_um_del_\', \'\', ps(\'smd_um_grp_del\'));\r\n		assert_int($id);\r\n\r\n		$affected_users = safe_column(\'user_id\', \'txp_users\', \"privs = \'\".doSlash($id).\"\'\");\r\n		if ($affected_users) {\r\n			// Set all orphaned users to no privs -- can always assign them a new group from the main screen later\r\n			$ret = safe_update(\'txp_users\', \"privs=0\", \"user_id IN (\'\". join(\"\',\'\", doSlash($affected_users)) .\"\')\");\r\n		}\r\n\r\n		// TODO: double check this isn\'t a core group?\r\n		$red = safe_delete(SMD_UM_GROUPS, \"id=\'\".doSlash($id).\"\'\");\r\n\r\n		if ($red) {\r\n			$ret = safe_delete(SMD_UM_PRIVS, \"priv=\'\".doSlash($id).\"\'\");\r\n			$msg = gTxt(\'smd_um_grp_deleted\') . ($affected_users ? gTxt(\'smd_um_grp_affected\', array(\'{num}\' => count($affected_users))) : \'\');\r\n		}\r\n	}\r\n\r\n	// Render the page\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_grp_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'grp\');\r\n	$grouplist = smd_um_get_groups(1);\r\n	unset($grouplist[0]); // Don\'t want None privs\r\n	$grouplist = selectInput(\'smd_um_new_grp_based_on\', $grouplist, \'\', true, \'\', \'smd_um_new_grp_based_on\');\r\n\r\n	// New group\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_grp\'), \'</h1>\',\r\n		n, \'<div id=\"\'.$smd_um_event.\'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, form(\r\n			graf(\r\n				\'<label for=\"smd_um_new_grp\">\' . gTxt(\'smd_um_grp_new\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_grp\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_grp\')\r\n				.n.\'<label for=\"smd_um_new_grp_name\">\' . gTxt(\'smd_um_grp_new_name\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_grp_name\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_grp_name\')\r\n				.n.\'<label for=\"smd_um_new_grp_based_on\">\' . gTxt(\'smd_um_based_on\') . \'</label>\'\r\n				.n.$grouplist\r\n				.n.fInput(\'submit\', \'smd_um_group_add\', gTxt(\'create\'))\r\n				.n.eInput($smd_um_event)\r\n				.n.sInput(\'smd_um_groups\')\r\n				)\r\n			, \'\',\'\',\'post\',\'search-form\'\r\n			),\r\n		n, \'</div>\';\r\n\r\n	// Retrieve the group info and user counts per privilege level\r\n	$fields = \'smdg.id, smdg.name, smdg.core, txu.total AS user_count\';\r\n	$clause = \' FROM \'.PFX.\'smd_um_groups AS smdg\r\n		LEFT JOIN (SELECT privs, count(privs) AS total FROM \'.PFX.\'txp_users GROUP BY privs) AS txu ON smdg.id = txu.privs\';\r\n\r\n	$rs = getRows(\'SELECT \' . $fields.$clause . \' ORDER BY id\');\r\n\r\n	if ($rs) {\r\n		echo n, \'<div class=\"plugin-column\">\',\r\n			n, \'<form action=\"index.php\" id=\"smd_um_grp_form\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n			n.\'<div class=\"txp-listtables\">\'.\r\n			n, startTable(\'\', \'\', \'txp-list\'),\r\n			n, \'<thead>\',\r\n			n, tr(\r\n				hCell(\'ID\', \'\', \' class=\"id\"\').\r\n				hCell(\'name\', \'\', \' class=\"name\"\').\r\n				hCell(\'title\', \'\', \' class=\"name\"\').\r\n				hCell(\'\', \'\', \'\')\r\n			),\r\n			n, \'</thead>\',\r\n			n, \'<tbody>\';\r\n\r\n		foreach ($rs as $row) {\r\n			extract(doSpecial($row));\r\n			$user_count = empty($user_count) ? 0 : $user_count;\r\n			$dLink = ($core) ? \'&nbsp;\' : fInput(\'submit\', \'smd_um_group_del\', \"×\", \'\', \'\', \'smd_um_presub(this)\', \'\', \'\', \'smd_um_del_\'.$id)\r\n				.eInput($smd_um_event)\r\n				.sInput(\'smd_um_groups\')\r\n				.tInput();\r\n			echo tr(\r\n				tda(\r\n					hInput(\'smd_um_group_id[]\', $id)\r\n					.(($user_count) ? eLink($smd_um_event, \'\', \'search_method\', \'privileges\', $id, \'crit\', $id) : $id)\r\n				, \' class=\"id\"\'\r\n					.(($user_count) ? \' title=\"\' . gTxt(\'smd_um_user_count\') . $user_count . \'\"\': \'\')\r\n				)\r\n				.td(fInput(\'text\', \'smd_um_group_name[]\', $name), \'\', \'name\')\r\n				.td(fInput(\'text\', \'smd_um_group_title[]\', gTxt($name)), \'\', \'name\')\r\n				.td($dLink)\r\n			);\r\n		}\r\n\r\n		echo n, \'</tbody>\',\r\n			n, endTable(),\r\n			n, \'</div>\',\r\n			n, graf(fInput(\'submit\', \'smd_um_group_save\', gTxt(\'save\'), \'publish\')),\r\n			n, fInput(\'hidden\', \'smd_um_grp_del\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_grp_del\'),\r\n			n, eInput($smd_um_event),\r\n			n, sInput(\'smd_um_groups\'),\r\n			n, tInput(),\r\n			n, \'</form>\',\r\n			n, smd_um_active_users(),\r\n			n, \'</div>\',\r\n			script_js(<<<EOJS\r\nfunction smd_um_presub(obj) {\r\n	jQuery(\'#smd_um_grp_del\').val(jQuery(obj).attr(\'id\'));\r\n}\r\nEOJS\r\n			);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Privs management panel\r\nfunction smd_um_privs($msg=\'\') {\r\n	global $smd_um_event, $txp_user, $txp_permissions;\r\n\r\n	require_privs($smd_um_event.\'.prv\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	if (ps(\'smd_um_priv_save\')) {\r\n		$areas = ps(\'smd_um_areas\');\r\n		foreach ($areas as $area) {\r\n			$ar_fakename = str_replace(\'.\', \'---\', $area);\r\n			$privs = ps($ar_fakename);\r\n			$area = strtolower(sanitizeForPage($area));\r\n			$safe_area = doSlash($area);\r\n\r\n			$current_privs = safe_column(\'priv\', SMD_UM_PRIVS, \"area=\'{$safe_area}\'\");\r\n			$default_privs = isset($txp_permissions[$area]) ? do_list($txp_permissions[$area]) : array();\r\n			$diff_added = array_diff($privs, $default_privs);\r\n			$diff_removed = array_diff($current_privs, $privs);\r\n\r\n			// Only alter privs if they differ from what\'s already stored\r\n			if ($diff_added || $diff_removed) {\r\n				// Delete the old area privs if they exist\r\n				safe_delete(SMD_UM_PRIVS, \"area=\'{$safe_area}\'\");\r\n\r\n				if (is_array($privs)) {\r\n					foreach ($privs as $priv) {\r\n						// Reset should always be first in the list since it\'s the first checkbox col\r\n						// If reset, don\'t add the privs again (thus they will be read from admin_config.php)\r\n						if ($priv == \'smd_um_reset\') {\r\n							break;\r\n						} else {\r\n							assert_int($priv);\r\n							safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($area) . \"\', priv=\'\" . doSlash($priv) . \"\'\");\r\n						}\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		// Merge the changes into the priv table\r\n		smd_um_priv_merge(0,1);\r\n		$msg = gTxt(\'smd_um_prv_saved\');\r\n	} else if (ps(\'smd_um_priv_add\')) {\r\n		$name = ps(\'smd_um_new_prv\');\r\n		$name = strtolower(sanitizeForPage($name));\r\n\r\n		if ($name) {\r\n			if (strpos($name, \'smd_um\') === 0) {\r\n				// Can\'t create privs for this plugin\r\n				$msg = array(gTxt(\'smd_um_prv_smd_um\'), E_USER_WARNING);\r\n			} else {\r\n				$exists = array_key_exists($name, $txp_permissions);\r\n				if ($exists) {\r\n					$msg = array(gTxt(\'smd_um_prv_exists\'), E_USER_WARNING);\r\n				} else {\r\n					safe_insert(SMD_UM_PRIVS, \"area=\'\" . doSlash($name) . \"\'\");\r\n\r\n					smd_um_priv_merge(0,1);\r\n					$msg = gTxt(\'smd_um_prv_created\', array(\'{area}\' => $name));\r\n				}\r\n			}\r\n		} else {\r\n			$msg = array(gTxt(\'smd_um_name_required\'), E_ERROR);\r\n		}\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_prv_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'prv\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_prv\'), \'</h1>\',\r\n		n, \'<div id=\"\'.$smd_um_event.\'_control\" class=\"txp-control-panel\">\',\r\n		n, $btnbar,\r\n		n, form(\r\n			graf(\r\n				\'<label for=\"smd_um_new_prv\">\' . gTxt(\'smd_um_prv_new\') . \'</label>\'\r\n				.n.fInput(\'text\', \'smd_um_new_prv\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_new_prv\')\r\n				.n.fInput(\'submit\', \'smd_um_priv_add\', gTxt(\'create\'))\r\n				.n.eInput($smd_um_event)\r\n				.n.sInput(\'smd_um_privs\')\r\n				)\r\n			, \'\',\'\',\'post\',\'search-form\'\r\n			),\r\n		n, \'</div>\';\r\n\r\n	$grouplist_name = smd_um_get_groups(0);\r\n	$grouplist_title = smd_um_get_groups(1);\r\n\r\n	unset($grouplist_name[0]); // Don\'t want None privs\r\n	unset($grouplist_title[0]); // Ditto\r\n\r\n	$curr_area = \'\';\r\n	$area_count = 0;\r\n	$thatts = \' class=\"smd_um_grp_name\" title=\"\' . gTxt(\'smd_um_sel_grp\') . \'\"\';\r\n	$headers = \'<thead>\'.tr(\r\n		hCell(\'\', \'\', \' class=\"smd_um_sel_area\" title=\"\' . gTxt(\'smd_um_sel_all\') . \'\"\')\r\n		.hCell(gTxt(\'smd_um_reset\'), \'\', \' class=\"smd_um_reset_col\" title=\"\' . gTxt(\'smd_um_sel_reset\') . \'\"\')\r\n		.hCell(join(\'</th><th\'.$thatts.\'>\', $grouplist_title), \'\', $thatts)\r\n	, \' class=\"smd_um_prv_hdr\"\'). \'</thead>\';\r\n\r\n	$viz = do_list(get_pref(\'pane_smd_um_priv_visible\'));\r\n\r\n	echo script_js(<<<EOJS\r\nfunction smd_um_presub(obj) {\r\n	jQuery(\'#smd_um_prv_del\').val(jQuery(obj).attr(\'id\'));\r\n}\r\n\r\njQuery.fn.smd_um_rowsel = function(idx) {\r\n	return jQuery(\'tr:nth-child(\'+(idx+1)+\') td.smd_um_checkbox\', this);\r\n}\r\njQuery.fn.smd_um_colsel = function(idx) {\r\n	return jQuery(\'tr td:nth-child(\'+(idx+1)+\')\', this);\r\n}\r\n\r\n// Affect all highlighted checkboxes on keypress\r\nfunction smd_um_toggleCheckbox(ev) {\r\n	key = ev.keyCode;\r\n	obj = jQuery(\'.smd_um_selected :checkbox\');\r\n	switch(key) {\r\n		case 67:\r\n			// (c)heck selected boxes\r\n			obj.prop(\'checked\', true);\r\n		break;\r\n		case 68:\r\n			// (d)eselect all selected rows/cols\r\n			jQuery(\'.smd_um_selected, .smd_um_rsel, .smd_um_csel\').removeClass(\'smd_um_selected smd_um_rsel smd_um_csel\');\r\n		break;\r\n\r\n		case 84:\r\n			// (t)oggle selected boxes\r\n			obj.each(function() {\r\n				cb = jQuery(this);\r\n				if (cb.prop(\'checked\') == true) {\r\n					cb.prop(\'checked\', false);\r\n				} else {\r\n					cb.prop(\'checked\', true);\r\n				}\r\n			});\r\n		break;\r\n		case 85:\r\n			// (u)ncheck selected boxes\r\n			obj.prop(\'checked\', false);\r\n		break;\r\n	}\r\n}\r\n\r\njQuery(function() {\r\n	jQuery(document).bind(\'keyup\', smd_um_toggleCheckbox);\r\n\r\n	// Row selector\r\n	jQuery(\'.smd_um_prv_name\').click(function() {\r\n		tr = jQuery(this).closest(\'tr\');\r\n		rownum = tr.index();\r\n		obj = jQuery(tr).parent().smd_um_rowsel(rownum);\r\n\r\n		// Can\'t use toggleClass because it would untoggle any cols that were already selected\r\n		if (jQuery(this).hasClass(\'smd_um_rsel\')) {\r\n			obj.removeClass(\'smd_um_selected\');\r\n		} else {\r\n			obj.addClass(\'smd_um_selected\');\r\n		}\r\n		jQuery(this).toggleClass(\'smd_um_rsel\');\r\n	});\r\n\r\n	// Column selector\r\n	jQuery(\'.smd_um_grp_name, .smd_um_reset_col\').click(function() {\r\n		colnum = jQuery(this).index();\r\n		tbody = jQuery(this).parent().parent().next(\'tbody\');\r\n		obj = jQuery(tbody).smd_um_colsel(colnum);\r\n\r\n		// Can\'t use toggleClass because it would untoggle any rows that were already selected\r\n		if (colnum > 0) {\r\n			if (jQuery(this).hasClass(\'smd_um_csel\')) {\r\n				obj.removeClass(\'smd_um_selected\');\r\n			} else {\r\n				obj.addClass(\'smd_um_selected\');\r\n			}\r\n		}\r\n		jQuery(this).toggleClass(\'smd_um_csel\');\r\n	});\r\n\r\n	// Whole table selector\r\n	jQuery(\'.smd_um_sel_area\').click(function() {\r\n		me = jQuery(this);\r\n		thead = me.parent().parent();\r\n		tbody = thead.next(\'tbody\');\r\n		tbody.toggleClass(\'smd_um_allsel\');\r\n		if (tbody.hasClass(\'smd_um_allsel\')) {\r\n			tbody.find(\'.smd_um_prv_name\').removeClass(\'smd_um_rsel\').click();\r\n			me.nextAll(\'.smd_um_grp_name\').addClass(\'smd_um_csel\');\r\n		} else {\r\n			tbody.find(\'.smd_um_prv_name\').addClass(\'smd_um_rsel\').click();\r\n			me.nextAll(\'.smd_um_grp_name\').removeClass(\'smd_um_csel\');\r\n		}\r\n	});\r\n\r\n});\r\nEOJS\r\n	);\r\n\r\n	echo n, \'<div class=\"txp-list\">\',\r\n		n, \'<form action=\"index.php\" id=\"smd_um_privilege_form\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\',\r\n		n, eInput($smd_um_event).sInput(\'smd_um_privs\').tInput();\r\n\r\n	foreach ($txp_permissions as $area => $privs) {\r\n		$priv_list = do_list($privs);\r\n		$area_parts = do_list($area, \'.\');\r\n		if (preg_match(\'/^([A-Za-z0-9]{3,3})\\_/\', $area_parts[0], $matches)) {\r\n			// Plugin\r\n			$area_parts[0] = $matches[1];\r\n		}\r\n		// Start of a new area so close the previous one and start a new block\r\n		if ($curr_area != $area_parts[0]) {\r\n			if ($area_count > 0) {\r\n				echo \'</tbody>\' . endTable() . \'</div></div>\';\r\n			}\r\n			$area_head = gTxt($area_parts[0]);\r\n			$is_viz = in_array($area_parts[0], $viz);\r\n			$ref = \'smd_um_priv_\'.$area_parts[0];\r\n			echo n, \'<div class=\"smd_um_privgroup\"><h3 class=\"txp-summary lever\', ($is_viz ? \' expanded\' : \'\'), \'\"><a href=\"#\', $ref, \'\">\', $area_parts[0], (($area_parts[0] != $area_head) ? \' (\'. gTxt($area_parts[0]). \')\' : \'\'), \'</a></h3>\',\r\n				n, \'<div id=\"\', $ref, \'\" class=\"toggle\" style=\"display:\', ($is_viz ? \'block\' : \'none\'), \'\">\',\r\n				n, fInput(\'submit\', \'smd_um_priv_save\', gTxt(\'save\'), \'smd_um_privsave\'),\r\n				n, startTable(\'\', \'\', \'txp-list\'),\r\n				n, $headers,\r\n				n, \'<tbody>\';\r\n		}\r\n\r\n		$privboxes = array();\r\n		$safe_area = str_replace(\'.\', \'---\', $area).\'[]\';\r\n		foreach ($grouplist_name as $id => $priv) {\r\n			/// Dots aren\'t valid characters for a name so replace them now and swap them back upon Save\r\n			$privboxes[] = td(checkbox($safe_area, $id, (in_array($id, $priv_list))), \'\', \'smd_um_checkbox\');\r\n		}\r\n\r\n		echo tr(\r\n			tda($area.hInput(\'smd_um_areas[]\', $area), \' class=\"smd_um_prv_name\" title=\"\' . gTxt(\'smd_um_sel_prv\') . \'\"\')\r\n			.td(checkbox($safe_area, \'smd_um_reset\', 0), \'\', \'smd_um_resetbox\')\r\n			.join(n, $privboxes)\r\n		);\r\n		$curr_area = $area_parts[0];\r\n		$area_count++;\r\n	}\r\n\r\n	echo n, endTable(),\r\n		n, \'</div></div>\',\r\n		n, fInput(\'hidden\', \'smd_um_prv_del\', \'\', \'\', \'\', \'\', \'\', \'\', \'smd_um_prv_del\'),\r\n		n, \'</form>\',\r\n		n, smd_um_active_users(),\r\n		n, \'</div>\';\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_um_wrap_widget($widget) {\r\n	return \'<span class=\"edit-value\">\'.$widget.\'</span>\';\r\n}\r\n\r\n// ------------------------\r\n// Prefs management panel\r\nfunction smd_um_prefs($msg=\'\') {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	require_privs($smd_um_event.\'.prf\');\r\n\r\n	if (!smd_um_table_exist()) {\r\n		smd_um_table_install(0);\r\n	}\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if (ps(\'smd_um_pref_save\')) {\r\n		foreach ($smd_um_prefs as $idx => $prefobj) {\r\n			$val = ps($idx);\r\n			$val = (is_array($val)) ? join(\', \', $val) : $val;\r\n			set_pref($idx, doSlash($val), $smd_um_event, $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n		}\r\n\r\n		$msg = gTxt(\'preferences_saved\');\r\n	}\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_prf_lbl\'), $msg);\r\n	$btnbar = smd_um_buttons(\'prf\');\r\n\r\n	echo \'<h1 class=\"txp-heading\">\', gTxt(\'smd_um_heading_prf\'), \'</h1>\',\r\n		n, \'<div id=\"\', $smd_um_event, \'_control\" class=\"txp-control-panel\">\', $btnbar, \'</div>\';\r\n\r\n	$out = array();\r\n	$out[] = n.\'<div class=\"plugin-column\">\';\r\n	$out[] = \'<form name=\"smd_um_prefs\" id=\"smd_um_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_um_event).sInput(\'smd_um_prefs\');\r\n\r\n	$last_grp = \'\';\r\n	foreach ($smd_um_prefs as $idx => $prefobj) {\r\n		if ($last_grp != $prefobj[\'group\']) {\r\n			$out[] = hed(gTxt($prefobj[\'group\']), 2);\r\n		}\r\n		$last_grp = $prefobj[\'group\'];\r\n		$subout = array();\r\n		$label = \'<span class=\"edit-label\">\'\r\n				.\'<label>\'.gTxt($idx).\'</label>\'\r\n				.\'</span>\';\r\n		$val = get_pref($idx, $prefobj[\'default\'], 1);\r\n		$vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'empty\' : \'\';\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = smd_um_wrap_widget(fInput(\'text\', $idx, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $idx));\r\n			break;\r\n			case \'textarea\':\r\n				$subout[] = text_area($idx, \'\', \'\', $val, $idx);\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = smd_um_wrap_widget(yesnoRadio($idx, $val));\r\n			break;\r\n			case \'radioset\':\r\n				$subout[] = smd_um_wrap_widget(radioSet($prefobj[\'content\'], $idx, $val));\r\n			break;\r\n			case \'checkboxset\':\r\n				$vals = do_list($val);\r\n				$lclout = array();\r\n				foreach ($prefobj[\'content\'] as $cb => $val) {\r\n					$checked = in_array($cb, $vals);\r\n					$lclout[] = checkbox($idx.\'[]\', $cb, $checked). \'<label>\' . gTxt($val) . \'</label>\';\r\n				}\r\n				$subout[] = smd_um_wrap_widget(join(n, $lclout));\r\n			break;\r\n			case \'selectlist\':\r\n				$subout[] = smd_um_wrap_widget(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]));\r\n			break;\r\n			default:\r\n				if ( strpos($prefobj[\'html\'], \'smd_um_\') !== false && is_callable($prefobj[\'html\']) ) {\r\n					$subout[] = smd_um_wrap_widget($prefobj[\'html\']($idx, $val));\r\n				}\r\n			break;\r\n		}\r\n		$out[] = graf($label . n.join(n ,$subout), ($vis ? \' class=\"\'.$vis.\'\"\' : \'\'));\r\n	}\r\n	$out[] = graf(fInput(\'submit\', \'smd_um_pref_save\', gTxt(\'save\'), \'publish\'));\r\n	$out[] = tInput();\r\n	$out[] = \'</form>\'.smd_um_active_users().\'</div>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// ------------------------\r\n// Common buttons for the interface\r\nfunction smd_um_buttons($curr=\'usr\') {\r\n	global $step, $smd_um_event;\r\n\r\n	$grp = has_privs($smd_um_event.\'.grp\');\r\n	$prf = has_privs($smd_um_event.\'.prf\');\r\n	$prv = has_privs($smd_um_event.\'.prv\');\r\n	$new = has_privs($smd_um_event.\'.usr.create\');\r\n	$usr = ($grp || $prf || $prv); // Don\'t show usr button if it\'s the only tab available\r\n\r\n	$btns = array (\r\n		\'new\' => ( ($new) ? sLink($smd_um_event, \'smd_um_edit\', gTxt(\'smd_um_new_user\'), \'navlink\') : \'\'),\r\n		\'grp\' => ( ($grp) ? sLink($smd_um_event, \'smd_um_groups\', gTxt(\'smd_um_grp_lbl\'), \'navlink\') : \'\'),\r\n		\'prf\' => ( ($prf) ? sLink($smd_um_event, \'smd_um_prefs\', gTxt(\'smd_um_prf_lbl\'), \'navlink\') : \'\'),\r\n		\'prv\' => ( ($prv) ? sLink($smd_um_event, \'smd_um_privs\', gTxt(\'smd_um_prv_lbl\'), \'navlink\') : \'\'),\r\n		\'usr\' => ( ($usr) ? sLink($smd_um_event, \'smd_um_list\', gTxt(\'smd_um_usr_lbl\'), \'navlink\') : \'\'),\r\n		\'chp\' => ( sLink($smd_um_event, \'smd_um_change_pass_form\', gTxt(\'change_password\'), \'navlink\')),\r\n	);\r\n\r\n	return graf(\r\n			(($curr == \'usr\') ? n. ($step === \'smd_um_edit\' ? \'\' : $btns[\'chp\']) .n. $btns[\'new\'] .n. strong($btns[\'usr\']) : n.$btns[\'usr\'])\r\n			.n.(($curr == \'grp\') ? strong($btns[\'grp\']) : $btns[\'grp\'])\r\n			.n.(($curr == \'prv\') ? strong($btns[\'prv\']) : $btns[\'prv\'])\r\n			.n.(($curr == \'prf\') ? strong($btns[\'prf\']) : $btns[\'prf\'])\r\n		, \' class=\"txp-buttons\"\');\r\n}\r\n\r\n// ------------------------\r\n// Alter the pageby quantity\r\nfunction smd_um_change_pageby() {\r\n	global $smd_um_event;\r\n	event_change_pageby(\'smd_um\');\r\n	$smd_um_event();\r\n}\r\n\r\n// ------------------------\r\n// The user panel search dropdown list\r\nfunction smd_um_search_form($crit, $method) {\r\n	global $smd_um_event;\r\n\r\n	$methods =	array(\r\n		\'login_name\'    => gTxt(\'login_name\'),\r\n		\'real_name\'     => gTxt(\'real_name\'),\r\n		\'email\'         => gTxt(\'email\'),\r\n		\'privileges\'    => gTxt(\'privileges\'),\r\n		\'article_count\' => gTxt(\'smd_um_article_count\'),\r\n		\'image_count\'   => gTxt(\'smd_um_image_count\'),\r\n		\'file_count\'    => gTxt(\'smd_um_file_count\'),\r\n		\'link_count\'    => gTxt(\'smd_um_link_count\'),\r\n	);\r\n\r\n	return search_form($smd_um_event, \'\', $crit, $methods, $method, \'login\');\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_get_priv_level($priv) {\r\n	$levels = get_groups();\r\n	return $levels[$priv];\r\n}\r\n\r\n// ------------------------\r\n// Merge/edit the groups & privs into the existing global arrays\r\nfunction smd_um_priv_merge($do_grp=1, $do_priv=1) {\r\n	global $txp_groups, $txp_permissions;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	if ($do_grp && smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		$new_groups = safe_rows(\'id, name\', SMD_UM_GROUPS, \'1=1\');\r\n		foreach ($new_groups as $row) {\r\n			$txp_groups[$row[\'id\']] = $row[\'name\'];\r\n		}\r\n		ksort($txp_groups);\r\n	}\r\n	if ($do_priv && smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		$new_privs = safe_rows(\'area, GROUP_CONCAT(priv) AS privs\', SMD_UM_PRIVS, \'1=1 GROUP BY area ORDER BY area\');\r\n\r\n		// Allow this plugin\'s strings to be skipped if we don\'t want people upsetting the plugin\'s behaviour\r\n		$self_edit = get_pref(\'smd_um_self_alter\', $smd_um_prefs[\'smd_um_self_alter\'][\'default\']);\r\n		foreach ($new_privs as $row) {\r\n			if (strpos($row[\'area\'], \'smd_um\') === false || $self_edit) {\r\n				$txp_permissions[$row[\'area\']] = $row[\'privs\'];\r\n			}\r\n		}\r\n		ksort($txp_permissions);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Show who\'s currently online\r\nfunction smd_um_active_users() {\r\n	global $smd_um_event;\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$curr_users = unserialize(get_pref(\'smd_um_current_users\', \'\', 1));\r\n	$timeout = get_pref(\'smd_um_active_timeout\', $smd_um_prefs[\'smd_um_active_timeout\'][\'default\']);\r\n	$online = array();\r\n	if (is_array($curr_users)) {\r\n		foreach ($curr_users as $user => $last_access) {\r\n			$still_active = strtotime(\"+$timeout seconds\", $last_access);\r\n			if ( ($still_active !== false) && ($still_active > time()) ) {\r\n				$online[] = eLink($smd_um_event, \'\', \'search_method\', \'login_name\', $user, \'crit\', $user);\r\n			}\r\n		}\r\n	}\r\n\r\n	return ($online) ? \'<div class=\"smd_um_active_users\">\' . gTxt(\'smd_um_active\') . join(\', \', $online) . \'</div>\' : \'\';\r\n}\r\n\r\n// ------------------------\r\n// Update any language string. Note this may leave orphan strings if the name is changed\r\nfunction smd_um_upsert_lang($title, $name=\'\') {\r\n	global $textarray;\r\n\r\n	$lang = get_pref(\'language\', \'en-gb\');\r\n	$name = (isset($name) && $name != \'\') ? $name : strtolower(sanitizeForUrl($title));\r\n	$table = \'txp_lang\';\r\n	$where = \"name=\'\" . doSlash($name) . \"\' AND lang=\'\" . doSlash($lang) . \"\'\";\r\n\r\n	// Try to update first\r\n	$ret = safe_update($table, \"data=\'\" . doSlash($title) . \"\'\", $where);\r\n	if ($ret && (mysql_affected_rows() || safe_count($table, $where))) {\r\n		// Do nothing -- record has been updated\r\n	} else {\r\n		safe_insert($table, \"event=\'admin\', name=\'\" . doSlash($name) . \"\', lang=\'\" . doSlash($lang) . \"\', data=\'\" . doSlash($title) . \"\'\");\r\n	}\r\n	// Update the global array on the page so gtxt() can get it immediately\r\n	$textarray[$name] = $title;\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_get_groups($type=0) {\r\n	global $txp_groups, $txp_user;\r\n	static $permitted_users = array();\r\n\r\n	if (isset($permitted_users[$type])) {\r\n		return $permitted_users[$type];\r\n	}\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$levels = ($type) ? get_groups() : $txp_groups;\r\n	$tiered = get_pref(\'smd_um_hierarchical_groups\', $smd_um_prefs[\'smd_um_hierarchical_groups\'][\'default\']);\r\n	$curr_priv = safe_field(\'privs\', \'txp_users\', \"name = \'\" .doSlash($txp_user). \"\'\");\r\n\r\n	if (smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		$protected = get_pref(\'smd_um_admin_group\', $smd_um_prefs[\'smd_um_admin_group\'][\'default\']);\r\n		$grp = safe_rows(\'id, name\', SMD_UM_GROUPS, \'1=1\');\r\n		foreach ($grp as $row) {\r\n			if ($protected && ($row[\'id\'] == $protected) && ($curr_priv != $protected) ) {\r\n				unset($levels[$row[\'id\']]);\r\n			} else {\r\n				$levels[$row[\'id\']] = (($type) ? gTxt($row[\'name\']) : $row[\'name\']);\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($tiered) {\r\n		// Remove any privs higher than the current logged in user\r\n		foreach ($levels as $priv => $name) {\r\n			if ( ($priv == 0) || ($priv >= $curr_priv) ) {\r\n				$permitted_users[$type][$priv] = $name;\r\n			}\r\n		}\r\n	} else {\r\n		$permitted_users[$type] = $levels;\r\n	}\r\n\r\n	ksort($permitted_users[$type]);\r\n\r\n	return $permitted_users[$type];\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) {\r\n\r\n	$levels = smd_um_get_groups(1);\r\n	$privileges = selectInput(\'privs\', $levels, \'\', \'\', \'\', \'privileges\');\r\n\r\n	$rs = safe_column(\'name\', \'txp_users\', \'1=1\');\r\n	$assign_assets = $rs ? \'<label for=\"assign_assets\">\'.gTxt(\'assign_assets_to\').\'</label>\'.n.selectInput(\'assign_assets\', $rs, \'\', true, \'\', \'assign_assets\') : \'\';\r\n\r\n	$methods = array(\r\n		\'changeprivilege\' => array(\'label\' => gTxt(\'changeprivilege\'), \'html\' => $privileges),\r\n		\'resetpassword\'   => gTxt(\'resetpassword\'),\r\n		\'delete\'          => array(\'label\' => gTxt(\'delete\'), \'html\' => $assign_assets),\r\n	);\r\n\r\n	if (safe_count(\'txp_users\', \'1=1\') <= 1) unset($methods[\'delete\']);\r\n\r\n	return multi_edit($methods, \'smd_um\', \'smd_um_multi_edit\', $page, $sort, $dir, $crit, $search_method);\r\n}\r\n\r\n// ------------------------\r\n// Cloned and tweaked from Admin tab :-(\r\nfunction smd_um_multi_edit() {\r\n	global $smd_um_event, $txp_user;\r\n\r\n	require_privs($smd_um_event.\'.usr.edit\');\r\n\r\n	$smd_um_prefs = smd_um_get_prefs();\r\n\r\n	$selected = ps(\'selected\');\r\n	$method   = ps(\'edit_method\');\r\n	$changed  = array();\r\n\r\n	if (!$selected or !is_array($selected))\r\n	{\r\n		return $smd_um_event();\r\n	}\r\n\r\n	$names = safe_column(\'name\', \'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($selected)).\"\') AND name != \'\".doSlash($txp_user).\"\'\");\r\n\r\n	if (!$names) return $smd_um_event();\r\n\r\n	switch ($method) {\r\n		case \'delete\':\r\n			$assign_assets = ps(\'assign_assets\');\r\n			if ($assign_assets === \'\') {\r\n				$msg = array(\'must_reassign_assets\', E_ERROR);\r\n			} elseif (in_array($assign_assets, $names)) {\r\n				$msg = array(\'cannot_assign_assets_to_deletee\', E_ERROR);\r\n			} elseif (safe_delete(\'txp_users\', \"name IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\")) {\r\n				$changed = $names;\r\n				$assign_assets = doSlash($assign_assets);\r\n				$names = join(\"\',\'\", doSlash($names));\r\n\r\n				// Delete private prefs\r\n				safe_delete(\'txp_prefs\', \"user_name IN (\'$names\')\");\r\n\r\n				// Assign dangling assets to their new owner\r\n				$reassign = array(\r\n					\'textpattern\' => \'AuthorID\',\r\n					\'txp_file\' 	=> \'author\',\r\n					\'txp_image\' => \'author\',\r\n					\'txp_link\' 	=> \'author\',\r\n				);\r\n				foreach ($reassign as $table => $col) {\r\n					safe_update($table, \"$col=\'$assign_assets\'\", \"$col IN (\'$names\')\");\r\n				}\r\n				callback_event(\'authors_deleted\', \'\', 0, $changed);\r\n				$msg = \'author_deleted\';\r\n			}\r\n			break;\r\n		case \'changeprivilege\':\r\n			$levels = smd_um_get_groups(1);\r\n			$privilege = ps(\'privs\');\r\n\r\n			if (!isset($levels[$privilege])) return $smd_um_event();\r\n\r\n			if (safe_update(\'txp_users\', \'privs = \'.intval($privilege), \"name IN (\'\".join(\"\',\'\", doSlash($names)).\"\')\")) {\r\n				$changed = $names;\r\n				$msg = \'author_updated\';\r\n			}\r\n			break;\r\n\r\n		case \'resetpassword\':\r\n			$failed  = array();\r\n			$pass_len = get_pref(\'smd_um_pass_length\', $smd_um_prefs[\'smd_um_pass_length\'][\'default\']);\r\n\r\n			foreach ($names as $name) {\r\n				$passwd = generate_password($pass_len);\r\n				$hash = doSlash(txp_hash_password($passwd));\r\n\r\n				if (safe_update(\'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($name).\"\'\")) {\r\n					$email = safe_field(\'email\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n					$cb_params = array(\r\n						\'name\'  => $name,\r\n						\'email\' => $email,\r\n						\'pass\'  => $passwd,\r\n					);\r\n\r\n					$msg = callback_event(\'smd_user_manager\', \'password.reset\', 0, $cb_params);\r\n					if (strlen($msg) === 0) {\r\n						if (send_new_password($passwd, $email, $name)) {\r\n							$changed[] = $name;\r\n							$msg = \'author_updated\';\r\n						} else {\r\n							$msg = (array(gTxt(\'could_not_mail\').\' \'.txpspecialchars($name), E_ERROR));\r\n						}\r\n					}\r\n				}\r\n			}\r\n			break;\r\n	}\r\n\r\n	if ($changed) {\r\n		return $smd_um_event(gTxt($msg, array(\'{name}\' => txpspecialchars(join(\', \', $changed)))));\r\n	}\r\n\r\n	$smd_um_event($msg);\r\n}\r\n\r\n// ------------------------\r\n// Mostly cloned from Admin->Users tab\r\nfunction smd_um_change_pass_form() {\r\n	global $smd_um_event;\r\n\r\n	pagetop(gTxt(\'smd_um_tab_name\').\' &raquo; \'.gTxt(\'smd_um_chp_lbl\'), \'\');\r\n\r\n	echo form(\r\n		\'<div class=\"txp-edit\">\'.\r\n		hed(gTxt(\'change_password\'), 2).n.\r\n		inputLabel(\'new_pass\', fInput(\'password\', \'new_pass\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', \'new_pass\'), \'new_password\').n.\r\n		graf(checkbox(\'mail_password\', \'1\', true, \'\', \'mail_password\') .n. \'<label for=\"mail_password\">\'.gTxt(\'mail_it\').\'</label>\', \' class=\"edit-mail-password\"\').n.\r\n		graf(fInput(\'submit\', \'change_pass\', gTxt(\'submit\'), \'publish\')).\r\n		eInput($smd_um_event).\r\n		sInput(\'smd_um_change_pass\').\r\n		\'</div>\'\r\n	, \'\', \'\', \'post\', \'\', \'\', \'change_password\');\r\n}\r\n\r\n// ------------------------\r\n// Mostly cloned from Admin->Users tab\r\nfunction smd_um_change_pass() {\r\n	global $event, $step, $txp_user;\r\n\r\n	// Call the Admin side\'s change_pass routine so other plugins with a vested interest can join the party\r\n	callback_event(\'admin\', \'change_pass\');\r\n\r\n	extract(psa(array(\'new_pass\', \'mail_password\')));\r\n\r\n	if (empty($new_pass)) {\r\n		smd_um_dispatcher($event, \'\', array(gTxt(\'password_required\'), E_ERROR));\r\n		return;\r\n	}\r\n\r\n	$hash = doSlash(txp_hash_password($new_pass));\r\n	$rs = safe_update(\'txp_users\', \"pass = \'$hash\'\", \"name = \'\".doSlash($txp_user).\"\'\");\r\n\r\n	if ($rs) {\r\n		$msg = gTxt(\'password_changed\');\r\n\r\n		if ($mail_password) {\r\n			$email = fetch(\'email\', \'txp_users\', \'name\', $txp_user);\r\n			send_new_password($new_pass, $email, $txp_user);\r\n			$msg .= sp.gTxt(\'and_mailed_to\').sp.$email;\r\n		} else {\r\n			echo comment(mysql_error());\r\n		}\r\n\r\n		$msg .= \'.\';\r\n	} else {\r\n		$msg = array(gTxt(\'smd_um_pass_change_error\'), E_ERROR);\r\n	}\r\n	smd_um_dispatcher($event, \'\', $msg);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_save_pane_state() {\r\n	global $smd_um_event;\r\n\r\n	$pane = str_replace(\'smd_um_priv_\', \'\', gps(\'pane\'));\r\n	$curr = do_list(get_pref(\'pane_smd_um_priv_visible\'));\r\n	$visible = gps(\'visible\');\r\n	if ($visible == \'true\') {\r\n		$curr[] = $pane;\r\n	} else {\r\n		$pos = array_search($pane, $curr);\r\n		if ($pos !== false) {\r\n			unset($curr[$pos]);\r\n		}\r\n	}\r\n\r\n	$curr = array_unique($curr);\r\n\r\n	set_pref(\"pane_smd_um_priv_visible\", (join(\',\', $curr)), $smd_um_event, PREF_HIDDEN, \'text_input\', 0, PREF_PRIVATE);\r\n	send_xml_response();\r\n}\r\n\r\n// ****************\r\n// TABLE MANAGEMENT\r\n// ****************\r\n// Add group/priv tables if not already installed\r\nfunction smd_um_table_install($showpane=\'1\') {\r\n	global $smd_um_event, $txp_groups, $txp_permissions;\r\n\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	$ret = \'\';\r\n	$sql = array();\r\n\r\n	// In truth, this table should be normalised further, but for the sake\r\n	// of one row per priv level per area, it\'s quicker than a join, and\r\n	// using GROUP_CONCAT() gets the priv table as in admin.config.php\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_UM_PRIVS.\"` (\r\n		`area` varchar(127) NOT NULL default \'\',\r\n		`priv` smallint NOT NULL default 0,\r\n		PRIMARY KEY (`area`,`priv`)\r\n	) ENGINE=MyISAM\";\r\n\r\n	// id is NOT an auto_increment colummn because autoinc doesn\'t allow a 0 entry, which we need for \'None\'\r\n	$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_UM_GROUPS.\"` (\r\n		`id` smallint(4) NOT NULL default 0,\r\n		`name` varchar(64) NOT NULL default \'\',\r\n		`core` bool NOT NULL default 0,\r\n		PRIMARY KEY (`id`)\r\n	) ENGINE=MyISAM PACK_KEYS=1\";\r\n\r\n	// Handle upgrades: be kind to beta testers\r\n	if (smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		$flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_UM_PRIVS.\'`\');\r\n		if (in_array(\'core\',$flds)) {\r\n			$sql[] = \"ALTER TABLE `\".PFX.SMD_UM_PRIVS.\"` DROP `core`\";\r\n		}\r\n	}\r\n\r\n	// Append initial value population to query stack if this is a new install\r\n	if (!smd_um_table_exist(SMD_UM_GROUPS)) {\r\n		foreach ($txp_groups as $id => $name) {\r\n			$sql[] = \"INSERT INTO `\".PFX.SMD_UM_GROUPS.\"` VALUES (\'$id\', \'$name\', 1)\";\r\n		}\r\n	}\r\n	if (!smd_um_table_exist(SMD_UM_PRIVS)) {\r\n		foreach ($txp_permissions as $area => $privs) {\r\n			$priv_list = do_list($privs);\r\n			foreach ($priv_list as $priv) {\r\n				$sql[] = \"INSERT INTO `\".PFX.SMD_UM_PRIVS.\"` VALUES (\'$area\', \'$priv\')\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if(gps(\'debug\')) {\r\n		dmp($sql);\r\n	}\r\n	foreach ($sql as $qry) {\r\n		$ret = safe_query($qry);\r\n		if ($ret===false) {\r\n			$GLOBALS[\'txp_err_count\']++;\r\n			echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n			echo \"<!--\\n $qry \\n-->\\n\";\r\n		}\r\n	}\r\n\r\n	// Spit out results\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		if ($showpane) {\r\n			$msg = gTxt(\'smd_um_tbl_installed\');\r\n			$smd_um_event($msg);\r\n		}\r\n	} else {\r\n		if ($showpane) {\r\n			$msg = gTxt(\'smd_um_tbl_not_installed\');\r\n			$smd_um_event($msg);\r\n		}\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Drop table if in database\r\nfunction smd_um_table_remove() {\r\n	global $smd_um_event;\r\n\r\n	$ret = \'\';\r\n	$sql = array();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if (smd_um_table_exist()) {\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_UM_PRIVS. \"; \";\r\n		$sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_UM_GROUPS. \"; \";\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$msg = gTxt(\'smd_um_tbl_removed\');\r\n	} else {\r\n		$msg = gTxt(\'smd_um_tbl_not_removed\');\r\n		$smd_um_event($msg);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_um_table_exist($which=\'\') {\r\n	static $smd_um_installed = array();\r\n\r\n	// The number of expected cols in each table\r\n	$tbls = array(\r\n		SMD_UM_GROUPS => 3,\r\n		SMD_UM_PRIVS => 2,\r\n	);\r\n\r\n	if ($which && array_key_exists($which, $tbls) && isset($smd_um_installed[$which])) {\r\n		return ($smd_um_installed[$which] == $tbls[$which]);\r\n	}\r\n\r\n	if ($which == \'1\') {\r\n		$out = count($tbls);\r\n		foreach ($tbls as $tbl => $cols) {\r\n			$num = count(@safe_show(\'columns\', $tbl));\r\n			$smd_um_installed[$tbl] = $num;\r\n			$out -= ($tbls[$tbl] == $num) ? 1 : 0;\r\n		}\r\n		return ($out===0) ? 1 : 0;\r\n	} else if (array_key_exists($which, $tbls)) {\r\n		$num = count(@safe_show(\'columns\', $which));\r\n		$smd_um_installed[$which] = $num;\r\n		return ($smd_um_installed[$which] == $tbls[$which]);\r\n	}\r\n\r\n	return false;\r\n}\r\n\r\n\r\n//*****************\r\n// PUBLIC SIDE TAGS\r\n// Though we could load all $txp_permissions / $txp_groups to the public side for speed,\r\n// exposing permissions to the world is not such a hot idea. Therefore the privs are\r\n// fetched ad-hoc and cached\r\nfunction smd_um_has_privs($atts, $thing=NULL) {\r\n	global $txp_user;\r\n	static $smd_um_permissions;\r\n	static $smd_um_groups;\r\n	static $smd_ili = 0;\r\n\r\n	extract(lAtts(array(\r\n		\'name\'  => \'\',\r\n		\'group\' => \'\',\r\n		\'area\'  => \'\',\r\n		\'debug\' => 0,\r\n	),$atts));\r\n\r\n	$ret = false;\r\n	$smd_ili = ($smd_ili === 0) ? is_logged_in() : $smd_ili;\r\n\r\n	if ($smd_ili) {\r\n		$names = do_list($name);\r\n		$groups = do_list($group);\r\n		$areas = do_list($area);\r\n\r\n		// Handle > and < groups\r\n		$grplist = array();\r\n		foreach ($groups as $grp) {\r\n			if ( (strpos($grp, \'>\') === 0) || (strpos($grp, \'<\') === 0) ) {\r\n				if (!isset($smd_um_groups)) {\r\n					$smd_um_groups = safe_column(\'id\', SMD_UM_GROUPS, \'1=1 ORDER BY id\');\r\n				}\r\n				$val = substr($grp, 1);\r\n				// Pull out all groups higher than this one\r\n				if (substr($grp, 0, 1) == \'>\') {\r\n					foreach($smd_um_groups as $ug) {\r\n						if ($ug > $val) $grplist[] = $ug;\r\n					}\r\n				}\r\n				// Pull out all groups lower than this one\r\n				if (substr($grp, 0, 1) == \'<\') {\r\n					foreach($smd_um_groups as $ug) {\r\n						if (($ug < $val) && ($ug != \'0\')) $grplist[] = $ug;\r\n					}\r\n				}\r\n			} else {\r\n				$grplist[] = $grp;\r\n			}\r\n		}\r\n		$groups = array_unique($grplist);\r\n\r\n		if ($debug) {\r\n			echo \'++ LOGGED IN CREDENTIALS / PERMISSION AREAS / ALL GROUP IDs / NAME ATTR / GROUP ATTR / AREA ATTR ++\';\r\n			dmp($smd_ili, $smd_um_permissions, $smd_um_groups, $names, $groups, $areas);\r\n		}\r\n\r\n		$isname  = ($name && in_array($smd_ili[\'name\'], $names));\r\n		$isgroup = (($group != \'\') && in_array($smd_ili[\'privs\'], $groups));\r\n		$isarea = false;\r\n\r\n		// Build up a cached array of privs by area\r\n		if ($areas) {\r\n			// TODO: would be nice to do this in one query somehow\r\n			foreach ($areas as $place) {\r\n				if (!isset($smd_um_permissions[$place])) {\r\n					$prv = safe_field(\'GROUP_CONCAT(priv) AS privs\', SMD_UM_PRIVS, \"area = \'\" . doSlash($place) . \"\'\");\r\n					$smd_um_permissions[$place] = $prv;\r\n				}\r\n				$isarea = ( $isarea || (in_array($smd_ili[\'privs\'], do_list($smd_um_permissions[$place]))) );\r\n			}\r\n		}\r\n\r\n		if ($debug) {\r\n			echo \'++ TEST AGAINST NAME / GROUP / AREA ++\';\r\n			dmp($isname, $isgroup, $isarea);\r\n		}\r\n\r\n		// Compare the current logged in credentials against the relevant passed-in attribute combinations\r\n		if ($name) {\r\n			if ($group) {\r\n				if ($area) {\r\n					if ($debug) dmp(\'CHECK NAME AND GROUP AND AREA\');\r\n					$ret = ($isname && $isgroup && $isarea);\r\n				} else {\r\n					if ($debug) dmp(\'CHECK NAME AND GROUP\');\r\n					$ret = ($isname && $isgroup);\r\n				}\r\n			} else if ($area) {\r\n				if ($debug) dmp(\'CHECK NAME AND AREA\');\r\n				$ret = ($isname && $isarea);\r\n			} else {\r\n				if ($debug) dmp(\'CHECK NAME\');\r\n				$ret = $isname;\r\n			}\r\n		} elseif($group) {\r\n			if ($area) {\r\n				if ($debug) dmp(\'CHECK GROUP AND AREA\');\r\n				$ret = ($isgroup && $isarea);\r\n			} else {\r\n				if ($debug) dmp(\'CHECK GROUP\');\r\n				$ret = $isgroup;\r\n			}\r\n		} elseif($area) {\r\n			if ($debug) dmp(\'CHECK AREA\');\r\n			$ret = $isarea;\r\n		} else {\r\n			if ($debug) dmp(\'NO CHECKS (ANY USER)\');\r\n			$ret = true;\r\n		}\r\n	}\r\n\r\n	return parse(EvalElse($thing, $ret));\r\n}\r\n\r\n// ------------------------\r\n// Settings for the plugin\r\nfunction smd_um_get_prefs() {\r\n	global $prefs;\r\n\r\n	$smd_um_prefs = array(\r\n		\'smd_um_hierarchical_groups\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'0\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_admin_group\' => array(\r\n			\'html\'     => \'selectlist\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'content\'  => array(get_groups(), false),\r\n			\'default\'  => \'1\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_max_search_limit\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'500\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_pass_length\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'default\'  => \'12\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_active_timeout\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 50,\r\n			\'default\'  => \'60\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n		\'smd_um_self_alter\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'0\',\r\n			\'group\'    => \'smd_um_settings\',\r\n		),\r\n	);\r\n\r\n	return $smd_um_prefs;\r\n}', 'c34775d3a2613ebef32fdd155ade9526', '#@smd_um\n#@language es\nsmd_um_active => Usuarios actualmente conectados:\nsmd_um_active_timeout => Desconectar a los usuarios después de (segundos)\nsmd_um_admin_group => Grupo protegido de administradores\nsmd_um_article_count => Artículos\nsmd_um_based_on => basado en\nsmd_um_chp_lbl => Cambiar contraseña\nsmd_um_file_count => Ficheros\nsmd_um_grp_affected => . Usuarios afectados: {num}\nsmd_um_grp_created => Grupo \"{name}\" creado\nsmd_um_grp_deleted => Grupo eliminado\nsmd_um_grp_exists => El grupo ya existe, su ID de privilegios es {id}\nsmd_um_grp_lbl => Grupos\nsmd_um_grp_new => Nuevo nombre de grupo\nsmd_um_grp_new_name => nombre\nsmd_um_grp_saved => Información de grupo actualizada\nsmd_um_heading_grp => Grupos de usuarios\nsmd_um_heading_prf => Preferencias del gestor de usuarios\nsmd_um_heading_prv => Privilegios de usuarios\nsmd_um_heading_usr => Gestión de usuarios\nsmd_um_hierarchical_groups => Asumir grupos jerárquicos (niveles)\nsmd_um_image_count => Imágenes\nsmd_um_link_count => Enlaces\nsmd_um_max_search_limit => Límite máximo de resultados en búsqueda de usuarios\nsmd_um_name_required => Se requiere un nombre\nsmd_um_new_user => Nuevo usuario\nsmd_um_pass_change_error => Contraseña NO guardada\nsmd_um_pass_length => Longitud de contraseña (caracteres)\nsmd_um_prf_lbl => Preferencias\nsmd_um_prv_created => Área de privilegios \"{area}\" creada\nsmd_um_prv_exists => Área de privilegios ya existe\nsmd_um_prv_lbl => Privilegios\nsmd_um_prv_new => Nuevo área de privilegios\nsmd_um_prv_saved => Privilegios actualizados\nsmd_um_prv_smd_um => Imposible crear privilegios para smd_user_manager\nsmd_um_reset => [R]\nsmd_um_self_alter => Permitir cambiar privilegios a smd_um\nsmd_um_sel_all => Selecciona esta área completa, luego marca (c), desmarca (u) o invierte (t) la selección\nsmd_um_sel_grp => Selecciona este grupo, luego marca (c), desmarca (u) o invierte (t) la selección\nsmd_um_sel_prv => Selecciona este área, luego marca (c), desmarca (u) o invierte (t) la selección\nsmd_um_sel_reset => Reajustar: todas las áreas marcadas volverán a sus valores por defecto después de guardar\nsmd_um_settings => Preferencias\nsmd_um_tab_name => Gestor de usuarios\nsmd_um_tbl_installed => Tablas instaladas\nsmd_um_tbl_not_installed => Tablas no instaladas\nsmd_um_tbl_not_removed => Tablas no eliminadas\nsmd_um_tbl_removed => Tablas eliminadas\nsmd_um_user_count => Usuarios en este grupo:\nsmd_um_usr_lbl => Usuarios\n#@smd_um\n#@language fr\nsmd_um_active => Utilisateurs actuellement actifs :&nbsp;\nsmd_um_active_timeout => Durée d\'activité (secondes)\nsmd_um_admin_group => Groupe protégé d\'administrateurs\nsmd_um_article_count => Articles\nsmd_um_based_on => basé sur\nsmd_um_chp_lbl => Changer le mot de passe\nsmd_um_file_count => Fichiers\nsmd_um_grp_affected => . Utilisateurs affectés : {num}\nsmd_um_grp_created => Groupe \"{name}\" créé\nsmd_um_grp_deleted => Groupe supprimé\nsmd_um_grp_exists => Ce groupe existe déjà sous l\'ID {id}\nsmd_um_grp_lbl => Groupes\nsmd_um_grp_new => Titre du nouveau groupe\nsmd_um_grp_new_name => nom\nsmd_um_grp_saved => Infos du groupe mises à jour\nsmd_um_heading_grp => Groupe d\'utilisateurs\nsmd_um_heading_prf => Paramètres utilisateurs\nsmd_um_heading_prv => Privilèges utilisateurs\nsmd_um_heading_usr => Gestion des utilisateurs\nsmd_um_hierarchical_groups => Chargé de la hiérarchie des groupes (levels)\nsmd_um_image_count => Images\nsmd_um_link_count => Liens\nsmd_um_max_search_limit => Résultats des recherches utilisateurs maxi\nsmd_um_name_required => Un nom est requis\nsmd_um_new_user => Nouvel utilisateur\nsmd_um_pass_change_error => Mot de passe NON enregistré\nsmd_um_pass_length => Longueur du mot de passe (caractères)&nbsp;\nsmd_um_prf_lbl => Préférences\nsmd_um_prv_created => Définition de privilèges \"{area}\" créée\nsmd_um_prv_exists => Cette définition de privilèges existe déjà\nsmd_um_prv_lbl => Privilèges\nsmd_um_prv_new => Nouvelle définition de privilège\nsmd_um_prv_saved => Privilèges mis à jour\nsmd_um_prv_smd_um => Impossible de créer les privilèges pour smd_user_manager\nsmd_um_reset => [R]\nsmd_um_self_alter => Accès aux privilèges de smd_um\nsmd_um_sel_all => Sélectionnez la zone entière puis cocher (c), décocher (u) ou basculer les cases en surbrillance (t)\nsmd_um_sel_grp => Sélectionnez le groupe puis cocher (c), décocher (u) ou basculer les cases en surbrillance (t)\nsmd_um_sel_prv => Sélectionnez la zone puis cocher (c), décocher (u) ou basculer les cases en surbrillance (t)\nsmd_um_sel_reset => Réinitialiser : toutes les sélections verront leurs réglages rétablis par défaut après enregistrement\nsmd_um_settings => Paramètres\nsmd_um_tab_name => Gestion utilisateurs\nsmd_um_tbl_installed => Tables installées\nsmd_um_tbl_not_installed => Tables non installées\nsmd_um_tbl_not_removed => Tables non supprimées\nsmd_um_tbl_removed => Tables supprimées\nsmd_um_user_count => Utilisateurs de ce groupe :&nbsp;\nsmd_um_usr_lbl => Utilisateurs', '', 5, 8, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_where_used', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.30', 'Find which forms/pages/articles/plugins/text have been used where in your design', '<div id=\"smd_help\">\n\n	<h1>smd_where_used</h1>\n\n	<p>Really simple admin-side plugin to help find what you need to edit/delete. If you have a tonne of forms or pages and want to tidy stuff up it can be a bit of a pain to find what&#8217;s actually in use.</p>\n\n	<p>So use this plugin to search your sections, pages, forms or articles for references to plugins, or other pages/forms/stylesheets or even just plain text in articles / stylesheets.</p>\n\n	<h2 class=\"author\">Author</h2>\n\n	<p><a href=\"http://stefdawson.com/commentForm\">Stef Dawson</a></p>\n\n	<h2 class=\"install\">Installation / Uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/984/smd_where_used\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=27493\">forum thread</a> for more info and to report the success (or otherwise) of this plugin.</p>\n\n	<p>Uninstall by simply deleting the plugin from the Admin-&gt;Plugins pane.</p>\n\n	<h2 class=\"usage\">Usage</h2>\n\n	<p>Visit the Extensions tab and Click &#8216;Where used&#8217; to access the search form. Type some stuff to find, select where you want to look and hit &#8216;Search&#8217; or press Enter. Your requested locations will be searched for that term and the results tabulated, with hyperlinks to the offending items so you can quickly edit them.</p>\n\n	<p>You can decide whether to search for the term (&#8220;Include&#8221;) or to search for stuff <span class=\"caps\">NOT</span> containing that term (&#8220;Exclude&#8221;). You may also choose whether your search term matches/does not match a whole word or is case sensitive.</p>\n\n	<p>This is what the plugin looks at when you select a particular checkbox from the &#8220;Look at&#8221; row:</p>\n\n	<ul>\n		<li>&#8216;Sections&#8217; searches every Section for pages or stylesheets with the matching name</li>\n		<li>&#8216;Pages&#8217; searches every Page for forms / plugins / text with the matching name</li>\n		<li>&#8216;Forms&#8217; searches every Form for plugins / other forms / text with the matching name</li>\n		<li>&#8216;Stylesheets&#8217; searches every Stylesheet for text with the matching name</li>\n		<li>&#8216;Articles&#8217; searches (by default) every Article section, body, excerpt, override_form, title and keyword for mention of the text you specify. Click the <em>Article search fields</em> button to see a list of database columns that you may search. Check the ones you wish to consider and click <em>Save</em></li>\n	</ul>\n\n	<p>If, however, you leave the &#8216;Find&#8217; box empty and click &#8216;Search&#8217; the plugin will search for orphans. Orphans are defined as follows:</p>\n\n	<ul>\n		<li>Any Page that is not assigned to a Section</li>\n		<li>Any Stylesheet that is not assigned to a Section</li>\n		<li>Any Section that has no Articles in it (excluding ‘default’ which cannot have an article anyway)</li>\n		<li>Any Plugin that is not referenced from an Article (body/excerpt) or another Form or Page</li>\n		<li>Any Form that has no reference to it in any Article (body/excerpt/override_form) or another Form or Page</li>\n	</ul>\n\n	<p>Notes:</p>\n\n	<ul>\n		<li>Orphaned articles don&#8217;t make sense so they are omitted. The checkbox is ignored</li>\n		<li>Essential sections / pages / forms that cannot be deleted are not listed</li>\n		<li>Plugins are always displayed because there&#8217;s no check box for it. The reason is that smd_where_used does not allow searching within a plugin for a reference to a word, but it does check other places for references <em>to</em> plugins</li>\n		<li>The Plugin list can be toggled. It begins by showing both admin and public (A+P) plugins. If you wish to see public-only (P) plugins, click &#8216;Search&#8217; again to toggle the list</li>\n		<li>Just because an item is listed as orphaned does <em>not necessarily mean it is not used</em>! For example, rvm_maintenance checks for the existence of an <code>error_503</code> Page. Since it is never assigned to a Section it will be listed as orphaned. If you have a dedicated stylesheet for maintenance mode that, too, will be shown as orphaned. In short, be careful and make a backup :-)</li>\n		<li>Plugins such as pap_contact_cleaner and rvm_maintenance are listed as orphans even though they are used by other plugins / usually disabled. If you are unsure about a plugin, check it by typing a partial tag name into the search box</li>\n	</ul>\n\n	<h2 class=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>16 Jun 08 | v0.1 | Initial release</li>\n		<li>16 Jun 08 | v0.11 | Added section search and override_form</li>\n		<li>16 Jun 08 | v0.12 | Added include/exclude (thanks mrdale)</li>\n		<li>16 Jun 08 | v0.13 | Added ability to match whole words (thanks zanza)</li>\n		<li>17 Jun 08 | v0.14 | Added orphan search (thanks uli/net-carver)</li>\n		<li>18 Jun 08 | v0.15 | Fixed plugin list so it does a deep search inside the source code for tag names</li>\n		<li>18 Jun 08 | v0.16 | Added admin/client side plugin toggle ; searches now extended to title and keywords (both thanks uli)</li>\n		<li>18 Jun 08 | v0.17 | Articles are linked with ID and title (thanks uli)</li>\n		<li>18 Jun 08 | v0.18 | Added case sensitive searches (thanks geoff777)</li>\n		<li>11 Mar 09 | v0.19 | Added auto-focus to search box</li>\n		<li>12 Aug 09 | v0.2 | Requires <span class=\"caps\">TXP</span> 4.2.0 ; added auto-prefs for &#8216;look in&#8217; and article search fields pref (thanks uli)</li>\n		<li>19 May 10 | v0.3 | Requires <span class=\"caps\">TXP</span> 4.3.0 ; added searching inside Stylesheets</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.3 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help a:link, #smd_help a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n#smd_help a:hover, #smd_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .att-name { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:100%; text-align:center; padding-bottom:1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em 0;}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '// -------------------------------------------------------------\r\nif (@txpinterface == \'admin\') {\r\n	add_privs(\'smd_wu\',\'1,2\');\r\n\r\n	// Extensions tab\r\n	register_tab(\'extensions\', \'smd_wu\', smd_wu_gTxt(\'smd_wu\'));\r\n	register_callback(\'smd_wu\', \'smd_wu\');\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu($event, $step) {\r\n	if(!$step or !in_array($step, array(\r\n			\'smd_wu_search\',\r\n			\'smd_wu_prefsave\',\r\n		))) {\r\n		smd_wu_showform(\'\');\r\n	} else $step();\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_showform($message, $term=\'\', $sel=array(), $incl=0, $whole=0, $mode=0, $plugtype=0, $case=0) {\r\n	pagetop(smd_wu_gTxt(\'smd_wu\'),$message);\r\n	$btnSearch = fInput(\'submit\', \'smd_wu_search\', gTxt(\'search\'), \'smallerbox\').sInput(\'smd_wu_search\').hInput(\'plugtype\', (($plugtype==1) ? 0 : 1));\r\n	$btnStyle = \' style=\"border:0;height:25px\"\';\r\n	$place = array(\'sections\',\'pages\',\'forms\',\'stylesheets\',\'articles\');\r\n	$sel = empty($sel) ? unserialize(get_pref(\'smd_wu_look_in\')) : $sel;\r\n	$sel = empty($sel) ? $place : $sel;\r\n\r\n	echo \'<form method=\"post\" name=\"smd_wu_form\">\';\r\n	echo startTable(\'list\');\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'search_lbl\')) . tdcs(fInput(\'text\', \'search_for\', stripslashes($term), \'\', \'\', \'\', 30, \'\', \'smd_search_for\'), 5) . tda($btnSearch, $btnStyle));\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'filter\')).tdcs(radio(\'meth\',0,(($incl==0)?1:0)).smd_wu_gTxt(\'inclusion\').\" \" . radio(\'meth\',1,(($incl==1)?1:0)).smd_wu_gTxt(\'exclusion\'). \" | \" . smd_wu_gTxt(\'whole_word\').checkbox(\'whole\', 1, (($whole==1)?1:0)). \" | \" . smd_wu_gTxt(\'match_case\').checkbox(\'case\', 1, (($case==1)?1:0)), 5));\r\n	$out = \'\';\r\n	foreach ($place as $here) {\r\n		$out .= fLabelCell(ucfirst(gTxt($here)) . checkbox(\'places[]\', $here, in_array($here,$sel)));\r\n	}\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'search_where_lbl\')) . $out);\r\n	echo endTable();\r\n	echo \'</form>\';\r\n\r\n	// Render the prefs checkboxes\r\n	$cols = getThings(\'describe `\'.PFX.\'textpattern`\');\r\n	$flds = do_list(get_pref(\'smd_wu_article_fields\'));\r\n	$flds = empty($flds[0]) ? do_list(\'Body,Excerpt,override_form,Section,Title,Keywords\') : $flds;\r\n\r\n	echo \'<div id=\"smd_wu_prefwrap\"><a id=\"smd_wu_preftog\" href=\"#\">\'.smd_wu_gTxt(\'prefs_link\').\'</a>\';\r\n	echo \'<form method=\"post\" id=\"smd_wu_prefs\" style=\"display:none;\">\';\r\n	foreach ($cols as $col) {\r\n		echo br.checkbox(\'smd_wu_article_fields[]\', $col, (in_array($col, $flds) ? 1 : 0)).sp.$col;\r\n	}\r\n	echo fInput(\'submit\', \'smd_wu_prefsave\', gTxt(\'save\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'smd_wu_prefsave\');\r\n	echo \'</form></div>\';\r\n	echo <<<EOJS\r\n<script type=\"text/javascript\">\r\njQuery(function() {\r\n	jQuery(\"#smd_search_for\").focus();\r\n	jQuery(\"#smd_wu_prefs\").hide();\r\n	jQuery(\"#smd_wu_preftog\").click(function() {\r\n		jQuery(\"#smd_wu_prefs\").toggle(\'fast\');\r\n		return false;\r\n	});\r\n	jQuery(\"#smd_wu_prefsave\").click(function() {\r\n		var out = [];\r\n		jQuery(\"#smd_wu_prefs input:checked\").each(function() {\r\n			out.push(jQuery(this).val());\r\n		});\r\n\r\n		sendAsyncEvent(\r\n			{\r\n				event: textpattern.event,\r\n				step: \'smd_wu_prefsave\',\r\n				smd_wu_article_fields: out.join(\',\')\r\n			}\r\n		);\r\n		jQuery(\"#smd_wu_prefs\").toggle(\'fast\');\r\n		return false;\r\n	});\r\n});\r\n</script>\r\n<style type=\"text/css\">\r\n#smd_wu_prefwrap {\r\n	width:140px;\r\n	margin:10px auto 0;\r\n}\r\n</style>\r\nEOJS;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_prefsave() {\r\n	$cols = getThings(\'describe `\'.PFX.\'textpattern`\');\r\n	$fields = do_list(gps(\'smd_wu_article_fields\'));\r\n	$oflds = array();\r\n	foreach($fields as $fld) {\r\n		if (in_array($fld, $cols)) {\r\n			$oflds[] = $fld;\r\n		}\r\n	}\r\n	set_pref(\'smd_wu_article_fields\', join(\',\',$oflds), \'smd_wu\', PREF_HIDDEN, \'text_input\');\r\n	send_xml_response();\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_search() {\r\n	extract(doSlash(gpsa(array(\'search_for\'))));\r\n	$whole = gps(\'whole\');\r\n	$case = gps(\'case\'); // 0 = case insensitive, 1 = case sensitive\r\n	$caseSense = ($case==1) ? \' BINARY \' : \'\';\r\n	$meth = gps(\'meth\'); // 0 = include, 1 = exclude\r\n	$plugtype = gps(\'plugtype\'); // 0 = public, 1 = 0+admin, 2 = 1+library\r\n	$plugtype = (is_numeric($plugtype) && $plugtype < 3) ? $plugtype : 0;\r\n	$joinme = ($meth == 0) ? \" OR \" : \" AND \";\r\n	$places = gps(\'places\');\r\n	$places = is_array($places) ? $places : array();\r\n	set_pref(\'smd_wu_look_in\', serialize($places), \'smd_wu\', PREF_HIDDEN, \'text_input\');\r\n	$mode = ($search_for) ? 0 : 1;\r\n	smd_wu_showform(\'\', $search_for, $places, $meth, $whole, $mode, $plugtype, $case);\r\n\r\n	$artflds = get_pref(\'smd_wu_article_fields\');\r\n	$artflds = empty($artflds) ? \'Body,Excerpt,override_form,Section,Title,Keywords\' : $artflds;\r\n\r\n	// Entries in the placeTable array are:\r\n	//  0: Table to search\r\n	//  1: Column to search\r\n	//  2: Column to return\r\n	//  3: Heading to display if results found\r\n	//  4: Event of destination URL\r\n	//  5: Step of destination URL\r\n	//  6: Additional URL var\r\n	//  7: Additional URL var replacement (used in strtr)\r\n	$placeTable = array(\r\n		\'pages\' => array(\'txp_page\', \'user_html\', \'name\', gTxt(\'pages\'), \'page\', \'\', \'name\', \'{name}\', \'\'),\r\n		\'forms\' => array(\'txp_form\', \'form\', \'name\', gTxt(\'forms\'), \'form\', \'form_edit\', \'name\', \'{name}\', \'\'),\r\n		\'articles\' => array(\'textpattern\', $artflds, \'ID,title\', gTxt(\'articles\'), \'article\', \'edit\', \'ID\', \'{ID}\', \'\'),\r\n		\'sections\' => array(\'txp_section\', \'page,css\', \'name\', gTxt(\'sections\'), \'section\', \'\', \'#\', \'section-{name}\', \'\'),\r\n		\'stylesheets\' => array(\'txp_css\', \'css\', \'name\', smd_wu_gTxt(\'stylesheets\'), \'css\', \'\', \'name\', \'{name}\', \'\'),\r\n	);\r\n\r\n	$rs = array();\r\n	echo n.\'<hr width=\"50%\" />\';\r\n	echo startTable(\'list\');\r\n	$colHead = array();\r\n	$colBody = array();\r\n\r\n	if ($search_for) {\r\n		echo tr(tdcs(tag(gTxt(\'search_results\'), \'h2\'), 5));\r\n		foreach ($places as $place) {\r\n			$crow = $placeTable[$place];\r\n			$where = array();\r\n			$witems = do_list($crow[1]);\r\n			$list = do_list($crow[2]);\r\n\r\n			foreach ($witems as $item) {\r\n				$where[] = $caseSense . $item . (($meth==1) ? \' NOT\' : \'\') . (($whole==1) ? \' REGEXP \\\'[[:<:]]\'.$search_for.\'[[:>:]]\\\'\' : \" LIKE \'%$search_for%\'\");\r\n			}\r\n			$rs = safe_rows($crow[2], $crow[0], \'(\'.join($joinme, $where).\') ORDER BY \'.$list[0]);\r\n			$colHead[] = td(strong($crow[3]));\r\n			if ($rs) {\r\n				$out = \'<ul>\';\r\n				foreach ($rs as $row) {\r\n					$hlink = \'\';\r\n					$vars = \'\';\r\n					foreach ($list as $col) {\r\n						$from = \'{\'.$col.\'}\';\r\n						if (strpos($crow[7], $from) !== false) {\r\n							if ($crow[6] == \"#\") {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5])).$crow[6].strtr($crow[7], array($from => $row[$col]));\r\n							} else {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5], $crow[6] => strtr($crow[7], array($from => $row[$col]))));\r\n							}\r\n						}\r\n						$hlink .= $row[$col].\" \";\r\n					}\r\n					$out .= ($hlink) ? \'<li>\' . (($vars) ? \'<a href=\"index.php\'.$vars.\'\">\'.$hlink.\'</a> \' : $hlink) . \'</li>\': \'\';\r\n				}\r\n				$out .= \'</ul>\';\r\n				$colBody[] = td($out);\r\n			} else {\r\n				$colBody[] = td(gTxt(\'no_results_found\'));\r\n			}\r\n		}\r\n		echo tr(join(\" \", $colHead));\r\n		echo tr(join(\" \", $colBody));\r\n	} else {\r\n		// No search criteria, so show orphans\r\n		echo tr(tdcs(tag(smd_wu_gTxt(\'orphan_results\'), \'h2\'), 5));\r\n\r\n		// Reprogram the places/placeTable\r\n		$artkey = array_search(\'articles\', $places);\r\n		if ($artkey !== false) {\r\n			unset($places[$artkey]);\r\n		}\r\n\r\n		// pages/forms/sections to ignore because they are static and cannot be deleted\r\n		$essentials = array(\r\n			\'sections\' => array(\'default\'),\r\n			\'pages\' => array(\'error_default\'),\r\n			\'forms\' => array(\'comments\',\'comments_display\',\'comment_form\',\'default\',\'Links\',\'files\'), // copied from txp_forms\r\n		);\r\n\r\n		$places[] = \'plugins\';\r\n\r\n		$placeTable[\'plugins\'] = array(\'txp_plugin\', \'name\', \'name\', gTxt(\'plugins\'), \'plugin\', \'\', \'#\', \'{name}\');\r\n		$placeTable[\'stylesheets\'] = array(\'txp_css\', \'name\', \'name\', smd_wu_gTxt(\'stylesheets\'), \'css\', \'\', \'name\', \'{name}\');\r\n\r\n		$placeTable[\'forms\'][0] = \"SELECT tf.name FROM \" .safe_pfx(\'txp_form\'). \" AS tf WHERE tf.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'forms\'])). \") ORDER BY tf.name\"; // Would be nice to exclude forms that reference forms here instead of iterating through them later\r\n		$placeTable[\'pages\'][0] = \"SELECT tp.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts RIGHT JOIN \" .safe_pfx(\'txp_page\'). \" AS tp ON ts.page = tp.name WHERE ts.page IS NULL AND tp.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'pages\'])). \") ORDER BY tp.name\";\r\n		$placeTable[\'plugins\'][0] = \"SELECT tg.name, tg.code FROM \" .safe_pfx(\'txp_plugin\'). \" AS tg WHERE type < \" .($plugtype+1). \" AND tg.name != \'smd_where_used\' ORDER BY tg.name\";\r\n		$placeTable[\'sections\'][0] = \"SELECT ts.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts LEFT JOIN \" .safe_pfx(\'textpattern\'). \" AS txp ON ts.name = txp.section WHERE ID IS NULL AND ts.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'sections\'])). \") ORDER BY ts.name\";\r\n		$placeTable[\'stylesheets\'][0] = \"SELECT tc.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts RIGHT JOIN \" .safe_pfx(\'txp_css\'). \" AS tc ON ts.css = tc.name WHERE ts.page IS NULL ORDER BY tc.name\";\r\n\r\n		// For \"awkward\" queries that can\'t be done in one shot, there are three things required per $place:\r\n		//  1: txp table name\r\n		//  2: list of columns to compare\r\n		//  3: method of comparison per column (0 = \"direct match\", 1 = \"like\")\r\n		$cTable = array(\r\n			\'plugins\' => array(\r\n				\'txp_form\' => array(\'form\' => 1),\r\n				\'txp_page\' => array(\'user_html\' => 1),\r\n				\'textpattern\' => array(\'Body\' => 1, \'Excerpt\' => 1),\r\n			),\r\n			\'forms\' => array(\r\n				\'txp_form\' => array(\'form\' => 1),\r\n				\'txp_page\' => array(\'user_html\' => 1),\r\n				\'textpattern\' => array(\'Body\' => 1, \'Excerpt\' => 1, \'override_form\' => 0),\r\n			),\r\n		);\r\n\r\n		// Work on a column at a time\r\n		foreach ($places as $place) {\r\n			$crow = $placeTable[$place];\r\n			$colRefs = do_list($crow[2]);\r\n			$colHead[] = td(strong($crow[3] . (($place == \'plugins\') ? (($plugtype==0) ? smd_wu_gTxt(\'public_plugins\') : smd_wu_gTxt(\'admin_plugins\') ) : \'\') ));\r\n			$rs = startRows($crow[0]);\r\n			if ($rs) {\r\n				$out = \'<ul>\';\r\n				while ($row = nextRow($rs)) {\r\n					// Count the no of records matching this name in each table.\r\n					// Any time this goes above zero, the item is used and it can be ignored\r\n					$fnaliases = array();\r\n					if (array_key_exists($place, $cTable)) {\r\n						// Plugin functions are not necessarily the name of the plugin itself.\r\n						// Find all function definitions and build a list of aliases\r\n						if ($place == \'plugins\') {\r\n							$re = \'/function\\s+([A-Za-z0-9_]+)\\s*\\(/\';\r\n							$num = preg_match_all($re, $row[\'code\'], $fnaliases);\r\n						}\r\n\r\n						$recs = 0;\r\n						foreach ($cTable[$place] as $tbl => $colInfo) {\r\n							if ($recs == 0) {\r\n								$where = array();\r\n								foreach ($colInfo as $colName => $qryType) {\r\n									$where[] = $colName . (($qryType==0) ? \" = \'\" .$row[$colRefs[0]]. \"\'\" : \" LIKE \'%\" .$row[$colRefs[0]]. \"%\'\");\r\n									if (count($fnaliases) > 1) {\r\n										foreach ($fnaliases[1] as $fnalias) {\r\n											$where[] = $colName . (($qryType==0) ? \" = \'\" .$fnalias. \"\'\" : \" LIKE \'%\" .$fnalias. \"%\'\");\r\n										}\r\n									}\r\n								}\r\n								$where = join(\" OR \", $where);\r\n								$recs += safe_count($tbl, $where);\r\n							}\r\n						}\r\n\r\n						// If the item has not been found, flag it\r\n						if ($recs == 0) {\r\n							$colNames = $colRefs;\r\n						} else {\r\n							$colNames = array();\r\n						}\r\n					} else {\r\n						$colNames = $colRefs;\r\n					}\r\n\r\n					// Make up the string to display, and hyperlink it if directed\r\n					$hlink = \'\';\r\n					$vars = \'\';\r\n					foreach ($colNames as $col) {\r\n						$from = \'{\'.$col.\'}\';\r\n						if (strpos($crow[7], $from) !== false) {\r\n							if ($crow[6] == \"#\") {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5])).$crow[6].strtr($crow[7], array($from => $row[$col]));\r\n							} else {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5], $crow[6] => strtr($crow[7], array($from => $row[$col]))));\r\n							}\r\n						}\r\n						$hlink .= $row[$col].\" \";\r\n					}\r\n					$out .= ($hlink) ? \'<li>\' . (($vars) ? \'<a href=\"index.php\'.$vars.\'\">\'.$hlink.\'</a> \' : $hlink) . \'</li>\': \'\';\r\n				}\r\n				$out .= \'</ul>\';\r\n				$colBody[] = td($out);\r\n			} else {\r\n				$colBody[] = td(smd_wu_gTxt(\'no_orphans_found\'));\r\n			}\r\n		}\r\n		echo tr(join(\" \", $colHead));\r\n		echo tr(join(\" \", $colBody));\r\n	}\r\n	echo endTable();\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// Plugin-specific replacement strings - localise as required\r\n// -------------------------------------------------------------\r\nfunction smd_wu_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'admin_plugins\' => \' [ A+P ]\',\r\n		\'public_plugins\' => \' [ P ]\',\r\n		\'exclusion\' => \'Exclude\',\r\n		\'inclusion\' => \'Include\',\r\n		\'filter\' => \'Filter:\',\r\n		\'match_case\' => \'Match case\',\r\n		\'no_orphans_found\' => \'No orphans found.\',\r\n		\'orphan_results\' => \'Possible orphans\',\r\n		\'prefs_link\' => \'Article search fields\',\r\n		\'search_lbl\' => \'Find:\',\r\n		\'search_where_lbl\' => \'Look at:\',\r\n		\'stylesheets\' => \'Stylesheets\',\r\n		\'smd_wu\' => \'Where used\',\r\n		\'whole_word\' => \'Whole words\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', '// -------------------------------------------------------------\r\nif (@txpinterface == \'admin\') {\r\n	add_privs(\'smd_wu\',\'1,2\');\r\n\r\n	// Extensions tab\r\n	register_tab(\'extensions\', \'smd_wu\', smd_wu_gTxt(\'smd_wu\'));\r\n	register_callback(\'smd_wu\', \'smd_wu\');\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu($event, $step) {\r\n	if(!$step or !in_array($step, array(\r\n			\'smd_wu_search\',\r\n			\'smd_wu_prefsave\',\r\n		))) {\r\n		smd_wu_showform(\'\');\r\n	} else $step();\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_showform($message, $term=\'\', $sel=array(), $incl=0, $whole=0, $mode=0, $plugtype=0, $case=0) {\r\n	pagetop(smd_wu_gTxt(\'smd_wu\'),$message);\r\n	$btnSearch = fInput(\'submit\', \'smd_wu_search\', gTxt(\'search\'), \'smallerbox\').sInput(\'smd_wu_search\').hInput(\'plugtype\', (($plugtype==1) ? 0 : 1));\r\n	$btnStyle = \' style=\"border:0;height:25px\"\';\r\n	$place = array(\'sections\',\'pages\',\'forms\',\'stylesheets\',\'articles\');\r\n	$sel = empty($sel) ? unserialize(get_pref(\'smd_wu_look_in\')) : $sel;\r\n	$sel = empty($sel) ? $place : $sel;\r\n\r\n	echo \'<form method=\"post\" name=\"smd_wu_form\">\';\r\n	echo startTable(\'list\');\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'search_lbl\')) . tdcs(fInput(\'text\', \'search_for\', stripslashes($term), \'\', \'\', \'\', 30, \'\', \'smd_search_for\'), 5) . tda($btnSearch, $btnStyle));\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'filter\')).tdcs(radio(\'meth\',0,(($incl==0)?1:0)).smd_wu_gTxt(\'inclusion\').\" \" . radio(\'meth\',1,(($incl==1)?1:0)).smd_wu_gTxt(\'exclusion\'). \" | \" . smd_wu_gTxt(\'whole_word\').checkbox(\'whole\', 1, (($whole==1)?1:0)). \" | \" . smd_wu_gTxt(\'match_case\').checkbox(\'case\', 1, (($case==1)?1:0)), 5));\r\n	$out = \'\';\r\n	foreach ($place as $here) {\r\n		$out .= fLabelCell(ucfirst(gTxt($here)) . checkbox(\'places[]\', $here, in_array($here,$sel)));\r\n	}\r\n	echo tr(fLabelCell(smd_wu_gTxt(\'search_where_lbl\')) . $out);\r\n	echo endTable();\r\n	echo \'</form>\';\r\n\r\n	// Render the prefs checkboxes\r\n	$cols = getThings(\'describe `\'.PFX.\'textpattern`\');\r\n	$flds = do_list(get_pref(\'smd_wu_article_fields\'));\r\n	$flds = empty($flds[0]) ? do_list(\'Body,Excerpt,override_form,Section,Title,Keywords\') : $flds;\r\n\r\n	echo \'<div id=\"smd_wu_prefwrap\"><a id=\"smd_wu_preftog\" href=\"#\">\'.smd_wu_gTxt(\'prefs_link\').\'</a>\';\r\n	echo \'<form method=\"post\" id=\"smd_wu_prefs\" style=\"display:none;\">\';\r\n	foreach ($cols as $col) {\r\n		echo br.checkbox(\'smd_wu_article_fields[]\', $col, (in_array($col, $flds) ? 1 : 0)).sp.$col;\r\n	}\r\n	echo fInput(\'submit\', \'smd_wu_prefsave\', gTxt(\'save\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'smd_wu_prefsave\');\r\n	echo \'</form></div>\';\r\n	echo <<<EOJS\r\n<script type=\"text/javascript\">\r\njQuery(function() {\r\n	jQuery(\"#smd_search_for\").focus();\r\n	jQuery(\"#smd_wu_prefs\").hide();\r\n	jQuery(\"#smd_wu_preftog\").click(function() {\r\n		jQuery(\"#smd_wu_prefs\").toggle(\'fast\');\r\n		return false;\r\n	});\r\n	jQuery(\"#smd_wu_prefsave\").click(function() {\r\n		var out = [];\r\n		jQuery(\"#smd_wu_prefs input:checked\").each(function() {\r\n			out.push(jQuery(this).val());\r\n		});\r\n\r\n		sendAsyncEvent(\r\n			{\r\n				event: textpattern.event,\r\n				step: \'smd_wu_prefsave\',\r\n				smd_wu_article_fields: out.join(\',\')\r\n			}\r\n		);\r\n		jQuery(\"#smd_wu_prefs\").toggle(\'fast\');\r\n		return false;\r\n	});\r\n});\r\n</script>\r\n<style type=\"text/css\">\r\n#smd_wu_prefwrap {\r\n	width:140px;\r\n	margin:10px auto 0;\r\n}\r\n</style>\r\nEOJS;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_prefsave() {\r\n	$cols = getThings(\'describe `\'.PFX.\'textpattern`\');\r\n	$fields = do_list(gps(\'smd_wu_article_fields\'));\r\n	$oflds = array();\r\n	foreach($fields as $fld) {\r\n		if (in_array($fld, $cols)) {\r\n			$oflds[] = $fld;\r\n		}\r\n	}\r\n	set_pref(\'smd_wu_article_fields\', join(\',\',$oflds), \'smd_wu\', PREF_HIDDEN, \'text_input\');\r\n	send_xml_response();\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction smd_wu_search() {\r\n	extract(doSlash(gpsa(array(\'search_for\'))));\r\n	$whole = gps(\'whole\');\r\n	$case = gps(\'case\'); // 0 = case insensitive, 1 = case sensitive\r\n	$caseSense = ($case==1) ? \' BINARY \' : \'\';\r\n	$meth = gps(\'meth\'); // 0 = include, 1 = exclude\r\n	$plugtype = gps(\'plugtype\'); // 0 = public, 1 = 0+admin, 2 = 1+library\r\n	$plugtype = (is_numeric($plugtype) && $plugtype < 3) ? $plugtype : 0;\r\n	$joinme = ($meth == 0) ? \" OR \" : \" AND \";\r\n	$places = gps(\'places\');\r\n	$places = is_array($places) ? $places : array();\r\n	set_pref(\'smd_wu_look_in\', serialize($places), \'smd_wu\', PREF_HIDDEN, \'text_input\');\r\n	$mode = ($search_for) ? 0 : 1;\r\n	smd_wu_showform(\'\', $search_for, $places, $meth, $whole, $mode, $plugtype, $case);\r\n\r\n	$artflds = get_pref(\'smd_wu_article_fields\');\r\n	$artflds = empty($artflds) ? \'Body,Excerpt,override_form,Section,Title,Keywords\' : $artflds;\r\n\r\n	// Entries in the placeTable array are:\r\n	//  0: Table to search\r\n	//  1: Column to search\r\n	//  2: Column to return\r\n	//  3: Heading to display if results found\r\n	//  4: Event of destination URL\r\n	//  5: Step of destination URL\r\n	//  6: Additional URL var\r\n	//  7: Additional URL var replacement (used in strtr)\r\n	$placeTable = array(\r\n		\'pages\' => array(\'txp_page\', \'user_html\', \'name\', gTxt(\'pages\'), \'page\', \'\', \'name\', \'{name}\', \'\'),\r\n		\'forms\' => array(\'txp_form\', \'form\', \'name\', gTxt(\'forms\'), \'form\', \'form_edit\', \'name\', \'{name}\', \'\'),\r\n		\'articles\' => array(\'textpattern\', $artflds, \'ID,title\', gTxt(\'articles\'), \'article\', \'edit\', \'ID\', \'{ID}\', \'\'),\r\n		\'sections\' => array(\'txp_section\', \'page,css\', \'name\', gTxt(\'sections\'), \'section\', \'\', \'#\', \'section-{name}\', \'\'),\r\n		\'stylesheets\' => array(\'txp_css\', \'css\', \'name\', smd_wu_gTxt(\'stylesheets\'), \'css\', \'\', \'name\', \'{name}\', \'\'),\r\n	);\r\n\r\n	$rs = array();\r\n	echo n.\'<hr width=\"50%\" />\';\r\n	echo startTable(\'list\');\r\n	$colHead = array();\r\n	$colBody = array();\r\n\r\n	if ($search_for) {\r\n		echo tr(tdcs(tag(gTxt(\'search_results\'), \'h2\'), 5));\r\n		foreach ($places as $place) {\r\n			$crow = $placeTable[$place];\r\n			$where = array();\r\n			$witems = do_list($crow[1]);\r\n			$list = do_list($crow[2]);\r\n\r\n			foreach ($witems as $item) {\r\n				$where[] = $caseSense . $item . (($meth==1) ? \' NOT\' : \'\') . (($whole==1) ? \' REGEXP \\\'[[:<:]]\'.$search_for.\'[[:>:]]\\\'\' : \" LIKE \'%$search_for%\'\");\r\n			}\r\n			$rs = safe_rows($crow[2], $crow[0], \'(\'.join($joinme, $where).\') ORDER BY \'.$list[0]);\r\n			$colHead[] = td(strong($crow[3]));\r\n			if ($rs) {\r\n				$out = \'<ul>\';\r\n				foreach ($rs as $row) {\r\n					$hlink = \'\';\r\n					$vars = \'\';\r\n					foreach ($list as $col) {\r\n						$from = \'{\'.$col.\'}\';\r\n						if (strpos($crow[7], $from) !== false) {\r\n							if ($crow[6] == \"#\") {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5])).$crow[6].strtr($crow[7], array($from => $row[$col]));\r\n							} else {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5], $crow[6] => strtr($crow[7], array($from => $row[$col]))));\r\n							}\r\n						}\r\n						$hlink .= $row[$col].\" \";\r\n					}\r\n					$out .= ($hlink) ? \'<li>\' . (($vars) ? \'<a href=\"index.php\'.$vars.\'\">\'.$hlink.\'</a> \' : $hlink) . \'</li>\': \'\';\r\n				}\r\n				$out .= \'</ul>\';\r\n				$colBody[] = td($out);\r\n			} else {\r\n				$colBody[] = td(gTxt(\'no_results_found\'));\r\n			}\r\n		}\r\n		echo tr(join(\" \", $colHead));\r\n		echo tr(join(\" \", $colBody));\r\n	} else {\r\n		// No search criteria, so show orphans\r\n		echo tr(tdcs(tag(smd_wu_gTxt(\'orphan_results\'), \'h2\'), 5));\r\n\r\n		// Reprogram the places/placeTable\r\n		$artkey = array_search(\'articles\', $places);\r\n		if ($artkey !== false) {\r\n			unset($places[$artkey]);\r\n		}\r\n\r\n		// pages/forms/sections to ignore because they are static and cannot be deleted\r\n		$essentials = array(\r\n			\'sections\' => array(\'default\'),\r\n			\'pages\' => array(\'error_default\'),\r\n			\'forms\' => array(\'comments\',\'comments_display\',\'comment_form\',\'default\',\'Links\',\'files\'), // copied from txp_forms\r\n		);\r\n\r\n		$places[] = \'plugins\';\r\n\r\n		$placeTable[\'plugins\'] = array(\'txp_plugin\', \'name\', \'name\', gTxt(\'plugins\'), \'plugin\', \'\', \'#\', \'{name}\');\r\n		$placeTable[\'stylesheets\'] = array(\'txp_css\', \'name\', \'name\', smd_wu_gTxt(\'stylesheets\'), \'css\', \'\', \'name\', \'{name}\');\r\n\r\n		$placeTable[\'forms\'][0] = \"SELECT tf.name FROM \" .safe_pfx(\'txp_form\'). \" AS tf WHERE tf.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'forms\'])). \") ORDER BY tf.name\"; // Would be nice to exclude forms that reference forms here instead of iterating through them later\r\n		$placeTable[\'pages\'][0] = \"SELECT tp.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts RIGHT JOIN \" .safe_pfx(\'txp_page\'). \" AS tp ON ts.page = tp.name WHERE ts.page IS NULL AND tp.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'pages\'])). \") ORDER BY tp.name\";\r\n		$placeTable[\'plugins\'][0] = \"SELECT tg.name, tg.code FROM \" .safe_pfx(\'txp_plugin\'). \" AS tg WHERE type < \" .($plugtype+1). \" AND tg.name != \'smd_where_used\' ORDER BY tg.name\";\r\n		$placeTable[\'sections\'][0] = \"SELECT ts.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts LEFT JOIN \" .safe_pfx(\'textpattern\'). \" AS txp ON ts.name = txp.section WHERE ID IS NULL AND ts.name NOT IN (\" .doQuote(implode(\"\',\'\",$essentials[\'sections\'])). \") ORDER BY ts.name\";\r\n		$placeTable[\'stylesheets\'][0] = \"SELECT tc.name FROM \" .safe_pfx(\'txp_section\'). \" AS ts RIGHT JOIN \" .safe_pfx(\'txp_css\'). \" AS tc ON ts.css = tc.name WHERE ts.page IS NULL ORDER BY tc.name\";\r\n\r\n		// For \"awkward\" queries that can\'t be done in one shot, there are three things required per $place:\r\n		//  1: txp table name\r\n		//  2: list of columns to compare\r\n		//  3: method of comparison per column (0 = \"direct match\", 1 = \"like\")\r\n		$cTable = array(\r\n			\'plugins\' => array(\r\n				\'txp_form\' => array(\'form\' => 1),\r\n				\'txp_page\' => array(\'user_html\' => 1),\r\n				\'textpattern\' => array(\'Body\' => 1, \'Excerpt\' => 1),\r\n			),\r\n			\'forms\' => array(\r\n				\'txp_form\' => array(\'form\' => 1),\r\n				\'txp_page\' => array(\'user_html\' => 1),\r\n				\'textpattern\' => array(\'Body\' => 1, \'Excerpt\' => 1, \'override_form\' => 0),\r\n			),\r\n		);\r\n\r\n		// Work on a column at a time\r\n		foreach ($places as $place) {\r\n			$crow = $placeTable[$place];\r\n			$colRefs = do_list($crow[2]);\r\n			$colHead[] = td(strong($crow[3] . (($place == \'plugins\') ? (($plugtype==0) ? smd_wu_gTxt(\'public_plugins\') : smd_wu_gTxt(\'admin_plugins\') ) : \'\') ));\r\n			$rs = startRows($crow[0]);\r\n			if ($rs) {\r\n				$out = \'<ul>\';\r\n				while ($row = nextRow($rs)) {\r\n					// Count the no of records matching this name in each table.\r\n					// Any time this goes above zero, the item is used and it can be ignored\r\n					$fnaliases = array();\r\n					if (array_key_exists($place, $cTable)) {\r\n						// Plugin functions are not necessarily the name of the plugin itself.\r\n						// Find all function definitions and build a list of aliases\r\n						if ($place == \'plugins\') {\r\n							$re = \'/function\\s+([A-Za-z0-9_]+)\\s*\\(/\';\r\n							$num = preg_match_all($re, $row[\'code\'], $fnaliases);\r\n						}\r\n\r\n						$recs = 0;\r\n						foreach ($cTable[$place] as $tbl => $colInfo) {\r\n							if ($recs == 0) {\r\n								$where = array();\r\n								foreach ($colInfo as $colName => $qryType) {\r\n									$where[] = $colName . (($qryType==0) ? \" = \'\" .$row[$colRefs[0]]. \"\'\" : \" LIKE \'%\" .$row[$colRefs[0]]. \"%\'\");\r\n									if (count($fnaliases) > 1) {\r\n										foreach ($fnaliases[1] as $fnalias) {\r\n											$where[] = $colName . (($qryType==0) ? \" = \'\" .$fnalias. \"\'\" : \" LIKE \'%\" .$fnalias. \"%\'\");\r\n										}\r\n									}\r\n								}\r\n								$where = join(\" OR \", $where);\r\n								$recs += safe_count($tbl, $where);\r\n							}\r\n						}\r\n\r\n						// If the item has not been found, flag it\r\n						if ($recs == 0) {\r\n							$colNames = $colRefs;\r\n						} else {\r\n							$colNames = array();\r\n						}\r\n					} else {\r\n						$colNames = $colRefs;\r\n					}\r\n\r\n					// Make up the string to display, and hyperlink it if directed\r\n					$hlink = \'\';\r\n					$vars = \'\';\r\n					foreach ($colNames as $col) {\r\n						$from = \'{\'.$col.\'}\';\r\n						if (strpos($crow[7], $from) !== false) {\r\n							if ($crow[6] == \"#\") {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5])).$crow[6].strtr($crow[7], array($from => $row[$col]));\r\n							} else {\r\n								$vars = join_qs(array(\"event\" => $crow[4], \"step\" => $crow[5], $crow[6] => strtr($crow[7], array($from => $row[$col]))));\r\n							}\r\n						}\r\n						$hlink .= $row[$col].\" \";\r\n					}\r\n					$out .= ($hlink) ? \'<li>\' . (($vars) ? \'<a href=\"index.php\'.$vars.\'\">\'.$hlink.\'</a> \' : $hlink) . \'</li>\': \'\';\r\n				}\r\n				$out .= \'</ul>\';\r\n				$colBody[] = td($out);\r\n			} else {\r\n				$colBody[] = td(smd_wu_gTxt(\'no_orphans_found\'));\r\n			}\r\n		}\r\n		echo tr(join(\" \", $colHead));\r\n		echo tr(join(\" \", $colBody));\r\n	}\r\n	echo endTable();\r\n}\r\n\r\n// -------------------------------------------------------------\r\n// Plugin-specific replacement strings - localise as required\r\n// -------------------------------------------------------------\r\nfunction smd_wu_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'admin_plugins\' => \' [ A+P ]\',\r\n		\'public_plugins\' => \' [ P ]\',\r\n		\'exclusion\' => \'Exclude\',\r\n		\'inclusion\' => \'Include\',\r\n		\'filter\' => \'Filter:\',\r\n		\'match_case\' => \'Match case\',\r\n		\'no_orphans_found\' => \'No orphans found.\',\r\n		\'orphan_results\' => \'Possible orphans\',\r\n		\'prefs_link\' => \'Article search fields\',\r\n		\'search_lbl\' => \'Find:\',\r\n		\'search_where_lbl\' => \'Look at:\',\r\n		\'stylesheets\' => \'Stylesheets\',\r\n		\'smd_wu\' => \'Where used\',\r\n		\'whole_word\' => \'Whole words\',\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', '7deec62596fd41cc12a55b18c4ae9cbf', '', '', 4, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_xml', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.40', 'Extract any XML/feed info and reformat it', '<div id=\"smd_help\">\n\n	<h1>smd_xml</h1>\n\n	<p>Yank bits out of any hunk of <span class=\"caps\">XML</span> and reformat it to your own needs. Great for pulling feed info into your Textpattern site, for example from delicious.com.</p>\n\n	<h2>Features</h2>\n\n	<ul>\n		<li>Specify your <span class=\"caps\">XML</span> data from any <span class=\"caps\">URL</span> &#8212; internal or external to <span class=\"caps\">TXP</span> &#8212; or from a string</li>\n		<li>Optionally process <span class=\"caps\">XML</span> data using <span class=\"caps\">XSLT</span></li>\n		<li>Selectively extract any items in your record set</li>\n		<li>Use a Form or the plugin container to output data you have extracted</li>\n		<li><span class=\"caps\">XML</span> tag attributes are available as well</li>\n		<li>Supports pagination of results with limit/offset</li>\n	</ul>\n\n	<h2 id=\"author\">Author</h2>\n\n	<p><a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. For other software by me, or to make a donation, see the <a href=\"http://stefdawson.com/sw\">software page</a>.</p>\n\n	<h2 id=\"install\">Installation / Uninstallation</h2>\n\n	<p class=\"required\">Requires <span class=\"caps\">PHP</span> 5.2+ (and the <span class=\"caps\">SOAP</span> extension for <span class=\"caps\">SOAP</span> data feeds)</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1138/smd_xml\">textpattern.org</a>, or the software page above, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=32718\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To remove the plugin, simply delete it from the Admin-&gt;Plugins tab.</p>\n\n	<h2>Tag: smd_xml</h2>\n\n	<p>Place a <code>&lt;txp:smd_xml&gt;</code> tag where you wish to process <span class=\"caps\">XML</span> data &#8212; this could be from a feed. Since this plugin is best explained by example, assume the following <span class=\"caps\">XML</span> document is presented to the plugin:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;employees&gt;\n   &lt;employee&gt;\n      &lt;name id=&quot;wile_e_coyote&quot;&gt;Wile E. Coyote&lt;/name&gt;\n      &lt;job_title&gt;Schemer&lt;/job_title&gt;\n      &lt;dept&gt;ACME corp&lt;/dept&gt;\n      &lt;quality&gt;Cunning&lt;/quality&gt;\n      &lt;quality&gt;Deviousness&lt;/quality&gt;\n      &lt;quality&gt;Persistence&lt;/quality&gt;\n      &lt;inventions&gt;\n         &lt;name&gt;ACME Rocket Sled&lt;/name&gt;\n         &lt;name&gt;ACME Super Cannon&lt;/name&gt;\n         &lt;name&gt;ACME Jetpack&lt;/name&gt;\n      &lt;/inventions&gt;\n   &lt;/employee&gt;\n   &lt;employee&gt;\n      &lt;name id=&quot;road_runner&quot;&gt;Road Runner&lt;/name&gt;\n      &lt;job_title&gt;Seed expert&lt;/job_title&gt;\n      &lt;dept&gt;Evasion&lt;/dept&gt;\n      &lt;quality&gt;Speed&lt;/quality&gt;\n      &lt;quality&gt;Meep meep&lt;/quality&gt;\n   &lt;/employee&gt;\n&lt;/employees&gt;\n</code></pre>\n\n	<p>Use the following attributes to configure the smd_xml plugin (shaded attributes are mandatory) :</p>\n\n	<h3>Data import attributes</h3>\n\n	<dl>\n		<dt><span class=\"atnm mand\">data</span></dt>\n		<dd>The <span class=\"caps\">XML</span> data source. Most of the time this will be a <span class=\"caps\">URL</span>, though you could hard-code the <span class=\"caps\">XML</span> data to use another <span class=\"caps\">TXP</span> tag here (e.g. <code>&lt;txp:variable /&gt;</code>).</dd>\n		<dt><span class=\"atnm mand\">record</span></dt>\n		<dd>The name of the <span class=\"caps\">XML</span> tag that surrounds each record of data in your feed. Thus you would need <code>record=&quot;employee&quot;</code> in the above document.</dd>\n		<dt><span class=\"atnm\">fields</span></dt>\n		<dd>List of <span class=\"caps\">XML</span> nodes you want to extract from each record. For example, <code>fields=&quot;name, dept&quot;</code>.</dd>\n		<dd>Each field you specify here will create a similarly-named <a href=\"#reps\">replacement tag</a> that you may use in your form/container to display the relevant piece(s) of data. In this case, <code>{name}</code> and <code>{dept}</code> would be available in your output.</dd>\n		<dd>You may extract multiple copies of the same field by separating the name of the field&#8217;s copy with <code>param_delim</code>. For example: <code>fields=&quot;pubDate, title|url_ttl, id, link&quot;</code> would extract title twice: once as <code>{title}</code> and again as <code>{url_ttl}</code>. See <a href=\"#eg6\">example 6</a> for a practical application.</dd>\n		<dd>Finally, you can extract specific items based on their hierarchy in the tree. For example, if you specified <code>field=&quot;name&quot;</code> on the above document you would retrieve the concatenation of the employee and invention &#8216;name&#8217; nodes. If you wanted to only extract the names of the inventions, you would specify <code>field=&quot;inventions-&gt;name&quot;</code>. Similarly, if you only wanted the employee name you would use <code>field=&quot;employee-&gt;name&quot;</code>. Chain nodes together with as many <code>-&gt;</code> connectors as necessary to suit your <span class=\"caps\">XML</span> stream.</dd>\n		<dt><span class=\"atnm\">datawrap</span></dt>\n		<dd>Sometimes the incoming <span class=\"caps\">XML</span> document is just a series of records without any container. This can cause the plugin to get confused under certain circumstances. If you find this happening, use this attribute to manually wrap your data in the given <span class=\"caps\">XML</span> tag. e.g. <code>datawrap=&quot;my_records&quot;</code> would wrap the data stream with <code>&lt;my_records&gt; ... &lt;/my_records&gt;</code> tags.</dd>\n		<dd>This attribute is also used as the default <span class=\"caps\">SOAP</span> wrapper.</dd>\n		<dt><span class=\"atnm\">load_atts</span></dt>\n		<dd>When field attributes are detected they can be made available either when the start tag is encountered, or when the corresponding end tag is found. Options:\n	<dl>\n		<dd><strong>start</strong></dd>\n		<dd><strong>end</strong></dd>\n	</dl></dd>\n		<dd>Default: <code>start</code></dd>\n		<dt><span class=\"atnm\">match</span></dt>\n		<dd>Consider nodes if its data matches this given regular expression. Specify as many matches as you like, each separated by <code>delim</code>. A match must comprise two elements:\n	<dl>\n		<dd>The tag name to consider.</dd>\n		<dd>The full regular expression (including delimiters) to compare the data in that tag against.</dd>\n	</dl></dd>\n		<dt><span class=\"atnm\">skip</span></dt>\n		<dd>List of <span class=\"caps\">XML</span> nodes you want to skip over in each record. Useful if a field you wish to extract is used in two places in the same record. See <a href=\"#eg2\">example 2</a> for a practical application.</dd>\n		<dt><span class=\"atnm\">defaults</span></dt>\n		<dd>List of default values you wish to set if any <code>fields</code> are not set in your document. Specify defaults in pairs of entries like this: <code>defaults=&quot;field|default, field|default, ...&quot;</code>.</dd>\n		<dd>The pipe can be altered with <code>param_delim</code>.</dd>\n		<dt><span class=\"atnm\">set_empty</span></dt>\n		<dd>Any fields that are not set in your document will normally mean that you&#8217;ll see the raw <code>{replacement tag}</code> in your output. Use <code>set_empty=&quot;1&quot;</code> to ensure that all empty nodes are set to an empty value. Any <code>defaults</code> you specify will take precedence over empties.</dd>\n		<dt><span class=\"atnm\">cache_time</span></dt>\n		<dd>If set, the <span class=\"caps\">XML</span> document is cached in the <span class=\"caps\">TXP</span> prefs. Subsequent calls to smd_xml (e.g. refreshing the page) will read the cached information instead of hitting the <code>data</code> <span class=\"caps\">URL</span>, thus cutting down on network traffic.</dd>\n		<dd>After <code>cache_time</code> (specified in seconds) has elapsed, the next page refresh will cause the document to be fetched from the <code>data</code> <span class=\"caps\">URL</span> again. You may, however, force a refresh from the data <span class=\"caps\">URL</span> at any time by adding <code>&amp;force_read=1</code> to the browser <span class=\"caps\">URL</span> (you can use smd_prefalizer and search for &#8216;smd_xml&#8217; to find the cached documents &#8212; each is referenced by its unique ID)</dd>\n	</dl>\n\n	<h3>Manipulation attributes</h3>\n\n	<dl>\n		<dt><strong>kill_spaces</strong></dt>\n		<dd>Remove all inter-tag whitespace, newlines and tabs, i.e. redundant spaces surrounding the tags in the stream. It does not touch spaces within nodes.</dd>\n		<dd>Although optional, this attribute is <strong>highly</strong> recommended as it has the side effect of usually speeding up the parsing process. It does, however, make the feed very difficult to read as it squishes it all up on one line. So consider turning this off if you are debugging. Options:\n	<dl>\n		<dd>0: no, keep inter-tag spaces in the feed</dd>\n		<dd>1: yes, remove them</dd>\n	</dl></dd>\n		<dd>Default: 1</dd>\n		<dt><span class=\"atnm\">transform</span></dt>\n		<dd>Perform tranformations to the raw data stream. The transformations occur prior to the data being cached so the results are cached as well. Specify as many transformations as you like, each separated by <code>delim</code>. Each transformation is broken down into a class (type) and a list of parameters for that class, all separated by <code>param_delim</code>. You can choose from the following classes of transform:\n	<dl>\n		<dd><strong>xsl</strong>: the second parameter is the <span class=\"caps\">URL</span> of the <span class=\"caps\">XSL</span> stylesheet to fetch, e.g. <code>transform=&quot;xsl|http://site.com/path/to/stylesheet.xslt&quot;</code>.</dd>\n		<dd><strong>replace</strong>: swap portions of the document that match the (full, including delimiters) regular expression given in the second parameter with the value given in the third. If the third parameter is omitted, the matching content is removed. e.g. <code>transform=&quot;replace|%&lt;xs:schema.+?&lt;\\/xs:schema&gt;%&quot;</code>.</dd>\n	</dl></dd>\n		<dt><span class=\"atnm\">format</span></dt>\n		<dd>Alter the format of this list of fields. For each field, specify items separated by <code>param_delim</code>: The first is the name of the field you want to alter; The 2nd is the type of alteration required; The 3rd|4th|5th|.. specify how you want to alter the data. The following data types are supported:\n	<dl>\n		<dd><span class=\"atnm\">case</span> : alter the case of the field. The items may be cumulative. Choose from four options as the third, fourth, etc parameters:\n	<dl>\n		<dd><strong>upper</strong></dd>\n		<dd><strong>lower</strong></dd>\n		<dd><strong>ucfirst</strong></dd>\n		<dd><strong>ucwords</strong></dd>\n	</dl></dd>\n		<dd>Example: to first convert the field to lower case then convert the first letter of each word to upper case, use <code>format=&quot;Country|case|lower|ucwords&quot;</code></dd>\n		<dd><span class=\"atnm\">date</span> : takes one argument; the format string as detailed in <a href=\"http://php.net/manual/en/function.strftime.php\">strftime</a>. Example: <code>format=&quot;pubDate|date|%d %B %Y %H:%I:%S&quot;</code> would reformat the pubDate field. Can also be used to reformat time strings.</dd>\n		<dd><span class=\"atnm\">escape</span> : escape the field so special characters are encoded as their <span class=\"caps\">HTML</span> entity values. Options:\n	<dl>\n		<dd><strong>double_quotes</strong>: encode only double quotes (default)</dd>\n		<dd><strong>all_quotes</strong>: encode both double and single quotes</dd>\n		<dd><strong>no_quotes</strong>: don&#8217;t encode any double or single quotes</dd>\n	</dl></dd>\n		<dd><span class=\"atnm\">fordb</span> : harden the field so it can be used in an <span class=\"caps\">SQL</span> statement.</dd>\n		<dd><span class=\"atnm\">link</span> : convert the <span class=\"caps\">URL</span> in this field to an <span class=\"caps\">HTML</span> anchor hyperlink. Example: <code>format=&quot;cat_url|link&quot;</code> (replaces the <code>linkify</code> attribute from the v0.2x plugin versions).</dd>\n		<dd><span class=\"atnm\">sanitize</span> : convert the field into one of three &#8216;dumed down&#8217; formats, as specified by the third parameter. Choose from:\n	<dl>\n		<dd><strong>url</strong> for creating simple, valid <span class=\"caps\">URL</span> strings</dd>\n		<dd><strong>file</strong> for creating valid file names</dd>\n		<dd><strong>url_title</strong> for making <span class=\"caps\">TXP</span>-style <span class=\"caps\">URL</span> titles as governed by your prefs settings</dd>\n	</dl></dd>\n		<dd>Example: <code>format=&quot;Title|sanitize|url&quot;</code> to sanitize the Title field suitable for use in a web address</dd>\n	</dl></dd>\n		<dd><span class=\"caps\">NOTE</span>: format only applies to the form/container content. It is <span class=\"caps\">NOT</span> applicable in <code>ontag</code> Forms. If you wish to apply formatting to ontag attributes, or perform more complicated transformations, consider the smd_wrap plugin.</dd>\n		<dt><span class=\"atnm\">target_enc</span></dt>\n		<dd>Character encoding to apply to the parsed <span class=\"caps\">XML</span> data. Choose from:\n	<dl>\n		<dd><strong><span class=\"caps\">ISO</span>-8859-1</strong></dd>\n		<dd><strong>US-<span class=\"caps\">ASCII</span></strong></dd>\n		<dd><strong><span class=\"caps\">UTF</span>-8</strong></dd>\n	</dl></dd>\n		<dd>Default: <code>UTF-8</code>.</dd>\n		<dt><span class=\"atnm\">uppercase</span></dt>\n		<dd>Set to 1 to force all <span class=\"caps\">XML</span> tag names to be in upper case, thus you would have to specify <code>fields=&quot;NAME, DEPT&quot;</code> in order to successfully extract those fields.</dd>\n		<dt><span class=\"atnm\">concat</span></dt>\n		<dd>Any duplicate nodes in the stream are usually concatenated together. If you wish to turn this feature off so that only the last tag&#8217;s content remains, set <code>concat=&quot;0&quot;</code>.</dd>\n		<dd>Default: 1</dd>\n		<dt><span class=\"atnm\">convert</span></dt>\n		<dd>If your data stream contains data you don&#8217;t want or data that you wish to translate (for example, character entities) you can list them here.</dd>\n		<dd>Items are specified in pairs separated by <code>param_delim</code>; the first is the item to search for and the second is its replacement.</dd>\n		<dd>For example: <code>convert=&quot;&amp;amp;#039|&#39;&quot;</code> would replace all occurrences of <code>&amp;amp;#039</code> with an apostrophe character. Note that the replacements are performed on the raw stream <em>before</em> it is parsed and <em>after</em> it is cached. Also take care when decoding double quotes; this is the correct method: <code>convert=&quot;&amp;amp;quot;|&quot;&quot;&quot;</code> (note the double quote is escaped by putting <em>two</em> double quote characters in)</dd>\n	</dl>\n\n	<h3>Forms and paging attributes</h3>\n\n	<dl>\n		<dt><span class=\"atnm\">form</span></dt>\n		<dd>The Txp Form with which to parse each record. You may use the plugin as a container instead if you prefer.</dd>\n		<dt><span class=\"atnm\">pageform</span></dt>\n		<dd>Optional Txp form used to specify the layout of any paging navigation and statistics such as page number, quantity of records per page, total number of records, etc. See <a href=\"#pgreps\">paging replacement tags</a>.</dd>\n		<dt><span class=\"atnm\">pagepos</span></dt>\n		<dd>The position of the paging information. Options are <code>below</code> (the default), <code>above</code>, or both of them separated by <code>delim</code>.</dd>\n		<dt><span class=\"atnm\">limit</span></dt>\n		<dd>Show this many records per page. Setting a <code>limit</code> smaller than the total number of records switches paging on automatically so you can use the <code>&lt;txp:older /&gt;</code> and <code>&lt;txp:newer /&gt;</code> tags inside your <code>pageform</code> to step through each page of results.</dd>\n		<dd>You may also construct your own paging (see <a href=\"#eg3\">example 3</a>)</dd>\n		<dt><span class=\"atnm\">offset</span></dt>\n		<dd>Skip this many records before outputting the results.</dd>\n		<dd>If you specify a negative <code>offset</code> you start that many records from the end of the document</dd>\n		<dt><span class=\"atnm\">pagevar</span></dt>\n		<dd>If you are putting smd_xml on the same page as a standard article list, the built-in newer and older tags will clash with those of smd_xml; clicking next/prev will step through both your result set and your article list.</dd>\n		<dd>Specify a different variable name here so the two lists can be navigated independently, e.g. <code>pagevar=&quot;xpage&quot;</code>.</dd>\n		<dd>Note that if you change this, you will have to generate your own custom newer/older links (see <a href=\"#eg4\">example 4</a>) and the <a href=\"#smd_xif\">conditional tags</a>.</dd>\n		<dd>There is also a special value <code>SMD_XML_UNIQUE_ID</code> which assigns the tags&#8217; unique ID as the paging variable. See <a href=\"#eg5\">example 5</a> for more.</dd>\n		<dd>Default: <code>pg</code>.</dd>\n		<dt><span class=\"atnm\">ontagstart / ontagend</span></dt>\n		<dd>Under normal operation, each time the plugin encounters a node that matches one of your <code>fields</code> it is extracted and the output stored for display <em>at the end of processing the entire document</em>. Sometimes you might wish to output information on-the-fly as the document is read. This is where <code>ontagstart</code> and its companion <code>ontagend</code> can help.</dd>\n		<dd>Specify as many ontag items as you like, each separated by a comma. Within each ontag item you first specify the name of a Txp Form that will determine what to do or display when the tag is encountered. The remaining items (each separated by <code>param_delim</code>) are the tag names to &#8220;watch&#8221;.</dd>\n		<dd>Whenever one of the given tags is encountered (start of node or end of node depending on which ontag you have chosen) control is immediately passed to the relevant Form.</dd>\n		<dd>Note that you may not use the node&#8217;s data <code>{replacement}</code> value unless using <code>ontagend</code> (because its value has not been discovered at tag start!) You may, however, use any attribute values if you have set <code>load_atts=&quot;start&quot;</code>.</dd>\n		<dd>You canot use the <code>format</code> attribute in your ontag Forms: consider the smd_wrap plugin if you need additional processing.</dd>\n	</dl>\n\n	<h4>Tag/class/formatting attributes</h4>\n\n	<dl>\n		<dt><span class=\"atnm\">wraptag</span></dt>\n		<dd>The <span class=\"caps\">HTML</span> tag, without brackets, to surround each record you output.</dd>\n		<dt><span class=\"atnm\">break</span></dt>\n		<dd>The <span class=\"caps\">HTML</span> tag, without brackets, to surround each field you output.</dd>\n		<dt><span class=\"atnm\">class</span></dt>\n		<dd>The <span class=\"caps\">CSS</span> class name to apply to the <code>wraptag</code>.</dd>\n	</dl>\n\n	<h4>Plugin customisation</h4>\n\n	<dl>\n		<dt><span class=\"atnm\">delim</span></dt>\n		<dd>The delimiter to use between items in the plugin attributes.<br />\nDefault: <code>,</code> (comma).</dd>\n		<dt><span class=\"atnm\">param_delim</span></dt>\n		<dd>The delimiter to use between items in <span class=\"caps\">XML</span> and plugin data attributes.</dd>\n		<dd>Default: <code>|</code> (pipe).</dd>\n		<dt><span class=\"atnm\">concat_delim</span></dt>\n		<dd>The delimiter to use between identically-named tags in the <span class=\"caps\">XML</span> data stream.<br />\nDefault: <code> </code> (space).</dd>\n		<dt><span class=\"atnm\">var_prefix</span></dt>\n		<dd>If you wish to embed an smd_xml tag inside the container of another, the replacement and paging variables might clash. Use this in one of your tags to help prevent this.</dd>\n		<dd>It takes up to two values separated by a comma: the first is the prefix to apply to regular replacement tags; the second is the prefix to apply to page-based replacement tags.</dd>\n		<dd>If only one value is specifed, the same prefix will be applied to both tag and page replacements.</dd>\n		<dd>Default: <code>, smd_xml_</code> (i.e. no tag prefix, and <code>smd_xml_</code> page prefix)</dd>\n		<dt><span class=\"atnm\">timeout</span></dt>\n		<dd>The time in seconds to wait for the remote server to respond before giving up.</dd>\n		<dd>Default: 10</dd>\n		<dt><span class=\"atnm\">transport</span></dt>\n		<dd>(should not be needed) If you would like to force the plugin to use a particular <span class=\"caps\">HTTP</span> transport mechanism to fetch your <code>data</code> you can specify it here. Choose from:\n	<dl>\n		<dd><strong>fsock</strong></dd>\n		<dd><strong>curl</strong></dd>\n		<dd><strong>soap</strong></dd>\n	</dl></dd>\n		<dd>The <code>soap</code> mechanism uses <span class=\"caps\">CURL</span> internally so you must have that available.</dd>\n		<dd>Default: <code>fsock</code>.</dd>\n		<dt><span class=\"atnm\">transport_opts</span></dt>\n		<dd>When using <code>soap</code> transport you often need to pass additional parameters to the <span class=\"caps\">SOAP</span> server. <code>transport_opts</code> takes up to three paramaters, separated by <code>delim</code>:\n	<dl>\n		<dd>Client method: the name of a <span class=\"caps\">SOAP</span> method to call</dd>\n		<dd>Data: a series of name-val pairs (separated by <code>param_delim</code>) or an <span class=\"caps\">XML</span> document which will be passed to the client method. e.g. <code>type|table|user|Bloke|pass|wilecoyote</code> passes three params (type, user, and pass) with corresponding values. Note that if you want to use <span class=\"caps\">XML</span> here you need to declare your intention using the <code>transport_config</code> attribute.</dd>\n		<dd>Result method: the name of a <span class=\"caps\">SOAP</span> method to fetch the output. The first <code>param_delim</code> option is the method name to call to obtain the result set, and the second is the portion of the results you want returned (e.g. <code>any</code>)</dd>\n	</dl></dd>\n		<dt><span class=\"atnm\">transport_config</span></dt>\n		<dd>Allows you to configure how the plugin interacts with the server. The following configuration parameters are available; separate each configuration item from its predecessor using <code>delim</code> and separate any value from its parameter name using <code>param_delim</code> :\n	<dl>\n		<dd><strong>soap_wrap</strong> : the data you pass to the <span class=\"caps\">SOAP</span> server may not be encapsulated in its own unique element. If that&#8217;s the case and the server requires this, you can specify the wrapper here. For example, some servers require <code>soap_wrap|Request</code>.</dd>\n		<dd><strong>soap_delim</strong> : when retrieving multiple <span class=\"caps\">SOAP</span> items, they will be concatenated together using this delimiter. Default: the same delimiter as set in <code>param_delim</code>.</dd>\n		<dd><strong>soap_type_input</strong> : can be either <code>nvpairs</code> (the default, as shown above) or <code>xml</code> if you are passing in a complete <span class=\"caps\">XML</span> document to configure the <span class=\"caps\">SOAP</span> server. When using xml input format, the plugin automatically converts the given <span class=\"caps\">XML</span> document into a <span class=\"caps\">SOAP</span> array.</dd>\n		<dd><strong>soap_type_output</strong> : <span class=\"caps\">SOAP</span> data is normally returned as an <span class=\"caps\">XML</span> document, but if for some reason the server sends back a raw <span class=\"caps\">SOAP</span> array you can use this with an <code>xml</code> parameter to ask the plugin to try and interpret the <span class=\"caps\">SOAP</span> data into an <span class=\"caps\">XML</span> stream for you. The success of this operation is duty bound by how well formed the resulting data is. If using this you may (probably will) also need to specify <code>soap_numeric_wrap</code>.</dd>\n		<dd><strong>soap_numeric_wrap</strong> : when converting a <span class=\"caps\">SOAP</span> array back to <span class=\"caps\">XML</span>, any repeating records are normally indexed starting from 0. Since raw numbers are invalid <span class=\"caps\">XML</span> tag names they need to be altered somehow. By default, this is done by taking the parent class and appending a sequential number to it. If you wish to set any numeric records to a specific wrapper element, specify that element here.</dd>\n	</dl></dd>\n		<dt><span class=\"atnm\">line_length</span></dt>\n		<dd>If you are using the <code>fsock</code> transport mechanism, the plugin grabs the <span class=\"caps\">XML</span> document line by line and uses a maximum line length of 8192 characters by default. This is usually good enough because most feeds contain newlines, but some (e.g. Google Spreadsheet) don&#8217;t have any newlines in them.</dd>\n		<dd>To successfully parse such documents you may need to increase the line length. In these situations, however, it is highly recommended to switch to <code>transport=&quot;curl&quot;</code> instead (if you can) because it does not have any line length restrictions.</dd>\n		<dt><span class=\"atnm\">hashsize</span></dt>\n		<dd>(should not be needed) When specifying a <code>cache_time</code> the plugin assigns a 32-character, unique reference to the current smd_xml based on your import attributes. <code>hashsize</code> governs the mechanism for making this long reference shorter.</dd>\n		<dd>It comprises two numbers separated by a colon; the first is the length of the uniqe ID, the second is how many characters to skip past each time a character is chosen. For example, if the unique_reference was <code>0cf285879bf9d6b812539eb748fbc8f6</code> then <code>hashsize=&quot;6:5&quot;</code> would make a 6-character unique ID using every 5th character; in other words <code>05f898</code>. If at any time, you &#8220;fall off&#8221; the end of the long string, the plugin wraps back to the beginning of the string and continues counting.<br />\nDefault: <code>6:5</code>.</dd>\n	</dl>\n\n	<h3 id=\"reps\">Replacement tags</h3>\n\n	<p>Each <span class=\"caps\">XML</span> field you extract from your data stream has an equivalently-named replacement tag available so you may use it anywhere you like in your Form/container. Although the examples here don&#8217;t demonstrate this, the replacement names will be prefixed by whatever you have set in your <code>var_prefix</code> attribute.</p>\n\n	<p>If you chose to extract <code>fields=&quot;name, job_title, quality&quot;</code> you would have the following replacement tags available during the first record:</p>\n\n	<ul>\n		<li><code>{name}</code> : Wile E. Coyote (+ the names of the Inventions)</li>\n		<li><code>{name|id}</code> : wile_e_coyote</li>\n		<li><code>{job_title}</code> : Schemer</li>\n		<li><code>{quality}</code> : Cunning Deviousness Persistence</li>\n	</ul>\n\n	<p>And during the second record, the same replacement tag names would refer to the following items:</p>\n\n	<ul>\n		<li><code>{name}</code> : Road Runner</li>\n		<li><code>{name|id}</code> : road_runner</li>\n		<li><code>{job_title}</code> : Seed expert</li>\n		<li><code>{quality}</code> : Speed Meep meep</li>\n	</ul>\n\n	<p>Note that the attribute called <code>id</code> that is part of the <code>&lt;name&gt;</code> <span class=\"caps\">XML</span> tag has been extracted and is made available automatically. By default, the names of attributes are defined as <code>{tag|attribute}</code>. The pipe can be altered using <code>param_delim</code>.</p>\n\n	<p>The <code>{quality}</code> tag appears more than once in the example document above and is thus concatenated by default. You can influence its output using the <code>concat</code> and <code>concat_delim</code> attributes, e.g. using <code>concat_delim=&quot;|&quot;</code> would render the following replacement variable on the first record:</p>\n\n	<ul>\n		<li><code>{quality}</code> : Cunning|Deviousness|Persistence</li>\n	</ul>\n\n	<p>while <code>concat=&quot;0&quot;</code> would render this (i.e. the value of the last node encountered):</p>\n\n	<ul>\n		<li><code>{quality}</code> : Persistence</li>\n	</ul>\n\n	<p>There are also some special statistical tags available in each record:</p>\n\n	<ul>\n		<li><code>{smd_xml_totalrecs}</code> : the total number of records found in your <span class=\"caps\">XML</span> document</li>\n		<li><code>{smd_xml_pagerecs}</code> : the number of records on this page (if not using paging, this is the same as above)</li>\n		<li><code>{smd_xml_pages}</code> : the number of available pages</li>\n		<li><code>{smd_xml_thispage}</code> : the page number of the currently visible page</li>\n		<li><code>{smd_xml_thisrec}</code> : the record number, starting at 1</li>\n		<li><code>{smd_xml_thisindex}</code> : the record number, starting at 0</li>\n		<li><code>{smd_xml_runrec}</code> : the record number, starting at 1 and including any offset</li>\n		<li><code>{smd_xml_runindex}</code> : the record number, starting at 0 and including any offset</li>\n	</ul>\n\n	<h3 id=\"pgreps\">Paging replacement tags</h3>\n\n	<p>In your <code>pageform</code> you can employ any of the following replacement tags to build up a navigation system for stepping through your <span class=\"caps\">XML</span> document. Note that they all show <code>smd_xml_</code> as the prefix here, but that may be changed with the <code>var_prefix</code> attribute:</p>\n\n	<ul>\n		<li><code>{smd_xml_totalrecs}</code> : the total number of records found in your <span class=\"caps\">XML</span> document</li>\n		<li><code>{smd_xml_pagerecs}</code> : the number of records on this page</li>\n		<li><code>{smd_xml_pages}</code> : the number of available pages</li>\n		<li><code>{smd_xml_prevpage}</code> : the page number of the previous page &#8212; empty if on first page</li>\n		<li><code>{smd_xml_thispage}</code> : the page number of the current page</li>\n		<li><code>{smd_xml_nextpage}</code> : the page number of the next page &#8212; empty if on last page</li>\n		<li><code>{smd_xml_rec_start}</code> : the record number of the first record on this page (counted from the start of the record set)</li>\n		<li><code>{smd_xml_rec_end}</code> : the record number of the last record on this page (counted from the start of the record set)</li>\n		<li><code>{smd_xml_recs_prev}</code> : the number of records on the previous page</li>\n		<li><code>{smd_xml_recs_next}</code> : the number of records on the next page</li>\n		<li><code>{smd_xml_unique_id}</code> : the unique reference number assigned to this smd_xml tag (see <a href=\"#eg5\">example 5</a> for usage of this)</li>\n	</ul>\n\n	<h2 id=\"smd_xif\">Tags: <code>&lt;txp:smd_xml_if_prev&gt;</code> / <code>&lt;txp:smd_xml_if_next&gt;</code></h2>\n\n	<p>Use these container tags to determine if there is a next or previous page and take action if so. Can only be used inside <code>pageform</code>, thus all <a href=\"#pgreps\">paging replacement variables</a> are available inside these tags.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_xml_if_prev&gt;Previous page&lt;/txp:smd_xml_if_prev&gt;\n&lt;txp:smd_xml_if_next&gt;Next page&lt;/txp:smd_xml_if_next&gt;\n</code></pre>\n\n	<p>The tags supprt <code>&lt;txp:else /&gt;</code>. See <a href=\"#eg5\">example 5</a> for more.</p>\n\n	<h2>Examples</h2>\n\n	<h3 id=\"eg1\">Example 1: delicious links</h3>\n\n	<p>Swap <code>roadrunner</code> in this code with your delicious username to get your own feed:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_xml data=&quot;http://feeds.delicious.com/v2/rss/roadrunner&quot;\n     record=&quot;item&quot; fields=&quot;title, link, pubDate, description&quot;\n     wraptag=&quot;dl&quot;&gt;\n   &lt;dt&gt;&lt;a href=&quot;{link}&quot;&gt;{title}&lt;/a&gt;&lt;/dt&gt;\n   &lt;dd&gt;Posted: {pubDate}&lt;br /&gt;{description}&lt;/dd&gt;\n&lt;/txp:smd_xml&gt;\n</code></pre>\n\n	<h3 id=\"eg2\">Example 2: twitter feed</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_xml\n     data=&quot;http://twitter.com/statuses/user_timeline/textpattern.xml&quot;\n     record=&quot;status&quot; fields=&quot;id, text, created_at&quot; skip=&quot;user&quot;\n     wraptag=&quot;ul&quot; format=&quot;text|link&quot;&gt;\n   &lt;li&gt;\n      &lt;a href=&quot;http://twitter.com/textpattern/statuses/{id}&quot;&gt;\n         {created_at}\n      &lt;/a&gt;\n      &lt;br /&gt;{text}\n   &lt;/li&gt;\n&lt;/txp:smd_xml&gt;\n</code></pre>\n\n	<p>Notice that we <code>skip</code> the whole <em>user</em> block in the <span class=\"caps\">XML</span> data stream. This is for two reasons:</p>\n\n	<ol>\n		<li>it is redundant information that appears in every record &#8212; we already know to which user the feed belongs because they&#8217;re all from the same user</li>\n		<li><em>created_at</em> is used inside the user block as well as in the outer status block so we get two datestamps, which is not what we want (if we simply used <code>concat=&quot;0&quot;</code> to only grab one of the created_at entries, the last one would prevail &#8212; the one from the user block)</li>\n	</ol>\n\n	<h3 id=\"eg3\">Example 3: limit and paging</h3>\n\n	<p>Viewing the I Love <span class=\"caps\">TXP</span> feed 3 records at a time. Note that since the site is not updated frequently, the cache_time of 86400 seconds (1 day) is ample to avoid hammering the network:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_xml\n     data=&quot;http://feeds.feedburner.com/welovetxp&quot;\n     record=&quot;item&quot; fields=&quot;title,description, link, pubDate&quot;\n     wraptag=&quot;ul&quot; limit=&quot;3&quot; pageform=&quot;pager&quot;\n     cache_time=&quot;86400&quot;&gt;\n   &lt;li&gt;\n      &lt;a href=&quot;{link}&quot;&gt;\n         {title}\n      &lt;/a&gt;&lt;span class=&quot;published&quot;&gt;{pubDate}&lt;/span&gt;\n      &lt;br /&gt;{description}\n   &lt;/li&gt;\n&lt;/txp:smd_xml&gt;\n</code></pre>\n\n	<p>And in form <code>pager</code>:</p>\n\n<pre class=\"block\"><code class=\"block\">Page {smd_xml_thispage} of {smd_xml_pages}\n&lt;txp:newer&gt;Previous page&lt;/txp:newer&gt;\n&lt;txp:older&gt;Next page&lt;/txp:older&gt;\n</code></pre>\n\n	<p>If you wanted to view the last three entries in the feed instead of the first three, you could set <code>offset=&quot;-3&quot;</code>.</p>\n\n	<h3 id=\"eg4\">Example 4: using <code>pagevar</code></h3>\n\n	<p>Adding <code>pagevar=&quot;xmlpg&quot;</code> to example 3 allows paging independently of txp:older and txp:newer tags. You then need to build your own links in your <code>pager</code> form, like this:</p>\n\n<pre class=\"block\"><code class=\"block\">Page {smd_xml_thispage} of {smd_xml_pages} |\n   Showing records {smd_xml_rec_start} to {smd_xml_rec_end}\n   of {smd_xml_totalrecs} |\n  &lt;a href=&quot;?xmlpg={smd_xml_prevpage}&quot;&gt;Previous {smd_xml_recs_prev}&lt;/a&gt;\n  &lt;a href=&quot;?xmlpg={smd_xml_nextpage}&quot;&gt;Next {smd_xml_recs_next}&lt;/a&gt;\n</code></pre>\n\n	<p>That creates links to next and previous record sets using the assigned <code>pagevar</code> as the <span class=\"caps\">URL</span> parameter.</p>\n\n	<h3 id=\"eg5\">Example 5: conditional navigation and the unique ID</h3>\n\n	<p>Again using example 3, if you used <code>pagevar=&quot;SMD_XML_UNIQUE_ID&quot;</code> the pagevar would be assigned the value <code>f290b8</code>. In this case we could use it like this:</p>\n\n<pre class=\"block\"><code class=\"block\">Page {smd_xml_thispage} of {smd_xml_pages} |\n   Showing records {smd_xml_rec_start} to {smd_xml_rec_end}\n   of {smd_xml_totalrecs} |\n&lt;txp:smd_xml_if_prev&gt;\n  &lt;a href=&quot;?{smd_xml_unique_id}={smd_xml_prevpage}&quot;&gt;Previous {smd_xml_recs_prev}&lt;/a&gt;\n&lt;/txp:smd_xml_if_prev&gt;\n&lt;txp:smd_xml_if_next&gt;\n  &lt;a href=&quot;?{smd_xml_unique_id}={smd_xml_nextpage}&quot;&gt;Next {smd_xml_recs_next}&lt;/a&gt;\n&lt;/txp:smd_xml_if_next&gt;\n</code></pre>\n\n	<p>Note that we are using the conditional tags to only display the next and previous links if the next/prev page exists and also that the <span class=\"caps\">URL</span> link is generated using <code>{smd_xml_unique_id}</code>. You could conceivably use this same pageform on more than one <span class=\"caps\">XML</span> feed on the same page and navigate the two feeds indpendently, though you would have to work out a clever way of amalgamating the <span class=\"caps\">URL</span> vars (perhaps using the adi_gps plugin).</p>\n\n	<h3 id=\"eg6\">Example 6: inserting <span class=\"caps\">XML</span> data into <span class=\"caps\">TXP</span></h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_xml data=&quot;http://feeds.delicious.com/v2/rss/roadrunner&quot;\n     record=&quot;item&quot; fields=&quot;title|utitle, link, pubDate, description, category&quot;\n     format=&quot;pubDate|date|%Y-%m-%d %H:%I:%S,\n     description|fordb, title|fordb, utitle|sanitize|url_title&quot;&gt;\n   &lt;txp:smd_query query=&quot;INSERT INTO textpattern\n     SET Posted=&#39;{pubDate}&#39;, LastMod=NOW(),\n     url_title=&#39;{utitle}&#39;,\n     Title=&#39;{title}&#39;, custom_3=&#39;{link}&#39;,\n     Body=&#39;{description}&#39;, Body_html=&#39;{description}&#39;,\n     Section=&#39;links&#39;, Category1=&#39;delicious&#39;,\n     keywords=&#39;{category}&#39;&quot; /&gt;\n&lt;/txp:smd_xml&gt;\n</code></pre>\n\n	<p>This example takes a delicious feed, reformats the various entries and inserts them into the textpattern table in a dedicated section. Note that the date format is altered and the feed&#8217;s title is converted to a sanitized <span class=\"caps\">TXP</span> <span class=\"caps\">URL</span> suitable for the url_title field.</p>\n\n	<h2>Credits</h2>\n\n	<p>This plugin would not have been possible without the tireless help from those community members willing to test my flaky beta code as I strive to make the plugin work across as many types of feed as possible. Special mentions, in no particular order, go to oliverker, aslsw66, tye, jakob, Mats, and Destry.</p>\n\n	<h2 class=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>02 Jan 2010 | 0.10 | Initial release</li>\n		<li>03 Jan 2010 | 0.20 | Added cache support (thanks variaas) ; added <code>limit</code>, <code>offset</code> and paging features ; added <code>linkify</code> (thanks Jaro)</li>\n		<li>05 Jan 2010 | 0.21 | Supports https:// feeds (thanks photonomad) ; added <code>transport</code>, <code>defaults</code> and <code>set_empty</code> attributes</li>\n		<li>13 Jan 2010 | 0.22 | Added <code>line_length</code> (thanks nardo)</li>\n		<li>17 Jan 2010 | 0.30 | Enabled <span class=\"caps\">URL</span> params to be passed in the <code>data</code> attribute ; added <code>format</code> ; deprecated <code>linkify</code> ; <code>param_delim</code> default is now pipe</li>\n		<li>03 Apr 2012 | 0.40 | Improved feed support and tag detection for more varied / complicated feeds ; added <span class=\"caps\">XML</span>-over-<span class=\"caps\">FTP</span> support (thanks aslsw66) ; added <span class=\"caps\">SOAP</span> transport facility, <code>transport_opts</code> and <code>transport_config</code> attributes ; added <span class=\"caps\">XSL</span> and regex transform support ; allowed <code>sub-&gt;field</code> support and added <code>match</code>, <code>ontagstart</code>, <code>ontagend</code> and <code>load_atts</code> for finer control over field extraction ; added <code>datawrap</code>, <code>var_prefix</code> and <code>timeout</code> attributes ; added record attribute support (thanks Mats) ; fixed mangled date field bug ; fixed attributes-in-record-entry limit bug and undesired ontag output (both thanks tye) ; changed <code>format</code>&#8217;s <code>escape</code> attribute to <code>fordb</code> (<code>escape</code> is now for <code>htmlspecialchars()</code>) ; added <code>kill_spaces</code> so inter-tag whitespace removal is optional (but highly recommended) ; added <code>tag_delim</code> (thanks MattD)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_xml\r\n *\r\n * A Textpattern CMS plugin for reading and parsing XML data:\r\n *  -> Read XML stream from URL, string or (experimental) SOAP client. XSL transform available\r\n *  -> Specify field hierarchy to extract specific parts of the data stream\r\n *  -> Optionally format data items prior to output\r\n *  -> Cacheing supported to ease server load\r\n *  -> Results can be paged\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n//      --> JSON support?\r\n//      -->   Some way of grouping items, maybe via concat. e.g. instead of assigning output directly to $out[], add the group to a pseudo-tag in $xmldata,\r\n//      -->     e.g. concat=\"2|Measure=>My_Measurements\" will make {My_Measurements} available in the container which will contain the concatenated output of all (possibly processed with ontag) Measure tags\r\n//     Allow fields=\"NodeWrapper->*\" to specify that you want all sub-nodes to be included in the replacement stream\r\n//      -->  Perhaps some mechanism to specify the number of levels to go down as well, e.g.:\r\n//      -->    NodeWrapper->*    =  all sub-nodes\r\n//      -->    NodeWrapper->*1   =  1st-child sub-nodes\r\n//      -->    NodeWrapper->*2   =  2nd-child sub-nodes\r\n//      -->    NodeWrapper->*1+2 =  1st and 2nd-child sub-nodes\r\n//      -->    NodeWrapper->*1-4 =  1st thru 4th-child sub-nodes\r\nfunction smd_xml($atts, $thing=NULL) {\r\n	global $pretext, $thispage, $smd_xml_pginfo;\r\n\r\n	extract(lAtts(array(\r\n		\'data\'             => \'\',\r\n		\'datawrap\'         => \'\',\r\n		\'record\'           => \'\',\r\n		\'fields\'           => \'\',\r\n		\'skip\'             => \'\',\r\n		\'match\'            => \'\',\r\n		\'timeout\'          => 10,\r\n		\'transform\'        => \'\',\r\n		\'kill_spaces\'      => 1,\r\n		\'ontagstart\'       => \'\',\r\n		\'ontagend\'         => \'\',\r\n		\'load_atts\'        => \'start\', // (tag) start or end\r\n		\'uppercase\'        => \'0\',\r\n		\'convert\'          => \'\', // search|replace, search|replace, ...\r\n		\'target_enc\'       => \'UTF-8\',\r\n		\'defaults\'         => \'\',\r\n		\'set_empty\'        => \'0\',\r\n		\'format\'           => \'\',\r\n		\'form\'             => \'\',\r\n		\'pageform\'         => \'\',\r\n		\'pagevar\'          => \'pg\',\r\n		\'pagepos\'          => \'below\',\r\n		\'delim\'            => \',\',\r\n		\'param_delim\'      => \'|\',\r\n		\'tag_delim\'        => \'|\',\r\n		\'concat\'           => \'1\',\r\n		\'concat_delim\'     => \' \',\r\n		\'transport\'        => \'\',\r\n		\'transport_opts\'   => \'\',\r\n		\'transport_config\' => \'\',\r\n		\'cache_time\'       => \'3600\', // in seconds\r\n		\'hashsize\'         => \'6:5\',\r\n		\'line_length\'      => \'8192\',\r\n		\'var_prefix\'       => \', smd_xml_\',\r\n		\'limit\'            => 0,\r\n		\'offset\'           => 0,\r\n		\'wraptag\'          => \'\',\r\n		\'break\'            => \'\',\r\n		\'class\'            => \'\',\r\n		\'debug\'            => \'0\',\r\n	), $atts));\r\n\r\n	// This constant is only available in PHP 5.4+ so fake it for earlier versions\r\n	// and take the hit on them not knowing what the flag means\r\n	if (!defined(\'ENT_XML1\')) {\r\n		define(\'ENT_XML1\', 16);\r\n	}\r\n\r\n	$src = \'\';\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$soap_wrapped = false;\r\n\r\n	if (empty($data)) {\r\n		trigger_error(\"smd_xml requires a data source\", E_USER_ERROR);\r\n		return;\r\n	}\r\n	if (empty($record)) {\r\n		trigger_error(\"smd_xml requires a record name within your data stream\", E_USER_ERROR);\r\n		return;\r\n	}\r\n\r\n	// Work out where the paging info is to appear\r\n	$pagebit = $rowinfo = array();\r\n	if ($pageform) {\r\n		$pagePosAllowed = array(\"below\", \"above\");\r\n		$pageform = fetch_form($pageform);\r\n		$pagepos = str_replace(\'smd_\', \'\', $pagepos); // For convenience\r\n		$pagepos = do_list($pagepos, $delim);\r\n		foreach ($pagepos as $pageitem) {\r\n			$pagebit[] = (in_array($pageitem, $pagePosAllowed)) ? $pageitem : $pagePosAllowed[0];\r\n		}\r\n	}\r\n\r\n	$target_enc = (in_array($target_enc, array(\'ISO-8859-1\', \'US-ASCII\', \'UTF-8\'))) ? $target_enc : \'UTF-8\';\r\n\r\n	// Extract the prefixes\r\n	$prefixes = do_list($var_prefix);\r\n	$tag_prefix = $prefixes[0];\r\n	$page_prefix = isset($prefixes[1]) ? $prefixes[1] : $tag_prefix;\r\n\r\n	// Make a unique hash value for this instance so the XML document can be cached in txp_prefs\r\n	$uniq = \'\';\r\n	$md5 = md5($data.$record.$fields);\r\n	list($hashLen, $hashSkip) = explode(\':\', $hashsize);\r\n	for ($idx = 0, $cnt = 0; $cnt < $hashLen; $cnt++, $idx = (($idx+$hashSkip) % strlen($md5))) {\r\n		$uniq .= $md5[$idx];\r\n	}\r\n\r\n	$var_lastmod = \'smd_xml_lmod_\'.$uniq;\r\n	$var_data = \'smd_xml_data_\'.$uniq;\r\n	$lastmod = get_pref($var_lastmod, 0);\r\n	$read_cache = (($cache_time > 0) && ((time() - $lastmod) < $cache_time)) ? true : false;\r\n	$crush = function_exists(\'gzcompress\') && function_exists(\'gzuncompress\');\r\n	$pagevar = ($pagevar == \'SMD_XML_UNIQUE_ID\') ? $uniq : $pagevar;\r\n\r\n	// Cached document is gzipped and then (yuk!) base64\'d if zlib is compiled in.\r\n	// Would prefer to store binary data directly but trying to insert it into a txp_prefs\r\n	// text field always gives problems on insertion and/or retrieval\r\n	if ($read_cache) {\r\n		if ($debug > 1) {\r\n			trace_add (\'[smd_xml reading cache \'.$var_data.\']\');\r\n		}\r\n		$src = $crush ? gzuncompress(base64_decode(get_pref($var_data))) : get_pref($var_data);\r\n	} else {\r\n		if ((strpos($data, \'http:\') === 0) || (strpos($data, \'https:\') === 0) || (strpos($data, \'ftp:\') === 0)) {\r\n			// The data is to be fetched from a URL.\r\n			// Has the transport mechanism been specified? If not, choose one\r\n			if (!$transport) {\r\n				if ( function_exists(\'curl_version\') ) {\r\n					$transport = \'curl\';\r\n				} else if ( function_exists(\'fsockopen\') ) {\r\n					$transport = \'fsock\';\r\n				} else {\r\n					$transport = \'\';\r\n				}\r\n			}\r\n\r\n			switch ($transport) {\r\n				case \'curl\':\r\n					$src = smd_xml_curl($data, $timeout);\r\n				break;\r\n				case \'fsock\':\r\n					$url = parse_url($data);\r\n					switch ($url[\'scheme\']) {\r\n						case \'https\':\r\n							$url[\'scheme\'] = \'ssl://\';\r\n							$url[\'port\'] = 443;\r\n						break;\r\n						case \'ftp\':\r\n							$url[\'scheme\'] = \'\';\r\n							$url[\'port\'] = 21;\r\n						break;\r\n						case \'http\':\r\n						default:\r\n							$url[\'scheme\'] = \'\';\r\n							$url[\'port\'] = 80;\r\n					}\r\n					$fp = fsockopen ($url[\'scheme\'] . $url[\'host\'], $url[\'port\'], $errno, $errstr, $timeout);\r\n\r\n					$qry = \'GET \'.$url[\'path\'] . ((isset($url[\'query\'])) ? \'?\'.$url[\'query\'] : \'\');\r\n					$qry .= \" HTTP/1.0\\r\\n\";\r\n					$qry .= \"Host: \".$url[\'host\'].\"\\r\\n\";\r\n					$qry .= \"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\\r\\n\\r\\n\"; // *shrug*\r\n\r\n					fputs($fp, $qry);\r\n					stream_set_timeout($fp, $timeout);\r\n					$info = stream_get_meta_data($fp);\r\n\r\n					$hdrs = true;\r\n					while ((!feof($fp)) && (!$info[\'timed_out\'])) {\r\n						$line = fgets($fp, $line_length);\r\n						$line = preg_replace(\"[\\r\\n]\", \"\", $line);\r\n						if ($hdrs == false) {\r\n							$src .= $line.\"\\n\";\r\n						}\r\n						if (strlen($line) == 0) $hdrs = false;\r\n					}\r\n					if ($info[\'timed_out\']) {\r\n						$src = \'\';\r\n					}\r\n					fclose($fp);\r\n				break;\r\n				case \'soap\':\r\n					ini_set(\'soap.wsdl_cache_enabled\', \'0\');\r\n					if (class_exists(\'SoapClient\')) {\r\n						$url = $data;\r\n						$url .= (stripos($url, \'?WSDL\') === false) ? \'?WSDL\' : \'\';\r\n\r\n						$client = \'\';\r\n\r\n						try {\r\n							$client = new SoapClient($url);\r\n						} catch (SoapFault $E) {\r\n							echo $E->faultstring;\r\n							$src = \'\';\r\n						}\r\n\r\n						if ($client) {\r\n							// Use explode() because do_list() removes spaces\r\n							$transport_config = explode($delim, $transport_config);\r\n							$tcfg = array();\r\n							foreach ($transport_config as $transopt) {\r\n								$topts = explode($param_delim, $transopt);\r\n								if (count($topts) == 2) {\r\n									$tcfg[$topts[0]] = $topts[1];\r\n								}\r\n							}\r\n\r\n							$tcfg[\'soap_delim\'] = isset($tcfg[\'soap_delim\']) ? $tcfg[\'soap_delim\'] : $param_delim;\r\n							$tcfg[\'soap_type_input\'] = isset($tcfg[\'soap_type_input\']) ? $tcfg[\'soap_type_input\'] : \'nvpairs\';\r\n							$tcfg[\'soap_type_output\'] = isset($tcfg[\'soap_type_output\']) ? $tcfg[\'soap_type_output\'] : \'\';\r\n							$tcfg[\'soap_numeric_wrap\'] = isset($tcfg[\'soap_numeric_wrap\']) ? $tcfg[\'soap_numeric_wrap\'] : $record;\r\n\r\n							$transItems = do_list($transport_opts, $delim);\r\n							$cliFn = $transItems[0];\r\n							$params = array();\r\n\r\n							switch ($tcfg[\'soap_type_input\']) {\r\n								case \'nvpairs\':\r\n									$param = do_list($transItems[1], $param_delim);\r\n									$num = (count($param) / 2);\r\n									for ($idx = 0; $idx < $num; $idx++) {\r\n										$key = array_shift($param);\r\n										$val = array_shift($param);\r\n										$params[$key] = $val;\r\n									}\r\n								break;\r\n								case \'xml\':\r\n									$doc = new DOMDocument();\r\n									$doc->preserveWhiteSpace = false;\r\n									$doc->loadXML($transItems[1]);\r\n									$params = smd_xml_to_array($doc->documentElement);\r\n									if (isset($tcfg[\'soap_wrap\'])) {\r\n										$params[$tcfg[\'soap_wrap\']] = $params;\r\n									}\r\n								break;\r\n							}\r\n							$resParts = do_list($transItems[2], $param_delim);\r\n							$resFn = array_shift($resParts);\r\n\r\n							$result = \'\';\r\n							try {\r\n							   $result = $client->$cliFn($params);\r\n							} catch (SoapFault $E) {\r\n							   echo $E->faultstring;\r\n							}\r\n\r\n							if ($result) {\r\n								if ($resFn) {\r\n									try {\r\n										$src = $result->$resFn;\r\n										if ($resParts) {\r\n											$srcbobs = array();\r\n											foreach ($resParts as $resIdx) {\r\n												$srcbobs[] = $src->$resIdx;\r\n											}\r\n\r\n											$src = join($tcfg[\'soap_delim\'], $srcbobs);\r\n										}\r\n										switch ($tcfg[\'soap_type_output\']) {\r\n											case \'xml\':\r\n												// Oh lordy me -- just to convert stdClass:: format to associative array\r\n												$xmlbld = new smd_xml_build_data(json_decode(json_encode($src), true), $datawrap, $tcfg[\'soap_numeric_wrap\']);\r\n												$soap_wrapped = true;\r\n												$src = $xmlbld->getData();\r\n											break;\r\n										}\r\n									} catch (SoapFault $E) {\r\n									   echo $E->faultstring;\r\n									}\r\n								} else {\r\n									$src = $result;\r\n								}\r\n							} else {\r\n								$src = \'\';\r\n							}\r\n						}\r\n					} else {\r\n						$src = \'\';\r\n					}\r\n				break;\r\n				default:\r\n					$src = \'\';\r\n			}\r\n		} else  {\r\n			// Assume data is presented in raw XML\r\n			$src = $data;\r\n		}\r\n	}\r\n\r\n	// Remove inter-tag whitespace: highly recommended, but makes the feed less easy to debug\r\n	// so you may elect to turn it off while testing\r\n	if ($kill_spaces) {\r\n		$src = preg_replace(\"/>\".\"[[:space:]]+\".\"</i\", \"><\", $src);\r\n	}\r\n\r\n	// Perform transformations on the fetched source\r\n	if ($transform) {\r\n		$xforms = do_list($transform, $delim);\r\n		foreach ($xforms as $xform) {\r\n			$tops = do_list($xform, $param_delim);\r\n			$ttyp = array_shift($tops);\r\n			switch ($ttyp) {\r\n				case \'xsl\':\r\n					$xsl = smd_xml_curl($tops[0], $timeout);\r\n					$src = smd_xml_xsl_transform($src, $xsl);\r\n				break;\r\n				case \'replace\':\r\n					$src = preg_replace($tops[0], (isset($tops[1]) ? $tops[1] : \'\'), $src);\r\n				break;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Store the current document in the cache and datestamp it\r\n	if ($cache_time > 0 && !$read_cache) {\r\n		$srcinfo = $crush ? base64_encode(gzcompress($src)) : doSlash($src);\r\n		set_pref($var_lastmod, time(), \'smd_xml\', PREF_HIDDEN, \'text_input\');\r\n		set_pref($var_data, $srcinfo, \'smd_xml\', PREF_HIDDEN, \'text_input\');\r\n	}\r\n\r\n	// Make up a replacement array for decoded entities...\r\n	$conversions = array();\r\n	$convert = do_list($convert, $delim);\r\n	foreach ($convert as $pair) {\r\n		if (empty($pair)) continue;\r\n\r\n		$pair = do_list($pair, $param_delim);\r\n		$conversions[$pair[0]] = $pair[1];\r\n	}\r\n\r\n	if ($src && ($debug > 1)) {\r\n		trace_add (\'[smd_xml conversions: \' . print_r($conversions, true) . \']\');\r\n	}\r\n\r\n	// ... and replace them\r\n	$src = strtr($src, $conversions);\r\n\r\n	// Wrap if necessary\r\n	if ($datawrap && !$soap_wrapped) {\r\n		$src = \"<$datawrap>$src</$datawrap>\";\r\n	}\r\n\r\n	if ($debug > 2) {\r\n		trace_add (\'[smd_xml filtered source: \' . $src . \']\');\r\n	}\r\n\r\n	// Set up any ontag processing\r\n	$ontagstart = do_list($ontagstart);\r\n	$ontagend = do_list($ontagend);\r\n	$watchStart = $watchEnd = $watchForm = array();\r\n	foreach ($ontagstart as $ontag) {\r\n		if ($ontag == \'\') continue;\r\n		$parts = explode($param_delim, $ontag);\r\n		$frm = array_shift($parts);\r\n		$watchStart[$frm] = $parts;\r\n		$watchForm[$frm] = fetch_form($frm);\r\n	}\r\n	foreach ($ontagend as $ontag) {\r\n		if ($ontag == \'\') continue;\r\n		$parts = explode($param_delim, $ontag);\r\n		$frm = array_shift($parts);\r\n		$watchEnd[$frm] = $parts;\r\n		$watchForm[$frm] = fetch_form($frm);\r\n	}\r\n\r\n	if ($src && ($debug > 1)) {\r\n		trace_add (\'[smd_xml start watchers: \' . print_r($watchStart, true) . \']\');\r\n		trace_add (\'[smd_xml end watchers: \' . print_r($watchEnd, true) . \']\');\r\n		trace_add (\'[smd_xml watch forms: \' . print_r($watchForm, true) . \']\');\r\n	}\r\n\r\n	// Set up any defaults\r\n	$defaults = do_list($defaults, $delim);\r\n	$dflts = array();\r\n	foreach ($defaults as $dflt) {\r\n		if ($dflt == \'\') continue;\r\n		$parts = explode($param_delim, $dflt);\r\n		$dflts[$parts[0]] = $parts[1];\r\n	}\r\n	$defaults = $dflts;\r\n\r\n	// Set up any formatting\r\n	$format = do_list($format, $delim);\r\n	$formats = array();\r\n	foreach ($format as $frmdef) {\r\n		if ($frmdef == \'\') continue;\r\n		$parts = explode($param_delim, $frmdef);\r\n		$formats[\'type\'][$parts[0]] = $parts[1];\r\n		for($idx = 0; $idx < count($parts)-2; $idx++) {\r\n			$formats[\'data\'][$parts[0]][] = $parts[$idx+2];\r\n		}\r\n	}\r\n\r\n	// Set up any matches\r\n	$match = do_list($match, $delim);\r\n	$matches = array();\r\n	foreach ($match as $item) {\r\n		if ($item == \'\') continue;\r\n		$parts = explode($param_delim, $item);\r\n		$matches[$parts[0]] = $parts[1];\r\n	}\r\n	$match = $matches;\r\n\r\n	if ($src && ($debug > 1)) {\r\n		if ($defaults) {\r\n			trace_add (\'[smd_xml defaults: \' . print_r($defaults, true) . \']\');\r\n		}\r\n		if ($formats) {\r\n			trace_add (\'[smd_xml formats: \' . print_r($formats, true) . \']\');\r\n		}\r\n		if ($match) {\r\n			trace_add (\'[smd_xml matches: \' . print_r($match, true) . \']\');\r\n		}\r\n	}\r\n\r\n	if (!empty($src)) {\r\n		// Paging information\r\n		$rowinfo[\'numrecs\'] = substr_count($src, \'<\'.$record);\r\n		$rowinfo[\'page_rowcnt\'] = 0;\r\n		$rowinfo[\'limit\'] = ($limit < $rowinfo[\'numrecs\']) ? $limit : 0;\r\n		if ($offset >= 0) {\r\n			if ($offset < $rowinfo[\'numrecs\']) {\r\n				$rowinfo[\'offset\'] = $offset;\r\n			} else {\r\n				$rowinfo[\'offset\'] = $rowinfo[\'numrecs\'];\r\n				$rowinfo[\'limit\'] = 0;\r\n			}\r\n		} else {\r\n			$negoff = $rowinfo[\'numrecs\'] + $offset;\r\n			if ($negoff > 0) {\r\n				$rowinfo[\'offset\'] = $negoff;\r\n			} else {\r\n				$rowinfo[\'offset\'] = 0;\r\n				$rowinfo[\'limit\'] = $rowinfo[\'numrecs\'];\r\n			}\r\n		}\r\n\r\n		// Re-assign the atts in case they\'ve been changed by reaching the bounds of the document\r\n		$offset = $rowinfo[\'offset\'];\r\n		$limit = $rowinfo[\'limit\'];\r\n\r\n		if ($limit > 0) {\r\n			$keepsafe = $thispage;\r\n			$rowinfo[\'total\'] = $rowinfo[\'numrecs\'] - $offset;\r\n			$rowinfo[\'numPages\'] = ceil($rowinfo[\'total\'] / $limit);\r\n			$rowinfo[\'pg\'] = (!gps($pagevar)) ? 1 : gps($pagevar);\r\n			$rowinfo[\'pgoffset\'] = $offset + (($rowinfo[\'pg\'] - 1) * $limit);\r\n			$rowinfo[\'prevpg\'] = (($rowinfo[\'pg\']-1) > 0) ? $rowinfo[\'pg\']-1 : \'\';\r\n			$rowinfo[\'nextpg\'] = (($rowinfo[\'pg\']+1) <= $rowinfo[\'numPages\']) ? $rowinfo[\'pg\']+1 : \'\';\r\n			$rowinfo[\'pagerows\'] = ($rowinfo[\'pg\'] == $rowinfo[\'numPages\']) ? $rowinfo[\'total\']-($limit * ($rowinfo[\'numPages\']-1)) : $limit;\r\n			$rowinfo[\'unique_id\'] = $uniq;\r\n\r\n			// send paging info to txp:newer and txp:older\r\n			$pageout[\'pg\'] = $rowinfo[\'pg\'];\r\n			$pageout[\'numPages\'] = $rowinfo[\'numPages\'];\r\n			$pageout[\'s\'] = $pretext[\'s\'];\r\n			$pageout[\'c\'] = $pretext[\'c\'];\r\n			$pageout[\'grand_total\'] = $rowinfo[\'numrecs\'];\r\n			$pageout[\'total\'] = $rowinfo[\'total\'];\r\n			$thispage = $pageout;\r\n		} else {\r\n			$rowinfo[\'pgoffset\'] = $offset;\r\n		}\r\n\r\n		$rowinfo[\'running_rowcnt\'] = $rowinfo[\'pgoffset\']-$offset;\r\n		$rowinfo[\'first_rec\'] = $rowinfo[\'running_rowcnt\'] + 1;\r\n		$rowinfo[\'last_rec\'] = ($limit > 0) ? $rowinfo[\'first_rec\'] + $rowinfo[\'pagerows\'] - 1 : $rowinfo[\'numrecs\'];\r\n		if ($limit > 0) {\r\n			$rowinfo[\'prev_rows\'] = (($rowinfo[\'prevpg\']) ? $limit : 0);\r\n			$rowinfo[\'next_rows\'] = (($rowinfo[\'nextpg\']) ? (($rowinfo[\'last_rec\']+$limit+1) > $rowinfo[\'total\'] ? $rowinfo[\'total\']-$rowinfo[\'last_rec\'] : $limit) : 0);\r\n		}\r\n\r\n		if ($debug > 0) {\r\n			trace_add (\'[smd_xml paging info: \' . print_r($rowinfo, true) . \']\');\r\n		}\r\n\r\n		// Do the dirty XML deed\r\n		$ref = new smd_xml_parser(array(\r\n			\'src\'          => $src,\r\n			\'delim\'        => $delim,\r\n			\'param_delim\'  => $param_delim,\r\n			\'tag_delim\'    => $tag_delim,\r\n			\'concat\'       => $concat,\r\n			\'concat_delim\' => $concat_delim,\r\n			\'fields\'       => $fields,\r\n			\'skip\'         => $skip,\r\n			\'match\'        => $match,\r\n			\'record\'       => $record,\r\n			\'casefold\'     => $uppercase,\r\n			\'target_enc\'   => $target_enc,\r\n			\'tag_prefix\'   => $tag_prefix,\r\n			\'page_prefix\'  => $page_prefix,\r\n			\'defaults\'     => $defaults,\r\n			\'load_atts\'    => $load_atts,\r\n			\'watchStart\'   => $watchStart,\r\n			\'watchEnd\'     => $watchEnd,\r\n			\'watchForm\'    => $watchForm,\r\n			\'set_empty\'    => $set_empty,\r\n			\'formats\'      => $formats,\r\n			\'thing\'        => $thing,\r\n			\'rinfo\'        => $rowinfo,\r\n			\'debug\'        => $debug,\r\n		));\r\n\r\n		// Grab the parsed results\r\n		$result = $ref->getResults();\r\n\r\n		// Create the page form\r\n		$pageblock = \'\';\r\n		$finalout = $repagements = array();\r\n\r\n		if ($rowinfo[\'limit\'] > 0) {\r\n			$repagements[\'{\'.$page_prefix.\'totalrecs}\'] = $rowinfo[\'total\'];\r\n			$repagements[\'{\'.$page_prefix.\'pagerecs}\']  = $rowinfo[\'pagerows\'];\r\n			$repagements[\'{\'.$page_prefix.\'pages}\']     = $rowinfo[\'numPages\'];\r\n			$repagements[\'{\'.$page_prefix.\'prevpage}\']  = $rowinfo[\'prevpg\'];\r\n			$repagements[\'{\'.$page_prefix.\'thispage}\']  = $rowinfo[\'pg\'];\r\n			$repagements[\'{\'.$page_prefix.\'nextpage}\']  = $rowinfo[\'nextpg\'];\r\n			$repagements[\'{\'.$page_prefix.\'rec_start}\'] = $rowinfo[\'first_rec\'];\r\n			$repagements[\'{\'.$page_prefix.\'rec_end}\']   = $rowinfo[\'last_rec\'];\r\n			$repagements[\'{\'.$page_prefix.\'recs_prev}\'] = $rowinfo[\'prev_rows\'];\r\n			$repagements[\'{\'.$page_prefix.\'recs_next}\'] = $rowinfo[\'next_rows\'];\r\n			$repagements[\'{\'.$page_prefix.\'unique_id}\'] = $rowinfo[\'unique_id\'];\r\n			$smd_xml_pginfo = $repagements;\r\n			$pageblock = parse(strtr($pageform, $repagements));\r\n		}\r\n\r\n		// Make up the final output\r\n		if (in_array(\"above\", $pagebit)) {\r\n			$finalout[] = $pageblock;\r\n		}\r\n		$finalout[] = doWrap($result, $wraptag, $break, $class);\r\n		if (in_array(\"below\", $pagebit)) {\r\n			$finalout[] = $pageblock;\r\n		}\r\n\r\n		// Restore the paging outside the plugin container\r\n		if ($limit > 0) {\r\n			$thispage = $keepsafe;\r\n		}\r\n\r\n		return join(\'\', $finalout);\r\n	} else {\r\n		return \'\';\r\n	}\r\n}\r\n\r\n// Convenience tags to check if there\'s a prev/next page defined. Could also use smd_if\r\nfunction smd_xml_if_prev($atts, $thing) {\r\n	global $smd_xml_pginfo;\r\n\r\n	$res = $smd_xml_pginfo && $smd_xml_pginfo[\'{smd_xml_prevpage}\'] != \'\';\r\n	return parse(EvalElse(strtr($thing, $smd_xml_pginfo), $res));\r\n}\r\nfunction smd_xml_if_next($atts, $thing) {\r\n	global $smd_xml_pginfo;\r\n\r\n	$res = $smd_xml_pginfo && $smd_xml_pginfo[\'{smd_xml_nextpage}\'] != \'\';\r\n	return parse(EvalElse(strtr($thing, $smd_xml_pginfo), $res));\r\n}\r\n\r\n/*****************\r\n FUNCTION LIBRARY\r\n*****************/\r\n// Retrieve a resource via curl; return false otherwise\r\nfunction smd_xml_curl($data, $timeout=10) {\r\n	$ret = false;\r\n\r\n	if (function_exists(\'curl_version\')) {\r\n		$c = curl_init();\r\n		curl_setopt($c, CURLOPT_URL, $data);\r\n		curl_setopt($c, CURLOPT_REFERER, hu);\r\n		curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\r\n		curl_setopt($c, CURLOPT_VERBOSE, false);\r\n		curl_setopt($c, CURLOPT_TIMEOUT, $timeout);\r\n		$ret = curl_exec($c);\r\n	}\r\n\r\n	return $ret;\r\n}\r\n\r\n// Transform an XML document using the given XSL stylesheet\r\nfunction smd_xml_xsl_transform($xml, $xsl) {\r\n	$ret = $xml;\r\n\r\n	if (class_exists(\'XSLTProcessor\')) {\r\n		$xslt = new XSLTProcessor();\r\n		$xslt->importStylesheet(new SimpleXMLElement($xsl));\r\n		$ret = $xslt->transformToXml(new SimpleXMLElement($xml));\r\n	}\r\n\r\n	return $ret;\r\n}\r\n\r\n// Convert XML document to associative array\r\n// (from http://stackoverflow.com/questions/99350/php-associative-arrays-to-and-from-xml)\r\nfunction smd_xml_to_array($curr_node) {\r\n	$val_array = array();\r\n	$typ_array = array();\r\n\r\n	foreach($curr_node->childNodes as $node) {\r\n		if ($node->nodeType == XML_ELEMENT_NODE) {\r\n			$val = smd_xml_to_array($node);\r\n			if (array_key_exists($node->tagName, $val_array)) {\r\n				if (!is_array($val_array[$node->tagName]) || $type_array[$node->tagName] == \'hash\') {\r\n					$existing_val = $val_array[$node->tagName];\r\n					unset($val_array[$node->tagName]);\r\n					$val_array[$node->tagName][0] = $existing_val;\r\n					$type_array[$node->tagName] = \'array\';\r\n				}\r\n				$val_array[$node->tagName][] = $val;\r\n			} else {\r\n				$val_array[$node->tagName] = $val;\r\n				if (is_array($val)) {\r\n					$type_array[$node->tagName] = \'hash\';\r\n				}\r\n			} // end if array key exists\r\n		} // end if element node\r\n	} // end for each\r\n\r\n	if (count($val_array) == 0) {\r\n		return $curr_node->nodeValue;\r\n	} else {\r\n		return $val_array;\r\n	}\r\n}\r\n\r\n// Build an XML data set from associative array\r\nclass smd_xml_build_data {\r\n	private $xml, $last_idx, $recWrap;\r\n\r\n	function smd_xml_build_data ($data, $startElement, $recWrap, $xml_version = \'1.0\', $xml_encoding = \'UTF-8\') {\r\n		$startElement = ($startElement) ? $startElement : \'fx_request\';\r\n		if (!is_array($data)) {\r\n			$err = \'Invalid variable type supplied, expected array not found on line \'.__LINE__.\" in Class: \".__CLASS__.\" Method: \".__METHOD__;\r\n			trigger_error($err);\r\n			return false;\r\n		}\r\n		$this->xml = new XmlWriter();\r\n		$this->xml->openMemory();\r\n		$this->xml->startDocument($xml_version, $xml_encoding);\r\n		$this->xml->startElement($startElement);\r\n\r\n		$this->last_idx = 0;\r\n		$this->recWrap = $recWrap;\r\n		$this->write($this->xml, $data, $startElement);\r\n\r\n		$this->xml->endElement();\r\n	}\r\n\r\n	// Standard getter\r\n	function getData() {\r\n		return $this->xml->outputMemory(true);\r\n	}\r\n\r\n	// Recurse array elements and build XML tag tree\r\n	function write(XMLWriter $xml, $data, $parent) {\r\n		foreach ($data as $key => $value) {\r\n			// Nodes that aren\'t valid attributes get given an array index\r\n			if (!preg_match(\'/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/\', $key)) {\r\n				$key = ($this->recWrap) ? $this->recWrap : ($parent . \'_\' . (is_numeric($key) ? $key : $last_idx));\r\n			}\r\n			if (is_array($value)) {\r\n				$xml->startElement($key);\r\n				$this->write($xml, $value, $key);\r\n				$xml->endElement();\r\n				continue;\r\n			}\r\n			$xml->writeElement($key, $value);\r\n			$this->last_idx++;\r\n		}\r\n	}\r\n}\r\n\r\n// Dirty XML work done here\r\nclass smd_xml_parser {\r\n	private $data, $rec;\r\n	private $fields, $subfields, $treefields;\r\n	private $skip, $match, $defaults, $set_empty;\r\n	private $casefold, $outenc;\r\n	private $load_atts, $watchStart, $watchEnd, $watchForm;\r\n	private $formats, $tag_prefix, $page_prefix;\r\n\r\n	private $intag, $indata;\r\n	private $skiptag, $xmltag, $xmlatts, $xmldata;\r\n	private $thing, $out;\r\n	private $delim, $concat, $tdelim, $cdelim;\r\n	private $rowinfo, $show_record;\r\n	private $debug;\r\n\r\n	/**\r\n	* constructor\r\n	*/\r\n	function __construct($atts) {\r\n		$this->data        = $atts[\'src\'];\r\n		$this->delim       = $atts[\'delim\'];\r\n		$this->tdelim      = $atts[\'tag_delim\'];\r\n		$this->concat      = $atts[\'concat\'];\r\n		$this->cdelim      = $atts[\'concat_delim\'];\r\n		$this->fields      = do_list($atts[\'fields\'], $this->delim);\r\n		$this->skip        = do_list($atts[\'skip\'], $this->delim);\r\n		$this->match       = $atts[\'match\'];\r\n		$this->rec         = $atts[\'record\'];\r\n		$this->casefold    = $atts[\'casefold\'];\r\n		$this->outenc      = $atts[\'target_enc\'];\r\n		$this->tag_prefix  = $atts[\'tag_prefix\'];\r\n		$this->page_prefix = $atts[\'page_prefix\'];\r\n		$this->defaults    = $atts[\'defaults\'];\r\n		$this->load_atts   = $atts[\'load_atts\'];\r\n		$this->watchStart  = $atts[\'watchStart\'];\r\n		$this->watchEnd    = $atts[\'watchEnd\'];\r\n		$this->watchForm   = $atts[\'watchForm\'];\r\n		$this->set_empty   = $atts[\'set_empty\'];\r\n		$this->formats     = $atts[\'formats\'];\r\n		$this->thing       = $atts[\'thing\'];\r\n		$this->rowinfo     = $atts[\'rinfo\'];\r\n		$this->debug       = $atts[\'debug\'];\r\n\r\n		$this->subfields  = array();\r\n		$this->treefields = array();\r\n		$this->tagtree    = array();\r\n		$this->xmldata    = array();\r\n		$this->out        = array();\r\n		$this->intag      = false;\r\n		$this->exists     = false;\r\n		$this->skiptag    = \'\';\r\n		$this->xmltag     = \'\';\r\n		$this->xmltatts   = \'\';\r\n\r\n		// Copy any tree- and sub-fields out of the list\r\n		foreach ($this->fields as $key => $fld) {\r\n			$sf = do_list($fld, $this->tdelim);\r\n			$tf = do_list($sf[0], \'->\');\r\n			$numSFs = count($sf);\r\n			$numTFs = count($tf);\r\n\r\n			// First subfield needs tree portion removing, and\r\n			// last treefield needs removing (\'cos it\'s the first subfield)\r\n			$sf[0] = $tf[$numTFs-1];\r\n			unset($tf[$numTFs-1]);\r\n			$numTFs--;\r\n\r\n			// Build the tree view.\r\n			// The tree holds the path from the sub-node back to the top, so when\r\n			// a leaf is encountered its path can be looked up as $treefields[$leaf]\r\n			// and be path verified to see if it matches a wanted leaf\r\n			foreach ($sf as $sub) {\r\n				$this->treefields[$sub] = $tf;\r\n			}\r\n\r\n			// The subfields are built as a pair of arrays -- one flat, one indexed by parent field -- for searching later\r\n			$this->byfields[$sf[0]] = array_slice($sf, 1);\r\n			$this->subfields = array_merge($this->subfields, $sf);\r\n\r\n			// Make sure the field only holds the zeroth entry\r\n			$this->fields[$key] = ($tf) ? $tf[0] : $sf[0];\r\n\r\n			// and make sure the root node of any tree is not added\r\n			// as checkable to the tree itself\r\n			$this->treefields[$this->fields[$key]] = array();\r\n		}\r\n		if ($this->debug > 1) {\r\n			trace_add (\'[smd_xml fields: \' . print_r($this->fields, true) . \']\');\r\n			trace_add (\'[smd_xml subfields: \' . print_r($this->subfields, true) . \']\');\r\n			trace_add (\'[smd_xml indexed subfields: \' . print_r($this->byfields, true) . \']\');\r\n			trace_add (\'[smd_xml tree fields: \' . print_r($this->treefields, true) . \']\');\r\n		}\r\n		$this->parse();\r\n	}\r\n\r\n	public function getResults() {\r\n		if ($this->out) {\r\n			return $this->out;\r\n		} else {\r\n			return \'\';\r\n		}\r\n	}\r\n\r\n	private function parse() {\r\n		$xmlparser = xml_parser_create();\r\n		xml_set_object($xmlparser, $this);\r\n		xml_parser_set_option($xmlparser, XML_OPTION_CASE_FOLDING, $this->casefold);\r\n		xml_parser_set_option($xmlparser, XML_OPTION_TARGET_ENCODING, $this->outenc);\r\n		xml_set_default_handler($xmlparser, \"smd_xml_default\");\r\n		xml_set_element_handler($xmlparser, \"smd_xml_start_tag\", \"smd_xml_end_tag\");\r\n		xml_set_character_data_handler($xmlparser, \"smd_xml_tag_contents\");\r\n		xml_parse($xmlparser, $this->data);\r\n		xml_parser_free($xmlparser);\r\n	}\r\n\r\n	// Do nothing with default (non-XML) data. Just report it in debug mode\r\n	// TODO: allow some callback / Form to handle this type of data\r\n	private function smd_xml_default($parser, $data) {\r\n		if ($this->debug > 1) {\r\n			trace_add (\'[smd_xml default data: \' . print_r($data, true) . \']\');\r\n		}\r\n	}\r\n\r\n	// Start of XML tag\r\n	private function smd_xml_start_tag($parser, $name, $attribs) {\r\n		array_push($this->tagtree, $name);\r\n\r\n		$pgval = $this->rowinfo[\'pgoffset\'] - 1;\r\n		$lim = $this->rowinfo[\'limit\'] > 0;\r\n\r\n		// Is this record to be processed? i.e. is it within this page\'s limit/offset?\r\n		$this->show_record = $lim ? (($this->rowinfo[\'page_rowcnt\'] > $pgval) && ($this->rowinfo[\'page_rowcnt\'] <= $pgval + $this->rowinfo[\'pagerows\'])) : $this->rowinfo[\'page_rowcnt\'] > $pgval;\r\n\r\n		// Check the tree if necessary\r\n		$tree_ok = true;\r\n		if (array_key_exists($name, $this->treefields) && !empty($this->treefields[$name])) {\r\n			$treeSize = count($this->treefields[$name]);\r\n			$treeOffset = \'-\' .$treeSize - 1;\r\n			// If there\'s no difference, the tree is ok\r\n			$tree_ok = ( array_diff ($this->treefields[$name], array_slice($this->tagtree, $treeOffset, $treeSize) ) ) ? false : true;\r\n			if ($this->debug > 2) {\r\n				trace_add (\'[smd_xml tree check on field: \' . $name . \']\');\r\n				trace_add (\'[smd_xml tree compare: \' . print_r($this->treefields[$name], true) . \']\');\r\n				trace_add (\'[smd_xml tree with: \' . print_r($this->tagtree, true) . \']\');\r\n				trace_add (\'[smd_xml tree result: \' . ($tree_ok ? \'YES\' : \'NO\') . \']\');\r\n			}\r\n		}\r\n\r\n		// Start of a wanted record: flag this situation and grab any attributes\r\n		if ( ($name == $this->rec) && $this->show_record ) {\r\n			$this->intag = true;\r\n			$this->xmlatts[$name] = $attribs;\r\n			if ($this->load_atts == \'start\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n		}\r\n\r\n		// We\'re inside a wanted record\r\n		if ($this->intag && $tree_ok) {\r\n			if (in_array($name, $this->skip)) {\r\n				$this->xmltag = \'\';\r\n				$this->xmlatts[$name] = array();\r\n				$this->skiptag = $name;\r\n			} else {\r\n				$this->xmltag = $name;\r\n				$this->xmlatts[$name] = $attribs;\r\n				if ( ($this->load_atts == \'start\') && ($name != $this->rec) ) {\r\n					$this->smd_xml_store_attribs($name);\r\n				}\r\n\r\n				// Process any ontagstart\r\n				foreach ($this->watchStart as $frm => $watchlist) {\r\n					if (in_array($name, $watchlist)) {\r\n						$op = trim(parse(strtr($this->watchForm[$frm], $this->xmldata)));\r\n						if ($op != \'\') {\r\n							$this->out[] = $op;\r\n						}\r\n					}\r\n				}\r\n				if ($this->concat && isset($this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'])) {\r\n					$this->exists = true;\r\n				} else {\r\n					$this->exists = false;\r\n				}\r\n			}\r\n		}\r\n		$this->indata = false;\r\n	}\r\n\r\n	// End of XML tag\r\n	private function smd_xml_end_tag($parser, $name) {\r\n		// End of a regular/attribute-only/container/self-closing tag\r\n		if ( ($name != $this->rec) && ($name != $this->skiptag) && $this->intag && in_array($name, array_merge($this->fields, $this->subfields)) ) {\r\n			$this->xmltag = $name;\r\n			if ($this->load_atts == \'end\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n\r\n			// Process any ontagend\r\n			foreach ($this->watchEnd as $frm => $watchlist) {\r\n				if (in_array($name, $watchlist)) {\r\n					$op = trim(parse(strtr($this->watchForm[$frm], $this->xmldata)));\r\n					if ($op != \'\') {\r\n						$this->out[] = $op;\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		// End of the record\r\n		if ($name == $this->rec && $name != $this->skiptag) {\r\n			$this->intag = false;\r\n			if ($this->load_atts == \'end\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n\r\n			$matched = true;\r\n			if (array_key_exists($this->xmltag, $this->match)) {\r\n				$matched = preg_match($this->match[$this->xmltag], $this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\']);\r\n			}\r\n			if ($matched) {\r\n				$lim = ($this->rowinfo[\'limit\'] > 0) ? true : false;\r\n\r\n				// Append row counter information\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'totalrecs}\'] = $lim ? $this->rowinfo[\'total\'] : $this->rowinfo[\'numrecs\'] - $this->rowinfo[\'pgoffset\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'pagerecs}\']  = $lim ? $this->rowinfo[\'pagerows\'] : $this->xmldata[\'{\'.$this->page_prefix.\'totalrecs}\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'pages}\']     = $lim ? $this->rowinfo[\'numPages\'] : 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thispage}\']  = $lim ? $this->rowinfo[\'pg\'] : 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thisindex}\'] = $this->rowinfo[\'page_rowcnt\'] - $this->rowinfo[\'offset\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thisrec}\']   = $this->rowinfo[\'page_rowcnt\'] - $this->rowinfo[\'offset\'] + 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'runindex}\']  = $this->rowinfo[\'running_rowcnt\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'runrec}\']    = $this->rowinfo[\'running_rowcnt\'] + 1;\r\n\r\n				$sfields = array_unique(array_merge($this->fields, $this->subfields));\r\n\r\n				// Set any tag contents to a default value, if specified\r\n				if ($this->defaults || $this->set_empty) {\r\n					foreach ($sfields as $field) {\r\n						if (!isset($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'])) {\r\n							if (array_key_exists($field, $this->defaults)) {\r\n								$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = $this->defaults[$field];\r\n							} else if ($this->set_empty) {\r\n								$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = \'\';\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				// Reformat any fields, if specified\r\n				if ($this->formats) {\r\n					foreach ($sfields as $field) {\r\n						if (isset($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']) && array_key_exists($field, $this->formats[\'type\'])) {\r\n							switch ($this->formats[\'type\'][$field]) {\r\n								case \'date\':\r\n									$nd = (is_numeric($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'])) ? $this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] : strtotime($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									if ($nd !== false) {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strftime($this->formats[\'data\'][$field][0], $nd);\r\n									}\r\n									break;\r\n								case \'link\':\r\n									// From http://codesnippets.joyent.com/posts/show/2104\r\n									$pat = \"@\\b(https?://)?(([0-9a-zA-Z_!~*\'().&=+$%-]+:)?[0-9a-zA-Z_!~*\'().&=+$%-]+\\@)?(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*\'()-]+\\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*\'().;?:\\@&=+$,%#-]+)*/?)@\";\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = preg_replace($pat, \'<a href=\"$0\">$0</a>\', $this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									break;\r\n								case \'escape\':\r\n									$flags = ENT_XML1;\r\n									if (isset($this->formats[\'data\'][$field][0])) {\r\n										switch ($this->formats[\'data\'][$field][0]) {\r\n											case \'no_quotes\':\r\n												$flags |= ENT_NOQUOTES;\r\n											break;\r\n											case \'all_quotes\':\r\n												$flags |= ENT_QUOTES;\r\n											break;\r\n											case \'double_quotes\':\r\n											default:\r\n												$flags |= ENT_COMPAT;\r\n											break;\r\n										}\r\n									}\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = htmlspecialchars($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'], $flags);\r\n									break;\r\n								case \'fordb\':\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = doSlash($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									break;\r\n								case \'sanitize\':\r\n									if ($this->formats[\'data\'][$field][0] == \"url\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = sanitizeForUrl($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									} else if ($this->formats[\'data\'][$field][0] == \"file\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = sanitizeForFile($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									} else if ($this->formats[\'data\'][$field][0] == \"url_title\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = stripSpace($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'], 1);\r\n									}\r\n									break;\r\n								case \'case\':\r\n									for ($idx = 0; $idx < count($this->formats[\'data\'][$field]); $idx++) {\r\n										if ($this->formats[\'data\'][$field][$idx] == \"upper\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strtoupper($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"lower\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strtolower($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"ucfirst\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = ucfirst($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"ucwords\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = ucwords($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										}\r\n									}\r\n									break;\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				if ($this->debug > 0 && $this->show_record) {\r\n					trace_add (\'[smd_xml replacements for record \'.($this->rowinfo[\'running_rowcnt\'] + 1).\':\' . print_r($this->xmldata, true) . \']\');\r\n				}\r\n			}\r\n\r\n			if ($this->show_record && $matched) {\r\n				$this->out[] = parse(strtr($this->thing, $this->xmldata));\r\n			}\r\n\r\n			// Prepare for next record iteration\r\n			$this->rowinfo[\'running_rowcnt\'] = $this->rowinfo[\'running_rowcnt\']+1;\r\n			$this->rowinfo[\'page_rowcnt\'] = $this->rowinfo[\'page_rowcnt\']+1;\r\n			$this->xmldata = array();\r\n			$this->indata = false;\r\n		}\r\n		if ($name == $this->skiptag) {\r\n			$this->skiptag = \'\';\r\n		}\r\n		array_pop($this->tagtree);\r\n	}\r\n\r\n	// Node data/text that is not an XML tag\r\n	private function smd_xml_tag_contents($parser, $data) {\r\n		if ($this->intag && !$this->skiptag) {\r\n			if ($this->debug > 1) {\r\n				trace_add (\'[smd_xml tag:\' . $this->xmltag . \']\');\r\n				trace_add (\'[smd_xml tag data:\' . print_r($data, true) . \']\');\r\n			}\r\n			if (in_array($this->xmltag, array_merge($this->fields, $this->subfields))) {\r\n				if ($this->indata) {\r\n					// Annoying logic, but necessary since the parser may not split at a tag\r\n					// boundary so we need to know if we\'re already in some data block and append.\r\n					// If not we can safely start a new xmldata node for this tag\r\n					if ($this->exists) {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $this->cdelim.$data;\r\n					} else {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $data;\r\n					}\r\n				} else {\r\n					if ($this->exists) {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $this->cdelim.$data;\r\n					} else {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] = $data;\r\n					}\r\n				}\r\n\r\n				// Copy the tag to any duplicate (alias) nodes\r\n				if (array_key_exists($this->xmltag, $this->byfields)) {\r\n					foreach($this->byfields[$this->xmltag] as $copyfield) {\r\n						if (!isset($this->xmldata[\'{\'.$this->tag_prefix.$copyfield.\'}\'])) {\r\n							$this->xmldata[\'{\'.$this->tag_prefix.$copyfield.\'}\'] = $this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'];\r\n						}\r\n					}\r\n				}\r\n				$this->indata = true;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Create any attribute nodes\r\n	private function smd_xml_store_attribs($name) {\r\n		if ($this->xmlatts[$name]) {\r\n			foreach ($this->xmlatts[$name] as $xkey => $xval) {\r\n				// Append if attribute previously encountered\r\n				if ($this->concat && isset($this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'])) {\r\n					$this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'] .= $this->cdelim.$xval;\r\n				} else {\r\n					$this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'] = $xval;\r\n				}\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\n\r\n/*\r\n\r\n// JSON reader from tumblr: adapt and genericise?\r\n\r\n$offset = $_GET[\'offset\'];\r\n$ch = curl_init();\r\ncurl_setopt($ch,CURLOPT_URL,\"http://blog.dgovil.com/api/read/json?num=10&start=\".$offset);\r\ncurl_setopt($ch,CURLOPT_RETURNTRANSFER,true);\r\n$result = curl_exec($ch);\r\ncurl_close($ch);\r\n\r\n$result = str_replace(\"var tumblr_api_read = \",\"\",$result);\r\n$result = str_replace(\';\',\'\',$result);\r\n$result = str_replace(\'\\u00a0\',\'&amp;nbsp;\',$result);\r\n\r\n$jsondata = json_decode($result,true);\r\n$posts = $jsondata[\'posts\'];\r\n\r\n\r\nforeach($posts as $post){   ?>\r\n<div class=\"tumblr_post post-<?php echo $post[\'type\'] ?>\">\r\n\r\n    <?php if ($post[\'type\'] == \'regular\') { ?>\r\n        <div class=\"post-title\" id=\"post-<?php echo $post[\'id\'];?>\"><a href=\"<?php echo $post[\'url-with-slug\']; ?>\"><?php echo $post{\'regular-title\'}; ?></a></div>\r\n    <?php echo $post{\'regular-body\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'quote\') {  ?>\r\n        <?php echo $post{\'quote-text\'}; ?>\r\n        <?php echo $post{\'quote-source\'}; ?>\r\n      <?php } ?>\r\n\r\n\r\n    <?php if ($post[\'type\'] == \'photo\') {  ?>\r\n        <img src=\"<?php echo $post[\'photo-url-500\'];?>\">\r\n        <?php echo $post{\'photo-caption\'}; ?>\r\n        <?php echo $post{\'photo-set\'}; ?>\r\n\r\n        <a href=\"<?php echo $post{\'photo-url\'}; ?>\" class=\"fImage\">View Full Size</a>\r\n    <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'link\') {  ?>\r\n\r\n        <p><a href=\"<?php echo $post{\'link-url\'}; ?>\"><?php echo $post{\'link-text\'}; ?></a>\r\n        <?php echo $post{\'link-description\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'conversation\') {  ?>\r\n        <?php echo $post{\'conversation-text\'}; ?>\r\n      <?php } ?>\r\n\r\n\r\n    <?php if ($post[\'type\'] == \'video\') {  ?>\r\n        <!--<?php echo $post{\'video-source\'}; ?>-->\r\n        <?php echo $post{\'video-player\'}; ?>\r\n        <?php echo $post{\'video-caption\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'conversation\') {  ?>\r\n        <?php echo $post{\'audio-caption\'}; ?>\r\n        <?php echo $post{\'audio-player\'}; ?>\r\n        <?php echo $post{\'audio-plays\'}; ?>\r\n      <?php } ?>\r\n\r\n<div id=\"post-date\">\r\n<?php echo date(\"jS D M, H:i\",strtotime($post[\'date\'])); ?>&nbsp; &nbsp;<a href=\"<?php echo $post[\'url-with-slug\']; ?>\">Read on Tumblr</a>\r\n</div>\r\n\r\n</div>\r\n\r\n*/', '/**\r\n * smd_xml\r\n *\r\n * A Textpattern CMS plugin for reading and parsing XML data:\r\n *  -> Read XML stream from URL, string or (experimental) SOAP client. XSL transform available\r\n *  -> Specify field hierarchy to extract specific parts of the data stream\r\n *  -> Optionally format data items prior to output\r\n *  -> Cacheing supported to ease server load\r\n *  -> Results can be paged\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n//      --> JSON support?\r\n//      -->   Some way of grouping items, maybe via concat. e.g. instead of assigning output directly to $out[], add the group to a pseudo-tag in $xmldata,\r\n//      -->     e.g. concat=\"2|Measure=>My_Measurements\" will make {My_Measurements} available in the container which will contain the concatenated output of all (possibly processed with ontag) Measure tags\r\n//     Allow fields=\"NodeWrapper->*\" to specify that you want all sub-nodes to be included in the replacement stream\r\n//      -->  Perhaps some mechanism to specify the number of levels to go down as well, e.g.:\r\n//      -->    NodeWrapper->*    =  all sub-nodes\r\n//      -->    NodeWrapper->*1   =  1st-child sub-nodes\r\n//      -->    NodeWrapper->*2   =  2nd-child sub-nodes\r\n//      -->    NodeWrapper->*1+2 =  1st and 2nd-child sub-nodes\r\n//      -->    NodeWrapper->*1-4 =  1st thru 4th-child sub-nodes\r\nfunction smd_xml($atts, $thing=NULL) {\r\n	global $pretext, $thispage, $smd_xml_pginfo;\r\n\r\n	extract(lAtts(array(\r\n		\'data\'             => \'\',\r\n		\'datawrap\'         => \'\',\r\n		\'record\'           => \'\',\r\n		\'fields\'           => \'\',\r\n		\'skip\'             => \'\',\r\n		\'match\'            => \'\',\r\n		\'timeout\'          => 10,\r\n		\'transform\'        => \'\',\r\n		\'kill_spaces\'      => 1,\r\n		\'ontagstart\'       => \'\',\r\n		\'ontagend\'         => \'\',\r\n		\'load_atts\'        => \'start\', // (tag) start or end\r\n		\'uppercase\'        => \'0\',\r\n		\'convert\'          => \'\', // search|replace, search|replace, ...\r\n		\'target_enc\'       => \'UTF-8\',\r\n		\'defaults\'         => \'\',\r\n		\'set_empty\'        => \'0\',\r\n		\'format\'           => \'\',\r\n		\'form\'             => \'\',\r\n		\'pageform\'         => \'\',\r\n		\'pagevar\'          => \'pg\',\r\n		\'pagepos\'          => \'below\',\r\n		\'delim\'            => \',\',\r\n		\'param_delim\'      => \'|\',\r\n		\'tag_delim\'        => \'|\',\r\n		\'concat\'           => \'1\',\r\n		\'concat_delim\'     => \' \',\r\n		\'transport\'        => \'\',\r\n		\'transport_opts\'   => \'\',\r\n		\'transport_config\' => \'\',\r\n		\'cache_time\'       => \'3600\', // in seconds\r\n		\'hashsize\'         => \'6:5\',\r\n		\'line_length\'      => \'8192\',\r\n		\'var_prefix\'       => \', smd_xml_\',\r\n		\'limit\'            => 0,\r\n		\'offset\'           => 0,\r\n		\'wraptag\'          => \'\',\r\n		\'break\'            => \'\',\r\n		\'class\'            => \'\',\r\n		\'debug\'            => \'0\',\r\n	), $atts));\r\n\r\n	// This constant is only available in PHP 5.4+ so fake it for earlier versions\r\n	// and take the hit on them not knowing what the flag means\r\n	if (!defined(\'ENT_XML1\')) {\r\n		define(\'ENT_XML1\', 16);\r\n	}\r\n\r\n	$src = \'\';\r\n	$thing = (empty($form)) ? $thing : fetch_form($form);\r\n	$soap_wrapped = false;\r\n\r\n	if (empty($data)) {\r\n		trigger_error(\"smd_xml requires a data source\", E_USER_ERROR);\r\n		return;\r\n	}\r\n	if (empty($record)) {\r\n		trigger_error(\"smd_xml requires a record name within your data stream\", E_USER_ERROR);\r\n		return;\r\n	}\r\n\r\n	// Work out where the paging info is to appear\r\n	$pagebit = $rowinfo = array();\r\n	if ($pageform) {\r\n		$pagePosAllowed = array(\"below\", \"above\");\r\n		$pageform = fetch_form($pageform);\r\n		$pagepos = str_replace(\'smd_\', \'\', $pagepos); // For convenience\r\n		$pagepos = do_list($pagepos, $delim);\r\n		foreach ($pagepos as $pageitem) {\r\n			$pagebit[] = (in_array($pageitem, $pagePosAllowed)) ? $pageitem : $pagePosAllowed[0];\r\n		}\r\n	}\r\n\r\n	$target_enc = (in_array($target_enc, array(\'ISO-8859-1\', \'US-ASCII\', \'UTF-8\'))) ? $target_enc : \'UTF-8\';\r\n\r\n	// Extract the prefixes\r\n	$prefixes = do_list($var_prefix);\r\n	$tag_prefix = $prefixes[0];\r\n	$page_prefix = isset($prefixes[1]) ? $prefixes[1] : $tag_prefix;\r\n\r\n	// Make a unique hash value for this instance so the XML document can be cached in txp_prefs\r\n	$uniq = \'\';\r\n	$md5 = md5($data.$record.$fields);\r\n	list($hashLen, $hashSkip) = explode(\':\', $hashsize);\r\n	for ($idx = 0, $cnt = 0; $cnt < $hashLen; $cnt++, $idx = (($idx+$hashSkip) % strlen($md5))) {\r\n		$uniq .= $md5[$idx];\r\n	}\r\n\r\n	$var_lastmod = \'smd_xml_lmod_\'.$uniq;\r\n	$var_data = \'smd_xml_data_\'.$uniq;\r\n	$lastmod = get_pref($var_lastmod, 0);\r\n	$read_cache = (($cache_time > 0) && ((time() - $lastmod) < $cache_time)) ? true : false;\r\n	$crush = function_exists(\'gzcompress\') && function_exists(\'gzuncompress\');\r\n	$pagevar = ($pagevar == \'SMD_XML_UNIQUE_ID\') ? $uniq : $pagevar;\r\n\r\n	// Cached document is gzipped and then (yuk!) base64\'d if zlib is compiled in.\r\n	// Would prefer to store binary data directly but trying to insert it into a txp_prefs\r\n	// text field always gives problems on insertion and/or retrieval\r\n	if ($read_cache) {\r\n		if ($debug > 1) {\r\n			trace_add (\'[smd_xml reading cache \'.$var_data.\']\');\r\n		}\r\n		$src = $crush ? gzuncompress(base64_decode(get_pref($var_data))) : get_pref($var_data);\r\n	} else {\r\n		if ((strpos($data, \'http:\') === 0) || (strpos($data, \'https:\') === 0) || (strpos($data, \'ftp:\') === 0)) {\r\n			// The data is to be fetched from a URL.\r\n			// Has the transport mechanism been specified? If not, choose one\r\n			if (!$transport) {\r\n				if ( function_exists(\'curl_version\') ) {\r\n					$transport = \'curl\';\r\n				} else if ( function_exists(\'fsockopen\') ) {\r\n					$transport = \'fsock\';\r\n				} else {\r\n					$transport = \'\';\r\n				}\r\n			}\r\n\r\n			switch ($transport) {\r\n				case \'curl\':\r\n					$src = smd_xml_curl($data, $timeout);\r\n				break;\r\n				case \'fsock\':\r\n					$url = parse_url($data);\r\n					switch ($url[\'scheme\']) {\r\n						case \'https\':\r\n							$url[\'scheme\'] = \'ssl://\';\r\n							$url[\'port\'] = 443;\r\n						break;\r\n						case \'ftp\':\r\n							$url[\'scheme\'] = \'\';\r\n							$url[\'port\'] = 21;\r\n						break;\r\n						case \'http\':\r\n						default:\r\n							$url[\'scheme\'] = \'\';\r\n							$url[\'port\'] = 80;\r\n					}\r\n					$fp = fsockopen ($url[\'scheme\'] . $url[\'host\'], $url[\'port\'], $errno, $errstr, $timeout);\r\n\r\n					$qry = \'GET \'.$url[\'path\'] . ((isset($url[\'query\'])) ? \'?\'.$url[\'query\'] : \'\');\r\n					$qry .= \" HTTP/1.0\\r\\n\";\r\n					$qry .= \"Host: \".$url[\'host\'].\"\\r\\n\";\r\n					$qry .= \"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\\r\\n\\r\\n\"; // *shrug*\r\n\r\n					fputs($fp, $qry);\r\n					stream_set_timeout($fp, $timeout);\r\n					$info = stream_get_meta_data($fp);\r\n\r\n					$hdrs = true;\r\n					while ((!feof($fp)) && (!$info[\'timed_out\'])) {\r\n						$line = fgets($fp, $line_length);\r\n						$line = preg_replace(\"[\\r\\n]\", \"\", $line);\r\n						if ($hdrs == false) {\r\n							$src .= $line.\"\\n\";\r\n						}\r\n						if (strlen($line) == 0) $hdrs = false;\r\n					}\r\n					if ($info[\'timed_out\']) {\r\n						$src = \'\';\r\n					}\r\n					fclose($fp);\r\n				break;\r\n				case \'soap\':\r\n					ini_set(\'soap.wsdl_cache_enabled\', \'0\');\r\n					if (class_exists(\'SoapClient\')) {\r\n						$url = $data;\r\n						$url .= (stripos($url, \'?WSDL\') === false) ? \'?WSDL\' : \'\';\r\n\r\n						$client = \'\';\r\n\r\n						try {\r\n							$client = new SoapClient($url);\r\n						} catch (SoapFault $E) {\r\n							echo $E->faultstring;\r\n							$src = \'\';\r\n						}\r\n\r\n						if ($client) {\r\n							// Use explode() because do_list() removes spaces\r\n							$transport_config = explode($delim, $transport_config);\r\n							$tcfg = array();\r\n							foreach ($transport_config as $transopt) {\r\n								$topts = explode($param_delim, $transopt);\r\n								if (count($topts) == 2) {\r\n									$tcfg[$topts[0]] = $topts[1];\r\n								}\r\n							}\r\n\r\n							$tcfg[\'soap_delim\'] = isset($tcfg[\'soap_delim\']) ? $tcfg[\'soap_delim\'] : $param_delim;\r\n							$tcfg[\'soap_type_input\'] = isset($tcfg[\'soap_type_input\']) ? $tcfg[\'soap_type_input\'] : \'nvpairs\';\r\n							$tcfg[\'soap_type_output\'] = isset($tcfg[\'soap_type_output\']) ? $tcfg[\'soap_type_output\'] : \'\';\r\n							$tcfg[\'soap_numeric_wrap\'] = isset($tcfg[\'soap_numeric_wrap\']) ? $tcfg[\'soap_numeric_wrap\'] : $record;\r\n\r\n							$transItems = do_list($transport_opts, $delim);\r\n							$cliFn = $transItems[0];\r\n							$params = array();\r\n\r\n							switch ($tcfg[\'soap_type_input\']) {\r\n								case \'nvpairs\':\r\n									$param = do_list($transItems[1], $param_delim);\r\n									$num = (count($param) / 2);\r\n									for ($idx = 0; $idx < $num; $idx++) {\r\n										$key = array_shift($param);\r\n										$val = array_shift($param);\r\n										$params[$key] = $val;\r\n									}\r\n								break;\r\n								case \'xml\':\r\n									$doc = new DOMDocument();\r\n									$doc->preserveWhiteSpace = false;\r\n									$doc->loadXML($transItems[1]);\r\n									$params = smd_xml_to_array($doc->documentElement);\r\n									if (isset($tcfg[\'soap_wrap\'])) {\r\n										$params[$tcfg[\'soap_wrap\']] = $params;\r\n									}\r\n								break;\r\n							}\r\n							$resParts = do_list($transItems[2], $param_delim);\r\n							$resFn = array_shift($resParts);\r\n\r\n							$result = \'\';\r\n							try {\r\n							   $result = $client->$cliFn($params);\r\n							} catch (SoapFault $E) {\r\n							   echo $E->faultstring;\r\n							}\r\n\r\n							if ($result) {\r\n								if ($resFn) {\r\n									try {\r\n										$src = $result->$resFn;\r\n										if ($resParts) {\r\n											$srcbobs = array();\r\n											foreach ($resParts as $resIdx) {\r\n												$srcbobs[] = $src->$resIdx;\r\n											}\r\n\r\n											$src = join($tcfg[\'soap_delim\'], $srcbobs);\r\n										}\r\n										switch ($tcfg[\'soap_type_output\']) {\r\n											case \'xml\':\r\n												// Oh lordy me -- just to convert stdClass:: format to associative array\r\n												$xmlbld = new smd_xml_build_data(json_decode(json_encode($src), true), $datawrap, $tcfg[\'soap_numeric_wrap\']);\r\n												$soap_wrapped = true;\r\n												$src = $xmlbld->getData();\r\n											break;\r\n										}\r\n									} catch (SoapFault $E) {\r\n									   echo $E->faultstring;\r\n									}\r\n								} else {\r\n									$src = $result;\r\n								}\r\n							} else {\r\n								$src = \'\';\r\n							}\r\n						}\r\n					} else {\r\n						$src = \'\';\r\n					}\r\n				break;\r\n				default:\r\n					$src = \'\';\r\n			}\r\n		} else  {\r\n			// Assume data is presented in raw XML\r\n			$src = $data;\r\n		}\r\n	}\r\n\r\n	// Remove inter-tag whitespace: highly recommended, but makes the feed less easy to debug\r\n	// so you may elect to turn it off while testing\r\n	if ($kill_spaces) {\r\n		$src = preg_replace(\"/>\".\"[[:space:]]+\".\"</i\", \"><\", $src);\r\n	}\r\n\r\n	// Perform transformations on the fetched source\r\n	if ($transform) {\r\n		$xforms = do_list($transform, $delim);\r\n		foreach ($xforms as $xform) {\r\n			$tops = do_list($xform, $param_delim);\r\n			$ttyp = array_shift($tops);\r\n			switch ($ttyp) {\r\n				case \'xsl\':\r\n					$xsl = smd_xml_curl($tops[0], $timeout);\r\n					$src = smd_xml_xsl_transform($src, $xsl);\r\n				break;\r\n				case \'replace\':\r\n					$src = preg_replace($tops[0], (isset($tops[1]) ? $tops[1] : \'\'), $src);\r\n				break;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Store the current document in the cache and datestamp it\r\n	if ($cache_time > 0 && !$read_cache) {\r\n		$srcinfo = $crush ? base64_encode(gzcompress($src)) : doSlash($src);\r\n		set_pref($var_lastmod, time(), \'smd_xml\', PREF_HIDDEN, \'text_input\');\r\n		set_pref($var_data, $srcinfo, \'smd_xml\', PREF_HIDDEN, \'text_input\');\r\n	}\r\n\r\n	// Make up a replacement array for decoded entities...\r\n	$conversions = array();\r\n	$convert = do_list($convert, $delim);\r\n	foreach ($convert as $pair) {\r\n		if (empty($pair)) continue;\r\n\r\n		$pair = do_list($pair, $param_delim);\r\n		$conversions[$pair[0]] = $pair[1];\r\n	}\r\n\r\n	if ($src && ($debug > 1)) {\r\n		trace_add (\'[smd_xml conversions: \' . print_r($conversions, true) . \']\');\r\n	}\r\n\r\n	// ... and replace them\r\n	$src = strtr($src, $conversions);\r\n\r\n	// Wrap if necessary\r\n	if ($datawrap && !$soap_wrapped) {\r\n		$src = \"<$datawrap>$src</$datawrap>\";\r\n	}\r\n\r\n	if ($debug > 2) {\r\n		trace_add (\'[smd_xml filtered source: \' . $src . \']\');\r\n	}\r\n\r\n	// Set up any ontag processing\r\n	$ontagstart = do_list($ontagstart);\r\n	$ontagend = do_list($ontagend);\r\n	$watchStart = $watchEnd = $watchForm = array();\r\n	foreach ($ontagstart as $ontag) {\r\n		if ($ontag == \'\') continue;\r\n		$parts = explode($param_delim, $ontag);\r\n		$frm = array_shift($parts);\r\n		$watchStart[$frm] = $parts;\r\n		$watchForm[$frm] = fetch_form($frm);\r\n	}\r\n	foreach ($ontagend as $ontag) {\r\n		if ($ontag == \'\') continue;\r\n		$parts = explode($param_delim, $ontag);\r\n		$frm = array_shift($parts);\r\n		$watchEnd[$frm] = $parts;\r\n		$watchForm[$frm] = fetch_form($frm);\r\n	}\r\n\r\n	if ($src && ($debug > 1)) {\r\n		trace_add (\'[smd_xml start watchers: \' . print_r($watchStart, true) . \']\');\r\n		trace_add (\'[smd_xml end watchers: \' . print_r($watchEnd, true) . \']\');\r\n		trace_add (\'[smd_xml watch forms: \' . print_r($watchForm, true) . \']\');\r\n	}\r\n\r\n	// Set up any defaults\r\n	$defaults = do_list($defaults, $delim);\r\n	$dflts = array();\r\n	foreach ($defaults as $dflt) {\r\n		if ($dflt == \'\') continue;\r\n		$parts = explode($param_delim, $dflt);\r\n		$dflts[$parts[0]] = $parts[1];\r\n	}\r\n	$defaults = $dflts;\r\n\r\n	// Set up any formatting\r\n	$format = do_list($format, $delim);\r\n	$formats = array();\r\n	foreach ($format as $frmdef) {\r\n		if ($frmdef == \'\') continue;\r\n		$parts = explode($param_delim, $frmdef);\r\n		$formats[\'type\'][$parts[0]] = $parts[1];\r\n		for($idx = 0; $idx < count($parts)-2; $idx++) {\r\n			$formats[\'data\'][$parts[0]][] = $parts[$idx+2];\r\n		}\r\n	}\r\n\r\n	// Set up any matches\r\n	$match = do_list($match, $delim);\r\n	$matches = array();\r\n	foreach ($match as $item) {\r\n		if ($item == \'\') continue;\r\n		$parts = explode($param_delim, $item);\r\n		$matches[$parts[0]] = $parts[1];\r\n	}\r\n	$match = $matches;\r\n\r\n	if ($src && ($debug > 1)) {\r\n		if ($defaults) {\r\n			trace_add (\'[smd_xml defaults: \' . print_r($defaults, true) . \']\');\r\n		}\r\n		if ($formats) {\r\n			trace_add (\'[smd_xml formats: \' . print_r($formats, true) . \']\');\r\n		}\r\n		if ($match) {\r\n			trace_add (\'[smd_xml matches: \' . print_r($match, true) . \']\');\r\n		}\r\n	}\r\n\r\n	if (!empty($src)) {\r\n		// Paging information\r\n		$rowinfo[\'numrecs\'] = substr_count($src, \'<\'.$record);\r\n		$rowinfo[\'page_rowcnt\'] = 0;\r\n		$rowinfo[\'limit\'] = ($limit < $rowinfo[\'numrecs\']) ? $limit : 0;\r\n		if ($offset >= 0) {\r\n			if ($offset < $rowinfo[\'numrecs\']) {\r\n				$rowinfo[\'offset\'] = $offset;\r\n			} else {\r\n				$rowinfo[\'offset\'] = $rowinfo[\'numrecs\'];\r\n				$rowinfo[\'limit\'] = 0;\r\n			}\r\n		} else {\r\n			$negoff = $rowinfo[\'numrecs\'] + $offset;\r\n			if ($negoff > 0) {\r\n				$rowinfo[\'offset\'] = $negoff;\r\n			} else {\r\n				$rowinfo[\'offset\'] = 0;\r\n				$rowinfo[\'limit\'] = $rowinfo[\'numrecs\'];\r\n			}\r\n		}\r\n\r\n		// Re-assign the atts in case they\'ve been changed by reaching the bounds of the document\r\n		$offset = $rowinfo[\'offset\'];\r\n		$limit = $rowinfo[\'limit\'];\r\n\r\n		if ($limit > 0) {\r\n			$keepsafe = $thispage;\r\n			$rowinfo[\'total\'] = $rowinfo[\'numrecs\'] - $offset;\r\n			$rowinfo[\'numPages\'] = ceil($rowinfo[\'total\'] / $limit);\r\n			$rowinfo[\'pg\'] = (!gps($pagevar)) ? 1 : gps($pagevar);\r\n			$rowinfo[\'pgoffset\'] = $offset + (($rowinfo[\'pg\'] - 1) * $limit);\r\n			$rowinfo[\'prevpg\'] = (($rowinfo[\'pg\']-1) > 0) ? $rowinfo[\'pg\']-1 : \'\';\r\n			$rowinfo[\'nextpg\'] = (($rowinfo[\'pg\']+1) <= $rowinfo[\'numPages\']) ? $rowinfo[\'pg\']+1 : \'\';\r\n			$rowinfo[\'pagerows\'] = ($rowinfo[\'pg\'] == $rowinfo[\'numPages\']) ? $rowinfo[\'total\']-($limit * ($rowinfo[\'numPages\']-1)) : $limit;\r\n			$rowinfo[\'unique_id\'] = $uniq;\r\n\r\n			// send paging info to txp:newer and txp:older\r\n			$pageout[\'pg\'] = $rowinfo[\'pg\'];\r\n			$pageout[\'numPages\'] = $rowinfo[\'numPages\'];\r\n			$pageout[\'s\'] = $pretext[\'s\'];\r\n			$pageout[\'c\'] = $pretext[\'c\'];\r\n			$pageout[\'grand_total\'] = $rowinfo[\'numrecs\'];\r\n			$pageout[\'total\'] = $rowinfo[\'total\'];\r\n			$thispage = $pageout;\r\n		} else {\r\n			$rowinfo[\'pgoffset\'] = $offset;\r\n		}\r\n\r\n		$rowinfo[\'running_rowcnt\'] = $rowinfo[\'pgoffset\']-$offset;\r\n		$rowinfo[\'first_rec\'] = $rowinfo[\'running_rowcnt\'] + 1;\r\n		$rowinfo[\'last_rec\'] = ($limit > 0) ? $rowinfo[\'first_rec\'] + $rowinfo[\'pagerows\'] - 1 : $rowinfo[\'numrecs\'];\r\n		if ($limit > 0) {\r\n			$rowinfo[\'prev_rows\'] = (($rowinfo[\'prevpg\']) ? $limit : 0);\r\n			$rowinfo[\'next_rows\'] = (($rowinfo[\'nextpg\']) ? (($rowinfo[\'last_rec\']+$limit+1) > $rowinfo[\'total\'] ? $rowinfo[\'total\']-$rowinfo[\'last_rec\'] : $limit) : 0);\r\n		}\r\n\r\n		if ($debug > 0) {\r\n			trace_add (\'[smd_xml paging info: \' . print_r($rowinfo, true) . \']\');\r\n		}\r\n\r\n		// Do the dirty XML deed\r\n		$ref = new smd_xml_parser(array(\r\n			\'src\'          => $src,\r\n			\'delim\'        => $delim,\r\n			\'param_delim\'  => $param_delim,\r\n			\'tag_delim\'    => $tag_delim,\r\n			\'concat\'       => $concat,\r\n			\'concat_delim\' => $concat_delim,\r\n			\'fields\'       => $fields,\r\n			\'skip\'         => $skip,\r\n			\'match\'        => $match,\r\n			\'record\'       => $record,\r\n			\'casefold\'     => $uppercase,\r\n			\'target_enc\'   => $target_enc,\r\n			\'tag_prefix\'   => $tag_prefix,\r\n			\'page_prefix\'  => $page_prefix,\r\n			\'defaults\'     => $defaults,\r\n			\'load_atts\'    => $load_atts,\r\n			\'watchStart\'   => $watchStart,\r\n			\'watchEnd\'     => $watchEnd,\r\n			\'watchForm\'    => $watchForm,\r\n			\'set_empty\'    => $set_empty,\r\n			\'formats\'      => $formats,\r\n			\'thing\'        => $thing,\r\n			\'rinfo\'        => $rowinfo,\r\n			\'debug\'        => $debug,\r\n		));\r\n\r\n		// Grab the parsed results\r\n		$result = $ref->getResults();\r\n\r\n		// Create the page form\r\n		$pageblock = \'\';\r\n		$finalout = $repagements = array();\r\n\r\n		if ($rowinfo[\'limit\'] > 0) {\r\n			$repagements[\'{\'.$page_prefix.\'totalrecs}\'] = $rowinfo[\'total\'];\r\n			$repagements[\'{\'.$page_prefix.\'pagerecs}\']  = $rowinfo[\'pagerows\'];\r\n			$repagements[\'{\'.$page_prefix.\'pages}\']     = $rowinfo[\'numPages\'];\r\n			$repagements[\'{\'.$page_prefix.\'prevpage}\']  = $rowinfo[\'prevpg\'];\r\n			$repagements[\'{\'.$page_prefix.\'thispage}\']  = $rowinfo[\'pg\'];\r\n			$repagements[\'{\'.$page_prefix.\'nextpage}\']  = $rowinfo[\'nextpg\'];\r\n			$repagements[\'{\'.$page_prefix.\'rec_start}\'] = $rowinfo[\'first_rec\'];\r\n			$repagements[\'{\'.$page_prefix.\'rec_end}\']   = $rowinfo[\'last_rec\'];\r\n			$repagements[\'{\'.$page_prefix.\'recs_prev}\'] = $rowinfo[\'prev_rows\'];\r\n			$repagements[\'{\'.$page_prefix.\'recs_next}\'] = $rowinfo[\'next_rows\'];\r\n			$repagements[\'{\'.$page_prefix.\'unique_id}\'] = $rowinfo[\'unique_id\'];\r\n			$smd_xml_pginfo = $repagements;\r\n			$pageblock = parse(strtr($pageform, $repagements));\r\n		}\r\n\r\n		// Make up the final output\r\n		if (in_array(\"above\", $pagebit)) {\r\n			$finalout[] = $pageblock;\r\n		}\r\n		$finalout[] = doWrap($result, $wraptag, $break, $class);\r\n		if (in_array(\"below\", $pagebit)) {\r\n			$finalout[] = $pageblock;\r\n		}\r\n\r\n		// Restore the paging outside the plugin container\r\n		if ($limit > 0) {\r\n			$thispage = $keepsafe;\r\n		}\r\n\r\n		return join(\'\', $finalout);\r\n	} else {\r\n		return \'\';\r\n	}\r\n}\r\n\r\n// Convenience tags to check if there\'s a prev/next page defined. Could also use smd_if\r\nfunction smd_xml_if_prev($atts, $thing) {\r\n	global $smd_xml_pginfo;\r\n\r\n	$res = $smd_xml_pginfo && $smd_xml_pginfo[\'{smd_xml_prevpage}\'] != \'\';\r\n	return parse(EvalElse(strtr($thing, $smd_xml_pginfo), $res));\r\n}\r\nfunction smd_xml_if_next($atts, $thing) {\r\n	global $smd_xml_pginfo;\r\n\r\n	$res = $smd_xml_pginfo && $smd_xml_pginfo[\'{smd_xml_nextpage}\'] != \'\';\r\n	return parse(EvalElse(strtr($thing, $smd_xml_pginfo), $res));\r\n}\r\n\r\n/*****************\r\n FUNCTION LIBRARY\r\n*****************/\r\n// Retrieve a resource via curl; return false otherwise\r\nfunction smd_xml_curl($data, $timeout=10) {\r\n	$ret = false;\r\n\r\n	if (function_exists(\'curl_version\')) {\r\n		$c = curl_init();\r\n		curl_setopt($c, CURLOPT_URL, $data);\r\n		curl_setopt($c, CURLOPT_REFERER, hu);\r\n		curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\r\n		curl_setopt($c, CURLOPT_VERBOSE, false);\r\n		curl_setopt($c, CURLOPT_TIMEOUT, $timeout);\r\n		$ret = curl_exec($c);\r\n	}\r\n\r\n	return $ret;\r\n}\r\n\r\n// Transform an XML document using the given XSL stylesheet\r\nfunction smd_xml_xsl_transform($xml, $xsl) {\r\n	$ret = $xml;\r\n\r\n	if (class_exists(\'XSLTProcessor\')) {\r\n		$xslt = new XSLTProcessor();\r\n		$xslt->importStylesheet(new SimpleXMLElement($xsl));\r\n		$ret = $xslt->transformToXml(new SimpleXMLElement($xml));\r\n	}\r\n\r\n	return $ret;\r\n}\r\n\r\n// Convert XML document to associative array\r\n// (from http://stackoverflow.com/questions/99350/php-associative-arrays-to-and-from-xml)\r\nfunction smd_xml_to_array($curr_node) {\r\n	$val_array = array();\r\n	$typ_array = array();\r\n\r\n	foreach($curr_node->childNodes as $node) {\r\n		if ($node->nodeType == XML_ELEMENT_NODE) {\r\n			$val = smd_xml_to_array($node);\r\n			if (array_key_exists($node->tagName, $val_array)) {\r\n				if (!is_array($val_array[$node->tagName]) || $type_array[$node->tagName] == \'hash\') {\r\n					$existing_val = $val_array[$node->tagName];\r\n					unset($val_array[$node->tagName]);\r\n					$val_array[$node->tagName][0] = $existing_val;\r\n					$type_array[$node->tagName] = \'array\';\r\n				}\r\n				$val_array[$node->tagName][] = $val;\r\n			} else {\r\n				$val_array[$node->tagName] = $val;\r\n				if (is_array($val)) {\r\n					$type_array[$node->tagName] = \'hash\';\r\n				}\r\n			} // end if array key exists\r\n		} // end if element node\r\n	} // end for each\r\n\r\n	if (count($val_array) == 0) {\r\n		return $curr_node->nodeValue;\r\n	} else {\r\n		return $val_array;\r\n	}\r\n}\r\n\r\n// Build an XML data set from associative array\r\nclass smd_xml_build_data {\r\n	private $xml, $last_idx, $recWrap;\r\n\r\n	function smd_xml_build_data ($data, $startElement, $recWrap, $xml_version = \'1.0\', $xml_encoding = \'UTF-8\') {\r\n		$startElement = ($startElement) ? $startElement : \'fx_request\';\r\n		if (!is_array($data)) {\r\n			$err = \'Invalid variable type supplied, expected array not found on line \'.__LINE__.\" in Class: \".__CLASS__.\" Method: \".__METHOD__;\r\n			trigger_error($err);\r\n			return false;\r\n		}\r\n		$this->xml = new XmlWriter();\r\n		$this->xml->openMemory();\r\n		$this->xml->startDocument($xml_version, $xml_encoding);\r\n		$this->xml->startElement($startElement);\r\n\r\n		$this->last_idx = 0;\r\n		$this->recWrap = $recWrap;\r\n		$this->write($this->xml, $data, $startElement);\r\n\r\n		$this->xml->endElement();\r\n	}\r\n\r\n	// Standard getter\r\n	function getData() {\r\n		return $this->xml->outputMemory(true);\r\n	}\r\n\r\n	// Recurse array elements and build XML tag tree\r\n	function write(XMLWriter $xml, $data, $parent) {\r\n		foreach ($data as $key => $value) {\r\n			// Nodes that aren\'t valid attributes get given an array index\r\n			if (!preg_match(\'/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/\', $key)) {\r\n				$key = ($this->recWrap) ? $this->recWrap : ($parent . \'_\' . (is_numeric($key) ? $key : $last_idx));\r\n			}\r\n			if (is_array($value)) {\r\n				$xml->startElement($key);\r\n				$this->write($xml, $value, $key);\r\n				$xml->endElement();\r\n				continue;\r\n			}\r\n			$xml->writeElement($key, $value);\r\n			$this->last_idx++;\r\n		}\r\n	}\r\n}\r\n\r\n// Dirty XML work done here\r\nclass smd_xml_parser {\r\n	private $data, $rec;\r\n	private $fields, $subfields, $treefields;\r\n	private $skip, $match, $defaults, $set_empty;\r\n	private $casefold, $outenc;\r\n	private $load_atts, $watchStart, $watchEnd, $watchForm;\r\n	private $formats, $tag_prefix, $page_prefix;\r\n\r\n	private $intag, $indata;\r\n	private $skiptag, $xmltag, $xmlatts, $xmldata;\r\n	private $thing, $out;\r\n	private $delim, $concat, $tdelim, $cdelim;\r\n	private $rowinfo, $show_record;\r\n	private $debug;\r\n\r\n	/**\r\n	* constructor\r\n	*/\r\n	function __construct($atts) {\r\n		$this->data        = $atts[\'src\'];\r\n		$this->delim       = $atts[\'delim\'];\r\n		$this->tdelim      = $atts[\'tag_delim\'];\r\n		$this->concat      = $atts[\'concat\'];\r\n		$this->cdelim      = $atts[\'concat_delim\'];\r\n		$this->fields      = do_list($atts[\'fields\'], $this->delim);\r\n		$this->skip        = do_list($atts[\'skip\'], $this->delim);\r\n		$this->match       = $atts[\'match\'];\r\n		$this->rec         = $atts[\'record\'];\r\n		$this->casefold    = $atts[\'casefold\'];\r\n		$this->outenc      = $atts[\'target_enc\'];\r\n		$this->tag_prefix  = $atts[\'tag_prefix\'];\r\n		$this->page_prefix = $atts[\'page_prefix\'];\r\n		$this->defaults    = $atts[\'defaults\'];\r\n		$this->load_atts   = $atts[\'load_atts\'];\r\n		$this->watchStart  = $atts[\'watchStart\'];\r\n		$this->watchEnd    = $atts[\'watchEnd\'];\r\n		$this->watchForm   = $atts[\'watchForm\'];\r\n		$this->set_empty   = $atts[\'set_empty\'];\r\n		$this->formats     = $atts[\'formats\'];\r\n		$this->thing       = $atts[\'thing\'];\r\n		$this->rowinfo     = $atts[\'rinfo\'];\r\n		$this->debug       = $atts[\'debug\'];\r\n\r\n		$this->subfields  = array();\r\n		$this->treefields = array();\r\n		$this->tagtree    = array();\r\n		$this->xmldata    = array();\r\n		$this->out        = array();\r\n		$this->intag      = false;\r\n		$this->exists     = false;\r\n		$this->skiptag    = \'\';\r\n		$this->xmltag     = \'\';\r\n		$this->xmltatts   = \'\';\r\n\r\n		// Copy any tree- and sub-fields out of the list\r\n		foreach ($this->fields as $key => $fld) {\r\n			$sf = do_list($fld, $this->tdelim);\r\n			$tf = do_list($sf[0], \'->\');\r\n			$numSFs = count($sf);\r\n			$numTFs = count($tf);\r\n\r\n			// First subfield needs tree portion removing, and\r\n			// last treefield needs removing (\'cos it\'s the first subfield)\r\n			$sf[0] = $tf[$numTFs-1];\r\n			unset($tf[$numTFs-1]);\r\n			$numTFs--;\r\n\r\n			// Build the tree view.\r\n			// The tree holds the path from the sub-node back to the top, so when\r\n			// a leaf is encountered its path can be looked up as $treefields[$leaf]\r\n			// and be path verified to see if it matches a wanted leaf\r\n			foreach ($sf as $sub) {\r\n				$this->treefields[$sub] = $tf;\r\n			}\r\n\r\n			// The subfields are built as a pair of arrays -- one flat, one indexed by parent field -- for searching later\r\n			$this->byfields[$sf[0]] = array_slice($sf, 1);\r\n			$this->subfields = array_merge($this->subfields, $sf);\r\n\r\n			// Make sure the field only holds the zeroth entry\r\n			$this->fields[$key] = ($tf) ? $tf[0] : $sf[0];\r\n\r\n			// and make sure the root node of any tree is not added\r\n			// as checkable to the tree itself\r\n			$this->treefields[$this->fields[$key]] = array();\r\n		}\r\n		if ($this->debug > 1) {\r\n			trace_add (\'[smd_xml fields: \' . print_r($this->fields, true) . \']\');\r\n			trace_add (\'[smd_xml subfields: \' . print_r($this->subfields, true) . \']\');\r\n			trace_add (\'[smd_xml indexed subfields: \' . print_r($this->byfields, true) . \']\');\r\n			trace_add (\'[smd_xml tree fields: \' . print_r($this->treefields, true) . \']\');\r\n		}\r\n		$this->parse();\r\n	}\r\n\r\n	public function getResults() {\r\n		if ($this->out) {\r\n			return $this->out;\r\n		} else {\r\n			return \'\';\r\n		}\r\n	}\r\n\r\n	private function parse() {\r\n		$xmlparser = xml_parser_create();\r\n		xml_set_object($xmlparser, $this);\r\n		xml_parser_set_option($xmlparser, XML_OPTION_CASE_FOLDING, $this->casefold);\r\n		xml_parser_set_option($xmlparser, XML_OPTION_TARGET_ENCODING, $this->outenc);\r\n		xml_set_default_handler($xmlparser, \"smd_xml_default\");\r\n		xml_set_element_handler($xmlparser, \"smd_xml_start_tag\", \"smd_xml_end_tag\");\r\n		xml_set_character_data_handler($xmlparser, \"smd_xml_tag_contents\");\r\n		xml_parse($xmlparser, $this->data);\r\n		xml_parser_free($xmlparser);\r\n	}\r\n\r\n	// Do nothing with default (non-XML) data. Just report it in debug mode\r\n	// TODO: allow some callback / Form to handle this type of data\r\n	private function smd_xml_default($parser, $data) {\r\n		if ($this->debug > 1) {\r\n			trace_add (\'[smd_xml default data: \' . print_r($data, true) . \']\');\r\n		}\r\n	}\r\n\r\n	// Start of XML tag\r\n	private function smd_xml_start_tag($parser, $name, $attribs) {\r\n		array_push($this->tagtree, $name);\r\n\r\n		$pgval = $this->rowinfo[\'pgoffset\'] - 1;\r\n		$lim = $this->rowinfo[\'limit\'] > 0;\r\n\r\n		// Is this record to be processed? i.e. is it within this page\'s limit/offset?\r\n		$this->show_record = $lim ? (($this->rowinfo[\'page_rowcnt\'] > $pgval) && ($this->rowinfo[\'page_rowcnt\'] <= $pgval + $this->rowinfo[\'pagerows\'])) : $this->rowinfo[\'page_rowcnt\'] > $pgval;\r\n\r\n		// Check the tree if necessary\r\n		$tree_ok = true;\r\n		if (array_key_exists($name, $this->treefields) && !empty($this->treefields[$name])) {\r\n			$treeSize = count($this->treefields[$name]);\r\n			$treeOffset = \'-\' .$treeSize - 1;\r\n			// If there\'s no difference, the tree is ok\r\n			$tree_ok = ( array_diff ($this->treefields[$name], array_slice($this->tagtree, $treeOffset, $treeSize) ) ) ? false : true;\r\n			if ($this->debug > 2) {\r\n				trace_add (\'[smd_xml tree check on field: \' . $name . \']\');\r\n				trace_add (\'[smd_xml tree compare: \' . print_r($this->treefields[$name], true) . \']\');\r\n				trace_add (\'[smd_xml tree with: \' . print_r($this->tagtree, true) . \']\');\r\n				trace_add (\'[smd_xml tree result: \' . ($tree_ok ? \'YES\' : \'NO\') . \']\');\r\n			}\r\n		}\r\n\r\n		// Start of a wanted record: flag this situation and grab any attributes\r\n		if ( ($name == $this->rec) && $this->show_record ) {\r\n			$this->intag = true;\r\n			$this->xmlatts[$name] = $attribs;\r\n			if ($this->load_atts == \'start\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n		}\r\n\r\n		// We\'re inside a wanted record\r\n		if ($this->intag && $tree_ok) {\r\n			if (in_array($name, $this->skip)) {\r\n				$this->xmltag = \'\';\r\n				$this->xmlatts[$name] = array();\r\n				$this->skiptag = $name;\r\n			} else {\r\n				$this->xmltag = $name;\r\n				$this->xmlatts[$name] = $attribs;\r\n				if ( ($this->load_atts == \'start\') && ($name != $this->rec) ) {\r\n					$this->smd_xml_store_attribs($name);\r\n				}\r\n\r\n				// Process any ontagstart\r\n				foreach ($this->watchStart as $frm => $watchlist) {\r\n					if (in_array($name, $watchlist)) {\r\n						$op = trim(parse(strtr($this->watchForm[$frm], $this->xmldata)));\r\n						if ($op != \'\') {\r\n							$this->out[] = $op;\r\n						}\r\n					}\r\n				}\r\n				if ($this->concat && isset($this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'])) {\r\n					$this->exists = true;\r\n				} else {\r\n					$this->exists = false;\r\n				}\r\n			}\r\n		}\r\n		$this->indata = false;\r\n	}\r\n\r\n	// End of XML tag\r\n	private function smd_xml_end_tag($parser, $name) {\r\n		// End of a regular/attribute-only/container/self-closing tag\r\n		if ( ($name != $this->rec) && ($name != $this->skiptag) && $this->intag && in_array($name, array_merge($this->fields, $this->subfields)) ) {\r\n			$this->xmltag = $name;\r\n			if ($this->load_atts == \'end\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n\r\n			// Process any ontagend\r\n			foreach ($this->watchEnd as $frm => $watchlist) {\r\n				if (in_array($name, $watchlist)) {\r\n					$op = trim(parse(strtr($this->watchForm[$frm], $this->xmldata)));\r\n					if ($op != \'\') {\r\n						$this->out[] = $op;\r\n					}\r\n				}\r\n			}\r\n		}\r\n\r\n		// End of the record\r\n		if ($name == $this->rec && $name != $this->skiptag) {\r\n			$this->intag = false;\r\n			if ($this->load_atts == \'end\') {\r\n				$this->smd_xml_store_attribs($name);\r\n			}\r\n\r\n			$matched = true;\r\n			if (array_key_exists($this->xmltag, $this->match)) {\r\n				$matched = preg_match($this->match[$this->xmltag], $this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\']);\r\n			}\r\n			if ($matched) {\r\n				$lim = ($this->rowinfo[\'limit\'] > 0) ? true : false;\r\n\r\n				// Append row counter information\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'totalrecs}\'] = $lim ? $this->rowinfo[\'total\'] : $this->rowinfo[\'numrecs\'] - $this->rowinfo[\'pgoffset\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'pagerecs}\']  = $lim ? $this->rowinfo[\'pagerows\'] : $this->xmldata[\'{\'.$this->page_prefix.\'totalrecs}\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'pages}\']     = $lim ? $this->rowinfo[\'numPages\'] : 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thispage}\']  = $lim ? $this->rowinfo[\'pg\'] : 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thisindex}\'] = $this->rowinfo[\'page_rowcnt\'] - $this->rowinfo[\'offset\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'thisrec}\']   = $this->rowinfo[\'page_rowcnt\'] - $this->rowinfo[\'offset\'] + 1;\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'runindex}\']  = $this->rowinfo[\'running_rowcnt\'];\r\n				$this->xmldata[\'{\'.$this->page_prefix.\'runrec}\']    = $this->rowinfo[\'running_rowcnt\'] + 1;\r\n\r\n				$sfields = array_unique(array_merge($this->fields, $this->subfields));\r\n\r\n				// Set any tag contents to a default value, if specified\r\n				if ($this->defaults || $this->set_empty) {\r\n					foreach ($sfields as $field) {\r\n						if (!isset($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'])) {\r\n							if (array_key_exists($field, $this->defaults)) {\r\n								$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = $this->defaults[$field];\r\n							} else if ($this->set_empty) {\r\n								$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = \'\';\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				// Reformat any fields, if specified\r\n				if ($this->formats) {\r\n					foreach ($sfields as $field) {\r\n						if (isset($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']) && array_key_exists($field, $this->formats[\'type\'])) {\r\n							switch ($this->formats[\'type\'][$field]) {\r\n								case \'date\':\r\n									$nd = (is_numeric($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'])) ? $this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] : strtotime($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									if ($nd !== false) {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strftime($this->formats[\'data\'][$field][0], $nd);\r\n									}\r\n									break;\r\n								case \'link\':\r\n									// From http://codesnippets.joyent.com/posts/show/2104\r\n									$pat = \"@\\b(https?://)?(([0-9a-zA-Z_!~*\'().&=+$%-]+:)?[0-9a-zA-Z_!~*\'().&=+$%-]+\\@)?(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*\'()-]+\\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*\'().;?:\\@&=+$,%#-]+)*/?)@\";\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = preg_replace($pat, \'<a href=\"$0\">$0</a>\', $this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									break;\r\n								case \'escape\':\r\n									$flags = ENT_XML1;\r\n									if (isset($this->formats[\'data\'][$field][0])) {\r\n										switch ($this->formats[\'data\'][$field][0]) {\r\n											case \'no_quotes\':\r\n												$flags |= ENT_NOQUOTES;\r\n											break;\r\n											case \'all_quotes\':\r\n												$flags |= ENT_QUOTES;\r\n											break;\r\n											case \'double_quotes\':\r\n											default:\r\n												$flags |= ENT_COMPAT;\r\n											break;\r\n										}\r\n									}\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = htmlspecialchars($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'], $flags);\r\n									break;\r\n								case \'fordb\':\r\n									$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = doSlash($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									break;\r\n								case \'sanitize\':\r\n									if ($this->formats[\'data\'][$field][0] == \"url\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = sanitizeForUrl($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									} else if ($this->formats[\'data\'][$field][0] == \"file\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = sanitizeForFile($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n									} else if ($this->formats[\'data\'][$field][0] == \"url_title\") {\r\n										$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = stripSpace($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'], 1);\r\n									}\r\n									break;\r\n								case \'case\':\r\n									for ($idx = 0; $idx < count($this->formats[\'data\'][$field]); $idx++) {\r\n										if ($this->formats[\'data\'][$field][$idx] == \"upper\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strtoupper($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"lower\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = strtolower($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"ucfirst\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = ucfirst($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										} else if ($this->formats[\'data\'][$field][$idx] == \"ucwords\") {\r\n											$this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\'] = ucwords($this->xmldata[\'{\'.$this->tag_prefix.$field.\'}\']);\r\n										}\r\n									}\r\n									break;\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				if ($this->debug > 0 && $this->show_record) {\r\n					trace_add (\'[smd_xml replacements for record \'.($this->rowinfo[\'running_rowcnt\'] + 1).\':\' . print_r($this->xmldata, true) . \']\');\r\n				}\r\n			}\r\n\r\n			if ($this->show_record && $matched) {\r\n				$this->out[] = parse(strtr($this->thing, $this->xmldata));\r\n			}\r\n\r\n			// Prepare for next record iteration\r\n			$this->rowinfo[\'running_rowcnt\'] = $this->rowinfo[\'running_rowcnt\']+1;\r\n			$this->rowinfo[\'page_rowcnt\'] = $this->rowinfo[\'page_rowcnt\']+1;\r\n			$this->xmldata = array();\r\n			$this->indata = false;\r\n		}\r\n		if ($name == $this->skiptag) {\r\n			$this->skiptag = \'\';\r\n		}\r\n		array_pop($this->tagtree);\r\n	}\r\n\r\n	// Node data/text that is not an XML tag\r\n	private function smd_xml_tag_contents($parser, $data) {\r\n		if ($this->intag && !$this->skiptag) {\r\n			if ($this->debug > 1) {\r\n				trace_add (\'[smd_xml tag:\' . $this->xmltag . \']\');\r\n				trace_add (\'[smd_xml tag data:\' . print_r($data, true) . \']\');\r\n			}\r\n			if (in_array($this->xmltag, array_merge($this->fields, $this->subfields))) {\r\n				if ($this->indata) {\r\n					// Annoying logic, but necessary since the parser may not split at a tag\r\n					// boundary so we need to know if we\'re already in some data block and append.\r\n					// If not we can safely start a new xmldata node for this tag\r\n					if ($this->exists) {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $this->cdelim.$data;\r\n					} else {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $data;\r\n					}\r\n				} else {\r\n					if ($this->exists) {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] .= $this->cdelim.$data;\r\n					} else {\r\n						$this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'] = $data;\r\n					}\r\n				}\r\n\r\n				// Copy the tag to any duplicate (alias) nodes\r\n				if (array_key_exists($this->xmltag, $this->byfields)) {\r\n					foreach($this->byfields[$this->xmltag] as $copyfield) {\r\n						if (!isset($this->xmldata[\'{\'.$this->tag_prefix.$copyfield.\'}\'])) {\r\n							$this->xmldata[\'{\'.$this->tag_prefix.$copyfield.\'}\'] = $this->xmldata[\'{\'.$this->tag_prefix.$this->xmltag.\'}\'];\r\n						}\r\n					}\r\n				}\r\n				$this->indata = true;\r\n			}\r\n		}\r\n	}\r\n\r\n	// Create any attribute nodes\r\n	private function smd_xml_store_attribs($name) {\r\n		if ($this->xmlatts[$name]) {\r\n			foreach ($this->xmlatts[$name] as $xkey => $xval) {\r\n				// Append if attribute previously encountered\r\n				if ($this->concat && isset($this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'])) {\r\n					$this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'] .= $this->cdelim.$xval;\r\n				} else {\r\n					$this->xmldata[\'{\'.$this->tag_prefix.$name.$this->tdelim.$xkey.\'}\'] = $xval;\r\n				}\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\n\r\n/*\r\n\r\n// JSON reader from tumblr: adapt and genericise?\r\n\r\n$offset = $_GET[\'offset\'];\r\n$ch = curl_init();\r\ncurl_setopt($ch,CURLOPT_URL,\"http://blog.dgovil.com/api/read/json?num=10&start=\".$offset);\r\ncurl_setopt($ch,CURLOPT_RETURNTRANSFER,true);\r\n$result = curl_exec($ch);\r\ncurl_close($ch);\r\n\r\n$result = str_replace(\"var tumblr_api_read = \",\"\",$result);\r\n$result = str_replace(\';\',\'\',$result);\r\n$result = str_replace(\'\\u00a0\',\'&amp;nbsp;\',$result);\r\n\r\n$jsondata = json_decode($result,true);\r\n$posts = $jsondata[\'posts\'];\r\n\r\n\r\nforeach($posts as $post){   ?>\r\n<div class=\"tumblr_post post-<?php echo $post[\'type\'] ?>\">\r\n\r\n    <?php if ($post[\'type\'] == \'regular\') { ?>\r\n        <div class=\"post-title\" id=\"post-<?php echo $post[\'id\'];?>\"><a href=\"<?php echo $post[\'url-with-slug\']; ?>\"><?php echo $post{\'regular-title\'}; ?></a></div>\r\n    <?php echo $post{\'regular-body\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'quote\') {  ?>\r\n        <?php echo $post{\'quote-text\'}; ?>\r\n        <?php echo $post{\'quote-source\'}; ?>\r\n      <?php } ?>\r\n\r\n\r\n    <?php if ($post[\'type\'] == \'photo\') {  ?>\r\n        <img src=\"<?php echo $post[\'photo-url-500\'];?>\">\r\n        <?php echo $post{\'photo-caption\'}; ?>\r\n        <?php echo $post{\'photo-set\'}; ?>\r\n\r\n        <a href=\"<?php echo $post{\'photo-url\'}; ?>\" class=\"fImage\">View Full Size</a>\r\n    <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'link\') {  ?>\r\n\r\n        <p><a href=\"<?php echo $post{\'link-url\'}; ?>\"><?php echo $post{\'link-text\'}; ?></a>\r\n        <?php echo $post{\'link-description\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'conversation\') {  ?>\r\n        <?php echo $post{\'conversation-text\'}; ?>\r\n      <?php } ?>\r\n\r\n\r\n    <?php if ($post[\'type\'] == \'video\') {  ?>\r\n        <!--<?php echo $post{\'video-source\'}; ?>-->\r\n        <?php echo $post{\'video-player\'}; ?>\r\n        <?php echo $post{\'video-caption\'}; ?>\r\n      <?php } ?>\r\n\r\n    <?php if ($post[\'type\'] == \'conversation\') {  ?>\r\n        <?php echo $post{\'audio-caption\'}; ?>\r\n        <?php echo $post{\'audio-player\'}; ?>\r\n        <?php echo $post{\'audio-plays\'}; ?>\r\n      <?php } ?>\r\n\r\n<div id=\"post-date\">\r\n<?php echo date(\"jS D M, H:i\",strtotime($post[\'date\'])); ?>&nbsp; &nbsp;<a href=\"<?php echo $post[\'url-with-slug\']; ?>\">Read on Tumblr</a>\r\n</div>\r\n\r\n</div>\r\n\r\n*/', 'f89ac169bf7fa09f3ffe0222f66baad4', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_write_cats', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.30', 'Add categories directly when saving articles, images, file and links', '<div id=\"smd_help\">\n\n	<h1>smd_write_cats</h1>\n\n	<p>Add article, image, file or link categories directly when creating content, without having to visit the Categories tab first.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires Textpattern <span class=\"caps\">CMS</span> 4.4.1+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1182/smd_write_cats\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp <i>Admin-&gt;Plugins</i> panel, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=34094\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To uninstall, delete from the <i>Admin-&gt;Plugins</i> page.</p>\n\n	<h2>Usage</h2>\n\n	<p>Type a comma-separated list of category titles into the <em>Or new categories</em> box and when you hit Save or Publish they will be created if they don&#8217;t already exist.</p>\n\n	<p>You can create as many as you like; just keep comma-separating them. On the Write panel, only the first two will be automatically assigned to the article; replacing any categories that were already assigned. If one or both of the first two category titles you type already exists they will be assigned to the article (it ignores the case when checking the database for matches, but the category title will be created exactly as you typed it). The plugin functions in a similar manner on the Images, Files and Links panels, except only the first category will be assigned.</p>\n\n	<p>For articles, if you want to set Category 2 only, just start your list with a comma, e.g. <code>, My new category</code>. If for some reason you want to create more categories but don&#8217;t want to assign them to the current article, put two commas at the start: <code>, , Some unlinked category, And another</code>. The same goes for the other content types, but you only need to specify one empty comma.</p>\n\n	<h2>Parent categories</h2>\n\n	<p>By default all categories you create will be stored under the &#8216;root&#8217; of the category tree. If you wish to assign your new category beneath an existing category you can specify an alternative syntax:</p>\n\n	<p><code>Parent category -&gt; New category</code></p>\n\n	<p>If the designated parent category title exists, your new category will be assigned to it. If it doesn&#8217;t exist (the plugin ignores the case of the parent category title) then it will be assigned to root. <span class=\"caps\">IMPORTANT</span>: although the case doesn&#8217;t matter, the parent must match your category <em>Title</em> (not-its-sanitized-name) for it to be assigned beneath it.</p>\n\n	<p>You can customize the mechanism for supplying parent-child relationships by creating the following prefs (smd_prefalizer can help here):</p>\n\n	<table>\n		<tr>\n			<th>Pref name </th>\n			<th>Value </th>\n			<th>Visiblity </th>\n			<th>Event </th>\n			<th>Notes </th>\n		</tr>\n		<tr>\n			<td> smd_write_cats_child_first </td>\n			<td> 0=parent first 1=child first </td>\n			<td> Hidden </td>\n			<td> smdwritecats </td>\n			<td> Setting this to 1 means that you specify the new category first and <em>then</em> specify the parent you want it assigned to </td>\n		</tr>\n		<tr>\n			<td> smd_write_cats_parent_delim </td>\n			<td> any string </td>\n			<td> Hidden </td>\n			<td> smdwritecats </td>\n			<td> Overrides the default <code>-&gt;</code>. Whatever you specify here will be used as a parent/child delimiter. Be careful to choose something that would not normally be used in a category name! </td>\n		</tr>\n	</table>\n\n	<p>If you create a new category, you may use that category as a parent in subsequent category declarations. For example:</p>\n\n	<p><code>Fruit, Fruit-&gt;Apple</code></p>\n\n	<p>will create the category <code>Fruit</code> (at the root of the tree) and assign it to Category1. It will then create <code>Apple</code> beneath the <code>Fruit</code> category, and assign it to Category2.</p>\n\n	<h2>Author / credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a></p>\n\n	<h2 id=\"changelog\">Changelog</h2>\n\n	<ul>\n		<li>06 Jul 2010 | 0.10 | Initial release</li>\n		<li>11 Jul 2010 | 0.20 | Added parent-&gt;child ability ; fixed &#8216;article&#8217; type selection (thanks monkeyninja/Uli) ; made tests case insensitive to prevent duplicate cats being created ; prevented &#8216;root&#8217; being added to category1/2 ; added <code>edit</code> class to input box and made sure only people who have category privs can create them (thanks Uli)</li>\n		<li>13 Jun 2012 | 0.30 | Extended for image, file and link categories ; made parent category assignment more lenient to case (both thanks MattD)</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#smd_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#smd_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase; text-decoration:underline;}\n#smd_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; color:#33d; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table {width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse;margin:0 auto 1em;}\n#smd_help td, #smd_help th {border:1px solid #999; padding:.5em;}\n#smd_help ul { list-style-type:square; }\n#smd_help .important {color:red;}\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\n * smd_write_cats\n *\n * A Textpattern CMS plugin for creating categories on the fly\n *  -> Can create article, image, file and link categories\n *  -> Parent->Child syntax supported\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (@txpinterface == \'admin\') {\n	register_callback(\'smd_writecat\', \'article_ui\', \'categories\');\n	register_callback(\'smd_contentcat\', \'image\', \'image_edit\');\n	register_callback(\'smd_contentcat\', \'image\', \'image_insert\');\n	register_callback(\'smd_contentcat\', \'file\', \'file_edit\');\n	register_callback(\'smd_contentcat\', \'file\', \'file_insert\');\n	register_callback(\'smd_contentcat\', \'link\');\n	register_callback(\'smd_writecat_update\', \'article_ui\', \'partials_meta\');\n	// Note: all the following run \'pre\' so the category tree can be built prior to saving the content\n	register_callback(\'smd_contentcat_insert\', \'article\', \'edit\', 1);\n	register_callback(\'smd_contentcat_insert\', \'article\', \'create\', 1);\n	register_callback(\'smd_contentcat_insert\', \'image\', \'image_save\', 1);\n	register_callback(\'smd_contentcat_insert\', \'file\', \'file_save\', 1);\n	register_callback(\'smd_contentcat_insert\', \'link\', \'link_post\', 1);\n	register_callback(\'smd_contentcat_insert\', \'link\', \'link_save\', 1);\n}\n\n// ------------------------\n// Use pluggable_ui() on the Write tab\nfunction smd_writecat($evt, $stp, $dflt, $rs) {\n	$catbox = \'\';\n	if(has_privs(\'category\')) {\n		$catbox = graf(\'<label for=\"smd_writecat\">\'.smd_writecat_gTxt(\'new_cat_label\').\'</label>\'.br.fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\'), \' class=\"category\"\');\n	}\n	return $dflt.$catbox;\n}\n\n// ------------------------\n// No appropriate pluggable_ui() on the other Content tabs: jQuery to the rescue\nfunction smd_contentcat($evt, $stp) {\n	$jsElem = \'\';\n	$catbox = graf(\'<label for=\"smd_writecat\">\'.smd_writecat_gTxt(\'new_cat_label\').\'</label>\'.br.fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\'), \' class=\"category\"\');\n	switch ($evt) {\n		case \'image\':\n			$jsElem = \"jQuery(\'#image-category\').parent()\";\n		break;\n		case \'file\':\n			$jsElem = \'jQuery(\"select[name=\\\'category\\\']\").parent()\';\n		break;\n		case \'link\':\n			// Damn Links panel is different DOM.\n			// TODO: Revisit when standardized\n			$catbox = tr(fLabelCell(smd_writecat_gTxt(\'new_cat_label\'), \'\', \'smd_writecat\') . tda(fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\')));\n			$jsElem = \'jQuery(\"#link_details .category\")\';\n		break;\n	}\n\n	if(has_privs(\'category\')) {\n		if ($jsElem) {\n			echo <<<EOJS\n<script type=\"text/javascript\">\njQuery(function() {\n	{$jsElem}.after(\'{$catbox}\');\n});\n</script>\nEOJS;\n		}\n	}\n}\n\n// ------------------------\n// Tell Txp that the categories are volatile and should be updated\nfunction smd_writecat_update($evt, $stp, $data, $options) {\n	$options[\'categories\'][\'mode\'] = PARTIAL_VOLATILE;\n	$options[\'categories\'][\'selector\'] = \'#category-1, #category-2\';\n}\n\n// ------------------------\n// Do the category insertion deed\nfunction smd_contentcat_insert($evt, $stp) {\n	$save = ps(\'save\');\n	$publish = ps(\'publish\');\n	$dlm = get_pref(\'smd_write_cats_parent_delim\', \'->\');\n	$ord = get_pref(\'smd_write_cats_child_first\', \'0\');\n\n	$article_save = ($evt == \'article\' && ($save || $publish));\n\n	if (has_privs(\'category\') && ($article_save || $evt != \'article\')) {\n		$newcats = ps(\'smd_category\');\n		if ($newcats) {\n			$newcats = do_list($newcats);\n			$cats = $parents = array();\n			foreach ($newcats as $newcat) {\n				$catparts = do_list($newcat, $dlm);\n				if (count($catparts) == 2) {\n					if(isset($catparts[$ord])) {\n						if ($catparts[$ord] == \'\') {\n							$parents[] = \'root\';\n						} else {\n							$parents[] = $catparts[$ord];\n						}\n					} else {\n						$parents[] = \'root\';\n					}\n					$cats[] = isset($catparts[!$ord]) ? $catparts[!$ord] : \'\';\n				} else {\n					$parents[] = \'root\';\n					$cats[] = $catparts[0];\n				}\n			}\n\n			// Get all cats and parents in one swoop -- more efficient than one by one\n			$findall = \"title IN (\'\".join(\"\',\'\", doSlash(array_unique(array_merge($parents, $cats)))).\"\') AND type=\'$evt\'\";\n			$matches = safe_rows(\'name, title\', \'txp_category\', $findall);\n\n			$exists = array();\n			foreach ($matches as $match) {\n				$exists[strtolower($match[\'name\'])] = strtolower($match[\'title\']);\n			}\n\n			$ctr = 1;\n			foreach ($cats as $idx => $newcat) {\n				if (!empty($newcat)) {\n					$catcmp = strtolower($newcat);\n					if (in_array($catcmp, $exists) || array_key_exists($catcmp, $exists)) {\n						$ret = true;\n						$name = strtolower(sanitizeForUrl($newcat));\n					} else {\n						$title = doSlash($newcat);\n						$name = strtolower(sanitizeForUrl($title));\n						$parcmp = strtolower($parents[$idx]);\n\n						if (in_array($parcmp, $exists)) {\n							$parlist = array_keys($exists, $parcmp);\n							$parent = $parlist[0];\n						} else if(array_key_exists($parcmp, $exists)) {\n							$parent = strtolower(sanitizeForUrl($exists[$parcmp]));\n						} else {\n							$parent= \'root\';\n						}\n						$ret = safe_insert(\'txp_category\', \"name=\'$name\', type=\'$evt\', parent=\'$parent\', title=\'$title\'\");\n\n						// Make a note of this one so it can be rejected if it appears later in the list, or used as a parent in subsequent entries\n						$exists[strtolower($title)] = strtolower($title);\n					}\n\n					// Inject up to the first 2 (for articles) or 1 (for other content types) new cats into the $_POST array\n					// so Txp thinks they have been assigned via the select input widgets\n					if ($ret && ($evt == \'article\') && ($ctr <= 2) && $name != \'root\') {\n						$_POST[\'Category\'.$ctr] = $name;\n					}\n					if ($ret && ($evt != \'article\') && ($ctr <= 1) && $name != \'root\') {\n						$_POST[\'category\'] = $name;\n					}\n\n				}\n				$ctr++;\n			}\n			rebuild_tree_full($evt);\n		}\n	}\n}\n\n// ------------------------\n// Plugin-specific replacement strings - localise as required\n// TODO: make these into a textpack when plugin composer supports them\nfunction smd_writecat_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'new_cat_label\' => \'Or new categories\',\n	);\n	return strtr($lang[$what], $atts);\n}', '/**\n * smd_write_cats\n *\n * A Textpattern CMS plugin for creating categories on the fly\n *  -> Can create article, image, file and link categories\n *  -> Parent->Child syntax supported\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (@txpinterface == \'admin\') {\n	register_callback(\'smd_writecat\', \'article_ui\', \'categories\');\n	register_callback(\'smd_contentcat\', \'image\', \'image_edit\');\n	register_callback(\'smd_contentcat\', \'image\', \'image_insert\');\n	register_callback(\'smd_contentcat\', \'file\', \'file_edit\');\n	register_callback(\'smd_contentcat\', \'file\', \'file_insert\');\n	register_callback(\'smd_contentcat\', \'link\');\n	register_callback(\'smd_writecat_update\', \'article_ui\', \'partials_meta\');\n	// Note: all the following run \'pre\' so the category tree can be built prior to saving the content\n	register_callback(\'smd_contentcat_insert\', \'article\', \'edit\', 1);\n	register_callback(\'smd_contentcat_insert\', \'article\', \'create\', 1);\n	register_callback(\'smd_contentcat_insert\', \'image\', \'image_save\', 1);\n	register_callback(\'smd_contentcat_insert\', \'file\', \'file_save\', 1);\n	register_callback(\'smd_contentcat_insert\', \'link\', \'link_post\', 1);\n	register_callback(\'smd_contentcat_insert\', \'link\', \'link_save\', 1);\n}\n\n// ------------------------\n// Use pluggable_ui() on the Write tab\nfunction smd_writecat($evt, $stp, $dflt, $rs) {\n	$catbox = \'\';\n	if(has_privs(\'category\')) {\n		$catbox = graf(\'<label for=\"smd_writecat\">\'.smd_writecat_gTxt(\'new_cat_label\').\'</label>\'.br.fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\'), \' class=\"category\"\');\n	}\n	return $dflt.$catbox;\n}\n\n// ------------------------\n// No appropriate pluggable_ui() on the other Content tabs: jQuery to the rescue\nfunction smd_contentcat($evt, $stp) {\n	$jsElem = \'\';\n	$catbox = graf(\'<label for=\"smd_writecat\">\'.smd_writecat_gTxt(\'new_cat_label\').\'</label>\'.br.fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\'), \' class=\"category\"\');\n	switch ($evt) {\n		case \'image\':\n			$jsElem = \"jQuery(\'#image-category\').parent()\";\n		break;\n		case \'file\':\n			$jsElem = \'jQuery(\"select[name=\\\'category\\\']\").parent()\';\n		break;\n		case \'link\':\n			// Damn Links panel is different DOM.\n			// TODO: Revisit when standardized\n			$catbox = tr(fLabelCell(smd_writecat_gTxt(\'new_cat_label\'), \'\', \'smd_writecat\') . tda(fInput(\'text\', \'smd_category\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'smd_writecat\')));\n			$jsElem = \'jQuery(\"#link_details .category\")\';\n		break;\n	}\n\n	if(has_privs(\'category\')) {\n		if ($jsElem) {\n			echo <<<EOJS\n<script type=\"text/javascript\">\njQuery(function() {\n	{$jsElem}.after(\'{$catbox}\');\n});\n</script>\nEOJS;\n		}\n	}\n}\n\n// ------------------------\n// Tell Txp that the categories are volatile and should be updated\nfunction smd_writecat_update($evt, $stp, $data, $options) {\n	$options[\'categories\'][\'mode\'] = PARTIAL_VOLATILE;\n	$options[\'categories\'][\'selector\'] = \'#category-1, #category-2\';\n}\n\n// ------------------------\n// Do the category insertion deed\nfunction smd_contentcat_insert($evt, $stp) {\n	$save = ps(\'save\');\n	$publish = ps(\'publish\');\n	$dlm = get_pref(\'smd_write_cats_parent_delim\', \'->\');\n	$ord = get_pref(\'smd_write_cats_child_first\', \'0\');\n\n	$article_save = ($evt == \'article\' && ($save || $publish));\n\n	if (has_privs(\'category\') && ($article_save || $evt != \'article\')) {\n		$newcats = ps(\'smd_category\');\n		if ($newcats) {\n			$newcats = do_list($newcats);\n			$cats = $parents = array();\n			foreach ($newcats as $newcat) {\n				$catparts = do_list($newcat, $dlm);\n				if (count($catparts) == 2) {\n					if(isset($catparts[$ord])) {\n						if ($catparts[$ord] == \'\') {\n							$parents[] = \'root\';\n						} else {\n							$parents[] = $catparts[$ord];\n						}\n					} else {\n						$parents[] = \'root\';\n					}\n					$cats[] = isset($catparts[!$ord]) ? $catparts[!$ord] : \'\';\n				} else {\n					$parents[] = \'root\';\n					$cats[] = $catparts[0];\n				}\n			}\n\n			// Get all cats and parents in one swoop -- more efficient than one by one\n			$findall = \"title IN (\'\".join(\"\',\'\", doSlash(array_unique(array_merge($parents, $cats)))).\"\') AND type=\'$evt\'\";\n			$matches = safe_rows(\'name, title\', \'txp_category\', $findall);\n\n			$exists = array();\n			foreach ($matches as $match) {\n				$exists[strtolower($match[\'name\'])] = strtolower($match[\'title\']);\n			}\n\n			$ctr = 1;\n			foreach ($cats as $idx => $newcat) {\n				if (!empty($newcat)) {\n					$catcmp = strtolower($newcat);\n					if (in_array($catcmp, $exists) || array_key_exists($catcmp, $exists)) {\n						$ret = true;\n						$name = strtolower(sanitizeForUrl($newcat));\n					} else {\n						$title = doSlash($newcat);\n						$name = strtolower(sanitizeForUrl($title));\n						$parcmp = strtolower($parents[$idx]);\n\n						if (in_array($parcmp, $exists)) {\n							$parlist = array_keys($exists, $parcmp);\n							$parent = $parlist[0];\n						} else if(array_key_exists($parcmp, $exists)) {\n							$parent = strtolower(sanitizeForUrl($exists[$parcmp]));\n						} else {\n							$parent= \'root\';\n						}\n						$ret = safe_insert(\'txp_category\', \"name=\'$name\', type=\'$evt\', parent=\'$parent\', title=\'$title\'\");\n\n						// Make a note of this one so it can be rejected if it appears later in the list, or used as a parent in subsequent entries\n						$exists[strtolower($title)] = strtolower($title);\n					}\n\n					// Inject up to the first 2 (for articles) or 1 (for other content types) new cats into the $_POST array\n					// so Txp thinks they have been assigned via the select input widgets\n					if ($ret && ($evt == \'article\') && ($ctr <= 2) && $name != \'root\') {\n						$_POST[\'Category\'.$ctr] = $name;\n					}\n					if ($ret && ($evt != \'article\') && ($ctr <= 1) && $name != \'root\') {\n						$_POST[\'category\'] = $name;\n					}\n\n				}\n				$ctr++;\n			}\n			rebuild_tree_full($evt);\n		}\n	}\n}\n\n// ------------------------\n// Plugin-specific replacement strings - localise as required\n// TODO: make these into a textpack when plugin composer supports them\nfunction smd_writecat_gTxt($what, $atts = array()) {\n	$lang = array(\n		\'new_cat_label\' => \'Or new categories\',\n	);\n	return strtr($lang[$what], $atts);\n}', 'bded3422778061b5ce5c170d1b0af25a', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_wrap', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.20', 'Conditionally transform and wrap stuff with tags and labels', '<div id=\"smd_help\">\n\n	<h1>smd_wrap / smd_wrap_all</h1>\n\n	<p>Wrap text with <span class=\"caps\">HTML</span>, class, prefix, suffix, and apply tranforms to it in the process (linkify, format as date, trim, escape, sanitize, etc).</p>\n\n	<p>If the Txp tags inside the item you are intending to wrap returns nothing then no wrap is applied and nothing is displayed. This makes it an ideal candidate for placing around other tags and saves having to do the &#8220;assign content to <code>&lt;txp:variable&gt;</code>, check if variable exists&#8221; dance. <code>&lt;txp:else /&gt;</code> is supported so you may take action if the contained content is empty.</p>\n\n	<p>If you want the plugin to wrap your content if there is <em>any</em> content in its form/container (e.g. if you are using it to wrap around a <code>{replacement}</code> tag or something) then use <code>&lt;txp:smd_wrap_all /&gt;</code> instead.</p>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/1242/smd_wrap\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> <em>Admin-&gt;Plugins</em> pane, install and enable the plugin.</p>\n\n	<p>To uninstall the plugin, simply delete the plugin from the <em>Admin-&gt;Plugins</em> page</p>\n\n	<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=37167\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<h2>Tag: <code>&lt;txp:smd_wrap&gt;</code></h2>\n\n	<p>Wrap the <code>item</code> or tag&#8217;s container with standard Txp <code>wraptag</code> paraphernalia. The content is parsed so you can include other Txp tags. If the results of the parse return nothing, the plugin does nothing, though you may fire an optional <code>&lt;txp:else /&gt;</code> if you wish inside your container. The following attributes configure the plugin:</p>\n\n	<dl>\n		<dt><strong>item</strong></dt>\n		<dd>The thing to wrap. Can be text, a tag-in-tag or some <code>{replacement}</code> from other smd_ plugins. If not specifed, the container is used as input unless <code>form</code> is given. If you use this attribute you should probably self-close <code>&lt;txp:smd_wrap /&gt;</code>.</dd>\n		<dt><strong>form</strong></dt>\n		<dd>An alternative to <code>item</code> for specifying the thing to wrap. If not used, and no <code>item</code> is given, the container is used as input.</dd>\n		<dd>If you employ a container as well, you can use <code>&lt;txp:yield /&gt;</code> inside your <code>form</code> to plug the contained content in. Any contained content is trimmed (if <code>trim=&quot;1&quot;</code>) and parsed before insertion.</dd>\n		<dt><strong>wraptag</strong></dt>\n		<dd>Wrap this <span class=\"caps\">HTML</span> tag around the item. Specify it without angle brackets, e.g. <code>wraptag=&quot;span&quot;</code></dd>\n		<dt><strong>class</strong></dt>\n		<dd>Assign this <span class=\"caps\">CSS</span> class to the wraptag. Unused if <code>wraptag</code> is empty.</dd>\n		<dt><strong>html_id</strong></dt>\n		<dd>Assign this <span class=\"caps\">HTML</span> <code>id</code> value to the wraptag. Unused if <code>wraptag</code> is empty.</dd>\n		<dt><strong>attr</strong></dt>\n		<dd>Supply your own name=value pairs to add to the wraptag. For example, <code>attr=&quot;rel|external, title|My useful popup title&quot;</code> would add <code>rel=&quot;external&quot; title=&quot;My useful popup title&quot;</code> as attributes to the wraptag.</dd>\n		<dt><strong>label</strong></dt>\n		<dd>Put this text as a heading / label before the item.</dd>\n		<dt><strong>labeltag</strong></dt>\n		<dd>Wrap the label with this <span class=\"caps\">HTML</span> tag. Specify without angle brackets, e.g. <code>labeltag=&quot;h4&quot;</code></dd>\n		<dt><strong>trim</strong></dt>\n		<dd>Strip leading and trailing whitespace from the item <em>before</em> it has prefix and suffix applied. Use <code>trim=&quot;0&quot;</code> to turn this feature off.</dd>\n		<dd>Default: 1</dd>\n		<dt><strong>prefix</strong></dt>\n		<dd>Put this text before the item. It is prepended <em>prior</em> to the item being passed through any <code>transform</code> chain.</dd>\n		<dt><strong>suffix</strong></dt>\n		<dd>Put this text after the item. It is appended <em>prior</em> to the item being passed through any <code>transform</code> chain.</dd>\n		<dt><strong>transform</strong></dt>\n		<dd>Apply transformations to the item. Transforms can be chained and are applied in order, each one separated by <code>delim</code>. Transform configuration parameters are separated by <code>param_delim</code>. The following transforms are defined (see the examples below for more information) :\n	<dl>\n		<dd><strong>add</strong> &#8212; append some text to the item. This is <em>not</em> the same as using prefix / suffix because the <code>add</code> transform can be inserted into the chain at any point. Use the second argument to determine in which position to add the new text; the text itself is the third argument. Here are the positional options:\n	<dl>\n		<dd><strong>before</strong> &#8212; add the text at the start of the item.</dd>\n		<dd><strong>after</strong> &#8212; add the text at the end of the item.</dd>\n		<dd><strong>both</strong> &#8212; add the text at both start and end of the item.</dd>\n	</dl></dd>\n		<dd><strong>case</strong> &#8212; change the case of the item. You may chain case transforms in sequence, e.g. <code>transform=&quot;case|lower|ucfirst&quot;</code>. Choose from:\n	<dl>\n		<dd><strong>upper</strong> &#8212; upper case.</dd>\n		<dd><strong>lower</strong> &#8212; lower case.</dd>\n		<dd><strong>ucfirst</strong> &#8212; upper case first character of sentence.</dd>\n		<dd><strong>ucwords</strong> &#8212; upper case first character of every word.</dd>\n		<dd><strong>title</strong> &#8212; intelligent(ish) version of ucwords that ignores small words and tries to leave intentional first-lower-case words (e.g. iPhone) alone. Works best on English but does support Unicode up to a point. You can specify your own list of words by creating a hidden pref called <strong>smd_wrap_small_words</strong>. List each word separated by a pipe. You may use regex strings if you wish but <em>no sanitization is done</em> so be careful! The default list of ignored short words are: <code>a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|vs?[.]?|via</code></dd>\n	</dl></dd>\n		<dd><strong>currency</strong> &#8212; format as a monetary value. Optional parameters:\n	<dl>\n		<dd>format string as defined in <span class=\"caps\">PHP</span>&#8217;s <a href=\"http://uk.php.net/manual/en/function.money-format.php\">money_format</a>. If not set, <code>%.2n</code> is used</dd>\n		<dd>locale string. Current locale used if not set</dd>\n	</dl></dd>\n		<dd><strong>cut</strong> &#8212; chop the item at the given character/word limit, providing it is longer than the passed value. Note that if the item contains <span class=\"caps\">HTML</span> tags then the output is likely to be spurious (wrong count, or with unclosed tags) so it&#8217;s best to employ <code>strip_tags</code> first. See <a href=\"#smd_wrap_eg7\">example 7</a>. Arguments can be of the following form:\n	<dl>\n		<dd><em>some_number</em> (plus optional <code>c</code>): chop at the given number of characters</dd>\n		<dd><em>some_number</em> (plus <code>w</code>): chop at the given number of words</dd>\n		<dd><em>some_string</em>: the continuation character(s) to add to the end of the item if it has been truncated (e.g. <code>...</code> or <code>&amp;hellip;</code>)</dd>\n	</dl></dd>\n		<dd><strong>date</strong> or <strong>time</strong> &#8212; treat the item as a date and/or time, formatting it according to the <a href=\"http://php.net/manual/en/function.strftime.php\">strftime()</a> compatible string given as the first argument. The item can either be a numeric (<span class=\"caps\">UNIX</span>) timestamp or an <em>English</em> date string in an acceptable format. For datetime fields you could split the string first just to get the date portion.</dd>\n		<dd><strong>escape</strong> &#8212; run the item through <code>htmlspecialchars()</code>. Use additional parameters to list optional <a href=\"http://php.net/manual/en/function.htmlspecialchars.php\">flags</a> or use any of the following special values:\n	<dl>\n		<dd><strong>no_quotes</strong> &#8212; <code>ENT_NOQUOTES</code></dd>\n		<dd><strong>double_quotes</strong> &#8212; <code>ENT_COMPAT</code></dd>\n		<dd><strong>all_quotes</strong> &#8212; <code>ENT_QUOTES</code></dd>\n	</dl></dd>\n		<dd><strong>fordb</strong> &#8212; run the item through Txp&#8217;s <code>doSlash()</code>. Always consider using this if you are posting the item back to the database (beware that other plugins may do this for you, so be careful not to duplicate it or you&#8217;ll get backslashes where there shouldn&#8217;t be backslashes).</dd>\n		<dd><strong>form</strong> &#8212; pass the item through the nominated forms. Specify each form as an argument, and inside those forms place <code>&lt;txp:smd_wrap_info /&gt;</code> where you wish the item to be inserted. You may also use <code>{smd_wrap_it}</code> but this may not be as secure as using the tag.</dd>\n		<dd><strong>link</strong> &#8212; turn the item into a clickable <span class=\"caps\">URL</span> anchor. Without an argument, the link text will be the link itself. If you specify an argument, the link text will be set to whatever you choose.</dd>\n		<dd><strong>no_widow</strong> &#8212; whether to allow the last word of the item to word-wrap to the next line. If you specify 0 as the argument, word-wrap will be allowed. Specifying 1 means the last word will never be allowed to wrap on its own. Without an argument, the Advanced Pref determines the behaviour.</dd>\n		<dd><strong>number</strong> &#8212; treat the item as numeric. The first parameter is optional and specifies the locale to assume (default: current locale). After that, three further optional parameters may be supplied:\n	<dl>\n		<dd>1: decimal places</dd>\n		<dd>2: decimal point character</dd>\n		<dd>3: thousands separator character</dd>\n	</dl></dd>\n		<dd><strong>replace</strong> &#8212; find a string or regex and replace it with something else. Additional parameters are:\n	<dl>\n		<dd>1: <em>type</em>: either <code>string</code> (or omitted) for a regular replace, or <code>regex</code> for a regular expression</dd>\n		<dd>2: <em>from</em>: the string to search for. If using a regex, you <span class=\"caps\">MUST</span> specify the regular expression delimiters as well or you&#8217;ll get an error</dd>\n		<dd>3: <em>to</em>: the text with which to replace any found strings. If omited, the found strings will simply be removed</dd>\n	</dl></dd>\n		<dd><strong>round</strong> &#8212; treat the item as a number and round it to the nearest whole number or positive value. One optional argument can either be:\n	<dl>\n		<dd><strong>down</strong> &#8212; round down</dd>\n		<dd><strong>up</strong> &#8212; round up</dd>\n		<dd><strong>positive</strong> &#8212; ignore sign</dd>\n		<dd><em>some number</em> &#8212; the decimal precision to round the value to</dd>\n	</dl></dd>\n		<dd><strong>sanitize</strong> &#8212; clean the item either for:\n	<dl>\n		<dd><strong>url</strong> &#8212; address bar use.</dd>\n		<dd><strong>file</strong> &#8212; filename use.</dd>\n		<dd><strong>url_title</strong> &#8212; use as a <span class=\"caps\">URL</span> title.</dd>\n		<dd><strong>encode</strong> &#8212; percent-encoded <span class=\"caps\">URL</span> use</dd>\n		<dd><strong>raw</strong> &#8212; raw percent-encoded <span class=\"caps\">URL</span> use compliant with <a href=\"http://www.faqs.org/rfcs/rfc3986.html\"><span class=\"caps\">RFC</span> 3986</a></dd>\n	</dl></dd>\n		<dd><strong>split</strong> &#8212; split the item at the given character(s) and return one or more parts, optionally joined by the given characters. Parameters:\n	<dl>\n		<dd>1: split character(s)</dd>\n		<dd>2: join character(s)</dd>\n		<dd>3+: which pieces to return, each separated by <code>param_delim</code>. You can either use numeric indices to specify discrete parts, e.g. <code>1|4</code> returns the first and 4th parts. You can specify negative numbers to get parts starting from the end, e.g. <code>-1|-2</code> returns the last and penultimate elements. <code>last</code> is a synonym for <code>-1</code>, <code>all</code> returns every piece, and <code>rand</code> chooses one piece at random. In addition you can also use greater-than and less-than symbols before a value to only return elements  higher or lower than the given value, e.g. <code>&gt;2</code> returns the 3rd, 4th, 5th,&#8230; elements. See <a href=\"#smd_wrap_eg3\">example 3</a> for details.</dd>\n	</dl></dd>\n		<dd><strong>strip_tags</strong> &#8212; remove all <span class=\"caps\">HTML</span> / <span class=\"caps\">PHP</span> tags from the item.</dd>\n		<dd><strong>textile</strong> &#8212; pass the item through the Textile processor.</dd>\n		<dd><strong>trim</strong> &#8212; remove leading and trailing characters from the item. Specify a list of characters as the argument. Without an argument, whitespace will be removed.</dd>\n	</dl></dd>\n		<dt><strong>delim</strong></dt>\n		<dd>The plugin delimiter.</dd>\n		<dd>Default: comma (,)</dd>\n		<dt><strong>param_delim</strong></dt>\n		<dd>The argument delimiter.</dd>\n		<dd>Default: pipe (|)</dd>\n		<dt><strong>debug</strong></dt>\n		<dd>Display the item the tag is trying to wrap.</dd>\n	</dl>\n\n	<h2 id=\"smd_wrap_eg1\">Example 1: Link anchor</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:variable name=&quot;wiki&quot; value=&quot;http://textpattern.net/wiki&quot; /&gt;\n&lt;txp:smd_wrap wraptag=&quot;div&quot; class=&quot;external&quot;\n     transform=&quot;link|Textpattern documentation&quot;&gt;\n   &lt;txp:variable name=&quot;wiki&quot; /&gt;\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;a href=&quot;http://textpattern.net/wiki&quot;&gt;Textpattern documentation&lt;/a&gt;\n</code></pre>\n\n	<h2 id=\"smd_wrap_eg2\">Example 2: Date formatting</h2>\n\n	<p>Notice that because we&#8217;re using a comma in the formatted string, the <code>delim</code> needs to be altered to some other (in this case arbitrary) string:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap delim=&quot;@&quot;\n     transform=&quot;date|%A, %d %B, %Y %l:%M%p&quot;&gt;\n2011-9-12 05:30:00\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns: <code>Monday, 12 September, 2011 5:30AM</code></p>\n\n	<h2 id=\"smd_wrap_eg3\">Example 3: Split and join</h2>\n\n	<h3>3a: Get a filename extension</h3>\n\n	<p>Notice no join character is specified in the third argument (although it doesn&#8217;t hurt to include one; it won&#8217;t be used in this case):</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap transform=&quot;split|.||last&quot;\n     item=&quot;/path/to/some/really.big.file.name.jpg&quot; /&gt;\n</code></pre>\n\n	<p>Returns: <code>jpg</code></p>\n\n	<h3>3b: Get the file path and filename extension</h3>\n\n	<p>Separated by colon (<code>last</code> is the same as <code>-1</code>) :</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap transform=&quot;split|.|:|1|-1&quot;&gt;\n/path/to/some/really.big.file.name.jpg\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns: <code>/path/to/some/really:jpg</code></p>\n\n	<h3>3c: Split the date portion of a combined datetime</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap delim=&quot;@&quot;\n     transform=&quot;split| ||1 @ date|%A, %d %B, %Y&quot;&gt;\n2011-9-12 05:30:00\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns: <code>Monday, 12 September, 2011</code></p>\n\n	<h3>3d: Make an <span class=\"caps\">HTML</span> list from some other markup system</h3>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap wraptag=&quot;ul&quot;\n     transform=&quot;split|#|&lt;/li&gt;&lt;li&gt;|&gt;1, add|before|&lt;li&gt;, add|after|&lt;/li&gt;&quot;&gt;\n# First\n# Second\n# Third\n# Fourth\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;ul&gt;\n   &lt;li&gt; First&lt;/li&gt;\n   &lt;li&gt; Second&lt;/li&gt;\n   &lt;li&gt; Third&lt;/li&gt;\n   &lt;li&gt; Fourth&lt;/li&gt;\n&lt;/ul&gt;\n</code></pre>\n\n	<p>A couple of things to note about this example:</p>\n\n	<ul>\n		<li>The <code>add</code> transform is used instead of prefix / suffix. This is because the latter pair become part of the item/container and are then split with the <code>transform</code> attribute. In this case we need to add the opening and closing tags afterwards.</li>\n		<li>Splitting at &#8216;#&#8217; renders an empty element before the &#8220;First&#8221; entry, so the transform is instructed to return items <code>&gt;1</code>.</li>\n	</ul>\n\n	<h2 id=\"smd_wrap_eg4\">Example 4: with smd_query {replacements}</h2>\n\n	<p>Wrap the custom_6 value with a div and sanitize it for use in a <span class=\"caps\">URL</span>. If custom_6 is empty, nothing would be displayed.</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_query query=&quot;SELECT * FROM wherever&quot;&gt;\n   &lt;txp:smd_wrap item=&quot;{custom_6}&quot; wraptag=&quot;div&quot;\n     transform=&quot;sanitize|url&quot; /&gt;\n&lt;/txp:smd_query&gt;\n</code></pre>\n\n	<p>If you wanted to trap the condition when custom_6 might be empty:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_query query=&quot;SELECT * FROM wherever&quot;&gt;\n   &lt;txp:smd_wrap wraptag=&quot;div&quot; transform=&quot;sanitize|url&quot;&gt;\n      {custom_6}\n   &lt;txp:else /&gt;\n      &lt;p&gt;Sorry, custom_6 is empty&lt;/p&gt;\n   &lt;/txp:smd_wrap&gt;\n&lt;/txp:smd_query&gt;\n</code></pre>\n\n	<h2 id=\"smd_wrap_eg5\">Example 5: find / replace</h2>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap\n     transform=&quot;replace||fox|badger&quot;&gt;\nFarewell my fox, it&#39;s been fun!\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>Returns: <code>Farewell my badger, it&#39;s been fun!</code></p>\n\n	<p>Note that the 2nd parameter (&#8216;type&#8217;) has been omitted. You could specify &#8216;string&#8217; here if you wished.</p>\n\n	<h2 id=\"smd_wrap_eg6\">Example 6: form + container + yield</h2>\n\n	<p>If pulling data from the user table from, say, smd_query you could reformat it:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap form=&quot;wrap_me&quot;\n     transform=&quot;replace||agent_hunt|Ethan Hunt&quot;&gt;\n{author}, is to explain the use of smd_wrap\n&lt;/txp:smd_wrap&gt;\n</code></pre>\n\n	<p>where <code>wrap_me</code> contains:</p>\n\n<pre class=\"block\"><code class=\"block\">Your mission, &lt;txp:yield /&gt;.\nThis message will self-destruct in 5 seconds.\n</code></pre>\n\n	<p>That will:</p>\n\n	<ul>\n		<li>stuff the <code>{author}</code> into the container from the surrounding plugin&#8217;s replacement tag</li>\n		<li>inject the container into the form where the yield tag is</li>\n		<li>run the transform so if the author was agent_hunt the entire message would read: &#8220;Your mission, Ethan Hunt, is to explain the use of smd_wrap. This message will self-destruct in 5 seconds.&#8221;</li>\n	</ul>\n\n	<h2 id=\"smd_wrap_eg7\">Example 7: truncate to excerpt</h2>\n\n	<p>Useful for excerpting data:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:smd_wrap_all\n     transform=&quot;strip_tags, cut|180|25w|&amp;hellip;&quot;&gt;\nBacon ipsum dolor sit amet shoulder drumstick boudin\nfrankfurter. Andouille shoulder pastrami, rump cow\nsausage ribeye shankle swine. Tail meatball cow pork.\nRump ham salami meatloaf sirloin beef ribs.\n&lt;/txp:smd_wrap_all&gt;\n</code></pre>\n\n	<p>That will write out the string and cut it first at 180 characters, then make sure there are no more than 25 words in it. Thus it will end at: <code>Rump ham salami…</code></p>\n\n	<p>If you omitted the <code>25w</code> or your chosen word count was higher than the number of remaining words you would see the following: <code>Rump ham salami meatlo…</code></p>\n\n	<h2>Author / credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. Thanks to the jakob for making a business case for creating this plugin, and of course the adi_wrap plugin for inspiration.</p>\n\n	<h2>Changelog</h2>\n\n	<p>22 Nov 2011 | 0.10 | Initial release<br />\n14 Jan 2012 | 0.20 | Now tests for Txp tag content akin to chh_if_data &#8212; added smd_wrap_all tag to emulate v0.10 behaviour (thanks maniqui) ; added <code>form</code> attribute and <code>&lt;txp:yield /&gt;</code> support (thanks maniqui) ; added <code>&lt;txp:smd_wrap_info /&gt;</code> instead of <code>{smd_wrap_it}</code>: strongly suggest it is used unless you know what you&#8217;re doing (thanks Gocom) ; added <code>time</code> as a synonym for <code>date</code> since they run the same transform; added <code>raw</code> and <code>encode</code> sanitization transforms (thanks maniqui) ; added <code>currency</code>, <code>round</code> and <code>number</code> transforms (thanks jakob, milosevic) ; added <code>cut</code> transform (thanks maniqui)</p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_wrap\r\n *\r\n * A Textpattern CMS plugin for wrapping content with HTML tags, labels and attributes.\r\n *  -> Adds wraptag / class / html_id / label support round any tag\r\n *  -> Permits a range of formatting options for manipulating the item\r\n *     (e.g. trim, escape, sanitize, change case, linkify, format date,\r\n *     strip tags, split / combine, process with textile, etc).\r\n *  -> If the content is empty, nothing is displayed.\r\n *  -> Supports <txp:else />\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n// * Security: http://forum.textpattern.com/viewtopic.php?pid=255560#p255560\r\n// * Allow hidden pref to determine default transforms?\r\n// * Conditionals: http://forum.textpattern.com/viewtopic.php?pid=256465#p256465\r\n\r\nfunction smd_wrap($atts, $thing=NULL) {\r\n	global $smd_wrap_data, $prefs, $yield;\r\n\r\n	extract(lAtts(array(\r\n		\'data_mode\'   => \'txp_tags\', // txp_tags (default), all\r\n		\'item\'        => \'\',\r\n		\'wraptag\'     => \'\',\r\n		\'class\'       => \'\',\r\n		\'html_id\'     => \'\',\r\n		\'label\'       => \'\',\r\n		\'labeltag\'    => \'\',\r\n		\'attr\'        => \'\',\r\n		\'prefix\'      => \'\',\r\n		\'suffix\'      => \'\',\r\n		\'format\'      => \'\', // convenience only: same as transform\r\n		\'transform\'   => \'\',\r\n		\'form\'         => \'\',\r\n		\'delim\'       => \',\',\r\n		\'param_delim\' => \'|\',\r\n		\'trim\'        => 1,\r\n		\'debug\'       => 0,\r\n	),$atts));\r\n\r\n	// item attribute trumps container\r\n	$data = ($item) ? $item : ((empty($form)) ? $thing : fetch_form($form));\r\n	$has_yield = ($form && $thing);\r\n	$out = \'\';\r\n	$tag_mode = ($data_mode == \'txp_tags\');\r\n\r\n	if ($format) {\r\n		trigger_error(\"smd_wrap: format attribute deprecated: use transform attribute instead.\", E_USER_NOTICE);\r\n		$transform = $format;\r\n	}\r\n\r\n	// Grab the true portion of any container\r\n	$truePart = EvalElse($data, 1);\r\n\r\n	if ($debug) {\r\n		echo \'++ TO WRAP ++\';\r\n		dmp($data);\r\n	}\r\n\r\n	if ($data) {\r\n		// Handle custom attributes\r\n		if ($attr) {\r\n			$custom_atts = array();\r\n			$attribs = do_list($attr);\r\n			foreach($attribs as $attdef) {\r\n				list($key, $val) = do_list($attdef, $param_delim);\r\n				$custom_atts[] = $key . \'=\"\' . $val . \'\"\';\r\n			}\r\n			$attr = \' \' . join(\' \', $custom_atts);\r\n		}\r\n\r\n		if ($tag_mode) {\r\n			// Regex to match Txp tags, stolen from the parser\r\n			$tagz = \'@(</?txp:\\w+(?:\\s+\\w+\\s*=\\s*(?:\"(?:[^\"]|\"\")*\"|\\\'(?:[^\\\']|\\\'\\\')*\\\'|[^\\s\\\'\"/>]+))*\\s*/?\'.chr(62).\')@s\';\r\n\r\n			// Strip Txp tags from the content and jot down the remaining content length\r\n			$size_without_txp_tags = strlen(preg_replace($tagz, \'\', $truePart));\r\n		}\r\n\r\n		// Run the Txp parser, taking into account any txp:yield\r\n		if ($has_yield) {\r\n			$input = $trim ? trim($thing) : $thing;\r\n			$yield[] = parse($input);\r\n		}\r\n		$out = parse($truePart);\r\n		if ($tag_mode) {\r\n			$size_after_parsing = strlen($out);\r\n			$has_data = ($size_after_parsing > $size_without_txp_tags);\r\n		}\r\n		if ($has_yield) {\r\n			array_pop($yield);\r\n		}\r\n\r\n		$out = $trim ? trim($out) : $out;\r\n		switch ($data_mode) {\r\n			case \'all\':\r\n				$has_content = $out;\r\n			break;\r\n			case \'txp_tags\':\r\n			default:\r\n				$has_content = $has_data;\r\n			break;\r\n		}\r\n\r\n		if ($has_content) {\r\n			// Top and tail the output\r\n			$out = $prefix.$out.$suffix;\r\n\r\n			// Reformat the item with any of the following transformations, in the supplied order\r\n			if ($transform) {\r\n				if ($debug) {\r\n					echo \'++ BEFORE TRANSFORM ++\';\r\n					dmp($out);\r\n				}\r\n				$formats = do_list($transform, $delim);\r\n				foreach ($formats as $xformlist) {\r\n					// Use explode() because do_list() performs a trim() that we don\'t want\r\n					$xform = explode($param_delim, $xformlist);\r\n					$xtype = array_shift($xform);\r\n\r\n					switch ($xtype) {\r\n						case \'add\':\r\n							$pos = array_shift($xform);\r\n							$val = $xform[0];\r\n							$out = (($pos == \'before\' || $pos == \'both\') ? $val : \'\') . $out . (($pos == \'after\' || $pos == \'both\') ? $val : \'\');\r\n						break;\r\n						case \'case\':\r\n							foreach ($xform as $arg) {\r\n								if ($arg == \"upper\") {\r\n									$out = strtoupper($out);\r\n								} else if ($arg == \"lower\") {\r\n									$out = strtolower($out);\r\n								} else if ($arg == \"ucfirst\") {\r\n									$out = ucfirst($out);\r\n								} else if ($arg == \"ucwords\") {\r\n									$out = ucwords($out);\r\n								} else if ($arg == \"title\") {\r\n									// Inelegantly ported + extended for Unicode from David Gouch\'s JS title case script: thanks!\r\n									// http://individed.com/code/to-title-case/js/to-title-case.js\r\n									$has_unicode = @preg_match(\'/\\pL/u\', \'a\');\r\n\r\n									$az = ($has_unicode) ? \'\\p{Lu}\' : \'A-Z\';\r\n									$wrd = ($has_unicode) ? \'(?:\\p{L}|\\p{M}|\\p{N}|\\p{Pc})\' : \'\\w\';\r\n									$capsre = \'/[\' . $az . \']+|&|[\' . $wrd . \']+[._][\' . $wrd . \']+/\';\r\n\r\n									$smalls = get_pref(\'smd_wrap_small_words\', \'a(nd?|s|t)?|b(ut|y)|en|for|i[fn]|o[fnr]|t(he|o)|vs?\\.?|via\');\r\n									$smallre = \'/^(\' . $smalls . \')[ \\-]/i\';\r\n\r\n									$pat = \'/([\' . $wrd . \'&`\\\'‘’\"“.@:\\/\\{\\(\\[<>_]+-? *)/\';\r\n									$ret = array();\r\n									preg_match_all($pat, $out, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE);\r\n\r\n									foreach ($matches[0] as $it) {\r\n										$match = $it[0];\r\n										$index = $it[1];\r\n										$idxm2 = $index - 2;\r\n										$length = strlen($match);\r\n										$title = $out; // Copy the original since we\'re working on $out directly\r\n\r\n										// Fudge because substr with negative start counts from end of string in PHP\r\n										$idxm1 = (($index-1) < 0) ? 0 : $index - 1;\r\n										$offset = (($index-1) < 0) ? 1 : 2;\r\n\r\n										if ($index > 0\r\n											&& ( $title{$idxm2} !== \":\" )\r\n											&& ( preg_match($smallre, $match) > 0 )\r\n										) {\r\n											$out = substr($out, 0, $index) . strtolower($match) . substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										if (preg_match(\'/[\\\'\\\"_{(\\[]/\', substr($title, $idxm1, $offset)) > 0) {\r\n											$out = substr($out, 0, $index) . $match{0} . @strtoupper($match{1}) . substr($match, 2). substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										if (\r\n											( preg_match($capsre, substr($match, 1)) > 0 )\r\n											|| ( preg_match(\'/[\\])}]/\', substr($title, $idxm1, $offset)) > 0 )\r\n										) {\r\n											$out = substr($out, 0, $index) . $match . substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										$out = substr($out, 0, $index) . strtoupper($match{0}) . substr($match, 1) . substr($out, $index+$length);\r\n									}\r\n								}\r\n							}\r\n						break;\r\n						case \'currency\':\r\n							$fmt = (isset($xform[0])) ? $xform[0] : \'%.2n\';\r\n							if (isset($xform[1])) {\r\n								$lcl = setlocale(LC_MONETARY, \"0\"); // Preserve current monetary locale\r\n								setlocale(LC_MONETARY, $xform[2]);\r\n							}\r\n							$out = money_format($fmt, (float)$out);\r\n							if (isset($xform[1])) {\r\n								// Restore locale\r\n								setlocale(LC_MONETARY, $lcl);\r\n							}\r\n						break;\r\n						case \'cut\':\r\n							$truncated = false;\r\n							foreach ($xform as $arg) {\r\n								// Split the argument into numeric and alpha parts [c]hars, [w]ords.\r\n								// If it matches at least one of them it\'s a number of items to split at (default: chars)\r\n								// If it matches none of them then it\'s a continuation char sequence (e.g. &hellip;)\r\n								preg_match(\'/(\\d+)([a-zA-Z])?/\', $arg, $cmatch);\r\n								if ($cmatch) {\r\n									$items = isset($cmatch[1]) ? $cmatch[1] : \'0\';\r\n									$plier = isset($cmatch[2]) ? $cmatch[2] : \'c\';\r\n									switch ($plier) {\r\n										case \'w\':\r\n											// Crude split: will probably break under UTF-8 strings\r\n											$words = preg_split(\"/[\\s]+/\", $out);\r\n											if( (count($words) > $items) && ($items > 0) ) {\r\n												$out = implode(\' \', array_slice($words, 0, $items));\r\n												$truncated = true;\r\n											}\r\n										break;\r\n										case \'c\':\r\n											$chars = strlen($out);\r\n											if ( ($chars > $items) && ($items > 0) ) {\r\n												$out = substr($out, 0, $items);\r\n												$truncated = true;\r\n											}\r\n										default:\r\n										break;\r\n									}\r\n								} else if ($truncated) {\r\n									// Tack on the continuation string\r\n									$out .= $arg;\r\n								}\r\n							}\r\n						break;\r\n						case \'date\':\r\n						case \'time\':\r\n							$nd = (is_numeric($out)) ? $out : strtotime($out);\r\n							if ($nd !== false) {\r\n								$out = strftime($xform[0], $nd);\r\n							}\r\n						break;\r\n						case \'escape\':\r\n							$flags = 0;\r\n							foreach ($xform as $arg) {\r\n								switch ($arg) {\r\n									case \'no_quotes\':\r\n										$flags |= ENT_NOQUOTES;\r\n									break;\r\n									case \'all_quotes\':\r\n										$flags |= ENT_QUOTES;\r\n									break;\r\n									case \'double_quotes\':\r\n										$flags |= ENT_COMPAT;\r\n									break;\r\n									default:\r\n										$flags |= $arg;\r\n									break;\r\n								}\r\n							}\r\n							$out = htmlspecialchars($out, $flags);\r\n						break;\r\n						case \'fordb\':\r\n							$out = doSlash($out);\r\n						break;\r\n						case \'form\':\r\n							foreach ($xform as $arg) {\r\n								$content = fetch_form($arg);\r\n								$smd_wrap_data = $out;\r\n								$reps = array(\r\n									\'{smd_wrap_it}\' => $out,\r\n								);\r\n								$out = parse(strtr($content, $reps));\r\n							}\r\n						break;\r\n						case \'link\':\r\n							// From http://codesnippets.joyent.com/posts/show/2104\r\n							$pat = \"@\\b(https?://)?(([0-9a-zA-Z_!~*\'().&=+$%-]+:)?[0-9a-zA-Z_!~*\'().&=+$%-]+\\@)?(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*\'()-]+\\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*\'().;?:\\@&=+$,%#-]+)*/?)@\";\r\n							$text = (isset($xform[0]) && $xform[0] != \'\') ? $xform[0] : \'$0\';\r\n							$out = preg_replace($pat, \'<a href=\"$0\">\'.$text.\'</a>\', $out);\r\n						break;\r\n						case \'no_widow\':\r\n							$no_widow = isset($xform[0]) ? $xform[0] : @$prefs[\'title_no_widow\'];\r\n							$out = ($no_widow) ? noWidow($out) : $out;\r\n						break;\r\n						case \'number\':\r\n							$lcl = NULL;\r\n							if ( isset($xform[0]) && !is_numeric($xform[0]) ) {\r\n								// Locale first, so preserve the current one\r\n								$lcl = setlocale(LC_NUMERIC, \"0\");\r\n								setlocale(LC_NUMERIC, $xform[0]);\r\n								array_shift($xform);\r\n							}\r\n							if (isset($xform[0])) {\r\n								$lcnv = localeconv();\r\n								$dec = $xform[0];\r\n								$dec_point = (isset($xform[1])) ? $xform[1] : $lcnv[\'decimal_point\'];\r\n								$thou_sep = (isset($xform[2])) ? $xform[2] : $lcnv[\'thousands_sep\'];\r\n								$out = number_format((float)$out, $dec, $dec_point, $thou_sep);\r\n							} else {\r\n								$out = number_format((float)$out);\r\n							}\r\n							if ($lcl) {\r\n								// Restore locale\r\n								setlocale(LC_NUMERIC, $lcl);\r\n							}\r\n							break;\r\n						case \'replace\':\r\n							$type = $xform[0] ? $xform[0] : \'string\'; // string / regex\r\n							$from = $xform[1];\r\n							$to = isset($xform[2]) ? $xform[2] : \'\';\r\n							$out = ($type==\'regex\') ? preg_replace($from, $to, $out) : str_replace($from, $to, $out);\r\n						break;\r\n						case \'round\':\r\n							if (isset($xform[0])) {\r\n								if ($xform[0] == \"down\") {\r\n									$out = floor((float)$out);\r\n								} else if ($xform[0] == \"up\") {\r\n									$out = ceil((float)$out);\r\n								} else if ($xform[0] == \"positive\") {\r\n									$out = abs($out);\r\n								} else {\r\n									// Assume any second arg is the precision\r\n									$out = round((float)$out, (int)$xform[0]);\r\n								}\r\n							} else {\r\n								$out = round((float)$out);\r\n							}\r\n						break;\r\n						case \'sanitize\':\r\n							if ($xform[0] == \"url\") {\r\n								$out = sanitizeForUrl($out);\r\n							} else if ($xform[0] == \"file\") {\r\n								$out = sanitizeForFile($out);\r\n							} else if ($xform[0] == \"url_title\") {\r\n								$out = stripSpace($out, 1);\r\n							} else if ($xform[0] == \"encode\") {\r\n								$out = urlencode($out);\r\n							} else if ($xform[0] == \"raw\") {\r\n								$out = rawurlencode($out);\r\n							}\r\n						break;\r\n						case \'split\':\r\n							$parts = explode($xform[0], $out);\r\n							array_shift($xform); // Throw away the split character\r\n							$joinchar = array_shift($xform);\r\n\r\n							// Grab the specified parts to return\r\n							$retstr = array();\r\n							$numParts = count($parts);\r\n							foreach ($xform as $idx) {\r\n								$addit = true;\r\n								if ($idx == \'all\') {\r\n									$retstr = array_merge($retstr, $parts);\r\n									$addit = false;\r\n								} else if ($idx == \'last\') {\r\n									$idx = $numParts;\r\n								} else if ($idx == \'rand\') {\r\n									$idx = mt_rand(1, $numParts);\r\n								} else if (strpos($idx, \'-\') === 0) {\r\n									// Negative offset: count from the end: -1 = last, -2 = penultimate, etc\r\n									// The +1 is to counter the fact we subtract one in a moment. Damn zero indices\r\n									$idx = $numParts - substr($idx, 1) + 1;\r\n								} else if (strpos($idx, \'>\') === 0) {\r\n									$retstr = array_merge($retstr, array_slice($parts, substr($idx, 1)));\r\n									$addit = false;\r\n								} else if (strpos($idx, \'<\') === 0) {\r\n									$retstr = array_merge($retstr, array_slice($parts, 0, substr($idx, 1) - 1 ));\r\n									$addit = false;\r\n								}\r\n								// Subtract one because the input is \'human\'\r\n								// e.g. split|.|+|1|3 == return the 1st and 3rd parts == $parts[0] + $parts[2]\r\n								if ($addit) {\r\n									$retstr[] = $parts[$idx-1];\r\n								}\r\n							}\r\n\r\n							$out = join($joinchar, $retstr);\r\n						break;\r\n						case \'strip_tags\':\r\n							$out = strip_tags($out);\r\n						break;\r\n						case \'textile\':\r\n							include_once txpath.\'/lib/classTextile.php\';\r\n							$textile = new Textile();\r\n							$out = $textile->TextileThis($out);\r\n						break;\r\n						case \'trim\':\r\n							$charlist = isset($xform[0]) ? $xform[0] : \'\';\r\n							$out = ($charlist) ? trim($out, $charlist) : trim($out);\r\n						break;\r\n					}\r\n					if ($debug) {\r\n						echo \'++ AFTER \' . $xtype . \' ++\';\r\n						dmp($out);\r\n					}\r\n				}\r\n			}\r\n		} else {\r\n			return parse(EvalElse($data, 0));\r\n		}\r\n	}\r\n\r\n	return ($out) ? doLabel($label, $labeltag).doTag($out, $wraptag, $class, $attr, $html_id) : \'\';\r\n}\r\n\r\n// Convenience to emulate v0.10 functionality\r\nfunction smd_wrap_all($atts, $thing=NULL) {\r\n	$atts[\'data_mode\'] = \'all\';\r\n	return smd_wrap($atts, $thing);\r\n}\r\n\r\n// Display the contents of the previous output in chained transform=\"form\"\r\nfunction smd_wrap_info($atts, $thing=NULL) {\r\n	global $smd_wrap_data;\r\n\r\n	extract(lAtts(array(\r\n		\'debug\'   => 0,\r\n	), $atts));\r\n\r\n	if ($debug) {\r\n		echo \'++ AVAILABLE WRAP INFO ++\';\r\n		dmp($smd_wrap_data);\r\n	}\r\n\r\n	return $smd_wrap_data;\r\n}', '/**\r\n * smd_wrap\r\n *\r\n * A Textpattern CMS plugin for wrapping content with HTML tags, labels and attributes.\r\n *  -> Adds wraptag / class / html_id / label support round any tag\r\n *  -> Permits a range of formatting options for manipulating the item\r\n *     (e.g. trim, escape, sanitize, change case, linkify, format date,\r\n *     strip tags, split / combine, process with textile, etc).\r\n *  -> If the content is empty, nothing is displayed.\r\n *  -> Supports <txp:else />\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\n// TODO:\r\n// * Security: http://forum.textpattern.com/viewtopic.php?pid=255560#p255560\r\n// * Allow hidden pref to determine default transforms?\r\n// * Conditionals: http://forum.textpattern.com/viewtopic.php?pid=256465#p256465\r\n\r\nfunction smd_wrap($atts, $thing=NULL) {\r\n	global $smd_wrap_data, $prefs, $yield;\r\n\r\n	extract(lAtts(array(\r\n		\'data_mode\'   => \'txp_tags\', // txp_tags (default), all\r\n		\'item\'        => \'\',\r\n		\'wraptag\'     => \'\',\r\n		\'class\'       => \'\',\r\n		\'html_id\'     => \'\',\r\n		\'label\'       => \'\',\r\n		\'labeltag\'    => \'\',\r\n		\'attr\'        => \'\',\r\n		\'prefix\'      => \'\',\r\n		\'suffix\'      => \'\',\r\n		\'format\'      => \'\', // convenience only: same as transform\r\n		\'transform\'   => \'\',\r\n		\'form\'         => \'\',\r\n		\'delim\'       => \',\',\r\n		\'param_delim\' => \'|\',\r\n		\'trim\'        => 1,\r\n		\'debug\'       => 0,\r\n	),$atts));\r\n\r\n	// item attribute trumps container\r\n	$data = ($item) ? $item : ((empty($form)) ? $thing : fetch_form($form));\r\n	$has_yield = ($form && $thing);\r\n	$out = \'\';\r\n	$tag_mode = ($data_mode == \'txp_tags\');\r\n\r\n	if ($format) {\r\n		trigger_error(\"smd_wrap: format attribute deprecated: use transform attribute instead.\", E_USER_NOTICE);\r\n		$transform = $format;\r\n	}\r\n\r\n	// Grab the true portion of any container\r\n	$truePart = EvalElse($data, 1);\r\n\r\n	if ($debug) {\r\n		echo \'++ TO WRAP ++\';\r\n		dmp($data);\r\n	}\r\n\r\n	if ($data) {\r\n		// Handle custom attributes\r\n		if ($attr) {\r\n			$custom_atts = array();\r\n			$attribs = do_list($attr);\r\n			foreach($attribs as $attdef) {\r\n				list($key, $val) = do_list($attdef, $param_delim);\r\n				$custom_atts[] = $key . \'=\"\' . $val . \'\"\';\r\n			}\r\n			$attr = \' \' . join(\' \', $custom_atts);\r\n		}\r\n\r\n		if ($tag_mode) {\r\n			// Regex to match Txp tags, stolen from the parser\r\n			$tagz = \'@(</?txp:\\w+(?:\\s+\\w+\\s*=\\s*(?:\"(?:[^\"]|\"\")*\"|\\\'(?:[^\\\']|\\\'\\\')*\\\'|[^\\s\\\'\"/>]+))*\\s*/?\'.chr(62).\')@s\';\r\n\r\n			// Strip Txp tags from the content and jot down the remaining content length\r\n			$size_without_txp_tags = strlen(preg_replace($tagz, \'\', $truePart));\r\n		}\r\n\r\n		// Run the Txp parser, taking into account any txp:yield\r\n		if ($has_yield) {\r\n			$input = $trim ? trim($thing) : $thing;\r\n			$yield[] = parse($input);\r\n		}\r\n		$out = parse($truePart);\r\n		if ($tag_mode) {\r\n			$size_after_parsing = strlen($out);\r\n			$has_data = ($size_after_parsing > $size_without_txp_tags);\r\n		}\r\n		if ($has_yield) {\r\n			array_pop($yield);\r\n		}\r\n\r\n		$out = $trim ? trim($out) : $out;\r\n		switch ($data_mode) {\r\n			case \'all\':\r\n				$has_content = $out;\r\n			break;\r\n			case \'txp_tags\':\r\n			default:\r\n				$has_content = $has_data;\r\n			break;\r\n		}\r\n\r\n		if ($has_content) {\r\n			// Top and tail the output\r\n			$out = $prefix.$out.$suffix;\r\n\r\n			// Reformat the item with any of the following transformations, in the supplied order\r\n			if ($transform) {\r\n				if ($debug) {\r\n					echo \'++ BEFORE TRANSFORM ++\';\r\n					dmp($out);\r\n				}\r\n				$formats = do_list($transform, $delim);\r\n				foreach ($formats as $xformlist) {\r\n					// Use explode() because do_list() performs a trim() that we don\'t want\r\n					$xform = explode($param_delim, $xformlist);\r\n					$xtype = array_shift($xform);\r\n\r\n					switch ($xtype) {\r\n						case \'add\':\r\n							$pos = array_shift($xform);\r\n							$val = $xform[0];\r\n							$out = (($pos == \'before\' || $pos == \'both\') ? $val : \'\') . $out . (($pos == \'after\' || $pos == \'both\') ? $val : \'\');\r\n						break;\r\n						case \'case\':\r\n							foreach ($xform as $arg) {\r\n								if ($arg == \"upper\") {\r\n									$out = strtoupper($out);\r\n								} else if ($arg == \"lower\") {\r\n									$out = strtolower($out);\r\n								} else if ($arg == \"ucfirst\") {\r\n									$out = ucfirst($out);\r\n								} else if ($arg == \"ucwords\") {\r\n									$out = ucwords($out);\r\n								} else if ($arg == \"title\") {\r\n									// Inelegantly ported + extended for Unicode from David Gouch\'s JS title case script: thanks!\r\n									// http://individed.com/code/to-title-case/js/to-title-case.js\r\n									$has_unicode = @preg_match(\'/\\pL/u\', \'a\');\r\n\r\n									$az = ($has_unicode) ? \'\\p{Lu}\' : \'A-Z\';\r\n									$wrd = ($has_unicode) ? \'(?:\\p{L}|\\p{M}|\\p{N}|\\p{Pc})\' : \'\\w\';\r\n									$capsre = \'/[\' . $az . \']+|&|[\' . $wrd . \']+[._][\' . $wrd . \']+/\';\r\n\r\n									$smalls = get_pref(\'smd_wrap_small_words\', \'a(nd?|s|t)?|b(ut|y)|en|for|i[fn]|o[fnr]|t(he|o)|vs?\\.?|via\');\r\n									$smallre = \'/^(\' . $smalls . \')[ \\-]/i\';\r\n\r\n									$pat = \'/([\' . $wrd . \'&`\\\'‘’\"“.@:\\/\\{\\(\\[<>_]+-? *)/\';\r\n									$ret = array();\r\n									preg_match_all($pat, $out, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE);\r\n\r\n									foreach ($matches[0] as $it) {\r\n										$match = $it[0];\r\n										$index = $it[1];\r\n										$idxm2 = $index - 2;\r\n										$length = strlen($match);\r\n										$title = $out; // Copy the original since we\'re working on $out directly\r\n\r\n										// Fudge because substr with negative start counts from end of string in PHP\r\n										$idxm1 = (($index-1) < 0) ? 0 : $index - 1;\r\n										$offset = (($index-1) < 0) ? 1 : 2;\r\n\r\n										if ($index > 0\r\n											&& ( $title{$idxm2} !== \":\" )\r\n											&& ( preg_match($smallre, $match) > 0 )\r\n										) {\r\n											$out = substr($out, 0, $index) . strtolower($match) . substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										if (preg_match(\'/[\\\'\\\"_{(\\[]/\', substr($title, $idxm1, $offset)) > 0) {\r\n											$out = substr($out, 0, $index) . $match{0} . @strtoupper($match{1}) . substr($match, 2). substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										if (\r\n											( preg_match($capsre, substr($match, 1)) > 0 )\r\n											|| ( preg_match(\'/[\\])}]/\', substr($title, $idxm1, $offset)) > 0 )\r\n										) {\r\n											$out = substr($out, 0, $index) . $match . substr($out, $index+$length);\r\n											continue;\r\n										}\r\n										$out = substr($out, 0, $index) . strtoupper($match{0}) . substr($match, 1) . substr($out, $index+$length);\r\n									}\r\n								}\r\n							}\r\n						break;\r\n						case \'currency\':\r\n							$fmt = (isset($xform[0])) ? $xform[0] : \'%.2n\';\r\n							if (isset($xform[1])) {\r\n								$lcl = setlocale(LC_MONETARY, \"0\"); // Preserve current monetary locale\r\n								setlocale(LC_MONETARY, $xform[2]);\r\n							}\r\n							$out = money_format($fmt, (float)$out);\r\n							if (isset($xform[1])) {\r\n								// Restore locale\r\n								setlocale(LC_MONETARY, $lcl);\r\n							}\r\n						break;\r\n						case \'cut\':\r\n							$truncated = false;\r\n							foreach ($xform as $arg) {\r\n								// Split the argument into numeric and alpha parts [c]hars, [w]ords.\r\n								// If it matches at least one of them it\'s a number of items to split at (default: chars)\r\n								// If it matches none of them then it\'s a continuation char sequence (e.g. &hellip;)\r\n								preg_match(\'/(\\d+)([a-zA-Z])?/\', $arg, $cmatch);\r\n								if ($cmatch) {\r\n									$items = isset($cmatch[1]) ? $cmatch[1] : \'0\';\r\n									$plier = isset($cmatch[2]) ? $cmatch[2] : \'c\';\r\n									switch ($plier) {\r\n										case \'w\':\r\n											// Crude split: will probably break under UTF-8 strings\r\n											$words = preg_split(\"/[\\s]+/\", $out);\r\n											if( (count($words) > $items) && ($items > 0) ) {\r\n												$out = implode(\' \', array_slice($words, 0, $items));\r\n												$truncated = true;\r\n											}\r\n										break;\r\n										case \'c\':\r\n											$chars = strlen($out);\r\n											if ( ($chars > $items) && ($items > 0) ) {\r\n												$out = substr($out, 0, $items);\r\n												$truncated = true;\r\n											}\r\n										default:\r\n										break;\r\n									}\r\n								} else if ($truncated) {\r\n									// Tack on the continuation string\r\n									$out .= $arg;\r\n								}\r\n							}\r\n						break;\r\n						case \'date\':\r\n						case \'time\':\r\n							$nd = (is_numeric($out)) ? $out : strtotime($out);\r\n							if ($nd !== false) {\r\n								$out = strftime($xform[0], $nd);\r\n							}\r\n						break;\r\n						case \'escape\':\r\n							$flags = 0;\r\n							foreach ($xform as $arg) {\r\n								switch ($arg) {\r\n									case \'no_quotes\':\r\n										$flags |= ENT_NOQUOTES;\r\n									break;\r\n									case \'all_quotes\':\r\n										$flags |= ENT_QUOTES;\r\n									break;\r\n									case \'double_quotes\':\r\n										$flags |= ENT_COMPAT;\r\n									break;\r\n									default:\r\n										$flags |= $arg;\r\n									break;\r\n								}\r\n							}\r\n							$out = htmlspecialchars($out, $flags);\r\n						break;\r\n						case \'fordb\':\r\n							$out = doSlash($out);\r\n						break;\r\n						case \'form\':\r\n							foreach ($xform as $arg) {\r\n								$content = fetch_form($arg);\r\n								$smd_wrap_data = $out;\r\n								$reps = array(\r\n									\'{smd_wrap_it}\' => $out,\r\n								);\r\n								$out = parse(strtr($content, $reps));\r\n							}\r\n						break;\r\n						case \'link\':\r\n							// From http://codesnippets.joyent.com/posts/show/2104\r\n							$pat = \"@\\b(https?://)?(([0-9a-zA-Z_!~*\'().&=+$%-]+:)?[0-9a-zA-Z_!~*\'().&=+$%-]+\\@)?(([0-9]{1,3}\\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*\'()-]+\\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*\'().;?:\\@&=+$,%#-]+)*/?)@\";\r\n							$text = (isset($xform[0]) && $xform[0] != \'\') ? $xform[0] : \'$0\';\r\n							$out = preg_replace($pat, \'<a href=\"$0\">\'.$text.\'</a>\', $out);\r\n						break;\r\n						case \'no_widow\':\r\n							$no_widow = isset($xform[0]) ? $xform[0] : @$prefs[\'title_no_widow\'];\r\n							$out = ($no_widow) ? noWidow($out) : $out;\r\n						break;\r\n						case \'number\':\r\n							$lcl = NULL;\r\n							if ( isset($xform[0]) && !is_numeric($xform[0]) ) {\r\n								// Locale first, so preserve the current one\r\n								$lcl = setlocale(LC_NUMERIC, \"0\");\r\n								setlocale(LC_NUMERIC, $xform[0]);\r\n								array_shift($xform);\r\n							}\r\n							if (isset($xform[0])) {\r\n								$lcnv = localeconv();\r\n								$dec = $xform[0];\r\n								$dec_point = (isset($xform[1])) ? $xform[1] : $lcnv[\'decimal_point\'];\r\n								$thou_sep = (isset($xform[2])) ? $xform[2] : $lcnv[\'thousands_sep\'];\r\n								$out = number_format((float)$out, $dec, $dec_point, $thou_sep);\r\n							} else {\r\n								$out = number_format((float)$out);\r\n							}\r\n							if ($lcl) {\r\n								// Restore locale\r\n								setlocale(LC_NUMERIC, $lcl);\r\n							}\r\n							break;\r\n						case \'replace\':\r\n							$type = $xform[0] ? $xform[0] : \'string\'; // string / regex\r\n							$from = $xform[1];\r\n							$to = isset($xform[2]) ? $xform[2] : \'\';\r\n							$out = ($type==\'regex\') ? preg_replace($from, $to, $out) : str_replace($from, $to, $out);\r\n						break;\r\n						case \'round\':\r\n							if (isset($xform[0])) {\r\n								if ($xform[0] == \"down\") {\r\n									$out = floor((float)$out);\r\n								} else if ($xform[0] == \"up\") {\r\n									$out = ceil((float)$out);\r\n								} else if ($xform[0] == \"positive\") {\r\n									$out = abs($out);\r\n								} else {\r\n									// Assume any second arg is the precision\r\n									$out = round((float)$out, (int)$xform[0]);\r\n								}\r\n							} else {\r\n								$out = round((float)$out);\r\n							}\r\n						break;\r\n						case \'sanitize\':\r\n							if ($xform[0] == \"url\") {\r\n								$out = sanitizeForUrl($out);\r\n							} else if ($xform[0] == \"file\") {\r\n								$out = sanitizeForFile($out);\r\n							} else if ($xform[0] == \"url_title\") {\r\n								$out = stripSpace($out, 1);\r\n							} else if ($xform[0] == \"encode\") {\r\n								$out = urlencode($out);\r\n							} else if ($xform[0] == \"raw\") {\r\n								$out = rawurlencode($out);\r\n							}\r\n						break;\r\n						case \'split\':\r\n							$parts = explode($xform[0], $out);\r\n							array_shift($xform); // Throw away the split character\r\n							$joinchar = array_shift($xform);\r\n\r\n							// Grab the specified parts to return\r\n							$retstr = array();\r\n							$numParts = count($parts);\r\n							foreach ($xform as $idx) {\r\n								$addit = true;\r\n								if ($idx == \'all\') {\r\n									$retstr = array_merge($retstr, $parts);\r\n									$addit = false;\r\n								} else if ($idx == \'last\') {\r\n									$idx = $numParts;\r\n								} else if ($idx == \'rand\') {\r\n									$idx = mt_rand(1, $numParts);\r\n								} else if (strpos($idx, \'-\') === 0) {\r\n									// Negative offset: count from the end: -1 = last, -2 = penultimate, etc\r\n									// The +1 is to counter the fact we subtract one in a moment. Damn zero indices\r\n									$idx = $numParts - substr($idx, 1) + 1;\r\n								} else if (strpos($idx, \'>\') === 0) {\r\n									$retstr = array_merge($retstr, array_slice($parts, substr($idx, 1)));\r\n									$addit = false;\r\n								} else if (strpos($idx, \'<\') === 0) {\r\n									$retstr = array_merge($retstr, array_slice($parts, 0, substr($idx, 1) - 1 ));\r\n									$addit = false;\r\n								}\r\n								// Subtract one because the input is \'human\'\r\n								// e.g. split|.|+|1|3 == return the 1st and 3rd parts == $parts[0] + $parts[2]\r\n								if ($addit) {\r\n									$retstr[] = $parts[$idx-1];\r\n								}\r\n							}\r\n\r\n							$out = join($joinchar, $retstr);\r\n						break;\r\n						case \'strip_tags\':\r\n							$out = strip_tags($out);\r\n						break;\r\n						case \'textile\':\r\n							include_once txpath.\'/lib/classTextile.php\';\r\n							$textile = new Textile();\r\n							$out = $textile->TextileThis($out);\r\n						break;\r\n						case \'trim\':\r\n							$charlist = isset($xform[0]) ? $xform[0] : \'\';\r\n							$out = ($charlist) ? trim($out, $charlist) : trim($out);\r\n						break;\r\n					}\r\n					if ($debug) {\r\n						echo \'++ AFTER \' . $xtype . \' ++\';\r\n						dmp($out);\r\n					}\r\n				}\r\n			}\r\n		} else {\r\n			return parse(EvalElse($data, 0));\r\n		}\r\n	}\r\n\r\n	return ($out) ? doLabel($label, $labeltag).doTag($out, $wraptag, $class, $attr, $html_id) : \'\';\r\n}\r\n\r\n// Convenience to emulate v0.10 functionality\r\nfunction smd_wrap_all($atts, $thing=NULL) {\r\n	$atts[\'data_mode\'] = \'all\';\r\n	return smd_wrap($atts, $thing);\r\n}\r\n\r\n// Display the contents of the previous output in chained transform=\"form\"\r\nfunction smd_wrap_info($atts, $thing=NULL) {\r\n	global $smd_wrap_data;\r\n\r\n	extract(lAtts(array(\r\n		\'debug\'   => 0,\r\n	), $atts));\r\n\r\n	if ($debug) {\r\n		echo \'++ AVAILABLE WRAP INFO ++\';\r\n		dmp($smd_wrap_data);\r\n	}\r\n\r\n	return $smd_wrap_data;\r\n}', '4eff23fbae27b0567e5dcbbfa73a45bb', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_tags', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.60', 'Unlimited tag taxonomy for articles, images, files and links', '<h1>smd_tags</h1>\n\n<p>Tag articles, images, files and links with stuff, then use the public-side tags to display the lists, filter or find related content.</p>\n\n<h2>Features</h2>\n\n<p>Admin-side:</p>\n\n<ul>\n	<li>Unlimited tags for articles, images, files and links</li>\n	<li>Fast tagging system for copying, auto-naming, and optional auto-completion</li>\n	<li>Nesting of tags</li>\n	<li>Live search / filter, with multi-tag operations</li>\n	<li>Optionally link tags to Txp categories so clients can only choose from a limited set of tags</li>\n	<li>Assign tags via a dropdown select list, a text area or a list of clickable tags</li>\n	<li>Set up (optional) dedicated sections to allow public side tag filtering</li>\n	<li>Import (article) tags from other tagging plugins or Txp&#8217;s categories / custom fields</li>\n</ul>\n\n<p>Public-side:</p>\n\n<ul>\n	<li>List / filter tags by type and name. Weighted (cloud) or alphabetical lists possible</li>\n	<li>Show related content:\n	<ul>\n		<li>by tag <em>between types</em>. For example if you have set up common tags for both articles and files, when viewing an article you can ask the plugin what files are related (or vice versa: what articles are related to the current file!)</li>\n		<li>between other content, e.g. ask it to match the tags from the current file with the article category or a custom field</li>\n	</ul></li>\n	<li>Multi-tag filtering</li>\n	<li>Tag conditionals and counts</li>\n</ul>\n\n<p>To do (possibly):</p>\n\n<ul>\n	<li>Fix for foreign character autotag dumbdown</li>\n	<li>Tag and article paging support: next/prev/older/newer</li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p>Requires Textpattern 4.6.2+.</p>\n\n<p>If upgrading from plugin v0.20 or earlier: delete smd_tags_client and smd_tags_admin plugins first, then install and activate this plugin. Your prefs and tags will be retained.</p>\n\n<p>Download the plugin from either <a href=\"https://github.com/Bloke/smd_tags/releases\" rel=\"nofollow\">GitHub</a> or the <a href=\"https://stefdawson.com/sw\" rel=\"nofollow\">software page</a>. Paste into the Txp <em>Admin-&gt;Plugins</em> panel, install and enable the plugin.</p>\n\n<p>Visit the <em>Extensions-&gt;Tags (smd)</em> panel. If it has not already been done automatically for you, click the Install tables/Install prefs buttons. The necessary tables and preferences will be installed. From this page you can Install, Delete and Save the plugin preferences or remove the plugin&#8217;s tables to empty your tags entirely and start again.</p>\n\n<p>To completely remove smd_tags, visit the <em>Tag Preferences</em> page, hit ‘Remove tables’ to remove the preferences and tag tables from the database, then simply delete the plugin as normal from the <em>Admin-&gt;Plugins</em> page.</p>\n\n<p>Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=28621\" rel=\"nofollow\">forum thread</a> for more info and to report the success (or otherwise) of this plugin.</p>\n\n<h2>Admin-side buttons</h2>\n\n<ul>\n	<li><strong>Manage tags</strong>: Switch to the <a href=\"https://stefdawson.com/#manage\" rel=\"nofollow\">tag management page</a></li>\n	<li><strong>Preferences</strong>: Switch to the <a href=\"https://stefdawson.com/#prefs\" rel=\"nofollow\">preferences page</a></li>\n	<li><strong>Import tags</strong>: <a href=\"https://stefdawson.com/#import\" rel=\"nofollow\">Import tags</a> from tru_tags, rss_unlimited_categories or Txp categories/custom fields</li>\n	<li><strong>Install prefs &amp;#8224;</strong>: Install the plugin preferences; useful if your have upgraded or wish to resync missing prefs. Any existing preference values will be retained.</li>\n	<li><strong>Remove prefs &amp;#8224;</strong>: Delete the plugin preferences; useful if your prefs have become corrupted or you simply want to return to “default” operation.</li>\n	<li><strong>Remove tables &amp;#8224;</strong>: Delete the tags tables. This will permanently erase all tags against all articles, images, files and links. Be very sure you mean it before clicking this!</li>\n	<li><strong>Rebuild tags &amp;#8224;</strong>: If your tag table is corrupt you <strong>may</strong> be able to fix it with this option. However, if it is badly damaged you may just get a white screen of death. If that is the case, your options are then to either fix it by hand or delete the bad entries, rebuild the table with the remaining items and then repopulate the tags.</li>\n</ul>\n\n<p>&amp;#8224; Use the <strong>More&#8230;</strong> link to reveal these items.</p>\n\n<h2>The preferences</h2>\n\n<h3>Interface setting prefs</h3>\n\n<ul>\n	<li><strong>Enable tags in</strong>: a tag entry mechanism is added to the admin side of each of the checked pages.</li>\n	<li><strong>Enter tags using</strong>: the mechanism you wish to use to assign tags. <code>Text area+</code> allows new tags to be added on the fly via contributors (a bit like tru_tags).</li>\n	<li><strong>Bi-directional tag trees</strong>: when you assign a tag to an item, any time you display related items by tag, only exact matching items are returned. If you organise your tags in hierarchical trees, you may wish to have parent or child tags automatically considered. Choose from:\n	<ul>\n		<li><strong>No</strong>: Only exact tag matches are considered when returning related content.</li>\n		<li><strong>Up</strong>: for any given tag assigned to an item, consider all its parent tags also implicitly assigned.</li>\n		<li><strong>Down</strong>: for any given tag assigned to an item, consider all its child tags also implicitly assigned.</li>\n		<li><strong>Both</strong>: for any given tag assigned to an item, consider all its parent and child tags also implicitly assigned.</li>\n	</ul></li>\n	<li><strong>Link tags to categories</strong>: allow tags to be associated with Txp categories. If this is on and <code>Text area+</code> is being used, any new tags added via the article, image, file or link screens will be associated with the currently selected category (or category1 in the case of articles).</li>\n	<li><strong>Permit parent tag selection</strong>: if set to ‘yes’, the parent tags assigned to the chosen categories are selecteable as tags. Set to ‘no’ if your tag hierarchy is such that the parent tag is just a placeholder or “group leader” with no intrinsic value other than as a parent for a bunch of child tags.</li>\n	<li><strong>Master parent tag</strong>: the name of a tag that you designate as the ‘master’ tag. Assigning any tags beneath this one will be added to any per-category tags to make up the final available tag pool.</li>\n	<li><strong>Quick tag</strong>: if using a textarea and you install the jQuery autocomplete plugin, this determines which method of auto-complete to use: <code>strict</code> prevents tags being submitted that are disallowed by the current Txp category; <code>standard</code> allows new tags to be entered. Note that you <strong>may not enter new tags at all</strong> if Quick tags is in <code>strict</code> mode: it overrides <code>Text area+</code>.</li>\n	<li><strong>js plugin dir</strong>: the directory in which your auto-complete plugin resides (the filename it expects is <code>jquery.autocomplete.pack.js</code>). If you begin the directory name with a <code>/</code> the path will be relative to your site root. Without a preceding ‘/’ it is relative to the ‘textpattern’ directory. The trailing slash is optional and will be added internally if you omit it. This gives you the freedom to put the files wherever it suits you and reference them like this:\n	<ul>\n		<li>use <code>my_js</code> for ‘textpattern/my_js/jquery.autocomplete.pack.js’</li>\n		<li>use either <code>../my_scripts</code>  or <code>/my_scripts</code> for ‘site_root/my_scripts/jquery.autocomplete.pack.js’</li>\n	</ul></li>\n	<li><strong>js style dir</strong>: the directory in which your auto-complete <span class=\"caps\">CSS</span> files reside (the file name it expects is <code>jquery.autocomplete.css</code>). The same comments hold true about the path as above.</li>\n	<li><strong>Select/textarea rows</strong>: number of rows in the select list or textarea. If set to <code>1</code> a select list will become non-multiple and you can then only choose 1 tag. Set to <code>0</code> to show a multiple select list containing all tags. Other values show a multiple select list with the given number of rows but be aware this may turn out to be a guide as certain browser factors may override the value. For example, in Firefox if you choose a select list value of <code>2</code> you cannot see the whole list because the input is not big enough to show the scrollbar (you get a scrollbar from a value of <code>3</code> or more). Also, if you are using autocomplete for the text area you may not get the number of rows you expect due to the <span class=\"caps\">CSS</span> that comes with the autocomplete plugin.</li>\n</ul>\n\n<h3>Tag management prefs</h3>\n\n<ul>\n	<li><strong>Initial pane</strong>: when you click <em>Extensions -&gt; Tags (smd)</em>, show either the preferences page or the tag management page.</li>\n	<li><strong>Auto name</strong>: helps speed tag creation by automatically naming them based on the title you use.</li>\n	<li><strong>Tag layout</strong>: can be either:\n	<ul>\n		<li>the number of columns of tags to display in the management page. This creates a table.</li>\n		<li>the word <code>list</code> to show them as an unordered list. If you specify <code>list:N</code> then the list will be split every ‘N’ tags and start a new row/column (depending on your setting of <em>Order tags by</em>).</li>\n		<li>the word <code>group</code> to start a new row or column each time a new top-level tag is encountered. Useful for heavily nested or hierarchical tags.</li>\n	</ul></li>\n	<li><strong>Order tags by</strong>: ‘column’ to have the tags work down the page first; ‘row’ to work across the page.</li>\n	<li><strong>Show tag usage counts</strong>: whether to indicate the number of items for which a tag is used.</li>\n	<li><strong>When deleting a parent</strong>: choose whether any child tags will be promoted to the same level as the tag you just deleted, or the entire tree below the deleted tag will be removed as well.</li>\n	<li><strong>Allow deletion of used tags</strong>: when set to ‘no’, if a tag has been used by an article/image/file/link it cannot be deleted.</li>\n	<li><strong>Textile description</strong>: allow Textile to be used in tag descriptions.</li>\n	<li><strong>Show description as tooltip</strong>: when on a content panel and you have elected to use Select List or Text List input modes, this setting tells the plugin to popup a tooltip of the description as you hover over a tag.</li>\n	<li><strong>Automatically display reports</strong>: after operations that affect multiple tags, a report listing the alterations is available. If set to ‘yes’ this report is popped up for you to read immediately after the operation. If set to ‘no’ you can view the report by clicking the <em>Display recent report</em> link in the Tag Search box.</li>\n	<li><strong>Clicked <span class=\"caps\">RGB</span> colour</strong>: the <span class=\"caps\">CSS</span> background colour for the currently edited tag.</li>\n	<li><strong>Mouse-over <span class=\"caps\">RGB</span> colour</strong>: the <span class=\"caps\">CSS</span> background colour as you move over each item in the list.</li>\n	<li><strong>Sub-tag level indicator</strong>: the <span class=\"caps\">HTML</span> entity or text you wish to precede each sub-tag with. Level 1 tags have one of these symbols added, level 2 tags have two added, and so on.</li>\n	<li><strong>Multi-tag delimiter</strong>: allows you to specify more than one tag at a time during creation. If you enter more than one character here, only the first will be used. To disable this feature, empty the box contents.</li>\n	<li><strong>Description width, height</strong>: width and height (comma-separated, in pixels) of the description textarea input field.</li>\n</ul>\n\n<h3><span class=\"caps\">URL</span> management prefs</h3>\n\n<ul>\n	<li><strong>Use tag combinations</strong>: If set to yes, you may perform multi-tag searches from the <span class=\"caps\">URL</span>. If set to no, only the last tag is taken into account.</li>\n	<li><strong><span class=\"caps\">AND</span> combinator char</strong>: The character to use between tags when you want to find smd_related_tags that match <span class=\"caps\">ALL</span> the tags in the <span class=\"caps\">URL</span>. Default: <code>+</code>.</li>\n	<li><strong>OR combinator char</strong>: The character to use between tags when you want to find smd_related_tags that match <span class=\"caps\">ANY</span> of the tags in the <span class=\"caps\">URL</span>. Default: <code>|</code>.</li>\n	<li><strong><span class=\"caps\">URL</span> name parameter</strong>: When filtering by tag name, this is the <span class=\"caps\">URL</span> string used to indicate a tag.</li>\n	<li><strong><span class=\"caps\">URL</span> type parameter</strong>: When filtering by tag type, this is the <span class=\"caps\">URL</span> string used to indicate a tag type.</li>\n	<li><strong>Trigger(s) for tag lists</strong>: A comma-separated list of trigger words, or Txp Sections which are valid tag landing pages<sup class=\"footnote\" id=\"fnrev4225642125c6d733a40748-1\"><a href=\"#fn4225642125c6d733a40748-1\">1</a></sup>. Any automatically generated links from the public-side tags will be sent to the <em>first</em> section in the list for display. Can be overridden on a tag-by-tag basis with the <code>section_link</code> attribute.</li>\n</ul>\n\n<p class=\"footnote\" id=\"fn4225642125c6d733a40748-1\"><sup>1</sup> If using this feature for Sections, they must exist in your Txp Sections tab, unless you are using some gbp_permanent_links magic. Note that <span class=\"caps\">URL</span>s can be any of the following formats:</p>\n\n<ul>\n	<li>Full clean : <code>site.com/trigger/tag-type/tag-name</code></li>\n	<li>Short-circuited clean : <code>site.com/trigger/tag-name</code> (defaults to <code>article</code> tag type)</li>\n	<li>Messy default : <code>site.com/trigger?smd_tagtype=tag-type&amp;smd_tag=tag-name</code> (smd_tagtype can be omitted to mean ‘article’)</li>\n	<li>Messy per-section: <code>site.com/section?smd_tagtype=tag-type&amp;smd_tag=tag-name</code></li>\n	<li>Clean per-section: <code>site.com/section/trigger/tag-type/tag-name</code></li>\n	<li>Short-circuited per-section clean : <code>site.com/section/trigger/tag-name</code></li>\n	<li>Short-circuited per-section messy : <code>site.com/section?smd_tag=tag-name</code></li>\n</ul>\n\n<p>(the <span class=\"caps\">URL</span> params <code>smd_tag</code> and <code>smd_tagtype</code> can be altered from the prefs).</p>\n\n<p>Watch out if you are using clean urls because they may clash with your individual article views. For example, if you chose to detect tag lists on your <code>widgets</code> section and you&#8217;re using the section/title permlink scheme, the plugin won&#8217;t know the difference between:</p>\n\n<ul>\n	<li>/widgets/how-to-order (an individual article under the /section/title permlink scheme)</li>\n	<li>/widgets/widget-c (a tag for widget-c)</li>\n</ul>\n\n<p>So you should make sure in this case that you either a) use a dedicated (non-article-bearing) section for your tag lists, b) always use the full tag syntax including type: <code>widgets/article/widget-c</code>, c) use messy tag syntax, d) change permlink scheme. The plugin tries to avoid clobbering <code>/section/id/title</code> format by detecting the presence of the numeric id, so that may be the safest option if you are mixing tag landing pages with article sections.</p>\n\n<h2>Tag management pane</h2>\n\n<p>Displays a list of all defined tags. In contrast to the built-in categories, only one type is shown at a time; switch between them with the radio buttons in the ‘Filter’ row.</p>\n\n<p>The input row at the top has four or five fields in it, depending if you have chosen to link tags to categories:</p>\n\n<ul>\n	<li><strong>Title</strong>: The display name of the tag on the public site.</li>\n	<li><strong>Description</strong>: A description to explain the tag&#8217;s purpose.</li>\n	<li><strong>Parent</strong>: Whether the tag is in a sub-category. The empty item is considered ‘root’ (top level).</li>\n	<li><strong>Linked cat</strong>: (optional) Whether the tag is associated with a Txp category. Any sub-tags are automatically assigned to the chosen category as well.</li>\n	<li><strong>Name</strong>: The ‘internal’ Txp tag name. Probably shouldn&#8217;t contain spaces or weird characters, although you can put them in if you know what you&#8217;re doing.</li>\n</ul>\n\n<p>With the auto-name feature enabled, whatever you type in the Title field will be mimicked in the Name field, but with only lower case alphanumeric characters. Spaces will be converted to dashes. Note that at present, foreign characters are not ‘dumbed down’ to <span class=\"caps\">ASCII</span>. This feature is planned but for the time being it is probably best to switch off the auto-name feature if you are dealing heavily with unicode characters. Your tags will still be dumbed down according to Txp&#8217;s internal rules exactly like they are on the Categories panel; you just won&#8217;t be able to see its name until you click to highlight it.</p>\n\n<p>If you have elected to use multi-tag entry, you may put as many tags as you like in the Tag Title/Tag Name boxes, each separated by your chosen delimiter character (see the <em>Multi-tag delimiter</em> preference).</p>\n\n<p>Hit Save (or Create) to define the tag(s), or use the Enter key when in any input field as a shortcut for Create. The tag(s) will be created in the currently selected type (article, image, file or link) and assigned to the currently chosen Parent/Category. If you have added only one tag, it will remain ‘selected’ after creation and will also stay selected when switching between types. This allows for rapid creation of similar-named tags or for very quickly ‘copying’ tags from one type to another.</p>\n\n<p>You can edit any tag by clicking on it in the list to pull it into the edit line at the top. Make any changes and hit Save to overwrite the existing tag, or hit Create to make a new one. Use the ‘x’ button to delete the selected tag; note there is no confirmation step, since the act of clicking the tag is the confirmation itself! When you switch between types, if the currently edited tag exists in another type, it will be selected automatically upon switching.</p>\n\n<p>If the option to display counts is selected the number of articles / images / files / links will be shown in brackets alongside each tag. If the value is greater than 0 the number can be clicked to view the list of items containing that tag.</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>If you Edit a tag and try to Save it when it has the same name as an existing tag, you will receive a warning message.</li>\n	<li>If you try to delete a tag that is in use and the preference ‘Allow deletion of used tags’ is set to ‘no’, it will be forbidden.</li>\n	<li>The parent list is populated via <span class=\"caps\">AJAX</span> from the database each time you click a tag, and the Linked cat list is populated each time you change type. This is unfortunately unavoidable. The lists will fade out while the request is taking place. Occasionally it may get “stuck” if you click too quickly between tags or the server is slow to respond. In these circumstances, refreshing the page by clicking the <em>smd_tags</em> tab will restore things.</li>\n	<li>When using the auto-tag feature, if you want to change the Name field, do it <em>after</em> you have finished typing in the Title field; any subsequent changes to the Title field will overwrite anything in the Name field.</li>\n	<li>Deleting a tag will remove all references to it, so be careful.</li>\n</ul>\n\n<h3>Live tag search</h3>\n\n<p>You can use the <em>Tag search</em> box to filter your tag list in real-time. This facilitates easy searching when your tag list becomes large. Simply start typing in the text box and your tags will be filtered as you type. An indicator to the right shows how many tags match your current search criteria. You can choose that your text be matched against the tag&#8217;s name, its title, its parent tag, linked category (if you have allowed tags to be linked), or all of the above.</p>\n\n<p>When you flick between tag types your search criteria remain in force. Hit the <em><span class=\"caps\">ESC</span>ape</em> key to clear the text box and return your tag list to its unfiltered state. You may hide/display the filter box by clicking its heading to toggle its visibility (the current state is remembered for you).</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>Searches are case insensitive.</li>\n	<li>Multi-word searches find tags with <em>any</em> of the matching words. This allows you to build up complex searches using many words and apply actions to them all.</li>\n	<li>Linked categories and tag names/parents are escaped so they will not contain any non-<span class=\"caps\">ASCII</span> or special characters. For example, if your tag title is <em>Paul O&#8217;Grady</em> you can search its title for <em>O&#8217;Gr</em> and it will locate the tag. If, however, you wanted to find all tags that have <em>Paul O&#8217;Grady</em> as their parent tag you&#8217;d have to type <em>ogra</em> to have it filter by this parameter.</li>\n</ul>\n\n<h3>Performing actions on filtered tags</h3>\n\n<p>While you are filtering the tags, you can perform a set of actions on all the visible tags. Simply enter some search criteria and, when you are happy with the presented list of tags, select one of the actions from the <em>With filtered</em> dropdown.  You can multi-delete, assign a parent tag, or (if permitted) assign a linked category.</p>\n\n<p>Choosing to <em>Assign parent</em> or <em>Link to category</em> presents a further dropdown. Select the parent tag/category to which you wish to assign the filtered tags. When you select <em>Go</em> and confirm you are sure, the action will be applied to all selected tags.</p>\n\n<p>Things to note:</p>\n\n<ul>\n	<li>If you try to assign a parent to a tag that is itself, it will be safely ignored.</li>\n	<li>If the <em>Automatically show reports</em> preference is on, the result of the action will be popped up immediately upon completion.</li>\n	<li>If you leave the search box empty, any multi-action you choose will be applied to <span class=\"caps\">ALL</span> tags.</li>\n	<li>You can view the most recent report at any time by clicking the <em>Display recent report</em> link in the <em>Tag search</em> box, but once you have navigated away from the Manage Tags panel, or performed another action such as creating a tag, the report is refreshed/lost.</li>\n</ul>\n\n<h2>Importing tags en-masse</h2>\n\n<p>Back up your database first!</p>\n\n<p>If you have used tru_tags and/or rss_unlimited_categories you can import the information from either system and create smd_tags. You may also import tags from Textpattern&#8217;s own category tree or from (delimited) custom fields. View the Import panel and set the following options to configure how the tags are imported:</p>\n\n<h3>Source options</h3>\n\n<ul>\n	<li><strong>Import from</strong>: Choose from where you wish to import. To see the plugin options, that particular plugin needs to be installed and activated for it to appear.</li>\n	<li><strong>Custom field</strong>: If you have elected to import from custom field, choose which one contains your tag list. If you want to import from more than one, import them one after another.</li>\n	<li><strong>Custom field delimiter</strong>: The character sequence that delimits each tag in your custom field. Default: comma.</li>\n	<li><strong>Articles from section</strong>: Only import tags from the articles in the selected section. If not chosen, it uses all articles in your site.</li>\n	<li><strong>Start from parent category</strong>: If you are importing category names from rss_unlimited_categories, you can choose to only import them from this parent and below.</li>\n	<li><strong>Delete original</strong>: Empty the Keywords field (tru_tags), custom field, or remove any rss_unlimited_categories linked to the articles you are importing from. For safety, you should do this in two steps; import the tags and leave the originals intact, then import again with <em>Delete originals</em> set to ‘yes’ once you are happy with the results. Note that Textpattern category1 and category2 assignments are <em>not</em> deleted.</li>\n</ul>\n\n<h3>Import options</h3>\n\n<ul>\n	<li><strong>Link to category</strong>: if you permit tags to be linked to category, this dropdown is available. All imported keywords/categories will be linked to this Txp category.</li>\n	<li><strong>Force category link if tag already exists</strong>: if checked, any tags that have already been imported will have their category re-assigned to the chosen category. If not checked, any tag that already exists will have its category left intact.</li>\n	<li><strong>Assign to parent tag</strong>: Link all imported keywords/categories beneath this smd_tag. If you are linking tags to categories then the available parent tags will be influenced by the setting of the category chosen above.</li>\n	<li><strong>Force parent if tag already exists</strong>: if checked, any tags that have already been imported will have their parent re-assigned to the chosen smd_tag. If not checked, any tag that already exists will have its tag hierarchy left intact.</li>\n</ul>\n\n<p>Once you have chosen the relevant options, hit <em>Go</em>. The plugin will do your bidding and:</p>\n\n<ol>\n	<li>Copy the information from the Keywords (tru_tags) / Categories (rss_uc), Textpattern article category, or custom field and create smd_tags for each item. If the tag already exists it will be ignored unless you override it with one of the ‘force’ options.</li>\n	<li>Assign the created tags to the relevant parent, either for tags that do not exist or for all tags if the ‘force’ option is chosen.</li>\n	<li>Link the created tags to the relevant category, either for tags that do not exist or for all tags if the ‘force’ option is chosen.</li>\n	<li>Copy the assigned tags in each article from the Keywords/Categories so the same tags are assigned.</li>\n	<li>(optionally) Delete the original custom field/Keywords/Unlimited categories (not from Textpattern category1/2).</li>\n	<li>Remember your options so you can quickly make alterations and import tags in batches if you wish.</li>\n</ol>\n\n<p>As the plugin goes through your articles it will update the counts below the import options: the number of articles done/out of total, and the number of tags that have been imported and linked to your articles. Once it has completed the task, a report is available detailing the actions the plugin took. It will either be displayed automatically (depending on your pref setting) or you can view it by clicking the <em>Display recent report</em> link.</p>\n\n<h2>Editing articles / images / files / links</h2>\n\n<p>On each of the four pages that have checkboxes set in the smd_tags preferences, your chosen input mechanism will appear beneath the category assignment. This allows you to tag a particular article, image, file or link with any number of items. Once you have selected your tags, the usual Save button on the screen will commit the changes. Note that if you have chosen to link tags with categories and you have not chosen any category(ies) &#8212; or have chosen categories that are not linked to tags &#8212;  you will see no tags in the list modes! Further, in the Textarea mode you will not be able to save any tags unless you choose a valid category first. See below for more.</p>\n\n<p>The <code>[Edit]</code> link takes you to the Tag Management page that allows you to create new tags. If using a select list, two other buttons are available: <code>[Clr]</code> deselects all tags and <code>[Tog]</code> toggles all tags so if they were ‘on’ they become ‘off’, and vice versa.</p>\n\n<p>Notes on the Text area entry mechanisms:</p>\n\n<ul>\n	<li>Tags are case sensitive. Auto-complete is strongly recommended.</li>\n	<li>New tags can <em>only</em> be added if the <code>Text area+</code> input method is chosen and Quick tag is <strong>not</strong> strict.</li>\n	<li>New tags are normally assigned at the root level; if you wish to sub-tag them at this point, write the tag like this: <code>parent_tag--&gt;new_tag</code>. Anything to the left of the hyphen-hyphen-arrow is first checked to see if it exists as a tag and, if it does, the new_tag is made a sub-tag of it. If the parent_tag doesn&#8217;t exist, new_tag is assigned to root.</li>\n	<li>New tags become Tag Titles. Corresponding tag names are subject to the usual lower case/dumbing down.</li>\n	<li>If <code>Link tags to categories</code> is set, only valid tags assigned to the chosen categories will be saved.</li>\n	<li>If you alter category your tag list is rebuilt to only show / allow the ones in the selected category(ies). If you have already selected some tags, this will result in the ones you have selected since last save being removed/unhighlighted. So choose your categories first and then tag away.</li>\n</ul>\n\n<h2>Tag: <code>&lt;txp:smd_tag_list&gt;</code></h2>\n\n<p>Display a list of tags matching certain criteria, from the current context (<span class=\"caps\">URL</span>, article, file, link, or image) or a fixed context supplied as attributes. Use the following attributes to configure the tag:</p>\n\n<dl>\n	<dt><strong>type</strong></dt>\n	<dd>Where to look for the list of tags. If omitted the best match is used based on where the tag is used. For example, if it is inside the <code>plainlinks</code> form, the type would default to <code>link</code>. If a best match cannot be found, it uses <code>article</code>. Options are:\n	<dl>\n		<dd><code>article</code></dd>\n		<dd><code>image</code></dd>\n		<dd><code>file</code></dd>\n		<dd><code>link</code></dd>\n	</dl></dd>\n	<dd>Default: best match.</dd>\n	<dt><strong>flavour</strong></dt>\n	<dd>The type of tag list to display. Choose from:\n	<dl>\n		<dd><code>list</code> : a standard list in parent-&gt;child (tree) order</dd>\n		<dd><code>cloud</code> : a weighted tag cloud</dd>\n		<dd><code>crumb</code> : a list of tags in the order they are presented in the <span class=\"caps\">URL</span></dd>\n		<dd><code>head</code> : the first tag listed in the <span class=\"caps\">URL</span></dd>\n		<dd><code>tail</code> : the last tag listed in the <span class=\"caps\">URL</span></dd>\n	</dl></dd>\n	<dd>When using <code>cloud</code>, some weighting information is calculated for each tag at the expense of a little extra processing overhead. You can also, optionally, specify the scale by adding <code>:min:max</code>. For example: <code>flavour=&quot;cloud:75:300&quot;</code> would make the minimum weight 75 and the maximum 300. Default is <code>:100:150</code>.</dd>\n	<dd>Default: <code>list</code></dd>\n	<dt><strong>id</strong></dt>\n	<dd>List of tag IDs to show. If omitted and you were viewing an article it will default to the tags from the current article.</dd>\n	<dt><strong>name</strong></dt>\n	<dd>Fixed list of tag names to show. If used, they trump the <code>id</code>.</dd>\n	<dt><strong>exclude</strong></dt>\n	<dd>List of tag names you wish to omit from the list.</dd>\n	<dt><strong>parent</strong></dt>\n	<dd>Start the list from this parent tag.</dd>\n	<dd>Default: unset (i.e. the ‘root’ node of this particular type)</dd>\n	<dt><strong>sublevel</strong></dt>\n	<dd>Only show tags matching this number level. You can either specify:\n	<dl>\n		<dd><code>all</code> which shows all tags from all levels</dd>\n		<dd><em>any number</em> to match only tags from that level.</dd>\n	</dl></dd>\n	<dd>You may also prefix the number with a comparison operator (e.g. <code>sublevel=&quot;&gt;=1&quot;</code>) which would only show tags from level 1 and its descendents. Valid operators are:\n	<dl>\n		<dd><code>&gt;</code></dd>\n		<dd><code>&lt;</code></dd>\n		<dd><code>&gt;=</code></dd>\n		<dd><code>&lt;=</code></dd>\n	</dl></dd>\n	<dd>If anything else is used, equality is assumed.</dd>\n	<dd>Note that if you specify a parent, and that parent is in the current tag list, its level is 0 and the children beneath it start at level 1. If, however, your chosen parent is not in the current tag list, <em>the children will be at level 0 and any sub-children will start from level 1</em>. To help manage this, if you leave <code>sublevel</code> empty the plugin figures out the appropriate level automatically and only shows tags below the given parent.</dd>\n	<dt><strong>showall</strong></dt>\n	<dd>Whether to show empty tags or not. Choose from:\n	<dl>\n		<dd>0: only show tags that are in use</dd>\n		<dd>1: show all tags, even empty ones. Useful for generating hierarchical tag trees</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>offset</strong></dt>\n	<dd>Skip over this number of tags before starting to display them.</dd>\n	<dt><strong>limit</strong></dt>\n	<dd>The maximum number of tags to show.</dd>\n	<dd>Default: 99999 (i.e. effectively unlimited)</dd>\n	<dt><strong>form</strong></dt>\n	<dd>Pass control to this form to display the tag list. You may also use the tag as a container. Without form or container, the tag name and its count will be output.</dd>\n	<dt><strong>indent</strong></dt>\n	<dd>Character sequence to use to indicate that a tag is a sub-tag of a parent.</dd>\n	<dd>Default: two non-breaking spaces</dd>\n	<dt><strong>section_link</strong></dt>\n	<dd>If you want the tags to be clickable so they lead to another page of tag details, this can be used to override the ‘landing page’ they will appear on.</dd>\n	<dd>Default: unset (it uses the value of the <code>Default section for tags</code> preference)</dd>\n	<dt><strong>sort</strong></dt>\n	<dd>Order the results by this field, e.g. <code>sort=&quot;name asc&quot;</code>.</dd>\n	<dt><strong>shuffle</strong></dt>\n	<dd>Randomly sort the tags.</dd>\n	<dt><strong>label</strong></dt>\n	<dd>Label the top of the list with this heading.</dd>\n	<dt><strong>labeltag</strong></dt>\n	<dd>Wrap the label with this <span class=\"caps\">HTML</span> tag (e.g. <code>labeltag=&quot;div&quot;</code>).</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>Wrap the tag list with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dd>Default: <code>ul</code></dd>\n	<dt><strong>class</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to the tag list.</dd>\n	<dd>Default: <code>smd_tag_list</code></dd>\n	<dt><strong>break</strong></dt>\n	<dd>Wrap each tag with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dd>Default: <code>li</code></dd>\n	<dt><strong>breakclass</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to each tag.</dd>\n</dl>\n\n<h2>Tag: <code>&lt;txp:smd_tag_name&gt;</code></h2>\n\n<p>Display the name of the current tag. Ususally used inside an smd_tag_list container. Can be a container to add extra formatting around the name if you wish. Use the following attributes to customise the tag:</p>\n\n<dl>\n	<dt><strong>title</strong></dt>\n	<dd>Whether to show the tag name or its title. Choose from:\n	<dl>\n		<dd>0: name</dd>\n		<dd>1: title</dd>\n	</dl></dd>\n	<dd>Default: 1</dd>\n	<dt><strong>link</strong></dt>\n	<dd>Whether to make the tag clickable to take visitors to a tag page about that particular tag. Choose from:\n	<dl>\n		<dd>0: not clickable</dd>\n		<dd>1: clickable</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>section</strong></dt>\n	<dd>The section name to link to.</dd>\n	<dd>Default: the first item in the <code>Trigger(s) for tag lists</code> preference</dd>\n	<dt><strong>cleanurls</strong></dt>\n	<dd>Whether to generate clean <span class=\"caps\">URL</span> syntax for trigger/tagtype/tag links. Choose from:\n	<dl>\n		<dd>0: messy tag mode (use this if you&#8217;re having trouble with clean mode)</dd>\n		<dd>1: clean mode</dd>\n	</dl></dd>\n	<dd>Default: 1</dd>\n	<dt><strong>parent</strong></dt>\n	<dd>Not very useful to display, but can be useful to offer tree-browsing of tags. Choose from:\n	<dl>\n		<dd>0: don&#8217;t use parent tag name</dd>\n		<dd>1: use parent tag name</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>parentlabel</strong></dt>\n	<dd>The label to use for any parent links.</dd>\n	<dd>Default: ‘Up a level’</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>Wrap the tag with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to the wraptag.</dd>\n	<dd>Default: <code>smd_tag_name</code></dd>\n	<dt><strong>style</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> inline style definition to the tag.</dd>\n	<dt><strong>pad_str</strong></dt>\n	<dd>Put this string adjacent to the label to indicate its level in the hierarchy.</dd>\n	<dt><strong>pad_pos</strong></dt>\n	<dd>Where to put the padding. Choose from:\n	<dl>\n		<dd><code>left</code></dd>\n		<dd><code>right</code></dd>\n		<dd><code>both</code></dd>\n	</dl></dd>\n	<dd>If using <code>link</code> you may add <code>:in</code> to the pad_pos value to indicate if you want the string to be included inside the anchor (by default it will be outside).</dd>\n	<dd>Default: <code>left</code></dd>\n</dl>\n\n<h2>Tag: <code>&lt;txp:smd_tag_count&gt;</code></h2>\n\n<p>Display the number of articles, files, images or links that use the current tag. Use the following attributes to customise the tag:</p>\n\n<dl>\n	<dt><strong>showempty</strong></dt>\n	<dd>Determines whether to display counts that have zero items.\n	<dl>\n		<dd>0: skip zero values</dd>\n		<dd>1: show all values, even if zero</dd>\n	</dl></dd>\n	<dd>Default: 1</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>Wrap the count with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to the count.</dd>\n	<dd>Default: <code>smd_tag_count</code></dd>\n	<dt><strong>style</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> inline style definition to the count.</dd>\n	<dt><strong>wrapcount</strong></dt>\n	<dd>Characters to wrap around the count itself. Specify up to two items, separated by a colon. If you use just one item it will appear on both sides of the count.</dd>\n	<dd>Default: <code> (:)</code> (with a space at the start)</dd>\n	<dt><strong>paramdelim</strong></dt>\n	<dd>If you don&#8217;t like the default separator for the <code>wrapcount</code> attribute, change it.</dd>\n	<dd>Default: <code>:</code> (colon)</dd>\n</dl>\n\n<p>You may also uses this tag to return the <em>total</em> number of pieces of content that match after using smd_related_tags. Any time immediately after encountering <code>&lt;txp:smd_related_tags&gt;</code>, you can use this tag to fetch the total number of articles, images, files or links that matched the given tag(s). Respects and/or matches.</p>\n\n<h3>Example: display tags/counts from the current article</h3>\n\n<p>Here&#8217;s an example, using the last three tags. In your default article form, add this to show a list of clickable tags associated with the current article:</p>\n\n<pre><code>Filed in: &lt;txp:smd_tag_list wraptag=&quot;&quot; break=&quot; | &quot;\n     shuffle=&quot;1&quot; indent=&quot;&quot;&gt;\n  &lt;txp:smd_tag_name link=&quot;1&quot; /&gt;\n  &lt;txp:smd_tag_count wrapcount=&quot;/&quot; /&gt;\n&lt;/txp:smd_tag_list&gt;</code></pre>\n\n<p>That might display:</p>\n\n<pre><code>Filed in: UK /8/ | politics /1/ | media /14/ | money /9/ | banks /3/</code></pre>\n\n<h2>Tag: <code>&lt;txp:smd_tag_info&gt;</code></h2>\n\n<p>Display any other pieces of information about a tag. Use the following attributes to customise the tag:</p>\n\n<dl>\n	<dt><strong>item</strong></dt>\n	<dd>List of one or more things to display. Can be any of:\n	<dl>\n		<dd><code>id</code></dd>\n		<dd><code>name</code></dd>\n		<dd><code>description</code></dd>\n		<dd><code>title</code></dd>\n		<dd><code>lettername</code> (first unicode letter of the tag&#8217;s name)</dd>\n		<dd><code>lettertitle</code> (first unicode letter of the tag&#8217;s title)</dd>\n		<dd><code>type</code> (article, image, file, link)</dd>\n		<dd><code>parent</code> (the parent tag)</dd>\n		<dd><code>children</code> (number of child tags this tag has)</dd>\n		<dd><code>level</code> (level 0 = ‘root’)</dd>\n		<dd><code>count</code> (number of articles/images/files/links assigned to this tag)</dd>\n		<dd><code>weight</code> (‘size’ of this tag, weighted as a percentage of all tags based on the count: useful inside <code>style</code> attribute of smd_tag_name for creating weighted tag clouds)</dd>\n	</dl></dd>\n	<dd>Default: <code>name</code></dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>Wrap the tag items with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dt><strong>class</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to the tag items.</dd>\n	<dd>Default: <code>smd_tag_info</code></dd>\n	<dt><strong>break</strong></dt>\n	<dd>Wrap each tag item with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dd>Default: <code>br</code></dd>\n	<dt><strong>breakclass</strong></dt>\n	<dd>Apply this <span class=\"caps\">CSS</span> class to each tag item.</dd>\n</dl>\n\n<h3>Example: extended tag information</h3>\n\n<p>Inside an smd_tag_list, put this to display some information about each tag:</p>\n\n<pre><code>&lt;txp:smd_tag_info item=&quot;id&quot; /&gt;:\n &lt;p&gt;parent | #children | level&lt;br /&gt;\n &lt;txp:smd_tag_info item=&quot;parent, children, level&quot;\n      break=&quot; | &quot; /&gt;&lt;/p&gt;</code></pre>\n\n<h2>Tag: <code>&lt;txp:smd_if_tag_list&gt;</code></h2>\n\n<p>Conditional tag that returns true if the current scope is a tag list. Useful for detecting if a tag has been clicked and redirected to a landing page so you can take some action on that page.</p>\n\n<h2>Tag: <code>&lt;txp:smd_if_tag&gt;</code></h2>\n\n<p>Rudimentary conditional to check a tag for certain parameters and execute the contained content if it matches. Every attribute you specify must match for the conditional content to be executed, otherwise any <code>&lt;txp:else /&gt;</code> branch will be followed. Use the following attributes to customise it:</p>\n\n<dl>\n	<dt><strong>type</strong></dt>\n	<dd>Executes the contained statements if the type matches the one given. Takes one of:\n	<dl>\n		<dd><code>article</code></dd>\n		<dd><code>image</code></dd>\n		<dd><code>file</code></dd>\n		<dd><code>link</code></dd>\n	</dl></dd>\n	<dt><strong>id</strong></dt>\n	<dd>Checks if the tag ID matches the one given.</dd>\n	<dt><strong>name</strong></dt>\n	<dd>Checks if the tag name matches the one given.</dd>\n	<dt><strong>title</strong></dt>\n	<dd>Checks if the tag title matches the one given.</dd>\n	<dt><strong>description</strong></dt>\n	<dd>Checks if the tag description matches the one given.</dd>\n	<dt><strong>parent</strong></dt>\n	<dd>Checks if the tag parent matches the one given.</dd>\n	<dt><strong>count</strong></dt>\n	<dd>Checks if the tag count matches the number given.</dd>\n	<dt><strong>children</strong></dt>\n	<dd>Checks if the number of children the tag has matches the number given.</dd>\n	<dt><strong>level</strong></dt>\n	<dd>Checks if the tag is at a specific level in the hierarchy.</dd>\n	<dt><strong>is</strong></dt>\n	<dd>Checks if the tag is of a particular variety. Choose from:\n	<dl>\n		<dd><code>master</code> : one of the master tags</dd>\n		<dd><code>first</code> : the first tag in the list</dd>\n		<dd><code>last</code> : the last tag in the list</dd>\n	</dl></dd>\n	</dl>\n\n<p>Each of the items (except <code>is</code>) may take an exact value/string to match or a modified syntax: you may precede the value with one of the following symbols to perform a mathematical/boolean comparison:</p>\n\n<ul>\n	<li><code>&lt;</code> : less than.</li>\n	<li><code>&lt;=</code> : less than or equal to.</li>\n	<li><code>&gt;</code> : greater than.</li>\n	<li><code>&gt;=</code> : greater than or equal to.</li>\n	<li><code>!</code> : not equal to.</li>\n</ul>\n\n<p>For example:</p>\n\n<pre><code>&lt;txp:smd_if_tag parent=&quot;!root&quot; count=&quot;&gt;8&quot;&gt;\n// Current tag is not a top-level tag and has\n// more than 8 items associated with it\n&lt;/txp:smd_if_tag&gt;</code></pre>\n\n<p>For more fine-grained control or more detailed conditional branching, consider the smd_if plugin.</p>\n\n<h2>Tag: <code>&lt;txp:smd_related_tags&gt;</code></h2>\n\n<p>The big one! This tag allows you to find and display any content that matches some facet of the current content. It defaults to matching tag names. For example, if the tag appeared on its own on an article page, it would find all ‘related’ articles that contained the same tags as the ones in the current article.</p>\n\n<p>But it is not restricted to searching within its own type. You could find all the matching images or links that shared a tag with the current article. Or if showing a list of links, display all articles that share tags with the current link.</p>\n\n<p>Finally, you can also match other stuff such as image, file, link or article categories, custom fields, and so on.</p>\n\n<p>Use the following attributes to customise the tag:</p>\n\n<dl>\n	<dt><strong>type</strong></dt>\n	<dd>The type of information you want to <em>find</em>. Can be one of:\n	<dl>\n		<dd><code>article</code></dd>\n		<dd><code>image</code></dd>\n		<dd><code>file</code></dd>\n		<dd><code>link</code></dd>\n	</dl></dd>\n	<dd>Default: the same type as the current context (i.e. if in an article, will look for other articles)</dd>\n	<dt><strong>match</strong></dt>\n	<dd>The information you want to <em>compare</em>. Can be one of:\n	<dl>\n		<dd><code>tag_name</code></dd>\n		<dd><code>tag_id</code></dd>\n		<dd><code>tag_title</code></dd>\n		<dd><code>tag_description</code></dd>\n		<dd><code>tag_parent</code></dd>\n		<dd><code>tag_count</code></dd>\n		<dd><code>tag_children</code></dd>\n		<dd><code>tag_level</code></dd>\n		<dd>some field name such as <code>category</code> or <code>custom1</code></dd>\n	</dl></dd>\n	<dd>Default: <code>tag_name</code></dd>\n	<dt><strong>match_self</strong></dt>\n	<dd>Whether to include a reference to the current item. Usually you won&#8217;t want this so it is off and in fact, setting it on can sometimes cause errors. Options:\n	<dl>\n		<dd>0: off</dd>\n		<dd>1: on</dd>\n	</dl></dd>\n	<dd>Default: 0</dd>\n	<dt><strong>form</strong></dt>\n	<dd>Pass control to the given form for rendering the tag output. Can also be used as a container. If you don&#8217;t specify either a <code>form</code> or a container, it outputs some sensible default based on the current context (i.e. the permlinked article title in the <code>article</code> context, the file download link in the <code>file</code> context, etc).</dd>\n	<dt><strong>section</strong></dt>\n	<dd><span>Only of use for articles</span>. Limits the search to articles in a particular section.</dd>\n	<dd>Default: current section, but check the caveats below for a side-effect of this attribute</dd>\n	<dt><strong>offset</strong></dt>\n	<dd>Skip this number of matched items before starting to display them.</dd>\n	<dt><strong>limit</strong></dt>\n	<dd>The maximum number of items to display.</dd>\n	<dd>Default: 99999 (i.e. virtually unlimited)</dd>\n	<dt><strong>no_widow</strong></dt>\n	<dd>When displaying article titles, prevent lone words on the 2nd line.</dd>\n	<dd>Default: Txp Admin preference of the same name</dd>\n	<dt><strong>sort</strong></dt>\n	<dd>Reorder the items by some field.</dd>\n	<dd>Default: <code>Posted desc</code> for articles / <code>date desc</code> for everything else</dd>\n	<dt><strong>label</strong></dt>\n	<dd>Display this label at the top of the list of items.</dd>\n	<dt><strong>labeltag</strong></dt>\n	<dd>Wrap this <span class=\"caps\">HTML</span> tag around the label.</dd>\n	<dt><strong>wraptag</strong></dt>\n	<dd>Wrap this <span class=\"caps\">HTML</span> tag around the items.</dd>\n	<dt><strong>break</strong></dt>\n	<dd>Wrap each item with this <span class=\"caps\">HTML</span> tag.</dd>\n	<dd>Default: <code>br</code></dd>\n	<dt><strong>class</strong></dt>\n	<dd>Apply the given <span class=\"caps\">CSS</span> class to the item list.</dd>\n	<dd>Default: <code>smd_related_tags</code></dd>\n	<dt><strong>delim</strong></dt>\n	<dd>If you prefer something other than the default to specify lists of items in the tag attributes, change it.</dd>\n	<dd>Default: <code>,</code> (comma)</dd>\n	<dt><strong>paramdelim</strong></dt>\n	<dd>If you prefer something other than the default to separate parameters inside attributes, change it.</dd>\n	<dd>Default: <code>:</code> (colon)</dd>\n</dl>\n\n<p>When specifying items to <code>match</code> you are not restricted to searching the current context. For example, say you had a list of file downloads and next to each file you wanted to show the articles that matched the file&#8217;s category. You might do this in your <code>files</code> form:</p>\n\n<pre><code>&lt;txp:file_download_link&gt;\n  &lt;txp:file_download_name /&gt;\n&lt;/txp:file_download_link&gt;\n&lt;txp:smd_related_tags type=&quot;article&quot;\n     label=&quot;You might also like&quot; labeltag=&quot;p&quot;\n     match=&quot;file:category&quot; wraptag=&quot;ul&quot;\n     break=&quot;li&quot; /&gt;</code></pre>\n\n<h3>Caveat</h3>\n\n<p>Be careful with the <code>section</code> attribute: on a tag list, <em>all</em> items of a given type are considered, irrespective of section. Thus if you did:</p>\n\n<pre><code>&lt;txp:smd_if_tag_list&gt;\n  &lt;h2&gt;Tags list&lt;/h2&gt;\n  &lt;txp:smd_tag_list&gt;\n    &lt;txp:smd_tag_name title=&quot;1&quot; link=&quot;1&quot; /&gt;\n    &lt;txp:smd_tag_count /&gt;\n    &lt;txp:smd_related_tags section=&quot;archive&quot; wraptag=&quot;ul&quot; break=&quot;li&quot; /&gt;\n  &lt;/txp:smd_tag_list&gt;\n&lt;/txp:smd_if_tag_list&gt;</code></pre>\n\n<p>You might see a tag name with a count of 12 but only 7 articles listed under it, if five of your articles that used that particular tag were from another section. To be sure, only use the <code>section</code> attribute on individual article pages.</p>\n\n<h2>Example 1: linked tag tree</h2>\n\n<pre><code>&lt;txp:smd_tag_list showall=&quot;1&quot; parent=&quot;translations&quot;&gt;\n   &lt;txp:smd_tag_info item=&quot;indent&quot; /&gt;\n   &lt;txp:smd_if_tag children=&quot;&gt;0&quot;&gt;\n      &lt;txp:hide&gt; A parent tag: do not link &lt;/txp:hide&gt;\n      &lt;txp:smd_tag_name title=&quot;1&quot; /&gt;\n   &lt;txp:else /&gt;\n      &lt;txp:hide&gt; A child tag: link it to the default trigger section &lt;/txp:hide&gt;\n      &lt;txp:smd_tag_name link=&quot;1&quot; title=&quot;1&quot; /&gt;\n   &lt;/txp:smd_if_tag&gt;\n   [&lt;txp:smd_tag_info item=&quot;count&quot; /&gt;]\n&lt;/txp:smd_tag_list&gt;</code></pre>\n\n<h2>Example 2: font-weighted tag cloud</h2>\n\n<pre><code>&lt;txp:smd_tag_list parent=&quot;animals&quot; flavour=&quot;cloud:75:300&quot;\n      wraptag=&quot;div&quot; break=&quot;&quot;&gt;\n   &lt;txp:variable name=&quot;fontweight&quot;&gt;\n      font-size:&lt;txp:smd_tag_info item=&quot;weight&quot; /&gt;%;\n   &lt;/txp:variable&gt;\n   &lt;txp:smd_tag_info item=&quot;indent&quot; /&gt;\n   &lt;txp:smd_tag_name link=&quot;1&quot; title=&quot;1&quot; wraptag=&quot;span&quot;\n           style=\'&lt;txp:variable name=&quot;fontweight&quot; /&gt;\' /&gt;\n   (&lt;txp:smd_tag_info item=&quot;count&quot; /&gt;)\n&lt;/txp:smd_tag_list&gt;</code></pre>\n\n<h2>Example 3: weighted alphabetic tag listing</h2>\n\n<pre><code>&lt;txp:smd_tag_list showall=&quot;1&quot; sort=&quot;name asc&quot;\n     break=&quot;&quot; wraptag=&quot;div&quot; flavour=&quot;cloud&quot;&gt;\n   &lt;txp:if_different&gt;\n      &lt;h2 class=&quot;alphachar&quot;&gt;&lt;txp:smd_tag_info item=&quot;lettername&quot; /&gt;&lt;/h2&gt;\n   &lt;/txp:if_different&gt;\n   &lt;span class=&quot;atag&quot;&gt;\n      &lt;txp:smd_tag_name link=&quot;1&quot; wraptag=&quot;span&quot;\n           style=\'font-size:&lt;txp:smd_tag_info item=&quot;weight&quot; /&gt;%;\' /&gt;\n      &lt;txp:smd_tag_count /&gt;\n   &lt;/span&gt;\n&lt;/txp:smd_tag_list&gt;</code></pre>\n\n<p>Just take out the <code>flavour</code> and <code>style</code> attributes to render a regular alphabetic tag listing. Note that you could use <code>lettertitle</code> but you would get both lower- and upper-case ‘d’ sections if you had tags ‘Dastardly’ and ‘deviant’.</p>\n\n<h2>Known issues</h2>\n\n<p>Issue: Under Txp 4.6.x, the ‘More&#8230;’ link and the preference panel groups don&#8217;t open/close. A JS error is thrown.<br />\nCause: Bug in Textpattern&#8217;s <code>textpattern.js</code> file.<br />\nFix: Edit the line that reads:</p>\n\n<pre><code>if (selectedTab === undefined) {</code></pre>\n\n<p>and change it to:</p>\n\n<pre><code>if (typeof selectedTab === \'undefined\') {</code></pre>\n\n<h2>Author</h2>\n\n<p><a href=\"https://stefdawson.com/contact\" rel=\"nofollow\">Stef Dawson</a></p>', '//<?php\n/**\n * smd_tags: A Textpattern CMS plugin for unlimited, structured taxonomy across content types.\n *\n * Features:\n *   Article, image, file and link tags\n *   Rapid tag creation / editing / multi-editing\n *   Optionally import tags from rss_uc, tru_tags, category1/2 or custom fields\n *   Create tag lists, trees and clouds\n *   Find related content based on tag\n *   Tag combinators from the URL\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n *\n * @todo\n * Remove EvalElse()\n * Alter tag pool layout to allow collapsing groups of parent->children\n * Use article_row_info(), link_format_info()\n * Investigate storing tag data as jQuery .data() instead of in <span>s (from Txp 4.6+ it can be done in HTML data-* attributes)\n * Pagination of tag lists / related content\n * Feed handler prefs to inject tags into RSS+Atom (like tru_tags: code at bottom of this plugin)\n * Add option to display tag lists AND TextArea(+) on list pages\n * See if rapid tagging can be made more intuitive wrt Create/Save to avoid clobbering already created tags (from pieman http://forum.textpattern.com/viewtopic.php?pid=240099#p240099)\n * When refreshing the Manage tab, FF remembers the Title and tries to add it on page load, throwing a msgarea warning about the tag already exists (or that name is empty)\n * Possibility of section tags? (from jakob)\n * Per-section / per-category tag counts\n * Query tags without section/article context (Dwayne: http://forum.textpattern.com/viewtopic.php?pid=246750#p246750)\n * On list pages:\n *   AJAX fetch of the tag set for TextArea and TextArea+ modes to improve page load time (plus we then only grab the ones in the current category hierarchy)\n *   Add autocomplete options to smd_tags prefs so autofill etc can be configured (from aswihart)\n *   Check what happens when deleting content (e.g. images): it sometimes triggers \" \'\' is not an Integer \" assertion (from curiouz http://forum.textpattern.com/viewtopic.php?pid=236023#p236023)\n *   Perhaps offer sort option for List mode to a) retain hierarchy, b) strict alphabetical, c) group them somehow to show how sets of tags are related (from pieman)\n * @tofix\n * Sanitize tags from custom fields / cats during import\n * Blob of output from AJAX (cf. \"If you spot such JavaScript responses your plugin breaks AJAX at the write tab, for instance by echo()ing something as text/html when send_script_response() with text/javascript would be the proper reply with the expected mime type.\")\n * Query count -- cache tag sets and verify URL tag lists are as optimum as possible (see smd_bio)\n * Live search sometimes gives wrong results when group tags contain no (or very few) children\n * Verify limit/offset behaviour (giz: http://forum.textpattern.com/viewtopic.php?pid=244020#p244020)\n * smd_tags_context() : Invalid argument supplied for foreach() (frickinmuck: http://forum.textpattern.com/viewtopic.php?pid=249250#p249250)\n * Deal with parent attribute in smd_tag_name better (stop it showing \'root\'?)\n * Detect scenario when article saves fail and don\'t update tags under these circumstances (e.g. when concurrent users are editing the same document)\n */\n\n// ------------------------\n// ADMIN SIDE CODE\nif (txpinterface === \'admin\') {\n    add_privs(\'smd_tags\',\'1,2\');\n    add_privs(\'smd_tags_users\',\'1,2,3,4,5,6\');\n    add_privs(\'plugin_prefs.smd_tags\',\'1,2\');\n\n    // Extensions tab\n    register_tab(\'extensions\', \'smd_tags\', gTxt(\'smd_tags\'));\n    register_callback(\'smd_tags\', \'smd_tags\');\n    register_callback(\'smd_tags_users\', \'smd_tags_users\');\n\n    // Note the callbacks are ordered with \'save\' first\n    register_callback(\'smd_tags_multi_edit\', \'list\', \'list_multi_edit\');\n    register_callback(\'smd_tags_savelist\', \'article\', \'create\');\n    register_callback(\'smd_tags_savelist\', \'article\', \'edit\');\n    register_callback(\'smd_tags_loadlist\', \'article\');\n\n    register_callback(\'smd_tags_multi_edit\', \'image\', \'image_delete\');\n    register_callback(\'smd_tags_savelist\', \'image\', \'image_save\');\n    register_callback(\'smd_tags_loadlist\', \'image\');\n\n    register_callback(\'smd_tags_multi_edit\', \'file\', \'file_delete\');\n    register_callback(\'smd_tags_savelist\', \'file\', \'file_save\');\n    register_callback(\'smd_tags_loadlist\', \'file\');\n\n    register_callback(\'smd_tags_multi_edit\', \'link\', \'link_multi_edit\');\n    register_callback(\'smd_tags_savelist\', \'link\', \'link_post\');\n    register_callback(\'smd_tags_savelist\', \'link\', \'link_save\');\n    register_callback(\'smd_tags_loadlist\', \'link\');\n\n    register_callback(\'smd_tags_setup\', \'plugin_prefs.smd_tags\');\n    register_callback(\'smd_tags_welcome\', \'plugin_lifecycle.smd_tags\');\n    register_callback(\'smd_tags_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_tag_list\')\n            ->register(\'smd_tag_name\')\n            ->register(\'smd_tag_count\')\n            ->register(\'smd_tag_info\')\n            ->register(\'smd_if_tag\')\n            ->register(\'smd_if_tag_list\')\n            ->register(\'smd_related_tags\');\n    }\n\n    register_callback(\'smd_tags_url_handler\', \'pretext\');\n}\n\nif (!defined(\'SMD_TAG\')) {\n    define(\"SMD_TAG\", \'smd_tags\');\n}\nif (!defined(\'SMD_TAGC\')) {\n    define(\"SMD_TAGC\", \'smd_tags_cat\');\n}\nif (!defined(\'SMD_TAGU\')) {\n    define(\"SMD_TAGU\", \'smd_tags_used\');\n}\n\n/**\n * Runs on plugin installation.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_tags_table_install(0);\n            $msg = \'Happy tagging!\';\n            break;\n        case \'deleted\':\n            smd_tags_prefs_remove(\'\', 0);\n            break;\n    }\n    return $msg;\n}\n\n/**\n * Step dispatcher.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_tag_catlist\'           => true,\n        \'smd_tag_create\'            => true,\n        \'smd_tag_parentlist\'        => true,\n        \'smd_tag_save\'              => true,\n        \'smd_tags_delete\'           => true,\n        \'smd_tags_import\'           => true,\n        \'smd_tags_import_one\'       => true,\n        \'smd_tags_manage\'           => false,\n        \'smd_tags_multi_set_parent\' => true,\n        \'smd_tags_multi_catlink\'    => true,\n        \'smd_tags_prefs_show\'       => false,\n        \'smd_tags_prefs_install\'    => false,\n        \'smd_tags_prefs_remove\'     => false,\n        \'smd_tags_prefs_update\'     => true,\n        \'smd_tags_sync\'             => false,\n        \'smd_tags_table_install\'    => false,\n        \'smd_tags_table_remove\'     => false,\n        \'smd_tags_table_rebuild\'    => false,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $pref = smd_tags_pref_get(\'smd_tag_t_astart\');\n        $stp = \'smd_tags_prefs_show\';\n\n        if ($pref) {\n            switch ($pref[\'smd_tag_t_astart\'][\'val\']) {\n                case 1:\n                    $stp = \'smd_tags_manage\';\n                    break;\n                case 0:\n                default:\n                    $stp = \'smd_tags_prefs_show\';\n            }\n        }\n    }\n\n    $stp();\n}\n\n/**\n * Ajax step dispatcher.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags_users($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_tag_catlist\'    => true,\n        \'smd_tag_parentlist\' => true,\n        \'smd_tag_get_desc\'   => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        // Do nothing\n    } else {\n        $stp();\n    }\n}\n\n// ------------------------\n// Create dropdown and populate it with tags of the relevant type, highlighting any $sel tags\nfunction smd_multiTreeSelectInput($select_name = \'\', $tree = \'\', $sel = \'\') {\n    $pref = smd_tags_pref_get(array(\'smd_tag_p_size\', \'smd_tag_p_listpar\'), 1);\n    $rows = $pref[\'smd_tag_p_size\'][\'val\'];\n    $incl = $pref[\'smd_tag_p_listpar\'][\'val\'];\n\n    $out[] = \'<select id=\"\'.$select_name.\'\" name=\"\'.$select_name.\'[]\" class=\"list\" size=\"\'. $rows .\'\"\' .(($rows==1) ? \'\' : \' multiple=\"multiple\"\'). \'>\';\n    foreach ($tree as $leaf) {\n        if ($leaf[\'name\'] == \'root\') continue;\n\n        $selected=\'\';\n        foreach ($sel as $gid => $item) {\n            if ($leaf[\'id\'] == $item[\'tag_id\']) {\n                $selected = \' selected=\"selected\"\';\n                continue;\n            }\n        }\n\n        $leaf[\'name\'] = htmlspecialchars($leaf[\'name\']);\n        $indent = ($leaf[\'level\'] > 0) ? str_repeat(sp.sp,$leaf[\'level\']) : \'\';\n        $selectable = (!$incl && $leaf[\'children\'] > 0) ? \' disabled=\"disabled\"\' : \'\';\n        $out[] = t.\'<option value=\"\'.$leaf[\'id\'].\'\"\'.$selected.$selectable.\'>\'.$indent.htmlspecialchars($leaf[\'title\']).\'</option>\';\n    }\n    $out[] = \'</select>\';\n    return join(\'\',$out);\n}\n\n// ------------------------\n// Called whenever one of the 4 main tabs are used on the admin side. Inserts the input method if required\nfunction smd_tags_loadlist($evt, $stp) {\n    global $app_mode, $step;\n\n    if (smd_tags_table_exist()) {\n        $ctrls = smd_tags_pref_get(array(\'smd_tag_p_enable\', \'smd_tag_p_input\', \'smd_tag_p_size\', \'smd_tag_p_qtag\', \'smd_tag_p_qtpath\', \'smd_tag_p_qtstyl\', \'smd_tag_p_linkcat\', \'smd_tag_t_desc_tooltip\'), 1);\n        $onoff = smd_tags_pref_explode($ctrls[\'smd_tag_p_enable\'][\'val\']);\n        $iptyp = $ctrls[\'smd_tag_p_input\'][\'val\'];\n        $selsz = $ctrls[\'smd_tag_p_size\'][\'val\'];\n        $quick = $ctrls[\'smd_tag_p_qtag\'][\'val\'];\n        $jsdir = $ctrls[\'smd_tag_p_qtpath\'][\'val\'];\n        $csdir = $ctrls[\'smd_tag_p_qtstyl\'][\'val\'];\n        $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n        $toolt = $ctrls[\'smd_tag_t_desc_tooltip\'][\'val\'];\n        $jstail = ($jsdir) ? \'/\' : \'\';\n        $cstail = ($csdir) ? \'/\' : \'\';\n        $addIt = false;\n\n        $itemID = smd_getID();\n        $edtBtn = has_privs(\'smd_tags\') ? \' [\'.eLink(\'smd_tags\', \'smd_tags_manage\', \'smd_tag_type\', $evt, gTxt(\'edit\')).\'] \' : \'\';\n        $tagtop = gTxt(\'smd_tag_title\').$edtBtn;\n        $selist = \'<span id=\"smd_tags\" name=\"smd_tags_bylist\" class=\"smd_tagip\"></span>\';\n        $selhid = \'<span id=\"smd_tags\" name=\"smd_tags_bylist\" class=\"smd_hidden\"></span>\';\n        $tooltip = ($toolt) ? \'<span id=\"smd_tag_tt\" class=\"smd_tooltip smd_hidden\"></span>\' : \'\';\n        $widget = \'\';\n\n        switch ($iptyp) {\n            case 0:\n                $ipmeth = $selist;\n                break;\n            case 1:\n                $ipmeth = \'<span name=\"smd_tags_bylink\" id=\"smd_tags_bylink\" class=\"smd_tagip\"></span>\'.$selhid;\n                break;\n            case 2:\n            case 3:\n                $ipmeth = \'<textarea name=\"smd_tags_bytext\" rows=\"\'.$selsz.\'\" class=\"smd_tagip\"></textarea>\'.$selhid;\n                break;\n        }\n\n        switch ($evt) {\n            case \"article\":\n                $addIt = ($onoff[gTxt(\'tab_list\')] == 1) ? true : false;\n                if ($addIt) {\n                    extract(gpsa(array(\'view\',\'from_view\')));\n                    $view = ($view) ? $view : \"text\";\n                    $grabcats = \'jQuery(\"#category-1 option:selected, #category-2 option:selected\")\';\n                    $jsElem = \'jQuery(\".category-2\").parent()\';\n                    $trigger = \'jQuery(\"#category-1, #category-2\")\';\n                    if ($view != \'text\') {\n                        $widget = \'\';\n                    } else {\n                        $widget = escape_js(\'<div>\'.$tagtop.\'</div>\' . $ipmeth);\n                    }\n                }\n                break;\n            case \"image\":\n                if ($step == \"image_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_image\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#image_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-image-category\")\';\n                        $trigger = \'jQuery(\"#image_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n            case \"file\":\n                if ($step == \"file_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_file\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#file_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-file-category\")\';\n                        $trigger = \'jQuery(\"#file_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n            case \"link\":\n                if ($step == \"link_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_link\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#link_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-link-category\")\';\n                        $trigger = \'jQuery(\"#link_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n        }\n\n        // Don\'t re-serve the css+javascript if this is an AJAX save\n        if ($app_mode == \'async\') {\n            $addIt = false;\n        }\n\n        // Add the dropdown and js toggle/clear options if required\n        if ($addIt) {\n            $qs = array(\n                \"event\" => \"smd_tags_users\",\n            );\n\n            $qsVars = \"index.php\".join_qs($qs);\n\n            if ($quick > 0) {\n                echo <<<EOJS\n<script type=\"text/javascript\" src=\"{$jsdir}{$jstail}jquery.autocomplete.pack.js\"></script>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{$csdir}{$cstail}jquery.autocomplete.css\"></script>\nEOJS;\n            }\n\n            echo script_js(<<<EOJS\njQuery(function() {\n    {$jsElem}.after(\'{$widget}{$tooltip}\');\n\n    function smd_tagList(typ) {\n        var grabcats = [];\n        {$grabcats}.each(function() {\n            if (jQuery(this).val() != \'\') {\n               grabcats.push(jQuery(this).val());\n            }\n        });\n        grabcats = grabcats.join(\",\");\n        var smd_link_mode = ((\'{$clink}\'==\'0\') ? \'2\' : ((grabcats==\'\') ? 1 : \'\'));\n\n        jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: \'smd_tag_parent\', type: typ, cat: grabcats, link_mode: smd_link_mode, itemid: \"{$itemID}\", _txp_token: textpattern._txp_token },\n            function(data) {\n                jQuery(\"#smd_tags\").html(data);\n                jQuery(\"#smd_tags_bylink\").empty();\n                var smd_tagpool = [];\n                var smd_tagsel = [];\n                var smd_taglinks = [];\n                jQuery(\'#smd_tags select option\').each(function() {\n                    var curr = jQuery(this);\n                    var txt = jQuery.trim(curr.text());\n                    smd_tagpool.push(txt);\n\n                    if (jQuery(\"#smd_tags_bylink\").text() != \"\") {\n                        jQuery(\"#smd_tags_bylink\").append(\" &middot; \");\n                    }\n\n                    if (curr.prop(\"selected\") == true) {\n                        smd_tagsel.push(txt);\n                        jQuery(\"#smd_tags_bylink\").append(\'<span class=\"smd_sel\">\'+txt+\'</span>\');\n                    } else {\n                        jQuery(\"#smd_tags_bylink\").append(\'<span>\'+txt+\'</span>\');\n                    }\n                });\n\n                jQuery(\"#smd_tags_bylink span\").click(function() {\n                    jQuery(this).toggleClass(\"smd_sel\");\n                    var ltxt = jQuery(this).text();\n                    jQuery(\"#smd_tags select option\").each(function() {\n                        var curr = jQuery(this);\n                        if (jQuery.trim(curr.text()) == ltxt) {\n                            this.selected = !this.selected;\n                        }\n                    });\n                });\n\n                jQuery(\"textarea[name=\'smd_tags_bytext\']\").val(smd_tagsel.join(\", \"));\n\n                if ({$quick} > 0) {\n                    jQuery(\"textarea[name=\'smd_tags_bytext\']\").autocomplete(smd_tagpool, {\n                        multiple: true,\n                        mustMatch: (($quick==2)?true:false),\n                        autoFill: true\n                    });\n                }\n                jQuery(\"textarea[name=\'smd_tags_bytext\']\").keyup(function() {\n                    smd_tagsResyncSelect({$quick});\n                });\n                jQuery(\'#smd_clr\').click(function() {\n                    jQuery(\'#smd_tags select option\').prop(\"selected\", \"\");\n                });\n                jQuery(\'#smd_tog\').click(function() {\n                    jQuery(\'#smd_tags select option\').each(function() {\n                        this.selected = !this.selected;\n                    });\n                });\n\n                // Tooltips\n                if (\'{$toolt}\' == \'1\') {\n                    jQuery(\'#smd_tags select, #smd_tags_bylink\').hover( function(ev) {\n                        // Do nothing\n                }, function() {\n                        // Hide tooltip when exiting the entire tag area\n                        jQuery(\'.smd_tooltip\').hide(\'fast\');\n                    });\n                    jQuery(\'#smd_tags select option, #smd_tags_bylink span\').hover( function(ev) {\n                        var thisTag = jQuery(this).val();\n                        if (!thisTag) {\n                            thisTag = jQuery(this).html();\n                        }\n                        if (jQuery(\'#smd_tag_tt\').data(thisTag)) {\n                            smd_tags_show_desc(ev, jQuery(\'#smd_tag_tt\').data(thisTag));\n                        } else {\n                            jQuery.post(\'{$qsVars}\',\n                            {\n                                step: \"smd_tag_get_desc\",\n                                tag_ref: thisTag,\n                                _txp_token: textpattern._txp_token\n                            },\n                            function(data) {\n                                jQuery(\'#smd_tag_tt\').data(thisTag, data);\n                                smd_tags_show_desc(ev, jQuery(\'#smd_tag_tt\').data(thisTag));\n                            });\n                        }\n                    });\n                }\n            }\n        );\n    }\n\n    function smd_tags_show_desc(ev, content) {\n        jQuery(\'#smd_tag_tt\').hide().empty().append(content).show();\n        jQuery(\'.smd_tooltip\').css(\'left\', ev.pageX - 60).css(\'top\', ev.pageY + 15).css(\'display\', \'block\');\n    }\n\n    smd_tagList(\'{$evt}\');\n    if (\'{$clink}\'==\'1\') {\n        {$trigger}.change(function() {\n            smd_tagList(\'{$evt}\');\n        });\n    }\n\n    function smd_tagsResyncSelect(lvl) {\n        var tbTags = jQuery(\"textarea[name=\'smd_tags_bytext\']\").val().split(/,\\s+?/);\n        jQuery(\"#smd_tags select option\").each(function() {\n            var curr = jQuery(this);\n            if(jQuery.inArray(jQuery.trim(curr.text()), tbTags) == -1) {\n                curr.prop(\"selected\", \"\");\n            } else {\n                curr.prop(\"selected\", true);\n            }\n        });\n    }\n});\nEOJS\n            );\n        }\n    }\n}\n\n// ------------------------\n// Update the tags used table if some tags have been added/saved\nfunction smd_tags_savelist($evt, $stp) {\n    if (smd_tags_table_exist()) {\n        $ctrls = smd_tags_pref_get(array(\'smd_tag_p_qtag\', \'smd_tag_p_input\', \'smd_tag_p_linkcat\', \'smd_tag_p_master\'), 1);\n        $quick = $ctrls[\'smd_tag_p_qtag\'][\'val\'];\n        $iptyp = $ctrls[\'smd_tag_p_input\'][\'val\'];\n        $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n        $god = $ctrls[\'smd_tag_p_master\'][\'val\'];\n        $god_clause = ($god == \'\') ? \'\' : \" AND parent = \'\" . doSlash($god) . \"\'\";\n\n        $itemID = smd_getID();\n\n        if ($itemID) {\n            // Can\'t use the \'edit\' step because otherwise save triggers on article page load;\n            // hence, the \'save\' button state is used instead. Also, on article creation, the global\n            // step is set to \'edit\' by the time the plugin is hit, so we hack the step if the \'publish\'\n            // button was hit\n            $stp = (ps(\'publish\')) ? \'publish\' : $stp;\n            $saveSteps = array(\'create\', \'publish\', \'image_save\', \'file_save\', \'link_save\', \'link_post\');\n            if (ps(\'save\') || in_array($stp, $saveSteps)) {\n                safe_delete(SMD_TAGU, \'item_id=\"\'.$itemID.\'\" AND type=\"\'.$evt.\'\"\');\n\n                $tags = ps(\'smd_tag_parent\') ? ps(\'smd_tag_parent\') : array();\n\n                $allowedTags = array();\n                if ($god_clause) {\n                    $allowedTags = safe_column(\'id\', SMD_TAG, \"type=\'$evt\'\" . $god_clause);\n                }\n\n                // Validate the incoming tags to those in the currently selected category|ies\n                if ($clink) {\n                    $usedCats = ($evt==\"article\") ? array(gps(\'Category1\'), gps(\'Category2\')) : array(gps(\'category\'));\n                    $catids = safe_column(\'id\', \'txp_category\', \"name IN (\" .join(\',\', quote_list($usedCats)). \") AND type=\'$evt\'\");\n                    $validTags = ($catids) ? safe_column(\'tag_id\', SMD_TAGC, \"cat_id IN (\" .join(\',\', quote_list($catids)). \")\") : array();\n                    foreach ($validTags as $validTag) {\n                        $rows = smd_tag_tree(safe_field(\'name\',SMD_TAG,\"id=\'\".doSlash($validTag).\"\'\"), $evt, \'1=1\', SMD_TAG);\n                        foreach ($rows as $row) {\n                            $allowedTags[] = $row[\'id\'];\n                        }\n                    }\n                    $tags = array_intersect(array_unique($allowedTags), $tags);\n                }\n\n                $vals = array();\n                foreach($tags as $val){\n                    safe_insert(SMD_TAGU, \'tag_id = \"\'.$val.\'\", item_id=\"\'.$itemID.\'\", type=\"\'.$evt.\'\"\');\n                    $vals[] = $val;\n                }\n                $tagsText = preg_split(\"/,\\s+?/\", ps(\'smd_tags_bytext\'));\n\n                // If we\'re in non-STRICT mode and there are any typed tags that are not in the select list, check they exist and make links to them\n                if ($quick < 2) {\n                    $added = ($vals) ? safe_column(\"title\", SMD_TAG, \"id IN (\'\".join(\"\',\'\",$vals).\"\') AND type=\'$evt\'\") : array();\n                    $tagsText = array_diff($tagsText, $added);\n\n                    // Only loop over tags that don\'t appear to be already defined\n                    foreach($tagsText as $tag) {\n                        if ($tag == \"\") continue;\n                        $exists = safe_field(\'id\', SMD_TAG, \"name = \'$tag\' AND type = \'$evt\'\"); // 1st check\n\n                        // Textarea+ input system can add (linked) tags with impunity\n                        if ($iptyp == 3 && !$exists) {\n                            $tagBits = explode(\'-->\', $tag);\n                            if (count($tagBits) == 2) {\n                                $tagparent = strtolower(sanitizeForUrl($tagBits[0]));\n                                $tagparent = safe_field(\'name\', SMD_TAG, \"name = \'$tagparent\' AND type = \'$evt\'\"); // Check parent exists for this type\n                                $tagparent = ($tagparent === false) ? \'root\' : $tagparent;\n                                $tagchild = $tagBits[1];\n                            } else {\n                                $tagparent = \'root\';\n                                $tagchild = $tagBits[0];\n                            }\n                            $sanitag = strtolower(sanitizeForUrl($tagchild));\n                            $already = safe_field(\'id\', SMD_TAG, \"name = \'$sanitag\' AND type = \'$evt\'\"); // Check again because parent-->child will fail 1st check\n                            if ($already === false) {\n                                $exists = safe_insert(SMD_TAG, \"name=\'\".$sanitag.\"\', title=\'$tagchild\', parent=\'$tagparent\', type=\'$evt\'\");\n                            }\n                            $catfield = ($evt==\"article\") ? gps(\'Category1\') : gps(\'category\');\n                            if ($clink && $catfield && $exists) {\n                                // Assign new tag to current category (cat1 for articles)\n                                $catid = safe_insert(SMD_TAGC,\"tag_id=\'$exists\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$catfield\' AND type=\'$evt\')\");\n                            }\n                            rebuild_tree_full($evt, SMD_TAG);\n                        }\n                        if ($exists) {\n                            safe_insert(SMD_TAGU, \'tag_id = \"\'.$exists.\'\", item_id=\"\'.$itemID.\'\", type=\"\'.$evt.\'\"\');\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// ------------------------\n// Handle changes to the tags table during multi-item edits\nfunction smd_tags_multi_edit($evt, $stp) {\n    // In Txp 4.0.6 the only tabs to allow multi-edits are the Article (list) and Link tabs. Cheat for now\n    if (smd_tags_table_exist()) {\n        switch ($evt) {\n            case \"list\":\n            case \"link\":\n                $method = ps(\'edit_method\');\n                $selected = ps(\'selected\');\n                break;\n            default:\n                $method = \"delete\";\n                $selected = array(ps(\'id\'));\n                break;\n        }\n\n        // \'list\' is a special case that equates to articles\n        $type = ($evt == \"list\") ? \"article\" : $evt;\n\n        if ($selected) {\n            switch ($method) {\n                case \"delete\":\n                    $ids = array();\n                    foreach ($selected as $id) {\n                        $id = assert_int($id);\n                        if (safe_delete(SMD_TAGU, \'item_id = \'.$id.\' AND type=\"\'.$type.\'\"\')) {\n                            $ids[] = $id;\n                        }\n                    }\n                    return join(\', \', $ids);\n                    break;\n            }\n        }\n        return \'\';\n    }\n}\n\n// ------------------------\n// Grab the current article/image/file/link ID\nfunction smd_getID()\n{\n    if (!empty($GLOBALS[\'ID\'])) { // newly-saved item\n        $itemID = intval($GLOBALS[\'ID\']);\n    } else {\n        $itemID = (gps(\'ID\')) ? gps(\'ID\') : gps(\'id\');\n    }\n\n    return $itemID;\n}\n\n/**\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_tags_inject_css($evt, $stp)\n{\n    global $event;\n\n    $eventmap = array(\n        \'article\' => \'list\',\n        \'file\'    => \'file\',\n        \'image\'   => \'image\',\n        \'link\'    => \'link\',\n        \'list\'    => \'list\',\n    );\n\n    $smd_tag_preflist = smd_tags_get_prefs(\'name\');\n    $prefset = smd_tags_pref_get($smd_tag_preflist);\n    $onoff = smd_tags_pref_explode($prefset[\'smd_tag_p_enable\'][\'val\']);\n    $mapped = isset($eventmap[$event]) ? $eventmap[$event] : \'\';\n    $addIt = ($mapped && $onoff[gTxt(\'tab_\'.$mapped)] == 1) ? true : false;\n\n    $stylereps = array(\n        \'{hov}\' => $prefset[\'smd_tag_t_hover\'][\'val\'],\n        \'{cur}\' => $prefset[\'smd_tag_t_hilite\'][\'val\'],\n    );\n\n    $plugin_styles = array();\n\n    if ($event === \'smd_tags\') {\n        $plugin_styles[] = smd_tags_get_style_rules(\'all\');\n    } elseif ($addIt) {\n        $plugin_styles[] = smd_tags_get_style_rules(\'common\');\n    }\n\n    if ($plugin_styles) {\n        echo \'<style>\' . strtr(implode(n, $plugin_styles), $stylereps) . \'</style>\';\n    }\n\n    return;\n}\n\n/**\n * CSS definitions: hopefully kind to themers.\n */\nfunction smd_tags_get_style_rules($type = \'all\')\n{\n    $smd_tags_styles[\'tag\'] = array(\n        \'.smd_current { padding:3px; font-weight:bold; background:#{cur}; }\',\n        \'.smd_tags_showpars, .smd_tags_linkcat { max-width:12em; }\',\n        \'.smd_tag_list_adm { display: flex; flex-wrap: wrap; justify-content: space-between; }\',\n        \'.smd_tag_list_adm.byrow { flex-direction: column; }\',\n        \'.smd_tag_list_adm.bycol ul { border:1px solid #ccc; padding:1em; }\',\n        \'.smd_tag_list_adm.byrow li { display:inline-block;  }\',\n        \'.smd_tag_list_adm.bycol li { list-style-type:none; }\',\n        \'#smd_tag_filt { float:right; margin:0; padding:1em 2em; box-shadow: 4px 4px 5px #999; }\',\n        \'#smd_tag_multisel { margin:10px 0 0; }\',\n        \'.smd_tags_new label {display:block; margin:2px 1px; }\',\n        \'.smd_tags_new input[type=\"submit\"] { margin:1.5em 0 0; }\',\n        \'.smd_tags_input_group { float:left; margin:0 1em; }\',\n        \'.smd_tags_pool { clear:both; padding:1em 0;}\',\n        \'#smd_tag_report_pane { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }\',\n        \'#smd_tag_report_pane .publish { float:right; }\',\n        \'.smd_tags_btn_ok { position: absolute; top: .27777777777778em; right: .72222222222222em; font-size: 18px; font-weight: 700; text-decoration: none; }\',\n        \'.txp-actions { float:right; }\',\n    );\n\n    $smd_tags_styles[\'common\'] = array(\n        \'.smd_hidden { display:none; }\',\n        \'.smd_hover { cursor:pointer; background:#{hov}; }\',\n        \'.smd_fakebtn, #smd_tags_bylink span { cursor:pointer; }\',\n        \'.smd_tagip { margin:.4em 0 .8em; max-width:400px; }\',\n        \'.smd_sel { font-weight:bold; }\',\n        \'.smd_tooltip { position:absolute; background:#eee; max-width:300px; min-width:150px; border:1px solid black; padding:1em; box-shadow:5px 5px 4px #999; z-index:100; }\',\n    );\n\n    $out = array();\n\n    if ($type === \'all\') {\n        $out = array_merge($smd_tags_styles[\'tag\'], $smd_tags_styles[\'common\']);\n    } elseif (array_key_exists($type, $smd_tags_styles)) {\n        $out = $smd_tags_styles[$type];\n    }\n\n    return implode(n, $out);\n}\n\n/**\n * Fetch all prefs.\n *\n * Internally, it tracks two lists:\n * a) Current prefs\n * b) Legacy prefs that needs removing\n */\nfunction smd_tags_get_prefs($type)\n{\n    $current = array(\n        \'smd_tag_prefs_p\' => array(\n            \'smd_tag_p_enable\'  => \'1111\', // one digit for article/image/file/link. 1=enabled; 0=disabled\n            \'smd_tag_p_input\'   => \'0\', // 0=select, 1=links, 2=textarea, 3=textarea+\n            \'smd_tag_p_lbi\'     => \'0\',\n            \'smd_tag_p_linkcat\' => \'0\',\n            \'smd_tag_p_listpar\' => \'1\', // permit parent in tag tree on content pages\n            \'smd_tag_p_master\'  => \'\', // special \'global\' category (per type: same name for each)\n            \'smd_tag_p_qtag\'    => \'0\', // 0=no autocomplete, 1=std a/c, 2=strict a/c\n            \'smd_tag_p_qtpath\'  => \'\', // Dir to js file(s) relative to textpattern dir\n            \'smd_tag_p_qtstyl\'  => \'\', // Dir to css file(s)\n            \'smd_tag_p_size\'    => \'6\',\n        ),\n        \'smd_tag_prefs_t\' => array(\n            \'smd_tag_t_astart\'       => \'0\',\n            \'smd_tag_t_auto\'         => \'1\',\n            \'smd_tag_t_cols\'         => \'1\',\n            \'smd_tag_t_colsord\'      => \'0\',\n            \'smd_tag_t_count\'        => \'1\',\n            \'smd_tag_t_deltree\'      => \'0\',\n            \'smd_tag_t_delused\'      => \'0\',\n            \'smd_tag_t_desc_textile\' => \'1\',\n            \'smd_tag_t_desc_tooltip\' => \'1\',\n            \'smd_tag_t_enrep\'        => \'1\',\n            \'smd_tag_t_hilite\'       => \'91919a\',\n            \'smd_tag_t_hover\'        => \'aaa\',\n            \'smd_tag_t_indent\'       => \'&nbsp;&nbsp;\',\n            \'smd_tag_t_mdelim\'       => \',\',\n            \'smd_tag_t_size_desc\'    => \'240, 80\',\n        ),\n        \'smd_tag_prefs_u\' => array(\n            \'smd_tag_u_combi\'     => \'1\',\n            \'smd_tag_u_combi_and\' => \'+\',\n            \'smd_tag_u_combi_or\'  => \'|\',\n            \'smd_tag_u_pnam\'      => \'smd_tag\',\n            \'smd_tag_u_ptyp\'      => \'smd_tagtype\',\n            \'smd_tag_u_sec\'       => \'smd_tags\',\n        ),\n    );\n\n    $old = array(\n        \'smd_tag_p_first\',\n    );\n\n    switch ($type) {\n        case \'set\':\n            $out = $current;\n            break;\n        case \'nameval\':\n            foreach ($current as $grp => $pref) {\n                foreach ($pref as $name => $dflt) {\n                    $out[$name] = $dflt;\n                }\n            }\n            break;\n        case \'name\':\n            foreach ($current as $grp => $pref) {\n                foreach ($pref as $name => $dflt) {\n                    $out[] = $name;\n                }\n            }\n            break;\n        case \'old\':\n            $out = $old;\n            break;\n        case \'default\':\n            $out = array();\n            break;\n    }\n\n    return $out;\n}\n\n// ------------------------\nfunction smd_tags_table_exist($all=\'0\') {\n    static $smd_tags_table_ok = array();\n\n    if (isset($smd_tags_table_ok[$all])) {\n        return $smd_tags_table_ok[$all];\n    }\n\n    if ($all) {\n        $tbls = array(SMD_TAG => 9, SMD_TAGC => 2, SMD_TAGU => 3);\n        $out = count($tbls);\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \" . $tbl . \" HAS \" . count(@safe_show(\'columns\', $tbl)) . \" COLUMNS; REQUIRES \" . $cols . \" ++\" . br;\n            }\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n        $ret = ($out===0) ? 1 : 0;\n        $smd_tags_table_ok[1] = $ret;\n        return $ret;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \" . SMD_TAG . \" HAS \" . count(@safe_show(\'columns\', SMD_TAG)) . \" COLUMNS;\";\n        }\n        $ret = @safe_show(\'columns\', SMD_TAG);\n        $smd_tags_table_ok[0] = $ret;\n        return $ret;\n    }\n}\n\n// ------------------------\n// Add tag tables if not already installed\nfunction smd_tags_table_install($showpane=\'1\') {\n    global $DB;\n\n    $debug = gps(\'debug\');\n    $GLOBALS[\'txp_err_count\'] = 0;\n\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAG.\"` (\n        `id` int(6) NOT NULL auto_increment,\n        `name` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `type` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `parent` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `lft` int(6) NOT NULL default \'0\',\n        `rgt` int(6) NOT NULL default \'0\',\n        `title` varchar(255) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `description` text NULL COLLATE utf8_general_ci,\n        `desc_html` text NULL COLLATE utf8_general_ci,\n        PRIMARY KEY (`id`)\n    ) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=5 CHARACTER SET=utf8\";\n\n    if (!smd_tags_table_exist()) {\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (1, \'root\', \'article\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (2, \'root\', \'link\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (3, \'root\', \'image\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (4, \'root\', \'file\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n    }\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAGC.\"` (\n        `tag_id` int(6) NOT NULL default \'0\',\n        `cat_id` int(6) NOT NULL default \'0\',\n        PRIMARY KEY (`tag_id`,`cat_id`)\n    ) ENGINE=MyISAM\";\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAGU.\"` (\n        `item_id` int(11) NOT NULL default \'0\',\n        `tag_id` int(6) NOT NULL default \'0\',\n        `type` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        PRIMARY KEY (`item_id`,`tag_id`)\n    ) ENGINE=MyISAM CHARACTER SET=utf8\";\n\n    if($debug) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\" . $GLOBALS[\'txp_err_count\'] . \".</b> \" . mysqli_error($DB->link) . \"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Upgrade table collation if necessary\n    $ret = getRows(\"SHOW TABLE STATUS WHERE name IN (\'\".PFX.SMD_TAG.\"\', \'\".PFX.SMD_TAGU.\"\')\");\n    if ($ret[0][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_TAG, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n    }\n    if ($ret[1][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_TAGU, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n    }\n\n    // Add the description column on upgrade\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_TAG.\'`\');\n    if (!in_array(\'description\',$flds)) {\n        safe_alter(SMD_TAG, \"ADD `description` TEXT NULL AFTER `title`\", $debug);\n    }\n    if (!in_array(\'desc_html\',$flds)) {\n        safe_alter(SMD_TAG, \"ADD `desc_html` TEXT NULL AFTER `description`\", $debug);\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_tag_tbl_installed\');\n        smd_tags_prefs_install($message, $showpane);\n    } else {\n        if ($showpane) {\n            $message = gTxt(\'smd_tag_tbl_not_installed\');\n            smd_tags_prefs_show($message);\n        }\n    }\n}\n\n// ------------------------\n// Drop tag tables if in database\nfunction smd_tags_table_remove() {\n    global $DB;\n\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    if (smd_tags_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAG. \"; \";\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAGU. \"; \";\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAGC. \"; \";\n        if(gps(\'debug\')) {\n            dmp($sql);\n        }\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\" . $GLOBALS[\'txp_err_count\'] . \".</b> \" . mysqli_error($DB->link) . \"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_tag_tbl_removed\');\n        smd_tags_prefs_remove($message);\n    } else {\n        $message = gTxt(\'smd_tag_tbl_not_removed\');\n        smd_tags_prefs_show($message);\n    }\n}\n// ------------------------\n// Rebuild modified preorder tag links\nfunction smd_tags_table_rebuild() {\n    $types = array(\'article\', \'image\', \'file\', \'link\');\n    foreach ($types as $tag_type) {\n        rebuild_tree_full($tag_type, SMD_TAG);\n    }\n    $message = gTxt(\'smd_tag_tbl_rebuilt\');\n    smd_tags_prefs_show($message);\n}\n\n// ------------------------\n// Add plugin preferences to prefs\nfunction smd_tags_prefs_install($message = \'\', $showpane = \'1\') {\n    $smd_tag_prefs = smd_tags_get_prefs(\'nameval\');\n\n    $ctr = safe_count(\'txp_prefs\', \'event=\"smd_tags\"\') + 1;\n\n    foreach ($smd_tag_prefs as $pref => $dflt) {\n        if (!get_pref($pref)) {\n            set_pref($pref, $dflt, \'smd_tags\', PREF_PLUGIN, \'text_input\', $ctr);\n            $ctr++;\n        }\n    }\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'old\');\n\n    // Tidy up any legacy prefs\n    foreach ($smd_tag_prefs as $pref) {\n        remove_pref($pref, \'smd_tags\');\n    }\n\n    if ($showpane) {\n        $message .= gTxt(\'smd_tag_prefs_installed\');\n        smd_tags_prefs_show($message);\n    }\n}\n\n// ------------------------\n// Remove plugin preferences from prefs table.\nfunction smd_tags_prefs_remove($message = \'\', $showpane = \'1\') {\n    $smd_tag_prefs = array_merge(smd_tags_get_prefs(\'name\'), smd_tags_get_prefs(\'old\'));\n\n    foreach ($smd_tag_prefs as $pref) {\n        remove_pref($pref, \'smd_tags\');\n    }\n\n    if ($showpane) {\n        $message .= gTxt(\'smd_tag_prefs_removed\');\n        smd_tags_prefs_show($message);\n    }\n}\n\n// ------------------------\n// Saves plugin preferences\nfunction smd_tags_prefs_update($message = \'\') {\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    $post = doSlash(stripPost());\n\n    foreach ($smd_tag_prefs as $pref) {\n        if (isset($post[$pref])) {\n            update_pref($pref, $post[$pref]);\n        }\n    }\n\n    $message .= gTxt(\'preferences_saved\');\n    smd_tags_prefs_show($message);\n}\n\n// ------------------------\n// Split a multi-char preference value into an array of keys/values.\n// Note: value is passed in and not read directly from the prefs array in this function - intentionally\nfunction smd_tags_pref_explode($val) {\n    $order = array_values(array(gTxt(\'tab_list\'),gTxt(\'tab_image\'),gTxt(\'tab_file\'),gTxt(\'tab_link\')));\n    $onoff = array_values(preg_split(\'//\', $val, -1, PREG_SPLIT_NO_EMPTY));\n    $out = array();\n\n    foreach($order as $key1 => $value1) {\n        $out[(string)$value1] = $onoff[$key1];\n    }\n\n    return $out;\n}\n\n// ------------------------\n// Get values from prefs - pass either a single key or an array.\n// If 2nd arg is set and the pref doesn\'t exist, read it from the plugin default\nfunction smd_tags_pref_get($keys, $dflt = 0) {\n    static $smd_tag_prefstore;\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'nameval\');\n    $prefout = array();\n\n    if (!is_array($keys)) {\n        $keys = array($keys);\n    }\n\n    // For later looping round defaults\n    $allkeys = $keys;\n\n    // Check the cache and remove any keys we\'ve already fetched\n    foreach ($keys as $idx => $key) {\n        if (isset($smd_tag_prefstore[$key])) {\n            $prefout[$key] = $smd_tag_prefstore[$key];\n            unset($keys[$idx]);\n        }\n    }\n\n    if ($keys) {\n        $prefkeys = doQuote(join(\"\',\'\",doSlash($keys)));\n        $rs = safe_rows(\'name,val,html\',\'txp_prefs\',\'name IN (\'.$prefkeys.\') ORDER BY name\');\n\n        foreach($rs as $pref) {\n            $smd_tag_prefstore[$pref[\'name\']] = $prefout[$pref[\'name\']] = array_slice($pref, 1);\n        }\n    }\n\n    if ($dflt) {\n        foreach ($allkeys as $pref) {\n            if (!isset($prefout[$pref])) {\n                $prefout[$pref] = $smd_tag_prefs[$pref];\n            }\n        }\n    }\n\n    return $prefout;\n}\n\n// ------------------------\n// Common buttons on the admin panel\nfunction smd_tags_buttons() {\n    $ret = array (\n        \'btnPrefsSave\' => fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'),\n        \'btnInstallTbl\' => sLink(\'smd_tags\', \'smd_tags_table_install\', \'<span class=\"ui-icon ui-extra-icon-upload\"></span> \'.gTxt(\'smd_tag_tbl_install_lbl\')),\n        \'btnRemoveTbl\' => sLink(\'smd_tags\', \'smd_tags_table_remove\', \'<span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'smd_tag_tbl_remove_lbl\')),\n        \'btnRebuildTbl\' => sLink(\'smd_tags\', \'smd_tags_table_rebuild\', \'<span class=\"ui-icon ui-icon-refresh\"></span> \'.gTxt(\'smd_tag_tbl_rebuild_lbl\')),\n        \'btnInstall\' => sLink(\'smd_tags\', \'smd_tags_prefs_install\', \'<span class=\"ui-icon ui-extra-icon-upload\"></span> \'.gTxt(\'smd_tag_pref_install_lbl\')),\n        \'btnRemove\' => sLink(\'smd_tags\', \'smd_tags_prefs_remove\', \'<span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'smd_tag_pref_remove_lbl\')),\n        \'btnPrefs\' => sLink(\'smd_tags\', \'smd_tags_prefs_show\', \'<span class=\"ui-icon ui-icon-gear\"></span> \'.gTxt(\'smd_tag_pref_show_lbl\')),\n        \'btnManage\' => sLink(\'smd_tags\', \'smd_tags_manage\', \'<span class=\"ui-icon ui-icon-tag\"></span> \'.gTxt(\'smd_tag_manage_lbl\')),\n        \'btnSync\' => sLink(\'smd_tags\', \'smd_tags_sync\', \'<span class=\"ui-icon ui-icon-arrowreturn-1-n\"></span> \'.gTxt(\'smd_tag_sync_lbl\')),\n        \'btnSyncGo\' => fInput(\'submit\', \'smd_tags_do_import\', gTxt(\'go\'), \'publish\'),\n        \'btnHelp\' => \'<a class=\"pophelp\" rel=\"help\" href=\"?event=plugin\'.a.\'step=plugin_help\'.a.\'name=smd_tags\">?</a>\',\n        \'btnCreate\' => fInput(\'submit\', \'smd_tag_create\', gTxt(\'create\'), \'publish\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnSave\' => fInput(\'submit\', \'smd_tag_save\', gTxt(\'update\'), \'\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnDelete\' => fInput(\'submit\', \'smd_tags_delete\', \'×\', \'\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnStyle\' => \' style=\"border:0;height:25px\"\',\n    );\n    return $ret;\n}\n\n// ------------------------\n// A stub that can be called from a Txp event\nfunction smd_tags_setup($evt=\'\', $stp=\'\', $message=\'\') {\n    smd_tags_prefs_show();\n}\n\n/**\n * Display the prefs panel.\n *\n * @param      string  $message  The message to display\n */\nfunction smd_tags_prefs_show($message = \'\')\n{\n    $smd_tag_prefs = smd_tags_get_prefs(\'set\');\n    $smd_tag_preflist = smd_tags_get_prefs(\'name\');\n\n    pagetop(gTxt(\'smd_tag_prefs_title\'), $message);\n    extract(smd_tags_buttons());\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_preflist);\n    $numReqPrefs = count($smd_tag_preflist);\n    $numPrefs = count($prefset);\n    $adv_text = gTxt(\'smd_tag_extras\');\n\n    echo script_js(<<<EOJS\n// Concatenate checkbox options for storage\nfunction smd_presub() {\n    var smd_out = \"\";\n    jQuery(\"#prefs-smd_tag_p_enable :checkbox\").each(function() {\n        smd_out += (this.checked) ? 1 : 0;\n    });\n    jQuery(\"#smd_tag_p_enable\").val(smd_out);\n    return true;\n}\njQuery(function() {\n    jQuery(\'.smd_tag_btn_advanced_toggle\').click(function(e) {\n        e.preventDefault();\n        jQuery(\'.smd_tag_btn_advanced\').toggle(\'fast\');\n    });\n});\nEOJS\n    );\n\n    if (smd_tags_table_exist(1)) {\n        // Tables installed\n        echo \'<form method=\"post\" action=\"?event=smd_tags\' . a . \'step=smd_tags_prefs_update\" onsubmit=\"return smd_presub();\">\';\n        echo \'<div class=\"txp-layout\">\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            n. \'<h1 class=\"txp-heading\">\'.gTxt(\'smd_tag_prefs_title\').sp.$btnHelp.\'</h1>\'.\n            n. \'</div>\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            graf(\n                    $btnManage\n                    .n.$btnSync\n                    .n.tag($btnInstall.n.$btnRemove.n.$btnRemoveTbl.n.$btnRebuildTbl, \'span\', \' class=\"smd_tag_btn_advanced smd_hidden\"\')\n                    .n.href($adv_text, \'#\', \' class=\"smd_tag_btn_advanced_toggle\"\')\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            $groupOut = array();\n            $prefOut = array();\n\n            foreach ($smd_tag_prefs as $grp => $pset) {\n                $out = array();\n                $groupOut[] = n.tag(href(\n                        gTxt($grp),\n                        \'#prefs_group_\'.$grp,\n                        array(\n                            \'data-txp-pane\'  => $grp,\n                            \'data-txp-token\' => md5($grp.\'prefs\'.form_token().get_pref(\'blog_uid\')),\n                        )),\n                    \'li\');\n\n                foreach ($pset as $pnam => $pdflt) {\n                    $pval = get_pref($pnam, $pdflt, 1);\n                    $label = \'<label for=\"\'.$pnam.\'\">\'.gTxt($pnam).\'</label>\';\n                    $widget = \'\';\n\n                    switch($pnam) {\n                        case \"smd_tag_p_enable\":\n                            $options = smd_tags_pref_explode($pval);\n                            foreach ($options as $opt => $onoff) {\n                                $widget .= \'<label>\'.$opt.\'</label>\' . checkbox(\"enable\".$opt, 1, $onoff) . \'&#160;&#160;\';\n                            }\n\n                            $widget .= fInput(\'hidden\', \'smd_tag_p_enable\', join($options),\'\',\'\',\'\',\'\',\'\',\'smd_tag_p_enable\');\n                            break;\n                        case \"smd_tag_p_size\":\n                        case \"smd_tag_t_size_desc\":\n                        case \"smd_tag_t_cols\":\n                        case \"smd_tag_t_hilite\":\n                        case \"smd_tag_t_hover\":\n                        case \"smd_tag_t_indent\":\n                        case \"smd_tag_u_pnam\":\n                        case \"smd_tag_u_ptyp\":\n                        case \"smd_tag_u_sec\":\n                        case \"smd_tag_p_master\":\n                        case \"smd_tag_u_combi_and\":\n                        case \"smd_tag_u_combi_or\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 20, \'\', $pnam);\n                            break;\n                        case \"smd_tag_t_mdelim\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 1, \'\', $pnam);\n                            break;\n                        case \"smd_tag_p_qtpath\":\n                        case \"smd_tag_p_qtstyl\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 30, \'\', $pnam);\n                            break;\n                        case \"smd_tag_t_auto\":\n                            $widget = onoffRadio($pnam, $pval);\n                            break;\n                        case \"smd_tag_t_colsord\":\n                            $rset = array(0 => gTxt(\'smd_tag_by_column\'), 1 => gTxt(\'smd_tag_by_row\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_t_deltree\":\n                            $rset = array(0 => gTxt(\'smd_tag_children_pro\'), 1 => gTxt(\'smd_tag_children_del\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_t_count\":\n                        case \"smd_tag_t_delused\":\n                        case \"smd_tag_t_desc_textile\":\n                        case \"smd_tag_t_desc_tooltip\":\n                        case \"smd_tag_t_enrep\":\n                        case \"smd_tag_p_linkcat\":\n                        case \"smd_tag_p_listpar\":\n                        case \"smd_tag_u_combi\":\n                            $widget .= yesnoRadio($pnam, $pval);\n                            break;\n                        case \"smd_tag_t_astart\":\n                            $rset = array(0 => gTxt(\'smd_tag_prefs_pane\'), 1 => gTxt(\'smd_tag_tags_pane\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_qtag\":\n                            $rset = array(0 => gTxt(\'off\'), 1 => gTxt(\'smd_tag_ac_std\'), 2 => gTxt(\'smd_tag_ac_str\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_input\":\n                            $rset = array(0 => gTxt(\'smd_tag_sel_list\'), 1 => gTxt(\'smd_tag_textlist\'), 2 => gTxt(\'smd_tag_textbox\'), 3 => gTxt(\'smd_tag_textboxplus\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_lbi\":\n                            $rset = array(0 => gTxt(\'no\'), 1 => gTxt(\'smd_tag_bi_up\'), 2 => gTxt(\'smd_tag_bi_down\'), 3 => gTxt(\'smd_tag_bi_both\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                    }\n\n                    $out[] = inputLabel(\n                        $pnam,\n                        $widget,\n                        $label,\n                        \'\',\n                        array(\n                            \'class\' => \'txp-form-field\',\n                            \'id\'    => \'prefs-\'.$pnam,\n                        )\n                    );\n\n                }\n\n                $prefOut[] = tag(\n                    hed(gTxt($grp), 2, array(\'id\' => \'prefs_group_\'.$grp.\'-label\')).\n                    join(n, $out), \'section\', array(\n                        \'class\'           => \'txp-prefs-group\',\n                        \'id\'              => \'prefs_group_\'.$grp,\n                        \'aria-labelledby\' => \'prefs_group_\'.$grp.\'-label\',\n                    )\n                );\n            }\n\n        echo tag_start(\'div\', array(\'class\' => \'txp-layout-4col-alt\')).\n            wrapGroup(\n                \'smd_tags_prefs\',\n                n.tag(join($groupOut), \'ul\', array(\'class\' => \'switcher-list\')),\n                \'smd_tag_prefs_title\'\n            );\n\n        echo graf(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'), array(\'class\' => \'txp-save\'));\n\n        echo n.tag_end(\'div\'). // End of .txp-layout-4col-alt.\n            n.tag_start(\'div\', array(\'class\' => \'txp-layout-4col-3span\')).\n            join(n, $prefOut).\n            n.tag_end(\'div\'). // End of .txp-layout-4col-3span.\n            tInput();\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo startTable()\n                .tr(tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                    .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                    .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .endTable();\n        } else {\n            // Prefs not installed\n            echo startTable()\n                .tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .tr(tda($btnInstall, $btnStyle))\n                .endTable();\n        }\n    } else {\n        // Tables not installed\n        echo startTable()\n            .tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .tr(tda($btnInstallTbl, $btnStyle))\n            .endTable();\n    }\n\n    echo \'</div></form>\';\n}\n\n/**\n * Manage tags in a similar way to the categories tab.\n *\n * @param      string  $message  The message to display\n * @param      string  $report   The report to display\n */\nfunction smd_tags_manage($message = \'\', $report = \'\')\n{\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    extract(doSlash(gpsa(array(\'smd_tag_name\', \'smd_tag_title\', \'smd_tag_description\', \'smd_tag_parent\', \'smd_tag_cat\', \'smd_tag_type\', \'smd_tag_id\'))));\n    pagetop(gTxt(\'smd_tag_manage_lbl\'),$message);\n    extract(smd_tags_buttons());\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_prefs);\n    $numReqPrefs = count($smd_tag_prefs);\n    $numPrefs = count($prefset);\n\n    $types = array(\n        \'article\' => ucfirst(gtxt(\'article\')),\n        \'image\' => gtxt(\'tag_image\'),\n        \'file\' => gtxt(\'file\'),\n        \'link\' => gtxt(\'tag_link\'),\n    );\n\n    // Make up the radio buttons.\n    $smd_tag_type = (!empty($smd_tag_type)) ? $smd_tag_type : \'article\';\n\n    $radios = array();\n\n    foreach ($types as $key => $val) {\n        $id = \'smd_tags_type-\'.$key;\n        $radios[] = n.radio(\'smd_tags_type\', $key, ($smd_tag_type == $key) ? 1 : 0, $id).n.\n                \'<label for=\"\'.$id.\'\">\'.$val.\'</label>\';\n    }\n\n    $radios = join(n, $radios).n;\n\n    // Create all the lists but hide them with CSS.\n    // jQuery takes over and switches between them on demand.\n    if (smd_tags_table_exist(1) && $numPrefs == $numReqPrefs) {\n        // Sanitize the multi-tag delimiter\n        $mdelim = substr($prefset[\'smd_tag_t_mdelim\'][\'val\'], 0, 1);\n        $mdelim = ($mdelim == \'\') ? \'\' : \'\\\\\'.$mdelim;\n\n        $colOpts = do_list($prefset[\'smd_tag_t_cols\'][\'val\'], \':\');\n        $layout = ($colOpts[0] == \'0\' || $colOpts[0] == \'list\') ? \'list\' : (($colOpts[0] == \'group\') ? \'listgrp\' : \'table\');\n        $colOrder = ($prefset[\'smd_tag_t_colsord\'][\'val\'] == 0) ? \'bycol\' : \'byrow\';\n        $wraptag = $layout.\':\';\n\n        if ($layout == \'table\') {\n            $tagCols = (is_numeric($colOpts[0]) && $colOpts[0] > 0) ? $colOpts[0] : 1;\n            $wraptag .= $tagCols;\n            $sel = \'td\';\n        } else {\n            $wraptag .= (isset($colOpts[1])) ? $colOpts[1] : \'0\';\n            $sel = \'li\';\n        }\n        $wraptag .= \':cols:\'.$colOrder;\n        $counts = $prefset[\'smd_tag_t_count\'][\'val\'];\n        $clink = $prefset[\'smd_tag_p_linkcat\'][\'val\'];\n        $showrep = $report && $prefset[\'smd_tag_t_enrep\'][\'val\'];\n\n        foreach (array_keys($types) as $type) {\n            $divtagid = \"smd_tags_grp_\".$type;\n            $taglist = \"tags_\".$type;\n            $$taglist = smd_tag_list_adm(\n                array(\n                    \"type\" => $type,\n                    \"wraptag\" => $wraptag,\n                    \"count\" => $counts,\n                    \"indent\" => $prefset[\'smd_tag_t_indent\'][\'val\']\n                )\n            );\n\n            // @todo Find a way to add the divtagid only when needed. At the moment it appears twice on the page (illegal DOM)\n            $tagout[$type] = \'<div id=\"\'.$divtagid.\'\">\'.($$taglist ? $$taglist : gTxt(\'smd_tag_no_tags\', array(\'{type}\' => $type))).\'</div>\';\n            echo \'<div class=\"smd_hidden\">\' . $tagout[$type] . \'</div>\';\n        }\n\n        $qs = array(\n            \"event\" => \"smd_tags\",\n        );\n\n\n        $qsVars = \"index.php\".join_qs($qs);\n        $count_lbl = gTxt(\'smd_tag_count_lbl\');\n\n        echo script_js(<<<EOJS\nvar smd_tags_step = \'\';\n\n// Page initialisation\njQuery(function() {\n    // Indicate parent list is loading\n    jQuery(\".smd_tags_showpars\").ajaxStart(function() {\n        jQuery(this).fadeTo(\"normal\", 0.33);\n    }).ajaxStop(function() {\n        jQuery(this).fadeTo(\"normal\", 1);\n    });\n\n    // Switch between types\n    jQuery(\"input[name=\'smd_tags_type\']\").change(function () {\n        var smd_newcontent = jQuery(\"#smd_tags_grp_\"+jQuery(this).val()).html();\n        jQuery(\"input[name=\'smd_tag_id\']\").val(\'\'); // Changing type forces any \'save\' to trigger the \'create\' behaviour\n        jQuery(\".smd_tags_showlist\").html(smd_newcontent);\n        var nam = jQuery(\"input[name=\'smd_tag_oname\']\").val();\n        var dsc = jQuery(\"input[name=\'smd_tag_description\']\").val();\n        var tid = jQuery(\"input[name=\'smd_tag_id\']\").val();\n        var typ = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n        var par = \'root\';\n        var cat = \'\';\n\n        jQuery(\".smd_tags_showlist {$sel} span[name=\'smd_tagnam\']\").each(function() {\n            if (jQuery(this).text() == nam) {\n                jQuery(this).parent().addClass(\'smd_current\');\n                par = jQuery(this).siblings(\":eq(2)\").text();\n                cat = jQuery(this).siblings(\":eq(3)\").text();\n                dsc = jQuery(this).siblings(\":eq(4)\").text();\n                tid = (tid == \'\') ? jQuery(this).siblings(\":eq(0)\").text() : tid;\n            } else {\n                jQuery(this).parent().removeClass(\'smd_current\');\n            }\n        });\n\n        // Re-insert the ID if this named item exists in the new list\n        jQuery(\"input[name=\'smd_tag_id\']\").val(tid);\n\n        // Update the UI\n        smd_parentHandler(nam, typ, tid, par);\n        smd_catHandler(cat, typ);\n        jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n        smd_autofocus();\n        smd_cellHandler();\n        jQuery(\"input[name=\'smd_tags_type\']\").each(function () {\n            if (jQuery(this).val() == typ) {\n                jQuery(this).next().addClass(\'smd_current\');\n            } else {\n                jQuery(this).next().removeClass(\'smd_current\');\n            }\n        });\n    });\n\n    // Call the onchange handler for the current type\n    jQuery(\"input[name=\'smd_tags_type\']:checked\").change();\n\n    // Autotag - TODO: foreign character dumbdown for URI\n    if ({$prefset[\'smd_tag_t_auto\'][\'val\']}) {\n        spcRE = /\\s/g;\n        badRE = /[^a-zA-Z0-9_\\-{$mdelim}]/g;\n        jQuery(\"input[name=\'smd_tags_newtitle\']\").keyup(function() {\n            tagname = jQuery(this).val().replace(spcRE, \'-\').replace(badRE, \'\').toLowerCase();\n            jQuery(\"input[name=\'smd_tags_newname\']\").val(tagname);\n        });\n    }\n    // Bind Enter key to create\n    jQuery(\"input[name^=\'smd_tag\']\").keyup(function(ev) {\n        if (ev.keyCode == 13) {\n            jQuery(\"input[name=\'smd_tag_create\']\").click();\n        }\n    });\n\n    // Prepare the live filter\n    jQuery(\'#smd_tag_filt legend a\').click(function() {\n        if (jQuery(this).parent().hasClass(\'expanded\')) {\n            jQuery(\'#smd_tagfilter\').focus();\n        }\n    });\n\n    jQuery(\'.smd_tag_list_adm {$sel}\').addClass(\'visible\');\n\n    jQuery(\'#smd_tagfilter\').keyup(function(event) {\n        // if esc is pressed or nothing is entered\n        if (event.keyCode == 27 || jQuery(this).val() == \'\') {\n            jQuery(this).val(\'\');\n            jQuery(\'.smd_tag_list_adm {$sel}\').removeClass(\'visible\').show().addClass(\'visible\');\n            jQuery(\"#smd_tag_filter_count\").empty();\n        } else {\n            currOpt = jQuery(\'#smd_tag_filt_radios :radio:checked\').val();\n            subfilt = (currOpt == \'smd_all\') ? \'\' : currOpt;\n            smd_tag_filter(\'.smd_tag_list_adm {$sel}\', jQuery(this).val(), subfilt);\n        }\n    });\n    // Trigger the filter if the radio buttons are clicked\n    jQuery(\'#smd_tag_filt_radios :radio\').change(function() {\n        jQuery(\'#smd_tagfilter\').keyup().focus();\n    });\n    if (\'{$showrep}\') {\n        smd_tags_toggle_report();\n    }\n\n    jQuery(\'#smd_tags_report_toggler, .smd_tags_btn_ok\').on(\'click\', smd_tags_toggle_report_handler);\n});\n\n// Keep track of which tag has been clicked so it can be edited/updated\nfunction smd_cellHandler() {\n    // Cell highlights\n    jQuery(\".smd_tags_showlist {$sel}\").each(function() {\n        if (jQuery.trim(jQuery(this).text()) != \"\") {\n            jQuery(this).hover(\n                function () {\n                    jQuery(this).addClass(\'smd_hover\');\n                },\n                function () {\n                    jQuery(this).removeClass(\'smd_hover\');\n                }\n            )\n            .click(function() {\n                var nam = jQuery(this).children(\":eq(0)\").text();\n                var tid = jQuery(this).children(\":eq(1)\").text();\n                var ttl = jQuery(this).children(\":eq(2)\").text();\n                var par = jQuery(this).children(\":eq(3)\").text();\n                var cat = jQuery(this).children(\":eq(4)\").text();\n                var dsc = jQuery(this).children(\":eq(5)\").text();\n                var typ = jQuery(\"input[name=\'smd_tags_type\']:checked\").val()\n                jQuery(\"input[name=\'smd_tag_oname\']\").val(nam);\n                jQuery(\"input[name=\'smd_tags_newname\']\").val(nam);\n                jQuery(\"input[name=\'smd_tag_id\']\").val(tid);\n                jQuery(\"input[name=\'smd_tags_newtitle\']\").val(ttl);\n                jQuery(\"textarea[name=\'smd_tags_description\']\").val(dsc);\n                jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n                smd_parentHandler(nam, typ, tid, par);\n                jQuery(\".smd_tags_showlist ${sel}\").removeClass(\'smd_current\');\n                jQuery(this).addClass(\'smd_current\');\n                smd_autofocus();\n            });\n        }\n    });\n}\n\n// Ask Txp for the parent dropdown without child entries\nfunction smd_parentHandler(nam, typ, tid, par) {\n    jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: nam, type: typ, id: tid, link_mode: \'2\', _txp_token: textpattern._txp_token },\n        function(data) {\n            jQuery(\".smd_tags_showpars\").html(data);\n            if (par == \"root\" || par == \"\") {\n                jQuery(\".smd_tags_showpars select option:first\").prop(\"selected\", true);\n            } else {\n                jQuery(\".smd_tags_showpars select option[value=\'\"+par+\"\']\").prop(\"selected\", true);\n            }\n        }\n    );\n}\n\n// Ask Txp for the category dropdown\nfunction smd_catHandler(cat, typ) {\n    jQuery(\".smd_tags_linkcat\").fadeTo(\"normal\", 0.33);\n    jQuery.post(\'{$qsVars}\', { step: \"smd_tag_catlist\", name: cat, type: typ, _txp_token: textpattern._txp_token },\n        function(data) {\n            jQuery(\".smd_tags_linkcat\").html(data);\n            if (cat == \"root\" || cat == \"\") {\n                jQuery(\".smd_tags_linkcat select option:first\").prop(\"selected\", true);\n            } else {\n                jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n            }\n            jQuery(\".smd_tags_linkcat\").fadeTo(\"normal\", 1);\n        }\n    );\n}\n\nfunction smd_autofocus() {\n    jQuery(\"input[name=\'smd_tags_newtitle\']\").focus().select();\n}\n\n// Make sure what is in the visible boxes is POSTed via the hidden form\nfunction smd_presub() {\n    jQuery(\"#smd_tag_postit input[name=\'step\']\").val(smd_tags_step);\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_name\']\").val(jQuery(\"input[name=\'smd_tags_newname\']\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_title\']\").val(jQuery(\"input[name=\'smd_tags_newtitle\']\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_type\']\").val(jQuery(\"input[name=\'smd_tags_type\']:checked\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_parent\']\").val(jQuery(\".smd_tags_showpars option:selected\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_cat\']\").val(jQuery(\".smd_tags_linkcat option:selected\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_description\']\").val(jQuery(\"textarea[name=\'smd_tags_description\']\").val());\n    jQuery(\"#smd_tag_postit\").submit();\n}\n\nfunction smd_multisub() {\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n    jQuery(\"#smd_tag_multiform input[name=\'step\']\").val(smd_tags_step);\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_type\']\").val(theType);\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_id\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagid\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_name\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagnam\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_description\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagdsc\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_extra\']\").val(jQuery(\"#smd_tag_sel_secondary option:selected\").val());\n}\n\n// Live tag filter\nfunction smd_tag_filter(selector, query, nam) {\n    var count_lbl = \'{$count_lbl}\';\n    var query = jQuery.trim(query);\n    query = query.replace(/ /gi, \'|\'); // add OR for regex query\n    var re = new RegExp(query, \"i\");\n    jQuery(selector).each(function() {\n        sel = (typeof nam==\"undefined\" || nam==\'\') ? jQuery(this) : jQuery(this).find(\"span[name=\'\"+nam+\"\']\");\n        (sel.text().search(re) < 0) ? jQuery(this).hide().removeClass(\'visible\') : jQuery(this).show().addClass(\'visible\');\n    });\n\n    // Display the matched count\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n    num_matches = count_lbl + jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").length;\n    jQuery(\"#smd_tag_filter_count\").text(num_matches);\n}\n\n// Handle secondary multi-select options\nfunction smd_tags_seledit(obj) {\n    smd_tags_step = jQuery(obj).val();\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n\n    switch(smd_tags_step) {\n        case \"smd_tags_multi_set_parent\":\n            // Grab all elements of the current type that have data in them\n            listitems = jQuery(\"#smd_tags_grp_\"+theType+\" {$sel}\").filter(function() {return jQuery(this).find(\'span\').length > 0}).get();\n            listitems.sort(function(a, b) {\n                var compA = jQuery(a).text().toUpperCase();\n                var compB = jQuery(b).text().toUpperCase();\n                return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;\n            });\n\n            out = \'<select id=\"smd_tag_sel_secondary\">\';\n            out += \'<option value=\"0\"></option>\';\n            jQuery.each(listitems, function() {\n                item = jQuery(this);\n                tagid = jQuery(this).find(\"span[name=\'smd_tagid\']\").text();\n                tagnam = jQuery(this).find(\"span[name=\'smd_tagnam\']\").text();\n                out += \'<option value=\"\'+tagid+\'\">\'+tagnam+\'</option>\';\n            });\n            out += \'</select>\';\n            jQuery(\"#smd_tag_multi_placeholder\").html(out);\n            break\n        case \"smd_tags_multi_catlink\":\n            jQuery.post(\'{$qsVars}\', { step: \"smd_tag_catlist\", type: theType, html_id: \'smd_tag_sel_secondary\', _txp_token: textpattern._txp_token  },\n                function(data) {\n                    jQuery(\"#smd_tag_multi_placeholder\").html(data);\n                }\n            );\n            break\n        case \"smd_tags_delete\":\n        case \'\':\n            jQuery(\"#smd_tag_multi_placeholder\").empty();\n            break;\n    }\n}\n\n/**\n * jQuery callback for displaying tag report.\n */\nfunction smd_tags_toggle_report_handler(ev) {\n    ev.preventDefault();\n    smd_tags_toggle_report();\n}\n\nfunction smd_tags_toggle_report() {\n    jQuery(\"#smd_tag_report_pane\").toggle(\'fast\');\n}\nEOJS\n        );\n    }\n\n    // The tag management panel\n    if (smd_tags_table_exist()) {\n        // Tables installed\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            echo \'<div class=\"txp-layout\">\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                n. hed(gTxt(\'smd_tag_manage_lbl\').sp.$btnHelp, 1).\n                n. \'</div>\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                graf(\n                    $btnSync\n                    .n.$btnPrefs\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n            // Live search / multi-edit\n            $filtopts = array(\n                \'smd_all\' => gTxt(\'smd_tag_all_lbl\'),\n                \'smd_tagttl\' => gTxt(\'title\'),\n                \'smd_tagdsc\' => gTxt(\'description\'),\n                \'smd_tagnam\' => gTxt(\'name\'),\n                \'smd_tagpar\' => gTxt(\'smd_tag_parent_lbl\'),\n            );\n            $withselopts = array(\n                \'smd_tags_delete\' => gTxt(\'delete\'),\n                \'smd_tags_multi_set_parent\' => gTxt(\'smd_tag_assign_parent_lbl\'),\n            );\n            // Tack on the category link items if required\n            if ($clink) {\n                $filtopts[\'smd_tagcat\'] = gTxt(\'smd_tag_clink_lbl\');\n                $withselopts[\'smd_tags_multi_catlink\'] = gTxt(\'smd_tag_link_to_cat_lbl\');\n            }\n\n            $descsizes = do_list($prefset[\'smd_tag_t_size_desc\'][\'val\']);\n            $descw = (int)$descsizes[0];\n            $desch = (isset($descsizes[1])) ? (int)$descsizes[1] : $descw;\n            echo \'<div class=\"txp-layout-1col\">\';\n            echo n. \'<form id=\"smd_tag_multiform\" action=\"?event=smd_tags\" method=\"post\" onsubmit=\"smd_multisub(); return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'\n                .n. \'<fieldset id=\"smd_tag_filt\"><legend class=\"txp-summary lever\'.(get_pref(\'pane_smd_tag_filters_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_tag_filters\">\'.gTxt(\'smd_tag_tag_search\').\'</a></legend><div id=\"smd_tag_filters\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_tag_filters_visible\') ? \'block\' : \'none\').\'\">\'\n                .n. hInput(\'step\', \'\')\n                .n. hInput(\'smd_tag_type\', \'\')\n                .n. hInput(\'smd_tag_id\', \'\')\n                .n. hInput(\'smd_tag_name\', \'\')\n                .n. hInput(\'smd_tag_description\', \'\')\n                .n. hInput(\'smd_tag_extra\', \'\')\n                .n. tInput()\n                .n. \'<input type=\"text\" name=\"smd_tagfilter\" value=\"\" id=\"smd_tagfilter\" size=\"30\" />\'\n                .n. \'<span id=\"smd_tag_filter_count\"></span>\'\n                .n. \'<div id=\"smd_tag_filt_radios\">\'.radioSet($filtopts, \'smd_tag_filtopt\', \'smd_all\', \'\', \'smd_tag_filtopt\').\'</div>\'\n                .n. \'<div id=\"smd_tag_multisel\">\'.gTxt(\'smd_tag_with_filtered\').selectInput(\'smd_tag_multisel\',$withselopts, \'\', true, \' onchange=\"smd_tags_seledit(this); return false;\"\').n.fInput(\'submit\', \'\', gTxt(\'go\'), \'publish\').\'</div>\'\n                .n. \'<div id=\"smd_tag_multi_placeholder\"></div><a href=\"#\" id=\"smd_tags_report_toggler\">\'.gTxt(\'smd_tag_recent_report\').\'</a>\'\n                .n. \'</div>\'\n                .n. \'</fieldset>\'\n                .n. \'</form>\';\n\n            // Report popup\n            echo n. \'<div id=\"smd_tag_report_pane\"><div>\'.href(\'&#215;\', \'#close\', \' class=\"smd_tags_btn_ok\" role=\"button\" title=\"\'.gTxt(\'close\').\'\" aria-label=\"\'.gTxt(\'close\').\'\"\').\'<h3>\'.gTxt(\'smd_tag_report_lbl\').\'</h3>\'.$report.\'</div></div>\'\n                .n. \'<form method=\"post\" id=\"smd_tag_postit\" action=\"?event=smd_tags\">\'\n                .n. hInput(\'step\', \'\')\n                .n. hInput(\'smd_tag_id\', $smd_tag_id)\n                .n. hInput(\'smd_tag_name\', \'\')\n                .n. hInput(\'smd_tag_oname\', $smd_tag_name)\n                .n. hInput(\'smd_tag_parent\', \'\')\n                .n. hInput(\'smd_tag_cat\', \'\')\n                .n. hInput(\'smd_tag_title\', \'\')\n                .n. hInput(\'smd_tag_type\', \'\')\n                .n. hInput(\'smd_tag_description\', \'\')\n                .n. tInput()\n                .n. \'</form>\';\n\n            // Tag create/edit row\n            echo n.hed(gTxt(\'smd_tag_create_lbl\'), 2)\n                .n.\'<div class=\"smd_tags_new\">\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label for=\"smd_tags_newtitle\">\'.gTxt(\'title\').\'</label>\'\n                        . fInput(\'text\', \'smd_tags_newtitle\', $smd_tag_title, \'\', \'\', \'\', \'\', \'\', \'smd_tags_newtitle\')\n                        . \'<label for=\"smd_tags_newname\">\'.gTxt(\'name\').\'</label>\'\n                        . fInput(\'text\', \'smd_tags_newname\', $smd_tag_name, \'\', \'\', \'\', \'\', \'\', \'smd_tags_newname\')\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label for=\"smd_tags_description\">\'.gTxt(\'description\').\'</label>\'\n                        . text_area(\'smd_tags_description\', $desch, $descw, $smd_tag_description, \'smd_tags_description\')\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label>\'.gTxt(\'parent\').\'</label>\'\n                        . \'<span class=\"smd_tags_showpars\"></span>\'\n                        . (($clink)\n                            ? n. \'<label>\'.gTxt(\'smd_tag_clink_lbl\').\'</label>\'\n                                .n. \'<span class=\"smd_tags_linkcat\"></span>\'\n                            : \'\'\n                        )\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        .n. $btnCreate\n                        .n. $btnSave\n                        .n. $btnDelete\n                    .n.\'</div>\'\n                .n.\'</div>\';\n\n            // The tags themselves\n            echo \'<div class=\"smd_tags_pool\">\'\n                    .n. hed(gTxt(\'smd_tag_pool_lbl\'), 2)\n                    .n. \'<div class=\"smd_tags_type_selection\">\' . $radios .n. \'</div>\'\n                    .n. \'<div class=\"smd_tags_showlist\">\' . $tagout[$smd_tag_type] .n. \'</div>\'\n                .n.\'</div>\'\n                .n.\'</div>\';\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo n. startTable()\n                .n. tr(\n                    tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                        .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                        .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .n. tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .n. endTable();\n\n        } else {\n            // Prefs not installed\n            echo n. startTable()\n                .n. tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .n. tr(tda($btnInstall, $btnStyle))\n                .n. endTable();\n        }\n    } else {\n        // Tables not installed\n        echo n. startTable()\n            .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .n. tr(tda($btnInstallTbl, $btnStyle))\n            .n. endTable();\n    }\n\n    echo \'</div>\';\n}\n\n// ------------------------\n// Store the tag that is currently being edited/created\nfunction smd_tag_save() {\n    // Defer doSlash of description until after Textile\'s had a go\n    extract(doSlash(gpsa(array(\'smd_tag_oname\', \'smd_tag_name\', \'smd_tag_title\', \'smd_tag_parent\', \'smd_tag_cat\', \'smd_tag_type\', \'smd_tag_id\'))));\n    include_once txpath.\'/publish.php\'; // for parse()\n\n    $smd_tag_description = $smd_tags_desctile = ps(\'smd_tag_description\');\n\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_t_mdelim\', \'smd_tag_t_desc_textile\'), 1);\n    $mdelim = $ctrls[\'smd_tag_t_mdelim\'][\'val\'];\n    $txt_desc = $ctrls[\'smd_tag_t_desc_textile\'][\'val\'];\n\n    $message = $report = \'\';\n    $missing = 0;\n    $ok = $notok = array();\n    $smd_tag_parent = (!empty($smd_tag_parent)) ? $smd_tag_parent : \'root\';\n    $smd_tag_name = trim($smd_tag_name);\n\n    $textile = new \\Textpattern\\Textile\\Parser();\n    $smd_tags_desctile = doSlash((($txt_desc) ? $textile->parse(parse($smd_tag_description)) : parse($smd_tag_description)));\n    $smd_tag_description = doSlash($smd_tag_description);\n\n    // Can\'t use safe_upsert() because the WHERE is for ID AND type\n    if (empty($smd_tag_id)) {\n        // Create\n        if ($smd_tag_name == \'\' && $smd_tag_title == \'\') {\n            $message = array(gTxt(\'smd_tag_no_name\', array(\'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n        } else {\n            $alltagnam = do_list($smd_tag_name, $mdelim);\n            $alltagttl = do_list($smd_tag_title, $mdelim);\n            $numtags = count($alltagnam);\n\n            foreach ($alltagnam as $idx => $theTag) {\n                if ($theTag == \'\') {\n                    $missing++;\n                }\n\n                $theTtl = (isset($alltagttl[$idx]) && $alltagttl[$idx] != \'\') ? $alltagttl[$idx] : $theTag;\n                $theNam = ($theTag == \'\') ? sanitizeForUrl($theTtl) : sanitizeForUrl($theTag);\n\n                $exists = safe_field(\'name\', SMD_TAG, \"name = \'$theNam\' AND type = \'$smd_tag_type\'\");\n                $parex = safe_field(\'name\', SMD_TAG, \"name = \'$smd_tag_parent\' AND type = \'$smd_tag_type\'\");\n\n                if ($exists) {\n                    if ($numtags > 1) {\n                        $notok[$idx] = \'smd_tag_already_exists\';\n                    } else {\n                        $message = array(gTxt(\'smd_tag_exists\', array(\'{name}\' => $theNam, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n                    }\n                } else {\n                    $smd_tag_id = safe_insert(\n                        SMD_TAG,\n                        \"name=\'$theNam\', title=\'$theTtl\', description=\'$smd_tag_description\', desc_html=\'$smd_tags_desctile\', parent=\'\".(($parex) ? $smd_tag_parent : \'root\').\"\', type=\'$smd_tag_type\'\"\n                    );\n                    if ($smd_tag_id > 0) {\n                        if ($smd_tag_cat) {\n                            safe_insert(SMD_TAGC,\"tag_id=\'$smd_tag_id\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$smd_tag_cat\' AND type=\'$smd_tag_type\')\");\n                        }\n                        if ($numtags > 1) {\n                            $ok[] = $theNam;\n                        } else {\n                            $message = gTxt(\'smd_tag_created\', array(\'{name}\' => $theNam, \'{type}\' => ucfirst($smd_tag_type)));\n                        }\n                    } else {\n                        if ($numtags > 1) {\n                            $notok[$idx] = \'smd_tag_unable_to_create\';\n                        } else {\n                            $message = array(gTxt(\'smd_tag_not_created\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n                        }\n                    }\n                }\n            }\n\n            // Generate the report\n            if ($ok) {\n                $report = gTxt(\'smd_tag_created_rep_lbl\').join(\', \', $ok).\'.\';\n            }\n            if ($notok) {\n                $msgs = array();\n                foreach ($notok as $idx => $reason) {\n                    $msgs[] = $alltagnam[$idx].sp.\'(\'.gTxt($reason).\')\';\n                }\n                $report .= br.br.gTxt(\'smd_tag_not_created_rep_lbl\').join(\', \', $msgs);\n                $report .= ($missing) ? br.br.gTxt(\'smd_tag_missing_rep_lbl\').$missing : \'\';\n            }\n        }\n    } else {\n        // Update - no need to maintain referential integrity unlike txp_cats\n        // since tags are stored against item IDs\n        $smd_tag_title = (empty($smd_tag_title)) ? $smd_tag_name : $smd_tag_title;\n        $smd_tag_name = (empty($smd_tag_name)) ? sanitizeForUrl($smd_tag_title) : sanitizeForUrl($smd_tag_name);\n        $existing_id = safe_field(\'id\', SMD_TAG, \"name = \'$smd_tag_name\' and type = \'$smd_tag_type\'\");\n\n        if ($existing_id and $existing_id != $smd_tag_id) {\n            $message = array(gTxt(\'smd_tag_exists\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n        } else {\n            if (safe_update(\n                    SMD_TAG,\n                    \"name=\'$smd_tag_name\', title=\'$smd_tag_title\', parent=\'$smd_tag_parent\', description=\'$smd_tag_description\', desc_html=\'$smd_tags_desctile\'\",\n                    \"type=\'$smd_tag_type\' AND id=$smd_tag_id\"\n                )) {\n                safe_update(SMD_TAG, \"parent=\'$smd_tag_name\'\", \"parent=\'$smd_tag_oname\' AND type=\'$smd_tag_type\'\");\n                if ($smd_tag_cat) {\n                    safe_upsert(SMD_TAGC, \"cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$smd_tag_cat\' AND type=\'$smd_tag_type\')\", \"tag_id = \'$smd_tag_id\'\");\n                } else {\n                    safe_delete(SMD_TAGC, \"tag_id=$smd_tag_id\");\n                }\n                $message = gTxt(\'smd_tag_updated\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type)));\n            } else {\n                $message = array(gTxt(\'smd_tag_not_updated\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n            }\n        }\n    }\n\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    // Force smd_tag_id to the new ID so it can be immediately edited\n    $_POST[\'smd_tag_id\'] = $smd_tag_id;\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Store new tag - uses smd_tag_save without the ID/old_name\nfunction smd_tag_create() {\n//  extract(doSlash(gpsa(array(\'smd_tag_name\', \'smd_tag_title\', \'smd_tag_description\', \'smd_tag_parent\', \'smd_tag_type\'))));\n    unset($_POST[\'smd_tag_id\']);\n    smd_tag_save();\n}\n\n// ------------------------\n// Check if the passed tag exists.\n// Return its ID if it does or insert it if it doesn\'t. Optionally assign it to a parent tag / Txp category\nfunction smd_tag_getsert($tag_name, $tag_type, $tag_title=\'\', $tag_parent=\'\', $tag_cat=\'\', $tag_desc=\'\', $force_parent=false, $force_cat=false) {\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_p_linkcat\'), 1);\n    $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n    $tag_title = ($tag_title == \'\') ? $tag_name : $tag_title;\n    $tag_title = doSlash($tag_title);\n    $tag_name = doSlash(sanitizeForUrl($tag_name));\n    $tag_type = doSlash($tag_type);\n    $tag_desc = doSlash($tag_desc);\n    $tag_parent = ($tag_parent) ? doSlash(sanitizeForUrl($tag_parent)) : \'root\';\n    $tag_cat = doSlash($tag_cat);\n\n    $ret = safe_field(\'id\', SMD_TAG, \"name = \'$tag_name\' AND type = \'$tag_type\'\");\n    if (!$ret) {\n        $ret = safe_insert(SMD_TAG, \"name=\'\".$tag_name.\"\', title=\'$tag_title\', parent=\'$tag_parent\', type=\'$tag_type\', description=\'$tag_desc\', desc_html=\'$tag_desc\'\");\n        rebuild_tree_full($tag_type, SMD_TAG);\n    } elseif($force_parent) {\n        $upd = safe_update(SMD_TAG, \"parent=\'$tag_parent\'\", \"id=\'\".doSlash($ret).\"\'\");\n        rebuild_tree_full($tag_type, SMD_TAG);\n    }\n\n    // Assign tag to category if required\n    $ret = doSlash($ret);\n    if ($clink && $tag_cat) {\n        $catid = safe_field(\'cat_id\', SMD_TAGC, \"tag_id=\'$ret\'\");\n\n        if ($catid && $force_cat) {\n            safe_delete(SMD_TAGC, \"cat_id=\'\".$catid.\"\' AND tag_id=\'\".$ret.\"\'\");\n            $catid = false;\n        }\n\n        if(!$catid) {\n            safe_insert(SMD_TAGC, \"tag_id=\'$ret\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$tag_cat\' AND type=\'$tag_type\')\");\n        }\n    }\n\n    return $ret;\n}\n\n// ------------------------\n// Link the passed tags array with the given item ID\nfunction smd_tag_link($itemid, $tagarr, $tag_type) {\n    assert_int($itemid);\n\n    // TODO: Remove any existing tags?? Perhaps make this an option?\n//  safe_delete(SMD_TAGU, \'item_id=\"\'.$itemid.\'\" AND type=\"\'.doSlash($tag_type).\'\"\');\n    $tag_type = doSlash($tag_type);\n\n    $ctr = $ret = 0;\n    foreach ($tagarr as $theTag) {\n        if ($theTag == \'\') continue;\n        $theTag = doSlash($theTag); // Probably not necessary\n\n        $exists = safe_field(\'item_id\', SMD_TAGU, \"tag_id = \'$theTag\' AND item_id = \'$itemid\' AND type = \'$tag_type\'\");\n\n        if (!$exists) {\n            $ret = safe_insert(SMD_TAGU, \'tag_id = \"\'.$theTag.\'\", item_id=\"\'.$itemid.\'\", type=\"\'.$tag_type.\'\"\');\n            if ($ret) {\n                $ctr++;\n            }\n        }\n    }\n\n    return $ctr;\n}\n\n// ------------------------\n// Delete the given tag(s). Respects noclobber/tree/orphans depending on prefs\nfunction smd_tags_delete() {\n    extract(doSlash(gpsa(array(\'smd_tag_id\', \'smd_tag_name\', \'smd_tag_type\'))));\n\n    $message = $msgExtra = $report = \'\';\n    $ok = $notok = array();\n\n    $pref = smd_tags_pref_get(array(\'smd_tag_t_delused\', \'smd_tag_t_deltree\'));\n    $delu = $pref[\'smd_tag_t_delused\'][\'val\'];\n    $delt = $pref[\'smd_tag_t_deltree\'][\'val\'];\n\n    $smd_tag_id = do_list($smd_tag_id);\n    $smd_tag_name = do_list($smd_tag_name);\n\n    $with_child = safe_column(\'id\', SMD_TAG, \"id IN (\'\".join(\"\',\'\", $smd_tag_id).\"\') AND (((rgt-lft-1)/2) > 0) AND type=\'$smd_tag_type\'\");\n    $used_tags = safe_column(\'tag_id\', SMD_TAGU, \"tag_id IN (\'\".join(\"\',\'\", $smd_tag_id).\"\') AND type=\'$smd_tag_type\'\");\n\n    $to_del = $smd_tag_id;\n    // Protect used and child tags if required\n    if ($delu == 0) {\n        $to_del = array_diff($smd_tag_id, $with_child, $used_tags);\n        foreach (array_merge($with_child, $used_tags) as $delid) {\n            $notok[] = $smd_tag_name[array_search($delid, $smd_tag_id)];\n        }\n    }\n\n    $orphans = array();\n    if ($delt) {\n        // Add child tags if they are to be deleted\n        $ids = $to_del;\n        foreach ($to_del as $delid) {\n            $row = safe_row(\'*\', SMD_TAG, \"id = \'$delid\' AND type=\'$smd_tag_type\'\");\n            $ids = array_merge($ids, safe_column(\'id\', SMD_TAG, \"type = \'$smd_tag_type\' AND (lft BETWEEN \".$row[\'lft\'].\" AND \".$row[\'rgt\'].\")\"));\n        }\n        $to_del = array_unique($ids);\n        $msgExtra = gTxt(\'smd_tag_children_and\');\n    } else {\n        // Collect the potential orphans and prepare to promote them\n        foreach ($to_del as $delid) {\n            $children = safe_column(\'id\', SMD_TAG, \"parent = \'\".$smd_tag_name[array_search($delid, $smd_tag_id)].\"\' AND type=\'$smd_tag_type\'\");\n            $children = array_diff($children, $to_del);\n            if ($children) {\n                $orphans += $children;\n            }\n        }\n    }\n\n    // Everything\'s prepped: begin the deletion\n    $numdel = count($to_del);\n\n    // Make a note of what we\'re about to do for the report\n    $all_dels = safe_rows(\'id, name\', SMD_TAG, \"id IN (\" . join(\',\', quote_list($to_del)) . \")\");\n    $all_names = array();\n    foreach ($all_dels as $aname) {\n        $all_names[$aname[\'id\']] = $aname[\'name\'];\n    }\n    foreach ($to_del as $delid) {\n        if (isset($all_names[$delid])) {\n            $ok[] = $all_names[$delid];\n        }\n    }\n\n    // First get rid of the direct tags\n    $where = \" IN (\'\".join(\"\',\'\", $to_del).\"\')\";\n    $ret = safe_delete(SMD_TAG, \'id\'.$where.\" AND type=\'$smd_tag_type\'\");\n    if ($ret) {\n        if (!$delt) {\n            // Promote the orphans if required\n            $orphlist = safe_column(\'name\', SMD_TAG, \"type=\'$smd_tag_type\' AND id IN (\'\".join(\"\',\'\", $orphans).\"\')\");\n            safe_update(SMD_TAG, \"parent=\'root\'\", \"type=\'$smd_tag_type\' AND id IN (\'\".join(\"\',\'\", $orphans).\"\')\");\n        }\n        // Remove references to any deleted tags in articles and linked categories\n        safe_delete(SMD_TAGU, \'tag_id\'.$where.\" AND type=\'$smd_tag_type\'\");\n        safe_delete(SMD_TAGC, \'tag_id\'.$where);\n\n        // Force all smd_tag_ boxes clear in the form\n        $_POST[\'smd_tag_id\'] = $_POST[\'smd_tag_oname\'] = $_POST[\'smd_tag_name\'] = $_POST[\'smd_tag_title\'] = $_POST[\'smd_tag_parent\'] = \'\';\n    }\n\n    // Build and generate the final report\n    if ($numdel > 1) {\n        $message = gTxt(\'smd_tag_multi_delete\', array(\'{number}\' => $numdel));\n        if ($ok) {\n            $report = gTxt(\'smd_tag_deleted_lbl\').join(\', \', $ok).\'.\';\n        }\n        if ($notok) {\n            $report .= br.br.gTxt(\'smd_tag_err_in_use_lbl\').join(\', \', $notok);\n        }\n        if ($orphans) {\n            $report .= br.br.gTxt(\'smd_tag_err_orphaned_lbl\').join(\', \', $orphlist);\n        }\n    } else {\n        if ($ok) {\n            $message = gTxt(\'smd_tag_deleted\', array(\'{type}\' => ucfirst($smd_tag_type), \'{name}\' => join(\', \', $ok))).$msgExtra;\n        } elseif ($notok) {\n            $message = gTxt(\'smd_tag_in_use\', array(\'{type}\' => ucfirst($smd_tag_type), \'{name}\' => join(\', \', $notok)));\n        }\n    }\n\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Make the passed tag a child of the given parent (if possible)\nfunction smd_tag_assign_parent($parid, $childid, $tag_type, $childnam, $rebuild = true) {\n    assert_int($childid);\n    assert_int($parid);\n    $status = array();\n    if ($childid == $parid) {\n        $status[] = \'err_self_parent\';\n    } else {\n        $exists = safe_field(\'name\', SMD_TAG, \"id = \'\".doSlash($parid).\"\' AND type = \'\".doSlash($tag_type).\"\'\");\n        $exists = ($exists == \'\') ? \'root\' : $exists;\n        $ret = safe_update(SMD_TAG, \"parent=\'\".doSlash($exists).\"\'\", \"id=\'\".$childid.\"\'\");\n        if ($rebuild) {\n            // Doesn\'t hurt to rebuild it anyway, even if the assignment fails\n            rebuild_tree_full($tag_type, SMD_TAG);\n        }\n        if ($ret) {\n            $status[] = \'parent_linked\';\n        } else {\n            $status[] = \'parent_not_linked\';\n        }\n    }\n\n    return $status;\n}\n\n\n// ------------------------\n// Assign a parent to a bunch of tags at once\n// Cheating code really; just calls the smd_tag_assign_parent for each tag but delays the\n// tree rebuild until the end for speed\nfunction smd_tags_multi_set_parent() {\n    extract(doSlash(gpsa(array(\'smd_tag_type\', \'smd_tag_id\', \'smd_tag_name\', \'smd_tag_extra\'))));\n\n    $message = $report = \'\';\n    $ok = $notok = array();\n    $theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));\n\n    foreach ($theList as $idx => $name) {\n        $status = smd_tag_assign_parent($smd_tag_extra, $idx, $smd_tag_type, $name, false);\n        if (in_array(\'parent_linked\', $status)) {\n            $ok[] = $name;\n        } else {\n            if (($pos = array_search(\'parent_not_linked\', $status)) !== false) {\n                unset($status[$pos]);\n            }\n            $notok[$idx] = $status;\n        }\n    }\n\n    if ($ok) {\n        $parnam = safe_field(\'name\', SMD_TAG, \"id=\'\".doSlash($smd_tag_extra).\"\'\");\n        $parnam = ($parnam == \'\') ? \'root\' : $parnam ;\n        $report = gTxt(\'smd_tag_parent_linked_lbl\', array(\'{parent}\' => $parnam)).join(\', \', $ok).\'.\';\n    }\n    if ($notok) {\n        $msgs = array();\n        foreach ($notok as $idx => $reasons) {\n            $rsn = array();\n            foreach ($reasons as $reason) {\n                $rsn[] = gTxt($reason);\n            }\n            $msgs[] = $theList[$idx].sp.\'(\'.join(\'/\', $rsn).\')\';\n        }\n        $report .= br.br.gTxt(\'smd_tag_not_parent_linked_lbl\').join(\', \', $msgs);\n    }\n\n    // Rebuild the tree now all the manipulation has been done\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Links the passed tag with the given category\nfunction smd_tag_assign_category($parnam, $childid, $cat_type, $childnam) {\n    assert_int($childid);\n    $status = array();\n\n    // Unlink if parent category name is empty\n    if ($parnam == \'\') {\n        safe_delete(SMD_TAGC, \"tag_id=\'\".doSlash($childid).\"\'\");\n        $status[] = \'category_unlinked\';\n    } else {\n        $exists = safe_field(\'id\', \'txp_category\', \"name = \'\".doSlash($parnam).\"\' AND type = \'\".doSlash($cat_type).\"\'\");\n        if ($exists) {\n            safe_delete(SMD_TAGC, \"tag_id=\'\".doSlash($childid).\"\'\");\n            $ret = safe_insert(SMD_TAGC, \"cat_id=\'\".doSlash($exists).\"\', tag_id = \'\".doSlash($childid).\"\'\");\n            if ($ret) {\n                $status[] = \'category_linked\';\n            } else {\n                $status[] = \'category_unlinked\';\n            }\n        }\n    }\n    return $status;\n}\n\n\n// ------------------------\n// Assign a bunch of tags to a category\n// Cheatingly calls smd_tag_assign_category multiple times\nfunction smd_tags_multi_catlink() {\n    extract(doSlash(gpsa(array(\'smd_tag_type\', \'smd_tag_id\', \'smd_tag_name\', \'smd_tag_extra\'))));\n\n    $message = $report = \'\';\n    $ok = $notok = array();\n\n    $theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));\n\n    // Add in any child elements since they all need to take the category too\n    foreach ($theList as $idx => $name) {\n        $row = safe_row(\'*\', SMD_TAG, \"id=\'\".doSlash($idx).\"\'\");\n        $rs = safe_rows(\'id, name\', SMD_TAG, \"type = \'\".doSlash($smd_tag_type).\"\' AND (lft BETWEEN \" .$row[\'lft\']. \" AND \" .$row[\'rgt\']. \")\");\n        if ($rs) {\n            foreach ($rs as $rec) {\n                $theList[$rec[\'id\']] = $rec[\'name\'];\n            }\n        }\n    }\n\n    foreach ($theList as $idx => $name) {\n        $status = smd_tag_assign_category($smd_tag_extra, $idx, $smd_tag_type, $name);\n        if (in_array(\'category_linked\', $status)) {\n            $ok[] = $name;\n        } elseif (in_array(\'category_unlinked\', $status)) {\n            $notok[$idx] = $status;\n        }\n    }\n\n    if ($ok) {\n        $report = gTxt(\'smd_tag_category_linked_lbl\', array(\'{category}\' => $smd_tag_extra)).join(\', \', $ok).\'.\';\n    }\n    if ($notok) {\n        $msgs = array();\n        foreach ($notok as $idx => $reasons) {\n            $msgs[] = $theList[$idx];\n        }\n        $report .= br.br.gTxt(\'smd_tag_category_unlinked_lbl\').join(\', \', $msgs);\n    }\n\n    smd_tags_manage($message, $report);\n}\n\n/**\n * Allow tags to be imported from other tagging / unlimited cats plugins, or txp fields.\n *\n * @param      string  $message  The message to display\n * @param      string  $report   The report to display\n */\nfunction smd_tags_sync($message = \'\', $report = \'\')\n{\n    global $plugins;\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    extract(doSlash(gpsa(array(\'smd_tags_sync_type\', \'smd_tags_sync_cfs\', \'smd_tags_sync_cfs_delim\', \'smd_tags_sync_section\', \'smd_tags_sync_parent\', \'smd_tags_delete_orig\', \'smd_tags_import_tag_parent\', \'smd_tags_import_cat_parent\', \'smd_tags_import_force_parent\', \'smd_tags_import_force_cat\', \'smd_tags_do_import\'))));\n\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_p_linkcat\', \'smd_tag_t_enrep\'), 1);\n    $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n    $showrep = $report && $ctrls[\'smd_tag_t_enrep\'][\'val\'];\n\n    pagetop(gTxt(\'smd_tag_sync_lbl\'),$message);\n    extract(smd_tags_buttons());\n\n    $plug_tt = (is_array($plugins) && in_array(\'tru_tags\',$plugins));\n    $plug_uc = (is_array($plugins) && in_array(\'rss_unlimited_categories\',$plugins));\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_prefs);\n    $numReqPrefs = count($smd_tag_prefs);\n    $numPrefs = count($prefset);\n\n    // Perform the import via AJAX due to the quantity of tags that may be involved\n    if ($smd_tags_do_import) {\n        $impopts = array(\n            \'smd_tags_sync_type\' => $smd_tags_sync_type,\n            \'smd_tags_sync_cfs\' => $smd_tags_sync_cfs,\n            \'smd_tags_sync_cfs_delim\' => $smd_tags_sync_cfs_delim,\n            \'smd_tags_sync_section\' => $smd_tags_sync_section,\n            \'smd_tags_sync_parent\' => $smd_tags_sync_parent,\n            \'smd_tags_delete_orig\' => $smd_tags_delete_orig,\n            \'smd_tags_import_tag_parent\' => $smd_tags_import_tag_parent,\n            \'smd_tags_import_cat_parent\' => $smd_tags_import_cat_parent,\n            \'smd_tags_import_force_parent\' => $smd_tags_import_force_parent,\n            \'smd_tags_import_force_cat\' => $smd_tags_import_force_cat,\n        );\n        smd_tags_import($impopts);\n    }\n\n    $qs = array(\n        \"event\" => \"smd_tags\",\n    );\n\n    $qsVars = \"index.php\".join_qs($qs);\n\n    echo script_js(<<<EOS\njQuery(function() {\n    function smd_tagRestrict(typ) {\n        grabcat = jQuery(\"#smd_tags_import_cat_parent option:selected\").val();\n        var smd_link_mode = ((\'{$clink}\'==\'0\') ? \'2\' : ((grabcat==\'\') ? \'2\' : \'\'));\n\n        if (grabcat != \"undefined\" || grabcat != \'\') {\n            jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: \'smd_tag_parent\', type: typ, cat: grabcat, listonly: true, html_id: \'smd_tags_import_tag_parent\', link_mode: smd_link_mode, _txp_token: textpattern._txp_token },\n                function(data) {\n                    jQuery(\"#smd_tags_import_tag_parent_holder\").html(data);\n                    jQuery(\"#smd_tags_import_tag_parent_holder select option[value=\'{$smd_tags_import_tag_parent}\']\").prop(\"selected\", true);\n                }\n            );\n        }\n    }\n\n    smd_tagRestrict(\'article\');\n\n    if (\'{$clink}\' == \'1\') {\n        jQuery(\"#smd_tags_import_cat_parent\").change(function() {\n            smd_tagRestrict(\'article\');\n        });\n    }\n    if (\'{$showrep}\' == \'1\') {\n        smd_tags_toggle_report();\n    }\n\n    jQuery(\'input[name=\"smd_tags_sync_type\"]\').change(function() {\n        if (jQuery(this).filter(\':checked\').val() == \'3\') {\n            jQuery(\'.smd_tags_sync_txp\').show();\n        } else {\n            jQuery(\'.smd_tags_sync_txp\').hide();\n        }\n    }).change();\n\n    jQuery(\'#smd_tags_report_toggler, .smd_tags_btn_ok\').on(\'click\', smd_tags_toggle_report_handler);\n});\n\n/**\n * jQuery callback for displaying tag report.\n */\nfunction smd_tags_toggle_report_handler(ev) {\n    ev.preventDefault();\n    smd_tags_toggle_report();\n}\n\nfunction smd_tags_toggle_report() {\n    jQuery(\"#smd_tag_report_pane\").toggle(\'fast\');\n}\nEOS\n    );\n\n    // The tag sync preferences\n    if (smd_tags_table_exist()) {\n        // Tables installed\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            echo \'<form method=\"post\" id=\"smd_syncit\" action=\"?event=smd_tags&step=smd_tags_sync\">\'.\n                n.\'<div class=\"txp-layout\">\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                n. hed(gTxt(\'smd_tag_sync_lbl\').sp.$btnHelp, 1).\n                n. \'</div>\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                graf(\n                    $btnManage\n                    .n.$btnPrefs\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n            echo n. \'<div id=\"smd_tag_report_pane\">\'\n                .n. \'<div>\'\n                .n. href(\'&#215;\', \'#close\', \' class=\"smd_tags_btn_ok\" role=\"button\" title=\"\'.gTxt(\'close\').\'\" aria-label=\"\'.gTxt(\'close\').\'\"\')\n                .n. \'<h3>\', gTxt(\'smd_tag_report_lbl\'), \'</h3>\'\n                .$report\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-1col\">\';\n\n            $lblStyle = \' class=\"pref-label\"\';\n\n            echo \'<a id=\"smd_tags_report_toggler\" href=\"#\">\'.gTxt(\'smd_tag_recent_report\').\'</a>\';\n            echo \'<section class=\"txp-prefs-group\">\';\n            echo hed(gTxt(\'smd_tag_sync_plugin_opts\'), 3);\n            $rset = array();\n\n            if ($plug_tt) {\n                $rset[0] = gTxt(\'smd_tag_sync_type1\');\n            }\n\n            if ($plug_uc) {\n                $rset[1] = gTxt(\'smd_tag_sync_type2\');\n            }\n\n            $rset[2] = gTxt(\'smd_tag_sync_type3\');\n            $rset[3] = gTxt(\'smd_tag_sync_type4\');\n\n            $rsel = 0;\n\n            if (empty($smd_tags_sync_type) || $plug_tt) {\n                $rsel = 0;\n            }\n\n            if ($smd_tags_sync_type == 1 || ($plug_uc && !$plug_tt)) {\n                $rsel = 1;\n            } elseif ($smd_tags_sync_type == 2 || (!$plug_uc && !$plug_tt)) {\n                $rsel = 2;\n            } elseif ($smd_tags_sync_type == 3 || (!$plug_uc && !$plug_tt && !$smd_tags_sync_type == 2)) {\n                $rsel = 3;\n            }\n\n            echo inputLabel(\n                \'smd_tags_sync_type\',\n                radioSet($rset, \'smd_tags_sync_type\', $rsel),\n                \'smd_tag_sync_type_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_type\',\n                )\n            );\n\n            // Txp category / custom_field import options\n            $cfs = getCustomFields();\n\n            echo ($cfs)\n                ? inputLabel(\n                    \'smd_tags_sync_cfs\',\n                    selectInput(\'smd_tags_sync_cfs\', $cfs, $smd_tags_sync_cfs, true),\n                    \'smd_tag_sync_cfs_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field smd_tags_sync_txp\',\n                        \'id\'    => \'prefs-smd_tags_sync_cfs\',\n                    )\n                )\n                : \'\';\n\n            echo ($cfs)\n                ? inputLabel(\n                    \'smd_tags_sync_cfs_delim\',\n                    fInput(\n                        \'text\',\n                        \'smd_tags_sync_cfs_delim\',\n                        ($smd_tags_sync_cfs_delim ? $smd_tags_sync_cfs_delim : \',\'),\n                        \'\',\n                        \'\',\n                        \'\',\n                        INPUT_XSMALL),\n                    \'smd_tag_sync_cfs_delim_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field smd_tags_sync_txp\',\n                        \'id\'    => \'prefs-smd_tags_sync_cfs_delim\',\n                    )\n                )\n                : \'\';\n\n            // Section import\n            $rs = safe_column(\'name\', \'txp_section\', \"name != \'default\'\");\n\n            if ($rs) {\n                $widget = selectInput(\'smd_tags_sync_section\', $rs, $smd_tags_sync_section, true);\n            } else {\n                $widget = gTxt(\'smd_tag_no_sections\');\n            }\n\n            echo inputLabel(\n                \'smd_tags_sync_section\',\n                $widget,\n                \'smd_tag_sync_section_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_section\',\n                )\n            );\n\n            // Start at parent category (rss_uc only)\n            // The list is populated anyway even if the plugin isn\'t active because $rsCats is used later\n            if ($clink) {\n                $rsCats = getTree(\'root\', \'article\');\n\n                if ($rsCats) {\n                    $catlist = treeSelectInput(\'smd_tags_sync_parent\', $rsCats, $smd_tags_sync_parent, \'smd_tags_sync_parent\', 35)\n                        .(($plug_tt) ? gTxt(\'smd_tag_rss_uc_only\') : \'\');\n                } else {\n                    $catlist = gTxt(\'smd_tag_no_parent\');\n                }\n\n                echo inputLabel(\n                    \'smd_tags_sync_parent\',\n                    $catlist,\n                    \'smd_tag_sync_parent_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field\',\n                        \'id\'    => \'prefs-smd_tags_sync_parent\',\n                    )\n                );\n            }\n\n            // Delete original\n            echo inputLabel(\n                \'smd_tags_delete_orig\',\n                yesnoRadio(\'smd_tags_delete_orig\', (int)$smd_tags_delete_orig).n.gTxt(\'smd_tag_rss_uc_tru_tags_only\'),\n                \'smd_tag_sync_delete_orig_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_delete_orig\',\n                )\n            );\n\n            echo \'</section>\';\n            echo hed(gTxt(\'smd_tag_sync_import_opts\'), 3);\n            echo \'<section class=\"txp-prefs-group\">\';\n\n            // Assign to category\n            if ($clink) {\n                if ($rsCats) {\n                    $catlist = treeSelectInput(\'smd_tags_import_cat_parent\', $rsCats, $smd_tags_import_cat_parent, \'smd_tags_import_cat_parent\', 35);\n                } else {\n                    $catlist = gTxt(\'smd_tag_no_parent\');\n                }\n\n                echo inputLabel(\n                    \'smd_tags_import_cat_parent\',\n                    $catlist .br. checkbox(\'smd_tags_import_force_cat\', 1, (int)$smd_tags_import_force_cat, 0, \'smd_tags_import_force_cat\').n.\'<label for=\"smd_tags_import_force_cat\">\'.gTxt(\'smd_tag_sync_force_cat_lbl\').\'</label>\',\n                    \'smd_tag_sync_parent_cat_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field\',\n                        \'id\'    => \'prefs-smd_tags_sync_parent_cat\',\n                    )\n                );\n            }\n\n            // Assign to parent tag\n            echo inputLabel(\n                \'smd_tags_import_tag_parent\',\n                \'<div id=\"smd_tags_import_tag_parent_holder\"></div>\'\n                    .n.checkbox(\'smd_tags_import_force_parent\', 1, (int)$smd_tags_import_force_parent, 0, \'smd_tags_import_force_parent\').n.\'<label for=\"smd_tags_import_force_parent\">\'.gTxt(\'smd_tag_sync_force_parent_lbl\').\'</label>\',\n                \'smd_tag_sync_parent_tag_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_parent_tag\',\n                )\n            );\n\n            // Report viewer\n            echo gTxt(\'smd_tag_import_results_pt1\') . \'<span id=\"smd_tags_import_icurr\"></span><span id=\"smd_tags_import_itot\"></span> \'\n                .br. gTxt(\'smd_tag_import_results_pt2\') . \'<span id=\"smd_tags_import_lnk_curr\"></span>\';\n            echo \'</section>\';\n\n            echo n.$btnSyncGo;\n            echo tInput()\n                . \'</div></form>\';\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo startTable()\n                .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                    .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                    .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .n. tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .n. endTable();\n        } else {\n            // Prefs not installed\n            echo startTable()\n                .n. tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .n. tr(tda($btnInstall, $btnStyle))\n                .n. endTable();\n        }\n    } else {\n        // Tables not installed\n        echo startTable()\n            .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'psmd_tag_refs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .n. tr(tda($btnInstallTbl, $btnStyle))\n            .n. endTable();\n    }\n\n    echo \'</div>\';\n}\n\n// ------------------------\n// Import tags from other places\nfunction smd_tags_import($smd_tag_options) {\n    extract($smd_tag_options);\n\n    $message = \'\';\n    $iparent = ($smd_tags_sync_parent) ? \" AND parent = \\\'\".doSlash($smd_tags_sync_parent).\"\\\' \" : \'\';\n\n    $rs = safe_rows(\'*\', \'textpattern\', (($smd_tags_sync_section) ? \"Section=\'\".doSlash($smd_tags_sync_section).\"\'\" : \'1=1\'));\n    $row_ctr = $tag_ctr = 0;\n    $total = count($rs);\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_t_enrep\'), 1);\n    $showrep = $ctrls[\'smd_tag_t_enrep\'][\'val\'];\n\n    echo \'<script type=\"text/javascript\">\';\n    echo \"var ictr = 0; var itot = {$total}; var lnk_ctr = 0;\";\n    echo \'jQuery(function() {\';\n    echo \'jQuery(\"#smd_tags_import_itot\").text(\"/\'.$total.\'\");\';\n    if ($rs) {\n        foreach ($rs as $row) {\n            switch ($smd_tags_sync_type) {\n                case \'0\':\n                    // tru_tags\n                    $idata = doSlash($row[\'Keywords\']);\n                    break;\n                case \'1\':\n                case \'2\':\n                    // rss_uc / Txp cats\n                    $idata = doSlash($row[\'ID\']);\n                    break;\n                case \'3\':\n                    // CFs\n                    $idata = (($smd_tags_sync_cfs && isset($row[\'custom_\'.$smd_tags_sync_cfs])) ? doSlash($row[\'custom_\'.$smd_tags_sync_cfs]) : \'\');\n                    break;\n            }\n            $ititle = doSlash($row[\'Title\']);\n            $artid = doSlash($row[\'ID\']);\n            echo <<<EOJS\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'smd_tags_import_one\',\n                smd_tags_sync_type: \'{$smd_tags_sync_type}\',\n                smd_tags_sync_id: \'{$artid}\',\n                smd_tags_sync_data: \'{$idata}\',\n                smd_tags_sync_title: \'{$ititle}\',\n                smd_tags_sync_parent: \'{$iparent}\',\n                smd_tags_sync_cfs: \'{$smd_tags_sync_cfs}\',\n                smd_tags_sync_cfs_delim: \'{$smd_tags_sync_cfs_delim}\',\n                smd_tags_import_tag_parent: \'{$smd_tags_import_tag_parent}\',\n                smd_tags_import_cat_parent: \'{$smd_tags_import_cat_parent}\',\n                smd_tags_import_force_parent: \'{$smd_tags_import_force_parent}\',\n                smd_tags_import_force_cat: \'{$smd_tags_import_force_cat}\',\n                smd_tags_delete_orig: \'{$smd_tags_delete_orig}\'\n            }, smd_tags_group_feedback);\nEOJS;\n        }\n        echo \'});\';\n        echo <<<EOJS\n        function smd_tags_group_feedback(data) {\n            ictr++;\n            jQuery(\'#smd_tags_import_icurr\').text(ictr);\n            jQuery(\'#smd_tag_report_pane\').append(jQuery(data).find(\'smd_tags_report\').attr(\'value\'));\n            lnk_ctr += (jQuery(data).find(\'smd_tags_link_ctr\').attr(\'value\')) * 1; // multiply by 1 to convert to number\n            jQuery(\'#smd_tags_import_lnk_curr\').text(lnk_ctr);\n            if (\'{$showrep}\' == \'1\' && ictr >= itot) {\n                smd_tags_toggle_report();\n            }\n        }\nEOJS;\n        echo \'</script>\';\n    }\n}\n\n// ------------------------\nfunction smd_tags_import_one() {\n    $smd_tags_sync_type = gps(\'smd_tags_sync_type\');\n    $smd_tags_sync_data = gps(\'smd_tags_sync_data\');\n    $smd_tags_sync_id = gps(\'smd_tags_sync_id\');\n    $smd_tags_sync_title = gps(\'smd_tags_sync_title\');\n    $smd_tags_sync_parent = gps(\'smd_tags_sync_parent\');\n    $smd_tags_sync_cfs = gps(\'smd_tags_sync_cfs\');\n    $smd_tags_sync_cfs_delim = gps(\'smd_tags_sync_cfs_delim\');\n    $smd_tags_import_tag_parent = gps(\'smd_tags_import_tag_parent\');\n    $smd_tags_import_cat_parent = gps(\'smd_tags_import_cat_parent\');\n    $smd_tags_import_force_parent = gps(\'smd_tags_import_force_parent\');\n    $smd_tags_imort_force_cat = gps(\'smd_tags_import_force_cat\');\n    $smd_tags_delete_orig = gps(\'smd_tags_delete_orig\');\n\n    $tag_ids = $tag_names = $keylist = array();\n\n    if (in_array($smd_tags_sync_type, array (\'0\', \'3\'))) {\n        // tru_tags / Txp CF\n        $dlm = ($smd_tags_sync_type == \'3\' && $smd_tags_sync_cfs_delim) ? $smd_tags_sync_cfs_delim : \',\';\n        $keys = do_list($smd_tags_sync_data, $dlm);\n        foreach ($keys as $key) {\n            if ($key==\'\') continue;\n            $keylist[] = array(\'name\' => $key, \'title\' => $key);\n        }\n    } elseif ($smd_tags_sync_type == \'1\') {\n        // rss_uc\n        $clause = \'tc.article_id = \'.doSlash($smd_tags_sync_data);\n        $keylist = getRows(\"SELECT c.name, c.title FROM \".PFX.\"textpattern_category as tc LEFT JOIN \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE \".$clause.$smd_tags_sync_parent);\n        $keylist = ($keylist) ? $keylist : array();\n    } elseif ($smd_tags_sync_type == \'2\') {\n        // txp cats\n        $clause = \'txt.ID = \'.doSlash($smd_tags_sync_data);\n        $keylist = getRows(\"SELECT c.name, c.title FROM \".PFX.\"textpattern as txt LEFT JOIN \".PFX.\"txp_category as c ON (txt.Category1 = c.name OR txt.Category2 = c.name) WHERE \".$clause.$smd_tags_sync_parent);\n        $keylist = ($keylist) ? $keylist : array();\n    }\n\n    // Insert /update each tag in the given place\n    foreach ($keylist as $item) {\n        $itn = trim($item[\'name\']);\n        $itt = trim($item[\'title\']);\n\n        // Skip invalid entries\n        if ($itn == \'\' || $itn == NULL || $itt == \'\' || $itt == NULL) continue;\n\n        $id = smd_tag_getsert($itn, \'article\', $itt, $smd_tags_import_tag_parent, $smd_tags_import_cat_parent, \'\', $smd_tags_import_force_parent, $smd_tags_import_force_cat);\n        if ($id) {\n            $tag_ids[] = $id;\n            $tag_names[] = $itn;\n        }\n    }\n\n    if ($tag_ids) {\n        $report = strong($smd_tags_sync_title).\': \'.join(\', \', $tag_names).br;\n        if ($smd_tags_delete_orig) {\n            if ($smd_tags_sync_type == \'0\') {\n                safe_update(\'textpattern\', \"Keywords=\'\'\", \"id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            } elseif ($smd_tags_sync_type == \'1\') {\n                safe_delete(\'textpattern_category\', \"article_id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            } elseif ($smd_tags_sync_type == \'2\') {\n                // Conspicuously missing until such time as a suitable method can be found to deal with it\n            } elseif ($smd_tags_sync_type == \'3\') {\n                safe_update(\'textpattern\', \"custom_$smd_tags_sync_cfs=\'\'\", \"id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            }\n        }\n    }\n    $tag_ctr = smd_tag_link($smd_tags_sync_id, $tag_ids, \'article\');\n    send_xml_response(array(\'smd_tags_report\' => $report, \'smd_tags_link_ctr\' => $tag_ctr));\n}\n\n// ------------------------\n// MIDDLEWARE - ADMIN/PUBLIC GLUE\n// ------------------------\n// Grab a valid tag parent list as HTML. Cannot be done easily from fixed data: AJAX is the only reliable way.\n// Needs to take into account the various use cases:\n//  * full list (link_mode=2)\n//  * from given parent nodes (link_mode=1)\n//  * excluding forbidden items (e.g. when assigning parent to tag that is already in the subtree)\n//  * adding interface elements like [clr] and [tog] buttons\n// TODO: take the bi-directional setting into account\nfunction smd_tag_parentlist() {\n    $trycat = isset($_POST[\'cat\']);\n    extract(doSlash(gpsa(array(\'name\', \'type\', \'id\', \'cat\', \'itemid\', \'listonly\', \'html_id\', \'link_mode\'))));\n\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $listonly = ($listonly == \'\' || $listonly == \'undefined\') ? \'\' : $listonly;\n    $html_id = ($html_id == \'\' || $html_id == \'undefined\') ? \'smd_tag_parent\' : $html_id;\n    $type = ($type == \'\' || $type == \'undefined\') ? \'article\' : $type;\n    $cat = ($cat == \'\' || $cat == \'undefined\') ? \'\' : $cat;\n    $link_mode = ($link_mode == \'\' || $link_mode == \'undefined\') ? \'1\' : $link_mode;\n    $tags = ($itemid == \'\' || $itemid == \'undefined\') ? array() : safe_rows(\'tag_id\', SMD_TAGU, \"type=\'$type\' AND item_id=\'$itemid\'\");\n    $clrBtn = \'[<span id=\"smd_clr\" class=\"smd_fakebtn\">\'.gTxt(\'smd_tag_clear\').\'</span>]\';\n    $togBtn = \'[<span id=\"smd_tog\" class=\"smd_fakebtn\">\'.gTxt(\'smd_tag_toggle\').\'</span>]\';\n\n    $pref = smd_tags_pref_get(array(\'smd_tag_p_listpar\', \'smd_tag_p_master\', \'smd_tag_p_linkcat\'), 1);\n    $incl = $pref[\'smd_tag_p_listpar\'][\'val\'];\n    $god = $pref[\'smd_tag_p_master\'][\'val\'];\n    $clink = $pref[\'smd_tag_p_linkcat\'][\'val\'];\n   $god_clause = ($god == \'\') ? \'\' : \"txt.parent = \'\" . doSlash($god) . \"\'\";\n\n    if ($cat && $clink) {\n       $cat = do_list($cat);\n       $rsid = array();\n       $rsc = getRows(\"SELECT DISTINCT txt.id, txt.parent, txt.lft, txt.rgt\n            FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.\"txp_category AS txc, \".PFX.SMD_TAGC.\" AS txl\n            WHERE ( ( txt.id = txl.tag_id\n            AND  txl.cat_id = txc.id\n            AND txc.name IN (\'\" .join(\"\',\'\", $cat). \"\') )\" . ($god_clause ? \' OR \'.$god_clause : \'\') . \")\n            AND txt.type = \'$type\'\");\n        if ($rsc) {\n            foreach ($rsc as $row) {\n                $include_parent = ($incl || $row[\'parent\'] == $god) ? \'\' : \' AND lft != \'.$row[\'lft\'];\n                $ids = safe_column(\'id\', SMD_TAG, \"type = \'$type\' AND (lft BETWEEN \" .$row[\'lft\']. \" AND \" .$row[\'rgt\']. \")\" . $include_parent);\n                $rsid = array_merge($rsid, $ids);\n            }\n        }\n        $cat = ($rsc) ? \' AND id IN (\'.doQuote(join(\"\',\'\",$rsid)).\')\' : \'\';\n    } elseif ($god != \'\' && $link_mode == \'1\') {\n        $ids = safe_column(\'id\', SMD_TAG, \"type = \'$type\' AND parent = \'\" . doSlash($god) . \"\'\");\n        $cat = ($ids) ? \' AND id IN (\'.doQuote(join(\"\',\'\",$ids)).\')\' : \'\';\n    } else {\n        $cat = \'\';\n    }\n\n    $link_mode = (($cat && $link_mode == \'1\') || $link_mode == \'2\') ? \'1=1\' : \'1=0\';\n\n    if ($id) {\n        $id = assert_int($id);\n        list($lft, $rgt) = array_values(safe_row(\'lft, rgt\', SMD_TAG, \'id = \'.$id));\n        $rs = smd_tag_tree(\'root\', $type, \"lft not between $lft and $rgt\".$cat, SMD_TAG);\n    } else {\n        $rs = smd_tag_tree(\'root\', $type, $link_mode.$cat, SMD_TAG);\n    }\n\n    if ($rs) {\n       if ($trycat && !$listonly) {\n            echo smd_multiTreeSelectInput($html_id, $rs, $tags).$clrBtn.$togBtn;\n        } else {\n            echo treeSelectInput($html_id, $rs, $name);\n        }\n    } else {\n        echo gTxt(\'smd_tag_no_parent\');\n    }\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Grab a valid category list\nfunction smd_tag_catlist($cat=\'\') {\n    extract(doSlash(gpsa(array(\'name\',\'type\',\'html_id\'))));\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $type = ($type == \"\" || $type == \"undefined\") ? \'article\' : $type;\n    $html_id = ($html_id == \"\" || $html_id == \"undefined\") ? \'smd_tags_catlist\' : $html_id;\n    $rs = getTree(\'root\', $type, \"1=1\");\n\n    if ($rs) {\n        echo treeSelectInput($html_id, $rs, $name, $html_id);\n    } else {\n        echo gTxt(\'no_categories_available\');\n    }\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Fetch a description for use as a tooltip\nfunction smd_tag_get_desc() {\n    extract(doSlash(gpsa(array(\'tag_ref\'))));\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $tag_ref = ($tag_ref == \"\" || $tag_ref == \"undefined\") ? \'\' : $tag_ref;\n\n    if ($tag_ref) {\n        $field = is_numeric($tag_ref) ? \'id\' : \'title\';\n        $desc = safe_field(\'desc_html\', SMD_TAG, \"$field=\'\" . doSlash($tag_ref) . \"\'\");\n        echo ($desc) ? $desc : gTxt(\'smd_tag_no_desc\');\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Remove empty URL elements (used as callback to array_filter())\nfunction smd_tags_remove_empty($var) {\n    return($var != \'\');\n}\n\n// ------------------------\n// Handle fake tag section URL\nfunction smd_tags_url_handler($evt, $stp) {\n    global $smd_tag_type, $pretext, $permlink_mode;\n\n    if (!smd_tags_table_exist()) {\n        return;\n    }\n\n    $prefs = smd_tags_pref_get(array(\'smd_tag_u_sec\', \'smd_tag_u_pnam\', \'smd_tag_u_ptyp\'), 1);\n    $urlsec = do_list($prefs[\'smd_tag_u_sec\'][\'val\']);\n    $urlnam = $prefs[\'smd_tag_u_pnam\'][\'val\'];\n    $urltyp = $prefs[\'smd_tag_u_ptyp\'][\'val\'];\n\n    $subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\", \"$1\", hu);\n    $regsafesubpath = preg_quote($subpath, \'/\');\n    $req = preg_replace(\"/^$regsafesubpath/i\", \'/\', serverSet(\'REQUEST_URI\'));\n\n    $qs = strpos($req, \'?\');\n    $qatts = ($qs ? a.substr($req, $qs + 1) : \'\');\n\n    if ($qs) $req = substr($req, 0, $qs);\n    $parts = array_values(array_filter(explode(\'/\', $req), \'smd_tags_remove_empty\'));\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n\n    // Deal with clean URL syntax first, trying to avoid clashes with built-in permlink schemes.\n    if (count($parts) > 1) {\n        // Determine if this URL is one we care about.\n        // If so, find where the tag portion begins (immediately after the trigger).\n        $pos = false;\n        foreach($urlsec as $trigger) {\n            if (($pos = array_search($trigger, $parts)) !== false) {\n                $pos++; // Start of the tag info\n                break;\n            }\n        }\n\n        if ($pos !== false) {\n            // Try to detect section/id/title permlink scheme\n            $sit = false;\n            if (count($parts) == 3) {\n                // Note that \'0\' is not a valid article ID but is numeric and could be the name of a tag, so it needs special treatment\n                if (is_numeric($parts[1]) && $parts[1] != \'0\') {\n                    $sit = true;\n                }\n            }\n\n            // As long as this is a regular permlink scheme, set the tag to be the remaining URL portion\n            if (!$sit) {\n                if (in_array($parts[$pos], $validTypes)) {\n                    $smd_tag_type = $parts[$pos];\n                    $pos++;\n                } else {\n                    $smd_tag_type = $validTypes[0];\n                }\n\n                $smd_tag = join(\'/\', array_slice($parts, $pos));\n                smd_tags_set($smd_tag_type, $smd_tag);\n                $_SERVER[\'QUERY_STRING\'] = $qatts;\n                $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0]; // Drop back to section list mode\n//              $_SERVER[\'QUERY_STRING\'] = $urlnam.\'=\'.$smd_tag .a. $urltyp.\'=\'.$smd_tag_type . $qatts;\n//              $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0] . \'/?\' . serverSet(\'QUERY_STRING\');\n            }\n        }\n    } elseif ((count($parts) == 1) && (in_array($parts[0], $urlsec) || in_array(gps(\'s\'), $urlsec))) {\n        // Default or named section (or /title permlink mode) + possible messy tag syntax\n        $theType = gps($urltyp);\n        $smd_tag = gps($urlnam);\n        $smd_tag_type = (empty($theType) && empty($smd_tag)) ? \'\' : ((in_array($theType, $validTypes)) ? $theType : $validTypes[0]);\n        smd_tags_set($smd_tag_type, $smd_tag);\n        $_SERVER[\'QUERY_STRING\'] = (($permlink_mode == \'messy\') ? $urlnam.\'=\'.$smd_tag .a. $urltyp.\'=\'.$smd_tag_type : \'\') . $qatts;\n        $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0] . (($permlink_mode == \'messy\') ? \'/?\' . serverSet(\'QUERY_STRING\') : \'\');\n    }\n}\n\n// ------------------------\nfunction smd_tags_set($typ, $tag=\'\', $item=\'\') {\n    global $smd_tags;\n\n    $smd_tags = array();\n\n    $prefs = smd_tags_pref_get(array(\'smd_tag_u_combi\', \'smd_tag_u_combi_and\', \'smd_tag_u_combi_or\'), 1);\n    $combi = $prefs[\'smd_tag_u_combi\'][\'val\'];\n    $cand = $prefs[\'smd_tag_u_combi_and\'][\'val\'];\n    $cor = $prefs[\'smd_tag_u_combi_or\'][\'val\'];\n\n    // Multi-tag filtering\n    if ($tag) {\n        $taglist = explode($cor, $tag);\n        $num_or = count($taglist);\n        if ($num_or == 1) {\n            // Only one item in the exploded list could mean one of two things:\n            //  a) only one item(!)\n            //  b) the \'and\' delimiter is in use\n            $andlist = explode($cand, $tag);\n            $num_and = count($andlist);\n            if ($num_and > 1) {\n                $smd_tags[\'meta\'][\'search_mode\'] = \'and\';\n                $smd_tags[\'meta\'][\'tag_head\'] = $andlist[0];\n                $smd_tags[\'meta\'][\'tag_tail\'] = $andlist[count($andlist)-1];\n                $taglist = $andlist;\n            } else {\n                $smd_tags[\'meta\'][\'search_mode\'] = \'single\';\n                $smd_tags[\'meta\'][\'tag_head\'] = $smd_tags[\'meta\'][\'tag_tail\'] = $andlist[0];\n            }\n        } elseif ($num_or > 1) {\n            $smd_tags[\'meta\'][\'search_mode\'] = \'or\';\n            $smd_tags[\'meta\'][\'tag_head\'] = $taglist[0];\n            $smd_tags[\'meta\'][\'tag_tail\'] = $taglist[count($taglist)-1];\n        }\n        $smd_tags[\'meta\'][\'tag_list\'] = $taglist;\n        $tag = join(\',\', quote_list($taglist));\n    }\n\n    // Override the search mode if it\'s switched off in prefs\n    if (!$combi) {\n        $smd_tags[\'meta\'][\'search_mode\'] = \'single\';\n    }\n\n    // MONSTER!\n    $rs = startRows(\n        \"SELECT id, name, txt.type, parent, title, desc_html AS description, count(item_id) AS count\"\n        .\",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children\"\n        .\",(SELECT COUNT(*) FROM \".PFX.SMD_TAG.\" WHERE name=name\" . (($typ) ? \" AND type=\'$typ\'\" : \"\") . \" AND lft < txt.lft AND rgt > txt.rgt)-1 AS level\"\n        .\" FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.SMD_TAGU.\" AS txu\"\n        .\" WHERE tag_id = id\"\n        .(($typ) ? \" AND txt.type=\'$typ\'\" : \"\")\n        .(($tag) ? \" AND name IN ($tag)\" : \"\")\n        .(($item) ? \" AND item_id=\'$item\'\" : \"\")\n        .\" GROUP BY id\");\n    smd_tags_populate($rs, $typ);\n\n    // In case one of the URL tags isn\'t in use, populate $smd_tags with the tag details.\n    // Note this one requires type.\n    if (!$item) {\n        $rs = startRows(\n            \"SELECT id, name, txt.type, parent, title, desc_html AS description\"\n            .\",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children\"\n            .\",(SELECT COUNT(*) FROM \".PFX.SMD_TAG.\" WHERE type=\'$typ\' AND name=name AND lft < txt.lft AND rgt > txt.rgt)-1 AS level\"\n            .\" FROM \".PFX.SMD_TAG.\" AS txt\"\n            .\" WHERE txt.type=\'$typ\'\"\n            .(($tag) ? \" AND name IN ($tag)\" : \"\")\n            .\" GROUP BY id\");\n        smd_tags_populate($rs, $typ);\n    }\n}\n\n// ------------------------\nfunction smd_tags_populate($rs, $typ) {\n    global $smd_tags;\n\n    while ($row = nextRow($rs)) {\n        $cnt = (isset($row[\'count\'])) ? $row[\'count\'] : \'0\';\n        $smd_tags[$typ][$row[\'id\']] = array(\'tag_name\' => $row[\'name\'], \'tag_title\' => $row[\'title\'], \'tag_description\' => $row[\'description\'], \'tag_parent\' => $row[\'parent\'], \'tag_count\' => $cnt, \'tag_children\' => $row[\'children\'], \'tag_level\' => $row[\'level\']);\n        $smd_tags[$typ][\'tag_name\'][$row[\'id\']] = $row[\'name\'];\n        $smd_tags[$typ][\'tag_title\'][$row[\'id\']] = $row[\'title\'];\n        $smd_tags[$typ][\'tag_description\'][$row[\'id\']] = $row[\'description\'];\n        $smd_tags[$typ][\'tag_parent\'][$row[\'id\']] = $row[\'parent\'];\n        $smd_tags[$typ][\'tag_count\'][$row[\'id\']] = $cnt;\n        $smd_tags[$typ][\'tag_children\'][$row[\'id\']] = $row[\'children\'];\n        $smd_tags[$typ][\'tag_level\'][$row[\'id\']] = $row[\'level\'];\n    }\n}\n\n// ------------------------\n// Add tags to the global scope, depending on context.\n// Returns the current context\nfunction smd_tags_context() {\n    global $thisarticle, $thisfile, $thislink, $thisimage, $smd_tag_type, $smd_tag_items, $smd_tags, $smd_thistag;\n\n    $ctxt = $smd_tag_type;\n    $ids = array();\n    $scp = isset($smd_tags[$ctxt]) ? $smd_tags[$ctxt] : array();\n\n    // Individual article or list\n    $id = \'\';\n    if (!empty($thisimage)) {\n        $id = $thisimage[\'id\'];\n        $ctxt = \'image\';\n        $scp = $thisimage;\n    } elseif (!empty($thisfile)) {\n        $id = $thisfile[\'id\'];\n        $ctxt = \'file\';\n        $scp = $thisfile;\n    } elseif (!empty($thislink)) {\n        $id = $thislink[\'id\'];\n        $ctxt = \'link\';\n        $scp = $thislink;\n    } elseif (!empty($thisarticle)) {\n        $id = $thisarticle[\'thisid\'];\n        $ctxt = \'article\';\n        $scp = $thisarticle;\n    }\n\n    if ($id) {\n        smd_tags_set($ctxt, \'\', $id);\n    }\n\n    if (isset($smd_tags[$ctxt])) {\n        foreach($smd_tags[$ctxt] as $rid => $row) {\n            if (is_int($rid)) {\n                $ids[] = $rid;\n            }\n        }\n    }\n\n    // TODO: is this needed to overwrite if tag already set?\n    if (!empty($smd_thistag)) {\n        $ids = array($smd_thistag[\'tag_id\']);\n    }\n\n    return array(\'context\' => $ctxt, \'scope\' => $scp, \'id\' => $ids);\n}\n\n// ------------------------\n// Load current tag into global scope\nfunction smd_tag_populate($row) {\n    global $smd_thistag;\n    $smd_thistag[\'tag_id\'] = $row[\'id\'];\n    $smd_thistag[\'tag_name\'] = $row[\'name\'];\n    $smd_thistag[\'tag_lettername\'] = smd_tags_utf8_substr($row[\'name\'], 0, 1);\n    $smd_thistag[\'tag_lettertitle\'] = smd_tags_utf8_substr($row[\'title\'], 0, 1);\n    $smd_thistag[\'tag_type\'] = $row[\'type\'];\n    $smd_thistag[\'tag_parent\'] = $row[\'parent\'];\n    $smd_thistag[\'tag_title\'] = $row[\'title\'];\n    $smd_thistag[\'tag_description\'] = $row[\'description\'];\n    $smd_thistag[\'tag_children\'] = $row[\'children\'];\n    $smd_thistag[\'tag_level\'] = $row[\'level\'];\n    $smd_thistag[\'tag_count\'] = $row[\'count\'];\n    $smd_thistag[\'tag_indent\'] = $row[\'indent\'];\n    $smd_thistag[\'tag_weight\'] = $row[\'weight\'];\n    $smd_thistag[\'tag_first\'] = $row[\'first\'];\n    $smd_thistag[\'tag_last\'] = $row[\'last\'];\n}\n\n// Thanks http://us2.php.net/manual/en/function.substr.php\nfunction smd_tags_utf8_substr($str, $start) {\n    preg_match_all(\"/./u\", $str, $ar);\n    if(func_num_args() >= 3) {\n        $end = func_get_arg(2);\n        return join(\'\',array_slice($ar[0],$start,$end));\n    } else {\n        return join(\'\',array_slice($ar[0],$start));\n    }\n}\n\n// ------------------------\n// All tags/counts\nfunction smd_tag_list_adm($atts) {\n    include_once txpath.\'/publish/taghandlers.php\';\n    extract(lAtts(array(\n        \'type\'       => \'article\',\n        \'indent\'     => \'&#160;&#160;\',\n        \'count\'      => 1,\n        \'label\'      => \'\',\n        \'labeltag\'   => \'\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'\',\n        \'class\'      => __FUNCTION__,\n        \'breakclass\' => \'\',\n    ),$atts));\n\n    $validTypes = array(\'list\' => \'article\', \'image\' => \'image\', \'file\' => \'file\', \'link\' => \'link\');\n    $rs = smd_tag_tree(\'root\', $type, \'1=1\', SMD_TAG);\n    $rsc = getRows(\"SELECT txl.tag_id, txc.name\n            FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.SMD_TAGC.\" AS txl, \".PFX.\"txp_category AS txc\n            WHERE txt.type = \'$type\' AND txt.id = txl.tag_id AND txl.cat_id = txc.id\");\n    $rscout = array();\n    if ($rsc) {\n        foreach ($rsc as $idx => $row) {\n            $rscout[$row[\'tag_id\']] = $row[\'name\'];\n        }\n    }\n\n    if ($rs) {\n        $out = array();\n        $totals = array();\n        $pref = smd_tags_pref_get(\'smd_tag_p_master\', 1);\n        $god = $pref[\'smd_tag_p_master\'][\'val\'];\n\n        // Tally each tag\'s usage\n        if ($count) {\n            $rsu = safe_rows_start(\'tag_id, count(*) as num\', SMD_TAGU, \"type=\'$type\' GROUP BY tag_id\");\n            while ($row = nextRow($rsu)) {\n                $tagid = $row[\'tag_id\'];\n                $num = $row[\'num\'];\n                $ids = safe_column(\'item_id\', SMD_TAGU, \"type=\'$type\' AND tag_id=\'$tagid\'\");\n                $totals[$tagid] = array($num, join(\",\",$ids));\n            }\n        }\n\n        foreach ($rs as $row) {\n            extract($row);\n            $parent = (isset($parent)) ? $parent : \'\'; // Parent might not be defined\n            $row[\'type\'] = $type;\n            if (isset($totals[$id])) {\n                $row[\'count\'] = $totals[$id][0];\n                $ev = array_keys($validTypes,$type);\n                $url = \'index.php?event=\'.$ev[0].a.\'search_method=id\'.a.\'crit=\'.$totals[$id][1];\n                $cntop = sp.href(\'(\'.$totals[$id][0].\')\', $url);\n            } else {\n                $row[\'count\'] = 0;\n                $cntop = sp.\'(0)\';\n            }\n\n            $rowdata = \'<span name=\"smd_tagnam\" class=\"smd_hidden\">\'.$name\n                    .\'</span><span name=\"smd_tagid\" class=\"smd_hidden\">\'.$id\n                    .\'</span><span name=\"smd_tagttl\" class=\"smd_hidden\">\'.$title\n                    .\'</span><span name=\"smd_tagpar\" class=\"smd_hidden\">\'.$parent\n                    .\'</span><span name=\"smd_tagcat\" class=\"smd_hidden\">\'.((isset($rscout[$id])) ? $rscout[$id] : \'\')\n                    .\'</span><span name=\"smd_tagdsc\" class=\"smd_hidden\">\'.$description\n                    .\'</span>\';\n            $out[] = array(\n                \'data\' => $rowdata.str_repeat($indent, $level * 1) . $title . (($count) ? $cntop : \'\').n,\n                \'level\' => $level,\n                \'class\' => ($god==$name ? array(\'smd_tag_master\', \'smd_tag_parent\') : ($god==$parent ? array(\'smd_tag_master\',\'smd_tag_child\') : array())),\n                );\n//          $smd_thistag = array();\n        }\n\n        if ($out) {\n            $wrapit = do_list($wraptag, \":\");\n\n            if ($wrapit[0] == \'table\') {\n                // Tables are a special case\n                $totalItems = $rows = count($rs);\n                $step = 1;\n                $cols = 1;\n                $numopts = count($wrapit);\n                // Each successive level overrides the previous step so by the end of the ifs,\n                // 3 values are set up: step, rows and cols\n                if ($numopts > 1) {\n                    $cols = $wrapit[1];\n                    $rows = ceil($totalItems/$cols);\n                }\n\n                if ($numopts > 2) {\n                    if ($wrapit[2] == \'rows\') {\n                        $rows = $wrapit[1];\n                        $cols = ceil($totalItems/$rows);\n                    }\n                }\n\n                if ($numopts > 3) {\n                    if ($wrapit[3] == \'bycol\') {\n                        $step = ($wrapit[2]==\'cols\') ? ceil($totalItems/$cols) : $rows;\n                        if ($wrapit[2]==\'cols\') {\n                            $rows = $step;\n                        } else {\n                            $cols = ceil($totalItems/$rows);\n                        }\n                    }\n                }\n\n                // Generate table based on above rules\n                $tblout = array();\n                $cellCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n                $breakclass = (!empty($breakclass)) ? \' class=\"\'.$breakclass.\'\"\' : \'\';\n\n                for ($idx = 0; $idx < $rows; $idx++) {\n                    $bld = array();\n                    for ($jdx = 0; $jdx < $cols; $jdx++) {\n                        $offset = ($wrapit[3]==\'bycol\') ? ($jdx * $step) + $idx : $cellCtr;\n                        $bclass = (isset($out[$offset][\'class\'])) ? join(\' \', $out[$offset][\'class\']) : \'\';\n                        $bld[] = (isset($out[$offset][\'data\'])) ? tda($out[$offset][\'data\'], ($bclass ? \' class = \"\'.$bclass.\'\"\': \'\')) : td(\'\');\n                        $cellCtr++;\n                    }\n                    $tblout[] = tr(join(\'\', $bld), $breakclass);\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $tblout), $wrapit[0], $class);\n            } elseif ($wrapit[0] == \'list\') {\n                // Tags by list, optionally split into new row/col every N items\n                $totalItems = count($rs);\n                $lim = 0;\n\n                $numopts = count($wrapit);\n\n                if ($numopts > 1) {\n                    $lim = $wrapit[1];\n                }\n\n                // Generate the list\n                $listout = array();\n                $itemCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n\n                foreach ($out as $item) {\n                    $bclass = join(\' \', array_merge($item[\'class\'], array($breakclass)));\n                    $bclass = (!empty($bclass)) ? \' class=\"\'.$bclass.\'\"\' : \'\';\n                    if ($itemCtr == 0) {\n                        $listout[] = \"<ul>\";\n                    }\n                    $listout[] = \'<li\'.$bclass.\'>\'.$item[\'data\'].\'</li>\';\n                    $itemCtr++;\n                    if (($lim > 0 && $itemCtr >= $lim) || $itemCtr >= $totalItems-1) {\n                        $listout[] = \'</ul>\';\n                        $itemCtr = 0;\n                    }\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $listout), \'div\', $class);\n            } elseif ($wrapit[0] == \'listgrp\') {\n                // Tags by list, split into new row/col every time a new level \'N\' is reached\n                $totalItems = count($rs);\n                $level = 0;\n\n                $numopts = count($wrapit);\n\n                if ($numopts > 1) {\n                    $level = $wrapit[1];\n                }\n\n                if ($numopts > 3) {\n                    $class .= ($wrapit[3] == \'byrow\') ? \' byrow\' : \' bycol\';\n              }\n\n                // Generate the list\n                $listout = array();\n                $itemCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n\n                $prevel = 0;\n                foreach ($out as $item) {\n                    $bclass = join(\' \', array_merge($item[\'class\'], array($breakclass)));\n                    $bclass = (!empty($bclass)) ? \' class=\"\'.$bclass.\'\"\' : \'\';\n                    if ($prevel >= $item[\'level\'] && $item[\'level\'] <= $level) {\n                        if ($itemCtr > 0) {\n                            $listout[] = \'</ul>\';\n                        }\n                        $listout[] = \"<ul>\";\n                    }\n                    $listout[] = \'<li\'.$bclass.\'>\'.$item[\'data\'].\'</li>\';\n                    $prevel = $item[\'level\'];\n                    if ($itemCtr >= $totalItems-1) {\n                        $listout[] = \'</ul>\';\n                    }\n                    $itemCtr++;\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $listout), \'div\', $class);\n\n            } else {\n                //TODO: only use the data column from $out\n                return doLabel($label, $labeltag).doWrap($out, $wrapit[0], $break, $class, $breakclass);\n            }\n        }\n        return \'\';\n    }\n}\n\n// -------------------------------------------------------------\n// Cloned getTree() from txplib_db.php because that function doesn\'t extract description\nfunction smd_tag_tree($root, $type, $where=\'1=1\', $tbl=\'txp_category\') {\n    $root = doSlash($root);\n    $type = doSlash($type);\n\n    $rs = safe_row(\n        \"lft as l, rgt as r\",\n        $tbl,\n        \"name=\'$root\' and type = \'$type\'\"\n    );\n\n    if (!$rs) return array();\n    extract($rs);\n\n    $out = array();\n    $right = array();\n\n    $rs = safe_rows_start(\n        \"id, name, lft, rgt, parent, title, description, desc_html\",\n        $tbl,\n        \"lft between $l and $r and type = \'$type\' and name != \'root\' and $where order by lft asc\"\n    );\n\n    while ($rs and $row = nextRow($rs)) {\n        extract($row);\n        while (count($right) > 0 && $right[count($right)-1] < $rgt) {\n            array_pop($right);\n        }\n\n        $out[] =\n            array(\n                \'id\' => $id,\n                \'name\' => $name,\n                \'title\' => $title,\n                \'level\' => count($right),\n                \'children\' => ($rgt - $lft - 1) / 2,\n                \'description\' => $description,\n                \'desc_html\' => $desc_html,\n                \'parent\' => $parent,\n            );\n\n        $right[] = $rgt;\n    }\n    return($out);\n}\n\n// ------------------------\n// Find some value in a tree and return the matching entries\nfunction smd_tag_tree_search($val, $tree, $limit=0, $field=\'name\') {\n    $ctr = 0;\n    $out = array();\n    foreach ($tree as $idx => $entry) {\n        if ($entry[$field] == $val) {\n            $out[] = $tree[$idx];\n            $ctr++;\n        }\n        if ($limit > 0 && $ctr == $limit) {\n            break;\n        }\n    }\n    return $out;\n}\n\n// ------------------------\n// PHP4 equivalent of array_combine (from http://www.php.net/manual/en/function.array-combine.php)\nif (!function_exists(\'array_combine\')) {\nfunction array_combine($arr1, $arr2) {\n    $out = array();\n\n    $arr1 = array_values($arr1);\n    $arr2 = array_values($arr2);\n\n    foreach($arr1 as $key1 => $value1) {\n    $out[(string)$value1] = $arr2[$key1];\n    }\n\n    return $out;\n}\n}\n\n// ------------------------\n// PUBLIC TAGS\n// ------------------------\n// Check tags of a particular type, or those that have a specific property in context.\nfunction smd_if_tag ($atts, $thing) {\n    global $smd_tags, $smd_tag_type, $smd_thistag;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'type\'        => NULL,\n        \'id\'          => NULL,\n        \'name\'        => NULL,\n        \'title\'       => NULL,\n        \'description\' => NULL,\n        \'parent\'      => NULL,\n        \'count\'       => NULL,\n        \'children\'    => NULL,\n        \'level\'       => NULL,\n        \'search_mode\' => NULL,\n        \'is\'          => NULL,\n    ),$atts));\n\n    // Validate atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ctxt = smd_tags_context();\n    $ctype = $ctxt[\'context\'];\n    $type = (in_array($type, $validTypes)) ? $type : ( ($ctxt[\'context\']) ? $ctxt[\'context\'] : ( ($smd_tag_type) ? $smd_tag_type :  $validTypes[0] ) );\n\n    $eqtest = array();\n    $nutest = array();\n    $searchtest = array();\n    $opRE = \'/(\\>|\\>\\=|\\<|\\<\\=|\\!)([0-9a-zA-Z- ]+)/\';\n\n    // Master tag?\n    $pref = smd_tags_pref_get(\'smd_tag_p_master\', 1);\n    $god = $pref[\'smd_tag_p_master\'][\'val\'];\n    $eqtest[\'master\'] = (($is == \'master\') && ($god != \'\')) ? $god : NULL;\n\n    // Start / end of list?\n    $eqtest[\'first\'] = ($is == \'first\') ? 1 : NULL;\n    $eqtest[\'last\'] = ($is == \'last\') ? 1 : NULL;\n\n    // Combination moade?\n    $searchtest[\'single\'] = ($search_mode == \'single\') ? 1 : NULL;\n    $searchtest[\'or\'] = ($search_mode == \'or\') ? 1 : NULL;\n    $searchtest[\'and\'] = ($search_mode == \'and\') ? 1 : NULL;\n\n    // TODO: Consolidate this into a loop for each \'id\', \'name\', type\', \'title\', ...\n    $num = preg_match_all($opRE, $id, $parts);\n    $eqtest[\'id\'] = ($num<=0) ? $id : NULL;\n    if ($num>0) $nutest[\'id\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $name, $parts);\n    $eqtest[\'name\'] = ($num<=0) ? $name : NULL;\n    if ($num>0) $nutest[\'name\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $type, $parts);\n    $eqtest[\'type\'] = ($num<=0) ? $type : NULL;\n    if ($num>0) $nutest[\'type\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $title, $parts);\n    $eqtest[\'title\'] = ($num<=0) ? $title : NULL;\n    if ($num>0) $nutest[\'title\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $description, $parts);\n    $eqtest[\'description\'] = ($num<=0) ? $description : NULL;\n    if ($num>0) $nutest[\'description\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $parent, $parts);\n    $eqtest[\'parent\'] = ($num<=0) ? $parent : NULL;\n    if ($num>0) $nutest[\'parent\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $count, $parts);\n    $eqtest[\'count\'] = ($num<=0) ? $count : NULL;\n    if ($num>0) $nutest[\'count\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $children, $parts);\n    $eqtest[\'children\'] = ($num<=0) ? $children : NULL;\n    if ($num>0) $nutest[\'children\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $level, $parts);\n    $eqtest[\'level\'] = ($num<=0) ? $level : NULL;\n    if ($num>0) $nutest[\'level\'] = array($parts[1][0] => $parts[2][0]);\n\n    trace_add(\'[smd_if_tag equality tests: \'. implode(\', \', $eqtest) . \']\');\n    trace_add(\'[smd_if_tag search_mode tests: \'. implode(\', \', $searchtest) . \']\');\n    trace_add(\'[smd_if_tag numeric tests: \'.  print_r($nutest, true) . \']\');\n\n    // Init\n    $out = $result = $numTests = 0;\n    if (!isset($smd_tags[$type]) && empty($smd_thistag)) {\n        // not in scope\n    } else {\n        // Equality comparisons\n        foreach ($eqtest as $tname => $tval) {\n            if (!is_null($tval)) {\n                $numTests++;\n\n                // Master is a special case which checks if the parent is the master tag\n                $tname = ($tname == \'master\') ? \'parent\' : $tname;\n\n                if ($smd_thistag) {\n                    // Local scope.\n                    if ($smd_thistag[\'tag_\'.$tname] == $tval) {\n                        $out++;\n                    }\n                } else {\n                    // Global scope.\n                    if ($tname == \"type\") {\n                        if ($smd_tag_type == $type || $ctype == $type) {\n                            $out++;\n                        }\n                    }\n                    if (isset($smd_tags[$type][\'tag_\'.$tname]) && in_array($tval, $smd_tags[$type][\'tag_\'.$tname], empty($tval) && $tval !== \'0\')) {\n                        $out++;\n                    }\n                }\n            }\n        }\n        // Search comparisons\n        foreach ($searchtest as $tname => $tval) {\n            if (!is_null($tval)) {\n                $numTests++;\n\n                // Global scope only for search mode.\n                if (isset($smd_tags[\'meta\'][\'search_mode\']) && $smd_tags[\'meta\'][\'search_mode\'] == $tname) {\n                    $out++;\n                }\n            }\n        }\n        // Numeric comparisons\n        foreach ($nutest as $tname => $tval) {\n            $numTests++;\n            $op = current(array_keys($tval));\n            $val = current($tval);\n            if ($smd_thistag) {\n                $comparison = $smd_thistag[\'tag_\'.$tname];\n            } else {\n                $comparison = $smd_tags[$type][\'tag_\'.$tname];\n            }\n            switch ($op) {\n                case \'>\':\n                    if (isset($comparison) && $comparison > $val) {\n                        $out++;\n                    }\n                    break;\n                case \'>=\':\n                    if (isset($comparison) && $comparison >= $val) {\n                        $out++;\n                    }\n                    break;\n                case \'<\':\n                    if (isset($comparison) && $comparison < $val) {\n                        $out++;\n                    }\n                    break;\n                case \'<=\':\n                    if (isset($comparison) && $comparison <= $val) {\n                        $out++;\n                    }\n                    break;\n                case \'!\':\n                    if (isset($comparison) && $comparison != $val) {\n                        $out++;\n                    }\n                    break;\n            }\n        }\n\n        trace_add(\'[smd_if_tag num tests | results: \' . $numTests . \'|\' . $out . \']\');\n\n        // Count how many successes there were\n        if ($numTests == $out) {\n            $result = 1;\n        }\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// ------------------------\nfunction smd_if_tag_list ($atts, $thing)\n{\n    global $smd_tag_type;\n    return parse(EvalElse($thing, (!empty($smd_tag_type))));\n}\n\n// ------------------------\n// Return name/title of current tag\nfunction smd_tag_name($atts = array(), $thing = null)\n{\n    global $smd_thistag, $permlink_mode, $plugins;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'title\'       => 1,\n        \'link\'        => \'\',\n        \'section\'     => \'\',\n        \'cleanurls\'   => 1,\n        \'parent\'      => 0, // not useful to print but good for URLs to nav back up the tree\n        \'parentlabel\' => \'Up a level\',\n        \'wraptag\'     => \'\',\n        \'class\'       => __FUNCTION__,\n        \'style\'       => \'\',\n        \'pad_str\'     => \'\',\n        \'pad_pos\'     => \'left\', // left/right/both with optional :in suffix to indicate if it\'s to go inside the link\n    ), $atts));\n\n    $smdpref = smd_tags_pref_get(array(\'smd_tag_u_sec\', \'smd_tag_u_pnam\', \'smd_tag_u_ptyp\'), 1);\n    $urlsec = do_list($smdpref[\'smd_tag_u_sec\'][\'val\']);\n    $section = ($section) ? $section : $urlsec[0];\n    $in_default = (in_array($section, $urlsec));\n    $urlnam = $smdpref[\'smd_tag_u_pnam\'][\'val\'];\n    $urltyp = $smdpref[\'smd_tag_u_ptyp\'][\'val\'];\n\n    // gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs\n    // so it requires an exception\n    $gbp_pl = (is_array($plugins) && in_array(\'gbp_permanent_links\', $plugins));\n    $messy = ($permlink_mode == \'messy\') && (!$gbp_pl);\n\n    $parentlabel = ($parentlabel) ? $parentlabel : (($parent) ? $smd_thistag[\'tag_parent\'] : $smd_thistag[\'tag_name\']);\n    $label = ($parent) ? $parentlabel : (($title) ? $smd_thistag[\'tag_title\'] : $smd_thistag[\'tag_name\']);\n    $tname = ($parent) ? $smd_thistag[\'tag_parent\'] : $smd_thistag[\'tag_name\'];\n    $level = $smd_thistag[\'tag_level\'];\n    $style = ($style) ? \' style=\"\'.$style.\'\"\' : \'\';\n    $padding = ($pad_str) ? str_repeat($pad_str, $level) : \'\';\n    $pad_pos = do_list($pad_pos, \':\');\n    $pad[\'lin\'] = ($padding && (in_array(\'left\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && in_array(\'in\', $pad_pos) !== false) ? $padding : \'\';\n    $pad[\'rin\'] = ($padding && (in_array(\'right\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && in_array(\'in\', $pad_pos) !== false) ? $padding : \'\';\n    $pad[\'l\'] = ($padding && (in_array(\'left\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && (empty($pad[\'lin\']) || !$link)) ? $padding : \'\';\n    $pad[\'r\'] = ($padding && (in_array(\'right\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && (empty($pad[\'rin\']) || !$link)) ? $padding : \'\';\n    $dest = ($messy || !$cleanurls)\n                ? pagelinkurl(array(\'s\' => $section, $urlnam => $tname, $urltyp => $smd_thistag[\'tag_type\']))\n                : hu.$section.\'/\'.(($in_default) ? \'\' : $urlsec[0].\'/\').$smd_thistag[\'tag_type\'].\'/\'.$tname;\n    if ($thing) {\n        $out = \'<a\'.\n            ($messy ? \'\' : \' rel=\"tag\"\').\n            ( ($class and !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\' ).\n            \' href=\"\'.$dest.\'\"\'.\n            ($title ? \' title=\"\'.$label.\'\"\' : \'\').\n            \'>\'.parse($thing).\'</a>\';\n    } elseif ($link) {\n        $out = $pad[\'l\'].\'<a\'.\n            ($messy ? \'\' : \' rel=\"tag\"\').\n            \' href=\"\'.$dest.\'\">\'.$pad[\'lin\'].$label.$pad[\'rin\'].\'</a>\'.$pad[\'r\'];\n    } else {\n        $out = $pad[\'l\'].$label.$pad[\'r\'];\n    }\n\n    return doTag($out, $wraptag, $class, $style);\n}\n\n// ------------------------\n// Return # of items associated with this tag\n//TODO: think about per-section / per-category counts\nfunction smd_tag_count($atts = array(), $thing = null)\n{\n    global $smd_thistag, $smd_tags;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'class\'      => __FUNCTION__,\n        \'style\'      => \'\',\n        \'wraptag\'    => \'\',\n        \'wrapcount\'  => \' (:)\',\n        \'showempty\'  => \'1\',\n        \'paramdelim\' => \':\',\n    ), $atts));\n\n    $wrapcount = explode($paramdelim, $wrapcount); // do_list does a trim: don\'t want that\n    $style = ($style) ? \' style=\"\'.$style.\'\"\' : \'\';\n\n    if (count($wrapcount) == 1) {\n        $wrapcount[1] = $wrapcount[0];\n    }\n\n    if ($smd_thistag) {\n        $out = $smd_thistag[\'tag_count\'];\n    } else {\n        $out = isset($smd_tags[\'meta\'][\'content_count\']) ? $smd_tags[\'meta\'][\'content_count\'] : \'\';\n    }\n\n    $out = ($out == 0 && !$showempty) ? \'\' : $wrapcount[0].$out.$wrapcount[1];\n\n    return doTag($out, $wraptag, $class, $style);\n}\n\n// ------------------------\n// Return other info about the current tag\nfunction smd_tag_info($atts = array(), $thing = null)\n{\n    global $smd_thistag;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'item\'       => \'name\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'br\',\n        \'class\'      => __FUNCTION__,\n        \'breakclass\' => \'\',\n    ), $atts));\n\n    $out = array();\n    $items = do_list($item);\n    $availableItems = array(\n        \'id\',\n        \'name\',\n        \'title\',\n        \'description\',\n        \'lettername\',\n        \'lettertitle\',\n        \'type\',\n        \'parent\',\n        \'children\',\n        \'level\',\n        \'count\',\n        \'indent\',\n        \'weight\',\n    );\n\n    foreach ($items as $whatnot) {\n        if (in_array($whatnot, $availableItems)) {\n            $out[] = $smd_thistag[\'tag_\'.$whatnot];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\n// ------------------------\n// Related articles/images/files/links by tag\nfunction smd_related_tags($atts = array(), $thing = null)\n{\n    global $thisarticle, $thisfile, $thislink, $thisimage, $pretext, $prefs, $smd_tags, $smd_thistag;\n\n    static $smd_tags_pc;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'type\'       => \'\',\n        \'section\'    => $pretext[\'s\'],\n        \'status\'     => \'4\',\n        \'limit\'      => 99999,\n        \'offset\'     => 0,\n        \'form\'       => \'\',\n        \'match\'      => \'tag_name\',\n        \'match_self\' => 0,\n        \'no_widow\'   => @$prefs[\'title_no_widow\'],\n        \'sort\'       => \'\',\n        \'label\'      => \'\',\n        \'labeltag\'   => \'\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'br\',\n        \'class\'      => __FUNCTION__,\n        \'delim\'      => \',\',\n        \'paramdelim\' => \':\',\n        \'debug\'      => \'0\',\n    ), $atts));\n\n    // Validate atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ctxt = smd_tags_context();\n\n    $scope = $ctxt[\'scope\'];\n    $idlist = $ctxt[\'id\'];\n    $ctype = $ctxt[\'context\'];\n    $type = (in_array($type, $validTypes)) ? $type : (($ctxt[\'context\']) ? $ctxt[\'context\'] : $validTypes[0]);\n\n    if (!isset($smd_tags_pc[$type])) {\n        $smd_tags_pc[$type] = array(\n            \'all\' => array(),\n            \'parlist\' => array(),\n            \'chilist\' => array(),\n        );\n    }\n\n    $sectionClause = ($section) ? \" AND txp.Section IN (\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\" : \'\';\n    $status = do_list($status);\n    $stati = array();\n\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } elseif (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n\n    $statSQL = \' AND txp.Status IN (\'.join(\',\', $stati).\')\';\n\n    $out = array();\n\n    if (!$sort) {\n        switch ($type) {\n            case \"article\":\n                $sort = \"Posted desc\";\n                break;\n            case \"image\":\n\n            case \"link\":\n                $sort = \"date desc\";\n                break;\n            case \"file\":\n                $sort = \"created desc\";\n                break;\n        }\n    }\n\n    $cfs = getCustomfields();\n    $cfKeys = implode(\',\', array_map(\n        function($k) { return \'custom_\' . $k; }, array_keys($cfs)\n    ));\n\n    // Lookup table for making SQL queries\n    $sqlStubs = array(\n        \"article\" => array(\n            \"select\" => \"txp.ID, Posted, Expires, AuthorID, LastMod, LastModID, txp.Title, Title_html, Body, Body_html, Excerpt, Excerpt_html, Image, Category1, Category2, Annotate, AnnotateInvite, comments_count, Status, textile_body, textile_excerpt, Section, override_form, Keywords, txp.description, url_title\" . ($cfKeys ? \',\'. $cfKeys : \'\') . \", uid, feed_time, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"textpattern\",\n            \"gtags\" => $thisarticle,\n            \"gid\" => \"thisid\",\n        ),\n        \"image\" => array(\n            \"select\" => \"txp.id, txp.name, txp.category, txp.ext, txp.w, txp.h, txp.alt, txp.caption, txp.date, txp.author, txp.thumb_w, txp.thumb_h, txp.thumbnail, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_image\",\n            \"gtags\" => $thisimage,\n            \"gid\" => \"id\",\n        ),\n        \"file\" => array(\n            \"select\" => \"txp.id, txp.filename, txp.title, txp.category, txp.permissions, txp.description, txp.downloads, txp.status, txp.modified, txp.created, txp.size, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_file\",\n            \"gtags\" => $thisfile,\n            \"gid\" => \"id\",\n        ),\n        \"link\" => array(\n            \"select\" => \"txp.id, txp.date, txp.category, txp.url, txp.linkname, txp.linksort, txp.description, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_link\",\n            \"gtags\" => $thislink,\n            \"gid\" => \"id\",\n        ),\n    );\n\n    // Extract stuff to match & make up query replacement variables\n    $match = do_list($match, $paramdelim);\n    $matchType = array_shift($match);\n\n    if (count($match) == 0) {\n        // Assume current type unless a tag is being matched (in which case, use $ctype)\n        $matchWith = array($matchType);\n        $matchType = (strpos($matchWith[0], \'tag_\') !== false) ? $ctype : $type;\n    } else {\n        $matchWith = $match;\n    }\n\n    $matches = array();\n\n    foreach ($matchWith as $matchItem) {\n        if (strpos($matchItem, \'tag_\') !== false) {\n            $lookin = (isset($smd_tags[$matchType])) ? $smd_tags[$matchType] : ( (isset($smd_thistag)) ? $smd_thistag : array() );\n        } else {\n            $lookin = $sqlStubs[$matchType][\"gtags\"];\n        }\n\n        if (isset($lookin[$matchItem])) {\n            $thismatch = $lookin[$matchItem];\n\n            if (is_array($thismatch)) {\n                foreach ($thismatch as $subID => $subItem) {\n                    if (in_array($subID, $idlist)) {\n                        $matches[] = $subItem;\n                    }\n                }\n            } else {\n                $matches[] = $thismatch;\n            }\n        }\n    }\n\n    $matches = array_unique($matches);\n\n    // Should the tag hierarchy be taken into account?\n    $opts = smd_tags_pref_get(array(\'smd_tag_p_lbi\', \'smd_tag_u_combi\'));\n    $bidir = $opts[\'smd_tag_p_lbi\'][\'val\'];\n    $combi = $opts[\'smd_tag_u_combi\'][\'val\'];\n\n    if ($matches) {\n        $parents = $children = array();\n\n        if ($smd_tags_pc[$type][\'all\']) {\n            $full_tag_tree = $smd_tags_pc[$type][\'all\'];\n        } else {\n            $full_tag_tree = $smd_tags_pc[$type][\'all\'] = smd_tag_tree(\'root\', $type, \'1=1\', SMD_TAG);\n        }\n\n        // Build two lists from the full tag tree:\n        //  1) parlist: each node\'s parent\n        //  2) chilist: list of nodes below the current\n        if (!$smd_tags_pc[$type][\'parlist\'] || !$smd_tags_pc[$type][\'chilist\']) {\n            foreach ($full_tag_tree as $node) {\n                $smd_tags_pc[$type][\'parlist\'][$node[\'name\']] = $node[\'parent\'];\n\n                if ($node[\'parent\'] == \'root\') {\n                    $smd_tags_pc[$type][\'chilist\'][$node[\'name\']] = array();\n                } else {\n                    $smd_tags_pc[$type][\'chilist\'][$node[\'parent\']][] = $node[\'name\'];\n\n                    foreach ($smd_tags_pc[$type][\'chilist\'] as $key => $nodes) {\n                        if (in_array($node[\'parent\'], $nodes)) {\n                            $smd_tags_pc[$type][\'chilist\'][$key][] = $node[\'name\'];\n                        }\n                    }\n                }\n            }\n        }\n\n        // Reverse lookup to find parents of child\n        if ($bidir == 1 || $bidir == 3) {\n            foreach ($matches as $matchit) {\n                $theparent = $matchit;\n\n                while (array_key_exists($theparent, $smd_tags_pc[$type][\'parlist\'])) {\n                    if ($smd_tags_pc[$type][\'parlist\'][$theparent] == \'root\') {\n                        break;\n                    }\n                    $parents[] = $theparent = $smd_tags_pc[$type][\'parlist\'][$theparent];\n                }\n            }\n        }\n\n        // Forward lookup to find children of parent\n        if ($bidir == 2 || $bidir == 3) {\n            foreach ($matches as $matchit) {\n                if (array_key_exists($matchit, $smd_tags_pc[$type][\'chilist\'])) {\n                    $children = array_merge($children, $smd_tags_pc[$type][\'chilist\'][$matchit]);\n                }\n            }\n        }\n\n        $matches = array_unique(array_merge( $matches, $parents, $children ));\n    }\n\n    trace_add(\'[smd_related_tags matches: \' . join(\', \', $matches) . \']\');\n\n    // Convert above opts to SQL query clauses\n    $excludeClause = ($match_self) ? \'\' : \' AND txp.id != \"\'.$sqlStubs[$type][\"gtags\"][$sqlStubs[$type][\"gid\"]].\'\"\';\n\n    $and_mode = (isset($smd_tags[\'meta\'][\'search_mode\']) && ($smd_tags[\'meta\'][\'search_mode\'] == \'and\')) ? true : false;\n    $and_modeClause = ($and_mode && $matches) ? \' HAVING COUNT(*) = \' . count($matches) : \'\';\n\n    $last_tag = (isset($smd_tags[\'meta\'][\'tag_tail\'])) ? $smd_tags[\'meta\'][\'tag_tail\'] : \'\';\n    $matchClause = \" AND \" .(($matches) ? ($combi ? \"smt.name IN (\" .join(\",\", quote_list($matches)). \")\" : \"smt.name = \'\".$last_tag.\"\'\") : \"smt.name = \'\'\");\n    $orderBy = \" ORDER BY \" .$sort. \" LIMIT \" .$offset. \",\" .$limit;\n    $contentCount = 0;\n\n    switch ($type) {\n        case \"article\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'article\'\" . $statSQL . $excludeClause . $sectionClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags article records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'ID\'], $uniqrs)) {\n                        $safe = ($thisarticle) ? $thisarticle : NULL;\n                        populateArticleData($row);\n                        $row[\'Title\'] = ($no_widow) ? noWidow(escape_title($row[\'Title\'])) : escape_title($row[\'Title\']);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row[\'Title\'], permlinkurl($row)));\n                        $uniqrs[] = $row[\'ID\'];\n                        $thisarticle = $safe;\n                    }\n                }\n            }\n            break;\n        case \"image\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'image\'\" . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags image records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thisimage) ? $thisimage : NULL;\n                        $thisimage = image_format_info($row);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : image(array(\'id\' => $row[\'id\'])));\n                        $uniqrs[] = $row[\'id\'];\n                        $thisimage = $safe;\n                    }\n                }\n            }\n            break;\n        case \"file\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'file\'\" . $statSQL . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags file records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thisfile) ? $thisfile : NULL;\n                        $thisfile = file_download_format_info($row);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : file_download_link(array(\'filename\' => $row[\'filename\']), $row[\'filename\']));\n                        $uniqrs[] = $row[\'id\'];\n                        $thisfile = $safe;\n                    }\n                }\n            }\n            break;\n        case \"link\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'link\'\" . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags link records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thislink) ? $thislink : NULL;\n                        $thislink = array(\n                            \'id\'          => $row[\'id\'],\n                            \'linkname\'    => $row[\'linkname\'],\n                            \'url\'         => $row[\'url\'],\n                            \'description\' => $row[\'description\'],\n                            \'date\'        => $row[\'date\'],\n                            \'category\'    => $row[\'category\'],\n                        );\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row[\'linkname\'], $row[\'url\']));\n                        $uniqrs[] = $row[\'id\'];\n                        $thislink = $safe;\n                    }\n                }\n            }\n            break;\n    }\n\n    $smd_tags[\'meta\'][\'content_count\'] = $contentCount;\n\n    if ($out) {\n        return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n    }\n\n    return \'\';\n}\n\n// ------------------------\n// List tags from current context, or given type\nfunction smd_tag_list($atts = array(), $thing = null)\n{\n    global $smd_tags, $smd_thistag;\n    static $smd_tags_tree = array();\n    static $smd_tags_count = array();\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'flavour\'      => \'list\', // 1) list: only tags with assignments, 2) cloud (adds weighting to smd_thistag), 3) head (first tag in list), 4) tail (last tag in list)\n        \'showall\'      => \'0\', // 0: just used, 1: include empties\n        \'type\'         => \'\',\n        \'id\'           => \'\',\n        \'name\'         => \'\',\n        \'exclude\'      => \'\',\n        \'parent\'       => \'\', // Start list from here\n        \'sublevel\'     => \'\', // Get tags from this sub-level only (0=top level, 1-level 1, ..., all=everything)\n        \'auto_detect\'  => \'1\',\n        \'offset\'       => 0,\n        \'limit\'        => 999999,\n        \'form\'         => \'\',\n        \'indent\'       => \'&#160;&#160;\',\n        \'section_link\' => \'\',\n        \'sort\'         => \'\',\n        \'shuffle\'      => 0,\n        \'label\'        => \'\',\n        \'labeltag\'     => \'\',\n        \'wraptag\'      => \'ul\',\n        \'break\'        => \'li\',\n        \'class\'        => __FUNCTION__,\n        \'active_class\' => \'\', // TODO\n        \'breakclass\'   => \'\',\n    ), $atts));\n\n    // Validate client side atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ids = \'\';\n    $where = array();\n    $ctxt = smd_tags_context();\n\n    $attemptMatch = (empty($name) && empty($id) && empty($exclude) && !empty($ctxt[\'id\'])) ? false : true;\n    $type = (in_array($type, $validTypes)) ? $type : (($ctxt[\'context\']) ? $ctxt[\'context\'] : $validTypes[0]);\n    $ids = ($id) ? do_list($id) : (($auto_detect && $ctxt[\'id\']) ? $ctxt[\'id\'] : \'\');\n\n    // Unique identifiers for cacheing the lists\n    $gopts = $name.$id.$exclude.$parent.$sublevel;\n    $guid = md5($gopts);\n\n    if ($name) {\n        $name = \"name IN (\" .join(\',\', quote_list(do_list($name))). \")\";\n        $where[] = $name;\n        $ids = \'\'; // name trumps id; TODO: maybe offer a way of combining them?\n    }\n\n    if ($ids) {\n        $ids = \"id IN (\" .join(\',\', quote_list($ids)). \")\";\n        $where[] = $ids;\n    }\n\n    if ($exclude) {\n        $exclude = \"name NOT IN (\" .join(\',\', quote_list(do_list($exclude))). \")\";\n        $where[] = $exclude;\n    }\n\n    if (strtolower($parent) == \'smd_auto\') {\n        $parent = \'\';\n        $plist = (isset($smd_tags[$type])) ? $smd_tags[$type][\'tag_name\'] : \'\';\n        if (is_array($plist)) {\n            foreach ($plist as $idx => $pname) {\n                $parent = $pname;\n                break;\n            }\n        }\n    }\n\n    $where = join(\' AND \', $where);\n    $oper = \'\';\n\n    if (!$where && isset($smd_tags[$type])) {\n        // Use URL tags\n        if ($showall) {\n            if (isset($smd_tags_tree[$type][$guid][\'all\'])) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'];\n            } else {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'] = smd_tag_tree((($parent) ? $parent : \'root\'), $type, \'1=1\', SMD_TAG);\n            }\n        } else {\n            // Was just the parent attribute supplied?\n            if ($parent) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'] = smd_tag_tree($parent, $type, \'1=1\', SMD_TAG);\n            } else {\n                $rs = $smd_tags[$type];\n            }\n        }\n    } else {\n        if (!$where && $attemptMatch && !$showall) {\n            $where = \'\';\n            $rs = array();\n        } else {\n            $where = ($where) ? $where : \'1=1\';\n        }\n\n        if ($where) {\n            trace_add(\'[smd_tag_list query: \' . $where . \']\');\n        }\n\n        if ($where) {\n            if ( ($showall || $where == \'1=1\') && isset($smd_tags_tree[$type][$guid][\'all\']) ) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'];\n            } else {\n                $windex = md5($gopts.$where);\n\n                if (isset($smd_tags_tree[$type][$windex])) {\n                    $rs = $smd_tags_tree[$type][$windex];\n                } else {\n                    $rs = $smd_tags_tree[$type][$windex] = smd_tag_tree((($parent) ? $parent : \'root\'), $type, (($showall) ? \'1=1\' : $where), SMD_TAG);\n                }\n            }\n        }\n\n        $sublevel = ($parent && $sublevel==\'\') ? ((smd_tag_tree_search($parent, $rs, 1)) ? \'>=1\' : \'>=0\') : $sublevel;\n        preg_match(\'/([=<>]+)?([\\d]+|all)/\', $sublevel, $matches);\n\n        if ($matches) {\n            $oper = ($matches[1] && in_list($matches[1], \'>, <, >=, <=\')) ? $matches[1] : \'==\';\n            $sublevel=$matches[2];\n        } else {\n            $oper = \'==\';\n        }\n\n        trace_add(\'[smd_tag_list sublevel | operator | matches: \' . $sublevel . \'|\' . $oper . \'|\' . join(\', \', $matches) . \']\');\n    }\n\n    trace_add(\'[smd_tag_list tree: \' . print_r($rs, true) . \']\');\n\n    if ($rs) {\n        $out = array();\n        $totals = array();\n        $outsub = array();\n\n        foreach ($rs as $key => $row) {\n            // Only add the row if one of:\n            // 1) showall is on\n            // 2) sublevel = \'all\'\n            // 3) no parent specified\n            // 4) sublevel is equal/greater/less than the row\'s level\n            if ($showall == 1 || strtolower($sublevel) == \"all\" || !$parent || ($oper==\'==\'? $row[\'level\'] == $sublevel : (($oper==\'>=\') ? $row[\'level\'] >= $sublevel : (($oper==\'<=\') ? $row[\'level\'] <= $sublevel : (($oper==\'>\') ? $row[\'level\'] > $sublevel : $row[\'level\'] < $sublevel) ) ) ) ) {\n                $outsub[] = $row;\n            }\n        }\n\n        $rs = $outsub;\n        trace_add(\'[smd_tag_list sublevel records: \' . print_r($rs, true) . \']\');\n\n        if ($shuffle) {\n            shuffle($rs);\n        } elseif ($sort) {\n            $sortPrefix = \"SORT_\";\n            $sortOrder = array();\n            $sort = do_list($sort);\n            $nsrt = count($sort);\n\n            for ($idx = 0; $idx < $nsrt; $idx++) {\n                $sort_dir = explode(\' \', $sort[$idx]);\n\n                if (count($sort_dir) <= 1) {\n                    $sort_dir[1] = \"asc\";\n                }\n\n                $direction = ($sort_dir[1] == \"desc\") ? $sortPrefix.\'DESC\' : $sortPrefix.\'ASC\';\n                $sortOrder[] = array(\"col\" => $sort_dir[0], \"sort\" => $direction);\n            }\n\n            // Translate the rows into columns that can be sorted\n            foreach ($rs as $key => $row) {\n                foreach ($row as $identifier => $item) {\n                    $varname = \"col_\".$identifier;\n                    ${$varname}[$key] = $item;\n                }\n            }\n\n            // Make up an array_multisort arg list and execute it.\n            // The necessary evil() is because we don\'t know how many cols there will be in the array\n            for ($idx = 0; $idx < count($sortOrder); $idx++) {\n                $sortargs[] = \'$col_\'.$sortOrder[$idx][\'col\'];\n                $sortargs[] = $sortOrder[$idx][\'sort\'];\n            }\n\n            $sortit = \'array_multisort(\'.join(\", \",$sortargs).\', $rs);\';\n            eval($sortit);\n        }\n\n        $rs = array_slice($rs, $offset, $limit);\n        trace_add(\'[smd_tag_list post-filter records: \' . print_r($rs, true) . \']\');\n        $numrecs = count($rs);\n\n        // Cache the relevant info from the tag usage table\n        if (!isset($smd_tags_count[$type])) {\n            $taguse = safe_rows(\'tag_id, count(*) AS tally\', SMD_TAGU, \"type=\'\" . doSlash($type) . \"\' GROUP BY tag_id\");\n\n            foreach ($taguse as $tagblob) {\n                $smd_tags_count[$type][$tagblob[\'tag_id\']] = $tagblob[\'tally\'];\n            }\n        }\n\n        // Set up the cloud weighting if desired\n        $flavour = do_list($flavour, \':\');\n\n        if ($flavour[0] == \'cloud\') {\n            $minPercent = (isset($flavour[1]) && !empty($flavour[1])) ? $flavour[1] : 100;\n            $maxPercent = (isset($flavour[2]) && !empty($flavour[2])) ? $flavour[2] : 200;\n            $max = 1; $min = 99999; $count = 0;\n\n            foreach ($rs as $row) {\n                $count = (isset($smd_tags_count[$type][$row[\'id\']])) ? $smd_tags_count[$type][$row[\'id\']] : 0;\n                $max = ($count > $max ? $count : $max);\n                $min = ($min > $count ? $count : $min);\n            }\n\n            $multiplier = ($max > $min) ? ($maxPercent - $minPercent) / ($max - $min) : 1;\n        }\n\n        $tag_head = (isset($smd_tags[\'meta\'][\'tag_head\'])) ? $smd_tags[\'meta\'][\'tag_head\'] : \'\';\n        $tag_tail = (isset($smd_tags[\'meta\'][\'tag_tail\'])) ? $smd_tags[\'meta\'][\'tag_tail\'] : \'\';\n        $tag_list = (isset($smd_tags[\'meta\'][\'tag_list\'])) ? $smd_tags[\'meta\'][\'tag_list\'] : array();\n\n        // Preserve the current tag so lists in lists can be created\n        $old_tag = $smd_thistag;\n\n        // Iterate over the tags, populate the local context (global!) variable and parse through the form/container\n        foreach ($rs as $idx => $row) {\n            $index = $idx; // Because we can muck about with this value without affecting the loop counter\n\n            if (($flavour[0] == \'head\') && ($row[\'name\'] != $tag_head)) {\n                continue;\n            }\n\n            if (($flavour[0] == \'tail\') && ($row[\'name\'] != $tag_tail)) {\n                continue;\n            }\n\n            $row[\'type\'] = $type;\n            $row[\'count\'] = (isset($smd_tags_count[$type][$row[\'id\']])) ? $smd_tags_count[$type][$row[\'id\']] : 0;\n            $row[\'indent\'] = str_repeat($indent, $row[\'level\'] * 1);\n            $row[\'weight\'] = ($flavour[0] == \'cloud\') ? floor($minPercent + (($max - ( $max - ($row[\'count\'] - $min))) * $multiplier)) : 1;\n\n            if (($flavour[0] == \'crumb\') && (($pos = array_search($row[\'name\'], $tag_list)) !== false)) {\n                // Override the position of this entry in the list to fake a crumbtrail\n                $index = $pos;\n            }\n\n            $row[\'first\'] = ($index == 0) ? 1 : 0;\n            $row[\'last\'] = ($index == ($numrecs-1)) ? 1 : 0;\n\n            smd_tag_populate($row);\n            $out[$index] = ($thing) ? parse($thing) : (($form) ? parse_form($form) :\n                    str_repeat($indent, $row[\'level\'] * 1)\n                    .smd_tag_name(array(\'title\' => 1, \'section\' => $section_link))\n                    .smd_tag_count(array()));\n            $smd_thistag = array();\n        }\n\n        // In case the order has been fudged for crumbtrail\n        ksort($out);\n\n        // Restore previous tag, if any\n        $smd_thistag = $old_tag;\n\n        if ($out) {\n            return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class, $breakclass);\n        }\n    }\n\n    return \'\';\n}\n\n/*\n\nregister_callback(\'tru_tags_atom_handler\', \'atom_entry\');\nfunction tru_tags_atom_handler($event, $step) { return tru_tags_feed_handler(true); }\nregister_callback(\'tru_tags_rss_handler\', \'rss_entry\');\nfunction tru_tags_rss_handler($event, $step) { return tru_tags_feed_handler(false); }\n\nfunction tru_tags_feed_handler($atom) {\n    global $thisarticle, $tru_tags_prefs;\n    extract($thisarticle);\n\n    $tags = tru_tags_get_tags_for_article($thisid);\n\n    if ($tru_tags_prefs[TAGS_IN_FEED_BODY]->value) {\n        $extrabody = \'\';\n        $FORM_NAME = \'tru_tags_feed_tags\';\n        if (fetch(\'form\', \'txp_form\', \'name\', $FORM_NAME)) {\n            $form = fetch_form($FORM_NAME);\n            $extrabody = trim(parse($form));\n        } else {\n            $atts = tru_tags_get_standard_cloud_atts(array(), false, true);\n            $extrabody = \'<p>tags: \' . tru_tags_render_cloud($atts, $tags, $tags) . \'</p>\';\n        }\n        global $thisarticle;\n        if (trim($thisarticle[\'excerpt\'])) {\n            $thisarticle[\'excerpt\'] = trim($thisarticle[\'excerpt\']).n.$extrabody.n;\n        }\n        $thisarticle[\'body\'] = trim($thisarticle[\'body\']).n.$extrabody.n;\n    }\n\n    if ($tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value) {\n        $output = array();\n        foreach ($tags as $tag) {\n            if ($atom) {\n                $output[] = \'<category term=\"\' . htmlspecialchars($tag) . \'\" />\';\n            } else {\n                $output[] = \'<category>\' . htmlspecialchars($tag) . \'</category>\';\n            }\n        }\n        return n.join(n, $output).n;\n    }\n}\n\n*/', '//<?php\n/**\n * smd_tags: A Textpattern CMS plugin for unlimited, structured taxonomy across content types.\n *\n * Features:\n *   Article, image, file and link tags\n *   Rapid tag creation / editing / multi-editing\n *   Optionally import tags from rss_uc, tru_tags, category1/2 or custom fields\n *   Create tag lists, trees and clouds\n *   Find related content based on tag\n *   Tag combinators from the URL\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n *\n * @todo\n * Remove EvalElse()\n * Alter tag pool layout to allow collapsing groups of parent->children\n * Use article_row_info(), link_format_info()\n * Investigate storing tag data as jQuery .data() instead of in <span>s (from Txp 4.6+ it can be done in HTML data-* attributes)\n * Pagination of tag lists / related content\n * Feed handler prefs to inject tags into RSS+Atom (like tru_tags: code at bottom of this plugin)\n * Add option to display tag lists AND TextArea(+) on list pages\n * See if rapid tagging can be made more intuitive wrt Create/Save to avoid clobbering already created tags (from pieman http://forum.textpattern.com/viewtopic.php?pid=240099#p240099)\n * When refreshing the Manage tab, FF remembers the Title and tries to add it on page load, throwing a msgarea warning about the tag already exists (or that name is empty)\n * Possibility of section tags? (from jakob)\n * Per-section / per-category tag counts\n * Query tags without section/article context (Dwayne: http://forum.textpattern.com/viewtopic.php?pid=246750#p246750)\n * On list pages:\n *   AJAX fetch of the tag set for TextArea and TextArea+ modes to improve page load time (plus we then only grab the ones in the current category hierarchy)\n *   Add autocomplete options to smd_tags prefs so autofill etc can be configured (from aswihart)\n *   Check what happens when deleting content (e.g. images): it sometimes triggers \" \'\' is not an Integer \" assertion (from curiouz http://forum.textpattern.com/viewtopic.php?pid=236023#p236023)\n *   Perhaps offer sort option for List mode to a) retain hierarchy, b) strict alphabetical, c) group them somehow to show how sets of tags are related (from pieman)\n * @tofix\n * Sanitize tags from custom fields / cats during import\n * Blob of output from AJAX (cf. \"If you spot such JavaScript responses your plugin breaks AJAX at the write tab, for instance by echo()ing something as text/html when send_script_response() with text/javascript would be the proper reply with the expected mime type.\")\n * Query count -- cache tag sets and verify URL tag lists are as optimum as possible (see smd_bio)\n * Live search sometimes gives wrong results when group tags contain no (or very few) children\n * Verify limit/offset behaviour (giz: http://forum.textpattern.com/viewtopic.php?pid=244020#p244020)\n * smd_tags_context() : Invalid argument supplied for foreach() (frickinmuck: http://forum.textpattern.com/viewtopic.php?pid=249250#p249250)\n * Deal with parent attribute in smd_tag_name better (stop it showing \'root\'?)\n * Detect scenario when article saves fail and don\'t update tags under these circumstances (e.g. when concurrent users are editing the same document)\n */\n\n// ------------------------\n// ADMIN SIDE CODE\nif (txpinterface === \'admin\') {\n    add_privs(\'smd_tags\',\'1,2\');\n    add_privs(\'smd_tags_users\',\'1,2,3,4,5,6\');\n    add_privs(\'plugin_prefs.smd_tags\',\'1,2\');\n\n    // Extensions tab\n    register_tab(\'extensions\', \'smd_tags\', gTxt(\'smd_tags\'));\n    register_callback(\'smd_tags\', \'smd_tags\');\n    register_callback(\'smd_tags_users\', \'smd_tags_users\');\n\n    // Note the callbacks are ordered with \'save\' first\n    register_callback(\'smd_tags_multi_edit\', \'list\', \'list_multi_edit\');\n    register_callback(\'smd_tags_savelist\', \'article\', \'create\');\n    register_callback(\'smd_tags_savelist\', \'article\', \'edit\');\n    register_callback(\'smd_tags_loadlist\', \'article\');\n\n    register_callback(\'smd_tags_multi_edit\', \'image\', \'image_delete\');\n    register_callback(\'smd_tags_savelist\', \'image\', \'image_save\');\n    register_callback(\'smd_tags_loadlist\', \'image\');\n\n    register_callback(\'smd_tags_multi_edit\', \'file\', \'file_delete\');\n    register_callback(\'smd_tags_savelist\', \'file\', \'file_save\');\n    register_callback(\'smd_tags_loadlist\', \'file\');\n\n    register_callback(\'smd_tags_multi_edit\', \'link\', \'link_multi_edit\');\n    register_callback(\'smd_tags_savelist\', \'link\', \'link_post\');\n    register_callback(\'smd_tags_savelist\', \'link\', \'link_save\');\n    register_callback(\'smd_tags_loadlist\', \'link\');\n\n    register_callback(\'smd_tags_setup\', \'plugin_prefs.smd_tags\');\n    register_callback(\'smd_tags_welcome\', \'plugin_lifecycle.smd_tags\');\n    register_callback(\'smd_tags_inject_css\', \'admin_side\', \'head_end\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'smd_tag_list\')\n            ->register(\'smd_tag_name\')\n            ->register(\'smd_tag_count\')\n            ->register(\'smd_tag_info\')\n            ->register(\'smd_if_tag\')\n            ->register(\'smd_if_tag_list\')\n            ->register(\'smd_related_tags\');\n    }\n\n    register_callback(\'smd_tags_url_handler\', \'pretext\');\n}\n\nif (!defined(\'SMD_TAG\')) {\n    define(\"SMD_TAG\", \'smd_tags\');\n}\nif (!defined(\'SMD_TAGC\')) {\n    define(\"SMD_TAGC\", \'smd_tags_cat\');\n}\nif (!defined(\'SMD_TAGU\')) {\n    define(\"SMD_TAGU\", \'smd_tags_used\');\n}\n\n/**\n * Runs on plugin installation.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags_welcome($evt, $stp)\n{\n    $msg = \'\';\n    switch ($stp) {\n        case \'installed\':\n            smd_tags_table_install(0);\n            $msg = \'Happy tagging!\';\n            break;\n        case \'deleted\':\n            smd_tags_prefs_remove(\'\', 0);\n            break;\n    }\n    return $msg;\n}\n\n/**\n * Step dispatcher.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_tag_catlist\'           => true,\n        \'smd_tag_create\'            => true,\n        \'smd_tag_parentlist\'        => true,\n        \'smd_tag_save\'              => true,\n        \'smd_tags_delete\'           => true,\n        \'smd_tags_import\'           => true,\n        \'smd_tags_import_one\'       => true,\n        \'smd_tags_manage\'           => false,\n        \'smd_tags_multi_set_parent\' => true,\n        \'smd_tags_multi_catlink\'    => true,\n        \'smd_tags_prefs_show\'       => false,\n        \'smd_tags_prefs_install\'    => false,\n        \'smd_tags_prefs_remove\'     => false,\n        \'smd_tags_prefs_update\'     => true,\n        \'smd_tags_sync\'             => false,\n        \'smd_tags_table_install\'    => false,\n        \'smd_tags_table_remove\'     => false,\n        \'smd_tags_table_rebuild\'    => false,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        $pref = smd_tags_pref_get(\'smd_tag_t_astart\');\n        $stp = \'smd_tags_prefs_show\';\n\n        if ($pref) {\n            switch ($pref[\'smd_tag_t_astart\'][\'val\']) {\n                case 1:\n                    $stp = \'smd_tags_manage\';\n                    break;\n                case 0:\n                default:\n                    $stp = \'smd_tags_prefs_show\';\n            }\n        }\n    }\n\n    $stp();\n}\n\n/**\n * Ajax step dispatcher.\n *\n * @param      string  $evt    Textpattern event (panel)\n * @param      string  $stp    Textpattern step (action)\n */\nfunction smd_tags_users($evt, $stp)\n{\n    $available_steps = array(\n        \'smd_tag_catlist\'    => true,\n        \'smd_tag_parentlist\' => true,\n        \'smd_tag_get_desc\'   => true,\n    );\n\n    if (!$stp or !bouncer($stp, $available_steps)) {\n        // Do nothing\n    } else {\n        $stp();\n    }\n}\n\n// ------------------------\n// Create dropdown and populate it with tags of the relevant type, highlighting any $sel tags\nfunction smd_multiTreeSelectInput($select_name = \'\', $tree = \'\', $sel = \'\') {\n    $pref = smd_tags_pref_get(array(\'smd_tag_p_size\', \'smd_tag_p_listpar\'), 1);\n    $rows = $pref[\'smd_tag_p_size\'][\'val\'];\n    $incl = $pref[\'smd_tag_p_listpar\'][\'val\'];\n\n    $out[] = \'<select id=\"\'.$select_name.\'\" name=\"\'.$select_name.\'[]\" class=\"list\" size=\"\'. $rows .\'\"\' .(($rows==1) ? \'\' : \' multiple=\"multiple\"\'). \'>\';\n    foreach ($tree as $leaf) {\n        if ($leaf[\'name\'] == \'root\') continue;\n\n        $selected=\'\';\n        foreach ($sel as $gid => $item) {\n            if ($leaf[\'id\'] == $item[\'tag_id\']) {\n                $selected = \' selected=\"selected\"\';\n                continue;\n            }\n        }\n\n        $leaf[\'name\'] = htmlspecialchars($leaf[\'name\']);\n        $indent = ($leaf[\'level\'] > 0) ? str_repeat(sp.sp,$leaf[\'level\']) : \'\';\n        $selectable = (!$incl && $leaf[\'children\'] > 0) ? \' disabled=\"disabled\"\' : \'\';\n        $out[] = t.\'<option value=\"\'.$leaf[\'id\'].\'\"\'.$selected.$selectable.\'>\'.$indent.htmlspecialchars($leaf[\'title\']).\'</option>\';\n    }\n    $out[] = \'</select>\';\n    return join(\'\',$out);\n}\n\n// ------------------------\n// Called whenever one of the 4 main tabs are used on the admin side. Inserts the input method if required\nfunction smd_tags_loadlist($evt, $stp) {\n    global $app_mode, $step;\n\n    if (smd_tags_table_exist()) {\n        $ctrls = smd_tags_pref_get(array(\'smd_tag_p_enable\', \'smd_tag_p_input\', \'smd_tag_p_size\', \'smd_tag_p_qtag\', \'smd_tag_p_qtpath\', \'smd_tag_p_qtstyl\', \'smd_tag_p_linkcat\', \'smd_tag_t_desc_tooltip\'), 1);\n        $onoff = smd_tags_pref_explode($ctrls[\'smd_tag_p_enable\'][\'val\']);\n        $iptyp = $ctrls[\'smd_tag_p_input\'][\'val\'];\n        $selsz = $ctrls[\'smd_tag_p_size\'][\'val\'];\n        $quick = $ctrls[\'smd_tag_p_qtag\'][\'val\'];\n        $jsdir = $ctrls[\'smd_tag_p_qtpath\'][\'val\'];\n        $csdir = $ctrls[\'smd_tag_p_qtstyl\'][\'val\'];\n        $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n        $toolt = $ctrls[\'smd_tag_t_desc_tooltip\'][\'val\'];\n        $jstail = ($jsdir) ? \'/\' : \'\';\n        $cstail = ($csdir) ? \'/\' : \'\';\n        $addIt = false;\n\n        $itemID = smd_getID();\n        $edtBtn = has_privs(\'smd_tags\') ? \' [\'.eLink(\'smd_tags\', \'smd_tags_manage\', \'smd_tag_type\', $evt, gTxt(\'edit\')).\'] \' : \'\';\n        $tagtop = gTxt(\'smd_tag_title\').$edtBtn;\n        $selist = \'<span id=\"smd_tags\" name=\"smd_tags_bylist\" class=\"smd_tagip\"></span>\';\n        $selhid = \'<span id=\"smd_tags\" name=\"smd_tags_bylist\" class=\"smd_hidden\"></span>\';\n        $tooltip = ($toolt) ? \'<span id=\"smd_tag_tt\" class=\"smd_tooltip smd_hidden\"></span>\' : \'\';\n        $widget = \'\';\n\n        switch ($iptyp) {\n            case 0:\n                $ipmeth = $selist;\n                break;\n            case 1:\n                $ipmeth = \'<span name=\"smd_tags_bylink\" id=\"smd_tags_bylink\" class=\"smd_tagip\"></span>\'.$selhid;\n                break;\n            case 2:\n            case 3:\n                $ipmeth = \'<textarea name=\"smd_tags_bytext\" rows=\"\'.$selsz.\'\" class=\"smd_tagip\"></textarea>\'.$selhid;\n                break;\n        }\n\n        switch ($evt) {\n            case \"article\":\n                $addIt = ($onoff[gTxt(\'tab_list\')] == 1) ? true : false;\n                if ($addIt) {\n                    extract(gpsa(array(\'view\',\'from_view\')));\n                    $view = ($view) ? $view : \"text\";\n                    $grabcats = \'jQuery(\"#category-1 option:selected, #category-2 option:selected\")\';\n                    $jsElem = \'jQuery(\".category-2\").parent()\';\n                    $trigger = \'jQuery(\"#category-1, #category-2\")\';\n                    if ($view != \'text\') {\n                        $widget = \'\';\n                    } else {\n                        $widget = escape_js(\'<div>\'.$tagtop.\'</div>\' . $ipmeth);\n                    }\n                }\n                break;\n            case \"image\":\n                if ($step == \"image_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_image\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#image_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-image-category\")\';\n                        $trigger = \'jQuery(\"#image_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n            case \"file\":\n                if ($step == \"file_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_file\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#file_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-file-category\")\';\n                        $trigger = \'jQuery(\"#file_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n            case \"link\":\n                if ($step == \"link_edit\") {\n                    $addIt = ($onoff[gTxt(\'tab_link\')] == 1) ? true : false;\n                    if ($addIt) {\n                        $grabcats = \'jQuery(\"#link_category option:selected\")\';\n                        $jsElem = \'jQuery(\".edit-link-category\")\';\n                        $trigger = \'jQuery(\"#link_category\")\';\n                        $widget = escape_js(inputLabel(\'smd_tags\', $ipmeth, $tagtop));\n                    }\n                }\n                break;\n        }\n\n        // Don\'t re-serve the css+javascript if this is an AJAX save\n        if ($app_mode == \'async\') {\n            $addIt = false;\n        }\n\n        // Add the dropdown and js toggle/clear options if required\n        if ($addIt) {\n            $qs = array(\n                \"event\" => \"smd_tags_users\",\n            );\n\n            $qsVars = \"index.php\".join_qs($qs);\n\n            if ($quick > 0) {\n                echo <<<EOJS\n<script type=\"text/javascript\" src=\"{$jsdir}{$jstail}jquery.autocomplete.pack.js\"></script>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{$csdir}{$cstail}jquery.autocomplete.css\"></script>\nEOJS;\n            }\n\n            echo script_js(<<<EOJS\njQuery(function() {\n    {$jsElem}.after(\'{$widget}{$tooltip}\');\n\n    function smd_tagList(typ) {\n        var grabcats = [];\n        {$grabcats}.each(function() {\n            if (jQuery(this).val() != \'\') {\n               grabcats.push(jQuery(this).val());\n            }\n        });\n        grabcats = grabcats.join(\",\");\n        var smd_link_mode = ((\'{$clink}\'==\'0\') ? \'2\' : ((grabcats==\'\') ? 1 : \'\'));\n\n        jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: \'smd_tag_parent\', type: typ, cat: grabcats, link_mode: smd_link_mode, itemid: \"{$itemID}\", _txp_token: textpattern._txp_token },\n            function(data) {\n                jQuery(\"#smd_tags\").html(data);\n                jQuery(\"#smd_tags_bylink\").empty();\n                var smd_tagpool = [];\n                var smd_tagsel = [];\n                var smd_taglinks = [];\n                jQuery(\'#smd_tags select option\').each(function() {\n                    var curr = jQuery(this);\n                    var txt = jQuery.trim(curr.text());\n                    smd_tagpool.push(txt);\n\n                    if (jQuery(\"#smd_tags_bylink\").text() != \"\") {\n                        jQuery(\"#smd_tags_bylink\").append(\" &middot; \");\n                    }\n\n                    if (curr.prop(\"selected\") == true) {\n                        smd_tagsel.push(txt);\n                        jQuery(\"#smd_tags_bylink\").append(\'<span class=\"smd_sel\">\'+txt+\'</span>\');\n                    } else {\n                        jQuery(\"#smd_tags_bylink\").append(\'<span>\'+txt+\'</span>\');\n                    }\n                });\n\n                jQuery(\"#smd_tags_bylink span\").click(function() {\n                    jQuery(this).toggleClass(\"smd_sel\");\n                    var ltxt = jQuery(this).text();\n                    jQuery(\"#smd_tags select option\").each(function() {\n                        var curr = jQuery(this);\n                        if (jQuery.trim(curr.text()) == ltxt) {\n                            this.selected = !this.selected;\n                        }\n                    });\n                });\n\n                jQuery(\"textarea[name=\'smd_tags_bytext\']\").val(smd_tagsel.join(\", \"));\n\n                if ({$quick} > 0) {\n                    jQuery(\"textarea[name=\'smd_tags_bytext\']\").autocomplete(smd_tagpool, {\n                        multiple: true,\n                        mustMatch: (($quick==2)?true:false),\n                        autoFill: true\n                    });\n                }\n                jQuery(\"textarea[name=\'smd_tags_bytext\']\").keyup(function() {\n                    smd_tagsResyncSelect({$quick});\n                });\n                jQuery(\'#smd_clr\').click(function() {\n                    jQuery(\'#smd_tags select option\').prop(\"selected\", \"\");\n                });\n                jQuery(\'#smd_tog\').click(function() {\n                    jQuery(\'#smd_tags select option\').each(function() {\n                        this.selected = !this.selected;\n                    });\n                });\n\n                // Tooltips\n                if (\'{$toolt}\' == \'1\') {\n                    jQuery(\'#smd_tags select, #smd_tags_bylink\').hover( function(ev) {\n                        // Do nothing\n                }, function() {\n                        // Hide tooltip when exiting the entire tag area\n                        jQuery(\'.smd_tooltip\').hide(\'fast\');\n                    });\n                    jQuery(\'#smd_tags select option, #smd_tags_bylink span\').hover( function(ev) {\n                        var thisTag = jQuery(this).val();\n                        if (!thisTag) {\n                            thisTag = jQuery(this).html();\n                        }\n                        if (jQuery(\'#smd_tag_tt\').data(thisTag)) {\n                            smd_tags_show_desc(ev, jQuery(\'#smd_tag_tt\').data(thisTag));\n                        } else {\n                            jQuery.post(\'{$qsVars}\',\n                            {\n                                step: \"smd_tag_get_desc\",\n                                tag_ref: thisTag,\n                                _txp_token: textpattern._txp_token\n                            },\n                            function(data) {\n                                jQuery(\'#smd_tag_tt\').data(thisTag, data);\n                                smd_tags_show_desc(ev, jQuery(\'#smd_tag_tt\').data(thisTag));\n                            });\n                        }\n                    });\n                }\n            }\n        );\n    }\n\n    function smd_tags_show_desc(ev, content) {\n        jQuery(\'#smd_tag_tt\').hide().empty().append(content).show();\n        jQuery(\'.smd_tooltip\').css(\'left\', ev.pageX - 60).css(\'top\', ev.pageY + 15).css(\'display\', \'block\');\n    }\n\n    smd_tagList(\'{$evt}\');\n    if (\'{$clink}\'==\'1\') {\n        {$trigger}.change(function() {\n            smd_tagList(\'{$evt}\');\n        });\n    }\n\n    function smd_tagsResyncSelect(lvl) {\n        var tbTags = jQuery(\"textarea[name=\'smd_tags_bytext\']\").val().split(/,\\s+?/);\n        jQuery(\"#smd_tags select option\").each(function() {\n            var curr = jQuery(this);\n            if(jQuery.inArray(jQuery.trim(curr.text()), tbTags) == -1) {\n                curr.prop(\"selected\", \"\");\n            } else {\n                curr.prop(\"selected\", true);\n            }\n        });\n    }\n});\nEOJS\n            );\n        }\n    }\n}\n\n// ------------------------\n// Update the tags used table if some tags have been added/saved\nfunction smd_tags_savelist($evt, $stp) {\n    if (smd_tags_table_exist()) {\n        $ctrls = smd_tags_pref_get(array(\'smd_tag_p_qtag\', \'smd_tag_p_input\', \'smd_tag_p_linkcat\', \'smd_tag_p_master\'), 1);\n        $quick = $ctrls[\'smd_tag_p_qtag\'][\'val\'];\n        $iptyp = $ctrls[\'smd_tag_p_input\'][\'val\'];\n        $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n        $god = $ctrls[\'smd_tag_p_master\'][\'val\'];\n        $god_clause = ($god == \'\') ? \'\' : \" AND parent = \'\" . doSlash($god) . \"\'\";\n\n        $itemID = smd_getID();\n\n        if ($itemID) {\n            // Can\'t use the \'edit\' step because otherwise save triggers on article page load;\n            // hence, the \'save\' button state is used instead. Also, on article creation, the global\n            // step is set to \'edit\' by the time the plugin is hit, so we hack the step if the \'publish\'\n            // button was hit\n            $stp = (ps(\'publish\')) ? \'publish\' : $stp;\n            $saveSteps = array(\'create\', \'publish\', \'image_save\', \'file_save\', \'link_save\', \'link_post\');\n            if (ps(\'save\') || in_array($stp, $saveSteps)) {\n                safe_delete(SMD_TAGU, \'item_id=\"\'.$itemID.\'\" AND type=\"\'.$evt.\'\"\');\n\n                $tags = ps(\'smd_tag_parent\') ? ps(\'smd_tag_parent\') : array();\n\n                $allowedTags = array();\n                if ($god_clause) {\n                    $allowedTags = safe_column(\'id\', SMD_TAG, \"type=\'$evt\'\" . $god_clause);\n                }\n\n                // Validate the incoming tags to those in the currently selected category|ies\n                if ($clink) {\n                    $usedCats = ($evt==\"article\") ? array(gps(\'Category1\'), gps(\'Category2\')) : array(gps(\'category\'));\n                    $catids = safe_column(\'id\', \'txp_category\', \"name IN (\" .join(\',\', quote_list($usedCats)). \") AND type=\'$evt\'\");\n                    $validTags = ($catids) ? safe_column(\'tag_id\', SMD_TAGC, \"cat_id IN (\" .join(\',\', quote_list($catids)). \")\") : array();\n                    foreach ($validTags as $validTag) {\n                        $rows = smd_tag_tree(safe_field(\'name\',SMD_TAG,\"id=\'\".doSlash($validTag).\"\'\"), $evt, \'1=1\', SMD_TAG);\n                        foreach ($rows as $row) {\n                            $allowedTags[] = $row[\'id\'];\n                        }\n                    }\n                    $tags = array_intersect(array_unique($allowedTags), $tags);\n                }\n\n                $vals = array();\n                foreach($tags as $val){\n                    safe_insert(SMD_TAGU, \'tag_id = \"\'.$val.\'\", item_id=\"\'.$itemID.\'\", type=\"\'.$evt.\'\"\');\n                    $vals[] = $val;\n                }\n                $tagsText = preg_split(\"/,\\s+?/\", ps(\'smd_tags_bytext\'));\n\n                // If we\'re in non-STRICT mode and there are any typed tags that are not in the select list, check they exist and make links to them\n                if ($quick < 2) {\n                    $added = ($vals) ? safe_column(\"title\", SMD_TAG, \"id IN (\'\".join(\"\',\'\",$vals).\"\') AND type=\'$evt\'\") : array();\n                    $tagsText = array_diff($tagsText, $added);\n\n                    // Only loop over tags that don\'t appear to be already defined\n                    foreach($tagsText as $tag) {\n                        if ($tag == \"\") continue;\n                        $exists = safe_field(\'id\', SMD_TAG, \"name = \'$tag\' AND type = \'$evt\'\"); // 1st check\n\n                        // Textarea+ input system can add (linked) tags with impunity\n                        if ($iptyp == 3 && !$exists) {\n                            $tagBits = explode(\'-->\', $tag);\n                            if (count($tagBits) == 2) {\n                                $tagparent = strtolower(sanitizeForUrl($tagBits[0]));\n                                $tagparent = safe_field(\'name\', SMD_TAG, \"name = \'$tagparent\' AND type = \'$evt\'\"); // Check parent exists for this type\n                                $tagparent = ($tagparent === false) ? \'root\' : $tagparent;\n                                $tagchild = $tagBits[1];\n                            } else {\n                                $tagparent = \'root\';\n                                $tagchild = $tagBits[0];\n                            }\n                            $sanitag = strtolower(sanitizeForUrl($tagchild));\n                            $already = safe_field(\'id\', SMD_TAG, \"name = \'$sanitag\' AND type = \'$evt\'\"); // Check again because parent-->child will fail 1st check\n                            if ($already === false) {\n                                $exists = safe_insert(SMD_TAG, \"name=\'\".$sanitag.\"\', title=\'$tagchild\', parent=\'$tagparent\', type=\'$evt\'\");\n                            }\n                            $catfield = ($evt==\"article\") ? gps(\'Category1\') : gps(\'category\');\n                            if ($clink && $catfield && $exists) {\n                                // Assign new tag to current category (cat1 for articles)\n                                $catid = safe_insert(SMD_TAGC,\"tag_id=\'$exists\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$catfield\' AND type=\'$evt\')\");\n                            }\n                            rebuild_tree_full($evt, SMD_TAG);\n                        }\n                        if ($exists) {\n                            safe_insert(SMD_TAGU, \'tag_id = \"\'.$exists.\'\", item_id=\"\'.$itemID.\'\", type=\"\'.$evt.\'\"\');\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// ------------------------\n// Handle changes to the tags table during multi-item edits\nfunction smd_tags_multi_edit($evt, $stp) {\n    // In Txp 4.0.6 the only tabs to allow multi-edits are the Article (list) and Link tabs. Cheat for now\n    if (smd_tags_table_exist()) {\n        switch ($evt) {\n            case \"list\":\n            case \"link\":\n                $method = ps(\'edit_method\');\n                $selected = ps(\'selected\');\n                break;\n            default:\n                $method = \"delete\";\n                $selected = array(ps(\'id\'));\n                break;\n        }\n\n        // \'list\' is a special case that equates to articles\n        $type = ($evt == \"list\") ? \"article\" : $evt;\n\n        if ($selected) {\n            switch ($method) {\n                case \"delete\":\n                    $ids = array();\n                    foreach ($selected as $id) {\n                        $id = assert_int($id);\n                        if (safe_delete(SMD_TAGU, \'item_id = \'.$id.\' AND type=\"\'.$type.\'\"\')) {\n                            $ids[] = $id;\n                        }\n                    }\n                    return join(\', \', $ids);\n                    break;\n            }\n        }\n        return \'\';\n    }\n}\n\n// ------------------------\n// Grab the current article/image/file/link ID\nfunction smd_getID()\n{\n    if (!empty($GLOBALS[\'ID\'])) { // newly-saved item\n        $itemID = intval($GLOBALS[\'ID\']);\n    } else {\n        $itemID = (gps(\'ID\')) ? gps(\'ID\') : gps(\'id\');\n    }\n\n    return $itemID;\n}\n\n/**\n * @param  string $evt Textpattern event (panel)\n * @param  string $stp Textpattern step (action)\n */\nfunction smd_tags_inject_css($evt, $stp)\n{\n    global $event;\n\n    $eventmap = array(\n        \'article\' => \'list\',\n        \'file\'    => \'file\',\n        \'image\'   => \'image\',\n        \'link\'    => \'link\',\n        \'list\'    => \'list\',\n    );\n\n    $smd_tag_preflist = smd_tags_get_prefs(\'name\');\n    $prefset = smd_tags_pref_get($smd_tag_preflist);\n    $onoff = smd_tags_pref_explode($prefset[\'smd_tag_p_enable\'][\'val\']);\n    $mapped = isset($eventmap[$event]) ? $eventmap[$event] : \'\';\n    $addIt = ($mapped && $onoff[gTxt(\'tab_\'.$mapped)] == 1) ? true : false;\n\n    $stylereps = array(\n        \'{hov}\' => $prefset[\'smd_tag_t_hover\'][\'val\'],\n        \'{cur}\' => $prefset[\'smd_tag_t_hilite\'][\'val\'],\n    );\n\n    $plugin_styles = array();\n\n    if ($event === \'smd_tags\') {\n        $plugin_styles[] = smd_tags_get_style_rules(\'all\');\n    } elseif ($addIt) {\n        $plugin_styles[] = smd_tags_get_style_rules(\'common\');\n    }\n\n    if ($plugin_styles) {\n        echo \'<style>\' . strtr(implode(n, $plugin_styles), $stylereps) . \'</style>\';\n    }\n\n    return;\n}\n\n/**\n * CSS definitions: hopefully kind to themers.\n */\nfunction smd_tags_get_style_rules($type = \'all\')\n{\n    $smd_tags_styles[\'tag\'] = array(\n        \'.smd_current { padding:3px; font-weight:bold; background:#{cur}; }\',\n        \'.smd_tags_showpars, .smd_tags_linkcat { max-width:12em; }\',\n        \'.smd_tag_list_adm { display: flex; flex-wrap: wrap; justify-content: space-between; }\',\n        \'.smd_tag_list_adm.byrow { flex-direction: column; }\',\n        \'.smd_tag_list_adm.bycol ul { border:1px solid #ccc; padding:1em; }\',\n        \'.smd_tag_list_adm.byrow li { display:inline-block;  }\',\n        \'.smd_tag_list_adm.bycol li { list-style-type:none; }\',\n        \'#smd_tag_filt { float:right; margin:0; padding:1em 2em; box-shadow: 4px 4px 5px #999; }\',\n        \'#smd_tag_multisel { margin:10px 0 0; }\',\n        \'.smd_tags_new label {display:block; margin:2px 1px; }\',\n        \'.smd_tags_new input[type=\"submit\"] { margin:1.5em 0 0; }\',\n        \'.smd_tags_input_group { float:left; margin:0 1em; }\',\n        \'.smd_tags_pool { clear:both; padding:1em 0;}\',\n        \'#smd_tag_report_pane { display:none; position:absolute; left:200px; max-width:500px; border:3px ridge #999; opacity:.92; filter:alpha(opacity:92); padding:15px 20px; background-color:#e2dfce; color:#80551e; }\',\n        \'#smd_tag_report_pane .publish { float:right; }\',\n        \'.smd_tags_btn_ok { position: absolute; top: .27777777777778em; right: .72222222222222em; font-size: 18px; font-weight: 700; text-decoration: none; }\',\n        \'.txp-actions { float:right; }\',\n    );\n\n    $smd_tags_styles[\'common\'] = array(\n        \'.smd_hidden { display:none; }\',\n        \'.smd_hover { cursor:pointer; background:#{hov}; }\',\n        \'.smd_fakebtn, #smd_tags_bylink span { cursor:pointer; }\',\n        \'.smd_tagip { margin:.4em 0 .8em; max-width:400px; }\',\n        \'.smd_sel { font-weight:bold; }\',\n        \'.smd_tooltip { position:absolute; background:#eee; max-width:300px; min-width:150px; border:1px solid black; padding:1em; box-shadow:5px 5px 4px #999; z-index:100; }\',\n    );\n\n    $out = array();\n\n    if ($type === \'all\') {\n        $out = array_merge($smd_tags_styles[\'tag\'], $smd_tags_styles[\'common\']);\n    } elseif (array_key_exists($type, $smd_tags_styles)) {\n        $out = $smd_tags_styles[$type];\n    }\n\n    return implode(n, $out);\n}\n\n/**\n * Fetch all prefs.\n *\n * Internally, it tracks two lists:\n * a) Current prefs\n * b) Legacy prefs that needs removing\n */\nfunction smd_tags_get_prefs($type)\n{\n    $current = array(\n        \'smd_tag_prefs_p\' => array(\n            \'smd_tag_p_enable\'  => \'1111\', // one digit for article/image/file/link. 1=enabled; 0=disabled\n            \'smd_tag_p_input\'   => \'0\', // 0=select, 1=links, 2=textarea, 3=textarea+\n            \'smd_tag_p_lbi\'     => \'0\',\n            \'smd_tag_p_linkcat\' => \'0\',\n            \'smd_tag_p_listpar\' => \'1\', // permit parent in tag tree on content pages\n            \'smd_tag_p_master\'  => \'\', // special \'global\' category (per type: same name for each)\n            \'smd_tag_p_qtag\'    => \'0\', // 0=no autocomplete, 1=std a/c, 2=strict a/c\n            \'smd_tag_p_qtpath\'  => \'\', // Dir to js file(s) relative to textpattern dir\n            \'smd_tag_p_qtstyl\'  => \'\', // Dir to css file(s)\n            \'smd_tag_p_size\'    => \'6\',\n        ),\n        \'smd_tag_prefs_t\' => array(\n            \'smd_tag_t_astart\'       => \'0\',\n            \'smd_tag_t_auto\'         => \'1\',\n            \'smd_tag_t_cols\'         => \'1\',\n            \'smd_tag_t_colsord\'      => \'0\',\n            \'smd_tag_t_count\'        => \'1\',\n            \'smd_tag_t_deltree\'      => \'0\',\n            \'smd_tag_t_delused\'      => \'0\',\n            \'smd_tag_t_desc_textile\' => \'1\',\n            \'smd_tag_t_desc_tooltip\' => \'1\',\n            \'smd_tag_t_enrep\'        => \'1\',\n            \'smd_tag_t_hilite\'       => \'91919a\',\n            \'smd_tag_t_hover\'        => \'aaa\',\n            \'smd_tag_t_indent\'       => \'&nbsp;&nbsp;\',\n            \'smd_tag_t_mdelim\'       => \',\',\n            \'smd_tag_t_size_desc\'    => \'240, 80\',\n        ),\n        \'smd_tag_prefs_u\' => array(\n            \'smd_tag_u_combi\'     => \'1\',\n            \'smd_tag_u_combi_and\' => \'+\',\n            \'smd_tag_u_combi_or\'  => \'|\',\n            \'smd_tag_u_pnam\'      => \'smd_tag\',\n            \'smd_tag_u_ptyp\'      => \'smd_tagtype\',\n            \'smd_tag_u_sec\'       => \'smd_tags\',\n        ),\n    );\n\n    $old = array(\n        \'smd_tag_p_first\',\n    );\n\n    switch ($type) {\n        case \'set\':\n            $out = $current;\n            break;\n        case \'nameval\':\n            foreach ($current as $grp => $pref) {\n                foreach ($pref as $name => $dflt) {\n                    $out[$name] = $dflt;\n                }\n            }\n            break;\n        case \'name\':\n            foreach ($current as $grp => $pref) {\n                foreach ($pref as $name => $dflt) {\n                    $out[] = $name;\n                }\n            }\n            break;\n        case \'old\':\n            $out = $old;\n            break;\n        case \'default\':\n            $out = array();\n            break;\n    }\n\n    return $out;\n}\n\n// ------------------------\nfunction smd_tags_table_exist($all=\'0\') {\n    static $smd_tags_table_ok = array();\n\n    if (isset($smd_tags_table_ok[$all])) {\n        return $smd_tags_table_ok[$all];\n    }\n\n    if ($all) {\n        $tbls = array(SMD_TAG => 9, SMD_TAGC => 2, SMD_TAGU => 3);\n        $out = count($tbls);\n        foreach ($tbls as $tbl => $cols) {\n            if (gps(\'debug\')) {\n                echo \"++ TABLE \" . $tbl . \" HAS \" . count(@safe_show(\'columns\', $tbl)) . \" COLUMNS; REQUIRES \" . $cols . \" ++\" . br;\n            }\n            if (count(@safe_show(\'columns\', $tbl)) == $cols) {\n                $out--;\n            }\n        }\n        $ret = ($out===0) ? 1 : 0;\n        $smd_tags_table_ok[1] = $ret;\n        return $ret;\n    } else {\n        if (gps(\'debug\')) {\n            echo \"++ TABLE \" . SMD_TAG . \" HAS \" . count(@safe_show(\'columns\', SMD_TAG)) . \" COLUMNS;\";\n        }\n        $ret = @safe_show(\'columns\', SMD_TAG);\n        $smd_tags_table_ok[0] = $ret;\n        return $ret;\n    }\n}\n\n// ------------------------\n// Add tag tables if not already installed\nfunction smd_tags_table_install($showpane=\'1\') {\n    global $DB;\n\n    $debug = gps(\'debug\');\n    $GLOBALS[\'txp_err_count\'] = 0;\n\n    $ret = \'\';\n    $sql = array();\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAG.\"` (\n        `id` int(6) NOT NULL auto_increment,\n        `name` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `type` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `parent` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `lft` int(6) NOT NULL default \'0\',\n        `rgt` int(6) NOT NULL default \'0\',\n        `title` varchar(255) NOT NULL default \'\' COLLATE utf8_general_ci,\n        `description` text NULL COLLATE utf8_general_ci,\n        `desc_html` text NULL COLLATE utf8_general_ci,\n        PRIMARY KEY (`id`)\n    ) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=5 CHARACTER SET=utf8\";\n\n    if (!smd_tags_table_exist()) {\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (1, \'root\', \'article\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (2, \'root\', \'link\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (3, \'root\', \'image\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n        $sql[] = \"INSERT INTO `\".PFX.SMD_TAG.\"` VALUES (4, \'root\', \'file\', \'\', 1, 2, \'root\', \'DO NOT DELETE\', NULL)\";\n    }\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAGC.\"` (\n        `tag_id` int(6) NOT NULL default \'0\',\n        `cat_id` int(6) NOT NULL default \'0\',\n        PRIMARY KEY (`tag_id`,`cat_id`)\n    ) ENGINE=MyISAM\";\n\n    $sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.SMD_TAGU.\"` (\n        `item_id` int(11) NOT NULL default \'0\',\n        `tag_id` int(6) NOT NULL default \'0\',\n        `type` varchar(64) NOT NULL default \'\' COLLATE utf8_general_ci,\n        PRIMARY KEY (`item_id`,`tag_id`)\n    ) ENGINE=MyISAM CHARACTER SET=utf8\";\n\n    if($debug) {\n        dmp($sql);\n    }\n    foreach ($sql as $qry) {\n        $ret = safe_query($qry);\n        if ($ret===false) {\n            $GLOBALS[\'txp_err_count\']++;\n            echo \"<b>\" . $GLOBALS[\'txp_err_count\'] . \".</b> \" . mysqli_error($DB->link) . \"<br />\\n\";\n            echo \"<!--\\n $qry \\n-->\\n\";\n        }\n    }\n\n    // Upgrade table collation if necessary\n    $ret = getRows(\"SHOW TABLE STATUS WHERE name IN (\'\".PFX.SMD_TAG.\"\', \'\".PFX.SMD_TAGU.\"\')\");\n    if ($ret[0][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_TAG, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n    }\n    if ($ret[1][\'Collation\'] != \'utf8_general_ci\') {\n        $ret = safe_alter(SMD_TAGU, \'CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci\', $debug);\n    }\n\n    // Add the description column on upgrade\n    $flds = getThings(\'SHOW COLUMNS FROM `\'.PFX.SMD_TAG.\'`\');\n    if (!in_array(\'description\',$flds)) {\n        safe_alter(SMD_TAG, \"ADD `description` TEXT NULL AFTER `title`\", $debug);\n    }\n    if (!in_array(\'desc_html\',$flds)) {\n        safe_alter(SMD_TAG, \"ADD `desc_html` TEXT NULL AFTER `description`\", $debug);\n    }\n\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_tag_tbl_installed\');\n        smd_tags_prefs_install($message, $showpane);\n    } else {\n        if ($showpane) {\n            $message = gTxt(\'smd_tag_tbl_not_installed\');\n            smd_tags_prefs_show($message);\n        }\n    }\n}\n\n// ------------------------\n// Drop tag tables if in database\nfunction smd_tags_table_remove() {\n    global $DB;\n\n    $ret = \'\';\n    $sql = array();\n    $GLOBALS[\'txp_err_count\'] = 0;\n    if (smd_tags_table_exist()) {\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAG. \"; \";\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAGU. \"; \";\n        $sql[] = \"DROP TABLE IF EXISTS \" .PFX.SMD_TAGC. \"; \";\n        if(gps(\'debug\')) {\n            dmp($sql);\n        }\n        foreach ($sql as $qry) {\n            $ret = safe_query($qry);\n            if ($ret===false) {\n                $GLOBALS[\'txp_err_count\']++;\n                echo \"<b>\" . $GLOBALS[\'txp_err_count\'] . \".</b> \" . mysqli_error($DB->link) . \"<br />\\n\";\n                echo \"<!--\\n $qry \\n-->\\n\";\n            }\n        }\n    }\n    if ($GLOBALS[\'txp_err_count\'] == 0) {\n        $message = gTxt(\'smd_tag_tbl_removed\');\n        smd_tags_prefs_remove($message);\n    } else {\n        $message = gTxt(\'smd_tag_tbl_not_removed\');\n        smd_tags_prefs_show($message);\n    }\n}\n// ------------------------\n// Rebuild modified preorder tag links\nfunction smd_tags_table_rebuild() {\n    $types = array(\'article\', \'image\', \'file\', \'link\');\n    foreach ($types as $tag_type) {\n        rebuild_tree_full($tag_type, SMD_TAG);\n    }\n    $message = gTxt(\'smd_tag_tbl_rebuilt\');\n    smd_tags_prefs_show($message);\n}\n\n// ------------------------\n// Add plugin preferences to prefs\nfunction smd_tags_prefs_install($message = \'\', $showpane = \'1\') {\n    $smd_tag_prefs = smd_tags_get_prefs(\'nameval\');\n\n    $ctr = safe_count(\'txp_prefs\', \'event=\"smd_tags\"\') + 1;\n\n    foreach ($smd_tag_prefs as $pref => $dflt) {\n        if (!get_pref($pref)) {\n            set_pref($pref, $dflt, \'smd_tags\', PREF_PLUGIN, \'text_input\', $ctr);\n            $ctr++;\n        }\n    }\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'old\');\n\n    // Tidy up any legacy prefs\n    foreach ($smd_tag_prefs as $pref) {\n        remove_pref($pref, \'smd_tags\');\n    }\n\n    if ($showpane) {\n        $message .= gTxt(\'smd_tag_prefs_installed\');\n        smd_tags_prefs_show($message);\n    }\n}\n\n// ------------------------\n// Remove plugin preferences from prefs table.\nfunction smd_tags_prefs_remove($message = \'\', $showpane = \'1\') {\n    $smd_tag_prefs = array_merge(smd_tags_get_prefs(\'name\'), smd_tags_get_prefs(\'old\'));\n\n    foreach ($smd_tag_prefs as $pref) {\n        remove_pref($pref, \'smd_tags\');\n    }\n\n    if ($showpane) {\n        $message .= gTxt(\'smd_tag_prefs_removed\');\n        smd_tags_prefs_show($message);\n    }\n}\n\n// ------------------------\n// Saves plugin preferences\nfunction smd_tags_prefs_update($message = \'\') {\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    $post = doSlash(stripPost());\n\n    foreach ($smd_tag_prefs as $pref) {\n        if (isset($post[$pref])) {\n            update_pref($pref, $post[$pref]);\n        }\n    }\n\n    $message .= gTxt(\'preferences_saved\');\n    smd_tags_prefs_show($message);\n}\n\n// ------------------------\n// Split a multi-char preference value into an array of keys/values.\n// Note: value is passed in and not read directly from the prefs array in this function - intentionally\nfunction smd_tags_pref_explode($val) {\n    $order = array_values(array(gTxt(\'tab_list\'),gTxt(\'tab_image\'),gTxt(\'tab_file\'),gTxt(\'tab_link\')));\n    $onoff = array_values(preg_split(\'//\', $val, -1, PREG_SPLIT_NO_EMPTY));\n    $out = array();\n\n    foreach($order as $key1 => $value1) {\n        $out[(string)$value1] = $onoff[$key1];\n    }\n\n    return $out;\n}\n\n// ------------------------\n// Get values from prefs - pass either a single key or an array.\n// If 2nd arg is set and the pref doesn\'t exist, read it from the plugin default\nfunction smd_tags_pref_get($keys, $dflt = 0) {\n    static $smd_tag_prefstore;\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'nameval\');\n    $prefout = array();\n\n    if (!is_array($keys)) {\n        $keys = array($keys);\n    }\n\n    // For later looping round defaults\n    $allkeys = $keys;\n\n    // Check the cache and remove any keys we\'ve already fetched\n    foreach ($keys as $idx => $key) {\n        if (isset($smd_tag_prefstore[$key])) {\n            $prefout[$key] = $smd_tag_prefstore[$key];\n            unset($keys[$idx]);\n        }\n    }\n\n    if ($keys) {\n        $prefkeys = doQuote(join(\"\',\'\",doSlash($keys)));\n        $rs = safe_rows(\'name,val,html\',\'txp_prefs\',\'name IN (\'.$prefkeys.\') ORDER BY name\');\n\n        foreach($rs as $pref) {\n            $smd_tag_prefstore[$pref[\'name\']] = $prefout[$pref[\'name\']] = array_slice($pref, 1);\n        }\n    }\n\n    if ($dflt) {\n        foreach ($allkeys as $pref) {\n            if (!isset($prefout[$pref])) {\n                $prefout[$pref] = $smd_tag_prefs[$pref];\n            }\n        }\n    }\n\n    return $prefout;\n}\n\n// ------------------------\n// Common buttons on the admin panel\nfunction smd_tags_buttons() {\n    $ret = array (\n        \'btnPrefsSave\' => fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'),\n        \'btnInstallTbl\' => sLink(\'smd_tags\', \'smd_tags_table_install\', \'<span class=\"ui-icon ui-extra-icon-upload\"></span> \'.gTxt(\'smd_tag_tbl_install_lbl\')),\n        \'btnRemoveTbl\' => sLink(\'smd_tags\', \'smd_tags_table_remove\', \'<span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'smd_tag_tbl_remove_lbl\')),\n        \'btnRebuildTbl\' => sLink(\'smd_tags\', \'smd_tags_table_rebuild\', \'<span class=\"ui-icon ui-icon-refresh\"></span> \'.gTxt(\'smd_tag_tbl_rebuild_lbl\')),\n        \'btnInstall\' => sLink(\'smd_tags\', \'smd_tags_prefs_install\', \'<span class=\"ui-icon ui-extra-icon-upload\"></span> \'.gTxt(\'smd_tag_pref_install_lbl\')),\n        \'btnRemove\' => sLink(\'smd_tags\', \'smd_tags_prefs_remove\', \'<span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'smd_tag_pref_remove_lbl\')),\n        \'btnPrefs\' => sLink(\'smd_tags\', \'smd_tags_prefs_show\', \'<span class=\"ui-icon ui-icon-gear\"></span> \'.gTxt(\'smd_tag_pref_show_lbl\')),\n        \'btnManage\' => sLink(\'smd_tags\', \'smd_tags_manage\', \'<span class=\"ui-icon ui-icon-tag\"></span> \'.gTxt(\'smd_tag_manage_lbl\')),\n        \'btnSync\' => sLink(\'smd_tags\', \'smd_tags_sync\', \'<span class=\"ui-icon ui-icon-arrowreturn-1-n\"></span> \'.gTxt(\'smd_tag_sync_lbl\')),\n        \'btnSyncGo\' => fInput(\'submit\', \'smd_tags_do_import\', gTxt(\'go\'), \'publish\'),\n        \'btnHelp\' => \'<a class=\"pophelp\" rel=\"help\" href=\"?event=plugin\'.a.\'step=plugin_help\'.a.\'name=smd_tags\">?</a>\',\n        \'btnCreate\' => fInput(\'submit\', \'smd_tag_create\', gTxt(\'create\'), \'publish\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnSave\' => fInput(\'submit\', \'smd_tag_save\', gTxt(\'update\'), \'\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnDelete\' => fInput(\'submit\', \'smd_tags_delete\', \'×\', \'\', \'\', \'smd_tags_step=this.name;smd_presub()\'),\n        \'btnStyle\' => \' style=\"border:0;height:25px\"\',\n    );\n    return $ret;\n}\n\n// ------------------------\n// A stub that can be called from a Txp event\nfunction smd_tags_setup($evt=\'\', $stp=\'\', $message=\'\') {\n    smd_tags_prefs_show();\n}\n\n/**\n * Display the prefs panel.\n *\n * @param      string  $message  The message to display\n */\nfunction smd_tags_prefs_show($message = \'\')\n{\n    $smd_tag_prefs = smd_tags_get_prefs(\'set\');\n    $smd_tag_preflist = smd_tags_get_prefs(\'name\');\n\n    pagetop(gTxt(\'smd_tag_prefs_title\'), $message);\n    extract(smd_tags_buttons());\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_preflist);\n    $numReqPrefs = count($smd_tag_preflist);\n    $numPrefs = count($prefset);\n    $adv_text = gTxt(\'smd_tag_extras\');\n\n    echo script_js(<<<EOJS\n// Concatenate checkbox options for storage\nfunction smd_presub() {\n    var smd_out = \"\";\n    jQuery(\"#prefs-smd_tag_p_enable :checkbox\").each(function() {\n        smd_out += (this.checked) ? 1 : 0;\n    });\n    jQuery(\"#smd_tag_p_enable\").val(smd_out);\n    return true;\n}\njQuery(function() {\n    jQuery(\'.smd_tag_btn_advanced_toggle\').click(function(e) {\n        e.preventDefault();\n        jQuery(\'.smd_tag_btn_advanced\').toggle(\'fast\');\n    });\n});\nEOJS\n    );\n\n    if (smd_tags_table_exist(1)) {\n        // Tables installed\n        echo \'<form method=\"post\" action=\"?event=smd_tags\' . a . \'step=smd_tags_prefs_update\" onsubmit=\"return smd_presub();\">\';\n        echo \'<div class=\"txp-layout\">\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            n. \'<h1 class=\"txp-heading\">\'.gTxt(\'smd_tag_prefs_title\').sp.$btnHelp.\'</h1>\'.\n            n. \'</div>\'.\n            n. \'<div class=\"txp-layout-2col\">\'.\n            graf(\n                    $btnManage\n                    .n.$btnSync\n                    .n.tag($btnInstall.n.$btnRemove.n.$btnRemoveTbl.n.$btnRebuildTbl, \'span\', \' class=\"smd_tag_btn_advanced smd_hidden\"\')\n                    .n.href($adv_text, \'#\', \' class=\"smd_tag_btn_advanced_toggle\"\')\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            $groupOut = array();\n            $prefOut = array();\n\n            foreach ($smd_tag_prefs as $grp => $pset) {\n                $out = array();\n                $groupOut[] = n.tag(href(\n                        gTxt($grp),\n                        \'#prefs_group_\'.$grp,\n                        array(\n                            \'data-txp-pane\'  => $grp,\n                            \'data-txp-token\' => md5($grp.\'prefs\'.form_token().get_pref(\'blog_uid\')),\n                        )),\n                    \'li\');\n\n                foreach ($pset as $pnam => $pdflt) {\n                    $pval = get_pref($pnam, $pdflt, 1);\n                    $label = \'<label for=\"\'.$pnam.\'\">\'.gTxt($pnam).\'</label>\';\n                    $widget = \'\';\n\n                    switch($pnam) {\n                        case \"smd_tag_p_enable\":\n                            $options = smd_tags_pref_explode($pval);\n                            foreach ($options as $opt => $onoff) {\n                                $widget .= \'<label>\'.$opt.\'</label>\' . checkbox(\"enable\".$opt, 1, $onoff) . \'&#160;&#160;\';\n                            }\n\n                            $widget .= fInput(\'hidden\', \'smd_tag_p_enable\', join($options),\'\',\'\',\'\',\'\',\'\',\'smd_tag_p_enable\');\n                            break;\n                        case \"smd_tag_p_size\":\n                        case \"smd_tag_t_size_desc\":\n                        case \"smd_tag_t_cols\":\n                        case \"smd_tag_t_hilite\":\n                        case \"smd_tag_t_hover\":\n                        case \"smd_tag_t_indent\":\n                        case \"smd_tag_u_pnam\":\n                        case \"smd_tag_u_ptyp\":\n                        case \"smd_tag_u_sec\":\n                        case \"smd_tag_p_master\":\n                        case \"smd_tag_u_combi_and\":\n                        case \"smd_tag_u_combi_or\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 20, \'\', $pnam);\n                            break;\n                        case \"smd_tag_t_mdelim\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 1, \'\', $pnam);\n                            break;\n                        case \"smd_tag_p_qtpath\":\n                        case \"smd_tag_p_qtstyl\":\n                            $widget = fInput(\'text\', $pnam, $pval, \'edit\', \'\', \'\', 30, \'\', $pnam);\n                            break;\n                        case \"smd_tag_t_auto\":\n                            $widget = onoffRadio($pnam, $pval);\n                            break;\n                        case \"smd_tag_t_colsord\":\n                            $rset = array(0 => gTxt(\'smd_tag_by_column\'), 1 => gTxt(\'smd_tag_by_row\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_t_deltree\":\n                            $rset = array(0 => gTxt(\'smd_tag_children_pro\'), 1 => gTxt(\'smd_tag_children_del\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_t_count\":\n                        case \"smd_tag_t_delused\":\n                        case \"smd_tag_t_desc_textile\":\n                        case \"smd_tag_t_desc_tooltip\":\n                        case \"smd_tag_t_enrep\":\n                        case \"smd_tag_p_linkcat\":\n                        case \"smd_tag_p_listpar\":\n                        case \"smd_tag_u_combi\":\n                            $widget .= yesnoRadio($pnam, $pval);\n                            break;\n                        case \"smd_tag_t_astart\":\n                            $rset = array(0 => gTxt(\'smd_tag_prefs_pane\'), 1 => gTxt(\'smd_tag_tags_pane\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_qtag\":\n                            $rset = array(0 => gTxt(\'off\'), 1 => gTxt(\'smd_tag_ac_std\'), 2 => gTxt(\'smd_tag_ac_str\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_input\":\n                            $rset = array(0 => gTxt(\'smd_tag_sel_list\'), 1 => gTxt(\'smd_tag_textlist\'), 2 => gTxt(\'smd_tag_textbox\'), 3 => gTxt(\'smd_tag_textboxplus\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                        case \"smd_tag_p_lbi\":\n                            $rset = array(0 => gTxt(\'no\'), 1 => gTxt(\'smd_tag_bi_up\'), 2 => gTxt(\'smd_tag_bi_down\'), 3 => gTxt(\'smd_tag_bi_both\'));\n                            $widget = radioSet($rset, $pnam, $pval);\n                            break;\n                    }\n\n                    $out[] = inputLabel(\n                        $pnam,\n                        $widget,\n                        $label,\n                        \'\',\n                        array(\n                            \'class\' => \'txp-form-field\',\n                            \'id\'    => \'prefs-\'.$pnam,\n                        )\n                    );\n\n                }\n\n                $prefOut[] = tag(\n                    hed(gTxt($grp), 2, array(\'id\' => \'prefs_group_\'.$grp.\'-label\')).\n                    join(n, $out), \'section\', array(\n                        \'class\'           => \'txp-prefs-group\',\n                        \'id\'              => \'prefs_group_\'.$grp,\n                        \'aria-labelledby\' => \'prefs_group_\'.$grp.\'-label\',\n                    )\n                );\n            }\n\n        echo tag_start(\'div\', array(\'class\' => \'txp-layout-4col-alt\')).\n            wrapGroup(\n                \'smd_tags_prefs\',\n                n.tag(join($groupOut), \'ul\', array(\'class\' => \'switcher-list\')),\n                \'smd_tag_prefs_title\'\n            );\n\n        echo graf(fInput(\'submit\', \'submit\', gTxt(\'save\'), \'publish\'), array(\'class\' => \'txp-save\'));\n\n        echo n.tag_end(\'div\'). // End of .txp-layout-4col-alt.\n            n.tag_start(\'div\', array(\'class\' => \'txp-layout-4col-3span\')).\n            join(n, $prefOut).\n            n.tag_end(\'div\'). // End of .txp-layout-4col-3span.\n            tInput();\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo startTable()\n                .tr(tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                    .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                    .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .endTable();\n        } else {\n            // Prefs not installed\n            echo startTable()\n                .tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .tr(tda($btnInstall, $btnStyle))\n                .endTable();\n        }\n    } else {\n        // Tables not installed\n        echo startTable()\n            .tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .tr(tda($btnInstallTbl, $btnStyle))\n            .endTable();\n    }\n\n    echo \'</div></form>\';\n}\n\n/**\n * Manage tags in a similar way to the categories tab.\n *\n * @param      string  $message  The message to display\n * @param      string  $report   The report to display\n */\nfunction smd_tags_manage($message = \'\', $report = \'\')\n{\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    extract(doSlash(gpsa(array(\'smd_tag_name\', \'smd_tag_title\', \'smd_tag_description\', \'smd_tag_parent\', \'smd_tag_cat\', \'smd_tag_type\', \'smd_tag_id\'))));\n    pagetop(gTxt(\'smd_tag_manage_lbl\'),$message);\n    extract(smd_tags_buttons());\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_prefs);\n    $numReqPrefs = count($smd_tag_prefs);\n    $numPrefs = count($prefset);\n\n    $types = array(\n        \'article\' => ucfirst(gtxt(\'article\')),\n        \'image\' => gtxt(\'tag_image\'),\n        \'file\' => gtxt(\'file\'),\n        \'link\' => gtxt(\'tag_link\'),\n    );\n\n    // Make up the radio buttons.\n    $smd_tag_type = (!empty($smd_tag_type)) ? $smd_tag_type : \'article\';\n\n    $radios = array();\n\n    foreach ($types as $key => $val) {\n        $id = \'smd_tags_type-\'.$key;\n        $radios[] = n.radio(\'smd_tags_type\', $key, ($smd_tag_type == $key) ? 1 : 0, $id).n.\n                \'<label for=\"\'.$id.\'\">\'.$val.\'</label>\';\n    }\n\n    $radios = join(n, $radios).n;\n\n    // Create all the lists but hide them with CSS.\n    // jQuery takes over and switches between them on demand.\n    if (smd_tags_table_exist(1) && $numPrefs == $numReqPrefs) {\n        // Sanitize the multi-tag delimiter\n        $mdelim = substr($prefset[\'smd_tag_t_mdelim\'][\'val\'], 0, 1);\n        $mdelim = ($mdelim == \'\') ? \'\' : \'\\\\\'.$mdelim;\n\n        $colOpts = do_list($prefset[\'smd_tag_t_cols\'][\'val\'], \':\');\n        $layout = ($colOpts[0] == \'0\' || $colOpts[0] == \'list\') ? \'list\' : (($colOpts[0] == \'group\') ? \'listgrp\' : \'table\');\n        $colOrder = ($prefset[\'smd_tag_t_colsord\'][\'val\'] == 0) ? \'bycol\' : \'byrow\';\n        $wraptag = $layout.\':\';\n\n        if ($layout == \'table\') {\n            $tagCols = (is_numeric($colOpts[0]) && $colOpts[0] > 0) ? $colOpts[0] : 1;\n            $wraptag .= $tagCols;\n            $sel = \'td\';\n        } else {\n            $wraptag .= (isset($colOpts[1])) ? $colOpts[1] : \'0\';\n            $sel = \'li\';\n        }\n        $wraptag .= \':cols:\'.$colOrder;\n        $counts = $prefset[\'smd_tag_t_count\'][\'val\'];\n        $clink = $prefset[\'smd_tag_p_linkcat\'][\'val\'];\n        $showrep = $report && $prefset[\'smd_tag_t_enrep\'][\'val\'];\n\n        foreach (array_keys($types) as $type) {\n            $divtagid = \"smd_tags_grp_\".$type;\n            $taglist = \"tags_\".$type;\n            $$taglist = smd_tag_list_adm(\n                array(\n                    \"type\" => $type,\n                    \"wraptag\" => $wraptag,\n                    \"count\" => $counts,\n                    \"indent\" => $prefset[\'smd_tag_t_indent\'][\'val\']\n                )\n            );\n\n            // @todo Find a way to add the divtagid only when needed. At the moment it appears twice on the page (illegal DOM)\n            $tagout[$type] = \'<div id=\"\'.$divtagid.\'\">\'.($$taglist ? $$taglist : gTxt(\'smd_tag_no_tags\', array(\'{type}\' => $type))).\'</div>\';\n            echo \'<div class=\"smd_hidden\">\' . $tagout[$type] . \'</div>\';\n        }\n\n        $qs = array(\n            \"event\" => \"smd_tags\",\n        );\n\n\n        $qsVars = \"index.php\".join_qs($qs);\n        $count_lbl = gTxt(\'smd_tag_count_lbl\');\n\n        echo script_js(<<<EOJS\nvar smd_tags_step = \'\';\n\n// Page initialisation\njQuery(function() {\n    // Indicate parent list is loading\n    jQuery(\".smd_tags_showpars\").ajaxStart(function() {\n        jQuery(this).fadeTo(\"normal\", 0.33);\n    }).ajaxStop(function() {\n        jQuery(this).fadeTo(\"normal\", 1);\n    });\n\n    // Switch between types\n    jQuery(\"input[name=\'smd_tags_type\']\").change(function () {\n        var smd_newcontent = jQuery(\"#smd_tags_grp_\"+jQuery(this).val()).html();\n        jQuery(\"input[name=\'smd_tag_id\']\").val(\'\'); // Changing type forces any \'save\' to trigger the \'create\' behaviour\n        jQuery(\".smd_tags_showlist\").html(smd_newcontent);\n        var nam = jQuery(\"input[name=\'smd_tag_oname\']\").val();\n        var dsc = jQuery(\"input[name=\'smd_tag_description\']\").val();\n        var tid = jQuery(\"input[name=\'smd_tag_id\']\").val();\n        var typ = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n        var par = \'root\';\n        var cat = \'\';\n\n        jQuery(\".smd_tags_showlist {$sel} span[name=\'smd_tagnam\']\").each(function() {\n            if (jQuery(this).text() == nam) {\n                jQuery(this).parent().addClass(\'smd_current\');\n                par = jQuery(this).siblings(\":eq(2)\").text();\n                cat = jQuery(this).siblings(\":eq(3)\").text();\n                dsc = jQuery(this).siblings(\":eq(4)\").text();\n                tid = (tid == \'\') ? jQuery(this).siblings(\":eq(0)\").text() : tid;\n            } else {\n                jQuery(this).parent().removeClass(\'smd_current\');\n            }\n        });\n\n        // Re-insert the ID if this named item exists in the new list\n        jQuery(\"input[name=\'smd_tag_id\']\").val(tid);\n\n        // Update the UI\n        smd_parentHandler(nam, typ, tid, par);\n        smd_catHandler(cat, typ);\n        jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n        smd_autofocus();\n        smd_cellHandler();\n        jQuery(\"input[name=\'smd_tags_type\']\").each(function () {\n            if (jQuery(this).val() == typ) {\n                jQuery(this).next().addClass(\'smd_current\');\n            } else {\n                jQuery(this).next().removeClass(\'smd_current\');\n            }\n        });\n    });\n\n    // Call the onchange handler for the current type\n    jQuery(\"input[name=\'smd_tags_type\']:checked\").change();\n\n    // Autotag - TODO: foreign character dumbdown for URI\n    if ({$prefset[\'smd_tag_t_auto\'][\'val\']}) {\n        spcRE = /\\s/g;\n        badRE = /[^a-zA-Z0-9_\\-{$mdelim}]/g;\n        jQuery(\"input[name=\'smd_tags_newtitle\']\").keyup(function() {\n            tagname = jQuery(this).val().replace(spcRE, \'-\').replace(badRE, \'\').toLowerCase();\n            jQuery(\"input[name=\'smd_tags_newname\']\").val(tagname);\n        });\n    }\n    // Bind Enter key to create\n    jQuery(\"input[name^=\'smd_tag\']\").keyup(function(ev) {\n        if (ev.keyCode == 13) {\n            jQuery(\"input[name=\'smd_tag_create\']\").click();\n        }\n    });\n\n    // Prepare the live filter\n    jQuery(\'#smd_tag_filt legend a\').click(function() {\n        if (jQuery(this).parent().hasClass(\'expanded\')) {\n            jQuery(\'#smd_tagfilter\').focus();\n        }\n    });\n\n    jQuery(\'.smd_tag_list_adm {$sel}\').addClass(\'visible\');\n\n    jQuery(\'#smd_tagfilter\').keyup(function(event) {\n        // if esc is pressed or nothing is entered\n        if (event.keyCode == 27 || jQuery(this).val() == \'\') {\n            jQuery(this).val(\'\');\n            jQuery(\'.smd_tag_list_adm {$sel}\').removeClass(\'visible\').show().addClass(\'visible\');\n            jQuery(\"#smd_tag_filter_count\").empty();\n        } else {\n            currOpt = jQuery(\'#smd_tag_filt_radios :radio:checked\').val();\n            subfilt = (currOpt == \'smd_all\') ? \'\' : currOpt;\n            smd_tag_filter(\'.smd_tag_list_adm {$sel}\', jQuery(this).val(), subfilt);\n        }\n    });\n    // Trigger the filter if the radio buttons are clicked\n    jQuery(\'#smd_tag_filt_radios :radio\').change(function() {\n        jQuery(\'#smd_tagfilter\').keyup().focus();\n    });\n    if (\'{$showrep}\') {\n        smd_tags_toggle_report();\n    }\n\n    jQuery(\'#smd_tags_report_toggler, .smd_tags_btn_ok\').on(\'click\', smd_tags_toggle_report_handler);\n});\n\n// Keep track of which tag has been clicked so it can be edited/updated\nfunction smd_cellHandler() {\n    // Cell highlights\n    jQuery(\".smd_tags_showlist {$sel}\").each(function() {\n        if (jQuery.trim(jQuery(this).text()) != \"\") {\n            jQuery(this).hover(\n                function () {\n                    jQuery(this).addClass(\'smd_hover\');\n                },\n                function () {\n                    jQuery(this).removeClass(\'smd_hover\');\n                }\n            )\n            .click(function() {\n                var nam = jQuery(this).children(\":eq(0)\").text();\n                var tid = jQuery(this).children(\":eq(1)\").text();\n                var ttl = jQuery(this).children(\":eq(2)\").text();\n                var par = jQuery(this).children(\":eq(3)\").text();\n                var cat = jQuery(this).children(\":eq(4)\").text();\n                var dsc = jQuery(this).children(\":eq(5)\").text();\n                var typ = jQuery(\"input[name=\'smd_tags_type\']:checked\").val()\n                jQuery(\"input[name=\'smd_tag_oname\']\").val(nam);\n                jQuery(\"input[name=\'smd_tags_newname\']\").val(nam);\n                jQuery(\"input[name=\'smd_tag_id\']\").val(tid);\n                jQuery(\"input[name=\'smd_tags_newtitle\']\").val(ttl);\n                jQuery(\"textarea[name=\'smd_tags_description\']\").val(dsc);\n                jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n                smd_parentHandler(nam, typ, tid, par);\n                jQuery(\".smd_tags_showlist ${sel}\").removeClass(\'smd_current\');\n                jQuery(this).addClass(\'smd_current\');\n                smd_autofocus();\n            });\n        }\n    });\n}\n\n// Ask Txp for the parent dropdown without child entries\nfunction smd_parentHandler(nam, typ, tid, par) {\n    jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: nam, type: typ, id: tid, link_mode: \'2\', _txp_token: textpattern._txp_token },\n        function(data) {\n            jQuery(\".smd_tags_showpars\").html(data);\n            if (par == \"root\" || par == \"\") {\n                jQuery(\".smd_tags_showpars select option:first\").prop(\"selected\", true);\n            } else {\n                jQuery(\".smd_tags_showpars select option[value=\'\"+par+\"\']\").prop(\"selected\", true);\n            }\n        }\n    );\n}\n\n// Ask Txp for the category dropdown\nfunction smd_catHandler(cat, typ) {\n    jQuery(\".smd_tags_linkcat\").fadeTo(\"normal\", 0.33);\n    jQuery.post(\'{$qsVars}\', { step: \"smd_tag_catlist\", name: cat, type: typ, _txp_token: textpattern._txp_token },\n        function(data) {\n            jQuery(\".smd_tags_linkcat\").html(data);\n            if (cat == \"root\" || cat == \"\") {\n                jQuery(\".smd_tags_linkcat select option:first\").prop(\"selected\", true);\n            } else {\n                jQuery(\".smd_tags_linkcat select option[value=\'\"+cat+\"\']\").prop(\"selected\", true);\n            }\n            jQuery(\".smd_tags_linkcat\").fadeTo(\"normal\", 1);\n        }\n    );\n}\n\nfunction smd_autofocus() {\n    jQuery(\"input[name=\'smd_tags_newtitle\']\").focus().select();\n}\n\n// Make sure what is in the visible boxes is POSTed via the hidden form\nfunction smd_presub() {\n    jQuery(\"#smd_tag_postit input[name=\'step\']\").val(smd_tags_step);\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_name\']\").val(jQuery(\"input[name=\'smd_tags_newname\']\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_title\']\").val(jQuery(\"input[name=\'smd_tags_newtitle\']\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_type\']\").val(jQuery(\"input[name=\'smd_tags_type\']:checked\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_parent\']\").val(jQuery(\".smd_tags_showpars option:selected\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_cat\']\").val(jQuery(\".smd_tags_linkcat option:selected\").val());\n    jQuery(\"#smd_tag_postit input[name=\'smd_tag_description\']\").val(jQuery(\"textarea[name=\'smd_tags_description\']\").val());\n    jQuery(\"#smd_tag_postit\").submit();\n}\n\nfunction smd_multisub() {\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n    jQuery(\"#smd_tag_multiform input[name=\'step\']\").val(smd_tags_step);\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_type\']\").val(theType);\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_id\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagid\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_name\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagnam\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_description\']\").val(jQuery.map( jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").find(\"span[name=\'smd_tagdsc\']\"), function(n,i) {return jQuery(n).text()} ));\n    jQuery(\"#smd_tag_multiform input[name=\'smd_tag_extra\']\").val(jQuery(\"#smd_tag_sel_secondary option:selected\").val());\n}\n\n// Live tag filter\nfunction smd_tag_filter(selector, query, nam) {\n    var count_lbl = \'{$count_lbl}\';\n    var query = jQuery.trim(query);\n    query = query.replace(/ /gi, \'|\'); // add OR for regex query\n    var re = new RegExp(query, \"i\");\n    jQuery(selector).each(function() {\n        sel = (typeof nam==\"undefined\" || nam==\'\') ? jQuery(this) : jQuery(this).find(\"span[name=\'\"+nam+\"\']\");\n        (sel.text().search(re) < 0) ? jQuery(this).hide().removeClass(\'visible\') : jQuery(this).show().addClass(\'visible\');\n    });\n\n    // Display the matched count\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n    num_matches = count_lbl + jQuery(\"#smd_tags_grp_\"+theType+\" .visible\").length;\n    jQuery(\"#smd_tag_filter_count\").text(num_matches);\n}\n\n// Handle secondary multi-select options\nfunction smd_tags_seledit(obj) {\n    smd_tags_step = jQuery(obj).val();\n    theType = jQuery(\"input[name=\'smd_tags_type\']:checked\").val();\n\n    switch(smd_tags_step) {\n        case \"smd_tags_multi_set_parent\":\n            // Grab all elements of the current type that have data in them\n            listitems = jQuery(\"#smd_tags_grp_\"+theType+\" {$sel}\").filter(function() {return jQuery(this).find(\'span\').length > 0}).get();\n            listitems.sort(function(a, b) {\n                var compA = jQuery(a).text().toUpperCase();\n                var compB = jQuery(b).text().toUpperCase();\n                return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;\n            });\n\n            out = \'<select id=\"smd_tag_sel_secondary\">\';\n            out += \'<option value=\"0\"></option>\';\n            jQuery.each(listitems, function() {\n                item = jQuery(this);\n                tagid = jQuery(this).find(\"span[name=\'smd_tagid\']\").text();\n                tagnam = jQuery(this).find(\"span[name=\'smd_tagnam\']\").text();\n                out += \'<option value=\"\'+tagid+\'\">\'+tagnam+\'</option>\';\n            });\n            out += \'</select>\';\n            jQuery(\"#smd_tag_multi_placeholder\").html(out);\n            break\n        case \"smd_tags_multi_catlink\":\n            jQuery.post(\'{$qsVars}\', { step: \"smd_tag_catlist\", type: theType, html_id: \'smd_tag_sel_secondary\', _txp_token: textpattern._txp_token  },\n                function(data) {\n                    jQuery(\"#smd_tag_multi_placeholder\").html(data);\n                }\n            );\n            break\n        case \"smd_tags_delete\":\n        case \'\':\n            jQuery(\"#smd_tag_multi_placeholder\").empty();\n            break;\n    }\n}\n\n/**\n * jQuery callback for displaying tag report.\n */\nfunction smd_tags_toggle_report_handler(ev) {\n    ev.preventDefault();\n    smd_tags_toggle_report();\n}\n\nfunction smd_tags_toggle_report() {\n    jQuery(\"#smd_tag_report_pane\").toggle(\'fast\');\n}\nEOJS\n        );\n    }\n\n    // The tag management panel\n    if (smd_tags_table_exist()) {\n        // Tables installed\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            echo \'<div class=\"txp-layout\">\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                n. hed(gTxt(\'smd_tag_manage_lbl\').sp.$btnHelp, 1).\n                n. \'</div>\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                graf(\n                    $btnSync\n                    .n.$btnPrefs\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n            // Live search / multi-edit\n            $filtopts = array(\n                \'smd_all\' => gTxt(\'smd_tag_all_lbl\'),\n                \'smd_tagttl\' => gTxt(\'title\'),\n                \'smd_tagdsc\' => gTxt(\'description\'),\n                \'smd_tagnam\' => gTxt(\'name\'),\n                \'smd_tagpar\' => gTxt(\'smd_tag_parent_lbl\'),\n            );\n            $withselopts = array(\n                \'smd_tags_delete\' => gTxt(\'delete\'),\n                \'smd_tags_multi_set_parent\' => gTxt(\'smd_tag_assign_parent_lbl\'),\n            );\n            // Tack on the category link items if required\n            if ($clink) {\n                $filtopts[\'smd_tagcat\'] = gTxt(\'smd_tag_clink_lbl\');\n                $withselopts[\'smd_tags_multi_catlink\'] = gTxt(\'smd_tag_link_to_cat_lbl\');\n            }\n\n            $descsizes = do_list($prefset[\'smd_tag_t_size_desc\'][\'val\']);\n            $descw = (int)$descsizes[0];\n            $desch = (isset($descsizes[1])) ? (int)$descsizes[1] : $descw;\n            echo \'<div class=\"txp-layout-1col\">\';\n            echo n. \'<form id=\"smd_tag_multiform\" action=\"?event=smd_tags\" method=\"post\" onsubmit=\"smd_multisub(); return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'\n                .n. \'<fieldset id=\"smd_tag_filt\"><legend class=\"txp-summary lever\'.(get_pref(\'pane_smd_tag_filters_visible\') ? \' expanded\' : \'\').\'\"><a href=\"#smd_tag_filters\">\'.gTxt(\'smd_tag_tag_search\').\'</a></legend><div id=\"smd_tag_filters\" class=\"toggle\" style=\"display:\'.(get_pref(\'pane_smd_tag_filters_visible\') ? \'block\' : \'none\').\'\">\'\n                .n. hInput(\'step\', \'\')\n                .n. hInput(\'smd_tag_type\', \'\')\n                .n. hInput(\'smd_tag_id\', \'\')\n                .n. hInput(\'smd_tag_name\', \'\')\n                .n. hInput(\'smd_tag_description\', \'\')\n                .n. hInput(\'smd_tag_extra\', \'\')\n                .n. tInput()\n                .n. \'<input type=\"text\" name=\"smd_tagfilter\" value=\"\" id=\"smd_tagfilter\" size=\"30\" />\'\n                .n. \'<span id=\"smd_tag_filter_count\"></span>\'\n                .n. \'<div id=\"smd_tag_filt_radios\">\'.radioSet($filtopts, \'smd_tag_filtopt\', \'smd_all\', \'\', \'smd_tag_filtopt\').\'</div>\'\n                .n. \'<div id=\"smd_tag_multisel\">\'.gTxt(\'smd_tag_with_filtered\').selectInput(\'smd_tag_multisel\',$withselopts, \'\', true, \' onchange=\"smd_tags_seledit(this); return false;\"\').n.fInput(\'submit\', \'\', gTxt(\'go\'), \'publish\').\'</div>\'\n                .n. \'<div id=\"smd_tag_multi_placeholder\"></div><a href=\"#\" id=\"smd_tags_report_toggler\">\'.gTxt(\'smd_tag_recent_report\').\'</a>\'\n                .n. \'</div>\'\n                .n. \'</fieldset>\'\n                .n. \'</form>\';\n\n            // Report popup\n            echo n. \'<div id=\"smd_tag_report_pane\"><div>\'.href(\'&#215;\', \'#close\', \' class=\"smd_tags_btn_ok\" role=\"button\" title=\"\'.gTxt(\'close\').\'\" aria-label=\"\'.gTxt(\'close\').\'\"\').\'<h3>\'.gTxt(\'smd_tag_report_lbl\').\'</h3>\'.$report.\'</div></div>\'\n                .n. \'<form method=\"post\" id=\"smd_tag_postit\" action=\"?event=smd_tags\">\'\n                .n. hInput(\'step\', \'\')\n                .n. hInput(\'smd_tag_id\', $smd_tag_id)\n                .n. hInput(\'smd_tag_name\', \'\')\n                .n. hInput(\'smd_tag_oname\', $smd_tag_name)\n                .n. hInput(\'smd_tag_parent\', \'\')\n                .n. hInput(\'smd_tag_cat\', \'\')\n                .n. hInput(\'smd_tag_title\', \'\')\n                .n. hInput(\'smd_tag_type\', \'\')\n                .n. hInput(\'smd_tag_description\', \'\')\n                .n. tInput()\n                .n. \'</form>\';\n\n            // Tag create/edit row\n            echo n.hed(gTxt(\'smd_tag_create_lbl\'), 2)\n                .n.\'<div class=\"smd_tags_new\">\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label for=\"smd_tags_newtitle\">\'.gTxt(\'title\').\'</label>\'\n                        . fInput(\'text\', \'smd_tags_newtitle\', $smd_tag_title, \'\', \'\', \'\', \'\', \'\', \'smd_tags_newtitle\')\n                        . \'<label for=\"smd_tags_newname\">\'.gTxt(\'name\').\'</label>\'\n                        . fInput(\'text\', \'smd_tags_newname\', $smd_tag_name, \'\', \'\', \'\', \'\', \'\', \'smd_tags_newname\')\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label for=\"smd_tags_description\">\'.gTxt(\'description\').\'</label>\'\n                        . text_area(\'smd_tags_description\', $desch, $descw, $smd_tag_description, \'smd_tags_description\')\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        . \'<label>\'.gTxt(\'parent\').\'</label>\'\n                        . \'<span class=\"smd_tags_showpars\"></span>\'\n                        . (($clink)\n                            ? n. \'<label>\'.gTxt(\'smd_tag_clink_lbl\').\'</label>\'\n                                .n. \'<span class=\"smd_tags_linkcat\"></span>\'\n                            : \'\'\n                        )\n                    .n.\'</div>\'\n                    .n.\'<div class=\"smd_tags_input_group\">\'\n                        .n. $btnCreate\n                        .n. $btnSave\n                        .n. $btnDelete\n                    .n.\'</div>\'\n                .n.\'</div>\';\n\n            // The tags themselves\n            echo \'<div class=\"smd_tags_pool\">\'\n                    .n. hed(gTxt(\'smd_tag_pool_lbl\'), 2)\n                    .n. \'<div class=\"smd_tags_type_selection\">\' . $radios .n. \'</div>\'\n                    .n. \'<div class=\"smd_tags_showlist\">\' . $tagout[$smd_tag_type] .n. \'</div>\'\n                .n.\'</div>\'\n                .n.\'</div>\';\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo n. startTable()\n                .n. tr(\n                    tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                        .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                        .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .n. tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .n. endTable();\n\n        } else {\n            // Prefs not installed\n            echo n. startTable()\n                .n. tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .n. tr(tda($btnInstall, $btnStyle))\n                .n. endTable();\n        }\n    } else {\n        // Tables not installed\n        echo n. startTable()\n            .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .n. tr(tda($btnInstallTbl, $btnStyle))\n            .n. endTable();\n    }\n\n    echo \'</div>\';\n}\n\n// ------------------------\n// Store the tag that is currently being edited/created\nfunction smd_tag_save() {\n    // Defer doSlash of description until after Textile\'s had a go\n    extract(doSlash(gpsa(array(\'smd_tag_oname\', \'smd_tag_name\', \'smd_tag_title\', \'smd_tag_parent\', \'smd_tag_cat\', \'smd_tag_type\', \'smd_tag_id\'))));\n    include_once txpath.\'/publish.php\'; // for parse()\n\n    $smd_tag_description = $smd_tags_desctile = ps(\'smd_tag_description\');\n\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_t_mdelim\', \'smd_tag_t_desc_textile\'), 1);\n    $mdelim = $ctrls[\'smd_tag_t_mdelim\'][\'val\'];\n    $txt_desc = $ctrls[\'smd_tag_t_desc_textile\'][\'val\'];\n\n    $message = $report = \'\';\n    $missing = 0;\n    $ok = $notok = array();\n    $smd_tag_parent = (!empty($smd_tag_parent)) ? $smd_tag_parent : \'root\';\n    $smd_tag_name = trim($smd_tag_name);\n\n    $textile = new \\Textpattern\\Textile\\Parser();\n    $smd_tags_desctile = doSlash((($txt_desc) ? $textile->parse(parse($smd_tag_description)) : parse($smd_tag_description)));\n    $smd_tag_description = doSlash($smd_tag_description);\n\n    // Can\'t use safe_upsert() because the WHERE is for ID AND type\n    if (empty($smd_tag_id)) {\n        // Create\n        if ($smd_tag_name == \'\' && $smd_tag_title == \'\') {\n            $message = array(gTxt(\'smd_tag_no_name\', array(\'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n        } else {\n            $alltagnam = do_list($smd_tag_name, $mdelim);\n            $alltagttl = do_list($smd_tag_title, $mdelim);\n            $numtags = count($alltagnam);\n\n            foreach ($alltagnam as $idx => $theTag) {\n                if ($theTag == \'\') {\n                    $missing++;\n                }\n\n                $theTtl = (isset($alltagttl[$idx]) && $alltagttl[$idx] != \'\') ? $alltagttl[$idx] : $theTag;\n                $theNam = ($theTag == \'\') ? sanitizeForUrl($theTtl) : sanitizeForUrl($theTag);\n\n                $exists = safe_field(\'name\', SMD_TAG, \"name = \'$theNam\' AND type = \'$smd_tag_type\'\");\n                $parex = safe_field(\'name\', SMD_TAG, \"name = \'$smd_tag_parent\' AND type = \'$smd_tag_type\'\");\n\n                if ($exists) {\n                    if ($numtags > 1) {\n                        $notok[$idx] = \'smd_tag_already_exists\';\n                    } else {\n                        $message = array(gTxt(\'smd_tag_exists\', array(\'{name}\' => $theNam, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n                    }\n                } else {\n                    $smd_tag_id = safe_insert(\n                        SMD_TAG,\n                        \"name=\'$theNam\', title=\'$theTtl\', description=\'$smd_tag_description\', desc_html=\'$smd_tags_desctile\', parent=\'\".(($parex) ? $smd_tag_parent : \'root\').\"\', type=\'$smd_tag_type\'\"\n                    );\n                    if ($smd_tag_id > 0) {\n                        if ($smd_tag_cat) {\n                            safe_insert(SMD_TAGC,\"tag_id=\'$smd_tag_id\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$smd_tag_cat\' AND type=\'$smd_tag_type\')\");\n                        }\n                        if ($numtags > 1) {\n                            $ok[] = $theNam;\n                        } else {\n                            $message = gTxt(\'smd_tag_created\', array(\'{name}\' => $theNam, \'{type}\' => ucfirst($smd_tag_type)));\n                        }\n                    } else {\n                        if ($numtags > 1) {\n                            $notok[$idx] = \'smd_tag_unable_to_create\';\n                        } else {\n                            $message = array(gTxt(\'smd_tag_not_created\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n                        }\n                    }\n                }\n            }\n\n            // Generate the report\n            if ($ok) {\n                $report = gTxt(\'smd_tag_created_rep_lbl\').join(\', \', $ok).\'.\';\n            }\n            if ($notok) {\n                $msgs = array();\n                foreach ($notok as $idx => $reason) {\n                    $msgs[] = $alltagnam[$idx].sp.\'(\'.gTxt($reason).\')\';\n                }\n                $report .= br.br.gTxt(\'smd_tag_not_created_rep_lbl\').join(\', \', $msgs);\n                $report .= ($missing) ? br.br.gTxt(\'smd_tag_missing_rep_lbl\').$missing : \'\';\n            }\n        }\n    } else {\n        // Update - no need to maintain referential integrity unlike txp_cats\n        // since tags are stored against item IDs\n        $smd_tag_title = (empty($smd_tag_title)) ? $smd_tag_name : $smd_tag_title;\n        $smd_tag_name = (empty($smd_tag_name)) ? sanitizeForUrl($smd_tag_title) : sanitizeForUrl($smd_tag_name);\n        $existing_id = safe_field(\'id\', SMD_TAG, \"name = \'$smd_tag_name\' and type = \'$smd_tag_type\'\");\n\n        if ($existing_id and $existing_id != $smd_tag_id) {\n            $message = array(gTxt(\'smd_tag_exists\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n        } else {\n            if (safe_update(\n                    SMD_TAG,\n                    \"name=\'$smd_tag_name\', title=\'$smd_tag_title\', parent=\'$smd_tag_parent\', description=\'$smd_tag_description\', desc_html=\'$smd_tags_desctile\'\",\n                    \"type=\'$smd_tag_type\' AND id=$smd_tag_id\"\n                )) {\n                safe_update(SMD_TAG, \"parent=\'$smd_tag_name\'\", \"parent=\'$smd_tag_oname\' AND type=\'$smd_tag_type\'\");\n                if ($smd_tag_cat) {\n                    safe_upsert(SMD_TAGC, \"cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$smd_tag_cat\' AND type=\'$smd_tag_type\')\", \"tag_id = \'$smd_tag_id\'\");\n                } else {\n                    safe_delete(SMD_TAGC, \"tag_id=$smd_tag_id\");\n                }\n                $message = gTxt(\'smd_tag_updated\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type)));\n            } else {\n                $message = array(gTxt(\'smd_tag_not_updated\', array(\'{name}\' => $smd_tag_name, \'{type}\' => ucfirst($smd_tag_type))), E_WARNING);\n            }\n        }\n    }\n\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    // Force smd_tag_id to the new ID so it can be immediately edited\n    $_POST[\'smd_tag_id\'] = $smd_tag_id;\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Store new tag - uses smd_tag_save without the ID/old_name\nfunction smd_tag_create() {\n//  extract(doSlash(gpsa(array(\'smd_tag_name\', \'smd_tag_title\', \'smd_tag_description\', \'smd_tag_parent\', \'smd_tag_type\'))));\n    unset($_POST[\'smd_tag_id\']);\n    smd_tag_save();\n}\n\n// ------------------------\n// Check if the passed tag exists.\n// Return its ID if it does or insert it if it doesn\'t. Optionally assign it to a parent tag / Txp category\nfunction smd_tag_getsert($tag_name, $tag_type, $tag_title=\'\', $tag_parent=\'\', $tag_cat=\'\', $tag_desc=\'\', $force_parent=false, $force_cat=false) {\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_p_linkcat\'), 1);\n    $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n    $tag_title = ($tag_title == \'\') ? $tag_name : $tag_title;\n    $tag_title = doSlash($tag_title);\n    $tag_name = doSlash(sanitizeForUrl($tag_name));\n    $tag_type = doSlash($tag_type);\n    $tag_desc = doSlash($tag_desc);\n    $tag_parent = ($tag_parent) ? doSlash(sanitizeForUrl($tag_parent)) : \'root\';\n    $tag_cat = doSlash($tag_cat);\n\n    $ret = safe_field(\'id\', SMD_TAG, \"name = \'$tag_name\' AND type = \'$tag_type\'\");\n    if (!$ret) {\n        $ret = safe_insert(SMD_TAG, \"name=\'\".$tag_name.\"\', title=\'$tag_title\', parent=\'$tag_parent\', type=\'$tag_type\', description=\'$tag_desc\', desc_html=\'$tag_desc\'\");\n        rebuild_tree_full($tag_type, SMD_TAG);\n    } elseif($force_parent) {\n        $upd = safe_update(SMD_TAG, \"parent=\'$tag_parent\'\", \"id=\'\".doSlash($ret).\"\'\");\n        rebuild_tree_full($tag_type, SMD_TAG);\n    }\n\n    // Assign tag to category if required\n    $ret = doSlash($ret);\n    if ($clink && $tag_cat) {\n        $catid = safe_field(\'cat_id\', SMD_TAGC, \"tag_id=\'$ret\'\");\n\n        if ($catid && $force_cat) {\n            safe_delete(SMD_TAGC, \"cat_id=\'\".$catid.\"\' AND tag_id=\'\".$ret.\"\'\");\n            $catid = false;\n        }\n\n        if(!$catid) {\n            safe_insert(SMD_TAGC, \"tag_id=\'$ret\', cat_id=(SELECT id FROM \".PFX.\"txp_category WHERE name=\'$tag_cat\' AND type=\'$tag_type\')\");\n        }\n    }\n\n    return $ret;\n}\n\n// ------------------------\n// Link the passed tags array with the given item ID\nfunction smd_tag_link($itemid, $tagarr, $tag_type) {\n    assert_int($itemid);\n\n    // TODO: Remove any existing tags?? Perhaps make this an option?\n//  safe_delete(SMD_TAGU, \'item_id=\"\'.$itemid.\'\" AND type=\"\'.doSlash($tag_type).\'\"\');\n    $tag_type = doSlash($tag_type);\n\n    $ctr = $ret = 0;\n    foreach ($tagarr as $theTag) {\n        if ($theTag == \'\') continue;\n        $theTag = doSlash($theTag); // Probably not necessary\n\n        $exists = safe_field(\'item_id\', SMD_TAGU, \"tag_id = \'$theTag\' AND item_id = \'$itemid\' AND type = \'$tag_type\'\");\n\n        if (!$exists) {\n            $ret = safe_insert(SMD_TAGU, \'tag_id = \"\'.$theTag.\'\", item_id=\"\'.$itemid.\'\", type=\"\'.$tag_type.\'\"\');\n            if ($ret) {\n                $ctr++;\n            }\n        }\n    }\n\n    return $ctr;\n}\n\n// ------------------------\n// Delete the given tag(s). Respects noclobber/tree/orphans depending on prefs\nfunction smd_tags_delete() {\n    extract(doSlash(gpsa(array(\'smd_tag_id\', \'smd_tag_name\', \'smd_tag_type\'))));\n\n    $message = $msgExtra = $report = \'\';\n    $ok = $notok = array();\n\n    $pref = smd_tags_pref_get(array(\'smd_tag_t_delused\', \'smd_tag_t_deltree\'));\n    $delu = $pref[\'smd_tag_t_delused\'][\'val\'];\n    $delt = $pref[\'smd_tag_t_deltree\'][\'val\'];\n\n    $smd_tag_id = do_list($smd_tag_id);\n    $smd_tag_name = do_list($smd_tag_name);\n\n    $with_child = safe_column(\'id\', SMD_TAG, \"id IN (\'\".join(\"\',\'\", $smd_tag_id).\"\') AND (((rgt-lft-1)/2) > 0) AND type=\'$smd_tag_type\'\");\n    $used_tags = safe_column(\'tag_id\', SMD_TAGU, \"tag_id IN (\'\".join(\"\',\'\", $smd_tag_id).\"\') AND type=\'$smd_tag_type\'\");\n\n    $to_del = $smd_tag_id;\n    // Protect used and child tags if required\n    if ($delu == 0) {\n        $to_del = array_diff($smd_tag_id, $with_child, $used_tags);\n        foreach (array_merge($with_child, $used_tags) as $delid) {\n            $notok[] = $smd_tag_name[array_search($delid, $smd_tag_id)];\n        }\n    }\n\n    $orphans = array();\n    if ($delt) {\n        // Add child tags if they are to be deleted\n        $ids = $to_del;\n        foreach ($to_del as $delid) {\n            $row = safe_row(\'*\', SMD_TAG, \"id = \'$delid\' AND type=\'$smd_tag_type\'\");\n            $ids = array_merge($ids, safe_column(\'id\', SMD_TAG, \"type = \'$smd_tag_type\' AND (lft BETWEEN \".$row[\'lft\'].\" AND \".$row[\'rgt\'].\")\"));\n        }\n        $to_del = array_unique($ids);\n        $msgExtra = gTxt(\'smd_tag_children_and\');\n    } else {\n        // Collect the potential orphans and prepare to promote them\n        foreach ($to_del as $delid) {\n            $children = safe_column(\'id\', SMD_TAG, \"parent = \'\".$smd_tag_name[array_search($delid, $smd_tag_id)].\"\' AND type=\'$smd_tag_type\'\");\n            $children = array_diff($children, $to_del);\n            if ($children) {\n                $orphans += $children;\n            }\n        }\n    }\n\n    // Everything\'s prepped: begin the deletion\n    $numdel = count($to_del);\n\n    // Make a note of what we\'re about to do for the report\n    $all_dels = safe_rows(\'id, name\', SMD_TAG, \"id IN (\" . join(\',\', quote_list($to_del)) . \")\");\n    $all_names = array();\n    foreach ($all_dels as $aname) {\n        $all_names[$aname[\'id\']] = $aname[\'name\'];\n    }\n    foreach ($to_del as $delid) {\n        if (isset($all_names[$delid])) {\n            $ok[] = $all_names[$delid];\n        }\n    }\n\n    // First get rid of the direct tags\n    $where = \" IN (\'\".join(\"\',\'\", $to_del).\"\')\";\n    $ret = safe_delete(SMD_TAG, \'id\'.$where.\" AND type=\'$smd_tag_type\'\");\n    if ($ret) {\n        if (!$delt) {\n            // Promote the orphans if required\n            $orphlist = safe_column(\'name\', SMD_TAG, \"type=\'$smd_tag_type\' AND id IN (\'\".join(\"\',\'\", $orphans).\"\')\");\n            safe_update(SMD_TAG, \"parent=\'root\'\", \"type=\'$smd_tag_type\' AND id IN (\'\".join(\"\',\'\", $orphans).\"\')\");\n        }\n        // Remove references to any deleted tags in articles and linked categories\n        safe_delete(SMD_TAGU, \'tag_id\'.$where.\" AND type=\'$smd_tag_type\'\");\n        safe_delete(SMD_TAGC, \'tag_id\'.$where);\n\n        // Force all smd_tag_ boxes clear in the form\n        $_POST[\'smd_tag_id\'] = $_POST[\'smd_tag_oname\'] = $_POST[\'smd_tag_name\'] = $_POST[\'smd_tag_title\'] = $_POST[\'smd_tag_parent\'] = \'\';\n    }\n\n    // Build and generate the final report\n    if ($numdel > 1) {\n        $message = gTxt(\'smd_tag_multi_delete\', array(\'{number}\' => $numdel));\n        if ($ok) {\n            $report = gTxt(\'smd_tag_deleted_lbl\').join(\', \', $ok).\'.\';\n        }\n        if ($notok) {\n            $report .= br.br.gTxt(\'smd_tag_err_in_use_lbl\').join(\', \', $notok);\n        }\n        if ($orphans) {\n            $report .= br.br.gTxt(\'smd_tag_err_orphaned_lbl\').join(\', \', $orphlist);\n        }\n    } else {\n        if ($ok) {\n            $message = gTxt(\'smd_tag_deleted\', array(\'{type}\' => ucfirst($smd_tag_type), \'{name}\' => join(\', \', $ok))).$msgExtra;\n        } elseif ($notok) {\n            $message = gTxt(\'smd_tag_in_use\', array(\'{type}\' => ucfirst($smd_tag_type), \'{name}\' => join(\', \', $notok)));\n        }\n    }\n\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Make the passed tag a child of the given parent (if possible)\nfunction smd_tag_assign_parent($parid, $childid, $tag_type, $childnam, $rebuild = true) {\n    assert_int($childid);\n    assert_int($parid);\n    $status = array();\n    if ($childid == $parid) {\n        $status[] = \'err_self_parent\';\n    } else {\n        $exists = safe_field(\'name\', SMD_TAG, \"id = \'\".doSlash($parid).\"\' AND type = \'\".doSlash($tag_type).\"\'\");\n        $exists = ($exists == \'\') ? \'root\' : $exists;\n        $ret = safe_update(SMD_TAG, \"parent=\'\".doSlash($exists).\"\'\", \"id=\'\".$childid.\"\'\");\n        if ($rebuild) {\n            // Doesn\'t hurt to rebuild it anyway, even if the assignment fails\n            rebuild_tree_full($tag_type, SMD_TAG);\n        }\n        if ($ret) {\n            $status[] = \'parent_linked\';\n        } else {\n            $status[] = \'parent_not_linked\';\n        }\n    }\n\n    return $status;\n}\n\n\n// ------------------------\n// Assign a parent to a bunch of tags at once\n// Cheating code really; just calls the smd_tag_assign_parent for each tag but delays the\n// tree rebuild until the end for speed\nfunction smd_tags_multi_set_parent() {\n    extract(doSlash(gpsa(array(\'smd_tag_type\', \'smd_tag_id\', \'smd_tag_name\', \'smd_tag_extra\'))));\n\n    $message = $report = \'\';\n    $ok = $notok = array();\n    $theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));\n\n    foreach ($theList as $idx => $name) {\n        $status = smd_tag_assign_parent($smd_tag_extra, $idx, $smd_tag_type, $name, false);\n        if (in_array(\'parent_linked\', $status)) {\n            $ok[] = $name;\n        } else {\n            if (($pos = array_search(\'parent_not_linked\', $status)) !== false) {\n                unset($status[$pos]);\n            }\n            $notok[$idx] = $status;\n        }\n    }\n\n    if ($ok) {\n        $parnam = safe_field(\'name\', SMD_TAG, \"id=\'\".doSlash($smd_tag_extra).\"\'\");\n        $parnam = ($parnam == \'\') ? \'root\' : $parnam ;\n        $report = gTxt(\'smd_tag_parent_linked_lbl\', array(\'{parent}\' => $parnam)).join(\', \', $ok).\'.\';\n    }\n    if ($notok) {\n        $msgs = array();\n        foreach ($notok as $idx => $reasons) {\n            $rsn = array();\n            foreach ($reasons as $reason) {\n                $rsn[] = gTxt($reason);\n            }\n            $msgs[] = $theList[$idx].sp.\'(\'.join(\'/\', $rsn).\')\';\n        }\n        $report .= br.br.gTxt(\'smd_tag_not_parent_linked_lbl\').join(\', \', $msgs);\n    }\n\n    // Rebuild the tree now all the manipulation has been done\n    rebuild_tree_full($smd_tag_type, SMD_TAG);\n\n    smd_tags_manage($message, $report);\n}\n\n// ------------------------\n// Links the passed tag with the given category\nfunction smd_tag_assign_category($parnam, $childid, $cat_type, $childnam) {\n    assert_int($childid);\n    $status = array();\n\n    // Unlink if parent category name is empty\n    if ($parnam == \'\') {\n        safe_delete(SMD_TAGC, \"tag_id=\'\".doSlash($childid).\"\'\");\n        $status[] = \'category_unlinked\';\n    } else {\n        $exists = safe_field(\'id\', \'txp_category\', \"name = \'\".doSlash($parnam).\"\' AND type = \'\".doSlash($cat_type).\"\'\");\n        if ($exists) {\n            safe_delete(SMD_TAGC, \"tag_id=\'\".doSlash($childid).\"\'\");\n            $ret = safe_insert(SMD_TAGC, \"cat_id=\'\".doSlash($exists).\"\', tag_id = \'\".doSlash($childid).\"\'\");\n            if ($ret) {\n                $status[] = \'category_linked\';\n            } else {\n                $status[] = \'category_unlinked\';\n            }\n        }\n    }\n    return $status;\n}\n\n\n// ------------------------\n// Assign a bunch of tags to a category\n// Cheatingly calls smd_tag_assign_category multiple times\nfunction smd_tags_multi_catlink() {\n    extract(doSlash(gpsa(array(\'smd_tag_type\', \'smd_tag_id\', \'smd_tag_name\', \'smd_tag_extra\'))));\n\n    $message = $report = \'\';\n    $ok = $notok = array();\n\n    $theList = array_combine(do_list($smd_tag_id), do_list($smd_tag_name));\n\n    // Add in any child elements since they all need to take the category too\n    foreach ($theList as $idx => $name) {\n        $row = safe_row(\'*\', SMD_TAG, \"id=\'\".doSlash($idx).\"\'\");\n        $rs = safe_rows(\'id, name\', SMD_TAG, \"type = \'\".doSlash($smd_tag_type).\"\' AND (lft BETWEEN \" .$row[\'lft\']. \" AND \" .$row[\'rgt\']. \")\");\n        if ($rs) {\n            foreach ($rs as $rec) {\n                $theList[$rec[\'id\']] = $rec[\'name\'];\n            }\n        }\n    }\n\n    foreach ($theList as $idx => $name) {\n        $status = smd_tag_assign_category($smd_tag_extra, $idx, $smd_tag_type, $name);\n        if (in_array(\'category_linked\', $status)) {\n            $ok[] = $name;\n        } elseif (in_array(\'category_unlinked\', $status)) {\n            $notok[$idx] = $status;\n        }\n    }\n\n    if ($ok) {\n        $report = gTxt(\'smd_tag_category_linked_lbl\', array(\'{category}\' => $smd_tag_extra)).join(\', \', $ok).\'.\';\n    }\n    if ($notok) {\n        $msgs = array();\n        foreach ($notok as $idx => $reasons) {\n            $msgs[] = $theList[$idx];\n        }\n        $report .= br.br.gTxt(\'smd_tag_category_unlinked_lbl\').join(\', \', $msgs);\n    }\n\n    smd_tags_manage($message, $report);\n}\n\n/**\n * Allow tags to be imported from other tagging / unlimited cats plugins, or txp fields.\n *\n * @param      string  $message  The message to display\n * @param      string  $report   The report to display\n */\nfunction smd_tags_sync($message = \'\', $report = \'\')\n{\n    global $plugins;\n\n    $smd_tag_prefs = smd_tags_get_prefs(\'name\');\n\n    extract(doSlash(gpsa(array(\'smd_tags_sync_type\', \'smd_tags_sync_cfs\', \'smd_tags_sync_cfs_delim\', \'smd_tags_sync_section\', \'smd_tags_sync_parent\', \'smd_tags_delete_orig\', \'smd_tags_import_tag_parent\', \'smd_tags_import_cat_parent\', \'smd_tags_import_force_parent\', \'smd_tags_import_force_cat\', \'smd_tags_do_import\'))));\n\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_p_linkcat\', \'smd_tag_t_enrep\'), 1);\n    $clink = $ctrls[\'smd_tag_p_linkcat\'][\'val\'];\n    $showrep = $report && $ctrls[\'smd_tag_t_enrep\'][\'val\'];\n\n    pagetop(gTxt(\'smd_tag_sync_lbl\'),$message);\n    extract(smd_tags_buttons());\n\n    $plug_tt = (is_array($plugins) && in_array(\'tru_tags\',$plugins));\n    $plug_uc = (is_array($plugins) && in_array(\'rss_unlimited_categories\',$plugins));\n\n    // Prefs check\n    $prefset = smd_tags_pref_get($smd_tag_prefs);\n    $numReqPrefs = count($smd_tag_prefs);\n    $numPrefs = count($prefset);\n\n    // Perform the import via AJAX due to the quantity of tags that may be involved\n    if ($smd_tags_do_import) {\n        $impopts = array(\n            \'smd_tags_sync_type\' => $smd_tags_sync_type,\n            \'smd_tags_sync_cfs\' => $smd_tags_sync_cfs,\n            \'smd_tags_sync_cfs_delim\' => $smd_tags_sync_cfs_delim,\n            \'smd_tags_sync_section\' => $smd_tags_sync_section,\n            \'smd_tags_sync_parent\' => $smd_tags_sync_parent,\n            \'smd_tags_delete_orig\' => $smd_tags_delete_orig,\n            \'smd_tags_import_tag_parent\' => $smd_tags_import_tag_parent,\n            \'smd_tags_import_cat_parent\' => $smd_tags_import_cat_parent,\n            \'smd_tags_import_force_parent\' => $smd_tags_import_force_parent,\n            \'smd_tags_import_force_cat\' => $smd_tags_import_force_cat,\n        );\n        smd_tags_import($impopts);\n    }\n\n    $qs = array(\n        \"event\" => \"smd_tags\",\n    );\n\n    $qsVars = \"index.php\".join_qs($qs);\n\n    echo script_js(<<<EOS\njQuery(function() {\n    function smd_tagRestrict(typ) {\n        grabcat = jQuery(\"#smd_tags_import_cat_parent option:selected\").val();\n        var smd_link_mode = ((\'{$clink}\'==\'0\') ? \'2\' : ((grabcat==\'\') ? \'2\' : \'\'));\n\n        if (grabcat != \"undefined\" || grabcat != \'\') {\n            jQuery.post(\'{$qsVars}\', { step: \"smd_tag_parentlist\", name: \'smd_tag_parent\', type: typ, cat: grabcat, listonly: true, html_id: \'smd_tags_import_tag_parent\', link_mode: smd_link_mode, _txp_token: textpattern._txp_token },\n                function(data) {\n                    jQuery(\"#smd_tags_import_tag_parent_holder\").html(data);\n                    jQuery(\"#smd_tags_import_tag_parent_holder select option[value=\'{$smd_tags_import_tag_parent}\']\").prop(\"selected\", true);\n                }\n            );\n        }\n    }\n\n    smd_tagRestrict(\'article\');\n\n    if (\'{$clink}\' == \'1\') {\n        jQuery(\"#smd_tags_import_cat_parent\").change(function() {\n            smd_tagRestrict(\'article\');\n        });\n    }\n    if (\'{$showrep}\' == \'1\') {\n        smd_tags_toggle_report();\n    }\n\n    jQuery(\'input[name=\"smd_tags_sync_type\"]\').change(function() {\n        if (jQuery(this).filter(\':checked\').val() == \'3\') {\n            jQuery(\'.smd_tags_sync_txp\').show();\n        } else {\n            jQuery(\'.smd_tags_sync_txp\').hide();\n        }\n    }).change();\n\n    jQuery(\'#smd_tags_report_toggler, .smd_tags_btn_ok\').on(\'click\', smd_tags_toggle_report_handler);\n});\n\n/**\n * jQuery callback for displaying tag report.\n */\nfunction smd_tags_toggle_report_handler(ev) {\n    ev.preventDefault();\n    smd_tags_toggle_report();\n}\n\nfunction smd_tags_toggle_report() {\n    jQuery(\"#smd_tag_report_pane\").toggle(\'fast\');\n}\nEOS\n    );\n\n    // The tag sync preferences\n    if (smd_tags_table_exist()) {\n        // Tables installed\n        if ($numPrefs == $numReqPrefs) {\n            // Prefs all installed\n            echo \'<form method=\"post\" id=\"smd_syncit\" action=\"?event=smd_tags&step=smd_tags_sync\">\'.\n                n.\'<div class=\"txp-layout\">\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                n. hed(gTxt(\'smd_tag_sync_lbl\').sp.$btnHelp, 1).\n                n. \'</div>\'.\n                n. \'<div class=\"txp-layout-2col\">\'.\n                graf(\n                    $btnManage\n                    .n.$btnPrefs\n                , \' class=\"txp-actions\"\').\n            n. \'</div>\';\n\n            echo n. \'<div id=\"smd_tag_report_pane\">\'\n                .n. \'<div>\'\n                .n. href(\'&#215;\', \'#close\', \' class=\"smd_tags_btn_ok\" role=\"button\" title=\"\'.gTxt(\'close\').\'\" aria-label=\"\'.gTxt(\'close\').\'\"\')\n                .n. \'<h3>\', gTxt(\'smd_tag_report_lbl\'), \'</h3>\'\n                .$report\n                .n. \'</div>\'\n                .n. \'</div>\'\n                .n. \'<div class=\"txp-layout-1col\">\';\n\n            $lblStyle = \' class=\"pref-label\"\';\n\n            echo \'<a id=\"smd_tags_report_toggler\" href=\"#\">\'.gTxt(\'smd_tag_recent_report\').\'</a>\';\n            echo \'<section class=\"txp-prefs-group\">\';\n            echo hed(gTxt(\'smd_tag_sync_plugin_opts\'), 3);\n            $rset = array();\n\n            if ($plug_tt) {\n                $rset[0] = gTxt(\'smd_tag_sync_type1\');\n            }\n\n            if ($plug_uc) {\n                $rset[1] = gTxt(\'smd_tag_sync_type2\');\n            }\n\n            $rset[2] = gTxt(\'smd_tag_sync_type3\');\n            $rset[3] = gTxt(\'smd_tag_sync_type4\');\n\n            $rsel = 0;\n\n            if (empty($smd_tags_sync_type) || $plug_tt) {\n                $rsel = 0;\n            }\n\n            if ($smd_tags_sync_type == 1 || ($plug_uc && !$plug_tt)) {\n                $rsel = 1;\n            } elseif ($smd_tags_sync_type == 2 || (!$plug_uc && !$plug_tt)) {\n                $rsel = 2;\n            } elseif ($smd_tags_sync_type == 3 || (!$plug_uc && !$plug_tt && !$smd_tags_sync_type == 2)) {\n                $rsel = 3;\n            }\n\n            echo inputLabel(\n                \'smd_tags_sync_type\',\n                radioSet($rset, \'smd_tags_sync_type\', $rsel),\n                \'smd_tag_sync_type_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_type\',\n                )\n            );\n\n            // Txp category / custom_field import options\n            $cfs = getCustomFields();\n\n            echo ($cfs)\n                ? inputLabel(\n                    \'smd_tags_sync_cfs\',\n                    selectInput(\'smd_tags_sync_cfs\', $cfs, $smd_tags_sync_cfs, true),\n                    \'smd_tag_sync_cfs_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field smd_tags_sync_txp\',\n                        \'id\'    => \'prefs-smd_tags_sync_cfs\',\n                    )\n                )\n                : \'\';\n\n            echo ($cfs)\n                ? inputLabel(\n                    \'smd_tags_sync_cfs_delim\',\n                    fInput(\n                        \'text\',\n                        \'smd_tags_sync_cfs_delim\',\n                        ($smd_tags_sync_cfs_delim ? $smd_tags_sync_cfs_delim : \',\'),\n                        \'\',\n                        \'\',\n                        \'\',\n                        INPUT_XSMALL),\n                    \'smd_tag_sync_cfs_delim_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field smd_tags_sync_txp\',\n                        \'id\'    => \'prefs-smd_tags_sync_cfs_delim\',\n                    )\n                )\n                : \'\';\n\n            // Section import\n            $rs = safe_column(\'name\', \'txp_section\', \"name != \'default\'\");\n\n            if ($rs) {\n                $widget = selectInput(\'smd_tags_sync_section\', $rs, $smd_tags_sync_section, true);\n            } else {\n                $widget = gTxt(\'smd_tag_no_sections\');\n            }\n\n            echo inputLabel(\n                \'smd_tags_sync_section\',\n                $widget,\n                \'smd_tag_sync_section_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_section\',\n                )\n            );\n\n            // Start at parent category (rss_uc only)\n            // The list is populated anyway even if the plugin isn\'t active because $rsCats is used later\n            if ($clink) {\n                $rsCats = getTree(\'root\', \'article\');\n\n                if ($rsCats) {\n                    $catlist = treeSelectInput(\'smd_tags_sync_parent\', $rsCats, $smd_tags_sync_parent, \'smd_tags_sync_parent\', 35)\n                        .(($plug_tt) ? gTxt(\'smd_tag_rss_uc_only\') : \'\');\n                } else {\n                    $catlist = gTxt(\'smd_tag_no_parent\');\n                }\n\n                echo inputLabel(\n                    \'smd_tags_sync_parent\',\n                    $catlist,\n                    \'smd_tag_sync_parent_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field\',\n                        \'id\'    => \'prefs-smd_tags_sync_parent\',\n                    )\n                );\n            }\n\n            // Delete original\n            echo inputLabel(\n                \'smd_tags_delete_orig\',\n                yesnoRadio(\'smd_tags_delete_orig\', (int)$smd_tags_delete_orig).n.gTxt(\'smd_tag_rss_uc_tru_tags_only\'),\n                \'smd_tag_sync_delete_orig_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_delete_orig\',\n                )\n            );\n\n            echo \'</section>\';\n            echo hed(gTxt(\'smd_tag_sync_import_opts\'), 3);\n            echo \'<section class=\"txp-prefs-group\">\';\n\n            // Assign to category\n            if ($clink) {\n                if ($rsCats) {\n                    $catlist = treeSelectInput(\'smd_tags_import_cat_parent\', $rsCats, $smd_tags_import_cat_parent, \'smd_tags_import_cat_parent\', 35);\n                } else {\n                    $catlist = gTxt(\'smd_tag_no_parent\');\n                }\n\n                echo inputLabel(\n                    \'smd_tags_import_cat_parent\',\n                    $catlist .br. checkbox(\'smd_tags_import_force_cat\', 1, (int)$smd_tags_import_force_cat, 0, \'smd_tags_import_force_cat\').n.\'<label for=\"smd_tags_import_force_cat\">\'.gTxt(\'smd_tag_sync_force_cat_lbl\').\'</label>\',\n                    \'smd_tag_sync_parent_cat_lbl\',\n                    \'\',\n                    array(\n                        \'class\' => \'txp-form-field\',\n                        \'id\'    => \'prefs-smd_tags_sync_parent_cat\',\n                    )\n                );\n            }\n\n            // Assign to parent tag\n            echo inputLabel(\n                \'smd_tags_import_tag_parent\',\n                \'<div id=\"smd_tags_import_tag_parent_holder\"></div>\'\n                    .n.checkbox(\'smd_tags_import_force_parent\', 1, (int)$smd_tags_import_force_parent, 0, \'smd_tags_import_force_parent\').n.\'<label for=\"smd_tags_import_force_parent\">\'.gTxt(\'smd_tag_sync_force_parent_lbl\').\'</label>\',\n                \'smd_tag_sync_parent_tag_lbl\',\n                \'\',\n                array(\n                    \'class\' => \'txp-form-field\',\n                    \'id\'    => \'prefs-smd_tags_sync_parent_tag\',\n                )\n            );\n\n            // Report viewer\n            echo gTxt(\'smd_tag_import_results_pt1\') . \'<span id=\"smd_tags_import_icurr\"></span><span id=\"smd_tags_import_itot\"></span> \'\n                .br. gTxt(\'smd_tag_import_results_pt2\') . \'<span id=\"smd_tags_import_lnk_curr\"></span>\';\n            echo \'</section>\';\n\n            echo n.$btnSyncGo;\n            echo tInput()\n                . \'</div></form>\';\n        } elseif ($numPrefs > 0 && $numPrefs < $numReqPrefs) {\n            // Prefs possibly corrupt, or plugin updated\n            echo startTable()\n                .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some\')).br.br\n                    .gTxt(\'smd_tag_prefs_some_explain\').br.br\n                    .gTxt(\'smd_tag_prefs_some_opts1\'), \' colspan=\"2\"\')\n                )\n                .n. tr(\n                    tda($btnRemove,$btnStyle)\n                    . tda($btnInstall, $btnStyle)\n                )\n                .n. endTable();\n        } else {\n            // Prefs not installed\n            echo startTable()\n                .n. tr(tda(gTxt(\'smd_tag_prefs_not_installed\'), \' colspan=\"2\"\'))\n                .n. tr(tda($btnInstall, $btnStyle))\n                .n. endTable();\n        }\n    } else {\n        // Tables not installed\n        echo startTable()\n            .n. tr(tda(strong(gTxt(\'smd_tag_prefs_some_tbl\')).br.br\n                .gTxt(\'psmd_tag_refs_some_explain\').br.br\n                .gTxt(\'smd_tag_prefs_some_opts2\'), \' colspan=\"2\"\')\n            )\n            .n. tr(tda($btnInstallTbl, $btnStyle))\n            .n. endTable();\n    }\n\n    echo \'</div>\';\n}\n\n// ------------------------\n// Import tags from other places\nfunction smd_tags_import($smd_tag_options) {\n    extract($smd_tag_options);\n\n    $message = \'\';\n    $iparent = ($smd_tags_sync_parent) ? \" AND parent = \\\'\".doSlash($smd_tags_sync_parent).\"\\\' \" : \'\';\n\n    $rs = safe_rows(\'*\', \'textpattern\', (($smd_tags_sync_section) ? \"Section=\'\".doSlash($smd_tags_sync_section).\"\'\" : \'1=1\'));\n    $row_ctr = $tag_ctr = 0;\n    $total = count($rs);\n    $ctrls = smd_tags_pref_get(array(\'smd_tag_t_enrep\'), 1);\n    $showrep = $ctrls[\'smd_tag_t_enrep\'][\'val\'];\n\n    echo \'<script type=\"text/javascript\">\';\n    echo \"var ictr = 0; var itot = {$total}; var lnk_ctr = 0;\";\n    echo \'jQuery(function() {\';\n    echo \'jQuery(\"#smd_tags_import_itot\").text(\"/\'.$total.\'\");\';\n    if ($rs) {\n        foreach ($rs as $row) {\n            switch ($smd_tags_sync_type) {\n                case \'0\':\n                    // tru_tags\n                    $idata = doSlash($row[\'Keywords\']);\n                    break;\n                case \'1\':\n                case \'2\':\n                    // rss_uc / Txp cats\n                    $idata = doSlash($row[\'ID\']);\n                    break;\n                case \'3\':\n                    // CFs\n                    $idata = (($smd_tags_sync_cfs && isset($row[\'custom_\'.$smd_tags_sync_cfs])) ? doSlash($row[\'custom_\'.$smd_tags_sync_cfs]) : \'\');\n                    break;\n            }\n            $ititle = doSlash($row[\'Title\']);\n            $artid = doSlash($row[\'ID\']);\n            echo <<<EOJS\n            sendAsyncEvent(\n            {\n                event: textpattern.event,\n                step: \'smd_tags_import_one\',\n                smd_tags_sync_type: \'{$smd_tags_sync_type}\',\n                smd_tags_sync_id: \'{$artid}\',\n                smd_tags_sync_data: \'{$idata}\',\n                smd_tags_sync_title: \'{$ititle}\',\n                smd_tags_sync_parent: \'{$iparent}\',\n                smd_tags_sync_cfs: \'{$smd_tags_sync_cfs}\',\n                smd_tags_sync_cfs_delim: \'{$smd_tags_sync_cfs_delim}\',\n                smd_tags_import_tag_parent: \'{$smd_tags_import_tag_parent}\',\n                smd_tags_import_cat_parent: \'{$smd_tags_import_cat_parent}\',\n                smd_tags_import_force_parent: \'{$smd_tags_import_force_parent}\',\n                smd_tags_import_force_cat: \'{$smd_tags_import_force_cat}\',\n                smd_tags_delete_orig: \'{$smd_tags_delete_orig}\'\n            }, smd_tags_group_feedback);\nEOJS;\n        }\n        echo \'});\';\n        echo <<<EOJS\n        function smd_tags_group_feedback(data) {\n            ictr++;\n            jQuery(\'#smd_tags_import_icurr\').text(ictr);\n            jQuery(\'#smd_tag_report_pane\').append(jQuery(data).find(\'smd_tags_report\').attr(\'value\'));\n            lnk_ctr += (jQuery(data).find(\'smd_tags_link_ctr\').attr(\'value\')) * 1; // multiply by 1 to convert to number\n            jQuery(\'#smd_tags_import_lnk_curr\').text(lnk_ctr);\n            if (\'{$showrep}\' == \'1\' && ictr >= itot) {\n                smd_tags_toggle_report();\n            }\n        }\nEOJS;\n        echo \'</script>\';\n    }\n}\n\n// ------------------------\nfunction smd_tags_import_one() {\n    $smd_tags_sync_type = gps(\'smd_tags_sync_type\');\n    $smd_tags_sync_data = gps(\'smd_tags_sync_data\');\n    $smd_tags_sync_id = gps(\'smd_tags_sync_id\');\n    $smd_tags_sync_title = gps(\'smd_tags_sync_title\');\n    $smd_tags_sync_parent = gps(\'smd_tags_sync_parent\');\n    $smd_tags_sync_cfs = gps(\'smd_tags_sync_cfs\');\n    $smd_tags_sync_cfs_delim = gps(\'smd_tags_sync_cfs_delim\');\n    $smd_tags_import_tag_parent = gps(\'smd_tags_import_tag_parent\');\n    $smd_tags_import_cat_parent = gps(\'smd_tags_import_cat_parent\');\n    $smd_tags_import_force_parent = gps(\'smd_tags_import_force_parent\');\n    $smd_tags_imort_force_cat = gps(\'smd_tags_import_force_cat\');\n    $smd_tags_delete_orig = gps(\'smd_tags_delete_orig\');\n\n    $tag_ids = $tag_names = $keylist = array();\n\n    if (in_array($smd_tags_sync_type, array (\'0\', \'3\'))) {\n        // tru_tags / Txp CF\n        $dlm = ($smd_tags_sync_type == \'3\' && $smd_tags_sync_cfs_delim) ? $smd_tags_sync_cfs_delim : \',\';\n        $keys = do_list($smd_tags_sync_data, $dlm);\n        foreach ($keys as $key) {\n            if ($key==\'\') continue;\n            $keylist[] = array(\'name\' => $key, \'title\' => $key);\n        }\n    } elseif ($smd_tags_sync_type == \'1\') {\n        // rss_uc\n        $clause = \'tc.article_id = \'.doSlash($smd_tags_sync_data);\n        $keylist = getRows(\"SELECT c.name, c.title FROM \".PFX.\"textpattern_category as tc LEFT JOIN \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE \".$clause.$smd_tags_sync_parent);\n        $keylist = ($keylist) ? $keylist : array();\n    } elseif ($smd_tags_sync_type == \'2\') {\n        // txp cats\n        $clause = \'txt.ID = \'.doSlash($smd_tags_sync_data);\n        $keylist = getRows(\"SELECT c.name, c.title FROM \".PFX.\"textpattern as txt LEFT JOIN \".PFX.\"txp_category as c ON (txt.Category1 = c.name OR txt.Category2 = c.name) WHERE \".$clause.$smd_tags_sync_parent);\n        $keylist = ($keylist) ? $keylist : array();\n    }\n\n    // Insert /update each tag in the given place\n    foreach ($keylist as $item) {\n        $itn = trim($item[\'name\']);\n        $itt = trim($item[\'title\']);\n\n        // Skip invalid entries\n        if ($itn == \'\' || $itn == NULL || $itt == \'\' || $itt == NULL) continue;\n\n        $id = smd_tag_getsert($itn, \'article\', $itt, $smd_tags_import_tag_parent, $smd_tags_import_cat_parent, \'\', $smd_tags_import_force_parent, $smd_tags_import_force_cat);\n        if ($id) {\n            $tag_ids[] = $id;\n            $tag_names[] = $itn;\n        }\n    }\n\n    if ($tag_ids) {\n        $report = strong($smd_tags_sync_title).\': \'.join(\', \', $tag_names).br;\n        if ($smd_tags_delete_orig) {\n            if ($smd_tags_sync_type == \'0\') {\n                safe_update(\'textpattern\', \"Keywords=\'\'\", \"id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            } elseif ($smd_tags_sync_type == \'1\') {\n                safe_delete(\'textpattern_category\', \"article_id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            } elseif ($smd_tags_sync_type == \'2\') {\n                // Conspicuously missing until such time as a suitable method can be found to deal with it\n            } elseif ($smd_tags_sync_type == \'3\') {\n                safe_update(\'textpattern\', \"custom_$smd_tags_sync_cfs=\'\'\", \"id=\'\".doSlash($smd_tags_sync_id).\"\'\");\n            }\n        }\n    }\n    $tag_ctr = smd_tag_link($smd_tags_sync_id, $tag_ids, \'article\');\n    send_xml_response(array(\'smd_tags_report\' => $report, \'smd_tags_link_ctr\' => $tag_ctr));\n}\n\n// ------------------------\n// MIDDLEWARE - ADMIN/PUBLIC GLUE\n// ------------------------\n// Grab a valid tag parent list as HTML. Cannot be done easily from fixed data: AJAX is the only reliable way.\n// Needs to take into account the various use cases:\n//  * full list (link_mode=2)\n//  * from given parent nodes (link_mode=1)\n//  * excluding forbidden items (e.g. when assigning parent to tag that is already in the subtree)\n//  * adding interface elements like [clr] and [tog] buttons\n// TODO: take the bi-directional setting into account\nfunction smd_tag_parentlist() {\n    $trycat = isset($_POST[\'cat\']);\n    extract(doSlash(gpsa(array(\'name\', \'type\', \'id\', \'cat\', \'itemid\', \'listonly\', \'html_id\', \'link_mode\'))));\n\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $listonly = ($listonly == \'\' || $listonly == \'undefined\') ? \'\' : $listonly;\n    $html_id = ($html_id == \'\' || $html_id == \'undefined\') ? \'smd_tag_parent\' : $html_id;\n    $type = ($type == \'\' || $type == \'undefined\') ? \'article\' : $type;\n    $cat = ($cat == \'\' || $cat == \'undefined\') ? \'\' : $cat;\n    $link_mode = ($link_mode == \'\' || $link_mode == \'undefined\') ? \'1\' : $link_mode;\n    $tags = ($itemid == \'\' || $itemid == \'undefined\') ? array() : safe_rows(\'tag_id\', SMD_TAGU, \"type=\'$type\' AND item_id=\'$itemid\'\");\n    $clrBtn = \'[<span id=\"smd_clr\" class=\"smd_fakebtn\">\'.gTxt(\'smd_tag_clear\').\'</span>]\';\n    $togBtn = \'[<span id=\"smd_tog\" class=\"smd_fakebtn\">\'.gTxt(\'smd_tag_toggle\').\'</span>]\';\n\n    $pref = smd_tags_pref_get(array(\'smd_tag_p_listpar\', \'smd_tag_p_master\', \'smd_tag_p_linkcat\'), 1);\n    $incl = $pref[\'smd_tag_p_listpar\'][\'val\'];\n    $god = $pref[\'smd_tag_p_master\'][\'val\'];\n    $clink = $pref[\'smd_tag_p_linkcat\'][\'val\'];\n   $god_clause = ($god == \'\') ? \'\' : \"txt.parent = \'\" . doSlash($god) . \"\'\";\n\n    if ($cat && $clink) {\n       $cat = do_list($cat);\n       $rsid = array();\n       $rsc = getRows(\"SELECT DISTINCT txt.id, txt.parent, txt.lft, txt.rgt\n            FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.\"txp_category AS txc, \".PFX.SMD_TAGC.\" AS txl\n            WHERE ( ( txt.id = txl.tag_id\n            AND  txl.cat_id = txc.id\n            AND txc.name IN (\'\" .join(\"\',\'\", $cat). \"\') )\" . ($god_clause ? \' OR \'.$god_clause : \'\') . \")\n            AND txt.type = \'$type\'\");\n        if ($rsc) {\n            foreach ($rsc as $row) {\n                $include_parent = ($incl || $row[\'parent\'] == $god) ? \'\' : \' AND lft != \'.$row[\'lft\'];\n                $ids = safe_column(\'id\', SMD_TAG, \"type = \'$type\' AND (lft BETWEEN \" .$row[\'lft\']. \" AND \" .$row[\'rgt\']. \")\" . $include_parent);\n                $rsid = array_merge($rsid, $ids);\n            }\n        }\n        $cat = ($rsc) ? \' AND id IN (\'.doQuote(join(\"\',\'\",$rsid)).\')\' : \'\';\n    } elseif ($god != \'\' && $link_mode == \'1\') {\n        $ids = safe_column(\'id\', SMD_TAG, \"type = \'$type\' AND parent = \'\" . doSlash($god) . \"\'\");\n        $cat = ($ids) ? \' AND id IN (\'.doQuote(join(\"\',\'\",$ids)).\')\' : \'\';\n    } else {\n        $cat = \'\';\n    }\n\n    $link_mode = (($cat && $link_mode == \'1\') || $link_mode == \'2\') ? \'1=1\' : \'1=0\';\n\n    if ($id) {\n        $id = assert_int($id);\n        list($lft, $rgt) = array_values(safe_row(\'lft, rgt\', SMD_TAG, \'id = \'.$id));\n        $rs = smd_tag_tree(\'root\', $type, \"lft not between $lft and $rgt\".$cat, SMD_TAG);\n    } else {\n        $rs = smd_tag_tree(\'root\', $type, $link_mode.$cat, SMD_TAG);\n    }\n\n    if ($rs) {\n       if ($trycat && !$listonly) {\n            echo smd_multiTreeSelectInput($html_id, $rs, $tags).$clrBtn.$togBtn;\n        } else {\n            echo treeSelectInput($html_id, $rs, $name);\n        }\n    } else {\n        echo gTxt(\'smd_tag_no_parent\');\n    }\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Grab a valid category list\nfunction smd_tag_catlist($cat=\'\') {\n    extract(doSlash(gpsa(array(\'name\',\'type\',\'html_id\'))));\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $type = ($type == \"\" || $type == \"undefined\") ? \'article\' : $type;\n    $html_id = ($html_id == \"\" || $html_id == \"undefined\") ? \'smd_tags_catlist\' : $html_id;\n    $rs = getTree(\'root\', $type, \"1=1\");\n\n    if ($rs) {\n        echo treeSelectInput($html_id, $rs, $name, $html_id);\n    } else {\n        echo gTxt(\'no_categories_available\');\n    }\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Fetch a description for use as a tooltip\nfunction smd_tag_get_desc() {\n    extract(doSlash(gpsa(array(\'tag_ref\'))));\n    while(@ob_end_clean()); // Get rid of any page so far\n\n    $tag_ref = ($tag_ref == \"\" || $tag_ref == \"undefined\") ? \'\' : $tag_ref;\n\n    if ($tag_ref) {\n        $field = is_numeric($tag_ref) ? \'id\' : \'title\';\n        $desc = safe_field(\'desc_html\', SMD_TAG, \"$field=\'\" . doSlash($tag_ref) . \"\'\");\n        echo ($desc) ? $desc : gTxt(\'smd_tag_no_desc\');\n    } else {\n        send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\n    }\n\n    exit(); // Don\'t call page_end()\n}\n\n// ------------------------\n// Remove empty URL elements (used as callback to array_filter())\nfunction smd_tags_remove_empty($var) {\n    return($var != \'\');\n}\n\n// ------------------------\n// Handle fake tag section URL\nfunction smd_tags_url_handler($evt, $stp) {\n    global $smd_tag_type, $pretext, $permlink_mode;\n\n    if (!smd_tags_table_exist()) {\n        return;\n    }\n\n    $prefs = smd_tags_pref_get(array(\'smd_tag_u_sec\', \'smd_tag_u_pnam\', \'smd_tag_u_ptyp\'), 1);\n    $urlsec = do_list($prefs[\'smd_tag_u_sec\'][\'val\']);\n    $urlnam = $prefs[\'smd_tag_u_pnam\'][\'val\'];\n    $urltyp = $prefs[\'smd_tag_u_ptyp\'][\'val\'];\n\n    $subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\", \"$1\", hu);\n    $regsafesubpath = preg_quote($subpath, \'/\');\n    $req = preg_replace(\"/^$regsafesubpath/i\", \'/\', serverSet(\'REQUEST_URI\'));\n\n    $qs = strpos($req, \'?\');\n    $qatts = ($qs ? a.substr($req, $qs + 1) : \'\');\n\n    if ($qs) $req = substr($req, 0, $qs);\n    $parts = array_values(array_filter(explode(\'/\', $req), \'smd_tags_remove_empty\'));\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n\n    // Deal with clean URL syntax first, trying to avoid clashes with built-in permlink schemes.\n    if (count($parts) > 1) {\n        // Determine if this URL is one we care about.\n        // If so, find where the tag portion begins (immediately after the trigger).\n        $pos = false;\n        foreach($urlsec as $trigger) {\n            if (($pos = array_search($trigger, $parts)) !== false) {\n                $pos++; // Start of the tag info\n                break;\n            }\n        }\n\n        if ($pos !== false) {\n            // Try to detect section/id/title permlink scheme\n            $sit = false;\n            if (count($parts) == 3) {\n                // Note that \'0\' is not a valid article ID but is numeric and could be the name of a tag, so it needs special treatment\n                if (is_numeric($parts[1]) && $parts[1] != \'0\') {\n                    $sit = true;\n                }\n            }\n\n            // As long as this is a regular permlink scheme, set the tag to be the remaining URL portion\n            if (!$sit) {\n                if (in_array($parts[$pos], $validTypes)) {\n                    $smd_tag_type = $parts[$pos];\n                    $pos++;\n                } else {\n                    $smd_tag_type = $validTypes[0];\n                }\n\n                $smd_tag = join(\'/\', array_slice($parts, $pos));\n                smd_tags_set($smd_tag_type, $smd_tag);\n                $_SERVER[\'QUERY_STRING\'] = $qatts;\n                $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0]; // Drop back to section list mode\n//              $_SERVER[\'QUERY_STRING\'] = $urlnam.\'=\'.$smd_tag .a. $urltyp.\'=\'.$smd_tag_type . $qatts;\n//              $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0] . \'/?\' . serverSet(\'QUERY_STRING\');\n            }\n        }\n    } elseif ((count($parts) == 1) && (in_array($parts[0], $urlsec) || in_array(gps(\'s\'), $urlsec))) {\n        // Default or named section (or /title permlink mode) + possible messy tag syntax\n        $theType = gps($urltyp);\n        $smd_tag = gps($urlnam);\n        $smd_tag_type = (empty($theType) && empty($smd_tag)) ? \'\' : ((in_array($theType, $validTypes)) ? $theType : $validTypes[0]);\n        smd_tags_set($smd_tag_type, $smd_tag);\n        $_SERVER[\'QUERY_STRING\'] = (($permlink_mode == \'messy\') ? $urlnam.\'=\'.$smd_tag .a. $urltyp.\'=\'.$smd_tag_type : \'\') . $qatts;\n        $_SERVER[\'REQUEST_URI\'] = $subpath . $parts[0] . (($permlink_mode == \'messy\') ? \'/?\' . serverSet(\'QUERY_STRING\') : \'\');\n    }\n}\n\n// ------------------------\nfunction smd_tags_set($typ, $tag=\'\', $item=\'\') {\n    global $smd_tags;\n\n    $smd_tags = array();\n\n    $prefs = smd_tags_pref_get(array(\'smd_tag_u_combi\', \'smd_tag_u_combi_and\', \'smd_tag_u_combi_or\'), 1);\n    $combi = $prefs[\'smd_tag_u_combi\'][\'val\'];\n    $cand = $prefs[\'smd_tag_u_combi_and\'][\'val\'];\n    $cor = $prefs[\'smd_tag_u_combi_or\'][\'val\'];\n\n    // Multi-tag filtering\n    if ($tag) {\n        $taglist = explode($cor, $tag);\n        $num_or = count($taglist);\n        if ($num_or == 1) {\n            // Only one item in the exploded list could mean one of two things:\n            //  a) only one item(!)\n            //  b) the \'and\' delimiter is in use\n            $andlist = explode($cand, $tag);\n            $num_and = count($andlist);\n            if ($num_and > 1) {\n                $smd_tags[\'meta\'][\'search_mode\'] = \'and\';\n                $smd_tags[\'meta\'][\'tag_head\'] = $andlist[0];\n                $smd_tags[\'meta\'][\'tag_tail\'] = $andlist[count($andlist)-1];\n                $taglist = $andlist;\n            } else {\n                $smd_tags[\'meta\'][\'search_mode\'] = \'single\';\n                $smd_tags[\'meta\'][\'tag_head\'] = $smd_tags[\'meta\'][\'tag_tail\'] = $andlist[0];\n            }\n        } elseif ($num_or > 1) {\n            $smd_tags[\'meta\'][\'search_mode\'] = \'or\';\n            $smd_tags[\'meta\'][\'tag_head\'] = $taglist[0];\n            $smd_tags[\'meta\'][\'tag_tail\'] = $taglist[count($taglist)-1];\n        }\n        $smd_tags[\'meta\'][\'tag_list\'] = $taglist;\n        $tag = join(\',\', quote_list($taglist));\n    }\n\n    // Override the search mode if it\'s switched off in prefs\n    if (!$combi) {\n        $smd_tags[\'meta\'][\'search_mode\'] = \'single\';\n    }\n\n    // MONSTER!\n    $rs = startRows(\n        \"SELECT id, name, txt.type, parent, title, desc_html AS description, count(item_id) AS count\"\n        .\",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children\"\n        .\",(SELECT COUNT(*) FROM \".PFX.SMD_TAG.\" WHERE name=name\" . (($typ) ? \" AND type=\'$typ\'\" : \"\") . \" AND lft < txt.lft AND rgt > txt.rgt)-1 AS level\"\n        .\" FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.SMD_TAGU.\" AS txu\"\n        .\" WHERE tag_id = id\"\n        .(($typ) ? \" AND txt.type=\'$typ\'\" : \"\")\n        .(($tag) ? \" AND name IN ($tag)\" : \"\")\n        .(($item) ? \" AND item_id=\'$item\'\" : \"\")\n        .\" GROUP BY id\");\n    smd_tags_populate($rs, $typ);\n\n    // In case one of the URL tags isn\'t in use, populate $smd_tags with the tag details.\n    // Note this one requires type.\n    if (!$item) {\n        $rs = startRows(\n            \"SELECT id, name, txt.type, parent, title, desc_html AS description\"\n            .\",(SELECT CAST((rgt - lft - 1) / 2 AS UNSIGNED)) AS children\"\n            .\",(SELECT COUNT(*) FROM \".PFX.SMD_TAG.\" WHERE type=\'$typ\' AND name=name AND lft < txt.lft AND rgt > txt.rgt)-1 AS level\"\n            .\" FROM \".PFX.SMD_TAG.\" AS txt\"\n            .\" WHERE txt.type=\'$typ\'\"\n            .(($tag) ? \" AND name IN ($tag)\" : \"\")\n            .\" GROUP BY id\");\n        smd_tags_populate($rs, $typ);\n    }\n}\n\n// ------------------------\nfunction smd_tags_populate($rs, $typ) {\n    global $smd_tags;\n\n    while ($row = nextRow($rs)) {\n        $cnt = (isset($row[\'count\'])) ? $row[\'count\'] : \'0\';\n        $smd_tags[$typ][$row[\'id\']] = array(\'tag_name\' => $row[\'name\'], \'tag_title\' => $row[\'title\'], \'tag_description\' => $row[\'description\'], \'tag_parent\' => $row[\'parent\'], \'tag_count\' => $cnt, \'tag_children\' => $row[\'children\'], \'tag_level\' => $row[\'level\']);\n        $smd_tags[$typ][\'tag_name\'][$row[\'id\']] = $row[\'name\'];\n        $smd_tags[$typ][\'tag_title\'][$row[\'id\']] = $row[\'title\'];\n        $smd_tags[$typ][\'tag_description\'][$row[\'id\']] = $row[\'description\'];\n        $smd_tags[$typ][\'tag_parent\'][$row[\'id\']] = $row[\'parent\'];\n        $smd_tags[$typ][\'tag_count\'][$row[\'id\']] = $cnt;\n        $smd_tags[$typ][\'tag_children\'][$row[\'id\']] = $row[\'children\'];\n        $smd_tags[$typ][\'tag_level\'][$row[\'id\']] = $row[\'level\'];\n    }\n}\n\n// ------------------------\n// Add tags to the global scope, depending on context.\n// Returns the current context\nfunction smd_tags_context() {\n    global $thisarticle, $thisfile, $thislink, $thisimage, $smd_tag_type, $smd_tag_items, $smd_tags, $smd_thistag;\n\n    $ctxt = $smd_tag_type;\n    $ids = array();\n    $scp = isset($smd_tags[$ctxt]) ? $smd_tags[$ctxt] : array();\n\n    // Individual article or list\n    $id = \'\';\n    if (!empty($thisimage)) {\n        $id = $thisimage[\'id\'];\n        $ctxt = \'image\';\n        $scp = $thisimage;\n    } elseif (!empty($thisfile)) {\n        $id = $thisfile[\'id\'];\n        $ctxt = \'file\';\n        $scp = $thisfile;\n    } elseif (!empty($thislink)) {\n        $id = $thislink[\'id\'];\n        $ctxt = \'link\';\n        $scp = $thislink;\n    } elseif (!empty($thisarticle)) {\n        $id = $thisarticle[\'thisid\'];\n        $ctxt = \'article\';\n        $scp = $thisarticle;\n    }\n\n    if ($id) {\n        smd_tags_set($ctxt, \'\', $id);\n    }\n\n    if (isset($smd_tags[$ctxt])) {\n        foreach($smd_tags[$ctxt] as $rid => $row) {\n            if (is_int($rid)) {\n                $ids[] = $rid;\n            }\n        }\n    }\n\n    // TODO: is this needed to overwrite if tag already set?\n    if (!empty($smd_thistag)) {\n        $ids = array($smd_thistag[\'tag_id\']);\n    }\n\n    return array(\'context\' => $ctxt, \'scope\' => $scp, \'id\' => $ids);\n}\n\n// ------------------------\n// Load current tag into global scope\nfunction smd_tag_populate($row) {\n    global $smd_thistag;\n    $smd_thistag[\'tag_id\'] = $row[\'id\'];\n    $smd_thistag[\'tag_name\'] = $row[\'name\'];\n    $smd_thistag[\'tag_lettername\'] = smd_tags_utf8_substr($row[\'name\'], 0, 1);\n    $smd_thistag[\'tag_lettertitle\'] = smd_tags_utf8_substr($row[\'title\'], 0, 1);\n    $smd_thistag[\'tag_type\'] = $row[\'type\'];\n    $smd_thistag[\'tag_parent\'] = $row[\'parent\'];\n    $smd_thistag[\'tag_title\'] = $row[\'title\'];\n    $smd_thistag[\'tag_description\'] = $row[\'description\'];\n    $smd_thistag[\'tag_children\'] = $row[\'children\'];\n    $smd_thistag[\'tag_level\'] = $row[\'level\'];\n    $smd_thistag[\'tag_count\'] = $row[\'count\'];\n    $smd_thistag[\'tag_indent\'] = $row[\'indent\'];\n    $smd_thistag[\'tag_weight\'] = $row[\'weight\'];\n    $smd_thistag[\'tag_first\'] = $row[\'first\'];\n    $smd_thistag[\'tag_last\'] = $row[\'last\'];\n}\n\n// Thanks http://us2.php.net/manual/en/function.substr.php\nfunction smd_tags_utf8_substr($str, $start) {\n    preg_match_all(\"/./u\", $str, $ar);\n    if(func_num_args() >= 3) {\n        $end = func_get_arg(2);\n        return join(\'\',array_slice($ar[0],$start,$end));\n    } else {\n        return join(\'\',array_slice($ar[0],$start));\n    }\n}\n\n// ------------------------\n// All tags/counts\nfunction smd_tag_list_adm($atts) {\n    include_once txpath.\'/publish/taghandlers.php\';\n    extract(lAtts(array(\n        \'type\'       => \'article\',\n        \'indent\'     => \'&#160;&#160;\',\n        \'count\'      => 1,\n        \'label\'      => \'\',\n        \'labeltag\'   => \'\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'\',\n        \'class\'      => __FUNCTION__,\n        \'breakclass\' => \'\',\n    ),$atts));\n\n    $validTypes = array(\'list\' => \'article\', \'image\' => \'image\', \'file\' => \'file\', \'link\' => \'link\');\n    $rs = smd_tag_tree(\'root\', $type, \'1=1\', SMD_TAG);\n    $rsc = getRows(\"SELECT txl.tag_id, txc.name\n            FROM \".PFX.SMD_TAG.\" AS txt, \".PFX.SMD_TAGC.\" AS txl, \".PFX.\"txp_category AS txc\n            WHERE txt.type = \'$type\' AND txt.id = txl.tag_id AND txl.cat_id = txc.id\");\n    $rscout = array();\n    if ($rsc) {\n        foreach ($rsc as $idx => $row) {\n            $rscout[$row[\'tag_id\']] = $row[\'name\'];\n        }\n    }\n\n    if ($rs) {\n        $out = array();\n        $totals = array();\n        $pref = smd_tags_pref_get(\'smd_tag_p_master\', 1);\n        $god = $pref[\'smd_tag_p_master\'][\'val\'];\n\n        // Tally each tag\'s usage\n        if ($count) {\n            $rsu = safe_rows_start(\'tag_id, count(*) as num\', SMD_TAGU, \"type=\'$type\' GROUP BY tag_id\");\n            while ($row = nextRow($rsu)) {\n                $tagid = $row[\'tag_id\'];\n                $num = $row[\'num\'];\n                $ids = safe_column(\'item_id\', SMD_TAGU, \"type=\'$type\' AND tag_id=\'$tagid\'\");\n                $totals[$tagid] = array($num, join(\",\",$ids));\n            }\n        }\n\n        foreach ($rs as $row) {\n            extract($row);\n            $parent = (isset($parent)) ? $parent : \'\'; // Parent might not be defined\n            $row[\'type\'] = $type;\n            if (isset($totals[$id])) {\n                $row[\'count\'] = $totals[$id][0];\n                $ev = array_keys($validTypes,$type);\n                $url = \'index.php?event=\'.$ev[0].a.\'search_method=id\'.a.\'crit=\'.$totals[$id][1];\n                $cntop = sp.href(\'(\'.$totals[$id][0].\')\', $url);\n            } else {\n                $row[\'count\'] = 0;\n                $cntop = sp.\'(0)\';\n            }\n\n            $rowdata = \'<span name=\"smd_tagnam\" class=\"smd_hidden\">\'.$name\n                    .\'</span><span name=\"smd_tagid\" class=\"smd_hidden\">\'.$id\n                    .\'</span><span name=\"smd_tagttl\" class=\"smd_hidden\">\'.$title\n                    .\'</span><span name=\"smd_tagpar\" class=\"smd_hidden\">\'.$parent\n                    .\'</span><span name=\"smd_tagcat\" class=\"smd_hidden\">\'.((isset($rscout[$id])) ? $rscout[$id] : \'\')\n                    .\'</span><span name=\"smd_tagdsc\" class=\"smd_hidden\">\'.$description\n                    .\'</span>\';\n            $out[] = array(\n                \'data\' => $rowdata.str_repeat($indent, $level * 1) . $title . (($count) ? $cntop : \'\').n,\n                \'level\' => $level,\n                \'class\' => ($god==$name ? array(\'smd_tag_master\', \'smd_tag_parent\') : ($god==$parent ? array(\'smd_tag_master\',\'smd_tag_child\') : array())),\n                );\n//          $smd_thistag = array();\n        }\n\n        if ($out) {\n            $wrapit = do_list($wraptag, \":\");\n\n            if ($wrapit[0] == \'table\') {\n                // Tables are a special case\n                $totalItems = $rows = count($rs);\n                $step = 1;\n                $cols = 1;\n                $numopts = count($wrapit);\n                // Each successive level overrides the previous step so by the end of the ifs,\n                // 3 values are set up: step, rows and cols\n                if ($numopts > 1) {\n                    $cols = $wrapit[1];\n                    $rows = ceil($totalItems/$cols);\n                }\n\n                if ($numopts > 2) {\n                    if ($wrapit[2] == \'rows\') {\n                        $rows = $wrapit[1];\n                        $cols = ceil($totalItems/$rows);\n                    }\n                }\n\n                if ($numopts > 3) {\n                    if ($wrapit[3] == \'bycol\') {\n                        $step = ($wrapit[2]==\'cols\') ? ceil($totalItems/$cols) : $rows;\n                        if ($wrapit[2]==\'cols\') {\n                            $rows = $step;\n                        } else {\n                            $cols = ceil($totalItems/$rows);\n                        }\n                    }\n                }\n\n                // Generate table based on above rules\n                $tblout = array();\n                $cellCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n                $breakclass = (!empty($breakclass)) ? \' class=\"\'.$breakclass.\'\"\' : \'\';\n\n                for ($idx = 0; $idx < $rows; $idx++) {\n                    $bld = array();\n                    for ($jdx = 0; $jdx < $cols; $jdx++) {\n                        $offset = ($wrapit[3]==\'bycol\') ? ($jdx * $step) + $idx : $cellCtr;\n                        $bclass = (isset($out[$offset][\'class\'])) ? join(\' \', $out[$offset][\'class\']) : \'\';\n                        $bld[] = (isset($out[$offset][\'data\'])) ? tda($out[$offset][\'data\'], ($bclass ? \' class = \"\'.$bclass.\'\"\': \'\')) : td(\'\');\n                        $cellCtr++;\n                    }\n                    $tblout[] = tr(join(\'\', $bld), $breakclass);\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $tblout), $wrapit[0], $class);\n            } elseif ($wrapit[0] == \'list\') {\n                // Tags by list, optionally split into new row/col every N items\n                $totalItems = count($rs);\n                $lim = 0;\n\n                $numopts = count($wrapit);\n\n                if ($numopts > 1) {\n                    $lim = $wrapit[1];\n                }\n\n                // Generate the list\n                $listout = array();\n                $itemCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n\n                foreach ($out as $item) {\n                    $bclass = join(\' \', array_merge($item[\'class\'], array($breakclass)));\n                    $bclass = (!empty($bclass)) ? \' class=\"\'.$bclass.\'\"\' : \'\';\n                    if ($itemCtr == 0) {\n                        $listout[] = \"<ul>\";\n                    }\n                    $listout[] = \'<li\'.$bclass.\'>\'.$item[\'data\'].\'</li>\';\n                    $itemCtr++;\n                    if (($lim > 0 && $itemCtr >= $lim) || $itemCtr >= $totalItems-1) {\n                        $listout[] = \'</ul>\';\n                        $itemCtr = 0;\n                    }\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $listout), \'div\', $class);\n            } elseif ($wrapit[0] == \'listgrp\') {\n                // Tags by list, split into new row/col every time a new level \'N\' is reached\n                $totalItems = count($rs);\n                $level = 0;\n\n                $numopts = count($wrapit);\n\n                if ($numopts > 1) {\n                    $level = $wrapit[1];\n                }\n\n                if ($numopts > 3) {\n                    $class .= ($wrapit[3] == \'byrow\') ? \' byrow\' : \' bycol\';\n              }\n\n                // Generate the list\n                $listout = array();\n                $itemCtr = 0;\n                $class = (!empty($class)) ? \' class=\"\'.$class.\'\"\' : \'\';\n\n                $prevel = 0;\n                foreach ($out as $item) {\n                    $bclass = join(\' \', array_merge($item[\'class\'], array($breakclass)));\n                    $bclass = (!empty($bclass)) ? \' class=\"\'.$bclass.\'\"\' : \'\';\n                    if ($prevel >= $item[\'level\'] && $item[\'level\'] <= $level) {\n                        if ($itemCtr > 0) {\n                            $listout[] = \'</ul>\';\n                        }\n                        $listout[] = \"<ul>\";\n                    }\n                    $listout[] = \'<li\'.$bclass.\'>\'.$item[\'data\'].\'</li>\';\n                    $prevel = $item[\'level\'];\n                    if ($itemCtr >= $totalItems-1) {\n                        $listout[] = \'</ul>\';\n                    }\n                    $itemCtr++;\n                }\n\n                return doLabel($label, $labeltag).tag(join(n, $listout), \'div\', $class);\n\n            } else {\n                //TODO: only use the data column from $out\n                return doLabel($label, $labeltag).doWrap($out, $wrapit[0], $break, $class, $breakclass);\n            }\n        }\n        return \'\';\n    }\n}\n\n// -------------------------------------------------------------\n// Cloned getTree() from txplib_db.php because that function doesn\'t extract description\nfunction smd_tag_tree($root, $type, $where=\'1=1\', $tbl=\'txp_category\') {\n    $root = doSlash($root);\n    $type = doSlash($type);\n\n    $rs = safe_row(\n        \"lft as l, rgt as r\",\n        $tbl,\n        \"name=\'$root\' and type = \'$type\'\"\n    );\n\n    if (!$rs) return array();\n    extract($rs);\n\n    $out = array();\n    $right = array();\n\n    $rs = safe_rows_start(\n        \"id, name, lft, rgt, parent, title, description, desc_html\",\n        $tbl,\n        \"lft between $l and $r and type = \'$type\' and name != \'root\' and $where order by lft asc\"\n    );\n\n    while ($rs and $row = nextRow($rs)) {\n        extract($row);\n        while (count($right) > 0 && $right[count($right)-1] < $rgt) {\n            array_pop($right);\n        }\n\n        $out[] =\n            array(\n                \'id\' => $id,\n                \'name\' => $name,\n                \'title\' => $title,\n                \'level\' => count($right),\n                \'children\' => ($rgt - $lft - 1) / 2,\n                \'description\' => $description,\n                \'desc_html\' => $desc_html,\n                \'parent\' => $parent,\n            );\n\n        $right[] = $rgt;\n    }\n    return($out);\n}\n\n// ------------------------\n// Find some value in a tree and return the matching entries\nfunction smd_tag_tree_search($val, $tree, $limit=0, $field=\'name\') {\n    $ctr = 0;\n    $out = array();\n    foreach ($tree as $idx => $entry) {\n        if ($entry[$field] == $val) {\n            $out[] = $tree[$idx];\n            $ctr++;\n        }\n        if ($limit > 0 && $ctr == $limit) {\n            break;\n        }\n    }\n    return $out;\n}\n\n// ------------------------\n// PHP4 equivalent of array_combine (from http://www.php.net/manual/en/function.array-combine.php)\nif (!function_exists(\'array_combine\')) {\nfunction array_combine($arr1, $arr2) {\n    $out = array();\n\n    $arr1 = array_values($arr1);\n    $arr2 = array_values($arr2);\n\n    foreach($arr1 as $key1 => $value1) {\n    $out[(string)$value1] = $arr2[$key1];\n    }\n\n    return $out;\n}\n}\n\n// ------------------------\n// PUBLIC TAGS\n// ------------------------\n// Check tags of a particular type, or those that have a specific property in context.\nfunction smd_if_tag ($atts, $thing) {\n    global $smd_tags, $smd_tag_type, $smd_thistag;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'type\'        => NULL,\n        \'id\'          => NULL,\n        \'name\'        => NULL,\n        \'title\'       => NULL,\n        \'description\' => NULL,\n        \'parent\'      => NULL,\n        \'count\'       => NULL,\n        \'children\'    => NULL,\n        \'level\'       => NULL,\n        \'search_mode\' => NULL,\n        \'is\'          => NULL,\n    ),$atts));\n\n    // Validate atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ctxt = smd_tags_context();\n    $ctype = $ctxt[\'context\'];\n    $type = (in_array($type, $validTypes)) ? $type : ( ($ctxt[\'context\']) ? $ctxt[\'context\'] : ( ($smd_tag_type) ? $smd_tag_type :  $validTypes[0] ) );\n\n    $eqtest = array();\n    $nutest = array();\n    $searchtest = array();\n    $opRE = \'/(\\>|\\>\\=|\\<|\\<\\=|\\!)([0-9a-zA-Z- ]+)/\';\n\n    // Master tag?\n    $pref = smd_tags_pref_get(\'smd_tag_p_master\', 1);\n    $god = $pref[\'smd_tag_p_master\'][\'val\'];\n    $eqtest[\'master\'] = (($is == \'master\') && ($god != \'\')) ? $god : NULL;\n\n    // Start / end of list?\n    $eqtest[\'first\'] = ($is == \'first\') ? 1 : NULL;\n    $eqtest[\'last\'] = ($is == \'last\') ? 1 : NULL;\n\n    // Combination moade?\n    $searchtest[\'single\'] = ($search_mode == \'single\') ? 1 : NULL;\n    $searchtest[\'or\'] = ($search_mode == \'or\') ? 1 : NULL;\n    $searchtest[\'and\'] = ($search_mode == \'and\') ? 1 : NULL;\n\n    // TODO: Consolidate this into a loop for each \'id\', \'name\', type\', \'title\', ...\n    $num = preg_match_all($opRE, $id, $parts);\n    $eqtest[\'id\'] = ($num<=0) ? $id : NULL;\n    if ($num>0) $nutest[\'id\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $name, $parts);\n    $eqtest[\'name\'] = ($num<=0) ? $name : NULL;\n    if ($num>0) $nutest[\'name\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $type, $parts);\n    $eqtest[\'type\'] = ($num<=0) ? $type : NULL;\n    if ($num>0) $nutest[\'type\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $title, $parts);\n    $eqtest[\'title\'] = ($num<=0) ? $title : NULL;\n    if ($num>0) $nutest[\'title\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $description, $parts);\n    $eqtest[\'description\'] = ($num<=0) ? $description : NULL;\n    if ($num>0) $nutest[\'description\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $parent, $parts);\n    $eqtest[\'parent\'] = ($num<=0) ? $parent : NULL;\n    if ($num>0) $nutest[\'parent\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $count, $parts);\n    $eqtest[\'count\'] = ($num<=0) ? $count : NULL;\n    if ($num>0) $nutest[\'count\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $children, $parts);\n    $eqtest[\'children\'] = ($num<=0) ? $children : NULL;\n    if ($num>0) $nutest[\'children\'] = array($parts[1][0] => $parts[2][0]);\n\n    $num = preg_match_all($opRE, $level, $parts);\n    $eqtest[\'level\'] = ($num<=0) ? $level : NULL;\n    if ($num>0) $nutest[\'level\'] = array($parts[1][0] => $parts[2][0]);\n\n    trace_add(\'[smd_if_tag equality tests: \'. implode(\', \', $eqtest) . \']\');\n    trace_add(\'[smd_if_tag search_mode tests: \'. implode(\', \', $searchtest) . \']\');\n    trace_add(\'[smd_if_tag numeric tests: \'.  print_r($nutest, true) . \']\');\n\n    // Init\n    $out = $result = $numTests = 0;\n    if (!isset($smd_tags[$type]) && empty($smd_thistag)) {\n        // not in scope\n    } else {\n        // Equality comparisons\n        foreach ($eqtest as $tname => $tval) {\n            if (!is_null($tval)) {\n                $numTests++;\n\n                // Master is a special case which checks if the parent is the master tag\n                $tname = ($tname == \'master\') ? \'parent\' : $tname;\n\n                if ($smd_thistag) {\n                    // Local scope.\n                    if ($smd_thistag[\'tag_\'.$tname] == $tval) {\n                        $out++;\n                    }\n                } else {\n                    // Global scope.\n                    if ($tname == \"type\") {\n                        if ($smd_tag_type == $type || $ctype == $type) {\n                            $out++;\n                        }\n                    }\n                    if (isset($smd_tags[$type][\'tag_\'.$tname]) && in_array($tval, $smd_tags[$type][\'tag_\'.$tname], empty($tval) && $tval !== \'0\')) {\n                        $out++;\n                    }\n                }\n            }\n        }\n        // Search comparisons\n        foreach ($searchtest as $tname => $tval) {\n            if (!is_null($tval)) {\n                $numTests++;\n\n                // Global scope only for search mode.\n                if (isset($smd_tags[\'meta\'][\'search_mode\']) && $smd_tags[\'meta\'][\'search_mode\'] == $tname) {\n                    $out++;\n                }\n            }\n        }\n        // Numeric comparisons\n        foreach ($nutest as $tname => $tval) {\n            $numTests++;\n            $op = current(array_keys($tval));\n            $val = current($tval);\n            if ($smd_thistag) {\n                $comparison = $smd_thistag[\'tag_\'.$tname];\n            } else {\n                $comparison = $smd_tags[$type][\'tag_\'.$tname];\n            }\n            switch ($op) {\n                case \'>\':\n                    if (isset($comparison) && $comparison > $val) {\n                        $out++;\n                    }\n                    break;\n                case \'>=\':\n                    if (isset($comparison) && $comparison >= $val) {\n                        $out++;\n                    }\n                    break;\n                case \'<\':\n                    if (isset($comparison) && $comparison < $val) {\n                        $out++;\n                    }\n                    break;\n                case \'<=\':\n                    if (isset($comparison) && $comparison <= $val) {\n                        $out++;\n                    }\n                    break;\n                case \'!\':\n                    if (isset($comparison) && $comparison != $val) {\n                        $out++;\n                    }\n                    break;\n            }\n        }\n\n        trace_add(\'[smd_if_tag num tests | results: \' . $numTests . \'|\' . $out . \']\');\n\n        // Count how many successes there were\n        if ($numTests == $out) {\n            $result = 1;\n        }\n    }\n\n    return parse(EvalElse($thing, $result));\n}\n\n// ------------------------\nfunction smd_if_tag_list ($atts, $thing)\n{\n    global $smd_tag_type;\n    return parse(EvalElse($thing, (!empty($smd_tag_type))));\n}\n\n// ------------------------\n// Return name/title of current tag\nfunction smd_tag_name($atts = array(), $thing = null)\n{\n    global $smd_thistag, $permlink_mode, $plugins;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'title\'       => 1,\n        \'link\'        => \'\',\n        \'section\'     => \'\',\n        \'cleanurls\'   => 1,\n        \'parent\'      => 0, // not useful to print but good for URLs to nav back up the tree\n        \'parentlabel\' => \'Up a level\',\n        \'wraptag\'     => \'\',\n        \'class\'       => __FUNCTION__,\n        \'style\'       => \'\',\n        \'pad_str\'     => \'\',\n        \'pad_pos\'     => \'left\', // left/right/both with optional :in suffix to indicate if it\'s to go inside the link\n    ), $atts));\n\n    $smdpref = smd_tags_pref_get(array(\'smd_tag_u_sec\', \'smd_tag_u_pnam\', \'smd_tag_u_ptyp\'), 1);\n    $urlsec = do_list($smdpref[\'smd_tag_u_sec\'][\'val\']);\n    $section = ($section) ? $section : $urlsec[0];\n    $in_default = (in_array($section, $urlsec));\n    $urlnam = $smdpref[\'smd_tag_u_pnam\'][\'val\'];\n    $urltyp = $smdpref[\'smd_tag_u_ptyp\'][\'val\'];\n\n    // gbp_permanent_links sets messy mode behind the scenes but still uses non-messy URLs\n    // so it requires an exception\n    $gbp_pl = (is_array($plugins) && in_array(\'gbp_permanent_links\', $plugins));\n    $messy = ($permlink_mode == \'messy\') && (!$gbp_pl);\n\n    $parentlabel = ($parentlabel) ? $parentlabel : (($parent) ? $smd_thistag[\'tag_parent\'] : $smd_thistag[\'tag_name\']);\n    $label = ($parent) ? $parentlabel : (($title) ? $smd_thistag[\'tag_title\'] : $smd_thistag[\'tag_name\']);\n    $tname = ($parent) ? $smd_thistag[\'tag_parent\'] : $smd_thistag[\'tag_name\'];\n    $level = $smd_thistag[\'tag_level\'];\n    $style = ($style) ? \' style=\"\'.$style.\'\"\' : \'\';\n    $padding = ($pad_str) ? str_repeat($pad_str, $level) : \'\';\n    $pad_pos = do_list($pad_pos, \':\');\n    $pad[\'lin\'] = ($padding && (in_array(\'left\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && in_array(\'in\', $pad_pos) !== false) ? $padding : \'\';\n    $pad[\'rin\'] = ($padding && (in_array(\'right\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && in_array(\'in\', $pad_pos) !== false) ? $padding : \'\';\n    $pad[\'l\'] = ($padding && (in_array(\'left\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && (empty($pad[\'lin\']) || !$link)) ? $padding : \'\';\n    $pad[\'r\'] = ($padding && (in_array(\'right\', $pad_pos) !== false || in_array(\'both\', $pad_pos) !== false) && (empty($pad[\'rin\']) || !$link)) ? $padding : \'\';\n    $dest = ($messy || !$cleanurls)\n                ? pagelinkurl(array(\'s\' => $section, $urlnam => $tname, $urltyp => $smd_thistag[\'tag_type\']))\n                : hu.$section.\'/\'.(($in_default) ? \'\' : $urlsec[0].\'/\').$smd_thistag[\'tag_type\'].\'/\'.$tname;\n    if ($thing) {\n        $out = \'<a\'.\n            ($messy ? \'\' : \' rel=\"tag\"\').\n            ( ($class and !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\' ).\n            \' href=\"\'.$dest.\'\"\'.\n            ($title ? \' title=\"\'.$label.\'\"\' : \'\').\n            \'>\'.parse($thing).\'</a>\';\n    } elseif ($link) {\n        $out = $pad[\'l\'].\'<a\'.\n            ($messy ? \'\' : \' rel=\"tag\"\').\n            \' href=\"\'.$dest.\'\">\'.$pad[\'lin\'].$label.$pad[\'rin\'].\'</a>\'.$pad[\'r\'];\n    } else {\n        $out = $pad[\'l\'].$label.$pad[\'r\'];\n    }\n\n    return doTag($out, $wraptag, $class, $style);\n}\n\n// ------------------------\n// Return # of items associated with this tag\n//TODO: think about per-section / per-category counts\nfunction smd_tag_count($atts = array(), $thing = null)\n{\n    global $smd_thistag, $smd_tags;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'class\'      => __FUNCTION__,\n        \'style\'      => \'\',\n        \'wraptag\'    => \'\',\n        \'wrapcount\'  => \' (:)\',\n        \'showempty\'  => \'1\',\n        \'paramdelim\' => \':\',\n    ), $atts));\n\n    $wrapcount = explode($paramdelim, $wrapcount); // do_list does a trim: don\'t want that\n    $style = ($style) ? \' style=\"\'.$style.\'\"\' : \'\';\n\n    if (count($wrapcount) == 1) {\n        $wrapcount[1] = $wrapcount[0];\n    }\n\n    if ($smd_thistag) {\n        $out = $smd_thistag[\'tag_count\'];\n    } else {\n        $out = isset($smd_tags[\'meta\'][\'content_count\']) ? $smd_tags[\'meta\'][\'content_count\'] : \'\';\n    }\n\n    $out = ($out == 0 && !$showempty) ? \'\' : $wrapcount[0].$out.$wrapcount[1];\n\n    return doTag($out, $wraptag, $class, $style);\n}\n\n// ------------------------\n// Return other info about the current tag\nfunction smd_tag_info($atts = array(), $thing = null)\n{\n    global $smd_thistag;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'item\'       => \'name\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'br\',\n        \'class\'      => __FUNCTION__,\n        \'breakclass\' => \'\',\n    ), $atts));\n\n    $out = array();\n    $items = do_list($item);\n    $availableItems = array(\n        \'id\',\n        \'name\',\n        \'title\',\n        \'description\',\n        \'lettername\',\n        \'lettertitle\',\n        \'type\',\n        \'parent\',\n        \'children\',\n        \'level\',\n        \'count\',\n        \'indent\',\n        \'weight\',\n    );\n\n    foreach ($items as $whatnot) {\n        if (in_array($whatnot, $availableItems)) {\n            $out[] = $smd_thistag[\'tag_\'.$whatnot];\n        }\n    }\n\n    return doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\n// ------------------------\n// Related articles/images/files/links by tag\nfunction smd_related_tags($atts = array(), $thing = null)\n{\n    global $thisarticle, $thisfile, $thislink, $thisimage, $pretext, $prefs, $smd_tags, $smd_thistag;\n\n    static $smd_tags_pc;\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'type\'       => \'\',\n        \'section\'    => $pretext[\'s\'],\n        \'status\'     => \'4\',\n        \'limit\'      => 99999,\n        \'offset\'     => 0,\n        \'form\'       => \'\',\n        \'match\'      => \'tag_name\',\n        \'match_self\' => 0,\n        \'no_widow\'   => @$prefs[\'title_no_widow\'],\n        \'sort\'       => \'\',\n        \'label\'      => \'\',\n        \'labeltag\'   => \'\',\n        \'wraptag\'    => \'\',\n        \'break\'      => \'br\',\n        \'class\'      => __FUNCTION__,\n        \'delim\'      => \',\',\n        \'paramdelim\' => \':\',\n        \'debug\'      => \'0\',\n    ), $atts));\n\n    // Validate atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ctxt = smd_tags_context();\n\n    $scope = $ctxt[\'scope\'];\n    $idlist = $ctxt[\'id\'];\n    $ctype = $ctxt[\'context\'];\n    $type = (in_array($type, $validTypes)) ? $type : (($ctxt[\'context\']) ? $ctxt[\'context\'] : $validTypes[0]);\n\n    if (!isset($smd_tags_pc[$type])) {\n        $smd_tags_pc[$type] = array(\n            \'all\' => array(),\n            \'parlist\' => array(),\n            \'chilist\' => array(),\n        );\n    }\n\n    $sectionClause = ($section) ? \" AND txp.Section IN (\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\" : \'\';\n    $status = do_list($status);\n    $stati = array();\n\n    foreach ($status as $stat) {\n        if (empty($stat)) {\n            continue;\n        } elseif (is_numeric($stat)) {\n            $stati[] = $stat;\n        } else {\n            $stati[] = getStatusNum($stat);\n        }\n    }\n\n    $statSQL = \' AND txp.Status IN (\'.join(\',\', $stati).\')\';\n\n    $out = array();\n\n    if (!$sort) {\n        switch ($type) {\n            case \"article\":\n                $sort = \"Posted desc\";\n                break;\n            case \"image\":\n\n            case \"link\":\n                $sort = \"date desc\";\n                break;\n            case \"file\":\n                $sort = \"created desc\";\n                break;\n        }\n    }\n\n    $cfs = getCustomfields();\n    $cfKeys = implode(\',\', array_map(\n        function($k) { return \'custom_\' . $k; }, array_keys($cfs)\n    ));\n\n    // Lookup table for making SQL queries\n    $sqlStubs = array(\n        \"article\" => array(\n            \"select\" => \"txp.ID, Posted, Expires, AuthorID, LastMod, LastModID, txp.Title, Title_html, Body, Body_html, Excerpt, Excerpt_html, Image, Category1, Category2, Annotate, AnnotateInvite, comments_count, Status, textile_body, textile_excerpt, Section, override_form, Keywords, txp.description, url_title\" . ($cfKeys ? \',\'. $cfKeys : \'\') . \", uid, feed_time, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"textpattern\",\n            \"gtags\" => $thisarticle,\n            \"gid\" => \"thisid\",\n        ),\n        \"image\" => array(\n            \"select\" => \"txp.id, txp.name, txp.category, txp.ext, txp.w, txp.h, txp.alt, txp.caption, txp.date, txp.author, txp.thumb_w, txp.thumb_h, txp.thumbnail, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_image\",\n            \"gtags\" => $thisimage,\n            \"gid\" => \"id\",\n        ),\n        \"file\" => array(\n            \"select\" => \"txp.id, txp.filename, txp.title, txp.category, txp.permissions, txp.description, txp.downloads, txp.status, txp.modified, txp.created, txp.size, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_file\",\n            \"gtags\" => $thisfile,\n            \"gid\" => \"id\",\n        ),\n        \"link\" => array(\n            \"select\" => \"txp.id, txp.date, txp.category, txp.url, txp.linkname, txp.linksort, txp.description, tu.item_id, tu.tag_id, smt.id AS smtid, smt.name AS smtname, smt.desc_html AS smtdescription, smt.type, smt.parent, smt.lft, smt.rgt, smt.title as tag_title, COUNT(smt.name) as tag_sum\",\n            \"table\" => \"txp_link\",\n            \"gtags\" => $thislink,\n            \"gid\" => \"id\",\n        ),\n    );\n\n    // Extract stuff to match & make up query replacement variables\n    $match = do_list($match, $paramdelim);\n    $matchType = array_shift($match);\n\n    if (count($match) == 0) {\n        // Assume current type unless a tag is being matched (in which case, use $ctype)\n        $matchWith = array($matchType);\n        $matchType = (strpos($matchWith[0], \'tag_\') !== false) ? $ctype : $type;\n    } else {\n        $matchWith = $match;\n    }\n\n    $matches = array();\n\n    foreach ($matchWith as $matchItem) {\n        if (strpos($matchItem, \'tag_\') !== false) {\n            $lookin = (isset($smd_tags[$matchType])) ? $smd_tags[$matchType] : ( (isset($smd_thistag)) ? $smd_thistag : array() );\n        } else {\n            $lookin = $sqlStubs[$matchType][\"gtags\"];\n        }\n\n        if (isset($lookin[$matchItem])) {\n            $thismatch = $lookin[$matchItem];\n\n            if (is_array($thismatch)) {\n                foreach ($thismatch as $subID => $subItem) {\n                    if (in_array($subID, $idlist)) {\n                        $matches[] = $subItem;\n                    }\n                }\n            } else {\n                $matches[] = $thismatch;\n            }\n        }\n    }\n\n    $matches = array_unique($matches);\n\n    // Should the tag hierarchy be taken into account?\n    $opts = smd_tags_pref_get(array(\'smd_tag_p_lbi\', \'smd_tag_u_combi\'));\n    $bidir = $opts[\'smd_tag_p_lbi\'][\'val\'];\n    $combi = $opts[\'smd_tag_u_combi\'][\'val\'];\n\n    if ($matches) {\n        $parents = $children = array();\n\n        if ($smd_tags_pc[$type][\'all\']) {\n            $full_tag_tree = $smd_tags_pc[$type][\'all\'];\n        } else {\n            $full_tag_tree = $smd_tags_pc[$type][\'all\'] = smd_tag_tree(\'root\', $type, \'1=1\', SMD_TAG);\n        }\n\n        // Build two lists from the full tag tree:\n        //  1) parlist: each node\'s parent\n        //  2) chilist: list of nodes below the current\n        if (!$smd_tags_pc[$type][\'parlist\'] || !$smd_tags_pc[$type][\'chilist\']) {\n            foreach ($full_tag_tree as $node) {\n                $smd_tags_pc[$type][\'parlist\'][$node[\'name\']] = $node[\'parent\'];\n\n                if ($node[\'parent\'] == \'root\') {\n                    $smd_tags_pc[$type][\'chilist\'][$node[\'name\']] = array();\n                } else {\n                    $smd_tags_pc[$type][\'chilist\'][$node[\'parent\']][] = $node[\'name\'];\n\n                    foreach ($smd_tags_pc[$type][\'chilist\'] as $key => $nodes) {\n                        if (in_array($node[\'parent\'], $nodes)) {\n                            $smd_tags_pc[$type][\'chilist\'][$key][] = $node[\'name\'];\n                        }\n                    }\n                }\n            }\n        }\n\n        // Reverse lookup to find parents of child\n        if ($bidir == 1 || $bidir == 3) {\n            foreach ($matches as $matchit) {\n                $theparent = $matchit;\n\n                while (array_key_exists($theparent, $smd_tags_pc[$type][\'parlist\'])) {\n                    if ($smd_tags_pc[$type][\'parlist\'][$theparent] == \'root\') {\n                        break;\n                    }\n                    $parents[] = $theparent = $smd_tags_pc[$type][\'parlist\'][$theparent];\n                }\n            }\n        }\n\n        // Forward lookup to find children of parent\n        if ($bidir == 2 || $bidir == 3) {\n            foreach ($matches as $matchit) {\n                if (array_key_exists($matchit, $smd_tags_pc[$type][\'chilist\'])) {\n                    $children = array_merge($children, $smd_tags_pc[$type][\'chilist\'][$matchit]);\n                }\n            }\n        }\n\n        $matches = array_unique(array_merge( $matches, $parents, $children ));\n    }\n\n    trace_add(\'[smd_related_tags matches: \' . join(\', \', $matches) . \']\');\n\n    // Convert above opts to SQL query clauses\n    $excludeClause = ($match_self) ? \'\' : \' AND txp.id != \"\'.$sqlStubs[$type][\"gtags\"][$sqlStubs[$type][\"gid\"]].\'\"\';\n\n    $and_mode = (isset($smd_tags[\'meta\'][\'search_mode\']) && ($smd_tags[\'meta\'][\'search_mode\'] == \'and\')) ? true : false;\n    $and_modeClause = ($and_mode && $matches) ? \' HAVING COUNT(*) = \' . count($matches) : \'\';\n\n    $last_tag = (isset($smd_tags[\'meta\'][\'tag_tail\'])) ? $smd_tags[\'meta\'][\'tag_tail\'] : \'\';\n    $matchClause = \" AND \" .(($matches) ? ($combi ? \"smt.name IN (\" .join(\",\", quote_list($matches)). \")\" : \"smt.name = \'\".$last_tag.\"\'\") : \"smt.name = \'\'\");\n    $orderBy = \" ORDER BY \" .$sort. \" LIMIT \" .$offset. \",\" .$limit;\n    $contentCount = 0;\n\n    switch ($type) {\n        case \"article\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'article\'\" . $statSQL . $excludeClause . $sectionClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags article records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'ID\'], $uniqrs)) {\n                        $safe = ($thisarticle) ? $thisarticle : NULL;\n                        populateArticleData($row);\n                        $row[\'Title\'] = ($no_widow) ? noWidow(escape_title($row[\'Title\'])) : escape_title($row[\'Title\']);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row[\'Title\'], permlinkurl($row)));\n                        $uniqrs[] = $row[\'ID\'];\n                        $thisarticle = $safe;\n                    }\n                }\n            }\n            break;\n        case \"image\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'image\'\" . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags image records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thisimage) ? $thisimage : NULL;\n                        $thisimage = image_format_info($row);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : image(array(\'id\' => $row[\'id\'])));\n                        $uniqrs[] = $row[\'id\'];\n                        $thisimage = $safe;\n                    }\n                }\n            }\n            break;\n        case \"file\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'file\'\" . $statSQL . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags file records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thisfile) ? $thisfile : NULL;\n                        $thisfile = file_download_format_info($row);\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : file_download_link(array(\'filename\' => $row[\'filename\']), $row[\'filename\']));\n                        $uniqrs[] = $row[\'id\'];\n                        $thisfile = $safe;\n                    }\n                }\n            }\n            break;\n        case \"link\":\n            $rs = getRows(\"SELECT SQL_CALC_FOUND_ROWS \" . $sqlStubs[$type][\"select\"] . \" FROM \" . safe_pfx($sqlStubs[$type][\"table\"]) . \" AS txp\n                LEFT JOIN \".PFX.SMD_TAGU. \" AS tu ON txp.id = tu.item_id\n                LEFT JOIN \".PFX.SMD_TAG. \" AS smt ON tu.tag_id = smt.id\n                WHERE tu.type = \'link\'\" . $excludeClause . $matchClause . \' GROUP BY txp.id\' . $and_modeClause . $orderBy, $debug);\n            $contentResult = mysqli_fetch_assoc(safe_query(\'SELECT FOUND_ROWS() AS found\', $debug));\n            $contentCount = $contentResult[\'found\'];\n\n            if ($rs) {\n                trace_add(\'[smd_related_tags link records: \' . print_r($rs, true) . \']\');\n                $uniqrs = array();\n\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $uniqrs)) {\n                        $safe = ($thislink) ? $thislink : NULL;\n                        $thislink = array(\n                            \'id\'          => $row[\'id\'],\n                            \'linkname\'    => $row[\'linkname\'],\n                            \'url\'         => $row[\'url\'],\n                            \'description\' => $row[\'description\'],\n                            \'date\'        => $row[\'date\'],\n                            \'category\'    => $row[\'category\'],\n                        );\n                        $out[] = ($form) ? parse_form($form) : (($thing) ? parse($thing) : href($row[\'linkname\'], $row[\'url\']));\n                        $uniqrs[] = $row[\'id\'];\n                        $thislink = $safe;\n                    }\n                }\n            }\n            break;\n    }\n\n    $smd_tags[\'meta\'][\'content_count\'] = $contentCount;\n\n    if ($out) {\n        return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n    }\n\n    return \'\';\n}\n\n// ------------------------\n// List tags from current context, or given type\nfunction smd_tag_list($atts = array(), $thing = null)\n{\n    global $smd_tags, $smd_thistag;\n    static $smd_tags_tree = array();\n    static $smd_tags_count = array();\n\n    if (!smd_tags_table_exist()) {\n        trigger_error(gTxt(\'smd_tag_not_available\'));\n        return;\n    }\n\n    extract(lAtts(array(\n        \'flavour\'      => \'list\', // 1) list: only tags with assignments, 2) cloud (adds weighting to smd_thistag), 3) head (first tag in list), 4) tail (last tag in list)\n        \'showall\'      => \'0\', // 0: just used, 1: include empties\n        \'type\'         => \'\',\n        \'id\'           => \'\',\n        \'name\'         => \'\',\n        \'exclude\'      => \'\',\n        \'parent\'       => \'\', // Start list from here\n        \'sublevel\'     => \'\', // Get tags from this sub-level only (0=top level, 1-level 1, ..., all=everything)\n        \'auto_detect\'  => \'1\',\n        \'offset\'       => 0,\n        \'limit\'        => 999999,\n        \'form\'         => \'\',\n        \'indent\'       => \'&#160;&#160;\',\n        \'section_link\' => \'\',\n        \'sort\'         => \'\',\n        \'shuffle\'      => 0,\n        \'label\'        => \'\',\n        \'labeltag\'     => \'\',\n        \'wraptag\'      => \'ul\',\n        \'break\'        => \'li\',\n        \'class\'        => __FUNCTION__,\n        \'active_class\' => \'\', // TODO\n        \'breakclass\'   => \'\',\n    ), $atts));\n\n    // Validate client side atts\n    $validTypes = array(\'article\',\'image\',\'file\',\'link\');\n    $ids = \'\';\n    $where = array();\n    $ctxt = smd_tags_context();\n\n    $attemptMatch = (empty($name) && empty($id) && empty($exclude) && !empty($ctxt[\'id\'])) ? false : true;\n    $type = (in_array($type, $validTypes)) ? $type : (($ctxt[\'context\']) ? $ctxt[\'context\'] : $validTypes[0]);\n    $ids = ($id) ? do_list($id) : (($auto_detect && $ctxt[\'id\']) ? $ctxt[\'id\'] : \'\');\n\n    // Unique identifiers for cacheing the lists\n    $gopts = $name.$id.$exclude.$parent.$sublevel;\n    $guid = md5($gopts);\n\n    if ($name) {\n        $name = \"name IN (\" .join(\',\', quote_list(do_list($name))). \")\";\n        $where[] = $name;\n        $ids = \'\'; // name trumps id; TODO: maybe offer a way of combining them?\n    }\n\n    if ($ids) {\n        $ids = \"id IN (\" .join(\',\', quote_list($ids)). \")\";\n        $where[] = $ids;\n    }\n\n    if ($exclude) {\n        $exclude = \"name NOT IN (\" .join(\',\', quote_list(do_list($exclude))). \")\";\n        $where[] = $exclude;\n    }\n\n    if (strtolower($parent) == \'smd_auto\') {\n        $parent = \'\';\n        $plist = (isset($smd_tags[$type])) ? $smd_tags[$type][\'tag_name\'] : \'\';\n        if (is_array($plist)) {\n            foreach ($plist as $idx => $pname) {\n                $parent = $pname;\n                break;\n            }\n        }\n    }\n\n    $where = join(\' AND \', $where);\n    $oper = \'\';\n\n    if (!$where && isset($smd_tags[$type])) {\n        // Use URL tags\n        if ($showall) {\n            if (isset($smd_tags_tree[$type][$guid][\'all\'])) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'];\n            } else {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'] = smd_tag_tree((($parent) ? $parent : \'root\'), $type, \'1=1\', SMD_TAG);\n            }\n        } else {\n            // Was just the parent attribute supplied?\n            if ($parent) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'] = smd_tag_tree($parent, $type, \'1=1\', SMD_TAG);\n            } else {\n                $rs = $smd_tags[$type];\n            }\n        }\n    } else {\n        if (!$where && $attemptMatch && !$showall) {\n            $where = \'\';\n            $rs = array();\n        } else {\n            $where = ($where) ? $where : \'1=1\';\n        }\n\n        if ($where) {\n            trace_add(\'[smd_tag_list query: \' . $where . \']\');\n        }\n\n        if ($where) {\n            if ( ($showall || $where == \'1=1\') && isset($smd_tags_tree[$type][$guid][\'all\']) ) {\n                $rs = $smd_tags_tree[$type][$guid][\'all\'];\n            } else {\n                $windex = md5($gopts.$where);\n\n                if (isset($smd_tags_tree[$type][$windex])) {\n                    $rs = $smd_tags_tree[$type][$windex];\n                } else {\n                    $rs = $smd_tags_tree[$type][$windex] = smd_tag_tree((($parent) ? $parent : \'root\'), $type, (($showall) ? \'1=1\' : $where), SMD_TAG);\n                }\n            }\n        }\n\n        $sublevel = ($parent && $sublevel==\'\') ? ((smd_tag_tree_search($parent, $rs, 1)) ? \'>=1\' : \'>=0\') : $sublevel;\n        preg_match(\'/([=<>]+)?([\\d]+|all)/\', $sublevel, $matches);\n\n        if ($matches) {\n            $oper = ($matches[1] && in_list($matches[1], \'>, <, >=, <=\')) ? $matches[1] : \'==\';\n            $sublevel=$matches[2];\n        } else {\n            $oper = \'==\';\n        }\n\n        trace_add(\'[smd_tag_list sublevel | operator | matches: \' . $sublevel . \'|\' . $oper . \'|\' . join(\', \', $matches) . \']\');\n    }\n\n    trace_add(\'[smd_tag_list tree: \' . print_r($rs, true) . \']\');\n\n    if ($rs) {\n        $out = array();\n        $totals = array();\n        $outsub = array();\n\n        foreach ($rs as $key => $row) {\n            // Only add the row if one of:\n            // 1) showall is on\n            // 2) sublevel = \'all\'\n            // 3) no parent specified\n            // 4) sublevel is equal/greater/less than the row\'s level\n            if ($showall == 1 || strtolower($sublevel) == \"all\" || !$parent || ($oper==\'==\'? $row[\'level\'] == $sublevel : (($oper==\'>=\') ? $row[\'level\'] >= $sublevel : (($oper==\'<=\') ? $row[\'level\'] <= $sublevel : (($oper==\'>\') ? $row[\'level\'] > $sublevel : $row[\'level\'] < $sublevel) ) ) ) ) {\n                $outsub[] = $row;\n            }\n        }\n\n        $rs = $outsub;\n        trace_add(\'[smd_tag_list sublevel records: \' . print_r($rs, true) . \']\');\n\n        if ($shuffle) {\n            shuffle($rs);\n        } elseif ($sort) {\n            $sortPrefix = \"SORT_\";\n            $sortOrder = array();\n            $sort = do_list($sort);\n            $nsrt = count($sort);\n\n            for ($idx = 0; $idx < $nsrt; $idx++) {\n                $sort_dir = explode(\' \', $sort[$idx]);\n\n                if (count($sort_dir) <= 1) {\n                    $sort_dir[1] = \"asc\";\n                }\n\n                $direction = ($sort_dir[1] == \"desc\") ? $sortPrefix.\'DESC\' : $sortPrefix.\'ASC\';\n                $sortOrder[] = array(\"col\" => $sort_dir[0], \"sort\" => $direction);\n            }\n\n            // Translate the rows into columns that can be sorted\n            foreach ($rs as $key => $row) {\n                foreach ($row as $identifier => $item) {\n                    $varname = \"col_\".$identifier;\n                    ${$varname}[$key] = $item;\n                }\n            }\n\n            // Make up an array_multisort arg list and execute it.\n            // The necessary evil() is because we don\'t know how many cols there will be in the array\n            for ($idx = 0; $idx < count($sortOrder); $idx++) {\n                $sortargs[] = \'$col_\'.$sortOrder[$idx][\'col\'];\n                $sortargs[] = $sortOrder[$idx][\'sort\'];\n            }\n\n            $sortit = \'array_multisort(\'.join(\", \",$sortargs).\', $rs);\';\n            eval($sortit);\n        }\n\n        $rs = array_slice($rs, $offset, $limit);\n        trace_add(\'[smd_tag_list post-filter records: \' . print_r($rs, true) . \']\');\n        $numrecs = count($rs);\n\n        // Cache the relevant info from the tag usage table\n        if (!isset($smd_tags_count[$type])) {\n            $taguse = safe_rows(\'tag_id, count(*) AS tally\', SMD_TAGU, \"type=\'\" . doSlash($type) . \"\' GROUP BY tag_id\");\n\n            foreach ($taguse as $tagblob) {\n                $smd_tags_count[$type][$tagblob[\'tag_id\']] = $tagblob[\'tally\'];\n            }\n        }\n\n        // Set up the cloud weighting if desired\n        $flavour = do_list($flavour, \':\');\n\n        if ($flavour[0] == \'cloud\') {\n            $minPercent = (isset($flavour[1]) && !empty($flavour[1])) ? $flavour[1] : 100;\n            $maxPercent = (isset($flavour[2]) && !empty($flavour[2])) ? $flavour[2] : 200;\n            $max = 1; $min = 99999; $count = 0;\n\n            foreach ($rs as $row) {\n                $count = (isset($smd_tags_count[$type][$row[\'id\']])) ? $smd_tags_count[$type][$row[\'id\']] : 0;\n                $max = ($count > $max ? $count : $max);\n                $min = ($min > $count ? $count : $min);\n            }\n\n            $multiplier = ($max > $min) ? ($maxPercent - $minPercent) / ($max - $min) : 1;\n        }\n\n        $tag_head = (isset($smd_tags[\'meta\'][\'tag_head\'])) ? $smd_tags[\'meta\'][\'tag_head\'] : \'\';\n        $tag_tail = (isset($smd_tags[\'meta\'][\'tag_tail\'])) ? $smd_tags[\'meta\'][\'tag_tail\'] : \'\';\n        $tag_list = (isset($smd_tags[\'meta\'][\'tag_list\'])) ? $smd_tags[\'meta\'][\'tag_list\'] : array();\n\n        // Preserve the current tag so lists in lists can be created\n        $old_tag = $smd_thistag;\n\n        // Iterate over the tags, populate the local context (global!) variable and parse through the form/container\n        foreach ($rs as $idx => $row) {\n            $index = $idx; // Because we can muck about with this value without affecting the loop counter\n\n            if (($flavour[0] == \'head\') && ($row[\'name\'] != $tag_head)) {\n                continue;\n            }\n\n            if (($flavour[0] == \'tail\') && ($row[\'name\'] != $tag_tail)) {\n                continue;\n            }\n\n            $row[\'type\'] = $type;\n            $row[\'count\'] = (isset($smd_tags_count[$type][$row[\'id\']])) ? $smd_tags_count[$type][$row[\'id\']] : 0;\n            $row[\'indent\'] = str_repeat($indent, $row[\'level\'] * 1);\n            $row[\'weight\'] = ($flavour[0] == \'cloud\') ? floor($minPercent + (($max - ( $max - ($row[\'count\'] - $min))) * $multiplier)) : 1;\n\n            if (($flavour[0] == \'crumb\') && (($pos = array_search($row[\'name\'], $tag_list)) !== false)) {\n                // Override the position of this entry in the list to fake a crumbtrail\n                $index = $pos;\n            }\n\n            $row[\'first\'] = ($index == 0) ? 1 : 0;\n            $row[\'last\'] = ($index == ($numrecs-1)) ? 1 : 0;\n\n            smd_tag_populate($row);\n            $out[$index] = ($thing) ? parse($thing) : (($form) ? parse_form($form) :\n                    str_repeat($indent, $row[\'level\'] * 1)\n                    .smd_tag_name(array(\'title\' => 1, \'section\' => $section_link))\n                    .smd_tag_count(array()));\n            $smd_thistag = array();\n        }\n\n        // In case the order has been fudged for crumbtrail\n        ksort($out);\n\n        // Restore previous tag, if any\n        $smd_thistag = $old_tag;\n\n        if ($out) {\n            return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class, $breakclass);\n        }\n    }\n\n    return \'\';\n}\n\n/*\n\nregister_callback(\'tru_tags_atom_handler\', \'atom_entry\');\nfunction tru_tags_atom_handler($event, $step) { return tru_tags_feed_handler(true); }\nregister_callback(\'tru_tags_rss_handler\', \'rss_entry\');\nfunction tru_tags_rss_handler($event, $step) { return tru_tags_feed_handler(false); }\n\nfunction tru_tags_feed_handler($atom) {\n    global $thisarticle, $tru_tags_prefs;\n    extract($thisarticle);\n\n    $tags = tru_tags_get_tags_for_article($thisid);\n\n    if ($tru_tags_prefs[TAGS_IN_FEED_BODY]->value) {\n        $extrabody = \'\';\n        $FORM_NAME = \'tru_tags_feed_tags\';\n        if (fetch(\'form\', \'txp_form\', \'name\', $FORM_NAME)) {\n            $form = fetch_form($FORM_NAME);\n            $extrabody = trim(parse($form));\n        } else {\n            $atts = tru_tags_get_standard_cloud_atts(array(), false, true);\n            $extrabody = \'<p>tags: \' . tru_tags_render_cloud($atts, $tags, $tags) . \'</p>\';\n        }\n        global $thisarticle;\n        if (trim($thisarticle[\'excerpt\'])) {\n            $thisarticle[\'excerpt\'] = trim($thisarticle[\'excerpt\']).n.$extrabody.n;\n        }\n        $thisarticle[\'body\'] = trim($thisarticle[\'body\']).n.$extrabody.n;\n    }\n\n    if ($tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value) {\n        $output = array();\n        foreach ($tags as $tag) {\n            if ($atom) {\n                $output[] = \'<category term=\"\' . htmlspecialchars($tag) . \'\" />\';\n            } else {\n                $output[] = \'<category>\' . htmlspecialchars($tag) . \'</category>\';\n            }\n        }\n        return n.join(n, $output).n;\n    }\n}\n\n*/', 'd2be88d904ce15eb67ef90b1e78ae54f', '#@smd_tag\nsmd_tags => Tags (smd)\nsmd_tag_ac_std => Standard\nsmd_tag_ac_str => Strict\nsmd_tag_all_lbl => All\nsmd_tag_already_exists => already exists\nsmd_tag_assign_parent_lbl => Assign parent\nsmd_tag_bi_both => Both\nsmd_tag_bi_down => Down\nsmd_tag_bi_up => Up\nsmd_tag_by_column => Column\nsmd_tag_by_row => Row\nsmd_tag_category_linked_lbl => Linked to category \"{category}\":\nsmd_tag_category_unlinked_lbl => Unlinked:\nsmd_tag_children_and => and its children\nsmd_tag_children_del => Delete children\nsmd_tag_children_pro => Promote children\nsmd_tag_clear => clr\nsmd_tag_clink_lbl => Linked cat\nsmd_tag_count_lbl => Matched tags:\nsmd_tag_created => {type} tag \"{name}\" created\nsmd_tag_created_rep_lbl => Created tags:\nsmd_tag_create_lbl => Create/Edit tag\nsmd_tag_deleted => {type} tag \"{name}\" deleted\nsmd_tag_deleted_lbl => Deleted tags:\nsmd_tag_err_in_use_lbl => Tags in use:\nsmd_tag_err_orphaned_lbl => Orphaned tags:\nsmd_tag_exists => {type} tag \"{name}\" already exists\nsmd_tag_extras => More...\nsmd_tag_import_results_pt1 => Articles:\nsmd_tag_import_results_pt2 => Tags linked:\nsmd_tag_in_use => {type} tag \"{name}\" is in use\nsmd_tag_link_to_cat_lbl => Link to category\nsmd_tag_manage_lbl => Manage tags\nsmd_tag_missing_rep_lbl => Missing tags:\nsmd_tag_multi_delete => Tags deleted: {number} (see report for details)\nsmd_tag_not_available => Tables are not installed\nsmd_tag_not_created => {type} tag \"{name}\" NOT created\nsmd_tag_not_created_rep_lbl => Tags NOT created:\nsmd_tag_not_parent_linked_lbl => Not linked:\nsmd_tag_not_updated => {type} tag \"{name}\" NOT updated\nsmd_tag_no_desc => No description available\nsmd_tag_no_name => {type} tag NOT created: needs a name\nsmd_tag_no_parent => No parent tags.\nsmd_tag_no_sections => No sections\nsmd_tag_no_tags => No {type} tags defined\nsmd_tag_parent_lbl => Parent\nsmd_tag_parent_linked_lbl => Assigned these tag(s) to \"{parent}\":\nsmd_tag_pool_lbl => Tag pool\nsmd_tag_prefs_installed => Preferences installed\nsmd_tag_prefs_not_installed => Preferences not installed.\nsmd_tag_prefs_p => Interface settings\nsmd_tag_prefs_pane => Prefs\nsmd_tag_prefs_removed => Preferences removed\nsmd_tag_prefs_some => Not all preferences available.\nsmd_tag_prefs_some_explain => This is either a new installation or a different version<br />of the plugin to one you had before.\nsmd_tag_prefs_some_opts1 => Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.\nsmd_tag_prefs_some_opts2 => Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.\nsmd_tag_prefs_some_tbl => Not all table info available.\nsmd_tag_prefs_t => Tag management\nsmd_tag_prefs_title => Tag Preferences\nsmd_tag_prefs_u => URL management\nsmd_tag_pref_install_lbl => Install prefs\nsmd_tag_pref_remove_lbl => Remove prefs\nsmd_tag_pref_show_lbl => Preferences\nsmd_tag_p_enable => Enable tags in\nsmd_tag_p_input => Enter tags using\nsmd_tag_p_lbi => Bi-directional tag trees\nsmd_tag_p_linkcat => Link tags to categories\nsmd_tag_p_listpar => Permit parent tag selection\nsmd_tag_p_master => Master parent tag\nsmd_tag_p_qtag => Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)\nsmd_tag_p_qtpath => JS plugin dir\nsmd_tag_p_qtstyl => JS style dir\nsmd_tag_p_size => Select/textarea rows\nsmd_tag_recent_report => Display recent report\nsmd_tag_report_lbl => Report: smd_tags\nsmd_tag_rss_uc_only => (ignored for tru_tags)\nsmd_tag_rss_uc_tru_tags_only => (ignored for Textpattern cats)\nsmd_tag_sel_list => Select list\nsmd_tag_sync_cfs_delim_lbl => Custom field delimiter\nsmd_tag_sync_cfs_lbl => Custom field\nsmd_tag_sync_delete_orig_lbl => Delete original\nsmd_tag_sync_force_cat_lbl => Force category link if tag already exists\nsmd_tag_sync_force_parent_lbl => Force parent if tag already exists\nsmd_tag_sync_import_opts => Import options\nsmd_tag_sync_lbl => Import tags\nsmd_tag_sync_link_cat_lbl => Link tags to category\nsmd_tag_sync_parent_cat_lbl => Link to category\nsmd_tag_sync_parent_lbl => Start from parent category\nsmd_tag_sync_parent_tag_lbl => Assign to parent tag\nsmd_tag_sync_plugin_opts => Source options\nsmd_tag_sync_plugs_not_installed => tru_tags or rss_unlimited_categories must be installed and activated to import\nsmd_tag_sync_section_lbl => Articles from section\nsmd_tag_sync_txpcats_lbl => Textpattern category\nsmd_tag_sync_type1 => tru_tags\nsmd_tag_sync_type2 => rss_unlimited_categories\nsmd_tag_sync_type3 => Textpattern categories\nsmd_tag_sync_type4 => Custom field\nsmd_tag_sync_type_lbl => Import from\nsmd_tag_tags_pane => Manage tags\nsmd_tag_tag_search => Tag search\nsmd_tag_tbl_installed => Tag tables installed.\nsmd_tag_tbl_install_lbl => Install tables\nsmd_tag_tbl_not_installed => Tag tables NOT installed.\nsmd_tag_tbl_not_removed => Tag tables NOT removed.\nsmd_tag_tbl_rebuild_lbl => Rebuild tags\nsmd_tag_tbl_rebuilt => Tag tables rebuilt\nsmd_tag_tbl_removed => Tag tables removed.\nsmd_tag_tbl_remove_lbl => Remove tables\nsmd_tag_textbox => Text area\nsmd_tag_textboxplus => Text area+\nsmd_tag_textlist => Text list\nsmd_tag_title => Tags\nsmd_tag_toggle => tog\nsmd_tag_t_astart => Initial pane\nsmd_tag_t_auto => Auto name\nsmd_tag_t_cols => Tag layout\nsmd_tag_t_colsord => Order tags by\nsmd_tag_t_count => Show tag usage counts\nsmd_tag_t_deltree => When deleting a parent\nsmd_tag_t_delused => Allow deletion of used tags\nsmd_tag_t_desc_textile => Textile description\nsmd_tag_t_desc_tooltip => Show description as tooltip\nsmd_tag_t_enrep => Automatically display reports\nsmd_tag_t_hilite => Clicked RGB colour\nsmd_tag_t_hover => Mouse-over RGB colour\nsmd_tag_t_indent => Sub-tag level indicator\nsmd_tag_t_mdelim => Multi-tag delimiter (1 char)\nsmd_tag_t_size_desc => Description width, height\nsmd_tag_unable_to_create => cannot create\nsmd_tag_updated => {type} tag \"{name}\" updated\nsmd_tag_u_combi => Use tag combinations\nsmd_tag_u_combi_and => AND combinator char\nsmd_tag_u_combi_or => OR combinator char\nsmd_tag_u_pnam => URL name parameter\nsmd_tag_u_ptyp => URL type parameter\nsmd_tag_u_sec => Trigger(s) for tag lists\nsmd_tag_with_filtered => With filtered:\n#@smd_tag\n#@language de\nsmd_tags => Tags (smd)\n#@smd_tag\n#@language es\nsmd_tags => Tags (smd)\n#@smd_tag\n#@language fr\nsmd_tags => Tags (smd)\n#@smd_tag\n#@language de\nsmd_tag_ac_std => Standard\nsmd_tag_ac_str => Strict\nsmd_tag_all_lbl => All\nsmd_tag_already_exists => already exists\nsmd_tag_assign_parent_lbl => Assign parent\nsmd_tag_bi_both => Both\nsmd_tag_bi_down => Down\nsmd_tag_bi_up => Up\nsmd_tag_by_column => Column\nsmd_tag_by_row => Row\nsmd_tag_category_linked_lbl => Linked to category \"{category}\":\nsmd_tag_category_unlinked_lbl => Unlinked:\nsmd_tag_children_and => and its children\nsmd_tag_children_del => Delete children\nsmd_tag_children_pro => Promote children\nsmd_tag_clear => clr\nsmd_tag_clink_lbl => Linked cat\nsmd_tag_count_lbl => Matched tags:\nsmd_tag_created => {type} tag \"{name}\" created\nsmd_tag_created_rep_lbl => Created tags:\nsmd_tag_create_lbl => Create/Edit tag\nsmd_tag_deleted => {type} tag \"{name}\" deleted\nsmd_tag_deleted_lbl => Deleted tags:\nsmd_tag_err_in_use_lbl => Tags in use:\nsmd_tag_err_orphaned_lbl => Orphaned tags:\nsmd_tag_exists => {type} tag \"{name}\" already exists\nsmd_tag_extras => More...\nsmd_tag_import_results_pt1 => Articles:\nsmd_tag_import_results_pt2 => Tags linked:\nsmd_tag_in_use => {type} tag \"{name}\" is in use\nsmd_tag_link_to_cat_lbl => Link to category\nsmd_tag_manage_lbl => Manage tags\nsmd_tag_missing_rep_lbl => Missing tags:\nsmd_tag_multi_delete => Tags deleted: {number} (see report for details)\nsmd_tag_not_available => Tables are not installed\nsmd_tag_not_created => {type} tag \"{name}\" NOT created\nsmd_tag_not_created_rep_lbl => Tags NOT created:\nsmd_tag_not_parent_linked_lbl => Not linked:\nsmd_tag_not_updated => {type} tag \"{name}\" NOT updated\nsmd_tag_no_desc => No description available\nsmd_tag_no_name => {type} tag NOT created: needs a name\nsmd_tag_no_parent => No parent tags.\nsmd_tag_no_sections => No sections\nsmd_tag_no_tags => No {type} tags defined\nsmd_tag_parent_lbl => Parent\nsmd_tag_parent_linked_lbl => Assigned these tag(s) to \"{parent}\":\nsmd_tag_pool_lbl => Tag pool\nsmd_tag_prefs_installed => Preferences installed\nsmd_tag_prefs_not_installed => Preferences not installed.\nsmd_tag_prefs_p => Interface settings\nsmd_tag_prefs_pane => Prefs\nsmd_tag_prefs_removed => Preferences removed\nsmd_tag_prefs_some => Not all preferences available.\nsmd_tag_prefs_some_explain => This is either a new installation or a different version<br />of the plugin to one you had before.\nsmd_tag_prefs_some_opts1 => Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.\nsmd_tag_prefs_some_opts2 => Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.\nsmd_tag_prefs_some_tbl => Not all table info available.\nsmd_tag_prefs_t => Tag management\nsmd_tag_prefs_title => Tag Preferences\nsmd_tag_prefs_u => URL management\nsmd_tag_pref_install_lbl => Install prefs\nsmd_tag_pref_remove_lbl => Remove prefs\nsmd_tag_pref_show_lbl => Preferences\nsmd_tag_p_enable => Enable tags in\nsmd_tag_p_input => Enter tags using\nsmd_tag_p_lbi => Bi-directional tag trees\nsmd_tag_p_linkcat => Link tags to categories\nsmd_tag_p_listpar => Permit parent tag selection\nsmd_tag_p_master => Master parent tag\nsmd_tag_p_qtag => Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)\nsmd_tag_p_qtpath => JS plugin dir\nsmd_tag_p_qtstyl => JS style dir\nsmd_tag_p_size => Select/textarea rows\nsmd_tag_recent_report => Display recent report\nsmd_tag_report_lbl => Report: smd_tags\nsmd_tag_rss_uc_only => (ignored for tru_tags)\nsmd_tag_rss_uc_tru_tags_only => (ignored for Textpattern cats)\nsmd_tag_sel_list => Select list\nsmd_tag_sync_cfs_delim_lbl => Custom field delimiter\nsmd_tag_sync_cfs_lbl => Custom field\nsmd_tag_sync_delete_orig_lbl => Delete original\nsmd_tag_sync_force_cat_lbl => Force category link if tag already exists\nsmd_tag_sync_force_parent_lbl => Force parent if tag already exists\nsmd_tag_sync_import_opts => Import options\nsmd_tag_sync_lbl => Import tags\nsmd_tag_sync_link_cat_lbl => Link tags to category\nsmd_tag_sync_parent_cat_lbl => Link to category\nsmd_tag_sync_parent_lbl => Start from parent category\nsmd_tag_sync_parent_tag_lbl => Assign to parent tag\nsmd_tag_sync_plugin_opts => Source options\nsmd_tag_sync_plugs_not_installed => tru_tags or rss_unlimited_categories must be installed and activated to import\nsmd_tag_sync_section_lbl => Articles from section\nsmd_tag_sync_txpcats_lbl => Textpattern category\nsmd_tag_sync_type1 => tru_tags\nsmd_tag_sync_type2 => rss_unlimited_categories\nsmd_tag_sync_type3 => Textpattern categories\nsmd_tag_sync_type4 => Custom field\nsmd_tag_sync_type_lbl => Import from\nsmd_tag_tags_pane => Manage tags\nsmd_tag_tag_search => Tag search\nsmd_tag_tbl_installed => Tag tables installed.\nsmd_tag_tbl_install_lbl => Install tables\nsmd_tag_tbl_not_installed => Tag tables NOT installed.\nsmd_tag_tbl_not_removed => Tag tables NOT removed.\nsmd_tag_tbl_rebuild_lbl => Rebuild tags\nsmd_tag_tbl_rebuilt => Tag tables rebuilt\nsmd_tag_tbl_removed => Tag tables removed.\nsmd_tag_tbl_remove_lbl => Remove tables\nsmd_tag_textbox => Text area\nsmd_tag_textboxplus => Text area+\nsmd_tag_textlist => Text list\nsmd_tag_title => Tags\nsmd_tag_toggle => tog\nsmd_tag_t_astart => Initial pane\nsmd_tag_t_auto => Auto name\nsmd_tag_t_cols => Tag layout\nsmd_tag_t_colsord => Order tags by\nsmd_tag_t_count => Show tag usage counts\nsmd_tag_t_deltree => When deleting a parent\nsmd_tag_t_delused => Allow deletion of used tags\nsmd_tag_t_desc_textile => Textile description\nsmd_tag_t_desc_tooltip => Show description as tooltip\nsmd_tag_t_enrep => Automatically display reports\nsmd_tag_t_hilite => Clicked RGB colour\nsmd_tag_t_hover => Mouse-over RGB colour\nsmd_tag_t_indent => Sub-tag level indicator\nsmd_tag_t_mdelim => Multi-tag delimiter (1 char)\nsmd_tag_t_size_desc => Description width, height\nsmd_tag_unable_to_create => cannot create\nsmd_tag_updated => {type} tag \"{name}\" updated\nsmd_tag_u_combi => Use tag combinations\nsmd_tag_u_combi_and => AND combinator char\nsmd_tag_u_combi_or => OR combinator char\nsmd_tag_u_pnam => URL name parameter\nsmd_tag_u_ptyp => URL type parameter\nsmd_tag_u_sec => Trigger(s) for tag lists\nsmd_tag_with_filtered => With filtered:\n#@smd_tag\n#@language es\nsmd_tag_ac_std => Standard\nsmd_tag_ac_str => Strict\nsmd_tag_all_lbl => All\nsmd_tag_already_exists => already exists\nsmd_tag_assign_parent_lbl => Assign parent\nsmd_tag_bi_both => Both\nsmd_tag_bi_down => Down\nsmd_tag_bi_up => Up\nsmd_tag_by_column => Column\nsmd_tag_by_row => Row\nsmd_tag_category_linked_lbl => Linked to category \"{category}\":\nsmd_tag_category_unlinked_lbl => Unlinked:\nsmd_tag_children_and => and its children\nsmd_tag_children_del => Delete children\nsmd_tag_children_pro => Promote children\nsmd_tag_clear => clr\nsmd_tag_clink_lbl => Linked cat\nsmd_tag_count_lbl => Matched tags:\nsmd_tag_created => {type} tag \"{name}\" created\nsmd_tag_created_rep_lbl => Created tags:\nsmd_tag_create_lbl => Create/Edit tag\nsmd_tag_deleted => {type} tag \"{name}\" deleted\nsmd_tag_deleted_lbl => Deleted tags:\nsmd_tag_err_in_use_lbl => Tags in use:\nsmd_tag_err_orphaned_lbl => Orphaned tags:\nsmd_tag_exists => {type} tag \"{name}\" already exists\nsmd_tag_extras => More...\nsmd_tag_import_results_pt1 => Articles:\nsmd_tag_import_results_pt2 => Tags linked:\nsmd_tag_in_use => {type} tag \"{name}\" is in use\nsmd_tag_link_to_cat_lbl => Link to category\nsmd_tag_manage_lbl => Manage tags\nsmd_tag_missing_rep_lbl => Missing tags:\nsmd_tag_multi_delete => Tags deleted: {number} (see report for details)\nsmd_tag_not_available => Tables are not installed\nsmd_tag_not_created => {type} tag \"{name}\" NOT created\nsmd_tag_not_created_rep_lbl => Tags NOT created:\nsmd_tag_not_parent_linked_lbl => Not linked:\nsmd_tag_not_updated => {type} tag \"{name}\" NOT updated\nsmd_tag_no_desc => No description available\nsmd_tag_no_name => {type} tag NOT created: needs a name\nsmd_tag_no_parent => No parent tags.\nsmd_tag_no_sections => No sections\nsmd_tag_no_tags => No {type} tags defined\nsmd_tag_parent_lbl => Parent\nsmd_tag_parent_linked_lbl => Assigned these tag(s) to \"{parent}\":\nsmd_tag_pool_lbl => Tag pool\nsmd_tag_prefs_installed => Preferences installed\nsmd_tag_prefs_not_installed => Preferences not installed.\nsmd_tag_prefs_p => Interface settings\nsmd_tag_prefs_pane => Prefs\nsmd_tag_prefs_removed => Preferences removed\nsmd_tag_prefs_some => Not all preferences available.\nsmd_tag_prefs_some_explain => This is either a new installation or a different version<br />of the plugin to one you had before.\nsmd_tag_prefs_some_opts1 => Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.\nsmd_tag_prefs_some_opts2 => Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.\nsmd_tag_prefs_some_tbl => Not all table info available.\nsmd_tag_prefs_t => Tag management\nsmd_tag_prefs_title => Tag Preferences\nsmd_tag_prefs_u => URL management\nsmd_tag_pref_install_lbl => Install prefs\nsmd_tag_pref_remove_lbl => Remove prefs\nsmd_tag_pref_show_lbl => Preferences\nsmd_tag_p_enable => Enable tags in\nsmd_tag_p_input => Enter tags using\nsmd_tag_p_lbi => Bi-directional tag trees\nsmd_tag_p_linkcat => Link tags to categories\nsmd_tag_p_listpar => Permit parent tag selection\nsmd_tag_p_master => Master parent tag\nsmd_tag_p_qtag => Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)\nsmd_tag_p_qtpath => JS plugin dir\nsmd_tag_p_qtstyl => JS style dir\nsmd_tag_p_size => Select/textarea rows\nsmd_tag_recent_report => Display recent report\nsmd_tag_report_lbl => Report: smd_tags\nsmd_tag_rss_uc_only => (ignored for tru_tags)\nsmd_tag_rss_uc_tru_tags_only => (ignored for Textpattern cats)\nsmd_tag_sel_list => Select list\nsmd_tag_sync_cfs_delim_lbl => Custom field delimiter\nsmd_tag_sync_cfs_lbl => Custom field\nsmd_tag_sync_delete_orig_lbl => Delete original\nsmd_tag_sync_force_cat_lbl => Force category link if tag already exists\nsmd_tag_sync_force_parent_lbl => Force parent if tag already exists\nsmd_tag_sync_import_opts => Import options\nsmd_tag_sync_lbl => Import tags\nsmd_tag_sync_link_cat_lbl => Link tags to category\nsmd_tag_sync_parent_cat_lbl => Link to category\nsmd_tag_sync_parent_lbl => Start from parent category\nsmd_tag_sync_parent_tag_lbl => Assign to parent tag\nsmd_tag_sync_plugin_opts => Source options\nsmd_tag_sync_plugs_not_installed => tru_tags or rss_unlimited_categories must be installed and activated to import\nsmd_tag_sync_section_lbl => Articles from section\nsmd_tag_sync_txpcats_lbl => Textpattern category\nsmd_tag_sync_type1 => tru_tags\nsmd_tag_sync_type2 => rss_unlimited_categories\nsmd_tag_sync_type3 => Textpattern categories\nsmd_tag_sync_type4 => Custom field\nsmd_tag_sync_type_lbl => Import from\nsmd_tag_tags_pane => Manage tags\nsmd_tag_tag_search => Tag search\nsmd_tag_tbl_installed => Tag tables installed.\nsmd_tag_tbl_install_lbl => Install tables\nsmd_tag_tbl_not_installed => Tag tables NOT installed.\nsmd_tag_tbl_not_removed => Tag tables NOT removed.\nsmd_tag_tbl_rebuild_lbl => Rebuild tags\nsmd_tag_tbl_rebuilt => Tag tables rebuilt\nsmd_tag_tbl_removed => Tag tables removed.\nsmd_tag_tbl_remove_lbl => Remove tables\nsmd_tag_textbox => Text area\nsmd_tag_textboxplus => Text area+\nsmd_tag_textlist => Text list\nsmd_tag_title => Tags\nsmd_tag_toggle => tog\nsmd_tag_t_astart => Initial pane\nsmd_tag_t_auto => Auto name\nsmd_tag_t_cols => Tag layout\nsmd_tag_t_colsord => Order tags by\nsmd_tag_t_count => Show tag usage counts\nsmd_tag_t_deltree => When deleting a parent\nsmd_tag_t_delused => Allow deletion of used tags\nsmd_tag_t_desc_textile => Textile description\nsmd_tag_t_desc_tooltip => Show description as tooltip\nsmd_tag_t_enrep => Automatically display reports\nsmd_tag_t_hilite => Clicked RGB colour\nsmd_tag_t_hover => Mouse-over RGB colour\nsmd_tag_t_indent => Sub-tag level indicator\nsmd_tag_t_mdelim => Multi-tag delimiter (1 char)\nsmd_tag_t_size_desc => Description width, height\nsmd_tag_unable_to_create => cannot create\nsmd_tag_updated => {type} tag \"{name}\" updated\nsmd_tag_u_combi => Use tag combinations\nsmd_tag_u_combi_and => AND combinator char\nsmd_tag_u_combi_or => OR combinator char\nsmd_tag_u_pnam => URL name parameter\nsmd_tag_u_ptyp => URL type parameter\nsmd_tag_u_sec => Trigger(s) for tag lists\nsmd_tag_with_filtered => With filtered:\n#@smd_tag\n#@language fr\nsmd_tag_ac_std => Standard\nsmd_tag_ac_str => Strict\nsmd_tag_all_lbl => All\nsmd_tag_already_exists => already exists\nsmd_tag_assign_parent_lbl => Assign parent\nsmd_tag_bi_both => Both\nsmd_tag_bi_down => Down\nsmd_tag_bi_up => Up\nsmd_tag_by_column => Column\nsmd_tag_by_row => Row\nsmd_tag_category_linked_lbl => Linked to category \"{category}\":\nsmd_tag_category_unlinked_lbl => Unlinked:\nsmd_tag_children_and => and its children\nsmd_tag_children_del => Delete children\nsmd_tag_children_pro => Promote children\nsmd_tag_clear => clr\nsmd_tag_clink_lbl => Linked cat\nsmd_tag_count_lbl => Matched tags:\nsmd_tag_created => {type} tag \"{name}\" created\nsmd_tag_created_rep_lbl => Created tags:\nsmd_tag_create_lbl => Create/Edit tag\nsmd_tag_deleted => {type} tag \"{name}\" deleted\nsmd_tag_deleted_lbl => Deleted tags:\nsmd_tag_err_in_use_lbl => Tags in use:\nsmd_tag_err_orphaned_lbl => Orphaned tags:\nsmd_tag_exists => {type} tag \"{name}\" already exists\nsmd_tag_extras => More...\nsmd_tag_import_results_pt1 => Articles:\nsmd_tag_import_results_pt2 => Tags linked:\nsmd_tag_in_use => {type} tag \"{name}\" is in use\nsmd_tag_link_to_cat_lbl => Link to category\nsmd_tag_manage_lbl => Manage tags\nsmd_tag_missing_rep_lbl => Missing tags:\nsmd_tag_multi_delete => Tags deleted: {number} (see report for details)\nsmd_tag_not_available => Tables are not installed\nsmd_tag_not_created => {type} tag \"{name}\" NOT created\nsmd_tag_not_created_rep_lbl => Tags NOT created:\nsmd_tag_not_parent_linked_lbl => Not linked:\nsmd_tag_not_updated => {type} tag \"{name}\" NOT updated\nsmd_tag_no_desc => No description available\nsmd_tag_no_name => {type} tag NOT created: needs a name\nsmd_tag_no_parent => No parent tags.\nsmd_tag_no_sections => No sections\nsmd_tag_no_tags => No {type} tags defined\nsmd_tag_parent_lbl => Parent\nsmd_tag_parent_linked_lbl => Assigned these tag(s) to \"{parent}\":\nsmd_tag_pool_lbl => Tag pool\nsmd_tag_prefs_installed => Preferences installed\nsmd_tag_prefs_not_installed => Preferences not installed.\nsmd_tag_prefs_p => Interface settings\nsmd_tag_prefs_pane => Prefs\nsmd_tag_prefs_removed => Preferences removed\nsmd_tag_prefs_some => Not all preferences available.\nsmd_tag_prefs_some_explain => This is either a new installation or a different version<br />of the plugin to one you had before.\nsmd_tag_prefs_some_opts1 => Choose \"Remove prefs\" to remove them all or \"Install prefs\" to add<br />any new ones, leaving all existing prefs untouched.\nsmd_tag_prefs_some_opts2 => Click \"Install tables\" to add or update the tables<br />leaving all existing data untouched.\nsmd_tag_prefs_some_tbl => Not all table info available.\nsmd_tag_prefs_t => Tag management\nsmd_tag_prefs_title => Tag Preferences\nsmd_tag_prefs_u => URL management\nsmd_tag_pref_install_lbl => Install prefs\nsmd_tag_pref_remove_lbl => Remove prefs\nsmd_tag_pref_show_lbl => Preferences\nsmd_tag_p_enable => Enable tags in\nsmd_tag_p_input => Enter tags using\nsmd_tag_p_lbi => Bi-directional tag trees\nsmd_tag_p_linkcat => Link tags to categories\nsmd_tag_p_listpar => Permit parent tag selection\nsmd_tag_p_master => Master parent tag\nsmd_tag_p_qtag => Quick tag (<a href=\"http://plugins.jquery.com/project/autocompletex\">requires plugin</a>)\nsmd_tag_p_qtpath => JS plugin dir\nsmd_tag_p_qtstyl => JS style dir\nsmd_tag_p_size => Select/textarea rows\nsmd_tag_recent_report => Display recent report\nsmd_tag_report_lbl => Report: smd_tags\nsmd_tag_rss_uc_only => (ignored for tru_tags)\nsmd_tag_rss_uc_tru_tags_only => (ignored for Textpattern cats)\nsmd_tag_sel_list => Select list\nsmd_tag_sync_cfs_delim_lbl => Custom field delimiter\nsmd_tag_sync_cfs_lbl => Custom field\nsmd_tag_sync_delete_orig_lbl => Delete original\nsmd_tag_sync_force_cat_lbl => Force category link if tag already exists\nsmd_tag_sync_force_parent_lbl => Force parent if tag already exists\nsmd_tag_sync_import_opts => Import options\nsmd_tag_sync_lbl => Import tags\nsmd_tag_sync_link_cat_lbl => Link tags to category\nsmd_tag_sync_parent_cat_lbl => Link to category\nsmd_tag_sync_parent_lbl => Start from parent category\nsmd_tag_sync_parent_tag_lbl => Assign to parent tag\nsmd_tag_sync_plugin_opts => Source options\nsmd_tag_sync_plugs_not_installed => tru_tags or rss_unlimited_categories must be installed and activated to import\nsmd_tag_sync_section_lbl => Articles from section\nsmd_tag_sync_txpcats_lbl => Textpattern category\nsmd_tag_sync_type1 => tru_tags\nsmd_tag_sync_type2 => rss_unlimited_categories\nsmd_tag_sync_type3 => Textpattern categories\nsmd_tag_sync_type4 => Custom field\nsmd_tag_sync_type_lbl => Import from\nsmd_tag_tags_pane => Manage tags\nsmd_tag_tag_search => Tag search\nsmd_tag_tbl_installed => Tag tables installed.\nsmd_tag_tbl_install_lbl => Install tables\nsmd_tag_tbl_not_installed => Tag tables NOT installed.\nsmd_tag_tbl_not_removed => Tag tables NOT removed.\nsmd_tag_tbl_rebuild_lbl => Rebuild tags\nsmd_tag_tbl_rebuilt => Tag tables rebuilt\nsmd_tag_tbl_removed => Tag tables removed.\nsmd_tag_tbl_remove_lbl => Remove tables\nsmd_tag_textbox => Text area\nsmd_tag_textboxplus => Text area+\nsmd_tag_textlist => Text list\nsmd_tag_title => Tags\nsmd_tag_toggle => tog\nsmd_tag_t_astart => Initial pane\nsmd_tag_t_auto => Auto name\nsmd_tag_t_cols => Tag layout\nsmd_tag_t_colsord => Order tags by\nsmd_tag_t_count => Show tag usage counts\nsmd_tag_t_deltree => When deleting a parent\nsmd_tag_t_delused => Allow deletion of used tags\nsmd_tag_t_desc_textile => Textile description\nsmd_tag_t_desc_tooltip => Show description as tooltip\nsmd_tag_t_enrep => Automatically display reports\nsmd_tag_t_hilite => Clicked RGB colour\nsmd_tag_t_hover => Mouse-over RGB colour\nsmd_tag_t_indent => Sub-tag level indicator\nsmd_tag_t_mdelim => Multi-tag delimiter (1 char)\nsmd_tag_t_size_desc => Description width, height\nsmd_tag_unable_to_create => cannot create\nsmd_tag_updated => {type} tag \"{name}\" updated\nsmd_tag_u_combi => Use tag combinations\nsmd_tag_u_combi_and => AND combinator char\nsmd_tag_u_combi_or => OR combinator char\nsmd_tag_u_pnam => URL name parameter\nsmd_tag_u_ptyp => URL type parameter\nsmd_tag_u_sec => Trigger(s) for tag lists\nsmd_tag_with_filtered => With filtered:', '', 5, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('arc_redirect', 0, 'Andy Carter', 'http://andy-carter.com/', '1.3', 'Love redirects, hate 404s', '<h1>arc_redirect (love redirects, hate 404s)</h1>\n\n<p>If you&#8217;re in the process of restructuring a Textpattern site, then this is the plugin for you!</p>\n\n<p>Requirements:-</p>\n\n<ul>\n	<li>Textpattern 4.5+</li>\n</ul>\n\n<h2>Installation</h2>\n\n<p>To install go to ‘Plugins’ under ‘Admin’ and paste the plugin code into the ‘Install plugin’ box, ‘upload’ and then ‘install’. You will then need to activate the plugin.</p>\n\n\n<h2>Uninstall</h2>\n\n<p>To uninstall <span>arc_redirect</span> simply delete the plugin from the ‘Plugins’ tab.  This will remove the plugin code and drop the <span>arc_redirect</span> table from your Textpattern database.</p>\n\n\n<h2>Usage</h2>\n\n<p>arc_redirect adds a new tab under ‘Extensions’ from where you can define pairs of <span class=\"caps\">URL</span>s for handling redirects. Basically provide an original <span class=\"caps\">URL</span> on your Textpattern site that is generating a 404, “page not found”, error and a redirect <span class=\"caps\">URL</span>. Then whenever someone goes to the original <span class=\"caps\">URL</span> rather than get the standard 404 error page they will be redircted to the new <span class=\"caps\">URL</span> (with a 301 permenantely moved, or 302 temporarily removed).</p>\n\n<p>The redirect from <span class=\"caps\">URL</span> must produce a 404 page in Textpattern on the site this plugin is installed.</p>\n\n<ul>\n	<li>arc_redirect treats <em>http://www.example.com/missing</em> the same as <em>http://www.example.com/missing/</em></li>\n	<li>arc_redirect does not treat <em>http://example.com/missing</em> and <em>http://www.example.com/missing</em> as the same <span class=\"caps\">URL</span></li>\n	<li>You can use absolute <span class=\"caps\">URL</span>s like <em>/missing</em></li>\n</ul>\n\n\n<h2>Author</h2>\n\n<p><a href=\"http://andy-carter.com\" rel=\"nofollow\">Andy Carter</a>. For other Textpattern plugins by me visit my <a href=\"http://andy-carter.com/txp\" rel=\"nofollow\">Plugins page</a>.</p>\n\n<p>Thanks to <a href=\"http://oliverker.co.uk/\" rel=\"nofollow\">Oliver Ker</a> for giving me the idea for this plugin.</p>\n\n<h2>License</h2>\n\n<p>The <span class=\"caps\">MIT</span> License (<span class=\"caps\">MIT</span>)</p>\n\n<p>Copyright &#169; 2015 Andy Carter</p>\n\n<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>\n\n<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>\n\n<p><span class=\"caps\">THE</span> <span class=\"caps\">SOFTWARE</span> IS <span class=\"caps\">PROVIDED</span> “AS IS”, <span class=\"caps\">WITHOUT</span> <span class=\"caps\">WARRANTY</span> OF <span class=\"caps\">ANY</span> <span class=\"caps\">KIND</span>, <span class=\"caps\">EXPRESS</span> OR <span class=\"caps\">IMPLIED</span>, <span class=\"caps\">INCLUDING</span> <span class=\"caps\">BUT</span> <span class=\"caps\">NOT</span> <span class=\"caps\">LIMITED</span> TO <span class=\"caps\">THE</span> <span class=\"caps\">WARRANTIES</span> OF <span class=\"caps\">MERCHANTABILITY</span>, <span class=\"caps\">FITNESS</span> <span class=\"caps\">FOR</span> A <span class=\"caps\">PARTICULAR</span> <span class=\"caps\">PURPOSE</span> <span class=\"caps\">AND</span> <span class=\"caps\">NONINFRINGEMENT</span>. IN NO <span class=\"caps\">EVENT</span> <span class=\"caps\">SHALL</span> <span class=\"caps\">THE</span> <span class=\"caps\">AUTHORS</span> OR <span class=\"caps\">COPYRIGHT</span> <span class=\"caps\">HOLDERS</span> BE <span class=\"caps\">LIABLE</span> <span class=\"caps\">FOR</span> <span class=\"caps\">ANY</span> <span class=\"caps\">CLAIM</span>, <span class=\"caps\">DAMAGES</span> OR <span class=\"caps\">OTHER</span> <span class=\"caps\">LIABILITY</span>, <span class=\"caps\">WHETHER</span> IN AN <span class=\"caps\">ACTION</span> OF <span class=\"caps\">CONTRACT</span>, <span class=\"caps\">TORT</span> OR <span class=\"caps\">OTHERWISE</span>, <span class=\"caps\">ARISING</span> <span class=\"caps\">FROM</span>, <span class=\"caps\">OUT</span> OF OR IN <span class=\"caps\">CONNECTION</span> <span class=\"caps\">WITH</span> <span class=\"caps\">THE</span> <span class=\"caps\">SOFTWARE</span> OR <span class=\"caps\">THE</span> <span class=\"caps\">USE</span> OR <span class=\"caps\">OTHER</span> <span class=\"caps\">DEALINGS</span> IN <span class=\"caps\">THE</span> <span class=\"caps\">SOFTWARE</span>.</p>', 'register_callback(\'arc_redirect_install\',\'plugin_lifecycle.arc_redirect\', \'installed\');\nregister_callback(\'arc_redirect_uninstall\',\'plugin_lifecycle.arc_redirect\', \'deleted\');\nregister_callback(\'arc_redirect\', \'pretext_end\');\nadd_privs(\'arc_redirect\', \'1,2,3,4\');\nregister_tab(\'extensions\', \'arc_redirect\', \'arc_redirect\');\nregister_callback(\'arc_redirect_tab\', \'arc_redirect\');\n\nuse Textpattern\\Search\\Filter;\n\n/*\n * Check for redirected URLs and forward with a 301 or 302.\n */\n\nfunction arc_redirect($event, $step)\n{\n	global $pretext;\n\n	$url = parse_url($pretext[\'request_uri\']);\n	$url = doSlash(rtrim($url[\'path\'], \'/\'));\n\n	// full URL including the protocol and domain\n	$fullUrl = hu . ltrim($url, \'/\');\n\n	$redirect = safe_row(\n		\'redirectUrl, statusCode\',\n		\'arc_redirect\',\n		\"originalUrl = \'$url\' OR originalUrl = \'$fullUrl\' ORDER BY CHAR_LENGTH(originalUrl) DESC, arc_redirectID DESC\"\n	);\n\n	if (isset($redirect[\'redirectUrl\']))\n	{\n		txp_die(\'\', $redirect[\'statusCode\'], $redirect[\'redirectUrl\']);\n	}\n\n	return;\n\n}\n\n\n/**\n * Redirects panel.\n *\n * @package Extensions\\Redirect\n */\n\nfunction arc_redirect_tab($event, $step) {\n\n	switch ($step) {\n		case \'add\': arc_redirect_add(); break;\n		case \'save\': arc_redirect_save(); break;\n		case \'edit\': arc_redirect_edit(); break;\n		case \'arc_redirect_multi_edit\': arc_redirect_multi_edit(); break;\n		case \'arc_redirect_change_pageby\': arc_redirect_change_pageby(); break;\n		default: arc_redirect_list();\n	}\n}\n\n\n/**\n * The main panel listing all redirects.\n *\n * @param string|array $message The activity message\n */\n\nfunction arc_redirect_list($message = \'\')\n{\n	global $event;\n\n	pagetop(\'tab_arc_redirect\',$message);\n\n	extract(gpsa(array(\n		\'page\',\n		\'sort\',\n		\'dir\',\n		\'crit\',\n		\'search_method\',\n	)));\n\n	if ($sort === \'\') {\n		$sort = get_pref(\'arc_redirect_sort_column\', \'arc_redirectID\');\n	} else {\n		if (!in_array($sort, array(\'arc_redirectID\', \'originalUrl\', \'redirectUrl\', \'statusCode\'))) {\n			$sort = \'arc_redirectID\';\n		}\n\n		set_pref(\'arc_redirect_sort_column\', $sort, \'arc_redirect\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n	}\n\n	if ($dir === \'\') {\n		$dir = get_pref(\'arc_redirect_sort_dir\', \'asc\');\n	} else {\n		$dir = ($dir == \'desc\') ? \"desc\" : \"asc\";\n		set_pref(\'arc_redirect_sort_dir\', $dir, \'arc_redirect\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n	}\n\n	$sort_sql = \"$sort $dir\";\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n	$search = new Filter($event,\n		array(\n			\'arc_redirectID\' => array(\n				\'column\' => \'arc_redirect.arc_redirectID\',\n				\'label\'  => gTxt(\'id\'),\n			),\n			\'originalUrl\' => array(\n				\'column\' => \'arc_redirect.originalUrl\',\n				\'label\'  => gTxt(\'arc_original_url\'),\n			),\n			\'redirectUrl\' => array(\n				\'column\' => \'arc_redirect.redirectUrl\',\n				\'label\'  => gTxt(\'arc_redirect_url\'),\n			),\n			\'statusCode\' => array(\n				\'column\' => \'arc_redirect.statusCode\',\n				\'label\'  => gTxt(\'type\'),\n			),\n		)\n	);\n\n	$alias_permanent = \'301, Permanent\';\n	$alias_temporary = \'302, Temporary\';\n	$search->setAliases(\'statusCode\', array($alias_permanent, $alias_temporary));\n\n	list($criteria, $crit, $search_method) = $search->getFilter();\n\n	$search_render_options = array(\'placeholder\' => gTxt(\'search_redirects\'));\n	$total = safe_count(\'arc_redirect\', $criteria);\n\n	$searchBlock =\n		n.tag(\n			$search->renderForm(\'arc_redirect\', $search_render_options),\n			\'div\', array(\n				\'class\' => \'txp-layout-4col-3span\',\n				\'id\'	=> $event.\'_control\',\n			)\n		);\n\n	$createBlock  = tag(arc_redirect_form(\'\'), \'div\', array(\'class\' => \'txp-control-panel\', \'style\' => \'width: 100%\'));\n	$contentBlock = \'\';\n\n	$paginator = new \\Textpattern\\Admin\\Paginator($event, \'arc_redirect\');\n	$limit = $paginator->getLimit();\n\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n	if ($total < 1) {\n        if ($criteria != 1) {\n    		$contentBlock .= graf(\n    			span(null, array(\'class\' => \'ui-icon ui-icon-info\')).\' \'.\n    			gTxt(\'no_results_found\'),\n    			array(\'class\' => \'alert-block information\')\n    		);\n        }\n	} else {\n		$rs = safe_rows_start(\n			\"arc_redirectID, originalUrl, redirectUrl, statusCode\",\n			\'arc_redirect\',\n			\"$criteria ORDER BY $sort_sql LIMIT $offset, $limit\"\n		);\n\n		$contentBlock .=\n			n.tag_start(\'form\', array(\n				\'class\'  => \'multi_edit_form\',\n				\'id\'	 => \'arc_redirect_form\',\n				\'name\'   => \'longform\',\n				\'method\' => \'post\',\n				\'action\' => \'index.php\',\n			)).\n			n.tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n			n.tag_start(\'table\', array(\'class\' => \'txp-list\')).\n			n.tag_start(\'thead\').\n			tr(\n				hCell(\n					fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'),\n						\'\', \' class=\"txp-list-col-multi-edit\" scope=\"col\" title=\"\'.gTxt(\'toggle_all_selected\').\'\"\'\n				).\n				column_head(\n					gTxt(\'id\'), \'arc_redirectID\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'arc_redirectID\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-id\'\n				).\n				column_head(\n					gTxt(\'arc_original_url\'), \'originalUrl\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'originalUrl\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-originalurl\'\n				).\n				column_head(\n					gTxt(\'arc_redirect_url\'), \'redirectUrl\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'redirectUrl\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-redirecturl\'\n				).\n				column_head(\n					gTxt(\'type\'), \'statusCode\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'statusCode\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-type\'\n				).\n				hCell(\n					gTxt(\'manage\'), \'\', \' class=\"txp-list-col-manage\" scope=\"col\"\'\n				)\n			).\n			n.tag_end(\'thead\').\n			n.tag_start(\'tbody\');\n\n		while ($a = nextRow($rs)) {\n			extract($a, EXTR_PREFIX_ALL, \'redirect\');\n\n			$edit_url = eLink(\'arc_redirect\', \'edit\', \'id\', $redirect_arc_redirectID, gTxt(\'edit\'));\n\n			$test_url = $redirect_originalUrl;\n			if (strpos($test_url, \'/\') === 0) {\n				$test_url = hu . ltrim($test_url, \'/\');\n			}\n			$redirect_url = href(gTxt(\'test_redirect\'), $test_url, \' rel=\"external\" target=\"_blank\"\');\n\n			$contentBlock .= tr(\n				td(\n					fInput(\'checkbox\', \'selected[]\', $redirect_arc_redirectID), \'\', \'txp-list-col-multi-edit\'\n				).\n				hCell(\n					$redirect_arc_redirectID, \'\', \' class=\"txp-list-col-id\" scope=\"row\"\'\n				).\n				td(\n					txpspecialchars($redirect_originalUrl), \'\', \'txp-list-col-originalurl\'\n				).\n				td(\n					txpspecialchars($redirect_redirectUrl), \'\', \'txp-list-col-redirecturl\'\n				).\n				td(\n					$redirect_statusCode == 301 ? gTxt(\'redirect_permanent\') : gTxt(\'redirect_temporary\'), \'\', \'txp-list-col-type\'\n				).\n				td(\n					\"$edit_url <span> | </span> $redirect_url\", \'\', \'txp-list-col-manage\'\n				)\n			);\n            unset($redirect_arc_redirectID);\n		}\n\n		$methods = array(\n			\'delete\' => gTxt(\'delete\')\n		);\n\n		$contentBlock .= n.tag_end(\'tbody\').\n			n.tag_end(\'table\').\n			n.tag_end(\'div\').\n			multi_edit($methods, \'arc_redirect\', \'arc_redirect_multi_edit\').\n			tInput().\n			n.tag_end(\'form\');\n\n		$pageBlock = $paginator->render().\n		nav_form(\'arc_redirect\', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit);\n\n		$table = new \\Textpattern\\Admin\\Table($event);\n		echo $table->render(compact(\'total\', \'criteria\') + array(\'heading\' => \'tab_arc_redirect\'), $searchBlock, $createBlock, $contentBlock, $pageBlock);\n\n	}\n\n}\n\n\n/**\n * Edit redirect.\n *\n * @param string|array $message The activity message\n */\n\nfunction arc_redirect_edit($message=\'\')\n{\n	pagetop(\'arc_redirect\',$message);\n\n	$originalUrl = gps(\'originalUrl\');\n	$redirectUrl = gps(\'redirectUrl\');\n	$statusCode = gps(\'statusCode\');\n\n	if ($id = gps(\'id\')) {\n		$id = doSlash($id);\n		$rs = safe_row(\'originalUrl, redirectUrl, statusCode\', \'arc_redirect\', \"arc_redirectID = $id\");\n		extract($rs);\n	}\n\n	echo hed(gTxt(\'tab_arc_redirect\'), 1, array(\'class\' => \'txp-heading\')).\n         arc_redirect_form($id, $originalUrl, $redirectUrl, $statusCode);\n}\n\n\n/**\n * Add redirect.\n */\n\nfunction arc_redirect_add()\n{\n	$originalUrl = ps(\'originalUrl\');\n	$redirectUrl = ps(\'redirectUrl\');\n\n	if ($originalUrl === \'\' || $redirectUrl === \'\')\n	{\n		arc_redirect_edit(gTxt(\'error_details_incomplete\'));\n		return;\n	}\n\n	$statusCode = ps(\'statusCode\') == 301 ? 301 : 302;\n\n	// Strip final slash from original url\n	$originalUrl = rtrim($originalUrl, \'/\');\n\n	$q = safe_insert(\n		\"arc_redirect\",\n		\"originalUrl = \'\" . trim(doSlash($originalUrl)) . \"\', redirectUrl = \'\" . trim(doSlash($redirectUrl)) . \"\', statusCode = \" . $statusCode\n	);\n\n	if ($q)\n	{\n		$message = gTxt(\'redirect_added\');\n		arc_redirect_list($message);\n	}\n\n	return;\n}\n\n\n/**\n * Saves redirect.\n */\n\nfunction arc_redirect_save()\n{\n	if (!$id=ps(\'id\'))\n	{\n		arc_redirect_list(gTxt(\'error_details_incomplete_no_id\'));\n		return;\n	}\n\n	$originalUrl = ps(\'originalUrl\');\n	$redirectUrl = ps(\'redirectUrl\');\n	$statusCode = ps(\'statusCode\');\n\n	if ($originalUrl == \'\' || $redirectUrl == \'\' || empty($statusCode))\n	{\n		arc_redirect_edit(gTxt(\'error_details_incomplete\'));\n		return;\n	}\n\n	// Strip final slash from original url\n	$originalUrl = rtrim($originalUrl, \'/\');\n\n	$id = doSlash($id);\n\n	$rs = safe_update(\n		\"arc_redirect\",\n		\"originalUrl	= \'\" . trim(doSlash($originalUrl)) . \"\',  redirectUrl = \'\" . trim(doSlash($redirectUrl)) . \"\',  statusCode = \" . trim(doSlash($statusCode)) . \"\",\n		\"arc_redirectID = $id\"\n	);\n\n	if ($rs)\n	{\n		$message = gTxt(\'redirect_updated\');\n		arc_redirect_list($message);\n	}\n	return;\n}\n\n\n/**\n * Processes multi-edit actions.\n */\n\nfunction arc_redirect_multi_edit() {\n	$selected = ps(\'selected\');\n\n	if (!$selected || !is_array($selected)) {\n		arc_redirect_list();\n		return;\n	}\n\n	$method = ps(\'edit_method\');\n	$changed = array();\n\n	$message = \'\';\n\n	switch ($method) {\n		case \'delete\':\n\n			foreach ($selected as $id) {\n				$id = doSlash($id);\n				if (safe_delete(\'arc_redirect\', \'arc_redirectID = \'.$id)) {\n					$changed[] = $id;\n				}\n			}\n			$num = count($changed);\n			if ($num > 1) {\n				$message = $num . \' \' . gTxt(\'redirects_deleted\');\n			} else {\n				$message = gTxt(\'redirect_deleted\');\n			}\n\n			break;\n	}\n\n	arc_redirect_list($message);\n}\n\n\n/**\n * Updates pageby value.\n */\n\nfunction arc_redirect_change_pageby()\n{\n	global $event;\n\n	Txp::get(\'\\Textpattern\\Admin\\Paginator\', $event, \'arc_redirect\')->change();\n	arc_redirect_list();\n}\n\n\n/**\n * Renders an add a redirect form.\n *\n * @return string HTML\n * @access private\n * @see	form()\n */\n\n function arc_redirect_form($id = \'\', $originalUrl = \'\', $redirectUrl = \'\', $statusCode = \'\')\n {\n     // bump back if non-existent ID specified\n     if ($id) {\n         $rs = safe_row(\'arc_redirectID\', \'arc_redirect\', \"arc_redirectID = $id\");\n         if (!$rs) {\n            arc_redirect_list(gTxt(\'error_no_such_redirect_id\'));\n     		return;\n         }\n     }\n\n     // \'edit\' or \'add\' operation\n     $action = ($id != \'\') ? \'save\' : \'add\';\n\n     $statusCodes = array(\n 		301 => gTxt(\'redirect_permanent\'),\n 		302 => gTxt(\'redirect_temporary\')\n 	);\n\n 	return form(\n 		(($action == \"add\") ? hed(gTxt(\'add_new_redirect\'), 2) : hed(gTxt(\'edit_redirect\'), 2)).\n 		inputLabel(\n 			\'originalUrl\',\n 			fInput(\'text\', \'originalUrl\', $originalUrl, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'originalUrl\'),\n 			gTxt(\'arc_original_url\'), \'\', array(\'class\' => \'txp-form-field input-original-url\')\n 		).\n 		inputLabel(\n 			\'redirectUrl\',\n 			fInput(\'text\', \'redirectUrl\', $redirectUrl, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'redirectUrl\'),\n 			gTxt(\'arc_redirect_url\'), \'\', array(\'class\' => \'txp-form-field input-redirect-url\')\n 		).\n 		inputLabel(\n 			\'statusCode\',\n 			selectInput(\'statusCode\', $statusCodes, $statusCode),\n 			gTxt(\'redirect_type\'), \'\', array(\'class\' => \'txp-form-field set-status-code\')\n 		).\n 		graf(\n 			fInput(\'submit\', $action, gTxt($action), \'publish\'),\n 			array(\'class\' => \'txp-edit-actions\', \'style\' => \'text-align: left\')\n 		).\n 		eInput(\'arc_redirect\').\n 		(($id != \'\') ? hInput(\'id\',$id) : \'\').\n 		sInput($action),\n 		\'display:block; margin-left: 0;\', \'\', \'post\', \'txp-edit\', \'\', \'arc_redirect_details\'\n 	);\n\n }\n\n\n/**\n * Installation function – builds MySQL table.\n */\n\nfunction arc_redirect_install()\n{\n	// Create the arc_redirect table if it does not already exist\n    safe_create(\'arc_redirect\', \'\n        arc_redirectID  INT          NOT NULL AUTO_INCREMENT,\n        originalUrl     VARCHAR(255) NOT NULL,\n        redirectUrl     VARCHAR(255) NOT NULL,\n        statusCode      INT          NOT NULL DEFAULT \\\'301\\\',\n        PRIMARY KEY (arc_redirectID)\n    \');\n\n	// Add \'statusCode\' column to an existing legacy \'arc_redirect\' table\n    if (!getRows(\"SHOW COLUMNS FROM \".safe_pfx(\'arc_redirect\').\" LIKE \'statusCode\'\")) {\n        safe_alter(\n            \'arc_redirect\',\n            \"statusCode INT NOT NULL DEFAULT \\\'301\\\'\"\n        );\n    }\n\n	return;\n}\n\n\n/**\n * Uninstall function – deletes MySQL table and related preferences.\n */\n\nfunction arc_redirect_uninstall()\n{\n	// Drop the arc_redirect table\n    safe_drop(\"arc_redirect\");\n    // Remove arc_redirect prefs\n    safe_delete(\'txp_prefs\', \'event = \"arc_redirect\"\');\n}', 'register_callback(\'arc_redirect_install\',\'plugin_lifecycle.arc_redirect\', \'installed\');\nregister_callback(\'arc_redirect_uninstall\',\'plugin_lifecycle.arc_redirect\', \'deleted\');\nregister_callback(\'arc_redirect\', \'pretext_end\');\nadd_privs(\'arc_redirect\', \'1,2,3,4\');\nregister_tab(\'extensions\', \'arc_redirect\', \'arc_redirect\');\nregister_callback(\'arc_redirect_tab\', \'arc_redirect\');\n\nuse Textpattern\\Search\\Filter;\n\n/*\n * Check for redirected URLs and forward with a 301 or 302.\n */\n\nfunction arc_redirect($event, $step)\n{\n	global $pretext;\n\n	$url = parse_url($pretext[\'request_uri\']);\n	$url = doSlash(rtrim($url[\'path\'], \'/\'));\n\n	// full URL including the protocol and domain\n	$fullUrl = hu . ltrim($url, \'/\');\n\n	$redirect = safe_row(\n		\'redirectUrl, statusCode\',\n		\'arc_redirect\',\n		\"originalUrl = \'$url\' OR originalUrl = \'$fullUrl\' ORDER BY CHAR_LENGTH(originalUrl) DESC, arc_redirectID DESC\"\n	);\n\n	if (isset($redirect[\'redirectUrl\']))\n	{\n		txp_die(\'\', $redirect[\'statusCode\'], $redirect[\'redirectUrl\']);\n	}\n\n	return;\n\n}\n\n\n/**\n * Redirects panel.\n *\n * @package Extensions\\Redirect\n */\n\nfunction arc_redirect_tab($event, $step) {\n\n	switch ($step) {\n		case \'add\': arc_redirect_add(); break;\n		case \'save\': arc_redirect_save(); break;\n		case \'edit\': arc_redirect_edit(); break;\n		case \'arc_redirect_multi_edit\': arc_redirect_multi_edit(); break;\n		case \'arc_redirect_change_pageby\': arc_redirect_change_pageby(); break;\n		default: arc_redirect_list();\n	}\n}\n\n\n/**\n * The main panel listing all redirects.\n *\n * @param string|array $message The activity message\n */\n\nfunction arc_redirect_list($message = \'\')\n{\n	global $event;\n\n	pagetop(\'tab_arc_redirect\',$message);\n\n	extract(gpsa(array(\n		\'page\',\n		\'sort\',\n		\'dir\',\n		\'crit\',\n		\'search_method\',\n	)));\n\n	if ($sort === \'\') {\n		$sort = get_pref(\'arc_redirect_sort_column\', \'arc_redirectID\');\n	} else {\n		if (!in_array($sort, array(\'arc_redirectID\', \'originalUrl\', \'redirectUrl\', \'statusCode\'))) {\n			$sort = \'arc_redirectID\';\n		}\n\n		set_pref(\'arc_redirect_sort_column\', $sort, \'arc_redirect\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n	}\n\n	if ($dir === \'\') {\n		$dir = get_pref(\'arc_redirect_sort_dir\', \'asc\');\n	} else {\n		$dir = ($dir == \'desc\') ? \"desc\" : \"asc\";\n		set_pref(\'arc_redirect_sort_dir\', $dir, \'arc_redirect\', PREF_HIDDEN, \'\', 0, PREF_PRIVATE);\n	}\n\n	$sort_sql = \"$sort $dir\";\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n	$search = new Filter($event,\n		array(\n			\'arc_redirectID\' => array(\n				\'column\' => \'arc_redirect.arc_redirectID\',\n				\'label\'  => gTxt(\'id\'),\n			),\n			\'originalUrl\' => array(\n				\'column\' => \'arc_redirect.originalUrl\',\n				\'label\'  => gTxt(\'arc_original_url\'),\n			),\n			\'redirectUrl\' => array(\n				\'column\' => \'arc_redirect.redirectUrl\',\n				\'label\'  => gTxt(\'arc_redirect_url\'),\n			),\n			\'statusCode\' => array(\n				\'column\' => \'arc_redirect.statusCode\',\n				\'label\'  => gTxt(\'type\'),\n			),\n		)\n	);\n\n	$alias_permanent = \'301, Permanent\';\n	$alias_temporary = \'302, Temporary\';\n	$search->setAliases(\'statusCode\', array($alias_permanent, $alias_temporary));\n\n	list($criteria, $crit, $search_method) = $search->getFilter();\n\n	$search_render_options = array(\'placeholder\' => gTxt(\'search_redirects\'));\n	$total = safe_count(\'arc_redirect\', $criteria);\n\n	$searchBlock =\n		n.tag(\n			$search->renderForm(\'arc_redirect\', $search_render_options),\n			\'div\', array(\n				\'class\' => \'txp-layout-4col-3span\',\n				\'id\'	=> $event.\'_control\',\n			)\n		);\n\n	$createBlock  = tag(arc_redirect_form(\'\'), \'div\', array(\'class\' => \'txp-control-panel\', \'style\' => \'width: 100%\'));\n	$contentBlock = \'\';\n\n	$paginator = new \\Textpattern\\Admin\\Paginator($event, \'arc_redirect\');\n	$limit = $paginator->getLimit();\n\n	list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n	if ($total < 1) {\n        if ($criteria != 1) {\n    		$contentBlock .= graf(\n    			span(null, array(\'class\' => \'ui-icon ui-icon-info\')).\' \'.\n    			gTxt(\'no_results_found\'),\n    			array(\'class\' => \'alert-block information\')\n    		);\n        }\n	} else {\n		$rs = safe_rows_start(\n			\"arc_redirectID, originalUrl, redirectUrl, statusCode\",\n			\'arc_redirect\',\n			\"$criteria ORDER BY $sort_sql LIMIT $offset, $limit\"\n		);\n\n		$contentBlock .=\n			n.tag_start(\'form\', array(\n				\'class\'  => \'multi_edit_form\',\n				\'id\'	 => \'arc_redirect_form\',\n				\'name\'   => \'longform\',\n				\'method\' => \'post\',\n				\'action\' => \'index.php\',\n			)).\n			n.tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n			n.tag_start(\'table\', array(\'class\' => \'txp-list\')).\n			n.tag_start(\'thead\').\n			tr(\n				hCell(\n					fInput(\'checkbox\', \'select_all\', 0, \'\', \'\', \'\', \'\', \'\', \'select_all\'),\n						\'\', \' class=\"txp-list-col-multi-edit\" scope=\"col\" title=\"\'.gTxt(\'toggle_all_selected\').\'\"\'\n				).\n				column_head(\n					gTxt(\'id\'), \'arc_redirectID\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'arc_redirectID\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-id\'\n				).\n				column_head(\n					gTxt(\'arc_original_url\'), \'originalUrl\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'originalUrl\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-originalurl\'\n				).\n				column_head(\n					gTxt(\'arc_redirect_url\'), \'redirectUrl\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'redirectUrl\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-redirecturl\'\n				).\n				column_head(\n					gTxt(\'type\'), \'statusCode\', \'arc_redirect\', true, $switch_dir, \'\', \'\',\n						((\'statusCode\' == $sort) ? \"$dir \" : \'\').\'txp-list-col-type\'\n				).\n				hCell(\n					gTxt(\'manage\'), \'\', \' class=\"txp-list-col-manage\" scope=\"col\"\'\n				)\n			).\n			n.tag_end(\'thead\').\n			n.tag_start(\'tbody\');\n\n		while ($a = nextRow($rs)) {\n			extract($a, EXTR_PREFIX_ALL, \'redirect\');\n\n			$edit_url = eLink(\'arc_redirect\', \'edit\', \'id\', $redirect_arc_redirectID, gTxt(\'edit\'));\n\n			$test_url = $redirect_originalUrl;\n			if (strpos($test_url, \'/\') === 0) {\n				$test_url = hu . ltrim($test_url, \'/\');\n			}\n			$redirect_url = href(gTxt(\'test_redirect\'), $test_url, \' rel=\"external\" target=\"_blank\"\');\n\n			$contentBlock .= tr(\n				td(\n					fInput(\'checkbox\', \'selected[]\', $redirect_arc_redirectID), \'\', \'txp-list-col-multi-edit\'\n				).\n				hCell(\n					$redirect_arc_redirectID, \'\', \' class=\"txp-list-col-id\" scope=\"row\"\'\n				).\n				td(\n					txpspecialchars($redirect_originalUrl), \'\', \'txp-list-col-originalurl\'\n				).\n				td(\n					txpspecialchars($redirect_redirectUrl), \'\', \'txp-list-col-redirecturl\'\n				).\n				td(\n					$redirect_statusCode == 301 ? gTxt(\'redirect_permanent\') : gTxt(\'redirect_temporary\'), \'\', \'txp-list-col-type\'\n				).\n				td(\n					\"$edit_url <span> | </span> $redirect_url\", \'\', \'txp-list-col-manage\'\n				)\n			);\n            unset($redirect_arc_redirectID);\n		}\n\n		$methods = array(\n			\'delete\' => gTxt(\'delete\')\n		);\n\n		$contentBlock .= n.tag_end(\'tbody\').\n			n.tag_end(\'table\').\n			n.tag_end(\'div\').\n			multi_edit($methods, \'arc_redirect\', \'arc_redirect_multi_edit\').\n			tInput().\n			n.tag_end(\'form\');\n\n		$pageBlock = $paginator->render().\n		nav_form(\'arc_redirect\', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit);\n\n		$table = new \\Textpattern\\Admin\\Table($event);\n		echo $table->render(compact(\'total\', \'criteria\') + array(\'heading\' => \'tab_arc_redirect\'), $searchBlock, $createBlock, $contentBlock, $pageBlock);\n\n	}\n\n}\n\n\n/**\n * Edit redirect.\n *\n * @param string|array $message The activity message\n */\n\nfunction arc_redirect_edit($message=\'\')\n{\n	pagetop(\'arc_redirect\',$message);\n\n	$originalUrl = gps(\'originalUrl\');\n	$redirectUrl = gps(\'redirectUrl\');\n	$statusCode = gps(\'statusCode\');\n\n	if ($id = gps(\'id\')) {\n		$id = doSlash($id);\n		$rs = safe_row(\'originalUrl, redirectUrl, statusCode\', \'arc_redirect\', \"arc_redirectID = $id\");\n		extract($rs);\n	}\n\n	echo hed(gTxt(\'tab_arc_redirect\'), 1, array(\'class\' => \'txp-heading\')).\n         arc_redirect_form($id, $originalUrl, $redirectUrl, $statusCode);\n}\n\n\n/**\n * Add redirect.\n */\n\nfunction arc_redirect_add()\n{\n	$originalUrl = ps(\'originalUrl\');\n	$redirectUrl = ps(\'redirectUrl\');\n\n	if ($originalUrl === \'\' || $redirectUrl === \'\')\n	{\n		arc_redirect_edit(gTxt(\'error_details_incomplete\'));\n		return;\n	}\n\n	$statusCode = ps(\'statusCode\') == 301 ? 301 : 302;\n\n	// Strip final slash from original url\n	$originalUrl = rtrim($originalUrl, \'/\');\n\n	$q = safe_insert(\n		\"arc_redirect\",\n		\"originalUrl = \'\" . trim(doSlash($originalUrl)) . \"\', redirectUrl = \'\" . trim(doSlash($redirectUrl)) . \"\', statusCode = \" . $statusCode\n	);\n\n	if ($q)\n	{\n		$message = gTxt(\'redirect_added\');\n		arc_redirect_list($message);\n	}\n\n	return;\n}\n\n\n/**\n * Saves redirect.\n */\n\nfunction arc_redirect_save()\n{\n	if (!$id=ps(\'id\'))\n	{\n		arc_redirect_list(gTxt(\'error_details_incomplete_no_id\'));\n		return;\n	}\n\n	$originalUrl = ps(\'originalUrl\');\n	$redirectUrl = ps(\'redirectUrl\');\n	$statusCode = ps(\'statusCode\');\n\n	if ($originalUrl == \'\' || $redirectUrl == \'\' || empty($statusCode))\n	{\n		arc_redirect_edit(gTxt(\'error_details_incomplete\'));\n		return;\n	}\n\n	// Strip final slash from original url\n	$originalUrl = rtrim($originalUrl, \'/\');\n\n	$id = doSlash($id);\n\n	$rs = safe_update(\n		\"arc_redirect\",\n		\"originalUrl	= \'\" . trim(doSlash($originalUrl)) . \"\',  redirectUrl = \'\" . trim(doSlash($redirectUrl)) . \"\',  statusCode = \" . trim(doSlash($statusCode)) . \"\",\n		\"arc_redirectID = $id\"\n	);\n\n	if ($rs)\n	{\n		$message = gTxt(\'redirect_updated\');\n		arc_redirect_list($message);\n	}\n	return;\n}\n\n\n/**\n * Processes multi-edit actions.\n */\n\nfunction arc_redirect_multi_edit() {\n	$selected = ps(\'selected\');\n\n	if (!$selected || !is_array($selected)) {\n		arc_redirect_list();\n		return;\n	}\n\n	$method = ps(\'edit_method\');\n	$changed = array();\n\n	$message = \'\';\n\n	switch ($method) {\n		case \'delete\':\n\n			foreach ($selected as $id) {\n				$id = doSlash($id);\n				if (safe_delete(\'arc_redirect\', \'arc_redirectID = \'.$id)) {\n					$changed[] = $id;\n				}\n			}\n			$num = count($changed);\n			if ($num > 1) {\n				$message = $num . \' \' . gTxt(\'redirects_deleted\');\n			} else {\n				$message = gTxt(\'redirect_deleted\');\n			}\n\n			break;\n	}\n\n	arc_redirect_list($message);\n}\n\n\n/**\n * Updates pageby value.\n */\n\nfunction arc_redirect_change_pageby()\n{\n	global $event;\n\n	Txp::get(\'\\Textpattern\\Admin\\Paginator\', $event, \'arc_redirect\')->change();\n	arc_redirect_list();\n}\n\n\n/**\n * Renders an add a redirect form.\n *\n * @return string HTML\n * @access private\n * @see	form()\n */\n\n function arc_redirect_form($id = \'\', $originalUrl = \'\', $redirectUrl = \'\', $statusCode = \'\')\n {\n     // bump back if non-existent ID specified\n     if ($id) {\n         $rs = safe_row(\'arc_redirectID\', \'arc_redirect\', \"arc_redirectID = $id\");\n         if (!$rs) {\n            arc_redirect_list(gTxt(\'error_no_such_redirect_id\'));\n     		return;\n         }\n     }\n\n     // \'edit\' or \'add\' operation\n     $action = ($id != \'\') ? \'save\' : \'add\';\n\n     $statusCodes = array(\n 		301 => gTxt(\'redirect_permanent\'),\n 		302 => gTxt(\'redirect_temporary\')\n 	);\n\n 	return form(\n 		(($action == \"add\") ? hed(gTxt(\'add_new_redirect\'), 2) : hed(gTxt(\'edit_redirect\'), 2)).\n 		inputLabel(\n 			\'originalUrl\',\n 			fInput(\'text\', \'originalUrl\', $originalUrl, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'originalUrl\'),\n 			gTxt(\'arc_original_url\'), \'\', array(\'class\' => \'txp-form-field input-original-url\')\n 		).\n 		inputLabel(\n 			\'redirectUrl\',\n 			fInput(\'text\', \'redirectUrl\', $redirectUrl, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'redirectUrl\'),\n 			gTxt(\'arc_redirect_url\'), \'\', array(\'class\' => \'txp-form-field input-redirect-url\')\n 		).\n 		inputLabel(\n 			\'statusCode\',\n 			selectInput(\'statusCode\', $statusCodes, $statusCode),\n 			gTxt(\'redirect_type\'), \'\', array(\'class\' => \'txp-form-field set-status-code\')\n 		).\n 		graf(\n 			fInput(\'submit\', $action, gTxt($action), \'publish\'),\n 			array(\'class\' => \'txp-edit-actions\', \'style\' => \'text-align: left\')\n 		).\n 		eInput(\'arc_redirect\').\n 		(($id != \'\') ? hInput(\'id\',$id) : \'\').\n 		sInput($action),\n 		\'display:block; margin-left: 0;\', \'\', \'post\', \'txp-edit\', \'\', \'arc_redirect_details\'\n 	);\n\n }\n\n\n/**\n * Installation function – builds MySQL table.\n */\n\nfunction arc_redirect_install()\n{\n	// Create the arc_redirect table if it does not already exist\n    safe_create(\'arc_redirect\', \'\n        arc_redirectID  INT          NOT NULL AUTO_INCREMENT,\n        originalUrl     VARCHAR(255) NOT NULL,\n        redirectUrl     VARCHAR(255) NOT NULL,\n        statusCode      INT          NOT NULL DEFAULT \\\'301\\\',\n        PRIMARY KEY (arc_redirectID)\n    \');\n\n	// Add \'statusCode\' column to an existing legacy \'arc_redirect\' table\n    if (!getRows(\"SHOW COLUMNS FROM \".safe_pfx(\'arc_redirect\').\" LIKE \'statusCode\'\")) {\n        safe_alter(\n            \'arc_redirect\',\n            \"statusCode INT NOT NULL DEFAULT \\\'301\\\'\"\n        );\n    }\n\n	return;\n}\n\n\n/**\n * Uninstall function – deletes MySQL table and related preferences.\n */\n\nfunction arc_redirect_uninstall()\n{\n	// Drop the arc_redirect table\n    safe_drop(\"arc_redirect\");\n    // Remove arc_redirect prefs\n    safe_delete(\'txp_prefs\', \'event = \"arc_redirect\"\');\n}', '50d692abacd1f33cc9b9b50d4351aa42', '#@arc_redirect\r\n#@language en\r\narc_redirect => Redirects\r\ntab_arc_redirect => Redirects\r\narc_original_url => Original URL\r\narc_redirect_url => Redirect URL\r\nredirect_type => Redirect type\r\nredirect_permanent => Permanent\r\nredirect_temporary => Temporary\r\nsearch_redirects => Search redirects\r\nno_redirects_founded => No redirects found\r\nredirect_added => Redirect added\r\nredirect_updated => Redirect updated\r\nadd_new_redirect => Add new redirect\r\nedit_redirect => Edit redirect\r\nredirect_deleted => Redirect deleted\r\nredirects_deleted => redirects deleted\r\nerror_details_incomplete_no_id => No ID# specified – unable to save redirect\r\nerror_details_incomplete => Some details are missing – unable to save redirect\r\nerror_no_such_redirect_id => A redirect with that ID# does not exist. Please try again.\r\ntest_redirect => Test\r\n#@language de-de\r\narc_redirect => Weiterleitungen\r\ntab_arc_redirect => Weiterleitungen\r\narc_original_url => Von URL\r\narc_redirect_url => Ziel URL\r\nredirect_type => Art der Weiterleitung\r\nredirect_permanent => Dauerhaft\r\nredirect_temporary => Vorübergehend\r\nsearch_redirects => Weiterleitungen suchen\r\nno_redirects_founded => Keine Weiterleitungen gefunden\r\nredirect_added => Weiterleitung hinzugefügt\r\nredirect_updated => Weiterleitung aktualisiert\r\nadd_new_redirect => Neue Weiterleitung\r\nedit_redirect => Weiterleitung bearbeiten\r\nredirect_deleted => Weiterleitung gelöscht\r\nredirects_deleted => Weiterleitungen gelöscht\r\nunable_to_add_new_redirect => Neue Weiterleitung konnte nicht hinzugefügt werden\r\nerror_details_incomplete_no_id => ID# fehlt – die Weiterleitung konnte nicht gespeichert werden\r\nerror_details_incomplete => Angaben unvollständing – die Weiterleitung konnte nicht gespeichert werden\r\nerror_no_such_redirect_id => Eine Weiterleitung mit der ID# existiert nicht. Bitte erneuert versuchen!\r\ntest_redirect => Prüfen\r\n', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_pagination', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.5.0', 'Pagination navigation bars for Textpattern', '<h1>etc_pagination</h1>\n\n<p><a href=\"https://github.com/etc-plugins/etc_pagination/releases\">Download</a> | <a href=\"https://packagist.org/packages/etc-plugins/etc_pagination\">Packagist</a></p>\n\n<p>This Textpattern plugin creates a paginated navigation bar. It has a wide variety of attributes &#8211; so you are able to customise it until you drop! It can be used alone for <code>&lt;txp:article /&gt;</code> and <code>&lt;txp:article_custom /&gt;</code> pagination, but it also has the ability to paginate any list, with a little help from <a href=\"http://www.iut-fbleau.fr/projet/etc/index.php?id=3\">etc_query</a>.</p>\n\n<p>Please report bugs and problems with this plugin at <a href=\"https://github.com/etc-plugins/etc_pagination/issues\">the GitHub project&#8217;s issues page</a>.</p>\n\n<p class=\"alert-block information\">Note that this plugin creates a list that is meant to be <em>styled by you</em> using <span class=\"caps\">CSS</span>.</p>\n\n<h2>Installing</h2>\n\n<p>Using <a href=\"https://getcomposer.org\">Composer</a>:</p>\n\n<pre><code>$ composer require etc-plugins/etc_pagination:*</code></pre>\n\n<p>Or download the latest version of the plugin from <a href=\"https://github.com/etc-plugins/etc_pagination/releases\">the GitHub project page</a>, paste the code into the Textpattern Plugins administration panel, install and enable the plugin. Visit the <a href=\"https://forum.textpattern.io/viewtopic.php?id=39302\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h2>Tags</h2>\n\n<h3>etc_pagination tag</h3>\n\n<pre><code>&lt;txp:etc_pagination /&gt;</code></pre>\n\n<p>The <strong>etc_pagination</strong> tag is a <em>single</em> or a <em>container</em> tag that renders the pagination widget <span class=\"caps\">HTML</span> structure.</p>\n\n<p>If used as a container, it must be specified as an opening and closing pair of tags, like this:</p>\n\n<pre><code>&lt;txp:etc_pagination&gt;\n    ...contained statements...\n&lt;/txp:etc_pagination&gt;</code></pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>atts=&quot;value&quot;</code><br />\nAdditional attributes to apply to the <code>wraptag</code> attribute value.</li>\n	<li><code>break=&quot;value&quot;</code><br />\nWhere value is an <span class=\"caps\">HTML</span> element, specified without brackets (e.g., <code>break=&quot;li&quot;</code>) to separate list items.</li>\n	<li><code>class=&quot;class name&quot;</code><br />\n<span class=\"caps\">HTML</span> <code>class</code> to apply to the <code>wraptag</code> attribute value.</li>\n	<li><code>current=&quot;text&quot;</code><br />\nA text active on the current tab.</li>\n	<li><code>delimiter=&quot;value&quot;</code><br />\nA string to use as delimiter in <code>general,current</code> link pairs, see below. Default: <code>,</code> (a comma).</li>\n	<li><code>first=&quot;text&quot;</code><br />\nEnables you to alter the text inside the &#8216;first&#8217; link.</li>\n	<li><code>gap=&quot;text&quot;</code><br />\nOne or two *delimiter*-separated symbols that state that there are more tabs before or after the ones currently viewable. Default: <code>…</code>.</li>\n	<li><code>html_id=&quot;id&quot;</code><br />\nThe <span class=\"caps\">HTML</span> <code>id</code> attribute assigned to the <code>wraptag</code> attribute value.</li>\n	<li><code>last=&quot;text&quot;</code><br />\nEnables you to alter the text inside the &#8216;last&#8217; link.</li>\n	<li><code>link=&quot;text&quot;</code><br />\nEnables you to alter the text in the titles of the page tabs. If two *delimiter*-separated strings are given, then the first one will be used on <code>general</code> pages, and the second one on the <code>current</code> page. Default: <code>{*}</code>, where <code>{*}</code> will be replaced by appropriate tab numbers, see &#8216;Replacements&#8217; section below.</li>\n	<li><code>mask=&quot;value&quot;</code><br />\nIf set, the whole output will be constructed by replacing the patterns <code>{links}</code>, <code>{first}</code>, <code>{last}</code>, <code>{prev}</code>, <code>{next}</code>, <code>{&lt;+}</code> (gap before) and <code>{+&gt;}</code> (gap after) by corresponding strings. Default: unset.</li>\n	<li><code>next=&quot;text&quot;</code><br />\nEnables you to alter the text inside the &#8216;next&#8217; link.</li>\n	<li><code>offset=&quot;number&quot;</code><br />\nPage number offset. Default: <code>0</code>.</li>\n	<li><code>page=&quot;number&quot;</code><br />\nAn integer to be considered as <code>default</code> page (typically <code>1</code>).</li>\n	<li><code>pages=&quot;value&quot;</code><br />\nThe total number of pages, or a range <code>start..end</code>, or a *delimiter*-separated list of <code>page[::title]</code> items. Not needed when paginating <code>&lt;txp:article /&gt;</code> tag (default value).</li>\n	<li><code>pgcounter=&quot;value&quot;</code><br />\nThe <span class=\"caps\">URL</span> parameter to drive the navigation. Not needed when paginating <code>&lt;txp:article /&gt;</code> tag. Default: <code>pg</code>.</li>\n	<li><code>prev=&quot;text&quot;</code><br />\nEnables you to alter the text inside the &#8216;previous&#8217; link.</li>\n	<li><code>query=&quot;a&amp;b=c&quot;</code><br />\nA <code>&amp;</code>-separated list of <span class=\"caps\">GET</span> parameters to be unset/modified in <code>root</code>.</li>\n	<li><code>range=&quot;number&quot;</code><br />\nThe maximum number of left/right neighbours (including gaps) to display. If negative (default), all pages will be displayed. The plugin tries to avoid &#8216;nonsense&#8217; gaps like <code>1 … 3 4</code> and adjust the output so that the number of displayed tabs is <code>2*range+1</code>.</li>\n	<li><code>reversenumberorder=&quot;number&quot;</code><br />\nMakes it possible to reverse the numbers in the tabs. Setting to value to <code>0</code> (default) renders <code>1,2,3,...</code>, setting value to <code>1</code> reverses the link numbers, <code>2</code> reverses theirs hrefs, and <code>3</code> reverses both numbers and hrefs.</li>\n	<li><code>root=&quot;URL&quot;</code><br />\nThe <span class=\"caps\">URL</span> to be used as base for navigation, defaults to the current page <span class=\"caps\">URL</span>.</li>\n	<li><code>scale=&quot;1&quot;</code><br />\nAn integer to be used as grid for &#8216;gap&#8217; links.</li>\n	<li><code>wraptag=&quot;element&quot;</code><br />\n<span class=\"caps\">HTML</span> element to wrap (markup) block, specified without brackets (e.g., <code>wraptag=&quot;ul&quot;</code>).</li>\n</ul>\n\n<h4>Replacements</h4>\n\n<p>If you are not happy with the default <code>&lt;a&gt;</code> links, use <code>&lt;etc_pagination /&gt;</code> as container to construct your own links. The following replacement tokens are available inside <code>etc_pagination</code>:</p>\n\n<ul>\n	<li><code>{$}</code><br />\nThe absolute page number.</li>\n	<li><code>{#}</code><br />\nThe displayed page number.</li>\n	<li><code>{*}</code><br />\nThe page title.</li>\n	<li><code>{current}</code><br />\nThe text given by <code>current</code> attribute, enabled only on the current tab.</li>\n	<li><code>{href}</code><br />\nThe page <span class=\"caps\">URL</span>.</li>\n	<li><code>{link}</code><br />\nThe text given by <code>link</code> attribute, replaced by <code>first</code>, <code>prev</code>, etc when necessary.</li>\n	<li><code>{pages}</code><br />\nThe total pages number.</li>\n	<li><code>{rel}</code><br />\nThe page relation (<code>next</code>, <code>prev</code>).</li>\n</ul>\n\n<p>For example, the following (container tag) will generate a <code>&lt;select&gt;</code> pagination list:</p>\n\n<pre><code>&lt;txp:etc_pagination link=&quot;Page {*}&quot; current=&quot;selected&quot; wraptag=&quot;select&quot; atts=&#39;name=&quot;pg&quot;&#39;&gt;\n    &lt;option value=&quot;{*}&quot; {current}&gt;{link}&lt;/option&gt;\n&lt;/txp:etc_pagination</code></pre>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:etc_pagination range=&quot;2&quot; prev=&quot;Previous&quot; next=&quot;Next&quot;  wraptag=&quot;ul&quot; break=&quot;li&quot; /&gt;</code></pre>\n\n<p>This example outputs, if there are ten pages and we are on the third one, like so:</p>\n\n<pre><code>&lt;ul&gt;\n    &lt;li&gt;\n        &lt;a href=&quot;https://example.com/blog/?pg=2&quot; rel=&quot;prev&quot;&gt;Previous&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;a href=&quot;https://example.com/blog/&quot;&gt;1&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;a href=&quot;https://example.com/blog/?pg=2&quot;&gt;2&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;span data-rel=&quot;current&quot;&gt;3&lt;/span&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;span data-rel=&quot;gap&quot;&gt;…&lt;/span&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;a href=&quot;https://example.com/blog/?pg=10&quot;&gt;10&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;li&gt;\n        &lt;a href=&quot;https://example.com/blog/?pg=4&quot; rel=&quot;next&quot;&gt;Next&lt;/a&gt;\n    &lt;/li&gt;\n&lt;/ul&gt;</code></pre>\n\n<p>The <code>&lt;a&gt;</code> and <code>&lt;span&gt;</code> tags linking to <code>first</code>, <code>prev</code>, <code>current</code>, <code>next</code>, <code>last</code>, <code>gap</code> pages, will be given the corresponding value of <code>rel</code> or <code>data-rel</code> attributes.</p>\n\n<h5>Example 2</h5>\n\n<pre><code>&lt;txp:etc_pagination range=&quot;0&quot;&gt;\n    &lt;p&gt;Page {*} of {pages}&lt;/p&gt;\n&lt;/txp:etc_pagination&gt;</code></pre>\n\n<p>This example outputs, if there are eight pages and we are on the sixth one, like so:</p>\n\n<pre><code>&lt;p&gt;Page 6 of 8&lt;/p&gt;</code></pre>\n\n<h5>Example 3</h5>\n\n<pre><code>&lt;txp:etc_pagination wraptag=&quot;nav&quot; class=&quot;paginator&quot; range=&quot;3&quot; atts=&#39;aria-label=&quot;Blog navigation&quot;&#39;\n    prev=&#39;&lt;a class=&quot;prev&quot; rel=&quot;prev&quot; href=&quot;https://example.com{href}&quot; title=&quot;Go to previous page&quot; aria-label=&quot;Go to previous page&quot;&gt;Prev&lt;/a&gt;,\n          &lt;span class=&quot;prev disabled&quot; aria-label=&quot;This is the first page&quot;&gt;Prev&lt;/span&gt;&#39;\n    next=&#39;&lt;a class=&quot;next&quot; rel=&quot;next&quot; href=&quot;https://example.com{href}&quot; title=&quot;Go to next page&quot; aria-label=&quot;Go to next page&quot;&gt;Next&lt;/a&gt;,\n          &lt;span class=&quot;next disabled&quot; aria-label=&quot;This is the last page&quot;&gt;Next&lt;/span&gt;&#39;\n    link=&#39;&lt;li&gt;&lt;a href=&quot;https://example.com{href}&quot; title=&quot;Go to page {*}&quot; aria-label=&quot;Go to page {*}&quot;&gt;{*}&lt;/a&gt;&lt;/li&gt;,\n          &lt;li class=&quot;current&quot;&gt;&lt;b title=&quot;Current page&quot; aria-label=&quot;Current page&quot;&gt;{*}&lt;/b&gt;&lt;/li&gt;&#39;\n    gap=&#39;&lt;li&gt;&lt;span title=&quot;More pages&quot; aria-label=&quot;More pages&quot;&gt;…&lt;/span&gt;&lt;/li&gt;&#39;\n    mask=&#39;{prev}{next}\n    &lt;ul class=&quot;pagination&quot;&gt;\n        {first}{&lt;+}{links}{+&gt;}{last}\n    &lt;/ul&gt;&#39;\n    /&gt;</code></pre>\n\n<p>Fully customised <span class=\"caps\">HTML</span> solutions can be achieved &#8211; this example outputs, if there are eleven pages and we are on the fifth one, like so:</p>\n\n<pre><code>&lt;nav class=&quot;paginator&quot; aria-label=&quot;Blog navigation&quot;&gt;\n    &lt;a class=&quot;prev&quot; rel=&quot;prev&quot; href=&quot;https://example.com/blog/?pg=4&quot; title=&quot;Go to previous page&quot; aria-label=&quot;Go to previous page&quot;&gt;Prev&lt;/a&gt;\n    &lt;a class=&quot;next&quot; rel=&quot;next&quot; href=&quot;https://example.com/blog/?pg=6&quot; title=&quot;Go to next page&quot; aria-label=&quot;Go to next page&quot;&gt;Next&lt;/a&gt;\n    &lt;ul class=&quot;pagination&quot;&gt;\n        &lt;li&gt;\n            &lt;a href=&quot;https://example.com/blog/&quot; title=&quot;Go to page 1&quot; aria-label=&quot;Go to page 1&quot;&gt;1&lt;/a&gt;\n        &lt;/li&gt;\n        &lt;li&gt;\n            &lt;span title=&quot;More pages&quot; aria-label=&quot;More pages&quot;&gt;…&lt;/span&gt;\n        &lt;/li&gt;\n        &lt;li&gt;\n            &lt;a href=&quot;https://example.com/blog/?pg=4&quot; title=&quot;Go to page 4&quot; aria-label=&quot;Go to page 4&quot;&gt;4&lt;/a&gt;\n        &lt;/li&gt;\n        &lt;li class=&quot;current&quot;&gt;\n            &lt;b title=&quot;Current page&quot; aria-label=&quot;Current page&quot;&gt;5&lt;/b&gt;\n        &lt;/li&gt;\n        &lt;li&gt;\n            &lt;a href=&quot;https://example.com/blog/?pg=6&quot; title=&quot;Go to page 6&quot; aria-label=&quot;Go to page 6&quot;&gt;6&lt;/a&gt;\n        &lt;/li&gt;\n        &lt;li&gt;\n            &lt;span title=&quot;More pages&quot; aria-label=&quot;More pages&quot;&gt;…&lt;/span&gt;\n        &lt;/li&gt;\n        &lt;li&gt;\n            &lt;a href=&quot;https://example.com/blog/?pg=11&quot; title=&quot;Go to page 11&quot; aria-label=&quot;Go to page 11&quot;&gt;11&lt;/a&gt;\n        &lt;/li&gt;\n    &lt;/ul&gt;\n&lt;/nav&gt;</code></pre>\n\n<h3>etc_numpages tag</h3>\n\n<pre><code>&lt;txp:etc_numpages /&gt;</code></pre>\n\n<p>The <strong>etc_numpages</strong> tag is a <em>single</em> helper tag that counts the number of pages in various lists (articles, images, links, &#8230;).</p>\n\n<p>This tag is typically used as a <strong>pages</strong> attribute value, passed to <strong>etc_pagination</strong>, like this:</p>\n\n<pre><code>&lt;txp:etc_pagination pages=&#39;&lt;txp:numpages section=&quot;example&quot; /&gt;&#39; /&gt;</code></pre>\n\n<h4>Attributes</h4>\n\n<p>Most attributes come from <code>&lt;txp:article /&gt;</code> and other Textpattern list tags:</p>\n\n<ul>\n	<li><code>limit=&quot;10&quot;</code><br />\nThe default value of <strong>pageby</strong>, see below.</li>\n	<li><code>offset=&quot;0&quot;</code><br />\nThe number of items to exclude from the beginning of the list.</li>\n	<li><code>pageby=&quot;number&quot;</code><br />\nThe maximum number of items per page.</li>\n	<li><code>table=&quot;txp_table&quot;</code><br />\nA name of Textpattern database table from which the list will be extracted. Default: <code>textpattern</code> (articles).</li>\n	<li><code>total=&quot;number&quot;</code><br />\nAn optional list length, if known.</li>\n	<li><code>author</code>, <code>category</code>, <code>excerpted</code>, <code>exclude</code>, <code>expired</code>, <code>id</code>, <code>include</code>, <code>keywords</code>, <code>month</code>, <code>realname</code>, <code>section</code>, <code>status</code>, <code>time</code><br />\nThese attributes have the same function as in <a href=\"http://www.textpattern.net/wiki/index.php?title=article_custom\">txp:article_custom</a> and other <a href=\"http://www.textpattern.net/wiki/index.php?title=Category:List_Tags\">list tags</a>.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n    &lt;txp:etc_pagination pgcounter=&quot;page&quot; pages=&#39;&lt;txp:etc_numpages section=&quot;news&quot; /&gt;&#39; /&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>This example shows pagination on individual article pages within the <code>news</code> section (i.e., <strong>not</strong> on an article list context page).</p>\n\n<h3>etc_offset tag</h3>\n\n<pre><code>&lt;txp:article_custom offset=&#39;&lt;txp:etc_offset pgcounter=&quot;page&quot; pageby=&quot;5&quot; /&gt;&#39; limit=&quot;5&quot; /&gt;</code></pre>\n\n<p>Used as helper tag to calculate the offset of article|image|&#8230; listing tags. The example above sets the offset to <code>15</code> on a page with <code>...&amp;page=4</code> <span class=\"caps\">URL</span>, so <code>&lt;txp:article_custom /&gt;</code> will display the fourth page of 5 articles.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>pageby=&quot;10&quot;</code><br />\nThe number of items per page.</li>\n	<li><code>pgcounter=&quot;pg&quot;</code><br />\nThe <span class=\"caps\">URL</span> variable to be considered as page number.</li>\n	<li><code>offset=&quot;0&quot;</code><br />\nAn optional page offset.</li>\n	<li><code>type=&quot;value|page|start|end&quot;</code><br />\nOutputs either the raw (escaped) <strong>value</strong> of <code>pgcounter</code>, or the calculated <strong>page</strong> number, or the <strong>start</strong> page item number, or the <strong>end</strong> page item number.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>Showing articles &lt;txp:etc_offset type=&quot;start&quot; /&gt; to &lt;txp:etc_offset type=&quot;end&quot; /&gt;</code></pre>\n\n<p>Outputs <code>Showing articles 21 to 30</code> on the third page of the standard <code>&lt;txp:article /&gt;</code> list.</p>\n\n<h2>Authors/credits</h2>\n\n<p>Written by <a href=\"http://www.iut-fbleau.fr/projet/etc/\">Oleg Loukianov</a>. Many thanks to <a href=\"https://github.com/etc-plugins/etc_pagination/graphs/contributors\">all additional contributors</a>.</p>', '// TXP 4.6 tag registration\n	if(class_exists(\'\\Textpattern\\Tag\\Registry\')) Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'etc_pagination\')\n		->register(\'etc_numpages\')\n		->register(\'etc_offset\')\n	;\n\nif (@txpinterface == \'public\') {\n	register_callback(\'etc_pagination_url\', \'pretext\');\n}\n\nfunction etc_pagination_url($event, $step) {\n	global $etc_pagination;\n	$etc_pagination[\'url\'] = preg_replace(\"|^https?://[^/]+|i\",\"\",serverSet(\'REQUEST_URI\'));\n}\n\nfunction etc_pagination($atts, $thing=\'\') {\n	global $thispage, $etc_pagination;\n\n	extract(lAtts(array(\n		\"root\"=>null,\n		\"query\"=>\'\',\n		\"pages\"=>null,\n//		\"links\"=>null,\n		\"page\"=>null,\n		\"pgcounter\"=>\'pg\',\n		\"offset\"=>0,\n		\"range\"=>-1,\n		\"scale\"=>1,\n		\"mask\"=>null,\n		\"link\"=>\'{*}\',\n		\"current\"=>\'\',\n		\"next\"=>\'\',\n		\"prev\"=>\'\',\n		\"first\"=>null,\n		\"last\"=>null,\n		\"gap\"=>\'&hellip;\',\n		\"delimiter\"=>\',\',\n		\"wraptag\"=>\'\',\n		\"break\"=>\'\',\n		\"class\"=>\'\',\n		\"html_id\"=>\'\',\n		\"atts\"=>\'\',\n		\"reversenumberorder\"=>0\n	),$atts));\n\n	$etc_pagination[\'pgcounter\'] = $pgcounter;\n	$cpages = 0;\n	if(!isset($pages)) {\n		$numberOfTabs = isset($thispage[\'numPages\']) ? $thispage[\'numPages\'] : 1;\n		$pages = $numberOfTabs > 1 ? range(1, $numberOfTabs) : null;\n	}\n	elseif(strpos($pages, $delimiter) !== false) {\n		$cpages = (strpos($pages, \'::\') !== false ? 2 : 1);\n		$numberOfTabs = count($pages = do_list($pages, $delimiter));\n	}\n	elseif(strpos($pages, \'..\') !== false) {\n		$cpages = 1;\n		list($start, $end) = do_list($pages, \'..\');\n		$numberOfTabs = count($pages = range($start, $end));\n	} else {\n		$numberOfTabs = intval($pages);\n		$pages = range(1, $numberOfTabs);\n	}\n\n	if($numberOfTabs <= 1) return parse(EvalElse($thing, false));\n\n	# if we got tabs, start the outputting\n	$reversenumberorder = (int)$reversenumberorder;\n	if(isset($links)) $links = array_pad(do_list($links, $delimiter), $numberOfTabs, \'\');\n	elseif($cpages < 2) switch($reversenumberorder) {\n		case 1 : case 2 : $links = array_reverse($pages); break;\n		default : $links = &$pages;\n	}\n	else $links = array();\n\n	$range = (int) $range;\n	if($range < 0) $range = $numberOfTabs; else $range += 1;\n	if($scale == \'auto\') $scale = pow($numberOfTabs, 1/$range);\n	else $scale = max(floatval($scale), 1);\n\n	$out = $parts = array();\n	$fragment = \'\';\n	if($root === \'\') $hu = hu;\n	elseif($root === null || $root[0] === \'#\') {$hu = strtok($etc_pagination[\'url\'], \'?\'); $parts = $_GET; if($root) $fragment = $root;}\n	else {\n		$qs = parse_url($root);\n		if(isset($qs[\'fragment\'])) $root = str_replace($fragment = \'#\'.$qs[\'fragment\'], \'\', $root);\n		if(!empty($qs[\'query\'])) parse_str(str_replace(\'&amp;\', \'&\', $qs[\'query\']), $parts);\n		$hu = strtok($root, \'?\');\n	}\n\n	if($query) foreach(do_list($query, \'&\') as $qs) {\n		@list($k, $v) = explode(\'=\', $qs, 2);\n		if(!isset($v)) if($k === \'?\') $parts = array();\n			elseif($k === \'#\') $fragment = \'\';\n			else unset($parts[$k]);\n		else if($k === \'#\') $fragment = \'#\'.$v;\n			elseif($k === \'+\') $hu .= $v;\n			elseif($k[0] === \'/\') $hu = preg_replace($k, $v, $hu);\n			else $parts[$k] = $v;\n	}\n\n	if(isset($page))\n		if(!$cpages) $pgdefault = intval($page);\n		elseif($cpages == 1)\n			if(($pgdefault = array_search($page, $pages)) !== false) $pgdefault++;\n			else $pgdefault = 0;\n		else for($pgdefault = $numberOfTabs; $pgdefault > 0 && strpos($pages[$pgdefault-1].\'::\', $page.\'::\') !== 0; $pgdefault--);\n	else $pgdefault = $reversenumberorder & 1 ? $numberOfTabs : 1;\n	if(isset($parts[$pgcounter]))\n		if($cpages < 2)\n			if(($page = array_search($parts[$pgcounter], $links)) !== false) $page++;\n			else $page = 0;\n		else for($page = $numberOfTabs; $page > 0 && strpos($pages[$page-1].\'::\', $parts[$pgcounter].\'::\') !== 0; $page--);\n	else $page = $pgdefault;\n	$etc_pagination[\'page\'] = $page;\n	$page += $offset;\n	if($page < 1 || $page > $numberOfTabs) return parse(EvalElse($thing, 0));\n\n	unset($parts[$pgcounter]);\n	$qs = array();//join_qs($parts);\n	foreach($parts as $k => $v) $qs[] = urlencode($k) . \'=\' . urlencode(is_array($v) ? implode(\',\', $v) : $v);\n	$qs = \'?\'.implode(\'&amp;\', $qs);\n	$pagebase = $qs !== \'?\' ? $hu.$qs : $hu;\n	if($qs !== \'?\') $qs .= \'&amp;\';\n	$pageurl = $pgcounter ? $hu.$qs.$pgcounter.\'=\' : \'\';\n\n	$currentclass = (empty($thing) && $current && strpos($link, \'{current}\') === false ? ($break ? 1 : -1) : 0);\n\n	@list($gap1, $gap2) = explode($delimiter, $gap); if(!isset($gap2)) $gap2 = $gap1;\n	@list($link, $link_) = explode($delimiter, $link, 2); if(!isset($link_)) $link_ = $link;\n	foreach(array(\'first\', \'prev\', \'next\', \'last\', \'current\') as $item) if(isset($$item))\n		{@list($$item, ${$item.\'_\'}) = explode($delimiter, $$item, 2); if(!isset(${$item.\'_\'})) ${$item.\'_\'} = \'\';}\n	if($currentclass) {if($current) $current = \" class=\'$current\'\"; if($current_) $current_ = \" class=\'$current_\'\";}\n\n	$skip1 = $range < 3 ? $range : 1 + ($gap1 ? 1 : 0) + (isset($first) ? 0 : 1);\n	$skip2 = $range < 3 ? $range : 1 + ($gap2 ? 1 : 0) + (isset($last) ? 0 : 1);\n	if($numberOfTabs < 2*$range) {$loopStart = 1; $loopEnd = $numberOfTabs;}\n	elseif($page <= $range) {$loopStart = 1; $loopEnd = 2*$range - $skip2;}\n	elseif($page > $numberOfTabs - $range) {$loopStart = $numberOfTabs - 2*$range + $skip1 + 1; $loopEnd = $numberOfTabs;}\n	else {$loopStart = $page - $range + $skip1; $loopEnd = $page + $range - $skip2;}\n\n	if($custom = isset($mask)) {\n		if(isset($thing)) {$link = str_replace(\'{link}\', $link, $thing); $link_ = str_replace(\'{link}\', $link_, $thing);}\n		$thing = $mask;\n	}\n	elseif(!isset($thing)) {\n		if($link) $link = \'<a href=\"{href}\" data-rel=\"{rel}\"\'.($currentclass < 0 ? \'{current}\' : \'\').\'>\'.$link.\'</a>\';\n		if($link_) $link_ = \'<span data-rel=\"self\"\'.($currentclass < 0 ? \'{current}\' : \'\').\'>\'.$link_.\'</span>\';\n		foreach(array(\'first\', \'prev\', \'next\', \'last\', \'gap1\', \'gap2\') as $item) {\n			if(!empty($$item)) $$item = \'<a href=\"{href}\" rel=\"{rel}\" title=\"{*}\">\'.$$item.\'</a>\';\n			if(!empty(${$item.\'_\'})) ${$item.\'_\'} = \'<span data-rel=\"\'.$item.\'\">\'.${$item.\'_\'}.\'</span>\';\n		}\n		$thing = \'{link}\';\n	}\n	else $thing = EvalElse($thing, 1);\n	$replacements = array_fill_keys(array(\'{*}\', \'{#}\', \'{$}\', \'{href}\', \'{rel}\', \'{link}\'), \'\');\n	$replacements[\'{pages}\'] = $numberOfTabs;\n	$replacements[\'{current}\'] = $current_;\n	$mask = array_fill_keys(array(\'{links}\', \'{first}\', \'{prev}\', \'{next}\', \'{last}\', \'{<+}\', \'{+>}\'), \'\');\n\n	$outfirst = $outprev = $outgap = \'\';\n	if($prev || $prev_) {\n		etc_pagination_link($replacements, $links, $pages, $page-1, $pgdefault, $pagebase, $pageurl, $fragment, \'prev\', $cpages>1);\n		if($page <= 1) $replacements[\'{#}\'] = $replacements[\'{*}\'] = \'\';\n		$replacements[\'{link}\'] = $mask[\'{prev}\'] = strtr($page > 1 ? $prev : $prev_, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outprev = strtr($thing, $replacements);\n	}\n\n	if($loopStart > 1 && $range > 1 || isset($first)) {\n		etc_pagination_link($replacements, $links, $pages, 1, $pgdefault, $pagebase, $pageurl, $fragment, \'\', $cpages>1);\n		$replacements[\'{link}\'] = $mask[\'{first}\'] = strtr(isset($first) ? ($page > 1 ? $first : $first_) : $link, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outfirst = strtr($thing, $replacements);\n		if($gap1 && $loopStart > 1) {\n//			if($custom) $mask[\'{<+}\'] = $gap1; else $outgap = $gap1;\n			$i = $loopStart-$range-1+$skip2;\n			if($scale > 1 && $i >= $scale) {\n				$n = pow($scale, min(floor(log($i, $scale)), ceil(log($numberOfTabs - $i + 1, $scale)))); $i = intval(floor($i/$n)*$n);\n			}\n			$i = max($range ? 2 : 1, $i);\n			etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, \'prev\', $cpages>1);\n			if($replacements[\'{link}\'] = strtr($gap1, $replacements))\n				if($custom) $mask[\'{<+}\'] = $replacements[\'{link}\']; else $outgap = strtr($thing, $replacements);\n		}\n	}\n\n	if($first) {\n		if($outfirst) $out[] = $outfirst; if($outprev) $out[] = $outprev;\n	} else {\n		if($outprev) $out[] = $outprev; if($outfirst) $out[] = $outfirst;\n	}\n	if($outgap) $out[] = $outgap;\n\n	if($link || $link_) for($i=$loopStart; $i<=$loopEnd; $i++) {\n		etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, $i == $page-1 ? \'prev\' : ($i == $page+1 ? \'next\' : \'\'), $cpages>1);\n		$self = $i == $page;\n		$replacements[\'{current}\'] = $self ? $current : $current_;\n		if($replacements[\'{link}\'] = strtr($self ? $link_ : $link, $replacements))\n			if($custom) $mask[\'{links}\'] .= $replacements[\'{link}\'];\n			else $out[] = ($currentclass > 0 ? ($self ? \'{current}\' : \'{current_}\') : \'\').strtr($thing, $replacements);\n	}\n\n	$outlast = $outnext = $outgap = \'\';\n	$replacements[\'{current}\'] = $current_;\n	if($loopEnd < $numberOfTabs && $range > 1 || isset($last)) {\n		etc_pagination_link($replacements, $links, $pages, $numberOfTabs, $pgdefault, $pagebase, $pageurl, $fragment, \'\', $cpages>1);\n		$replacements[\'{link}\'] = $mask[\'{last}\'] = strtr(isset($last) ? ($page < $numberOfTabs ? $last : $last_) : $link, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outlast = strtr($thing, $replacements);\n		if($gap2 && $loopEnd < $numberOfTabs) {\n//			if($custom) $mask[\'{+>}\'] = $gap2; else $outgap = $gap2;\n			$i = $loopEnd+$range+1-$skip1;\n			if($scale > 1) {\n				$n = pow($scale, floor(log($numberOfTabs, $scale)));\n				$nt = ceil($numberOfTabs/$n)*$n;\n				if($nt >= $scale + $i - 1) {\n					$n = pow($scale, min(floor(log($nt - $i + 1, $scale)), ceil(log($i, $scale))));\n					do {\n						$j = intval($nt - floor(($nt - $i + 1)/$n)*$n);\n						$n /= $scale;\n					} while($j >= $numberOfTabs && $n >= 1);\n					$i = $j;\n				}\n			}\n			$i = min($range ? $numberOfTabs-1 : $numberOfTabs, $i);\n\n			etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, \'next\', $cpages>1);\n			if($replacements[\'{link}\'] = strtr($gap2, $replacements))\n				if($custom) $mask[\'{+>}\'] = $replacements[\'{link}\']; else $outgap = strtr($thing, $replacements);\n\n		}\n	}\n\n	if($next || $next_) {\n		etc_pagination_link($replacements, $links, $pages, $page+1, $pgdefault, $pagebase, $pageurl, $fragment, \'next\', $cpages>1);\n		if($page >= $numberOfTabs) $replacements[\'{#}\'] = $replacements[\'{*}\'] = \'\';\n		$replacements[\'{link}\'] = $mask[\'{next}\'] = strtr($page < $numberOfTabs ? $next : $next_, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outnext = strtr($thing, $replacements);\n	}\n\n	if($outgap) $out[] = $outgap;\n	if($last) {\n		if($outnext) $out[] = $outnext; if($outlast) $out[] = $outlast;\n	} else {\n		if($outlast) $out[] = $outlast; if($outnext) $out[] = $outnext;\n	}\n\n	if($atts) $atts = \' \'.$atts;\n	if($custom) $out = array(strtr($thing, $mask));\n	if($reversenumberorder & 1) $out = array_reverse($out);\n	$out = doWrap($out, $wraptag, $break, $class, \'\', $atts, \'\', $html_id);\n	if($currentclass > 0) $out = str_replace(array(\"<$break>{current}\", \"<$break>{current_}\"), array(\"<{$break}{$current}>\", \"<{$break}{$current_}>\"), $out);\n	return parse($out);\n}\n\nfunction etc_pagination_link(&$replacements, $links, $pages, $page, $pgdefault, $pagebase, $pageurl, $fragment, $rel, $custom) {\n		if (isset($pages[$page-1])) if($custom) @list($replacements[\'{#}\'], $replacements[\'{*}\']) = explode(\'::\', $pages[$page-1].\'::\'.$pages[$page-1]);\n			else {$replacements[\'{*}\'] = $pages[$page-1]; $replacements[\'{#}\'] = $links[$page-1];}\n		$replacements[\'{$}\'] = $page;\n//		if($reversenumberorder) $replacements[\'{$}\'] = $numberOfTabs-$replacements[\'{$}\']+1;\n		$replacements[\'{href}\'] = ($replacements[\'{$}\'] == $pgdefault ? $pagebase : $pageurl.$replacements[\'{#}\']).$fragment;\n		$replacements[\'{rel}\'] = $rel;\n}\n\n\n// -------------------------------------------------------------\n	function etc_numpages($atts)\n	{\n		global $pretext, $prefs, $thispage, $etc_pagination, $etc_pagination_total;\n		if(empty($atts) && isset($thispage)) {$etc_pagination_total = $etc_pagination[\'total\'] = $thispage[\'total\']; return empty($thispage[\'numPages\']) ? 1 : $thispage[\'numPages\'];}\n		extract($pretext);\n		if(empty($atts[\'table\']) && !isset($atts[\'total\'])) {\n			$customFields = getCustomFields();\n			$customlAtts = array_null(array_flip($customFields));\n		} else $customFields = $customlAtts = array();\n\n		//getting attributes\n		extract(lAtts(array(\n			\'table\'     => \'\',\n			\'total\'     => null,\n			\'limit\'     => 10,\n			\'pageby\'    => \'\',\n			\'category\'  => \'\',\n			\'section\'   => \'\',\n			\'exclude\'   => \'\',\n			\'include\'   => \'\',\n			\'excerpted\' => \'\',\n			\'author\'    => \'\',\n			\'realname\'  => \'\',\n			\'month\'     => \'\',\n			\'keywords\'  => \'\',\n			\'expired\'   => $prefs[\'publish_expired_articles\'],\n			\'id\'        => \'\',\n			\'time\'      => \'past\',\n			\'status\'    => \'4\',\n			\'offset\'    => 0\n		)+$customlAtts, $atts));\n\n		if(!($pageby = intval(empty($pageby) ? $limit : $pageby))) return 0;\n		$etc_pagination[\'pageby\'] = $pageby;\n		if(isset($total)) return ceil(intval($total)/$pageby);\n\n		$where = array(\"1\");\n\n		//Building query parts\n		$category  = join(\"\',\'\", doSlash(do_list($category)));\n		if($category) $where[] = !$table ? \"(Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\" : \"category IN (\'\".$category.\"\')\";\n		if($author) $where[] = (!$table ? \"AuthorID\" : \"author\").\" IN (\'\".join(\"\',\'\", doSlash(do_list($author))).\"\')\";\n		if($id) $where[] = \"ID IN (\".join(\',\', array_map(\'intval\', do_list($id))).\")\";\n		if($status && (!$table || $table == \'file\')) $where[] = \'Status in(\'.implode(\',\', doSlash(do_list($status))).\')\';\n		if ($realname) {\n			$authorlist = safe_column(\'name\', \'txp_users\', \"RealName IN (\'\". join(\"\',\'\", doArray(doSlash(do_list($realname)), \'urldecode\')) .\"\')\" );\n			$where[] = (!$table ? \"AuthorID\" : \"author\").\" IN (\'\".join(\"\',\'\", doSlash($authorlist)).\"\')\";\n		}\n		if(!$table) {\n			if($section) $where[] = \"Section IN (\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\";\n			if($month) $where[] = \"Posted like \'\".doSlash($month).\"%\'\";\n			if($excerpted==\'y\' || $excerpted==\'1\') $where[] = \"Excerpt !=\'\'\";\n			switch ($time) {\n				case \'past\':\n					$where[] = \"Posted <= now()\"; break;\n				case \'future\':\n					$where[] = \"Posted > now()\"; break;\n			}\n			if (!$expired) {\n				$where[] = \"(now() <= Expires or Expires IS NULL)\";\n			}\n			//Allow keywords for no-custom articles. That tagging mode, you know\n			if ($keywords) {\n				$keys = doSlash(do_list($keywords));\n				foreach ($keys as $key) {\n					$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n				}\n				$where[] = \"(\" . join(\' or \',$keyparts) . \")\";\n			}\n		} else {\n			if($include) $where[] = \"name IN (\'\".join(\"\',\'\", doSlash(do_list($include))).\"\')\";\n			if($exclude) $where[] = \"name NOT IN (\'\".join(\"\',\'\", doSlash(do_list($exclude))).\"\')\";\n		}\n\n		$customq = \'\';\n		if ($customFields) {\n			foreach($customFields as $cField) {\n				if (isset($atts[$cField]))\n					$customPairs[$cField] = $atts[$cField];\n			}\n			if(!empty($customPairs)) {\n				$customq = buildCustomSql($customFields,$customPairs);\n			}\n		}\n\n//		$where = \"1=1\" . $statusq. $time. $search . $id . $category . $section . $excerpted . $month . $author . $keywords . $customq;\n		$where = implode(\' AND \', $where) . $customq;\n\n		//paginate\n		$grand_total = safe_count($table ? \'txp_\'.$table : \'textpattern\', $where);\n		$etc_pagination_total = $etc_pagination[\'total\'] = $grand_total - $offset;\n		return ceil($etc_pagination[\'total\']/$pageby);\n	}\n\n	function etc_offset($atts)\n	{\n		global $etc_pagination;\n		//getting attributes\n		extract(lAtts((empty($etc_pagination) ? array() : $etc_pagination) + array(\n			\'type\'      => \'\',\n			\'pageby\'    => \'10\',\n			\'pgcounter\' => \'pg\',\n			\'offset\'    => \'0\'\n		), $atts));\n\n		if(!empty($etc_pagination)) extract($etc_pagination);\n		$counter = isset($page) ? $page : urldecode(gps($pgcounter));\n		$page = max(intval($counter), 1) + $offset;\n		$max = isset($total) ? $total : $page*$pageby;\n		switch($type) {\n			case \'value\' : return htmlspecialchars($counter, ENT_QUOTES);\n			case \'page\' : return $page;\n			case \'start\' : return min($max, ($page - 1)*$pageby + 1);\n			case \'end\' : return min($max, $page*$pageby);\n			default : return ($page - 1)*$pageby;\n		}\n	}', '// TXP 4.6 tag registration\n	if(class_exists(\'\\Textpattern\\Tag\\Registry\')) Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'etc_pagination\')\n		->register(\'etc_numpages\')\n		->register(\'etc_offset\')\n	;\n\nif (@txpinterface == \'public\') {\n	register_callback(\'etc_pagination_url\', \'pretext\');\n}\n\nfunction etc_pagination_url($event, $step) {\n	global $etc_pagination;\n	$etc_pagination[\'url\'] = preg_replace(\"|^https?://[^/]+|i\",\"\",serverSet(\'REQUEST_URI\'));\n}\n\nfunction etc_pagination($atts, $thing=\'\') {\n	global $thispage, $etc_pagination;\n\n	extract(lAtts(array(\n		\"root\"=>null,\n		\"query\"=>\'\',\n		\"pages\"=>null,\n//		\"links\"=>null,\n		\"page\"=>null,\n		\"pgcounter\"=>\'pg\',\n		\"offset\"=>0,\n		\"range\"=>-1,\n		\"scale\"=>1,\n		\"mask\"=>null,\n		\"link\"=>\'{*}\',\n		\"current\"=>\'\',\n		\"next\"=>\'\',\n		\"prev\"=>\'\',\n		\"first\"=>null,\n		\"last\"=>null,\n		\"gap\"=>\'&hellip;\',\n		\"delimiter\"=>\',\',\n		\"wraptag\"=>\'\',\n		\"break\"=>\'\',\n		\"class\"=>\'\',\n		\"html_id\"=>\'\',\n		\"atts\"=>\'\',\n		\"reversenumberorder\"=>0\n	),$atts));\n\n	$etc_pagination[\'pgcounter\'] = $pgcounter;\n	$cpages = 0;\n	if(!isset($pages)) {\n		$numberOfTabs = isset($thispage[\'numPages\']) ? $thispage[\'numPages\'] : 1;\n		$pages = $numberOfTabs > 1 ? range(1, $numberOfTabs) : null;\n	}\n	elseif(strpos($pages, $delimiter) !== false) {\n		$cpages = (strpos($pages, \'::\') !== false ? 2 : 1);\n		$numberOfTabs = count($pages = do_list($pages, $delimiter));\n	}\n	elseif(strpos($pages, \'..\') !== false) {\n		$cpages = 1;\n		list($start, $end) = do_list($pages, \'..\');\n		$numberOfTabs = count($pages = range($start, $end));\n	} else {\n		$numberOfTabs = intval($pages);\n		$pages = range(1, $numberOfTabs);\n	}\n\n	if($numberOfTabs <= 1) return parse(EvalElse($thing, false));\n\n	# if we got tabs, start the outputting\n	$reversenumberorder = (int)$reversenumberorder;\n	if(isset($links)) $links = array_pad(do_list($links, $delimiter), $numberOfTabs, \'\');\n	elseif($cpages < 2) switch($reversenumberorder) {\n		case 1 : case 2 : $links = array_reverse($pages); break;\n		default : $links = &$pages;\n	}\n	else $links = array();\n\n	$range = (int) $range;\n	if($range < 0) $range = $numberOfTabs; else $range += 1;\n	if($scale == \'auto\') $scale = pow($numberOfTabs, 1/$range);\n	else $scale = max(floatval($scale), 1);\n\n	$out = $parts = array();\n	$fragment = \'\';\n	if($root === \'\') $hu = hu;\n	elseif($root === null || $root[0] === \'#\') {$hu = strtok($etc_pagination[\'url\'], \'?\'); $parts = $_GET; if($root) $fragment = $root;}\n	else {\n		$qs = parse_url($root);\n		if(isset($qs[\'fragment\'])) $root = str_replace($fragment = \'#\'.$qs[\'fragment\'], \'\', $root);\n		if(!empty($qs[\'query\'])) parse_str(str_replace(\'&amp;\', \'&\', $qs[\'query\']), $parts);\n		$hu = strtok($root, \'?\');\n	}\n\n	if($query) foreach(do_list($query, \'&\') as $qs) {\n		@list($k, $v) = explode(\'=\', $qs, 2);\n		if(!isset($v)) if($k === \'?\') $parts = array();\n			elseif($k === \'#\') $fragment = \'\';\n			else unset($parts[$k]);\n		else if($k === \'#\') $fragment = \'#\'.$v;\n			elseif($k === \'+\') $hu .= $v;\n			elseif($k[0] === \'/\') $hu = preg_replace($k, $v, $hu);\n			else $parts[$k] = $v;\n	}\n\n	if(isset($page))\n		if(!$cpages) $pgdefault = intval($page);\n		elseif($cpages == 1)\n			if(($pgdefault = array_search($page, $pages)) !== false) $pgdefault++;\n			else $pgdefault = 0;\n		else for($pgdefault = $numberOfTabs; $pgdefault > 0 && strpos($pages[$pgdefault-1].\'::\', $page.\'::\') !== 0; $pgdefault--);\n	else $pgdefault = $reversenumberorder & 1 ? $numberOfTabs : 1;\n	if(isset($parts[$pgcounter]))\n		if($cpages < 2)\n			if(($page = array_search($parts[$pgcounter], $links)) !== false) $page++;\n			else $page = 0;\n		else for($page = $numberOfTabs; $page > 0 && strpos($pages[$page-1].\'::\', $parts[$pgcounter].\'::\') !== 0; $page--);\n	else $page = $pgdefault;\n	$etc_pagination[\'page\'] = $page;\n	$page += $offset;\n	if($page < 1 || $page > $numberOfTabs) return parse(EvalElse($thing, 0));\n\n	unset($parts[$pgcounter]);\n	$qs = array();//join_qs($parts);\n	foreach($parts as $k => $v) $qs[] = urlencode($k) . \'=\' . urlencode(is_array($v) ? implode(\',\', $v) : $v);\n	$qs = \'?\'.implode(\'&amp;\', $qs);\n	$pagebase = $qs !== \'?\' ? $hu.$qs : $hu;\n	if($qs !== \'?\') $qs .= \'&amp;\';\n	$pageurl = $pgcounter ? $hu.$qs.$pgcounter.\'=\' : \'\';\n\n	$currentclass = (empty($thing) && $current && strpos($link, \'{current}\') === false ? ($break ? 1 : -1) : 0);\n\n	@list($gap1, $gap2) = explode($delimiter, $gap); if(!isset($gap2)) $gap2 = $gap1;\n	@list($link, $link_) = explode($delimiter, $link, 2); if(!isset($link_)) $link_ = $link;\n	foreach(array(\'first\', \'prev\', \'next\', \'last\', \'current\') as $item) if(isset($$item))\n		{@list($$item, ${$item.\'_\'}) = explode($delimiter, $$item, 2); if(!isset(${$item.\'_\'})) ${$item.\'_\'} = \'\';}\n	if($currentclass) {if($current) $current = \" class=\'$current\'\"; if($current_) $current_ = \" class=\'$current_\'\";}\n\n	$skip1 = $range < 3 ? $range : 1 + ($gap1 ? 1 : 0) + (isset($first) ? 0 : 1);\n	$skip2 = $range < 3 ? $range : 1 + ($gap2 ? 1 : 0) + (isset($last) ? 0 : 1);\n	if($numberOfTabs < 2*$range) {$loopStart = 1; $loopEnd = $numberOfTabs;}\n	elseif($page <= $range) {$loopStart = 1; $loopEnd = 2*$range - $skip2;}\n	elseif($page > $numberOfTabs - $range) {$loopStart = $numberOfTabs - 2*$range + $skip1 + 1; $loopEnd = $numberOfTabs;}\n	else {$loopStart = $page - $range + $skip1; $loopEnd = $page + $range - $skip2;}\n\n	if($custom = isset($mask)) {\n		if(isset($thing)) {$link = str_replace(\'{link}\', $link, $thing); $link_ = str_replace(\'{link}\', $link_, $thing);}\n		$thing = $mask;\n	}\n	elseif(!isset($thing)) {\n		if($link) $link = \'<a href=\"{href}\" data-rel=\"{rel}\"\'.($currentclass < 0 ? \'{current}\' : \'\').\'>\'.$link.\'</a>\';\n		if($link_) $link_ = \'<span data-rel=\"self\"\'.($currentclass < 0 ? \'{current}\' : \'\').\'>\'.$link_.\'</span>\';\n		foreach(array(\'first\', \'prev\', \'next\', \'last\', \'gap1\', \'gap2\') as $item) {\n			if(!empty($$item)) $$item = \'<a href=\"{href}\" rel=\"{rel}\" title=\"{*}\">\'.$$item.\'</a>\';\n			if(!empty(${$item.\'_\'})) ${$item.\'_\'} = \'<span data-rel=\"\'.$item.\'\">\'.${$item.\'_\'}.\'</span>\';\n		}\n		$thing = \'{link}\';\n	}\n	else $thing = EvalElse($thing, 1);\n	$replacements = array_fill_keys(array(\'{*}\', \'{#}\', \'{$}\', \'{href}\', \'{rel}\', \'{link}\'), \'\');\n	$replacements[\'{pages}\'] = $numberOfTabs;\n	$replacements[\'{current}\'] = $current_;\n	$mask = array_fill_keys(array(\'{links}\', \'{first}\', \'{prev}\', \'{next}\', \'{last}\', \'{<+}\', \'{+>}\'), \'\');\n\n	$outfirst = $outprev = $outgap = \'\';\n	if($prev || $prev_) {\n		etc_pagination_link($replacements, $links, $pages, $page-1, $pgdefault, $pagebase, $pageurl, $fragment, \'prev\', $cpages>1);\n		if($page <= 1) $replacements[\'{#}\'] = $replacements[\'{*}\'] = \'\';\n		$replacements[\'{link}\'] = $mask[\'{prev}\'] = strtr($page > 1 ? $prev : $prev_, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outprev = strtr($thing, $replacements);\n	}\n\n	if($loopStart > 1 && $range > 1 || isset($first)) {\n		etc_pagination_link($replacements, $links, $pages, 1, $pgdefault, $pagebase, $pageurl, $fragment, \'\', $cpages>1);\n		$replacements[\'{link}\'] = $mask[\'{first}\'] = strtr(isset($first) ? ($page > 1 ? $first : $first_) : $link, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outfirst = strtr($thing, $replacements);\n		if($gap1 && $loopStart > 1) {\n//			if($custom) $mask[\'{<+}\'] = $gap1; else $outgap = $gap1;\n			$i = $loopStart-$range-1+$skip2;\n			if($scale > 1 && $i >= $scale) {\n				$n = pow($scale, min(floor(log($i, $scale)), ceil(log($numberOfTabs - $i + 1, $scale)))); $i = intval(floor($i/$n)*$n);\n			}\n			$i = max($range ? 2 : 1, $i);\n			etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, \'prev\', $cpages>1);\n			if($replacements[\'{link}\'] = strtr($gap1, $replacements))\n				if($custom) $mask[\'{<+}\'] = $replacements[\'{link}\']; else $outgap = strtr($thing, $replacements);\n		}\n	}\n\n	if($first) {\n		if($outfirst) $out[] = $outfirst; if($outprev) $out[] = $outprev;\n	} else {\n		if($outprev) $out[] = $outprev; if($outfirst) $out[] = $outfirst;\n	}\n	if($outgap) $out[] = $outgap;\n\n	if($link || $link_) for($i=$loopStart; $i<=$loopEnd; $i++) {\n		etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, $i == $page-1 ? \'prev\' : ($i == $page+1 ? \'next\' : \'\'), $cpages>1);\n		$self = $i == $page;\n		$replacements[\'{current}\'] = $self ? $current : $current_;\n		if($replacements[\'{link}\'] = strtr($self ? $link_ : $link, $replacements))\n			if($custom) $mask[\'{links}\'] .= $replacements[\'{link}\'];\n			else $out[] = ($currentclass > 0 ? ($self ? \'{current}\' : \'{current_}\') : \'\').strtr($thing, $replacements);\n	}\n\n	$outlast = $outnext = $outgap = \'\';\n	$replacements[\'{current}\'] = $current_;\n	if($loopEnd < $numberOfTabs && $range > 1 || isset($last)) {\n		etc_pagination_link($replacements, $links, $pages, $numberOfTabs, $pgdefault, $pagebase, $pageurl, $fragment, \'\', $cpages>1);\n		$replacements[\'{link}\'] = $mask[\'{last}\'] = strtr(isset($last) ? ($page < $numberOfTabs ? $last : $last_) : $link, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outlast = strtr($thing, $replacements);\n		if($gap2 && $loopEnd < $numberOfTabs) {\n//			if($custom) $mask[\'{+>}\'] = $gap2; else $outgap = $gap2;\n			$i = $loopEnd+$range+1-$skip1;\n			if($scale > 1) {\n				$n = pow($scale, floor(log($numberOfTabs, $scale)));\n				$nt = ceil($numberOfTabs/$n)*$n;\n				if($nt >= $scale + $i - 1) {\n					$n = pow($scale, min(floor(log($nt - $i + 1, $scale)), ceil(log($i, $scale))));\n					do {\n						$j = intval($nt - floor(($nt - $i + 1)/$n)*$n);\n						$n /= $scale;\n					} while($j >= $numberOfTabs && $n >= 1);\n					$i = $j;\n				}\n			}\n			$i = min($range ? $numberOfTabs-1 : $numberOfTabs, $i);\n\n			etc_pagination_link($replacements, $links, $pages, $i, $pgdefault, $pagebase, $pageurl, $fragment, \'next\', $cpages>1);\n			if($replacements[\'{link}\'] = strtr($gap2, $replacements))\n				if($custom) $mask[\'{+>}\'] = $replacements[\'{link}\']; else $outgap = strtr($thing, $replacements);\n\n		}\n	}\n\n	if($next || $next_) {\n		etc_pagination_link($replacements, $links, $pages, $page+1, $pgdefault, $pagebase, $pageurl, $fragment, \'next\', $cpages>1);\n		if($page >= $numberOfTabs) $replacements[\'{#}\'] = $replacements[\'{*}\'] = \'\';\n		$replacements[\'{link}\'] = $mask[\'{next}\'] = strtr($page < $numberOfTabs ? $next : $next_, $replacements);\n		if(!$custom && $replacements[\'{link}\']) $outnext = strtr($thing, $replacements);\n	}\n\n	if($outgap) $out[] = $outgap;\n	if($last) {\n		if($outnext) $out[] = $outnext; if($outlast) $out[] = $outlast;\n	} else {\n		if($outlast) $out[] = $outlast; if($outnext) $out[] = $outnext;\n	}\n\n	if($atts) $atts = \' \'.$atts;\n	if($custom) $out = array(strtr($thing, $mask));\n	if($reversenumberorder & 1) $out = array_reverse($out);\n	$out = doWrap($out, $wraptag, $break, $class, \'\', $atts, \'\', $html_id);\n	if($currentclass > 0) $out = str_replace(array(\"<$break>{current}\", \"<$break>{current_}\"), array(\"<{$break}{$current}>\", \"<{$break}{$current_}>\"), $out);\n	return parse($out);\n}\n\nfunction etc_pagination_link(&$replacements, $links, $pages, $page, $pgdefault, $pagebase, $pageurl, $fragment, $rel, $custom) {\n		if (isset($pages[$page-1])) if($custom) @list($replacements[\'{#}\'], $replacements[\'{*}\']) = explode(\'::\', $pages[$page-1].\'::\'.$pages[$page-1]);\n			else {$replacements[\'{*}\'] = $pages[$page-1]; $replacements[\'{#}\'] = $links[$page-1];}\n		$replacements[\'{$}\'] = $page;\n//		if($reversenumberorder) $replacements[\'{$}\'] = $numberOfTabs-$replacements[\'{$}\']+1;\n		$replacements[\'{href}\'] = ($replacements[\'{$}\'] == $pgdefault ? $pagebase : $pageurl.$replacements[\'{#}\']).$fragment;\n		$replacements[\'{rel}\'] = $rel;\n}\n\n\n// -------------------------------------------------------------\n	function etc_numpages($atts)\n	{\n		global $pretext, $prefs, $thispage, $etc_pagination, $etc_pagination_total;\n		if(empty($atts) && isset($thispage)) {$etc_pagination_total = $etc_pagination[\'total\'] = $thispage[\'total\']; return empty($thispage[\'numPages\']) ? 1 : $thispage[\'numPages\'];}\n		extract($pretext);\n		if(empty($atts[\'table\']) && !isset($atts[\'total\'])) {\n			$customFields = getCustomFields();\n			$customlAtts = array_null(array_flip($customFields));\n		} else $customFields = $customlAtts = array();\n\n		//getting attributes\n		extract(lAtts(array(\n			\'table\'     => \'\',\n			\'total\'     => null,\n			\'limit\'     => 10,\n			\'pageby\'    => \'\',\n			\'category\'  => \'\',\n			\'section\'   => \'\',\n			\'exclude\'   => \'\',\n			\'include\'   => \'\',\n			\'excerpted\' => \'\',\n			\'author\'    => \'\',\n			\'realname\'  => \'\',\n			\'month\'     => \'\',\n			\'keywords\'  => \'\',\n			\'expired\'   => $prefs[\'publish_expired_articles\'],\n			\'id\'        => \'\',\n			\'time\'      => \'past\',\n			\'status\'    => \'4\',\n			\'offset\'    => 0\n		)+$customlAtts, $atts));\n\n		if(!($pageby = intval(empty($pageby) ? $limit : $pageby))) return 0;\n		$etc_pagination[\'pageby\'] = $pageby;\n		if(isset($total)) return ceil(intval($total)/$pageby);\n\n		$where = array(\"1\");\n\n		//Building query parts\n		$category  = join(\"\',\'\", doSlash(do_list($category)));\n		if($category) $where[] = !$table ? \"(Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\" : \"category IN (\'\".$category.\"\')\";\n		if($author) $where[] = (!$table ? \"AuthorID\" : \"author\").\" IN (\'\".join(\"\',\'\", doSlash(do_list($author))).\"\')\";\n		if($id) $where[] = \"ID IN (\".join(\',\', array_map(\'intval\', do_list($id))).\")\";\n		if($status && (!$table || $table == \'file\')) $where[] = \'Status in(\'.implode(\',\', doSlash(do_list($status))).\')\';\n		if ($realname) {\n			$authorlist = safe_column(\'name\', \'txp_users\', \"RealName IN (\'\". join(\"\',\'\", doArray(doSlash(do_list($realname)), \'urldecode\')) .\"\')\" );\n			$where[] = (!$table ? \"AuthorID\" : \"author\").\" IN (\'\".join(\"\',\'\", doSlash($authorlist)).\"\')\";\n		}\n		if(!$table) {\n			if($section) $where[] = \"Section IN (\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\";\n			if($month) $where[] = \"Posted like \'\".doSlash($month).\"%\'\";\n			if($excerpted==\'y\' || $excerpted==\'1\') $where[] = \"Excerpt !=\'\'\";\n			switch ($time) {\n				case \'past\':\n					$where[] = \"Posted <= now()\"; break;\n				case \'future\':\n					$where[] = \"Posted > now()\"; break;\n			}\n			if (!$expired) {\n				$where[] = \"(now() <= Expires or Expires IS NULL)\";\n			}\n			//Allow keywords for no-custom articles. That tagging mode, you know\n			if ($keywords) {\n				$keys = doSlash(do_list($keywords));\n				foreach ($keys as $key) {\n					$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n				}\n				$where[] = \"(\" . join(\' or \',$keyparts) . \")\";\n			}\n		} else {\n			if($include) $where[] = \"name IN (\'\".join(\"\',\'\", doSlash(do_list($include))).\"\')\";\n			if($exclude) $where[] = \"name NOT IN (\'\".join(\"\',\'\", doSlash(do_list($exclude))).\"\')\";\n		}\n\n		$customq = \'\';\n		if ($customFields) {\n			foreach($customFields as $cField) {\n				if (isset($atts[$cField]))\n					$customPairs[$cField] = $atts[$cField];\n			}\n			if(!empty($customPairs)) {\n				$customq = buildCustomSql($customFields,$customPairs);\n			}\n		}\n\n//		$where = \"1=1\" . $statusq. $time. $search . $id . $category . $section . $excerpted . $month . $author . $keywords . $customq;\n		$where = implode(\' AND \', $where) . $customq;\n\n		//paginate\n		$grand_total = safe_count($table ? \'txp_\'.$table : \'textpattern\', $where);\n		$etc_pagination_total = $etc_pagination[\'total\'] = $grand_total - $offset;\n		return ceil($etc_pagination[\'total\']/$pageby);\n	}\n\n	function etc_offset($atts)\n	{\n		global $etc_pagination;\n		//getting attributes\n		extract(lAtts((empty($etc_pagination) ? array() : $etc_pagination) + array(\n			\'type\'      => \'\',\n			\'pageby\'    => \'10\',\n			\'pgcounter\' => \'pg\',\n			\'offset\'    => \'0\'\n		), $atts));\n\n		if(!empty($etc_pagination)) extract($etc_pagination);\n		$counter = isset($page) ? $page : urldecode(gps($pgcounter));\n		$page = max(intval($counter), 1) + $offset;\n		$max = isset($total) ? $total : $page*$pageby;\n		switch($type) {\n			case \'value\' : return htmlspecialchars($counter, ENT_QUOTES);\n			case \'page\' : return $page;\n			case \'start\' : return min($max, ($page - 1)*$pageby + 1);\n			case \'end\' : return min($max, $page*$pageby);\n			default : return ($page - 1)*$pageby;\n		}\n	}', '1313758a794ca11c0dfbe6a16fcb49f8', '', '', 0, 3, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('asy_jpcache', 0, 'Sencer Yurdaguel', 'http://www.sencer.de/', '0.9.8', 'JPCache Integration. (This Admin-Plugin is only responsible for cleaning the cache. Click help, to see Installation Instructions.)', '<p>&lt;h1&gt;&lt;strong&gt;asy_jpcache: <span class=\"caps\">JPC</span>ache Integration&lt;/strong&gt;&lt;/h1&gt;</p>\n\n<p>	&lt;p&gt;asy_jpcache is based on &lt;a href=“http://forum.textpattern.com/viewtopic.php?id=3195”&gt;zem_cache&lt;/a&gt;, &lt;a href=“http://www.jpcache.com”&gt;jpcache&lt;/a&gt; and some ideas from &lt;a href=“http://mnm.uib.es/gallir/wp-cache-2/”&gt;WP-Cache&lt;/a&gt;.&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;asy_jpcache will only cache &lt;em&gt;complete&lt;/em&gt; pages (including your feeds). If you want to cache partial pages to retain some dynamic parts take a look at &lt;a href=“http://forum.textpattern.com/viewtopic.php?id=3195”&gt;zem_cache&lt;/a&gt; which is perfect for that kind of thing.&lt;/p&gt;</p>\n\n<p>	&lt;h2&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/h2&gt;</p>\n\n<p>	&lt;ol&gt;\n	&lt;li&gt;Copy the directory &lt;strong&gt;jpcache&lt;/strong&gt; and its contents into your main directory. It should be right next to your &lt;em&gt;images&lt;/em&gt; and your &lt;em&gt;textpattern&lt;/em&gt; directory (on a default install).&lt;/li&gt;\n		&lt;li&gt;Make sure that the &lt;strong&gt;jpcache/cache&lt;/strong&gt; directory can be written to. Usually &lt;em&gt;chmod 777 jpcache/cache/&lt;/em&gt; will do the trick.&lt;/li&gt;\n		&lt;li&gt;Edit your &lt;strong&gt;index.php&lt;/strong&gt; in the main directory and <span class=\"caps\">BEFORE</span>\n  &lt;pre&gt;include $txpcfg[‘txpath’].’/publish.php’;&lt;/pre&gt;</p>\n\n<p>insert the following line:</p>\n\n  &lt;pre&gt;include ‘./jpcache/jpcache.php’;&lt;/pre&gt;\n\n  (<span class=\"caps\">IMPORTANT</span>: do &lt;strong&gt;<span class=\"caps\">NOT</span>&lt;/strong&gt; edit textpattern/index.php )\n		&lt;li&gt;Don&amp;#8217;t forget to activate this Admin-plugin.&lt;/li&gt;\n		&lt;li&gt;Optional: Look inside jpcache/jpcache-config.php to change a few settings, like enabling Debugging, permanently turning off gzip-encoding, changing timout, skipping feeds, re-enabling logging etc.&lt;/li&gt;\n	&lt;/ol&gt;\n\n<p>	&lt;p&gt;If you are experiencing problems (or want to deactivate jpcache), comment out the line in your index.php that you added:&lt;br /&gt;<br />\n&lt;/p&gt;\n  &lt;pre&gt;//include ‘./jpcache/jpcache.php’;&lt;/pre&gt;</p>\n\n\n<p>	&lt;h3&gt;&lt;strong&gt;Details about how <span class=\"caps\">JPC</span>ache works&lt;/strong&gt;&lt;/h3&gt;</p>\n\n<p>	&lt;p&gt;Note: The files you received are a modified version of the original jpcache available at &lt;a href=“http://www.jpcache.com”&gt;jpcache.com&lt;/a&gt;. Do &lt;strong&gt;<span class=\"caps\">NOT</span>&lt;/strong&gt; use this modified version for other stuff, please use the original jpcache version, because this here was hacked to serve textpattern.&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;After a user requests a page, <span class=\"caps\">JPC</span>ache will save the result-page in compressed format in the cache-directory. For subsequent requests, <span class=\"caps\">JPC</span>ache will&lt;/p&gt;\n	&lt;ul&gt;\n	&lt;li&gt;not serve a cached page, if the latest cached file is older than X seconds (default 900)&lt;/li&gt;\n		&lt;li&gt;send a 304 header it ETag-headers are set&lt;/li&gt;\n		&lt;li&gt;otherwise send the compressed page if gzip is supported&lt;/li&gt;\n		&lt;li&gt;or sent the uncompressed page if gzip is not supported&lt;/li&gt;\n		&lt;li&gt;delete a cached page, if a comment is posted at that <span class=\"caps\">URL</span>&lt;/li&gt;\n	&lt;/ul&gt;</p>\n\n<p>	&lt;p&gt;asy_jpcache will also &lt;strong&gt;automatically clean the cache&lt;/strong&gt; when&lt;/p&gt;\n	&lt;ul&gt;\n	&lt;li&gt;comments are edited, deleted, moderated (for this and the following activate this plugin)&lt;/li&gt;\n		&lt;li&gt;articles are edited, posted, deleted&lt;/li&gt;\n		&lt;li&gt;forms or pages are edited.&lt;/li&gt;\n	&lt;/ul&gt;</p>\n\n<p>	&lt;p&gt;asy_jpcache will not cache requests made via <span class=\"caps\">POST</span> (like comment previews).&lt;br /&gt;<br />\nasy_jpcache will not cache file-downloads.&lt;/p&gt;</p>\n\n<p>	&lt;h3&gt;&lt;strong&gt;<span class=\"caps\">FAQ</span>&lt;/strong&gt;&lt;/h3&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;I have Problems with my Feeds&lt;/strong&gt;&lt;br /&gt;<br />\nThere have been reports that some people have occasionally problems with the <span class=\"caps\">RSS</span>/Atom-Feeds. I was not able to reliable reproduce them to find a fix. I have implemented a few things in 0.9 that should solve those problems. If you do however have problems with Feeds, please report so in the Forum. In the meanwhile you can easily turn of caching of the Feeds in jpcache-config.php by setting $JPCACHE_SKIP_FEEDS to 1.&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;My Textpatterns access logs keep getting shorter&lt;/strong&gt;&lt;br /&gt;<br />\nThat&amp;#8217;s a good thing, because when cached files are served it is usually not recorded in the db. However if you wish to keep recording hits in the db set $JPCACHE_TXPLOG_DO to 1. (Default is off, because cache-reads require no access to the db. Enabling this will lower performance a bit.)&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;I am using the <span class=\"caps\">XRT</span>-Plugin&lt;/strong&gt;&lt;br /&gt;<br />\nIf you are using the <span class=\"caps\">XRT</span>-Plugin (<span class=\"caps\">XML</span>-<span class=\"caps\">RPC</span> for Textpattern) by pixelmeadow, you are basically posting content to your blog without going through the Admin-Interface. Therefore the cache will not be cleaned right away. Due to the fact, that cache is periodically regenerated (default time 900 seconds), your changes may appear up to 900 seconds later on the actual site. You can shorten the default caching time in jpcache-config.php.&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;What happens with <span class=\"caps\">CSS</span>-Requests&lt;/strong&gt;&lt;br /&gt;<br />\nThe stylesheets are not affected by jpcache, because they are served from a different file (not index.php). Since stylesheets usually do not change, my advice would be to put them in a regular (static) file and let the Webserver handle it (it&amp;#8217;s more efficient).&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;Content-Type on PHP4&lt;/strong&gt;&lt;br /&gt;<br />\nIf you are using PHP4, asy_jpcache cannot get and cache the proper Content-Type of the generated page. The current workaround to this, is to take a look at the first 50 characters of the content and decide wether it&amp;#8217;s a rss-feed, xml-feed, or text/html. This is fine for most Textpattern users. If for some reason you want to change those hardcoded headers or add more, take a look at $JPCACHE_DEFAULT_MIMETYPE in jpcache/jpcache-config.php and maybe the code in jpcache/file.php below the Line saying:&lt;br /&gt;<br />\n//Content-Sniffing to set correct Content-Type, because headers were not available&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;&lt;strong&gt;I am serving application/xhtml+xml sometimes&lt;/strong&gt;&lt;br /&gt;<br />\n(This assumes you already have a plugin that handles this like &lt;a href=“http://forum.textpattern.com/viewtopic.php?id=4634”&gt;phw_DTD&lt;/a&gt; )&lt;br /&gt;<br />\nIf performance is &lt;em&gt;really&lt;/em&gt;, &lt;em&gt;really&lt;/em&gt; important, then don&amp;#8217;t, because it complicates caching and lessens its benefits (at least the way it is implemented here). Otherwise:&lt;br /&gt;<br />\nIf you are using <span class=\"caps\">PHP</span> 5 simply open jpcache/jpcache-config.php and set $JPCACHE_XHTML_XML to 1. This will add the Accept-Header to the caching-key, meaning that for each <span class=\"caps\">URI</span> one cache-file for every different Accept-Header will be written. Done.&lt;br /&gt;<br />\nIf you are using <span class=\"caps\">PHP</span> 4 &lt;strong&gt;in addition to the above&lt;/strong&gt; you will also have to change the code that decides which Content-Type Header to send. There must be a line that is similar to header(&amp;#8220;Content-Type: application/xhtml+xml; charset=utf-8&amp;#8221;);. Add another line right there that sets the variable&lt;br /&gt;<br />\n$GLOBALS[&amp;#8216;<span class=\"caps\">JPCACHE</span>_DEFAULT_MIMETYPE&amp;#8217;] to something like &amp;#8220;application/xhtml+xml; charset=utf-8&amp;#8221;. That way, <span class=\"caps\">JPC</span>ache will know which Content-Type to associate with the request.&lt;/p&gt;</p>\n\n<p>	&lt;p&gt;Do you have further questions? Please ask in the &lt;a href=“http://forum.textpattern.com/viewtopic.php?id=8352”&gt;Textpattern Forum-Thread for this plugin&lt;/a&gt;.&lt;/p&gt;</p>', '\n// I stole plenty of code from zem. Don\'t tell him! ;)\n	if (function_exists(\"register_callback\")) {\n		register_callback(\"asy_flush_event\", \"article\", \"edit\");\n		register_callback(\"asy_flush_event\", \"article\", \"create\");\n		register_callback(\"asy_flush_event\", \"link\");\n		register_callback(\"asy_flush_event\", \"page\", \"page_save\");\n		register_callback(\"asy_flush_event\", \"form\", \"form_save\");\n		register_callback(\"asy_flush_event\", \"list\", \"list_multi_edit\");\n		register_callback(\"asy_flush_event\", \"discuss\");\n		// We do not have a callback when comments are posted on the front_end\n		// but that\'s ok, I hacked some magic into jpcache-main.php\n\n		register_callback(\'asy_public_flush\', \'zemcontact.submit\'); # Thanks Adam Messinger\n	}\n\n\n	// Add a new tab to the Content area.\n	if (@txpinterface == \'admin\') {\n    add_privs(\'asy_jpcache\',\'1,2\');\n		register_tab(\"extensions\", \"asy_jpcache\", \"jpcache-cleaner\");\n		register_callback(\"asy_jpcachecleaner\", \"asy_jpcache\");\n	}\n\n	// This is the callback-function when something in the Admin-Panel gets changed. (Wrapper)\n	function asy_flush_event($event, $step) {\n		if ( ($event===\'article\')\n			 && (($step===\'create\') || ($step===\'edit\'))\n       && ((count($_POST)==0) || (isset($_REQUEST[\'view\']) && $_REQUEST[\'view\']!=\'\')) ) return;\n		elseif (count($_POST)==0) return;\n		$count = asy_flushdir(true);\n	}\n\n	// Thanks Adam Messinger\n	function asy_public_flush()\n	{\n		asy_flushdir(true);\n	}\n\n\n	// This is the Callback-Function for the Admin-CP\n	function asy_jpcachecleaner($event, $step) {\n		global $lastmod,$prefs,$path_to_site;\n		// ps() returns the contents of POST vars, if any;\n		if (ps(\"step\") === \"clean\")\n		{\n			pagetop(\"JPCache Cleaner\", ( (ps(\"asy_token\") === (md5($lastmod)))\n					? \"Successful\"\n					: \"Token expired. Please try again.\"));\n			if (ps(\"asy_token\") === (md5($lastmod)))\n			{\n				echo \"<div align=\\\"center\\\" style=\\\"margin-top:3em\\\">\";\n				printf(\"Deleted %s files. Cache is clean.\",\'\'.asy_flushdir(true));\n				echo \"</div>\";\n			}\n		} else {\n			pagetop(\"JPCache Cleaner\");\n		}\n		echo \"<div align=\\\"center\\\" style=\\\"margin-top:3em\\\">\";\n		echo form(\n			tag(\"JPCache-Cleaner\", \"h3\").\n			graf(\"Usually you don\'t need to do that. Cache is <b>automatically</b> cleared <br />1)\n				  after a certain amount of time <br />2) when a comment is posted, edited or moderated\n			      <br />3) after a page-template or form-tag is is modified.<br /><br />\".\n				fInput(\"hidden\", \"asy_token\", md5($lastmod)).\n				fInput(\"submit\", \"clean_cache\", \"Clean all cached Files\", \"smallerbox\").\n				eInput(\"asy_jpcache\").sInput(\"clean\")\n			,\" style=\\\"text-align:center\\\"\")\n		);\n		echo tag(\"Cache Statistics\",\"h3\");\n		global $path_to_site;$count = array(\'size\'=>0, \'num\'=>0);\n		$asy_cache_dir = $path_to_site .\'/jpcache/cache\';\n		if (!empty($asy_cache_dir) and $fp = opendir($asy_cache_dir)) {\n			while (false !== ($file = readdir($fp))) {\n				if ($file{0} != \".\") {\n					$count[\'size\'] += filesize(\"$asy_cache_dir/$file\");\n					++$count[\'num\'];\n				}\n			}\n			closedir($fp);\n			printf(\"There are %d cache files with a total size of %d kb.\", $count[\'num\'], floor($count[\'size\']/1000));\n		} else { echo \"Cache is empty.\";}\n		include $path_to_site .\'/jpcache/jpcache-config.php\';\n/*		if (@$JPCACHE_TXPLOG_DO == 1 && $prefs[\'logging\']==\'all\'){\n			echo tag(\"Read-Write-Ratio<sup>1</sup>\",\"h3\");;\n			$cachehits = safe_field(\'COUNT( id ) as hit\', \'txp_log\', \"page LIKE \'%#cachehit\'\");\n			$totalhits = getThing(\"SELECT MIN(time) FROM \".PFX.\"txp_log WHERE page LIKE \'%#cachehit\'\");\n			$totalhits = getThing(\"SELECT COUNT(id) FROM \".PFX.\"txp_log WHERE time > \'\". $totalhits.\"\'\");\n			printf(\"There were <b>%d</b> cache-reads recorded and <b>%d</b> possible cache-writes. <br />Average number of reads per write: <b>%01.2f</b>\",$cachehits, $totalhits-$cachehits, (($totalhits-$cachehits) > 0) ? ($cachehits/($totalhits-$cachehits)) : \'0\');\n			echo \"<br /><br /><sup>1</sup>This is a (low) Approximation. Initially wait a week before numbers become meaningful.\";\n		}\n*/		echo \"</div>\";\n	}\n\n	// This function clears the Cache directory. Make sure jpcache is installed in the right directory.\n	function asy_flushdir($force_clean = false) {\n		global $path_to_site, $lastmod;\n\n		$count = 0;\n		$asy_cache_dir = $path_to_site .\'/jpcache/cache\';\n\n		if (!empty($asy_cache_dir) and $fp = opendir($asy_cache_dir)) {\n			$last = strtotime($lastmod);\n			while (false !== ($file = readdir($fp))) {\n				if ($file{0} != \".\" AND\n					 ((filemtime(\"$asy_cache_dir/$file\") < $last) OR $force_clean)){\n					@unlink(\"$asy_cache_dir/$file\");\n					++$count;\n				}\n			}\n\n			closedir($fp);\n		}\n\n		return $count;\n	}\n\n', '\n// I stole plenty of code from zem. Don\'t tell him! ;)\n	if (function_exists(\"register_callback\")) {\n		register_callback(\"asy_flush_event\", \"article\", \"edit\");\n		register_callback(\"asy_flush_event\", \"article\", \"create\");\n		register_callback(\"asy_flush_event\", \"link\");\n		register_callback(\"asy_flush_event\", \"page\", \"page_save\");\n		register_callback(\"asy_flush_event\", \"form\", \"form_save\");\n		register_callback(\"asy_flush_event\", \"list\", \"list_multi_edit\");\n		register_callback(\"asy_flush_event\", \"discuss\");\n		// We do not have a callback when comments are posted on the front_end\n		// but that\'s ok, I hacked some magic into jpcache-main.php\n\n		register_callback(\'asy_public_flush\', \'zemcontact.submit\'); # Thanks Adam Messinger\n	}\n\n\n	// Add a new tab to the Content area.\n	if (@txpinterface == \'admin\') {\n    add_privs(\'asy_jpcache\',\'1,2\');\n		register_tab(\"extensions\", \"asy_jpcache\", \"jpcache-cleaner\");\n		register_callback(\"asy_jpcachecleaner\", \"asy_jpcache\");\n	}\n\n	// This is the callback-function when something in the Admin-Panel gets changed. (Wrapper)\n	function asy_flush_event($event, $step) {\n		if ( ($event===\'article\')\n			 && (($step===\'create\') || ($step===\'edit\'))\n       && ((count($_POST)==0) || (isset($_REQUEST[\'view\']) && $_REQUEST[\'view\']!=\'\')) ) return;\n		elseif (count($_POST)==0) return;\n		$count = asy_flushdir(true);\n	}\n\n	// Thanks Adam Messinger\n	function asy_public_flush()\n	{\n		asy_flushdir(true);\n	}\n\n\n	// This is the Callback-Function for the Admin-CP\n	function asy_jpcachecleaner($event, $step) {\n		global $lastmod,$prefs,$path_to_site;\n		// ps() returns the contents of POST vars, if any;\n		if (ps(\"step\") === \"clean\")\n		{\n			pagetop(\"JPCache Cleaner\", ( (ps(\"asy_token\") === (md5($lastmod)))\n					? \"Successful\"\n					: \"Token expired. Please try again.\"));\n			if (ps(\"asy_token\") === (md5($lastmod)))\n			{\n				echo \"<div align=\\\"center\\\" style=\\\"margin-top:3em\\\">\";\n				printf(\"Deleted %s files. Cache is clean.\",\'\'.asy_flushdir(true));\n				echo \"</div>\";\n			}\n		} else {\n			pagetop(\"JPCache Cleaner\");\n		}\n		echo \"<div align=\\\"center\\\" style=\\\"margin-top:3em\\\">\";\n		echo form(\n			tag(\"JPCache-Cleaner\", \"h3\").\n			graf(\"Usually you don\'t need to do that. Cache is <b>automatically</b> cleared <br />1)\n				  after a certain amount of time <br />2) when a comment is posted, edited or moderated\n			      <br />3) after a page-template or form-tag is is modified.<br /><br />\".\n				fInput(\"hidden\", \"asy_token\", md5($lastmod)).\n				fInput(\"submit\", \"clean_cache\", \"Clean all cached Files\", \"smallerbox\").\n				eInput(\"asy_jpcache\").sInput(\"clean\")\n			,\" style=\\\"text-align:center\\\"\")\n		);\n		echo tag(\"Cache Statistics\",\"h3\");\n		global $path_to_site;$count = array(\'size\'=>0, \'num\'=>0);\n		$asy_cache_dir = $path_to_site .\'/jpcache/cache\';\n		if (!empty($asy_cache_dir) and $fp = opendir($asy_cache_dir)) {\n			while (false !== ($file = readdir($fp))) {\n				if ($file{0} != \".\") {\n					$count[\'size\'] += filesize(\"$asy_cache_dir/$file\");\n					++$count[\'num\'];\n				}\n			}\n			closedir($fp);\n			printf(\"There are %d cache files with a total size of %d kb.\", $count[\'num\'], floor($count[\'size\']/1000));\n		} else { echo \"Cache is empty.\";}\n		include $path_to_site .\'/jpcache/jpcache-config.php\';\n/*		if (@$JPCACHE_TXPLOG_DO == 1 && $prefs[\'logging\']==\'all\'){\n			echo tag(\"Read-Write-Ratio<sup>1</sup>\",\"h3\");;\n			$cachehits = safe_field(\'COUNT( id ) as hit\', \'txp_log\', \"page LIKE \'%#cachehit\'\");\n			$totalhits = getThing(\"SELECT MIN(time) FROM \".PFX.\"txp_log WHERE page LIKE \'%#cachehit\'\");\n			$totalhits = getThing(\"SELECT COUNT(id) FROM \".PFX.\"txp_log WHERE time > \'\". $totalhits.\"\'\");\n			printf(\"There were <b>%d</b> cache-reads recorded and <b>%d</b> possible cache-writes. <br />Average number of reads per write: <b>%01.2f</b>\",$cachehits, $totalhits-$cachehits, (($totalhits-$cachehits) > 0) ? ($cachehits/($totalhits-$cachehits)) : \'0\');\n			echo \"<br /><br /><sup>1</sup>This is a (low) Approximation. Initially wait a week before numbers become meaningful.\";\n		}\n*/		echo \"</div>\";\n	}\n\n	// This function clears the Cache directory. Make sure jpcache is installed in the right directory.\n	function asy_flushdir($force_clean = false) {\n		global $path_to_site, $lastmod;\n\n		$count = 0;\n		$asy_cache_dir = $path_to_site .\'/jpcache/cache\';\n\n		if (!empty($asy_cache_dir) and $fp = opendir($asy_cache_dir)) {\n			$last = strtotime($lastmod);\n			while (false !== ($file = readdir($fp))) {\n				if ($file{0} != \".\" AND\n					 ((filemtime(\"$asy_cache_dir/$file\") < $last) OR $force_clean)){\n					@unlink(\"$asy_cache_dir/$file\");\n					++$count;\n				}\n			}\n\n			closedir($fp);\n		}\n\n		return $count;\n	}\n\n', '9e6fbb78a99b3f615bcd3a7ff77f0c6a', '', '', 5, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('sed_cleaner', 0, 'Netcarver', 'https://github.com/netcarver/sed_cleaner', '0.8.45', 'Does a little house cleaning on new installs.', '	<style type=\"text/css\">\n	div#sed_cleaner_help td { vertical-align:top; }\n	div#sed_cleaner_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n	div#sed_cleaner_help code.sed_code_tag { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n	div#sed_cleaner_help a:link, div#sed_cleaner_help a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n	div#sed_cleaner_help a:hover, div#sed_cleaner_help a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n	div#sed_cleaner_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n	div#sed_cleaner_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n	div#sed_cleaner_help h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n	div#sed_cleaner_help ul ul { font-size:85%; }\n	div#sed_cleaner_help h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n	</style>\n	<p><div id=\"sed_cleaner_help\"></p>\n	<h1 id=\"top\">SED Cleaner.</h1>\n	<p>Introduces section-specific overrides for admin interface fields.</p>\n	<p>Kills site content. Only enable this on <b>NEW</b> sites!</p>\n	<h2 id=\"changelog\"><a href=\"http://forum.textpattern.com/viewtopic.php?pid=250247#p250247\">Change Log</a></h2>\n	<p>That&#8217;s it.</p>\n <span style=\"float:right\"><a href=\"#top\" title=\"Jump to the top\">top</a></span>\n	<p></div></p>\n\n ', '\nglobal $event;\n\nif( @txpinterface === \'admin\' )\n{\n	register_callback(\'sed_cleaner_installed\', \'plugin_lifecycle.sed_cleaner\', \'installed\' );\n}\n\n\n#\n#	Installation handler...\n#\nfunction sed_cleaner_installed( $evt, $stp )\n{\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"theme/classic/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div>\n		<h1>** WARNING: <em>sed_cleaner deletes ALL site content</em> **</h1>\n		<p>If you make the sed_cleaner plugin <strong>ACTIVE</strong> (at any time), all your site content will be destroyed.</p>\n		<p>Clicking <strong>OK</strong> will take you back to the TXP Plugins tab.</p>\n		<p><em>Decide what you want to do when you get there:</em></p>\n		<ul>\n			<li>Make the plugin <strong>ACTIVE</strong> to clean your site</li>\n			<li>OR <strong>DELETE</strong> the plugin to prevent any future threat to your content</li>\n			<li>OR leave the plugin <strong>INACTIVE</strong> to do nothing now</li>\n		</ul>\n		<form action=\"index.php\" method=\"GET\">\n			<input type=\"hidden\" name=\"event\" value=\"plugin\">\n			<input value=\"OK\" name=\"sed_cleaner_warning\" class=\"publish\" type=\"submit\">\n		</form>\n	</div>\n</body>\n</html>\nHTML;\n	exit(0);\n}\n\nfunction sed_cleaner_lastchance( $count )\n{\n	global $event, $step;\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"theme/classic/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div style=\"text-align: center;\">\n	<p>Confirm removal of $count articles and all associated content.</p>\n	<form>\n		<input type=\"hidden\" name=\"event\" value=\"{$event}\">\n		<input type=\"hidden\" name=\"step\"  value=\"{$step}\">\n		<input value=\"Ok\"     name=\"sed_cleaner_confirmation\" class=\"publish\" type=\"submit\">\n		<input value=\"Cancel\" name=\"sed_cleaner_confirmation\" class=\"publish\" type=\"submit\">\n		<ul>\n			<li>Pressing <strong>Ok</strong> will REMOVE ALL CONTENT.</li>\n			<li>Pressing <strong>Cancel</strong> will disable sed_cleaner, saving your content.</li>\n		</ul>\n	</form>\n	</div>\n</body>\n</html>\nHTML;\n  exit(0);\n}\n\nfunction sed_cleaner_done()\n{\n	global $txpcfg;\n	include_once $txpcfg[\'txpath\']. DS . \'lib\' . DS . \'txplib_head.php\';\n	echo pagetop(\'\');\n	echo<<<HTML\n<body>\n  <p style=\"text-align: center\">sed_cleaner all done. Self terminating.</p>\n</body>\n</html>\nHTML;\n}\n\nif( @txpinterface === \'admin\' && $event !== \'plugin\' ) # Should only happen if plugin enabled.\n{\n	$count = safe_count( \'textpattern\', \'1=1\' );\n	if( $count == 1 )\n		sed_cleaner_gogogo();	# Only one article => a fresh install?\n	else\n	{\n		#\n		#	Confirm removal...\n		#\n		$remove = gps( \'sed_cleaner_confirmation\' );\n		if( $remove === \'Ok\' )\n			sed_cleaner_gogogo();		# Pressing OK kills content\n		elseif( $remove === \'Cancel\' )\n			safe_update( \'txp_plugin\', \"`status`=\'0\'\", \"`name`=\'sed_cleaner\'\" );	# Cancelling disables plugin\n		else\n			sed_cleaner_lastchance( $count );	# Display last chance page\n	}\n}\n\nfunction sed_cleaner_gogogo()\n{\n	global $event, $prefs, $txpcfg;\n	$files_path = $prefs[\'file_base_path\'];\n	$tpref = $txpcfg[\'table_prefix\'];\n	$debug = 0;\n\n	#\n	#	Remove default content...\n	#\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_discuss`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_link`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}textpattern`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_image`\", $debug );\n	safe_delete( \'txp_category\', \"`name` <> \'root\'\");\n\n	#\n	#	Setup some defaults...\n	#\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'site_slogan\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'custom_1_set\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'custom_2_set\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'spam_blacklists\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'0\'\",              \"`name`=\'use_dns\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'1\'\",              \"`name`=\'never_display_email\'\", $debug );\n\n	#\n	# Remove the setup directory (if permissions allow)...\n	#\n	sed_cleaner_rmdir( \'setup\', $debug );\n\n\n	#\n	#	identify installable files found in the files/ directory...\n	#\n	include_once $txpcfg[\'txpath\']. DS . \'include\' . DS . \'txp_plugin.php\';\n	$files = array();\n	$path = $files_path;\n	if( $debug ) echo br , \"Auto Install Plugins... Accessing dir($path) ...\";\n	$dir = @dir( $path );\n	if( $dir === false )\n	{\n		if( $debug ) echo \" failed!\";\n	}\n	else\n	{\n		while( $file = $dir->read() )\n		{\n			$parts = pathinfo($file);\n			$fileaddr = $path.DS.$file;\n			if( !is_dir($fileaddr) )\n			{\n				if( $debug ) echo br , \"... found ($file)\";\n				switch( @$parts[\'extension\'] )\n				{\n					case \'plugin\' :\n						$files[\'plugins\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate plugin file.\";\n						break;\n					case \'css\' :\n						$files[\'css\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate CSS file.\";\n						break;\n					case \'page\' :\n						$files[\'page\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate Txp page file.\";\n						break;\n					case \'form\' :\n						$files[\'form\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate Txp form file.\";\n						break;\n					default:\n						break;\n				}\n			}\n		}\n	}\n	$dir->close();\n\n\n	#\n	#	Try to auto-install any plugin files found in the files directory...\n	#\n	$plugin_count = 0;\n	if( empty( $files[\'plugins\'] ) )\n	{\n		if( $debug ) echo \" no plugin candidate files found.\";\n	}\n	else\n	{\n		include_once $txpcfg[\'txpath\'].\'/lib/txplib_head.php\';\n		foreach( $files[\'plugins\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			#\n			#	Load the file into the $_POST[\'plugin64\'] entry and try installing it...\n			#\n			$plugin = join( \'\', file($path.DS.$file) );\n			$_POST[\'plugin64\'] = $plugin;\n			if( $debug ) echo \"installing,\";\n			plugin_install();\n			$plugin_count += 1;\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any CSS files found...\n	#\n	if( empty( $files[\'css\'] ) )\n	{\n		if( $debug ) echo \" no CSS candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'css\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$name = doSlash( $parts[\'filename\'] );\n			safe_upsert( \'txp_css\', \"`css`=\'$content\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any page files found...\n	#\n	if( empty( $files[\'page\'] ) )\n	{\n		if( $debug ) echo \" no page candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'page\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$name = doSlash( $parts[\'filename\'] );\n			safe_upsert( \'txp_page\', \"`user_html`=\'$content\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any form files found...\n	#\n	#	Filename format = name.type.form\n	#	where type is one of { article, link, file, comment, misc }\n	#\n	if( empty( $files[\'form\'] ) )\n	{\n		if( $debug ) echo \" no form candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'form\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$tmp = explode( \'.\', $parts[\'filename\'] );\n			$type = doSlash( array_pop($tmp) );\n			$name = doSlash( implode( \'.\', $tmp ) );\n\n			echo br, \"Found form $name of type $type.\";\n			safe_upsert( \'txp_form\', \"`Form`=\'$content\', `type`=\'$type\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	# Process the cleanups.php file...\n	#\n	$file = $files_path . DS . \'cleanups.php\' ;\n	if( file_exists( $file ) )\n	{\n		$cleanups = array();\n\n		#\n		# Include the scripted cleanups...\n		#\n		@include $file;\n		if( is_callable( \'sed_cleaner_config\' ) )\n			$cleanups = sed_cleaner_config();\n\n		if( !empty( $cleanups ) )\n		{\n			#\n			#	Take the scripted actions...\n			#\n			foreach( $cleanups as $action )\n			{\n				$p = explode( \' \', $action );\n				if( $debug )\n					dmp( $p );\n\n				$action = strtolower( array_shift( $p ) );\n				$fn = \"sed_cleaner_{$action}_action\";\n\n				if( is_callable( $fn ) )\n				{\n					$fn( $p, $debug );\n				}\n			}\n		}\n		elseif( $debug )\n			echo \"<pre>No installation specific cleanups found.\\n</pre>\";\n\n		unlink( $file );\n	}\n	elseif( $debug )\n		echo \"<pre>No installation specific cleanup file found.\\n</pre>\";\n\n	sed_cleaner_done();\n\n	if( !$debug )\n	{\n		#\n		#	cleanup the cleanup files...\n		#\n#		sed_cleaner_empty_dir( $prefs[\'file_base_path\'], $debug, true );	# exclude hiddens!\n		safe_query( \"TRUNCATE TABLE `{$tpref}txp_file`\", $debug );\n\n		#\n		# Finally, we self-destruct unless debugging and redirect to the plugins tab...\n		#\n		safe_delete( \'txp_plugin\', \"`name`=\'sed_cleaner\'\", $debug );\n		if( $plugin_count )\n		{\n			while( @ob_end_clean() );\n			header(\'Location: \'.hu.\'textpattern/index.php?event=prefs\');\n			header(\'Connection: close\');\n			header(\'Content-Length: 0\');\n		}\n		exit(0);\n	}\n}\n\n\n\n#\n#   Action handlers for the cleanups.php script follow...\n#\nfunction sed_cleaner_removesection_action( $args, $debug )\n{\n	$section_name = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to remove section $section_name.\";\n	safe_delete(\'txp_section\', \"`name` = \'$section_name\'\" , $debug );\n}\n\nfunction sed_cleaner_addsection_action( $args, $debug )\n{\n	$section_title = doSlash( array_shift( $args ) );\n	$section_name = strtolower(sanitizeForUrl($section_title));\n\n	if( !empty( $args ) )\n		$page = doSlash( array_shift( $args ) );\n	else\n		$page = $default[\'page\'];\n\n	if( !empty( $args ) )\n		$css = doSlash( array_shift( $args ) );\n	else\n		$css = $default[\'css\'];\n\n	if( !empty( $args ) )\n		$rss = doSlash( array_shift( $args ) );\n	else\n		$rss = 0;\n\n	if( !empty( $args ) )\n		$frontpage = doSlash( array_shift( $args ) );\n	else\n		$frontpage = 0;\n\n	if( !empty( $args ) )\n		$searchable = doSlash( array_shift( $args ) );\n	else\n		$searchable = 0;\n\n	$default = doSlash(safe_row(\'page, css\', \'txp_section\', \"name = \'default\'\"));\n	if( $debug ) echo \" attempting to add a section entitled \'$section_title\'.\";\n	safe_insert( \'txp_section\',\n		\"`name` = \'$section_name\',\n		`title` = \'$section_title\',\n		`page`  = \'$page\',\n		`css`   = \'$css\',\n		`is_default` = 0,\n		`in_rss` = $rss,\n		`on_frontpage` = $frontpage,\n		`searchable` = $searchable\",\n		$debug );\n}\n\nfunction sed_cleaner_removepage_action( $args, $debug )\n{\n	$page = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" removing page $page.\";\n	safe_delete( \'txp_page\', \"`name` = \'$page\'\", $debug );\n}\n\nfunction sed_cleaner_blankpage_action( $args, $debug )\n{\n	$page = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking page $page.\";\n	$content = \'\';\n	if( $page === \'default\' )\n		$content = <<<HTML\n<html>\n  <head>\n    <title><txp:site_name /></title>\n  </head>\n\n  <body>\n    <p><txp:site_name /> is blank.</p>\n  </body>\n</html>\nHTML;\n	safe_update( \'txp_page\', \"`user_html` = \'$content\'\", \"`name` = \'$page\'\", $debug );\n}\n\nfunction sed_cleaner_blankcss_action( $args, $debug )\n{\n	$css = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking CSS $css.\";\n	safe_update( \'txp_css\', \"`css`=\'\'\", \"`name`=\'$css\'\", $debug );\n}\n\nfunction sed_cleaner_enableplugin_action( $args, $debug )\n{\n	$plugin = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to activate $plugin.\";\n	safe_update( \'txp_plugin\', \"`status`=\'1\'\", \"`name`=\'$plugin\'\", $debug );\n}\n\nfunction sed_cleaner_removeform_action( $args, $debug )\n{\n	$form = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" removing form $form.\";\n  safe_delete( \'txp_form\', \"`name`=\'$form\'\", $debug );\n}\n\nfunction sed_cleaner_blankform_action( $args, $debug )\n{\n	$form = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking form $form.\";\n	safe_update( \'txp_form\', \"`Form` = \'\'\", \"`name`=\'$form\'\", $debug );\n}\n\nfunction sed_cleaner_disableplugin_action( $args, $debug )\n{\n	$plugin = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to deactivate $plugin.\";\n	safe_update( \'txp_plugin\', \"`status`=\'0\'\", \"`name`=\'$plugin\'\", $debug );\n}\n\nfunction sed_cleaner_setpref_action( $args, $debug )\n{\n	$key = doSlash( array_shift( $args ) );\n	$args = join( \' \', $args );\n	$args = doSlash( trim( $args, \'\" \' ) );\n	safe_upsert( \'txp_prefs\', \"`val`=\'$args\'\",  \"`name`=\'$key\'\", $debug );\n}\n\nfunction sed_cleaner_truncate_action( $args, $debug )\n{\n	$table = doSlash( array_shift( $args ) );\n	safe_query( \"TRUNCATE TABLE `$table`\", $debug );\n}\n\nfunction sed_cleaner_removedir_action( $args, $debug )\n{\n	$dir = array_shift( $args );\n	sed_cleaner_rmdir( $dir, $debug );\n}\n\n\n#\n#	sed_cleaner_rmdir removes a dir non-recursively...\n#\nfunction sed_cleaner_rmdir( $dir, $debug = 0 )\n{\n	if( !is_string( $dir ) || empty( $dir ) || !is_dir( $dir ) )\n	{\n		echo \"<pre>Could not remove the directory [$dir]. It doesn\'t seem to exist.</pre><br />\";\n		return false;\n	}\n\n	sed_cleaner_empty_dir( $dir, $debug );\n\n	if( $debug ) echo \"<pre>Removing $dir\\n</pre>\";\n	rmdir($dir);\n\n	return true;\n}\n\nfunction sed_cleaner_empty_dir($dir, $debug, $exclude_hidden = false)\n{\n	$objects = scandir($dir);\n	foreach ($objects as $object)\n	{\n		if ($object != \".\" && $object != \"..\")\n		{\n			if (filetype($dir . DS . $object) !== \"dir\")\n			{\n				if( $object[0] == \'.\' && $exclude_hidden )\n					continue;\n\n				if( $debug ) echo \"<pre>Removing $dir/$object\\n</pre>\";\n				unlink($dir. DS .$object);\n			}\n    }\n  }\n  reset($objects);\n}\n\nfunction sed_cleaner_removefile_action( $args, $debug )\n{\n	$filename = array_shift( $args );\n	$whitelist = array( \'license.txt\', \'lgpl-2.1.txt\', \'../HISTORY.txt\', \'../README.txt\' );\n	if( !in_array( $filename, $whitelist ) )\n	{\n		echo \"[$filename] is not in the files whitelist.\";\n		return;\n	}\n\n	if( $debug ) echo \" attempting to remove file [$filename].\";\n	if( file_exists( $filename ) )\n		unlink( $filename );\n}\n', '\nglobal $event;\n\nif( @txpinterface === \'admin\' )\n{\n	register_callback(\'sed_cleaner_installed\', \'plugin_lifecycle.sed_cleaner\', \'installed\' );\n}\n\n\n#\n#	Installation handler...\n#\nfunction sed_cleaner_installed( $evt, $stp )\n{\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"theme/classic/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div>\n		<h1>** WARNING: <em>sed_cleaner deletes ALL site content</em> **</h1>\n		<p>If you make the sed_cleaner plugin <strong>ACTIVE</strong> (at any time), all your site content will be destroyed.</p>\n		<p>Clicking <strong>OK</strong> will take you back to the TXP Plugins tab.</p>\n		<p><em>Decide what you want to do when you get there:</em></p>\n		<ul>\n			<li>Make the plugin <strong>ACTIVE</strong> to clean your site</li>\n			<li>OR <strong>DELETE</strong> the plugin to prevent any future threat to your content</li>\n			<li>OR leave the plugin <strong>INACTIVE</strong> to do nothing now</li>\n		</ul>\n		<form action=\"index.php\" method=\"GET\">\n			<input type=\"hidden\" name=\"event\" value=\"plugin\">\n			<input value=\"OK\" name=\"sed_cleaner_warning\" class=\"publish\" type=\"submit\">\n		</form>\n	</div>\n</body>\n</html>\nHTML;\n	exit(0);\n}\n\nfunction sed_cleaner_lastchance( $count )\n{\n	global $event, $step;\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"theme/classic/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div style=\"text-align: center;\">\n	<p>Confirm removal of $count articles and all associated content.</p>\n	<form>\n		<input type=\"hidden\" name=\"event\" value=\"{$event}\">\n		<input type=\"hidden\" name=\"step\"  value=\"{$step}\">\n		<input value=\"Ok\"     name=\"sed_cleaner_confirmation\" class=\"publish\" type=\"submit\">\n		<input value=\"Cancel\" name=\"sed_cleaner_confirmation\" class=\"publish\" type=\"submit\">\n		<ul>\n			<li>Pressing <strong>Ok</strong> will REMOVE ALL CONTENT.</li>\n			<li>Pressing <strong>Cancel</strong> will disable sed_cleaner, saving your content.</li>\n		</ul>\n	</form>\n	</div>\n</body>\n</html>\nHTML;\n  exit(0);\n}\n\nfunction sed_cleaner_done()\n{\n	global $txpcfg;\n	include_once $txpcfg[\'txpath\']. DS . \'lib\' . DS . \'txplib_head.php\';\n	echo pagetop(\'\');\n	echo<<<HTML\n<body>\n  <p style=\"text-align: center\">sed_cleaner all done. Self terminating.</p>\n</body>\n</html>\nHTML;\n}\n\nif( @txpinterface === \'admin\' && $event !== \'plugin\' ) # Should only happen if plugin enabled.\n{\n	$count = safe_count( \'textpattern\', \'1=1\' );\n	if( $count == 1 )\n		sed_cleaner_gogogo();	# Only one article => a fresh install?\n	else\n	{\n		#\n		#	Confirm removal...\n		#\n		$remove = gps( \'sed_cleaner_confirmation\' );\n		if( $remove === \'Ok\' )\n			sed_cleaner_gogogo();		# Pressing OK kills content\n		elseif( $remove === \'Cancel\' )\n			safe_update( \'txp_plugin\', \"`status`=\'0\'\", \"`name`=\'sed_cleaner\'\" );	# Cancelling disables plugin\n		else\n			sed_cleaner_lastchance( $count );	# Display last chance page\n	}\n}\n\nfunction sed_cleaner_gogogo()\n{\n	global $event, $prefs, $txpcfg;\n	$files_path = $prefs[\'file_base_path\'];\n	$tpref = $txpcfg[\'table_prefix\'];\n	$debug = 0;\n\n	#\n	#	Remove default content...\n	#\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_discuss`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_link`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}textpattern`\", $debug );\n	safe_query( \"TRUNCATE TABLE `{$tpref}txp_image`\", $debug );\n	safe_delete( \'txp_category\', \"`name` <> \'root\'\");\n\n	#\n	#	Setup some defaults...\n	#\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'site_slogan\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'custom_1_set\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'custom_2_set\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'\'\",               \"`name`=\'spam_blacklists\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'0\'\",              \"`name`=\'use_dns\'\", $debug );\n	safe_update( \'txp_prefs\', \"`val`=\'1\'\",              \"`name`=\'never_display_email\'\", $debug );\n\n	#\n	# Remove the setup directory (if permissions allow)...\n	#\n	sed_cleaner_rmdir( \'setup\', $debug );\n\n\n	#\n	#	identify installable files found in the files/ directory...\n	#\n	include_once $txpcfg[\'txpath\']. DS . \'include\' . DS . \'txp_plugin.php\';\n	$files = array();\n	$path = $files_path;\n	if( $debug ) echo br , \"Auto Install Plugins... Accessing dir($path) ...\";\n	$dir = @dir( $path );\n	if( $dir === false )\n	{\n		if( $debug ) echo \" failed!\";\n	}\n	else\n	{\n		while( $file = $dir->read() )\n		{\n			$parts = pathinfo($file);\n			$fileaddr = $path.DS.$file;\n			if( !is_dir($fileaddr) )\n			{\n				if( $debug ) echo br , \"... found ($file)\";\n				switch( @$parts[\'extension\'] )\n				{\n					case \'plugin\' :\n						$files[\'plugins\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate plugin file.\";\n						break;\n					case \'css\' :\n						$files[\'css\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate CSS file.\";\n						break;\n					case \'page\' :\n						$files[\'page\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate Txp page file.\";\n						break;\n					case \'form\' :\n						$files[\'form\'][] = $file;\n						if( $debug ) echo \" : accepting as a candidate Txp form file.\";\n						break;\n					default:\n						break;\n				}\n			}\n		}\n	}\n	$dir->close();\n\n\n	#\n	#	Try to auto-install any plugin files found in the files directory...\n	#\n	$plugin_count = 0;\n	if( empty( $files[\'plugins\'] ) )\n	{\n		if( $debug ) echo \" no plugin candidate files found.\";\n	}\n	else\n	{\n		include_once $txpcfg[\'txpath\'].\'/lib/txplib_head.php\';\n		foreach( $files[\'plugins\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			#\n			#	Load the file into the $_POST[\'plugin64\'] entry and try installing it...\n			#\n			$plugin = join( \'\', file($path.DS.$file) );\n			$_POST[\'plugin64\'] = $plugin;\n			if( $debug ) echo \"installing,\";\n			plugin_install();\n			$plugin_count += 1;\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any CSS files found...\n	#\n	if( empty( $files[\'css\'] ) )\n	{\n		if( $debug ) echo \" no CSS candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'css\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$name = doSlash( $parts[\'filename\'] );\n			safe_upsert( \'txp_css\', \"`css`=\'$content\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any page files found...\n	#\n	if( empty( $files[\'page\'] ) )\n	{\n		if( $debug ) echo \" no page candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'page\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$name = doSlash( $parts[\'filename\'] );\n			safe_upsert( \'txp_page\', \"`user_html`=\'$content\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	#	Try to install any form files found...\n	#\n	#	Filename format = name.type.form\n	#	where type is one of { article, link, file, comment, misc }\n	#\n	if( empty( $files[\'form\'] ) )\n	{\n		if( $debug ) echo \" no form candidates found.\";\n	}\n	else\n	{\n		foreach( $files[\'form\'] as $file )\n		{\n			if( $debug ) echo br , \"Processing $file : \";\n			$content = doSlash( file_get_contents( $path.DS.$file ) );\n			$parts = pathinfo($file);\n			$tmp = explode( \'.\', $parts[\'filename\'] );\n			$type = doSlash( array_pop($tmp) );\n			$name = doSlash( implode( \'.\', $tmp ) );\n\n			echo br, \"Found form $name of type $type.\";\n			safe_upsert( \'txp_form\', \"`Form`=\'$content\', `type`=\'$type\'\", \"`name`=\'$name\'\" , $debug );\n			unlink( $path.DS.$file );\n		}\n	}\n\n\n	#\n	# Process the cleanups.php file...\n	#\n	$file = $files_path . DS . \'cleanups.php\' ;\n	if( file_exists( $file ) )\n	{\n		$cleanups = array();\n\n		#\n		# Include the scripted cleanups...\n		#\n		@include $file;\n		if( is_callable( \'sed_cleaner_config\' ) )\n			$cleanups = sed_cleaner_config();\n\n		if( !empty( $cleanups ) )\n		{\n			#\n			#	Take the scripted actions...\n			#\n			foreach( $cleanups as $action )\n			{\n				$p = explode( \' \', $action );\n				if( $debug )\n					dmp( $p );\n\n				$action = strtolower( array_shift( $p ) );\n				$fn = \"sed_cleaner_{$action}_action\";\n\n				if( is_callable( $fn ) )\n				{\n					$fn( $p, $debug );\n				}\n			}\n		}\n		elseif( $debug )\n			echo \"<pre>No installation specific cleanups found.\\n</pre>\";\n\n		unlink( $file );\n	}\n	elseif( $debug )\n		echo \"<pre>No installation specific cleanup file found.\\n</pre>\";\n\n	sed_cleaner_done();\n\n	if( !$debug )\n	{\n		#\n		#	cleanup the cleanup files...\n		#\n#		sed_cleaner_empty_dir( $prefs[\'file_base_path\'], $debug, true );	# exclude hiddens!\n		safe_query( \"TRUNCATE TABLE `{$tpref}txp_file`\", $debug );\n\n		#\n		# Finally, we self-destruct unless debugging and redirect to the plugins tab...\n		#\n		safe_delete( \'txp_plugin\', \"`name`=\'sed_cleaner\'\", $debug );\n		if( $plugin_count )\n		{\n			while( @ob_end_clean() );\n			header(\'Location: \'.hu.\'textpattern/index.php?event=prefs\');\n			header(\'Connection: close\');\n			header(\'Content-Length: 0\');\n		}\n		exit(0);\n	}\n}\n\n\n\n#\n#   Action handlers for the cleanups.php script follow...\n#\nfunction sed_cleaner_removesection_action( $args, $debug )\n{\n	$section_name = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to remove section $section_name.\";\n	safe_delete(\'txp_section\', \"`name` = \'$section_name\'\" , $debug );\n}\n\nfunction sed_cleaner_addsection_action( $args, $debug )\n{\n	$section_title = doSlash( array_shift( $args ) );\n	$section_name = strtolower(sanitizeForUrl($section_title));\n\n	if( !empty( $args ) )\n		$page = doSlash( array_shift( $args ) );\n	else\n		$page = $default[\'page\'];\n\n	if( !empty( $args ) )\n		$css = doSlash( array_shift( $args ) );\n	else\n		$css = $default[\'css\'];\n\n	if( !empty( $args ) )\n		$rss = doSlash( array_shift( $args ) );\n	else\n		$rss = 0;\n\n	if( !empty( $args ) )\n		$frontpage = doSlash( array_shift( $args ) );\n	else\n		$frontpage = 0;\n\n	if( !empty( $args ) )\n		$searchable = doSlash( array_shift( $args ) );\n	else\n		$searchable = 0;\n\n	$default = doSlash(safe_row(\'page, css\', \'txp_section\', \"name = \'default\'\"));\n	if( $debug ) echo \" attempting to add a section entitled \'$section_title\'.\";\n	safe_insert( \'txp_section\',\n		\"`name` = \'$section_name\',\n		`title` = \'$section_title\',\n		`page`  = \'$page\',\n		`css`   = \'$css\',\n		`is_default` = 0,\n		`in_rss` = $rss,\n		`on_frontpage` = $frontpage,\n		`searchable` = $searchable\",\n		$debug );\n}\n\nfunction sed_cleaner_removepage_action( $args, $debug )\n{\n	$page = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" removing page $page.\";\n	safe_delete( \'txp_page\', \"`name` = \'$page\'\", $debug );\n}\n\nfunction sed_cleaner_blankpage_action( $args, $debug )\n{\n	$page = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking page $page.\";\n	$content = \'\';\n	if( $page === \'default\' )\n		$content = <<<HTML\n<html>\n  <head>\n    <title><txp:site_name /></title>\n  </head>\n\n  <body>\n    <p><txp:site_name /> is blank.</p>\n  </body>\n</html>\nHTML;\n	safe_update( \'txp_page\', \"`user_html` = \'$content\'\", \"`name` = \'$page\'\", $debug );\n}\n\nfunction sed_cleaner_blankcss_action( $args, $debug )\n{\n	$css = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking CSS $css.\";\n	safe_update( \'txp_css\', \"`css`=\'\'\", \"`name`=\'$css\'\", $debug );\n}\n\nfunction sed_cleaner_enableplugin_action( $args, $debug )\n{\n	$plugin = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to activate $plugin.\";\n	safe_update( \'txp_plugin\', \"`status`=\'1\'\", \"`name`=\'$plugin\'\", $debug );\n}\n\nfunction sed_cleaner_removeform_action( $args, $debug )\n{\n	$form = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" removing form $form.\";\n  safe_delete( \'txp_form\', \"`name`=\'$form\'\", $debug );\n}\n\nfunction sed_cleaner_blankform_action( $args, $debug )\n{\n	$form = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" blanking form $form.\";\n	safe_update( \'txp_form\', \"`Form` = \'\'\", \"`name`=\'$form\'\", $debug );\n}\n\nfunction sed_cleaner_disableplugin_action( $args, $debug )\n{\n	$plugin = doSlash( array_shift( $args ) );\n	if( $debug ) echo \" attempting to deactivate $plugin.\";\n	safe_update( \'txp_plugin\', \"`status`=\'0\'\", \"`name`=\'$plugin\'\", $debug );\n}\n\nfunction sed_cleaner_setpref_action( $args, $debug )\n{\n	$key = doSlash( array_shift( $args ) );\n	$args = join( \' \', $args );\n	$args = doSlash( trim( $args, \'\" \' ) );\n	safe_upsert( \'txp_prefs\', \"`val`=\'$args\'\",  \"`name`=\'$key\'\", $debug );\n}\n\nfunction sed_cleaner_truncate_action( $args, $debug )\n{\n	$table = doSlash( array_shift( $args ) );\n	safe_query( \"TRUNCATE TABLE `$table`\", $debug );\n}\n\nfunction sed_cleaner_removedir_action( $args, $debug )\n{\n	$dir = array_shift( $args );\n	sed_cleaner_rmdir( $dir, $debug );\n}\n\n\n#\n#	sed_cleaner_rmdir removes a dir non-recursively...\n#\nfunction sed_cleaner_rmdir( $dir, $debug = 0 )\n{\n	if( !is_string( $dir ) || empty( $dir ) || !is_dir( $dir ) )\n	{\n		echo \"<pre>Could not remove the directory [$dir]. It doesn\'t seem to exist.</pre><br />\";\n		return false;\n	}\n\n	sed_cleaner_empty_dir( $dir, $debug );\n\n	if( $debug ) echo \"<pre>Removing $dir\\n</pre>\";\n	rmdir($dir);\n\n	return true;\n}\n\nfunction sed_cleaner_empty_dir($dir, $debug, $exclude_hidden = false)\n{\n	$objects = scandir($dir);\n	foreach ($objects as $object)\n	{\n		if ($object != \".\" && $object != \"..\")\n		{\n			if (filetype($dir . DS . $object) !== \"dir\")\n			{\n				if( $object[0] == \'.\' && $exclude_hidden )\n					continue;\n\n				if( $debug ) echo \"<pre>Removing $dir/$object\\n</pre>\";\n				unlink($dir. DS .$object);\n			}\n    }\n  }\n  reset($objects);\n}\n\nfunction sed_cleaner_removefile_action( $args, $debug )\n{\n	$filename = array_shift( $args );\n	$whitelist = array( \'license.txt\', \'lgpl-2.1.txt\', \'../HISTORY.txt\', \'../README.txt\' );\n	if( !in_array( $filename, $whitelist ) )\n	{\n		echo \"[$filename] is not in the files whitelist.\";\n		return;\n	}\n\n	if( $debug ) echo \" attempting to remove file [$filename].\";\n	if( file_exists( $filename ) )\n		unlink( $filename );\n}\n', 'ee5c33e540365792de41f3b7ea9c17ee', '', '', 3, 1, 2),
('yab_cf_article_list', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.2', 'List a custom_field in admin article list', '<h1>yab_cf_article_list</h1>\n\n<p>List a custom_field in admin article list and search article list for this custom field.</p>\n\n<p><strong>Version:</strong> 0.2</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section02\">Plugin requirements</a></li>\n	<li><a href=\"#help-config03\">Configuration</a></li>\n	<li><a href=\"#help-section10\">Changelog</a></li>\n	<li><a href=\"#help-section11\">License</a></li>\n	<li><a href=\"#help-section12\">Author contact</a></li>\n</ol>\n\n<h2 id=\"help-section02\">Plugin requirements</h2>\n\n<p>Minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.6.2</li>\n</ul>\n\n<h2 id=\"help-config03\">Configuration</h2>\n\n<p>Install and activate the plugin.</p>\n\n<p>The function yab_cfal_config() in the plugin code contains an array with some config values:</p>\n\n<p><code>&#39;custom_field&#39;</code>: Number of the custom field<br />\nDefault: 1</p>\n\n<p><code>&#39;name_for_list&#39;</code>: Name of the custom field in the table head and in article search.<br />\nDefault: empty (The given name for this custom field will be shown.)</p>\n\n<h2 id=\"help-section10\">Changelog</h2>\n\n<ul>\n	<li>v0.1: 2014-05-06\n	<ul>\n		<li>initial release</li>\n	</ul></li>\n	<li>v0.2: 2017-02-18\n	<ul>\n		<li>bugfix: <span class=\"caps\">TXP</span> 4.6.ready (required)</li>\n	</ul></li>\n	</ul>\n\n<h2 id=\"help-section11\">Licence</h2>\n\n<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above<br />\n<ul>\n	<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n	<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li><br />\n</ul></p>\n\n<h2 id=\"help-section12\">Author contact</h2>\n\n<ul>\n	<li><a href=\"http://www.yablo.de/article/482/yab_cf_article_list-list-and-search-a-custom_field-in-admin-article-list\">Plugin on author&#8217;s site</a></li>\n	<li><a href=\"https://github.com/trenc/yab_cf_article_list\">Plugin on GitHub</a></li>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=40971\">Plugin on textpattern forum</a></li>\n	<li><a href=\"http://textpattern.org/plugins/1292/yab_cf_article_list\">Plugin on textpattern.org</a></li>\n</ul>', '/**\n * Config function holder to avoid some globals\n * Can later be changed to receive config from database id needed\n *\n * @param  string $name name of the config\n * @return string\n */\nfunction yab_cfal_config($name)\n{\n	global $prefs;\n\n	// config begin\n	$config = array(\n		\'custom_field\'  => \'1\', // number of the custom field\n		\'name_for_list\' => \'\' // Title in table head and search field, default custom_field name\n	);\n	// config end\n\n	// get default name of custom field\n	if (!$config[\'name_for_list\'])\n	{\n		$config[\'name_for_list\'] = @$prefs[\'custom_1_set\'];\n	}\n\n	return $config[$name];\n}\n\n// admin callbacks\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_cfal_js\',\n		\'admin_side\',\n		\'body_end\'\n	);\n\n	register_callback(\n		\'yab_cfal_search\',\n		\'search_criteria\',\n		\'list\'\n	);\n\n	// our AJAX endpoint\n	$ajax = gps(\'yab_cfal_ajax\');\n\n	// is AJAX endpoint is called\n	if ($ajax)\n	{\n		// echo our stuff\n		echo yab_cfal_ajax(ps(\'ids\'));\n		// but no textpattern stuff\n		exit();\n	}\n}\n\n/**\n * Enhance the search method with custom field criteria\n * Adminside Textpattern callback function\n * Hooked in the search_criteria in article list\n *\n * @return void\n */\nfunction yab_cfal_search($event, $step, &$data, &$rs)\n{\n	$cf   = \'custom_\'.yab_cfal_config(\'custom_field\');\n	$name = yab_cfal_config(\'name_for_list\');\n\n	$crit = array(\n		\'column\'  => array(\'textpattern.\'.$cf),\n		\'label\'   => $name,\n		\'options\' => array(\'case_sensitive\' => true)\n	);\n\n	$data[$cf] = $crit;\n}\n\n/**\n * Echo the JavaScript\n * Adminside Textpattern callback function\n * Fired at body_end in ui\n *\n * @return void\n */\nfunction yab_cfal_js()\n{\n	global $event;\n\n	// be sure we are article list area\n	if ($event != \'list\')\n	{\n		return false;\n	}\n\n	$ajax_uri = hu.\'textpattern/?yab_cfal_ajax=1\';\n	$thead    = yab_cfal_config(\'name_for_list\');\n\n	$js     = <<<EOF\n<script>\n(function() {\n\n	var th = \'<th class=\"txp-list-col-cf\" scope=\"col\">$thead</th>\';\n	$(\'th.txp-list-col-id\', \'thead\').after(th);\n\n	// get ids\n	var ids = [];\n	var tdid = $(\'th.txp-list-col-id\', \'.txp-list tr\');\n\n	tdid.each(function() {\n		var article_id = $(this).children(\'a\').text();\n		ids.push(article_id);\n	});\n\n		$.ajax({\n			type:   \'POST\',\n			url:    \'$ajax_uri\',\n			cache:  false,\n			data :  {\'ids\':ids}, // POST json\n			success: function(result) {\n				result = result ? result : \'\';\n				data = JSON.parse(result);\n\n				tdid.each(function() {\n					var that = this;\n					var this_id = $(this).children(\'a\').text();\n					$.each(data, function(i) {\n						if (data[i].id == this_id) {\n							var this_cf = data[i].cf;\n							$(that).after(\'<td class=\"custom_field\">\' + this_cf + \'</td>\');\n						}\n					});\n				});\n			}\n		});\n\n})();\n</script>\nEOF;\n\n	echo $js;\n}\n\n/**\n * Return ID and custom_field values as JSON\n *\n * @param  array  $ids POSTed array of Textpattern IDs\n * @return string JSON string\n */\nfunction yab_cfal_ajax($ids)\n{\n	$json = \'\';\n	$ids = array_map(\'intval\', $ids);\n	$ids = implode(\',\', $ids);\n	$ids = doSlash($ids);\n	$cf = yab_cfal_config(\'custom_field\');\n\n	$rs = safe_rows(\"ID as id, custom_$cf as cf\", \'textpattern\', \"ID IN ($ids)\");\n\n	if ($rs)\n	{\n		$json .= json_encode($rs);\n	}\n\n	return $json;\n}', '/**\n * Config function holder to avoid some globals\n * Can later be changed to receive config from database id needed\n *\n * @param  string $name name of the config\n * @return string\n */\nfunction yab_cfal_config($name)\n{\n	global $prefs;\n\n	// config begin\n	$config = array(\n		\'custom_field\'  => \'1\', // number of the custom field\n		\'name_for_list\' => \'\' // Title in table head and search field, default custom_field name\n	);\n	// config end\n\n	// get default name of custom field\n	if (!$config[\'name_for_list\'])\n	{\n		$config[\'name_for_list\'] = @$prefs[\'custom_1_set\'];\n	}\n\n	return $config[$name];\n}\n\n// admin callbacks\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_cfal_js\',\n		\'admin_side\',\n		\'body_end\'\n	);\n\n	register_callback(\n		\'yab_cfal_search\',\n		\'search_criteria\',\n		\'list\'\n	);\n\n	// our AJAX endpoint\n	$ajax = gps(\'yab_cfal_ajax\');\n\n	// is AJAX endpoint is called\n	if ($ajax)\n	{\n		// echo our stuff\n		echo yab_cfal_ajax(ps(\'ids\'));\n		// but no textpattern stuff\n		exit();\n	}\n}\n\n/**\n * Enhance the search method with custom field criteria\n * Adminside Textpattern callback function\n * Hooked in the search_criteria in article list\n *\n * @return void\n */\nfunction yab_cfal_search($event, $step, &$data, &$rs)\n{\n	$cf   = \'custom_\'.yab_cfal_config(\'custom_field\');\n	$name = yab_cfal_config(\'name_for_list\');\n\n	$crit = array(\n		\'column\'  => array(\'textpattern.\'.$cf),\n		\'label\'   => $name,\n		\'options\' => array(\'case_sensitive\' => true)\n	);\n\n	$data[$cf] = $crit;\n}\n\n/**\n * Echo the JavaScript\n * Adminside Textpattern callback function\n * Fired at body_end in ui\n *\n * @return void\n */\nfunction yab_cfal_js()\n{\n	global $event;\n\n	// be sure we are article list area\n	if ($event != \'list\')\n	{\n		return false;\n	}\n\n	$ajax_uri = hu.\'textpattern/?yab_cfal_ajax=1\';\n	$thead    = yab_cfal_config(\'name_for_list\');\n\n	$js     = <<<EOF\n<script>\n(function() {\n\n	var th = \'<th class=\"txp-list-col-cf\" scope=\"col\">$thead</th>\';\n	$(\'th.txp-list-col-id\', \'thead\').after(th);\n\n	// get ids\n	var ids = [];\n	var tdid = $(\'th.txp-list-col-id\', \'.txp-list tr\');\n\n	tdid.each(function() {\n		var article_id = $(this).children(\'a\').text();\n		ids.push(article_id);\n	});\n\n		$.ajax({\n			type:   \'POST\',\n			url:    \'$ajax_uri\',\n			cache:  false,\n			data :  {\'ids\':ids}, // POST json\n			success: function(result) {\n				result = result ? result : \'\';\n				data = JSON.parse(result);\n\n				tdid.each(function() {\n					var that = this;\n					var this_id = $(this).children(\'a\').text();\n					$.each(data, function(i) {\n						if (data[i].id == this_id) {\n							var this_cf = data[i].cf;\n							$(that).after(\'<td class=\"custom_field\">\' + this_cf + \'</td>\');\n						}\n					});\n				});\n			}\n		});\n\n})();\n</script>\nEOF;\n\n	echo $js;\n}\n\n/**\n * Return ID and custom_field values as JSON\n *\n * @param  array  $ids POSTed array of Textpattern IDs\n * @return string JSON string\n */\nfunction yab_cfal_ajax($ids)\n{\n	$json = \'\';\n	$ids = array_map(\'intval\', $ids);\n	$ids = implode(\',\', $ids);\n	$ids = doSlash($ids);\n	$cf = yab_cfal_config(\'custom_field\');\n\n	$rs = safe_rows(\"ID as id, custom_$cf as cf\", \'textpattern\', \"ID IN ($ids)\");\n\n	if ($rs)\n	{\n		$json .= json_encode($rs);\n	}\n\n	return $json;\n}', '89fcdb99ccedf62ad32d35b79b30424c', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('yab_article_crossref', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.1', 'Easy cross-reference an article in custom fields.', '<h1>yab_article_crossref</h1>\n\n<p>Easy cross-reference an article in custom fields.</p>\n\n<p><strong>Version:</strong> 0.1</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section02\">Plugin requirements</a></li>\n	<li><a href=\"#help-config03\">Configuration</a></li>\n	<li><a href=\"#help-section10\">Changelog</a></li>\n	<li><a href=\"#help-section11\">License</a></li>\n	<li><a href=\"#help-section12\">Author contact</a></li>\n</ol>\n\n<h2 id=\"help-section02\">Plugin requirements</h2>\n\n<p>yab_article_crossref&#8217;s  minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.x</li>\n</ul>\n\n<h2 id=\"help-config03\">Configuration</h2>\n\n<p>Open the plugin code. the first function contains the configuration values:</p>\n\n<pre><code>&#39;custom_field_nr&#39; =&gt; &#39;, // custom_field number containing the cross reference article ID\n&#39;sections&#39;        =&gt; &#39;, // sections from which we build the dropdown, comma separated\n&#39;categories&#39;      =&gt; &#39; // categories from which we build the dropdown, comma separated</code></pre>\n\n<h2 id=\"help-section10\">Changelog</h2>\n\n<ul>\n	<li>v0.1: 2017-02-12\n	<ul>\n		<li>initial release</li>\n	</ul></li>\n	</ul>\n\n<h2 id=\"help-section11\">Licence</h2>\n\n<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above<br />\n<ul>\n	<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n	<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li><br />\n</ul></p>\n\n<h2 id=\"help-section12\">Author contact</h2>\n\n<ul>\n	<li><a href=\"http://www.yablo.de/article/500/yab_article_crossref-cross-reference-articles-in-custom-fields\">Plugin on author&#8217;s site</a></li>\n	<li><a href=\"https://github.com/trenc/yab_article_crossref\">Plugin on GitHub</a></li>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?pid=303985\">Plugin on textpattern forum</a></li>\n	<li><a href=\"http://textpattern.org/plugins/1321/yab_article_crossref\">Plugin on textpattern.org</a></li>\n</ul>', '/**\n * yab_article_crossref\n *\n * A Textpattern CMS plugin.\n * Easy cross-reference an article in custom fields.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   201702-12\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\n/**\n * Configuration\n * Do your configuration here\n *\n * @return array\n */\nfunction yab_article_crossref_config()\n{\n	$config = array(\n		\'custom_field_nr\' => \'\', // custom_field number containing the cross reference article ID\n		\'sections\'        => \'\', // sections from which we build the dropdown, comma separated\n		\'categories\'      => \'\' // categories from which we build the dropdown, comma separated\n	);\n\n	return $config;\n}\n\nif (txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_article_crossref\',\n		\'article_ui\',\n		\'custom_fields\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab.\n * Adminside Textpattern callback function\n * Hooked in the custom_fields on article_ui\n *\n * @param  string $event Textpattern admin event\n * @param  string $step  Textpattern admin step\n * @param  string $data  Textpattern data from callback step hook\n * @param  array  $rs    Textpattern data array from callback event hook\n * @return string        Echos the JavaScript\n */\nfunction yab_article_crossref($event, $step, $data, $rs)\n{\n	$config = yab_article_crossref_config();\n\n	if (gps(\'event\') !== \'article\' or !$config[\'custom_field_nr\'])\n	{\n		return;\n	}\n\n	$cf_nr    = $config[\'custom_field_nr\'];\n	$dropdown = yab_cf_cr_dropdown($config, $rs);\n\n	$js = <<<EOF\n<script>\n$(function() {\n	$(\'#custom-$cf_nr\').replaceWith(\'$dropdown\');\n});\n</script>\nEOF;\n\n	return $data.$js;\n}\n\n/**\n * Build dropdown depending on configuration\n *\n * @param  array  $config Configuration array\n * @param  array  $rs     Textpattern data array from callback event hook\n * @return string         Dropdown select HTML element\n */\nfunction yab_cf_cr_dropdown($config, $rs)\n{\n	$category     = join(\"\',\'\", doSlash(do_list($config[\'categories\'])));\n	$categories   = array();\n	$categories[] = \"Category1 IN (\'$category\')\";\n	$categories[] = \"Category2 IN (\'$category\')\";\n	$categories   = join(\" OR \", $categories);\n	$category     = (!$category or !$categories)  ? \'\' : \" AND ($categories)\";\n	$sects        = $config[\'sections\'];\n	$sections     = (!$sects) ? \'\' : \" AND Section IN (\'\".join(\"\',\'\", doSlash(do_list($sects))).\"\')\";\n	$cf_nr        = $config[\'custom_field_nr\'];\n	$old          = $rs[\'custom_\'.$cf_nr];\n	$out_begin    = \'<select name=\"custom_\'.$cf_nr.\'\" id=\"custom-\'.$cf_nr.\'\">\';\n	$out_end      = \'</select>\';\n	$options      = \'<option></option>\';\n	$where        = \"1 = 1\".$sections.$category;\n	$rows         = safe_rows(\'ID, Title\', \'textpattern\', $where);\n\n	foreach($rows as $row)\n	{\n		$selected = ($row[\'ID\'] === $old) ? \' selected=\"selected\"\': \'\';\n\n		$options .= \'<option value=\"\'.$row[\'ID\'].\'\"\'.$selected.\'>\'.$row[\'Title\'].\'</option>\';\n	}\n\n	return $out_begin.$options.$out_end;\n}', '/**\n * yab_article_crossref\n *\n * A Textpattern CMS plugin.\n * Easy cross-reference an article in custom fields.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   201702-12\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\n/**\n * Configuration\n * Do your configuration here\n *\n * @return array\n */\nfunction yab_article_crossref_config()\n{\n	$config = array(\n		\'custom_field_nr\' => \'\', // custom_field number containing the cross reference article ID\n		\'sections\'        => \'\', // sections from which we build the dropdown, comma separated\n		\'categories\'      => \'\' // categories from which we build the dropdown, comma separated\n	);\n\n	return $config;\n}\n\nif (txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_article_crossref\',\n		\'article_ui\',\n		\'custom_fields\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab.\n * Adminside Textpattern callback function\n * Hooked in the custom_fields on article_ui\n *\n * @param  string $event Textpattern admin event\n * @param  string $step  Textpattern admin step\n * @param  string $data  Textpattern data from callback step hook\n * @param  array  $rs    Textpattern data array from callback event hook\n * @return string        Echos the JavaScript\n */\nfunction yab_article_crossref($event, $step, $data, $rs)\n{\n	$config = yab_article_crossref_config();\n\n	if (gps(\'event\') !== \'article\' or !$config[\'custom_field_nr\'])\n	{\n		return;\n	}\n\n	$cf_nr    = $config[\'custom_field_nr\'];\n	$dropdown = yab_cf_cr_dropdown($config, $rs);\n\n	$js = <<<EOF\n<script>\n$(function() {\n	$(\'#custom-$cf_nr\').replaceWith(\'$dropdown\');\n});\n</script>\nEOF;\n\n	return $data.$js;\n}\n\n/**\n * Build dropdown depending on configuration\n *\n * @param  array  $config Configuration array\n * @param  array  $rs     Textpattern data array from callback event hook\n * @return string         Dropdown select HTML element\n */\nfunction yab_cf_cr_dropdown($config, $rs)\n{\n	$category     = join(\"\',\'\", doSlash(do_list($config[\'categories\'])));\n	$categories   = array();\n	$categories[] = \"Category1 IN (\'$category\')\";\n	$categories[] = \"Category2 IN (\'$category\')\";\n	$categories   = join(\" OR \", $categories);\n	$category     = (!$category or !$categories)  ? \'\' : \" AND ($categories)\";\n	$sects        = $config[\'sections\'];\n	$sections     = (!$sects) ? \'\' : \" AND Section IN (\'\".join(\"\',\'\", doSlash(do_list($sects))).\"\')\";\n	$cf_nr        = $config[\'custom_field_nr\'];\n	$old          = $rs[\'custom_\'.$cf_nr];\n	$out_begin    = \'<select name=\"custom_\'.$cf_nr.\'\" id=\"custom-\'.$cf_nr.\'\">\';\n	$out_end      = \'</select>\';\n	$options      = \'<option></option>\';\n	$where        = \"1 = 1\".$sections.$category;\n	$rows         = safe_rows(\'ID, Title\', \'textpattern\', $where);\n\n	foreach($rows as $row)\n	{\n		$selected = ($row[\'ID\'] === $old) ? \' selected=\"selected\"\': \'\';\n\n		$options .= \'<option value=\"\'.$row[\'ID\'].\'\"\'.$selected.\'>\'.$row[\'Title\'].\'</option>\';\n	}\n\n	return $out_begin.$options.$out_end;\n}', 'b9346e5c44d4aaa9fb17baaa4d2ded11', '', '', 3, 5, 0),
('yab_copy_to_new', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.2', 'Copy the current article content to a new one.', '	<h1>yab_copy_to_new</h1>\n\n	<p>Displays a new button in article write tab to copy the current article to a new one.</p>\n\n	<p><strong>Version:</strong> 0.2</p>\n\n	<h2>Table of contents</h2>\n\n	<ol>\n		<li><a href=\"#help-section02\">Plugin requirements</a></li>\n		<li><a href=\"#help-config03\">Configuration</a></li>\n		<li><a href=\"#help-section10\">Changelog</a></li>\n		<li><a href=\"#help-section11\">License</a></li>\n		<li><a href=\"#help-section12\">Author contact</a></li>\n	</ol>\n\n	<h2 id=\"help-section02\">Plugin requirements</h2>\n\n	<p>yab_copy_to_new&#8217;s  minimum requirements:</p>\n\n	<ul>\n		<li>Textpattern 4.x</li>\n		<li>Modern browser capable of HTML5 sessionStorage</li>\n	</ul>\n\n	<h2 id=\"help-config03\">Configuration</h2>\n\n	<p>Install and activate the plugin.</p>\n\n	<p>The following form fields will not be copied by default:\n	<ul>\n		<li>all of hidden type</li>\n		<li>an exclude array of posted day and time and the url_title<br />\nYou can modify this exclude array on your own,</li>\n	</ul></p>\n\n	<p>The function yab_ctn_config() contains an array with some config values and a commented example of the array which places the button near the top »Create new button«.</p>\n\n	<ul>\n		<li><code>&#39;exclude&#39;</code>: Javascript array with field to excluded from copying</li>\n		<li><code>&#39;position_selector&#39;</code>: a valid jQery selector (used by <code>position_method</code>)</li>\n		<li><code>&#39;position_method&#39;</code>: any jQuery <span class=\"caps\">DOM</span> insert method (after, prepend, append, before etc.)</li>\n		<li><code>&#39;class&#39;</code>: html class applied to teh button</li>\n		<li><code>&#39;style&#39;</code>: inline style attribute aplied to the button</li>\n	</ul>\n\n	<h2 id=\"help-section10\">Changelog</h2>\n\n	<ul>\n		<li>v0.1: 2014-02-06\n	<ul>\n		<li>initial release</li>\n	</ul></li>\n		<li>v0.2: 2014-04-11\n	<ul>\n		<li>feature: enhanced config for placing/styling the button</li>\n	</ul></li>\n	</ul>\n\n	<h2 id=\"help-section11\">Licence</h2>\n\n	<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above\n	<ul>\n		<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n		<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li>\n	</ul></p>\n\n	<h2 id=\"help-section12\">Author contact</h2>\n\n	<ul>\n		<li><a href=\"http://www.yablo.de/article/479/yab_copy_to_new-copy-the-current-article-content-to-a-new-one\">Plugin on author&#8217;s site</a></li>\n		<li><a href=\"https://github.com/trenc/yab_copy_to_new\">Plugin on GitHub</a></li>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?pid=278692\">Plugin on textpattern forum</a></li>\n		<li><a href=\"http://textpattern.org/plugins/1289/yab_copy_to_new\">Plugin on textpattern.org</a></li>\n	</ul>', '/**\n * yab_copy_to_new\n *\n * A Textpattern CMS plugin.\n * Copy the current article content to a new one.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2014-02-06\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\n/**\n * Config var\n *\n * @param string  $what\n * @return string\n */\nfunction yab_ctn_config($what)\n{\n	$config = array(\n		\'exclude\'           => \'[\"url_title\", \"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\"]\',\n		\'position_selector\' => \'#write-save\', // a valid jQery selector\n		\'position_method\'   => \'append\', // any jQuery DOM insert method\n		\'class\'             => \'publish\', // html class applied to element\n		\'style\'             => \'margin-left: 0.5em;\' // inline style attribute\n	);\n\n	// example config for button placed near top »Create new« button\n// 	$config = array(\n// 		\'exclude\'           => \'[\"url_title\", \"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\"]\',\n// 		\'position_selector\' => \'.action-create\', // a valid jQery selector\n// 		\'position_method\'   => \'append\', // any jQuery DOM insert method\n// 		\'class\'             => \'\', // html class applied to element\n// 		\'style\'             => \'margin-left: 0.5em;\' // inline style attribute\n// 	);\n\n	return $config[$what];\n}\n\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_copy_to_new\',\n		\'admin_side\',\n		\'body_end\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab.\n *\n * @return void Echos the JavaScript\n */\nfunction yab_copy_to_new()\n{\n	global $event;\n\n	// Config: name attribute for excluded fields (JavaScript array)\n	$exclude  = yab_ctn_config(\'exclude\');\n	$anchor   = yab_ctn_config(\'position_selector\');\n	$method   = yab_ctn_config(\'position_method\');\n	$class    = yab_ctn_config(\'class\');\n	$style    = yab_ctn_config(\'style\');\n\n	$buttontext = gTxt(\'yab_copy_to_new\');\n\n	$js = <<<EOF\n<script>\n(function() {\n\n	// ECMAScript < 5 indexOf method\n	if (!Array.prototype.indexOf) {\n		Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {\n			\"use strict\";\n			if (this == null) {\n				throw new TypeError();\n			}\n			var t = Object(this);\n			var len = t.length >>> 0;\n			if (len === 0) {\n				return -1;\n			}\n			var n = 0;\n			if (arguments.length > 0) {\n				n = Number(arguments[1]);\n				if (n != n) { // shortcut for verifying if it\'s NaN\n					n = 0;\n				} else if (n != 0 && n != Infinity && n != -Infinity) {\n					n = (n > 0 || -1) * Math.floor(Math.abs(n));\n				}\n			}\n			if (n >= len) {\n				return -1;\n			}\n			var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);\n			for (; k < len; k++) {\n				if (k in t && t[k] === searchElement) {\n					return k;\n				}\n			}\n			return -1;\n		}\n	}\n\n	var form2string = function(txp_form) {\n		return JSON.stringify(txp_form.serializeArray());\n	}\n\n	var string2form = function(txp_form, serialized) {\n		var excludes = $exclude;\n		var fields = JSON.parse(serialized);\n		var flength = fields.length;\n		for (var i = 0; i < flength; i++) {\n			var e_name = fields[i].name;\n			var e_value = fields[i].value;\n			var e = txp_form.find(\'[name=\"\' + e_name + \'\"]\');\n\n			if (e.is(\':hidden\') || excludes.indexOf(e_name) != -1) {\n				continue;\n			} else if (e.is(\':radio\')) {\n				e.filter(\'[value=\\\'\' + e_value + \'\\\']\').prop(\'checked\', true);\n			} else if (e.is(\':checkbox\') && e_value) {\n				e.prop(\'checked\', true);\n			} else if (e.is(\'select\')) {\n				e.find(\'[value=\\\'\' + e_value + \'\\\']\').prop(\'selected\', true);\n			} else {\n				e.val(e_value);\n			}\n		}\n	}\n\n	var j_form = $(\'#article_form\');\n\n	var button = \'<button id=\"yab-copy-to-new\" class=\"$class\" tabindex=\"5\" style=\"$style\">$buttontext</button>\';\n\n	$(\'$anchor\').$method(button);\n\n		j_form.on(\'click\', \'#yab-copy-to-new\', function(ev) {\n		ev.preventDefault();\n		var form_string = form2string(j_form);\n		sessionStorage.setItem(\'yab_copy_to_new_form\', form_string);\n		window.location.href = \'index.php?event=article\';\n	});\n\n		var form_string = sessionStorage.getItem(\'yab_copy_to_new_form\');\n		if (form_string) {\n			string2form(j_form, form_string);\n			sessionStorage.removeItem(\'yab_copy_to_new_form\');\n		}\n\n})();\n</script>\nEOF;\n\n	if ($event == \'article\')\n	{\n		echo $js;\n	}\n	return;\n}', '/**\n * yab_copy_to_new\n *\n * A Textpattern CMS plugin.\n * Copy the current article content to a new one.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2014-02-06\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\n/**\n * Config var\n *\n * @param string  $what\n * @return string\n */\nfunction yab_ctn_config($what)\n{\n	$config = array(\n		\'exclude\'           => \'[\"url_title\", \"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\"]\',\n		\'position_selector\' => \'#write-save\', // a valid jQery selector\n		\'position_method\'   => \'append\', // any jQuery DOM insert method\n		\'class\'             => \'publish\', // html class applied to element\n		\'style\'             => \'margin-left: 0.5em;\' // inline style attribute\n	);\n\n	// example config for button placed near top »Create new« button\n// 	$config = array(\n// 		\'exclude\'           => \'[\"url_title\", \"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\"]\',\n// 		\'position_selector\' => \'.action-create\', // a valid jQery selector\n// 		\'position_method\'   => \'append\', // any jQuery DOM insert method\n// 		\'class\'             => \'\', // html class applied to element\n// 		\'style\'             => \'margin-left: 0.5em;\' // inline style attribute\n// 	);\n\n	return $config[$what];\n}\n\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_copy_to_new\',\n		\'admin_side\',\n		\'body_end\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab.\n *\n * @return void Echos the JavaScript\n */\nfunction yab_copy_to_new()\n{\n	global $event;\n\n	// Config: name attribute for excluded fields (JavaScript array)\n	$exclude  = yab_ctn_config(\'exclude\');\n	$anchor   = yab_ctn_config(\'position_selector\');\n	$method   = yab_ctn_config(\'position_method\');\n	$class    = yab_ctn_config(\'class\');\n	$style    = yab_ctn_config(\'style\');\n\n	$buttontext = gTxt(\'yab_copy_to_new\');\n\n	$js = <<<EOF\n<script>\n(function() {\n\n	// ECMAScript < 5 indexOf method\n	if (!Array.prototype.indexOf) {\n		Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {\n			\"use strict\";\n			if (this == null) {\n				throw new TypeError();\n			}\n			var t = Object(this);\n			var len = t.length >>> 0;\n			if (len === 0) {\n				return -1;\n			}\n			var n = 0;\n			if (arguments.length > 0) {\n				n = Number(arguments[1]);\n				if (n != n) { // shortcut for verifying if it\'s NaN\n					n = 0;\n				} else if (n != 0 && n != Infinity && n != -Infinity) {\n					n = (n > 0 || -1) * Math.floor(Math.abs(n));\n				}\n			}\n			if (n >= len) {\n				return -1;\n			}\n			var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);\n			for (; k < len; k++) {\n				if (k in t && t[k] === searchElement) {\n					return k;\n				}\n			}\n			return -1;\n		}\n	}\n\n	var form2string = function(txp_form) {\n		return JSON.stringify(txp_form.serializeArray());\n	}\n\n	var string2form = function(txp_form, serialized) {\n		var excludes = $exclude;\n		var fields = JSON.parse(serialized);\n		var flength = fields.length;\n		for (var i = 0; i < flength; i++) {\n			var e_name = fields[i].name;\n			var e_value = fields[i].value;\n			var e = txp_form.find(\'[name=\"\' + e_name + \'\"]\');\n\n			if (e.is(\':hidden\') || excludes.indexOf(e_name) != -1) {\n				continue;\n			} else if (e.is(\':radio\')) {\n				e.filter(\'[value=\\\'\' + e_value + \'\\\']\').prop(\'checked\', true);\n			} else if (e.is(\':checkbox\') && e_value) {\n				e.prop(\'checked\', true);\n			} else if (e.is(\'select\')) {\n				e.find(\'[value=\\\'\' + e_value + \'\\\']\').prop(\'selected\', true);\n			} else {\n				e.val(e_value);\n			}\n		}\n	}\n\n	var j_form = $(\'#article_form\');\n\n	var button = \'<button id=\"yab-copy-to-new\" class=\"$class\" tabindex=\"5\" style=\"$style\">$buttontext</button>\';\n\n	$(\'$anchor\').$method(button);\n\n		j_form.on(\'click\', \'#yab-copy-to-new\', function(ev) {\n		ev.preventDefault();\n		var form_string = form2string(j_form);\n		sessionStorage.setItem(\'yab_copy_to_new_form\', form_string);\n		window.location.href = \'index.php?event=article\';\n	});\n\n		var form_string = sessionStorage.getItem(\'yab_copy_to_new_form\');\n		if (form_string) {\n			string2form(j_form, form_string);\n			sessionStorage.removeItem(\'yab_copy_to_new_form\');\n		}\n\n})();\n</script>\nEOF;\n\n	if ($event == \'article\')\n	{\n		echo $js;\n	}\n	return;\n}', 'f1435c9ed9dd9f76ed48e69dffad0123', '#@admin\n#@language en-gb\nyab_copy_to_new => Copy\n#@language en-us\nyab_copy_to_new => Copy\n#@language de-de\nyab_copy_to_new => Kopieren\n#@language fr-fr\nyab_copy_to_new => Copie\n#@language es-es\nyab_copy_to_new => Copia\n#@language it-it\nyab_copy_to_new => Copia\n#@language fi-fi\nyab_copy_to_new => Kopio\n#@language nl-nl\nyab_copy_to_new => Kopie\n#@language ru-ru\nyab_copy_to_new => Копия\n', '', 3, 5, 0),
('yab_remember_categories', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.2', 'Remembers the selected categories in write tab.', '	<h1>yab_remember_categories</h1>\n\n	<p>Remembers the selected categories in write tab.<br />\nDoes only work while creating articles not on editing existing articles.</p>\n\n	<p><strong>Version:</strong> 0.2</p>\n\n	<h2>Table of contents</h2>\n\n	<ol>\n		<li><a href=\"#help-section02\">Plugin requirements</a></li>\n		<li><a href=\"#help-config03\">Configuration</a></li>\n		<li><a href=\"#help-section10\">Changelog</a></li>\n		<li><a href=\"#help-section11\">License</a></li>\n		<li><a href=\"#help-section12\">Author contact</a></li>\n	</ol>\n\n	<h2 id=\"help-section02\">Plugin requirements</h2>\n\n	<p>yab_remember_categories&#8217;s  minimum requirements:</p>\n\n	<ul>\n		<li>Textpattern 4.x</li>\n	</ul>\n\n	<h2 id=\"help-config03\">Configuration</h2>\n\n	<p>Install and activate the plugin. The Plugin remembers the selected category 1 and category 2 in write tab while creating an article by saving with HTML5 localStorage.</p>\n\n	<h2 id=\"help-section10\">Changelog</h2>\n\n	<ul>\n		<li>v0.1: 2014-02-04\n	<ul>\n		<li>initial release</li>\n	</ul></li>\n		<li>v0.2: 2014-02-06\n	<ul>\n		<li>bugfix: does now only work on while in article create step</li>\n	</ul></li>\n	</ul>\n\n	<h2 id=\"help-section11\">Licence</h2>\n\n	<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above\n	<ul>\n		<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n		<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li>\n	</ul></p>\n\n	<h2 id=\"help-section12\">Author contact</h2>\n\n	<ul>\n		<li><a href=\"http://www.yablo.de/article/478/yab_remember_categories-remembers-the-selected-categories-in-write-tab\">Plugin on author&#8217;s site</a></li>\n		<li><a href=\"https://github.com/trenc/yab_remember_categories\">Plugin on GitHub</a></li>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?pid=278664\">Plugin on textpattern forum</a></li>\n		<li><a href=\"http://textpattern.org/plugins/1288/yab_remember_categories\">Plugin on textpattern.org</a></li>\n	</ul>', '/**\n * yab_remember_categories\n *\n * A Textpattern CMS plugin.\n * Remembers the selected categories in write tab\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2014-02-06\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_remember_categories\',\n		\'admin_side\',\n		\'body_end\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab, when article is created.\n *\n * @return void Echos the JavaScript\n */\nfunction yab_remember_categories()\n{\n	global $event, $step;\n\n	$js        = <<<EOF\n<script>\n(function() {\n	var cat1 = localStorage.getItem(\'category-1\');\n	var cat2 = localStorage.getItem(\'category-2\');\n	$(\'option[value=\"\' + cat1 + \'\"]\', \'#category-1\').prop(\'selected\', true);\n	$(\'option[value=\"\' + cat2 + \'\"]\', \'#category-2\').prop(\'selected\', true);\n\n	$(\'select\', \'#categories_group\').change(function() {\n		var val = $(this).val();\n		localStorage.setItem(this.id, val);\n	});\n})();\n</script>\nEOF;\n\n	if ($event == \'article\' and $step == \'create\')\n	{\n		echo $js;\n	}\n	return;\n}', '/**\n * yab_remember_categories\n *\n * A Textpattern CMS plugin.\n * Remembers the selected categories in write tab\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2014-02-06\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (@txpinterface == \'admin\')\n{\n	register_callback(\n		\'yab_remember_categories\',\n		\'admin_side\',\n		\'body_end\'\n	);\n}\n\n/**\n * Echo the plugin JavaScript on article write tab, when article is created.\n *\n * @return void Echos the JavaScript\n */\nfunction yab_remember_categories()\n{\n	global $event, $step;\n\n	$js        = <<<EOF\n<script>\n(function() {\n	var cat1 = localStorage.getItem(\'category-1\');\n	var cat2 = localStorage.getItem(\'category-2\');\n	$(\'option[value=\"\' + cat1 + \'\"]\', \'#category-1\').prop(\'selected\', true);\n	$(\'option[value=\"\' + cat2 + \'\"]\', \'#category-2\').prop(\'selected\', true);\n\n	$(\'select\', \'#categories_group\').change(function() {\n		var val = $(this).val();\n		localStorage.setItem(this.id, val);\n	});\n})();\n</script>\nEOF;\n\n	if ($event == \'article\' and $step == \'create\')\n	{\n		echo $js;\n	}\n	return;\n}', '0cc96dd23df520fe5b0969277a59e2fa', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('yab_review_rating', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.6', 'A comment based rating system for articles.', '<h1>yab_review_rating</h1>\n\n<p>A comment based rating system for articles.</p>\n\n<p><strong>Version:</strong> 0.6</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section02\">Plugin requirements</a></li>\n	<li><a href=\"#help-config03\">Configuration</a></li>\n	<li><a href=\"#help-section05\">Tags</a></li>\n	<li><a href=\"#help-section09\">Examples</a></li>\n	<li><a href=\"#help-section10\">Changelog</a></li>\n	<li><a href=\"#help-section11\">License</a></li>\n	<li><a href=\"#help-section12\">Author contact</a></li>\n</ol>\n\n<h2 id=\"help-section02\">Plugin requirements</h2>\n\n<p>yab_review_rating&#8217;s  minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.x</li>\n</ul>\n\n<h2 id=\"help-config03\">Configuration</h2>\n\n<p>Open the plugin code. the first function contains the configuration values. There is a min and a max values for the rating. Valid values are 0-255.</p>\n\n<h2 id=\"help-section05\">Tags</h2>\n\n<h3>yab_review_rating</h3>\n\n<p>Place this in your comment form. It will show the rating of the current comment.<br />\nCan be used elsewhere. If not used in comment context as comments_form or recent_comments you have to fill the id attribute.</p>\n\n<p><strong>id:</strong> integer (comment id)<br />\nDefault: <i>no set</i><br />\nShow the rating of a comment with this ID. Useful in a non comment context.</p>\n\n<p><strong>char:</strong> a valid string<br />\nDefault: <i>no set</i><br />\nIf empty (default) the output will be the rating number. If a char (e.g. a asterisk <code>*</code>) is set the output will be the n-times repeated char, where n is the rating.</p>\n\n<h3>yab_review_rating_input</h3>\n\n<p>The form element for the rating. Should be placed in the <code>comment_form</code> form.</p>\n\n<p><strong>type:</strong> input type (text, select, radio, number, range)<br />\nDefault: text<br />\nThe type of the form element for the rating. Valid value are <code>text</code>, <code>select</code>, <code>radio</code>, <code>number</code> and <code>range</code>.</p>\n\n<p><strong>html_id:</strong> <span class=\"caps\">HTML</span> id name<br />\nDefault: <i>not set</i><br />\nThe <span class=\"caps\">HTML</span> id attribute applied to the element.</p>\n\n<p><strong>class:</strong> <span class=\"caps\">HTML</span> class name<br />\nDefault: <i>not set</i><br />\nThe <span class=\"caps\">HTML</span>/CSS class attribute applied to the element.</p>\n\n<p><strong>reverse:</strong> integer|string (a non-null value)<br />\nDefault: 0<br />\nIf reverse is given the output of the select or radio type is displayed in reverse order.</p>\n\n<p><strong>break:</strong> breakpoint (br|__empty__)<br />\nDefault: &#8216;br&#8217;<br />\nBreakpoints für radio intputs. Can be empty or <code>br</code>.</p>\n\n<p><strong>default:</strong> integer<br />\nDefault: <i>not set</i><br />\nPreselected rating value (Could be any number between your min and max values).</p>\n\n<h3>yab_review_rating_average</h3>\n\n<p>Display the average rating for a given article.</p>\n\n<p><strong>id:</strong> string (comma-separated article ids)<br />\nDefault: <i>no set</i><br />\nThe IDs of articles. If not set it must be placed in an article form (article context).</p>\n\n<p><strong>only_visible:</strong> integer|bool (1|0)<br />\nDefault: 1<br />\nIf set to 0 all comments (spam and moderated comments too) will be calculated.</p>\n\n<p><strong>exclude:</strong> string (a comma separated list of ratings)<br />\nDefault: <i>null</i><br />\nExclude these ratings from the average rating calculation. So you can exclude &#8216;0&#8217; values for not rated articles, due 0 is the default value. Depending on your rating system setting.</p>\n\n<p><strong>default:</strong> string (Text)<br />\nDefault: &#8216;not yet rated&#8217;<br />\nThe default text on articles without a rating.</p>\n\n<p><strong>decimals:</strong> integer<br />\nDefault: 1<br />\nDefine the decimal precision of the calculation and the output.</p>\n\n<p><strong>separator:</strong> string (string|empty)<br />\nDefault: . (perdiod)<br />\nChoose your decimal separator. Can be empty (separator will be omitted) for <span class=\"caps\">HTML</span> class friendly output.</p>\n\n<p><strong>round_to_half</strong>: string (up|down|)<br />\nDefault: <i>no net</i><br />\nRound to first half integer up or down or not at all. If not set the last decimal is automatically rounded up.</p>\n\n<h2 id=\"help-section09\">Examples</h2>\n\n<h3>Example 1</h3>\n\n<p>Example of <code>yab_review_rating_input</code> in a <code>comment_form</code> form.</p>\n\n<pre><code>&lt;txp:comments_error wraptag=&quot;ul&quot; break=&quot;li&quot; /&gt;\n	&lt;div class=&quot;message&quot;&gt;\n		&lt;p&gt;&lt;label for=&quot;name&quot;&gt;Name:&lt;/label&gt;&lt;br /&gt;&lt;txp:comment_name_input /&gt;&lt;/p&gt;\n		&lt;p&gt;&lt;label for=&quot;email&quot;&gt;Mail (not required, not visible):&lt;/label&gt;&lt;br /&gt;\n			&lt;txp:comment_email_input /&gt;&lt;/p&gt;\n		&lt;p&gt;&lt;label for=&quot;yab-rr-rating&quot;&gt;Rating&lt;/label&gt;&lt;br /&gt;\n			&lt;txp:yab_review_rating_input html_id=&quot;yab-rr-rating&quot; type=&quot;select&quot; reverse=&quot;1&quot; default=&quot;3&quot; /&gt;&lt;/p&gt;\n		&lt;p&gt;&lt;label for=&quot;message&quot;&gt;Review:&lt;/label&gt;&lt;br /&gt;\n			&lt;txp:comment_message_input /&gt;&lt;/p&gt;\n		&lt;p class=&quot;submit&quot;&gt;&lt;txp:comments_help /&gt;&lt;txp:comment_preview /&gt;&lt;txp:comment_submit /&gt;&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n\n<p>Will produce a comment form for article reviews (e.g. with yab_shop). The select dropdown menu for the rating is in reversed order (highest top) and the preselected rating value is 3.</p>\n\n<h3>Example 2</h3>\n\n<p>Example of <code>yab_review_rating</code> in a <code>comments</code> form.</p>\n\n<pre><code>&lt;h3 class=&quot;commenthead&quot;&gt;&lt;txp:comment_permlink&gt;#&lt;/txp:comment_permlink&gt; - &lt;txp:comment_name /&gt; wrote at &lt;txp:comment_time /&gt;:&lt;/h3&gt;\n&lt;span class=&quot;rating&quot;&gt;Rating: &lt;txp:yab_review_rating char=&quot;*&quot; /&gt;&lt;/span&gt;\n&lt;txp:comment_message /&gt;</code></pre>\n\n<p>Will produce a comment/review with the name, text and time of the comment and the rating with asterisks <code>*</code>.</p>\n\n<h3>Example 3</h3>\n\n<p>Example of <code>yab_review_rating</code> in a <code>comments</code> form.</p>\n\n<pre><code>&lt;h3 class=&quot;commenthead&quot;&gt;&lt;txp:comment_permlink&gt;#&lt;/txp:comment_permlink&gt; - &lt;txp:comment_name /&gt; wrote at &lt;txp:comment_time /&gt;:&lt;/h3&gt;\n&lt;span class=&quot;rating rating-value-&lt;txp:yab_review_rating /&gt;&quot;&gt;Rating:&lt;/span&gt;\n&lt;txp:comment_message /&gt;</code></pre>\n\n<p>Will produce a the a comment/review with the name, text and time of the comment and the rating as <span class=\"caps\">HTML</span>/CSS class.</p>\n\n<h3>Example 4</h3>\n\n<p>Example <code>yab_review_rating_average</code>.</p>\n\n<pre><code>&lt;txp:yab_review_rating_average id=&quot;12&quot; exclude=&quot;0&quot; decimals=&quot;2&quot; separator=&quot;&quot; round_to_half=&quot;down&quot; /&gt;</code></pre>\n\n<p>Say the article with the ID 12 do have 3 reviews: One with a rating of 0 and two with a rating of 4 each. The output will exclude the 0 from the calculation. So only the two 4-ratings will be used 4+4 = 8÷2 = 4. Average rating is 4. But we have decimals precision of 2, so it will be 4.00. No rounding required but the separator will be ommitted: 400 will be displayed.<br />\nexclude=&#8220;0&#8221; decimals=&#8220;2&#8221; separator=&#8221;&#8220; round_to_half=&#8220;down&#8221; /&gt;</p>\n\n<pre><code>&lt;txp:yab_review_rating_average id=&quot;12&quot; decimals=&quot;2&quot; separator=&quot;&quot; round_to_half=&quot;down&quot; /&gt;</code></pre>\n\n<p>Here we calculate an average from all reviews/ratings. Like above we have two 4 and 0-rating. So the rating is 0+4+4 = 8÷3 = 2.6666666667. Now we round to half down: 2.500000000 and use the decimal precision of 2: 2.50 and ommit the separator: 250.</p>\n\n<h2 id=\"help-section10\">Changelog</h2>\n\n<ul>\n	<li>v0.1: 2013-12-24\n	<ul>\n		<li>initial release</li>\n	</ul></li>\n	<li>v0.2: 2014-01-08\n	<ul>\n		<li>new: added a the tag <code>&lt;txp:yab_review_rating_average /&gt;</code></li>\n	</ul></li>\n	<li>v0.3: 2014-01-12\n	<ul>\n		<li>new: added the id attribute to <code>&lt;txp:yab_review_rating /&gt;</code></li>\n		<li>modify: <code>&lt;txp:yab_review_rating /&gt;</code> can now be used in <code>&lt;txp:recent_comments /&gt;</code></li>\n	</ul></li>\n	<li>v0.4: 2014-01-16\n	<ul>\n		<li>new: added reverse attribute to <code>&lt;txp:yab_review_rating_input /&gt;</code></li>\n		<li>new: added only_visible attribute to <code>&lt;txp:yab_review_rating_average /&gt;</code></li>\n		<li>modify: id attribute of <code>&lt;txp:yab_review_rating_average /&gt;</code> can now contain list of article ids</li>\n	</ul></li>\n	<li>v0.5: 2017-02-10\n	<ul>\n		<li><span class=\"caps\">TXP</span> 4.6-ready</li>\n	</ul></li>\n	</ul>\n\n<h2 id=\"help-section11\">Licence</h2>\n\n<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above<br />\n<ul>\n	<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n	<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li><br />\n</ul></p>\n\n<h2 id=\"help-section12\">Author contact</h2>\n\n<ul>\n	<li><a href=\"http://www.yablo.de/article/475/yab_review_rating-a-comment-based-rating-system-for-textpattern\">Plugin on author&#8217;s site</a></li>\n	<li><a href=\"https://github.com/trenc/yab_review_rating\">Plugin on GitHub</a></li>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=40374\">Plugin on textpattern forum</a></li>\n	<li><a href=\"http://textpattern.org/plugins/1285/yab_review_rating\">Plugin on textpattern.org</a></li>\n</ul>', '/**\n * yab_review_rating\n *\n * A Textpattern CMS plugin.\n * A comment based rating system for articles.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2013-12-24\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_review_rating\')\n		->register(\'yab_review_rating_input\')\n		->register(\'yab_review_rating_average\');\n}\n\n\n/**\n * Config function holder to avoid some globals\n * Can later be changed to receive config from database\n *\n * @param  string $name name of the config\n * @return string\n */\nfunction yab_rr_config($name)\n{\n	$config = array(\n		\'min\' => 0, // min value of the rating (0-255)\n		\'max\' => 5  // max value of the rating (0-255)\n	);\n\n	return $config[$name];\n}\n\n// admin callbacks\nif (@txpinterface == \'admin\') {\n	register_callback(\n		\'yab_rr_install\',\n		\'plugin_lifecycle.yab_review_rating\',\n		\'installed\'\n	);\n	register_callback(\n		\'yab_rr_discuss_ui\',\n		\'admin_side\',\n		\'body_end\'\n	);\n	register_callback(\n		\'yab_rr_discuss_save\',\n		\'discuss\',\n		\'discuss_save\'\n	);\n}\n\n// public callbacks\nregister_callback(\'yab_rr_save\', \'comment.saved\');\nregister_callback(\'yab_rr_check\', \'comment.save\');\n\n/**\n * Textpattern tag\n * Display the rating average for a given article\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating_average($atts)\n{\n	global $thisarticle;\n\n	extract(\n		lAtts(\n			array(\n				\'id\'             => \'\', // article ids (comma separated) or empty in article context\n				\'exclude\'        => null, // exclude ratings from calculations (e.g. 0)\n				\'only_visible\'   => 1, // show all or only visible comments/reviews\n				\'default\'        => \'not yet rated\', // default text on articles without rating\n				\'decimals\'       => 1, // precision of the calculation\n				\'separator\'      => \'.\', // decimal separator\n				\'round_to_half\'  => \'\' // round to first half integer up or down or not at all (up|down|empty)\n			), $atts\n		)\n	);\n\n	$exclude_rating = \'\';\n	$average        = $default;\n\n	if ($id)\n	{\n		$id = do_list($id);\n		$id = join(\"\',\'\", doSlash($id));\n	}\n	else\n	{\n		assert_article();\n		$id = $thisarticle[\'thisid\'];\n	}\n	$parentid = \"parentid IN (\'$id\')\";\n\n	$visible = \'\';\n	if ($only_visible)\n	{\n		$visible = \"AND visible = 1\";\n	}\n\n	if ($exclude !== null)\n	{\n		$exclude_rating = do_list($exclude);\n		$exclude_rating = join(\"\',\'\", doSlash($exclude_rating));\n		$exclude_rating = \"AND yab_rr_rating NOT IN (\'$exclude_rating\')\";\n	}\n\n	$rs = safe_rows(\n		\'yab_rr_rating\',\n		\'txp_discuss\',\n		\"$parentid $visible $exclude_rating\"\n	);\n\n	if ($rs)\n	{\n		$count   = sizeof($rs);\n		$sum     = array_map(\'yab_rr_get_array_column\', $rs);\n		$sum     = array_sum($sum);\n		$average = $sum / $count;\n\n		if ($round_to_half)\n		{\n			if ($round_to_half == \'down\')\n			{\n				$average = floor($average * 2) / 2;\n			}\n			else\n			{\n				$average = ceil($average * 2) / 2;\n			}\n		}\n\n		$average = number_format($average, $decimals, $separator, \'\');\n	}\n\n	return $average;\n}\n\n/**\n * Get the rating column of the safe_rows() array\n * Is used as array_map function to build a array_sum array\n *\n * @param  array $element Array\n * @return string         yab_rr_rating Column from param array\n */\nfunction yab_rr_get_array_column($element)\n{\n	return $element[\'yab_rr_rating\'];\n}\n\n/**\n * Save the rating\n * Adminside Textpattern callback function\n * Fired after discuss is saved\n *\n * @param  string $event Textpattern admin event\n @ @param  string $step  Textpattern admin step\n * @return boolean      true on success\n */\nfunction yab_rr_discuss_save($event, $step)\n{\n	$discussid = doSlash(assert_int(ps(\'discussid\')));\n	$rating    = doSlash(intval(ps(\'yab_rr_rating\')));\n\n	$rs = safe_update(\n		\'txp_discuss\',\n		\"yab_rr_rating = \'$rating\'\",\n		\"discussid = \'$discussid\'\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Show Rating Input field in discuss edit panel\n * Adminside Textpattern callback function\n * Fired at body_end in ui\n *\n * @return mixed string in Textpattern admin step \'discuss_edit\' | false\n */\nfunction yab_rr_discuss_ui()\n{\n	global $step;\n\n	// be sure we are in discuss edit area\n	if ($step != \'discuss_edit\')\n	{\n		return false;\n	}\n\n	$discussid = gps(\'discussid\');\n	$rating    = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n	$label     = gTxt(\'yab_rating_label\');\n	$js        = <<<EOF\n<script>\n(function() {\n	var yab_rr_js = \'<div class=\"txp-form-field edit-comment-yab-rating\"><div class=\"txp-form-field-label\"><label for=\"yab_rr_rating\">$label</label></div><div class=\"txp-form-field-value\"><input type=\"text\" value=\"$rating\" name=\"yab_rr_rating\" size=\"32\" id=\"yab_rr_rating\"></div></div>\';\n	$(\'.edit-comment-name\', \'#discuss_edit_form\').after(yab_rr_js);\n})();\n</script>\nEOF;\n\n	echo $js;\n}\n\n/**\n * Textpattern tag\n * Display the rating\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating($atts)\n{\n	global $thiscomment;\n\n	extract(\n		lAtts(\n			array(\n				\'id\'  => \'\', // id of the comment\n				\'char\' => \'\' // type of input, if empty number is displayed\n			), $atts\n		)\n	);\n\n	// commentid is given, serve before all othe\n	if ($id)\n	{\n		$discussid = (int) $id;\n		$rating = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n	}\n	else\n	{\n		// normal comment list\n		if (isset($thiscomment[\'yab_rr_rating\']))\n		{\n			$rating = $thiscomment[\'yab_rr_rating\'];\n		}\n		// recent comments\n		elseif (isset($thiscomment[\'discussid\']))\n		{\n			$discussid = $thiscomment[\'discussid\'];\n			$rating    = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n		}\n		// comment preview\n		else\n		{\n			$rating = intval(ps(\'yab_rr_value\'));\n		}\n	}\n\n	if ($char)\n	{\n		$chars = \'\';\n		for ($i = 0; $i < $rating; $i++)\n		{\n			$chars .= $char;\n		}\n		return $chars;\n	}\n\n	return $rating;\n}\n\n/**\n * Check rating value and\n * Public callback function\n * Fired after comment is send\n *\n * @return void Die on invalid value\n */\nfunction yab_rr_check()\n{\n	$min    = yab_rr_config(\'min\');\n	$max    = yab_rr_config(\'max\');\n	$rating = intval(ps(\'yab_rr_value\'));\n\n	if ($rating < $min or $rating > $max)\n	{\n		txp_die(\n			\'Unable to record the comment. No valid rating value.\',\n			\'412 Precondition failed\');\n	}\n}\n\n/**\n * Save the rating after the comment is saved\n * Public callback function\n * Fired after comment is saved\n *\n * @param  string  $event   public Textpattern event\n * @param  string  $step    public Textpattern step\n * @param  array   $comment array of name-value pairs of saved comment\n * @return boolean true on success\n */\nfunction yab_rr_save($event, $step, $comment)\n{\n	$id     = $comment[\'commentid\'];\n	$rating = doSlash(intval(ps(\'yab_rr_value\')));\n\n	$rs = safe_update(\n		\'txp_discuss\',\n		\"yab_rr_rating = \'$rating\'\",\n		\"discussid = \'$id\'\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Textpattern tag\n * Display the rating input in the comment form\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating_input($atts)\n{\n	extract(\n		lAtts(\n			array(\n				\'type\'    => \'text\', // select, text, radio, number, range\n				\'html_id\' => \'\', // HTML id to apply the item attribute value\n				\'class\'   => \'yab-rr-review\', // HTML class to apply the item attribute value\n				\'break\'   => \'br\', // br or empty\n				\'reverse\' => 0, // reverse order for radio and select types\n				\'default\' => \'\' // preselected rating value\n			), $atts\n		)\n	);\n\n	$selector_attributes = \'\';\n	$min                 = yab_rr_config(\'min\');\n	$max                 = yab_rr_config(\'max\');\n\n	if (!$default)\n	{\n		$default = $min;\n	}\n\n	// show selected value on comment preview\n	if (ps(\'preview\'))\n	{\n		$default = intval(ps(\'yab_rr_value\'));\n	}\n\n	$value_attribute = \' value=\"\'.$default.\'\"\';\n\n	if ($class)\n	{\n		$selector_attributes .= \' class=\"\'.$class.\'\"\';\n	}\n\n	if ($html_id)\n	{\n		$selector_attributes .= \' id=\"\'.$html_id.\'\"\';\n	}\n\n	switch ($type)\n	{\n		case \'select\':\n			$options = array();\n			for ($i = $min; $i <= $max; $i++)\n			{\n				$selected = \'\';\n				if ($i == $default)\n				{\n					$selected = \' selected=\"selected\"\';\n				}\n				$options[] = \'<option value=\"\'.$i.\'\"\'.$selected.\'>\'.$i.\'</option>\';\n			}\n			if ($reverse)\n			{\n				$options = array_reverse($options);\n			}\n				$out = doWrap(\n					$options,\n					\'select\',\n					\'\',\n					\'\',\n					\'\',\n					\' name=\"yab_rr_value\"\'.$selector_attributes\n				);\n			break;\n		case \'radio\':\n			$radios = array();\n			for ($i = $min; $i <= $max; $i++)\n			{\n				$checked = \'\';\n				if ($i == $default)\n				{\n					$checked = \' checked=\"checked\"\';\n				}\n				$radios[] = \'<label for=\"yab-rr-\'.$i.\'\">\'.$i.\'</label>\'\n					.\'<input  name=\"yab_rr_value\" type=\"\'.$type.\'\"\'\n					.$checked\n					.\' value=\"\'.$i.\'\"\'\n					.\' id=\"yab-rr-\'.$i.\'\"\'\n				.\' />\';\n			}\n			if ($reverse)\n			{\n				$radios = array_reverse($radios);\n			}\n			$out = doWrap($radios, \'\', $break);\n			break;\n		case \'text\':\n			$out = \'<input name=\"yab_rr_value\" type=\"\'.$type.\'\"\'\n				.$selector_attributes\n				.$value_attribute\n			.\' />\';\n			break;\n		case \'number\':\n		case \'range\':\n			$out = \'<input name=\"yab_rr_value\"  type=\"\'.$type.\'\"\'\n				.$selector_attributes\n				.$value_attribute\n				.\' min=\"\'.$min.\'\" max=\"\'.$max.\'\"\'\n			.\' />\';\n			break;\n		default:\n			$out = \'\';\n			break;\n	}\n\n	return $out;\n}\n\n/**\n * Install the rating column inside txp_discuss\n * It is an unsigned tinyint type so 0-255 are valid values\n *\n * @return void\n */\nfunction yab_rr_install()\n{\n	$columns = getRows(\'show columns from \'.safe_pfx(\'txp_discuss\'));\n	foreach($columns as $column)\n	{\n		if ($column[\'Field\'] == \'yab_rr_rating\')\n		{\n			return;\n		}\n	}\n	safe_query(\n		\"ALTER TABLE \"\n		.safe_pfx(\"txp_discuss\")\n		.\" ADD yab_rr_rating TINYINT UNSIGNED NOT NULL DEFAULT \'0\';\"\n	);\n}', '/**\n * yab_review_rating\n *\n * A Textpattern CMS plugin.\n * A comment based rating system for articles.\n *\n * @author Tommy Schmucker\n * @link   http://www.yablo.de/\n * @link   http://tommyschmucker.de/\n * @date   2013-12-24\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_review_rating\')\n		->register(\'yab_review_rating_input\')\n		->register(\'yab_review_rating_average\');\n}\n\n\n/**\n * Config function holder to avoid some globals\n * Can later be changed to receive config from database\n *\n * @param  string $name name of the config\n * @return string\n */\nfunction yab_rr_config($name)\n{\n	$config = array(\n		\'min\' => 0, // min value of the rating (0-255)\n		\'max\' => 5  // max value of the rating (0-255)\n	);\n\n	return $config[$name];\n}\n\n// admin callbacks\nif (@txpinterface == \'admin\') {\n	register_callback(\n		\'yab_rr_install\',\n		\'plugin_lifecycle.yab_review_rating\',\n		\'installed\'\n	);\n	register_callback(\n		\'yab_rr_discuss_ui\',\n		\'admin_side\',\n		\'body_end\'\n	);\n	register_callback(\n		\'yab_rr_discuss_save\',\n		\'discuss\',\n		\'discuss_save\'\n	);\n}\n\n// public callbacks\nregister_callback(\'yab_rr_save\', \'comment.saved\');\nregister_callback(\'yab_rr_check\', \'comment.save\');\n\n/**\n * Textpattern tag\n * Display the rating average for a given article\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating_average($atts)\n{\n	global $thisarticle;\n\n	extract(\n		lAtts(\n			array(\n				\'id\'             => \'\', // article ids (comma separated) or empty in article context\n				\'exclude\'        => null, // exclude ratings from calculations (e.g. 0)\n				\'only_visible\'   => 1, // show all or only visible comments/reviews\n				\'default\'        => \'not yet rated\', // default text on articles without rating\n				\'decimals\'       => 1, // precision of the calculation\n				\'separator\'      => \'.\', // decimal separator\n				\'round_to_half\'  => \'\' // round to first half integer up or down or not at all (up|down|empty)\n			), $atts\n		)\n	);\n\n	$exclude_rating = \'\';\n	$average        = $default;\n\n	if ($id)\n	{\n		$id = do_list($id);\n		$id = join(\"\',\'\", doSlash($id));\n	}\n	else\n	{\n		assert_article();\n		$id = $thisarticle[\'thisid\'];\n	}\n	$parentid = \"parentid IN (\'$id\')\";\n\n	$visible = \'\';\n	if ($only_visible)\n	{\n		$visible = \"AND visible = 1\";\n	}\n\n	if ($exclude !== null)\n	{\n		$exclude_rating = do_list($exclude);\n		$exclude_rating = join(\"\',\'\", doSlash($exclude_rating));\n		$exclude_rating = \"AND yab_rr_rating NOT IN (\'$exclude_rating\')\";\n	}\n\n	$rs = safe_rows(\n		\'yab_rr_rating\',\n		\'txp_discuss\',\n		\"$parentid $visible $exclude_rating\"\n	);\n\n	if ($rs)\n	{\n		$count   = sizeof($rs);\n		$sum     = array_map(\'yab_rr_get_array_column\', $rs);\n		$sum     = array_sum($sum);\n		$average = $sum / $count;\n\n		if ($round_to_half)\n		{\n			if ($round_to_half == \'down\')\n			{\n				$average = floor($average * 2) / 2;\n			}\n			else\n			{\n				$average = ceil($average * 2) / 2;\n			}\n		}\n\n		$average = number_format($average, $decimals, $separator, \'\');\n	}\n\n	return $average;\n}\n\n/**\n * Get the rating column of the safe_rows() array\n * Is used as array_map function to build a array_sum array\n *\n * @param  array $element Array\n * @return string         yab_rr_rating Column from param array\n */\nfunction yab_rr_get_array_column($element)\n{\n	return $element[\'yab_rr_rating\'];\n}\n\n/**\n * Save the rating\n * Adminside Textpattern callback function\n * Fired after discuss is saved\n *\n * @param  string $event Textpattern admin event\n @ @param  string $step  Textpattern admin step\n * @return boolean      true on success\n */\nfunction yab_rr_discuss_save($event, $step)\n{\n	$discussid = doSlash(assert_int(ps(\'discussid\')));\n	$rating    = doSlash(intval(ps(\'yab_rr_rating\')));\n\n	$rs = safe_update(\n		\'txp_discuss\',\n		\"yab_rr_rating = \'$rating\'\",\n		\"discussid = \'$discussid\'\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Show Rating Input field in discuss edit panel\n * Adminside Textpattern callback function\n * Fired at body_end in ui\n *\n * @return mixed string in Textpattern admin step \'discuss_edit\' | false\n */\nfunction yab_rr_discuss_ui()\n{\n	global $step;\n\n	// be sure we are in discuss edit area\n	if ($step != \'discuss_edit\')\n	{\n		return false;\n	}\n\n	$discussid = gps(\'discussid\');\n	$rating    = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n	$label     = gTxt(\'yab_rating_label\');\n	$js        = <<<EOF\n<script>\n(function() {\n	var yab_rr_js = \'<div class=\"txp-form-field edit-comment-yab-rating\"><div class=\"txp-form-field-label\"><label for=\"yab_rr_rating\">$label</label></div><div class=\"txp-form-field-value\"><input type=\"text\" value=\"$rating\" name=\"yab_rr_rating\" size=\"32\" id=\"yab_rr_rating\"></div></div>\';\n	$(\'.edit-comment-name\', \'#discuss_edit_form\').after(yab_rr_js);\n})();\n</script>\nEOF;\n\n	echo $js;\n}\n\n/**\n * Textpattern tag\n * Display the rating\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating($atts)\n{\n	global $thiscomment;\n\n	extract(\n		lAtts(\n			array(\n				\'id\'  => \'\', // id of the comment\n				\'char\' => \'\' // type of input, if empty number is displayed\n			), $atts\n		)\n	);\n\n	// commentid is given, serve before all othe\n	if ($id)\n	{\n		$discussid = (int) $id;\n		$rating = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n	}\n	else\n	{\n		// normal comment list\n		if (isset($thiscomment[\'yab_rr_rating\']))\n		{\n			$rating = $thiscomment[\'yab_rr_rating\'];\n		}\n		// recent comments\n		elseif (isset($thiscomment[\'discussid\']))\n		{\n			$discussid = $thiscomment[\'discussid\'];\n			$rating    = safe_field(\'yab_rr_rating\', \'txp_discuss\', \"discussid = $discussid\");\n		}\n		// comment preview\n		else\n		{\n			$rating = intval(ps(\'yab_rr_value\'));\n		}\n	}\n\n	if ($char)\n	{\n		$chars = \'\';\n		for ($i = 0; $i < $rating; $i++)\n		{\n			$chars .= $char;\n		}\n		return $chars;\n	}\n\n	return $rating;\n}\n\n/**\n * Check rating value and\n * Public callback function\n * Fired after comment is send\n *\n * @return void Die on invalid value\n */\nfunction yab_rr_check()\n{\n	$min    = yab_rr_config(\'min\');\n	$max    = yab_rr_config(\'max\');\n	$rating = intval(ps(\'yab_rr_value\'));\n\n	if ($rating < $min or $rating > $max)\n	{\n		txp_die(\n			\'Unable to record the comment. No valid rating value.\',\n			\'412 Precondition failed\');\n	}\n}\n\n/**\n * Save the rating after the comment is saved\n * Public callback function\n * Fired after comment is saved\n *\n * @param  string  $event   public Textpattern event\n * @param  string  $step    public Textpattern step\n * @param  array   $comment array of name-value pairs of saved comment\n * @return boolean true on success\n */\nfunction yab_rr_save($event, $step, $comment)\n{\n	$id     = $comment[\'commentid\'];\n	$rating = doSlash(intval(ps(\'yab_rr_value\')));\n\n	$rs = safe_update(\n		\'txp_discuss\',\n		\"yab_rr_rating = \'$rating\'\",\n		\"discussid = \'$id\'\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Textpattern tag\n * Display the rating input in the comment form\n *\n * @param  array $atts Array of Textpattern tag attributes\n * @return string\n */\nfunction yab_review_rating_input($atts)\n{\n	extract(\n		lAtts(\n			array(\n				\'type\'    => \'text\', // select, text, radio, number, range\n				\'html_id\' => \'\', // HTML id to apply the item attribute value\n				\'class\'   => \'yab-rr-review\', // HTML class to apply the item attribute value\n				\'break\'   => \'br\', // br or empty\n				\'reverse\' => 0, // reverse order for radio and select types\n				\'default\' => \'\' // preselected rating value\n			), $atts\n		)\n	);\n\n	$selector_attributes = \'\';\n	$min                 = yab_rr_config(\'min\');\n	$max                 = yab_rr_config(\'max\');\n\n	if (!$default)\n	{\n		$default = $min;\n	}\n\n	// show selected value on comment preview\n	if (ps(\'preview\'))\n	{\n		$default = intval(ps(\'yab_rr_value\'));\n	}\n\n	$value_attribute = \' value=\"\'.$default.\'\"\';\n\n	if ($class)\n	{\n		$selector_attributes .= \' class=\"\'.$class.\'\"\';\n	}\n\n	if ($html_id)\n	{\n		$selector_attributes .= \' id=\"\'.$html_id.\'\"\';\n	}\n\n	switch ($type)\n	{\n		case \'select\':\n			$options = array();\n			for ($i = $min; $i <= $max; $i++)\n			{\n				$selected = \'\';\n				if ($i == $default)\n				{\n					$selected = \' selected=\"selected\"\';\n				}\n				$options[] = \'<option value=\"\'.$i.\'\"\'.$selected.\'>\'.$i.\'</option>\';\n			}\n			if ($reverse)\n			{\n				$options = array_reverse($options);\n			}\n				$out = doWrap(\n					$options,\n					\'select\',\n					\'\',\n					\'\',\n					\'\',\n					\' name=\"yab_rr_value\"\'.$selector_attributes\n				);\n			break;\n		case \'radio\':\n			$radios = array();\n			for ($i = $min; $i <= $max; $i++)\n			{\n				$checked = \'\';\n				if ($i == $default)\n				{\n					$checked = \' checked=\"checked\"\';\n				}\n				$radios[] = \'<label for=\"yab-rr-\'.$i.\'\">\'.$i.\'</label>\'\n					.\'<input  name=\"yab_rr_value\" type=\"\'.$type.\'\"\'\n					.$checked\n					.\' value=\"\'.$i.\'\"\'\n					.\' id=\"yab-rr-\'.$i.\'\"\'\n				.\' />\';\n			}\n			if ($reverse)\n			{\n				$radios = array_reverse($radios);\n			}\n			$out = doWrap($radios, \'\', $break);\n			break;\n		case \'text\':\n			$out = \'<input name=\"yab_rr_value\" type=\"\'.$type.\'\"\'\n				.$selector_attributes\n				.$value_attribute\n			.\' />\';\n			break;\n		case \'number\':\n		case \'range\':\n			$out = \'<input name=\"yab_rr_value\"  type=\"\'.$type.\'\"\'\n				.$selector_attributes\n				.$value_attribute\n				.\' min=\"\'.$min.\'\" max=\"\'.$max.\'\"\'\n			.\' />\';\n			break;\n		default:\n			$out = \'\';\n			break;\n	}\n\n	return $out;\n}\n\n/**\n * Install the rating column inside txp_discuss\n * It is an unsigned tinyint type so 0-255 are valid values\n *\n * @return void\n */\nfunction yab_rr_install()\n{\n	$columns = getRows(\'show columns from \'.safe_pfx(\'txp_discuss\'));\n	foreach($columns as $column)\n	{\n		if ($column[\'Field\'] == \'yab_rr_rating\')\n		{\n			return;\n		}\n	}\n	safe_query(\n		\"ALTER TABLE \"\n		.safe_pfx(\"txp_discuss\")\n		.\" ADD yab_rr_rating TINYINT UNSIGNED NOT NULL DEFAULT \'0\';\"\n	);\n}', 'c98802be61e74f8d822d0573434b10da', '#@admin\n#@language en-gb\nyab_rating_label => Rating\n#@language en-us\nyab_rating_label => Rating\n#@language de-de\nyab_rating_label => Bewertung\n#@language fr-fr\nyab_rating_label => Évaluation\n#@language es-es\nyab_rating_label => Clasificación\n#@language it-it\nyab_rating_label => valutazione\n#@language fi-fi\nyab_rating_label => Luokitus\n#@language nl-nl\nyab_rating_label => Aanslag\n#@language ru-ru\nyab_rating_label => Pейтинг\n', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('yab_navigation', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.1.beta', 'Create and manage section/article based navigation', '	<h1>yab_navigation plugin help</h1>\n\n	<p class=\"info\">There are a couple of browser issues this time, namely for Opera, all Internet Explorer versions and Konqueror. Some are <span class=\"caps\">CSS</span>-related, some Javascript related. Because of these issues this is a beta release. I will try to fix this issues asap.</p>\n\n	<h2>What you need to know</h2>\n\n	<p>The navigation used and generated by yab_navigation is based of a »simple« template of Textpattern tags (<code>&lt;txp:variable /&gt;</code>, <code>&lt;txp:section_list /&gt;</code>, <code>&lt;txp:article_custom /&gt;</code> etc.). This navigation will be saved in form called »yab_navigation« (Opps!). This form will be created automatically.</p>\n\n	<p>Due the navigation is saved in this form you can call the navigation with the following command in your page template:</p>\n\n	<p><code>&lt;txp:output_form form=&quot;yab_navigation&quot; /&gt;</code></p>\n\n	<p>And your articles you can call simply with</p>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n  &lt;txp:article status=&quot;4&quot; limit=&quot;1&quot; /&gt;\n&lt;txp:else /&gt;\n  &lt;txp:article status=&quot;5&quot; limit=&quot;1&quot; /&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>in your page template(s).</p>\n\n	<p><strong>For your frontpage you need a section with the value »On front page« »yes« and the same title as the home_title in the configuration (see below).</strong></p>\n\n	<p>That&#8217;s all actually, but</p>\n\n	<h2>What you really need to know</h2>\n\n	<p>Due Textpattern sections and articles are completly different in behaviour and database structure and they are not only pages &#8211; as maybe known from other <span class=\"caps\">CMS</span> &#8211; this plugin can only be an approach, of course. For understanding the concept I have to explain something.</p>\n\n	<p>The first section on the left side in your backend navigation will represent your frontpage aka your home. So the name of this section is only showed in the plugin, The real name, which is showed as a link in your frontend navigation is set in the config (see below).</p>\n\n	<p>The first article (first listpoint) of every section in the backend navigation will represent the parent section in the frontend navigation. This article will always be fixed (status 5) and is not displayed in the frontend navigation. So the name of the article is only showed in the backend navigation.</p>\n\n	<p>The rest of the articles will be shown in the frontend navigation in the order you choose.</p>\n\n	<h2>What you maybe need to know</h2>\n\n	<p>When you install the plugin and you go to the backend navigation, you whole navigation is shown of the right.</p>\n\n	<p><strong>Reload the site again</strong></p>\n\n	<p>Now your navigation should be shown on the left side but maybe it looks weird. This is because the plugin reads the whole stuff from database and not from a navigation template, because this is still empty. You have to move a section to force the generating of the navigation template. If done, reload the backend page again.</p>\n\n	<p>Now you should see on the right side a place where you can see hidden or new detected sections &#8211; if they exist.</p>\n\n	<p>If something won&#8217;t work or you can&#8217;t see the navigation on the left side, go to you form templates and empty the yab_navigation form. Then begin anew in the navigation backend.</p>\n\n	<h2>Useful things you need to know</h2>\n\n	<p>Every new created section is hidden by default, you have to move it from the right side in your backend navigation. Every created article is hidden by default, except it is the first article of a section, then it&#8217;s fixed.</p>\n\n	<p>Btw. hiding the first article of a section doesn&#8217;t really make sense. :)</p>\n\n	<p>Renaming a section or an article will <strong>not</strong> touch the name of section or the <span class=\"caps\">URI</span> of the article. It will only rename the title of the element. I&#8217;ve decided for this behaviour because of the <span class=\"caps\">URI</span> consistence (»Cool <span class=\"caps\">URI</span>&#8217;s don&#8217;t change!«).</p>\n\n	<p>If you ever delete or rename a section in the Textpattern section menu you are lost.<br />\nNot really but &#8230;<br />\nIf you have deleted a section this way you only have to hide this section in backend navigation. Next time (or after an reload) this section is vanished.<br />\nIf you have renamed a section this way &#8230; uh &#8230; fucked up! You have have to empty the <code>yab_navigation</code> form and build your navigation in the backend anew. Sorry!</p>\n\n	<h2>Really useful things you need to know</h2>\n\n	<p>There are configuration options for the plugin. You will find it as an array <code>$yab_nav_prefs</code> in the plugin code:</p>\n\n	<table id=\"yab-nav\">\n		<tr>\n			<th>Option</th>\n			<th>Description</th>\n			<th>Default</th>\n		</tr>\n		<tr>\n			<td>home_title</td>\n			<td>The title of the frontpage. Has to be the same title as your frontpage section title.</td>\n			<td>Home</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The sort order for the articles</td>\n			<td>position asc</td>\n		</tr>\n		<tr>\n			<td>active_class</td>\n			<td>Active class for sections or articles</td>\n			<td>active</td>\n		</tr>\n		<tr>\n			<td>passive_class</td>\n			<td>Passive class for sections or articles</td>\n			<td>passive</td>\n		</tr>\n		<tr>\n			<td>exclude_sections</td>\n			<td>Here you can completly exclude sections from navigation listing (comma delimited)</td>\n			<td>(empty)</td>\n		</tr>\n		<tr>\n			<td>exclude_articles</td>\n			<td>Here you can set sections, which articles will not listed in the navigation. Useful for news section or section which not have a single article as content. And you can&#8217;t move other articles in this section in the backend. (comma delimted)</td>\n			<td>(empty)</td>\n		</tr>\n		<tr>\n			<td>section_default_page</td>\n			<td>Default page template when creating a section</td>\n			<td>default</td>\n		</tr>\n		<tr>\n			<td>section_default_css</td>\n			<td>Default <span class=\"caps\"><span class=\"caps\">CSS</span></span> when creating a section</td>\n			<td>default</td>\n		</tr>\n		<tr>\n			<td>section_default_rss</td>\n			<td>Should be created sections include in <span class=\"caps\"><span class=\"caps\">RSS</span></span> Feed?</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>section_default_frontpage</td>\n			<td>Should be created sections on frontpage?</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>section_default_searchable</td>\n			<td>Should be created sections searchable?</td>\n			<td>1</td>\n		</tr>\n		<tr>\n			<td>jquery_ui</td>\n			<td>The link to the jQueryUI Script</td>\n			<td>min. JS from Google <span class=\"caps\"><span class=\"caps\">CDN</span></span></td>\n		</tr>\n		<tr>\n			<td>article_limit</td>\n			<td>Hard limit for backend navigation article list</td>\n			<td>30</td>\n		</tr>\n	</table>\n\n	<p>If you develop sites locally with no internet connection or you don&#8217;t like Google, you can change the <code>jquery_ui</code> link to a local <a href=\"http://jqueryui.com/\">jQueryUI framework</a>. If you want to minfy the script, you don&#8217;t need to load the complete jQueryUI framework. The only things you need are Core, Widget, Mouse and Sortable. (<a href=\"http://jqueryui.com/download\">http://jqueryui.com/download</a>)</p>\n\n	<p>The fucntion <code>yab_nav_gTxt</code> below the configuration array is used for the localisation and language. You can edit this on your own.</p>\n\n	<h2>Other things you need to know</h2>\n\n	<p>Deleting sections or articles will not be implemented. Think: An user could really delete an article! No way!<br />\nThe right management (user privileges) could be bypassed by this plugin.<br />\nThe plugin does only know the article status fixed (5), live (4) and hidden (2). <strong>Other status &#8211; as draft or pending &#8211; will be overwritten!</strong></p>\n\n	<h2>Things you really don&#8217;t want to know</h2>\n\n	<p>This plugin is made with some Javascript, jQuery, jQuery UI, <span class=\"caps\">XML</span> (really), <span class=\"caps\">AJAX</span>, <span class=\"caps\">PHP</span>, Textpattern, Debian, Kate, Firefox/Iceweasel. I like the Allman style for php coding with a maximal measure of 80 chars &#8211; if you ever look at the plugin code. And if have seen the feature screeancast, this is made with recordMyDesktop and converted with mencoder.</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n	h1, h2, h3\n	h1 code, h2 code, h3 code {\n		margin-bottom: 0.6em;\n		font-weight: bold\n	}\n	h1 {\n		font-size: 1.4em\n	}\n	h2 {\n		font-size: 1.25em\n	}\n	h3 {\n	margin-bottom: 0;\n		font-size: 1.1em\n	}\n	.info {\n		font-style: italic\n	}\n	table#yab-nav {\n		margin-bottom: 1em\n	}\n	table#yab-nav td {\n		padding: 3px;\n		border-bottom: 1px solid #000000\n	}\n	table#yab-nav th {\n	text-align: left;\n	border-bottom: 1px solid #000000\n	}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (@txpinterface == \'admin\')\n{\n	global $yab_nav_prefs;\n	$yab_nav_prefs = array(\n		// navigation settings\n		\'home_title\'                 => \'Home\',\n		\'sort\'                       => \'position asc\',\n		\'active_class\'               => \'active\',\n		\'passive_class\'              => \'passive\',\n		\'exclude_sections\'           => \'\',\n		\'exclude_articles\'           => \'\',\n		// new section defaults\n		\'section_default_page\'       => \'default\',\n		\'section_default_css\'        => \'default\',\n		\'section_default_rss\'        => 0,\n		\'section_default_frontpage\'  => 0,\n		\'section_default_searchable\' => 1,\n		// URIs\n		\'hu_link\'                    => hu.\'textpattern/index.php\',\n		\'jquery_ui\'                  => \'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js\',\n		// other\n		\'article_limit\'              => 30\n	);\n\n	add_privs(\'yab_navigation\',\'1, 2\');\n	register_tab(\'extensions\', \'yab_navigation\', \'Manage Navigation\');\n	register_callback(\'yab_navigation\', \'yab_navigation\');\n\n	add_privs(\'yab_navigation_inject\',\'1, 2\');\n	register_callback(\'yab_navigation_inject\', \'yab_navigation_inject\', \'\', 1);\n}\n\n/**\n * L10n; you can edit the values on your own\n *\n * @param srting $what\n * @return string\n */\nfunction yab_nav_gTxt($what)\n{\n	$l10n = array(\n		\'create_new_section\'   => \'Create New Section\',\n		\'create_new_article\'   => \'Create new article here\',\n		\'move_this\'            => \'Move This\',\n		\'collapse_expand\'      => \'Collapse/Expand\',\n		\'hide\'                 => \'Hide\',\n		\'hide_section\'         => \'Hide this section\',\n		\'rename\'               => \'Rename\',\n		\'rename_section\'       => \'Rename this section\',\n		\'edit\'                 => \'Edit\',\n		\'edit_article\'         => \'Edit this article\',\n		\'rename\'               => \'Rename\',\n		\'rename_article\'       => \'Rename this article\',\n		\'hide_unhide\'          => \'Hide/Unhide\',\n		\'hide_unhide_article\'  => \'Hide/Unhide this article\',\n		\'edit_section\'         => \'Edit This Section\',\n		\'hidden_info\'          => \'To unhide move to the left navigation!\',\n		\'hidden_heading\'       => \'Hidden and new sections\',\n		\'save\'                 => \'Save\',\n		\'create\'               => \'Create\',\n		\'updating\'             => \'Updating &hellip; Please wait\',\n		\'error_new_article\'    => \'Could not create new article!\',\n		\'error_new_section\'    => \'Could not create new section!\',\n		\'error_navigation\'     => \'Could not create navigation!\',\n		\'error_rename_section\' => \'Could not rename section!\',\n		\'error_rename_article\' => \'Could not rename article!\',\n		\'error_unhide_article\' => \'Could not unhide article!\',\n		\'error_hide_article\'   => \'Could not hide article!\',\n		\'error_alter_db\'       => \'An error occured! Could not alter textpattern table.\',\n		\'welcome\'              => \'Welcome to yab_navigation\',\n		\'welcome_info\'         => \'This seems to be your first installation of yab_navigation.<br /><br />Please do an action (f.i. moving your frontpage section to the top of the list or moving another section) on the left side and then reload this site again!<br />So the navigation will be build and this text will disappear.<br /><br />You can call the build navigation with &lt;txp:output_form form=\"yab_navigation\" /&gt; in your page template.\'\n	);\n\n	if ($l10n[$what])\n	{\n		return $l10n[$what];\n	}\n	else\n	{\n		return $what;\n	}\n}\n\n/**\n * Draw initialise and yab_navigation\n *\n * @param string $event as $_GET or $_POST\n * @param string $step as $_GET or $_POST\n * @return echo string displays the ui\n */\nfunction yab_navigation($event, $step)\n{\n	$message = \'\';\n	$content = \'\';\n	$left_side = \'\';\n	$inject = yab_nav_header_inject();\n\n	// look for position columns in articles\n	yab_nav_db_entries(\'position\');\n\n	switch($step)\n	{\n		case \'create_nav\':\n			if (!yab_nav_create_template())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'rename_section\':\n			if (!yab_nav_rename_section())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'create_section\':\n			if (!yab_nav_create_section())\n			{\n				txp_status_header(\'400 Bad Request\');\n			};\n			break;\n		case \'create_article\':\n			if (!yab_nav_create_article())\n			{\n				txp_status_header(\'400 Bad Request\');\n			};\n			break;\n		case \'rename_article\':\n			if(!yab_nav_rename_article())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'unhide_article\':\n			if(!yab_nav_set_article_status(4))\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'hide_article\':\n			if(!yab_nav_set_article_status(2))\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		default:\n			// exist form yab_navigation form (first install)?\n			$form = yab_nav_db_entries();\n			if ($form === false)\n			{\n				$message = yab_nav_gTxt(\'error_alter_db\');\n			}\n			else // true, form exists\n			{\n				if (empty($form)) // but empty\n				{\n					$content .= yab_nav_db_nav($form, true); // show nav drom db, template empty\n					$left_side = tag(\n						tag(yab_nav_gTxt(\'welcome\'), \'h2\').yab_nav_gTxt(\'welcome_info\'),\n						\'div\',\n						\' id=\"yab-nav-wrap2\"\'\n					);\n				}\n				else // not empty\n				{\n					$content .= yab_nav_template_nav($form);\n					$left_side = tag(\n						tag(yab_nav_gTxt(\'hidden_heading\'), \'h2\').\n						tag(yab_nav_gTxt(\'hidden_info\'), \'span\', \' class=\"hide-info\"\').\n						yab_nav_db_nav($form),\n						\'div\',\n						\' id=\"yab-nav-wrap2\"\'\n					);\n				}\n			}\n			break;\n	}\n\n	$new_section = tag(\n		yab_nav_gTxt(\'create_new_section\'),\n		\'span\',\n		\' id=\"new-section\" title=\"\'.yab_nav_gTxt(\'create_new_section\').\'\"\'\n	);\n	$clearfix = tag(\'&nbsp;\', \'div\', \' class=\"clearfix\"\');\n\n	$out = tag(\n		tag($new_section.$content, \'div\', \' id=\"yab-nav-wrap1\"\').\n		$left_side.\n		$clearfix,\n		\'div\',\n		\' id=\"yab-nav-wrap\"\'\n	);\n\n	echo $inject.pagetop(\'Manage Navigation\', $message).$out;\n}\n\n/**\n * Returns navigation of new detected section\n *\n * @param string $form yab_navigation form content\n * @param boolean $first should be true if $form is empty\n * @return $string\n */\nfunction yab_nav_db_nav($form = \'\', $first = false)\n{\n	global $yab_nav_prefs;\n	$home_title = $yab_nav_prefs[\'home_title\'];\n	$exclude = $yab_nav_prefs[\'exclude_sections\'];\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n	$limit = $yab_nav_prefs[\'article_limit\'];\n\n	if ($exclude)\n	{\n		$exclude = do_list($exclude);\n		$exclude = join(\"\',\'\", doSlash($exclude));\n		$exclude = \"and name not in(\'$exclude\')\";\n	}\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	// if nav template dont\' exists show only hidden and new sections\n	if (!$first)\n	{\n		$ol_id = \' id=\"yab-navigation-new\"\';\n\n		$sections = safe_rows(\n			\'name, title\',\n			\'txp_section\',\n			\"name != \'default\' AND title != \'$home_title\' $exclude\"\n		);\n\n		// get all sections from nav template\n		preg_match_all(\n			\'/section_list sections=\"(.*?)\"/\',\n			$form,\n			$matches,\n			PREG_SET_ORDER\n		);\n\n		// and unset these on current section array\n		foreach ($matches as $match)\n		{\n			$match_name = trim($match[1]);\n\n			foreach ($sections as $section_key => &$section_value)\n			{\n				if ($match_name == $section_value[\'name\'])\n				{\n					unset($sections[$section_key]);\n				}\n			}\n		}\n	}\n	else\n	{\n	$ol_id = \' id=\"yab-navigation\"\';\n\n		$sections = safe_rows(\n			\'name, title\',\n			\'txp_section\',\n			\"name != \'default\' $exclude\"\n		);\n	}\n\n	$out = \'\';\n	if ($sections)\n	{\n		foreach ($sections as $section)\n		{\n			$section_name = $section[\'name\'];\n			$section_title = $section[\'title\'];\n\n			// show new article icon, if not in exclude articles\n			$yab_nav_new_article_icon = yab_nav_new_article_icon();\n			if ($exlude_article_section)\n			{\n				if (in_array($section_name, $exlude_article_section))\n				{\n					$yab_nav_new_article_icon = \'\';\n				}\n			}\n\n			$articles = safe_rows(\n				\'ID, Title, Status\',\n				\'textpattern\',\n				\"Section = \'$section_name\' ORDER BY title LIMIT $limit\"\n			);\n\n			$a_out = \'\';\n			if ($articles)\n			{\n				foreach ($articles as $article)\n				{\n					$article_class = \'\';\n					if ($article[\'Status\'] == 2)\n					{\n						$article_class = \' class=\"article-hidden\"\';\n					}\n\n					$a_out .= tag(\n						yab_nav_show_move_icon().\n						tag($article[\'Title\'], \'span\', \' class=\"article-title\"\').\n						yab_nav_show_prep_wrap_article(),\n						\'li\',\n						\' id=\"a-\'.$article[\'ID\'].\'\"\'.$article_class.\'\'\n					);\n				}\n			}\n\n			// don\'t show empty ol (sortable) for excluded sections\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section();\n			}\n			else\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section().\'<ol>\'.$a_out.\'</ol>\';\n			}\n\n			$out .= tag(\n				yab_nav_show_move_icon().$yab_nav_new_article_icon.\n				tag($section_title, \'span\', \' class=\"section-title\"\').\n				$a_out_complete,\n				\'li\',\n				\' id=\"yab-nav-s-\'.$section_name.\'\"\'\n			);\n		}\n	}\n	return \'<ol\'.$ol_id.\'>\'.$out.\'</ol>\';\n}\n\n/**\n * Show navigation based on saved yab_navigation form\n *\n * @param string $form from yab_navigation form content\n * @return string\n */\nfunction yab_nav_template_nav($form)\n{\n	global $yab_nav_prefs;\n	$sort = $yab_nav_prefs[\'sort\'];\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n	$limit = $yab_nav_prefs[\'article_limit\'];\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	// preg_match for sections in navigation template\n	preg_match_all(\n		\'/section_list sections=\"(.*?)\"/\',\n		$form,\n		$matches,\n		PREG_SET_ORDER\n	);\n\n	// build frontpage list point\n	$frontpage = safe_row(\n		\'title, name\',\n		\'txp_section\',\n		\"name != \'default\' AND on_frontpage = \'1\'\"\n	);\n\n	$frontpage_section_listpoint = \'\';\n	if ($frontpage)\n	{\n		$frontpage_name = $frontpage[\'name\'];\n		$frontpage_title = $frontpage[\'title\'];\n		$frontpage_articles = safe_rows(\n			\'ID, Title\',\n			\'textpattern\',\n			\"Section = \'$frontpage_name\' ORDER BY $sort LIMIT $limit\"\n		);\n\n		$frontpage_article_listpoint = \'\';\n		if ($frontpage_articles)\n		{\n			foreach ($frontpage_articles as $frontspage_article)\n			{\n				$frontpage_article_listpoint .= tag(\n					yab_nav_show_move_icon().\n					tag($frontspage_article[\'Title\'], \'span\', \' class=\"article-title\"\').\n					yab_nav_show_prep_wrap_article(),\n					\'li\',\n					\' id=\"a-\'.$frontspage_article[\'ID\'].\'\"\'\n				);\n			}\n		}\n\n		$frontpage_article_listpoint = yab_nav_show_prep_wrap_section().\n			\'<ol>\'.$frontpage_article_listpoint.\'</ol>\';\n		$frontpage_section_listpoint = tag(\n			yab_nav_show_move_icon().\n			tag($frontpage_title, \'span\', \' class=\"section-title\"\').n.\n			$frontpage_article_listpoint,\n			\'li\',\n			\' id=\"yab-nav-s-\'.$frontpage_name.\'\"\'\n		);\n	}\n\n	// build other listpoints\n	if ($matches)\n	{\n		$out = \'\';\n\n		foreach ($matches as $sections)\n		{\n			$section_name = trim($sections[1]);\n			$section = safe_row(\n				\'title\',\n				\'txp_section\',\n				\"name = \'$section_name\'\"\n			);\n			$section_title = $section[\'title\'];\n\n			// show new article icon, if not in exclude articles\n			$yab_nav_new_article_icon = yab_nav_new_article_icon();\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$yab_nav_new_article_icon = \'\';\n			}\n\n			$articles = safe_rows(\n				\'ID, Title, Status\',\n				\'textpattern\',\n				\"Section = \'$section_name\' ORDER BY $sort LIMIT $limit\"\n			);\n\n			$a_out = \'\';\n			if ($articles)\n			{\n				foreach ($articles as $article)\n				{\n					$article_class = \'\';\n					if ($article[\'Status\'] == 2)\n					{\n						$article_class = \' class=\"article-hidden\"\';\n					}\n					$a_out .= tag(\n						yab_nav_show_move_icon().\n						tag($article[\'Title\'], \'span\', \' class=\"article-title\"\').\n						yab_nav_show_prep_wrap_article(),\n						\'li\',\n						\' id=\"a-\'.$article[\'ID\'].\'\"\'.$article_class.\'\'\n					);\n				}\n			}\n\n			// don\'t show empty ol (sortable) for excluded sections\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section();\n			}\n			else\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section().\'<ol>\'.$a_out.\'</ol>\';\n			}\n\n			$out .= tag(\n				yab_nav_show_move_icon().$yab_nav_new_article_icon.\n				tag($section_title, \'span\', \' class=\"section-title\"\').\n				$a_out_complete,\n				\'li\',\n				\' id=\"yab-nav-s-\'.$section_name.\'\"\'\n			);\n		}\n		$out = $frontpage_section_listpoint.$out;\n		return tag($out, \'ol\', \' id=\"yab-navigation\"\');\n	}\n}\n\n/**\n * Hides and Unhides articles\n *\n * @param integer $status numeric status value of the article\n * @return boolean\n */\nfunction yab_nav_set_article_status($status)\n{\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$id = $article[\'id\'];\n\n	$rs = safe_update(\n		\'textpattern\',\n		\"Status = $status\",\n		\"ID = $id\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Creates a new section\n *\n * @return boolean\n */\nfunction yab_nav_create_section()\n{\n	global $yab_nav_prefs;\n	$page = $yab_nav_prefs[\'section_default_page\'];\n	$css = $yab_nav_prefs[\'section_default_css\'];\n	$rss = $yab_nav_prefs[\'section_default_rss\'];\n	$frontpage = $yab_nav_prefs[\'section_default_frontpage\'];\n	$searchable = $yab_nav_prefs[\'section_default_searchable\'];\n\n	$xml_array = yab_nav_load_from_ajax();\n	$section = $xml_array[\'section\'];\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$section_title = doSlash($textile->TextileThis($section[\'title\'], 1));\n	$section_name = doSlash(strtolower(sanitizeForUrl($section[\'title\'])));\n\n	$rs = safe_insert(\n		\'txp_section\',\n		\"name        = \'$section_name\',\n		title        = \'$section_title\',\n		page         = \'$page\',\n		css          = \'$css\',\n		is_default   = 0,\n		in_rss       = $rss,\n		on_frontpage = $frontpage,\n		searchable   = $searchable\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Creates a new article\n *\n * @return boolean\n */\nfunction yab_nav_create_article()\n{\n	global $txp_user;\n\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$section = $article[\'section\'];\n	$position = $article[\'position\'];\n	$status = 2;\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$article_title = doSlash($textile->TextileThis($article[\'title\'], \'\', 1));\n	$url_title = stripSpace($article[\'title\'], 1);\n\n	$count = safe_count(\'textpattern\', \"Section = \'$section\'\");\n	if ($count < 1)\n	{\n		$status = 5;\n	}\n\n	$rs = safe_insert(\n		\'textpattern\',\n		\"Title    = \'$article_title\',\n		Status    = $status,\n		Posted    = now(),\n		AuthorID  = \'$txp_user\',\n		LastMod   = now(),\n		LastModID = \'$txp_user\',\n		Section   = \'$section\',\n		url_title = \'$url_title\',\n		position  = $position,\n		uid       = \'\".md5(uniqid(rand(),true)).\"\',\n		feed_time = now()\"\n	);\n\n	$GLOBALS[\'ID\'] = mysql_insert_id();\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Renames an article title\n *\n * @return boolean\n */\nfunction yab_nav_rename_article()\n{\n	global $txp_user;\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$article_id = $article[\'id\'];\n	$article_title = doSlash($textile->TextileThis($article[\'title\'], \'\', 1));\n\n	$rs = safe_update(\n		\'textpattern\',\n		\"Title    = \'$article_title\',\n		LastMod   = now(),\n		LastModID = \'$txp_user\'\",\n		\"ID = $article_id\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Renames a section title\n *\n * @return boolean\n */\nfunction yab_nav_rename_section()\n{\n	$xml_array = yab_nav_load_from_ajax();\n	$section = $xml_array[\'section\'];\n	$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']);\n	$section_title = doSlash($section[\'title\']);\n\n	$rs = safe_update(\n		\'txp_section\',\n		\"title = \'$section_title\'\",\n		\"name = \'$section_name\'\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Updates the navigation with ajax xml values\n *\n * @return boolean\n */\nfunction yab_nav_create_template()\n{\n	$xml_array = yab_nav_load_from_ajax();\n\n	$do_form = yab_nav_do_form($xml_array);\n	if (!$do_form)\n	{\n		return false;\n	}\n\n	$i = 0;\n	foreach ($xml_array[\'section\'] as $section)\n	{\n		$i++;\n		$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']);\n		if ($i === 1)\n		{\n			$rsa = safe_update(\n				\'txp_section\',\n				\"on_frontpage = \'1\'\",\n				\"name = \'$section_name\'\"\n			);\n		}\n		else\n		{\n			$rsa = safe_update(\n				\'txp_section\',\n				\"on_frontpage = \'0\'\",\n				\"name = \'$section_name\'\"\n			);\n		}\n\n		if (!$rsa)\n		{\n			return false;\n		}\n\n		if (isset($section[\'article\']))\n		{\n			if (is_array($section[\'article\']))\n			{\n				$j = 0;\n				foreach ($section[\'article\'] as $article)\n				{\n					$j++;\n					$article_id = str_replace(\'a-\', \'\', $article);\n					if ($j === 1)\n					{\n						$rsb = safe_update(\n							\'textpattern\',\n							\"position = $j, Status = 5, Section = \'$section_name\'\",\n							\"ID = \'$article_id\'\"\n						);\n					}\n					else\n					{\n						$old_status = safe_field(\n							\'Status\',\n							\'textpattern\',\n							\"ID = $article_id\"\n						);\n\n						$status = 4;\n						if ($old_status)\n						{\n							if ($old_status < 4)\n							{\n								$status = $old_status;\n							}\n							$rsb = safe_update(\n								\'textpattern\',\n								\"position = $j, Status = $status, Section = \'$section_name\'\",\n								\"ID = $article_id\"\n							);\n						}\n						else\n						{\n							return false;\n						}\n					}\n					if (!$rsb)\n					{\n						return false;\n					}\n				}\n			}\n			else\n			{\n				$article_id = str_replace(\'a-\', \'\', $section[\'article\']);\n				$rsc = safe_update(\n					\'textpattern\',\n					\"position = 1, Status = 5, Section = \'$section_name\'\",\n					\"ID = \'$article_id\'\"\n				);\n				if (!$rsc)\n				{\n					return false;\n				}\n			}\n			update_lastmod();\n		}\n	}\n	return true;\n}\n\n\n/**\n * Save navigation template in yab_navigation form\n *\n * @param array $xml_array converted xml object\n * @return boolean\n */\nfunction yab_nav_do_form($xml_array)\n{\n	$nav = \'<ul>\'.n;\n	$nav .= yab_nav_do_section_class(\'default\').n;\n	$nav .= yab_nav_do_section(\'default\', true).n;\n\n	$i = 0;\n	foreach ($xml_array[\'section\'] as $section)\n	{\n		if ($i > 0)\n		{\n			$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']).n;\n			$section_name = str_replace(n, \'\', $section_name);\n			$nav .= yab_nav_do_section_class($section_name).n;\n			$nav .= yab_nav_do_section($section_name).n;\n		}\n		$i++;\n	}\n\n	$nav .= \'</ul>\';\n	$nav = doSlash($nav);\n	$rs = safe_update(\'txp_form\', \"Form = \'$nav\'\", \"name = \'yab_navigation\'\");\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Alters/prepares the database for usage\n *\n * @param string $what what should be performed\n * @return mixed boolean/string\n */\nfunction yab_nav_db_entries($what = \'\')\n{\n	if ($what == \'position\')\n	{\n		$columns = safe_show(\'columns\', \'textpattern\');\n		foreach ($columns as $column)\n		{\n			if ($column[\'Field\'] == \'position\')\n			{\n				return true;\n			}\n		}\n\n		$rs_pos = safe_alter(\n			\'textpattern\',\n			\"ADD position INT NOT NULL DEFAULT \'0\';\"\n		);\n		if ($rs_pos)\n		{\n			return true;\n		}\n		else\n		{\n			return false;\n		}\n	}\n	else\n	{\n		$form = safe_field(\'Form\', \'txp_form\', \"Name = \'yab_navigation\'\");\n		if ($form !== false)\n		{\n			return $form;\n		}\n		else\n		{\n			$rs_form = safe_insert(\n				\'txp_form\',\n				\"Form = \'\', type = \'misc\', name = \'yab_navigation\'\"\n			);\n			if ($rs_form)\n			{\n				return true;\n			}\n			else\n			{\n				return false;\n			}\n		}\n	}\n}\n\n/**\n * Get ajax xml from php://input and return as array\n *\n * @return array\n */\nfunction yab_nav_load_from_ajax()\n{\n	$xml = file_get_contents(\'php://input\');\n	$xml = simplexml_load_string($xml);\n	$xml = yab_nav_xml2array($xml);\n\n	return $xml;\n}\n\n/**\n * Converts a XML object to an array\n *\n * @param object $xmlobject XML object\n * @return array\n */\nfunction yab_nav_xml2array($xmlobject)\n{\n	$xml_array = array();\n	if (is_object($xmlobject))\n	{\n		settype($xmlobject, \'array\');\n	}\n	foreach ($xmlobject as $key => $value)\n	{\n		if (is_array($value) or is_object($value))\n		{\n			$xml_array[$key] = yab_nav_xml2array($value);\n		}\n		else\n		{\n			$xml_array[$key] = $value;\n		}\n	}\n	return $xml_array;\n}\n\n/**\n * Build txp tag variable\n *\n * @param string $name\n * @param string $value\n * @return string\n */\nfunction yab_nav_do_variable($name, $value)\n{\n	return \'<txp:variable name=\"\'.$name.\'\" value=\"\'.$value.\'\" />\'.n;\n}\n\n/**\n * Build txp tag if_section statement for active class\n *\n * @param string $section\n * @return string\n */\nfunction yab_nav_do_section_class($section)\n{\n	global $yab_nav_prefs;\n	$passive_class = $yab_nav_prefs[\'passive_class\'];\n	$active_class = $yab_nav_prefs[\'active_class\'];\n\n	return\n		\'<txp:variable name=\"default_class\" value=\"\'.$passive_class.\'\" />\'.n.\n		\'<txp:if_section name=\"\'.$section.\'\">\'.n.\n			\'<txp:variable name=\"default_class\" value=\"\'.$active_class.\'\" />\'.n.\n		\'</txp:if_section>\';\n}\n\n/**\n * Build txp tag if_article_id statement for active class\n *\n * @return string\n */\nfunction yab_nav_do_article_class()\n{\n	global $yab_nav_prefs;\n	$passive_class = $yab_nav_prefs[\'passive_class\'];\n	$active_class = $yab_nav_prefs[\'active_class\'];\n\n	return\n		\'<txp:variable name=\"article_class\" value=\"\'.$passive_class.\'\" />\'.n.\n		\'<txp:if_article_id>\'.n.\n			\'<txp:variable name=\"article_class\" value=\"\'.$active_class.\'\" />\'.n.\n		\'</txp:if_article_id>\';\n}\n\n/**\n * Build txp tag article list\n *\n * @param string $section\n * @param string $sort\n * @return string\n */\nfunction yab_nav_do_article_list($section, $sort)\n{\n	global $yab_nav_prefs;\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	if (!in_array($section, $exlude_article_section))\n	{\n	return\n		\'<txp:article_custom section=\"\'.\n				$section.\n				\'\"  wraptag=\"ul\"  break=\"\" sort=\"\'.\n				$sort.\n				\'\" status=\"4\">\'.n.\n			yab_nav_do_article_class().n.\n			\'<li class=\"<txp:variable name=\"article_class\" />\">\'.n.\n				\'<txp:permlink><txp:title /></txp:permlink>\'.n.\n			\'</li>\'.n.\n		\'</txp:article_custom>\';\n	}\n	else\n	{\n		return \'\';\n	}\n}\n\n/**\n * Build txp tag section lists\n *\n * @param string $section\n * @param boolean $home\n * @return string\n */\nfunction yab_nav_do_section($section, $home = false)\n{\n	global $yab_nav_prefs;\n	$home_title = $yab_nav_prefs[\'home_title\'];\n	$sort = $yab_nav_prefs[\'sort\'];\n\n	if ($home)\n	{\n		return\n			\'<li class=\"<txp:variable name=\\\'default_class\\\' />\">\'.n.\n				\'<txp:link_to_home>\'.$home_title.\'</txp:link_to_home>\'.n.\n			\'</li>\';\n	}\n	else\n	{\n		return\n			\'<li class=\"<txp:variable name=\\\'default_class\\\' />\">\'.n.\n				\'<txp:section_list sections=\"\'.\n					$section.\n					\'\" wraptag=\"\" break=\"\" include_default=\"0\" />\'.n.\n				yab_nav_do_article_list($section, $sort).n.\n			\'</li>\';\n	}\n}\n\n/**\n * Build span for create article icon\n *\n * @return $string\n */\nfunction yab_nav_new_article_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'create_new_article\'),\n		\'span\',\n		\' class=\"new-article\" title=\"\'.yab_nav_gTxt(\'create_new_article\').\'\"\'\n	);\n}\n\n/**\n * Build span for move icon\n *\n * @return $string\n */\nfunction yab_nav_show_move_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'move_this\'),\n		\'span\',\n		\' class=\"move\" title=\"\'.yab_nav_gTxt(\'move_this\').\'\"\'\n	);\n}\n\n/**\n * Build span for expand/collapse icon\n *\n * @return $string\n */\nfunction yab_nav_show_exp_coll_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'collapse_expand\'),\n		\'span\',\n		\' class=\"hide-show\" title=\"\'.yab_nav_gTxt(\'collapse_expand\').\'\"\'\n	);\n}\n\n/**\n * Build list for section editing\n *\n * @return $string\n */\nfunction yab_nav_show_edit_section_list()\n{\n	return tag(\n		tag(\n			yab_nav_gTxt(\'hide\'),\n			\'li\',\n			\' class=\"hide-section\" title=\"\'.yab_nav_gTxt(\'hide_section\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'rename\'),\n			\'li\',\n			\' class=\"rename-section\" title=\"\'.yab_nav_gTxt(\'rename_section\').\'\"\'\n		),\n		\'ul\',\n		\' class=\"edit-list\"\'\n	);\n}\n\n/**\n * Build list for article editing\n *\n * @return $string\n */\nfunction yab_nav_show_edit_article_list()\n{\n	return tag(\n		tag(\n			yab_nav_gTxt(\'edit\'),\n			\'li\',\n			\' class=\"edit-article\" title=\"\'.yab_nav_gTxt(\'edit_article\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'rename\'),\n			\'li\',\n			\' class=\"rename-article\" title=\"\'.yab_nav_gTxt(\'rename_article\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'hide_unhide\'),\n			\'li\',\n			\' class=\"hide-article\" title=\"\'.yab_nav_gTxt(\'hide_unhide_article\').\'\"\'\n		),\n		\'ul\',\n		\' class=\"edit-list\"\'\n	);\n}\n\n/**\n * Build div for section editing icon\n *\n * @return $string\n */\nfunction yab_nav_show_edit_icon_section()\n{\n	return tag(\n		yab_nav_show_edit_section_list(),\n		\'div\',\n		\' class=\"edit\" title=\"\'.yab_nav_gTxt(\'edit_section\').\'\"\'\n	);\n}\n\n/**\n * Build wrapper for section editing\n *\n * @return $string\n */\nfunction yab_nav_show_prep_wrap_section()\n{\n	return tag(\n		yab_nav_show_exp_coll_icon().yab_nav_show_edit_icon_section(),\n		\'div\',\n		\' class=\"prep-section\"\'\n	);\n}\n\n/**\n * Build div for article editing list\n *\n * @return $string\n */\nfunction yab_nav_show_edit_icon_article()\n{\n	return tag(\n		yab_nav_show_edit_article_list(),\n		\'div\',\n		\' class=\"edit\" title=\"\'.yab_nav_gTxt(\'edit_article\').\'\"\'\n	);\n}\n\n/**\n * Build wrapper for article editing\n *\n * @return $string\n */\nfunction yab_nav_show_prep_wrap_article()\n{\n	return tag(\n		yab_nav_show_edit_icon_article(),\n		\'div\',\n		\' class=\"prep-article\"\'\n	);\n}\n\n/**\n * Echoes javascript or css content\n *\n * @param string $event\n * @param string $step\n * @return void\n */\nfunction yab_navigation_inject($event, $step)\n{\n	while (@ob_end_clean());\n\n	if ($step == \'javascript\')\n	{\n		header(\"Content-type: text/javascript\");\n		echo yab_nav_javascript();\n	}\n	else\n	{\n		header(\"Content-type: text/css\");\n		echo yab_nav_css();\n	}\n\n	exit(0);\n}\n\n/**\n * Returns javascript\n *\n * @return string\n */\nfunction yab_nav_javascript()\n{\n	global $yab_nav_prefs;\n	$hu_link = $yab_nav_prefs[\'hu_link\'];\n\n	$save = yab_nav_gTxt(\'save\');\n	$create = yab_nav_gTxt(\'create\');\n	$updating = yab_nav_gTxt(\'updating\');\n	$error_new_article = yab_nav_gTxt(\'error_new_article\');\n	$error_new_section = yab_nav_gTxt(\'error_new_section\');\n	$error_navigation = yab_nav_gTxt(\'error_navigation\');\n	$error_rename_section = yab_nav_gTxt(\'error_rename_section\');\n	$error_rename_article = yab_nav_gTxt(\'error_rename_article\');\n	$error_unhide_article = yab_nav_gTxt(\'error_unhide_article\');\n	$error_hide_article = yab_nav_gTxt(\'error_hide_article\');\n\n	$js = <<<EOF\n$(document).ready(function() {\n	// info while ajax request\n	$(\'#messagepane\').ajaxSend(function() {\n		$(this).append(\n			\'<span class=\"yab-nav-updating\">$updating</span>\'\n		);\n		$(\'body\').addClass(\'yab-nav-wait\');\n	});\n	$(\'#messagepane\').ajaxStop(function() {\n		$(\'.yab-nav-updating\').remove();\n		$(\'body\').removeClass(\'yab-nav-wait\');\n	});\n	// can sort all li between all ol\n	$(\'#yab-navigation ol\').sortable({\n		connectWith: \'#yab-navigation ol\',\n		placeholder: \'placeholder\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	// 	can sort ol\n	$(\'#yab-navigation\').sortable({\n		placeholder: \'placeholder\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	$(\'#yab-navigation-new\').sortable({\n		placeholder: \'placeholder\',\n		connectWith: \'#yab-navigation\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	// show article edit icons on li hover\n	$(\'#yab-navigation li li\').hover(function() {\n		$(\'.prep-article .edit\', this).addClass(\'show-icon\');\n	}, function() {\n		$(\'.prep-article .edit\', this).removeClass(\'show-icon\');\n	});\n	// hide article menu\n	$(\'.hide-show\').toggle(function() {\n		$(this).addClass(\'hiding\');\n		$(this).parents(\'li\').addClass(\'hide-li\');\n	}, function() {\n		$(this).removeClass(\'hiding\');\n		$(this).parents(\'li\').removeClass(\'hide-li\');\n	});\n	// show/hide edit menu\n	$(\'.edit, .edit ul\').hover(function() {\n		$(this).addClass(\'show\');\n	}, function() {\n		$(this).removeClass(\'show\');\n	});\n	// rename section\n	$(\'.rename-section\').live(\'click\', function() {\n		var section_li = $(this).parents(\'#yab-navigation li\');\n		var section =	$(section_li).children(\'.section-title\');\n		$(section).replaceWith(\n			\'<input type=\"text\" value=\"\' +\n			yab_nav_htmlquot($(section).text()) +\n			\'\" class=\"section-input\" /><button class=\"section-submit\">$save</button>\'\n		);\n		$(\'.section-submit\').click(function() {\n			var sibling_input = $(this).siblings(\'.section-input\');\n			// save new name with ajax\n			if ($(section).text() != $(sibling_input).val())\n			{\n				var section_name = $(section_li).attr(\'id\');\n				yab_nav_do_ajax_rename_section(section_name, $(sibling_input).val());\n			}\n			$(sibling_input).replaceWith(\n				\'<span class=\"section-title\">\' +\n				$(sibling_input).val() +\n				\'</span>\'\n			);\n			$(this).remove();\n		});\n	});\n	// rename article\n	$(\'.rename-article\').live(\'click\', function() {\n		var article_li = $(this).parents(\'#yab-navigation ol li\');\n		var article = $(article_li).children(\'.article-title\');\n		$(article).replaceWith(\n			\'<input type=\"text\" value=\"\' +\n			yab_nav_htmlquot(yab_nav_htmlspecialchars($(article).text())) +\n			\'\" class=\"article-input\" /><button class=\"article-submit\">$save</button>\'\n		);\n		$(\'.article-submit\').click(function() {\n			var sibling_input = $(this).siblings(\'.article-input\');\n			// save new name with ajax\n			if ($(article).text() != $(sibling_input).val())\n			{\n				var article_id = $(article_li).attr(\'id\').replace(/a-/, \'\');\n				yab_nav_do_ajax_rename_article(article_id, $(sibling_input).val());\n			}\n			$(sibling_input).replaceWith(\n				\'<span class=\"article-title\">\' +\n				$(sibling_input).val() +\n				\'</span>\'\n			);\n			$(this).remove();\n		});\n	});\n	// edit this article\n	$(\'.edit-article\').click(function() {\n		var article_id = $(this).parents(\'#yab-navigation ol li\').attr(\'id\');\n		article_id = article_id.replace(/a-/, \'\');\n		location.href = \'$hu_link?event=article&step=edit&ID=\' + article_id;\n	});\n	//create new section\n	$(\'#new-section\').live(\'click\', function() {\n		$(this).after(\n			\'<input type=\"text\" value=\"\" id=\"new-section-title\" />\' +\n			\'<button id=\"new-section-submit\">Create</button>\'\n		);\n		$(\'#new-section-submit\').click(function() {\n			var new_title = $(\'#new-section-title\').val();\n			if (new_title != \'\')\n			{\n				yab_nav_do_ajax_create_section(new_title);\n			}\n				$(\'#new-section-rm\').attr(\'id\', \'new-section\');\n				$(\'#new-section-submit, #new-section-title\').remove();\n		});\n		$(this).attr(\'id\', \'new-section-rm\');\n	});\n	// second click, toogle won\'t work correctly with live()-event\n	$(\'#new-section-rm\').live(\'click\', function() {\n		$(this).attr(\'id\', \'new-section\');\n		$(\'#new-section-submit, #new-section-title\').remove();\n	});\n	// hide sections\n	$(\'.hide-section\').click(function() {\n		$(\'#yab-navigation-new\').append($(this).parents(\'#yab-navigation li\'));\n		yab_nav_do_ajax_nav();\n	});\n	// hide articles\n	$(\'.hide-article\').click(function() {\n		var this_article_li = $(this).parents(\'#yab-navigation ol li\');\n		var this_article_id = this_article_li.attr(\'id\');\n		this_article_id = this_article_id.replace(/a-/, \'\');\n		var this_class = this_article_li.attr(\'class\');\n		if (this_class == \'article-hidden\') {	// unhide\n			$(this_article_li).removeClass(\'article-hidden\');\n			yab_nav_do_unhide_article(this_article_id);\n		} else { // hide\n			$(this_article_li).addClass(\'article-hidden\');\n			yab_nav_do_hide_article(this_article_id);\n		}\n	});\n	//create new articles\n	$(\'.new-article\').live(\'click\', function() {\n		var this_section_li = $(this).parents(\'#yab-navigation li\');\n		var this_section = this_section_li.attr(\'id\').replace(/yab-nav-s-/, \'\');\n		$(this_section_li).children(\'ol\').append(\n			\'<li class=\"new-article-input\"><input type=\"text\" value=\"\" class=\"new-article-title\" />\' +\n			\'<button class=\"new-article-submit\">$create</button></li>\'\n		);\n		$(\'.new-article-submit\').click(function() {\n			var new_article_title = $(\'.new-article-title\').val();\n			if (new_article_title != \'\')\n			{\n				var position = $(this).parent(\'li\').prevAll().length + 1;\n				yab_nav_do_ajax_create_article(new_article_title, this_section, position);\n			}\n				$(\'.new-article-rm\').attr(\'class\', \'new-article\');\n				$(\'.new-article-input\').remove();\n		});\n		$(this).attr(\'class\', \'new-article-rm\');\n	});\n	// second click, toogle won\'t work correctly with live()-event\n	$(\'.new-article-rm\').live(\'click\', function() {\n		$(this).attr(\'class\', \'new-article\');\n		$(\'.new-article-input\').remove();\n	});\n});\n\nfunction yab_nav_do_ajax_create_article(new_title, section, position) {\n	var nav;\n	new_title = yab_nav_htmlspecialchars(new_title);\n	nav = yab_nav_do_xml(\'title\', new_title) +\n		yab_nav_do_xml(\'section\', section) +\n		yab_nav_do_xml(\'position\', position);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'create_article\');\n}\n\nfunction yab_nav_do_unhide_article(id) {\n	var nav;\n	nav = yab_nav_do_xml(\'id\', id);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'unhide_article\');\n}\n\nfunction yab_nav_do_hide_article(id) {\n	var nav;\n	nav = yab_nav_do_xml(\'id\', id);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'hide_article\');\n}\n\nfunction yab_nav_do_ajax_create_section(new_title) {\n	var nav;\n	new_title = yab_nav_htmlspecialchars(new_title);\n	nav = yab_nav_do_xml(\'title\', new_title);\n	nav = yab_nav_do_xml(\'section\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'create_section\');\n}\n\nfunction yab_nav_do_ajax_rename_section(name, title) {\n	var nav;\n	title = yab_nav_htmlspecialchars(title);\n	nav = yab_nav_do_xml(\'name\', name) + yab_nav_do_xml(\'title\', title);\n	nav = yab_nav_do_xml(\'section\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'rename_section\');\n}\n\nfunction yab_nav_do_ajax_rename_article(id, title) {\n	var nav;\n	title = yab_nav_htmlspecialchars(title);\n	nav = yab_nav_do_xml(\'id\', id) + yab_nav_do_xml(\'title\', title);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'rename_article\');\n}\n\nfunction yab_nav_do_ajax_nav() {\n	var nav;\n	var section = \'\';\n	var name = \'\';\n	var this_section;\n	$(\'#yab-navigation > li\').each(function() {\n		var article = \'\';\n		this_section = $(this).attr(\'id\');\n		$(\'ol > li\', this).each(function() {\n			article = article + yab_nav_do_xml(\'article\', $(this).attr(\'id\'));\n		});\n		name = yab_nav_do_xml(\'name\', this_section);\n		section = section + yab_nav_do_xml(\'section\', name + article);\n	});\n	nav = yab_nav_do_xml(\'navigation\', section);\n	yab_nav_do_ajax(nav, \'create_nav\');\n}\n\nfunction yab_nav_do_ajax(xml, step) {\n	return $.ajax({\n		url: \'$hu_link?event=yab_navigation&step=\' + step,\n		type: \'POST\',\n		contentType: \'text/xml\',\n		processData: false,\n		data: xml,\n		complete: function(response, status) {\n			yab_nav_complete_handler(status, step);\n		}\n	});\n}\n\nfunction yab_nav_complete_handler(status, step) {\n	switch (step) {\n		case \'create_article\':\n			if (status == \'success\') {\n				location.href = \'$hu_link?event=yab_navigation\';\n			} else {\n				$(\'#messagepane\').append(\'$error_new_article\');\n			}\n			break;\n		case \'create_section\':\n			if (status == \'success\') {\n				location.href = \'$hu_link?event=yab_navigation\';\n			} else {\n				$(\'#messagepane\').append(\'$error_new_section\');\n			}\n			break;\n		case \'create_nav\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_navigation\');\n			}\n			break;\n		case \'rename_section\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_rename_section\');\n			}\n			break;\n		case \'rename_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_rename_article\');\n			}\n		case \'unhide_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_unhide_article\');\n			}\n		case \'hide_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_hide_article\');\n			}\n			break;\n		default:\n			break;\n	}\n}\n\nfunction yab_nav_do_xml(name, content) {\n	var xml;\n	if (!content) { xml = \'<\' + name + \'/>\'; }\n	else { xml = \'<\' + name + \'>\' + content + \'</\' + name + \'>\'; }\n	return xml;\n}\n\nfunction yab_nav_htmlquot(string) {\n	return string.replace(/\"/g, \'&quot;\');\n}\n\nfunction yab_nav_htmlspecialchars(string) {\n	return $(\'<span>\').text(string).html();\n}\nEOF;\n\n	return $js;\n}\n\n/**\n * Returns CSS\n *\n * @return string\n */\nfunction yab_nav_css()\n{\n	$pic = \'iVBORw0KGgoAAAANSUhEUgAAABAAAABQCAYAAAAQq/UNAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAASFJREFUWMPtWDsSgjAQfTAUGTs6uR00egEKCzuZsckFrOAsXsFDYJeWSm1SRCYbNjCMn9lXwbL7siSb5CWAIKE+GK22AHr7WuT1cGcTjIIRIkmJBHqmLQyj1dNo9Qz5pEs7MV1lFKi083pIuBkUTNvyOhD853pgtDoBOHKqEJwytjNyR5V3zGTa+0gyIgs1Ml0o1oywuwS5yz2HIAXwiM1gw+2YVQgaADfCv5GpI/iYQgnuSlyFwpc4hLyJkjh9hOwRCARztLJPqcRoZa+dq5XJ9SCZ0EusZU0g+OLd2SnnM4BD7MnVxVuw0arkSF2MnZ3nFkA3SWADWsfUsk8snmAXVfAXPMHVyOVKjoIvOK+HLurOxDmplXOuQCpLEtfyb+MFV4x3lC1D5QoAAAAASUVORK5CYII=\';\n\n	$css = <<<EOF\nbody.yab-nav-wait * {\n		cursor: wait\n	}\n	#yab-nav-wrap {\n		width: 90%;\n		margin: 0 auto\n	}\n	.placeholder {\n		background: #FFCC33;\n		height: 16px\n	}\n	#yab-nav-wrap1 {\n		clear: both;\n		float: left;\n		width: 30%;\n	}\n	#yab-nav-wrap2 {\n		float: right;\n		width: 30%;\n	}\n	.clearfix {\n		clear: both\n	}\n	#yab-navigation ol, #yab-navigation-new ol {\n		padding: 5px 0\n	}\n	#yab-navigation li, #yab-navigation-new li {\n		position: relative;\n		padding: 0 0 5px 0;\n		margin: 0;\n		clear: both;\n		list-style: none;\n		border: 1px solid transparent;\n		cursor: default\n	}\n	#yab-navigation li li, #yab-navigation-new li li {\n		position: relative;\n		padding: 0;\n		width: auto;\n		margin-left: 1.5em\n	}\n	#yab-navigation li:hover, #yab-navigation-new li:hover {\n		border: 1px dotted #EF8C08\n	}\n	#yab-navigation-new .section-title, #yab-navigation-new .article-title {\n		font-style: italic;\n		color: #777;\n	}\n	#new-section, #new-section-rm,\n	.move,\n	.hide-show,\n	.edit,\n	.new-article, .new-article-rm  {\n		display: block;\n		height: 16px;\n		background: transparent url(data:image/png;base64,$pic) no-repeat 0 0;\n	}\n	#new-section, #new-section-rm {\n		margin-bottom: 1em;\n		float: left;\n		width: 16px;\n		background-position: 0 -16px;\n		text-indent: -3000em;\n		cursor: pointer\n	}\n	#yab-navigation-new .new-article {\n		display: none\n	}\n	.move {\n		float: left;\n		width: 20px;\n		background-position: 0 0;\n		cursor: move;\n		text-indent: -3000em\n	}\n	.new-article, .new-article-rm  {\n		position: absolute;\n		bottom: 5px;\n		left: 18px;\n		width: 16px;\n		background-position: 0 -16px;\n		cursor: pointer;\n		text-indent: -3000em\n	}\n	.prep-section {\n		position: absolute;\n		top: 0;\n		right: 0;\n 		width: 32px;\n 		height: 16px\n	}\n	.prep-article {\n		position: absolute;\n		top: 0;\n		right: 0;\n 		width: 16px;\n 		height: 16px\n	}\n	.hide-show {\n		float: left;\n		width: 16px;\n		background-position: 0 -32px;\n		text-indent: -3000em;\n		cursor: pointer\n	}\n	.edit {\n		position: relative;\n 		float: left;\n		width: 16px;\n		cursor: pointer\n	}\n	#yab-navigation-new .edit {\n		display: none\n	}\n	.prep-section .edit {\n		background-position: 0 -64px\n\n	}\n	.prep-article .edit {\n		background-position: 0 32px\n	}\n	.prep-article .show-icon {\n		background-position: 0 -64px\n	}\n	.hide-li li {\n		display: none\n	}\n	.hide-li .edit-list li {\n		display: block\n	}\n	.hiding {\n		background-position: 0 -48px\n	}\n	.edit ul {\n 		display: none;\n		position: absolute;\n		left: 16px;\n		background: #FFCC33\n	}\n	#yab-navigation .edit ul li {\n		margin: 0;\n		border-bottom: 1px solid #FFFFFF;\n		padding: 0 5px;\n		cursor: pointer\n	}\n	#yab-navigation .edit ul li:hover {\n		color: #FFFFFF\n	}\n	.show ul {\n		display: block\n	}\n	#yab-nav-wrap1 input {\n		margin-right: 5px;\n		width: 60%;\n		background: #FFFFFF;\n		border: 1px solid #FFCC33\n	}\n	#yab-nav-wrap1 input:hover,\n	#yab-nav-wrap1 input:focus {\n		background: #FFCC33;\n		border: 1px solid #FFCC33\n	}\n	#yab-nav-wrap1 button {\n		background: #FFFFFF;\n		border: 1px solid #FFCC33;\n		font-size: 10px;\n		cursor: pointer\n	}\n	#yab-nav-wrap1 button:hover,\n	#yab-nav-wrap button:focus {\n		background: #FFCC33;\n		border: 1px solid #FFCC33\n	}\n	.hide-info {\n		display: block;\n		margin-bottom: 2em\n	}\n	.article-hidden .article-title {\n		color: #777;\n		font-style: italic\n	}\n	#yab-navigation li.new-article-input {\n		position: absolute;\n		width: 90%;\n		bottom: 3px;\n		left: 20px;\n	}\n	#yab-navigation li.new-article-input input {\n		width: 70%\n	}\nEOF;\n\n	return $css;\n}\n\n/**\n * Returns javascript snippet\n *\n * @return string\n */\nfunction yab_nav_header_inject()\n{\n	global $yab_nav_prefs;\n	$hu_link = $yab_nav_prefs[\'hu_link\'];\n	$jquery_ui = $yab_nav_prefs[\'jquery_ui\'];\n\n	$out = <<<EOF\n/* <![CDATA[ */\n$(document).ready(function() {\n	$(\'head\').append(\'<link href=\"$hu_link?event=yab_navigation_inject&step=css\" rel=\"stylesheet\" type=\"text/css\" />\');\n	$.ajax({\n		url: \'$jquery_ui\',\n		dataType: \'script\',\n		cache: true,\n		success: function() {\n			$.ajax({\n				url:\'$hu_link?event=yab_navigation_inject&step=javascript\',\n				dataType: \'script\',\n				cache: true\n			});\n		}\n	});\n});\n/* ]]> */\nEOF;\n\n	return \'<script type=\"text/javascript\">\'.$out.\'</script>\';\n}', '/**\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (@txpinterface == \'admin\')\n{\n	global $yab_nav_prefs;\n	$yab_nav_prefs = array(\n		// navigation settings\n		\'home_title\'                 => \'Home\',\n		\'sort\'                       => \'position asc\',\n		\'active_class\'               => \'active\',\n		\'passive_class\'              => \'passive\',\n		\'exclude_sections\'           => \'\',\n		\'exclude_articles\'           => \'\',\n		// new section defaults\n		\'section_default_page\'       => \'default\',\n		\'section_default_css\'        => \'default\',\n		\'section_default_rss\'        => 0,\n		\'section_default_frontpage\'  => 0,\n		\'section_default_searchable\' => 1,\n		// URIs\n		\'hu_link\'                    => hu.\'textpattern/index.php\',\n		\'jquery_ui\'                  => \'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js\',\n		// other\n		\'article_limit\'              => 30\n	);\n\n	add_privs(\'yab_navigation\',\'1, 2\');\n	register_tab(\'extensions\', \'yab_navigation\', \'Manage Navigation\');\n	register_callback(\'yab_navigation\', \'yab_navigation\');\n\n	add_privs(\'yab_navigation_inject\',\'1, 2\');\n	register_callback(\'yab_navigation_inject\', \'yab_navigation_inject\', \'\', 1);\n}\n\n/**\n * L10n; you can edit the values on your own\n *\n * @param srting $what\n * @return string\n */\nfunction yab_nav_gTxt($what)\n{\n	$l10n = array(\n		\'create_new_section\'   => \'Create New Section\',\n		\'create_new_article\'   => \'Create new article here\',\n		\'move_this\'            => \'Move This\',\n		\'collapse_expand\'      => \'Collapse/Expand\',\n		\'hide\'                 => \'Hide\',\n		\'hide_section\'         => \'Hide this section\',\n		\'rename\'               => \'Rename\',\n		\'rename_section\'       => \'Rename this section\',\n		\'edit\'                 => \'Edit\',\n		\'edit_article\'         => \'Edit this article\',\n		\'rename\'               => \'Rename\',\n		\'rename_article\'       => \'Rename this article\',\n		\'hide_unhide\'          => \'Hide/Unhide\',\n		\'hide_unhide_article\'  => \'Hide/Unhide this article\',\n		\'edit_section\'         => \'Edit This Section\',\n		\'hidden_info\'          => \'To unhide move to the left navigation!\',\n		\'hidden_heading\'       => \'Hidden and new sections\',\n		\'save\'                 => \'Save\',\n		\'create\'               => \'Create\',\n		\'updating\'             => \'Updating &hellip; Please wait\',\n		\'error_new_article\'    => \'Could not create new article!\',\n		\'error_new_section\'    => \'Could not create new section!\',\n		\'error_navigation\'     => \'Could not create navigation!\',\n		\'error_rename_section\' => \'Could not rename section!\',\n		\'error_rename_article\' => \'Could not rename article!\',\n		\'error_unhide_article\' => \'Could not unhide article!\',\n		\'error_hide_article\'   => \'Could not hide article!\',\n		\'error_alter_db\'       => \'An error occured! Could not alter textpattern table.\',\n		\'welcome\'              => \'Welcome to yab_navigation\',\n		\'welcome_info\'         => \'This seems to be your first installation of yab_navigation.<br /><br />Please do an action (f.i. moving your frontpage section to the top of the list or moving another section) on the left side and then reload this site again!<br />So the navigation will be build and this text will disappear.<br /><br />You can call the build navigation with &lt;txp:output_form form=\"yab_navigation\" /&gt; in your page template.\'\n	);\n\n	if ($l10n[$what])\n	{\n		return $l10n[$what];\n	}\n	else\n	{\n		return $what;\n	}\n}\n\n/**\n * Draw initialise and yab_navigation\n *\n * @param string $event as $_GET or $_POST\n * @param string $step as $_GET or $_POST\n * @return echo string displays the ui\n */\nfunction yab_navigation($event, $step)\n{\n	$message = \'\';\n	$content = \'\';\n	$left_side = \'\';\n	$inject = yab_nav_header_inject();\n\n	// look for position columns in articles\n	yab_nav_db_entries(\'position\');\n\n	switch($step)\n	{\n		case \'create_nav\':\n			if (!yab_nav_create_template())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'rename_section\':\n			if (!yab_nav_rename_section())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'create_section\':\n			if (!yab_nav_create_section())\n			{\n				txp_status_header(\'400 Bad Request\');\n			};\n			break;\n		case \'create_article\':\n			if (!yab_nav_create_article())\n			{\n				txp_status_header(\'400 Bad Request\');\n			};\n			break;\n		case \'rename_article\':\n			if(!yab_nav_rename_article())\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'unhide_article\':\n			if(!yab_nav_set_article_status(4))\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		case \'hide_article\':\n			if(!yab_nav_set_article_status(2))\n			{\n				txp_status_header(\'400 Bad Request\');\n			}\n			break;\n		default:\n			// exist form yab_navigation form (first install)?\n			$form = yab_nav_db_entries();\n			if ($form === false)\n			{\n				$message = yab_nav_gTxt(\'error_alter_db\');\n			}\n			else // true, form exists\n			{\n				if (empty($form)) // but empty\n				{\n					$content .= yab_nav_db_nav($form, true); // show nav drom db, template empty\n					$left_side = tag(\n						tag(yab_nav_gTxt(\'welcome\'), \'h2\').yab_nav_gTxt(\'welcome_info\'),\n						\'div\',\n						\' id=\"yab-nav-wrap2\"\'\n					);\n				}\n				else // not empty\n				{\n					$content .= yab_nav_template_nav($form);\n					$left_side = tag(\n						tag(yab_nav_gTxt(\'hidden_heading\'), \'h2\').\n						tag(yab_nav_gTxt(\'hidden_info\'), \'span\', \' class=\"hide-info\"\').\n						yab_nav_db_nav($form),\n						\'div\',\n						\' id=\"yab-nav-wrap2\"\'\n					);\n				}\n			}\n			break;\n	}\n\n	$new_section = tag(\n		yab_nav_gTxt(\'create_new_section\'),\n		\'span\',\n		\' id=\"new-section\" title=\"\'.yab_nav_gTxt(\'create_new_section\').\'\"\'\n	);\n	$clearfix = tag(\'&nbsp;\', \'div\', \' class=\"clearfix\"\');\n\n	$out = tag(\n		tag($new_section.$content, \'div\', \' id=\"yab-nav-wrap1\"\').\n		$left_side.\n		$clearfix,\n		\'div\',\n		\' id=\"yab-nav-wrap\"\'\n	);\n\n	echo $inject.pagetop(\'Manage Navigation\', $message).$out;\n}\n\n/**\n * Returns navigation of new detected section\n *\n * @param string $form yab_navigation form content\n * @param boolean $first should be true if $form is empty\n * @return $string\n */\nfunction yab_nav_db_nav($form = \'\', $first = false)\n{\n	global $yab_nav_prefs;\n	$home_title = $yab_nav_prefs[\'home_title\'];\n	$exclude = $yab_nav_prefs[\'exclude_sections\'];\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n	$limit = $yab_nav_prefs[\'article_limit\'];\n\n	if ($exclude)\n	{\n		$exclude = do_list($exclude);\n		$exclude = join(\"\',\'\", doSlash($exclude));\n		$exclude = \"and name not in(\'$exclude\')\";\n	}\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	// if nav template dont\' exists show only hidden and new sections\n	if (!$first)\n	{\n		$ol_id = \' id=\"yab-navigation-new\"\';\n\n		$sections = safe_rows(\n			\'name, title\',\n			\'txp_section\',\n			\"name != \'default\' AND title != \'$home_title\' $exclude\"\n		);\n\n		// get all sections from nav template\n		preg_match_all(\n			\'/section_list sections=\"(.*?)\"/\',\n			$form,\n			$matches,\n			PREG_SET_ORDER\n		);\n\n		// and unset these on current section array\n		foreach ($matches as $match)\n		{\n			$match_name = trim($match[1]);\n\n			foreach ($sections as $section_key => &$section_value)\n			{\n				if ($match_name == $section_value[\'name\'])\n				{\n					unset($sections[$section_key]);\n				}\n			}\n		}\n	}\n	else\n	{\n	$ol_id = \' id=\"yab-navigation\"\';\n\n		$sections = safe_rows(\n			\'name, title\',\n			\'txp_section\',\n			\"name != \'default\' $exclude\"\n		);\n	}\n\n	$out = \'\';\n	if ($sections)\n	{\n		foreach ($sections as $section)\n		{\n			$section_name = $section[\'name\'];\n			$section_title = $section[\'title\'];\n\n			// show new article icon, if not in exclude articles\n			$yab_nav_new_article_icon = yab_nav_new_article_icon();\n			if ($exlude_article_section)\n			{\n				if (in_array($section_name, $exlude_article_section))\n				{\n					$yab_nav_new_article_icon = \'\';\n				}\n			}\n\n			$articles = safe_rows(\n				\'ID, Title, Status\',\n				\'textpattern\',\n				\"Section = \'$section_name\' ORDER BY title LIMIT $limit\"\n			);\n\n			$a_out = \'\';\n			if ($articles)\n			{\n				foreach ($articles as $article)\n				{\n					$article_class = \'\';\n					if ($article[\'Status\'] == 2)\n					{\n						$article_class = \' class=\"article-hidden\"\';\n					}\n\n					$a_out .= tag(\n						yab_nav_show_move_icon().\n						tag($article[\'Title\'], \'span\', \' class=\"article-title\"\').\n						yab_nav_show_prep_wrap_article(),\n						\'li\',\n						\' id=\"a-\'.$article[\'ID\'].\'\"\'.$article_class.\'\'\n					);\n				}\n			}\n\n			// don\'t show empty ol (sortable) for excluded sections\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section();\n			}\n			else\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section().\'<ol>\'.$a_out.\'</ol>\';\n			}\n\n			$out .= tag(\n				yab_nav_show_move_icon().$yab_nav_new_article_icon.\n				tag($section_title, \'span\', \' class=\"section-title\"\').\n				$a_out_complete,\n				\'li\',\n				\' id=\"yab-nav-s-\'.$section_name.\'\"\'\n			);\n		}\n	}\n	return \'<ol\'.$ol_id.\'>\'.$out.\'</ol>\';\n}\n\n/**\n * Show navigation based on saved yab_navigation form\n *\n * @param string $form from yab_navigation form content\n * @return string\n */\nfunction yab_nav_template_nav($form)\n{\n	global $yab_nav_prefs;\n	$sort = $yab_nav_prefs[\'sort\'];\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n	$limit = $yab_nav_prefs[\'article_limit\'];\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	// preg_match for sections in navigation template\n	preg_match_all(\n		\'/section_list sections=\"(.*?)\"/\',\n		$form,\n		$matches,\n		PREG_SET_ORDER\n	);\n\n	// build frontpage list point\n	$frontpage = safe_row(\n		\'title, name\',\n		\'txp_section\',\n		\"name != \'default\' AND on_frontpage = \'1\'\"\n	);\n\n	$frontpage_section_listpoint = \'\';\n	if ($frontpage)\n	{\n		$frontpage_name = $frontpage[\'name\'];\n		$frontpage_title = $frontpage[\'title\'];\n		$frontpage_articles = safe_rows(\n			\'ID, Title\',\n			\'textpattern\',\n			\"Section = \'$frontpage_name\' ORDER BY $sort LIMIT $limit\"\n		);\n\n		$frontpage_article_listpoint = \'\';\n		if ($frontpage_articles)\n		{\n			foreach ($frontpage_articles as $frontspage_article)\n			{\n				$frontpage_article_listpoint .= tag(\n					yab_nav_show_move_icon().\n					tag($frontspage_article[\'Title\'], \'span\', \' class=\"article-title\"\').\n					yab_nav_show_prep_wrap_article(),\n					\'li\',\n					\' id=\"a-\'.$frontspage_article[\'ID\'].\'\"\'\n				);\n			}\n		}\n\n		$frontpage_article_listpoint = yab_nav_show_prep_wrap_section().\n			\'<ol>\'.$frontpage_article_listpoint.\'</ol>\';\n		$frontpage_section_listpoint = tag(\n			yab_nav_show_move_icon().\n			tag($frontpage_title, \'span\', \' class=\"section-title\"\').n.\n			$frontpage_article_listpoint,\n			\'li\',\n			\' id=\"yab-nav-s-\'.$frontpage_name.\'\"\'\n		);\n	}\n\n	// build other listpoints\n	if ($matches)\n	{\n		$out = \'\';\n\n		foreach ($matches as $sections)\n		{\n			$section_name = trim($sections[1]);\n			$section = safe_row(\n				\'title\',\n				\'txp_section\',\n				\"name = \'$section_name\'\"\n			);\n			$section_title = $section[\'title\'];\n\n			// show new article icon, if not in exclude articles\n			$yab_nav_new_article_icon = yab_nav_new_article_icon();\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$yab_nav_new_article_icon = \'\';\n			}\n\n			$articles = safe_rows(\n				\'ID, Title, Status\',\n				\'textpattern\',\n				\"Section = \'$section_name\' ORDER BY $sort LIMIT $limit\"\n			);\n\n			$a_out = \'\';\n			if ($articles)\n			{\n				foreach ($articles as $article)\n				{\n					$article_class = \'\';\n					if ($article[\'Status\'] == 2)\n					{\n						$article_class = \' class=\"article-hidden\"\';\n					}\n					$a_out .= tag(\n						yab_nav_show_move_icon().\n						tag($article[\'Title\'], \'span\', \' class=\"article-title\"\').\n						yab_nav_show_prep_wrap_article(),\n						\'li\',\n						\' id=\"a-\'.$article[\'ID\'].\'\"\'.$article_class.\'\'\n					);\n				}\n			}\n\n			// don\'t show empty ol (sortable) for excluded sections\n			if (in_array($section_name, $exlude_article_section))\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section();\n			}\n			else\n			{\n				$a_out_complete = yab_nav_show_prep_wrap_section().\'<ol>\'.$a_out.\'</ol>\';\n			}\n\n			$out .= tag(\n				yab_nav_show_move_icon().$yab_nav_new_article_icon.\n				tag($section_title, \'span\', \' class=\"section-title\"\').\n				$a_out_complete,\n				\'li\',\n				\' id=\"yab-nav-s-\'.$section_name.\'\"\'\n			);\n		}\n		$out = $frontpage_section_listpoint.$out;\n		return tag($out, \'ol\', \' id=\"yab-navigation\"\');\n	}\n}\n\n/**\n * Hides and Unhides articles\n *\n * @param integer $status numeric status value of the article\n * @return boolean\n */\nfunction yab_nav_set_article_status($status)\n{\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$id = $article[\'id\'];\n\n	$rs = safe_update(\n		\'textpattern\',\n		\"Status = $status\",\n		\"ID = $id\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Creates a new section\n *\n * @return boolean\n */\nfunction yab_nav_create_section()\n{\n	global $yab_nav_prefs;\n	$page = $yab_nav_prefs[\'section_default_page\'];\n	$css = $yab_nav_prefs[\'section_default_css\'];\n	$rss = $yab_nav_prefs[\'section_default_rss\'];\n	$frontpage = $yab_nav_prefs[\'section_default_frontpage\'];\n	$searchable = $yab_nav_prefs[\'section_default_searchable\'];\n\n	$xml_array = yab_nav_load_from_ajax();\n	$section = $xml_array[\'section\'];\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$section_title = doSlash($textile->TextileThis($section[\'title\'], 1));\n	$section_name = doSlash(strtolower(sanitizeForUrl($section[\'title\'])));\n\n	$rs = safe_insert(\n		\'txp_section\',\n		\"name        = \'$section_name\',\n		title        = \'$section_title\',\n		page         = \'$page\',\n		css          = \'$css\',\n		is_default   = 0,\n		in_rss       = $rss,\n		on_frontpage = $frontpage,\n		searchable   = $searchable\"\n	);\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Creates a new article\n *\n * @return boolean\n */\nfunction yab_nav_create_article()\n{\n	global $txp_user;\n\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$section = $article[\'section\'];\n	$position = $article[\'position\'];\n	$status = 2;\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$article_title = doSlash($textile->TextileThis($article[\'title\'], \'\', 1));\n	$url_title = stripSpace($article[\'title\'], 1);\n\n	$count = safe_count(\'textpattern\', \"Section = \'$section\'\");\n	if ($count < 1)\n	{\n		$status = 5;\n	}\n\n	$rs = safe_insert(\n		\'textpattern\',\n		\"Title    = \'$article_title\',\n		Status    = $status,\n		Posted    = now(),\n		AuthorID  = \'$txp_user\',\n		LastMod   = now(),\n		LastModID = \'$txp_user\',\n		Section   = \'$section\',\n		url_title = \'$url_title\',\n		position  = $position,\n		uid       = \'\".md5(uniqid(rand(),true)).\"\',\n		feed_time = now()\"\n	);\n\n	$GLOBALS[\'ID\'] = mysql_insert_id();\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Renames an article title\n *\n * @return boolean\n */\nfunction yab_nav_rename_article()\n{\n	global $txp_user;\n\n	include_once txpath.\'/lib/classTextile.php\';\n	$textile = new Textile();\n	$xml_array = yab_nav_load_from_ajax();\n	$article = $xml_array[\'article\'];\n	$article_id = $article[\'id\'];\n	$article_title = doSlash($textile->TextileThis($article[\'title\'], \'\', 1));\n\n	$rs = safe_update(\n		\'textpattern\',\n		\"Title    = \'$article_title\',\n		LastMod   = now(),\n		LastModID = \'$txp_user\'\",\n		\"ID = $article_id\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Renames a section title\n *\n * @return boolean\n */\nfunction yab_nav_rename_section()\n{\n	$xml_array = yab_nav_load_from_ajax();\n	$section = $xml_array[\'section\'];\n	$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']);\n	$section_title = doSlash($section[\'title\']);\n\n	$rs = safe_update(\n		\'txp_section\',\n		\"title = \'$section_title\'\",\n		\"name = \'$section_name\'\"\n	);\n\n	if ($rs)\n	{\n		return true;\n	}\n	return false;\n}\n\n/**\n * Updates the navigation with ajax xml values\n *\n * @return boolean\n */\nfunction yab_nav_create_template()\n{\n	$xml_array = yab_nav_load_from_ajax();\n\n	$do_form = yab_nav_do_form($xml_array);\n	if (!$do_form)\n	{\n		return false;\n	}\n\n	$i = 0;\n	foreach ($xml_array[\'section\'] as $section)\n	{\n		$i++;\n		$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']);\n		if ($i === 1)\n		{\n			$rsa = safe_update(\n				\'txp_section\',\n				\"on_frontpage = \'1\'\",\n				\"name = \'$section_name\'\"\n			);\n		}\n		else\n		{\n			$rsa = safe_update(\n				\'txp_section\',\n				\"on_frontpage = \'0\'\",\n				\"name = \'$section_name\'\"\n			);\n		}\n\n		if (!$rsa)\n		{\n			return false;\n		}\n\n		if (isset($section[\'article\']))\n		{\n			if (is_array($section[\'article\']))\n			{\n				$j = 0;\n				foreach ($section[\'article\'] as $article)\n				{\n					$j++;\n					$article_id = str_replace(\'a-\', \'\', $article);\n					if ($j === 1)\n					{\n						$rsb = safe_update(\n							\'textpattern\',\n							\"position = $j, Status = 5, Section = \'$section_name\'\",\n							\"ID = \'$article_id\'\"\n						);\n					}\n					else\n					{\n						$old_status = safe_field(\n							\'Status\',\n							\'textpattern\',\n							\"ID = $article_id\"\n						);\n\n						$status = 4;\n						if ($old_status)\n						{\n							if ($old_status < 4)\n							{\n								$status = $old_status;\n							}\n							$rsb = safe_update(\n								\'textpattern\',\n								\"position = $j, Status = $status, Section = \'$section_name\'\",\n								\"ID = $article_id\"\n							);\n						}\n						else\n						{\n							return false;\n						}\n					}\n					if (!$rsb)\n					{\n						return false;\n					}\n				}\n			}\n			else\n			{\n				$article_id = str_replace(\'a-\', \'\', $section[\'article\']);\n				$rsc = safe_update(\n					\'textpattern\',\n					\"position = 1, Status = 5, Section = \'$section_name\'\",\n					\"ID = \'$article_id\'\"\n				);\n				if (!$rsc)\n				{\n					return false;\n				}\n			}\n			update_lastmod();\n		}\n	}\n	return true;\n}\n\n\n/**\n * Save navigation template in yab_navigation form\n *\n * @param array $xml_array converted xml object\n * @return boolean\n */\nfunction yab_nav_do_form($xml_array)\n{\n	$nav = \'<ul>\'.n;\n	$nav .= yab_nav_do_section_class(\'default\').n;\n	$nav .= yab_nav_do_section(\'default\', true).n;\n\n	$i = 0;\n	foreach ($xml_array[\'section\'] as $section)\n	{\n		if ($i > 0)\n		{\n			$section_name = str_replace(\'yab-nav-s-\', \'\', $section[\'name\']).n;\n			$section_name = str_replace(n, \'\', $section_name);\n			$nav .= yab_nav_do_section_class($section_name).n;\n			$nav .= yab_nav_do_section($section_name).n;\n		}\n		$i++;\n	}\n\n	$nav .= \'</ul>\';\n	$nav = doSlash($nav);\n	$rs = safe_update(\'txp_form\', \"Form = \'$nav\'\", \"name = \'yab_navigation\'\");\n\n	if ($rs)\n	{\n		update_lastmod();\n		return true;\n	}\n	return false;\n}\n\n/**\n * Alters/prepares the database for usage\n *\n * @param string $what what should be performed\n * @return mixed boolean/string\n */\nfunction yab_nav_db_entries($what = \'\')\n{\n	if ($what == \'position\')\n	{\n		$columns = safe_show(\'columns\', \'textpattern\');\n		foreach ($columns as $column)\n		{\n			if ($column[\'Field\'] == \'position\')\n			{\n				return true;\n			}\n		}\n\n		$rs_pos = safe_alter(\n			\'textpattern\',\n			\"ADD position INT NOT NULL DEFAULT \'0\';\"\n		);\n		if ($rs_pos)\n		{\n			return true;\n		}\n		else\n		{\n			return false;\n		}\n	}\n	else\n	{\n		$form = safe_field(\'Form\', \'txp_form\', \"Name = \'yab_navigation\'\");\n		if ($form !== false)\n		{\n			return $form;\n		}\n		else\n		{\n			$rs_form = safe_insert(\n				\'txp_form\',\n				\"Form = \'\', type = \'misc\', name = \'yab_navigation\'\"\n			);\n			if ($rs_form)\n			{\n				return true;\n			}\n			else\n			{\n				return false;\n			}\n		}\n	}\n}\n\n/**\n * Get ajax xml from php://input and return as array\n *\n * @return array\n */\nfunction yab_nav_load_from_ajax()\n{\n	$xml = file_get_contents(\'php://input\');\n	$xml = simplexml_load_string($xml);\n	$xml = yab_nav_xml2array($xml);\n\n	return $xml;\n}\n\n/**\n * Converts a XML object to an array\n *\n * @param object $xmlobject XML object\n * @return array\n */\nfunction yab_nav_xml2array($xmlobject)\n{\n	$xml_array = array();\n	if (is_object($xmlobject))\n	{\n		settype($xmlobject, \'array\');\n	}\n	foreach ($xmlobject as $key => $value)\n	{\n		if (is_array($value) or is_object($value))\n		{\n			$xml_array[$key] = yab_nav_xml2array($value);\n		}\n		else\n		{\n			$xml_array[$key] = $value;\n		}\n	}\n	return $xml_array;\n}\n\n/**\n * Build txp tag variable\n *\n * @param string $name\n * @param string $value\n * @return string\n */\nfunction yab_nav_do_variable($name, $value)\n{\n	return \'<txp:variable name=\"\'.$name.\'\" value=\"\'.$value.\'\" />\'.n;\n}\n\n/**\n * Build txp tag if_section statement for active class\n *\n * @param string $section\n * @return string\n */\nfunction yab_nav_do_section_class($section)\n{\n	global $yab_nav_prefs;\n	$passive_class = $yab_nav_prefs[\'passive_class\'];\n	$active_class = $yab_nav_prefs[\'active_class\'];\n\n	return\n		\'<txp:variable name=\"default_class\" value=\"\'.$passive_class.\'\" />\'.n.\n		\'<txp:if_section name=\"\'.$section.\'\">\'.n.\n			\'<txp:variable name=\"default_class\" value=\"\'.$active_class.\'\" />\'.n.\n		\'</txp:if_section>\';\n}\n\n/**\n * Build txp tag if_article_id statement for active class\n *\n * @return string\n */\nfunction yab_nav_do_article_class()\n{\n	global $yab_nav_prefs;\n	$passive_class = $yab_nav_prefs[\'passive_class\'];\n	$active_class = $yab_nav_prefs[\'active_class\'];\n\n	return\n		\'<txp:variable name=\"article_class\" value=\"\'.$passive_class.\'\" />\'.n.\n		\'<txp:if_article_id>\'.n.\n			\'<txp:variable name=\"article_class\" value=\"\'.$active_class.\'\" />\'.n.\n		\'</txp:if_article_id>\';\n}\n\n/**\n * Build txp tag article list\n *\n * @param string $section\n * @param string $sort\n * @return string\n */\nfunction yab_nav_do_article_list($section, $sort)\n{\n	global $yab_nav_prefs;\n	$exlude_article_section = $yab_nav_prefs[\'exclude_articles\'];\n\n	if ($exlude_article_section)\n	{\n		$exlude_article_section = do_list($exlude_article_section);\n	}\n	else\n	{\n		$exlude_article_section = array();\n	}\n\n	if (!in_array($section, $exlude_article_section))\n	{\n	return\n		\'<txp:article_custom section=\"\'.\n				$section.\n				\'\"  wraptag=\"ul\"  break=\"\" sort=\"\'.\n				$sort.\n				\'\" status=\"4\">\'.n.\n			yab_nav_do_article_class().n.\n			\'<li class=\"<txp:variable name=\"article_class\" />\">\'.n.\n				\'<txp:permlink><txp:title /></txp:permlink>\'.n.\n			\'</li>\'.n.\n		\'</txp:article_custom>\';\n	}\n	else\n	{\n		return \'\';\n	}\n}\n\n/**\n * Build txp tag section lists\n *\n * @param string $section\n * @param boolean $home\n * @return string\n */\nfunction yab_nav_do_section($section, $home = false)\n{\n	global $yab_nav_prefs;\n	$home_title = $yab_nav_prefs[\'home_title\'];\n	$sort = $yab_nav_prefs[\'sort\'];\n\n	if ($home)\n	{\n		return\n			\'<li class=\"<txp:variable name=\\\'default_class\\\' />\">\'.n.\n				\'<txp:link_to_home>\'.$home_title.\'</txp:link_to_home>\'.n.\n			\'</li>\';\n	}\n	else\n	{\n		return\n			\'<li class=\"<txp:variable name=\\\'default_class\\\' />\">\'.n.\n				\'<txp:section_list sections=\"\'.\n					$section.\n					\'\" wraptag=\"\" break=\"\" include_default=\"0\" />\'.n.\n				yab_nav_do_article_list($section, $sort).n.\n			\'</li>\';\n	}\n}\n\n/**\n * Build span for create article icon\n *\n * @return $string\n */\nfunction yab_nav_new_article_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'create_new_article\'),\n		\'span\',\n		\' class=\"new-article\" title=\"\'.yab_nav_gTxt(\'create_new_article\').\'\"\'\n	);\n}\n\n/**\n * Build span for move icon\n *\n * @return $string\n */\nfunction yab_nav_show_move_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'move_this\'),\n		\'span\',\n		\' class=\"move\" title=\"\'.yab_nav_gTxt(\'move_this\').\'\"\'\n	);\n}\n\n/**\n * Build span for expand/collapse icon\n *\n * @return $string\n */\nfunction yab_nav_show_exp_coll_icon()\n{\n	return tag(\n		yab_nav_gTxt(\'collapse_expand\'),\n		\'span\',\n		\' class=\"hide-show\" title=\"\'.yab_nav_gTxt(\'collapse_expand\').\'\"\'\n	);\n}\n\n/**\n * Build list for section editing\n *\n * @return $string\n */\nfunction yab_nav_show_edit_section_list()\n{\n	return tag(\n		tag(\n			yab_nav_gTxt(\'hide\'),\n			\'li\',\n			\' class=\"hide-section\" title=\"\'.yab_nav_gTxt(\'hide_section\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'rename\'),\n			\'li\',\n			\' class=\"rename-section\" title=\"\'.yab_nav_gTxt(\'rename_section\').\'\"\'\n		),\n		\'ul\',\n		\' class=\"edit-list\"\'\n	);\n}\n\n/**\n * Build list for article editing\n *\n * @return $string\n */\nfunction yab_nav_show_edit_article_list()\n{\n	return tag(\n		tag(\n			yab_nav_gTxt(\'edit\'),\n			\'li\',\n			\' class=\"edit-article\" title=\"\'.yab_nav_gTxt(\'edit_article\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'rename\'),\n			\'li\',\n			\' class=\"rename-article\" title=\"\'.yab_nav_gTxt(\'rename_article\').\'\"\'\n		).\n		tag(\n			yab_nav_gTxt(\'hide_unhide\'),\n			\'li\',\n			\' class=\"hide-article\" title=\"\'.yab_nav_gTxt(\'hide_unhide_article\').\'\"\'\n		),\n		\'ul\',\n		\' class=\"edit-list\"\'\n	);\n}\n\n/**\n * Build div for section editing icon\n *\n * @return $string\n */\nfunction yab_nav_show_edit_icon_section()\n{\n	return tag(\n		yab_nav_show_edit_section_list(),\n		\'div\',\n		\' class=\"edit\" title=\"\'.yab_nav_gTxt(\'edit_section\').\'\"\'\n	);\n}\n\n/**\n * Build wrapper for section editing\n *\n * @return $string\n */\nfunction yab_nav_show_prep_wrap_section()\n{\n	return tag(\n		yab_nav_show_exp_coll_icon().yab_nav_show_edit_icon_section(),\n		\'div\',\n		\' class=\"prep-section\"\'\n	);\n}\n\n/**\n * Build div for article editing list\n *\n * @return $string\n */\nfunction yab_nav_show_edit_icon_article()\n{\n	return tag(\n		yab_nav_show_edit_article_list(),\n		\'div\',\n		\' class=\"edit\" title=\"\'.yab_nav_gTxt(\'edit_article\').\'\"\'\n	);\n}\n\n/**\n * Build wrapper for article editing\n *\n * @return $string\n */\nfunction yab_nav_show_prep_wrap_article()\n{\n	return tag(\n		yab_nav_show_edit_icon_article(),\n		\'div\',\n		\' class=\"prep-article\"\'\n	);\n}\n\n/**\n * Echoes javascript or css content\n *\n * @param string $event\n * @param string $step\n * @return void\n */\nfunction yab_navigation_inject($event, $step)\n{\n	while (@ob_end_clean());\n\n	if ($step == \'javascript\')\n	{\n		header(\"Content-type: text/javascript\");\n		echo yab_nav_javascript();\n	}\n	else\n	{\n		header(\"Content-type: text/css\");\n		echo yab_nav_css();\n	}\n\n	exit(0);\n}\n\n/**\n * Returns javascript\n *\n * @return string\n */\nfunction yab_nav_javascript()\n{\n	global $yab_nav_prefs;\n	$hu_link = $yab_nav_prefs[\'hu_link\'];\n\n	$save = yab_nav_gTxt(\'save\');\n	$create = yab_nav_gTxt(\'create\');\n	$updating = yab_nav_gTxt(\'updating\');\n	$error_new_article = yab_nav_gTxt(\'error_new_article\');\n	$error_new_section = yab_nav_gTxt(\'error_new_section\');\n	$error_navigation = yab_nav_gTxt(\'error_navigation\');\n	$error_rename_section = yab_nav_gTxt(\'error_rename_section\');\n	$error_rename_article = yab_nav_gTxt(\'error_rename_article\');\n	$error_unhide_article = yab_nav_gTxt(\'error_unhide_article\');\n	$error_hide_article = yab_nav_gTxt(\'error_hide_article\');\n\n	$js = <<<EOF\n$(document).ready(function() {\n	// info while ajax request\n	$(\'#messagepane\').ajaxSend(function() {\n		$(this).append(\n			\'<span class=\"yab-nav-updating\">$updating</span>\'\n		);\n		$(\'body\').addClass(\'yab-nav-wait\');\n	});\n	$(\'#messagepane\').ajaxStop(function() {\n		$(\'.yab-nav-updating\').remove();\n		$(\'body\').removeClass(\'yab-nav-wait\');\n	});\n	// can sort all li between all ol\n	$(\'#yab-navigation ol\').sortable({\n		connectWith: \'#yab-navigation ol\',\n		placeholder: \'placeholder\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	// 	can sort ol\n	$(\'#yab-navigation\').sortable({\n		placeholder: \'placeholder\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	$(\'#yab-navigation-new\').sortable({\n		placeholder: \'placeholder\',\n		connectWith: \'#yab-navigation\',\n		handle: \'.move\',\n		opacity: \'0.5\',\n		stop: yab_nav_do_ajax_nav\n	}).disableSelection();\n	// show article edit icons on li hover\n	$(\'#yab-navigation li li\').hover(function() {\n		$(\'.prep-article .edit\', this).addClass(\'show-icon\');\n	}, function() {\n		$(\'.prep-article .edit\', this).removeClass(\'show-icon\');\n	});\n	// hide article menu\n	$(\'.hide-show\').toggle(function() {\n		$(this).addClass(\'hiding\');\n		$(this).parents(\'li\').addClass(\'hide-li\');\n	}, function() {\n		$(this).removeClass(\'hiding\');\n		$(this).parents(\'li\').removeClass(\'hide-li\');\n	});\n	// show/hide edit menu\n	$(\'.edit, .edit ul\').hover(function() {\n		$(this).addClass(\'show\');\n	}, function() {\n		$(this).removeClass(\'show\');\n	});\n	// rename section\n	$(\'.rename-section\').live(\'click\', function() {\n		var section_li = $(this).parents(\'#yab-navigation li\');\n		var section =	$(section_li).children(\'.section-title\');\n		$(section).replaceWith(\n			\'<input type=\"text\" value=\"\' +\n			yab_nav_htmlquot($(section).text()) +\n			\'\" class=\"section-input\" /><button class=\"section-submit\">$save</button>\'\n		);\n		$(\'.section-submit\').click(function() {\n			var sibling_input = $(this).siblings(\'.section-input\');\n			// save new name with ajax\n			if ($(section).text() != $(sibling_input).val())\n			{\n				var section_name = $(section_li).attr(\'id\');\n				yab_nav_do_ajax_rename_section(section_name, $(sibling_input).val());\n			}\n			$(sibling_input).replaceWith(\n				\'<span class=\"section-title\">\' +\n				$(sibling_input).val() +\n				\'</span>\'\n			);\n			$(this).remove();\n		});\n	});\n	// rename article\n	$(\'.rename-article\').live(\'click\', function() {\n		var article_li = $(this).parents(\'#yab-navigation ol li\');\n		var article = $(article_li).children(\'.article-title\');\n		$(article).replaceWith(\n			\'<input type=\"text\" value=\"\' +\n			yab_nav_htmlquot(yab_nav_htmlspecialchars($(article).text())) +\n			\'\" class=\"article-input\" /><button class=\"article-submit\">$save</button>\'\n		);\n		$(\'.article-submit\').click(function() {\n			var sibling_input = $(this).siblings(\'.article-input\');\n			// save new name with ajax\n			if ($(article).text() != $(sibling_input).val())\n			{\n				var article_id = $(article_li).attr(\'id\').replace(/a-/, \'\');\n				yab_nav_do_ajax_rename_article(article_id, $(sibling_input).val());\n			}\n			$(sibling_input).replaceWith(\n				\'<span class=\"article-title\">\' +\n				$(sibling_input).val() +\n				\'</span>\'\n			);\n			$(this).remove();\n		});\n	});\n	// edit this article\n	$(\'.edit-article\').click(function() {\n		var article_id = $(this).parents(\'#yab-navigation ol li\').attr(\'id\');\n		article_id = article_id.replace(/a-/, \'\');\n		location.href = \'$hu_link?event=article&step=edit&ID=\' + article_id;\n	});\n	//create new section\n	$(\'#new-section\').live(\'click\', function() {\n		$(this).after(\n			\'<input type=\"text\" value=\"\" id=\"new-section-title\" />\' +\n			\'<button id=\"new-section-submit\">Create</button>\'\n		);\n		$(\'#new-section-submit\').click(function() {\n			var new_title = $(\'#new-section-title\').val();\n			if (new_title != \'\')\n			{\n				yab_nav_do_ajax_create_section(new_title);\n			}\n				$(\'#new-section-rm\').attr(\'id\', \'new-section\');\n				$(\'#new-section-submit, #new-section-title\').remove();\n		});\n		$(this).attr(\'id\', \'new-section-rm\');\n	});\n	// second click, toogle won\'t work correctly with live()-event\n	$(\'#new-section-rm\').live(\'click\', function() {\n		$(this).attr(\'id\', \'new-section\');\n		$(\'#new-section-submit, #new-section-title\').remove();\n	});\n	// hide sections\n	$(\'.hide-section\').click(function() {\n		$(\'#yab-navigation-new\').append($(this).parents(\'#yab-navigation li\'));\n		yab_nav_do_ajax_nav();\n	});\n	// hide articles\n	$(\'.hide-article\').click(function() {\n		var this_article_li = $(this).parents(\'#yab-navigation ol li\');\n		var this_article_id = this_article_li.attr(\'id\');\n		this_article_id = this_article_id.replace(/a-/, \'\');\n		var this_class = this_article_li.attr(\'class\');\n		if (this_class == \'article-hidden\') {	// unhide\n			$(this_article_li).removeClass(\'article-hidden\');\n			yab_nav_do_unhide_article(this_article_id);\n		} else { // hide\n			$(this_article_li).addClass(\'article-hidden\');\n			yab_nav_do_hide_article(this_article_id);\n		}\n	});\n	//create new articles\n	$(\'.new-article\').live(\'click\', function() {\n		var this_section_li = $(this).parents(\'#yab-navigation li\');\n		var this_section = this_section_li.attr(\'id\').replace(/yab-nav-s-/, \'\');\n		$(this_section_li).children(\'ol\').append(\n			\'<li class=\"new-article-input\"><input type=\"text\" value=\"\" class=\"new-article-title\" />\' +\n			\'<button class=\"new-article-submit\">$create</button></li>\'\n		);\n		$(\'.new-article-submit\').click(function() {\n			var new_article_title = $(\'.new-article-title\').val();\n			if (new_article_title != \'\')\n			{\n				var position = $(this).parent(\'li\').prevAll().length + 1;\n				yab_nav_do_ajax_create_article(new_article_title, this_section, position);\n			}\n				$(\'.new-article-rm\').attr(\'class\', \'new-article\');\n				$(\'.new-article-input\').remove();\n		});\n		$(this).attr(\'class\', \'new-article-rm\');\n	});\n	// second click, toogle won\'t work correctly with live()-event\n	$(\'.new-article-rm\').live(\'click\', function() {\n		$(this).attr(\'class\', \'new-article\');\n		$(\'.new-article-input\').remove();\n	});\n});\n\nfunction yab_nav_do_ajax_create_article(new_title, section, position) {\n	var nav;\n	new_title = yab_nav_htmlspecialchars(new_title);\n	nav = yab_nav_do_xml(\'title\', new_title) +\n		yab_nav_do_xml(\'section\', section) +\n		yab_nav_do_xml(\'position\', position);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'create_article\');\n}\n\nfunction yab_nav_do_unhide_article(id) {\n	var nav;\n	nav = yab_nav_do_xml(\'id\', id);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'unhide_article\');\n}\n\nfunction yab_nav_do_hide_article(id) {\n	var nav;\n	nav = yab_nav_do_xml(\'id\', id);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'hide_article\');\n}\n\nfunction yab_nav_do_ajax_create_section(new_title) {\n	var nav;\n	new_title = yab_nav_htmlspecialchars(new_title);\n	nav = yab_nav_do_xml(\'title\', new_title);\n	nav = yab_nav_do_xml(\'section\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'create_section\');\n}\n\nfunction yab_nav_do_ajax_rename_section(name, title) {\n	var nav;\n	title = yab_nav_htmlspecialchars(title);\n	nav = yab_nav_do_xml(\'name\', name) + yab_nav_do_xml(\'title\', title);\n	nav = yab_nav_do_xml(\'section\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'rename_section\');\n}\n\nfunction yab_nav_do_ajax_rename_article(id, title) {\n	var nav;\n	title = yab_nav_htmlspecialchars(title);\n	nav = yab_nav_do_xml(\'id\', id) + yab_nav_do_xml(\'title\', title);\n	nav = yab_nav_do_xml(\'article\', nav);\n	nav = yab_nav_do_xml(\'navigation\', nav);\n	yab_nav_do_ajax(nav, \'rename_article\');\n}\n\nfunction yab_nav_do_ajax_nav() {\n	var nav;\n	var section = \'\';\n	var name = \'\';\n	var this_section;\n	$(\'#yab-navigation > li\').each(function() {\n		var article = \'\';\n		this_section = $(this).attr(\'id\');\n		$(\'ol > li\', this).each(function() {\n			article = article + yab_nav_do_xml(\'article\', $(this).attr(\'id\'));\n		});\n		name = yab_nav_do_xml(\'name\', this_section);\n		section = section + yab_nav_do_xml(\'section\', name + article);\n	});\n	nav = yab_nav_do_xml(\'navigation\', section);\n	yab_nav_do_ajax(nav, \'create_nav\');\n}\n\nfunction yab_nav_do_ajax(xml, step) {\n	return $.ajax({\n		url: \'$hu_link?event=yab_navigation&step=\' + step,\n		type: \'POST\',\n		contentType: \'text/xml\',\n		processData: false,\n		data: xml,\n		complete: function(response, status) {\n			yab_nav_complete_handler(status, step);\n		}\n	});\n}\n\nfunction yab_nav_complete_handler(status, step) {\n	switch (step) {\n		case \'create_article\':\n			if (status == \'success\') {\n				location.href = \'$hu_link?event=yab_navigation\';\n			} else {\n				$(\'#messagepane\').append(\'$error_new_article\');\n			}\n			break;\n		case \'create_section\':\n			if (status == \'success\') {\n				location.href = \'$hu_link?event=yab_navigation\';\n			} else {\n				$(\'#messagepane\').append(\'$error_new_section\');\n			}\n			break;\n		case \'create_nav\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_navigation\');\n			}\n			break;\n		case \'rename_section\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_rename_section\');\n			}\n			break;\n		case \'rename_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_rename_article\');\n			}\n		case \'unhide_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_unhide_article\');\n			}\n		case \'hide_article\':\n			if (status == \'error\') {\n				$(\'#messagepane\').append(\'$error_hide_article\');\n			}\n			break;\n		default:\n			break;\n	}\n}\n\nfunction yab_nav_do_xml(name, content) {\n	var xml;\n	if (!content) { xml = \'<\' + name + \'/>\'; }\n	else { xml = \'<\' + name + \'>\' + content + \'</\' + name + \'>\'; }\n	return xml;\n}\n\nfunction yab_nav_htmlquot(string) {\n	return string.replace(/\"/g, \'&quot;\');\n}\n\nfunction yab_nav_htmlspecialchars(string) {\n	return $(\'<span>\').text(string).html();\n}\nEOF;\n\n	return $js;\n}\n\n/**\n * Returns CSS\n *\n * @return string\n */\nfunction yab_nav_css()\n{\n	$pic = \'iVBORw0KGgoAAAANSUhEUgAAABAAAABQCAYAAAAQq/UNAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAASFJREFUWMPtWDsSgjAQfTAUGTs6uR00egEKCzuZsckFrOAsXsFDYJeWSm1SRCYbNjCMn9lXwbL7siSb5CWAIKE+GK22AHr7WuT1cGcTjIIRIkmJBHqmLQyj1dNo9Qz5pEs7MV1lFKi083pIuBkUTNvyOhD853pgtDoBOHKqEJwytjNyR5V3zGTa+0gyIgs1Ml0o1oywuwS5yz2HIAXwiM1gw+2YVQgaADfCv5GpI/iYQgnuSlyFwpc4hLyJkjh9hOwRCARztLJPqcRoZa+dq5XJ9SCZ0EusZU0g+OLd2SnnM4BD7MnVxVuw0arkSF2MnZ3nFkA3SWADWsfUsk8snmAXVfAXPMHVyOVKjoIvOK+HLurOxDmplXOuQCpLEtfyb+MFV4x3lC1D5QoAAAAASUVORK5CYII=\';\n\n	$css = <<<EOF\nbody.yab-nav-wait * {\n		cursor: wait\n	}\n	#yab-nav-wrap {\n		width: 90%;\n		margin: 0 auto\n	}\n	.placeholder {\n		background: #FFCC33;\n		height: 16px\n	}\n	#yab-nav-wrap1 {\n		clear: both;\n		float: left;\n		width: 30%;\n	}\n	#yab-nav-wrap2 {\n		float: right;\n		width: 30%;\n	}\n	.clearfix {\n		clear: both\n	}\n	#yab-navigation ol, #yab-navigation-new ol {\n		padding: 5px 0\n	}\n	#yab-navigation li, #yab-navigation-new li {\n		position: relative;\n		padding: 0 0 5px 0;\n		margin: 0;\n		clear: both;\n		list-style: none;\n		border: 1px solid transparent;\n		cursor: default\n	}\n	#yab-navigation li li, #yab-navigation-new li li {\n		position: relative;\n		padding: 0;\n		width: auto;\n		margin-left: 1.5em\n	}\n	#yab-navigation li:hover, #yab-navigation-new li:hover {\n		border: 1px dotted #EF8C08\n	}\n	#yab-navigation-new .section-title, #yab-navigation-new .article-title {\n		font-style: italic;\n		color: #777;\n	}\n	#new-section, #new-section-rm,\n	.move,\n	.hide-show,\n	.edit,\n	.new-article, .new-article-rm  {\n		display: block;\n		height: 16px;\n		background: transparent url(data:image/png;base64,$pic) no-repeat 0 0;\n	}\n	#new-section, #new-section-rm {\n		margin-bottom: 1em;\n		float: left;\n		width: 16px;\n		background-position: 0 -16px;\n		text-indent: -3000em;\n		cursor: pointer\n	}\n	#yab-navigation-new .new-article {\n		display: none\n	}\n	.move {\n		float: left;\n		width: 20px;\n		background-position: 0 0;\n		cursor: move;\n		text-indent: -3000em\n	}\n	.new-article, .new-article-rm  {\n		position: absolute;\n		bottom: 5px;\n		left: 18px;\n		width: 16px;\n		background-position: 0 -16px;\n		cursor: pointer;\n		text-indent: -3000em\n	}\n	.prep-section {\n		position: absolute;\n		top: 0;\n		right: 0;\n 		width: 32px;\n 		height: 16px\n	}\n	.prep-article {\n		position: absolute;\n		top: 0;\n		right: 0;\n 		width: 16px;\n 		height: 16px\n	}\n	.hide-show {\n		float: left;\n		width: 16px;\n		background-position: 0 -32px;\n		text-indent: -3000em;\n		cursor: pointer\n	}\n	.edit {\n		position: relative;\n 		float: left;\n		width: 16px;\n		cursor: pointer\n	}\n	#yab-navigation-new .edit {\n		display: none\n	}\n	.prep-section .edit {\n		background-position: 0 -64px\n\n	}\n	.prep-article .edit {\n		background-position: 0 32px\n	}\n	.prep-article .show-icon {\n		background-position: 0 -64px\n	}\n	.hide-li li {\n		display: none\n	}\n	.hide-li .edit-list li {\n		display: block\n	}\n	.hiding {\n		background-position: 0 -48px\n	}\n	.edit ul {\n 		display: none;\n		position: absolute;\n		left: 16px;\n		background: #FFCC33\n	}\n	#yab-navigation .edit ul li {\n		margin: 0;\n		border-bottom: 1px solid #FFFFFF;\n		padding: 0 5px;\n		cursor: pointer\n	}\n	#yab-navigation .edit ul li:hover {\n		color: #FFFFFF\n	}\n	.show ul {\n		display: block\n	}\n	#yab-nav-wrap1 input {\n		margin-right: 5px;\n		width: 60%;\n		background: #FFFFFF;\n		border: 1px solid #FFCC33\n	}\n	#yab-nav-wrap1 input:hover,\n	#yab-nav-wrap1 input:focus {\n		background: #FFCC33;\n		border: 1px solid #FFCC33\n	}\n	#yab-nav-wrap1 button {\n		background: #FFFFFF;\n		border: 1px solid #FFCC33;\n		font-size: 10px;\n		cursor: pointer\n	}\n	#yab-nav-wrap1 button:hover,\n	#yab-nav-wrap button:focus {\n		background: #FFCC33;\n		border: 1px solid #FFCC33\n	}\n	.hide-info {\n		display: block;\n		margin-bottom: 2em\n	}\n	.article-hidden .article-title {\n		color: #777;\n		font-style: italic\n	}\n	#yab-navigation li.new-article-input {\n		position: absolute;\n		width: 90%;\n		bottom: 3px;\n		left: 20px;\n	}\n	#yab-navigation li.new-article-input input {\n		width: 70%\n	}\nEOF;\n\n	return $css;\n}\n\n/**\n * Returns javascript snippet\n *\n * @return string\n */\nfunction yab_nav_header_inject()\n{\n	global $yab_nav_prefs;\n	$hu_link = $yab_nav_prefs[\'hu_link\'];\n	$jquery_ui = $yab_nav_prefs[\'jquery_ui\'];\n\n	$out = <<<EOF\n/* <![CDATA[ */\n$(document).ready(function() {\n	$(\'head\').append(\'<link href=\"$hu_link?event=yab_navigation_inject&step=css\" rel=\"stylesheet\" type=\"text/css\" />\');\n	$.ajax({\n		url: \'$jquery_ui\',\n		dataType: \'script\',\n		cache: true,\n		success: function() {\n			$.ajax({\n				url:\'$hu_link?event=yab_navigation_inject&step=javascript\',\n				dataType: \'script\',\n				cache: true\n			});\n		}\n	});\n});\n/* ]]> */\nEOF;\n\n	return \'<script type=\"text/javascript\">\'.$out.\'</script>\';\n}', 'be146da5d53107b7df445c4a89651503', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('yab_email', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.7', 'Simple Email Obfuscator/Defuscator (jQuery based)', '<h1>yab_email</h1>\n\n<p>A simple email obfuscator/defuscator (jQuery based)</p>\n\n<p><strong>Version:</strong> 0.6</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section02\">Plugin requirements</a></li>\n	<li><a href=\"#help-section05\">Tags</a></li>\n	<li><a href=\"#help-section09\">Examples</a></li>\n	<li><a href=\"#help-section10\">License</a></li>\n	<li><a href=\"#help-section11\">Author contact</a></li>\n</ol>\n\n<h2 id=\"help-section02\">Plugin requirements</h2>\n\n<p>yab_email’s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.x</li>\n	<li>jQuery 1.3.x</li>\n</ul>\n\n<h2 id=\"help-section05\">Tags</h2>\n\n<h3>yab_email</h3>\n\n<p>Place this in your site to hide your email address from harvesters and bots.</p>\n\n<p><strong>email:</strong> a valid email address<br />\nDefault: name@example.com<br />\nAn email address, you want to be obfuscated.</p>\n\n<p><strong>class:</strong> class name<br />\nDefault: yab-email-link<br />\nA class name for generated <code>&lt;span /&gt;</code> or <code>&lt;a /&gt;</code> element.</p>\n\n<p><strong>at:</strong> word between parenthesis<br />\nDefault: at<br />\nA text string, that will be in parenthesis in obfuscated email.</p>\n\n<p><strong>link:</strong> boolean<br />\nDefault: 1<br />\nGenerate a link or show email as span element.</p>\n\n<p><strong>text:</strong> a link text<br />\nDefault: <i>not set</i><br />\nA text string, that will be the link text. Overwrite a <code>link=&quot;0&quot;</code>.</p>\n\n<p><strong>set_js:</strong> boolean<br />\nDefault: 1<br />\nGenerate Javascript for this very tag.</p>\n\n<h3>yab_email_javascript</h3>\n\n<p>If you have more email addresses you want to be obfuscated or your jQuery inlcude is at the bottom of your site, you can place this tag after the jQuery include.</p>\n\n<p><strong>class:</strong> class name<br />\nDefault: yab-email-link<br />\nA class name for generated <code>&lt;span /&gt;</code> or <code>&lt;a /&gt;</code> element. Has to be the same as in <code>&lt;txp:yab_email /&gt;</code></p>\n\n<p><strong>link:</strong> boolean<br />\nDefault: 1<br />\nGenerate a link or show as span element.</p>\n\n<h2 id=\"help-section09\">Examples</h2>\n\n<h3>Example 1</h3>\n\n<p>Example with a jQuery inlcude in the head and only one address to obfuscate.</p>\n\n<pre><code>&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n	&lt;meta charset=&quot;utf-8&quot; /&gt;\n	&lt;title&gt;Your tilte&lt;/title&gt;\n	&lt;link rel=&quot;stylesheet&quot; href=&quot;/path/to/your/stylesheet.css&quot; /&gt;\n	&lt;script src=&quot;/path/to/your/jQuery.js&quot;&gt;&lt;/script&gt;\n&lt;/head&gt;\n&lt;body&gt;\n\n&lt;p&gt;&lt;txp:yab_email email=&quot;contact@example.com&quot; /&gt;&lt;/p&gt;\n\n&lt;/body&gt;\n&lt;/html&gt;</code></pre>\n\n<h3>Example 2</h3>\n\n<p>Example with a jQuery inlcude at the bottom and probably more address to obfuscate.</p>\n\n<pre><code>&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n	&lt;meta charset=&quot;utf-8&quot; /&gt;\n	&lt;title&gt;Your tilte&lt;/title&gt;\n	&lt;link rel=&quot;stylesheet&quot; href=&quot;/path/to/your/stylesheet.css&quot; /&gt;\n&lt;/head&gt;\n&lt;body&gt;\n\n&lt;p&gt;&lt;txp:yab_email email=&quot;contact@example.com&quot; set_js=&quot;0&quot; /&gt;&lt;/p&gt;\n&lt;p&gt;&lt;txp:yab_email email=&quot;ceo@example.com&quot; set_js=&quot;0&quot;  /&gt;&lt;/p&gt;\n\n&lt;/body&gt;\n&lt;script src=&quot;/path/to/your/jQuery.js&quot;&gt;&lt;/script&gt;\n&lt;txp:yab_email_javascript /&gt;\n&lt;/html&gt;</code></pre>\n\n<h2 id=\"help-section10\">Licence</h2>\n\n<p>This plugin is released under the <span class=\"caps\">GNU</span> General Public License Version 2 and above<br />\n<ul>\n	<li>Version 2: <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">http://www.gnu.org/licenses/gpl-2.0.html</a></li>\n	<li>Version 3: <a href=\"http://www.gnu.org/licenses/gpl-3.0.html\">http://www.gnu.org/licenses/gpl-3.0.html</a></li><br />\n</ul></p>\n\n<h2 id=\"help-section11\">Author contact</h2>\n\n<ul>\n	<li><a href=\"http://www.yablo.de/article/408/yab_email-email-obfuscate-defuscate-as-textpattern-plugin\">Plugin on author&#8217;s site</a></li>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=31846\">Plugin on textpattern forum</a></li>\n	<li><a href=\"http://textpattern.org/plugins/1123/yab_email\">Plugin on textpattern.org</a></li>\n</ul>', '/**\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_email\')\n		->register(\'yab_email_javascript\');\n}\n\n/**\n * Creates an obfuscated email adres\n *\n * @param array $atts\n * @return string\n */\nfunction yab_email($atts)\n{\n	extract(\n		lAtts(\n			array(\n				\'email\'  => \'name@example.com\',\n				\'text\'   => \'\',\n				\'class\'  => \'yab-email-link\',\n				\'at\'     => \'at\',\n				\'link\'   => 1,\n				\'set_js\' => 1\n			),$atts\n		)\n	);\n\n	$tmp   = explode(\'@\', $email);\n	$email = yab_email_encode($tmp[0]).\'(%20\'.$at.\'%20)\'.yab_email_encode($tmp[1]);\n\n	if ($text)\n	{\n		$out = tag($text, \'a\', \' class=\"\'.$class.\'\" href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;\'.$email.\'\"\');\n	}\n	else\n	{\n		$out = tag($email, \'span\', \' class=\"\'.$class.\'\"\');\n	}\n\n	if ($set_js)\n	{\n		return $out.n.yab_email_javascript(array(\'class\' => $class, \'link\' => $link), $set_js);\n	}\n	else\n	{\n		return $out;\n	}\n}\n\n/**\n * Return jQuery Plugin »Email Defuscator«\n * jQuery Plugin »Email Defuscator« 1.0 alpha by Joakim Stai\n * <http://www.ia-stud.hiof.no/~joakims/projects/defuscator/>\n * Dual licensed under the MIT and GPL licenses:\n * <http://www.opensource.org/licenses/mit-license.php>\n * <http://www.gnu.org/licenses/gpl.html>\n * 24-09-12, modified for jQuery >= 1.8.0, Tommy Schmucker\n *\n * @param array class from $atts\n * @param boolean $set_js\n * @return string HTML element <script>\n */\nfunction yab_email_javascript($atts, $set_js)\n{\n	extract(\n		lAtts(\n			array(\n				\'class\' => \'yab-email-link\',\n				\'link\'  => 1\n			),$atts\n		)\n	);\n\n	$js = <<<EOF\n<script>\njQuery.fn.defuscate = function(settings){settings = jQuery.extend({link: true},settings);\nvar regex = /\\b([A-Z0-9._%-]+)\\([^)]+\\)((?:[A-Z0-9-]+\\.)+[A-Z]{2,6})\\b/gi;\nreturn this.each(function(){if($(this).is(\'a[href]\')){ $(this).attr(\'href\', $(this).attr(\'href\').replace(regex, \'$1@$2\'));var is_link = true;}\n$(this).html($(this).html().replace(regex, (settings.link && !is_link ? \'<a href=\"mailto:$1@$2\">$1@$2</a>\' : \'$1@$2\')));});}\n</script>\nEOF;\n\n	if ($set_js)\n	{\n		$js1 = <<<EOF0\n<script>$(\'.$class\').defuscate({link:$link});</script>\nEOF0;\n	}\n	else\n	{\n		$js1 = <<<EOF0\n<script>$(document).ready(function(){ $(\'.$class\').defuscate({link:$link}); });</script>\nEOF0;\n	}\n\n	return $js.$js1;\n}\n\n/**\n * Enocde chars to html entities\n *\n * @param string $string Chars to be encoded\n * @return string HTML encoded chars\n */\nfunction yab_email_encode($string)\n{\n	$encode = \'\';\n	for ($i = 0; $i < strlen($string);)\n	{\n		$encode .= \'&#\'.ord(substr($string,$i)).\';\';\n		$i++;\n	}\n	return $encode;\n}', '/**\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_email\')\n		->register(\'yab_email_javascript\');\n}\n\n/**\n * Creates an obfuscated email adres\n *\n * @param array $atts\n * @return string\n */\nfunction yab_email($atts)\n{\n	extract(\n		lAtts(\n			array(\n				\'email\'  => \'name@example.com\',\n				\'text\'   => \'\',\n				\'class\'  => \'yab-email-link\',\n				\'at\'     => \'at\',\n				\'link\'   => 1,\n				\'set_js\' => 1\n			),$atts\n		)\n	);\n\n	$tmp   = explode(\'@\', $email);\n	$email = yab_email_encode($tmp[0]).\'(%20\'.$at.\'%20)\'.yab_email_encode($tmp[1]);\n\n	if ($text)\n	{\n		$out = tag($text, \'a\', \' class=\"\'.$class.\'\" href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;\'.$email.\'\"\');\n	}\n	else\n	{\n		$out = tag($email, \'span\', \' class=\"\'.$class.\'\"\');\n	}\n\n	if ($set_js)\n	{\n		return $out.n.yab_email_javascript(array(\'class\' => $class, \'link\' => $link), $set_js);\n	}\n	else\n	{\n		return $out;\n	}\n}\n\n/**\n * Return jQuery Plugin »Email Defuscator«\n * jQuery Plugin »Email Defuscator« 1.0 alpha by Joakim Stai\n * <http://www.ia-stud.hiof.no/~joakims/projects/defuscator/>\n * Dual licensed under the MIT and GPL licenses:\n * <http://www.opensource.org/licenses/mit-license.php>\n * <http://www.gnu.org/licenses/gpl.html>\n * 24-09-12, modified for jQuery >= 1.8.0, Tommy Schmucker\n *\n * @param array class from $atts\n * @param boolean $set_js\n * @return string HTML element <script>\n */\nfunction yab_email_javascript($atts, $set_js)\n{\n	extract(\n		lAtts(\n			array(\n				\'class\' => \'yab-email-link\',\n				\'link\'  => 1\n			),$atts\n		)\n	);\n\n	$js = <<<EOF\n<script>\njQuery.fn.defuscate = function(settings){settings = jQuery.extend({link: true},settings);\nvar regex = /\\b([A-Z0-9._%-]+)\\([^)]+\\)((?:[A-Z0-9-]+\\.)+[A-Z]{2,6})\\b/gi;\nreturn this.each(function(){if($(this).is(\'a[href]\')){ $(this).attr(\'href\', $(this).attr(\'href\').replace(regex, \'$1@$2\'));var is_link = true;}\n$(this).html($(this).html().replace(regex, (settings.link && !is_link ? \'<a href=\"mailto:$1@$2\">$1@$2</a>\' : \'$1@$2\')));});}\n</script>\nEOF;\n\n	if ($set_js)\n	{\n		$js1 = <<<EOF0\n<script>$(\'.$class\').defuscate({link:$link});</script>\nEOF0;\n	}\n	else\n	{\n		$js1 = <<<EOF0\n<script>$(document).ready(function(){ $(\'.$class\').defuscate({link:$link}); });</script>\nEOF0;\n	}\n\n	return $js.$js1;\n}\n\n/**\n * Enocde chars to html entities\n *\n * @param string $string Chars to be encoded\n * @return string HTML encoded chars\n */\nfunction yab_email_encode($string)\n{\n	$encode = \'\';\n	for ($i = 0; $i < strlen($string);)\n	{\n		$encode .= \'&#\'.ord(substr($string,$i)).\';\';\n		$i++;\n	}\n	return $encode;\n}', '54b7a31f35656b34dd853fd4319ad765', '', '', 0, 5, 0),
('yab_image', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.5', 'Tiny txp:image replacement that allows you to display an image with an assigned caption', '<h1>yab_image</h1>\n\n<p>This plugin allows you to display an image with an assigned caption as caption :). The width of the caption will always fits the width of the displayed image.</p>\n\n<h2>Usage</h2>\n\n<p>You can use this plugin almost the same as <code>&lt;txp:image /&gt;</code>. The behavior is a little bit different: If a caption for an image is available and if you want to float the image and the caption, use the <code>align</code> attribute. This will float the image <strong>and</strong> the caption.</p>\n\n<p>If no image caption (neither as tag attribute nor in image tab) is given <code>&lt;txp:yab_image /&gt;</code> works similar as <code>&lt;txp:image /&gt;</code>·</p>\n\n<p>F.i.: <code>&lt;txp:yab_image id=&quot;5&quot; align=&quot;right&quot; /&gt;</code></p>\n\n<h2>Attributes</h2>\n\n<p>The following attributes works as expected (as in <code>&lt;txp:image /&gt;</code>·) when no caption is given:</p>\n\n<p><strong>id</strong><br />\nSpecifies the id assigned at upload of the image to display. Can be found on the images tab. If both name and id are specified, name is used while id is ignored.<br />\n(Integer)<br />\n<strong>name</strong><br />\nSpecifies which image to display by its image name as shown on the images tab.<br />\n(String)<br />\n<strong>class</strong><br />\n<span class=\"caps\">CSS</span> class attribute applied to the wraptag, if set, otherwise to the img tag.<br />\n(Default: unset)<br />\n<strong>escape</strong><br />\nEscape <span class=\"caps\">HTML</span> entities such as &lt;, &gt; and &amp; for the image&#8217;s alt and title attributes.<br />\n(Default: unset)<br />\n<strong>html_id</strong><br />\nThe <span class=\"caps\">HTML</span> id attribute applied to the wraptag, if set, otherwise to the img tag.<br />\n(Default: unset)<br />\n<strong>style</strong><br />\nInline <span class=\"caps\">CSS</span> style rule.<br />\n(Default: unset)<br />\n<strong>wraptag</strong><br />\n<span class=\"caps\">HTML</span> tag to be used to wrap the img tag, specified without brackets.<br />\n(Default: unset)</p>\n\n<p>The following attributes are new oder modified:</p>\n\n\n<p><strong>align</strong><br />\nAlignment of the image and the caption.<br />\nleft, right, center (default: center)<br />\n<strong>alt_caption</strong><br />\nAlternate caption, overwrites caption given in image tab.<br />\n(Default: unset)<br />\n<strong>alt_alt</strong><br />\nAlternate img alt, overwrites alternate text given in image tab.<br />\n(Default: unset)<br />\n<strong>alt_title</strong><br />\nAlternate title, overwrites caption text given in image tab, which is regulary used for img title attribute.<br />\n(Default: unset)<br />\n<strong>alt_as_title</strong><br />\nIf set, the alt_alt tag attribute or the alternate text is used for the img title attribute.<br />\n(Default: 0)</p>\n\n<h2>Some weird logic and priorities:</h2>\n\n<p>Tag attributes will always overwrites given text in the image tab!</p>\n\n<p>Intentionally usage of alt_as_title tag attribute:<br />\nMainly it could be used for displaying hyperlinks in the caption (the thingy, which is shown under the image) without breaking the <span class=\"caps\">HTML</span> validity.<br />\nIf you want to display hyperlinks in the caption so you can either write these hyperlinks as <strong>raw <span class=\"caps\">HTML</span> in the caption field in your image tab</strong> or as <strong>textile markup in the tag attribute</strong>:</p>\n\n<p>F.i.: <code>&lt;txp:yab_image id=&quot;5&quot; align=&quot;right&quot; alt_caption=&#39;This is a &quot;Link (Link)&quot;:http://textpattern.com/ in a caption&#39; /&gt;</code></p>\n\n<p><strong>Important:</strong><br />\n<ul>\n	<li>Use single quotes for tags when insert double quotes!</li>\n	<li>Don&#8217;t start with an double quote (<code>alt_caption=&#39;&quot;Link (Link)&quot;:http://textpattern.com/ in a caption&#39;</code> will not work).<br />\nUse a beginning whitespace or text before the double quote (<code>alt_caption=&#39; &quot;Link (Link)&quot;:http://textpattern.com/ in a caption&#39;</code>).</li>\n	<li>Be sure the line will be textiled.</li><br />\n</ul></p>\n\n<p>Problem: The generated img title attribute is broken, because the hyperlink with the double quotes breaks it.<br />\nSolution: Now you can additionally use the <strong>alt_title</strong> tag attribute to set a specific img title or the <strong>alt_as_title</strong> so that the img title will generated from alternate text in the image tab.</p>\n\n<p>F.i.: <code>&lt;txp:yab_image id=&quot;5&quot; align=&quot;right&quot; alt_caption=&#39;This is a &quot;Link (Link)&quot;:http://textpattern.com/ in a caption&#39; alt_as_title=&quot;1&quot; /&gt;</code></p>\n\n<p>or</p>\n\n<p>F.i.: <code>&lt;txp:yab_image id=&quot;5&quot; align=&quot;right&quot; alt_caption=&#39;This is a &quot;Link (Link)&quot;:http://textpattern.com/ in a caption&#39; alt_title=&quot;Nice picture which shows something&quot; /&gt;</code></p>\n\n<h2>Styling</h2>\n\n<p>The plugin will create new <span class=\"caps\">HTML</span> elements (other than <code>&lt;txp:image /&gt;</code>):</p>\n\n<p>If an image caption is given:<br />\n<code>&lt;small class=&quot;caption&quot;&gt;given caption here&lt;/small&gt;</code>: This elements will wrap the caption.</p>\n\n<p>And if additionally the attribute <code>align</code> is used:<br />\n<code>&lt;span class=&quot;img-caption-{align}&quot;&gt;image+caption&lt;/span&gt;</code>, where <code>{align}</code> will be your given align: This span is wrapped around the image and the caption. So you have the ability to set different paddings for different floats.</p>\n\n<p>As of version 0.3 <code>&lt;txp:yab_image /&gt;</code> will produce (X)HTML-strict output. The align attribute in the image element will be completly removed. So the <span class=\"caps\">HTML</span> markup will be different from <code>&lt;txp:image /&gt;</code>.</p>\n\n<p>You can always use the <code>wraptag</code> attribute to wrap all with another html element.<br />\nDepending on your usage and used wraptag you have to clear the floats.</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n	h1, h2, h3\n	h1 code, h2 code, h3 code {\n		margin-bottom: 0.6em;\n		font-weight: bold\n	}\n	h1 {\n		font-size: 1.4em\n	}\n	h2 {\n		font-size: 1.25em\n	}\n	h3 {\n		margin-bottom: 0;\n		font-size: 1.1em\n	}\n	table {\n		margin-bottom: 1em\n	}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_image\');\n}\n\nfunction yab_image($atts)\n{\n	global $img_dir;\n\n	static $cache = array();\n\n	extract(lAtts(array(\n		\'align\'        => \'center\',\n		\'class\'        => \'\',\n		\'escape\'       => \'\',\n		\'html_id\'      => \'\',\n		\'id\'           => \'\',\n		\'name\'         => \'\',\n		\'style\'        => \'\',\n		\'wraptag\'      => \'\',\n		\'alt_caption\'  => \'\',\n		\'alt_alt\'      => \'\',\n		\'alt_title\'    => \'\',\n		\'alt_as_title\' => 0 // 0 or 1\n	), $atts));\n\n	if ($name)\n	{\n		if (isset($cache[\'n\'][$name]))\n		{\n			$rs = $cache[\'n\'][$name];\n		}\n		else\n		{\n			$name = doSlash($name);\n			$rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\n			$cache[\'n\'][$name] = $rs;\n		}\n	}\n	elseif ($id)\n	{\n		if (isset($cache[\'i\'][$id]))\n		{\n			$rs = $cache[\'i\'][$id];\n		}\n		else\n		{\n			$id = (int) $id;\n			$rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\n			$cache[\'i\'][$id] = $rs;\n		}\n	}\n	else\n	{\n		trigger_error(gTxt(\'unknown_image\'));\n		return;\n	}\n\n	if ($rs)\n	{\n		extract($rs);\n\n		if ($escape == \'html\')\n		{\n			$alt = htmlspecialchars($alt);\n			$alt_alt = htmlspecialchars($alt_alt);\n			$caption = htmlspecialchars($caption);\n			$alt_caption = htmlspecialchars($alt_caption);\n			$alt_title = htmlspecialchars($alt_title);\n		}\n\n		$img_title = \'\';\n		if ($alt_title)\n		{\n			$img_title .= \' title = \"\'.$alt_title.\'\"\';\n		}\n		else\n		{\n			if ($alt_as_title)\n			{\n				if ($alt_alt)\n				{\n					$img_alt .= \' title=\"\'.$alt_alt.\'\"\';\n				}\n				else\n				{\n					if ($alt)\n					{\n						$img_alt .= \' title=\"\'.$alt.\'\"\';\n					}\n				}\n			}\n			else\n			{\n				if ($alt_caption)\n				{\n					$img_title .= \' title = \"\'.$alt_caption.\'\"\';\n				}\n				else\n				{\n					if ($caption)\n					{\n						$img_title .= \' title = \"\'.$caption.\'\"\';\n					}\n				}\n			}\n		}\n\n	$img_alt = \'\';\n	if ($alt_alt)\n	{\n		$img_alt .= \' alt=\"\'.$alt_alt.\'\"\';\n	}\n	else\n	{\n		if ($alt)\n		{\n			$img_alt .= \' alt=\"\'.$alt.\'\"\';\n		}\n	}\n\n		$out =\n			\'<img src=\"\'.hu.$img_dir.\'/\'.$id.$ext.\'\" width=\"\'.$w.\'\" height=\"\'.$h.\'\"\'.\n				$img_alt.\n				$img_title.\n				(($html_id and !$wraptag) ? \' id=\"\'.$html_id.\'\"\' : \'\').\n				(($class and !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\n				($style ? \' style=\"\'.$style.\'\"\' : \'\').\n		\' />\';\n\n		if ($caption or $alt_caption)\n		{\n			if ($alt_caption)\n			{\n				$out .= doTag(\n					$alt_caption,\n					\'small\',\n					\'caption\',\n					\' style=\"display:block;width:\'.$w.\'px;\"\',\n					\'\'\n				);\n			}\n			else\n			{\n				$out .= doTag(\n					$caption,\n					\'small\',\n					\'caption\',\n					\' style=\"display:block;width:\'.$w.\'px;\"\',\n					\'\'\n				);\n			}\n\n			if ($align == \'left\' or $align == \'right\')\n			{\n				$out = doTag(\n					$out,\n					\'span\',\n					\'img-caption-\'.$align,\n					\' style=\"float:\'.$align.\';\"\',\n					\'\'\n				);\n			}\n			if ($align == \'center\')\n			{\n				$out = doTag(\n					$out,\n					\'span\',\n					\'img-caption-\'.$align,\n					\' style=\"display:block;text-align:\'.$align.\';\"\',\n				\'\'\n				);\n			}\n		}\n\n		return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\n	}\n\n	trigger_error(gTxt(\'unknown_image\'));\n}', '/**\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_image\');\n}\n\nfunction yab_image($atts)\n{\n	global $img_dir;\n\n	static $cache = array();\n\n	extract(lAtts(array(\n		\'align\'        => \'center\',\n		\'class\'        => \'\',\n		\'escape\'       => \'\',\n		\'html_id\'      => \'\',\n		\'id\'           => \'\',\n		\'name\'         => \'\',\n		\'style\'        => \'\',\n		\'wraptag\'      => \'\',\n		\'alt_caption\'  => \'\',\n		\'alt_alt\'      => \'\',\n		\'alt_title\'    => \'\',\n		\'alt_as_title\' => 0 // 0 or 1\n	), $atts));\n\n	if ($name)\n	{\n		if (isset($cache[\'n\'][$name]))\n		{\n			$rs = $cache[\'n\'][$name];\n		}\n		else\n		{\n			$name = doSlash($name);\n			$rs = safe_row(\'*\', \'txp_image\', \"name = \'$name\' limit 1\");\n			$cache[\'n\'][$name] = $rs;\n		}\n	}\n	elseif ($id)\n	{\n		if (isset($cache[\'i\'][$id]))\n		{\n			$rs = $cache[\'i\'][$id];\n		}\n		else\n		{\n			$id = (int) $id;\n			$rs = safe_row(\'*\', \'txp_image\', \"id = $id limit 1\");\n			$cache[\'i\'][$id] = $rs;\n		}\n	}\n	else\n	{\n		trigger_error(gTxt(\'unknown_image\'));\n		return;\n	}\n\n	if ($rs)\n	{\n		extract($rs);\n\n		if ($escape == \'html\')\n		{\n			$alt = htmlspecialchars($alt);\n			$alt_alt = htmlspecialchars($alt_alt);\n			$caption = htmlspecialchars($caption);\n			$alt_caption = htmlspecialchars($alt_caption);\n			$alt_title = htmlspecialchars($alt_title);\n		}\n\n		$img_title = \'\';\n		if ($alt_title)\n		{\n			$img_title .= \' title = \"\'.$alt_title.\'\"\';\n		}\n		else\n		{\n			if ($alt_as_title)\n			{\n				if ($alt_alt)\n				{\n					$img_alt .= \' title=\"\'.$alt_alt.\'\"\';\n				}\n				else\n				{\n					if ($alt)\n					{\n						$img_alt .= \' title=\"\'.$alt.\'\"\';\n					}\n				}\n			}\n			else\n			{\n				if ($alt_caption)\n				{\n					$img_title .= \' title = \"\'.$alt_caption.\'\"\';\n				}\n				else\n				{\n					if ($caption)\n					{\n						$img_title .= \' title = \"\'.$caption.\'\"\';\n					}\n				}\n			}\n		}\n\n	$img_alt = \'\';\n	if ($alt_alt)\n	{\n		$img_alt .= \' alt=\"\'.$alt_alt.\'\"\';\n	}\n	else\n	{\n		if ($alt)\n		{\n			$img_alt .= \' alt=\"\'.$alt.\'\"\';\n		}\n	}\n\n		$out =\n			\'<img src=\"\'.hu.$img_dir.\'/\'.$id.$ext.\'\" width=\"\'.$w.\'\" height=\"\'.$h.\'\"\'.\n				$img_alt.\n				$img_title.\n				(($html_id and !$wraptag) ? \' id=\"\'.$html_id.\'\"\' : \'\').\n				(($class and !$wraptag) ? \' class=\"\'.$class.\'\"\' : \'\').\n				($style ? \' style=\"\'.$style.\'\"\' : \'\').\n		\' />\';\n\n		if ($caption or $alt_caption)\n		{\n			if ($alt_caption)\n			{\n				$out .= doTag(\n					$alt_caption,\n					\'small\',\n					\'caption\',\n					\' style=\"display:block;width:\'.$w.\'px;\"\',\n					\'\'\n				);\n			}\n			else\n			{\n				$out .= doTag(\n					$caption,\n					\'small\',\n					\'caption\',\n					\' style=\"display:block;width:\'.$w.\'px;\"\',\n					\'\'\n				);\n			}\n\n			if ($align == \'left\' or $align == \'right\')\n			{\n				$out = doTag(\n					$out,\n					\'span\',\n					\'img-caption-\'.$align,\n					\' style=\"float:\'.$align.\';\"\',\n					\'\'\n				);\n			}\n			if ($align == \'center\')\n			{\n				$out = doTag(\n					$out,\n					\'span\',\n					\'img-caption-\'.$align,\n					\' style=\"display:block;text-align:\'.$align.\';\"\',\n				\'\'\n				);\n			}\n		}\n\n		return ($wraptag) ? doTag($out, $wraptag, $class, \'\', $html_id) : $out;\n	}\n\n	trigger_error(gTxt(\'unknown_image\'));\n}', '1cbc0a36ff7176ed833d87dd9fcf5cb0', '', '', 0, 5, 0),
('pax_grep', 0, 'John Stephens', 'https://cyclexo.com/', '0.2.3', 'Replace all occurrences of a regular expression with replacements.', '<h1>pax_grep</h1>\n\n	<dl>\n		<dt>Summary</dt>\n		<dd>Replace all occurrences of a regular expression with replacements.</dd>\n		<dt>Version</dt>\n		<dd>0.2.3 (updated 14 Jun 2018)</dd>\n	</dl>\n\n	<h2>Table of contents</h2>\n\n	<ol>\n		<li><a href=\"#help-section01\" rel=\"nofollow\">Overview</a></li>\n		<li><a href=\"#help-section02\" rel=\"nofollow\">Installing and uninstalling</a></li>\n		<li><a href=\"#help-section03\" rel=\"nofollow\">Usage and syntax</a></li>\n		<li><a href=\"#help-section04\" rel=\"nofollow\">Attributes</a>\n	<ol>\n		<li><a href=\"#help-section05\" rel=\"nofollow\">from</a></li>\n		<li><a href=\"#help-section06\" rel=\"nofollow\">to</a></li>\n		<li><a href=\"#help-section07\" rel=\"nofollow\">delimiter</a></li>\n	</ol></li>\n		<li><a href=\"#help-section08\" rel=\"nofollow\">Examples</a></li>\n		<li><a href=\"#help-section09\" rel=\"nofollow\">License</a></li>\n		<li><a href=\"#help-section10\" rel=\"nofollow\">Author contact</a></li>\n		<li><a href=\"#help-section11\" rel=\"nofollow\">Changelog</a></li>\n	</ol>\n\n	<h2 id=\"help-section01\">Overview</h2>\n\n	<p>This plugin allows you to <em>find occurrences of a <a href=\"http://en.wikipedia.org/wiki/Regular_expression\" rel=\"nofollow\">regular expression</a> pattern</em> and <em>replace them with something else</em>. It&#8217;s almost identical to it&#8217;s awesome parent plugin <a href=\"http://rahforum.biz/plugins/rah_replace\" rel=\"nofollow\">rah_replace by Jukka Svahn</a>, but <strong>pax_grep</strong> uses <span class=\"caps\">PHP</span>&#8217;s <a href=\"http://fi.php.net/preg_replace\" rel=\"nofollow\">preg_replace()</a> function, packaged in a compact and easy-to-use Textpattern tag.</p>\n\n	<h2 id=\"help-section02\">Installing and uninstalling</h2>\n\n	<p>In Textpattern, navigate to the &#8220;Plugins&#8221; tab under &#8220;Admin&#8221;, and paste the code into the &#8220;Install plugin&#8221; pane. Install and enable the plugin.</p>\n\n	<p>To uninstall, simply delete the plugin from the &#8220;Plugins&#8221; tab.</p>\n\n	<h2 id=\"help-section03\">Usage and syntax</h2>\n\n	<p><strong>pax_grep</strong> takes a regular expression and searches content for matching patterns. Then it replaces matches with the string(s) you supply. This works just like <a href=\"http://rahforum.biz/plugins/rah_replace\" rel=\"nofollow\">rah_replace</a>, except it supports regular expression searches. If you don&#8217;t need to search and replace based on a regular expression, <strong>you should use rah_replace instead</strong>.</p>\n\n	<p><strong>pax_grep</strong> s a container tag with three attributes.</p>\n\n<pre><code>&lt;txp:pax_grep ↩\n	from=&quot;/search pattern/&quot; ↩\n	to=&quot;replacement text&quot; ↩\n	delimiter=&quot;|&quot;&gt;\n	Content\n&lt;/txp:pax_grep&gt;\n</code></pre>\n\n	<h3 id=\"help-section04\">Attributes</h3>\n\n	<h4 id=\"help-section05\"><code>from</code> &#8212; <em>Required</em></h4>\n\n	<p>Give this attribute the value of a pattern or patterns you wish to search for. A pattern should be <strong>delimited with single quotes</strong> or some other character not used in the pattern. Separate multiple patterns by commas (use the <code>delimiter</code> attribute to specify and alternate separator).</p>\n\n	<dl>\n		<dt>Default</dt>\n		<dd><code>from=&quot;&quot;</code></dd>\n		<dt>Example</dt>\n		<dd><code>from=&quot;\'^foo\',\'bar$\'&quot;</code></dd>\n	</dl>\n\n	<h4 id=\"help-section06\"><code>to</code> &#8212; <em>Required</em></h4>\n\n	<p>This attribute holds the replacement value(s) for each pattern in the <code>from</code> attribute. No delimiters are needed, but <strong>multiple values must be separated by commas (use the <code>delimiter</code> attribute to specify and alternate separator)</strong>. Each <code>from</code> value will be replaced with the corresponding <code>to</code> value.</p>\n\n	<dl>\n		<dt>Default</dt>\n		<dd><code>to=&quot;&quot;</code></dd>\n		<dt>Example</dt>\n		<dd><code>to=&quot;fox,bat&quot;</code></dd>\n	</dl>\n\n	<h4 id=\"help-section07\"><code>delimiter</code> &#8212; <em>Optional</em></h4>\n\n	<p>So you don&#8217;t like using commas to separate your search patterns or replacement values? Sometimes, you need to use a comma in the search pattern, and you need a different separator to break search patterns and replacement values. Use this attribute to specify an alternate separator.</p>\n\n	<dl>\n		<dt>Default</dt>\n		<dd><code>delimiter=&quot;,&quot;</code></dd>\n		<dt>Example</dt>\n		<dd><code>delimiter=&quot; | &quot;</code></dd>\n	</dl>\n\n	<h2 id=\"help-section08\">Examples</h2>\n\n	<h3>Example 1: Get words out of Textpattern&#8217;s <code>request_uri</code>.</h3>\n\n	<p>This example outputs the current request <acronym title=\"Uniform Resource Identifier\"><span class=\"caps\">URI</span></acronym>, and uses a regular expression to drop the leading slash and transform many delimiter characters into spaces. You might use code like this on a 404 error page to populate a search field with information from a mistyped <span class=\"caps\">URL</span>.</p>\n\n<pre><code>&lt;txp:pax_grep\n	from=&quot;\'^\\/\',\'\\/\',\'%20\',\'\\-\',\'\\+\',\'\\?=\',\'\\_\'&quot; ↩\n	to=&quot;, , , , , , &quot;&gt;\n	&lt;txp:page_url type=&quot;request_uri&quot;/&gt;\n&lt;/txp:pax_grep&gt;\n</code></pre>\n\n	<h3>Example 2: Strip Textile-generated <code>p</code> elements from Textpattern&#8217;s excerpt output</h3>\n\n	<p>Sometimes you might want to show the excerpt of a Textpattern article without mucking around with a bunch of paragraph tags. Here&#8217;s how.</p>\n\n<pre><code>&lt;txp:pax_grep from=&quot;/&lt;\\/?p&gt;/,/\\t/&quot; to=&quot;&quot;&gt;\n	&lt;txp:excerpt/&gt;\n&lt;/txp:pax_grep&gt;\n</code></pre>\n\n	<h2 id=\"help-section09\">Licence</h2>\n\n	<p>This plugin is licenced under <a href=\"https://textpattern.com/license\" rel=\"nofollow\"><span class=\"caps\">GPL</span>, Version 2</a>.</p>\n\n	<h2 id=\"help-section10\">Author contact</h2>\n\n	<p>John Stephens is known as &#8220;johnstephens&#8221; on the Textpattern support forum and on Twitter. You can reach me at <a href=\"https://cyclexo.com/\" rel=\"nofollow\">Cycle Design Co.</a> or find <a href=\"https://twitter.com/johnstephens\" rel=\"nofollow\">@johnstephens</a> on Twitter <a href=\"https://twitter.com/johnstephens\" rel=\"nofollow\">here</a>.</p>\n\n\n\n	<h2 id=\"#help-section11\">Changelog</h2>\n\n	<dl>\n	<dt>Version 0.2.3</dt>\n		<dd>2018-06-14: Clean up code style and fix plugin help navigation.</dd>\n		<dt>Version 0.2.2</dt>\n		<dd>2016-12-05: Support the tag registry in Textpattern 4.6.0+.</dd>\n		<dt>Version 0.2.1</dt>\n		<dd>2012-04-26: Expand and revise plugin help.</dd>\n		<dt>Version 0.2</dt>\n		<dd>2010-10-01: Add an optional <code>delimiter</code> attribute, so you can use commas in your search pattern. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=235323#p235323\" rel=\"nofollow\">Thanks, Jan</a>.</dd>\n		<dt>Version 0.1.1</dt>\n		<dd>2009-01-25: Change plugin name from <strong>opus_grep</strong> to <strong>pax_grep</strong> in compliance with de facto standard of three-letter prefix for plugins.</dd>\n		<dt>Version 0.1</dt>\n		<dd>2009-01-23: Branch from <a href=\"http://rahforum.biz/plugins/rah_replace\" rel=\"nofollow\">rah_replace</a> by Jukka Svahn&#8212; use <code>preg_replace()</code> instead of <code>str_replace()</code> to allow regex search patterns.</dd>\n	</dl>', '/**\r\n * This is pax_grep: A plugin for Textpattern\r\n * version 0.2.3\r\n * by John Stephens, adapted from rah_replace by Jukka Svahn\r\n * https://cyclexo.com/\r\n */\r\n\r\n// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n        ->register(\'pax_grep\');\r\n}\r\n\r\n// pax_grep :: String -> String\r\nfunction pax_grep ($atts, $thing = null) {\r\n\r\n    global $pretext;\r\n\r\n    extract (lAtts (array (\r\n        \'from\' => \'\',\r\n        \'to\' => \'\',\r\n        \'delimiter\' => \',\'\r\n    ), $atts));\r\n\r\n    $from = explode ($delimiter, $from);\r\n\r\n    $to = explode ($delimiter, $to);\r\n\r\n    $count = count ($to);\r\n\r\n    if ($count == 1) $to = implode (\'\', $to);\r\n\r\n    if ($count == 0) $to = \'\';\r\n\r\n    return preg_replace ($from, $to, parse($thing));\r\n\r\n}', '/**\r\n * This is pax_grep: A plugin for Textpattern\r\n * version 0.2.3\r\n * by John Stephens, adapted from rah_replace by Jukka Svahn\r\n * https://cyclexo.com/\r\n */\r\n\r\n// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n        ->register(\'pax_grep\');\r\n}\r\n\r\n// pax_grep :: String -> String\r\nfunction pax_grep ($atts, $thing = null) {\r\n\r\n    global $pretext;\r\n\r\n    extract (lAtts (array (\r\n        \'from\' => \'\',\r\n        \'to\' => \'\',\r\n        \'delimiter\' => \',\'\r\n    ), $atts));\r\n\r\n    $from = explode ($delimiter, $from);\r\n\r\n    $to = explode ($delimiter, $to);\r\n\r\n    $count = count ($to);\r\n\r\n    if ($count == 1) $to = implode (\'\', $to);\r\n\r\n    if ($count == 0) $to = \'\';\r\n\r\n    return preg_replace ($from, $to, parse($thing));\r\n\r\n}', '94a5ba9bb8f7aa4798d5279b5102f609', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('own_upm_img_popper', 0, 'Dmitry Shovchko', 'http://github.com/dshovchko', '4.6.2.20161226', 'More powerful image display', '', '\nif (txpinterface == \'admin\')\n	{\n		upm_img_popper_install();\n\n		add_privs(\'upm_img_popper\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper\', \'upm_img_popper\');\n\n		add_privs(\'upm_img_popper_link\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper_link\', \'article\');\n\n		add_privs(\'upm_img_popper_js\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper_js\', \'upm_img_popper_js\', \'\', 1);\n\n		add_privs(\'upm_img_popper_prefs\', \'1,2\');\n		register_tab(\'extensions\', \'upm_img_popper_prefs\', \'upm_img_popper\');\n		register_callback(\'upm_img_popper_prefs\', \'upm_img_popper_prefs\');\n	}\n\n// ---\n\n	function upm_img_popper()\n	{\n		global $step;\n\n		if ($step == \'upm_img_popper_change_pageby\')\n		{\n			event_change_pageby(\'image\');\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n\n		upm_img_popper_list();\n	}\n\n// ---\n\n	function upm_img_popper_list($message = \'\')\n	{\n		global $prefs;\n\n		pagetop(upm_img_popper_gTxt(\'image_selector\'), $message);\n\n		$path_to_site = $prefs[\'path_to_site\'];\n		$img_dir = $prefs[\'img_dir\'];\n\n		if (!defined(\'IMPATH\'))\n		{\n			define(\'IMPATH\', $path_to_site.\'/\'.$img_dir.\'/\');\n		}\n\n		echo <<<css\n<style type=\"text/css\">\n<!--\nlabel{color:#000;cursor:pointer;}\nul{margin:0;padding:0;list-style:none;}\nli{margin:0 0 2px 0;padding:0;}\np{margin:5px 0;padding:0;}\n.right{text-align:right;}\n.center{margin:0 auto;text-align:center;}\n#no{margin-left:2em;}\n#return,#success,#failed{padding:1em;text-align:center;background-color:#fff;border:5px solid #fc3;}\n-->\n</style>\n<script type=\"text/javascript\" src=\"index.php?event=upm_img_popper_js&#38;name=pop\"></script>\ncss;\n\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		switch ($sort)\n		{\n			case \'id\':\n				$sort_sql = \"id $dir\";\n			break;\n\n			case \'name\':\n				$sort_sql = \"name $dir\";\n			break;\n\n			case \'thumbnail\':\n				$sort_sql = \"thumbnail $dir, id asc\";\n			break;\n\n			case \'category\':\n				$sort_sql = \"category $dir, id asc\";\n			break;\n\n			case \'date\':\n				$sort_sql = \"date $dir, id asc\";\n			break;\n\n			case \'author\':\n				$sort_sql = \"author $dir, id asc\";\n			break;\n\n			default:\n				$dir = \'desc\';\n				$sort_sql = \"id $dir\";\n			break;\n		}\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$criteria = 1;\n\n		if ($search_method and $crit)\n		{\n			$crit_escaped = doSlash($crit);\n\n			$critsql = array(\n				\'id\'		=> \"id = \'$crit_escaped\'\",\n				\'name\'		=> \"name like \'%$crit_escaped%\'\",\n				\'category\'	=> \"category like \'%$crit_escaped%\'\",\n				\'author\'	=> \"author like \'%$crit_escaped%\'\",\n				\'alt\'		=> \"alt like \'%$crit_escaped%\'\",\n				\'caption\'	=> \"caption like \'%$crit_escaped%\'\"\n			);\n\n			if (array_key_exists($search_method, $critsql))\n			{\n				$criteria = $critsql[$search_method];\n				$limit = 500;\n			}\n\n			else\n			{\n				$search_method = \'\';\n				$crit = \'\';\n			}\n		}\n\n		else\n		{\n			$search_method = \'\';\n			$crit = \'\';\n		}\n\n		$total = safe_count(\'txp_image\', \"$criteria\");\n\n		if ($total < 1)\n		{\n			if ($criteria != 1)\n			{\n				echo n.upm_img_popper_search_form($crit, $search_method, $sort, $dir).\n					n.graf(gTxt(\'no_results_found\'), \' style=\"text-align: center;\"\');\n			}\n\n			else\n			{\n				echo n.graf(gTxt(\'no_images_recorded\'), \' style=\"text-align: center;\"\');\n			}\n\n			return;\n		}\n\n		echo upm_img_popper_options_form();\n\n		$limit = max($prefs[\'image_list_pageby\'], 15);\n\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n		echo upm_img_popper_search_form($crit, $search_method, $sort, $dir);\n\n		$rs = safe_rows_start(\'*, unix_timestamp(date) as uDate\', \'txp_image\',\n			\"$criteria order by $sort_sql limit $offset, $limit\n		\");\n\n		if ($rs)\n		{\n			$sort_link = \'upm_img_popper\'.a.\'bm=true\';\n\n			echo n.n.startTable(\'list\').\n				n.tr(\n					column_head(\'ID\', \'id\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'name\', \'name\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'thumbnail\', \'thumbnail\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell(gTxt(\'preview\')).\n					column_head(\'author\', \'author\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell(upm_img_popper_gTxt(\'insert\')).\n					( function_exists(\'upm_image\') ? hCell(upm_img_popper_gTxt(\'insert_plugin\')) : \'\' ).\n					hCell()\n				);\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				$edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$id.a.\'sort=\'.$sort.\n					a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n\n				$name = empty($name) ? gTxt(\'unnamed\') : $name;\n\n				$thumbnail = ($thumbnail) ?\n					\'<img src=\"\'.hu.$img_dir.\'/\'.$id.\'t\'.$ext.\'\" />\' :\n					gTxt(\'no\');\n\n				// god I hate javascript in php\n\n				$ids = n.\'<ul>\'.n.\n					n.t.\'<li>\'.$id.\'</li>\'.\n					n.t.\'<li><a href=\"\'.$edit_url.\'\" onclick=\"warnEditImage(\'.$id.\'); return false;\">\'.gTxt(\'edit\').\'</a></li>\'.\n					n.t.\'<li>\'.safe_strftime(\'%d %b %Y %I:%M %p\', $uDate).\'</li>\'.\n					n.\'</ul>\';\n\n				$insert = n.\'<ul>\'.n.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'append-article-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'article_image_append\').\'</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'replace-article-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'article_image_replace\').\'</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				$plugin = n.\'<ul>\'.n.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				if ($thumbnail)\n				{\n					$insert .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'thumb\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.gTxt(\'thumbnail\').\'</a></li>\'.\n						n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'popup\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'popup\').\'</a></li>\';\n\n					$plugin .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-thumb\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.gTxt(\'thumbnail\').\'</a></li>\'.\n						n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-popup\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'popup\').\'</a></li>\';\n				}\n\n				$insert .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'textile\\\', \'.chr(39).$id.chr(39).\', \'.chr(39).$ext.chr(39).\', \\\'\\\', \\\'\\\', \'.chr(39).htmlspecialchars($alt).chr(39).\'); return false;\">Textile</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'xhtml\\\', \'.chr(39).$id.chr(39).\', \'.chr(39).$ext.chr(39).\', \'.chr(39).$w.chr(39).\', \'.chr(39).$h.chr(39).\', \'.chr(39).htmlspecialchars($alt).chr(39).\', \'.chr(39).htmlspecialchars($caption).chr(39).\'); return false;\">XHTML</a></li>\'.\n					n.\'</ul>\';\n\n				$plugin .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-custom\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'custom_form\').\'</a></li>\'.\n					n.\'</ul>\';\n\n				$preview = n.\'<ul>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).\'&#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).\'&#60;img src=&#34;\'.hu.$img_dir.\'/\'.$id.$ext.\'&#34; /&#62;\'.chr(39).\', \'.$w.\', \'.$h.\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				if (!empty($alt))\n				{\n					$preview .= n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).upm_img_popper_gTxt(\'alt_text_for\').\' &#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).htmlspecialchars($alt).chr(39).\'); return false;\">\'.gTxt(\'alt_text\').\'</a></li>\';\n				}\n\n				if (!empty($caption))\n				{\n					$preview .= n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).upm_img_popper_gTxt(\'caption_for\').\' &#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).htmlspecialchars($caption).chr(39).\'); return false;\">\'.gTxt(\'caption\').\'</a></li>\';\n				}\n\n				$preview .= n.\'</ul>\';\n\n				$category = ($category) ? \'<span title=\"\'.fetch_category_title($category, \'image\').\'\">\'.$category.\'</span>\' : \'\';\n\n				echo n.n.tr(\n					n.td($ids, 125).\n\n					td(\'<a href=\"\'.$edit_url.\'\" onclick=\"warnEditImage(\'.$id.\'); return false;\">\'.$name.\'</a>\'\n					, 75).\n\n					td($thumbnail, 195).\n\n					td($preview).\n\n					td(\n						\'<span title=\"\'.get_author_name($author).\'\">\'.$author.\'</span>\'\n					, 75).\n\n					td($insert, 150).\n					( function_exists(\'upm_image\') ? n.td($plugin) : \'\' ).\n\n					td(\n						dLink(\'image\', \'image_delete\', \'id\', $id)\n					, 10)\n				);\n			}\n\n			echo endTable().\n\n				nav_form_upm(\'upm_img_popper\',\'1\', $page, $numPages, $sort, $dir, $crit, $search_method).\n\n				upm_img_popper_pageby_form().\n\n				n.tag(\n					n.graf(upm_img_popper_gTxt(\'return\')).\n					n.graf(\'<a id=\"yes\" href=\"#\" onclick=\"goBack(this.href); return false;\">\'.gTxt(\'yes\').\'</a>.\'.\n						\' <a id=\"no\" href=\"#\" onclick=\"done(\\\'return\\\'); return false;\">\'.gTxt(\'no\').\'</a>.\')\n				, \'div\',	\' id=\"return\" style=\"display: none; width: 200px; height: 75px;\"\').\n\n				n.graf(upm_img_popper_gTxt(\'success\'), \' id=\"success\" style=\"display: none; width: 150px; height: 35px;\"\').\n				n.graf(upm_img_popper_gTxt(\'failed\'), \' id=\"failed\" style=\"display: none; width: 150px; height: 35px;\"\');\n		}\n	}\n\n// ---\n\n	function upm_img_popper_options_form()\n	{\n		global $prefs;\n\n		echo n.\'<form action=\"\" style=\"margin-bottom: 15px;\'.( !$prefs[\'upm_img_popper_show_form\'] ? \' display: none;\' : \'\').\'\">\'.\n			graf(\n				n.\'<input type=\"checkbox\" id=\"inc_width\"\'.(($prefs[\'upm_img_popper_show_width\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_width\">\'.upm_img_popper_gTxt(\'include_width\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_height\"\'.(($prefs[\'upm_img_popper_show_height\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_height\">\'.upm_img_popper_gTxt(\'include_height\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_alt\"\'.(($prefs[\'upm_img_popper_show_alt\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_alt\">\'.upm_img_popper_gTxt(\'include_alt\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_title\"\'.(($prefs[\'upm_img_popper_show_title\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_title\">\'.upm_img_popper_gTxt(\'include_caption\').\'</label>\'.br.\n				n.\'<input type=\"checkbox\" id=\"inc_class\"\'.(($prefs[\'upm_img_popper_show_class\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_class\">\'.upm_img_popper_gTxt(\'assign_class\').\'</label> \'.\n				n.\'<input type=\"text\" id=\"the_class\" value=\"\'.$prefs[\'upm_img_popper_the_class\'].\'\" class=\"edit\" />\'\n			, \' style=\"text-align: center;\"\').\n\n			n.graf(\n				\'<label for=\"custom_form\">\'.upm_img_popper_gTxt(\'custom_form\').\'</label> <input type=\"text\" id=\"custom_form\" value=\"\'.$prefs[\'upm_img_popper_custom_form\'].\'\" class=\"edit\" /> (\'.upm_img_popper_gTxt(\'override\').\')\'\n			, \' style=\"text-align: center;\"\').\n\n		n.\'</form>\';\n	}\n\n// ---\n\n	function upm_img_popper_search_form($crit, $method, $sort, $dir)\n	{\n		$default_method = \'name\';\n\n		$method = ($method) ? $method : $default_method;\n\n		$methods =	array(\n			\'id\'			 => gTxt(\'ID\'),\n			\'name\'		 => gTxt(\'name\'),\n			\'category\' => gTxt(\'image_category\'),\n			\'author\'	 => gTxt(\'author\'),\n			\'alt\'			 => gTxt(\'alt_text\'),\n			\'caption\'	 => gTxt(\'caption\')\n		);\n\n		return n.n.\'<form method=\"get\" action=\"index.php\" style=\"margin: auto; text-align: center;\">\'.\n			n.graf(\n				\'<label for=\"upm-img-popper-search\">\'.gTxt(\'search\').\'</label>\'.sp.\n				selectInput(\'search_method\', $methods, $method, \'\', \'\', \'upm-img-popper-search\').sp.\n				fInput(\'text\', \'crit\', $crit, \'edit\', \'\', \'\', \'15\').\n				eInput(\'upm_img_popper\').\n				hInput(\'bm\',\'1\').\n				fInput(\'submit\', \'search\', gTxt(\'go\'), \'smallerbox\')\n			).\n\n			($crit ? n.graf(\'<a href=\"?event=upm_img_popper\'.a.\'bm=true\">\'.upm_img_popper_gTxt(\'view_all\').\'</a>\') : \'\').\n\n		n.\'</form>\';\n	}\n\n// ---\n\n	function upm_img_popper_link()\n	{\n		$app_mode = gps(\'app_mode\');\n		if ($app_mode===\'async\') return;\n\n		$view = gps(\'view\');\n\n		if ($view == \'text\' or empty($view))\n		{\n			echo <<<form\n<form action=\"\">\n<input type=\"hidden\" id=\"upm_img_popper_start\" value=\"\">\n</form>\n<script type=\"text/javascript\" src=\"index.php?event=upm_img_popper_js&#38;name=link\"></script>\nform;\n\n		}\n	}\n\n// ---\n\n	function upm_img_popper_pageby_form()\n	{\n		global $prefs;\n\n		$vals = array(\n			15	=> 15,\n			25	=> 25,\n			50	=> 50,\n			100 => 100\n		);\n\n		$select_page = selectInput(\'qty\', $vals, $prefs[\'image_list_pageby\'], \'\', 1);\n\n		// proper localisation\n		$page = str_replace(\'{page}\', $select_page, gTxt(\'view_per_page\'));\n\n		return form(\n			\'<div style=\"margin: auto; text-align: center;\">\'.\n				$page.\n				eInput(\'upm_img_popper\').\n				sInput(\'upm_img_popper_change_pageby\').\n				hInput(\'bm\', \'1\').\n				\'<noscript> <input type=\"submit\" value=\"\'.gTxt(\'go\').\'\" class=\"smallerbox\" /></noscript>\'.\n			\'</div>\'\n		);\n	}\n\n// ---\n\n	function upm_img_popper_js()\n	{\n		while (@ob_end_clean());\n\n		if (gps(\'name\') == \'link\')\n		{\n			global $prefs;\n\n			$txt = gTxt(\'advanced_options\');\n			$lang_insert_image = upm_img_popper_gTxt(\'insert_image\');\n\n			header(\"Content-type: text/javascript\");\n\n			echo <<<js\n\ndocument.getElementById(\'upm_img_popper_start\').value = document.getElementById(\'body\').value;\n\nvar current = \'body\';\n\nif (document.getElementById(\'excerpt\'))\n{\n	// IE is retarded\n	document.getElementById(\'body\').onclick = function () {\n		current = \'body\';\n	};\n\n	document.getElementById(\'excerpt\').onclick = function () {\n		current = \'excerpt\';\n	};\n}\n\n// ---\n\nvar txt = \'$txt\';\n\nvar a = (document.createElementNS) ?\n	document.createElementNS(\'http://www.w3.org/1999/xhtml\', \'a\') :\n	document.createElement(\'a\');\n\na.appendChild(document.createTextNode(\'$lang_insert_image\'));\n\nvar w = {$prefs[\'upm_img_popper_admin_popup_width\']};\nvar h = {$prefs[\'upm_img_popper_admin_popup_height\']};\n\nvar t = (screen.height) ? (screen.height - h) / 2 : 0;\nvar l = (screen.width) ? (screen.width - w) / 2 : 0;\n\na.onclick = function () {\n	var upm_img_popper = window.open(this.href, \'upm_img_popper\', \'top = \'+t+\', left = \'+l+\', width = \'+w+\', height = \'+h+\', toolbar = no, location = no, directories = no, status = yes, menubar = no, scrollbars = yes, copyhistory = no, resizable = yes\');\n		upm_img_popper.focus();\n		return false;\n};\n\na.setAttribute(\'target\', \'blank\');\na.setAttribute(\'href\', \'?event=upm_img_popper\\u0026bm=1\');\n\nvar link = (document.createElementNS) ?\n	document.createElementNS(\'http://www.w3.org/1999/xhtml\', \'h3\') :\n	document.createElement(\'h3\');\n\nlink.setAttribute(\'className\', \'plain\'); // stupid IE\nlink.setAttribute(\'class\', \'plain\');\nlink.appendChild(a);\n\n// ---\n\nvar h3s = document.getElementsByTagName(\'h3\');\n\nfor (var i = 0; i < h3s.length; i++)\n{\n	var h3 = document.getElementsByTagName(\'h3\')[i];\n\n	if (h3.firstChild.firstChild)\n	{\n		if (h3.firstChild.firstChild.nodeValue == txt)\n		{\n			h3.parentNode.insertBefore(link, h3);\n		}\n	}\n}\n\n// ---\n// thanks Alex! <http://www.alexking.org/>\n\nfunction upm_img_popper_insert(myField, myValue)\n{\n	// IE support\n	if (document.selection)\n	{\n		myField.focus();\n		sel = document.selection.createRange();\n		sel.text = myValue;\n		myField.focus();\n	}\n\n	// Mozilla/Netscape support\n	else if (myField.selectionStart || myField.selectionStart == \'0\')\n	{\n		startPos = myField.selectionStart;\n		endPos = myField.selectionEnd;\n\n		myField.value = myField.value.substring(0, startPos) + myValue + myField.value.substring(endPos, myField.value.length);\n\n		myField.focus();\n		myField.selectionStart = startPos + myValue.length;\n		myField.selectionEnd = startPos + myValue.length;\n	}\n\n	else\n	{\n		myField.value += myValue;\n		myField.focus();\n	}\n}\njs;\n		}\n\n		elseif (gps(\'name\') == \'pop\')\n		{\n			global $img_dir;\n\n			$img_url = hu.$img_dir;\n\n			$lang_confirm_overwrite = upm_img_popper_gTxt(\'confirm_overwrite\');\n			$lang_check_leave = upm_img_popper_gTxt(\'check_leave\');\n\n			header(\"Content-type: text/javascript\");\n\n			echo <<<js\nfunction makeWin(title, content, w, h)\n{\n	if (w && h)\n	{\n		var ww = parseInt(w);\n		var wh = parseInt(h);\n\n		if (ww < 100)\n		{\n			ww = 125;\n		}\n\n		if (wh < 100)\n		{\n			wh = 125;\n\n			ww += 75;\n		}\n\n		var scroll = false;\n\n		if (screen.width && (screen.width < ww))\n		{\n			scroll = \'yes\';\n			ww = screen.width;\n		}\n\n		if (screen.height && (screen.height < wh))\n		{\n			scroll = \'yes\';\n			wh = screen.height;\n		}\n	}\n\n	else\n	{\n		ww = 450;\n		wh = 250;\n	}\n\n	if (!title)\n	{\n		title = name;\n	}\n\n	var t = (screen.height) ? (screen.height - wh) / 2 : 0;\n	var l = (screen.width) ? (screen.width - ww) / 2 : 0;\n\n	var fastWin = window.open(\'\', \'upm_img_popper_preview\', \'top = \'+t+\', left = \'+l+\', width = \'+ww+\', height = \'+wh+\', toolbar = no, location = no, directories = no, status = no, menubar = no, scrollbars = \'+scroll+\', copyhistory = no, resizable = yes\');\n\n	fastWin.document.writeln(\'<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\');\n	fastWin.document.writeln(\'<html>\');\n	fastWin.document.writeln(\'<head>\');\n	fastWin.document.writeln(\'<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\');\n	fastWin.document.writeln(\'<meta http-equiv=\"imagetoolbar\" content=\"no\">\');\n	fastWin.document.writeln(\'<title>\'+title+\'</title>\');\n	fastWin.document.writeln(\'<style type=\"text/css\">\');\n	fastWin.document.writeln(\'<!--\');\n	fastWin.document.writeln(\'body {\');\n	fastWin.document.writeln(\'margin: 0;\');\n	fastWin.document.writeln(\'padding: 0;\');\n	fastWin.document.writeln(\'color: #000;\');\n	fastWin.document.writeln(\'background-color: #fff;\');\n	fastWin.document.writeln(\'border-top: 15px solid #fc3;\');\n	fastWin.document.writeln(\'text-align: center;\');\n\n	if (scroll == false)\n	{\n		fastWin.document.writeln(\'overflow: hidden;\');\n	}\n\n	fastWin.document.writeln(\'}\');\n\n	fastWin.document.writeln(\'\');\n	fastWin.document.writeln(\'img {\');\n	fastWin.document.writeln(\'margin: 0 auto;\');\n	fastWin.document.writeln(\'padding: 0;\');\n	fastWin.document.writeln(\'border: none;\');\n\n	if (wh == 125)\n	{\n		fastWin.document.writeln(\'margin-top: \' + Math.floor(50-(h/2)) + \'px;\');\n	}\n\n	fastWin.document.writeln(\'}\');\n\n	fastWin.document.writeln(\'-->\');\n	fastWin.document.writeln(\'</style>\');\n	fastWin.document.writeln(\'</head>\');\n	fastWin.document.writeln(\'<body>\');\n	fastWin.document.writeln(\'<div>\');\n	fastWin.document.writeln(content);\n	fastWin.document.writeln(\'</div>\');\n	fastWin.document.writeln(\'</body>\');\n	fastWin.document.write(\'</html>\');\n\n	fastWin.document.close();\n\n	fastWin.focus();\n\n	return false;\n}\n\n// ---\n\nfunction buildTag(type, id, ext, width, height, alt, title)\n{\n	var inc_alt = document.getElementById(\'inc_alt\');\n	var inc_title = document.getElementById(\'inc_title\');\n\n	var inc_width = document.getElementById(\'inc_width\');\n	var inc_height = document.getElementById(\'inc_height\');\n\n	var inc_class = document.getElementById(\'inc_class\');\n	var the_class = document.getElementById(\'the_class\').value;\n\n	var custom_form = document.getElementById(\'custom_form\').value;\n\n	if (type == \'image\')\n	{\n		var insert = \'<txp:image id=\"\'+id+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n	}\n\n	else if (type == \'thumb\')\n	{\n		var insert = \'<txp:thumbnail id=\"\'+id+\'\"\';\n	}\n\n	else if (type == \'popup\')\n	{\n		var insert = \'<txp:thumbnail id=\"\'+id+\'\" poplink=\"1\"\';\n	}\n\n	else if (type == \'textile\')\n	{\n		var insert = \'!\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \'(\'+the_class+\')\';\n		}\n\n		insert += \'$img_url/\'+id+ext;\n\n		if (inc_alt.checked && alt)\n		{\n			insert += \'(\'+alt+\')\';\n		}\n\n		insert += \'!\';\n\n		return insert;\n	}\n\n	else if (type == \'xhtml\')\n	{\n		var insert = \'<img src=\"$img_url/\'+id+ext+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n\n		if (inc_width.checked && width)\n		{\n			insert += \' width=\"\'+width+\'\"\';\n		}\n\n		if (inc_height.checked && height)\n		{\n			insert += \' height=\"\'+height+\'\"\';\n		}\n\n		if (inc_alt.checked && alt)\n		{\n			insert += \' alt=\"\'+alt+\'\"\';\n		}\n\n		else\n		{\n			insert += \' alt=\"\"\';\n		}\n\n		if (inc_title.checked && title)\n		{\n			insert += \' title=\"\'+title+\'\"\';\n		}\n	}\n\n	else if (type == \'plugin-custom\')\n	{\n		var insert = \'<txp:upm_image image_id=\"\'+id+\'\" form=\"\'+custom_form+\'\"\';\n	}\n\n	else\n	{\n		type = new String(type);\n		type = type.replace(\'plugin-\', \'\');\n\n		var insert = \'<txp:upm_image type=\"\'+type+\'\" image_id=\"\'+id+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n\n		if (inc_width.checked == false)\n		{\n			insert += \' show_width=\"no\"\';\n		}\n\n		if (inc_height.checked == false)\n		{\n			insert += \' show_height=\"no\"\';\n		}\n\n		if (inc_alt.checked == false)\n		{\n			insert += \' show_alt=\"no\"\';\n		}\n\n		if (inc_title.checked == false)\n		{\n			insert += \' show_title=\"no\"\';\n		}\n	}\n\n	insert += \' />\';\n\n	return insert;\n}\n\n// ---\n\nfunction insertTag(type, id, ext, width, height, alt, title)\n{\n	var insert = buildTag(type, id, ext, width, height, alt, title);\n\n	if (window.opener.document.article_form)\n	{\n		if (type == \'append-article-image\')\n		{\n			if (window.opener.document.getElementById(\'article-image\').value == \'\')\n			{\n				window.opener.document.getElementById(\'article-image\').value = id;\n				return showMsg(\'success\');\n			}\n\n			else\n			{\n				window.opener.document.getElementById(\'article-image\').value += \',\'+id;\n				return showMsg(\'success\');\n			}\n		}\n\n		else if (type == \'replace-article-image\')\n		{\n			if (window.opener.document.getElementById(\'article-image\').value.length > 1)\n			{\n				var option = confirm(\'$lang_confirm_overwrite\');\n\n				if (option)\n				{\n					window.opener.document.getElementById(\'article-image\').value = id;\n					return showMsg(\'success\');\n				}\n			}\n\n			else\n			{\n				window.opener.document.getElementById(\'article-image\').value = id;\n				return showMsg(\'success\');\n			}\n		}\n\n		else\n		{\n			var temp = \'window.opener.document.getElementById(window.opener.current)\';\n\n			temp = eval(temp);\n\n			window.opener.upm_img_popper_insert(temp, insert);\n\n			return showMsg(\'success\');\n		}\n	}\n\n	else if (window.opener.document.getElementById(\'form\'))\n	{\n		window.opener.upm_img_popper_insert(window.opener.document.getElementById(\'form\'), insert);\n		return showMsg(\'success\');\n	}\n\n	else if (window.opener.document.getElementById(\'html\'))\n	{\n		window.opener.upm_img_popper_insert(window.opener.document.getElementById(\'html\'), insert);\n		return showMsg(\'success\');\n	}\n\n	return showMsg(\'failed\');\n}\n\n// ---\n\nfunction warnEditImage(id)\n{\n	if (window.opener.document.getElementById(\'body\').value != window.opener.document.getElementById(\'upm_img_popper_start\').value)\n	{\n		var check = confirm(\'$lang_check_leave\');\n\n		if (check)\n		{\n			goEditImage(id);\n		}\n	}\n\n	else\n	{\n		goEditImage(id);\n	}\n\n	return false;\n}\n\n// ---\n\nfunction goEditImage(id)\n{\n	window.opener.focus();\n\n	if (window.opener.document.article.step.value != \'create\')\n	{\n		document.getElementById(\'yes\').setAttribute(\'href\', window.opener.document.location);\n		showMsg(\'return\');\n	}\n\n	window.opener.document.location = \'?event=image\\u0026step=image_edit\\u0026id=\'+id;\n}\n\n// ---\n\nfunction goBack(url)\n{\n	window.opener.focus();\n\n	done(\'return\');\n\n	window.opener.document.location = url;\n}\n\n// ---\n\nfunction showMsg(id)\n{\n	var obj = document.getElementById(id);\n\n	var width = parseInt(obj.style.width);\n	var height = parseInt(obj.style.height);\n\n	var top = 100 + scrollTop();\n	var left = ( (pageWidth() / 2) - (width / 2) ) + scrollLeft();\n\n	obj.style.position = \'absolute\';\n	obj.style.top = top + \'px\';\n	obj.style.left = left + \'px\';\n	obj.style.display = \'block\';\n\n	if (id != \'return\')\n	{\n		setTimeout(\"done(\'\"+id+\"\')\", 1000);\n	}\n}\n\n// ---\n\nfunction done(id)\n{\n	document.getElementById(id).style.display = \'none\';\n}\n\n// ---\n\nfunction pageWidth()\n{\n	if (self.innerWidth)\n	{\n		return self.innerWidth;\n	}\n\n	else if (document.documentElement && document.documentElement.clientWidth)\n	{\n		return document.documentElement.clientWidth;\n	}\n\n	return 0;\n}\n\n// ---\n\nfunction scrollTop()\n{\n	if (self.pageYOffset)\n	{\n		return self.pageYOffset;\n	}\n\n	else if (document.documentElement && document.documentElement.scrollTop)\n	{\n		return document.documentElement.scrollTop;\n	}\n\n	return 0;\n}\n\n// ---\n\nfunction scrollLeft()\n{\n	if (self.pageYOffset)\n	{\n		return self.pageXOffset;\n	}\n\n	else if (document.documentElement && document.documentElement.scrollTop)\n	{\n		return document.documentElement.scrollLeft;\n	}\n\n	return 0;\n}\n\njs;\n		}\n\n		exit(0);\n	}\n\n// ---\n\n	function upm_img_popper_prefs($event, $step)\n	{\n		global $prefs;\n\n		pagetop(upm_img_popper_gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		if ($step == \'update\')\n		{\n			extract(doSlash(psas(array(\n				\'show_width\', \'show_height\', \'show_alt\', \'show_title\', \'show_class\', \'the_class\', \'custom_form\',\n				\'show_form\', \'admin_popup_width\', \'admin_popup_height\'\n			))));\n\n			$show_width  = ($show_width == \'1\') ? $show_width : \'0\';\n			$show_height = ($show_height == \'1\') ? $show_height : \'0\';\n			$show_alt    = ($show_alt == \'1\') ? $show_alt : \'0\';\n			$show_title  = ($show_title == \'1\') ? $show_title : \'0\';\n			$show_class  = ($show_class == \'1\') ? $show_class : \'0\';\n\n			$the_class   = preg_replace(\"/(^|&\\S+;)|(<[^>]*>)/U\", \'\', dumbDown($the_class));\n			$custom_form = sanitizeForUrl($custom_form);\n\n			if (empty($admin_popup_width))\n			{\n				$admin_popup_width = \'725\';\n			}\n\n			if (empty($admin_popup_height))\n			{\n				$admin_popup_height = \'400\';\n			}\n\n			safe_update(\'txp_prefs\', \"val = \'$show_width\'\", \"name = \'upm_img_popper_show_width\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_height\'\", \"name = \'upm_img_popper_show_height\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_alt\'\", \"name = \'upm_img_popper_show_alt\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_title\'\", \"name = \'upm_img_popper_show_title\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_class\'\", \"name = \'upm_img_popper_show_class\'\");\n			safe_update(\'txp_prefs\', \"val = \'$the_class\'\", \"name = \'upm_img_popper_the_class\'\");\n			safe_update(\'txp_prefs\', \"val = \'$custom_form\'\", \"name = \'upm_img_popper_custom_form\'\");\n\n			safe_update(\'txp_prefs\', \"val = \'$show_form\'\", \"name = \'upm_img_popper_show_form\'\");\n			safe_update(\'txp_prefs\', \"val = \'$admin_popup_width\'\", \"name = \'upm_img_popper_admin_popup_width\'\");\n			safe_update(\'txp_prefs\', \"val = \'$admin_popup_height\'\", \"name = \'upm_img_popper_admin_popup_height\'\");\n\n			$prefs = get_prefs();\n		}\n\n		echo n.n.\'<div style=\"margin: 3em auto auto auto; width: 20em;\">\'.\n\n		n.n.hed(upm_img_popper_gTxt(\'prefs\'), \'1\').\n\n		n.n.form(\n			n.eInput(\'upm_img_popper_prefs\').\n			n.sInput(\'update\').\n\n			n.n.tag(\n				n.n.tag(upm_img_popper_gTxt(\'public_side\'), \'legend\').\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_width\').br.\n					n.yesnoRadio(\'show_width\', $prefs[\'upm_img_popper_show_width\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_height\').br.\n					n.yesnoRadio(\'show_height\', $prefs[\'upm_img_popper_show_height\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_alt\').br.\n					n.yesnoRadio(\'show_alt\', $prefs[\'upm_img_popper_show_alt\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_title\').br.\n					n.yesnoRadio(\'show_title\', $prefs[\'upm_img_popper_show_title\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_class\').br.\n					n.yesnoRadio(\'show_class\', $prefs[\'upm_img_popper_show_class\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'the_class\').br.\n					n.fInput(\'text\', \'the_class\', $prefs[\'upm_img_popper_the_class\'], \'edit\')\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'custom_form\').br.\n					n.fInput(\'text\', \'custom_form\', $prefs[\'upm_img_popper_custom_form\'], \'edit\')\n				)\n			, \'fieldset\').\n\n			n.n.tag(\n				n.n.tag(upm_img_popper_gTxt(\'admin_side\'), \'legend\').\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_form\').br.\n					n.yesnoRadio(\'show_form\', $prefs[\'upm_img_popper_show_form\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'admin_popup_width\').br.\n					n.fInput(\'text\', \'admin_popup_width\', $prefs[\'upm_img_popper_admin_popup_width\'], \'edit\')\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'admin_popup_height\').br.\n					n.fInput(\'text\', \'admin_popup_height\', $prefs[\'upm_img_popper_admin_popup_height\'], \'edit\')\n				)\n\n			, \'fieldset\').\n\n			n.n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n\n		).n.n.\'</div>\';\n	}\n\n// ---\n\n	function upm_img_popper_install()\n	{\n		global $prefs;\n\n		$updated = false;\n\n		$settings = \"prefs_id = 1, type = 2, event = \'admin\', position = 0\";\n\n		if (!isset($prefs[\'upm_img_popper_show_width\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_width\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_height\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_height\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_alt\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_alt\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_title\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_title\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_class\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_class\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_the_class\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_the_class\',\n				val = \'image\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_custom_form\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_custom_form\',\n				val = \'upm_img_popper\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_form\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_form\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_admin_popup_width\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_admin_popup_width\',\n				val = \'725\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_admin_popup_height\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_admin_popup_height\',\n				val = \'400\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if ($updated)\n		{\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n	}\n\n// ---\n\n	function upm_img_popper_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'admin_popup_width\'     => \'Popup width\',\n			\'admin_popup_height\'    => \'Popup height\',\n			\'admin_side\'            => \'Admin\',\n			\'alt_text_for\'		=> \'Alternate text for\',\n			\'article_image_append\'	=> \'Article image (append)\',\n			\'article_image_replace\' => \'Article image (replace)\',\n			\'assign_class\'		=> \'assign class\',\n			\'built_in\'		=> \'Built-in\',\n			\'caption_for\'		=> \'Caption for\',\n			\'check_leave\'		=> \'You have made changes to the current post which you have not saved. Continue without saving?\',\n			\'confirm_overwrite\'	=> \'You have more than one article image defined. This action will overwrite the entire list. Continue?\',\n			\'custom_form\'		=> \'Custom form\',\n			\'failed\'		=> \'Insert failed.\',\n			\'for\'			=> \'for\',\n			\'image\'			=> \'Image\',\n			\'image_selector\'	=> \'Image Selector\',\n			\'include\'		=> \'include\',\n			\'include_alt\'		=> \'include alt text\',\n			\'include_caption\'	=> \'include caption\',\n			\'include_height\'	=> \'include height\',\n			\'include_width\'		=> \'include width\',\n			\'insert\'		=> \'Insert\',\n			\'insert_image\'		=> \'Insert Image\',\n			\'insert_plugin\'		=> \'Insert (upm_image)\',\n			\'no_thumbnail\'		=> \'Thumbnail not found.\',\n			\'override\'		=> \'overrides all optional parameters\',\n			\'popup\'			=> \'Popup\',\n			\'prefs\'			=> \'upm_img_popper Preferences\',\n			\'public_side\'           => \'Public\',\n			\'return\'		=> \'Return to editing the post?\',\n			\'show_alt\'		=> \'Show alternate text?\',\n			\'show_class\'		=> \'Show class?\',\n			\'show_height\'		=> \'Show height?\',\n			\'show_title\'		=> \'Show caption?\',\n			\'show_width\'		=> \'Show width?\',\n			\'show_form\'		=> \'Show customization options in popup?\',\n			\'success\'		=> \'Success.\',\n			\'the_class\'		=> \'The class\',\n			\'view_all\'		=> \'View All\'\n		);\n\n		return strtr($lang[$what], $atts);\n	}\n\n	function nav_form_upm($event, $bm, $page, $numPages, $sort, $dir, $crit, $search_method, $total=0, $limit=0, $step=\'list\')\n	{\n		global $theme;\n		if ($crit != \'\' && $total > 1)\n		{\n			$out[] = $theme->announce(\n				gTxt(\'showing_search_results\',\n					array(\n						\'{from}\'  => (($page - 1) * $limit) + 1,\n						\'{to}\'    => min($total, $page * $limit),\n						\'{total}\' => $total\n						)\n					)\n				);\n		}\n\n		if ($numPages > 1)\n		{\n			$option_list = array();\n\n			for ($i = 1; $i <= $numPages; $i++)\n			{\n				if ($i == $page)\n				{\n					$option_list[] = \'<option value=\"\'.$i.\'\" selected=\"selected\">\'.\"$i/$numPages\".\'</option>\';\n				}\n\n				else\n				{\n					$option_list[] = \'<option value=\"\'.$i.\'\">\'.\"$i/$numPages\".\'</option>\';\n				}\n			}\n\n			$nav = array();\n\n			$nav[] = ($page > 1) ?\n				PrevNextLink($event, $page - 1, gTxt(\'prev\'), \'prev\', $sort, $dir, $crit, $search_method, $step).sp :\n				tag(gTxt(\'prev\'), \'span\', \' class=\"navlink-disabled\"\').sp;\n\n			$nav[] = \'<select name=\"page\" onchange=\"submit(this.form);\">\';\n			$nav[] = n.join(n, $option_list);\n			$nav[] = n.\'</select>\';\n\n			$nav[] = ($page != $numPages) ?\n				sp.PrevNextLink($event, $page + 1, gTxt(\'next\'), \'next\', $sort, $dir, $crit, $search_method, $step) :\n				sp.tag(gTxt(\'next\'), \'span\', \' class=\"navlink-disabled\"\');\n\n			$out[] = \'<form class=\"nav-form\" method=\"get\" action=\"index.php\">\'.\n				n.eInput($event).\n				n.hInput(\'bm\', $bm).\n				n.sInput($step).\n				( $sort ? n.hInput(\'sort\', $sort).n.hInput(\'dir\', $dir) : \'\' ).\n				( ($crit != \'\') ? n.hInput(\'crit\', $crit).n.hInput(\'search_method\', $search_method) : \'\' ).\n				\'<p class=\"prev-next\">\'.\n				join(\'\', $nav).\n				\'</p>\'.\n				n.tInput().\n				n.\'</form>\';\n		}\n		else\n		{\n			$out[] = graf($page.\'/\'.$numPages, \' class=\"prev-next\"\');\n		}\n\n		return join(n, $out);\n	}', '\nif (txpinterface == \'admin\')\n	{\n		upm_img_popper_install();\n\n		add_privs(\'upm_img_popper\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper\', \'upm_img_popper\');\n\n		add_privs(\'upm_img_popper_link\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper_link\', \'article\');\n\n		add_privs(\'upm_img_popper_js\', \'1,2,3,4,5\');\n		register_callback(\'upm_img_popper_js\', \'upm_img_popper_js\', \'\', 1);\n\n		add_privs(\'upm_img_popper_prefs\', \'1,2\');\n		register_tab(\'extensions\', \'upm_img_popper_prefs\', \'upm_img_popper\');\n		register_callback(\'upm_img_popper_prefs\', \'upm_img_popper_prefs\');\n	}\n\n// ---\n\n	function upm_img_popper()\n	{\n		global $step;\n\n		if ($step == \'upm_img_popper_change_pageby\')\n		{\n			event_change_pageby(\'image\');\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n\n		upm_img_popper_list();\n	}\n\n// ---\n\n	function upm_img_popper_list($message = \'\')\n	{\n		global $prefs;\n\n		pagetop(upm_img_popper_gTxt(\'image_selector\'), $message);\n\n		$path_to_site = $prefs[\'path_to_site\'];\n		$img_dir = $prefs[\'img_dir\'];\n\n		if (!defined(\'IMPATH\'))\n		{\n			define(\'IMPATH\', $path_to_site.\'/\'.$img_dir.\'/\');\n		}\n\n		echo <<<css\n<style type=\"text/css\">\n<!--\nlabel{color:#000;cursor:pointer;}\nul{margin:0;padding:0;list-style:none;}\nli{margin:0 0 2px 0;padding:0;}\np{margin:5px 0;padding:0;}\n.right{text-align:right;}\n.center{margin:0 auto;text-align:center;}\n#no{margin-left:2em;}\n#return,#success,#failed{padding:1em;text-align:center;background-color:#fff;border:5px solid #fc3;}\n-->\n</style>\n<script type=\"text/javascript\" src=\"index.php?event=upm_img_popper_js&#38;name=pop\"></script>\ncss;\n\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		switch ($sort)\n		{\n			case \'id\':\n				$sort_sql = \"id $dir\";\n			break;\n\n			case \'name\':\n				$sort_sql = \"name $dir\";\n			break;\n\n			case \'thumbnail\':\n				$sort_sql = \"thumbnail $dir, id asc\";\n			break;\n\n			case \'category\':\n				$sort_sql = \"category $dir, id asc\";\n			break;\n\n			case \'date\':\n				$sort_sql = \"date $dir, id asc\";\n			break;\n\n			case \'author\':\n				$sort_sql = \"author $dir, id asc\";\n			break;\n\n			default:\n				$dir = \'desc\';\n				$sort_sql = \"id $dir\";\n			break;\n		}\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$criteria = 1;\n\n		if ($search_method and $crit)\n		{\n			$crit_escaped = doSlash($crit);\n\n			$critsql = array(\n				\'id\'		=> \"id = \'$crit_escaped\'\",\n				\'name\'		=> \"name like \'%$crit_escaped%\'\",\n				\'category\'	=> \"category like \'%$crit_escaped%\'\",\n				\'author\'	=> \"author like \'%$crit_escaped%\'\",\n				\'alt\'		=> \"alt like \'%$crit_escaped%\'\",\n				\'caption\'	=> \"caption like \'%$crit_escaped%\'\"\n			);\n\n			if (array_key_exists($search_method, $critsql))\n			{\n				$criteria = $critsql[$search_method];\n				$limit = 500;\n			}\n\n			else\n			{\n				$search_method = \'\';\n				$crit = \'\';\n			}\n		}\n\n		else\n		{\n			$search_method = \'\';\n			$crit = \'\';\n		}\n\n		$total = safe_count(\'txp_image\', \"$criteria\");\n\n		if ($total < 1)\n		{\n			if ($criteria != 1)\n			{\n				echo n.upm_img_popper_search_form($crit, $search_method, $sort, $dir).\n					n.graf(gTxt(\'no_results_found\'), \' style=\"text-align: center;\"\');\n			}\n\n			else\n			{\n				echo n.graf(gTxt(\'no_images_recorded\'), \' style=\"text-align: center;\"\');\n			}\n\n			return;\n		}\n\n		echo upm_img_popper_options_form();\n\n		$limit = max($prefs[\'image_list_pageby\'], 15);\n\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n		echo upm_img_popper_search_form($crit, $search_method, $sort, $dir);\n\n		$rs = safe_rows_start(\'*, unix_timestamp(date) as uDate\', \'txp_image\',\n			\"$criteria order by $sort_sql limit $offset, $limit\n		\");\n\n		if ($rs)\n		{\n			$sort_link = \'upm_img_popper\'.a.\'bm=true\';\n\n			echo n.n.startTable(\'list\').\n				n.tr(\n					column_head(\'ID\', \'id\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'name\', \'name\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'thumbnail\', \'thumbnail\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell(gTxt(\'preview\')).\n					column_head(\'author\', \'author\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell(upm_img_popper_gTxt(\'insert\')).\n					( function_exists(\'upm_image\') ? hCell(upm_img_popper_gTxt(\'insert_plugin\')) : \'\' ).\n					hCell()\n				);\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				$edit_url = \'?event=image\'.a.\'step=image_edit\'.a.\'id=\'.$id.a.\'sort=\'.$sort.\n					a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n\n				$name = empty($name) ? gTxt(\'unnamed\') : $name;\n\n				$thumbnail = ($thumbnail) ?\n					\'<img src=\"\'.hu.$img_dir.\'/\'.$id.\'t\'.$ext.\'\" />\' :\n					gTxt(\'no\');\n\n				// god I hate javascript in php\n\n				$ids = n.\'<ul>\'.n.\n					n.t.\'<li>\'.$id.\'</li>\'.\n					n.t.\'<li><a href=\"\'.$edit_url.\'\" onclick=\"warnEditImage(\'.$id.\'); return false;\">\'.gTxt(\'edit\').\'</a></li>\'.\n					n.t.\'<li>\'.safe_strftime(\'%d %b %Y %I:%M %p\', $uDate).\'</li>\'.\n					n.\'</ul>\';\n\n				$insert = n.\'<ul>\'.n.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'append-article-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'article_image_append\').\'</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'replace-article-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'article_image_replace\').\'</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				$plugin = n.\'<ul>\'.n.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-image\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				if ($thumbnail)\n				{\n					$insert .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'thumb\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.gTxt(\'thumbnail\').\'</a></li>\'.\n						n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'popup\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'popup\').\'</a></li>\';\n\n					$plugin .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-thumb\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.gTxt(\'thumbnail\').\'</a></li>\'.\n						n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-popup\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'popup\').\'</a></li>\';\n				}\n\n				$insert .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'textile\\\', \'.chr(39).$id.chr(39).\', \'.chr(39).$ext.chr(39).\', \\\'\\\', \\\'\\\', \'.chr(39).htmlspecialchars($alt).chr(39).\'); return false;\">Textile</a></li>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'xhtml\\\', \'.chr(39).$id.chr(39).\', \'.chr(39).$ext.chr(39).\', \'.chr(39).$w.chr(39).\', \'.chr(39).$h.chr(39).\', \'.chr(39).htmlspecialchars($alt).chr(39).\', \'.chr(39).htmlspecialchars($caption).chr(39).\'); return false;\">XHTML</a></li>\'.\n					n.\'</ul>\';\n\n				$plugin .= n.t.\'<li><a href=\"#\" onclick=\"insertTag(\\\'plugin-custom\\\', \'.chr(39).$id.chr(39).\'); return false;\">\'.upm_img_popper_gTxt(\'custom_form\').\'</a></li>\'.\n					n.\'</ul>\';\n\n				$preview = n.\'<ul>\'.\n					n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).\'&#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).\'&#60;img src=&#34;\'.hu.$img_dir.\'/\'.$id.$ext.\'&#34; /&#62;\'.chr(39).\', \'.$w.\', \'.$h.\'); return false;\">\'.upm_img_popper_gTxt(\'image\').\'</a></li>\';\n\n				if (!empty($alt))\n				{\n					$preview .= n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).upm_img_popper_gTxt(\'alt_text_for\').\' &#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).htmlspecialchars($alt).chr(39).\'); return false;\">\'.gTxt(\'alt_text\').\'</a></li>\';\n				}\n\n				if (!empty($caption))\n				{\n					$preview .= n.t.\'<li><a href=\"#\" onclick=\"makeWin(\'.chr(39).upm_img_popper_gTxt(\'caption_for\').\' &#8220;\'.$name.\'&#8221;\'.chr(39).\', \'.chr(39).htmlspecialchars($caption).chr(39).\'); return false;\">\'.gTxt(\'caption\').\'</a></li>\';\n				}\n\n				$preview .= n.\'</ul>\';\n\n				$category = ($category) ? \'<span title=\"\'.fetch_category_title($category, \'image\').\'\">\'.$category.\'</span>\' : \'\';\n\n				echo n.n.tr(\n					n.td($ids, 125).\n\n					td(\'<a href=\"\'.$edit_url.\'\" onclick=\"warnEditImage(\'.$id.\'); return false;\">\'.$name.\'</a>\'\n					, 75).\n\n					td($thumbnail, 195).\n\n					td($preview).\n\n					td(\n						\'<span title=\"\'.get_author_name($author).\'\">\'.$author.\'</span>\'\n					, 75).\n\n					td($insert, 150).\n					( function_exists(\'upm_image\') ? n.td($plugin) : \'\' ).\n\n					td(\n						dLink(\'image\', \'image_delete\', \'id\', $id)\n					, 10)\n				);\n			}\n\n			echo endTable().\n\n				nav_form_upm(\'upm_img_popper\',\'1\', $page, $numPages, $sort, $dir, $crit, $search_method).\n\n				upm_img_popper_pageby_form().\n\n				n.tag(\n					n.graf(upm_img_popper_gTxt(\'return\')).\n					n.graf(\'<a id=\"yes\" href=\"#\" onclick=\"goBack(this.href); return false;\">\'.gTxt(\'yes\').\'</a>.\'.\n						\' <a id=\"no\" href=\"#\" onclick=\"done(\\\'return\\\'); return false;\">\'.gTxt(\'no\').\'</a>.\')\n				, \'div\',	\' id=\"return\" style=\"display: none; width: 200px; height: 75px;\"\').\n\n				n.graf(upm_img_popper_gTxt(\'success\'), \' id=\"success\" style=\"display: none; width: 150px; height: 35px;\"\').\n				n.graf(upm_img_popper_gTxt(\'failed\'), \' id=\"failed\" style=\"display: none; width: 150px; height: 35px;\"\');\n		}\n	}\n\n// ---\n\n	function upm_img_popper_options_form()\n	{\n		global $prefs;\n\n		echo n.\'<form action=\"\" style=\"margin-bottom: 15px;\'.( !$prefs[\'upm_img_popper_show_form\'] ? \' display: none;\' : \'\').\'\">\'.\n			graf(\n				n.\'<input type=\"checkbox\" id=\"inc_width\"\'.(($prefs[\'upm_img_popper_show_width\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_width\">\'.upm_img_popper_gTxt(\'include_width\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_height\"\'.(($prefs[\'upm_img_popper_show_height\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_height\">\'.upm_img_popper_gTxt(\'include_height\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_alt\"\'.(($prefs[\'upm_img_popper_show_alt\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_alt\">\'.upm_img_popper_gTxt(\'include_alt\').\'</label> \'.\n				n.\'<input type=\"checkbox\" id=\"inc_title\"\'.(($prefs[\'upm_img_popper_show_title\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_title\">\'.upm_img_popper_gTxt(\'include_caption\').\'</label>\'.br.\n				n.\'<input type=\"checkbox\" id=\"inc_class\"\'.(($prefs[\'upm_img_popper_show_class\'] == \'1\') ? \' checked=\"checked\"\' : \'\').\' /> <label for=\"inc_class\">\'.upm_img_popper_gTxt(\'assign_class\').\'</label> \'.\n				n.\'<input type=\"text\" id=\"the_class\" value=\"\'.$prefs[\'upm_img_popper_the_class\'].\'\" class=\"edit\" />\'\n			, \' style=\"text-align: center;\"\').\n\n			n.graf(\n				\'<label for=\"custom_form\">\'.upm_img_popper_gTxt(\'custom_form\').\'</label> <input type=\"text\" id=\"custom_form\" value=\"\'.$prefs[\'upm_img_popper_custom_form\'].\'\" class=\"edit\" /> (\'.upm_img_popper_gTxt(\'override\').\')\'\n			, \' style=\"text-align: center;\"\').\n\n		n.\'</form>\';\n	}\n\n// ---\n\n	function upm_img_popper_search_form($crit, $method, $sort, $dir)\n	{\n		$default_method = \'name\';\n\n		$method = ($method) ? $method : $default_method;\n\n		$methods =	array(\n			\'id\'			 => gTxt(\'ID\'),\n			\'name\'		 => gTxt(\'name\'),\n			\'category\' => gTxt(\'image_category\'),\n			\'author\'	 => gTxt(\'author\'),\n			\'alt\'			 => gTxt(\'alt_text\'),\n			\'caption\'	 => gTxt(\'caption\')\n		);\n\n		return n.n.\'<form method=\"get\" action=\"index.php\" style=\"margin: auto; text-align: center;\">\'.\n			n.graf(\n				\'<label for=\"upm-img-popper-search\">\'.gTxt(\'search\').\'</label>\'.sp.\n				selectInput(\'search_method\', $methods, $method, \'\', \'\', \'upm-img-popper-search\').sp.\n				fInput(\'text\', \'crit\', $crit, \'edit\', \'\', \'\', \'15\').\n				eInput(\'upm_img_popper\').\n				hInput(\'bm\',\'1\').\n				fInput(\'submit\', \'search\', gTxt(\'go\'), \'smallerbox\')\n			).\n\n			($crit ? n.graf(\'<a href=\"?event=upm_img_popper\'.a.\'bm=true\">\'.upm_img_popper_gTxt(\'view_all\').\'</a>\') : \'\').\n\n		n.\'</form>\';\n	}\n\n// ---\n\n	function upm_img_popper_link()\n	{\n		$app_mode = gps(\'app_mode\');\n		if ($app_mode===\'async\') return;\n\n		$view = gps(\'view\');\n\n		if ($view == \'text\' or empty($view))\n		{\n			echo <<<form\n<form action=\"\">\n<input type=\"hidden\" id=\"upm_img_popper_start\" value=\"\">\n</form>\n<script type=\"text/javascript\" src=\"index.php?event=upm_img_popper_js&#38;name=link\"></script>\nform;\n\n		}\n	}\n\n// ---\n\n	function upm_img_popper_pageby_form()\n	{\n		global $prefs;\n\n		$vals = array(\n			15	=> 15,\n			25	=> 25,\n			50	=> 50,\n			100 => 100\n		);\n\n		$select_page = selectInput(\'qty\', $vals, $prefs[\'image_list_pageby\'], \'\', 1);\n\n		// proper localisation\n		$page = str_replace(\'{page}\', $select_page, gTxt(\'view_per_page\'));\n\n		return form(\n			\'<div style=\"margin: auto; text-align: center;\">\'.\n				$page.\n				eInput(\'upm_img_popper\').\n				sInput(\'upm_img_popper_change_pageby\').\n				hInput(\'bm\', \'1\').\n				\'<noscript> <input type=\"submit\" value=\"\'.gTxt(\'go\').\'\" class=\"smallerbox\" /></noscript>\'.\n			\'</div>\'\n		);\n	}\n\n// ---\n\n	function upm_img_popper_js()\n	{\n		while (@ob_end_clean());\n\n		if (gps(\'name\') == \'link\')\n		{\n			global $prefs;\n\n			$txt = gTxt(\'advanced_options\');\n			$lang_insert_image = upm_img_popper_gTxt(\'insert_image\');\n\n			header(\"Content-type: text/javascript\");\n\n			echo <<<js\n\ndocument.getElementById(\'upm_img_popper_start\').value = document.getElementById(\'body\').value;\n\nvar current = \'body\';\n\nif (document.getElementById(\'excerpt\'))\n{\n	// IE is retarded\n	document.getElementById(\'body\').onclick = function () {\n		current = \'body\';\n	};\n\n	document.getElementById(\'excerpt\').onclick = function () {\n		current = \'excerpt\';\n	};\n}\n\n// ---\n\nvar txt = \'$txt\';\n\nvar a = (document.createElementNS) ?\n	document.createElementNS(\'http://www.w3.org/1999/xhtml\', \'a\') :\n	document.createElement(\'a\');\n\na.appendChild(document.createTextNode(\'$lang_insert_image\'));\n\nvar w = {$prefs[\'upm_img_popper_admin_popup_width\']};\nvar h = {$prefs[\'upm_img_popper_admin_popup_height\']};\n\nvar t = (screen.height) ? (screen.height - h) / 2 : 0;\nvar l = (screen.width) ? (screen.width - w) / 2 : 0;\n\na.onclick = function () {\n	var upm_img_popper = window.open(this.href, \'upm_img_popper\', \'top = \'+t+\', left = \'+l+\', width = \'+w+\', height = \'+h+\', toolbar = no, location = no, directories = no, status = yes, menubar = no, scrollbars = yes, copyhistory = no, resizable = yes\');\n		upm_img_popper.focus();\n		return false;\n};\n\na.setAttribute(\'target\', \'blank\');\na.setAttribute(\'href\', \'?event=upm_img_popper\\u0026bm=1\');\n\nvar link = (document.createElementNS) ?\n	document.createElementNS(\'http://www.w3.org/1999/xhtml\', \'h3\') :\n	document.createElement(\'h3\');\n\nlink.setAttribute(\'className\', \'plain\'); // stupid IE\nlink.setAttribute(\'class\', \'plain\');\nlink.appendChild(a);\n\n// ---\n\nvar h3s = document.getElementsByTagName(\'h3\');\n\nfor (var i = 0; i < h3s.length; i++)\n{\n	var h3 = document.getElementsByTagName(\'h3\')[i];\n\n	if (h3.firstChild.firstChild)\n	{\n		if (h3.firstChild.firstChild.nodeValue == txt)\n		{\n			h3.parentNode.insertBefore(link, h3);\n		}\n	}\n}\n\n// ---\n// thanks Alex! <http://www.alexking.org/>\n\nfunction upm_img_popper_insert(myField, myValue)\n{\n	// IE support\n	if (document.selection)\n	{\n		myField.focus();\n		sel = document.selection.createRange();\n		sel.text = myValue;\n		myField.focus();\n	}\n\n	// Mozilla/Netscape support\n	else if (myField.selectionStart || myField.selectionStart == \'0\')\n	{\n		startPos = myField.selectionStart;\n		endPos = myField.selectionEnd;\n\n		myField.value = myField.value.substring(0, startPos) + myValue + myField.value.substring(endPos, myField.value.length);\n\n		myField.focus();\n		myField.selectionStart = startPos + myValue.length;\n		myField.selectionEnd = startPos + myValue.length;\n	}\n\n	else\n	{\n		myField.value += myValue;\n		myField.focus();\n	}\n}\njs;\n		}\n\n		elseif (gps(\'name\') == \'pop\')\n		{\n			global $img_dir;\n\n			$img_url = hu.$img_dir;\n\n			$lang_confirm_overwrite = upm_img_popper_gTxt(\'confirm_overwrite\');\n			$lang_check_leave = upm_img_popper_gTxt(\'check_leave\');\n\n			header(\"Content-type: text/javascript\");\n\n			echo <<<js\nfunction makeWin(title, content, w, h)\n{\n	if (w && h)\n	{\n		var ww = parseInt(w);\n		var wh = parseInt(h);\n\n		if (ww < 100)\n		{\n			ww = 125;\n		}\n\n		if (wh < 100)\n		{\n			wh = 125;\n\n			ww += 75;\n		}\n\n		var scroll = false;\n\n		if (screen.width && (screen.width < ww))\n		{\n			scroll = \'yes\';\n			ww = screen.width;\n		}\n\n		if (screen.height && (screen.height < wh))\n		{\n			scroll = \'yes\';\n			wh = screen.height;\n		}\n	}\n\n	else\n	{\n		ww = 450;\n		wh = 250;\n	}\n\n	if (!title)\n	{\n		title = name;\n	}\n\n	var t = (screen.height) ? (screen.height - wh) / 2 : 0;\n	var l = (screen.width) ? (screen.width - ww) / 2 : 0;\n\n	var fastWin = window.open(\'\', \'upm_img_popper_preview\', \'top = \'+t+\', left = \'+l+\', width = \'+ww+\', height = \'+wh+\', toolbar = no, location = no, directories = no, status = no, menubar = no, scrollbars = \'+scroll+\', copyhistory = no, resizable = yes\');\n\n	fastWin.document.writeln(\'<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\');\n	fastWin.document.writeln(\'<html>\');\n	fastWin.document.writeln(\'<head>\');\n	fastWin.document.writeln(\'<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\');\n	fastWin.document.writeln(\'<meta http-equiv=\"imagetoolbar\" content=\"no\">\');\n	fastWin.document.writeln(\'<title>\'+title+\'</title>\');\n	fastWin.document.writeln(\'<style type=\"text/css\">\');\n	fastWin.document.writeln(\'<!--\');\n	fastWin.document.writeln(\'body {\');\n	fastWin.document.writeln(\'margin: 0;\');\n	fastWin.document.writeln(\'padding: 0;\');\n	fastWin.document.writeln(\'color: #000;\');\n	fastWin.document.writeln(\'background-color: #fff;\');\n	fastWin.document.writeln(\'border-top: 15px solid #fc3;\');\n	fastWin.document.writeln(\'text-align: center;\');\n\n	if (scroll == false)\n	{\n		fastWin.document.writeln(\'overflow: hidden;\');\n	}\n\n	fastWin.document.writeln(\'}\');\n\n	fastWin.document.writeln(\'\');\n	fastWin.document.writeln(\'img {\');\n	fastWin.document.writeln(\'margin: 0 auto;\');\n	fastWin.document.writeln(\'padding: 0;\');\n	fastWin.document.writeln(\'border: none;\');\n\n	if (wh == 125)\n	{\n		fastWin.document.writeln(\'margin-top: \' + Math.floor(50-(h/2)) + \'px;\');\n	}\n\n	fastWin.document.writeln(\'}\');\n\n	fastWin.document.writeln(\'-->\');\n	fastWin.document.writeln(\'</style>\');\n	fastWin.document.writeln(\'</head>\');\n	fastWin.document.writeln(\'<body>\');\n	fastWin.document.writeln(\'<div>\');\n	fastWin.document.writeln(content);\n	fastWin.document.writeln(\'</div>\');\n	fastWin.document.writeln(\'</body>\');\n	fastWin.document.write(\'</html>\');\n\n	fastWin.document.close();\n\n	fastWin.focus();\n\n	return false;\n}\n\n// ---\n\nfunction buildTag(type, id, ext, width, height, alt, title)\n{\n	var inc_alt = document.getElementById(\'inc_alt\');\n	var inc_title = document.getElementById(\'inc_title\');\n\n	var inc_width = document.getElementById(\'inc_width\');\n	var inc_height = document.getElementById(\'inc_height\');\n\n	var inc_class = document.getElementById(\'inc_class\');\n	var the_class = document.getElementById(\'the_class\').value;\n\n	var custom_form = document.getElementById(\'custom_form\').value;\n\n	if (type == \'image\')\n	{\n		var insert = \'<txp:image id=\"\'+id+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n	}\n\n	else if (type == \'thumb\')\n	{\n		var insert = \'<txp:thumbnail id=\"\'+id+\'\"\';\n	}\n\n	else if (type == \'popup\')\n	{\n		var insert = \'<txp:thumbnail id=\"\'+id+\'\" poplink=\"1\"\';\n	}\n\n	else if (type == \'textile\')\n	{\n		var insert = \'!\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \'(\'+the_class+\')\';\n		}\n\n		insert += \'$img_url/\'+id+ext;\n\n		if (inc_alt.checked && alt)\n		{\n			insert += \'(\'+alt+\')\';\n		}\n\n		insert += \'!\';\n\n		return insert;\n	}\n\n	else if (type == \'xhtml\')\n	{\n		var insert = \'<img src=\"$img_url/\'+id+ext+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n\n		if (inc_width.checked && width)\n		{\n			insert += \' width=\"\'+width+\'\"\';\n		}\n\n		if (inc_height.checked && height)\n		{\n			insert += \' height=\"\'+height+\'\"\';\n		}\n\n		if (inc_alt.checked && alt)\n		{\n			insert += \' alt=\"\'+alt+\'\"\';\n		}\n\n		else\n		{\n			insert += \' alt=\"\"\';\n		}\n\n		if (inc_title.checked && title)\n		{\n			insert += \' title=\"\'+title+\'\"\';\n		}\n	}\n\n	else if (type == \'plugin-custom\')\n	{\n		var insert = \'<txp:upm_image image_id=\"\'+id+\'\" form=\"\'+custom_form+\'\"\';\n	}\n\n	else\n	{\n		type = new String(type);\n		type = type.replace(\'plugin-\', \'\');\n\n		var insert = \'<txp:upm_image type=\"\'+type+\'\" image_id=\"\'+id+\'\"\';\n\n		if (inc_class.checked && the_class)\n		{\n			insert += \' class=\"\'+the_class+\'\"\';\n		}\n\n		if (inc_width.checked == false)\n		{\n			insert += \' show_width=\"no\"\';\n		}\n\n		if (inc_height.checked == false)\n		{\n			insert += \' show_height=\"no\"\';\n		}\n\n		if (inc_alt.checked == false)\n		{\n			insert += \' show_alt=\"no\"\';\n		}\n\n		if (inc_title.checked == false)\n		{\n			insert += \' show_title=\"no\"\';\n		}\n	}\n\n	insert += \' />\';\n\n	return insert;\n}\n\n// ---\n\nfunction insertTag(type, id, ext, width, height, alt, title)\n{\n	var insert = buildTag(type, id, ext, width, height, alt, title);\n\n	if (window.opener.document.article_form)\n	{\n		if (type == \'append-article-image\')\n		{\n			if (window.opener.document.getElementById(\'article-image\').value == \'\')\n			{\n				window.opener.document.getElementById(\'article-image\').value = id;\n				return showMsg(\'success\');\n			}\n\n			else\n			{\n				window.opener.document.getElementById(\'article-image\').value += \',\'+id;\n				return showMsg(\'success\');\n			}\n		}\n\n		else if (type == \'replace-article-image\')\n		{\n			if (window.opener.document.getElementById(\'article-image\').value.length > 1)\n			{\n				var option = confirm(\'$lang_confirm_overwrite\');\n\n				if (option)\n				{\n					window.opener.document.getElementById(\'article-image\').value = id;\n					return showMsg(\'success\');\n				}\n			}\n\n			else\n			{\n				window.opener.document.getElementById(\'article-image\').value = id;\n				return showMsg(\'success\');\n			}\n		}\n\n		else\n		{\n			var temp = \'window.opener.document.getElementById(window.opener.current)\';\n\n			temp = eval(temp);\n\n			window.opener.upm_img_popper_insert(temp, insert);\n\n			return showMsg(\'success\');\n		}\n	}\n\n	else if (window.opener.document.getElementById(\'form\'))\n	{\n		window.opener.upm_img_popper_insert(window.opener.document.getElementById(\'form\'), insert);\n		return showMsg(\'success\');\n	}\n\n	else if (window.opener.document.getElementById(\'html\'))\n	{\n		window.opener.upm_img_popper_insert(window.opener.document.getElementById(\'html\'), insert);\n		return showMsg(\'success\');\n	}\n\n	return showMsg(\'failed\');\n}\n\n// ---\n\nfunction warnEditImage(id)\n{\n	if (window.opener.document.getElementById(\'body\').value != window.opener.document.getElementById(\'upm_img_popper_start\').value)\n	{\n		var check = confirm(\'$lang_check_leave\');\n\n		if (check)\n		{\n			goEditImage(id);\n		}\n	}\n\n	else\n	{\n		goEditImage(id);\n	}\n\n	return false;\n}\n\n// ---\n\nfunction goEditImage(id)\n{\n	window.opener.focus();\n\n	if (window.opener.document.article.step.value != \'create\')\n	{\n		document.getElementById(\'yes\').setAttribute(\'href\', window.opener.document.location);\n		showMsg(\'return\');\n	}\n\n	window.opener.document.location = \'?event=image\\u0026step=image_edit\\u0026id=\'+id;\n}\n\n// ---\n\nfunction goBack(url)\n{\n	window.opener.focus();\n\n	done(\'return\');\n\n	window.opener.document.location = url;\n}\n\n// ---\n\nfunction showMsg(id)\n{\n	var obj = document.getElementById(id);\n\n	var width = parseInt(obj.style.width);\n	var height = parseInt(obj.style.height);\n\n	var top = 100 + scrollTop();\n	var left = ( (pageWidth() / 2) - (width / 2) ) + scrollLeft();\n\n	obj.style.position = \'absolute\';\n	obj.style.top = top + \'px\';\n	obj.style.left = left + \'px\';\n	obj.style.display = \'block\';\n\n	if (id != \'return\')\n	{\n		setTimeout(\"done(\'\"+id+\"\')\", 1000);\n	}\n}\n\n// ---\n\nfunction done(id)\n{\n	document.getElementById(id).style.display = \'none\';\n}\n\n// ---\n\nfunction pageWidth()\n{\n	if (self.innerWidth)\n	{\n		return self.innerWidth;\n	}\n\n	else if (document.documentElement && document.documentElement.clientWidth)\n	{\n		return document.documentElement.clientWidth;\n	}\n\n	return 0;\n}\n\n// ---\n\nfunction scrollTop()\n{\n	if (self.pageYOffset)\n	{\n		return self.pageYOffset;\n	}\n\n	else if (document.documentElement && document.documentElement.scrollTop)\n	{\n		return document.documentElement.scrollTop;\n	}\n\n	return 0;\n}\n\n// ---\n\nfunction scrollLeft()\n{\n	if (self.pageYOffset)\n	{\n		return self.pageXOffset;\n	}\n\n	else if (document.documentElement && document.documentElement.scrollTop)\n	{\n		return document.documentElement.scrollLeft;\n	}\n\n	return 0;\n}\n\njs;\n		}\n\n		exit(0);\n	}\n\n// ---\n\n	function upm_img_popper_prefs($event, $step)\n	{\n		global $prefs;\n\n		pagetop(upm_img_popper_gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		if ($step == \'update\')\n		{\n			extract(doSlash(psas(array(\n				\'show_width\', \'show_height\', \'show_alt\', \'show_title\', \'show_class\', \'the_class\', \'custom_form\',\n				\'show_form\', \'admin_popup_width\', \'admin_popup_height\'\n			))));\n\n			$show_width  = ($show_width == \'1\') ? $show_width : \'0\';\n			$show_height = ($show_height == \'1\') ? $show_height : \'0\';\n			$show_alt    = ($show_alt == \'1\') ? $show_alt : \'0\';\n			$show_title  = ($show_title == \'1\') ? $show_title : \'0\';\n			$show_class  = ($show_class == \'1\') ? $show_class : \'0\';\n\n			$the_class   = preg_replace(\"/(^|&\\S+;)|(<[^>]*>)/U\", \'\', dumbDown($the_class));\n			$custom_form = sanitizeForUrl($custom_form);\n\n			if (empty($admin_popup_width))\n			{\n				$admin_popup_width = \'725\';\n			}\n\n			if (empty($admin_popup_height))\n			{\n				$admin_popup_height = \'400\';\n			}\n\n			safe_update(\'txp_prefs\', \"val = \'$show_width\'\", \"name = \'upm_img_popper_show_width\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_height\'\", \"name = \'upm_img_popper_show_height\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_alt\'\", \"name = \'upm_img_popper_show_alt\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_title\'\", \"name = \'upm_img_popper_show_title\'\");\n			safe_update(\'txp_prefs\', \"val = \'$show_class\'\", \"name = \'upm_img_popper_show_class\'\");\n			safe_update(\'txp_prefs\', \"val = \'$the_class\'\", \"name = \'upm_img_popper_the_class\'\");\n			safe_update(\'txp_prefs\', \"val = \'$custom_form\'\", \"name = \'upm_img_popper_custom_form\'\");\n\n			safe_update(\'txp_prefs\', \"val = \'$show_form\'\", \"name = \'upm_img_popper_show_form\'\");\n			safe_update(\'txp_prefs\', \"val = \'$admin_popup_width\'\", \"name = \'upm_img_popper_admin_popup_width\'\");\n			safe_update(\'txp_prefs\', \"val = \'$admin_popup_height\'\", \"name = \'upm_img_popper_admin_popup_height\'\");\n\n			$prefs = get_prefs();\n		}\n\n		echo n.n.\'<div style=\"margin: 3em auto auto auto; width: 20em;\">\'.\n\n		n.n.hed(upm_img_popper_gTxt(\'prefs\'), \'1\').\n\n		n.n.form(\n			n.eInput(\'upm_img_popper_prefs\').\n			n.sInput(\'update\').\n\n			n.n.tag(\n				n.n.tag(upm_img_popper_gTxt(\'public_side\'), \'legend\').\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_width\').br.\n					n.yesnoRadio(\'show_width\', $prefs[\'upm_img_popper_show_width\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_height\').br.\n					n.yesnoRadio(\'show_height\', $prefs[\'upm_img_popper_show_height\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_alt\').br.\n					n.yesnoRadio(\'show_alt\', $prefs[\'upm_img_popper_show_alt\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_title\').br.\n					n.yesnoRadio(\'show_title\', $prefs[\'upm_img_popper_show_title\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_class\').br.\n					n.yesnoRadio(\'show_class\', $prefs[\'upm_img_popper_show_class\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'the_class\').br.\n					n.fInput(\'text\', \'the_class\', $prefs[\'upm_img_popper_the_class\'], \'edit\')\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'custom_form\').br.\n					n.fInput(\'text\', \'custom_form\', $prefs[\'upm_img_popper_custom_form\'], \'edit\')\n				)\n			, \'fieldset\').\n\n			n.n.tag(\n				n.n.tag(upm_img_popper_gTxt(\'admin_side\'), \'legend\').\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'show_form\').br.\n					n.yesnoRadio(\'show_form\', $prefs[\'upm_img_popper_show_form\'])\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'admin_popup_width\').br.\n					n.fInput(\'text\', \'admin_popup_width\', $prefs[\'upm_img_popper_admin_popup_width\'], \'edit\')\n				).\n\n				n.n.graf(\n					upm_img_popper_gTxt(\'admin_popup_height\').br.\n					n.fInput(\'text\', \'admin_popup_height\', $prefs[\'upm_img_popper_admin_popup_height\'], \'edit\')\n				)\n\n			, \'fieldset\').\n\n			n.n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n\n		).n.n.\'</div>\';\n	}\n\n// ---\n\n	function upm_img_popper_install()\n	{\n		global $prefs;\n\n		$updated = false;\n\n		$settings = \"prefs_id = 1, type = 2, event = \'admin\', position = 0\";\n\n		if (!isset($prefs[\'upm_img_popper_show_width\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_width\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_height\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_height\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_alt\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_alt\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_title\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_title\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_class\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_class\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_the_class\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_the_class\',\n				val = \'image\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_custom_form\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_custom_form\',\n				val = \'upm_img_popper\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_show_form\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_show_form\',\n				val = \'1\',\n				html = \'yesnoradio\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_admin_popup_width\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_admin_popup_width\',\n				val = \'725\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if (!isset($prefs[\'upm_img_popper_admin_popup_height\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_img_popper_admin_popup_height\',\n				val = \'400\',\n				html = \'text_input\',\n				$settings\n			\");\n\n			$updated = true;\n		}\n\n		if ($updated)\n		{\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n	}\n\n// ---\n\n	function upm_img_popper_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'admin_popup_width\'     => \'Popup width\',\n			\'admin_popup_height\'    => \'Popup height\',\n			\'admin_side\'            => \'Admin\',\n			\'alt_text_for\'		=> \'Alternate text for\',\n			\'article_image_append\'	=> \'Article image (append)\',\n			\'article_image_replace\' => \'Article image (replace)\',\n			\'assign_class\'		=> \'assign class\',\n			\'built_in\'		=> \'Built-in\',\n			\'caption_for\'		=> \'Caption for\',\n			\'check_leave\'		=> \'You have made changes to the current post which you have not saved. Continue without saving?\',\n			\'confirm_overwrite\'	=> \'You have more than one article image defined. This action will overwrite the entire list. Continue?\',\n			\'custom_form\'		=> \'Custom form\',\n			\'failed\'		=> \'Insert failed.\',\n			\'for\'			=> \'for\',\n			\'image\'			=> \'Image\',\n			\'image_selector\'	=> \'Image Selector\',\n			\'include\'		=> \'include\',\n			\'include_alt\'		=> \'include alt text\',\n			\'include_caption\'	=> \'include caption\',\n			\'include_height\'	=> \'include height\',\n			\'include_width\'		=> \'include width\',\n			\'insert\'		=> \'Insert\',\n			\'insert_image\'		=> \'Insert Image\',\n			\'insert_plugin\'		=> \'Insert (upm_image)\',\n			\'no_thumbnail\'		=> \'Thumbnail not found.\',\n			\'override\'		=> \'overrides all optional parameters\',\n			\'popup\'			=> \'Popup\',\n			\'prefs\'			=> \'upm_img_popper Preferences\',\n			\'public_side\'           => \'Public\',\n			\'return\'		=> \'Return to editing the post?\',\n			\'show_alt\'		=> \'Show alternate text?\',\n			\'show_class\'		=> \'Show class?\',\n			\'show_height\'		=> \'Show height?\',\n			\'show_title\'		=> \'Show caption?\',\n			\'show_width\'		=> \'Show width?\',\n			\'show_form\'		=> \'Show customization options in popup?\',\n			\'success\'		=> \'Success.\',\n			\'the_class\'		=> \'The class\',\n			\'view_all\'		=> \'View All\'\n		);\n\n		return strtr($lang[$what], $atts);\n	}\n\n	function nav_form_upm($event, $bm, $page, $numPages, $sort, $dir, $crit, $search_method, $total=0, $limit=0, $step=\'list\')\n	{\n		global $theme;\n		if ($crit != \'\' && $total > 1)\n		{\n			$out[] = $theme->announce(\n				gTxt(\'showing_search_results\',\n					array(\n						\'{from}\'  => (($page - 1) * $limit) + 1,\n						\'{to}\'    => min($total, $page * $limit),\n						\'{total}\' => $total\n						)\n					)\n				);\n		}\n\n		if ($numPages > 1)\n		{\n			$option_list = array();\n\n			for ($i = 1; $i <= $numPages; $i++)\n			{\n				if ($i == $page)\n				{\n					$option_list[] = \'<option value=\"\'.$i.\'\" selected=\"selected\">\'.\"$i/$numPages\".\'</option>\';\n				}\n\n				else\n				{\n					$option_list[] = \'<option value=\"\'.$i.\'\">\'.\"$i/$numPages\".\'</option>\';\n				}\n			}\n\n			$nav = array();\n\n			$nav[] = ($page > 1) ?\n				PrevNextLink($event, $page - 1, gTxt(\'prev\'), \'prev\', $sort, $dir, $crit, $search_method, $step).sp :\n				tag(gTxt(\'prev\'), \'span\', \' class=\"navlink-disabled\"\').sp;\n\n			$nav[] = \'<select name=\"page\" onchange=\"submit(this.form);\">\';\n			$nav[] = n.join(n, $option_list);\n			$nav[] = n.\'</select>\';\n\n			$nav[] = ($page != $numPages) ?\n				sp.PrevNextLink($event, $page + 1, gTxt(\'next\'), \'next\', $sort, $dir, $crit, $search_method, $step) :\n				sp.tag(gTxt(\'next\'), \'span\', \' class=\"navlink-disabled\"\');\n\n			$out[] = \'<form class=\"nav-form\" method=\"get\" action=\"index.php\">\'.\n				n.eInput($event).\n				n.hInput(\'bm\', $bm).\n				n.sInput($step).\n				( $sort ? n.hInput(\'sort\', $sort).n.hInput(\'dir\', $dir) : \'\' ).\n				( ($crit != \'\') ? n.hInput(\'crit\', $crit).n.hInput(\'search_method\', $search_method) : \'\' ).\n				\'<p class=\"prev-next\">\'.\n				join(\'\', $nav).\n				\'</p>\'.\n				n.tInput().\n				n.\'</form>\';\n		}\n		else\n		{\n			$out[] = graf($page.\'/\'.$numPages, \' class=\"prev-next\"\');\n		}\n\n		return join(n, $out);\n	}', 'cdb53ec4c9057869b1b540967edaaa6e', '', '', 4, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('own_category_tree', 0, 'Dmitry Shovchko', 'http://github.com/dshovchko/own_category_tree', '4.6.2.20161213', 'Creates a hierarchical list of categories', '', '\n### TAG REGISTRATION ###\n########################\n\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n ->register(\'own_category_tree\');\n\n\n### PRIMARY TAG FUNCTIONS ###\n#############################\n\nfunction own_category_tree($atts) {\n	global $pretext;\n\n	extract(lAtts(array(\n		\'start\'		=> \'root\',\n		\'section\'	=> 0,\n		\'type\'		=> \'article\',\n		\'cssid\'		=> \'\',\n		\'onclass\'	=> \'\',\n		\'wraptag\'	=> \'ul\',\n		\'break\'		=> \'li\',\n		\'locale\'	=> \'\',\n	),$atts));\n\n	if ($start == \'*\') $start = $pretext[\'c\'] ? $pretext[\'c\'] : \'root\';\n	if ($start == \'*s\') $start = $GLOBALS[\'s\'];\n	if ($section == \'*\') $section = $GLOBALS[\'s\'];\n	if ($wraptag == \' \') $wraptag = \'\';\n	if ($break == \' \') $break = \'\';\n\n	$vars = compact(\'start\',\'section\',\'type\',\'cssid\',\'onclass\',\'wraptag\',\'break\');\n\n	if ($locale) {\n		setlocale(LC_ALL, $locale);\n	}\n\n	$l = own_category_tree_get_list($type);\n	$r = own_category_tree_make_tree($l, $vars);\n\n	return $r;\n}\n\nfunction own_category_tree_make_tree($list, $vars) {\n	global $pretext;\n\n	$categories = array();\n	if (intval($pretext[\'id\']) == 0) {\n		$categories[] = $pretext[\'c\'];\n	} else {\n		global $thisarticle;\n\n		$categories[] = $thisarticle[\'category1\'];\n		$categories[] = $thisarticle[\'category2\'];\n	}\n\n	$t = own_category_tree_get_branch($list, $vars[\'start\']);\n	$l = 0;\n\n	return own_category_tree_make_subtree($t, $categories, $l, $vars);\n}\n\nfunction own_category_tree_get_branch($list, $from) {\n	$r = \'\';\n\n	$a = array();\n	foreach($list as $v) {\n		if ($v[\'parent\'] == $from) {\n			$a[$v[\'name\']] = $v[\'title\'];\n		}\n	}\n\n	asort($a, SORT_LOCALE_STRING);\n\n	$s = array();\n	foreach($a as $name=>$title) {\n		foreach($list as $v) {\n			if ($v[\'name\'] == $name) {\n				$v[\'children\'] = own_category_tree_get_branch($list, $name);\n				$s[] = $v;\n			}\n		}\n	}\n\n	return $s;\n}\n\nfunction own_category_tree_check_subtree($list, $categories) {\n\n	$l = 0;\n	foreach($list as $v) {\n		if (in_array($v[\'name\'], $categories)) {\n			$l++;\n		}\n		$l += own_category_tree_check_subtree($v[\'children\'], $categories);\n	}\n\n	return $l;\n}\n\nfunction own_category_tree_make_subtree($list, $categories, $l, $vars) {\n\n	$r = \'\';\n	foreach($list as $v) {\n		$rc = \'\';\n		if (own_category_tree_check_subtree($v[\'children\'], $categories) > 0 || in_array($v[\'name\'],$categories)) {\n			$rc = own_category_tree_make_subtree($v[\'children\'], $categories, $l+1, $vars);\n		}\n		$class = $vars[\'onclass\'] && in_array($v[\'name\'],$categories) ? $vars[\'onclass\'] : \'\';\n		$link = own_category_tree_link($v[\'name\'], $vars[\'section\'], $v[\'title\']);\n		$break = $vars[\'break\'];\n		if ($break == \'br\' || $break == \'hr\') {\n			$r .= own_category_tree_tab($l).join(doTag(\'\', $break), array($link, $rc));\n		} else {\n			$r .= own_category_tree_tab($l).doWrap(array($link, $rc), $break, \'\', $class);\n		}\n	}\n\n	$atts = ($vars[\'cssid\'] && $l == 0)?\' id=\"\'.$vars[\'cssid\'].\'\"\':\'\';\n\n	return own_category_tree_tab($l).doWrap(array($r), $vars[\'wraptag\'], \'\', \'\', \'\', $atts);\n}\n\nfunction own_category_tree_get_list($type) {\n	global $own_category_tree_cache;\n\n	if (!isset($own_category_tree_cache)){\n		$own_category_tree_cache = array();\n	}\n\n	if (!isset($own_category_tree_cache[$type])) {\n		$r = safe_rows(\"name,parent,title\", \"txp_category\", \"type=\'$type\' order by id asc\");\n		$own_category_tree_cache[$type] = $r;\n	} else {\n		$r = $own_category_tree_cache[$type];\n	}\n\n	return $r;\n}\n\nfunction own_category_tree_link($category, $section, $title)\n{\n	$cat_link = pagelinkurl(array(\'c\'=>$category, \'s\'=>$section));\n\n	return tag(htmlspecialchars($title), \'a\', \' href=\"\'.$cat_link.\'\" title=\"\'.htmlspecialchars($title).\'\"\');\n}\n\nfunction own_category_tree_tab($l) {\n	return str_repeat(\"\\t\", $l);\n}', '\n### TAG REGISTRATION ###\n########################\n\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n ->register(\'own_category_tree\');\n\n\n### PRIMARY TAG FUNCTIONS ###\n#############################\n\nfunction own_category_tree($atts) {\n	global $pretext;\n\n	extract(lAtts(array(\n		\'start\'		=> \'root\',\n		\'section\'	=> 0,\n		\'type\'		=> \'article\',\n		\'cssid\'		=> \'\',\n		\'onclass\'	=> \'\',\n		\'wraptag\'	=> \'ul\',\n		\'break\'		=> \'li\',\n		\'locale\'	=> \'\',\n	),$atts));\n\n	if ($start == \'*\') $start = $pretext[\'c\'] ? $pretext[\'c\'] : \'root\';\n	if ($start == \'*s\') $start = $GLOBALS[\'s\'];\n	if ($section == \'*\') $section = $GLOBALS[\'s\'];\n	if ($wraptag == \' \') $wraptag = \'\';\n	if ($break == \' \') $break = \'\';\n\n	$vars = compact(\'start\',\'section\',\'type\',\'cssid\',\'onclass\',\'wraptag\',\'break\');\n\n	if ($locale) {\n		setlocale(LC_ALL, $locale);\n	}\n\n	$l = own_category_tree_get_list($type);\n	$r = own_category_tree_make_tree($l, $vars);\n\n	return $r;\n}\n\nfunction own_category_tree_make_tree($list, $vars) {\n	global $pretext;\n\n	$categories = array();\n	if (intval($pretext[\'id\']) == 0) {\n		$categories[] = $pretext[\'c\'];\n	} else {\n		global $thisarticle;\n\n		$categories[] = $thisarticle[\'category1\'];\n		$categories[] = $thisarticle[\'category2\'];\n	}\n\n	$t = own_category_tree_get_branch($list, $vars[\'start\']);\n	$l = 0;\n\n	return own_category_tree_make_subtree($t, $categories, $l, $vars);\n}\n\nfunction own_category_tree_get_branch($list, $from) {\n	$r = \'\';\n\n	$a = array();\n	foreach($list as $v) {\n		if ($v[\'parent\'] == $from) {\n			$a[$v[\'name\']] = $v[\'title\'];\n		}\n	}\n\n	asort($a, SORT_LOCALE_STRING);\n\n	$s = array();\n	foreach($a as $name=>$title) {\n		foreach($list as $v) {\n			if ($v[\'name\'] == $name) {\n				$v[\'children\'] = own_category_tree_get_branch($list, $name);\n				$s[] = $v;\n			}\n		}\n	}\n\n	return $s;\n}\n\nfunction own_category_tree_check_subtree($list, $categories) {\n\n	$l = 0;\n	foreach($list as $v) {\n		if (in_array($v[\'name\'], $categories)) {\n			$l++;\n		}\n		$l += own_category_tree_check_subtree($v[\'children\'], $categories);\n	}\n\n	return $l;\n}\n\nfunction own_category_tree_make_subtree($list, $categories, $l, $vars) {\n\n	$r = \'\';\n	foreach($list as $v) {\n		$rc = \'\';\n		if (own_category_tree_check_subtree($v[\'children\'], $categories) > 0 || in_array($v[\'name\'],$categories)) {\n			$rc = own_category_tree_make_subtree($v[\'children\'], $categories, $l+1, $vars);\n		}\n		$class = $vars[\'onclass\'] && in_array($v[\'name\'],$categories) ? $vars[\'onclass\'] : \'\';\n		$link = own_category_tree_link($v[\'name\'], $vars[\'section\'], $v[\'title\']);\n		$break = $vars[\'break\'];\n		if ($break == \'br\' || $break == \'hr\') {\n			$r .= own_category_tree_tab($l).join(doTag(\'\', $break), array($link, $rc));\n		} else {\n			$r .= own_category_tree_tab($l).doWrap(array($link, $rc), $break, \'\', $class);\n		}\n	}\n\n	$atts = ($vars[\'cssid\'] && $l == 0)?\' id=\"\'.$vars[\'cssid\'].\'\"\':\'\';\n\n	return own_category_tree_tab($l).doWrap(array($r), $vars[\'wraptag\'], \'\', \'\', \'\', $atts);\n}\n\nfunction own_category_tree_get_list($type) {\n	global $own_category_tree_cache;\n\n	if (!isset($own_category_tree_cache)){\n		$own_category_tree_cache = array();\n	}\n\n	if (!isset($own_category_tree_cache[$type])) {\n		$r = safe_rows(\"name,parent,title\", \"txp_category\", \"type=\'$type\' order by id asc\");\n		$own_category_tree_cache[$type] = $r;\n	} else {\n		$r = $own_category_tree_cache[$type];\n	}\n\n	return $r;\n}\n\nfunction own_category_tree_link($category, $section, $title)\n{\n	$cat_link = pagelinkurl(array(\'c\'=>$category, \'s\'=>$section));\n\n	return tag(htmlspecialchars($title), \'a\', \' href=\"\'.$cat_link.\'\" title=\"\'.htmlspecialchars($title).\'\"\');\n}\n\nfunction own_category_tree_tab($l) {\n	return str_repeat(\"\\t\", $l);\n}', 'e463451e7b90fcddaaa0970f39504990', '', '', 1, 5, 0),
('aks_cache', 0, 'makss, Dmitry Shovchko', 'http://github.com/dshovchko/aks_cache', '0.2.7f', 'Partial caching web pages', '<div id=\'aks\'>\n<div style=\'text-align:right;\'><a href=\'?event=aks_cache_tab\'>Plugin preferences</a><br />\n<a href=\"http://textpattern.org.ua/plugins/aks_cache\">Homepage</a></div>\n<h1>aks_cache - partial caching web pages</h1>\n<br />\n    <h2>Summary</h2>\n\n    <p>Caching all inside <code>&lt;txp:aks_cache id=\"unique block id\"&gt;</code> Any content with TxP tags <code>&lt;/txp:aks_cache&gt;</code></p>\n\n    <p>Good practice:\n    </p><ul>\n        <li>Cache some menu elements; recent articles list; last added articles list; any popular lists.</li>\n        <li>Cache <code>cloud</code> tags</li>\n        <li>Cache site <code>main page</code> or cache some ‘slow’ pages with many <span class=\"caps\">TXP</span> tags.</li>\n\n        <li>Cache output some slow or difficult tags.</li>\n    </ul>\n\n    <p>Bad practice:\n    </p><ul>\n        <li>Cache static content without TxP(or php include) tags</li>\n        <li>Full cache every page</li>\n        <li>Cache static <code>body</code> every page</li>\n\n    </ul>\n\n    <h2>Installation</h2>\n\n    <ol>\n        <li>Install this plugin.</li>\n        <li>Go to Extension / <a href=\"?event=aks_cache_tab\">aks_cache Tab</a></li>\n    </ol>\n\n    <p></p>\n\n\n    <h2>Tags</h2>\n    <p><code>&lt;txp:aks_cache&gt;</code> – Uses in page templates, forms, articles.</p>\n    <table>\n        <tbody><tr>\n            <th>attributes</th>\n            <th>default</th>\n            <th>sample</th>\n            <th>description</th>\n        </tr>\n        <tr>\n            <td>id</td>\n            <td><code>[\"REQUEST_URI\"]</code></td>\n            <td>‘’</td>\n            <td>Unique ID for each cache block</td>\n        </tr>\n        <tr>\n            <td>block</td>\n            <td>‘’</td>\n            <td>‘’</td>\n            <td>for using without <code>id</code> attribute</td>\n        </tr>\n        <tr>\n            <td>hour</td>\n            <td>0</td>\n            <td>‘’</td>\n            <td>Cache time = hour*60+min</td>\n        </tr>\n        <tr>\n            <td>min</td>\n            <td>0</td>\n            <td>‘’</td>\n            <td>—//—</td>\n        </tr>\n        <tr>\n            <td>noreset</td>\n            <td>0</td>\n            <td>1</td>\n            <td>No reset cache for this block – ignore option “Reset cache if site was updated”</td>\n        </tr>\n        <tr>\n            <td>disable</td>\n            <td>0</td>\n            <td>1</td>\n            <td>Disable cache</td>\n        </tr>\n        <tr>\n\n            <td>debug</td>\n            <td>0</td>\n            <td>1</td>\n            <td>0 or 1</td>\n        </tr>\n    </tbody></table><br />\n\n    <p><code>&lt;txp:aks_cache_disable /&gt;</code> – Used <strong><span class=\"caps\">ONLY</span></strong> inside <code>&lt;txp:aks_cache&gt;</code> … <code>&lt;/txp:aks_cache&gt;</code> block.</p>\n\n\n    <h2>Example</h2>\n\n<pre>&lt;txp:aks_cache id=\"block1\"&gt;Some content and txp tags...&lt;/txp:aks_cache&gt;\n</pre>\n\n    <p>Cache menu elements per section</p>\n\n<pre>&lt;txp:aks_cache id=\'menu &lt;txp:section /&gt;\'&gt;\n&lt;h3&gt;Tags cloud&lt;/h3&gt;\n&lt;rss_uc_cloud section=\'&lt;txp:section /&gt;\' /&gt;\n&lt;h3&gt;Last added articles&lt;/h3&gt;\n&lt;txp:article_custom form=\"tf_excerpt\" limit=\"20\" section=\'&lt;txp:section /&gt;\' sort=\"LastMod desc\" /&gt;\n&lt;/txp:aks_cache&gt;</pre>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n    background-color: #E3E3DB;\n    border:1px solid;\n    border-color:#ddd #999 #888 #ddd;\n    padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #000000;\nmargin: 1.5em 0;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if(@txpinterface == \'admin\') {\n  add_privs(\'aks_cache_tab\', \'1,2\');\n  register_tab(\"extensions\", \"aks_cache_tab\", \"aks_cache\");\n  register_callback(\"aks_cache_tab\", \"aks_cache_tab\");\n  add_privs(\'plugin_prefs.aks_cache\',\'1,2\');\n  register_callback(\'aks_cache_tab\', \'plugin_prefs.aks_cache\');\n}\n\nfunction aks_cache_disable(){ global $aks_cache_disable; $aks_cache_disable=1; }\n\nfunction _aks_is_really_writable($file)\n{\n	// If we\'re on a Unix server with safe_mode off we call is_writable\n	if (DIRECTORY_SEPARATOR == \'/\' AND @ini_get(\"safe_mode\") == FALSE)\n	{\n		return is_writable($file);\n	}\n\n	// For windows servers and safe_mode \"on\" installations we\'ll actually\n	// write a file then read it.  Bah...\n	if (is_dir($file))\n	{\n		$file = rtrim($file, \'/\').\'/\'.md5(rand(1,100));\n\n		if (($fp = @fopen($file, \'ab\')) === FALSE)\n		{\n			return FALSE;\n		}\n\n		fclose($fp);\n		@chmod($file, 0777);\n		@unlink($file);\n		return TRUE;\n	}\n	elseif (($fp = @fopen($file, \'ab\')) === FALSE)\n	{\n		return FALSE;\n	}\n\n	fclose($fp);\n	return TRUE;\n}\n\nfunction _aks_cache_write($hid, $data, $expire = 0)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return;\n	}\n\n	$cache_path .= $hid;\n\n	if ( ! $fp = @fopen($cache_path, \'wb\'))\n	{\n		echo \"<!--Unable to write cache file: \".$cache_path.\"-->\";\n		return;\n	}\n\n	if ($expire == 0)\n	{\n		$expire = time() + ($prefs[\'aks_cache_time\'] * 60);\n	}\n\n	if (flock($fp, LOCK_EX))\n	{\n		fwrite($fp, $expire.\'TS--->\'.$data);\n		flock($fp, LOCK_UN);\n	}\n	else\n	{\n		echo \"<!--Unable to secure a file lock for file at: \".$cache_path.\"-->\";\n		return;\n	}\n	fclose($fp);\n	@chmod($cache_path, 0777);\n\n}\n\nfunction _aks_cache_read($hid)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return false;\n	}\n\n	$filepath = $cache_path.$hid;\n\n	if ( ! @file_exists($filepath))\n	{\n		return false;\n	}\n\n	if ( ! $fp = @fopen($filepath, \'rb\'))\n	{\n		return false;\n	}\n\n	flock($fp, LOCK_SH);\n\n	$cache = \'\';\n	if (filesize($filepath) > 0)\n	{\n		$cache = fread($fp, filesize($filepath));\n	}\n\n	flock($fp, LOCK_UN);\n	fclose($fp);\n\n	// Strip out the embedded timestamp\n	if ( ! preg_match(\"/(\\d+TS--->)/\", $cache, $match))\n	{\n		return false;\n	}\n\n	// Has the file expired? If so we\'ll delete it.\n	if (time() >= trim(str_replace(\'TS--->\', \'\', $match[\'1\'])))\n	{\n		@unlink($filepath);\n		return false;\n	}\n\n	return str_replace($match[\'0\'], \'\', $cache);\n}\n\nfunction _aks_cache_reset($clear = false)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return;\n	}\n\n	$dirHandle = @opendir($cache_path);\n\n	while( false !== ( $file = @readdir($dirHandle) ) )\n	{\n		if ($clear === false && substr($file, 0, 1) == \'@\')\n		{\n			continue;\n		}\n		if ( $file != \'.\' && $file != \'..\' && $file != \'index.html\')\n		{\n			$tmpPath = $cache_path.$file;\n\n			if ( ! is_dir($tmpPath) )\n			{\n				@unlink($tmpPath);\n			}\n		}\n	}\n\n	closedir($dirHandle);\n}\n\nfunction aks_cache($atts,$thing=\'\') {\n	global $prefs, $aks_cache_disable;\n	extract(lAtts(array(\n		\'block\'		=> \'\',\n		\'id\'		=> $_SERVER[\"REQUEST_URI\"],\n		\'hour\'		=> 0,\n		\'min\'		=> 0,\n		\'noreset\'	=> \'\',\n		\'disable\'	=> 0,\n		\'debug\'		=> ((strpos($prefs[\'aks_cache_opt\'],\'debug\')===false)? 0:1)\n	),$atts));\n\n        $start = getmicrotime();\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'disable\')===false) || $disable){ return parse($thing); }\n	$prr=is_logged_in();\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'dis_adm\')===false)&& $prr[\'privs\']==1 ){ return parse($thing); }\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'dis_users\')===false)&& $prr ){ return parse($thing); }\n\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'reset\')===false)&& ($prefs[\'aks_cache_site_lastmod\']!=$prefs[\'lastmod\']) ){\n		safe_update(\"txp_prefs\", \"val=\'\".$prefs[\'lastmod\'].\"\'\", \"name=\'aks_cache_site_lastmod\'\");\n		if ( strpos($prefs[\'aks_cache_opt\'],\'infile\')===false )\n		{\n			safe_delete(\"aks_cache\",\"infos not like \'noreset-%\'\");\n		}\n		else\n		{\n			_aks_cache_reset();\n		}\n		$prefs[\'aks_cache_site_lastmod\']=$prefs[\'lastmod\'];\n	}\n	if($noreset){ $noreset=\'noreset-\'; }\n	$diff=$hour*60+$min;\n	if(!$diff){\n		$diff=$prefs[\'aks_cache_time\'];\n	}\n	$ttl2=time()+$diff*60;\n	$hash=md5($noreset.$block.$id);\n	$id2=mysql_real_escape_string($noreset.$block.$id);\n	$act=\"from cache\";\n\n	if ( strpos($prefs[\'aks_cache_opt\'],\'infile\')===false )\n	{\n		$rs = safe_row(\"ttl, data\", \"aks_cache\", \"hid=\'$hash\'\");\n\n		if($rs){\n			extract($rs);\n			if($ttl<time() ){\n				$data=parse($thing);\n				$data2=mysql_real_escape_string($data);\n				$act=\"update cache\";\n				safe_update(\"aks_cache\", \"ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\", \"hid=\'$hash\'\");\n			}\n		}else{\n			$data=parse($thing);\n			$data2=mysql_real_escape_string($data);\n			$act=\"insert into cache\";\n			safe_insert(\"aks_cache\", \"hid=\'$hash\', ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\");\n		}\n	}\n	else\n	{\n                if($noreset) {\n		    $hash = \"@\".$hash;\n		}\n		if (($data = _aks_cache_read($hash)) === false){\n			$data=parse($thing);\n			_aks_cache_write($hash, $data, $ttl2);\n			$act=\"insert into cache\";\n		}\n	}\n\n	$time = getmicrotime() - $start;\n	if($debug){return \"<!--txp:aks_cache block=\'$block\' id=\'$id\' action=\'$act\' ttl=\'$diff minutes\' hash=\'$hash\' -->\".$data.\"<!--/txp:aks_cache ($time)-->\"; }\n	return $data;\n}\n\n\nfunction aks_cache_tab($event, $step) {\n	global $prefs;\n\n	$tcheck = getRows(\"SHOW TABLES LIKE \'\".PFX.\"aks_cache\'\");\n	if(!$tcheck) {\n		$qc=\"CREATE TABLE `\".PFX.\"aks_cache` (\";\n		$qc.= <<<EOF\n		`hid` binary(32) NOT NULL default \'\',\n		`ttl` bigint(20) unsigned default \'0\',\n		`data` mediumtext,\n		`infos` varchar(255) default \'\',\n		PRIMARY KEY  (`hid`),\n		KEY `ind_ttl` (`ttl`)\n		) ENGINE=MyISAM DEFAULT CHARSET=utf8;\nEOF;\n		safe_query($qc);\n	}\n\n	if(!isset($prefs[\'aks_cache_time\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_time\', val=\'60\', prefs_id=\'1\'\");\n	}\n	$isnw=false;\n	if(!isset($prefs[\'aks_cache_path\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_path\', val=\'/aks_cache/\', prefs_id=\'1\'\");\n	} else {\n		$cache_path=txpath.$prefs[\'aks_cache_path\'];\n		if ( !is_dir($cache_path) OR !_aks_is_really_writable($cache_path))\n		{\n		    $isnw=true;\n		}\n	}\n	if(!isset($prefs[\'aks_cache_opt\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_opt\',val=\'\', prefs_id=\'1\'\");\n		$qq=\'\';\n	}else {\n		$qq=$prefs[\'aks_cache_opt\'];\n	}\n	if(!isset($prefs[\'aks_cache_site_lastmod\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_site_lastmod\', val=\'\', prefs_id=\'1\'\");\n	}\n\n	safe_delete(\"aks_cache\",\"ttl<UNIX_TIMESTAMP()\");\n\n        if (ps(\"save\")) {\n		pagetop(\"aks_cache Prefs\", \"Preferences Saved\");\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_cache_time\')).\"\'\",\"name = \'aks_cache_time\' and prefs_id =\'1\'\");\n		$path=ps(\'aks_cache_path\');\n		if (substr($path,0,1) != \"/\") $path = \"/\".$path;\n		if (substr($path,-1) != \"/\") $path .= \"/\";\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes($path).\"\'\",\"name = \'aks_cache_path\' and prefs_id =\'1\'\");\n		if(isset($_POST[\'aks_cache_opt\'])){ $opt=implode(\',\',$_POST[\'aks_cache_opt\']); }else{ $opt=\"\"; }\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes($opt).\"\'\",\"name = \'aks_cache_opt\' and prefs_id =\'1\'\");\n		header(\"Location: index.php?event=aks_cache_tab\");\n        } else {\n		pagetop(\"aks_cache Prefs\");\n        }\n	if(ps(\"reset_cache\")){\n		_aks_cache_reset();\n		safe_delete(\"aks_cache\",\"infos not like \'noreset-%\'\");\n	}\n	if(ps(\"clear_cache\")){\n		_aks_cache_reset(true);\n		safe_query(\"truncate table `\".PFX.\"aks_cache`\");\n	}\n	$GLOBALS[\'prefs\'] = get_prefs();\n	$qcount=safe_count(\'aks_cache\',\'1=1\');\n\n        echo \'<table width=\"100%\" border=\"0\"><tr valign=\"top\"><td width=\"35%\">\'.startTable(\'list\').\n        form(\n        tr(tdcs(\"<div style=\'text-align:right;\'>[<a href=\'?event=plugin&step=plugin_help&name=aks_cache\'>Plugin help</a>]</div>\".hed(\"aks_cache Preferences\",1),3)).\n        tr(tda(gTxt(\'Disable aks_cache:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'disable\',((strpos($qq,\'disable\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Debug:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'debug\',((strpos($qq,\'debug\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'<b>Reset cache</b> if site was updated:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'reset\',((strpos($qq,\'reset\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Disable cache for admin:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'dis_adm\',((strpos($qq,\'dis_adm\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Disable cache for users:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'dis_users\',((strpos($qq,\'dis_users\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Cache in file:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'infile\',((strpos($qq,\'infile\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Default cache time in minutes:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_cache_time\",$prefs[\'aks_cache_time\'],\'edit\',\'\',\'\',\'10\'), \' \') .tda(\"\") ).\n        tr(tda(gTxt(\'Cache path:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_cache_path\",$prefs[\'aks_cache_path\'],\'edit\',\'\',\'\',\'20\'), \' \') .tda(\"\") ).\n	(($isnw===true)?tr(tda(gTxt(\'Cache path is not writable!!!\'), \' colspan=\"2\" class=\"not-ok\"\') ):\'\').\n        tr(\n		tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_cache_tab\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\")\n	).\n\n	tr(tda(\'<br /><br /><br />\').tda(\" \")).\n\n	tr(\n            tda(\'Items in cache:\', \' style=\"text-align:right;vertical-align:middle\"\').tda(\"<b>$qcount</b>\")\n	).\n	tr(\n            tda(fInput(\"submit\",\"clear_cache\",\"Clear all cache\",\"publish\")).tda(fInput(\"submit\",\"reset_cache\",\"Reset cache\",\"publish\"))\n	)). endTable();\n\n	$qinfos=safe_column(\"infos\", \"aks_cache\", \"1=1 order by infos asc\");\n	echo \'</td><td><table width=\"98%\"><tr><th>cache block_id</th><th>ttl(minutes)</th><th>size(bytes)</th></tr>\';\n	foreach($qinfos as $qs){ $qs=preg_replace(\'/\\|/\', \'</td><td style=\"text-align:right;\">\', $qs); echo \"<tr><td>$qs</td></tr>\"; }\n	echo \"</table></td></tr></table>\";\n}\n', 'if(@txpinterface == \'admin\') {\n  add_privs(\'aks_cache_tab\', \'1,2\');\n  register_tab(\"extensions\", \"aks_cache_tab\", \"aks_cache\");\n  register_callback(\"aks_cache_tab\", \"aks_cache_tab\");\n  add_privs(\'plugin_prefs.aks_cache\',\'1,2\');\n  register_callback(\'aks_cache_tab\', \'plugin_prefs.aks_cache\');\n}\n\nfunction aks_cache_disable(){ global $aks_cache_disable; $aks_cache_disable=1; }\n\nfunction _aks_is_really_writable($file)\n{\n	// If we\'re on a Unix server with safe_mode off we call is_writable\n	if (DIRECTORY_SEPARATOR == \'/\' AND @ini_get(\"safe_mode\") == FALSE)\n	{\n		return is_writable($file);\n	}\n\n	// For windows servers and safe_mode \"on\" installations we\'ll actually\n	// write a file then read it.  Bah...\n	if (is_dir($file))\n	{\n		$file = rtrim($file, \'/\').\'/\'.md5(rand(1,100));\n\n		if (($fp = @fopen($file, \'ab\')) === FALSE)\n		{\n			return FALSE;\n		}\n\n		fclose($fp);\n		@chmod($file, 0777);\n		@unlink($file);\n		return TRUE;\n	}\n	elseif (($fp = @fopen($file, \'ab\')) === FALSE)\n	{\n		return FALSE;\n	}\n\n	fclose($fp);\n	return TRUE;\n}\n\nfunction _aks_cache_write($hid, $data, $expire = 0)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return;\n	}\n\n	$cache_path .= $hid;\n\n	if ( ! $fp = @fopen($cache_path, \'wb\'))\n	{\n		echo \"<!--Unable to write cache file: \".$cache_path.\"-->\";\n		return;\n	}\n\n	if ($expire == 0)\n	{\n		$expire = time() + ($prefs[\'aks_cache_time\'] * 60);\n	}\n\n	if (flock($fp, LOCK_EX))\n	{\n		fwrite($fp, $expire.\'TS--->\'.$data);\n		flock($fp, LOCK_UN);\n	}\n	else\n	{\n		echo \"<!--Unable to secure a file lock for file at: \".$cache_path.\"-->\";\n		return;\n	}\n	fclose($fp);\n	@chmod($cache_path, 0777);\n\n}\n\nfunction _aks_cache_read($hid)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return false;\n	}\n\n	$filepath = $cache_path.$hid;\n\n	if ( ! @file_exists($filepath))\n	{\n		return false;\n	}\n\n	if ( ! $fp = @fopen($filepath, \'rb\'))\n	{\n		return false;\n	}\n\n	flock($fp, LOCK_SH);\n\n	$cache = \'\';\n	if (filesize($filepath) > 0)\n	{\n		$cache = fread($fp, filesize($filepath));\n	}\n\n	flock($fp, LOCK_UN);\n	fclose($fp);\n\n	// Strip out the embedded timestamp\n	if ( ! preg_match(\"/(\\d+TS--->)/\", $cache, $match))\n	{\n		return false;\n	}\n\n	// Has the file expired? If so we\'ll delete it.\n	if (time() >= trim(str_replace(\'TS--->\', \'\', $match[\'1\'])))\n	{\n		@unlink($filepath);\n		return false;\n	}\n\n	return str_replace($match[\'0\'], \'\', $cache);\n}\n\nfunction _aks_cache_reset($clear = false)\n{\n	global $prefs;\n\n	$cache_path = txpath.$prefs[\'aks_cache_path\'];\n\n	if ( ! is_dir($cache_path) OR ! _aks_is_really_writable($cache_path))\n	{\n		return;\n	}\n\n	$dirHandle = @opendir($cache_path);\n\n	while( false !== ( $file = @readdir($dirHandle) ) )\n	{\n		if ($clear === false && substr($file, 0, 1) == \'@\')\n		{\n			continue;\n		}\n		if ( $file != \'.\' && $file != \'..\' && $file != \'index.html\')\n		{\n			$tmpPath = $cache_path.$file;\n\n			if ( ! is_dir($tmpPath) )\n			{\n				@unlink($tmpPath);\n			}\n		}\n	}\n\n	closedir($dirHandle);\n}\n\nfunction aks_cache($atts,$thing=\'\') {\n	global $prefs, $aks_cache_disable;\n	extract(lAtts(array(\n		\'block\'		=> \'\',\n		\'id\'		=> $_SERVER[\"REQUEST_URI\"],\n		\'hour\'		=> 0,\n		\'min\'		=> 0,\n		\'noreset\'	=> \'\',\n		\'disable\'	=> 0,\n		\'debug\'		=> ((strpos($prefs[\'aks_cache_opt\'],\'debug\')===false)? 0:1)\n	),$atts));\n\n        $start = getmicrotime();\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'disable\')===false) || $disable){ return parse($thing); }\n	$prr=is_logged_in();\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'dis_adm\')===false)&& $prr[\'privs\']==1 ){ return parse($thing); }\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'dis_users\')===false)&& $prr ){ return parse($thing); }\n\n	if( !(strpos($prefs[\'aks_cache_opt\'],\'reset\')===false)&& ($prefs[\'aks_cache_site_lastmod\']!=$prefs[\'lastmod\']) ){\n		safe_update(\"txp_prefs\", \"val=\'\".$prefs[\'lastmod\'].\"\'\", \"name=\'aks_cache_site_lastmod\'\");\n		if ( strpos($prefs[\'aks_cache_opt\'],\'infile\')===false )\n		{\n			safe_delete(\"aks_cache\",\"infos not like \'noreset-%\'\");\n		}\n		else\n		{\n			_aks_cache_reset();\n		}\n		$prefs[\'aks_cache_site_lastmod\']=$prefs[\'lastmod\'];\n	}\n	if($noreset){ $noreset=\'noreset-\'; }\n	$diff=$hour*60+$min;\n	if(!$diff){\n		$diff=$prefs[\'aks_cache_time\'];\n	}\n	$ttl2=time()+$diff*60;\n	$hash=md5($noreset.$block.$id);\n	$id2=mysql_real_escape_string($noreset.$block.$id);\n	$act=\"from cache\";\n\n	if ( strpos($prefs[\'aks_cache_opt\'],\'infile\')===false )\n	{\n		$rs = safe_row(\"ttl, data\", \"aks_cache\", \"hid=\'$hash\'\");\n\n		if($rs){\n			extract($rs);\n			if($ttl<time() ){\n				$data=parse($thing);\n				$data2=mysql_real_escape_string($data);\n				$act=\"update cache\";\n				safe_update(\"aks_cache\", \"ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\", \"hid=\'$hash\'\");\n			}\n		}else{\n			$data=parse($thing);\n			$data2=mysql_real_escape_string($data);\n			$act=\"insert into cache\";\n			safe_insert(\"aks_cache\", \"hid=\'$hash\', ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\");\n		}\n	}\n	else\n	{\n                if($noreset) {\n		    $hash = \"@\".$hash;\n		}\n		if (($data = _aks_cache_read($hash)) === false){\n			$data=parse($thing);\n			_aks_cache_write($hash, $data, $ttl2);\n			$act=\"insert into cache\";\n		}\n	}\n\n	$time = getmicrotime() - $start;\n	if($debug){return \"<!--txp:aks_cache block=\'$block\' id=\'$id\' action=\'$act\' ttl=\'$diff minutes\' hash=\'$hash\' -->\".$data.\"<!--/txp:aks_cache ($time)-->\"; }\n	return $data;\n}\n\n\nfunction aks_cache_tab($event, $step) {\n	global $prefs;\n\n	$tcheck = getRows(\"SHOW TABLES LIKE \'\".PFX.\"aks_cache\'\");\n	if(!$tcheck) {\n		$qc=\"CREATE TABLE `\".PFX.\"aks_cache` (\";\n		$qc.= <<<EOF\n		`hid` binary(32) NOT NULL default \'\',\n		`ttl` bigint(20) unsigned default \'0\',\n		`data` mediumtext,\n		`infos` varchar(255) default \'\',\n		PRIMARY KEY  (`hid`),\n		KEY `ind_ttl` (`ttl`)\n		) ENGINE=MyISAM DEFAULT CHARSET=utf8;\nEOF;\n		safe_query($qc);\n	}\n\n	if(!isset($prefs[\'aks_cache_time\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_time\', val=\'60\', prefs_id=\'1\'\");\n	}\n	$isnw=false;\n	if(!isset($prefs[\'aks_cache_path\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_path\', val=\'/aks_cache/\', prefs_id=\'1\'\");\n	} else {\n		$cache_path=txpath.$prefs[\'aks_cache_path\'];\n		if ( !is_dir($cache_path) OR !_aks_is_really_writable($cache_path))\n		{\n		    $isnw=true;\n		}\n	}\n	if(!isset($prefs[\'aks_cache_opt\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_opt\',val=\'\', prefs_id=\'1\'\");\n		$qq=\'\';\n	}else {\n		$qq=$prefs[\'aks_cache_opt\'];\n	}\n	if(!isset($prefs[\'aks_cache_site_lastmod\'])){\n		$rs = safe_insert(\'txp_prefs\', \"name=\'aks_cache_site_lastmod\', val=\'\', prefs_id=\'1\'\");\n	}\n\n	safe_delete(\"aks_cache\",\"ttl<UNIX_TIMESTAMP()\");\n\n        if (ps(\"save\")) {\n		pagetop(\"aks_cache Prefs\", \"Preferences Saved\");\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_cache_time\')).\"\'\",\"name = \'aks_cache_time\' and prefs_id =\'1\'\");\n		$path=ps(\'aks_cache_path\');\n		if (substr($path,0,1) != \"/\") $path = \"/\".$path;\n		if (substr($path,-1) != \"/\") $path .= \"/\";\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes($path).\"\'\",\"name = \'aks_cache_path\' and prefs_id =\'1\'\");\n		if(isset($_POST[\'aks_cache_opt\'])){ $opt=implode(\',\',$_POST[\'aks_cache_opt\']); }else{ $opt=\"\"; }\n		safe_update(\"txp_prefs\", \"val = \'\".addslashes($opt).\"\'\",\"name = \'aks_cache_opt\' and prefs_id =\'1\'\");\n		header(\"Location: index.php?event=aks_cache_tab\");\n        } else {\n		pagetop(\"aks_cache Prefs\");\n        }\n	if(ps(\"reset_cache\")){\n		_aks_cache_reset();\n		safe_delete(\"aks_cache\",\"infos not like \'noreset-%\'\");\n	}\n	if(ps(\"clear_cache\")){\n		_aks_cache_reset(true);\n		safe_query(\"truncate table `\".PFX.\"aks_cache`\");\n	}\n	$GLOBALS[\'prefs\'] = get_prefs();\n	$qcount=safe_count(\'aks_cache\',\'1=1\');\n\n        echo \'<table width=\"100%\" border=\"0\"><tr valign=\"top\"><td width=\"35%\">\'.startTable(\'list\').\n        form(\n        tr(tdcs(\"<div style=\'text-align:right;\'>[<a href=\'?event=plugin&step=plugin_help&name=aks_cache\'>Plugin help</a>]</div>\".hed(\"aks_cache Preferences\",1),3)).\n        tr(tda(gTxt(\'Disable aks_cache:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'disable\',((strpos($qq,\'disable\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Debug:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'debug\',((strpos($qq,\'debug\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'<b>Reset cache</b> if site was updated:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'reset\',((strpos($qq,\'reset\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Disable cache for admin:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'dis_adm\',((strpos($qq,\'dis_adm\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Disable cache for users:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'dis_users\',((strpos($qq,\'dis_users\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Cache in file:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(checkbox(\'aks_cache_opt[]\',\'infile\',((strpos($qq,\'infile\')===false)? 0:1) ), \' \') ).\n        tr(tda(gTxt(\'Default cache time in minutes:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_cache_time\",$prefs[\'aks_cache_time\'],\'edit\',\'\',\'\',\'10\'), \' \') .tda(\"\") ).\n        tr(tda(gTxt(\'Cache path:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_cache_path\",$prefs[\'aks_cache_path\'],\'edit\',\'\',\'\',\'20\'), \' \') .tda(\"\") ).\n	(($isnw===true)?tr(tda(gTxt(\'Cache path is not writable!!!\'), \' colspan=\"2\" class=\"not-ok\"\') ):\'\').\n        tr(\n		tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_cache_tab\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\")\n	).\n\n	tr(tda(\'<br /><br /><br />\').tda(\" \")).\n\n	tr(\n            tda(\'Items in cache:\', \' style=\"text-align:right;vertical-align:middle\"\').tda(\"<b>$qcount</b>\")\n	).\n	tr(\n            tda(fInput(\"submit\",\"clear_cache\",\"Clear all cache\",\"publish\")).tda(fInput(\"submit\",\"reset_cache\",\"Reset cache\",\"publish\"))\n	)). endTable();\n\n	$qinfos=safe_column(\"infos\", \"aks_cache\", \"1=1 order by infos asc\");\n	echo \'</td><td><table width=\"98%\"><tr><th>cache block_id</th><th>ttl(minutes)</th><th>size(bytes)</th></tr>\';\n	foreach($qinfos as $qs){ $qs=preg_replace(\'/\\|/\', \'</td><td style=\"text-align:right;\">\', $qs); echo \"<tr><td>$qs</td></tr>\"; }\n	echo \"</table></td></tr></table>\";\n}\n', '2992cf92b43cd6cd3b9c256ab1f22bc1', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('tru_tags', 0, 'Nathan Arthur', 'http://www.rainskit.com/', '3.9', 'Article tagging', '<p>To learn more about tru_tags, check out the <a href=\"http://www.rainskit.com/blog/493/trutags-a-tagging-plugin-for-textpattern\" rel=\"nofollow\">introductory article</a>, <a href=\"http://www.rainskit.com/reference/537/tru_tags-releases\" rel=\"nofollow\">releases page</a>, <a href=\"http://www.rainskit.com/reference/495/trutags-feature-list\" rel=\"nofollow\">feature list</a>, <a href=\"http://www.rainskit.com/reference/554/tru_tags-roadmap\" rel=\"nofollow\">roadmap</a>, and <a href=\"http://www.rainskit.com/reference/497/trutags-usage-instructions\" rel=\"nofollow\">usage instructions</a>.</p>\n\n<p>You can also find the source code at <a href=\"https://github.com/truist/tru-tags\" rel=\"nofollow\">github.com/truist/tru-tags</a>.</p>\n\n<p>I&#8217;ve taken the detailed help out of the plugin; my apologies.  It was too big and too difficult to keep maintaining on my site and in the plugin.  If, my site is ever down, however, the source <span class=\"caps\">HTML</span> of the usage instructions is available from the <a href=\"https://github.com/truist/tru-tags/blob/master/tru_tags-docs.html\" rel=\"nofollow\">github repo</a></p>', '\n# Copyright 2017 Nathan Arthur\n# Released under the GNU Public License, see http://www.opensource.org/licenses/gpl-license.php for details\n#\n# This work was originally inspired by ran_tags by Ran Aroussi, originally found at http://aroussi.com/article/45/tagging-textpattern\n# Javascript bug fixes were provided by the_ghost - http://victorus.net\n# Javascript code for the tag auto-complete feature was provided by Jim Biancolo - http://www.biancolo.com/\n# Fixes for Textpattern 4.6 provided by:\n#   - Rimas Kudelis; https://github.com/rimas-kudelis\n#   - jools-r; https://github.com/jools-r\n#   - Simon Finch; https://github.com/spiffin\n\n\n### CONFIGURATION ###\n#####################\n\n# TURN BACK!  Configuration is no longer handled by editing the plugin.\n# Check out the \'Extensions\' tab in the Textpattern admin :)\n\n\n# Changing these won\'t do any good.  They\'re just here as a convenience for development.\ndefine(\'TRU_TAGS_FIELD\', \'Keywords\');\ndefine(\'ENCODING\', \'utf-8\');\n\ndefine(\'CLEAN_URLS\', \'clean_urls\');\ndefine(\'TAG_SECTION\', \'tag_section\');\ndefine(\'PARM_NAME\', \'parm\');\ndefine(\'TAGS_IN_FEED_CATEGORIES\', \'tags_in_feed_categories\');\ndefine(\'TAGS_IN_FEED_BODY\', \'tags_in_feed_body\');\ndefine(\'TAGS_IN_WRITE_TAB\', \'tags_in_write_tab\');\ndefine(\'AUTOCOMPLETE_IN_WRITE_TAB\', \'autocomplete_in_write_tab\');\ndefine(\'UTF_8_CASE\', \'utf_8_case\');\n\nglobal $tru_tags_prefs;\n$tru_tags_prefs = tru_tags_load_prefs();\n\n\n### TAG REGISTRATION ###\n########################\n\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n	->register(\'tru_tags_handler\')\n	->register(\'tru_tags_if_has_tags\')\n	->register(\'tru_tags_from_article\')\n	->register(\'tru_tags_cloud\')\n	->register(\'tru_tags_list\')\n	->register(\'tru_tags_if_tag_search\')\n	->register(\'tru_tags_tag_parameter\')\n	->register(\'tru_tags_search_result_excerpt\')\n	->register(\'tru_tags_search_parameter\')\n	->register(\'tru_tags_related_tags_from_search\')\n	->register(\'tru_tags_archive\')\n	->register(\'tru_tags_current_archive_tag\');\n\n### PRIMARY TAG FUNCTIONS ###\n#############################\n\nfunction tru_tags_handler($atts) {\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	if (!empty($tag_parameter)) {\n		if (tru_tags_redirect_if_needed($tag_parameter)) {\n			return \'\';\n		}\n		$clean_atts = tru_tags_fixup_query_atts($atts, $tag_parameter);\n		$result = doArticles($clean_atts, true);		#function in TXP code\n		if (trim($result) == \'\') {\n			if (isset($atts[\'noarticles\'])) {\n				tru_tags_redirect($atts[\'noarticles\'], true);\n			} else if (isset($atts[\'404redirect\']) && !$atts[\'404redirect\']) {\n				return $result;\n			} else {\n				txp_die(gTxt(\'404_not_found\'), \'404\');\n			}\n		} else {\n			return $result;\n		}\n	} else {\n		return tru_tags_cloud($atts);\n	}\n}\n\n\nfunction tru_tags_archive($atts) {\n	global $tru_tags_current_archive_tag;\n	$tags = array_unique(tru_tags_cloud_query(tru_tags_get_standard_cloud_atts($atts, false, false)));\n	natcasesort($tags);\n	foreach ($tags as $tag) {\n		$tru_tags_current_archive_tag = $tag;\n		$clean_atts = tru_tags_fixup_query_atts($atts, $tag);\n		$results[] = doArticles($clean_atts, true);		#function in TXP code\n	}\n	return join(\' \', $results);\n}\n\n\nfunction tru_tags_current_archive_tag($atts) {\n	global $tru_tags_current_archive_tag;\n	extract(lAtts(array(\'link\' => \'0\'),  $atts, 0));\n	if ($link) {\n		return \'<a href=\"\' . tru_tags_linkify_tag($tru_tags_current_archive_tag) . \'\">\' . $tru_tags_current_archive_tag . \'</a>\';\n	} else {\n		return $tru_tags_current_archive_tag;\n	}\n}\n\n\nfunction tru_tags_cloud($atts) {\n	return tru_tags_list(tru_tags_get_standard_cloud_atts($atts, false, false));\n}\n\n\nfunction tru_tags_list($atts) {\n	$atts = tru_tags_get_standard_cloud_atts($atts, true, false);\n	$all_tags = tru_tags_cloud_query($atts);\n\n	return tru_tags_render_cloud($atts, $all_tags, $all_tags);\n}\n\n\nfunction tru_tags_from_article($atts) {\n	global $thisarticle;\n	extract($thisarticle);\n\n	$all_tags = tru_tags_get_tags_for_article($thisid);\n\n	$atts = tru_tags_get_standard_cloud_atts($atts, false, true);\n	$all_tags_for_weight = $all_tags;\n	if ($atts[\'useoverallcounts\']) {\n		$all_tags_for_weight = tru_tags_cloud_query($atts);\n	}\n\n	return tru_tags_render_cloud($atts, $all_tags, $all_tags_for_weight);\n}\n\n\nfunction tru_tags_if_has_tags($atts, $thing) {\n	global $thisarticle;\n	extract($thisarticle);\n\n	$tags_field = TRU_TAGS_FIELD;\n\n	$rs = safe_row($tags_field, \"textpattern\", \"ID=\'$thisid\' AND $tags_field <> \'\'\");\n	return parse(EvalElse($thing, $rs));\n}\n\n\nfunction tru_tags_if_tag_search($atts, $thing) {\n	extract(lAtts(array(\'tag\' => \'\'),  $atts, 0));\n\n	$tag_parameter = tru_tags_tag_parameter(array(\'striphyphens\' => 1), false);\n	$condition = (!empty($tag_parameter)) ? true : false;\n	if ($condition && !empty($tag)) {\n		$condition = ($tag_parameter == $tag);\n	}\n	return parse(EvalElse($thing, $condition));\n}\n\n\nfunction tru_tags_tag_parameter($atts, $safehtml = true) {\n	global $tru_tags_prefs;\n	extract(lAtts(array(\'striphyphens\' => \'0\', \'urlencode\' => 0),  $atts, 0));\n\n	$parm = urldecode(strip_tags(gps($tru_tags_prefs[PARM_NAME]->value)));\n	if (\'lookup\' == $striphyphens) {\n		$atts = tru_tags_get_standard_cloud_atts(array(), false, false);\n		$tag_list = array_unique(tru_tags_cloud_query($atts));\n		foreach ($tag_list as $cloud_tag) {\n			if ($parm == str_replace(\' \', \'-\', $cloud_tag)) {\n				$parm = $cloud_tag;\n				break;\n			}\n		}\n	} else if ($striphyphens) {\n		$parm = str_replace(\'-\', \' \', $parm);\n	}\n	if ($urlencode) {\n		$parm = urlencode($parm);\n	 } else if ($safehtml) {\n		$parm = htmlspecialchars($parm);\n	}\n\n	return $parm;\n}\n\n\nfunction tru_tags_search_result_excerpt($atts) {\n	global $pretext;\n\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	if (trim($tag_parameter) != \'\') {\n		$q = $pretext[\'q\'];\n		$pretext[\'q\'] = $tag_parameter;\n		$result = search_result_excerpt($atts);\n		$pretext[\'q\'] = $q;\n	} else {\n		$result = search_result_excerpt($atts);\n	}\n	return $result;\n}\n\n\nfunction tru_tags_search_parameter() {\n	trigger_error(gTxt(\'deprecated_tag\'), E_USER_NOTICE);\n	return strip_tags(gps(\'q\'));\n}\n\n\nfunction tru_tags_related_tags_from_search($atts) {\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	extract(lAtts(array(\'tag_parameter\' => $tag_parameter),  $atts, 0));\n	if (!empty($tag_parameter)) {\n        $tags_field = TRU_TAGS_FIELD;\n		$all_tags = array();\n\n		$query_atts = tru_tags_fixup_query_atts($atts, $tag_parameter);\n		$rs = tru_tags_redo_article_search($query_atts);\n		if ($rs) {\n			while ($a = nextRow($rs)) {\n				$article_tags = array();\n				if (isset($a[$tags_field])) {\n					$article_tags = explode(\",\", trim(tru_tags_strtolower($a[$tags_field])));\n				}\n				# faster implementation of array_merge(), thanks to whocarez\n				# (http://forum.textpattern.com/viewtopic.php?pid=238373#p238373)\n				foreach(tru_tags_trim_tags($article_tags) as $i) {$all_tags[] = $i;}\n			}\n		}\n\n		$alt_tag_parameter = str_replace(\'-\', \' \', $tag_parameter);\n		foreach ($all_tags as $key => $tag) {\n			if ($tag == $tag_parameter || $tag == $alt_tag_parameter) {\n				unset($all_tags[$key]);\n			}\n		}\n\n		$cloud_atts = tru_tags_get_standard_cloud_atts($atts, false, false);\n		$all_tags_for_weight = $all_tags;\n		if ($cloud_atts[\'useoverallcounts\']) {\n			$all_tags_for_weight = tru_tags_cloud_query($cloud_atts);\n		}\n\n		return tru_tags_render_cloud($cloud_atts, $all_tags, $all_tags_for_weight);\n	} else {\n		return \'\';\n	}\n}\n\n\n### CLOUD SUPPORT FUNCTIONS ###\n###############################\n\nfunction tru_tags_get_standard_cloud_atts($atts, $isList, $isArticle, $showAll=false) {\n	return lAtts(array(\'wraptag\'	=> ($isList ? \'ul\' : \'\'),\n			\'break\'		=> ($isList ? \'li\' : \', \'),\n			\'class\'		=> \'\',\n			\'breakclass\'	=> \'\',\n			\'section\'	=> \'\',\n			\'minpercent\'	=> \'100\',\n			\'maxpercent\'	=> ($isList || $isArticle ? \'100\' : \'200\'),\n			\'showcounts\'	=> \'\',\n			\'countwrapchars\'	=> \'[]\',\n			\'usereltag\'	=> ($isArticle ? \'1\' : \'\'),\n			\'generatelinks\'	=> \'1\',\n			\'mintagcount\'	=> \'0\',\n			\'maxtagcount\'	=> \'1000\',\n			\'setsizes\'	=> ($isArticle ? \'0\' : \'1\'),\n			\'usenofollow\'	=> \'\',\n			\'sort\'		=> \'alpha\',\n			\'useoverallcounts\'	=> \'\',\n			\'setclasses\'	=> ($isArticle ? \'0\' : \'1\'),\n			\'title\'		=> \'\',\n			\'listlimit\'	=> \'\',\n			\'keep\'		=> \'largest\',\n			\'cutoff\'	=> \'chunk\',\n			\'texttransform\'	=> \'none\',\n			\'linkpath\'	=> \'\',\n			\'linkpathtail\'	=> \'\',\n			\'filtersearch\'	=> ($showAll ? 0 : \'1\'),\n			\'excludesection\'=> \'\',\n			\'activeclass\'	=> \'tagActive\',\n			\'time\'		=> ($showAll ? \'any\' : \'past\')\n		),$atts, 0);\n}\n\n\nfunction tru_tags_cloud_query($atts) {\n	extract($atts);\n\n	$section_clause = \'\';\n	if ($section <> \'\') {\n		$keys = explode(\',\', $section);\n		foreach ($keys as $key) {\n			$keyparts[] = \" Section = \'\" . trim($key) . \"\'\";\n		}\n		$section_clause = \" AND (\" . join(\' or \', $keyparts) . \")\";\n	}\n\n	$tags_field = TRU_TAGS_FIELD;\n	include_once txpath.\'/publish/search.php\';\n\n	$filter = tru_tags_filter_sections($excludesection);\n	$filter .= ($filtersearch ? filterSearch() : \'\');\n\n	switch ($time) {\n		case \'any\':\n			$time = \"\"; break;\n		case \'future\':\n			$time = \" and Posted > now()\"; break;\n		default:\n			$time = \" and Posted <= now()\";\n	}\n	global $prefs;\n	extract($prefs);\n	if (!$publish_expired_articles) {\n		$time .= \" and (now() <= Expires or Expires IS NULL)\";\n	}\n\n	$all_tags = array();\n	$rs = safe_rows(\"$tags_field\", \"textpattern\", \"$tags_field <> \'\'\" . $section_clause . $filter . \" and Status >= \'4\'\" . $time);\n	foreach ($rs as $row) {\n		$temp_array = array();\n		if (isset($row[$tags_field])) {\n			$temp_array = explode(\",\", trim(tru_tags_strtolower($row[$tags_field])));\n		}\n		# faster implementation of array_merge(), thanks to whocarez\n		# (http://forum.textpattern.com/viewtopic.php?pid=238373#p238373)\n		foreach(tru_tags_trim_tags($temp_array) as $i) {$all_tags[] = $i;}\n	}\n\n	return $all_tags;\n}\n\n\nfunction tru_tags_filter_sections($excludesection) {\n	$sections = explode(\',\', $excludesection);\n	$filters = array();\n	foreach ($sections as $section) {\n		$filters[] = \"and Section != \'\".doSlash($section).\"\'\";\n	}\n	return join(\' \', $filters);\n}\n\n\nfunction tru_tags_render_cloud($atts, $all_tags, $all_tags_for_weight) {\n	global $tru_tags_prefs;\n	extract($atts);\n\n	$tags_weight = array_count_values($all_tags_for_weight);\n\n	foreach ($tags_weight as $tag => $weight) {\n		if (!in_array($tag, $all_tags)\n		   || $tags_weight[$tag] < $mintagcount\n		   || $tags_weight[$tag] > $maxtagcount) {\n			unset($tags_weight[$tag]);\n		}\n	}\n\n	$sort_by_count = (strpos($sort, \'count\') !== false);\n	$sort_ascending = (strpos($sort, \'asc\') !== false);\n	$tags_weight = tru_tags_sort_tags($tags_weight, $sort_by_count, $sort_ascending);\n\n	if ($listlimit > 0 && $listlimit < count($tags_weight)) {\n		$resorted_tags = array();\n		if ($keep == \'largest\') {\n			$resorted_tags = array_keys(tru_tags_sort_tags($tags_weight, true, false));\n		} else {\n			if ($keep == \'random\') {\n				foreach ($tags_weight as $tag => $weight) {\n					$resorted_tags[$tag] = rand(0, $weight);\n				}\n				$resorted_tags = array_keys(tru_tags_sort_tags($resorted_tags, true, false));\n			} else if ($keep == \'alpha\') {\n				$resorted_tags = array_keys($tags_weight);\n				natcasesort($resorted_tags);\n			}\n			$cutoff = \'exact\';\n		}\n\n		$last_good_index = $listlimit - 1;\n		if ($cutoff == \'chunk\') { //alternative is \'exact\'\n			$last_weight = -1;\n			for ($i = 0; $i < $listlimit + 1; $i++) {\n				$new_weight = $tags_weight[$resorted_tags[$i]];\n				if ($new_weight != $last_weight) {\n					$last_good_index = $i - 1;\n					$last_weight = $new_weight;\n				}\n			}\n			if ($last_good_index < 0) {\n				$last_good_index = $listlimit - 1;\n			}\n		}\n\n		$resorted_tags = array_chunk($resorted_tags, $last_good_index + 1);\n		$resorted_tags = $resorted_tags[0];\n\n		foreach ($tags_weight as $tag => $weight) {\n			if (!in_array($tag, $resorted_tags)) {\n				unset($tags_weight[$tag]);\n			}\n		}\n	}\n\n	if ($generatelinks) {\n		if ($linkpath) {\n			$urlprefix = $linkpath;\n			$urlsuffix = $linkpathtail;\n		} else {\n			if (tru_tags_clean_urls()) {\n				$urlprefix = hu . $tru_tags_prefs[TAG_SECTION]->value . \'/\';\n			} else {\n				$urlprefix = hu . \'?s=\' . $tru_tags_prefs[TAG_SECTION]->value . \'&amp;\' . $tru_tags_prefs[PARM_NAME]->value . \'=\';\n			}\n			$urlsuffix = (tru_tags_clean_urls() ? \'/\' : \'\');\n		}\n\n		if ($usereltag) {\n			if ($usenofollow) {\n				$urlsuffix .= \'\" rel=\"tag nofollow\';\n			} else {\n				$urlsuffix .= \'\" rel=\"tag\';\n			}\n		} else if ($usenofollow) {\n			$urlsuffix .= \'\" rel=\"nofollow\';\n		}\n	}\n\n	if (count($tags_weight) > 0) {\n		$max = max($tags_weight);\n		$min = min($tags_weight);\n	} else {\n		$max = $min = 0;\n	}\n	$stepvalue = ($max == $min) ? 0 : ($maxpercent - $minpercent) / ($max - $min);\n\n	$tags_html = array();\n	$tag_search_tag = tru_tags_tag_parameter(array(\'striphyphens\' => \'lookup\'));\n	$tag_search_tag = function_exists(\"htmlspecialchars_decode\") ? htmlspecialchars_decode($tag_search_tag) : html_entity_decode($tag_search_tag);\n	foreach ($tags_weight as $tag => $weight) {\n		$tag_weight = floor($minpercent + ($weight - $min) * $stepvalue);\n\n		$style = \'\';\n		if ($setsizes)\n			$style = \' style=\"font-size: \' . $tag_weight . \'%;\"\';\n\n		$tag_class = \'\';\n		if ($setclasses) {\n			$tag_class = \' class=\"\';\n			if ($weight == $min) {\n				$tag_class .= \"tagSizeSmallest\";\n			} else if ($weight == $max) {\n				$tag_class .= \"tagSizeLargest\";\n			} else {\n				$tag_class .= \"tagSizeMedium\";\n			}\n			$tag_class .= \' tagSize\' . ($weight + 1 - $min);\n			if ($tag == $tag_search_tag) {\n				$tag_class .= \' \' . $activeclass;\n			}\n			$tag_class .= \'\"\';\n		}\n\n		//adapted from code by gdtroiano, see http://forum.textpattern.com/viewtopic.php?pid=102875#p102875\n		$titlecount = \'\';\n		if ($title)\n			$titlecount = \' title=\"\' . $title . \'\"\';\n		$displaycount= \'\';\n		$count = $countwrapchars{0} . $weight . $countwrapchars{1};\n		if ($showcounts == \'title\' || $showcounts == \'both\')\n			$titlecount = \' title=\"\' . $title . $count . \'\"\';\n		if ($showcounts && $showcounts != \'title\')\n			$displaycount = \' \' . $count;\n\n		if ($texttransform == \'capitalize\') {\n			$tag = tru_tags_ucwords($tag);\n		} else if ($texttransform == \'uppercase\') {\n			$tag = tru_tags_strtoupper($tag);\n		} else if ($texttransform == \'lowercase\') {\n			$tag = tru_tags_strtolower($tag);\n		} else if ($texttransform == \'capfirst\') {\n			$tag = tru_tags_ucfirst($tag);\n		}\n\n		if ($generatelinks) {\n			$wholeurl = \'\"\' . $urlprefix . urlencode($tag) . $urlsuffix . \'\"\';\n			$tags_html[] = \'<a href=\' . $wholeurl . $tag_class . $style . $titlecount . \'>\' . htmlspecialchars($tag) . \'</a>\' . $displaycount;\n		} else if ($tag_class || $style || $titlecount) {\n			$tags_html[] = \'<span\' . $tag_class . $style . $titlecount . \'>\' . htmlspecialchars($tag) . \'</span>\' . $displaycount;\n		} else {\n			$tags_html[] = htmlspecialchars($tag) . $displaycount;\n		}\n	}\n	return tru_tags_do_wrap($tags_html, $wraptag, $break, $class, $breakclass);\n}\n\n\n### CLEAN URL FUNCTIONS ###\n###########################\n\nif (tru_tags_clean_urls()) {\n	register_callback(\'tru_tags_clean_url_handler\', \'pretext\');\n}\n\n\nfunction tru_tags_clean_url_handler($event, $step) {\n	global $tru_tags_prefs;\n	$subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu);\n	$regsafesubpath = preg_quote($subpath, \'/\');\n	$req = preg_replace(\"/^$regsafesubpath/i\",\'/\',$_SERVER[\'REQUEST_URI\']);\n\n	$qs = strpos($req, \'?\');\n	$qatts = ($qs ? \'&\'.substr($req, $qs + 1) : \'\');\n	if ($qs) $req = substr($req, 0, $qs);\n\n	$parts = array_values(array_filter(explode(\'/\', $req)));\n	if (count($parts) == 2 && $parts[0] == $tru_tags_prefs[TAG_SECTION]->value) {\n		$tag = $parts[1];\n		$_SERVER[\'QUERY_STRING\'] = $tru_tags_prefs[PARM_NAME]->value . \'=\' . $tag . $qatts;\n		$_SERVER[\'REQUEST_URI\'] = $subpath . $tru_tags_prefs[TAG_SECTION]->value . \'/?\' . $_SERVER[\'QUERY_STRING\'];\n		if (count($_POST) > 0) {\n			$_POST[\'section\'] = $tru_tags_prefs[TAG_SECTION]->value;\n			$_POST[$tru_tags_prefs[PARM_NAME]->value] = $tag;\n		} else {\n			$_GET[\'section\'] = $tru_tags_prefs[TAG_SECTION]->value;\n			$_GET[$tru_tags_prefs[PARM_NAME]->value] = $tag;\n		}\n	}\n}\n\n\nfunction tru_tags_clean_urls() {\n	global $tru_tags_prefs;\n	return (\'clean\' == $tru_tags_prefs[CLEAN_URLS]->value);\n}\n\n\n### ADMIN SIDE FUNCTIONS ###\n############################\n\nif (@txpinterface == \'admin\') {\n	add_privs(\'tru_tags\', \'1,2\');\n	register_tab(\'extensions\', \'tru_tags\', \'tru_tags\');\n	register_callback(\'tru_tags_admin_tab\', \'tru_tags\');\n\n	if ($tru_tags_prefs[TAGS_IN_WRITE_TAB]->value || $tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value) {\n		register_callback(\'tru_tags_admin_write_tab_handler\', \'article_ui\', \'keywords\');\n	}\n}\n\n\nfunction tru_tags_admin_tab($event, $step) {\n	require_privs(\'tru_tags\');\n\n	$results = tru_tags_admin_tab_handle_input();\n\n	$atts = tru_tags_get_standard_cloud_atts(array(), false, false, true);\n	$all_tags = tru_tags_cloud_query($atts);\n	$cloud = tru_tags_render_cloud($atts, $all_tags, $all_tags);\n\n	$redirects = tru_tags_load_redirects();\n\n	tru_tags_admin_tab_render_page($results, $cloud, $redirects);\n}\n\n\nfunction tru_tags_admin_tab_render_page($results, $cloud, $redirects) {\n	global $event;\n	pagetop(\'tru_tags\', \'\');\n\n	include_once(txpath . \'/include/txp_prefs.php\');\n	global $tru_tags_prefs;\n\n	echo startTable(\'layout\', \'\', \'\', \'10px\').\'<tr><td style=\"border-right:2px solid gray\">\'.  # I know, I know...\n		startTable(\'layout\', \'\', \'\', \'\', \'10px\').\'<tr><td style=\"border-bottom:2px solid gray\">\'.\n			startTable(\'list\', \'\', \'\', \'\', \'300px\').\n				tr(hCell(gTxt(\'Current tags\'))).\n				tr(td($cloud)).\n			endTable().\n		\'</td></tr><tr><td>\'.\n			startTable(\'list\', \'\', \'\', \'\', \'300px\').\n				tr(hCell(gTxt(\'tru_tags Reference\'))).\n				tr(td(\'<a href=\"http://www.rainskit.com/reference/497/trutags-usage-instructions\">Usage instructions</a>\'.\n				\'<br><a href=\"http://forum.textpattern.com/viewtopic.php?id=15084\">Forum pages</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/537/tru_tags-releases\">Releases page</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/554/tru_tags-roadmap\">Release roadmap</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/495/trutags-feature-list\">Feature list</a>\'.\n				\'<br><br><a href=\"https://github.com/truist/tru-tags/blob/master/tru_tags.php\">tru_tags source code</a>\'.\n				\'<br><a href=\"https://github.com/truist/tru-tags/blob/master/tru_tags-docs.html\">Source HTML for the usage instructions</a>\'.\n				\'<br><br><a href=\"http://www.rainskit.com/blog/493/trutags-a-tagging-plugin-for-textpattern\">tru_tags</a>, by <a href=\"http://www.rainskit.com/\">Nathan Arthur</a>\'.\n				\'<br><br>\'.\n				\'<div id=\"paypal\"><form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\"><input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" /><input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but04.gif\" name=\"submit\" alt=\"Make a donation to Nathan Arthur\" /><img alt=\"\" border=\"0\" src=\"https://www.paypal.com/en_US/i/scr/pixel.gif\" width=\"1\" height=\"1\" /><input type=\"hidden\" name=\"encrypted\" value=\"-----BEGIN PKCS7-----MIIHTwYJKoZIhvcNAQcEoIIHQDCCBzwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYC8i1T27ljKfpNTEQi0wKHcdGulxxkMqwdCMmbGvs87n/4TsJtiAsqMo2hys7ZsGy5RF/O7s+B2oQ76zUlT52WW7QeXUK3Gp0nr2cP3ioBStNu+RZ6jkam2E0FGLXyV6+UNVEOwh8lmoISRotvSvIgQyTLnEeDHqG9qvUzqvF3SqjELMAkGBSsOAwIaBQAwgcwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIpmPZrlrZfZmAgaiePVb+n9sVdsufgGrmAw2rXAzR39kYqPUJ7n0LiNDmdAq73JoP53kZy8gSpovucL2S0jC1sXrcpELApLL8BFSHfdLiZoZSV/CYOppH5+dx2YqFIdyCCdjIX7oOPgQyAugRa2Qr3b+yutuG0DFsd+LAJGb8l4CnnrbmwdYK3NnVDBPOmxEOjlXUgEzlFLXmE3w5+MoPKQcp2n8fdJLsgG15xoVPFzCd/K2gggOHMIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQUFADCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wHhcNMDQwMjEzMTAxMzE1WhcNMzUwMjEzMTAxMzE1WjCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMFHTt38RMxLXJyO2SmS+Ndl72T7oKJ4u4uw+6awntALWh03PewmIJuzbALScsTS4sZoS1fKciBGoh11gIfHzylvkdNe/hJl66/RGqrj5rFb08sAABNTzDTiqqNpJeBsYs/c2aiGozptX2RlnBktH+SUNpAajW724Nv2Wvhif6sFAgMBAAGjge4wgeswHQYDVR0OBBYEFJaffLvGbxe9WT9S1wob7BDWZJRrMIG7BgNVHSMEgbMwgbCAFJaffLvGbxe9WT9S1wob7BDWZJRroYGUpIGRMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAIFfOlaagFrl71+jq6OKidbWFSE+Q4FqROvdgIONth+8kSK//Y/4ihuE4Ymvzn5ceE3S/iBSQQMjyvb+s2TWbQYDwcp129OPIbD9epdr4tJOUNiSojw7BHwYRiPh58S1xGlFgHFXwrEBb3dgNbMUa+u4qectsMAXpVHnD9wIyfmHMYIBmjCCAZYCAQEwgZQwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tAgEAMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wODA3MDMwNDE0MTJaMCMGCSqGSIb3DQEJBDEWBBT0tkj4dZLe/E4Qwbib29XEdHxAYjANBgkqhkiG9w0BAQEFAASBgL5JsQHjQ9Sg4Y3eDWKDO16r+tfEz4RYADt+6h981fkVCxfNHFDxofDcxyzRMYr7y95cdnVi4ANQwMUY6yJW5jm/GD17rjgSxZMEvsAe6YcCSLK5ZapCw1qlySpPGZBA3MTt6OD+ovVoa/1v8CNsEcHp7f4tOxOUSw5P4nHyLPWj-----END PKCS7-----\"></form></div>\'\n				)).\n			endTable().\n		\'</td></tr>\'.endTable().\n	\'</td><td>\'.\n		startTable(\'layout\', \'\', \'\', \'10px\').\'<tr><td style=\"border-bottom:2px solid gray;width:400px\">\';\n\n		if ($results) {\n			echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n				tr(hCell(gTxt(\'Results\'))).\n				tr(tda($results), \' style=\"color:red\"\').\n			endTable();\n			echo \'</td></tr><tr><td style=\"border-bottom:2px solid gray\">\';\n		}\n\n\n		echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n			tr(tag(gTxt(\'Article Tag Maintenance\').\' (\'.gTxt(\'Case Sensitive\').\'!)\', \'th\', \' colspan=\"5\"\')).\n			tr(\n				form(\n					tda(gTxt(\'Rename\').\': \', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'matchtag\', \'\', \'15\')).\n					tda(gTxt(\'to\').\':\', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'replacetag\', \'\', \'15\')).\n					td(fInput(\'submit\', \'replace\', gTxt(\'Run\'), \'publish\').eInput(\'tru_tags\')),\n					\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n				)\n			).\n			tr(\n				form(\n					tda(gTxt(\'Delete\').\': \', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'deletetag\', \'\', \'15\')).\n					tdcs(\'\', 2).\n					td(fInput(\'submit\', \'delete\', gTxt(\'Run\'), \'publish\').eInput(\'tru_tags\')),\n					\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n				)\n			).\n		endTable();\n\n	echo \'</td></tr><tr><td style=\"border-bottom:2px solid gray\">\';\n\n		echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n			tr(tag(gTxt(\'Redirections\'), \'th\', \' colspan=\"4\"\'));\n			foreach ($redirects as $lefttag => $righttag) {\n				echo tr(\n					tda(href($lefttag, tru_tags_linkify_tag($lefttag)), \' style=\"text-align: center\"\').\n					tda(htmlspecialchars(\'=>\'), \' style=\"text-align: center\"\').\n					tda(href($righttag, tru_tags_linkify_tag($righttag)), \' style=\"text-align: center\"\').\n					td(\'<a href=\"index.php?event=tru_tags&amp;delete_redirect=\'.urlencode($lefttag).\'\"  onclick=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">Delete</a>\')\n				);\n			}\n			echo tr(\n				\'<form name=\"redirect\" id=\"redirect\" method=\"post\" action=\"index.php?event=tru_tags\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\n					tda(text_input(\'lefttag\', \'\', \'15\'), \' style=\"text-align:center\"\').\n					tda(htmlspecialchars(\'=>\'), \' style=\"vertical-align:middle;text-align:center\"\').\n					tda(text_input(\'righttag\', \'\', \'15\'), \' style=\"text-align:center\"\').\n					tda(\'<a href=\"#\" onclick=\"if (verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')) document.getElementById(\\\'redirect\\\').submit(); return false;\">Add new</a>\', \' style=\"vertical-align:middle\"\').\n					fInput(\'hidden\', \'redirect\', \'1\').\n				\'</form>\'\n\n			).\n		endTable();\n\n	echo \'</td></tr><tr><td>\';\n\n		echo startTable(\'list\').\n			tr(tag(gTxt(\'Preferences\'), \'th\', \' colspan=\"2\"\')).\n			form(\n				tr(\n					tda(gTxt(\'Use clean URLs\').\' (\'.gTxt(\'site default is\').\' \'.$tru_tags_prefs[CLEAN_URLS]->default_value.\'): \', \' style=\"vertical-align:middle\"\').\n					td(radio_list(CLEAN_URLS,\n							array(\'clean\'=>gTxt(\'clean\'), \'messy\'=>gTxt(\'messy\')),\n							$tru_tags_prefs[CLEAN_URLS]->value))\n				).\n				tr(\n					tda(gTxt(\'Tag section name\').\' (default is \"\'.$tru_tags_prefs[TAG_SECTION]->default_value.\'\"): \', \' style=\"vertical-align:middle\"\').\n					td(text_input(TAG_SECTION, $tru_tags_prefs[TAG_SECTION]->value, \'15\'))\n				).\n				tr(\n					tda(gTxt(\'URL parameter for tag search\').\' (default is \"\'.$tru_tags_prefs[PARM_NAME]->default_value.\'\"): \'.\n						\'<br><em>(you shouldn\\\'t change this unless you really know what you are doing)</em>\', \' style=\"vertical-align:middle\"\').\n					td(text_input(PARM_NAME, $tru_tags_prefs[PARM_NAME]->value, \'15\'))\n				).\n				tr(\n					tda(gTxt(\'Put tags into RSS/Atom feeds, in \"Category\" elements\').\n						\': <br><em>(you probably want this)</em>\', \' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_FEED_CATEGORIES, $tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value))\n				).\n				tr(\n					tda(\'Append the tag list to the body of RSS/Atom feeds, \'.\n						\'with links, and with rel=\"tag\":\'.\n						\'<br><em>(if this is turned on,\'.\n						\'you can define a \"misc\" form named tru_tags_feed_tags \'.\n						\'that will be used to render the tags in the feed)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_FEED_BODY, $tru_tags_prefs[TAGS_IN_FEED_BODY]->value))\n				).\n				tr(\n					tda(gTxt(\'Show a clickable list of tags on the \"Write\" page\').\': \',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_WRITE_TAB, $tru_tags_prefs[TAGS_IN_WRITE_TAB]->value))\n				).\n				tr(\n					tda(gTxt(\'Autocomplete tags on the \"Write\" page\').\': <br>\'.\n						\'<em>(requires that the <a href=\"http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/\">jQuery Autocomplete plugin</a> <code>.css</code> and <code>.min.js</code> files be uploaded into a \"<code>js</code>\" folder on your site)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(AUTOCOMPLETE_IN_WRITE_TAB, $tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value))\n				).\n				tr(\n					tda(gTxt(\'Convert tags to lowercase by default\').\': <br>\'.\n						\'<em>(you probably want this, unless you are having problems with utf-8 characters)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(UTF_8_CASE, $tru_tags_prefs[UTF_8_CASE]->value))\n				).\n				tr(\n					td(\'\').\n					tda(fInput(\'submit\', \'prefs\', gTxt(\'Save\'), \'publish\').eInput(\'tru_tags\'), \' style=\"text-align:right\"\')\n				),\n				\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n			).\n		endTable().\'</td></tr>\'.\n		endTable();\n\n	echo \'</td></tr>\'.endTable();\n}\n\n\nfunction tru_tags_admin_tab_handle_input() {\n	if (gps(\'prefs\')) {\n		return tru_tags_admin_update_prefs();\n	} else if (gps(\'delete\')) {\n		return tru_tags_admin_delete_tag(gps(\'deletetag\'));\n	} else if (gps(\'replace\')) {\n		$result = tru_tags_admin_replace_tag(gps(\'matchtag\'), gps(\'replacetag\'), false);\n		return $result . \'<br>\' . tru_tags_admin_redirect_tag(gps(\'matchtag\'), gps(\'replacetag\'));\n	} else if (gps(\'redirect\')) {\n		return tru_tags_admin_redirect_tag(gps(\'lefttag\'), gps(\'righttag\'));\n	} else if (gps(\'delete_redirect\')) {\n		return tru_tags_admin_delete_redirect(gps(\'delete_redirect\'));\n	} else {\n		return \'\';\n	}\n}\n\n\nfunction tru_tags_admin_update_prefs() {\n	global $tru_tags_prefs;\n	$results = array();\n	foreach ($tru_tags_prefs as $pref) {\n		$valid_value = $pref->validate_value(gps($pref->name));\n		if ($valid_value && $valid_value <> gps($pref->name)) {\n			return $valid_value;  ### this is a sneaky way to handle validation - sorry ;)\n		}\n		if ($valid_value <> $pref->value) {\n			if ($valid_value == $pref->default_value) {\n				$result = tru_tags_delete_pref($pref->name);\n			} else {\n				$result = tru_tags_upsert_pref($pref->name, $valid_value);\n			}\n			if ($result) {\n				$results[] = \'Updated \' . $pref->name;\n				$pref->value = $valid_value;\n			} else {\n				$results[] = \'Error updating \' . $pref->name;\n			}\n		}\n	}\n	if (count($results) == 0) {\n		return \'No records need updating\';\n	} else {\n		return join(\'<br>\', $results);\n	}\n}\n\n\nfunction tru_tags_upsert_pref($name, $value) {\n	if (!safe_query(\'create table if not exists \' . safe_pfx(\'tru_tags_prefs\').\n		\' (name varchar(255) primary key, \'.\n		\'value varchar(255) not null)\')) {\n		return \'Serious error - unable to create the \' . safe_pfx(\'tru_tags_prefs\') . \' table.\';\n	}\n\n	return safe_upsert(\'tru_tags_prefs\', \'value=\"\'.$value.\'\"\', \'name=\"\'.$name.\'\"\');\n}\n\n\nfunction tru_tags_delete_pref($name) {\n	if (safe_delete(\'tru_tags_prefs\', \'name=\"\'.$name.\'\"\')) {\n		if (safe_count(\'tru_tags_prefs\', \'1\') == 0 && !safe_query(\'drop table \' . safe_pfx(\'tru_tags_prefs\'))) {\n			return false;\n		}\n		return true;\n	} else {\n		return false;\n	}\n}\n\n\nfunction tru_tags_admin_delete_tag($deletetag) {\n	if (trim($deletetag)) {\n		return tru_tags_admin_replace_tag($deletetag, \'\', true);\n	} else {\n		return \'Please enter a value\';\n	}\n}\n\n\nfunction tru_tags_admin_replace_tag($matchtag, $replacetag, $allow_blank_replacetag) {\n	$matchtag = trim($matchtag);\n	$replacetag = trim($replacetag);\n	if ($matchtag && ($allow_blank_replacetag || $replacetag)) {\n		if (safe_update(\'textpattern\', TRU_TAGS_FIELD.\'=trim(both \\\',\\\' from replace(concat(\",\", \'.TRU_TAGS_FIELD.\', \",\"), concat(\",\", \\\'\'.addslashes($matchtag).\'\\\', \",\"), \\\',\'.addslashes($replacetag).\',\\\'))\', \'1\')) {\n			return \'Updated \'.mysqli_affected_rows().\' rows (\"\'.htmlspecialchars($matchtag).\'\"=>\"\'.htmlspecialchars($replacetag).\'\")\';\n		} else {\n			return \'Error: \' . mysqli_error();\n		}\n	} else {\n		return \'Please enter a value in both fields\';\n	}\n}\n\n\nfunction tru_tags_admin_redirect_tag($lefttag, $righttag) {\n	$lefttag = addslashes(tru_tags_strtolower(trim($lefttag)));\n	$righttag = addslashes(tru_tags_strtolower(trim($righttag)));\n	if (!$lefttag || !$righttag) {\n		return \'Please enter a value in both fields\';\n	}\n\n	if (!safe_query(\'create table if not exists \' . safe_pfx(\'tru_tags_redirects\').\n		\' (lefttag varchar(255) primary key, \'.\n		\'righttag varchar(255) not null)\')) {\n		return \'Serious error - unable to create the \' . safe_pfx(\'tru_tags_redirects\') . \' table.\';\n	}\n\n	if (safe_insert(\'tru_tags_redirects\', \'lefttag=\"\'.$lefttag.\'\",righttag=\"\'.$righttag.\'\"\')) {\n		return \'Redirect added (\"\'.htmlspecialchars($lefttag).\'\"=>\"\'.htmlspecialchars($righttag).\'\") - please test it!\';\n	} else {\n		return \'Error adding record - does it already exist?\';\n	}\n}\n\n\nfunction tru_tags_admin_delete_redirect($lefttag) {\n	if (safe_delete(\'tru_tags_redirects\', \'lefttag=\"\'.addslashes($lefttag).\'\"\')) {\n		if (safe_count(\'tru_tags_redirects\', \'1\') == 0 && !safe_query(\'drop table \' . safe_pfx(\'tru_tags_redirects\'))) {\n			return \'Redirect deleted, but unable to drop table \' . safe_pfx(\'tru_tags_redirects\');\n		}\n		return \'Redirect deleted (\"\'.htmlspecialchars($lefttag).\'\")\';\n	} else {\n		return \'Error deleting redirect\';\n	}\n}\n\n\nfunction tru_tags_admin_write_tab_handler($event, $step, $data) {\n	global $tru_tags_prefs;\n	$atts = tru_tags_get_standard_cloud_atts(array(), true, true, true);\n	$cloud = array_unique(tru_tags_cloud_query($atts));\n	natcasesort($cloud);\n\n	$id = (empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\']);\n	$article_tags = (empty($id) ? array() : tru_tags_get_tags_for_article($id));\n\n	$links = array();\n	$raw_cloud = \'\';\n	foreach ($cloud as $tag) {\n		$style = (in_array($tag, $article_tags) ? \' class=\"tag_chosen\"\' : \'\');\n		$links[] = \'<a href=\"#advanced\"\'.$style.\' onclick=\"toggleClass(this, toggleTag(\\\\\\\'\'.addslashes(addslashes(htmlspecialchars($tag))).\'\\\\\\\')); return false;\">\' . addslashes(htmlspecialchars($tag)) . \'<\\/a>\';\n		$raw_cloud .= \'\"\' . addSlashes($tag) . \'\",\';\n	}\n	$raw_cloud = substr($raw_cloud, 0, strlen($raw_cloud) - 1);\n	$to_insert = join(\', \', $links);\n\n	if ($tru_tags_prefs[TAGS_IN_WRITE_TAB]->value) {\n		$js = <<<EOF\n			var keywordsField = document.getElementById(\'keywords\');\n			keywordsField.parentNode.appendChild(document.createElement(\'br\'));\n			var cloud = document.createElement(\'span\');\n			keywordsField.parentNode.appendChild(cloud);\n			cloud.setAttribute(\'class\', \'tru_tags_admin_tags\');\n			cloud.innerHTML = \'{$to_insert}\';\n\n			var styleElement = document.createElement(\'style\');\n			styleElement.setAttribute(\"type\", \"text/css\");\n			var styleText = \'a.tag_chosen{background-color: #FEB; color: black;}\';\n			if (styleElement.styleSheet) { //IE\n				styleElement.styleSheet.cssText = styleText;\n			} else {\n				var textNode = document.createTextNode(styleText);\n				styleElement.appendChild(textNode);\n			}\n			var headElement = document.getElementsByTagName(\'head\')[0];\n			headElement.appendChild(styleElement);\n\n			function toggleTag(tagName) {\n				var regexTag = tagName.replace(/([\\\\\\\\\\^\\\\$*+[\\\\]?{}.=!:(|)])/g,\"\\\\\\\\$1\");\n				var tagRegex = new RegExp(\"((^|,)\\\\\\s*)\" + regexTag + \"\\\\\\s*(,\\\\\\s*|$)\");\n				var textarea = document.getElementById(\'keywords\');\n				var curval = textarea.value.replace(/,?\\s+$/, \'\');\n				if (\'\' == curval) {\n					textarea.value = tagName;\n				} else if (curval.match(tagRegex)) {\n					textarea.value = curval.replace(tagRegex, \'$1\').replace(/,?\\s+$/, \'\');\n					return \'\';\n				} else if (\',\' == curval.charAt(curval.length - 1)) {\n					textarea.value += \' \' + tagName;\n				} else {\n					textarea.value += \', \' + tagName;\n				}\n				return \'tag_chosen\';\n			}\n\n			function toggleClass(element, className) {\n				element.setAttribute(\'class\', className);\n				element.setAttribute(\'className\', className); /* for IE */\n			}\nEOF;\n		return $data . script_js($js);\n	}\n\n	if ($tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value) {\n		echo <<<EOF\n			<link rel=\"stylesheet\" type=\"text/css\" href=\"../js/jquery.autocomplete.css\" media=\"projection, screen\" />\n			<script src=\"../js/jquery.autocomplete.min.js\" type=\"text/javascript\"></script>\n			<script language=\"JavaScript\" type=\"text/javascript\"><!--\n				$(document).ready(function(){ var tags = new Array({$raw_cloud});\n					$(\"#keywords\").autocomplete(tags, { multiple: true, mustMatch: false, autoFill: false });\n				});\n			//--></script>\nEOF;\n	}\n\n}\n\n\n### ATOM/RSS FEED FUNCTIONS ###\n###############################\n\nregister_callback(\'tru_tags_atom_handler\', \'atom_entry\');\nfunction tru_tags_atom_handler($event, $step) { return tru_tags_feed_handler(true); }\nregister_callback(\'tru_tags_rss_handler\', \'rss_entry\');\nfunction tru_tags_rss_handler($event, $step) { return tru_tags_feed_handler(false); }\n\nfunction tru_tags_feed_handler($atom) {\n	global $thisarticle, $tru_tags_prefs;\n	extract($thisarticle);\n\n	$tags = tru_tags_get_tags_for_article($thisid);\n\n	if ($tru_tags_prefs[TAGS_IN_FEED_BODY]->value) {\n		$extrabody = \'\';\n		$FORM_NAME = \'tru_tags_feed_tags\';\n		if (fetch(\'form\', \'txp_form\', \'name\', $FORM_NAME)) {\n			$form = fetch_form($FORM_NAME);\n			$extrabody = trim(parse($form));\n		} else {\n			$atts = tru_tags_get_standard_cloud_atts(array(), false, true);\n			$extrabody = \'<p>tags: \' . tru_tags_render_cloud($atts, $tags, $tags) . \'</p>\';\n		}\n		global $thisarticle;\n		if (trim($thisarticle[\'excerpt\'])) {\n			$thisarticle[\'excerpt\'] = trim($thisarticle[\'excerpt\']).n.$extrabody.n;\n		}\n		$thisarticle[\'body\'] = trim($thisarticle[\'body\']).n.$extrabody.n;\n	}\n\n	if ($tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value) {\n		$output = array();\n		foreach ($tags as $tag) {\n			if ($atom) {\n				$output[] = \'<category term=\"\' . htmlspecialchars($tag) . \'\" />\';\n			} else {\n				$output[] = \'<category>\' . htmlspecialchars($tag) . \'</category>\';\n			}\n		}\n		return n.join(n, $output).n;\n	}\n}\n\n\n### PREFS FUNCTIONS / CLASSES ###\n#################################\n\nclass tru_tags_pref {\n	function __construct($name, $default_value, $type) {\n		$this->name = $name;\n		$this->value = $default_value;\n		$this->default_value = $default_value;\n		$this->type = $type;\n	}\n\n	function validate_value($value) {\n		if ($value) {\n			return $value;\n		} else if ($this->type == \'boolean\') {\n			return \'0\';\n		} else {\n			return \'Please enter a value for \' . $this->name;\n		}\n	}\n}\n\nfunction tru_tags_load_prefs() {\n	$prefs = array();\n\n	global $permlink_mode;\n	$prefs[CLEAN_URLS] = new tru_tags_pref(CLEAN_URLS, ($permlink_mode != \'messy\' ? \'clean\' : \'messy\'), \'string\');\n	$prefs[TAG_SECTION] = new tru_tags_pref(TAG_SECTION, \'tag\', \'string\');\n	$prefs[PARM_NAME] = new tru_tags_pref(PARM_NAME, \'t\', \'string\');\n	$prefs[TAGS_IN_FEED_CATEGORIES] = new tru_tags_pref(TAGS_IN_FEED_CATEGORIES, \'1\', \'boolean\');\n	$prefs[TAGS_IN_FEED_BODY] = new tru_tags_pref(TAGS_IN_FEED_BODY, \'0\', \'boolean\');\n	$prefs[TAGS_IN_WRITE_TAB] = new tru_tags_pref(TAGS_IN_WRITE_TAB, \'1\', \'boolean\');\n	$prefs[AUTOCOMPLETE_IN_WRITE_TAB] = new tru_tags_pref(AUTOCOMPLETE_IN_WRITE_TAB, \'0\', \'boolean\');\n	$prefs[UTF_8_CASE] = new tru_tags_pref(UTF_8_CASE, \'1\', \'boolean\');\n\n	if (safe_query(\"describe \" . PFX . \"tru_tags_prefs\")) {\n		$rs = safe_rows(\'*\', \'tru_tags_prefs\', \'1\');\n		foreach ($rs as $row) {\n			$prefs[$row[\'name\']]->value = $row[\'value\'];\n		}\n	}\n\n	return $prefs;\n}\n\n\n### OTHER SUPPORT FUNCTIONS ###\n###############################\n\nfunction tru_tags_redirect_if_needed($tag_parameter) {\n	$redirects = tru_tags_load_redirects();\n	foreach ($redirects as $lefttag => $righttag) {\n		if ($lefttag == $tag_parameter || $lefttag == str_replace(\'-\', \' \', $tag_parameter)) {\n			tru_tags_redirect($righttag, false);\n			return true;\n		}\n	}\n\n	return false;\n}\n\n\nfunction tru_tags_load_redirects() {\n	$redirects = array();\n	if (safe_query(\"describe \" . PFX . \"tru_tags_redirects\")) {\n		$rs = safe_rows(\'*\', \'tru_tags_redirects\', \'1 order by lefttag\');\n		foreach ($rs as $row) {\n			$redirects[$row[\'lefttag\']] = $row[\'righttag\'];\n		}\n	}\n	return $redirects;\n}\n\n\nfunction tru_tags_redirect($destination, $is_full_url) {\n	global $tru_tags_prefs;\n	if ($is_full_url) {\n		$url = $destination;\n		$message = \'The resource you requested has moved to \' . $destination;\n	} else {\n		$url = tru_tags_linkify_tag($destination, false);\n		$message = \'The requested tag has been replaced by \' . $destination;\n	}\n\n	header(\'Location: \' . $url);\n	txp_die($message, \'301\');\n}\n\n\nfunction tru_tags_linkify_tag($tag, $use_amp = true) {\n	global $tru_tags_prefs;\n	if (tru_tags_clean_urls()) {\n		$urlprefix = hu . $tru_tags_prefs[TAG_SECTION]->value . \'/\';\n	} else {\n		$urlprefix = hu . \'?s=\' . $tru_tags_prefs[TAG_SECTION]->value . ($use_amp ? \'&amp;\' : \'&\') . $tru_tags_prefs[PARM_NAME]->value . \'=\';\n	}\n	$urlsuffix = (tru_tags_clean_urls() ? \'/\' : \'\');\n	return $urlprefix . urlencode($tag) . $urlsuffix;\n}\n\n\nfunction tru_tags_get_tags_for_article($articleID) {\n	global $thisarticle, $tru_tags_prefs;\n	if (isset($thisarticle)) {\n		extract($thisarticle);\n	}\n	$keywords = \'\';\n	if (isset($thisid) && $thisid == $articleID) {\n		if (isset($thisarticle[\'keywords\'])) {\n			$keywords = $thisarticle[\'keywords\'];\n		}\n	} else {\n		$tags_field = TRU_TAGS_FIELD;\n		$rs = safe_row($tags_field, \"textpattern\", \"ID=\'$articleID\' AND $tags_field <> \'\'\");\n		if (isset($rs[$tags_field])) {\n			$keywords = $rs[$tags_field];\n		}\n	}\n	$all_tags = array();\n	$all_tags = explode(\",\", trim(tru_tags_strtolower($keywords)));\n\n	return tru_tags_trim_tags($all_tags);\n}\n\n\n# fixes bug with 4.0.4\'s version of doWrap that caused spaces to show up before the commas\nfunction tru_tags_do_wrap($list, $wraptag, $break, $class, $breakclass) {\n	if (!$wraptag && !preg_match(\'/^\\w+$/\', $break)) {\n		return join($break.n, $list);\n	} else {\n		return doWrap($list, $wraptag, $break, $class, $breakclass);\n	}\n}\n\n\nfunction tru_tags_trim_tags($tag_array) {\n	$trimmed = array();\n	foreach ($tag_array as $tag) {\n		if (\"\" != trim($tag)) {\n			$trimmed[] = trim($tag);\n		}\n	}\n	return $trimmed;\n}\n\n\nfunction tru_tags_fixup_query_atts($atts, $tag_parameter) {\n	$keywords = explode(\',\', $tag_parameter);\n	foreach ($keywords as $keyword) {\n		if (strpos($keyword, \'-\') !== false) {\n			$keywords[] = str_replace(\'-\', \' \', $keyword);\n		}\n	}\n	$atts[\'keywords\'] = implode(\',\', $keywords);\n\n	if (isset($atts[\'section\']) && strpos($atts[\'section\'], \',\') !== false) {\n		$atts[\'section\'] = \'\';\n	}\n\n	if (isset($atts[\'excludesection\'])) {\n		unset($atts[\'excludesection\']);\n	}\n\n	if (isset($atts[\'noarticles\'])) {\n		unset($atts[\'noarticles\']);\n	}\n\n	if (!isset($atts[\'limit\'])) {\n		$atts[\'limit\'] = \'1000\';\n	}\n\n	if (!isset($atts[\'allowoverride\'])) {\n		$atts[\'allowoverride\'] = true;\n	}\n\n	if (isset($atts[\'searchform\']) && !isset($atts[\'listform\'])) {\n		$atts[\'listform\'] = $atts[\'searchform\'];\n	}\n\n	if (isset($atts[\'showcounts\'])) {\n		unset($atts[\'showcounts\']);\n	}\n\n	if (isset($atts[\'404redirect\'])) {\n		unset($atts[\'404redirect\']);\n	}\n\n	return $atts;\n}\n\n\nfunction tru_tags_strtolower($str) {\n	global $tru_tags_prefs;\n	if ($tru_tags_prefs[UTF_8_CASE]->value) {\n		if (function_exists(\'mb_strtolower\')) {\n			return mb_strtolower($str, ENCODING);\n		} else {\n			return strtolower($str);\n		}\n	} else {\n		return $str;\n	}\n}\n\nfunction tru_tags_strtoupper($str) {\n	if (function_exists(\'mb_strtoupper\')) {\n		return mb_strtoupper($str, ENCODING);\n	} else {\n		return strtoupper($str);\n	}\n}\n\nfunction tru_tags_ucfirst($str) {\n	if (function_exists(\'mb_substr\') && function_exists(\'mb_strtoupper\') && function_exists(\'mb_strlen\') && mb_strlen($str, ENCODING) > 1) {\n		return mb_strtoupper(mb_substr($str, 0, 1, ENCODING), ENCODING) . mb_substr($str, 1, mb_strlen($str, ENCODING), ENCODING);\n	} else {\n		return ucfirst($str);\n	}\n}\n\nfunction tru_tags_ucwords($str) {\n	if (function_exists(\'mb_convert_case\')) {\n		return mb_convert_case($str, MB_CASE_TITLE, ENCODING);\n	} else {\n		return ucwords($str);\n	}\n}\n\n\n//these next two functions are gross, but I can\'t figure out another way to do it\nfunction tru_tags_sort_tags($tags_weight, $sort_by_count, $sort_ascending) {\n	global $tru_tags_tags_weight, $tru_tags_sort_ascending;\n\n	$tru_tags_tags_weight = $tags_weight;\n	$tru_tags_sort_ascending = $sort_ascending;\n\n	$temp_array = array_keys($tags_weight);\n	if ($sort_by_count) {\n		usort($temp_array, \"tru_tags_sort_tags_comparator\");\n	} else {\n		natcasesort($temp_array);\n		$temp_array = array_values($temp_array);\n	}\n\n	$sorted_array = array();\n	foreach ($temp_array as $tag) {\n		$sorted_array[$tag] = $tags_weight[$tag];\n	}\n\n	return $sorted_array;\n}\n\n\nfunction tru_tags_sort_tags_comparator($left, $right) {\n	global $tru_tags_tags_weight, $tru_tags_sort_ascending;\n\n	$left_weight = $tru_tags_tags_weight[$left];\n	$right_weight = $tru_tags_tags_weight[$right];\n	if ($left_weight == $right_weight) {\n		$temp_array = array($left, $right);\n		natcasesort($temp_array);\n		$temp_array = array_values($temp_array);\n		return ($temp_array[0] == $left ? -1 : 1);\n	} else if ($tru_tags_sort_ascending) {\n		return $left_weight - $right_weight;\n	} else {\n		return $right_weight - $left_weight;\n	}\n}\n\n\n\n### BASTARD FUNCTIONS THAT SHOULDN\'T HAVE TO EXIST ###\n######################################################\n\n\nfunction tru_tags_redo_article_search($atts) {\n	$theAtts = lAtts(array(\'limit\'     => 1000,\n				\'category\'  => \'\',\n				\'section\'   => \'\',\n				\'excerpted\' => \'\',\n				\'author\'    => \'\',\n				\'sortby\'    => \'Posted\',\n				\'sortdir\'   => \'desc\',\n				\'month\'     => \'\',\n				\'keywords\'  => \'\',\n				\'frontpage\' => \'\',\n				\'id\'        => \'\',\n				\'time\'      => \'past\',\n				\'status\'    => \'4\',\n				\'offset\'    => 0\n			),$atts, 0);\n	extract($theAtts);\n\n	//Building query parts\n	$frontpage = ($frontpage) ? filterFrontPage() : \'\';\n	$category  = (!$category)  ? \'\' : \" and ((Category1=\'\".doslash($category).\"\') or (Category2=\'\".doSlash($category).\"\')) \";\n	$section   = (!$section)   ? \'\' : \" and Section = \'\".doslash($section).\"\'\";\n	$excerpted = ($excerpted==\'y\')  ? \" and Excerpt !=\'\'\" : \'\';\n	$author    = (!$author)    ? \'\' : \" and AuthorID = \'\".doslash($author).\"\'\";\n	$month     = (!$month)     ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\n	$id        = (!$id)        ? \'\' : \" and ID = \'\".intval($id).\"\'\";\n	switch ($time) {\n		case \'any\':\n			$time = \"\"; break;\n		case \'future\':\n			$time = \" and Posted > now()\"; break;\n		default:\n			$time = \" and Posted < now()\";\n	}\n	if (!is_numeric($status))\n		$status = getStatusNum($status);\n\n	$custom = \'\';\n	// trying custom fields here\n	$customFields = getCustomFields();\n	if ($customFields) {\n		foreach($customFields as $cField) {\n			if (isset($atts[$cField]))\n				$customPairs[$cField] = $atts[$cField];\n		}\n		if(!empty($customPairs))\n			$custom =  buildCustomSql($customFields,$customPairs);\n		else\n			$custom = \'\';\n	}\n\n	//Allow keywords for no-custom articles. That tagging mode, you know\n	if ($keywords) {\n		$keys = doSlash(array_map(\'trim\', explode(\',\', $keywords)));\n		foreach ($keys as $key) {\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n		}\n		$keywords = \" and (\" . join(\' or \',$keyparts) . \")\";\n	}\n\n	if ($id)\n		$statusq = \" and Status >= \'4\'\";\n	else\n		$statusq = \" and Status=\'\".doSlash($status).\"\'\";\n\n	$where = \"1\" . $statusq. $time.\n		$id . $category . $section . $excerpted . $month . $author . $keywords . $custom . $frontpage;\n\n		$pgoffset = $offset . \', \';\n\n	$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted\", \'textpattern\',\n		$where. \' order by \' . doslash($sortby) . \' \' . doSlash($sortdir) . \' limit \' . doSlash($limit));\n\n	return $rs;\n}\n\n\n', '\n# Copyright 2017 Nathan Arthur\n# Released under the GNU Public License, see http://www.opensource.org/licenses/gpl-license.php for details\n#\n# This work was originally inspired by ran_tags by Ran Aroussi, originally found at http://aroussi.com/article/45/tagging-textpattern\n# Javascript bug fixes were provided by the_ghost - http://victorus.net\n# Javascript code for the tag auto-complete feature was provided by Jim Biancolo - http://www.biancolo.com/\n# Fixes for Textpattern 4.6 provided by:\n#   - Rimas Kudelis; https://github.com/rimas-kudelis\n#   - jools-r; https://github.com/jools-r\n#   - Simon Finch; https://github.com/spiffin\n\n\n### CONFIGURATION ###\n#####################\n\n# TURN BACK!  Configuration is no longer handled by editing the plugin.\n# Check out the \'Extensions\' tab in the Textpattern admin :)\n\n\n# Changing these won\'t do any good.  They\'re just here as a convenience for development.\ndefine(\'TRU_TAGS_FIELD\', \'Keywords\');\ndefine(\'ENCODING\', \'utf-8\');\n\ndefine(\'CLEAN_URLS\', \'clean_urls\');\ndefine(\'TAG_SECTION\', \'tag_section\');\ndefine(\'PARM_NAME\', \'parm\');\ndefine(\'TAGS_IN_FEED_CATEGORIES\', \'tags_in_feed_categories\');\ndefine(\'TAGS_IN_FEED_BODY\', \'tags_in_feed_body\');\ndefine(\'TAGS_IN_WRITE_TAB\', \'tags_in_write_tab\');\ndefine(\'AUTOCOMPLETE_IN_WRITE_TAB\', \'autocomplete_in_write_tab\');\ndefine(\'UTF_8_CASE\', \'utf_8_case\');\n\nglobal $tru_tags_prefs;\n$tru_tags_prefs = tru_tags_load_prefs();\n\n\n### TAG REGISTRATION ###\n########################\n\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n	->register(\'tru_tags_handler\')\n	->register(\'tru_tags_if_has_tags\')\n	->register(\'tru_tags_from_article\')\n	->register(\'tru_tags_cloud\')\n	->register(\'tru_tags_list\')\n	->register(\'tru_tags_if_tag_search\')\n	->register(\'tru_tags_tag_parameter\')\n	->register(\'tru_tags_search_result_excerpt\')\n	->register(\'tru_tags_search_parameter\')\n	->register(\'tru_tags_related_tags_from_search\')\n	->register(\'tru_tags_archive\')\n	->register(\'tru_tags_current_archive_tag\');\n\n### PRIMARY TAG FUNCTIONS ###\n#############################\n\nfunction tru_tags_handler($atts) {\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	if (!empty($tag_parameter)) {\n		if (tru_tags_redirect_if_needed($tag_parameter)) {\n			return \'\';\n		}\n		$clean_atts = tru_tags_fixup_query_atts($atts, $tag_parameter);\n		$result = doArticles($clean_atts, true);		#function in TXP code\n		if (trim($result) == \'\') {\n			if (isset($atts[\'noarticles\'])) {\n				tru_tags_redirect($atts[\'noarticles\'], true);\n			} else if (isset($atts[\'404redirect\']) && !$atts[\'404redirect\']) {\n				return $result;\n			} else {\n				txp_die(gTxt(\'404_not_found\'), \'404\');\n			}\n		} else {\n			return $result;\n		}\n	} else {\n		return tru_tags_cloud($atts);\n	}\n}\n\n\nfunction tru_tags_archive($atts) {\n	global $tru_tags_current_archive_tag;\n	$tags = array_unique(tru_tags_cloud_query(tru_tags_get_standard_cloud_atts($atts, false, false)));\n	natcasesort($tags);\n	foreach ($tags as $tag) {\n		$tru_tags_current_archive_tag = $tag;\n		$clean_atts = tru_tags_fixup_query_atts($atts, $tag);\n		$results[] = doArticles($clean_atts, true);		#function in TXP code\n	}\n	return join(\' \', $results);\n}\n\n\nfunction tru_tags_current_archive_tag($atts) {\n	global $tru_tags_current_archive_tag;\n	extract(lAtts(array(\'link\' => \'0\'),  $atts, 0));\n	if ($link) {\n		return \'<a href=\"\' . tru_tags_linkify_tag($tru_tags_current_archive_tag) . \'\">\' . $tru_tags_current_archive_tag . \'</a>\';\n	} else {\n		return $tru_tags_current_archive_tag;\n	}\n}\n\n\nfunction tru_tags_cloud($atts) {\n	return tru_tags_list(tru_tags_get_standard_cloud_atts($atts, false, false));\n}\n\n\nfunction tru_tags_list($atts) {\n	$atts = tru_tags_get_standard_cloud_atts($atts, true, false);\n	$all_tags = tru_tags_cloud_query($atts);\n\n	return tru_tags_render_cloud($atts, $all_tags, $all_tags);\n}\n\n\nfunction tru_tags_from_article($atts) {\n	global $thisarticle;\n	extract($thisarticle);\n\n	$all_tags = tru_tags_get_tags_for_article($thisid);\n\n	$atts = tru_tags_get_standard_cloud_atts($atts, false, true);\n	$all_tags_for_weight = $all_tags;\n	if ($atts[\'useoverallcounts\']) {\n		$all_tags_for_weight = tru_tags_cloud_query($atts);\n	}\n\n	return tru_tags_render_cloud($atts, $all_tags, $all_tags_for_weight);\n}\n\n\nfunction tru_tags_if_has_tags($atts, $thing) {\n	global $thisarticle;\n	extract($thisarticle);\n\n	$tags_field = TRU_TAGS_FIELD;\n\n	$rs = safe_row($tags_field, \"textpattern\", \"ID=\'$thisid\' AND $tags_field <> \'\'\");\n	return parse(EvalElse($thing, $rs));\n}\n\n\nfunction tru_tags_if_tag_search($atts, $thing) {\n	extract(lAtts(array(\'tag\' => \'\'),  $atts, 0));\n\n	$tag_parameter = tru_tags_tag_parameter(array(\'striphyphens\' => 1), false);\n	$condition = (!empty($tag_parameter)) ? true : false;\n	if ($condition && !empty($tag)) {\n		$condition = ($tag_parameter == $tag);\n	}\n	return parse(EvalElse($thing, $condition));\n}\n\n\nfunction tru_tags_tag_parameter($atts, $safehtml = true) {\n	global $tru_tags_prefs;\n	extract(lAtts(array(\'striphyphens\' => \'0\', \'urlencode\' => 0),  $atts, 0));\n\n	$parm = urldecode(strip_tags(gps($tru_tags_prefs[PARM_NAME]->value)));\n	if (\'lookup\' == $striphyphens) {\n		$atts = tru_tags_get_standard_cloud_atts(array(), false, false);\n		$tag_list = array_unique(tru_tags_cloud_query($atts));\n		foreach ($tag_list as $cloud_tag) {\n			if ($parm == str_replace(\' \', \'-\', $cloud_tag)) {\n				$parm = $cloud_tag;\n				break;\n			}\n		}\n	} else if ($striphyphens) {\n		$parm = str_replace(\'-\', \' \', $parm);\n	}\n	if ($urlencode) {\n		$parm = urlencode($parm);\n	 } else if ($safehtml) {\n		$parm = htmlspecialchars($parm);\n	}\n\n	return $parm;\n}\n\n\nfunction tru_tags_search_result_excerpt($atts) {\n	global $pretext;\n\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	if (trim($tag_parameter) != \'\') {\n		$q = $pretext[\'q\'];\n		$pretext[\'q\'] = $tag_parameter;\n		$result = search_result_excerpt($atts);\n		$pretext[\'q\'] = $q;\n	} else {\n		$result = search_result_excerpt($atts);\n	}\n	return $result;\n}\n\n\nfunction tru_tags_search_parameter() {\n	trigger_error(gTxt(\'deprecated_tag\'), E_USER_NOTICE);\n	return strip_tags(gps(\'q\'));\n}\n\n\nfunction tru_tags_related_tags_from_search($atts) {\n	$tag_parameter = tru_tags_tag_parameter(array(), false);\n	extract(lAtts(array(\'tag_parameter\' => $tag_parameter),  $atts, 0));\n	if (!empty($tag_parameter)) {\n        $tags_field = TRU_TAGS_FIELD;\n		$all_tags = array();\n\n		$query_atts = tru_tags_fixup_query_atts($atts, $tag_parameter);\n		$rs = tru_tags_redo_article_search($query_atts);\n		if ($rs) {\n			while ($a = nextRow($rs)) {\n				$article_tags = array();\n				if (isset($a[$tags_field])) {\n					$article_tags = explode(\",\", trim(tru_tags_strtolower($a[$tags_field])));\n				}\n				# faster implementation of array_merge(), thanks to whocarez\n				# (http://forum.textpattern.com/viewtopic.php?pid=238373#p238373)\n				foreach(tru_tags_trim_tags($article_tags) as $i) {$all_tags[] = $i;}\n			}\n		}\n\n		$alt_tag_parameter = str_replace(\'-\', \' \', $tag_parameter);\n		foreach ($all_tags as $key => $tag) {\n			if ($tag == $tag_parameter || $tag == $alt_tag_parameter) {\n				unset($all_tags[$key]);\n			}\n		}\n\n		$cloud_atts = tru_tags_get_standard_cloud_atts($atts, false, false);\n		$all_tags_for_weight = $all_tags;\n		if ($cloud_atts[\'useoverallcounts\']) {\n			$all_tags_for_weight = tru_tags_cloud_query($cloud_atts);\n		}\n\n		return tru_tags_render_cloud($cloud_atts, $all_tags, $all_tags_for_weight);\n	} else {\n		return \'\';\n	}\n}\n\n\n### CLOUD SUPPORT FUNCTIONS ###\n###############################\n\nfunction tru_tags_get_standard_cloud_atts($atts, $isList, $isArticle, $showAll=false) {\n	return lAtts(array(\'wraptag\'	=> ($isList ? \'ul\' : \'\'),\n			\'break\'		=> ($isList ? \'li\' : \', \'),\n			\'class\'		=> \'\',\n			\'breakclass\'	=> \'\',\n			\'section\'	=> \'\',\n			\'minpercent\'	=> \'100\',\n			\'maxpercent\'	=> ($isList || $isArticle ? \'100\' : \'200\'),\n			\'showcounts\'	=> \'\',\n			\'countwrapchars\'	=> \'[]\',\n			\'usereltag\'	=> ($isArticle ? \'1\' : \'\'),\n			\'generatelinks\'	=> \'1\',\n			\'mintagcount\'	=> \'0\',\n			\'maxtagcount\'	=> \'1000\',\n			\'setsizes\'	=> ($isArticle ? \'0\' : \'1\'),\n			\'usenofollow\'	=> \'\',\n			\'sort\'		=> \'alpha\',\n			\'useoverallcounts\'	=> \'\',\n			\'setclasses\'	=> ($isArticle ? \'0\' : \'1\'),\n			\'title\'		=> \'\',\n			\'listlimit\'	=> \'\',\n			\'keep\'		=> \'largest\',\n			\'cutoff\'	=> \'chunk\',\n			\'texttransform\'	=> \'none\',\n			\'linkpath\'	=> \'\',\n			\'linkpathtail\'	=> \'\',\n			\'filtersearch\'	=> ($showAll ? 0 : \'1\'),\n			\'excludesection\'=> \'\',\n			\'activeclass\'	=> \'tagActive\',\n			\'time\'		=> ($showAll ? \'any\' : \'past\')\n		),$atts, 0);\n}\n\n\nfunction tru_tags_cloud_query($atts) {\n	extract($atts);\n\n	$section_clause = \'\';\n	if ($section <> \'\') {\n		$keys = explode(\',\', $section);\n		foreach ($keys as $key) {\n			$keyparts[] = \" Section = \'\" . trim($key) . \"\'\";\n		}\n		$section_clause = \" AND (\" . join(\' or \', $keyparts) . \")\";\n	}\n\n	$tags_field = TRU_TAGS_FIELD;\n	include_once txpath.\'/publish/search.php\';\n\n	$filter = tru_tags_filter_sections($excludesection);\n	$filter .= ($filtersearch ? filterSearch() : \'\');\n\n	switch ($time) {\n		case \'any\':\n			$time = \"\"; break;\n		case \'future\':\n			$time = \" and Posted > now()\"; break;\n		default:\n			$time = \" and Posted <= now()\";\n	}\n	global $prefs;\n	extract($prefs);\n	if (!$publish_expired_articles) {\n		$time .= \" and (now() <= Expires or Expires IS NULL)\";\n	}\n\n	$all_tags = array();\n	$rs = safe_rows(\"$tags_field\", \"textpattern\", \"$tags_field <> \'\'\" . $section_clause . $filter . \" and Status >= \'4\'\" . $time);\n	foreach ($rs as $row) {\n		$temp_array = array();\n		if (isset($row[$tags_field])) {\n			$temp_array = explode(\",\", trim(tru_tags_strtolower($row[$tags_field])));\n		}\n		# faster implementation of array_merge(), thanks to whocarez\n		# (http://forum.textpattern.com/viewtopic.php?pid=238373#p238373)\n		foreach(tru_tags_trim_tags($temp_array) as $i) {$all_tags[] = $i;}\n	}\n\n	return $all_tags;\n}\n\n\nfunction tru_tags_filter_sections($excludesection) {\n	$sections = explode(\',\', $excludesection);\n	$filters = array();\n	foreach ($sections as $section) {\n		$filters[] = \"and Section != \'\".doSlash($section).\"\'\";\n	}\n	return join(\' \', $filters);\n}\n\n\nfunction tru_tags_render_cloud($atts, $all_tags, $all_tags_for_weight) {\n	global $tru_tags_prefs;\n	extract($atts);\n\n	$tags_weight = array_count_values($all_tags_for_weight);\n\n	foreach ($tags_weight as $tag => $weight) {\n		if (!in_array($tag, $all_tags)\n		   || $tags_weight[$tag] < $mintagcount\n		   || $tags_weight[$tag] > $maxtagcount) {\n			unset($tags_weight[$tag]);\n		}\n	}\n\n	$sort_by_count = (strpos($sort, \'count\') !== false);\n	$sort_ascending = (strpos($sort, \'asc\') !== false);\n	$tags_weight = tru_tags_sort_tags($tags_weight, $sort_by_count, $sort_ascending);\n\n	if ($listlimit > 0 && $listlimit < count($tags_weight)) {\n		$resorted_tags = array();\n		if ($keep == \'largest\') {\n			$resorted_tags = array_keys(tru_tags_sort_tags($tags_weight, true, false));\n		} else {\n			if ($keep == \'random\') {\n				foreach ($tags_weight as $tag => $weight) {\n					$resorted_tags[$tag] = rand(0, $weight);\n				}\n				$resorted_tags = array_keys(tru_tags_sort_tags($resorted_tags, true, false));\n			} else if ($keep == \'alpha\') {\n				$resorted_tags = array_keys($tags_weight);\n				natcasesort($resorted_tags);\n			}\n			$cutoff = \'exact\';\n		}\n\n		$last_good_index = $listlimit - 1;\n		if ($cutoff == \'chunk\') { //alternative is \'exact\'\n			$last_weight = -1;\n			for ($i = 0; $i < $listlimit + 1; $i++) {\n				$new_weight = $tags_weight[$resorted_tags[$i]];\n				if ($new_weight != $last_weight) {\n					$last_good_index = $i - 1;\n					$last_weight = $new_weight;\n				}\n			}\n			if ($last_good_index < 0) {\n				$last_good_index = $listlimit - 1;\n			}\n		}\n\n		$resorted_tags = array_chunk($resorted_tags, $last_good_index + 1);\n		$resorted_tags = $resorted_tags[0];\n\n		foreach ($tags_weight as $tag => $weight) {\n			if (!in_array($tag, $resorted_tags)) {\n				unset($tags_weight[$tag]);\n			}\n		}\n	}\n\n	if ($generatelinks) {\n		if ($linkpath) {\n			$urlprefix = $linkpath;\n			$urlsuffix = $linkpathtail;\n		} else {\n			if (tru_tags_clean_urls()) {\n				$urlprefix = hu . $tru_tags_prefs[TAG_SECTION]->value . \'/\';\n			} else {\n				$urlprefix = hu . \'?s=\' . $tru_tags_prefs[TAG_SECTION]->value . \'&amp;\' . $tru_tags_prefs[PARM_NAME]->value . \'=\';\n			}\n			$urlsuffix = (tru_tags_clean_urls() ? \'/\' : \'\');\n		}\n\n		if ($usereltag) {\n			if ($usenofollow) {\n				$urlsuffix .= \'\" rel=\"tag nofollow\';\n			} else {\n				$urlsuffix .= \'\" rel=\"tag\';\n			}\n		} else if ($usenofollow) {\n			$urlsuffix .= \'\" rel=\"nofollow\';\n		}\n	}\n\n	if (count($tags_weight) > 0) {\n		$max = max($tags_weight);\n		$min = min($tags_weight);\n	} else {\n		$max = $min = 0;\n	}\n	$stepvalue = ($max == $min) ? 0 : ($maxpercent - $minpercent) / ($max - $min);\n\n	$tags_html = array();\n	$tag_search_tag = tru_tags_tag_parameter(array(\'striphyphens\' => \'lookup\'));\n	$tag_search_tag = function_exists(\"htmlspecialchars_decode\") ? htmlspecialchars_decode($tag_search_tag) : html_entity_decode($tag_search_tag);\n	foreach ($tags_weight as $tag => $weight) {\n		$tag_weight = floor($minpercent + ($weight - $min) * $stepvalue);\n\n		$style = \'\';\n		if ($setsizes)\n			$style = \' style=\"font-size: \' . $tag_weight . \'%;\"\';\n\n		$tag_class = \'\';\n		if ($setclasses) {\n			$tag_class = \' class=\"\';\n			if ($weight == $min) {\n				$tag_class .= \"tagSizeSmallest\";\n			} else if ($weight == $max) {\n				$tag_class .= \"tagSizeLargest\";\n			} else {\n				$tag_class .= \"tagSizeMedium\";\n			}\n			$tag_class .= \' tagSize\' . ($weight + 1 - $min);\n			if ($tag == $tag_search_tag) {\n				$tag_class .= \' \' . $activeclass;\n			}\n			$tag_class .= \'\"\';\n		}\n\n		//adapted from code by gdtroiano, see http://forum.textpattern.com/viewtopic.php?pid=102875#p102875\n		$titlecount = \'\';\n		if ($title)\n			$titlecount = \' title=\"\' . $title . \'\"\';\n		$displaycount= \'\';\n		$count = $countwrapchars{0} . $weight . $countwrapchars{1};\n		if ($showcounts == \'title\' || $showcounts == \'both\')\n			$titlecount = \' title=\"\' . $title . $count . \'\"\';\n		if ($showcounts && $showcounts != \'title\')\n			$displaycount = \' \' . $count;\n\n		if ($texttransform == \'capitalize\') {\n			$tag = tru_tags_ucwords($tag);\n		} else if ($texttransform == \'uppercase\') {\n			$tag = tru_tags_strtoupper($tag);\n		} else if ($texttransform == \'lowercase\') {\n			$tag = tru_tags_strtolower($tag);\n		} else if ($texttransform == \'capfirst\') {\n			$tag = tru_tags_ucfirst($tag);\n		}\n\n		if ($generatelinks) {\n			$wholeurl = \'\"\' . $urlprefix . urlencode($tag) . $urlsuffix . \'\"\';\n			$tags_html[] = \'<a href=\' . $wholeurl . $tag_class . $style . $titlecount . \'>\' . htmlspecialchars($tag) . \'</a>\' . $displaycount;\n		} else if ($tag_class || $style || $titlecount) {\n			$tags_html[] = \'<span\' . $tag_class . $style . $titlecount . \'>\' . htmlspecialchars($tag) . \'</span>\' . $displaycount;\n		} else {\n			$tags_html[] = htmlspecialchars($tag) . $displaycount;\n		}\n	}\n	return tru_tags_do_wrap($tags_html, $wraptag, $break, $class, $breakclass);\n}\n\n\n### CLEAN URL FUNCTIONS ###\n###########################\n\nif (tru_tags_clean_urls()) {\n	register_callback(\'tru_tags_clean_url_handler\', \'pretext\');\n}\n\n\nfunction tru_tags_clean_url_handler($event, $step) {\n	global $tru_tags_prefs;\n	$subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu);\n	$regsafesubpath = preg_quote($subpath, \'/\');\n	$req = preg_replace(\"/^$regsafesubpath/i\",\'/\',$_SERVER[\'REQUEST_URI\']);\n\n	$qs = strpos($req, \'?\');\n	$qatts = ($qs ? \'&\'.substr($req, $qs + 1) : \'\');\n	if ($qs) $req = substr($req, 0, $qs);\n\n	$parts = array_values(array_filter(explode(\'/\', $req)));\n	if (count($parts) == 2 && $parts[0] == $tru_tags_prefs[TAG_SECTION]->value) {\n		$tag = $parts[1];\n		$_SERVER[\'QUERY_STRING\'] = $tru_tags_prefs[PARM_NAME]->value . \'=\' . $tag . $qatts;\n		$_SERVER[\'REQUEST_URI\'] = $subpath . $tru_tags_prefs[TAG_SECTION]->value . \'/?\' . $_SERVER[\'QUERY_STRING\'];\n		if (count($_POST) > 0) {\n			$_POST[\'section\'] = $tru_tags_prefs[TAG_SECTION]->value;\n			$_POST[$tru_tags_prefs[PARM_NAME]->value] = $tag;\n		} else {\n			$_GET[\'section\'] = $tru_tags_prefs[TAG_SECTION]->value;\n			$_GET[$tru_tags_prefs[PARM_NAME]->value] = $tag;\n		}\n	}\n}\n\n\nfunction tru_tags_clean_urls() {\n	global $tru_tags_prefs;\n	return (\'clean\' == $tru_tags_prefs[CLEAN_URLS]->value);\n}\n\n\n### ADMIN SIDE FUNCTIONS ###\n############################\n\nif (@txpinterface == \'admin\') {\n	add_privs(\'tru_tags\', \'1,2\');\n	register_tab(\'extensions\', \'tru_tags\', \'tru_tags\');\n	register_callback(\'tru_tags_admin_tab\', \'tru_tags\');\n\n	if ($tru_tags_prefs[TAGS_IN_WRITE_TAB]->value || $tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value) {\n		register_callback(\'tru_tags_admin_write_tab_handler\', \'article_ui\', \'keywords\');\n	}\n}\n\n\nfunction tru_tags_admin_tab($event, $step) {\n	require_privs(\'tru_tags\');\n\n	$results = tru_tags_admin_tab_handle_input();\n\n	$atts = tru_tags_get_standard_cloud_atts(array(), false, false, true);\n	$all_tags = tru_tags_cloud_query($atts);\n	$cloud = tru_tags_render_cloud($atts, $all_tags, $all_tags);\n\n	$redirects = tru_tags_load_redirects();\n\n	tru_tags_admin_tab_render_page($results, $cloud, $redirects);\n}\n\n\nfunction tru_tags_admin_tab_render_page($results, $cloud, $redirects) {\n	global $event;\n	pagetop(\'tru_tags\', \'\');\n\n	include_once(txpath . \'/include/txp_prefs.php\');\n	global $tru_tags_prefs;\n\n	echo startTable(\'layout\', \'\', \'\', \'10px\').\'<tr><td style=\"border-right:2px solid gray\">\'.  # I know, I know...\n		startTable(\'layout\', \'\', \'\', \'\', \'10px\').\'<tr><td style=\"border-bottom:2px solid gray\">\'.\n			startTable(\'list\', \'\', \'\', \'\', \'300px\').\n				tr(hCell(gTxt(\'Current tags\'))).\n				tr(td($cloud)).\n			endTable().\n		\'</td></tr><tr><td>\'.\n			startTable(\'list\', \'\', \'\', \'\', \'300px\').\n				tr(hCell(gTxt(\'tru_tags Reference\'))).\n				tr(td(\'<a href=\"http://www.rainskit.com/reference/497/trutags-usage-instructions\">Usage instructions</a>\'.\n				\'<br><a href=\"http://forum.textpattern.com/viewtopic.php?id=15084\">Forum pages</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/537/tru_tags-releases\">Releases page</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/554/tru_tags-roadmap\">Release roadmap</a>\'.\n				\'<br><a href=\"http://www.rainskit.com/reference/495/trutags-feature-list\">Feature list</a>\'.\n				\'<br><br><a href=\"https://github.com/truist/tru-tags/blob/master/tru_tags.php\">tru_tags source code</a>\'.\n				\'<br><a href=\"https://github.com/truist/tru-tags/blob/master/tru_tags-docs.html\">Source HTML for the usage instructions</a>\'.\n				\'<br><br><a href=\"http://www.rainskit.com/blog/493/trutags-a-tagging-plugin-for-textpattern\">tru_tags</a>, by <a href=\"http://www.rainskit.com/\">Nathan Arthur</a>\'.\n				\'<br><br>\'.\n				\'<div id=\"paypal\"><form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\"><input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" /><input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but04.gif\" name=\"submit\" alt=\"Make a donation to Nathan Arthur\" /><img alt=\"\" border=\"0\" src=\"https://www.paypal.com/en_US/i/scr/pixel.gif\" width=\"1\" height=\"1\" /><input type=\"hidden\" name=\"encrypted\" value=\"-----BEGIN PKCS7-----MIIHTwYJKoZIhvcNAQcEoIIHQDCCBzwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYC8i1T27ljKfpNTEQi0wKHcdGulxxkMqwdCMmbGvs87n/4TsJtiAsqMo2hys7ZsGy5RF/O7s+B2oQ76zUlT52WW7QeXUK3Gp0nr2cP3ioBStNu+RZ6jkam2E0FGLXyV6+UNVEOwh8lmoISRotvSvIgQyTLnEeDHqG9qvUzqvF3SqjELMAkGBSsOAwIaBQAwgcwGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIpmPZrlrZfZmAgaiePVb+n9sVdsufgGrmAw2rXAzR39kYqPUJ7n0LiNDmdAq73JoP53kZy8gSpovucL2S0jC1sXrcpELApLL8BFSHfdLiZoZSV/CYOppH5+dx2YqFIdyCCdjIX7oOPgQyAugRa2Qr3b+yutuG0DFsd+LAJGb8l4CnnrbmwdYK3NnVDBPOmxEOjlXUgEzlFLXmE3w5+MoPKQcp2n8fdJLsgG15xoVPFzCd/K2gggOHMIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQUFADCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wHhcNMDQwMjEzMTAxMzE1WhcNMzUwMjEzMTAxMzE1WjCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMFHTt38RMxLXJyO2SmS+Ndl72T7oKJ4u4uw+6awntALWh03PewmIJuzbALScsTS4sZoS1fKciBGoh11gIfHzylvkdNe/hJl66/RGqrj5rFb08sAABNTzDTiqqNpJeBsYs/c2aiGozptX2RlnBktH+SUNpAajW724Nv2Wvhif6sFAgMBAAGjge4wgeswHQYDVR0OBBYEFJaffLvGbxe9WT9S1wob7BDWZJRrMIG7BgNVHSMEgbMwgbCAFJaffLvGbxe9WT9S1wob7BDWZJRroYGUpIGRMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAIFfOlaagFrl71+jq6OKidbWFSE+Q4FqROvdgIONth+8kSK//Y/4ihuE4Ymvzn5ceE3S/iBSQQMjyvb+s2TWbQYDwcp129OPIbD9epdr4tJOUNiSojw7BHwYRiPh58S1xGlFgHFXwrEBb3dgNbMUa+u4qectsMAXpVHnD9wIyfmHMYIBmjCCAZYCAQEwgZQwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tAgEAMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wODA3MDMwNDE0MTJaMCMGCSqGSIb3DQEJBDEWBBT0tkj4dZLe/E4Qwbib29XEdHxAYjANBgkqhkiG9w0BAQEFAASBgL5JsQHjQ9Sg4Y3eDWKDO16r+tfEz4RYADt+6h981fkVCxfNHFDxofDcxyzRMYr7y95cdnVi4ANQwMUY6yJW5jm/GD17rjgSxZMEvsAe6YcCSLK5ZapCw1qlySpPGZBA3MTt6OD+ovVoa/1v8CNsEcHp7f4tOxOUSw5P4nHyLPWj-----END PKCS7-----\"></form></div>\'\n				)).\n			endTable().\n		\'</td></tr>\'.endTable().\n	\'</td><td>\'.\n		startTable(\'layout\', \'\', \'\', \'10px\').\'<tr><td style=\"border-bottom:2px solid gray;width:400px\">\';\n\n		if ($results) {\n			echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n				tr(hCell(gTxt(\'Results\'))).\n				tr(tda($results), \' style=\"color:red\"\').\n			endTable();\n			echo \'</td></tr><tr><td style=\"border-bottom:2px solid gray\">\';\n		}\n\n\n		echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n			tr(tag(gTxt(\'Article Tag Maintenance\').\' (\'.gTxt(\'Case Sensitive\').\'!)\', \'th\', \' colspan=\"5\"\')).\n			tr(\n				form(\n					tda(gTxt(\'Rename\').\': \', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'matchtag\', \'\', \'15\')).\n					tda(gTxt(\'to\').\':\', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'replacetag\', \'\', \'15\')).\n					td(fInput(\'submit\', \'replace\', gTxt(\'Run\'), \'publish\').eInput(\'tru_tags\')),\n					\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n				)\n			).\n			tr(\n				form(\n					tda(gTxt(\'Delete\').\': \', \' style=\"vertical-align:middle\"\').\n					td(text_input(\'deletetag\', \'\', \'15\')).\n					tdcs(\'\', 2).\n					td(fInput(\'submit\', \'delete\', gTxt(\'Run\'), \'publish\').eInput(\'tru_tags\')),\n					\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n				)\n			).\n		endTable();\n\n	echo \'</td></tr><tr><td style=\"border-bottom:2px solid gray\">\';\n\n		echo startTable(\'list\', \'\', \'\', \'\', \'100%\').\n			tr(tag(gTxt(\'Redirections\'), \'th\', \' colspan=\"4\"\'));\n			foreach ($redirects as $lefttag => $righttag) {\n				echo tr(\n					tda(href($lefttag, tru_tags_linkify_tag($lefttag)), \' style=\"text-align: center\"\').\n					tda(htmlspecialchars(\'=>\'), \' style=\"text-align: center\"\').\n					tda(href($righttag, tru_tags_linkify_tag($righttag)), \' style=\"text-align: center\"\').\n					td(\'<a href=\"index.php?event=tru_tags&amp;delete_redirect=\'.urlencode($lefttag).\'\"  onclick=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">Delete</a>\')\n				);\n			}\n			echo tr(\n				\'<form name=\"redirect\" id=\"redirect\" method=\"post\" action=\"index.php?event=tru_tags\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\n					tda(text_input(\'lefttag\', \'\', \'15\'), \' style=\"text-align:center\"\').\n					tda(htmlspecialchars(\'=>\'), \' style=\"vertical-align:middle;text-align:center\"\').\n					tda(text_input(\'righttag\', \'\', \'15\'), \' style=\"text-align:center\"\').\n					tda(\'<a href=\"#\" onclick=\"if (verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')) document.getElementById(\\\'redirect\\\').submit(); return false;\">Add new</a>\', \' style=\"vertical-align:middle\"\').\n					fInput(\'hidden\', \'redirect\', \'1\').\n				\'</form>\'\n\n			).\n		endTable();\n\n	echo \'</td></tr><tr><td>\';\n\n		echo startTable(\'list\').\n			tr(tag(gTxt(\'Preferences\'), \'th\', \' colspan=\"2\"\')).\n			form(\n				tr(\n					tda(gTxt(\'Use clean URLs\').\' (\'.gTxt(\'site default is\').\' \'.$tru_tags_prefs[CLEAN_URLS]->default_value.\'): \', \' style=\"vertical-align:middle\"\').\n					td(radio_list(CLEAN_URLS,\n							array(\'clean\'=>gTxt(\'clean\'), \'messy\'=>gTxt(\'messy\')),\n							$tru_tags_prefs[CLEAN_URLS]->value))\n				).\n				tr(\n					tda(gTxt(\'Tag section name\').\' (default is \"\'.$tru_tags_prefs[TAG_SECTION]->default_value.\'\"): \', \' style=\"vertical-align:middle\"\').\n					td(text_input(TAG_SECTION, $tru_tags_prefs[TAG_SECTION]->value, \'15\'))\n				).\n				tr(\n					tda(gTxt(\'URL parameter for tag search\').\' (default is \"\'.$tru_tags_prefs[PARM_NAME]->default_value.\'\"): \'.\n						\'<br><em>(you shouldn\\\'t change this unless you really know what you are doing)</em>\', \' style=\"vertical-align:middle\"\').\n					td(text_input(PARM_NAME, $tru_tags_prefs[PARM_NAME]->value, \'15\'))\n				).\n				tr(\n					tda(gTxt(\'Put tags into RSS/Atom feeds, in \"Category\" elements\').\n						\': <br><em>(you probably want this)</em>\', \' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_FEED_CATEGORIES, $tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value))\n				).\n				tr(\n					tda(\'Append the tag list to the body of RSS/Atom feeds, \'.\n						\'with links, and with rel=\"tag\":\'.\n						\'<br><em>(if this is turned on,\'.\n						\'you can define a \"misc\" form named tru_tags_feed_tags \'.\n						\'that will be used to render the tags in the feed)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_FEED_BODY, $tru_tags_prefs[TAGS_IN_FEED_BODY]->value))\n				).\n				tr(\n					tda(gTxt(\'Show a clickable list of tags on the \"Write\" page\').\': \',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(TAGS_IN_WRITE_TAB, $tru_tags_prefs[TAGS_IN_WRITE_TAB]->value))\n				).\n				tr(\n					tda(gTxt(\'Autocomplete tags on the \"Write\" page\').\': <br>\'.\n						\'<em>(requires that the <a href=\"http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/\">jQuery Autocomplete plugin</a> <code>.css</code> and <code>.min.js</code> files be uploaded into a \"<code>js</code>\" folder on your site)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(AUTOCOMPLETE_IN_WRITE_TAB, $tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value))\n				).\n				tr(\n					tda(gTxt(\'Convert tags to lowercase by default\').\': <br>\'.\n						\'<em>(you probably want this, unless you are having problems with utf-8 characters)</em>\',\n						\' style=\"vertical-align:middle\"\').\n					td(yesnoRadio(UTF_8_CASE, $tru_tags_prefs[UTF_8_CASE]->value))\n				).\n				tr(\n					td(\'\').\n					tda(fInput(\'submit\', \'prefs\', gTxt(\'Save\'), \'publish\').eInput(\'tru_tags\'), \' style=\"text-align:right\"\')\n				),\n				\'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n			).\n		endTable().\'</td></tr>\'.\n		endTable();\n\n	echo \'</td></tr>\'.endTable();\n}\n\n\nfunction tru_tags_admin_tab_handle_input() {\n	if (gps(\'prefs\')) {\n		return tru_tags_admin_update_prefs();\n	} else if (gps(\'delete\')) {\n		return tru_tags_admin_delete_tag(gps(\'deletetag\'));\n	} else if (gps(\'replace\')) {\n		$result = tru_tags_admin_replace_tag(gps(\'matchtag\'), gps(\'replacetag\'), false);\n		return $result . \'<br>\' . tru_tags_admin_redirect_tag(gps(\'matchtag\'), gps(\'replacetag\'));\n	} else if (gps(\'redirect\')) {\n		return tru_tags_admin_redirect_tag(gps(\'lefttag\'), gps(\'righttag\'));\n	} else if (gps(\'delete_redirect\')) {\n		return tru_tags_admin_delete_redirect(gps(\'delete_redirect\'));\n	} else {\n		return \'\';\n	}\n}\n\n\nfunction tru_tags_admin_update_prefs() {\n	global $tru_tags_prefs;\n	$results = array();\n	foreach ($tru_tags_prefs as $pref) {\n		$valid_value = $pref->validate_value(gps($pref->name));\n		if ($valid_value && $valid_value <> gps($pref->name)) {\n			return $valid_value;  ### this is a sneaky way to handle validation - sorry ;)\n		}\n		if ($valid_value <> $pref->value) {\n			if ($valid_value == $pref->default_value) {\n				$result = tru_tags_delete_pref($pref->name);\n			} else {\n				$result = tru_tags_upsert_pref($pref->name, $valid_value);\n			}\n			if ($result) {\n				$results[] = \'Updated \' . $pref->name;\n				$pref->value = $valid_value;\n			} else {\n				$results[] = \'Error updating \' . $pref->name;\n			}\n		}\n	}\n	if (count($results) == 0) {\n		return \'No records need updating\';\n	} else {\n		return join(\'<br>\', $results);\n	}\n}\n\n\nfunction tru_tags_upsert_pref($name, $value) {\n	if (!safe_query(\'create table if not exists \' . safe_pfx(\'tru_tags_prefs\').\n		\' (name varchar(255) primary key, \'.\n		\'value varchar(255) not null)\')) {\n		return \'Serious error - unable to create the \' . safe_pfx(\'tru_tags_prefs\') . \' table.\';\n	}\n\n	return safe_upsert(\'tru_tags_prefs\', \'value=\"\'.$value.\'\"\', \'name=\"\'.$name.\'\"\');\n}\n\n\nfunction tru_tags_delete_pref($name) {\n	if (safe_delete(\'tru_tags_prefs\', \'name=\"\'.$name.\'\"\')) {\n		if (safe_count(\'tru_tags_prefs\', \'1\') == 0 && !safe_query(\'drop table \' . safe_pfx(\'tru_tags_prefs\'))) {\n			return false;\n		}\n		return true;\n	} else {\n		return false;\n	}\n}\n\n\nfunction tru_tags_admin_delete_tag($deletetag) {\n	if (trim($deletetag)) {\n		return tru_tags_admin_replace_tag($deletetag, \'\', true);\n	} else {\n		return \'Please enter a value\';\n	}\n}\n\n\nfunction tru_tags_admin_replace_tag($matchtag, $replacetag, $allow_blank_replacetag) {\n	$matchtag = trim($matchtag);\n	$replacetag = trim($replacetag);\n	if ($matchtag && ($allow_blank_replacetag || $replacetag)) {\n		if (safe_update(\'textpattern\', TRU_TAGS_FIELD.\'=trim(both \\\',\\\' from replace(concat(\",\", \'.TRU_TAGS_FIELD.\', \",\"), concat(\",\", \\\'\'.addslashes($matchtag).\'\\\', \",\"), \\\',\'.addslashes($replacetag).\',\\\'))\', \'1\')) {\n			return \'Updated \'.mysqli_affected_rows().\' rows (\"\'.htmlspecialchars($matchtag).\'\"=>\"\'.htmlspecialchars($replacetag).\'\")\';\n		} else {\n			return \'Error: \' . mysqli_error();\n		}\n	} else {\n		return \'Please enter a value in both fields\';\n	}\n}\n\n\nfunction tru_tags_admin_redirect_tag($lefttag, $righttag) {\n	$lefttag = addslashes(tru_tags_strtolower(trim($lefttag)));\n	$righttag = addslashes(tru_tags_strtolower(trim($righttag)));\n	if (!$lefttag || !$righttag) {\n		return \'Please enter a value in both fields\';\n	}\n\n	if (!safe_query(\'create table if not exists \' . safe_pfx(\'tru_tags_redirects\').\n		\' (lefttag varchar(255) primary key, \'.\n		\'righttag varchar(255) not null)\')) {\n		return \'Serious error - unable to create the \' . safe_pfx(\'tru_tags_redirects\') . \' table.\';\n	}\n\n	if (safe_insert(\'tru_tags_redirects\', \'lefttag=\"\'.$lefttag.\'\",righttag=\"\'.$righttag.\'\"\')) {\n		return \'Redirect added (\"\'.htmlspecialchars($lefttag).\'\"=>\"\'.htmlspecialchars($righttag).\'\") - please test it!\';\n	} else {\n		return \'Error adding record - does it already exist?\';\n	}\n}\n\n\nfunction tru_tags_admin_delete_redirect($lefttag) {\n	if (safe_delete(\'tru_tags_redirects\', \'lefttag=\"\'.addslashes($lefttag).\'\"\')) {\n		if (safe_count(\'tru_tags_redirects\', \'1\') == 0 && !safe_query(\'drop table \' . safe_pfx(\'tru_tags_redirects\'))) {\n			return \'Redirect deleted, but unable to drop table \' . safe_pfx(\'tru_tags_redirects\');\n		}\n		return \'Redirect deleted (\"\'.htmlspecialchars($lefttag).\'\")\';\n	} else {\n		return \'Error deleting redirect\';\n	}\n}\n\n\nfunction tru_tags_admin_write_tab_handler($event, $step, $data) {\n	global $tru_tags_prefs;\n	$atts = tru_tags_get_standard_cloud_atts(array(), true, true, true);\n	$cloud = array_unique(tru_tags_cloud_query($atts));\n	natcasesort($cloud);\n\n	$id = (empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\']);\n	$article_tags = (empty($id) ? array() : tru_tags_get_tags_for_article($id));\n\n	$links = array();\n	$raw_cloud = \'\';\n	foreach ($cloud as $tag) {\n		$style = (in_array($tag, $article_tags) ? \' class=\"tag_chosen\"\' : \'\');\n		$links[] = \'<a href=\"#advanced\"\'.$style.\' onclick=\"toggleClass(this, toggleTag(\\\\\\\'\'.addslashes(addslashes(htmlspecialchars($tag))).\'\\\\\\\')); return false;\">\' . addslashes(htmlspecialchars($tag)) . \'<\\/a>\';\n		$raw_cloud .= \'\"\' . addSlashes($tag) . \'\",\';\n	}\n	$raw_cloud = substr($raw_cloud, 0, strlen($raw_cloud) - 1);\n	$to_insert = join(\', \', $links);\n\n	if ($tru_tags_prefs[TAGS_IN_WRITE_TAB]->value) {\n		$js = <<<EOF\n			var keywordsField = document.getElementById(\'keywords\');\n			keywordsField.parentNode.appendChild(document.createElement(\'br\'));\n			var cloud = document.createElement(\'span\');\n			keywordsField.parentNode.appendChild(cloud);\n			cloud.setAttribute(\'class\', \'tru_tags_admin_tags\');\n			cloud.innerHTML = \'{$to_insert}\';\n\n			var styleElement = document.createElement(\'style\');\n			styleElement.setAttribute(\"type\", \"text/css\");\n			var styleText = \'a.tag_chosen{background-color: #FEB; color: black;}\';\n			if (styleElement.styleSheet) { //IE\n				styleElement.styleSheet.cssText = styleText;\n			} else {\n				var textNode = document.createTextNode(styleText);\n				styleElement.appendChild(textNode);\n			}\n			var headElement = document.getElementsByTagName(\'head\')[0];\n			headElement.appendChild(styleElement);\n\n			function toggleTag(tagName) {\n				var regexTag = tagName.replace(/([\\\\\\\\\\^\\\\$*+[\\\\]?{}.=!:(|)])/g,\"\\\\\\\\$1\");\n				var tagRegex = new RegExp(\"((^|,)\\\\\\s*)\" + regexTag + \"\\\\\\s*(,\\\\\\s*|$)\");\n				var textarea = document.getElementById(\'keywords\');\n				var curval = textarea.value.replace(/,?\\s+$/, \'\');\n				if (\'\' == curval) {\n					textarea.value = tagName;\n				} else if (curval.match(tagRegex)) {\n					textarea.value = curval.replace(tagRegex, \'$1\').replace(/,?\\s+$/, \'\');\n					return \'\';\n				} else if (\',\' == curval.charAt(curval.length - 1)) {\n					textarea.value += \' \' + tagName;\n				} else {\n					textarea.value += \', \' + tagName;\n				}\n				return \'tag_chosen\';\n			}\n\n			function toggleClass(element, className) {\n				element.setAttribute(\'class\', className);\n				element.setAttribute(\'className\', className); /* for IE */\n			}\nEOF;\n		return $data . script_js($js);\n	}\n\n	if ($tru_tags_prefs[AUTOCOMPLETE_IN_WRITE_TAB]->value) {\n		echo <<<EOF\n			<link rel=\"stylesheet\" type=\"text/css\" href=\"../js/jquery.autocomplete.css\" media=\"projection, screen\" />\n			<script src=\"../js/jquery.autocomplete.min.js\" type=\"text/javascript\"></script>\n			<script language=\"JavaScript\" type=\"text/javascript\"><!--\n				$(document).ready(function(){ var tags = new Array({$raw_cloud});\n					$(\"#keywords\").autocomplete(tags, { multiple: true, mustMatch: false, autoFill: false });\n				});\n			//--></script>\nEOF;\n	}\n\n}\n\n\n### ATOM/RSS FEED FUNCTIONS ###\n###############################\n\nregister_callback(\'tru_tags_atom_handler\', \'atom_entry\');\nfunction tru_tags_atom_handler($event, $step) { return tru_tags_feed_handler(true); }\nregister_callback(\'tru_tags_rss_handler\', \'rss_entry\');\nfunction tru_tags_rss_handler($event, $step) { return tru_tags_feed_handler(false); }\n\nfunction tru_tags_feed_handler($atom) {\n	global $thisarticle, $tru_tags_prefs;\n	extract($thisarticle);\n\n	$tags = tru_tags_get_tags_for_article($thisid);\n\n	if ($tru_tags_prefs[TAGS_IN_FEED_BODY]->value) {\n		$extrabody = \'\';\n		$FORM_NAME = \'tru_tags_feed_tags\';\n		if (fetch(\'form\', \'txp_form\', \'name\', $FORM_NAME)) {\n			$form = fetch_form($FORM_NAME);\n			$extrabody = trim(parse($form));\n		} else {\n			$atts = tru_tags_get_standard_cloud_atts(array(), false, true);\n			$extrabody = \'<p>tags: \' . tru_tags_render_cloud($atts, $tags, $tags) . \'</p>\';\n		}\n		global $thisarticle;\n		if (trim($thisarticle[\'excerpt\'])) {\n			$thisarticle[\'excerpt\'] = trim($thisarticle[\'excerpt\']).n.$extrabody.n;\n		}\n		$thisarticle[\'body\'] = trim($thisarticle[\'body\']).n.$extrabody.n;\n	}\n\n	if ($tru_tags_prefs[TAGS_IN_FEED_CATEGORIES]->value) {\n		$output = array();\n		foreach ($tags as $tag) {\n			if ($atom) {\n				$output[] = \'<category term=\"\' . htmlspecialchars($tag) . \'\" />\';\n			} else {\n				$output[] = \'<category>\' . htmlspecialchars($tag) . \'</category>\';\n			}\n		}\n		return n.join(n, $output).n;\n	}\n}\n\n\n### PREFS FUNCTIONS / CLASSES ###\n#################################\n\nclass tru_tags_pref {\n	function __construct($name, $default_value, $type) {\n		$this->name = $name;\n		$this->value = $default_value;\n		$this->default_value = $default_value;\n		$this->type = $type;\n	}\n\n	function validate_value($value) {\n		if ($value) {\n			return $value;\n		} else if ($this->type == \'boolean\') {\n			return \'0\';\n		} else {\n			return \'Please enter a value for \' . $this->name;\n		}\n	}\n}\n\nfunction tru_tags_load_prefs() {\n	$prefs = array();\n\n	global $permlink_mode;\n	$prefs[CLEAN_URLS] = new tru_tags_pref(CLEAN_URLS, ($permlink_mode != \'messy\' ? \'clean\' : \'messy\'), \'string\');\n	$prefs[TAG_SECTION] = new tru_tags_pref(TAG_SECTION, \'tag\', \'string\');\n	$prefs[PARM_NAME] = new tru_tags_pref(PARM_NAME, \'t\', \'string\');\n	$prefs[TAGS_IN_FEED_CATEGORIES] = new tru_tags_pref(TAGS_IN_FEED_CATEGORIES, \'1\', \'boolean\');\n	$prefs[TAGS_IN_FEED_BODY] = new tru_tags_pref(TAGS_IN_FEED_BODY, \'0\', \'boolean\');\n	$prefs[TAGS_IN_WRITE_TAB] = new tru_tags_pref(TAGS_IN_WRITE_TAB, \'1\', \'boolean\');\n	$prefs[AUTOCOMPLETE_IN_WRITE_TAB] = new tru_tags_pref(AUTOCOMPLETE_IN_WRITE_TAB, \'0\', \'boolean\');\n	$prefs[UTF_8_CASE] = new tru_tags_pref(UTF_8_CASE, \'1\', \'boolean\');\n\n	if (safe_query(\"describe \" . PFX . \"tru_tags_prefs\")) {\n		$rs = safe_rows(\'*\', \'tru_tags_prefs\', \'1\');\n		foreach ($rs as $row) {\n			$prefs[$row[\'name\']]->value = $row[\'value\'];\n		}\n	}\n\n	return $prefs;\n}\n\n\n### OTHER SUPPORT FUNCTIONS ###\n###############################\n\nfunction tru_tags_redirect_if_needed($tag_parameter) {\n	$redirects = tru_tags_load_redirects();\n	foreach ($redirects as $lefttag => $righttag) {\n		if ($lefttag == $tag_parameter || $lefttag == str_replace(\'-\', \' \', $tag_parameter)) {\n			tru_tags_redirect($righttag, false);\n			return true;\n		}\n	}\n\n	return false;\n}\n\n\nfunction tru_tags_load_redirects() {\n	$redirects = array();\n	if (safe_query(\"describe \" . PFX . \"tru_tags_redirects\")) {\n		$rs = safe_rows(\'*\', \'tru_tags_redirects\', \'1 order by lefttag\');\n		foreach ($rs as $row) {\n			$redirects[$row[\'lefttag\']] = $row[\'righttag\'];\n		}\n	}\n	return $redirects;\n}\n\n\nfunction tru_tags_redirect($destination, $is_full_url) {\n	global $tru_tags_prefs;\n	if ($is_full_url) {\n		$url = $destination;\n		$message = \'The resource you requested has moved to \' . $destination;\n	} else {\n		$url = tru_tags_linkify_tag($destination, false);\n		$message = \'The requested tag has been replaced by \' . $destination;\n	}\n\n	header(\'Location: \' . $url);\n	txp_die($message, \'301\');\n}\n\n\nfunction tru_tags_linkify_tag($tag, $use_amp = true) {\n	global $tru_tags_prefs;\n	if (tru_tags_clean_urls()) {\n		$urlprefix = hu . $tru_tags_prefs[TAG_SECTION]->value . \'/\';\n	} else {\n		$urlprefix = hu . \'?s=\' . $tru_tags_prefs[TAG_SECTION]->value . ($use_amp ? \'&amp;\' : \'&\') . $tru_tags_prefs[PARM_NAME]->value . \'=\';\n	}\n	$urlsuffix = (tru_tags_clean_urls() ? \'/\' : \'\');\n	return $urlprefix . urlencode($tag) . $urlsuffix;\n}\n\n\nfunction tru_tags_get_tags_for_article($articleID) {\n	global $thisarticle, $tru_tags_prefs;\n	if (isset($thisarticle)) {\n		extract($thisarticle);\n	}\n	$keywords = \'\';\n	if (isset($thisid) && $thisid == $articleID) {\n		if (isset($thisarticle[\'keywords\'])) {\n			$keywords = $thisarticle[\'keywords\'];\n		}\n	} else {\n		$tags_field = TRU_TAGS_FIELD;\n		$rs = safe_row($tags_field, \"textpattern\", \"ID=\'$articleID\' AND $tags_field <> \'\'\");\n		if (isset($rs[$tags_field])) {\n			$keywords = $rs[$tags_field];\n		}\n	}\n	$all_tags = array();\n	$all_tags = explode(\",\", trim(tru_tags_strtolower($keywords)));\n\n	return tru_tags_trim_tags($all_tags);\n}\n\n\n# fixes bug with 4.0.4\'s version of doWrap that caused spaces to show up before the commas\nfunction tru_tags_do_wrap($list, $wraptag, $break, $class, $breakclass) {\n	if (!$wraptag && !preg_match(\'/^\\w+$/\', $break)) {\n		return join($break.n, $list);\n	} else {\n		return doWrap($list, $wraptag, $break, $class, $breakclass);\n	}\n}\n\n\nfunction tru_tags_trim_tags($tag_array) {\n	$trimmed = array();\n	foreach ($tag_array as $tag) {\n		if (\"\" != trim($tag)) {\n			$trimmed[] = trim($tag);\n		}\n	}\n	return $trimmed;\n}\n\n\nfunction tru_tags_fixup_query_atts($atts, $tag_parameter) {\n	$keywords = explode(\',\', $tag_parameter);\n	foreach ($keywords as $keyword) {\n		if (strpos($keyword, \'-\') !== false) {\n			$keywords[] = str_replace(\'-\', \' \', $keyword);\n		}\n	}\n	$atts[\'keywords\'] = implode(\',\', $keywords);\n\n	if (isset($atts[\'section\']) && strpos($atts[\'section\'], \',\') !== false) {\n		$atts[\'section\'] = \'\';\n	}\n\n	if (isset($atts[\'excludesection\'])) {\n		unset($atts[\'excludesection\']);\n	}\n\n	if (isset($atts[\'noarticles\'])) {\n		unset($atts[\'noarticles\']);\n	}\n\n	if (!isset($atts[\'limit\'])) {\n		$atts[\'limit\'] = \'1000\';\n	}\n\n	if (!isset($atts[\'allowoverride\'])) {\n		$atts[\'allowoverride\'] = true;\n	}\n\n	if (isset($atts[\'searchform\']) && !isset($atts[\'listform\'])) {\n		$atts[\'listform\'] = $atts[\'searchform\'];\n	}\n\n	if (isset($atts[\'showcounts\'])) {\n		unset($atts[\'showcounts\']);\n	}\n\n	if (isset($atts[\'404redirect\'])) {\n		unset($atts[\'404redirect\']);\n	}\n\n	return $atts;\n}\n\n\nfunction tru_tags_strtolower($str) {\n	global $tru_tags_prefs;\n	if ($tru_tags_prefs[UTF_8_CASE]->value) {\n		if (function_exists(\'mb_strtolower\')) {\n			return mb_strtolower($str, ENCODING);\n		} else {\n			return strtolower($str);\n		}\n	} else {\n		return $str;\n	}\n}\n\nfunction tru_tags_strtoupper($str) {\n	if (function_exists(\'mb_strtoupper\')) {\n		return mb_strtoupper($str, ENCODING);\n	} else {\n		return strtoupper($str);\n	}\n}\n\nfunction tru_tags_ucfirst($str) {\n	if (function_exists(\'mb_substr\') && function_exists(\'mb_strtoupper\') && function_exists(\'mb_strlen\') && mb_strlen($str, ENCODING) > 1) {\n		return mb_strtoupper(mb_substr($str, 0, 1, ENCODING), ENCODING) . mb_substr($str, 1, mb_strlen($str, ENCODING), ENCODING);\n	} else {\n		return ucfirst($str);\n	}\n}\n\nfunction tru_tags_ucwords($str) {\n	if (function_exists(\'mb_convert_case\')) {\n		return mb_convert_case($str, MB_CASE_TITLE, ENCODING);\n	} else {\n		return ucwords($str);\n	}\n}\n\n\n//these next two functions are gross, but I can\'t figure out another way to do it\nfunction tru_tags_sort_tags($tags_weight, $sort_by_count, $sort_ascending) {\n	global $tru_tags_tags_weight, $tru_tags_sort_ascending;\n\n	$tru_tags_tags_weight = $tags_weight;\n	$tru_tags_sort_ascending = $sort_ascending;\n\n	$temp_array = array_keys($tags_weight);\n	if ($sort_by_count) {\n		usort($temp_array, \"tru_tags_sort_tags_comparator\");\n	} else {\n		natcasesort($temp_array);\n		$temp_array = array_values($temp_array);\n	}\n\n	$sorted_array = array();\n	foreach ($temp_array as $tag) {\n		$sorted_array[$tag] = $tags_weight[$tag];\n	}\n\n	return $sorted_array;\n}\n\n\nfunction tru_tags_sort_tags_comparator($left, $right) {\n	global $tru_tags_tags_weight, $tru_tags_sort_ascending;\n\n	$left_weight = $tru_tags_tags_weight[$left];\n	$right_weight = $tru_tags_tags_weight[$right];\n	if ($left_weight == $right_weight) {\n		$temp_array = array($left, $right);\n		natcasesort($temp_array);\n		$temp_array = array_values($temp_array);\n		return ($temp_array[0] == $left ? -1 : 1);\n	} else if ($tru_tags_sort_ascending) {\n		return $left_weight - $right_weight;\n	} else {\n		return $right_weight - $left_weight;\n	}\n}\n\n\n\n### BASTARD FUNCTIONS THAT SHOULDN\'T HAVE TO EXIST ###\n######################################################\n\n\nfunction tru_tags_redo_article_search($atts) {\n	$theAtts = lAtts(array(\'limit\'     => 1000,\n				\'category\'  => \'\',\n				\'section\'   => \'\',\n				\'excerpted\' => \'\',\n				\'author\'    => \'\',\n				\'sortby\'    => \'Posted\',\n				\'sortdir\'   => \'desc\',\n				\'month\'     => \'\',\n				\'keywords\'  => \'\',\n				\'frontpage\' => \'\',\n				\'id\'        => \'\',\n				\'time\'      => \'past\',\n				\'status\'    => \'4\',\n				\'offset\'    => 0\n			),$atts, 0);\n	extract($theAtts);\n\n	//Building query parts\n	$frontpage = ($frontpage) ? filterFrontPage() : \'\';\n	$category  = (!$category)  ? \'\' : \" and ((Category1=\'\".doslash($category).\"\') or (Category2=\'\".doSlash($category).\"\')) \";\n	$section   = (!$section)   ? \'\' : \" and Section = \'\".doslash($section).\"\'\";\n	$excerpted = ($excerpted==\'y\')  ? \" and Excerpt !=\'\'\" : \'\';\n	$author    = (!$author)    ? \'\' : \" and AuthorID = \'\".doslash($author).\"\'\";\n	$month     = (!$month)     ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\n	$id        = (!$id)        ? \'\' : \" and ID = \'\".intval($id).\"\'\";\n	switch ($time) {\n		case \'any\':\n			$time = \"\"; break;\n		case \'future\':\n			$time = \" and Posted > now()\"; break;\n		default:\n			$time = \" and Posted < now()\";\n	}\n	if (!is_numeric($status))\n		$status = getStatusNum($status);\n\n	$custom = \'\';\n	// trying custom fields here\n	$customFields = getCustomFields();\n	if ($customFields) {\n		foreach($customFields as $cField) {\n			if (isset($atts[$cField]))\n				$customPairs[$cField] = $atts[$cField];\n		}\n		if(!empty($customPairs))\n			$custom =  buildCustomSql($customFields,$customPairs);\n		else\n			$custom = \'\';\n	}\n\n	//Allow keywords for no-custom articles. That tagging mode, you know\n	if ($keywords) {\n		$keys = doSlash(array_map(\'trim\', explode(\',\', $keywords)));\n		foreach ($keys as $key) {\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n		}\n		$keywords = \" and (\" . join(\' or \',$keyparts) . \")\";\n	}\n\n	if ($id)\n		$statusq = \" and Status >= \'4\'\";\n	else\n		$statusq = \" and Status=\'\".doSlash($status).\"\'\";\n\n	$where = \"1\" . $statusq. $time.\n		$id . $category . $section . $excerpted . $month . $author . $keywords . $custom . $frontpage;\n\n		$pgoffset = $offset . \', \';\n\n	$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted\", \'textpattern\',\n		$where. \' order by \' . doslash($sortby) . \' \' . doSlash($sortdir) . \' limit \' . doSlash($limit));\n\n	return $rs;\n}\n\n\n', 'e804f0571103dcbef02946526a043a9e', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('tru_podcast', 0, 'Nathan Arthur', 'http://www.rainskit.com/', '1.2', 'Podcasting support for Textpattern', '<p>To learn more about tru_podcast, check out the <a href=\"http://www.rainskit.com/blog/542/tru_podcast-a-podcasting-plugin-for-textpattern\" rel=\"nofollow\">introductory article</a>, <a href=\"http://www.rainskit.com/reference/539/tru_podcast-releases\" rel=\"nofollow\">releases page</a>, <a href=\"http://www.rainskit.com/reference/540/tru_podcast-feature-list\" rel=\"nofollow\">feature list</a>, and <a href=\"http://www.rainskit.com/reference/541/tru_podcast-usage-instructions\" rel=\"nofollow\">usage instructions</a>.</p>', '\n#Copyright 2007 Nathan Arthur\n#Released under the GNU Public License, see http://www.opensource.org/licenses/gpl-license.php for details\n#This work is based partially on WordPress code; see http://trac.wordpress.org/browser/trunk/wp-includes/functions.php?rev=4725\n\n\n### CONFIGURATION ###\n#####################\n\n#See http://www.rainskit.com/reference/541/tru_podcast-usage-instructions for instructions\n\n# Set this to the web-accessible URL of Textpattern\'s \"files\" directory\n# If you haven\'t customized your \"File directory path\" setting, this should be right already\ndefine(\'TRU_PODCAST_FILES_URL\', hu . \'files/\');\n\n# When you upload a file via TXP\'s \"Files\" interface, TXP-generated URLs that point to that\n# file always look like \"/file_download/3\", if you have clean urls turned on.  Some podcast\n# clients get confused by the number (\"3\") and don\'t know what name to give the downloaded\n# file.  (TXP has a broken file-download implementation.)  By default, if you have clean\n# URLs turned on, tru_podcast will generate links like \"/file_download/test.mp3\".  I haven\'t\n# observed any negative side effects from this choice, but if you want tru_podcast to\n# generate the \'standard\' URLs, switch this flag to \'0\'.  Note that iTunes and a few other\n# podcast clients seem to misbehave if you set this to \'0\', though.\ndefine(\'TRU_PODCAST_USE_FILENAMES\', \'1\');\n\n# Set this to \'1\' if you want tru_podcast to generate direct links to files uploaded via\n# Textpattern\'s \"Files\" interface, rather than using TXP\'s built-in file download feature.\n# If you\'ve set TRU_PODCAST_USE_FILENAMES to \'0\', you might want to set this to \'1\'.\ndefine(\'TRU_PODCAST_USE_DIRECT_LINKS\', \'0\');\n\n\n### CODE ###\n#############################\n\nfunction tru_podcast_link($atts) {\n	$podcast_url = tru_podcast_get_url();\n	if ($podcast_url) {\n		$linktext = ($atts[\'linktext\'] ? $atts[\'linktext\'] : $podcast_url);\n		unset($atts[\'linktext\']);\n\n		$link = \"<a href=\\\"$podcast_url\\\" rel=\\\"enclosure\\\"\";\n		foreach ($atts as $key => $value) {\n			$link .= \" $key=\\\"$value\\\"\";\n		}\n		$link .= \">$linktext</a>\";\n\n		return $link;\n	}\n}\n\n\nfunction tru_podcast_if_podcast($atts, $thing) {\n        return parse(EvalElse($thing, tru_podcast_get_url()));\n}\n\n\nfunction tru_podcast_get_url($atts = array()) {\n	global $thisarticle, $permlink_mode;\n	extract(lAtts(array(\'full\'			=> \'0\',\n						\'convertnumber\'	=> \'1\',\n						\'protocol\'		=> \'http://\'\n						), $atts, 0));\n\n	$url = trim($thisarticle[\'tru_podcast\']);\n\n	if ($convertnumber && is_numeric($url)) {\n		$id = intval($url);\n		$url = file_download_link(array(\'id\' => $id), \'\');\n\n		if (TRU_PODCAST_USE_FILENAMES && $permlink_mode != \'messy\') {\n			$rs = safe_row(\"*\", \"txp_file\", \'id=\'.$id);\n			if ($rs) {\n				$parts = explode(\'/\', $url);\n				$parts[count($parts) - 1] = $rs[\'filename\'];\n				$url = implode(\'/\', $parts);\n			}\n		}\n	}\n\n	if ($full) {\n		return tru_podcast_clean_url($url, $protocol);\n	} else {\n		return $url;\n	}\n}\n\n\nfunction tru_podcast_clean_url($url, $protocol) {\n	$parts = parse_url($url);\n	if ($parts[\'host\']) {\n		return $url;\n	} else {\n		return $protocol . $_SERVER[\'HTTP_HOST\'] . ($parts[\'port\'] ? \':\'.$parts[\'port\'] : \'\') . $parts[\'path\'] . ($parts[\'query\'] ? \'?\'.$parts[\'query\'] : \'\');\n	}\n}\n\n\nregister_callback(\'tru_podcast_rss_handler_rss\', \'rss_entry\');\nfunction tru_podcast_rss_handler_rss($event, $step){return tru_podcast_rss_handler(false);}\nregister_callback(\'tru_podcast_rss_handler_atom\', \'atom_entry\');\nfunction tru_podcast_rss_handler_atom($event, $step){return tru_podcast_rss_handler(true);}\n\nfunction tru_podcast_rss_handler($atom)\n{\n	$raw_url = tru_podcast_get_url(array(\'convertnumber\' => \'0\'));\n	if ($raw_url) {\n		$direct_url = $raw_url;\n		if (is_numeric($raw_url)) {\n			$rs = safe_row(\"*\", \"txp_file\", \'id=\'.intval($raw_url));\n			if ($rs) {\n				$direct_url = TRU_PODCAST_FILES_URL . $rs[\'filename\'];\n			}\n		}\n		$podcast_url = (TRU_PODCAST_USE_DIRECT_LINKS ? $direct_url : tru_podcast_get_url(array(\'full\' => \'1\')));\n		return tru_podcast_generate_enclosure($direct_url, $podcast_url, $atom);\n	} else {\n		return;\n	}\n}\n\n\nfunction tru_podcast_generate_enclosure($direct_url, $podcast_url, $atom) {\n	$length = $type = false;\n\n	$headers = tru_podcast_get_headers_for($direct_url);\n	if ($headers) {\n		$type = $headers[\'content-type\'];\n		$length = $headers[\'content-length\'];\n	}\n\n	$xml_mid = ($type ? \" type=\\\"$type\\\"\" : \"\") . ($length ? \" length=\\\"$length\\\"\" : \"\");\n	if ($atom)\n		$xml = \"<link rel=\\\"enclosure\\\" href=\\\"$podcast_url\\\"\" . $xml_mid . \" />\";\n	else\n		$xml = \"<enclosure url=\\\"$podcast_url\\\"\" . $xml_mid . \" />\";\n\n	return $xml.n;\n}\n\n\nfunction tru_podcast_get_headers_for($url) {\n	global $cached_plugin_values;\n	$MAX_REDIRECTS = 5;\n	$SOCKET_CONNECT_TIMEOUT = 3;\n\n	$redirects = 0;\n	while ($redirects++ < $MAX_REDIRECTS) {\n		$parts = parse_url($url);\n		$file = $parts[\'path\'] . (isset($parts[\'query\']) ? \'?\'.$parts[\'query\'] : \'\');\n		$host = (isset($parts[\'host\']) ? $parts[\'host\'] : $_SERVER[\'HTTP_HOST\']);\n		$port = (isset($parts[\'port\']) ? $parts[\'port\'] : 80);\n\n		$fp = @fsockopen($host, $port, $err_num, $err_msg, $SOCKET_CONNECT_TIMEOUT);\n		if (!$fp)\n			return false;\n\n		$response = \'\';\n		$head = \"HEAD $file HTTP/1.1\\r\\nHOST: $host\\r\\nUser-Agent: \" . $cached_plugin_values[\'name\'] . \"/\" . $cached_plugin_values[\'version\'] . \"\\r\\n\\r\\n\";\n		fputs($fp, $head);\n		while (!feof($fp) && strpos($response, \"\\r\\n\\r\\n\") == false)\n			$response .= fgets($fp, 2048);\n		fclose($fp);\n\n		//ugly\n		preg_match(\'/.*([0-9]{3}).*/\', $response, $return);\n		$code = $headers[\'response\'] = $return[1]; // HTTP response code\n\n		preg_match_all(\'/(.*?): (.*)\\r/\', $response, $matches);\n		$count = count($matches[1]);\n		for ($i = 0; $i < $count; $i++) {\n			$key = strtolower($matches[1][$i]);\n			$headers[\"$key\"] = $matches[2][$i];\n		}\n\n		if ((\'302\' == $code || \'301\' == $code) && isset($headers[\'location\']))\n			$url = $headers[\'location\'];\n		else\n			return $headers;\n	}\n	return false;\n}\n\n', '\n#Copyright 2007 Nathan Arthur\n#Released under the GNU Public License, see http://www.opensource.org/licenses/gpl-license.php for details\n#This work is based partially on WordPress code; see http://trac.wordpress.org/browser/trunk/wp-includes/functions.php?rev=4725\n\n\n### CONFIGURATION ###\n#####################\n\n#See http://www.rainskit.com/reference/541/tru_podcast-usage-instructions for instructions\n\n# Set this to the web-accessible URL of Textpattern\'s \"files\" directory\n# If you haven\'t customized your \"File directory path\" setting, this should be right already\ndefine(\'TRU_PODCAST_FILES_URL\', hu . \'files/\');\n\n# When you upload a file via TXP\'s \"Files\" interface, TXP-generated URLs that point to that\n# file always look like \"/file_download/3\", if you have clean urls turned on.  Some podcast\n# clients get confused by the number (\"3\") and don\'t know what name to give the downloaded\n# file.  (TXP has a broken file-download implementation.)  By default, if you have clean\n# URLs turned on, tru_podcast will generate links like \"/file_download/test.mp3\".  I haven\'t\n# observed any negative side effects from this choice, but if you want tru_podcast to\n# generate the \'standard\' URLs, switch this flag to \'0\'.  Note that iTunes and a few other\n# podcast clients seem to misbehave if you set this to \'0\', though.\ndefine(\'TRU_PODCAST_USE_FILENAMES\', \'1\');\n\n# Set this to \'1\' if you want tru_podcast to generate direct links to files uploaded via\n# Textpattern\'s \"Files\" interface, rather than using TXP\'s built-in file download feature.\n# If you\'ve set TRU_PODCAST_USE_FILENAMES to \'0\', you might want to set this to \'1\'.\ndefine(\'TRU_PODCAST_USE_DIRECT_LINKS\', \'0\');\n\n\n### CODE ###\n#############################\n\nfunction tru_podcast_link($atts) {\n	$podcast_url = tru_podcast_get_url();\n	if ($podcast_url) {\n		$linktext = ($atts[\'linktext\'] ? $atts[\'linktext\'] : $podcast_url);\n		unset($atts[\'linktext\']);\n\n		$link = \"<a href=\\\"$podcast_url\\\" rel=\\\"enclosure\\\"\";\n		foreach ($atts as $key => $value) {\n			$link .= \" $key=\\\"$value\\\"\";\n		}\n		$link .= \">$linktext</a>\";\n\n		return $link;\n	}\n}\n\n\nfunction tru_podcast_if_podcast($atts, $thing) {\n        return parse(EvalElse($thing, tru_podcast_get_url()));\n}\n\n\nfunction tru_podcast_get_url($atts = array()) {\n	global $thisarticle, $permlink_mode;\n	extract(lAtts(array(\'full\'			=> \'0\',\n						\'convertnumber\'	=> \'1\',\n						\'protocol\'		=> \'http://\'\n						), $atts, 0));\n\n	$url = trim($thisarticle[\'tru_podcast\']);\n\n	if ($convertnumber && is_numeric($url)) {\n		$id = intval($url);\n		$url = file_download_link(array(\'id\' => $id), \'\');\n\n		if (TRU_PODCAST_USE_FILENAMES && $permlink_mode != \'messy\') {\n			$rs = safe_row(\"*\", \"txp_file\", \'id=\'.$id);\n			if ($rs) {\n				$parts = explode(\'/\', $url);\n				$parts[count($parts) - 1] = $rs[\'filename\'];\n				$url = implode(\'/\', $parts);\n			}\n		}\n	}\n\n	if ($full) {\n		return tru_podcast_clean_url($url, $protocol);\n	} else {\n		return $url;\n	}\n}\n\n\nfunction tru_podcast_clean_url($url, $protocol) {\n	$parts = parse_url($url);\n	if ($parts[\'host\']) {\n		return $url;\n	} else {\n		return $protocol . $_SERVER[\'HTTP_HOST\'] . ($parts[\'port\'] ? \':\'.$parts[\'port\'] : \'\') . $parts[\'path\'] . ($parts[\'query\'] ? \'?\'.$parts[\'query\'] : \'\');\n	}\n}\n\n\nregister_callback(\'tru_podcast_rss_handler_rss\', \'rss_entry\');\nfunction tru_podcast_rss_handler_rss($event, $step){return tru_podcast_rss_handler(false);}\nregister_callback(\'tru_podcast_rss_handler_atom\', \'atom_entry\');\nfunction tru_podcast_rss_handler_atom($event, $step){return tru_podcast_rss_handler(true);}\n\nfunction tru_podcast_rss_handler($atom)\n{\n	$raw_url = tru_podcast_get_url(array(\'convertnumber\' => \'0\'));\n	if ($raw_url) {\n		$direct_url = $raw_url;\n		if (is_numeric($raw_url)) {\n			$rs = safe_row(\"*\", \"txp_file\", \'id=\'.intval($raw_url));\n			if ($rs) {\n				$direct_url = TRU_PODCAST_FILES_URL . $rs[\'filename\'];\n			}\n		}\n		$podcast_url = (TRU_PODCAST_USE_DIRECT_LINKS ? $direct_url : tru_podcast_get_url(array(\'full\' => \'1\')));\n		return tru_podcast_generate_enclosure($direct_url, $podcast_url, $atom);\n	} else {\n		return;\n	}\n}\n\n\nfunction tru_podcast_generate_enclosure($direct_url, $podcast_url, $atom) {\n	$length = $type = false;\n\n	$headers = tru_podcast_get_headers_for($direct_url);\n	if ($headers) {\n		$type = $headers[\'content-type\'];\n		$length = $headers[\'content-length\'];\n	}\n\n	$xml_mid = ($type ? \" type=\\\"$type\\\"\" : \"\") . ($length ? \" length=\\\"$length\\\"\" : \"\");\n	if ($atom)\n		$xml = \"<link rel=\\\"enclosure\\\" href=\\\"$podcast_url\\\"\" . $xml_mid . \" />\";\n	else\n		$xml = \"<enclosure url=\\\"$podcast_url\\\"\" . $xml_mid . \" />\";\n\n	return $xml.n;\n}\n\n\nfunction tru_podcast_get_headers_for($url) {\n	global $cached_plugin_values;\n	$MAX_REDIRECTS = 5;\n	$SOCKET_CONNECT_TIMEOUT = 3;\n\n	$redirects = 0;\n	while ($redirects++ < $MAX_REDIRECTS) {\n		$parts = parse_url($url);\n		$file = $parts[\'path\'] . (isset($parts[\'query\']) ? \'?\'.$parts[\'query\'] : \'\');\n		$host = (isset($parts[\'host\']) ? $parts[\'host\'] : $_SERVER[\'HTTP_HOST\']);\n		$port = (isset($parts[\'port\']) ? $parts[\'port\'] : 80);\n\n		$fp = @fsockopen($host, $port, $err_num, $err_msg, $SOCKET_CONNECT_TIMEOUT);\n		if (!$fp)\n			return false;\n\n		$response = \'\';\n		$head = \"HEAD $file HTTP/1.1\\r\\nHOST: $host\\r\\nUser-Agent: \" . $cached_plugin_values[\'name\'] . \"/\" . $cached_plugin_values[\'version\'] . \"\\r\\n\\r\\n\";\n		fputs($fp, $head);\n		while (!feof($fp) && strpos($response, \"\\r\\n\\r\\n\") == false)\n			$response .= fgets($fp, 2048);\n		fclose($fp);\n\n		//ugly\n		preg_match(\'/.*([0-9]{3}).*/\', $response, $return);\n		$code = $headers[\'response\'] = $return[1]; // HTTP response code\n\n		preg_match_all(\'/(.*?): (.*)\\r/\', $response, $matches);\n		$count = count($matches[1]);\n		for ($i = 0; $i < $count; $i++) {\n			$key = strtolower($matches[1][$i]);\n			$headers[\"$key\"] = $matches[2][$i];\n		}\n\n		if ((\'302\' == $code || \'301\' == $code) && isset($headers[\'location\']))\n			$url = $headers[\'location\'];\n		else\n			return $headers;\n	}\n	return false;\n}\n\n', '3ef49a0b3c22f79ffa900bb8e95b26f3', '', '', 0, 5, 0),
('spf_js', 0, 'Simon Finch', 'https://github.com/spiffin/spf_js', '0.52', 'JavaScript management', '<p>&lt;h1&gt;spf_js: JavaScript management&lt;/h1&gt;</p>\n\n<p>&lt;p&gt;Create, edit and delete scripts in Textpattern admin and export on save to external files.&lt;/p&gt;<br />\n&lt;p&gt;<span class=\"caps\">REQUIRES</span>: &lt;strong&gt;Texpattern 4.5.1&lt;/strong&gt; and <span class=\"caps\">PHP</span> 5.&lt;/p&gt;<br />\n&lt;p&gt;Please read the instructions and notes below before use.&lt;/p&gt;<br />\n&lt;p&gt;Latest version: &lt;a href=“https://github.com/spiffin/spf_js”&gt;spf_js GitHub repository&lt;/a&gt;.&lt;/p&gt;<br />\n&lt;p&gt;A combination of two previously-released plugins: stm_javascript by Stanislav Müller and rvm_css by Ruud van Melick. Thanks to the original authors and to Jukka (Gocom) and Stef (Bloke) for invaluable feedback.&lt;/p&gt;<br />\n&lt;p&gt;Features include exporting scripts as files to a directory, optional “type” attribute &lt;code&gt;type=&amp;quot;text/javascript&amp;quot;&lt;/code&gt; and changing the tag argument from &lt;code&gt;n=&lt;/code&gt; to &lt;code&gt;name=&lt;/code&gt; to bring it in line with default css syntax.&lt;/p&gt;</p>\n\n<p>&lt;p&gt;Re-written for Textpattern 4.5.1 and above.&lt;/p&gt;</p>\n\n<p>&lt;p&gt;&lt;strong&gt;For Textpattern 4.4.1 and below use &lt;a href=“https://raw.github.com/spiffin/spf_js/master/spf_js_0.41.txt”&gt;this version&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;Instructions:&lt;/h2&gt;</p>\n\n<p>&lt;ol&gt;<br />\n&lt;li&gt;Create a directory for the static JavaScript files in the root of your textpattern installation. You should make sure that &lt;span class=“caps”&gt;<span class=\"caps\">PHP</span>&lt;/span&gt; is able to write to that directory.&lt;/li&gt;<br />\n&lt;li&gt;Visit the Advanced Preferences (Admin &gt; Preferences &gt; Advanced) and make sure the “JavaScript directory” preference contains the directory you created in step 1 (by default ‘js’). This path is relative path to the directory of your root Textpattern installation.&lt;/li&gt;<br />\n&lt;li&gt;Activate this plugin.&lt;/li&gt;<br />\n&lt;li&gt;Go to Presentation &gt; JavaScript and create JavaScripts you’d like to embed within your page templates.&lt;/li&gt;<br />\n&lt;li&gt;JavaScript files are stored in the database (for easy management and editing) and, on save, exported to a directory in your website where they can be referenced (as external JavaScript) with the tag below.&lt;/li&gt;<br />\n&lt;/ol&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;Tags:&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;&lt;code&gt;&amp;lt;txp:spf_js /&amp;gt; (embeds the default JavaScript file)&lt;/code&gt;&lt;/p&gt;<br />\n&lt;p&gt;&lt;code&gt;&amp;lt;txp:spf_js name=&amp;quot;myscript&amp;quot; /&amp;gt; (embeds the JavaScript file named &amp;quot;myscript&amp;quot;)&lt;/code&gt;&lt;/p&gt;</p>\n\n<p>&lt;h3&gt;<span class=\"caps\">HTML</span> output:&lt;/h3&gt;<br />\n&lt;p&gt;&lt;code&gt;&amp;lt;script src=&amp;quot;http://mysite.com/js/myscript.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;</p>\n\n<p>&lt;h3&gt;“type” attribute&lt;/h3&gt;<br />\n&lt;p&gt;By default the plugin outputs a script tag without the &lt;a href=“http://www.w3schools.com/html5/tag_script.asp”&gt;“type” attribute&lt;/a&gt; (required in <span class=\"caps\">XHTML</span>/HTML4 but optional in HTML5).&lt;/p&gt;<br />\n&lt;p&gt;To include a “type” attribute just use the &lt;code&gt;type=&amp;quot;1&amp;quot;&lt;/code&gt; argument:&lt;/p&gt;<br />\n&lt;p&gt;&lt;code&gt;&amp;lt;txp:spf_js name=&amp;quot;myscript&amp;quot; type=&amp;quot;1&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/p&gt;<br />\n&lt;p&gt;Outputs:&lt;/p&gt;<br />\n&lt;p&gt;&lt;code&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;http://mysite.com/js/myscript.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;Notes:&lt;/h2&gt;</p>\n\n<p>&lt;ol&gt;<br />\n&lt;li&gt;Don’t use non-alphanumeric characters in script names (if you try to they’ll be stripped).&lt;/li&gt;<br />\n&lt;li&gt;The plugin will convert your script names to lowercase.&lt;/li&gt;<br />\n&lt;li&gt;The plugin will throw an error if you try to embed a non-existent script &#8211; similar to: &lt;code&gt;Tag error:   -&amp;gt;  Textpattern Notice: The requested resource was not found. &amp;quot;script_name&amp;quot;&lt;/code&gt;.&lt;/li&gt;<br />\n&lt;li&gt;&amp;mdash; In which case check the script exists and your embed tag for typos.&lt;/li&gt;<br />\n&lt;/ol&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;stm_javascript&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;If stm_javascript is installed and activated you will see two JavaScript tabs in Presentation &#8211; one named ‘Javascript’ (stm_javascript &#8211; with lowercase ’s’) and another ‘JavaScript’ (spf_js &#8211; uppercase ‘S’). You can copy and paste scripts from stm_javascript to spf_js &#8211; and then disable stm_javascript. It’s not advisable to run both plugins simultaneously.&lt;/p&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;Language support (Textpack)&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;This plugin uses an English Textpack by default and installs French (fr-fr), German (de-de) and Italian (it-it) Textpacks.&lt;/p&gt;<br />\n&lt;p&gt;To use your own language see the &lt;a href=“https://raw.github.com/spiffin/spf_js/master/spf_js_textpack.txt”&gt;spf_js_textpack&lt;/a&gt; file on GitHub.&lt;/p&gt;</p>\n\n<p>&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;</p>\n\n<p>&lt;h2&gt;Version history&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;0.52 &#8211; 21 May 2019&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Fixed issue setting prefs in 4.7.x.&lt;/li&gt;<br />\n&lt;li&gt;Removed &lt;a href=“http://code.google.com/p/minify”&gt;Minify&lt;/a&gt; support &#8211; did anyone ever use it?&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.51 &#8211; November 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Fixed issue setting prefs in 4.5.x.&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.5 &#8211; November 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Rewritten for Textpattern 4.5.x.&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.41 &#8211; June 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Italian Textpack added (thanks Marco).&lt;/li&gt;<br />\n&lt;li&gt;Last version to support Textpattern 4.4.1 and below.&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.4 &#8211; May 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Added &lt;a href=“http://code.google.com/p/minify”&gt;Minify&lt;/a&gt; support.&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.3 &#8211; May 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;Fixed delete issue with script names containing dots (thanks Yiannis).&lt;/li&gt;<br />\n&lt;/ul&gt;<br />\n&lt;p&gt;0.2 &#8211; April 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;French and German Textpacks added (thanks Patrick and Uli);&lt;/li&gt;<br />\n&lt;li&gt;added compatibility with the syntax-highlighting &lt;a href=“https://github.com/spiffin/spf_codemirror”&gt;spf_codemirror&lt;/a&gt;.&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;p&gt;0.1 &#8211; April 2012&lt;/p&gt;<br />\n&lt;ul&gt;<br />\n&lt;li&gt;first release.&lt;/li&gt;<br />\n&lt;/ul&gt;</p>', '/**\n * spf_js - JavaScript management for Textpattern 4.5.x\n *\n * © 2012 Simon Finch - https://github.com/spiffin\n *\n * Licensed under GNU General Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n *\n * Version 0.52 -- 21 May 2019\n *\n * Many thanks to Jukka for invaluable feedback\n */\n\nif (@txpinterface == \'admin\') {\n\n    add_privs(\'spf_js\', \'1,2\');\n    register_tab(\'presentation\', \'spf_js\', gTxt(\'spf_javascript\'));\n    register_callback(\'spf_js_event\', \'spf_js\');\n    register_callback(\'spf_js_install\', \'plugin_lifecycle.spf_js\', \'installed\');\n    //register_callback(\'spf_js_install\', \'plugin_lifecycle.spf_js\', \'enabled\');\n    register_callback(\'spf_js_remove\', \'plugin_lifecycle.spf_js\', \'deleted\');\n\n}\n\n/**\n * Removal function\n */\n\nfunction spf_js_remove($event, $step) {\nglobal $prefs, $step;\n\n    if(isset($prefs[\'spf_js_dir\'])) {\n\n        safe_delete(\n            \'txp_prefs\',\n            \"name=\'spf_js_dir\'\"\n        );\n\n        // Don\'t drop the table - just in case.\n        //@safe_query(\n        //    \'DROP TABLE IF EXISTS \'.safe_pfx(\'spf_js\')\n        //);\n\n        // delete the Textpack\n\n        safe_delete(\n            \'txp_lang\',\n            \"event = \'spf_js\'\"\n        );\n\n    }\n\n}\n\n/**\n * Installer function\n */\n\nfunction spf_js_install($event, $step) {\nglobal $prefs, $step;\n\n    // Version check\n    if (txp_version >= \'4.5.0\') {\n\n    /* Create table, prefs and default.js when spf_js_dir isn\'t set */\n    if(!isset($prefs[\'spf_js_dir\'])) {\n\n        safe_query(\n            \"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'spf_js\').\" (\n                name varchar(255) NOT NULL default \'\',\n                js longtext NOT NULL,\n                PRIMARY KEY(name)\n            ) CHARSET=utf8\"\n        );\n\n        if(!safe_count(\"spf_js\", \"name=\'default\'\")) {\n\n            safe_insert(\"spf_js\", \"name=\'default\', js=\'// Your default JavaScript\'\");\n\n        }\n\n        safe_insert(\n            \'txp_prefs\',\n            \"name=\'spf_js_dir\',\n            val=\'js\',\n            type=1,\n            event=\'admin\',\n            html=\'text_input\',\n            position=35\"\n        );\n\n    }\n\n\n// If Txp version is less than 4.5.x return message.\n\n    } else {\n        return \'The plugin spf_js version 0.5 requires Textpattern 4.5.1 - you are running Textpattern \' . txp_version . \' - please delete spf_js.\';\n    }\n\n}\n\n/**\n * Event functions\n */\n\nfunction spf_js_event() {\n\n    global $event, $step;\n\n    if ($event == \'spf_js\') {\n        require_privs(\'spf_js\');\n\n        bouncer($step,\n            array(\n                \'spf_js_edit_raw\'   => false,\n                \'pour\'              => false,\n                \'spf_js_save\'       => true,\n                \'spf_js_copy\'       => true,\n                \'spf_js_delete\'     => true,\n                \'spf_js_edit\'       => false,\n            )\n        );\n\n        switch ($step) {\n            case \'\': spf_js_edit();                         break;\n            case \'spf_js_edit_raw\': spf_js_edit();          break;\n            case \'pour\': spf_js_edit();                     break;\n            case \'spf_js_save\': spf_js_save();              break;\n            case \'spf_js_copy\': spf_js_copy();              break;\n            case \'spf_js_delete\': spf_js_delete();          break;\n            case \'spf_js_edit\': spf_js_edit();\n        }\n    }\n}\n\n/**\n * Output tag\n */\n\nfunction spf_js($atts) {\n\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => \'default\',\n        \'type\' => \'\',\n        \'min\' => \'\'\n    ), $atts));\n\n    $name = strtolower(sanitizeForUrl($name));\n\n    if(!safe_row(\'name\', \'spf_js\', \"name=\'\".doSlash($name).\"\'\")) {\n\n         trigger_error(gTxt(\'404_not_found\').sp.strong(\'\"\'.$name.\'\"\'));\n\n        return;\n\n    }\n\n    if (!$type) {\n\n        return \'<script src=\"\'.txpspecialchars(hu.$prefs[\'spf_js_dir\'].\'/\'.$name).\'.js\"></script>\';\n\n    } elseif ($type) {\n\n        return \'<script type=\"text/javascript\" src=\"\'.txpspecialchars(hu.$prefs[\'spf_js_dir\'].\'/\'.$name).\'.js\"></script>\';\n\n    }\n\n}\n\n/**\n * List function\n */\n\nfunction spf_js_list($name) {\n\n    $out[] = startTable(\'\', \'\', \'txp-list\');\n\n    $rs = safe_rows_start(\"name as jsname\", (\"spf_js\"), \"1=1\");\n\n    if ($rs) {\n\n        while ($a = nextRow($rs)) {\n            extract($a);\n            $edit = ($name!=$jsname) ? eLink(\'spf_js\', \'\', \'name\', $jsname, $jsname) : txpspecialchars($name);\n            $delete = ($jsname!=\'default\') ? dLink(\'spf_js\', \'spf_js_delete\', \'name\', $jsname) : \'\';\n            $out[] = tr(td($edit).td($delete));\n        }\n\n        $out[] =  endTable();\n\n        return join(\'\', $out);\n\n    }\n}\n\n/**\n * Edit function\n */\n\nfunction spf_js_edit($message=\'\') {\n\n    pagetop(gTxt(\'spf_javascript\'),$message);\n    global $step, $prefs;\n    spf_js_edit_raw();\n\n}\n\n/**\n * Edit function (raw)\n */\n\nfunction spf_js_edit_raw() {\n\n    global $event, $step;\n\n    $default_name = \'default\';\n    extract(gpsa(array(\'name\', \'newname\', \'copy\', \'savenew\')));\n\n    if ($step == \'spf_js_delete\' || empty($name) && $step != \'pour\' && !$savenew) {\n\n        $name = $default_name;\n    }\n\n    elseif (($copy || $savenew) && trim(preg_replace(\'/[<>&\"\\\']/\', \'\', $newname))) {\n\n        $name = $newname;\n\n    }\n\n    if (empty($name)) {\n\n        $buttons = \'<div class=\"edit-title\">\'.\n        gTxt(\'spf_script_name\').\': \'\n        .fInput(\'text\',\'newname\',\'\',\'edit\',\'\',\'\',20).\n        hInput(\'savenew\',\'savenew\').\n        \'</div>\';\n        $thejs = gps(\'spf_js\');\n\n    } else {\n\n        $buttons = \'<div class=\"edit-title\">\'.gTxt(\'spf_edit_script\').sp.strong(txpspecialchars($name)).\'</div>\';\n        $thejs = fetch(\"js\",\'spf_js\',\'name\',$name);\n\n    }\n\n    if (!empty($name)) {\n\n        $copy = \'<span class=\"copy-as\"><label for=\"copy-js\">\'.gTxt(\'spf_copy_script\').\'</label>\'.sp.fInput(\'text\', \'newname\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'copy-js\').sp.\n            fInput(\'submit\', \'copy\', gTxt(\'copy\'), \'smallerbox\').\'</span>\';\n\n    } else {\n\n        $copy = \'\';\n\n    }\n\n    $right =\n    \'<div id=\"content_switcher\">\'.\n    hed(gTxt(\'spf_all_scripts\'),2).\n    graf(sLink(\'spf_js\', \'pour\', gTxt(\'spf_create_new_script\')), \' class=\"action-create\"\').\n    spf_js_list($name, $default_name).\n    \'</div>\';\n\n    echo\n    \'<h1 class=\"txp-heading\">\'.gTxt(\'spf_tab_script\').\'</h1>\'.\n    \'<div id=\"\'.$event.\'_container\" class=\"txp-container\">\'.\n    startTable(\'\', \'\', \'txp-columntable\').\n    tr(\n        td(\n            form(\n                \'<div id=\"main_content\">\'.\n                $buttons.\n                \'<textarea id=\"spf_js\" class=\"code\" name=\"spf_js\" cols=\"\'.INPUT_LARGE.\'\" rows=\"\'.INPUT_REGULAR.\'\" style=\"height: 39.25em;\">\'.txpspecialchars($thejs).\'</textarea>\'.\n                \'<p>\'.fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\').\n                eInput(\'spf_js\').sInput(\'spf_js_save\').\n                hInput(\'name\',$name).\'</p>\'\n                .$copy.\n                \'</div>\'\n            , \'\', \'\', \'post\', \'edit-form\', \'\', \'style_form\')\n        , \'\', \'column\').\n        tdtl(\n            $right\n        , \' class=\"column\"\')\n    ).\n    endTable().\n    \'</div>\';\n}\n\n/**\n * Copy function\n *\n */\n\nfunction spf_js_copy() {\n\n    extract(gpsa(array(\'oldname\', \'newname\')));\n\n    $js = doSlash(fetch(\'js\', \'spf_js\', \'name\', $oldname));\n\n    $rs = safe_insert(\'spf_js\', \"js = \'$js\', name = \'\".doSlash($newname).\"\'\");\n\n    spf_js_edit(\n        gTxt(\'spf_script_created\', array(\'{name}\' => $newname))\n    );\n}\n\n/**\n * Save function\n */\n\nfunction spf_js_save() {\n\n    extract(gpsa(array(\'name\',\'spf_js\',\'savenew\',\'newname\',\'copy\')));\n    $js = doSlash($spf_js);\n\n    if ($savenew or $copy) {\n\n        $newname = doSlash(trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))));\n\n        if ($newname and safe_field(\'name\', \'spf_js\', \"name = \'$newname\'\")) {\n\n            $message = gTxt(\'spf_script_exists\', array(\'{name}\' => $newname));\n\n            if ($savenew) {\n\n                $_POST[\'newname\'] = \'\';\n\n            }\n\n        } elseif ($newname) {\n\n            safe_insert(\'spf_js\', \"name = \'\".$newname.\"\', js = \'$js\'\");\n\n            $message = gTxt(\'spf_script_created\', array(\'{name}\' => $newname));\n\n            spf_js_write();\n\n        } else {\n\n            $message = array(gTxt(\'spf_script_name_required\'), E_ERROR);\n\n        }\n\n        spf_js_edit($message);\n\n    } else {\n\n        safe_update(\'spf_js\', \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\n\n        $message = gTxt(\'spf_script_updated\', array(\'{name}\' => $name));\n\n        spf_js_write();\n\n        spf_js_edit($message);\n\n    }\n\n}\n\n/**\n * Write function\n */\n\nfunction spf_js_write() {\n\n    global $prefs;\n    extract(gpsa(array(\'name\',\'spf_js\',\'savenew\',\'newname\',\'copy\')));\n\n    $name = (ps(\'copy\') or ps(\'savenew\')) ? ps(\'newname\') : ps(\'name\');\n    $filename = strtolower(sanitizeForUrl($name));\n    $file = $prefs[\'path_to_site\'].\'/\'.$prefs[\'spf_js_dir\'].\'/\'.$filename;\n\n    if (empty($prefs[\'spf_js_dir\']) or !$filename) {\n\n        return;\n\n    } else {\n\n    $js_raw = fetch(\"js\", \"spf_js\", \'name\', $name); // Moved here to save newly-created scripts\n\n        $handle = fopen($file.\'.js\', \'wb\');\n        fwrite($handle, $js_raw);\n        fclose($handle);\n        chmod($file.\'.js\', 0644);\n    }\n\n}\n\n/**\n * Delete function\n */\n\nfunction spf_js_delete() {\n\n    global $prefs;\n\n    $dbname = trim(preg_replace(\'/[<>&\"\\\']/\', \'\', ps(\'name\')));\n    $name = strtolower(sanitizeForUrl(ps(\'name\')));\n    $file = $prefs[\'path_to_site\'].\'/\'.$prefs[\'spf_js_dir\'].\'/\'.$name;\n\n    if ($name != \'default\') {\n\n        safe_delete(\"spf_js\", \"name = \'\".doSlash($dbname).\"\'\");\n\n        if (!empty($prefs[\'spf_js_dir\']) and $name) {\n\n            @unlink($file.\'.js\');\n\n        }\n\n        spf_js_edit(\n\n            gTxt(\'spf_script_deleted\', array(\'{name}\' => $name))\n        );\n\n    } else {\n\n        echo gTxt(\'spf_cannot_delete_default_script\').\'.\';\n\n    }\n}', '/**\n * spf_js - JavaScript management for Textpattern 4.5.x\n *\n * © 2012 Simon Finch - https://github.com/spiffin\n *\n * Licensed under GNU General Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n *\n * Version 0.52 -- 21 May 2019\n *\n * Many thanks to Jukka for invaluable feedback\n */\n\nif (@txpinterface == \'admin\') {\n\n    add_privs(\'spf_js\', \'1,2\');\n    register_tab(\'presentation\', \'spf_js\', gTxt(\'spf_javascript\'));\n    register_callback(\'spf_js_event\', \'spf_js\');\n    register_callback(\'spf_js_install\', \'plugin_lifecycle.spf_js\', \'installed\');\n    //register_callback(\'spf_js_install\', \'plugin_lifecycle.spf_js\', \'enabled\');\n    register_callback(\'spf_js_remove\', \'plugin_lifecycle.spf_js\', \'deleted\');\n\n}\n\n/**\n * Removal function\n */\n\nfunction spf_js_remove($event, $step) {\nglobal $prefs, $step;\n\n    if(isset($prefs[\'spf_js_dir\'])) {\n\n        safe_delete(\n            \'txp_prefs\',\n            \"name=\'spf_js_dir\'\"\n        );\n\n        // Don\'t drop the table - just in case.\n        //@safe_query(\n        //    \'DROP TABLE IF EXISTS \'.safe_pfx(\'spf_js\')\n        //);\n\n        // delete the Textpack\n\n        safe_delete(\n            \'txp_lang\',\n            \"event = \'spf_js\'\"\n        );\n\n    }\n\n}\n\n/**\n * Installer function\n */\n\nfunction spf_js_install($event, $step) {\nglobal $prefs, $step;\n\n    // Version check\n    if (txp_version >= \'4.5.0\') {\n\n    /* Create table, prefs and default.js when spf_js_dir isn\'t set */\n    if(!isset($prefs[\'spf_js_dir\'])) {\n\n        safe_query(\n            \"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'spf_js\').\" (\n                name varchar(255) NOT NULL default \'\',\n                js longtext NOT NULL,\n                PRIMARY KEY(name)\n            ) CHARSET=utf8\"\n        );\n\n        if(!safe_count(\"spf_js\", \"name=\'default\'\")) {\n\n            safe_insert(\"spf_js\", \"name=\'default\', js=\'// Your default JavaScript\'\");\n\n        }\n\n        safe_insert(\n            \'txp_prefs\',\n            \"name=\'spf_js_dir\',\n            val=\'js\',\n            type=1,\n            event=\'admin\',\n            html=\'text_input\',\n            position=35\"\n        );\n\n    }\n\n\n// If Txp version is less than 4.5.x return message.\n\n    } else {\n        return \'The plugin spf_js version 0.5 requires Textpattern 4.5.1 - you are running Textpattern \' . txp_version . \' - please delete spf_js.\';\n    }\n\n}\n\n/**\n * Event functions\n */\n\nfunction spf_js_event() {\n\n    global $event, $step;\n\n    if ($event == \'spf_js\') {\n        require_privs(\'spf_js\');\n\n        bouncer($step,\n            array(\n                \'spf_js_edit_raw\'   => false,\n                \'pour\'              => false,\n                \'spf_js_save\'       => true,\n                \'spf_js_copy\'       => true,\n                \'spf_js_delete\'     => true,\n                \'spf_js_edit\'       => false,\n            )\n        );\n\n        switch ($step) {\n            case \'\': spf_js_edit();                         break;\n            case \'spf_js_edit_raw\': spf_js_edit();          break;\n            case \'pour\': spf_js_edit();                     break;\n            case \'spf_js_save\': spf_js_save();              break;\n            case \'spf_js_copy\': spf_js_copy();              break;\n            case \'spf_js_delete\': spf_js_delete();          break;\n            case \'spf_js_edit\': spf_js_edit();\n        }\n    }\n}\n\n/**\n * Output tag\n */\n\nfunction spf_js($atts) {\n\n    global $prefs;\n\n    extract(lAtts(array(\n        \'name\' => \'default\',\n        \'type\' => \'\',\n        \'min\' => \'\'\n    ), $atts));\n\n    $name = strtolower(sanitizeForUrl($name));\n\n    if(!safe_row(\'name\', \'spf_js\', \"name=\'\".doSlash($name).\"\'\")) {\n\n         trigger_error(gTxt(\'404_not_found\').sp.strong(\'\"\'.$name.\'\"\'));\n\n        return;\n\n    }\n\n    if (!$type) {\n\n        return \'<script src=\"\'.txpspecialchars(hu.$prefs[\'spf_js_dir\'].\'/\'.$name).\'.js\"></script>\';\n\n    } elseif ($type) {\n\n        return \'<script type=\"text/javascript\" src=\"\'.txpspecialchars(hu.$prefs[\'spf_js_dir\'].\'/\'.$name).\'.js\"></script>\';\n\n    }\n\n}\n\n/**\n * List function\n */\n\nfunction spf_js_list($name) {\n\n    $out[] = startTable(\'\', \'\', \'txp-list\');\n\n    $rs = safe_rows_start(\"name as jsname\", (\"spf_js\"), \"1=1\");\n\n    if ($rs) {\n\n        while ($a = nextRow($rs)) {\n            extract($a);\n            $edit = ($name!=$jsname) ? eLink(\'spf_js\', \'\', \'name\', $jsname, $jsname) : txpspecialchars($name);\n            $delete = ($jsname!=\'default\') ? dLink(\'spf_js\', \'spf_js_delete\', \'name\', $jsname) : \'\';\n            $out[] = tr(td($edit).td($delete));\n        }\n\n        $out[] =  endTable();\n\n        return join(\'\', $out);\n\n    }\n}\n\n/**\n * Edit function\n */\n\nfunction spf_js_edit($message=\'\') {\n\n    pagetop(gTxt(\'spf_javascript\'),$message);\n    global $step, $prefs;\n    spf_js_edit_raw();\n\n}\n\n/**\n * Edit function (raw)\n */\n\nfunction spf_js_edit_raw() {\n\n    global $event, $step;\n\n    $default_name = \'default\';\n    extract(gpsa(array(\'name\', \'newname\', \'copy\', \'savenew\')));\n\n    if ($step == \'spf_js_delete\' || empty($name) && $step != \'pour\' && !$savenew) {\n\n        $name = $default_name;\n    }\n\n    elseif (($copy || $savenew) && trim(preg_replace(\'/[<>&\"\\\']/\', \'\', $newname))) {\n\n        $name = $newname;\n\n    }\n\n    if (empty($name)) {\n\n        $buttons = \'<div class=\"edit-title\">\'.\n        gTxt(\'spf_script_name\').\': \'\n        .fInput(\'text\',\'newname\',\'\',\'edit\',\'\',\'\',20).\n        hInput(\'savenew\',\'savenew\').\n        \'</div>\';\n        $thejs = gps(\'spf_js\');\n\n    } else {\n\n        $buttons = \'<div class=\"edit-title\">\'.gTxt(\'spf_edit_script\').sp.strong(txpspecialchars($name)).\'</div>\';\n        $thejs = fetch(\"js\",\'spf_js\',\'name\',$name);\n\n    }\n\n    if (!empty($name)) {\n\n        $copy = \'<span class=\"copy-as\"><label for=\"copy-js\">\'.gTxt(\'spf_copy_script\').\'</label>\'.sp.fInput(\'text\', \'newname\', \'\', \'edit\', \'\', \'\', \'\', \'\', \'copy-js\').sp.\n            fInput(\'submit\', \'copy\', gTxt(\'copy\'), \'smallerbox\').\'</span>\';\n\n    } else {\n\n        $copy = \'\';\n\n    }\n\n    $right =\n    \'<div id=\"content_switcher\">\'.\n    hed(gTxt(\'spf_all_scripts\'),2).\n    graf(sLink(\'spf_js\', \'pour\', gTxt(\'spf_create_new_script\')), \' class=\"action-create\"\').\n    spf_js_list($name, $default_name).\n    \'</div>\';\n\n    echo\n    \'<h1 class=\"txp-heading\">\'.gTxt(\'spf_tab_script\').\'</h1>\'.\n    \'<div id=\"\'.$event.\'_container\" class=\"txp-container\">\'.\n    startTable(\'\', \'\', \'txp-columntable\').\n    tr(\n        td(\n            form(\n                \'<div id=\"main_content\">\'.\n                $buttons.\n                \'<textarea id=\"spf_js\" class=\"code\" name=\"spf_js\" cols=\"\'.INPUT_LARGE.\'\" rows=\"\'.INPUT_REGULAR.\'\" style=\"height: 39.25em;\">\'.txpspecialchars($thejs).\'</textarea>\'.\n                \'<p>\'.fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\').\n                eInput(\'spf_js\').sInput(\'spf_js_save\').\n                hInput(\'name\',$name).\'</p>\'\n                .$copy.\n                \'</div>\'\n            , \'\', \'\', \'post\', \'edit-form\', \'\', \'style_form\')\n        , \'\', \'column\').\n        tdtl(\n            $right\n        , \' class=\"column\"\')\n    ).\n    endTable().\n    \'</div>\';\n}\n\n/**\n * Copy function\n *\n */\n\nfunction spf_js_copy() {\n\n    extract(gpsa(array(\'oldname\', \'newname\')));\n\n    $js = doSlash(fetch(\'js\', \'spf_js\', \'name\', $oldname));\n\n    $rs = safe_insert(\'spf_js\', \"js = \'$js\', name = \'\".doSlash($newname).\"\'\");\n\n    spf_js_edit(\n        gTxt(\'spf_script_created\', array(\'{name}\' => $newname))\n    );\n}\n\n/**\n * Save function\n */\n\nfunction spf_js_save() {\n\n    extract(gpsa(array(\'name\',\'spf_js\',\'savenew\',\'newname\',\'copy\')));\n    $js = doSlash($spf_js);\n\n    if ($savenew or $copy) {\n\n        $newname = doSlash(trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))));\n\n        if ($newname and safe_field(\'name\', \'spf_js\', \"name = \'$newname\'\")) {\n\n            $message = gTxt(\'spf_script_exists\', array(\'{name}\' => $newname));\n\n            if ($savenew) {\n\n                $_POST[\'newname\'] = \'\';\n\n            }\n\n        } elseif ($newname) {\n\n            safe_insert(\'spf_js\', \"name = \'\".$newname.\"\', js = \'$js\'\");\n\n            $message = gTxt(\'spf_script_created\', array(\'{name}\' => $newname));\n\n            spf_js_write();\n\n        } else {\n\n            $message = array(gTxt(\'spf_script_name_required\'), E_ERROR);\n\n        }\n\n        spf_js_edit($message);\n\n    } else {\n\n        safe_update(\'spf_js\', \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\n\n        $message = gTxt(\'spf_script_updated\', array(\'{name}\' => $name));\n\n        spf_js_write();\n\n        spf_js_edit($message);\n\n    }\n\n}\n\n/**\n * Write function\n */\n\nfunction spf_js_write() {\n\n    global $prefs;\n    extract(gpsa(array(\'name\',\'spf_js\',\'savenew\',\'newname\',\'copy\')));\n\n    $name = (ps(\'copy\') or ps(\'savenew\')) ? ps(\'newname\') : ps(\'name\');\n    $filename = strtolower(sanitizeForUrl($name));\n    $file = $prefs[\'path_to_site\'].\'/\'.$prefs[\'spf_js_dir\'].\'/\'.$filename;\n\n    if (empty($prefs[\'spf_js_dir\']) or !$filename) {\n\n        return;\n\n    } else {\n\n    $js_raw = fetch(\"js\", \"spf_js\", \'name\', $name); // Moved here to save newly-created scripts\n\n        $handle = fopen($file.\'.js\', \'wb\');\n        fwrite($handle, $js_raw);\n        fclose($handle);\n        chmod($file.\'.js\', 0644);\n    }\n\n}\n\n/**\n * Delete function\n */\n\nfunction spf_js_delete() {\n\n    global $prefs;\n\n    $dbname = trim(preg_replace(\'/[<>&\"\\\']/\', \'\', ps(\'name\')));\n    $name = strtolower(sanitizeForUrl(ps(\'name\')));\n    $file = $prefs[\'path_to_site\'].\'/\'.$prefs[\'spf_js_dir\'].\'/\'.$name;\n\n    if ($name != \'default\') {\n\n        safe_delete(\"spf_js\", \"name = \'\".doSlash($dbname).\"\'\");\n\n        if (!empty($prefs[\'spf_js_dir\']) and $name) {\n\n            @unlink($file.\'.js\');\n\n        }\n\n        spf_js_edit(\n\n            gTxt(\'spf_script_deleted\', array(\'{name}\' => $name))\n        );\n\n    } else {\n\n        echo gTxt(\'spf_cannot_delete_default_script\').\'.\';\n\n    }\n}', '8541565c017c21102c611e8ac032399c', '#@spf_js\nspf_javascript => JavaScript\nspf_tab_script => JavaScripts\nspf_js_dir => JavaScript directory\nspf_script_name => Name for this script\nspf_edit_script => You are editing script\nspf_copy_script => &#8230;or copy script as\nspf_all_scripts => All Scripts\nspf_create_new_script => Create new script\nspf_script_created => Script <strong>{name}</strong> created.\nspf_script_exists => Script <strong>{name}</strong> already exists.\nspf_script_name_required => Please provide a name for your script.\nspf_script_updated => Script <strong>{name}</strong> updated.\nspf_script_deleted => Script <strong>{name}</strong> deleted.\nspf_cannot_delete_default_script => Script <strong>default</strong> cannot be deleted.\n#@language fr-fr\nspf_javascript => JavaScript\nspf_tab_script => JavaScripts\nspf_js_dir => Répertoire JavaScript\nspf_script_name => Nom de ce script\nspf_edit_script => Vous éditez le script\nspf_copy_script => Enregistrer le script sous le nom :\nspf_all_scripts => Tous les scripts\nspf_create_new_script => Créer un nouveau script\nspf_script_created => Le script <strong>{name}</strong> a été créé.\nspf_script_exists => Le script <strong>{name}</strong> existe déjà.\nspf_script_name_required => Veuillez renseigner un nom pour votre script.\nspf_script_updated => Le script <strong>{name}</strong> a été mis à jour.\nspf_script_deleted => Le script <strong>{name}</strong> a été supprimé.\nspf_cannot_delete_default_script => Le script <strong>default</strong> ne peut pas être supprimé.\n#@language de-de\nspf_javascript => JavaScript\nspf_tab_script => JavaScripts\nspf_js_dir => JavaScript-Verzeichnis\nspf_script_name => Name dieses Script\nspf_edit_script => Sie bearbeiten das Script\nspf_copy_script => Kopiere Script als:\nspf_all_scripts => Alle Scripts\nspf_create_new_script => Neues Script erstellen\nspf_script_created => Script <strong>{name}</strong> wurde erstellt.\nspf_script_exists => Script <strong>{name}</strong> existiert bereits.\nScript <strong>{name}</strong> existiert bereits.\nspf_script_name_required => Bitte vergeben Sie einen Namen für Ihr Script.\nspf_script_updated => Script <strong>{name}</strong> wurde aktualisiert.\nspf_script_deleted => Script <strong>{name}</strong> wurde gelöscht.\nspf_cannot_delete_default_script => Script <strong>default</strong> konnte nicht gelöscht werden.\n#@language it-it\nspf_javascript => JavaScript\nspf_tab_script => JavaScripts\nspf_js_dir => JavaScript directory\nspf_script_name => Nome dello script\nspf_edit_script => Stai modificando lo script\nspf_copy_script => &#8230;o copia lo script con nome\nspf_all_scripts => Tutti gli script\nspf_create_new_script => Crea nuovo script\nspf_script_created => Script <strong>{name}</strong> creato.\nspf_script_exists => Lo script <strong>{name}</strong> esiste gi&agrave;.\nspf_script_name_required => Per favore inserisci un nome per lo script\nspf_script_updated => Script <strong>{name}</strong> aggiornato.\nspf_script_deleted => Script <strong>{name}</strong> cancellato.\nspf_cannot_delete_default_script => Lo script <strong>default</strong> non pu&ograve; essere cancellato.', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_cookie', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA4', 'Set, read, reset or delete cookies', '<h1>oui_cookie</h1>\n\n<h2>Introduction</h2>\n\n<p>Set, check, read, reset or delete cookies manually or through <span class=\"caps\">GET</span>/POST parameters.</p>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> According to the <a href=\"http://ec.europa.eu/ipg/basics/legal/cookies/index_en.htm#section_2\">EU legislation</a>, some cookies need the user consent before to be set.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_cookie&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> 4.6+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_cookie/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin.</li>\n</ol>\n\n<h4>Via Composer</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_cookie:<br />\n<code>$ composer require oui/oui_cookie</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer</h4>\n\n<pre><code>$ composer update oui/oui_cookie</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer</h4>\n\n<pre><code>$ composer remove oui/oui_cookie</code></pre>\n\n<h2>Tags</h2>\n\n<h3 id=\"oui_cookie\">oui_cookie</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_cookie name=&quot;…&quot; /&gt;</code></pre>\n\n<p>or</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_cookie name=&quot;…&quot;&gt;\n    […]\n&lt;/txp:oui_cookie&gt;</code></pre>\n\n<h4>Attributes</h4>\n\n<h5>Required</h5>\n\n<dl>\n	<dt><code>name</code></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nThe cookie (and <span class=\"caps\">GET</span>/POST parmater) name you want to use. If no other attibutes are defined, the tag will read and display the related value.</p></dd>\n</dl>\n\n<h5>Manually set a cookie</h5>\n\n<dl>\n	<dt><code>value</code></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nA value to manually set the named cookie.<br />\nYou can also set the cookie value by using a continer tag like you would for a variable.</p></dd>\n</dl>\n\n<h5>Set a cookie through a <span class=\"caps\">GET</span>/POST parameter</h5>\n\n<dl>\n	<dt><code>values</code></dt>\n	<dd><p><strong>Value:</strong> comma separated list of strings; none by default.<br />\nA comma separated list of accepted values for the <span class=\"caps\">GET</span>/POST parameter and its cookie.</p></dd>\n	<dt><code>default</code></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nA default value.<br />\nIf set, the plugin conditional tag will always be true if not check against a defined value.</p></dd>\n	<dt><code>delete</code></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nAn <span class=\"caps\">GET</span>/POST parameter value used to delete the cookie.</p></dd>\n</dl>\n\n<h5>Optional cookie settings</h5>\n\n<dl>\n	<dt><code>duration</code></dt>\n	<dd><p><strong>Value:</strong> <a href=\"http://php.net/manual/fr/function.strtotime.php\">strtotime</a> value, <code>+1 day</code> by default.<br />\nThe duration of the cookie.</p></dd>\n</dl>\n\n<h5>Delete a cookie</h5>\n\n<dl>\n	<dt><code>delete</code></dt>\n	<dd><p><strong>Value:</strong> <code>0</code> or <code>1</code>; <code>0</code> by default.<br />\nIf set to <code>1</code> this attribute will delete the named cookie.</p></dd>\n</dl>\n\n<h3 id=\"oui_cookie\">oui_if_cookie</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_cookie name=&quot;…&quot;&gt;\n    […]\n&lt;txp:else /&gt;\n    […]\n&lt;/txp:oui_if_cookie&gt;</code></pre>\n\n<p>This tag checks the status or the value of the cookie (and/or the related <span class=\"caps\">GET</span>/POST parameter) defined by the <code>name</code> attribute.</p>\n\n<h4>Attributes</h4>\n\n<h5>Required</h5>\n\n<dl>\n	<dt><strong>@name@</strong></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nThe cookie (and <span class=\"caps\">GET</span>/POST parameter) name you want to use.</p></dd>\n</dl>\n\n<h5>Optional</h5>\n\n<dl>\n	<dt><code>value</code></dt>\n	<dd><p><strong>Value:</strong> string; none by default.<br />\nA value to check against the cookie (and/or the <span class=\"caps\">GET</span>/POST parameter) value.</p></dd>\n</dl>\n\n<h2>Examples</h2>\n\n<h3>Front-end articles sorting</h3>\n\n<p>List the sort options you want to use:</p>\n\n<pre class=\"language-html\"><code>&lt;form action=&quot;#&quot; method=&quot;post&quot;&gt;\n    &lt;select name=&quot;sort_by&quot;&gt;\n        &lt;option value=&quot;custom_1&quot;&lt;txp:oui_if_cookie name=&quot;sort_by&quot; value=&quot;custom_1&quot;&gt; disabled selected&lt;/txp:oui_if_cookie&gt;&gt;Size&lt;/option&gt;\n        &lt;option value=&quot;custom_2&quot;&lt;txp:oui_if_cookie name=&quot;sort_by&quot; value=&quot;custom_2&quot;&gt; disabled selected&lt;/txp:oui_if_cookie&gt;&gt;Weight&lt;/option&gt;\n    &lt;/select&gt;\n    &lt;input type=&quot;submit&quot; value=&quot;Sort&quot;&gt;\n&lt;/form&gt;</code></pre>\n\n<p>Then, catch the <span class=\"caps\">GET</span>/POST parameter sent by this list to store it (useful to keep the sort order pages after pages).</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_cookie name=&quot;sort_by&quot; values=&quot;custom_1, custom_2&quot; default=&quot;custom_1&quot; /&gt;</code></pre>\n\n<p>Now use the new value as the value of the <code>sort</code> attribute of your article tag.</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:article sort=&#39;&lt;txp:oui_cookie name=&quot;sort_by&quot; /&gt;&#39; /&gt;</code></pre>\n\n<h3>Last viewed article</h3>\n\n<p>Store the current article id in a cookie:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:if_individual_article&gt;\n    &lt;txp:oui_cookie name=&quot;last_article&quot; value=&#39;&lt;txp:article_id /&gt;&#39; /&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>Now, use the following code anywhere you want to display the last viewed article.</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:if_cookie name=&quot;last_article&quot;&gt;\n    &lt;txp:article_custom id=&#39;&lt;txp:oui_cookie name=&quot;last_article&quot; /&gt;&#39; /&gt;\n&lt;/txp:if_cookie&gt;</code></pre>\n\n<h3>EU cookies Warning</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_cookie name=&quot;accept_cookies&quot; values=&quot;yes&quot; /&gt;\n\n&lt;txp:oui_if_cookie name=&quot;accept_cookies&quot;&gt;\n&lt;txp:else /&gt;\n    &lt;div id=&quot;cookies-warning&quot;&gt;\n        &lt;p&gt;This website uses cookies. &lt;a id=&quot;accept_cookies&quot; href=&quot;?accept_cookies=yes&quot;&gt;I agree, remove this message!&lt;/a&gt;&lt;/p&gt;\n    &lt;/div&gt;\n&lt;/txp:oui_if_cookie&gt;</code></pre>\n\n<p>Now, you can add some progressive enhancement to avoid to refresh the page if javascript is on.<br />\nHowever, note that it wouldn&#8217;t instantaneously display a potential alternative content placed before the <code>&lt;txp:else /&gt;</code> tag if the cookie is not set/setting.</p>\n\n<pre class=\"language-javascript\"><code>&lt;script&gt;\n    (function() {\n        // Get the link which should remove the warning.\n        var accept = document.getElementById(&#39;accept_cookies&#39;);\n\n        // If it exists, listen it…\n        if (accept) {\n            accept.addEventListener(&#39;click&#39;, removeWarning);\n        }\n\n        // It is clicked!\n        function removeWarning(e){\n            document.cookie = &#39;accept_cookies=yes;path=/;expires=864e2&#39;; // Set the cookie for the whole domain for 1 day.\n            var warning = document.getElementById(&#39;cookies-warning&#39;); // Get the warning message to remove.\n            warning.remove(); // Remove the message.\n            e.preventDefault(); // Do not follow the link; everything&#39;s already done!\n        };\n    }());\n&lt;/script&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_cookie version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>1.0.0-beta3</strong> (2018-07-06)\n	<ul>\n		<li>Changed: <span class=\"caps\">OOP</span> related code rework;</li>\n		<li>Removed: Txp 4.5 support;</li>\n		<li>Adds <a href=\"https://getcomposer.org/\">Composer</a> support for <a href=\"https://packagist.org/packages/textpattern/installer\">textpattern/installer</a>.</li>\n	</ul></li>\n	<li><strong>0.2.3</strong> (2016-06-21)\n	<ul>\n		<li>Code rewrite;</li>\n		<li>manual cookie setting added;</li>\n		<li><code>display</code> attribute removed (setting and reading are now separated);</li>\n		<li>deletion process changed.</li>\n	</ul></li>\n	<li><strong>0.1.3</strong> (2016-05-29)\n	<ul>\n		<li>improves conditional tag results;</li>\n		<li><code>delete</code> attribute unset by default (but any none valid value for a defined <span class=\"caps\">GET</span>/POST parameter will delete the related cookie).</li>\n	</ul></li>\n	<li><strong>0.1.2</strong> (2016-05-26)</li>\n</ul>', '\n\n\n/*\n * This file is part of oui_cookie,\n * a plugin allowing easy cookies management in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_cookie\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\nnamespace Oui {\n\n    class cookie {\n\n        /**\n         * Cookie name.\n         *\n         * @var string\n         * @see        setName(), getName().\n         */\n\n        protected $name;\n\n        /**\n         * Cookie related GET/POST parameter.\n         *\n         * @var string\n         * @see        setParam(), getParam().\n         */\n\n        protected $param;\n\n        /**\n         * In process cookie setting value.\n         *\n         * @var array The provided value as a string or FALSE.\n         * @see       setProcessing(), getProcessing().\n         */\n\n        protected static $processing = array();\n\n        /**\n         * Cookie value set.\n         *\n         * @var string\n         * @see        setCookie(), getCookie().\n         */\n\n        protected $cookie;\n\n        /**\n         * $name property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setName($value) {\n            $this->name = $value;\n\n            return $this;\n        }\n\n        /**\n         * $name property getter.\n         *\n         * @return string $this->name.\n         */\n\n        protected function getName() {\n            return $this->name;\n        }\n\n        /**\n         * $param property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setParam() {\n            $this->param = strval(gps($this->getName()));\n\n            return $this;\n        }\n\n        /**\n         * $param property getter.\n         *\n         * @return string $this->param.\n         */\n\n        protected function getParam() {\n            $this->param or $this->setParam();\n\n            return $this->param;\n        }\n\n        /**\n         * $processing property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setProcessing($value) {\n            static::$processing[$this->getName()] = $value;\n\n            return $this;\n        }\n\n        /**\n         * $processing property getter.\n         *\n         * @return mixed\n         */\n\n        protected function getProcessing() {\n            if (array_key_exists($this->getName(), static::$processing)) {\n                return static::$processing[$this->getName()];\n            }\n\n            return null;\n        }\n\n        /**\n         * $cookie property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setCookie() {\n            $this->cookie = cs($this->getName());\n\n            return $this;\n        }\n\n        /**\n         * $cookie property getter.\n         *\n         * @return mixed $this->cookie\n         */\n\n        protected function getCookie() {\n            $this->cookie or $this->setCookie();\n\n            return $this->cookie;\n        }\n\n        /**\n         * Delete a cookie.\n         * Set the related $processing property value to false.\n         *\n         * @return bool TRUE if the cookie unsetting succeeded.\n         */\n\n        protected function delete() {\n            $this->setProcessing(false);\n\n            return setcookie($this->getName(), \'\', -1, \'/\');\n        }\n\n        /**\n         * Set a cookie.\n         * Set the related $processing property value.\n         *\n         * @return bool TRUE if the cookie setting succeeded.\n         */\n\n        public function set($value, $duration) {\n            $this->setProcessing($value);\n\n            return setcookie($this->getName(), $value, strtotime($duration), \'/\', null, false, true);\n        }\n\n        /**\n         * Check the status/value of a defined cookie name against its cookie/URL-parameter related value.\n         *\n         * @param  string $value A value to check against the set one;\n         * @return bool   TRUE if the defined cookie is set or in setting\n         *                and, optionally, if its value match the one provided.\n         */\n\n        public function isCookieSet($value = null) {\n            $cs = $this->getCookie();\n            $processing = $this->getProcessing();\n            $out = false;\n\n            if ($processing === false) {\n                $out = false;\n            } elseif ($processing) {\n                $out = $value ? ($value === $processing ? true : false) : true;\n            } elseif ($cs) {\n                $out = $value ? ($value === $cs ? true : false) : true;\n            }\n\n            return $out;\n        }\n\n        /**\n         * oui_cookie callback method.\n         * Set, read or delete a cookie, manually or from a GET/POST parameter.\n         *\n         * @param  array  $atts Attributes in use;\n         * @param  string $thing Tag content when used as a container.\n         * @return string The cookie value on reading.\n         */\n\n        public static function renderCookie($atts, $thing = null)\n        {\n            $instance = \\Txp::get(\'Oui\\Cookie\');\n\n            extract(lAtts(array(\n                \'name\'     => \'\',\n                \'value\'    => \'\',\n                \'values\'   => \'\',\n                \'default\'  => \'\',\n                \'duration\' => \'+1 day\',\n                \'delete\'   => \'\',\n            ), $atts));\n\n            if ($name) {\n                $cs = $instance->setName($name)->getCookie();\n                $gps = $instance->getParam();\n            } else {\n                trigger_error(\'oui_cookie requires an \"name\" attribute.\');\n                return;\n            }\n\n            if ($thing) {\n                $value = parse($thing);\n            } elseif ($values && in_list($gps, $values, $delim = \',\')) {\n                $value = $gps;\n            } elseif (!$cs && $default) {\n                $value = $default;\n            }\n\n            if ($value) {\n                $instance->set($value, $duration);\n            } elseif ($delete && !$values || $values && $gps && $gps === $delete) {\n                $instance->delete();\n            } elseif (!$values) {\n                $processing = $instance->getProcessing();\n\n                return $cs && $processing !== false ? $cs : $processing;\n            }\n        }\n\n        /**\n         * oui_if_cookie callback method.\n         * Switch between two defined contents/behaviours depending on the $isCookieSet() result.\n         *\n         * @param  array  $atts  Attributes in use;\n         * @param  string $thing Tag content.\n         * @return mixed  The default contents/behaviour if $isCookieSet returns TRUE, the else part otherwise.\n         */\n\n        public static function renderIfCookie($atts, $thing = null)\n        {\n            $instance = \\Txp::get(\'Oui\\Cookie\');\n\n            extract(lAtts(array(\n                \'name\'  => \'\',\n                \'value\' => \'\',\n            ), $atts));\n\n            if ($name) {\n                $instance->setName($name);\n            } else {\n                trigger_error(\'oui_cookie requires a name attribute.\');\n                return;\n            }\n\n            return parse($thing, $instance->isCookieSet($value));\n        }\n    }\n}\n\nnamespace {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'Oui\\Cookie::renderCookie\', \'oui_cookie\')\n        ->register(\'Oui\\Cookie::renderIfCookie\', \'oui_if_cookie\');\n}\n', '\n\n\n/*\n * This file is part of oui_cookie,\n * a plugin allowing easy cookies management in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_cookie\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\nnamespace Oui {\n\n    class cookie {\n\n        /**\n         * Cookie name.\n         *\n         * @var string\n         * @see        setName(), getName().\n         */\n\n        protected $name;\n\n        /**\n         * Cookie related GET/POST parameter.\n         *\n         * @var string\n         * @see        setParam(), getParam().\n         */\n\n        protected $param;\n\n        /**\n         * In process cookie setting value.\n         *\n         * @var array The provided value as a string or FALSE.\n         * @see       setProcessing(), getProcessing().\n         */\n\n        protected static $processing = array();\n\n        /**\n         * Cookie value set.\n         *\n         * @var string\n         * @see        setCookie(), getCookie().\n         */\n\n        protected $cookie;\n\n        /**\n         * $name property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setName($value) {\n            $this->name = $value;\n\n            return $this;\n        }\n\n        /**\n         * $name property getter.\n         *\n         * @return string $this->name.\n         */\n\n        protected function getName() {\n            return $this->name;\n        }\n\n        /**\n         * $param property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setParam() {\n            $this->param = strval(gps($this->getName()));\n\n            return $this;\n        }\n\n        /**\n         * $param property getter.\n         *\n         * @return string $this->param.\n         */\n\n        protected function getParam() {\n            $this->param or $this->setParam();\n\n            return $this->param;\n        }\n\n        /**\n         * $processing property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setProcessing($value) {\n            static::$processing[$this->getName()] = $value;\n\n            return $this;\n        }\n\n        /**\n         * $processing property getter.\n         *\n         * @return mixed\n         */\n\n        protected function getProcessing() {\n            if (array_key_exists($this->getName(), static::$processing)) {\n                return static::$processing[$this->getName()];\n            }\n\n            return null;\n        }\n\n        /**\n         * $cookie property setter.\n         *\n         * @return object $this.\n         */\n\n        protected function setCookie() {\n            $this->cookie = cs($this->getName());\n\n            return $this;\n        }\n\n        /**\n         * $cookie property getter.\n         *\n         * @return mixed $this->cookie\n         */\n\n        protected function getCookie() {\n            $this->cookie or $this->setCookie();\n\n            return $this->cookie;\n        }\n\n        /**\n         * Delete a cookie.\n         * Set the related $processing property value to false.\n         *\n         * @return bool TRUE if the cookie unsetting succeeded.\n         */\n\n        protected function delete() {\n            $this->setProcessing(false);\n\n            return setcookie($this->getName(), \'\', -1, \'/\');\n        }\n\n        /**\n         * Set a cookie.\n         * Set the related $processing property value.\n         *\n         * @return bool TRUE if the cookie setting succeeded.\n         */\n\n        public function set($value, $duration) {\n            $this->setProcessing($value);\n\n            return setcookie($this->getName(), $value, strtotime($duration), \'/\', null, false, true);\n        }\n\n        /**\n         * Check the status/value of a defined cookie name against its cookie/URL-parameter related value.\n         *\n         * @param  string $value A value to check against the set one;\n         * @return bool   TRUE if the defined cookie is set or in setting\n         *                and, optionally, if its value match the one provided.\n         */\n\n        public function isCookieSet($value = null) {\n            $cs = $this->getCookie();\n            $processing = $this->getProcessing();\n            $out = false;\n\n            if ($processing === false) {\n                $out = false;\n            } elseif ($processing) {\n                $out = $value ? ($value === $processing ? true : false) : true;\n            } elseif ($cs) {\n                $out = $value ? ($value === $cs ? true : false) : true;\n            }\n\n            return $out;\n        }\n\n        /**\n         * oui_cookie callback method.\n         * Set, read or delete a cookie, manually or from a GET/POST parameter.\n         *\n         * @param  array  $atts Attributes in use;\n         * @param  string $thing Tag content when used as a container.\n         * @return string The cookie value on reading.\n         */\n\n        public static function renderCookie($atts, $thing = null)\n        {\n            $instance = \\Txp::get(\'Oui\\Cookie\');\n\n            extract(lAtts(array(\n                \'name\'     => \'\',\n                \'value\'    => \'\',\n                \'values\'   => \'\',\n                \'default\'  => \'\',\n                \'duration\' => \'+1 day\',\n                \'delete\'   => \'\',\n            ), $atts));\n\n            if ($name) {\n                $cs = $instance->setName($name)->getCookie();\n                $gps = $instance->getParam();\n            } else {\n                trigger_error(\'oui_cookie requires an \"name\" attribute.\');\n                return;\n            }\n\n            if ($thing) {\n                $value = parse($thing);\n            } elseif ($values && in_list($gps, $values, $delim = \',\')) {\n                $value = $gps;\n            } elseif (!$cs && $default) {\n                $value = $default;\n            }\n\n            if ($value) {\n                $instance->set($value, $duration);\n            } elseif ($delete && !$values || $values && $gps && $gps === $delete) {\n                $instance->delete();\n            } elseif (!$values) {\n                $processing = $instance->getProcessing();\n\n                return $cs && $processing !== false ? $cs : $processing;\n            }\n        }\n\n        /**\n         * oui_if_cookie callback method.\n         * Switch between two defined contents/behaviours depending on the $isCookieSet() result.\n         *\n         * @param  array  $atts  Attributes in use;\n         * @param  string $thing Tag content.\n         * @return mixed  The default contents/behaviour if $isCookieSet returns TRUE, the else part otherwise.\n         */\n\n        public static function renderIfCookie($atts, $thing = null)\n        {\n            $instance = \\Txp::get(\'Oui\\Cookie\');\n\n            extract(lAtts(array(\n                \'name\'  => \'\',\n                \'value\' => \'\',\n            ), $atts));\n\n            if ($name) {\n                $instance->setName($name);\n            } else {\n                trigger_error(\'oui_cookie requires a name attribute.\');\n                return;\n            }\n\n            return parse($thing, $instance->isCookieSet($value));\n        }\n    }\n}\n\nnamespace {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'Oui\\Cookie::renderCookie\', \'oui_cookie\')\n        ->register(\'Oui\\Cookie::renderIfCookie\', \'oui_if_cookie\');\n}\n', 'f8134e2fdd59f2c00701fc95f6660069', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_player', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '2.0.0-BETA3', 'Manage configurable media players', '<h1>oui_player</h1>\n\n<h2>Introduction</h2>\n\n<p>An extendable plugin to easily embed customized audio and video players.</p>\n\n<p class=\"alert-block information\"><span class=\"ui-icon ui-icon-info\">.</span> This plugin does not use oembed, it builds iframe embedding codes and HTML5 players by its own without any external request for better performances.</p>\n\n<h3>Supported extensions</h3>\n\n<p>As known at the last plugin release…</p>\n\n<ul>\n	<li><a href=\"http://abcnews.go.com/video\">Abc News</a>: <a href=\"https://github.com/NicolasGraph/oui_abcnews\">oui_abcnews</a>;</li>\n	<li><a href=\"https://archive.org/\">Archive</a>: <a href=\"https://github.com/NicolasGraph/oui_archive\">oui_archive</a>;</li>\n	<li><a href=\"https://bandcamp.com/\">Bandcamp</a>: <a href=\"https://github.com/NicolasGraph/oui_bandcamp\">oui_bandcamp</a>;</li>\n	<li><a href=\"http://www.dailymotion.com/\">Dailymotion</a>: <a href=\"https://github.com/NicolasGraph/oui_dailymotion\">oui_dailymotion</a>;</li>\n	<li><a href=\"https://giphy.com/\">Giphy</a>: <a href=\"https://github.com/NicolasGraph/oui_giphy\">oui_giphy</a>;</li>\n	<li><a href=\"https://developer.mozilla.org/docs/Web/HTML/Element/audio\">HTML5 audio</a>: <a href=\"https://github.com/NicolasGraph/oui_audio\">oui_audio</a>;</li>\n	<li><a href=\"https://developer.mozilla.org/docs/Web/HTML/Element/video\">HTML5 video</a>: <a href=\"https://github.com/NicolasGraph/oui_video\">oui_video</a>;</li>\n	<li><a href=\"https://www.mixcloud.com/\">Mixcloud</a>: <a href=\"https://github.com/NicolasGraph/oui_mixcloud\">oui_mixcloud</a>;</li>\n	<li><a href=\"https://myspace.com/myspace/videos\">Myspace videos</a>: <a href=\"https://github.com/NicolasGraph/oui_myspace\">oui_myspace</a>;</li>\n	<li><a href=\"https://soundcloud.com/\">Soundcloud</a>: <a href=\"https://github.com/NicolasGraph/oui_soundcloud\">oui_soundcloud</a>;</li>\n	<li><a href=\"https://www.twitch.tv/\">Twitch</a>: <a href=\"https://github.com/NicolasGraph/oui_twitch\">oui_twitch</a>;</li>\n	<li><a href=\"https://www.viddsee.com/\">Viddsee</a>: <a href=\"https://github.com/NicolasGraph/oui_viddsee\">oui_viddsee</a>;</li>\n	<li><a href=\"http://www.vimeo.com/\">Vimeo</a>: <a href=\"https://github.com/NicolasGraph/oui_vimeo\">oui_vimeo</a>;</li>\n	<li><a href=\"http://vine.co/\">Vine</a>: <a href=\"https://github.com/NicolasGraph/oui_vine\">oui_vine</a>;</li>\n	<li><a href=\"https://www.youtube.com/\">Youtube</a>: <a href=\"https://github.com/NicolasGraph/oui_youtube\">oui_youtube</a>.</li>\n</ul>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_player&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> 4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+;</li>\n	<li>Any of the above provider related extension.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<p>Follow the below instructions before or after installing the wanted oui_player extensions:</p>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player/releases\">Download</a> the <code>.txt</code> or <code>_zip.txt</code> compiled plugin file;</li>\n	<li>Upload its contents through the <em>Admin &gt; Plugins</em> tab;</li>\n	<li>Confirm the plugin install on the preview page.</li>\n	<li>Enable the plugin <strong>and</strong> click the <em>Options</em> link to set the plugin preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_player:<br />\n<code>$ composer require nicolasgraph/oui_player</code></li>\n	<li>Connect to the Txp admin interface and click <em>Options</em> or visit your <a href=\"?event=prefs#prefs_group_oui_player\">Admin &gt; Preferences</a> tab to fill the plugin prefs.</li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<p>Follow the install instructions.<br />\nIf you are updating from v1.*, be sure to install all wanted extensions before to visit the <a href=\"?event=prefs#prefs_group_oui_player\">Admin &gt; Preferences</a> tab to keep your preference values untouched.</p>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_player</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>In the <em>Admin &gt; Plugins</em> tab, check the box on the left of the plugin related row.</li>\n	<li>Under the plugins list, select the the <em>Delete</em> option.</li>\n	<li>confirm the deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_player</code></pre>\n\n<h2>Preferences/options</h2>\n\n<p>Plugin prefs can be set under the <em>Admin &gt; Preferences</em> tab and are mainly used as default values for the <code>&lt;txp:oui_player /&gt;</code> tag attributes.</p>\n\n<h3>Global prefs</h3>\n\n<dl>\n	<dt><code>Field used by default</code></dt>\n	<dd><p>Defines a field to use as the default <code>play</code> attribute value.<br />\n<span class=\"warning\">The provided fieldname must be different than any value entered in it; avoid digits if you use ID&#8217;s as <code>play</code> attribute values</span><br />\n<strong>Default:</strong> <code>article_image</code><br />\n<strong>valid:</strong> <code>article_image</code> or any existing custom field</p></dd>\n	<dt><code>Favourite provider</code></dt>\n	<dd><p>Set the default <code>provider</code> attribute value.<br />\n<strong>Default:</strong> the first plugged provider<br />\n<strong>valid:</strong> Any plugged provider in the select list</p></dd>\n	<dt><code>Display the provider player preferences</code></dt>\n	<dd><p>Displays the defined provider prefs as a goup of prefs.<br />\n<strong>Default:</strong> yes<br />\n<strong>valid:</strong> yes or no</p></dd>\n</dl>\n\n<h3>Providers prefs</h3>\n\n<p>While <span class=\"information\">some player parameters, related to the item to play, should be used through attributes</span> (e.g. the Youtube <code>start</code> parameter which defines the second where to start playing the video), <span class=\"information\">these used as global settings should preferrably be set through the plugin preferences</span> to <span class=\"warning\">avoid a massive use of tag attributes</span>.</p>\n\n<p>Of course, it is still possible to override a plugin pref value via an attribute when needed.</p>\n\n<h2>Tags</h2>\n\n<h3>oui_player</h3>\n\n<p>Embeds a player.</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<h4>Presentational attributes</h4>\n\n<dl>\n	<dt><code>label</code></dt>\n	<dd><p>String; empty by default.<br />\nLabel for the video.</p></dd>\n	<dt><code>labeltag</code></dt>\n	<dd><p><span class=\"caps\">HTML</span> container tag name; empty by default.<br />\n<span class=\"caps\">HTML</span> wraptag for the label, without brackets.</p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p><span class=\"caps\">HTML</span> container tag name; empty by default.<br />\n<span class=\"caps\">HTML</span> tag to be used as the content wraptag, without brackets.</p></dd>\n	<dt><code>class</code></dt>\n	<dd><p>String; empty by default.<br />\n<span class=\"caps\">CSS</span> class for <span class=\"caps\">HTML</span> content wraptag.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>, related preference value used by default.<br />\nAdapts the final player size from provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h4>Main tag attributes</h4>\n\n<p>Most of the following attributes can be set via the <em>Preferences</em> tab; <ins>always prefer plugin &#8220;global&#8221; prefs to tag related attributes!</ins></p>\n\n<dl>\n	<dt><code>play</code></dt>\n	<dd><p>Valid values differs from one provider to another, check each extension.<br />\nThe item to play; <strong>required if</strong> it does not use the value of the custom field selected in the plugin prefs (See each provider to know the valid values).<br />\n<strong>default:</strong> <span class=\"information\">changed v1.2.0</span> the <code>play</code> attribute value of an eventual <code>&lt;txp:oui_if_play&gt;&lt;/txp:oui_if_play&gt;</code> container tag, or the custom field value of the one selected in the plugin prefs.</p></dd>\n	<dt><code>provider</code></dt>\n	<dd><p>Extension related provider name; uses the related preference value by default.<br />\nThe provider to use; <strong>required if</strong> the <code>play</code> attribute is an id, <strong>recommended anyways</strong> if known as it increases the plugin performances by skipping the provider guess.</p></dd>\n</dl>\n\n<h4>Providers related tag attributes</h4>\n\n<p>See each provider related extension.</p>\n\n<h3>oui_if_player</h3>\n\n<p>Checks the <code>play</code> attribute value againsts defined <span class=\"caps\">URL</span> schemes.</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player&gt;\n[…]\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<h4>Attributes</h4>\n\n<dl>\n	<dt><code>play</code></dt>\n	<dd><p>The custom field value of the one selected in the plugin prefs by default.<br />\nThe item to play; <strong>required if</strong> it does not use the value of the custom field selected in the plugin prefs (See each provider to know the valid values).</p></dd>\n	<dt><code>provider</code></dt>\n	<dd><p>Extension related provider name; empty by default.<br />\nCheck the <span class=\"caps\">URL</span> against valid <span class=\"caps\">URL</span> schemes of the defined provider.</p></dd>\n</dl>\n\n<h2>Examples</h2>\n\n<h3>Display a player from the default values collected via plugin prefs</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<h3>Display a player from a manually filled <span class=\"caps\">URL</span></h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player play=&quot;https://vimeo.com/155020267&quot; /&gt;</code></pre>\n\n<p>…or, maybe a bit faster…</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;vimeo&quot; play=&quot;https://vimeo.com/155020267&quot; /&gt;</code></pre>\n\n<h3>Display a player from manually filled ID</h3>\n\n<p>Using the favourite provider set in the prefs.</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player play=&quot;x4l8awd&quot; /&gt;</code></pre>\n\n<p>…or, in conjonction with the <code>provider</code> attribute…</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;dailymotion&quot; play=&quot;x4l8awd&quot; /&gt;</code></pre>\n\n<h3>Display a player only if its <span class=\"caps\">URL</span> is recognized as a provider <span class=\"caps\">URL</span> scheme</h3>\n\n<p>With the default values collected via plugin prefs…</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or, with a manually filled <span class=\"caps\">URL</span>…</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player play=&quot;https://vimeo.com/155020267&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>You can also add the provider attribute to check the <span class=\"caps\">URL</span> against <span class=\"caps\">URL</span> schemes of a defined provider.</p>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_player version 2.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>v2.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Added: admin side player preview;</li>\n		<li>Changed: excerpt field no longer supported;</li>\n		<li>Added: responsive embedding;\n		<ul>\n			<li>changed: providers related preferences displayed by default;</li>\n		</ul></li>\n		<li>changed: code rework:\n		<ul>\n			<li>providers now work as pluggable extensions:\n			<ul>\n				<li>Extension base (required): <a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a>; any plugin extending the <code>\\Oui\\Provider</code> class in an author prefix related namespace will be plugged into <em>oui_player</em>;</li>\n				<li><span class=\"caps\">ABC</span> News: <a href=\"https://github.com/NicolasGraph/oui_abcnews\">oui_abcnews</a>;</li>\n				<li>Archive: <a href=\"https://github.com/NicolasGraph/oui_archive\">oui_archive</a>;</li>\n				<li>Bandcamp: <a href=\"https://github.com/NicolasGraph/oui_bandcamp\">oui_bandcamp</a>;</li>\n				<li>Dailymotion: <a href=\"https://github.com/NicolasGraph/oui_dailymotion\">oui_bandcamp</a>;</li>\n				<li>Giphy: <a href=\"https://github.com/NicolasGraph/oui_giphy\">oui_giphy</a> (new);</li>\n				<li>Mixcloud: <a href=\"https://github.com/NicolasGraph/oui_mixcloud\">oui_mixcloud</a>;</li>\n				<li>MySpace: <a href=\"https://github.com/NicolasGraph/oui_myspace\">oui_myspace</a>;</li>\n				<li>HTML5 audio: <a href=\"https://github.com/NicolasGraph/oui_audio\">oui_audio</a>;</li>\n				<li>HTML5 video: <a href=\"https://github.com/NicolasGraph/oui_video\">oui_video</a>;</li>\n				<li>Soundcloud: <a href=\"https://github.com/NicolasGraph/oui_soundcloud\">oui_soundcloud</a>;</li>\n				<li>Twitch: <a href=\"https://github.com/NicolasGraph/oui_twitch\">oui_twitch</a>;</li>\n				<li>Viddsee: <a href=\"https://github.com/NicolasGraph/oui_viddsee\">oui_viddsee</a>;</li>\n				<li>Vimeo: <a href=\"https://github.com/NicolasGraph/oui_vimeo\">oui_vimeo</a>;</li>\n				<li>Vine: <a href=\"https://github.com/NicolasGraph/oui_vine\">oui_vine</a>;</li>\n				<li>Youtube: <a href=\"https://github.com/NicolasGraph/oui_youtube\">oui_youtube</a>;</li>\n			</ul></li>\n			<li>author prefix related namespace;</li>\n			<li>new class names (<code>Admin</code> =&gt; <code>PlayerAdmin</code>, <code>Main</code> =&gt; <code>Player</code>, <code>Player</code> =&gt; <code>PlayerBase</code>);</li>\n			<li>tags now registered with related callback methods from the <code>Player</code> class;</li>\n			<li>providers stored with their plugin author related prefix/namespace.</li>\n			<li>instances created/get via <code>Txp::get()</code>.</li>\n		</ul></li>\n		</ul></li>\n	<li><strong>v1.3.1</strong> (2017-07-13)</li>\n	<li><strong>v1.3.0</strong> (2017-05-24)\n	<ul>\n		<li>Adds <a href=\"https://getcomposer.org/\">Composer</a> support for <a href=\"https://packagist.org/packages/textpattern/installer\">textpattern/installer</a>;</li>\n		<li>fixes multiple players prefs display and improves perfs;</li>\n		<li>improves scripts embedding;</li>\n		<li>added: Youtube player parameters <code>enablejsapi</code> and <code>origin</code>;</li>\n		<li>added: Dailymotion player parameters <code>api</code> and <code>origin</code>;</li>\n		<li>added: Vimeo player parameter <code>api</code> and removes <code>badge</code>;</li>\n		<li>added: support for:\n		<ul>\n			<li><a href=\"https://bandcamp.com/\">Bandcamp</a>;</li>\n			<li>HTML5 <a href=\"https://developer.mozilla.org/fr/docs/Web/HTML/Element/Audio\">audio</a> + <a href=\"https://developer.mozilla.org/fr/docs/Web/HTML/Element/Video\">video</a>;</li>\n		</ul></li>\n		</ul></li>\n	<li><strong>v1.2.1</strong> (2017-01-16)\n	<ul>\n		<li>Fixed: Fixes the <em>Field used by default</em> pref display when no custom field exists;</li>\n		<li>fixed: fixes Textpack typo;</li>\n		<li>changed: compiled code lightened by removing duplicated license related comments;</li>\n	</ul></li>\n	<li><strong>v1.2.0</strong> (2016-12-12)\n	<ul>\n		<li>added: sets the custom field plugin pref as the defaut value of the conditional tag <code>play</code> attribute;</li>\n		<li>added: introduces a <code>play</code> attribute value inheritance in a conditional/container tag context;</li>\n		<li>added: support for:\n		<ul>\n			<li><a href=\"https://www.twitch.tv/\">Twitch channels</a>,</li>\n			<li><a href=\"https://vine.co/\">Vine</a>;</li>\n		</ul></li>\n		<li>changed: improves <span class=\"caps\">PHP</span> classes and methods use (see <em>For developers</em>).</li>\n	</ul></li>\n	<li><strong>oui_player v1.1.0</strong> (2016-10-14)\n	<ul>\n		<li>changed: plugin/tag names;</li>\n		<li>removed: Txp 4.5 support;</li>\n		<li>changed: consistency between attribute and player parameter names increased;</li>\n		<li>changed: code rework to be easily extendable;</li>\n		<li>added: support for:\n		<ul>\n			<li><a href=\"http://abcnews.go.com/video\">Abc News</a>,</li>\n			<li><a href=\"https://archive.org/\">Archive</a>,</li>\n			<li><a href=\"https://www.mixcloud.com/\">Mixcloud</a>,</li>\n			<li><a href=\"https://myspace.com/myspace/videos\">Myspace videos</a>,</li>\n			<li><a href=\"https://soundcloud.com/\">Soundcloud</a>,</li>\n			<li><a href=\"https://www.twitch.tv/\">Twitch</a>,</li>\n			<li><a href=\"https://www.viddsee.com/\">Viddsee</a>;</li>\n		</ul></li>\n		<li>(changed) Help file styling improvements.</li>\n	</ul></li>\n	<li><strong>oui_video v0.1.0</strong> (2016-09-13)</li>\n	<li><strong>oui_dailymotion v1.3.1</strong> (2016-02-25) inspired by <a href=\"http://andy-carter.com/txp/arc_youtube\">arc_youtube</a> and <a href=\"http://andy-carter.com/txp/arc_vimeo\">arc_vimeo</a> by <a href=\"http://andy-carter.com\">Andy Carter</a>.</li>\n</ul>', '\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n /**\n  * PlayerBase\n  *\n  * Plugin base class.\n  *\n  * @package Oui\\Player\n  */\n\nnamespace Oui;\n\nabstract class PlayerBase\n{\n    /**\n     * Master plugin name.\n     *\n     * @var string\n     * @see getPlugin().\n     */\n\n    protected static $plugin = \'oui_player\';\n\n    /**\n     * Installed providers as an array of provider names\n     * associated with their extensions related plugin author prefixes.\n     *\n     * @var array\n     * @see setProviders(), getProviders().\n     */\n\n    protected static $providers = array();\n\n    /**\n     * Media related field name.\n     *\n     * @var string\n     * @see setMediaField(), getMediaField().\n     */\n\n    protected static $mediaField;\n\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        self::setMediaField();\n        static::setProviders();\n    }\n\n    /**\n     * $plugin getter.\n     *\n     * @return string\n     */\n\n    public static function getPlugin()\n    {\n        return self::$plugin;\n    }\n\n    /**\n     * $providers getter.\n     *\n     * @return array\n     */\n\n    protected static function getProviders()\n    {\n        return static::$providers;\n    }\n\n    /**\n     * $mediaField setter.\n     */\n\n    protected static function setMediaField()\n    {\n        return self::$mediaField = get_pref(\'oui_player_custom_field\');\n    }\n\n    /**\n     * $mediaField getter.\n     */\n\n    protected static function getMediaField()\n    {\n        return self::$mediaField;\n    }\n}\n\n\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * PalyerAdmin\n *\n * Manages admin side plugin features.\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nclass PlayerAdmin extends PlayerBase\n{\n    /**\n     * Plugin preferences visibility related privilege levels.\n     *\n     * @var string\n     * @see getPrivs();\n     */\n\n    protected static $privs = \'1, 2\';\n\n    /**\n     * Associative array of all preference names and their initial related infos.\n     * TODO: complete docs.\n     *\n     * @var array\n     * @see setIniPrefs(), getIniPrefs().\n     */\n\n    protected static $iniPrefs;\n\n    /**\n     * Associative array of all preference names and their current related values.\n     *\n     * @var array.\n     * @see getPrefs();\n     */\n\n    protected static $prefs;\n\n    /**\n     * Constructor\n     * - Add main privileges;\n     * - register callbacks.\n     */\n\n    public function __construct()\n    {\n        parent::__construct();\n\n        $plugin = self::getPlugin();\n\n        foreach (array(\'plugin_prefs.\', \'prefs.\') as $event) {\n            add_privs($event . $plugin, self::getPrivs());\n        }\n\n        register_callback(array($this, \'managePrefs\'), \'prefs\', \'\', 1);\n        register_callback(\'Oui\\PlayerAdmin::optionsLink\', \'plugin_prefs.\' . $plugin, null, 1);\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.\' . $plugin, \'deleted\');\n\n        foreach (self::getProviders() as $provider => $author) {\n            $extension = strtolower($author . \'_\' . $provider);\n\n            add_privs(\'plugin_prefs.\' . $extension, self::getPrivs());\n            register_callback(\'Oui\\PlayerAdmin::optionsLink\', \'plugin_prefs.\' . $extension, null, 1);\n        }\n\n        $mediaField = self::getMediaField();\n        $step = $mediaField === \'article_image\' ? $mediaField : \'custom_fields\';\n\n        register_callback(array($this, \'render\'), \'article_ui\', $step);\n    }\n\n    /**\n     * $providers setter.\n     * Trigger an error on duplicate provider related extensions.\n     */\n\n    public static function setProviders()\n    {\n        foreach(get_declared_classes() as $className) {\n            if (is_subclass_of($className, \'Oui\\Provider\')) {\n                list($author, $provider) = explode(\'\\\\\', $className);\n\n                if (array_key_exists($provider, static::$providers)) {\n                    trigger_error(gtxt(array(\n                        \'oui_player_duplicate_provider_extensions\',\n                        \'{provider}\' => $provider,\n                    )));\n                } else {\n                    static::$providers[$provider] = $author;\n                }\n            }\n        }\n    }\n\n    /**\n     * $privs getter.\n     *\n     * @return array\n     */\n\n    public static function getPrivs()\n    {\n        return self::$privs;\n    }\n\n    /**\n     * $iniPrefs property setter\n     */\n\n    public static function setIniPrefs()\n    {\n        $providers = self::getProviders();\n        $providerNames = array_keys($providers);\n\n        $mainPrefs = array(\n            \'custom_field\' => array(\n                \'widget\'  => \'Oui\\PlayerAdmin::customFields\',\n                \'default\' => \'article_image\',\n            ),\n            \'provider\' => array(\n                \'default\' => $providerNames[0],\n                \'valid\'   => $providerNames,\n            ),\n            \'providers\' => array(\n                \'default\' => http_build_query($providers),\n                \'type\'    => PREF_HIDDEN,\n            ),\n        );\n\n        $plugin = self::getPlugin();\n\n        // Collect the plugin main prefs.\n        foreach ($mainPrefs as $pref => $options) {\n            $options[\'group\'] = $plugin;\n            $pref = $options[\'group\'] . \'_\' . $pref;\n            static::$iniPrefs[$pref] = $options;\n        }\n\n        foreach ($providers as $provider => $author) {\n            // Add privilieges to provider prefs only if they are enabled.\n            $group = $plugin . \'_\' . strtolower($provider);\n            $pref = $group . \'_prefs\';\n\n            if (!empty($_POST[$pref]) || (!isset($_POST[$pref]) && get_pref($pref))) {\n                add_privs(\'prefs.\' . $group, self::getPrivs());\n            }\n\n            // Add a pref per provider to display/hide its own prefs group.\n            $options = array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            );\n            $options[\'group\'] = $plugin;\n            static::$iniPrefs[$pref] = $options;\n\n            // Collect provider prefs.\n            $class = $author . \'\\\\\' . $provider;\n            static::$iniPrefs = array_merge(static::$iniPrefs, $class::GetIniPrefs());\n        }\n    }\n\n    /**\n     * $iniPrefs getter\n     *\n     * @return array\n     */\n\n    public static function getIniPrefs()\n    {\n        static::$iniPrefs or self::setIniPrefs();\n\n        return static::$iniPrefs;\n    }\n\n    /**\n     * $prefs setter.\n     */\n\n    protected static function setPrefs()\n    {\n        $rs = safe_rows_start(\n            \'name, html\',\n            doSlash(\'txp_prefs\'),\n            \"name LIKE \'\" . doSlash(self::getPlugin() . \'_%\') . \"\'\"\n        );\n\n        if ($rs) {\n            static::$prefs = array();\n\n            while ($row = nextRow($rs)) {\n                static::$prefs[$row[\'name\']] = $row[\'html\'];\n            }\n        }\n    }\n\n    /**\n     * $prefs getter.\n     * Call setter if necesary.\n     *\n     * @return array\n     */\n\n    public static function getPrefs()\n    {\n        static::$prefs or self::setPrefs();\n\n        return static::$prefs;\n    }\n\n    /**\n     * Install/update/remove preferences.\n     */\n\n    public function managePrefs()\n    {\n        if (self::getProviders()) {\n            $this->upsertPrefs();\n            $this->deleteOldPrefs();\n        } else {\n            $this->uninstall();\n        }\n    }\n\n    /**\n     * Upsert plugin preferences.\n     */\n\n    public function upsertPrefs()\n    {\n        $iniPrefs = $this->getIniPrefs();\n        $position = 250;\n        $existing = self::getPrefs();\n\n        foreach ($iniPrefs as $pref => $options) {\n            $default = isset($options[\'default\']) ? $options[\'default\'] : \'\';\n\n            if (isset($options[\'widget\'])) {\n                $widget = $options[\'widget\'];\n            } else {\n                $valid = isset($options[\'valid\']) ? $options[\'valid\'] : null;\n                $widget = self::getPrefWidget($valid);\n            }\n\n            if ($pref === \'oui_player_providers\') {\n                set_pref(\n                    $pref,\n                    $default,\n                    $options[\'group\'],\n                    PREF_HIDDEN,\n                    $widget,\n                    $position\n                );\n            } elseif (!isset($existing[$pref])) {\n                create_pref(\n                    $pref,\n                    $default,\n                    $options[\'group\'],\n                    isset($options[\'type\']) ? $options[\'type\'] : PREF_PLUGIN,\n                    $widget,\n                    $position\n                );\n            } elseif ($existing[$pref] !== $widget) {\n                update_pref($pref, $default, null, null, $widget, null);\n            }\n\n            $position += 10;\n        }\n    }\n\n    /**\n     * Delete outdated plugin preferences.\n     */\n\n    protected function deleteOldPrefs()\n    {\n        safe_delete(\n            \'txp_prefs\',\n            \"event LIKE \'\" . self::getPlugin() . \"%\' AND \" .\n            \"name NOT IN ( \'\" . implode(array_keys($this->getIniPrefs()), \"\', \'\") . \"\' )\"\n        );\n    }\n\n    /**\n     * Remove plugin preferences.\n     */\n\n    public function uninstall()\n    {\n        safe_delete(\'txp_prefs\', \"event LIKE \'\" . self::getPlugin() . \"%\'\");\n    }\n\n    /**\n     * Redirect Options links to the general preferences tab.\n     */\n\n    public static function optionsLink()\n    {\n        global $event;\n\n        header(\'Location: ?event=\' . str_replace(\'plugin_prefs.\', \'prefs#prefs_group_\', $event));\n    }\n\n    /**\n     * Define a plugin preference widget.\n     *\n     * @param  array  $options Current pref options.\n     * @return string Function/method name.\n     */\n\n    protected static function getPrefWidget($valid = null)\n    {\n        if ($valid) {\n            $validIsArray = is_array($valid);\n\n            if ($validIsArray && empty(array_diff($valid, array(\'0\', \'1\')))) {\n                $widget = \'yesnoradio\';\n            } elseif ($validIsArray && empty(array_diff($valid, array(\'true\', \'false\')))) {\n                $widget = \'Oui\\PlayerAdmin::truefalseradio\';\n            } else {\n                $widget = \'Oui\\PlayerAdmin::prefFunction\';\n            }\n        } else {\n            $widget = \'text_input\';\n        }\n\n        return $widget;\n    }\n\n    /**\n     * Generate a Yes/No radio button toggle using \'true\' and \'false\' as values.\n     *\n     * @param  string $field    The field name\n     * @param  string $checked  The checked button, either \'true\', \'false\'\n     * @param  int    $tabindex The HTML tabindex\n     * @param  string $id       The HTML id\n     * @return string HTML\n     */\n\n    public static function truefalseradio(\n        $field,\n        $checked = \'\',\n        $tabindex = 0,\n        $id = \'\'\n    ) {\n        $vals = array(\n            \'false\' => gTxt(\'no\'),\n            \'true\'  => gTxt(\'yes\'),\n        );\n\n        return radioSet($vals, $field, $checked, $tabindex, $id);\n    }\n\n    /**\n     * Plugin preference widget.\n     *\n     * @param  string $name The preference name\n     * @param  string $val  The preference value\n     * @return string HTML\n     */\n\n    public static function prefFunction($name, $val)\n    {\n        $prefs = self::getIniPrefs();\n        $valid = $prefs[$name][\'valid\'];\n\n        if (is_array($valid)) {\n            $vals = array();\n\n            foreach ($valid as $value) {\n                $value === \'\' ?: $vals[$value] = gtxt($name . \'_\' . strtolower($value));\n            }\n\n            return selectInput($name, $vals, $val, $valid[0] === \'\' ? true : false);\n        } else {\n            return fInput($valid, $name, $val);\n        }\n    }\n\n    /**\n     * Generate a select list of article_image + custom fields.\n     *\n     * @param  string $name The name of the preference\n     * @param  string $val  The value of the preference\n     * @return string HTML\n     */\n\n    public static function customFields($name, $val)\n    {\n        $vals = array();\n        $vals[\'article_image\'] = gtxt(\'article_image\');\n        // $vals[\'excerpt\'] = gtxt(\'excerpt\');\n\n        $customFields = safe_rows(\n            \"name, val\",\n            \'txp_prefs\',\n            \"name LIKE \'custom_%_set\' AND val<>\'\' ORDER BY name\"\n        );\n\n        foreach ($customFields as $row) {\n            $vals[$row[\'val\']] = $row[\'val\'];\n        }\n\n        return selectInput($name, $vals, $val);\n    }\n\n    /**\n     * Add a responsive player preview near to the media related field.\n     *\n     * @param  string $evt  Textpattern event (panel)\n     * @param  string $stp  Textpattern step (action)\n     * @param  string $data Original markup\n     * @param  array  $rs   Accompanyng record set\n     * @return string       HTML\n     */\n\n    public function render($evt, $stp, $data, $rs)\n    {\n        $fieldCol = self::getMediaField();\n\n        // Find the $mediaField related ID.\n        if ($fieldCol === \'article_image\') {\n            $inputName = \'Image\';\n        } else {\n            $inputName = str_replace(\'_set\', \'\', safe_field(\'name\', \'txp_prefs\', \'val LIKE \"\' . $fieldCol . \'\"\'));\n        }\n\n        if ($rs[$inputName]) { // Add the player preview.\n            $pluginName = self::getPlugin();\n            $inputID = str_replace(\'_\', \'-\', $inputName);\n            $class = $inputID . str_replace(\'_\', \'-\', $pluginName);\n            $data .= \\Txp::get(\'\\Oui\\Player\')->renderPlayer(array(\n                    \'play\'       => $rs[$inputName],\n                    \'wraptag\'    => \'div\',\n                    \'class\'      => $class,\n                    \'responsive\' => true,\n                )) . \'</div>\' . n .\n                \'<script>$(function() { $( \".\' . $class . \'\" ).css(\"margin-top\", \"1em\").insertAfter( \"#\' . $inputID . \'\" ); });</script>\';\n        }\n\n        return $data;\n    }\n}\n\ntxpinterface === \'admin\' ? new PlayerAdmin : \'\';\n\n\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n /**\n  * Player\n  *\n  * Manages public side plugin features.\n  *\n  * @package Oui\\Player\n  */\n\nnamespace Oui;\n\nclass Player extends PlayerBase implements \\Textpattern\\Container\\ReusableInterface\n{\n    /**\n     * Initial plugin tags and attributes.\n     *\n     * @var array\n     * @see getIniTagAtts().\n     */\n\n    private static $iniTagAtts = array(\n        \'oui_player\'    => array(\n            \'class\',\n            \'label\',\n            \'labeltag\',\n            \'provider\',\n            \'play\',\n            \'wraptag\',\n        ),\n        \'oui_if_player\' => array(\n            \'play\',\n            \'provider\',\n        ),\n    );\n\n    /**\n     * Media URL/ID.\n     *\n     * @var string|array An array can be used to provide multiple sources to HTML5 players.\n     * @see setMedia(), getMedia().\n     */\n\n    protected $media;\n\n    /**\n     * Media related provider.\n     *\n     * @var string\n     * @see getMediaProvider().\n     */\n\n    protected $mediaProvider;\n\n    /**\n     * Associative array of $media value(s) and their related infos.\n     *\n     * @var array\n     * @see setMediaInfos(), getMediaInfos().\n     */\n\n    protected $mediaInfos = array();\n\n    /**\n     * Associative array of \'media\' and \'provider\' values cached via the conditional tag for inheritence.\n     *\n     * @var array\n     * @see setCache(), getCache().\n     */\n\n    protected static $cache = array();\n\n    /**\n     * Constructor\n     * - Register all tags.\n     */\n\n    public function __construct() {\n        parent::__construct();\n\n        // Register initial tags.\n        foreach (self::getIniTagAtts() as $tag => $atts) {\n            $tagMethod = str_replace(array(\'oui\', \'_\'), array(\'render\', \'\'), $tag);\n            $tagMethods[$tag] = $tagMethod;\n\n            \\Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(array($this, $tagMethod), $tag);\n        }\n\n        // Register providers/extensions related tags.\n        foreach (self::getProviders() as $provider => $author) {\n            $lcProvider = strtolower($provider);\n\n            foreach ($tagMethods as $tag => $method) {\n                \\Txp::get(\'\\Textpattern\\Tag\\Registry\')->register($author . \'\\\\\' . $lcProvider . \'::\' . $method, str_replace(\'player\', $lcProvider, $tag));\n            }\n        }\n    }\n\n    /**\n     * $iniTagAtts getter.\n     *\n     * @param  string $tag Optional tag name.\n     * @return array\n     */\n\n    protected static function getIniTagAtts($tag = null)\n    {\n        return $tag ? static::$iniTagAtts[$tag] : static::$iniTagAtts;\n    }\n\n    /**\n     * $providers setter.\n     */\n\n    protected static function setProviders()\n    {\n        foreach (explode(\'&\', get_pref(self::getPlugin() . \'_providers\')) as $providerAuthor) {\n            list($provider, $author) = explode(\'=\', $providerAuthor);\n            static::$providers[$provider] = $author;\n        }\n    }\n\n    /**\n     * $media setter.\n     * Also call the $mediaInfos setter.\n     *\n     * @param string|array $value    The media URL/ID to play,\n     *                               an array can be used to provide multiple sources to HTML5 players;\n     * @param bool         $fallback Whether to force the $mediaInfos setter to set fallback infos or not.\n     * @return object $this\n     */\n\n    public function setMedia($value, $fallback = false)\n    {\n        $this->media = $value;\n        is_array($value) ?: $value = array($value);\n        $mediaInfos = $this->getMediaInfos();\n\n        if (!$mediaInfos || array_diff($value, array_keys($mediaInfos))) {\n            $this->setMediaInfos($fallback);\n        }\n\n        return $this;\n    }\n\n    /**\n     * $media getter.\n     *\n     * @return string|array.\n     */\n\n    public function getMedia()\n    {\n        return $this->media;\n    }\n\n    /**\n     * $provider getter (call the setter if necessary).\n     *\n     * @param  bool  Whether to force the $mediaInfos setter to set fallback infos or not.\n     * @return array|false false if $fallback is disable and the $media is not recognised as a valid provider related URL.\n     */\n\n    protected function getMediaProvider($fallback = false)\n    {\n        $this->mediaInfos or $this->setMediaInfos($fallback);\n        $media = $this->getMedia();\n        is_array($media) ?: $media = explode(\', \', $media);\n\n        if ($this->mediaProvider) {\n            return $this->mediaProvider;\n        }\n\n        return false;\n    }\n\n    /**\n     * $mediaInfos setter\n     *\n     * @param  bool $fallback Whether to set fallback media infos or not.\n     * @return array\n     */\n\n    protected function setMediaInfos($fallback = false)\n    {\n        $providers = self::getProviders();\n        $media = $this->getMedia();\n\n        foreach ($providers as $provider => $author) {\n            $this->mediaInfos = \\Txp::get($author . \'\\\\\' . $provider)\n                ->setMedia($media)\n                ->getMediaInfos();\n\n            if ($this->mediaInfos) {\n                $this->mediaProvider = $provider;\n                return $this->mediaInfos;\n            }\n        }\n\n        if ($fallback) { // No matched provider, set default media infos.\n            $this->mediaInfos = array(\n                $media => array(\n                    \'id\'  => $media,\n                    \'uri\' => $media,\n                )\n            );\n\n            $this->mediaProvider = get_pref(self::getPlugin() . \'_provider\');\n        }\n\n        return $this->mediaInfos;\n    }\n\n    /**\n     * $mediaInfos getter.\n     *\n     * @return array\n     */\n\n    public function getMediaInfos()\n    {\n        return $this->mediaInfos;\n    }\n\n    /**\n     * $cache setter.\n     *\n     * @return array\n     */\n\n    protected static function setCache($media = null, $provider = null)\n    {\n        return self::$cache = $media ? compact(\'media\', \'provider\') : array();\n    }\n\n    /**\n     * $cache getter.\n     *\n     * @return array\n     */\n\n    protected static function getCache()\n    {\n        return self::$cache;\n    }\n\n    /**\n     * Get all tag attributes — including providers related ones.\n     *\n     * @param string $tag A plugin tag.\n     * @return array Tag attributes\n     */\n\n    protected static function getTagAtts($tag)\n    {\n        $allAtts = self::getIniTagAtts($tag);\n\n        if ($tag === self::getPlugin()) {\n            // Collects provider attributes.\n            foreach (self::getProviders() as $provider => $author) {\n                $class = $author . \'\\\\\' . $provider;\n                $allAtts = array_merge($allAtts, $class::getTagAtts($tag));\n            }\n        }\n\n        return array_fill_keys($allAtts, \'\');\n    }\n\n    /**\n     * Generate a player.\n     * oui_player tag related callback method.\n     *\n     * @param  array  $atts Tag attributes\n     * @return string HTML\n     */\n\n    public function renderPlayer($atts)\n    {\n        $lAtts = lAtts(self::getTagAtts(\'oui_player\'), $atts);\n\n        $notParams = array_merge(\n            self::getIniTagAtts(),\n            array(\'width\', \'height\', \'ratio\', \'responsive\')\n        );\n\n        $params = array();\n\n        // Parse attribute values.\n        foreach ($lAtts as $lAtt => $value) {\n            switch ($value) {\n                case \'\':\n                    $lAtts[$lAtt] = null;\n                    break;\n                case \'true\':\n                    $lAtts[$lAtt] = true;\n                    break;\n                case \'false\':\n                    $lAtts[$lAtt] = false;\n                    break;\n            }\n\n            // Store player parameters related values.\n            if (!in_array($lAtt, $notParams) && $lAtts[$lAtt] !== null) {\n                $params[$lAtt] = $lAtts[$lAtt];\n            }\n        }\n\n        extract($lAtts);\n\n        $playProvider = $this->parsePlayProvider($play, $provider, true, true);\n\n        if (!$playProvider) {\n            return;\n        }\n\n        extract($playProvider);\n\n        $player = \\Txp::get(self::getProviders()[$provider] . \'\\\\\' . $provider)\n            ->setMedia($play, true)\n            ->setDims($width, $height, $ratio, $responsive)\n            ->setParams($params);\n\n        $label ? $player->setLabel($label, $labeltag) : \'\';\n        $wraptag ? $player->setWrap($wraptag, $class) : \'\';\n\n        return $player->render();\n    }\n\n    /**\n     * Generates tag contents or alternative contents.\n     * oui_if_player tag related callback method.\n     *\n     * Generated contents depends on whether the \'play\' attribute value\n     * matches a provider URL scheme.\n     *\n     * @param  array  $atts  Tag attributes\n     * @param  string $thing Tag contents\n     * @return mixed  Tag contents or alternative contents\n     */\n\n    public function renderIfPlayer($atts, $thing)\n    {\n        $lAtts = lAtts(self::getTagAtts(\'oui_if_player\'), $atts);\n\n        extract($lAtts);\n\n        $playProvider = $this->parsePlayProvider($play, $provider);\n\n        if ($playProvider) {\n            extract($playProvider);\n\n            $isValid = \\Txp::get(self::getProviders()[$provider] . \'\\\\\' . $provider)->setMedia($play)->getMediaInfos();\n\n            if ($isValid) { // Set the cache.\n                self::setCache(implode(\', \', array_keys($isValid)), $provider);\n                $out = parse($thing, true); // Set the output.\n                self::setCache(); // Reset the cache.\n            }\n        }\n\n        return isset($out) ? $out : parse($thing, false);\n    }\n\n    /**\n     * Parse play and provider attribute to get their right values.\n     *\n     * @param string $play     The play attribute value;\n     * @param string $provider The provider attribute value;\n     * @param string $cache    Whether to get potentially cached values or not;\n     * @param string $fallback Whether to get the default provider if the play value is not recognised.\n     */\n\n    protected function parsePlayProvider(\n        $play = null,\n        $provider = null,\n        $cache = false,\n        $fallback = false\n    ) {\n        global $thisarticle;\n\n        if (!$play) { // Set the potentially missing media URL/ID.\n            $cache = $cache ? self::getCache() : \'\';\n            $field = strtolower(self::getMediaField());\n\n            if ($cache) { // Play the cache related media.\n                $play = $cache[\'media\'];\n                $provider = $cache[\'provider\'];\n            } elseif (isset($thisarticle[$field])) { // Play the field related media.\n                $play = $thisarticle[$field];\n            } else {\n                trigger_error(\'play attribute or related field required\');\n            }\n        }\n\n        if ($play) {\n            // Explode the play attribute value if it is a comma separated list of values.\n            explode(\', \', $play, -1) ? $play = explode(\', \', $play) : \'\';\n\n            // Find the potentially missing provider name.\n            $provider = $provider ? ucfirst($provider) : $this->setMedia($play)->getMediaProvider($fallback);\n            $play = compact(\'play\', \'provider\');\n        }\n\n        return $play;\n    }\n}\n\ntxpinterface === \'public\' ? \\Txp::get(\'Oui\\Player\') : \'\';\n', '\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n /**\n  * PlayerBase\n  *\n  * Plugin base class.\n  *\n  * @package Oui\\Player\n  */\n\nnamespace Oui;\n\nabstract class PlayerBase\n{\n    /**\n     * Master plugin name.\n     *\n     * @var string\n     * @see getPlugin().\n     */\n\n    protected static $plugin = \'oui_player\';\n\n    /**\n     * Installed providers as an array of provider names\n     * associated with their extensions related plugin author prefixes.\n     *\n     * @var array\n     * @see setProviders(), getProviders().\n     */\n\n    protected static $providers = array();\n\n    /**\n     * Media related field name.\n     *\n     * @var string\n     * @see setMediaField(), getMediaField().\n     */\n\n    protected static $mediaField;\n\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        self::setMediaField();\n        static::setProviders();\n    }\n\n    /**\n     * $plugin getter.\n     *\n     * @return string\n     */\n\n    public static function getPlugin()\n    {\n        return self::$plugin;\n    }\n\n    /**\n     * $providers getter.\n     *\n     * @return array\n     */\n\n    protected static function getProviders()\n    {\n        return static::$providers;\n    }\n\n    /**\n     * $mediaField setter.\n     */\n\n    protected static function setMediaField()\n    {\n        return self::$mediaField = get_pref(\'oui_player_custom_field\');\n    }\n\n    /**\n     * $mediaField getter.\n     */\n\n    protected static function getMediaField()\n    {\n        return self::$mediaField;\n    }\n}\n\n\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * PalyerAdmin\n *\n * Manages admin side plugin features.\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nclass PlayerAdmin extends PlayerBase\n{\n    /**\n     * Plugin preferences visibility related privilege levels.\n     *\n     * @var string\n     * @see getPrivs();\n     */\n\n    protected static $privs = \'1, 2\';\n\n    /**\n     * Associative array of all preference names and their initial related infos.\n     * TODO: complete docs.\n     *\n     * @var array\n     * @see setIniPrefs(), getIniPrefs().\n     */\n\n    protected static $iniPrefs;\n\n    /**\n     * Associative array of all preference names and their current related values.\n     *\n     * @var array.\n     * @see getPrefs();\n     */\n\n    protected static $prefs;\n\n    /**\n     * Constructor\n     * - Add main privileges;\n     * - register callbacks.\n     */\n\n    public function __construct()\n    {\n        parent::__construct();\n\n        $plugin = self::getPlugin();\n\n        foreach (array(\'plugin_prefs.\', \'prefs.\') as $event) {\n            add_privs($event . $plugin, self::getPrivs());\n        }\n\n        register_callback(array($this, \'managePrefs\'), \'prefs\', \'\', 1);\n        register_callback(\'Oui\\PlayerAdmin::optionsLink\', \'plugin_prefs.\' . $plugin, null, 1);\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.\' . $plugin, \'deleted\');\n\n        foreach (self::getProviders() as $provider => $author) {\n            $extension = strtolower($author . \'_\' . $provider);\n\n            add_privs(\'plugin_prefs.\' . $extension, self::getPrivs());\n            register_callback(\'Oui\\PlayerAdmin::optionsLink\', \'plugin_prefs.\' . $extension, null, 1);\n        }\n\n        $mediaField = self::getMediaField();\n        $step = $mediaField === \'article_image\' ? $mediaField : \'custom_fields\';\n\n        register_callback(array($this, \'render\'), \'article_ui\', $step);\n    }\n\n    /**\n     * $providers setter.\n     * Trigger an error on duplicate provider related extensions.\n     */\n\n    public static function setProviders()\n    {\n        foreach(get_declared_classes() as $className) {\n            if (is_subclass_of($className, \'Oui\\Provider\')) {\n                list($author, $provider) = explode(\'\\\\\', $className);\n\n                if (array_key_exists($provider, static::$providers)) {\n                    trigger_error(gtxt(array(\n                        \'oui_player_duplicate_provider_extensions\',\n                        \'{provider}\' => $provider,\n                    )));\n                } else {\n                    static::$providers[$provider] = $author;\n                }\n            }\n        }\n    }\n\n    /**\n     * $privs getter.\n     *\n     * @return array\n     */\n\n    public static function getPrivs()\n    {\n        return self::$privs;\n    }\n\n    /**\n     * $iniPrefs property setter\n     */\n\n    public static function setIniPrefs()\n    {\n        $providers = self::getProviders();\n        $providerNames = array_keys($providers);\n\n        $mainPrefs = array(\n            \'custom_field\' => array(\n                \'widget\'  => \'Oui\\PlayerAdmin::customFields\',\n                \'default\' => \'article_image\',\n            ),\n            \'provider\' => array(\n                \'default\' => $providerNames[0],\n                \'valid\'   => $providerNames,\n            ),\n            \'providers\' => array(\n                \'default\' => http_build_query($providers),\n                \'type\'    => PREF_HIDDEN,\n            ),\n        );\n\n        $plugin = self::getPlugin();\n\n        // Collect the plugin main prefs.\n        foreach ($mainPrefs as $pref => $options) {\n            $options[\'group\'] = $plugin;\n            $pref = $options[\'group\'] . \'_\' . $pref;\n            static::$iniPrefs[$pref] = $options;\n        }\n\n        foreach ($providers as $provider => $author) {\n            // Add privilieges to provider prefs only if they are enabled.\n            $group = $plugin . \'_\' . strtolower($provider);\n            $pref = $group . \'_prefs\';\n\n            if (!empty($_POST[$pref]) || (!isset($_POST[$pref]) && get_pref($pref))) {\n                add_privs(\'prefs.\' . $group, self::getPrivs());\n            }\n\n            // Add a pref per provider to display/hide its own prefs group.\n            $options = array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            );\n            $options[\'group\'] = $plugin;\n            static::$iniPrefs[$pref] = $options;\n\n            // Collect provider prefs.\n            $class = $author . \'\\\\\' . $provider;\n            static::$iniPrefs = array_merge(static::$iniPrefs, $class::GetIniPrefs());\n        }\n    }\n\n    /**\n     * $iniPrefs getter\n     *\n     * @return array\n     */\n\n    public static function getIniPrefs()\n    {\n        static::$iniPrefs or self::setIniPrefs();\n\n        return static::$iniPrefs;\n    }\n\n    /**\n     * $prefs setter.\n     */\n\n    protected static function setPrefs()\n    {\n        $rs = safe_rows_start(\n            \'name, html\',\n            doSlash(\'txp_prefs\'),\n            \"name LIKE \'\" . doSlash(self::getPlugin() . \'_%\') . \"\'\"\n        );\n\n        if ($rs) {\n            static::$prefs = array();\n\n            while ($row = nextRow($rs)) {\n                static::$prefs[$row[\'name\']] = $row[\'html\'];\n            }\n        }\n    }\n\n    /**\n     * $prefs getter.\n     * Call setter if necesary.\n     *\n     * @return array\n     */\n\n    public static function getPrefs()\n    {\n        static::$prefs or self::setPrefs();\n\n        return static::$prefs;\n    }\n\n    /**\n     * Install/update/remove preferences.\n     */\n\n    public function managePrefs()\n    {\n        if (self::getProviders()) {\n            $this->upsertPrefs();\n            $this->deleteOldPrefs();\n        } else {\n            $this->uninstall();\n        }\n    }\n\n    /**\n     * Upsert plugin preferences.\n     */\n\n    public function upsertPrefs()\n    {\n        $iniPrefs = $this->getIniPrefs();\n        $position = 250;\n        $existing = self::getPrefs();\n\n        foreach ($iniPrefs as $pref => $options) {\n            $default = isset($options[\'default\']) ? $options[\'default\'] : \'\';\n\n            if (isset($options[\'widget\'])) {\n                $widget = $options[\'widget\'];\n            } else {\n                $valid = isset($options[\'valid\']) ? $options[\'valid\'] : null;\n                $widget = self::getPrefWidget($valid);\n            }\n\n            if ($pref === \'oui_player_providers\') {\n                set_pref(\n                    $pref,\n                    $default,\n                    $options[\'group\'],\n                    PREF_HIDDEN,\n                    $widget,\n                    $position\n                );\n            } elseif (!isset($existing[$pref])) {\n                create_pref(\n                    $pref,\n                    $default,\n                    $options[\'group\'],\n                    isset($options[\'type\']) ? $options[\'type\'] : PREF_PLUGIN,\n                    $widget,\n                    $position\n                );\n            } elseif ($existing[$pref] !== $widget) {\n                update_pref($pref, $default, null, null, $widget, null);\n            }\n\n            $position += 10;\n        }\n    }\n\n    /**\n     * Delete outdated plugin preferences.\n     */\n\n    protected function deleteOldPrefs()\n    {\n        safe_delete(\n            \'txp_prefs\',\n            \"event LIKE \'\" . self::getPlugin() . \"%\' AND \" .\n            \"name NOT IN ( \'\" . implode(array_keys($this->getIniPrefs()), \"\', \'\") . \"\' )\"\n        );\n    }\n\n    /**\n     * Remove plugin preferences.\n     */\n\n    public function uninstall()\n    {\n        safe_delete(\'txp_prefs\', \"event LIKE \'\" . self::getPlugin() . \"%\'\");\n    }\n\n    /**\n     * Redirect Options links to the general preferences tab.\n     */\n\n    public static function optionsLink()\n    {\n        global $event;\n\n        header(\'Location: ?event=\' . str_replace(\'plugin_prefs.\', \'prefs#prefs_group_\', $event));\n    }\n\n    /**\n     * Define a plugin preference widget.\n     *\n     * @param  array  $options Current pref options.\n     * @return string Function/method name.\n     */\n\n    protected static function getPrefWidget($valid = null)\n    {\n        if ($valid) {\n            $validIsArray = is_array($valid);\n\n            if ($validIsArray && empty(array_diff($valid, array(\'0\', \'1\')))) {\n                $widget = \'yesnoradio\';\n            } elseif ($validIsArray && empty(array_diff($valid, array(\'true\', \'false\')))) {\n                $widget = \'Oui\\PlayerAdmin::truefalseradio\';\n            } else {\n                $widget = \'Oui\\PlayerAdmin::prefFunction\';\n            }\n        } else {\n            $widget = \'text_input\';\n        }\n\n        return $widget;\n    }\n\n    /**\n     * Generate a Yes/No radio button toggle using \'true\' and \'false\' as values.\n     *\n     * @param  string $field    The field name\n     * @param  string $checked  The checked button, either \'true\', \'false\'\n     * @param  int    $tabindex The HTML tabindex\n     * @param  string $id       The HTML id\n     * @return string HTML\n     */\n\n    public static function truefalseradio(\n        $field,\n        $checked = \'\',\n        $tabindex = 0,\n        $id = \'\'\n    ) {\n        $vals = array(\n            \'false\' => gTxt(\'no\'),\n            \'true\'  => gTxt(\'yes\'),\n        );\n\n        return radioSet($vals, $field, $checked, $tabindex, $id);\n    }\n\n    /**\n     * Plugin preference widget.\n     *\n     * @param  string $name The preference name\n     * @param  string $val  The preference value\n     * @return string HTML\n     */\n\n    public static function prefFunction($name, $val)\n    {\n        $prefs = self::getIniPrefs();\n        $valid = $prefs[$name][\'valid\'];\n\n        if (is_array($valid)) {\n            $vals = array();\n\n            foreach ($valid as $value) {\n                $value === \'\' ?: $vals[$value] = gtxt($name . \'_\' . strtolower($value));\n            }\n\n            return selectInput($name, $vals, $val, $valid[0] === \'\' ? true : false);\n        } else {\n            return fInput($valid, $name, $val);\n        }\n    }\n\n    /**\n     * Generate a select list of article_image + custom fields.\n     *\n     * @param  string $name The name of the preference\n     * @param  string $val  The value of the preference\n     * @return string HTML\n     */\n\n    public static function customFields($name, $val)\n    {\n        $vals = array();\n        $vals[\'article_image\'] = gtxt(\'article_image\');\n        // $vals[\'excerpt\'] = gtxt(\'excerpt\');\n\n        $customFields = safe_rows(\n            \"name, val\",\n            \'txp_prefs\',\n            \"name LIKE \'custom_%_set\' AND val<>\'\' ORDER BY name\"\n        );\n\n        foreach ($customFields as $row) {\n            $vals[$row[\'val\']] = $row[\'val\'];\n        }\n\n        return selectInput($name, $vals, $val);\n    }\n\n    /**\n     * Add a responsive player preview near to the media related field.\n     *\n     * @param  string $evt  Textpattern event (panel)\n     * @param  string $stp  Textpattern step (action)\n     * @param  string $data Original markup\n     * @param  array  $rs   Accompanyng record set\n     * @return string       HTML\n     */\n\n    public function render($evt, $stp, $data, $rs)\n    {\n        $fieldCol = self::getMediaField();\n\n        // Find the $mediaField related ID.\n        if ($fieldCol === \'article_image\') {\n            $inputName = \'Image\';\n        } else {\n            $inputName = str_replace(\'_set\', \'\', safe_field(\'name\', \'txp_prefs\', \'val LIKE \"\' . $fieldCol . \'\"\'));\n        }\n\n        if ($rs[$inputName]) { // Add the player preview.\n            $pluginName = self::getPlugin();\n            $inputID = str_replace(\'_\', \'-\', $inputName);\n            $class = $inputID . str_replace(\'_\', \'-\', $pluginName);\n            $data .= \\Txp::get(\'\\Oui\\Player\')->renderPlayer(array(\n                    \'play\'       => $rs[$inputName],\n                    \'wraptag\'    => \'div\',\n                    \'class\'      => $class,\n                    \'responsive\' => true,\n                )) . \'</div>\' . n .\n                \'<script>$(function() { $( \".\' . $class . \'\" ).css(\"margin-top\", \"1em\").insertAfter( \"#\' . $inputID . \'\" ); });</script>\';\n        }\n\n        return $data;\n    }\n}\n\ntxpinterface === \'admin\' ? new PlayerAdmin : \'\';\n\n\n\n/*\n * This file is part of oui_player,\n * an extendable plugin to easily embed\n * customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_player\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n /**\n  * Player\n  *\n  * Manages public side plugin features.\n  *\n  * @package Oui\\Player\n  */\n\nnamespace Oui;\n\nclass Player extends PlayerBase implements \\Textpattern\\Container\\ReusableInterface\n{\n    /**\n     * Initial plugin tags and attributes.\n     *\n     * @var array\n     * @see getIniTagAtts().\n     */\n\n    private static $iniTagAtts = array(\n        \'oui_player\'    => array(\n            \'class\',\n            \'label\',\n            \'labeltag\',\n            \'provider\',\n            \'play\',\n            \'wraptag\',\n        ),\n        \'oui_if_player\' => array(\n            \'play\',\n            \'provider\',\n        ),\n    );\n\n    /**\n     * Media URL/ID.\n     *\n     * @var string|array An array can be used to provide multiple sources to HTML5 players.\n     * @see setMedia(), getMedia().\n     */\n\n    protected $media;\n\n    /**\n     * Media related provider.\n     *\n     * @var string\n     * @see getMediaProvider().\n     */\n\n    protected $mediaProvider;\n\n    /**\n     * Associative array of $media value(s) and their related infos.\n     *\n     * @var array\n     * @see setMediaInfos(), getMediaInfos().\n     */\n\n    protected $mediaInfos = array();\n\n    /**\n     * Associative array of \'media\' and \'provider\' values cached via the conditional tag for inheritence.\n     *\n     * @var array\n     * @see setCache(), getCache().\n     */\n\n    protected static $cache = array();\n\n    /**\n     * Constructor\n     * - Register all tags.\n     */\n\n    public function __construct() {\n        parent::__construct();\n\n        // Register initial tags.\n        foreach (self::getIniTagAtts() as $tag => $atts) {\n            $tagMethod = str_replace(array(\'oui\', \'_\'), array(\'render\', \'\'), $tag);\n            $tagMethods[$tag] = $tagMethod;\n\n            \\Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(array($this, $tagMethod), $tag);\n        }\n\n        // Register providers/extensions related tags.\n        foreach (self::getProviders() as $provider => $author) {\n            $lcProvider = strtolower($provider);\n\n            foreach ($tagMethods as $tag => $method) {\n                \\Txp::get(\'\\Textpattern\\Tag\\Registry\')->register($author . \'\\\\\' . $lcProvider . \'::\' . $method, str_replace(\'player\', $lcProvider, $tag));\n            }\n        }\n    }\n\n    /**\n     * $iniTagAtts getter.\n     *\n     * @param  string $tag Optional tag name.\n     * @return array\n     */\n\n    protected static function getIniTagAtts($tag = null)\n    {\n        return $tag ? static::$iniTagAtts[$tag] : static::$iniTagAtts;\n    }\n\n    /**\n     * $providers setter.\n     */\n\n    protected static function setProviders()\n    {\n        foreach (explode(\'&\', get_pref(self::getPlugin() . \'_providers\')) as $providerAuthor) {\n            list($provider, $author) = explode(\'=\', $providerAuthor);\n            static::$providers[$provider] = $author;\n        }\n    }\n\n    /**\n     * $media setter.\n     * Also call the $mediaInfos setter.\n     *\n     * @param string|array $value    The media URL/ID to play,\n     *                               an array can be used to provide multiple sources to HTML5 players;\n     * @param bool         $fallback Whether to force the $mediaInfos setter to set fallback infos or not.\n     * @return object $this\n     */\n\n    public function setMedia($value, $fallback = false)\n    {\n        $this->media = $value;\n        is_array($value) ?: $value = array($value);\n        $mediaInfos = $this->getMediaInfos();\n\n        if (!$mediaInfos || array_diff($value, array_keys($mediaInfos))) {\n            $this->setMediaInfos($fallback);\n        }\n\n        return $this;\n    }\n\n    /**\n     * $media getter.\n     *\n     * @return string|array.\n     */\n\n    public function getMedia()\n    {\n        return $this->media;\n    }\n\n    /**\n     * $provider getter (call the setter if necessary).\n     *\n     * @param  bool  Whether to force the $mediaInfos setter to set fallback infos or not.\n     * @return array|false false if $fallback is disable and the $media is not recognised as a valid provider related URL.\n     */\n\n    protected function getMediaProvider($fallback = false)\n    {\n        $this->mediaInfos or $this->setMediaInfos($fallback);\n        $media = $this->getMedia();\n        is_array($media) ?: $media = explode(\', \', $media);\n\n        if ($this->mediaProvider) {\n            return $this->mediaProvider;\n        }\n\n        return false;\n    }\n\n    /**\n     * $mediaInfos setter\n     *\n     * @param  bool $fallback Whether to set fallback media infos or not.\n     * @return array\n     */\n\n    protected function setMediaInfos($fallback = false)\n    {\n        $providers = self::getProviders();\n        $media = $this->getMedia();\n\n        foreach ($providers as $provider => $author) {\n            $this->mediaInfos = \\Txp::get($author . \'\\\\\' . $provider)\n                ->setMedia($media)\n                ->getMediaInfos();\n\n            if ($this->mediaInfos) {\n                $this->mediaProvider = $provider;\n                return $this->mediaInfos;\n            }\n        }\n\n        if ($fallback) { // No matched provider, set default media infos.\n            $this->mediaInfos = array(\n                $media => array(\n                    \'id\'  => $media,\n                    \'uri\' => $media,\n                )\n            );\n\n            $this->mediaProvider = get_pref(self::getPlugin() . \'_provider\');\n        }\n\n        return $this->mediaInfos;\n    }\n\n    /**\n     * $mediaInfos getter.\n     *\n     * @return array\n     */\n\n    public function getMediaInfos()\n    {\n        return $this->mediaInfos;\n    }\n\n    /**\n     * $cache setter.\n     *\n     * @return array\n     */\n\n    protected static function setCache($media = null, $provider = null)\n    {\n        return self::$cache = $media ? compact(\'media\', \'provider\') : array();\n    }\n\n    /**\n     * $cache getter.\n     *\n     * @return array\n     */\n\n    protected static function getCache()\n    {\n        return self::$cache;\n    }\n\n    /**\n     * Get all tag attributes — including providers related ones.\n     *\n     * @param string $tag A plugin tag.\n     * @return array Tag attributes\n     */\n\n    protected static function getTagAtts($tag)\n    {\n        $allAtts = self::getIniTagAtts($tag);\n\n        if ($tag === self::getPlugin()) {\n            // Collects provider attributes.\n            foreach (self::getProviders() as $provider => $author) {\n                $class = $author . \'\\\\\' . $provider;\n                $allAtts = array_merge($allAtts, $class::getTagAtts($tag));\n            }\n        }\n\n        return array_fill_keys($allAtts, \'\');\n    }\n\n    /**\n     * Generate a player.\n     * oui_player tag related callback method.\n     *\n     * @param  array  $atts Tag attributes\n     * @return string HTML\n     */\n\n    public function renderPlayer($atts)\n    {\n        $lAtts = lAtts(self::getTagAtts(\'oui_player\'), $atts);\n\n        $notParams = array_merge(\n            self::getIniTagAtts(),\n            array(\'width\', \'height\', \'ratio\', \'responsive\')\n        );\n\n        $params = array();\n\n        // Parse attribute values.\n        foreach ($lAtts as $lAtt => $value) {\n            switch ($value) {\n                case \'\':\n                    $lAtts[$lAtt] = null;\n                    break;\n                case \'true\':\n                    $lAtts[$lAtt] = true;\n                    break;\n                case \'false\':\n                    $lAtts[$lAtt] = false;\n                    break;\n            }\n\n            // Store player parameters related values.\n            if (!in_array($lAtt, $notParams) && $lAtts[$lAtt] !== null) {\n                $params[$lAtt] = $lAtts[$lAtt];\n            }\n        }\n\n        extract($lAtts);\n\n        $playProvider = $this->parsePlayProvider($play, $provider, true, true);\n\n        if (!$playProvider) {\n            return;\n        }\n\n        extract($playProvider);\n\n        $player = \\Txp::get(self::getProviders()[$provider] . \'\\\\\' . $provider)\n            ->setMedia($play, true)\n            ->setDims($width, $height, $ratio, $responsive)\n            ->setParams($params);\n\n        $label ? $player->setLabel($label, $labeltag) : \'\';\n        $wraptag ? $player->setWrap($wraptag, $class) : \'\';\n\n        return $player->render();\n    }\n\n    /**\n     * Generates tag contents or alternative contents.\n     * oui_if_player tag related callback method.\n     *\n     * Generated contents depends on whether the \'play\' attribute value\n     * matches a provider URL scheme.\n     *\n     * @param  array  $atts  Tag attributes\n     * @param  string $thing Tag contents\n     * @return mixed  Tag contents or alternative contents\n     */\n\n    public function renderIfPlayer($atts, $thing)\n    {\n        $lAtts = lAtts(self::getTagAtts(\'oui_if_player\'), $atts);\n\n        extract($lAtts);\n\n        $playProvider = $this->parsePlayProvider($play, $provider);\n\n        if ($playProvider) {\n            extract($playProvider);\n\n            $isValid = \\Txp::get(self::getProviders()[$provider] . \'\\\\\' . $provider)->setMedia($play)->getMediaInfos();\n\n            if ($isValid) { // Set the cache.\n                self::setCache(implode(\', \', array_keys($isValid)), $provider);\n                $out = parse($thing, true); // Set the output.\n                self::setCache(); // Reset the cache.\n            }\n        }\n\n        return isset($out) ? $out : parse($thing, false);\n    }\n\n    /**\n     * Parse play and provider attribute to get their right values.\n     *\n     * @param string $play     The play attribute value;\n     * @param string $provider The provider attribute value;\n     * @param string $cache    Whether to get potentially cached values or not;\n     * @param string $fallback Whether to get the default provider if the play value is not recognised.\n     */\n\n    protected function parsePlayProvider(\n        $play = null,\n        $provider = null,\n        $cache = false,\n        $fallback = false\n    ) {\n        global $thisarticle;\n\n        if (!$play) { // Set the potentially missing media URL/ID.\n            $cache = $cache ? self::getCache() : \'\';\n            $field = strtolower(self::getMediaField());\n\n            if ($cache) { // Play the cache related media.\n                $play = $cache[\'media\'];\n                $provider = $cache[\'provider\'];\n            } elseif (isset($thisarticle[$field])) { // Play the field related media.\n                $play = $thisarticle[$field];\n            } else {\n                trigger_error(\'play attribute or related field required\');\n            }\n        }\n\n        if ($play) {\n            // Explode the play attribute value if it is a comma separated list of values.\n            explode(\', \', $play, -1) ? $play = explode(\', \', $play) : \'\';\n\n            // Find the potentially missing provider name.\n            $provider = $provider ? ucfirst($provider) : $this->setMedia($play)->getMediaProvider($fallback);\n            $play = compact(\'play\', \'provider\');\n        }\n\n        return $play;\n    }\n}\n\ntxpinterface === \'public\' ? \\Txp::get(\'Oui\\Player\') : \'\';\n', 'f023e0e3fd5fb6c7609445f0d47046b7', '#Default language, en-us, en-gb\n#@admin\noui_player => Embedded players\noui_player_provider => Favourite provider\noui_player_custom_field => Field used by default\noui_player_microdata => Add microdata\noui_player_duplicate_provider_extensions => Duplicate provider extensions found for {provider}. Please disable or delete duplicates.\n\n#@language fr-fr\n#@admin\noui_player => Lecteurs embarqués\noui_player_provider => Service préféré\noui_player_custom_field => Champ utilisé par défaut\noui_player_responsive => Rendre les lecteurs adaptatifs\noui_player_microdata => Ajouter les microdonnées\noui_player_duplicate_provider_extensions => Extensions de fournisseur en double trouvées pour {provider}. Veuillez désactiver ou supprimer les doublons.\n', '', 5, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_imagery', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.10', 'A Textpattern CMS plugin for managing images in the Write panel.', '<h1>smd_imagery</h1>\n\n<p>Insert images into your Write panel. Very handy for people who run photoblog or image-heavy sites, or those who categorise images for inclusion in articles.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Replace category names with image id values in the Article Image field.</li>\n	<li>Comma-separate category names to grab id values from them all at once.</li>\n	<li>Order of image id values is preserved.</li>\n	<li>Craft custom image sets based on an image category, or load images from an article field.</li>\n	<li>Build a list of id values or a complete tag to paste into your article via a template.</li>\n	<li>Fast and efficient: only one query.</li>\n</ul>\n\n<h2>Installation / Uninstallation</h2>\n\n<p class=\"information\">Requires Textpattern 4.5+</p>\n\n<p class=\"information\">Recommended: Textpattern 4.6+</p>\n\n<p><a href=\"https://github.com/Bloke/smd_imagery/releases\">Download the plugin</a>, paste the code into the Textpattern <em>Admin-&gt;Plugins</em> panel, install and enable the plugin. For bug reports, please <a href=\"https://github.com/Bloke/smd_imagery/issues\">raise an issue</a>.</p>\n\n<p>To uninstall, delete the plugin from the <em>Admin-&gt;Plugins</em> panel.</p>\n\n<h2>Usage</h2>\n\n<p>Once installed, two new buttons labelled <em>Fetch</em> and <em>Manage</em> appear below the Article Image field. Both of these allow you to insert images into your article, but they do it in different ways, as detailed below.</p>\n\n<h3>Populating Article Image by category</h3>\n\n<p>Use the <em>Fetch</em> button to immediately ask the database to grab the image id values from any category names listed in the <em>Article Image</em> field. If you are using Textpattern v4.6.0 or later, just save or publish the article: the image id values will be fetched for you automatically and populated in the <em>Article Image</em> box.</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>The order of id values already present in the Article Image field is perserved after replacement.</li>\n	<li>Any categories that do not exist (or typos) will remain in the list.</li>\n	<li>Any category names that are wholly numeric will <em>not</em> be fetched, as the plugin cannot distinguish between them and id values.</li>\n	<li>The maximum number of characters &#8212; incuding commas and spaces &#8212; that can be stored in the Article Image field is 255 by default. This is why the plugin doesn&#8217;t put spaces between its id values. If you insert a category name that results in the image id list exceeding this number of characters, the article will throw an error when saving.</li>\n</ul>\n\n<h3>Examples</h3>\n\n<h4>Example 1</h4>\n\n<ul>\n	<li>Create an image category called <code>holiday_snaps</code>.</li>\n	<li>Upload some images and assign them to that category.</li>\n	<li>On the Write panel, type <code>holiday_snaps</code> into the Article Image field.</li>\n	<li>Hit <em>Fetch</em>.</li>\n	<li>Note that <code>holiday_snaps</code> has been replaced with a list of image id values that were assoaciated with that category.</li>\n</ul>\n\n<h4>Example 2</h4>\n\n<ul>\n	<li>Create a few image categories.</li>\n	<li>Upload some images to each.</li>\n	<li>On the Write panel, list the image category names in the Article Image field.</li>\n	<li>Hit <em>Fetch</em> and note the content of the Article Image field has been populated with the id values from each of the categories.</li>\n</ul>\n\n<h4>Example 3</h4>\n\n<p>Do something similar to Example 2, but before httng the <em>Fetch</em> button, sprinkle some other image id values in the Article Image field. For example:</p>\n\n<pre><code>15, 42, holiday_snaps, 6, night-out, 129</code></pre>\n\n<p>After hitting <em>Fetch</em>, note that the id values pulled from the database are replaced in-situ. Also note that if one or more of the id values you&#8217;ve typed are the same as any in the categories you&#8217;re fetching, the duplicates will not be fetched. Further, if all of the id values that comprise a category are already in the Article Image field, that category name will remain in the list.</p>\n\n<h3>Crafting image lists by hand</h3>\n\n<p>For more control over your image lists, and where you can insert the resulting values, use the <em>Manage</em> button. This will pop up a dialog box that contains a radio button to allow you to choose between two methods of fetching images:</p>\n\n<ul>\n	<li>By category: to load images by the chosen category, at the chosen size, ordered by the chosen property in the desired order depicted by the arrow.</li>\n	<li>From field: to load images from the selected article field, in the order defined in that field.</li>\n</ul>\n\n<p>In the latter case, the plugin searches for images in the following order:</p>\n\n<ol>\n	<li>A straight list of id values.</li>\n	<li><code>&lt;txp:images id=&quot;x, y, z, ...&quot;&gt;</code>.</li>\n	<li><code>&lt;txp:images name=&quot;file1.jpg, file2.png, file3.jpg, ...&quot;&gt;</code>.</li>\n	<li><code>&lt;txp:image id=&quot;x&quot; /&gt;</code> <code>&lt;txp:image id=&quot;y&quot; /&gt;</code> <code>&lt;txp:image id=&quot;z&quot; /&gt;</code> &#8230;</li>\n	<li><code>&lt;img src=&quot;http://example.org/images/x.jpg&quot; /&gt;</code> <code>&lt;img src=&quot;http://example.org/images/y.png&quot; /&gt;</code> <code>&lt;img src=&quot;http://example.org/images/z.jpg&quot; /&gt;</code> &#8230;</li>\n</ol>\n\n<p>Notes:</p>\n\n<ul>\n	<li>Selecting the empty entry at the top of the category list will fetch all uncategorized images.</li>\n	<li>If you have the smd_thumbnail plugin installed, any active profiles you have defined are also available in the &#8216;size&#8217; dropdown.</li>\n	<li>If you choose &#8216;Image&#8217; as the size, it will fetch every full-size image in the chosen category. This may take some time if the number of images is large!</li>\n</ul>\n\n<p>Once you have selected your image category/field and properties, hit the nearby <em>Fetch</em> button. All images that match the criteria of that size will be loaded in the dialog window for your consideration. You may drag and drop the images to reorder them to taste, or hit the &#8216;x&#8217; button to remove an image from the list. It <strong>will not</strong> delete the real image on disk, just remove it from the list in the dialog box.</p>\n\n<p>As you alter the images in the dialog box, the <em>Result</em> textarea box at the bottom of the dialog is updated in real-time to reflect the list of id values that represent your chosen image set. At any time you can copy that list and paste it into the Article Image field, a custom field, the body, etc in order to build your gallery.</p>\n\n<p>You may also use the <em>Template</em> box to specify a template into which the list will be inserted. The entire set will then be available in the <em>Result</em> box. This is primarily designed for creating tags such as <code>&lt;txp:images&gt;</code> from your lists. For example, you could define your tag template as:</p>\n\n<pre><code>&lt;txp:images id=&quot;{smd_imagery_list_id}&quot; form=&quot;gallery&quot; /&gt;</code></pre>\n\n<p>Whichever images you choose in the list, their id values will be inserted in place of the <code>{smd_imagery_list_id}</code>, in real-time. If you prefer image names, you may elect to define your tag something like this:</p>\n\n<pre><code>&lt;txp:images name=&quot;{smd_imagery_list_name}&quot; form=&quot;gallery&quot;\n   wraptag=&quot;div&quot; class=&quot;photos&quot; /&gt;</code></pre>\n\n<p>There are also a pair of specialised replacement tags to return quoted lists of ids or names: <code>{smd_imagery_list_id_quoted}</code> or <code>{smd_imagery_list_name_quoted}</code>.</p>\n\n<p>If you wish to insert each image individually, you might like one of these templates:</p>\n\n<pre><code>&lt;txp:image id=&quot;{smd_imagery_id}&quot; /&gt;</code></pre>\n\n<p>or</p>\n\n<pre><code>&lt;txp:image name=&quot;{smd_imagery_name}&quot; /&gt;</code></pre>\n\n<p>You can copy and paste the complete tag(s) from the <em>Result</em> box and paste it into your article somewhere, or use it as a sneaky tag builder for galleries. Your template could even include an smd_macro! If using the core tags, your <code>gallery</code> Form can, of course, be used to render anything you like using the <code>&lt;txp:image&gt;</code>, <code>&lt;txp:image_info&gt;</code> and <code>&lt;txp:image_url&gt;</code> et al tags.</p>\n\n<p>Notes:</p>\n\n<ul>\n	<li>Your chosen image size, field, template tag and sort options are automatically remembered and recalled each time you open the dialog box so you can rapidly build galleries on photo-heavy sites.</li>\n	<li>When using the <code>&lt;txp:images&gt;</code> tag to construct galleries using its <code>name</code> attribute, the resulting gallery <em>will not contain images in the order you specify</em>. Only its <code>id</code> attrbute will return them in the defined order.</li>\n	<li>The dialog box is dog ugly under Txp 4.5.x because jQuery UI is not included, nor styled to match the admin theme. Using the plugin under Txp 4.6+ offers a much cleaner experience.</li>\n</ul>', '/**\n * smd_imagery\n *\n * A Textpattern CMS plugin for managing images in the Write panel.\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (txpinterface === \'admin\') {\n    new smd_imagery();\n}\n\n// 4.5.x polyfill.\nif (!function_exists(\'send_json_response\')) {\n    function send_json_response($out = \'\')\n    {\n        static $headers_sent = false;\n\n        if (!$headers_sent) {\n            ob_clean();\n            header(\'Content-Type: application/json; charset=utf-8\');\n            txp_status_header(\'200 OK\');\n            $headers_sent = true;\n        }\n\n        if (!is_string($out)) {\n            $out = json_encode($out);\n        }\n\n        echo $out;\n    }\n}\n\n/**\n * Admin interface.\n */\nclass smd_imagery\n{\n    /**\n     * The plugin\'s event as registered in Txp.\n     *\n     * @var string\n     */\n    protected $plugin_event = \'smd_imagery\';\n\n    /**\n     * Constructor to set up callbacks and environment.\n     */\n    public function __construct()\n    {\n        add_privs($this->plugin_event, \'1,2,3,4,5,6\');\n        register_callback(array($this, \'welcome\'), \'plugin_lifecycle.\' . $this->plugin_event);\n        register_callback(array($this, \'render_ui\'), \'article_ui\', \'article_image\');\n        register_callback(array($this, \'inject_head\'), \'admin_side\', \'head_end\');\n\n        // Handler for calls from the plugin\'s UI buttons.\n        register_callback(array($this, \'dispatch\'), $this->plugin_event);\n\n        // Handlers for 4.6+ core Txp-based events.\n        // These run \'pre\' so they can inject stuff into the POSTed array\n        // prior to Txp\'s involvement.\n        // The reason it\'s not for earlier versions is that the Article Image field\n        // wasn\'t volatile, so the new id values aren\'t injected.\n        if (version_compare(txp_version, \'4.6.0\', \'>=\')) {\n            register_callback(array($this, \'replacePOST\'), \'article\', \'edit\', 1);\n            register_callback(array($this, \'replacePOST\'), \'article\', \'create\', 1);\n        }\n    }\n\n    /**\n     * Install/uninstall jumpoff point.\n     *\n     * @param  string $evt  Textpattern event (lifecycle)\n     * @param  string $stp  Textpattern step (action)\n     */\n    public function welcome($evt, $stp)\n    {\n        switch ($stp) {\n            case \'deleted\':\n                safe_delete(\'txp_lang\', \"event like \'\" . $this->plugin_event . \"%\'\");\n                break;\n        }\n\n        return;\n    }\n\n    /**\n     * Divert plugin callbacks to the correct function.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     */\n    public function dispatch($evt, $stp)\n    {\n        $available_steps = array(\n            \'fetchImages\'  => true,\n            \'renderDialog\' => true,\n            \'replaceJSON\'  => true,\n            \'saveState\'    => true,\n        );\n\n        if (!bouncer($stp, $available_steps)) {\n            return;\n        }\n\n        $this->$stp($evt, $stp);\n    }\n\n    /**\n     * Inject style rules / header material into the &lt;head&gt; of the page.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return string      Content to inject, or nothing if not the plugin\'s $event\n     */\n    public function inject_head($evt, $stp)\n    {\n        global $event;\n\n        // Also add fallback to load jQuery UI in case we\'re on < Txp 4.6.\n        if ($event === \'article\') {\n            echo \'<style>\n.ui-dialog { min-width: 50vw; }\n.content-image { padding: 0; border-radius: none; }\nimg { max-width: 100%; }\n.smd_imagery_images { display: flex; flex-wrap: wrap; }\n.smd_imagery_image { max-width: 33%; position: relative; }\n.smd_imagery_image .destroy { position: absolute; right: 0; z-index: 15; }\n.smd_imagery_btn { margin-bottom: 1rem; }\n</style>\'\n                .n.script_js(<<<EOJS\nwindow.jQuery.ui || document.write(\'<link rel=\"stylesheet\" href=\"https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css\" media=\"screen\" /><script src=\"https://code.jquery.com/ui/1.12.1/jquery-ui.min.js\" integrity=\"sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=\" crossorigin=\"anonymous\"><\\/script>\');\nEOJS\n            );\n        }\n\n        return;\n    }\n\n    /**\n     * Add buttons to the article image field.\n     *\n     * @param  string $evt  Textpattern event (panel)\n     * @param  string $stp  Textpattern step (action)\n     * @param  string $data Original markup\n     * @param  array  $rs   Accompanyng record set\n     * @return string       HTML\n     */\n    public function render_ui($evt, $stp, $data, $rs)\n    {\n        $btn = \'<button class=\"\' . $this->plugin_event . \'_fetch \'.$this->plugin_event.\'_btn\">\' . gTxt(\'smd_imagery_id_btn\') . \'</button>\'\n            .n. \'<button class=\"\' . $this->plugin_event . \'_organise \'.$this->plugin_event.\'_btn\">\' . gTxt(\'smd_imagery_organise_btn\') . \'</button>\'\n            .n. \'<div class=\"\' . $this->plugin_event . \'_dialog\" title=\"\' . gTxt(\'smd_imagery_dialog_title\') . \'\">\'\n            .n. \'</div>\';\n\n        $js = script_js(<<< EOJS\njQuery(function() {\n    /**\n     * Fetch button handler.\n     *\n     * Grabs images by category name and stuffs them directly in the article image field.\n     */\n    jQuery(\'.{$this->plugin_event}_fetch\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var dest = jQuery(\'.article-image input\');\n\n        sendAsyncEvent({\n                event        : \'{$this->plugin_event}\',\n                step         : \'replaceJSON\',\n                articleImage : dest.val(),\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                dest.val(data);\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n    });\n\n    /**\n     * Throw up a dialog to allow image order to be manipulated by hand.\n     */\n    jQuery(\'.{$this->plugin_event}_organise\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var dest = jQuery(\'.{$this->plugin_event}_dialog\');\n\n        sendAsyncEvent({\n                event : \'{$this->plugin_event}\',\n                step  : \'renderDialog\',\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                dest.empty().html(data.content);\n                dest.dialog({\n                    // Expensive, but prevents multiple dialogs appearing\n                    // after article Save.\n                    close: function(event, ui) {\n                        dest.empty().dialog(\'destroy\');\n                    }\n                });\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n\n    });\n});\nEOJS\n    );\n\n        return $data.$btn.$js;\n    }\n\n    /**\n     * Draw the bare, starting UI for the dialog.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return HTML\n     */\n    public function renderDialog($evt, $stp)\n    {\n        global $plugins;\n\n        $out = \'\';\n        $smdthumb = (is_array($plugins) and in_array(\'smd_thumbnail\', $plugins)) ? \'1\' : \'0\';\n        $thumb_sizes = array();\n        $thumb_sizes[\'txp_thumb\'] = gTxt(\'thumbnail\');\n        $thumb_sizes[\'txp_image\'] = gTxt(\'smd_imagery_image\');\n\n        if ($smdthumb == \'1\') {\n            $rs = smd_thumb_get_profiles();\n\n            if ($rs) {\n                foreach ($rs as $row) {\n                    $thumb_sizes[$row[\'name\']] = $row[\'name\'];\n                }\n            }\n        }\n\n        $thumbSelector = selectInput(\n            \'smd_imagery_size\',\n            $thumb_sizes,\n            get_pref(\'smd_imagery_size\'),\n            false,\n            \'\',\n            \'smd_imagery_size\'\n        );\n\n        $catList = event_category_popup(\'image\', \'\', $this->plugin_event . \'_list\');\n\n        $fldChoices = array(\n            \'article-image\' => gTxt(\'article_image\'),\n            \'body\'          => gTxt(\'body\'),\n            \'excerpt\'       => gTxt(\'excerpt\'),\n            );\n        $cfs = getCustomFields();\n\n        foreach ($cfs as $i => $cf_name) {\n            $fldChoices[\'custom-\'.$i] = get_pref(\"custom_{$i}_set\");\n        }\n\n        $chosenMethod = get_pref(\'smd_imagery_load_method\', \'bycat\');\n\n        $fldList = selectInput($this->plugin_event . \'_field\', $fldChoices, get_pref(\'smd_imagery_field\'), false, \'\', $this->plugin_event . \'_field\');\n\n        $loadChoices = array(\n            \'bycat\' => gTxt(\'smd_imagery_opt_cat\'),\n            \'byfld\' => gTxt(\'smd_imagery_opt_fld\'),\n            );\n\n        $loadMethod = radioSet($loadChoices, $this->plugin_event . \'_load_method\', $chosenMethod);\n\n        $actions = \'<button class=\"\' . $this->plugin_event . \'_fetch_img\">\' . gTxt(\'smd_imagery_fetch_btn\') . \'</button>\';\n\n        $sortable = $this->sortOpts();\n        $sort = get_pref(\'smd_imagery_sort_order\', key($sortable));\n        $dir = get_pref(\'smd_imagery_sort_dir\', \'asc\');\n\n        $listActions = \'<span class=\"smd_imagery_sortopts\">\'\n            . selectInput(\'smd_imagery_sort_order\', $sortable, $sort, false, \'\', \'smd_imagery_sort_order\')\n            . (($dir === \'asc\')\n                ? href(\'<span class=\"ui-icon ui-icon-arrowthick-1-n smd_imagery_sort_dir\"></span> \', \'#\')\n                : href(\'<span class=\"ui-icon ui-icon-arrowthick-1-s smd_imagery_sort_dir\"></span> \', \'#\')\n            )\n            . \'</span>\';\n\n        $panel = \'<div class=\"\' . $this->plugin_event . \'_images\">\'\n            .n. \'</div>\';\n        $plate = \'<label for=\"\' . $this->plugin_event . \'_template\">\' . gTxt(\'smd_imagery_template\') . \'</label>\'\n            .n. \'<textarea name=\"\' . $this->plugin_event . \'_template\" id=\"\' . $this->plugin_event . \'_template\" class=\"\' . $this->plugin_event . \'_template\">\'.txpspecialchars(get_pref(\'smd_imagery_template\')).\'</textarea>\';\n        $result = \'<label for=\"\' . $this->plugin_event . \'_result\">\' . gTxt(\'smd_imagery_result\') . \'</label>\'\n            .n. \'<textarea name=\"\' . $this->plugin_event . \'_result\" id=\"\' . $this->plugin_event . \'_result\" class=\"\' . $this->plugin_event . \'_result\" rows=4\"></textarea>\';\n        $js = script_js(<<< EOJS\njQuery(function() {\n\n    /**\n     * Fetch button handler.\n     *\n     * Grabs images by category name and shows them in the dialog.\n     */\n    jQuery(\'.{$this->plugin_event}_fetch_img\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var type = jQuery(\'[name={$this->plugin_event}_load_method]:checked\').val()\n        var catName = null;\n        var idList = [];\n        var nameList = [];\n\n        if (type === \'bycat\') {\n            catName = jQuery(\'#{$this->plugin_event}_list\').val();\n        } else if (type === \'byfld\') {\n            var theField = jQuery(\'#{$this->plugin_event}_field\').val();\n            var content = jQuery(\'#\' + theField).val();\n\n            // Check for an entire list of ids.\n            var rex = /^([0-9, ]+)$/g;\n            result = rex.exec(content);\n\n            if (result !== null) {\n                if (typeof(result[1]) != \'undefined\') {\n                    idList.push(result[1]);\n               }\n            }\n\n            // Check for txp:image/txp:images tags and pull out ids/names.\n            // Very simplistic. The downside to separating passes will only\n            // become apparent if people mix HTML and Txp tags, as the\n            // images will not be in source order.\n            // In reality this probably won\'t be an issue.\n            var rex = /<txp:image[s]?.*((id)\\s*=\\s*\"([0-9, ]+)\"|(name)\\s*=\\s*\"(.+?)\").*?\\/?>/g;\n\n            while ((result = rex.exec(content)) !== null) {\n                if (typeof(result) != \'undefined\') {\n                    if (typeof(result[3]) != \'undefined\') {\n                        // id matches\n                        idList.push(result[3].trim());\n                    } else if (typeof(result[5]) != \'undefined\') {\n                        // name matches\n                        nameList.push(result[5].trim());\n                    }\n                }\n            }\n\n            // Next, check for HTML img tags and extract file ids.\n            var rex = /<img.*(src)\\s*=\\s*\".*?\\/([0-9]+)\\..{3,4}\".*?\\/?>/g;\n\n            while ((result = rex.exec(content)) !== null) {\n                if (typeof(result) != \'undefined\') {\n                    if (typeof(result[2]) != \'undefined\') {\n                        // id found\n                        idList.push(result[2]);\n                    }\n                }\n            }\n        }\n\n        var size = jQuery(\'#{$this->plugin_event}_size\').val();\n        var dest = jQuery(\'.{$this->plugin_event}_images\');\n\n        sendAsyncEvent({\n                event    : \'{$this->plugin_event}\',\n                step     : \'fetchImages\',\n                type     : type,\n                category : catName,\n                idList   : idList.join(\',\'),\n                nameList : nameList.join(\',\'),\n                size     : size,\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                // Drag & drop, yeah!\n                dest.empty().html(data.content).sortable({\n                    tolerance: \"pointer\",\n                    opacity: 0.9,\n                    create: smd_imagery_result,\n                    change: smd_imagery_result,\n                    update: smd_imagery_result\n                });\n\n                jQuery(\'.{$this->plugin_event}_image .destroy\').click(function(ev) {\n                    ev.preventDefault();\n                    var target = jQuery(this).closest(\'.{$this->plugin_event}_image\');\n                    target.hide(\'fast\', function() { target.remove(); smd_imagery_result(); });\n                });\n\n                smd_imagery_result();\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n    });\n\n    /**\n     * Save pane state: loadMethod.\n     */\n    jQuery(\'[name={$this->plugin_event}_load_method]\').on(\'change\', function(ev) {\n        var meVal = jQuery(this).filter(\':checked\').val();\n        var catObj = jQuery(\'#{$this->plugin_event}_list\');\n        var fldObj = jQuery(\'#{$this->plugin_event}_field\');\n        var ordObj = jQuery(\'.{$this->plugin_event}_sortopts\');\n\n        if (meVal === \'bycat\') {\n            fldObj.hide();\n            catObj.show();\n            ordObj.show();\n        } else if (meVal === \'byfld\') {\n            fldObj.show();\n            catObj.hide();\n            ordObj.hide();\n        }\n\n        smd_imagery_stash([\'load_method\']);\n    }).change();\n\n    /**\n     * Save pane state: size, field and sort order.\n     */\n    jQuery(\'#{$this->plugin_event}_size, #{$this->plugin_event}_field, #{$this->plugin_event}_sort_order\').on(\'change\', function() {\n        var toStash = [\n            \'size\',\n            \'field\',\n            \'sort_order\'\n        ];\n\n        smd_imagery_stash(toStash);\n    });\n\n    /**\n     * Save pane state: sort dir.\n     */\n    jQuery(\'.{$this->plugin_event}_sort_dir\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        jQuery(this).toggleClass(\'ui-icon-arrowthick-1-s ui-icon-arrowthick-1-n\');\n        smd_imagery_stash([\'sort_dir\']);\n    });\n\n    /**\n     * Save pane state: template.\n     */\n    jQuery(\'.{$this->plugin_event}_template\').on(\'blur\', function() {\n        smd_imagery_stash([\'plate\']);\n        smd_imagery_result();\n    });\n});\n\n/**\n * Store pane state.\n */\nfunction smd_imagery_stash(things) {\n    sort_dir = jQuery(\'.{$this->plugin_event}_sort_dir\').hasClass(\'ui-icon-arrowthick-1-n\') ? \'asc\' : \'desc\'\n\n    var opts = {\n        event      : \'{$this->plugin_event}\',\n        step       : \'saveState\',\n        loadMethod : \'null\',\n        size       : \'null\',\n        field      : \'null\',\n        sort_order : \'null\',\n        sort_dir   : \'null\',\n        plate      : \'null\',\n    };\n\n    jQuery(things).each(function(idx, thing) {\n        switch (thing) {\n            case \'load_method\':\n                opts.loadMethod = jQuery(\'[name={$this->plugin_event}_load_method]:checked\').val();\n                break;\n            case \'size\':\n                opts.size = jQuery(\'#{$this->plugin_event}_size\').val();\n                break;\n            case \'field\':\n                opts.field = jQuery(\'#{$this->plugin_event}_field\').val();\n                break;\n            case \'sort_order\':\n                opts.sort_order = jQuery(\'#{$this->plugin_event}_sort_order\').val();\n                break;\n            case \'sort_dir\':\n                opts.sort_dir = sort_dir;\n                break;\n            case \'plate\':\n                opts.plate = jQuery(\'.{$this->plugin_event}_template\').val();\n                break;\n        }\n    });\n\n    sendAsyncEvent(opts);\n}\n\n/**\n * jQuery UI callback to update the results textarea.\n */\nfunction smd_imagery_result(event, ui) {\n    var meta = {\n        \"id\"   : [],\n        \"name\" : [],\n    };\n\n    var out = \'\';\n    var reps = {};\n\n    jQuery(\'.{$this->plugin_event}_images img\').each(function(idx) {\n        meta.id.push(jQuery(this).data(\'ref\'));\n        meta.name.push(jQuery(this).data(\'name\'));\n    });\n\n    var imgIdList = meta.id.join(\',\');\n    var imgNameList = meta.name.join(\',\');\n\n    var plate = jQuery(\'.{$this->plugin_event}_template\').val();\n    var re1 = /\\{smd_imagery_list_(.+)\\}/i;\n    var re2 = /\\{smd_imagery_(id|name)\\}/i;\n    var foundList = plate.match(re1);\n    var foundSingle = plate.match(re2);\n\n    // Default output is an id list, if no matches found.\n    out = imgIdList;\n\n    if (foundList) {\n        reps[\"{{$this->plugin_event}_list_id}\"] = imgIdList;\n        reps[\"{{$this->plugin_event}_list_name}\"] = imgNameList;\n        reps[\"{{$this->plugin_event}_list_id_quoted}\"] = \"\'\" + meta.id.join(\"\',\'\") + \"\'\";\n        reps[\"{{$this->plugin_event}_list_name_quoted}\"] = \"\'\" + meta.name.join(\"\',\'\") + \"\'\";\n        out = plate.strtr(reps);\n    } else if (foundSingle) {\n        out = \'\';\n\n        jQuery(meta.id).each(function(idx, val) {\n            reps[\"{{$this->plugin_event}_id}\"] = val;\n            reps[\"{{$this->plugin_event}_name}\"] = meta.name[idx];\n            out += plate.strtr(reps) + \'\\\\n\';\n        });\n    }\n\n    jQuery(\'.{$this->plugin_event}_result\').val(out);\n}\n\n/**\n * Equivalent to PHP\'s strtr().\n *\n * From https://gist.github.com/dsheiko/2774533.\n */\nString.prototype.strtr = function (replacePairs) {\n    \"use strict\";\n    var str = this.toString(), key, re;\n\n    for (key in replacePairs) {\n        if (replacePairs.hasOwnProperty(key)) {\n            re = new RegExp(key, \"g\");\n            str = str.replace(re, replacePairs[key]);\n        }\n    }\n\n    return str;\n}\nEOJS\n        );\n\n        $out = $loadMethod\n            .br. $catList\n            .n. $fldList\n            .n. $thumbSelector\n            .n. $listActions.$actions\n            .n. $panel\n            .n. $plate\n            .n. $result\n            .n. $js;\n\n        // Have to send the template content separately so it can be rendered\n        // after the content. Otherwise, jQuery converts self-closed tags to\n        // fully closed when rendering via the html() method.\n        send_json_response(array(\'content\' => $out));\n    }\n\n    /**\n     * Grab the images from the POSTed category.\n     *\n     * Permits selection of uncategorised images by deliberately not\n     * checking if the passed category is empty.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return HTML\n     */\n    public function fetchImages($evt, $stp)\n    {\n        global $img_dir;\n\n        $type = ps(\'type\');\n        $where = \'\';\n\n        switch ($type) {\n            case \'bycat\':\n                $sortable = $this->sortOpts();\n                $dirable = array(\'asc\', \'desc\');\n                $catName = ps(\'category\');\n                $sort = get_pref(\'smd_imagery_sort_order\');\n                $dir = get_pref(\'smd_imagery_sort_dir\');\n\n                if (!array_key_exists($sort, $sortable)) {\n                    $sort = key($sortable);\n                }\n\n                if (!in_array($dir, $dirable)) {\n                    $dir = $dirable[0];\n                }\n\n                $orderBy = \' ORDER BY \' . doSlash($sort) . \' \' . doSlash($dir);\n                $where = \"category = \'\" . doSlash($catName) . \"\'\" . $orderBy;\n                break;\n            case \'byfld\':\n                $idList = ps(\'idList\');\n                $nameList = ps(\'nameList\');\n\n                if ($idList) {\n                    $ids = doSlash($idList);\n                    $where = \"id IN(\" . $ids . \") ORDER BY field(id, \" . $ids . \")\";\n                }\n\n                if ($nameList) {\n                    $names = implode(\', \', quote_list(do_list($nameList)));\n                    $where = \"name IN(\" . $names . \") ORDER BY field(name, \" . $names . \")\";\n                }\n\n                break;\n        }\n\n\n        $size = ps(\'size\');\n        $img = array();\n\n        $rs = safe_rows(\'*\', \'txp_image\', $where);\n\n        foreach ($rs as $row) {\n            switch ($size) {\n                case \'txp_thumb\':\n                    // Thumbnail.\n                    $url = imagesrcurl($row[\'id\'], $row[\'ext\'], true);\n                    break;\n                case \'txp_image\':\n                    // Full-size image.\n                    $url = imagesrcurl($row[\'id\'], $row[\'ext\']);\n                    break;\n                default:\n                    // smd_thumbnail profile.\n                    $url = ihu . $img_dir . \'/\' . $size . \'/\' . $row[\'id\'] . $row[\'ext\'];\n                    break;\n            }\n\n            $aspect = ($row[\'h\'] == $row[\'w\']) ? \' square\' : (($row[\'h\'] > $row[\'w\']) ? \' portrait\' : \' landscape\');\n            $img_info = $row[\'id\'].$row[\'ext\'].\' (\'.$row[\'w\'].\' &#215; \'.$row[\'h\'].\')\';\n            $img[] = \'<div class=\"\'.$this->plugin_event.\'_image\' . $aspect . \'\">\'\n                .n. \'<button\n                    class=\"destroy\"\n                    title=\"\'.gTxt(\'delete\').\'\"\n                    aria-label=\"\' . gTxt(\'delete\') . \'\"><span class=\"ui-icon ui-icon-close\">\' . gTxt(\'delete\') . \'</button>\'\n                .n. \'<img class=\"content-image\"\n                    src=\"\' . $url . \'\"\n                    alt=\"\' . $img_info . \'\"\n                    title=\"\' . $img_info . \'\"\n                    data-ref=\"\' . $row[\'id\'] . \'\"\n                    data-name=\"\' . $row[\'name\'] . \'\"\n                    />\'\n                .n. \'</div>\';\n        }\n\n        $out = implode(n, $img);\n\n        send_json_response(array(\'content\' => $out));\n    }\n\n    /**\n     * Store the state of the UI for layter recall.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     */\n    public function saveState($evt, $stp)\n    {\n        $loadMethod = ps(\'loadMethod\');\n        $size = ps(\'size\');\n        $field = ps(\'field\');\n        $sort = ps(\'sort_order\');\n        $dir = ps(\'sort_dir\');\n        $plate = ps(\'plate\');\n\n        if ($loadMethod !== \"null\") {\n            set_pref(\'smd_imagery_load_method\', $loadMethod, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($size !== \"null\") {\n            set_pref(\'smd_imagery_size\', $size, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($field !== \"null\") {\n            set_pref(\'smd_imagery_field\', $field, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($sort !== \"null\") {\n            set_pref(\'smd_imagery_sort_order\', $sort, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($dir !== \"null\") {\n            set_pref(\'smd_imagery_sort_dir\', $dir, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($plate !== \"null\") {\n            set_pref(\'smd_imagery_template\', $plate, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        send_json_response(array(\'lm\' => $loadMethod, \'sz\' => $size, \'fl\' => $field, \'so\' => $sort, \'dr\' => $dir, \'pl\' => $plate));\n    }\n\n    /**\n     * Look up any categories and return all id values via JSON.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return JSON        List of IDs\n     */\n    public function replaceJSON($evt, $stp)\n    {\n        $data = ps(\'articleImage\');\n        $idList = $this->replace($data);\n        send_json_response(json_encode($idList));\n    }\n\n    /**\n     * Look up any categories and return all id values during save.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return string      List of IDs\n     */\n    public function replacePOST($evt, $stp)\n    {\n        $data = ps(\'Image\');\n        $idList = $this->replace($data);\n        $_POST[\'Image\'] = $idList;\n    }\n\n    /**\n     * Replace any categories in the passed string with id values.\n     *\n     * Any duplicate values are only included once.\n     *\n     * @param  string $data The list of IDs / cat names\n     * @return string       List of IDs\n     */\n    protected function replace($data)\n    {\n        if ($data) {\n            $reps = array();\n\n            // Create an array of IDs/cat names.\n            $items = do_list($data);\n\n            // Get just the category names (if any).\n            $cats = array_filter($items, array($this, \'filterCats\'));\n\n            if ($cats) {\n                // Could use group_concat, but it\'s MySQL specific. Think of the future.\n                $rs = safe_rows(\'id, category\', \'txp_image\', \'category IN(\' . implode(\',\', quote_list($cats)) . \')\');\n\n                // Extract IDs into a category-indexed array, as long as they\n                // haven\'t been seen before.\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $items)) {\n                        $reps[$row[\'category\']][] = $row[\'id\'];\n                    }\n                }\n\n                // Replace any element in the original $items array that\n                // matches a category name pulled from the DB, with the\n                // concatenated list of id values it represents.\n                if ($reps) {\n                    foreach ($reps as $cat => $ids) {\n                        $pos = array_search($cat, $items);\n\n                        if ($pos !== false) {\n                            $items[$pos] = implode(\',\', $ids);\n                        }\n                    }\n                }\n\n                // Squish al items into a single comma-separated list.\n                $data = implode(\',\', $items);\n            }\n        }\n\n        return $data;\n    }\n\n    /**\n     * array_filter callback to remove purely numeric id values.\n     *\n     * @param  string $v Value to compare, from array_filter() function\n     * @return bool\n     */\n    protected function filterCats($v)\n    {\n        return !is_numeric($v);\n    }\n\n    /**\n     * Get a list of valid image fields to sort by.\n     *\n     * @return array\n     */\n    protected function sortOpts()\n    {\n        return array(\n            \'name\'   => gTxt(\'name\'),\n            \'id\'     => gTxt(\'ID\'),\n            \'date\'   => gTxt(\'date\'),\n            \'author\' => gTxt(\'author\'),\n            \'ext\'    => gTxt(\'extension\'),\n            \'w\'      => gTxt(\'width\'),\n            \'h\'      => gTxt(\'height\'),\n        );\n    }\n}', '/**\n * smd_imagery\n *\n * A Textpattern CMS plugin for managing images in the Write panel.\n *\n * @author Stef Dawson\n * @link   http://stefdawson.com/\n */\nif (txpinterface === \'admin\') {\n    new smd_imagery();\n}\n\n// 4.5.x polyfill.\nif (!function_exists(\'send_json_response\')) {\n    function send_json_response($out = \'\')\n    {\n        static $headers_sent = false;\n\n        if (!$headers_sent) {\n            ob_clean();\n            header(\'Content-Type: application/json; charset=utf-8\');\n            txp_status_header(\'200 OK\');\n            $headers_sent = true;\n        }\n\n        if (!is_string($out)) {\n            $out = json_encode($out);\n        }\n\n        echo $out;\n    }\n}\n\n/**\n * Admin interface.\n */\nclass smd_imagery\n{\n    /**\n     * The plugin\'s event as registered in Txp.\n     *\n     * @var string\n     */\n    protected $plugin_event = \'smd_imagery\';\n\n    /**\n     * Constructor to set up callbacks and environment.\n     */\n    public function __construct()\n    {\n        add_privs($this->plugin_event, \'1,2,3,4,5,6\');\n        register_callback(array($this, \'welcome\'), \'plugin_lifecycle.\' . $this->plugin_event);\n        register_callback(array($this, \'render_ui\'), \'article_ui\', \'article_image\');\n        register_callback(array($this, \'inject_head\'), \'admin_side\', \'head_end\');\n\n        // Handler for calls from the plugin\'s UI buttons.\n        register_callback(array($this, \'dispatch\'), $this->plugin_event);\n\n        // Handlers for 4.6+ core Txp-based events.\n        // These run \'pre\' so they can inject stuff into the POSTed array\n        // prior to Txp\'s involvement.\n        // The reason it\'s not for earlier versions is that the Article Image field\n        // wasn\'t volatile, so the new id values aren\'t injected.\n        if (version_compare(txp_version, \'4.6.0\', \'>=\')) {\n            register_callback(array($this, \'replacePOST\'), \'article\', \'edit\', 1);\n            register_callback(array($this, \'replacePOST\'), \'article\', \'create\', 1);\n        }\n    }\n\n    /**\n     * Install/uninstall jumpoff point.\n     *\n     * @param  string $evt  Textpattern event (lifecycle)\n     * @param  string $stp  Textpattern step (action)\n     */\n    public function welcome($evt, $stp)\n    {\n        switch ($stp) {\n            case \'deleted\':\n                safe_delete(\'txp_lang\', \"event like \'\" . $this->plugin_event . \"%\'\");\n                break;\n        }\n\n        return;\n    }\n\n    /**\n     * Divert plugin callbacks to the correct function.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     */\n    public function dispatch($evt, $stp)\n    {\n        $available_steps = array(\n            \'fetchImages\'  => true,\n            \'renderDialog\' => true,\n            \'replaceJSON\'  => true,\n            \'saveState\'    => true,\n        );\n\n        if (!bouncer($stp, $available_steps)) {\n            return;\n        }\n\n        $this->$stp($evt, $stp);\n    }\n\n    /**\n     * Inject style rules / header material into the &lt;head&gt; of the page.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return string      Content to inject, or nothing if not the plugin\'s $event\n     */\n    public function inject_head($evt, $stp)\n    {\n        global $event;\n\n        // Also add fallback to load jQuery UI in case we\'re on < Txp 4.6.\n        if ($event === \'article\') {\n            echo \'<style>\n.ui-dialog { min-width: 50vw; }\n.content-image { padding: 0; border-radius: none; }\nimg { max-width: 100%; }\n.smd_imagery_images { display: flex; flex-wrap: wrap; }\n.smd_imagery_image { max-width: 33%; position: relative; }\n.smd_imagery_image .destroy { position: absolute; right: 0; z-index: 15; }\n.smd_imagery_btn { margin-bottom: 1rem; }\n</style>\'\n                .n.script_js(<<<EOJS\nwindow.jQuery.ui || document.write(\'<link rel=\"stylesheet\" href=\"https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css\" media=\"screen\" /><script src=\"https://code.jquery.com/ui/1.12.1/jquery-ui.min.js\" integrity=\"sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=\" crossorigin=\"anonymous\"><\\/script>\');\nEOJS\n            );\n        }\n\n        return;\n    }\n\n    /**\n     * Add buttons to the article image field.\n     *\n     * @param  string $evt  Textpattern event (panel)\n     * @param  string $stp  Textpattern step (action)\n     * @param  string $data Original markup\n     * @param  array  $rs   Accompanyng record set\n     * @return string       HTML\n     */\n    public function render_ui($evt, $stp, $data, $rs)\n    {\n        $btn = \'<button class=\"\' . $this->plugin_event . \'_fetch \'.$this->plugin_event.\'_btn\">\' . gTxt(\'smd_imagery_id_btn\') . \'</button>\'\n            .n. \'<button class=\"\' . $this->plugin_event . \'_organise \'.$this->plugin_event.\'_btn\">\' . gTxt(\'smd_imagery_organise_btn\') . \'</button>\'\n            .n. \'<div class=\"\' . $this->plugin_event . \'_dialog\" title=\"\' . gTxt(\'smd_imagery_dialog_title\') . \'\">\'\n            .n. \'</div>\';\n\n        $js = script_js(<<< EOJS\njQuery(function() {\n    /**\n     * Fetch button handler.\n     *\n     * Grabs images by category name and stuffs them directly in the article image field.\n     */\n    jQuery(\'.{$this->plugin_event}_fetch\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var dest = jQuery(\'.article-image input\');\n\n        sendAsyncEvent({\n                event        : \'{$this->plugin_event}\',\n                step         : \'replaceJSON\',\n                articleImage : dest.val(),\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                dest.val(data);\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n    });\n\n    /**\n     * Throw up a dialog to allow image order to be manipulated by hand.\n     */\n    jQuery(\'.{$this->plugin_event}_organise\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var dest = jQuery(\'.{$this->plugin_event}_dialog\');\n\n        sendAsyncEvent({\n                event : \'{$this->plugin_event}\',\n                step  : \'renderDialog\',\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                dest.empty().html(data.content);\n                dest.dialog({\n                    // Expensive, but prevents multiple dialogs appearing\n                    // after article Save.\n                    close: function(event, ui) {\n                        dest.empty().dialog(\'destroy\');\n                    }\n                });\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n\n    });\n});\nEOJS\n    );\n\n        return $data.$btn.$js;\n    }\n\n    /**\n     * Draw the bare, starting UI for the dialog.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return HTML\n     */\n    public function renderDialog($evt, $stp)\n    {\n        global $plugins;\n\n        $out = \'\';\n        $smdthumb = (is_array($plugins) and in_array(\'smd_thumbnail\', $plugins)) ? \'1\' : \'0\';\n        $thumb_sizes = array();\n        $thumb_sizes[\'txp_thumb\'] = gTxt(\'thumbnail\');\n        $thumb_sizes[\'txp_image\'] = gTxt(\'smd_imagery_image\');\n\n        if ($smdthumb == \'1\') {\n            $rs = smd_thumb_get_profiles();\n\n            if ($rs) {\n                foreach ($rs as $row) {\n                    $thumb_sizes[$row[\'name\']] = $row[\'name\'];\n                }\n            }\n        }\n\n        $thumbSelector = selectInput(\n            \'smd_imagery_size\',\n            $thumb_sizes,\n            get_pref(\'smd_imagery_size\'),\n            false,\n            \'\',\n            \'smd_imagery_size\'\n        );\n\n        $catList = event_category_popup(\'image\', \'\', $this->plugin_event . \'_list\');\n\n        $fldChoices = array(\n            \'article-image\' => gTxt(\'article_image\'),\n            \'body\'          => gTxt(\'body\'),\n            \'excerpt\'       => gTxt(\'excerpt\'),\n            );\n        $cfs = getCustomFields();\n\n        foreach ($cfs as $i => $cf_name) {\n            $fldChoices[\'custom-\'.$i] = get_pref(\"custom_{$i}_set\");\n        }\n\n        $chosenMethod = get_pref(\'smd_imagery_load_method\', \'bycat\');\n\n        $fldList = selectInput($this->plugin_event . \'_field\', $fldChoices, get_pref(\'smd_imagery_field\'), false, \'\', $this->plugin_event . \'_field\');\n\n        $loadChoices = array(\n            \'bycat\' => gTxt(\'smd_imagery_opt_cat\'),\n            \'byfld\' => gTxt(\'smd_imagery_opt_fld\'),\n            );\n\n        $loadMethod = radioSet($loadChoices, $this->plugin_event . \'_load_method\', $chosenMethod);\n\n        $actions = \'<button class=\"\' . $this->plugin_event . \'_fetch_img\">\' . gTxt(\'smd_imagery_fetch_btn\') . \'</button>\';\n\n        $sortable = $this->sortOpts();\n        $sort = get_pref(\'smd_imagery_sort_order\', key($sortable));\n        $dir = get_pref(\'smd_imagery_sort_dir\', \'asc\');\n\n        $listActions = \'<span class=\"smd_imagery_sortopts\">\'\n            . selectInput(\'smd_imagery_sort_order\', $sortable, $sort, false, \'\', \'smd_imagery_sort_order\')\n            . (($dir === \'asc\')\n                ? href(\'<span class=\"ui-icon ui-icon-arrowthick-1-n smd_imagery_sort_dir\"></span> \', \'#\')\n                : href(\'<span class=\"ui-icon ui-icon-arrowthick-1-s smd_imagery_sort_dir\"></span> \', \'#\')\n            )\n            . \'</span>\';\n\n        $panel = \'<div class=\"\' . $this->plugin_event . \'_images\">\'\n            .n. \'</div>\';\n        $plate = \'<label for=\"\' . $this->plugin_event . \'_template\">\' . gTxt(\'smd_imagery_template\') . \'</label>\'\n            .n. \'<textarea name=\"\' . $this->plugin_event . \'_template\" id=\"\' . $this->plugin_event . \'_template\" class=\"\' . $this->plugin_event . \'_template\">\'.txpspecialchars(get_pref(\'smd_imagery_template\')).\'</textarea>\';\n        $result = \'<label for=\"\' . $this->plugin_event . \'_result\">\' . gTxt(\'smd_imagery_result\') . \'</label>\'\n            .n. \'<textarea name=\"\' . $this->plugin_event . \'_result\" id=\"\' . $this->plugin_event . \'_result\" class=\"\' . $this->plugin_event . \'_result\" rows=4\"></textarea>\';\n        $js = script_js(<<< EOJS\njQuery(function() {\n\n    /**\n     * Fetch button handler.\n     *\n     * Grabs images by category name and shows them in the dialog.\n     */\n    jQuery(\'.{$this->plugin_event}_fetch_img\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        var me = jQuery(this);\n        var body = jQuery(\'body\');\n        var spinner = jQuery(\'<span />\').addClass(\'spinner\');\n\n        // Show feedback while processing.\n        me.addClass(\'busy\').attr(\'disabled\', true).after(spinner);\n        body.addClass(\'busy\');\n\n        var type = jQuery(\'[name={$this->plugin_event}_load_method]:checked\').val()\n        var catName = null;\n        var idList = [];\n        var nameList = [];\n\n        if (type === \'bycat\') {\n            catName = jQuery(\'#{$this->plugin_event}_list\').val();\n        } else if (type === \'byfld\') {\n            var theField = jQuery(\'#{$this->plugin_event}_field\').val();\n            var content = jQuery(\'#\' + theField).val();\n\n            // Check for an entire list of ids.\n            var rex = /^([0-9, ]+)$/g;\n            result = rex.exec(content);\n\n            if (result !== null) {\n                if (typeof(result[1]) != \'undefined\') {\n                    idList.push(result[1]);\n               }\n            }\n\n            // Check for txp:image/txp:images tags and pull out ids/names.\n            // Very simplistic. The downside to separating passes will only\n            // become apparent if people mix HTML and Txp tags, as the\n            // images will not be in source order.\n            // In reality this probably won\'t be an issue.\n            var rex = /<txp:image[s]?.*((id)\\s*=\\s*\"([0-9, ]+)\"|(name)\\s*=\\s*\"(.+?)\").*?\\/?>/g;\n\n            while ((result = rex.exec(content)) !== null) {\n                if (typeof(result) != \'undefined\') {\n                    if (typeof(result[3]) != \'undefined\') {\n                        // id matches\n                        idList.push(result[3].trim());\n                    } else if (typeof(result[5]) != \'undefined\') {\n                        // name matches\n                        nameList.push(result[5].trim());\n                    }\n                }\n            }\n\n            // Next, check for HTML img tags and extract file ids.\n            var rex = /<img.*(src)\\s*=\\s*\".*?\\/([0-9]+)\\..{3,4}\".*?\\/?>/g;\n\n            while ((result = rex.exec(content)) !== null) {\n                if (typeof(result) != \'undefined\') {\n                    if (typeof(result[2]) != \'undefined\') {\n                        // id found\n                        idList.push(result[2]);\n                    }\n                }\n            }\n        }\n\n        var size = jQuery(\'#{$this->plugin_event}_size\').val();\n        var dest = jQuery(\'.{$this->plugin_event}_images\');\n\n        sendAsyncEvent({\n                event    : \'{$this->plugin_event}\',\n                step     : \'fetchImages\',\n                type     : type,\n                category : catName,\n                idList   : idList.join(\',\'),\n                nameList : nameList.join(\',\'),\n                size     : size,\n            }, function() {}, \'json\')\n            .done(function (data, textStatus, jqXHR) {\n                // Drag & drop, yeah!\n                dest.empty().html(data.content).sortable({\n                    tolerance: \"pointer\",\n                    opacity: 0.9,\n                    create: smd_imagery_result,\n                    change: smd_imagery_result,\n                    update: smd_imagery_result\n                });\n\n                jQuery(\'.{$this->plugin_event}_image .destroy\').click(function(ev) {\n                    ev.preventDefault();\n                    var target = jQuery(this).closest(\'.{$this->plugin_event}_image\');\n                    target.hide(\'fast\', function() { target.remove(); smd_imagery_result(); });\n                });\n\n                smd_imagery_result();\n            })\n            .always(function () {\n                me.removeClass(\'busy\').removeAttr(\'disabled\');\n                spinner.remove();\n                body.removeClass(\'busy\');\n            });\n    });\n\n    /**\n     * Save pane state: loadMethod.\n     */\n    jQuery(\'[name={$this->plugin_event}_load_method]\').on(\'change\', function(ev) {\n        var meVal = jQuery(this).filter(\':checked\').val();\n        var catObj = jQuery(\'#{$this->plugin_event}_list\');\n        var fldObj = jQuery(\'#{$this->plugin_event}_field\');\n        var ordObj = jQuery(\'.{$this->plugin_event}_sortopts\');\n\n        if (meVal === \'bycat\') {\n            fldObj.hide();\n            catObj.show();\n            ordObj.show();\n        } else if (meVal === \'byfld\') {\n            fldObj.show();\n            catObj.hide();\n            ordObj.hide();\n        }\n\n        smd_imagery_stash([\'load_method\']);\n    }).change();\n\n    /**\n     * Save pane state: size, field and sort order.\n     */\n    jQuery(\'#{$this->plugin_event}_size, #{$this->plugin_event}_field, #{$this->plugin_event}_sort_order\').on(\'change\', function() {\n        var toStash = [\n            \'size\',\n            \'field\',\n            \'sort_order\'\n        ];\n\n        smd_imagery_stash(toStash);\n    });\n\n    /**\n     * Save pane state: sort dir.\n     */\n    jQuery(\'.{$this->plugin_event}_sort_dir\').on(\'click\', function(ev) {\n        ev.preventDefault();\n        jQuery(this).toggleClass(\'ui-icon-arrowthick-1-s ui-icon-arrowthick-1-n\');\n        smd_imagery_stash([\'sort_dir\']);\n    });\n\n    /**\n     * Save pane state: template.\n     */\n    jQuery(\'.{$this->plugin_event}_template\').on(\'blur\', function() {\n        smd_imagery_stash([\'plate\']);\n        smd_imagery_result();\n    });\n});\n\n/**\n * Store pane state.\n */\nfunction smd_imagery_stash(things) {\n    sort_dir = jQuery(\'.{$this->plugin_event}_sort_dir\').hasClass(\'ui-icon-arrowthick-1-n\') ? \'asc\' : \'desc\'\n\n    var opts = {\n        event      : \'{$this->plugin_event}\',\n        step       : \'saveState\',\n        loadMethod : \'null\',\n        size       : \'null\',\n        field      : \'null\',\n        sort_order : \'null\',\n        sort_dir   : \'null\',\n        plate      : \'null\',\n    };\n\n    jQuery(things).each(function(idx, thing) {\n        switch (thing) {\n            case \'load_method\':\n                opts.loadMethod = jQuery(\'[name={$this->plugin_event}_load_method]:checked\').val();\n                break;\n            case \'size\':\n                opts.size = jQuery(\'#{$this->plugin_event}_size\').val();\n                break;\n            case \'field\':\n                opts.field = jQuery(\'#{$this->plugin_event}_field\').val();\n                break;\n            case \'sort_order\':\n                opts.sort_order = jQuery(\'#{$this->plugin_event}_sort_order\').val();\n                break;\n            case \'sort_dir\':\n                opts.sort_dir = sort_dir;\n                break;\n            case \'plate\':\n                opts.plate = jQuery(\'.{$this->plugin_event}_template\').val();\n                break;\n        }\n    });\n\n    sendAsyncEvent(opts);\n}\n\n/**\n * jQuery UI callback to update the results textarea.\n */\nfunction smd_imagery_result(event, ui) {\n    var meta = {\n        \"id\"   : [],\n        \"name\" : [],\n    };\n\n    var out = \'\';\n    var reps = {};\n\n    jQuery(\'.{$this->plugin_event}_images img\').each(function(idx) {\n        meta.id.push(jQuery(this).data(\'ref\'));\n        meta.name.push(jQuery(this).data(\'name\'));\n    });\n\n    var imgIdList = meta.id.join(\',\');\n    var imgNameList = meta.name.join(\',\');\n\n    var plate = jQuery(\'.{$this->plugin_event}_template\').val();\n    var re1 = /\\{smd_imagery_list_(.+)\\}/i;\n    var re2 = /\\{smd_imagery_(id|name)\\}/i;\n    var foundList = plate.match(re1);\n    var foundSingle = plate.match(re2);\n\n    // Default output is an id list, if no matches found.\n    out = imgIdList;\n\n    if (foundList) {\n        reps[\"{{$this->plugin_event}_list_id}\"] = imgIdList;\n        reps[\"{{$this->plugin_event}_list_name}\"] = imgNameList;\n        reps[\"{{$this->plugin_event}_list_id_quoted}\"] = \"\'\" + meta.id.join(\"\',\'\") + \"\'\";\n        reps[\"{{$this->plugin_event}_list_name_quoted}\"] = \"\'\" + meta.name.join(\"\',\'\") + \"\'\";\n        out = plate.strtr(reps);\n    } else if (foundSingle) {\n        out = \'\';\n\n        jQuery(meta.id).each(function(idx, val) {\n            reps[\"{{$this->plugin_event}_id}\"] = val;\n            reps[\"{{$this->plugin_event}_name}\"] = meta.name[idx];\n            out += plate.strtr(reps) + \'\\\\n\';\n        });\n    }\n\n    jQuery(\'.{$this->plugin_event}_result\').val(out);\n}\n\n/**\n * Equivalent to PHP\'s strtr().\n *\n * From https://gist.github.com/dsheiko/2774533.\n */\nString.prototype.strtr = function (replacePairs) {\n    \"use strict\";\n    var str = this.toString(), key, re;\n\n    for (key in replacePairs) {\n        if (replacePairs.hasOwnProperty(key)) {\n            re = new RegExp(key, \"g\");\n            str = str.replace(re, replacePairs[key]);\n        }\n    }\n\n    return str;\n}\nEOJS\n        );\n\n        $out = $loadMethod\n            .br. $catList\n            .n. $fldList\n            .n. $thumbSelector\n            .n. $listActions.$actions\n            .n. $panel\n            .n. $plate\n            .n. $result\n            .n. $js;\n\n        // Have to send the template content separately so it can be rendered\n        // after the content. Otherwise, jQuery converts self-closed tags to\n        // fully closed when rendering via the html() method.\n        send_json_response(array(\'content\' => $out));\n    }\n\n    /**\n     * Grab the images from the POSTed category.\n     *\n     * Permits selection of uncategorised images by deliberately not\n     * checking if the passed category is empty.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return HTML\n     */\n    public function fetchImages($evt, $stp)\n    {\n        global $img_dir;\n\n        $type = ps(\'type\');\n        $where = \'\';\n\n        switch ($type) {\n            case \'bycat\':\n                $sortable = $this->sortOpts();\n                $dirable = array(\'asc\', \'desc\');\n                $catName = ps(\'category\');\n                $sort = get_pref(\'smd_imagery_sort_order\');\n                $dir = get_pref(\'smd_imagery_sort_dir\');\n\n                if (!array_key_exists($sort, $sortable)) {\n                    $sort = key($sortable);\n                }\n\n                if (!in_array($dir, $dirable)) {\n                    $dir = $dirable[0];\n                }\n\n                $orderBy = \' ORDER BY \' . doSlash($sort) . \' \' . doSlash($dir);\n                $where = \"category = \'\" . doSlash($catName) . \"\'\" . $orderBy;\n                break;\n            case \'byfld\':\n                $idList = ps(\'idList\');\n                $nameList = ps(\'nameList\');\n\n                if ($idList) {\n                    $ids = doSlash($idList);\n                    $where = \"id IN(\" . $ids . \") ORDER BY field(id, \" . $ids . \")\";\n                }\n\n                if ($nameList) {\n                    $names = implode(\', \', quote_list(do_list($nameList)));\n                    $where = \"name IN(\" . $names . \") ORDER BY field(name, \" . $names . \")\";\n                }\n\n                break;\n        }\n\n\n        $size = ps(\'size\');\n        $img = array();\n\n        $rs = safe_rows(\'*\', \'txp_image\', $where);\n\n        foreach ($rs as $row) {\n            switch ($size) {\n                case \'txp_thumb\':\n                    // Thumbnail.\n                    $url = imagesrcurl($row[\'id\'], $row[\'ext\'], true);\n                    break;\n                case \'txp_image\':\n                    // Full-size image.\n                    $url = imagesrcurl($row[\'id\'], $row[\'ext\']);\n                    break;\n                default:\n                    // smd_thumbnail profile.\n                    $url = ihu . $img_dir . \'/\' . $size . \'/\' . $row[\'id\'] . $row[\'ext\'];\n                    break;\n            }\n\n            $aspect = ($row[\'h\'] == $row[\'w\']) ? \' square\' : (($row[\'h\'] > $row[\'w\']) ? \' portrait\' : \' landscape\');\n            $img_info = $row[\'id\'].$row[\'ext\'].\' (\'.$row[\'w\'].\' &#215; \'.$row[\'h\'].\')\';\n            $img[] = \'<div class=\"\'.$this->plugin_event.\'_image\' . $aspect . \'\">\'\n                .n. \'<button\n                    class=\"destroy\"\n                    title=\"\'.gTxt(\'delete\').\'\"\n                    aria-label=\"\' . gTxt(\'delete\') . \'\"><span class=\"ui-icon ui-icon-close\">\' . gTxt(\'delete\') . \'</button>\'\n                .n. \'<img class=\"content-image\"\n                    src=\"\' . $url . \'\"\n                    alt=\"\' . $img_info . \'\"\n                    title=\"\' . $img_info . \'\"\n                    data-ref=\"\' . $row[\'id\'] . \'\"\n                    data-name=\"\' . $row[\'name\'] . \'\"\n                    />\'\n                .n. \'</div>\';\n        }\n\n        $out = implode(n, $img);\n\n        send_json_response(array(\'content\' => $out));\n    }\n\n    /**\n     * Store the state of the UI for layter recall.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     */\n    public function saveState($evt, $stp)\n    {\n        $loadMethod = ps(\'loadMethod\');\n        $size = ps(\'size\');\n        $field = ps(\'field\');\n        $sort = ps(\'sort_order\');\n        $dir = ps(\'sort_dir\');\n        $plate = ps(\'plate\');\n\n        if ($loadMethod !== \"null\") {\n            set_pref(\'smd_imagery_load_method\', $loadMethod, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($size !== \"null\") {\n            set_pref(\'smd_imagery_size\', $size, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($field !== \"null\") {\n            set_pref(\'smd_imagery_field\', $field, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($sort !== \"null\") {\n            set_pref(\'smd_imagery_sort_order\', $sort, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($dir !== \"null\") {\n            set_pref(\'smd_imagery_sort_dir\', $dir, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        if ($plate !== \"null\") {\n            set_pref(\'smd_imagery_template\', $plate, $this->plugin_event, 2, null, 0, PREF_PRIVATE);\n        }\n\n        send_json_response(array(\'lm\' => $loadMethod, \'sz\' => $size, \'fl\' => $field, \'so\' => $sort, \'dr\' => $dir, \'pl\' => $plate));\n    }\n\n    /**\n     * Look up any categories and return all id values via JSON.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return JSON        List of IDs\n     */\n    public function replaceJSON($evt, $stp)\n    {\n        $data = ps(\'articleImage\');\n        $idList = $this->replace($data);\n        send_json_response(json_encode($idList));\n    }\n\n    /**\n     * Look up any categories and return all id values during save.\n     *\n     * @param  string $evt Textpattern event (panel)\n     * @param  string $stp Textpattern step (action)\n     * @return string      List of IDs\n     */\n    public function replacePOST($evt, $stp)\n    {\n        $data = ps(\'Image\');\n        $idList = $this->replace($data);\n        $_POST[\'Image\'] = $idList;\n    }\n\n    /**\n     * Replace any categories in the passed string with id values.\n     *\n     * Any duplicate values are only included once.\n     *\n     * @param  string $data The list of IDs / cat names\n     * @return string       List of IDs\n     */\n    protected function replace($data)\n    {\n        if ($data) {\n            $reps = array();\n\n            // Create an array of IDs/cat names.\n            $items = do_list($data);\n\n            // Get just the category names (if any).\n            $cats = array_filter($items, array($this, \'filterCats\'));\n\n            if ($cats) {\n                // Could use group_concat, but it\'s MySQL specific. Think of the future.\n                $rs = safe_rows(\'id, category\', \'txp_image\', \'category IN(\' . implode(\',\', quote_list($cats)) . \')\');\n\n                // Extract IDs into a category-indexed array, as long as they\n                // haven\'t been seen before.\n                foreach ($rs as $row) {\n                    if (!in_array($row[\'id\'], $items)) {\n                        $reps[$row[\'category\']][] = $row[\'id\'];\n                    }\n                }\n\n                // Replace any element in the original $items array that\n                // matches a category name pulled from the DB, with the\n                // concatenated list of id values it represents.\n                if ($reps) {\n                    foreach ($reps as $cat => $ids) {\n                        $pos = array_search($cat, $items);\n\n                        if ($pos !== false) {\n                            $items[$pos] = implode(\',\', $ids);\n                        }\n                    }\n                }\n\n                // Squish al items into a single comma-separated list.\n                $data = implode(\',\', $items);\n            }\n        }\n\n        return $data;\n    }\n\n    /**\n     * array_filter callback to remove purely numeric id values.\n     *\n     * @param  string $v Value to compare, from array_filter() function\n     * @return bool\n     */\n    protected function filterCats($v)\n    {\n        return !is_numeric($v);\n    }\n\n    /**\n     * Get a list of valid image fields to sort by.\n     *\n     * @return array\n     */\n    protected function sortOpts()\n    {\n        return array(\n            \'name\'   => gTxt(\'name\'),\n            \'id\'     => gTxt(\'ID\'),\n            \'date\'   => gTxt(\'date\'),\n            \'author\' => gTxt(\'author\'),\n            \'ext\'    => gTxt(\'extension\'),\n            \'w\'      => gTxt(\'width\'),\n            \'h\'      => gTxt(\'height\'),\n        );\n    }\n}', 'e3af0462a83e0b23e1c5c58ddd344fa3', '#@smd_imagery\nsmd_imagery_dialog_title => Insert images\nsmd_imagery_fetch_btn => Fetch\nsmd_imagery_id_btn => Get IDs\nsmd_imagery_image => Image\nsmd_imagery_opt_cat => By category\nsmd_imagery_opt_fld => From field\nsmd_imagery_organise_btn => Manage\nsmd_imagery_result => Result\nsmd_imagery_template => Template', '', 4, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('mkp_url_parameters', 0, 'Michael K Pate', 'https://github.com/michaelkpate/mkp_url_parameters/', '1.2', 'A Textpattern Plugin for creating Date-Based Archives', '<h1>mkp_url_parameters</h1>\n\n<p>This tag examines the url of the current page and populated four variables: y for year, d for day, m for month, and monthtitle (the name of the month referenced in m). With access to those bits of information, your Textpattern install can be customized to do Yearly, Monthly and Daily Archives in a manner similar to what you may have seen from some other <span class=\"caps\">CMS</span>.</p>\n\n<h2>Acknowledgements</h2>\n\n<p>There was an earlier version of this plugin called mkp_archives that I lost when the Textdrive hosting service went down. But I think this one is actually an improvement anyway. Also thanks to <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_gps\">adi_gps</a> and <a href=\"http://stefdawson.com/sw/plugins/smd_if\">smd_if</a> for some definite inspiration.</p>\n\n<h2>Links</h2>\n\n<ul>\n	<li><a href=\"https://github.com/michaelkpate/mkp_url_parameters\">Github Code Repository</a></li>\n	<li><a href=\"https://raw.githubusercontent.com/michaelkpate/mkp_archives/master/mkp_url_parameters.txt\">Download</a></li>\n	<li><a href=\"http://datebasedarchives.cmsstyles.com/\">Demo Site</a></li>\n</ul>\n\n<h2>mkp_url_parameters</h2>\n\n<p>Just use &lt;txp:mkp_url_parameters /&gt; at the top of a page or form where you need to capture <span class=\"caps\">URL</span> information. For a better explanation, you will want to visit the Demo Site.</p>\n\n<h3>Attributes</h3>\n\n<h4>None</h4>\n\n<h3>Examples</h3>\n\n<pre><code>&lt;txp:mkp_url_parameters /&gt;</code></pre>\n\n<h3>Release History</h3>\n\n<p>Version 1.0: October 22nd, 2014<br />\nVersion 1.1: October 29th, 2014 (added Subdirectory Check)<br />\nVersion 1.2: June 3rd, 2016 (added Textpattern 4.6 Tag Registration and uses isset to prevent <span class=\"caps\">PHP</span> errors from being generated)</p>', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_url_parameters\')\r\n;\r\n}\r\n\r\nfunction mkp_url_parameters()\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	if (is_numeric($parts[1])) {\r\n\r\n	// y is numeric year, m is numeric month, monthtitle is name of month, d is numeric day \r\n\r\n		if (isset($parts[1])) { $variable[\'y\'] = (is_numeric($parts[1])) ? $parts[1] : null; }\r\n\r\n		if (isset($parts[2])) { $variable[\'m\'] = (is_numeric($parts[2]))? $parts[2] : null; }\r\n\r\n		if (isset($parts[2])) { $variable[\'monthtitle\'] = (is_numeric($parts[2])) ? date(\'F\', mktime(0, 0, 0, $parts[2])) : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'d\'] = (is_numeric($parts[3]))? $parts[3] : null; }\r\n\r\n	} else {\r\n\r\n	// at this point we are going to assume that Textpattern is running in a subdirectory and shift to the right in our array\r\n\r\n		if (isset($parts[2])) { $variable[\'y\'] = (is_numeric($parts[2])) ? $parts[2] : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'m\'] = (is_numeric($parts[3]))? $parts[3] : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'monthtitle\'] = (is_numeric($parts[3])) ? date(\'F\', mktime(0, 0, 0, $parts[3])) : null; }\r\n\r\n		if (isset($parts[4])) { $variable[\'d\'] = (is_numeric($parts[4]))? $parts[4] : null; }\r\n\r\n	} \r\n	 \r\n	return null;\r\n}\r\n', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_url_parameters\')\r\n;\r\n}\r\n\r\nfunction mkp_url_parameters()\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	if (is_numeric($parts[1])) {\r\n\r\n	// y is numeric year, m is numeric month, monthtitle is name of month, d is numeric day \r\n\r\n		if (isset($parts[1])) { $variable[\'y\'] = (is_numeric($parts[1])) ? $parts[1] : null; }\r\n\r\n		if (isset($parts[2])) { $variable[\'m\'] = (is_numeric($parts[2]))? $parts[2] : null; }\r\n\r\n		if (isset($parts[2])) { $variable[\'monthtitle\'] = (is_numeric($parts[2])) ? date(\'F\', mktime(0, 0, 0, $parts[2])) : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'d\'] = (is_numeric($parts[3]))? $parts[3] : null; }\r\n\r\n	} else {\r\n\r\n	// at this point we are going to assume that Textpattern is running in a subdirectory and shift to the right in our array\r\n\r\n		if (isset($parts[2])) { $variable[\'y\'] = (is_numeric($parts[2])) ? $parts[2] : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'m\'] = (is_numeric($parts[3]))? $parts[3] : null; }\r\n\r\n		if (isset($parts[3])) { $variable[\'monthtitle\'] = (is_numeric($parts[3])) ? date(\'F\', mktime(0, 0, 0, $parts[3])) : null; }\r\n\r\n		if (isset($parts[4])) { $variable[\'d\'] = (is_numeric($parts[4]))? $parts[4] : null; }\r\n\r\n	} \r\n	 \r\n	return null;\r\n}\r\n', '970b62a3a376a22551dd1d34181dad11', '', '', 0, 5, 0),
('mkp_gravatar', 0, 'Michael K Pate', 'https://github.com/michaelkpate/mkp_gravatar', '1.0', 'Yet Another Textpattern Gravatar Plugin', '	<h1>mkp_gravatar</h1>\n\n	<p>This tag returns an image tag or an <span class=\"caps\">URL</span> with the Gravatar for the email address of the current comment or any given email address.</p>\n\n	<p>From the Gravatar Site: An &#8220;avatar&#8221; is an image that represents you online—a little picture that appears next to your name when you interact with websites. A Gravatar is a Globally Recognized Avatar. You upload it and create your profile just once, and then when you participate in any Gravatar-enabled site, your Gravatar image will automatically follow you there.</p>\n\n	<h2>Acknowledgements</h2>\n\n	<p>Previously, there have been two other Gravatar plugins for Textpattern but both are currently in orphan status. I started this one from scratch using <a href=\"https://en.gravatar.com/site/implement/images/php/\"><span class=\"caps\">PHP</span> Image Requests</a> from the Gravatar Developer Resources as a Guide. I also looked at the code of glx_gravatar for implementation ideas (and the format of this help document).</p>\n\n	<h2>Links</h2>\n\n	<ul>\n		<li><a href=\"https://github.com/michaelkpate/mkp_gravatar\">Github Code Repository</a></li>\n		<li><a href=\"https://raw.githubusercontent.com/michaelkpate/mkp_gravatar/master/mkp_gravatar.txt\">Download</a></li>\n	</ul>\n\n	<h2>mkp_gravatar</h2>\n\n	<p>Just use &lt;txp:mkp_gravatar /&gt; where ever you need a Gravatar to be displayed. If you put this tag into a comment form the Gravatar will be constructed using the email address given by the current comment. If you put this tag anywhere else (where mkp_gravatar has no access to a comment email address) you can supply an email address by the attribute email to create the accompanying Gravatar.</p></p>\n\n	<h3>Attributes</h3>\n\n	<h4>secure (default: 0)</h4>\n\n	<ul>\n		<li>1: returns the gravatar using <span class=\"caps\">SSL</span></li>\n		<li>0: returns the gravatar using non-<span class=\"caps\">SSL</span></li>\n	</ul>\n\n	<h4>email (default: empty/email address of current commentator)</h4></h4>\n\n	<p>Used inside a comment form the plugin will choose the current commenter&#8217;s email address. If you use this tag outside of the comment forms this attribute is needed to give mkp_gravatar the email address to be encoded. Any email can be Gravatar encoded.</p>\n\n	<h4>size (default: 80)</h4>\n\n	<p>Defines the size of the resulting Gravatar (range: 1 to 512). All Gravatars are squared.</p>\n\n	<h4>rating (default: g)</h4>\n\n	<p>Defines the rating for the Gravatar.</p>\n\n	<ul>\n		<li>g: suitable for display on all websites with any audience type.</li>\n		<li>pg: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.</li>\n		<li>r: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.</li>\n		<li>x: may contain hardcore sexual imagery or extremely disturbing violence.</li>\n	</ul>\n\n	<h4>default (default: gravatar-logo)</h4>\n\n	<p>Define which image(s) to display when an email address has no Gravatar. If this attribute is missing gravatar.com&#8217;s white G on blue ground will be displayed. If you supply the <span class=\"caps\"><span class=\"caps\">URL</span></span>of an image here it will be used. Further it is possible to use gravatar.com&#8217;s integration icons based on the submitted email address:</p>\n\n	<ul>\n		<li>404: do not load any image if none is associated with the email hash, instead return an <span class=\"caps\"><span class=\"caps\">HTTP</span></span> 404 (File Not Found) response</li>\n		<li>mm: (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash)</li>\n		<li>identicon: a geometric pattern based on an email hash</li>\n		<li>monsterid: a generated &#8216;monster&#8217; with different colors, faces, etc</li>\n		<li>wavatar: generated faces with differing features and backgrounds</li>\n		<li>retro: awesome generated, 8-bit arcade-style pixelated faces</li>\n	</ul>\n\n	<h4>force (default: 0)</h4>\n\n	<ul>\n		<li>1: always returns the Gravatar in the form chosen as the default.</li>\n		<li>0: returns the Gravatar as normal</li>\n	</ul>\n\n	<h4>disable (default: 0)</h4>\n\n	<ul>\n		<li>1: nothing is returned</li>\n		<li>0: returns the Gravatar as normal</li>\n	</ul>\n\n	<h3>Image and Related Attributes</h3>\n\n	<h4>image (default: 1)</h4>\n\n	<ul>\n		<li>1: returns the image tag of the Gravatar</li>\n		<li>0: returns the url of the Gravatar</li>\n	</ul>\n\n	<h4>alt (default: empty)</h4>\n\n	<p>Content of the alt attribute of the image tag.</p>\n\n	<h4>class (default: empty)</h4>\n\n	<p>Content of the class attribute of the image tag.</p>\n\n	<h4>id (default: empty)</h4>\n\n	<p>Content of the id attribute of the image tag.</p>\n\n	<h4>style (default: empty)</h4>\n\n	<p>Content of the style attribute of the image tag.</p>\n\n	<h4>title (default: empty)</h4>\n\n	<p>Content of the title attribute of the image tag.</p>\n\n	<h2>Examples</h2>\n\n<pre><code>&lt;txp:mkp_gravatar /&gt;;\n</code></pre>\n\n<pre><code>&lt;txp:mkp_gravatar size=&quot;60&quot; default=&quot;http://www.example.com/images/gravatar.png&quot; class=&quot;gravatar&quot; /&gt;;\n</code></pre>\n\n	<p>Used in the form comments it will show the Gravatar of the current commentator</p>\n\n<pre><code>&lt;txp:mkp_gravatar email=&quot;address@example.com&quot;; /&gt;\n</code></pre>\n\n	<p>Shows the Gravatar for address@example.com.</p>\n\n	<h3>Release History</h3>\n\n	<p>Version 1.0: September 16th, 2014</p>', '	function mkp_gravatar($atts)\r\n	{\r\n	\r\n		global $thiscomment;\r\n	\r\n		extract(lAtts(array(\r\n			\'secure\'   => \'0\',\r\n			\'email\'   => \'\',	\r\n			\'size\'   => \'80\',\r\n			\'rating\'   => \'g\',\r\n			\'default\'   => \'\',\r\n			\'force\'   => \'\',	\r\n			\'image\'   => \'1\',\r\n			\'alt\'  => \'\',\r\n			\'class\'   => \'\',		\r\n			\'id\'  => \'\',\r\n			\'style\'  => \'\',\r\n			\'title\'  => \'\',\r\n			\'disable\'  => \'\',\r\n		),$atts));\r\n		\r\n		$url = ($secure==\'1\' || $secure==\'y\' || $secure==\'true\') ? \'https://secure.gravatar.com/avatar/\' : \'http://www.gravatar.com/avatar/\';\r\n		\r\n		$url .= (!empty($email)) ? md5(strtolower(trim(parse($email)))) : md5(strtolower(trim($thiscomment[\'email\'])));\r\n\r\n		$url .= \'?s=\'. $size . \'&r=\' . $rating;\r\n\r\n		if (!empty($default)) {\r\n			$url .= \'&d=\'.$default;\r\n			$url .= ($force==\'1\' || $force==\'y\' || $force==\'true\') ? \'&f=y\' : \'\';\r\n		}\r\n		\r\n		if ($image==\'1\' || $image==\'y\' || $image==\'true\') {\r\n			$url = \'<img src=\"\' . $url . \'\" height=\"\' . $size . \'\" width=\"\' . $size . \'\" \';\r\n			if (!empty($alt)) {		\r\n				$url .= \'alt=\"\' . $alt . \'\" \';\r\n			}\r\n			if (!empty($class)) {		\r\n				$url .= \'class=\"\' . $class . \'\" \';\r\n			}\r\n			if (!empty($id)) {		\r\n				$url .= \'id=\"\' . $id . \'\" \';\r\n			}\r\n			if (!empty($style)) {		\r\n				$url .= \'style=\"\' . $style . \'\" \';\r\n			}\r\n			if (!empty($title)) {		\r\n				$url .= \'title=\"\' . $title . \'\" \';\r\n			}\r\n			$url .= \'/>\';\r\n		}\r\n\r\n		$url = ($disable==\'1\' || $disable==\'y\' || $disable==\'true\') ? \'\' : $url;\r\n \r\n		return $url;\r\n	}', '	function mkp_gravatar($atts)\r\n	{\r\n	\r\n		global $thiscomment;\r\n	\r\n		extract(lAtts(array(\r\n			\'secure\'   => \'0\',\r\n			\'email\'   => \'\',	\r\n			\'size\'   => \'80\',\r\n			\'rating\'   => \'g\',\r\n			\'default\'   => \'\',\r\n			\'force\'   => \'\',	\r\n			\'image\'   => \'1\',\r\n			\'alt\'  => \'\',\r\n			\'class\'   => \'\',		\r\n			\'id\'  => \'\',\r\n			\'style\'  => \'\',\r\n			\'title\'  => \'\',\r\n			\'disable\'  => \'\',\r\n		),$atts));\r\n		\r\n		$url = ($secure==\'1\' || $secure==\'y\' || $secure==\'true\') ? \'https://secure.gravatar.com/avatar/\' : \'http://www.gravatar.com/avatar/\';\r\n		\r\n		$url .= (!empty($email)) ? md5(strtolower(trim(parse($email)))) : md5(strtolower(trim($thiscomment[\'email\'])));\r\n\r\n		$url .= \'?s=\'. $size . \'&r=\' . $rating;\r\n\r\n		if (!empty($default)) {\r\n			$url .= \'&d=\'.$default;\r\n			$url .= ($force==\'1\' || $force==\'y\' || $force==\'true\') ? \'&f=y\' : \'\';\r\n		}\r\n		\r\n		if ($image==\'1\' || $image==\'y\' || $image==\'true\') {\r\n			$url = \'<img src=\"\' . $url . \'\" height=\"\' . $size . \'\" width=\"\' . $size . \'\" \';\r\n			if (!empty($alt)) {		\r\n				$url .= \'alt=\"\' . $alt . \'\" \';\r\n			}\r\n			if (!empty($class)) {		\r\n				$url .= \'class=\"\' . $class . \'\" \';\r\n			}\r\n			if (!empty($id)) {		\r\n				$url .= \'id=\"\' . $id . \'\" \';\r\n			}\r\n			if (!empty($style)) {		\r\n				$url .= \'style=\"\' . $style . \'\" \';\r\n			}\r\n			if (!empty($title)) {		\r\n				$url .= \'title=\"\' . $title . \'\" \';\r\n			}\r\n			$url .= \'/>\';\r\n		}\r\n\r\n		$url = ($disable==\'1\' || $disable==\'y\' || $disable==\'true\') ? \'\' : $url;\r\n \r\n		return $url;\r\n	}', 'd0fb847ab62bb616dd289167449cf44a', '', '', 0, 5, 0),
('mkp_if_ia', 0, 'Michael K Pate', 'http://ampdemo.cmsstyles.com/', '1.0', 'A Textpattern Conditional Plugin for Facebook\'s Instant Articles', '<h1>mkp_if_ia</h1>\n\n<p>This conditional tag examines the url of the current page and determines if the url ends in &#8216;ia.&#8217; This allows for a custom page to be rendered using the standards for Facebook&#8217;s Instant Articles.</p>\n\n<h2>Links</h2>\n\n<ul>\n	<li><a href=\"https://github.com/michaelkpate/mkp_if_ia\">Github Code Repository</a></li>\n	<li><a href=\"https://raw.githubusercontent.com/michaelkpate/mkp_if_ia/master/mkp_if_ia.txt\">Download</a></li>\n	<li><a href=\"http://ampdemo.cmsstyles.com/\">Demo Site</a></li>\n</ul>\n\n<h2>Required Plugins for <span class=\"caps\">RSS</span> Feed<br />\n<ul>\n	<li><a href=\"http://makss.uaho.net/plugins/aks_header\">aks_header</a></li>\n	<li><a href=\"http://rahforum.biz/plugins/rah_function\">rah_function</a></li><br />\n</ul></h2>\n\n<h2>mkp_if_ia</h2>\n\n<h3>Attributes</h3>\n\n<h4>None</h4>\n\n<h3>Examples</h3>\n\n<p>You will need to create an <a href=\"https://developers.facebook.com/docs/instant-articles/publishing/setup-rss-feed\"><span class=\"caps\">RSS</span> Feed</a> that meets Facebook&#8217;s requirements for Instant Articles.</p>\n\n<h5>Create a new section. I called mine &#8216;ia.&#8217; This is the page template.</h5>\n\n<pre><code>\n&lt;txp:aks_header name=&quot;Content-Type&quot; value=&quot;application/rss+xml&quot; strip=&quot;0&quot; gzip=&quot;1&quot; nodebug=&quot;1&quot; /&gt;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;rss version=&quot;2.0&quot; xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:content=&quot;http://purl.org/rss/1.0/modules/content/&quot; xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;&gt;\n &lt;channel&gt;\n  &lt;title&gt;&lt;txp:site_name /&gt;&lt;/title&gt;\n  &lt;description&gt;&lt;txp:site_slogan /&gt; - instant articles feed&lt;/description&gt;\n  &lt;link&gt;&lt;txp:link_to_home /&gt;&lt;/link&gt;\n  &lt;atom:link href=&quot;&lt;txp:link_to_home /&gt;&lt;txp:page_url type=&quot;s&quot; /&gt;/&quot; rel=&quot;self&quot; type=&quot;application/rss+xml&quot; /&gt;\n  &lt;pubDate&gt;&lt;txp:rah_function call=&quot;safe_strftime&quot; str=&quot;rfc822&quot; /&gt;&lt;/pubDate&gt;\n  &lt;txp:article_custom form=&quot;ia&quot; section=&quot;article&quot; /&gt;\n &lt;/channel&gt;\n&lt;/rss&gt;\n</code></pre>\n\n<h5>I made an article template &#8211; also called &#8216;ia&#8217;</h5>\n\n<pre><code>\n&lt;item&gt;\n&lt;title&gt;&lt;txp:title /&gt;&lt;/title&gt;\n&lt;content:encoded&gt;\n &lt;![CDATA[\n  &lt;txp:body /&gt;\n ]]&gt;\n&lt;/description&gt;\n&lt;guid&gt;&lt;txp:permlink /&gt;&lt;/guid&gt;\n&lt;description&gt;\n &lt;txp:rah_function call=&quot;strip_tags&quot;&gt;\n	&lt;txp:body /&gt;\n &lt;/txp:rah_function&gt;\n&lt;/description&gt;\n&lt;link&gt;&lt;txp:permlink /&gt;ia/&lt;/link&gt;\n&lt;pubDate&gt;&lt;txp:posted format=&quot;%a, %d %b %G %T %Z&quot; /&gt;&lt;/pubDate&gt;\n&lt;author&gt;&lt;txp:author /&gt;&lt;/author&gt;\n&lt;/item&gt;\n</code></pre>\n\n<p>You can see an example of the results: <a href=\"http://ampdemo.cmsstyles.com/ia/\">http://ampdemo.cmsstyles.com/ia/</a></p>\n\n<p>You will want something similar to this in your individual article template to display the alternative formatting.</p>\n\n<pre><code>&lt;txp:mkp_if_ia&gt;\n &lt;txp:output_form form=&quot;layout_archives_ia&quot; /&gt;\n&lt;txp:else /&gt;\n &lt;txp:output_form form=&quot;layout_archives_standard&quot; /&gt;\n&lt;/txp:_mkp_if_amp&gt;</code></pre>\n\n<h3>Release History</h3>\n\n<p>Version 1.0: May 10th, 2016</p>', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_if_ia\')\r\n;\r\n}\r\n\r\nfunction mkp_if_ia($atts, $thing=\'\')\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	// if the url ends in \'ia\' this will return true; otherwise false\r\n	 \r\n	return (end($parts) == \'ia\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\r\n}', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_if_ia\')\r\n;\r\n}\r\n\r\nfunction mkp_if_ia($atts, $thing=\'\')\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	// if the url ends in \'ia\' this will return true; otherwise false\r\n	 \r\n	return (end($parts) == \'ia\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\r\n}', '475184c3e8d75431e3090d8527c24e1f', '', '', 0, 5, 0),
('mkp_if_amp', 0, 'Michael K Pate', 'https://github.com/michaelkpate/', '1.0', 'A Textpattern Conditional Plugin for Google\'s Accelerated Mobile Pages Project (AMP) ', '<h1>mkp_if_amp</h1>\n\n<p>This conditional tag examines the url of the current page and determines if the url ends in ‘amp.’ This allows for a custom page to be rendered using the standards for Google&#8217;s Accelerated Mobile Pages Project.</p>\n\n<h2>Links</h2>\n\n<ul>\n    <li><a href=\"https://github.com/michaelkpate/mkp_if_amp\">Github Code Repository</a></li>\n    <li><a href=\"https://raw.githubusercontent.com/michaelkpate/mkp_if_amp/master/mkp_if_amp.txt\">Download</a></li>\n    <li><a href=\"http://ampdemo.cmsstyles.com/\">Demo Site</a></li>\n</ul>\n\n<h2>mkp_if_amp</h2>\n\n<h3>Attributes</h3>\n\n<h4>None</h4>\n\n<h3>Examples</h3>\n\n<p>You will want some code similar to this in your individual article template header so that the amp page can be properly detected.</p>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n&lt;link rel=&quot;canonical&quot; href=&quot;&lt;txp:permlink /&gt;&quot;&gt;\n&lt;link rel=&quot;amphtml&quot; href=&quot;&lt;txp:permlink /&gt;/amp/&quot;&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>You will want something similar to this in your individual article template to display the alternative formatting.</p>\n\n<pre><code>&lt;txp:mkp_if_amp&gt;\n&lt;txp:output_form form=&quot;layout_archives_amp&quot; /&gt;\n&lt;txp:else /&gt;\n&lt;txp:output_form form=&quot;layout_archives_standard&quot; /&gt;\n&lt;/txp:_mkp_if_amp&gt;</code></pre>\n\n<h3>Release History</h3>\n\n<p>Version 1.0: March 24th, 2016</p>', '// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n->register(\'mkp_if_amp\')\n;\n}\n\nfunction mkp_if_amp($atts, $thing=\'\')\n{\n\n	global $variable;\n\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\n\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\n\n	// if the url ends in \'amp\' this will return true; otherwise false\n	 \n	return (end($parts) == \'amp\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\n}\n', '// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n->register(\'mkp_if_amp\')\n;\n}\n\nfunction mkp_if_amp($atts, $thing=\'\')\n{\n\n	global $variable;\n\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\n\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\n\n	// if the url ends in \'amp\' this will return true; otherwise false\n	 \n	return (end($parts) == \'amp\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\n}\n', '2d3cb39c0403e5498018f5358def9463', '', '', 0, 5, 0),
('mkp_if_en', 0, 'Michael K Pate', 'https://github.com/michaelkpate/', '1.1', 'allowing Textpattern to detect an alternative URL that required English', '<h1>mkp_if_en</h1>\n\n<p>This conditional tag examines the url of the current page and determines if the url begins with &#8216;en.&#8217; This allows for a custom page to be rendered in English.</p>\n\n<h2>Links</h2>\n\n<ul>\n	<li><a href=\"https://github.com/michaelkpate/mkp_if_en\">Github Code Repository</a></li>\n	<li><a href=\"https://raw.githubusercontent.com/michaelkpate/mkp_if_amp/master/mkp_if_en.txt\">Download</a></li>\n</ul>\n\n<h2>mkp_if_en</h2>\n\n<h3>Attributes</h3>\n\n<h4>None</h4>\n\n<h3>Examples</h3>\n\n<p>You will want something similar to this in your error template to display the alternative formatting.</p>\n\n<pre><code>&lt;txp:mkp_if_en&gt;\n  &lt;txp:smd_if field=&quot;txpvar:target_article&quot; operator=&quot;isused&quot;&gt;\n    [individual article page header design]\n    &lt;txp:article_custom form=&quot;[article form]&quot; id=&#39;&lt;txp:variable name=&quot;target_article&quot; /&gt;&#39; /&gt;\n    [individual article page footer design]\n  &lt;txp:else /&gt;\n    [section page header design]\n    &lt;txp:article_custom form=&quot;[article form]&quot; section=&#39;&lt;txp:variable name=&quot;target_section&quot; /&gt;&#39; /&gt;\n    [section footer design]\n&lt;txp:else /&gt;\n [standard error page code]\n&lt;/txp:_mkp_if_en&gt;</code></pre>\n\n<h3>Release History</h3>\n\n<p>Version 1.1: November 2nd, 2016 (added the variables)<br />\nVersion 1.0: November 1st, 2016</p>', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_if_en\')\r\n;\r\n}\r\n\r\nfunction mkp_if_en($atts, $thing=\'\')\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	// if the url begins with \'en\' this will return true; otherwise false\r\n\r\n	if ($parts[1] == \'en\') {\r\n\r\n		// the variable target_section will return the section specified in the url\r\n\r\n		if (isset($parts[2])) { $variable[\'target_section\'] = $parts[2]; }\r\n\r\n		// the variable target_article will return the article id specified in the url\r\n\r\n		if (isset($parts[3])) { $variable[\'target_article\'] = (is_numeric($parts[3])) ? $parts[3] : null; }\r\n\r\n		return parse(EvalElse($thing, true));\r\n\r\n} else {\r\n\r\n		return parse(EvalElse($thing, false));\r\n}\r\n\r\n\r\n}', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\r\n->register(\'mkp_if_en\')\r\n;\r\n}\r\n\r\nfunction mkp_if_en($atts, $thing=\'\')\r\n{\r\n\r\n	global $variable;\r\n\r\n	// preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')) was copied from the function preText in publish.php\r\n\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", serverSet(\'REQUEST_URI\')), 5);\r\n\r\n	// if the url begins with \'en\' this will return true; otherwise false\r\n\r\n	if ($parts[1] == \'en\') {\r\n\r\n		// the variable target_section will return the section specified in the url\r\n\r\n		if (isset($parts[2])) { $variable[\'target_section\'] = $parts[2]; }\r\n\r\n		// the variable target_article will return the article id specified in the url\r\n\r\n		if (isset($parts[3])) { $variable[\'target_article\'] = (is_numeric($parts[3])) ? $parts[3] : null; }\r\n\r\n		return parse(EvalElse($thing, true));\r\n\r\n} else {\r\n\r\n		return parse(EvalElse($thing, false));\r\n}\r\n\r\n\r\n}', '1fb83a0f58e4d1d347cc8d91fd6f0364', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cxc_templates', 0, '~cXc~', 'http://code.google.com/p/cxc-templates/', '0.4.5', 'Template engine for TextPattern 4.3.0 with support for forms, pages, plugins, sections, styles and other template specific assets.', '<h1>Import/Export/Remove/Download/Upload Templates as Files</h1>\n<p>This plugin creates a new <strong>Templates</strong> tab under <strong>Extensions</strong>, enabling the trivial export of<strong> Forms</strong>, <strong>Pages</strong>, <strong>Plugins</strong>, <strong>Sections</strong>, and <strong>Style</strong> rules to a specified folder for convenient editing, and the subsequent import of new and updated files. Existing template directories, as well as, the $cxc_templates[&#8217;base_dir&#8217;] can be deleted. Please note, the $cxc_templates[&#8217;base_dir&#8217;] will be recreated when the plugin is next accessed. Other features include zip and download of template directories, and a template upload option that will upload and import new templates with a single click.</p>\n\n<h2 class=\"cxc-tpl-slide-head\"><a id=\"plugin-requirements\">Plugin Requirements</a> &lt;/&gt;</h2>\n<div class=\"cxc-tpl-slide-body\">\n<p>This plugin requires Textpattern <strong>4.3.0</strong> and above.</p>\n<p>Regardless of where it&#8217;s been tested, this plugin messes around with your database.</p>\n<p><em>Do not use it without backing up your database</em>.</p>\n</div>\n\n<h2 class=\"cxc-tpl-slide-head\"><a id=\"setup-instructions\">Setup Instructions</a> &lt;/&gt;</h2>\n<div class=\"cxc-tpl-slide-body\">\n<p>By default, the plugin looks for the <strong>tmp</strong> and <strong>tpl</strong> directories, the <strong>tpl</strong> directory should be in the webroot with images, rpc, sites, and textpattern directories and the <strong>tmp</strong> directory should be in the textpattern directory. If the directories don&#8217;t exist, the plugin will attempt to create them the first time you access the plugin. This creation will sometimes fail, if that occurs, you&#8217;ll need to create the directories manually, and ensure that the web server has write access.</p>\n<p>If your Textpattern root is located at <strong>/users/home/myuser/web/public/</strong>, something similar to the following commands could be used:</p>\n<pre><code>cd /users/home/myuser/web/public/\nmkdir directory\nchmod 777 directory\n</code></pre>\n<p>Just replace the word &#8217;directory&#8217; in the example above with the directory you need to create.</p>\n<p><strong>Note:</strong> <em>if using an alternate template directory you will need to adjust accordingly.</em></p>\n</div>\n\n<h2 class=\"cxc-tpl-slide-head\"><a id=\"usage-instructions\">Usage Instructions</a> &lt;/&gt;</h2>\n<div class=\"cxc-tpl-slide-body\">\n<p><strong>Import Template</strong>&#8211; select a template to import from the dropdown on the <strong>Templates</strong> tab and press <em>Go</em>. Before importing, the plugin will do an export of your currently installed templates to a folder called. If this is not your first install this may overwrite the current template backup located in <strong>preimport-data</strong>.</p>\n<p><strong>Safe Mode</strong> &#8211; allows you to import a template with out overwriting any existing database entries. When this setting is enabled the plugin will skip importing the forms, pages, sections and styles if the database already contains an entry with the same name and only import new entries. This setting will usually require additional editing and is turned off by default.</p>\n<p><strong>Export Template</strong> &#8211; is achieved by typing in an export name and pressing <em>Go</em>. Keep in mind naming an export the same as an exisiting template directory will overwrite the contents and that the assets folder is not created. This is done for two reasons ...</p>\n<ol>\n    <li>The system is completely unaware of which template you are using and there isn&#8217;t a meta file to tell the plugin what assets to clone or where to find them.</li>\n    <li>Even if the above was added to the plugin, cloned templates would need to edit the exported files to change resource directories used for css, images, and js.</li>\n</ol>\n<p>... that doesn&#8217;t mean it can&#8217;t be added, only that for now it doesn&#8217;t work that way.</p>\n<p><strong>Delete Template</strong> &#8211; select a template to remove from the dropdown on the <strong>Templates</strong> tab and press <em>Go</em>. Follow the instructions on the <strong>Delete Directory Confirmation</strong> page to remove the selected template directory from your site, if no template is selected from the dropdown list the entire templates directory will be removed. If this feature is unable to remove templates it is usually due to the server configuration of your host and removal of templates will need to be done manually.</p>\n<p><strong>Zip Project Folder</strong> &#8211; select a template directory to zip from the dropdown on the <strong>Templates</strong> tab and press <em>Go</em>. This will zip the entire template directory and force download of the template, once downloaded you can extract the contents and remove files that are unecessary. This is mostly a feature to help designers share their templates, downloaded zip files must be extracted and rezipped before they can be used with the upload feature. If this feature is unable to zip the template directory it is usually due to the server configuration of your host and downlpad of templates will not be possible<strong></strong>.</p>\n<p><strong>Upload Template</strong> &#8211; use the browse button to locate a template zip file you have and press <em>Go</em>. Keep in mind uploading a templates zip file with a template of the same name as an exisiting folder will overwrite the contents of the existing folder. Uploaded templates are extracted to the templates directory and can then be imported using the Import feature of the plugin.</p>\n<p><strong>Advanced</strong> &#8211; this area will allow you to do a webroot template installation and is not recommended unless instructed to by the template designer <strong></strong>or you know hwat you are doing. This feature can be used for support files or common files used by designers that must be in the webroot to function properly. When using this method for installation the uploaded zip file will be extracted directly into teh webroot and will overwrite existing files of the same name.</p>\n</div>\n\n<h2 class=\"cxc-tpl-slide-head\"><a id=\"designing-templates\">Designing Templates</a> &lt;/&gt;</h2>\n<div class=\"cxc-tpl-slide-body\">\n<p class=\"cxc-tpl-slide-head\">The following <a id=\"file-naming-conventions\">file naming conventions</a> &lt;/&gt; are recommended to designers:</p>\n<div class=\"cxc-tpl-slide-body preview\">\n    <p>Default pages, forms and styles should be prefaced with the design’s name.</p>\n    <ul>\n        <li>Where possible the default page becomes THEME_NAME_default.page and so on ...</li>\n        <li><strong>Note:</strong> <em>all core code findings discovered by <a href=\"\">Bert Garcia</a> are still relevant</em>.</li>\n    </ul>\n    <br />\n    <p>Templating file extensions, simple:</p>\n    <ul>\n        <li>Forms → .form</li>\n        <li>Pages → .page</li>\n        <li>Plugins → .plugin</li>\n        <li>Sections → .section</li>\n        <li>Styles → .css  </li>\n    </ul>\n</div>\n<p class=\"cxc-tpl-slide-head\">This is <a id=\"suggested-folder-structure\" title=\"Click to view example template directory structure\">the folder and subfolders structure</a> &lt;/&gt; used for template creation:</p>\n<div class=\"cxc-tpl-slide-body preview\">\n    <ul>\n        <li>tpl<ul>\n            <li>THEME_NAME<ul>\n                <li>assets<ul>\n                    <li>css<ul>\n                        <li>additional.css</li>\n                    </ul></li>\n                    <li>js<ul>\n                        <li>additional.js</li>\n                    </ul></li>\n                    <li>additional.png</li>\n                </ul></li>\n                <li>forms<ul>\n                    <li>default.article.form</li>\n                    <li>...</li>\n                </ul></li>\n                <li>pages<ul>\n                    <li>default.page</li>\n                    <li>...</li>\n                </ul></li>\n                <li>plugins<ul>\n                    <li>cxc_templates.plugin</li>\n                </ul></li>\n                <li>sections<ul>\n                    <li>default.section</li>\n                </ul></li>\n                <li>style<ul>\n                    <li>default.css</li>\n                </ul></li>\n            </ul></li>\n            <li>DESIGNER.txt</li>\n            <li>README.txt</li>\n            <li>preview.img</li>\n      </ul></li>\n    </ul>\n</div>\n<p>The default templates directory is the &quot;<strong>tpl</strong>&quot; directory, but in the past &quot;_templates&quot; was used and some templates may still require you to use it (or another) directory. I hope everyone will adopt the use of the &quot;<strong>tpl</strong>&quot; directory but I&#8217;m not forcing it on anyone. Please note, it is possible to have and use multiple template directories on a single site, but only templates existing in the directory set as $cxc_templates[&#8217;base_dir&#8217;] will be used to display available templates.</p>\n<p>You will need to replace &quot;<strong>THEME_NAME</strong>&quot; with the name of the template you are designing. The name of the template folder should be lower-cased and alpha numeric (can contain hyphens &quot;-&quot; and underscores &quot;_&quot;). Technically it does not have to be lower-cased but it is definitely the standard since asset links are case-sensitive.</p>\n<p>The &quot;<strong>assets</strong>&quot; folder is not required nor are the sub-directories, I added them with organization in mind, it is simply a design choice. The concept is that all support files (css, images, js, and other files) could be placed in this folder (or another folder below the <strong>THEME_NAME</strong> directory) instead of requiring an advanced install into the webroot.</p>\n<p>The &quot;<strong>forms</strong>&quot; folder contains all forms included with the template, form files not required or part of the template should be removed before sharing your design publicly.</p>\n<p>The &quot;<strong>pages</strong>&quot; folder contains all pages included with the template, page files not required or part of the template should be removed before sharing your design publicly.</p>\n<p>The &quot;<strong>plugins</strong>&quot; folder contains all plugins included with the template, plugin files not required or part of the template should be removed before sharing your design publicly.</p>\n<p>The &quot;<strong>sections</strong>&quot; folder contains all sections included with the template, section files not required or part of the template should be removed before sharing your design publicly.</p>\n<p>The &quot;<strong>styles</strong>&quot; folder contains all css style sheets included with the template, css files not required or part of the template should be removed before sharing your design publicly.</p>\n<p>The &quot;<strong>DESIGNER.txt</strong>&quot; (<em>optional</em>) file must be located in the templates root directory and can be used by designers to link to their homepage or advertise additional products and services they offer. This file is not required and will not be displayed unless the user clicks on the &quot;<strong>Additional Information</strong> &lt;/&gt;&quot; area located below allow other results. The &quot;<strong>DESIGNER.txt</strong>&quot; file name is case sensitive and may contain simple html markup.</p>\n<p>The &quot;<strong>README.txt</strong>&quot; (<em>optional</em>) file must be located in the templates root directory and can be used by designers to display after installation instructions. This file is not required but if present, it will be displayed above all other information during install. The &quot;<strong>README.txt</strong>&quot; file name is case sensitive and may contain simple html markup.</p>\n<p>The &quot;<strong>preview.img</strong>&quot; (<em>optional</em>) file must be located in the templates root directory and is an image or logo that can be added to display the template previews. This file is not required but if present, it will be displayed on the right hand side of the template manager after the first import. The file name and acceptable image formats are &quot;<strong>preview.gif</strong>&quot;, &quot;<strong>preview.jpg</strong>&quot; and &quot;<strong>preview.png</strong>&quot; and is case sensitive.</p>\n<p><strong>Note:</strong> <em>designers are encouraged to include empty index.html files in all subdirectories of their template to help keep our Textpattern sites secure.</em></p>\n</div>\n\n<h2>Plugin Credits</h2>\n\n<p>Plugin code based on a modified version of mem_templates by <a href=\"http://manfre.net/\">Michael Manfre</a> that was released with one of <a href=\"http://protextthemes.com\">Stuart Butcher&#8217;s</a> TXP 4.3.0 templates, which is based off of hcg_templates by <a href=\"http://txptag.com/\">Bert Garcia</a>, which is based off of mcw_templates by <a href=\"http://mikewest.org/\" rel=\"nofollow\">Mike West</a> with additional features introduced to an alternate hcg_templates provided by <a href=\"http://clueless.com.ar/\">Mariano Absatz</a>. </p>\n<p>Without code contributions, help and tutoring from <a href=\"http://zegnat.com/\">Martijn van der Ven</a> and <a href=\"http://stefdawson.com/\">Stef Dawson</a>, as well as, the mentioned plugins and contributions from <strong>all</strong> the above this plugin would not have been made possible. </p>\n<p><strong>Note:</strong> <em>when </em><strong>&lt;/&gt;</strong><em> is encountered throughout the template manager it denotes information that can be expanded/collapsed to show/hide additional information.</em></p>\n\n<script type=\"text/javascript\">\n	$(document).ready(function()\n	{\n	  $(\".cxc-tpl-slide-body\").hide();\n	  $(\".cxc-tpl-slide-head\").click(function()\n	  {\n		$(this).next(\".cxc-tpl-slide-body\").slideToggle(600);\n	  });\n	});\n</script>', ' \r\n/*\r\n	PUBLIC PLUGIN CONFIG\r\n	-------------------------------------------------------------------------\r\n*/\r\n	$cxc_templates = array(\r\n		\'base_dir\'			=>	\'tpl\',\r\n		\'cache_dir\'			=>	\'tmp\',\r\n\r\n		\'subdir_css\'		=>	\'style\',\r\n		\'subdir_forms\'		=>	\'forms\',\r\n		\'subdir_pages\'		=>	\'pages\',\r\n		\'subdir_plugins\'	=>	\'plugins\',\r\n		\'subdir_sections\'	=>	\'sections\',\r\n\r\n		\'ext_css\'			=>	\'.css\',\r\n		\'ext_forms\'			=>	\'.form\',\r\n		\'ext_pages\'			=>	\'.page\',\r\n		\'ext_plugins\'		=>	\'.plugin\',\r\n		\'ext_section\'		=>	\'.section\'\r\n	);\r\n\r\n/*\r\n	PLUGIN CODE (no editing below this line, please)\r\n	-------------------------------------------------------------------------\r\n*/\r\n	define(\'_CXC_TEMPLATES_IMPORT\', 1);\r\n	define(\'_CXC_TEMPLATES_EXPORT\', 2);\r\n	$GLOBALS[\'_CXC_TEMPLATES\'] = $cxc_templates;\r\n\r\n/*\r\n	PLUGIN CODE::INSTANTIATION\r\n	-------------------------------------------------------------\r\n*/	\r\n	if (@txpinterface == \'admin\') {\r\n		$import = \'cxc_templates\';\r\n		$import_tab = cxc_templates_gTxt(\'cxc_tpl_templates_tab\');\r\n\r\n		add_privs($import, \'1,2\');\r\n		register_tab(\'extensions\', $import, $import_tab);\r\n		register_callback(\'cxc_templates\', $import);\r\n		register_callback(\'cxc_tpl_prep\', \'plugin_lifecycle.cxc_templates\');\r\n	}\r\n\r\n/*\r\n	PLUGIN CODE::LIFECYCLE\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_tpl_prep($event, $step) {\r\n		global $prefs;\r\n		switch ($step) {\r\n			case \'disabled\':\r\n				if (isset($prefs[\'cxc_tpl_current\'])) {\r\n					$prep = safe_delete(\'txp_prefs\',\'name = \"cxc_tpl_current\"\');\r\n				}\r\n				break;\r\n			case \'deleted\':\r\n				if (isset($prefs[\'cxc_tpl_current\'])) {\r\n					$prep = safe_delete(\'txp_prefs\',\'name = \"cxc_tpl_current\"\');\r\n				}\r\n				break;\r\n		}\r\n	}\r\n\r\n/*\r\n	PLUGIN CODE::MAIN CALLBACK\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_templates($event, $step=\'\') {\r\n		$GLOBALS[\'prefs\'] = get_prefs();\r\n		global $prefs;\r\n		$template = new cxc_template();\r\n\r\n		pagetop(cxc_templates_gTxt(\'cxc_tpl_process\'), \'\');\r\n		print \'\r\n		<style type=\"text/css\">\r\n			.cxc-tpl-boxedup { display: block; width: 450px; }\r\n			.cxc-tpl-success { color: #009900; }\r\n			.cxc-tpl-failure { color: #FF0000; }\r\n			.cxc-tpl-capital { text-transform: capitalize; }\r\n			.cxc-tpl-current { border: medium ridge;float: right; margin: 0 0 0 5px; padding: 1em 1em 0; text-align: center; width: 220px; }\r\n			.cxc-tpl-preview { background: #fff; border: medium ridge; float: right; margin: 0 0 0 10px; padding: 1em 1em 0; text-align: center; width: 220px; }\r\n			.cxc-tpl-default { max-height: 200px; overflow: hidden; }\r\n			.cxc-tpl-padded { border: 1px solid; padding: 2em; }\r\n			.cxc-tpl-smaller { font-size: 80%; }\r\n		</style>\r\n\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(function()\r\n			{\r\n			  $(\".cxc-tpl-slide-body\").hide();\r\n			  $(\".cxc-tpl-slide-head\").click(function()\r\n			  {\r\n				$(this).next(\".cxc-tpl-slide-body\").slideToggle(600);\r\n			  });\r\n			});\r\n		</script>\r\n\r\n		<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\">\r\n			<tr>\r\n				<td>\r\n		\';\r\n\r\n		if (!isset($prefs[\'cxc_tpl_current\']) && !set_pref(\'cxc_tpl_current\', \'\', \'publish\', 2) && !get_pref(\'cxc_tpl_current\')) {\r\n			print \'\r\n				<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_preferences\').\'</h1>\r\n				<ul class=\"results\">\r\n					<li>\'.cxc_templates_gTxt(\'cxc_tpl_dbupdate\').\'</li>\r\n				</ul>\r\n				<br />\r\n			\';\r\n		}\r\n\r\n		$theme_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\'];\r\n		$cache_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .\'textpattern\'. DIRECTORY_SEPARATOR .$template->_config[\'cache_dir\'];\r\n		if (is_dir($theme_dir) && is_dir($cache_dir)) {\r\n\r\n			$theme_index = $theme_dir. DIRECTORY_SEPARATOR .\'index.html\';\r\n			$cache_index = $cache_dir. DIRECTORY_SEPARATOR .\'index.html\';\r\n			if (!file_exists($theme_index) || !file_exists($cache_index)) {\r\n				$template->writeIndexFiles($theme_dir);\r\n				$template->writeIndexFiles($cache_dir);\r\n			}\r\n							\r\n			switch ($step) {\r\n				case \'import\':\r\n					$import_full = ps(\'import_full\');\r\n					$template->import($import_full, ps(\'import_dir\'));\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .ps(\'import_dir\'));\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'export\':\r\n					$dir = ps(\'export_dir\');\r\n\r\n					$dir =  str_replace(\r\n								array(\' \'),\r\n								array(\'-\'),\r\n								$dir\r\n							);\r\n					$template->export($dir);\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .ps(\'export_dir\'));\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'rusure\':\r\n					$dir = $theme_dir. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n					if (ps(\'remove_dir\') != \'preimport-data\') {\r\n						$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n						if (is_dir($tpl_dir)) {\r\n							print \'<div class=\"cxc-tpl-current\">\';\r\n							$template->cxc_tpl_preview(ps(\'remove_dir\'), $tpl_dir);\r\n							print \'</div>\';\r\n						}\r\n					}\r\n\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_delete_confirm\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_remove_before\').$dir.cxc_templates_gTxt(\'cxc_tpl_remove_after\').\'</p>\r\n						\'.form(\r\n							graf(\'\'.\r\n								hInput(\'remove_dir\',ps(\'remove_dir\')).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'remove\')\r\n							)\r\n						).\'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n						\';\r\n\r\n					break;\r\n\r\n				case \'remove\':\r\n					$dir = $theme_dir. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n					if (is_dir($dir)) {\r\n						$objects = scandir($dir);\r\n						foreach ($objects as $object) {\r\n							if ($object != \'.\' && $object != \'..\') {\r\n								if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n									$template->removeDirectory($dir. DIRECTORY_SEPARATOR .$object);\r\n								} else { \r\n									@unlink($dir. DIRECTORY_SEPARATOR .$object);\r\n								}\r\n							}\r\n						}\r\n						reset($objects);\r\n						@rmdir($dir);\r\n					}\r\n					if (!is_dir($dir)){					\r\n						print \'\r\n							<h1 class=\"cxc-tpl-success\"><span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', ps(\'remove_dir\')).\'</span> \'.cxc_templates_gTxt(\'cxc_tpl_removed\').\'</h1>\r\n							<p>\'.cxc_templates_gTxt(\'cxc_tpl_the_capital\').str_replace(\'_\', \' \', ps(\'remove_dir\')).cxc_templates_gTxt(\'cxc_tpl_removed_from\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_removed_dir\').\'</p>\r\n						\';\r\n					} else {\r\n						print \'\r\n							<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_remove_failed\').\'</h1>\r\n							<p>\'.cxc_templates_gTxt(\'cxc_tpl_the_capital\').str_replace(\'_\', \' \', ps(\'remove_dir\')).cxc_templates_gTxt(\'cxc_tpl_not_removed\').\'</p>\r\n						\';\r\n					}\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'importZip\':\r\n					$adv_live = ps(\'adv_live\');\r\n					$adv_root = ps(\'adv_root\');\r\n					$import_full = ps(\'import_full\');\r\n					$tpl_alist = scandir($theme_dir);\r\n					$rel_temp_dir = $template->_config[\'cache_dir\']. DIRECTORY_SEPARATOR . $_FILES[\'file\'][\'name\'];															\r\n					move_uploaded_file($_FILES[\'file\'][\'tmp_name\'],$rel_temp_dir);\r\n					$template->importZip($adv_live, $adv_root, $rel_temp_dir,$_FILES[\'file\'][\'name\']);\r\n					$tpl_blist = scandir($theme_dir);\r\n					$newtpl = array_merge(array_diff($tpl_blist,$tpl_alist));\r\n					if ($adv_live){\r\n						if ($newtpl != \'\' && count($newtpl) == 1) {\r\n							$template->import($import_full, $newtpl[0]);\r\n							$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .$newtpl[0]);\r\n						} else {\r\n							print \'\r\n								<h1>\'.cxc_templates_gTxt(\'cxc_tpl_template_import\').\' <span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', str_replace(\'.zip\', \'\', $_FILES[\'file\'][\'name\'])).\'</span></h1>\r\n								<ul class=\"results\">\r\n									<li>\'.cxc_templates_gTxt(\'cxc_tpl_import_failed_before\').str_replace(\' \', \'-\', str_replace(\'.zip\', \'\', $_FILES[\'file\'][\'name\'])).cxc_templates_gTxt(\'cxc_tpl_import_failed_after\').\'</li>\r\n								</ul>\r\n								<br />\r\n								<p>\'.cxc_templates_gTxt(\'cxc_tpl_upload_import_fail\').\'</p>\r\n							\';\r\n						}\r\n					}\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'docs\':\r\n					$template->cxc_tpl_docs($prefs[\'cxc_tpl_current\']);\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'downzip\':\r\n					$zipdir	= ps(\'zip_dir\');\r\n					$stripz	= $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR;\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .$zipdir);\r\n					$template->cxc_tpl_downzip($theme_dir. DIRECTORY_SEPARATOR .$zipdir, $zipdir.\'.zip\', $stripz);\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				default:\r\n					$importlist = $template->getTemplateList();\r\n					$php_modules = array_map(\'strtolower\', get_loaded_extensions());\r\n\r\n					if (!empty($prefs[\'cxc_tpl_current\']) && $prefs[\'cxc_tpl_current\'] != \'preimport-data\') {\r\n						$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR .$prefs[\'cxc_tpl_current\'];\r\n						if (is_dir($tpl_dir)) {\r\n							print \'<div class=\"cxc-tpl-current\">\';\r\n							$template->cxc_tpl_current($tpl_dir);\r\n							print \'</div>\';\r\n						}\r\n					}\r\n\r\n					if (empty($importlist) || $importlist == \'\') {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_import_template\').\'</h1>\r\n							<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_none_before\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_none_after\').\'</p>\r\n							<span class=\"cxc-tpl-slide-head\">\r\n								\'.form(\r\n									graf(\'\'.\r\n										checkbox(\'show_alt\', \'show_alt\', \'0\', \'\', \'show_alt\').\' \'.cxc_templates_gTxt(\'cxc_tpl_alternate_dir\').\' &lt;/&gt;\')\r\n								).\'\r\n							</span>\r\n							<div class=\"cxc-tpl-slide-body\">\r\n								<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_alt_adjust\').\'</p>\r\n								<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_alt_note\').\'</p>\r\n							</div>\r\n						\';\r\n					} else {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_import_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_import_which\').\' <br />\'.\r\n								selectInput(\'import_dir\', $importlist, \'\', 1).\' <br />\'.\r\n								checkbox(\'import_full\', \'import_full\', \'0\', \'\', \'import_full\').\' \'.cxc_templates_gTxt(\'cxc_tpl_import_safe_mode\').\' <br />\'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'import\')\r\n							)\r\n						);\r\n					}\r\n\r\n					print \'\r\n						<h1>\'.cxc_templates_gTxt(\'cxc_tpl_export_template\').\'</h1>	\r\n					\'.form(\r\n						graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_export_name\').\' <br />\'.\r\n							fInput(\'text\', \'export_dir\', \'\').\r\n							fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n							eInput(\'cxc_templates\').sInput(\'export\')\r\n						)\r\n					);\r\n\r\n					if (!empty($importlist) && !$importlist == \'\') {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_delete_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.\r\n								selectInput(\'remove_dir\', $importlist, \'\', 1).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'rusure\')\r\n							)\r\n						);\r\n					}\r\n\r\n					if (!empty($importlist) && !$importlist == \'\' && in_array(\'zlib\', $php_modules)) {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_zip_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.\r\n								selectInput(\'zip_dir\', $importlist, \'\', 1).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'downzip\')\r\n							)\r\n						);\r\n					}\r\n\r\n					print \'\r\n						<h1>\'.cxc_templates_gTxt(\'cxc_tpl_upload_template\').\'</h1>\r\n					\';\r\n					if (in_array(\'zlib\', $php_modules)) {\r\n						print \'\r\n						\'.form(\r\n							graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_upload_select\').\' <br />\'.\r\n								fInput(\'file\', \'file\', \'\', \'\', \'\', \'\',50,\'\',\'file\').\r\n								eInput(\'cxc_templates\').sInput(\'importZip\').\' <br />\'.\r\n								checkbox(\'adv_live\', \'adv_live\', \'1\', \'\', \'adv_live\').\' \'.cxc_templates_gTxt(\'cxc_tpl_upload_import\').\' <br />\'.\r\n								checkbox(\'import_full\', \'import_full\', \'0\', \'\', \'import_full\').\' \'.cxc_templates_gTxt(\'cxc_tpl_import_safe_mode\').\' <br />\r\n								<span class=\"cxc-tpl-slide-head cxc-tpl-boxedup\"><a id=\"upload-advanced-options\">\'.cxc_templates_gTxt(\'cxc_tpl_advanced_options\').\'</a> &lt;/&gt;</span>\r\n								<span class=\"cxc-tpl-slide-body cxc-tpl-boxedup\">\'.\r\n									checkbox(\'adv_root\', \'adv_root\', \'0\', \'\', \'adv_root\').\' \'.cxc_templates_gTxt(\'cxc_tpl_root_install\').\' <br />\r\n									<strong>\'.cxc_templates_gTxt(\'cxc_tpl_advanced_note\').\'</em>\r\n								</span>\'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\',\'\',\'\')\r\n							), \'\', \'\', \'post\', \'\', str_replace(\'\\\\\', \'\', \'\\\" enctype=\\\"multipart/form-data\'), \'\'\r\n						);\r\n					} else {\r\n						print \'\r\n						<span class=\"cxc-tpl-slide-head cxc-tpl-boxedup\"><a id=\"upload-advanced-options\">\'.cxc_templates_gTxt(\'cxc_tpl_feature_unavailable\').\'</a> &lt;/&gt;</span>\r\n						<span class=\"cxc-tpl-slide-body cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_zlib_enabled_text\').\'</span>\r\n						\';\r\n					}\r\n\r\n					break;\r\n			}\r\n		} else {\r\n			$error = false;\r\n			if (!is_dir($theme_dir) && !mkdir($theme_dir, 0777)) { $error = true; }\r\n			if (is_dir($theme_dir) && !is_writable($theme_dir) && !chmod($theme_dir, 0777)) { $error = true; }\r\n			if (!is_dir($cache_dir) && !mkdir($cache_dir, 0777)) { $error = true; }\r\n			if (is_dir($cache_dir) && !is_writable($cache_dir) && !chmod($cache_dir, 0777)) { $error = true; }\r\n			if (!$error) { // no errors, let’s do your thing\r\n				print \'\r\n					<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_created\').\'</h1>\r\n					<p><a href=\"index.php?event=cxc_templates\">\'.cxc_templates_gTxt(\'cxc_tpl_reload_click\').\'</a> \'.cxc_templates_gTxt(\'cxc_tpl_reload_display\').\'</p>\r\n				\';\r\n			} else {\r\n				if (!is_dir($theme_dir) && !is_dir($cache_dir)) {\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_this\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_req_dir_theme\').$template->_config[\'cache_dir\'].cxc_templates_gTxt(\'cxc_tpl_req_dir_cache\').$theme_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_or\').$cache_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_manual\').\'</p>\r\n<pre><code>    mkdir \'.$template->_config[\'base_dir\'].\'\r\n    chmod 777 \'.$template->_config[\'base_dir\'].\'\r\n</code></pre>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_after\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_add_security\').$theme_dir.cxc_templates_gTxt(\'cxc_tpl_add_empty_dir\').\'</p>\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n				} else {\r\n					if (!is_dir($theme_dir)){\r\n						$missing_dir = $template->_config[\'base_dir\'];\r\n						$missing_loc = \'\';\r\n						$is_missing_dir = \'base_dir\';\r\n						$is_missing_loc = cxc_templates_gTxt(\'cxc_tpl_webroot \');\r\n					}else{\r\n						$missing_dir = $template->_config[\'cache_dir\'];\r\n						$missing_loc = DIRECTORY_SEPARATOR .\'textpattern\'. DIRECTORY_SEPARATOR;\r\n						$is_missing_dir = \'cache_dir\';\r\n						$is_missing_loc = \'textpattern\';\r\n					}\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_this\').$missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_directory\').$is_missing_loc.cxc_templates_gTxt(\'cxc_tpl_req_properly\').$missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_manual\').\'</p>\r\n<pre><code>    mkdir \'.$missing_loc.$missing_dir.\'\r\n    chmod 777 \'.$missing_loc.$missing_dir.\'\r\n</code></pre>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_after\').$is_missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_code\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_secure_dir\').\'</p>\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n				}\r\n			}\r\n		}\r\n\r\n		print \"\r\n				</td>\r\n			</tr>\r\n		</table>\r\n		\";\r\n	}\r\n\r\n	class cxc_template {\r\n		function cxc_template() {\r\n			global $prefs;\r\n			global $_CXC_TEMPLATES;\r\n\r\n			$this->_config = $_CXC_TEMPLATES;\r\n\r\n		/*\r\n			PRIVATE CONFIG\r\n			------------------------------------------------------\r\n		*/\r\n			$this->_config[\'root_path\']         =   $prefs[\'path_to_site\'];\r\n			$this->_config[\'full_base_path\']    =   sprintf(\r\n														\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n														$this->_config[\'root_path\'],\r\n														$this->_config[\'base_dir\']\r\n													);\r\n\r\n			$this->_config[\'error_template\']    =   \'\r\n				<h1 class=\"cxc-tpl-failure\">%s</h1>\r\n				<p>%s</p>\r\n			\';\r\n\r\n			$missing_dir_head   = cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\');\r\n			$missing_dir_text   = cxc_templates_gTxt(\'cxc_tpl_template_dir\').\'%1\\$s\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\' \'.cxc_templates_gTxt(\'cxc_tpl_would_you\').\'</p><pre><code>    mkdir %1\\$s\\n    chmod 777 %1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_should_fix\');\r\n			$cant_write_head    = cxc_templates_gTxt(\'cxc_tpl_not_writable\');\r\n			$cant_write_text    = cxc_templates_gTxt(\'cxc_tpl_chmod_write\').\'</p><pre><code>    chmod 777 %1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_problem_fix\');\r\n			$cant_read_head     = cxc_templates_gTxt(\'cxc_tpl_not_readable\');\r\n			$cant_read_text     = cxc_templates_gTxt(\'cxc_tpl_chmod_read\').\'</p><pre><code>    chmod 777 %%1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_problem_fix\');\r\n			$wrong_file_head	= cxc_templates_gTxt(\'cxc_tpl_wrong_file_type\');\r\n			$wrong_file_text	= cxc_templates_gTxt(\'cxc_tpl_corrupt_file\');\r\n\r\n			$this->_config[\'error_missing_dir\'] =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$missing_dir_head,\r\n														$missing_dir_text\r\n													);\r\n			$this->_config[\'error_cant_write\']  =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$cant_write_head,\r\n														$cant_write_text\r\n													);\r\n			$this->_config[\'error_cant_read\']   =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$cant_read_head,\r\n														$cant_read_text\r\n													);\r\n			$this->_config[\'error_wrong_file\']	=	sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$wrong_file_head,\r\n														$wrong_file_text\r\n													);\r\n	\r\n			$this->exportTypes = array(\r\n				\'css\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_css\'],\r\n									\'data\'      =>  \'css\',\r\n									\'fields\'    =>  \'name, css\',\r\n									\'nice_name\' =>  \'CSS Rules\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_css\'].\'/\',\r\n									\'sql\'       =>  \'`css` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_css\'],\r\n									\'table\'     =>  \'txp_css\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'forms\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_forms\'],\r\n									\'data\'      =>  \'Form\',\r\n									\'fields\'    =>  \'name, type, Form\',\r\n									\'nice_name\' =>  \'Form Files\',\r\n									\'regex\'     =>  \'/(.+)\\.(.+)\'.$this->_config[\'ext_forms\'].\'/\',\r\n									\'sql\'       =>  \'`Form` = \"%s\", `type` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_forms\'],\r\n									\'table\'     =>  \'txp_form\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'pages\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_pages\'],\r\n									\'data\'      =>  \'user_html\',\r\n									\'fields\'    =>  \'name, user_html\',\r\n									\'nice_name\' =>  \'Page Files\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_pages\'].\'/\',\r\n									\'sql\'       =>  \'`user_html` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_pages\'],\r\n									\'table\'     =>  \'txp_page\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'plugins\'	=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_plugins\'],\r\n									\'data\'      =>  \'code\',\r\n									\'fields\'    =>  \'name, status, author, author_uri, version, description, help, code, code_restore, code_md5, type\',\r\n									\'nice_name\' =>  \'Plugin Files\',\r\n									\'regex\'     =>  \'/(.+)\\.(.+)\'.$this->_config[\'ext_plugins\'].\'/\',\r\n									\'sql\'       =>  \'`status` = %d, `author` = \"%s\", `author_uri` = \"%s\", `version` = \"%s\", `description` = \"%s\", `help` = \"%s\", `code` = \"%s\", `code_restore` = \"%s\", `code_md5` = \"%s\", `type` = %d\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_plugins\'],\r\n									\'table\'     =>  \'txp_plugin\',\r\n									\'filter\'    =>	\'`status` = 1\'\r\n								),\r\n				\'sections\'	=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_section\'],\r\n									\'data\'      =>  \'section\',\r\n									\'fields\'    =>  \'name, page, css, in_rss, on_frontpage, searchable, title\',\r\n									\'nice_name\' =>  \'Section Parameters\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_section\'].\'/\',\r\n									\'sql\'       =>  \'`page` = \"%s\", `css` = \"%s\", `in_rss` = \"%d\", `on_frontpage` = \"%d\", `searchable` = \"%d\", `title` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_sections\'],\r\n									\'table\'     =>  \'txp_section\',\r\n									\'filter\'    =>	\'1=1\'\r\n								)\r\n			);\r\n		}\r\n\r\n		function checkdir($dir = \'\', $type = _CXC_TEMPLATES_EXPORT) {\r\n			/*\r\n				If $type == _EXPORT, then:\r\n					1.  Check to see that /base/path/$dir exists, and is\r\n						writable.  If not, create it.\r\n					2.  Check to see that /base/path/$dir/subdir_* exist,\r\n						and are writable.  If not, create them.\r\n\r\n				If $type == _IMPORT, then:\r\n					1.  Check to see that /base/path/$dir exists, and is readable.\r\n					2.  Check to see that /base/path/$dir/subdir_* exist, and are readable.\r\n			*/\r\n			$dir =  sprintf(\r\n						\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n						$this->_config[\'full_base_path\'],\r\n						$dir\r\n					);\r\n\r\n			$tocheck =  array(\r\n							$dir,\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_css\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_forms\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_pages\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_plugins\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_sections\']\r\n						);\r\n			foreach ($tocheck as $curDir) {\r\n				switch ($type) {\r\n					case _CXC_TEMPLATES_IMPORT:\r\n						if (!is_dir($curDir) && !mkdir($curDir, 0777)) {\r\n							echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n							return false;\r\n						}\r\n						if (is_dir($curDir) && !is_readable($curDir)) {\r\n							echo sprintf($this->_config[\'error_cant_read\'], $curDir);\r\n							return false;\r\n						}\r\n						break;\r\n\r\n					case _CXC_TEMPLATES_EXPORT:\r\n						if (!is_dir($curDir) && !mkdir($curDir, 0777)) {\r\n								echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n								return false;\r\n						}\r\n						if (is_dir($curDir) && !is_writable($curDir) && !chmod($theme_dir, 0777)) {\r\n							echo sprintf($this->_config[\'error_cant_write\'], $curDir);\r\n							return false;\r\n						}\r\n						break;\r\n				}\r\n			}\r\n			return true;\r\n		}\r\n\r\n		function checkdirImportZip() {\r\n\r\n			$dir =  $this->_config[\'full_base_path\'];\r\n\r\n			if (!is_dir($dir)) {\r\n			   echo sprintf($this->_config[\'error_missing_dir\'], $dir);\r\n			   return false;\r\n			}\r\n			if (!is_readable($dir)) {\r\n			   echo sprintf($this->_config[\'error_cant_read\'], $dir);\r\n			   return false;\r\n			}\r\n			return true;\r\n		}\r\n\r\n		function checkFileTypeZip() {\r\n			if ($_FILES[\'file\'][\'type\'] != \'application/zip\') {\r\n			   echo sprintf($this->_config[\'error_wrong_text\']);\r\n			   return false;\r\n			}\r\n			return true;\r\n		}\r\n\r\n		/*\r\n			EXPORT FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n		function export($dir = \'\') {\r\n			if (!$this->checkdir($dir, _CXC_TEMPLATES_EXPORT)) {\r\n				return;\r\n			}\r\n\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"exporting-details\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_export_current\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n				<blockquote>\r\n			\';\r\n\r\n			foreach ($this->exportTypes as $type => $config) {\r\n				print \'\r\n					<h1>\'.$config[\'nice_name\'].\'</h1>\r\n					<ul class=\"results\">\r\n				\';\r\n\r\n				$rows = safe_rows($config[\'fields\'], $config[\'table\'], $config[\'filter\']);\r\n\r\n				foreach ($rows as $row) {\r\n					$filename		=	sprintf(\r\n											\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s%s\',\r\n											$this->_config[\'full_base_path\'],\r\n											$dir,\r\n											$config[\'subdir\'],\r\n											$row[\'name\'] . (isset($row[\'type\'])?\'.\'.$row[\'type\']:\'\'),\r\n											$config[\'ext\']\r\n										);\r\n					$nicefilename	=	sprintf(\r\n											\'...\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s%s\',\r\n											$dir,\r\n											$config[\'subdir\'],\r\n											$row[\'name\'] . (isset($row[\'type\'])?\'.\'.$row[\'type\']:\'\'),\r\n											$config[\'ext\']\r\n										);\r\n\r\n					$data = \'\';\r\n\r\n					if (isset($row[\'css\'])) {\r\n						$data = $row[\'css\'];\r\n					} elseif ($type==\'plugins\') {\r\n						$data = base64_encode(serialize($row));\r\n					} elseif ($config[\'subdir\'] != \'sections\') {\r\n						$data = $row[$config[\'data\']];\r\n					}\r\n\r\n					$f = @fopen($filename, \'w+\');\r\n					if ($f) {\r\n						if ($config[\'subdir\'] == \'sections\'){\r\n							$this->writeSectionFiles($f,$row);\r\n						} else {\r\n							fwrite($f,$data);\r\n						}\r\n						fclose($f);\r\n						print \'\r\n						<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_export_success\').\'</span> \'.$config[\'nice_name\'].\' \\\'\'.$row[\'name\'].\'\\\' to \\\'\'.$nicefilename.\'\\\'</li>\r\n						\';\r\n					} else {\r\n						print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_export_failed\').\'</span> \'.$config[\'nice_name\'].\' \\\'\'.$row[\'name\'].\'\\\' to \\\'\'.$nicefilename.\'\\\'</li>\r\n						\';\r\n					}\r\n				}\r\n				print \'\r\n					</ul>\r\n				<br />\r\n				\';\r\n			}\r\n			print \'\r\n				</blockquote>\r\n				</div>\r\n			\';\r\n		}\r\n\r\n		function writeSectionFiles($f,$row){\r\n			if ($row[\'name\'] == \'default\'){\r\n				$name = \"name=default\\n\";\r\n				$page = \"page=\".$row[\'page\'].\"\\n\";\r\n				$css = \"css=\".$row[\'css\'].\"\\n\";\r\n				$in_rss = \"in_rss=1\\n\";\r\n				$on_frontpage = \"on_frontpage=1\\n\";\r\n				$searchable = \"searchable=1\\n\";\r\n				$title = \"title=default\";\r\n				fwrite($f,$name);\r\n				fwrite($f,$page);\r\n				fwrite($f,$css);\r\n				fwrite($f,$in_rss);\r\n				fwrite($f,$on_frontpage);\r\n				fwrite($f,$searchable);\r\n				fwrite($f,$title);\r\n			}else{\r\n				$name = \"name=\".$row[\'name\'].\"\\n\";\r\n				$page = \"page=\".$row[\'page\'].\"\\n\";\r\n				$css = \"css=\".$row[\'css\'].\"\\n\";\r\n				$in_rss = \"in_rss=\".$row[\'in_rss\'].\"\\n\";\r\n				$on_frontpage = \"on_frontpage=\".$row[\'on_frontpage\'].\"\\n\";\r\n				$searchable = \"searchable=\".$row[\'searchable\'].\"\\n\";\r\n				$title = \"title=\".$row[\'title\'];\r\n				fwrite($f,$name);\r\n				fwrite($f,$page);\r\n				fwrite($f,$css);\r\n				fwrite($f,$in_rss);\r\n				fwrite($f,$on_frontpage);\r\n				fwrite($f,$searchable);\r\n				fwrite($f,$title);\r\n			}\r\n		}\r\n\r\n		/*\r\n			IMPORT FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n		function getTemplateList() {\r\n			if (!is_readable($this->_config[\'full_base_path\'])) {\r\n				return array();\r\n			}\r\n			$list = \'\';\r\n			$dir = opendir($this->_config[\'full_base_path\']);\r\n\r\n			while(false !== ($filename = readdir($dir))) {\r\n				if (\r\n					is_dir(\r\n						sprintf(\r\n							\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$filename\r\n						)\r\n					) && $filename != \'.\' && $filename != \'..\'\r\n				) {\r\n					$list[$filename] = $filename;\r\n				}\r\n			}\r\n\r\n			return $list;\r\n		}\r\n\r\n		function import($import_full, $dir) {\r\n			if (!$this->checkdir($dir, _CXC_TEMPLATES_IMPORT)) {\r\n				return;\r\n			}\r\n			$basedir =  sprintf(\r\n							\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$dir\r\n						);\r\n			if (!set_pref(\'cxc_tpl_current\', $dir, \'publish\', 2)){\r\n				print \'\r\n					<ul class=\"results\">\r\n						<li>\'.cxc_templates_gTxt(\'cxc_tpl_update_unable\').\' \'.str_replace(\'_\', \' \', $dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_update_none\').\'</li>\r\n					</ul>\r\n					<br />\r\n				\';\r\n			}\r\n			if (file_exists($basedir. DIRECTORY_SEPARATOR .\'README.txt\')){\r\n				print \'\r\n				<div>\'.\r\n					file_get_contents($basedir. DIRECTORY_SEPARATOR .\'README.txt\').\'\r\n				</div>\r\n				<br />\r\n				\';\r\n			}\r\n\r\n			/*\r\n				Auto export into `preimport-data`\r\n			*/\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"processing-backup\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_processing\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n			\';\r\n			$pre_dir = $this->_config[\'full_base_path\']. DIRECTORY_SEPARATOR .\'preimport-data\';\r\n			if (is_dir($pre_dir)) {\r\n				$objects = scandir($pre_dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($pre_dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->removeDirectory($pre_dir. DIRECTORY_SEPARATOR .$object);\r\n						} else { \r\n							@unlink($pre_dir. DIRECTORY_SEPARATOR .$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n				@rmdir($pre_dir);\r\n			}\r\n			if (!is_dir($pre_dir)){					\r\n				print \'\r\n					<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_remove\').$this->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_pre_newback\').\'</p>\r\n				\';\r\n			} else {\r\n				print \'\r\n					<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_failed\').\'</p>\r\n				\';\r\n			}\r\n			print \'\r\n				<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_note\').\'</p>\r\n				</div>\r\n			\';\r\n\r\n			$this->export(\'preimport-data\');\r\n\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"importing-details\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_template_import\').\' <span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', $dir).\'</span></a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n				<blockquote>\r\n			\';\r\n\r\n			foreach ($this->exportTypes as $type => $config) {\r\n				print \'\r\n					<h1>\'.$config[\'nice_name\'].\'</h1>\r\n					<ul class=\"results\">\r\n				\';\r\n\r\n				$exportdir =    sprintf(\r\n									\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n									$basedir,\r\n									$config[\'subdir\']\r\n								);\r\n\r\n				$dir	= opendir($exportdir);\r\n				while (false !== ($filename = readdir($dir))) {\r\n					if (preg_match($config[\'regex\'], $filename, $filedata)) {\r\n						$templateName = addslashes($filedata[1]);\r\n						$templateType = (isset($filedata[2]))?$filedata[2]:\'\';\r\n\r\n						$f =    sprintf(\r\n									\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n									$exportdir,\r\n									$filename\r\n								);\r\n\r\n						if ($data = file($f)) {\r\n							if ($type == \'css\') {\r\n								$data = doSlash(implode(\'\', $data));\r\n							} elseif ($type == \'plugins\') {\r\n								$data = doSlash(unserialize(base64_decode(implode(\'\', $data))));\r\n							} elseif ($type != \'sections\') {\r\n								$data = addslashes(implode(\'\', $data));\r\n							}\r\n\r\n							if ($type == \'plugins\') {\r\n								$rs = safe_row(\'version, status\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\');\r\n								$set = sprintf($config[\'sql\'], $data[\'status\'], $data[\'author\'], $data[\'author_uri\'], $data[\'version\'], $data[\'description\'], $data[\'help\'], $data[\'code\'], $data[\'code_restore\'], $data[\'code_md5\'], $data[\'type\']);\r\n								if ($rs) {\r\n									if ($rs[\'status\'] == 0 || strcasecmp($data[\'version\'], $rs[\'version\']) < 0) {\r\n										$result = safe_update($config[\'table\'], $set, \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = 1;\r\n									}\r\n									$success = ($result)?1:0;\r\n								} else {\r\n									$result = safe_insert($config[\'table\'], $set.\', `name` = \"\'.$templateName.\'\"\');\r\n									$success = ($result)?1:0;\r\n								}\r\n							} elseif ($type == \'sections\') {\r\n								$rs = safe_row(\'page, css, in_rss, on_frontpage, searchable, title\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\');\r\n								$set = $this->parseSectionFile($config[\'sql\'], $data, $filename, $config[\'ext\']);\r\n								if ($import_full == 0){\r\n									if ($rs) {\r\n										$result = safe_update($config[\'table\'], $set, \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = safe_insert($config[\'table\'], $set.\', `name` = \"\'.$templateName.\'\"\');\r\n									}\r\n								} else {\r\n									$result = 1;\r\n								}\r\n								$success = ($result)?1:0;\r\n							} else {\r\n								if (safe_field(\'name\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\')) {\r\n									if ($import_full == 0){\r\n										$result = safe_update($config[\'table\'], sprintf($config[\'sql\'], $data, $templateType), \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = 0;\r\n									}\r\n									$success = ($result)?1:0;\r\n								} else {\r\n									$result = safe_insert($config[\'table\'], sprintf($config[\'sql\'], $data, $templateType).\', `name` = \"\'.$templateName.\'\"\');\r\n									$success = ($result)?1:0;\r\n								}\r\n							}\r\n						}\r\n\r\n						//$success = true;\r\n						if ($success == 1) {\r\n							print \'\r\n						<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_import_success\').\'</span> file \"\'.$filename.\'\"</li>\r\n							\';\r\n						} else {\r\n							if ($type == \'sections\' && $import_full == 1){\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_skipped\').\'</span> file \"\'.$filename.\'\"</li>\r\n								\';\r\n							} elseif ($import_full == 1){\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_skipped\').\'</span> \"\'.$filename.cxc_templates_gTxt(\'cxc_tpl_import_present\').\'</li>\r\n								\';\r\n							} else {\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_failed\').\'</span> file \"\'.$filename.\'\"</li>\r\n								\';\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				print \'\r\n					</ul>\r\n					<br />\r\n				\';\r\n			}\r\n			print \'\r\n				</blockquote>\r\n				</div>\r\n			\';\r\n			if (file_exists($basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\')){\r\n				print \'\r\n				<h1 class=\"cxc-tpl-slide-head\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_add_click\').\'\"><a id=\"additional-info\">\'.cxc_templates_gTxt(\'cxc_tpl_add_info\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\'.\r\n					file_get_contents($basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\').\'\r\n				</div>\r\n				\';\r\n			}\r\n		}\r\n\r\n		function importZip($adv_live, $adv_root, $rel_temp_dir, $fileName) {\r\n			global $prefs;\r\n\r\n			if (!$this->checkdirImportZip()) {\r\n					unlink($rel_temp_dir);\r\n				return;\r\n			}\r\n\r\n			if (!$adv_root){\r\n				$templates_base_dir = $this->_config[\'full_base_path\'];\r\n			} else {\r\n				$templates_base_dir = $prefs[\'path_to_site\'];\r\n			}\r\n			$full_temp_dir = $this->_config[\'root_path\']. DIRECTORY_SEPARATOR .\"textpattern\". DIRECTORY_SEPARATOR .$rel_temp_dir;\r\n\r\n			print \'\r\n				<ul class=\"results\">\r\n			\';\r\n			$zip = new cxc_dUnzip2($full_temp_dir);\r\n			if (!empty($zip)) {\r\n				$zip->unzipAll($templates_base_dir);\r\n				$zip->__destroy($full_temp_dir);\r\n				@unlink($full_temp_dir);\r\n				print \'<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_upload_success\').\'</span> file \"\'.$fileName.\'\"</li>\';\r\n			} else {\r\n				$zip->__destroy($full_temp_dir);\r\n				@unlink($full_temp_dir);\r\n				print \'<li>\'.cxc_templates_gTxt(\'cxc_tpl_move_fail\').$fileName.cxc_templates_gTxt(\'cxc_tpl_move_temp\').\'</li>\';\r\n			}\r\n			print \'\r\n				</ul>\r\n				<br />\r\n			\';\r\n		}\r\n\r\n		function parseSectionFile($sql,$data,$fname,$ext) {\r\n			$sectionValues = array  (\r\n					// if section title is not within the file, use the filename without the extension\r\n					\'title\'			=> substr($fname,0,-strlen($ext)),\r\n					\'page\'			=> \'default\',\r\n					\'css\'			=> \'default\',\r\n					\'in_rss\'		=> 1,\r\n					\'on_frontpage\'	=> 1,\r\n					\'searchable\'	=> 1,\r\n				);\r\n\r\n			foreach($data as $line) {\r\n				// Split the \'attribute = value\' from within the section parameters file.\r\n				// Ignore whitespace surrounding both the attribute and the value.\r\n				// Limit the split to 2 values (just in case the right part contains another \'=\'\r\n				// which is very unlikely, anyway.\r\n				$splitText = split(\'=\',$line,2);\r\n				$sectionParameter = trim($splitText[0]);\r\n				$sectionValues[$sectionParameter] = trim($splitText[1]);\r\n			}\r\n\r\n			$sectionLine = sprintf($sql, $sectionValues[\'page\'], $sectionValues[\'css\'], $sectionValues[\'in_rss\'], $sectionValues[\'on_frontpage\'], $sectionValues[\'searchable\'], $sectionValues[\'title\']);\r\n			return $sectionLine;\r\n		}\r\n\r\n		/*\r\n			OTHER FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n\r\n		function cxc_tpl_current($tpl_dir){\r\n			global $prefs;\r\n\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			$readme = $tpl_dir. DIRECTORY_SEPARATOR .\'README.txt\';\r\n			$design = $tpl_dir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\';\r\n			\r\n			if (!empty($prefs[\'cxc_tpl_current\']) && is_dir($tpl_dir)){\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.png\';\r\n				}\r\n	\r\n				print \'<h2 class=\"cxc-tpl-capital\">\'.str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_template\').\'</h2>\';\r\n				if (isset($tpl_preview)) {\r\n					print \'<p class=\"cxc-tpl-default\"><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n				if (file_exists($readme) || file_exists($design)) {\r\n					print form(\'\'.\r\n							graf(\r\n								fInput(\'submit\', \'go\', \'\'.cxc_templates_gTxt(\'cxc_tpl_preview_docs\').\'\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'docs\')\r\n							)\r\n					);\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-smaller\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_note\').str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).cxc_templates_gTxt(\'cxc_tpl_preview_last\').\'</p>\';					\r\n				}\r\n			}\r\n		}\r\n\r\n		function cxc_tpl_preview($dir, $tpl_dir){\r\n			global $prefs;\r\n\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			if (is_dir($tpl_dir)){\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.png\';\r\n				}\r\n	\r\n				if ($dir == \'\') {\r\n					print \'\r\n					<h2>\'.cxc_templates_gTxt(\'cxc_tpl_top_level\').str_replace(\'_\',\' \',$this->_config[\'base_dir\']).cxc_templates_gTxt(\'cxc_tpl_top_dir\').\'</h2>\r\n					<p class=\"cxc-tpl-smaller\">\'.cxc_templates_gTxt(\'cxc_tpl_top_remove\').\'</p>				\r\n					\';\r\n				} else {\r\n					print \'<h2 class=\"cxc-tpl-capital\">\'.str_replace(\'_\',\' \',$dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_template\').\'</h2>\';\r\n				}\r\n				if (isset($tpl_preview)) {\r\n					print \'<p class=\"cxc-tpl-default\"><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n			}\r\n		}\r\n\r\n		function cxc_tpl_docs($tpl_dir){\r\n			global $prefs;\r\n\r\n			$basedir =  sprintf(\r\n							\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$tpl_dir\r\n						);\r\n			$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$this->_config[\'base_dir\']. DIRECTORY_SEPARATOR .$prefs[\'cxc_tpl_current\'];\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			$readme = $basedir. DIRECTORY_SEPARATOR .\'README.txt\';\r\n			$design = $basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\';\r\n\r\n			if (!empty($prefs[\'cxc_tpl_current\']) && $prefs[\'cxc_tpl_current\'] != \'preimport-data\') {\r\n				print \'\r\n					<div class=\"cxc-tpl-preview\">\r\n				\';\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.png\';\r\n				}\r\n	\r\n				print \'<h2 class=\"cxc-tpl-capital\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_img\').\'</h2>\';\r\n				if (isset($tpl_preview)) {\r\n					print \'<p><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n				print \'\r\n					<br />\r\n					</div>\r\n				\';\r\n			}\r\n			if (file_exists($readme)){\r\n				print \'\r\n					<div>\'.\r\n						file_get_contents($readme).\'\r\n					</div>\r\n					<br />\r\n				\';\r\n			}\r\n			if (file_exists($design)){\r\n				print \'\r\n					<h1>\'.cxc_templates_gTxt(\'cxc_tpl_add_info\').\'</h1>\r\n					<div>\'.\r\n						file_get_contents($design).\'\r\n					</div>\r\n					<br />\r\n				\';\r\n			}			\r\n		}\r\n\r\n		function cxc_tpl_downzip($folder, $to=\'archive.zip\', $basedir) {\r\n			$zip = new cxc_dZip($to, $overwrite=true);\r\n			$php_modules = array_map(\'strtolower\', get_loaded_extensions());\r\n			if (in_array(\'zlib\', $php_modules)) {\r\n				$found = array(rtrim($folder,DIRECTORY_SEPARATOR.\'\\/\'));\r\n				while ($path = each($found)) {\r\n					$path = current($path);\r\n					if (is_dir($path)) {\r\n						foreach (scandir($path) as $subpath) {\r\n							if ($subpath==\'.\'||$subpath==\'..\'||substr($subpath,-2)==DIRECTORY_SEPARATOR.\'.\'||substr($subpath,-3)==DIRECTORY_SEPARATOR.\'..\') continue;\r\n							$found[] = $path.DIRECTORY_SEPARATOR.$subpath;\r\n						}\r\n					} else {\r\n						$zip->addFile($path, substr($path, strlen($basedir)));\r\n					}\r\n				}\r\n				$zip->save();\r\n				if (file_exists($to)) {\r\n					header (\"Content-Type: application/zip\");\r\n					header (\"Content-Disposition: attachment; filename=$to\");\r\n					header (\"Pragma: no-cache\");\r\n					header (\"Expires: 0\");\r\n					if (!readfile($to)){\r\n						print cxc_templates_gTxt(\'cxc_tpl_error_zip_dir\');\r\n					}\r\n					if (!unlink($to)) {\r\n						print cxc_templates_gTxt(\'cxc_tpl_error_zip_remove\');\r\n					}\r\n		\r\n					return true;\r\n				} else {\r\n					print cxc_templates_gTxt(\'cxc_tpl_error_zip_final\');\r\n				}\r\n			} else {\r\n				print cxc_templates_gTxt(\'cxc_tpl_error_zip_failed\').\' \'.$to.\' \'.cxc_templates_gTxt(\'cxc_tpl_error_zip_archive\');\r\n			}\r\n			return false;\r\n		}\r\n\r\n		function removeDirectory($dir) {\r\n			if (is_dir($dir)) {\r\n				$objects = scandir($dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->removeDirectory($dir.\'/\'.$object);\r\n						} else { \r\n							@unlink($dir. DIRECTORY_SEPARATOR .$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n				@rmdir($dir);\r\n			}\r\n		}\r\n\r\n		function writeIndexFiles($dir) {\r\n			if (is_dir($dir)) {\r\n				if (!file_exists($dir. DIRECTORY_SEPARATOR .\'index.html\')) {\r\n					$f = @fopen($dir. DIRECTORY_SEPARATOR .\'index.html\', \'x+\');\r\n					if ($f) {\r\n						fwrite($f,\'<html><body bgcolor=\"#FFFFFF\"></body></html>\');\r\n						fclose($f);\r\n					}\r\n				}\r\n				$objects = scandir($dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->writeIndexFiles($dir.\'/\'.$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n			}\r\n		}\r\n	}\r\n\r\n##############################################################\r\n# Class dUnzip2 v2.62\r\n#\r\n#  Author: Alexandre Tedeschi (d)\r\n#  E-Mail: alexandrebr at gmail dot com\r\n#  Londrina - PR / Brazil\r\n#\r\n#  Objective:\r\n#    This class allows programmer to easily unzip files on the fly.\r\n#\r\n#  Requirements:\r\n#    This class requires extension ZLib Enabled. It is default\r\n#    for most site hosts around the world, and for the PHP Win32 dist.\r\n#\r\n#  To do:\r\n#   * Error handling\r\n#   * Write a PHP-Side gzinflate, to completely avoid any external extensions\r\n#   * Write other decompress algorithms\r\n#\r\n#  Methods:\r\n#  * dUnzip2($filename)         - Constructor - Opens $filename\r\n#  * getList([$stopOnFile])     - Retrieve the file list\r\n#  * getExtraInfo($zipfilename) - Retrieve more information about compressed file\r\n#  * getZipInfo([$entry])       - Retrieve ZIP file details.\r\n#  * unzip($zipfilename, [$outfilename, [$applyChmod]]) - Unzip file\r\n#  * unzipAll([$outDir, [$zipDir, [$maintainStructure, [$applyChmod]]]])\r\n#  * close()                    - Close file handler, but keep the list\r\n#  * __destroy()                - Close file handler and release memory\r\n#\r\n#  If you modify this class, or have any ideas to improve it, please contact me!\r\n#  You are allowed to redistribute this class, if you keep my name and contact e-mail on it.\r\n#\r\n#  PLEASE! IF YOU USE THIS CLASS IN ANY OF YOUR PROJECTS, PLEASE LET ME KNOW!\r\n#  If you have problems using it, don\'t think twice before contacting me!\r\n#\r\n##############################################################\r\n\r\n	if(!function_exists(\'file_put_contents\')){\r\n		// If not PHP5, creates a compatible function\r\n		Function file_put_contents($file, $data){\r\n			if($tmp = fopen($file, \"w\")){\r\n				fwrite($tmp, $data);\r\n				fclose($tmp);\r\n				return true;\r\n			}\r\n			echo \"<b>file_put_contents:</b> Cannot create file $file<br>\";\r\n			return false;\r\n		}\r\n	}\r\n	\r\n	class cxc_dUnzip2{\r\n		function getVersion(){\r\n			return \"2.62\";\r\n		}\r\n		// Public\r\n		var $fileName;\r\n		var $lastError;\r\n		var $compressedList; // You will problably use only this one!\r\n		var $centralDirList; // Central dir list... It\'s a kind of \'extra attributes\' for a set of files\r\n		var $endOfCentral;   // End of central dir, contains ZIP Comments\r\n		var $debug;\r\n	\r\n		// Private\r\n		var $fh;\r\n		var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n		var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n		var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n	\r\n		// Public\r\n		function cxc_dUnzip2($fileName){\r\n			$this->fileName       = $fileName;\r\n			$this->compressedList =\r\n			$this->centralDirList =\r\n			$this->endOfCentral   = Array();\r\n		}\r\n	\r\n		function getList($stopOnFile=false){\r\n			if(sizeof($this->compressedList)){\r\n				$this->debugMsg(1, \"Returning already loaded file list.\");\r\n				return $this->compressedList;\r\n			}\r\n	\r\n			// Open file, and set file handler\r\n			$fh = fopen($this->fileName, \"r\");\r\n			$this->fh = &$fh;\r\n			if(!$fh){\r\n				$this->debugMsg(2, \"Failed to load file.\");\r\n				return false;\r\n			}\r\n	\r\n			$this->debugMsg(1, \"Loading list from \'End of Central Dir\' index list...\");\r\n			if(!$this->_loadFileListByEOF($fh, $stopOnFile)){\r\n				$this->debugMsg(1, \"Failed! Trying to load list looking for signatures...\");\r\n				if(!$this->_loadFileListBySignatures($fh, $stopOnFile)){\r\n					$this->debugMsg(1, \"Failed! Could not find any valid header.\");\r\n					$this->debugMsg(2, \"ZIP File is corrupted or empty\");\r\n					return false;\r\n				}\r\n			}\r\n	\r\n			if($this->debug){\r\n				#------- Debug compressedList\r\n				$kkk = 0;\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n				foreach($this->compressedList as $fileName=>$item){\r\n					if(!$kkk && $kkk=1){\r\n						echo \"<tr style=\'background: #ADA\'>\";\r\n						foreach($item as $fieldName=>$value)\r\n							echo \"<td>$fieldName</td>\";\r\n						echo \'</tr>\';\r\n					}\r\n					echo \"<tr style=\'background: #CFC\'>\";\r\n					foreach($item as $fieldName=>$value){\r\n						if($fieldName == \'lastmod_datetime\')\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n						else\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n					}\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n	\r\n				#------- Debug centralDirList\r\n				$kkk = 0;\r\n				if(sizeof($this->centralDirList)){\r\n					echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n					foreach($this->centralDirList as $fileName=>$item){\r\n						if(!$kkk && $kkk=1){\r\n							echo \"<tr style=\'background: #AAD\'>\";\r\n							foreach($item as $fieldName=>$value)\r\n								echo \"<td>$fieldName</td>\";\r\n							echo \'</tr>\';\r\n						}\r\n						echo \"<tr style=\'background: #CCF\'>\";\r\n						foreach($item as $fieldName=>$value){\r\n							if($fieldName == \'lastmod_datetime\')\r\n								echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n							else\r\n								echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n						}\r\n						echo \"</tr>\";\r\n					}\r\n					echo \"</table>\";\r\n				}\r\n	\r\n				#------- Debug endOfCentral\r\n				$kkk = 0;\r\n				if(sizeof($this->endOfCentral)){\r\n					echo \"<table border=\'0\' style=\'font: 11px Verdana\' style=\'border: 1px solid #000\'>\";\r\n					echo \"<tr style=\'background: #DAA\'><td colspan=\'2\'>dUnzip - End of file</td></tr>\";\r\n					foreach($this->endOfCentral as $field=>$value){\r\n						echo \"<tr>\";\r\n						echo \"<td style=\'background: #FCC\'>$field</td>\";\r\n						echo \"<td style=\'background: #FDD\'>$value</td>\";\r\n						echo \"</tr>\";\r\n					}\r\n					echo \"</table>\";\r\n				}\r\n			}\r\n	\r\n			return $this->compressedList;\r\n		}\r\n		function getExtraInfo($compressedFileName){\r\n			return\r\n				isset($this->centralDirList[$compressedFileName])?\r\n				$this->centralDirList[$compressedFileName]:\r\n				false;\r\n		}\r\n		function getZipInfo($detail=false){\r\n			return $detail?\r\n				$this->endOfCentral[$detail]:\r\n				$this->endOfCentral;\r\n		}\r\n	\r\n		function unzip($compressedFileName, $targetFileName=false, $applyChmod=0777){\r\n			if(!sizeof($this->compressedList)){\r\n				$this->debugMsg(1, \"Trying to unzip before loading file list... Loading it!\");\r\n				$this->getList(false, $compressedFileName);\r\n			}\r\n	\r\n			$fdetails = &$this->compressedList[$compressedFileName];\r\n			if(!isset($this->compressedList[$compressedFileName])){\r\n				$this->debugMsg(2, \"File \'<b>$compressedFileName</b>\' is not compressed in the zip.\");\r\n				return false;\r\n			}\r\n			if(substr($compressedFileName, -1) == \"/\"){\r\n				$this->debugMsg(2, \"Trying to unzip a folder name \'<b>$compressedFileName</b>\'.\");\r\n				return false;\r\n			}\r\n			if(!$fdetails[\'uncompressed_size\']){\r\n				$this->debugMsg(1, \"File \'<b>$compressedFileName</b>\' is empty.\");\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, \"\"):\r\n					\"\";\r\n			}\r\n	\r\n			fseek($this->fh, $fdetails[\'contents-startOffset\']);\r\n			$ret = $this->uncompress(\r\n					fread($this->fh, $fdetails[\'compressed_size\']),\r\n					$fdetails[\'compression_method\'],\r\n					$fdetails[\'uncompressed_size\'],\r\n					$targetFileName\r\n				);\r\n			if($applyChmod && $targetFileName)\r\n				chmod($targetFileName, 0777);\r\n			\r\n			return $ret;\r\n		}\r\n		function unzipAll($targetDir=false, $baseDir=\"\", $maintainStructure=true, $applyChmod=0777){\r\n			if($targetDir === false)\r\n				$targetDir = dirname($_SERVER[\'SCRIPT_FILENAME\']).\"/\";\r\n			\r\n			$lista = $this->getList();\r\n			if(sizeof($lista)) foreach($lista as $fileName=>$trash){\r\n				$dirname  = dirname($fileName);\r\n				$outDN    = \"$targetDir/$dirname\";\r\n				\r\n				if(substr($dirname, 0, strlen($baseDir)) != $baseDir)\r\n					continue;\r\n				\r\n				if(!is_dir($outDN) && $maintainStructure){\r\n					$str = \"\";\r\n					$folders = explode(\"/\", $dirname);\r\n					foreach($folders as $folder){\r\n						$str = $str?\"$str/$folder\":$folder;\r\n						if(!is_dir(\"$targetDir/$str\")){\r\n							$this->debugMsg(1, \"Creating folder: $targetDir/$str\");\r\n							mkdir(\"$targetDir/$str\");\r\n							if($applyChmod)\r\n								chmod(\"$targetDir/$str\", $applyChmod);\r\n						}\r\n					}\r\n				}\r\n				if(substr($fileName, -1, 1) == \"/\")\r\n					continue;\r\n	\r\n				$maintainStructure?\r\n					$this->unzip($fileName, \"$targetDir/$fileName\", $applyChmod):\r\n					$this->unzip($fileName, \"$targetDir/\".basename($fileName), $applyChmod);\r\n			}\r\n		}\r\n		\r\n		function close(){     // Free the file resource\r\n			if($this->fh)\r\n				fclose($this->fh);\r\n		}\r\n		function __destroy(){ \r\n			$this->close();\r\n		}\r\n	\r\n		// Private (you should NOT call these methods):\r\n		function uncompress(&$content, $mode, $uncompressedSize, $targetFileName=false){\r\n			switch($mode){\r\n				case 0:\r\n					// Not compressed\r\n					return $targetFileName?\r\n						file_put_contents($targetFileName, $content):\r\n						$content;\r\n				case 1:\r\n					$this->debugMsg(2, \"Shrunk mode is not supported... yet?\");\r\n					return false;\r\n				case 2:\r\n				case 3:\r\n				case 4:\r\n				case 5:\r\n					$this->debugMsg(2, \"Compression factor \".($mode-1).\" is not supported... yet?\");\r\n					return false;\r\n				case 6:\r\n					$this->debugMsg(2, \"Implode is not supported... yet?\");\r\n					return false;\r\n				case 7:\r\n					$this->debugMsg(2, \"Tokenizing compression algorithm is not supported... yet?\");\r\n					return false;\r\n				case 8:\r\n					// Deflate\r\n					return $targetFileName?\r\n						file_put_contents($targetFileName, gzinflate($content, $uncompressedSize)):\r\n						gzinflate($content, $uncompressedSize);\r\n				case 9:\r\n					$this->debugMsg(2, \"Enhanced Deflating is not supported... yet?\");\r\n					return false;\r\n				case 10:\r\n					$this->debugMsg(2, \"PKWARE Date Compression Library Impoloding is not supported... yet?\");\r\n					return false;\r\n			   case 12:\r\n				   // Bzip2\r\n				   return $targetFileName?\r\n					   file_put_contents($targetFileName, bzdecompress($content)):\r\n					   bzdecompress($content);\r\n				case 18:\r\n					$this->debugMsg(2, \"IBM TERSE is not supported... yet?\");\r\n					return false;\r\n				default:\r\n					$this->debugMsg(2, \"Unknown uncompress method: $mode\");\r\n					return false;\r\n			}\r\n		}\r\n		function debugMsg($level, $string){\r\n			if($this->debug){\r\n				if($level == 1)\r\n					echo \"<b style=\'color: #777\'>dUnzip2:</b> $string<br>\";\r\n				\r\n				if($level == 2)\r\n					echo \"<b style=\'color: #F00\'>dUnzip2:</b> $string<br>\";\r\n			}\r\n			$this->lastError = $string;\r\n		}\r\n		function getLastError(){\r\n			return $this->lastError;\r\n		}\r\n	\r\n		function _loadFileListByEOF(&$fh, $stopOnFile=false){\r\n			// Check if there\'s a valid Central Dir signature.\r\n			// Let\'s consider a file comment smaller than 1024 characters...\r\n			// Actually, it length can be 65536.. But we\'re not going to support it.\r\n	\r\n			for($x = 0; $x < 1024; $x++){\r\n				fseek($fh, -22-$x, SEEK_END);\r\n	\r\n				$signature = fread($fh, 4);\r\n				if($signature == $this->dirSignatureE){\r\n					// If found EOF Central Dir\r\n					$eodir[\'disk_number_this\']   = unpack(\"v\", fread($fh, 2)); // number of this disk\r\n					$eodir[\'disk_number\']        = unpack(\"v\", fread($fh, 2)); // number of the disk with the start of the central directory\r\n					$eodir[\'total_entries_this\'] = unpack(\"v\", fread($fh, 2)); // total number of entries in the central dir on this disk\r\n					$eodir[\'total_entries\']      = unpack(\"v\", fread($fh, 2)); // total number of entries in\r\n					$eodir[\'size_of_cd\']         = unpack(\"V\", fread($fh, 4)); // size of the central directory\r\n					$eodir[\'offset_start_cd\']    = unpack(\"V\", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number\r\n					$zipFileCommentLenght        = unpack(\"v\", fread($fh, 2)); // zipfile comment length\r\n					$eodir[\'zipfile_comment\']    = $zipFileCommentLenght[1]?fread($fh, $zipFileCommentLenght[1]):\'\'; // zipfile comment\r\n					$this->endOfCentral = Array(\r\n						\'disk_number_this\'=>$eodir[\'disk_number_this\'][1],\r\n						\'disk_number\'=>$eodir[\'disk_number\'][1],\r\n						\'total_entries_this\'=>$eodir[\'total_entries_this\'][1],\r\n						\'total_entries\'=>$eodir[\'total_entries\'][1],\r\n						\'size_of_cd\'=>$eodir[\'size_of_cd\'][1],\r\n						\'offset_start_cd\'=>$eodir[\'offset_start_cd\'][1],\r\n						\'zipfile_comment\'=>$eodir[\'zipfile_comment\'],\r\n					);\r\n	\r\n					// Then, load file list\r\n					fseek($fh, $this->endOfCentral[\'offset_start_cd\']);\r\n					$signature = fread($fh, 4);\r\n	\r\n					while($signature == $this->dirSignature){\r\n						$dir[\'version_madeby\']      = unpack(\"v\", fread($fh, 2)); // version made by\r\n						$dir[\'version_needed\']      = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n						$dir[\'general_bit_flag\']    = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n						$dir[\'compression_method\']  = unpack(\"v\", fread($fh, 2)); // compression method\r\n						$dir[\'lastmod_time\']        = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n						$dir[\'lastmod_date\']        = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n						$dir[\'crc-32\']              = fread($fh, 4);              // crc-32\r\n						$dir[\'compressed_size\']     = unpack(\"V\", fread($fh, 4)); // compressed size\r\n						$dir[\'uncompressed_size\']   = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n						$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n						$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n						$fileCommentLength          = unpack(\"v\", fread($fh, 2)); // file comment length\r\n						$dir[\'disk_number_start\']   = unpack(\"v\", fread($fh, 2)); // disk number start\r\n						$dir[\'internal_attributes\'] = unpack(\"v\", fread($fh, 2)); // internal file attributes-byte1\r\n						$dir[\'external_attributes1\']= unpack(\"v\", fread($fh, 2)); // external file attributes-byte2\r\n						$dir[\'external_attributes2\']= unpack(\"v\", fread($fh, 2)); // external file attributes\r\n						$dir[\'relative_offset\']     = unpack(\"V\", fread($fh, 4)); // relative offset of local header\r\n						$dir[\'file_name\']           = fread($fh, $fileNameLength[1]);                             // filename\r\n						$dir[\'extra_field\']         = $extraFieldLength[1] ?fread($fh, $extraFieldLength[1]) :\'\'; // extra field\r\n						$dir[\'file_comment\']        = $fileCommentLength[1]?fread($fh, $fileCommentLength[1]):\'\'; // file comment\r\n	\r\n						// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n						$BINlastmod_date = str_pad(decbin($dir[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n						$BINlastmod_time = str_pad(decbin($dir[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n						$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n						$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n						$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n						$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n						$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n						$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n	\r\n						// Some protection agains attacks...\r\n						if(!$dir[\'file_name\'] = $this->_protect($dir[\'file_name\']))\r\n							continue;\r\n	\r\n						$this->centralDirList[$dir[\'file_name\']] = Array(\r\n							\'version_madeby\'=>$dir[\'version_madeby\'][1],\r\n							\'version_needed\'=>$dir[\'version_needed\'][1],\r\n							\'general_bit_flag\'=>str_pad(decbin($dir[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n							\'compression_method\'=>$dir[\'compression_method\'][1],\r\n							\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n							\'crc-32\'            =>str_pad(dechex(ord($dir[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n							\'compressed_size\'=>$dir[\'compressed_size\'][1],\r\n							\'uncompressed_size\'=>$dir[\'uncompressed_size\'][1],\r\n							\'disk_number_start\'=>$dir[\'disk_number_start\'][1],\r\n							\'internal_attributes\'=>$dir[\'internal_attributes\'][1],\r\n							\'external_attributes1\'=>$dir[\'external_attributes1\'][1],\r\n							\'external_attributes2\'=>$dir[\'external_attributes2\'][1],\r\n							\'relative_offset\'=>$dir[\'relative_offset\'][1],\r\n							\'file_name\'=>$dir[\'file_name\'],\r\n							\'extra_field\'=>$dir[\'extra_field\'],\r\n							\'file_comment\'=>$dir[\'file_comment\'],\r\n						);\r\n						$signature = fread($fh, 4);\r\n					}\r\n	\r\n					// If loaded centralDirs, then try to identify the offsetPosition of the compressed data.\r\n					if($this->centralDirList) foreach($this->centralDirList as $filename=>$details){\r\n						$i = $this->_getFileHeaderInformation($fh, $details[\'relative_offset\']);\r\n						$this->compressedList[$filename][\'file_name\']          = $filename;\r\n						$this->compressedList[$filename][\'compression_method\'] = $details[\'compression_method\'];\r\n						$this->compressedList[$filename][\'version_needed\']     = $details[\'version_needed\'];\r\n						$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n						$this->compressedList[$filename][\'crc-32\']             = $details[\'crc-32\'];\r\n						$this->compressedList[$filename][\'compressed_size\']    = $details[\'compressed_size\'];\r\n						$this->compressedList[$filename][\'uncompressed_size\']  = $details[\'uncompressed_size\'];\r\n						$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n						$this->compressedList[$filename][\'extra_field\']        = $i[\'extra_field\'];\r\n						$this->compressedList[$filename][\'contents-startOffset\']=$i[\'contents-startOffset\'];\r\n						if(strtolower($stopOnFile) == strtolower($filename))\r\n							break;\r\n					}\r\n					return true;\r\n				}\r\n			}\r\n			return false;\r\n		}\r\n		function _loadFileListBySignatures(&$fh, $stopOnFile=false){\r\n			fseek($fh, 0);\r\n			\r\n			$return = false;\r\n			for(;;){\r\n				$details = $this->_getFileHeaderInformation($fh);\r\n				if(!$details){\r\n					$this->debugMsg(1, \"Invalid signature. Trying to verify if is old style Data Descriptor...\");\r\n					fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again)\r\n					$details = $this->_getFileHeaderInformation($fh);\r\n				}\r\n				if(!$details){\r\n					$this->debugMsg(1, \"Still invalid signature. Probably reached the end of the file.\");\r\n					break;\r\n				}\r\n				$filename = $details[\'file_name\'];\r\n				$this->compressedList[$filename] = $details;\r\n				$return = true;\r\n				if(strtolower($stopOnFile) == strtolower($filename))\r\n					break;\r\n			}\r\n			\r\n			return $return;\r\n		}\r\n		function _getFileHeaderInformation(&$fh, $startOffset=false){\r\n			if($startOffset !== false)\r\n				fseek($fh, $startOffset);\r\n			\r\n			$signature = fread($fh, 4);\r\n			if($signature == $this->zipSignature){\r\n				# $this->debugMsg(1, \"Zip Signature!\");\r\n				\r\n				// Get information about the zipped file\r\n				$file[\'version_needed\']     = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n				$file[\'general_bit_flag\']   = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n				$file[\'compression_method\'] = unpack(\"v\", fread($fh, 2)); // compression method\r\n				$file[\'lastmod_time\']       = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n				$file[\'lastmod_date\']       = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n				$file[\'crc-32\']             = fread($fh, 4);              // crc-32\r\n				$file[\'compressed_size\']    = unpack(\"V\", fread($fh, 4)); // compressed size\r\n				$file[\'uncompressed_size\']  = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n				$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n				$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n				$file[\'file_name\']          = fread($fh, $fileNameLength[1]); // filename\r\n				$file[\'extra_field\']        = $extraFieldLength[1]?fread($fh, $extraFieldLength[1]):\'\'; // extra field\r\n				$file[\'contents-startOffset\']= ftell($fh);\r\n				\r\n				// Bypass the whole compressed contents, and look for the next file\r\n				fseek($fh, $file[\'compressed_size\'][1], SEEK_CUR);\r\n				\r\n				// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n				$BINlastmod_date = str_pad(decbin($file[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n				$BINlastmod_time = str_pad(decbin($file[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n				$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n				$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n				$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n				$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n				$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n				$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n				\r\n				// Some protection agains attacks...\r\n				if(!$file[\'file_name\'] = $this->_protect($file[\'file_name\']))\r\n					return;\r\n				\r\n				// Mount file table\r\n				$i = Array(\r\n					\'file_name\'         =>$file[\'file_name\'],\r\n					\'compression_method\'=>$file[\'compression_method\'][1],\r\n					\'version_needed\'    =>$file[\'version_needed\'][1],\r\n					\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n					\'crc-32\'            =>str_pad(dechex(ord($file[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n					\'compressed_size\'   =>$file[\'compressed_size\'][1],\r\n					\'uncompressed_size\' =>$file[\'uncompressed_size\'][1],\r\n					\'extra_field\'       =>$file[\'extra_field\'],\r\n					\'general_bit_flag\'  =>str_pad(decbin($file[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n					\'contents-startOffset\'=>$file[\'contents-startOffset\']\r\n				);\r\n				return $i;\r\n			}\r\n			return false;\r\n		}\r\n		\r\n		function _protect($fullPath){\r\n			// Known hack-attacks (filename like):\r\n			//   /home/usr\r\n			//   ../../home/usr\r\n			//   folder/../../../home/usr\r\n			//   sample/(x0)../home/usr\r\n			\r\n			$fullPath = strtr($fullPath, \":*<>|\\\"\\x0\\\\\", \"......./\");\r\n			while($fullPath[0] == \"/\")\r\n				$fullPath = substr($fullPath, 1);\r\n			\r\n			if(substr($fullPath, -1) == \"/\"){\r\n				$base     = \'\';\r\n				$fullPath = substr($fullPath, 0, -1);\r\n			}\r\n			else{\r\n				$base     = basename($fullPath);\r\n				$fullPath = dirname($fullPath);\r\n			}\r\n			\r\n			$parts   = explode(\"/\", $fullPath);\r\n			$lastIdx = false;\r\n			foreach($parts as $idx=>$part){\r\n				if($part == \".\")\r\n					unset($parts[$idx]);\r\n				elseif($part == \"..\"){\r\n					unset($parts[$idx]);\r\n					if($lastIdx !== false){\r\n						unset($parts[$lastIdx]);\r\n					}\r\n				}\r\n				elseif($part === \'\'){\r\n					unset($parts[$idx]);\r\n				}\r\n				else{\r\n					$lastIdx = $idx;\r\n				}\r\n			}\r\n	\r\n			$fullPath = sizeof($parts)?implode(\"/\", $parts).\"/\":\"\";\r\n			return $fullPath.$base;\r\n		}\r\n	}\r\n	\r\n	class cxc_dZip{\r\n		var $filename;\r\n		var $overwrite;\r\n		\r\n		var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n		var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n		var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n		var $files_count  = 0;\r\n		var $fh;\r\n		\r\n		function cxc_dZip($filename, $overwrite=true){\r\n			$this->filename  = $filename;\r\n			$this->overwrite = $overwrite;\r\n		}\r\n		function addDir($dirname, $fileComments=\'\'){\r\n			if(substr($dirname, -1) != \'/\')\r\n				$dirname .= \'/\';\r\n			$this->addFile(false, $dirname, $fileComments);\r\n		}\r\n		function addFile($filename, $cfilename, $fileComments=\'\', $data=false){\r\n			if(!($fh = &$this->fh))\r\n				$fh = fopen($this->filename, $this->overwrite?\'wb\':\'a+b\');\r\n			\r\n			// $filename can be a local file OR the data wich will be compressed\r\n			if(substr($cfilename, -1)==\'/\'){\r\n				$details[\'uncsize\'] = 0;\r\n				$data = \'\';\r\n			}\r\n			elseif(file_exists($filename)){\r\n				$details[\'uncsize\'] = filesize($filename);\r\n				$data = file_get_contents($filename);\r\n			}\r\n			elseif($filename){\r\n				echo \"<b>Cannot add $filename. File not found</b><br>\";\r\n				return false;\r\n			}\r\n			else{\r\n				$details[\'uncsize\'] = strlen($filename);\r\n				// DATA is given.. use it! :|\r\n			}\r\n	\r\n			// if data to compress is too small, just store it\r\n			if($details[\'uncsize\'] < 256){\r\n				$details[\'comsize\'] = $details[\'uncsize\'];\r\n				$details[\'vneeded\'] = 10;\r\n				$details[\'cmethod\'] = 0;\r\n				$zdata = &$data;\r\n			}\r\n			else{ // otherwise, compress it\r\n				$zdata = gzcompress($data);\r\n				$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)\r\n				$details[\'comsize\'] = strlen($zdata);\r\n				$details[\'vneeded\'] = 10;\r\n				$details[\'cmethod\'] = 8;\r\n			}\r\n			\r\n			$details[\'bitflag\'] = 0;\r\n			$details[\'crc_32\']  = crc32($data);\r\n			\r\n			// Convert date and time to DOS Format, and set then\r\n			$lastmod_timeS  = str_pad(decbin(date(\'s\')>=32?date(\'s\')-32:date(\'s\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_timeM  = str_pad(decbin(date(\'i\')), 6, \'0\', STR_PAD_LEFT);\r\n			$lastmod_timeH  = str_pad(decbin(date(\'H\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateD  = str_pad(decbin(date(\'d\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateM  = str_pad(decbin(date(\'m\')), 4, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateY  = str_pad(decbin(date(\'Y\')-1980), 7, \'0\', STR_PAD_LEFT);\r\n			\r\n			# echo \"ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (\".date(\"s H H\").\")\\n\";\r\n			# echo \"ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (\".date(\"d m Y\").\")\\n\";\r\n			$details[\'modtime\'] = bindec(\"$lastmod_timeH$lastmod_timeM$lastmod_timeS\");\r\n			$details[\'moddate\'] = bindec(\"$lastmod_dateY$lastmod_dateM$lastmod_dateD\");\r\n			\r\n			$details[\'offset\'] = ftell($fh);\r\n			fwrite($fh, $this->zipSignature);\r\n			fwrite($fh, pack(\'s\', $details[\'vneeded\'])); // version_needed\r\n			fwrite($fh, pack(\'s\', $details[\'bitflag\'])); // general_bit_flag\r\n			fwrite($fh, pack(\'s\', $details[\'cmethod\'])); // compression_method\r\n			fwrite($fh, pack(\'s\', $details[\'modtime\'])); // lastmod_time\r\n			fwrite($fh, pack(\'s\', $details[\'moddate\'])); // lastmod_date\r\n			fwrite($fh, pack(\'V\', $details[\'crc_32\']));  // crc-32\r\n			fwrite($fh, pack(\'I\', $details[\'comsize\'])); // compressed_size\r\n			fwrite($fh, pack(\'I\', $details[\'uncsize\'])); // uncompressed_size\r\n			fwrite($fh, pack(\'s\', strlen($cfilename)));   // file_name_length\r\n			fwrite($fh, pack(\'s\', 0));  // extra_field_length\r\n			fwrite($fh, $cfilename);    // file_name\r\n			// ignoring extra_field\r\n			fwrite($fh, $zdata);\r\n			\r\n			// Append it to central dir\r\n			$details[\'external_attributes\']  = (substr($cfilename, -1)==\'/\'&&!$zdata)?16:32; // Directory or file name\r\n			$details[\'comments\']             = $fileComments;\r\n			$this->appendCentralDir($cfilename, $details);\r\n			$this->files_count++;\r\n		}\r\n		function setExtra($filename, $property, $value){\r\n			$this->centraldirs[$filename][$property] = $value;\r\n		}\r\n		function save($zipComments=\'\'){\r\n			if(!($fh = &$this->fh))\r\n				$fh = fopen($this->filename, $this->overwrite?\'w\':\'a+\');\r\n			\r\n			$cdrec = \"\";\r\n			foreach($this->centraldirs as $filename=>$cd){\r\n				$cdrec .= $this->dirSignature;\r\n				$cdrec .= \"\\x0\\x0\";                  // version made by\r\n				$cdrec .= pack(\'v\', $cd[\'vneeded\']); // version needed to extract\r\n				$cdrec .= \"\\x0\\x0\";                  // general bit flag\r\n				$cdrec .= pack(\'v\', $cd[\'cmethod\']); // compression method\r\n				$cdrec .= pack(\'v\', $cd[\'modtime\']); // lastmod time\r\n				$cdrec .= pack(\'v\', $cd[\'moddate\']); // lastmod date\r\n				$cdrec .= pack(\'V\', $cd[\'crc_32\']);  // crc32\r\n				$cdrec .= pack(\'V\', $cd[\'comsize\']); // compressed filesize\r\n				$cdrec .= pack(\'V\', $cd[\'uncsize\']); // uncompressed filesize\r\n				$cdrec .= pack(\'v\', strlen($filename)); // file comment length\r\n				$cdrec .= pack(\'v\', 0);                // extra field length\r\n				$cdrec .= pack(\'v\', strlen($cd[\'comments\'])); // file comment length\r\n				$cdrec .= pack(\'v\', 0); // disk number start\r\n				$cdrec .= pack(\'v\', 0); // internal file attributes\r\n				$cdrec .= pack(\'V\', $cd[\'external_attributes\']); // internal file attributes\r\n				$cdrec .= pack(\'V\', $cd[\'offset\']); // relative offset of local header\r\n				$cdrec .= $filename;\r\n				$cdrec .= $cd[\'comments\'];\r\n			}\r\n			$before_cd = ftell($fh);\r\n			fwrite($fh, $cdrec);\r\n			\r\n			// end of central dir\r\n			fwrite($fh, $this->dirSignatureE);\r\n			fwrite($fh, pack(\'v\', 0)); // number of this disk\r\n			fwrite($fh, pack(\'v\', 0)); // number of the disk with the start of the central directory\r\n			fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries \"on this disk\" \r\n			fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries overall \r\n			fwrite($fh, pack(\'V\', strlen($cdrec)));     // size of central dir \r\n			fwrite($fh, pack(\'V\', $before_cd));         // offset to start of central dir\r\n			fwrite($fh, pack(\'v\', strlen($zipComments))); // .zip file comment length\r\n			fwrite($fh, $zipComments);\r\n			\r\n			fclose($fh);\r\n		}\r\n		\r\n		// Private\r\n		function appendCentralDir($filename,$properties){\r\n			$this->centraldirs[$filename] = $properties;\r\n		}\r\n	} \r\n\r\n/*\r\n	PLUGIN CODE::LANGUAGE SUPPORT = cxc_templates_gTxt(\'\')\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_templates_gTxt($what, $atts = array()) {\r\n		$lang = array(\r\n			\'cxc_tpl_templates_tab\' => \'Templates\',\r\n			\'cxc_tpl_process\' => \'Process Templates\',\r\n			\'cxc_tpl_preferences\' => \'Plugin Preferences\',\r\n			\'cxc_tpl_dbupdate\' => \'<span class=\"cxc-tpl-failure\">Database update failed</span> entry for current template will be unavailable.\',\r\n			\'cxc_tpl_return\' => \'Click here to return to the template manager.\',\r\n			\'cxc_tpl_delete_confirm\' => \'Delete Directory Confirmation\',\r\n			\'cxc_tpl_remove_before\' => \'This will completely remove the \"<strong>\',\r\n			\'cxc_tpl_remove_after\' => \'</strong>\" directory from your site, click \"<strong>GO</strong>\" to continue or use the link below to return to the template manager.\',\r\n			\'cxc_tpl_removed\' => \'Template Removed\',\r\n			\'cxc_tpl_the_capital\' => \'The <span class=\"cxc-tpl-capital\"><strong>\',\r\n			\'cxc_tpl_removed_from\' => \'</strong></span> template files have been successfully removed from the \"<strong>\',\r\n			\'cxc_tpl_removed_dir\' => \'</strong>\" directory.\',\r\n			\'cxc_tpl_remove_failed\' => \'Unable to Remove Template\',\r\n			\'cxc_tpl_not_removed\' => \'</span> template directory was not removed, this might be due to the server configuration of your host and removal of templates may need to be done manually\',\r\n			\'cxc_tpl_template_import\' => \'Template Import:\',\r\n			\'cxc_tpl_import_failed_before\' => \'<span class=\"cxc-tpl-failure\">Failed importing</span> the \\\'<span class=\"cxc-tpl-capital\">\',\r\n			\'cxc_tpl_import_failed_after\' => \'</span>\\\' template files\',\r\n			\'cxc_tpl_upload_import_fail\' => \'It was not possible to import the uploaded template because the Zip file contained more than one template or it was already present in the templates directory.\',\r\n			\'cxc_tpl_import_template\' => \'Import Template\',\r\n			\'cxc_tpl_none_before\' => \'There are no templates installed in the \\\'<strong>\',\r\n			\'cxc_tpl_none_after\' => \'</strong>\\\' directory, please clone your current template or upload a new one.\',\r\n			\'cxc_tpl_alternate_dir\' => \'Use Alternate Template Directory (<em class=\"cxc-tpl-failure\">not recommended</em>)\',\r\n			\'cxc_tpl_alt_adjust\' => \'You will need to adjust the location to be used as the template directory by modifying <code>$cxc_templates[\\\'base_dir\\\']</code> in the plugin\\\'s code. After you have adjusted the plugin\\\'s code it will try to automatically create the chosen directory for you if not already present in your webroot.\',\r\n			\'cxc_tpl_alt_note\' => \'<strong>Note:</strong> this could affect template assets and result in broken links to css, images, JS and other template files so it is recommended you use the default or select the directory used by the templates designer.\',\r\n			\'cxc_tpl_import_which\' => \'Which template would you like to import?\',\r\n			\'cxc_tpl_import_safe_mode\' => \'Use Import Safe Mode (<em class=\"cxc-tpl-failure\">non-destructive</em>)\',\r\n			\'cxc_tpl_export_template\' => \'Export Template\',\r\n			\'cxc_tpl_export_name\' => \'Choose a name for the exported template.\',\r\n			\'cxc_tpl_delete_template\' => \'Delete Template\',\r\n			\'cxc_tpl_zip_template\' => \'Zip Project Folder\',\r\n			\'cxc_tpl_upload_template\' => \'Upload Template\',\r\n			\'cxc_tpl_upload_select\' => \'Please select the template you would like to upload.\',\r\n			\'cxc_tpl_upload_import\' => \'Import Uploaded Template\',\r\n			\'cxc_tpl_advanced_options\' => \'Advanced Options\',\r\n			\'cxc_tpl_root_install\' => \'Web Root Installation (<em class=\"cxc-tpl-failure\">not recommended</em>)\',\r\n			\'cxc_tpl_advanced_note\' => \'Note:</strong> <em>do not use unless required or you know what you are doing!\',\r\n			\'cxc_tpl_feature_unavailable\' => \'Feature Unavailable\',\r\n			\'cxc_tpl_zlib_enabled_text\' => \'If you are seeing this error it means your host compiled version of PHP does not include the ZLib modules. This feature requires the extension ZLib to be enabled, it is compiled by default for most site hosts around the world, and for the PHP Win32 distributions. If this is unavailable you should try speaking to your hosts to have it enabled, if they are not able to provide this for you, I recommend considering a new host. If you choose to remain with them you will have to unzip locally and FTP templates into your template directory, sorry for the inconvenience.\',\r\n			\'cxc_tpl_req_dir_created\' => \'Required Directories Created\',\r\n			\'cxc_tpl_reload_click\' => \'Click here to reload this page\',\r\n			\'cxc_tpl_reload_display\' => \'and display the template manager.\',\r\n			\'cxc_tpl_req_dir_missing\' => \'Required Directories Missing\',\r\n			\'cxc_tpl_req_dir_this\' => \'This plugin requires the `<strong>\',\r\n			\'cxc_tpl_req_dir_theme\' => \'</strong>` directory to be located in the webroot for it to function properly and the `<strong>\',\r\n			\'cxc_tpl_req_dir_cache\' => \'</strong>` directory to be in the textpattern directory, either `<strong>\',\r\n			\'cxc_tpl_req_dir_or\' => \'</strong>` directory or `<strong>\',\r\n			\'cxc_tpl_req_dir_auto\' => \'</strong>` directory does not exist, and could not be automatically created.\',\r\n			\'cxc_tpl_req_dir_manual\' => \'Please create these directories manually using your FTP client, hosting control panel or by running something like ...\',\r\n			\'cxc_tpl_req_dir_after\' => \'After you have created the missing directories, return to (or reload) this page to display the template manager. You could also adjust the plugin\\\'s directory by modifying <code>$cxc_templates[\\\'base_dir\\\']</code> and/or <code>$cxc_templates[\\\'cache_dir\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_add_security\' => \'For additional security you may also want to include empty index.html files or adjust your .htaccess for the `<strong>\',\r\n			\'cxc_tpl_add_empty_dir\' => \'</strong>` directory.\',\r\n			\'cxc_tpl_webroot\' => \'webroot\',\r\n			\'cxc_tpl_req_directory\' => \'</strong>` directory to be located in the `<strong>\',\r\n			\'cxc_tpl_req_properly\' => \'</strong>` directory for it to function properly, the `<strong>\',\r\n			\'cxc_tpl_req_manual\' => \'Please create the directory manually using your FTP client, hosting control panel or by running something like ...\',\r\n			\'cxc_tpl_req_after\' => \'After you have created the missing directory, return to (or reload) this page to display the template manager. You could also adjust the plugin\\\'s directory by modifying <code>$cxc_templates[\\\'\',\r\n			\'cxc_tpl_req_code\' => \'\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_secure_dir\' => \'For additional security you may also want to include empty index.html files or adjust your .htaccess for the directories.\',\r\n			\'cxc_tpl_template_dir\' => \'The template directory `<strong>\',\r\n			\'cxc_tpl_would_you\' => \'Would you mind creating it yourself by running something like ...\',\r\n			\'cxc_tpl_should_fix\' => \'That should fix the issue. You could also adjust the plugin\\\'s directory by modifying <code>\\$cxc_templates[\\\'base_dir\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_not_writable\' => \'Template Directory Not Writable\',\r\n			\'cxc_tpl_chmod_write\' => \'I can\\\'t seem to write to the template directory \\\'<strong>%1\\$s</strong>\\\'.  Would you mind running something like ...\',\r\n			\'cxc_tpl_not_readable\' => \'Template Directory Not Readable\',\r\n			\'cxc_tpl_chmod_read\' => \'I can\\\'t seem to read from the template directory \\\'<strong>%1\\$s</strong>\\\'.  Would you mind running something like ...\',\r\n			\'cxc_tpl_problem_fix\' => \'... to fix the problem?\',\r\n			\'cxc_tpl_wrong_file_type\' => \'Unsupported File Type\',\r\n			\'cxc_tpl_corrupt_file\' => \'The file is either corrupt or is an unsupported file type, only zip files are currently supported by this plugin.\',\r\n			\'cxc_tpl_open_close\' => \'Click here to open/close detailed list of events.\',\r\n			\'cxc_tpl_export_current\' => \'Template Export: Current\',\r\n			\'cxc_tpl_export_success\' => \'Successfully exported\',\r\n			\'cxc_tpl_export_failed\' => \'Failed exporting\',\r\n			\'cxc_tpl_update_unable\' => \'<span class=\"cxc-tpl-failure\">Unable to update</span> entry for current template in the database,\',\r\n			\'cxc_tpl_update_none\' => \'template information will be unavailabl6e.\',\r\n			\'cxc_tpl_processing\' => \'Template Processing\',\r\n			\'cxc_tpl_pre_remove\' => \'The `<strong>preimport-data</strong>` template directory has been successfully removed from the `<strong>\',\r\n			\'cxc_tpl_pre_newback\' => \'</strong>` directory so a new backup can be written to replace previous template backups.\',\r\n			\'cxc_tpl_pre_failed\' => \'The `<strong>preimport-data</strong>` template directory was not removed, the backup may contain additional files from previous backups. Your current template data will be added to `<strong>preimport-data</strong>` for future use.\',\r\n			\'cxc_tpl_pre_note\' => \'<strong>Note:</strong> <em>installing another template will overwrite the current `<strong>preimport-data</strong>` so rename the directory to something else after it is exported to preserve a more permanent backup.</em>\',\r\n			\'cxc_tpl_import_success\' => \'Successfully imported\',\r\n			\'cxc_tpl_import_skipped\' => \'Skipped importing\',\r\n			\'cxc_tpl_import_present\' => \'\", it was already present.\',\r\n			\'cxc_tpl_import_failed\' => \'Failed importing\',\r\n			\'cxc_tpl_add_click\' => \'Click here to see additional information from the imported templates designer.\',\r\n			\'cxc_tpl_add_info\' => \'Additional Information\',\r\n			\'cxc_tpl_upload_success\' => \'Successfully uploaded\',\r\n			\'cxc_tpl_move_fail\' => \'<span class=\"cxc-tpl-failure\">Failed removing </span> file \"\',\r\n			\'cxc_tpl_move_temp\' => \'\" the temporary files.\',\r\n			\'cxc_tpl_preview_text\' => \'Template Preview\',\r\n			\'cxc_tpl_preview_none\' => \'No Preview Image Available\',\r\n			\'cxc_tpl_preview_docs\' => \'Template Documentation\',\r\n			\'cxc_tpl_preview_note\' => \'(<em><span class=\"cxc-tpl-capital\">\',\r\n			\'cxc_tpl_preview_last\' => \'</span> was the last template imported</em>)\',\r\n			\'cxc_tpl_top_level\' => \'Top Level \"<strong>\',\r\n			\'cxc_tpl_top_dir\' => \'</strong>\" Directory\',\r\n			\'cxc_tpl_top_remove\' => \'<strong>Note:</strong> <em class=\"cxc-tpl-failure\">this will remove all templates.</em>\',\r\n			\'cxc_tpl_template\' => \'Template\',\r\n			\'cxc_tpl_preview_img\' => \'Template Preview Image\',\r\n			\'cxc_tpl_error_zip_dir\' => \'Error, there was a problem creating the zip file for the template directory.\',\r\n			\'cxc_tpl_error_zip_remove\' => \'Error, there was a problem removing the zip file after the download.\',\r\n			\'cxc_tpl_error_zip_final\' => \'Error, could not finalise the archive.\',\r\n			\'cxc_tpl_error_zip_failed\' => \'Error, could not create the\',\r\n			\'cxc_tpl_error_zip_archive\' => \'archive file\'\r\n		);\r\n		return strtr($lang[$what], $atts);\r\n	} ', ' \r\n/*\r\n	PUBLIC PLUGIN CONFIG\r\n	-------------------------------------------------------------------------\r\n*/\r\n	$cxc_templates = array(\r\n		\'base_dir\'			=>	\'tpl\',\r\n		\'cache_dir\'			=>	\'tmp\',\r\n\r\n		\'subdir_css\'		=>	\'style\',\r\n		\'subdir_forms\'		=>	\'forms\',\r\n		\'subdir_pages\'		=>	\'pages\',\r\n		\'subdir_plugins\'	=>	\'plugins\',\r\n		\'subdir_sections\'	=>	\'sections\',\r\n\r\n		\'ext_css\'			=>	\'.css\',\r\n		\'ext_forms\'			=>	\'.form\',\r\n		\'ext_pages\'			=>	\'.page\',\r\n		\'ext_plugins\'		=>	\'.plugin\',\r\n		\'ext_section\'		=>	\'.section\'\r\n	);\r\n\r\n/*\r\n	PLUGIN CODE (no editing below this line, please)\r\n	-------------------------------------------------------------------------\r\n*/\r\n	define(\'_CXC_TEMPLATES_IMPORT\', 1);\r\n	define(\'_CXC_TEMPLATES_EXPORT\', 2);\r\n	$GLOBALS[\'_CXC_TEMPLATES\'] = $cxc_templates;\r\n\r\n/*\r\n	PLUGIN CODE::INSTANTIATION\r\n	-------------------------------------------------------------\r\n*/	\r\n	if (@txpinterface == \'admin\') {\r\n		$import = \'cxc_templates\';\r\n		$import_tab = cxc_templates_gTxt(\'cxc_tpl_templates_tab\');\r\n\r\n		add_privs($import, \'1,2\');\r\n		register_tab(\'extensions\', $import, $import_tab);\r\n		register_callback(\'cxc_templates\', $import);\r\n		register_callback(\'cxc_tpl_prep\', \'plugin_lifecycle.cxc_templates\');\r\n	}\r\n\r\n/*\r\n	PLUGIN CODE::LIFECYCLE\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_tpl_prep($event, $step) {\r\n		global $prefs;\r\n		switch ($step) {\r\n			case \'disabled\':\r\n				if (isset($prefs[\'cxc_tpl_current\'])) {\r\n					$prep = safe_delete(\'txp_prefs\',\'name = \"cxc_tpl_current\"\');\r\n				}\r\n				break;\r\n			case \'deleted\':\r\n				if (isset($prefs[\'cxc_tpl_current\'])) {\r\n					$prep = safe_delete(\'txp_prefs\',\'name = \"cxc_tpl_current\"\');\r\n				}\r\n				break;\r\n		}\r\n	}\r\n\r\n/*\r\n	PLUGIN CODE::MAIN CALLBACK\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_templates($event, $step=\'\') {\r\n		$GLOBALS[\'prefs\'] = get_prefs();\r\n		global $prefs;\r\n		$template = new cxc_template();\r\n\r\n		pagetop(cxc_templates_gTxt(\'cxc_tpl_process\'), \'\');\r\n		print \'\r\n		<style type=\"text/css\">\r\n			.cxc-tpl-boxedup { display: block; width: 450px; }\r\n			.cxc-tpl-success { color: #009900; }\r\n			.cxc-tpl-failure { color: #FF0000; }\r\n			.cxc-tpl-capital { text-transform: capitalize; }\r\n			.cxc-tpl-current { border: medium ridge;float: right; margin: 0 0 0 5px; padding: 1em 1em 0; text-align: center; width: 220px; }\r\n			.cxc-tpl-preview { background: #fff; border: medium ridge; float: right; margin: 0 0 0 10px; padding: 1em 1em 0; text-align: center; width: 220px; }\r\n			.cxc-tpl-default { max-height: 200px; overflow: hidden; }\r\n			.cxc-tpl-padded { border: 1px solid; padding: 2em; }\r\n			.cxc-tpl-smaller { font-size: 80%; }\r\n		</style>\r\n\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(function()\r\n			{\r\n			  $(\".cxc-tpl-slide-body\").hide();\r\n			  $(\".cxc-tpl-slide-head\").click(function()\r\n			  {\r\n				$(this).next(\".cxc-tpl-slide-body\").slideToggle(600);\r\n			  });\r\n			});\r\n		</script>\r\n\r\n		<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\">\r\n			<tr>\r\n				<td>\r\n		\';\r\n\r\n		if (!isset($prefs[\'cxc_tpl_current\']) && !set_pref(\'cxc_tpl_current\', \'\', \'publish\', 2) && !get_pref(\'cxc_tpl_current\')) {\r\n			print \'\r\n				<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_preferences\').\'</h1>\r\n				<ul class=\"results\">\r\n					<li>\'.cxc_templates_gTxt(\'cxc_tpl_dbupdate\').\'</li>\r\n				</ul>\r\n				<br />\r\n			\';\r\n		}\r\n\r\n		$theme_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\'];\r\n		$cache_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .\'textpattern\'. DIRECTORY_SEPARATOR .$template->_config[\'cache_dir\'];\r\n		if (is_dir($theme_dir) && is_dir($cache_dir)) {\r\n\r\n			$theme_index = $theme_dir. DIRECTORY_SEPARATOR .\'index.html\';\r\n			$cache_index = $cache_dir. DIRECTORY_SEPARATOR .\'index.html\';\r\n			if (!file_exists($theme_index) || !file_exists($cache_index)) {\r\n				$template->writeIndexFiles($theme_dir);\r\n				$template->writeIndexFiles($cache_dir);\r\n			}\r\n							\r\n			switch ($step) {\r\n				case \'import\':\r\n					$import_full = ps(\'import_full\');\r\n					$template->import($import_full, ps(\'import_dir\'));\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .ps(\'import_dir\'));\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'export\':\r\n					$dir = ps(\'export_dir\');\r\n\r\n					$dir =  str_replace(\r\n								array(\' \'),\r\n								array(\'-\'),\r\n								$dir\r\n							);\r\n					$template->export($dir);\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .ps(\'export_dir\'));\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'rusure\':\r\n					$dir = $theme_dir. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n					if (ps(\'remove_dir\') != \'preimport-data\') {\r\n						$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n						if (is_dir($tpl_dir)) {\r\n							print \'<div class=\"cxc-tpl-current\">\';\r\n							$template->cxc_tpl_preview(ps(\'remove_dir\'), $tpl_dir);\r\n							print \'</div>\';\r\n						}\r\n					}\r\n\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_delete_confirm\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_remove_before\').$dir.cxc_templates_gTxt(\'cxc_tpl_remove_after\').\'</p>\r\n						\'.form(\r\n							graf(\'\'.\r\n								hInput(\'remove_dir\',ps(\'remove_dir\')).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'remove\')\r\n							)\r\n						).\'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n						\';\r\n\r\n					break;\r\n\r\n				case \'remove\':\r\n					$dir = $theme_dir. DIRECTORY_SEPARATOR .ps(\'remove_dir\');\r\n					if (is_dir($dir)) {\r\n						$objects = scandir($dir);\r\n						foreach ($objects as $object) {\r\n							if ($object != \'.\' && $object != \'..\') {\r\n								if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n									$template->removeDirectory($dir. DIRECTORY_SEPARATOR .$object);\r\n								} else { \r\n									@unlink($dir. DIRECTORY_SEPARATOR .$object);\r\n								}\r\n							}\r\n						}\r\n						reset($objects);\r\n						@rmdir($dir);\r\n					}\r\n					if (!is_dir($dir)){					\r\n						print \'\r\n							<h1 class=\"cxc-tpl-success\"><span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', ps(\'remove_dir\')).\'</span> \'.cxc_templates_gTxt(\'cxc_tpl_removed\').\'</h1>\r\n							<p>\'.cxc_templates_gTxt(\'cxc_tpl_the_capital\').str_replace(\'_\', \' \', ps(\'remove_dir\')).cxc_templates_gTxt(\'cxc_tpl_removed_from\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_removed_dir\').\'</p>\r\n						\';\r\n					} else {\r\n						print \'\r\n							<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_remove_failed\').\'</h1>\r\n							<p>\'.cxc_templates_gTxt(\'cxc_tpl_the_capital\').str_replace(\'_\', \' \', ps(\'remove_dir\')).cxc_templates_gTxt(\'cxc_tpl_not_removed\').\'</p>\r\n						\';\r\n					}\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'importZip\':\r\n					$adv_live = ps(\'adv_live\');\r\n					$adv_root = ps(\'adv_root\');\r\n					$import_full = ps(\'import_full\');\r\n					$tpl_alist = scandir($theme_dir);\r\n					$rel_temp_dir = $template->_config[\'cache_dir\']. DIRECTORY_SEPARATOR . $_FILES[\'file\'][\'name\'];															\r\n					move_uploaded_file($_FILES[\'file\'][\'tmp_name\'],$rel_temp_dir);\r\n					$template->importZip($adv_live, $adv_root, $rel_temp_dir,$_FILES[\'file\'][\'name\']);\r\n					$tpl_blist = scandir($theme_dir);\r\n					$newtpl = array_merge(array_diff($tpl_blist,$tpl_alist));\r\n					if ($adv_live){\r\n						if ($newtpl != \'\' && count($newtpl) == 1) {\r\n							$template->import($import_full, $newtpl[0]);\r\n							$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .$newtpl[0]);\r\n						} else {\r\n							print \'\r\n								<h1>\'.cxc_templates_gTxt(\'cxc_tpl_template_import\').\' <span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', str_replace(\'.zip\', \'\', $_FILES[\'file\'][\'name\'])).\'</span></h1>\r\n								<ul class=\"results\">\r\n									<li>\'.cxc_templates_gTxt(\'cxc_tpl_import_failed_before\').str_replace(\' \', \'-\', str_replace(\'.zip\', \'\', $_FILES[\'file\'][\'name\'])).cxc_templates_gTxt(\'cxc_tpl_import_failed_after\').\'</li>\r\n								</ul>\r\n								<br />\r\n								<p>\'.cxc_templates_gTxt(\'cxc_tpl_upload_import_fail\').\'</p>\r\n							\';\r\n						}\r\n					}\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'docs\':\r\n					$template->cxc_tpl_docs($prefs[\'cxc_tpl_current\']);\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				case \'downzip\':\r\n					$zipdir	= ps(\'zip_dir\');\r\n					$stripz	= $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR;\r\n					$template->writeIndexFiles($theme_dir. DIRECTORY_SEPARATOR .$zipdir);\r\n					$template->cxc_tpl_downzip($theme_dir. DIRECTORY_SEPARATOR .$zipdir, $zipdir.\'.zip\', $stripz);\r\n					print \'\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n					break;\r\n\r\n				default:\r\n					$importlist = $template->getTemplateList();\r\n					$php_modules = array_map(\'strtolower\', get_loaded_extensions());\r\n\r\n					if (!empty($prefs[\'cxc_tpl_current\']) && $prefs[\'cxc_tpl_current\'] != \'preimport-data\') {\r\n						$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$template->_config[\'base_dir\']. DIRECTORY_SEPARATOR .$prefs[\'cxc_tpl_current\'];\r\n						if (is_dir($tpl_dir)) {\r\n							print \'<div class=\"cxc-tpl-current\">\';\r\n							$template->cxc_tpl_current($tpl_dir);\r\n							print \'</div>\';\r\n						}\r\n					}\r\n\r\n					if (empty($importlist) || $importlist == \'\') {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_import_template\').\'</h1>\r\n							<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_none_before\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_none_after\').\'</p>\r\n							<span class=\"cxc-tpl-slide-head\">\r\n								\'.form(\r\n									graf(\'\'.\r\n										checkbox(\'show_alt\', \'show_alt\', \'0\', \'\', \'show_alt\').\' \'.cxc_templates_gTxt(\'cxc_tpl_alternate_dir\').\' &lt;/&gt;\')\r\n								).\'\r\n							</span>\r\n							<div class=\"cxc-tpl-slide-body\">\r\n								<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_alt_adjust\').\'</p>\r\n								<p class=\"cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_alt_note\').\'</p>\r\n							</div>\r\n						\';\r\n					} else {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_import_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_import_which\').\' <br />\'.\r\n								selectInput(\'import_dir\', $importlist, \'\', 1).\' <br />\'.\r\n								checkbox(\'import_full\', \'import_full\', \'0\', \'\', \'import_full\').\' \'.cxc_templates_gTxt(\'cxc_tpl_import_safe_mode\').\' <br />\'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'import\')\r\n							)\r\n						);\r\n					}\r\n\r\n					print \'\r\n						<h1>\'.cxc_templates_gTxt(\'cxc_tpl_export_template\').\'</h1>	\r\n					\'.form(\r\n						graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_export_name\').\' <br />\'.\r\n							fInput(\'text\', \'export_dir\', \'\').\r\n							fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n							eInput(\'cxc_templates\').sInput(\'export\')\r\n						)\r\n					);\r\n\r\n					if (!empty($importlist) && !$importlist == \'\') {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_delete_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.\r\n								selectInput(\'remove_dir\', $importlist, \'\', 1).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'rusure\')\r\n							)\r\n						);\r\n					}\r\n\r\n					if (!empty($importlist) && !$importlist == \'\' && in_array(\'zlib\', $php_modules)) {\r\n						print \'\r\n							<h1>\'.cxc_templates_gTxt(\'cxc_tpl_zip_template\').\'</h1>\r\n						\'.form(\r\n							graf(\'\'.\r\n								selectInput(\'zip_dir\', $importlist, \'\', 1).\' \'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'downzip\')\r\n							)\r\n						);\r\n					}\r\n\r\n					print \'\r\n						<h1>\'.cxc_templates_gTxt(\'cxc_tpl_upload_template\').\'</h1>\r\n					\';\r\n					if (in_array(\'zlib\', $php_modules)) {\r\n						print \'\r\n						\'.form(\r\n							graf(\'\'.cxc_templates_gTxt(\'cxc_tpl_upload_select\').\' <br />\'.\r\n								fInput(\'file\', \'file\', \'\', \'\', \'\', \'\',50,\'\',\'file\').\r\n								eInput(\'cxc_templates\').sInput(\'importZip\').\' <br />\'.\r\n								checkbox(\'adv_live\', \'adv_live\', \'1\', \'\', \'adv_live\').\' \'.cxc_templates_gTxt(\'cxc_tpl_upload_import\').\' <br />\'.\r\n								checkbox(\'import_full\', \'import_full\', \'0\', \'\', \'import_full\').\' \'.cxc_templates_gTxt(\'cxc_tpl_import_safe_mode\').\' <br />\r\n								<span class=\"cxc-tpl-slide-head cxc-tpl-boxedup\"><a id=\"upload-advanced-options\">\'.cxc_templates_gTxt(\'cxc_tpl_advanced_options\').\'</a> &lt;/&gt;</span>\r\n								<span class=\"cxc-tpl-slide-body cxc-tpl-boxedup\">\'.\r\n									checkbox(\'adv_root\', \'adv_root\', \'0\', \'\', \'adv_root\').\' \'.cxc_templates_gTxt(\'cxc_tpl_root_install\').\' <br />\r\n									<strong>\'.cxc_templates_gTxt(\'cxc_tpl_advanced_note\').\'</em>\r\n								</span>\'.\r\n								fInput(\'submit\', \'go\', \'Go\', \'smallerbox\',\'\',\'\')\r\n							), \'\', \'\', \'post\', \'\', str_replace(\'\\\\\', \'\', \'\\\" enctype=\\\"multipart/form-data\'), \'\'\r\n						);\r\n					} else {\r\n						print \'\r\n						<span class=\"cxc-tpl-slide-head cxc-tpl-boxedup\"><a id=\"upload-advanced-options\">\'.cxc_templates_gTxt(\'cxc_tpl_feature_unavailable\').\'</a> &lt;/&gt;</span>\r\n						<span class=\"cxc-tpl-slide-body cxc-tpl-boxedup\">\'.cxc_templates_gTxt(\'cxc_tpl_zlib_enabled_text\').\'</span>\r\n						\';\r\n					}\r\n\r\n					break;\r\n			}\r\n		} else {\r\n			$error = false;\r\n			if (!is_dir($theme_dir) && !mkdir($theme_dir, 0777)) { $error = true; }\r\n			if (is_dir($theme_dir) && !is_writable($theme_dir) && !chmod($theme_dir, 0777)) { $error = true; }\r\n			if (!is_dir($cache_dir) && !mkdir($cache_dir, 0777)) { $error = true; }\r\n			if (is_dir($cache_dir) && !is_writable($cache_dir) && !chmod($cache_dir, 0777)) { $error = true; }\r\n			if (!$error) { // no errors, let’s do your thing\r\n				print \'\r\n					<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_created\').\'</h1>\r\n					<p><a href=\"index.php?event=cxc_templates\">\'.cxc_templates_gTxt(\'cxc_tpl_reload_click\').\'</a> \'.cxc_templates_gTxt(\'cxc_tpl_reload_display\').\'</p>\r\n				\';\r\n			} else {\r\n				if (!is_dir($theme_dir) && !is_dir($cache_dir)) {\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_this\').$template->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_req_dir_theme\').$template->_config[\'cache_dir\'].cxc_templates_gTxt(\'cxc_tpl_req_dir_cache\').$theme_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_or\').$cache_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_manual\').\'</p>\r\n<pre><code>    mkdir \'.$template->_config[\'base_dir\'].\'\r\n    chmod 777 \'.$template->_config[\'base_dir\'].\'\r\n</code></pre>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_after\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_add_security\').$theme_dir.cxc_templates_gTxt(\'cxc_tpl_add_empty_dir\').\'</p>\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n				} else {\r\n					if (!is_dir($theme_dir)){\r\n						$missing_dir = $template->_config[\'base_dir\'];\r\n						$missing_loc = \'\';\r\n						$is_missing_dir = \'base_dir\';\r\n						$is_missing_loc = cxc_templates_gTxt(\'cxc_tpl_webroot \');\r\n					}else{\r\n						$missing_dir = $template->_config[\'cache_dir\'];\r\n						$missing_loc = DIRECTORY_SEPARATOR .\'textpattern\'. DIRECTORY_SEPARATOR;\r\n						$is_missing_dir = \'cache_dir\';\r\n						$is_missing_loc = \'textpattern\';\r\n					}\r\n					print \'\r\n						<h1 class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\').\'</h1>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_this\').$missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_directory\').$is_missing_loc.cxc_templates_gTxt(\'cxc_tpl_req_properly\').$missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_manual\').\'</p>\r\n<pre><code>    mkdir \'.$missing_loc.$missing_dir.\'\r\n    chmod 777 \'.$missing_loc.$missing_dir.\'\r\n</code></pre>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_req_after\').$is_missing_dir.cxc_templates_gTxt(\'cxc_tpl_req_code\').\'</p>\r\n						<p>\'.cxc_templates_gTxt(\'cxc_tpl_secure_dir\').\'</p>\r\n						<h2><a href=\"index.php?event=cxc_templates\">&#8617; \'.cxc_templates_gTxt(\'cxc_tpl_return\').\'</a></h2>\r\n					\';\r\n				}\r\n			}\r\n		}\r\n\r\n		print \"\r\n				</td>\r\n			</tr>\r\n		</table>\r\n		\";\r\n	}\r\n\r\n	class cxc_template {\r\n		function cxc_template() {\r\n			global $prefs;\r\n			global $_CXC_TEMPLATES;\r\n\r\n			$this->_config = $_CXC_TEMPLATES;\r\n\r\n		/*\r\n			PRIVATE CONFIG\r\n			------------------------------------------------------\r\n		*/\r\n			$this->_config[\'root_path\']         =   $prefs[\'path_to_site\'];\r\n			$this->_config[\'full_base_path\']    =   sprintf(\r\n														\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n														$this->_config[\'root_path\'],\r\n														$this->_config[\'base_dir\']\r\n													);\r\n\r\n			$this->_config[\'error_template\']    =   \'\r\n				<h1 class=\"cxc-tpl-failure\">%s</h1>\r\n				<p>%s</p>\r\n			\';\r\n\r\n			$missing_dir_head   = cxc_templates_gTxt(\'cxc_tpl_req_dir_missing\');\r\n			$missing_dir_text   = cxc_templates_gTxt(\'cxc_tpl_template_dir\').\'%1\\$s\'.cxc_templates_gTxt(\'cxc_tpl_req_dir_auto\').\' \'.cxc_templates_gTxt(\'cxc_tpl_would_you\').\'</p><pre><code>    mkdir %1\\$s\\n    chmod 777 %1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_should_fix\');\r\n			$cant_write_head    = cxc_templates_gTxt(\'cxc_tpl_not_writable\');\r\n			$cant_write_text    = cxc_templates_gTxt(\'cxc_tpl_chmod_write\').\'</p><pre><code>    chmod 777 %1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_problem_fix\');\r\n			$cant_read_head     = cxc_templates_gTxt(\'cxc_tpl_not_readable\');\r\n			$cant_read_text     = cxc_templates_gTxt(\'cxc_tpl_chmod_read\').\'</p><pre><code>    chmod 777 %%1\\$s</code></pre><p>\'.cxc_templates_gTxt(\'cxc_tpl_problem_fix\');\r\n			$wrong_file_head	= cxc_templates_gTxt(\'cxc_tpl_wrong_file_type\');\r\n			$wrong_file_text	= cxc_templates_gTxt(\'cxc_tpl_corrupt_file\');\r\n\r\n			$this->_config[\'error_missing_dir\'] =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$missing_dir_head,\r\n														$missing_dir_text\r\n													);\r\n			$this->_config[\'error_cant_write\']  =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$cant_write_head,\r\n														$cant_write_text\r\n													);\r\n			$this->_config[\'error_cant_read\']   =   sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$cant_read_head,\r\n														$cant_read_text\r\n													);\r\n			$this->_config[\'error_wrong_file\']	=	sprintf(\r\n														$this->_config[\'error_template\'],\r\n														$wrong_file_head,\r\n														$wrong_file_text\r\n													);\r\n	\r\n			$this->exportTypes = array(\r\n				\'css\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_css\'],\r\n									\'data\'      =>  \'css\',\r\n									\'fields\'    =>  \'name, css\',\r\n									\'nice_name\' =>  \'CSS Rules\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_css\'].\'/\',\r\n									\'sql\'       =>  \'`css` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_css\'],\r\n									\'table\'     =>  \'txp_css\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'forms\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_forms\'],\r\n									\'data\'      =>  \'Form\',\r\n									\'fields\'    =>  \'name, type, Form\',\r\n									\'nice_name\' =>  \'Form Files\',\r\n									\'regex\'     =>  \'/(.+)\\.(.+)\'.$this->_config[\'ext_forms\'].\'/\',\r\n									\'sql\'       =>  \'`Form` = \"%s\", `type` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_forms\'],\r\n									\'table\'     =>  \'txp_form\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'pages\'		=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_pages\'],\r\n									\'data\'      =>  \'user_html\',\r\n									\'fields\'    =>  \'name, user_html\',\r\n									\'nice_name\' =>  \'Page Files\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_pages\'].\'/\',\r\n									\'sql\'       =>  \'`user_html` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_pages\'],\r\n									\'table\'     =>  \'txp_page\',\r\n									\'filter\'    =>	\'1=1\'\r\n								),\r\n				\'plugins\'	=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_plugins\'],\r\n									\'data\'      =>  \'code\',\r\n									\'fields\'    =>  \'name, status, author, author_uri, version, description, help, code, code_restore, code_md5, type\',\r\n									\'nice_name\' =>  \'Plugin Files\',\r\n									\'regex\'     =>  \'/(.+)\\.(.+)\'.$this->_config[\'ext_plugins\'].\'/\',\r\n									\'sql\'       =>  \'`status` = %d, `author` = \"%s\", `author_uri` = \"%s\", `version` = \"%s\", `description` = \"%s\", `help` = \"%s\", `code` = \"%s\", `code_restore` = \"%s\", `code_md5` = \"%s\", `type` = %d\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_plugins\'],\r\n									\'table\'     =>  \'txp_plugin\',\r\n									\'filter\'    =>	\'`status` = 1\'\r\n								),\r\n				\'sections\'	=>	array(\r\n									\'ext\'       =>  $this->_config[\'ext_section\'],\r\n									\'data\'      =>  \'section\',\r\n									\'fields\'    =>  \'name, page, css, in_rss, on_frontpage, searchable, title\',\r\n									\'nice_name\' =>  \'Section Parameters\',\r\n									\'regex\'     =>  \'/(.+)\'.$this->_config[\'ext_section\'].\'/\',\r\n									\'sql\'       =>  \'`page` = \"%s\", `css` = \"%s\", `in_rss` = \"%d\", `on_frontpage` = \"%d\", `searchable` = \"%d\", `title` = \"%s\"\',\r\n									\'subdir\'    =>  $this->_config[\'subdir_sections\'],\r\n									\'table\'     =>  \'txp_section\',\r\n									\'filter\'    =>	\'1=1\'\r\n								)\r\n			);\r\n		}\r\n\r\n		function checkdir($dir = \'\', $type = _CXC_TEMPLATES_EXPORT) {\r\n			/*\r\n				If $type == _EXPORT, then:\r\n					1.  Check to see that /base/path/$dir exists, and is\r\n						writable.  If not, create it.\r\n					2.  Check to see that /base/path/$dir/subdir_* exist,\r\n						and are writable.  If not, create them.\r\n\r\n				If $type == _IMPORT, then:\r\n					1.  Check to see that /base/path/$dir exists, and is readable.\r\n					2.  Check to see that /base/path/$dir/subdir_* exist, and are readable.\r\n			*/\r\n			$dir =  sprintf(\r\n						\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n						$this->_config[\'full_base_path\'],\r\n						$dir\r\n					);\r\n\r\n			$tocheck =  array(\r\n							$dir,\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_css\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_forms\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_pages\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_plugins\'],\r\n							$dir. DIRECTORY_SEPARATOR .$this->_config[\'subdir_sections\']\r\n						);\r\n			foreach ($tocheck as $curDir) {\r\n				switch ($type) {\r\n					case _CXC_TEMPLATES_IMPORT:\r\n						if (!is_dir($curDir) && !mkdir($curDir, 0777)) {\r\n							echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n							return false;\r\n						}\r\n						if (is_dir($curDir) && !is_readable($curDir)) {\r\n							echo sprintf($this->_config[\'error_cant_read\'], $curDir);\r\n							return false;\r\n						}\r\n						break;\r\n\r\n					case _CXC_TEMPLATES_EXPORT:\r\n						if (!is_dir($curDir) && !mkdir($curDir, 0777)) {\r\n								echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n								return false;\r\n						}\r\n						if (is_dir($curDir) && !is_writable($curDir) && !chmod($theme_dir, 0777)) {\r\n							echo sprintf($this->_config[\'error_cant_write\'], $curDir);\r\n							return false;\r\n						}\r\n						break;\r\n				}\r\n			}\r\n			return true;\r\n		}\r\n\r\n		function checkdirImportZip() {\r\n\r\n			$dir =  $this->_config[\'full_base_path\'];\r\n\r\n			if (!is_dir($dir)) {\r\n			   echo sprintf($this->_config[\'error_missing_dir\'], $dir);\r\n			   return false;\r\n			}\r\n			if (!is_readable($dir)) {\r\n			   echo sprintf($this->_config[\'error_cant_read\'], $dir);\r\n			   return false;\r\n			}\r\n			return true;\r\n		}\r\n\r\n		function checkFileTypeZip() {\r\n			if ($_FILES[\'file\'][\'type\'] != \'application/zip\') {\r\n			   echo sprintf($this->_config[\'error_wrong_text\']);\r\n			   return false;\r\n			}\r\n			return true;\r\n		}\r\n\r\n		/*\r\n			EXPORT FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n		function export($dir = \'\') {\r\n			if (!$this->checkdir($dir, _CXC_TEMPLATES_EXPORT)) {\r\n				return;\r\n			}\r\n\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"exporting-details\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_export_current\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n				<blockquote>\r\n			\';\r\n\r\n			foreach ($this->exportTypes as $type => $config) {\r\n				print \'\r\n					<h1>\'.$config[\'nice_name\'].\'</h1>\r\n					<ul class=\"results\">\r\n				\';\r\n\r\n				$rows = safe_rows($config[\'fields\'], $config[\'table\'], $config[\'filter\']);\r\n\r\n				foreach ($rows as $row) {\r\n					$filename		=	sprintf(\r\n											\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s%s\',\r\n											$this->_config[\'full_base_path\'],\r\n											$dir,\r\n											$config[\'subdir\'],\r\n											$row[\'name\'] . (isset($row[\'type\'])?\'.\'.$row[\'type\']:\'\'),\r\n											$config[\'ext\']\r\n										);\r\n					$nicefilename	=	sprintf(\r\n											\'...\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\'.  DIRECTORY_SEPARATOR  .\'%s%s\',\r\n											$dir,\r\n											$config[\'subdir\'],\r\n											$row[\'name\'] . (isset($row[\'type\'])?\'.\'.$row[\'type\']:\'\'),\r\n											$config[\'ext\']\r\n										);\r\n\r\n					$data = \'\';\r\n\r\n					if (isset($row[\'css\'])) {\r\n						$data = $row[\'css\'];\r\n					} elseif ($type==\'plugins\') {\r\n						$data = base64_encode(serialize($row));\r\n					} elseif ($config[\'subdir\'] != \'sections\') {\r\n						$data = $row[$config[\'data\']];\r\n					}\r\n\r\n					$f = @fopen($filename, \'w+\');\r\n					if ($f) {\r\n						if ($config[\'subdir\'] == \'sections\'){\r\n							$this->writeSectionFiles($f,$row);\r\n						} else {\r\n							fwrite($f,$data);\r\n						}\r\n						fclose($f);\r\n						print \'\r\n						<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_export_success\').\'</span> \'.$config[\'nice_name\'].\' \\\'\'.$row[\'name\'].\'\\\' to \\\'\'.$nicefilename.\'\\\'</li>\r\n						\';\r\n					} else {\r\n						print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_export_failed\').\'</span> \'.$config[\'nice_name\'].\' \\\'\'.$row[\'name\'].\'\\\' to \\\'\'.$nicefilename.\'\\\'</li>\r\n						\';\r\n					}\r\n				}\r\n				print \'\r\n					</ul>\r\n				<br />\r\n				\';\r\n			}\r\n			print \'\r\n				</blockquote>\r\n				</div>\r\n			\';\r\n		}\r\n\r\n		function writeSectionFiles($f,$row){\r\n			if ($row[\'name\'] == \'default\'){\r\n				$name = \"name=default\\n\";\r\n				$page = \"page=\".$row[\'page\'].\"\\n\";\r\n				$css = \"css=\".$row[\'css\'].\"\\n\";\r\n				$in_rss = \"in_rss=1\\n\";\r\n				$on_frontpage = \"on_frontpage=1\\n\";\r\n				$searchable = \"searchable=1\\n\";\r\n				$title = \"title=default\";\r\n				fwrite($f,$name);\r\n				fwrite($f,$page);\r\n				fwrite($f,$css);\r\n				fwrite($f,$in_rss);\r\n				fwrite($f,$on_frontpage);\r\n				fwrite($f,$searchable);\r\n				fwrite($f,$title);\r\n			}else{\r\n				$name = \"name=\".$row[\'name\'].\"\\n\";\r\n				$page = \"page=\".$row[\'page\'].\"\\n\";\r\n				$css = \"css=\".$row[\'css\'].\"\\n\";\r\n				$in_rss = \"in_rss=\".$row[\'in_rss\'].\"\\n\";\r\n				$on_frontpage = \"on_frontpage=\".$row[\'on_frontpage\'].\"\\n\";\r\n				$searchable = \"searchable=\".$row[\'searchable\'].\"\\n\";\r\n				$title = \"title=\".$row[\'title\'];\r\n				fwrite($f,$name);\r\n				fwrite($f,$page);\r\n				fwrite($f,$css);\r\n				fwrite($f,$in_rss);\r\n				fwrite($f,$on_frontpage);\r\n				fwrite($f,$searchable);\r\n				fwrite($f,$title);\r\n			}\r\n		}\r\n\r\n		/*\r\n			IMPORT FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n		function getTemplateList() {\r\n			if (!is_readable($this->_config[\'full_base_path\'])) {\r\n				return array();\r\n			}\r\n			$list = \'\';\r\n			$dir = opendir($this->_config[\'full_base_path\']);\r\n\r\n			while(false !== ($filename = readdir($dir))) {\r\n				if (\r\n					is_dir(\r\n						sprintf(\r\n							\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$filename\r\n						)\r\n					) && $filename != \'.\' && $filename != \'..\'\r\n				) {\r\n					$list[$filename] = $filename;\r\n				}\r\n			}\r\n\r\n			return $list;\r\n		}\r\n\r\n		function import($import_full, $dir) {\r\n			if (!$this->checkdir($dir, _CXC_TEMPLATES_IMPORT)) {\r\n				return;\r\n			}\r\n			$basedir =  sprintf(\r\n							\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$dir\r\n						);\r\n			if (!set_pref(\'cxc_tpl_current\', $dir, \'publish\', 2)){\r\n				print \'\r\n					<ul class=\"results\">\r\n						<li>\'.cxc_templates_gTxt(\'cxc_tpl_update_unable\').\' \'.str_replace(\'_\', \' \', $dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_update_none\').\'</li>\r\n					</ul>\r\n					<br />\r\n				\';\r\n			}\r\n			if (file_exists($basedir. DIRECTORY_SEPARATOR .\'README.txt\')){\r\n				print \'\r\n				<div>\'.\r\n					file_get_contents($basedir. DIRECTORY_SEPARATOR .\'README.txt\').\'\r\n				</div>\r\n				<br />\r\n				\';\r\n			}\r\n\r\n			/*\r\n				Auto export into `preimport-data`\r\n			*/\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"processing-backup\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_processing\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n			\';\r\n			$pre_dir = $this->_config[\'full_base_path\']. DIRECTORY_SEPARATOR .\'preimport-data\';\r\n			if (is_dir($pre_dir)) {\r\n				$objects = scandir($pre_dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($pre_dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->removeDirectory($pre_dir. DIRECTORY_SEPARATOR .$object);\r\n						} else { \r\n							@unlink($pre_dir. DIRECTORY_SEPARATOR .$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n				@rmdir($pre_dir);\r\n			}\r\n			if (!is_dir($pre_dir)){					\r\n				print \'\r\n					<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_remove\').$this->_config[\'base_dir\'].cxc_templates_gTxt(\'cxc_tpl_pre_newback\').\'</p>\r\n				\';\r\n			} else {\r\n				print \'\r\n					<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_failed\').\'</p>\r\n				\';\r\n			}\r\n			print \'\r\n				<p>\'.cxc_templates_gTxt(\'cxc_tpl_pre_note\').\'</p>\r\n				</div>\r\n			\';\r\n\r\n			$this->export(\'preimport-data\');\r\n\r\n			print \'\r\n				<h1 class=\"cxc-tpl-slide-head\"><a id=\"importing-details\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_open_close\').\'\">\'.cxc_templates_gTxt(\'cxc_tpl_template_import\').\' <span class=\"cxc-tpl-capital\">\'.str_replace(\'_\', \' \', $dir).\'</span></a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\r\n				<blockquote>\r\n			\';\r\n\r\n			foreach ($this->exportTypes as $type => $config) {\r\n				print \'\r\n					<h1>\'.$config[\'nice_name\'].\'</h1>\r\n					<ul class=\"results\">\r\n				\';\r\n\r\n				$exportdir =    sprintf(\r\n									\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n									$basedir,\r\n									$config[\'subdir\']\r\n								);\r\n\r\n				$dir	= opendir($exportdir);\r\n				while (false !== ($filename = readdir($dir))) {\r\n					if (preg_match($config[\'regex\'], $filename, $filedata)) {\r\n						$templateName = addslashes($filedata[1]);\r\n						$templateType = (isset($filedata[2]))?$filedata[2]:\'\';\r\n\r\n						$f =    sprintf(\r\n									\'%s\'.  DIRECTORY_SEPARATOR  .\'%s\',\r\n									$exportdir,\r\n									$filename\r\n								);\r\n\r\n						if ($data = file($f)) {\r\n							if ($type == \'css\') {\r\n								$data = doSlash(implode(\'\', $data));\r\n							} elseif ($type == \'plugins\') {\r\n								$data = doSlash(unserialize(base64_decode(implode(\'\', $data))));\r\n							} elseif ($type != \'sections\') {\r\n								$data = addslashes(implode(\'\', $data));\r\n							}\r\n\r\n							if ($type == \'plugins\') {\r\n								$rs = safe_row(\'version, status\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\');\r\n								$set = sprintf($config[\'sql\'], $data[\'status\'], $data[\'author\'], $data[\'author_uri\'], $data[\'version\'], $data[\'description\'], $data[\'help\'], $data[\'code\'], $data[\'code_restore\'], $data[\'code_md5\'], $data[\'type\']);\r\n								if ($rs) {\r\n									if ($rs[\'status\'] == 0 || strcasecmp($data[\'version\'], $rs[\'version\']) < 0) {\r\n										$result = safe_update($config[\'table\'], $set, \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = 1;\r\n									}\r\n									$success = ($result)?1:0;\r\n								} else {\r\n									$result = safe_insert($config[\'table\'], $set.\', `name` = \"\'.$templateName.\'\"\');\r\n									$success = ($result)?1:0;\r\n								}\r\n							} elseif ($type == \'sections\') {\r\n								$rs = safe_row(\'page, css, in_rss, on_frontpage, searchable, title\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\');\r\n								$set = $this->parseSectionFile($config[\'sql\'], $data, $filename, $config[\'ext\']);\r\n								if ($import_full == 0){\r\n									if ($rs) {\r\n										$result = safe_update($config[\'table\'], $set, \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = safe_insert($config[\'table\'], $set.\', `name` = \"\'.$templateName.\'\"\');\r\n									}\r\n								} else {\r\n									$result = 1;\r\n								}\r\n								$success = ($result)?1:0;\r\n							} else {\r\n								if (safe_field(\'name\', $config[\'table\'], \'name=\"\'.$templateName.\'\"\')) {\r\n									if ($import_full == 0){\r\n										$result = safe_update($config[\'table\'], sprintf($config[\'sql\'], $data, $templateType), \'`name` = \"\'.$templateName.\'\"\');\r\n									} else {\r\n										$result = 0;\r\n									}\r\n									$success = ($result)?1:0;\r\n								} else {\r\n									$result = safe_insert($config[\'table\'], sprintf($config[\'sql\'], $data, $templateType).\', `name` = \"\'.$templateName.\'\"\');\r\n									$success = ($result)?1:0;\r\n								}\r\n							}\r\n						}\r\n\r\n						//$success = true;\r\n						if ($success == 1) {\r\n							print \'\r\n						<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_import_success\').\'</span> file \"\'.$filename.\'\"</li>\r\n							\';\r\n						} else {\r\n							if ($type == \'sections\' && $import_full == 1){\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_skipped\').\'</span> file \"\'.$filename.\'\"</li>\r\n								\';\r\n							} elseif ($import_full == 1){\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_skipped\').\'</span> \"\'.$filename.cxc_templates_gTxt(\'cxc_tpl_import_present\').\'</li>\r\n								\';\r\n							} else {\r\n								print \'\r\n						<li><span class=\"cxc-tpl-failure\">\'.cxc_templates_gTxt(\'cxc_tpl_import_failed\').\'</span> file \"\'.$filename.\'\"</li>\r\n								\';\r\n							}\r\n						}\r\n					}\r\n				}\r\n\r\n				print \'\r\n					</ul>\r\n					<br />\r\n				\';\r\n			}\r\n			print \'\r\n				</blockquote>\r\n				</div>\r\n			\';\r\n			if (file_exists($basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\')){\r\n				print \'\r\n				<h1 class=\"cxc-tpl-slide-head\" title=\"\'.cxc_templates_gTxt(\'cxc_tpl_add_click\').\'\"><a id=\"additional-info\">\'.cxc_templates_gTxt(\'cxc_tpl_add_info\').\'</a> &lt;/&gt;</h1>\r\n				<div class=\"cxc-tpl-slide-body\">\'.\r\n					file_get_contents($basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\').\'\r\n				</div>\r\n				\';\r\n			}\r\n		}\r\n\r\n		function importZip($adv_live, $adv_root, $rel_temp_dir, $fileName) {\r\n			global $prefs;\r\n\r\n			if (!$this->checkdirImportZip()) {\r\n					unlink($rel_temp_dir);\r\n				return;\r\n			}\r\n\r\n			if (!$adv_root){\r\n				$templates_base_dir = $this->_config[\'full_base_path\'];\r\n			} else {\r\n				$templates_base_dir = $prefs[\'path_to_site\'];\r\n			}\r\n			$full_temp_dir = $this->_config[\'root_path\']. DIRECTORY_SEPARATOR .\"textpattern\". DIRECTORY_SEPARATOR .$rel_temp_dir;\r\n\r\n			print \'\r\n				<ul class=\"results\">\r\n			\';\r\n			$zip = new cxc_dUnzip2($full_temp_dir);\r\n			if (!empty($zip)) {\r\n				$zip->unzipAll($templates_base_dir);\r\n				$zip->__destroy($full_temp_dir);\r\n				@unlink($full_temp_dir);\r\n				print \'<li><span class=\"cxc-tpl-success\">\'.cxc_templates_gTxt(\'cxc_tpl_upload_success\').\'</span> file \"\'.$fileName.\'\"</li>\';\r\n			} else {\r\n				$zip->__destroy($full_temp_dir);\r\n				@unlink($full_temp_dir);\r\n				print \'<li>\'.cxc_templates_gTxt(\'cxc_tpl_move_fail\').$fileName.cxc_templates_gTxt(\'cxc_tpl_move_temp\').\'</li>\';\r\n			}\r\n			print \'\r\n				</ul>\r\n				<br />\r\n			\';\r\n		}\r\n\r\n		function parseSectionFile($sql,$data,$fname,$ext) {\r\n			$sectionValues = array  (\r\n					// if section title is not within the file, use the filename without the extension\r\n					\'title\'			=> substr($fname,0,-strlen($ext)),\r\n					\'page\'			=> \'default\',\r\n					\'css\'			=> \'default\',\r\n					\'in_rss\'		=> 1,\r\n					\'on_frontpage\'	=> 1,\r\n					\'searchable\'	=> 1,\r\n				);\r\n\r\n			foreach($data as $line) {\r\n				// Split the \'attribute = value\' from within the section parameters file.\r\n				// Ignore whitespace surrounding both the attribute and the value.\r\n				// Limit the split to 2 values (just in case the right part contains another \'=\'\r\n				// which is very unlikely, anyway.\r\n				$splitText = split(\'=\',$line,2);\r\n				$sectionParameter = trim($splitText[0]);\r\n				$sectionValues[$sectionParameter] = trim($splitText[1]);\r\n			}\r\n\r\n			$sectionLine = sprintf($sql, $sectionValues[\'page\'], $sectionValues[\'css\'], $sectionValues[\'in_rss\'], $sectionValues[\'on_frontpage\'], $sectionValues[\'searchable\'], $sectionValues[\'title\']);\r\n			return $sectionLine;\r\n		}\r\n\r\n		/*\r\n			OTHER FUNCTIONS\r\n			----------------------------------------------------------\r\n		*/\r\n\r\n		function cxc_tpl_current($tpl_dir){\r\n			global $prefs;\r\n\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			$readme = $tpl_dir. DIRECTORY_SEPARATOR .\'README.txt\';\r\n			$design = $tpl_dir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\';\r\n			\r\n			if (!empty($prefs[\'cxc_tpl_current\']) && is_dir($tpl_dir)){\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.png\';\r\n				}\r\n	\r\n				print \'<h2 class=\"cxc-tpl-capital\">\'.str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_template\').\'</h2>\';\r\n				if (isset($tpl_preview)) {\r\n					print \'<p class=\"cxc-tpl-default\"><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n				if (file_exists($readme) || file_exists($design)) {\r\n					print form(\'\'.\r\n							graf(\r\n								fInput(\'submit\', \'go\', \'\'.cxc_templates_gTxt(\'cxc_tpl_preview_docs\').\'\', \'smallerbox\').\r\n								eInput(\'cxc_templates\').sInput(\'docs\')\r\n							)\r\n					);\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-smaller\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_note\').str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).cxc_templates_gTxt(\'cxc_tpl_preview_last\').\'</p>\';					\r\n				}\r\n			}\r\n		}\r\n\r\n		function cxc_tpl_preview($dir, $tpl_dir){\r\n			global $prefs;\r\n\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			if (is_dir($tpl_dir)){\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$dir.\'/preview.png\';\r\n				}\r\n	\r\n				if ($dir == \'\') {\r\n					print \'\r\n					<h2>\'.cxc_templates_gTxt(\'cxc_tpl_top_level\').str_replace(\'_\',\' \',$this->_config[\'base_dir\']).cxc_templates_gTxt(\'cxc_tpl_top_dir\').\'</h2>\r\n					<p class=\"cxc-tpl-smaller\">\'.cxc_templates_gTxt(\'cxc_tpl_top_remove\').\'</p>				\r\n					\';\r\n				} else {\r\n					print \'<h2 class=\"cxc-tpl-capital\">\'.str_replace(\'_\',\' \',$dir).\' \'.cxc_templates_gTxt(\'cxc_tpl_template\').\'</h2>\';\r\n				}\r\n				if (isset($tpl_preview)) {\r\n					print \'<p class=\"cxc-tpl-default\"><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n			}\r\n		}\r\n\r\n		function cxc_tpl_docs($tpl_dir){\r\n			global $prefs;\r\n\r\n			$basedir =  sprintf(\r\n							\'%s\'. DIRECTORY_SEPARATOR .\'%s\',\r\n							$this->_config[\'full_base_path\'],\r\n							$tpl_dir\r\n						);\r\n			$tpl_dir = $prefs[\'path_to_site\']. DIRECTORY_SEPARATOR .$this->_config[\'base_dir\']. DIRECTORY_SEPARATOR .$prefs[\'cxc_tpl_current\'];\r\n			$tpl_pre = $tpl_dir. DIRECTORY_SEPARATOR .\'preview\';\r\n			$tpl_alt = str_replace(\'_\',\' \',$prefs[\'cxc_tpl_current\']).\' \'.cxc_templates_gTxt(\'cxc_tpl_preview_text\');\r\n			$readme = $basedir. DIRECTORY_SEPARATOR .\'README.txt\';\r\n			$design = $basedir. DIRECTORY_SEPARATOR .\'DESIGNER.txt\';\r\n\r\n			if (!empty($prefs[\'cxc_tpl_current\']) && $prefs[\'cxc_tpl_current\'] != \'preimport-data\') {\r\n				print \'\r\n					<div class=\"cxc-tpl-preview\">\r\n				\';\r\n\r\n				if ($img_size = @getimagesize($tpl_pre.\'.gif\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.gif\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.jpg\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.jpg\';\r\n				} elseif ($img_size = @getimagesize($tpl_pre.\'.png\')) {\r\n					$tpl_preview = \'../\'.$this->_config[\'base_dir\'].\'/\'.$prefs[\'cxc_tpl_current\'].\'/preview.png\';\r\n				}\r\n	\r\n				print \'<h2 class=\"cxc-tpl-capital\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_img\').\'</h2>\';\r\n				if (isset($tpl_preview)) {\r\n					print \'<p><img src=\"\'.$tpl_preview.\'\" width=\"200px\" height=\"auto\" alt=\"\'.$tpl_alt.\'\" /></p>\';\r\n				} else {\r\n					print \'<p class=\"cxc-tpl-padded\">\'.cxc_templates_gTxt(\'cxc_tpl_preview_none\').\'</p>\';\r\n				}\r\n				print \'\r\n					<br />\r\n					</div>\r\n				\';\r\n			}\r\n			if (file_exists($readme)){\r\n				print \'\r\n					<div>\'.\r\n						file_get_contents($readme).\'\r\n					</div>\r\n					<br />\r\n				\';\r\n			}\r\n			if (file_exists($design)){\r\n				print \'\r\n					<h1>\'.cxc_templates_gTxt(\'cxc_tpl_add_info\').\'</h1>\r\n					<div>\'.\r\n						file_get_contents($design).\'\r\n					</div>\r\n					<br />\r\n				\';\r\n			}			\r\n		}\r\n\r\n		function cxc_tpl_downzip($folder, $to=\'archive.zip\', $basedir) {\r\n			$zip = new cxc_dZip($to, $overwrite=true);\r\n			$php_modules = array_map(\'strtolower\', get_loaded_extensions());\r\n			if (in_array(\'zlib\', $php_modules)) {\r\n				$found = array(rtrim($folder,DIRECTORY_SEPARATOR.\'\\/\'));\r\n				while ($path = each($found)) {\r\n					$path = current($path);\r\n					if (is_dir($path)) {\r\n						foreach (scandir($path) as $subpath) {\r\n							if ($subpath==\'.\'||$subpath==\'..\'||substr($subpath,-2)==DIRECTORY_SEPARATOR.\'.\'||substr($subpath,-3)==DIRECTORY_SEPARATOR.\'..\') continue;\r\n							$found[] = $path.DIRECTORY_SEPARATOR.$subpath;\r\n						}\r\n					} else {\r\n						$zip->addFile($path, substr($path, strlen($basedir)));\r\n					}\r\n				}\r\n				$zip->save();\r\n				if (file_exists($to)) {\r\n					header (\"Content-Type: application/zip\");\r\n					header (\"Content-Disposition: attachment; filename=$to\");\r\n					header (\"Pragma: no-cache\");\r\n					header (\"Expires: 0\");\r\n					if (!readfile($to)){\r\n						print cxc_templates_gTxt(\'cxc_tpl_error_zip_dir\');\r\n					}\r\n					if (!unlink($to)) {\r\n						print cxc_templates_gTxt(\'cxc_tpl_error_zip_remove\');\r\n					}\r\n		\r\n					return true;\r\n				} else {\r\n					print cxc_templates_gTxt(\'cxc_tpl_error_zip_final\');\r\n				}\r\n			} else {\r\n				print cxc_templates_gTxt(\'cxc_tpl_error_zip_failed\').\' \'.$to.\' \'.cxc_templates_gTxt(\'cxc_tpl_error_zip_archive\');\r\n			}\r\n			return false;\r\n		}\r\n\r\n		function removeDirectory($dir) {\r\n			if (is_dir($dir)) {\r\n				$objects = scandir($dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->removeDirectory($dir.\'/\'.$object);\r\n						} else { \r\n							@unlink($dir. DIRECTORY_SEPARATOR .$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n				@rmdir($dir);\r\n			}\r\n		}\r\n\r\n		function writeIndexFiles($dir) {\r\n			if (is_dir($dir)) {\r\n				if (!file_exists($dir. DIRECTORY_SEPARATOR .\'index.html\')) {\r\n					$f = @fopen($dir. DIRECTORY_SEPARATOR .\'index.html\', \'x+\');\r\n					if ($f) {\r\n						fwrite($f,\'<html><body bgcolor=\"#FFFFFF\"></body></html>\');\r\n						fclose($f);\r\n					}\r\n				}\r\n				$objects = scandir($dir);\r\n				foreach ($objects as $object) {\r\n					if ($object != \'.\' && $object != \'..\') {\r\n						if (is_dir($dir. DIRECTORY_SEPARATOR .$object)) {\r\n							$this->writeIndexFiles($dir.\'/\'.$object);\r\n						}\r\n					}\r\n				}\r\n				reset($objects);\r\n			}\r\n		}\r\n	}\r\n\r\n##############################################################\r\n# Class dUnzip2 v2.62\r\n#\r\n#  Author: Alexandre Tedeschi (d)\r\n#  E-Mail: alexandrebr at gmail dot com\r\n#  Londrina - PR / Brazil\r\n#\r\n#  Objective:\r\n#    This class allows programmer to easily unzip files on the fly.\r\n#\r\n#  Requirements:\r\n#    This class requires extension ZLib Enabled. It is default\r\n#    for most site hosts around the world, and for the PHP Win32 dist.\r\n#\r\n#  To do:\r\n#   * Error handling\r\n#   * Write a PHP-Side gzinflate, to completely avoid any external extensions\r\n#   * Write other decompress algorithms\r\n#\r\n#  Methods:\r\n#  * dUnzip2($filename)         - Constructor - Opens $filename\r\n#  * getList([$stopOnFile])     - Retrieve the file list\r\n#  * getExtraInfo($zipfilename) - Retrieve more information about compressed file\r\n#  * getZipInfo([$entry])       - Retrieve ZIP file details.\r\n#  * unzip($zipfilename, [$outfilename, [$applyChmod]]) - Unzip file\r\n#  * unzipAll([$outDir, [$zipDir, [$maintainStructure, [$applyChmod]]]])\r\n#  * close()                    - Close file handler, but keep the list\r\n#  * __destroy()                - Close file handler and release memory\r\n#\r\n#  If you modify this class, or have any ideas to improve it, please contact me!\r\n#  You are allowed to redistribute this class, if you keep my name and contact e-mail on it.\r\n#\r\n#  PLEASE! IF YOU USE THIS CLASS IN ANY OF YOUR PROJECTS, PLEASE LET ME KNOW!\r\n#  If you have problems using it, don\'t think twice before contacting me!\r\n#\r\n##############################################################\r\n\r\n	if(!function_exists(\'file_put_contents\')){\r\n		// If not PHP5, creates a compatible function\r\n		Function file_put_contents($file, $data){\r\n			if($tmp = fopen($file, \"w\")){\r\n				fwrite($tmp, $data);\r\n				fclose($tmp);\r\n				return true;\r\n			}\r\n			echo \"<b>file_put_contents:</b> Cannot create file $file<br>\";\r\n			return false;\r\n		}\r\n	}\r\n	\r\n	class cxc_dUnzip2{\r\n		function getVersion(){\r\n			return \"2.62\";\r\n		}\r\n		// Public\r\n		var $fileName;\r\n		var $lastError;\r\n		var $compressedList; // You will problably use only this one!\r\n		var $centralDirList; // Central dir list... It\'s a kind of \'extra attributes\' for a set of files\r\n		var $endOfCentral;   // End of central dir, contains ZIP Comments\r\n		var $debug;\r\n	\r\n		// Private\r\n		var $fh;\r\n		var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n		var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n		var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n	\r\n		// Public\r\n		function cxc_dUnzip2($fileName){\r\n			$this->fileName       = $fileName;\r\n			$this->compressedList =\r\n			$this->centralDirList =\r\n			$this->endOfCentral   = Array();\r\n		}\r\n	\r\n		function getList($stopOnFile=false){\r\n			if(sizeof($this->compressedList)){\r\n				$this->debugMsg(1, \"Returning already loaded file list.\");\r\n				return $this->compressedList;\r\n			}\r\n	\r\n			// Open file, and set file handler\r\n			$fh = fopen($this->fileName, \"r\");\r\n			$this->fh = &$fh;\r\n			if(!$fh){\r\n				$this->debugMsg(2, \"Failed to load file.\");\r\n				return false;\r\n			}\r\n	\r\n			$this->debugMsg(1, \"Loading list from \'End of Central Dir\' index list...\");\r\n			if(!$this->_loadFileListByEOF($fh, $stopOnFile)){\r\n				$this->debugMsg(1, \"Failed! Trying to load list looking for signatures...\");\r\n				if(!$this->_loadFileListBySignatures($fh, $stopOnFile)){\r\n					$this->debugMsg(1, \"Failed! Could not find any valid header.\");\r\n					$this->debugMsg(2, \"ZIP File is corrupted or empty\");\r\n					return false;\r\n				}\r\n			}\r\n	\r\n			if($this->debug){\r\n				#------- Debug compressedList\r\n				$kkk = 0;\r\n				echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n				foreach($this->compressedList as $fileName=>$item){\r\n					if(!$kkk && $kkk=1){\r\n						echo \"<tr style=\'background: #ADA\'>\";\r\n						foreach($item as $fieldName=>$value)\r\n							echo \"<td>$fieldName</td>\";\r\n						echo \'</tr>\';\r\n					}\r\n					echo \"<tr style=\'background: #CFC\'>\";\r\n					foreach($item as $fieldName=>$value){\r\n						if($fieldName == \'lastmod_datetime\')\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n						else\r\n							echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n					}\r\n					echo \"</tr>\";\r\n				}\r\n				echo \"</table>\";\r\n	\r\n				#------- Debug centralDirList\r\n				$kkk = 0;\r\n				if(sizeof($this->centralDirList)){\r\n					echo \"<table border=\'0\' style=\'font: 11px Verdana; border: 1px solid #000\'>\";\r\n					foreach($this->centralDirList as $fileName=>$item){\r\n						if(!$kkk && $kkk=1){\r\n							echo \"<tr style=\'background: #AAD\'>\";\r\n							foreach($item as $fieldName=>$value)\r\n								echo \"<td>$fieldName</td>\";\r\n							echo \'</tr>\';\r\n						}\r\n						echo \"<tr style=\'background: #CCF\'>\";\r\n						foreach($item as $fieldName=>$value){\r\n							if($fieldName == \'lastmod_datetime\')\r\n								echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>\".date(\"d/m/Y H:i:s\", $value).\"</td>\";\r\n							else\r\n								echo \"<td title=\'$fieldName\' nowrap=\'nowrap\'>$value</td>\";\r\n						}\r\n						echo \"</tr>\";\r\n					}\r\n					echo \"</table>\";\r\n				}\r\n	\r\n				#------- Debug endOfCentral\r\n				$kkk = 0;\r\n				if(sizeof($this->endOfCentral)){\r\n					echo \"<table border=\'0\' style=\'font: 11px Verdana\' style=\'border: 1px solid #000\'>\";\r\n					echo \"<tr style=\'background: #DAA\'><td colspan=\'2\'>dUnzip - End of file</td></tr>\";\r\n					foreach($this->endOfCentral as $field=>$value){\r\n						echo \"<tr>\";\r\n						echo \"<td style=\'background: #FCC\'>$field</td>\";\r\n						echo \"<td style=\'background: #FDD\'>$value</td>\";\r\n						echo \"</tr>\";\r\n					}\r\n					echo \"</table>\";\r\n				}\r\n			}\r\n	\r\n			return $this->compressedList;\r\n		}\r\n		function getExtraInfo($compressedFileName){\r\n			return\r\n				isset($this->centralDirList[$compressedFileName])?\r\n				$this->centralDirList[$compressedFileName]:\r\n				false;\r\n		}\r\n		function getZipInfo($detail=false){\r\n			return $detail?\r\n				$this->endOfCentral[$detail]:\r\n				$this->endOfCentral;\r\n		}\r\n	\r\n		function unzip($compressedFileName, $targetFileName=false, $applyChmod=0777){\r\n			if(!sizeof($this->compressedList)){\r\n				$this->debugMsg(1, \"Trying to unzip before loading file list... Loading it!\");\r\n				$this->getList(false, $compressedFileName);\r\n			}\r\n	\r\n			$fdetails = &$this->compressedList[$compressedFileName];\r\n			if(!isset($this->compressedList[$compressedFileName])){\r\n				$this->debugMsg(2, \"File \'<b>$compressedFileName</b>\' is not compressed in the zip.\");\r\n				return false;\r\n			}\r\n			if(substr($compressedFileName, -1) == \"/\"){\r\n				$this->debugMsg(2, \"Trying to unzip a folder name \'<b>$compressedFileName</b>\'.\");\r\n				return false;\r\n			}\r\n			if(!$fdetails[\'uncompressed_size\']){\r\n				$this->debugMsg(1, \"File \'<b>$compressedFileName</b>\' is empty.\");\r\n				return $targetFileName?\r\n					file_put_contents($targetFileName, \"\"):\r\n					\"\";\r\n			}\r\n	\r\n			fseek($this->fh, $fdetails[\'contents-startOffset\']);\r\n			$ret = $this->uncompress(\r\n					fread($this->fh, $fdetails[\'compressed_size\']),\r\n					$fdetails[\'compression_method\'],\r\n					$fdetails[\'uncompressed_size\'],\r\n					$targetFileName\r\n				);\r\n			if($applyChmod && $targetFileName)\r\n				chmod($targetFileName, 0777);\r\n			\r\n			return $ret;\r\n		}\r\n		function unzipAll($targetDir=false, $baseDir=\"\", $maintainStructure=true, $applyChmod=0777){\r\n			if($targetDir === false)\r\n				$targetDir = dirname($_SERVER[\'SCRIPT_FILENAME\']).\"/\";\r\n			\r\n			$lista = $this->getList();\r\n			if(sizeof($lista)) foreach($lista as $fileName=>$trash){\r\n				$dirname  = dirname($fileName);\r\n				$outDN    = \"$targetDir/$dirname\";\r\n				\r\n				if(substr($dirname, 0, strlen($baseDir)) != $baseDir)\r\n					continue;\r\n				\r\n				if(!is_dir($outDN) && $maintainStructure){\r\n					$str = \"\";\r\n					$folders = explode(\"/\", $dirname);\r\n					foreach($folders as $folder){\r\n						$str = $str?\"$str/$folder\":$folder;\r\n						if(!is_dir(\"$targetDir/$str\")){\r\n							$this->debugMsg(1, \"Creating folder: $targetDir/$str\");\r\n							mkdir(\"$targetDir/$str\");\r\n							if($applyChmod)\r\n								chmod(\"$targetDir/$str\", $applyChmod);\r\n						}\r\n					}\r\n				}\r\n				if(substr($fileName, -1, 1) == \"/\")\r\n					continue;\r\n	\r\n				$maintainStructure?\r\n					$this->unzip($fileName, \"$targetDir/$fileName\", $applyChmod):\r\n					$this->unzip($fileName, \"$targetDir/\".basename($fileName), $applyChmod);\r\n			}\r\n		}\r\n		\r\n		function close(){     // Free the file resource\r\n			if($this->fh)\r\n				fclose($this->fh);\r\n		}\r\n		function __destroy(){ \r\n			$this->close();\r\n		}\r\n	\r\n		// Private (you should NOT call these methods):\r\n		function uncompress(&$content, $mode, $uncompressedSize, $targetFileName=false){\r\n			switch($mode){\r\n				case 0:\r\n					// Not compressed\r\n					return $targetFileName?\r\n						file_put_contents($targetFileName, $content):\r\n						$content;\r\n				case 1:\r\n					$this->debugMsg(2, \"Shrunk mode is not supported... yet?\");\r\n					return false;\r\n				case 2:\r\n				case 3:\r\n				case 4:\r\n				case 5:\r\n					$this->debugMsg(2, \"Compression factor \".($mode-1).\" is not supported... yet?\");\r\n					return false;\r\n				case 6:\r\n					$this->debugMsg(2, \"Implode is not supported... yet?\");\r\n					return false;\r\n				case 7:\r\n					$this->debugMsg(2, \"Tokenizing compression algorithm is not supported... yet?\");\r\n					return false;\r\n				case 8:\r\n					// Deflate\r\n					return $targetFileName?\r\n						file_put_contents($targetFileName, gzinflate($content, $uncompressedSize)):\r\n						gzinflate($content, $uncompressedSize);\r\n				case 9:\r\n					$this->debugMsg(2, \"Enhanced Deflating is not supported... yet?\");\r\n					return false;\r\n				case 10:\r\n					$this->debugMsg(2, \"PKWARE Date Compression Library Impoloding is not supported... yet?\");\r\n					return false;\r\n			   case 12:\r\n				   // Bzip2\r\n				   return $targetFileName?\r\n					   file_put_contents($targetFileName, bzdecompress($content)):\r\n					   bzdecompress($content);\r\n				case 18:\r\n					$this->debugMsg(2, \"IBM TERSE is not supported... yet?\");\r\n					return false;\r\n				default:\r\n					$this->debugMsg(2, \"Unknown uncompress method: $mode\");\r\n					return false;\r\n			}\r\n		}\r\n		function debugMsg($level, $string){\r\n			if($this->debug){\r\n				if($level == 1)\r\n					echo \"<b style=\'color: #777\'>dUnzip2:</b> $string<br>\";\r\n				\r\n				if($level == 2)\r\n					echo \"<b style=\'color: #F00\'>dUnzip2:</b> $string<br>\";\r\n			}\r\n			$this->lastError = $string;\r\n		}\r\n		function getLastError(){\r\n			return $this->lastError;\r\n		}\r\n	\r\n		function _loadFileListByEOF(&$fh, $stopOnFile=false){\r\n			// Check if there\'s a valid Central Dir signature.\r\n			// Let\'s consider a file comment smaller than 1024 characters...\r\n			// Actually, it length can be 65536.. But we\'re not going to support it.\r\n	\r\n			for($x = 0; $x < 1024; $x++){\r\n				fseek($fh, -22-$x, SEEK_END);\r\n	\r\n				$signature = fread($fh, 4);\r\n				if($signature == $this->dirSignatureE){\r\n					// If found EOF Central Dir\r\n					$eodir[\'disk_number_this\']   = unpack(\"v\", fread($fh, 2)); // number of this disk\r\n					$eodir[\'disk_number\']        = unpack(\"v\", fread($fh, 2)); // number of the disk with the start of the central directory\r\n					$eodir[\'total_entries_this\'] = unpack(\"v\", fread($fh, 2)); // total number of entries in the central dir on this disk\r\n					$eodir[\'total_entries\']      = unpack(\"v\", fread($fh, 2)); // total number of entries in\r\n					$eodir[\'size_of_cd\']         = unpack(\"V\", fread($fh, 4)); // size of the central directory\r\n					$eodir[\'offset_start_cd\']    = unpack(\"V\", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number\r\n					$zipFileCommentLenght        = unpack(\"v\", fread($fh, 2)); // zipfile comment length\r\n					$eodir[\'zipfile_comment\']    = $zipFileCommentLenght[1]?fread($fh, $zipFileCommentLenght[1]):\'\'; // zipfile comment\r\n					$this->endOfCentral = Array(\r\n						\'disk_number_this\'=>$eodir[\'disk_number_this\'][1],\r\n						\'disk_number\'=>$eodir[\'disk_number\'][1],\r\n						\'total_entries_this\'=>$eodir[\'total_entries_this\'][1],\r\n						\'total_entries\'=>$eodir[\'total_entries\'][1],\r\n						\'size_of_cd\'=>$eodir[\'size_of_cd\'][1],\r\n						\'offset_start_cd\'=>$eodir[\'offset_start_cd\'][1],\r\n						\'zipfile_comment\'=>$eodir[\'zipfile_comment\'],\r\n					);\r\n	\r\n					// Then, load file list\r\n					fseek($fh, $this->endOfCentral[\'offset_start_cd\']);\r\n					$signature = fread($fh, 4);\r\n	\r\n					while($signature == $this->dirSignature){\r\n						$dir[\'version_madeby\']      = unpack(\"v\", fread($fh, 2)); // version made by\r\n						$dir[\'version_needed\']      = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n						$dir[\'general_bit_flag\']    = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n						$dir[\'compression_method\']  = unpack(\"v\", fread($fh, 2)); // compression method\r\n						$dir[\'lastmod_time\']        = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n						$dir[\'lastmod_date\']        = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n						$dir[\'crc-32\']              = fread($fh, 4);              // crc-32\r\n						$dir[\'compressed_size\']     = unpack(\"V\", fread($fh, 4)); // compressed size\r\n						$dir[\'uncompressed_size\']   = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n						$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n						$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n						$fileCommentLength          = unpack(\"v\", fread($fh, 2)); // file comment length\r\n						$dir[\'disk_number_start\']   = unpack(\"v\", fread($fh, 2)); // disk number start\r\n						$dir[\'internal_attributes\'] = unpack(\"v\", fread($fh, 2)); // internal file attributes-byte1\r\n						$dir[\'external_attributes1\']= unpack(\"v\", fread($fh, 2)); // external file attributes-byte2\r\n						$dir[\'external_attributes2\']= unpack(\"v\", fread($fh, 2)); // external file attributes\r\n						$dir[\'relative_offset\']     = unpack(\"V\", fread($fh, 4)); // relative offset of local header\r\n						$dir[\'file_name\']           = fread($fh, $fileNameLength[1]);                             // filename\r\n						$dir[\'extra_field\']         = $extraFieldLength[1] ?fread($fh, $extraFieldLength[1]) :\'\'; // extra field\r\n						$dir[\'file_comment\']        = $fileCommentLength[1]?fread($fh, $fileCommentLength[1]):\'\'; // file comment\r\n	\r\n						// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n						$BINlastmod_date = str_pad(decbin($dir[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n						$BINlastmod_time = str_pad(decbin($dir[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n						$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n						$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n						$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n						$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n						$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n						$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n	\r\n						// Some protection agains attacks...\r\n						if(!$dir[\'file_name\'] = $this->_protect($dir[\'file_name\']))\r\n							continue;\r\n	\r\n						$this->centralDirList[$dir[\'file_name\']] = Array(\r\n							\'version_madeby\'=>$dir[\'version_madeby\'][1],\r\n							\'version_needed\'=>$dir[\'version_needed\'][1],\r\n							\'general_bit_flag\'=>str_pad(decbin($dir[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n							\'compression_method\'=>$dir[\'compression_method\'][1],\r\n							\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n							\'crc-32\'            =>str_pad(dechex(ord($dir[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n												  str_pad(dechex(ord($dir[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n							\'compressed_size\'=>$dir[\'compressed_size\'][1],\r\n							\'uncompressed_size\'=>$dir[\'uncompressed_size\'][1],\r\n							\'disk_number_start\'=>$dir[\'disk_number_start\'][1],\r\n							\'internal_attributes\'=>$dir[\'internal_attributes\'][1],\r\n							\'external_attributes1\'=>$dir[\'external_attributes1\'][1],\r\n							\'external_attributes2\'=>$dir[\'external_attributes2\'][1],\r\n							\'relative_offset\'=>$dir[\'relative_offset\'][1],\r\n							\'file_name\'=>$dir[\'file_name\'],\r\n							\'extra_field\'=>$dir[\'extra_field\'],\r\n							\'file_comment\'=>$dir[\'file_comment\'],\r\n						);\r\n						$signature = fread($fh, 4);\r\n					}\r\n	\r\n					// If loaded centralDirs, then try to identify the offsetPosition of the compressed data.\r\n					if($this->centralDirList) foreach($this->centralDirList as $filename=>$details){\r\n						$i = $this->_getFileHeaderInformation($fh, $details[\'relative_offset\']);\r\n						$this->compressedList[$filename][\'file_name\']          = $filename;\r\n						$this->compressedList[$filename][\'compression_method\'] = $details[\'compression_method\'];\r\n						$this->compressedList[$filename][\'version_needed\']     = $details[\'version_needed\'];\r\n						$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n						$this->compressedList[$filename][\'crc-32\']             = $details[\'crc-32\'];\r\n						$this->compressedList[$filename][\'compressed_size\']    = $details[\'compressed_size\'];\r\n						$this->compressedList[$filename][\'uncompressed_size\']  = $details[\'uncompressed_size\'];\r\n						$this->compressedList[$filename][\'lastmod_datetime\']   = $details[\'lastmod_datetime\'];\r\n						$this->compressedList[$filename][\'extra_field\']        = $i[\'extra_field\'];\r\n						$this->compressedList[$filename][\'contents-startOffset\']=$i[\'contents-startOffset\'];\r\n						if(strtolower($stopOnFile) == strtolower($filename))\r\n							break;\r\n					}\r\n					return true;\r\n				}\r\n			}\r\n			return false;\r\n		}\r\n		function _loadFileListBySignatures(&$fh, $stopOnFile=false){\r\n			fseek($fh, 0);\r\n			\r\n			$return = false;\r\n			for(;;){\r\n				$details = $this->_getFileHeaderInformation($fh);\r\n				if(!$details){\r\n					$this->debugMsg(1, \"Invalid signature. Trying to verify if is old style Data Descriptor...\");\r\n					fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again)\r\n					$details = $this->_getFileHeaderInformation($fh);\r\n				}\r\n				if(!$details){\r\n					$this->debugMsg(1, \"Still invalid signature. Probably reached the end of the file.\");\r\n					break;\r\n				}\r\n				$filename = $details[\'file_name\'];\r\n				$this->compressedList[$filename] = $details;\r\n				$return = true;\r\n				if(strtolower($stopOnFile) == strtolower($filename))\r\n					break;\r\n			}\r\n			\r\n			return $return;\r\n		}\r\n		function _getFileHeaderInformation(&$fh, $startOffset=false){\r\n			if($startOffset !== false)\r\n				fseek($fh, $startOffset);\r\n			\r\n			$signature = fread($fh, 4);\r\n			if($signature == $this->zipSignature){\r\n				# $this->debugMsg(1, \"Zip Signature!\");\r\n				\r\n				// Get information about the zipped file\r\n				$file[\'version_needed\']     = unpack(\"v\", fread($fh, 2)); // version needed to extract\r\n				$file[\'general_bit_flag\']   = unpack(\"v\", fread($fh, 2)); // general purpose bit flag\r\n				$file[\'compression_method\'] = unpack(\"v\", fread($fh, 2)); // compression method\r\n				$file[\'lastmod_time\']       = unpack(\"v\", fread($fh, 2)); // last mod file time\r\n				$file[\'lastmod_date\']       = unpack(\"v\", fread($fh, 2)); // last mod file date\r\n				$file[\'crc-32\']             = fread($fh, 4);              // crc-32\r\n				$file[\'compressed_size\']    = unpack(\"V\", fread($fh, 4)); // compressed size\r\n				$file[\'uncompressed_size\']  = unpack(\"V\", fread($fh, 4)); // uncompressed size\r\n				$fileNameLength             = unpack(\"v\", fread($fh, 2)); // filename length\r\n				$extraFieldLength           = unpack(\"v\", fread($fh, 2)); // extra field length\r\n				$file[\'file_name\']          = fread($fh, $fileNameLength[1]); // filename\r\n				$file[\'extra_field\']        = $extraFieldLength[1]?fread($fh, $extraFieldLength[1]):\'\'; // extra field\r\n				$file[\'contents-startOffset\']= ftell($fh);\r\n				\r\n				// Bypass the whole compressed contents, and look for the next file\r\n				fseek($fh, $file[\'compressed_size\'][1], SEEK_CUR);\r\n				\r\n				// Convert the date and time, from MS-DOS format to UNIX Timestamp\r\n				$BINlastmod_date = str_pad(decbin($file[\'lastmod_date\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n				$BINlastmod_time = str_pad(decbin($file[\'lastmod_time\'][1]), 16, \'0\', STR_PAD_LEFT);\r\n				$lastmod_dateY = bindec(substr($BINlastmod_date,  0, 7))+1980;\r\n				$lastmod_dateM = bindec(substr($BINlastmod_date,  7, 4));\r\n				$lastmod_dateD = bindec(substr($BINlastmod_date, 11, 5));\r\n				$lastmod_timeH = bindec(substr($BINlastmod_time,   0, 5));\r\n				$lastmod_timeM = bindec(substr($BINlastmod_time,   5, 6));\r\n				$lastmod_timeS = bindec(substr($BINlastmod_time,  11, 5));\r\n				\r\n				// Some protection agains attacks...\r\n				if(!$file[\'file_name\'] = $this->_protect($file[\'file_name\']))\r\n					return;\r\n				\r\n				// Mount file table\r\n				$i = Array(\r\n					\'file_name\'         =>$file[\'file_name\'],\r\n					\'compression_method\'=>$file[\'compression_method\'][1],\r\n					\'version_needed\'    =>$file[\'version_needed\'][1],\r\n					\'lastmod_datetime\'  =>mktime($lastmod_timeH, $lastmod_timeM, $lastmod_timeS, $lastmod_dateM, $lastmod_dateD, $lastmod_dateY),\r\n					\'crc-32\'            =>str_pad(dechex(ord($file[\'crc-32\'][3])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][2])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][1])), 2, \'0\', STR_PAD_LEFT).\r\n										  str_pad(dechex(ord($file[\'crc-32\'][0])), 2, \'0\', STR_PAD_LEFT),\r\n					\'compressed_size\'   =>$file[\'compressed_size\'][1],\r\n					\'uncompressed_size\' =>$file[\'uncompressed_size\'][1],\r\n					\'extra_field\'       =>$file[\'extra_field\'],\r\n					\'general_bit_flag\'  =>str_pad(decbin($file[\'general_bit_flag\'][1]), 8, \'0\', STR_PAD_LEFT),\r\n					\'contents-startOffset\'=>$file[\'contents-startOffset\']\r\n				);\r\n				return $i;\r\n			}\r\n			return false;\r\n		}\r\n		\r\n		function _protect($fullPath){\r\n			// Known hack-attacks (filename like):\r\n			//   /home/usr\r\n			//   ../../home/usr\r\n			//   folder/../../../home/usr\r\n			//   sample/(x0)../home/usr\r\n			\r\n			$fullPath = strtr($fullPath, \":*<>|\\\"\\x0\\\\\", \"......./\");\r\n			while($fullPath[0] == \"/\")\r\n				$fullPath = substr($fullPath, 1);\r\n			\r\n			if(substr($fullPath, -1) == \"/\"){\r\n				$base     = \'\';\r\n				$fullPath = substr($fullPath, 0, -1);\r\n			}\r\n			else{\r\n				$base     = basename($fullPath);\r\n				$fullPath = dirname($fullPath);\r\n			}\r\n			\r\n			$parts   = explode(\"/\", $fullPath);\r\n			$lastIdx = false;\r\n			foreach($parts as $idx=>$part){\r\n				if($part == \".\")\r\n					unset($parts[$idx]);\r\n				elseif($part == \"..\"){\r\n					unset($parts[$idx]);\r\n					if($lastIdx !== false){\r\n						unset($parts[$lastIdx]);\r\n					}\r\n				}\r\n				elseif($part === \'\'){\r\n					unset($parts[$idx]);\r\n				}\r\n				else{\r\n					$lastIdx = $idx;\r\n				}\r\n			}\r\n	\r\n			$fullPath = sizeof($parts)?implode(\"/\", $parts).\"/\":\"\";\r\n			return $fullPath.$base;\r\n		}\r\n	}\r\n	\r\n	class cxc_dZip{\r\n		var $filename;\r\n		var $overwrite;\r\n		\r\n		var $zipSignature = \"\\x50\\x4b\\x03\\x04\"; // local file header signature\r\n		var $dirSignature = \"\\x50\\x4b\\x01\\x02\"; // central dir header signature\r\n		var $dirSignatureE= \"\\x50\\x4b\\x05\\x06\"; // end of central dir signature\r\n		var $files_count  = 0;\r\n		var $fh;\r\n		\r\n		function cxc_dZip($filename, $overwrite=true){\r\n			$this->filename  = $filename;\r\n			$this->overwrite = $overwrite;\r\n		}\r\n		function addDir($dirname, $fileComments=\'\'){\r\n			if(substr($dirname, -1) != \'/\')\r\n				$dirname .= \'/\';\r\n			$this->addFile(false, $dirname, $fileComments);\r\n		}\r\n		function addFile($filename, $cfilename, $fileComments=\'\', $data=false){\r\n			if(!($fh = &$this->fh))\r\n				$fh = fopen($this->filename, $this->overwrite?\'wb\':\'a+b\');\r\n			\r\n			// $filename can be a local file OR the data wich will be compressed\r\n			if(substr($cfilename, -1)==\'/\'){\r\n				$details[\'uncsize\'] = 0;\r\n				$data = \'\';\r\n			}\r\n			elseif(file_exists($filename)){\r\n				$details[\'uncsize\'] = filesize($filename);\r\n				$data = file_get_contents($filename);\r\n			}\r\n			elseif($filename){\r\n				echo \"<b>Cannot add $filename. File not found</b><br>\";\r\n				return false;\r\n			}\r\n			else{\r\n				$details[\'uncsize\'] = strlen($filename);\r\n				// DATA is given.. use it! :|\r\n			}\r\n	\r\n			// if data to compress is too small, just store it\r\n			if($details[\'uncsize\'] < 256){\r\n				$details[\'comsize\'] = $details[\'uncsize\'];\r\n				$details[\'vneeded\'] = 10;\r\n				$details[\'cmethod\'] = 0;\r\n				$zdata = &$data;\r\n			}\r\n			else{ // otherwise, compress it\r\n				$zdata = gzcompress($data);\r\n				$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)\r\n				$details[\'comsize\'] = strlen($zdata);\r\n				$details[\'vneeded\'] = 10;\r\n				$details[\'cmethod\'] = 8;\r\n			}\r\n			\r\n			$details[\'bitflag\'] = 0;\r\n			$details[\'crc_32\']  = crc32($data);\r\n			\r\n			// Convert date and time to DOS Format, and set then\r\n			$lastmod_timeS  = str_pad(decbin(date(\'s\')>=32?date(\'s\')-32:date(\'s\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_timeM  = str_pad(decbin(date(\'i\')), 6, \'0\', STR_PAD_LEFT);\r\n			$lastmod_timeH  = str_pad(decbin(date(\'H\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateD  = str_pad(decbin(date(\'d\')), 5, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateM  = str_pad(decbin(date(\'m\')), 4, \'0\', STR_PAD_LEFT);\r\n			$lastmod_dateY  = str_pad(decbin(date(\'Y\')-1980), 7, \'0\', STR_PAD_LEFT);\r\n			\r\n			# echo \"ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (\".date(\"s H H\").\")\\n\";\r\n			# echo \"ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (\".date(\"d m Y\").\")\\n\";\r\n			$details[\'modtime\'] = bindec(\"$lastmod_timeH$lastmod_timeM$lastmod_timeS\");\r\n			$details[\'moddate\'] = bindec(\"$lastmod_dateY$lastmod_dateM$lastmod_dateD\");\r\n			\r\n			$details[\'offset\'] = ftell($fh);\r\n			fwrite($fh, $this->zipSignature);\r\n			fwrite($fh, pack(\'s\', $details[\'vneeded\'])); // version_needed\r\n			fwrite($fh, pack(\'s\', $details[\'bitflag\'])); // general_bit_flag\r\n			fwrite($fh, pack(\'s\', $details[\'cmethod\'])); // compression_method\r\n			fwrite($fh, pack(\'s\', $details[\'modtime\'])); // lastmod_time\r\n			fwrite($fh, pack(\'s\', $details[\'moddate\'])); // lastmod_date\r\n			fwrite($fh, pack(\'V\', $details[\'crc_32\']));  // crc-32\r\n			fwrite($fh, pack(\'I\', $details[\'comsize\'])); // compressed_size\r\n			fwrite($fh, pack(\'I\', $details[\'uncsize\'])); // uncompressed_size\r\n			fwrite($fh, pack(\'s\', strlen($cfilename)));   // file_name_length\r\n			fwrite($fh, pack(\'s\', 0));  // extra_field_length\r\n			fwrite($fh, $cfilename);    // file_name\r\n			// ignoring extra_field\r\n			fwrite($fh, $zdata);\r\n			\r\n			// Append it to central dir\r\n			$details[\'external_attributes\']  = (substr($cfilename, -1)==\'/\'&&!$zdata)?16:32; // Directory or file name\r\n			$details[\'comments\']             = $fileComments;\r\n			$this->appendCentralDir($cfilename, $details);\r\n			$this->files_count++;\r\n		}\r\n		function setExtra($filename, $property, $value){\r\n			$this->centraldirs[$filename][$property] = $value;\r\n		}\r\n		function save($zipComments=\'\'){\r\n			if(!($fh = &$this->fh))\r\n				$fh = fopen($this->filename, $this->overwrite?\'w\':\'a+\');\r\n			\r\n			$cdrec = \"\";\r\n			foreach($this->centraldirs as $filename=>$cd){\r\n				$cdrec .= $this->dirSignature;\r\n				$cdrec .= \"\\x0\\x0\";                  // version made by\r\n				$cdrec .= pack(\'v\', $cd[\'vneeded\']); // version needed to extract\r\n				$cdrec .= \"\\x0\\x0\";                  // general bit flag\r\n				$cdrec .= pack(\'v\', $cd[\'cmethod\']); // compression method\r\n				$cdrec .= pack(\'v\', $cd[\'modtime\']); // lastmod time\r\n				$cdrec .= pack(\'v\', $cd[\'moddate\']); // lastmod date\r\n				$cdrec .= pack(\'V\', $cd[\'crc_32\']);  // crc32\r\n				$cdrec .= pack(\'V\', $cd[\'comsize\']); // compressed filesize\r\n				$cdrec .= pack(\'V\', $cd[\'uncsize\']); // uncompressed filesize\r\n				$cdrec .= pack(\'v\', strlen($filename)); // file comment length\r\n				$cdrec .= pack(\'v\', 0);                // extra field length\r\n				$cdrec .= pack(\'v\', strlen($cd[\'comments\'])); // file comment length\r\n				$cdrec .= pack(\'v\', 0); // disk number start\r\n				$cdrec .= pack(\'v\', 0); // internal file attributes\r\n				$cdrec .= pack(\'V\', $cd[\'external_attributes\']); // internal file attributes\r\n				$cdrec .= pack(\'V\', $cd[\'offset\']); // relative offset of local header\r\n				$cdrec .= $filename;\r\n				$cdrec .= $cd[\'comments\'];\r\n			}\r\n			$before_cd = ftell($fh);\r\n			fwrite($fh, $cdrec);\r\n			\r\n			// end of central dir\r\n			fwrite($fh, $this->dirSignatureE);\r\n			fwrite($fh, pack(\'v\', 0)); // number of this disk\r\n			fwrite($fh, pack(\'v\', 0)); // number of the disk with the start of the central directory\r\n			fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries \"on this disk\" \r\n			fwrite($fh, pack(\'v\', $this->files_count)); // total # of entries overall \r\n			fwrite($fh, pack(\'V\', strlen($cdrec)));     // size of central dir \r\n			fwrite($fh, pack(\'V\', $before_cd));         // offset to start of central dir\r\n			fwrite($fh, pack(\'v\', strlen($zipComments))); // .zip file comment length\r\n			fwrite($fh, $zipComments);\r\n			\r\n			fclose($fh);\r\n		}\r\n		\r\n		// Private\r\n		function appendCentralDir($filename,$properties){\r\n			$this->centraldirs[$filename] = $properties;\r\n		}\r\n	} \r\n\r\n/*\r\n	PLUGIN CODE::LANGUAGE SUPPORT = cxc_templates_gTxt(\'\')\r\n	-------------------------------------------------------------\r\n*/\r\n	function cxc_templates_gTxt($what, $atts = array()) {\r\n		$lang = array(\r\n			\'cxc_tpl_templates_tab\' => \'Templates\',\r\n			\'cxc_tpl_process\' => \'Process Templates\',\r\n			\'cxc_tpl_preferences\' => \'Plugin Preferences\',\r\n			\'cxc_tpl_dbupdate\' => \'<span class=\"cxc-tpl-failure\">Database update failed</span> entry for current template will be unavailable.\',\r\n			\'cxc_tpl_return\' => \'Click here to return to the template manager.\',\r\n			\'cxc_tpl_delete_confirm\' => \'Delete Directory Confirmation\',\r\n			\'cxc_tpl_remove_before\' => \'This will completely remove the \"<strong>\',\r\n			\'cxc_tpl_remove_after\' => \'</strong>\" directory from your site, click \"<strong>GO</strong>\" to continue or use the link below to return to the template manager.\',\r\n			\'cxc_tpl_removed\' => \'Template Removed\',\r\n			\'cxc_tpl_the_capital\' => \'The <span class=\"cxc-tpl-capital\"><strong>\',\r\n			\'cxc_tpl_removed_from\' => \'</strong></span> template files have been successfully removed from the \"<strong>\',\r\n			\'cxc_tpl_removed_dir\' => \'</strong>\" directory.\',\r\n			\'cxc_tpl_remove_failed\' => \'Unable to Remove Template\',\r\n			\'cxc_tpl_not_removed\' => \'</span> template directory was not removed, this might be due to the server configuration of your host and removal of templates may need to be done manually\',\r\n			\'cxc_tpl_template_import\' => \'Template Import:\',\r\n			\'cxc_tpl_import_failed_before\' => \'<span class=\"cxc-tpl-failure\">Failed importing</span> the \\\'<span class=\"cxc-tpl-capital\">\',\r\n			\'cxc_tpl_import_failed_after\' => \'</span>\\\' template files\',\r\n			\'cxc_tpl_upload_import_fail\' => \'It was not possible to import the uploaded template because the Zip file contained more than one template or it was already present in the templates directory.\',\r\n			\'cxc_tpl_import_template\' => \'Import Template\',\r\n			\'cxc_tpl_none_before\' => \'There are no templates installed in the \\\'<strong>\',\r\n			\'cxc_tpl_none_after\' => \'</strong>\\\' directory, please clone your current template or upload a new one.\',\r\n			\'cxc_tpl_alternate_dir\' => \'Use Alternate Template Directory (<em class=\"cxc-tpl-failure\">not recommended</em>)\',\r\n			\'cxc_tpl_alt_adjust\' => \'You will need to adjust the location to be used as the template directory by modifying <code>$cxc_templates[\\\'base_dir\\\']</code> in the plugin\\\'s code. After you have adjusted the plugin\\\'s code it will try to automatically create the chosen directory for you if not already present in your webroot.\',\r\n			\'cxc_tpl_alt_note\' => \'<strong>Note:</strong> this could affect template assets and result in broken links to css, images, JS and other template files so it is recommended you use the default or select the directory used by the templates designer.\',\r\n			\'cxc_tpl_import_which\' => \'Which template would you like to import?\',\r\n			\'cxc_tpl_import_safe_mode\' => \'Use Import Safe Mode (<em class=\"cxc-tpl-failure\">non-destructive</em>)\',\r\n			\'cxc_tpl_export_template\' => \'Export Template\',\r\n			\'cxc_tpl_export_name\' => \'Choose a name for the exported template.\',\r\n			\'cxc_tpl_delete_template\' => \'Delete Template\',\r\n			\'cxc_tpl_zip_template\' => \'Zip Project Folder\',\r\n			\'cxc_tpl_upload_template\' => \'Upload Template\',\r\n			\'cxc_tpl_upload_select\' => \'Please select the template you would like to upload.\',\r\n			\'cxc_tpl_upload_import\' => \'Import Uploaded Template\',\r\n			\'cxc_tpl_advanced_options\' => \'Advanced Options\',\r\n			\'cxc_tpl_root_install\' => \'Web Root Installation (<em class=\"cxc-tpl-failure\">not recommended</em>)\',\r\n			\'cxc_tpl_advanced_note\' => \'Note:</strong> <em>do not use unless required or you know what you are doing!\',\r\n			\'cxc_tpl_feature_unavailable\' => \'Feature Unavailable\',\r\n			\'cxc_tpl_zlib_enabled_text\' => \'If you are seeing this error it means your host compiled version of PHP does not include the ZLib modules. This feature requires the extension ZLib to be enabled, it is compiled by default for most site hosts around the world, and for the PHP Win32 distributions. If this is unavailable you should try speaking to your hosts to have it enabled, if they are not able to provide this for you, I recommend considering a new host. If you choose to remain with them you will have to unzip locally and FTP templates into your template directory, sorry for the inconvenience.\',\r\n			\'cxc_tpl_req_dir_created\' => \'Required Directories Created\',\r\n			\'cxc_tpl_reload_click\' => \'Click here to reload this page\',\r\n			\'cxc_tpl_reload_display\' => \'and display the template manager.\',\r\n			\'cxc_tpl_req_dir_missing\' => \'Required Directories Missing\',\r\n			\'cxc_tpl_req_dir_this\' => \'This plugin requires the `<strong>\',\r\n			\'cxc_tpl_req_dir_theme\' => \'</strong>` directory to be located in the webroot for it to function properly and the `<strong>\',\r\n			\'cxc_tpl_req_dir_cache\' => \'</strong>` directory to be in the textpattern directory, either `<strong>\',\r\n			\'cxc_tpl_req_dir_or\' => \'</strong>` directory or `<strong>\',\r\n			\'cxc_tpl_req_dir_auto\' => \'</strong>` directory does not exist, and could not be automatically created.\',\r\n			\'cxc_tpl_req_dir_manual\' => \'Please create these directories manually using your FTP client, hosting control panel or by running something like ...\',\r\n			\'cxc_tpl_req_dir_after\' => \'After you have created the missing directories, return to (or reload) this page to display the template manager. You could also adjust the plugin\\\'s directory by modifying <code>$cxc_templates[\\\'base_dir\\\']</code> and/or <code>$cxc_templates[\\\'cache_dir\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_add_security\' => \'For additional security you may also want to include empty index.html files or adjust your .htaccess for the `<strong>\',\r\n			\'cxc_tpl_add_empty_dir\' => \'</strong>` directory.\',\r\n			\'cxc_tpl_webroot\' => \'webroot\',\r\n			\'cxc_tpl_req_directory\' => \'</strong>` directory to be located in the `<strong>\',\r\n			\'cxc_tpl_req_properly\' => \'</strong>` directory for it to function properly, the `<strong>\',\r\n			\'cxc_tpl_req_manual\' => \'Please create the directory manually using your FTP client, hosting control panel or by running something like ...\',\r\n			\'cxc_tpl_req_after\' => \'After you have created the missing directory, return to (or reload) this page to display the template manager. You could also adjust the plugin\\\'s directory by modifying <code>$cxc_templates[\\\'\',\r\n			\'cxc_tpl_req_code\' => \'\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_secure_dir\' => \'For additional security you may also want to include empty index.html files or adjust your .htaccess for the directories.\',\r\n			\'cxc_tpl_template_dir\' => \'The template directory `<strong>\',\r\n			\'cxc_tpl_would_you\' => \'Would you mind creating it yourself by running something like ...\',\r\n			\'cxc_tpl_should_fix\' => \'That should fix the issue. You could also adjust the plugin\\\'s directory by modifying <code>\\$cxc_templates[\\\'base_dir\\\']</code> in the plugin\\\'s code.\',\r\n			\'cxc_tpl_not_writable\' => \'Template Directory Not Writable\',\r\n			\'cxc_tpl_chmod_write\' => \'I can\\\'t seem to write to the template directory \\\'<strong>%1\\$s</strong>\\\'.  Would you mind running something like ...\',\r\n			\'cxc_tpl_not_readable\' => \'Template Directory Not Readable\',\r\n			\'cxc_tpl_chmod_read\' => \'I can\\\'t seem to read from the template directory \\\'<strong>%1\\$s</strong>\\\'.  Would you mind running something like ...\',\r\n			\'cxc_tpl_problem_fix\' => \'... to fix the problem?\',\r\n			\'cxc_tpl_wrong_file_type\' => \'Unsupported File Type\',\r\n			\'cxc_tpl_corrupt_file\' => \'The file is either corrupt or is an unsupported file type, only zip files are currently supported by this plugin.\',\r\n			\'cxc_tpl_open_close\' => \'Click here to open/close detailed list of events.\',\r\n			\'cxc_tpl_export_current\' => \'Template Export: Current\',\r\n			\'cxc_tpl_export_success\' => \'Successfully exported\',\r\n			\'cxc_tpl_export_failed\' => \'Failed exporting\',\r\n			\'cxc_tpl_update_unable\' => \'<span class=\"cxc-tpl-failure\">Unable to update</span> entry for current template in the database,\',\r\n			\'cxc_tpl_update_none\' => \'template information will be unavailabl6e.\',\r\n			\'cxc_tpl_processing\' => \'Template Processing\',\r\n			\'cxc_tpl_pre_remove\' => \'The `<strong>preimport-data</strong>` template directory has been successfully removed from the `<strong>\',\r\n			\'cxc_tpl_pre_newback\' => \'</strong>` directory so a new backup can be written to replace previous template backups.\',\r\n			\'cxc_tpl_pre_failed\' => \'The `<strong>preimport-data</strong>` template directory was not removed, the backup may contain additional files from previous backups. Your current template data will be added to `<strong>preimport-data</strong>` for future use.\',\r\n			\'cxc_tpl_pre_note\' => \'<strong>Note:</strong> <em>installing another template will overwrite the current `<strong>preimport-data</strong>` so rename the directory to something else after it is exported to preserve a more permanent backup.</em>\',\r\n			\'cxc_tpl_import_success\' => \'Successfully imported\',\r\n			\'cxc_tpl_import_skipped\' => \'Skipped importing\',\r\n			\'cxc_tpl_import_present\' => \'\", it was already present.\',\r\n			\'cxc_tpl_import_failed\' => \'Failed importing\',\r\n			\'cxc_tpl_add_click\' => \'Click here to see additional information from the imported templates designer.\',\r\n			\'cxc_tpl_add_info\' => \'Additional Information\',\r\n			\'cxc_tpl_upload_success\' => \'Successfully uploaded\',\r\n			\'cxc_tpl_move_fail\' => \'<span class=\"cxc-tpl-failure\">Failed removing </span> file \"\',\r\n			\'cxc_tpl_move_temp\' => \'\" the temporary files.\',\r\n			\'cxc_tpl_preview_text\' => \'Template Preview\',\r\n			\'cxc_tpl_preview_none\' => \'No Preview Image Available\',\r\n			\'cxc_tpl_preview_docs\' => \'Template Documentation\',\r\n			\'cxc_tpl_preview_note\' => \'(<em><span class=\"cxc-tpl-capital\">\',\r\n			\'cxc_tpl_preview_last\' => \'</span> was the last template imported</em>)\',\r\n			\'cxc_tpl_top_level\' => \'Top Level \"<strong>\',\r\n			\'cxc_tpl_top_dir\' => \'</strong>\" Directory\',\r\n			\'cxc_tpl_top_remove\' => \'<strong>Note:</strong> <em class=\"cxc-tpl-failure\">this will remove all templates.</em>\',\r\n			\'cxc_tpl_template\' => \'Template\',\r\n			\'cxc_tpl_preview_img\' => \'Template Preview Image\',\r\n			\'cxc_tpl_error_zip_dir\' => \'Error, there was a problem creating the zip file for the template directory.\',\r\n			\'cxc_tpl_error_zip_remove\' => \'Error, there was a problem removing the zip file after the download.\',\r\n			\'cxc_tpl_error_zip_final\' => \'Error, could not finalise the archive.\',\r\n			\'cxc_tpl_error_zip_failed\' => \'Error, could not create the\',\r\n			\'cxc_tpl_error_zip_archive\' => \'archive file\'\r\n		);\r\n		return strtr($lang[$what], $atts);\r\n	} ', '', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etz_striptags', 0, 'Nikolaos Genimakis', 'http://www.agitprop.gr/', '0.2', 'Strips (X)HTML and PHP tags from its input', '<h4>Strips (X)HTML and PHP tags from its input</h4>\n\n	<p>Usage:</p>\n\n	<p>Surround a block of TXP code, either in a form or in a page template, with <code>&lt;txp:etz_striptags&gt;</code> and <code>&lt;/txp:etz_striptags&gt;</code>.</p>', '\nfunction etz_striptags ($atts, $thing) {\n	return trim(strip_tags(parse($thing)));\n}', '\nfunction etz_striptags ($atts, $thing) {\n	return trim(strip_tags(parse($thing)));\n}', '', '', '', 0, 5, 0),
('arc_social_share', 0, 'Andy Carter', 'https://andy-carter.com/', '1.5.0', 'Social media share links', '	<h1>arc_social_share</h1>\n\n	<p>Easily add links for sharing content with numerous social networks.</p>\n\n	<h2>The Tags</h2>\n\n	<p>All tags have the following attributes:-</p>\n\n	<ul>\n		<li>class: class name to be applied to the link tag</li>\n		<li>url: <span class=\"caps\">URL</span> to share, use this to override the article&#8217;s permlink</li>\n		<li>utm: pass &#8216;1&#8217; to enable <span class=\"caps\">UTM</span> parameters for Google Analytics (off by default)</li>\n	</ul>\n\n	<p>All tags apart from <code>arc_social_share_facebook</code> and <code>arc_social_share_gplus</code> have a &#8216;title&#8217; attribute for overridding the article&#8217;s title to be included in the share link.</p>\n\n	<h3>Delicious</h3>\n\n<pre><code>&lt;txp:arc_social_share_delicious /&gt;\n</code></pre>\n\n	<h3>Facebook</h3>\n\n<pre><code>&lt;txp:arc_social_share_facebook /&gt;\n</code></pre>\n\n	<h3>Google+</h3>\n\n<pre><code>&lt;txp:arc_social_share_gplus /&gt;\n</code></pre>\n\n	<h3>LinkedIn</h3>\n\n<pre><code>&lt;txp:arc_social_share_linkedin /&gt;\n</code></pre>\n\n	<h4>Additional Attributes</h4>\n\n	<ul>\n		<li>source: by default this is your site&#8217;s name</li>\n		<li>summary: pass some summary text to LinkedIn (LinkedIn will truncate summaries greater than 256 characters long)</li>\n	</ul>\n\n	<h3>Pinterest</h3>\n\n<pre><code>&lt;txp:arc_social_share_pinterest /&gt;\n</code></pre>\n\n	<h4>Additional Attributes</h4>\n\n	<ul>\n		<li>image: <span class=\"caps\">URL</span> to an image, by default this is the article&#8217;s image</li>\n	</ul>\n\n	<h3>Pocket</h3>\n\n<pre><code>&lt;txp:arc_social_share_pocket /&gt;\n</code></pre>\n\n	<h3>Reddit</h3>\n\n<pre><code>&lt;txp:arc_social_share_reddit /&gt;\n</code></pre>\n\n	<h3>StumbleUpon</h3>\n\n<pre><code>&lt;txp:arc_social_share_stumbleupon /&gt;\n</code></pre>\n\n	<h3>Tumblr</h3>\n\n<pre><code>&lt;txp:arc_social_share_tumblr /&gt;\n</code></pre>\n\n	<h3>Twitter</h3>\n\n<pre><code>&lt;txp:arc_social_share_twitter /&gt;\n</code></pre>\n\n	<h4>Additional Attributes</h4>\n\n	<ul>\n		<li>mention: adds a Twitter username to the end of a tweet (<i>e.g.</i> /@drmonkeyninja)</li>\n	</ul>\n\n	<h2>Usage</h2>\n\n	<p>The majority of the tags work the same, with a few exceptions where there are additional parameters available.</p>\n\n	<p>They can all be used as either a single tag or a wrapper tag. For example:-</p>\n\n<pre><code>&lt;txp:arc_social_share_twitter /&gt;\n</code></pre>\n\n	<p>or,</p>\n\n<pre><code>&lt;txp:arc_social_share_twitter&gt;Tweet This&lt;/txp:arc_social_share_twitter&gt;\n</code></pre>\n\n	<p>They&#8217;re intended to work within an individual article context, so used in an article form. The <span class=\"caps\">URL</span> used for sharing will be the default permlink created by Textpattern. However, you can override the <span class=\"caps\">URL</span>:-</p>\n\n<pre><code>&lt;txp:arc_social_share_twitter url=&#39;http://www.example.com&#39; /&gt;\n</code></pre>\n\n	<p>All links created by the tags are <span class=\"caps\">URL</span> encoded.</p>\n\n	<p>The plugin won&#8217;t do anything fancy with the way the links work when clicked. So if you want to open the links in a new window you will need to put in place some JavaScript to do this yourself. You can easily add a class to the links to help target them with your JavaScript:-</p>\n\n<pre><code>&lt;txp:arc_social_share_twitter class=&#39;bookmarklet&#39; /&gt;\n</code></pre>\n\n	<h2>Author</h2>\n\n	<p><a href=\"https://andy-carter.com\">Andy Carter</a>.</p>\n\n	<h2>License</h2>\n\n	<p><a href=\"https://github.com/drmonkeyninja/arc_social_share/blob/master/LICENSE\">License</a></p>', 'global $prefs, $txpcfg;\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    // Register Textpattern tags for TXP 4.6+.\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'arc_social_share_delicious\')\n        ->register(\'arc_social_share_facebook\')\n        ->register(\'arc_social_share_gplus\')\n        ->register(\'arc_social_share_linkedin\')\n        ->register(\'arc_social_share_pinterest\')\n        ->register(\'arc_social_share_pocket\')\n        ->register(\'arc_social_share_reddit\')\n        ->register(\'arc_social_share_stumbleupon\')\n        ->register(\'arc_social_share_tumblr\')\n        ->register(\'arc_social_share_twitter\');\n}\n\nfunction arc_social_share_delicious($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing===null) ? \'Share on Delicious\' : parse($thing);\n\n    $utmSource = $utm ? \'delicious.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://delicious.com/post?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_facebook($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing===null) ? \'Share on Facebook\' : parse($thing);\n\n    $utmSource = $utm ? \'facebook.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n\n    $html = href($thing, \"https://www.facebook.com/sharer/sharer.php?u=$url\", \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_gplus($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Google+\' : parse($thing);\n\n    $utmSource = $utm ? \'gplus\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n\n    $html = href($thing, \"https://plus.google.com/share?url=$url\", \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_linkedin($atts, $thing = null)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'source\' => null,\n        \'summary\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on LinkedIn\' : parse($thing);\n\n    $utmSource = $utm ? \'linkedin\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n    $source = $source === null && !empty($prefs[\'sitename\']) ? urldecode($prefs[\'sitename\']) : urlencode($source);\n\n    $link = \"http://www.linkedin.com/shareArticle?mini=true&amp;url=$url&amp;title=$title&amp;source=$source\";\n\n    if (!empty($summary)) {\n        $link .= \"&amp;summary=$summary\";\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_pinterest($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'image\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Pinterest\' : parse($thing);\n\n    $utmSource = $utm ? \'pinterest\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n    $image = _arc_social_share_image($image);\n\n    $link = \"http://www.pinterest.com/pin/create/button/?url=$url&amp;description=$title\";\n    if ($image) {\n        $link .= \"&amp;media=$image\";\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_pocket($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Add to Pocket\' : parse($thing);\n\n    $utmSource = $utm ? \'getpocket.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"https://getpocket.com/save?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_reddit($atts, $thing = null)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Reddit\' : parse($thing);\n\n    $utmSource = $utm ? \'reddit\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.reddit.com/submit?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_stumbleupon($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on StumbleUpon\' : parse($thing);\n\n    $utmSource = $utm ? \'stumbleupon\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.stumbleupon.com/submit?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_tumblr($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Tumblr\' : parse($thing);\n\n    $utmSource = $utm ? \'tumblr\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.tumblr.com/share?v=3&amp;u=$url&amp;t=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n\n}\n\nfunction arc_social_share_twitter($atts, $thing = null)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'mention\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Twitter\' : parse($thing);\n\n    $utmSource = $utm ? \'twitter.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://twitter.com/home?status=$title+$url\";\n\n    if (!empty($mention)) {\n        $link .= urlencode(\" /@$mention\");\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction _arc_social_share_title($title = null)\n{\n    global $thisarticle;\n\n    $title = $title === null && !empty($thisarticle[\'title\']) ? urlencode($thisarticle[\'title\']) : urlencode($title);\n\n    return $title;\n}\n\nfunction _arc_social_share_url($url, $source = null)\n{\n    global $thisarticle;\n\n    $url = $url === null && !empty($thisarticle[\'thisid\']) ? permlinkurl_id($thisarticle[\'thisid\']) : $url;\n\n    if (!empty($url) && !empty($source)) {\n        // Add Google Analytics urchin tracking module to the URL\n        $query = \"utm_source=$source&utm_medium=social&utm_campaign=arc_social_share\";\n        $query .= !empty($thisarticle[\'thisid\']) ? \'&utm_content=txp:\' . $thisarticle[\'thisid\'] : \'\';\n        $separator = (parse_url($url, PHP_URL_QUERY) == null) ? \'?\' : \'&\';\n        $url .= $separator . $query;\n\n    }\n\n    if (!empty($url)) {\n        // Encode the URL\n        $url = urlencode($url);\n    }\n\n    return $url;\n}\n\nfunction _arc_social_share_image($image = null)\n{\n    global $thisarticle;\n\n    if ($image === null && !empty($thisarticle[\'article_image\'])) {\n        $image = $thisarticle[\'article_image\'];\n\n        if (intval($image)) {\n            if ($rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($image))) {\n                $image = urlencode(imagesrcurl($rs[\'id\'], $rs[\'ext\']));\n            } else {\n                $image = null;\n            }\n\n        }\n\n    }\n\n    return $image;\n}\n\n', 'global $prefs, $txpcfg;\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    // Register Textpattern tags for TXP 4.6+.\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'arc_social_share_delicious\')\n        ->register(\'arc_social_share_facebook\')\n        ->register(\'arc_social_share_gplus\')\n        ->register(\'arc_social_share_linkedin\')\n        ->register(\'arc_social_share_pinterest\')\n        ->register(\'arc_social_share_pocket\')\n        ->register(\'arc_social_share_reddit\')\n        ->register(\'arc_social_share_stumbleupon\')\n        ->register(\'arc_social_share_tumblr\')\n        ->register(\'arc_social_share_twitter\');\n}\n\nfunction arc_social_share_delicious($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing===null) ? \'Share on Delicious\' : parse($thing);\n\n    $utmSource = $utm ? \'delicious.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://delicious.com/post?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_facebook($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing===null) ? \'Share on Facebook\' : parse($thing);\n\n    $utmSource = $utm ? \'facebook.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n\n    $html = href($thing, \"https://www.facebook.com/sharer/sharer.php?u=$url\", \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_gplus($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Google+\' : parse($thing);\n\n    $utmSource = $utm ? \'gplus\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n\n    $html = href($thing, \"https://plus.google.com/share?url=$url\", \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_linkedin($atts, $thing = null)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'source\' => null,\n        \'summary\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on LinkedIn\' : parse($thing);\n\n    $utmSource = $utm ? \'linkedin\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n    $source = $source === null && !empty($prefs[\'sitename\']) ? urldecode($prefs[\'sitename\']) : urlencode($source);\n\n    $link = \"http://www.linkedin.com/shareArticle?mini=true&amp;url=$url&amp;title=$title&amp;source=$source\";\n\n    if (!empty($summary)) {\n        $link .= \"&amp;summary=$summary\";\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_pinterest($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'image\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Pinterest\' : parse($thing);\n\n    $utmSource = $utm ? \'pinterest\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n    $image = _arc_social_share_image($image);\n\n    $link = \"http://www.pinterest.com/pin/create/button/?url=$url&amp;description=$title\";\n    if ($image) {\n        $link .= \"&amp;media=$image\";\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_pocket($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Add to Pocket\' : parse($thing);\n\n    $utmSource = $utm ? \'getpocket.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"https://getpocket.com/save?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_reddit($atts, $thing = null)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Reddit\' : parse($thing);\n\n    $utmSource = $utm ? \'reddit\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.reddit.com/submit?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_stumbleupon($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on StumbleUpon\' : parse($thing);\n\n    $utmSource = $utm ? \'stumbleupon\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.stumbleupon.com/submit?url=$url&amp;title=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction arc_social_share_tumblr($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Tumblr\' : parse($thing);\n\n    $utmSource = $utm ? \'tumblr\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://www.tumblr.com/share?v=3&amp;u=$url&amp;t=$title\";\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n\n}\n\nfunction arc_social_share_twitter($atts, $thing = null)\n{\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'class\' => \'\',\n        \'mention\' => null,\n        \'title\' => null,\n        \'url\' => null,\n        \'utm\' => false\n    ), $atts));\n\n    $thing = ($thing === null) ? \'Share on Twitter\' : parse($thing);\n\n    $utmSource = $utm ? \'twitter.com\' : null;\n\n    $url = _arc_social_share_url($url, $utmSource);\n    $title = _arc_social_share_title($title);\n\n    $link = \"http://twitter.com/home?status=$title+$url\";\n\n    if (!empty($mention)) {\n        $link .= urlencode(\" /@$mention\");\n    }\n\n    $html = href($thing, $link, \' class=\"\' . $class . \'\"\');\n\n    return $html;\n}\n\nfunction _arc_social_share_title($title = null)\n{\n    global $thisarticle;\n\n    $title = $title === null && !empty($thisarticle[\'title\']) ? urlencode($thisarticle[\'title\']) : urlencode($title);\n\n    return $title;\n}\n\nfunction _arc_social_share_url($url, $source = null)\n{\n    global $thisarticle;\n\n    $url = $url === null && !empty($thisarticle[\'thisid\']) ? permlinkurl_id($thisarticle[\'thisid\']) : $url;\n\n    if (!empty($url) && !empty($source)) {\n        // Add Google Analytics urchin tracking module to the URL\n        $query = \"utm_source=$source&utm_medium=social&utm_campaign=arc_social_share\";\n        $query .= !empty($thisarticle[\'thisid\']) ? \'&utm_content=txp:\' . $thisarticle[\'thisid\'] : \'\';\n        $separator = (parse_url($url, PHP_URL_QUERY) == null) ? \'?\' : \'&\';\n        $url .= $separator . $query;\n\n    }\n\n    if (!empty($url)) {\n        // Encode the URL\n        $url = urlencode($url);\n    }\n\n    return $url;\n}\n\nfunction _arc_social_share_image($image = null)\n{\n    global $thisarticle;\n\n    if ($image === null && !empty($thisarticle[\'article_image\'])) {\n        $image = $thisarticle[\'article_image\'];\n\n        if (intval($image)) {\n            if ($rs = safe_row(\'*\', \'txp_image\', \'id = \' . intval($image))) {\n                $image = urlencode(imagesrcurl($rs[\'id\'], $rs[\'ext\']));\n            } else {\n                $image = null;\n            }\n\n        }\n\n    }\n\n    return $image;\n}\n\n', '5c3e90d317daa8d754f4f0d26df33866', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('arc_twitter', 0, 'Andy Carter', 'http://andy-carter.com/', '4.5.0', '<a href=\"http://www.twitter.com\">Twitter</a> for Textpattern', '	<h1><span class=\"caps\">TXP</span> Tweet (arc_twitter for Textpattern)</h1>\n\n	<p><span class=\"caps\">TXP</span> Tweet provides access to your Twitter account through both the admin interface and the public side of your site. Update Twitter when you post a new article (with article-by-article opt out option), update and view your Twitter feed through the admin Twitter tab, and display Twitter feeds on your site.</p>\n\n	<h2>Table of contents</h2>\n\n	<ol>\n		<li><a href=\"#arc_twitter_requirements\">Plugin requirements</a></li>\n		<li><a href=\"#arc_twitter_installation\">Installation / Uninstallation</a></li>\n		<li><a href=\"#arc_twitter_tags\">Tags</a></li>\n		<li><a href=\"#arc_twitter_caching\">Caching</a></li>\n		<li><a href=\"#arc_twitter_prefs\">Preferences</a></li>\n		<li><a href=\"#arc_twitter_article\">Tweeting articles</a></li>\n		<li><a href=\"#arc_twitter_admin\">The Twitter tab</a></li>\n		<li><a href=\"#arc_twitter_author\">Author</a></li>\n	</ol>\n\n	<h2 id=\"arc_twitter_requirements\">Plugin requirements</h2>\n\n	<p>arc_twitter&#8217;s minumum requirements:</p>\n\n	<ul>\n		<li>Textpattern 4.6+</li>\n		<li><span class=\"caps\">PHP</span> 5 and cURL</li>\n	</ul>\n\n	<h2 id=\"arc_twitter_installation\">Installation / Uninstallation</h2>\n\n	<p>To install go to the &#8216;plugins&#8217; tab under &#8216;admin&#8217; and paste the plugin code into the &#8216;Install plugin&#8217; box, &#8216;upload&#8217; and then &#8216;install&#8217;. The plugin will be automatically activated.</p>\n\n	<p>Before you start using <span class=\"tag\">arc_twitter</span> you will need to make sure that the cache directory is writable. See the &#8216;Caching&#8217; subsection below for further information.</p>\n\n	<p>You will need to associate your site with a Twitter account by connecting to Twitter from the plugin&#8217;s options screen. Click on the link to connect to Twitter, you will be asked to login to Twitter, clicking this link will temporarily take you to the Twitter site where you will be asked to login and approve access for <span class=\"caps\">TXP</span> Tweet to read and write to your Twitter account. If all is successful you will be returned to the options screen and your user name will appear.</p>\n\n	<p>At any time you can disassociate your Twitter account with <span class=\"caps\">TXP</span> Tweet via your Twitter account preferences on the <a href=\"http://www.twitter.com\">Twitter website</a>.</p>\n\n	<p>To uninstall <span class=\"tag\">arc_twitter</span> simply delete the plugin from the &#8216;Plugins&#8217; tab.  This will remove the plugin code, delete related preferences and drop the <span class=\"tag\">arc_twitter</span> table from your Textpattern database.</p>\n\n	<h2 id=\"arc_twitter_tags\">Tags</h2>\n\n	<h3 id=\"arc_twitter_tag\">arc_twitter</h3>\n\n<pre><code>&lt;txp: arc_twitter /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>user</em> &#8212; Twitter user name. Default: <em>arc_twitter username</em>.</li>\n		<li><em>retweets</em> &#8212; 1 to include retweets, 0 to exclude them from the feed. Default: &#8216;0&#8217;.</li>\n		<li><em>replies</em> &#8212; 1 to include replies, 0 to exclude them from the feed. Default: &#8216;1&#8217;.</li>\n		<li><em>limit</em> &#8212; maximum number of tweets to display (max. 200). Default: &#8216;25&#8217;.</li>\n		<li><em>fetch</em> &#8212; maximum number of tweets to retrieve from Twitter (max. 200)<sup class=\"footnote\"><a href=\"#fn5900381775a78da6a7844f\">1</a></sup>. Default: &#8216;0&#8217;.</li>\n		<li><em>full_urls</em> &#8212; 1 to display full <span class=\"caps\">URL</span>s, 0 for t.co <span class=\"caps\">URL</span>s. Default: &#8216;0&#8217;.</li>\n		<li><em>dateformat</em> &#8212; format that update dates will appear as. Default: <em>Archive date format</em>.</li>\n		<li><em>label</em> &#8212; label for the top of the list.</li>\n		<li><em>labeltag</em> &#8212; independent wraptag for label.</li>\n		<li><em>break</em> &#8212; <span class=\"caps\">HTML</span> tag (without brackets), or string, used to separate the updates. Default: &#8216;li&#8217;.</li>\n		<li><em>wraptag</em> &#8212; <span class=\"caps\">HTML</span> tag to be used as the wraptag, without brackets.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute for wraptag. Default: &#8216;arc_twitter&#8217;.</li>\n		<li><em>class_posted</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to span tag around posted date. Default: &#8216;arc_twitter-posted&#8217;.</li>\n	</ul>\n\n	<p id=\"fn5900381775a78da6a7844f\" class=\"footnote\"><sup>1</sup> Twitter&#8217;s <span class=\"caps\">API</span> counts the number of tweets to return before applying some of the filters, so the response can be fewer tweets than expected. To help avoid this you can set a <em>fetch</em> limit that is higher than the display <em>limit</em> that you want to output. If <em>fetch</em> is less than <em>limit</em> the latter will be used instead.</p>\n\n	<h4>Example usage</h4>\n\n<pre><code>&lt;txp:arc_twitter user=&quot;drmonkeyninja&quot; limit=&quot;5&quot; wraptag=&quot;ul&quot; break=&quot;li&quot; dateformat=&quot;%b %Oe, %I:%M %p&quot; /&gt;\n</code></pre>\n\n	<p>Produces a bullet point list of the last 5 Twitter updates from drmonkeyninja&#8217;s Twitter feed with a defined date format to override the default archive date format.</p>\n\n	<h3 id=\"arc_twitter_search_tag\">arc_twitter_search</h3>\n\n<pre><code>&lt;txp:arc_twitter_search hashtags=&quot;txp&quot; /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>search</em> &#8212; comma separated list of search words.</li>\n		<li><em>hashtags</em> &#8212; comma separated list of hashtags to search for (not including the hash).</li>\n		<li><em>reply</em> &#8212; username of tweets in reply to.</li>\n		<li><em>mention</em> &#8212; username of user mentioned in tweets (<i>i.e.</i> tweets containing @username).</li>\n		<li><em>limit</em> &#8212; maximum number of tweets to display (max. 200). Default: &#8216;10&#8217;.</li>\n		<li><em>full_urls</em> &#8212; 1 to display full <span class=\"caps\">URL</span>s, 0 for t.co <span class=\"caps\">URL</span>s. Default: &#8216;0&#8217;.</li>\n		<li><em>dateformat</em> &#8212; format that update dates will appear as. Default: <em>Archive date format</em>.</li>\n		<li><em>label</em> &#8212; label for the top of the list.</li>\n		<li><em>labeltag</em> &#8212; independent wraptag for label.</li>\n		<li><em>break</em> &#8212; <span class=\"caps\">HTML</span> tag (without brackets), or string, used to separate the updates. Default: &#8216;li&#8217;.</li>\n		<li><em>wraptag</em> &#8212; <span class=\"caps\">HTML</span> tag to be used as the wraptag, without brackets.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute for wraptag. Default: &#8216;arc_twitter_search&#8217;.</li>\n		<li><em>class_user</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to span tag around user name. Default: &#8216;arc_twitter-user&#8217;.</li>\n		<li><em>class_posted</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to span tag around posted date. Default: &#8216;arc_twitter-posted&#8217;.</li>\n	</ul>\n\n	<h4>Example usage</h4>\n\n<pre><code>&lt;txp:arc_twitter_search search=&quot;plugin&quot; hashtags=&quot;txp,textpattern&quot; limit=&quot;25&quot; /&gt;\n</code></pre>\n\n	<p>Produces a list of tweets containing the word &#8216;plugin&#8217; and the hashtags &#8216;#txp&#8217; and &#8216;#textpattern&#8217;. The tag will return a maximum of 25 tweets.</p>\n\n	<h3 id=\"arc_twitter_retweet_tag\">arc_twitter_tweet_button</h3>\n\n	<p>Outputs the Twitter tweet button (formerly <code>&lt;txp:arc_twitter_retweet /&gt;</code>).</p>\n\n<pre><code>&lt;txp:arc_twitter_tweet_button /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>user</em> &#8212; Twitter user name to quote. Default: <em>arc_twitter username</em>.</li>\n		<li><em>url</em> &#8212; <span class=\"caps\">URL</span> to tweet.</li>\n		<li><em>text</em> &#8212; tweet text.</li>\n		<li><em>follow1</em> &#8212; suggested Twitter account to follow.</li>\n		<li><em>follow2</em> &#8212; as <em>follow1</em>.</li>\n		<li><em>lang</em> &#8212; langauge. Default: &#8216;en&#8217;.</li>\n		<li><em>count</em> &#8212; count box position, options: none, horizontal or vertical. Default: &#8216;horizontal&#8217;.</li>\n		<li><em>include_js</em> &#8212; whether or not to include the widget JavaScript. Default: &#8216;1&#8217;.</li>\n		<li><em>optimise_js</em> &#8212; optimise the JavaScript by using a local script rather than that hosted on theTwitter site. Default: &#8216;0&#8217;.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to the tweet button. Default: &#8216;twitter-share-button&#8217;.</li>\n	</ul>\n\n	<h3 id=\"arc_twitter_follow_button_tag\">arc_twitter_follow_button</h3>\n\n	<p>Outputs the Twitter follow button (an alternative version is included in the <a href=\"http://andy-carter.com/txp/arc_twitter_intents-txp-tweet-add-on\">arc_twitter_intents extension plugin</a>).</p>\n\n<pre><code>&lt;txp:arc_twitter_follow_button /&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>user</em> &#8212; Twitter user name to follow. Default: <em>arc_twitter username</em>.</li>\n		<li><em>lang</em> &#8212; language. Default: &#8216;en&#8217;.</li>\n		<li><em>count</em> &#8212; whether or not the follower count is shown. Default: &#8216;1&#8217;.</li>\n		<li><em>include_js</em> &#8212; whether or not to include the widget JavaScript. Default: &#8216;1&#8217;.</li>\n		<li><em>optimise_js</em> &#8212; optimise the JavaScript by using a local script rather than that hosted on the Twitter site. Default: &#8216;0&#8217;.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to the follow button. Default: &#8216;twitter-follow-button&#8217;.</li>\n	</ul>\n\n	<h3 id=\"arc_twitter_tweet_url\">arc_twitter_tweet_url</h3>\n\n	<p>Returns the <span class=\"caps\">URL</span> of the Twitter status for an article.</p>\n\n<pre><code>&lt;txp:arc_twitter_tweet_url /&gt;\n</code>\n<code>&lt;txp:arc_twitter_tweet_url&gt;Link text&lt;/txp:arc_twitter_tweet_url&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>id</em> &#8212; Textpattern article ID. Default: <em>current article</em>.</li>\n		<li><em>title</em> &#8212; title attribute of the link.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to the link.</li>\n	</ul>\n\n	<h3 id=\"arc_twitter_tinyurl\">arc_twitter_tinyurl</h3>\n\n	<p>Returns the shortened <span class=\"caps\">URL</span> of the article used for the Twitter update.</p>\n\n<pre><code>&lt;txp:arc_twitter_tinyurl /&gt;\n</code>\n<code>&lt;txp:arc_twitter_tinyurl&gt;Link text&lt;/txp:arc_twitter_tinyurl&gt;\n</code></pre>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><em>id</em> &#8212; Textpattern article ID. Default: <em>current article</em>.</li>\n		<li><em>title</em> &#8212; title attribute of the link.</li>\n		<li><em>class</em> &#8212; <span class=\"caps\">CSS</span> class attribute applied to the link.</li>\n	</ul>\n\n	<h2 id=\"arc_twitter_caching\">Caching</h2>\n\n	<p>In order to prevent excessive repeatitive calls to the Twitter website it is recommended to cache results. Twitter limits the number of calls through the <span class=\"caps\">API</span>, and continuous calls will result in Twitter closing to further requests. By default, arc_twitter caches for 30 minute intervals.</p>\n\n	<p>The following attributes are available to the <em>arc_twitter</em> and <em>arc_twitter_search</em> tags:-</p>\n\n	<ul>\n		<li><em>caching</em> &#8212; &#8216;1&#8217; to cache feed, &#8216;0&#8217; to turn caching off (not recommended). Default: &#8216;1&#8217;.</li>\n		<li><em>cache_dir</em> &#8212; absolute path to the cache directory (must be writable). Default: <em>arc_twitter preferences</em>.</li>\n		<li><em>cache_time</em> &#8212; time in minutes that the cache files are stored before being refreshed. Default: &#8216;5&#8217;.</li>\n	</ul>\n\n	<p>The admin side of this plugin enforces caching, apart from when it is posting to Twitter (<i>e.g.</i> when posting or deleting an update).</p>\n\n	<h2 id=\"arc_twitter_prefs\">Preferences</h2>\n\n	<p>You can access the plugins core preferences from either the Preferences or Plugins tabs in admin. Setup your Twitter account (you will be asked to connect via Twitter and this needs doing before you can use the plugin) and change the cache directory using arc_twitter&#8217;s preferences. Without providing your account login details the admin area features of this plugin will be inactive.</p>\n\n	<p>You can select the <span class=\"caps\">URL</span> shortener method you want to use to link back to your article on Twitter. Please note that if you select smd_short_url you will need to have installed and activated the <a href=\"http://textpattern.org/plugins/1099/smd_short_url\"><span class=\"tag\">smd_short_url</span> plugin</a> developed by Stef Dawson.</p>\n\n	<h2 id=\"arc_twitter_article\">Tweeting articles</h2>\n\n	<p>By default arc_twitter will post an update to Twitter including a shortened <span class=\"caps\">URL</span> to your article. Only live and active articles will be sent to Twitter, <i>i.e.</i> articles posted in the future or as sticky articles will not be sent. If your article is successfully submitted to Twitter the update will appear in place of the Twitter option on the right-hand-side of the article edit screen.</p>\n\n	<p>Tweets are sent in the following format: <em>Tweet prefix</em> <em>Article title</em> <em>Shortened <span class=\"caps\">URL</span></em> <em>Tweet suffix</em>. You can change the prefix and suffix on an article-by-article basis by changing the tweet options under &#8216;Update Twitter&#8217; on the article editor screen. The default <em>Tweet prefix</em> can be set under the <span class=\"tag\">arc_twitter</span> preferences screen (the default on installation is &#8220;Just posted:&#8221;).</p>\n\n	<p>Please note that once an article has been tweeted the tweet cannot be edited.</p>\n\n	<p>If you want to monitor how many clicks you&#8217;re getting from your <span class=\"caps\">TXP</span> tweets in Google Analytics enable <span class=\"caps\">UTM</span> in the plugin&#8217;s preferences. It will add <span class=\"caps\">UTM</span> parameters to the <span class=\"caps\">URL</span>s tweeted that can then be monitored via the <em>Campaigns</em> section of Google Analytics.</p>\n\n	<h2 id=\"arc_twitter_admin\">The Twitter tab</h2>\n\n	<p>Under the Extensions tab (this can be changed from the plugin&#8217;s preference page) a new Twitter tab should appear once you have connected your site to your Twitter account. From here you will be able to submit new Twitter updates, view basic account statistics, and check out your recent updates (including the option to delete your tweets).</p>\n\n	<h2 id=\"arc_twitter_author\">Author</h2>\n\n	<p><a href=\"http://andy-carter.com\">Andy Carter</a>. For other Textpattern plugins by me visit my <a href=\"http://andy-carter.com/txp\">Plugins page</a>.</p>\n\n	<p>Contributors: Andy Carter, Phil Wareham and Tommy Schmucker.</p>\n\n	<p>Thanks to <a href=\"http://manfre.net/\">Michael Manfre</a> for inspiration for the article tweet part of this plugin based on his <span class=\"tag\">mem_twitter</span> plugin.  Additional thanks to the great Textpattern community for helping to test this plugin and for suggesting new features. The OAuth part of the plugin is thanks to <a href=\"http://twitter.com/abraham\">Abraham Williams</a>.</p>\n\n', 'global $prefs,$txpcfg,$arc_twitter_consumerKey,$arc_twitter_consumerSecret;\n\n$arc_twitter = array();\n\n// Register tags.\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'arc_twitter\')\n    ->register(\'arc_twitter_search\')\n    ->register(\'arc_twitter_tweet_button\')\n    ->register(\'arc_twitter_follow_button\')\n    ->register(\'arc_twitter_tweet_url\')\n    ->register(\'arc_twitter_tinyurl\')\n    ->register(\'arc_twitter_widget_js\');\n\n$arc_twitter_consumerKey = \'nKcXslwzZhBd0kfKMetnPA\';\n$arc_twitter_consumerSecret = \'C6nSPCL3eeHGTBhKCgwd9oclcuD0srB8WVkfXQYC54\';\n\nadd_privs(\'plugin_prefs.arc_twitter\',\'1,2\');\nregister_callback(\'arc_twitter_install\',\'plugin_lifecycle.arc_twitter\', \'installed\');\nregister_callback(\'arc_twitter_uninstall\',\'plugin_lifecycle.arc_twitter\', \'deleted\');\nregister_callback(\'arc_twitter_prefs\',\'plugin_prefs.arc_twitter\');\n\n/*\n * Setup initial preferences if not in the txp_prefs table.\n */\nif (!isset($prefs[\'arc_twitter_user\'])) {\n	set_pref(\'arc_twitter_user\', \'\', \'arc_twitter\', 1, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_prefix\'])) {\n	set_pref(\'arc_twitter_prefix\',\'Just posted:\', \'arc_twitter\', 2, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_suffix\'])) {\n	set_pref(\'arc_twitter_suffix\',\'\', \'arc_twitter\', 2, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_cache_dir\'])) {\n	set_pref(\'arc_twitter_cache_dir\',$txpcfg[\'txpath\'].$prefs[\'tempdir\'], \'arc_twitter\', 1, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_tweet_default\'])) {\n	set_pref(\'arc_twitter_tweet_default\', 1, \'arc_twitter\', 2, \'yesnoRadio\');\n}\nif (!isset($prefs[\'arc_twitter_url_method\'])) {\n	set_pref(\'arc_twitter_url_method\', \'full\', \'arc_twitter\', 2, \'arc_twitter_url_method_select\');\n}\nif (!isset($prefs[\'arc_short_url\'])) {\n	set_pref(\'arc_short_url\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\n}\nif (!isset($prefs[\'arc_short_site_url\'])) {\n	set_pref(\'arc_short_site_url\', $prefs[\'siteurl\'], \'arc_twitter\', 2, \'text_input\');\n}\n// Make sure that the Twitter tab has been defined\nif (!isset($prefs[\'arc_twitter_tab\'])) {\n  set_pref(\'arc_twitter_tab\', \'extensions\', \'arc_twitter\', 2,\n	\'arc_twitter_tab_select\');\n	$prefs[\'arc_twitter_tab\'] = \'extensions\';\n}\nif (!isset($prefs[\'arc_short_utm\'])) {\n	set_pref(\'arc_short_utm\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\n}\n\n// Check if arc_short_url is enabled\nif ((isset($prefs[\'arc_short_url\'])&&$prefs[\'arc_short_url\'])\n|| (isset($prefs[\'arc_short_url_method\'])&&$prefs[\'arc_twitter_url_method\']==\'arc_twitter\')) {\n  register_callback(\'arc_short_url_redirect\', \'txp_die\', 404);\n}\n\nif (@txpinterface == \'admin\') {\n	register_callback(\'_arc_twitter_auto_enable\', \'plugin_lifecycle.arc_twitter\', \'installed\');\n	if (!empty($prefs[\'arc_twitter_user\'])\n		&& !empty($prefs[\'arc_twitter_accessToken\'])\n		&& !empty($prefs[\'arc_twitter_accessTokenSecret\']) ) {\n\n		if ($prefs[\'arc_twitter_tab\']) {\n			add_privs(\'arc_admin_twitter\', \'1,2,3,4\');\n			register_tab($prefs[\'arc_twitter_tab\'], \'arc_admin_twitter\', \'Twitter\');\n			register_callback(\'arc_admin_twitter\', \'arc_admin_twitter\');\n		}\n\n		register_callback(\'arc_article_tweet\', \'ping\');\n		register_callback(\'arc_article_tweet\', \'article_saved\');\n		register_callback(\'arc_article_tweet\', \'article_posted\');\n		register_callback(\'arc_append_twitter\', \'article_ui\', \'status\');\n	}\n}\n\n/*\n	Public-side functions\n	================================================================\n*/\n\nfunction arc_twitter($atts)\n{\n  global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n  extract(lAtts(array(\n	\'user\'      => $prefs[\'arc_twitter_user\'],\n	\'password\'  => \'\',\n	\'timeline\'  => \'user\',\n	\'limit\'     => \'10\',\n	\'fetch\'     => 0,\n	\'full_urls\' => false,\n	\'retweets\'  => false,\n	\'replies\'   => true,\n	\'dateformat\'=> $prefs[\'archive_dateformat\'],\n	\'caching\'   => \'1\',\n	\'cache_dir\' => $prefs[\'arc_twitter_cache_dir\'],\n	\'cache_time\'=> \'5\',\n	\'label\'     => \'\',\n	\'labeltag\'  => \'\',\n	\'break\'     => \'li\',\n	\'wraptag\'   => \'\',\n	\'class\'     => __FUNCTION__,\n	\'class_posted\' => __FUNCTION__.\'-posted\'\n	),$atts));\n\n  $twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n  if ($caching) {  // turn on caching, recommended (default)\n	$twit->setCaching(true);\n	$twit->cacheDir($cache_dir);\n	$twit->cacheTime($cache_time);\n  } else {  // turn off caching, not recommended other than for testing\n	$twit->setCaching(false);\n  }\n\n  switch ($timeline) {\n	case \'home\': case \'friends\':\n	  $timeline = \'home_timeline\'; break;\n	case \'mentions\':\n	  $timeline = \'statuses/mentions_timeline\'; break;\n	case \'user\': default: $timeline = \'statuses/user_timeline\';\n  }\n\n  // Check that the fetch (Twitter\'s count attribute) is set correctly\n  $fetch = (!$fetch || $fetch<$limit) ? $limit : $fetch;\n\n  $out = array();\n  $tweets = $twit->get($timeline, array(\n	  \'screen_name\'=>$user,\n	  \'count\'=>$fetch,\n	  \'include_rts\'=>$retweets,\n	  \'exclude_replies\'=>!$replies\n	));\n\n  if ($tweets) {\n	// Apply the display limit to the returned tweets\n	$tweets = array_slice($tweets, 0, $limit);\n	foreach ($tweets as $tweet) {\n	  $time = strtotime(htmlentities($tweet->created_at));\n	  $date = safe_strftime($dateformat,$time);\n	  $links = false;\n	  if (isset($tweet->entities->urls) || isset($tweet->entities->media)) {\n		$links = array();\n	  }\n	  if (isset($tweet->entities->urls)) {\n		foreach ($tweet->entities->urls as $url) {\n			$links[$url->url] = array(\n				\'expanded_url\' => $url->expanded_url,\n				\'display_url\' => $url->display_url\n			);\n		}\n	  }\n	  if (isset($tweet->entities->media)) {\n		foreach ($tweet->entities->media as $url) {\n			$links[$url->url] = array(\n				\'expanded_url\' => $url->expanded_url,\n				\'display_url\' => $url->display_url\n			);\n		}\n	  }\n	  $out[] = arc_Twitter::makeLinks(htmlentities($tweet->text, ENT_QUOTES,\'UTF-8\'), $links, $full_urls)\n		.\' \'.tag(htmlentities($date),\'span\',\' class=\"\'.$class_posted.\'\"\');\n	}\n  }\n\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n}\n\nfunction arc_twitter_search($atts)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	extract(lAtts(array(\n		\'user\'      => $prefs[\'arc_twitter_user\'],\n		\'search\'    => \'\',\n		\'hashtags\'  => \'\',\n		\'user\'      => \'\',\n		\'reply\'     => \'\',\n		\'mention\'   => \'\',\n		\'limit\'     => \'10\',\n		\'full_urls\' => false,\n		\'lang\'      => \'\',\n		\'dateformat\'=> $prefs[\'archive_dateformat\'],\n		\'caching\'   => \'1\',\n		\'cache_dir\' => $prefs[\'arc_twitter_cache_dir\'],\n		\'cache_time\'=> \'5\',\n		\'label\'     => \'\',\n		\'labeltag\'  => \'\',\n		\'break\'     => \'li\',\n		\'wraptag\'   => \'\',\n		\'class\'     => __FUNCTION__,\n		\'class_posted\' => __FUNCTION__.\'-posted\',\n		\'class_user\'   => __FUNCTION__.\'-user\'\n	),$atts));\n\n		$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n		if ($caching) {  // turn on caching, recommended (default)\n			$twit->setCaching(true);\n			$twit->cacheDir($cache_dir);\n			$twit->cacheTime($cache_time);\n		} else {  // turn off caching, not recommended other than for testing\n			$twit->setCaching(false);\n		}\n\n		// construct search query\n		if (!empty($search)) {\n			$terms = explode(\',\',$search); $terms = array_map(\'trim\',$terms);\n			$search = implode(\' \',$terms);\n		}\n		if ($hashtags) {\n			$hashes = explode(\',\',$hashtags); $hashes = array_map(\'trim\',$hashes);\n			$search.= (($search)?\' \':\'\').\'#\'.implode(\' #\',$hashes);\n		}\n		if ($reply) {\n			$search.= (($search)?\' \':\'\').\'to:\'.trim($reply);\n		}\n		if ($user) {\n			$search.= (($search)?\' \':\'\').\'from:\'.trim($user);\n		}\n		if ($mention) {\n			$search.= (($search)?\' \':\'\').\'@\'.trim($mention);\n		}\n\n		if (empty($search)) {\n			return \'\';\n		} else {\n			$search = urlencode($search);\n		}\n\n		$out = array();\n		$results = $twit->get(\'search/tweets\'\n			, array(\'q\'=>$search,\'count\'=>$limit,\'lang\'=>$lang));\n\n		$tweets = $results->statuses;\n		if ($tweets) {\n\n			foreach ($tweets as $tweet) {\n\n				$uname = $tweet->user->screen_name;\n				$name = $tweet->user->name;\n				$time = strtotime(htmlentities($tweet->created_at));\n				$date = safe_strftime($dateformat,$time);\n				$text = $tweet->text;\n\n				$links = false;\n				if (isset($tweet->entities->urls)) {\n					$links = array();\n					foreach ($tweet->entities->urls as $url) {\n						$links[$url->url] = array(\n							\'expanded_url\' => $url->expanded_url,\n							\'display_url\' => $url->display_url\n						);\n					}\n				}\n\n				$out[] = tag(href(htmlentities($uname),\'http://twitter.com/\' . $tweet->user->screen_name,\n					\' title=\"\'.htmlentities($name).\'\"\').\': \',\'span\'\n						,\' class=\"\'.$class_user.\'\"\')\n					.arc_Twitter::makeLinks(htmlentities($text, ENT_QUOTES,\'UTF-8\'), $links, $full_urls)\n					.\' \'.tag(htmlentities($date),\'span\'\n						,\' class=\"\'.$class_posted.\'\"\');\n\n			}\n\n			return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n		}\n\n}\n\nfunction arc_twitter_tweet($atts) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n	  \'id\'        => $thisarticle[\'thisid\'],\n	  \'include_url\'   => true\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = ($include_url) ? safe_row(\"tweet\", \'arc_twitter\', \"article_id={$id}\")\n		: safe_row(\"REPLACE(tweet,CONCAT(\' \',tinyurl),\'\') AS tweet\"\n		  , \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tweet\']) {\n	  return arc_Twitter::makeLinks(\n		htmlentities($tweet[\'tweet\'], ENT_QUOTES,\'UTF-8\'));\n	}\n}\n\nfunction arc_twitter_tweet_url($atts, $thing=null) {\n	global $thisarticle,$prefs;\n\n	extract(lAtts(array(\n	  \'id\'      => $thisarticle[\'thisid\'],\n	  \'title\'   => \'\',\n	  \'class\'   => \'\'\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = safe_row(\"tweet_id\"\n		, \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tweet_id\']) {\n	  $url = \"http://twitter.com/\".$prefs[\'arc_twitter_user\'].\"/status/\".$tweet[\'tweet_id\'];\n	  if ($thing===null) {\n		return $url;\n	  }\n	  return href(parse($thing), $url,\n		($title ? \' title=\"\'.$title.\'\"\' : \'\')\n		.($class ? \' class=\"\'.$class.\'\"\' : \'\'));\n	}\n}\n\nfunction arc_twitter_tinyurl($atts, $thing=null) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n	  \'id\'      => $thisarticle[\'thisid\'],\n	  \'title\'   => \'\',\n	  \'class\'   => \'\'\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = safe_row(\"tinyurl\"\n		, \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tinyurl\']) {\n	  if ($thing===null) {\n		return $tweet[\'tinyurl\'];\n	  }\n\n	  return href(parse($thing), $tweet[\'tinyurl\'],\n		($title ? \' title=\"\'.$title.\'\"\' : \'\')\n		.($class ? \' class=\"\'.$class.\'\"\' : \'\'));\n	}\n}\n\n/*\n * Public tag for outputting widget JS\n */\nfunction arc_twitter_widget_js($atts)\n{\n  extract(lAtts(array(\n		\'optimise\' => false\n	),$atts));\n\n  return _arc_twitter_widget_js($optimise);\n}\n\nfunction _arc_twitter_widget_js($optimise=true)\n{\n  global $arc_twitter;\n\n  // Check if widget JS has already been output\n  if ($arc_twitter[\'widget_js\']) return;\n\n  if ($optimise==false) {\n	return \'<script src=\"http://platform.twitter.com/widgets.js\" type=\"text/javascript\"></script>\';\n  }\n\n  $js = <<<JS\n<script type=\"text/javascript\">\n(function() {\n  if (window.__twitterIntentHandler) return;\n  var intentRegex = /twitter\\.com(\\:\\d{2,4})?\\/intent\\/(\\w+)/,\n	  windowOptions = \'scrollbars=yes,resizable=yes,toolbar=no,location=yes\',\n	  width = 550,\n	  height = 420,\n	  winHeight = screen.height,\n	  winWidth = screen.width;\n\n  function handleIntent(e) {\n	e = e || window.event;\n	var target = e.target || e.srcElement,\n		m, left, top;\n\n	while (target && target.nodeName.toLowerCase() !== \'a\') {\n	  target = target.parentNode;\n	}\n\n	if (target && target.nodeName.toLowerCase() === \'a\' && target.href) {\n	  m = target.href.match(intentRegex);\n	  if (m) {\n		left = Math.round((winWidth / 2) - (width / 2));\n		top = 0;\n\n		if (winHeight > height) {\n		  top = Math.round((winHeight / 2) - (height / 2));\n		}\n\n		window.open(target.href, \'intent\', windowOptions + \',width=\' + width +\n										   \',height=\' + height + \',left=\' + left + \',top=\' + top);\n		e.returnValue = false;\n		e.preventDefault && e.preventDefault();\n	  }\n	}\n  }\n\n  if (document.addEventListener) {\n	document.addEventListener(\'click\', handleIntent, false);\n  } else if (document.attachEvent) {\n	document.attachEvent(\'onclick\', handleIntent);\n  }\n  window.__twitterIntentHandler = true;\n}());\n</script>\nJS;\n  $arc_twitter[\'widget_js\'] = true;\n  return $js;\n}\n\n// Deprecated arc_twitter_retweet tag, use arc_twitter_tweet_button instead\nfunction arc_twitter_retweet($atts, $thing=null)\n{\n  return arc_twitter_tweet_button($atts, $thing=null);\n}\nfunction arc_twitter_tweet_button($atts, $thing=null)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'user\'        => $prefs[\'arc_twitter_user\'], // via user account\n		\'url\'         => \'\',\n		\'text\'        => \'\',\n		\'follow1\'     => \'\',\n		\'follow2\'     => \'\',\n		\'lang\'        => \'\',\n		\'count\'       => \'horizontal\',\n		\'include_js\'  => true,\n		\'optimise_js\' => false,\n		\'wraptag\'     => \'\',\n		\'class\'       => \'twitter-share-button\'\n	),$atts));\n\n	$q = \'\'; // query string\n\n	if ($id=$thisarticle[\'thisid\']) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $row = safe_row(\"REPLACE(tweet,CONCAT(\' \',tinyurl),\'\') AS tweet,tinyurl\"\n		, \'arc_twitter\', \"article_id={$id}\");\n\n	  if ($url==\'\') {\n		$url = ($url) ? $url : permlinkurl($thisarticle);\n		$q = \'url=\'.urlencode($url);\n	  }\n	  if ($text==\'\') {\n		$text = ($row[\'tweet\']) ? $row[\'tweet\'] : $thisarticle[\'title\'];\n	  }\n	  $q .= ($q ? \'&amp;\' : \'\').\'text=\'.urlencode($text);\n	}\n	if ($user) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'via=\'.urlencode($user);\n	}\n	if ($follow1&&$follow2) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'related=\'.urlencode($follow1.\':\'.$follow2);\n	} elseif ($follow1||$follow2) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'related=\'.urlencode($follow1.$follow2);\n	}\n\n	switch ($lang) {\n	  case \'fr\': break; case \'de\': break; case \'es\': break; case \'jp\': break;\n	  default:\n		$lang = \'en\';\n	}\n	$q .= ($q ? \'&amp;\' : \'\').\'lang=\'.urlencode($lang);\n\n	switch ($count) {\n	  case \'none\': break; case \'vertical\': break;\n	  default:\n		$count = \'horizontal\';\n	}\n	$q .= ($q ? \'&amp;\' : \'\').\'count=\'.urlencode($count);\n\n	$thing = ($thing===null) ? \'Tweet\' : parse($thing);\n\n	$html = href($thing,\'http://twitter.com/share?\'.$q\n	  , \' class=\"\'.$class.\'\"\');\n\n	$js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n	return $js.$html;\n}\n\n/*\n * Twitter Follow button\n */\nfunction arc_twitter_follow_button($atts, $thing=null)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'user\'        => $prefs[\'arc_twitter_user\'], // via user account\n		\'lang\'        => \'\',\n		\'count\'       => true,\n		\'include_js\'  => true,\n		\'optimise_js\' => false,\n		\'class\'       => \'twitter-follow-button\'\n	),$atts));\n\n	$atts = \'\'; // data attributes\n\n	switch ($lang) {\n	  case \'fr\': break; case \'de\': break; case \'es\': break; case \'jp\': break;\n	  default:\n		$lang = \'en\';\n	}\n	$atts .= \' data-lang=\"\'.urlencode($lang).\'\"\';\n\n	$atts .= \' data-show-count=\"\'.($count?\'true\':\'false\').\'\"\';\n\n	$thing = ($thing===null) ? \'Follow @\'.$user : parse($thing);\n\n	$html = href($thing,\'http://twitter.com/\'.urlencode($user)\n	  , \' class=\"\'.$class.\'\"\'.$atts);\n\n	$js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n	return $js.$html;\n}\n\n/*\n	Admin-side functions\n	================================================================\n*/\n\n// Installation function - builds MySQL table\nfunction arc_twitter_install()\n{\n	global $DB;\n	// For first install, create table for tweets\n	$sql = \"CREATE TABLE IF NOT EXISTS \".PFX.\"arc_twitter \";\n	$sql.= \"(arc_twitterid INTEGER AUTO_INCREMENT PRIMARY KEY,\n		article_id INTEGER(11),\n		tweet_id VARCHAR(100),\n		tweet VARCHAR(280),\n		tinyurl VARCHAR(30));\";\n\n	if (!safe_query($sql)) {\n		return \'Error - unable to create arc_twitter table\';\n	}\n\n	// Check if we need to update an existing table to use the larger string length.\n	$sql = \'SELECT column_type FROM information_schema.columns WHERE column_name = \\\'tweet\\\' AND table_name = \\\'\' . safe_pfx(\'arc_twitter\') . \'\\\'\';\n	$sql .= \' AND table_schema = \\\'\' . $DB->db . \'\\\'\';\n	$result = nextRow(safe_query($sql));\n	if (! empty($result[\'column_type\']) && strpos($result[\'column_type\'], \'varchar(140)\') !== false) {\n		safe_alter(\'arc_twitter\', \'CHANGE tweet tweet VARCHAR(280)\');\n	}\n\n}\n\n// Uninstall function - deletes MySQL table and related preferences\nfunction arc_twitter_uninstall()\n{\n\n	$sql = \"DROP TABLE IF EXISTS \".PFX.\"arc_twitter;\";\n	if (!safe_query($sql)) {\n		return \'Error - unable to delete arc_twitter table\';\n	}\n\n	$sql = \"DELETE FROM  \".PFX.\"txp_prefs WHERE event=\'arc_twitter\';\";\n	if (!safe_query($sql)) {\n		return \'Error - unable to delete arc_twitter preferences\';\n	}\n\n}\nfunction arc_twitter_url_method_select($name, $val)\n{\n	$methods = array(\n		\'full\' => \'Do not shorten\',\n		\'tinyurl\' => \'Tinyurl\',\n		\'isgd\' => \'Is.gd\',\n		\'arc_twitter\' => \'TXP Tweet\',\n		\'smd\' => \'smd_short_url\'\n	);\n	return selectInput($name, $methods, $val, \'\', \'\', $name);\n}\nfunction arc_twitter_tab_select($name, $val)\n{\n	$tabs = array(\'content\' => \'Content\',\n		\'extensions\' => \'Extensions\',\n		\'\' => \'Hidden\');\n	return selectInput($name, $tabs, $val, \'\', \'\', $name);\n}\n// Provide interface for setting preferences\nfunction arc_twitter_prefs($event,$step)\n{\n\n	global $prefs, $arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	$user = $prefs[\'arc_twitter_user\'];\n	$prefix = $prefs[\'arc_twitter_prefix\'];\n	$suffix = $prefs[\'arc_twitter_suffix\'];\n	$tweet_default = $prefs[\'arc_twitter_tweet_default\'];\n	$url_method = $prefs[\'arc_twitter_url_method\'];\n	$short_url = $prefs[\'arc_short_url\'];\n	$short_site_url = $prefs[\'arc_short_site_url\'];\n	$cache_dir = $prefs[\'arc_twitter_cache_dir\'];\n	$tab = $prefs[\'arc_twitter_tab\'];\n	$utm = !empty($prefs[\'arc_twitter_utm\']) ? $prefs[\'arc_twitter_utm\'] : 0;\n\n	switch ($step) {\n		case \'prefs_save\': pagetop(\'TXP Tweet\', \'Preferences saved\'); break;\n		case \'register\': pagetop(\'TXP Tweet\',\'Connect to Twitter\'); break;\n		case \'validate\':\n		default: pagetop(\'TXP Tweet\');\n	}\n\n	$html = \'\';\n\n	if ($step==\'register\') { // OAuth registration process\n\n		$twit = new arc_twitter($arc_twitter_consumerKey, $arc_twitter_consumerSecret);\n\n		// Build a callback URL for Twitter to return to the next stage\n		$callbackURL = $twit->callbackURL($event,\'validate\');\n\n		$request = $twit->getRequestToken($callbackURL);\n		$request_token = $request[\"oauth_token\"];\n		$request_token_secret = $request[\"oauth_token_secret\"];\n\n		set_pref(\'arc_twitter_requestToken\',$request_token, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_requestTokenSecret\',$request_token_secret, \'arc_twitter\',2);\n\n	$html = \"<div class=\'text-column\'>\"\n	  .\"<p>\".href(\'Sign-in to Twitter\', $twit->getAuthorizeURL($request)).\" and follow the instructions to allow TXP Tweet to use your account. If you are already signed in to Twitter then that account will be associated with TXP Tweet so you may need to sign out first if you want to use a different account.</p>\"\n	  .\"</div>\";\n\n	} elseif ($step==\'validate\') {\n		$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_requestToken\']\n			, $prefs[\'arc_twitter_requestTokenSecret\']);\n		// Ask Twitter for an access token (and an access token secret)\n		$request = $twit->getAccessToken( gps(\'oauth_verifier\') );\n		$access_token = $request[\'oauth_token\'];\n		$access_token_secret = $request[\'oauth_token_secret\'];\n		$user = $request[\'screen_name\'];\n		// Store the access token and secret\n		set_pref(\'arc_twitter_accessToken\',$access_token, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_accessTokenSecret\',$access_token_secret\n			, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_user\',$user);\n		$prefs[\'arc_twitter_accessToken\'] = $access_token;\n		$prefs[\'arc_twitter_accessTokenSecret\'] = $access_token_secret;\n		unset($twit);\n	}\n\n	if ($step==\"prefs_save\") {\n\n		$prefix = trim(gps(\'arc_twitter_prefix\'));\n		$suffix = trim(gps(\'arc_twitter_suffix\'));\n		$tweet_default = gps(\'arc_twitter_tweet_default\');\n		$url_method = gps(\'arc_twitter_url_method\');\n		$short_url = gps(\'arc_short_url\');\n		$short_site_url = gps(\'arc_short_site_url\');\n		$cache_dir = gps(\'arc_twitter_cache_dir\');\n		$tab = gps(\'arc_twitter_tab\');\n		$utm = gps(\'arc_twitter_utm\');\n\n		if (strlen($prefix)<=20) {\n			set_pref(\'arc_twitter_prefix\',$prefix);\n		} else {\n			$prefix = $prefs[\'arc_twitter_prefix\'];\n		}\n		if (strlen($suffix)<=20) {\n			set_pref(\'arc_twitter_suffix\',$suffix);\n		} else {\n			$suffix = $prefs[\'arc_twitter_suffix\'];\n		}\n		$tweet_default = ($tweet_default) ? 1 : 0;\n		$short_url = ($short_url) ? 1 : 0;\n		if (!$short_site_url) {\n			$short_site_url = $prefs[\'siteurl\'];\n		}\n		set_pref(\'arc_twitter_tweet_default\', $tweet_default);\n		set_pref(\'arc_short_url\', $short_url);\n		set_pref(\'arc_twitter_url_method\', $url_method);\n		set_pref(\'arc_short_site_url\', $short_site_url);\n		set_pref(\'arc_twitter_cache_dir\', $cache_dir);\n		set_pref(\'arc_twitter_tab\', $tab);\n		set_pref(\'arc_twitter_utm\', $utm);\n\n	}\n\n	if ( $step!=\'register\' ) {\n		if ( isset($prefs[\'arc_twitter_accessToken\'])\n		&& isset($prefs[\'arc_twitter_accessTokenSecret\']) ) {\n			$twit = new arc_twitter($arc_twitter_consumerKey\n				, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n				, $prefs[\'arc_twitter_accessTokenSecret\']);\n			$registerURL = $twit->callbackURL($event,\'register\');\n\n			// Define the fields ready to build the form\n			$fields = array(\n				\'Tweet Settings\' => array(\n				  \'arc_twitter_prefix\' => array(\n					\'label\' => \'Tweet prefix\',\n					\'value\' => $prefix\n				  ),\n				  \'arc_twitter_suffix\' => array(\n					\'label\' => \'Tweet suffix\',\n					\'value\' => $suffix\n				  ),\n				  \'arc_twitter_tweet_default\' => array(\n					\'label\' => \'Tweet articles by default\',\n					\'type\' => \'yesnoRadio\',\n					\'value\' => $tweet_default\n				  ),\n				  \'arc_twitter_url_method\' => array(\n					\'label\' => \'URL shortner\',\n					\'type\' => \'arc_twitter_url_method_select\',\n					\'value\' => $url_method\n				  )\n				),\n				\'TXP Tweet short URL\' => array(\n				  \'arc_short_url\' => array(\n					\'label\' => \'Enable TXP Tweet short URL redirect\',\n					\'type\' => \'yesnoRadio\',\n					\'value\' => $short_url\n				  ),\n				  \'arc_short_site_url\' => array(\n					\'label\' => \'TXP Tweet short site URL\',\n					\'value\' => $short_site_url\n				  )\n				),\n				\'Twitter Tab\' => array(\n				  \'arc_twitter_tab\' => array(\n					\'label\' => \'Location of tab\',\n					\'type\' => \'arc_twitter_tab_select\',\n					\'value\' => $tab\n				  )\n				),\n				\'Google Analytics\' => array(\n					\'arc_twitter_utm\' => array(\n						\'label\' => \'Enable UTM\',\n						\'type\' => \'yesnoRadio\',\n						\'value\' => $utm\n					)\n				),\n				\'Cache\' => array(\n				  \'arc_twitter_cache_dir\' => array(\n					\'label\' => \'Cache directory\',\n					\'value\' => $cache_dir\n				  )\n				)\n			);\n\n			$form = \"<h2>Twitter account details</h2>\"\n		.\"<p><span class=\'edit-label\'>Twitter username</span>\"\n		.\"<span class=\'edit-value\'>\"\n		.($prefs[\'arc_twitter_user\'] ? $user.\' (\'.href(\'Re-connect\',$registerURL).\')\' : \'<em>unknown</em>\'.href(\'Connect to Twitter\',$registerURL))\n				.\"</span></p>\";\n\n			$form .= _arc_twitter_form_builder($fields);\n\n			$form .= sInput(\'prefs_save\').n.eInput(\'plugin_prefs.arc_twitter\');\n\n	  $form .= \'<p>\'.fInput(\'submit\', \'Submit\', gTxt(\'save_button\'), \'publish\').\'</p>\';\n\n	  $html = \"<h1 class=\'txp-heading\'>TXP Tweet</h1>\"\n		.\"<p class=\'nav-tertiary\'>\"\n		.\"<a href=\'./?event=arc_admin_twitter\' class=\'navlink\'>Twitter</a><a href=\'./?event=plugin_prefs.arc_twitter\' class=\'navlink-active\'>Options</a>\"\n		.\"</p>\";\n\n	  $html .= form(\"<div class=\'plugin-column\'>\".$form.\"</div>\", \" class=\'edit-form\'\");\n\n		} elseif ( $step!=\'register\' ) {\n\n			$registerURL = arc_twitter::callbackURL($event,\'register\');\n\n			$form = \"<h2>Twitter account details</h2>\"\n		.\"<span class=\'edit-label\'>Twitter username</span>\"\n		.\"<span class=\'edit-value\'><em>unknown</em> &mdash; \"\n		.href(\'Connect to Twitter\',$registerURL)\n				.\"</span>\";\n\n	  $html = form(\"<div class=\'plugin-column\'>\".$form.\"</div>\", \" class=\'edit-form\'\");\n\n		}\n	}\n\n	// Set jQuery for switching on/off relevant arc_short_url fields\n	$js = <<<JS\n<script language=\"javascript\" type=\"text/javascript\">\n$(document).ready(function(){\n  var onoff = $(\'.arc_short_url\');\n  var arc_short_url_off = $(\'#arc_short_url-arc_short_url-0\');\n  var url = $(\'.arc_short_site_url\');\n  var url_method = $(\'select[name=\"arc_twitter_url_method\"]\');\n\n  if (arc_short_url_off.attr(\'checked\')==\'checked\' && $(\'option:selected\', url_method).val()!=\'arc_twitter\') {\n	url.hide();\n  }\n  $(\'input\', onoff).change(function(){\n	if ($(\'option:selected\', url_method).val()!=\'arc_twitter\') {\n	  arc_short_url_off.attr(\'checked\')==\'checked\' ? url.hide() : url.show();\n	}\n  });\n\n  if ($(\'option:selected\', url_method).val()==\'arc_twitter\') {\n	onoff.hide(); url.show();\n  }\n  url_method.change(function(){\n	if ($(\'option:selected\', url_method).val()==\'arc_twitter\') {\n	  onoff.toggle(); url.show();\n	} else {\n	  onoff.toggle();\n	  arc_short_url_off.attr(\'checked\')==\'checked\' ? url.hide() : url.show();\n	}\n  })\n});\n</script>\nJS;\n\n	echo $js.$html;\n}\n\nfunction _arc_twitter_form_builder($fields) {\n\n  $form = \'\';\n\n  foreach ($fields as $fk => $fv) {\n\n	$form .= ($fk) ? \"<h2>$fk</h2>\" : \'\';\n\n	foreach ($fv as $k => $v) {\n\n	  $type = isset($v[\'type\']) ? $v[\'type\'] : \'text\';\n\n	  $form .= \"<p class=\'$k\'>\"\n		.\"<span class=\'edit-label\'><label for=\'$k\'>\".$v[\'label\'].\"</label></span>\";\n\n	  switch ($type)  {\n\n		case \'textarea\':\n\n		  $form .= text_area($k, \'50\', \'550\', $v[\'value\'], $k);\n		  break;\n\n		case \'yesnoRadio\':\n\n		  $form .= \"<span class=\'edit-value\'>\".yesnoRadio($k, $v[\'value\'], \'\', $k).\"</span>\";\n		  break;\n\n		case \'arc_twitter_tab_select\':\n\n		  $form .= \"<span class=\'edit-value\'>\".arc_twitter_tab_select($k, $v[\'value\']).\"</span>\";\n		  break;\n\n		case \'arc_twitter_url_method_select\':\n\n		  $form .= \"<span class=\'edit-value\'>\".arc_twitter_url_method_select($k, $v[\'value\']).\"</span>\";\n		  break;\n\n		default:\n\n		  $form .= \"<span class=\'edit-value\'>\".fInput(\'text\',$k,$v[\'value\'],\'\',\'\',\'\',\'\',\'\',$k).\"</span>\";\n		  break;\n\n	  }\n\n	  $form .= \"</p>\";\n	}\n\n  }\n\n  return $form;\n}\n\n// Add Twitter tab to Textpattern\nfunction arc_admin_twitter($event,$step)\n{\n	global $prefs, $arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n	$twit->cacheDir($prefs[\'arc_twitter_cache_dir\']);\n\n	$data = $twit->get(\'users/show\'\n		, array(\'screen_name\'=>$prefs[\'arc_twitter_user\']));\n	$twitterUser = $data;\n	$twitterUserURL = \'http://www.twitter.com/\'.$twitterUser->screen_name;\n\n	if ($step==\"tweet\") { // post an update to Twitter\n\n		// fetch and clean message\n		$message = strip_tags(gps(\'message\'));\n		// We need to account for t.co URL shortening in our count (22 characters for a URL, 23 if https).\n		$urlPattern = \"/(http|https):\\/\\/[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&amp;:\\/~+#-]*[\\w@?^=%&amp;\\/~+#-])?/\";\n		$tmpMessage = preg_replace($urlPattern, \"$1xxxxxxxxxxxxxxxxxx\", $message);\n		$count = strlen($tmpMessage);\n		unset($tmpMessage);\n\n		if ($count<=280 && $count>0) { // post update\n			$result = $twit->post(\'statuses/update\', array(\'status\' => $message));\n		} else { // message too long, JavaScript interface should prevent this\n			$result = false;\n		}\n\n		pagetop(\'Twitter\',\n			(($result!=false)?\'Twitter updated\':\'Error updating Twitter\'));\n\n	} elseif ($step==\"delete\") { // delete an update from Twitter\n\n		$id = strip_tags(gps(\'id\'));\n		if ($id) {\n			$twit->delete(\'statuses/destroy/\'.$id);\n			safe_delete(\'arc_twitter\',\"tweet_id = $id\");\n		}\n\n		pagetop(\'Twitter\',\'Twitter updated\');\n\n	} else {\n\n		pagetop(\'Twitter\');\n\n	}\n\n	// Prepare JavaScript to create Twitter update interface\n\n	$js = \'<script language=\"javascript\" type=\"text/javascript\">\';\n	$js.= <<<JS\n$(document).ready(function(){\n	var counter = $(\'<span>\', {\n		\'text\' : \'280\',\n		\'id\' : \'tweetcount\'\n	});\n	$(\'.message\').append(counter);\n	var counterStyle = \'font-weight:bold;padding-left:.5em;font-size:2em;line-height:1.2em;\';\n	$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#ccc;\');\n	$(\'#message\').keyup(function() {\n		// We need to account for t.co URL shortening in our count (22 characters for a URL, 23 if https).\n		var message = $(\'#message\').val();\n		var urlPattern = /(http|https):\\/\\/[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&amp;:\\/~+#-]*[\\w@?^=%&amp;\\/~+#-])?/;\n		message = message.replace(urlPattern, \'$1xxxxxxxxxxxxxxxxxx\');\n		var count = 280-message.length;\n		$(\'#tweetcount\').html(count+\'\'); // hack to force output of 0\n		if (count<0) {\n			$(\'input.publish\').prop(\'disabled\', \'disabled\');\n		} else {\n			$(\'input.publish\').prop(\'disabled\', \'\');\n		}\n		if (count<0) {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#f00;\');\n		} else if (count<10) {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#000;\');\n		} else {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#ccc;\');\n		}\n	})\n});\nJS;\n	$js.= \"</script>\";\n\n	$out = \'\';\n	$tweets = $twit->get(\'statuses/user_timeline\'\n		, array(\'screen_name\'=>$prefs[\'arc_twitter_user\'],\'count\'=>25));\n	if ($tweets) foreach ($tweets as $tweet) {\n		$time = strtotime(htmlentities($tweet->created_at));\n		$date = safe_strftime($prefs[\'archive_dateformat\'],$time);\n		$links = false;\n		if (isset($tweet->entities->urls)) {\n			$links = array();\n			foreach ($tweet->entities->urls as $url) {\n				$links[$url->url] = array(\n					\'expanded_url\' => $url->expanded_url,\n					\'display_url\' => $url->display_url\n				);\n			}\n		}\n		$out.= tr(td($date,\'span\')\n			.td(arc_Twitter::makeLinks(htmlentities($tweet->text\n				, ENT_QUOTES,\'UTF-8\'), $links, true))\n			.td(dLink(\'arc_admin_twitter\',\'delete\',\'id\',$tweet->id_str,\'\'))\n		);\n	}\n\n	$fields = array(\n	\'\' => array(\n	  \'message\' => array(\n		\'label\' => \'Update Twitter\',\n		\'type\' => \'textarea\',\n		\'value\' => \'\'\n	  )\n	)\n	);\n\n	$profile = \'<img src=\"\'.$twitterUser->profile_image_url.\'\" alt=\"Twitter avatar\" style=\"float:left; margin-right: 1em\" />\'\n	.graf(href($twitterUser->name,$twitterUserURL),\' style=\"font-size:1.2em;font-weight:bold;\"\')\n	.graf(href($twitterUser->friends_count.\' following\',$twitterUserURL.\'/following\')\n	.\', \'.href($twitterUser->followers_count.\' followers\',$twitterUserURL.\'/followers\')\n	.\', \'.href($twitterUser->statuses_count.\' updates\',$twitterUserURL));\n\n	$form = _arc_twitter_form_builder($fields)\n	.eInput(\'arc_admin_twitter\')\n	.sInput(\'tweet\');\n  $form .= \'<p>\'.fInput(\'submit\', \'Submit\', gTxt(\'Update\'), \'publish\').\'</p>\';\n\n  $html = \"<h1 class=\'txp-heading\'>TXP Tweet</h1>\"\n	.\"<p class=\'nav-tertiary\'>\"\n	.\"<a href=\'./?event=arc_admin_twitter\' class=\'navlink-active\'>Twitter</a><a href=\'./?event=plugin_prefs.arc_twitter\' class=\'navlink\'>Options</a>\"\n	.\"</p>\";\n\n	$html .= \"<div class=\'text-column\'>\".$profile.\"</div>\"\n	.\"<br style=\'clear:both\' />\"\n	.form(\"<div class=\'plugin-column\'>\".$form.\"</div>\".br);\n\n	// Attach recent Twitter updates\n\n	$html.= \"<div class=\'txp-listtables\'>\"\n	.startTable(\'arc_twitter_timeline\',\'\',\'txp-list\').$out.endTable()\n	.\"</div>\";\n\n	// Output JavaScript and HTML\n\n	echo $js.$html;\n}\n\n// Add Twitter options to article article screen\nfunction arc_append_twitter($event, $step, $data, $rs1)\n{\n	global $prefs, $arc_twitter, $app_mode;\n\n	$prefix = trim(gps(\'arc_twitter_prefix\'));\n	$prefix = ($prefix) ? $prefix : $prefs[\'arc_twitter_prefix\'];\n	$suffix = trim(gps(\'arc_twitter_suffix\'));\n	$suffix = ($suffix) ? $suffix : $prefs[\'arc_twitter_suffix\'];\n\n	if ($rs1[\'ID\']) {\n		$sql = \"SELECT tweet_id,tweet FROM \".PFX.\"arc_twitter WHERE article_id=\".$rs1[\'ID\'].\";\";\n		$rs2 = safe_query($sql); $rs2 = nextRow($rs2);\n	} else { // new article\n		$rs2 = \'\';\n	}\n\n	if ($app_mode == \'async\')\n	{\n	 send_script_response(\'$(\"#arc_twitter\").remove();\');\n	}\n\n	if ($rs1[\'ID\'] && $rs2[\'tweet_id\'])\n	{\n		$content = arc_Twitter::makeLinks($rs2[\'tweet\']);\n		return $data.fieldset(tag($content, \'p\'), \'Twitter update\', \'arc_twitter\');\n	}\n	else\n	{\n		$var = gps(\'arc_tweet_this\');\n		$var = ($rs1[\'ID\']&&!$var) ? 0 : $prefs[\'arc_twitter_tweet_default\'];\n		$content  = tag(yesnoRadio(\'arc_tweet_this\', $var, \'\', \'arc_tweet_this\'),\'p\');\n		$content .= tag(href(\'Options\',\'#arc_twitter_options\', \' onclick=\"$(\\\'#arc_twitter_options\\\').toggle(); return false;\"\'),\'p\',\' style=\"margin-top:5px;\"\');\n		$content .= tag(tag(tag(\'Tweet prefix\',\'label\', \' for=\"arc_twitter_prefix\"\') . \'<br />\'\n			.fInput(\'text\',\'arc_twitter_prefix\',$prefix,\'edit\',\'\',\'\',\'22\',\'\',\'arc_twitter_prefix\'),\'p\')\n			.tag(tag(\'Tweet suffix (eg #hashtags)\',\'label\', \' for=\"arc_twitter_suffix\"\') . \'<br />\'\n			.fInput(\'text\',\'arc_twitter_suffix\',$suffix,\'edit\',\'\',\'\',\'22\',\'\',\'arc_twitter_suffix\'),\'p\')\n			,\'div\',\' id=\"arc_twitter_options\" class=\"toggle\" style=\"display:none\"\');\n		if (isset($arc_twitter[\'error\'])) {\n			$content .= \'<p>\'.$arc_twitter[\'error\'].\'</p>\';\n		}\n		return $data.fieldset($content, \'Update Twitter\', \'arc_twitter\');\n	}\n\n}\n\n// Update Twitter with posted article\nfunction arc_article_tweet($event,$step)\n{\n	global $prefs, $arc_twitter, $arc_twitter_consumerKey\n		, $arc_twitter_consumerSecret;\n\n	$article_id = empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\'];\n	if (!empty($article_id)) {\n\n		include_once txpath.\'/publish/taghandlers.php\';\n\n		$article = safe_row(\"ID, Title, Section, Posted\", \'textpattern\',\n			\"ID={$article_id} AND Status=4 AND now()>=Posted\");\n\n		if ($article && gps(\'arc_tweet_this\')) { // tweet article\n\n			// Need to manually update the \'URL only title\' before building the\n			// URL\n			$article[\'url_title\'] = gps(\'url_title\');\n\n			// Make short URL\n			$url = permlinkurl($article);\n			$short_url = arc_shorten_url(\n				$url,\n				$prefs[\'arc_twitter_url_method\'],\n				array(\'id\' => $article_id)\n			);\n\n			if (!$short_url) { // Failed to obtain a shortened URL, do not tweet!\n				$arc_twitter[\'error\'] = \'Unable to obtain a short URL for this article.\';\n\n				return false;\n			}\n\n			// Construct Twitter update\n			$prefix  = trim(gps(\'arc_twitter_prefix\'));\n			$pre_len = strlen($prefix);\n			$prefix  = ($prefix && $pre_len<=20) ? $prefix.\' \' : \'\';\n			$suffix  = trim(gps(\'arc_twitter_suffix\'));\n			$suf_len = strlen($suffix);\n			$suffix  = ($suffix && $suf_len<=40) ? \' \'.$suffix : \'\';\n			$url_len = preg_match(\'|^https|i\', $short_url) ? 23 : 22; // count URL length + 1 for prefixed space\n			if ($prefix) {\n				$pre_len += 1;\n			}\n			if ($suffix) {\n				$suf_len += 1;\n			}\n			if ((strlen($article[\'Title\'])+$url_len+$pre_len+$suf_len)>280) {\n				$article[\'Title\'] = substr($article[\'Title\'],0,135-$url_len-$pre_len-$suf_len).\'...\';\n			}\n			$tweet = $prefix.$article[\'Title\'].\" \".$short_url.$suffix;\n\n			// Update Twitter\n			$twit = new arc_twitter($arc_twitter_consumerKey\n				, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n				, $prefs[\'arc_twitter_accessTokenSecret\']);\n			$result = $twit->post(\'statuses/update\', array(\'status\' => $tweet));\n\n			$tweet_id = (is_object($result)) ? $result->id_str : 0;\n\n			if ($tweet_id) {\n\n				$tweet = addslashes($tweet);\n\n				// If we\'re not using a URL shortener don\'t store the short URL.\n				if ($prefs[\'arc_twitter_url_method\']==\'full\') {\n					$short_url = null;\n				}\n\n				// update arc_twitter table with tweet\n				$sql = \"INSERT INTO \".PFX.\"arc_twitter (article_id,tweet_id,tweet,tinyurl) \";\n				$sql.= \"VALUES($article_id,$tweet_id,\\\"$tweet\\\",\'$short_url\');\";\n				safe_query($sql);\n\n				return true;\n\n			} else {\n\n				$arc_twitter[\'error\'] = \'Twitter response: \'\n					.$twit->http_code;\n				return false;\n\n			}\n\n		}\n\n	}\n\n	return false;\n\n}\n\n/*\n * Shorten URLs using various methods\n */\n\nfunction arc_shorten_url($url, $method=\'\', $atts=array())\n{\n  global $prefs;\n\n  $ch = curl_init();\n  curl_setopt($ch, CURLOPT_HEADER, 0);\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n\n  $utm = null;\n\n  if (!empty($prefs[\'arc_twitter_utm\'])) {\n\n	$utm = \'utm_source=twitter.com&utm_medium=social&utm_campaign=arc_twitter&utm_content=txp:\' . $atts[\'id\'];\n	$separator = (parse_url($url, PHP_URL_QUERY) == NULL) ? \'?\' : \'&\';\n	$url .= $separator . $utm;\n\n  }\n\n  switch ($method) {\n	case \'smd\': // create a shortened URL using SMD Short URL\n\n		$url = !empty($atts[\'id\']) ? hu . $atts[\'id\'] : false;\n		$url .= ($url!==false && !empty($utm)) ? \'?\' . $utm : false;\n		return $url; break;\n\n	case \'arc_twitter\': // native URL shortening\n\n		$url = !empty($atts[\'id\']) ? PROTOCOL . $prefs[\'arc_short_site_url\'] . \'/\' . $atts[\'id\'] : false;\n		$url .= ($url!==false && !empty($utm)) ? \'?\' . $utm : false;\n		return $url;\n		break;\n\n	case \'isgd\':\n\n		$u = \'http://is.gd/api.php?longurl=\'.urlencode($url);\n		curl_setopt($ch, CURLOPT_URL, $u);\n\n		$tinyurl = curl_exec($ch);\n		if ($tinyurl!=\'Error\' && $tinyurl!=\'\') {\n			return $tinyurl;\n		} else {\n			trigger_error(\'arc_twitter failed to get a is.gd URL for \'\n				.$url,E_USER_WARNING);\n		}\n		break;\n\n	case \'tinyurl\': // create a shortened URL using TinyURL\n\n		$u = \'http://tinyurl.com/api-create.php?url=\'.urlencode($url);\n		curl_setopt($ch, CURLOPT_URL, $u);\n		$tinyurl = curl_exec($ch);\n		if ($tinyurl!=\'Error\' && $tinyurl!=\'\') {\n			return $tinyurl;\n		} else {\n			trigger_error(\'arc_twitter failed to get a TinyURL for \'.$url,E_USER_WARNING);\n		}\n\n	default:\n\n		return $url;\n\n  }\n\n  return false; // fail\n\n}\n\n/*\n * Shortened URL redirect based on smd_short_url\n */\nfunction arc_short_url_redirect($event, $step) {\n  global $prefs;\n\n  $have_id = 0;\n\n  // Check if there is an available short site url and if it is being used in\n  // this instance\n  $short_site_url = $prefs[\'arc_short_site_url\'];\n  if ($short_site_url) {\n	$short_site_url = PROTOCOL.$short_site_url.\'/\';\n	$url_parts = parse_url($short_site_url);\n	$re = \'#^\'.$url_parts[\'path\'].\'([0-9].*)#\';\n	$have_id = preg_match($re, $_SERVER[\'REQUEST_URI\'], $m);\n  }\n\n  // Fall back to standard site url (smd_short_url behaviour)\n  if ($have_id) {\n	$url_parts = parse_url(hu);\n	$re = \'#^\'.$url_parts[\'path\'].\'([0-9].*)#\';\n	$have_id = preg_match($re, $_SERVER[\'REQUEST_URI\'], $m);\n  }\n\n  // Do the redirect if we\'ve got an article id\n  if ($have_id) {\n	$id = $m[1];\n	$permlink = permlinkurl_id($id);\n\n	if ($permlink) {\n	  ob_end_clean();\n\n	  // Stupid, over the top header setting for IE\n	  header(\"Status: 301\");\n	  header(\"HTTP/1.0 301 Moved Permanently\");\n	  header(\"Location: \".$permlink, TRUE, 301);\n\n	  // In case the header() method fails, fall back on a classic redirect\n	  echo \'<html><head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=\'\n		.$permlink.\'\"></head><body></body></html>\';\n	  die();\n	}\n  }\n\n}\n\n// Auto enable plugin on install (original idea by Michael Manfre)\nfunction _arc_twitter_auto_enable($event, $step, $prefix=\'arc_twitter\')\n{\n  $plugin = substr($event, strlen(\'plugin_lifecycle.\'));\n  if (strncmp($plugin, $prefix, strlen($prefix)) == 0)\n  {\n	safe_update(\'txp_plugin\', \"status = 1\", \"name = \'\" . doSlash($plugin) . \"\'\");\n  }\n}\n\n/*\n *******************************************************************************\n*/\n\nclass arc_twitter extends TwitterOAuth {\n	// Caching variables\n	private $_cache = true;\n	private $_cache_dir = \'./tmp\';\n	private $_cache_time = 1800; // 30 minute cache\n\n	function __construct($consumer_key, $consumer_secret, $oauth_token = NULL\n		, $oauth_token_secret = NULL)\n	{\n		parent::__construct($consumer_key, $consumer_secret, $oauth_token\n			, $oauth_token_secret);\n		$this->format = \'json\';\n		$this->timeout = 15;\n		$this->connecttimeout = 15;\n	}\n\n	public function callbackURL($event,$step)\n	{\n		return preg_replace(\'/\\?.*/\', \'\',PROTOCOL.$_SERVER[\'SERVER_NAME\'].$_SERVER[\'REQUEST_URI\'])\n		  .\'?event=\'.$event.\'&amp;step=\'.$step;\n	}\n\n	// create Twitter and external links in text\n	public static function makeLinks($text, $urls = false, $expandedUrls = false)\n	{\n		if ($urls!==false) {\n\n			foreach ($urls as $tcoUrl => $url)\n			{\n				if (!$expandedUrls)\n				{\n					$url[\'expanded_url\'] = $tcoUrl;\n					$url[\'display_url\'] = preg_replace(\'|^https?:\\/\\/|i\', \'\', $tcoUrl);\n				}\n				$text = str_replace(\n					$tcoUrl,\n					\"<a href=\'{$url[\'expanded_url\']}\'>{$url[\'display_url\']}</a>\",\n					$text\n				);\n			}\n\n		}\n		elseif ($urls!==false)\n		{\n\n		}\n		else\n		{\n			$links = array(\n				\'/\\b(http|https|ftp):\\/\\/([A-Z0-9][A-Z0-9_-]*(?:\\.[A-Z0-9][A-Z0-9_-]*)+):?(\\d+)?\\/?([\\/\\w+-\\.]+)(\\?[\\.a-z=&0-4:_-]+){0,1}\\b/i\' => \"<a href=\'$0\' rel=\'external\'>$2$3$4</a>\",\n				\'/\\b(^|\\s)www.([a-z_A-Z0-9]+)((\\.[a-z]+)+)\\b/i\' => \"<a href=\'http://www.$2$3\' rel=\'external\'>www.$2$3</a>\"\n			);\n			$text = preg_replace(\n				array_keys($links),\n				array_values($links),\n				$text\n			);\n		}\n\n		$links = array(\n			\'/(^|\\s).?@([a-z_A-Z0-9]+)/\' => \"$1<a href=\'http://twitter.com/$2\' rel=\'external\'>@$2</a>\",\n			\'/(^|\\s)(\\#([a-z_A-Z0-9:_-]+))/\' => \"$1<a href=\'http://twitter.com/search?q=%23$3\' rel=\'external\'>$2</a>\"\n		);\n		return preg_replace(\n			array_keys($links),\n			array_values($links),\n			$text\n		);\n  }\n\n	public function get($url, $params = array())\n	{\n		$api_url = md5($url.urlencode(serialize($params)));\n		$data = \'\';\n\n		if ($this->_cache) { // check for cached json\n\n			$data = $this->_retrieveCache($api_url);\n\n		}\n		if (empty($data)) {\n			$data = parent::get($url, $params); // data already json_decode\'d\n			if ($this->http_code===200 && $encoded_data=json_encode($data)) { // save cache\n				$file = $this->_cache_dir.\'/\'.$api_url;\n				file_put_contents($file,$encoded_data,LOCK_EX);\n				return $data;\n			} else { // failed to retrieve data from Twitter\n\n				if ($this->_cache) { // attempt to force cached json return\n\n					$data = $this->_retrieveCache($api_url,true);\n					if ($data) return json_decode($data);\n\n				}\n\n				return false;\n\n			}\n		} else { // return cached json\n			return json_decode($data);\n		}\n	} //end get()\n\n	function post($url, $params = array())\n	{\n		$data = parent::post($url,$params);\n		return $data;\n	}\n\n	function delete($url, $params = array())\n	{\n		$data = parent::delete($url,$params);\n		return $data;\n	}\n\n	// Cache methods\n\n	public function setCaching($cache=true)\n	{\n		$this->_cache = ($cache) ? true : false;\n		return true;\n	}\n\n	public function cacheDir($loc)\n	{\n		$this->_cache_dir = $loc;\n		return true;\n	}\n\n	public function cacheTime($mins)\n	{\n		$this->_cache_time = $mins*60; // convert minutes into seconds\n		return true;\n	}\n\n	private function _retrieveCache($url,$overide_timeout=false)\n	{\n		$file = $this->_cache_dir.\'/\'.$url;\n		if (file_exists($file)) {\n\n			$diff = time() - filemtime($file);\n			if ($overide_timeout || $diff < $this->_cache_time) {\n				return file_get_contents($file);\n			} else {\n				return false;\n			}\n\n		} else {\n			return false;\n		}\n	}\n}\n\n/*\n * Abraham Williams (abraham@abrah.am) http://abrah.am\n *\n * The first PHP Library to support OAuth for Twitter\'s REST API.\n */\n\n/**\n * Twitter OAuth class\n */\nclass TwitterOAuth {\n  /* Contains the last HTTP status code returned. */\n  public $http_code;\n  /* Contains the last API call. */\n  public $url;\n  /* Set up the API root URL. */\n  public $host = \"https://api.twitter.com/1.1/\";\n  /* Set timeout default. */\n  public $timeout = 30;\n  /* Set connect timeout. */\n  public $connecttimeout = 30;\n  /* Verify SSL Cert. */\n  public $ssl_verifypeer = FALSE;\n  /* Respons format. */\n  public $format = \'json\';\n  /* Decode returned json data. */\n  public $decode_json = TRUE;\n  /* Contains the last HTTP headers returned. */\n  public $http_info;\n  /* Set the useragnet. */\n  public $useragent = \'TwitterOAuth v0.2.0-beta2\';\n  /* Immediately retry the API call if the response was not successful. */\n  //public $retry = TRUE;\n\n  /**\n   * Set API URLS\n   */\n  function accessTokenURL()  { return \'https://api.twitter.com/oauth/access_token\'; }\n  function authenticateURL() { return \'https://api.twitter.com/oauth/authenticate\'; }\n  function authorizeURL()    { return \'https://api.twitter.com/oauth/authorize\'; }\n  function requestTokenURL() { return \'https://api.twitter.com/oauth/request_token\'; }\n\n  /**\n   * Debug helpers\n   */\n  function lastStatusCode() { return $this->http_status; }\n  function lastAPICall() { return $this->last_api_call; }\n\n  /**\n   * construct TwitterOAuth object\n   */\n  function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {\n	$this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();\n	$this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);\n	if (!empty($oauth_token) && !empty($oauth_token_secret)) {\n	  $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);\n	} else {\n	  $this->token = NULL;\n	}\n  }\n\n\n  /**\n   * Get a request_token from Twitter\n   *\n   * @returns a key/value array containing oauth_token and oauth_token_secret\n   */\n  function getRequestToken($oauth_callback) {\n	$parameters = array();\n	$parameters[\'oauth_callback\'] = $oauth_callback;\n	$request = $this->oAuthRequest($this->requestTokenURL(), \'GET\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * Get the authorize URL\n   *\n   * @returns a string\n   */\n  function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) {\n	if (is_array($token)) {\n	  $token = $token[\'oauth_token\'];\n	}\n	if (empty($sign_in_with_twitter)) {\n	  return $this->authorizeURL() . \"?oauth_token={$token}\";\n	} else {\n	   return $this->authenticateURL() . \"?oauth_token={$token}\";\n	}\n  }\n\n  /**\n   * Exchange request token and secret for an access token and\n   * secret, to sign API calls.\n   *\n   * @returns array(\"oauth_token\" => \"the-access-token\",\n   *                \"oauth_token_secret\" => \"the-access-secret\",\n   *                \"user_id\" => \"9436992\",\n   *                \"screen_name\" => \"abraham\")\n   */\n  function getAccessToken($oauth_verifier) {\n	$parameters = array();\n	$parameters[\'oauth_verifier\'] = $oauth_verifier;\n	$request = $this->oAuthRequest($this->accessTokenURL(), \'GET\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * One time exchange of username and password for access token and secret.\n   *\n   * @returns array(\"oauth_token\" => \"the-access-token\",\n   *                \"oauth_token_secret\" => \"the-access-secret\",\n   *                \"user_id\" => \"9436992\",\n   *                \"screen_name\" => \"abraham\",\n   *                \"x_auth_expires\" => \"0\")\n   */\n  function getXAuthToken($username, $password) {\n	$parameters = array();\n	$parameters[\'x_auth_username\'] = $username;\n	$parameters[\'x_auth_password\'] = $password;\n	$parameters[\'x_auth_mode\'] = \'client_auth\';\n	$request = $this->oAuthRequest($this->accessTokenURL(), \'POST\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * GET wrapper for oAuthRequest.\n   */\n  function get($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'GET\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * POST wrapper for oAuthRequest.\n   */\n  function post($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'POST\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * DELETE wrapper for oAuthReqeust.\n   */\n  function delete($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'DELETE\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * Format and sign an OAuth / API request\n   */\n  function oAuthRequest($url, $method, $parameters) {\n	if (strrpos($url, \'https://\') !== 0 && strrpos($url, \'http://\') !== 0) {\n	  $url = \"{$this->host}{$url}.{$this->format}\";\n	}\n	$request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);\n	$request->sign_request($this->sha1_method, $this->consumer, $this->token);\n	switch ($method) {\n	case \'GET\':\n	  return $this->http($request->to_url(), \'GET\');\n	default:\n	  return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata());\n	}\n  }\n\n  /**\n   * Make an HTTP request\n   *\n   * @return API results\n   */\n  function http($url, $method, $postfields = NULL) {\n	$this->http_info = array();\n	$ci = curl_init();\n	/* Curl settings */\n	curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);\n	curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);\n	curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);\n	curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);\n	curl_setopt($ci, CURLOPT_HTTPHEADER, array(\'Expect:\'));\n	curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);\n	curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, \'getHeader\'));\n	curl_setopt($ci, CURLOPT_HEADER, FALSE);\n\n	switch ($method) {\n	  case \'POST\':\n		curl_setopt($ci, CURLOPT_POST, TRUE);\n		if (!empty($postfields)) {\n		  curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);\n		}\n		break;\n	  case \'DELETE\':\n		curl_setopt($ci, CURLOPT_CUSTOMREQUEST, \'DELETE\');\n		if (!empty($postfields)) {\n		  $url = \"{$url}?{$postfields}\";\n		}\n	}\n\n	curl_setopt($ci, CURLOPT_URL, $url);\n	$response = curl_exec($ci);\n	$this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);\n	$this->http_info = array_merge($this->http_info, curl_getinfo($ci));\n	$this->url = $url;\n	curl_close ($ci);\n	return $response;\n  }\n\n  /**\n   * Get the header info to store.\n   */\n  function getHeader($ch, $header) {\n	$i = strpos($header, \':\');\n	if (!empty($i)) {\n	  $key = str_replace(\'-\', \'_\', strtolower(substr($header, 0, $i)));\n	  $value = trim(substr($header, $i + 2));\n	  $this->http_header[$key] = $value;\n	}\n	return strlen($header);\n  }\n}\n/* Generic exception class\n */\nif (!class_exists(\'OAuthException\')) {\n  class OAuthException extends Exception {\n	// pass\n  }\n}\n\nclass OAuthConsumer {\n  public $key;\n  public $secret;\n\n  function __construct($key, $secret, $callback_url=NULL) {\n	$this->key = $key;\n	$this->secret = $secret;\n	$this->callback_url = $callback_url;\n  }\n\n  function __toString() {\n	return \"OAuthConsumer[key=$this->key,secret=$this->secret]\";\n  }\n}\n\nclass OAuthToken {\n  // access tokens and request tokens\n  public $key;\n  public $secret;\n\n  /**\n   * key = the token\n   * secret = the token secret\n   */\n  function __construct($key, $secret) {\n	$this->key = $key;\n	$this->secret = $secret;\n  }\n\n  /**\n   * generates the basic string serialization of a token that a server\n   * would respond to request_token and access_token calls with\n   */\n  function to_string() {\n	return \"oauth_token=\" .\n		   OAuthUtil::urlencode_rfc3986($this->key) .\n		   \"&oauth_token_secret=\" .\n		   OAuthUtil::urlencode_rfc3986($this->secret);\n  }\n\n  function __toString() {\n	return $this->to_string();\n  }\n}\n\n/**\n * A class for implementing a Signature Method\n * See section 9 (\"Signing Requests\") in the spec\n */\nabstract class OAuthSignatureMethod {\n  /**\n   * Needs to return the name of the Signature Method (ie HMAC-SHA1)\n   * @return string\n   */\n  abstract public function get_name();\n\n  /**\n   * Build up the signature\n   * NOTE: The output of this function MUST NOT be urlencoded.\n   * the encoding is handled in OAuthRequest when the final\n   * request is serialized\n   * @param OAuthRequest $request\n   * @param OAuthConsumer $consumer\n   * @param OAuthToken $token\n   * @return string\n   */\n  abstract public function build_signature($request, $consumer, $token);\n\n  /**\n   * Verifies that a given signature is correct\n   * @param OAuthRequest $request\n   * @param OAuthConsumer $consumer\n   * @param OAuthToken $token\n   * @param string $signature\n   * @return bool\n   */\n  public function check_signature($request, $consumer, $token, $signature) {\n	$built = $this->build_signature($request, $consumer, $token);\n	return $built == $signature;\n  }\n}\n\n/**\n * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]\n * where the Signature Base String is the text and the key is the concatenated values (each first\n * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an \'&\'\n * character (ASCII code 38) even if empty.\n *   - Chapter 9.2 (\"HMAC-SHA1\")\n */\nclass OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {\n  function get_name() {\n	return \"HMAC-SHA1\";\n  }\n\n  public function build_signature($request, $consumer, $token) {\n	$base_string = $request->get_signature_base_string();\n	$request->base_string = $base_string;\n\n	$key_parts = array(\n	  $consumer->secret,\n	  ($token) ? $token->secret : \"\"\n	);\n\n	$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);\n	$key = implode(\'&\', $key_parts);\n\n	return base64_encode(hash_hmac(\'sha1\', $base_string, $key, true));\n  }\n}\n\n/**\n * The PLAINTEXT method does not provide any security protection and SHOULD only be used\n * over a secure channel such as HTTPS. It does not use the Signature Base String.\n *   - Chapter 9.4 (\"PLAINTEXT\")\n */\nclass OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {\n  public function get_name() {\n	return \"PLAINTEXT\";\n  }\n\n  /**\n   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and\n   * Token Secret, separated by a \'&\' character (ASCII code 38), even if either secret is\n   * empty. The result MUST be encoded again.\n   *   - Chapter 9.4.1 (\"Generating Signatures\")\n   *\n   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as\n   * OAuthRequest handles this!\n   */\n  public function build_signature($request, $consumer, $token) {\n	$key_parts = array(\n	  $consumer->secret,\n	  ($token) ? $token->secret : \"\"\n	);\n\n	$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);\n	$key = implode(\'&\', $key_parts);\n	$request->base_string = $key;\n\n	return $key;\n  }\n}\n\n/**\n * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in\n * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for\n * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a\n * verified way to the Service Provider, in a manner which is beyond the scope of this\n * specification.\n *   - Chapter 9.3 (\"RSA-SHA1\")\n */\nabstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {\n  public function get_name() {\n	return \"RSA-SHA1\";\n  }\n\n  // Up to the SP to implement this lookup of keys. Possible ideas are:\n  // (1) do a lookup in a table of trusted certs keyed off of consumer\n  // (2) fetch via http using a url provided by the requester\n  // (3) some sort of specific discovery code based on request\n  //\n  // Either way should return a string representation of the certificate\n  protected abstract function fetch_public_cert(&$request);\n\n  // Up to the SP to implement this lookup of keys. Possible ideas are:\n  // (1) do a lookup in a table of trusted certs keyed off of consumer\n  //\n  // Either way should return a string representation of the certificate\n  protected abstract function fetch_private_cert(&$request);\n\n  public function build_signature($request, $consumer, $token) {\n	$base_string = $request->get_signature_base_string();\n	$request->base_string = $base_string;\n\n	// Fetch the private key cert based on the request\n	$cert = $this->fetch_private_cert($request);\n\n	// Pull the private key ID from the certificate\n	$privatekeyid = openssl_get_privatekey($cert);\n\n	// Sign using the key\n	$ok = openssl_sign($base_string, $signature, $privatekeyid);\n\n	// Release the key resource\n	openssl_free_key($privatekeyid);\n\n	return base64_encode($signature);\n  }\n\n  public function check_signature($request, $consumer, $token, $signature) {\n	$decoded_sig = base64_decode($signature);\n\n	$base_string = $request->get_signature_base_string();\n\n	// Fetch the public key cert based on the request\n	$cert = $this->fetch_public_cert($request);\n\n	// Pull the public key ID from the certificate\n	$publickeyid = openssl_get_publickey($cert);\n\n	// Check the computed signature against the one passed in the query\n	$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);\n\n	// Release the key resource\n	openssl_free_key($publickeyid);\n\n	return $ok == 1;\n  }\n}\n\nclass OAuthRequest {\n  private $parameters;\n  private $http_method;\n  private $http_url;\n  // for debug purposes\n  public $base_string;\n  public static $version = \'1.0\';\n  public static $POST_INPUT = \'php://input\';\n\n  function __construct($http_method, $http_url, $parameters=NULL) {\n	@$parameters or $parameters = array();\n	$parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);\n	$this->parameters = $parameters;\n	$this->http_method = $http_method;\n	$this->http_url = $http_url;\n  }\n\n\n  /**\n   * attempt to build up a request from what was passed to the server\n   */\n  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {\n	$scheme = (!isset($_SERVER[\'HTTPS\']) || $_SERVER[\'HTTPS\'] != \"on\")\n			  ? \'http\'\n			  : \'https\';\n	@$http_url or $http_url = $scheme .\n							  \'://\' . $_SERVER[\'HTTP_HOST\'] .\n							  \':\' .\n							  $_SERVER[\'SERVER_PORT\'] .\n							  $_SERVER[\'REQUEST_URI\'];\n	@$http_method or $http_method = $_SERVER[\'REQUEST_METHOD\'];\n\n	// We weren\'t handed any parameters, so let\'s find the ones relevant to\n	// this request.\n	// If you run XML-RPC or similar you should use this to provide your own\n	// parsed parameter-list\n	if (!$parameters) {\n	  // Find request headers\n	  $request_headers = OAuthUtil::get_headers();\n\n	  // Parse the query-string to find GET parameters\n	  $parameters = OAuthUtil::parse_parameters($_SERVER[\'QUERY_STRING\']);\n\n	  // It\'s a POST request of the proper content-type, so parse POST\n	  // parameters and add those overriding any duplicates from GET\n	  if ($http_method == \"POST\"\n		  && @strstr($request_headers[\"Content-Type\"],\n					 \"application/x-www-form-urlencoded\")\n		  ) {\n		$post_data = OAuthUtil::parse_parameters(\n		  file_get_contents(self::$POST_INPUT)\n		);\n		$parameters = array_merge($parameters, $post_data);\n	  }\n\n	  // We have a Authorization-header with OAuth data. Parse the header\n	  // and add those overriding any duplicates from GET or POST\n	  if (@substr($request_headers[\'Authorization\'], 0, 6) == \"OAuth \") {\n		$header_parameters = OAuthUtil::split_header(\n		  $request_headers[\'Authorization\']\n		);\n		$parameters = array_merge($parameters, $header_parameters);\n	  }\n\n	}\n\n	return new OAuthRequest($http_method, $http_url, $parameters);\n  }\n\n  /**\n   * pretty much a helper function to set up the request\n   */\n  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {\n	@$parameters or $parameters = array();\n	$defaults = array(\"oauth_version\" => OAuthRequest::$version,\n					  \"oauth_nonce\" => OAuthRequest::generate_nonce(),\n					  \"oauth_timestamp\" => OAuthRequest::generate_timestamp(),\n					  \"oauth_consumer_key\" => $consumer->key);\n	if ($token)\n	  $defaults[\'oauth_token\'] = $token->key;\n\n	$parameters = array_merge($defaults, $parameters);\n\n	return new OAuthRequest($http_method, $http_url, $parameters);\n  }\n\n  public function set_parameter($name, $value, $allow_duplicates = true) {\n	if ($allow_duplicates && isset($this->parameters[$name])) {\n	  // We have already added parameter(s) with this name, so add to the list\n	  if (is_scalar($this->parameters[$name])) {\n		// This is the first duplicate, so transform scalar (string)\n		// into an array so we can add the duplicates\n		$this->parameters[$name] = array($this->parameters[$name]);\n	  }\n\n	  $this->parameters[$name][] = $value;\n	} else {\n	  $this->parameters[$name] = $value;\n	}\n  }\n\n  public function get_parameter($name) {\n	return isset($this->parameters[$name]) ? $this->parameters[$name] : null;\n  }\n\n  public function get_parameters() {\n	return $this->parameters;\n  }\n\n  public function unset_parameter($name) {\n	unset($this->parameters[$name]);\n  }\n\n  /**\n   * The request parameters, sorted and concatenated into a normalized string.\n   * @return string\n   */\n  public function get_signable_parameters() {\n	// Grab all parameters\n	$params = $this->parameters;\n\n	// Remove oauth_signature if present\n	// Ref: Spec: 9.1.1 (\"The oauth_signature parameter MUST be excluded.\")\n	if (isset($params[\'oauth_signature\'])) {\n	  unset($params[\'oauth_signature\']);\n	}\n\n	return OAuthUtil::build_http_query($params);\n  }\n\n  /**\n   * Returns the base string of this request\n   *\n   * The base string defined as the method, the url\n   * and the parameters (normalized), each urlencoded\n   * and the concated with &.\n   */\n  public function get_signature_base_string() {\n	$parts = array(\n	  $this->get_normalized_http_method(),\n	  $this->get_normalized_http_url(),\n	  $this->get_signable_parameters()\n	);\n\n	$parts = OAuthUtil::urlencode_rfc3986($parts);\n\n	return implode(\'&\', $parts);\n  }\n\n  /**\n   * just uppercases the http method\n   */\n  public function get_normalized_http_method() {\n	return strtoupper($this->http_method);\n  }\n\n  /**\n   * parses the url and rebuilds it to be\n   * scheme://host/path\n   */\n  public function get_normalized_http_url() {\n	$parts = parse_url($this->http_url);\n\n	$port = @$parts[\'port\'];\n	$scheme = $parts[\'scheme\'];\n	$host = $parts[\'host\'];\n	$path = @$parts[\'path\'];\n\n	$port or $port = ($scheme == \'https\') ? \'443\' : \'80\';\n\n	if (($scheme == \'https\' && $port != \'443\')\n		|| ($scheme == \'http\' && $port != \'80\')) {\n	  $host = \"$host:$port\";\n	}\n	return \"$scheme://$host$path\";\n  }\n\n  /**\n   * builds a url usable for a GET request\n   */\n  public function to_url() {\n	$post_data = $this->to_postdata();\n	$out = $this->get_normalized_http_url();\n	if ($post_data) {\n	  $out .= \'?\'.$post_data;\n	}\n	return $out;\n  }\n\n  /**\n   * builds the data one would send in a POST request\n   */\n  public function to_postdata() {\n	return OAuthUtil::build_http_query($this->parameters);\n  }\n\n  /**\n   * builds the Authorization: header\n   */\n  public function to_header($realm=null) {\n	$first = true;\n  if($realm) {\n	  $out = \'Authorization: OAuth realm=\"\' . OAuthUtil::urlencode_rfc3986($realm) . \'\"\';\n	  $first = false;\n	} else\n	  $out = \'Authorization: OAuth\';\n\n	$total = array();\n	foreach ($this->parameters as $k => $v) {\n	  if (substr($k, 0, 5) != \"oauth\") continue;\n	  if (is_array($v)) {\n		throw new OAuthException(\'Arrays not supported in headers\');\n	  }\n	  $out .= ($first) ? \' \' : \',\';\n	  $out .= OAuthUtil::urlencode_rfc3986($k) .\n			  \'=\"\' .\n			  OAuthUtil::urlencode_rfc3986($v) .\n			  \'\"\';\n	  $first = false;\n	}\n	return $out;\n  }\n\n  public function __toString() {\n	return $this->to_url();\n  }\n\n\n  public function sign_request($signature_method, $consumer, $token) {\n	$this->set_parameter(\n	  \"oauth_signature_method\",\n	  $signature_method->get_name(),\n	  false\n	);\n	$signature = $this->build_signature($signature_method, $consumer, $token);\n	$this->set_parameter(\"oauth_signature\", $signature, false);\n  }\n\n  public function build_signature($signature_method, $consumer, $token) {\n	$signature = $signature_method->build_signature($this, $consumer, $token);\n	return $signature;\n  }\n\n  /**\n   * util function: current timestamp\n   */\n  private static function generate_timestamp() {\n	return time();\n  }\n\n  /**\n   * util function: current nonce\n   */\n  private static function generate_nonce() {\n	$mt = microtime();\n	$rand = mt_rand();\n\n	return md5($mt . $rand); // md5s look nicer than numbers\n  }\n}\n\nclass OAuthServer {\n  protected $timestamp_threshold = 300; // in seconds, five minutes\n  protected $version = \'1.0\';             // hi blaine\n  protected $signature_methods = array();\n\n  protected $data_store;\n\n  function __construct($data_store) {\n	$this->data_store = $data_store;\n  }\n\n  public function add_signature_method($signature_method) {\n	$this->signature_methods[$signature_method->get_name()] =\n	  $signature_method;\n  }\n\n  // high level functions\n\n  /**\n   * process a request_token request\n   * returns the request token on success\n   */\n  public function fetch_request_token(&$request) {\n	$this->get_version($request);\n\n	$consumer = $this->get_consumer($request);\n\n	// no token required for the initial token request\n	$token = NULL;\n\n	$this->check_signature($request, $consumer, $token);\n\n	// Rev A change\n	$callback = $request->get_parameter(\'oauth_callback\');\n	$new_token = $this->data_store->new_request_token($consumer, $callback);\n\n	return $new_token;\n  }\n\n  /**\n   * process an access_token request\n   * returns the access token on success\n   */\n  public function fetch_access_token(&$request) {\n	$this->get_version($request);\n\n	$consumer = $this->get_consumer($request);\n\n	// requires authorized request token\n	$token = $this->get_token($request, $consumer, \"request\");\n\n	$this->check_signature($request, $consumer, $token);\n\n	// Rev A change\n	$verifier = $request->get_parameter(\'oauth_verifier\');\n	$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);\n\n	return $new_token;\n  }\n\n  /**\n   * verify an api call, checks all the parameters\n   */\n  public function verify_request(&$request) {\n	$this->get_version($request);\n	$consumer = $this->get_consumer($request);\n	$token = $this->get_token($request, $consumer, \"access\");\n	$this->check_signature($request, $consumer, $token);\n	return array($consumer, $token);\n  }\n\n  // Internals from here\n  /**\n   * version 1\n   */\n  private function get_version(&$request) {\n	$version = $request->get_parameter(\"oauth_version\");\n	if (!$version) {\n	  // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.\n	  // Chapter 7.0 (\"Accessing Protected Ressources\")\n	  $version = \'1.0\';\n	}\n	if ($version !== $this->version) {\n	  throw new OAuthException(\"OAuth version \'$version\' not supported\");\n	}\n	return $version;\n  }\n\n  /**\n   * figure out the signature with some defaults\n   */\n  private function get_signature_method(&$request) {\n	$signature_method =\n		@$request->get_parameter(\"oauth_signature_method\");\n\n	if (!$signature_method) {\n	  // According to chapter 7 (\"Accessing Protected Ressources\") the signature-method\n	  // parameter is required, and we can\'t just fallback to PLAINTEXT\n	  throw new OAuthException(\'No signature method parameter. This parameter is required\');\n	}\n\n	if (!in_array($signature_method,\n				  array_keys($this->signature_methods))) {\n	  throw new OAuthException(\n		\"Signature method \'$signature_method\' not supported \" .\n		\"try one of the following: \" .\n		implode(\", \", array_keys($this->signature_methods))\n	  );\n	}\n	return $this->signature_methods[$signature_method];\n  }\n\n  /**\n   * try to find the consumer for the provided request\'s consumer key\n   */\n  private function get_consumer(&$request) {\n	$consumer_key = @$request->get_parameter(\"oauth_consumer_key\");\n	if (!$consumer_key) {\n	  throw new OAuthException(\"Invalid consumer key\");\n	}\n\n	$consumer = $this->data_store->lookup_consumer($consumer_key);\n	if (!$consumer) {\n	  throw new OAuthException(\"Invalid consumer\");\n	}\n\n	return $consumer;\n  }\n\n  /**\n   * try to find the token for the provided request\'s token key\n   */\n  private function get_token(&$request, $consumer, $token_type=\"access\") {\n	$token_field = @$request->get_parameter(\'oauth_token\');\n	$token = $this->data_store->lookup_token(\n	  $consumer, $token_type, $token_field\n	);\n	if (!$token) {\n	  throw new OAuthException(\"Invalid $token_type token: $token_field\");\n	}\n	return $token;\n  }\n\n  /**\n   * all-in-one function to check the signature on a request\n   * should guess the signature method appropriately\n   */\n  private function check_signature(&$request, $consumer, $token) {\n	// this should probably be in a different method\n	$timestamp = @$request->get_parameter(\'oauth_timestamp\');\n	$nonce = @$request->get_parameter(\'oauth_nonce\');\n\n	$this->check_timestamp($timestamp);\n	$this->check_nonce($consumer, $token, $nonce, $timestamp);\n\n	$signature_method = $this->get_signature_method($request);\n\n	$signature = $request->get_parameter(\'oauth_signature\');\n	$valid_sig = $signature_method->check_signature(\n	  $request,\n	  $consumer,\n	  $token,\n	  $signature\n	);\n\n	if (!$valid_sig) {\n	  throw new OAuthException(\"Invalid signature\");\n	}\n  }\n\n  /**\n   * check that the timestamp is new enough\n   */\n  private function check_timestamp($timestamp) {\n	if( ! $timestamp )\n	  throw new OAuthException(\n		\'Missing timestamp parameter. The parameter is required\'\n	  );\n\n	// verify that timestamp is recentish\n	$now = time();\n	if (abs($now - $timestamp) > $this->timestamp_threshold) {\n	  throw new OAuthException(\n		\"Expired timestamp, yours $timestamp, ours $now\"\n	  );\n	}\n  }\n\n  /**\n   * check that the nonce is not repeated\n   */\n  private function check_nonce($consumer, $token, $nonce, $timestamp) {\n	if( ! $nonce )\n	  throw new OAuthException(\n		\'Missing nonce parameter. The parameter is required\'\n	  );\n\n	// verify that the nonce is uniqueish\n	$found = $this->data_store->lookup_nonce(\n	  $consumer,\n	  $token,\n	  $nonce,\n	  $timestamp\n	);\n	if ($found) {\n	  throw new OAuthException(\"Nonce already used: $nonce\");\n	}\n  }\n\n}\n\nclass OAuthDataStore {\n  function lookup_consumer($consumer_key) {\n	// implement me\n  }\n\n  function lookup_token($consumer, $token_type, $token) {\n	// implement me\n  }\n\n  function lookup_nonce($consumer, $token, $nonce, $timestamp) {\n	// implement me\n  }\n\n  function new_request_token($consumer, $callback = null) {\n	// return a new token attached to this consumer\n  }\n\n  function new_access_token($token, $consumer, $verifier = null) {\n	// return a new access token attached to this consumer\n	// for the user associated with this token if the request token\n	// is authorized\n	// should also invalidate the request token\n  }\n\n}\n\nclass OAuthUtil {\n  public static function urlencode_rfc3986($input) {\n  if (is_array($input)) {\n	return array_map(array(\'OAuthUtil\', \'urlencode_rfc3986\'), $input);\n  } else if (is_scalar($input)) {\n	return str_replace(\n	  \'+\',\n	  \' \',\n	  str_replace(\'%7E\', \'~\', rawurlencode($input))\n	);\n  } else {\n	return \'\';\n  }\n}\n\n\n  // This decode function isn\'t taking into consideration the above\n  // modifications to the encoding process. However, this method doesn\'t\n  // seem to be used anywhere so leaving it as is.\n  public static function urldecode_rfc3986($string) {\n	return urldecode($string);\n  }\n\n  // Utility function for turning the Authorization: header into\n  // parameters, has to do some unescaping\n  // Can filter out any non-oauth parameters if needed (default behaviour)\n  public static function split_header($header, $only_allow_oauth_parameters = true) {\n	$pattern = \'/(([-_a-z]*)=(\"([^\"]*)\"|([^,]*)),?)/\';\n	$offset = 0;\n	$params = array();\n	while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {\n	  $match = $matches[0];\n	  $header_name = $matches[2][0];\n	  $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];\n	  if (preg_match(\'/^oauth_/\', $header_name) || !$only_allow_oauth_parameters) {\n		$params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);\n	  }\n	  $offset = $match[1] + strlen($match[0]);\n	}\n\n	if (isset($params[\'realm\'])) {\n	  unset($params[\'realm\']);\n	}\n\n	return $params;\n  }\n\n  // helper to try to sort out headers for people who aren\'t running apache\n  public static function get_headers() {\n	if (function_exists(\'apache_request_headers\')) {\n	  // we need this to get the actual Authorization: header\n	  // because apache tends to tell us it doesn\'t exist\n	  $headers = apache_request_headers();\n\n	  // sanitize the output of apache_request_headers because\n	  // we always want the keys to be Cased-Like-This and arh()\n	  // returns the headers in the same case as they are in the\n	  // request\n	  $out = array();\n	  foreach( $headers AS $key => $value ) {\n		$key = str_replace(\n			\" \",\n			\"-\",\n			ucwords(strtolower(str_replace(\"-\", \" \", $key)))\n		  );\n		$out[$key] = $value;\n	  }\n	} else {\n	  // otherwise we don\'t have apache and are just going to have to hope\n	  // that $_SERVER actually contains what we need\n	  $out = array();\n	  if( isset($_SERVER[\'CONTENT_TYPE\']) )\n		$out[\'Content-Type\'] = $_SERVER[\'CONTENT_TYPE\'];\n	  if( isset($_ENV[\'CONTENT_TYPE\']) )\n		$out[\'Content-Type\'] = $_ENV[\'CONTENT_TYPE\'];\n\n	  foreach ($_SERVER as $key => $value) {\n		if (substr($key, 0, 5) == \"HTTP_\") {\n		  // this is chaos, basically it is just there to capitalize the first\n		  // letter of every word that is not an initial HTTP and strip HTTP\n		  // code from przemek\n		  $key = str_replace(\n			\" \",\n			\"-\",\n			ucwords(strtolower(str_replace(\"_\", \" \", substr($key, 5))))\n		  );\n		  $out[$key] = $value;\n		}\n	  }\n	}\n	return $out;\n  }\n\n  // This function takes a input like a=b&a=c&d=e and returns the parsed\n  // parameters like this\n  // array(\'a\' => array(\'b\',\'c\'), \'d\' => \'e\')\n  public static function parse_parameters( $input ) {\n	if (!isset($input) || !$input) return array();\n\n	$pairs = explode(\'&\', $input);\n\n	$parsed_parameters = array();\n	foreach ($pairs as $pair) {\n	  $split = explode(\'=\', $pair, 2);\n	  $parameter = OAuthUtil::urldecode_rfc3986($split[0]);\n	  $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : \'\';\n\n	  if (isset($parsed_parameters[$parameter])) {\n		// We have already recieved parameter(s) with this name, so add to the list\n		// of parameters with this name\n\n		if (is_scalar($parsed_parameters[$parameter])) {\n		  // This is the first duplicate, so transform scalar (string) into an array\n		  // so we can add the duplicates\n		  $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);\n		}\n\n		$parsed_parameters[$parameter][] = $value;\n	  } else {\n		$parsed_parameters[$parameter] = $value;\n	  }\n	}\n	return $parsed_parameters;\n  }\n\n  public static function build_http_query($params) {\n	if (!$params) return \'\';\n\n	// Urlencode both keys and values\n	$keys = OAuthUtil::urlencode_rfc3986(array_keys($params));\n	$values = OAuthUtil::urlencode_rfc3986(array_values($params));\n	$params = array_combine($keys, $values);\n\n	// Parameters are sorted by name, using lexicographical byte value ordering.\n	// Ref: Spec: 9.1.1 (1)\n	uksort($params, \'strcmp\');\n\n	$pairs = array();\n	foreach ($params as $parameter => $value) {\n	  if (is_array($value)) {\n		// If two or more parameters share the same name, they are sorted by their value\n		// Ref: Spec: 9.1.1 (1)\n		natsort($value);\n		foreach ($value as $duplicate_value) {\n		  $pairs[] = $parameter . \'=\' . $duplicate_value;\n		}\n	  } else {\n		$pairs[] = $parameter . \'=\' . $value;\n	  }\n	}\n	// For each parameter, the name is separated from the corresponding value by an \'=\' character (ASCII code 61)\n	// Each name-value pair is separated by an \'&\' character (ASCII code 38)\n	return implode(\'&\', $pairs);\n  }\n}', 'global $prefs,$txpcfg,$arc_twitter_consumerKey,$arc_twitter_consumerSecret;\n\n$arc_twitter = array();\n\n// Register tags.\nTxp::get(\'\\Textpattern\\Tag\\Registry\')\n    ->register(\'arc_twitter\')\n    ->register(\'arc_twitter_search\')\n    ->register(\'arc_twitter_tweet_button\')\n    ->register(\'arc_twitter_follow_button\')\n    ->register(\'arc_twitter_tweet_url\')\n    ->register(\'arc_twitter_tinyurl\')\n    ->register(\'arc_twitter_widget_js\');\n\n$arc_twitter_consumerKey = \'nKcXslwzZhBd0kfKMetnPA\';\n$arc_twitter_consumerSecret = \'C6nSPCL3eeHGTBhKCgwd9oclcuD0srB8WVkfXQYC54\';\n\nadd_privs(\'plugin_prefs.arc_twitter\',\'1,2\');\nregister_callback(\'arc_twitter_install\',\'plugin_lifecycle.arc_twitter\', \'installed\');\nregister_callback(\'arc_twitter_uninstall\',\'plugin_lifecycle.arc_twitter\', \'deleted\');\nregister_callback(\'arc_twitter_prefs\',\'plugin_prefs.arc_twitter\');\n\n/*\n * Setup initial preferences if not in the txp_prefs table.\n */\nif (!isset($prefs[\'arc_twitter_user\'])) {\n	set_pref(\'arc_twitter_user\', \'\', \'arc_twitter\', 1, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_prefix\'])) {\n	set_pref(\'arc_twitter_prefix\',\'Just posted:\', \'arc_twitter\', 2, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_suffix\'])) {\n	set_pref(\'arc_twitter_suffix\',\'\', \'arc_twitter\', 2, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_cache_dir\'])) {\n	set_pref(\'arc_twitter_cache_dir\',$txpcfg[\'txpath\'].$prefs[\'tempdir\'], \'arc_twitter\', 1, \'text_input\');\n}\nif (!isset($prefs[\'arc_twitter_tweet_default\'])) {\n	set_pref(\'arc_twitter_tweet_default\', 1, \'arc_twitter\', 2, \'yesnoRadio\');\n}\nif (!isset($prefs[\'arc_twitter_url_method\'])) {\n	set_pref(\'arc_twitter_url_method\', \'full\', \'arc_twitter\', 2, \'arc_twitter_url_method_select\');\n}\nif (!isset($prefs[\'arc_short_url\'])) {\n	set_pref(\'arc_short_url\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\n}\nif (!isset($prefs[\'arc_short_site_url\'])) {\n	set_pref(\'arc_short_site_url\', $prefs[\'siteurl\'], \'arc_twitter\', 2, \'text_input\');\n}\n// Make sure that the Twitter tab has been defined\nif (!isset($prefs[\'arc_twitter_tab\'])) {\n  set_pref(\'arc_twitter_tab\', \'extensions\', \'arc_twitter\', 2,\n	\'arc_twitter_tab_select\');\n	$prefs[\'arc_twitter_tab\'] = \'extensions\';\n}\nif (!isset($prefs[\'arc_short_utm\'])) {\n	set_pref(\'arc_short_utm\', 0, \'arc_twitter\', 2, \'yesnoRadio\');\n}\n\n// Check if arc_short_url is enabled\nif ((isset($prefs[\'arc_short_url\'])&&$prefs[\'arc_short_url\'])\n|| (isset($prefs[\'arc_short_url_method\'])&&$prefs[\'arc_twitter_url_method\']==\'arc_twitter\')) {\n  register_callback(\'arc_short_url_redirect\', \'txp_die\', 404);\n}\n\nif (@txpinterface == \'admin\') {\n	register_callback(\'_arc_twitter_auto_enable\', \'plugin_lifecycle.arc_twitter\', \'installed\');\n	if (!empty($prefs[\'arc_twitter_user\'])\n		&& !empty($prefs[\'arc_twitter_accessToken\'])\n		&& !empty($prefs[\'arc_twitter_accessTokenSecret\']) ) {\n\n		if ($prefs[\'arc_twitter_tab\']) {\n			add_privs(\'arc_admin_twitter\', \'1,2,3,4\');\n			register_tab($prefs[\'arc_twitter_tab\'], \'arc_admin_twitter\', \'Twitter\');\n			register_callback(\'arc_admin_twitter\', \'arc_admin_twitter\');\n		}\n\n		register_callback(\'arc_article_tweet\', \'ping\');\n		register_callback(\'arc_article_tweet\', \'article_saved\');\n		register_callback(\'arc_article_tweet\', \'article_posted\');\n		register_callback(\'arc_append_twitter\', \'article_ui\', \'status\');\n	}\n}\n\n/*\n	Public-side functions\n	================================================================\n*/\n\nfunction arc_twitter($atts)\n{\n  global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n  extract(lAtts(array(\n	\'user\'      => $prefs[\'arc_twitter_user\'],\n	\'password\'  => \'\',\n	\'timeline\'  => \'user\',\n	\'limit\'     => \'10\',\n	\'fetch\'     => 0,\n	\'full_urls\' => false,\n	\'retweets\'  => false,\n	\'replies\'   => true,\n	\'dateformat\'=> $prefs[\'archive_dateformat\'],\n	\'caching\'   => \'1\',\n	\'cache_dir\' => $prefs[\'arc_twitter_cache_dir\'],\n	\'cache_time\'=> \'5\',\n	\'label\'     => \'\',\n	\'labeltag\'  => \'\',\n	\'break\'     => \'li\',\n	\'wraptag\'   => \'\',\n	\'class\'     => __FUNCTION__,\n	\'class_posted\' => __FUNCTION__.\'-posted\'\n	),$atts));\n\n  $twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n  if ($caching) {  // turn on caching, recommended (default)\n	$twit->setCaching(true);\n	$twit->cacheDir($cache_dir);\n	$twit->cacheTime($cache_time);\n  } else {  // turn off caching, not recommended other than for testing\n	$twit->setCaching(false);\n  }\n\n  switch ($timeline) {\n	case \'home\': case \'friends\':\n	  $timeline = \'home_timeline\'; break;\n	case \'mentions\':\n	  $timeline = \'statuses/mentions_timeline\'; break;\n	case \'user\': default: $timeline = \'statuses/user_timeline\';\n  }\n\n  // Check that the fetch (Twitter\'s count attribute) is set correctly\n  $fetch = (!$fetch || $fetch<$limit) ? $limit : $fetch;\n\n  $out = array();\n  $tweets = $twit->get($timeline, array(\n	  \'screen_name\'=>$user,\n	  \'count\'=>$fetch,\n	  \'include_rts\'=>$retweets,\n	  \'exclude_replies\'=>!$replies\n	));\n\n  if ($tweets) {\n	// Apply the display limit to the returned tweets\n	$tweets = array_slice($tweets, 0, $limit);\n	foreach ($tweets as $tweet) {\n	  $time = strtotime(htmlentities($tweet->created_at));\n	  $date = safe_strftime($dateformat,$time);\n	  $links = false;\n	  if (isset($tweet->entities->urls) || isset($tweet->entities->media)) {\n		$links = array();\n	  }\n	  if (isset($tweet->entities->urls)) {\n		foreach ($tweet->entities->urls as $url) {\n			$links[$url->url] = array(\n				\'expanded_url\' => $url->expanded_url,\n				\'display_url\' => $url->display_url\n			);\n		}\n	  }\n	  if (isset($tweet->entities->media)) {\n		foreach ($tweet->entities->media as $url) {\n			$links[$url->url] = array(\n				\'expanded_url\' => $url->expanded_url,\n				\'display_url\' => $url->display_url\n			);\n		}\n	  }\n	  $out[] = arc_Twitter::makeLinks(htmlentities($tweet->text, ENT_QUOTES,\'UTF-8\'), $links, $full_urls)\n		.\' \'.tag(htmlentities($date),\'span\',\' class=\"\'.$class_posted.\'\"\');\n	}\n  }\n\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n}\n\nfunction arc_twitter_search($atts)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	extract(lAtts(array(\n		\'user\'      => $prefs[\'arc_twitter_user\'],\n		\'search\'    => \'\',\n		\'hashtags\'  => \'\',\n		\'user\'      => \'\',\n		\'reply\'     => \'\',\n		\'mention\'   => \'\',\n		\'limit\'     => \'10\',\n		\'full_urls\' => false,\n		\'lang\'      => \'\',\n		\'dateformat\'=> $prefs[\'archive_dateformat\'],\n		\'caching\'   => \'1\',\n		\'cache_dir\' => $prefs[\'arc_twitter_cache_dir\'],\n		\'cache_time\'=> \'5\',\n		\'label\'     => \'\',\n		\'labeltag\'  => \'\',\n		\'break\'     => \'li\',\n		\'wraptag\'   => \'\',\n		\'class\'     => __FUNCTION__,\n		\'class_posted\' => __FUNCTION__.\'-posted\',\n		\'class_user\'   => __FUNCTION__.\'-user\'\n	),$atts));\n\n		$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n		if ($caching) {  // turn on caching, recommended (default)\n			$twit->setCaching(true);\n			$twit->cacheDir($cache_dir);\n			$twit->cacheTime($cache_time);\n		} else {  // turn off caching, not recommended other than for testing\n			$twit->setCaching(false);\n		}\n\n		// construct search query\n		if (!empty($search)) {\n			$terms = explode(\',\',$search); $terms = array_map(\'trim\',$terms);\n			$search = implode(\' \',$terms);\n		}\n		if ($hashtags) {\n			$hashes = explode(\',\',$hashtags); $hashes = array_map(\'trim\',$hashes);\n			$search.= (($search)?\' \':\'\').\'#\'.implode(\' #\',$hashes);\n		}\n		if ($reply) {\n			$search.= (($search)?\' \':\'\').\'to:\'.trim($reply);\n		}\n		if ($user) {\n			$search.= (($search)?\' \':\'\').\'from:\'.trim($user);\n		}\n		if ($mention) {\n			$search.= (($search)?\' \':\'\').\'@\'.trim($mention);\n		}\n\n		if (empty($search)) {\n			return \'\';\n		} else {\n			$search = urlencode($search);\n		}\n\n		$out = array();\n		$results = $twit->get(\'search/tweets\'\n			, array(\'q\'=>$search,\'count\'=>$limit,\'lang\'=>$lang));\n\n		$tweets = $results->statuses;\n		if ($tweets) {\n\n			foreach ($tweets as $tweet) {\n\n				$uname = $tweet->user->screen_name;\n				$name = $tweet->user->name;\n				$time = strtotime(htmlentities($tweet->created_at));\n				$date = safe_strftime($dateformat,$time);\n				$text = $tweet->text;\n\n				$links = false;\n				if (isset($tweet->entities->urls)) {\n					$links = array();\n					foreach ($tweet->entities->urls as $url) {\n						$links[$url->url] = array(\n							\'expanded_url\' => $url->expanded_url,\n							\'display_url\' => $url->display_url\n						);\n					}\n				}\n\n				$out[] = tag(href(htmlentities($uname),\'http://twitter.com/\' . $tweet->user->screen_name,\n					\' title=\"\'.htmlentities($name).\'\"\').\': \',\'span\'\n						,\' class=\"\'.$class_user.\'\"\')\n					.arc_Twitter::makeLinks(htmlentities($text, ENT_QUOTES,\'UTF-8\'), $links, $full_urls)\n					.\' \'.tag(htmlentities($date),\'span\'\n						,\' class=\"\'.$class_posted.\'\"\');\n\n			}\n\n			return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n		}\n\n}\n\nfunction arc_twitter_tweet($atts) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n	  \'id\'        => $thisarticle[\'thisid\'],\n	  \'include_url\'   => true\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = ($include_url) ? safe_row(\"tweet\", \'arc_twitter\', \"article_id={$id}\")\n		: safe_row(\"REPLACE(tweet,CONCAT(\' \',tinyurl),\'\') AS tweet\"\n		  , \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tweet\']) {\n	  return arc_Twitter::makeLinks(\n		htmlentities($tweet[\'tweet\'], ENT_QUOTES,\'UTF-8\'));\n	}\n}\n\nfunction arc_twitter_tweet_url($atts, $thing=null) {\n	global $thisarticle,$prefs;\n\n	extract(lAtts(array(\n	  \'id\'      => $thisarticle[\'thisid\'],\n	  \'title\'   => \'\',\n	  \'class\'   => \'\'\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = safe_row(\"tweet_id\"\n		, \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tweet_id\']) {\n	  $url = \"http://twitter.com/\".$prefs[\'arc_twitter_user\'].\"/status/\".$tweet[\'tweet_id\'];\n	  if ($thing===null) {\n		return $url;\n	  }\n	  return href(parse($thing), $url,\n		($title ? \' title=\"\'.$title.\'\"\' : \'\')\n		.($class ? \' class=\"\'.$class.\'\"\' : \'\'));\n	}\n}\n\nfunction arc_twitter_tinyurl($atts, $thing=null) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n	  \'id\'      => $thisarticle[\'thisid\'],\n	  \'title\'   => \'\',\n	  \'class\'   => \'\'\n	),$atts));\n\n	if ($id) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $tweet = safe_row(\"tinyurl\"\n		, \'arc_twitter\', \"article_id={$id}\");\n	}\n\n	if ($tweet[\'tinyurl\']) {\n	  if ($thing===null) {\n		return $tweet[\'tinyurl\'];\n	  }\n\n	  return href(parse($thing), $tweet[\'tinyurl\'],\n		($title ? \' title=\"\'.$title.\'\"\' : \'\')\n		.($class ? \' class=\"\'.$class.\'\"\' : \'\'));\n	}\n}\n\n/*\n * Public tag for outputting widget JS\n */\nfunction arc_twitter_widget_js($atts)\n{\n  extract(lAtts(array(\n		\'optimise\' => false\n	),$atts));\n\n  return _arc_twitter_widget_js($optimise);\n}\n\nfunction _arc_twitter_widget_js($optimise=true)\n{\n  global $arc_twitter;\n\n  // Check if widget JS has already been output\n  if ($arc_twitter[\'widget_js\']) return;\n\n  if ($optimise==false) {\n	return \'<script src=\"http://platform.twitter.com/widgets.js\" type=\"text/javascript\"></script>\';\n  }\n\n  $js = <<<JS\n<script type=\"text/javascript\">\n(function() {\n  if (window.__twitterIntentHandler) return;\n  var intentRegex = /twitter\\.com(\\:\\d{2,4})?\\/intent\\/(\\w+)/,\n	  windowOptions = \'scrollbars=yes,resizable=yes,toolbar=no,location=yes\',\n	  width = 550,\n	  height = 420,\n	  winHeight = screen.height,\n	  winWidth = screen.width;\n\n  function handleIntent(e) {\n	e = e || window.event;\n	var target = e.target || e.srcElement,\n		m, left, top;\n\n	while (target && target.nodeName.toLowerCase() !== \'a\') {\n	  target = target.parentNode;\n	}\n\n	if (target && target.nodeName.toLowerCase() === \'a\' && target.href) {\n	  m = target.href.match(intentRegex);\n	  if (m) {\n		left = Math.round((winWidth / 2) - (width / 2));\n		top = 0;\n\n		if (winHeight > height) {\n		  top = Math.round((winHeight / 2) - (height / 2));\n		}\n\n		window.open(target.href, \'intent\', windowOptions + \',width=\' + width +\n										   \',height=\' + height + \',left=\' + left + \',top=\' + top);\n		e.returnValue = false;\n		e.preventDefault && e.preventDefault();\n	  }\n	}\n  }\n\n  if (document.addEventListener) {\n	document.addEventListener(\'click\', handleIntent, false);\n  } else if (document.attachEvent) {\n	document.attachEvent(\'onclick\', handleIntent);\n  }\n  window.__twitterIntentHandler = true;\n}());\n</script>\nJS;\n  $arc_twitter[\'widget_js\'] = true;\n  return $js;\n}\n\n// Deprecated arc_twitter_retweet tag, use arc_twitter_tweet_button instead\nfunction arc_twitter_retweet($atts, $thing=null)\n{\n  return arc_twitter_tweet_button($atts, $thing=null);\n}\nfunction arc_twitter_tweet_button($atts, $thing=null)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'user\'        => $prefs[\'arc_twitter_user\'], // via user account\n		\'url\'         => \'\',\n		\'text\'        => \'\',\n		\'follow1\'     => \'\',\n		\'follow2\'     => \'\',\n		\'lang\'        => \'\',\n		\'count\'       => \'horizontal\',\n		\'include_js\'  => true,\n		\'optimise_js\' => false,\n		\'wraptag\'     => \'\',\n		\'class\'       => \'twitter-share-button\'\n	),$atts));\n\n	$q = \'\'; // query string\n\n	if ($id=$thisarticle[\'thisid\']) {\n	  // Fetch arc_twitter stuff to build tweet from\n	  $row = safe_row(\"REPLACE(tweet,CONCAT(\' \',tinyurl),\'\') AS tweet,tinyurl\"\n		, \'arc_twitter\', \"article_id={$id}\");\n\n	  if ($url==\'\') {\n		$url = ($url) ? $url : permlinkurl($thisarticle);\n		$q = \'url=\'.urlencode($url);\n	  }\n	  if ($text==\'\') {\n		$text = ($row[\'tweet\']) ? $row[\'tweet\'] : $thisarticle[\'title\'];\n	  }\n	  $q .= ($q ? \'&amp;\' : \'\').\'text=\'.urlencode($text);\n	}\n	if ($user) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'via=\'.urlencode($user);\n	}\n	if ($follow1&&$follow2) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'related=\'.urlencode($follow1.\':\'.$follow2);\n	} elseif ($follow1||$follow2) {\n	  $q .= ($q ? \'&amp;\' : \'\').\'related=\'.urlencode($follow1.$follow2);\n	}\n\n	switch ($lang) {\n	  case \'fr\': break; case \'de\': break; case \'es\': break; case \'jp\': break;\n	  default:\n		$lang = \'en\';\n	}\n	$q .= ($q ? \'&amp;\' : \'\').\'lang=\'.urlencode($lang);\n\n	switch ($count) {\n	  case \'none\': break; case \'vertical\': break;\n	  default:\n		$count = \'horizontal\';\n	}\n	$q .= ($q ? \'&amp;\' : \'\').\'count=\'.urlencode($count);\n\n	$thing = ($thing===null) ? \'Tweet\' : parse($thing);\n\n	$html = href($thing,\'http://twitter.com/share?\'.$q\n	  , \' class=\"\'.$class.\'\"\');\n\n	$js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n	return $js.$html;\n}\n\n/*\n * Twitter Follow button\n */\nfunction arc_twitter_follow_button($atts, $thing=null)\n{\n	global $prefs,$arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'user\'        => $prefs[\'arc_twitter_user\'], // via user account\n		\'lang\'        => \'\',\n		\'count\'       => true,\n		\'include_js\'  => true,\n		\'optimise_js\' => false,\n		\'class\'       => \'twitter-follow-button\'\n	),$atts));\n\n	$atts = \'\'; // data attributes\n\n	switch ($lang) {\n	  case \'fr\': break; case \'de\': break; case \'es\': break; case \'jp\': break;\n	  default:\n		$lang = \'en\';\n	}\n	$atts .= \' data-lang=\"\'.urlencode($lang).\'\"\';\n\n	$atts .= \' data-show-count=\"\'.($count?\'true\':\'false\').\'\"\';\n\n	$thing = ($thing===null) ? \'Follow @\'.$user : parse($thing);\n\n	$html = href($thing,\'http://twitter.com/\'.urlencode($user)\n	  , \' class=\"\'.$class.\'\"\'.$atts);\n\n	$js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n	return $js.$html;\n}\n\n/*\n	Admin-side functions\n	================================================================\n*/\n\n// Installation function - builds MySQL table\nfunction arc_twitter_install()\n{\n	global $DB;\n	// For first install, create table for tweets\n	$sql = \"CREATE TABLE IF NOT EXISTS \".PFX.\"arc_twitter \";\n	$sql.= \"(arc_twitterid INTEGER AUTO_INCREMENT PRIMARY KEY,\n		article_id INTEGER(11),\n		tweet_id VARCHAR(100),\n		tweet VARCHAR(280),\n		tinyurl VARCHAR(30));\";\n\n	if (!safe_query($sql)) {\n		return \'Error - unable to create arc_twitter table\';\n	}\n\n	// Check if we need to update an existing table to use the larger string length.\n	$sql = \'SELECT column_type FROM information_schema.columns WHERE column_name = \\\'tweet\\\' AND table_name = \\\'\' . safe_pfx(\'arc_twitter\') . \'\\\'\';\n	$sql .= \' AND table_schema = \\\'\' . $DB->db . \'\\\'\';\n	$result = nextRow(safe_query($sql));\n	if (! empty($result[\'column_type\']) && strpos($result[\'column_type\'], \'varchar(140)\') !== false) {\n		safe_alter(\'arc_twitter\', \'CHANGE tweet tweet VARCHAR(280)\');\n	}\n\n}\n\n// Uninstall function - deletes MySQL table and related preferences\nfunction arc_twitter_uninstall()\n{\n\n	$sql = \"DROP TABLE IF EXISTS \".PFX.\"arc_twitter;\";\n	if (!safe_query($sql)) {\n		return \'Error - unable to delete arc_twitter table\';\n	}\n\n	$sql = \"DELETE FROM  \".PFX.\"txp_prefs WHERE event=\'arc_twitter\';\";\n	if (!safe_query($sql)) {\n		return \'Error - unable to delete arc_twitter preferences\';\n	}\n\n}\nfunction arc_twitter_url_method_select($name, $val)\n{\n	$methods = array(\n		\'full\' => \'Do not shorten\',\n		\'tinyurl\' => \'Tinyurl\',\n		\'isgd\' => \'Is.gd\',\n		\'arc_twitter\' => \'TXP Tweet\',\n		\'smd\' => \'smd_short_url\'\n	);\n	return selectInput($name, $methods, $val, \'\', \'\', $name);\n}\nfunction arc_twitter_tab_select($name, $val)\n{\n	$tabs = array(\'content\' => \'Content\',\n		\'extensions\' => \'Extensions\',\n		\'\' => \'Hidden\');\n	return selectInput($name, $tabs, $val, \'\', \'\', $name);\n}\n// Provide interface for setting preferences\nfunction arc_twitter_prefs($event,$step)\n{\n\n	global $prefs, $arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	$user = $prefs[\'arc_twitter_user\'];\n	$prefix = $prefs[\'arc_twitter_prefix\'];\n	$suffix = $prefs[\'arc_twitter_suffix\'];\n	$tweet_default = $prefs[\'arc_twitter_tweet_default\'];\n	$url_method = $prefs[\'arc_twitter_url_method\'];\n	$short_url = $prefs[\'arc_short_url\'];\n	$short_site_url = $prefs[\'arc_short_site_url\'];\n	$cache_dir = $prefs[\'arc_twitter_cache_dir\'];\n	$tab = $prefs[\'arc_twitter_tab\'];\n	$utm = !empty($prefs[\'arc_twitter_utm\']) ? $prefs[\'arc_twitter_utm\'] : 0;\n\n	switch ($step) {\n		case \'prefs_save\': pagetop(\'TXP Tweet\', \'Preferences saved\'); break;\n		case \'register\': pagetop(\'TXP Tweet\',\'Connect to Twitter\'); break;\n		case \'validate\':\n		default: pagetop(\'TXP Tweet\');\n	}\n\n	$html = \'\';\n\n	if ($step==\'register\') { // OAuth registration process\n\n		$twit = new arc_twitter($arc_twitter_consumerKey, $arc_twitter_consumerSecret);\n\n		// Build a callback URL for Twitter to return to the next stage\n		$callbackURL = $twit->callbackURL($event,\'validate\');\n\n		$request = $twit->getRequestToken($callbackURL);\n		$request_token = $request[\"oauth_token\"];\n		$request_token_secret = $request[\"oauth_token_secret\"];\n\n		set_pref(\'arc_twitter_requestToken\',$request_token, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_requestTokenSecret\',$request_token_secret, \'arc_twitter\',2);\n\n	$html = \"<div class=\'text-column\'>\"\n	  .\"<p>\".href(\'Sign-in to Twitter\', $twit->getAuthorizeURL($request)).\" and follow the instructions to allow TXP Tweet to use your account. If you are already signed in to Twitter then that account will be associated with TXP Tweet so you may need to sign out first if you want to use a different account.</p>\"\n	  .\"</div>\";\n\n	} elseif ($step==\'validate\') {\n		$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_requestToken\']\n			, $prefs[\'arc_twitter_requestTokenSecret\']);\n		// Ask Twitter for an access token (and an access token secret)\n		$request = $twit->getAccessToken( gps(\'oauth_verifier\') );\n		$access_token = $request[\'oauth_token\'];\n		$access_token_secret = $request[\'oauth_token_secret\'];\n		$user = $request[\'screen_name\'];\n		// Store the access token and secret\n		set_pref(\'arc_twitter_accessToken\',$access_token, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_accessTokenSecret\',$access_token_secret\n			, \'arc_twitter\',2);\n		set_pref(\'arc_twitter_user\',$user);\n		$prefs[\'arc_twitter_accessToken\'] = $access_token;\n		$prefs[\'arc_twitter_accessTokenSecret\'] = $access_token_secret;\n		unset($twit);\n	}\n\n	if ($step==\"prefs_save\") {\n\n		$prefix = trim(gps(\'arc_twitter_prefix\'));\n		$suffix = trim(gps(\'arc_twitter_suffix\'));\n		$tweet_default = gps(\'arc_twitter_tweet_default\');\n		$url_method = gps(\'arc_twitter_url_method\');\n		$short_url = gps(\'arc_short_url\');\n		$short_site_url = gps(\'arc_short_site_url\');\n		$cache_dir = gps(\'arc_twitter_cache_dir\');\n		$tab = gps(\'arc_twitter_tab\');\n		$utm = gps(\'arc_twitter_utm\');\n\n		if (strlen($prefix)<=20) {\n			set_pref(\'arc_twitter_prefix\',$prefix);\n		} else {\n			$prefix = $prefs[\'arc_twitter_prefix\'];\n		}\n		if (strlen($suffix)<=20) {\n			set_pref(\'arc_twitter_suffix\',$suffix);\n		} else {\n			$suffix = $prefs[\'arc_twitter_suffix\'];\n		}\n		$tweet_default = ($tweet_default) ? 1 : 0;\n		$short_url = ($short_url) ? 1 : 0;\n		if (!$short_site_url) {\n			$short_site_url = $prefs[\'siteurl\'];\n		}\n		set_pref(\'arc_twitter_tweet_default\', $tweet_default);\n		set_pref(\'arc_short_url\', $short_url);\n		set_pref(\'arc_twitter_url_method\', $url_method);\n		set_pref(\'arc_short_site_url\', $short_site_url);\n		set_pref(\'arc_twitter_cache_dir\', $cache_dir);\n		set_pref(\'arc_twitter_tab\', $tab);\n		set_pref(\'arc_twitter_utm\', $utm);\n\n	}\n\n	if ( $step!=\'register\' ) {\n		if ( isset($prefs[\'arc_twitter_accessToken\'])\n		&& isset($prefs[\'arc_twitter_accessTokenSecret\']) ) {\n			$twit = new arc_twitter($arc_twitter_consumerKey\n				, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n				, $prefs[\'arc_twitter_accessTokenSecret\']);\n			$registerURL = $twit->callbackURL($event,\'register\');\n\n			// Define the fields ready to build the form\n			$fields = array(\n				\'Tweet Settings\' => array(\n				  \'arc_twitter_prefix\' => array(\n					\'label\' => \'Tweet prefix\',\n					\'value\' => $prefix\n				  ),\n				  \'arc_twitter_suffix\' => array(\n					\'label\' => \'Tweet suffix\',\n					\'value\' => $suffix\n				  ),\n				  \'arc_twitter_tweet_default\' => array(\n					\'label\' => \'Tweet articles by default\',\n					\'type\' => \'yesnoRadio\',\n					\'value\' => $tweet_default\n				  ),\n				  \'arc_twitter_url_method\' => array(\n					\'label\' => \'URL shortner\',\n					\'type\' => \'arc_twitter_url_method_select\',\n					\'value\' => $url_method\n				  )\n				),\n				\'TXP Tweet short URL\' => array(\n				  \'arc_short_url\' => array(\n					\'label\' => \'Enable TXP Tweet short URL redirect\',\n					\'type\' => \'yesnoRadio\',\n					\'value\' => $short_url\n				  ),\n				  \'arc_short_site_url\' => array(\n					\'label\' => \'TXP Tweet short site URL\',\n					\'value\' => $short_site_url\n				  )\n				),\n				\'Twitter Tab\' => array(\n				  \'arc_twitter_tab\' => array(\n					\'label\' => \'Location of tab\',\n					\'type\' => \'arc_twitter_tab_select\',\n					\'value\' => $tab\n				  )\n				),\n				\'Google Analytics\' => array(\n					\'arc_twitter_utm\' => array(\n						\'label\' => \'Enable UTM\',\n						\'type\' => \'yesnoRadio\',\n						\'value\' => $utm\n					)\n				),\n				\'Cache\' => array(\n				  \'arc_twitter_cache_dir\' => array(\n					\'label\' => \'Cache directory\',\n					\'value\' => $cache_dir\n				  )\n				)\n			);\n\n			$form = \"<h2>Twitter account details</h2>\"\n		.\"<p><span class=\'edit-label\'>Twitter username</span>\"\n		.\"<span class=\'edit-value\'>\"\n		.($prefs[\'arc_twitter_user\'] ? $user.\' (\'.href(\'Re-connect\',$registerURL).\')\' : \'<em>unknown</em>\'.href(\'Connect to Twitter\',$registerURL))\n				.\"</span></p>\";\n\n			$form .= _arc_twitter_form_builder($fields);\n\n			$form .= sInput(\'prefs_save\').n.eInput(\'plugin_prefs.arc_twitter\');\n\n	  $form .= \'<p>\'.fInput(\'submit\', \'Submit\', gTxt(\'save_button\'), \'publish\').\'</p>\';\n\n	  $html = \"<h1 class=\'txp-heading\'>TXP Tweet</h1>\"\n		.\"<p class=\'nav-tertiary\'>\"\n		.\"<a href=\'./?event=arc_admin_twitter\' class=\'navlink\'>Twitter</a><a href=\'./?event=plugin_prefs.arc_twitter\' class=\'navlink-active\'>Options</a>\"\n		.\"</p>\";\n\n	  $html .= form(\"<div class=\'plugin-column\'>\".$form.\"</div>\", \" class=\'edit-form\'\");\n\n		} elseif ( $step!=\'register\' ) {\n\n			$registerURL = arc_twitter::callbackURL($event,\'register\');\n\n			$form = \"<h2>Twitter account details</h2>\"\n		.\"<span class=\'edit-label\'>Twitter username</span>\"\n		.\"<span class=\'edit-value\'><em>unknown</em> &mdash; \"\n		.href(\'Connect to Twitter\',$registerURL)\n				.\"</span>\";\n\n	  $html = form(\"<div class=\'plugin-column\'>\".$form.\"</div>\", \" class=\'edit-form\'\");\n\n		}\n	}\n\n	// Set jQuery for switching on/off relevant arc_short_url fields\n	$js = <<<JS\n<script language=\"javascript\" type=\"text/javascript\">\n$(document).ready(function(){\n  var onoff = $(\'.arc_short_url\');\n  var arc_short_url_off = $(\'#arc_short_url-arc_short_url-0\');\n  var url = $(\'.arc_short_site_url\');\n  var url_method = $(\'select[name=\"arc_twitter_url_method\"]\');\n\n  if (arc_short_url_off.attr(\'checked\')==\'checked\' && $(\'option:selected\', url_method).val()!=\'arc_twitter\') {\n	url.hide();\n  }\n  $(\'input\', onoff).change(function(){\n	if ($(\'option:selected\', url_method).val()!=\'arc_twitter\') {\n	  arc_short_url_off.attr(\'checked\')==\'checked\' ? url.hide() : url.show();\n	}\n  });\n\n  if ($(\'option:selected\', url_method).val()==\'arc_twitter\') {\n	onoff.hide(); url.show();\n  }\n  url_method.change(function(){\n	if ($(\'option:selected\', url_method).val()==\'arc_twitter\') {\n	  onoff.toggle(); url.show();\n	} else {\n	  onoff.toggle();\n	  arc_short_url_off.attr(\'checked\')==\'checked\' ? url.hide() : url.show();\n	}\n  })\n});\n</script>\nJS;\n\n	echo $js.$html;\n}\n\nfunction _arc_twitter_form_builder($fields) {\n\n  $form = \'\';\n\n  foreach ($fields as $fk => $fv) {\n\n	$form .= ($fk) ? \"<h2>$fk</h2>\" : \'\';\n\n	foreach ($fv as $k => $v) {\n\n	  $type = isset($v[\'type\']) ? $v[\'type\'] : \'text\';\n\n	  $form .= \"<p class=\'$k\'>\"\n		.\"<span class=\'edit-label\'><label for=\'$k\'>\".$v[\'label\'].\"</label></span>\";\n\n	  switch ($type)  {\n\n		case \'textarea\':\n\n		  $form .= text_area($k, \'50\', \'550\', $v[\'value\'], $k);\n		  break;\n\n		case \'yesnoRadio\':\n\n		  $form .= \"<span class=\'edit-value\'>\".yesnoRadio($k, $v[\'value\'], \'\', $k).\"</span>\";\n		  break;\n\n		case \'arc_twitter_tab_select\':\n\n		  $form .= \"<span class=\'edit-value\'>\".arc_twitter_tab_select($k, $v[\'value\']).\"</span>\";\n		  break;\n\n		case \'arc_twitter_url_method_select\':\n\n		  $form .= \"<span class=\'edit-value\'>\".arc_twitter_url_method_select($k, $v[\'value\']).\"</span>\";\n		  break;\n\n		default:\n\n		  $form .= \"<span class=\'edit-value\'>\".fInput(\'text\',$k,$v[\'value\'],\'\',\'\',\'\',\'\',\'\',$k).\"</span>\";\n		  break;\n\n	  }\n\n	  $form .= \"</p>\";\n	}\n\n  }\n\n  return $form;\n}\n\n// Add Twitter tab to Textpattern\nfunction arc_admin_twitter($event,$step)\n{\n	global $prefs, $arc_twitter_consumerKey, $arc_twitter_consumerSecret;\n\n	$twit = new arc_twitter($arc_twitter_consumerKey\n			, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n			, $prefs[\'arc_twitter_accessTokenSecret\']);\n\n	$twit->cacheDir($prefs[\'arc_twitter_cache_dir\']);\n\n	$data = $twit->get(\'users/show\'\n		, array(\'screen_name\'=>$prefs[\'arc_twitter_user\']));\n	$twitterUser = $data;\n	$twitterUserURL = \'http://www.twitter.com/\'.$twitterUser->screen_name;\n\n	if ($step==\"tweet\") { // post an update to Twitter\n\n		// fetch and clean message\n		$message = strip_tags(gps(\'message\'));\n		// We need to account for t.co URL shortening in our count (22 characters for a URL, 23 if https).\n		$urlPattern = \"/(http|https):\\/\\/[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&amp;:\\/~+#-]*[\\w@?^=%&amp;\\/~+#-])?/\";\n		$tmpMessage = preg_replace($urlPattern, \"$1xxxxxxxxxxxxxxxxxx\", $message);\n		$count = strlen($tmpMessage);\n		unset($tmpMessage);\n\n		if ($count<=280 && $count>0) { // post update\n			$result = $twit->post(\'statuses/update\', array(\'status\' => $message));\n		} else { // message too long, JavaScript interface should prevent this\n			$result = false;\n		}\n\n		pagetop(\'Twitter\',\n			(($result!=false)?\'Twitter updated\':\'Error updating Twitter\'));\n\n	} elseif ($step==\"delete\") { // delete an update from Twitter\n\n		$id = strip_tags(gps(\'id\'));\n		if ($id) {\n			$twit->delete(\'statuses/destroy/\'.$id);\n			safe_delete(\'arc_twitter\',\"tweet_id = $id\");\n		}\n\n		pagetop(\'Twitter\',\'Twitter updated\');\n\n	} else {\n\n		pagetop(\'Twitter\');\n\n	}\n\n	// Prepare JavaScript to create Twitter update interface\n\n	$js = \'<script language=\"javascript\" type=\"text/javascript\">\';\n	$js.= <<<JS\n$(document).ready(function(){\n	var counter = $(\'<span>\', {\n		\'text\' : \'280\',\n		\'id\' : \'tweetcount\'\n	});\n	$(\'.message\').append(counter);\n	var counterStyle = \'font-weight:bold;padding-left:.5em;font-size:2em;line-height:1.2em;\';\n	$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#ccc;\');\n	$(\'#message\').keyup(function() {\n		// We need to account for t.co URL shortening in our count (22 characters for a URL, 23 if https).\n		var message = $(\'#message\').val();\n		var urlPattern = /(http|https):\\/\\/[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&amp;:\\/~+#-]*[\\w@?^=%&amp;\\/~+#-])?/;\n		message = message.replace(urlPattern, \'$1xxxxxxxxxxxxxxxxxx\');\n		var count = 280-message.length;\n		$(\'#tweetcount\').html(count+\'\'); // hack to force output of 0\n		if (count<0) {\n			$(\'input.publish\').prop(\'disabled\', \'disabled\');\n		} else {\n			$(\'input.publish\').prop(\'disabled\', \'\');\n		}\n		if (count<0) {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#f00;\');\n		} else if (count<10) {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#000;\');\n		} else {\n			$(\'#tweetcount\').attr(\'style\', counterStyle+\'color:#ccc;\');\n		}\n	})\n});\nJS;\n	$js.= \"</script>\";\n\n	$out = \'\';\n	$tweets = $twit->get(\'statuses/user_timeline\'\n		, array(\'screen_name\'=>$prefs[\'arc_twitter_user\'],\'count\'=>25));\n	if ($tweets) foreach ($tweets as $tweet) {\n		$time = strtotime(htmlentities($tweet->created_at));\n		$date = safe_strftime($prefs[\'archive_dateformat\'],$time);\n		$links = false;\n		if (isset($tweet->entities->urls)) {\n			$links = array();\n			foreach ($tweet->entities->urls as $url) {\n				$links[$url->url] = array(\n					\'expanded_url\' => $url->expanded_url,\n					\'display_url\' => $url->display_url\n				);\n			}\n		}\n		$out.= tr(td($date,\'span\')\n			.td(arc_Twitter::makeLinks(htmlentities($tweet->text\n				, ENT_QUOTES,\'UTF-8\'), $links, true))\n			.td(dLink(\'arc_admin_twitter\',\'delete\',\'id\',$tweet->id_str,\'\'))\n		);\n	}\n\n	$fields = array(\n	\'\' => array(\n	  \'message\' => array(\n		\'label\' => \'Update Twitter\',\n		\'type\' => \'textarea\',\n		\'value\' => \'\'\n	  )\n	)\n	);\n\n	$profile = \'<img src=\"\'.$twitterUser->profile_image_url.\'\" alt=\"Twitter avatar\" style=\"float:left; margin-right: 1em\" />\'\n	.graf(href($twitterUser->name,$twitterUserURL),\' style=\"font-size:1.2em;font-weight:bold;\"\')\n	.graf(href($twitterUser->friends_count.\' following\',$twitterUserURL.\'/following\')\n	.\', \'.href($twitterUser->followers_count.\' followers\',$twitterUserURL.\'/followers\')\n	.\', \'.href($twitterUser->statuses_count.\' updates\',$twitterUserURL));\n\n	$form = _arc_twitter_form_builder($fields)\n	.eInput(\'arc_admin_twitter\')\n	.sInput(\'tweet\');\n  $form .= \'<p>\'.fInput(\'submit\', \'Submit\', gTxt(\'Update\'), \'publish\').\'</p>\';\n\n  $html = \"<h1 class=\'txp-heading\'>TXP Tweet</h1>\"\n	.\"<p class=\'nav-tertiary\'>\"\n	.\"<a href=\'./?event=arc_admin_twitter\' class=\'navlink-active\'>Twitter</a><a href=\'./?event=plugin_prefs.arc_twitter\' class=\'navlink\'>Options</a>\"\n	.\"</p>\";\n\n	$html .= \"<div class=\'text-column\'>\".$profile.\"</div>\"\n	.\"<br style=\'clear:both\' />\"\n	.form(\"<div class=\'plugin-column\'>\".$form.\"</div>\".br);\n\n	// Attach recent Twitter updates\n\n	$html.= \"<div class=\'txp-listtables\'>\"\n	.startTable(\'arc_twitter_timeline\',\'\',\'txp-list\').$out.endTable()\n	.\"</div>\";\n\n	// Output JavaScript and HTML\n\n	echo $js.$html;\n}\n\n// Add Twitter options to article article screen\nfunction arc_append_twitter($event, $step, $data, $rs1)\n{\n	global $prefs, $arc_twitter, $app_mode;\n\n	$prefix = trim(gps(\'arc_twitter_prefix\'));\n	$prefix = ($prefix) ? $prefix : $prefs[\'arc_twitter_prefix\'];\n	$suffix = trim(gps(\'arc_twitter_suffix\'));\n	$suffix = ($suffix) ? $suffix : $prefs[\'arc_twitter_suffix\'];\n\n	if ($rs1[\'ID\']) {\n		$sql = \"SELECT tweet_id,tweet FROM \".PFX.\"arc_twitter WHERE article_id=\".$rs1[\'ID\'].\";\";\n		$rs2 = safe_query($sql); $rs2 = nextRow($rs2);\n	} else { // new article\n		$rs2 = \'\';\n	}\n\n	if ($app_mode == \'async\')\n	{\n	 send_script_response(\'$(\"#arc_twitter\").remove();\');\n	}\n\n	if ($rs1[\'ID\'] && $rs2[\'tweet_id\'])\n	{\n		$content = arc_Twitter::makeLinks($rs2[\'tweet\']);\n		return $data.fieldset(tag($content, \'p\'), \'Twitter update\', \'arc_twitter\');\n	}\n	else\n	{\n		$var = gps(\'arc_tweet_this\');\n		$var = ($rs1[\'ID\']&&!$var) ? 0 : $prefs[\'arc_twitter_tweet_default\'];\n		$content  = tag(yesnoRadio(\'arc_tweet_this\', $var, \'\', \'arc_tweet_this\'),\'p\');\n		$content .= tag(href(\'Options\',\'#arc_twitter_options\', \' onclick=\"$(\\\'#arc_twitter_options\\\').toggle(); return false;\"\'),\'p\',\' style=\"margin-top:5px;\"\');\n		$content .= tag(tag(tag(\'Tweet prefix\',\'label\', \' for=\"arc_twitter_prefix\"\') . \'<br />\'\n			.fInput(\'text\',\'arc_twitter_prefix\',$prefix,\'edit\',\'\',\'\',\'22\',\'\',\'arc_twitter_prefix\'),\'p\')\n			.tag(tag(\'Tweet suffix (eg #hashtags)\',\'label\', \' for=\"arc_twitter_suffix\"\') . \'<br />\'\n			.fInput(\'text\',\'arc_twitter_suffix\',$suffix,\'edit\',\'\',\'\',\'22\',\'\',\'arc_twitter_suffix\'),\'p\')\n			,\'div\',\' id=\"arc_twitter_options\" class=\"toggle\" style=\"display:none\"\');\n		if (isset($arc_twitter[\'error\'])) {\n			$content .= \'<p>\'.$arc_twitter[\'error\'].\'</p>\';\n		}\n		return $data.fieldset($content, \'Update Twitter\', \'arc_twitter\');\n	}\n\n}\n\n// Update Twitter with posted article\nfunction arc_article_tweet($event,$step)\n{\n	global $prefs, $arc_twitter, $arc_twitter_consumerKey\n		, $arc_twitter_consumerSecret;\n\n	$article_id = empty($GLOBALS[\'ID\']) ? gps(\'ID\') : $GLOBALS[\'ID\'];\n	if (!empty($article_id)) {\n\n		include_once txpath.\'/publish/taghandlers.php\';\n\n		$article = safe_row(\"ID, Title, Section, Posted\", \'textpattern\',\n			\"ID={$article_id} AND Status=4 AND now()>=Posted\");\n\n		if ($article && gps(\'arc_tweet_this\')) { // tweet article\n\n			// Need to manually update the \'URL only title\' before building the\n			// URL\n			$article[\'url_title\'] = gps(\'url_title\');\n\n			// Make short URL\n			$url = permlinkurl($article);\n			$short_url = arc_shorten_url(\n				$url,\n				$prefs[\'arc_twitter_url_method\'],\n				array(\'id\' => $article_id)\n			);\n\n			if (!$short_url) { // Failed to obtain a shortened URL, do not tweet!\n				$arc_twitter[\'error\'] = \'Unable to obtain a short URL for this article.\';\n\n				return false;\n			}\n\n			// Construct Twitter update\n			$prefix  = trim(gps(\'arc_twitter_prefix\'));\n			$pre_len = strlen($prefix);\n			$prefix  = ($prefix && $pre_len<=20) ? $prefix.\' \' : \'\';\n			$suffix  = trim(gps(\'arc_twitter_suffix\'));\n			$suf_len = strlen($suffix);\n			$suffix  = ($suffix && $suf_len<=40) ? \' \'.$suffix : \'\';\n			$url_len = preg_match(\'|^https|i\', $short_url) ? 23 : 22; // count URL length + 1 for prefixed space\n			if ($prefix) {\n				$pre_len += 1;\n			}\n			if ($suffix) {\n				$suf_len += 1;\n			}\n			if ((strlen($article[\'Title\'])+$url_len+$pre_len+$suf_len)>280) {\n				$article[\'Title\'] = substr($article[\'Title\'],0,135-$url_len-$pre_len-$suf_len).\'...\';\n			}\n			$tweet = $prefix.$article[\'Title\'].\" \".$short_url.$suffix;\n\n			// Update Twitter\n			$twit = new arc_twitter($arc_twitter_consumerKey\n				, $arc_twitter_consumerSecret, $prefs[\'arc_twitter_accessToken\']\n				, $prefs[\'arc_twitter_accessTokenSecret\']);\n			$result = $twit->post(\'statuses/update\', array(\'status\' => $tweet));\n\n			$tweet_id = (is_object($result)) ? $result->id_str : 0;\n\n			if ($tweet_id) {\n\n				$tweet = addslashes($tweet);\n\n				// If we\'re not using a URL shortener don\'t store the short URL.\n				if ($prefs[\'arc_twitter_url_method\']==\'full\') {\n					$short_url = null;\n				}\n\n				// update arc_twitter table with tweet\n				$sql = \"INSERT INTO \".PFX.\"arc_twitter (article_id,tweet_id,tweet,tinyurl) \";\n				$sql.= \"VALUES($article_id,$tweet_id,\\\"$tweet\\\",\'$short_url\');\";\n				safe_query($sql);\n\n				return true;\n\n			} else {\n\n				$arc_twitter[\'error\'] = \'Twitter response: \'\n					.$twit->http_code;\n				return false;\n\n			}\n\n		}\n\n	}\n\n	return false;\n\n}\n\n/*\n * Shorten URLs using various methods\n */\n\nfunction arc_shorten_url($url, $method=\'\', $atts=array())\n{\n  global $prefs;\n\n  $ch = curl_init();\n  curl_setopt($ch, CURLOPT_HEADER, 0);\n  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n  curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n\n  $utm = null;\n\n  if (!empty($prefs[\'arc_twitter_utm\'])) {\n\n	$utm = \'utm_source=twitter.com&utm_medium=social&utm_campaign=arc_twitter&utm_content=txp:\' . $atts[\'id\'];\n	$separator = (parse_url($url, PHP_URL_QUERY) == NULL) ? \'?\' : \'&\';\n	$url .= $separator . $utm;\n\n  }\n\n  switch ($method) {\n	case \'smd\': // create a shortened URL using SMD Short URL\n\n		$url = !empty($atts[\'id\']) ? hu . $atts[\'id\'] : false;\n		$url .= ($url!==false && !empty($utm)) ? \'?\' . $utm : false;\n		return $url; break;\n\n	case \'arc_twitter\': // native URL shortening\n\n		$url = !empty($atts[\'id\']) ? PROTOCOL . $prefs[\'arc_short_site_url\'] . \'/\' . $atts[\'id\'] : false;\n		$url .= ($url!==false && !empty($utm)) ? \'?\' . $utm : false;\n		return $url;\n		break;\n\n	case \'isgd\':\n\n		$u = \'http://is.gd/api.php?longurl=\'.urlencode($url);\n		curl_setopt($ch, CURLOPT_URL, $u);\n\n		$tinyurl = curl_exec($ch);\n		if ($tinyurl!=\'Error\' && $tinyurl!=\'\') {\n			return $tinyurl;\n		} else {\n			trigger_error(\'arc_twitter failed to get a is.gd URL for \'\n				.$url,E_USER_WARNING);\n		}\n		break;\n\n	case \'tinyurl\': // create a shortened URL using TinyURL\n\n		$u = \'http://tinyurl.com/api-create.php?url=\'.urlencode($url);\n		curl_setopt($ch, CURLOPT_URL, $u);\n		$tinyurl = curl_exec($ch);\n		if ($tinyurl!=\'Error\' && $tinyurl!=\'\') {\n			return $tinyurl;\n		} else {\n			trigger_error(\'arc_twitter failed to get a TinyURL for \'.$url,E_USER_WARNING);\n		}\n\n	default:\n\n		return $url;\n\n  }\n\n  return false; // fail\n\n}\n\n/*\n * Shortened URL redirect based on smd_short_url\n */\nfunction arc_short_url_redirect($event, $step) {\n  global $prefs;\n\n  $have_id = 0;\n\n  // Check if there is an available short site url and if it is being used in\n  // this instance\n  $short_site_url = $prefs[\'arc_short_site_url\'];\n  if ($short_site_url) {\n	$short_site_url = PROTOCOL.$short_site_url.\'/\';\n	$url_parts = parse_url($short_site_url);\n	$re = \'#^\'.$url_parts[\'path\'].\'([0-9].*)#\';\n	$have_id = preg_match($re, $_SERVER[\'REQUEST_URI\'], $m);\n  }\n\n  // Fall back to standard site url (smd_short_url behaviour)\n  if ($have_id) {\n	$url_parts = parse_url(hu);\n	$re = \'#^\'.$url_parts[\'path\'].\'([0-9].*)#\';\n	$have_id = preg_match($re, $_SERVER[\'REQUEST_URI\'], $m);\n  }\n\n  // Do the redirect if we\'ve got an article id\n  if ($have_id) {\n	$id = $m[1];\n	$permlink = permlinkurl_id($id);\n\n	if ($permlink) {\n	  ob_end_clean();\n\n	  // Stupid, over the top header setting for IE\n	  header(\"Status: 301\");\n	  header(\"HTTP/1.0 301 Moved Permanently\");\n	  header(\"Location: \".$permlink, TRUE, 301);\n\n	  // In case the header() method fails, fall back on a classic redirect\n	  echo \'<html><head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=\'\n		.$permlink.\'\"></head><body></body></html>\';\n	  die();\n	}\n  }\n\n}\n\n// Auto enable plugin on install (original idea by Michael Manfre)\nfunction _arc_twitter_auto_enable($event, $step, $prefix=\'arc_twitter\')\n{\n  $plugin = substr($event, strlen(\'plugin_lifecycle.\'));\n  if (strncmp($plugin, $prefix, strlen($prefix)) == 0)\n  {\n	safe_update(\'txp_plugin\', \"status = 1\", \"name = \'\" . doSlash($plugin) . \"\'\");\n  }\n}\n\n/*\n *******************************************************************************\n*/\n\nclass arc_twitter extends TwitterOAuth {\n	// Caching variables\n	private $_cache = true;\n	private $_cache_dir = \'./tmp\';\n	private $_cache_time = 1800; // 30 minute cache\n\n	function __construct($consumer_key, $consumer_secret, $oauth_token = NULL\n		, $oauth_token_secret = NULL)\n	{\n		parent::__construct($consumer_key, $consumer_secret, $oauth_token\n			, $oauth_token_secret);\n		$this->format = \'json\';\n		$this->timeout = 15;\n		$this->connecttimeout = 15;\n	}\n\n	public function callbackURL($event,$step)\n	{\n		return preg_replace(\'/\\?.*/\', \'\',PROTOCOL.$_SERVER[\'SERVER_NAME\'].$_SERVER[\'REQUEST_URI\'])\n		  .\'?event=\'.$event.\'&amp;step=\'.$step;\n	}\n\n	// create Twitter and external links in text\n	public static function makeLinks($text, $urls = false, $expandedUrls = false)\n	{\n		if ($urls!==false) {\n\n			foreach ($urls as $tcoUrl => $url)\n			{\n				if (!$expandedUrls)\n				{\n					$url[\'expanded_url\'] = $tcoUrl;\n					$url[\'display_url\'] = preg_replace(\'|^https?:\\/\\/|i\', \'\', $tcoUrl);\n				}\n				$text = str_replace(\n					$tcoUrl,\n					\"<a href=\'{$url[\'expanded_url\']}\'>{$url[\'display_url\']}</a>\",\n					$text\n				);\n			}\n\n		}\n		elseif ($urls!==false)\n		{\n\n		}\n		else\n		{\n			$links = array(\n				\'/\\b(http|https|ftp):\\/\\/([A-Z0-9][A-Z0-9_-]*(?:\\.[A-Z0-9][A-Z0-9_-]*)+):?(\\d+)?\\/?([\\/\\w+-\\.]+)(\\?[\\.a-z=&0-4:_-]+){0,1}\\b/i\' => \"<a href=\'$0\' rel=\'external\'>$2$3$4</a>\",\n				\'/\\b(^|\\s)www.([a-z_A-Z0-9]+)((\\.[a-z]+)+)\\b/i\' => \"<a href=\'http://www.$2$3\' rel=\'external\'>www.$2$3</a>\"\n			);\n			$text = preg_replace(\n				array_keys($links),\n				array_values($links),\n				$text\n			);\n		}\n\n		$links = array(\n			\'/(^|\\s).?@([a-z_A-Z0-9]+)/\' => \"$1<a href=\'http://twitter.com/$2\' rel=\'external\'>@$2</a>\",\n			\'/(^|\\s)(\\#([a-z_A-Z0-9:_-]+))/\' => \"$1<a href=\'http://twitter.com/search?q=%23$3\' rel=\'external\'>$2</a>\"\n		);\n		return preg_replace(\n			array_keys($links),\n			array_values($links),\n			$text\n		);\n  }\n\n	public function get($url, $params = array())\n	{\n		$api_url = md5($url.urlencode(serialize($params)));\n		$data = \'\';\n\n		if ($this->_cache) { // check for cached json\n\n			$data = $this->_retrieveCache($api_url);\n\n		}\n		if (empty($data)) {\n			$data = parent::get($url, $params); // data already json_decode\'d\n			if ($this->http_code===200 && $encoded_data=json_encode($data)) { // save cache\n				$file = $this->_cache_dir.\'/\'.$api_url;\n				file_put_contents($file,$encoded_data,LOCK_EX);\n				return $data;\n			} else { // failed to retrieve data from Twitter\n\n				if ($this->_cache) { // attempt to force cached json return\n\n					$data = $this->_retrieveCache($api_url,true);\n					if ($data) return json_decode($data);\n\n				}\n\n				return false;\n\n			}\n		} else { // return cached json\n			return json_decode($data);\n		}\n	} //end get()\n\n	function post($url, $params = array())\n	{\n		$data = parent::post($url,$params);\n		return $data;\n	}\n\n	function delete($url, $params = array())\n	{\n		$data = parent::delete($url,$params);\n		return $data;\n	}\n\n	// Cache methods\n\n	public function setCaching($cache=true)\n	{\n		$this->_cache = ($cache) ? true : false;\n		return true;\n	}\n\n	public function cacheDir($loc)\n	{\n		$this->_cache_dir = $loc;\n		return true;\n	}\n\n	public function cacheTime($mins)\n	{\n		$this->_cache_time = $mins*60; // convert minutes into seconds\n		return true;\n	}\n\n	private function _retrieveCache($url,$overide_timeout=false)\n	{\n		$file = $this->_cache_dir.\'/\'.$url;\n		if (file_exists($file)) {\n\n			$diff = time() - filemtime($file);\n			if ($overide_timeout || $diff < $this->_cache_time) {\n				return file_get_contents($file);\n			} else {\n				return false;\n			}\n\n		} else {\n			return false;\n		}\n	}\n}\n\n/*\n * Abraham Williams (abraham@abrah.am) http://abrah.am\n *\n * The first PHP Library to support OAuth for Twitter\'s REST API.\n */\n\n/**\n * Twitter OAuth class\n */\nclass TwitterOAuth {\n  /* Contains the last HTTP status code returned. */\n  public $http_code;\n  /* Contains the last API call. */\n  public $url;\n  /* Set up the API root URL. */\n  public $host = \"https://api.twitter.com/1.1/\";\n  /* Set timeout default. */\n  public $timeout = 30;\n  /* Set connect timeout. */\n  public $connecttimeout = 30;\n  /* Verify SSL Cert. */\n  public $ssl_verifypeer = FALSE;\n  /* Respons format. */\n  public $format = \'json\';\n  /* Decode returned json data. */\n  public $decode_json = TRUE;\n  /* Contains the last HTTP headers returned. */\n  public $http_info;\n  /* Set the useragnet. */\n  public $useragent = \'TwitterOAuth v0.2.0-beta2\';\n  /* Immediately retry the API call if the response was not successful. */\n  //public $retry = TRUE;\n\n  /**\n   * Set API URLS\n   */\n  function accessTokenURL()  { return \'https://api.twitter.com/oauth/access_token\'; }\n  function authenticateURL() { return \'https://api.twitter.com/oauth/authenticate\'; }\n  function authorizeURL()    { return \'https://api.twitter.com/oauth/authorize\'; }\n  function requestTokenURL() { return \'https://api.twitter.com/oauth/request_token\'; }\n\n  /**\n   * Debug helpers\n   */\n  function lastStatusCode() { return $this->http_status; }\n  function lastAPICall() { return $this->last_api_call; }\n\n  /**\n   * construct TwitterOAuth object\n   */\n  function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {\n	$this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();\n	$this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);\n	if (!empty($oauth_token) && !empty($oauth_token_secret)) {\n	  $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);\n	} else {\n	  $this->token = NULL;\n	}\n  }\n\n\n  /**\n   * Get a request_token from Twitter\n   *\n   * @returns a key/value array containing oauth_token and oauth_token_secret\n   */\n  function getRequestToken($oauth_callback) {\n	$parameters = array();\n	$parameters[\'oauth_callback\'] = $oauth_callback;\n	$request = $this->oAuthRequest($this->requestTokenURL(), \'GET\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * Get the authorize URL\n   *\n   * @returns a string\n   */\n  function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) {\n	if (is_array($token)) {\n	  $token = $token[\'oauth_token\'];\n	}\n	if (empty($sign_in_with_twitter)) {\n	  return $this->authorizeURL() . \"?oauth_token={$token}\";\n	} else {\n	   return $this->authenticateURL() . \"?oauth_token={$token}\";\n	}\n  }\n\n  /**\n   * Exchange request token and secret for an access token and\n   * secret, to sign API calls.\n   *\n   * @returns array(\"oauth_token\" => \"the-access-token\",\n   *                \"oauth_token_secret\" => \"the-access-secret\",\n   *                \"user_id\" => \"9436992\",\n   *                \"screen_name\" => \"abraham\")\n   */\n  function getAccessToken($oauth_verifier) {\n	$parameters = array();\n	$parameters[\'oauth_verifier\'] = $oauth_verifier;\n	$request = $this->oAuthRequest($this->accessTokenURL(), \'GET\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * One time exchange of username and password for access token and secret.\n   *\n   * @returns array(\"oauth_token\" => \"the-access-token\",\n   *                \"oauth_token_secret\" => \"the-access-secret\",\n   *                \"user_id\" => \"9436992\",\n   *                \"screen_name\" => \"abraham\",\n   *                \"x_auth_expires\" => \"0\")\n   */\n  function getXAuthToken($username, $password) {\n	$parameters = array();\n	$parameters[\'x_auth_username\'] = $username;\n	$parameters[\'x_auth_password\'] = $password;\n	$parameters[\'x_auth_mode\'] = \'client_auth\';\n	$request = $this->oAuthRequest($this->accessTokenURL(), \'POST\', $parameters);\n	$token = OAuthUtil::parse_parameters($request);\n	$this->token = new OAuthConsumer($token[\'oauth_token\'], $token[\'oauth_token_secret\']);\n	return $token;\n  }\n\n  /**\n   * GET wrapper for oAuthRequest.\n   */\n  function get($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'GET\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * POST wrapper for oAuthRequest.\n   */\n  function post($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'POST\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * DELETE wrapper for oAuthReqeust.\n   */\n  function delete($url, $parameters = array()) {\n	$response = $this->oAuthRequest($url, \'DELETE\', $parameters);\n	if ($this->format === \'json\' && $this->decode_json) {\n	  return json_decode($response);\n	}\n	return $response;\n  }\n\n  /**\n   * Format and sign an OAuth / API request\n   */\n  function oAuthRequest($url, $method, $parameters) {\n	if (strrpos($url, \'https://\') !== 0 && strrpos($url, \'http://\') !== 0) {\n	  $url = \"{$this->host}{$url}.{$this->format}\";\n	}\n	$request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);\n	$request->sign_request($this->sha1_method, $this->consumer, $this->token);\n	switch ($method) {\n	case \'GET\':\n	  return $this->http($request->to_url(), \'GET\');\n	default:\n	  return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata());\n	}\n  }\n\n  /**\n   * Make an HTTP request\n   *\n   * @return API results\n   */\n  function http($url, $method, $postfields = NULL) {\n	$this->http_info = array();\n	$ci = curl_init();\n	/* Curl settings */\n	curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);\n	curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);\n	curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);\n	curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);\n	curl_setopt($ci, CURLOPT_HTTPHEADER, array(\'Expect:\'));\n	curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);\n	curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, \'getHeader\'));\n	curl_setopt($ci, CURLOPT_HEADER, FALSE);\n\n	switch ($method) {\n	  case \'POST\':\n		curl_setopt($ci, CURLOPT_POST, TRUE);\n		if (!empty($postfields)) {\n		  curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);\n		}\n		break;\n	  case \'DELETE\':\n		curl_setopt($ci, CURLOPT_CUSTOMREQUEST, \'DELETE\');\n		if (!empty($postfields)) {\n		  $url = \"{$url}?{$postfields}\";\n		}\n	}\n\n	curl_setopt($ci, CURLOPT_URL, $url);\n	$response = curl_exec($ci);\n	$this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);\n	$this->http_info = array_merge($this->http_info, curl_getinfo($ci));\n	$this->url = $url;\n	curl_close ($ci);\n	return $response;\n  }\n\n  /**\n   * Get the header info to store.\n   */\n  function getHeader($ch, $header) {\n	$i = strpos($header, \':\');\n	if (!empty($i)) {\n	  $key = str_replace(\'-\', \'_\', strtolower(substr($header, 0, $i)));\n	  $value = trim(substr($header, $i + 2));\n	  $this->http_header[$key] = $value;\n	}\n	return strlen($header);\n  }\n}\n/* Generic exception class\n */\nif (!class_exists(\'OAuthException\')) {\n  class OAuthException extends Exception {\n	// pass\n  }\n}\n\nclass OAuthConsumer {\n  public $key;\n  public $secret;\n\n  function __construct($key, $secret, $callback_url=NULL) {\n	$this->key = $key;\n	$this->secret = $secret;\n	$this->callback_url = $callback_url;\n  }\n\n  function __toString() {\n	return \"OAuthConsumer[key=$this->key,secret=$this->secret]\";\n  }\n}\n\nclass OAuthToken {\n  // access tokens and request tokens\n  public $key;\n  public $secret;\n\n  /**\n   * key = the token\n   * secret = the token secret\n   */\n  function __construct($key, $secret) {\n	$this->key = $key;\n	$this->secret = $secret;\n  }\n\n  /**\n   * generates the basic string serialization of a token that a server\n   * would respond to request_token and access_token calls with\n   */\n  function to_string() {\n	return \"oauth_token=\" .\n		   OAuthUtil::urlencode_rfc3986($this->key) .\n		   \"&oauth_token_secret=\" .\n		   OAuthUtil::urlencode_rfc3986($this->secret);\n  }\n\n  function __toString() {\n	return $this->to_string();\n  }\n}\n\n/**\n * A class for implementing a Signature Method\n * See section 9 (\"Signing Requests\") in the spec\n */\nabstract class OAuthSignatureMethod {\n  /**\n   * Needs to return the name of the Signature Method (ie HMAC-SHA1)\n   * @return string\n   */\n  abstract public function get_name();\n\n  /**\n   * Build up the signature\n   * NOTE: The output of this function MUST NOT be urlencoded.\n   * the encoding is handled in OAuthRequest when the final\n   * request is serialized\n   * @param OAuthRequest $request\n   * @param OAuthConsumer $consumer\n   * @param OAuthToken $token\n   * @return string\n   */\n  abstract public function build_signature($request, $consumer, $token);\n\n  /**\n   * Verifies that a given signature is correct\n   * @param OAuthRequest $request\n   * @param OAuthConsumer $consumer\n   * @param OAuthToken $token\n   * @param string $signature\n   * @return bool\n   */\n  public function check_signature($request, $consumer, $token, $signature) {\n	$built = $this->build_signature($request, $consumer, $token);\n	return $built == $signature;\n  }\n}\n\n/**\n * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]\n * where the Signature Base String is the text and the key is the concatenated values (each first\n * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an \'&\'\n * character (ASCII code 38) even if empty.\n *   - Chapter 9.2 (\"HMAC-SHA1\")\n */\nclass OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {\n  function get_name() {\n	return \"HMAC-SHA1\";\n  }\n\n  public function build_signature($request, $consumer, $token) {\n	$base_string = $request->get_signature_base_string();\n	$request->base_string = $base_string;\n\n	$key_parts = array(\n	  $consumer->secret,\n	  ($token) ? $token->secret : \"\"\n	);\n\n	$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);\n	$key = implode(\'&\', $key_parts);\n\n	return base64_encode(hash_hmac(\'sha1\', $base_string, $key, true));\n  }\n}\n\n/**\n * The PLAINTEXT method does not provide any security protection and SHOULD only be used\n * over a secure channel such as HTTPS. It does not use the Signature Base String.\n *   - Chapter 9.4 (\"PLAINTEXT\")\n */\nclass OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {\n  public function get_name() {\n	return \"PLAINTEXT\";\n  }\n\n  /**\n   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and\n   * Token Secret, separated by a \'&\' character (ASCII code 38), even if either secret is\n   * empty. The result MUST be encoded again.\n   *   - Chapter 9.4.1 (\"Generating Signatures\")\n   *\n   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as\n   * OAuthRequest handles this!\n   */\n  public function build_signature($request, $consumer, $token) {\n	$key_parts = array(\n	  $consumer->secret,\n	  ($token) ? $token->secret : \"\"\n	);\n\n	$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);\n	$key = implode(\'&\', $key_parts);\n	$request->base_string = $key;\n\n	return $key;\n  }\n}\n\n/**\n * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in\n * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for\n * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a\n * verified way to the Service Provider, in a manner which is beyond the scope of this\n * specification.\n *   - Chapter 9.3 (\"RSA-SHA1\")\n */\nabstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {\n  public function get_name() {\n	return \"RSA-SHA1\";\n  }\n\n  // Up to the SP to implement this lookup of keys. Possible ideas are:\n  // (1) do a lookup in a table of trusted certs keyed off of consumer\n  // (2) fetch via http using a url provided by the requester\n  // (3) some sort of specific discovery code based on request\n  //\n  // Either way should return a string representation of the certificate\n  protected abstract function fetch_public_cert(&$request);\n\n  // Up to the SP to implement this lookup of keys. Possible ideas are:\n  // (1) do a lookup in a table of trusted certs keyed off of consumer\n  //\n  // Either way should return a string representation of the certificate\n  protected abstract function fetch_private_cert(&$request);\n\n  public function build_signature($request, $consumer, $token) {\n	$base_string = $request->get_signature_base_string();\n	$request->base_string = $base_string;\n\n	// Fetch the private key cert based on the request\n	$cert = $this->fetch_private_cert($request);\n\n	// Pull the private key ID from the certificate\n	$privatekeyid = openssl_get_privatekey($cert);\n\n	// Sign using the key\n	$ok = openssl_sign($base_string, $signature, $privatekeyid);\n\n	// Release the key resource\n	openssl_free_key($privatekeyid);\n\n	return base64_encode($signature);\n  }\n\n  public function check_signature($request, $consumer, $token, $signature) {\n	$decoded_sig = base64_decode($signature);\n\n	$base_string = $request->get_signature_base_string();\n\n	// Fetch the public key cert based on the request\n	$cert = $this->fetch_public_cert($request);\n\n	// Pull the public key ID from the certificate\n	$publickeyid = openssl_get_publickey($cert);\n\n	// Check the computed signature against the one passed in the query\n	$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);\n\n	// Release the key resource\n	openssl_free_key($publickeyid);\n\n	return $ok == 1;\n  }\n}\n\nclass OAuthRequest {\n  private $parameters;\n  private $http_method;\n  private $http_url;\n  // for debug purposes\n  public $base_string;\n  public static $version = \'1.0\';\n  public static $POST_INPUT = \'php://input\';\n\n  function __construct($http_method, $http_url, $parameters=NULL) {\n	@$parameters or $parameters = array();\n	$parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);\n	$this->parameters = $parameters;\n	$this->http_method = $http_method;\n	$this->http_url = $http_url;\n  }\n\n\n  /**\n   * attempt to build up a request from what was passed to the server\n   */\n  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {\n	$scheme = (!isset($_SERVER[\'HTTPS\']) || $_SERVER[\'HTTPS\'] != \"on\")\n			  ? \'http\'\n			  : \'https\';\n	@$http_url or $http_url = $scheme .\n							  \'://\' . $_SERVER[\'HTTP_HOST\'] .\n							  \':\' .\n							  $_SERVER[\'SERVER_PORT\'] .\n							  $_SERVER[\'REQUEST_URI\'];\n	@$http_method or $http_method = $_SERVER[\'REQUEST_METHOD\'];\n\n	// We weren\'t handed any parameters, so let\'s find the ones relevant to\n	// this request.\n	// If you run XML-RPC or similar you should use this to provide your own\n	// parsed parameter-list\n	if (!$parameters) {\n	  // Find request headers\n	  $request_headers = OAuthUtil::get_headers();\n\n	  // Parse the query-string to find GET parameters\n	  $parameters = OAuthUtil::parse_parameters($_SERVER[\'QUERY_STRING\']);\n\n	  // It\'s a POST request of the proper content-type, so parse POST\n	  // parameters and add those overriding any duplicates from GET\n	  if ($http_method == \"POST\"\n		  && @strstr($request_headers[\"Content-Type\"],\n					 \"application/x-www-form-urlencoded\")\n		  ) {\n		$post_data = OAuthUtil::parse_parameters(\n		  file_get_contents(self::$POST_INPUT)\n		);\n		$parameters = array_merge($parameters, $post_data);\n	  }\n\n	  // We have a Authorization-header with OAuth data. Parse the header\n	  // and add those overriding any duplicates from GET or POST\n	  if (@substr($request_headers[\'Authorization\'], 0, 6) == \"OAuth \") {\n		$header_parameters = OAuthUtil::split_header(\n		  $request_headers[\'Authorization\']\n		);\n		$parameters = array_merge($parameters, $header_parameters);\n	  }\n\n	}\n\n	return new OAuthRequest($http_method, $http_url, $parameters);\n  }\n\n  /**\n   * pretty much a helper function to set up the request\n   */\n  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {\n	@$parameters or $parameters = array();\n	$defaults = array(\"oauth_version\" => OAuthRequest::$version,\n					  \"oauth_nonce\" => OAuthRequest::generate_nonce(),\n					  \"oauth_timestamp\" => OAuthRequest::generate_timestamp(),\n					  \"oauth_consumer_key\" => $consumer->key);\n	if ($token)\n	  $defaults[\'oauth_token\'] = $token->key;\n\n	$parameters = array_merge($defaults, $parameters);\n\n	return new OAuthRequest($http_method, $http_url, $parameters);\n  }\n\n  public function set_parameter($name, $value, $allow_duplicates = true) {\n	if ($allow_duplicates && isset($this->parameters[$name])) {\n	  // We have already added parameter(s) with this name, so add to the list\n	  if (is_scalar($this->parameters[$name])) {\n		// This is the first duplicate, so transform scalar (string)\n		// into an array so we can add the duplicates\n		$this->parameters[$name] = array($this->parameters[$name]);\n	  }\n\n	  $this->parameters[$name][] = $value;\n	} else {\n	  $this->parameters[$name] = $value;\n	}\n  }\n\n  public function get_parameter($name) {\n	return isset($this->parameters[$name]) ? $this->parameters[$name] : null;\n  }\n\n  public function get_parameters() {\n	return $this->parameters;\n  }\n\n  public function unset_parameter($name) {\n	unset($this->parameters[$name]);\n  }\n\n  /**\n   * The request parameters, sorted and concatenated into a normalized string.\n   * @return string\n   */\n  public function get_signable_parameters() {\n	// Grab all parameters\n	$params = $this->parameters;\n\n	// Remove oauth_signature if present\n	// Ref: Spec: 9.1.1 (\"The oauth_signature parameter MUST be excluded.\")\n	if (isset($params[\'oauth_signature\'])) {\n	  unset($params[\'oauth_signature\']);\n	}\n\n	return OAuthUtil::build_http_query($params);\n  }\n\n  /**\n   * Returns the base string of this request\n   *\n   * The base string defined as the method, the url\n   * and the parameters (normalized), each urlencoded\n   * and the concated with &.\n   */\n  public function get_signature_base_string() {\n	$parts = array(\n	  $this->get_normalized_http_method(),\n	  $this->get_normalized_http_url(),\n	  $this->get_signable_parameters()\n	);\n\n	$parts = OAuthUtil::urlencode_rfc3986($parts);\n\n	return implode(\'&\', $parts);\n  }\n\n  /**\n   * just uppercases the http method\n   */\n  public function get_normalized_http_method() {\n	return strtoupper($this->http_method);\n  }\n\n  /**\n   * parses the url and rebuilds it to be\n   * scheme://host/path\n   */\n  public function get_normalized_http_url() {\n	$parts = parse_url($this->http_url);\n\n	$port = @$parts[\'port\'];\n	$scheme = $parts[\'scheme\'];\n	$host = $parts[\'host\'];\n	$path = @$parts[\'path\'];\n\n	$port or $port = ($scheme == \'https\') ? \'443\' : \'80\';\n\n	if (($scheme == \'https\' && $port != \'443\')\n		|| ($scheme == \'http\' && $port != \'80\')) {\n	  $host = \"$host:$port\";\n	}\n	return \"$scheme://$host$path\";\n  }\n\n  /**\n   * builds a url usable for a GET request\n   */\n  public function to_url() {\n	$post_data = $this->to_postdata();\n	$out = $this->get_normalized_http_url();\n	if ($post_data) {\n	  $out .= \'?\'.$post_data;\n	}\n	return $out;\n  }\n\n  /**\n   * builds the data one would send in a POST request\n   */\n  public function to_postdata() {\n	return OAuthUtil::build_http_query($this->parameters);\n  }\n\n  /**\n   * builds the Authorization: header\n   */\n  public function to_header($realm=null) {\n	$first = true;\n  if($realm) {\n	  $out = \'Authorization: OAuth realm=\"\' . OAuthUtil::urlencode_rfc3986($realm) . \'\"\';\n	  $first = false;\n	} else\n	  $out = \'Authorization: OAuth\';\n\n	$total = array();\n	foreach ($this->parameters as $k => $v) {\n	  if (substr($k, 0, 5) != \"oauth\") continue;\n	  if (is_array($v)) {\n		throw new OAuthException(\'Arrays not supported in headers\');\n	  }\n	  $out .= ($first) ? \' \' : \',\';\n	  $out .= OAuthUtil::urlencode_rfc3986($k) .\n			  \'=\"\' .\n			  OAuthUtil::urlencode_rfc3986($v) .\n			  \'\"\';\n	  $first = false;\n	}\n	return $out;\n  }\n\n  public function __toString() {\n	return $this->to_url();\n  }\n\n\n  public function sign_request($signature_method, $consumer, $token) {\n	$this->set_parameter(\n	  \"oauth_signature_method\",\n	  $signature_method->get_name(),\n	  false\n	);\n	$signature = $this->build_signature($signature_method, $consumer, $token);\n	$this->set_parameter(\"oauth_signature\", $signature, false);\n  }\n\n  public function build_signature($signature_method, $consumer, $token) {\n	$signature = $signature_method->build_signature($this, $consumer, $token);\n	return $signature;\n  }\n\n  /**\n   * util function: current timestamp\n   */\n  private static function generate_timestamp() {\n	return time();\n  }\n\n  /**\n   * util function: current nonce\n   */\n  private static function generate_nonce() {\n	$mt = microtime();\n	$rand = mt_rand();\n\n	return md5($mt . $rand); // md5s look nicer than numbers\n  }\n}\n\nclass OAuthServer {\n  protected $timestamp_threshold = 300; // in seconds, five minutes\n  protected $version = \'1.0\';             // hi blaine\n  protected $signature_methods = array();\n\n  protected $data_store;\n\n  function __construct($data_store) {\n	$this->data_store = $data_store;\n  }\n\n  public function add_signature_method($signature_method) {\n	$this->signature_methods[$signature_method->get_name()] =\n	  $signature_method;\n  }\n\n  // high level functions\n\n  /**\n   * process a request_token request\n   * returns the request token on success\n   */\n  public function fetch_request_token(&$request) {\n	$this->get_version($request);\n\n	$consumer = $this->get_consumer($request);\n\n	// no token required for the initial token request\n	$token = NULL;\n\n	$this->check_signature($request, $consumer, $token);\n\n	// Rev A change\n	$callback = $request->get_parameter(\'oauth_callback\');\n	$new_token = $this->data_store->new_request_token($consumer, $callback);\n\n	return $new_token;\n  }\n\n  /**\n   * process an access_token request\n   * returns the access token on success\n   */\n  public function fetch_access_token(&$request) {\n	$this->get_version($request);\n\n	$consumer = $this->get_consumer($request);\n\n	// requires authorized request token\n	$token = $this->get_token($request, $consumer, \"request\");\n\n	$this->check_signature($request, $consumer, $token);\n\n	// Rev A change\n	$verifier = $request->get_parameter(\'oauth_verifier\');\n	$new_token = $this->data_store->new_access_token($token, $consumer, $verifier);\n\n	return $new_token;\n  }\n\n  /**\n   * verify an api call, checks all the parameters\n   */\n  public function verify_request(&$request) {\n	$this->get_version($request);\n	$consumer = $this->get_consumer($request);\n	$token = $this->get_token($request, $consumer, \"access\");\n	$this->check_signature($request, $consumer, $token);\n	return array($consumer, $token);\n  }\n\n  // Internals from here\n  /**\n   * version 1\n   */\n  private function get_version(&$request) {\n	$version = $request->get_parameter(\"oauth_version\");\n	if (!$version) {\n	  // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.\n	  // Chapter 7.0 (\"Accessing Protected Ressources\")\n	  $version = \'1.0\';\n	}\n	if ($version !== $this->version) {\n	  throw new OAuthException(\"OAuth version \'$version\' not supported\");\n	}\n	return $version;\n  }\n\n  /**\n   * figure out the signature with some defaults\n   */\n  private function get_signature_method(&$request) {\n	$signature_method =\n		@$request->get_parameter(\"oauth_signature_method\");\n\n	if (!$signature_method) {\n	  // According to chapter 7 (\"Accessing Protected Ressources\") the signature-method\n	  // parameter is required, and we can\'t just fallback to PLAINTEXT\n	  throw new OAuthException(\'No signature method parameter. This parameter is required\');\n	}\n\n	if (!in_array($signature_method,\n				  array_keys($this->signature_methods))) {\n	  throw new OAuthException(\n		\"Signature method \'$signature_method\' not supported \" .\n		\"try one of the following: \" .\n		implode(\", \", array_keys($this->signature_methods))\n	  );\n	}\n	return $this->signature_methods[$signature_method];\n  }\n\n  /**\n   * try to find the consumer for the provided request\'s consumer key\n   */\n  private function get_consumer(&$request) {\n	$consumer_key = @$request->get_parameter(\"oauth_consumer_key\");\n	if (!$consumer_key) {\n	  throw new OAuthException(\"Invalid consumer key\");\n	}\n\n	$consumer = $this->data_store->lookup_consumer($consumer_key);\n	if (!$consumer) {\n	  throw new OAuthException(\"Invalid consumer\");\n	}\n\n	return $consumer;\n  }\n\n  /**\n   * try to find the token for the provided request\'s token key\n   */\n  private function get_token(&$request, $consumer, $token_type=\"access\") {\n	$token_field = @$request->get_parameter(\'oauth_token\');\n	$token = $this->data_store->lookup_token(\n	  $consumer, $token_type, $token_field\n	);\n	if (!$token) {\n	  throw new OAuthException(\"Invalid $token_type token: $token_field\");\n	}\n	return $token;\n  }\n\n  /**\n   * all-in-one function to check the signature on a request\n   * should guess the signature method appropriately\n   */\n  private function check_signature(&$request, $consumer, $token) {\n	// this should probably be in a different method\n	$timestamp = @$request->get_parameter(\'oauth_timestamp\');\n	$nonce = @$request->get_parameter(\'oauth_nonce\');\n\n	$this->check_timestamp($timestamp);\n	$this->check_nonce($consumer, $token, $nonce, $timestamp);\n\n	$signature_method = $this->get_signature_method($request);\n\n	$signature = $request->get_parameter(\'oauth_signature\');\n	$valid_sig = $signature_method->check_signature(\n	  $request,\n	  $consumer,\n	  $token,\n	  $signature\n	);\n\n	if (!$valid_sig) {\n	  throw new OAuthException(\"Invalid signature\");\n	}\n  }\n\n  /**\n   * check that the timestamp is new enough\n   */\n  private function check_timestamp($timestamp) {\n	if( ! $timestamp )\n	  throw new OAuthException(\n		\'Missing timestamp parameter. The parameter is required\'\n	  );\n\n	// verify that timestamp is recentish\n	$now = time();\n	if (abs($now - $timestamp) > $this->timestamp_threshold) {\n	  throw new OAuthException(\n		\"Expired timestamp, yours $timestamp, ours $now\"\n	  );\n	}\n  }\n\n  /**\n   * check that the nonce is not repeated\n   */\n  private function check_nonce($consumer, $token, $nonce, $timestamp) {\n	if( ! $nonce )\n	  throw new OAuthException(\n		\'Missing nonce parameter. The parameter is required\'\n	  );\n\n	// verify that the nonce is uniqueish\n	$found = $this->data_store->lookup_nonce(\n	  $consumer,\n	  $token,\n	  $nonce,\n	  $timestamp\n	);\n	if ($found) {\n	  throw new OAuthException(\"Nonce already used: $nonce\");\n	}\n  }\n\n}\n\nclass OAuthDataStore {\n  function lookup_consumer($consumer_key) {\n	// implement me\n  }\n\n  function lookup_token($consumer, $token_type, $token) {\n	// implement me\n  }\n\n  function lookup_nonce($consumer, $token, $nonce, $timestamp) {\n	// implement me\n  }\n\n  function new_request_token($consumer, $callback = null) {\n	// return a new token attached to this consumer\n  }\n\n  function new_access_token($token, $consumer, $verifier = null) {\n	// return a new access token attached to this consumer\n	// for the user associated with this token if the request token\n	// is authorized\n	// should also invalidate the request token\n  }\n\n}\n\nclass OAuthUtil {\n  public static function urlencode_rfc3986($input) {\n  if (is_array($input)) {\n	return array_map(array(\'OAuthUtil\', \'urlencode_rfc3986\'), $input);\n  } else if (is_scalar($input)) {\n	return str_replace(\n	  \'+\',\n	  \' \',\n	  str_replace(\'%7E\', \'~\', rawurlencode($input))\n	);\n  } else {\n	return \'\';\n  }\n}\n\n\n  // This decode function isn\'t taking into consideration the above\n  // modifications to the encoding process. However, this method doesn\'t\n  // seem to be used anywhere so leaving it as is.\n  public static function urldecode_rfc3986($string) {\n	return urldecode($string);\n  }\n\n  // Utility function for turning the Authorization: header into\n  // parameters, has to do some unescaping\n  // Can filter out any non-oauth parameters if needed (default behaviour)\n  public static function split_header($header, $only_allow_oauth_parameters = true) {\n	$pattern = \'/(([-_a-z]*)=(\"([^\"]*)\"|([^,]*)),?)/\';\n	$offset = 0;\n	$params = array();\n	while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {\n	  $match = $matches[0];\n	  $header_name = $matches[2][0];\n	  $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];\n	  if (preg_match(\'/^oauth_/\', $header_name) || !$only_allow_oauth_parameters) {\n		$params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);\n	  }\n	  $offset = $match[1] + strlen($match[0]);\n	}\n\n	if (isset($params[\'realm\'])) {\n	  unset($params[\'realm\']);\n	}\n\n	return $params;\n  }\n\n  // helper to try to sort out headers for people who aren\'t running apache\n  public static function get_headers() {\n	if (function_exists(\'apache_request_headers\')) {\n	  // we need this to get the actual Authorization: header\n	  // because apache tends to tell us it doesn\'t exist\n	  $headers = apache_request_headers();\n\n	  // sanitize the output of apache_request_headers because\n	  // we always want the keys to be Cased-Like-This and arh()\n	  // returns the headers in the same case as they are in the\n	  // request\n	  $out = array();\n	  foreach( $headers AS $key => $value ) {\n		$key = str_replace(\n			\" \",\n			\"-\",\n			ucwords(strtolower(str_replace(\"-\", \" \", $key)))\n		  );\n		$out[$key] = $value;\n	  }\n	} else {\n	  // otherwise we don\'t have apache and are just going to have to hope\n	  // that $_SERVER actually contains what we need\n	  $out = array();\n	  if( isset($_SERVER[\'CONTENT_TYPE\']) )\n		$out[\'Content-Type\'] = $_SERVER[\'CONTENT_TYPE\'];\n	  if( isset($_ENV[\'CONTENT_TYPE\']) )\n		$out[\'Content-Type\'] = $_ENV[\'CONTENT_TYPE\'];\n\n	  foreach ($_SERVER as $key => $value) {\n		if (substr($key, 0, 5) == \"HTTP_\") {\n		  // this is chaos, basically it is just there to capitalize the first\n		  // letter of every word that is not an initial HTTP and strip HTTP\n		  // code from przemek\n		  $key = str_replace(\n			\" \",\n			\"-\",\n			ucwords(strtolower(str_replace(\"_\", \" \", substr($key, 5))))\n		  );\n		  $out[$key] = $value;\n		}\n	  }\n	}\n	return $out;\n  }\n\n  // This function takes a input like a=b&a=c&d=e and returns the parsed\n  // parameters like this\n  // array(\'a\' => array(\'b\',\'c\'), \'d\' => \'e\')\n  public static function parse_parameters( $input ) {\n	if (!isset($input) || !$input) return array();\n\n	$pairs = explode(\'&\', $input);\n\n	$parsed_parameters = array();\n	foreach ($pairs as $pair) {\n	  $split = explode(\'=\', $pair, 2);\n	  $parameter = OAuthUtil::urldecode_rfc3986($split[0]);\n	  $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : \'\';\n\n	  if (isset($parsed_parameters[$parameter])) {\n		// We have already recieved parameter(s) with this name, so add to the list\n		// of parameters with this name\n\n		if (is_scalar($parsed_parameters[$parameter])) {\n		  // This is the first duplicate, so transform scalar (string) into an array\n		  // so we can add the duplicates\n		  $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);\n		}\n\n		$parsed_parameters[$parameter][] = $value;\n	  } else {\n		$parsed_parameters[$parameter] = $value;\n	  }\n	}\n	return $parsed_parameters;\n  }\n\n  public static function build_http_query($params) {\n	if (!$params) return \'\';\n\n	// Urlencode both keys and values\n	$keys = OAuthUtil::urlencode_rfc3986(array_keys($params));\n	$values = OAuthUtil::urlencode_rfc3986(array_values($params));\n	$params = array_combine($keys, $values);\n\n	// Parameters are sorted by name, using lexicographical byte value ordering.\n	// Ref: Spec: 9.1.1 (1)\n	uksort($params, \'strcmp\');\n\n	$pairs = array();\n	foreach ($params as $parameter => $value) {\n	  if (is_array($value)) {\n		// If two or more parameters share the same name, they are sorted by their value\n		// Ref: Spec: 9.1.1 (1)\n		natsort($value);\n		foreach ($value as $duplicate_value) {\n		  $pairs[] = $parameter . \'=\' . $duplicate_value;\n		}\n	  } else {\n		$pairs[] = $parameter . \'=\' . $value;\n	  }\n	}\n	// For each parameter, the name is separated from the corresponding value by an \'=\' character (ASCII code 61)\n	// Each name-value pair is separated by an \'&\' character (ASCII code 38)\n	return implode(\'&\', $pairs);\n  }\n}', 'de92afe81c0a751e355cf43d5728647b', '', '', 5, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('arc_twitter_intents', 0, 'Andy Carter', 'http://redhotchilliproject.com/', '1.1', 'Twitter Web Intents', '	<h1 class=\"title\"><span class=\"caps\">TXP</span> Tweet Intents</h1>\n\n	<p>arc_twitter_intents requires arc_twitter v3 or higher to work.</p>\n\n	<h2 class=\"section\">The arc_twitter_intent_follow tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_intent_follow&gt;Follow&lt;/txp:arc_twitter_intent_follow&gt;</p>\n\n	<h3>Usage</h3>\n\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n			<th>Example</th>\n		</tr>\n		<tr>\n			<td>user</td>\n			<td>Twitter user name to follow</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>user_id</td>\n			<td>Twitter user id to follow (overrides the user attribute)</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\">The arc_twitter_intent_favorite tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_intent_favorite&gt;Favorite&lt;/txp:arc_twitter_intent_favorite&gt;</p>\n\n	<h3>Usage</h3>\n\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n			<th>Example</th>\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>Tweet id to favourite</td>\n			<td> <em>article&#8217;s associated tweet from arc_twitter</em></td>\n		</tr>\n		<tr>\n			<td>user</td>\n			<td>Twitter user name related to tweet</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>related</td>\n			<td>Comma separated list of related Twitter users to recommend following</td>\n			<td> <em>unset</em></td>\n			<td>related=&quot;textpattern, twitter&quot;</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\">The arc_twitter_intent_retweet tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_intent_retweet&gt;Retweet&lt;/txp:arc_twitter_intent_retweet&gt;</p>\n\n	<h3>Usage</h3>\n\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n			<th>Example</th>\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>Tweet id to retweet</td>\n			<td> <em>article&#8217;s associated tweet from arc_twitter</em></td>\n		</tr>\n		<tr>\n			<td>user</td>\n			<td>Twitter user name related to tweet</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>related</td>\n			<td>Comma separated list of related Twitter users to recommend following</td>\n			<td> <em>unset</em></td>\n			<td>related=&quot;textpattern, twitter&quot;</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\">The arc_twitter_intent_reply tag</h2>\n\n	<h3>Syntax</h3>\n\n	<p>&lt;txp:arc_twitter_intent_reply&gt;Reply&lt;/txp:arc_twitter_intent_reply&gt;</p>\n\n	<h3>Usage</h3>\n\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n			<th>Example</th>\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>Tweet id to reply to</td>\n			<td> <em>article&#8217;s associated tweet from arc_twitter</em></td>\n		</tr>\n		<tr>\n			<td>user</td>\n			<td>Twitter user name related to tweet (used for recommended accounts)</td>\n			<td> <em>arc_twitter username</em></td>\n			<td>user=&quot;drmonkeyninja&quot;</td>\n		</tr>\n		<tr>\n			<td>related</td>\n			<td>Comma separated list of related Twitter users to recommend following</td>\n			<td> <em>unset</em></td>\n			<td>related=&quot;textpattern, twitter&quot;</td>\n		</tr>\n		<tr>\n			<td>text</td>\n			<td>Set some text for the reply tweet</td>\n			<td> <em>unset</em></td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\">Language</h2>\n\n	<p>All the arc_twitter_intents tags have an optional &#8220;lang&#8221; attribute for setting the language of the Twitter interface. The following values/languages are available:-</p>\n\n	<table>\n		<tr>\n			<th>Attribute value</th>\n			<th>Language</th>\n		</tr>\n		<tr>\n			<td>en</td>\n			<td>English (default)</td>\n		</tr>\n		<tr>\n			<td>fr</td>\n			<td>French</td>\n		</tr>\n		<tr>\n			<td>it</td>\n			<td>Italian</td>\n		</tr>\n		<tr>\n			<td>es</td>\n			<td>Spanish</td>\n		</tr>\n		<tr>\n			<td>ko</td>\n			<td>Korean</td>\n		</tr>\n		<tr>\n			<td>jp</td>\n			<td>Japanese</td>\n		</tr>\n	</table>\n\n	<h2 class=\"section\">JavaScript</h2>\n\n	<p>All the arc_twitter_intents tags have two optional attributes that relate to the inclusion of the Twitter widget JavaScript.</p>\n\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n			<th>Example</th>\n		</tr>\n		<tr>\n			<td>include_js</td>\n			<td>Include the Twitter widget JavaScript</td>\n			<td>true</td>\n			<td>include_js=&quot;false&quot;</td>\n		</tr>\n		<tr>\n			<td>optimise_js</td>\n			<td>Optimise the JavaScript by using a local script rather than that hosted on the Twitter site</td>\n			<td>false</td>\n			<td>optimise_js=&quot;true&quot;</td>\n		</tr>\n	</table>\n\n	<p><span class=\"caps\">TXP</span> Tweet will only include the widget JavaScript once on a page regardless of the number of tags used that include it.</p>\n\n', 'global $prefs,$txpcfg;\n\n// arc_twitter_intents requires arc_twitter v3 or higher\nrequire_plugin(\'arc_twitter\');\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'_arc_twitter_auto_enable\', \'plugin_lifecycle.arc_twitter_intents\', \'installed\');\n}\n\nfunction arc_twitter_intent_follow($atts, $thing=null)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'user_id\'   => \'\',\n        \'lang\'      => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'class\'     => \'\'\n    ),$atts));\n\n    $q = ($user_id) ? \'user_id=\'.$user_id : \'screen_name=\'.$user;\n\n    $lang = _arc_twitter_intents_lang($lang);\n    $q .= \'&amp;lang=\'.urlencode($lang);\n\n    $thing = ($thing===null) ? \'Follow\' : parse($thing);\n\n    $html = href($thing,\'http://twitter.com/intent/user?\'.$q\n      , \' class=\"\'.$class.\'\"\');\n\n    $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n    return $js.$html;\n}\n\nfunction arc_twitter_intent_favorite($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      $q = \'related=\'.$user;\n      if ($related) $q .= urlencode(($q?\',\':\'related=\').$related);\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q .= \'&amp;tweet_id=\'.$id;\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Favorite\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/favorite?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\nfunction arc_twitter_intent_retweet($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      $q = \'related=\'.$user;\n      if ($related) $q .= urlencode(($q?\',\':\'related=\').$related);\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q .= \'&amp;tweet_id=\'.$id;\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Retweet\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/retweet?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\nfunction arc_twitter_intent_reply($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'text\'      => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q = \'in_reply_to=\'.$id;\n\n      if ($user) {\n        $q .= \'&amp;related=\'.urlencode($user);\n      }\n      if ($related) {\n        $q .= urlencode(($user?\',\':\'&related=\').$related);\n      }\n      if ($text) {\n        $q .= \'&amp;text=\'.urlencode($text);\n      }\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Reply\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/tweet?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\n/*\n * Set the intent language\n */\nfunction _arc_twitter_intents_lang($lang=\'en\')\n{\n  $lang = strtolower($lang);\n  $langs = array(\'en\', \'it\', \'es\', \'fr\', \'ko\', \'jp\');\n  return (in_array($lang,$langs)) ? $lang : \'en\';\n}\n\n', 'global $prefs,$txpcfg;\n\n// arc_twitter_intents requires arc_twitter v3 or higher\nrequire_plugin(\'arc_twitter\');\n\nif (@txpinterface == \'admin\') {\n    register_callback(\'_arc_twitter_auto_enable\', \'plugin_lifecycle.arc_twitter_intents\', \'installed\');\n}\n\nfunction arc_twitter_intent_follow($atts, $thing=null)\n{\n    global $prefs;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'user_id\'   => \'\',\n        \'lang\'      => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'class\'     => \'\'\n    ),$atts));\n\n    $q = ($user_id) ? \'user_id=\'.$user_id : \'screen_name=\'.$user;\n\n    $lang = _arc_twitter_intents_lang($lang);\n    $q .= \'&amp;lang=\'.urlencode($lang);\n\n    $thing = ($thing===null) ? \'Follow\' : parse($thing);\n\n    $html = href($thing,\'http://twitter.com/intent/user?\'.$q\n      , \' class=\"\'.$class.\'\"\');\n\n    $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n    return $js.$html;\n}\n\nfunction arc_twitter_intent_favorite($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      $q = \'related=\'.$user;\n      if ($related) $q .= urlencode(($q?\',\':\'related=\').$related);\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q .= \'&amp;tweet_id=\'.$id;\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Favorite\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/favorite?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\nfunction arc_twitter_intent_retweet($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      $q = \'related=\'.$user;\n      if ($related) $q .= urlencode(($q?\',\':\'related=\').$related);\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q .= \'&amp;tweet_id=\'.$id;\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Retweet\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/retweet?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\nfunction arc_twitter_intent_reply($atts, $thing=null)\n{\n    global $prefs, $thisarticle;\n\n    extract(lAtts(array(\n        \'user\'      => $prefs[\'arc_twitter_user\'],\n        \'related\'   => \'\',\n        \'text\'      => \'\',\n        \'include_js\'=> true,\n        \'optimise_js\' => false,\n        \'lang\'      => \'\',\n        \'id\'        => \'\',\n        \'class\'     => \'\'\n    ),$atts));\n\n    if ($id || $thisarticle[\'thisid\']) {\n\n      if (!$id) {\n        $row = safe_row(\"tweet_id\"\n        , \'arc_twitter\', \"article_id={$thisarticle[\'thisid\']}\");\n        if (!$id = $row[\'tweet_id\']) return false;\n      }\n\n      $q = \'in_reply_to=\'.$id;\n\n      if ($user) {\n        $q .= \'&amp;related=\'.urlencode($user);\n      }\n      if ($related) {\n        $q .= urlencode(($user?\',\':\'&related=\').$related);\n      }\n      if ($text) {\n        $q .= \'&amp;text=\'.urlencode($text);\n      }\n\n      $lang = _arc_twitter_intents_lang($lang);\n      $q .= \'&amp;lang=\'.urlencode($lang);\n\n      $thing = ($thing===null) ? \'Reply\' : parse($thing);\n\n      $html = href($thing,\'http://twitter.com/intent/tweet?\'.$q\n        , \' class=\"\'.$class.\'\"\');\n\n      $js = ($include_js) ? _arc_twitter_widget_js($optimise_js?true:false) : \'\';\n\n      return $js.$html;\n\n    }\n\n    return false;\n}\n\n/*\n * Set the intent language\n */\nfunction _arc_twitter_intents_lang($lang=\'en\')\n{\n  $lang = strtolower($lang);\n  $langs = array(\'en\', \'it\', \'es\', \'fr\', \'ko\', \'jp\');\n  return (in_array($lang,$langs)) ? $lang : \'en\';\n}\n\n', 'd765cc14ec29b2e621cfecbc6d0a5f40', '', '', 0, 5, 3),
('pro_googlemapv3', 0, 'Hilary Quinn', 'http://www.proximowebdesign.ie', '0.1.1', 'Google Maps Plugin based on JavaScript API V3', '<h1>1. Insert plugin tag in the head of your page:</h1>\n\n<div class=\"code\">&lt;txp:pro_googlemapv3 apikey=\"pasteyourapikeyhere\" zoom=\"14\" center=\"51.896777,-8.4763924\" loc1=\"51.896777,-8.4763924\" heading1=\"My Company Name\" bodycontent1=\"My First Location\" loc2=\"51.898281,-8.475543\" heading2=\"My Company Name\" bodycontent2=\"My Second Location\" /&gt;</div>\n\n<p>I recommend only executing the code <strong>on the page you need it:</strong></p>\n\n<div class=\"code\">&lt;txp:if_section name=\"contact\"&gt;\n&lt;txp:pro_googlemapv3 apikey=\"pasteyourapikeyhere\" zoom=\"14\" center=\"51.896777,-8.4763924\" loc1=\"51.896777,-8.4763924\" heading1=\"My Company Name\" bodycontent1=\"My First Location\" loc2=\"51.898281,-8.475543\" heading2=\"My Company Name\" bodycontent2=\"My Second Location\" /&gt;\n&lt;/txp:if_section&gt;</div>\n\n<p>The above code will place 2 markers on one map, to only <strong>insert a single marker</strong> use the below code:</p>\n\n<div class=\"code\">&lt;txp:if_section name=\"contact\"&gt;\n&lt;txp:pro_googlemapv3 apikey=\"pasteyourapikeyhere\" zoom=\"14\" center=\"51.896777,-8.4763924\" loc1=\"51.896777,-8.4763924\" heading1=\"My Company Name\" bodycontent1=\"My Location\" /&gt;\n&lt;/txp:if_section&gt;</div>\n\n<h1>2. Insert html code in page article:</h1>\n\n<div class=\"code\">&lt;div id=\"pro_map_canvas\"  &gt;&lt;/div&gt;</div>\n\n<h1>3. CSS</h1>\n\n<p>In your main stylesheet css:</p>\n\n<div class=\"code\">#pro_map_canvas{\n    width: 100%;\n    height: 360px;\n    z-index: 1;\n    margin-bottom: 10px;\n    position: relative;\n}</div>\n\n<p>In your mobile stylesheet css:</p>\n\n<div class=\"code\">#pro_map_canvas{\n    max-width: 100%;\n    height: 300px;\n    margin-bottom: 10px;\n    position: relative;\n}\n<br />\n#pro_map_canvas img{\n    max-width: none;\n    background: transparent;\n}</div>\n\n<p>Feel free to change width,height,z-index as you see fit. This plugin works on mobile devices any plugin based on Google Maps V2 will not display on mobile and is deprecated, so it\'s a good idea to update now!</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\nh1{font-weight: bold; margin: 20px 0;}\np{margin: 10px 0;}\npre{padding: 10px;}\n.code{background: #F4F3ED; border-top: 1px dotted #DBD7C5; border-bottom: 1px dotted #DBD7C5; padding: 10px;}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function pro_googlemapv3($atts, $thing=NULL){\n\nextract(lAtts(array(\n                \'apikey\'        => \'\',\n                \'zoom\'	        => \'14\',\n                \'center\'	=> \'\',\n                \'loc1\'		=> \'\',\n                \'loc2\'		=> \'\',\n                \'heading1\'	=> \'\',\n                \'heading2\'	=> \'\',\n                \'bodycontent1\'	=> \'\',\n                \'bodycontent2\'	=> \'\'		\n	), $atts));\n\n$apikey = urlencode($apikey);\n\n$center = escape_js(txpspecialchars($center));\n$loc1 = escape_js(txpspecialchars($loc1));\n$loc2 = escape_js(txpspecialchars($loc2));\n$heading1 = escape_js(txpspecialchars($heading1));\n$heading2 = escape_js(txpspecialchars($heading2));\n$bodycontent1 = escape_js(txpspecialchars($bodycontent1));\n$bodycontent2 = escape_js(txpspecialchars($bodycontent2));\n\nreturn \"\n<script type=\\\"text/javascript\\\" src=\\\"http://maps.googleapis.com/maps/api/js?key=$apikey&amp;sensor=false\\\"></script>\n\n<script type=\\\"text/javascript\\\">\n\nvar chosenloc = new google.maps.LatLng($center);\nvar loc1 = new google.maps.LatLng($loc1);\nvar loc2 = new google.maps.LatLng($loc2);\nvar marker;\nvar map;\n\nfunction initialize() {\n\n  var mapOptions = {\n    zoom: $zoom,\n    mapTypeId: google.maps.MapTypeId.ROADMAP,\n    center: chosenloc\n  };\n\n  map = new google.maps.Map(document.getElementById(\\\"pro_map_canvas\\\"),\n      mapOptions);\n\n  var contentString1 =\n    \'<div id=\\\"pro_siteNotice\\\">\'+\n    \'<h2 id=\\\"pro_firstHeading\\\" class=\\\"pro_firstHeading\\\">$heading1\'+\n    \'</h2>\'+\n    \'<div id=\\\"pro_bodyContent\\\">\'+\n    \'<p>$bodycontent1\' +\n    \'</p>\'+\n    \'</div>\'+\n    \'</div>\';\n\n  var infowindow1 = new google.maps.InfoWindow({\n    content: contentString1\n  });\n\n  var contentString2 = \'<div id=\\\"content\\\">\'+\n    \'<div id=\\\"pro_siteNotice\\\">\'+\n    \'<h2 id=\\\"pro_firstHeading\\\" class=\\\"pro_firstHeading\\\">$heading2\'+\n    \'</h2>\'+\n    \'<div id=\\\"pro_bodyContent\\\">\'+\n    \'<p>$bodycontent2\' +\n    \'</p>\'+\n    \'</div>\'+\n    \'</div>\';\n\n  var infowindow2 = new google.maps.InfoWindow({\n    content: contentString2\n  });\n\n  marker = new google.maps.Marker({\n    map:map,\n    draggable:false,\n    title:\\\"$heading1\\\",\n    animation: google.maps.Animation.DROP,\n    position: loc1 \n  });\n\n  google.maps.event.addListener(marker, \'click\', function() {\n    infowindow1.open(map,marker);\n  });\n\n  marker1 = new google.maps.Marker({\n    map:map,\n    draggable:false,\n    title:\\\"$heading2\\\",\n    animation: google.maps.Animation.DROP,\n    position: loc2\n });\n\n  google.maps.event.addListener(marker1, \'click\', function() {\n    infowindow2.open(map,marker1);\n  });\n\n}\n\n</script>\n\n<script type=\\\"text/javascript\\\">\n  google.maps.event.addDomListener(window, \'load\', initialize);\n</script>\n\";\n}', 'function pro_googlemapv3($atts, $thing=NULL){\n\nextract(lAtts(array(\n                \'apikey\'        => \'\',\n                \'zoom\'	        => \'14\',\n                \'center\'	=> \'\',\n                \'loc1\'		=> \'\',\n                \'loc2\'		=> \'\',\n                \'heading1\'	=> \'\',\n                \'heading2\'	=> \'\',\n                \'bodycontent1\'	=> \'\',\n                \'bodycontent2\'	=> \'\'		\n	), $atts));\n\n$apikey = urlencode($apikey);\n\n$center = escape_js(txpspecialchars($center));\n$loc1 = escape_js(txpspecialchars($loc1));\n$loc2 = escape_js(txpspecialchars($loc2));\n$heading1 = escape_js(txpspecialchars($heading1));\n$heading2 = escape_js(txpspecialchars($heading2));\n$bodycontent1 = escape_js(txpspecialchars($bodycontent1));\n$bodycontent2 = escape_js(txpspecialchars($bodycontent2));\n\nreturn \"\n<script type=\\\"text/javascript\\\" src=\\\"http://maps.googleapis.com/maps/api/js?key=$apikey&amp;sensor=false\\\"></script>\n\n<script type=\\\"text/javascript\\\">\n\nvar chosenloc = new google.maps.LatLng($center);\nvar loc1 = new google.maps.LatLng($loc1);\nvar loc2 = new google.maps.LatLng($loc2);\nvar marker;\nvar map;\n\nfunction initialize() {\n\n  var mapOptions = {\n    zoom: $zoom,\n    mapTypeId: google.maps.MapTypeId.ROADMAP,\n    center: chosenloc\n  };\n\n  map = new google.maps.Map(document.getElementById(\\\"pro_map_canvas\\\"),\n      mapOptions);\n\n  var contentString1 =\n    \'<div id=\\\"pro_siteNotice\\\">\'+\n    \'<h2 id=\\\"pro_firstHeading\\\" class=\\\"pro_firstHeading\\\">$heading1\'+\n    \'</h2>\'+\n    \'<div id=\\\"pro_bodyContent\\\">\'+\n    \'<p>$bodycontent1\' +\n    \'</p>\'+\n    \'</div>\'+\n    \'</div>\';\n\n  var infowindow1 = new google.maps.InfoWindow({\n    content: contentString1\n  });\n\n  var contentString2 = \'<div id=\\\"content\\\">\'+\n    \'<div id=\\\"pro_siteNotice\\\">\'+\n    \'<h2 id=\\\"pro_firstHeading\\\" class=\\\"pro_firstHeading\\\">$heading2\'+\n    \'</h2>\'+\n    \'<div id=\\\"pro_bodyContent\\\">\'+\n    \'<p>$bodycontent2\' +\n    \'</p>\'+\n    \'</div>\'+\n    \'</div>\';\n\n  var infowindow2 = new google.maps.InfoWindow({\n    content: contentString2\n  });\n\n  marker = new google.maps.Marker({\n    map:map,\n    draggable:false,\n    title:\\\"$heading1\\\",\n    animation: google.maps.Animation.DROP,\n    position: loc1 \n  });\n\n  google.maps.event.addListener(marker, \'click\', function() {\n    infowindow1.open(map,marker);\n  });\n\n  marker1 = new google.maps.Marker({\n    map:map,\n    draggable:false,\n    title:\\\"$heading2\\\",\n    animation: google.maps.Animation.DROP,\n    position: loc2\n });\n\n  google.maps.event.addListener(marker1, \'click\', function() {\n    infowindow2.open(map,marker1);\n  });\n\n}\n\n</script>\n\n<script type=\\\"text/javascript\\\">\n  google.maps.event.addDomListener(window, \'load\', initialize);\n</script>\n\";\n}', '409e111f9351564f591e01bca6c668dd', '', '', 1, 5, 0),
('kuo_admin_chat', 0, 'Petri Ikonen', 'https://www.paypal.me/kuopassa', '0.1', 'A minimalistic chat in Textpattern\'s admin-side.', '<p>The admin-side chat should be visible to all Textpattern users, except those who have \"None\" as privileges. Chat should be located at <strong>Admin</strong> &rarr; <strong>Chat</strong>.</p>\n<p>Publishers and Managing Editors can delete messages. Deleted messages are actually not completely removed from database, but they are marked as hidden messages.</p>\n<h3>Keep the chat in sight</h3>\n<p>To <a href=\"index.php?event=kuo_admin_chat\">launch the chat</a> page when a user opens Textpattern, go to <strong>Preferences</strong> &rarr; <strong>Admin</strong> &rarr; <strong>Default administration panel</strong> and select <strong>Chat</strong>.</p>', 'if (strcmp(txpinterface,\'admin\') == 0) {\r\n	\r\n	add_privs(\r\n		\'kuo_admin_chat\',\r\n		\'1,2,3,4,5,6\'\r\n	);\r\n\r\n	register_tab(\r\n		\'admin\',\r\n		\'kuo_admin_chat\',\r\n		\'Chat\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_tab\',\r\n		\'kuo_admin_chat\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_css\',\r\n		\'admin_side\',\r\n		\'head_end\'\r\n	);\r\n\r\n	register_callback(\r\n		\'kuo_admin_chat_enabled\',\r\n		\'plugin_lifecycle.kuo_admin_chat\',\r\n		\'enabled\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_deleted\',\r\n		\'plugin_lifecycle.kuo_admin_chat\',\r\n		\'deleted\'\r\n	);\r\n}\r\n\r\nfunction kuo_admin_chat_enabled() {\r\n	\r\n	$messages_table = safe_create(\r\n		\'kuo_admin_chat\',\r\n		\'\r\n			`row` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,\r\n			`name` varchar(64) NOT NULL,\r\n			`created` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`modified` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1,\r\n			`message` text NOT NULL,\r\n			PRIMARY KEY (`row`)\r\n		\',\r\n		\'\',\r\n		0\r\n	);\r\n	\r\n	$settings_table = safe_create(\r\n		\'kuo_admin_chat_prefs\',\r\n		\'\r\n			`name` varchar(64) NOT NULL,\r\n			`message_limit` tinyint(25) UNSIGNED NOT NULL,\r\n			PRIMARY KEY (`name`)\r\n		\',\r\n		\'\',\r\n		0\r\n	);\r\n	\r\n	if (($messages_table) && ($settings_table)) {\r\n		return true;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_admin_chat_deleted() {\r\n	\r\n	if ((safe_drop(\'kuo_admin_chat\')) && (safe_drop(\'kuo_admin_chat_prefs\'))) {\r\n		return true;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_admin_chat_tab($event,$step) {\r\n	\r\n	global $txp_user;\r\n	\r\n	# BEGINS: $_POST\r\n	if ((isset($_POST)) && (!empty($_POST))) {\r\n		\r\n		# BEGINS: SAVE NEW MESSAGE\r\n		if (\r\n			(isset($_POST[\'message\']))\r\n			&&\r\n			(strlen($_POST[\'message\']) >= 1)\r\n			&&\r\n			(strlen($_POST[\'message\']) <= 10000)\r\n		) {\r\n			\r\n			kuo_admin_chat_save_message(\r\n				$txp_user,\r\n				$_POST[\'message\']\r\n			);\r\n		}\r\n		# ENDS: SAVE NEW MESSAGE\r\n		\r\n		header(\'Location: index.php?event=\'.$event);\r\n		die;\r\n	}\r\n	# ENDS: $_POST\r\n	\r\n	# BEGINS: SPECIAL $_GET\r\n	if ((isset($_GET[\'show\'])) || (isset($_GET[\'delete\']))) {\r\n		\r\n		if (\r\n			(isset($_GET[\'show\']))\r\n			&&\r\n			(in_array($_GET[\'show\'],array(25,50,100)))\r\n		) {\r\n			\r\n			kuo_admin_chat_save_settings(\r\n				$txp_user,\r\n				array(\r\n					\'message_limit\'=>$_GET[\'show\'],\r\n				)\r\n			);\r\n		}\r\n		elseif (\r\n			(isset($_GET[\'delete\']))\r\n			&&\r\n			(is_numeric($_GET[\'delete\']))\r\n		) {\r\n			kuo_admin_chat_delete_message(\r\n				$_GET[\'delete\']\r\n			);\r\n		}\r\n		\r\n		header(\'Location: index.php?event=\'.$event);\r\n		die;\r\n	}\r\n	# ENDS: SPECIAL $_GET\r\n	\r\n	$settings = safe_row(\r\n		\'message_limit\',\r\n		\'kuo_admin_chat_prefs\',\r\n		\'`name` = \"\'.safe_escape($txp_user).\'\"\',\r\n		0\r\n	);\r\n	\r\n	if ((empty($settings)) || (!isset($settings[\'message_limit\']))) {\r\n		\r\n		$settings = array(\r\n			\'message_limit\'=>25\r\n		);\r\n	}\r\n	\r\n	$messages = getRows(\r\n	\'\r\n	SELECT\r\n		`row`,\r\n		`name`,\r\n		`created`,\r\n		`message`\r\n	FROM\r\n		`\'.safe_pfx(\'kuo_admin_chat\').\'`\r\n	WHERE\r\n		`status` = 1\r\n	ORDER BY\r\n		`created` DESC\r\n	LIMIT\r\n		0, \'.$settings[\'message_limit\'],\r\n		0\r\n	);\r\n	\r\n	if ($messages == false) {\r\n		$content = \r\n			\'<div id=\"kuo_admin_chat\">\'.\r\n				kuo_admin_chat_show($messages,$event,$txp_user).\r\n				kuo_admin_chat_post($txp_user).\r\n			\'</div>\';\r\n	}\r\n	else {\r\n		$content = \r\n			\'<div id=\"kuo_admin_chat\">\'.\r\n				kuo_admin_chat_settings_tool($settings[\'message_limit\'],$event).\r\n				kuo_admin_chat_show($messages,$event,$txp_user).\r\n				kuo_admin_chat_post($txp_user).\r\n			\'</div>\'.\r\n			kuo_admin_chat_js();\r\n	}\r\n	\r\n	pagetop(\'Chat\');\r\n	\r\n	echo $content;\r\n}\r\n\r\nfunction kuo_admin_chat_js() {\r\n\r\n	return \'\r\n		<script>\r\n			const messages = document.getElementById(\"kuo_admin_chat_messages\");\r\n			\r\n			if (messages) {\r\n				messages.scrollTop = messages.scrollHeight-messages.clientHeight;\r\n			} \r\n		</script>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_settings_tool($selected = 25,$event) {\r\n	\r\n	$css_id = \' id=\"kuo_admin_chat_selected\"\';\r\n	\r\n	if ($selected == 25) {\r\n		\r\n		$selected = array(\r\n			$css_id,\r\n			null,\r\n			null\r\n		);\r\n	}\r\n	elseif ($selected == 50) {\r\n		\r\n		$selected = array(\r\n			null,\r\n			$css_id,\r\n			null\r\n		);\r\n	}\r\n	else {\r\n		\r\n		$selected = array(\r\n			null,\r\n			null,\r\n			$css_id\r\n		);\r\n	}\r\n	\r\n	$href = \'?event=\'.$event.\'&amp;show=\';\r\n	\r\n	return \'\r\n		<nav>\r\n			<p><a href=\"?event=\'.$event.\'\">Refresh &#8635;</a></p>\r\n			<p>Show max. \r\n			<a href=\"\'.$href.\'25\"\'.$selected[0].\'>25</a>, \r\n			<a href=\"\'.$href.\'50\"\'.$selected[1].\'>50</a>, \r\n			<a href=\"\'.$href.\'100\"\'.$selected[2].\'>100</a> \r\n			messages.</p>\r\n		</nav>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_css() {\r\n	\r\n	echo \'\r\n		<style>\r\n			#kuo_admin_chat {\r\n				font-size: 1.2em;\r\n			}\r\n			#kuo_admin_chat label,\r\n			#kuo_admin_chat button {\r\n				cursor: pointer;\r\n			}\r\n			#kuo_admin_chat form p {\r\n				display: inline-block;\r\n				vertical-align: top;\r\n			}\r\n			#kuo_admin_chat q:before,\r\n			#kuo_admin_chat q:after {\r\n				content: \"\";\r\n			}\r\n			#kuo_admin_chat ol {\r\n				list-style: none;\r\n				display: block;\r\n				overflow: scroll;\r\n				border: 1px solid #ccc;\r\n				padding: 0;\r\n				margin: 0 0 1em 0;\r\n				background: #fff;\r\n				min-height: 150px;\r\n				max-height: 350px;\r\n			}\r\n			#kuo_admin_chat li {\r\n				display: block;\r\n				clear: both;\r\n				padding: 0.4em 0.7em;\r\n				overflow: auto;\r\n			}\r\n			#kuo_admin_chat li:hover {\r\n				background-color: #f0f0f0;\r\n			}\r\n			#kuo_admin_chat span {\r\n				color: gray;\r\n			}\r\n			#kuo_admin_chat span:before {\r\n				content: \"[\";\r\n			}\r\n			#kuo_admin_chat span:after {\r\n				content: \"]\";\r\n			}\r\n			#kuo_admin_chat strong {\r\n				font-weight: 600;\r\n				padding: 0 0.5em;\r\n			}\r\n			#kuo_admin_chat_selected {\r\n				font-weight: 800;\r\n			}\r\n			#kuo_admin_chat nav p {\r\n				display: inline-block;\r\n				vertical-align: top;\r\n				font-size: 1.2em;\r\n			}\r\n			#kuo_admin_chat nav p:first-of-type {\r\n				margin-right: 1em;\r\n			}\r\n			#kuo_admin_chat input {\r\n				min-width: 50px;\r\n				max-width: 90%;\r\n				width: auto;\r\n			}\r\n			#kuo_admin_chat a.delete {\r\n				padding: 0.1em;\r\n			}\r\n			#kuo_admin_chat a.delete:hover,\r\n			#kuo_admin_chat a.delete:focus,\r\n			#kuo_admin_chat a.delete:active {\r\n				text-decoration: none;\r\n				background: cyan;\r\n				color: #fff;\r\n			}\r\n		</style>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_user_privileges($name) {\r\n	\r\n	$privileges = safe_row(\r\n		\'privs\',\r\n		\'txp_users\',\r\n		\'`name` = \"\'.safe_escape($name).\'\" AND `privs` IN (1,2)\',\r\n		0\r\n	);\r\n	\r\n	if (\r\n		(is_array($privileges))\r\n		&&\r\n		(isset($privileges[\'privs\']))\r\n		&&\r\n		(count($privileges) == 1)\r\n	) {\r\n		$privileges = $privileges[\'privs\'];\r\n		\r\n		settype($privileges,\'int\');\r\n	}\r\n	\r\n	return $privileges;\r\n}\r\n\r\nfunction kuo_admin_chat_delete_link($row,$event) {\r\n	\r\n	$href = \'index.php?event=\'.$event.\'&amp;delete=\'.$row;\r\n	\r\n	return \'<a href=\"\'.$href.\'\" class=\"delete\">&#10060;</a> \';\r\n}\r\n\r\nfunction kuo_admin_chat_show($messages = array(),$event,$reader_name) {\r\n	\r\n	$output = null;\r\n	\r\n	# BEGINS: MESSAGES ARRAY\r\n	if ((!empty($messages)) && (is_array($messages))) {\r\n		\r\n		$messages = array_reverse($messages);\r\n		\r\n		$datetime_format = get_pref(\r\n			\'comments_dateformat\',\r\n			\'%c\',\r\n			0\r\n		);\r\n		\r\n		$is_allowed_to_delete = kuo_admin_chat_user_privileges($reader_name);\r\n		\r\n		# BEGINS: FOREACH\r\n		foreach($messages as $messages) {\r\n			\r\n			# BEGINS: ISSET MESSAGE DATA\r\n			if (isset(\r\n				$messages[\'name\'],\r\n				$messages[\'created\'],\r\n				$messages[\'message\'])\r\n			) {\r\n				\r\n				$messages[\'created\'] = strtotime($messages[\'created\']);\r\n				$messages[\'created\'] = \r\n					strftime($datetime_format,$messages[\'created\']);\r\n				\r\n				$messages[\'name\'] = \r\n					htmlentities($messages[\'name\']);\r\n					\r\n				$messages[\'message\'] = \r\n					wordwrap($messages[\'message\'],175,PHP_EOL,false);\r\n				\r\n				$messages[\'message\'] = \r\n					htmlentities($messages[\'message\']);\r\n					\r\n				$messages[\'message\'] = \r\n					str_replace(PHP_EOL,\'<br />\',$messages[\'message\']);\r\n					\r\n				# BEGINS: THIS READER IS ALLOWED TO DELETE ANY MESSAGE\r\n				if ($is_allowed_to_delete) {\r\n					\r\n					$delete_link = kuo_admin_chat_delete_link(\r\n						$messages[\'row\'],\r\n						$event\r\n					);\r\n				}\r\n				# ENDS: THIS READER IS ALLOWED TO DELETE ANY MESSAGE\r\n				else {\r\n					$delete_link = null;\r\n				}\r\n				\r\n				$output .= \r\n					\'<li>\'.\r\n						$delete_link.\r\n						\'<span>\'.$messages[\'created\'].\'</span> \'.\r\n						\'<strong>\'.$messages[\'name\'].\':</strong> \'.\r\n						\'<q cite=\"\'.$messages[\'name\'].\'\">\'.\r\n							$messages[\'message\'].\r\n						\'</q>\'.\r\n					\'</li>\'\r\n				;\r\n			}\r\n			# ENDS: ISSET MESSAGE DATA\r\n		}\r\n		# ENDS: FOREACH\r\n		\r\n		$output = \'<ol id=\"kuo_admin_chat_messages\">\'.$output.\'</ol>\';\r\n	}\r\n	# ENDS: MESSAGES ARRAY\r\n	else {\r\n		$output = \'<p><mark>No messages.</mark></p>\';\r\n	}\r\n	\r\n	return $output;\r\n}\r\n\r\nfunction kuo_admin_chat_post($sender_name) {\r\n	\r\n	$sender_name = htmlentities($sender_name);\r\n	\r\n	return \'\r\n		<form method=\"post\" autocomplete=\"off\">\r\n			<fieldset>\r\n				<legend>Write a new message</legend>\r\n				<p><label \r\n				for=\"kuo_new_message\">\'.$sender_name.\':&nbsp;</label>\'.finput(\r\n					\'text\',\r\n					\'message\',\r\n					\'\',\r\n					\'\',\r\n					\'\',\r\n					\'\',\r\n					100,\r\n					\'\',\r\n					\'kuo_new_message\',\r\n					false,\r\n					true,\r\n					\'\'\r\n				).\'</p>\r\n				<p><button type=\"submit\">\'.gTxt(\'submit\').\'</button></p>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_save_message($name,$message) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($name,$message)) {\r\n		\r\n		$status = safe_insert(\r\n			\'kuo_admin_chat\',\r\n			\'\r\n				`name` = \"\'.safe_escape($name).\'\",\r\n				`created` = \"\'.date(\'Y-m-d H:i:s\').\'\",\r\n				`modified` = \"\'.date(\'Y-m-d H:i:s\').\'\",\r\n				`status` = 1,\r\n				`message` = \"\'.safe_escape($message).\'\"\r\n			\',\r\n			0\r\n		);\r\n	}\r\n\r\n	return $status;\r\n}\r\n\r\nfunction kuo_admin_chat_save_settings($name,$settings) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($name,$settings[\'message_limit\'])) {\r\n		\r\n		$status = safe_upsert(\r\n			\'kuo_admin_chat_prefs\',\r\n			\'`message_limit` = \'.safe_escape($settings[\'message_limit\']),\r\n			\'`name` = \"\'.safe_escape($name).\'\"\',\r\n			0\r\n		);\r\n	}\r\n	\r\n	return $status;\r\n}\r\n\r\nfunction kuo_admin_chat_delete_message($row) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($row)) {\r\n		\r\n		$status = safe_update(\r\n			\'kuo_admin_chat\',\r\n			\'`status` = 0, `modified` = \"\'.date(\'Y-m-d H:i:s\').\'\"\',\r\n			\'`row` = \'.safe_escape($row).\' AND `status` = 1\',\r\n			0\r\n		);\r\n	}\r\n	\r\n	return $status;\r\n}', 'if (strcmp(txpinterface,\'admin\') == 0) {\r\n	\r\n	add_privs(\r\n		\'kuo_admin_chat\',\r\n		\'1,2,3,4,5,6\'\r\n	);\r\n\r\n	register_tab(\r\n		\'admin\',\r\n		\'kuo_admin_chat\',\r\n		\'Chat\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_tab\',\r\n		\'kuo_admin_chat\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_css\',\r\n		\'admin_side\',\r\n		\'head_end\'\r\n	);\r\n\r\n	register_callback(\r\n		\'kuo_admin_chat_enabled\',\r\n		\'plugin_lifecycle.kuo_admin_chat\',\r\n		\'enabled\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_admin_chat_deleted\',\r\n		\'plugin_lifecycle.kuo_admin_chat\',\r\n		\'deleted\'\r\n	);\r\n}\r\n\r\nfunction kuo_admin_chat_enabled() {\r\n	\r\n	$messages_table = safe_create(\r\n		\'kuo_admin_chat\',\r\n		\'\r\n			`row` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,\r\n			`name` varchar(64) NOT NULL,\r\n			`created` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`modified` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1,\r\n			`message` text NOT NULL,\r\n			PRIMARY KEY (`row`)\r\n		\',\r\n		\'\',\r\n		0\r\n	);\r\n	\r\n	$settings_table = safe_create(\r\n		\'kuo_admin_chat_prefs\',\r\n		\'\r\n			`name` varchar(64) NOT NULL,\r\n			`message_limit` tinyint(25) UNSIGNED NOT NULL,\r\n			PRIMARY KEY (`name`)\r\n		\',\r\n		\'\',\r\n		0\r\n	);\r\n	\r\n	if (($messages_table) && ($settings_table)) {\r\n		return true;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_admin_chat_deleted() {\r\n	\r\n	if ((safe_drop(\'kuo_admin_chat\')) && (safe_drop(\'kuo_admin_chat_prefs\'))) {\r\n		return true;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_admin_chat_tab($event,$step) {\r\n	\r\n	global $txp_user;\r\n	\r\n	# BEGINS: $_POST\r\n	if ((isset($_POST)) && (!empty($_POST))) {\r\n		\r\n		# BEGINS: SAVE NEW MESSAGE\r\n		if (\r\n			(isset($_POST[\'message\']))\r\n			&&\r\n			(strlen($_POST[\'message\']) >= 1)\r\n			&&\r\n			(strlen($_POST[\'message\']) <= 10000)\r\n		) {\r\n			\r\n			kuo_admin_chat_save_message(\r\n				$txp_user,\r\n				$_POST[\'message\']\r\n			);\r\n		}\r\n		# ENDS: SAVE NEW MESSAGE\r\n		\r\n		header(\'Location: index.php?event=\'.$event);\r\n		die;\r\n	}\r\n	# ENDS: $_POST\r\n	\r\n	# BEGINS: SPECIAL $_GET\r\n	if ((isset($_GET[\'show\'])) || (isset($_GET[\'delete\']))) {\r\n		\r\n		if (\r\n			(isset($_GET[\'show\']))\r\n			&&\r\n			(in_array($_GET[\'show\'],array(25,50,100)))\r\n		) {\r\n			\r\n			kuo_admin_chat_save_settings(\r\n				$txp_user,\r\n				array(\r\n					\'message_limit\'=>$_GET[\'show\'],\r\n				)\r\n			);\r\n		}\r\n		elseif (\r\n			(isset($_GET[\'delete\']))\r\n			&&\r\n			(is_numeric($_GET[\'delete\']))\r\n		) {\r\n			kuo_admin_chat_delete_message(\r\n				$_GET[\'delete\']\r\n			);\r\n		}\r\n		\r\n		header(\'Location: index.php?event=\'.$event);\r\n		die;\r\n	}\r\n	# ENDS: SPECIAL $_GET\r\n	\r\n	$settings = safe_row(\r\n		\'message_limit\',\r\n		\'kuo_admin_chat_prefs\',\r\n		\'`name` = \"\'.safe_escape($txp_user).\'\"\',\r\n		0\r\n	);\r\n	\r\n	if ((empty($settings)) || (!isset($settings[\'message_limit\']))) {\r\n		\r\n		$settings = array(\r\n			\'message_limit\'=>25\r\n		);\r\n	}\r\n	\r\n	$messages = getRows(\r\n	\'\r\n	SELECT\r\n		`row`,\r\n		`name`,\r\n		`created`,\r\n		`message`\r\n	FROM\r\n		`\'.safe_pfx(\'kuo_admin_chat\').\'`\r\n	WHERE\r\n		`status` = 1\r\n	ORDER BY\r\n		`created` DESC\r\n	LIMIT\r\n		0, \'.$settings[\'message_limit\'],\r\n		0\r\n	);\r\n	\r\n	if ($messages == false) {\r\n		$content = \r\n			\'<div id=\"kuo_admin_chat\">\'.\r\n				kuo_admin_chat_show($messages,$event,$txp_user).\r\n				kuo_admin_chat_post($txp_user).\r\n			\'</div>\';\r\n	}\r\n	else {\r\n		$content = \r\n			\'<div id=\"kuo_admin_chat\">\'.\r\n				kuo_admin_chat_settings_tool($settings[\'message_limit\'],$event).\r\n				kuo_admin_chat_show($messages,$event,$txp_user).\r\n				kuo_admin_chat_post($txp_user).\r\n			\'</div>\'.\r\n			kuo_admin_chat_js();\r\n	}\r\n	\r\n	pagetop(\'Chat\');\r\n	\r\n	echo $content;\r\n}\r\n\r\nfunction kuo_admin_chat_js() {\r\n\r\n	return \'\r\n		<script>\r\n			const messages = document.getElementById(\"kuo_admin_chat_messages\");\r\n			\r\n			if (messages) {\r\n				messages.scrollTop = messages.scrollHeight-messages.clientHeight;\r\n			} \r\n		</script>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_settings_tool($selected = 25,$event) {\r\n	\r\n	$css_id = \' id=\"kuo_admin_chat_selected\"\';\r\n	\r\n	if ($selected == 25) {\r\n		\r\n		$selected = array(\r\n			$css_id,\r\n			null,\r\n			null\r\n		);\r\n	}\r\n	elseif ($selected == 50) {\r\n		\r\n		$selected = array(\r\n			null,\r\n			$css_id,\r\n			null\r\n		);\r\n	}\r\n	else {\r\n		\r\n		$selected = array(\r\n			null,\r\n			null,\r\n			$css_id\r\n		);\r\n	}\r\n	\r\n	$href = \'?event=\'.$event.\'&amp;show=\';\r\n	\r\n	return \'\r\n		<nav>\r\n			<p><a href=\"?event=\'.$event.\'\">Refresh &#8635;</a></p>\r\n			<p>Show max. \r\n			<a href=\"\'.$href.\'25\"\'.$selected[0].\'>25</a>, \r\n			<a href=\"\'.$href.\'50\"\'.$selected[1].\'>50</a>, \r\n			<a href=\"\'.$href.\'100\"\'.$selected[2].\'>100</a> \r\n			messages.</p>\r\n		</nav>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_css() {\r\n	\r\n	echo \'\r\n		<style>\r\n			#kuo_admin_chat {\r\n				font-size: 1.2em;\r\n			}\r\n			#kuo_admin_chat label,\r\n			#kuo_admin_chat button {\r\n				cursor: pointer;\r\n			}\r\n			#kuo_admin_chat form p {\r\n				display: inline-block;\r\n				vertical-align: top;\r\n			}\r\n			#kuo_admin_chat q:before,\r\n			#kuo_admin_chat q:after {\r\n				content: \"\";\r\n			}\r\n			#kuo_admin_chat ol {\r\n				list-style: none;\r\n				display: block;\r\n				overflow: scroll;\r\n				border: 1px solid #ccc;\r\n				padding: 0;\r\n				margin: 0 0 1em 0;\r\n				background: #fff;\r\n				min-height: 150px;\r\n				max-height: 350px;\r\n			}\r\n			#kuo_admin_chat li {\r\n				display: block;\r\n				clear: both;\r\n				padding: 0.4em 0.7em;\r\n				overflow: auto;\r\n			}\r\n			#kuo_admin_chat li:hover {\r\n				background-color: #f0f0f0;\r\n			}\r\n			#kuo_admin_chat span {\r\n				color: gray;\r\n			}\r\n			#kuo_admin_chat span:before {\r\n				content: \"[\";\r\n			}\r\n			#kuo_admin_chat span:after {\r\n				content: \"]\";\r\n			}\r\n			#kuo_admin_chat strong {\r\n				font-weight: 600;\r\n				padding: 0 0.5em;\r\n			}\r\n			#kuo_admin_chat_selected {\r\n				font-weight: 800;\r\n			}\r\n			#kuo_admin_chat nav p {\r\n				display: inline-block;\r\n				vertical-align: top;\r\n				font-size: 1.2em;\r\n			}\r\n			#kuo_admin_chat nav p:first-of-type {\r\n				margin-right: 1em;\r\n			}\r\n			#kuo_admin_chat input {\r\n				min-width: 50px;\r\n				max-width: 90%;\r\n				width: auto;\r\n			}\r\n			#kuo_admin_chat a.delete {\r\n				padding: 0.1em;\r\n			}\r\n			#kuo_admin_chat a.delete:hover,\r\n			#kuo_admin_chat a.delete:focus,\r\n			#kuo_admin_chat a.delete:active {\r\n				text-decoration: none;\r\n				background: cyan;\r\n				color: #fff;\r\n			}\r\n		</style>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_user_privileges($name) {\r\n	\r\n	$privileges = safe_row(\r\n		\'privs\',\r\n		\'txp_users\',\r\n		\'`name` = \"\'.safe_escape($name).\'\" AND `privs` IN (1,2)\',\r\n		0\r\n	);\r\n	\r\n	if (\r\n		(is_array($privileges))\r\n		&&\r\n		(isset($privileges[\'privs\']))\r\n		&&\r\n		(count($privileges) == 1)\r\n	) {\r\n		$privileges = $privileges[\'privs\'];\r\n		\r\n		settype($privileges,\'int\');\r\n	}\r\n	\r\n	return $privileges;\r\n}\r\n\r\nfunction kuo_admin_chat_delete_link($row,$event) {\r\n	\r\n	$href = \'index.php?event=\'.$event.\'&amp;delete=\'.$row;\r\n	\r\n	return \'<a href=\"\'.$href.\'\" class=\"delete\">&#10060;</a> \';\r\n}\r\n\r\nfunction kuo_admin_chat_show($messages = array(),$event,$reader_name) {\r\n	\r\n	$output = null;\r\n	\r\n	# BEGINS: MESSAGES ARRAY\r\n	if ((!empty($messages)) && (is_array($messages))) {\r\n		\r\n		$messages = array_reverse($messages);\r\n		\r\n		$datetime_format = get_pref(\r\n			\'comments_dateformat\',\r\n			\'%c\',\r\n			0\r\n		);\r\n		\r\n		$is_allowed_to_delete = kuo_admin_chat_user_privileges($reader_name);\r\n		\r\n		# BEGINS: FOREACH\r\n		foreach($messages as $messages) {\r\n			\r\n			# BEGINS: ISSET MESSAGE DATA\r\n			if (isset(\r\n				$messages[\'name\'],\r\n				$messages[\'created\'],\r\n				$messages[\'message\'])\r\n			) {\r\n				\r\n				$messages[\'created\'] = strtotime($messages[\'created\']);\r\n				$messages[\'created\'] = \r\n					strftime($datetime_format,$messages[\'created\']);\r\n				\r\n				$messages[\'name\'] = \r\n					htmlentities($messages[\'name\']);\r\n					\r\n				$messages[\'message\'] = \r\n					wordwrap($messages[\'message\'],175,PHP_EOL,false);\r\n				\r\n				$messages[\'message\'] = \r\n					htmlentities($messages[\'message\']);\r\n					\r\n				$messages[\'message\'] = \r\n					str_replace(PHP_EOL,\'<br />\',$messages[\'message\']);\r\n					\r\n				# BEGINS: THIS READER IS ALLOWED TO DELETE ANY MESSAGE\r\n				if ($is_allowed_to_delete) {\r\n					\r\n					$delete_link = kuo_admin_chat_delete_link(\r\n						$messages[\'row\'],\r\n						$event\r\n					);\r\n				}\r\n				# ENDS: THIS READER IS ALLOWED TO DELETE ANY MESSAGE\r\n				else {\r\n					$delete_link = null;\r\n				}\r\n				\r\n				$output .= \r\n					\'<li>\'.\r\n						$delete_link.\r\n						\'<span>\'.$messages[\'created\'].\'</span> \'.\r\n						\'<strong>\'.$messages[\'name\'].\':</strong> \'.\r\n						\'<q cite=\"\'.$messages[\'name\'].\'\">\'.\r\n							$messages[\'message\'].\r\n						\'</q>\'.\r\n					\'</li>\'\r\n				;\r\n			}\r\n			# ENDS: ISSET MESSAGE DATA\r\n		}\r\n		# ENDS: FOREACH\r\n		\r\n		$output = \'<ol id=\"kuo_admin_chat_messages\">\'.$output.\'</ol>\';\r\n	}\r\n	# ENDS: MESSAGES ARRAY\r\n	else {\r\n		$output = \'<p><mark>No messages.</mark></p>\';\r\n	}\r\n	\r\n	return $output;\r\n}\r\n\r\nfunction kuo_admin_chat_post($sender_name) {\r\n	\r\n	$sender_name = htmlentities($sender_name);\r\n	\r\n	return \'\r\n		<form method=\"post\" autocomplete=\"off\">\r\n			<fieldset>\r\n				<legend>Write a new message</legend>\r\n				<p><label \r\n				for=\"kuo_new_message\">\'.$sender_name.\':&nbsp;</label>\'.finput(\r\n					\'text\',\r\n					\'message\',\r\n					\'\',\r\n					\'\',\r\n					\'\',\r\n					\'\',\r\n					100,\r\n					\'\',\r\n					\'kuo_new_message\',\r\n					false,\r\n					true,\r\n					\'\'\r\n				).\'</p>\r\n				<p><button type=\"submit\">\'.gTxt(\'submit\').\'</button></p>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n}\r\n\r\nfunction kuo_admin_chat_save_message($name,$message) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($name,$message)) {\r\n		\r\n		$status = safe_insert(\r\n			\'kuo_admin_chat\',\r\n			\'\r\n				`name` = \"\'.safe_escape($name).\'\",\r\n				`created` = \"\'.date(\'Y-m-d H:i:s\').\'\",\r\n				`modified` = \"\'.date(\'Y-m-d H:i:s\').\'\",\r\n				`status` = 1,\r\n				`message` = \"\'.safe_escape($message).\'\"\r\n			\',\r\n			0\r\n		);\r\n	}\r\n\r\n	return $status;\r\n}\r\n\r\nfunction kuo_admin_chat_save_settings($name,$settings) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($name,$settings[\'message_limit\'])) {\r\n		\r\n		$status = safe_upsert(\r\n			\'kuo_admin_chat_prefs\',\r\n			\'`message_limit` = \'.safe_escape($settings[\'message_limit\']),\r\n			\'`name` = \"\'.safe_escape($name).\'\"\',\r\n			0\r\n		);\r\n	}\r\n	\r\n	return $status;\r\n}\r\n\r\nfunction kuo_admin_chat_delete_message($row) {\r\n	\r\n	$status = false;\r\n	\r\n	if (isset($row)) {\r\n		\r\n		$status = safe_update(\r\n			\'kuo_admin_chat\',\r\n			\'`status` = 0, `modified` = \"\'.date(\'Y-m-d H:i:s\').\'\"\',\r\n			\'`row` = \'.safe_escape($row).\' AND `status` = 1\',\r\n			0\r\n		);\r\n	}\r\n	\r\n	return $status;\r\n}', '2b7b7c8341f1f3bdddfca431af402bbe', '', '', 3, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_fuzzy_find', 0, 'Stef Dawson', 'http://stefdawson.com', '0.30', 'Offers alternative spellings and/or close-matching articles from search terms.', '<h1>smd_fuzzy_find</h1>\n\n<p>Ever wanted <span class=\"caps\">TXP</span>&#8217;s search facility to be a little more, well, inaccurate? Help people with fat fingers to find what they were actually looking for with this tool that can find mis-spelled or like-sounding words from search results.</p>\n\n<p>Results are ordered by approximate relevancy and it&#8217;s automatically context-sensitive because the pool of words it compares against are from your site. On a zoo website, someone typing in “lino” won&#8217;t get articles about flooring but more likely articles about lions, which is what they really wanted. We hope.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Search for similarly spelled, or similar sounding words (may be switched on/off)</li>\n	<li>Limit the search to article <code>status</code>, <code>section</code> or <code>category</code> to speed up proceedings</li>\n	<li>Tweak sensitivity to give better results (more specific, less likely to find a match) or general results (less specific, may match stuff you don&#8217;t expect)</li>\n	<li>Can offer links to exact search terms if you wish (a bit like Google&#8217;s “Did you mean &#8230;”)</li>\n	<li>Display matching articles using a form/container. Default is the built-in <code>search_results</code> form</li>\n	<li>Unless overridden using the <code>match_with</code> attribute, the Title and Body will be searched. Alternatively, if you have set the search locations using <a href=\"http://forum.textpattern.com/viewtopic.php?id=29036\" rel=\"nofollow\">wet_haystack</a> then those places will be searched instead</li>\n</ul>\n\n<h2>Author</h2>\n\n<p><a href=\"http://stefdawson.com/commentForm\" rel=\"nofollow\">Stef Dawson</a>, with notable mention to <a href=\"http://elonen.iki.fi/code/misc-notes/appr-search-php/index.html\" rel=\"nofollow\">Jarno Elonen</a> for the Fuzzy Find algorithm which &#8212; to this day &#8212; remains mere voodoo to me.</p>\n\n<h2>Installation / Uninstallation</h2>\n\n<p>Requires Textpattern 4.0.7 and <a href=\"http://stefdawson.com/downloads/smd_lib_v0.33.txt\" rel=\"nofollow\">smd_lib v0.33</a> must be installed and activated.</p>\n\n<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/932/smd_fuzzy_find\" rel=\"nofollow\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\" rel=\"nofollow\">software page</a>, paste the code into the <span class=\"caps\">TXP</span> Admin -&gt; Plugins pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=25367\" rel=\"nofollow\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>To uninstall, simply delete from the Admin -&gt; Plugins page.</p>\n\n<h2>Usage</h2>\n\n<p>The plugin is <strong>not</strong> a replacement for the built-in <span class=\"caps\">TXP</span> search; it should be used to augment it, like this:</p>\n\n<pre><code>&lt;txp:if_search&gt;\n  &lt;dl class=&quot;results&quot;&gt;\n    &lt;txp:chh_if_data&gt;\n      &lt;txp:article limit=&quot;8&quot; searchform=&quot;excerpts&quot; /&gt;\n    &lt;txp:else /&gt;\n      &lt;txp:smd_fuzzy_find form=&quot;excerpts&quot; /&gt;\n    &lt;/txp:chh_if_data&gt;\n  &lt;/dl&gt;\n&lt;/txp:if_search&gt;</code></pre>\n\n<p>Exact matches will be processed as normal but mismatches will be handled by smd_fuzzy_find. If you try to use smd_fuzzy_find on its own, you will likely receive a warning about a missing <code>&lt;txp:article /&gt;</code> tag.</p>\n\n<h3>Attributes</h3>\n\n<table>\n		<tr>\n			<th>Attribute name </th>\n			<th>Default </th>\n			<th>Values </th>\n			<th>Description </th>\n		</tr>\n		<tr>\n			<td> search_term </td>\n			<td> <code>?q</code> </td>\n			<td> <code>?q</code> or any text </td>\n			<td> You may use a fixed string here but it&#8217;s rather pointless </td>\n		</tr>\n		<tr>\n			<td> match_with </td>\n			<td> <code>article:body;title</code> or wet_haystack criteria </td>\n			<td> <code>keywords</code> / <code>body</code> / <code>excerpt</code> / <code>category1</code> / <code>category2</code> / <code>section</code> / <code>id</code> / <code>authorid</code> / <code>title</code> / <code>custom_1</code> / etc </td>\n			<td> Which article fields you would like to match. Define the object you want to look in (currently only <code>article</code> is supported) followed by a colon, then a list of semi-colon separated places to look </td>\n		</tr>\n		<tr>\n			<td> show </td>\n			<td> <code>words, articles</code> </td>\n			<td>  <code>words, articles</code> / <code>words</code> / <code>articles</code> </td>\n			<td> Whether to list the closest matching articles, the closest matching search terms, or both </td>\n		</tr>\n		<tr>\n			<td> section </td>\n			<td> <em>unset</em> (i.e. search the whole site) </td>\n			<td> any valid section containing articles </td>\n			<td> Limit the search to one or more sections; give a comma-separated list. You can use <code>?s</code> for the current section or <code>!s</code> for anything except the current section, or you can read the value from another part of the article, a custom field / txp:variable / url variable </td>\n		</tr>\n		<tr>\n			<td> category </td>\n			<td> <em>unset</em> (i.e. search all categories) </td>\n			<td> any valid article category </td>\n			<td> Limit the search to one or more categories; give a comma-separated list. You can use <code>?c</code> for the current global category or <code>!c</code> for all cats except the current one. Like <code>section</code> you can read from other places too </td>\n		</tr>\n		<tr>\n			<td> sublevel (formerly <em>subcats</em>)</td>\n			<td> <code>0</code> </td>\n			<td> integer / <code>all</code> </td>\n			<td> Number of subcategory levels to traverse. <code>0</code> = top-level only; <code>1</code> = top level + one sub-level; and so on </td>\n		</tr>\n		<tr>\n			<td> status </td>\n			<td> <code>live, sticky</code> </td>\n			<td> <code>live</code> / <code>sticky</code> / <code>hidden</code> / <code>pending</code> / <code>draft</code> or any combination </td>\n			<td> Restricts the search to particular types of document status </td>\n		</tr>\n		<tr>\n			<td> tolerance </td>\n			<td> <code>2</code> </td>\n			<td> 0 &#8211; 5 </td>\n			<td> How fuzzy the search is and how long the minimum search term is allowed to be. 0 means a very close match, that allows short search words. 5 means it&#8217;s quite relaxed and is likelt return nothing like what you searched for; search words must then be longer, roughly &gt;7 characters. Practical values are 0-3 </td>\n		</tr>\n		<tr>\n			<td> refine  </td>\n			<td> <code>soundex, metaphone</code> </td>\n			<td> <code>soundex</code>, <code>metaphone</code> or <code>soundex, metaphone</code> </td>\n			<td> Switch on soundex and/or metaphone support for potentially better matching (though it&#8217;s usually only of use in English) </td>\n		</tr>\n		<tr>\n			<td> case_sensitive </td>\n			<td> <code>0</code> </td>\n			<td> <code>0</code> (off) / <code>1</code> (case-sensitive) </td>\n			<td> Does what it says on the tin </td>\n		</tr>\n		<tr>\n			<td> min_word_length </td>\n			<td> <code>4</code> </td>\n			<td> integer </td>\n			<td> The minimum word length allowed in the search results </td>\n		</tr>\n		<tr>\n			<td> limit </td>\n			<td> <code>words:5, articles:10</code> </td>\n			<td> integer / <code>words:</code> + a number / <code>articles:</code> + a number </td>\n			<td> The maximum number of words and/or articles to display in the results. Use a single integer to limit both to the same value. Specify <code>articles:5</code> to remove any limit on the number of alternative search words offered, and limit the number of returned articles to a maximum of 5. Similarly, <code>words:4</code> will remove the article limit, but only show a maximum of 4 alternative words. Both of these are subject to their respective <code>show</code> options being set </td>\n		</tr>\n		<tr>\n			<td> form </td>\n			<td> <code>search_results</code> </td>\n			<td> any valid form name </td>\n			<td> The <span class=\"caps\">TXP</span> form with which to process each matching article. You may also use the plugin as a container. Note that <code>&lt;txp:search_result_excerpt /&gt;</code> is honoured as closely as possible, i.e. if the closest words are found in the keywords, body or excerpt fields. If they are found in any other location (e.g. custom_3) the article title will be returned but the search_result_excerpt will likely be empty. This is a limitation of that tag, not the plugin directly </td>\n		</tr>\n		<tr>\n			<td> delim </td>\n			<td> <code>,</code> </td>\n			<td> any characters </td>\n			<td> Change the delimiter for all options that take a comma-separated list </td>\n		</tr>\n		<tr>\n			<td> no_match_label </td>\n			<td> <span class=\"caps\">MLP</span>: <code>no_match</code> </td>\n			<td> any text or <span class=\"caps\">MLP</span> string </td>\n			<td> The phrase to display when no matches (maybe not even fuzzy ones) are found. Use <code>no_match_label=&quot;&quot;</code> to turn it off </td>\n		</tr>\n		<tr>\n			<td> suggest_label </td>\n			<td> <span class=\"caps\">MLP</span>: <code>suggest</code> </td>\n			<td> any text or <span class=\"caps\">MLP</span> string </td>\n			<td> The phrase to display immediately prior to showing close-matching articles/words. Use <code>suggest_label=&quot;&quot;</code> to switch it off </td>\n		</tr>\n		<tr>\n			<td> too_short_label </td>\n			<td> <span class=\"caps\">MLP</span>: <code>too_short</code> </td>\n			<td> any text or <span class=\"caps\">MLP</span> string  </td>\n			<td> Searches of under about 3 characters (sometimes more depending on your content) are too short for any reasonable fuzziness to be applied. This message is displayed in that circumstance. Use <code>too_short_label=&quot;&quot;</code> to switch it off </td>\n		</tr>\n		<tr>\n			<td> labeltag </td>\n			<td> <code>p</code> </td>\n			<td> any valid tag name, without its brackets </td>\n			<td> The (X)HTML tag in which to wrap any labels </td>\n		</tr>\n</table>\n\n<h2>Tips and tricks</h2>\n\n<ul>\n	<li>If you can, limit the search criteria using <code>status</code>, <code>section</code> and <code>category</code> to improve performance</li>\n	<li>Tweak the <code>refine</code> options to see if you get better or worse results for your content / language</li>\n	<li>Most of the default values are optimal for good results, but for scientific or specialist sites you may wish to increase the <code>tolerance</code> and <code>min_word_length</code> to avoid false positives</li>\n	<li>For offering an advanced search facility, write an <span class=\"caps\">HTML</span> form that allows people to customise the search criteria (e.g. check boxes for <code>metaphone</code> / <code>soundex / case_sensitive</code> ; text boxes for <code>tolerance</code> / <code>min_word_length</code> / <code>limit</code> ; select lists of categories to search ; etc). Then use a series of smd_if statements inside your <code>&lt;txp:if_search /&gt;</code> to check for the existence of each <span class=\"caps\">URL</span> variable, check they have acceptable values and then plug them into the smd_fuzzy_find tag using replacements such as <code>tolerance=&quot;{smd_if_tolerance}&quot;</code></li>\n</ul>\n\n<h2>Known issues</h2>\n\n<ul>\n	<li>Slow with large article sets</li>\n	<li>Searching for a word with an apostrophe in it may cause odd character encoding or incomplete results</li>\n	<li>Searching for multiple (space-separated) words can lead to odd results</li>\n	<li>Sometimes it makes you laugh and picks something that seems totally unrelated</li>\n</ul>\n\n<h2>Credits</h2>\n\n<p>This plugin wouldn&#8217;t have existed without the original Fuzzy Find algorithm by Jarno Elonen, as noted above. All kudos goes in that direction. Also, extended thanks to the beta testers, especially Els Lepelaars for feedback and unending patience during development.</p>', 'require_plugin(\'smd_lib\');\n\n// MLP support\nglobal $smd_fuzzLang;\n\n$smd_fuzz_str = array(\n    \'too_short\' => \'The text you are searching for is probably too short. Try a longer word. \',\n    \'no_match\' => \'Sorry, no results matched \"{search_term}\" exactly. \',\n    \'suggest\' => \'Here are the closest matching {thingies} that may help you find what you are looking for: \',\n    \'suggest_join\' => \'and\',\n    \'articles\' => \'articles\',\n    \'words\' => \'words\',\n);\n\n$smd_fuzzLang = new smd_MLP(\'smd_fuzzy_find\', \'smd_fuzz\', $smd_fuzz_str);\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'smd_fuzzy_find\');\n}\n\nfunction smd_fuzzy_find($atts, $thing = \'\')\n{\n    global $pretext, $smd_fuzzLang, $prefs;\n\n    extract(lAtts(array(\n        \'form\'            => \'search_results\',\n        \'section\'         => \'\',\n        \'category\'        => \'\',\n        \'sublevel\'        => \'0\',\n        \'search_term\'     => \'?q\',\n        \'match_with\'      => \'\',\n        \'tolerance\'       => \'2\',\n        \'min_word_length\' => \'4\',\n        \'delim\'           => \',\',\n        \'limit\'           => \'\',\n        \'case_sensitive\'  => \'0\',\n        \'refine\'          => \'\',\n        \'show\'            => \'\',\n        \'status\'          => \'\',\n        \'debug\'           => \'0\',\n        \'no_match_label\'  => \'#\',\n        \'suggest_label\'   => \'#\',\n        \'too_short_label\' => \'#\',\n        \'labeltag\'        => \'p\',\n    ), $atts));\n\n    // Process defaults; most can\'t be set in lAtts() because of the custom delimiter\n    $match_with = empty($match_with) ? \"article:\" . implode(\";\", do_list($prefs[\'searchable_article_fields\'])) : $match_with;\n    $limit = ($limit) ? $limit : \"words:5\" .$delim. \"articles:10\";\n    $refine = ($refine) ? $refine : \"metaphone\" .$delim. \"soundex\";\n    $show = ($show) ? $show : \"words\" .$delim. \"articles\";\n    $status = ($status) ? $status : \"live\" .$delim. \"sticky\";\n\n    $refineAllow = array(\"metaphone\", \"soundex\");\n    $showAllow = array(\"articles\", \"words\");\n    $colNames = array(\n        \'Keywords\'  => \"article:keywords\",\n        \'Body\'      => \"article:body\",\n        \'Excerpt\'   => \"article:excerpt\",\n        \'Category1\' => \"article:category1\",\n        \'Category2\' => \"article:category2\",\n        \'Section\'   => \"article:section\",\n        \'ID\'        => \"article:id\",\n        \'AuthorID\'  => \"article:authorid\",\n        \'Title\'     => \"article:title\",\n        \'message\'   => \"comments:message\",\n        \'email\'     => \"comments:email\",\n        \'name\'      => \"comments:name\",\n        \'web\'       => \"comments:web\",\n    );\n\n    $places = array(\'textpattern\' => \"article\", \'txp_discuss\' => \"comments\");\n    $clause = array();\n    $refineList = array();\n    $dbTables = array();\n    $dbFields = array();\n\n    // Expand the args in case they\'re ? or ! shortcuts, and do some validity checking.\n    $search_term = smd_doList($search_term, false, \"\", false, $delim);\n    $search_term = $search_term[0][0];\n\n    if ($debug > 1) {\n        echo \"++ SEARCH TERM ++\";\n        dmp($search_term);\n    }\n\n    $refine = do_list($refine, $delim);\n\n    for ($idx = 0; $idx < count($refine); $idx++) {\n        if (in_array($refine[$idx], $refineAllow)) {\n            $refineList[$idx] = $refine[$idx];\n        }\n    }\n\n    $meta_search = (in_array(\"metaphone\", $refineList)) ? metaphone($search_term) : \"\";\n    $sound_search = (in_array(\"soundex\", $refineList)) ? soundex($search_term) : \"\";\n    $tolerance = intval($tolerance);\n\n    // match_with needs to be built into a series of arrays of database tables and columns\n    $lookin = smd_split($match_with, false, \":,\\s\");\n\n    // Loop over pairs of elements\n    for ($idx = 0; $idx < count($lookin); $idx += 2) {\n        if (($tmp = array_search($lookin[$idx], $places)) !== false) {\n            $dbTables[] = $tmp;\n            $dbFieldList = smd_split($lookin[$idx+1], false, \";\");\n            $dbField = array();\n\n            foreach ($dbFieldList as $lookField) {\n                $key = array_search($lookin[$idx].\":\".strtolower($lookField), $colNames);\n\n                if ($key) {\n                    $dbField[] = $key;\n                } else if (strpos($lookField, \"custom_\") === 0) {\n                    $dbField[] = $lookField;\n                }\n            }\n\n            if (count($dbField) > 0) {\n                $dbFields[] = $dbField;\n            }\n        }\n    }\n\n    if (count($dbTables) == 0 || count($dbFields) == 0) {\n        $dbTables[] = \"textpattern\";\n        $dbFields[] = \"*\";\n    }\n\n    if ($debug) {\n        echo \"++ FIELDS ++\";\n        dmp($dbFields);\n    }\n\n    $showList = do_list($show, $delim);\n\n    for ($idx = count($showList); $idx > 0; $idx--) {\n        if (!in_array($showList[$idx-1], $showAllow)) {\n            unset($showList[$idx]);\n        }\n    }\n\n    $limitBy = array();\n    $limit = do_list($limit, $delim);\n\n    foreach ($limit as $limOption) {\n        if (is_numeric($limOption)) {\n            $limitBy[\"articles\"] = $limOption;\n            $limitBy[\"words\"] = $limOption;\n        } else {\n            $limsplit = smd_split($limOption, false, \":\");\n\n            if ((count($limsplit) == 2) && (in_array($limsplit[0], $showAllow)) && (is_numeric($limsplit[1]))) {\n                $limitBy[$limsplit[0]] = $limsplit[1];\n            }\n        }\n    }\n\n    $thingiesL10n = array();\n\n    foreach ($showList as $item) {\n        $thingiesL10n[] = $smd_fuzzLang->gTxt($item);\n    }\n\n    $thingies = implode(\" \".$smd_fuzzLang->gTxt(\'suggest_join\').\" \", $thingiesL10n);\n    $no_match_label = ($no_match_label == \"#\") ? $smd_fuzzLang->gTxt(\'no_match\', array(\"{search_term}\" => $search_term)) : $no_match_label;\n    $suggest_label = ($suggest_label == \"#\") ? $smd_fuzzLang->gTxt(\'suggest\', array(\"{thingies}\" => $thingies)) : $suggest_label;\n    $too_short_label = ($too_short_label == \"#\") ? $smd_fuzzLang->gTxt(\'too_short\') : $too_short_label;\n\n    // Roll any status, section and category filters into the initial query\n    $clause[] = \'1=1\';\n\n    if (in_array(\"textpattern\", $dbTables)) {\n        // Status\n        list($statinc, $statexc) = smd_doList($status, false, \'\', false, $delim);\n\n        for ($idx = 0; $idx < count($statinc); $idx++) {\n            $tmpa[] = doQuote(getStatusNum($statinc[$idx]));\n        }\n\n        if ($tmpa) {\n            $clause[] = \'Status IN (\' .implode(\",\", $tmpa). \')\';\n        }\n\n        // Section\n        list($secinc, $secexc) = smd_doList($section, false, \'\', true, $delim);\n\n        if ($secinc) {\n            $clause[] = \'(Section IN (\' .implode(\",\", $secinc). \'))\';\n        }\n\n        // Category\n        list($catinc, $catexc) = smd_doList($category, false, \"article:\".$sublevel, true, $delim);\n\n        if ($catinc) {\n            $imp = implode(\",\", $catinc);\n            $clause[] = \'(Category1 IN (\' .$imp. \') OR Category2 IN (\' .$imp. \'))\';\n        }\n\n        // Combine the query portions\n        $clause = implode(\" AND \", $clause);\n\n        // Add on any exclusions\n        $tmpa = array();\n\n        for ($idx = 0; $idx < count($statexc); $idx++) {\n            $tmpa[] = doQuote(getStatusNum($statexc[$idx]));\n        }\n\n        $clause .= ($tmpa) ? \' AND Status NOT IN (\'.implode(\",\", $tmpa).\')\' : \'\';\n\n        $imp = implode(\",\", $secexc);\n        $clause .= ($secexc) ? \' AND Section NOT IN (\'.$imp.\')\' : \'\';\n\n        $imp = implode(\",\", $catexc);\n        $clause .= ($catexc) ? \' AND Category1 NOT IN (\'.$imp.\') AND Category2 NOT IN (\'.$imp.\')\' : \'\';\n    }\n\n    $clause = is_array($clause) ? join(\" \", $clause) : $clause;\n    //TODO: comments\n/*\n    if (in_array(\"txp_discuss\",$dbTables)) {\n        $clause .= \" AND textpattern.ID = txp_discuss.parentid\";\n    }\n*/\n\n    if ($debug > 0) {\n        echo \"++ WHERE CLAUSE ++\";\n        dmp($clause);\n    }\n\n    $out = \"\";\n    // Perform the searches\n    $finder = new smd_FuzzyFind($search_term, $tolerance);\n\n    if ($finder->too_short_err) {\n        $out .= ($labeltag == \"\") ? \"\" : \"<\" .$labeltag.\">\";\n        $out .= $no_match_label;\n        $out .= $too_short_label;\n        $out .= ($labeltag == \"\") ? \"\" : \"</\" .$labeltag.\">\";\n    } else {\n        $cols = \"*\" . ((in_array(\"textpattern\", $dbTables)) ? \", unix_timestamp(textpattern.Posted) AS uPosted, unix_timestamp(textpattern.LastMod) AS uLastMod, unix_timestamp(textpattern.Expires) AS uExpires\" : \"\");\n        $rs = safe_rows_start($cols, implode($dbTables, \", \"), $clause, $debug);\n\n        if (in_array(\"textpattern\",$dbTables)) {\n            $opform = ($thing) ? $thing : fetch_form($form);\n        }\n\n        $pageurl = smd_removeQSVar($pretext[\'request_uri\'],\'q\');\n        $allFields = \"\";\n        $artList = array();\n        $wordList = array();\n        $termList = array();\n\n        while ($row = nextRow($rs)) {\n            $allFields = \"\";\n            // Join all the required places to look into a looong text block\n            if ($dbFields[0] == \"*\") {\n                foreach ($row as $colname) {\n                    $allFields .= $colname.\" \";\n                }\n            } else {\n                foreach ($dbFields as $fieldRow) {\n                    foreach ($fieldRow as $theField) {\n                        $allFields .= $row[$theField].\" \";\n                    }\n                }\n            }\n\n            // Remove between-word Unicode punctuation and replace with space\n            $allFields = strip_tags($allFields);\n            $allFields = trim(preg_replace(\'#[\\p{P}]+#u\', \' \', $allFields));\n\n            // Split the remainder by (single or multiple) spaces\n            $werds = preg_split(\'/\\s+/\', $allFields, -1, PREG_SPLIT_NO_EMPTY);\n            // ...and reconstitute the unique words as a huge space-delimited string\n            $werds = implode(\" \",array_unique($werds));\n            // Take into account case sensitivity\n            $werds = ($case_sensitive) ? $werds : strtolower($werds);\n\n            // Find close word matches\n            $matches = $finder->search($werds);\n\n            if ($debug > 1) {\n                if ($debug > 3 || $matches) {\n                    echo \"++ UNIQUE WORDS ++\";\n                    dmp($werds);\n                }\n\n                if ($matches) {\n                    echo \"++ CLOSEST MATCHES ++\";\n                    dmp($matches);\n                }\n            }\n\n            if (count($matches) > 0) {\n                $shortestDist = 100; // A stupidly high number to start with\n                $shortestMetaDist = -1;\n                $closestWord = \"\";\n                $closestMetaWord = \"\";\n                $max_term_len = 0;\n\n                // Build a unique array of closest matching words\n                while (list($idx, $dist) = each($matches)) {\n                    $term = smd_getWord($werds, $search_term, $idx);\n\n                    // Only words meeting the minimum requirement need apply\n                    $max_term_len = (strlen($term) > $max_term_len) ? strlen($term) : $max_term_len;\n\n                    if (strlen($term) < $min_word_length) {\n                        continue;\n                    }\n\n                    $term = ($case_sensitive) ? $term : strtolower($term);\n\n                    if ($debug > 2) {\n                        echo $term.br;\n                    }\n\n                    if ($dist < $shortestDist) {\n                        $shortestDist = $dist;\n                        $closestWord = $term;\n                    }\n\n                    if ($meta_search != \"\") {\n                        $meta_term = metaphone($term);\n\n                        if ($debug > 2) {\n                            echo $meta_term . \" : \" . $meta_search .\" \".br;\n                        }\n\n                        $levDist = levenshtein($meta_search, $meta_term);\n\n                        if ($levDist <= $shortestMetaDist || $shortestMetaDist < 0) {\n                            $shortestMetaDist = $levDist;\n                            $closestMetaWord  = $term;\n                        }\n                    }\n                }\n\n                // Pick the one that sounds closest to the original\n                if (trim($closestWord) != \"\") {\n                    $idx = md5($closestWord);\n                    $bestFit = $closestWord;\n                    $bestDist = $shortestDist;\n\n                    if ($sound_search != \"\") {\n                        $sound1 = levenshtein(soundex($closestWord), $sound_search);\n                        $sound2 = levenshtein(soundex($closestMetaWord), $sound_search);\n\n                        if ($sound1 >= $sound2) {\n                            $idx = md5($closestMetaWord);\n                            $bestFit = $closestMetaWord;\n                            $bestDist = $shortestMetaDist;\n                        }\n                    }\n\n                    $wordList[$idx] = $bestFit;\n                    $wordDist[$idx] = $bestDist;\n\n                    if ($debug > 2) {\n                        echo \"++ BEST FIT ++\";\n                        dmp($bestFit);\n                    }\n                }\n\n                // Build an array of unique matching articles\n                if ($max_term_len >= $min_word_length) {\n                    if (in_array(\"textpattern\", $dbTables)) {\n                        populateArticleData($row);\n                    }\n\n                    // Temporarily assign the closest match to the query string so that\n                    // the search_result_excerpt can hilight the found words\n                    $pretext[\'q\'] = $term;\n                    $artList[] = parse($opform);\n                    $pretext[\'q\'] = $search_term;\n                }\n            }\n        }\n\n        // Sort the word list in order of relevance\n        if (count($wordList) > 0) {\n            array_multisort($wordDist,$wordList);\n        }\n\n        // Output stuff to the page\n        $out .= ($labeltag == \"\") ? \"\" : \"<\" .$labeltag.\">\";\n        $out .= $no_match_label;\n\n        if (count($wordList) > 0) {\n            $out .= (count($showList) > 0) ? $suggest_label : \"\";\n\n            if (in_array(\"words\", $showList)) {\n                $ctr = 0;\n\n                foreach ($wordList as $item) {\n                    if (array_key_exists(\"words\", $limitBy) && $ctr >= $limitBy[\"words\"]) {\n                        break;\n                    }\n\n                    $out .= \'<a class=\"smd_fuzzy_suggest\" href=\"\'.smd_addQSVar($pageurl,\'q\',$item).\'\">\'.$item.\'</a>\'.n;\n                    $ctr++;\n                }\n            }\n\n            $out .= ($labeltag == \"\") ? \"\" : \"</\" .$labeltag.\">\";\n\n            if (in_array(\"articles\", $showList)) {\n                $ctr = 0;\n\n                foreach ($artList as $art) {\n                    if (array_key_exists(\"articles\", $limitBy) && $ctr >= $limitBy[\"articles\"]) {\n                        break;\n                    }\n\n                    $out .= $art;\n                    $ctr++;\n                }\n            }\n        }\n    }\n    return $out;\n}\n\n\n/* smd_FuzzyFind\nA PHP class for approximate string searching of large text masses, adapted (*cough* borrowed) from http://elonen.iki.fi/code/misc-notes/appr-search-php/. Instantiate one of these and pass it the string pattern/word you are looking for and a number indicating how close that match has to be/minimum length of strings to consider (i.e. the amount of error tolerable). 0=close match/short words; 10=pretty much every long (10 char minimum) string in the world. Practical values are usually 1 or 2, sometimes 3.\n\nUsage example:\n  $finder = new smd_FuzzyFind($patt, $max_err);\n  if ($finder->too_short_err)\n    $error = \"Unable to search: use longer pattern or reduce error tolerance.\";\n\n  while($text = get_next_page_of_text()) {\n    $matches = $finder->search($text);\n    while(list($idx,$rng) = each($matches))\n      print \"Match found ending at position $idx with a closeness of $val\\n\";\n  }\n\nThe code uses initial filtering to sort out possible match candidates and then applies a slower character-by-character search (search_short()) against them.\n*/\n\nif(!class_exists(\'smd_FuzzyFind\')) {\nclass smd_FuzzyFind {\n    // The last 3 parameters are for optimization only, to avoid the\n    // surprisingly slow strlen() and substr() calls:\n    //  - $start_index = from which character of $text to start the search\n    //  - $max_len = maximum character to search (starting from $start_index)\n    //  - $text_strlen =\n    // The return value is an array of matches:\n    //   Array( [<match-end-index>] => <error>, ... )\n    // Note: <error> is generally NOT an exact edit distance but rather a\n    // lower bound. This is unfortunate but the routine would be slower if\n    // the exact error was calculate along with the matches.\n    // The function is based on the non-deterministic automaton simulation\n    // algorithm (without bit parallelism optimizations).\n    function search_short($patt, $k, $text, $start_index = 0, $max_len = -1, $text_strlen = -1)\n    {\n        if ($text_strlen < 0) {\n            $text_strlen = strlen($text);\n        }\n\n        if ($max_len < 0) {\n            $max_len = $text_strlen;\n        }\n\n        $start_index = (int)max(0, $start_index);\n        $n = min($max_len, $text_strlen - $start_index);\n        $m = strlen($patt);\n        $end_index = $start_index + $n;\n\n        // If $text is shorter than $patt, use the built-in\n        // levenshtein() instead:\n        if ($n < $m) {\n            $lev = levenshtein(substr($text, $start_index, $n), $patt);\n\n            if ($lev <= $k) {\n                return Array($start_index + $n - 1 => $lev);\n            } else {\n                return Array();\n            }\n        }\n\n        $s = Array();\n\n        for ($i = 0; $i < $m; $i++) {\n            $c = $patt{$i};\n\n            if (isset($s[$c])) {\n                $s[$c] = min($i, $s[$c]);\n            } else {\n                $s[$c] = $i;\n            }\n        }\n\n        if ($end_index < $start_index) {\n            return Array();\n        }\n\n        $matches = Array();\n        $da = $db = range(0, $m - $k + 1);\n\n        $mk = $m - $k;\n\n        for ($t = $start_index; $t < $end_index; $t++) {\n            $c = $text{$t};\n            $in_patt = isset($s[$c]);\n\n            if ($t & 1) {\n                $d = &$da;\n                $e = &$db;\n            } else {\n                $d = &$db;\n                $e = &$da;\n            }\n\n            for ($i = 1; $i <= $mk; $i++) {\n                $g = min($k + 1, $e[$i] + 1, $e[$i + 1] + 1);\n\n                // TODO: optimize this with a look-up-table?\n                if ($in_patt)\n                    for ($j = $e[$i - 1]; ($j < $g && $j <= $mk); $j++) {\n                        if ($patt{$i + $j - 1} == $c) {\n                            $g = $j;\n                        }\n                    }\n\n                $d[$i] = $g;\n            }\n\n            if ($d[$mk] <= $k) {\n                $err = $d[$mk];\n                $i = min($t-$err + $k + 1, $start_index + $n - 1);\n\n                if (!isset($matches[$i]) || $err < $matches[$i]) {\n                    $matches[$i] = $err;\n                }\n            }\n        }\n\n        unset($da, $db);\n\n        return $matches;\n    }\n\n    function test_short_search()\n    {\n        $test_text = \"Olipa kerran jussi bj&xling ja kolme\\n iloista \".\n            \"jussi bforling:ia mutta ei yhtaan jussi bjorling-nimista laulajaa.\";\n        $test_patt = \"jussi bjorling\";\n        assert( $this->search_short($test_patt, 4, $test_text) == Array(27=>2, 60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 2, $test_text) == Array(27=>2, 60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 1, $test_text) == Array(60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 0, $test_text) == Array(94=>0));\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 19, 7) == Array());\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 19, 8) == Array(26=>2));\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 20, 8) == Array());\n    }\n\n    var $patt, $patt_len, $max_err;\n    var $parts, $n_parts, $unique_parts, $max_part_len;\n    var $transf_patt;\n    var $too_short_err;\n\n    function __construct ($pattern, $max_error)\n    {\n        $this->patt = $pattern;\n        $this->patt_len = strlen($this->patt);\n        $this->max_err = $max_error;\n\n        // Calculate pattern partition size\n        $intpartlen = floor($this->patt_len / ($this->max_err + 2));\n\n        if ($intpartlen < 1) {\n            $this->too_short_err = true;\n            return;\n        } else {\n            $this->too_short_err = false;\n        }\n\n        // Partition the pattern for pruning\n        $this->parts = Array();\n\n        for ($i = 0; $i < $this->patt_len; $i += $intpartlen) {\n            if ($i + $intpartlen * 2 > $this->patt_len) {\n                $this->parts[] = substr($this->patt, $i);\n                break;\n            } else {\n                $this->parts[] = substr($this->patt, $i, $intpartlen);\n            }\n        }\n\n        $this->n_parts = count($this->parts);\n\n        // The intpartlen test above should have covered this:\n        assert($this->n_parts >= $this->max_err + 1);\n\n        // Find maximum part length\n        foreach ($this->parts as $p) {\n            $this->max_part_len = max( $this->max_part_len, strlen($p));\n        }\n\n        // Make a new part array with duplicate strings removed\n        $this->unique_parts = array_unique($this->parts);\n\n        // Transform the pattern into a low resolution pruning string\n        // by replacing parts with single characters\n        $this->transf_patt = \"\";\n        reset($this->parts);\n\n        while (list(,$p) = each($this->parts)) {\n           $this->transf_patt .= chr(array_search($p, $this->unique_parts) + ord(\"A\"));\n        }\n\n        // Self diagnostics\n        $this->test_short_search();\n    }\n\n    function search($text)\n    {\n        // Find all occurrences of unique parts in the\n        // full text. The result is an array:\n        //   Array( <index> => <part#>, .. )\n        $part_map = Array();\n        reset($this->unique_parts);\n\n        while (list($pi, $part_str) = each($this->unique_parts)) {\n            $pos = strpos($text, $part_str);\n\n            while ($pos !== false) {\n                $part_map[$pos] = $pi;\n                $pos = strpos($text, $part_str, $pos+1);\n            }\n        }\n\n        ksort($part_map); // Sort by string index\n\n        // The following code does several things simultaneously:\n        //  1) Divide the indices into groups using gaps\n        //    larger than $this->max_err as boundaries.\n        //  2) Translate the groups into strings so that\n        //    part# 0 = \'A\', part# 1 = \'B\' etc. to make\n        //    a low resolution approximate search possible later\n        //  3) Save the string indices in the full string\n        //    that correspond to characters in the translated string.\n        //  4) Discard groups (=part sequences) that are too\n        //    short to contain the approximate pattern.\n        // The format of resulting array:\n        //   Array(\n        //    Array( \"<translate-string>\",\n        //           Array( <translated-idx> => <full-index>, ... ) ),\n        //    ... )\n        $transf = Array();\n        $transf_text = \"\";\n        $transf_pos = Array();\n        $last_end = 0;\n        $group_len = 0;\n        reset($part_map);\n\n        while (list($i,$p) = each($part_map)) {\n            if ($i - $last_end > $this->max_part_len+$this->max_err) {\n                if ($group_len >= ($this->n_parts-$this->max_err)) {\n                    $transf[] = Array( $transf_text, $transf_pos );\n                }\n\n                $transf_text = \"\";\n                $transf_pos = Array();\n                $group_len = 0;\n            }\n\n            $transf_text .= chr($p + ord(\"A\"));\n            $transf_pos[] = $i;\n            $group_len++;\n            $last_end = $i + strlen($this->parts[$p]);\n        }\n\n        if (strlen($transf_text) >= ($this->n_parts-$this->max_err)) {\n            $transf[] = Array( $transf_text, $transf_pos );\n        }\n\n        unset($transf_text, $transf_pos);\n\n        if (current($transf) === false) {\n            return Array();\n        }\n\n        // Filter the remaining groups (\"approximate anagrams\"\n        // of the pattern) and leave only the ones that have enough\n        // parts in correct order. You can think of this last step of the\n        // algorithm as a *low resolution* approximate string search.\n        // The result is an array of candidate text spans to be scanned:\n        //   Array( Array(<full-start-idx>, <full-end-idx>), ... )\n        $part_positions = Array();\n\n        while (list(,list($str, $pos_map)) = each($transf)) {\n//          print \"|$transf_patt| - |$str|\\n\";\n            $lores_matches = $this->search_short($this->transf_patt, $this->max_err, $str);\n\n            while (list($tr_end, ) = each($lores_matches)) {\n                $tr_start = max(0, $tr_end - $this->n_parts);\n\n                if ($tr_end >= $tr_start) {\n                    $median_pos = $pos_map[(int)(($tr_start + $tr_end) / 2)];\n                    $start = $median_pos - ($this->patt_len / 2 + 1) - $this->max_err - $this->max_part_len;\n                    $end = $median_pos + ($this->patt_len / 2 + 1) + $this->max_err + $this->max_part_len;\n\n//                  print \"#\" . strtr(substr( $text, $start, $end-$start ), \"\\n\\r\", \"$$\") . \"#\\n\";\n//                  print_r( $this->search_short( &$this->patt, $this->max_err, &$text, $start, $end-$start ));\n\n                    $part_positions[] = Array($start, $end);\n                }\n            }\n            unset( $lores_matches );\n        }\n        unset( $transf );\n\n        if (current($part_positions) === false) {\n            return Array();\n        }\n\n        // Scan the final candidates and put the matches in a new array:\n        $matches = Array();\n        $text_len = strlen($text);\n\n        while (list(, list($start, $end)) = each($part_positions)) {\n            $m = $this->search_short($this->patt, $this->max_err, $text, $start, $end - $start, $text_len);\n\n            while (list($i, $cost) = each($m)) {\n                $matches[$i] = $cost;\n            }\n        }\n\n        unset($part_positions);\n\n        return $matches;\n    }\n}\n}\n\n/* smd_getWord\n\nTakes a string and an offset into that string and returns the nearest \"word\" before that offset position.\nIf the offset is not supplied it starts from the beginning of the string, thus returning the first word.\n\nTakes 3 args:\n# [*] The (usually looong) space-delimited string to look in\n# [*] The word to look for\n# The offset into the string at which to start looking\n*/\n\nif (!function_exists(\"smd_getWord\")) {\nfunction smd_getWord($haystack, $searchterm, $offset = 0)\n{\n    $numwords = str_word_count($searchterm);\n\n    $len = strlen($haystack);\n\n    // If we\'re mid-word, find its end\n    $idx = $offset-1;\n\n    while ($idx < $len && $haystack[$idx] != \" \") {\n        $idx++;\n    }\n\n    $offset = $idx;\n\n    // Move the word we want to the start\n    $haystack = trim(strrev(substr($haystack,0,$offset)));\n\n    // Make sure to return the correct number of words\n    $spacePos = false;\n\n    for ($idx = 0; $idx < $numwords; $idx++) {\n        $spacePos = (strpos($haystack, \" \", $spacePos));\n        if ($spacePos !== false) {\n            $spacePos += 1;\n        }\n    }\n\n    return trim(strrev((($spacePos === false) ? $haystack : substr($haystack, 0, $spacePos))));\n}\n}', 'require_plugin(\'smd_lib\');\n\n// MLP support\nglobal $smd_fuzzLang;\n\n$smd_fuzz_str = array(\n    \'too_short\' => \'The text you are searching for is probably too short. Try a longer word. \',\n    \'no_match\' => \'Sorry, no results matched \"{search_term}\" exactly. \',\n    \'suggest\' => \'Here are the closest matching {thingies} that may help you find what you are looking for: \',\n    \'suggest_join\' => \'and\',\n    \'articles\' => \'articles\',\n    \'words\' => \'words\',\n);\n\n$smd_fuzzLang = new smd_MLP(\'smd_fuzzy_find\', \'smd_fuzz\', $smd_fuzz_str);\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'smd_fuzzy_find\');\n}\n\nfunction smd_fuzzy_find($atts, $thing = \'\')\n{\n    global $pretext, $smd_fuzzLang, $prefs;\n\n    extract(lAtts(array(\n        \'form\'            => \'search_results\',\n        \'section\'         => \'\',\n        \'category\'        => \'\',\n        \'sublevel\'        => \'0\',\n        \'search_term\'     => \'?q\',\n        \'match_with\'      => \'\',\n        \'tolerance\'       => \'2\',\n        \'min_word_length\' => \'4\',\n        \'delim\'           => \',\',\n        \'limit\'           => \'\',\n        \'case_sensitive\'  => \'0\',\n        \'refine\'          => \'\',\n        \'show\'            => \'\',\n        \'status\'          => \'\',\n        \'debug\'           => \'0\',\n        \'no_match_label\'  => \'#\',\n        \'suggest_label\'   => \'#\',\n        \'too_short_label\' => \'#\',\n        \'labeltag\'        => \'p\',\n    ), $atts));\n\n    // Process defaults; most can\'t be set in lAtts() because of the custom delimiter\n    $match_with = empty($match_with) ? \"article:\" . implode(\";\", do_list($prefs[\'searchable_article_fields\'])) : $match_with;\n    $limit = ($limit) ? $limit : \"words:5\" .$delim. \"articles:10\";\n    $refine = ($refine) ? $refine : \"metaphone\" .$delim. \"soundex\";\n    $show = ($show) ? $show : \"words\" .$delim. \"articles\";\n    $status = ($status) ? $status : \"live\" .$delim. \"sticky\";\n\n    $refineAllow = array(\"metaphone\", \"soundex\");\n    $showAllow = array(\"articles\", \"words\");\n    $colNames = array(\n        \'Keywords\'  => \"article:keywords\",\n        \'Body\'      => \"article:body\",\n        \'Excerpt\'   => \"article:excerpt\",\n        \'Category1\' => \"article:category1\",\n        \'Category2\' => \"article:category2\",\n        \'Section\'   => \"article:section\",\n        \'ID\'        => \"article:id\",\n        \'AuthorID\'  => \"article:authorid\",\n        \'Title\'     => \"article:title\",\n        \'message\'   => \"comments:message\",\n        \'email\'     => \"comments:email\",\n        \'name\'      => \"comments:name\",\n        \'web\'       => \"comments:web\",\n    );\n\n    $places = array(\'textpattern\' => \"article\", \'txp_discuss\' => \"comments\");\n    $clause = array();\n    $refineList = array();\n    $dbTables = array();\n    $dbFields = array();\n\n    // Expand the args in case they\'re ? or ! shortcuts, and do some validity checking.\n    $search_term = smd_doList($search_term, false, \"\", false, $delim);\n    $search_term = $search_term[0][0];\n\n    if ($debug > 1) {\n        echo \"++ SEARCH TERM ++\";\n        dmp($search_term);\n    }\n\n    $refine = do_list($refine, $delim);\n\n    for ($idx = 0; $idx < count($refine); $idx++) {\n        if (in_array($refine[$idx], $refineAllow)) {\n            $refineList[$idx] = $refine[$idx];\n        }\n    }\n\n    $meta_search = (in_array(\"metaphone\", $refineList)) ? metaphone($search_term) : \"\";\n    $sound_search = (in_array(\"soundex\", $refineList)) ? soundex($search_term) : \"\";\n    $tolerance = intval($tolerance);\n\n    // match_with needs to be built into a series of arrays of database tables and columns\n    $lookin = smd_split($match_with, false, \":,\\s\");\n\n    // Loop over pairs of elements\n    for ($idx = 0; $idx < count($lookin); $idx += 2) {\n        if (($tmp = array_search($lookin[$idx], $places)) !== false) {\n            $dbTables[] = $tmp;\n            $dbFieldList = smd_split($lookin[$idx+1], false, \";\");\n            $dbField = array();\n\n            foreach ($dbFieldList as $lookField) {\n                $key = array_search($lookin[$idx].\":\".strtolower($lookField), $colNames);\n\n                if ($key) {\n                    $dbField[] = $key;\n                } else if (strpos($lookField, \"custom_\") === 0) {\n                    $dbField[] = $lookField;\n                }\n            }\n\n            if (count($dbField) > 0) {\n                $dbFields[] = $dbField;\n            }\n        }\n    }\n\n    if (count($dbTables) == 0 || count($dbFields) == 0) {\n        $dbTables[] = \"textpattern\";\n        $dbFields[] = \"*\";\n    }\n\n    if ($debug) {\n        echo \"++ FIELDS ++\";\n        dmp($dbFields);\n    }\n\n    $showList = do_list($show, $delim);\n\n    for ($idx = count($showList); $idx > 0; $idx--) {\n        if (!in_array($showList[$idx-1], $showAllow)) {\n            unset($showList[$idx]);\n        }\n    }\n\n    $limitBy = array();\n    $limit = do_list($limit, $delim);\n\n    foreach ($limit as $limOption) {\n        if (is_numeric($limOption)) {\n            $limitBy[\"articles\"] = $limOption;\n            $limitBy[\"words\"] = $limOption;\n        } else {\n            $limsplit = smd_split($limOption, false, \":\");\n\n            if ((count($limsplit) == 2) && (in_array($limsplit[0], $showAllow)) && (is_numeric($limsplit[1]))) {\n                $limitBy[$limsplit[0]] = $limsplit[1];\n            }\n        }\n    }\n\n    $thingiesL10n = array();\n\n    foreach ($showList as $item) {\n        $thingiesL10n[] = $smd_fuzzLang->gTxt($item);\n    }\n\n    $thingies = implode(\" \".$smd_fuzzLang->gTxt(\'suggest_join\').\" \", $thingiesL10n);\n    $no_match_label = ($no_match_label == \"#\") ? $smd_fuzzLang->gTxt(\'no_match\', array(\"{search_term}\" => $search_term)) : $no_match_label;\n    $suggest_label = ($suggest_label == \"#\") ? $smd_fuzzLang->gTxt(\'suggest\', array(\"{thingies}\" => $thingies)) : $suggest_label;\n    $too_short_label = ($too_short_label == \"#\") ? $smd_fuzzLang->gTxt(\'too_short\') : $too_short_label;\n\n    // Roll any status, section and category filters into the initial query\n    $clause[] = \'1=1\';\n\n    if (in_array(\"textpattern\", $dbTables)) {\n        // Status\n        list($statinc, $statexc) = smd_doList($status, false, \'\', false, $delim);\n\n        for ($idx = 0; $idx < count($statinc); $idx++) {\n            $tmpa[] = doQuote(getStatusNum($statinc[$idx]));\n        }\n\n        if ($tmpa) {\n            $clause[] = \'Status IN (\' .implode(\",\", $tmpa). \')\';\n        }\n\n        // Section\n        list($secinc, $secexc) = smd_doList($section, false, \'\', true, $delim);\n\n        if ($secinc) {\n            $clause[] = \'(Section IN (\' .implode(\",\", $secinc). \'))\';\n        }\n\n        // Category\n        list($catinc, $catexc) = smd_doList($category, false, \"article:\".$sublevel, true, $delim);\n\n        if ($catinc) {\n            $imp = implode(\",\", $catinc);\n            $clause[] = \'(Category1 IN (\' .$imp. \') OR Category2 IN (\' .$imp. \'))\';\n        }\n\n        // Combine the query portions\n        $clause = implode(\" AND \", $clause);\n\n        // Add on any exclusions\n        $tmpa = array();\n\n        for ($idx = 0; $idx < count($statexc); $idx++) {\n            $tmpa[] = doQuote(getStatusNum($statexc[$idx]));\n        }\n\n        $clause .= ($tmpa) ? \' AND Status NOT IN (\'.implode(\",\", $tmpa).\')\' : \'\';\n\n        $imp = implode(\",\", $secexc);\n        $clause .= ($secexc) ? \' AND Section NOT IN (\'.$imp.\')\' : \'\';\n\n        $imp = implode(\",\", $catexc);\n        $clause .= ($catexc) ? \' AND Category1 NOT IN (\'.$imp.\') AND Category2 NOT IN (\'.$imp.\')\' : \'\';\n    }\n\n    $clause = is_array($clause) ? join(\" \", $clause) : $clause;\n    //TODO: comments\n/*\n    if (in_array(\"txp_discuss\",$dbTables)) {\n        $clause .= \" AND textpattern.ID = txp_discuss.parentid\";\n    }\n*/\n\n    if ($debug > 0) {\n        echo \"++ WHERE CLAUSE ++\";\n        dmp($clause);\n    }\n\n    $out = \"\";\n    // Perform the searches\n    $finder = new smd_FuzzyFind($search_term, $tolerance);\n\n    if ($finder->too_short_err) {\n        $out .= ($labeltag == \"\") ? \"\" : \"<\" .$labeltag.\">\";\n        $out .= $no_match_label;\n        $out .= $too_short_label;\n        $out .= ($labeltag == \"\") ? \"\" : \"</\" .$labeltag.\">\";\n    } else {\n        $cols = \"*\" . ((in_array(\"textpattern\", $dbTables)) ? \", unix_timestamp(textpattern.Posted) AS uPosted, unix_timestamp(textpattern.LastMod) AS uLastMod, unix_timestamp(textpattern.Expires) AS uExpires\" : \"\");\n        $rs = safe_rows_start($cols, implode($dbTables, \", \"), $clause, $debug);\n\n        if (in_array(\"textpattern\",$dbTables)) {\n            $opform = ($thing) ? $thing : fetch_form($form);\n        }\n\n        $pageurl = smd_removeQSVar($pretext[\'request_uri\'],\'q\');\n        $allFields = \"\";\n        $artList = array();\n        $wordList = array();\n        $termList = array();\n\n        while ($row = nextRow($rs)) {\n            $allFields = \"\";\n            // Join all the required places to look into a looong text block\n            if ($dbFields[0] == \"*\") {\n                foreach ($row as $colname) {\n                    $allFields .= $colname.\" \";\n                }\n            } else {\n                foreach ($dbFields as $fieldRow) {\n                    foreach ($fieldRow as $theField) {\n                        $allFields .= $row[$theField].\" \";\n                    }\n                }\n            }\n\n            // Remove between-word Unicode punctuation and replace with space\n            $allFields = strip_tags($allFields);\n            $allFields = trim(preg_replace(\'#[\\p{P}]+#u\', \' \', $allFields));\n\n            // Split the remainder by (single or multiple) spaces\n            $werds = preg_split(\'/\\s+/\', $allFields, -1, PREG_SPLIT_NO_EMPTY);\n            // ...and reconstitute the unique words as a huge space-delimited string\n            $werds = implode(\" \",array_unique($werds));\n            // Take into account case sensitivity\n            $werds = ($case_sensitive) ? $werds : strtolower($werds);\n\n            // Find close word matches\n            $matches = $finder->search($werds);\n\n            if ($debug > 1) {\n                if ($debug > 3 || $matches) {\n                    echo \"++ UNIQUE WORDS ++\";\n                    dmp($werds);\n                }\n\n                if ($matches) {\n                    echo \"++ CLOSEST MATCHES ++\";\n                    dmp($matches);\n                }\n            }\n\n            if (count($matches) > 0) {\n                $shortestDist = 100; // A stupidly high number to start with\n                $shortestMetaDist = -1;\n                $closestWord = \"\";\n                $closestMetaWord = \"\";\n                $max_term_len = 0;\n\n                // Build a unique array of closest matching words\n                while (list($idx, $dist) = each($matches)) {\n                    $term = smd_getWord($werds, $search_term, $idx);\n\n                    // Only words meeting the minimum requirement need apply\n                    $max_term_len = (strlen($term) > $max_term_len) ? strlen($term) : $max_term_len;\n\n                    if (strlen($term) < $min_word_length) {\n                        continue;\n                    }\n\n                    $term = ($case_sensitive) ? $term : strtolower($term);\n\n                    if ($debug > 2) {\n                        echo $term.br;\n                    }\n\n                    if ($dist < $shortestDist) {\n                        $shortestDist = $dist;\n                        $closestWord = $term;\n                    }\n\n                    if ($meta_search != \"\") {\n                        $meta_term = metaphone($term);\n\n                        if ($debug > 2) {\n                            echo $meta_term . \" : \" . $meta_search .\" \".br;\n                        }\n\n                        $levDist = levenshtein($meta_search, $meta_term);\n\n                        if ($levDist <= $shortestMetaDist || $shortestMetaDist < 0) {\n                            $shortestMetaDist = $levDist;\n                            $closestMetaWord  = $term;\n                        }\n                    }\n                }\n\n                // Pick the one that sounds closest to the original\n                if (trim($closestWord) != \"\") {\n                    $idx = md5($closestWord);\n                    $bestFit = $closestWord;\n                    $bestDist = $shortestDist;\n\n                    if ($sound_search != \"\") {\n                        $sound1 = levenshtein(soundex($closestWord), $sound_search);\n                        $sound2 = levenshtein(soundex($closestMetaWord), $sound_search);\n\n                        if ($sound1 >= $sound2) {\n                            $idx = md5($closestMetaWord);\n                            $bestFit = $closestMetaWord;\n                            $bestDist = $shortestMetaDist;\n                        }\n                    }\n\n                    $wordList[$idx] = $bestFit;\n                    $wordDist[$idx] = $bestDist;\n\n                    if ($debug > 2) {\n                        echo \"++ BEST FIT ++\";\n                        dmp($bestFit);\n                    }\n                }\n\n                // Build an array of unique matching articles\n                if ($max_term_len >= $min_word_length) {\n                    if (in_array(\"textpattern\", $dbTables)) {\n                        populateArticleData($row);\n                    }\n\n                    // Temporarily assign the closest match to the query string so that\n                    // the search_result_excerpt can hilight the found words\n                    $pretext[\'q\'] = $term;\n                    $artList[] = parse($opform);\n                    $pretext[\'q\'] = $search_term;\n                }\n            }\n        }\n\n        // Sort the word list in order of relevance\n        if (count($wordList) > 0) {\n            array_multisort($wordDist,$wordList);\n        }\n\n        // Output stuff to the page\n        $out .= ($labeltag == \"\") ? \"\" : \"<\" .$labeltag.\">\";\n        $out .= $no_match_label;\n\n        if (count($wordList) > 0) {\n            $out .= (count($showList) > 0) ? $suggest_label : \"\";\n\n            if (in_array(\"words\", $showList)) {\n                $ctr = 0;\n\n                foreach ($wordList as $item) {\n                    if (array_key_exists(\"words\", $limitBy) && $ctr >= $limitBy[\"words\"]) {\n                        break;\n                    }\n\n                    $out .= \'<a class=\"smd_fuzzy_suggest\" href=\"\'.smd_addQSVar($pageurl,\'q\',$item).\'\">\'.$item.\'</a>\'.n;\n                    $ctr++;\n                }\n            }\n\n            $out .= ($labeltag == \"\") ? \"\" : \"</\" .$labeltag.\">\";\n\n            if (in_array(\"articles\", $showList)) {\n                $ctr = 0;\n\n                foreach ($artList as $art) {\n                    if (array_key_exists(\"articles\", $limitBy) && $ctr >= $limitBy[\"articles\"]) {\n                        break;\n                    }\n\n                    $out .= $art;\n                    $ctr++;\n                }\n            }\n        }\n    }\n    return $out;\n}\n\n\n/* smd_FuzzyFind\nA PHP class for approximate string searching of large text masses, adapted (*cough* borrowed) from http://elonen.iki.fi/code/misc-notes/appr-search-php/. Instantiate one of these and pass it the string pattern/word you are looking for and a number indicating how close that match has to be/minimum length of strings to consider (i.e. the amount of error tolerable). 0=close match/short words; 10=pretty much every long (10 char minimum) string in the world. Practical values are usually 1 or 2, sometimes 3.\n\nUsage example:\n  $finder = new smd_FuzzyFind($patt, $max_err);\n  if ($finder->too_short_err)\n    $error = \"Unable to search: use longer pattern or reduce error tolerance.\";\n\n  while($text = get_next_page_of_text()) {\n    $matches = $finder->search($text);\n    while(list($idx,$rng) = each($matches))\n      print \"Match found ending at position $idx with a closeness of $val\\n\";\n  }\n\nThe code uses initial filtering to sort out possible match candidates and then applies a slower character-by-character search (search_short()) against them.\n*/\n\nif(!class_exists(\'smd_FuzzyFind\')) {\nclass smd_FuzzyFind {\n    // The last 3 parameters are for optimization only, to avoid the\n    // surprisingly slow strlen() and substr() calls:\n    //  - $start_index = from which character of $text to start the search\n    //  - $max_len = maximum character to search (starting from $start_index)\n    //  - $text_strlen =\n    // The return value is an array of matches:\n    //   Array( [<match-end-index>] => <error>, ... )\n    // Note: <error> is generally NOT an exact edit distance but rather a\n    // lower bound. This is unfortunate but the routine would be slower if\n    // the exact error was calculate along with the matches.\n    // The function is based on the non-deterministic automaton simulation\n    // algorithm (without bit parallelism optimizations).\n    function search_short($patt, $k, $text, $start_index = 0, $max_len = -1, $text_strlen = -1)\n    {\n        if ($text_strlen < 0) {\n            $text_strlen = strlen($text);\n        }\n\n        if ($max_len < 0) {\n            $max_len = $text_strlen;\n        }\n\n        $start_index = (int)max(0, $start_index);\n        $n = min($max_len, $text_strlen - $start_index);\n        $m = strlen($patt);\n        $end_index = $start_index + $n;\n\n        // If $text is shorter than $patt, use the built-in\n        // levenshtein() instead:\n        if ($n < $m) {\n            $lev = levenshtein(substr($text, $start_index, $n), $patt);\n\n            if ($lev <= $k) {\n                return Array($start_index + $n - 1 => $lev);\n            } else {\n                return Array();\n            }\n        }\n\n        $s = Array();\n\n        for ($i = 0; $i < $m; $i++) {\n            $c = $patt{$i};\n\n            if (isset($s[$c])) {\n                $s[$c] = min($i, $s[$c]);\n            } else {\n                $s[$c] = $i;\n            }\n        }\n\n        if ($end_index < $start_index) {\n            return Array();\n        }\n\n        $matches = Array();\n        $da = $db = range(0, $m - $k + 1);\n\n        $mk = $m - $k;\n\n        for ($t = $start_index; $t < $end_index; $t++) {\n            $c = $text{$t};\n            $in_patt = isset($s[$c]);\n\n            if ($t & 1) {\n                $d = &$da;\n                $e = &$db;\n            } else {\n                $d = &$db;\n                $e = &$da;\n            }\n\n            for ($i = 1; $i <= $mk; $i++) {\n                $g = min($k + 1, $e[$i] + 1, $e[$i + 1] + 1);\n\n                // TODO: optimize this with a look-up-table?\n                if ($in_patt)\n                    for ($j = $e[$i - 1]; ($j < $g && $j <= $mk); $j++) {\n                        if ($patt{$i + $j - 1} == $c) {\n                            $g = $j;\n                        }\n                    }\n\n                $d[$i] = $g;\n            }\n\n            if ($d[$mk] <= $k) {\n                $err = $d[$mk];\n                $i = min($t-$err + $k + 1, $start_index + $n - 1);\n\n                if (!isset($matches[$i]) || $err < $matches[$i]) {\n                    $matches[$i] = $err;\n                }\n            }\n        }\n\n        unset($da, $db);\n\n        return $matches;\n    }\n\n    function test_short_search()\n    {\n        $test_text = \"Olipa kerran jussi bj&xling ja kolme\\n iloista \".\n            \"jussi bforling:ia mutta ei yhtaan jussi bjorling-nimista laulajaa.\";\n        $test_patt = \"jussi bjorling\";\n        assert( $this->search_short($test_patt, 4, $test_text) == Array(27=>2, 60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 2, $test_text) == Array(27=>2, 60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 1, $test_text) == Array(60=>1, 94=>0));\n        assert( $this->search_short($test_patt, 0, $test_text) == Array(94=>0));\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 19, 7) == Array());\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 19, 8) == Array(26=>2));\n        assert( $this->search_short(\"bjorling\", 2, $test_text, 20, 8) == Array());\n    }\n\n    var $patt, $patt_len, $max_err;\n    var $parts, $n_parts, $unique_parts, $max_part_len;\n    var $transf_patt;\n    var $too_short_err;\n\n    function __construct ($pattern, $max_error)\n    {\n        $this->patt = $pattern;\n        $this->patt_len = strlen($this->patt);\n        $this->max_err = $max_error;\n\n        // Calculate pattern partition size\n        $intpartlen = floor($this->patt_len / ($this->max_err + 2));\n\n        if ($intpartlen < 1) {\n            $this->too_short_err = true;\n            return;\n        } else {\n            $this->too_short_err = false;\n        }\n\n        // Partition the pattern for pruning\n        $this->parts = Array();\n\n        for ($i = 0; $i < $this->patt_len; $i += $intpartlen) {\n            if ($i + $intpartlen * 2 > $this->patt_len) {\n                $this->parts[] = substr($this->patt, $i);\n                break;\n            } else {\n                $this->parts[] = substr($this->patt, $i, $intpartlen);\n            }\n        }\n\n        $this->n_parts = count($this->parts);\n\n        // The intpartlen test above should have covered this:\n        assert($this->n_parts >= $this->max_err + 1);\n\n        // Find maximum part length\n        foreach ($this->parts as $p) {\n            $this->max_part_len = max( $this->max_part_len, strlen($p));\n        }\n\n        // Make a new part array with duplicate strings removed\n        $this->unique_parts = array_unique($this->parts);\n\n        // Transform the pattern into a low resolution pruning string\n        // by replacing parts with single characters\n        $this->transf_patt = \"\";\n        reset($this->parts);\n\n        while (list(,$p) = each($this->parts)) {\n           $this->transf_patt .= chr(array_search($p, $this->unique_parts) + ord(\"A\"));\n        }\n\n        // Self diagnostics\n        $this->test_short_search();\n    }\n\n    function search($text)\n    {\n        // Find all occurrences of unique parts in the\n        // full text. The result is an array:\n        //   Array( <index> => <part#>, .. )\n        $part_map = Array();\n        reset($this->unique_parts);\n\n        while (list($pi, $part_str) = each($this->unique_parts)) {\n            $pos = strpos($text, $part_str);\n\n            while ($pos !== false) {\n                $part_map[$pos] = $pi;\n                $pos = strpos($text, $part_str, $pos+1);\n            }\n        }\n\n        ksort($part_map); // Sort by string index\n\n        // The following code does several things simultaneously:\n        //  1) Divide the indices into groups using gaps\n        //    larger than $this->max_err as boundaries.\n        //  2) Translate the groups into strings so that\n        //    part# 0 = \'A\', part# 1 = \'B\' etc. to make\n        //    a low resolution approximate search possible later\n        //  3) Save the string indices in the full string\n        //    that correspond to characters in the translated string.\n        //  4) Discard groups (=part sequences) that are too\n        //    short to contain the approximate pattern.\n        // The format of resulting array:\n        //   Array(\n        //    Array( \"<translate-string>\",\n        //           Array( <translated-idx> => <full-index>, ... ) ),\n        //    ... )\n        $transf = Array();\n        $transf_text = \"\";\n        $transf_pos = Array();\n        $last_end = 0;\n        $group_len = 0;\n        reset($part_map);\n\n        while (list($i,$p) = each($part_map)) {\n            if ($i - $last_end > $this->max_part_len+$this->max_err) {\n                if ($group_len >= ($this->n_parts-$this->max_err)) {\n                    $transf[] = Array( $transf_text, $transf_pos );\n                }\n\n                $transf_text = \"\";\n                $transf_pos = Array();\n                $group_len = 0;\n            }\n\n            $transf_text .= chr($p + ord(\"A\"));\n            $transf_pos[] = $i;\n            $group_len++;\n            $last_end = $i + strlen($this->parts[$p]);\n        }\n\n        if (strlen($transf_text) >= ($this->n_parts-$this->max_err)) {\n            $transf[] = Array( $transf_text, $transf_pos );\n        }\n\n        unset($transf_text, $transf_pos);\n\n        if (current($transf) === false) {\n            return Array();\n        }\n\n        // Filter the remaining groups (\"approximate anagrams\"\n        // of the pattern) and leave only the ones that have enough\n        // parts in correct order. You can think of this last step of the\n        // algorithm as a *low resolution* approximate string search.\n        // The result is an array of candidate text spans to be scanned:\n        //   Array( Array(<full-start-idx>, <full-end-idx>), ... )\n        $part_positions = Array();\n\n        while (list(,list($str, $pos_map)) = each($transf)) {\n//          print \"|$transf_patt| - |$str|\\n\";\n            $lores_matches = $this->search_short($this->transf_patt, $this->max_err, $str);\n\n            while (list($tr_end, ) = each($lores_matches)) {\n                $tr_start = max(0, $tr_end - $this->n_parts);\n\n                if ($tr_end >= $tr_start) {\n                    $median_pos = $pos_map[(int)(($tr_start + $tr_end) / 2)];\n                    $start = $median_pos - ($this->patt_len / 2 + 1) - $this->max_err - $this->max_part_len;\n                    $end = $median_pos + ($this->patt_len / 2 + 1) + $this->max_err + $this->max_part_len;\n\n//                  print \"#\" . strtr(substr( $text, $start, $end-$start ), \"\\n\\r\", \"$$\") . \"#\\n\";\n//                  print_r( $this->search_short( &$this->patt, $this->max_err, &$text, $start, $end-$start ));\n\n                    $part_positions[] = Array($start, $end);\n                }\n            }\n            unset( $lores_matches );\n        }\n        unset( $transf );\n\n        if (current($part_positions) === false) {\n            return Array();\n        }\n\n        // Scan the final candidates and put the matches in a new array:\n        $matches = Array();\n        $text_len = strlen($text);\n\n        while (list(, list($start, $end)) = each($part_positions)) {\n            $m = $this->search_short($this->patt, $this->max_err, $text, $start, $end - $start, $text_len);\n\n            while (list($i, $cost) = each($m)) {\n                $matches[$i] = $cost;\n            }\n        }\n\n        unset($part_positions);\n\n        return $matches;\n    }\n}\n}\n\n/* smd_getWord\n\nTakes a string and an offset into that string and returns the nearest \"word\" before that offset position.\nIf the offset is not supplied it starts from the beginning of the string, thus returning the first word.\n\nTakes 3 args:\n# [*] The (usually looong) space-delimited string to look in\n# [*] The word to look for\n# The offset into the string at which to start looking\n*/\n\nif (!function_exists(\"smd_getWord\")) {\nfunction smd_getWord($haystack, $searchterm, $offset = 0)\n{\n    $numwords = str_word_count($searchterm);\n\n    $len = strlen($haystack);\n\n    // If we\'re mid-word, find its end\n    $idx = $offset-1;\n\n    while ($idx < $len && $haystack[$idx] != \" \") {\n        $idx++;\n    }\n\n    $offset = $idx;\n\n    // Move the word we want to the start\n    $haystack = trim(strrev(substr($haystack,0,$offset)));\n\n    // Make sure to return the correct number of words\n    $spacePos = false;\n\n    for ($idx = 0; $idx < $numwords; $idx++) {\n        $spacePos = (strpos($haystack, \" \", $spacePos));\n        if ($spacePos !== false) {\n            $spacePos += 1;\n        }\n    }\n\n    return trim(strrev((($spacePos === false) ? $haystack : substr($haystack, 0, $spacePos))));\n}\n}', 'bcdf782e3c743ae57dbaff52ff4103ba', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('geo_vote', 0, 'Geoff Forster', '', '0.1.4', 'Voting - Poll Plugin', '<div id=\"geo_help\">\n\n    <h1>geo_votes</h1>\n\n    <p>Poll - Ballot - Voting plugin</p>\n\n    <h2>Features</h2>\n\n    <ul>\n        <li>Create votes/polls/ballots for your website.</li>\n        <li>Vote Info is added/edited on the Admin-&gt;Vote tab.</li>\n        <li>Presently up to 10 vote options can be set.</li>\n        <li>Ballot fraud check methods include a choice of cookie, ip, login. Cookie (default) reverts to IP if cookie can\'t be set.</li>\n        <li>A closing date and time for your ballot can be set. Leave blank or set to \'00-00-00 00:00:00\' for a never ending vote.</li>\n        <li>Option to show results before voting closes or not. (Unless never-ending vote chosen)</li>\n    </ul>\n\n    <h2>Author / Credits</h2>\n\n    <p>geoff777 - thanks to Stef Dawson (Bloke) for his amazing code which helped me to understand a little about the anatomy of a plugin. Thanks to to Jukka (Gocom) for making the plugin more secure.</p>\n\n    <h2>Installation / Uninstallation</h2>\n\n    <p class=\"required\">Requires Textpattern ? - it was written using 4.4.1 but may well work in earlier versions?</p>\n\n    <p>E-mail geoff777 for copies of the plugin to alpha test.</p>\n\n    <p>When you first visit the Extension-&gt;Vote page, the extra tables wil be installed automatically.</p>\n\n    <p>To uninstall, simply delete from the Admin -&gt; Plugins page. But note the extra tables will remain until you delete them manually. At some future point it may clean up it\'s own mess, think of the plugin as a teenage boy ... just be thankful it gets out of bed and does anything at all.</p>\n\n    <h2>Configuring Vote details</h2>\n\n    <p>Visit the Extensions-&gt;Vote config tab. Add votes with a title at least two options plus an end date time unless you require a never ending poll. CSS classes can be added to make the plugin\'s output fit in with your site.</p>\n\n    <h2>How to Use</h2>\n\n        <h3><code>&lt;txp:geo_votes  vote_id=&quot;integer&quot; /&gt;</code></h3>\n\n    <p>That\'s it! One tag with one attribute.</p>\n\n    <p><code>vote_id</code> is generated by auto_increment when you add a new vote on the Extensions&gt;Vote tab. Just put the vote_id in the tag and your vote will appear.</p>\n\n        <p>Example:- <code>&lt;txp:geo_votes vote_id=&quot;1&quot; /&gt;</code></p>\n\n    <p>The tag can be used anywhere. In articles, article lists, sidebars, footers or headers.</p>\n\n        <p>Possible problems:- pagetop() error - you\'ve probably used geo_vote instead of geo_votes. (I want the public tag to be geo_vote but an admin function uses geo_vote and when I change it the plugin rolls over and dies. If anyone can assist with this I\'d like the public tag to have the same name as the plugin.)</p>\n\n        <p>The plugin doesn\'t sort the results, this option will probably be added.</p>\n        <p>The plugin currently has a fixed number of vote options (10). I realise this should be more flexible but I wanted to get the plugin working, so I\'ve written it knowing it has limitations that I will change in future releases.</p>\n\n\n        <h2>CSS</h2>\n        <p>Add your own CSS classes in the Extensions Vote tab. They are named to be fairly obvious. One class for the results bars is hard coded, You can now  (0.1.4) change the colour.</p>\n\n    <h2>Changelog</h2>\n\n    <ul>\n        <li>22 Feb 2012 | 0.1.3 | Initial (non-public) release</li>\n        <li>28 Feb 2012 | 0.1.4 | Fixed lots of security issues - thanks Gocom - Removed \'none\' from Fraud methods. Now a fraud method must be used. If Cookie can\'t be set IP is now stored. Now only one cookie is used and a hash value is stored. XHTML now validates. Ballot form post variables are now cast as integers. Changed dBase names. Added fields. Vote totals now stored instead of dynamically calculated. The ballot form now times out after 10 minutes and a hash is included that must validate. A new table has been added to hold the hash keys. This table is automatically \'cleaned\' of old values. A new field means you can change the colour of the results bar graph.</li>\n    </ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#geo_help { line-height:1.5 ;}\n#geo_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n#geo_help code.block { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n#geo_help h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n#geo_help h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n#geo_help h3 { color: #275685; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;}\n#geo_help h4 { font: bold 11px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0 ;text-transform: uppercase; }\n#geo_help .atnm { font-weight:bold; color:#33d; }\n#geo_help .mand { background:#eee; border:1px dotted #999; }\n#geo_help table {width:90%; text-align:center; padding-bottom:1em;}\n#geo_help td, #geo_help th {border:1px solid #999; padding:.5em 0;}\n#geo_help ul { list-style-type:square; }\n#geo_help .required {color:red;}\n#geo_help li { margin:5px 20px 5px 30px; }\n#geo_help .break { margin-top:5px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\n	add_privs(\'geo_vote\',\'1\');\r\n\r\n	// Extensions tab\r\n	register_tab(\'extensions\', \'geo_vote\', geo_vote_gTxt(\'geo_vote\'));\r\n	register_callback(\'geo_vote\', \'geo_vote\');\r\n}\r\n\r\nif (!defined(\'GEO_VOTE_CHEAT\')) {\r\n	define(\"GEO_VOTE_CHEAT\", \'geo_vote_cheat\');\r\n}\r\nif (!defined(\'GEO_VOTE_META\')) {\r\n	define(\"GEO_VOTE_META\", \'geo_vote_meta\');\r\n}\r\nif (!defined(\'GEO_VOTE_FORM\')) {\r\n	define(\"GEO_VOTE_FORM\", \'geo_vote_form\');\r\n}\r\n// ************************\r\n// Vote CONFIGURATION\r\n// ------------------------\r\n// Tab handler\r\nfunction geo_vote($event, $step) {\r\n$available_steps = array(\r\n			\'geo_vote_meta_add\' 	=> false,\r\n			\'geo_vote_meta_del\' 	=> true,\r\n			\'geo_vote_meta_save\' 	=> true\r\n			);\r\n	if(!$step or !bouncer($step, $available_steps)) {\r\n			geo_vote_config(\'\');\r\n            }\r\n	else $step();\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_config($message) {\r\n	if (!geo_vote_table_exist()) {\r\n		geo_vote_install(\'\');\r\n		exit;\r\n	}\r\n\r\n	pagetop(geo_vote_gTxt(\'admin_tab\'), $message);\r\n\r\n	echo geo_vote_meta_edit();\r\n	echo geo_vote_meta_list();\r\n}\r\n\r\n\r\nfunction geo_vote_meta_edit() {\r\n\r\nglobal $cheat_method_types;\r\n$cheat_method_types = array(\r\n		\'cookie\' => \'cookie\',\r\n		\'ip\' => \'ip\',\r\n		\'login\' => \'login\'\r\n		);\r\nglobal $show_result_types;\r\n$show_result_types = array(\r\n		\'No\' => \'No\',\r\n		\'Yes\' => \'Yes\'\r\n	);\r\n\r\n	$vars = array(\'step\', \'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\');\r\n	$rs = array();\r\n	\r\n	extract(doSlash(gpsa($vars)));\r\n	if ($vote_id && $step == \'geo_vote_meta_edit\') {\r\n		$rs = safe_row(\'*\', GEO_VOTE_META, \"vote_id = $vote_id\");\r\n		extract($rs);\r\n	}\r\n\r\n	if ($step == \'geo_vote_meta_save\' || $step == \'geo_vote_meta_add\' || $step == \'geo_vote_meta_del\') {\r\n		foreach ($vars as $var) {\r\n			$$var = \'\';\r\n		}\r\n	}\r\n\r\n	$caption = geo_vote_gTxt(($step == \'geo_vote_meta_edit\') ? \'geo_vote_meta_edit\' : \'geo_vote_meta_add\');\r\n\r\n	// Make the name/val pairs for selectInput\r\n	$selu = array();\r\n	foreach ($show_result_types as $widx => $wval) {\r\n		$selu[$widx] = $wval;\r\n	}\r\n	$selv = array();\r\n	foreach ($cheat_method_types as $widx => $wval) {\r\n		$selv[$widx] = $wval;\r\n	}\r\n	//add some defaults\r\n	if (!$vote_bar_colour) $vote_bar_colour = \'LightBlue\';\r\n	if (!$end_date_time) $end_date_time = \'0000-00-00 00:00:00\';\r\n	if (!$show_results) $show_results = \'Yes\';\r\n	if (!$cheat_method) $cheat_method = \'cookie\';\r\n	if (!$vote_title_class) $vote_title_class = \'vote_title_class\';\r\n	if (!$option_title_class) $option_title_class = \'option_title_class\';\r\n	if (!$end_date_time_class) $end_date_time_class = \'end_date_time_class\';\r\n	if (!$vote_button_class) $vote_button_class = \'vote_button_class\';\r\n\r\n	return form(\r\n		hed($caption, 3,\' style=\"text-align: center;\"\').\r\n\r\n		startTable(\'edit\', \'\', \'edit-pane\').\r\n//TR BELOW WAS ($vote_id && $step == \'geo_vote_meta_edit\' ? td(strong($vote_id)) : fInputCell(\'vote_id\', $vote_id)) need to take the or out its redundant\r\n		tr(\r\n			($vote_id && $step == \'geo_vote_meta_edit\' ? fLabelCell(\'vote_id\').td(strong($vote_id)) : fLabelCell(\'&nbsp;\').fLabelCell(\'&nbsp;\')).\r\ntd(fLabelCell(\'&nbsp;\')).\r\ntd(fLabelCell(\'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\'))\r\n		).\r\n\r\n		tr(\r\n			fLabelCell(\'Vote Title\').\r\n			fInputCell(\'vote_title\', $vote_title)\r\n		).\r\n// future :- put loop in here\r\n		tr(\r\n			fLabelCell(\'Option 1 Title\').\r\n			fInputCell(\'option_title_1\', $option_title_1)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'Option 2 Title\').\r\n			fInputCell(\'option_title_2\', $option_title_2)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 3 Title\').\r\n			fInputCell(\'option_title_3\', $option_title_3)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 4 Title\').\r\n			fInputCell(\'option_title_4\', $option_title_4)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 5 Title\').\r\n			fInputCell(\'option_title_5\', $option_title_5)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 6 Title\').\r\n			fInputCell(\'option_title_6\', $option_title_6)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 7 Title\').\r\n			fInputCell(\'option_title_7\', $option_title_7)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 8 Title\').\r\n			fInputCell(\'option_title_8\', $option_title_8)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 9 Title\').\r\n			fInputCell(\'option_title_9\', $option_title_9)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 10 Title\').\r\n			fInputCell(\'option_title_10\', $option_title_10)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'Vote Bar Colour\').\r\n			fInputCell(\'vote_bar_colour\', $vote_bar_colour).\r\n			td(fLabelCell(\'default = #ADD8E6 / LightBlue include # or use a valid color name\'))\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Vote Closing Date & Time\').\r\n			fInputCell(\'end_date_time\', $end_date_time).\r\ntd(fLabelCell(\'Format YYYY-MM-DD HH:MM:SS &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\'))\r\n		).\r\n		\r\n				\r\n		tr(\r\n\r\n			fLabelCell(\'Show Results Before Vote Closes\').\r\n			td(selectInput(\'show_results\', $selu, $show_results, false))\r\n		).		\r\n		tr(\r\n			fLabelCell(\'Voting Fraud Prevention Method\').\r\n			td(selectInput(\'cheat_method\', $selv, $cheat_method, false))\r\n\r\n		).\r\n		tr(\r\n			fLabelCell(\'===========\')\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'CSS classes\')\r\n		).\r\n		tr(\r\n			fLabelCell(\'vote_title_class\').\r\n			fInputCell(\'vote_title_class\', $vote_title_class)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'option_title_class\').\r\n			fInputCell(\'option_title_class\', $option_title_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			fLabelCell(\'end_date_time_class\').\r\n			fInputCell(\'end_date_time_class\', $end_date_time_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			fLabelCell(\'vote_button_class\').\r\n			fInputCell(\'vote_button_class\', $vote_button_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			td().\r\n			td(\r\n				fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')\r\n			)\r\n		).\r\n\r\n		endTable().\r\n\r\n		eInput(\'geo_vote\').\r\n		($vote_id ? hInput(\'vote_id\', $vote_id).sInput(\'geo_vote_meta_save\') : sInput(\'geo_vote_meta_add\'))\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_list() {\r\n\r\n	extract(doSlash(gpsa(array(\'sort\', \'dir\', \'crit\', \'search_method\'))));\r\n	if ($sort === \'\') $sort = get_pref(\'geo_vote_meta_sort_column\', \'vote_id\');\r\n	if ($dir === \'\') $dir = get_pref(\'geo_vote_meta_sort_dir\', \'asc\');\r\n	$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\r\n\r\n	if (!in_array($sort, array(\'vote_id\', \'vote_title\', \'show_results\', \'cheat_method\', \'end_date_time\'))) $sort = \'vote_id\';\r\n\r\n	$sort_sql   = $sort.\' \'.$dir;\r\n\r\n	set_pref(\'geo_vote_meta_sort_column\', $sort, \'geo_vote\', 2, \'\', 0, PREF_PRIVATE);\r\n	set_pref(\'geo_vote_meta_sort_dir\', $dir, \'geo_vote\', 2, \'\', 0, PREF_PRIVATE);\r\n\r\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n	$rs = safe_rows_start(\'*\', GEO_VOTE_META, \'1 = 1 ORDER BY \'.$sort_sql);\r\n\r\n	if ($rs) {\r\n		echo \'<form action=\"index.php\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\r\n\r\n		startTable(\'list\').\r\n		tr(\r\n			column_head(\'vote_id\', \'vote_id\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'vote_id\' == $sort) ? $dir : \'\').\r\n			column_head(\'vote_title\', \'vote_title\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'vote_title\' == $sort) ? $dir : \'\').\r\n			column_head(\'show_results\', \'show_results\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'show_results\' == $sort) ? $dir : \'\').\r\n			column_head(\'cheat_method\', \'cheat_method\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'cheat_method\' == $sort) ? $dir : \'\').\r\n			column_head(\'end_date_time\', \'end_date_time\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'end_date_time\' == $sort) ? $dir : \'\').\r\n			hCell().\r\n			hCell()\r\n		);\r\n\r\n		while ($a = nextRow($rs)) {\r\n			extract(doSpecial($a));\r\n\r\n			echo tr(\r\n				td($vote_id).\r\n				td($vote_title).\r\n				td($show_results).\r\n				td($cheat_method).\r\n				td($end_date_time).\r\n				td(eLink(\'geo_vote\', \'geo_vote_meta_edit\', \'vote_id\', $vote_id, gTxt(\'edit\'))).\r\n				td(dLink(\'geo_vote\', \'geo_vote_meta_del\', \'vote_id\', $vote_id, 1))\r\n			);\r\n		}\r\n\r\n		echo endTable().\'</form>\';\r\n	}\r\n}\r\n\r\n// ------------------------ Can all the option_title_1 - 10 be looped here?\r\nfunction geo_vote_meta_save() {\r\n	extract(doSlash(psa(array(\'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\'))));\r\n	\r\n	$rs = safe_update(GEO_VOTE_META, \"\r\n				vote_title = \'$vote_title\',\r\n				vote_title_class = \'$vote_title_class\',\r\n				option_title_1 = \'$option_title_1\',\r\n				option_title_2 = \'$option_title_2\',\r\n				option_title_3 = \'$option_title_3\',\r\n				option_title_4 = \'$option_title_4\',\r\n				option_title_5 = \'$option_title_5\',\r\n				option_title_6 = \'$option_title_6\',\r\n				option_title_7 = \'$option_title_7\',\r\n				option_title_8 = \'$option_title_8\',\r\n				option_title_9 = \'$option_title_9\',\r\n				option_title_10 = \'$option_title_10\',\r\n				vote_bar_colour = \'$vote_bar_colour\',				\r\n				option_title_class = \'$option_title_class\',\r\n				end_date_time = \'$end_date_time\',\r\n				end_date_time_class = \'$end_date_time_class\',\r\n				vote_button_class = \'$vote_button_class\',\r\n				show_results = \'$show_results\',\r\n				cheat_method = \'$cheat_method\'\",\r\n				\"vote_id = \'$vote_id\'\"\r\n			);\r\n\r\n	if ($rs) {\r\n		geo_vote_config(\r\n			geo_vote_gTxt(\'meta_updated\', array(\'{vote_id}\' => $vote_id))\r\n		);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_add() {\r\n	extract(doSlash(psa(array(\'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\'))));\r\n\r\n			$rs = safe_insert(GEO_VOTE_META, \"\r\n				vote_id = \'$vote_id\',\r\n				vote_title = \'$vote_title\',\r\n				vote_title_class = \'$vote_title_class\',\r\n				option_title_1 = \'$option_title_1\',\r\n				option_title_2 = \'$option_title_2\',\r\n				option_title_3 = \'$option_title_3\',\r\n				option_title_4 = \'$option_title_4\',\r\n				option_title_5 = \'$option_title_5\',\r\n				option_title_6 = \'$option_title_6\',\r\n				option_title_7 = \'$option_title_7\',\r\n				option_title_8 = \'$option_title_8\',\r\n				option_title_9 = \'$option_title_9\',\r\n				option_title_10 = \'$option_title_10\',\r\n				vote_bar_colour = \'$vote_bar_colour\',		\r\n				option_title_class = \'$option_title_class\',\r\n				end_date_time = \'$end_date_time\',\r\n				end_date_time_class = \'$end_date_time_class\',\r\n				vote_button_class = \'$vote_button_class\',\r\n				show_results = \'$show_results\',\r\n				cheat_method = \'$cheat_method\'\"\r\n			);\r\n\r\n			if ($rs) {\r\n				geo_vote_config(geo_vote_gTxt(\'meta_added\'));\r\n				return;\r\n			}\r\n					\r\n	\r\n	geo_vote_config(array(geo_vote_gTxt(\'meta_not_added\'), E_ERROR));\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_del() {\r\n	extract(doSlash(psa(array(\'vote_id\'))));\r\n	$ret = @safe_delete(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\'\");\r\n	if ($ret) {\r\n		$ret = safe_delete(GEO_VOTE_META, \"vote_id=\'$vote_id\'\");\r\n		$message = geo_vote_gTxt(\'meta_deleted\');\r\n	}\r\n\r\n	geo_vote_config($message);\r\n}\r\n\r\n\r\n// ************************\r\n// TABLE GENERATION\r\n// ------------------------\r\nfunction geo_vote_table_exist() {\r\n	if (gps(\'debug\')) {\r\n		echo \"++ TABLE \".GEO_VOTE_META.\" HAS \".count(@safe_show(\'columns\', GEO_VOTE_META)).\" COLUMNS;\";\r\n	}\r\n	return(@safe_show(\'columns\', GEO_VOTE_META));\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_install($message) {\r\n\r\n	$version = mysql_get_server_info();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if ($version < \"4.1.2\") {\r\n		$GLOBALS[\'txp_err_count\']++;\r\n		trigger_error(\"geo_vote requires MySQL v4.1.2 or greater.\");\r\n	} else {\r\n		$ret = \'\';\r\n		$sql = array();\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_CHEAT.\"` (\r\n						`pk` int(4) NOT NULL auto_increment PRIMARY KEY,\r\n			`vote_id` int(4) NOT NULL default 0,\r\n			`option` int(4) NOT NULL default 0,\r\n			`cookie_ip_login` varchar(64) NULL default \'\',\r\n			INDEX (vote_id)\r\n						) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=1\";\r\n						\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_FORM.\"` (\r\n			`hash_key` varchar(32) NOT NULL PRIMARY KEY,\r\n			`t_stamp` TIMESTAMP NOT NULL default CURRENT_TIMESTAMP\r\n						) ENGINE=MyISAM PACK_KEYS=1\";\r\n\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_META.\"` (\r\n			`vote_id` int(4) NOT NULL auto_increment PRIMARY KEY,\r\n			`vote_title` varchar(64) NULL default \'\',\r\n			`option_title_1` varchar(64) NULL default \'\',\r\n			`option_title_2` varchar(64) NULL default \'\',\r\n			`option_title_3` varchar(64) NULL default \'\',\r\n			`option_title_4` varchar(64) NULL default \'\',\r\n			`option_title_5` varchar(64) NULL default \'\',\r\n			`option_title_6` varchar(64) NULL default \'\',\r\n			`option_title_7` varchar(64) NULL default \'\',\r\n			`option_title_8` varchar(64) NULL default \'\',\r\n			`option_title_9` varchar(64) NULL default \'\',\r\n			`option_title_10` varchar(64) NULL default \'\',\r\n			`option_1` int(4) NOT NULL default 0,\r\n			`option_2` int(4) NOT NULL default 0,\r\n			`option_3` int(4) NOT NULL default 0,\r\n			`option_4` int(4) NOT NULL default 0,\r\n			`option_5` int(4) NOT NULL default 0,\r\n			`option_6` int(4) NOT NULL default 0,\r\n			`option_7` int(4) NOT NULL default 0,\r\n			`option_8` int(4) NOT NULL default 0,\r\n			`option_9` int(4) NOT NULL default 0,\r\n			`option_10` int(4) NOT NULL default 0,\r\n			`total_votes` int(4) NOT NULL default 0,\r\n			`show_results` varchar(3) NOT NULL default \'\',\r\n			`cheat_method` varchar(6) NOT NULL default \'\',\r\n			`end_date_time` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\r\n			`vote_bar_colour` varchar(32) NOT NULL default \'\',\r\n			`vote_title_class` varchar(64) NOT NULL default \'\',\r\n			`option_title_class` varchar(64) NOT NULL default \'\',\r\n			`end_date_time_class` varchar(64) NOT NULL default \'\',\r\n			`vote_button_class` varchar(64) NOT NULL default \'\'\r\n							) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=1\";\r\n\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$message = geo_vote_gTxt(\'tbl_installed\');\r\n		geo_vote_config($message);\r\n	} else {\r\n		$message = geo_vote_gTxt(\'tbl_not_installed\');\r\n		geo_vote_config($message);\r\n	}\r\n}\r\n\r\n\r\n// ************************\r\n// CLIENT-SIDE TAGS\r\n// ------------------------\r\nfunction geo_votes($atts) {\r\n	\r\n	// extract / declare variables\r\n		extract(lAtts(array(\r\n			\'vote_id\'	=> \'1\'\r\n		), $atts));\r\n		$out = array();\r\n		$geo_vote_form_id = 0;\r\n		$vars = array(\'geo_vote_step\',\'geo_vote_option\',\'geo_vote_form_id\', \'hash_key\');\r\n		extract(doSlash(psa($vars)));\r\n		$geo_vote_form_id = (int)$geo_vote_form_id;\r\n		$geo_vote_option = (int)$geo_vote_option;\r\n		\r\n				\r\n    //If the vote was cast for another vote on the same page - ignore it and show ballot\r\n		if($geo_vote_form_id != $vote_id) $geo_vote_step = \'show_ballot\';\r\n			\r\n	//If step isn\'t vote_cast set step to show_ballot - should help security\r\n		if($geo_vote_step != \'vote_cast\') $geo_vote_step = \'show_ballot\'; \r\n    		\r\n	//Get Vote Meta data from db for this vote_id\r\n		$rs = geo_vote_get_meta($vote_id);\r\n		extract($rs);\r\n\r\n	//Is the Vote set never to close (YES - must show results) & if not Has the Vote Closed?\r\n		if ($end_date_time == \'0000-00-00 00:00:00\') $show_results = \'Yes\';\r\n	    else {\r\n			$date_time_now = date(\'Y-m-d H:i:s\');\r\n			if ($end_date_time < $date_time_now ) $geo_vote_step = \'vote_closed\';\r\n		}\r\n			\r\n	//Is Form hash_key in table and less than ten minutes old?\r\n		if($geo_vote_step == \'vote_cast\') {\r\n			$rhk = array();\r\n			$rhk = safe_row(\'*\', GEO_VOTE_FORM, \"hash_key = \'$hash_key\'\");\r\n			if($rhk) {\r\n				extract($rhk);\r\n				$time_now_minus_ten_minutes = date(\'Y-m-d H:i:s\', time()-600);\r\n				if($t_stamp < $time_now_minus_ten_minutes) {\r\n					$geo_vote_step = \'hash_key_timed_out\';\r\n				 }\r\n			}\r\n			else {\r\n				$geo_vote_step = \'no_hash_key\';\r\n			}\r\n		// might as well clean up the hash_key table here\r\n		$ret = @safe_delete(GEO_VOTE_FORM, \"t_stamp < \'$time_now_minus_ten_minutes\'\");\r\n		// not sure if this is something that should go in a plugin? But there is lots of deleting from this table & it works?\r\n		$repair = safe_repair(GEO_VOTE_FORM);\r\n		}\r\n		\r\n		\r\n   // Main routine\r\n		switch ($geo_vote_step) {\r\n			case \"vote_cast\":\r\n				if ($geo_vote_option < 1) {\r\n					//No radio button selected when submit pressed\r\n					$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'no_option_selected\'));\r\n				}\r\n				else { \r\n					// check if voted before\r\n					if (geo_vote_valid($cheat_method, $vote_id)){\r\n						//Is the vote set to show results before voting closes?\r\n						if ($show_results == \"Yes\") {\r\n							if (geo_vote_vote_cast($rs, $geo_vote_option)) {\r\n								//Was the vote written to database? Yes - get the updated totals\r\n								$rs = geo_vote_get_meta($vote_id);\r\n								extract($rs);\r\n								$out = geo_vote_show_results($rs, geo_vote_gTxt(\'vote_cast\'));\r\n							}\r\n							else {\r\n								$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'vote_not_cast\'));\r\n							}\r\n						} \r\n						else {\r\n							if (geo_vote_vote_cast($rs, $geo_vote_option)) {\r\n								//Was the vote written to database? Yes\r\n								$out = geo_vote_show_options($rs, geo_vote_gTxt(\'vote_cast_no_results\'));\r\n							}\r\n							else {\r\n								$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'vote_not_cast\'));\r\n							}\r\n						} \r\n					}\r\n					else {\r\n						if ($show_results == \"Yes\") $out = geo_vote_show_results($rs, geo_vote_gTxt(\'voted_before\'));\r\n						else $out = geo_vote_show_options($rs, geo_vote_gTxt(\'already_voted_results\'));\r\n					}\r\n				}\r\n					break;\r\n					\r\n			case \"show_ballot\":\r\n				if (geo_vote_valid($cheat_method, $vote_id))$out = geo_vote_show_ballot($rs, \"\");\r\n				else {\r\n					if ($show_results == \"Yes\") $out = geo_vote_show_results($rs, geo_vote_gTxt(\'already_voted\'));\r\n					else $out = geo_vote_show_options($rs, geo_vote_gTxt(\'already_voted_results\'));\r\n				}\r\n				break;\r\n				\r\n			case \"vote_closed\":\r\n				$out = geo_vote_show_results($rs, geo_vote_gTxt(\'vote_closed\'));\r\n				break;\r\n				\r\n			case \"hash_key_timed_out\":\r\n				$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'form_expired\'));\r\n				break;\r\n				\r\n			case \"no_hash_key\":\r\n				sleep(3);\r\n				$out = geo_vote_show_options($rs, geo_vote_gTxt(\'security_token\'));\r\n				break;\r\n				\r\n			default: // shouldn\'t get here\r\n				$out = geo_vote_show_options($rs, \"\");\r\n				break;\r\n		}\r\n		$out = join(\'\', $out);\r\n		return $out;\r\n}\r\n// end function geo_vote	\r\n			\r\nfunction geo_vote_get_meta($vote_id){\r\n	//Get Vote Meta Data\r\n		$rs = array();\r\n		$rs = doSlash(safe_row(\'*\', GEO_VOTE_META, \"vote_id = $vote_id\"));\r\n		return $rs;\r\n}\r\n// end function geo_vote_meta	\r\n \r\nfunction geo_vote_vote_cast($rs, $geo_vote_option) {\r\n\r\n		extract($rs);\r\n			switch ($cheat_method) {\r\n				case \"cookie\":\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (empty($cookie_ip_login)) $cookie_ip_login = md5(uniqid(mt_rand(), TRUE));\r\n					$svc = geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time);\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (empty($cookie_ip_login)) $cookie_ip_login = remote_addr();	\r\n					break;\r\n					\r\n				case \"ip\":\r\n					$cookie_ip_login = remote_addr();\r\n					break;\r\n					\r\n   				case \"login\":\r\n					$cookie_ip_login = substr(cs(\'txp_login_public\'), 10);\r\n					break;\r\n				\r\n				default:\r\n				//Should never get here ... but if it does use cookie then ip\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (!isset($cookie_ip_login)) $cookie_ip_login = md5(uniqid(mt_rand(), TRUE));\r\n					$svc = geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time);\r\n					if (!isset($cookie_ip_login)) $cookie_ip_login = remote_addr();\r\n					break;\r\n			}\r\n		$si = safe_insert( GEO_VOTE_CHEAT, \"`pk` = NULL, `vote_id` = \'$vote_id\', `option` = \'$geo_vote_option\', `cookie_ip_login` = \'$cookie_ip_login\'\" );\r\n		${\"option_\".$geo_vote_option}++;\r\n		$total_votes++;\r\n		$su = safe_update( GEO_VOTE_META, \"option_1 = \'$option_1\', option_2 = \'$option_2\', option_3 = \'$option_3\', option_4 = \'$option_4\', option_5 = \'$option_5\', option_6 = \'$option_6\', option_7 = \'$option_7\', option_8 = \'$option_8\', option_9 = \'$option_9\', option_10 = \'$option_10\', total_votes = \'$total_votes\'\", \"vote_id = \'$vote_id\'\");\r\n		if (isset($si) && isset($su)) return 1;\r\n		else return 0;\r\n}\r\n// end function geo_vote_cast	\r\n\r\n\r\n\r\nfunction geo_vote_show_ballot($rs, $message){\r\n	\r\n			extract($rs);\r\n			$out = array();\r\n			$hash_key = md5(uniqid(mt_rand(), TRUE));\r\n			$si = safe_insert(GEO_VOTE_FORM, \"`hash_key` = \'$hash_key\', `t_stamp` = NULL\");\r\n			$out[] = \'<div class= \"$vote_title_class\"><h2>\'.doSpecial($vote_title).\'</h2></div>\';\r\n			$out[] = \'<form name=\"geo_vote_form_\'.$vote_id.\'\" action=\"\" method=\"post\" enctype=\"multipart/form-data\">\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_step\" value=\"vote_cast\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"hash_key\" value=\"\'.$hash_key.\'\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_form_id\" value=\"\'.$vote_id.\'\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_option\" value=\"0\" />\';\r\n			\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\"option_title_\".$i} != \"\")	{\r\n		 			$out[]= \'<span class=\"\'.$option_title_class.\'\"><input type=\"radio\" name=\"geo_vote_option\" value=\"\'.$i.\'\" />\'.doSpecial(${\"option_title_\".$i}).\'</span><br />\';\r\n				}\r\n			}\r\n			\r\n			$out[] = \'<span class= \"\'.$vote_button_class.\'\"><input type=\"submit\" name=\"geo_vote_submit_\'.$vote_id.\'\" value=\"\'.geo_vote_gTxt(\'submit_button\').\'\" /></span></form><br /><br />\';\r\n			$out[] = $message;\r\n			\r\n			return $out;\r\n}\r\n// end function geo_vote_show_ballot\r\n\r\nfunction geo_vote_show_options($rs, $message){\r\n	\r\n			extract($rs);\r\n			$out = array();\r\n			$out[] = \'<div class= \"$vote_title_class\"><h2>\'.doSpecial($vote_title).\'</h2></div>\';\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\"option_title_\".$i} != \"\")	{\r\n		 			$out[]= \'<span class=\"\'.$option_title_class.\'\">\'.doSpecial(${\"option_title_\".$i}).\'</span><br />\';\r\n				}\r\n			}\r\n			if ($end_date_time != \"00-00-00 00:00:00\" ) {\r\n	    		$date_time_now = date(\"Y-m-d H:i:s\");\r\n				if ($end_date_time > $date_time_now) {\r\n					$phpdate = strtotime( $end_date_time );\r\n					$out[] = \'<br />\'.geo_vote_gTxt(\'voting_closes_on\').\' \'.date(\'D jS F Y\', $phpdate).\' \'.geo_vote_gTxt(\'at\').\' \'.date(\'G:i:s\', $phpdate);\r\n					}\r\n				}\r\n			$out[] = \'<br />\'.$message;\r\n			\r\n			return $out;\r\n}\r\n// end function geo_vote_show_options		\r\n			\r\nfunction geo_vote_show_results($rs, $message){\r\n			\r\n			extract($rs);\r\n			$out = array();\r\n			$total_votes_percent = array_fill(1, 10, 0);\r\n			\r\n				if ($total_votes > 0){\r\n					for ($i = 1; $i <= 10; $i++) {\r\n						$total_votes_percent[$i] = round((${\'option_\'.$i}/$total_votes)*100, 2);\r\n						}\r\n				}\r\n			\r\n			//arsort() worth trying at some point to order the results\r\n		\r\n			$out[] =  \'<span class= \"\'.$vote_title_class.\'\"><h2>\'.doSpecial($vote_title).\'</h2></span><br />\';\r\n			\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\'option_title_\'.$i} != \'\')	{\r\n		 			$out[] =  \'<span class=\"\'.$option_title_class.\'\">\'.doSpecial(${\"option_title_\".$i}).\' (\'.$total_votes_percent[$i].\'%, \'.${\"option_\".$i}.\' \'.geo_vote_gTxt(\'votes\').\')</span><br />\';\r\n		 			//hard coded css for alpha release \r\n		 			$out[] =  \'<div style=\"background:\'.$vote_bar_colour.\';width:\'.$total_votes_percent[$i].\'%;height:16px;\"></div>\';\r\n				}\r\n			}\r\n			$out[] = \'<span class=\"$option_title_class\">\'.geo_vote_gTxt(\'total_votes\').\' \'.$total_votes.\' </span><br /><br />\';\r\n			if ($end_date_time != \"00-00-00 00:00:00\" ) {\r\n	    		$date_time_now = date(\"Y-m-d H:i:s\");\r\n				if ($end_date_time > $date_time_now) {\r\n					$phpdate = strtotime( $end_date_time );\r\n					$out[] = geo_vote_gTxt(\'voting_closes_on\').\' \'.date(\'D jS F Y\', $phpdate).\' \'.geo_vote_gTxt(\'at\').\' \'.date(\'G:i:s\', $phpdate).\'<br /><br />\';\r\n					}\r\n				}\r\n			$out[] = $message;\r\n			return $out;\r\n}\r\n// end function geo_vote_show_results	\r\n\r\nfunction geo_vote_valid($cheat_method, $vote_id) {\r\n\r\n	switch ($cheat_method) {\r\n		case \"cookie\":\r\n			$cookie_ip_login = cs(\'geo_vote\');\r\n			if(empty($cookie_ip_login)) { // fallback and check ip method\r\n				$cookie_ip_login = remote_addr();\r\n				$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n				if ($voted_before) return 0;\r\n				else return 1;\r\n				}\r\n				\r\n			else {\r\n				$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n				if ($voted_before) return 0;\r\n				else { // fallback and check ip method\r\n					$cookie_ip_login = remote_addr();\r\n					$voted_before = 0;\r\n					$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n					if ($voted_before) return 0;\r\n					else return 1;\r\n					}\r\n				}\r\n			break;\r\n			\r\n		case \"ip\":		\r\n			$cookie_ip_login = remote_addr();\r\n			$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n			if ($voted_before) return 0;\r\n			else return 1;\r\n			break;\r\n			\r\n		case \"login\":		\r\n			$name = substr(cs(\'txp_login_public\'), 10);\r\n			if ($name) 	$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$name\'\");\r\n			if ($voted_before) return 0;\r\n			else return 1;\r\n			break;\r\n\r\n		default: //should never get here ... but if you do your vote isn\'t vadid	\r\n			return 0;\r\n			break;\r\n		}\r\n}\r\n// end function geo_vote_valid\r\n\r\n\r\nfunction geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time) {\r\n	//sets expiration time at a year - change to $end-date-time at some future point.\r\n			$exp_time = time()+60*60*24*365;\r\n			$pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\') ;\r\n			setcookie(\'geo_vote\', $cookie_ip_login, $exp_time, $pub_path);\r\n}\r\n// end function geo_vote_set_vote_cookie\r\n\r\n\r\n// -----------------------------------\r\n// Plugin-specific replacement strings \r\n// -----------------------------------\r\nfunction geo_vote_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'admin_tab\' => \'Vote\',\r\n		\'geo_vote\' => \'Vote\',\r\n		\'geo_vote_meta_add\' => \'Add New Vote\',\r\n		\'meta_added\' => \'New Vote Successfully Added\',\r\n		\'meta_not_added\' => \'Problem adding vew vote: check vote_id is not already used\',\r\n		\'meta_deleted\' => \'Vote Data Successfully Deleted\',\r\n		\'meta_not_deleted\' => \'Problem deleting Vote: it may still exist\',\r\n		\'geo_vote_meta_edit\' => \'Edit Vote\',\r\n		\'meta_updated\' => \'Vote Tables Successfully Updated\',\r\n		\'tbl_installed\' => \'Tables installed.\',\r\n		\'tbl_not_installed\' => \'Tables NOT installed.\',\r\n		\'no_option_selected\' => \'Try again please! You forgot to make a selection.\',\r\n		\'vote_cast\' => \'Thank you, your vote has been cast\',\r\n		\'vote_cast_no_results\' => \'Thank you, your vote has been cast. Results can be viewed after voting closes\',\r\n		\'vote_not_cast\' => \'Sorry, your vote was not recorded, please vote again.\',\r\n		\'voted_before\' => \'Sorry, but you have already voted.\',\r\n		\'already_voted\' => \'You have already voted.\',\r\n		\'already_voted_results\' => \'You have already voted. Results can be viewed after voting closes.\',\r\n		\'submit_button\' => \'Vote Now\',\r\n		\'vote_closed\' => \'Voting has closed\',\r\n		\'votes\' => \'votes,\',\r\n		\'total_votes\' => \'Total Votes\',\r\n		\'voting_closes_on\' => \'Voting closes on\',\r\n		\'at\' => \'at\',\r\n		\'form_expired\' => \'Try again, the voting form has a security time limit\',\r\n		\'security_token\' => \'Vote not cast, a security token was missing from your ballot\'		\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', 'if (@txpinterface == \'admin\') {\r\n	add_privs(\'geo_vote\',\'1\');\r\n\r\n	// Extensions tab\r\n	register_tab(\'extensions\', \'geo_vote\', geo_vote_gTxt(\'geo_vote\'));\r\n	register_callback(\'geo_vote\', \'geo_vote\');\r\n}\r\n\r\nif (!defined(\'GEO_VOTE_CHEAT\')) {\r\n	define(\"GEO_VOTE_CHEAT\", \'geo_vote_cheat\');\r\n}\r\nif (!defined(\'GEO_VOTE_META\')) {\r\n	define(\"GEO_VOTE_META\", \'geo_vote_meta\');\r\n}\r\nif (!defined(\'GEO_VOTE_FORM\')) {\r\n	define(\"GEO_VOTE_FORM\", \'geo_vote_form\');\r\n}\r\n// ************************\r\n// Vote CONFIGURATION\r\n// ------------------------\r\n// Tab handler\r\nfunction geo_vote($event, $step) {\r\n$available_steps = array(\r\n			\'geo_vote_meta_add\' 	=> false,\r\n			\'geo_vote_meta_del\' 	=> true,\r\n			\'geo_vote_meta_save\' 	=> true\r\n			);\r\n	if(!$step or !bouncer($step, $available_steps)) {\r\n			geo_vote_config(\'\');\r\n            }\r\n	else $step();\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_config($message) {\r\n	if (!geo_vote_table_exist()) {\r\n		geo_vote_install(\'\');\r\n		exit;\r\n	}\r\n\r\n	pagetop(geo_vote_gTxt(\'admin_tab\'), $message);\r\n\r\n	echo geo_vote_meta_edit();\r\n	echo geo_vote_meta_list();\r\n}\r\n\r\n\r\nfunction geo_vote_meta_edit() {\r\n\r\nglobal $cheat_method_types;\r\n$cheat_method_types = array(\r\n		\'cookie\' => \'cookie\',\r\n		\'ip\' => \'ip\',\r\n		\'login\' => \'login\'\r\n		);\r\nglobal $show_result_types;\r\n$show_result_types = array(\r\n		\'No\' => \'No\',\r\n		\'Yes\' => \'Yes\'\r\n	);\r\n\r\n	$vars = array(\'step\', \'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\');\r\n	$rs = array();\r\n	\r\n	extract(doSlash(gpsa($vars)));\r\n	if ($vote_id && $step == \'geo_vote_meta_edit\') {\r\n		$rs = safe_row(\'*\', GEO_VOTE_META, \"vote_id = $vote_id\");\r\n		extract($rs);\r\n	}\r\n\r\n	if ($step == \'geo_vote_meta_save\' || $step == \'geo_vote_meta_add\' || $step == \'geo_vote_meta_del\') {\r\n		foreach ($vars as $var) {\r\n			$$var = \'\';\r\n		}\r\n	}\r\n\r\n	$caption = geo_vote_gTxt(($step == \'geo_vote_meta_edit\') ? \'geo_vote_meta_edit\' : \'geo_vote_meta_add\');\r\n\r\n	// Make the name/val pairs for selectInput\r\n	$selu = array();\r\n	foreach ($show_result_types as $widx => $wval) {\r\n		$selu[$widx] = $wval;\r\n	}\r\n	$selv = array();\r\n	foreach ($cheat_method_types as $widx => $wval) {\r\n		$selv[$widx] = $wval;\r\n	}\r\n	//add some defaults\r\n	if (!$vote_bar_colour) $vote_bar_colour = \'LightBlue\';\r\n	if (!$end_date_time) $end_date_time = \'0000-00-00 00:00:00\';\r\n	if (!$show_results) $show_results = \'Yes\';\r\n	if (!$cheat_method) $cheat_method = \'cookie\';\r\n	if (!$vote_title_class) $vote_title_class = \'vote_title_class\';\r\n	if (!$option_title_class) $option_title_class = \'option_title_class\';\r\n	if (!$end_date_time_class) $end_date_time_class = \'end_date_time_class\';\r\n	if (!$vote_button_class) $vote_button_class = \'vote_button_class\';\r\n\r\n	return form(\r\n		hed($caption, 3,\' style=\"text-align: center;\"\').\r\n\r\n		startTable(\'edit\', \'\', \'edit-pane\').\r\n//TR BELOW WAS ($vote_id && $step == \'geo_vote_meta_edit\' ? td(strong($vote_id)) : fInputCell(\'vote_id\', $vote_id)) need to take the or out its redundant\r\n		tr(\r\n			($vote_id && $step == \'geo_vote_meta_edit\' ? fLabelCell(\'vote_id\').td(strong($vote_id)) : fLabelCell(\'&nbsp;\').fLabelCell(\'&nbsp;\')).\r\ntd(fLabelCell(\'&nbsp;\')).\r\ntd(fLabelCell(\'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\'))\r\n		).\r\n\r\n		tr(\r\n			fLabelCell(\'Vote Title\').\r\n			fInputCell(\'vote_title\', $vote_title)\r\n		).\r\n// future :- put loop in here\r\n		tr(\r\n			fLabelCell(\'Option 1 Title\').\r\n			fInputCell(\'option_title_1\', $option_title_1)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'Option 2 Title\').\r\n			fInputCell(\'option_title_2\', $option_title_2)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 3 Title\').\r\n			fInputCell(\'option_title_3\', $option_title_3)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 4 Title\').\r\n			fInputCell(\'option_title_4\', $option_title_4)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 5 Title\').\r\n			fInputCell(\'option_title_5\', $option_title_5)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 6 Title\').\r\n			fInputCell(\'option_title_6\', $option_title_6)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 7 Title\').\r\n			fInputCell(\'option_title_7\', $option_title_7)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 8 Title\').\r\n			fInputCell(\'option_title_8\', $option_title_8)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 9 Title\').\r\n			fInputCell(\'option_title_9\', $option_title_9)\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Option 10 Title\').\r\n			fInputCell(\'option_title_10\', $option_title_10)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'Vote Bar Colour\').\r\n			fInputCell(\'vote_bar_colour\', $vote_bar_colour).\r\n			td(fLabelCell(\'default = #ADD8E6 / LightBlue include # or use a valid color name\'))\r\n		).\r\n				\r\n		tr(\r\n			fLabelCell(\'Vote Closing Date & Time\').\r\n			fInputCell(\'end_date_time\', $end_date_time).\r\ntd(fLabelCell(\'Format YYYY-MM-DD HH:MM:SS &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\'))\r\n		).\r\n		\r\n				\r\n		tr(\r\n\r\n			fLabelCell(\'Show Results Before Vote Closes\').\r\n			td(selectInput(\'show_results\', $selu, $show_results, false))\r\n		).		\r\n		tr(\r\n			fLabelCell(\'Voting Fraud Prevention Method\').\r\n			td(selectInput(\'cheat_method\', $selv, $cheat_method, false))\r\n\r\n		).\r\n		tr(\r\n			fLabelCell(\'===========\')\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'CSS classes\')\r\n		).\r\n		tr(\r\n			fLabelCell(\'vote_title_class\').\r\n			fInputCell(\'vote_title_class\', $vote_title_class)\r\n		).\r\n		\r\n		tr(\r\n			fLabelCell(\'option_title_class\').\r\n			fInputCell(\'option_title_class\', $option_title_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			fLabelCell(\'end_date_time_class\').\r\n			fInputCell(\'end_date_time_class\', $end_date_time_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			fLabelCell(\'vote_button_class\').\r\n			fInputCell(\'vote_button_class\', $vote_button_class)\r\n		).\r\n		\r\n		\r\n		tr(\r\n			td().\r\n			td(\r\n				fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')\r\n			)\r\n		).\r\n\r\n		endTable().\r\n\r\n		eInput(\'geo_vote\').\r\n		($vote_id ? hInput(\'vote_id\', $vote_id).sInput(\'geo_vote_meta_save\') : sInput(\'geo_vote_meta_add\'))\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_list() {\r\n\r\n	extract(doSlash(gpsa(array(\'sort\', \'dir\', \'crit\', \'search_method\'))));\r\n	if ($sort === \'\') $sort = get_pref(\'geo_vote_meta_sort_column\', \'vote_id\');\r\n	if ($dir === \'\') $dir = get_pref(\'geo_vote_meta_sort_dir\', \'asc\');\r\n	$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\r\n\r\n	if (!in_array($sort, array(\'vote_id\', \'vote_title\', \'show_results\', \'cheat_method\', \'end_date_time\'))) $sort = \'vote_id\';\r\n\r\n	$sort_sql   = $sort.\' \'.$dir;\r\n\r\n	set_pref(\'geo_vote_meta_sort_column\', $sort, \'geo_vote\', 2, \'\', 0, PREF_PRIVATE);\r\n	set_pref(\'geo_vote_meta_sort_dir\', $dir, \'geo_vote\', 2, \'\', 0, PREF_PRIVATE);\r\n\r\n	$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\r\n\r\n	$rs = safe_rows_start(\'*\', GEO_VOTE_META, \'1 = 1 ORDER BY \'.$sort_sql);\r\n\r\n	if ($rs) {\r\n		echo \'<form action=\"index.php\" method=\"post\" name=\"longform\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\r\n\r\n		startTable(\'list\').\r\n		tr(\r\n			column_head(\'vote_id\', \'vote_id\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'vote_id\' == $sort) ? $dir : \'\').\r\n			column_head(\'vote_title\', \'vote_title\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'vote_title\' == $sort) ? $dir : \'\').\r\n			column_head(\'show_results\', \'show_results\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'show_results\' == $sort) ? $dir : \'\').\r\n			column_head(\'cheat_method\', \'cheat_method\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'cheat_method\' == $sort) ? $dir : \'\').\r\n			column_head(\'end_date_time\', \'end_date_time\', \'geo_vote\', true, $switch_dir, \'\', \'\', (\'end_date_time\' == $sort) ? $dir : \'\').\r\n			hCell().\r\n			hCell()\r\n		);\r\n\r\n		while ($a = nextRow($rs)) {\r\n			extract(doSpecial($a));\r\n\r\n			echo tr(\r\n				td($vote_id).\r\n				td($vote_title).\r\n				td($show_results).\r\n				td($cheat_method).\r\n				td($end_date_time).\r\n				td(eLink(\'geo_vote\', \'geo_vote_meta_edit\', \'vote_id\', $vote_id, gTxt(\'edit\'))).\r\n				td(dLink(\'geo_vote\', \'geo_vote_meta_del\', \'vote_id\', $vote_id, 1))\r\n			);\r\n		}\r\n\r\n		echo endTable().\'</form>\';\r\n	}\r\n}\r\n\r\n// ------------------------ Can all the option_title_1 - 10 be looped here?\r\nfunction geo_vote_meta_save() {\r\n	extract(doSlash(psa(array(\'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\'))));\r\n	\r\n	$rs = safe_update(GEO_VOTE_META, \"\r\n				vote_title = \'$vote_title\',\r\n				vote_title_class = \'$vote_title_class\',\r\n				option_title_1 = \'$option_title_1\',\r\n				option_title_2 = \'$option_title_2\',\r\n				option_title_3 = \'$option_title_3\',\r\n				option_title_4 = \'$option_title_4\',\r\n				option_title_5 = \'$option_title_5\',\r\n				option_title_6 = \'$option_title_6\',\r\n				option_title_7 = \'$option_title_7\',\r\n				option_title_8 = \'$option_title_8\',\r\n				option_title_9 = \'$option_title_9\',\r\n				option_title_10 = \'$option_title_10\',\r\n				vote_bar_colour = \'$vote_bar_colour\',				\r\n				option_title_class = \'$option_title_class\',\r\n				end_date_time = \'$end_date_time\',\r\n				end_date_time_class = \'$end_date_time_class\',\r\n				vote_button_class = \'$vote_button_class\',\r\n				show_results = \'$show_results\',\r\n				cheat_method = \'$cheat_method\'\",\r\n				\"vote_id = \'$vote_id\'\"\r\n			);\r\n\r\n	if ($rs) {\r\n		geo_vote_config(\r\n			geo_vote_gTxt(\'meta_updated\', array(\'{vote_id}\' => $vote_id))\r\n		);\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_add() {\r\n	extract(doSlash(psa(array(\'vote_id\', \'vote_title\', \'vote_title_class\', \'option_title_1\', \'option_title_2\', \'option_title_3\', \'option_title_4\',  \'option_title_5\', \'option_title_6\', \'option_title_7\', \'option_title_8\', \'option_title_9\', \'option_title_10\', \'vote_bar_colour\', \'option_title_class\', \'end_date_time\', \'end_date_time_class\', \'vote_button_class\', \'show_results\', \'cheat_method\'))));\r\n\r\n			$rs = safe_insert(GEO_VOTE_META, \"\r\n				vote_id = \'$vote_id\',\r\n				vote_title = \'$vote_title\',\r\n				vote_title_class = \'$vote_title_class\',\r\n				option_title_1 = \'$option_title_1\',\r\n				option_title_2 = \'$option_title_2\',\r\n				option_title_3 = \'$option_title_3\',\r\n				option_title_4 = \'$option_title_4\',\r\n				option_title_5 = \'$option_title_5\',\r\n				option_title_6 = \'$option_title_6\',\r\n				option_title_7 = \'$option_title_7\',\r\n				option_title_8 = \'$option_title_8\',\r\n				option_title_9 = \'$option_title_9\',\r\n				option_title_10 = \'$option_title_10\',\r\n				vote_bar_colour = \'$vote_bar_colour\',		\r\n				option_title_class = \'$option_title_class\',\r\n				end_date_time = \'$end_date_time\',\r\n				end_date_time_class = \'$end_date_time_class\',\r\n				vote_button_class = \'$vote_button_class\',\r\n				show_results = \'$show_results\',\r\n				cheat_method = \'$cheat_method\'\"\r\n			);\r\n\r\n			if ($rs) {\r\n				geo_vote_config(geo_vote_gTxt(\'meta_added\'));\r\n				return;\r\n			}\r\n					\r\n	\r\n	geo_vote_config(array(geo_vote_gTxt(\'meta_not_added\'), E_ERROR));\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_meta_del() {\r\n	extract(doSlash(psa(array(\'vote_id\'))));\r\n	$ret = @safe_delete(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\'\");\r\n	if ($ret) {\r\n		$ret = safe_delete(GEO_VOTE_META, \"vote_id=\'$vote_id\'\");\r\n		$message = geo_vote_gTxt(\'meta_deleted\');\r\n	}\r\n\r\n	geo_vote_config($message);\r\n}\r\n\r\n\r\n// ************************\r\n// TABLE GENERATION\r\n// ------------------------\r\nfunction geo_vote_table_exist() {\r\n	if (gps(\'debug\')) {\r\n		echo \"++ TABLE \".GEO_VOTE_META.\" HAS \".count(@safe_show(\'columns\', GEO_VOTE_META)).\" COLUMNS;\";\r\n	}\r\n	return(@safe_show(\'columns\', GEO_VOTE_META));\r\n}\r\n\r\n// ------------------------\r\nfunction geo_vote_install($message) {\r\n\r\n	$version = mysql_get_server_info();\r\n	$GLOBALS[\'txp_err_count\'] = 0;\r\n	if ($version < \"4.1.2\") {\r\n		$GLOBALS[\'txp_err_count\']++;\r\n		trigger_error(\"geo_vote requires MySQL v4.1.2 or greater.\");\r\n	} else {\r\n		$ret = \'\';\r\n		$sql = array();\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_CHEAT.\"` (\r\n						`pk` int(4) NOT NULL auto_increment PRIMARY KEY,\r\n			`vote_id` int(4) NOT NULL default 0,\r\n			`option` int(4) NOT NULL default 0,\r\n			`cookie_ip_login` varchar(64) NULL default \'\',\r\n			INDEX (vote_id)\r\n						) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=1\";\r\n						\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_FORM.\"` (\r\n			`hash_key` varchar(32) NOT NULL PRIMARY KEY,\r\n			`t_stamp` TIMESTAMP NOT NULL default CURRENT_TIMESTAMP\r\n						) ENGINE=MyISAM PACK_KEYS=1\";\r\n\r\n		$sql[] = \"CREATE TABLE IF NOT EXISTS `\".PFX.GEO_VOTE_META.\"` (\r\n			`vote_id` int(4) NOT NULL auto_increment PRIMARY KEY,\r\n			`vote_title` varchar(64) NULL default \'\',\r\n			`option_title_1` varchar(64) NULL default \'\',\r\n			`option_title_2` varchar(64) NULL default \'\',\r\n			`option_title_3` varchar(64) NULL default \'\',\r\n			`option_title_4` varchar(64) NULL default \'\',\r\n			`option_title_5` varchar(64) NULL default \'\',\r\n			`option_title_6` varchar(64) NULL default \'\',\r\n			`option_title_7` varchar(64) NULL default \'\',\r\n			`option_title_8` varchar(64) NULL default \'\',\r\n			`option_title_9` varchar(64) NULL default \'\',\r\n			`option_title_10` varchar(64) NULL default \'\',\r\n			`option_1` int(4) NOT NULL default 0,\r\n			`option_2` int(4) NOT NULL default 0,\r\n			`option_3` int(4) NOT NULL default 0,\r\n			`option_4` int(4) NOT NULL default 0,\r\n			`option_5` int(4) NOT NULL default 0,\r\n			`option_6` int(4) NOT NULL default 0,\r\n			`option_7` int(4) NOT NULL default 0,\r\n			`option_8` int(4) NOT NULL default 0,\r\n			`option_9` int(4) NOT NULL default 0,\r\n			`option_10` int(4) NOT NULL default 0,\r\n			`total_votes` int(4) NOT NULL default 0,\r\n			`show_results` varchar(3) NOT NULL default \'\',\r\n			`cheat_method` varchar(6) NOT NULL default \'\',\r\n			`end_date_time` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\r\n			`vote_bar_colour` varchar(32) NOT NULL default \'\',\r\n			`vote_title_class` varchar(64) NOT NULL default \'\',\r\n			`option_title_class` varchar(64) NOT NULL default \'\',\r\n			`end_date_time_class` varchar(64) NOT NULL default \'\',\r\n			`vote_button_class` varchar(64) NOT NULL default \'\'\r\n							) ENGINE=MyISAM PACK_KEYS=1 AUTO_INCREMENT=1\";\r\n\r\n		if(gps(\'debug\')) {\r\n			dmp($sql);\r\n		}\r\n		foreach ($sql as $qry) {\r\n			$ret = safe_query($qry);\r\n			if ($ret===false) {\r\n				$GLOBALS[\'txp_err_count\']++;\r\n				echo \"<b>\".$GLOBALS[\'txp_err_count\'].\".</b> \".mysql_error().\"<br />\\n\";\r\n				echo \"<!--\\n $qry \\n-->\\n\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if ($GLOBALS[\'txp_err_count\'] == 0) {\r\n		$message = geo_vote_gTxt(\'tbl_installed\');\r\n		geo_vote_config($message);\r\n	} else {\r\n		$message = geo_vote_gTxt(\'tbl_not_installed\');\r\n		geo_vote_config($message);\r\n	}\r\n}\r\n\r\n\r\n// ************************\r\n// CLIENT-SIDE TAGS\r\n// ------------------------\r\nfunction geo_votes($atts) {\r\n	\r\n	// extract / declare variables\r\n		extract(lAtts(array(\r\n			\'vote_id\'	=> \'1\'\r\n		), $atts));\r\n		$out = array();\r\n		$geo_vote_form_id = 0;\r\n		$vars = array(\'geo_vote_step\',\'geo_vote_option\',\'geo_vote_form_id\', \'hash_key\');\r\n		extract(doSlash(psa($vars)));\r\n		$geo_vote_form_id = (int)$geo_vote_form_id;\r\n		$geo_vote_option = (int)$geo_vote_option;\r\n		\r\n				\r\n    //If the vote was cast for another vote on the same page - ignore it and show ballot\r\n		if($geo_vote_form_id != $vote_id) $geo_vote_step = \'show_ballot\';\r\n			\r\n	//If step isn\'t vote_cast set step to show_ballot - should help security\r\n		if($geo_vote_step != \'vote_cast\') $geo_vote_step = \'show_ballot\'; \r\n    		\r\n	//Get Vote Meta data from db for this vote_id\r\n		$rs = geo_vote_get_meta($vote_id);\r\n		extract($rs);\r\n\r\n	//Is the Vote set never to close (YES - must show results) & if not Has the Vote Closed?\r\n		if ($end_date_time == \'0000-00-00 00:00:00\') $show_results = \'Yes\';\r\n	    else {\r\n			$date_time_now = date(\'Y-m-d H:i:s\');\r\n			if ($end_date_time < $date_time_now ) $geo_vote_step = \'vote_closed\';\r\n		}\r\n			\r\n	//Is Form hash_key in table and less than ten minutes old?\r\n		if($geo_vote_step == \'vote_cast\') {\r\n			$rhk = array();\r\n			$rhk = safe_row(\'*\', GEO_VOTE_FORM, \"hash_key = \'$hash_key\'\");\r\n			if($rhk) {\r\n				extract($rhk);\r\n				$time_now_minus_ten_minutes = date(\'Y-m-d H:i:s\', time()-600);\r\n				if($t_stamp < $time_now_minus_ten_minutes) {\r\n					$geo_vote_step = \'hash_key_timed_out\';\r\n				 }\r\n			}\r\n			else {\r\n				$geo_vote_step = \'no_hash_key\';\r\n			}\r\n		// might as well clean up the hash_key table here\r\n		$ret = @safe_delete(GEO_VOTE_FORM, \"t_stamp < \'$time_now_minus_ten_minutes\'\");\r\n		// not sure if this is something that should go in a plugin? But there is lots of deleting from this table & it works?\r\n		$repair = safe_repair(GEO_VOTE_FORM);\r\n		}\r\n		\r\n		\r\n   // Main routine\r\n		switch ($geo_vote_step) {\r\n			case \"vote_cast\":\r\n				if ($geo_vote_option < 1) {\r\n					//No radio button selected when submit pressed\r\n					$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'no_option_selected\'));\r\n				}\r\n				else { \r\n					// check if voted before\r\n					if (geo_vote_valid($cheat_method, $vote_id)){\r\n						//Is the vote set to show results before voting closes?\r\n						if ($show_results == \"Yes\") {\r\n							if (geo_vote_vote_cast($rs, $geo_vote_option)) {\r\n								//Was the vote written to database? Yes - get the updated totals\r\n								$rs = geo_vote_get_meta($vote_id);\r\n								extract($rs);\r\n								$out = geo_vote_show_results($rs, geo_vote_gTxt(\'vote_cast\'));\r\n							}\r\n							else {\r\n								$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'vote_not_cast\'));\r\n							}\r\n						} \r\n						else {\r\n							if (geo_vote_vote_cast($rs, $geo_vote_option)) {\r\n								//Was the vote written to database? Yes\r\n								$out = geo_vote_show_options($rs, geo_vote_gTxt(\'vote_cast_no_results\'));\r\n							}\r\n							else {\r\n								$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'vote_not_cast\'));\r\n							}\r\n						} \r\n					}\r\n					else {\r\n						if ($show_results == \"Yes\") $out = geo_vote_show_results($rs, geo_vote_gTxt(\'voted_before\'));\r\n						else $out = geo_vote_show_options($rs, geo_vote_gTxt(\'already_voted_results\'));\r\n					}\r\n				}\r\n					break;\r\n					\r\n			case \"show_ballot\":\r\n				if (geo_vote_valid($cheat_method, $vote_id))$out = geo_vote_show_ballot($rs, \"\");\r\n				else {\r\n					if ($show_results == \"Yes\") $out = geo_vote_show_results($rs, geo_vote_gTxt(\'already_voted\'));\r\n					else $out = geo_vote_show_options($rs, geo_vote_gTxt(\'already_voted_results\'));\r\n				}\r\n				break;\r\n				\r\n			case \"vote_closed\":\r\n				$out = geo_vote_show_results($rs, geo_vote_gTxt(\'vote_closed\'));\r\n				break;\r\n				\r\n			case \"hash_key_timed_out\":\r\n				$out = geo_vote_show_ballot($rs, geo_vote_gTxt(\'form_expired\'));\r\n				break;\r\n				\r\n			case \"no_hash_key\":\r\n				sleep(3);\r\n				$out = geo_vote_show_options($rs, geo_vote_gTxt(\'security_token\'));\r\n				break;\r\n				\r\n			default: // shouldn\'t get here\r\n				$out = geo_vote_show_options($rs, \"\");\r\n				break;\r\n		}\r\n		$out = join(\'\', $out);\r\n		return $out;\r\n}\r\n// end function geo_vote	\r\n			\r\nfunction geo_vote_get_meta($vote_id){\r\n	//Get Vote Meta Data\r\n		$rs = array();\r\n		$rs = doSlash(safe_row(\'*\', GEO_VOTE_META, \"vote_id = $vote_id\"));\r\n		return $rs;\r\n}\r\n// end function geo_vote_meta	\r\n \r\nfunction geo_vote_vote_cast($rs, $geo_vote_option) {\r\n\r\n		extract($rs);\r\n			switch ($cheat_method) {\r\n				case \"cookie\":\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (empty($cookie_ip_login)) $cookie_ip_login = md5(uniqid(mt_rand(), TRUE));\r\n					$svc = geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time);\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (empty($cookie_ip_login)) $cookie_ip_login = remote_addr();	\r\n					break;\r\n					\r\n				case \"ip\":\r\n					$cookie_ip_login = remote_addr();\r\n					break;\r\n					\r\n   				case \"login\":\r\n					$cookie_ip_login = substr(cs(\'txp_login_public\'), 10);\r\n					break;\r\n				\r\n				default:\r\n				//Should never get here ... but if it does use cookie then ip\r\n					$cookie_ip_login = cs(\'geo_vote\');\r\n					if (!isset($cookie_ip_login)) $cookie_ip_login = md5(uniqid(mt_rand(), TRUE));\r\n					$svc = geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time);\r\n					if (!isset($cookie_ip_login)) $cookie_ip_login = remote_addr();\r\n					break;\r\n			}\r\n		$si = safe_insert( GEO_VOTE_CHEAT, \"`pk` = NULL, `vote_id` = \'$vote_id\', `option` = \'$geo_vote_option\', `cookie_ip_login` = \'$cookie_ip_login\'\" );\r\n		${\"option_\".$geo_vote_option}++;\r\n		$total_votes++;\r\n		$su = safe_update( GEO_VOTE_META, \"option_1 = \'$option_1\', option_2 = \'$option_2\', option_3 = \'$option_3\', option_4 = \'$option_4\', option_5 = \'$option_5\', option_6 = \'$option_6\', option_7 = \'$option_7\', option_8 = \'$option_8\', option_9 = \'$option_9\', option_10 = \'$option_10\', total_votes = \'$total_votes\'\", \"vote_id = \'$vote_id\'\");\r\n		if (isset($si) && isset($su)) return 1;\r\n		else return 0;\r\n}\r\n// end function geo_vote_cast	\r\n\r\n\r\n\r\nfunction geo_vote_show_ballot($rs, $message){\r\n	\r\n			extract($rs);\r\n			$out = array();\r\n			$hash_key = md5(uniqid(mt_rand(), TRUE));\r\n			$si = safe_insert(GEO_VOTE_FORM, \"`hash_key` = \'$hash_key\', `t_stamp` = NULL\");\r\n			$out[] = \'<div class= \"$vote_title_class\"><h2>\'.doSpecial($vote_title).\'</h2></div>\';\r\n			$out[] = \'<form name=\"geo_vote_form_\'.$vote_id.\'\" action=\"\" method=\"post\" enctype=\"multipart/form-data\">\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_step\" value=\"vote_cast\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"hash_key\" value=\"\'.$hash_key.\'\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_form_id\" value=\"\'.$vote_id.\'\" />\';\r\n			$out[] = \'<input type=\"hidden\" name=\"geo_vote_option\" value=\"0\" />\';\r\n			\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\"option_title_\".$i} != \"\")	{\r\n		 			$out[]= \'<span class=\"\'.$option_title_class.\'\"><input type=\"radio\" name=\"geo_vote_option\" value=\"\'.$i.\'\" />\'.doSpecial(${\"option_title_\".$i}).\'</span><br />\';\r\n				}\r\n			}\r\n			\r\n			$out[] = \'<span class= \"\'.$vote_button_class.\'\"><input type=\"submit\" name=\"geo_vote_submit_\'.$vote_id.\'\" value=\"\'.geo_vote_gTxt(\'submit_button\').\'\" /></span></form><br /><br />\';\r\n			$out[] = $message;\r\n			\r\n			return $out;\r\n}\r\n// end function geo_vote_show_ballot\r\n\r\nfunction geo_vote_show_options($rs, $message){\r\n	\r\n			extract($rs);\r\n			$out = array();\r\n			$out[] = \'<div class= \"$vote_title_class\"><h2>\'.doSpecial($vote_title).\'</h2></div>\';\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\"option_title_\".$i} != \"\")	{\r\n		 			$out[]= \'<span class=\"\'.$option_title_class.\'\">\'.doSpecial(${\"option_title_\".$i}).\'</span><br />\';\r\n				}\r\n			}\r\n			if ($end_date_time != \"00-00-00 00:00:00\" ) {\r\n	    		$date_time_now = date(\"Y-m-d H:i:s\");\r\n				if ($end_date_time > $date_time_now) {\r\n					$phpdate = strtotime( $end_date_time );\r\n					$out[] = \'<br />\'.geo_vote_gTxt(\'voting_closes_on\').\' \'.date(\'D jS F Y\', $phpdate).\' \'.geo_vote_gTxt(\'at\').\' \'.date(\'G:i:s\', $phpdate);\r\n					}\r\n				}\r\n			$out[] = \'<br />\'.$message;\r\n			\r\n			return $out;\r\n}\r\n// end function geo_vote_show_options		\r\n			\r\nfunction geo_vote_show_results($rs, $message){\r\n			\r\n			extract($rs);\r\n			$out = array();\r\n			$total_votes_percent = array_fill(1, 10, 0);\r\n			\r\n				if ($total_votes > 0){\r\n					for ($i = 1; $i <= 10; $i++) {\r\n						$total_votes_percent[$i] = round((${\'option_\'.$i}/$total_votes)*100, 2);\r\n						}\r\n				}\r\n			\r\n			//arsort() worth trying at some point to order the results\r\n		\r\n			$out[] =  \'<span class= \"\'.$vote_title_class.\'\"><h2>\'.doSpecial($vote_title).\'</h2></span><br />\';\r\n			\r\n			for ($i = 1; $i <= 10; $i++){\r\n				if (${\'option_title_\'.$i} != \'\')	{\r\n		 			$out[] =  \'<span class=\"\'.$option_title_class.\'\">\'.doSpecial(${\"option_title_\".$i}).\' (\'.$total_votes_percent[$i].\'%, \'.${\"option_\".$i}.\' \'.geo_vote_gTxt(\'votes\').\')</span><br />\';\r\n		 			//hard coded css for alpha release \r\n		 			$out[] =  \'<div style=\"background:\'.$vote_bar_colour.\';width:\'.$total_votes_percent[$i].\'%;height:16px;\"></div>\';\r\n				}\r\n			}\r\n			$out[] = \'<span class=\"$option_title_class\">\'.geo_vote_gTxt(\'total_votes\').\' \'.$total_votes.\' </span><br /><br />\';\r\n			if ($end_date_time != \"00-00-00 00:00:00\" ) {\r\n	    		$date_time_now = date(\"Y-m-d H:i:s\");\r\n				if ($end_date_time > $date_time_now) {\r\n					$phpdate = strtotime( $end_date_time );\r\n					$out[] = geo_vote_gTxt(\'voting_closes_on\').\' \'.date(\'D jS F Y\', $phpdate).\' \'.geo_vote_gTxt(\'at\').\' \'.date(\'G:i:s\', $phpdate).\'<br /><br />\';\r\n					}\r\n				}\r\n			$out[] = $message;\r\n			return $out;\r\n}\r\n// end function geo_vote_show_results	\r\n\r\nfunction geo_vote_valid($cheat_method, $vote_id) {\r\n\r\n	switch ($cheat_method) {\r\n		case \"cookie\":\r\n			$cookie_ip_login = cs(\'geo_vote\');\r\n			if(empty($cookie_ip_login)) { // fallback and check ip method\r\n				$cookie_ip_login = remote_addr();\r\n				$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n				if ($voted_before) return 0;\r\n				else return 1;\r\n				}\r\n				\r\n			else {\r\n				$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n				if ($voted_before) return 0;\r\n				else { // fallback and check ip method\r\n					$cookie_ip_login = remote_addr();\r\n					$voted_before = 0;\r\n					$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n					if ($voted_before) return 0;\r\n					else return 1;\r\n					}\r\n				}\r\n			break;\r\n			\r\n		case \"ip\":		\r\n			$cookie_ip_login = remote_addr();\r\n			$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$cookie_ip_login\'\");\r\n			if ($voted_before) return 0;\r\n			else return 1;\r\n			break;\r\n			\r\n		case \"login\":		\r\n			$name = substr(cs(\'txp_login_public\'), 10);\r\n			if ($name) 	$voted_before = safe_count(GEO_VOTE_CHEAT, \"vote_id=\'$vote_id\' AND cookie_ip_login=\'$name\'\");\r\n			if ($voted_before) return 0;\r\n			else return 1;\r\n			break;\r\n\r\n		default: //should never get here ... but if you do your vote isn\'t vadid	\r\n			return 0;\r\n			break;\r\n		}\r\n}\r\n// end function geo_vote_valid\r\n\r\n\r\nfunction geo_vote_set_vote_cookie ($cookie_ip_login, $end_date_time) {\r\n	//sets expiration time at a year - change to $end-date-time at some future point.\r\n			$exp_time = time()+60*60*24*365;\r\n			$pub_path = preg_replace(\'|//$|\',\'/\', rhu.\'/\') ;\r\n			setcookie(\'geo_vote\', $cookie_ip_login, $exp_time, $pub_path);\r\n}\r\n// end function geo_vote_set_vote_cookie\r\n\r\n\r\n// -----------------------------------\r\n// Plugin-specific replacement strings \r\n// -----------------------------------\r\nfunction geo_vote_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'admin_tab\' => \'Vote\',\r\n		\'geo_vote\' => \'Vote\',\r\n		\'geo_vote_meta_add\' => \'Add New Vote\',\r\n		\'meta_added\' => \'New Vote Successfully Added\',\r\n		\'meta_not_added\' => \'Problem adding vew vote: check vote_id is not already used\',\r\n		\'meta_deleted\' => \'Vote Data Successfully Deleted\',\r\n		\'meta_not_deleted\' => \'Problem deleting Vote: it may still exist\',\r\n		\'geo_vote_meta_edit\' => \'Edit Vote\',\r\n		\'meta_updated\' => \'Vote Tables Successfully Updated\',\r\n		\'tbl_installed\' => \'Tables installed.\',\r\n		\'tbl_not_installed\' => \'Tables NOT installed.\',\r\n		\'no_option_selected\' => \'Try again please! You forgot to make a selection.\',\r\n		\'vote_cast\' => \'Thank you, your vote has been cast\',\r\n		\'vote_cast_no_results\' => \'Thank you, your vote has been cast. Results can be viewed after voting closes\',\r\n		\'vote_not_cast\' => \'Sorry, your vote was not recorded, please vote again.\',\r\n		\'voted_before\' => \'Sorry, but you have already voted.\',\r\n		\'already_voted\' => \'You have already voted.\',\r\n		\'already_voted_results\' => \'You have already voted. Results can be viewed after voting closes.\',\r\n		\'submit_button\' => \'Vote Now\',\r\n		\'vote_closed\' => \'Voting has closed\',\r\n		\'votes\' => \'votes,\',\r\n		\'total_votes\' => \'Total Votes\',\r\n		\'voting_closes_on\' => \'Voting closes on\',\r\n		\'at\' => \'at\',\r\n		\'form_expired\' => \'Try again, the voting form has a security time limit\',\r\n		\'security_token\' => \'Vote not cast, a security token was missing from your ballot\'		\r\n	);\r\n	return strtr($lang[$what], $atts);\r\n}', '4cb1d52f06f4370129f5e43b2f5412b5', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_native', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_native-textpattern-plugin', '0.4', 'Set a user\'s native backend language from the admin tab. Requires Textpattern CMS 4.5.0+', '<p>Off the shelf, Textpattern <span class=\"caps\">CMS</span> operates a site in one of the 40+ languages it supports and uses this i18n setting for both the publicly visible front-end and the administrative back end as well. In a multilingual development team, this forces a usability hurdle upon the more forgiving members of the team as they have to bear an eventually undecipherable backend interface.</p>\n\n<p><strong>wet_native</strong> lifts this restriction and lets administrators <a href=\"http://localhost/site1/?event=admin\" rel=\"nofollow\">assign a distinct language</a> to each registered user.</p>', '\nclass wet_native\n{\n	function __construct()\n    {\n    	// hook into the admin tab to render a custom input element\n    	register_callback(array(\'wet_native\', \'ui\'), \'author_ui\', \'extend_detail_form\');\n		register_callback(array(\'wet_native\', \'save\'), \'admin\', \'author_save\');\n		register_callback(array(\'wet_native\', \'save\'), \'admin\', \'author_save_new\');\n		// load i18n strings when the end of the HEAD element is rendered\n		register_callback(array(\'wet_native\', \'run\'), \'admin_side\', \'head_end\');\n    }\n\n	/**\n	 * event handler for rendering the additional language drop down in the admin tab\n	 */\n    public static function ui($event, $step, $default, $author)\n	{\n		global $txp_user;\n\n		// new users: default to current user\'s language setting\n		$name = (isset($author[\'name\']) ? $author[\'name\'] : $txp_user);\n\n		// list all installed languages\n		$rs = safe_column(\'distinct(lang)\', \'txp_lang\', \'1=1\');\n		if ($rs === false) return \'\';\n		foreach ($rs as $lang)\n		{\n			$out[$lang] = gTxt($lang);\n		}\n\n		$select = $out ? selectInput(\'wet_native_lang\', $out, wet_native::_lang($name), true) : \'\';\n\n		// render additional input element\n		return inputLabel(\'language\', $select);\n	}\n\n	/**\n	 * event handler saves the user-specific language setting as a private pref.\n	 */\n	public static function save($event, $step)\n	{\n		$new_lang = doSlash(ps(\'wet_native_lang\'));\n\n		// existing author\n		if ($step == \'author_save\')\n		{\n			$user_id = doSlash(ps(\'user_id\'));\n			$name = safe_field(\'name\', \'txp_users\', \"user_id=$user_id\");\n		}\n		else\n		// new author\n		{\n			$name = gps(\'name\');\n		}\n\n		if ($name === \'\') return;\n\n		$name = doSlash($name);\n		$lang = safe_field(\'val\', \'txp_prefs\', \"name=\'wet_native_language\' and user_name=\'$name\'\");\n\n		// any changes?\n		if ($new_lang !== $lang)\n		{\n			if ($lang === false)\n			{\n				safe_insert(\'txp_prefs\', \"prefs_id=1, name=\'wet_native_language\', val=\'$new_lang\', type=2, html=\'\', position=0, user_name=\'$name\', event=\'publish\'\");\n			}\n			else\n			{\n				safe_update(\'txp_prefs\', \"val=\'$new_lang\'\", \"name=\'wet_native_language\' and user_name=\'$name\'\");\n			}\n		}\n	}\n\n	/**\n	 * load languages for this particular user\n	 */\n	public static function run()\n	{\n		global $txp_user, $textarray, $prefs, $language;\n		$language = $prefs[\'language\'] = wet_native::_lang($txp_user);\n		$textarray = load_lang($language);\n	}\n\n    /**\n     * helper function retrieves current language setting for specified user\n     */\n	private static function _lang($user)\n	{\n		$user = doSlash($user);\n		$lang = safe_field(\'val\', \'txp_prefs\', \"name=\'wet_native_language\' and user_name=\'$user\'\");\n		// prefs n/a: use site default language\n		if ($lang === false) $lang = get_pref(\'language\', \'en-gb\');\n		return $lang;\n	}\n\n}\n\nif (txpinterface == \'admin\') {\n	new wet_native;\n}\n', '\nclass wet_native\n{\n	function __construct()\n    {\n    	// hook into the admin tab to render a custom input element\n    	register_callback(array(\'wet_native\', \'ui\'), \'author_ui\', \'extend_detail_form\');\n		register_callback(array(\'wet_native\', \'save\'), \'admin\', \'author_save\');\n		register_callback(array(\'wet_native\', \'save\'), \'admin\', \'author_save_new\');\n		// load i18n strings when the end of the HEAD element is rendered\n		register_callback(array(\'wet_native\', \'run\'), \'admin_side\', \'head_end\');\n    }\n\n	/**\n	 * event handler for rendering the additional language drop down in the admin tab\n	 */\n    public static function ui($event, $step, $default, $author)\n	{\n		global $txp_user;\n\n		// new users: default to current user\'s language setting\n		$name = (isset($author[\'name\']) ? $author[\'name\'] : $txp_user);\n\n		// list all installed languages\n		$rs = safe_column(\'distinct(lang)\', \'txp_lang\', \'1=1\');\n		if ($rs === false) return \'\';\n		foreach ($rs as $lang)\n		{\n			$out[$lang] = gTxt($lang);\n		}\n\n		$select = $out ? selectInput(\'wet_native_lang\', $out, wet_native::_lang($name), true) : \'\';\n\n		// render additional input element\n		return inputLabel(\'language\', $select);\n	}\n\n	/**\n	 * event handler saves the user-specific language setting as a private pref.\n	 */\n	public static function save($event, $step)\n	{\n		$new_lang = doSlash(ps(\'wet_native_lang\'));\n\n		// existing author\n		if ($step == \'author_save\')\n		{\n			$user_id = doSlash(ps(\'user_id\'));\n			$name = safe_field(\'name\', \'txp_users\', \"user_id=$user_id\");\n		}\n		else\n		// new author\n		{\n			$name = gps(\'name\');\n		}\n\n		if ($name === \'\') return;\n\n		$name = doSlash($name);\n		$lang = safe_field(\'val\', \'txp_prefs\', \"name=\'wet_native_language\' and user_name=\'$name\'\");\n\n		// any changes?\n		if ($new_lang !== $lang)\n		{\n			if ($lang === false)\n			{\n				safe_insert(\'txp_prefs\', \"prefs_id=1, name=\'wet_native_language\', val=\'$new_lang\', type=2, html=\'\', position=0, user_name=\'$name\', event=\'publish\'\");\n			}\n			else\n			{\n				safe_update(\'txp_prefs\', \"val=\'$new_lang\'\", \"name=\'wet_native_language\' and user_name=\'$name\'\");\n			}\n		}\n	}\n\n	/**\n	 * load languages for this particular user\n	 */\n	public static function run()\n	{\n		global $txp_user, $textarray, $prefs, $language;\n		$language = $prefs[\'language\'] = wet_native::_lang($txp_user);\n		$textarray = load_lang($language);\n	}\n\n    /**\n     * helper function retrieves current language setting for specified user\n     */\n	private static function _lang($user)\n	{\n		$user = doSlash($user);\n		$lang = safe_field(\'val\', \'txp_prefs\', \"name=\'wet_native_language\' and user_name=\'$user\'\");\n		// prefs n/a: use site default language\n		if ($lang === false) $lang = get_pref(\'language\', \'en-gb\');\n		return $lang;\n	}\n\n}\n\nif (txpinterface == \'admin\') {\n	new wet_native;\n}\n', '81593feb056fb79bd184ea074d7259bc', '', '', 3, 5, 0),
('wet_article_info', 0, 'Robert Wetzlmayr, Threshold State', 'http://awasteofwords.com/software/wet_article_info-display-word-count-and-comments-for-textpattern', '0.3.3', 'Word count and comments list for article edit pages', '<p><strong>wet_article_info</strong> is a very simple plugin that displays some extra information on the <strong>article &gt; write</strong> page.</p>\n\n<p>When editing an existing article, you&#8217;ll see something like this just below the Last Modified details:</p>\n\n<blockquote>\n	<p>705 words (680 + 25) | comments</p>\n</blockquote>\n\n<p>The first item is a word count: the total words, followed by individual counts for the body and excerpt.</p>\n\n<p><code>comments</code> will display a short summary of article comments, if any, with edit links for each.</p>\n\n<p>The information is <em>not</em> real time; it is updated when you save an article or open it for editing.</p>\n\n<p>Based upon zem_article_info 0.2.</p>', '\nregister_callback(\'wet_article_info\', \'article_ui\', \'author\');\n\nfunction wet_article_info($event, $step, $default, $rs) {\n	global $app_mode;\n	if ($rs) {\n		$bcount = str_word_count(strip_tags($rs[\'Body_html\']));\n		$ecount = str_word_count(strip_tags($rs[\'Excerpt_html\']));\n		$wcount = $bcount + $ecount;\n\n		// comments\n		$comments = \'\';\n		$comment_link = \'\';\n		$rows = safe_rows(\'*\', \'txp_discuss\', \"parentid=\'\".doSlash($rs[\'ID\']).\"\' and visible=\'\".VISIBLE.\"\' order by posted desc\");\n		if ($rows) {\n			foreach ($rows as $c) {\n				$m = preg_replace(\'@\\s+@\', \' \', strip_tags($c[\'message\']));\n				if (strlen($m) > 60)\n					$m = substr($m, 0, 60).\'&hellip;\';\n				$edit_link = eLink(\'discuss\', \'discuss_edit\', \'discussid\', $c[\'discussid\'], $m);\n				$cout[] = $edit_link.br.$c[\'email\'].\' | \'.safe_strftime(\'since\', strtotime($c[\'posted\']));\n			}\n			$comments = doWrap($cout, \'ul\', \'li\', \'\', \'\', \' id=\"wet_article_comments\" style=\"display:none;\"\');\n			$comment_link = \' | <a href=\"#\" onclick=\"toggleDisplay(\\\'wet_article_comments\\\'); return false;\">\'.gTxt(\'comments\').\'</a>\';\n		}\n\n		// main text\n		$extra = ($bcount && $ecount) ? \" ($bcount + $ecount)\" : \'\';\n		if ($app_mode == \'async\') {\n			send_script_response(\'$(\"#wet_article_info\").remove();\');\n		}\n		return $default.n.\n			tag(\n				graf(\"<small>$wcount \".gTxt(\'words\').\"$extra $comment_link</small>\").n.\n				$comments,\n				\'div\', \' id=\"wet_article_info\"\'\n			);\n	}\n}', '\nregister_callback(\'wet_article_info\', \'article_ui\', \'author\');\n\nfunction wet_article_info($event, $step, $default, $rs) {\n	global $app_mode;\n	if ($rs) {\n		$bcount = str_word_count(strip_tags($rs[\'Body_html\']));\n		$ecount = str_word_count(strip_tags($rs[\'Excerpt_html\']));\n		$wcount = $bcount + $ecount;\n\n		// comments\n		$comments = \'\';\n		$comment_link = \'\';\n		$rows = safe_rows(\'*\', \'txp_discuss\', \"parentid=\'\".doSlash($rs[\'ID\']).\"\' and visible=\'\".VISIBLE.\"\' order by posted desc\");\n		if ($rows) {\n			foreach ($rows as $c) {\n				$m = preg_replace(\'@\\s+@\', \' \', strip_tags($c[\'message\']));\n				if (strlen($m) > 60)\n					$m = substr($m, 0, 60).\'&hellip;\';\n				$edit_link = eLink(\'discuss\', \'discuss_edit\', \'discussid\', $c[\'discussid\'], $m);\n				$cout[] = $edit_link.br.$c[\'email\'].\' | \'.safe_strftime(\'since\', strtotime($c[\'posted\']));\n			}\n			$comments = doWrap($cout, \'ul\', \'li\', \'\', \'\', \' id=\"wet_article_comments\" style=\"display:none;\"\');\n			$comment_link = \' | <a href=\"#\" onclick=\"toggleDisplay(\\\'wet_article_comments\\\'); return false;\">\'.gTxt(\'comments\').\'</a>\';\n		}\n\n		// main text\n		$extra = ($bcount && $ecount) ? \" ($bcount + $ecount)\" : \'\';\n		if ($app_mode == \'async\') {\n			send_script_response(\'$(\"#wet_article_info\").remove();\');\n		}\n		return $default.n.\n			tag(\n				graf(\"<small>$wcount \".gTxt(\'words\').\"$extra $comment_link</small>\").n.\n				$comments,\n				\'div\', \' id=\"wet_article_info\"\'\n			);\n	}\n}', '3d5954719f6bee538bd9b5e12627823a', '#@admin\r\n#@language de-de\r\nwords => Wörter', '', 4, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_notes', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '1.3.1', 'Leave notes for Textpattern users', '<h1><strong>adi_notes</strong> &#8211; Leave notes for Textpattern users</h1>\n\n<p>This plugin gives you:</p>\n\n<ul>\n	<li>new tabs under Home, Content, Presentation and Admin to present your pearls of wisdom</li>\n	<li>public &amp; private notes embedded in all Textpattern admin pages</li>\n	<li>a tab under Extensions, to set up <strong>adi_notes</strong></li>\n</ul>\n\n<h2><strong>Installation</strong></h2>\n\n<p><i>Installation of <strong>adi_notes</strong> adds a new table to your Textpattern database which should not interfere with anything else. That said, if you are of a cautious frame of mind then I can thoroughly recommend <a href=\"http://forum.textpattern.com/viewtopic.php?id=10395\" rel=\"nofollow\">rss_admin_db_manager</a> to do database backups before installation.</i></p>\n\n<p>Once the plugin is installed and activated, go to its Options page and click “Install”.</p>\n\n<h2><strong>Upgrading adi_notes</strong></h2>\n\n<p>Although a backup of your notes will be made during the upgrade process it is recommended that you <em>make an independent copy as well</em>.</p>\n\n<p>An attempt will be made to convert custom styles into the new format but it is advisable to <em>take a copy of your existing style</em>.</p>\n\n<h2><strong>adi_notes admin tab</strong></h2>\n\n<p>Users with sufficient privileges will see the <strong>adi_notes</strong> admin tab under Extensions.  This gives you functionality to:</p>\n\n<ul>\n	<li>toggle note tab visibility on/off</li>\n	<li>set privilege levels of who can edit public notes</li>\n	<li>set privilege levels of who can use <span class=\"caps\">TXP</span> tags in their private notes</li>\n	<li>change position of embedded notes</li>\n	<li>change note styles</li>\n	<li>choose between Textile or TinyMCE</li>\n</ul>\n\n<h2><strong>Public &amp; Private</strong></h2>\n\n<p><em>Public</em> notes are visible to all users but can only be added/edited by an Admin user (i.e. a user with sufficiently elevated privileges).  Public notes can either be note tabs or embedded notes.</p>\n\n<p><em>Private</em> notes can be added by all users (including “Admin users”) but are only visible to the individual users. Private notes can only be embedded.</p>\n\n<p>By default, “Admin users” are Publishers &amp; Managing Editors.  This may be changed in the <strong>adi_notes</strong> admin tab.</p>\n\n<h2><strong>Note contents</strong></h2>\n\n<p>Notes can consist of anything that you can put in a article:</p>\n\n<ul>\n	<li>textiled text</li>\n	<li><span class=\"caps\">HTML</span> tags</li>\n	<li><span class=\"caps\">TXP</span> tags</li>\n</ul>\n\n<p>Tags will always be parsed in Note tabs and Public notes. The parsing of tags in private notes can be restricted according to privileges set in <strong>adi_notes</strong> admin.</p>\n\n<p>If you want to use <code>&lt;txp:php&gt;</code> tags then <em>Allow <span class=\"caps\">PHP</span> in pages?</em> must be switched on in Preferences.</p>\n\n<h2><strong>Note tabs</strong></h2>\n\n<p>Note tabs will appear under Home, Content, Presentation or Admin depending on the visibility set in <strong>adi_notes</strong> admin.  After installation, the Content Notes tab is switched on by default.  If you make the Home note tab visible then the Home tab itself will automatically appear.</p>\n\n<p>The Notes tabs are <em>public</em>, i.e. they will be visible to anyone who logs in to the Textpattern Admin interface.</p>\n\n<p>Notes made in note tabs can only be edited by an “Admin” user.</p>\n\n<h2><strong>Embedded notes</strong></h2>\n\n<p>Embedded notes are available in all Textpattern admin pages. They appear either at the top or at bottom of the page.</p>\n\n<p>All users see public notes. Private notes are only visible to individual users.</p>\n\n<p>Only “Admin” users can add &amp; edit public notes. All users can add &amp; edit their own private notes.</p>\n\n<h2><strong>TinyMCE</strong></h2>\n\n<p>To use the TinyMCE <span class=\"caps\">WYSIWYG</span> editor, you must install it:</p>\n\n<ul>\n	<li>as part of the <a href=\"http://forum.textpattern.com/viewtopic.php?id=13089\" rel=\"nofollow\">hak_tinymce</a> plugin</li>\n	<li>as a separate Javascript package</li>\n	<li>as a separate jQuery package</li>\n</ul>\n\n<p>If you choose to install it as a separate package then the path to the <code>tiny_mce</code> directory must be specified (as a path relative to the <i>textpattern</i> subdirectory) &#8211; the default is <i>../scripts/tiny_mce</i>, i.e. subdirectory <i>scripts</i> at the same level as <i>textpattern</i>.</p>\n\n<p>Visit the <strong>adi_notes</strong> admin tab to see the various options.</p>\n\n<h2><strong>Notes about adi_notes</strong></h2>\n\n<ul>\n	<li>Uninstalling <strong>adi_notes</strong> from the plugin options page will delete all notes from the Textpattern database</li>\n	<li>Note tab visibility simply controls whether the tabs can be seen in the specified areas.  Notes that are not visible are not deleted, just hidden. Embedded notes are always visible, however they may be removed by deleting the note contents</li>\n	<li>Add/Edit links will only be seen by those sufficiently elevated in the privilege department</li>\n	<li>By default, notes are formatted using Textile (see above for TinyMCE support)</li>\n	<li>Options that are set blank in the <strong>adi_notes</strong> admin tab (or in the case of privileges, all switched off) will revert to defaults</li>\n</ul>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_notes\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=27957\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_notes\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '/*\n	adi_notes - Leave notes for Textpattern users\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		1.3.1	- strict MySQL workarounds: removed DEFAULT from \'note\' column, restricted event name to 12 chars in set_pref for pre TXP 4.6 (thanks Gallex)\n		1.3		- edit link fixes for 4.6 (thanks jpdupont)\n				- tag parsing (for jpdupont)\n				- style fixes & improvements (thanks Bloke)\n				- inline style fix (thanks uli)\n				- fix for Hive theme header (thanks MarcoK)\n				- fix for preference save issue in pre-4.5.6\n				- admin tab layout & styling\n				- TXP 4.5+ only\n		1.2		- TXP 4.5-ified\n				- code tidy up\n				- changed: admin tab name/title now \"Notes Admin\"\n				- now uses lifecycle events\n				- changed the default width (thanks RedFox)\n		1.1.2	- fix for multi-site installs (thanks AdamK)\n				- style tweak for Hive\n		1.1.1	- fixed: get_off_my_lawn error in new article tab in 4.4.1\n				- admin: style improvements for theme compatibility\n		1.1		- admin: moved install/uninstall etc to plugin options\n				- enhancement: embedded notes at top or bottom of page\n				- enhancement: embedded notes now available on all TXP admin pages\n				- enhancement: note tab for Home/Start tab\n				- enhancement: Textpack\n				- enhancement: TinyMCE support\n				- new options: custom note style, note width, note edit privs\n				- change: default note width now 900px (was 90%)\n		1.0.2	- enhancement: embedded notes for Variables tab (for maruchan) and also adi_menu, adi_prefs (for good measure)\n		1.0.1	- fix: uninitialised variable errors\n		1.0		- enhancement: footnotes\n				- enhancement: public/private notes\n				- enhancement: in situ editing\n		0.3		- added edit links & links from Admin tab to notes tabs\n		0.2		- fix: CREATE TABLE error (thanks gocom)\n		0.1		- initial release\n\n	Upgrades:\n		- database tables/preferences tweaked automatically if required\n\n	Downgrade instructions:\n		1)	Go to Options page.\n		2)	Add \"&action=downgrade\" to end of URL & hit enter - should then see \"adi_notes: Downgraded\" message.\n		3)	Immediately go to Plugins tab to delete adi_notes.\n		4)	Install older adi_notes plugin version.\n		5)	Go to adi_notes admin tab & verify.\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $adi_notes_debug;\n\n	$adi_notes_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_notes_init();\n}\n\nfunction adi_notes_init() {\n// initial set up\n	global $event,$prefs,$txp_user,$adi_notes_login_user,$adi_notes_tab_list,$adi_notes_gtxt,$adi_notes_prefs,$adi_notes_debug,$adi_notes_plugin_status,$adi_notes_url,$adi_notes_priv_levels,$adi_notes_txp460;\n\n	if (!class_exists(\'Textile\'))\n		include(txpath.\'/lib/classTextile.php\');\n\n	$adi_notes_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	$adi_notes_login_user = $txp_user; // record txp_user here, so including publish.php later doesn\'t splat it\n\n	// default preferences\n	$adi_notes_prefs = array(\n		\'style\' => \'sticky\',	// or \'red\', \'minimal\', \'custom\'\n		\'position\' => \'footer\', // or \'header\'\n		\'width\' => \'width:90%; max-width:900px\',\n		\'width_old\' => \'\',\n		\'custom_style\' =>\n\'#adi_notes { margin:1em auto; width:90%; max-width:900px; padding:2em; border:1px solid red; background-color:lightgray; }\n#adi_notes h1 { color:red }\n#adi_notes p { margin:1em 0; color:green }\n#adi_notes p:first-child { margin-top:0 }\n#adi_notes p:last-child { margin-bottom:0 }\n#adi_notes ul li { color:blue }\n.adi_notes_tab_link { background-color:yellow }\n.adi_notes_embed_links { background-color:orange }\n#adi_notes.adi_notes_embed_headnote {  background-color:pink; color:red; margin-bottom:2em }\',\n		\'custom_style_old\' => \'\',\n		\'note_tab_edit_privs\' => \'1,2\',\n		\'public_note_edit_privs\' => \'1,2\', // future requirement?\n		\'txp_tag_privs\' => \'1\', // always allow publisher\n		\'markup\' => \'textile\',	// or \'html\'\n		\'tiny_mce\' => \'none\',	// or \'hak\', \'jquery\', \'javascript\' - may be modified on install if hak_tiny_mce found\n		\'tiny_mce_dir_path\' => \'../scripts/tiny_mce\',\n		\'convert_link\' => \'0\'\n		);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_notes_gtxt = array(\n		\'adi_add_note\' => \'Add note\',\n		\'adi_add_private_note\' => \'Add private note\',\n		\'adi_add_public_note\' => \'Add public note\',\n		\'adi_convert_textile\' => \'Convert Textile\',\n		\'adi_custom\' => \'Custom\',\n		\'adi_display_convert_option\' => \'Show Textile convert option\',\n		\'adi_edit_private_note\' => \'Edit private note\',\n		\'adi_edit_public_note\' => \'Edit public note\',\n		\'adi_footer\' => \'Footer\',\n		\'adi_header\' => \'Header\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_minimal\' => \'Minimal\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_note_delete_fail\' => \'Unable to delete note\',\n		\'adi_note_deleted\' => \'Note deleted\',\n		\'adi_note_editing_private\' => \'Editing private note\',\n		\'adi_note_editing_public\' => \'Editing public note\',\n		\'adi_note_markup\' => \'Markup\',\n		\'adi_note_public_edit_privs\' => \'Public note edit privileges\',\n		\'adi_note_save_fail\' => \'Unable to save note\',\n		\'adi_note_saved\' => \'Note saved\',\n		\'adi_note_tabs\' => \'Note tabs\',\n		\'adi_note_txp_tag_privs\' => \'Private note TXP tag privileges\',\n		\'adi_notes\' => \'Notes\',\n		\'adi_notes_admin\' => \'Notes Admin\',\n		\'adi_nothing_to_do\' => \'Nothing to do\',\n		\'adi_position\' => \'Position\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_red\' => \'Red\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_tiny_mce_dir_path\' => \'TinyMCE directory path\',\n		\'adi_tiny_mce_hak\' => \'TinyMCE (hak_tinymce)\',\n		\'adi_tiny_mce_javascript\' =>\'TinyMCE (Javascript)\',\n		\'adi_tiny_mce_jquery\' => \'TinyMCE (jQuery)\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_width\' => \'Width\',\n		\'adi_yellow\' => \'Yellow\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_notes_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_notes\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_notes_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_notes_lifecycle\',\'plugin_lifecycle.adi_notes\');\n\n	// privilege levels\n	$adi_notes_priv_levels = array(\n		1 => gTxt(\'publisher\'),\n		2 => gTxt(\'managing_editor\'),\n		3 => gTxt(\'copy_editor\'),\n		4 => gTxt(\'staff_writer\'),\n		5 => gTxt(\'freelancer\'),\n		6 => gTxt(\'designer\'),\n	);\n\n	// \'note tab name\' (used in the database) => (\'title\' => nice title, \'where\' => parent tab, \'visible\' => installed visibility)\n	// - could probably take this back to old format: array(\'start\'=> 0, \'content\' => 1 ...)\n	// - as of v1.1, only Content tab note added to database on install (adi_notes_update_visibility() adds other tabs as & when)\n	$adi_notes_tab_list = array(\n		\'start\' =>			array(\'title\' => gTxt(\'tab_start\'), \'where\' => \'start\', \'visible\' => \'0\'),\n		\'content\' =>		array(\'title\' => gTxt(\'tab_content\'), \'where\' => \'content\', \'visible\' => \'1\'),\n		\'presentation\' =>	array(\'title\' => gTxt(\'tab_presentation\'), \'where\' => \'presentation\', \'visible\' => \'0\'),\n		\'admin\' =>			array(\'title\' => gTxt(\'tab_admin\'), \'where\' => \'admin\', \'visible\' => \'0\'),\n		);\n\n	// adi_notes Admin tab\n	add_privs(\"adi_notes_admin\"); // defaults to priv \'1\' only\n	register_tab(\"extensions\",\"adi_notes_admin\",adi_notes_gtxt(\'adi_notes_admin\')); // add new tab under \'Extensions\'\n	register_callback(\"adi_notes_admin\",\"adi_notes_admin\");\n\n	// adi_notes tabs & embedded notes\n	$installed = adi_notes_installed();\n	if ($installed) {\n		if (!adi_notes_upgrade()) {\n			$visibility = adi_notes_tab_visibility();\n			foreach ($adi_notes_tab_list as $tab_name => $tab_info) {\n				// register note tab (if marked as visible)\n				if ($visibility[$tab_name]) {\n					add_privs(\"adi_notes_tab_\".$tab_name,\'1,2,3,4,5,6\'); // all privs\n					register_callback(\"adi_notes_tab\",\"adi_notes_tab_\".$tab_name);\n					register_tab($tab_info[\'where\'],\"adi_notes_tab_\".$tab_name,adi_notes_gtxt(\'adi_notes\'));\n				}\n			}\n			// add embedded notes to all tabs\n			register_callback(\'adi_notes_embed_note\',\'admin_side\',\'footer\');\n			// jQuery fix for reliable header position (works for Classic/Remora/Hive in 4.5 & 4.6)\n			if (adi_notes_prefs(\'position\') == \'header\')\n				register_callback(\'adi_notes_header_script\',\'admin_side\',\'head_end\');\n			register_callback(\'adi_notes_embed_note_links\',\'admin_side\',\'footer\'); // add/edit links in footer\n			// switch on Home tab\n			if ($visibility[\'start\'])\n				add_privs(\'tab.start\',\'1,2,3,4,5,6\'); // all privs\n		}\n	}\n\n	// style\n	register_callback(\'adi_notes_style\',\'admin_side\',\'head_end\');\n\n	// admin page script\n	if ($event == \'adi_notes_admin\')\n		register_callback(\'adi_notes_admin_script\',\'admin_side\',\'head_end\');\n\n	// TinyMCE\n	if (adi_notes_prefs(\'tiny_mce\') != \'none\')\n		register_callback(\'adi_notes_tiny_mce_\'.adi_notes_prefs(\'tiny_mce\'),\'admin_side\',\'footer\');\n\n	// plugin options\n	$adi_notes_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_notes\',$adi_notes_debug);\n	if ($adi_notes_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_notes\'); // defaults to priv \'1\' only\n		register_callback(\'adi_notes_options\',\'plugin_prefs.adi_notes\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_notes_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_notes_options\',\'adi_notes options\');\n		register_callback(\'adi_notes_options\',\'adi_notes_options\');\n	}\n\n}\n\nfunction adi_notes_style() {\n// some style for the page\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step == \'edit\') || ($adi_notes_step == \'edit_user\'))\n		$nav_fix = t.t.t.\'#nav li ul { z-index:999 }\'.n; // so TXP remora dropdown overlays TinyMCE toolbar properly\n	else\n		$nav_fix = \'\';\n\n	// set up note style\n	$note_styles = array();\n	switch (adi_notes_prefs(\'style\')) {\n		case \'sticky\':\n			$note_styles[] = \'#adi_notes { margin:1em auto; padding:1em; border:1px solid; border-color:#e3d8c3 #c3b8a3 #c3b8a3 #e3d8c3; background-color:#ffffcc; color:#202020 }\';\n			break;\n		case \'red\':\n			$note_styles[] = \'#adi_notes { margin:1em auto; padding:1em; border:1px solid #b22222; background-color:#ffffff; color:#b22222 }\';\n			break;\n		case \'minimal\':\n			$note_styles[] = \'#adi_notes { margin:1em auto }\';\n			break;\n		case \'custom\':\n			$note_styles = explode(n,adi_notes_prefs(\'custom_style\'));\n			break;\n		default:\n			$note_styles[] = \'/* no style found */\';\n	}\n	if (adi_notes_prefs(\'style\') != \'custom\') {\n		$note_styles[] = \'#adi_notes p { margin:1em 0; }\';\n		$note_styles[] = \'#adi_notes p:first-child { margin-top:0 }\';\n		$note_styles[] = \'#adi_notes p:last-child { margin-bottom:0 }\';\n		$note_styles[] = \'#adi_notes.adi_notes_embed_headnote { margin-bottom:2em }\';\n	}\n\n	// collate rules\n	$note_style = \'/* adi_notes \'.adi_notes_prefs(\'style\').\' style */\'.n;\n	foreach ($note_styles as $style)\n		$note_style .= t.t.t.$style.n;\n\n	// generate <style>...</style>\n	echo\n		\'<style type=\"text/css\">\n			/* adi_notes - admin tab */\n			.adi_notes_form { width:60%; margin:0 auto 2em; padding:1em; text-align:center }\n			.adi_notes_form h2 { margin:1.5em 0 0 }\n			.adi_notes_form p { margin:0.8em 0 0 }\n			.adi_notes_form textarea { margin-top:0.8em }\n			.adi_notes_form .smallerbox { margin-top:3em }\n			.adi_notes_form label { margin-left:2em }\n			.adi_notes_form label:first-child { margin-left:0 }\n			.adi_notes_form p.adi_notes_width label, .adi_notes_form p.adi_notes_input_dir label { margin-left:0; margin-right:0.5em }\n			.adi_notes_form p.adi_notes_width input { width:18em }\n			/* adi_notes - note tabs & embedded notes */\n			#adi_notes { text-align:left }\n			#adi_notes .adi_notes_hr { width:100%; margin:1em auto; border:none; height:1px; color:#e0e0e0; background-color:#e0e0e0 }\n			#adi_notes .adi_notes_textarea { width:98%; height:25em; margin-top:0 }\n			#adi_notes .adi_notes_textarea_tab { height:50em }\n			#adi_notes .adi_notes_label { margin:0 0 0.5em 0; color:black }\n			#adi_notes .adi_notes_save { margin:0.5em 0 0 0; color:black }\n			#adi_notes .adi_notes_save input.publish { margin:0 1em 0 0 }\n			.txp-footer .adi_notes_embed_links { margin:1em 0; text-align:center }\n			/* adi_notes - note tabs */\n			.adi_notes_tab_link { margin:1em 0; text-align:center }\n			\'.$nav_fix\n			.$note_style\n			.(adi_notes_prefs(\'style\') == \'custom\' ? \'\' : t.t.t.\'/* adi_notes width */\'.n.t.t.t.\'#adi_notes { \'.adi_notes_prefs(\'width\').\' }\').n\n		.\'</style>\';\n}\n\nfunction adi_notes_admin_script() {\n// jQuery magic for admin tab\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	// adi_notes admin script\n\n	$().ready(function() {\n\n		// auto hide/show TinyMCE dir path input field\n		$(\'#adi_notes_tiny_mce_none,#adi_notes_tiny_mce_hak\').click(\n			function(){\n				$(\'p.adi_notes_input_dir\').hide();\n			}\n		);\n		$(\'#adi_notes_tiny_mce_jquery,#adi_notes_tiny_mce_javascript\').click(\n			function(){\n				$(\'p.adi_notes_input_dir\').show();\n			}\n		);\n\n		// auto hide/show Textile convert option input field\n		$(\'#adi_notes_tiny_mce_none\').click(\n			function(){\n				$(\'p.adi_notes_input_convert\').hide();\n			}\n		);\n		$(\'#adi_notes_tiny_mce_jquery,#adi_notes_tiny_mce_javascript,#adi_notes_tiny_mce_hak\').click(\n			function(){\n				$(\'p.adi_notes_input_convert\').show();\n			}\n		);\n\n		// auto hide/show custom style textarea\n		$(\'#adi_notes_style_custom\').click(\n			function(){\n				$(\'textarea[name=\"custom_style\"]\').show();\n				$(\'p.adi_notes_width\').hide();\n			}\n		);\n		$(\'#adi_notes_style_sticky,#adi_notes_style_red,#adi_notes_style_minimal\').click(\n			function(){\n				$(\'textarea[name=\"custom_style\"]\').hide();\n				$(\'p.adi_notes_width\').show();\n			}\n		);\n\n	});\n\n</script>\n\nEND_SCRIPT;\n	echo $script;\n}\n\nfunction adi_notes_header_script() {\n// header position fix for Hive theme\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	// adi_notes header script\n\n	$().ready(function() {\n		$(\"#adi_notes\").prependTo(\".txp-body\");\n	});\n\n</script>\n\nEND_SCRIPT;\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_jquery() {\n// TinyMCE implementation, using jQuery - TinyMCE options copied from hak_tinymce\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\" src=\"adi_notes_tiny_mce_dir_path/jquery.tinymce.js\"></script>\n\n<script type=\"text/javascript\">\n\n	$().ready(function() {\n\n		$(\'#adi_notes textarea\').tinymce({\n\n			// Location of TinyMCE script\n\n			script_url : \'adi_notes_tiny_mce_dir_path/tiny_mce.js\',\n\n			// General options\n\n			theme : \"advanced\",\n\n			language : \"en\",\n			relative_urls : false,\n			remove_script_host : false,\n			plugins : \"searchreplace\",\n			entity_encoding : \"numeric\",\n			// Theme options\n\n			theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n			theme_advanced_buttons2 : \"link,unlink,separator,image,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n			theme_advanced_buttons3 : \"\",\n			theme_advanced_toolbar_location : \"top\",\n			theme_advanced_toolbar_align : \"left\",\n\n		});\n\n\n	});\n\n</script>\n\nEND_SCRIPT;\n\n	$script = preg_replace(\'/adi_notes_tiny_mce_dir_path/\',adi_notes_prefs(\'tiny_mce_dir_path\'),$script);\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_hak() {\n// TinyMCE implementation, using hak_tinymce options & script installed in textpattern subdir\n	global $adi_notes_debug;\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = \'<script language=\"javascript\" type=\"text/javascript\" src=\"tiny_mce/tiny_mce.js\"></script>\';\n	$script .= \'<script type=\"text/javascript\">\';\n	$script .= \'tinyMCE.init({\';\n	$script .= \'mode : \"textareas\",\';\n	$script .= fetch(\'pref_value\',\'txp_hak_tinymce\',\'pref_name\',\'body_init\',$adi_notes_debug);\n	$script .= \'editor_selector : \"adi_notes_mceEditor\",\';\n	$script .= \'});\';\n	$script .= \'</script>\';\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_javascript() {\n// TinyMCE implementation, using Javascript\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\" src=\"adi_notes_tiny_mce_dir_path/tiny_mce.js\"></script>\n\n<script type=\"text/javascript\">\n\n	tinyMCE.init({\n\n			// General options\n			mode : \"textareas\",\n\n			theme : \"advanced\",\n\n			language : \"en\",\n			relative_urls : false,\n			remove_script_host : false,\n			plugins : \"searchreplace\",\n			entity_encoding : \"numeric\",\n			editor_selector : \"adi_notes_mceEditor\",\n\n			// Theme options\n\n			theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n			theme_advanced_buttons2 : \"link,unlink,separator,image,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n			theme_advanced_buttons3 : \"\",\n			theme_advanced_toolbar_location : \"top\",\n			theme_advanced_toolbar_align : \"left\",\n\n	});\n\n</script>\nEND_SCRIPT;\n\n	$script = preg_replace(\'/adi_notes_tiny_mce_dir_path/\',adi_notes_prefs(\'tiny_mce_dir_path\'),$script);\n	echo $script;\n}\n\nfunction adi_notes_has_privs($res, $user=\'\') {\n// like txplib_misc.php has_privs() but accepts a comma separated list of privs rather than a standard named combo\n	global $adi_notes_login_user,$adi_notes_debug;\n\n	if ($res) {\n		if (empty($user)) // no user supplied, so use the current login name\n			$user = $adi_notes_login_user;\n		$user_privs = safe_field(\"privs\", \"txp_users\", \"name=\'\".doSlash($user).\"\'\");\n		if ($adi_notes_debug)\n			echo \'PRIVS: required=\'.$res.\' actual=\'.$user_privs.\' (\'.$user.\')\';\n		return in_array($user_privs,explode(\',\',$res));\n	}\n	else\n		return false;\n}\n\nfunction adi_notes_admin($event, $step) {\n// the admin tab\n	global $prefs,$adi_notes_tab_list,$adi_notes_debug,$adi_notes_plugin_status,$adi_notes_priv_levels,$adi_notes_prefs;\n\n	$message = \'\';\n	$installed = adi_notes_installed();\n	$submit = gps(\'adi_notes_admin_submit\'); // check that someone\'s hit the tit\n\n	// process the information\n	if ($installed) {\n		$upgrade_required = adi_notes_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_notes_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else if ($submit) {\n			if ($step == \'update\') {\n\n				// massage submitted note_tab_edit_privs\n				$submitted_note_tab_edit_privs = ps(\"note_tab_edit_privs\");\n				if (empty($submitted_note_tab_edit_privs)) // revert to default privs\n					foreach (explode(\',\',$adi_notes_prefs[\'note_tab_edit_privs\']) as $priv)\n						$submitted_note_tab_edit_privs[$priv] = 1;\n				$submitted_note_tab_edit_privs[1] = TRUE; // make sure publisher priv switched on\n				$note_tab_edit_privs = implode(\',\',array_keys($submitted_note_tab_edit_privs));\n\n				// txp_tag_privs\n				$submitted_txp_tag_privs = ps(\"txp_tag_privs\");\n				if (empty($submitted_txp_tag_privs))\n					$submitted_txp_tag_privs = array();\n				$txp_tag_privs = implode(\',\',array_keys($submitted_txp_tag_privs));\n\n				// do update\n				$update_ok = adi_notes_update_visibility(ps(\"visibility\"))\n								&& adi_notes_prefs(\'note_tab_edit_privs\',$note_tab_edit_privs)\n								&& adi_notes_prefs(\'public_note_edit_privs\',$note_tab_edit_privs) // currently same as note tab edit privs\n								&& adi_notes_prefs(\'txp_tag_privs\',$txp_tag_privs)\n								&& adi_notes_prefs(\'style\',strip_tags(ps(\"style\")))\n								&& adi_notes_prefs(\'custom_style\',strip_tags(ps(\"custom_style\")))\n								&& adi_notes_prefs(\'position\',strip_tags(ps(\"position\")))\n								&& adi_notes_prefs(\'width\',strip_tags(ps(\"width\")))\n								&& adi_notes_prefs(\'tiny_mce\',strip_tags(ps(\"tiny_mce\")))\n								&& adi_notes_prefs(\'tiny_mce_dir_path\',strip_tags(ps(\"tiny_mce_dir_path\")))\n								&& adi_notes_prefs(\'convert_link\',strip_tags(ps(\"convert_link\")));\n				if (adi_notes_prefs(\'tiny_mce\') == \'none\')\n					$update_ok &= adi_notes_prefs(\'markup\',\'textile\');\n				else\n					$update_ok &= adi_notes_prefs(\'markup\',\'html\');\n				if ($update_ok)\n					$message = adi_notes_gtxt(\'preferences_saved\');\n				else\n					$message = array(adi_notes_gtxt(\'adi_pref_update_fail\'),E_ERROR);\n			}\n		}\n	}\n	else\n		$message = array(adi_notes_gtxt(\'adi_not_installed\'),E_ERROR);\n\n	// generate page\n	pagetop(adi_notes_gtxt(\'adi_notes\').\' \'.gTxt(\'admin\'),$message);\n\n	if ($installed && !$upgrade_required) {\n		// visibility checkboxes\n		$visibility = adi_notes_tab_visibility();\n		$visibility_checkboxes = \'\';\n		foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n			$visibility_checkboxes .= tag(checkbox(\"visibility[$tab_name]\",\'1\',$visibility[$tab_name]).$tab_info[\'title\'],\'label\');\n\n		// note tab edit priv checkboxes\n		$note_tab_edit_priv_checkboxes = \'\';\n		$current_privs = explode(\',\',adi_notes_prefs(\'note_tab_edit_privs\'));\n		foreach ($adi_notes_priv_levels as $priv => $priv_name)\n			$note_tab_edit_priv_checkboxes .= tag(checkbox(\"note_tab_edit_privs[$priv]\",\'1\',in_array($priv,$current_privs)).$priv_name,\'label\');\n\n		// txp tag priv checkboxes\n		$txp_tag_priv_checkboxes = \'\';\n		$current_privs = explode(\',\',adi_notes_prefs(\'txp_tag_privs\'));\n		foreach ($adi_notes_priv_levels as $priv => $priv_name)\n			$txp_tag_priv_checkboxes .= tag(checkbox(\"txp_tag_privs[\".$priv.\"]\", \"1\", in_array($priv,$current_privs)).$priv_name,\'label\');\n\n		// width field peekaboo\n		if (adi_notes_prefs(\'style\') == \'custom\') {\n			$hide_custom = \'\';\n			$hide_width = \' style=\"display:none\"\';\n		}\n		else {\n			$hide_width = \'\';\n			$hide_custom = \'display:none\';\n		}\n		// TinyMCE peepo\n		if ((adi_notes_prefs(\'tiny_mce\') == \'none\') || (adi_notes_prefs(\'tiny_mce\') == \'hak\'))\n			$hide_dir = \' style=\"display:none\"\';\n		else\n			$hide_dir = \'\';\n		if (adi_notes_prefs(\'tiny_mce\') == \'none\')\n			$hide_convert = \' style=\"display:none\"\';\n		else\n			$hide_convert = \'\';\n\n		// position\n		$position =\n			tag(adi_notes_gtxt(\'adi_position\'),\"h2\")\n			.graf(\n				tag(radio(\'position\',\'header\',(adi_notes_prefs(\'position\') == \'header\')).adi_notes_gtxt(\'adi_header\'),\'label\')\n				.tag(radio(\'position\',\'footer\',(adi_notes_prefs(\'position\') == \'footer\')).adi_notes_gtxt(\'adi_footer\'),\'label\')\n			);\n\n		$hak_tinymce_installed = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'hak_tinymce\'\",$adi_notes_debug);\n\n	    echo form(\n			tag(adi_notes_gtxt(\'adi_notes_admin\'),\'h1\')\n			// visibility\n			.tag(adi_notes_gtxt(\'adi_note_tabs\'),\'h2\')\n			.graf($visibility_checkboxes)\n			// note tab/public note edit privs\n			.tag(adi_notes_gtxt(\'adi_note_public_edit_privs\'),\"h2\")\n			.graf($note_tab_edit_priv_checkboxes)\n			// txp tag privs\n			.tag(adi_notes_gtxt(\'adi_note_txp_tag_privs\'),\"h2\")\n			.graf($txp_tag_priv_checkboxes)\n			// position\n			.$position\n			// style\n			.tag(gTxt(\'style\'),\"h2\")\n			.graf(\n				tag(radio(\'style\',\'sticky\',(adi_notes_prefs(\'style\') == \'sticky\'),\'adi_notes_style_sticky\').adi_notes_gtxt(\'adi_yellow\'),\'label\')\n				.tag(radio(\'style\',\'red\',(adi_notes_prefs(\'style\') == \'red\'),\'adi_notes_style_red\').adi_notes_gtxt(\'adi_red\'),\'label\')\n				.tag(radio(\'style\',\'minimal\',(adi_notes_prefs(\'style\') == \'minimal\'),\'adi_notes_style_minimal\').adi_notes_gtxt(\'adi_minimal\'),\'label\')\n				.tag(radio(\'style\',\'custom\',(adi_notes_prefs(\'style\') == \'custom\'),\'adi_notes_style_custom\').adi_notes_gtxt(\'adi_custom\'),\'label\')\n			)\n			// width\n			.graf(\n				tag(adi_notes_gtxt(\'adi_width\'),\'label\')\n				.finput(\"text\",\'width\',adi_notes_prefs(\'width\'))\n				,\' class=\"adi_notes_width\"\'.$hide_width\n			)\n			// custom style\n			.adi_notes_textarea(\'custom_style\',adi_notes_prefs(\'custom_style\'),\'adi_notes_style_custom_style\',5,40,$hide_custom)\n			// TinyMCE\n			.tag(adi_notes_gtxt(\'adi_note_markup\'),\"h2\")\n			.graf(\n				tag(radio(\'tiny_mce\',\'none\',(adi_notes_prefs(\'tiny_mce\') == \'none\'),\'adi_notes_tiny_mce_none\').gTxt(\'txptextile\'),\'label\')\n				.($hak_tinymce_installed ? tag(radio(\'tiny_mce\',\'hak\',(adi_notes_prefs(\'tiny_mce\') == \'hak\'),\'adi_notes_tiny_mce_hak\').adi_notes_gtxt(\'adi_tiny_mce_hak\'),\'label\') : \'\')\n				.tag(radio(\'tiny_mce\',\'jquery\',(adi_notes_prefs(\'tiny_mce\') == \'jquery\'),\'adi_notes_tiny_mce_jquery\').adi_notes_gtxt(\'adi_tiny_mce_jquery\'),\'label\')\n				.tag(radio(\'tiny_mce\',\'javascript\',(adi_notes_prefs(\'tiny_mce\') == \'javascript\'),\'adi_notes_tiny_mce_javascript\').adi_notes_gtxt(\'adi_tiny_mce_javascript\'),\'label\')\n			)\n			.graf(\n				tag(adi_notes_gtxt(\'adi_tiny_mce_dir_path\'),\'label\').finput(\"text\",\'tiny_mce_dir_path\',adi_notes_prefs(\'tiny_mce_dir_path\'))\n				,\' class=\"adi_notes_input_dir\"\'.$hide_dir\n			)\n			.graf(adi_notes_gtxt(\'adi_display_convert_option\').checkbox(\'convert_link\',\'1\',adi_notes_prefs(\'convert_link\'))\n				,\' class=\"adi_notes_input_convert\"\'.$hide_convert\n			)\n	        .fInput(\'submit\',\'adi_notes_admin_submit\',adi_notes_gtxt(\'adi_update_prefs\'),\"smallerbox\",\"\",\'\').\n	        eInput(\"adi_notes_admin\").sInput(\"update\")\n			,\'\',\'\',\'post\',\'adi_notes_form\'\n		);\n	}\n}\n\nfunction adi_notes_textarea($name, $thing = \'\', $id = \'\', $rows=\'5\', $cols=\'40\', $inline_style) {\n// based on text_area() but with added inline style & without height & width\n\n	$id = ($id) ? \' id=\"\'.$id.\'\"\' : \'\';\n	$rows = \' rows=\"\' . ( ($rows && is_numeric($rows)) ? $rows : \'5\') . \'\"\';\n	$cols = \' cols=\"\' . ( ($cols && is_numeric($cols)) ? $cols : \'40\') . \'\"\';\n	$style = ($inline_style) ? \' style=\"\'.$inline_style.\'\"\' : \'\';\n	return \'<textarea\'.$id.\' name=\"\'.$name.\'\"\'.$rows.$cols.$style.\'>\'.txpspecialchars($thing).\'</textarea>\';\n}\n\nfunction adi_notes_options($event,$step) {\n// display adi_notes options: install/uninstall/upgrade\n	global $adi_notes_debug,$adi_notes_url,$adi_notes_plugin_status,$adi_notes_txp460;\n\n	$message = \'\';\n	$installed = adi_notes_installed();\n\n	$action = gps(\'action\'); // \"under the counter\" action\n\n	$adi_notes_txp460 ? $e = \'adi_notes_admin\' : $e = \'adi_notes_ad\'; // transitional fix for event field length (prior to 4.6 = 12) & strict MySQL implementations\n\n	if ($installed) {\n		$upgrade_required = adi_notes_upgrade();\n		if ($upgrade_required) { // upgrade\n			$res = adi_notes_upgrade(TRUE); // copy notes old -> new\n			if ($res)\n				$message = adi_notes_gtxt(\'adi_upgraded\');\n			else\n				$message = array(adi_notes_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n		}\n		// $stepping out\n		if ($step == \"uninstall\") { // uninstall adi_notes\n			$res = adi_notes_drop(\'adi_notes\');\n			$res = $res && adi_notes_delete_prefs();\n			if ($res)\n	    		$message = adi_notes_gtxt(\'adi_uninstalled\');\n			else\n	    		$message = array(adi_notes_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n		}\n		else if ($step == \'textpack\') {\n			$adi_textpack = file_get_contents($adi_notes_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_notes_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n		// $action man\n		else if ($action == \"downgrade\") { // revert\n			if (adi_notes_installed(\'adi_notes_old\')) {\n				$res = adi_notes_drop(\'adi_notes\');\n				$res = $res && adi_notes_rename(\'adi_notes_old\',\'adi_notes\'); // restore from \'adi_notes_old\'\n				$res = $res && safe_delete(\'txp_prefs\',\"name = \'adi_notes_txp_tag_privs\'\",$adi_notes_debug); // delete pref introduced in 1.3\n				// restore old custom style\n				$res = $res && set_pref(\'adi_notes_custom_style\',adi_notes_prefs(\'custom_style_old\'),$e,2);\n				// restore old width style\n				$res = $res && set_pref(\'adi_notes_width\',adi_notes_prefs(\'width_old\'),$e,2);\n				if ($res)\n					$message = \"downgraded\";\n				else\n					$message = array(\"downgrade failed\",E_ERROR);\n			}\n			else\n				$message = array(\"downgrade failed, no backup found\",E_ERROR);\n		}\n		else if ($action == \"backup\") { // create backup in \'adi_notes_backup\'\n			$res = TRUE;\n			if (adi_notes_installed(\'adi_notes_backup\'))\n				$res = adi_notes_drop(\'adi_notes_backup\');\n			$res = $res && adi_notes_copy(\'adi_notes\',\'adi_notes_backup\');\n			if ($res)\n				$message = \"backed up\";\n			else\n				$message = array(\"backup failed\",E_ERROR);\n		}\n		else if ($action == \"restore\") { // restore from \'adi_notes_backup\'\n			if (adi_notes_installed(\'adi_notes_backup\')) {\n				$res = adi_notes_drop(\'adi_notes\');\n				$res = $res && adi_notes_copy(\'adi_notes_backup\',\'adi_notes\');\n				if ($res)\n					$message = \"restored\";\n				else\n					$message = array(\"restore failed\",E_ERROR);\n			}\n			else\n				$message = array(\"restore failed, no backup found\",E_ERROR);\n		}\n		else if ($action == \"cleanup\") { // delete upgrade backup\n			$res = TRUE;\n			if (adi_notes_installed(\'adi_notes_old\'))\n				$res = adi_notes_drop(\'adi_notes_old\');\n			if ($res) {\n				$message = \"upgrade backup deleted\";\n			}\n			else\n				$message = array(\"unable to delete upgrade backup\",E_ERROR);\n		}\n	}\n	else { // not installed\n		if ($step == \"install\") { // install adi_notes\n			$res = adi_notes_install();\n			if ($res)\n				$message = adi_notes_gtxt(\'adi_installed\');\n			else\n				$message = array(adi_notes_gtxt(\'adi_install_fail\'),E_ERROR);\n		}\n		else\n			$message = array(adi_notes_gtxt(\'adi_not_installed\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_notes - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_notes_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n			,\'\',\'\',\'post\',\'adi_notes_nstall_button\'\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", adi_notes_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n			,\'\',\'\',\'post\',\'adi_notes_nstall_button adi_notes_uninstall_button\'\n		);\n\n	if ($adi_notes_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_notes_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_notes \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_notes_gtxt(\'adi_textpack_online\'),$adi_notes_url[\'textpack_download\']))\n			.graf(href(adi_notes_gtxt(\'adi_textpack_feedback\'),$adi_notes_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n\nfunction adi_notes_prefs($name,$new_value=\'!return_value_only!\') {\n// set/read preferences\n	global $prefs,$adi_notes_prefs,$adi_notes_txp460,$adi_notes_debug;\n\n	if (empty($new_value)) // reset to default\n		$new_value = $adi_notes_prefs[$name];\n\n	// workaround for set pref escaping issue (fixed in 4.5.6)\n	if (!version_compare(txp_version,\'4.5.5\',\'>\'))\n		$new_value = doSlash($new_value);\n\n	// read current value (either from database or $adi_notes_prefs)\n	isset($prefs[\'adi_notes_\'.$name]) ? $value = $prefs[\'adi_notes_\'.$name] : $value = $adi_notes_prefs[$name];\n\n	if ($adi_notes_debug) echo \"adi_notes_prefs: name=$name, value=$value, new_value=$new_value\".br;\n\n	$adi_notes_txp460 ? $e = \'adi_notes_admin\' : $e = \'adi_notes_ad\'; // transitional fix for event field length (prior to 4.6 = 12) & strict MySQL implementations\n\n	if ($new_value == \'!toggle!\') { // toggle boolean\n		$value ? $value = 0 : $value = 1; // toggle debug mode\n		set_pref(\'adi_notes_\'.$name,$value,$e,2);\n		$prefs = get_prefs(); // re-sample $prefs\n	}\n	else if ($new_value == \'!return_value_only!\') // just return value\n		return $value;\n	else { // update pref\n		if ($adi_notes_debug) echo \"adi_notes_prefs: update pref ...\";\n		$res = set_pref(\'adi_notes_\'.$name,$new_value,$e,2);\n		$prefs = get_prefs(); // re-sample $prefs\n		if ($adi_notes_debug) echo \" res=$res\".br;\n		return $res;\n	}\n}\n\nfunction adi_notes_installed($table=\'adi_notes\') {\n// test if supplied table is present\n	global $adi_notes_debug;\n\n	$found = FALSE;\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\",$adi_notes_debug);\n	$a = nextRow($rs);\n	if ($a)\n		$found = TRUE;\n	return $found;\n}\n\nfunction adi_notes_upgrade($do_upgrade=FALSE) {\n// check format of adi_notes table / copy from previous version backup table if required\n	global $adi_notes_debug,$prefs;\n\n	if (adi_notes_installed()) { // find out what needs upgrading\n\n		// pre-v1.0 - check for presence of \'id\' column\n		$rs = safe_query(\"SHOW COLUMNS FROM \".safe_pfx(\'adi_notes\').\" LIKE \'id\'\",$adi_notes_debug); // \"SHOW COLUMNS WHERE\" no good for MySQL version 4.1\n		$a = nextRow($rs);\n		$pre_v1_0 = empty($a);\n\n		// v1.0 - look for non-event names - \'comment\',\'user\',\'variables\',\'adi_menu\',\'adi_prefs\' (should be \'discuss\',\'admin\',\'adi_variables_admin\',\'adi_menu_admin\',\'adi_prefs_admin\')\n		$oldies = \"\'comment\',\'user\',\'variables\',\'adi_menu\',\'adi_prefs\'\";\n		$newbies = \"\'discuss\',\'admin\',\'adi_variables_admin\',\'adi_menu_admin\',\'adi_prefs_admin\'\";\n		$v1_0 = safe_rows(\'*\',\'adi_notes\',\"name IN ($oldies)\",$adi_notes_debug); // read data from current table\n\n		// v1.3 - look for txp_tag_privs preference\n		$v1_3 = !isset($prefs[\'adi_notes_txp_tag_privs\']);\n\n		$upgrade_required = $pre_v1_0 || $v1_0 || $v1_3;\n\n		if ($adi_notes_debug) echo \"pre_v1_0=$pre_v1_0, v1_0=$v1_0, v1_3=$v1_3, upgrade_required=$upgrade_required\".br;\n\n		if ($do_upgrade) {\n			if ($upgrade_required) {\n				if ($pre_v1_0)\n					$backup_version = \'pre_v1_0\';\n				else\n					$backup_version = \'post_v1_0\';\n				if ($adi_notes_debug) echo \'Upgrading from \'.$backup_version.br;\n				$res = TRUE; // optimistic\n				$rs = safe_rows(\'*\',\'adi_notes\',\"1=1\",$adi_notes_debug); // read data from current table\n				// take backup\n				if (adi_notes_installed(\'adi_notes_old\')) // delete previous \"upgrade\" backup\n					$res = adi_notes_drop(\'adi_notes_old\');\n				$res = $res && adi_notes_copy(\'adi_notes\',\'adi_notes_old\',$backup_version);	// take an \"upgrade\" backup\n				// do upgrade stuff\n				if ($pre_v1_0) {\n					// change table structure\n					$res = $res && adi_notes_drop(\'adi_notes\'); // delete previous version table\n					$res = $res && adi_notes_install(FALSE); // create vanilla new format table\n					foreach ($rs as $name => $row) { // copy note data to new table\n						extract($row);\n						if ($adi_notes_debug) dmp($row);\n						$res = $res && safe_query(\n							\"INSERT INTO \".\n							safe_pfx(\'adi_notes\').\n							\" VALUES (DEFAULT,\'\".$name.\"\',\'tab\',DEFAULT,DEFAULT,\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_ID.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n							);\n					}\n				}\n				if ($v1_0) {\n					if ($adi_notes_debug) echo \'v1_0 upgrade:\'.br;\n					// rename non-event names\n					$old_list = explode(\',\',$oldies);\n					$new_list = explode(\',\',$newbies);\n					foreach ($old_list as $index => $name) {\n						$rs = safe_row(\'*\',\'adi_notes\',\"name=$name\",$adi_notes_debug);\n						if ($rs)\n							$res = $res && safe_update(\'adi_notes\',\"name=$new_list[$index]\",\"name=$name\",$adi_notes_debug);\n					}\n					// switch embedded note visibility on (for consistency & possible future use)\n					$res = $res && safe_update(\'adi_notes\',\"visible=1\",\"type=\'embed\'\",$adi_notes_debug);\n				}\n				if ($v1_3) {\n					if ($adi_notes_debug) echo \'v1_3 upgrade:\'.br;\n					// add TXP tag privs preference\n					$res = $res && adi_notes_prefs(\'txp_tag_privs\',\'\');\n					// make copy of old custom style\n					(isset($prefs[\'adi_notes_custom_style\'])) ? $old_custom_style = adi_notes_prefs(\'custom_style\') : $old_custom_style = \'\';\n					$res = $res && adi_notes_prefs(\'custom_style_old\',$old_custom_style);\n					// make copy of old width style\n					(isset($prefs[\'adi_notes_width\'])) ? $old_width = adi_notes_prefs(\'width\') : $old_width = \'\';\n					$res = $res && adi_notes_prefs(\'width_old\',$old_width);\n					// massage v1.1 width pref\n					if ((strpos($old_width,\'width:\') === FALSE) && $old_width) // v1.1 width pref didn\'t have \"width:\"\n						$res = $res && adi_notes_prefs(\'width\',\'width:\'.$old_width);\n					// wrap old custom style in #adi_notes selector\n					(isset($prefs[\'adi_notes_custom_style\'])) ? $new_custom_style = \'#adi_notes { \'.adi_notes_prefs(\'custom_style\').\' }\' : $new_custom_style = adi_notes_prefs(\'custom_style\');\n					// massage v1.1 width pref\n					if ((strpos($old_width,\'width:\') === FALSE) && $old_width) // v1.1 width pref didn\'t have \"width:\"\n						$old_width = \'width:\'.$old_width;\n					if (empty($old_width)) // use new default\n						$old_width = adi_notes_prefs(\'width\');\n					// add old width onto end of custom style\n					$new_custom_style .= n.\'#adi_notes { \'.$old_width.\' }\';\n					$res = $res && adi_notes_prefs(\'custom_style\',$new_custom_style);\n				}\n				return $res;\n			}\n			else\n				return FALSE;\n		}\n		else\n			return $upgrade_required;\n	}\n	else\n		return FALSE;\n}\n\nfunction adi_notes_copy($source,$destination,$backup_version=\'\') {\n// copy table: source -> destination\n	global $adi_notes_debug;\n\n	$res = safe_query(\"CREATE TABLE \".safe_pfx($destination).\" LIKE \".safe_pfx($source).\";\",$adi_notes_debug);\n	// can\'t use following because single quotes etc. in $note need to be escaped\n	// $res = $res && safe_query(\"INSERT \".safe_pfx($destination).\" SELECT * FROM \".safe_pfx($source).\";\",$adi_notes_debug);\n	$rs = safe_rows(\'*\',$source,\"1=1\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		if ($backup_version == \'pre_v1_0\')\n			$res = $res && safe_query(\n						\"INSERT INTO \".\n						safe_pfx($destination).\n						\" VALUES (\'\".$name.\"\',\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_ID.\"\',\'\".$restricted_privs.\"\',\'\".$restricted_ID.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n						);\n		else\n			$res = $res && safe_query(\n						\"INSERT INTO \".\n						safe_pfx($destination).\n						\" VALUES (DEFAULT,\'\".$name.\"\',\'\".$type.\"\',\'\".$user.\"\',\'\".$privs.\"\',\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_user.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n						);\n	}\n	return $res;\n}\n\nfunction adi_notes_rename($source,$destination) {\n// rename table: source -> destination\n	global $adi_notes_debug;\n\n	return safe_query(\"RENAME TABLE \".safe_pfx($source).\" TO \".safe_pfx($destination),$adi_notes_debug);\n}\n\nfunction adi_notes_drop($db_table) {\n// delete a table\n	global $adi_notes_debug;\n\n	return safe_query(\"DROP TABLE \".safe_pfx($db_table).\";\",$adi_notes_debug);\n}\n\nfunction adi_notes_install($vanilla=TRUE) {\n// create adi_notes table & insert note tabs that are visible by default (i.e. content) - adi_notes_tab_visibility will insert the others\n	global $adi_notes_tab_list,$adi_notes_debug;\n\n	// database table\n	$res = safe_query( // maintain quirk in CREATE TABLE - type maybe should\'ve defaulted to \'tab\'\n		\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_notes\').\" (\n		`id` INT(11) NOT NULL AUTO_INCREMENT,\n		`name` VARCHAR(64) NOT NULL DEFAULT \'\',\n		`type` VARCHAR(8) NOT NULL DEFAULT \'\',\n		`user` VARCHAR(64) NULL DEFAULT \'\',\n		`privs` VARCHAR(22) NOT NULL DEFAULT \'1,2,3,4,5,6\',\n		`visible` TINYINT(1) NOT NULL DEFAULT \'0\',\n		`last_mod` DATETIME NOT NULL DEFAULT \'0000-00-00 00:00:00\',\n		`last_mod_user` VARCHAR(64) NOT NULL DEFAULT \'\',\n		`note` TEXT NOT NULL,\n		PRIMARY KEY(id)\n		);\"\n		,$adi_notes_debug);\n\n	if ($vanilla) // populate table with empty note tab row(s)\n		foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n			if ($tab_info[\'visible\'])\n				$res = $res &&\n					safe_query(\n						\"INSERT INTO \".\n						safe_pfx(\'adi_notes\').\n// 						\" VALUES (DEFAULT,\'\".$tab_name.\"\',\'tab\',DEFAULT,DEFAULT,1,DEFAULT,DEFAULT,DEFAULT)\" // pre-1.3.1\n						\" VALUES (DEFAULT,\'\".$tab_name.\"\',\'tab\',DEFAULT,DEFAULT,1,DEFAULT,DEFAULT,\'\')\" // uses explicit value for empty note\n						,$adi_notes_debug);\n	return $res;\n}\n\nfunction adi_notes_delete_prefs() {\n// leave no trace\n	global $adi_notes_prefs,$adi_notes_debug;\n\n	$res = TRUE;\n	foreach ($adi_notes_prefs as $name => $value)\n		if (safe_row(\"*\",\'txp_prefs\',\"name = \'adi_notes_$name\'\",$adi_notes_debug))\n			$res = $res && safe_delete(\'txp_prefs\',\"name = \'adi_notes_$name\'\",$adi_notes_debug);\n	return $res;\n}\n\nfunction adi_notes_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_notes_debug;\n\n	$result = \'?\';\n	// set upgrade flag if reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_notes_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_notes_install();\n	else if ($step == \'deleted\') {\n		$result = adi_notes_drop(\'adi_notes\');\n		$result = $result && adi_notes_delete_prefs();\n	}\n	if ($upgrade)\n		$result = $result && adi_notes_upgrade(TRUE);\n	if ($adi_notes_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_notes_tab_visibility() {\n// create visibility list array of note tabs from database\n	global $adi_notes_tab_list,$adi_notes_debug;\n\n	$visibility = array();\n	// get current note tab visibility from database (BEWARE user tab event = \"admin\")\n	$rs = safe_rows(\'*\',\'adi_notes\',\"type=\'tab\'\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		if (array_key_exists($name,$adi_notes_tab_list))\n			$visibility[$name] = $visible; // create array, indexed by name, of note tab visibility found in database\n	}\n	// compare $adi_notes_tab_list with $visibility & add any missing tabs to array\n	foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n		if (!array_key_exists($tab_name,$visibility))\n			$visibility[$tab_name] = 0;\n	return $visibility;\n}\n\nfunction adi_notes_update_visibility($visibility) {\n// update database with new note tab visibility info (adding any newly visible note tabs to the mix at the same time)\n	global $adi_notes_debug;\n\n	$res = TRUE;\n	// switch all note tab visibility off (BEWARE user tab event = \"admin\")\n	$rs = safe_rows(\'*\',\'adi_notes\',\"type=\'tab\'\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		$res = $res && safe_update(\'adi_notes\',\"visible=0\",\"name=\'$name\' AND type = \'tab\'\",$adi_notes_debug);\n	}\n	if ($visibility) {\n		// selectively switch visibility back on (and insert row if missing)\n		foreach ($visibility as $tab_name => $visible) {\n			$found = safe_row(\'name\',\'adi_notes\',\"name=\'$tab_name\' AND type=\'tab\'\",$adi_notes_debug);\n			if ($found)\n				$res = $res && safe_update(\'adi_notes\',\"visible=$visible\",\"name=\'$tab_name\' AND type=\'tab\'\",$adi_notes_debug);\n			else\n// 				$res = $res && safe_insert(\'adi_notes\',\"name=\'$tab_name\', visible=$visible, type=\'tab\'\",$adi_notes_debug); // (pre-1.3.1) set type = \'tab\' coz it\'s not the default (historical quirk in CREATE TABLE)\n				$res = $res && safe_insert(\'adi_notes\',\"name=\'$tab_name\', visible=$visible, type=\'tab\', note=\'\'\",$adi_notes_debug); // set type = \'tab\' coz it\'s not the default (historical quirk in CREATE TABLE)\n		}\n	}\n	return $res;\n}\n\nfunction adi_notes_read($note_name,$type,$note_id,$user) {\n// read given note from database\n	global $adi_notes_debug;\n\n	$this_note = array();\n	if (empty($note_id)) { // selection of note based on context\n		$where = \"name=\'\".$note_name.\"\' AND type=\'\".$type.\"\'\";\n		$user ?\n			$where .= \" AND user=\'\".$user.\"\'\" :\n			$where .= \" AND user=\'\'\";\n		$this_note = safe_row(\"*\",\'adi_notes\',$where,$adi_notes_debug);\n	}\n	else // specific note id supplied\n		$this_note = safe_row(\"*\",\'adi_notes\',\"id=\'\".$note_id.\"\'\",$adi_notes_debug);\n	if ($this_note) {\n		if (adi_notes_prefs(\'markup\') == \'textile\') {\n			$textile = new Textile;\n			$this_note[\'formatted\'] = $textile->TextileThis($this_note[\'note\']);\n		}\n		else\n			$this_note[\'formatted\'] = $this_note[\'note\'];\n	}\n	return $this_note;\n}\n\nfunction adi_notes_tab($event,$step) {\n// display note tab\n	global $pretext,$adi_notes_debug,$adi_notes_tab_list,$adi_notes_prefs,$adi_notes_txp460;\n\n	include_once txpath.\'/publish.php\';\n\n	$message = \'\';\n\n	// style\n	adi_notes_prefs(\'style\') ?\n		$class = \' class=\"adi_notes_\'.adi_notes_prefs(\'style\').\'\"\' :\n		$class = \'\';\n\n	$note_name = substr($event,strlen(\"adi_notes_tab_\")); // strip \"adi_notes_tab_\" from event name\n	if (adi_notes_upgrade()) // upgrade required\n		pagetop($adi_notes_tab_list[$note_name][\'title\'].\' - \'.adi_notes_gtxt(\'adi_notes\'),\'adi_notes: <strong>\'.adi_notes_gtxt(\'adi_upgrade_required\').\'</strong>\');\n	else {\n		$adi_notes_step = gps(\'adi_notes_step\');\n		$adi_notes_id = gps(\'adi_notes_id\');\n		$adi_notes_user = gps(\'adi_notes_user\');\n		$adi_notes_note = gps(\'adi_notes_note\');\n		$adi_notes_convert = gps(\'adi_notes_convert\');\n\n		if ($adi_notes_debug)\n			print \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$adi_notes_id, adi_notes_user=$adi_notes_user</p>\";\n\n		// one $step at a time\n		if ($adi_notes_step == \'save\') {\n			$res = adi_notes_save($note_name,\'tab\',$adi_notes_id,\'\',$adi_notes_note);\n			if ($adi_notes_note)\n				$res ? $message = adi_notes_gtxt(\'adi_note_saved\') : $message = adi_notes_gtxt(\'adi_note_save_fail\');\n			else\n				$res ? $message = adi_notes_gtxt(\'adi_note_deleted\') : $message = adi_notes_gtxt(\'adi_note_delete_fail\');\n		}\n\n		// decide what to display\n		if ($adi_notes_step == \'edit\')\n			$thing = adi_notes_edit_form($note_name,\'tab\',$adi_notes_id,\'\',$adi_notes_convert);\n		else {\n			$this_note = adi_notes_read($note_name,\'tab\',$adi_notes_id,\'\');\n			$thing = $this_note[\'formatted\'];\n			if ($this_note[\'note\'])\n				$linktext = gTxt(\'edit\');\n			else\n				$linktext = adi_notes_gtxt(\'adi_add_note\');\n			$thing = parse($thing);\n		}\n\n		// output page\n		pagetop($adi_notes_tab_list[$note_name][\'title\'].\' - \'.adi_notes_gtxt(\'adi_notes\'),$message);\n		if ($thing)\n			echo tag($thing,\'div\',\' id=\"adi_notes\"\'.$class);\n\n		// display add/edit link\n		if (adi_notes_has_privs(adi_notes_prefs(\'note_tab_edit_privs\')) && !($adi_notes_step == \'edit\'))\n			echo graf(\n				\'[\'\n				.($adi_notes_txp460 ?\n					href(\n						$linktext\n						,array(\n							\'event\' => $event,\n							\'adi_notes_step\' => \'edit\',\n							\'adi_notes_id\' => $this_note[\'id\'],\n							\'adi_notes_user\' => \'\'\n						)\n// 						,array(\'class\' => \'small\')\n					)\n				:\n					slink($event,\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$this_note[\'id\'].\'&amp;adi_notes_user=\',$linktext,\'small\')\n				)\n				.\']\'\n				,\' class=\"adi_notes_tab_link\"\'\n			);\n	}\n}\n\nfunction adi_notes_edit_form($note_name,$type,$note_id,$note_user,$convert=\'\') {\n// display individual note edit form\n	global $event,$step,$adi_notes_login_user,$timeoffset,$adi_notes_debug,$adi_notes_txp460;\n\n	$preserve_urlvar = adi_notes_preserve_urlvar();\n	if ($note_id) { // read note from database\n		$this_note = safe_row(\"*\",\'adi_notes\',\"id=\'\".$note_id.\"\'\",$adi_notes_debug);\n		extract($this_note); // $name, $type, $user, $privs, $visible, $last_mod, $last_mod_user, $note\n		$realname = fetch(\'RealName\',\'txp_users\',\'name\',$last_mod_user);\n		$mod_msg = gTxt(\'modified_by\').\" $realname: \".date(\"H:i, d M Y\",strtotime($last_mod) + $timeoffset);\n		if ($convert == \'textile\') {\n			$textile = new Textile;\n			$note = $textile->TextileThis($note);\n		}\n	}\n	else // note doesn\'t exist yet\n		$note = \'\';\n\n	if (strpos($event,\'adi_notes_tab_\') === FALSE) { // don\'t want a footnote on a Notes tab\n		if ($note_user) {\n			$adi_notes_step = \'save_user\';\n			$title = adi_notes_gtxt(\'adi_note_editing_private\').\':\';\n		}\n		else {\n			$submit_button = gTxt(\'save\');\n			$adi_notes_step = \'save\';\n			$title = adi_notes_gtxt(\'adi_note_editing_public\').\':\';\n		}\n	}\n	else {\n		$adi_notes_step = \'save\';\n		$title = \'\';\n	}\n\n	if ($type == \'tab\')\n		$extra_class = \' adi_notes_textarea_tab\';\n	else\n		$extra_class = \'\';\n	if (adi_notes_prefs(\'tiny_mce\') != \'none\')\n		$extra_class .= \' adi_notes_mceEditor\';\n\n	if ($title)\n		$title = tag($title,\'p\',\' class=\"adi_notes_label\"\');\n	$out[] =\n		$title\n		.\'<textarea name=\"adi_notes_note\" class=\"adi_notes_textarea\'.$extra_class.\'\">\'.htmlspecialchars($note).\'</textarea>\'\n		.graf(\n			fInput(\'submit\',\'savenote\',gTxt(\'save\'),\'publish\')\n			.eInput($event)\n			.sInput($step)\n			.($preserve_urlvar ? hInput(adi_notes_preserve_urlvar(\'var\'),adi_notes_preserve_urlvar(\'value\')) : \'\') // pass on edit screen article/image ID/id\n			.hInput(\'adi_notes_step\',$adi_notes_step)\n			.($note_user ? hInput(\'adi_notes_user_note_id\',$note_id) : hInput(\'adi_notes_id\',$note_id))\n			.hInput(\'adi_notes_user\',$adi_notes_login_user)\n			.(empty($note) ? \'\' : $mod_msg)\n			.((adi_notes_prefs(\'markup\') != \'textile\') && adi_notes_prefs(\'convert_link\') && !empty($note) ? // not textile AND convert link requested AND non-empty note\n				sp\n				.sp\n				.\'[\'\n				.($adi_notes_txp460 ?\n					tag(\n						adi_notes_gtxt(\'adi_convert_textile\')\n						,\'a\'\n						,\'href=\"\'.join_qs(array(\'event\'=>$event,\'adi_notes_step\'=>\'edit\',\'adi_notes_user\'=>\'\',\'adi_notes_id\'=>$this_note[\'id\'],\'adi_notes_convert\'=>\'textile\')).\'\"\'\n					)\n				:\n					slink($event,\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$this_note[\'id\'].\'&amp;adi_notes_user=&amp;adi_notes_convert=textile\',adi_notes_gtxt(\'adi_convert_textile\'),\'small\')\n				)\n				.\']\'\n			:\n				\'\'\n			)\n			,\' class=\"adi_notes_save\"\'\n		);\n\n	if ($adi_notes_debug)\n		$note_user ?\n		$out[] = \'<p>\'.__FUNCTION__.\": Event=$event,Step=$step,adi_notes_step=$adi_notes_step,note_name=$note_name,adi_notes_id=$note_id,note_user=$note_user,adi_notes_login_user=$adi_notes_login_user</p>\" :\n		$out[] = \'<p>\'.__FUNCTION__.\": Event=$event,Step=$step,adi_notes_step=$adi_notes_step,note_name=$note_name,adi_notes_user_note_id=$note_id,note_user=$note_user,adi_notes_login_user=$adi_notes_login_user</p>\";\n\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$anchor = \'\'; // jump back to top of page after edit headnotes\n	else\n		$anchor = \'adi_notes\';\n\n	return form(implode(\'\',$out),\'\',\'\',\'post\',\'\',$anchor);\n}\n\nfunction adi_notes_save($note_name,$type,$note_id,$user,$note) {\n// save note to database\n	global $event,$adi_notes_login_user,$adi_notes_debug;\n\n	$note_name = doSlash($note_name);\n	$type = doSlash($type);\n	$note_id = doSlash($note_id);\n	$user = doSlash($user);\n	$note = doSlash($note);\n	empty($note_id) ? // new note\n		$note_id = \"DEFAULT\" :\n		$note_id = \"\'\".$note_id.\"\'\";\n	empty($user) ? // blank user = public note\n		$user = \"DEFAULT\" :\n		$user = \"\'\".$user.\"\'\";\n\n	return safe_query(\n		\"REPLACE INTO \".\n		safe_pfx(\'adi_notes\').\n		\" VALUES ($note_id,\'$note_name\',\'$type\',$user,DEFAULT,\'1\',now(),\'$adi_notes_login_user\',\'$note\')\",$adi_notes_debug\n		);\n}\n\nfunction adi_notes_preserve_urlvar($what=\'\') {\n// get an $event specific URL var & return various value permutations\n	global $event,$adi_notes_txp460;\n\n	$this_var = array();\n	$var_name = \'\';\n	// define which one\n	if ($event == \'article\')\n		$var_name = \'ID\'; // upper case ID\n	if ($event == \'image\')\n		$var_name = \'id\'; // lower case id\n	if (($event == \'page\') || ($event == \'css\') || ($event == \'form\'))\n		$var_name = \'name\';\n	// get it\n	if ($var_name) {\n		$value = gps($var_name);\n		if ($value) {\n			$this_var[\'var\'] = $var_name;\n			$this_var[\'value\'] = $value;\n		}\n	}\n	// sort out what to return\n	if ($this_var) {\n		if ($what == \'var\')\n			return $this_var[\'var\'];\n		else if ($what == \'value\')\n			return $this_var[\'value\'];\n		else\n			return \'&amp;\'.$this_var[\'var\'].\'=\'.$this_var[\'value\'];\n	}\n	else\n		return \'\';\n}\n\nfunction adi_notes_embed_note($x,$y,$default) {\n// generate markup for embedded notes\n	global $pretext,$event,$step,$adi_notes_debug,$adi_notes_prefs,$adi_notes_login_user;\n\n	include_once txpath.\'/publish.php\';\n\n	// bailout\n	if (adi_notes_upgrade()) // upgrade required\n		return \'\';\n	if (strpos($event,\'adi_notes_tab_\') !== FALSE) // don\'t want a embedded note on a Notes tab\n		return \'\';\n	if ($event == \'adi_notes_options\') // don\'t want embedded note on txpdev adi_notes options page\n		return \'\';\n	if ($event == \'plugin\') // don\'t want a embedded note during lifecycle events on plugin page (e.g. plugin delete)\n		return \'\';\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	$note = gps(\'adi_notes_note\');\n	$note_id = gps(\'adi_notes_id\');\n	$note_name = $event;\n	$note_user = gps(\'adi_notes_user\');\n	$user_note_id = gps(\'adi_notes_user_note_id\');\n\n	// class\n	adi_notes_prefs(\'style\') ?\n		$class = \'adi_notes_\'.adi_notes_prefs(\'style\') :\n		$class = \'\';\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$class .= \' adi_notes_embed_headnote\';\n	else\n		$class .= \' adi_notes_embed_footnote\';\n	if ($class)\n		$class = \' class=\"\'.$class.\'\"\';\n\n	$admin_note = $user_note = $edit_note = $edit_user_note = \'\';\n\n	$preamble = \'<div id=\"adi_notes\"\'.$class.\'>\';\n	$postamble = \'</div>\';\n\n	if ($adi_notes_debug)\n		$debug = \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_login_user=$adi_notes_login_user, note_user=$note_user, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$note_id, adi_notes_user_note_id=$user_note_id</p>\";\n	else\n		$debug = \'\';\n\n	if ($adi_notes_step == \'edit\')\n		$edit_note = adi_notes_edit_form($note_name,\'embed\',$note_id,\'\');\n	else if ($adi_notes_step == \'save\')\n		adi_notes_save($note_name,\'embed\',$note_id,\'\',$note);\n	else if ($adi_notes_step == \'edit_user\')\n		$edit_user_note = adi_notes_edit_form($note_name,\'embed\',$user_note_id,$note_user);\n	else if ($adi_notes_step == \'save_user\')\n		adi_notes_save($note_name,\'embed\',$user_note_id,$note_user,$note);\n\n	if (!($adi_notes_step == \'edit\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',\'\');\n		if (!empty($this_note)) {\n			$admin_note = $this_note[\'formatted\'];\n			$admin_note = parse($admin_note);\n			$note_id = $this_note[\'id\'];\n		}\n	}\n\n	if (!($adi_notes_step == \'edit_user\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',$adi_notes_login_user);\n		if (!empty($this_note)) {\n			$user_note = $this_note[\'formatted\'];\n			if (adi_notes_has_privs(adi_notes_prefs(\'txp_tag_privs\'))) // only parse private notes if user has sufficient privs\n				$user_note = parse($user_note);\n			$user_note_id = $this_note[\'id\'];\n		}\n	}\n\n	// set up what needs to be output\n	$out = $debug;\n	if (($admin_note || $edit_note) && ($user_note || $edit_user_note))\n		$hr = \'<hr class=\"adi_notes_hr\"/>\';\n	else\n		$hr =\'\';\n	if ($admin_note || $edit_note || $user_note || $edit_user_note)\n		$out .= $preamble.$admin_note.$edit_note.$hr.$user_note.$edit_user_note.$postamble;\n\n	return $out; // adi_notes_embed_note_links() sorts out $default (div#end_page)\n}\n\nfunction adi_notes_embed_note_links($x, $y, $default) {\n// create edit/add links for embedded notes\n	global $event,$step,$adi_notes_debug,$adi_notes_txp460,$adi_notes_login_user;\n\n	// bailout\n	if (adi_notes_upgrade()) // upgrade required\n		return \'\';\n	if (strpos($event,\'adi_notes_tab_\') !== FALSE) // don\'t want a embedded note on a Notes tab\n		return \'\';\n	if ($event == \'adi_notes_options\') // don\'t want embedded note on txpdev adi_notes options page\n		return \'\';\n	if ($event == \'plugin\') // don\'t want a embedded note during lifecycle events on plugin page (e.g. plugin delete)\n		return \'\';\n\n	// article edit & image edit screens get special treatment\n	$this_step = $step;\n	if (($event != \'article\') && ($event != \'image\'))\n		$this_step = \'\'; // too dangerous to preserve step (in edit/add links) if not article/image edit screen\n	$preserve_urlvar = adi_notes_preserve_urlvar(); // article/image edit tab id/ID\n\n	// token effort for TXP 4.4.1+\n	$txp_token = \'\';\n	if (version_compare(txp_version,\'4.4.1\',\'>=\'))\n		if (($event == \'article\') && ($step == \'create\')) // article create tab only\n			$txp_token = \'&amp;_txp_token=\'.form_token();\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	$note_name = $event;\n\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$anchor = \'\'; // jump back to top of page after editing headnotes\n	else\n		$anchor = \'#adi_notes\';\n\n	$out = \'\';\n	$admin_link = $user_link = \'\';\n\n	// set up admin link\n	$admin_link = \'\';\n	if (!($adi_notes_step == \'edit\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',\'\');\n		if (!empty($this_note)) {\n			$admin_note = $this_note[\'formatted\'];\n			$note_id = $this_note[\'id\'];\n		}\n		else {\n			$admin_note = \'\';\n			$note_id = \'\';\n		}\n		if ($admin_note)\n			$link_text = adi_notes_gtxt(\'adi_edit_public_note\');\n		else\n			$link_text = adi_notes_gtxt(\'adi_add_public_note\');\n		if (adi_notes_has_privs(adi_notes_prefs(\'public_note_edit_privs\')))\n			$admin_link =\n				\'[\'\n				.($adi_notes_txp460 ?\n					tag(//? TOKEN\n						$link_text\n						,\'a\'\n						,\'href=\"\'.join_qs(array(\'event\'=>$event,\'step\'=>$this_step,\'adi_notes_step\'=>\'edit\',\'adi_notes_id\'=>$note_id)).$preserve_urlvar.$anchor.\'\"\'\n					)\n				:\n					slink($event,$this_step.$preserve_urlvar.$txp_token.\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$note_id.$anchor,$link_text,\'small\')\n				)\n				.\']\';\n\n		if ($adi_notes_debug)\n			$out .= \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_login_user=$adi_notes_login_user, this_step=$this_step, preserve_urlvar=$preserve_urlvar, txp_token=$txp_token, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$note_id</p>\";\n	}\n\n	// set up user link\n	$user_link = \'\';\n	if (!($adi_notes_step == \'edit_user\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',$adi_notes_login_user);\n		if (!empty($this_note)) {\n			$user_note = $this_note[\'formatted\'];\n			$user_note_id = $this_note[\'id\'];\n		}\n		else {\n			$user_note = \'\';\n			$user_note_id = \'\';\n		}\n		if ($user_note) {\n			$link_text = adi_notes_gtxt(\'adi_edit_private_note\');\n			$note_user = $this_note[\'user\'];\n		}\n		else {\n			$link_text = adi_notes_gtxt(\'adi_add_private_note\');\n			$note_user = $adi_notes_login_user;\n		}\n		$user_link =\n			\' [\'\n			.($adi_notes_txp460 ?\n				tag( //? TOKEN\n					$link_text\n					,\'a\'\n					,\'href=\"\'.join_qs(array(\'event\'=>$event,\'step\'=>$this_step,\'adi_notes_step\'=>\'edit_user\',\'adi_notes_user_note_id\'=>$user_note_id,\'adi_notes_user\'=>$note_user)).$preserve_urlvar.$anchor.\'\"\'\n				)\n			:\n				slink($event,$this_step.$preserve_urlvar.$txp_token.\'&amp;adi_notes_step=edit_user\'.\'&amp;adi_notes_user_note_id=\'.$user_note_id.\'&amp;adi_notes_user=\'.$note_user.$anchor,$link_text,\'small\')\n			)\n			.\']\';\n\n		if ($adi_notes_debug)\n			$out .= \'<p>\'.__FUNCTION__.\": event=$event, step=$step, this_step=$this_step, adi_notes_login_user=$adi_notes_login_user, note_user=$note_user, preserve_urlvar=$preserve_urlvar, txp_token=$txp_token, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_user_note_id=$user_note_id</p>\";\n	}\n\n	$out .= graf($admin_link.$user_link,\' class=\"adi_notes_embed_links\"\');\n\n	return $out.$default;\n}\n\nfunction adi_notes_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_notes_gtxt() are functionally equivalent\n	global $adi_notes_gtxt;\n\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_notes_gtxt)) // adi translation found\n			return $adi_notes_gtxt[$phrase];\n		else // last resort\n			return $phrase;\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n', '/*\n	adi_notes - Leave notes for Textpattern users\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		1.3.1	- strict MySQL workarounds: removed DEFAULT from \'note\' column, restricted event name to 12 chars in set_pref for pre TXP 4.6 (thanks Gallex)\n		1.3		- edit link fixes for 4.6 (thanks jpdupont)\n				- tag parsing (for jpdupont)\n				- style fixes & improvements (thanks Bloke)\n				- inline style fix (thanks uli)\n				- fix for Hive theme header (thanks MarcoK)\n				- fix for preference save issue in pre-4.5.6\n				- admin tab layout & styling\n				- TXP 4.5+ only\n		1.2		- TXP 4.5-ified\n				- code tidy up\n				- changed: admin tab name/title now \"Notes Admin\"\n				- now uses lifecycle events\n				- changed the default width (thanks RedFox)\n		1.1.2	- fix for multi-site installs (thanks AdamK)\n				- style tweak for Hive\n		1.1.1	- fixed: get_off_my_lawn error in new article tab in 4.4.1\n				- admin: style improvements for theme compatibility\n		1.1		- admin: moved install/uninstall etc to plugin options\n				- enhancement: embedded notes at top or bottom of page\n				- enhancement: embedded notes now available on all TXP admin pages\n				- enhancement: note tab for Home/Start tab\n				- enhancement: Textpack\n				- enhancement: TinyMCE support\n				- new options: custom note style, note width, note edit privs\n				- change: default note width now 900px (was 90%)\n		1.0.2	- enhancement: embedded notes for Variables tab (for maruchan) and also adi_menu, adi_prefs (for good measure)\n		1.0.1	- fix: uninitialised variable errors\n		1.0		- enhancement: footnotes\n				- enhancement: public/private notes\n				- enhancement: in situ editing\n		0.3		- added edit links & links from Admin tab to notes tabs\n		0.2		- fix: CREATE TABLE error (thanks gocom)\n		0.1		- initial release\n\n	Upgrades:\n		- database tables/preferences tweaked automatically if required\n\n	Downgrade instructions:\n		1)	Go to Options page.\n		2)	Add \"&action=downgrade\" to end of URL & hit enter - should then see \"adi_notes: Downgraded\" message.\n		3)	Immediately go to Plugins tab to delete adi_notes.\n		4)	Install older adi_notes plugin version.\n		5)	Go to adi_notes admin tab & verify.\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $adi_notes_debug;\n\n	$adi_notes_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_notes_init();\n}\n\nfunction adi_notes_init() {\n// initial set up\n	global $event,$prefs,$txp_user,$adi_notes_login_user,$adi_notes_tab_list,$adi_notes_gtxt,$adi_notes_prefs,$adi_notes_debug,$adi_notes_plugin_status,$adi_notes_url,$adi_notes_priv_levels,$adi_notes_txp460;\n\n	if (!class_exists(\'Textile\'))\n		include(txpath.\'/lib/classTextile.php\');\n\n	$adi_notes_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	$adi_notes_login_user = $txp_user; // record txp_user here, so including publish.php later doesn\'t splat it\n\n	// default preferences\n	$adi_notes_prefs = array(\n		\'style\' => \'sticky\',	// or \'red\', \'minimal\', \'custom\'\n		\'position\' => \'footer\', // or \'header\'\n		\'width\' => \'width:90%; max-width:900px\',\n		\'width_old\' => \'\',\n		\'custom_style\' =>\n\'#adi_notes { margin:1em auto; width:90%; max-width:900px; padding:2em; border:1px solid red; background-color:lightgray; }\n#adi_notes h1 { color:red }\n#adi_notes p { margin:1em 0; color:green }\n#adi_notes p:first-child { margin-top:0 }\n#adi_notes p:last-child { margin-bottom:0 }\n#adi_notes ul li { color:blue }\n.adi_notes_tab_link { background-color:yellow }\n.adi_notes_embed_links { background-color:orange }\n#adi_notes.adi_notes_embed_headnote {  background-color:pink; color:red; margin-bottom:2em }\',\n		\'custom_style_old\' => \'\',\n		\'note_tab_edit_privs\' => \'1,2\',\n		\'public_note_edit_privs\' => \'1,2\', // future requirement?\n		\'txp_tag_privs\' => \'1\', // always allow publisher\n		\'markup\' => \'textile\',	// or \'html\'\n		\'tiny_mce\' => \'none\',	// or \'hak\', \'jquery\', \'javascript\' - may be modified on install if hak_tiny_mce found\n		\'tiny_mce_dir_path\' => \'../scripts/tiny_mce\',\n		\'convert_link\' => \'0\'\n		);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_notes_gtxt = array(\n		\'adi_add_note\' => \'Add note\',\n		\'adi_add_private_note\' => \'Add private note\',\n		\'adi_add_public_note\' => \'Add public note\',\n		\'adi_convert_textile\' => \'Convert Textile\',\n		\'adi_custom\' => \'Custom\',\n		\'adi_display_convert_option\' => \'Show Textile convert option\',\n		\'adi_edit_private_note\' => \'Edit private note\',\n		\'adi_edit_public_note\' => \'Edit public note\',\n		\'adi_footer\' => \'Footer\',\n		\'adi_header\' => \'Header\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_minimal\' => \'Minimal\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_note_delete_fail\' => \'Unable to delete note\',\n		\'adi_note_deleted\' => \'Note deleted\',\n		\'adi_note_editing_private\' => \'Editing private note\',\n		\'adi_note_editing_public\' => \'Editing public note\',\n		\'adi_note_markup\' => \'Markup\',\n		\'adi_note_public_edit_privs\' => \'Public note edit privileges\',\n		\'adi_note_save_fail\' => \'Unable to save note\',\n		\'adi_note_saved\' => \'Note saved\',\n		\'adi_note_tabs\' => \'Note tabs\',\n		\'adi_note_txp_tag_privs\' => \'Private note TXP tag privileges\',\n		\'adi_notes\' => \'Notes\',\n		\'adi_notes_admin\' => \'Notes Admin\',\n		\'adi_nothing_to_do\' => \'Nothing to do\',\n		\'adi_position\' => \'Position\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_red\' => \'Red\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_tiny_mce_dir_path\' => \'TinyMCE directory path\',\n		\'adi_tiny_mce_hak\' => \'TinyMCE (hak_tinymce)\',\n		\'adi_tiny_mce_javascript\' =>\'TinyMCE (Javascript)\',\n		\'adi_tiny_mce_jquery\' => \'TinyMCE (jQuery)\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_width\' => \'Width\',\n		\'adi_yellow\' => \'Yellow\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_notes_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_notes\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_notes_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_notes_lifecycle\',\'plugin_lifecycle.adi_notes\');\n\n	// privilege levels\n	$adi_notes_priv_levels = array(\n		1 => gTxt(\'publisher\'),\n		2 => gTxt(\'managing_editor\'),\n		3 => gTxt(\'copy_editor\'),\n		4 => gTxt(\'staff_writer\'),\n		5 => gTxt(\'freelancer\'),\n		6 => gTxt(\'designer\'),\n	);\n\n	// \'note tab name\' (used in the database) => (\'title\' => nice title, \'where\' => parent tab, \'visible\' => installed visibility)\n	// - could probably take this back to old format: array(\'start\'=> 0, \'content\' => 1 ...)\n	// - as of v1.1, only Content tab note added to database on install (adi_notes_update_visibility() adds other tabs as & when)\n	$adi_notes_tab_list = array(\n		\'start\' =>			array(\'title\' => gTxt(\'tab_start\'), \'where\' => \'start\', \'visible\' => \'0\'),\n		\'content\' =>		array(\'title\' => gTxt(\'tab_content\'), \'where\' => \'content\', \'visible\' => \'1\'),\n		\'presentation\' =>	array(\'title\' => gTxt(\'tab_presentation\'), \'where\' => \'presentation\', \'visible\' => \'0\'),\n		\'admin\' =>			array(\'title\' => gTxt(\'tab_admin\'), \'where\' => \'admin\', \'visible\' => \'0\'),\n		);\n\n	// adi_notes Admin tab\n	add_privs(\"adi_notes_admin\"); // defaults to priv \'1\' only\n	register_tab(\"extensions\",\"adi_notes_admin\",adi_notes_gtxt(\'adi_notes_admin\')); // add new tab under \'Extensions\'\n	register_callback(\"adi_notes_admin\",\"adi_notes_admin\");\n\n	// adi_notes tabs & embedded notes\n	$installed = adi_notes_installed();\n	if ($installed) {\n		if (!adi_notes_upgrade()) {\n			$visibility = adi_notes_tab_visibility();\n			foreach ($adi_notes_tab_list as $tab_name => $tab_info) {\n				// register note tab (if marked as visible)\n				if ($visibility[$tab_name]) {\n					add_privs(\"adi_notes_tab_\".$tab_name,\'1,2,3,4,5,6\'); // all privs\n					register_callback(\"adi_notes_tab\",\"adi_notes_tab_\".$tab_name);\n					register_tab($tab_info[\'where\'],\"adi_notes_tab_\".$tab_name,adi_notes_gtxt(\'adi_notes\'));\n				}\n			}\n			// add embedded notes to all tabs\n			register_callback(\'adi_notes_embed_note\',\'admin_side\',\'footer\');\n			// jQuery fix for reliable header position (works for Classic/Remora/Hive in 4.5 & 4.6)\n			if (adi_notes_prefs(\'position\') == \'header\')\n				register_callback(\'adi_notes_header_script\',\'admin_side\',\'head_end\');\n			register_callback(\'adi_notes_embed_note_links\',\'admin_side\',\'footer\'); // add/edit links in footer\n			// switch on Home tab\n			if ($visibility[\'start\'])\n				add_privs(\'tab.start\',\'1,2,3,4,5,6\'); // all privs\n		}\n	}\n\n	// style\n	register_callback(\'adi_notes_style\',\'admin_side\',\'head_end\');\n\n	// admin page script\n	if ($event == \'adi_notes_admin\')\n		register_callback(\'adi_notes_admin_script\',\'admin_side\',\'head_end\');\n\n	// TinyMCE\n	if (adi_notes_prefs(\'tiny_mce\') != \'none\')\n		register_callback(\'adi_notes_tiny_mce_\'.adi_notes_prefs(\'tiny_mce\'),\'admin_side\',\'footer\');\n\n	// plugin options\n	$adi_notes_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_notes\',$adi_notes_debug);\n	if ($adi_notes_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_notes\'); // defaults to priv \'1\' only\n		register_callback(\'adi_notes_options\',\'plugin_prefs.adi_notes\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_notes_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_notes_options\',\'adi_notes options\');\n		register_callback(\'adi_notes_options\',\'adi_notes_options\');\n	}\n\n}\n\nfunction adi_notes_style() {\n// some style for the page\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step == \'edit\') || ($adi_notes_step == \'edit_user\'))\n		$nav_fix = t.t.t.\'#nav li ul { z-index:999 }\'.n; // so TXP remora dropdown overlays TinyMCE toolbar properly\n	else\n		$nav_fix = \'\';\n\n	// set up note style\n	$note_styles = array();\n	switch (adi_notes_prefs(\'style\')) {\n		case \'sticky\':\n			$note_styles[] = \'#adi_notes { margin:1em auto; padding:1em; border:1px solid; border-color:#e3d8c3 #c3b8a3 #c3b8a3 #e3d8c3; background-color:#ffffcc; color:#202020 }\';\n			break;\n		case \'red\':\n			$note_styles[] = \'#adi_notes { margin:1em auto; padding:1em; border:1px solid #b22222; background-color:#ffffff; color:#b22222 }\';\n			break;\n		case \'minimal\':\n			$note_styles[] = \'#adi_notes { margin:1em auto }\';\n			break;\n		case \'custom\':\n			$note_styles = explode(n,adi_notes_prefs(\'custom_style\'));\n			break;\n		default:\n			$note_styles[] = \'/* no style found */\';\n	}\n	if (adi_notes_prefs(\'style\') != \'custom\') {\n		$note_styles[] = \'#adi_notes p { margin:1em 0; }\';\n		$note_styles[] = \'#adi_notes p:first-child { margin-top:0 }\';\n		$note_styles[] = \'#adi_notes p:last-child { margin-bottom:0 }\';\n		$note_styles[] = \'#adi_notes.adi_notes_embed_headnote { margin-bottom:2em }\';\n	}\n\n	// collate rules\n	$note_style = \'/* adi_notes \'.adi_notes_prefs(\'style\').\' style */\'.n;\n	foreach ($note_styles as $style)\n		$note_style .= t.t.t.$style.n;\n\n	// generate <style>...</style>\n	echo\n		\'<style type=\"text/css\">\n			/* adi_notes - admin tab */\n			.adi_notes_form { width:60%; margin:0 auto 2em; padding:1em; text-align:center }\n			.adi_notes_form h2 { margin:1.5em 0 0 }\n			.adi_notes_form p { margin:0.8em 0 0 }\n			.adi_notes_form textarea { margin-top:0.8em }\n			.adi_notes_form .smallerbox { margin-top:3em }\n			.adi_notes_form label { margin-left:2em }\n			.adi_notes_form label:first-child { margin-left:0 }\n			.adi_notes_form p.adi_notes_width label, .adi_notes_form p.adi_notes_input_dir label { margin-left:0; margin-right:0.5em }\n			.adi_notes_form p.adi_notes_width input { width:18em }\n			/* adi_notes - note tabs & embedded notes */\n			#adi_notes { text-align:left }\n			#adi_notes .adi_notes_hr { width:100%; margin:1em auto; border:none; height:1px; color:#e0e0e0; background-color:#e0e0e0 }\n			#adi_notes .adi_notes_textarea { width:98%; height:25em; margin-top:0 }\n			#adi_notes .adi_notes_textarea_tab { height:50em }\n			#adi_notes .adi_notes_label { margin:0 0 0.5em 0; color:black }\n			#adi_notes .adi_notes_save { margin:0.5em 0 0 0; color:black }\n			#adi_notes .adi_notes_save input.publish { margin:0 1em 0 0 }\n			.txp-footer .adi_notes_embed_links { margin:1em 0; text-align:center }\n			/* adi_notes - note tabs */\n			.adi_notes_tab_link { margin:1em 0; text-align:center }\n			\'.$nav_fix\n			.$note_style\n			.(adi_notes_prefs(\'style\') == \'custom\' ? \'\' : t.t.t.\'/* adi_notes width */\'.n.t.t.t.\'#adi_notes { \'.adi_notes_prefs(\'width\').\' }\').n\n		.\'</style>\';\n}\n\nfunction adi_notes_admin_script() {\n// jQuery magic for admin tab\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	// adi_notes admin script\n\n	$().ready(function() {\n\n		// auto hide/show TinyMCE dir path input field\n		$(\'#adi_notes_tiny_mce_none,#adi_notes_tiny_mce_hak\').click(\n			function(){\n				$(\'p.adi_notes_input_dir\').hide();\n			}\n		);\n		$(\'#adi_notes_tiny_mce_jquery,#adi_notes_tiny_mce_javascript\').click(\n			function(){\n				$(\'p.adi_notes_input_dir\').show();\n			}\n		);\n\n		// auto hide/show Textile convert option input field\n		$(\'#adi_notes_tiny_mce_none\').click(\n			function(){\n				$(\'p.adi_notes_input_convert\').hide();\n			}\n		);\n		$(\'#adi_notes_tiny_mce_jquery,#adi_notes_tiny_mce_javascript,#adi_notes_tiny_mce_hak\').click(\n			function(){\n				$(\'p.adi_notes_input_convert\').show();\n			}\n		);\n\n		// auto hide/show custom style textarea\n		$(\'#adi_notes_style_custom\').click(\n			function(){\n				$(\'textarea[name=\"custom_style\"]\').show();\n				$(\'p.adi_notes_width\').hide();\n			}\n		);\n		$(\'#adi_notes_style_sticky,#adi_notes_style_red,#adi_notes_style_minimal\').click(\n			function(){\n				$(\'textarea[name=\"custom_style\"]\').hide();\n				$(\'p.adi_notes_width\').show();\n			}\n		);\n\n	});\n\n</script>\n\nEND_SCRIPT;\n	echo $script;\n}\n\nfunction adi_notes_header_script() {\n// header position fix for Hive theme\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	// adi_notes header script\n\n	$().ready(function() {\n		$(\"#adi_notes\").prependTo(\".txp-body\");\n	});\n\n</script>\n\nEND_SCRIPT;\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_jquery() {\n// TinyMCE implementation, using jQuery - TinyMCE options copied from hak_tinymce\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\" src=\"adi_notes_tiny_mce_dir_path/jquery.tinymce.js\"></script>\n\n<script type=\"text/javascript\">\n\n	$().ready(function() {\n\n		$(\'#adi_notes textarea\').tinymce({\n\n			// Location of TinyMCE script\n\n			script_url : \'adi_notes_tiny_mce_dir_path/tiny_mce.js\',\n\n			// General options\n\n			theme : \"advanced\",\n\n			language : \"en\",\n			relative_urls : false,\n			remove_script_host : false,\n			plugins : \"searchreplace\",\n			entity_encoding : \"numeric\",\n			// Theme options\n\n			theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n			theme_advanced_buttons2 : \"link,unlink,separator,image,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n			theme_advanced_buttons3 : \"\",\n			theme_advanced_toolbar_location : \"top\",\n			theme_advanced_toolbar_align : \"left\",\n\n		});\n\n\n	});\n\n</script>\n\nEND_SCRIPT;\n\n	$script = preg_replace(\'/adi_notes_tiny_mce_dir_path/\',adi_notes_prefs(\'tiny_mce_dir_path\'),$script);\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_hak() {\n// TinyMCE implementation, using hak_tinymce options & script installed in textpattern subdir\n	global $adi_notes_debug;\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = \'<script language=\"javascript\" type=\"text/javascript\" src=\"tiny_mce/tiny_mce.js\"></script>\';\n	$script .= \'<script type=\"text/javascript\">\';\n	$script .= \'tinyMCE.init({\';\n	$script .= \'mode : \"textareas\",\';\n	$script .= fetch(\'pref_value\',\'txp_hak_tinymce\',\'pref_name\',\'body_init\',$adi_notes_debug);\n	$script .= \'editor_selector : \"adi_notes_mceEditor\",\';\n	$script .= \'});\';\n	$script .= \'</script>\';\n	echo $script;\n}\n\nfunction adi_notes_tiny_mce_javascript() {\n// TinyMCE implementation, using Javascript\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	if (($adi_notes_step != \'edit\') && ($adi_notes_step != \'edit_user\')) return;\n\n	$script = <<<END_SCRIPT\n<script type=\"text/javascript\" src=\"adi_notes_tiny_mce_dir_path/tiny_mce.js\"></script>\n\n<script type=\"text/javascript\">\n\n	tinyMCE.init({\n\n			// General options\n			mode : \"textareas\",\n\n			theme : \"advanced\",\n\n			language : \"en\",\n			relative_urls : false,\n			remove_script_host : false,\n			plugins : \"searchreplace\",\n			entity_encoding : \"numeric\",\n			editor_selector : \"adi_notes_mceEditor\",\n\n			// Theme options\n\n			theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\n			theme_advanced_buttons2 : \"link,unlink,separator,image,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\n			theme_advanced_buttons3 : \"\",\n			theme_advanced_toolbar_location : \"top\",\n			theme_advanced_toolbar_align : \"left\",\n\n	});\n\n</script>\nEND_SCRIPT;\n\n	$script = preg_replace(\'/adi_notes_tiny_mce_dir_path/\',adi_notes_prefs(\'tiny_mce_dir_path\'),$script);\n	echo $script;\n}\n\nfunction adi_notes_has_privs($res, $user=\'\') {\n// like txplib_misc.php has_privs() but accepts a comma separated list of privs rather than a standard named combo\n	global $adi_notes_login_user,$adi_notes_debug;\n\n	if ($res) {\n		if (empty($user)) // no user supplied, so use the current login name\n			$user = $adi_notes_login_user;\n		$user_privs = safe_field(\"privs\", \"txp_users\", \"name=\'\".doSlash($user).\"\'\");\n		if ($adi_notes_debug)\n			echo \'PRIVS: required=\'.$res.\' actual=\'.$user_privs.\' (\'.$user.\')\';\n		return in_array($user_privs,explode(\',\',$res));\n	}\n	else\n		return false;\n}\n\nfunction adi_notes_admin($event, $step) {\n// the admin tab\n	global $prefs,$adi_notes_tab_list,$adi_notes_debug,$adi_notes_plugin_status,$adi_notes_priv_levels,$adi_notes_prefs;\n\n	$message = \'\';\n	$installed = adi_notes_installed();\n	$submit = gps(\'adi_notes_admin_submit\'); // check that someone\'s hit the tit\n\n	// process the information\n	if ($installed) {\n		$upgrade_required = adi_notes_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_notes_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else if ($submit) {\n			if ($step == \'update\') {\n\n				// massage submitted note_tab_edit_privs\n				$submitted_note_tab_edit_privs = ps(\"note_tab_edit_privs\");\n				if (empty($submitted_note_tab_edit_privs)) // revert to default privs\n					foreach (explode(\',\',$adi_notes_prefs[\'note_tab_edit_privs\']) as $priv)\n						$submitted_note_tab_edit_privs[$priv] = 1;\n				$submitted_note_tab_edit_privs[1] = TRUE; // make sure publisher priv switched on\n				$note_tab_edit_privs = implode(\',\',array_keys($submitted_note_tab_edit_privs));\n\n				// txp_tag_privs\n				$submitted_txp_tag_privs = ps(\"txp_tag_privs\");\n				if (empty($submitted_txp_tag_privs))\n					$submitted_txp_tag_privs = array();\n				$txp_tag_privs = implode(\',\',array_keys($submitted_txp_tag_privs));\n\n				// do update\n				$update_ok = adi_notes_update_visibility(ps(\"visibility\"))\n								&& adi_notes_prefs(\'note_tab_edit_privs\',$note_tab_edit_privs)\n								&& adi_notes_prefs(\'public_note_edit_privs\',$note_tab_edit_privs) // currently same as note tab edit privs\n								&& adi_notes_prefs(\'txp_tag_privs\',$txp_tag_privs)\n								&& adi_notes_prefs(\'style\',strip_tags(ps(\"style\")))\n								&& adi_notes_prefs(\'custom_style\',strip_tags(ps(\"custom_style\")))\n								&& adi_notes_prefs(\'position\',strip_tags(ps(\"position\")))\n								&& adi_notes_prefs(\'width\',strip_tags(ps(\"width\")))\n								&& adi_notes_prefs(\'tiny_mce\',strip_tags(ps(\"tiny_mce\")))\n								&& adi_notes_prefs(\'tiny_mce_dir_path\',strip_tags(ps(\"tiny_mce_dir_path\")))\n								&& adi_notes_prefs(\'convert_link\',strip_tags(ps(\"convert_link\")));\n				if (adi_notes_prefs(\'tiny_mce\') == \'none\')\n					$update_ok &= adi_notes_prefs(\'markup\',\'textile\');\n				else\n					$update_ok &= adi_notes_prefs(\'markup\',\'html\');\n				if ($update_ok)\n					$message = adi_notes_gtxt(\'preferences_saved\');\n				else\n					$message = array(adi_notes_gtxt(\'adi_pref_update_fail\'),E_ERROR);\n			}\n		}\n	}\n	else\n		$message = array(adi_notes_gtxt(\'adi_not_installed\'),E_ERROR);\n\n	// generate page\n	pagetop(adi_notes_gtxt(\'adi_notes\').\' \'.gTxt(\'admin\'),$message);\n\n	if ($installed && !$upgrade_required) {\n		// visibility checkboxes\n		$visibility = adi_notes_tab_visibility();\n		$visibility_checkboxes = \'\';\n		foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n			$visibility_checkboxes .= tag(checkbox(\"visibility[$tab_name]\",\'1\',$visibility[$tab_name]).$tab_info[\'title\'],\'label\');\n\n		// note tab edit priv checkboxes\n		$note_tab_edit_priv_checkboxes = \'\';\n		$current_privs = explode(\',\',adi_notes_prefs(\'note_tab_edit_privs\'));\n		foreach ($adi_notes_priv_levels as $priv => $priv_name)\n			$note_tab_edit_priv_checkboxes .= tag(checkbox(\"note_tab_edit_privs[$priv]\",\'1\',in_array($priv,$current_privs)).$priv_name,\'label\');\n\n		// txp tag priv checkboxes\n		$txp_tag_priv_checkboxes = \'\';\n		$current_privs = explode(\',\',adi_notes_prefs(\'txp_tag_privs\'));\n		foreach ($adi_notes_priv_levels as $priv => $priv_name)\n			$txp_tag_priv_checkboxes .= tag(checkbox(\"txp_tag_privs[\".$priv.\"]\", \"1\", in_array($priv,$current_privs)).$priv_name,\'label\');\n\n		// width field peekaboo\n		if (adi_notes_prefs(\'style\') == \'custom\') {\n			$hide_custom = \'\';\n			$hide_width = \' style=\"display:none\"\';\n		}\n		else {\n			$hide_width = \'\';\n			$hide_custom = \'display:none\';\n		}\n		// TinyMCE peepo\n		if ((adi_notes_prefs(\'tiny_mce\') == \'none\') || (adi_notes_prefs(\'tiny_mce\') == \'hak\'))\n			$hide_dir = \' style=\"display:none\"\';\n		else\n			$hide_dir = \'\';\n		if (adi_notes_prefs(\'tiny_mce\') == \'none\')\n			$hide_convert = \' style=\"display:none\"\';\n		else\n			$hide_convert = \'\';\n\n		// position\n		$position =\n			tag(adi_notes_gtxt(\'adi_position\'),\"h2\")\n			.graf(\n				tag(radio(\'position\',\'header\',(adi_notes_prefs(\'position\') == \'header\')).adi_notes_gtxt(\'adi_header\'),\'label\')\n				.tag(radio(\'position\',\'footer\',(adi_notes_prefs(\'position\') == \'footer\')).adi_notes_gtxt(\'adi_footer\'),\'label\')\n			);\n\n		$hak_tinymce_installed = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'hak_tinymce\'\",$adi_notes_debug);\n\n	    echo form(\n			tag(adi_notes_gtxt(\'adi_notes_admin\'),\'h1\')\n			// visibility\n			.tag(adi_notes_gtxt(\'adi_note_tabs\'),\'h2\')\n			.graf($visibility_checkboxes)\n			// note tab/public note edit privs\n			.tag(adi_notes_gtxt(\'adi_note_public_edit_privs\'),\"h2\")\n			.graf($note_tab_edit_priv_checkboxes)\n			// txp tag privs\n			.tag(adi_notes_gtxt(\'adi_note_txp_tag_privs\'),\"h2\")\n			.graf($txp_tag_priv_checkboxes)\n			// position\n			.$position\n			// style\n			.tag(gTxt(\'style\'),\"h2\")\n			.graf(\n				tag(radio(\'style\',\'sticky\',(adi_notes_prefs(\'style\') == \'sticky\'),\'adi_notes_style_sticky\').adi_notes_gtxt(\'adi_yellow\'),\'label\')\n				.tag(radio(\'style\',\'red\',(adi_notes_prefs(\'style\') == \'red\'),\'adi_notes_style_red\').adi_notes_gtxt(\'adi_red\'),\'label\')\n				.tag(radio(\'style\',\'minimal\',(adi_notes_prefs(\'style\') == \'minimal\'),\'adi_notes_style_minimal\').adi_notes_gtxt(\'adi_minimal\'),\'label\')\n				.tag(radio(\'style\',\'custom\',(adi_notes_prefs(\'style\') == \'custom\'),\'adi_notes_style_custom\').adi_notes_gtxt(\'adi_custom\'),\'label\')\n			)\n			// width\n			.graf(\n				tag(adi_notes_gtxt(\'adi_width\'),\'label\')\n				.finput(\"text\",\'width\',adi_notes_prefs(\'width\'))\n				,\' class=\"adi_notes_width\"\'.$hide_width\n			)\n			// custom style\n			.adi_notes_textarea(\'custom_style\',adi_notes_prefs(\'custom_style\'),\'adi_notes_style_custom_style\',5,40,$hide_custom)\n			// TinyMCE\n			.tag(adi_notes_gtxt(\'adi_note_markup\'),\"h2\")\n			.graf(\n				tag(radio(\'tiny_mce\',\'none\',(adi_notes_prefs(\'tiny_mce\') == \'none\'),\'adi_notes_tiny_mce_none\').gTxt(\'txptextile\'),\'label\')\n				.($hak_tinymce_installed ? tag(radio(\'tiny_mce\',\'hak\',(adi_notes_prefs(\'tiny_mce\') == \'hak\'),\'adi_notes_tiny_mce_hak\').adi_notes_gtxt(\'adi_tiny_mce_hak\'),\'label\') : \'\')\n				.tag(radio(\'tiny_mce\',\'jquery\',(adi_notes_prefs(\'tiny_mce\') == \'jquery\'),\'adi_notes_tiny_mce_jquery\').adi_notes_gtxt(\'adi_tiny_mce_jquery\'),\'label\')\n				.tag(radio(\'tiny_mce\',\'javascript\',(adi_notes_prefs(\'tiny_mce\') == \'javascript\'),\'adi_notes_tiny_mce_javascript\').adi_notes_gtxt(\'adi_tiny_mce_javascript\'),\'label\')\n			)\n			.graf(\n				tag(adi_notes_gtxt(\'adi_tiny_mce_dir_path\'),\'label\').finput(\"text\",\'tiny_mce_dir_path\',adi_notes_prefs(\'tiny_mce_dir_path\'))\n				,\' class=\"adi_notes_input_dir\"\'.$hide_dir\n			)\n			.graf(adi_notes_gtxt(\'adi_display_convert_option\').checkbox(\'convert_link\',\'1\',adi_notes_prefs(\'convert_link\'))\n				,\' class=\"adi_notes_input_convert\"\'.$hide_convert\n			)\n	        .fInput(\'submit\',\'adi_notes_admin_submit\',adi_notes_gtxt(\'adi_update_prefs\'),\"smallerbox\",\"\",\'\').\n	        eInput(\"adi_notes_admin\").sInput(\"update\")\n			,\'\',\'\',\'post\',\'adi_notes_form\'\n		);\n	}\n}\n\nfunction adi_notes_textarea($name, $thing = \'\', $id = \'\', $rows=\'5\', $cols=\'40\', $inline_style) {\n// based on text_area() but with added inline style & without height & width\n\n	$id = ($id) ? \' id=\"\'.$id.\'\"\' : \'\';\n	$rows = \' rows=\"\' . ( ($rows && is_numeric($rows)) ? $rows : \'5\') . \'\"\';\n	$cols = \' cols=\"\' . ( ($cols && is_numeric($cols)) ? $cols : \'40\') . \'\"\';\n	$style = ($inline_style) ? \' style=\"\'.$inline_style.\'\"\' : \'\';\n	return \'<textarea\'.$id.\' name=\"\'.$name.\'\"\'.$rows.$cols.$style.\'>\'.txpspecialchars($thing).\'</textarea>\';\n}\n\nfunction adi_notes_options($event,$step) {\n// display adi_notes options: install/uninstall/upgrade\n	global $adi_notes_debug,$adi_notes_url,$adi_notes_plugin_status,$adi_notes_txp460;\n\n	$message = \'\';\n	$installed = adi_notes_installed();\n\n	$action = gps(\'action\'); // \"under the counter\" action\n\n	$adi_notes_txp460 ? $e = \'adi_notes_admin\' : $e = \'adi_notes_ad\'; // transitional fix for event field length (prior to 4.6 = 12) & strict MySQL implementations\n\n	if ($installed) {\n		$upgrade_required = adi_notes_upgrade();\n		if ($upgrade_required) { // upgrade\n			$res = adi_notes_upgrade(TRUE); // copy notes old -> new\n			if ($res)\n				$message = adi_notes_gtxt(\'adi_upgraded\');\n			else\n				$message = array(adi_notes_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n		}\n		// $stepping out\n		if ($step == \"uninstall\") { // uninstall adi_notes\n			$res = adi_notes_drop(\'adi_notes\');\n			$res = $res && adi_notes_delete_prefs();\n			if ($res)\n	    		$message = adi_notes_gtxt(\'adi_uninstalled\');\n			else\n	    		$message = array(adi_notes_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n		}\n		else if ($step == \'textpack\') {\n			$adi_textpack = file_get_contents($adi_notes_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_notes_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n		// $action man\n		else if ($action == \"downgrade\") { // revert\n			if (adi_notes_installed(\'adi_notes_old\')) {\n				$res = adi_notes_drop(\'adi_notes\');\n				$res = $res && adi_notes_rename(\'adi_notes_old\',\'adi_notes\'); // restore from \'adi_notes_old\'\n				$res = $res && safe_delete(\'txp_prefs\',\"name = \'adi_notes_txp_tag_privs\'\",$adi_notes_debug); // delete pref introduced in 1.3\n				// restore old custom style\n				$res = $res && set_pref(\'adi_notes_custom_style\',adi_notes_prefs(\'custom_style_old\'),$e,2);\n				// restore old width style\n				$res = $res && set_pref(\'adi_notes_width\',adi_notes_prefs(\'width_old\'),$e,2);\n				if ($res)\n					$message = \"downgraded\";\n				else\n					$message = array(\"downgrade failed\",E_ERROR);\n			}\n			else\n				$message = array(\"downgrade failed, no backup found\",E_ERROR);\n		}\n		else if ($action == \"backup\") { // create backup in \'adi_notes_backup\'\n			$res = TRUE;\n			if (adi_notes_installed(\'adi_notes_backup\'))\n				$res = adi_notes_drop(\'adi_notes_backup\');\n			$res = $res && adi_notes_copy(\'adi_notes\',\'adi_notes_backup\');\n			if ($res)\n				$message = \"backed up\";\n			else\n				$message = array(\"backup failed\",E_ERROR);\n		}\n		else if ($action == \"restore\") { // restore from \'adi_notes_backup\'\n			if (adi_notes_installed(\'adi_notes_backup\')) {\n				$res = adi_notes_drop(\'adi_notes\');\n				$res = $res && adi_notes_copy(\'adi_notes_backup\',\'adi_notes\');\n				if ($res)\n					$message = \"restored\";\n				else\n					$message = array(\"restore failed\",E_ERROR);\n			}\n			else\n				$message = array(\"restore failed, no backup found\",E_ERROR);\n		}\n		else if ($action == \"cleanup\") { // delete upgrade backup\n			$res = TRUE;\n			if (adi_notes_installed(\'adi_notes_old\'))\n				$res = adi_notes_drop(\'adi_notes_old\');\n			if ($res) {\n				$message = \"upgrade backup deleted\";\n			}\n			else\n				$message = array(\"unable to delete upgrade backup\",E_ERROR);\n		}\n	}\n	else { // not installed\n		if ($step == \"install\") { // install adi_notes\n			$res = adi_notes_install();\n			if ($res)\n				$message = adi_notes_gtxt(\'adi_installed\');\n			else\n				$message = array(adi_notes_gtxt(\'adi_install_fail\'),E_ERROR);\n		}\n		else\n			$message = array(adi_notes_gtxt(\'adi_not_installed\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_notes - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_notes_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n			,\'\',\'\',\'post\',\'adi_notes_nstall_button\'\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", adi_notes_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n			,\'\',\'\',\'post\',\'adi_notes_nstall_button adi_notes_uninstall_button\'\n		);\n\n	if ($adi_notes_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_notes_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_notes \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_notes_gtxt(\'adi_textpack_online\'),$adi_notes_url[\'textpack_download\']))\n			.graf(href(adi_notes_gtxt(\'adi_textpack_feedback\'),$adi_notes_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n\nfunction adi_notes_prefs($name,$new_value=\'!return_value_only!\') {\n// set/read preferences\n	global $prefs,$adi_notes_prefs,$adi_notes_txp460,$adi_notes_debug;\n\n	if (empty($new_value)) // reset to default\n		$new_value = $adi_notes_prefs[$name];\n\n	// workaround for set pref escaping issue (fixed in 4.5.6)\n	if (!version_compare(txp_version,\'4.5.5\',\'>\'))\n		$new_value = doSlash($new_value);\n\n	// read current value (either from database or $adi_notes_prefs)\n	isset($prefs[\'adi_notes_\'.$name]) ? $value = $prefs[\'adi_notes_\'.$name] : $value = $adi_notes_prefs[$name];\n\n	if ($adi_notes_debug) echo \"adi_notes_prefs: name=$name, value=$value, new_value=$new_value\".br;\n\n	$adi_notes_txp460 ? $e = \'adi_notes_admin\' : $e = \'adi_notes_ad\'; // transitional fix for event field length (prior to 4.6 = 12) & strict MySQL implementations\n\n	if ($new_value == \'!toggle!\') { // toggle boolean\n		$value ? $value = 0 : $value = 1; // toggle debug mode\n		set_pref(\'adi_notes_\'.$name,$value,$e,2);\n		$prefs = get_prefs(); // re-sample $prefs\n	}\n	else if ($new_value == \'!return_value_only!\') // just return value\n		return $value;\n	else { // update pref\n		if ($adi_notes_debug) echo \"adi_notes_prefs: update pref ...\";\n		$res = set_pref(\'adi_notes_\'.$name,$new_value,$e,2);\n		$prefs = get_prefs(); // re-sample $prefs\n		if ($adi_notes_debug) echo \" res=$res\".br;\n		return $res;\n	}\n}\n\nfunction adi_notes_installed($table=\'adi_notes\') {\n// test if supplied table is present\n	global $adi_notes_debug;\n\n	$found = FALSE;\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\",$adi_notes_debug);\n	$a = nextRow($rs);\n	if ($a)\n		$found = TRUE;\n	return $found;\n}\n\nfunction adi_notes_upgrade($do_upgrade=FALSE) {\n// check format of adi_notes table / copy from previous version backup table if required\n	global $adi_notes_debug,$prefs;\n\n	if (adi_notes_installed()) { // find out what needs upgrading\n\n		// pre-v1.0 - check for presence of \'id\' column\n		$rs = safe_query(\"SHOW COLUMNS FROM \".safe_pfx(\'adi_notes\').\" LIKE \'id\'\",$adi_notes_debug); // \"SHOW COLUMNS WHERE\" no good for MySQL version 4.1\n		$a = nextRow($rs);\n		$pre_v1_0 = empty($a);\n\n		// v1.0 - look for non-event names - \'comment\',\'user\',\'variables\',\'adi_menu\',\'adi_prefs\' (should be \'discuss\',\'admin\',\'adi_variables_admin\',\'adi_menu_admin\',\'adi_prefs_admin\')\n		$oldies = \"\'comment\',\'user\',\'variables\',\'adi_menu\',\'adi_prefs\'\";\n		$newbies = \"\'discuss\',\'admin\',\'adi_variables_admin\',\'adi_menu_admin\',\'adi_prefs_admin\'\";\n		$v1_0 = safe_rows(\'*\',\'adi_notes\',\"name IN ($oldies)\",$adi_notes_debug); // read data from current table\n\n		// v1.3 - look for txp_tag_privs preference\n		$v1_3 = !isset($prefs[\'adi_notes_txp_tag_privs\']);\n\n		$upgrade_required = $pre_v1_0 || $v1_0 || $v1_3;\n\n		if ($adi_notes_debug) echo \"pre_v1_0=$pre_v1_0, v1_0=$v1_0, v1_3=$v1_3, upgrade_required=$upgrade_required\".br;\n\n		if ($do_upgrade) {\n			if ($upgrade_required) {\n				if ($pre_v1_0)\n					$backup_version = \'pre_v1_0\';\n				else\n					$backup_version = \'post_v1_0\';\n				if ($adi_notes_debug) echo \'Upgrading from \'.$backup_version.br;\n				$res = TRUE; // optimistic\n				$rs = safe_rows(\'*\',\'adi_notes\',\"1=1\",$adi_notes_debug); // read data from current table\n				// take backup\n				if (adi_notes_installed(\'adi_notes_old\')) // delete previous \"upgrade\" backup\n					$res = adi_notes_drop(\'adi_notes_old\');\n				$res = $res && adi_notes_copy(\'adi_notes\',\'adi_notes_old\',$backup_version);	// take an \"upgrade\" backup\n				// do upgrade stuff\n				if ($pre_v1_0) {\n					// change table structure\n					$res = $res && adi_notes_drop(\'adi_notes\'); // delete previous version table\n					$res = $res && adi_notes_install(FALSE); // create vanilla new format table\n					foreach ($rs as $name => $row) { // copy note data to new table\n						extract($row);\n						if ($adi_notes_debug) dmp($row);\n						$res = $res && safe_query(\n							\"INSERT INTO \".\n							safe_pfx(\'adi_notes\').\n							\" VALUES (DEFAULT,\'\".$name.\"\',\'tab\',DEFAULT,DEFAULT,\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_ID.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n							);\n					}\n				}\n				if ($v1_0) {\n					if ($adi_notes_debug) echo \'v1_0 upgrade:\'.br;\n					// rename non-event names\n					$old_list = explode(\',\',$oldies);\n					$new_list = explode(\',\',$newbies);\n					foreach ($old_list as $index => $name) {\n						$rs = safe_row(\'*\',\'adi_notes\',\"name=$name\",$adi_notes_debug);\n						if ($rs)\n							$res = $res && safe_update(\'adi_notes\',\"name=$new_list[$index]\",\"name=$name\",$adi_notes_debug);\n					}\n					// switch embedded note visibility on (for consistency & possible future use)\n					$res = $res && safe_update(\'adi_notes\',\"visible=1\",\"type=\'embed\'\",$adi_notes_debug);\n				}\n				if ($v1_3) {\n					if ($adi_notes_debug) echo \'v1_3 upgrade:\'.br;\n					// add TXP tag privs preference\n					$res = $res && adi_notes_prefs(\'txp_tag_privs\',\'\');\n					// make copy of old custom style\n					(isset($prefs[\'adi_notes_custom_style\'])) ? $old_custom_style = adi_notes_prefs(\'custom_style\') : $old_custom_style = \'\';\n					$res = $res && adi_notes_prefs(\'custom_style_old\',$old_custom_style);\n					// make copy of old width style\n					(isset($prefs[\'adi_notes_width\'])) ? $old_width = adi_notes_prefs(\'width\') : $old_width = \'\';\n					$res = $res && adi_notes_prefs(\'width_old\',$old_width);\n					// massage v1.1 width pref\n					if ((strpos($old_width,\'width:\') === FALSE) && $old_width) // v1.1 width pref didn\'t have \"width:\"\n						$res = $res && adi_notes_prefs(\'width\',\'width:\'.$old_width);\n					// wrap old custom style in #adi_notes selector\n					(isset($prefs[\'adi_notes_custom_style\'])) ? $new_custom_style = \'#adi_notes { \'.adi_notes_prefs(\'custom_style\').\' }\' : $new_custom_style = adi_notes_prefs(\'custom_style\');\n					// massage v1.1 width pref\n					if ((strpos($old_width,\'width:\') === FALSE) && $old_width) // v1.1 width pref didn\'t have \"width:\"\n						$old_width = \'width:\'.$old_width;\n					if (empty($old_width)) // use new default\n						$old_width = adi_notes_prefs(\'width\');\n					// add old width onto end of custom style\n					$new_custom_style .= n.\'#adi_notes { \'.$old_width.\' }\';\n					$res = $res && adi_notes_prefs(\'custom_style\',$new_custom_style);\n				}\n				return $res;\n			}\n			else\n				return FALSE;\n		}\n		else\n			return $upgrade_required;\n	}\n	else\n		return FALSE;\n}\n\nfunction adi_notes_copy($source,$destination,$backup_version=\'\') {\n// copy table: source -> destination\n	global $adi_notes_debug;\n\n	$res = safe_query(\"CREATE TABLE \".safe_pfx($destination).\" LIKE \".safe_pfx($source).\";\",$adi_notes_debug);\n	// can\'t use following because single quotes etc. in $note need to be escaped\n	// $res = $res && safe_query(\"INSERT \".safe_pfx($destination).\" SELECT * FROM \".safe_pfx($source).\";\",$adi_notes_debug);\n	$rs = safe_rows(\'*\',$source,\"1=1\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		if ($backup_version == \'pre_v1_0\')\n			$res = $res && safe_query(\n						\"INSERT INTO \".\n						safe_pfx($destination).\n						\" VALUES (\'\".$name.\"\',\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_ID.\"\',\'\".$restricted_privs.\"\',\'\".$restricted_ID.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n						);\n		else\n			$res = $res && safe_query(\n						\"INSERT INTO \".\n						safe_pfx($destination).\n						\" VALUES (DEFAULT,\'\".$name.\"\',\'\".$type.\"\',\'\".$user.\"\',\'\".$privs.\"\',\'\".$visible.\"\',\'\".$last_mod.\"\',\'\".$last_mod_user.\"\',\'\".doSlash($note).\"\')\",$adi_notes_debug\n						);\n	}\n	return $res;\n}\n\nfunction adi_notes_rename($source,$destination) {\n// rename table: source -> destination\n	global $adi_notes_debug;\n\n	return safe_query(\"RENAME TABLE \".safe_pfx($source).\" TO \".safe_pfx($destination),$adi_notes_debug);\n}\n\nfunction adi_notes_drop($db_table) {\n// delete a table\n	global $adi_notes_debug;\n\n	return safe_query(\"DROP TABLE \".safe_pfx($db_table).\";\",$adi_notes_debug);\n}\n\nfunction adi_notes_install($vanilla=TRUE) {\n// create adi_notes table & insert note tabs that are visible by default (i.e. content) - adi_notes_tab_visibility will insert the others\n	global $adi_notes_tab_list,$adi_notes_debug;\n\n	// database table\n	$res = safe_query( // maintain quirk in CREATE TABLE - type maybe should\'ve defaulted to \'tab\'\n		\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_notes\').\" (\n		`id` INT(11) NOT NULL AUTO_INCREMENT,\n		`name` VARCHAR(64) NOT NULL DEFAULT \'\',\n		`type` VARCHAR(8) NOT NULL DEFAULT \'\',\n		`user` VARCHAR(64) NULL DEFAULT \'\',\n		`privs` VARCHAR(22) NOT NULL DEFAULT \'1,2,3,4,5,6\',\n		`visible` TINYINT(1) NOT NULL DEFAULT \'0\',\n		`last_mod` DATETIME NOT NULL DEFAULT \'0000-00-00 00:00:00\',\n		`last_mod_user` VARCHAR(64) NOT NULL DEFAULT \'\',\n		`note` TEXT NOT NULL,\n		PRIMARY KEY(id)\n		);\"\n		,$adi_notes_debug);\n\n	if ($vanilla) // populate table with empty note tab row(s)\n		foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n			if ($tab_info[\'visible\'])\n				$res = $res &&\n					safe_query(\n						\"INSERT INTO \".\n						safe_pfx(\'adi_notes\').\n// 						\" VALUES (DEFAULT,\'\".$tab_name.\"\',\'tab\',DEFAULT,DEFAULT,1,DEFAULT,DEFAULT,DEFAULT)\" // pre-1.3.1\n						\" VALUES (DEFAULT,\'\".$tab_name.\"\',\'tab\',DEFAULT,DEFAULT,1,DEFAULT,DEFAULT,\'\')\" // uses explicit value for empty note\n						,$adi_notes_debug);\n	return $res;\n}\n\nfunction adi_notes_delete_prefs() {\n// leave no trace\n	global $adi_notes_prefs,$adi_notes_debug;\n\n	$res = TRUE;\n	foreach ($adi_notes_prefs as $name => $value)\n		if (safe_row(\"*\",\'txp_prefs\',\"name = \'adi_notes_$name\'\",$adi_notes_debug))\n			$res = $res && safe_delete(\'txp_prefs\',\"name = \'adi_notes_$name\'\",$adi_notes_debug);\n	return $res;\n}\n\nfunction adi_notes_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_notes_debug;\n\n	$result = \'?\';\n	// set upgrade flag if reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_notes_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_notes_install();\n	else if ($step == \'deleted\') {\n		$result = adi_notes_drop(\'adi_notes\');\n		$result = $result && adi_notes_delete_prefs();\n	}\n	if ($upgrade)\n		$result = $result && adi_notes_upgrade(TRUE);\n	if ($adi_notes_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_notes_tab_visibility() {\n// create visibility list array of note tabs from database\n	global $adi_notes_tab_list,$adi_notes_debug;\n\n	$visibility = array();\n	// get current note tab visibility from database (BEWARE user tab event = \"admin\")\n	$rs = safe_rows(\'*\',\'adi_notes\',\"type=\'tab\'\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		if (array_key_exists($name,$adi_notes_tab_list))\n			$visibility[$name] = $visible; // create array, indexed by name, of note tab visibility found in database\n	}\n	// compare $adi_notes_tab_list with $visibility & add any missing tabs to array\n	foreach ($adi_notes_tab_list as $tab_name => $tab_info)\n		if (!array_key_exists($tab_name,$visibility))\n			$visibility[$tab_name] = 0;\n	return $visibility;\n}\n\nfunction adi_notes_update_visibility($visibility) {\n// update database with new note tab visibility info (adding any newly visible note tabs to the mix at the same time)\n	global $adi_notes_debug;\n\n	$res = TRUE;\n	// switch all note tab visibility off (BEWARE user tab event = \"admin\")\n	$rs = safe_rows(\'*\',\'adi_notes\',\"type=\'tab\'\",$adi_notes_debug);\n	foreach ($rs as $index => $row) {\n		extract($row);\n		$res = $res && safe_update(\'adi_notes\',\"visible=0\",\"name=\'$name\' AND type = \'tab\'\",$adi_notes_debug);\n	}\n	if ($visibility) {\n		// selectively switch visibility back on (and insert row if missing)\n		foreach ($visibility as $tab_name => $visible) {\n			$found = safe_row(\'name\',\'adi_notes\',\"name=\'$tab_name\' AND type=\'tab\'\",$adi_notes_debug);\n			if ($found)\n				$res = $res && safe_update(\'adi_notes\',\"visible=$visible\",\"name=\'$tab_name\' AND type=\'tab\'\",$adi_notes_debug);\n			else\n// 				$res = $res && safe_insert(\'adi_notes\',\"name=\'$tab_name\', visible=$visible, type=\'tab\'\",$adi_notes_debug); // (pre-1.3.1) set type = \'tab\' coz it\'s not the default (historical quirk in CREATE TABLE)\n				$res = $res && safe_insert(\'adi_notes\',\"name=\'$tab_name\', visible=$visible, type=\'tab\', note=\'\'\",$adi_notes_debug); // set type = \'tab\' coz it\'s not the default (historical quirk in CREATE TABLE)\n		}\n	}\n	return $res;\n}\n\nfunction adi_notes_read($note_name,$type,$note_id,$user) {\n// read given note from database\n	global $adi_notes_debug;\n\n	$this_note = array();\n	if (empty($note_id)) { // selection of note based on context\n		$where = \"name=\'\".$note_name.\"\' AND type=\'\".$type.\"\'\";\n		$user ?\n			$where .= \" AND user=\'\".$user.\"\'\" :\n			$where .= \" AND user=\'\'\";\n		$this_note = safe_row(\"*\",\'adi_notes\',$where,$adi_notes_debug);\n	}\n	else // specific note id supplied\n		$this_note = safe_row(\"*\",\'adi_notes\',\"id=\'\".$note_id.\"\'\",$adi_notes_debug);\n	if ($this_note) {\n		if (adi_notes_prefs(\'markup\') == \'textile\') {\n			$textile = new Textile;\n			$this_note[\'formatted\'] = $textile->TextileThis($this_note[\'note\']);\n		}\n		else\n			$this_note[\'formatted\'] = $this_note[\'note\'];\n	}\n	return $this_note;\n}\n\nfunction adi_notes_tab($event,$step) {\n// display note tab\n	global $pretext,$adi_notes_debug,$adi_notes_tab_list,$adi_notes_prefs,$adi_notes_txp460;\n\n	include_once txpath.\'/publish.php\';\n\n	$message = \'\';\n\n	// style\n	adi_notes_prefs(\'style\') ?\n		$class = \' class=\"adi_notes_\'.adi_notes_prefs(\'style\').\'\"\' :\n		$class = \'\';\n\n	$note_name = substr($event,strlen(\"adi_notes_tab_\")); // strip \"adi_notes_tab_\" from event name\n	if (adi_notes_upgrade()) // upgrade required\n		pagetop($adi_notes_tab_list[$note_name][\'title\'].\' - \'.adi_notes_gtxt(\'adi_notes\'),\'adi_notes: <strong>\'.adi_notes_gtxt(\'adi_upgrade_required\').\'</strong>\');\n	else {\n		$adi_notes_step = gps(\'adi_notes_step\');\n		$adi_notes_id = gps(\'adi_notes_id\');\n		$adi_notes_user = gps(\'adi_notes_user\');\n		$adi_notes_note = gps(\'adi_notes_note\');\n		$adi_notes_convert = gps(\'adi_notes_convert\');\n\n		if ($adi_notes_debug)\n			print \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$adi_notes_id, adi_notes_user=$adi_notes_user</p>\";\n\n		// one $step at a time\n		if ($adi_notes_step == \'save\') {\n			$res = adi_notes_save($note_name,\'tab\',$adi_notes_id,\'\',$adi_notes_note);\n			if ($adi_notes_note)\n				$res ? $message = adi_notes_gtxt(\'adi_note_saved\') : $message = adi_notes_gtxt(\'adi_note_save_fail\');\n			else\n				$res ? $message = adi_notes_gtxt(\'adi_note_deleted\') : $message = adi_notes_gtxt(\'adi_note_delete_fail\');\n		}\n\n		// decide what to display\n		if ($adi_notes_step == \'edit\')\n			$thing = adi_notes_edit_form($note_name,\'tab\',$adi_notes_id,\'\',$adi_notes_convert);\n		else {\n			$this_note = adi_notes_read($note_name,\'tab\',$adi_notes_id,\'\');\n			$thing = $this_note[\'formatted\'];\n			if ($this_note[\'note\'])\n				$linktext = gTxt(\'edit\');\n			else\n				$linktext = adi_notes_gtxt(\'adi_add_note\');\n			$thing = parse($thing);\n		}\n\n		// output page\n		pagetop($adi_notes_tab_list[$note_name][\'title\'].\' - \'.adi_notes_gtxt(\'adi_notes\'),$message);\n		if ($thing)\n			echo tag($thing,\'div\',\' id=\"adi_notes\"\'.$class);\n\n		// display add/edit link\n		if (adi_notes_has_privs(adi_notes_prefs(\'note_tab_edit_privs\')) && !($adi_notes_step == \'edit\'))\n			echo graf(\n				\'[\'\n				.($adi_notes_txp460 ?\n					href(\n						$linktext\n						,array(\n							\'event\' => $event,\n							\'adi_notes_step\' => \'edit\',\n							\'adi_notes_id\' => $this_note[\'id\'],\n							\'adi_notes_user\' => \'\'\n						)\n// 						,array(\'class\' => \'small\')\n					)\n				:\n					slink($event,\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$this_note[\'id\'].\'&amp;adi_notes_user=\',$linktext,\'small\')\n				)\n				.\']\'\n				,\' class=\"adi_notes_tab_link\"\'\n			);\n	}\n}\n\nfunction adi_notes_edit_form($note_name,$type,$note_id,$note_user,$convert=\'\') {\n// display individual note edit form\n	global $event,$step,$adi_notes_login_user,$timeoffset,$adi_notes_debug,$adi_notes_txp460;\n\n	$preserve_urlvar = adi_notes_preserve_urlvar();\n	if ($note_id) { // read note from database\n		$this_note = safe_row(\"*\",\'adi_notes\',\"id=\'\".$note_id.\"\'\",$adi_notes_debug);\n		extract($this_note); // $name, $type, $user, $privs, $visible, $last_mod, $last_mod_user, $note\n		$realname = fetch(\'RealName\',\'txp_users\',\'name\',$last_mod_user);\n		$mod_msg = gTxt(\'modified_by\').\" $realname: \".date(\"H:i, d M Y\",strtotime($last_mod) + $timeoffset);\n		if ($convert == \'textile\') {\n			$textile = new Textile;\n			$note = $textile->TextileThis($note);\n		}\n	}\n	else // note doesn\'t exist yet\n		$note = \'\';\n\n	if (strpos($event,\'adi_notes_tab_\') === FALSE) { // don\'t want a footnote on a Notes tab\n		if ($note_user) {\n			$adi_notes_step = \'save_user\';\n			$title = adi_notes_gtxt(\'adi_note_editing_private\').\':\';\n		}\n		else {\n			$submit_button = gTxt(\'save\');\n			$adi_notes_step = \'save\';\n			$title = adi_notes_gtxt(\'adi_note_editing_public\').\':\';\n		}\n	}\n	else {\n		$adi_notes_step = \'save\';\n		$title = \'\';\n	}\n\n	if ($type == \'tab\')\n		$extra_class = \' adi_notes_textarea_tab\';\n	else\n		$extra_class = \'\';\n	if (adi_notes_prefs(\'tiny_mce\') != \'none\')\n		$extra_class .= \' adi_notes_mceEditor\';\n\n	if ($title)\n		$title = tag($title,\'p\',\' class=\"adi_notes_label\"\');\n	$out[] =\n		$title\n		.\'<textarea name=\"adi_notes_note\" class=\"adi_notes_textarea\'.$extra_class.\'\">\'.htmlspecialchars($note).\'</textarea>\'\n		.graf(\n			fInput(\'submit\',\'savenote\',gTxt(\'save\'),\'publish\')\n			.eInput($event)\n			.sInput($step)\n			.($preserve_urlvar ? hInput(adi_notes_preserve_urlvar(\'var\'),adi_notes_preserve_urlvar(\'value\')) : \'\') // pass on edit screen article/image ID/id\n			.hInput(\'adi_notes_step\',$adi_notes_step)\n			.($note_user ? hInput(\'adi_notes_user_note_id\',$note_id) : hInput(\'adi_notes_id\',$note_id))\n			.hInput(\'adi_notes_user\',$adi_notes_login_user)\n			.(empty($note) ? \'\' : $mod_msg)\n			.((adi_notes_prefs(\'markup\') != \'textile\') && adi_notes_prefs(\'convert_link\') && !empty($note) ? // not textile AND convert link requested AND non-empty note\n				sp\n				.sp\n				.\'[\'\n				.($adi_notes_txp460 ?\n					tag(\n						adi_notes_gtxt(\'adi_convert_textile\')\n						,\'a\'\n						,\'href=\"\'.join_qs(array(\'event\'=>$event,\'adi_notes_step\'=>\'edit\',\'adi_notes_user\'=>\'\',\'adi_notes_id\'=>$this_note[\'id\'],\'adi_notes_convert\'=>\'textile\')).\'\"\'\n					)\n				:\n					slink($event,\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$this_note[\'id\'].\'&amp;adi_notes_user=&amp;adi_notes_convert=textile\',adi_notes_gtxt(\'adi_convert_textile\'),\'small\')\n				)\n				.\']\'\n			:\n				\'\'\n			)\n			,\' class=\"adi_notes_save\"\'\n		);\n\n	if ($adi_notes_debug)\n		$note_user ?\n		$out[] = \'<p>\'.__FUNCTION__.\": Event=$event,Step=$step,adi_notes_step=$adi_notes_step,note_name=$note_name,adi_notes_id=$note_id,note_user=$note_user,adi_notes_login_user=$adi_notes_login_user</p>\" :\n		$out[] = \'<p>\'.__FUNCTION__.\": Event=$event,Step=$step,adi_notes_step=$adi_notes_step,note_name=$note_name,adi_notes_user_note_id=$note_id,note_user=$note_user,adi_notes_login_user=$adi_notes_login_user</p>\";\n\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$anchor = \'\'; // jump back to top of page after edit headnotes\n	else\n		$anchor = \'adi_notes\';\n\n	return form(implode(\'\',$out),\'\',\'\',\'post\',\'\',$anchor);\n}\n\nfunction adi_notes_save($note_name,$type,$note_id,$user,$note) {\n// save note to database\n	global $event,$adi_notes_login_user,$adi_notes_debug;\n\n	$note_name = doSlash($note_name);\n	$type = doSlash($type);\n	$note_id = doSlash($note_id);\n	$user = doSlash($user);\n	$note = doSlash($note);\n	empty($note_id) ? // new note\n		$note_id = \"DEFAULT\" :\n		$note_id = \"\'\".$note_id.\"\'\";\n	empty($user) ? // blank user = public note\n		$user = \"DEFAULT\" :\n		$user = \"\'\".$user.\"\'\";\n\n	return safe_query(\n		\"REPLACE INTO \".\n		safe_pfx(\'adi_notes\').\n		\" VALUES ($note_id,\'$note_name\',\'$type\',$user,DEFAULT,\'1\',now(),\'$adi_notes_login_user\',\'$note\')\",$adi_notes_debug\n		);\n}\n\nfunction adi_notes_preserve_urlvar($what=\'\') {\n// get an $event specific URL var & return various value permutations\n	global $event,$adi_notes_txp460;\n\n	$this_var = array();\n	$var_name = \'\';\n	// define which one\n	if ($event == \'article\')\n		$var_name = \'ID\'; // upper case ID\n	if ($event == \'image\')\n		$var_name = \'id\'; // lower case id\n	if (($event == \'page\') || ($event == \'css\') || ($event == \'form\'))\n		$var_name = \'name\';\n	// get it\n	if ($var_name) {\n		$value = gps($var_name);\n		if ($value) {\n			$this_var[\'var\'] = $var_name;\n			$this_var[\'value\'] = $value;\n		}\n	}\n	// sort out what to return\n	if ($this_var) {\n		if ($what == \'var\')\n			return $this_var[\'var\'];\n		else if ($what == \'value\')\n			return $this_var[\'value\'];\n		else\n			return \'&amp;\'.$this_var[\'var\'].\'=\'.$this_var[\'value\'];\n	}\n	else\n		return \'\';\n}\n\nfunction adi_notes_embed_note($x,$y,$default) {\n// generate markup for embedded notes\n	global $pretext,$event,$step,$adi_notes_debug,$adi_notes_prefs,$adi_notes_login_user;\n\n	include_once txpath.\'/publish.php\';\n\n	// bailout\n	if (adi_notes_upgrade()) // upgrade required\n		return \'\';\n	if (strpos($event,\'adi_notes_tab_\') !== FALSE) // don\'t want a embedded note on a Notes tab\n		return \'\';\n	if ($event == \'adi_notes_options\') // don\'t want embedded note on txpdev adi_notes options page\n		return \'\';\n	if ($event == \'plugin\') // don\'t want a embedded note during lifecycle events on plugin page (e.g. plugin delete)\n		return \'\';\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	$note = gps(\'adi_notes_note\');\n	$note_id = gps(\'adi_notes_id\');\n	$note_name = $event;\n	$note_user = gps(\'adi_notes_user\');\n	$user_note_id = gps(\'adi_notes_user_note_id\');\n\n	// class\n	adi_notes_prefs(\'style\') ?\n		$class = \'adi_notes_\'.adi_notes_prefs(\'style\') :\n		$class = \'\';\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$class .= \' adi_notes_embed_headnote\';\n	else\n		$class .= \' adi_notes_embed_footnote\';\n	if ($class)\n		$class = \' class=\"\'.$class.\'\"\';\n\n	$admin_note = $user_note = $edit_note = $edit_user_note = \'\';\n\n	$preamble = \'<div id=\"adi_notes\"\'.$class.\'>\';\n	$postamble = \'</div>\';\n\n	if ($adi_notes_debug)\n		$debug = \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_login_user=$adi_notes_login_user, note_user=$note_user, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$note_id, adi_notes_user_note_id=$user_note_id</p>\";\n	else\n		$debug = \'\';\n\n	if ($adi_notes_step == \'edit\')\n		$edit_note = adi_notes_edit_form($note_name,\'embed\',$note_id,\'\');\n	else if ($adi_notes_step == \'save\')\n		adi_notes_save($note_name,\'embed\',$note_id,\'\',$note);\n	else if ($adi_notes_step == \'edit_user\')\n		$edit_user_note = adi_notes_edit_form($note_name,\'embed\',$user_note_id,$note_user);\n	else if ($adi_notes_step == \'save_user\')\n		adi_notes_save($note_name,\'embed\',$user_note_id,$note_user,$note);\n\n	if (!($adi_notes_step == \'edit\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',\'\');\n		if (!empty($this_note)) {\n			$admin_note = $this_note[\'formatted\'];\n			$admin_note = parse($admin_note);\n			$note_id = $this_note[\'id\'];\n		}\n	}\n\n	if (!($adi_notes_step == \'edit_user\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',$adi_notes_login_user);\n		if (!empty($this_note)) {\n			$user_note = $this_note[\'formatted\'];\n			if (adi_notes_has_privs(adi_notes_prefs(\'txp_tag_privs\'))) // only parse private notes if user has sufficient privs\n				$user_note = parse($user_note);\n			$user_note_id = $this_note[\'id\'];\n		}\n	}\n\n	// set up what needs to be output\n	$out = $debug;\n	if (($admin_note || $edit_note) && ($user_note || $edit_user_note))\n		$hr = \'<hr class=\"adi_notes_hr\"/>\';\n	else\n		$hr =\'\';\n	if ($admin_note || $edit_note || $user_note || $edit_user_note)\n		$out .= $preamble.$admin_note.$edit_note.$hr.$user_note.$edit_user_note.$postamble;\n\n	return $out; // adi_notes_embed_note_links() sorts out $default (div#end_page)\n}\n\nfunction adi_notes_embed_note_links($x, $y, $default) {\n// create edit/add links for embedded notes\n	global $event,$step,$adi_notes_debug,$adi_notes_txp460,$adi_notes_login_user;\n\n	// bailout\n	if (adi_notes_upgrade()) // upgrade required\n		return \'\';\n	if (strpos($event,\'adi_notes_tab_\') !== FALSE) // don\'t want a embedded note on a Notes tab\n		return \'\';\n	if ($event == \'adi_notes_options\') // don\'t want embedded note on txpdev adi_notes options page\n		return \'\';\n	if ($event == \'plugin\') // don\'t want a embedded note during lifecycle events on plugin page (e.g. plugin delete)\n		return \'\';\n\n	// article edit & image edit screens get special treatment\n	$this_step = $step;\n	if (($event != \'article\') && ($event != \'image\'))\n		$this_step = \'\'; // too dangerous to preserve step (in edit/add links) if not article/image edit screen\n	$preserve_urlvar = adi_notes_preserve_urlvar(); // article/image edit tab id/ID\n\n	// token effort for TXP 4.4.1+\n	$txp_token = \'\';\n	if (version_compare(txp_version,\'4.4.1\',\'>=\'))\n		if (($event == \'article\') && ($step == \'create\')) // article create tab only\n			$txp_token = \'&amp;_txp_token=\'.form_token();\n\n	$adi_notes_step = gps(\'adi_notes_step\');\n	$note_name = $event;\n\n	if (adi_notes_prefs(\'position\') == \'header\')\n		$anchor = \'\'; // jump back to top of page after editing headnotes\n	else\n		$anchor = \'#adi_notes\';\n\n	$out = \'\';\n	$admin_link = $user_link = \'\';\n\n	// set up admin link\n	$admin_link = \'\';\n	if (!($adi_notes_step == \'edit\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',\'\');\n		if (!empty($this_note)) {\n			$admin_note = $this_note[\'formatted\'];\n			$note_id = $this_note[\'id\'];\n		}\n		else {\n			$admin_note = \'\';\n			$note_id = \'\';\n		}\n		if ($admin_note)\n			$link_text = adi_notes_gtxt(\'adi_edit_public_note\');\n		else\n			$link_text = adi_notes_gtxt(\'adi_add_public_note\');\n		if (adi_notes_has_privs(adi_notes_prefs(\'public_note_edit_privs\')))\n			$admin_link =\n				\'[\'\n				.($adi_notes_txp460 ?\n					tag(//? TOKEN\n						$link_text\n						,\'a\'\n						,\'href=\"\'.join_qs(array(\'event\'=>$event,\'step\'=>$this_step,\'adi_notes_step\'=>\'edit\',\'adi_notes_id\'=>$note_id)).$preserve_urlvar.$anchor.\'\"\'\n					)\n				:\n					slink($event,$this_step.$preserve_urlvar.$txp_token.\'&amp;adi_notes_step=edit\'.\'&amp;adi_notes_id=\'.$note_id.$anchor,$link_text,\'small\')\n				)\n				.\']\';\n\n		if ($adi_notes_debug)\n			$out .= \'<p>\'.__FUNCTION__.\": event=$event, step=$step, adi_notes_login_user=$adi_notes_login_user, this_step=$this_step, preserve_urlvar=$preserve_urlvar, txp_token=$txp_token, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_id=$note_id</p>\";\n	}\n\n	// set up user link\n	$user_link = \'\';\n	if (!($adi_notes_step == \'edit_user\')) {\n		$this_note = adi_notes_read($note_name,\'embed\',\'\',$adi_notes_login_user);\n		if (!empty($this_note)) {\n			$user_note = $this_note[\'formatted\'];\n			$user_note_id = $this_note[\'id\'];\n		}\n		else {\n			$user_note = \'\';\n			$user_note_id = \'\';\n		}\n		if ($user_note) {\n			$link_text = adi_notes_gtxt(\'adi_edit_private_note\');\n			$note_user = $this_note[\'user\'];\n		}\n		else {\n			$link_text = adi_notes_gtxt(\'adi_add_private_note\');\n			$note_user = $adi_notes_login_user;\n		}\n		$user_link =\n			\' [\'\n			.($adi_notes_txp460 ?\n				tag( //? TOKEN\n					$link_text\n					,\'a\'\n					,\'href=\"\'.join_qs(array(\'event\'=>$event,\'step\'=>$this_step,\'adi_notes_step\'=>\'edit_user\',\'adi_notes_user_note_id\'=>$user_note_id,\'adi_notes_user\'=>$note_user)).$preserve_urlvar.$anchor.\'\"\'\n				)\n			:\n				slink($event,$this_step.$preserve_urlvar.$txp_token.\'&amp;adi_notes_step=edit_user\'.\'&amp;adi_notes_user_note_id=\'.$user_note_id.\'&amp;adi_notes_user=\'.$note_user.$anchor,$link_text,\'small\')\n			)\n			.\']\';\n\n		if ($adi_notes_debug)\n			$out .= \'<p>\'.__FUNCTION__.\": event=$event, step=$step, this_step=$this_step, adi_notes_login_user=$adi_notes_login_user, note_user=$note_user, preserve_urlvar=$preserve_urlvar, txp_token=$txp_token, adi_notes_step=$adi_notes_step, note_name=$note_name, adi_notes_user_note_id=$user_note_id</p>\";\n	}\n\n	$out .= graf($admin_link.$user_link,\' class=\"adi_notes_embed_links\"\');\n\n	return $out.$default;\n}\n\nfunction adi_notes_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_notes_gtxt() are functionally equivalent\n	global $adi_notes_gtxt;\n\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_notes_gtxt)) // adi translation found\n			return $adi_notes_gtxt[$phrase];\n		else // last resort\n			return $phrase;\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n', '28976fd90dd87278f7cc2fa6d7e39f9d', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_peex', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wet_peex-peek-into-textpattern-using-xml', '1.0', 'Peek into Textpattern using XML', '<h3>Peek into Textpattern using <span class=\"caps\">XML</span></h3>\n\n<p><strong>wet_peex</strong> peeks for Textpattern objects as <abbr title=\"Extensible Markup Language\"><span class=\"caps\">XML</span></abbr> resources.</p>\n\n<h4>usage:</h4>\n\n<p><strong>wet_peex</strong> is a backend plugin without any user interface. It handles <span class=\"caps\">HTTP</span> <span class=\"caps\">GET</span> requests and replies with an <span class=\"caps\">XML</span> response.</p>\n\n<p>To query <code>wet_peex</code>, send a request to the resource located at <code>http://yoursite.tld/textpattern?wet_peex</code>.</p>\n\n<p>wet_peex understands these query parameters:</p>\n\n<ul>\n	<li><code>object_type</code>: <code>article</code>, <code>section</code>, <code>category</code>, <code>link</code>, <code>image</code>, <code>file</code>. Use one of these as a value for the <code>wet_peex</code> <span class=\"caps\">GET</span> parameter, e.g. <code>http://yoursite.tld/textpattern?wet_peex=article</code></li>\n	<li><code>sort</code>: Sort criterion</li>\n	<li><code>dir</code>: Sort direction. Use either <code>asc</code> for ascending sort order, or <code>desc</code> for descending sort order</li>\n	<li><code>search</code>: Textual search criterion</li>\n	<li><code>offset</code>: First object&#8217;s ordinal in reply</li>\n	<li><code>limit</code>: Maximum objects returned</li>\n</ul>\n\n<p>A sample request:</p>\n\n<p><code>http://yoursite.tld/textpattern?wet_peex=article&amp;sort=posted&amp;dir=asc</code></p>\n\n<p>The <span class=\"caps\">XML</span> reply message is rather self explanatory. View source for details. <code>article</code> is the only currently implemented object type.</p>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nif (@txpinterface == \'admin\') {\n	$what = gps(\'wet_peex\');\n	if (in_array($what, array(\'article\', \'section\', \'category\', \'link\', \'image\', \'file\'))) {\n		wet_peex($what);\n		exit();\n	} elseif(!empty($what)) {\n		// curiosity killed the cat\n		header(\'HTTP/1.1 400 Bad request\');\n		header(\'Status: 400 Bad request\');\n		exit();\n	}\n}\n\nfunction wet_peex($what)\n{\n	// get and sanitize some common parameters\n	extract(gpsa(array(\'limit\', \'offset\', \'sort\', \'dir\', \'crit\', \'search\')));\n\n	$limit = intval(doSlash($limit));\n	if (0 == $limit) {\n		$limit = 20;\n	}\n\n	$offset = intval(doSlash($offset));\n\n	if ($dir != \'desc\') {\n		$dir = \'asc\';\n	}\n\n	// preludium\n	while(@ob_end_clean()); // eat previous output to assert well-formed XML\n	header(\'Content-Type: text/xml; charset=utf-8\');\n	header(\'Expires: \'.date(\'r\', time()+60)); // avoid too frequent updates\n	header(\'Cache-Control: private\');\n	echo \'<?xml version=\"1.0\" encoding=\"utf-8\" ?>\'.n;\n	echo \'<textpattern>\'.n;\n\n	switch($what) {\n		case \'article\':\n			if (empty($search)) {\n				$search = \'1=1\';\n			} else {\n				$search_escaped = doSlash($search);\n				$search = \"Title rlike \'$search_escaped\' or Body rlike \'$search_escaped\'\";\n			}\n\n			switch ($sort)\n			{\n				case \'id\':\n					$sort_sql = \'ID \'.$dir;\n				break;\n\n				case \'posted\':\n					$sort_sql = \'Posted \'.$dir;\n				break;\n\n				case \'lastmod\':\n					$sort_sql = \'LastMod \'.$dir;\n				break;\n\n				case \'title\':\n					$sort_sql = \'Title \'.$dir.\', Posted desc\';\n				break;\n\n				case \'section\':\n					$sort_sql = \'Section \'.$dir.\', Posted desc\';\n				break;\n\n				case \'category1\':\n					$sort_sql = \'Category1 \'.$dir.\', Posted desc\';\n				break;\n\n				case \'category2\':\n					$sort_sql = \'Category2 \'.$dir.\', Posted desc\';\n				break;\n\n				case \'status\':\n					$sort_sql = \'Status \'.$dir.\', Posted desc\';\n				break;\n\n				case \'author\':\n					$sort_sql = \'AuthorID \'.$dir.\', Posted desc\';\n				break;\n\n				case \'comments\':\n					$sort_sql = \'comments_count \'.$dir.\', Posted desc\';\n				break;\n\n				default:\n					$sort_sql = \'Posted \'.$dir;\n				break;\n			}\n\n			$count = safe_count(\'textpattern\', $search); // ignore article status\n			$articles = safe_rows(\'*, unix_timestamp(Posted) as Posted\', \'textpattern\', \"$search order by $sort_sql limit $limit offset $offset\");\n\n			require_once txpath.\'/publish/taghandlers.php\';\n\n			echo \"<articles count=\'$count\' offset=\'$offset\' limit=\'$limit\'>\".n;\n			foreach ($articles as $a) {\n				$teaser = htmlspecialchars\n							(preg_replace(\'/^(.{0,45}).*$/su\',\'$1\',\n								trim(strip_tags($a[\'Body_html\']))\n							));\n				echo \'<article>\'.n.\n					t.\'<id>\'.$a[\'ID\'].\'</id>\'.n.\n					t.\'<title>\'.escape_title($a[\'Title\']).\'</title>\'.n.\n					t.\'<section>\'.htmlspecialchars($a[\'Section\']).\'</section>\'.n;\n					if(!empty($a[\'Category1\']) || !empty($a[\'Category2\'])) {\n						echo t.\'<categories>\'.n.\n							t.t.\'<category level=\"1\">\'.htmlspecialchars($a[\'Category1\']).\'</category>\'.n.\n							t.t.\'<category level=\"2\">\'.htmlspecialchars($a[\'Category2\']).\'</category>\'.n.\n						t.\'</categories>\'.n;\n					}\n					echo t.\'<posted>\'.date(\'Y-m-d H:i:s\', $a[\'Posted\']).\'</posted>\'.n.\n					t.\'<teaser>\'.$teaser.\'</teaser>\'.n.\n					t.\'<lastmod>\'.$a[\'LastMod\'].\'</lastmod>\'.n.\n					t.\'<permlink>\'.permlinkurl($a).\'</permlink>\'.n.\n					\'</article>\'.n;\n			}\n			echo \'</articles>\';\n\n			break; // $what == \'article\'\n		/**\n		 * @todo Implement for other txp objects\n		 */\n		case \'section\':\n		case \'category\':\n		case \'image\';\n		case \'file\';\n			echo \'<message>not implemented</message>\';\n			break;\n		default:\n			break;\n	}\n\n	// postludium\n	echo n.\'</textpattern>\';\n}\n', '\nif (@txpinterface == \'admin\') {\n	$what = gps(\'wet_peex\');\n	if (in_array($what, array(\'article\', \'section\', \'category\', \'link\', \'image\', \'file\'))) {\n		wet_peex($what);\n		exit();\n	} elseif(!empty($what)) {\n		// curiosity killed the cat\n		header(\'HTTP/1.1 400 Bad request\');\n		header(\'Status: 400 Bad request\');\n		exit();\n	}\n}\n\nfunction wet_peex($what)\n{\n	// get and sanitize some common parameters\n	extract(gpsa(array(\'limit\', \'offset\', \'sort\', \'dir\', \'crit\', \'search\')));\n\n	$limit = intval(doSlash($limit));\n	if (0 == $limit) {\n		$limit = 20;\n	}\n\n	$offset = intval(doSlash($offset));\n\n	if ($dir != \'desc\') {\n		$dir = \'asc\';\n	}\n\n	// preludium\n	while(@ob_end_clean()); // eat previous output to assert well-formed XML\n	header(\'Content-Type: text/xml; charset=utf-8\');\n	header(\'Expires: \'.date(\'r\', time()+60)); // avoid too frequent updates\n	header(\'Cache-Control: private\');\n	echo \'<?xml version=\"1.0\" encoding=\"utf-8\" ?>\'.n;\n	echo \'<textpattern>\'.n;\n\n	switch($what) {\n		case \'article\':\n			if (empty($search)) {\n				$search = \'1=1\';\n			} else {\n				$search_escaped = doSlash($search);\n				$search = \"Title rlike \'$search_escaped\' or Body rlike \'$search_escaped\'\";\n			}\n\n			switch ($sort)\n			{\n				case \'id\':\n					$sort_sql = \'ID \'.$dir;\n				break;\n\n				case \'posted\':\n					$sort_sql = \'Posted \'.$dir;\n				break;\n\n				case \'lastmod\':\n					$sort_sql = \'LastMod \'.$dir;\n				break;\n\n				case \'title\':\n					$sort_sql = \'Title \'.$dir.\', Posted desc\';\n				break;\n\n				case \'section\':\n					$sort_sql = \'Section \'.$dir.\', Posted desc\';\n				break;\n\n				case \'category1\':\n					$sort_sql = \'Category1 \'.$dir.\', Posted desc\';\n				break;\n\n				case \'category2\':\n					$sort_sql = \'Category2 \'.$dir.\', Posted desc\';\n				break;\n\n				case \'status\':\n					$sort_sql = \'Status \'.$dir.\', Posted desc\';\n				break;\n\n				case \'author\':\n					$sort_sql = \'AuthorID \'.$dir.\', Posted desc\';\n				break;\n\n				case \'comments\':\n					$sort_sql = \'comments_count \'.$dir.\', Posted desc\';\n				break;\n\n				default:\n					$sort_sql = \'Posted \'.$dir;\n				break;\n			}\n\n			$count = safe_count(\'textpattern\', $search); // ignore article status\n			$articles = safe_rows(\'*, unix_timestamp(Posted) as Posted\', \'textpattern\', \"$search order by $sort_sql limit $limit offset $offset\");\n\n			require_once txpath.\'/publish/taghandlers.php\';\n\n			echo \"<articles count=\'$count\' offset=\'$offset\' limit=\'$limit\'>\".n;\n			foreach ($articles as $a) {\n				$teaser = htmlspecialchars\n							(preg_replace(\'/^(.{0,45}).*$/su\',\'$1\',\n								trim(strip_tags($a[\'Body_html\']))\n							));\n				echo \'<article>\'.n.\n					t.\'<id>\'.$a[\'ID\'].\'</id>\'.n.\n					t.\'<title>\'.escape_title($a[\'Title\']).\'</title>\'.n.\n					t.\'<section>\'.htmlspecialchars($a[\'Section\']).\'</section>\'.n;\n					if(!empty($a[\'Category1\']) || !empty($a[\'Category2\'])) {\n						echo t.\'<categories>\'.n.\n							t.t.\'<category level=\"1\">\'.htmlspecialchars($a[\'Category1\']).\'</category>\'.n.\n							t.t.\'<category level=\"2\">\'.htmlspecialchars($a[\'Category2\']).\'</category>\'.n.\n						t.\'</categories>\'.n;\n					}\n					echo t.\'<posted>\'.date(\'Y-m-d H:i:s\', $a[\'Posted\']).\'</posted>\'.n.\n					t.\'<teaser>\'.$teaser.\'</teaser>\'.n.\n					t.\'<lastmod>\'.$a[\'LastMod\'].\'</lastmod>\'.n.\n					t.\'<permlink>\'.permlinkurl($a).\'</permlink>\'.n.\n					\'</article>\'.n;\n			}\n			echo \'</articles>\';\n\n			break; // $what == \'article\'\n		/**\n		 * @todo Implement for other txp objects\n		 */\n		case \'section\':\n		case \'category\':\n		case \'image\';\n		case \'file\';\n			echo \'<message>not implemented</message>\';\n			break;\n		default:\n			break;\n	}\n\n	// postludium\n	echo n.\'</textpattern>\';\n}\n', '1039e4c101e0a90f4257471bd605b3d5', '', '', 3, 5, 0),
('wet_quickopen', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_quickopen-textpattern-plugin', '1.4.0', 'Open recent (and not so recent) articles quickly', '<h3>Open recent (and not so recent) articles quickly</h3>\n\n<p><strong>wet_quickopen</strong> is a plugin for Textpattern which prepends a search term input on top of the “Recent Articles” list at the article edit screen. The “Recent Articles” list is filtered by matching the current search term against all articles’ titles and bodies.</p>\n\n<h4>usage:</h4>\n\n<ol>\n	<li>Textpattern 4.7+ is required.</li>\n	<li>Install both <code>wet_quickopen</code> and <code>wet_peex</code> as a Textpattern plugin.</li>\n	<li>Done. If all went well, the <a href=\"./?event=article\" rel=\"nofollow\">Content &gt; Write</a> screen will be adorned by a new input box atop of the “Recent Articles” list.</li>\n</ol>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the Gnu General Public Licence.</p>', '\n// serve assorted resources\nswitch (gps(\'wet_rsrc\')) {\n	case \'quickopen_js\':\n		wet_quickopen_js();\n		break;\n	default:\n		break;\n}\n\nglobal $app_mode;\nif ($app_mode != \'async\') {\n	/**\n	 * Insert a search box at the top of \"Recent Articles\".\n	 */\n	register_callback(\'wet_quickopen_form\', \'article_ui\', \'recent_articles\');\n	function wet_quickopen_form($event, $step, $default)\n	{\n		return n .\n		fInput(\'text\', \'wet_quickopen_search\', \'\', \'edit txp-form-field-value\', \'\', \'\', INPUT_REGULAR, 0, \'wet_quickopen_search\') . n .\n		$default;\n	}\n\n	/**\n	 * Pull in the JS worker file near the end of the page\n	 */\n	register_callback(\'wet_quickopen_jslink\', \'article\');\n	function wet_quickopen_jslink($event, $step)\n	{\n		echo \'<script src=\"?wet_rsrc=quickopen_js\" type=\"text/javascript\"></script>\' . n;\n		require_plugin(\'wet_peex\'); // won\'t help for loading wet_peex on time, but point out the lack of it to unwary users.\n	}\n\n	/**\n	 * Pull in additional styles at the end of the HEAD\n	 */\n	register_callback(\'wet_quickopen_style\', \'admin_side\', \'head_end\');\n	function wet_quickopen_style($event, $step)\n	{\n		echo n . \'<style type=\"text/css\">#wet_quickopen_search{margin-top:1em;}</style>\' . n;\n	}\n}\n\n/**\n * Serve embedded JS resource\n */\nfunction wet_quickopen_js()\n{\n	while (@ob_end_clean()) ;\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \" . date(\"r\", time() + 3600));\n	header(\"Cache-Control: public\");\n	$rows = (defined(\'WRITE_RECENT_ARTICLES_COUNT\') ? WRITE_RECENT_ARTICLES_COUNT : 10);\n	echo <<<JS\n/**\n * wet_quickopen: Open recent (and not so recent) articles quickly\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/software/wet_quickopen-textpattern-plugin\n */\nvar wet_quickopen = {\n	rows: {$rows},\n	sortdir: \'desc\',\n	crit: \'lastmod\',\n	search: \'\',\n\n	// the worker function refreshes the list of matching articles and inserts the result into the \"recent articles\" list\n	refresh: function() {\n		var box = $(\'.recent\');\n		$.ajax({\n			url: \'\',\n			data: {\n				\'wet_peex\': \'article\',\n				\'limit\': wet_quickopen.rows.toString(),\n				\'offset\': \'0\',\n				\'dir\': wet_quickopen.sortdir,\n				\'sort\': wet_quickopen.crit,\n				\'search\': wet_quickopen.search\n			},\n			dataType: \'xml\',\n			success: function(xml) {\n				// paint the article list\n				var list = $(\'<dummy/>\');\n				// parse the XML response\n				$(\'article\', xml).each(function(i) {\n					var li = $(\'<li class=\"recent-article\"/>\');\n					var href = \'?event=article&step=edit&ID=\' + $(\'id\', this).text();\n					var title = wet_quickopen.htmlspecialchars($(\'title\', this).text());\n					list.append(\n						$(\'<dummy/>\').append(  // jQuerish outerHTML()\n							li.append(\n								$(\'<a />\').attr(\'href\', href).html(title)\n							)\n						).html()\n					);\n				});\n				// inject list into \"Recent Articles\"\n				box.html(list.html());\n			},\n			error: function(XMLHttpRequest, textStatus, errorThrown) {\n				 box.html(\'<strong>wet_quickopen: \' + textStatus + \'</strong>\');\n			}\n		});\n		return;\n	},\n\n	// add behaviours\n	behaviours: function() {\n		var i = $(\'input#wet_quickopen_search\');\n		// User hit <enter>: submit query immediately\n		i.keypress(\n			function(event) {\n				if (event.keyCode == \'13\') {\n					event.preventDefault();\n					wet_quickopen.refresh();\n				}\n			}\n		);\n		// User hit any other key: submit query after a little timeout to reduce network traffic\n		i.keyup(\n			function() {\n				if (this.value != wet_quickopen.search) {\n					wet_quickopen.search = this.value;\n					try {\n						window.clearTimeout(this.lazy);\n					} catch(e) {}\n					this.lazy = window.setTimeout(wet_quickopen.refresh, 750);\n				}\n			}\n		);\n	},\n\n	htmlspecialchars: function (s) {\n		return $(\'<p/>\').text(s).html();\n	}\n};\n\n$(document).ready( function(){wet_quickopen.behaviours();} );\nJS;\n	exit();\n}\n', '\n// serve assorted resources\nswitch (gps(\'wet_rsrc\')) {\n	case \'quickopen_js\':\n		wet_quickopen_js();\n		break;\n	default:\n		break;\n}\n\nglobal $app_mode;\nif ($app_mode != \'async\') {\n	/**\n	 * Insert a search box at the top of \"Recent Articles\".\n	 */\n	register_callback(\'wet_quickopen_form\', \'article_ui\', \'recent_articles\');\n	function wet_quickopen_form($event, $step, $default)\n	{\n		return n .\n		fInput(\'text\', \'wet_quickopen_search\', \'\', \'edit txp-form-field-value\', \'\', \'\', INPUT_REGULAR, 0, \'wet_quickopen_search\') . n .\n		$default;\n	}\n\n	/**\n	 * Pull in the JS worker file near the end of the page\n	 */\n	register_callback(\'wet_quickopen_jslink\', \'article\');\n	function wet_quickopen_jslink($event, $step)\n	{\n		echo \'<script src=\"?wet_rsrc=quickopen_js\" type=\"text/javascript\"></script>\' . n;\n		require_plugin(\'wet_peex\'); // won\'t help for loading wet_peex on time, but point out the lack of it to unwary users.\n	}\n\n	/**\n	 * Pull in additional styles at the end of the HEAD\n	 */\n	register_callback(\'wet_quickopen_style\', \'admin_side\', \'head_end\');\n	function wet_quickopen_style($event, $step)\n	{\n		echo n . \'<style type=\"text/css\">#wet_quickopen_search{margin-top:1em;}</style>\' . n;\n	}\n}\n\n/**\n * Serve embedded JS resource\n */\nfunction wet_quickopen_js()\n{\n	while (@ob_end_clean()) ;\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \" . date(\"r\", time() + 3600));\n	header(\"Cache-Control: public\");\n	$rows = (defined(\'WRITE_RECENT_ARTICLES_COUNT\') ? WRITE_RECENT_ARTICLES_COUNT : 10);\n	echo <<<JS\n/**\n * wet_quickopen: Open recent (and not so recent) articles quickly\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/software/wet_quickopen-textpattern-plugin\n */\nvar wet_quickopen = {\n	rows: {$rows},\n	sortdir: \'desc\',\n	crit: \'lastmod\',\n	search: \'\',\n\n	// the worker function refreshes the list of matching articles and inserts the result into the \"recent articles\" list\n	refresh: function() {\n		var box = $(\'.recent\');\n		$.ajax({\n			url: \'\',\n			data: {\n				\'wet_peex\': \'article\',\n				\'limit\': wet_quickopen.rows.toString(),\n				\'offset\': \'0\',\n				\'dir\': wet_quickopen.sortdir,\n				\'sort\': wet_quickopen.crit,\n				\'search\': wet_quickopen.search\n			},\n			dataType: \'xml\',\n			success: function(xml) {\n				// paint the article list\n				var list = $(\'<dummy/>\');\n				// parse the XML response\n				$(\'article\', xml).each(function(i) {\n					var li = $(\'<li class=\"recent-article\"/>\');\n					var href = \'?event=article&step=edit&ID=\' + $(\'id\', this).text();\n					var title = wet_quickopen.htmlspecialchars($(\'title\', this).text());\n					list.append(\n						$(\'<dummy/>\').append(  // jQuerish outerHTML()\n							li.append(\n								$(\'<a />\').attr(\'href\', href).html(title)\n							)\n						).html()\n					);\n				});\n				// inject list into \"Recent Articles\"\n				box.html(list.html());\n			},\n			error: function(XMLHttpRequest, textStatus, errorThrown) {\n				 box.html(\'<strong>wet_quickopen: \' + textStatus + \'</strong>\');\n			}\n		});\n		return;\n	},\n\n	// add behaviours\n	behaviours: function() {\n		var i = $(\'input#wet_quickopen_search\');\n		// User hit <enter>: submit query immediately\n		i.keypress(\n			function(event) {\n				if (event.keyCode == \'13\') {\n					event.preventDefault();\n					wet_quickopen.refresh();\n				}\n			}\n		);\n		// User hit any other key: submit query after a little timeout to reduce network traffic\n		i.keyup(\n			function() {\n				if (this.value != wet_quickopen.search) {\n					wet_quickopen.search = this.value;\n					try {\n						window.clearTimeout(this.lazy);\n					} catch(e) {}\n					this.lazy = window.setTimeout(wet_quickopen.refresh, 750);\n				}\n			}\n		);\n	},\n\n	htmlspecialchars: function (s) {\n		return $(\'<p/>\').text(s).html();\n	}\n};\n\n$(document).ready( function(){wet_quickopen.behaviours();} );\nJS;\n	exit();\n}\n', 'ab3fcdbf5b8de6c3911c0a1f39684124', '', '', 3, 5, 0),
('wet_babble', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_babble-textpattern-plugin', '0.2', 'Injects custom i18n strings, potentially overriding some built-ins', '<h3>Purpose</h3>\n\n<p>Sometimes Textpattern&#8217;s built-in texts just don&#8217;t fit the bill. This plugin overrides a choice of your liking with alternate texts. Updating the core texts from the Textpattern language server won&#8217;t wipe out your custom texts.</p>\n\n<h3>Usage</h3>\n\n<p>As this plugin currently is just quick and dirty patch, you&#8217;d have to modify the plugin&#8217;s code to add your customisations.</p>\n\n<ol>\n	<li>Open Textpattern&#8217;s language table <code>txp_lang</code> with your favourite MySQL administration tool and look for the string you want to override.</li>\n	<li>Note the contents of the <code>name</code> column.</li>\n	<li>Edit this plugin&#8217;s code and navigate to the lines starting with “ADD <span class=\"caps\">YOUR</span> <span class=\"caps\">TEXT</span> <span class=\"caps\">HERE</span>”.</li>\n	<li>Add entries as you please, adhering to the two sample lines.</li>\n</ol>\n\n<h3>Additional information</h3>\n\n<p><span class=\"caps\">TBD</span>.</p>\n\n<h3>Licence</h3>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nfunction wet_babble()\n{\n\n	$custom_strings = array(\n	// ADD YOUR TEXT HERE\n	// Format: \'name_from_txp_lang\' => \'My very own text\',\n	\'this_is_just_a_sample_1\' 	=> \'Lorem ipsum dolor sit amend&hellip;\',\n	\'this_is_just_a_sample_2\' 	=> \'Awesome!\',\n	// STOP EDITING!\n	);\n\n	global $textarray;\n	$textarray = $custom_strings + $textarray;\n}\n\nif (@txpinterface == \'public\' || @txpinterface == \'admin\') wet_babble();\n', '\nfunction wet_babble()\n{\n\n	$custom_strings = array(\n	// ADD YOUR TEXT HERE\n	// Format: \'name_from_txp_lang\' => \'My very own text\',\n	\'this_is_just_a_sample_1\' 	=> \'Lorem ipsum dolor sit amend&hellip;\',\n	\'this_is_just_a_sample_2\' 	=> \'Awesome!\',\n	// STOP EDITING!\n	);\n\n	global $textarray;\n	$textarray = $custom_strings + $textarray;\n}\n\nif (@txpinterface == \'public\' || @txpinterface == \'admin\') wet_babble();\n', '53a656e403a7c57d40a3de11830916a4', '', '', 1, 5, 0),
('wet_haystack', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_haystack-textpattern-plugin', '0.6', 'Custom full text index builder', '<h3>Purpose</h3>\n\n<p>Textpattern&#8217;s full text index uses the articles’ body and title contents to find proper matches for site-internal searches.</p>\n\n<p><strong>wet_haystack</strong> allows site publishers to modify this default behaviour by adding additional article fields to the set of indexed content.</p>\n\n<h3>Usage</h3>\n\n<p>NB: This plugin is intended for Textpattern 4.2.0+. It will not work with earlier versions.  You need to acquire publisher privileges to change a site&#8217;s fulltext search settings with this plugin.</p>\n\n<h4>Building a custom fulltext index</h4>\n\n<ul>\n	<li>Go to the <a href=\"http://localhost/site1/?event=plugin_prefs.wet_haystack\" rel=\"nofollow\">wet_haystack options</a> panel.</li>\n	<li>Check all article fields you want to include in the full text index.</li>\n	<li>Hit “Save” and wait for the database to reindex you current site&#8217;s content. Depending on the number and length of your articles, this may take a while.</li>\n</ul>\n\n<p><strong>wet_haystack</strong> needs not be kept installed once the custom fulltext index has been created.</p>\n\n<h4>Removing an obsolete custom fulltext index</h4>\n\n<p><strong>wet_haystack</strong> needs one additional database index for its operation, which it continues to use as long as you modify Textpattern&#8217;s default fulltext index settings. This custom fulltext index will be deleted when the customised setting matches Textpattern&#8217;s default setting (i.e. “Title” and “Body” are the solely checked properties).</p>\n\n<h3>Additional information</h3>\n\n<p>Visit the <a href=\"http://awasteofwords.com/software/wet_haystack-textpattern-plugin\" rel=\"nofollow\">wet_haystack documentation page</a>.</p>\n\n<h3>Licence</h3>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nglobal $textarray;\n\n/**\n *  User-editable strings\n */\n$textarray[\'wet_haystack\'] = \'Fulltext Index\';\n$textarray[\'wet_haystack_select_columns\'] = \'Select searchable columns\';\n$textarray[\'wet_haystack_please_wait\'] = \'Please wait…\';\n\n// == STOP EDITING! ==\n\n// Hook into the admin side of highly privileged users\nadd_privs(\'wet_haystack\', \'1\');\nadd_privs(\'plugin_prefs.wet_haystack\', \'1\');\nregister_callback(\'wet_haystack_ui\', \'plugin_prefs.wet_haystack\');\nregister_callback(\'wet_haystack_style\', \'admin_side\', \'head_end\');\nregister_callback(\'wet_haystack_bhvr\', \'admin_side\', \'body_end\');\n\n/**\n * Admin side UI\n */\nfunction wet_haystack_ui($event, $step)\n{\n  	global $prefs;\n\n	// new fulltext index\'s key name\n	$wet_haystack_key_name = \'wet_haystack\';\n\n  	// default TXP fulltext index\n  	$default_cols = array(\'Title\', \'Body\');\n\n  	$customfields = array();\n  	$max_cf = get_pref(\'max_custom_fields\', 10);\n	for ($i = 1; $i <= $max_cf; $i++) {\n  		if (!empty($prefs[\'custom_\'.$i.\'_set\'])) {\n			$customfields[\'custom_\'.$i] = $prefs[\'custom_\'.$i.\'_set\'];\n		}\n	}\n\n  	// a map of db columns which may participate in a fulltext index, and their UI names.\n  	$columns = array(\n  		\'AuthorID\' 	=> \'login_name\',\n  		\'Title\' 	=> \'title\',\n  		\'Body\'		=> \'body\',\n  		\'Excerpt\'	=> \'excerpt\',\n  		\'Keywords\'	=> \'keywords\',\n  		\'Category1\'	=> \'category1\',\n  		\'Category2\'	=> \'category2\',\n  		\'Section\'	=> \'section\'\n  	) + $customfields;\n\n  	// controller: build new fulltext index if requested\n	$saved = false;\n	if (gps(\'save\')) {\n  		$new_cols = gps(\'col\');\n  		if (!empty($new_cols)) {\n  			$new_cols = array_keys($new_cols);\n	  		$current_cols = wet_haystacked_cols($wet_haystack_key_name);\n	  		if (!empty($current_cols)) safe_alter(\"textpattern\", \"DROP INDEX $wet_haystack_key_name\");\n\n	  		$doit = array_diff($new_cols, $default_cols) + array_diff($default_cols, $new_cols);\n	  		if (!empty($doit)) {\n	  			@set_time_limit(0);\n	  			safe_alter(\"textpattern\", \"ADD FULLTEXT $wet_haystack_key_name (`\" . join(\'`, `\', doSlash($new_cols)) .\"`)\");\n	  		}\n  			update_lastmod();\n	  		set_pref(\'searchable_article_fields\', join(\',\', $new_cols), \'publish\', 2);\n	  		$saved = true;\n  		}\n  	}\n\n  	// model: retrieve current fulltext index from database\n  	$indexed_cols = wet_haystacked_cols($wet_haystack_key_name);\n  	if (empty($indexed_cols)) $indexed_cols = $default_cols;\n\n	// view: show current fulltext index, request new columns\n  	pagetop(gTxt(\'wet_haystack\'), ($saved ? gTxt(\'preferences_saved\') : \'\'));\n\n	foreach ($columns as $column => $label) {\n		$checked = in_array($column, $indexed_cols);\n		$check[] = graf(checkbox(\"col[$column]\", 1, $checked, \'\', \"col-$column\").\"<label for=\'col-$column\'>\".gTxt($label).\"</label>\");\n	}\n\n	echo \"<div class=\'wet_modal\'></div>\".\n		form(\n		n.hed(gTxt(\'wet_haystack_select_columns\'), 3).\n		n.\'<div id=\"wet_haystack_columns\">\'. // sorry, no fieldset. could not make overflow: auto work on fieldsets\n		n.join(n, $check).\n		n.\'</div>\'.\n		n.eInput(\'plugin_prefs.wet_haystack\').\n		n.graf(\n			fInput(\'submit\', \'save\', gTxt(\'save\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'wet_haystack_save\').\n			href(gTxt(\'cancel\'), \'?event=plugin\', \' id=\"wet_haystack_cancel\"\'),\n			\' id=\"wet_haystack_buttons\"\'),\n        \'\', \'\', \'post\', \'wet_modal\'\n	);\n}\n\n/**\n *  Return array of currently indexed column names\n */\nfunction wet_haystacked_cols($key_name)\n{\n	$indexed_cols = array();\n	$indexes = safe_show(\'INDEX\', \'textpattern\');\n	foreach ($indexes as $index) {\n		if(($index[\'Index_type\'] == \'FULLTEXT\') && ($index[\'Key_name\'] == $key_name)) {\n			$indexed_cols[] = $index[\'Column_name\'];\n		}\n	}\n	return $indexed_cols;\n}\n\n/**\n * Beautfying...\n */\nfunction wet_haystack_style($event, $step)\n{\n	echo n.\'<style type=\"text/css\">\ndiv.wet_modal{background-color:black;opacity:0.2;position:absolute;top:0;left:0;width:100%;height:100%}\nform.wet_modal{z-index:1000;width:20em;position:absolute;top:30px;left:50%;margin-left:-10em;background-color:white;padding:20px;border:2px solid #fc3;}\nform.wet_modal h3{border-bottom: 1px solid #ddd;padding-bottom:2px}\n#wet_haystack_save.busy{color:#999;}\n#wet_haystack_cancel{margin-left: 1em;}\n#wet_haystack_buttons{border-top: 1px solid #ddd;padding-top:5px;margin-top:5px;}\n#wet_haystack_columns{border:none;padding:0;overflow:auto;max-height:300px;}\n</style>\n<!--[if gte IE 5]>\n<style type=\"text/css\">div.wet_modal{filter: alpha(opacity = 20);}</style>\n<![endif]-->\'\n	.n;\n}\n\nfunction wet_haystack_bhvr($event, $step)\n{\n	$script = <<<EOS\n\n<script type=\"text/javascript\">\n$(document).ready(function() {\n	$(\"input#wet_haystack_save\").click( function() {\n		$(this).attr(\"value\", \"%wet_haystack_please_wait%\");\n		$(this).addClass(\"busy\");\n		return true;\n	});\n});\n</script>\n\nEOS;\n\n	echo str_replace(\'%wet_haystack_please_wait%\', gTxt(\'wet_haystack_please_wait\'), $script);\n}\n', '\nglobal $textarray;\n\n/**\n *  User-editable strings\n */\n$textarray[\'wet_haystack\'] = \'Fulltext Index\';\n$textarray[\'wet_haystack_select_columns\'] = \'Select searchable columns\';\n$textarray[\'wet_haystack_please_wait\'] = \'Please wait…\';\n\n// == STOP EDITING! ==\n\n// Hook into the admin side of highly privileged users\nadd_privs(\'wet_haystack\', \'1\');\nadd_privs(\'plugin_prefs.wet_haystack\', \'1\');\nregister_callback(\'wet_haystack_ui\', \'plugin_prefs.wet_haystack\');\nregister_callback(\'wet_haystack_style\', \'admin_side\', \'head_end\');\nregister_callback(\'wet_haystack_bhvr\', \'admin_side\', \'body_end\');\n\n/**\n * Admin side UI\n */\nfunction wet_haystack_ui($event, $step)\n{\n  	global $prefs;\n\n	// new fulltext index\'s key name\n	$wet_haystack_key_name = \'wet_haystack\';\n\n  	// default TXP fulltext index\n  	$default_cols = array(\'Title\', \'Body\');\n\n  	$customfields = array();\n  	$max_cf = get_pref(\'max_custom_fields\', 10);\n	for ($i = 1; $i <= $max_cf; $i++) {\n  		if (!empty($prefs[\'custom_\'.$i.\'_set\'])) {\n			$customfields[\'custom_\'.$i] = $prefs[\'custom_\'.$i.\'_set\'];\n		}\n	}\n\n  	// a map of db columns which may participate in a fulltext index, and their UI names.\n  	$columns = array(\n  		\'AuthorID\' 	=> \'login_name\',\n  		\'Title\' 	=> \'title\',\n  		\'Body\'		=> \'body\',\n  		\'Excerpt\'	=> \'excerpt\',\n  		\'Keywords\'	=> \'keywords\',\n  		\'Category1\'	=> \'category1\',\n  		\'Category2\'	=> \'category2\',\n  		\'Section\'	=> \'section\'\n  	) + $customfields;\n\n  	// controller: build new fulltext index if requested\n	$saved = false;\n	if (gps(\'save\')) {\n  		$new_cols = gps(\'col\');\n  		if (!empty($new_cols)) {\n  			$new_cols = array_keys($new_cols);\n	  		$current_cols = wet_haystacked_cols($wet_haystack_key_name);\n	  		if (!empty($current_cols)) safe_alter(\"textpattern\", \"DROP INDEX $wet_haystack_key_name\");\n\n	  		$doit = array_diff($new_cols, $default_cols) + array_diff($default_cols, $new_cols);\n	  		if (!empty($doit)) {\n	  			@set_time_limit(0);\n	  			safe_alter(\"textpattern\", \"ADD FULLTEXT $wet_haystack_key_name (`\" . join(\'`, `\', doSlash($new_cols)) .\"`)\");\n	  		}\n  			update_lastmod();\n	  		set_pref(\'searchable_article_fields\', join(\',\', $new_cols), \'publish\', 2);\n	  		$saved = true;\n  		}\n  	}\n\n  	// model: retrieve current fulltext index from database\n  	$indexed_cols = wet_haystacked_cols($wet_haystack_key_name);\n  	if (empty($indexed_cols)) $indexed_cols = $default_cols;\n\n	// view: show current fulltext index, request new columns\n  	pagetop(gTxt(\'wet_haystack\'), ($saved ? gTxt(\'preferences_saved\') : \'\'));\n\n	foreach ($columns as $column => $label) {\n		$checked = in_array($column, $indexed_cols);\n		$check[] = graf(checkbox(\"col[$column]\", 1, $checked, \'\', \"col-$column\").\"<label for=\'col-$column\'>\".gTxt($label).\"</label>\");\n	}\n\n	echo \"<div class=\'wet_modal\'></div>\".\n		form(\n		n.hed(gTxt(\'wet_haystack_select_columns\'), 3).\n		n.\'<div id=\"wet_haystack_columns\">\'. // sorry, no fieldset. could not make overflow: auto work on fieldsets\n		n.join(n, $check).\n		n.\'</div>\'.\n		n.eInput(\'plugin_prefs.wet_haystack\').\n		n.graf(\n			fInput(\'submit\', \'save\', gTxt(\'save\'), \'smallerbox\', \'\', \'\', \'\', \'\', \'wet_haystack_save\').\n			href(gTxt(\'cancel\'), \'?event=plugin\', \' id=\"wet_haystack_cancel\"\'),\n			\' id=\"wet_haystack_buttons\"\'),\n        \'\', \'\', \'post\', \'wet_modal\'\n	);\n}\n\n/**\n *  Return array of currently indexed column names\n */\nfunction wet_haystacked_cols($key_name)\n{\n	$indexed_cols = array();\n	$indexes = safe_show(\'INDEX\', \'textpattern\');\n	foreach ($indexes as $index) {\n		if(($index[\'Index_type\'] == \'FULLTEXT\') && ($index[\'Key_name\'] == $key_name)) {\n			$indexed_cols[] = $index[\'Column_name\'];\n		}\n	}\n	return $indexed_cols;\n}\n\n/**\n * Beautfying...\n */\nfunction wet_haystack_style($event, $step)\n{\n	echo n.\'<style type=\"text/css\">\ndiv.wet_modal{background-color:black;opacity:0.2;position:absolute;top:0;left:0;width:100%;height:100%}\nform.wet_modal{z-index:1000;width:20em;position:absolute;top:30px;left:50%;margin-left:-10em;background-color:white;padding:20px;border:2px solid #fc3;}\nform.wet_modal h3{border-bottom: 1px solid #ddd;padding-bottom:2px}\n#wet_haystack_save.busy{color:#999;}\n#wet_haystack_cancel{margin-left: 1em;}\n#wet_haystack_buttons{border-top: 1px solid #ddd;padding-top:5px;margin-top:5px;}\n#wet_haystack_columns{border:none;padding:0;overflow:auto;max-height:300px;}\n</style>\n<!--[if gte IE 5]>\n<style type=\"text/css\">div.wet_modal{filter: alpha(opacity = 20);}</style>\n<![endif]-->\'\n	.n;\n}\n\nfunction wet_haystack_bhvr($event, $step)\n{\n	$script = <<<EOS\n\n<script type=\"text/javascript\">\n$(document).ready(function() {\n	$(\"input#wet_haystack_save\").click( function() {\n		$(this).attr(\"value\", \"%wet_haystack_please_wait%\");\n		$(this).addClass(\"busy\");\n		return true;\n	});\n});\n</script>\n\nEOS;\n\n	echo str_replace(\'%wet_haystack_please_wait%\', gTxt(\'wet_haystack_please_wait\'), $script);\n}\n', 'fcb9d1ddc245f94c78932cb9220ab88d', '', '', 3, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_parachute', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/help/wet_parachute', '1.0', 'Confirm dismissal of unsaved changes on the \"Write\" tab.', '<h3>A parachute for the unheeding author</h3>\n\n<p><strong>wet_parachute</strong> requests the author&#8217;s confirmation before it allows the closing of browser windows containing unsaved or modified entries.</p>\n\n<h4>Usage:</h4>\n\n<ol>\n	<li>Textpattern 4.5.3 is required, wet_parachute will <em>not</em> work with any prior version.</li>\n	<li>Will not work with Opera due to their lack of support for the onbeforeunload event.</li>\n	<li>Install, enable, enjoy &#8211; no configuration required.</li>\n</ol>\n\n<p>Visit the <a href=\"http://awasteofwords.com/help/wet_parachute\" rel=\"nofollow\">plugin help page</a> for more detailled instructions.</p>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nswitch(gps(\'wet_rsrc\')) {\n	case \'parachute_js\':\n		wet_parachute::js();\n		break;\n	default:\n		break;\n}\n\nclass wet_parachute {\n	function __construct()\n	{\n		global $event;\n		if (in_array($event, array(\'article\'))) {\n			register_callback(array(__CLASS__, \'resource\'), \'admin_side\', \'head_end\');\n		}\n		register_callback(array(__CLASS__, \'fingerprint_article\'), \'article\', \'edit\');\n		register_callback(array(__CLASS__, \'fingerprint_article\'), \'article\', \'create\');\n	}\n\n	static function fingerprint_article($event, $step)\n	{\n		$vars = array(\n			\'Title\',\'Body\',\'Excerpt\',\n			\'Image\',\n			\'Keywords\',\n			\'url_title\',\n			\'custom_1\',\'custom_2\',\'custom_3\',\'custom_4\',\'custom_5\',\n			\'custom_6\',\'custom_7\',\'custom_8\',\'custom_9\',\'custom_10\'\n		);\n\n		if (!empty($GLOBALS[\'ID\'])) {\n			$ID = $GLOBALS[\'ID\']; 	// newly posted article?\n		} else {\n			$ID = gps(\'ID\');		// old article from the database?\n		}\n\n		$fingerprint = \'\';\n\n		// existing article?\n		if (!empty($ID)) {\n			$ID = assert_int($ID);\n			$a = safe_row(join(\",\", $vars), \'textpattern\', \"id=$ID\");\n\n			// \'Keywords\' field gets special treatment; @see ../include/txp_article.php\n			if (isset($a[\'Keywords\'])) {\n				$a[\'Keywords\'] = str_replace(\',\' ,\', \', $a[\'Keywords\']);\n			}\n\n			foreach ($vars as $v) {\n				if (isset($a[$v])) {\n					// concatenate all fields while de-entitifying ampersands\n					$fingerprint .= str_replace(array(\'&#38;\', \'&amp;\'), \'&\', $a[$v]);\n				}\n			}\n			// Equalize JS/PHP new-line differences\n			$fingerprint = str_replace(\"\\r\\n\", \"\\n\", $fingerprint);\n		}\n\n		$fingerprint = escape_js(md5($fingerprint));\n\n		$vars = join(\"\',\'\", array(\n			\'#title\',\'#body\',\'#excerpt\',\n			\'#article-image\',\n			\'#keywords\',\n			\'#url-title\',\n			\'#custom-1\',\'#custom-2\',\'#custom-3\',\'#custom-4\',\'#custom-5\',\n			\'#custom-6\',\'#custom-7\',\'#custom-8\',\'#custom-9\',\'#custom-10\'));\n\n		$disarms = \'#view_modes a, :submit\';\n\n		$js = <<<SGJS\nwet_parachute.fingerprint = \'{$fingerprint}\';\nwet_parachute.vars = [\'{$vars}\'];\nwet_parachute.armed = true;\nwet_parachute.disarms = \'{$disarms}\';\nSGJS;\n		echo http_accept_format(\'html\') ? script_js($js) : $js;\n	}\n\n	static function resource()\n	{\n		gTxtScript(\'are_you_sure\');\n		echo n.\"<script type=\'text/javascript\' src=\'?wet_rsrc=parachute_js\'></script>\".n;\n	}\n\n	static function js()\n	{\n		ob_clean();\n		header(\"Content-Type: text/javascript; charset=utf-8\");\n		header(\"Cache-Control: public\");\n		header(\"Expires: \".date(\"r\", time() + 3600));\n		echo <<<js\n/**\n * wet_parachute: A Textpattern lifesaver plugin\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/help/wet_parachute\n */\n\nvar wet_parachute = {\n    vars: [],\n    disarms: [],\n    fingerprint: 0,\n    armed: true,\n\n    // hook: set event handlers\n    hook: function() {\n        // capable browser?\n        if (!\'onbeforeunload\' in window) return;\n\n        wet_parachute.armed = true;\n\n        // Cannot use jQuery for binding a \'beforeunload\' event. @see http://dev.jquery.com/ticket/1365\n        window.onbeforeunload = function(e) {\n            // on a observable page/view with vars we want to fingerprint?\n            var observable = false;\n            jQuery.each(wet_parachute.vars, function(index, elm) {\n                if ($(elm).length > 0) {\n                    observable = true;\n                }\n            });\n            if (!observable) return;\n\n            // notify of unsaved changes\n            if (wet_parachute.armed && wet_parachute.isdirty()) {\n                return textpattern.gTxt(\'are_you_sure\');\n            }\n        };\n\n        // Disarm when specific elements gain focus/are clicked\n        $(document).on(\n            \'click focus\',\n            wet_parachute.disarms,\n            function() {\n                wet_parachute.armed = false;\n            }\n		);\n        $(document).on(\n            \'blur\',\n            wet_parachute.disarms,\n            function() {\n                wet_parachute.armed = true;\n            }\n		);\n    },\n\n    // compare fingerprint of selected article properties to their original values\n    isdirty: function() {\n        // concatenate all values\n        var tmp = \'\';\n        jQuery.each(wet_parachute.vars, function(index, elm) {\n            if ($(elm).length > 0) {\n                tmp += $(elm).val();\n            }\n        });\n        return this.md5(tmp) != wet_parachute.fingerprint;\n    },\n\n    md5: function (string) {\n        /**\n         *\n         *  MD5 (Message-Digest Algorithm)\n         *  http://www.webtoolkit.info/\n         *\n         **/\n        function RotateLeft(lValue, iShiftBits) {\n            return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));\n        }\n\n        function AddUnsigned(lX,lY) {\n            var lX4,lY4,lX8,lY8,lResult;\n            lX8 = (lX & 0x80000000);\n            lY8 = (lY & 0x80000000);\n            lX4 = (lX & 0x40000000);\n            lY4 = (lY & 0x40000000);\n            lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);\n            if (lX4 & lY4) {\n                return (lResult ^ 0x80000000 ^ lX8 ^ lY8);\n            }\n            if (lX4 | lY4) {\n                if (lResult & 0x40000000) {\n                    return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);\n                } else {\n                    return (lResult ^ 0x40000000 ^ lX8 ^ lY8);\n                }\n            } else {\n                return (lResult ^ lX8 ^ lY8);\n            }\n        }\n\n        function F(x,y,z) { return (x & y) | ((~x) & z); }\n        function G(x,y,z) { return (x & z) | (y & (~z)); }\n        function H(x,y,z) { return (x ^ y ^ z); }\n        function I(x,y,z) { return (y ^ (x | (~z))); }\n\n        function FF(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function GG(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function HH(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function II(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function ConvertToWordArray(string) {\n            var lWordCount;\n            var lMessageLength = string.length;\n            var lNumberOfWords_temp1=lMessageLength + 8;\n            var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;\n            var lNumberOfWords = (lNumberOfWords_temp2+1)*16;\n            var lWordArray=Array(lNumberOfWords-1);\n            var lBytePosition = 0;\n            var lByteCount = 0;\n            while ( lByteCount < lMessageLength ) {\n                lWordCount = (lByteCount-(lByteCount % 4))/4;\n                lBytePosition = (lByteCount % 4)*8;\n                lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));\n                lByteCount++;\n            }\n            lWordCount = (lByteCount-(lByteCount % 4))/4;\n            lBytePosition = (lByteCount % 4)*8;\n            lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);\n            lWordArray[lNumberOfWords-2] = lMessageLength<<3;\n            lWordArray[lNumberOfWords-1] = lMessageLength>>>29;\n            return lWordArray;\n        };\n\n        function WordToHex(lValue) {\n            var WordToHexValue=\"\",WordToHexValue_temp=\"\",lByte,lCount;\n            for (lCount = 0;lCount<=3;lCount++) {\n                lByte = (lValue>>>(lCount*8)) & 255;\n                WordToHexValue_temp = \"0\" + lByte.toString(16);\n                WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);\n            }\n            return WordToHexValue;\n        };\n\n        function Utf8Encode(string) {\n            var re_rn = new RegExp(\'\\\\r\\\\n\', \'g\'); // avoid literal CR and LF, as it would be parsed by PHP\'S HEREDOC\n            string = string.replace(re_rn, unescape(\'%0A\'));\n            var utftext = \"\";\n\n            for (var n = 0; n < string.length; n++) {\n\n                var c = string.charCodeAt(n);\n\n                if (c < 128) {\n                    utftext += String.fromCharCode(c);\n                }\n                else if((c > 127) && (c < 2048)) {\n                    utftext += String.fromCharCode((c >> 6) | 192);\n                    utftext += String.fromCharCode((c & 63) | 128);\n                }\n                else {\n                    utftext += String.fromCharCode((c >> 12) | 224);\n                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n                    utftext += String.fromCharCode((c & 63) | 128);\n                }\n\n            }\n\n            return utftext;\n        };\n\n        var x=Array();\n        var k,AA,BB,CC,DD,a,b,c,d;\n        var S11=7, S12=12, S13=17, S14=22;\n        var S21=5, S22=9 , S23=14, S24=20;\n        var S31=4, S32=11, S33=16, S34=23;\n        var S41=6, S42=10, S43=15, S44=21;\n\n        string = Utf8Encode(string);\n\n        x = ConvertToWordArray(string);\n\n        a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;\n\n        for (k=0;k<x.length;k+=16) {\n            AA=a; BB=b; CC=c; DD=d;\n            a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);\n            d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);\n            c=FF(c,d,a,b,x[k+2], S13,0x242070DB);\n            b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);\n            a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);\n            d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);\n            c=FF(c,d,a,b,x[k+6], S13,0xA8304613);\n            b=FF(b,c,d,a,x[k+7], S14,0xFD469501);\n            a=FF(a,b,c,d,x[k+8], S11,0x698098D8);\n            d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);\n            c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);\n            b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);\n            a=FF(a,b,c,d,x[k+12],S11,0x6B901122);\n            d=FF(d,a,b,c,x[k+13],S12,0xFD987193);\n            c=FF(c,d,a,b,x[k+14],S13,0xA679438E);\n            b=FF(b,c,d,a,x[k+15],S14,0x49B40821);\n            a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);\n            d=GG(d,a,b,c,x[k+6], S22,0xC040B340);\n            c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);\n            b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);\n            a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);\n            d=GG(d,a,b,c,x[k+10],S22,0x2441453);\n            c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);\n            b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);\n            a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);\n            d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);\n            c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);\n            b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);\n            a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);\n            d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);\n            c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);\n            b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);\n            a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);\n            d=HH(d,a,b,c,x[k+8], S32,0x8771F681);\n            c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);\n            b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);\n            a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);\n            d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);\n            c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);\n            b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);\n            a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);\n            d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);\n            c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);\n            b=HH(b,c,d,a,x[k+6], S34,0x4881D05);\n            a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);\n            d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);\n            c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);\n            b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);\n            a=II(a,b,c,d,x[k+0], S41,0xF4292244);\n            d=II(d,a,b,c,x[k+7], S42,0x432AFF97);\n            c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);\n            b=II(b,c,d,a,x[k+5], S44,0xFC93A039);\n            a=II(a,b,c,d,x[k+12],S41,0x655B59C3);\n            d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);\n            c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);\n            b=II(b,c,d,a,x[k+1], S44,0x85845DD1);\n            a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);\n            d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);\n            c=II(c,d,a,b,x[k+6], S43,0xA3014314);\n            b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);\n            a=II(a,b,c,d,x[k+4], S41,0xF7537E82);\n            d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);\n            c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);\n            b=II(b,c,d,a,x[k+9], S44,0xEB86D391);\n            a=AddUnsigned(a,AA);\n            b=AddUnsigned(b,BB);\n            c=AddUnsigned(c,CC);\n            d=AddUnsigned(d,DD);\n        }\n\n        var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);\n\n        return temp.toLowerCase();\n    }\n};\n\n$(document).ready(function() {\n    wet_parachute.hook();\n});\njs;\n	exit();\n	}\n}\n\nnew wet_parachute;', '\nswitch(gps(\'wet_rsrc\')) {\n	case \'parachute_js\':\n		wet_parachute::js();\n		break;\n	default:\n		break;\n}\n\nclass wet_parachute {\n	function __construct()\n	{\n		global $event;\n		if (in_array($event, array(\'article\'))) {\n			register_callback(array(__CLASS__, \'resource\'), \'admin_side\', \'head_end\');\n		}\n		register_callback(array(__CLASS__, \'fingerprint_article\'), \'article\', \'edit\');\n		register_callback(array(__CLASS__, \'fingerprint_article\'), \'article\', \'create\');\n	}\n\n	static function fingerprint_article($event, $step)\n	{\n		$vars = array(\n			\'Title\',\'Body\',\'Excerpt\',\n			\'Image\',\n			\'Keywords\',\n			\'url_title\',\n			\'custom_1\',\'custom_2\',\'custom_3\',\'custom_4\',\'custom_5\',\n			\'custom_6\',\'custom_7\',\'custom_8\',\'custom_9\',\'custom_10\'\n		);\n\n		if (!empty($GLOBALS[\'ID\'])) {\n			$ID = $GLOBALS[\'ID\']; 	// newly posted article?\n		} else {\n			$ID = gps(\'ID\');		// old article from the database?\n		}\n\n		$fingerprint = \'\';\n\n		// existing article?\n		if (!empty($ID)) {\n			$ID = assert_int($ID);\n			$a = safe_row(join(\",\", $vars), \'textpattern\', \"id=$ID\");\n\n			// \'Keywords\' field gets special treatment; @see ../include/txp_article.php\n			if (isset($a[\'Keywords\'])) {\n				$a[\'Keywords\'] = str_replace(\',\' ,\', \', $a[\'Keywords\']);\n			}\n\n			foreach ($vars as $v) {\n				if (isset($a[$v])) {\n					// concatenate all fields while de-entitifying ampersands\n					$fingerprint .= str_replace(array(\'&#38;\', \'&amp;\'), \'&\', $a[$v]);\n				}\n			}\n			// Equalize JS/PHP new-line differences\n			$fingerprint = str_replace(\"\\r\\n\", \"\\n\", $fingerprint);\n		}\n\n		$fingerprint = escape_js(md5($fingerprint));\n\n		$vars = join(\"\',\'\", array(\n			\'#title\',\'#body\',\'#excerpt\',\n			\'#article-image\',\n			\'#keywords\',\n			\'#url-title\',\n			\'#custom-1\',\'#custom-2\',\'#custom-3\',\'#custom-4\',\'#custom-5\',\n			\'#custom-6\',\'#custom-7\',\'#custom-8\',\'#custom-9\',\'#custom-10\'));\n\n		$disarms = \'#view_modes a, :submit\';\n\n		$js = <<<SGJS\nwet_parachute.fingerprint = \'{$fingerprint}\';\nwet_parachute.vars = [\'{$vars}\'];\nwet_parachute.armed = true;\nwet_parachute.disarms = \'{$disarms}\';\nSGJS;\n		echo http_accept_format(\'html\') ? script_js($js) : $js;\n	}\n\n	static function resource()\n	{\n		gTxtScript(\'are_you_sure\');\n		echo n.\"<script type=\'text/javascript\' src=\'?wet_rsrc=parachute_js\'></script>\".n;\n	}\n\n	static function js()\n	{\n		ob_clean();\n		header(\"Content-Type: text/javascript; charset=utf-8\");\n		header(\"Cache-Control: public\");\n		header(\"Expires: \".date(\"r\", time() + 3600));\n		echo <<<js\n/**\n * wet_parachute: A Textpattern lifesaver plugin\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/help/wet_parachute\n */\n\nvar wet_parachute = {\n    vars: [],\n    disarms: [],\n    fingerprint: 0,\n    armed: true,\n\n    // hook: set event handlers\n    hook: function() {\n        // capable browser?\n        if (!\'onbeforeunload\' in window) return;\n\n        wet_parachute.armed = true;\n\n        // Cannot use jQuery for binding a \'beforeunload\' event. @see http://dev.jquery.com/ticket/1365\n        window.onbeforeunload = function(e) {\n            // on a observable page/view with vars we want to fingerprint?\n            var observable = false;\n            jQuery.each(wet_parachute.vars, function(index, elm) {\n                if ($(elm).length > 0) {\n                    observable = true;\n                }\n            });\n            if (!observable) return;\n\n            // notify of unsaved changes\n            if (wet_parachute.armed && wet_parachute.isdirty()) {\n                return textpattern.gTxt(\'are_you_sure\');\n            }\n        };\n\n        // Disarm when specific elements gain focus/are clicked\n        $(document).on(\n            \'click focus\',\n            wet_parachute.disarms,\n            function() {\n                wet_parachute.armed = false;\n            }\n		);\n        $(document).on(\n            \'blur\',\n            wet_parachute.disarms,\n            function() {\n                wet_parachute.armed = true;\n            }\n		);\n    },\n\n    // compare fingerprint of selected article properties to their original values\n    isdirty: function() {\n        // concatenate all values\n        var tmp = \'\';\n        jQuery.each(wet_parachute.vars, function(index, elm) {\n            if ($(elm).length > 0) {\n                tmp += $(elm).val();\n            }\n        });\n        return this.md5(tmp) != wet_parachute.fingerprint;\n    },\n\n    md5: function (string) {\n        /**\n         *\n         *  MD5 (Message-Digest Algorithm)\n         *  http://www.webtoolkit.info/\n         *\n         **/\n        function RotateLeft(lValue, iShiftBits) {\n            return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));\n        }\n\n        function AddUnsigned(lX,lY) {\n            var lX4,lY4,lX8,lY8,lResult;\n            lX8 = (lX & 0x80000000);\n            lY8 = (lY & 0x80000000);\n            lX4 = (lX & 0x40000000);\n            lY4 = (lY & 0x40000000);\n            lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);\n            if (lX4 & lY4) {\n                return (lResult ^ 0x80000000 ^ lX8 ^ lY8);\n            }\n            if (lX4 | lY4) {\n                if (lResult & 0x40000000) {\n                    return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);\n                } else {\n                    return (lResult ^ 0x40000000 ^ lX8 ^ lY8);\n                }\n            } else {\n                return (lResult ^ lX8 ^ lY8);\n            }\n        }\n\n        function F(x,y,z) { return (x & y) | ((~x) & z); }\n        function G(x,y,z) { return (x & z) | (y & (~z)); }\n        function H(x,y,z) { return (x ^ y ^ z); }\n        function I(x,y,z) { return (y ^ (x | (~z))); }\n\n        function FF(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function GG(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function HH(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function II(a,b,c,d,x,s,ac) {\n            a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));\n            return AddUnsigned(RotateLeft(a, s), b);\n        };\n\n        function ConvertToWordArray(string) {\n            var lWordCount;\n            var lMessageLength = string.length;\n            var lNumberOfWords_temp1=lMessageLength + 8;\n            var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;\n            var lNumberOfWords = (lNumberOfWords_temp2+1)*16;\n            var lWordArray=Array(lNumberOfWords-1);\n            var lBytePosition = 0;\n            var lByteCount = 0;\n            while ( lByteCount < lMessageLength ) {\n                lWordCount = (lByteCount-(lByteCount % 4))/4;\n                lBytePosition = (lByteCount % 4)*8;\n                lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));\n                lByteCount++;\n            }\n            lWordCount = (lByteCount-(lByteCount % 4))/4;\n            lBytePosition = (lByteCount % 4)*8;\n            lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);\n            lWordArray[lNumberOfWords-2] = lMessageLength<<3;\n            lWordArray[lNumberOfWords-1] = lMessageLength>>>29;\n            return lWordArray;\n        };\n\n        function WordToHex(lValue) {\n            var WordToHexValue=\"\",WordToHexValue_temp=\"\",lByte,lCount;\n            for (lCount = 0;lCount<=3;lCount++) {\n                lByte = (lValue>>>(lCount*8)) & 255;\n                WordToHexValue_temp = \"0\" + lByte.toString(16);\n                WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);\n            }\n            return WordToHexValue;\n        };\n\n        function Utf8Encode(string) {\n            var re_rn = new RegExp(\'\\\\r\\\\n\', \'g\'); // avoid literal CR and LF, as it would be parsed by PHP\'S HEREDOC\n            string = string.replace(re_rn, unescape(\'%0A\'));\n            var utftext = \"\";\n\n            for (var n = 0; n < string.length; n++) {\n\n                var c = string.charCodeAt(n);\n\n                if (c < 128) {\n                    utftext += String.fromCharCode(c);\n                }\n                else if((c > 127) && (c < 2048)) {\n                    utftext += String.fromCharCode((c >> 6) | 192);\n                    utftext += String.fromCharCode((c & 63) | 128);\n                }\n                else {\n                    utftext += String.fromCharCode((c >> 12) | 224);\n                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n                    utftext += String.fromCharCode((c & 63) | 128);\n                }\n\n            }\n\n            return utftext;\n        };\n\n        var x=Array();\n        var k,AA,BB,CC,DD,a,b,c,d;\n        var S11=7, S12=12, S13=17, S14=22;\n        var S21=5, S22=9 , S23=14, S24=20;\n        var S31=4, S32=11, S33=16, S34=23;\n        var S41=6, S42=10, S43=15, S44=21;\n\n        string = Utf8Encode(string);\n\n        x = ConvertToWordArray(string);\n\n        a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;\n\n        for (k=0;k<x.length;k+=16) {\n            AA=a; BB=b; CC=c; DD=d;\n            a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);\n            d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);\n            c=FF(c,d,a,b,x[k+2], S13,0x242070DB);\n            b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);\n            a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);\n            d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);\n            c=FF(c,d,a,b,x[k+6], S13,0xA8304613);\n            b=FF(b,c,d,a,x[k+7], S14,0xFD469501);\n            a=FF(a,b,c,d,x[k+8], S11,0x698098D8);\n            d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);\n            c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);\n            b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);\n            a=FF(a,b,c,d,x[k+12],S11,0x6B901122);\n            d=FF(d,a,b,c,x[k+13],S12,0xFD987193);\n            c=FF(c,d,a,b,x[k+14],S13,0xA679438E);\n            b=FF(b,c,d,a,x[k+15],S14,0x49B40821);\n            a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);\n            d=GG(d,a,b,c,x[k+6], S22,0xC040B340);\n            c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);\n            b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);\n            a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);\n            d=GG(d,a,b,c,x[k+10],S22,0x2441453);\n            c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);\n            b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);\n            a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);\n            d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);\n            c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);\n            b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);\n            a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);\n            d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);\n            c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);\n            b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);\n            a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);\n            d=HH(d,a,b,c,x[k+8], S32,0x8771F681);\n            c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);\n            b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);\n            a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);\n            d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);\n            c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);\n            b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);\n            a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);\n            d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);\n            c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);\n            b=HH(b,c,d,a,x[k+6], S34,0x4881D05);\n            a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);\n            d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);\n            c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);\n            b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);\n            a=II(a,b,c,d,x[k+0], S41,0xF4292244);\n            d=II(d,a,b,c,x[k+7], S42,0x432AFF97);\n            c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);\n            b=II(b,c,d,a,x[k+5], S44,0xFC93A039);\n            a=II(a,b,c,d,x[k+12],S41,0x655B59C3);\n            d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);\n            c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);\n            b=II(b,c,d,a,x[k+1], S44,0x85845DD1);\n            a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);\n            d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);\n            c=II(c,d,a,b,x[k+6], S43,0xA3014314);\n            b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);\n            a=II(a,b,c,d,x[k+4], S41,0xF7537E82);\n            d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);\n            c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);\n            b=II(b,c,d,a,x[k+9], S44,0xEB86D391);\n            a=AddUnsigned(a,AA);\n            b=AddUnsigned(b,BB);\n            c=AddUnsigned(c,CC);\n            d=AddUnsigned(d,DD);\n        }\n\n        var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);\n\n        return temp.toLowerCase();\n    }\n};\n\n$(document).ready(function() {\n    wet_parachute.hook();\n});\njs;\n	exit();\n	}\n}\n\nnew wet_parachute;', 'f466aff4a62d053edd2018268ce21acd', '', '', 4, 5, 0),
('wet_commentmagic', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_commentmagic-textpattern-plugin', '0.5', 'A text matching and replacing filter for comment messages.', '<p><strong>wet_commentmagic</strong> searches for certain text patterns inside comment messages and replaces them with others. A basic, expandable set of rules replaces web addresses with clickable links.</p>\n\n<p>Add to the $rules array to please your desires.</p>', '\n// Apply text replacment when a comment is previewed\nif(ps(\'preview\')) {\n	wet_commentmagic();\n}\n\nfunction wet_commentmagic()\n{\n	// Limit maximum character count of hyperlinked text derived from URLs\n	$truncate_urls_at = 20;\n\n	// These are the replacement rules: Expand to your liking\n	// Caveat: Replacement is textiled later on!\n    $rules = Array (\n    // detect, autolink and truncate URLs (www.*, http://*, https://*, ftp://*)\n    \'/(\\s|^)(www\\.[\\w.\\/?#%=\\+]+)(\\s|$)*?/ieu\' => \"\'$1\\\"\'.wet_cm_truncate(\'$2\', $truncate_urls_at).\'\\\":http://$2$3\'\",\n    \'/(\\s|^)(https?:\\/\\/|ftp:\\/\\/)([\\w.\\/?#%=\\+]+)(\\s|$)*?/ieu\' => \"\'$1\\\"\'.wet_cm_truncate(\'$3\', $truncate_urls_at).\'\\\":$2$3$4\'\"\n    );\n\n    if(load_plugin(\'wet_foullanguage\')) {\n    	$rules = array_merge($rules, wet_foullanguage());\n    }\n\n    $needles = array_keys($rules);\n    $pins = array_values($rules);\n\n	$c = (getComment());\n	// The comment message lands as a salted $_POST member upon second preview\n	// @see publish/comment.php\n    if(empty($c[\'secret\'])) {\n    	$msg = \'message\';\n    } else {\n    	$msg = md5(\'message\'.$c[\'secret\']);\n    }\n\n	// apply the magic\n    $_POST[$msg] = preg_replace($needles, $pins, $_POST[$msg]);\n}\n\nfunction wet_cm_truncate($what, $max)\n{\n	return(strlen(utf8_decode($what)) > $max) ?\n		preg_replace(\'/^(.{0,\'.$max.\'}).*$/su\', \'$1\', $what).\'...\' : $what;\n}\n', '\n// Apply text replacment when a comment is previewed\nif(ps(\'preview\')) {\n	wet_commentmagic();\n}\n\nfunction wet_commentmagic()\n{\n	// Limit maximum character count of hyperlinked text derived from URLs\n	$truncate_urls_at = 20;\n\n	// These are the replacement rules: Expand to your liking\n	// Caveat: Replacement is textiled later on!\n    $rules = Array (\n    // detect, autolink and truncate URLs (www.*, http://*, https://*, ftp://*)\n    \'/(\\s|^)(www\\.[\\w.\\/?#%=\\+]+)(\\s|$)*?/ieu\' => \"\'$1\\\"\'.wet_cm_truncate(\'$2\', $truncate_urls_at).\'\\\":http://$2$3\'\",\n    \'/(\\s|^)(https?:\\/\\/|ftp:\\/\\/)([\\w.\\/?#%=\\+]+)(\\s|$)*?/ieu\' => \"\'$1\\\"\'.wet_cm_truncate(\'$3\', $truncate_urls_at).\'\\\":$2$3$4\'\"\n    );\n\n    if(load_plugin(\'wet_foullanguage\')) {\n    	$rules = array_merge($rules, wet_foullanguage());\n    }\n\n    $needles = array_keys($rules);\n    $pins = array_values($rules);\n\n	$c = (getComment());\n	// The comment message lands as a salted $_POST member upon second preview\n	// @see publish/comment.php\n    if(empty($c[\'secret\'])) {\n    	$msg = \'message\';\n    } else {\n    	$msg = md5(\'message\'.$c[\'secret\']);\n    }\n\n	// apply the magic\n    $_POST[$msg] = preg_replace($needles, $pins, $_POST[$msg]);\n}\n\nfunction wet_cm_truncate($what, $max)\n{\n	return(strlen(utf8_decode($what)) > $max) ?\n		preg_replace(\'/^(.{0,\'.$max.\'}).*$/su\', \'$1\', $what).\'...\' : $what;\n}\n', '874b2cd02d93c0642dba0970aaaa72d5', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_snitch', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_snitch-textpattern-plugin', '0.3', 'Secret feed for unpublished articles', '<h3>Purpose</h3>\n\n<p><strong>wet_snitch</strong> publishes a private Atom feed at a randomized “secret” <span class=\"caps\">URL</span> which contains articles either posted into the future or resting in a “pending” state. Authors and publishers of co-authored sites would share this secret <span class=\"caps\">URL</span> to stay current about each other&#8217;s articles while they are still hidden from the public. NB: It is crucial to keep the the “secret” <span class=\"caps\">URL</span> out of public view, hide it from search engine bots, and the like.</p>\n\n<h3>Usage</h3>\n\n<p>The secret feed <span class=\"caps\">URL</span> is revealed on this <a href=\"http://localhost/site1/?event=wet_snitch\" rel=\"nofollow\">administration tab</a> (requires publisher privileges to enter). If you ever wanted to change the secret, hit the button and share the new feed link with your companions.</p>\n\n<h3>Additional information</h3>\n\n<p>Visit the <a href=\"http://awasteofwords.com/software/wet_snitch-textpattern-plugin\" rel=\"nofollow\">wet_snitch documentation page</a>.</p>\n\n<h3>Licence</h3>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nglobal $textarray;\n\n/**\n *  User-editable strings\n */\n$textarray[\'wet_snitch\'] = \'Snitch\';\n$textarray[\'wet_snitch_feed_link\'] = \'Feed link\';\n$textarray[\'wet_snitch_new_secret\'] = \'New link\';\n\n// == STOP EDITING! ==\n\n// Hook into the admin side of highly privileged users\nif (@txpinterface == \'admin\') {\n	add_privs(\'wet_snitch\', \'1\');\n	register_tab(\'admin\', \'wet_snitch\', gTxt(\'wet_snitch\'));\n	register_callback(\'wet_snitch_ui\', \'wet_snitch\');\n}\n\n// Hook into the public ATOM feed URL\nif (@txpinterface == \'public\') {\n	register_callback(\'wet_snitch_feed\', \'atom_head\');\n}\n\n/**\n * Admin side UI\n */\nfunction wet_snitch_ui($event, $step)\n{\n  	global $prefs;\n	$secret = (isset($prefs[\'wet_snitch\']) ? $prefs[\'wet_snitch\'] : substr(md5(rand()), 0, 9));\n  	$title = (isset($prefs[\'wet_snitch_title\']) ? $prefs[\'wet_snitch_title\'] : $prefs[\'sitename\'] . \" - private feed\");\n\n	pagetop(gTxt(\'wet_snitch\'));\n\n	$url = pagelinkurl(array(\n			\'atom\'    => \'1\',\n			\'section\'  => \'\',\n			\'category\' => \'\',\n			\'limit\'    => \'\'\n		));\n\n	if (gps(\'new\')) {\n  		$secret = substr(md5(rand()), 0, 9);\n  	}\n  	set_pref(\'wet_snitch\', $secret, \'publish\', 2);\n\n	if (gps(\'save\')) {\n  		$title = gps(\'title\');\n  	}\n  	set_pref(\'wet_snitch_title\', $title, \'publish\', 2);\n\n  	echo form(\n		n.eInput(\'wet_snitch\').\n		n.startTable(\'edit\').\n			tr(\n				td(\'<label for=\"title\">\'.gTxt(\'title\').\'</label>\').\n				td(fInput(\'text\', \'title\', $title, \'\', \'\', \'\', \'\', \'\', \'title\')).\n				td(fInput(\'submit\', \'save\', gTxt(\'save\'), \'smallerbox\'))\n			).\n			tr(\n				td(gTxt(\'wet_snitch_feed_link\')).\n				td(\'<code><a href=\'.$url.\'?wet_snitch=\'.$secret.\'>\'.$secret.\'</a></code>\').\n				td(fInput(\'submit\', \'new\', gTxt(\'wet_snitch_new_secret\'), \'smallerbox\'))\n			).\n			endTable()\n		);\n}\n\n/**\n * Custom ATOM feed, roughly ripped from publish/atom.php\n */\nfunction wet_snitch_feed($event, $step)\n{\n	global $prefs, $thisarticle;\n	extract($prefs);\n\n	// Check prerequisites: Secret preference set and present as an URL-var\n	if (isset($wet_snitch)) {\n		$do_feed = gps(\'wet_snitch\');\n		if (empty($do_feed))\n			return;\n		else {\n			if($do_feed != $wet_snitch) txp_die(\'\', \'404\');\n\n			extract(doSlash(gpsa(array(\'category\',\'section\',\'limit\',\'area\'))));\n\n			$last = fetch(\'unix_timestamp(val)\',\'txp_prefs\',\'name\',\'lastmod\');\n\n			if(empty($wet_snitch_title)) {\n				$sitename .= ($section) ? \' - \'.fetch_section_title($section) : \'\';\n				$sitename .= ($category) ? \' - \'.fetch_category_title($category) : \'\';\n				$sitename .= \" - private feed\";\n			} else {\n				$sitename = $wet_snitch_title;\n			}\n\n			$pub = safe_row(\"RealName, email\", \"txp_users\", \"privs=1\");\n\n			$out[] = tag(htmlspecialchars($sitename),\'title\',t_text);\n			$out[] = tag(htmlspecialchars($site_slogan),\'subtitle\',t_text);\n			$out[] = \'<link\'.r_relself.\' href=\"\'.pagelinkurl(array(\'atom\'=>1,\'area\'=>$area,\'section\'=>$section,\'category\'=>$category,\'limit\'=>$limit)).\'\" />\';\n			$out[] = \'<link\'.r_relalt.t_texthtml.\' href=\"\'.hu.\'\" />\';\n			$articles = array();\n\n			//Atom feeds with mail or domain name\n			$dn = explode(\'/\',$siteurl);\n			$mail_or_domain = ($use_mail_on_feeds_id)? eE($blog_mail_uid):$dn[0];\n			$out[] = tag(\'tag:\'.$mail_or_domain.\',\'.$blog_time_uid.\':\'.$blog_uid.(($section)? \'/\'.$section:\'\').(($category)? \'/\'.$category:\'\'),\'id\');\n\n			$out[] = tag(\'Textpattern\',\'generator\',\n				\' uri=\"http://textpattern.com/\" version=\"\'.$version.\'\"\');\n			$out[] = tag(safe_strftime(\"w3cdtf\",$last),\'updated\');\n\n\n			$auth[] = tag($pub[\'RealName\'],\'name\');\n			$auth[] = ($include_email_atom) ? tag(eE($pub[\'email\']),\'email\') : \'\';\n			$auth[] = tag(hu,\'uri\');\n\n			$out[] = tag(n.t.t.join(n.t.t,$auth).n,\'author\');\n\n			$limit = ($limit) ? $limit : $rss_how_many;\n			$limit = intval(min($limit,max(100,$rss_how_many)));\n\n			$rs = safe_rows_start(\n				\"*,\n				ID as thisid,\n				unix_timestamp(Posted) as uPosted,\n				unix_timestamp(Expires) as uExpires,\n				unix_timestamp(LastMod) as uLastMod\",\n				\"textpattern\",\n				\"Status=3 OR Posted > now() order by Posted desc limit $limit\"\n			);\n			if ($rs) {\n				while ($a = nextRow($rs)) {\n\n					extract($a);\n					populateArticleData($a);\n					$cb = callback_event(\'atom_entry\');\n					$e = array();\n\n					$a[\'posted\'] = $uPosted;\n\n					$thisauthor = get_author_name($AuthorID);\n\n					$e[\'thisauthor\'] = tag(n.t.t.t.tag(htmlspecialchars($thisauthor),\'name\').n.t.t,\'author\');\n\n					$e[\'issued\'] = tag(safe_strftime(\'w3cdtf\',$uPosted),\'published\');\n					$e[\'modified\'] = tag(safe_strftime(\'w3cdtf\',$uLastMod),\'updated\');\n\n					$escaped_title = htmlspecialchars($Title);\n					$e[\'title\'] = tag($escaped_title,\'title\',t_html);\n\n					$permlink = permlinkurl($a);\n					$e[\'link\'] = \'<link\'.r_relalt.t_texthtml.\' href=\"\'.$permlink.\'\" />\';\n\n					$e[\'id\'] = tag(\'tag:\'.$mail_or_domain.\',\'.$feed_time.\':\'.$blog_uid.\'/\'.$uid,\'id\');\n\n					$e[\'category1\'] = (trim($Category1) ? \'<category term=\"\'.htmlspecialchars($Category1).\'\" />\' : \'\');\n					$e[\'category2\'] = (trim($Category2) ? \'<category term=\"\'.htmlspecialchars($Category2).\'\" />\' : \'\');\n\n					$summary = trim(replace_relative_urls(parse($thisarticle[\'excerpt\']), $permlink));\n					$content = trim(replace_relative_urls(parse($thisarticle[\'body\']), $permlink));\n\n					if ($syndicate_body_or_excerpt) {\n						# short feed: use body as summary if there\'s no excerpt\n						if (!trim($summary))\n							$summary = $content;\n						$content = \'\';\n					}\n\n					if (trim($content))\n						$e[\'content\'] = tag(n.escape_cdata($content).n,\'content\',t_html);\n\n					if (trim($summary))\n						$e[\'summary\'] = tag(n.escape_cdata($summary).n,\'summary\',t_html);\n\n					$articles[$ID] = tag(n.t.t.join(n.t.t,$e).n.$cb,\'entry\');\n\n					$etags[$ID] = strtoupper(dechex(crc32($articles[$ID])));\n					$dates[$ID] = $uLastMod;\n				}\n			}\n		}\n\n		if ($articles) {\n			//turn on compression if we aren\'t using it already\n			if (extension_loaded(\'zlib\') && ini_get(\"zlib.output_compression\") == 0 && ini_get(\'output_handler\') != \'ob_gzhandler\' && !headers_sent()) {\n				// make sure notices/warnings/errors don\'t fudge up the feed\n				// when compression is used\n				$buf = \'\';\n				while ($b = @ob_get_clean())\n					$buf .= $b;\n				@ob_start(\'ob_gzhandler\');\n				echo $buf;\n			}\n\n			handle_lastmod();\n			$hims = serverset(\'HTTP_IF_MODIFIED_SINCE\');\n			$imsd = ($hims) ? strtotime($hims) : 0;\n\n			if (is_callable(\'apache_request_headers\')) {\n				$headers = apache_request_headers();\n				if (isset($headers[\"A-IM\"])) {\n					$canaim = strpos($headers[\"A-IM\"], \"feed\");\n				} else {\n					$canaim = false;\n				}\n			} else {\n				$canaim = false;\n			}\n\n			$hinm = stripslashes(serverset(\'HTTP_IF_NONE_MATCH\'));\n\n			$cutarticles = false;\n\n			if ($canaim !== false) {\n				foreach($articles as $id=>$thing) {\n					if (strpos($hinm, $etags[$id])) {\n						unset($articles[$id]);\n						$cutarticles = true;\n						$cut_etag = true;\n					}\n\n					if ($dates[$id] < $imsd) {\n						unset($articles[$id]);\n						$cutarticles = true;\n						$cut_time = true;\n					}\n				}\n			}\n\n			if (isset($cut_etag) && isset($cut_time)) {\n				header(\"Vary: If-None-Match, If-Modified-Since\");\n			} else if (isset($cut_etag)) {\n				header(\"Vary: If-None-Match\");\n			} else if (isset($cut_time)) {\n				header(\"Vary: If-Modified-Since\");\n			}\n\n			$etag = @join(\"-\",$etags);\n\n			if (strstr($hinm, $etag)) {\n				txp_status_header(\'304 Not Modified\');\n				exit(0);\n			}\n\n			if ($etag) header(\'ETag: \"\'.$etag.\'\"\');\n\n			if ($cutarticles) {\n				//header(\"HTTP/1.1 226 IM Used\");\n				//This should be used as opposed to 200, but Apache doesn\'t like it.\n				//http://intertwingly.net/blog/2004/09/11/Vary-ETag/ says that the status code should be 200.\n				header(\"Cache-Control: no-store, im\");\n				header(\"IM: feed\");\n			}\n			$out = array_merge($out, $articles);\n\n		}\n		header(\'Content-type: application/atom+xml; charset=utf-8\');\n		echo chr(60).\'?xml version=\"1.0\" encoding=\"UTF-8\"?\'.chr(62).n.\n			\'<feed xml:lang=\"\'.$language.\'\" xmlns=\"http://www.w3.org/2005/Atom\">\'.join(n,$out).\'</feed>\';\n		exit();\n	}\n}\n', '\nglobal $textarray;\n\n/**\n *  User-editable strings\n */\n$textarray[\'wet_snitch\'] = \'Snitch\';\n$textarray[\'wet_snitch_feed_link\'] = \'Feed link\';\n$textarray[\'wet_snitch_new_secret\'] = \'New link\';\n\n// == STOP EDITING! ==\n\n// Hook into the admin side of highly privileged users\nif (@txpinterface == \'admin\') {\n	add_privs(\'wet_snitch\', \'1\');\n	register_tab(\'admin\', \'wet_snitch\', gTxt(\'wet_snitch\'));\n	register_callback(\'wet_snitch_ui\', \'wet_snitch\');\n}\n\n// Hook into the public ATOM feed URL\nif (@txpinterface == \'public\') {\n	register_callback(\'wet_snitch_feed\', \'atom_head\');\n}\n\n/**\n * Admin side UI\n */\nfunction wet_snitch_ui($event, $step)\n{\n  	global $prefs;\n	$secret = (isset($prefs[\'wet_snitch\']) ? $prefs[\'wet_snitch\'] : substr(md5(rand()), 0, 9));\n  	$title = (isset($prefs[\'wet_snitch_title\']) ? $prefs[\'wet_snitch_title\'] : $prefs[\'sitename\'] . \" - private feed\");\n\n	pagetop(gTxt(\'wet_snitch\'));\n\n	$url = pagelinkurl(array(\n			\'atom\'    => \'1\',\n			\'section\'  => \'\',\n			\'category\' => \'\',\n			\'limit\'    => \'\'\n		));\n\n	if (gps(\'new\')) {\n  		$secret = substr(md5(rand()), 0, 9);\n  	}\n  	set_pref(\'wet_snitch\', $secret, \'publish\', 2);\n\n	if (gps(\'save\')) {\n  		$title = gps(\'title\');\n  	}\n  	set_pref(\'wet_snitch_title\', $title, \'publish\', 2);\n\n  	echo form(\n		n.eInput(\'wet_snitch\').\n		n.startTable(\'edit\').\n			tr(\n				td(\'<label for=\"title\">\'.gTxt(\'title\').\'</label>\').\n				td(fInput(\'text\', \'title\', $title, \'\', \'\', \'\', \'\', \'\', \'title\')).\n				td(fInput(\'submit\', \'save\', gTxt(\'save\'), \'smallerbox\'))\n			).\n			tr(\n				td(gTxt(\'wet_snitch_feed_link\')).\n				td(\'<code><a href=\'.$url.\'?wet_snitch=\'.$secret.\'>\'.$secret.\'</a></code>\').\n				td(fInput(\'submit\', \'new\', gTxt(\'wet_snitch_new_secret\'), \'smallerbox\'))\n			).\n			endTable()\n		);\n}\n\n/**\n * Custom ATOM feed, roughly ripped from publish/atom.php\n */\nfunction wet_snitch_feed($event, $step)\n{\n	global $prefs, $thisarticle;\n	extract($prefs);\n\n	// Check prerequisites: Secret preference set and present as an URL-var\n	if (isset($wet_snitch)) {\n		$do_feed = gps(\'wet_snitch\');\n		if (empty($do_feed))\n			return;\n		else {\n			if($do_feed != $wet_snitch) txp_die(\'\', \'404\');\n\n			extract(doSlash(gpsa(array(\'category\',\'section\',\'limit\',\'area\'))));\n\n			$last = fetch(\'unix_timestamp(val)\',\'txp_prefs\',\'name\',\'lastmod\');\n\n			if(empty($wet_snitch_title)) {\n				$sitename .= ($section) ? \' - \'.fetch_section_title($section) : \'\';\n				$sitename .= ($category) ? \' - \'.fetch_category_title($category) : \'\';\n				$sitename .= \" - private feed\";\n			} else {\n				$sitename = $wet_snitch_title;\n			}\n\n			$pub = safe_row(\"RealName, email\", \"txp_users\", \"privs=1\");\n\n			$out[] = tag(htmlspecialchars($sitename),\'title\',t_text);\n			$out[] = tag(htmlspecialchars($site_slogan),\'subtitle\',t_text);\n			$out[] = \'<link\'.r_relself.\' href=\"\'.pagelinkurl(array(\'atom\'=>1,\'area\'=>$area,\'section\'=>$section,\'category\'=>$category,\'limit\'=>$limit)).\'\" />\';\n			$out[] = \'<link\'.r_relalt.t_texthtml.\' href=\"\'.hu.\'\" />\';\n			$articles = array();\n\n			//Atom feeds with mail or domain name\n			$dn = explode(\'/\',$siteurl);\n			$mail_or_domain = ($use_mail_on_feeds_id)? eE($blog_mail_uid):$dn[0];\n			$out[] = tag(\'tag:\'.$mail_or_domain.\',\'.$blog_time_uid.\':\'.$blog_uid.(($section)? \'/\'.$section:\'\').(($category)? \'/\'.$category:\'\'),\'id\');\n\n			$out[] = tag(\'Textpattern\',\'generator\',\n				\' uri=\"http://textpattern.com/\" version=\"\'.$version.\'\"\');\n			$out[] = tag(safe_strftime(\"w3cdtf\",$last),\'updated\');\n\n\n			$auth[] = tag($pub[\'RealName\'],\'name\');\n			$auth[] = ($include_email_atom) ? tag(eE($pub[\'email\']),\'email\') : \'\';\n			$auth[] = tag(hu,\'uri\');\n\n			$out[] = tag(n.t.t.join(n.t.t,$auth).n,\'author\');\n\n			$limit = ($limit) ? $limit : $rss_how_many;\n			$limit = intval(min($limit,max(100,$rss_how_many)));\n\n			$rs = safe_rows_start(\n				\"*,\n				ID as thisid,\n				unix_timestamp(Posted) as uPosted,\n				unix_timestamp(Expires) as uExpires,\n				unix_timestamp(LastMod) as uLastMod\",\n				\"textpattern\",\n				\"Status=3 OR Posted > now() order by Posted desc limit $limit\"\n			);\n			if ($rs) {\n				while ($a = nextRow($rs)) {\n\n					extract($a);\n					populateArticleData($a);\n					$cb = callback_event(\'atom_entry\');\n					$e = array();\n\n					$a[\'posted\'] = $uPosted;\n\n					$thisauthor = get_author_name($AuthorID);\n\n					$e[\'thisauthor\'] = tag(n.t.t.t.tag(htmlspecialchars($thisauthor),\'name\').n.t.t,\'author\');\n\n					$e[\'issued\'] = tag(safe_strftime(\'w3cdtf\',$uPosted),\'published\');\n					$e[\'modified\'] = tag(safe_strftime(\'w3cdtf\',$uLastMod),\'updated\');\n\n					$escaped_title = htmlspecialchars($Title);\n					$e[\'title\'] = tag($escaped_title,\'title\',t_html);\n\n					$permlink = permlinkurl($a);\n					$e[\'link\'] = \'<link\'.r_relalt.t_texthtml.\' href=\"\'.$permlink.\'\" />\';\n\n					$e[\'id\'] = tag(\'tag:\'.$mail_or_domain.\',\'.$feed_time.\':\'.$blog_uid.\'/\'.$uid,\'id\');\n\n					$e[\'category1\'] = (trim($Category1) ? \'<category term=\"\'.htmlspecialchars($Category1).\'\" />\' : \'\');\n					$e[\'category2\'] = (trim($Category2) ? \'<category term=\"\'.htmlspecialchars($Category2).\'\" />\' : \'\');\n\n					$summary = trim(replace_relative_urls(parse($thisarticle[\'excerpt\']), $permlink));\n					$content = trim(replace_relative_urls(parse($thisarticle[\'body\']), $permlink));\n\n					if ($syndicate_body_or_excerpt) {\n						# short feed: use body as summary if there\'s no excerpt\n						if (!trim($summary))\n							$summary = $content;\n						$content = \'\';\n					}\n\n					if (trim($content))\n						$e[\'content\'] = tag(n.escape_cdata($content).n,\'content\',t_html);\n\n					if (trim($summary))\n						$e[\'summary\'] = tag(n.escape_cdata($summary).n,\'summary\',t_html);\n\n					$articles[$ID] = tag(n.t.t.join(n.t.t,$e).n.$cb,\'entry\');\n\n					$etags[$ID] = strtoupper(dechex(crc32($articles[$ID])));\n					$dates[$ID] = $uLastMod;\n				}\n			}\n		}\n\n		if ($articles) {\n			//turn on compression if we aren\'t using it already\n			if (extension_loaded(\'zlib\') && ini_get(\"zlib.output_compression\") == 0 && ini_get(\'output_handler\') != \'ob_gzhandler\' && !headers_sent()) {\n				// make sure notices/warnings/errors don\'t fudge up the feed\n				// when compression is used\n				$buf = \'\';\n				while ($b = @ob_get_clean())\n					$buf .= $b;\n				@ob_start(\'ob_gzhandler\');\n				echo $buf;\n			}\n\n			handle_lastmod();\n			$hims = serverset(\'HTTP_IF_MODIFIED_SINCE\');\n			$imsd = ($hims) ? strtotime($hims) : 0;\n\n			if (is_callable(\'apache_request_headers\')) {\n				$headers = apache_request_headers();\n				if (isset($headers[\"A-IM\"])) {\n					$canaim = strpos($headers[\"A-IM\"], \"feed\");\n				} else {\n					$canaim = false;\n				}\n			} else {\n				$canaim = false;\n			}\n\n			$hinm = stripslashes(serverset(\'HTTP_IF_NONE_MATCH\'));\n\n			$cutarticles = false;\n\n			if ($canaim !== false) {\n				foreach($articles as $id=>$thing) {\n					if (strpos($hinm, $etags[$id])) {\n						unset($articles[$id]);\n						$cutarticles = true;\n						$cut_etag = true;\n					}\n\n					if ($dates[$id] < $imsd) {\n						unset($articles[$id]);\n						$cutarticles = true;\n						$cut_time = true;\n					}\n				}\n			}\n\n			if (isset($cut_etag) && isset($cut_time)) {\n				header(\"Vary: If-None-Match, If-Modified-Since\");\n			} else if (isset($cut_etag)) {\n				header(\"Vary: If-None-Match\");\n			} else if (isset($cut_time)) {\n				header(\"Vary: If-Modified-Since\");\n			}\n\n			$etag = @join(\"-\",$etags);\n\n			if (strstr($hinm, $etag)) {\n				txp_status_header(\'304 Not Modified\');\n				exit(0);\n			}\n\n			if ($etag) header(\'ETag: \"\'.$etag.\'\"\');\n\n			if ($cutarticles) {\n				//header(\"HTTP/1.1 226 IM Used\");\n				//This should be used as opposed to 200, but Apache doesn\'t like it.\n				//http://intertwingly.net/blog/2004/09/11/Vary-ETag/ says that the status code should be 200.\n				header(\"Cache-Control: no-store, im\");\n				header(\"IM: feed\");\n			}\n			$out = array_merge($out, $articles);\n\n		}\n		header(\'Content-type: application/atom+xml; charset=utf-8\');\n		echo chr(60).\'?xml version=\"1.0\" encoding=\"UTF-8\"?\'.chr(62).n.\n			\'<feed xml:lang=\"\'.$language.\'\" xmlns=\"http://www.w3.org/2005/Atom\">\'.join(n,$out).\'</feed>\';\n		exit();\n	}\n}\n', '8ac7fbd594ab57b863b32df07f224604', '', '', 1, 5, 0),
('wet_plugout', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_plugout-textpattern-plugin', '0.2', 'One button plugin state save and restore.', '<h3>Find conflicting plugins with ease</h3>\n\n<p><strong>wet_plugout</strong> saves and restores all plugins’ ‘active’ state at the click of one button.</p>\n\n<h4>usage:</h4>\n\n<ol>\n	<li>Install, enable, enjoy &#8211; no configuration required. Disable and re-enable all active plugins on the extension tab labelled <a href=\"http://localhost/site1/?event=wet_plugout\" rel=\"nofollow\">Plugout</a>.</li>\n</ol>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nif (@txpinterface == \'admin\') {\n  add_privs(\'wet_plugout\', \'1,2\');\n  register_tab(\'extensions\', \'wet_plugout\', \'Plugout\');\n  register_callback(\'wet_plugout\', \'wet_plugout\');\n}\n\nfunction wet_plugout($event, $step)\n{\n  global $prefs;\n  function my_gTxt($text)\n  {\n    $gTxt = array (\n	    \'disable\' => \'Disable\',\n	    \'enable\' => \'Enable\',\n	    \'no_active_plugins\' => \'No active plugins.\',\n	    \'about_to_disable\' => \'These plugins will be disabled:\',\n	    \'about_to_enable\' => \'These plugins will be enabled:\',\n    	\'file_error\' => \'A problem occured while writing to file \',\n    	\'manage_plugins\' => \'Manage plugins\'\n    );\n    return $gTxt[$text];\n  }\n\n  function fout($f, $s) {\n  	$r = fopen($f, \'w\');\n  	if(!$r) return false;\n  	$out = fputs($r, $s);\n  	fclose($r);\n  	return $out;\n  }\n  function fin($f) {\n  	$r = fopen($f, \'r\');\n  	if(!$r) return false;\n  	$out = fgets($r);\n  	fclose($f);\n  	return $out;\n  }\n\n  $f = $prefs[\'tempdir\'].\'/\'.__FUNCTION__.\'.txt\';\n  $fileerror = false;\n\n  pagetop(\'Plugout\', (!empty($step) ? gTxt(\'preferences_saved\') : \'\'));\n\n  if ($step == \'disable\')	{\n  	// currently active plugins?\n	$plugins = safe_column(\'name\', \'txp_plugin\', \"status=\'1\' and name <> \'\".__FUNCTION__.\"\' order by name\");\n    safe_update(\'txp_plugin\', \'status = \\\'0\\\'\', \'name in (\\\'\'.join(\"\', \'\", $plugins).\'\\\')\');\n    $plugins = join(\'|\', $plugins);\n    if(!fout($f, $plugins)) $filerror = true;\n  }\n  elseif ($step == \'enable\') {\n    // previously saved plugin state?\n    $plugins = @fin($f);\n    if(!empty($plugins)) {\n    	$plugins = explode(\'|\', $plugins);\n    	safe_update(\'txp_plugin\', \'status = \\\'1\\\'\', \'name in (\\\'\'.join($plugins,\"\', \'\").\'\\\')\');\n    	unlink($f);\n    }\n  }\n\n  $plugins = @fin($f);\n  if (empty($plugins)) {\n    $step = \'disable\';\n    $plugins = safe_column(\'name\', \'txp_plugin\', \"status=\'1\' and name <> \'\".__FUNCTION__.\"\' order by name\");\n    $plugins = join(\', \', $plugins);\n  } else {\n  	 $step = \'enable\';\n  	 $plugins = join(\', \', explode(\'|\', $plugins));\n  }\n\n  echo\n  \'<div style=\"margin:auto;width:30em\">\';\n\n  if (empty($plugins)) {\n	echo my_gTxt(\'no_active_plugins\');\n  } elseif ($fileerror) {\n  	echo my_gTxt(\'file_error\').$f;\n  } else {\n  	echo form(\n  		n.hed(($step == \'disable\') ? my_gTxt(\'about_to_disable\') : my_gTxt(\'about_to_enable\'), 3).\n	  	n.graf($plugins).\n	    n.eInput(__FUNCTION__).\n	    n.sInput($step).\n	    n.graf(fInput(\'submit\', \'\', my_gTxt($step), \'smallerbox\')).\n	    n.graf(href(my_gTxt(\'manage_plugins\'),\'?event=plugin\'))\n	  );\n  }\n  echo \'</div>\';\n}', '\nif (@txpinterface == \'admin\') {\n  add_privs(\'wet_plugout\', \'1,2\');\n  register_tab(\'extensions\', \'wet_plugout\', \'Plugout\');\n  register_callback(\'wet_plugout\', \'wet_plugout\');\n}\n\nfunction wet_plugout($event, $step)\n{\n  global $prefs;\n  function my_gTxt($text)\n  {\n    $gTxt = array (\n	    \'disable\' => \'Disable\',\n	    \'enable\' => \'Enable\',\n	    \'no_active_plugins\' => \'No active plugins.\',\n	    \'about_to_disable\' => \'These plugins will be disabled:\',\n	    \'about_to_enable\' => \'These plugins will be enabled:\',\n    	\'file_error\' => \'A problem occured while writing to file \',\n    	\'manage_plugins\' => \'Manage plugins\'\n    );\n    return $gTxt[$text];\n  }\n\n  function fout($f, $s) {\n  	$r = fopen($f, \'w\');\n  	if(!$r) return false;\n  	$out = fputs($r, $s);\n  	fclose($r);\n  	return $out;\n  }\n  function fin($f) {\n  	$r = fopen($f, \'r\');\n  	if(!$r) return false;\n  	$out = fgets($r);\n  	fclose($f);\n  	return $out;\n  }\n\n  $f = $prefs[\'tempdir\'].\'/\'.__FUNCTION__.\'.txt\';\n  $fileerror = false;\n\n  pagetop(\'Plugout\', (!empty($step) ? gTxt(\'preferences_saved\') : \'\'));\n\n  if ($step == \'disable\')	{\n  	// currently active plugins?\n	$plugins = safe_column(\'name\', \'txp_plugin\', \"status=\'1\' and name <> \'\".__FUNCTION__.\"\' order by name\");\n    safe_update(\'txp_plugin\', \'status = \\\'0\\\'\', \'name in (\\\'\'.join(\"\', \'\", $plugins).\'\\\')\');\n    $plugins = join(\'|\', $plugins);\n    if(!fout($f, $plugins)) $filerror = true;\n  }\n  elseif ($step == \'enable\') {\n    // previously saved plugin state?\n    $plugins = @fin($f);\n    if(!empty($plugins)) {\n    	$plugins = explode(\'|\', $plugins);\n    	safe_update(\'txp_plugin\', \'status = \\\'1\\\'\', \'name in (\\\'\'.join($plugins,\"\', \'\").\'\\\')\');\n    	unlink($f);\n    }\n  }\n\n  $plugins = @fin($f);\n  if (empty($plugins)) {\n    $step = \'disable\';\n    $plugins = safe_column(\'name\', \'txp_plugin\', \"status=\'1\' and name <> \'\".__FUNCTION__.\"\' order by name\");\n    $plugins = join(\', \', $plugins);\n  } else {\n  	 $step = \'enable\';\n  	 $plugins = join(\', \', explode(\'|\', $plugins));\n  }\n\n  echo\n  \'<div style=\"margin:auto;width:30em\">\';\n\n  if (empty($plugins)) {\n	echo my_gTxt(\'no_active_plugins\');\n  } elseif ($fileerror) {\n  	echo my_gTxt(\'file_error\').$f;\n  } else {\n  	echo form(\n  		n.hed(($step == \'disable\') ? my_gTxt(\'about_to_disable\') : my_gTxt(\'about_to_enable\'), 3).\n	  	n.graf($plugins).\n	    n.eInput(__FUNCTION__).\n	    n.sInput($step).\n	    n.graf(fInput(\'submit\', \'\', my_gTxt($step), \'smallerbox\')).\n	    n.graf(href(my_gTxt(\'manage_plugins\'),\'?event=plugin\'))\n	  );\n  }\n  echo \'</div>\';\n}', 'e41f178ddf76bd4729a6a9ee59e0d6f0', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_matrix', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '2.0', 'Multi-article update tabs', '<h1><strong>adi_matrix</strong> &#8211; Multi-article update tabs</h1>\n\n<p>This plugin provides a way of viewing and updating multiple articles from a single <span class=\"caps\">TXP</span> admin tab.</p>\n\n<p>Matrixes give you a summary view of multiple articles, where you can make changes to selected data &amp; update them all in one go.</p>\n\n<p>Two new tabs do the work:</p>\n\n<ul>\n	<li>adi_matrix admin tab under Extensions</li>\n	<li>article matrix tab(s) under Contents or Home</li>\n</ul>\n\n<p>The admin tab defines the matrixes and the article matrix tabs display the required articles with their data.</p>\n\n<h2><strong>Installation</strong></h2>\n\n<p>Installation of <strong>adi_matrix</strong> adds a new table to your Textpattern database which should not interfere with anything else.</p>\n\n<p><strong>adi_matrix</strong> is designed to make changes to groups of article.  I suggest that you make backups before installation of this plugin and during initial testing on your site.  I can thoroughly recommend <a href=\"http://forum.textpattern.com/viewtopic.php?id=10395\" rel=\"nofollow\">rss_admin_db_manager</a> to do database backups before installation.</p>\n\n<h2><strong>adi_matrix admin tab</strong></h2>\n\n<p>This is where you set up the article matrixes.  There are three aspects to this:</p>\n\n<h3>Matrix appearance</h3>\n\n<p>Here you can:</p>\n\n<ul>\n	<li>give the matrix a name (which will be used to list it under the Contents tab)</li>\n	<li>specify the order in which articles should be listed</li>\n	<li>specify whether a single user or all users are to get access to the matrix</li>\n	<li>specify whether access to the matrix is based on privileges or not</li>\n	<li>specify which tab to display the matrix under</li>\n	<li>permit users to add/delete articles</li>\n	<li>define how the matrix is laid out</li>\n</ul>\n\n<h3>Article selection criteria</h3>\n\n<p>By default all articles will be listed in the matrix, but you can narrow it down according to:</p>\n\n<ul>\n	<li>section</li>\n	<li>category</li>\n	<li>article status</li>\n	<li>author</li>\n	<li>keywords</li>\n	<li>posted &amp; expires timestamps</li>\n	<li>custom <span class=\"caps\">WHERE</span> clause condition</li>\n</ul>\n\n<h3>Article data display</h3>\n\n<p>This is where you define what data the user can see and change. Article that can be viewed &amp; updated in matrixes:</p>\n\n<ul>\n	<li>article status</li>\n	<li>custom fields</li>\n	<li>article image</li>\n	<li>keywords</li>\n	<li>categories</li>\n	<li>posted &amp; expires timestamps</li>\n	<li>title</li>\n	<li>section</li>\n</ul>\n\n<h3>Preferences</h3>\n\n<ul>\n	<li>maximum number of articles to be listed</li>\n	<li>display article ID</li>\n	<li>article title highlighting (indicate future or expired articles)</li>\n	<li>article title tooltips (show ID, posted &amp; expires timestamps in tooltip)</li>\n	<li>input field tooltips (show contents of input field in a tooltip)</li>\n</ul>\n\n<h2><strong>Getting started</strong></h2>\n\n<p>A new matrix can be added in <strong>adi_matrix</strong> admin tab simply by entering its details into the blank spaces.  As a minimum, a matrix name needs to be provided.</p>\n\n<p>Once a matrix has been defined, it&#8217;s settings can be changed at any time.  The new matrix will seen under the Contents or Home tabs after you have visited at least one other <span class=\"caps\">TXP</span> tab (a hop is required so that the the tab contents are refreshed).</p>\n\n<h2><strong>Article matrixes</strong></h2>\n\n<p>The matrix tabs show a number of articles, with their associated “data”. If you are the article author or have sufficient overriding privileges then you can make changes to the data &amp; update all articles with a single click.</p>\n\n<p>Note that only articles where you have actually changed anything will be updated &#8211; together with their <i>Last Modified Date</i> and <i>Author</i>.</p>\n\n<p><strong>adi_matrix</strong> respects all the standard restrictions on who can make changes to articles &#8211; based on authorship &amp; privilege level.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s options page and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Updates and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_matrix\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>glz_custom_fields</strong></h2>\n\n<p><strong>adi_matrix</strong> will automatically detect if <strong>glz_custom_fields</strong> is installed and should play nicely.</p>\n\n<h2><strong>TinyMCE</strong></h2>\n\n<p>If <strong>glz_custom_fields</strong> is installed you have the opportunity to use <a href=\"http://www.tinymce.com/\" rel=\"nofollow\">TinyMCE</a> to edit textarea custom fields.  Note that TinyMCE must be installed seperately.  To use it with <strong>adi_matrix</strong>, switch it on in the admin tab and fill in the configuration details.</p>\n\n<h2><strong>Uninstalling adi_matrix</strong></h2>\n\n<p>To uninstall <strong>adi_matrix</strong>, simply go to the Plugins tab and delete it.  No articles will be harmed in the process.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35972\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_matrix\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_matrix - Multi-article update tabs\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	2.0		- enhancements:\n				- publish & delete articles (for mrdale)\n				- extra article data options: title, section\n				- show sections\n				- added section & author to article title tooltip\n				- matrix under Content or Home tab\n				- improved validation error messages\n				- custom WHERE clause conditions\n				- article list paging\n			- requires TXP 4.5+\n			- tested on TXP 4.6\n			- fix: matrix privileges\n			- improved validation message (URL-only titles)\n			- article edit link styling\n			- and as Apple says \"Includes general performance and stability improvements\", as well as code \"sanity\" improvements for my sake\n	1.2		- TXP 4.5-ified\n			- French colon-isation\n			- lifecycle \"upgrade\" pseudo-event\n	1.1		- code tidy up (thanks gocom)\n			- enhancement: matrix tab optional footer\n			- enhancement: matrix tab column sorting\n			- enhancement: \"Any parent\" & \"Any child\" category wildcards\n			- enhancement: posted timestamp (& reset)\n			- enhancement: expires timestamp\n			- enhancement: multi-section select (for maniqui & mrdale)\n			- enhancement: input field tooltips (for masa)\n			- for mrdale:\n				- <body class=\"adi_matrix\"> on all matrix tabs\n				- another attempt at horizontal scrolling, this time with a fixed article title column\n				- more sorting options\n				- TinyMCE support for glz_custom_field textareas\n				- option to include descendent categories\n			- fixed: checkboxes again! (thanks redbot)\n			- changed: \"Nothing to do\" message changed to \"No articles modified\"\n			- changed: admin tab name/title now \"Article Matrix\"/\"Article Matrix Admin\"\n	1.0.1	- not officially released\n			- fixed: completely unticked checkboxes not updated (thanks redbot)\n			- fixed: detect glz_custom_fields in plugin cache (thanks gocom)\n	1.0		- enhancement: glz_custom_fields compatibility\n			- enhancement: force numeric sort (for jpdupont)\n			- enhancement: sort by Expires timestamp\n			- enhancement: article catagories (for maniqui)\n			- enhancement: option to switch on horizontal scroll (for mrdale)\n			- fixed: MySQL 4.1 compatibility (thanks colak)\n			- fixed: error if custom field contains single quote (thanks maniqui)\n			- fixed: superfluous \"Logged in user\" wildcard option in matrix appearance\n			- now uses lifecycle events\n	0.3		- enhancement: \"One category\", \"Two categories\" wildcards\n			- enhancement: timestamp (for CeBe)\n			- enhancement: expiry (for CeBe)\n			- enhancement: future/expired articles highlighted & preference (for CeBe)\n			- enhancement: article title tooltip, & preference\n			- admin: install/uninstall/textpack moved to plugin options\n	0.2		- fixed: missing child categories (thanks Zanza)\n			- enhancement: \"No category\", \"Any category\" wildcards\n			- enhancement: \"Logged in user\" wildcard\n			- enhancement: article image field (for Zanza)\n			- enhancement: article limit preference (for milosevic)\n	0.1		- initial release\n\n	Custom fields\n	- \"standard\" TXP custom fields: custom_1 ...custom_10, always present\n	- with glz_custom_fields, standard CFs can disappear (on \"reset\"), or additional ones added: custom_11 ...\n\n	Upgrade notes (1.0 - 1.1+)\n	- due to bug in 1.0, expires sort option will get changed to modified\n\n	Downgrade (from 2.0 to 1.1/1.2 only)\n	- go to adi_matrix plugin options tab\n	- add \"&step=downgrade\" to end of URL & hit return\n	- then immediately install previous version of adi_matrix\n	- BEWARE: multiple sections won\'t translate very well\n	- adi_matrix_article_limit pref will be reset to 100\n\n*/\n\nif (txpinterface === \'admin\') {\n	global $adi_matrix_debug,$adi_matrix_dump;\n\n	$adi_matrix_debug = 0; // general debuggy info\n	$adi_matrix_dump = 0; // dump of article data\n\n	// using article_validate & new default section pref (4.5.0), so decamp sharpish if need be\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_matrix_init();\n}\n\nfunction adi_matrix_init() {\n// general setup\n	global $event,$step,$prefs,$txp_groups,$txp_user,$txp_permissions,$theme,$adi_matrix_gtxt,$adi_matrix_url,$adi_matrix_glz_cfs,$adi_matrix_privs,$adi_matrix_groups,$adi_matrix_cfs,$adi_matrix_expiry_options,$adi_matrix_statuses,$adi_matrix_sort_options,$adi_matrix_sort_dir,$adi_matrix_timestamp_options,$adi_matrix_prefs,$adi_matrix_plugin_status,$adi_matrix_debug,$adi_matrix_glz_cfs,$adi_matrix_list,$adi_matrix_validation_errors,$adi_matrix_sort_type,$adi_matrix_categories,$adi_matrix_tabs,$adi_matrix_txp460;\n\n	$adi_matrix_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_matrix_gtxt = array(\n		\'adi_alphabetical\' => \'Alphabetical\',\n		\'adi_any_category\' => \'Any category\',\n		\'adi_any_child_category\' => \'Any child category\',\n		\'adi_article_data\' => \'Article Data\',\n		\'adi_article_matrix\' => \'Article Matrix\',\n		\'adi_article_highlighting\' => \'Article title highlighting\',\n		\'adi_article_limit\' => \'Maximum number of articles\',\n		\'adi_articles_not_modified\' => \'No articles modified\',\n		\'adi_article_selection\' => \'Article Selection\',\n		\'adi_article_tooltips\' => \'Article title tooltips\',\n		\'adi_article_update_fail\' => \'Article update failed\',\n		\'adi_articles_saved\' => \'Articles saved\',\n		\'adi_blank_url_title\' => \'URL-only title blank\',\n		\'adi_cancel\' => \'Cancel\',\n		\'adi_custom_condition\' => \'Custom condition\',\n		\'adi_cf_links\' => \'Custom field links\',\n		\'adi_default_sort\' => \'Default sort\',\n		\'adi_display_article_id\' => \'Display article ID#\',\n		\'adi_duplicate_url_title\' => \'URL-only title already used\',\n		\'adi_edit_titles\' => \'Edit titles\',\n		\'adi_expiry\' => \'Expiry\',\n		\'adi_footer\' => \'Footer\',\n		\'adi_has_expiry\' => \'Has expiry\',\n		\'adi_include_descendent_cats\' => \'Include descendent categories\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_invalid_timestamp\' => \'Invalid timestamp\',\n		\'adi_jquery_ui\' => \'jQuery UI script file\',\n		\'adi_jquery_ui_css\' => \'jQuery UI CSS file\',\n		\'adi_logged_in_user\' => \'Logged in user\',\n		\'adi_matrix\' => \'Matrix\',\n		\'adi_matrix_admin\' => \'Article Matrix Admin\',\n		\'adi_matrix_total_articles\' => \'Total articles in matrix:\',\n		\'adi_matrix_cfs_modified\' => \'Custom field list modified\',\n		\'adi_matrix_delete_fail\' => \'Matrix delete failed\',\n		\'adi_matrix_deleted\' => \'Matrix deleted\',\n		\'adi_matrix_input_field_tooltips\' => \'Input field tooltips\',\n		\'adi_matrix_validation_error\' => \'Validation errors\',\n		\'adi_matrix_name\' => \'Matrix name\',\n		\'adi_matrix_update_fail\' => \'Matrix settings update failed\',\n		\'adi_matrix_updated\' => \'Matrix settings updated\',\n		\'adi_new_article\' => \'New article\',\n		\'adi_no_category\' => \'No category\',\n		\'adi_no_expiry\' => \'No expiry\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_numerical\' => \'Numerical\',\n		\'adi_ok\' => \'OK\',\n		\'adi_one_category\' => \'One category\',\n		\'adi_any_parent_category\' => \'Any parent category\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_reset\' => \'Reset\',\n		\'adi_scroll\' => \'Scroll\',\n		\'adi_show_section\' => \'Show section\',\n		\'adi_sort_type\' => \'Sort type\',\n		\'adi_tab\' => \'Tab\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_tiny_mce\' => \'TinyMCE\',\n		\'adi_tiny_mce_dir_path\' => \'TinyMCE directory path\',\n		\'adi_tiny_mce_hak\' => \'TinyMCE (hak_tinymce)\',\n		\'adi_tiny_mce_javascript\' =>\'TinyMCE (Javascript)\',\n		\'adi_tiny_mce_jquery\' => \'TinyMCE (jQuery)\',\n		\'adi_tiny_mce_config\' => \'TinyMCE configuration\',\n		\'adi_two_categories\' => \'Two categories\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_matrix\' => \'Update matrix settings\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_user\' => \'User\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_matrix_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_matrix\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_matrix_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_matrix_lifecycle\',\'plugin_lifecycle.adi_matrix\');\n\n	// adi_matrix admin tab\n	add_privs(\'adi_matrix_admin\'); // add priv group - defaults to priv \'1\' only\n	register_tab(\'extensions\',\'adi_matrix_admin\',adi_matrix_gtxt(\'adi_article_matrix\')); // add new tab under \'Extensions\'\n	register_callback(\'adi_matrix_admin\',\'adi_matrix_admin\');\n\n	// look for glz_custom_fields\n	$adi_matrix_glz_cfs = load_plugin(\'glz_custom_fields\');\n\n	/*	User privilege summary:\n			0 - none			- can\'t even login\n			1 - publisher		- full matrix data & adi_matrix admin capability\n			2 - manager			- matrix data only\n			3 - copy editor		- matrix data only\n			4 - staff writer	- matrix data only\n			5 - freelancer		- matrix data only\n			6 - designer		- matrix data only\n		Standard article editing privileges:\n			\'article.edit\'                => \'1,2,3\',\n			\'article.edit.published\'      => \'1,2,3\',\n			\'article.edit.own\'            => \'1,2,3,4,5,6\',\n			\'article.edit.own.published\'  => \'1,2,3,4\',\n	*/\n\n	// defines privileges required to view a matrix with privilege restriction (same indexing as $txp_groups)\n	$adi_matrix_privs = array(\n		1 => \'1\',			// publisher\n		2 => \'1,2\',			// managing_editor\n		3 => \'1,2,3\',		// copy_editor\n		4 => \'1,2,3,4\',		// staff_writer\n		5 => \'1,2,3,4,5\',	// freelancer\n		6 => \'1,6\',			// designer\n	);\n\n	// set up user privilege names\n	$adi_matrix_groups = $txp_groups; // to get: 1 => \'publisher\', 2 => \'managing_editor\' etc\n	unset($adi_matrix_groups[0]); // lose index zero (none) - gets us a blank select option too!\n	foreach ($adi_matrix_groups as $index => $group)\n		$adi_matrix_groups[$index] = adi_matrix_gtxt($group); // to get: 1 => \'Publisher\', 2 => \'Managing Editor\' etc in the language \'de jour\'\n\n	// discover custom fields (standard 1-10 & glz 11+) and their non-lowercased titles\n	$adi_matrix_cfs = getCustomFields();\n	foreach ($adi_matrix_cfs as $index => $value)\n		$adi_matrix_cfs[$index] = $prefs[\'custom_\'.$index.\'_set\']; // index = custom fields number, value = custom field title\n\n	// build a picture of article categories\n	$adi_matrix_categories = adi_matrix_categories(getTree(\'root\',\'article\'));\n\n	// article expiry options\n	$adi_matrix_expiry_options = array(\n		0 => \'\',\n		1 => adi_matrix_gtxt(\'adi_no_expiry\'),\n		2 => adi_matrix_gtxt(\'adi_has_expiry\'),\n		3 => adi_matrix_gtxt(\'expired\'),\n	);\n\n	// article status code translation\n	$adi_matrix_statuses = array(\n		1 => adi_matrix_gtxt(\'draft\'),\n		2 => adi_matrix_gtxt(\'hidden\'),\n		3 => adi_matrix_gtxt(\'pending\'),\n		4 => adi_matrix_gtxt(\'live\'),\n		5 => adi_matrix_gtxt(\'sticky\'),\n	);\n\n	// article sort options\n	$adi_matrix_sort_options = array(\n		\'posted\' => adi_matrix_gtxt(\'posted\'),\n		\'title\' => adi_matrix_gtxt(\'title\'),\n		\'id\' => adi_matrix_gtxt(\'id\'),\n		\'lastmod\' => adi_matrix_gtxt(\'article_modified\'),\n		\'expires\' => adi_matrix_gtxt(\'expires\'),\n		\'status\' => adi_matrix_gtxt(\'status\'),\n		\'keywords\' => adi_matrix_gtxt(\'keywords\'),\n		\'article_image\' => adi_matrix_gtxt(\'article_image\'),\n		\'category1\' => adi_matrix_gtxt(\'category1\'),\n		\'category2\' => adi_matrix_gtxt(\'category2\'),\n		\'section\' => adi_matrix_gtxt(\'section\'),\n	);\n	foreach ($adi_matrix_cfs as $index => $value) // add custom fields to sort options\n		$adi_matrix_sort_options[\'custom_\'.$index] = $adi_matrix_cfs[$index];\n\n	// article sort direction\n	$adi_matrix_sort_dir = array(\n		\'desc\' => adi_matrix_gtxt(\'descending\'),\n		\'asc\' => adi_matrix_gtxt(\'ascending\'),\n	);\n\n	// article sort type\n	$adi_matrix_sort_type = array(\n		\'alphabetical\' => adi_matrix_gtxt(\'adi_alphabetical\'),\n		\'numerical\' => adi_matrix_gtxt(\'adi_numerical\'),\n	);\n\n	// article timestamp options\n	$adi_matrix_timestamp_options = array(\n		\'any\' => adi_matrix_gtxt(\'time_any\'),\n		\'past\' => adi_matrix_gtxt(\'time_past\'),\n		\'future\' => adi_matrix_gtxt(\'time_future\'),\n	);\n\n	// validation errors\n	$adi_matrix_validation_errors = array(\n		0 => adi_matrix_gtxt(\'adi_invalid_timestamp\'),\n		1 => adi_matrix_gtxt(\'article_expires_before_postdate\'),\n		2 => adi_matrix_gtxt(\'adi_duplicate_url_title\'),\n		3 => adi_matrix_gtxt(\'adi_blank_url_title\'),\n	);\n\n	// default preferences\n	$adi_matrix_prefs = array(\n		\'adi_matrix_article_highlighting\'	=> array(\'value\' => \'1\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_article_tooltips\'		=> array(\'value\' => \'1\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_display_id\'				=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_input_field_tooltips\'	=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_jquery_ui\'				=> array(\'value\' => \'../scripts/jquery-ui.js\', \'input\' => \'text_input\'),\n		\'adi_matrix_jquery_ui_css\'			=> array(\'value\' => \'../scripts/jquery-ui.css\', \'input\' => \'text_input\'),\n		\'adi_matrix_tiny_mce\'				=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_tiny_mce_type\'			=> array(\'value\' => \'custom\', \'input\' => \'text_input\'),\n 		\'adi_matrix_tiny_mce_dir\'			=> array(\'value\' => \'../scripts/tiny_mce\', \'input\' => \'text_input\'),\n		\'adi_matrix_tiny_mce_config\'		=> array(\'value\' => \'see below\', \'input\' => \'text_area\'),\n	);\n	$adi_matrix_prefs[\'adi_matrix_tiny_mce_config\'][\'value\'] = \'\nlanguage : \"en\",\ntheme : \"advanced\",\nplugins : \"safari,pagebreak,style,layer,table,save,advhr,advlink,emotions,iespell,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template\",\ntheme_advanced_buttons1 : \"pagebreak,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect\",\ntheme_advanced_buttons2 : \"cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor\",\ntheme_advanced_buttons3 : \"tablecontrols\",\ntheme_advanced_toolbar_location : \"top\",\ntheme_advanced_toolbar_align : \"left\",\ntheme_advanced_statusbar_location : \"bottom\",\ntheme_advanced_resizing : true,\nextended_valid_elements: \"style[*]\",\nwidth: \"600\",\nheight: \"400\",\n\';\n\n	// tabs\n	$adi_matrix_tabs = array(\n		\'content\' => adi_matrix_gtxt(\'tab_content\'),\n		\'start\' => adi_matrix_gtxt(\'tab_start\'),\n	);\n\n	// plugin options\n	$adi_matrix_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_matrix\',$adi_matrix_debug);\n	if ($adi_matrix_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_matrix\'); // add priv set - defaults to priv \'1\' only\n		register_callback(\'adi_matrix_options\',\'plugin_prefs.adi_matrix\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_matrix_options\'); // add priv set - defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_matrix_options\',\'adi_matrix options\');\n		register_callback(\'adi_matrix_options\',\'adi_matrix_options\');\n	}\n\n	// glz_custom_fields stuff\n	if ($adi_matrix_glz_cfs) {\n		if (strstr($event, \'adi_matrix_matrix_\')) {\n			// date & time pickers\n		    add_privs(\'glz_custom_fields_css_js\',\"1,2,3,4,5,6\");\n		    register_callback(\'glz_custom_fields_css_js\',\'admin_side\',\'head_end\');\n			// TinyMCE\n			if (adi_matrix_pref(\'adi_matrix_tiny_mce\')) {\n				register_callback(\'adi_matrix_tiny_mce_style\',\'admin_side\',\'head_end\');\n				register_callback(\'adi_matrix_tiny_mce_\'.adi_matrix_pref(\'adi_matrix_tiny_mce_type\'),\'admin_side\',\'footer\');\n			}\n		}\n	}\n\n	// article matrix tabs\n	$adi_matrix_list = array();\n	if (adi_matrix_installed())\n		$adi_matrix_list = adi_matrix_read_settings(adi_matrix_upgrade());\n	$all_privs = \'1,2,3,4,5,6\'; // everybody\n	foreach ($adi_matrix_list as $index => $matrix) {\n		$matrix_event = \'adi_matrix_matrix_\'.$index;\n		$matrix_tab_name = $matrix[\'name\'];\n		if ($matrix[\'privs\'])\n			$priv_set = $adi_matrix_privs[$matrix[\'privs\']];\n		else\n			$priv_set = $all_privs; // everybody\'s welcome\n		add_privs($matrix_event,$priv_set); // add priv set for each matrix event (to $txp_permissions)\n		if ($matrix[\'user\'])\n			$user_allowed = ($txp_user == $matrix[\'user\']);\n		else // open to all users\n			$user_allowed = TRUE;\n		$has_privs = has_privs($matrix_event);\n		$register_this_tab = ($user_allowed && $has_privs) || has_privs(\'adi_matrix_admin\');\n		if ($register_this_tab) {\n			$tab = $matrix[\'tab\'];\n			if ($tab == \'start\') // switch on Home tab\n				add_privs(\'tab.start\',$all_privs); // all privs\n			register_tab($tab,$matrix_event,$matrix_tab_name);\n			register_callback(\"adi_matrix_matrix\",$matrix_event);\n		}\n		if ($adi_matrix_debug) {\n			echo \"MATRIX: index=$index, name=\".$matrix[\'name\'].\", user=\".$matrix[\'user\'].\", privs=\".$matrix[\'privs\'].\", priv_set=$matrix_event($priv_set), tab=\".$matrix[\'tab\'].br;\n			$user_privs = safe_field(\'privs\',\'txp_users\',\"name=\'$txp_user\'\");\n			echo \"USER: user=$txp_user, user_privs=$user_privs, user_allowed=$user_allowed, has_privs=$has_privs\".br;\n			echo \"TAB: register_this_tab=$register_this_tab\".br.br;\n		}\n	}\n	if ($adi_matrix_debug) {\n		echo \'<b>$adi_matrix_privs:</b>\';\n		dmp($adi_matrix_privs);\n		echo \'<b>adi_matrix added priv sets ($txp_permissions):</b>\'.br;\n		foreach ($txp_permissions as $index => $value)\n			if (strpos($index,\'adi_matrix\') === 0)\n				echo $index.\' = \'.$value.br;\n	}\n\n	// style\n	if (strstr($event,\'adi_matrix_matrix_\') || ($event == \'adi_matrix_admin\'))\n		register_callback(\'adi_matrix_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_matrix_admin\'))\n		register_callback(\'adi_matrix_admin_script\',\'admin_side\',\'head_end\');\n	if (strstr($event,\'adi_matrix_matrix_\'))\n		register_callback(\'adi_matrix_matrix_script\',\'admin_side\',\'head_end\');\n}\n\nfunction adi_matrix_gtxt($phrase,$atts=array(),$suffix = \'\') {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_matrix_gtxt() are functionally equivalent\n	global $adi_matrix_gtxt,$prefs;\n\n	// make sure $atts is an array (done automatically in TXP 4.5+)\n	if (!is_array($atts)) $atts = array();\n	// French colon-isation\n	if ($suffix == \':\' && (strpos($prefs[\'language\'],\'fr-\') === 0))\n		$suffix = sp.$suffix;\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_matrix_gtxt)) // adi translation found\n			return $adi_matrix_gtxt[$phrase].$suffix;\n		else // last resort\n			return $phrase.$suffix;\n	else // TXP translation\n		return gTxt($phrase,$atts).$suffix;\n}\n\nfunction adi_matrix_style() {\n// some style (Classic: table#list, Remora/Hive: table.txp-list)\n	global $prefs,$adi_matrix_txp460;\n\n	echo \'<style type=\"text/css\">\n			/* adi_matrix */\n			/* general */\n			.txp-body { max-width:none }\n			table#list td { padding:0.5em }\n			.adi_matrix_button { margin:1em auto; text-align:center }\n			.txp-list { width:auto; margin:0 auto }\n			/* admin tab */\n			.adi_matrix_admin input.radio { margin-left:0.5em }\n			.adi_matrix_admin form { text-align:center }\n			.adi_matrix_field { text-align:left }\n			.adi_matrix_field label { display:block; float:left; width:8em }\n			.adi_matrix_field label.adi_matrix_label2 { width:auto }\n			.adi_matrix_field p { overflow:hidden; min-height:1.4em }\n			.adi_matrix_custom_field label { width:12em }\n			.adi_matrix_multi_checkboxes { margin:0.3em 0 0.5em; height:5em; padding:0.2em; overflow:auto; border:1px solid #ccc }\n			.adi_matrix_multi_checkboxes label { float:none; width:auto }\n			.adi_matrix_prefs { margin-top:5em; text-align:center }\n			.adi_matrix_prefs input { margin-left:1em }\n			.adi_matrix_prefs input.checkbox { margin-left:0.5em }\n			.adi_matrix_prefs input.smallerbox { margin-left:0 }\n			.adi_matrix_prefs .adi_matrix_radio label { margin-right:1em }\n			.adi_matrix_prefs .adi_matrix_radio input { margin-left:0.5em }\n			.adi_matrix_admin .adi_matrix_delete a { font-size:120% }\n			/* matrix tabs */\n			.adi_matrix_matrix h1 { margin-top:0; text-align:center; font-weight:bold }\n			.adi_matrix_matrix table#list th.adi_matrix_noborder { border:0 }\n			.adi_matrix_matrix table#list .adi_matrix_delete { width:4em; font-weight:normal; text-align:center }\n			.adi_matrix_matrix table#list .adi_matrix_delete a { font-size:120% }\n			.adi_matrix_matrix table#list td.adi_matrix_add { font-size:120% }\n			.adi_matrix_matrix .date input { margin-top:0 }\n			.adi_matrix_none { margin-top:2em; text-align:center }\n			.adi_matrix_field_title { white-space:nowrap } /* stops edit link dropping below */\n			.adi_matrix_timestamp { white-space:nowrap } /* stops date or time being split */\n			.adi_matrix_future a { font-weight:bold }\n			.adi_matrix_expired a { font-style:italic }\n			.adi_matrix_error input { border-color:#b22222; color:#b22222 }\n			.adi_matrix_matrix .adi_matrix_matrix_prefs { margin-top:4em; text-align:center }\n			.adi_matrix_grand_total { text-align:center }\n			.adi_matrix_edit_link a { display:inline-block; width:21px; height:21px; line-height:2; border:1px solid #ccc; border-radius:0.4em; background:url(theme/classic/view-text.png) 50% 50% no-repeat #FFF8DD }\n			.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { text-decoration:none; background-color:#fff }\n			/* matrix tabs 4.5 */\n			.txp-list .adi_matrix_timestamp .time input { margin-top:0.5em }\n			.txp-list .adi_matrix_timestamp { min-width:11em }\n			/* matrix tabs 4.6 */\n			.txp-list th,.txp-list td { padding-left:0.5em }\n			/* glz_custom_fields */\n			html[xmlns] td.glz_custom_date-picker_field.clearfix { display:table-cell!important } /* override clearfix  - for date-picker field */\n			/*.adi_matrix_matrix input.date-picker { float:left; width:7em }*/\n			.adi_matrix_matrix input.date-picker { width:7em } /* 2.0 layout tweak */\n			.adi_matrix_matrix td.glz_custom_date-picker_field a.dp-choose-date { display:block; clear:left } /* 2.0 layout tweak */\n			/*.adi_matrix_matrix td.glz_custom_date-picker_field { min-width:10em }*/ /* 2.0 layout tweak */\n			.adi_matrix_matrix input.time-picker { width:4em }\n			/* tinyMCE */\n			.adi_matrix_matrix .glz_text_area_field div.tie_div { overflow-y:scroll; width:17.6em; height:5.6em; padding:0.2em; border:1px solid; border-color:#aaa #eee #eee #aaa; background-color: #eee }\n			/* scrolling matrix */\n			.adi_matrix_scroll table#list th:first-child,\n			.adi_matrix_scroll table#list td:first-child { position:absolute; width:13%; left:0; top:auto; padding-right:1em; border-bottom-width:0 }\n			.adi_matrix_scroll table#list thead th:first-child { border-bottom-width:1px }\n			.adi_matrix_scroll div.scroll_box { width:87%; margin-left:13%; padding-bottom:1em;overflow-x:scroll; overflow-y:visible; border:solid #eee; border-width:0 1px }\n			.adi_matrix_scroll table#list tfoot td:first-child { border-top:1px solid #ddd }\n			.adi_matrix_scroll table#list tfoot td { border-bottom:0 }\n			.adi_matrix_scroll table#list tfoot td:first-child a { display:inline }\n			/* footer */\n			table#list tfoot td { font-weight:bold }\n			table#list tfoot td.desc a,\n			table#list tfoot td.asc a { width:auto; background-color:transparent; background-image:url(\"./txp_img/arrowupdn.gif\"); background-repeat: no-repeat; background-attachment: scroll; background-position: right -18px; padding-right: 14px; margin-right: 0pt }\n			table#list tfoot td.asc a { background-position: right 2px }\n		\';\n\n	if ($prefs[\'theme_name\'] == \'hive\')\n		echo \'\n			/* Hive theme (mostly stolen from Hive textpattern.css) */\n			.txp-list tfoot td.desc a, table#list tfoot td.asc a { background-image:none }\n			.txp-list tfoot td { text-shadow:1px 1px 0 rgba(255, 255, 255, 0.4); background-color:#eee; background-image:linear-gradient(#eee, #ddd); border-top:1px solid #ddd; border-right:1px solid #d0d0d0; border-bottom:1px solid #c4c4c4 }\n			.txp-list tfoot td:first-child { border-left:1px solid #ddd }\n			.txp-list tfoot td a { position:relative; display:block; color:#333; margin-right:.5em }\n			.txp-list tfoot td a:hover { color:#333; text-decoration:none }\n			.txp-list tfoot td a:after { position:absolute; top:.53846153846154em; right:-0.76923em; z-index:10; width:0; height:0; display:inline-block; *display:inline; *zoom:1; vertical-align:top; opacity:.5; text-indent:110%; white-space:nowrap; overflow:hidden }\n			.txp-list tfoot td a:hover:after { opacity:1 }\n			.txp-list tfoot td.desc a:after { content:\"&#8595;\"; border-left:.30769230769231em solid transparent; border-right:.30769230769231em solid transparent; border-top:0.30769em solid #333; border-bottom:0 }\n			.txp-list tfoot td.asc a:after { content:\"&#8593;\"; border-left:.30769230769231em solid transparent; border-right:.30769230769231em solid transparent; border-top:0; border-bottom:0.30769em solid #333 }\n			p.prev-next, form.pageby { text-align:center }\n			.adi_matrix_edit_link a { border:1px solid #eee; border-radius:0.5em; background:url(theme/classic/view-text.png) 50% 50% no-repeat #eee }\n			.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { background-color:#FFF6D3 }\n		\';\n\n	if ($adi_matrix_txp460) {\n		echo \'\n			/* TXP 4.6 only */\n			.adi_matrix_edit_link a { width:2em; height:2em; background-image:url(theme/classic/img/view-text.svg); border-radius:0 }\n		\';\n		if ($prefs[\'theme_name\'] == \'hive\')\n			echo \'\n				/* TXP 4.6 Hive only */\n				.adi_matrix_edit_link a { border:0; border-radius:0.5em }\n				.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { background-color:#FFF6D3 }\n			\';\n	}\n\n	echo \'</style>\'.n;\n}\n\nfunction adi_matrix_admin_script() {\n// jQuery magic for admin tab\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	$(function(){\n		$(\"#peekaboo\").hide();\n		$(\'input[name=\"adi_matrix_tiny_mce\"][value=\"1\"]:checked\').each(function(){\n			$(\"#peekaboo\").show();\n		});\n		$(\'input[name=\"adi_matrix_tiny_mce\"]:radio:eq(0)\').change(function(){\n			$(\"#peekaboo\").show();\n		});\n		$(\'input[name=\"adi_matrix_tiny_mce\"]:radio:eq(1)\').change(function(){\n			$(\"#peekaboo\").hide();\n		});\n	});\n</script>\n\nEND_SCRIPT;\n}\n\nfunction adi_matrix_matrix_script() {\n// jQuery action\n\n	// add class to <body>\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function(){\n		$(\'body\').addClass(\'adi_matrix\');\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_matrix_read_settings($just_the_basics=FALSE) {\n// get matrix settings from database\n	global $adi_matrix_cfs,$adi_matrix_debug;\n\n	$rs = safe_rows_start(\'*\',\'adi_matrix\',\"1=1\");\n	$matrix_list = array();\n	if ($rs)\n		while ($a = nextRow($rs)) {\n			extract($a);\n			// just enough to display matrix tab\n			$matrix_list[$id][\'name\'] = $name;\n			$matrix_list[$id][\'user\'] = $user;\n			$matrix_list[$id][\'privs\'] = $privs;\n			if (!isset($tab)) // tab introduced in v2.0, so may not be present during upgrade install\n				$tab = \'content\';\n			$matrix_list[$id][\'tab\'] = $tab;\n			// load in the rest\n			if (!$just_the_basics) {\n				$the_rest = array(\'sort\',\'dir\',\'sort_type\',\'scroll\',\'footer\',\'title\',\'publish\',\'show_section\',\'cf_links\',\'criteria_section\',\'criteria_category\',\'criteria_descendent_cats\',\'criteria_status\',\'criteria_author\',\'criteria_keywords\',\'criteria_timestamp\',\'criteria_expiry\',\'criteria_condition\',\'status\',\'keywords\',\'article_image\',\'category1\',\'category2\',\'posted\',\'expires\',\'section\');\n				foreach ($the_rest as $item)\n					$matrix_list[$id][$item] = $$item;\n				// custom fields\n				foreach ($adi_matrix_cfs as $index => $value) {\n					$custom_x = \'custom_\'.$index;\n					if (isset($$custom_x)) // check that custom field is known to adi_matrix\n						$matrix_list[$id][$custom_x] = $$custom_x;\n				}\n			}\n		}\n	return $matrix_list;\n}\n\nfunction adi_matrix_where($criteria=array()) {\n// take matrix criteria & create a WHERE clause\n// mostly ripped off from doArticles() in publish.php\n	global $adi_matrix_debug,$adi_matrix_categories;\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Criteria:</b>\";\n		dmp($criteria);\n	}\n\n	extract($criteria);\n	$excerpted = \'\';\n	$month = \'\';\n	$time = $timestamp;\n\n	// categories\n	$cats = array();\n	if ($category == \'!no_category!\')\n		$category = \" and (Category1 = \'\' and Category2 = \'\')\";\n	else if ($category == \'!any_category!\')\n		$category = \" and (Category1 != \'\' or Category2 != \'\')\";\n	else if ($category == \'!one_category!\')\n		$category = \" and (Category1 != \'\' and Category2 = \'\') or  (Category1 = \'\' and Category2 != \'\')\";\n	else if ($category == \'!two_categories!\')\n		$category = \" and (Category1 != \'\' and Category2 != \'\')\";\n	else if ($category == \'!any_parent_category!\') {\n		foreach ($adi_matrix_categories as $name => $this_cat)\n			if ($this_cat[\'children\'])\n				$cats[] = $name;\n		$category = implode(\',\',$cats);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n	else if ($category == \'!any_child_category!\') {\n		foreach ($adi_matrix_categories as $name => $this_cat)\n			if ($this_cat[\'parent\'] != \'root\')\n				$cats[] = $name;\n		$category = implode(\',\',$cats);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n	else { // single category (perhaps with optional descendents)\n		if ($descendent_cats)\n			$category .= \',\'.implode(\',\',$adi_matrix_categories[$category][\'children\']);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n\n	$section   = (!$section) ? \'\' : \" and Section IN (\'\".implode(\"\',\'\", doSlash(do_list($section))).\"\')\";\n	$excerpted = ($excerpted==\'y\') ? \" and Excerpt !=\'\'\" : \'\';\n	if ($author == \'!logged_in_user!\')\n		$author = $txp_user;\n	$author    = (!$author) ? \'\' : \" and AuthorID IN (\'\".implode(\"\',\'\", doSlash(do_list($author))).\"\')\";\n	$month     = (!$month) ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\n\n	// posted timestamp\n	switch ($time) {\n		case \'any\':\n			$time = \"\";\n			break;\n		case \'future\':\n			$time = \" and Posted > now()\";\n			break;\n		default:\n			$time = \" and Posted <= now()\";\n	}\n\n	// expiry\n	switch ($expiry) {\n		case \'1\': // no expiry set\n			$time .= \" and Expires = \".NULLDATETIME;\n			break;\n		case \'2\': // has expiry set\n			$time .= \" and Expires != \".NULLDATETIME;\n			break;\n		case \'3\': // expired\n			$time .= \" and now() > Expires and Expires != \".NULLDATETIME;\n			break;\n	}\n\n	$custom = \'\'; // MAY GET CONFUSING WITH criteria_condition\n//	if ($customFields) {\n//		foreach($customFields as $cField) {\n//			if (isset($atts[$cField]))\n//				$customPairs[$cField] = $atts[$cField];\n//		}\n//		if(!empty($customPairs)) {\n//			$custom = buildCustomSql($customFields,$customPairs);\n//		}\n//	}\n\n	if ($keywords) {\n		$keys = doSlash(do_list($keywords));\n		foreach ($keys as $key) {\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n		}\n		$keywords = \" and (\" . implode(\' or \',$keyparts) . \")\";\n	}\n\n	if ($status)\n		$statusq = \' and Status = \'.intval($status);\n	else // either blank or zero\n		$statusq = \'\'; // all statuses\n\n	if ($condition)\n		$conditionq = \' and \'.$condition;\n	else\n		$conditionq = \'\';\n\n	$where = \'1\'.$statusq.$time.$category.$section.$excerpted.$month.$author.$keywords.$custom.$conditionq;\n\n	if ($adi_matrix_debug) {\n		echo \"<b>WHERE clause:</b> $where\".br;\n	}\n\n	return $where;\n}\n\nfunction adi_matrix_order_by($sort,$dir,$sort_type) {\n// generate ORDER BY clause\n// mostly ripped off from doArticles() in publish.php\n	global $adi_matrix_debug;\n\n	// map columns to sort query\n	switch ($sort) {\n		case \'posted\':\n			$sortq = \'Posted\';\n			break;\n		case \'expires\':\n			$sortq = \'Expires\';\n			break;\n		case \'lastmod\':\n			$sortq = \'LastMod\';\n			break;\n		case \'title\':\n			$sortq = \'Title\';\n			break;\n		case \'id\':\n			$sortq = \'ID\';\n			break;\n		case \'status\':\n			$sortq = \'Status\';\n			break;\n		case \'article_image\':\n			$sortq = \'Image\';\n			break;\n		case \'category1\':\n			$sortq = \'Category1\';\n			break;\n		case \'category2\':\n			$sortq = \'Category2\';\n			break;\n		case \'section\':\n			$sortq = \'Section\';\n			break;\n		default: // custom_x will fall through to here\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'textpattern\').\" LIKE \'$sort\'\",$adi_matrix_debug); // find out if column (glz custom field probably) still exists\n			$a = nextRow($rs);\n			if (empty($a))\n				$sortq = \'Posted\';\n			else\n				$sortq = $sort;\n	}\n\n	// sort type\n	if ($sort_type == \'numerical\')\n		$sort_typeq = \' + 0\';\n	else\n		$sort_typeq = \'\';\n\n	// sort it all out\n	$sortq = \' ORDER BY \'.$sortq.$sort_typeq.\' \'.$dir.\', Posted desc\'; // add direction ... & also secondary sort (pointless but harmless with ID, Posted & Expires)\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Sort:</b> sort=$sort, dir=$dir, sort_type=$sort_type\".br;\n		echo \"<b>ORDER BY clause:</b> $sortq\".br;\n	}\n\n	return $sortq;\n}\n\nfunction adi_matrix_get_articles($where=\'1\',$sortq=\'\',$matrix_index,$offset,$limit) {\n// read required articles from database and populate $adi_matrix_articles\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_list,$adi_matrix_sort_options,$txp_user,$adi_matrix_timestamp_options;\n\n	$adi_matrix_articles = array();\n\n	// get the required articles from database\n	$rs = safe_rows_start(\n			\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\"\n			,\'textpattern\'\n			,$where.doSlash($sortq).\' LIMIT \'.intval($offset).\', \'.intval($limit)\n			,$adi_matrix_debug\n			);\n\n	if ($rs) // populate $adi_matrix_articles array\n		while ($a = nextRow($rs)) {\n			extract($a);\n			$adi_matrix_articles[$ID] = array();\n			$adi_matrix_articles[$ID][\'title\'] = html_entity_decode($Title, ENT_QUOTES, \'UTF-8\');\n			$adi_matrix_articles[$ID][\'section\'] = $Section;\n			$adi_matrix_articles[$ID][\'author\'] = $AuthorID; // need author for article edit priv check\n			$adi_matrix_articles[$ID][\'status\'] = $Status; // need status for article edit priv check\n			$adi_matrix_articles[$ID][\'keywords\'] = $Keywords;\n			$adi_matrix_articles[$ID][\'article_image\'] = $Image;\n			$adi_matrix_articles[$ID][\'category1\'] = $Category1;\n			$adi_matrix_articles[$ID][\'category2\'] = $Category2;\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				$adi_matrix_articles[$ID][$custom_x] = $$custom_x;\n			}\n\n			// article timestamps\n			$adi_matrix_articles[$ID][\'uposted\'] = $uPosted; // unix timestamp format (in server timezone)\n			$adi_matrix_articles[$ID][\'posted\'] = $Posted; // article date/time string (YY-MM-DD HH:MM:SS) from database\n			$adi_matrix_articles[$ID][\'display_posted\'] = safe_strftime(\'%Y-%m-%d %X\',$uPosted); // article date/time string (YY-MM-DD HH:MM:SS) displayed to user (TXP time)\n			$adi_matrix_articles[$ID][\'uexpires\'] = $uExpires; // unix timestamp format (in server timezone)\n			$adi_matrix_articles[$ID][\'expires\'] = $Expires; // article date/time string (YY-MM-DD HH:MM:SS) from database\n			if ($Expires == \'0000-00-00 00:00:00\')\n				$adi_matrix_articles[$ID][\'display_expires\'] = $Expires; // keep it zeroed\n			else\n				$adi_matrix_articles[$ID][\'display_expires\'] = safe_strftime(\'%Y-%m-%d %X\',$uExpires); // article date/time string (YY-MM-DD HH:MM:SS) displayed to user (TXP time)\n\n			// highlighting\n			$highlight = 0;\n			$now = time();\n			if (($now > $uExpires) && ($uExpires != 0)) // expired article\n				$highlight = 1;\n			if ($now < $uPosted) // future article\n				$highlight = 2;\n			$adi_matrix_articles[$ID][\'highlight\'] = $highlight;\n		}\n\n	return $adi_matrix_articles;\n}\n\nfunction adi_matrix_update_article($id,$data) {\n// translate $_POSTED article data into SQL-speak\n	global $adi_matrix_debug,$txp_user,$adi_matrix_cfs,$adi_matrix_articles,$vars,$prefs;\n\n	include_once txpath.\'/include/txp_article.php\'; // to get textile_main_fields()\n\n	// set up variables in the style of $vars\n	$Title = $Title_plain = isset($data[\'title\']) ? $data[\'title\'] : \'\';\n	$Status = isset($data[\'status\']) ? $data[\'status\'] : \'\';\n	$Section = isset($data[\'section\']) ? $data[\'section\'] : \'\';\n	$Keywords = isset($data[\'keywords\']) ? trim(preg_replace(\'/( ?[\\r\\n\\t,])+ ?/s\', \',\', preg_replace(\'/ +/\', \' \', $data[\'keywords\'])), \', \') : \'\';\n	$Image = isset($data[\'article_image\']) ? $data[\'article_image\'] : \'\';\n	$Category1 = isset($data[\'category1\']) ? $data[\'category1\'] : \'\';\n	$Category2 = isset($data[\'category2\']) ? $data[\'category2\'] : \'\';\n	// posted\n	if (isset($data[\'posted\'][\'reset_time\']))\n		$publish_now = \'1\';\n	else\n		$publish_now = \'\';\n	if (isset($data[\'posted\'])) { // this is in TXP date/time\n		extract($data[\'posted\']);\n		$Posted = $year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second;\n	}\n	else // force now\n		$publish_now = \'1\'; // NOT SURE WHY DOING THIS, BUT DOESN\'T DO ANY HARM (IS IGNORED IF POSTED NOT CHANGED)\n	// expires\n	if (isset($data[\'expires\'])) { // this is in TXP date/time\n		foreach ($data[\'expires\'] as $index => $value) { // convert expiry vars ($year -> $exp_year) to align with $vars in txp_article.php\n			$var = \'exp_\'.$index;\n			$$var = $value;\n		}\n		$Expires = $exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second;\n	}\n	else // force no expiry\n		$exp_year = \'0000\';\n	// custom Fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (isset($data[$custom_x]))\n			$$custom_x = $data[$custom_x];\n		else\n			$$custom_x = \'\';\n	}\n	// set the rest (not used by adi_matrix_update_article)\n	$Body = \'\';\n	$Body_html = \'\';\n	$Excerpt = \'\';\n	$Excerpt_html = \'\';\n	$textile_body = $prefs[\'use_textile\'];\n	$textile_excerpt = $prefs[\'use_textile\'];\n	$Annotate = \'0\';\n	$override_form = \'\';\n	$AnnotateInvite = \'\';\n\n	// package them all up\n	$updates = compact($vars);\n\n	if ($adi_matrix_debug) {\n		echo \'<b>Article \'.$id.\' data:</b>\';\n		dmp($updates);\n	}\n\n	// do some validation, textilation & slashing\n	$incoming = array_map(\'assert_string\',$updates);\n	$incoming = textile_main_fields($incoming);	// converts ampersands to &amp; in titles\n	extract(doSlash($incoming));\n	if (isset($data[\'status\']))\n		extract(array_map(\'assert_int\', array(\'Status\' => $Status)));\n\n	// title\n	if (isset($data[\'title\']))\n		$titleq = \"Title=\'$Title\', \";\n	else\n		$titleq = \'\';\n	// status\n	$old_status = $new_status = $adi_matrix_articles[$id][\'status\'];\n	if (isset($data[\'status\'])) {\n		$new_status = $Status;\n		// tweak status according to privs\n		if (!has_privs(\'article.publish\') && $new_status >= STATUS_LIVE)\n			$new_status = STATUS_PENDING;\n		$statusq = \'Status=\'.doSlash($new_status).\', \';\n		if ($new_status >= STATUS_LIVE) // live & sticky articles only\n			update_lastmod();\n	}\n	else\n		$statusq = \'\';\n	// section\n	if (isset($data[\'section\']))\n		$sectionq = \"Section=\'$Section\', \";\n	else\n		$sectionq = \'\';\n	// keywords\n	if (isset($data[\'keywords\']))\n		$keywordsq = \"Keywords=\'$Keywords\', \";\n	else\n		$keywordsq = \'\';\n	// article image\n	if (isset($data[\'article_image\']))\n		$article_imageq = \"Image=\'$Image\', \";\n	else\n		$article_imageq = \'\';\n	// categories\n	if (isset($data[\'category1\']))\n		$categoryq = \"Category1=\'$Category1\', \";\n	else\n		$categoryq = \'\';\n	if (isset($data[\'category2\']))\n		$categoryq .= \"Category2=\'$Category2\', \";\n	else\n		$categoryq .= \'\';\n	// posted\n	$postedq = \'\';\n	if (isset($data[\'posted\'])) {\n		if ($publish_now)\n			$postedq = \"Posted=now(), \";\n		else { // convert TXP date/time to DB timestamp\n			$ts = strtotime($Posted);\n			$date_error = ($ts === false || $ts === -1);\n			if (!$date_error) {\n				$when_ts = $ts - tz_offset($ts);\n				$when = \"from_unixtime($when_ts)\";\n				$postedq = \"Posted=$when, \";\n			}\n		}\n	}\n	// expires\n	$expiresq = \'\';\n	if (isset($data[\'expires\'])) {\n		if ($exp_year == \'0000\')\n			$expiry = 0;\n		else { // convert TXP date/time to DB timestamp\n			$ts = strtotime($Expires);\n			$expiry = $ts - tz_offset($ts);\n		}\n		if ($expiry) {\n			$date_error = ($ts === false || $ts === -1);\n			if (!$date_error) {\n				$expires = $ts - tz_offset($ts);\n				$whenexpires = \"from_unixtime($expires)\";\n				$expiresq = \"Expires=$whenexpires, \";\n			}\n		}\n		else\n			$expiresq = \"Expires=\".NULLDATETIME.\", \";\n	}\n	// custom fields\n	$cfq = array();\n	foreach($adi_matrix_cfs as $i => $cf_name) {\n		$custom_x = \"custom_{$i}\";\n		if (isset($data[$custom_x]))\n			$cfq[] = \"custom_$i = \'\".$$custom_x.\"\'\";\n	}\n	$cfq = implode(\', \', $cfq);\n\n	// update article in database\n	$res = safe_update(\"textpattern\",\n	   $titleq.$sectionq.$statusq.$keywordsq.$article_imageq.$categoryq.$postedq.$expiresq.(($cfq) ? $cfq.\', \' : \'\').\"LastMod=now(), LastModID=\'$txp_user\'\",\n		\"ID=$id\",\n		$adi_matrix_debug\n	);\n\n	if ($new_status >= STATUS_LIVE && $old_status < STATUS_LIVE)\n		do_pings();\n\n	if ($new_status >= STATUS_LIVE || $old_status >= STATUS_LIVE)\n		update_lastmod();\n\n	return $res;\n}\n\nfunction adi_matrix_update_articles($updates,$matrix_index) {\n// update articles\n\n	$res = TRUE;\n	if ($updates) {\n		foreach ($updates as $id => $data)\n			if ($id == \'new\')\n				$res = $res && adi_matrix_publish_article($data,$matrix_index);\n			else\n				$res = $res && adi_matrix_update_article($id,$data);\n	}\n	return $res;\n}\n\nfunction adi_matrix_article_defaults($matrix_index) {\n// default values for new article, adjusted for specified matrix\n	global $adi_matrix_debug,$adi_matrix_list,$prefs,$adi_matrix_cfs,$adi_matrix_glz_cfs;\n\n//	Article field	$defaults[\'xx\']	Who determines						Default values\n//	-------------	---------------	--------------						--------------\n//	ID				-				MySQL								generated on publish\n//	Posted			posted			adi_matrix_article_defaults,user	current date/time\n//	Expires			expires			adi_matrix_article_defaults,user	blank (converted to 0000-00-00 00:00:00 by adi_matrix_validate_post_data)\n//	AuthorID		-				adi_matrix_publish_article			current user\n//	LastMod			-				adi_matrix_publish_article			generated on publish\n//	LastModID		-				adi_matrix_publish_article			current user\n//	Title			title			adi_matrix_article_defaults,user	blank\n//	Title_html		-				adi_matrix_publish_article			blank\n//	Body			-				adi_matrix_publish_article			blank\n//	Body_html		-				adi_matrix_publish_article			blank\n//	Excerpt			-				adi_matrix_publish_article			blank\n//	Excerpt_html	-				adi_matrix_publish_article			blank\n//	Image			article_image	adi_matrix_article_defaults,user	blank\n//	Category1		category1		adi_matrix_article_defaults,user	criteria_category (if specific category set), blank\n//	Category2		category2		adi_matrix_article_defaults,user	blank\n//	Annotate		-				adi_matrix_publish_article			0\n//	AnnotateInvite	-				adi_matrix_publish_article			blank\n//	comments_count	-				MySQL								default\n//	Status			status			adi_matrix_article_defaults,user	criteria_status (if set), \"live\"\n//	textile_body	-				adi_matrix_publish_article			\'use_textile\' from $prefs\n//	textile_excerpt	-				adi_matrix_publish_article			\'use_textile\' from $prefs\n//	Section			section			adi_matrix_article_defaults			first section from criteria_section	(if set), \'default_section\' from $prefs\n//	override_form	-				adi_matrix_publish_article			blank\n//	Keywords		keywords		adi_matrix_article_defaults,user	criteria_keywords (if set), blank\n//	url_title		-				adi_matrix_publish_article			generated-from-title\n//	custom_x		custom_x		adi_matrix_article_defaults,user	blank (if TXP CFs), GLZ default value (if GLZ CFs)\n//	uid				-				adi_matrix_publish_article			generated on publish\n//	feed_time		-				adi_matrix_publish_article			generated on publish\n\n	$defaults = array();\n\n	// title - blank\n	$defaults[\'title\'] = \'\';\n	// status - from criteria (or live if not set)\n	if ($adi_matrix_list[$matrix_index][\'criteria_status\'])\n		$defaults[\'status\'] = $adi_matrix_list[$matrix_index][\'criteria_status\'];\n	else\n		$defaults[\'status\'] = \'4\';\n	// article image - blank\n	$defaults[\'article_image\'] = \'\';\n	// keywords - from criteria\n	$defaults[\'keywords\'] = $adi_matrix_list[$matrix_index][\'criteria_keywords\'];\n	// category1 - if specific category set - assign it to cat1, otherwise leave it up to user (i.e. blank)\n	if (($adi_matrix_list[$matrix_index][\'criteria_category\']) && (strpos($adi_matrix_list[$matrix_index][\'criteria_category\'],\'!\') === FALSE))\n		$defaults[\'category1\'] = $adi_matrix_list[$matrix_index][\'criteria_category\'];\n	else\n		$defaults[\'category1\'] = \'\';\n	// category2 - leave blank\n	$defaults[\'category2\'] = \'\';\n	// posted\n	$defaults[\'uposted\'] = time();\n	$defaults[\'posted\'] = date(\"Y-m-d H:i:s\",$defaults[\'uposted\']);\n	// expires - blank\n	$defaults[\'uexpires\'] = \'\';\n	$defaults[\'expires\'] = \'\';\n	// section - $prefs default_section if blank, else first section on list if criteria set\n	if ($adi_matrix_list[$matrix_index][\'criteria_section\']) {\n		$sections = explode(\',\',$adi_matrix_list[$matrix_index][\'criteria_section\']);\n		$defaults[\'section\'] = $sections[0];\n	}\n	else\n		$defaults[\'section\'] = $prefs[\'default_section\'];\n	// custom fields - blank\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		$defaults[$custom_x] = \'\';\n	}\n	// glean GLZ CF defaults & apply them\n	if ($adi_matrix_glz_cfs) {\n		$all_custom_sets = glz_custom_fields_MySQL(\"all\"); // array indexed by custom_xx_set, value array(name,position,type)\n		if ($adi_matrix_debug)\n			echo \"<b>GLZ custom field defaults</b>:\".br;\n		foreach ($all_custom_sets as $custom => $custom_set) { // index format is \"custom_x_set\"\n			$arr_custom_field_values = glz_custom_fields_MySQL(\"values\",$custom,\'\',array(\'custom_set_name\' => $custom_set[\'name\']));\n			$default_value = glz_return_clean_default(glz_default_value($arr_custom_field_values));\n			$custom_x = str_replace(\'_set\',\'\',$custom); // convert custom_x_set to custom_x\n			if ($adi_matrix_debug) echo $custom_x.\' default = \'.\'[\'.$default_value.\']\'.br;\n			$defaults[$custom_x] = $default_value; // use GLZ default\n		}\n	}\n\n	if ($adi_matrix_debug) {\n		echo br.\'<b>New article defaults:</b>\';\n		dmp($defaults);\n	}\n\n	return $defaults;\n}\n\nfunction adi_matrix_publish_article($data,$matrix_index) {\n// new article - based on article_post() from txp_article.php\n// update database with posted data or article defaults\n	global $adi_matrix_debug,$adi_matrix_cfs,$txp_user,$prefs,$vars,$step,$adi_matrix_list,$adi_matrix_article_defaults;\n\n	include_once txpath.\'/include/txp_article.php\'; // to get article_validate() (TXP 4.5.0+), textile_main_fields()\n\n	$defaults = $adi_matrix_article_defaults; // life\'s still to short, so make variable name short\n\n	// translate adi_matrix stuff into article_post() stuff\n	$Title = $Title_plain = $data[\'title\'];\n	$Body = \'\';\n	$Body_html = \'\';\n	$Excerpt = \'\';\n	$Excerpt_html = \'\';\n	$textile_body = $prefs[\'use_textile\'];\n	$textile_excerpt = $prefs[\'use_textile\'];\n	$Annotate = \'0\';\n	$override_form = \'\';\n	$AnnotateInvite = \'\';\n	// article image\n	if (isset($data[\'article_image\']))\n		$Image = $data[\'article_image\'];\n	else\n		$Image = $defaults[\'article_image\'];\n	// keywords\n	if (isset($data[\'keywords\']))\n		$Keywords = trim(preg_replace(\'/( ?[\\r\\n\\t,])+ ?/s\', \',\', preg_replace(\'/ +/\', \' \', $data[\'keywords\'])), \', \');\n	else\n		$Keywords = $defaults[\'keywords\'];\n	// status\n	if (isset($data[\'status\']))\n		$Status = $data[\'status\'];\n	else\n		$Status = $defaults[\'status\'];\n	// posted\n	if (isset($data[\'posted\'][\'reset_time\']))\n		$publish_now = \'1\';\n	else\n		$publish_now = \'\';\n	if (isset($data[\'posted\'])) { // this is in TXP date/time\n		extract($data[\'posted\']);\n		$Posted = $year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second;\n	}\n	else // force now\n		$publish_now = \'1\';\n	// expires\n	if (isset($data[\'expires\'])) { // this is in TXP date/time\n		foreach ($data[\'expires\'] as $index => $value) { // convert expiry vars ($year -> $exp_year) to align with $vars in txp_article.php\n			$var = \'exp_\'.$index;\n			$$var = $value;\n		}\n		$Expires = $exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second;\n	}\n	else // force no expiry\n		$exp_year = \'0000\';\n	// section\n	if (isset($data[\'section\']))\n		$Section = $data[\'section\'];\n	else\n		$Section = $defaults[\'section\'];\n	// categories\n	$Category1 = isset($data[\'category1\']) ? $data[\'category1\'] : $defaults[\'category1\'];\n	$Category2 = isset($data[\'category2\']) ? $data[\'category2\'] : $defaults[\'category2\'];\n	// custom Fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if (isset($data[$custom_x])) // present in data posted\n				$$custom_x = $data[$custom_x];\n			else // not present in posted data, so set default\n				$$custom_x = $defaults[$custom_x];\n	}\n\n	// package them all up\n	$new = compact($vars);\n\n	if ($adi_matrix_debug) {\n		echo \'<b>New article data:</b>\';\n		dmp($new);\n	}\n\n	// all fields are strings ...\n	$incoming = array_map(\'assert_string\',$new);\n\n	// textilation (converts ampersands to &amp; in titles)\n	$incoming = textile_main_fields($incoming);\n\n	// slash attack\n	extract(doSlash($incoming));\n\n	// ... except some are more integer than string\n	extract(array_map(\'assert_int\', array(\'Status\' => $Status, \'textile_body\' => $textile_body, \'textile_excerpt\' => $textile_excerpt)));\n	$Annotate = (int) $Annotate;\n\n	// set posted timestamp (already validated by adi_matrix_validate_post_data)\n	if ($publish_now == 1) {\n		$when = \'now()\';\n		$when_ts = time();\n	}\n	else { // convert TXP date/time to DB timestamp\n		$ts = strtotime($year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second);\n		$when_ts = $ts - tz_offset($ts);\n		$when = \"from_unixtime($when_ts)\";\n	}\n\n	// and I quote: \"Force a reasonable \'last modified\' date for future articles, keep recent articles list in order\"\n	$lastmod = ($when_ts > time() ? \'now()\' : $when);\n\n	// set expiry timestamp (already validated/massaged by adi_matrix_get_post_data)\n	if ($exp_year == \'0000\')\n		$expires = 0;\n	else { // convert TXP date/time to DB timestamp\n		$ts = strtotime($exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second);\n		$expires = $ts - tz_offset($ts);\n	}\n	if ($expires)\n		$whenexpires = \"from_unixtime($expires)\";\n	else\n		$whenexpires = NULLDATETIME;\n\n	// who\'s doing the doing?\n	$user = doSlash($txp_user);\n\n	$msg = \'\';\n\n	// tweak status according to privs\n	if (!has_privs(\'article.publish\') && $Status >= STATUS_LIVE)\n		$Status = STATUS_PENDING;\n\n	// set url-title\n	if (empty($url_title))\n		$url_title = stripSpace($Title_plain, 1);\n\n	// custom fields\n	$cfq = array();\n	foreach($adi_matrix_cfs as $i => $cf_name) {\n		$custom_x = \"custom_{$i}\";\n		if (isset($$custom_x))\n			$cfq[] = \"custom_$i = \'\".$$custom_x.\"\'\";\n	}\n	$cfq = implode(\', \',$cfq);\n\n	$rs = compact($vars);\n\n	if ($adi_matrix_debug) {\n		article_validate($rs, $msg);\n		echo \'<b>article_validate():</b>\';\n		dmp($msg);\n	}\n\n	if (article_validate($rs, $msg)) {\n		$ok = safe_insert(\n		   \"textpattern\",\n		   \"Title           = \'$Title\',\n			Body            = \'$Body\',\n			Body_html       = \'$Body_html\',\n			Excerpt         = \'$Excerpt\',\n			Excerpt_html    = \'$Excerpt_html\',\n			Image           = \'$Image\',\n			Keywords        = \'$Keywords\',\n			Status          =  $Status,\n			Posted          =  $when,\n			Expires         =  $whenexpires,\n			AuthorID        = \'$user\',\n			LastMod         =  $lastmod,\n			LastModID       = \'$user\',\n			Section         = \'$Section\',\n			Category1       = \'$Category1\',\n			Category2       = \'$Category2\',\n			textile_body    =  $textile_body,\n			textile_excerpt =  $textile_excerpt,\n			Annotate        =  $Annotate,\n			override_form   = \'$override_form\',\n			url_title       = \'$url_title\',\n			AnnotateInvite  = \'$AnnotateInvite\',\"\n			.(($adi_matrix_cfs) ? $cfq.\',\' : \'\').\n			\"uid            = \'\".md5(uniqid(rand(),true)).\"\',\n			feed_time       = now()\"\n			,$adi_matrix_debug\n		);\n\n		if ($ok) {\n			if ($Status >= STATUS_LIVE) {\n				do_pings();\n				update_lastmod();\n			}\n			return TRUE;\n		}\n	}\n\n	return FALSE;\n}\n\nfunction adi_matrix_delete_article($id) {\n// delete an article - ripped off from list_multi_edit() in txp_list.php\n	global $txp_user,$adi_matrix_debug;\n\n	// keeping the multi-edit/array thing going - just in case\n	$selected = array();\n	$selected[] = $id;\n\n	// is allowed?\n	if (!has_privs(\'article.delete\')) {\n		$allowed = array();\n		if (has_privs(\'article.delete.own\'))\n			$allowed = safe_column_num(\'ID\', \'textpattern\', \'ID in(\'.implode(\',\',$selected).\') and AuthorID=\\\'\'.doSlash($txp_user).\'\\\'\',$adi_matrix_debug);\n		$selected = $allowed;\n	}\n\n	// in the bin\n	foreach ($selected as $id) {\n		if (safe_delete(\'textpattern\', \"ID = $id\",$adi_matrix_debug))\n			$ids[] = $id;\n	}\n\n	// housekeeping\n	$changed = implode(\', \', $ids);\n	if ($changed) {\n		safe_update(\'txp_discuss\', \"visible = \".MODERATE, \"parentid in($changed)\",$adi_matrix_debug);\n		return TRUE;\n	}\n\n	return FALSE;\n}\n\nfunction adi_matrix_glz_cfs_input($custom_x,$var,$val) {\n// output custom field input according to glz_custom_fields format\n	global $adi_matrix_debug;\n\n	$row = safe_row(\'html\',\'txp_prefs\',\"name = \'\".$custom_x.\"_set\'\"); // get html input type from prefs\n	$html = $row[\'html\'];\n\n	$arr_custom_field_values = glz_custom_fields_MySQL(\"values\", $custom_x.\"_set\", \'\', array(\'custom_set_name\' => $custom_x.\"_set\"));\n	$default_value = glz_return_clean_default(glz_default_value($arr_custom_field_values));\n	if (is_array($arr_custom_field_values))\n		array_walk($arr_custom_field_values, \"glz_clean_default_array_values\"); // from glz_custom_fields_replace()\n	// glz radio reset - relies on name=\"field\", which has to match for=\"field\" in main label, which picks up for=\"field_value\" in sub labels but adi_matrix don\'t have main label & needs name=\"article_xx[field_xx]\" anyway - ne\'er the twain shall neet! (would have to write our own reset jQuery I guess)\n	// glz radio - uses an ID based on field & value i.e. field_value, but adi_matrix ends up as article_2[custom_3]_value which ain\'t valid (& don\'t work in jQuery anyway)\n	// glz checkbox - uses an ID based on value only, so may get duplicate ID warnings on Write tab (& definitely on matrix tab)!!!\n	if ($html == \'radio\') { // create a clean ID prefix (i.e. without [ or ]) for radio buttons - to get rid of some error messages\n		$glz_id_prefix = str_replace(\'[\',\'_\',$var);\n		$glz_id_prefix = str_replace(\']\',\'_\',$glz_id_prefix);\n	}\n	else\n		$glz_id_prefix = \'\';\n\n	$out = glz_format_custom_set_by_type($var,$glz_id_prefix,$html,$arr_custom_field_values,$val,$default_value);\n\n	return $out; // html in $out[0], glz class in $out[1]\n}\n\nfunction adi_matrix_get_post_data($adi_matrix_articles,$matrix_index) {\n// analyse submitted article data ($_POST), massage if necessary, & create new list of articles & data ($adi_matrix_post)\n	global $adi_matrix_list,$adi_matrix_cfs,$adi_matrix_glz_cfs,$adi_matrix_debug;\n\n	if ($adi_matrix_debug) {\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n	}\n\n 	// copy $_POST[\'article_xx\'] to $adi_matrix_post[xx] (where xx is article ID or \"new\")\n	$adi_matrix_post = array();\n	foreach ($_POST as $index => $value) {\n 		$this_index = explode(\'_\',$index);\n 		if (strpos($index,\'article_\') === 0) { // pick out anything from $_POST that starts with \"article_\" ... i.e. article_xx or article_new\n 			if ($adi_matrix_glz_cfs) // tweak POSTED values to convert from array to bar|separated|list - based on glz_custom_fields_before_save()\n 				foreach ($value as $key => $val) {\n 					if (strstr($key, \'custom_\') && is_array($val)) { // check for custom fields with multiple values e.g. arrays\n 						$val = implode($val,\'|\');\n 						$value[$key] = $val;\n 					}\n 				}\n 			$adi_matrix_post[$this_index[1]] = $value;\n 		}\n 	}\n\n	// new article fiddling\n	if (isset($adi_matrix_post[\'new\'])) {\n		if (trim($adi_matrix_post[\'new\'][\'title\']) == \'\') // remove from the equation if title is blank\n			unset($adi_matrix_post[\'new\']);\n	}\n\n	// check for missing GLZ custom field values & fire blanks if necessary - required for checkboxes/multiselects that have been completely unchecked (otherwise updates not registered)\n	// will also pick up radios & multiselects (though deselected multiselect may be present in $_POST anyway)\n	if ($adi_matrix_glz_cfs) {\n		if ($adi_matrix_debug) echo \'<b>GLZ blanks generated for:</b>\'.br;\n		foreach ($adi_matrix_articles as $id => $article_data) { // check all articles (& new) on page (article may be absent from POST if checkbox is the only data field & it\'s completely unticked)\n			if ($adi_matrix_debug) echo \"Article $id - \";\n			if (!array_key_exists($id,$adi_matrix_post)) { // article missing from POST completely\n				if ($adi_matrix_debug) echo \"(article absent) \";\n				$adi_matrix_post[$id] = array();\n			}\n			foreach ($adi_matrix_cfs as $index => $title) { // check each custom field\n				if ($adi_matrix_list[$matrix_index][\'custom_\'.$index]) { // only interested in custom field if it\'s visible in this matrix\n					if (!array_key_exists(\'custom_\'.$index,$adi_matrix_post[$id])) { // custom field absent from article in POST\n						$adi_matrix_post[$id][\'custom_\'.$index] = \'\'; // fire a blank\n						if ($adi_matrix_debug) echo \"custom_$index \";\n					}\n				}\n			}\n			if ($adi_matrix_debug) echo br;\n		}\n		if ($adi_matrix_debug) echo br;\n	}\n\n	// expires - change all blanks to all zeroes (existing & \"new\"), coz stored as zeroes but displayed as blanks\n	foreach ($adi_matrix_post as $id => $this_article) { // check each article\n		if (array_key_exists(\'expires\',$this_article))\n			if (($this_article[\'expires\'][\'year\'] == \'\') && ($this_article[\'expires\'][\'month\'] == \'\') && ($this_article[\'expires\'][\'day\'] == \'\') && ($this_article[\'expires\'][\'hour\'] == \'\') && ($this_article[\'expires\'][\'minute\'] == \'\') && ($this_article[\'expires\'][\'second\'] == \'\')) {\n				$adi_matrix_post[$id][\'expires\'][\'year\'] = \'0000\';\n				$adi_matrix_post[$id][\'expires\'][\'month\'] = $adi_matrix_post[$id][\'expires\'][\'day\'] = $adi_matrix_post[$id][\'expires\'][\'hour\'] = $adi_matrix_post[$id][\'expires\'][\'minute\'] = $adi_matrix_post[$id][\'expires\'][\'second\'] = \'00\';\n			}\n	}\n\n	if ($adi_matrix_debug) {\n		echo \'<b>$adi_matrix_post:</b>\';\n		dmp($adi_matrix_post);\n	}\n\n	return $adi_matrix_post;\n}\n\nfunction adi_matrix_get_updates($adi_matrix_post,$adi_matrix_articles) {\n// compare submitted article data with database data & create $adi_matrix_updates[id][field] if changed\n	global $adi_matrix_debug;\n\n	if ($adi_matrix_debug)\n		echo \'<b>Update processing (adi_matrix_get_updates):</b>\'.br;\n\n	$adi_matrix_updates = array();\n	foreach ($adi_matrix_post as $id => $data) {\n		foreach ($data as $field => $new_value) {\n			if (($field == \'posted\') || ($field == \'expires\'))\n				$date_string = $new_value[\'year\'].\'-\'.$new_value[\'month\'].\'-\'.$new_value[\'day\'].\' \'.$new_value[\'hour\'].\':\'.$new_value[\'minute\'].\':\'.$new_value[\'second\']; // set up new date/time string\n			if ($id == \'new\') { // new article\n				$adi_matrix_updates[$id][$field] = $new_value;\n				if (($field == \'posted\') || ($field == \'expires\'))\n					$new_value = $date_string; // use new time date/time string\n				if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', new_value=\'.$new_value.\' (NEW ARTICLE)\'.br;\n			}\n			else { // existing article\n				$equal = TRUE;\n				$old_value = $adi_matrix_articles[$id][$field];\n				$test_value = $new_value;\n				if ($field == \'keywords\') // remove human friendly spaces after commas\n					$test_value = str_replace(\', \' ,\',\', $new_value);\n				if (($field == \'posted\') || ($field == \'expires\')) // date/time requires special attention (because DB time may be different to TXP human time, so direct comparison not valid)\n					if (array_key_exists(\'reset_time\',$new_value)) {\n						$equal = FALSE; // force inequality - \"NOW()\" time will be set in database update\n						if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\' (RESET)\';\n					}\n					else { // use article\'s DB unix timestamp & convert to TXP date/time for comparison\n						$ufield = \'u\'.$field; // tweak field name from posted/expires to uposted/uexpires\n						if ($adi_matrix_articles[$id][$ufield])\n							$old_value = safe_strftime(\"%Y-%m-%d %X\",$adi_matrix_articles[$id][$ufield]); // use DB unix timestamp & convert to TXP date/time\n						else\n							$old_value = \'0000-00-00 00:00:00\';\n						$test_value = $date_string; // use new time date/time string\n						if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', old_value=\'.$old_value.\', test_value=\'.$test_value;\n					}\n				else\n					if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', new_value=\'.$new_value.\', old_value=\'.$old_value.\', test_value=\'.$test_value;\n				$equal = $equal && (strcmp($test_value,$old_value) == 0);\n				if ($adi_matrix_debug)\n					if ($equal) echo \" (EQUAL)\".br; else echo \" <b>(NOT EQUAL)</b>\".br;\n				if (!$equal)\n					$adi_matrix_updates[$id][$field] = $new_value;\n			}\n		}\n	}\n\n	if ($adi_matrix_debug) {\n		echo br.\'<b>$adi_matrix_updates:</b>\';\n		dmp($adi_matrix_updates);\n	}\n\n	return $adi_matrix_updates;\n}\n\nfunction adi_matrix_validate_post_data($adi_matrix_articles,$post_data) {\n// article data validation\n	global $adi_matrix_debug,$adi_matrix_validation_errors;\n\n	// create array of empties indexed by $adi_matrix_validation_errors id\n	$new_error_list = array();\n	foreach ($adi_matrix_validation_errors as $i => $v)\n		$new_error_list[$i] = array();\n\n	foreach ($post_data as $id => $data) {\n\n		// add empty \"error\" slots for article\n		foreach ($adi_matrix_validation_errors as $i => $v)\n			$new_error_list[$i][$id] = array();\n\n		// remember old timestamp values (existing articles only)\n		if ($id != \'new\') {\n			$posted = $adi_matrix_articles[$id][\'posted\'];\n			$expires = $adi_matrix_articles[$id][\'expires\'];\n		}\n\n		// iterate through $data (OTT but may change in the future)\n		foreach ($data as $field => $value) {\n			// do some date/time checking\n			if (($field == \'posted\') || ($field == \'expires\')) {\n				// record new (i.e. $_POSTed) timestamp values\n				$$field = $value[\'year\'].\'-\'.$value[\'month\'].\'-\'.$value[\'day\'].\' \'.$value[\'hour\'].\':\'.$value[\'minute\'].\':\'.$value[\'second\'];\n				if ($field == \'posted\')\n					if (array_key_exists(\'reset_time\',$value))\n						$$field = date(\'Y-m-d H:i:s\',time()); // have to predict the reset date/time (Article tab does it this way too!)\n				// check it\'s a valid date/time\n				$error = (!is_numeric($value[\'year\']) || !is_numeric($value[\'month\']) || !is_numeric($value[\'day\']) || !is_numeric($value[\'hour\'])  || !is_numeric($value[\'minute\']) || !is_numeric($value[\'second\']));\n				$ts = strtotime($value[\'year\'].\'-\'.$value[\'month\'].\'-\'.$value[\'day\'].\' \'.$value[\'hour\'].\':\'.$value[\'minute\'].\':\'.$value[\'second\']);\n				$error = $error || ($ts === FALSE || $ts === -1);\n				// special case - allow all blanks in expires\n				if ($error && ($field == \'expires\'))\n					$error = !(empty($value[\'year\']) && empty($value[\'month\']) && empty($value[\'day\']) && empty($value[\'hour\']) && empty($value[\'minute\']) && empty($value[\'second\']));\n				if ($error) {\n					if ($id != \'new\')\n						$$field = $adi_matrix_articles[$id][$field]; // restore old value (so it doesn\'t influence later \"expires before posted\" checking)\n					$new_error_list[\'0\'][$id][] = $field;\n				}\n			}\n		}\n\n		// check expires is not before posted (but only if expires is set)\n		if ((strtotime($expires) < strtotime($posted)) && ($expires != \'0000-00-00 00:00:00\')) {\n			$new_error_list[\'1\'][$id][] = \'posted\';\n			$new_error_list[\'1\'][$id][] = \'expires\';\n		}\n\n		// check URL-titles (duplicates & blanks)\n		// title supplied if new or edited, get title if not supplied\n		if (isset($data[\'title\']))\n			$title = $data[\'title\'];\n		else\n			$title = $adi_matrix_articles[$id][\'title\'];\n		$msg = 0;\n		$url_title = stripSpace($title, 1);\n		if (trim($url_title) == \'\') // blank\n			$msg = 3;\n		if ($msg) {\n			$new_error_list[$msg][$id][] = \'url_title\';\n		}\n		$msg = 0;\n		// duplicates?\n		$url_title_count = safe_count(\'textpattern\',\"url_title = \'$url_title\'\");\n		if (($url_title_count > 1) || (($id == \'new\') && $url_title_count)) { // duplicates found (existing articles: multiple in DB, new article: one or more in DB)\n			$msg = 2;\n			// get ids of all other articles with matching URL-only titles (they may not be in this matrix, or in this page of this matrix)\n			$duplicates = safe_rows(\'id\',\'textpattern\',\"url_title = \'$url_title\'\"); // returns array of arrays containing \'id\' => id#\n			if ($id == \'new\')\n				$duplicates[][\'id\'] = adi_matrix_gtxt(\'adi_new_article\'); // using \"new article\" in duplicates list coz don\'t have new article list\n		}\n		if ($msg) {\n			foreach ($duplicates as $duplicate) {\n				$new_error_list[$msg][$duplicate[\'id\']][] = \'url_title\';\n			}\n		}\n\n	}\n\n	// lose the empties\n	$new_error_list = array_filter(array_map(\'array_filter\', $new_error_list));\n\n	if ($adi_matrix_debug) {\n		echo \'<b>Invalid fields:</b>\';\n		dmp($new_error_list);\n	}\n\n	return $new_error_list;\n}\n\nfunction adi_matrix_remove_errors($updates,$errors) {\n// remove fields with invalid data from article update list\n	global $adi_matrix_debug;\n\n	foreach ($errors as $article)\n		foreach ($article as $id => $fields)\n			foreach ($fields as $field)\n				unset($updates[$id][$field]);\n\n	return $updates;\n}\n\nfunction adi_matrix_debug($adi_matrix_articles,$matrix_index) {\n// plot in the title\n	global $event,$step,$adi_matrix_cfs,$adi_matrix_list,$adi_matrix_glz_cfs,$adi_matrix_categories,$txp_user;\n\n	echo \"<b>Event:</b> \".$event.\", <b>Step:</b> \".$step.br;\n	echo \"<b>Date/time:</b> \"\n		.\'date = \'.date(\"Y-m-d H:i:s\")\n		.\'; tz_offset() = \'.tz_offset()\n		.\'; date + tz_offset() = \'.date(\"Y-m-d H:i:s\",time()+tz_offset()) // this is actual (TXP adjusted) local time (incl DST) regardless of server timezone\n		.br;\n	$rs = safe_query(\'SELECT NOW()\');\n	$a = nextRow($rs);\n	if ($a)\n		echo \'<b>Article timestamp in DB &hellip; SELECT NOW()</b> = \'.current($a).br;\n	else\n		echo \'Unable to determine article DB time\'.br;\n	$rs = safe_query(\'SELECT UNIX_TIMESTAMP(NOW())\');\n	$a = nextRow($rs);\n	$article_time = current($a);\n	if ($a) {\n		echo \'<b>Article time displayed in article tab &hellip; safe_strftime(\"%Y-%m-%d %X\")</b> = \'.safe_strftime(\"%Y-%m-%d %X\",$article_time).br;\n		$posted = safe_strftime(\'%Y-%m-%d %X\');\n		$ts = strtotime($posted);\n		$when_ts = $ts - tz_offset($ts);\n		$rs = safe_query(\"SELECT FROM_UNIXTIME($when_ts)\");\n		$a = nextRow($rs);\n		if ($a)\n			echo \'<b>Article date/time written to DB</b>  = \'.current($a).br;\n		else\n			echo \'Unable to determine date/time written to DB\'.br;\n	}\n	else\n		echo \'Unable to determine article tab timestamp\'.br;\n	echo br;\n	echo \'<b>This matrix:</b>\';\n	dmp($adi_matrix_list[$matrix_index]);\n	echo \'<b>$adi_matrix_cfs:</b>\';\n	dmp($adi_matrix_cfs);\n	if ($adi_matrix_glz_cfs) {\n		echo \'<b>Custom field input types:</b><br/>\';\n		foreach ($adi_matrix_cfs as $index => $title) {\n			$row = safe_row(\'html\',\'txp_prefs\',\"name = \'custom_\".$index.\"_set\'\"); // get html input type from prefs\n			echo \'custom_\'.$index.\' - \'.$row[\'html\'].\'<br/>\';\n		}\n		echo \'<br/>\';\n	}\n	echo \'<b>$adi_matrix_categories:</b><br/>\';\n	dmp($adi_matrix_categories);\n	echo \'<b>$adi_matrix_articles:</b>\';\n	dmp($adi_matrix_articles);\n}\n\nfunction adi_matrix_table_head($matrix_index,$type) {\n// matrix <table> header stuff\n// note that clicking a column header will reset back to the first page, which is standard TXP behaviour\n// - to fix would involve a custom elink() function to pass the \'page\' variable on\n	global $event,$adi_matrix_cfs,$adi_matrix_list;\n\n	// get current sort settings\n	list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n\n	if ($type == \'header\') {\n		$tag = \'th\';\n		$wraptag = \'thead\';\n	}\n	else {\n		$tag = \'td\';\n		$wraptag = \'tfoot\';\n	}\n\n	// article id/title heading\n	$field_list = array(\'id\',\'title\');\n	foreach ($field_list as $field) {\n		$var = $field.\'_hcell\';\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$field; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		$$var = tag(elink($event,\'\',\'sort\',$field,adi_matrix_gtxt($field),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class); // column heading/toggle sort\n	}\n\n	// standard field headings\n	$field_list = array(\'status\',\'article_image\',\'keywords\',\'category1\',\'category2\',\'posted\',\'expires\',\'section\');\n	foreach ($field_list as $field) {\n		$var = $field.\'_hcell\';\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$field; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		$adi_matrix_list[$matrix_index][$field] ? $$var = tag(elink($event,\'\',\'sort\',$field,adi_matrix_gtxt($field),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class) : $$var = \'\'; // column heading/toggle sort\n	}\n\n	// custom field headings\n	$cf_hcell = \'\';\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$custom_x; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if ($adi_matrix_list[$matrix_index][$custom_x])\n				$cf_hcell .= tag(elink($event,\'\',\'sort\',$custom_x,$cf_name,\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class);\n	}\n\n	// delete heading placeholder\n	$del_hcell = tag(sp,$tag,\' class=\"adi_matrix_delete\"\'); // spacer\n\n	// \"Show section\" heading\n	if ($sort == \'section\') // sort value matches field\n		$dir == \'desc\' ? $class = \' class=\"desc section\"\' : $class = \' class=\"asc section\"\'; // up/down arrow\n	else\n		$class = \' class= \"section\"\'; // no arrow - sort set in admin\n\n	$show_section_hcell = tag(elink($event,\'\',\'sort\',\'section\',adi_matrix_gtxt(\'section\'),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class);\n\n	return\n		tag(\n			tr(\n				(adi_matrix_pref(\'adi_matrix_display_id\') ? $id_hcell : \'\')\n				.$title_hcell\n				.($adi_matrix_list[$matrix_index][\'show_section\'] ? $show_section_hcell : \'\') // THIS NEEDS SORTING OUT\n				.($adi_matrix_list[$matrix_index][\'section\'] ? $section_hcell : \'\') // THIS NEEDS SORTING OUT\n				.$status_hcell\n				.$cf_hcell\n				.$article_image_hcell\n				.$keywords_hcell\n				.$category1_hcell\n				.$category2_hcell\n				.$posted_hcell\n				.$expires_hcell\n				.($adi_matrix_list[$matrix_index][\'publish\'] ? $del_hcell : \'\')\n			)\n			,$wraptag\n		);\n}\n\nfunction adi_matrix_table($adi_matrix_articles,$matrix_index,$page,$errors=array(),$updates=array()) {\n// generates matrix <table> and <form> for article data updates\n// stylish classes:\n// tr (article ids):	adi_matrix_article_xx, adi_matrix_article_new\n// td (field type):		adi_matrix_timestamp, adi_matrix_category\n// td (field specific): adi_matrix_field_id, adi_matrix_field_title, adi_matrix_field_custom_x etc etc\n	global $step,$adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_statuses,$adi_matrix_list,$txp_user,$txp_user,$adi_matrix_glz_cfs,$adi_matrix_validation_errors;\n\n	$out = \'\';\n	$out .= adi_matrix_table_head($matrix_index,\'header\');\n	if ($adi_matrix_list[$matrix_index][\'footer\'])\n		$out .= adi_matrix_table_head($matrix_index,\'footer\');\n	$out .= \'<tbody>\';\n	if ($adi_matrix_articles) {\n		foreach ($adi_matrix_articles as $id => $data) {\n			// set up validation error flags for this article\n			$article_errors = array();\n			foreach ($errors as $error_type)\n				if (isset($error_type[$id]))\n					$article_errors = array_merge($article_errors,$error_type[$id]);\n			$article_errors = array_unique($article_errors);\n			if ($adi_matrix_debug && ($step == \'update\')) {\n				echo \'<b>Validation errors #\'.$id.\':</b>\';\n				dmp($article_errors);\n			}\n			// based on standard save button in txp_article.php\n			$Status = $data[\'status\'];\n			$AuthorID = $data[\'author\'];\n			$has_privs = // work out if user has a right to fiddle with article\n				(($Status >= 4 and has_privs(\'article.edit.published\'))\n				or ($Status >= 4 and $AuthorID==$txp_user and has_privs(\'article.edit.own.published\'))\n				or ($Status <  4 and has_privs(\'article.edit\'))\n				or ($Status <  4 and $AuthorID==$txp_user and has_privs(\'article.edit.own\')));\n			$prefix = \'article_\'.$id; // use array index \'article_id\' rather than \'id\' in POST data (clearer/safer?)\n			$out .= \'<tr class=\"adi_matrix_\'.$prefix.\'\">\';\n			$highlight = $data[\'highlight\'];\n			// article title link tooltip text\n			// tooltip (&#10; = newline in non-Firefox tooltip)\n			if (adi_matrix_pref(\'adi_matrix_article_tooltips\')) {\n				$title_text = \'#\'.$id.\', \'.adi_matrix_gtxt(\'posted\').\' \'.$data[\'display_posted\'];\n				if ($data[\'expires\'] != \'0000-00-00 00:00:00\')\n					$title_text .= \', \'.adi_matrix_gtxt(\'expires\').\' \'.$data[\'display_expires\'];\n				if ($highlight == 1)\n					$title_text .= \' (\'.adi_matrix_gtxt(\'expired\').\')\';\n				if ($highlight == 2)\n					$title_text .= \' (\'.adi_matrix_gtxt(\'time_future\').\')\';\n				$title_text .= \', \'.$data[\'section\'];\n				$title_text .= \', \'.$AuthorID;\n			}\n			else\n				$title_text = adi_matrix_gtxt(\'edit\');\n			$class = \'\';\n			// highlighting for expired/future articles\n			if (adi_matrix_pref(\'adi_matrix_article_highlighting\')) {\n				if ($highlight) {\n					if ($highlight == 1) $class = \' class=\"adi_matrix_expired\"\';\n					if ($highlight == 2) $class = \' class=\"adi_matrix_future\"\';\n				}\n			}\n			// ID\n			if (adi_matrix_pref(\'adi_matrix_display_id\')) {\n				$id_link = eLink(\'article\',\'edit\',\'ID\',$id,$id);\n				$out .= tag($id_link,\'td\',\' class=\"adi_matrix_field_id\"\');\n			}\n			// title\n			$article_title = trim($data[\'title\']);\n			if ($article_title == \'\') // blank title\n				$title_link = tag(eLink(\'article\',\'edit\',\'ID\',$id,adi_matrix_gtxt(\'untitled\'),\'\',\'\',$title_text),\'em\');\n			else\n				$title_link = eLink(\'article\',\'edit\',\'ID\',$id,$article_title,\'\',\'\',$title_text);\n			$title_link = \'<span title=\"\'.$title_text.\'\"\'.$class.\'>\'.$title_link.\'</span>\';\n			$arrow_link = sp.tag(eLink(\'article\',\'edit\',\'ID\',$id,sp,\'\',\'\',$title_text),\'span\',\' class=\"adi_matrix_edit_link\"\'); // was &rarr;\n			if ($adi_matrix_list[$matrix_index][\'title\'] && adi_matrix_pref(\'adi_matrix_display_id\')) // don\'t need arrow if got IDs\n				$arrow_link = \'\';\n			if ($adi_matrix_list[$matrix_index][\'title\']) {\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(finput(\"text\",$prefix.\"[title]\",$data[\'title\'],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\') ?htmlspecialchars($data[\'title\']):\'\')).$arrow_link,\' class=\"adi_matrix_field_title\"\') :\n					$out .= tda($title_link,\' class=\"adi_matrix_field_title\"\');\n			}\n			else\n				$out .= tag($title_link,\'td\',\' class=\"adi_matrix_field_title\"\');\n			// section\n			if ($adi_matrix_list[$matrix_index][\'show_section\'])\n				$out .= tda($data[\'section\']);\n			if ($adi_matrix_list[$matrix_index][\'section\']) {\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_section_popup($prefix.\"[section]\",$data[\'section\'],$adi_matrix_list[$matrix_index][\'criteria_section\']),\' class=\"adi_matrix_field_section\"\') :\n					$out .= ($data[\'section\'] ? tda($data[\'section\'],\' class=\"adi_matrix_field_section\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_section\"\'));\n			}\n			// status\n			if ($adi_matrix_list[$matrix_index][\'status\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(selectInput($prefix.\'[status]\',$adi_matrix_statuses,$data[\'status\']),\' class=\"adi_matrix_field_status\"\') :\n					$out .= tda($adi_matrix_statuses[$data[\'status\']],\' class=\"adi_matrix_field_status\"\');\n			// custom fields\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n					if ($adi_matrix_list[$matrix_index][$custom_x])\n						if ($has_privs) // decide if user gets input fields or not\n							if ($adi_matrix_glz_cfs) {\n								$glz_input_stuff = adi_matrix_glz_cfs_input($custom_x,$prefix.\"[$custom_x]\",$data[$custom_x]);\n								if ($glz_input_stuff[1] == \'glz_custom_radio_field\') // don\'t apply glz_class coz can\'t handle glz reset function properly yet - see below\n									$out .= tda($glz_input_stuff[0],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n								else\n									$out .= tda($glz_input_stuff[0],\' class=\"\'.$glz_input_stuff[1].\' adi_matrix_field_\'.$custom_x.\'\"\');\n							}\n							else\n								$out .= tda(finput(\"text\",$prefix.\"[$custom_x]\",$data[$custom_x],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($data[$custom_x]):\'\')),\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n						else\n							$out .= ($data[$custom_x] ? tda($data[$custom_x],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_\'.$custom_x.\'\"\')); // make sure the table cell stretches if no data\n			}\n			// article image\n			if ($adi_matrix_list[$matrix_index][\'article_image\']) {\n				$arrow_link = \'\';\n				// CUSTOM FIELD LINKS DISABLED\n// 				if (trim($data[\'article_image\']) && ($adi_matrix_list[$matrix_index][\'cf_links\'] == \'article_image\')) {\n// 					$image_ids = explode(\',\',$data[\'article_image\']);\n// 					$image_id = $image_ids[0];\n// 					if (safe_count(\'txp_image\',\"id=$image_id\",$adi_matrix_debug))\n// 						$arrow_link = sp.eLink(\'image\',\'image_edit\',\'id\',$image_id,\'&rarr;\',\'\',\'\',adi_matrix_gtxt(\'edit_image\').\' #\'.$image_id);\n// 					else // image not found\n// 						$arrow_link = sp.\'?\';\n// 				}\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(finput(\"text\",$prefix.\"[article_image]\",$data[\'article_image\'],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($data[\'article_image\']):\'\')).$arrow_link,\' class=\"adi_matrix_field_image\"\') :\n					$out .= ($data[\'article_image\'] ? tda($data[\'article_image\'].$arrow_link,\' class=\"adi_matrix_field_image\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_image\"\')); // make sure the table cell stretches if no data\n			}\n			// keywords\n			if ($adi_matrix_list[$matrix_index][\'keywords\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(\'<textarea name=\"\'.$prefix.\"[keywords]\".\'\" cols=\"18\" rows=\"5\" class=\"mceNoEditor\">\'.htmlspecialchars(str_replace(\',\' ,\', \', $data[\'keywords\'])).\'</textarea>\',\' class=\"adi_matrix_field_keywords\"\') :\n					$out .= ($data[\'keywords\'] ? tda($data[\'keywords\'],\' class=\"adi_matrix_field_keywords\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_keywords\"\'));\n			// category1\n			if ($adi_matrix_list[$matrix_index][\'category1\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_category_popup($prefix.\"[category1]\",$data[\'category1\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category1\"\') :\n					$out .= ($data[\'category1\'] ? tda($data[\'category1\'],\' class=\"adi_matrix_field_category1\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_category adi_matrix_field_category1\"\'));\n			// category2\n			if ($adi_matrix_list[$matrix_index][\'category2\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_category_popup($prefix.\"[category2]\",$data[\'category2\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category2\"\') :\n					$out .= ($data[\'category2\'] ? tda($data[\'category2\'],\' class=\"adi_matrix_field_category2\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_category adi_matrix_field_category2\"\'));\n			// posted\n			$class = \'adi_matrix_timestamp adi_matrix_field_posted\';\n			if (array_search(\'posted\',$article_errors) !== FALSE)\n				$class .= \' adi_matrix_error\';\n			if ($adi_matrix_list[$matrix_index][\'posted\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_timestamp_input($prefix.\"[posted]\",$data[\'posted\'],$data[\'uposted\'],\'posted\'),\' class=\"\'.$class.\'\"\') :\n					$out .= ($data[\'posted\'] ? tda($data[\'posted\']) : tda(\'&nbsp;\'));\n			// expires\n			$class = \'adi_matrix_timestamp adi_matrix_field_expires\';\n			if (array_search(\'expires\',$article_errors) !== FALSE)\n				$class .= \' adi_matrix_error\';\n			if ($adi_matrix_list[$matrix_index][\'expires\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_timestamp_input($prefix.\"[expires]\",$data[\'expires\'],$data[\'uexpires\'],\'expires\'),\' class=\"\'.$class.\'\"\') :\n					$out .= ($data[\'expires\'] ? tda($data[\'expires\']) : tda(\'&nbsp;\'));\n			// delete button\n			if ($adi_matrix_list[$matrix_index][\'publish\']) { // got publish? - might delete!\n				 // a closer look at credentials - override delete priv OR (delete own priv and it\'s yours to delete)\n				if (has_privs(\'article.delete\') || (has_privs(\'article.delete.own\') && ($AuthorID == $txp_user))) {\n					$url = \'?event=\'.\'adi_matrix_matrix_\'.$matrix_index.a.\'step=delete\'.a.\'id=\'.$id.a.\'page=\'.$page;\n					$button =\n							\'<a href=\"\'\n							.$url\n							.\'\" class=\"dlink\" title=\"Delete?\" onclick=\"return verify(\\\'\'\n							.addslashes(htmlentities($data[\'title\']))\n							.\' - \'\n							.adi_matrix_gtxt(\'confirm_delete_popup\')\n							.\'\\\')\">&#215;</a>\';\n					$out .= tda($button,\' class=\"adi_matrix_delete\"\');\n				}\n				else\n					$out .= tda(\'&nbsp;\');\n			}\n			$out .= \'</tr>\';\n		}\n	}\n\n	if ($adi_matrix_list[$matrix_index][\'publish\'] && has_privs(\'article\'))\n		$out .= adi_matrix_new_article($matrix_index);\n\n	$out .= \'</tbody>\';\n\n	return $out;\n}\n\nfunction adi_matrix_new_article($matrix_index) {\n// data input fields for new article\n// styles as per adi_matrix_table()\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_statuses,$adi_matrix_list,$adi_matrix_glz_cfs,$adi_matrix_article_defaults;\n\n	$defaults = $adi_matrix_article_defaults; // life\'s too short to type\n\n	$prefix = \'article_new\';\n	$out = \'<tr class=\"adi_matrix_\'.$prefix.\'\">\';\n	// ID placeholder\n	if (adi_matrix_pref(\'adi_matrix_display_id\'))\n		$out .= tag(\'&#43;\',\'td\',\' class=\"adi_matrix_add adi_matrix_field_id\"\'); // plus\n	// title\n	$out .= tda(finput(\"text\",$prefix.\"[title]\",$defaults[\'title\']),\' class=\"adi_matrix_field_title\"\');\n	// section\n	if ($adi_matrix_list[$matrix_index][\'show_section\'])\n		$out .= tda($defaults[\'section\'],\' class=\"adi_matrix_field_section\"\');\n	else if ($adi_matrix_list[$matrix_index][\'section\'])\n		$out .= tda(adi_matrix_section_popup($prefix.\"[section]\",$defaults[\'section\'],$adi_matrix_list[$matrix_index][\'criteria_section\']),\' class=\"adi_matrix_field_section\"\');\n	// status\n	if ($adi_matrix_list[$matrix_index][\'status\'])\n		$out .= tda(selectInput($prefix.\'[status]\',$adi_matrix_statuses,$defaults[\'status\']),\' class=\"adi_matrix_field_status\"\');\n	// custom fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if ($adi_matrix_list[$matrix_index][$custom_x])\n				if ($adi_matrix_glz_cfs) {\n					$glz_input_stuff = adi_matrix_glz_cfs_input($custom_x,$prefix.\"[$custom_x]\",$defaults[$custom_x]);\n					if ($glz_input_stuff[1] == \'glz_custom_radio_field\') // don\'t apply glz_class coz can\'t handle glz reset function properly yet - see below\n						$out .= tda($glz_input_stuff[0],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n					else\n						$out .= tda($glz_input_stuff[0],\' class=\"\'.$glz_input_stuff[1].\' adi_matrix_field_\'.$custom_x.\'\"\');\n				}\n				else\n					$out .= tda(finput(\"text\",$prefix.\"[$custom_x]\",$defaults[$custom_x],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($defaults[$custom_x]):\'\')),\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n	}\n	// article image\n	if ($adi_matrix_list[$matrix_index][\'article_image\'])\n		$out .= tda(finput(\"text\",$prefix.\"[article_image]\",$defaults[\'title\']),\' class=\"adi_matrix_field_image\"\');\n	// keywords\n	if ($adi_matrix_list[$matrix_index][\'keywords\'])\n		$out .= tda(\'<textarea name=\"\'.$prefix.\"[keywords]\".\'\" cols=\"18\" rows=\"5\" class=\"mceNoEditor\">\'.htmlspecialchars(str_replace(\',\' ,\', \', $defaults[\'keywords\'])).\'</textarea>\',\' class=\"adi_matrix_field_keywords\"\');\n	// category1\n	if ($adi_matrix_list[$matrix_index][\'category1\'])\n		$out .= tda(adi_matrix_category_popup($prefix.\"[category1]\",$defaults[\'category1\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category1\"\');\n	// category2\n	if ($adi_matrix_list[$matrix_index][\'category2\'])\n		$out .= tda(adi_matrix_category_popup($prefix.\"[category2]\",$defaults[\'category2\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category2\"\');\n	// posted\n	if ($adi_matrix_list[$matrix_index][\'posted\'])\n		$out .= tda(adi_matrix_timestamp_input($prefix.\"[posted]\",$defaults[\'posted\'],$defaults[\'uposted\'],\'posted\'),\' class=\"adi_matrix_timestamp adi_matrix_field_posted\"\');\n	// expires\n	if ($adi_matrix_list[$matrix_index][\'expires\'])\n		$out .= tda(adi_matrix_timestamp_input($prefix.\"[expires]\",$defaults[\'expires\'],$defaults[\'uexpires\'],\'expires\'),\' class=\"adi_matrix_timestamp adi_matrix_field_expires\"\');\n\n	// Delete placeholder\n	if ($adi_matrix_list[$matrix_index][\'publish\'])\n		$out .= tag(sp,\'td\',\' class=\"adi_matrix_delete\"\');\n\n	$out .= \'</tr>\';\n\n	return $out;\n}\n\nfunction adi_matrix_matrix($event,$step) {\n// a matrix tab\n	global $prefs,$adi_matrix_debug,$adi_matrix_dump,$adi_matrix_list,$adi_matrix_articles,$adi_matrix_cfs,$adi_matrix_sort_dir,$adi_matrix_sort_type,$adi_matrix_sort_options,$adi_matrix_validation_errors,$adi_matrix_article_defaults;\n\n	// extract matrix index from event (e.g. adi_matrix_matrix_0 => 0)\n	$matrix_index = str_replace(\'adi_matrix_matrix_\',\'\',$event);\n\n	// bomb out if upgrade needed\n	$upgrade_required = adi_matrix_upgrade();\n	if ($upgrade_required) {\n		pagetop($adi_matrix_list[$matrix_index][\'name\'],array(adi_matrix_gtxt(\'adi_upgrade_required\'),E_WARNING));\n		return;\n	}\n\n	// current sort settings (read from user pref, default to matrix settings)\n	list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n\n	// user sort changes\n	$new_sort = doStripTags(gps(\'sort\'));\n	$new_dir = doStripTags(gps(\'dir\'));\n	$new_sort_type = doStripTags(gps(\'sort_type\'));\n	$reset_sort = doStripTags(gps(\'reset_sort\'));\n	// sort it all out\n	if ($new_sort || $new_dir || $new_sort_type || $reset_sort) {\n		if ($new_sort && $new_dir) // column heading clicked\n			adi_matrix_pref($event.\'_sort\',$new_sort.\',\'.$new_dir.\',\'.$sort_type,TRUE); // update user pref with sort & dir\n		else if ($new_sort_type) // sort_type change\n			adi_matrix_pref($event.\'_sort\',$sort.\',\'.$dir.\',\'.$new_sort_type,TRUE); // update user pref with sort_type\n		else if ($reset_sort) { // reset sort to default\n			safe_delete(\'txp_prefs\',\"name = \'\".$event.\"_sort\'\",$adi_matrix_debug); // delete user pref\n			$prefs = get_prefs();\n		}\n		// reread user pref, defaulting to matrix settings\n		list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n	}\n\n	// initialise some bits\n	$message = \'\';\n	$updates = $errors = array();\n\n	// article selection criteria\n	$criteria = array();\n	$criteria[\'section\'] = $adi_matrix_list[$matrix_index][\'criteria_section\'];\n	$criteria[\'category\'] = $adi_matrix_list[$matrix_index][\'criteria_category\'];\n	$criteria[\'descendent_cats\'] = $adi_matrix_list[$matrix_index][\'criteria_descendent_cats\'];\n	$criteria[\'status\'] = $adi_matrix_list[$matrix_index][\'criteria_status\'];\n	$criteria[\'author\'] = $adi_matrix_list[$matrix_index][\'criteria_author\'];\n	$criteria[\'keywords\'] = $adi_matrix_list[$matrix_index][\'criteria_keywords\'];\n	$criteria[\'timestamp\'] = $adi_matrix_list[$matrix_index][\'criteria_timestamp\'];\n	$criteria[\'expiry\'] = $adi_matrix_list[$matrix_index][\'criteria_expiry\'];\n	$criteria[\'condition\'] = $adi_matrix_list[$matrix_index][\'criteria_condition\'];\n\n	// article selection WHERE clause\n	$where = adi_matrix_where($criteria);\n\n	// article sort query\n	$sortq = adi_matrix_order_by($sort,$dir,$sort_type);\n\n	// paging Mr. Matrix\n	if ($step == $event.\'_change_pageby\') { // change of page length\n		$qty = gps(\'qty\');\n		adi_matrix_pref($event.\'_pageby\',$qty,TRUE);\n	}\n	$page = gps(\'page\'); // get page number\n	$pageby = get_pref($event.\'_pageby\',15); // get current page size (default to 15, if not saved as pref)\n	$total = safe_count(\'textpattern\',\"$where\");\n	list($page,$offset,$num_pages) = pager($total,$pageby,$page);\n	if ($adi_matrix_debug)\n		echo \"<b>Paging:</b> pageby=$pageby, total=$total, page=$page, offset=$offset, num_pages=$num_pages\".br;\n\n	// get a page of articles\n	$adi_matrix_articles = adi_matrix_get_articles($where,$sortq,$matrix_index,$offset,$pageby);\n\n	// article defaults\n	$adi_matrix_article_defaults = adi_matrix_article_defaults($matrix_index);\n\n	// $step aerobics\n	if ($step == \'update\') {\n		$post_data = adi_matrix_get_post_data($adi_matrix_articles,$matrix_index);\n		$errors = adi_matrix_validate_post_data($adi_matrix_articles,$post_data);\n		$updates = adi_matrix_get_updates(adi_matrix_remove_errors($post_data,$errors),$adi_matrix_articles);\n		if ($updates) {\n			$ok = adi_matrix_update_articles($updates,$matrix_index);\n			$ok ? $message = adi_matrix_gtxt(\'adi_articles_saved\') : $message = array(adi_matrix_gtxt(\'adi_article_update_fail\'),E_WARNING);\n		}\n		else\n			$message = adi_matrix_gtxt(\'adi_articles_not_modified\');\n		if ($errors) {\n			$message .= \'. \'.adi_matrix_gtxt(\'adi_matrix_validation_error\',\'\',\':\');\n			foreach ($errors as $i => $v)\n				$message .= \' \'.$adi_matrix_validation_errors[$i].\' (\'.implode(\',\',array_keys($v)).\')\';\n			$message = array($message,E_WARNING);\n		}\n	}\n	else if ($step == \'delete\') {\n		$id = gps(\'id\');\n		if (isset($adi_matrix_articles[$id])) {\n			$ok = adi_matrix_delete_article($id);\n			$message = adi_matrix_gtxt(\'article_deleted\').\' (\'.$id.\')\';\n		}\n		else {\n			$message = array(adi_matrix_gtxt(\'adi_article_delete_fail\'),E_ERROR);\n		}\n	}\n\n	// regenerate articles list & count array again?\n	if (($step == \'update\') || ($step == \'delete\')) {\n		$adi_matrix_articles = adi_matrix_get_articles($where,$sortq,$matrix_index,$offset,$pageby);\n		$total = safe_count(\'textpattern\',\"$where\");\n	}\n\n	if ($adi_matrix_debug) {\n		$matrix_page_id_list = implode(\',\',array_keys($adi_matrix_articles)); // list if all article ids on this page\n		echo \"<b>Article ID list (on this page):</b> $matrix_page_id_list\".br;\n		$col = safe_column(\'ID\',\'textpattern\',$where.$sortq);\n		$matrix_id_list = implode(\',\',array_keys($col));\n		echo \"<b>Article ID list (in matrix):</b> $matrix_id_list\".br.br;\n	}\n\n	if ($adi_matrix_dump) {\n		// dump article data (all articles in matrix)\n		$rows = safe_rows(\'*\',\'textpattern\',\"ID IN (\".implode(\',\',array_keys($adi_matrix_articles)).\")\");\n		echo \'START ARTICLE MATRIX DUMP\'.br;\n		foreach ($rows as $i => $a) {\n			foreach ($a as $f => $d)\n				echo htmlentities(\"$f=$d,\");\n			echo br;\n		}\n		echo \'END ARTICLE MATRIX DUMP\'.br;\n	}\n\n	// generate page\n	pagetop($adi_matrix_list[$matrix_index][\'name\'],$message);\n\n	// output matrix table & input form\n	$table = adi_matrix_table($adi_matrix_articles,$matrix_index,$page,$errors,$updates);\n	$tags = array(\'<input\', \'<textarea\', \'<select\'); // tags which indicate that a save button is deserved\n	$save_button = FALSE;\n	foreach ($tags as $tag)\n		$save_button = $save_button || strpos($table,$tag);\n	$class = \'adi_matrix_matrix\';\n	if ($adi_matrix_list[$matrix_index][\'scroll\'])\n		$class .= \' adi_matrix_scroll\';\n	$class .= \' txp-list\';\n	echo form(\n		tag($adi_matrix_list[$matrix_index][\'name\'],\'h1\')\n		.\'<div class=\"scroll_box\">\'\n		.startTable(\'list\',\'\',$class)\n		.$table\n		.endTable()\n		.\'</div>\'\n		.(empty($adi_matrix_articles) ?\n			graf(tag(adi_matrix_gtxt(\'no_articles_recorded\'),\'em\'),\' class=\"adi_matrix_none\"\')\n			: \'\'\n		)\n		.($save_button ?\n			tag(\n				hInput(\'page\',$page) // pass on paging\n				.fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'save\'), \"publish\")\n				.eInput(\"adi_matrix_matrix_\".$matrix_index).sInput(\"update\"),\n				\'div\',\n				\' class=\"adi_matrix_button\"\'\n			)\n			: \'\'\n		)\n		.tag(\n			graf(\n				adi_matrix_gtxt(\'adi_default_sort\',\'\',\':\')\n				.sp\n				.elink(\n					$event\n					,\'\'\n					,\'reset_sort\'\n					,1\n					,$adi_matrix_sort_options[$adi_matrix_list[$matrix_index][\'sort\']]\n						.\', \'\n						.$adi_matrix_sort_dir[$adi_matrix_list[$matrix_index][\'dir\']]\n						.\', \'\n						.$adi_matrix_sort_type[$adi_matrix_list[$matrix_index][\'sort_type\']]\n					,\'\',\'\',\'\' // to override TXP 4.5 default title \"Edit\"\n				)\n				.br\n				.adi_matrix_gtxt(\'adi_sort_type\',\'\',\':\')\n				.sp\n				.adi_matrix_gtxt(\'adi_\'.$sort_type)\n				.\' [\'\n				.elink(\n					$event\n					,\'\'\n					,\'sort_type\'\n					,($sort_type == \'numerical\' ? \'alphabetical\' : \'numerical\')\n					,adi_matrix_gtxt(($sort_type == \'numerical\' ? \'adi_alphabetical\' : \'adi_numerical\'))\n					,\'\',\'\',\'\' // to override TXP 4.5 default title \"Edit\"\n				)\n				.\']\'\n			)\n			,\'div\',\' class=\"adi_matrix_matrix_prefs\"\')\n		,\'\'\n		,\'\'\n		,\'post\'\n		,$class\n	);\n\n	// flashing message\n	if ($errors) {\n		echo <<<END_SCRIPT\n			<script type=\"text/javascript\">\n			<!--\n			$(document).ready( function(){\n						$(\'#messagepane\').fadeOut(800).fadeIn(800);\n						$(\'#messagepane\').fadeOut(800).fadeIn(800);\n					} )\n			// -->\n			</script>\nEND_SCRIPT;\n	}\n\n	// grand total\n	echo graf(adi_matrix_gtxt(\'adi_matrix_total_articles\').sp.$total,\' class=\"adi_matrix_grand_total\"\');\n\n	// paging\n	echo tag(\n			// prev/next page buttons (if muliple pages)\n			($total > $pageby ? nav_form($event,$page,$num_pages,$sort,$dir,\'\',\'\',$total,$pageby,\'\') : \'\') // set empty step (to avoid confusion), coz default is \"list\"\n			// page length select\n			.pageby_form($event,$pageby)\n			,\'div\'\n			,\' id=\"list_navigation\" class=\"txp-navigation\"\'\n		);\n\n	if ($adi_matrix_debug)\n		adi_matrix_debug($adi_matrix_articles,$matrix_index);\n\n}\n\nfunction adi_matrix_installed($table=\'adi_matrix\') {\n// test if database table is present\n\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_matrix_install() {\n// install adi_matrix table in database\n	global $adi_matrix_debug,$adi_matrix_cfs;\n\n	$cfq = \'\';\n	foreach ($adi_matrix_cfs as $index => $value) {\n		$cfq .= \', `custom_\'.$index.\'` TINYINT(1) DEFAULT 0 NOT NULL\';\n	}\n\n	$res = safe_query(\n			\"CREATE TABLE IF NOT EXISTS \"\n			.safe_pfx(\'adi_matrix\')\n			.\" (\n			`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n			`name` VARCHAR(255) NOT NULL,\n			`sort` VARCHAR(32) NOT NULL DEFAULT \'posted\',\n			`sort_type` VARCHAR(32) NOT NULL DEFAULT \'alphabetical\',\n			`dir` VARCHAR(32) NOT NULL DEFAULT \'desc\',\n			`user` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`privs` VARCHAR(16) NOT NULL DEFAULT \'\',\n			`scroll` TINYINT(1) DEFAULT 0 NOT NULL,\n			`footer` TINYINT(1) DEFAULT 0 NOT NULL,\n			`title` TINYINT(1) DEFAULT 0 NOT NULL,\n			`publish` TINYINT(1) DEFAULT 0 NOT NULL,\n			`show_section` TINYINT(1) DEFAULT 0 NOT NULL,\n			`cf_links` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`tab` VARCHAR(16) NOT NULL DEFAULT \'content\',\n			`criteria_section` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`criteria_category` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`criteria_status` INT(2) NOT NULL DEFAULT \'4\',\n			`criteria_author` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`criteria_keywords` VARCHAR(255) NOT NULL DEFAULT \'\',\n			`criteria_timestamp` VARCHAR(16) NOT NULL DEFAULT \'any\',\n			`criteria_expiry` INT(2) NOT NULL DEFAULT \'0\',\n			`criteria_descendent_cats` TINYINT(1) DEFAULT 0 NOT NULL,\n			`criteria_condition` VARCHAR(255) NOT NULL DEFAULT \'\',\n			`status` TINYINT(1) DEFAULT 0 NOT NULL,\n			`keywords` TINYINT(1) DEFAULT 0 NOT NULL,\n			`article_image` TINYINT(1) DEFAULT 0 NOT NULL,\n			`category1` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`category2` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`posted` TINYINT(1) DEFAULT 0 NOT NULL,\n			`expires` TINYINT(1) DEFAULT 0 NOT NULL,\n			`section` TINYINT(1) DEFAULT 0 NOT NULL\n			\"\n			.$cfq\n			.\");\"\n			,$adi_matrix_debug\n		);\n\n 	return $res;\n}\n\nfunction adi_matrix_uninstall() {\n// uninstall adi_matrix\n	global $adi_matrix_debug;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_matrix\').\";\",$adi_matrix_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_matrix_%\'\",$adi_matrix_debug);\n\n	return $res;\n}\n\nfunction adi_matrix_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_matrix\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5:	upgrade/reinstall only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_matrix_debug;\n\n	$result = \'?\';\n	// set upgrade flag if upgrading/reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_matrix_installed());\n	if ($step == \'enabled\') {\n		$result = $upgrade = adi_matrix_install();\n	}\n	else if ($step == \'deleted\')\n		$result = adi_matrix_uninstall();\n	if ($upgrade)\n		$result = $result && adi_matrix_upgrade(TRUE);\n	if ($adi_matrix_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_matrix_upgrade($do_upgrade=FALSE) {\n// check/perform upgrade\n	global $adi_matrix_debug;\n\n	$upgrade_required = FALSE;\n	// version 0.2\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'article_image\'\",$adi_matrix_debug); // find out if column exists\n	$a = nextRow($rs);\n	$v0_2 = empty($a);\n	$upgrade_required = $upgrade_required || $v0_2;\n	// version 0.3\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'criteria_timestamp\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v0_3t = empty($a);\n	$upgrade_required = $upgrade_required || $v0_3t;\n	// version 0.3\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'criteria_expiry\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v0_3e = empty($a);\n	$upgrade_required = $upgrade_required || $v0_3e;\n	// version 1.0\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'scroll\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v1_0 = empty($a);\n	$upgrade_required = $upgrade_required || $v1_0;\n	// version 1.1\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'footer\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v1_1 = empty($a);\n	$upgrade_required = $upgrade_required || $v1_1;\n	// version 2.0\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'title\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v2_0 = empty($a);\n	$upgrade_required = $upgrade_required || $v2_0;\n\n	if ($do_upgrade && $upgrade_required) {\n		$res = TRUE;\n		if ($v0_2)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `article_image` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n		if ($v0_3t)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_timestamp` VARCHAR(16) NOT NULL DEFAULT \'any\'\",$adi_matrix_debug);\n		if ($v0_3e)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_expiry` INT(2) NOT NULL DEFAULT \'0\'\",$adi_matrix_debug);\n		if ($v1_0) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `scroll` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `category1` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `category2` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `dir` VARCHAR(32) NOT NULL DEFAULT \'1\'\",$adi_matrix_debug);\n			// convert old `sort` to new `sort` & `dir`\n			//	OLD sort=1 (Posted desc) 	->  NEW sort=1, dir=1\n			//	OLD sort=2 (Posted asc) 	->  NEW sort=1, dir=2\n			//	OLD sort=3 (Title)			->  NEW sort=2, dir=\'\'\n			//	OLD sort=4 (LastMod desc)	->  NEW sort=3, dir=1\n			//	OLD sort=5 (LastMod asc)	->  NEW sort=3, dir=2\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'1\'\",\"`sort` IN (\'1\',\'4\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'2\'\",\"`sort` IN (\'2\',\'5\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'1\'\",\"`sort` = \'2\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'2\'\",\"`sort` = \'3\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'3\'\",\"`sort` IN (\'4\',\'5\')\",$adi_matrix_debug);\n		}\n		if ($v1_1) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `footer` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `sort_type` VARCHAR(32) NOT NULL DEFAULT \'alphabetical\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `posted` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `expires` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_descendent_cats` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			// one day sort will be sorted\n			//	OLD sort=1, NEW sort=\'posted\'\n			//	OLD sort=2, NEW sort=\'title\',\n			//	OLD sort=3, NEW sort=\'lastmod\',\n			//	OLD sort=4, NEW sort=\'expires\',\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'posted\'\",\"`sort` = \'1\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'title\'\",\"`sort` = \'2\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'lastmod\'\",\"`sort` = \'3\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'expires\'\",\"`sort` = \'4\'\",$adi_matrix_debug);\n			// sort_type \"+ 0 asc/desc\" now \"numerical\"\n			$res = $res && safe_update(\'adi_matrix\',\"`sort_type` = \'alphabetical\'\",\"`dir` IN (\'1\',\'2\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort_type` = \'numerical\'\",\"`dir` IN (\'3\',\'4\')\",$adi_matrix_debug);\n			// sort direction\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'desc\'\",\"`dir` IN (\'1\',\'4\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'asc\'\",\"`dir` IN (\'2\',\'3\')\",$adi_matrix_debug);\n		}\n		if ($v2_0) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `title` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `publish` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `show_section` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `cf_links` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `tab` VARCHAR(16) NOT NULL DEFAULT \'content\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `section` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_condition` VARCHAR(255) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_delete(\'txp_prefs\',\"name=\'adi_matrix_article_limit\'\",$adi_matrix_debug); // made redundant by paging\n		}\n		return $res;\n	}\n	else // report back only\n		return $upgrade_required;\n}\n\nfunction adi_matrix_downgrade() {\n// downgrade to previous version - 2.0 to 1.1/1.2 only\n	global $adi_matrix_debug;\n\n	$res = TRUE;\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `title`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `publish`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `show_section`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `cf_links`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `tab`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `section`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `criteria_condition`\",$adi_matrix_debug);\n\n	return $res;\n}\n\nfunction adi_matrix_cat_tree($list,$parent=\'root\') {\n// generate a tree of parent/child relationships\n\n	$return = array();\n	foreach ($list as $cat)\n		if ($cat[\'parent\'] == $parent)\n			$return[$cat[\'name\']] = adi_matrix_cat_tree($list,$cat[\'name\']);\n\n	return $return;\n}\n\nfunction adi_matrix_cat_descendents($tree,$parent=NULL,$found=FALSE) {\n// find all descendents of a given parent\n\n	$return = array();\n	foreach ($tree as $name => $children) {\n		if ($found)\n			$return[] = $name;\n		$return = array_merge($return,adi_matrix_cat_descendents($children,$parent,(($name == $parent) OR ($found))));\n	}\n	return $return;\n}\n\nfunction adi_matrix_categories($getTree_array) {\n// create adi_matrix_categories array, indexed by cat name containing pertinent information\n	global $adi_matrix_debug;\n\n	// $getTree_array is array of arrays:\n	//    \'id\' => \'xx\',\n	//    \'name\' =>	\'category-name\',\n	//    \'title\' => \'Category Title\',\n	//    \'level\' => 0,  (in category hierarchy)\n	//    \'children\' => 2,	(no. of children)\n	//    \'parent\' => \'root\',	(name of parent)\n\n	$cat_tree = adi_matrix_cat_tree($getTree_array);\n\n	$categories = array();\n	foreach ($getTree_array as $this_cat) {\n		$categories[$this_cat[\'name\']][\'parent\'] = $this_cat[\'parent\'];\n		$categories[$this_cat[\'name\']][\'children\'] = adi_matrix_cat_descendents($cat_tree,$this_cat[\'name\']);\n	}\n\n	return $categories;\n}\n\nfunction adi_matrix_section_popup($select_name,$value,$list=\'\') {\n// generate section popup list for admin settings table\n// where \'TRUE\' not supported on MySQL 4.0.27 (OK in MySQL 5+), so use 1=1\n\n	$blank_allowed = TRUE;\n	$where = \"name != \'default\'\";\n\n	// set up where condition if section list supplied\n	if ($list) {\n		$a = explode(\',\',$list);\n		foreach ($a as $i => $v)\n			$a[$i] = \"\'$v\'\";\n		$where .= \'AND name in (\'.implode(\',\',$a).\')\';\n		$blank_allowed = FALSE;\n	}\n\n	$rs = safe_column(\'name\', \'txp_section\', $where);\n	if ($rs)\n		return selectInput($select_name, $rs, $value, $blank_allowed);\n\n	return false;\n}\n\nfunction adi_matrix_section_checkboxes($field_name,$value) {\n// generate section checkboxes\n\n	$section_list = explode(\',\',$value);\n	$out = \'\';\n	$rs = safe_column(\'name\', \'txp_section\', \"name != \'default\'\");\n	if ($rs) {\n		foreach ($rs as $section)\n			$out .= tag(checkbox($field_name.\'[]\',$section,(array_search($section,$section_list) !== FALSE ? \'1\' : \'0\')).$section,\'label\');\n		return $out;\n	}\n	return FALSE;\n}\n\nfunction adi_matrix_category_popup($select_name,$value,$admin=TRUE) {\n// generate category popup list for admin settings table\n\n	$rs = getTree(\'root\',\'article\');\n	if ($rs) {\n		if ($admin) { /* create wildcards (wildcats?) */\n			$wildcard_list = array(\'no_category\',\'any_category\',\'one_category\',\'two_categories\',\'any_parent_category\',\'any_child_category\');\n			foreach (array_reverse($wildcard_list) as $wildcard)\n				/* add to front of array & renumber */\n				array_unshift($rs,array(\'id\' => \'0\', \'name\' => \'!\'.$wildcard.\'!\', \'title\' => adi_matrix_gtxt(\'adi_\'.$wildcard), \'level\' => \'0\', \'children\' => \'0\', \'parent\' => \'root\'));\n		}\n		return treeSelectInput($select_name,$rs,$value,\'\',35);\n	}\n	return tag(adi_matrix_gtxt(\'no_categories_exist\'),\'em\');\n}\n\nfunction adi_matrix_status_popup($select_name,$value) {\n// generate status popup list for admin settings table\n	global $adi_matrix_statuses;\n\n	return selectInput($select_name, $adi_matrix_statuses, $value, TRUE);\n}\n\nfunction adi_matrix_timestamp_popup($select_name,$value) {\n// generate timestamp popup list for admin settings table\n	global $adi_matrix_timestamp_options;\n\n	return selectInput($select_name, $adi_matrix_timestamp_options, $value, FALSE);\n}\n\nfunction adi_matrix_expiry_popup($select_name,$value) {\n// generate timestamp popup list for admin settings table\n	global $adi_matrix_expiry_options;\n\n	return selectInput($select_name, $adi_matrix_expiry_options, $value, FALSE);\n}\n\nfunction adi_matrix_user_popup($select_name,$value,$wildcard=FALSE) {\n// generate user/author popup list for admin settings table\n	global $adi_matrix_statuses;\n\n	$rs = safe_column(\'name\', \'txp_users\', \'1=1\');\n	if ($rs) {\n		if ($wildcard) { /* create wildcard */\n			$logged_in_user = array(\'!logged_in_user!\' => adi_matrix_gtxt(\'adi_logged_in_user\'));\n			$rs = $logged_in_user + $rs; /* add to front of array */\n		}\n		return selectInput($select_name, $rs, $value, TRUE);\n	}\n	return false;\n}\n\nfunction adi_matrix_privs_popup($select_name,$value) {\n// generate privs popup list for admin settings table\n	global $adi_matrix_groups;\n\n	return selectInput($select_name, $adi_matrix_groups, $value, TRUE);\n}\n\nfunction adi_matrix_tab_popup($select_name,$value) {\n// generate tab popup list for admin settings table\n	global $adi_matrix_tabs;\n\n	return selectInput($select_name, $adi_matrix_tabs, $value);\n}\n\nfunction adi_matrix_timestamp_input($name,$datetime,$ts,$type=\'posted\') {\n// date/time input fields - code stolen from include/txp_article.php\n\n	if ($datetime == \'0000-00-00 00:00:00\')\n		$ts = 0;\n\n	$class = \' \'.$type;\n	if ($type == \'posted\')\n		$class .= \' created\';\n\n	$out =\n		tag(\n			adi_matrix_tsi($name.\'[year]\',\'%Y\',$ts,\'\',$type)\n			.\' /\'\n			.adi_matrix_tsi($name.\'[month]\',\'%m\',$ts,\'\',$type)\n			.\' /\'\n			.adi_matrix_tsi($name.\'[day]\',\'%d\',$ts,\'\',$type)\n			,\'div\'\n			,\' class=\"date\'.$class.\'\"\'\n		)\n		.tag(\n			adi_matrix_tsi($name.\'[hour]\',\'%H\',$ts,\'\',$type)\n			.\' :\'\n			.adi_matrix_tsi($name.\'[minute]\',\'%M\',$ts,\'\',$type)\n			.\' :\'\n			.adi_matrix_tsi($name.\'[second]\',\'%S\',$ts,\'\',$type)\n			.($type == \'posted\' ? br.tag(adi_matrix_gtxt(\'adi_reset\'),\'label\',\' class=\"reset_time-now\"\').sp.checkbox($name.\'[reset_time]\',\'1\',\'0\') : \'\')\n			,\'div\'\n			,\' class=\"time\'.$class.\'\"\'\n		);\n\n	return $out;\n}\n\nfunction adi_matrix_tsi($name,$datevar,$time,$tab=\'\',$type=\'\') {\n// date/time item input - adapted from txplib_forms.php tsi()\n\n	preg_match_all(\'/.*\\[(.*)\\]$/\',$name,$matches); // to get \'year\' etc from article_x[posted][year]\n	$short_name = $matches[1][0];\n	if ($type == \'expires\')\n		$short_name = \'exp_\'.$short_name;\n	$size = ($short_name == \'year\' or $short_name == \'exp_year\') ? 4 : 2;\n	$s = ($time == 0) ? \'\' : safe_strftime($datevar, $time); // convert DB time to TXP date/time\n	return\n		n\n		.\'<input type=\"text\" name=\"\'.$name\n		.\'\" value=\"\'\n		.$s\n		.\'\" size=\"\'\n		.$size\n		.\'\" maxlength=\"\'\n		.$size\n		.\'\" class=\"edit \'\n		.$short_name\n		.\'\"\'\n		.(empty($tab) ? \'\' : \' tabindex=\"\'.$tab.\'\"\')\n		.\' title=\"\'\n		.adi_matrix_gtxt(\'article_\'.$short_name)\n		.\'\" />\';\n}\n\nfunction adi_matrix_delete_button($matrix_list,$matrix_index) {\n// matrix delete button [X]\n\n	$event = \'adi_matrix_admin\';\n	$step = \'delete\';\n	$url = \'?event=\'.$event.a.\'step=\'.$step.a.\'matrix=\'.$matrix_index;\n	if ($matrix_index == \'new\') // don\'t want delete button\n		return \'&nbsp;\';\n	else\n		return\n			\'<a href=\"\'\n			.$url\n			.\'\" class=\"dlink\" title=\"\'.adi_matrix_gtxt(\'delete\').\'?\" onclick=\"return verify(\\\'\'\n			.$matrix_list[$matrix_index][\'name\']\n			.\' &mdash; \'\n			.adi_matrix_gtxt(\'confirm_delete_popup\')\n			.\'\\\')\">&#215;</a>\'; // times\n}\n\nfunction adi_matrix_delete($matrix_index) {\n// stick a matrix in the bin\n	global $adi_matrix_debug;\n\n	$res = safe_delete(\'adi_matrix\', \"`id`=$matrix_index\", $adi_matrix_debug);\n	return $res;\n}\n\nfunction adi_matrix_update_settings() {\n// analyse $_POST & update settings\n	global $adi_matrix_debug,$adi_matrix_cfs;\n\n	$res = FALSE;\n	foreach ($_POST as $index => $value) {\n		$data = doArray($value,\'doStripTags\'); // strip out monkey business\n		$this_index = explode(\'_\',$index);\n		if ($this_index[0] == \'matrix\') {\n			$matrix_index = $this_index[1];\n			// adjustments\n			if ($data[\'publish\']) // if publish, then get title edit for free\n				$data[\'title\'] = \'1\';\n			if (isset($data[\'section\'])) // if section selected as data, then switch off show_section\n				$data[\'show_section\'] = \'0\';\n			// sort\n			if ($data[\'sort\'] == \'\')\n				$sortq = \"sort=\'desc\', \";\n			else\n				$sortq = \"sort=\'\".doSlash($data[\'sort\']).\"\', \";\n			// section\n			if (array_key_exists(\'criteria_section\',$data))\n				$criteria_sectionq = \"criteria_section=\'\".implode(\',\',$data[\'criteria_section\']).\"\', \";\n			else\n				$criteria_sectionq = \"criteria_section=\'\', \";\n			// status\n			if (array_key_exists(\'status\',$data))\n				$statusq = \'status=1, \';\n			else\n				$statusq = \'status=0, \';\n			// keywords\n			if (array_key_exists(\'keywords\',$data))\n				$keywordsq = \'keywords=1, \';\n			else\n				$keywordsq = \'keywords=0, \';\n			// article image\n			if (array_key_exists(\'article_image\',$data))\n				$article_imageq = \'article_image=1, \';\n			else\n				$article_imageq = \'article_image=0, \';\n			// category\n			if (array_key_exists(\'category1\',$data))\n				$categoryq = \'category1=1, \';\n			else\n				$categoryq = \'category1=0, \';\n			if (array_key_exists(\'category2\',$data))\n				$categoryq .= \'category2=1, \';\n			else\n				$categoryq .= \'category2=0, \';\n			// posted\n			if (array_key_exists(\'posted\',$data))\n				$postedq = \'posted=1, \';\n			else\n				$postedq = \'posted=0, \';\n			// expires\n			if (array_key_exists(\'expires\',$data))\n				$expiresq = \'expires=1, \';\n			else\n				$expiresq = \'expires=0, \';\n			// title\n			if (array_key_exists(\'title\',$data))\n				$titleq = \'title=1, \';\n			else\n				$titleq = \'title=0, \';\n			// section\n			if (array_key_exists(\'section\',$data))\n				$sectionq = \'section=1, \';\n			else\n				$sectionq = \'section=0, \';\n			// custom field\n			$cfq = \'\';\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				if (array_key_exists($custom_x,$data))\n					$cfq .= \"custom_\".$index.\"=\'1\', \";\n				else\n					$cfq .= \"custom_\".$index.\"=\'0\', \";\n			}\n			// category\n			if (!array_key_exists(\'criteria_category\',$data)) // in case there\'re no categories defined\n				$data[\'criteria_category\'] = \'\';\n			if (preg_match(\'/!.*!/\',$data[\'criteria_category\'])) // disable descendent cats option with wildcards\n				unset($data[\'criteria_descendent_cats\']);\n			if (array_key_exists(\'criteria_descendent_cats\',$data))\n				$criteria_descendent_catsq = \'criteria_descendent_cats=1, \';\n			else\n				$criteria_descendent_catsq = \'criteria_descendent_cats=0, \';\n\n			$matrix_sql_set =\n				\"name=\'\".doSlash($data[\'name\']).\"\', \"\n				.$sortq\n				.\"dir=\'\".doSlash($data[\'dir\']).\"\', \"\n				.\"sort_type=\'\".doSlash($data[\'sort_type\']).\"\', \"\n				.\"user=\'\".doSlash($data[\'user\']).\"\', \"\n				.\"privs=\'\".doSlash($data[\'privs\']).\"\', \"\n				.\"scroll=\'\".doSlash($data[\'scroll\']).\"\', \"\n				.\"footer=\'\".doSlash($data[\'footer\']).\"\', \"\n				.\"publish=\'\".doSlash($data[\'publish\']).\"\', \"\n				.\"show_section=\'\".doSlash($data[\'show_section\']).\"\', \"\n				.\"cf_links=\'\".doSlash($data[\'cf_links\']).\"\', \"\n				.\"tab=\'\".doSlash($data[\'tab\']).\"\', \"\n				.$criteria_sectionq\n				.\"criteria_status=\'\".doSlash($data[\'criteria_status\']).\"\', \"\n				.\"criteria_author=\'\".doSlash($data[\'criteria_author\']).\"\', \"\n				.\"criteria_keywords=\'\".doSlash($data[\'criteria_keywords\']).\"\', \"\n				.\"criteria_timestamp=\'\".doSlash($data[\'criteria_timestamp\']).\"\', \"\n				.\"criteria_expiry=\'\".doSlash($data[\'criteria_expiry\']).\"\', \"\n				.\"criteria_condition=\'\".doSlash($data[\'criteria_condition\']).\"\', \"\n				.$titleq\n				.$statusq\n				.$sectionq\n				.$keywordsq\n				.$article_imageq\n				.$postedq\n				.$expiresq\n				.$cfq\n				.$categoryq\n				.$criteria_descendent_catsq\n				.\"criteria_category=\'\".doSlash($data[\'criteria_category\']).\"\' \";\n\n			if ($matrix_index == \'new\') {  // add new matrix to the mix\n				if (!empty($data[\'name\'])) { // but don\'t add a blank one\n					$res = safe_insert(\n						\'adi_matrix\',\n						$matrix_sql_set\n						,$adi_matrix_debug\n					);\n				}\n			}\n			else { // update existing matrix\n				$res = safe_upsert(\n					\'adi_matrix\',\n					$matrix_sql_set\n					,\"id=\'$matrix_index\'\"\n					,$adi_matrix_debug\n				);\n			}\n		}\n	}\n	return $res;\n}\n\nfunction adi_matrix_admin_table_head($adi_matrix_cfs) {\n// <table> header stuff\n\n	// custom field headings\n	$data_span = 2; // [status/keywords] plus [custom fields]\n	return tr(\n		hcell(adi_matrix_gtxt(\'adi_matrix\'))\n		.hcell() // spacer for View link\n		.hcell(adi_matrix_gtxt(\'adi_article_selection\'))\n		.hcell(adi_matrix_gtxt(\'adi_article_data\'),\'\',\' colspan=\"\'.$data_span.\'\"\')\n		.hcell(\'\',\'\',\' class=\"adi_matrix_noborder\"\') // spacer for delete button\n	);\n}\n\nfunction adi_matrix_admin_table($matrix_list,$matrix_cfs) {\n// generate form fields for existing & new matrixes\n	global $adi_matrix_cfs,$adi_matrix_sort_options,$adi_matrix_sort_dir,$txp_user,$adi_matrix_sort_type,$prefs;\n\n	$out = \'\';\n	$out .= adi_matrix_admin_table_head($matrix_cfs);\n\n	// tack \'new\' index onto end of $matrix_list (field defaults for adding new matrix)\n	$matrix_list[\'new\'] = array(\n		\'name\' => \'\',\n		\'sort\' => \'posted\',\n		\'dir\' => \'desc\',\n		\'sort_type\' => \'alphabetical\',\n		\'user\' => $txp_user,\n		\'privs\' => \'\',\n		\'scroll\' => \'0\',\n		\'footer\' => \'0\',\n		\'title\' => \'0\',\n		\'publish\' => \'0\',\n		\'show_section\' => \'0\',\n		\'cf_links\' => \'\',\n		\'tab\' => \'0\',\n		\'criteria_section\' => \'\',\n		\'criteria_category\' => \'\',\n		\'criteria_descendent_cats\' => \'0\',\n		\'criteria_status\' => \'0\',\n		\'criteria_author\' => \'\',\n		\'criteria_keywords\' => \'\',\n		\'criteria_timestamp\' => \'\',\n		\'criteria_expiry\' => \'\',\n		\'criteria_condition\' => \'\',\n		\'status\' => \'0\',\n		\'keywords\' => \'0\',\n		\'article_image\' => \'0\',\n		\'category1\' => \'0\',\n		\'category2\' => \'0\',\n		\'posted\' => \'0\',\n		\'expires\' => \'0\',\n		\'section\' => \'0\'\n	);\n	foreach ($adi_matrix_cfs as $index => $value) // add custom fields to $matrix_list[\'new\']\n		$matrix_list[\'new\'][\'custom_\'.$index] = \'0\';\n\n	// existing matrixes followed by empty fields for a new one\n	foreach ($matrix_list as $matrix_index => $matrix) {\n		$cf_checkboxes = \'\';\n		foreach ($matrix_cfs as $index => $cf_name) {\n			$custom_x = \'custom_\'.$index;\n			$colon = \':\';\n			if (strpos($prefs[\'language\'],\'fr-\') === 0) // normally adi_matrix_gtxt takes care of this\n				$colon = \' \'.$colon;\n			$cf_checkboxes .= graf(tag($cf_name.$colon,\'label\').checkbox(\"matrix_\".$matrix_index.\"[$custom_x]\",1,$matrix[$custom_x]));\n		}\n		$cf_td = tda($cf_checkboxes,\' class=\"adi_matrix_field adi_matrix_custom_field\"\');\n		$url = \'?event=adi_matrix_matrix_\'.$matrix_index;\n		if ($matrix_index == \'new\')\n			$view_link = \' \';\n		else\n			$view_link = \'<a href=\"?event=adi_matrix_matrix_\'.$matrix_index.\'\">\'.adi_matrix_gtxt(\'view\').\'</a>\';\n		$out .= tr(\n				// matrix settings\n				tda(\n					graf(tag(adi_matrix_gtxt(\'name\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[name]\",$matrix[\'name\']))\n					.graf(tag(adi_matrix_gtxt(\'sort\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[sort]\",$adi_matrix_sort_options,$matrix[\'sort\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'sort_direction\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[dir]\",$adi_matrix_sort_dir,$matrix[\'dir\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'adi_sort_type\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[sort_type]\",$adi_matrix_sort_type,$matrix[\'sort_type\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'adi_user\',\'\',\':\'),\'label\').adi_matrix_user_popup(\"matrix_\".$matrix_index.\"[user]\",$matrix[\'user\']))\n					.graf(tag(adi_matrix_gtxt(\'privileges\',\'\',\':\'),\'label\').adi_matrix_privs_popup(\"matrix_\".$matrix_index.\"[privs]\",$matrix[\'privs\']))\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_scroll\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[scroll]\",\'1\',($matrix[\'scroll\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[scroll]\",\'0\',($matrix[\'scroll\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_footer\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[footer]\",\'1\',($matrix[\'footer\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[footer]\",\'0\',($matrix[\'footer\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_show_section\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[show_section]\",\'1\',($matrix[\'show_section\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[show_section]\",\'0\',($matrix[\'show_section\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_cf_links\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[cf_links]\",\'article_image\',(!empty($matrix[\'cf_links\']))) // will be comma list one day\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[cf_links]\",\'\',(empty($matrix[\'cf_links\']))) // will be comma list one day\n						,\' style=\"display:none\"\'\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'publish\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[publish]\",\'1\',($matrix[\'publish\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[publish]\",\'0\',($matrix[\'publish\'] == \'0\'))\n					)\n					.graf(tag(adi_matrix_gtxt(\'adi_tab\',\'\',\':\'),\'label\').adi_matrix_tab_popup(\"matrix_\".$matrix_index.\"[tab]\",$matrix[\'tab\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				.tda($view_link)\n				// article selection\n				.tda(\n					adi_matrix_gtxt(\'section\',\'\',\':\').br\n					.tag(adi_matrix_section_checkboxes(\"matrix_\".$matrix_index.\"[criteria_section]\",$matrix[\'criteria_section\']),\'div\',\' class=\"adi_matrix_multi_checkboxes\"\')\n					.graf(tag(adi_matrix_gtxt(\'category\',\'\',\':\'),\'label\').adi_matrix_category_popup(\"matrix_\".$matrix_index.\"[criteria_category]\",$matrix[\'criteria_category\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_include_descendent_cats\',\'\',\':\'),\'label\',\' class=\"adi_matrix_label2\"\').checkbox(\"matrix_\".$matrix_index.\"[criteria_descendent_cats]\",1,$matrix[\'criteria_descendent_cats\']))\n					.graf(tag(adi_matrix_gtxt(\'status\',\'\',\':\'),\'label\').adi_matrix_status_popup(\"matrix_\".$matrix_index.\"[criteria_status]\",$matrix[\'criteria_status\']))\n					.graf(tag(adi_matrix_gtxt(\'author\',\'\',\':\'),\'label\').adi_matrix_user_popup(\"matrix_\".$matrix_index.\"[criteria_author]\",$matrix[\'criteria_author\'],TRUE))\n					.graf(tag(adi_matrix_gtxt(\'keywords\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[criteria_keywords]\",$matrix[\'criteria_keywords\']))\n					.graf(tag(adi_matrix_gtxt(\'timestamp\',\'\',\':\'),\'label\').adi_matrix_timestamp_popup(\"matrix_\".$matrix_index.\"[criteria_timestamp]\",$matrix[\'criteria_timestamp\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_expiry\',\'\',\':\'),\'label\').adi_matrix_expiry_popup(\"matrix_\".$matrix_index.\"[criteria_expiry]\",$matrix[\'criteria_expiry\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_custom_condition\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[criteria_condition]\",$matrix[\'criteria_condition\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				// article data\n				.tda(\n					graf(tag(adi_matrix_gtxt(\'status\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[status]\",1,$matrix[\'status\']))\n					.graf(tag(adi_matrix_gtxt(\'keywords\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[keywords]\",1,$matrix[\'keywords\']))\n					.graf(tag(adi_matrix_gtxt(\'article_image\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[article_image]\",1,$matrix[\'article_image\']))\n					.graf(tag(adi_matrix_gtxt(\'category1\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[category1]\",1,$matrix[\'category1\']))\n					.graf(tag(adi_matrix_gtxt(\'category2\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[category2]\",1,$matrix[\'category2\']))\n					.graf(tag(adi_matrix_gtxt(\'posted\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[posted]\",1,$matrix[\'posted\']))\n					.graf(tag(adi_matrix_gtxt(\'expires\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[expires]\",1,$matrix[\'expires\']))\n					.graf(tag(adi_matrix_gtxt(\'title\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[title]\",1,$matrix[\'title\']))\n					.graf(tag(adi_matrix_gtxt(\'section\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[section]\",1,$matrix[\'section\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				.$cf_td\n				.tda(adi_matrix_delete_button($matrix_list,$matrix_index),\' class=\"adi_matrix_delete\"\')\n			);\n	}\n	return $out;\n}\n\nfunction adi_matrix_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_matrix_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_matrix_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_matrix_prefs))\n			$html = $adi_matrix_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_matrix_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_matrix_tiny_mce_custom() {\n// TinyMCE implementation in a modal window\n	global $adi_matrix_cfs;\n\n	$jquery_ui = adi_matrix_pref(\'adi_matrix_jquery_ui\');\n	$tiny_mce_dir = adi_matrix_pref(\'adi_matrix_tiny_mce_dir\');\n	$adi_matrix_tiny_mce_config = adi_matrix_pref(\'adi_matrix_tiny_mce_config\');\n\n	$title = adi_matrix_gtxt(\'edit\');\n	$ok = adi_matrix_gtxt(\'adi_ok\');\n	$cancel = adi_matrix_gtxt(\'adi_cancel\');\n\n	$script = <<<END_SCRIPT\n<script src=\"$jquery_ui\" type=\"text/javascript\"></script>\n<script type=\"text/javascript\" src=\"$tiny_mce_dir/tiny_mce.js\"></script>\n\n<script type=\"text/javascript\">\n\n//<![CDATA[\n	$(document).ready(function(){\n\n		var i = 0;\n		$(\'.adi_matrix_matrix .glz_text_area_field textarea\').each(function(){\n			// hide textareas\n			$(this).css({display:\"none\"});\n			// assign unique class\n			i++;\n			$(this).addClass(\'mceNoEditor tie\' + i);\n			// create div containing textarea contents (with same unique class)\n			var text = $(this).val();\n			$(this).after(\'<div class=\"tie_div tie\' + i + \'\">\' + text + \'<\\/div>\');\n\n		});\n\n		$(\'.adi_matrix_matrix div.tie_div\').click(function(){\n\n			// get unique class (actually the last class - potentially dodgy?)\n			var thisClass = $(this).attr(\'class\').split(\' \').slice(-1);\n			// get corresponding textarea\n			var thisTextarea = $(\'textarea.\' + thisClass);\n			// get textarea name attribute (e.g. article_2[custom_6])\n			// WILL NEED TO SET THIS INFO IN THE DOM IN ADVANCE - TEXTAREA TITLE ATTR?\n			// var thisName = $(thisTextarea).attr(\'name\');\n\n			$(\'#dialog\').remove();\n			$(\'body\').append(\'<div id=\"dialog\" \\/>\');\n			$(\'#dialog\').dialog({\n				autoOpen: false,\n				bgiframe: true,\n				resizable: false,\n				width: 700,\n				position: [\'center\',35],\n				overlay: { backgroundColor: \'#000\', opacity: 0.6 },\n				open: function(e, ui){\n\n				},\n				beforeclose: function(event, ui) {\n					tinyMCE.get(\'editor\').remove();\n					$(\'#editor\').remove();\n				}\n\n			});\n\n			$(\'#dialog\').dialog(\'option\', \'title\', \'$title\');\n			$(\'#dialog\').dialog(\'option\', \'modal\', true);\n			$(\'#dialog\').dialog(\'option\', \'buttons\', {\n				$cancel: function() {\n							$(this).dialog(\'close\');\n				},\n				$ok: function() {\n						var content = tinyMCE.get(\'editor\').getContent();\n						// feed edited contents into textarea\n						$(thisTextarea).val(content);\n						// feed edited contents into div\n						$(\'div.\' + thisClass).html(content);\n						$(this).dialog(\'close\');\n				}\n			});\n\n			$(\'#dialog\').html(\'<textarea name=\"editor\" id=\"editor\"><\\/textarea>\');\n			$(\'#dialog\').dialog(\'open\');\n			tinyMCE.init({\n				mode : \"specific_textareas\",\n				editor_deselector : \"mceNoEditor\",\n				// start of user configurable options\n				$adi_matrix_tiny_mce_config\n				// end of user-configurable options\n				setup : function(ed) {\n					ed.onInit.add(function(ed) {\n						tinyMCE.get(\'editor\').setContent($(thisTextarea).val());\n						tinyMCE.execCommand(\'mceRepaint\');\n					});\n				}\n\n		 	});\n			return false;\n		});\n	});\n//]]></script>\n\nEND_SCRIPT;\n\n	echo $script;\n}\n\nfunction adi_matrix_tiny_mce_style() {\n// jQuery UI CSS for TinyMCE modal window\n	$jquery_ui_css = adi_matrix_pref(\'adi_matrix_jquery_ui_css\');\n	echo \'<link href=\"\'.$jquery_ui_css.\'\" type=\"text/css\" rel=\"stylesheet\" />\';\n}\n\n\nfunction adi_matrix_admin($event, $step) {\n// adi_matrix admin tab\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_prefs,$adi_matrix_url,$prefs,$textarray,$adi_matrix_privs,$adi_matrix_groups,$txp_permissions,$adi_matrix_glz_cfs,$adi_matrix_sort_options;\n\n	$message = \'\';\n	$installed = adi_matrix_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_matrix_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_matrix_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else { // custom field musical chairs\n			$cfs_fiddled = FALSE;\n			// add additional custom fields that may have suddenly appeared (glz_cfs: custom_11+)\n			foreach ($adi_matrix_cfs as $index => $value) {\n				$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'custom_$index\'\",$adi_matrix_debug); // find out if column exists\n				$a = nextRow($rs);\n				if (empty($a)) {\n					safe_query(\"ALTER TABLE \".safe_pfx(\'adi_matrix\').\" ADD `custom_$index` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n					$cfs_fiddled = TRUE;\n				}\n			}\n			// remove custom fields that may have been deleted in glz_cfs\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'custom%\'\",$adi_matrix_debug);\n			$current_cfs = array();\n			while ($a = nextRow($rs)) { // get list of cf indexes from adi_matrix\n				$index = substr($a[\'Field\'],7); // strip \'custom_\' from \'custom_x\'\n				$current_cfs[$index] = TRUE;\n			}\n			foreach ($current_cfs as $index => $value)\n				if (!array_key_exists($index,$adi_matrix_cfs)) {\n					safe_query(\"ALTER TABLE \".safe_pfx(\'adi_matrix\').\" DROP COLUMN `custom_$index`\",$adi_matrix_debug);\n					$cfs_fiddled = TRUE;\n				}\n			if ($cfs_fiddled)\n				$message = adi_matrix_gtxt(\'adi_matrix_cfs_modified\');\n		}\n	}\n	else\n		$message = array(adi_matrix_gtxt(\'adi_not_installed\'),E_ERROR);\n\n	// admin $step aerobics\n	if ($step == \'update\') {\n		$result = adi_matrix_update_settings();\n		$result ? $message = adi_matrix_gtxt(\'adi_matrix_updated\') : $message = array(adi_matrix_gtxt(\'adi_matrix_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'delete\') {\n		$matrix_index = gps(\'matrix\');\n		$result = adi_matrix_delete($matrix_index);\n		$result ? $message = adi_matrix_gtxt(\'adi_matrix_deleted\') : $message = array(adi_matrix_gtxt(\'adi_matrix_delete_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		foreach ($adi_matrix_prefs as $name => $data) {\n			if (array_key_exists($name,$_POST))\n				$value = $_POST[$name];\n			else if ($data[\'input\'] == \'yesnoradio\')\n				$value = \'0\';\n			else\n				$value = $data[\'value\'];\n			// some values not allowed to be blank, reset to default\n			$non_blanks = array(\'adi_matrix_tiny_mce_dir\',\'adi_matrix_jquery_ui\',\'adi_matrix_jquery_ui_css\');\n			foreach ($non_blanks as $non_blank)\n				if (($name == $non_blank) && (trim($value) == \'\'))\n					$value = $adi_matrix_prefs[$non_blank][\'value\'];\n			$result = $result && adi_matrix_pref($name,$value);\n		}\n		$result ? $message = adi_matrix_gtxt(\'preferences_saved\') : $message = array(adi_matrix_gtxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(adi_matrix_gtxt(\'adi_matrix_admin\'),$message);\n\n	$installed = adi_matrix_installed();\n	if ($installed && !$upgrade_required) {\n		$adi_matrix_list = adi_matrix_read_settings();\n		// output table & input form\n		echo form(\n			startTable(\'list\',\'\',\'txp-list\')\n			.adi_matrix_admin_table($adi_matrix_list,$adi_matrix_cfs)\n			.endTable()\n			.tag(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_update_matrix\'), \"smallerbox\").\n				eInput(\"adi_matrix_admin\").sInput(\"update\"),\n				\'div\',\n				\' class=\"adi_matrix_button\"\'\n			)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_matrix_admin\'\n		);\n		// preferences\n		echo form(\n			tag(\n				tag(adi_matrix_gtxt(\'edit_preferences\'),\'h2\')\n				// display article id\n				.graf(\n					tag(adi_matrix_gtxt(\'adi_display_article_id\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_display_id\",adi_matrix_pref(\'adi_matrix_display_id\'))\n					.sp.sp\n					.tag(adi_matrix_gtxt(\'adi_article_highlighting\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_article_highlighting\",adi_matrix_pref(\'adi_matrix_article_highlighting\'))\n				)\n				// article tooltips\n				.graf(\n					tag(adi_matrix_gtxt(\'adi_article_tooltips\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_article_tooltips\",adi_matrix_pref(\'adi_matrix_article_tooltips\'))\n					.sp.sp\n					.tag(adi_matrix_gtxt(\'adi_matrix_input_field_tooltips\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_input_field_tooltips\",adi_matrix_pref(\'adi_matrix_input_field_tooltips\'))\n				)\n				.( $adi_matrix_glz_cfs ?\n					// tinymce\n					graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce\').\'? \',\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"adi_matrix_tiny_mce\",\'1\',(adi_matrix_pref(\'adi_matrix_tiny_mce\') == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"adi_matrix_tiny_mce\",\'0\',(adi_matrix_pref(\'adi_matrix_tiny_mce\') == \'0\'))\n						,\' class=\"adi_matrix_radio\"\'\n					)\n					.\'<div id=\"peekaboo\">\'\n					// tinymce dir\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce_dir_path\',\'\',\':\'),\'label\')\n						.finput(\"text\",\'adi_matrix_tiny_mce_dir\',adi_matrix_pref(\'adi_matrix_tiny_mce_dir\'),\'\',\'\',\'\',40)\n					)\n					// jquery ui\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_jquery_ui\').\':\',\'label\')\n						.finput(\"text\",\'adi_matrix_jquery_ui\',adi_matrix_pref(\'adi_matrix_jquery_ui\'),\'\',\'\',\'\',40)\n					)\n					// jquery ui css\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_jquery_ui_css\').\':\',\'label\')\n						.finput(\"text\",\'adi_matrix_jquery_ui_css\',adi_matrix_pref(\'adi_matrix_jquery_ui_css\'),\'\',\'\',\'\',40)\n					)\n					// tinymce config\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce_config\').\':\',\'label\')\n					)\n					.graf(\n						text_area(\'adi_matrix_tiny_mce_config\',300,600,adi_matrix_pref(\'adi_matrix_tiny_mce_config\'))\n					)\n					.\'</div>\'\n				: \'\')\n				.fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_update_prefs\'), \"smallerbox\")\n				.eInput(\"adi_matrix_admin\").sInput(\"update_prefs\")\n				,\'div\'\n				,\' class=\"adi_matrix_prefs\"\'\n			)\n		);\n	}\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Event:</b> \".$event.\", <b>Step:</b> \".$step.br.br;\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n		if ($installed) {\n			echo \'<b>Prefs:</b><br/>\';\n			foreach ($adi_matrix_prefs as $name => $this_pref)\n				echo $name.\' = \'.adi_matrix_pref($name).\'<br/>\';\n			echo \'<br/>\';\n			echo \'<b>$adi_matrix_list:</b>\';\n			dmp(adi_matrix_read_settings());\n			echo \'<b>$adi_matrix_sort_options:</b>\';\n			dmp($adi_matrix_sort_options);\n			echo \'<b>$adi_matrix_groups:</b>\';\n			dmp($adi_matrix_groups);\n			echo \'<b>$adi_matrix_cfs:</b>\';\n			dmp($adi_matrix_cfs);\n			echo \'<b>glz_custom_fields plugin:</b> is\';\n			if (!$adi_matrix_glz_cfs)\n				echo \' NOT\';\n			echo \' installed<br/>\';\n		}\n	}\n\n}\n\nfunction adi_matrix_options($event,$step) {\n// plugin options page\n	global $adi_matrix_debug,$adi_matrix_url,$adi_matrix_plugin_status,$textarray;\n\n	$message = \'\';\n\n	$installed = adi_matrix_installed();\n	if ($installed) {\n		$upgrade_required = adi_matrix_upgrade();\n		if ($upgrade_required)\n			$step = \'upgrade\';\n	}\n\n	// dance steps\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_matrix_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = adi_matrix_gtxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_matrix_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_matrix_upgrade(TRUE);\n		$result ? $message = adi_matrix_gtxt(\'adi_upgraded\') : $message = array(adi_matrix_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'downgrade\') {\n		$result = adi_matrix_downgrade();\n		$result ? $message = adi_matrix_gtxt(\'adi_downgraded\') : $message = array(adi_matrix_gtxt(\'adi_downgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'tweak\') { // for development updates\n		$result = safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `cf_links` VARCHAR(255) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n		$result ? $message = \'Tweaked\' : $message = array(\'Tweak failed\',E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_matrix_install();\n		$result ? $message = adi_matrix_gtxt(\'adi_installed\') : $message = array(adi_matrix_gtxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_matrix_uninstall();\n		$result ? $message = adi_matrix_gtxt(\'adi_uninstalled\') : $message = array(adi_matrix_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_matrix - \'.adi_matrix_gtxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		tag(\n			form(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.adi_matrix_gtxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"install\")\n				,\'\',\'\',\'post\',\'adi_matrix_nstall_button\'\n			)\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	$uninstall_button =\n		tag(\n	    	form(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.adi_matrix_gtxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"uninstall\")\n				,\'\',\'\',\'post\',\'adi_matrix_nstall_button adi_matrix_uninstall_button\'\n			)\n			,\'div\'\n			,\' style=\"margin-top:5em\"\');\n\n	if ($adi_matrix_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_matrix_installed();\n	if ($installed) {\n		// options\n		echo tag(\n			tag(\'adi_matrix \'.adi_matrix_gtxt(\'plugin_prefs\'),\'h2\')\n			// textpack links\n			.graf(href(adi_matrix_gtxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_matrix_gtxt(\'adi_textpack_online\'),$adi_matrix_url[\'textpack_download\']))\n			.graf(href(adi_matrix_gtxt(\'adi_textpack_feedback\'),$adi_matrix_url[\'textpack_feedback\']))\n			.$uninstall_button\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	}\n	else // install button\n	    echo $install_button;\n\n	if ($adi_matrix_debug) {\n		echo \"<p><b>Event:</b> \".$event.\", <b>Step:</b> \".$step.\"</p>\";\n		echo \'<b>$adi_textpack (\'.$adi_matrix_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_matrix_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', '\n/*\n	adi_matrix - Multi-article update tabs\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	2.0		- enhancements:\n				- publish & delete articles (for mrdale)\n				- extra article data options: title, section\n				- show sections\n				- added section & author to article title tooltip\n				- matrix under Content or Home tab\n				- improved validation error messages\n				- custom WHERE clause conditions\n				- article list paging\n			- requires TXP 4.5+\n			- tested on TXP 4.6\n			- fix: matrix privileges\n			- improved validation message (URL-only titles)\n			- article edit link styling\n			- and as Apple says \"Includes general performance and stability improvements\", as well as code \"sanity\" improvements for my sake\n	1.2		- TXP 4.5-ified\n			- French colon-isation\n			- lifecycle \"upgrade\" pseudo-event\n	1.1		- code tidy up (thanks gocom)\n			- enhancement: matrix tab optional footer\n			- enhancement: matrix tab column sorting\n			- enhancement: \"Any parent\" & \"Any child\" category wildcards\n			- enhancement: posted timestamp (& reset)\n			- enhancement: expires timestamp\n			- enhancement: multi-section select (for maniqui & mrdale)\n			- enhancement: input field tooltips (for masa)\n			- for mrdale:\n				- <body class=\"adi_matrix\"> on all matrix tabs\n				- another attempt at horizontal scrolling, this time with a fixed article title column\n				- more sorting options\n				- TinyMCE support for glz_custom_field textareas\n				- option to include descendent categories\n			- fixed: checkboxes again! (thanks redbot)\n			- changed: \"Nothing to do\" message changed to \"No articles modified\"\n			- changed: admin tab name/title now \"Article Matrix\"/\"Article Matrix Admin\"\n	1.0.1	- not officially released\n			- fixed: completely unticked checkboxes not updated (thanks redbot)\n			- fixed: detect glz_custom_fields in plugin cache (thanks gocom)\n	1.0		- enhancement: glz_custom_fields compatibility\n			- enhancement: force numeric sort (for jpdupont)\n			- enhancement: sort by Expires timestamp\n			- enhancement: article catagories (for maniqui)\n			- enhancement: option to switch on horizontal scroll (for mrdale)\n			- fixed: MySQL 4.1 compatibility (thanks colak)\n			- fixed: error if custom field contains single quote (thanks maniqui)\n			- fixed: superfluous \"Logged in user\" wildcard option in matrix appearance\n			- now uses lifecycle events\n	0.3		- enhancement: \"One category\", \"Two categories\" wildcards\n			- enhancement: timestamp (for CeBe)\n			- enhancement: expiry (for CeBe)\n			- enhancement: future/expired articles highlighted & preference (for CeBe)\n			- enhancement: article title tooltip, & preference\n			- admin: install/uninstall/textpack moved to plugin options\n	0.2		- fixed: missing child categories (thanks Zanza)\n			- enhancement: \"No category\", \"Any category\" wildcards\n			- enhancement: \"Logged in user\" wildcard\n			- enhancement: article image field (for Zanza)\n			- enhancement: article limit preference (for milosevic)\n	0.1		- initial release\n\n	Custom fields\n	- \"standard\" TXP custom fields: custom_1 ...custom_10, always present\n	- with glz_custom_fields, standard CFs can disappear (on \"reset\"), or additional ones added: custom_11 ...\n\n	Upgrade notes (1.0 - 1.1+)\n	- due to bug in 1.0, expires sort option will get changed to modified\n\n	Downgrade (from 2.0 to 1.1/1.2 only)\n	- go to adi_matrix plugin options tab\n	- add \"&step=downgrade\" to end of URL & hit return\n	- then immediately install previous version of adi_matrix\n	- BEWARE: multiple sections won\'t translate very well\n	- adi_matrix_article_limit pref will be reset to 100\n\n*/\n\nif (txpinterface === \'admin\') {\n	global $adi_matrix_debug,$adi_matrix_dump;\n\n	$adi_matrix_debug = 0; // general debuggy info\n	$adi_matrix_dump = 0; // dump of article data\n\n	// using article_validate & new default section pref (4.5.0), so decamp sharpish if need be\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_matrix_init();\n}\n\nfunction adi_matrix_init() {\n// general setup\n	global $event,$step,$prefs,$txp_groups,$txp_user,$txp_permissions,$theme,$adi_matrix_gtxt,$adi_matrix_url,$adi_matrix_glz_cfs,$adi_matrix_privs,$adi_matrix_groups,$adi_matrix_cfs,$adi_matrix_expiry_options,$adi_matrix_statuses,$adi_matrix_sort_options,$adi_matrix_sort_dir,$adi_matrix_timestamp_options,$adi_matrix_prefs,$adi_matrix_plugin_status,$adi_matrix_debug,$adi_matrix_glz_cfs,$adi_matrix_list,$adi_matrix_validation_errors,$adi_matrix_sort_type,$adi_matrix_categories,$adi_matrix_tabs,$adi_matrix_txp460;\n\n	$adi_matrix_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_matrix_gtxt = array(\n		\'adi_alphabetical\' => \'Alphabetical\',\n		\'adi_any_category\' => \'Any category\',\n		\'adi_any_child_category\' => \'Any child category\',\n		\'adi_article_data\' => \'Article Data\',\n		\'adi_article_matrix\' => \'Article Matrix\',\n		\'adi_article_highlighting\' => \'Article title highlighting\',\n		\'adi_article_limit\' => \'Maximum number of articles\',\n		\'adi_articles_not_modified\' => \'No articles modified\',\n		\'adi_article_selection\' => \'Article Selection\',\n		\'adi_article_tooltips\' => \'Article title tooltips\',\n		\'adi_article_update_fail\' => \'Article update failed\',\n		\'adi_articles_saved\' => \'Articles saved\',\n		\'adi_blank_url_title\' => \'URL-only title blank\',\n		\'adi_cancel\' => \'Cancel\',\n		\'adi_custom_condition\' => \'Custom condition\',\n		\'adi_cf_links\' => \'Custom field links\',\n		\'adi_default_sort\' => \'Default sort\',\n		\'adi_display_article_id\' => \'Display article ID#\',\n		\'adi_duplicate_url_title\' => \'URL-only title already used\',\n		\'adi_edit_titles\' => \'Edit titles\',\n		\'adi_expiry\' => \'Expiry\',\n		\'adi_footer\' => \'Footer\',\n		\'adi_has_expiry\' => \'Has expiry\',\n		\'adi_include_descendent_cats\' => \'Include descendent categories\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_invalid_timestamp\' => \'Invalid timestamp\',\n		\'adi_jquery_ui\' => \'jQuery UI script file\',\n		\'adi_jquery_ui_css\' => \'jQuery UI CSS file\',\n		\'adi_logged_in_user\' => \'Logged in user\',\n		\'adi_matrix\' => \'Matrix\',\n		\'adi_matrix_admin\' => \'Article Matrix Admin\',\n		\'adi_matrix_total_articles\' => \'Total articles in matrix:\',\n		\'adi_matrix_cfs_modified\' => \'Custom field list modified\',\n		\'adi_matrix_delete_fail\' => \'Matrix delete failed\',\n		\'adi_matrix_deleted\' => \'Matrix deleted\',\n		\'adi_matrix_input_field_tooltips\' => \'Input field tooltips\',\n		\'adi_matrix_validation_error\' => \'Validation errors\',\n		\'adi_matrix_name\' => \'Matrix name\',\n		\'adi_matrix_update_fail\' => \'Matrix settings update failed\',\n		\'adi_matrix_updated\' => \'Matrix settings updated\',\n		\'adi_new_article\' => \'New article\',\n		\'adi_no_category\' => \'No category\',\n		\'adi_no_expiry\' => \'No expiry\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_numerical\' => \'Numerical\',\n		\'adi_ok\' => \'OK\',\n		\'adi_one_category\' => \'One category\',\n		\'adi_any_parent_category\' => \'Any parent category\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_reset\' => \'Reset\',\n		\'adi_scroll\' => \'Scroll\',\n		\'adi_show_section\' => \'Show section\',\n		\'adi_sort_type\' => \'Sort type\',\n		\'adi_tab\' => \'Tab\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_tiny_mce\' => \'TinyMCE\',\n		\'adi_tiny_mce_dir_path\' => \'TinyMCE directory path\',\n		\'adi_tiny_mce_hak\' => \'TinyMCE (hak_tinymce)\',\n		\'adi_tiny_mce_javascript\' =>\'TinyMCE (Javascript)\',\n		\'adi_tiny_mce_jquery\' => \'TinyMCE (jQuery)\',\n		\'adi_tiny_mce_config\' => \'TinyMCE configuration\',\n		\'adi_two_categories\' => \'Two categories\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_matrix\' => \'Update matrix settings\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_user\' => \'User\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_matrix_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_matrix\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_matrix_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_matrix_lifecycle\',\'plugin_lifecycle.adi_matrix\');\n\n	// adi_matrix admin tab\n	add_privs(\'adi_matrix_admin\'); // add priv group - defaults to priv \'1\' only\n	register_tab(\'extensions\',\'adi_matrix_admin\',adi_matrix_gtxt(\'adi_article_matrix\')); // add new tab under \'Extensions\'\n	register_callback(\'adi_matrix_admin\',\'adi_matrix_admin\');\n\n	// look for glz_custom_fields\n	$adi_matrix_glz_cfs = load_plugin(\'glz_custom_fields\');\n\n	/*	User privilege summary:\n			0 - none			- can\'t even login\n			1 - publisher		- full matrix data & adi_matrix admin capability\n			2 - manager			- matrix data only\n			3 - copy editor		- matrix data only\n			4 - staff writer	- matrix data only\n			5 - freelancer		- matrix data only\n			6 - designer		- matrix data only\n		Standard article editing privileges:\n			\'article.edit\'                => \'1,2,3\',\n			\'article.edit.published\'      => \'1,2,3\',\n			\'article.edit.own\'            => \'1,2,3,4,5,6\',\n			\'article.edit.own.published\'  => \'1,2,3,4\',\n	*/\n\n	// defines privileges required to view a matrix with privilege restriction (same indexing as $txp_groups)\n	$adi_matrix_privs = array(\n		1 => \'1\',			// publisher\n		2 => \'1,2\',			// managing_editor\n		3 => \'1,2,3\',		// copy_editor\n		4 => \'1,2,3,4\',		// staff_writer\n		5 => \'1,2,3,4,5\',	// freelancer\n		6 => \'1,6\',			// designer\n	);\n\n	// set up user privilege names\n	$adi_matrix_groups = $txp_groups; // to get: 1 => \'publisher\', 2 => \'managing_editor\' etc\n	unset($adi_matrix_groups[0]); // lose index zero (none) - gets us a blank select option too!\n	foreach ($adi_matrix_groups as $index => $group)\n		$adi_matrix_groups[$index] = adi_matrix_gtxt($group); // to get: 1 => \'Publisher\', 2 => \'Managing Editor\' etc in the language \'de jour\'\n\n	// discover custom fields (standard 1-10 & glz 11+) and their non-lowercased titles\n	$adi_matrix_cfs = getCustomFields();\n	foreach ($adi_matrix_cfs as $index => $value)\n		$adi_matrix_cfs[$index] = $prefs[\'custom_\'.$index.\'_set\']; // index = custom fields number, value = custom field title\n\n	// build a picture of article categories\n	$adi_matrix_categories = adi_matrix_categories(getTree(\'root\',\'article\'));\n\n	// article expiry options\n	$adi_matrix_expiry_options = array(\n		0 => \'\',\n		1 => adi_matrix_gtxt(\'adi_no_expiry\'),\n		2 => adi_matrix_gtxt(\'adi_has_expiry\'),\n		3 => adi_matrix_gtxt(\'expired\'),\n	);\n\n	// article status code translation\n	$adi_matrix_statuses = array(\n		1 => adi_matrix_gtxt(\'draft\'),\n		2 => adi_matrix_gtxt(\'hidden\'),\n		3 => adi_matrix_gtxt(\'pending\'),\n		4 => adi_matrix_gtxt(\'live\'),\n		5 => adi_matrix_gtxt(\'sticky\'),\n	);\n\n	// article sort options\n	$adi_matrix_sort_options = array(\n		\'posted\' => adi_matrix_gtxt(\'posted\'),\n		\'title\' => adi_matrix_gtxt(\'title\'),\n		\'id\' => adi_matrix_gtxt(\'id\'),\n		\'lastmod\' => adi_matrix_gtxt(\'article_modified\'),\n		\'expires\' => adi_matrix_gtxt(\'expires\'),\n		\'status\' => adi_matrix_gtxt(\'status\'),\n		\'keywords\' => adi_matrix_gtxt(\'keywords\'),\n		\'article_image\' => adi_matrix_gtxt(\'article_image\'),\n		\'category1\' => adi_matrix_gtxt(\'category1\'),\n		\'category2\' => adi_matrix_gtxt(\'category2\'),\n		\'section\' => adi_matrix_gtxt(\'section\'),\n	);\n	foreach ($adi_matrix_cfs as $index => $value) // add custom fields to sort options\n		$adi_matrix_sort_options[\'custom_\'.$index] = $adi_matrix_cfs[$index];\n\n	// article sort direction\n	$adi_matrix_sort_dir = array(\n		\'desc\' => adi_matrix_gtxt(\'descending\'),\n		\'asc\' => adi_matrix_gtxt(\'ascending\'),\n	);\n\n	// article sort type\n	$adi_matrix_sort_type = array(\n		\'alphabetical\' => adi_matrix_gtxt(\'adi_alphabetical\'),\n		\'numerical\' => adi_matrix_gtxt(\'adi_numerical\'),\n	);\n\n	// article timestamp options\n	$adi_matrix_timestamp_options = array(\n		\'any\' => adi_matrix_gtxt(\'time_any\'),\n		\'past\' => adi_matrix_gtxt(\'time_past\'),\n		\'future\' => adi_matrix_gtxt(\'time_future\'),\n	);\n\n	// validation errors\n	$adi_matrix_validation_errors = array(\n		0 => adi_matrix_gtxt(\'adi_invalid_timestamp\'),\n		1 => adi_matrix_gtxt(\'article_expires_before_postdate\'),\n		2 => adi_matrix_gtxt(\'adi_duplicate_url_title\'),\n		3 => adi_matrix_gtxt(\'adi_blank_url_title\'),\n	);\n\n	// default preferences\n	$adi_matrix_prefs = array(\n		\'adi_matrix_article_highlighting\'	=> array(\'value\' => \'1\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_article_tooltips\'		=> array(\'value\' => \'1\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_display_id\'				=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_input_field_tooltips\'	=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_jquery_ui\'				=> array(\'value\' => \'../scripts/jquery-ui.js\', \'input\' => \'text_input\'),\n		\'adi_matrix_jquery_ui_css\'			=> array(\'value\' => \'../scripts/jquery-ui.css\', \'input\' => \'text_input\'),\n		\'adi_matrix_tiny_mce\'				=> array(\'value\' => \'0\', \'input\' => \'yesnoradio\'),\n		\'adi_matrix_tiny_mce_type\'			=> array(\'value\' => \'custom\', \'input\' => \'text_input\'),\n 		\'adi_matrix_tiny_mce_dir\'			=> array(\'value\' => \'../scripts/tiny_mce\', \'input\' => \'text_input\'),\n		\'adi_matrix_tiny_mce_config\'		=> array(\'value\' => \'see below\', \'input\' => \'text_area\'),\n	);\n	$adi_matrix_prefs[\'adi_matrix_tiny_mce_config\'][\'value\'] = \'\nlanguage : \"en\",\ntheme : \"advanced\",\nplugins : \"safari,pagebreak,style,layer,table,save,advhr,advlink,emotions,iespell,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template\",\ntheme_advanced_buttons1 : \"pagebreak,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect\",\ntheme_advanced_buttons2 : \"cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor\",\ntheme_advanced_buttons3 : \"tablecontrols\",\ntheme_advanced_toolbar_location : \"top\",\ntheme_advanced_toolbar_align : \"left\",\ntheme_advanced_statusbar_location : \"bottom\",\ntheme_advanced_resizing : true,\nextended_valid_elements: \"style[*]\",\nwidth: \"600\",\nheight: \"400\",\n\';\n\n	// tabs\n	$adi_matrix_tabs = array(\n		\'content\' => adi_matrix_gtxt(\'tab_content\'),\n		\'start\' => adi_matrix_gtxt(\'tab_start\'),\n	);\n\n	// plugin options\n	$adi_matrix_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_matrix\',$adi_matrix_debug);\n	if ($adi_matrix_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_matrix\'); // add priv set - defaults to priv \'1\' only\n		register_callback(\'adi_matrix_options\',\'plugin_prefs.adi_matrix\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_matrix_options\'); // add priv set - defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_matrix_options\',\'adi_matrix options\');\n		register_callback(\'adi_matrix_options\',\'adi_matrix_options\');\n	}\n\n	// glz_custom_fields stuff\n	if ($adi_matrix_glz_cfs) {\n		if (strstr($event, \'adi_matrix_matrix_\')) {\n			// date & time pickers\n		    add_privs(\'glz_custom_fields_css_js\',\"1,2,3,4,5,6\");\n		    register_callback(\'glz_custom_fields_css_js\',\'admin_side\',\'head_end\');\n			// TinyMCE\n			if (adi_matrix_pref(\'adi_matrix_tiny_mce\')) {\n				register_callback(\'adi_matrix_tiny_mce_style\',\'admin_side\',\'head_end\');\n				register_callback(\'adi_matrix_tiny_mce_\'.adi_matrix_pref(\'adi_matrix_tiny_mce_type\'),\'admin_side\',\'footer\');\n			}\n		}\n	}\n\n	// article matrix tabs\n	$adi_matrix_list = array();\n	if (adi_matrix_installed())\n		$adi_matrix_list = adi_matrix_read_settings(adi_matrix_upgrade());\n	$all_privs = \'1,2,3,4,5,6\'; // everybody\n	foreach ($adi_matrix_list as $index => $matrix) {\n		$matrix_event = \'adi_matrix_matrix_\'.$index;\n		$matrix_tab_name = $matrix[\'name\'];\n		if ($matrix[\'privs\'])\n			$priv_set = $adi_matrix_privs[$matrix[\'privs\']];\n		else\n			$priv_set = $all_privs; // everybody\'s welcome\n		add_privs($matrix_event,$priv_set); // add priv set for each matrix event (to $txp_permissions)\n		if ($matrix[\'user\'])\n			$user_allowed = ($txp_user == $matrix[\'user\']);\n		else // open to all users\n			$user_allowed = TRUE;\n		$has_privs = has_privs($matrix_event);\n		$register_this_tab = ($user_allowed && $has_privs) || has_privs(\'adi_matrix_admin\');\n		if ($register_this_tab) {\n			$tab = $matrix[\'tab\'];\n			if ($tab == \'start\') // switch on Home tab\n				add_privs(\'tab.start\',$all_privs); // all privs\n			register_tab($tab,$matrix_event,$matrix_tab_name);\n			register_callback(\"adi_matrix_matrix\",$matrix_event);\n		}\n		if ($adi_matrix_debug) {\n			echo \"MATRIX: index=$index, name=\".$matrix[\'name\'].\", user=\".$matrix[\'user\'].\", privs=\".$matrix[\'privs\'].\", priv_set=$matrix_event($priv_set), tab=\".$matrix[\'tab\'].br;\n			$user_privs = safe_field(\'privs\',\'txp_users\',\"name=\'$txp_user\'\");\n			echo \"USER: user=$txp_user, user_privs=$user_privs, user_allowed=$user_allowed, has_privs=$has_privs\".br;\n			echo \"TAB: register_this_tab=$register_this_tab\".br.br;\n		}\n	}\n	if ($adi_matrix_debug) {\n		echo \'<b>$adi_matrix_privs:</b>\';\n		dmp($adi_matrix_privs);\n		echo \'<b>adi_matrix added priv sets ($txp_permissions):</b>\'.br;\n		foreach ($txp_permissions as $index => $value)\n			if (strpos($index,\'adi_matrix\') === 0)\n				echo $index.\' = \'.$value.br;\n	}\n\n	// style\n	if (strstr($event,\'adi_matrix_matrix_\') || ($event == \'adi_matrix_admin\'))\n		register_callback(\'adi_matrix_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_matrix_admin\'))\n		register_callback(\'adi_matrix_admin_script\',\'admin_side\',\'head_end\');\n	if (strstr($event,\'adi_matrix_matrix_\'))\n		register_callback(\'adi_matrix_matrix_script\',\'admin_side\',\'head_end\');\n}\n\nfunction adi_matrix_gtxt($phrase,$atts=array(),$suffix = \'\') {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_matrix_gtxt() are functionally equivalent\n	global $adi_matrix_gtxt,$prefs;\n\n	// make sure $atts is an array (done automatically in TXP 4.5+)\n	if (!is_array($atts)) $atts = array();\n	// French colon-isation\n	if ($suffix == \':\' && (strpos($prefs[\'language\'],\'fr-\') === 0))\n		$suffix = sp.$suffix;\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_matrix_gtxt)) // adi translation found\n			return $adi_matrix_gtxt[$phrase].$suffix;\n		else // last resort\n			return $phrase.$suffix;\n	else // TXP translation\n		return gTxt($phrase,$atts).$suffix;\n}\n\nfunction adi_matrix_style() {\n// some style (Classic: table#list, Remora/Hive: table.txp-list)\n	global $prefs,$adi_matrix_txp460;\n\n	echo \'<style type=\"text/css\">\n			/* adi_matrix */\n			/* general */\n			.txp-body { max-width:none }\n			table#list td { padding:0.5em }\n			.adi_matrix_button { margin:1em auto; text-align:center }\n			.txp-list { width:auto; margin:0 auto }\n			/* admin tab */\n			.adi_matrix_admin input.radio { margin-left:0.5em }\n			.adi_matrix_admin form { text-align:center }\n			.adi_matrix_field { text-align:left }\n			.adi_matrix_field label { display:block; float:left; width:8em }\n			.adi_matrix_field label.adi_matrix_label2 { width:auto }\n			.adi_matrix_field p { overflow:hidden; min-height:1.4em }\n			.adi_matrix_custom_field label { width:12em }\n			.adi_matrix_multi_checkboxes { margin:0.3em 0 0.5em; height:5em; padding:0.2em; overflow:auto; border:1px solid #ccc }\n			.adi_matrix_multi_checkboxes label { float:none; width:auto }\n			.adi_matrix_prefs { margin-top:5em; text-align:center }\n			.adi_matrix_prefs input { margin-left:1em }\n			.adi_matrix_prefs input.checkbox { margin-left:0.5em }\n			.adi_matrix_prefs input.smallerbox { margin-left:0 }\n			.adi_matrix_prefs .adi_matrix_radio label { margin-right:1em }\n			.adi_matrix_prefs .adi_matrix_radio input { margin-left:0.5em }\n			.adi_matrix_admin .adi_matrix_delete a { font-size:120% }\n			/* matrix tabs */\n			.adi_matrix_matrix h1 { margin-top:0; text-align:center; font-weight:bold }\n			.adi_matrix_matrix table#list th.adi_matrix_noborder { border:0 }\n			.adi_matrix_matrix table#list .adi_matrix_delete { width:4em; font-weight:normal; text-align:center }\n			.adi_matrix_matrix table#list .adi_matrix_delete a { font-size:120% }\n			.adi_matrix_matrix table#list td.adi_matrix_add { font-size:120% }\n			.adi_matrix_matrix .date input { margin-top:0 }\n			.adi_matrix_none { margin-top:2em; text-align:center }\n			.adi_matrix_field_title { white-space:nowrap } /* stops edit link dropping below */\n			.adi_matrix_timestamp { white-space:nowrap } /* stops date or time being split */\n			.adi_matrix_future a { font-weight:bold }\n			.adi_matrix_expired a { font-style:italic }\n			.adi_matrix_error input { border-color:#b22222; color:#b22222 }\n			.adi_matrix_matrix .adi_matrix_matrix_prefs { margin-top:4em; text-align:center }\n			.adi_matrix_grand_total { text-align:center }\n			.adi_matrix_edit_link a { display:inline-block; width:21px; height:21px; line-height:2; border:1px solid #ccc; border-radius:0.4em; background:url(theme/classic/view-text.png) 50% 50% no-repeat #FFF8DD }\n			.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { text-decoration:none; background-color:#fff }\n			/* matrix tabs 4.5 */\n			.txp-list .adi_matrix_timestamp .time input { margin-top:0.5em }\n			.txp-list .adi_matrix_timestamp { min-width:11em }\n			/* matrix tabs 4.6 */\n			.txp-list th,.txp-list td { padding-left:0.5em }\n			/* glz_custom_fields */\n			html[xmlns] td.glz_custom_date-picker_field.clearfix { display:table-cell!important } /* override clearfix  - for date-picker field */\n			/*.adi_matrix_matrix input.date-picker { float:left; width:7em }*/\n			.adi_matrix_matrix input.date-picker { width:7em } /* 2.0 layout tweak */\n			.adi_matrix_matrix td.glz_custom_date-picker_field a.dp-choose-date { display:block; clear:left } /* 2.0 layout tweak */\n			/*.adi_matrix_matrix td.glz_custom_date-picker_field { min-width:10em }*/ /* 2.0 layout tweak */\n			.adi_matrix_matrix input.time-picker { width:4em }\n			/* tinyMCE */\n			.adi_matrix_matrix .glz_text_area_field div.tie_div { overflow-y:scroll; width:17.6em; height:5.6em; padding:0.2em; border:1px solid; border-color:#aaa #eee #eee #aaa; background-color: #eee }\n			/* scrolling matrix */\n			.adi_matrix_scroll table#list th:first-child,\n			.adi_matrix_scroll table#list td:first-child { position:absolute; width:13%; left:0; top:auto; padding-right:1em; border-bottom-width:0 }\n			.adi_matrix_scroll table#list thead th:first-child { border-bottom-width:1px }\n			.adi_matrix_scroll div.scroll_box { width:87%; margin-left:13%; padding-bottom:1em;overflow-x:scroll; overflow-y:visible; border:solid #eee; border-width:0 1px }\n			.adi_matrix_scroll table#list tfoot td:first-child { border-top:1px solid #ddd }\n			.adi_matrix_scroll table#list tfoot td { border-bottom:0 }\n			.adi_matrix_scroll table#list tfoot td:first-child a { display:inline }\n			/* footer */\n			table#list tfoot td { font-weight:bold }\n			table#list tfoot td.desc a,\n			table#list tfoot td.asc a { width:auto; background-color:transparent; background-image:url(\"./txp_img/arrowupdn.gif\"); background-repeat: no-repeat; background-attachment: scroll; background-position: right -18px; padding-right: 14px; margin-right: 0pt }\n			table#list tfoot td.asc a { background-position: right 2px }\n		\';\n\n	if ($prefs[\'theme_name\'] == \'hive\')\n		echo \'\n			/* Hive theme (mostly stolen from Hive textpattern.css) */\n			.txp-list tfoot td.desc a, table#list tfoot td.asc a { background-image:none }\n			.txp-list tfoot td { text-shadow:1px 1px 0 rgba(255, 255, 255, 0.4); background-color:#eee; background-image:linear-gradient(#eee, #ddd); border-top:1px solid #ddd; border-right:1px solid #d0d0d0; border-bottom:1px solid #c4c4c4 }\n			.txp-list tfoot td:first-child { border-left:1px solid #ddd }\n			.txp-list tfoot td a { position:relative; display:block; color:#333; margin-right:.5em }\n			.txp-list tfoot td a:hover { color:#333; text-decoration:none }\n			.txp-list tfoot td a:after { position:absolute; top:.53846153846154em; right:-0.76923em; z-index:10; width:0; height:0; display:inline-block; *display:inline; *zoom:1; vertical-align:top; opacity:.5; text-indent:110%; white-space:nowrap; overflow:hidden }\n			.txp-list tfoot td a:hover:after { opacity:1 }\n			.txp-list tfoot td.desc a:after { content:\"&#8595;\"; border-left:.30769230769231em solid transparent; border-right:.30769230769231em solid transparent; border-top:0.30769em solid #333; border-bottom:0 }\n			.txp-list tfoot td.asc a:after { content:\"&#8593;\"; border-left:.30769230769231em solid transparent; border-right:.30769230769231em solid transparent; border-top:0; border-bottom:0.30769em solid #333 }\n			p.prev-next, form.pageby { text-align:center }\n			.adi_matrix_edit_link a { border:1px solid #eee; border-radius:0.5em; background:url(theme/classic/view-text.png) 50% 50% no-repeat #eee }\n			.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { background-color:#FFF6D3 }\n		\';\n\n	if ($adi_matrix_txp460) {\n		echo \'\n			/* TXP 4.6 only */\n			.adi_matrix_edit_link a { width:2em; height:2em; background-image:url(theme/classic/img/view-text.svg); border-radius:0 }\n		\';\n		if ($prefs[\'theme_name\'] == \'hive\')\n			echo \'\n				/* TXP 4.6 Hive only */\n				.adi_matrix_edit_link a { border:0; border-radius:0.5em }\n				.adi_matrix_edit_link a:hover, .adi_matrix_edit_link a:active { background-color:#FFF6D3 }\n			\';\n	}\n\n	echo \'</style>\'.n;\n}\n\nfunction adi_matrix_admin_script() {\n// jQuery magic for admin tab\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n\n	$(function(){\n		$(\"#peekaboo\").hide();\n		$(\'input[name=\"adi_matrix_tiny_mce\"][value=\"1\"]:checked\').each(function(){\n			$(\"#peekaboo\").show();\n		});\n		$(\'input[name=\"adi_matrix_tiny_mce\"]:radio:eq(0)\').change(function(){\n			$(\"#peekaboo\").show();\n		});\n		$(\'input[name=\"adi_matrix_tiny_mce\"]:radio:eq(1)\').change(function(){\n			$(\"#peekaboo\").hide();\n		});\n	});\n</script>\n\nEND_SCRIPT;\n}\n\nfunction adi_matrix_matrix_script() {\n// jQuery action\n\n	// add class to <body>\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function(){\n		$(\'body\').addClass(\'adi_matrix\');\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_matrix_read_settings($just_the_basics=FALSE) {\n// get matrix settings from database\n	global $adi_matrix_cfs,$adi_matrix_debug;\n\n	$rs = safe_rows_start(\'*\',\'adi_matrix\',\"1=1\");\n	$matrix_list = array();\n	if ($rs)\n		while ($a = nextRow($rs)) {\n			extract($a);\n			// just enough to display matrix tab\n			$matrix_list[$id][\'name\'] = $name;\n			$matrix_list[$id][\'user\'] = $user;\n			$matrix_list[$id][\'privs\'] = $privs;\n			if (!isset($tab)) // tab introduced in v2.0, so may not be present during upgrade install\n				$tab = \'content\';\n			$matrix_list[$id][\'tab\'] = $tab;\n			// load in the rest\n			if (!$just_the_basics) {\n				$the_rest = array(\'sort\',\'dir\',\'sort_type\',\'scroll\',\'footer\',\'title\',\'publish\',\'show_section\',\'cf_links\',\'criteria_section\',\'criteria_category\',\'criteria_descendent_cats\',\'criteria_status\',\'criteria_author\',\'criteria_keywords\',\'criteria_timestamp\',\'criteria_expiry\',\'criteria_condition\',\'status\',\'keywords\',\'article_image\',\'category1\',\'category2\',\'posted\',\'expires\',\'section\');\n				foreach ($the_rest as $item)\n					$matrix_list[$id][$item] = $$item;\n				// custom fields\n				foreach ($adi_matrix_cfs as $index => $value) {\n					$custom_x = \'custom_\'.$index;\n					if (isset($$custom_x)) // check that custom field is known to adi_matrix\n						$matrix_list[$id][$custom_x] = $$custom_x;\n				}\n			}\n		}\n	return $matrix_list;\n}\n\nfunction adi_matrix_where($criteria=array()) {\n// take matrix criteria & create a WHERE clause\n// mostly ripped off from doArticles() in publish.php\n	global $adi_matrix_debug,$adi_matrix_categories;\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Criteria:</b>\";\n		dmp($criteria);\n	}\n\n	extract($criteria);\n	$excerpted = \'\';\n	$month = \'\';\n	$time = $timestamp;\n\n	// categories\n	$cats = array();\n	if ($category == \'!no_category!\')\n		$category = \" and (Category1 = \'\' and Category2 = \'\')\";\n	else if ($category == \'!any_category!\')\n		$category = \" and (Category1 != \'\' or Category2 != \'\')\";\n	else if ($category == \'!one_category!\')\n		$category = \" and (Category1 != \'\' and Category2 = \'\') or  (Category1 = \'\' and Category2 != \'\')\";\n	else if ($category == \'!two_categories!\')\n		$category = \" and (Category1 != \'\' and Category2 != \'\')\";\n	else if ($category == \'!any_parent_category!\') {\n		foreach ($adi_matrix_categories as $name => $this_cat)\n			if ($this_cat[\'children\'])\n				$cats[] = $name;\n		$category = implode(\',\',$cats);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n	else if ($category == \'!any_child_category!\') {\n		foreach ($adi_matrix_categories as $name => $this_cat)\n			if ($this_cat[\'parent\'] != \'root\')\n				$cats[] = $name;\n		$category = implode(\',\',$cats);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n	else { // single category (perhaps with optional descendents)\n		if ($descendent_cats)\n			$category .= \',\'.implode(\',\',$adi_matrix_categories[$category][\'children\']);\n		$category = implode(\"\',\'\", doSlash(do_list($category)));\n		$category = (!$category) ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\n	}\n\n	$section   = (!$section) ? \'\' : \" and Section IN (\'\".implode(\"\',\'\", doSlash(do_list($section))).\"\')\";\n	$excerpted = ($excerpted==\'y\') ? \" and Excerpt !=\'\'\" : \'\';\n	if ($author == \'!logged_in_user!\')\n		$author = $txp_user;\n	$author    = (!$author) ? \'\' : \" and AuthorID IN (\'\".implode(\"\',\'\", doSlash(do_list($author))).\"\')\";\n	$month     = (!$month) ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\n\n	// posted timestamp\n	switch ($time) {\n		case \'any\':\n			$time = \"\";\n			break;\n		case \'future\':\n			$time = \" and Posted > now()\";\n			break;\n		default:\n			$time = \" and Posted <= now()\";\n	}\n\n	// expiry\n	switch ($expiry) {\n		case \'1\': // no expiry set\n			$time .= \" and Expires = \".NULLDATETIME;\n			break;\n		case \'2\': // has expiry set\n			$time .= \" and Expires != \".NULLDATETIME;\n			break;\n		case \'3\': // expired\n			$time .= \" and now() > Expires and Expires != \".NULLDATETIME;\n			break;\n	}\n\n	$custom = \'\'; // MAY GET CONFUSING WITH criteria_condition\n//	if ($customFields) {\n//		foreach($customFields as $cField) {\n//			if (isset($atts[$cField]))\n//				$customPairs[$cField] = $atts[$cField];\n//		}\n//		if(!empty($customPairs)) {\n//			$custom = buildCustomSql($customFields,$customPairs);\n//		}\n//	}\n\n	if ($keywords) {\n		$keys = doSlash(do_list($keywords));\n		foreach ($keys as $key) {\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n		}\n		$keywords = \" and (\" . implode(\' or \',$keyparts) . \")\";\n	}\n\n	if ($status)\n		$statusq = \' and Status = \'.intval($status);\n	else // either blank or zero\n		$statusq = \'\'; // all statuses\n\n	if ($condition)\n		$conditionq = \' and \'.$condition;\n	else\n		$conditionq = \'\';\n\n	$where = \'1\'.$statusq.$time.$category.$section.$excerpted.$month.$author.$keywords.$custom.$conditionq;\n\n	if ($adi_matrix_debug) {\n		echo \"<b>WHERE clause:</b> $where\".br;\n	}\n\n	return $where;\n}\n\nfunction adi_matrix_order_by($sort,$dir,$sort_type) {\n// generate ORDER BY clause\n// mostly ripped off from doArticles() in publish.php\n	global $adi_matrix_debug;\n\n	// map columns to sort query\n	switch ($sort) {\n		case \'posted\':\n			$sortq = \'Posted\';\n			break;\n		case \'expires\':\n			$sortq = \'Expires\';\n			break;\n		case \'lastmod\':\n			$sortq = \'LastMod\';\n			break;\n		case \'title\':\n			$sortq = \'Title\';\n			break;\n		case \'id\':\n			$sortq = \'ID\';\n			break;\n		case \'status\':\n			$sortq = \'Status\';\n			break;\n		case \'article_image\':\n			$sortq = \'Image\';\n			break;\n		case \'category1\':\n			$sortq = \'Category1\';\n			break;\n		case \'category2\':\n			$sortq = \'Category2\';\n			break;\n		case \'section\':\n			$sortq = \'Section\';\n			break;\n		default: // custom_x will fall through to here\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'textpattern\').\" LIKE \'$sort\'\",$adi_matrix_debug); // find out if column (glz custom field probably) still exists\n			$a = nextRow($rs);\n			if (empty($a))\n				$sortq = \'Posted\';\n			else\n				$sortq = $sort;\n	}\n\n	// sort type\n	if ($sort_type == \'numerical\')\n		$sort_typeq = \' + 0\';\n	else\n		$sort_typeq = \'\';\n\n	// sort it all out\n	$sortq = \' ORDER BY \'.$sortq.$sort_typeq.\' \'.$dir.\', Posted desc\'; // add direction ... & also secondary sort (pointless but harmless with ID, Posted & Expires)\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Sort:</b> sort=$sort, dir=$dir, sort_type=$sort_type\".br;\n		echo \"<b>ORDER BY clause:</b> $sortq\".br;\n	}\n\n	return $sortq;\n}\n\nfunction adi_matrix_get_articles($where=\'1\',$sortq=\'\',$matrix_index,$offset,$limit) {\n// read required articles from database and populate $adi_matrix_articles\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_list,$adi_matrix_sort_options,$txp_user,$adi_matrix_timestamp_options;\n\n	$adi_matrix_articles = array();\n\n	// get the required articles from database\n	$rs = safe_rows_start(\n			\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\"\n			,\'textpattern\'\n			,$where.doSlash($sortq).\' LIMIT \'.intval($offset).\', \'.intval($limit)\n			,$adi_matrix_debug\n			);\n\n	if ($rs) // populate $adi_matrix_articles array\n		while ($a = nextRow($rs)) {\n			extract($a);\n			$adi_matrix_articles[$ID] = array();\n			$adi_matrix_articles[$ID][\'title\'] = html_entity_decode($Title, ENT_QUOTES, \'UTF-8\');\n			$adi_matrix_articles[$ID][\'section\'] = $Section;\n			$adi_matrix_articles[$ID][\'author\'] = $AuthorID; // need author for article edit priv check\n			$adi_matrix_articles[$ID][\'status\'] = $Status; // need status for article edit priv check\n			$adi_matrix_articles[$ID][\'keywords\'] = $Keywords;\n			$adi_matrix_articles[$ID][\'article_image\'] = $Image;\n			$adi_matrix_articles[$ID][\'category1\'] = $Category1;\n			$adi_matrix_articles[$ID][\'category2\'] = $Category2;\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				$adi_matrix_articles[$ID][$custom_x] = $$custom_x;\n			}\n\n			// article timestamps\n			$adi_matrix_articles[$ID][\'uposted\'] = $uPosted; // unix timestamp format (in server timezone)\n			$adi_matrix_articles[$ID][\'posted\'] = $Posted; // article date/time string (YY-MM-DD HH:MM:SS) from database\n			$adi_matrix_articles[$ID][\'display_posted\'] = safe_strftime(\'%Y-%m-%d %X\',$uPosted); // article date/time string (YY-MM-DD HH:MM:SS) displayed to user (TXP time)\n			$adi_matrix_articles[$ID][\'uexpires\'] = $uExpires; // unix timestamp format (in server timezone)\n			$adi_matrix_articles[$ID][\'expires\'] = $Expires; // article date/time string (YY-MM-DD HH:MM:SS) from database\n			if ($Expires == \'0000-00-00 00:00:00\')\n				$adi_matrix_articles[$ID][\'display_expires\'] = $Expires; // keep it zeroed\n			else\n				$adi_matrix_articles[$ID][\'display_expires\'] = safe_strftime(\'%Y-%m-%d %X\',$uExpires); // article date/time string (YY-MM-DD HH:MM:SS) displayed to user (TXP time)\n\n			// highlighting\n			$highlight = 0;\n			$now = time();\n			if (($now > $uExpires) && ($uExpires != 0)) // expired article\n				$highlight = 1;\n			if ($now < $uPosted) // future article\n				$highlight = 2;\n			$adi_matrix_articles[$ID][\'highlight\'] = $highlight;\n		}\n\n	return $adi_matrix_articles;\n}\n\nfunction adi_matrix_update_article($id,$data) {\n// translate $_POSTED article data into SQL-speak\n	global $adi_matrix_debug,$txp_user,$adi_matrix_cfs,$adi_matrix_articles,$vars,$prefs;\n\n	include_once txpath.\'/include/txp_article.php\'; // to get textile_main_fields()\n\n	// set up variables in the style of $vars\n	$Title = $Title_plain = isset($data[\'title\']) ? $data[\'title\'] : \'\';\n	$Status = isset($data[\'status\']) ? $data[\'status\'] : \'\';\n	$Section = isset($data[\'section\']) ? $data[\'section\'] : \'\';\n	$Keywords = isset($data[\'keywords\']) ? trim(preg_replace(\'/( ?[\\r\\n\\t,])+ ?/s\', \',\', preg_replace(\'/ +/\', \' \', $data[\'keywords\'])), \', \') : \'\';\n	$Image = isset($data[\'article_image\']) ? $data[\'article_image\'] : \'\';\n	$Category1 = isset($data[\'category1\']) ? $data[\'category1\'] : \'\';\n	$Category2 = isset($data[\'category2\']) ? $data[\'category2\'] : \'\';\n	// posted\n	if (isset($data[\'posted\'][\'reset_time\']))\n		$publish_now = \'1\';\n	else\n		$publish_now = \'\';\n	if (isset($data[\'posted\'])) { // this is in TXP date/time\n		extract($data[\'posted\']);\n		$Posted = $year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second;\n	}\n	else // force now\n		$publish_now = \'1\'; // NOT SURE WHY DOING THIS, BUT DOESN\'T DO ANY HARM (IS IGNORED IF POSTED NOT CHANGED)\n	// expires\n	if (isset($data[\'expires\'])) { // this is in TXP date/time\n		foreach ($data[\'expires\'] as $index => $value) { // convert expiry vars ($year -> $exp_year) to align with $vars in txp_article.php\n			$var = \'exp_\'.$index;\n			$$var = $value;\n		}\n		$Expires = $exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second;\n	}\n	else // force no expiry\n		$exp_year = \'0000\';\n	// custom Fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (isset($data[$custom_x]))\n			$$custom_x = $data[$custom_x];\n		else\n			$$custom_x = \'\';\n	}\n	// set the rest (not used by adi_matrix_update_article)\n	$Body = \'\';\n	$Body_html = \'\';\n	$Excerpt = \'\';\n	$Excerpt_html = \'\';\n	$textile_body = $prefs[\'use_textile\'];\n	$textile_excerpt = $prefs[\'use_textile\'];\n	$Annotate = \'0\';\n	$override_form = \'\';\n	$AnnotateInvite = \'\';\n\n	// package them all up\n	$updates = compact($vars);\n\n	if ($adi_matrix_debug) {\n		echo \'<b>Article \'.$id.\' data:</b>\';\n		dmp($updates);\n	}\n\n	// do some validation, textilation & slashing\n	$incoming = array_map(\'assert_string\',$updates);\n	$incoming = textile_main_fields($incoming);	// converts ampersands to &amp; in titles\n	extract(doSlash($incoming));\n	if (isset($data[\'status\']))\n		extract(array_map(\'assert_int\', array(\'Status\' => $Status)));\n\n	// title\n	if (isset($data[\'title\']))\n		$titleq = \"Title=\'$Title\', \";\n	else\n		$titleq = \'\';\n	// status\n	$old_status = $new_status = $adi_matrix_articles[$id][\'status\'];\n	if (isset($data[\'status\'])) {\n		$new_status = $Status;\n		// tweak status according to privs\n		if (!has_privs(\'article.publish\') && $new_status >= STATUS_LIVE)\n			$new_status = STATUS_PENDING;\n		$statusq = \'Status=\'.doSlash($new_status).\', \';\n		if ($new_status >= STATUS_LIVE) // live & sticky articles only\n			update_lastmod();\n	}\n	else\n		$statusq = \'\';\n	// section\n	if (isset($data[\'section\']))\n		$sectionq = \"Section=\'$Section\', \";\n	else\n		$sectionq = \'\';\n	// keywords\n	if (isset($data[\'keywords\']))\n		$keywordsq = \"Keywords=\'$Keywords\', \";\n	else\n		$keywordsq = \'\';\n	// article image\n	if (isset($data[\'article_image\']))\n		$article_imageq = \"Image=\'$Image\', \";\n	else\n		$article_imageq = \'\';\n	// categories\n	if (isset($data[\'category1\']))\n		$categoryq = \"Category1=\'$Category1\', \";\n	else\n		$categoryq = \'\';\n	if (isset($data[\'category2\']))\n		$categoryq .= \"Category2=\'$Category2\', \";\n	else\n		$categoryq .= \'\';\n	// posted\n	$postedq = \'\';\n	if (isset($data[\'posted\'])) {\n		if ($publish_now)\n			$postedq = \"Posted=now(), \";\n		else { // convert TXP date/time to DB timestamp\n			$ts = strtotime($Posted);\n			$date_error = ($ts === false || $ts === -1);\n			if (!$date_error) {\n				$when_ts = $ts - tz_offset($ts);\n				$when = \"from_unixtime($when_ts)\";\n				$postedq = \"Posted=$when, \";\n			}\n		}\n	}\n	// expires\n	$expiresq = \'\';\n	if (isset($data[\'expires\'])) {\n		if ($exp_year == \'0000\')\n			$expiry = 0;\n		else { // convert TXP date/time to DB timestamp\n			$ts = strtotime($Expires);\n			$expiry = $ts - tz_offset($ts);\n		}\n		if ($expiry) {\n			$date_error = ($ts === false || $ts === -1);\n			if (!$date_error) {\n				$expires = $ts - tz_offset($ts);\n				$whenexpires = \"from_unixtime($expires)\";\n				$expiresq = \"Expires=$whenexpires, \";\n			}\n		}\n		else\n			$expiresq = \"Expires=\".NULLDATETIME.\", \";\n	}\n	// custom fields\n	$cfq = array();\n	foreach($adi_matrix_cfs as $i => $cf_name) {\n		$custom_x = \"custom_{$i}\";\n		if (isset($data[$custom_x]))\n			$cfq[] = \"custom_$i = \'\".$$custom_x.\"\'\";\n	}\n	$cfq = implode(\', \', $cfq);\n\n	// update article in database\n	$res = safe_update(\"textpattern\",\n	   $titleq.$sectionq.$statusq.$keywordsq.$article_imageq.$categoryq.$postedq.$expiresq.(($cfq) ? $cfq.\', \' : \'\').\"LastMod=now(), LastModID=\'$txp_user\'\",\n		\"ID=$id\",\n		$adi_matrix_debug\n	);\n\n	if ($new_status >= STATUS_LIVE && $old_status < STATUS_LIVE)\n		do_pings();\n\n	if ($new_status >= STATUS_LIVE || $old_status >= STATUS_LIVE)\n		update_lastmod();\n\n	return $res;\n}\n\nfunction adi_matrix_update_articles($updates,$matrix_index) {\n// update articles\n\n	$res = TRUE;\n	if ($updates) {\n		foreach ($updates as $id => $data)\n			if ($id == \'new\')\n				$res = $res && adi_matrix_publish_article($data,$matrix_index);\n			else\n				$res = $res && adi_matrix_update_article($id,$data);\n	}\n	return $res;\n}\n\nfunction adi_matrix_article_defaults($matrix_index) {\n// default values for new article, adjusted for specified matrix\n	global $adi_matrix_debug,$adi_matrix_list,$prefs,$adi_matrix_cfs,$adi_matrix_glz_cfs;\n\n//	Article field	$defaults[\'xx\']	Who determines						Default values\n//	-------------	---------------	--------------						--------------\n//	ID				-				MySQL								generated on publish\n//	Posted			posted			adi_matrix_article_defaults,user	current date/time\n//	Expires			expires			adi_matrix_article_defaults,user	blank (converted to 0000-00-00 00:00:00 by adi_matrix_validate_post_data)\n//	AuthorID		-				adi_matrix_publish_article			current user\n//	LastMod			-				adi_matrix_publish_article			generated on publish\n//	LastModID		-				adi_matrix_publish_article			current user\n//	Title			title			adi_matrix_article_defaults,user	blank\n//	Title_html		-				adi_matrix_publish_article			blank\n//	Body			-				adi_matrix_publish_article			blank\n//	Body_html		-				adi_matrix_publish_article			blank\n//	Excerpt			-				adi_matrix_publish_article			blank\n//	Excerpt_html	-				adi_matrix_publish_article			blank\n//	Image			article_image	adi_matrix_article_defaults,user	blank\n//	Category1		category1		adi_matrix_article_defaults,user	criteria_category (if specific category set), blank\n//	Category2		category2		adi_matrix_article_defaults,user	blank\n//	Annotate		-				adi_matrix_publish_article			0\n//	AnnotateInvite	-				adi_matrix_publish_article			blank\n//	comments_count	-				MySQL								default\n//	Status			status			adi_matrix_article_defaults,user	criteria_status (if set), \"live\"\n//	textile_body	-				adi_matrix_publish_article			\'use_textile\' from $prefs\n//	textile_excerpt	-				adi_matrix_publish_article			\'use_textile\' from $prefs\n//	Section			section			adi_matrix_article_defaults			first section from criteria_section	(if set), \'default_section\' from $prefs\n//	override_form	-				adi_matrix_publish_article			blank\n//	Keywords		keywords		adi_matrix_article_defaults,user	criteria_keywords (if set), blank\n//	url_title		-				adi_matrix_publish_article			generated-from-title\n//	custom_x		custom_x		adi_matrix_article_defaults,user	blank (if TXP CFs), GLZ default value (if GLZ CFs)\n//	uid				-				adi_matrix_publish_article			generated on publish\n//	feed_time		-				adi_matrix_publish_article			generated on publish\n\n	$defaults = array();\n\n	// title - blank\n	$defaults[\'title\'] = \'\';\n	// status - from criteria (or live if not set)\n	if ($adi_matrix_list[$matrix_index][\'criteria_status\'])\n		$defaults[\'status\'] = $adi_matrix_list[$matrix_index][\'criteria_status\'];\n	else\n		$defaults[\'status\'] = \'4\';\n	// article image - blank\n	$defaults[\'article_image\'] = \'\';\n	// keywords - from criteria\n	$defaults[\'keywords\'] = $adi_matrix_list[$matrix_index][\'criteria_keywords\'];\n	// category1 - if specific category set - assign it to cat1, otherwise leave it up to user (i.e. blank)\n	if (($adi_matrix_list[$matrix_index][\'criteria_category\']) && (strpos($adi_matrix_list[$matrix_index][\'criteria_category\'],\'!\') === FALSE))\n		$defaults[\'category1\'] = $adi_matrix_list[$matrix_index][\'criteria_category\'];\n	else\n		$defaults[\'category1\'] = \'\';\n	// category2 - leave blank\n	$defaults[\'category2\'] = \'\';\n	// posted\n	$defaults[\'uposted\'] = time();\n	$defaults[\'posted\'] = date(\"Y-m-d H:i:s\",$defaults[\'uposted\']);\n	// expires - blank\n	$defaults[\'uexpires\'] = \'\';\n	$defaults[\'expires\'] = \'\';\n	// section - $prefs default_section if blank, else first section on list if criteria set\n	if ($adi_matrix_list[$matrix_index][\'criteria_section\']) {\n		$sections = explode(\',\',$adi_matrix_list[$matrix_index][\'criteria_section\']);\n		$defaults[\'section\'] = $sections[0];\n	}\n	else\n		$defaults[\'section\'] = $prefs[\'default_section\'];\n	// custom fields - blank\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		$defaults[$custom_x] = \'\';\n	}\n	// glean GLZ CF defaults & apply them\n	if ($adi_matrix_glz_cfs) {\n		$all_custom_sets = glz_custom_fields_MySQL(\"all\"); // array indexed by custom_xx_set, value array(name,position,type)\n		if ($adi_matrix_debug)\n			echo \"<b>GLZ custom field defaults</b>:\".br;\n		foreach ($all_custom_sets as $custom => $custom_set) { // index format is \"custom_x_set\"\n			$arr_custom_field_values = glz_custom_fields_MySQL(\"values\",$custom,\'\',array(\'custom_set_name\' => $custom_set[\'name\']));\n			$default_value = glz_return_clean_default(glz_default_value($arr_custom_field_values));\n			$custom_x = str_replace(\'_set\',\'\',$custom); // convert custom_x_set to custom_x\n			if ($adi_matrix_debug) echo $custom_x.\' default = \'.\'[\'.$default_value.\']\'.br;\n			$defaults[$custom_x] = $default_value; // use GLZ default\n		}\n	}\n\n	if ($adi_matrix_debug) {\n		echo br.\'<b>New article defaults:</b>\';\n		dmp($defaults);\n	}\n\n	return $defaults;\n}\n\nfunction adi_matrix_publish_article($data,$matrix_index) {\n// new article - based on article_post() from txp_article.php\n// update database with posted data or article defaults\n	global $adi_matrix_debug,$adi_matrix_cfs,$txp_user,$prefs,$vars,$step,$adi_matrix_list,$adi_matrix_article_defaults;\n\n	include_once txpath.\'/include/txp_article.php\'; // to get article_validate() (TXP 4.5.0+), textile_main_fields()\n\n	$defaults = $adi_matrix_article_defaults; // life\'s still to short, so make variable name short\n\n	// translate adi_matrix stuff into article_post() stuff\n	$Title = $Title_plain = $data[\'title\'];\n	$Body = \'\';\n	$Body_html = \'\';\n	$Excerpt = \'\';\n	$Excerpt_html = \'\';\n	$textile_body = $prefs[\'use_textile\'];\n	$textile_excerpt = $prefs[\'use_textile\'];\n	$Annotate = \'0\';\n	$override_form = \'\';\n	$AnnotateInvite = \'\';\n	// article image\n	if (isset($data[\'article_image\']))\n		$Image = $data[\'article_image\'];\n	else\n		$Image = $defaults[\'article_image\'];\n	// keywords\n	if (isset($data[\'keywords\']))\n		$Keywords = trim(preg_replace(\'/( ?[\\r\\n\\t,])+ ?/s\', \',\', preg_replace(\'/ +/\', \' \', $data[\'keywords\'])), \', \');\n	else\n		$Keywords = $defaults[\'keywords\'];\n	// status\n	if (isset($data[\'status\']))\n		$Status = $data[\'status\'];\n	else\n		$Status = $defaults[\'status\'];\n	// posted\n	if (isset($data[\'posted\'][\'reset_time\']))\n		$publish_now = \'1\';\n	else\n		$publish_now = \'\';\n	if (isset($data[\'posted\'])) { // this is in TXP date/time\n		extract($data[\'posted\']);\n		$Posted = $year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second;\n	}\n	else // force now\n		$publish_now = \'1\';\n	// expires\n	if (isset($data[\'expires\'])) { // this is in TXP date/time\n		foreach ($data[\'expires\'] as $index => $value) { // convert expiry vars ($year -> $exp_year) to align with $vars in txp_article.php\n			$var = \'exp_\'.$index;\n			$$var = $value;\n		}\n		$Expires = $exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second;\n	}\n	else // force no expiry\n		$exp_year = \'0000\';\n	// section\n	if (isset($data[\'section\']))\n		$Section = $data[\'section\'];\n	else\n		$Section = $defaults[\'section\'];\n	// categories\n	$Category1 = isset($data[\'category1\']) ? $data[\'category1\'] : $defaults[\'category1\'];\n	$Category2 = isset($data[\'category2\']) ? $data[\'category2\'] : $defaults[\'category2\'];\n	// custom Fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if (isset($data[$custom_x])) // present in data posted\n				$$custom_x = $data[$custom_x];\n			else // not present in posted data, so set default\n				$$custom_x = $defaults[$custom_x];\n	}\n\n	// package them all up\n	$new = compact($vars);\n\n	if ($adi_matrix_debug) {\n		echo \'<b>New article data:</b>\';\n		dmp($new);\n	}\n\n	// all fields are strings ...\n	$incoming = array_map(\'assert_string\',$new);\n\n	// textilation (converts ampersands to &amp; in titles)\n	$incoming = textile_main_fields($incoming);\n\n	// slash attack\n	extract(doSlash($incoming));\n\n	// ... except some are more integer than string\n	extract(array_map(\'assert_int\', array(\'Status\' => $Status, \'textile_body\' => $textile_body, \'textile_excerpt\' => $textile_excerpt)));\n	$Annotate = (int) $Annotate;\n\n	// set posted timestamp (already validated by adi_matrix_validate_post_data)\n	if ($publish_now == 1) {\n		$when = \'now()\';\n		$when_ts = time();\n	}\n	else { // convert TXP date/time to DB timestamp\n		$ts = strtotime($year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second);\n		$when_ts = $ts - tz_offset($ts);\n		$when = \"from_unixtime($when_ts)\";\n	}\n\n	// and I quote: \"Force a reasonable \'last modified\' date for future articles, keep recent articles list in order\"\n	$lastmod = ($when_ts > time() ? \'now()\' : $when);\n\n	// set expiry timestamp (already validated/massaged by adi_matrix_get_post_data)\n	if ($exp_year == \'0000\')\n		$expires = 0;\n	else { // convert TXP date/time to DB timestamp\n		$ts = strtotime($exp_year.\'-\'.$exp_month.\'-\'.$exp_day.\' \'.$exp_hour.\':\'.$exp_minute.\':\'.$exp_second);\n		$expires = $ts - tz_offset($ts);\n	}\n	if ($expires)\n		$whenexpires = \"from_unixtime($expires)\";\n	else\n		$whenexpires = NULLDATETIME;\n\n	// who\'s doing the doing?\n	$user = doSlash($txp_user);\n\n	$msg = \'\';\n\n	// tweak status according to privs\n	if (!has_privs(\'article.publish\') && $Status >= STATUS_LIVE)\n		$Status = STATUS_PENDING;\n\n	// set url-title\n	if (empty($url_title))\n		$url_title = stripSpace($Title_plain, 1);\n\n	// custom fields\n	$cfq = array();\n	foreach($adi_matrix_cfs as $i => $cf_name) {\n		$custom_x = \"custom_{$i}\";\n		if (isset($$custom_x))\n			$cfq[] = \"custom_$i = \'\".$$custom_x.\"\'\";\n	}\n	$cfq = implode(\', \',$cfq);\n\n	$rs = compact($vars);\n\n	if ($adi_matrix_debug) {\n		article_validate($rs, $msg);\n		echo \'<b>article_validate():</b>\';\n		dmp($msg);\n	}\n\n	if (article_validate($rs, $msg)) {\n		$ok = safe_insert(\n		   \"textpattern\",\n		   \"Title           = \'$Title\',\n			Body            = \'$Body\',\n			Body_html       = \'$Body_html\',\n			Excerpt         = \'$Excerpt\',\n			Excerpt_html    = \'$Excerpt_html\',\n			Image           = \'$Image\',\n			Keywords        = \'$Keywords\',\n			Status          =  $Status,\n			Posted          =  $when,\n			Expires         =  $whenexpires,\n			AuthorID        = \'$user\',\n			LastMod         =  $lastmod,\n			LastModID       = \'$user\',\n			Section         = \'$Section\',\n			Category1       = \'$Category1\',\n			Category2       = \'$Category2\',\n			textile_body    =  $textile_body,\n			textile_excerpt =  $textile_excerpt,\n			Annotate        =  $Annotate,\n			override_form   = \'$override_form\',\n			url_title       = \'$url_title\',\n			AnnotateInvite  = \'$AnnotateInvite\',\"\n			.(($adi_matrix_cfs) ? $cfq.\',\' : \'\').\n			\"uid            = \'\".md5(uniqid(rand(),true)).\"\',\n			feed_time       = now()\"\n			,$adi_matrix_debug\n		);\n\n		if ($ok) {\n			if ($Status >= STATUS_LIVE) {\n				do_pings();\n				update_lastmod();\n			}\n			return TRUE;\n		}\n	}\n\n	return FALSE;\n}\n\nfunction adi_matrix_delete_article($id) {\n// delete an article - ripped off from list_multi_edit() in txp_list.php\n	global $txp_user,$adi_matrix_debug;\n\n	// keeping the multi-edit/array thing going - just in case\n	$selected = array();\n	$selected[] = $id;\n\n	// is allowed?\n	if (!has_privs(\'article.delete\')) {\n		$allowed = array();\n		if (has_privs(\'article.delete.own\'))\n			$allowed = safe_column_num(\'ID\', \'textpattern\', \'ID in(\'.implode(\',\',$selected).\') and AuthorID=\\\'\'.doSlash($txp_user).\'\\\'\',$adi_matrix_debug);\n		$selected = $allowed;\n	}\n\n	// in the bin\n	foreach ($selected as $id) {\n		if (safe_delete(\'textpattern\', \"ID = $id\",$adi_matrix_debug))\n			$ids[] = $id;\n	}\n\n	// housekeeping\n	$changed = implode(\', \', $ids);\n	if ($changed) {\n		safe_update(\'txp_discuss\', \"visible = \".MODERATE, \"parentid in($changed)\",$adi_matrix_debug);\n		return TRUE;\n	}\n\n	return FALSE;\n}\n\nfunction adi_matrix_glz_cfs_input($custom_x,$var,$val) {\n// output custom field input according to glz_custom_fields format\n	global $adi_matrix_debug;\n\n	$row = safe_row(\'html\',\'txp_prefs\',\"name = \'\".$custom_x.\"_set\'\"); // get html input type from prefs\n	$html = $row[\'html\'];\n\n	$arr_custom_field_values = glz_custom_fields_MySQL(\"values\", $custom_x.\"_set\", \'\', array(\'custom_set_name\' => $custom_x.\"_set\"));\n	$default_value = glz_return_clean_default(glz_default_value($arr_custom_field_values));\n	if (is_array($arr_custom_field_values))\n		array_walk($arr_custom_field_values, \"glz_clean_default_array_values\"); // from glz_custom_fields_replace()\n	// glz radio reset - relies on name=\"field\", which has to match for=\"field\" in main label, which picks up for=\"field_value\" in sub labels but adi_matrix don\'t have main label & needs name=\"article_xx[field_xx]\" anyway - ne\'er the twain shall neet! (would have to write our own reset jQuery I guess)\n	// glz radio - uses an ID based on field & value i.e. field_value, but adi_matrix ends up as article_2[custom_3]_value which ain\'t valid (& don\'t work in jQuery anyway)\n	// glz checkbox - uses an ID based on value only, so may get duplicate ID warnings on Write tab (& definitely on matrix tab)!!!\n	if ($html == \'radio\') { // create a clean ID prefix (i.e. without [ or ]) for radio buttons - to get rid of some error messages\n		$glz_id_prefix = str_replace(\'[\',\'_\',$var);\n		$glz_id_prefix = str_replace(\']\',\'_\',$glz_id_prefix);\n	}\n	else\n		$glz_id_prefix = \'\';\n\n	$out = glz_format_custom_set_by_type($var,$glz_id_prefix,$html,$arr_custom_field_values,$val,$default_value);\n\n	return $out; // html in $out[0], glz class in $out[1]\n}\n\nfunction adi_matrix_get_post_data($adi_matrix_articles,$matrix_index) {\n// analyse submitted article data ($_POST), massage if necessary, & create new list of articles & data ($adi_matrix_post)\n	global $adi_matrix_list,$adi_matrix_cfs,$adi_matrix_glz_cfs,$adi_matrix_debug;\n\n	if ($adi_matrix_debug) {\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n	}\n\n 	// copy $_POST[\'article_xx\'] to $adi_matrix_post[xx] (where xx is article ID or \"new\")\n	$adi_matrix_post = array();\n	foreach ($_POST as $index => $value) {\n 		$this_index = explode(\'_\',$index);\n 		if (strpos($index,\'article_\') === 0) { // pick out anything from $_POST that starts with \"article_\" ... i.e. article_xx or article_new\n 			if ($adi_matrix_glz_cfs) // tweak POSTED values to convert from array to bar|separated|list - based on glz_custom_fields_before_save()\n 				foreach ($value as $key => $val) {\n 					if (strstr($key, \'custom_\') && is_array($val)) { // check for custom fields with multiple values e.g. arrays\n 						$val = implode($val,\'|\');\n 						$value[$key] = $val;\n 					}\n 				}\n 			$adi_matrix_post[$this_index[1]] = $value;\n 		}\n 	}\n\n	// new article fiddling\n	if (isset($adi_matrix_post[\'new\'])) {\n		if (trim($adi_matrix_post[\'new\'][\'title\']) == \'\') // remove from the equation if title is blank\n			unset($adi_matrix_post[\'new\']);\n	}\n\n	// check for missing GLZ custom field values & fire blanks if necessary - required for checkboxes/multiselects that have been completely unchecked (otherwise updates not registered)\n	// will also pick up radios & multiselects (though deselected multiselect may be present in $_POST anyway)\n	if ($adi_matrix_glz_cfs) {\n		if ($adi_matrix_debug) echo \'<b>GLZ blanks generated for:</b>\'.br;\n		foreach ($adi_matrix_articles as $id => $article_data) { // check all articles (& new) on page (article may be absent from POST if checkbox is the only data field & it\'s completely unticked)\n			if ($adi_matrix_debug) echo \"Article $id - \";\n			if (!array_key_exists($id,$adi_matrix_post)) { // article missing from POST completely\n				if ($adi_matrix_debug) echo \"(article absent) \";\n				$adi_matrix_post[$id] = array();\n			}\n			foreach ($adi_matrix_cfs as $index => $title) { // check each custom field\n				if ($adi_matrix_list[$matrix_index][\'custom_\'.$index]) { // only interested in custom field if it\'s visible in this matrix\n					if (!array_key_exists(\'custom_\'.$index,$adi_matrix_post[$id])) { // custom field absent from article in POST\n						$adi_matrix_post[$id][\'custom_\'.$index] = \'\'; // fire a blank\n						if ($adi_matrix_debug) echo \"custom_$index \";\n					}\n				}\n			}\n			if ($adi_matrix_debug) echo br;\n		}\n		if ($adi_matrix_debug) echo br;\n	}\n\n	// expires - change all blanks to all zeroes (existing & \"new\"), coz stored as zeroes but displayed as blanks\n	foreach ($adi_matrix_post as $id => $this_article) { // check each article\n		if (array_key_exists(\'expires\',$this_article))\n			if (($this_article[\'expires\'][\'year\'] == \'\') && ($this_article[\'expires\'][\'month\'] == \'\') && ($this_article[\'expires\'][\'day\'] == \'\') && ($this_article[\'expires\'][\'hour\'] == \'\') && ($this_article[\'expires\'][\'minute\'] == \'\') && ($this_article[\'expires\'][\'second\'] == \'\')) {\n				$adi_matrix_post[$id][\'expires\'][\'year\'] = \'0000\';\n				$adi_matrix_post[$id][\'expires\'][\'month\'] = $adi_matrix_post[$id][\'expires\'][\'day\'] = $adi_matrix_post[$id][\'expires\'][\'hour\'] = $adi_matrix_post[$id][\'expires\'][\'minute\'] = $adi_matrix_post[$id][\'expires\'][\'second\'] = \'00\';\n			}\n	}\n\n	if ($adi_matrix_debug) {\n		echo \'<b>$adi_matrix_post:</b>\';\n		dmp($adi_matrix_post);\n	}\n\n	return $adi_matrix_post;\n}\n\nfunction adi_matrix_get_updates($adi_matrix_post,$adi_matrix_articles) {\n// compare submitted article data with database data & create $adi_matrix_updates[id][field] if changed\n	global $adi_matrix_debug;\n\n	if ($adi_matrix_debug)\n		echo \'<b>Update processing (adi_matrix_get_updates):</b>\'.br;\n\n	$adi_matrix_updates = array();\n	foreach ($adi_matrix_post as $id => $data) {\n		foreach ($data as $field => $new_value) {\n			if (($field == \'posted\') || ($field == \'expires\'))\n				$date_string = $new_value[\'year\'].\'-\'.$new_value[\'month\'].\'-\'.$new_value[\'day\'].\' \'.$new_value[\'hour\'].\':\'.$new_value[\'minute\'].\':\'.$new_value[\'second\']; // set up new date/time string\n			if ($id == \'new\') { // new article\n				$adi_matrix_updates[$id][$field] = $new_value;\n				if (($field == \'posted\') || ($field == \'expires\'))\n					$new_value = $date_string; // use new time date/time string\n				if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', new_value=\'.$new_value.\' (NEW ARTICLE)\'.br;\n			}\n			else { // existing article\n				$equal = TRUE;\n				$old_value = $adi_matrix_articles[$id][$field];\n				$test_value = $new_value;\n				if ($field == \'keywords\') // remove human friendly spaces after commas\n					$test_value = str_replace(\', \' ,\',\', $new_value);\n				if (($field == \'posted\') || ($field == \'expires\')) // date/time requires special attention (because DB time may be different to TXP human time, so direct comparison not valid)\n					if (array_key_exists(\'reset_time\',$new_value)) {\n						$equal = FALSE; // force inequality - \"NOW()\" time will be set in database update\n						if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\' (RESET)\';\n					}\n					else { // use article\'s DB unix timestamp & convert to TXP date/time for comparison\n						$ufield = \'u\'.$field; // tweak field name from posted/expires to uposted/uexpires\n						if ($adi_matrix_articles[$id][$ufield])\n							$old_value = safe_strftime(\"%Y-%m-%d %X\",$adi_matrix_articles[$id][$ufield]); // use DB unix timestamp & convert to TXP date/time\n						else\n							$old_value = \'0000-00-00 00:00:00\';\n						$test_value = $date_string; // use new time date/time string\n						if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', old_value=\'.$old_value.\', test_value=\'.$test_value;\n					}\n				else\n					if ($adi_matrix_debug) echo \'id=\'.$id.\', field=\'.$field.\', new_value=\'.$new_value.\', old_value=\'.$old_value.\', test_value=\'.$test_value;\n				$equal = $equal && (strcmp($test_value,$old_value) == 0);\n				if ($adi_matrix_debug)\n					if ($equal) echo \" (EQUAL)\".br; else echo \" <b>(NOT EQUAL)</b>\".br;\n				if (!$equal)\n					$adi_matrix_updates[$id][$field] = $new_value;\n			}\n		}\n	}\n\n	if ($adi_matrix_debug) {\n		echo br.\'<b>$adi_matrix_updates:</b>\';\n		dmp($adi_matrix_updates);\n	}\n\n	return $adi_matrix_updates;\n}\n\nfunction adi_matrix_validate_post_data($adi_matrix_articles,$post_data) {\n// article data validation\n	global $adi_matrix_debug,$adi_matrix_validation_errors;\n\n	// create array of empties indexed by $adi_matrix_validation_errors id\n	$new_error_list = array();\n	foreach ($adi_matrix_validation_errors as $i => $v)\n		$new_error_list[$i] = array();\n\n	foreach ($post_data as $id => $data) {\n\n		// add empty \"error\" slots for article\n		foreach ($adi_matrix_validation_errors as $i => $v)\n			$new_error_list[$i][$id] = array();\n\n		// remember old timestamp values (existing articles only)\n		if ($id != \'new\') {\n			$posted = $adi_matrix_articles[$id][\'posted\'];\n			$expires = $adi_matrix_articles[$id][\'expires\'];\n		}\n\n		// iterate through $data (OTT but may change in the future)\n		foreach ($data as $field => $value) {\n			// do some date/time checking\n			if (($field == \'posted\') || ($field == \'expires\')) {\n				// record new (i.e. $_POSTed) timestamp values\n				$$field = $value[\'year\'].\'-\'.$value[\'month\'].\'-\'.$value[\'day\'].\' \'.$value[\'hour\'].\':\'.$value[\'minute\'].\':\'.$value[\'second\'];\n				if ($field == \'posted\')\n					if (array_key_exists(\'reset_time\',$value))\n						$$field = date(\'Y-m-d H:i:s\',time()); // have to predict the reset date/time (Article tab does it this way too!)\n				// check it\'s a valid date/time\n				$error = (!is_numeric($value[\'year\']) || !is_numeric($value[\'month\']) || !is_numeric($value[\'day\']) || !is_numeric($value[\'hour\'])  || !is_numeric($value[\'minute\']) || !is_numeric($value[\'second\']));\n				$ts = strtotime($value[\'year\'].\'-\'.$value[\'month\'].\'-\'.$value[\'day\'].\' \'.$value[\'hour\'].\':\'.$value[\'minute\'].\':\'.$value[\'second\']);\n				$error = $error || ($ts === FALSE || $ts === -1);\n				// special case - allow all blanks in expires\n				if ($error && ($field == \'expires\'))\n					$error = !(empty($value[\'year\']) && empty($value[\'month\']) && empty($value[\'day\']) && empty($value[\'hour\']) && empty($value[\'minute\']) && empty($value[\'second\']));\n				if ($error) {\n					if ($id != \'new\')\n						$$field = $adi_matrix_articles[$id][$field]; // restore old value (so it doesn\'t influence later \"expires before posted\" checking)\n					$new_error_list[\'0\'][$id][] = $field;\n				}\n			}\n		}\n\n		// check expires is not before posted (but only if expires is set)\n		if ((strtotime($expires) < strtotime($posted)) && ($expires != \'0000-00-00 00:00:00\')) {\n			$new_error_list[\'1\'][$id][] = \'posted\';\n			$new_error_list[\'1\'][$id][] = \'expires\';\n		}\n\n		// check URL-titles (duplicates & blanks)\n		// title supplied if new or edited, get title if not supplied\n		if (isset($data[\'title\']))\n			$title = $data[\'title\'];\n		else\n			$title = $adi_matrix_articles[$id][\'title\'];\n		$msg = 0;\n		$url_title = stripSpace($title, 1);\n		if (trim($url_title) == \'\') // blank\n			$msg = 3;\n		if ($msg) {\n			$new_error_list[$msg][$id][] = \'url_title\';\n		}\n		$msg = 0;\n		// duplicates?\n		$url_title_count = safe_count(\'textpattern\',\"url_title = \'$url_title\'\");\n		if (($url_title_count > 1) || (($id == \'new\') && $url_title_count)) { // duplicates found (existing articles: multiple in DB, new article: one or more in DB)\n			$msg = 2;\n			// get ids of all other articles with matching URL-only titles (they may not be in this matrix, or in this page of this matrix)\n			$duplicates = safe_rows(\'id\',\'textpattern\',\"url_title = \'$url_title\'\"); // returns array of arrays containing \'id\' => id#\n			if ($id == \'new\')\n				$duplicates[][\'id\'] = adi_matrix_gtxt(\'adi_new_article\'); // using \"new article\" in duplicates list coz don\'t have new article list\n		}\n		if ($msg) {\n			foreach ($duplicates as $duplicate) {\n				$new_error_list[$msg][$duplicate[\'id\']][] = \'url_title\';\n			}\n		}\n\n	}\n\n	// lose the empties\n	$new_error_list = array_filter(array_map(\'array_filter\', $new_error_list));\n\n	if ($adi_matrix_debug) {\n		echo \'<b>Invalid fields:</b>\';\n		dmp($new_error_list);\n	}\n\n	return $new_error_list;\n}\n\nfunction adi_matrix_remove_errors($updates,$errors) {\n// remove fields with invalid data from article update list\n	global $adi_matrix_debug;\n\n	foreach ($errors as $article)\n		foreach ($article as $id => $fields)\n			foreach ($fields as $field)\n				unset($updates[$id][$field]);\n\n	return $updates;\n}\n\nfunction adi_matrix_debug($adi_matrix_articles,$matrix_index) {\n// plot in the title\n	global $event,$step,$adi_matrix_cfs,$adi_matrix_list,$adi_matrix_glz_cfs,$adi_matrix_categories,$txp_user;\n\n	echo \"<b>Event:</b> \".$event.\", <b>Step:</b> \".$step.br;\n	echo \"<b>Date/time:</b> \"\n		.\'date = \'.date(\"Y-m-d H:i:s\")\n		.\'; tz_offset() = \'.tz_offset()\n		.\'; date + tz_offset() = \'.date(\"Y-m-d H:i:s\",time()+tz_offset()) // this is actual (TXP adjusted) local time (incl DST) regardless of server timezone\n		.br;\n	$rs = safe_query(\'SELECT NOW()\');\n	$a = nextRow($rs);\n	if ($a)\n		echo \'<b>Article timestamp in DB &hellip; SELECT NOW()</b> = \'.current($a).br;\n	else\n		echo \'Unable to determine article DB time\'.br;\n	$rs = safe_query(\'SELECT UNIX_TIMESTAMP(NOW())\');\n	$a = nextRow($rs);\n	$article_time = current($a);\n	if ($a) {\n		echo \'<b>Article time displayed in article tab &hellip; safe_strftime(\"%Y-%m-%d %X\")</b> = \'.safe_strftime(\"%Y-%m-%d %X\",$article_time).br;\n		$posted = safe_strftime(\'%Y-%m-%d %X\');\n		$ts = strtotime($posted);\n		$when_ts = $ts - tz_offset($ts);\n		$rs = safe_query(\"SELECT FROM_UNIXTIME($when_ts)\");\n		$a = nextRow($rs);\n		if ($a)\n			echo \'<b>Article date/time written to DB</b>  = \'.current($a).br;\n		else\n			echo \'Unable to determine date/time written to DB\'.br;\n	}\n	else\n		echo \'Unable to determine article tab timestamp\'.br;\n	echo br;\n	echo \'<b>This matrix:</b>\';\n	dmp($adi_matrix_list[$matrix_index]);\n	echo \'<b>$adi_matrix_cfs:</b>\';\n	dmp($adi_matrix_cfs);\n	if ($adi_matrix_glz_cfs) {\n		echo \'<b>Custom field input types:</b><br/>\';\n		foreach ($adi_matrix_cfs as $index => $title) {\n			$row = safe_row(\'html\',\'txp_prefs\',\"name = \'custom_\".$index.\"_set\'\"); // get html input type from prefs\n			echo \'custom_\'.$index.\' - \'.$row[\'html\'].\'<br/>\';\n		}\n		echo \'<br/>\';\n	}\n	echo \'<b>$adi_matrix_categories:</b><br/>\';\n	dmp($adi_matrix_categories);\n	echo \'<b>$adi_matrix_articles:</b>\';\n	dmp($adi_matrix_articles);\n}\n\nfunction adi_matrix_table_head($matrix_index,$type) {\n// matrix <table> header stuff\n// note that clicking a column header will reset back to the first page, which is standard TXP behaviour\n// - to fix would involve a custom elink() function to pass the \'page\' variable on\n	global $event,$adi_matrix_cfs,$adi_matrix_list;\n\n	// get current sort settings\n	list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n\n	if ($type == \'header\') {\n		$tag = \'th\';\n		$wraptag = \'thead\';\n	}\n	else {\n		$tag = \'td\';\n		$wraptag = \'tfoot\';\n	}\n\n	// article id/title heading\n	$field_list = array(\'id\',\'title\');\n	foreach ($field_list as $field) {\n		$var = $field.\'_hcell\';\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$field; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		$$var = tag(elink($event,\'\',\'sort\',$field,adi_matrix_gtxt($field),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class); // column heading/toggle sort\n	}\n\n	// standard field headings\n	$field_list = array(\'status\',\'article_image\',\'keywords\',\'category1\',\'category2\',\'posted\',\'expires\',\'section\');\n	foreach ($field_list as $field) {\n		$var = $field.\'_hcell\';\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$field; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		$adi_matrix_list[$matrix_index][$field] ? $$var = tag(elink($event,\'\',\'sort\',$field,adi_matrix_gtxt($field),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class) : $$var = \'\'; // column heading/toggle sort\n	}\n\n	// custom field headings\n	$cf_hcell = \'\';\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		$class = array();\n		if ($field == $sort) // sort value matches field\n			$dir == \'desc\' ? $class[] = \'desc\' : $class[] = \'asc\'; // up/down arrow\n		else ; // no arrow - sort set in admin\n		$class[] = \'adi_matrix_field_\'.$custom_x; // add field name to class\n		$class = \' class=\"\'.implode(\' \',$class).\'\"\';\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if ($adi_matrix_list[$matrix_index][$custom_x])\n				$cf_hcell .= tag(elink($event,\'\',\'sort\',$custom_x,$cf_name,\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class);\n	}\n\n	// delete heading placeholder\n	$del_hcell = tag(sp,$tag,\' class=\"adi_matrix_delete\"\'); // spacer\n\n	// \"Show section\" heading\n	if ($sort == \'section\') // sort value matches field\n		$dir == \'desc\' ? $class = \' class=\"desc section\"\' : $class = \' class=\"asc section\"\'; // up/down arrow\n	else\n		$class = \' class= \"section\"\'; // no arrow - sort set in admin\n\n	$show_section_hcell = tag(elink($event,\'\',\'sort\',\'section\',adi_matrix_gtxt(\'section\'),\'dir\',($dir == \'asc\' ? \'desc\' : \'asc\'),\'\'),$tag,$class);\n\n	return\n		tag(\n			tr(\n				(adi_matrix_pref(\'adi_matrix_display_id\') ? $id_hcell : \'\')\n				.$title_hcell\n				.($adi_matrix_list[$matrix_index][\'show_section\'] ? $show_section_hcell : \'\') // THIS NEEDS SORTING OUT\n				.($adi_matrix_list[$matrix_index][\'section\'] ? $section_hcell : \'\') // THIS NEEDS SORTING OUT\n				.$status_hcell\n				.$cf_hcell\n				.$article_image_hcell\n				.$keywords_hcell\n				.$category1_hcell\n				.$category2_hcell\n				.$posted_hcell\n				.$expires_hcell\n				.($adi_matrix_list[$matrix_index][\'publish\'] ? $del_hcell : \'\')\n			)\n			,$wraptag\n		);\n}\n\nfunction adi_matrix_table($adi_matrix_articles,$matrix_index,$page,$errors=array(),$updates=array()) {\n// generates matrix <table> and <form> for article data updates\n// stylish classes:\n// tr (article ids):	adi_matrix_article_xx, adi_matrix_article_new\n// td (field type):		adi_matrix_timestamp, adi_matrix_category\n// td (field specific): adi_matrix_field_id, adi_matrix_field_title, adi_matrix_field_custom_x etc etc\n	global $step,$adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_statuses,$adi_matrix_list,$txp_user,$txp_user,$adi_matrix_glz_cfs,$adi_matrix_validation_errors;\n\n	$out = \'\';\n	$out .= adi_matrix_table_head($matrix_index,\'header\');\n	if ($adi_matrix_list[$matrix_index][\'footer\'])\n		$out .= adi_matrix_table_head($matrix_index,\'footer\');\n	$out .= \'<tbody>\';\n	if ($adi_matrix_articles) {\n		foreach ($adi_matrix_articles as $id => $data) {\n			// set up validation error flags for this article\n			$article_errors = array();\n			foreach ($errors as $error_type)\n				if (isset($error_type[$id]))\n					$article_errors = array_merge($article_errors,$error_type[$id]);\n			$article_errors = array_unique($article_errors);\n			if ($adi_matrix_debug && ($step == \'update\')) {\n				echo \'<b>Validation errors #\'.$id.\':</b>\';\n				dmp($article_errors);\n			}\n			// based on standard save button in txp_article.php\n			$Status = $data[\'status\'];\n			$AuthorID = $data[\'author\'];\n			$has_privs = // work out if user has a right to fiddle with article\n				(($Status >= 4 and has_privs(\'article.edit.published\'))\n				or ($Status >= 4 and $AuthorID==$txp_user and has_privs(\'article.edit.own.published\'))\n				or ($Status <  4 and has_privs(\'article.edit\'))\n				or ($Status <  4 and $AuthorID==$txp_user and has_privs(\'article.edit.own\')));\n			$prefix = \'article_\'.$id; // use array index \'article_id\' rather than \'id\' in POST data (clearer/safer?)\n			$out .= \'<tr class=\"adi_matrix_\'.$prefix.\'\">\';\n			$highlight = $data[\'highlight\'];\n			// article title link tooltip text\n			// tooltip (&#10; = newline in non-Firefox tooltip)\n			if (adi_matrix_pref(\'adi_matrix_article_tooltips\')) {\n				$title_text = \'#\'.$id.\', \'.adi_matrix_gtxt(\'posted\').\' \'.$data[\'display_posted\'];\n				if ($data[\'expires\'] != \'0000-00-00 00:00:00\')\n					$title_text .= \', \'.adi_matrix_gtxt(\'expires\').\' \'.$data[\'display_expires\'];\n				if ($highlight == 1)\n					$title_text .= \' (\'.adi_matrix_gtxt(\'expired\').\')\';\n				if ($highlight == 2)\n					$title_text .= \' (\'.adi_matrix_gtxt(\'time_future\').\')\';\n				$title_text .= \', \'.$data[\'section\'];\n				$title_text .= \', \'.$AuthorID;\n			}\n			else\n				$title_text = adi_matrix_gtxt(\'edit\');\n			$class = \'\';\n			// highlighting for expired/future articles\n			if (adi_matrix_pref(\'adi_matrix_article_highlighting\')) {\n				if ($highlight) {\n					if ($highlight == 1) $class = \' class=\"adi_matrix_expired\"\';\n					if ($highlight == 2) $class = \' class=\"adi_matrix_future\"\';\n				}\n			}\n			// ID\n			if (adi_matrix_pref(\'adi_matrix_display_id\')) {\n				$id_link = eLink(\'article\',\'edit\',\'ID\',$id,$id);\n				$out .= tag($id_link,\'td\',\' class=\"adi_matrix_field_id\"\');\n			}\n			// title\n			$article_title = trim($data[\'title\']);\n			if ($article_title == \'\') // blank title\n				$title_link = tag(eLink(\'article\',\'edit\',\'ID\',$id,adi_matrix_gtxt(\'untitled\'),\'\',\'\',$title_text),\'em\');\n			else\n				$title_link = eLink(\'article\',\'edit\',\'ID\',$id,$article_title,\'\',\'\',$title_text);\n			$title_link = \'<span title=\"\'.$title_text.\'\"\'.$class.\'>\'.$title_link.\'</span>\';\n			$arrow_link = sp.tag(eLink(\'article\',\'edit\',\'ID\',$id,sp,\'\',\'\',$title_text),\'span\',\' class=\"adi_matrix_edit_link\"\'); // was &rarr;\n			if ($adi_matrix_list[$matrix_index][\'title\'] && adi_matrix_pref(\'adi_matrix_display_id\')) // don\'t need arrow if got IDs\n				$arrow_link = \'\';\n			if ($adi_matrix_list[$matrix_index][\'title\']) {\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(finput(\"text\",$prefix.\"[title]\",$data[\'title\'],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\') ?htmlspecialchars($data[\'title\']):\'\')).$arrow_link,\' class=\"adi_matrix_field_title\"\') :\n					$out .= tda($title_link,\' class=\"adi_matrix_field_title\"\');\n			}\n			else\n				$out .= tag($title_link,\'td\',\' class=\"adi_matrix_field_title\"\');\n			// section\n			if ($adi_matrix_list[$matrix_index][\'show_section\'])\n				$out .= tda($data[\'section\']);\n			if ($adi_matrix_list[$matrix_index][\'section\']) {\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_section_popup($prefix.\"[section]\",$data[\'section\'],$adi_matrix_list[$matrix_index][\'criteria_section\']),\' class=\"adi_matrix_field_section\"\') :\n					$out .= ($data[\'section\'] ? tda($data[\'section\'],\' class=\"adi_matrix_field_section\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_section\"\'));\n			}\n			// status\n			if ($adi_matrix_list[$matrix_index][\'status\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(selectInput($prefix.\'[status]\',$adi_matrix_statuses,$data[\'status\']),\' class=\"adi_matrix_field_status\"\') :\n					$out .= tda($adi_matrix_statuses[$data[\'status\']],\' class=\"adi_matrix_field_status\"\');\n			// custom fields\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n					if ($adi_matrix_list[$matrix_index][$custom_x])\n						if ($has_privs) // decide if user gets input fields or not\n							if ($adi_matrix_glz_cfs) {\n								$glz_input_stuff = adi_matrix_glz_cfs_input($custom_x,$prefix.\"[$custom_x]\",$data[$custom_x]);\n								if ($glz_input_stuff[1] == \'glz_custom_radio_field\') // don\'t apply glz_class coz can\'t handle glz reset function properly yet - see below\n									$out .= tda($glz_input_stuff[0],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n								else\n									$out .= tda($glz_input_stuff[0],\' class=\"\'.$glz_input_stuff[1].\' adi_matrix_field_\'.$custom_x.\'\"\');\n							}\n							else\n								$out .= tda(finput(\"text\",$prefix.\"[$custom_x]\",$data[$custom_x],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($data[$custom_x]):\'\')),\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n						else\n							$out .= ($data[$custom_x] ? tda($data[$custom_x],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_\'.$custom_x.\'\"\')); // make sure the table cell stretches if no data\n			}\n			// article image\n			if ($adi_matrix_list[$matrix_index][\'article_image\']) {\n				$arrow_link = \'\';\n				// CUSTOM FIELD LINKS DISABLED\n// 				if (trim($data[\'article_image\']) && ($adi_matrix_list[$matrix_index][\'cf_links\'] == \'article_image\')) {\n// 					$image_ids = explode(\',\',$data[\'article_image\']);\n// 					$image_id = $image_ids[0];\n// 					if (safe_count(\'txp_image\',\"id=$image_id\",$adi_matrix_debug))\n// 						$arrow_link = sp.eLink(\'image\',\'image_edit\',\'id\',$image_id,\'&rarr;\',\'\',\'\',adi_matrix_gtxt(\'edit_image\').\' #\'.$image_id);\n// 					else // image not found\n// 						$arrow_link = sp.\'?\';\n// 				}\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(finput(\"text\",$prefix.\"[article_image]\",$data[\'article_image\'],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($data[\'article_image\']):\'\')).$arrow_link,\' class=\"adi_matrix_field_image\"\') :\n					$out .= ($data[\'article_image\'] ? tda($data[\'article_image\'].$arrow_link,\' class=\"adi_matrix_field_image\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_image\"\')); // make sure the table cell stretches if no data\n			}\n			// keywords\n			if ($adi_matrix_list[$matrix_index][\'keywords\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(\'<textarea name=\"\'.$prefix.\"[keywords]\".\'\" cols=\"18\" rows=\"5\" class=\"mceNoEditor\">\'.htmlspecialchars(str_replace(\',\' ,\', \', $data[\'keywords\'])).\'</textarea>\',\' class=\"adi_matrix_field_keywords\"\') :\n					$out .= ($data[\'keywords\'] ? tda($data[\'keywords\'],\' class=\"adi_matrix_field_keywords\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_field_keywords\"\'));\n			// category1\n			if ($adi_matrix_list[$matrix_index][\'category1\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_category_popup($prefix.\"[category1]\",$data[\'category1\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category1\"\') :\n					$out .= ($data[\'category1\'] ? tda($data[\'category1\'],\' class=\"adi_matrix_field_category1\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_category adi_matrix_field_category1\"\'));\n			// category2\n			if ($adi_matrix_list[$matrix_index][\'category2\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_category_popup($prefix.\"[category2]\",$data[\'category2\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category2\"\') :\n					$out .= ($data[\'category2\'] ? tda($data[\'category2\'],\' class=\"adi_matrix_field_category2\"\') : tda(\'&nbsp;\',\' class=\"adi_matrix_category adi_matrix_field_category2\"\'));\n			// posted\n			$class = \'adi_matrix_timestamp adi_matrix_field_posted\';\n			if (array_search(\'posted\',$article_errors) !== FALSE)\n				$class .= \' adi_matrix_error\';\n			if ($adi_matrix_list[$matrix_index][\'posted\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_timestamp_input($prefix.\"[posted]\",$data[\'posted\'],$data[\'uposted\'],\'posted\'),\' class=\"\'.$class.\'\"\') :\n					$out .= ($data[\'posted\'] ? tda($data[\'posted\']) : tda(\'&nbsp;\'));\n			// expires\n			$class = \'adi_matrix_timestamp adi_matrix_field_expires\';\n			if (array_search(\'expires\',$article_errors) !== FALSE)\n				$class .= \' adi_matrix_error\';\n			if ($adi_matrix_list[$matrix_index][\'expires\'])\n				$has_privs ? // decide if user gets input fields or not\n					$out .= tda(adi_matrix_timestamp_input($prefix.\"[expires]\",$data[\'expires\'],$data[\'uexpires\'],\'expires\'),\' class=\"\'.$class.\'\"\') :\n					$out .= ($data[\'expires\'] ? tda($data[\'expires\']) : tda(\'&nbsp;\'));\n			// delete button\n			if ($adi_matrix_list[$matrix_index][\'publish\']) { // got publish? - might delete!\n				 // a closer look at credentials - override delete priv OR (delete own priv and it\'s yours to delete)\n				if (has_privs(\'article.delete\') || (has_privs(\'article.delete.own\') && ($AuthorID == $txp_user))) {\n					$url = \'?event=\'.\'adi_matrix_matrix_\'.$matrix_index.a.\'step=delete\'.a.\'id=\'.$id.a.\'page=\'.$page;\n					$button =\n							\'<a href=\"\'\n							.$url\n							.\'\" class=\"dlink\" title=\"Delete?\" onclick=\"return verify(\\\'\'\n							.addslashes(htmlentities($data[\'title\']))\n							.\' - \'\n							.adi_matrix_gtxt(\'confirm_delete_popup\')\n							.\'\\\')\">&#215;</a>\';\n					$out .= tda($button,\' class=\"adi_matrix_delete\"\');\n				}\n				else\n					$out .= tda(\'&nbsp;\');\n			}\n			$out .= \'</tr>\';\n		}\n	}\n\n	if ($adi_matrix_list[$matrix_index][\'publish\'] && has_privs(\'article\'))\n		$out .= adi_matrix_new_article($matrix_index);\n\n	$out .= \'</tbody>\';\n\n	return $out;\n}\n\nfunction adi_matrix_new_article($matrix_index) {\n// data input fields for new article\n// styles as per adi_matrix_table()\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_statuses,$adi_matrix_list,$adi_matrix_glz_cfs,$adi_matrix_article_defaults;\n\n	$defaults = $adi_matrix_article_defaults; // life\'s too short to type\n\n	$prefix = \'article_new\';\n	$out = \'<tr class=\"adi_matrix_\'.$prefix.\'\">\';\n	// ID placeholder\n	if (adi_matrix_pref(\'adi_matrix_display_id\'))\n		$out .= tag(\'&#43;\',\'td\',\' class=\"adi_matrix_add adi_matrix_field_id\"\'); // plus\n	// title\n	$out .= tda(finput(\"text\",$prefix.\"[title]\",$defaults[\'title\']),\' class=\"adi_matrix_field_title\"\');\n	// section\n	if ($adi_matrix_list[$matrix_index][\'show_section\'])\n		$out .= tda($defaults[\'section\'],\' class=\"adi_matrix_field_section\"\');\n	else if ($adi_matrix_list[$matrix_index][\'section\'])\n		$out .= tda(adi_matrix_section_popup($prefix.\"[section]\",$defaults[\'section\'],$adi_matrix_list[$matrix_index][\'criteria_section\']),\' class=\"adi_matrix_field_section\"\');\n	// status\n	if ($adi_matrix_list[$matrix_index][\'status\'])\n		$out .= tda(selectInput($prefix.\'[status]\',$adi_matrix_statuses,$defaults[\'status\']),\' class=\"adi_matrix_field_status\"\');\n	// custom fields\n	foreach ($adi_matrix_cfs as $index => $cf_name) {\n		$custom_x = \'custom_\'.$index;\n		if (array_key_exists($custom_x,$adi_matrix_list[$matrix_index])) // check that custom field is known to adi_matrix\n			if ($adi_matrix_list[$matrix_index][$custom_x])\n				if ($adi_matrix_glz_cfs) {\n					$glz_input_stuff = adi_matrix_glz_cfs_input($custom_x,$prefix.\"[$custom_x]\",$defaults[$custom_x]);\n					if ($glz_input_stuff[1] == \'glz_custom_radio_field\') // don\'t apply glz_class coz can\'t handle glz reset function properly yet - see below\n						$out .= tda($glz_input_stuff[0],\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n					else\n						$out .= tda($glz_input_stuff[0],\' class=\"\'.$glz_input_stuff[1].\' adi_matrix_field_\'.$custom_x.\'\"\');\n				}\n				else\n					$out .= tda(finput(\"text\",$prefix.\"[$custom_x]\",$defaults[$custom_x],\'\',(adi_matrix_pref(\'adi_matrix_input_field_tooltips\')?htmlspecialchars($defaults[$custom_x]):\'\')),\' class=\"adi_matrix_field_\'.$custom_x.\'\"\');\n	}\n	// article image\n	if ($adi_matrix_list[$matrix_index][\'article_image\'])\n		$out .= tda(finput(\"text\",$prefix.\"[article_image]\",$defaults[\'title\']),\' class=\"adi_matrix_field_image\"\');\n	// keywords\n	if ($adi_matrix_list[$matrix_index][\'keywords\'])\n		$out .= tda(\'<textarea name=\"\'.$prefix.\"[keywords]\".\'\" cols=\"18\" rows=\"5\" class=\"mceNoEditor\">\'.htmlspecialchars(str_replace(\',\' ,\', \', $defaults[\'keywords\'])).\'</textarea>\',\' class=\"adi_matrix_field_keywords\"\');\n	// category1\n	if ($adi_matrix_list[$matrix_index][\'category1\'])\n		$out .= tda(adi_matrix_category_popup($prefix.\"[category1]\",$defaults[\'category1\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category1\"\');\n	// category2\n	if ($adi_matrix_list[$matrix_index][\'category2\'])\n		$out .= tda(adi_matrix_category_popup($prefix.\"[category2]\",$defaults[\'category2\'],FALSE),\' class=\"adi_matrix_category adi_matrix_field_category2\"\');\n	// posted\n	if ($adi_matrix_list[$matrix_index][\'posted\'])\n		$out .= tda(adi_matrix_timestamp_input($prefix.\"[posted]\",$defaults[\'posted\'],$defaults[\'uposted\'],\'posted\'),\' class=\"adi_matrix_timestamp adi_matrix_field_posted\"\');\n	// expires\n	if ($adi_matrix_list[$matrix_index][\'expires\'])\n		$out .= tda(adi_matrix_timestamp_input($prefix.\"[expires]\",$defaults[\'expires\'],$defaults[\'uexpires\'],\'expires\'),\' class=\"adi_matrix_timestamp adi_matrix_field_expires\"\');\n\n	// Delete placeholder\n	if ($adi_matrix_list[$matrix_index][\'publish\'])\n		$out .= tag(sp,\'td\',\' class=\"adi_matrix_delete\"\');\n\n	$out .= \'</tr>\';\n\n	return $out;\n}\n\nfunction adi_matrix_matrix($event,$step) {\n// a matrix tab\n	global $prefs,$adi_matrix_debug,$adi_matrix_dump,$adi_matrix_list,$adi_matrix_articles,$adi_matrix_cfs,$adi_matrix_sort_dir,$adi_matrix_sort_type,$adi_matrix_sort_options,$adi_matrix_validation_errors,$adi_matrix_article_defaults;\n\n	// extract matrix index from event (e.g. adi_matrix_matrix_0 => 0)\n	$matrix_index = str_replace(\'adi_matrix_matrix_\',\'\',$event);\n\n	// bomb out if upgrade needed\n	$upgrade_required = adi_matrix_upgrade();\n	if ($upgrade_required) {\n		pagetop($adi_matrix_list[$matrix_index][\'name\'],array(adi_matrix_gtxt(\'adi_upgrade_required\'),E_WARNING));\n		return;\n	}\n\n	// current sort settings (read from user pref, default to matrix settings)\n	list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n\n	// user sort changes\n	$new_sort = doStripTags(gps(\'sort\'));\n	$new_dir = doStripTags(gps(\'dir\'));\n	$new_sort_type = doStripTags(gps(\'sort_type\'));\n	$reset_sort = doStripTags(gps(\'reset_sort\'));\n	// sort it all out\n	if ($new_sort || $new_dir || $new_sort_type || $reset_sort) {\n		if ($new_sort && $new_dir) // column heading clicked\n			adi_matrix_pref($event.\'_sort\',$new_sort.\',\'.$new_dir.\',\'.$sort_type,TRUE); // update user pref with sort & dir\n		else if ($new_sort_type) // sort_type change\n			adi_matrix_pref($event.\'_sort\',$sort.\',\'.$dir.\',\'.$new_sort_type,TRUE); // update user pref with sort_type\n		else if ($reset_sort) { // reset sort to default\n			safe_delete(\'txp_prefs\',\"name = \'\".$event.\"_sort\'\",$adi_matrix_debug); // delete user pref\n			$prefs = get_prefs();\n		}\n		// reread user pref, defaulting to matrix settings\n		list($sort,$dir,$sort_type) = explode(\',\',get_pref($event.\'_sort\',$adi_matrix_list[$matrix_index][\'sort\'].\',\'.$adi_matrix_list[$matrix_index][\'dir\'].\',\'.$adi_matrix_list[$matrix_index][\'sort_type\']));\n	}\n\n	// initialise some bits\n	$message = \'\';\n	$updates = $errors = array();\n\n	// article selection criteria\n	$criteria = array();\n	$criteria[\'section\'] = $adi_matrix_list[$matrix_index][\'criteria_section\'];\n	$criteria[\'category\'] = $adi_matrix_list[$matrix_index][\'criteria_category\'];\n	$criteria[\'descendent_cats\'] = $adi_matrix_list[$matrix_index][\'criteria_descendent_cats\'];\n	$criteria[\'status\'] = $adi_matrix_list[$matrix_index][\'criteria_status\'];\n	$criteria[\'author\'] = $adi_matrix_list[$matrix_index][\'criteria_author\'];\n	$criteria[\'keywords\'] = $adi_matrix_list[$matrix_index][\'criteria_keywords\'];\n	$criteria[\'timestamp\'] = $adi_matrix_list[$matrix_index][\'criteria_timestamp\'];\n	$criteria[\'expiry\'] = $adi_matrix_list[$matrix_index][\'criteria_expiry\'];\n	$criteria[\'condition\'] = $adi_matrix_list[$matrix_index][\'criteria_condition\'];\n\n	// article selection WHERE clause\n	$where = adi_matrix_where($criteria);\n\n	// article sort query\n	$sortq = adi_matrix_order_by($sort,$dir,$sort_type);\n\n	// paging Mr. Matrix\n	if ($step == $event.\'_change_pageby\') { // change of page length\n		$qty = gps(\'qty\');\n		adi_matrix_pref($event.\'_pageby\',$qty,TRUE);\n	}\n	$page = gps(\'page\'); // get page number\n	$pageby = get_pref($event.\'_pageby\',15); // get current page size (default to 15, if not saved as pref)\n	$total = safe_count(\'textpattern\',\"$where\");\n	list($page,$offset,$num_pages) = pager($total,$pageby,$page);\n	if ($adi_matrix_debug)\n		echo \"<b>Paging:</b> pageby=$pageby, total=$total, page=$page, offset=$offset, num_pages=$num_pages\".br;\n\n	// get a page of articles\n	$adi_matrix_articles = adi_matrix_get_articles($where,$sortq,$matrix_index,$offset,$pageby);\n\n	// article defaults\n	$adi_matrix_article_defaults = adi_matrix_article_defaults($matrix_index);\n\n	// $step aerobics\n	if ($step == \'update\') {\n		$post_data = adi_matrix_get_post_data($adi_matrix_articles,$matrix_index);\n		$errors = adi_matrix_validate_post_data($adi_matrix_articles,$post_data);\n		$updates = adi_matrix_get_updates(adi_matrix_remove_errors($post_data,$errors),$adi_matrix_articles);\n		if ($updates) {\n			$ok = adi_matrix_update_articles($updates,$matrix_index);\n			$ok ? $message = adi_matrix_gtxt(\'adi_articles_saved\') : $message = array(adi_matrix_gtxt(\'adi_article_update_fail\'),E_WARNING);\n		}\n		else\n			$message = adi_matrix_gtxt(\'adi_articles_not_modified\');\n		if ($errors) {\n			$message .= \'. \'.adi_matrix_gtxt(\'adi_matrix_validation_error\',\'\',\':\');\n			foreach ($errors as $i => $v)\n				$message .= \' \'.$adi_matrix_validation_errors[$i].\' (\'.implode(\',\',array_keys($v)).\')\';\n			$message = array($message,E_WARNING);\n		}\n	}\n	else if ($step == \'delete\') {\n		$id = gps(\'id\');\n		if (isset($adi_matrix_articles[$id])) {\n			$ok = adi_matrix_delete_article($id);\n			$message = adi_matrix_gtxt(\'article_deleted\').\' (\'.$id.\')\';\n		}\n		else {\n			$message = array(adi_matrix_gtxt(\'adi_article_delete_fail\'),E_ERROR);\n		}\n	}\n\n	// regenerate articles list & count array again?\n	if (($step == \'update\') || ($step == \'delete\')) {\n		$adi_matrix_articles = adi_matrix_get_articles($where,$sortq,$matrix_index,$offset,$pageby);\n		$total = safe_count(\'textpattern\',\"$where\");\n	}\n\n	if ($adi_matrix_debug) {\n		$matrix_page_id_list = implode(\',\',array_keys($adi_matrix_articles)); // list if all article ids on this page\n		echo \"<b>Article ID list (on this page):</b> $matrix_page_id_list\".br;\n		$col = safe_column(\'ID\',\'textpattern\',$where.$sortq);\n		$matrix_id_list = implode(\',\',array_keys($col));\n		echo \"<b>Article ID list (in matrix):</b> $matrix_id_list\".br.br;\n	}\n\n	if ($adi_matrix_dump) {\n		// dump article data (all articles in matrix)\n		$rows = safe_rows(\'*\',\'textpattern\',\"ID IN (\".implode(\',\',array_keys($adi_matrix_articles)).\")\");\n		echo \'START ARTICLE MATRIX DUMP\'.br;\n		foreach ($rows as $i => $a) {\n			foreach ($a as $f => $d)\n				echo htmlentities(\"$f=$d,\");\n			echo br;\n		}\n		echo \'END ARTICLE MATRIX DUMP\'.br;\n	}\n\n	// generate page\n	pagetop($adi_matrix_list[$matrix_index][\'name\'],$message);\n\n	// output matrix table & input form\n	$table = adi_matrix_table($adi_matrix_articles,$matrix_index,$page,$errors,$updates);\n	$tags = array(\'<input\', \'<textarea\', \'<select\'); // tags which indicate that a save button is deserved\n	$save_button = FALSE;\n	foreach ($tags as $tag)\n		$save_button = $save_button || strpos($table,$tag);\n	$class = \'adi_matrix_matrix\';\n	if ($adi_matrix_list[$matrix_index][\'scroll\'])\n		$class .= \' adi_matrix_scroll\';\n	$class .= \' txp-list\';\n	echo form(\n		tag($adi_matrix_list[$matrix_index][\'name\'],\'h1\')\n		.\'<div class=\"scroll_box\">\'\n		.startTable(\'list\',\'\',$class)\n		.$table\n		.endTable()\n		.\'</div>\'\n		.(empty($adi_matrix_articles) ?\n			graf(tag(adi_matrix_gtxt(\'no_articles_recorded\'),\'em\'),\' class=\"adi_matrix_none\"\')\n			: \'\'\n		)\n		.($save_button ?\n			tag(\n				hInput(\'page\',$page) // pass on paging\n				.fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'save\'), \"publish\")\n				.eInput(\"adi_matrix_matrix_\".$matrix_index).sInput(\"update\"),\n				\'div\',\n				\' class=\"adi_matrix_button\"\'\n			)\n			: \'\'\n		)\n		.tag(\n			graf(\n				adi_matrix_gtxt(\'adi_default_sort\',\'\',\':\')\n				.sp\n				.elink(\n					$event\n					,\'\'\n					,\'reset_sort\'\n					,1\n					,$adi_matrix_sort_options[$adi_matrix_list[$matrix_index][\'sort\']]\n						.\', \'\n						.$adi_matrix_sort_dir[$adi_matrix_list[$matrix_index][\'dir\']]\n						.\', \'\n						.$adi_matrix_sort_type[$adi_matrix_list[$matrix_index][\'sort_type\']]\n					,\'\',\'\',\'\' // to override TXP 4.5 default title \"Edit\"\n				)\n				.br\n				.adi_matrix_gtxt(\'adi_sort_type\',\'\',\':\')\n				.sp\n				.adi_matrix_gtxt(\'adi_\'.$sort_type)\n				.\' [\'\n				.elink(\n					$event\n					,\'\'\n					,\'sort_type\'\n					,($sort_type == \'numerical\' ? \'alphabetical\' : \'numerical\')\n					,adi_matrix_gtxt(($sort_type == \'numerical\' ? \'adi_alphabetical\' : \'adi_numerical\'))\n					,\'\',\'\',\'\' // to override TXP 4.5 default title \"Edit\"\n				)\n				.\']\'\n			)\n			,\'div\',\' class=\"adi_matrix_matrix_prefs\"\')\n		,\'\'\n		,\'\'\n		,\'post\'\n		,$class\n	);\n\n	// flashing message\n	if ($errors) {\n		echo <<<END_SCRIPT\n			<script type=\"text/javascript\">\n			<!--\n			$(document).ready( function(){\n						$(\'#messagepane\').fadeOut(800).fadeIn(800);\n						$(\'#messagepane\').fadeOut(800).fadeIn(800);\n					} )\n			// -->\n			</script>\nEND_SCRIPT;\n	}\n\n	// grand total\n	echo graf(adi_matrix_gtxt(\'adi_matrix_total_articles\').sp.$total,\' class=\"adi_matrix_grand_total\"\');\n\n	// paging\n	echo tag(\n			// prev/next page buttons (if muliple pages)\n			($total > $pageby ? nav_form($event,$page,$num_pages,$sort,$dir,\'\',\'\',$total,$pageby,\'\') : \'\') // set empty step (to avoid confusion), coz default is \"list\"\n			// page length select\n			.pageby_form($event,$pageby)\n			,\'div\'\n			,\' id=\"list_navigation\" class=\"txp-navigation\"\'\n		);\n\n	if ($adi_matrix_debug)\n		adi_matrix_debug($adi_matrix_articles,$matrix_index);\n\n}\n\nfunction adi_matrix_installed($table=\'adi_matrix\') {\n// test if database table is present\n\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_matrix_install() {\n// install adi_matrix table in database\n	global $adi_matrix_debug,$adi_matrix_cfs;\n\n	$cfq = \'\';\n	foreach ($adi_matrix_cfs as $index => $value) {\n		$cfq .= \', `custom_\'.$index.\'` TINYINT(1) DEFAULT 0 NOT NULL\';\n	}\n\n	$res = safe_query(\n			\"CREATE TABLE IF NOT EXISTS \"\n			.safe_pfx(\'adi_matrix\')\n			.\" (\n			`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n			`name` VARCHAR(255) NOT NULL,\n			`sort` VARCHAR(32) NOT NULL DEFAULT \'posted\',\n			`sort_type` VARCHAR(32) NOT NULL DEFAULT \'alphabetical\',\n			`dir` VARCHAR(32) NOT NULL DEFAULT \'desc\',\n			`user` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`privs` VARCHAR(16) NOT NULL DEFAULT \'\',\n			`scroll` TINYINT(1) DEFAULT 0 NOT NULL,\n			`footer` TINYINT(1) DEFAULT 0 NOT NULL,\n			`title` TINYINT(1) DEFAULT 0 NOT NULL,\n			`publish` TINYINT(1) DEFAULT 0 NOT NULL,\n			`show_section` TINYINT(1) DEFAULT 0 NOT NULL,\n			`cf_links` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`tab` VARCHAR(16) NOT NULL DEFAULT \'content\',\n			`criteria_section` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`criteria_category` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`criteria_status` INT(2) NOT NULL DEFAULT \'4\',\n			`criteria_author` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`criteria_keywords` VARCHAR(255) NOT NULL DEFAULT \'\',\n			`criteria_timestamp` VARCHAR(16) NOT NULL DEFAULT \'any\',\n			`criteria_expiry` INT(2) NOT NULL DEFAULT \'0\',\n			`criteria_descendent_cats` TINYINT(1) DEFAULT 0 NOT NULL,\n			`criteria_condition` VARCHAR(255) NOT NULL DEFAULT \'\',\n			`status` TINYINT(1) DEFAULT 0 NOT NULL,\n			`keywords` TINYINT(1) DEFAULT 0 NOT NULL,\n			`article_image` TINYINT(1) DEFAULT 0 NOT NULL,\n			`category1` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`category2` VARCHAR(128) NOT NULL DEFAULT \'\',\n			`posted` TINYINT(1) DEFAULT 0 NOT NULL,\n			`expires` TINYINT(1) DEFAULT 0 NOT NULL,\n			`section` TINYINT(1) DEFAULT 0 NOT NULL\n			\"\n			.$cfq\n			.\");\"\n			,$adi_matrix_debug\n		);\n\n 	return $res;\n}\n\nfunction adi_matrix_uninstall() {\n// uninstall adi_matrix\n	global $adi_matrix_debug;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_matrix\').\";\",$adi_matrix_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_matrix_%\'\",$adi_matrix_debug);\n\n	return $res;\n}\n\nfunction adi_matrix_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_matrix\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5:	upgrade/reinstall only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_matrix_debug;\n\n	$result = \'?\';\n	// set upgrade flag if upgrading/reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_matrix_installed());\n	if ($step == \'enabled\') {\n		$result = $upgrade = adi_matrix_install();\n	}\n	else if ($step == \'deleted\')\n		$result = adi_matrix_uninstall();\n	if ($upgrade)\n		$result = $result && adi_matrix_upgrade(TRUE);\n	if ($adi_matrix_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_matrix_upgrade($do_upgrade=FALSE) {\n// check/perform upgrade\n	global $adi_matrix_debug;\n\n	$upgrade_required = FALSE;\n	// version 0.2\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'article_image\'\",$adi_matrix_debug); // find out if column exists\n	$a = nextRow($rs);\n	$v0_2 = empty($a);\n	$upgrade_required = $upgrade_required || $v0_2;\n	// version 0.3\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'criteria_timestamp\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v0_3t = empty($a);\n	$upgrade_required = $upgrade_required || $v0_3t;\n	// version 0.3\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'criteria_expiry\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v0_3e = empty($a);\n	$upgrade_required = $upgrade_required || $v0_3e;\n	// version 1.0\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'scroll\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v1_0 = empty($a);\n	$upgrade_required = $upgrade_required || $v1_0;\n	// version 1.1\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'footer\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v1_1 = empty($a);\n	$upgrade_required = $upgrade_required || $v1_1;\n	// version 2.0\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'title\'\",$adi_matrix_debug);\n	$a = nextRow($rs);\n	$v2_0 = empty($a);\n	$upgrade_required = $upgrade_required || $v2_0;\n\n	if ($do_upgrade && $upgrade_required) {\n		$res = TRUE;\n		if ($v0_2)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `article_image` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n		if ($v0_3t)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_timestamp` VARCHAR(16) NOT NULL DEFAULT \'any\'\",$adi_matrix_debug);\n		if ($v0_3e)\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_expiry` INT(2) NOT NULL DEFAULT \'0\'\",$adi_matrix_debug);\n		if ($v1_0) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `scroll` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `category1` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `category2` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `dir` VARCHAR(32) NOT NULL DEFAULT \'1\'\",$adi_matrix_debug);\n			// convert old `sort` to new `sort` & `dir`\n			//	OLD sort=1 (Posted desc) 	->  NEW sort=1, dir=1\n			//	OLD sort=2 (Posted asc) 	->  NEW sort=1, dir=2\n			//	OLD sort=3 (Title)			->  NEW sort=2, dir=\'\'\n			//	OLD sort=4 (LastMod desc)	->  NEW sort=3, dir=1\n			//	OLD sort=5 (LastMod asc)	->  NEW sort=3, dir=2\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'1\'\",\"`sort` IN (\'1\',\'4\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'2\'\",\"`sort` IN (\'2\',\'5\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'1\'\",\"`sort` = \'2\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'2\'\",\"`sort` = \'3\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'3\'\",\"`sort` IN (\'4\',\'5\')\",$adi_matrix_debug);\n		}\n		if ($v1_1) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `footer` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `sort_type` VARCHAR(32) NOT NULL DEFAULT \'alphabetical\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `posted` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `expires` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_descendent_cats` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			// one day sort will be sorted\n			//	OLD sort=1, NEW sort=\'posted\'\n			//	OLD sort=2, NEW sort=\'title\',\n			//	OLD sort=3, NEW sort=\'lastmod\',\n			//	OLD sort=4, NEW sort=\'expires\',\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'posted\'\",\"`sort` = \'1\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'title\'\",\"`sort` = \'2\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'lastmod\'\",\"`sort` = \'3\'\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort` = \'expires\'\",\"`sort` = \'4\'\",$adi_matrix_debug);\n			// sort_type \"+ 0 asc/desc\" now \"numerical\"\n			$res = $res && safe_update(\'adi_matrix\',\"`sort_type` = \'alphabetical\'\",\"`dir` IN (\'1\',\'2\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`sort_type` = \'numerical\'\",\"`dir` IN (\'3\',\'4\')\",$adi_matrix_debug);\n			// sort direction\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'desc\'\",\"`dir` IN (\'1\',\'4\')\",$adi_matrix_debug);\n			$res = $res && safe_update(\'adi_matrix\',\"`dir` = \'asc\'\",\"`dir` IN (\'2\',\'3\')\",$adi_matrix_debug);\n		}\n		if ($v2_0) {\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `title` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `publish` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `show_section` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `cf_links` VARCHAR(128) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `tab` VARCHAR(16) NOT NULL DEFAULT \'content\'\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `section` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n			$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `criteria_condition` VARCHAR(255) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n			$res = $res && safe_delete(\'txp_prefs\',\"name=\'adi_matrix_article_limit\'\",$adi_matrix_debug); // made redundant by paging\n		}\n		return $res;\n	}\n	else // report back only\n		return $upgrade_required;\n}\n\nfunction adi_matrix_downgrade() {\n// downgrade to previous version - 2.0 to 1.1/1.2 only\n	global $adi_matrix_debug;\n\n	$res = TRUE;\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `title`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `publish`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `show_section`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `cf_links`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `tab`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `section`\",$adi_matrix_debug);\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" DROP `criteria_condition`\",$adi_matrix_debug);\n\n	return $res;\n}\n\nfunction adi_matrix_cat_tree($list,$parent=\'root\') {\n// generate a tree of parent/child relationships\n\n	$return = array();\n	foreach ($list as $cat)\n		if ($cat[\'parent\'] == $parent)\n			$return[$cat[\'name\']] = adi_matrix_cat_tree($list,$cat[\'name\']);\n\n	return $return;\n}\n\nfunction adi_matrix_cat_descendents($tree,$parent=NULL,$found=FALSE) {\n// find all descendents of a given parent\n\n	$return = array();\n	foreach ($tree as $name => $children) {\n		if ($found)\n			$return[] = $name;\n		$return = array_merge($return,adi_matrix_cat_descendents($children,$parent,(($name == $parent) OR ($found))));\n	}\n	return $return;\n}\n\nfunction adi_matrix_categories($getTree_array) {\n// create adi_matrix_categories array, indexed by cat name containing pertinent information\n	global $adi_matrix_debug;\n\n	// $getTree_array is array of arrays:\n	//    \'id\' => \'xx\',\n	//    \'name\' =>	\'category-name\',\n	//    \'title\' => \'Category Title\',\n	//    \'level\' => 0,  (in category hierarchy)\n	//    \'children\' => 2,	(no. of children)\n	//    \'parent\' => \'root\',	(name of parent)\n\n	$cat_tree = adi_matrix_cat_tree($getTree_array);\n\n	$categories = array();\n	foreach ($getTree_array as $this_cat) {\n		$categories[$this_cat[\'name\']][\'parent\'] = $this_cat[\'parent\'];\n		$categories[$this_cat[\'name\']][\'children\'] = adi_matrix_cat_descendents($cat_tree,$this_cat[\'name\']);\n	}\n\n	return $categories;\n}\n\nfunction adi_matrix_section_popup($select_name,$value,$list=\'\') {\n// generate section popup list for admin settings table\n// where \'TRUE\' not supported on MySQL 4.0.27 (OK in MySQL 5+), so use 1=1\n\n	$blank_allowed = TRUE;\n	$where = \"name != \'default\'\";\n\n	// set up where condition if section list supplied\n	if ($list) {\n		$a = explode(\',\',$list);\n		foreach ($a as $i => $v)\n			$a[$i] = \"\'$v\'\";\n		$where .= \'AND name in (\'.implode(\',\',$a).\')\';\n		$blank_allowed = FALSE;\n	}\n\n	$rs = safe_column(\'name\', \'txp_section\', $where);\n	if ($rs)\n		return selectInput($select_name, $rs, $value, $blank_allowed);\n\n	return false;\n}\n\nfunction adi_matrix_section_checkboxes($field_name,$value) {\n// generate section checkboxes\n\n	$section_list = explode(\',\',$value);\n	$out = \'\';\n	$rs = safe_column(\'name\', \'txp_section\', \"name != \'default\'\");\n	if ($rs) {\n		foreach ($rs as $section)\n			$out .= tag(checkbox($field_name.\'[]\',$section,(array_search($section,$section_list) !== FALSE ? \'1\' : \'0\')).$section,\'label\');\n		return $out;\n	}\n	return FALSE;\n}\n\nfunction adi_matrix_category_popup($select_name,$value,$admin=TRUE) {\n// generate category popup list for admin settings table\n\n	$rs = getTree(\'root\',\'article\');\n	if ($rs) {\n		if ($admin) { /* create wildcards (wildcats?) */\n			$wildcard_list = array(\'no_category\',\'any_category\',\'one_category\',\'two_categories\',\'any_parent_category\',\'any_child_category\');\n			foreach (array_reverse($wildcard_list) as $wildcard)\n				/* add to front of array & renumber */\n				array_unshift($rs,array(\'id\' => \'0\', \'name\' => \'!\'.$wildcard.\'!\', \'title\' => adi_matrix_gtxt(\'adi_\'.$wildcard), \'level\' => \'0\', \'children\' => \'0\', \'parent\' => \'root\'));\n		}\n		return treeSelectInput($select_name,$rs,$value,\'\',35);\n	}\n	return tag(adi_matrix_gtxt(\'no_categories_exist\'),\'em\');\n}\n\nfunction adi_matrix_status_popup($select_name,$value) {\n// generate status popup list for admin settings table\n	global $adi_matrix_statuses;\n\n	return selectInput($select_name, $adi_matrix_statuses, $value, TRUE);\n}\n\nfunction adi_matrix_timestamp_popup($select_name,$value) {\n// generate timestamp popup list for admin settings table\n	global $adi_matrix_timestamp_options;\n\n	return selectInput($select_name, $adi_matrix_timestamp_options, $value, FALSE);\n}\n\nfunction adi_matrix_expiry_popup($select_name,$value) {\n// generate timestamp popup list for admin settings table\n	global $adi_matrix_expiry_options;\n\n	return selectInput($select_name, $adi_matrix_expiry_options, $value, FALSE);\n}\n\nfunction adi_matrix_user_popup($select_name,$value,$wildcard=FALSE) {\n// generate user/author popup list for admin settings table\n	global $adi_matrix_statuses;\n\n	$rs = safe_column(\'name\', \'txp_users\', \'1=1\');\n	if ($rs) {\n		if ($wildcard) { /* create wildcard */\n			$logged_in_user = array(\'!logged_in_user!\' => adi_matrix_gtxt(\'adi_logged_in_user\'));\n			$rs = $logged_in_user + $rs; /* add to front of array */\n		}\n		return selectInput($select_name, $rs, $value, TRUE);\n	}\n	return false;\n}\n\nfunction adi_matrix_privs_popup($select_name,$value) {\n// generate privs popup list for admin settings table\n	global $adi_matrix_groups;\n\n	return selectInput($select_name, $adi_matrix_groups, $value, TRUE);\n}\n\nfunction adi_matrix_tab_popup($select_name,$value) {\n// generate tab popup list for admin settings table\n	global $adi_matrix_tabs;\n\n	return selectInput($select_name, $adi_matrix_tabs, $value);\n}\n\nfunction adi_matrix_timestamp_input($name,$datetime,$ts,$type=\'posted\') {\n// date/time input fields - code stolen from include/txp_article.php\n\n	if ($datetime == \'0000-00-00 00:00:00\')\n		$ts = 0;\n\n	$class = \' \'.$type;\n	if ($type == \'posted\')\n		$class .= \' created\';\n\n	$out =\n		tag(\n			adi_matrix_tsi($name.\'[year]\',\'%Y\',$ts,\'\',$type)\n			.\' /\'\n			.adi_matrix_tsi($name.\'[month]\',\'%m\',$ts,\'\',$type)\n			.\' /\'\n			.adi_matrix_tsi($name.\'[day]\',\'%d\',$ts,\'\',$type)\n			,\'div\'\n			,\' class=\"date\'.$class.\'\"\'\n		)\n		.tag(\n			adi_matrix_tsi($name.\'[hour]\',\'%H\',$ts,\'\',$type)\n			.\' :\'\n			.adi_matrix_tsi($name.\'[minute]\',\'%M\',$ts,\'\',$type)\n			.\' :\'\n			.adi_matrix_tsi($name.\'[second]\',\'%S\',$ts,\'\',$type)\n			.($type == \'posted\' ? br.tag(adi_matrix_gtxt(\'adi_reset\'),\'label\',\' class=\"reset_time-now\"\').sp.checkbox($name.\'[reset_time]\',\'1\',\'0\') : \'\')\n			,\'div\'\n			,\' class=\"time\'.$class.\'\"\'\n		);\n\n	return $out;\n}\n\nfunction adi_matrix_tsi($name,$datevar,$time,$tab=\'\',$type=\'\') {\n// date/time item input - adapted from txplib_forms.php tsi()\n\n	preg_match_all(\'/.*\\[(.*)\\]$/\',$name,$matches); // to get \'year\' etc from article_x[posted][year]\n	$short_name = $matches[1][0];\n	if ($type == \'expires\')\n		$short_name = \'exp_\'.$short_name;\n	$size = ($short_name == \'year\' or $short_name == \'exp_year\') ? 4 : 2;\n	$s = ($time == 0) ? \'\' : safe_strftime($datevar, $time); // convert DB time to TXP date/time\n	return\n		n\n		.\'<input type=\"text\" name=\"\'.$name\n		.\'\" value=\"\'\n		.$s\n		.\'\" size=\"\'\n		.$size\n		.\'\" maxlength=\"\'\n		.$size\n		.\'\" class=\"edit \'\n		.$short_name\n		.\'\"\'\n		.(empty($tab) ? \'\' : \' tabindex=\"\'.$tab.\'\"\')\n		.\' title=\"\'\n		.adi_matrix_gtxt(\'article_\'.$short_name)\n		.\'\" />\';\n}\n\nfunction adi_matrix_delete_button($matrix_list,$matrix_index) {\n// matrix delete button [X]\n\n	$event = \'adi_matrix_admin\';\n	$step = \'delete\';\n	$url = \'?event=\'.$event.a.\'step=\'.$step.a.\'matrix=\'.$matrix_index;\n	if ($matrix_index == \'new\') // don\'t want delete button\n		return \'&nbsp;\';\n	else\n		return\n			\'<a href=\"\'\n			.$url\n			.\'\" class=\"dlink\" title=\"\'.adi_matrix_gtxt(\'delete\').\'?\" onclick=\"return verify(\\\'\'\n			.$matrix_list[$matrix_index][\'name\']\n			.\' &mdash; \'\n			.adi_matrix_gtxt(\'confirm_delete_popup\')\n			.\'\\\')\">&#215;</a>\'; // times\n}\n\nfunction adi_matrix_delete($matrix_index) {\n// stick a matrix in the bin\n	global $adi_matrix_debug;\n\n	$res = safe_delete(\'adi_matrix\', \"`id`=$matrix_index\", $adi_matrix_debug);\n	return $res;\n}\n\nfunction adi_matrix_update_settings() {\n// analyse $_POST & update settings\n	global $adi_matrix_debug,$adi_matrix_cfs;\n\n	$res = FALSE;\n	foreach ($_POST as $index => $value) {\n		$data = doArray($value,\'doStripTags\'); // strip out monkey business\n		$this_index = explode(\'_\',$index);\n		if ($this_index[0] == \'matrix\') {\n			$matrix_index = $this_index[1];\n			// adjustments\n			if ($data[\'publish\']) // if publish, then get title edit for free\n				$data[\'title\'] = \'1\';\n			if (isset($data[\'section\'])) // if section selected as data, then switch off show_section\n				$data[\'show_section\'] = \'0\';\n			// sort\n			if ($data[\'sort\'] == \'\')\n				$sortq = \"sort=\'desc\', \";\n			else\n				$sortq = \"sort=\'\".doSlash($data[\'sort\']).\"\', \";\n			// section\n			if (array_key_exists(\'criteria_section\',$data))\n				$criteria_sectionq = \"criteria_section=\'\".implode(\',\',$data[\'criteria_section\']).\"\', \";\n			else\n				$criteria_sectionq = \"criteria_section=\'\', \";\n			// status\n			if (array_key_exists(\'status\',$data))\n				$statusq = \'status=1, \';\n			else\n				$statusq = \'status=0, \';\n			// keywords\n			if (array_key_exists(\'keywords\',$data))\n				$keywordsq = \'keywords=1, \';\n			else\n				$keywordsq = \'keywords=0, \';\n			// article image\n			if (array_key_exists(\'article_image\',$data))\n				$article_imageq = \'article_image=1, \';\n			else\n				$article_imageq = \'article_image=0, \';\n			// category\n			if (array_key_exists(\'category1\',$data))\n				$categoryq = \'category1=1, \';\n			else\n				$categoryq = \'category1=0, \';\n			if (array_key_exists(\'category2\',$data))\n				$categoryq .= \'category2=1, \';\n			else\n				$categoryq .= \'category2=0, \';\n			// posted\n			if (array_key_exists(\'posted\',$data))\n				$postedq = \'posted=1, \';\n			else\n				$postedq = \'posted=0, \';\n			// expires\n			if (array_key_exists(\'expires\',$data))\n				$expiresq = \'expires=1, \';\n			else\n				$expiresq = \'expires=0, \';\n			// title\n			if (array_key_exists(\'title\',$data))\n				$titleq = \'title=1, \';\n			else\n				$titleq = \'title=0, \';\n			// section\n			if (array_key_exists(\'section\',$data))\n				$sectionq = \'section=1, \';\n			else\n				$sectionq = \'section=0, \';\n			// custom field\n			$cfq = \'\';\n			foreach ($adi_matrix_cfs as $index => $cf_name) {\n				$custom_x = \'custom_\'.$index;\n				if (array_key_exists($custom_x,$data))\n					$cfq .= \"custom_\".$index.\"=\'1\', \";\n				else\n					$cfq .= \"custom_\".$index.\"=\'0\', \";\n			}\n			// category\n			if (!array_key_exists(\'criteria_category\',$data)) // in case there\'re no categories defined\n				$data[\'criteria_category\'] = \'\';\n			if (preg_match(\'/!.*!/\',$data[\'criteria_category\'])) // disable descendent cats option with wildcards\n				unset($data[\'criteria_descendent_cats\']);\n			if (array_key_exists(\'criteria_descendent_cats\',$data))\n				$criteria_descendent_catsq = \'criteria_descendent_cats=1, \';\n			else\n				$criteria_descendent_catsq = \'criteria_descendent_cats=0, \';\n\n			$matrix_sql_set =\n				\"name=\'\".doSlash($data[\'name\']).\"\', \"\n				.$sortq\n				.\"dir=\'\".doSlash($data[\'dir\']).\"\', \"\n				.\"sort_type=\'\".doSlash($data[\'sort_type\']).\"\', \"\n				.\"user=\'\".doSlash($data[\'user\']).\"\', \"\n				.\"privs=\'\".doSlash($data[\'privs\']).\"\', \"\n				.\"scroll=\'\".doSlash($data[\'scroll\']).\"\', \"\n				.\"footer=\'\".doSlash($data[\'footer\']).\"\', \"\n				.\"publish=\'\".doSlash($data[\'publish\']).\"\', \"\n				.\"show_section=\'\".doSlash($data[\'show_section\']).\"\', \"\n				.\"cf_links=\'\".doSlash($data[\'cf_links\']).\"\', \"\n				.\"tab=\'\".doSlash($data[\'tab\']).\"\', \"\n				.$criteria_sectionq\n				.\"criteria_status=\'\".doSlash($data[\'criteria_status\']).\"\', \"\n				.\"criteria_author=\'\".doSlash($data[\'criteria_author\']).\"\', \"\n				.\"criteria_keywords=\'\".doSlash($data[\'criteria_keywords\']).\"\', \"\n				.\"criteria_timestamp=\'\".doSlash($data[\'criteria_timestamp\']).\"\', \"\n				.\"criteria_expiry=\'\".doSlash($data[\'criteria_expiry\']).\"\', \"\n				.\"criteria_condition=\'\".doSlash($data[\'criteria_condition\']).\"\', \"\n				.$titleq\n				.$statusq\n				.$sectionq\n				.$keywordsq\n				.$article_imageq\n				.$postedq\n				.$expiresq\n				.$cfq\n				.$categoryq\n				.$criteria_descendent_catsq\n				.\"criteria_category=\'\".doSlash($data[\'criteria_category\']).\"\' \";\n\n			if ($matrix_index == \'new\') {  // add new matrix to the mix\n				if (!empty($data[\'name\'])) { // but don\'t add a blank one\n					$res = safe_insert(\n						\'adi_matrix\',\n						$matrix_sql_set\n						,$adi_matrix_debug\n					);\n				}\n			}\n			else { // update existing matrix\n				$res = safe_upsert(\n					\'adi_matrix\',\n					$matrix_sql_set\n					,\"id=\'$matrix_index\'\"\n					,$adi_matrix_debug\n				);\n			}\n		}\n	}\n	return $res;\n}\n\nfunction adi_matrix_admin_table_head($adi_matrix_cfs) {\n// <table> header stuff\n\n	// custom field headings\n	$data_span = 2; // [status/keywords] plus [custom fields]\n	return tr(\n		hcell(adi_matrix_gtxt(\'adi_matrix\'))\n		.hcell() // spacer for View link\n		.hcell(adi_matrix_gtxt(\'adi_article_selection\'))\n		.hcell(adi_matrix_gtxt(\'adi_article_data\'),\'\',\' colspan=\"\'.$data_span.\'\"\')\n		.hcell(\'\',\'\',\' class=\"adi_matrix_noborder\"\') // spacer for delete button\n	);\n}\n\nfunction adi_matrix_admin_table($matrix_list,$matrix_cfs) {\n// generate form fields for existing & new matrixes\n	global $adi_matrix_cfs,$adi_matrix_sort_options,$adi_matrix_sort_dir,$txp_user,$adi_matrix_sort_type,$prefs;\n\n	$out = \'\';\n	$out .= adi_matrix_admin_table_head($matrix_cfs);\n\n	// tack \'new\' index onto end of $matrix_list (field defaults for adding new matrix)\n	$matrix_list[\'new\'] = array(\n		\'name\' => \'\',\n		\'sort\' => \'posted\',\n		\'dir\' => \'desc\',\n		\'sort_type\' => \'alphabetical\',\n		\'user\' => $txp_user,\n		\'privs\' => \'\',\n		\'scroll\' => \'0\',\n		\'footer\' => \'0\',\n		\'title\' => \'0\',\n		\'publish\' => \'0\',\n		\'show_section\' => \'0\',\n		\'cf_links\' => \'\',\n		\'tab\' => \'0\',\n		\'criteria_section\' => \'\',\n		\'criteria_category\' => \'\',\n		\'criteria_descendent_cats\' => \'0\',\n		\'criteria_status\' => \'0\',\n		\'criteria_author\' => \'\',\n		\'criteria_keywords\' => \'\',\n		\'criteria_timestamp\' => \'\',\n		\'criteria_expiry\' => \'\',\n		\'criteria_condition\' => \'\',\n		\'status\' => \'0\',\n		\'keywords\' => \'0\',\n		\'article_image\' => \'0\',\n		\'category1\' => \'0\',\n		\'category2\' => \'0\',\n		\'posted\' => \'0\',\n		\'expires\' => \'0\',\n		\'section\' => \'0\'\n	);\n	foreach ($adi_matrix_cfs as $index => $value) // add custom fields to $matrix_list[\'new\']\n		$matrix_list[\'new\'][\'custom_\'.$index] = \'0\';\n\n	// existing matrixes followed by empty fields for a new one\n	foreach ($matrix_list as $matrix_index => $matrix) {\n		$cf_checkboxes = \'\';\n		foreach ($matrix_cfs as $index => $cf_name) {\n			$custom_x = \'custom_\'.$index;\n			$colon = \':\';\n			if (strpos($prefs[\'language\'],\'fr-\') === 0) // normally adi_matrix_gtxt takes care of this\n				$colon = \' \'.$colon;\n			$cf_checkboxes .= graf(tag($cf_name.$colon,\'label\').checkbox(\"matrix_\".$matrix_index.\"[$custom_x]\",1,$matrix[$custom_x]));\n		}\n		$cf_td = tda($cf_checkboxes,\' class=\"adi_matrix_field adi_matrix_custom_field\"\');\n		$url = \'?event=adi_matrix_matrix_\'.$matrix_index;\n		if ($matrix_index == \'new\')\n			$view_link = \' \';\n		else\n			$view_link = \'<a href=\"?event=adi_matrix_matrix_\'.$matrix_index.\'\">\'.adi_matrix_gtxt(\'view\').\'</a>\';\n		$out .= tr(\n				// matrix settings\n				tda(\n					graf(tag(adi_matrix_gtxt(\'name\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[name]\",$matrix[\'name\']))\n					.graf(tag(adi_matrix_gtxt(\'sort\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[sort]\",$adi_matrix_sort_options,$matrix[\'sort\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'sort_direction\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[dir]\",$adi_matrix_sort_dir,$matrix[\'dir\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'adi_sort_type\',\'\',\':\'),\'label\').selectInput(\"matrix_\".$matrix_index.\"[sort_type]\",$adi_matrix_sort_type,$matrix[\'sort_type\'],FALSE))\n					.graf(tag(adi_matrix_gtxt(\'adi_user\',\'\',\':\'),\'label\').adi_matrix_user_popup(\"matrix_\".$matrix_index.\"[user]\",$matrix[\'user\']))\n					.graf(tag(adi_matrix_gtxt(\'privileges\',\'\',\':\'),\'label\').adi_matrix_privs_popup(\"matrix_\".$matrix_index.\"[privs]\",$matrix[\'privs\']))\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_scroll\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[scroll]\",\'1\',($matrix[\'scroll\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[scroll]\",\'0\',($matrix[\'scroll\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_footer\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[footer]\",\'1\',($matrix[\'footer\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[footer]\",\'0\',($matrix[\'footer\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_show_section\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[show_section]\",\'1\',($matrix[\'show_section\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[show_section]\",\'0\',($matrix[\'show_section\'] == \'0\'))\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_cf_links\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[cf_links]\",\'article_image\',(!empty($matrix[\'cf_links\']))) // will be comma list one day\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[cf_links]\",\'\',(empty($matrix[\'cf_links\']))) // will be comma list one day\n						,\' style=\"display:none\"\'\n					)\n					.graf(\n						tag(adi_matrix_gtxt(\'publish\',\'\',\':\'),\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"matrix_\".$matrix_index.\"[publish]\",\'1\',($matrix[\'publish\'] == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"matrix_\".$matrix_index.\"[publish]\",\'0\',($matrix[\'publish\'] == \'0\'))\n					)\n					.graf(tag(adi_matrix_gtxt(\'adi_tab\',\'\',\':\'),\'label\').adi_matrix_tab_popup(\"matrix_\".$matrix_index.\"[tab]\",$matrix[\'tab\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				.tda($view_link)\n				// article selection\n				.tda(\n					adi_matrix_gtxt(\'section\',\'\',\':\').br\n					.tag(adi_matrix_section_checkboxes(\"matrix_\".$matrix_index.\"[criteria_section]\",$matrix[\'criteria_section\']),\'div\',\' class=\"adi_matrix_multi_checkboxes\"\')\n					.graf(tag(adi_matrix_gtxt(\'category\',\'\',\':\'),\'label\').adi_matrix_category_popup(\"matrix_\".$matrix_index.\"[criteria_category]\",$matrix[\'criteria_category\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_include_descendent_cats\',\'\',\':\'),\'label\',\' class=\"adi_matrix_label2\"\').checkbox(\"matrix_\".$matrix_index.\"[criteria_descendent_cats]\",1,$matrix[\'criteria_descendent_cats\']))\n					.graf(tag(adi_matrix_gtxt(\'status\',\'\',\':\'),\'label\').adi_matrix_status_popup(\"matrix_\".$matrix_index.\"[criteria_status]\",$matrix[\'criteria_status\']))\n					.graf(tag(adi_matrix_gtxt(\'author\',\'\',\':\'),\'label\').adi_matrix_user_popup(\"matrix_\".$matrix_index.\"[criteria_author]\",$matrix[\'criteria_author\'],TRUE))\n					.graf(tag(adi_matrix_gtxt(\'keywords\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[criteria_keywords]\",$matrix[\'criteria_keywords\']))\n					.graf(tag(adi_matrix_gtxt(\'timestamp\',\'\',\':\'),\'label\').adi_matrix_timestamp_popup(\"matrix_\".$matrix_index.\"[criteria_timestamp]\",$matrix[\'criteria_timestamp\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_expiry\',\'\',\':\'),\'label\').adi_matrix_expiry_popup(\"matrix_\".$matrix_index.\"[criteria_expiry]\",$matrix[\'criteria_expiry\']))\n					.graf(tag(adi_matrix_gtxt(\'adi_custom_condition\',\'\',\':\'),\'label\').finput(\"text\",\"matrix_\".$matrix_index.\"[criteria_condition]\",$matrix[\'criteria_condition\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				// article data\n				.tda(\n					graf(tag(adi_matrix_gtxt(\'status\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[status]\",1,$matrix[\'status\']))\n					.graf(tag(adi_matrix_gtxt(\'keywords\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[keywords]\",1,$matrix[\'keywords\']))\n					.graf(tag(adi_matrix_gtxt(\'article_image\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[article_image]\",1,$matrix[\'article_image\']))\n					.graf(tag(adi_matrix_gtxt(\'category1\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[category1]\",1,$matrix[\'category1\']))\n					.graf(tag(adi_matrix_gtxt(\'category2\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[category2]\",1,$matrix[\'category2\']))\n					.graf(tag(adi_matrix_gtxt(\'posted\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[posted]\",1,$matrix[\'posted\']))\n					.graf(tag(adi_matrix_gtxt(\'expires\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[expires]\",1,$matrix[\'expires\']))\n					.graf(tag(adi_matrix_gtxt(\'title\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[title]\",1,$matrix[\'title\']))\n					.graf(tag(adi_matrix_gtxt(\'section\',\'\',\':\'),\'label\').checkbox(\"matrix_\".$matrix_index.\"[section]\",1,$matrix[\'section\']))\n					,\' class=\"adi_matrix_field\"\'\n				)\n				.$cf_td\n				.tda(adi_matrix_delete_button($matrix_list,$matrix_index),\' class=\"adi_matrix_delete\"\')\n			);\n	}\n	return $out;\n}\n\nfunction adi_matrix_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_matrix_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_matrix_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_matrix_prefs))\n			$html = $adi_matrix_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_matrix_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_matrix_tiny_mce_custom() {\n// TinyMCE implementation in a modal window\n	global $adi_matrix_cfs;\n\n	$jquery_ui = adi_matrix_pref(\'adi_matrix_jquery_ui\');\n	$tiny_mce_dir = adi_matrix_pref(\'adi_matrix_tiny_mce_dir\');\n	$adi_matrix_tiny_mce_config = adi_matrix_pref(\'adi_matrix_tiny_mce_config\');\n\n	$title = adi_matrix_gtxt(\'edit\');\n	$ok = adi_matrix_gtxt(\'adi_ok\');\n	$cancel = adi_matrix_gtxt(\'adi_cancel\');\n\n	$script = <<<END_SCRIPT\n<script src=\"$jquery_ui\" type=\"text/javascript\"></script>\n<script type=\"text/javascript\" src=\"$tiny_mce_dir/tiny_mce.js\"></script>\n\n<script type=\"text/javascript\">\n\n//<![CDATA[\n	$(document).ready(function(){\n\n		var i = 0;\n		$(\'.adi_matrix_matrix .glz_text_area_field textarea\').each(function(){\n			// hide textareas\n			$(this).css({display:\"none\"});\n			// assign unique class\n			i++;\n			$(this).addClass(\'mceNoEditor tie\' + i);\n			// create div containing textarea contents (with same unique class)\n			var text = $(this).val();\n			$(this).after(\'<div class=\"tie_div tie\' + i + \'\">\' + text + \'<\\/div>\');\n\n		});\n\n		$(\'.adi_matrix_matrix div.tie_div\').click(function(){\n\n			// get unique class (actually the last class - potentially dodgy?)\n			var thisClass = $(this).attr(\'class\').split(\' \').slice(-1);\n			// get corresponding textarea\n			var thisTextarea = $(\'textarea.\' + thisClass);\n			// get textarea name attribute (e.g. article_2[custom_6])\n			// WILL NEED TO SET THIS INFO IN THE DOM IN ADVANCE - TEXTAREA TITLE ATTR?\n			// var thisName = $(thisTextarea).attr(\'name\');\n\n			$(\'#dialog\').remove();\n			$(\'body\').append(\'<div id=\"dialog\" \\/>\');\n			$(\'#dialog\').dialog({\n				autoOpen: false,\n				bgiframe: true,\n				resizable: false,\n				width: 700,\n				position: [\'center\',35],\n				overlay: { backgroundColor: \'#000\', opacity: 0.6 },\n				open: function(e, ui){\n\n				},\n				beforeclose: function(event, ui) {\n					tinyMCE.get(\'editor\').remove();\n					$(\'#editor\').remove();\n				}\n\n			});\n\n			$(\'#dialog\').dialog(\'option\', \'title\', \'$title\');\n			$(\'#dialog\').dialog(\'option\', \'modal\', true);\n			$(\'#dialog\').dialog(\'option\', \'buttons\', {\n				$cancel: function() {\n							$(this).dialog(\'close\');\n				},\n				$ok: function() {\n						var content = tinyMCE.get(\'editor\').getContent();\n						// feed edited contents into textarea\n						$(thisTextarea).val(content);\n						// feed edited contents into div\n						$(\'div.\' + thisClass).html(content);\n						$(this).dialog(\'close\');\n				}\n			});\n\n			$(\'#dialog\').html(\'<textarea name=\"editor\" id=\"editor\"><\\/textarea>\');\n			$(\'#dialog\').dialog(\'open\');\n			tinyMCE.init({\n				mode : \"specific_textareas\",\n				editor_deselector : \"mceNoEditor\",\n				// start of user configurable options\n				$adi_matrix_tiny_mce_config\n				// end of user-configurable options\n				setup : function(ed) {\n					ed.onInit.add(function(ed) {\n						tinyMCE.get(\'editor\').setContent($(thisTextarea).val());\n						tinyMCE.execCommand(\'mceRepaint\');\n					});\n				}\n\n		 	});\n			return false;\n		});\n	});\n//]]></script>\n\nEND_SCRIPT;\n\n	echo $script;\n}\n\nfunction adi_matrix_tiny_mce_style() {\n// jQuery UI CSS for TinyMCE modal window\n	$jquery_ui_css = adi_matrix_pref(\'adi_matrix_jquery_ui_css\');\n	echo \'<link href=\"\'.$jquery_ui_css.\'\" type=\"text/css\" rel=\"stylesheet\" />\';\n}\n\n\nfunction adi_matrix_admin($event, $step) {\n// adi_matrix admin tab\n	global $adi_matrix_debug,$adi_matrix_cfs,$adi_matrix_prefs,$adi_matrix_url,$prefs,$textarray,$adi_matrix_privs,$adi_matrix_groups,$txp_permissions,$adi_matrix_glz_cfs,$adi_matrix_sort_options;\n\n	$message = \'\';\n	$installed = adi_matrix_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_matrix_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_matrix_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else { // custom field musical chairs\n			$cfs_fiddled = FALSE;\n			// add additional custom fields that may have suddenly appeared (glz_cfs: custom_11+)\n			foreach ($adi_matrix_cfs as $index => $value) {\n				$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'custom_$index\'\",$adi_matrix_debug); // find out if column exists\n				$a = nextRow($rs);\n				if (empty($a)) {\n					safe_query(\"ALTER TABLE \".safe_pfx(\'adi_matrix\').\" ADD `custom_$index` TINYINT(1) DEFAULT 0 NOT NULL\",$adi_matrix_debug);\n					$cfs_fiddled = TRUE;\n				}\n			}\n			// remove custom fields that may have been deleted in glz_cfs\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_matrix\').\" LIKE \'custom%\'\",$adi_matrix_debug);\n			$current_cfs = array();\n			while ($a = nextRow($rs)) { // get list of cf indexes from adi_matrix\n				$index = substr($a[\'Field\'],7); // strip \'custom_\' from \'custom_x\'\n				$current_cfs[$index] = TRUE;\n			}\n			foreach ($current_cfs as $index => $value)\n				if (!array_key_exists($index,$adi_matrix_cfs)) {\n					safe_query(\"ALTER TABLE \".safe_pfx(\'adi_matrix\').\" DROP COLUMN `custom_$index`\",$adi_matrix_debug);\n					$cfs_fiddled = TRUE;\n				}\n			if ($cfs_fiddled)\n				$message = adi_matrix_gtxt(\'adi_matrix_cfs_modified\');\n		}\n	}\n	else\n		$message = array(adi_matrix_gtxt(\'adi_not_installed\'),E_ERROR);\n\n	// admin $step aerobics\n	if ($step == \'update\') {\n		$result = adi_matrix_update_settings();\n		$result ? $message = adi_matrix_gtxt(\'adi_matrix_updated\') : $message = array(adi_matrix_gtxt(\'adi_matrix_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'delete\') {\n		$matrix_index = gps(\'matrix\');\n		$result = adi_matrix_delete($matrix_index);\n		$result ? $message = adi_matrix_gtxt(\'adi_matrix_deleted\') : $message = array(adi_matrix_gtxt(\'adi_matrix_delete_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		foreach ($adi_matrix_prefs as $name => $data) {\n			if (array_key_exists($name,$_POST))\n				$value = $_POST[$name];\n			else if ($data[\'input\'] == \'yesnoradio\')\n				$value = \'0\';\n			else\n				$value = $data[\'value\'];\n			// some values not allowed to be blank, reset to default\n			$non_blanks = array(\'adi_matrix_tiny_mce_dir\',\'adi_matrix_jquery_ui\',\'adi_matrix_jquery_ui_css\');\n			foreach ($non_blanks as $non_blank)\n				if (($name == $non_blank) && (trim($value) == \'\'))\n					$value = $adi_matrix_prefs[$non_blank][\'value\'];\n			$result = $result && adi_matrix_pref($name,$value);\n		}\n		$result ? $message = adi_matrix_gtxt(\'preferences_saved\') : $message = array(adi_matrix_gtxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(adi_matrix_gtxt(\'adi_matrix_admin\'),$message);\n\n	$installed = adi_matrix_installed();\n	if ($installed && !$upgrade_required) {\n		$adi_matrix_list = adi_matrix_read_settings();\n		// output table & input form\n		echo form(\n			startTable(\'list\',\'\',\'txp-list\')\n			.adi_matrix_admin_table($adi_matrix_list,$adi_matrix_cfs)\n			.endTable()\n			.tag(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_update_matrix\'), \"smallerbox\").\n				eInput(\"adi_matrix_admin\").sInput(\"update\"),\n				\'div\',\n				\' class=\"adi_matrix_button\"\'\n			)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_matrix_admin\'\n		);\n		// preferences\n		echo form(\n			tag(\n				tag(adi_matrix_gtxt(\'edit_preferences\'),\'h2\')\n				// display article id\n				.graf(\n					tag(adi_matrix_gtxt(\'adi_display_article_id\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_display_id\",adi_matrix_pref(\'adi_matrix_display_id\'))\n					.sp.sp\n					.tag(adi_matrix_gtxt(\'adi_article_highlighting\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_article_highlighting\",adi_matrix_pref(\'adi_matrix_article_highlighting\'))\n				)\n				// article tooltips\n				.graf(\n					tag(adi_matrix_gtxt(\'adi_article_tooltips\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_article_tooltips\",adi_matrix_pref(\'adi_matrix_article_tooltips\'))\n					.sp.sp\n					.tag(adi_matrix_gtxt(\'adi_matrix_input_field_tooltips\',\'\',\':\'),\'label\')\n					.checkbox2(\"adi_matrix_input_field_tooltips\",adi_matrix_pref(\'adi_matrix_input_field_tooltips\'))\n				)\n				.( $adi_matrix_glz_cfs ?\n					// tinymce\n					graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce\').\'? \',\'label\')\n						.adi_matrix_gtxt(\'yes\')\n						.radio(\"adi_matrix_tiny_mce\",\'1\',(adi_matrix_pref(\'adi_matrix_tiny_mce\') == \'1\'))\n						.sp.sp\n						.adi_matrix_gtxt(\'no\')\n						.radio(\"adi_matrix_tiny_mce\",\'0\',(adi_matrix_pref(\'adi_matrix_tiny_mce\') == \'0\'))\n						,\' class=\"adi_matrix_radio\"\'\n					)\n					.\'<div id=\"peekaboo\">\'\n					// tinymce dir\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce_dir_path\',\'\',\':\'),\'label\')\n						.finput(\"text\",\'adi_matrix_tiny_mce_dir\',adi_matrix_pref(\'adi_matrix_tiny_mce_dir\'),\'\',\'\',\'\',40)\n					)\n					// jquery ui\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_jquery_ui\').\':\',\'label\')\n						.finput(\"text\",\'adi_matrix_jquery_ui\',adi_matrix_pref(\'adi_matrix_jquery_ui\'),\'\',\'\',\'\',40)\n					)\n					// jquery ui css\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_jquery_ui_css\').\':\',\'label\')\n						.finput(\"text\",\'adi_matrix_jquery_ui_css\',adi_matrix_pref(\'adi_matrix_jquery_ui_css\'),\'\',\'\',\'\',40)\n					)\n					// tinymce config\n					.graf(\n						tag(adi_matrix_gtxt(\'adi_tiny_mce_config\').\':\',\'label\')\n					)\n					.graf(\n						text_area(\'adi_matrix_tiny_mce_config\',300,600,adi_matrix_pref(\'adi_matrix_tiny_mce_config\'))\n					)\n					.\'</div>\'\n				: \'\')\n				.fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_update_prefs\'), \"smallerbox\")\n				.eInput(\"adi_matrix_admin\").sInput(\"update_prefs\")\n				,\'div\'\n				,\' class=\"adi_matrix_prefs\"\'\n			)\n		);\n	}\n\n	if ($adi_matrix_debug) {\n		echo \"<b>Event:</b> \".$event.\", <b>Step:</b> \".$step.br.br;\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n		if ($installed) {\n			echo \'<b>Prefs:</b><br/>\';\n			foreach ($adi_matrix_prefs as $name => $this_pref)\n				echo $name.\' = \'.adi_matrix_pref($name).\'<br/>\';\n			echo \'<br/>\';\n			echo \'<b>$adi_matrix_list:</b>\';\n			dmp(adi_matrix_read_settings());\n			echo \'<b>$adi_matrix_sort_options:</b>\';\n			dmp($adi_matrix_sort_options);\n			echo \'<b>$adi_matrix_groups:</b>\';\n			dmp($adi_matrix_groups);\n			echo \'<b>$adi_matrix_cfs:</b>\';\n			dmp($adi_matrix_cfs);\n			echo \'<b>glz_custom_fields plugin:</b> is\';\n			if (!$adi_matrix_glz_cfs)\n				echo \' NOT\';\n			echo \' installed<br/>\';\n		}\n	}\n\n}\n\nfunction adi_matrix_options($event,$step) {\n// plugin options page\n	global $adi_matrix_debug,$adi_matrix_url,$adi_matrix_plugin_status,$textarray;\n\n	$message = \'\';\n\n	$installed = adi_matrix_installed();\n	if ($installed) {\n		$upgrade_required = adi_matrix_upgrade();\n		if ($upgrade_required)\n			$step = \'upgrade\';\n	}\n\n	// dance steps\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_matrix_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = adi_matrix_gtxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_matrix_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_matrix_upgrade(TRUE);\n		$result ? $message = adi_matrix_gtxt(\'adi_upgraded\') : $message = array(adi_matrix_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'downgrade\') {\n		$result = adi_matrix_downgrade();\n		$result ? $message = adi_matrix_gtxt(\'adi_downgraded\') : $message = array(adi_matrix_gtxt(\'adi_downgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'tweak\') { // for development updates\n		$result = safe_query(\"ALTER TABLE \".safe_pfx(\"adi_matrix\").\" ADD `cf_links` VARCHAR(255) NOT NULL DEFAULT \'\'\",$adi_matrix_debug);\n		$result ? $message = \'Tweaked\' : $message = array(\'Tweak failed\',E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_matrix_install();\n		$result ? $message = adi_matrix_gtxt(\'adi_installed\') : $message = array(adi_matrix_gtxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_matrix_uninstall();\n		$result ? $message = adi_matrix_gtxt(\'adi_uninstalled\') : $message = array(adi_matrix_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_matrix - \'.adi_matrix_gtxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		tag(\n			form(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.adi_matrix_gtxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"install\")\n				,\'\',\'\',\'post\',\'adi_matrix_nstall_button\'\n			)\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	$uninstall_button =\n		tag(\n	    	form(\n				fInput(\"submit\", \"do_something\", adi_matrix_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.adi_matrix_gtxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"uninstall\")\n				,\'\',\'\',\'post\',\'adi_matrix_nstall_button adi_matrix_uninstall_button\'\n			)\n			,\'div\'\n			,\' style=\"margin-top:5em\"\');\n\n	if ($adi_matrix_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_matrix_installed();\n	if ($installed) {\n		// options\n		echo tag(\n			tag(\'adi_matrix \'.adi_matrix_gtxt(\'plugin_prefs\'),\'h2\')\n			// textpack links\n			.graf(href(adi_matrix_gtxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_matrix_gtxt(\'adi_textpack_online\'),$adi_matrix_url[\'textpack_download\']))\n			.graf(href(adi_matrix_gtxt(\'adi_textpack_feedback\'),$adi_matrix_url[\'textpack_feedback\']))\n			.$uninstall_button\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	}\n	else // install button\n	    echo $install_button;\n\n	if ($adi_matrix_debug) {\n		echo \"<p><b>Event:</b> \".$event.\", <b>Step:</b> \".$step.\"</p>\";\n		echo \'<b>$adi_textpack (\'.$adi_matrix_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_matrix_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', 'f982dc4576ea6ba6ddc94dbcb282ef41', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_article_tab', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Enhanced article tab', '<h1><strong>adi_article_tab</strong> &#8211; Enhanced article tab</h1>\n\n<p>This plugin provides some enhancements to the standard <span class=\"caps\">TXP</span> Article Edit tab:</p>\n\n<ul>\n	<li>display article ID</li>\n	<li>article delete button</li>\n</ul>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Install &amp; activate the plugin, then find an article to edit.  The article&#8217;s ID will be shown next to the author information and a delete button is also provided.</p>\n\n<h2><strong>Privileges</strong></h2>\n\n<p>You can adjust who gets to use the plugin by setting the privileges in options.</p>\n\n<p>Only users with sufficient “article delete” privileges will get the delete button. All users see the article ID.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on ‘Install textpack’.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <em><span class=\"caps\">TXP</span> Admin &#8211; Language</em> tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_article_tab\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=46211\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_article_tab\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_article_tab - Enhanced article tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- refixes for TXP 4.6\n	0.2		- fixes for TXP 4.6\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $prefs,$adi_article_tab_debug;\n\n	$adi_article_tab_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_article_tab_init();\n}\n\nfunction adi_article_tab_init() {\n// initial set up\n	global $prefs,$event,$step,$textarray,$adi_article_tab_tab_list,$adi_article_tab_prefs,$adi_article_tab_outlaws,$adi_article_tab_debug,$adi_article_tab_plugin_status,$adi_article_tab_url,$adi_article_tab_txp460;\n\n	$adi_article_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// default preferences\n	$adi_article_tab_prefs = array(\n		\'adi_article_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'private\' => 0, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n	);\n\n	// plugin privs to be excluded\n	$adi_article_tab_outlaws = array(0);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_article_tab_gtxt = array(\n		\'adi_admin_privs\' => \'Admin privileges\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_plugin_privs\' => \'Plugin privileges\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_article_tab_gtxt;\n\n	// Textpack\n	$adi_article_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_article_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_article_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	$view = gps(\'view\'); // values: blank or \"text\" (normal edit view), \"html\", \"preview\"\n	if (($view == \'text\') || ($view == \'\')) { // don\'t want anything in HTML or Preview views\n		// adi_article_tab privs & callbacks\n		add_privs(\'adi_article_tab\',adi_article_tab_pref(\'adi_article_tab_privs\'));\n		if (has_privs(\'adi_article_tab\')) {\n			if ($event == \'article\')\n				register_callback(\'adi_article_tab_delete_button\',\'admin_side\',\'body_end\'); // load at bottom of page (to get new article ID) & shift into position using jQuery (USE MAIN_CONTENT_END IN 4.6?)\n			register_callback(\'adi_article_tab_display_id\',\'article_ui\',\'author\');\n		}\n		// script\n		if ($event == \'article\')\n			register_callback(\'adi_article_tab_script\',\'admin_side\',\'head_end\');\n	}\n\n	// plugin options\n	$adi_article_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_article_tab\');\n	if ($adi_article_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_article_tab\'); // defaults to priv \'1\' only\n		register_callback(\'adi_article_tab_options\',\'plugin_prefs.adi_article_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_article_tab_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_article_tab_options\',\'adi_article_tab options\');\n		register_callback(\'adi_article_tab_options\',\'adi_article_tab_options\');\n	}\n\n}\n\nfunction adi_article_tab_script() {\n// scriptism\n	global $adi_article_tab_txp460;\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_article_tab\n		var txp460 = \"$adi_article_tab_txp460\";\n		// shift article delete button into place\n		if (txp460 == \"1\") {\n 			$(\"#adi_article_tab_delete_link\").appendTo(\"p.txp-save + p.txp-actions\");\n 		}\n 		else {\n	 		$(\"p.adi_article_tab_delete_button\").insertBefore(\"fieldset#write-status\");\n	 	}\n 		// link click -> form submit\n		$(\'#adi_article_tab_delete_link\').click(function() {\n			$(\'form#adi_article_tab_delete_form\').submit();\n		});\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_article_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_article_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_article_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_article_tab_prefs))\n			$html = $adi_article_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_article_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_article_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_article_tab_delete_button($e,$s) {\n// delete button\n// generate a hidden form to sit in footer (can\'t have it inside article tab\'s <form>)\n// generate a link \"button\" that\'ll get shifted into place by jQuery\n	global $event,$step,$txp_user,$txp_permissions,$adi_article_tab_debug,$adi_article_tab_txp460;\n\n	$debug = \'[NO BUTTON]\';\n\n	if ($step == \'edit\') { // article edit only\n\n		if (isset($GLOBALS[\'ID\'])) { // new article create\n			$id = $GLOBALS[\'ID\'];\n			$debug = \"id=n/a,globals_id=$id\";\n		}\n		else { // existing article\n			$id = gps(\'ID\');\n			$debug = \"id=$id,globals_id=n/a\";\n		}\n\n		$id = assert_int($id);\n\n		$author = safe_field(\'AuthorID\',\'textpattern\',\"ID=$id\");\n\n		if ($adi_article_tab_debug) {\n			$user_priv = safe_field(\"privs\",\"txp_users\",\"name=\'\".doSlash($txp_user).\"\'\");\n			$debug =\n				\"event=$event,step=$step,\".$debug\n				.br.\"txp_user=$txp_user,author=$author,user_priv=$user_priv\"\n				.br.\"article.delete=\".$txp_permissions[\'article.delete\'].\";article.delete.own=\".$txp_permissions[\'article.delete.own\'];\n		}\n		else\n			 $debug = \'\';\n\n		if (has_privs(\'article.delete\') || (($txp_user == $author) && has_privs(\'article.delete.own\'))) {\n\n			// echo (to avoid fiddling with $default) a hidden form that\'ll get actioned by jQuery when user clicks link button\n			// have to use form coz list tab expects instructions to be POSTed\n 			echo\n 				form(\n					eInput(\'list\')\n					.sInput(\'list_multi_edit\')\n					.hInput(\'edit_method\',\'delete\')\n					.hInput(\'selected[0]\',$id)\n 					,\'display:none\'\n 					,\"confirm(\'\".gTxt(\'confirm_delete_popup\').\"\');\"\n 					,\'post\'\n 					,\'\'\n 					,\'\'\n 					,\'adi_article_tab_delete_form\' // HTML ID\n 				);\n\n			if ($adi_article_tab_txp460)\n				$delete_link = \'<a href=\"#\" id=\"adi_article_tab_delete_link\" title=\"\'.gTxt(\'delete\').\'\" class=\"txp-destroy\"><span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'delete\').\'</a>\';\n			else\n				$delete_link = \'<p class=\"action-create adi_article_tab_delete_button\"><a href=\"#\" id=\"adi_article_tab_delete_link\" title=\"\'.gTxt(\'delete\').\'\" class=\"txp-destroy txp-button\">\'.gTxt(\'delete\').\'</a></p>\';\n\n			// echo (to avoid fiddling with $default) a \"delete article\" link (shifted into place using jQuery)\n			echo $debug.$delete_link;\n\n			return;\n		}\n		else\n			$debug .= br.\'[INSUFFICIENT PRIVS]\';\n	}\n\n	if ($adi_article_tab_debug) echo graf($debug,\' class=\"adi_article_tab_delete_button\"\');\n}\n\nfunction adi_article_tab_display_id($e,$s,$default) {\n// insert article id before \"Posted by\" bit\n\n	if (isset($GLOBALS[\'ID\'])) // new article create\n		$id = $GLOBALS[\'ID\'];\n	else // existing article\n		$id = gps(\'ID\');\n\n	return str_replace(\'<small>\',\'<small>#\'.$id.\' &#183; \',$default);\n}\n\nfunction adi_article_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields according to options/admin setting\n	global $txp_groups,$adi_article_tab_prefs,$adi_article_tab_outlaws,$adi_article_tab_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_article_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_article_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_article_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_article_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_article_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_article_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_article_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// plugin privs\n					if ($pref_name == \'adi_article_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_article_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_article_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_article_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					break;\n				default: // text_input\n					$size = (isset($pref_info[\'size\']) ? $pref_info[\'size\'] : \'\');\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_article_tab_pref($pref_name)),$pref_name,\'\',\'\',$size)\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\" class=\"adi_article_tab_prefs\"\'\n				)\n			);\n\n}\n\nfunction adi_article_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_article_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		foreach ($adi_article_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) { // tab match?\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_article_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_article_tab_prefs[$pref_name][\'value\'];\n				// update pref\n				$private = ($pref_info[\'tab\'] == \'options\' ? FALSE : $pref_info[\'private\']); // \"options\" prefs can\'t be private\n				$result &= adi_article_tab_pref($pref_name,$value,$private);\n			}\n		}\n		return $result;\n	}\n\n}\n\nfunction adi_article_tab_options($event,$step) {\n// adi_article_tab options page\n	global $txp_user,$txp_groups,$adi_article_tab_debug,$adi_article_tab_url,$adi_article_tab_plugin_status,$adi_article_tab_prefs,$adi_article_tab_outlaws;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_article_tab_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_article_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_article_tab - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// plugin priv checkboxes\n	$plugin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_privs\'));\n	$priv_checkboxes = \'\';\n	foreach ($txp_groups as $index => $group)\n		if (array_search($index,$adi_article_tab_outlaws) === FALSE) // ignore the outlaws\n			$priv_checkboxes .= tag(gTxt($group).checkbox2(\"adi_article_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_article_tab_privs\').sp.sp,\'label\');\n\n	// options\n	echo tag(\n		tag(\'adi_article_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// prefs\n		.adi_article_tab_pref_inputs($event,\'options\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_article_tab_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_article_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n', '\n/*\n	adi_article_tab - Enhanced article tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- refixes for TXP 4.6\n	0.2		- fixes for TXP 4.6\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $prefs,$adi_article_tab_debug;\n\n	$adi_article_tab_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_article_tab_init();\n}\n\nfunction adi_article_tab_init() {\n// initial set up\n	global $prefs,$event,$step,$textarray,$adi_article_tab_tab_list,$adi_article_tab_prefs,$adi_article_tab_outlaws,$adi_article_tab_debug,$adi_article_tab_plugin_status,$adi_article_tab_url,$adi_article_tab_txp460;\n\n	$adi_article_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// default preferences\n	$adi_article_tab_prefs = array(\n		\'adi_article_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'private\' => 0, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n	);\n\n	// plugin privs to be excluded\n	$adi_article_tab_outlaws = array(0);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_article_tab_gtxt = array(\n		\'adi_admin_privs\' => \'Admin privileges\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_plugin_privs\' => \'Plugin privileges\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_article_tab_gtxt;\n\n	// Textpack\n	$adi_article_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_article_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_article_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	$view = gps(\'view\'); // values: blank or \"text\" (normal edit view), \"html\", \"preview\"\n	if (($view == \'text\') || ($view == \'\')) { // don\'t want anything in HTML or Preview views\n		// adi_article_tab privs & callbacks\n		add_privs(\'adi_article_tab\',adi_article_tab_pref(\'adi_article_tab_privs\'));\n		if (has_privs(\'adi_article_tab\')) {\n			if ($event == \'article\')\n				register_callback(\'adi_article_tab_delete_button\',\'admin_side\',\'body_end\'); // load at bottom of page (to get new article ID) & shift into position using jQuery (USE MAIN_CONTENT_END IN 4.6?)\n			register_callback(\'adi_article_tab_display_id\',\'article_ui\',\'author\');\n		}\n		// script\n		if ($event == \'article\')\n			register_callback(\'adi_article_tab_script\',\'admin_side\',\'head_end\');\n	}\n\n	// plugin options\n	$adi_article_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_article_tab\');\n	if ($adi_article_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_article_tab\'); // defaults to priv \'1\' only\n		register_callback(\'adi_article_tab_options\',\'plugin_prefs.adi_article_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_article_tab_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_article_tab_options\',\'adi_article_tab options\');\n		register_callback(\'adi_article_tab_options\',\'adi_article_tab_options\');\n	}\n\n}\n\nfunction adi_article_tab_script() {\n// scriptism\n	global $adi_article_tab_txp460;\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_article_tab\n		var txp460 = \"$adi_article_tab_txp460\";\n		// shift article delete button into place\n		if (txp460 == \"1\") {\n 			$(\"#adi_article_tab_delete_link\").appendTo(\"p.txp-save + p.txp-actions\");\n 		}\n 		else {\n	 		$(\"p.adi_article_tab_delete_button\").insertBefore(\"fieldset#write-status\");\n	 	}\n 		// link click -> form submit\n		$(\'#adi_article_tab_delete_link\').click(function() {\n			$(\'form#adi_article_tab_delete_form\').submit();\n		});\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_article_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_article_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_article_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_article_tab_prefs))\n			$html = $adi_article_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_article_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_article_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_article_tab_delete_button($e,$s) {\n// delete button\n// generate a hidden form to sit in footer (can\'t have it inside article tab\'s <form>)\n// generate a link \"button\" that\'ll get shifted into place by jQuery\n	global $event,$step,$txp_user,$txp_permissions,$adi_article_tab_debug,$adi_article_tab_txp460;\n\n	$debug = \'[NO BUTTON]\';\n\n	if ($step == \'edit\') { // article edit only\n\n		if (isset($GLOBALS[\'ID\'])) { // new article create\n			$id = $GLOBALS[\'ID\'];\n			$debug = \"id=n/a,globals_id=$id\";\n		}\n		else { // existing article\n			$id = gps(\'ID\');\n			$debug = \"id=$id,globals_id=n/a\";\n		}\n\n		$id = assert_int($id);\n\n		$author = safe_field(\'AuthorID\',\'textpattern\',\"ID=$id\");\n\n		if ($adi_article_tab_debug) {\n			$user_priv = safe_field(\"privs\",\"txp_users\",\"name=\'\".doSlash($txp_user).\"\'\");\n			$debug =\n				\"event=$event,step=$step,\".$debug\n				.br.\"txp_user=$txp_user,author=$author,user_priv=$user_priv\"\n				.br.\"article.delete=\".$txp_permissions[\'article.delete\'].\";article.delete.own=\".$txp_permissions[\'article.delete.own\'];\n		}\n		else\n			 $debug = \'\';\n\n		if (has_privs(\'article.delete\') || (($txp_user == $author) && has_privs(\'article.delete.own\'))) {\n\n			// echo (to avoid fiddling with $default) a hidden form that\'ll get actioned by jQuery when user clicks link button\n			// have to use form coz list tab expects instructions to be POSTed\n 			echo\n 				form(\n					eInput(\'list\')\n					.sInput(\'list_multi_edit\')\n					.hInput(\'edit_method\',\'delete\')\n					.hInput(\'selected[0]\',$id)\n 					,\'display:none\'\n 					,\"confirm(\'\".gTxt(\'confirm_delete_popup\').\"\');\"\n 					,\'post\'\n 					,\'\'\n 					,\'\'\n 					,\'adi_article_tab_delete_form\' // HTML ID\n 				);\n\n			if ($adi_article_tab_txp460)\n				$delete_link = \'<a href=\"#\" id=\"adi_article_tab_delete_link\" title=\"\'.gTxt(\'delete\').\'\" class=\"txp-destroy\"><span class=\"ui-icon ui-icon-trash\"></span> \'.gTxt(\'delete\').\'</a>\';\n			else\n				$delete_link = \'<p class=\"action-create adi_article_tab_delete_button\"><a href=\"#\" id=\"adi_article_tab_delete_link\" title=\"\'.gTxt(\'delete\').\'\" class=\"txp-destroy txp-button\">\'.gTxt(\'delete\').\'</a></p>\';\n\n			// echo (to avoid fiddling with $default) a \"delete article\" link (shifted into place using jQuery)\n			echo $debug.$delete_link;\n\n			return;\n		}\n		else\n			$debug .= br.\'[INSUFFICIENT PRIVS]\';\n	}\n\n	if ($adi_article_tab_debug) echo graf($debug,\' class=\"adi_article_tab_delete_button\"\');\n}\n\nfunction adi_article_tab_display_id($e,$s,$default) {\n// insert article id before \"Posted by\" bit\n\n	if (isset($GLOBALS[\'ID\'])) // new article create\n		$id = $GLOBALS[\'ID\'];\n	else // existing article\n		$id = gps(\'ID\');\n\n	return str_replace(\'<small>\',\'<small>#\'.$id.\' &#183; \',$default);\n}\n\nfunction adi_article_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields according to options/admin setting\n	global $txp_groups,$adi_article_tab_prefs,$adi_article_tab_outlaws,$adi_article_tab_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_article_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_article_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_article_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_article_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_article_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_article_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_article_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// plugin privs\n					if ($pref_name == \'adi_article_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_article_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_article_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_article_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					break;\n				default: // text_input\n					$size = (isset($pref_info[\'size\']) ? $pref_info[\'size\'] : \'\');\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_article_tab_pref($pref_name)),$pref_name,\'\',\'\',$size)\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\" class=\"adi_article_tab_prefs\"\'\n				)\n			);\n\n}\n\nfunction adi_article_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_article_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		foreach ($adi_article_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) { // tab match?\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_article_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_article_tab_prefs[$pref_name][\'value\'];\n				// update pref\n				$private = ($pref_info[\'tab\'] == \'options\' ? FALSE : $pref_info[\'private\']); // \"options\" prefs can\'t be private\n				$result &= adi_article_tab_pref($pref_name,$value,$private);\n			}\n		}\n		return $result;\n	}\n\n}\n\nfunction adi_article_tab_options($event,$step) {\n// adi_article_tab options page\n	global $txp_user,$txp_groups,$adi_article_tab_debug,$adi_article_tab_url,$adi_article_tab_plugin_status,$adi_article_tab_prefs,$adi_article_tab_outlaws;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_article_tab_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_article_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_article_tab - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// plugin priv checkboxes\n	$plugin_privs = do_list(adi_article_tab_pref(\'adi_article_tab_privs\'));\n	$priv_checkboxes = \'\';\n	foreach ($txp_groups as $index => $group)\n		if (array_search($index,$adi_article_tab_outlaws) === FALSE) // ignore the outlaws\n			$priv_checkboxes .= tag(gTxt($group).checkbox2(\"adi_article_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_article_tab_privs\').sp.sp,\'label\');\n\n	// options\n	echo tag(\n		tag(\'adi_article_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// prefs\n		.adi_article_tab_pref_inputs($event,\'options\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_article_tab_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_article_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n', 'b4701c20f8e0cccc84b3f6346d54ea8d', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_calc', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '1.2', 'Calculator for TXP variables', '<h1><strong>adi_calc</strong> &#8211; Calculator for <span class=\"caps\">TXP</span> variables</h1>\n\n<p>This plugin does calculations using <span class=\"caps\">TXP</span> variables.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p><strong>adi_calc</strong> may be used as a single or container tag.</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>name=&quot;var name&quot;</code></p>\n\n<p>- a <span class=\"caps\">TXP</span> variable to work with. This attribute is optional &amp; is ignored in container mode.  If not supplied in single tag mode then the initial value for the calculation is taken from the <code>value</code> attribute. If this is not supplied, then zero is used.</p>\n\n<p><code>value=&quot;value&quot;</code></p>\n\n<p>- the initial value used for the calculation. This attribute is optional. Default = &#8220;&#8221; (i.e. if <code>name</code> variable exists, use its value; else use zero). <code>value</code> will <em>override</em> <code>name</code> if both are supplied.</p>\n\n<p><code>display=&quot;boolean&quot;</code></p>\n\n<p>- display the variable&#8217;s value <em>after</em> the calculation has been performed. Default = “0” (do not display). <em>If neither <code>name</code> or <code>result</code> attributes are supplied for the calculation then <code>display</code> is automatically defaulted to “1”</em>.</p>\n\n<p><code>add=&quot;value&quot;</code></p>\n\n<p>- add “value”.</p>\n\n<p><code>subtract=&quot;value&quot;</code></p>\n\n<p>- subtract “value”.</p>\n\n<p><code>multiply=&quot;value&quot;</code></p>\n\n<p>- multiply by “value”.</p>\n\n<p><code>div=&quot;value&quot;</code><br />\n<code>divide=&quot;value&quot;</code></p>\n\n<p>- divide by “value”. <em>By default, this is integer division &amp; will always round down, e.g. 11 <span class=\"caps\">DIV</span> 5 = 2.</em> See <code>precision</code> for enabling floating point calculations (&amp; hence rounding up <em>or</em> down).</p>\n\n<p><code>mod=&quot;value&quot;</code></p>\n\n<p>- divide by “value” &amp; return the remainder.</p>\n\n<p><code>ceiling=&quot;boolean&quot;</code></p>\n\n<p>- used with <code>div</code> to round <em>up</em> the result, e.g. 11 <span class=\"caps\">DIV</span> 5 = 3. Default = “0” (don&#8217;t round up).</p>\n\n<p><code>precision=&quot;value&quot;</code></p>\n\n<p>- switch from integer to floating point calculation and, optionally, specify the precision in decimal places. Results rounded up or down accordingly. Default = not defined (integer calculation). To switch on floating point mode without specifying the number of decimal places, use <code>precision=&quot;&quot;</code>.</p>\n\n<p><code>result=&quot;var name&quot;</code></p>\n\n<p>- a <span class=\"caps\">TXP</span> variable to contain the result of the calculation. If used then the original variable, specified by <code>name</code> left unchanged. Default = “”.</p>\n\n<p><code>reset=&quot;value&quot;</code></p>\n\n<p>- the point at which the variable specified by <code>name</code> is reset to zero. Default = &#8220;&#8221; (no reset).</p>\n\n<p><code>reset_name=&quot;var name&quot;</code></p>\n\n<p>- a <span class=\"caps\">TXP</span> variable to count resets. Default = &#8220;&#8221; (no reset counter)</p>\n\n<h2><strong>Notes</strong></h2>\n\n<ul>\n	<li><strong>adi_calc</strong> assumes that supplied <span class=\"caps\">TXP</span> variables &amp; values are numeric, and no checks are made to verify this.</li>\n	<li>by default <strong>adi_calc</strong> produces integer results &#8211; in this mode, <code>div</code> will <em>always</em> round down &#8211; use <code>precision</code> for floating point results</li>\n	<li>you can use either the standard <code>&lt;txp:variable /&gt;</code> tag to set the variable&#8217;s initial value or use the <strong>adi_calc</strong> <code>value</code> attribute.</li>\n	<li>if a <span class=\"caps\">TXP</span> variable is not supplied or set, and the <code>value</code> attribute is not provided, then <strong>adi_calc</strong> uses zero as the initial value.</li>\n	<li>in container tag mode the value of the enclosed contents will override any supplied <span class=\"caps\">TXP</span> variable in <code>name</code></li>\n	<li>you can use <code>&lt;txp:if_variable&gt;</code> or <a href=\"http://forum.textpattern.com/viewtopic.php?id=25357\" rel=\"nofollow\">smd_if</a> to test variable values.</li>\n	<li>if neither the <code>name</code> nor <code>result</code> attributes are supplied then display mode is switched on automatically (i.e. <code>display=&quot;1&quot;</code>) &amp; no <span class=\"caps\">TXP</span> variable will contain the result</li>\n</ul>\n\n<h2><strong>Examples</strong></h2>\n\n<h3><em>Single tag mode</em></h3>\n\n<p><code>&lt;txp:variable name=&quot;test&quot; value=&quot;2&quot; /&gt;</code><br />\nset a variable called <i>test</i> to 2</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; add=&quot;5&quot; /&gt;</code><br />\nadds 5 to <i>test</i> (result = 7)</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; subtract=&quot;3&quot; /&gt;</code><br />\nsubtracts 3 from <i>test</i> (result = 4)</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; multiply=&quot;9&quot; /&gt;</code><br />\nmultiplies <i>test</i> by 9 (result = 36)</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; div=&quot;5&quot; /&gt;</code><br />\nis integer division by 5 (result = 7)</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; mod=&quot;2&quot; /&gt;</code><br />\nis the remainder when divided by 2 (result = 1)</p>\n\n<p>In the above sequence of calculations the setting of the variable and the first calculation can be combined using the <code>value</code> attribute:</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;test&quot; value=&quot;2&quot; add=&quot;5&quot; /&gt;</code><br />\nsets <i>test</i> to 2 and adds 5 to it (result = 7)</p>\n\n<p>Use <code>display=&quot;1&quot;</code> to automatically display the result of the calculation:</p>\n\n<p>There are <code>&lt;txp:adi_calc name=&quot;total&quot; value=&quot;60&quot; multiply=&quot;24&quot; display=&quot;1&quot; /&gt;</code> minutes in a day.</p>\n\n<p>And <span class=\"caps\">TXP</span> variable <i>total</i> will contain the result, i.e. 1440.</p>\n\n<h3><em>Container tag mode</em></h3>\n\n<p><code>&lt;txp:adi_calc name=&quot;x&quot; add=&quot;2&quot;&gt;4&lt;/txp:adi_calc&gt;</code><br />\ncontents (i.e. <i>4</i>) override initial value of supplied variable <i>x</i>, but the result <em>will</em> be stored in <i>x</i></p>\n\n<p><code>&lt;txp:adi_calc display=&quot;1&quot; add=&quot;2&quot; result=&quot;y&quot;&gt;</code><br />\n<code>	&lt;txp:variable name=&quot;x&quot;/&gt;</code><br />\n<code>&lt;/txp:adi_calc&gt;</code><br />\ninitial value taken from variable <i>x</i>, 2 added, result stored in <i>y</i> and displayed, <i>x</i> is unchanged</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;x&quot; add=&quot;2&quot;&gt;&lt;/txp:adi_calc&gt;</code><br />\ncontents of container empty, initial value taken from variable <i>x</i>, 2 added</p>\n\n<h3><em>Floating point calculations</em></h3>\n\n<p><code>&lt;txp:adi_calc name=&quot;float&quot; value=&quot;1&quot; div=&quot;3&quot; precision=&quot;&quot; /&gt;</code><br />\ndivides 1 by 3 (result = 0.33333333333)</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;float&quot; value=&quot;2&quot; div=&quot;3&quot; precision=&quot;2&quot; /&gt;</code><br />\n2 divided by 3, two decimal places (result = 0.67)</p>\n\n<h3><em>Well rounded</em></h3>\n\n<p><code>&lt;txp:adi_calc name=&quot;round_me_down&quot; /&gt;</code><br />\nwill round down the value in the supplied variable</p>\n\n<p><code>&lt;txp:variable name=&quot;pi&quot; value=&quot;3.14159265&quot; /&gt;</code><br />\n<code>&lt;txp:adi_calc name=&quot;pi&quot; precision=&quot;2&quot; /&gt;</code><br />\nwill round <i>pi</i> to 2 decimal places</p>\n\n<h3><em>No variables required</em></h3>\n\n<p>There are <code>&lt;txp:adi_calc value=&quot;60&quot; multiply=&quot;24&quot; /&gt;</code> minutes in a day.</p>\n\n<p><code>&lt;txp:adi_calc add=&quot;4&quot;&gt;20&lt;/txp:adi_calc&gt;</code> is two dozen.</p>\n\n<p>pi is roughly <code>&lt;txp:adi_calc value=&quot;22&quot; divide=&quot;7&quot; precision=&quot;2&quot; /&gt;</code></p>\n\n<h3><em>Using reset &amp; reset counter</em></h3>\n\n<p>Consider the following:</p>\n\n<p><code>&lt;txp:variable name=&quot;counter&quot; value=&quot;0&quot; /&gt;</code><br />\n<code>&lt;txp:variable name=&quot;reset_counter&quot; value=&quot;0&quot; /&gt;</code><br />\ncounter = 0, reset_counter = 0</p>\n\n<p><code>&lt;txp:adi_calc name=&quot;counter&quot; add=&quot;1&quot; reset=&quot;4&quot; reset_name=&quot;reset_counter&quot; /&gt;</code><br />\ncounter = 1, reset_counter = 0</p>\n\n<p>if the above <strong>adi_calc</strong> tag is repeated a further 6 times, then the sequence of values will be as follows:</p>\n\n<p>counter = 2, reset_counter = 0<br />\ncounter = 3, reset_counter = 0<br />\ncounter = 0, reset_counter = 1<br />\ncounter = 1, reset_counter = 1<br />\ncounter = 2, reset_counter = 1<br />\ncounter = 3, reset_counter = 1</p>\n\n<p>Each time <i>counter</i> reaches the value of 4, it is reset to zero and the reset counter is incremented. This can be useful when counting rows &amp; columns. In the above example, <i>reset_counter</i> would be <i>row</i> and <i>counter</i> would be <i>column</i>.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=29885\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_calc\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_calc - Calculator for TXP variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	1.2		- TXP 4.6 tag registration\n	1.1		- tidied up integer/floating point modes, default now strictly integer, floating point switched on with \'precision\' (thanks Bloke)\n			- \'div\' can be specified as \'divide\' (improve code readability)\n	1.0		- new attribute: \'precision\' to switch into floating point mode\n			- new attribute: \'result\' variable set to result of calculation, original variable left unchanged\n			- new attribute: \'reset\' to automatically reset value to zero\n			- new attribute: \'reset_name\' to count the number of resets\n			- enhancement: container mode can be used to set the initial value\n			- \'name\' attribute no longer mandatory\n			- no longer picky about lack of initial value - if all else fails use zero\n	0.3		- fix: error if adi_calc is the first person to set a TXP variable (thanks maniqui)\n			- new attribute: \'ceiling\' to always round up the result of integer division (for aswihart)\n	0.2		- new attribute: \'value\' to explicitly set an initial value to variable (for maniqui)\n			- new attribute: \'display\' to display result of the calculation (for maniqui)\n	0.1		- initial release\n\n	INVESTIGATE: variable( array(\'name\' => \'something\', \'value\' => 42) );\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_calc\')\n	;\n}\n\nfunction adi_calc($atts,$thing=NULL) {\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// TXP variable name\n		\'value\'			=> \'\',	// set initial value of variable\n		\'display\'		=> \'0\',	// display the variable\'s value (after the calculation)\n		\'add\'			=> \'\',	// name = name + value\n		\'subtract\'		=> \'\',	// name = name - value\n		\'multiply\'		=> \'\',	// name = name * value\n		\'div\'			=> \'\',	// name = name / value\n		\'divide\'		=> \'\',	// name = name / value (equivalent to div)\n		\'mod\'			=> \'\',	// name = name MOD value (remainder)\n		\'ceiling\'		=> \'0\',	// used with integer division to always round up the result\n		\'precision\'		=> \'\',	// number of decimal places (floating point mode)\n		\'reset\'			=> \'0\', // the point at which the variable is reset to zero\n		\'reset_name\'	=> \'\',	// additional TXP variable name (for counting resets)\n		\'result\'		=> \'\',	// additional TXP variable to contain result, original variable (i.e. $name) unchanged\n\n	), $atts));\n\n	// tweak attributes\n	if (array_key_exists(\'divide\', $atts))\n		$div = $divide;\n\n	// check if any TXP variable has ever been set ever, ever, ever (else get error)\n	if (!isset($GLOBALS[\'variable\']))\n		$GLOBALS[\'variable\'] = array();\n\n	// get/set initial value\n	if ($thing === NULL) { // self closing mode\n		if (!array_key_exists(\'value\', $atts)) // TXP variable \'value\' attribute not supplied\n			if (array_key_exists($name,$GLOBALS[\'variable\'])) // use TXP variable value from \'name\' attribute\n				$value = $GLOBALS[\'variable\'][$name];\n			else\n				$value = 0; // default to zero\n	}\n	else { // container mode\n		$value = trim(parse($thing));\n		if (empty($value)) {\n			if (array_key_exists($name,$GLOBALS[\'variable\'])) // use TXP variable value from \'name\' attribute\n				$value = $GLOBALS[\'variable\'][$name];\n			else\n				$value = 0; // default to zero\n		}\n	}\n\n	// cope with a lack of \'name\' attribute\n	if (!$name) { // name not supplied\n		$display = \'1\'; // force display mode\n		$name = \'adi_calc_temp_name\'; // assign dumy name, else GLOBAL variables array gets an element called \'\'\n	}\n\n	// let\'s do it\n	if ($reset_name) // set reset counter\n		if (array_key_exists($reset_name,$GLOBALS[\'variable\'])) // use TXP variable value from \'reset_name\' attribute\n			$reset_value = $GLOBALS[\'variable\'][$reset_name];\n		else\n			$reset_value = $GLOBALS[\'variable\'][$reset_name] = 0;\n	if ($add)\n		$value = $value + $add;\n	if ($subtract)\n		$value = $value - $subtract;\n	if ($multiply)\n		$value = $value * $multiply;\n	if ($div)\n		$value = $value / $div;\n	if ($mod)\n		$value = $value % $mod;\n	if ($reset)\n		if ($value == $reset) {\n			$value = 0;\n			if ($reset_name)\n				$GLOBALS[\'variable\'][$reset_name] = $reset_value + 1; // increment reset counter\n		}\n\n	// massage the result\n	if (array_key_exists(\'precision\', $atts)) { // precision attribute supplied, so avoid integer mode\n		if ($precision != \'\')\n			$value = round($value, $precision); // round up/down\n	}\n	else // integer\n		if ($ceiling)\n			$value = (int)ceil($value);\n		else\n			$value = (int)($value);\n\n	// ta-da!\n	if ($result)\n		$GLOBALS[\'variable\'][$result] = $value; // set TXP variable \'result\' to new value\n	else\n		$GLOBALS[\'variable\'][$name] = $value; // set TXP variable \'name\' to new value\n	if ($display)\n		return $value;\n\n}\n', '\n/*\n	adi_calc - Calculator for TXP variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	1.2		- TXP 4.6 tag registration\n	1.1		- tidied up integer/floating point modes, default now strictly integer, floating point switched on with \'precision\' (thanks Bloke)\n			- \'div\' can be specified as \'divide\' (improve code readability)\n	1.0		- new attribute: \'precision\' to switch into floating point mode\n			- new attribute: \'result\' variable set to result of calculation, original variable left unchanged\n			- new attribute: \'reset\' to automatically reset value to zero\n			- new attribute: \'reset_name\' to count the number of resets\n			- enhancement: container mode can be used to set the initial value\n			- \'name\' attribute no longer mandatory\n			- no longer picky about lack of initial value - if all else fails use zero\n	0.3		- fix: error if adi_calc is the first person to set a TXP variable (thanks maniqui)\n			- new attribute: \'ceiling\' to always round up the result of integer division (for aswihart)\n	0.2		- new attribute: \'value\' to explicitly set an initial value to variable (for maniqui)\n			- new attribute: \'display\' to display result of the calculation (for maniqui)\n	0.1		- initial release\n\n	INVESTIGATE: variable( array(\'name\' => \'something\', \'value\' => 42) );\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_calc\')\n	;\n}\n\nfunction adi_calc($atts,$thing=NULL) {\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// TXP variable name\n		\'value\'			=> \'\',	// set initial value of variable\n		\'display\'		=> \'0\',	// display the variable\'s value (after the calculation)\n		\'add\'			=> \'\',	// name = name + value\n		\'subtract\'		=> \'\',	// name = name - value\n		\'multiply\'		=> \'\',	// name = name * value\n		\'div\'			=> \'\',	// name = name / value\n		\'divide\'		=> \'\',	// name = name / value (equivalent to div)\n		\'mod\'			=> \'\',	// name = name MOD value (remainder)\n		\'ceiling\'		=> \'0\',	// used with integer division to always round up the result\n		\'precision\'		=> \'\',	// number of decimal places (floating point mode)\n		\'reset\'			=> \'0\', // the point at which the variable is reset to zero\n		\'reset_name\'	=> \'\',	// additional TXP variable name (for counting resets)\n		\'result\'		=> \'\',	// additional TXP variable to contain result, original variable (i.e. $name) unchanged\n\n	), $atts));\n\n	// tweak attributes\n	if (array_key_exists(\'divide\', $atts))\n		$div = $divide;\n\n	// check if any TXP variable has ever been set ever, ever, ever (else get error)\n	if (!isset($GLOBALS[\'variable\']))\n		$GLOBALS[\'variable\'] = array();\n\n	// get/set initial value\n	if ($thing === NULL) { // self closing mode\n		if (!array_key_exists(\'value\', $atts)) // TXP variable \'value\' attribute not supplied\n			if (array_key_exists($name,$GLOBALS[\'variable\'])) // use TXP variable value from \'name\' attribute\n				$value = $GLOBALS[\'variable\'][$name];\n			else\n				$value = 0; // default to zero\n	}\n	else { // container mode\n		$value = trim(parse($thing));\n		if (empty($value)) {\n			if (array_key_exists($name,$GLOBALS[\'variable\'])) // use TXP variable value from \'name\' attribute\n				$value = $GLOBALS[\'variable\'][$name];\n			else\n				$value = 0; // default to zero\n		}\n	}\n\n	// cope with a lack of \'name\' attribute\n	if (!$name) { // name not supplied\n		$display = \'1\'; // force display mode\n		$name = \'adi_calc_temp_name\'; // assign dumy name, else GLOBAL variables array gets an element called \'\'\n	}\n\n	// let\'s do it\n	if ($reset_name) // set reset counter\n		if (array_key_exists($reset_name,$GLOBALS[\'variable\'])) // use TXP variable value from \'reset_name\' attribute\n			$reset_value = $GLOBALS[\'variable\'][$reset_name];\n		else\n			$reset_value = $GLOBALS[\'variable\'][$reset_name] = 0;\n	if ($add)\n		$value = $value + $add;\n	if ($subtract)\n		$value = $value - $subtract;\n	if ($multiply)\n		$value = $value * $multiply;\n	if ($div)\n		$value = $value / $div;\n	if ($mod)\n		$value = $value % $mod;\n	if ($reset)\n		if ($value == $reset) {\n			$value = 0;\n			if ($reset_name)\n				$GLOBALS[\'variable\'][$reset_name] = $reset_value + 1; // increment reset counter\n		}\n\n	// massage the result\n	if (array_key_exists(\'precision\', $atts)) { // precision attribute supplied, so avoid integer mode\n		if ($precision != \'\')\n			$value = round($value, $precision); // round up/down\n	}\n	else // integer\n		if ($ceiling)\n			$value = (int)ceil($value);\n		else\n			$value = (int)($value);\n\n	// ta-da!\n	if ($result)\n		$GLOBALS[\'variable\'][$result] = $value; // set TXP variable \'result\' to new value\n	else\n		$GLOBALS[\'variable\'][$name] = $value; // set TXP variable \'name\' to new value\n	if ($display)\n		return $value;\n\n}\n', '0c4a2abf9f5316d8b7e1bdcc187d5d76', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_cat_menu', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.6', 'Category & article menu', '<h1><strong>adi_cat_menu</strong> &#8211; Category &amp; article menu</h1>\n\n<p>This will produce a menu of categories and their articles.</p>\n\n<p>The category list can be defined according to a parent category, a user supplied list or a section.</p>\n\n<h2><strong>Installation</strong></h2>\n\n<p>1. Download and install the plugin in the usual way.</p>\n\n<p>2. To use with standard <span class=\"caps\">TXP</span> categories, create a new form of type “article”, called <i>adi_cat_menu_articles</i>, and fill it with:</p>\n\n<pre><code>&lt;li&lt;txp:adi_cat_menu_if&gt; class=&quot;active_class&quot;&lt;/txp:adi_cat_menu_if&gt;&gt;\n&lt;txp:permlink&gt;&lt;txp:title/&gt;&lt;/txp:permlink&gt;\n&lt;/li&gt;</code></pre>\n\n<p>3. To use with <code>rss_unlimited_categories</code>, create a new form of type “article”, called <i>adi_cat_menu_rss_articles</i>, and fill it with:</p>\n\n<pre><code>&lt;li&gt;\n&lt;txp:rss_sct_permlink&gt;&lt;txp:title/&gt;&lt;/txp:rss_sct_permlink&gt;\n&lt;/li&gt;</code></pre>\n\n<p><em>If you have a requirement to generate article lists in more than one format then make use of the <code>article_form</code> and <code>rss_article_form</code> attributes.</em></p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Place the <code>&lt;txp:adi_cat_menu /&gt;</code> tag wherever you want the menu to appear.  By default, <strong>adi_cat_menu</strong> will produce a menu containing all article categories together with the articles that belong to them. A parent category can be specified, using the <code>parent=&quot;category name&quot;</code> attribute, to restrict the list.</p>\n\n<p>With normal <span class=\"caps\">TXP</span> categories, the standard <code>&lt;txp:article_custom /&gt;</code> tag is used to present the lists of articles. <code>&lt;txp:article_custom /&gt;</code> attributes may be specified using the <code>article_attr</code> attribute and are documented in the <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:article_custom_/\" rel=\"nofollow\">Textbook</a>.</p>\n\n<p>With <code>&lt;txp:rss_unlimited_categories /&gt;</code>, the <code>&lt;txp:rss_unlimited_categories_article_list /&gt;</code> tag is used to present the lists of articles. Attributes for this tag are specified using the <code>article_attr</code> attribute and are documented on <a href=\"http://www.wilshireone.com/textpattern-plugins/rss-unlimited-categories#rss_unlimited_categories_article_list\" rel=\"nofollow\">wilshire|one</a>.</p>\n\n<p>If you want the output to be “section sensitive”, where only categories that contain articles in a specified section are displayed &#8211; then use <code>section_sensitive=&quot;1&quot;</code>.</p>\n\n<h2><strong>Well Sorted</strong></h2>\n\n<p>By default, <strong>adi_cat_menu</strong> will sort the category list according to <code>&quot;name asc&quot;</code> &#8211; that is, the category name (as opposed to its title) in ascending alphabetical order.</p>\n\n<p>To sort the category list in other ways use the attribute <code>sort=&quot;...&quot;</code>, where <code>&quot;...&quot;</code> is defined in the <strong>Attributes</strong> section below.</p>\n\n<p>Sort articles using the <code>article_attr=&quot;...&quot;</code> attribute. This passes attributes directly to <code>&lt;txp:article_custom /&gt;</code> or <code>&lt;txp:rss_unlimited_categories_article_list /&gt;</code> tags depending which mode you&#8217;re using. Be aware that, in default mode, you should use <code>sort=&quot;title asc&quot;</code> but in rss_unlimited_categories mode (<code>rss_unlimited=&quot;1&quot;</code>) use <code>sortby=&quot;title&quot; sortdir=&quot;asc&quot;</code>.</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>parent=&quot;category name&quot;</code></p>\n\n<p>- the parent category. Default = &#8220;&#8221; (all categories, parents &amp; children).</p>\n\n<p><code>categories=&quot;category name(s)&quot;</code></p>\n\n<p>- comma separated list of category names to include in the list. Default = &#8220;&#8221; (include all).</p>\n\n<p><code>exclude=&quot;category name(s)&quot;</code></p>\n\n<p>- comma separated list of category names which will be excluded from the list. Default = &#8220;&#8221; (exclude none).</p>\n\n<p><code>sort=&quot;sort values&quot;</code></p>\n\n<p>- how to sort the category list. Default is “name asc” (alphabetical order). Other options: “id”, “name”, “type”, “parent”, “title”.</p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- class applied to the top level <code>&lt;ul&gt;</code>. Default = “cat_menu”.</p>\n\n<p><code>menu_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the top level <code>&lt;ul&gt;</code>.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>active_class=&quot;class name&quot;</code></p>\n\n<p>- class applied to active category <code>&lt;li&gt;</code>. Default = “active_class”.</p>\n\n<p><i>Notes:</i><br />\n<i>1. In individual article display mode the class is <em>not</em> applied by default to the parent category (but see active_parent attribute).</i><br />\n<i>2. The class applied to active article <code>&lt;li&gt;</code> in the rendered menu defaults to “active_class” also, but can only be changed by editing the <strong>adi_cat_menu_articles</strong> form.</i><br />\n<i>3. <code>adi_cat_menu</code> uses <code>rss_unlimited_categories_article_list</code> to output the lists of articles. Currently these do not contain the active article so “active_class” does not apply in this case.</i></p>\n\n<p><code>active_parent=&quot;boolean&quot;</code></p>\n\n<p>- active class applied to parent category <code>&lt;li&gt;</code> of currently active article. Default = “0” (class not applied).</p>\n\n<p><code>list_id=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the <code>&lt;li&gt;</code> elements should have unique IDs applied. IDs are based on the section names. Default = “0” (No).</p>\n\n<p><code>list_id_prefix=&quot;text&quot;</code></p>\n\n<p>- the prefix to be used for the <code>&lt;li&gt;</code> IDs. Default = “menu_”.</p>\n\n<p><code>section=&quot;section name&quot;</code></p>\n\n<p>- generate category links to specified section. Default = &#8220;&#8221; (no section restriction). List of articles is also restricted to those belonging to the specified section.</p>\n\n<p><code>this_section=&quot;boolean&quot;</code></p>\n\n<p>- generate category links to currently active section. Default = “no”. List of articles is also restricted to those belonging to the current section. <em>Note that this overrides the <code>section</code> attribute.</em></p>\n\n<p><code>section_sensitive=&quot;boolean&quot;</code></p>\n\n<p>- generate category links which have articles in the currently active section. Default = “0” (not section sensitive).</p>\n\n<p><code>list_empty_cats=&quot;boolean&quot;</code></p>\n\n<p>- list categories which don&#8217;t have any articles. Default = “1” (yes).</p>\n\n<p><code>active_only=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether to only show articles in currently active category. Default = “0” (show articles in all categories).</p>\n\n<p><code>messy_url=&quot;boolean&quot;</code></p>\n\n<p>- force links to be output in messy <span class=\"caps\">URL</span> format. Default = “0” (output links according to Permanent Link Mode setting in <span class=\"caps\">TXP</span> Preferences).</p>\n\n<p><code>link=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether to output categories as links. Default = “1” (output links). If set to “0” then only the categories titles will be output.</p>\n\n<p><code>article_attr=\'article_custom attributes\'</code></p>\n\n<p>- a list of attributes to be passed through to <code>&lt;txp:article_custom /&gt;</code> or <code>&lt;txp:rss_unlimited_categories_article_list /&gt;</code> to control the appearance of articles (depends on <code>rss_unlimited</code> setting below). Attributes/values should be separated by spaces. Default = &#8216;&#8217; (i.e. <code>&lt;txp:article_custom /&gt;</code> or <code>&lt;txp:rss_unlimited_categories_article_list /&gt;</code> defaults). <strong>Note the use of single quotes</strong>. For example: <code>article_attr=\'sort=&quot;Title&quot; limit=&quot;20&quot;\'</code> or <code>rss_unlimited=&quot;1&quot; article_attr=\'sortby=&quot;Title&quot; limit=&quot;20&quot;\'</code></p>\n\n<p><code>article_form=&quot;form name&quot;</code></p>\n\n<p>- the form to use when listing articles. Default = “adi_cat_menu_articles”.</p>\n\n<p><code>rss_unlimited=&quot;boolean&quot;</code></p>\n\n<p>- enables compatibility with <code>rss_unlimited_categories</code>. Default = “0” (standard <span class=\"caps\">TXP</span> categories only).</p>\n\n<p><code>rss_article_form =&quot;form name&quot;</code></p>\n\n<p>- the form to use when listing articles in rss_unlimited_categories compatibility mode. Default = “adi_cat_menu_rss_articles”.</p>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the whole menu. Default = &#8220;&#8221; (no tag). <em>If there is no menu to output then no wraptag is generated.</em></p>\n\n<p><code>wraptag_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the wraptag. Default = “menu_wrapper”.</p>\n\n<p><code>wraptag_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the wraptag. Default = &#8220;&#8221; (none).</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p><code>&lt;txp:adi_cat_menu parent=&quot;daddy&quot; article_attr=\'sort=&quot;title&quot; limit=&quot;12&quot;\' /&gt;</code></p>\n\n<p>In the above example:</p>\n\n<ul>\n	<li>only child categories belonging to parent category “daddy” will be listed</li>\n	<li>the class on the top level <code>&lt;ul&gt;</code> will be “cat_menu”</li>\n	<li>the class on the active <code>&lt;li&gt;</code> (category or article) will be “active_class”</li>\n	<li>the articles will be sorted according to their titles</li>\n	<li>the number of articles per category will be limited to 12</li>\n</ul>\n\n<p><code>&lt;txp:adi_cat_menu article_attr=\'sort=&quot;title asc&quot;\' this_section=&quot;1&quot; section_sensitive=&quot;1&quot; /&gt;</code></p>\n\n<p>The above will:</p>\n\n<ul>\n	<li>create a menu of categories that contain articles belonging to the current section</li>\n	<li>list the categories in alphabetical order according to their titles</li>\n</ul>\n\n\n<h2><strong>Styling</strong></h2>\n\n<p>Some <span class=\"caps\">CSS</span> serving suggestions:</p>\n\n<pre><code>/* to set default link colour */\nul.cat_menu a { color: black }</code></pre>\n\n<pre><code>/* to set active link colour */\nul.cat_menu .active_class a { color: red }\nul.cat_menu .active_class ul a { color: black }</code></pre>\n\n<h2><strong>Credits</strong></h2>\n\n<p>Thanks to <a href=\"http://www.graphicpush.com/\" rel=\"nofollow\">Kevin Potts</a> for sponsoring this plugin. I&#8217;m now the proud owner of a signed copy of his book: <a href=\"http://www.graphicpush.com/books/web-design-and-marketing-solutions-for-business-websites\" rel=\"nofollow\">Web Design and Marketing Solutions</a>.</p>\n\n<p>Thanks also to <a href=\"http://jasondas.com/\" rel=\"nofollow\">Jason Das</a> (floodfish) for sponsoring the ‘list_empty_cats’ feature.</p>\n\n<p>Respect and apologies to <a href=\"http://www.digitalia.be/\" rel=\"nofollow\">Christophe Beyls</a> for the code that I&#8217;ve stolen from <a href=\"http://forum.textpattern.com/viewtopic.php?id=11843\" rel=\"nofollow\">cbs_category_list</a> to implement the section sensitivity gubbins.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=28531\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_cat_menu\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_cat_menu - Category & article menu\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- TXP 4.6 tag registration\n	0.5		- new attribute: \'list_empty_cats\' (for floodfish)\n			- new attributes \'list_id\' & \'list_id_prefix\' (for floodfish)\n			- new attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n			- new attribute: \'active_parent\' (for kbarlow)\n	0.4		- new adi_cat_menu attributes: \'link\' (for jpdupont), \'article_form\' & \'rss_article_form\' (for floodfish)\n			- enhancement: extended \'categories\' & \'exclude\' functionality to section sensitive mode\n			- fix: suppress empty <ul></ul> (e.g. when no categories found in section sensitive mode) (for jpdupont)\n	0.3		- enhancement: restored \'section\', \'this_section\' attributes & extended functionality to cover article lists\n			- enhancement: restored \'exclude\' attribute functionality\n			- enhancement: restored \'categories\' attribute functionality\n			- new adi_cat_menu attribute: \'section-sensitive\'\n	0.2		- enhancement: rss_unlimited_categories mode\n			- help: updated instructions regarding article <li> active class\n			- help: documented \"sort\' attribute\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_cat_menu\')\n		->register(\'adi_cat_menu_if\')\n	;\n}\n\nglobal $thing,$atts,$txp407;\n\n$txp407 = version_compare($prefs[\'version\'], \'4.0.7\', \'>=\');\n\nfunction adi_cat_menu_articles($category,$wraptag=\'\',$article_attr=\'\') {\n// retrieve articles using <txp:article_custom /> or <txp:rss_unlimited_categories_article_list /> tags\n	global $rss_unlimited,$s,$section,$this_section,$txp407,$article_form,$rss_article_form;\n	$article_attr == \'\' ? // any article_custom attributes specified?\n		$attr = \'\' :\n		$attr = \' \'.$article_attr;\n	// set default \"all sections\" attribute\n	if ($rss_unlimited)\n		$section_attribute = \' section=\"*\"\';\n	else\n		$section_attribute = \'\';\n	if ($section) // explicit section specified\n		$section_attribute = \' section=\"\'.$section.\'\"\';\n	if ($this_section) // current section required\n		$section_attribute = \' section=\"\'.$s.\'\"\';\n	$rss_unlimited ?\n		$article_list = trim(parse(\'<txp:rss_unlimited_categories_article_list category=\"\'.$category.\'\"\'.$attr.\' \'.$section_attribute.\' form=\"\'.$rss_article_form.\'\" />\')) :\n		$article_list = trim(parse(\'<txp:article_custom category=\"\'.$category.\'\"\'.$attr.\' \'.$section_attribute.\' form=\"\'.$article_form.\'\" />\'));\n	if ($article_list == \"\")\n		return \'\';\n	else // articles found\n		if ($wraptag == \"\")\n			return $article_list;\n		else\n			return tag($article_list,$wraptag,\'\');\n}\n\nfunction adi_cat_menu_if($atts, $thing) {\n// for use in the Form to identify the active article\n// copy of TXP 4.0.7 if_article_id\n	global $thisarticle, $pretext;\n	assert_article();\n	extract(lAtts(array(\n		\'id\' => $pretext[\'id\'],\n	), $atts));\n	if ($id)\n		return parse(EvalElse($thing, in_list($thisarticle[\'thisid\'], $id)));\n}\n\nfunction adi_cat_menu($atts) {\n// <txp:adi_cat_menu /> tag\n	global $prefs,$s,$c,$thisarticle,$rss_unlimited,$section,$this_section,$txp407,$article_form,$rss_article_form;\n\n	extract(lAtts(array(\n		\'class\'				=> \'cat_menu\',		// class applied to top level <ul>\n		\'menu_id\'			=> \'\',				// CSS ID for top level <ul>\n		\'active_class\'		=> \'active_class\',	// class applied to active <li>\n		\'parent\'			=> \'\',				// category parent\n		\'categories\'		=> \'\',				// list of categories to include\n		\'exclude\'			=> \'\',				// list of categories to exclude\n		\'restrict\'			=> \'\',				// category1 or category2\n		\'sort\'				=> \'\',				// category sort options\n		\'link\'				=> \'1\',				// output categories as links\n		\'messy_url\'			=> \'0\',				// force links to be output in messy URL format\n		\'list_id\'			=> \'0\',				// output <li> IDs or not\n		\'list_id_prefix\'	=> \'cat_menu_\',		// <li> ID prefix\n		\'article_attr\'		=> \'\',				// attributes to pass to article_custom\n		\'article_form\'		=> \'adi_cat_menu_articles\', // form to use with article_custom\n		\'active_only\'		=> \'0\',				// only show articles in currently active category\n		\'active_parent\'		=> \'0\',				// apply active class to category <li> when in individual article mode (SHOULD BE DEFAULT BEHAVIOUR?)\n		\'rss_unlimited\'		=> \'0\',				// rss_unlimited_categories mode\n		\'rss_article_form\'	=> \'adi_cat_menu_rss_articles\', // form to use with rss_unlimited_categories_article_list\n		\'section\'			=> \'\',				// link categories to specified section\n		\'this_section\'		=> \'\',				// link categories to current section (overrides \'section\')\n		\'section_sensitive\'	=> \'0\',				// display categories only that have articles belonging to specified \'section\' or \'this_section\'\n		\'list_empty_cats\'	=> \'1\',				// display empty categories (i.e. categories that have no articles)\n		\'type\'				=> \'article\',		// leftover from TXP category_list\n		\'wraptag\'			=> \'\',				// wrap a tag around the menu\n		\'wraptag_class\'		=> \'menu_wrapper\',	// class for wraptag\n		\'wraptag_id\'		=> \'\',				// id for wraptag\n		\'debug\'				=> \'0\',\n	), $atts));\n\n	// set up the basics\n	//$break = \'li\';\n	//$wraptag = \'ul\';\n	$sort = doSlash($sort);\n	$include_list = \'\';\n	if ($categories) {\n		$include_list = do_list($categories);\n		$include_list = join(\"\',\'\",doSlash($include_list));\n	}\n	$exclude_list = \'\';\n	if ($exclude) {\n		$exclude_list = do_list($exclude);\n		$exclude_list = join(\"\',\'\",doSlash($exclude_list));\n	}\n\n	// debug action\n	if ($debug) {\n		echo \"VERSIONS\";\n		echo \'<pre>\';\n		$version = safe_field(\"version\", \"txp_plugin\", \"name=\'adi_cat_menu\'\");\n		$status = safe_field(\"status\", \"txp_plugin\", \"name=\'adi_cat_menu\'\");\n		echo __FUNCTION__.\': \'.(empty($version)?\'not installed\':$version.($status?\' (active)\':\' (not active)\')).\'<br/>\';\n		echo \'TXP: \'.$prefs[\'version\'].($txp407 ? \' (4.0.7+)\' : \' (pre-4.0.7)\').\'<br/>\';\n		echo \'PHP: \'.phpversion().\'<br/>\';\n		echo \'MySQL: \'.mysql_get_server_info();\n		echo \'</pre>\';\n		echo \'SUPPLIED ATTRIBUTES:\';\n		dmp($atts);\n		echo \"CURRENT CATEGORY = $c<br/>\";\n		echo \"CURRENT SECTION = $s<br/>\";\n		echo \"INITIAL INCLUDE LIST = $include_list<br/>\";\n		echo \"INITIAL EXCLUDE LIST = $exclude_list<br/>\";\n	}\n\n	// here we go\n	if ($section_sensitive) {\n		if ($this_section) // current section required, override $section\n			$section = $s;\n		// set some cbs_category_list attribute values\n		$showcount = FALSE;\n		$sticky = FALSE;\n		$posted = \'\';\n		// some clever stuff from cbs_category_list\n		if ($section == \'default\') {\n			$table = \', `\'.PFX.\'txp_section` AS s\';\n			$sqlsection = \'s.name AND s.on_frontpage = 1\';\n		}\n		else {\n			$table = \'\';\n			$sqlsection = \"\'$section\'\";\n		}\n		// process cbs_category_list attributes\n		$parent = ($parent) ? \" AND c.parent = \'$parent\'\" : \'\';\n		$showcount = ($showcount == \'true\') ? \', COUNT(*) AS count\' : \'\';\n		$sticky = ($sticky == \'true\') ? \'5\' : \'4\';\n		switch($posted) {\n			case \'all\':\n				$posted = \'\';\n				break;\n			case \'future\':\n				$posted = \' AND t.Posted >= now()\';\n				break;\n			default:\n				$posted = \' AND t.Posted < now()\';\n		}\n		// I did these bits!\n		$sort ?\n			$sort = \' ORDER BY c.\'.$sort :\n			$sort = \' ORDER BY c.name asc\';\n		if ($include_list)\n			$include_list = \" AND c.name IN (\'$include_list\')\";\n		if ($exclude_list)\n			$exclude_list = \" AND c.name NOT IN (\'$exclude_list\')\";\n		// some magic SQL from cbs_category_list (with some include/exclude extras)\n		$rs = startRows(\n			\'SELECT c.name, c.title\'.$showcount.\' FROM `\'.PFX.\'txp_category` AS c, `\'.PFX.\'textpattern` AS t\'.$table\n			.\' WHERE (c.name = t.Category1 OR c.name = t.Category2)\'\n			.$include_list\n			.$exclude_list\n			.\' AND c.type = \\\'article\\\' AND t.Section = \'.$sqlsection.$parent.$posted\n			.\' AND t.Status = \'.$sticky.\' GROUP BY c.name\'.$sort\n			,$debug);\n	}\n	else { // not section sensitive\n		if ($parent) // exclude parent from list\n			if ($exclude_list) // combine parent & exclude list\n				$exclude_list = $exclude_list.\',\'.$parent;\n			else // just exclude parent\n				$exclude_list = $parent;\n		if ($debug) echo \"NEW EXCLUDE LIST = $exclude_list\";\n		// based on code from TXP 4.0.6 category_list tag\n		if ($exclude_list) {\n			$exclude_list = \"AND name NOT IN (\'$exclude_list\')\";\n		}\n		if ($parent) {\n			$qs = safe_row(\'lft, rgt\', \'txp_category\', \"name = \'\".doSlash($parent).\"\'\",$debug);\n			if ($qs) {\n				extract($qs);\n				$rs = safe_rows_start(\'name, title\', \'txp_category\',\n					\"(lft between $lft and $rgt) and type = \'\".doSlash($type).\"\' and name != \'default\' $exclude_list order by \".($sort ? $sort : \'lft ASC\'),$debug);\n			}\n		}\n		else if ($include_list) { // explicit list of categories supplied\n			$rs = safe_rows_start(\'name, title\', \'txp_category\',\n				\"type = \'\".doSlash($type).\"\' and name in (\'$include_list\') order by \".($sort ? $sort : \"field(name, \'$include_list\')\"),$debug);\n		}\n		else { // empty parent attribute (= all categories)\n			$rs = safe_rows_start(\'name, title\', \'txp_category\',\n				\"type = \'$type\' and name not in(\'default\',\'root\') $exclude_list order by \".($sort ? $sort : \'name ASC\'),$debug);\n		}\n	}\n\n	// if parent category not found then we fail in same way as TXP category_list with \"Undefined variable: rs\" - COULD DO BETTER?\n	if ($rs) {\n		$out = array();\n		empty($class) ? // set up <ul> class\n			$ul_class = \'\' :\n			$ul_class = \' class=\"\'.$class.\'\"\';\n		empty($menu_id) ? // set up <ul> ID\n			$ul_id = \'\' :\n			$ul_id = \' id=\"\'.$menu_id.\'\"\';\n		$empties = \'\';\n\n		// determine which categories current article belongs to (if individual article mode) or generate emmpty list if not\n		$article_cats = array();\n		if (!empty($thisarticle)) { // individual article being displayed\n			if ($debug) {\n				echo \'CURRENT ARTICLE:\';\n				dmp($thisarticle);\n			}\n			// get current article\'s category(ies) (single article) (from TXP if_article_category tag)\n			if (!empty($thisarticle[\'category1\'])) $article_cats[] = $thisarticle[\'category1\'];\n			if (!empty($thisarticle[\'category2\'])) $article_cats[] = $thisarticle[\'category2\'];\n			$article_cats = array_unique($article_cats);\n			if ($debug) {\n				echo \'CURRENT ARTICLE\\\'S CATEGORIES (\'.$thisarticle[\'title\'].\'):\';\n				dmp($article_cats);\n			}\n		}\n\n		// work through retrieved list of categories\n		while ($a = nextRow($rs)) {\n			extract($a); // extract category\'s $name & $title\n			if ($debug) {\n				echo \'CATEGORY \'.$title.\':\';\n				dmp($a);\n			}\n			if ($name) { // i.e. category name\n				// set section for category link (if this_section set then current section, else use supplied section)\n				$section = ($this_section) ? ( $s == \'default\' ? \'\' : $s ) : $section; // note: default = \'\'\n				$title = htmlspecialchars($title);\n\n				// determine if current category is active (can\'t tell if individual article being displayed)\n				// note use of strcasecmp - category names can be in different case if permanent link mode in TXP 4.0.5 is not messy!\n				$active_category = (strcasecmp($c, $name) == 0);\n\n				// determine if category is parent to individual article\n				$parent_category = FALSE;\n				if (!empty($article_cats)) { // contains individual article\'s categories (if any) - empty if not individual article display mode\n					$parent_category = (!(array_search($name,$article_cats) === FALSE));\n					if ($debug && $parent_category) echo \'PARENT OF CURRENT ARTICLE<br/><br/>\';\n				}\n\n				// set <li> class: IF active_class attribute set AND (category currently active OR (active_parent_category attribute set AND category is parent of active individual article))\n				$li_class = ($active_class AND ($active_category OR ($active_parent AND $parent_category))) ?\n					\' class=\"\'.$active_class.\'\"\' :\n					\'\';\n				$list_id ?\n					$li_id = \' id=\"\'.$list_id_prefix.$title.\'\"\' :\n					$li_id = \'\';\n				$li_tag = \'<li\'.$li_class.$li_id.\'>\';\n				$messy_url ?\n					$cat_url = hu.\'?c=\'.$name : // messy URL\n					$cat_url = pagelinkurl(array(\'s\' => $section, \'c\' => $name)); // render link according to Permanent Link Mode\n				$link ?\n					$a_tag = \'<a href=\"\'.$cat_url.\'\">\'.$title.\'</a>\' :\n					$a_tag = $title;\n\n				// determine whether to list articles or not\n				$list_articles = TRUE; // default setting\n				if ($active_only)\n					$list_articles = $active_category || $parent_category; // list articles if category is active or is parent of current individual article\n				if ($list_articles) {\n					$articles_found = trim(adi_cat_menu_articles($name,\'ul\',$article_attr));\n					if (!$articles_found && $debug) $empties .= \' \'.$title;\n				}\n				else\n					$articles_found = \'\';\n				if ($list_empty_cats || (!$list_empty_cats && $articles_found))  // generate category list item\n					$out[] = $li_tag.$a_tag.$articles_found.\'</li>\';\n			}\n			if ($debug) echo \'<hr />\';\n		}\n\n		if ($debug) $empties = \'<li>[Empties: \'.$empties.\']</li>\';\n\n		if ($out) { // it\'s a wrap\n			$out = \'<ul\'.$ul_class.$ul_id.\'>\'.$empties.join($out).\'</ul>\';\n			if ($wraptag) { // double wrapped for your protection\n				$wrap_attr = \'\';\n				if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n				if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n				$out = tag($out,$wraptag,$wrap_attr);\n			}\n			return $out;\n		}\n		else\n			return \'\';\n\n	}\n	return \'\';\n}\n', '\n/*\n	adi_cat_menu - Category & article menu\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- TXP 4.6 tag registration\n	0.5		- new attribute: \'list_empty_cats\' (for floodfish)\n			- new attributes \'list_id\' & \'list_id_prefix\' (for floodfish)\n			- new attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n			- new attribute: \'active_parent\' (for kbarlow)\n	0.4		- new adi_cat_menu attributes: \'link\' (for jpdupont), \'article_form\' & \'rss_article_form\' (for floodfish)\n			- enhancement: extended \'categories\' & \'exclude\' functionality to section sensitive mode\n			- fix: suppress empty <ul></ul> (e.g. when no categories found in section sensitive mode) (for jpdupont)\n	0.3		- enhancement: restored \'section\', \'this_section\' attributes & extended functionality to cover article lists\n			- enhancement: restored \'exclude\' attribute functionality\n			- enhancement: restored \'categories\' attribute functionality\n			- new adi_cat_menu attribute: \'section-sensitive\'\n	0.2		- enhancement: rss_unlimited_categories mode\n			- help: updated instructions regarding article <li> active class\n			- help: documented \"sort\' attribute\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_cat_menu\')\n		->register(\'adi_cat_menu_if\')\n	;\n}\n\nglobal $thing,$atts,$txp407;\n\n$txp407 = version_compare($prefs[\'version\'], \'4.0.7\', \'>=\');\n\nfunction adi_cat_menu_articles($category,$wraptag=\'\',$article_attr=\'\') {\n// retrieve articles using <txp:article_custom /> or <txp:rss_unlimited_categories_article_list /> tags\n	global $rss_unlimited,$s,$section,$this_section,$txp407,$article_form,$rss_article_form;\n	$article_attr == \'\' ? // any article_custom attributes specified?\n		$attr = \'\' :\n		$attr = \' \'.$article_attr;\n	// set default \"all sections\" attribute\n	if ($rss_unlimited)\n		$section_attribute = \' section=\"*\"\';\n	else\n		$section_attribute = \'\';\n	if ($section) // explicit section specified\n		$section_attribute = \' section=\"\'.$section.\'\"\';\n	if ($this_section) // current section required\n		$section_attribute = \' section=\"\'.$s.\'\"\';\n	$rss_unlimited ?\n		$article_list = trim(parse(\'<txp:rss_unlimited_categories_article_list category=\"\'.$category.\'\"\'.$attr.\' \'.$section_attribute.\' form=\"\'.$rss_article_form.\'\" />\')) :\n		$article_list = trim(parse(\'<txp:article_custom category=\"\'.$category.\'\"\'.$attr.\' \'.$section_attribute.\' form=\"\'.$article_form.\'\" />\'));\n	if ($article_list == \"\")\n		return \'\';\n	else // articles found\n		if ($wraptag == \"\")\n			return $article_list;\n		else\n			return tag($article_list,$wraptag,\'\');\n}\n\nfunction adi_cat_menu_if($atts, $thing) {\n// for use in the Form to identify the active article\n// copy of TXP 4.0.7 if_article_id\n	global $thisarticle, $pretext;\n	assert_article();\n	extract(lAtts(array(\n		\'id\' => $pretext[\'id\'],\n	), $atts));\n	if ($id)\n		return parse(EvalElse($thing, in_list($thisarticle[\'thisid\'], $id)));\n}\n\nfunction adi_cat_menu($atts) {\n// <txp:adi_cat_menu /> tag\n	global $prefs,$s,$c,$thisarticle,$rss_unlimited,$section,$this_section,$txp407,$article_form,$rss_article_form;\n\n	extract(lAtts(array(\n		\'class\'				=> \'cat_menu\',		// class applied to top level <ul>\n		\'menu_id\'			=> \'\',				// CSS ID for top level <ul>\n		\'active_class\'		=> \'active_class\',	// class applied to active <li>\n		\'parent\'			=> \'\',				// category parent\n		\'categories\'		=> \'\',				// list of categories to include\n		\'exclude\'			=> \'\',				// list of categories to exclude\n		\'restrict\'			=> \'\',				// category1 or category2\n		\'sort\'				=> \'\',				// category sort options\n		\'link\'				=> \'1\',				// output categories as links\n		\'messy_url\'			=> \'0\',				// force links to be output in messy URL format\n		\'list_id\'			=> \'0\',				// output <li> IDs or not\n		\'list_id_prefix\'	=> \'cat_menu_\',		// <li> ID prefix\n		\'article_attr\'		=> \'\',				// attributes to pass to article_custom\n		\'article_form\'		=> \'adi_cat_menu_articles\', // form to use with article_custom\n		\'active_only\'		=> \'0\',				// only show articles in currently active category\n		\'active_parent\'		=> \'0\',				// apply active class to category <li> when in individual article mode (SHOULD BE DEFAULT BEHAVIOUR?)\n		\'rss_unlimited\'		=> \'0\',				// rss_unlimited_categories mode\n		\'rss_article_form\'	=> \'adi_cat_menu_rss_articles\', // form to use with rss_unlimited_categories_article_list\n		\'section\'			=> \'\',				// link categories to specified section\n		\'this_section\'		=> \'\',				// link categories to current section (overrides \'section\')\n		\'section_sensitive\'	=> \'0\',				// display categories only that have articles belonging to specified \'section\' or \'this_section\'\n		\'list_empty_cats\'	=> \'1\',				// display empty categories (i.e. categories that have no articles)\n		\'type\'				=> \'article\',		// leftover from TXP category_list\n		\'wraptag\'			=> \'\',				// wrap a tag around the menu\n		\'wraptag_class\'		=> \'menu_wrapper\',	// class for wraptag\n		\'wraptag_id\'		=> \'\',				// id for wraptag\n		\'debug\'				=> \'0\',\n	), $atts));\n\n	// set up the basics\n	//$break = \'li\';\n	//$wraptag = \'ul\';\n	$sort = doSlash($sort);\n	$include_list = \'\';\n	if ($categories) {\n		$include_list = do_list($categories);\n		$include_list = join(\"\',\'\",doSlash($include_list));\n	}\n	$exclude_list = \'\';\n	if ($exclude) {\n		$exclude_list = do_list($exclude);\n		$exclude_list = join(\"\',\'\",doSlash($exclude_list));\n	}\n\n	// debug action\n	if ($debug) {\n		echo \"VERSIONS\";\n		echo \'<pre>\';\n		$version = safe_field(\"version\", \"txp_plugin\", \"name=\'adi_cat_menu\'\");\n		$status = safe_field(\"status\", \"txp_plugin\", \"name=\'adi_cat_menu\'\");\n		echo __FUNCTION__.\': \'.(empty($version)?\'not installed\':$version.($status?\' (active)\':\' (not active)\')).\'<br/>\';\n		echo \'TXP: \'.$prefs[\'version\'].($txp407 ? \' (4.0.7+)\' : \' (pre-4.0.7)\').\'<br/>\';\n		echo \'PHP: \'.phpversion().\'<br/>\';\n		echo \'MySQL: \'.mysql_get_server_info();\n		echo \'</pre>\';\n		echo \'SUPPLIED ATTRIBUTES:\';\n		dmp($atts);\n		echo \"CURRENT CATEGORY = $c<br/>\";\n		echo \"CURRENT SECTION = $s<br/>\";\n		echo \"INITIAL INCLUDE LIST = $include_list<br/>\";\n		echo \"INITIAL EXCLUDE LIST = $exclude_list<br/>\";\n	}\n\n	// here we go\n	if ($section_sensitive) {\n		if ($this_section) // current section required, override $section\n			$section = $s;\n		// set some cbs_category_list attribute values\n		$showcount = FALSE;\n		$sticky = FALSE;\n		$posted = \'\';\n		// some clever stuff from cbs_category_list\n		if ($section == \'default\') {\n			$table = \', `\'.PFX.\'txp_section` AS s\';\n			$sqlsection = \'s.name AND s.on_frontpage = 1\';\n		}\n		else {\n			$table = \'\';\n			$sqlsection = \"\'$section\'\";\n		}\n		// process cbs_category_list attributes\n		$parent = ($parent) ? \" AND c.parent = \'$parent\'\" : \'\';\n		$showcount = ($showcount == \'true\') ? \', COUNT(*) AS count\' : \'\';\n		$sticky = ($sticky == \'true\') ? \'5\' : \'4\';\n		switch($posted) {\n			case \'all\':\n				$posted = \'\';\n				break;\n			case \'future\':\n				$posted = \' AND t.Posted >= now()\';\n				break;\n			default:\n				$posted = \' AND t.Posted < now()\';\n		}\n		// I did these bits!\n		$sort ?\n			$sort = \' ORDER BY c.\'.$sort :\n			$sort = \' ORDER BY c.name asc\';\n		if ($include_list)\n			$include_list = \" AND c.name IN (\'$include_list\')\";\n		if ($exclude_list)\n			$exclude_list = \" AND c.name NOT IN (\'$exclude_list\')\";\n		// some magic SQL from cbs_category_list (with some include/exclude extras)\n		$rs = startRows(\n			\'SELECT c.name, c.title\'.$showcount.\' FROM `\'.PFX.\'txp_category` AS c, `\'.PFX.\'textpattern` AS t\'.$table\n			.\' WHERE (c.name = t.Category1 OR c.name = t.Category2)\'\n			.$include_list\n			.$exclude_list\n			.\' AND c.type = \\\'article\\\' AND t.Section = \'.$sqlsection.$parent.$posted\n			.\' AND t.Status = \'.$sticky.\' GROUP BY c.name\'.$sort\n			,$debug);\n	}\n	else { // not section sensitive\n		if ($parent) // exclude parent from list\n			if ($exclude_list) // combine parent & exclude list\n				$exclude_list = $exclude_list.\',\'.$parent;\n			else // just exclude parent\n				$exclude_list = $parent;\n		if ($debug) echo \"NEW EXCLUDE LIST = $exclude_list\";\n		// based on code from TXP 4.0.6 category_list tag\n		if ($exclude_list) {\n			$exclude_list = \"AND name NOT IN (\'$exclude_list\')\";\n		}\n		if ($parent) {\n			$qs = safe_row(\'lft, rgt\', \'txp_category\', \"name = \'\".doSlash($parent).\"\'\",$debug);\n			if ($qs) {\n				extract($qs);\n				$rs = safe_rows_start(\'name, title\', \'txp_category\',\n					\"(lft between $lft and $rgt) and type = \'\".doSlash($type).\"\' and name != \'default\' $exclude_list order by \".($sort ? $sort : \'lft ASC\'),$debug);\n			}\n		}\n		else if ($include_list) { // explicit list of categories supplied\n			$rs = safe_rows_start(\'name, title\', \'txp_category\',\n				\"type = \'\".doSlash($type).\"\' and name in (\'$include_list\') order by \".($sort ? $sort : \"field(name, \'$include_list\')\"),$debug);\n		}\n		else { // empty parent attribute (= all categories)\n			$rs = safe_rows_start(\'name, title\', \'txp_category\',\n				\"type = \'$type\' and name not in(\'default\',\'root\') $exclude_list order by \".($sort ? $sort : \'name ASC\'),$debug);\n		}\n	}\n\n	// if parent category not found then we fail in same way as TXP category_list with \"Undefined variable: rs\" - COULD DO BETTER?\n	if ($rs) {\n		$out = array();\n		empty($class) ? // set up <ul> class\n			$ul_class = \'\' :\n			$ul_class = \' class=\"\'.$class.\'\"\';\n		empty($menu_id) ? // set up <ul> ID\n			$ul_id = \'\' :\n			$ul_id = \' id=\"\'.$menu_id.\'\"\';\n		$empties = \'\';\n\n		// determine which categories current article belongs to (if individual article mode) or generate emmpty list if not\n		$article_cats = array();\n		if (!empty($thisarticle)) { // individual article being displayed\n			if ($debug) {\n				echo \'CURRENT ARTICLE:\';\n				dmp($thisarticle);\n			}\n			// get current article\'s category(ies) (single article) (from TXP if_article_category tag)\n			if (!empty($thisarticle[\'category1\'])) $article_cats[] = $thisarticle[\'category1\'];\n			if (!empty($thisarticle[\'category2\'])) $article_cats[] = $thisarticle[\'category2\'];\n			$article_cats = array_unique($article_cats);\n			if ($debug) {\n				echo \'CURRENT ARTICLE\\\'S CATEGORIES (\'.$thisarticle[\'title\'].\'):\';\n				dmp($article_cats);\n			}\n		}\n\n		// work through retrieved list of categories\n		while ($a = nextRow($rs)) {\n			extract($a); // extract category\'s $name & $title\n			if ($debug) {\n				echo \'CATEGORY \'.$title.\':\';\n				dmp($a);\n			}\n			if ($name) { // i.e. category name\n				// set section for category link (if this_section set then current section, else use supplied section)\n				$section = ($this_section) ? ( $s == \'default\' ? \'\' : $s ) : $section; // note: default = \'\'\n				$title = htmlspecialchars($title);\n\n				// determine if current category is active (can\'t tell if individual article being displayed)\n				// note use of strcasecmp - category names can be in different case if permanent link mode in TXP 4.0.5 is not messy!\n				$active_category = (strcasecmp($c, $name) == 0);\n\n				// determine if category is parent to individual article\n				$parent_category = FALSE;\n				if (!empty($article_cats)) { // contains individual article\'s categories (if any) - empty if not individual article display mode\n					$parent_category = (!(array_search($name,$article_cats) === FALSE));\n					if ($debug && $parent_category) echo \'PARENT OF CURRENT ARTICLE<br/><br/>\';\n				}\n\n				// set <li> class: IF active_class attribute set AND (category currently active OR (active_parent_category attribute set AND category is parent of active individual article))\n				$li_class = ($active_class AND ($active_category OR ($active_parent AND $parent_category))) ?\n					\' class=\"\'.$active_class.\'\"\' :\n					\'\';\n				$list_id ?\n					$li_id = \' id=\"\'.$list_id_prefix.$title.\'\"\' :\n					$li_id = \'\';\n				$li_tag = \'<li\'.$li_class.$li_id.\'>\';\n				$messy_url ?\n					$cat_url = hu.\'?c=\'.$name : // messy URL\n					$cat_url = pagelinkurl(array(\'s\' => $section, \'c\' => $name)); // render link according to Permanent Link Mode\n				$link ?\n					$a_tag = \'<a href=\"\'.$cat_url.\'\">\'.$title.\'</a>\' :\n					$a_tag = $title;\n\n				// determine whether to list articles or not\n				$list_articles = TRUE; // default setting\n				if ($active_only)\n					$list_articles = $active_category || $parent_category; // list articles if category is active or is parent of current individual article\n				if ($list_articles) {\n					$articles_found = trim(adi_cat_menu_articles($name,\'ul\',$article_attr));\n					if (!$articles_found && $debug) $empties .= \' \'.$title;\n				}\n				else\n					$articles_found = \'\';\n				if ($list_empty_cats || (!$list_empty_cats && $articles_found))  // generate category list item\n					$out[] = $li_tag.$a_tag.$articles_found.\'</li>\';\n			}\n			if ($debug) echo \'<hr />\';\n		}\n\n		if ($debug) $empties = \'<li>[Empties: \'.$empties.\']</li>\';\n\n		if ($out) { // it\'s a wrap\n			$out = \'<ul\'.$ul_class.$ul_id.\'>\'.$empties.join($out).\'</ul>\';\n			if ($wraptag) { // double wrapped for your protection\n				$wrap_attr = \'\';\n				if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n				if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n				$out = tag($out,$wraptag,$wrap_attr);\n			}\n			return $out;\n		}\n		else\n			return \'\';\n\n	}\n	return \'\';\n}\n', 'e55bff912accc2d7a180cf68f1a012b9', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_contact', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.6', 'Form field validation, spam prevention, email header/body extras, database update, MailChimp subscription', '<h1><strong>adi_contact</strong> &#8211; Form field validation, spam prevention, email header/body extras, database update, MailChimp subscription</h1>\n\n<p>This plugin is used in conjunction with <a href=\"http://forum.textpattern.com/viewtopic.php?id=23728\" rel=\"nofollow\">zem_contact_reborn</a> or <a href=\"http://forum.textpattern.com/viewtopic.php?id=46798\" rel=\"nofollow\">zem_contact_reborn v4.5+</a> to provide the following functionality:</p>\n\n<ul>\n	<li>submitted field combination check</li>\n	<li>submitted field content validation</li>\n	<li>anti-spam measures</li>\n	<li>database update</li>\n	<li>email header options (CC &amp; <span class=\"caps\">BCC</span>)*</li>\n	<li>append lines to email body*</li>\n	<li>MailChimp list subscription*</li>\n</ul>\n\n<p>Once installed, a collection of new tags are available.</p>\n\n<p><strong>Validation &amp; spam detection:</strong></p>\n\n<ul>\n	<li><a href=\"#adi_contact_combo\" rel=\"nofollow\">adi_contact_combo</a> &#8211; define valid combinations of submitted form fields</li>\n	<li><a href=\"#adi_contact_validate\" rel=\"nofollow\">adi_contact_validate</a> &#8211; validate the content of submitted form fields</li>\n	<li><a href=\"#adi_contact_spam\" rel=\"nofollow\">adi_contact_spam</a> &#8211; block spam in submitted form fields</li>\n	<li><a href=\"#adi_contact_labels\" rel=\"nofollow\">adi_contact_labels</a> &#8211; specify labels to be used in error messages</li>\n	<li><a href=\"#adi_contact_checkboxes\" rel=\"nofollow\">adi_contact_checkboxes</a> &#8211; to specify which fields are checkboxes</li>\n	<li><a href=\"#adi_contact_javascript\" rel=\"nofollow\">adi_contact_javascript</a> &#8211; required if zem_contact input/error forms are separate</li>\n</ul>\n\n<p><strong>Email header/body options</strong>:*</p>\n\n<ul>\n	<li><a href=\"#adi_contact_headers\" rel=\"nofollow\">adi_contact_headers</a> &#8211; CC and <span class=\"caps\">BCC</span> email addresses</li>\n	<li><a href=\"#adi_contact_body\" rel=\"nofollow\">adi_contact_body</a> &#8211; append text to email message</li>\n</ul>\n\n<p><strong>Database update:</strong></p>\n\n<ul>\n	<li><a href=\"#adi_contact_db\" rel=\"nofollow\">adi_contact_db</a> &#8211; container tag for database updates</li>\n	<li><a href=\"#adi_contact_db\" rel=\"nofollow\">adi_contact_db_data</a> &#8211; defines mapping of contact &amp; database fields, default values etc.</li>\n</ul>\n\n<p><strong>MailChimp list subscription</strong>:*</p>\n\n<ul>\n	<li><a href=\"#adi_contact_mailchimp\" rel=\"nofollow\">adi_contact_mailchimp</a> &#8211; container tag for MailChimp list subscription</li>\n	<li><a href=\"#adi_contact_mailchimp\" rel=\"nofollow\">adi_contact_mailchimp_data</a> &#8211; defines mapping of contact &amp; MailChimp fields</li>\n</ul>\n\n<p>*Requires <a href=\"http://forum.textpattern.com/viewtopic.php?id=46798\" rel=\"nofollow\">zem_contact_reborn v4.5+</a>.</p>\n\n<p>Use the above tags within a <code>&lt;txp:zem_contact&gt; ... &lt;/txp:zem_contact&gt;</code> container. A <a href=\"#adi_contact_example\" rel=\"nofollow\">full example</a> of usage is provided at the end.</p>\n\n<p>A number of additional <span class=\"caps\">CSS</span> classes are automatically applied to help with <a href=\"#adi_contact_styling\" rel=\"nofollow\">styling</a>.</p>\n\n<p><strong>Beware!</strong> If you&#8217;re using the database update feature, the load order for this plugin <em>must</em> be set higher than any other anti-spam plugins that interact with <strong>zem_contact</strong> (e.g. <strong>pap_contact_cleaner</strong>), otherwise there is a danger that the database will be updated with spam information. When you install or update <strong>adi_contact</strong> its load order will automatically be set to 9.  Be aware of the consequences if you manually change this.  Also, you should understand that if you are running plugins from the plugin cache (rather than installing them normally) then the load order cannot be determined or predicted.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Form field combinations (adi_contact_combo)</strong></h2>\n\n<p>Sometimes certain combinations of form fields are required from users, for example:</p>\n\n<ul>\n	<li>fill in at least one of “Likes”, “Dislikes”, “Other comments”</li>\n	<li>if “Phone number” is filled in then “Best time to call” is also required</li>\n	<li>if “Region” is <i>Other</i>, then “Other Region” field is required</li>\n</ul>\n\n<p>For example, to make sure the user fills in at least one field in a specified list:</p>\n\n<pre><code>&lt;txp:zem_contact&gt;\n  ...\n  &lt;txp:zem_contact_textarea name=&quot;likes&quot; label=&quot;Likes&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_textarea name=&quot;dislikes&quot; label=&quot;Dislikes&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_textarea name=&quot;comments&quot; label=&quot;Other comments&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:adi_contact_combo names=&quot;likes,dislikes,comments&quot;\n    restrict=&quot;min&quot; number=&quot;1&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>Or if an extra field has to be filled in:</p>\n\n<pre><code>&lt;txp:zem_contact&gt;\n  ...\n  &lt;txp:zem_contact_text name=&quot;phone&quot; label=&quot;Your phone number&quot;\n    required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;when&quot; label=&quot;When should we call you?&quot;\n    required=&quot;0&quot; /&gt;\n  &lt;txp:adi_contact_combo names=&quot;phone&quot; require=&quot;when&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>And to specify a required field depending on the value of a supplied field:</p>\n\n<pre><code>&lt;txp:zem_contact&gt;\n  ...\n  &lt;txp:zem_contact_select name=&quot;region&quot; label=&quot;Where do you live?&quot;\n    list=&quot;,UK,France,Germany,Other&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;region_other&quot; label=&quot;If Other, then where?&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:adi_contact_combo names=&quot;region&quot; value=&quot;other&quot; require=&quot;region_other&quot;\n    message=&quot;Where else do you live?&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>You can also generate custom error messages by using <code>required=&quot;0&quot;</code> in the standard <span class=\"caps\">ZCR</span> tags and <code>required=&quot;1&quot;</code> in <strong>adi_contact_combo</strong>.  For example:</p>\n\n<pre><code>&lt;txp:zem_contact&gt;\n  ...\n  &lt;txp:zem_contact_text name=&quot;name&quot; label=&quot;Name&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:adi_contact_combo names=&quot;name&quot; required=&quot;1&quot; message=&quot;Don\'t be shy!&quot;/&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>No real magic here &#8211; <code>required=&quot;1&quot;</code> is actually the equivalent of <code>restrict=&quot;equal&quot; number=&quot;1&quot;</code>.</p>\n\n<h3><strong>adi_contact_combo</strong> &#8211; attributes</h3>\n\n<p><code>names=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that are of interest. Default = &#8220;&#8221; (none).</p>\n\n<p><code>value=&quot;value&quot;</code></p>\n\n<p>- If value specified then additional fields of interest will only be flagged if the supplied name field has this value. Default =”“ (required/restricted fields not dependent on value).  Used in conjunction with <code>require</code> and <code>restrict</code>.</p>\n\n<p><code>restrict=&quot;restriction&quot;</code></p>\n\n<p>- defines a minimum (<code>min</code>), a maximum (<code>max</code>), or an exact number (<code>equal</code>),</p>\n\n<p><code>number=&quot;integer&quot;</code></p>\n\n<p>- number of fields, used in conjunction with <code>restrict</code>.</p>\n\n<p><code>case_sensitive=&quot;boolean&quot;</code></p>\n\n<p>- defines whether to take notice of case when checking <code>value</code>.  Default = “0” (case-insensitive).</p>\n\n<p><code>require=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that are required (or if used with <code>restrict</code> then of additional interest), depending on the presence or values of fields in <code>names</code> list.</p>\n\n<p><code>required=&quot;boolean</code>”</p>\n\n<p>- the specified field is required (useful for creating custom error messages).  Default = “0” (not required).</p>\n\n<p><code>message=&quot;text&quot;</code></p>\n\n<p>- alternative error message (see below for list of placeholders).</p>\n\n<p><code>checkbox</code></p>\n\n<p>- this attribute is no longer valid (use <a href=\"#adi_contact_checkboxes\" rel=\"nofollow\">adi_contact_checkboxes</a> tag instead).</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Form field validation (adi_contact_validate)</strong></h2>\n\n<p>This enables the contents of fields to be validated, for example to check that a phone number field has been filled in properly:</p>\n\n<pre><code>&lt;txp:adi_contact_validate names=&quot;home_phone&quot; type=&quot;phone&quot; /&gt;</code></pre>\n\n<p>Or to check that fields have been filled in the same (e.g. for double checking supplied email addresses):</p>\n\n<pre><code>&lt;txp:adi_contact_validate names=&quot;email1,email2&quot; type=&quot;equal&quot; /&gt;</code></pre>\n\n<h3><strong>adi_contact_validate</strong> &#8211; attributes</h3>\n\n<p><code>names=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that should be validated. Default = &#8220;&#8221; (none).</p>\n\n<p><code>type=&quot;validation&quot;</code></p>\n\n<p>- the type of validation that is required:</p>\n\n<ul>\n	<li><code>alpha</code> &#8211; alphabetic characters</li>\n	<li><code>alphanum</code> &#8211; alpha-numeric characters</li>\n	<li><code>integer</code> &#8211; digits only</li>\n	<li><code>numeric</code> &#8211; a number (e.g. +0123.45e6 is a number)</li>\n	<li><code>phone</code> &#8211; phone number (i.e. 0-9 + &#8211; . , () xX space)</li>\n	<li><code>url</code> &#8211; a <span class=\"caps\">URL</span> (with or without “http://”)</li>\n	<li><code>custom</code> &#8211; use in conjunction with <code>pattern</code></li>\n	<li><code>equal</code> &#8211; two or more field values must be the same</li>\n	<li><code>base64</code> &#8211; base64 encoded</li>\n</ul>\n\n<p><code>allow_whitespace=&quot;boolean&quot;</code></p>\n\n<p>- allow whitespace (i.e. space, tab, newlines etc) in addition to that specified in <code>type</code>. Default = “0” (No).</p>\n\n<p><code>pattern=&quot;pattern string&quot;</code></p>\n\n<p>- a <span class=\"caps\">PCRE</span> pattern that will be dropped straight into <code>preg_match</code>.  Used in conjunction with <code>type=&quot;custom&quot;</code>.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>message=&quot;text&quot;</code></p>\n\n<p>- alternative error message.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Spam prevention (adi_contact_spam)</strong></h2>\n\n<p>This will prevent email header injection and stop emails that contain links (which is, after all, what spammers want to send you).</p>\n\n<p>By default <strong>adi_contact_spam</strong> will check all fields, so if you want users to send you links then use the <code>exclude</code> attribute:</p>\n\n<pre><code>&lt;txp:zem_contact&gt;\n  ...\n  &lt;txp:zem_contact_text name=&quot;website&quot; label=&quot;Your website URL&quot; /&gt;\n  &lt;txp:adi_contact_spam exclude=&quot;website&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<h3><strong>adi_contact_spam</strong> &#8211; attributes</h3>\n\n<p><code>names=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that should be checked. Default = &#8220;&#8221; (all submitted fields).</p>\n\n<p><code>exclude=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that should <em><span class=\"caps\">NOT</span></em> be checked. Default = &#8220;&#8221; (none).</p>\n\n<p><code>detect_mail_headers=&quot;boolean&quot;</code></p>\n\n<p>- detect the presence of <code>to:</code>, <code>from:</code>, <code>bcc:</code>, <code>multipart</code>, <code>cc:</code>, <code>Content-Transfer-Encoding:</code>, <code>Content-Type:</code>, <code>Mime-Version:</code> and flag as spam if found. Default = “1” (Yes).</p>\n\n<p><code>detect_link_tags=&quot;boolean&quot;</code></p>\n\n<p>- detect the presence of <code>[url</code>, <code>http</code>, <code>www</code>, <code>href</code> and flag as spam if found. Default = “1” (Yes).</p>\n\n<p><code>allow_www=&quot;boolean&quot;</code></p>\n\n<p>- allow the presence of <code>www</code> and do not flag as spam. Default = “0” (No).</p>\n\n<p><code>message=&quot;text&quot;</code></p>\n\n<p>- alternative error message.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Error messages, labels and styling (adi_contact_labels &amp; the ‘message’ attribute)</strong></h2>\n\n<h3><strong>The ‘message’ attribute</strong></h3>\n\n<p>The <strong>adi_contact</strong> tags will generate standard error messages in English and are placed in the same list as the standard <strong>zem_contact</strong> error messages. The messages can be customised by using the <code>message=&quot;&quot;</code> attribute:</p>\n\n<ul>\n	<li>alter the standard message</li>\n	<li>translate the standard message into other languages</li>\n	<li>use [NAMES] (‘names’ attribute), [REQUIRE] (‘require’ attribute) &amp; [NUMBER] (‘number’ attribute) placeholders</li>\n</ul>\n\n<p>For example, if <strong>adi_contact_spam</strong> finds a link, say, in a field called Comments then the standard error message will be:</p>\n\n<p><i>Link detected in “Comments” ([url, http, www, href not allowed).</i></p>\n\n<p>But you can customise the error message:</p>\n\n<p><code>message=&quot;Nice try! No spam allowed in [NAMES].&quot;</code></p>\n\n<p>to produce:</p>\n\n<p><i>Nice try! No spam allowed in Comments.</i></p>\n\n<h3><strong>adi_contact_labels</strong> &#8211; attributes</h3>\n\n<p><code>name1=&quot;label string 1&quot; name2=&quot;label string 2&quot;</code></p>\n\n<p>- the labels to use in <strong>adi_contact</strong> error messages.</p>\n\n<p><strong>Important:</strong> The <strong>adi_contact_labels</strong> tag <em><span class=\"caps\">MUST</span></em> be placed in the page/form <em>before</em> other <strong>adi_contact</strong> tags &#8211; otherwise your label strings won&#8217;t be picked up.</p>\n\n<p>Example:</p>\n\n<pre><code>&lt;txp:adi_contact_labels phone=&quot;Phone number&quot; when=&quot;When to call&quot;\n  likes=&quot;Likes&quot; dislikes=&quot;Dislikes&quot; comments=&quot;Comments&quot; /&gt;</code></pre>\n\n<p>Notes:</p>\n\n<ul>\n	<li>a label specified with <strong>adi_contact_labels</strong> does not have to match the actual label used in the form</li>\n	<li>labels specified with <strong>adi_contact_labels</strong> are only used in <strong>adi_contact</strong> error messages</li>\n	<li>the error messages will use labels in the following order of preference:\n	<ul>\n		<li>label specified in <strong>adi_contact_labels</strong></li>\n		<li>form label retrieved from <strong>zem_contact</strong> (but only if a field has been filled in &amp; submitted)</li>\n		<li>the field name (with first letter in upper case)</li>\n	</ul></li>\n	</ul>\n\n &lt;hr /&gt;\n\n<h2><strong>Checkboxes (adi_contact_checkboxes)</strong></h2>\n\n<p>If you are using <strong>adi_contact</strong> to validate checkboxes then you need to identify them as such. Not required if you are just using <strong>adi_contact</strong> to update a database.</p>\n\n<h3><strong>adi_contact_checkboxes</strong> &#8211; attributes</h3>\n\n<p><code>names=&quot;name list&quot;</code></p>\n\n<p>- comma separated list of field names that are checkboxes. Default = &#8220;&#8221; (no checkboxes to be processed by <strong>adi_contact</strong>).</p>\n\n<p><strong>Important:</strong> The <strong>adi_contact_checkboxes</strong> tag <em><span class=\"caps\">MUST</span></em> be placed in the page/form <em>before</em> <strong>adi_contact_combo</strong> tags &#8211; otherwise checkbox validation won&#8217;t work.</p>\n\n<p>Example:</p>\n\n<pre><code>&lt;txp:zem_contact_checkbox name=&quot;orange&quot; label=&quot;Orange&quot; required=&quot;0&quot; /&gt;\n&lt;txp:zem_contact_checkbox name=&quot;apple&quot; label=&quot;Apple&quot; required=&quot;0&quot; /&gt;\n&lt;txp:zem_contact_checkbox name=&quot;banana&quot; label=&quot;Banana&quot; required=&quot;0&quot; /&gt;\n  ...\n&lt;txp:adi_contact_checkboxes names=&quot;orange,apple,banana&quot; /&gt;\n&lt;txp:adi_contact_combo names=&quot;orange,apple,banana&quot; restrict=&quot;min&quot; number=&quot;1&quot; message=&quot;Eat some fruit!&quot; /&gt;</code></pre>\n\n &lt;hr /&gt;\n\n<h2><strong>Email header options (adi_contact_headers)</strong></h2>\n\n<p>To send the contact form email to CC (carbon copy) or <span class=\"caps\">BCC</span> (blind carbon copy) addresses:</p>\n\n<pre><code>&lt;txp:adi_contact_headers cc=&quot;cc@example.com&quot; bcc=&quot;bcc@example.com&quot; /&gt;</code></pre>\n\n<h3><strong>adi_contact_headers</strong> &#8211; attributes</h3>\n\n<p><code>cc=&quot;email address list&quot;</code></p>\n\n<p>- comma separated list of email addresses to be CC&#8217;d. Default = &#8220;&#8221; (none).</p>\n\n<p><code>bcc=&quot;email address list&quot;</code></p>\n\n<p>- comma separated list of email addresses to be <span class=\"caps\">BCC</span>&#8217;d. Default = &#8220;&#8221; (none).</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Email body text (adi_contact_body)</strong></h2>\n\n<p>To append text to end of email body:</p>\n\n<pre><code>&lt;txp:adi_contact_body line=&quot;A line of extra text.&quot; /&gt;</code></pre>\n\n<p><em>Note that if you&#8217;re using <strong>adi_contact_mailchimp</strong> then lines appended using <strong>adi_contact_body</strong> will appear before any MailChimp diagnostic information.</em></p>\n\n<h3><strong>adi_contact_body</strong> &#8211; attributes</h3>\n\n<p><code>line=&quot;text&quot;</code></p>\n\n<p>- text to be appended to email message. Default = &#8220;&#8221; (blank line).</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Database update (adi_contact_db &amp; adi_contact_db_data)</strong></h2>\n\n<p>To insert information supplied in a contact form into a database, you need to use a combination of the <strong>adi_contact_db</strong> tag together with a number of <strong>adi_contact_db_data</strong> tags.</p>\n\n<p>The <strong>adi_contact_db</strong> tag defines the table to be updated in the <span class=\"caps\">TXP</span> database. The <strong>adi_contact_db_data</strong> tag defines the mapping between contact input fields and database columns, as well as providing default value and timestamp options.</p>\n\n<h3><strong>adi_contact_db</strong> &#8211; attributes</h3>\n\n<p><code>table=&quot;database table&quot;</code></p>\n\n<p>- a table in the <span class=\"caps\">TXP</span> database. Default unset. <em>This attribute must be specified and the database table must already exist.</em></p>\n\n<p><code>form=&quot;form name&quot;</code></p>\n\n<p>- a optional form containing the <strong>adi_contact_db_data</strong> tags.  Defaults to container mode.</p>\n\n<h3><strong>adi_contact_db_data</strong> &#8211; attributes</h3>\n\n<p><code>db_field=&quot;database table column&quot;</code></p>\n\n<p>- a column in the table specified in the <strong>adi_contact_db</strong> tag. Default unset. <em>Must be specified.</em></p>\n\n<p><code>contact_field=&quot;input name&quot;</code></p>\n\n<p>- the name of an input field in the contact form. If not specified, it is assumed to be the same as the database column name.</p>\n\n<p><code>default=&quot;value&quot;</code></p>\n\n<p>- default value to be use in the database if the input field is empty. Default = no default value used (database column default will be used instead).</p>\n\n<p><code>timestamp=&quot;type&quot;</code></p>\n\n<p>- specifies that the date/time should be entered into the database column. Default = “0” (no timestamp). Valid types are “datetime” (yyyy-mm-dd hh:mm:ss), “date” (yyyy-mm-dd) &amp; “timestamp” (yyyy-mm-dd hh:mm:ss <span class=\"caps\">UTC</span>). If timestamp used, a contact form input field is not required.</p>\n\n<h3><strong>Container mode example:</strong></h3>\n\n<pre><code>&lt;txp:adi_contact_db table=&quot;contacts&quot;&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;db_column_1&quot; contact_field=&quot;input_field_a&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;db_column_2&quot; contact_field=&quot;input_field_b&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;db_column_2&quot; contact_field=&quot;input_field_c&quot; default=&quot;unknown&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;name_1&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;name_2&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;timestamp&quot; timestamp=&quot;datetime&quot; /&gt;\n&lt;/txp:adi_contact_db&gt;</code></pre>\n\n<p><strong>Notes:</strong></p>\n\n<ul>\n	<li>the specified database table must be created manually</li>\n	<li>no checks are done on the data written into database, ensure that column data types are suitable</li>\n	<li>if no default is supplied in the tag, then the database column defaults will be used for empty form input fields</li>\n	<li>timestamp format is “yyyy-mm-dd hh-mm-ss”</li>\n	<li>in the above example:\n	<ul>\n		<li>if “input_field_c” is empty, “unknown” is written to the database</li>\n		<li>data from input fields “name_1” &amp; “name_2” is written to database columns of the same name</li>\n	</ul></li>\n	</ul>\n\n<h3><strong>Troubleshooting adi_contact_db</strong></h3>\n\n<p>To troubleshoot issues with <strong>adi_contact_db</strong>, switch your website&#8217;s Production Status to “debugging”.</p>\n\n<p>If there are issues with the existence/naming of database tables or columns then error messages will be generated. For more detailed information about what&#8217;s happening (or not) in the background, look in the page&#8217;s <a href=\"http://www.textpattern.net/wiki/index.php?title=tag_trace\" rel=\"nofollow\">Tag trace</a> for adi_contact logs.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>MailChimp list subscription (adi_contact_mailchimp &amp; adi_contact_mailchimp_data)</strong></h2>\n\n<p>To subscribe to a MailChimp mailing list, use a combination of the <strong>adi_contact_mailchimp</strong> tag together with a number of <strong>adi_contact_mailchimp_data</strong> tags.</p>\n\n<p>The <strong>adi_contact_mailchimp</strong> tag contains the MailChimp mailing list information. The <strong>adi_contact_mailchimp_data</strong> tag defines the mapping between contact input fields and MailChimp list fields.</p>\n\n<p>The <span class=\"caps\">API</span> key and List ID can be obtained from your MailChimp account.</p>\n\n<p><span class=\"caps\">API</span> key:</p>\n\n<ol>\n	<li>Go to Account page</li>\n	<li>Then “Extras” &gt; “API keys”</li>\n	<li>Click “Create A Key”</li>\n	<li>Make a note of the key, e.g. abc123abc123abc123abc123abc123-us1</li>\n</ol>\n\n<p>List ID:</p>\n\n<ol>\n	<li>Go to Lists page</li>\n	<li>Click on the list you&#8217;re interested in</li>\n	<li>Go to “Settings” &gt; “List name &amp; defaults”</li>\n	<li>Make a note of ID, e.g. abcdef12345</li>\n</ol>\n\n<h3><strong>adi_contact_mailchimp</strong> &#8211; attributes</h3>\n\n<p><code>api_key=&quot;api key&quot;</code></p>\n\n<p>- your MailChimp account <span class=\"caps\">API</span> key. Default unset. <em>Must be specified.</em></p>\n\n<p><code>list_id=&quot;list id&quot;</code></p>\n\n<p>- your MailChimp mailing list ID. Default unset. <em>Must be specified.</em></p>\n\n<p><code>status=&quot;status&quot;</code></p>\n\n<p>- the subscription status to be applied. Default “pending” (confirmation email will be sent my MailChimp). Other values: “subscribed” (subscribe immediately <span class=\"caps\">WITHOUT</span> user confirmation).</p>\n\n<p><code>opt_in_field=&quot;input name&quot;</code></p>\n\n<p>- the name of an input field in the contact form for the user to signal mailing list subscription intention. Default = “mailing_list”. Use &#8220;&#8221; to switch off explicit opt-in.</p>\n\n<p><code>opt_in_value=&quot;value&quot;</code></p>\n\n<p>- the value of the opt-in field in the contact form, to signal mailing list subscription positive intention. Default = “Yes” (in the website&#8217;s language), which is suitable for a checkbox.</p>\n\n<p><code>form=&quot;form name&quot;</code></p>\n\n<p>- a optional form containing the <strong>adi_contact_mailchimp_data</strong> tags.  Defaults to container mode.</p>\n\n<h3><strong>adi_contact_mailchimp_data</strong> &#8211; attributes</h3>\n\n<p><code>mailchimp_field=&quot;field name&quot;</code></p>\n\n<p>- the name of a MailChimp list field. Default unset. Examples: “email”, “FNAME”, “LNAME”. <em>Must be specified.</em></p>\n\n<p><code>contact_field=&quot;input name&quot;</code></p>\n\n<p>- the name of an input field in the contact form. Default unset. <em>Must be specified.</em></p>\n\n<h3><strong>adi_contact_mailchimp_email</strong> &#8211; attributes</h3>\n\n<p>In its simplest form, this tag:</p>\n\n<pre><code>&lt;txp:adi_contact_mailchimp_email /&gt;</code></pre>\n\n<p>is functionally equivalent to:</p>\n\n<pre><code>&lt;txp:adi_contact_mailchimp_data mailchimp_field=&quot;email&quot; contact_field=&quot;email&quot; /&gt;</code></pre>\n\n<p>If you need to specifiy a different contact field name for the email address use:</p>\n\n<p><code>contact_field=&quot;input name&quot;</code></p>\n\n<p>- the name of the email input field in the contact form. Default = “email” (use contact field called “email”).</p>\n\n<h3><strong>Examples:</strong></h3>\n\n<p>If all you want to do is take an address from a contact field called “email”, where the opt-in field is a checkbox called “mailing_list”, and send it to MailChimp:</p>\n\n<pre><code>&lt;txp:adi_contact_mailchimp api_key=&quot;abc123abc123abc123abc123abc123-us1&quot; list_id=&quot;abcdef12345&quot; /&gt;</code></pre>\n\n<p>For more complicated scenarios, try something like:</p>\n\n<pre><code>&lt;txp:adi_contact_mailchimp api_key=&quot;abc123abc123abc123abc123abc123-us1&quot; list_id=&quot;abcdef12345&quot; opt_in_field=&quot;subscribe&quot;&gt;\n  &lt;txp:zem_contact_email name=&quot;the_email&quot; label=&quot;Your email&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;first_name&quot; label=&quot;First name&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;last_name&quot; label=&quot;Last name&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;subscribe&quot; label=&quot;Please put me on the mailing list&quot; checked=&quot;1&quot; /&gt;\n  &lt;txp:adi_contact_mailchimp_email contact_field=&quot;email&quot; /&gt;\n  &lt;txp:adi_contact_mailchimp_data mailchimp_field=&quot;FNAME&quot; contact_field=&quot;first_name&quot; /&gt;\n  &lt;txp:adi_contact_mailchimp_data mailchimp_field=&quot;LNAME&quot; contact_field=&quot;last_name&quot; /&gt;\n&lt;/txp:adi_contact_mailchimp&gt;</code></pre>\n\n<p><strong>Notes:</strong></p>\n\n<ul>\n	<li>the opt-in “mailing_list” contact field is a checkbox, which must be ticked by the user for the MailChimp subscription request to be generated</li>\n	<li>contact fields “first_name” and “last_name” are mapped to their MailChimp equivalents, these are optional</li>\n	<li>merge field names (e.g. <span class=\"caps\">FNAME</span>) are case-sensitive</li>\n</ul>\n\n<h3><strong>Troubleshooting adi_contact_mailchimp</strong></h3>\n\n<p>To troubleshoot issues with <strong>adi_contact_mailchimp</strong>, switch your website&#8217;s Production Status to “debugging”.</p>\n\n<p>For detailed information about what&#8217;s happening (or not) in the background, look in the page&#8217;s <a href=\"http://www.textpattern.net/wiki/index.php?title=tag_trace\" rel=\"nofollow\">Tag trace</a> for adi_contact logs.</p>\n\n<p>If an attempt has been made to subscribe an email address to your MailChimp list, there&#8217;ll be information pertaining to this appended to the contact form email.  If no attempt has been made to subscribe (e.g. if the opt-in field is not filled in) no extra information is added to the form email.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Javascript (adi_contact_javascript)</strong></h2>\n\n<p><strong>adi_contact</strong> will normally automatically insert javascript into the page, so that the error case styling classes can be added as appropriate.  However, in the situation where <strong>zem_contact</strong> is implemented as two forms i.e. with <code>show_input=&quot;0&quot;</code> and <code>show_error=&quot;0&quot;</code> then <code>&lt;txp:adi_contact_javascript /&gt;</code> must be explicitly present in both forms. Make sure it is placed <em>before</em> all other <strong>adi_contact</strong> tags.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>Styling</strong></h2>\n\n<p>Classes are automatically added to form label and input/textarea elements when errors are found.</p>\n\n<p>A class of <code>adi_contact_error</code> is always added, together with the following:</p>\n\n<p><strong>adi_contact_combo</strong> &#8211; <code>adi_contact_combo_error</code>, <code>zemRequirederrorElement</code>**</p>\n\n<p><strong>adi_contact_validate</strong> &#8211; <code>adi_contact_validate_error</code>, <code>errorElement</code></p>\n\n<p><strong>adi_contact_spam</strong> &#8211; <code>adi_contact_spam_error</code>, <code>errorElement</code></p>\n\n<p>**This class is no longer generated by <strong>zem_contact_reborn</strong> v4.5+, required fields are given <code>errorElement</code> class instead. <strong>adi_contact_combo</strong> generates both <code>zemRequirederrorElement</code> &amp; <code>errorElement</code>.</p>\n\n &lt;hr /&gt;\n\n<h2><strong>The Full Monty Example</strong></h2>\n\n<p>The following is an example of a user feedback form:</p>\n\n<pre><code>&lt;txp:zem_contact to=&quot;me@example.com&quot; subject=&quot;Feedback form example&quot; browser_validate=&quot;0&quot;&gt;\n\n  &lt;p&gt;Please fill in the form:&lt;/p&gt;\n\n  &lt;!-- the feedback form presented to the user --&gt;\n  &lt;txp:zem_contact_select name=&quot;title&quot; label=&quot;Title&quot; options=&quot;,Mr,Mrs,Miss&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;name&quot; label=&quot;Name&quot; /&gt;\n  &lt;txp:zem_contact_radio name=&quot;gender&quot; label=&quot;Male&quot; group=&quot;Gender&quot; /&gt;\n  &lt;txp:zem_contact_radio label=&quot;Female&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;general&quot; label=&quot;General enquiry&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;sales&quot; label=&quot;Sales enquiry&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;technical&quot; label=&quot;Technical enquiry&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;feedback&quot; label=&quot;Feedback&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_email name=&quot;email&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;phone&quot; label=&quot;Your phone number&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_select name=&quot;region&quot; label=&quot;Where do you live?&quot; options=&quot;,UK,France,Germany,Other&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;region_other&quot; label=&quot;If Other, then where?&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_text name=&quot;when&quot; label=&quot;When should we call you?&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_textarea name=&quot;comments&quot; label=&quot;Comments&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_textarea name=&quot;likes&quot; label=&quot;Likes&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_textarea name=&quot;dislikes&quot; label=&quot;Dislikes&quot; required=&quot;0&quot; /&gt;\n  &lt;txp:zem_contact_checkbox name=&quot;mailing_list&quot; label=&quot;Please put me on the mailing list&quot; break=&quot;&quot; required=&quot;0&quot; /&gt;\n\n  &lt;txp:zem_contact_submit label=&quot;Send feedback&quot; /&gt;\n\n  &lt;!-- specify labels to be used in error messages --&gt;\n  &lt;txp:adi_contact_labels phone=&quot;Phone number&quot; when=&quot;When to call&quot; comments=&quot;Comments&quot; /&gt;\n\n  &lt;!-- identify which fields are checkboxes --&gt;\n  &lt;txp:adi_contact_checkboxes names=&quot;general,sales,technical,feedback,mailing_list&quot; /&gt;\n\n  &lt;!-- &quot;region_other&quot; field required if &quot;region&quot; field is set to other --&gt;\n  &lt;txp:adi_contact_combo names=&quot;region&quot; value=&quot;other&quot; require=&quot;region_other&quot; message=&quot;Where else do you live?&quot; /&gt;\n\n  &lt;!-- male or female required --&gt;\n  &lt;txp:adi_contact_combo names=&quot;gender&quot; required=&quot;1&quot; message=&quot;Male or Female?&quot; /&gt;\n\n  &lt;!-- at least one of &quot;general,sales,technical,feedback&quot; required --&gt;\n  &lt;txp:adi_contact_combo names=&quot;general,sales,technical,feedback&quot; restrict=&quot;min&quot; number=&quot;1&quot; message=&quot;What type of enquiry?&quot; /&gt;\n\n  &lt;!-- &quot;comments&quot; required for &quot;general,sales,technical&quot; enquiries --&gt;\n  &lt;txp:adi_contact_combo names=&quot;general,sales,technical&quot; require=&quot;comments&quot; /&gt;\n\n  &lt;!-- at least one of &quot;likes,dislikes&quot; required for &quot;feedback&quot; enquiry --&gt;\n  &lt;txp:adi_contact_combo names=&quot;feedback&quot; require=&quot;likes,dislikes&quot; restrict=&quot;min&quot; number=&quot;1&quot; /&gt;\n\n  &lt;!-- if &quot;phone&quot; supplied then &quot;when&quot; required as well; include custom error message --&gt;\n  &lt;txp:adi_contact_combo names=&quot;phone&quot; require=&quot;when&quot; message=&quot;You\'ve given me a [NAMES] but I need to know [REQUIRE] too&quot; /&gt;\n\n  &lt;!-- if &quot;mailing_list&quot; supplied then &quot;email&quot; required as well --&gt;\n  &lt;txp:adi_contact_combo names=&quot;mailing_list&quot; message=&quot;To be on our mailing list, please provide your email address&quot; require=&quot;email&quot; /&gt;\n\n  &lt;!-- validate the phone number --&gt;\n  &lt;txp:adi_contact_validate names=&quot;phone&quot; type=&quot;phone&quot; /&gt;\n\n  &lt;!-- check for spam but allow user to send simple &quot;www.&quot; URLs --&gt;\n  &lt;txp:adi_contact_spam allow_www=&quot;1&quot; /&gt;\n\n  &lt;!-- record name, email, phone, nature of enquiry &amp; timestamp in database table &quot;contacts&quot; --&gt;\n  &lt;txp:adi_contact_db table=&quot;contacts&quot;&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;fullname&quot; contact_field=&quot;name&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;email_address&quot; contact_field=&quot;email&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;phonenumber&quot; contact_field=&quot;phone&quot; default=&quot;no phone&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;general&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;sales&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;technical&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;feedback&quot; /&gt;\n    &lt;txp:adi_contact_db_data db_field=&quot;timestamp&quot; timestamp=&quot;1&quot; /&gt;\n  &lt;/txp:adi_contact_db&gt;\n\n  &lt;!-- simple MailChimp subscription --&gt;\n  &lt;txp:adi_contact_mailchimp api_key=&quot;abc123abc123abc123abc123abc123-us1&quot; list_id=&quot;abcdef12345&quot; /&gt;\n\n  &lt;!-- send copies --&gt;\n  &lt;txp:adi_contact_headers cc=&quot;the_other_me@example.com&quot; bcc=&quot;the_secret_me@example.com&quot; /&gt;\n\n  &lt;!-- email note --&gt;\n  &lt;txp:adi_contact_body /&gt;\n  &lt;txp:adi_contact_body line=&quot;This is a Full Monty test.&quot; /&gt;\n\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<h3><strong>Notes:</strong></h3>\n\n<ul>\n	<li>the <strong>adi_contact_labels</strong> tag is placed before the other <strong>adi_contact</strong> tags <em>(only the labels for ‘phone’, ‘when’ &amp; ‘comments’ are specified here because they&#8217;re required to be different from the actual form labels)</em></li>\n	<li>the <strong>adi_contact_checkboxes</strong> tag is placed before the <strong>adi_contact_combo</strong> tags</li>\n	<li>a name has been explicitly defined for every <strong>zem_contact</strong> field tag, and these are then used in the <strong>adi_contact</strong> tags &#8211; <em>if a name is not specified then it is automatically generated in the markup &amp; the <strong>adi_contact</strong> tags won&#8217;t be able to match them up</em></li>\n	<li>a checkbox that&#8217;s been ticked is taken to be “filled in”, so <code>value=&quot;yes&quot;</code> is not required</li>\n	<li>in the database update:\n	<ul>\n		<li>a blank phone number will be stored as “no phone”</li>\n		<li>the ‘general’, ‘sales’, ‘technical’ &amp; ‘feedback’ database column names match the contact form input field names</li>\n	</ul></li>\n	<li>MailChimp subscription:\n	<ul>\n		<li>if there&#8217;s no subscription attempt (e.g. the mailing list opt-in is unticked), no diagnostic information is appended to the email message</li>\n	</ul></li>\n	<li>the above code is using zem_contact_reborn v4.5+, hence:\n	<ul>\n		<li>using the <code>options</code> attribute with <strong>zem_contact_select</strong>, instead of <code>list</code></li>\n		<li><code>browser_validate=&quot;0&quot;</code> on the <strong>zem_contact</strong> tag, to prevent browsers interfering with form field validation (HTML5)</li>\n	</ul></li>\n	</ul>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=30130\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_contact\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_contact - Form field validation, spam prevention, email header/body extras, database update, MailChimp subscription\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- fixed: problem with validate type=\"equal\" error message\n			- enhancement: database update functionality\n			- enhancement: MailChimp subscription\n			- TXP 4.6 compatibility\n			- new tags: adi_contact_headers & adi_contact_body\n			- ZCR 4.5+ compatibility: \"errorElement\" class used in addition to \"zemRequirederrorElement\"\n	0.5		- fixed: javascript doesn\'t find contact <form> if zcr input & error forms are separate (thanks jakob)\n			- fixed: error message if no fields filled in when using \"value\" attribute\n			- fixed: errors in checkbox processing (thanks jakob)\n			- enhancement: extended \"name\"+\"value\" dependency to \"restrict\" & \"number\" (for jakob)\n	0.4		- change: new adi_contact_checkboxes tag (adi_contact_combo \'checkbox\' attribute withdrawn)\n			- enhancement: adi_contact_combo if \"value\" then \"required\" (for sacripant)\n			- fix: internationalised \'Yes\' & \'No\' in checkboxes (thanks sacripant)\n			- help section formatting errors (thanks net-carver) !!!!!!!! HAVEN\'T ACTUALLY FIXED ANYTHING !!!!!!!!!\n			- fix: no more duplicate javascript insertion\n			- class action: \'adi_contact_combo_error\', \'adi_contact_validate_error\', \'adi_contact_spam_error\' (for net-carver)\n			- new adi_contact_validate type \'base64\' (for net-carver)\n			- javascript code rewrite (& bug fixes: <select>, multiple forms)\n	0.3		- enhancement: new adi_contact_validate option type=\"equal\" (for kevinpotts)\n			- fix: adi_contact_combo now processes an unticked checkbox correctly (as not being a field of interest) (thanks Logoleptic)\n			- fix: adi_contact_validate coding error (thanks planeth)\n	0.2		- enhancement: new adi_contact_combo attribute \'required\'; equivalent to restrict=\"equal\" number=\"1\", enables custom error messages (inspired by Bloke)\n			- enhancement: new adi_contact_validate attribute & option type=\"custom\" pattern=\"pattern\" (for Bloke)\n			- enhancement: new adi_contact_validate attribute option type=\"url\" (inspired by Bloke)\n			- enhancement: new adi_contact_spam attribute \'exclude\' (e.g. use in conjunction with adi_contact_validate type=\"url\")\n			- fix: adi_contact_combo fails to cope with checkboxes (new attribute \'checkbox\') (thanks valpc)\n	0.1.1	- fix: adi_contact_spam error message field lists incorrectly generated (both standard & custom)\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_contact_body\')\n		->register(\'adi_contact_checkboxes\')\n		->register(\'adi_contact_combo\')\n		->register(\'adi_contact_db_data\')\n		->register(\'adi_contact_db\')\n		->register(\'adi_contact_debug\')\n		->register(\'adi_contact_headers\')\n		->register(\'adi_contact_javascript\')\n		->register(\'adi_contact_labels\')\n		->register(\'adi_contact_mailchimp_data\')\n		->register(\'adi_contact_mailchimp_email\')\n		->register(\'adi_contact_mailchimp\')\n		->register(\'adi_contact_spam\')\n		->register(\'adi_contact_validate\')\n	;\n}\n\nglobal $prefs,$zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_labels,$zem_contact_values,$adi_contact_js_added,$txp_using_svn;\n\nif (@txpinterface == \'admin\') {\n	register_callback(\'adi_contact_lifecycle\',\'plugin_lifecycle.adi_contact\');\n}\n\nif (txpinterface === \'public\') {\n	register_callback(\'adi_contact_db_update\',\'zemcontact.submit\');\n	register_callback(\'adi_contact_mailchimp_update\',\'zemcontact.submit\');\n    register_callback(\'adi_contact_deliver\',\'zemcontact.deliver\');\n}\n\nfunction adi_contact_lifecycle($event,$step) {\n// enforce the load order\n	if ($step == \'installed\')\n		$result = safe_update(\'txp_plugin\',\"load_order = 9\",\"name = \'adi_contact\'\");\n}\n\nfunction adi_contact_javascript() {\n// Javascript function for injecting error classes\n\n	$javascript = <<<END_SCRIPT\n<script type=\"text/javascript\">\nfunction addErrorClass(list,thisFunction) {\n\n	var zemRequiredClass = \'zemRequirederrorElement errorElement\', zemErrorElementClass = \'errorElement\' , adiContactClass = \'adi_contact_error\';\n\n	if (thisFunction === undefined)\n		thisClass = \'\';\n	else\n		thisClass = thisFunction + \'_error\';\n	thisClass = thisClass + \' \' + adiContactClass;\n	if (thisFunction == \'adi_contact_combo\')\n		thisClass = thisClass + \' \' + zemRequiredClass;\n	if (thisFunction == \'adi_contact_validate\')\n		thisClass = thisClass + \' \' + zemErrorElementClass;\n	if (thisFunction == \'adi_contact_spam\')\n		thisClass = thisClass + \' \' + zemErrorElementClass;\n\n	// former method of identifying form don\'t work all the time - if zcr input & error forms are separate then the <form> tags lose their id!\n	// var formId = window.location.hash.substr(1); // convert #zcr... to zcr...\n	// var thisForm = document.getElementById(formId);\n\n	// new method\n	// get #zcr... anchor from URL\n	var anchor = window.location.hash;\n\n	// find <form> that has the action=\"\" that matches the #zcr\n	var formsArray = document.getElementsByTagName(\"form\");\n	for (i=0; i<formsArray.length; i++) {\n		var action = formsArray[i].getAttribute(\'action\');\n		var pattern = RegExp(anchor,\'i\'); // i.e. /#zcr.../i\n		if (pattern.test(action)) // is #zcr... in form action=\"blah blah\"?\n			thisForm = formsArray[i];\n    }\n\n	// apply error class to list\n	var labels = thisForm.getElementsByTagName(\'label\'); // find all <label> in this form\n	var types = [\"input\",\"textarea\",\"select\"]; // tag types we\'re interested in\n	for (var x = 0; (thisType = types[x]); x++) { // for each tag type\n		var tags = thisForm.getElementsByTagName(thisType); // get all instances of tag type in this form\n		for (var i = 0; (thisTag = tags[i]); i++) { // for each tag of this type\n			if (list[thisTag.name]) { // name is on the list\n				for (var j = 0; (label = labels[j]); j++) {\n					if (label.htmlFor == thisTag.id) { // label \'for\' matches input id\n						label.className += \' \' + thisClass;\n						thisTag.className += \' \' + thisClass;\n// 						console.log(\"type=\",thisTag.type,\"name=\",thisTag.name,\"id=\",thisTag.id,\"label for=\",label.htmlFor,\"classes=\",thisClass);\n					}\n				}\n			}\n		}\n	}\n\n}\n</script>\nEND_SCRIPT;\n\n	return $javascript;\n}\n\nfunction adi_contact_insert_javascript() {\n// insert javascript function\n	global $adi_contact_js_added;\n\n	// only want to insert javascript function once\n	if (isset($adi_contact_js_added))\n		return \'\';\n	else {\n		$adi_contact_js_added = \'\';\n		return adi_contact_javascript();\n	}\n}\n\nfunction adi_contact_literal($list) {\n// create string in form of an object literal {\"field1\":1,\"field2\":1 ... }\n	$list = explode(\',\',$list); // convert comma separated list to array\n	foreach ($list as $index => $value) // create literal as array\n		$literal[] = \'\"\'.$value.\'\":1\';\n\n	return \'{\'.join(\',\',$literal).\'}\'; // return as {comma separated list}\n}\n\nfunction adi_contact_message($default_msg,$user_msg) {\n// generate zem_contact error message\n// ZCR will strip out duplicate error messages in $zem_contact_error\n	global $zem_contact_error,$label_list,$number,$require_label_list;\n\n	if ($user_msg) {\n		$user_msg = str_replace(\'[NAMES]\',$label_list,$user_msg);\n		$user_msg = str_replace(\'[REQUIRE]\',$require_label_list,$user_msg);\n		$user_msg = str_replace(\'[NUMBER]\',$number,$user_msg);\n		$zem_contact_error[] = $user_msg;\n	}\n	else\n		$zem_contact_error[] = $default_msg;\n}\n\nfunction adi_contact_get_labels($name_list) {\n// translate list of names into list of labels\n	global $adi_contact_labels,$zem_contact_labels;\n\n	$label_list = array();\n\n	if (!isset($adi_contact_labels)) // initialise to empty array (<txp:adi_contact_labels not used or not placed before <txp:adi_contact_combo /> etc.\n		$adi_contact_labels = array();\n\n	foreach ($name_list as $index => $value) // try three options\n		if (array_key_exists($value,$adi_contact_labels)) // 1. user supplied label via adi_contact_labels tag\n			$label_list[] = $adi_contact_labels[$value];\n		else if (isset($zem_contact_labels)) // check for presence of label supplied by submitted form\n			if (array_key_exists($value,$zem_contact_labels)) // 2. use label supplied by submitted form\n				$label_list[] = $zem_contact_labels[$value];\n			else // 3. use the name itself, but pretty it up\n				$label_list[] = ucfirst(strtolower($value));\n		else // 3. use the name itself, but pretty it up\n			$label_list[] = ucfirst(strtolower($value));\n\n	return join(\', \',$label_list);\n}\n\nfunction adi_contact_spam($atts) {\n// tag to check form fields for mail headers and link tags\n	global $zem_contact_form,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$adi_contact_labels,$label_list;\n\n	extract(lAtts(array(\n		\'names\'					=> \'\',	// list of form fields we\'re interested in (default = all submitted fields)\n		\'exclude\'				=> \'\',	// list of form fields to be ignored (default = none)\n		\'message\'				=> \'\',	// alternative error message (with [NAMES] & [NUMBER])\n		\'detect_mail_headers\'	=> \'1\',\n		\'detect_link_tags\'		=> \'1\',\n		\'allow_www\'				=> \'0\',	// allow presence of simple www.... links\n		\'debug\'					=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	if ($names) {\n		$name_list = explode(\',\',$names); // convert comma separated list to array\n		foreach ($name_list as $index => $field) // remove extraneous spaces\n			$name_list[$index] = trim($field);\n	}\n	else // get list of all submitted fields\n		isset($zem_contact_values) ?\n			$name_list = array_keys($zem_contact_values) :\n			$name_list = array();\n	if ($exclude) {\n		$exclude_list = explode(\',\',$exclude); // convert comma separated list to array\n		foreach ($exclude_list as $index => $field) // remove extraneous spaces\n			$exclude_list[$index] = trim($field);\n	}\n	else\n		$exclude_list = array();\n	$original_name_list = $name_list; // remember original list of fields\n	$name_list = array_diff($name_list,$exclude_list); // include minus exclude\n	if ($name_list) { // there\'s work to do\n		// link tags\n		$allow_www ?\n			$link_tags = \'/(\\[url|http|href)/i\' :\n			$link_tags = \'/(\\[url|http|www|href)/i\';\n		$link_tag_list = str_replace(\'/(\\\\\',\'\',$link_tags);\n		$link_tag_list = str_replace(\')/i\',\'\',$link_tag_list);\n		$link_tag_list = str_replace(\'|\',\', \',$link_tag_list);\n		// mail headers\n		$mail_headers = \'/(to\\:|from\\:|bcc\\:|multipart|cc\\:|Content\\-Transfer\\-Encoding\\:|Content\\-Type\\:|Mime\\-Version\\:)/i\';\n		$mail_headers_list = str_replace(\'/(\',\'\',$mail_headers);\n		$mail_headers_list = str_replace(\')/i\',\'\',$mail_headers_list);\n		$mail_headers_list = str_replace(\'\\\\\',\'\',$mail_headers_list);\n		$mail_headers_list = str_replace(\'|\',\', \',$mail_headers_list);\n		$mail_header_error = FALSE;\n		$link_tag_error = FALSE;\n		$error_list = array();\n		foreach ($name_list as $field => $value) { // scan fields for spam\n			if ($detect_mail_headers)\n				if (preg_match($mail_headers,$zem_contact_values[$value])) {\n					$mail_header_error = TRUE;\n					$error_list[] = $value; // add field name to list\n				}\n			if ($detect_link_tags)\n				if (preg_match($link_tags,$zem_contact_values[$value])) {\n					$link_tag_error = TRUE;\n					$error_list[] = $value; // add field name to list\n				}\n		}\n		$label_list = adi_contact_get_labels($error_list); // create list of labels for spam fields\n		if ($mail_header_error)\n			adi_contact_message(\"Mail header injection detected in &#8220;<strong>$label_list</strong>&#8221; ($mail_headers_list not allowed).\",$message);\n		if ($link_tag_error)\n			adi_contact_message(\"Link detected in &#8220;<strong>$label_list</strong>&#8221; ($link_tag_list not allowed).\",$message);\n		$error_fields = adi_contact_literal(join(\',\',$error_list)); // don\'t want spaces in list\n		if ($mail_header_error || $link_tag_error)\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$error_fields.\',\"\'.__FUNCTION__.\'\")</script>\';\n	}\n	if ($debug) {\n		adi_contact_debug(__FUNCTION__,$atts,$name_list);\n		echo \"FIELD LIST:<br/>\";\n		dmp($original_name_list);\n		echo \"EXCLUDE LIST:<br/>\";\n		dmp($exclude_list);\n		if (isset($error_list)) {\n			echo \"FIELD NAMES WITH SPAM ERRORS:<br/>\";\n			dmp($error_list);\n			echo \"SPAM FIELD LABELS:<br/>\";\n			dmp($label_list);\n		}\n	}\n	return $return;\n\n}\n\nfunction adi_contact_labels($atts) {\n// tag to record name/label mappings\n	global $adi_contact_labels;\n\n	$adi_contact_labels = $atts;\n}\n\nfunction adi_contact_checkboxes($atts) {\n// tag to manually define which fields are checkboxes\n	global $zem_contact_submit,$adi_contact_is_checkbox,$zem_contact_values;\n\n	extract(lAtts(array(\n		\'names\'	=> \'\',	// list of form fields that are checkboxes\n		\'debug\'	=> \'0\'\n	), $atts));\n\n	$adi_contact_is_checkbox = array();\n\n	if ($names) {\n		$name_list = explode(\',\',$names); // convert comma separated list to array\n		foreach ($name_list as $index => $field) // remove extraneous spaces\n			$name_list[$index] = trim($field);\n		foreach ($name_list as $index => $field_name)\n			$adi_contact_is_checkbox[$field_name] = TRUE;\n	}\n\n	if ($debug)\n		adi_contact_debug(__FUNCTION__,$atts,$name_list);\n}\n\nfunction adi_contact_error($atts) {\n// insert an extra <span>item</span> at the beginning of $zem_contact_error array - to masquerade as a introductory message (will be output as the first <li>)\n	global $zem_contact_error;\n\n	extract(lAtts(array(\n		\'preamble\'	=> \'\',		// text to precede error messages\n	), $atts));\n\n	if ($preamble && $zem_contact_error)\n		array_unshift($zem_contact_error,\'<span>\'.htmlentities($preamble).\'</span\');\n}\n\nfunction adi_contact_sed_is_base64_encoded($text) {\n	$text = strtr($text, array(\"\\n\"=>\'\', \"\\r\"=>\'\', \"\\t\"=>\'\', \' \'=>\'\'));\n	$b64_all   = \'A-Za-z0-9+/\';\n	$b64_mini  = \'AEIMQUYcgkosw048\';\n	$b64_micro = \'AQgw\';\n\n	$b64_pat = \"@\n	^                                         # Start of text followed by...\n	(?: [$b64_all]{4})*                       # Any number of groups of 4 chars from all chars.\n	(?:                                       # ...optionally (see ? at end of this subpattern) followed by...\n		[$b64_all]{2} [$b64_mini] =           #   Any two chars from all, then one from mini, then \'=\'\n	|                                         #   -- or --\n		[$b64_all]    [$b64_micro] ==         #   one from all, then one from micro then two \'=\'s.\n	)?                                        # ...followed by...\n	\\$                                        # End of text\n	@x\";\n\n	$b64 = (bool)preg_match($b64_pat, $text);\n	return $b64;\n}\n\nfunction adi_contact_validate($atts) {\n// tag to validate form field contents\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_values,$adi_contact_js_added;\n\n	extract(lAtts(array(\n		\'names\'				=> \'\',		// list of form fields we\'re interested in\n		\'type\'				=> \'\',		// \'alpha\', \'alphanum\', \'integer\', \'numeric\', \'equal\', \'base64\' etc\n		\'values\'			=> \'\',		// list of acceptable values\n		\'allow_whitespace\'	=> \'0\',		// allow spaces as well as \'type\'\n		\'pattern\'			=> \'\',		// custom pattern used with type=\"custom\'\n		\'message\'			=> \'\',		// alternative error message (with [NAMES])\n		\'debug\'				=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	// tidy up supplied attributes\n	$name_list = explode(\',\',$names); // convert comma separated list to array\n	foreach ($name_list as $index => $field) // remove extraneous spaces\n		$name_list[$index] = trim($field);\n\n	$invalids = array();\n	$valid_values = array();\n\n	if ($zem_contact_submit) { // form has been submitted\n\n		$ok = TRUE; // benefit of the doubt\n		if (isset($zem_contact_form))\n			if ($type == \'equal\') {\n				$undefined_field_value = \'thechancesofsomeonetypingthisinisextremelyremote...bim\';\n				$ok = (count($name_list) > 1); // must be at least two fields supplied\n				if ($ok) {\n					// remember first field value\n					if (array_key_exists($name_list[0], $zem_contact_values))\n						$first_field_value = $zem_contact_values[$name_list[0]];\n					else\n						$first_field_value = $undefined_field_value;\n					// compare all field values with the first\n					foreach ($name_list as $index => $field) {\n						if (array_key_exists($field, $zem_contact_values))\n							$this_field_value = $zem_contact_values[$field];\n						else\n							$this_field_value = $undefined_field_value;\n						$ok &= strcmp($this_field_value,$first_field_value) == 0;\n					}\n					if (!$ok) { // add to list of invalids ??? TEST THIS - ORIGINALLY BODGIED UP IN OBI\n						$invalids[] = $name_list[0]; // first field\n						$invalids[] = $field; // this field\n					}\n				}\n			}\n			else if ($values != \'\') {\n				$type = \'\';\n				$valid_values = explode(\',\',$values);\n				foreach ($name_list as $field) {\n					if (array_key_exists($field, $zem_contact_values))\n						$this_field_value = $zem_contact_values[$field];\n					else\n						$this_field_value = \'\';\n					$ok = (array_search($this_field_value,$valid_values) !== FALSE);\n					if (!$ok) { // add to list of invalids\n						$invalids[] = $field; // this field\n					}\n				}\n			}\n			else\n				foreach ($name_list as $index => $field) {\n					if (array_key_exists($field, $zem_contact_values)) {\n						$field_value = $zem_contact_values[$field];\n						if ($allow_whitespace)\n							$field_value = preg_replace(\'/[\\s]/\',\'\', $field_value);\n						switch ($type) {\n							case \'alpha\':\n								$ok &= ctype_alpha($field_value);\n								break;\n							case \'alphanum\':\n								$ok &= ctype_alnum($field_value);\n								break;\n							case \'integer\':\n								$ok &= ctype_digit($field_value);\n								break;\n							case \'numeric\':\n								$ok &= is_numeric($field_value);\n								break;\n							case \'phone\':\n								$ok &= preg_match(\"/^[0-9x\\ \\+\\-\\(\\)\\.,]*$/i\",$field_value);\n								break;\n							case \'url\':\n								$ok &= preg_match(\"/^(?:http:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,6}$/i\",$field_value);\n								break;\n							case \'base64\':\n								$ok &= adi_contact_sed_is_base64_encoded($field_value);\n								break;\n							case \'custom\':\n								if ($pattern)\n									$ok &= preg_match($pattern,$field_value);\n								break;\n							default:\n								break;\n						}\n						if (!$ok)\n							$invalids[] = $field; // add to list of invalids\n					}\n				}\n		if (!$ok) {\n			$label_list = adi_contact_get_labels($invalids); // create list of labels\n			switch ($type) {\n				case \'alpha\':\n					adi_contact_message(\"Non-alphabetical characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'alphanum\':\n					adi_contact_message(\"Non-alphanumeric characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'integer\':\n					adi_contact_message(\"Non-digits found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'numeric\':\n					adi_contact_message(\"Non-numeric characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'phone\':\n					adi_contact_message(\"Only 0-9 + - . , () xX allowed in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'url\':\n					adi_contact_message(\"Invalid URL in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'base64\':\n					adi_contact_message(\"Invalid Base64 encoded characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'custom\':\n					adi_contact_message(\"Invalid data in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'equal\':\n					adi_contact_message(\"&#8220;<strong>$label_list</strong>&#8221; are not the same\",$message);\n					break;\n				default:\n					adi_contact_message(\"Data in &#8220;<strong>$label_list</strong>&#8221; is not recognised\",$message); //??? IMPROVE THIS\n					break;\n			}\n			$invalid_list = adi_contact_literal(join(\',\',$invalids));\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$invalid_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n		if ($debug) {\n			adi_contact_debug(__FUNCTION__,$atts,$name_list);\n			if ($valid_values) {\n				echo \'VALID VALUES:<br/>\';\n				dmp($valid_values);\n			}\n			echo \'INVALID LIST:<br/>\';\n			dmp($invalids);\n		}\n	}\n	return $return;\n}\n\nfunction adi_contact_combo($atts) {\n// tag to check for specified field combinations\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$adi_contact_js_added,$adi_contact_is_checkbox;\n\n	extract(lAtts(array(\n		\'names\'				=> \'\',		// list of form fields we\'re interested in\n		\'require\'			=> \'\',		// list of form fields required\n		\'restrict\'			=> \'\',		// \'min\', \'max\' or \'equal\'\n		\'number\'			=> \'0\',		// number of fields required\n		\'value\'				=> \'\',		// pertinent value to trigger a \'require\'\n		\'case_sensitive\'	=> \'0\',		// when checking value\n		\'message\'			=> \'\',		// alternative error message (with [NAMES],[NUMBER],[REQUIRE])\n		\'required\'			=> \'0\',		// forces restrict=\"equal\" number=\"1\", e.g. for custom error messages or checkboxes that must be ticked\n		\'checkbox\'			=> \'0\',		// indicates that fields are checkboxes (i.e. check for \'Yes\' or \'No\' - in the appropriate language) - DEPRECATED\n		\'debug\'				=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	// just in case adi_contact_checkboxes hasn\'t been called\n	if (!isset($adi_contact_is_checkbox))\n		$adi_contact_is_checkbox = array();\n\n	// tidy up supplied attributes\n	$name_list = explode(\',\',$names); // convert comma separated list to array\n	foreach ($name_list as $index => $field) // process fields of interest\n		$name_list[$index] = trim($field); // remove extraneous spaces\n	$label_list = adi_contact_get_labels($name_list); // put field list back together tidily spaced out: field1, field2, field3\n	$require_list = explode(\',\',$require); // convert comma separated list to array\n	foreach ($require_list as $index => $field) // remove extraneous spaces\n		$require_list[$index] = trim($field);\n	$require_label_list = adi_contact_get_labels($require_list); // put field list back together tidily spaced out: field1, field2, field3\n	$restrict = strtolower($restrict);\n	if ($required) { // set other attribute values\n		$restrict = \'equal\';\n		$number = \'1\';\n	}\n	if ($checkbox) // old checkbox attribute is no longer used, so force the issue\n		echo \"<b>adi_contact_combo tag error:</b> The checkbox attribute is no longer valid (use adi_contact_checkboxes tag instead).\";\n\n	// initial settings\n	$field_count = 0;\n	$restrict_error = FALSE;\n	$require_error = FALSE;\n	$switch_name_require_list = FALSE;\n	$missing_required = array();\n\n	if ($zem_contact_submit) { // form has been submitted\n\n		foreach ($name_list as $index => $name) { // go through supplied fields of interest\n\n			$flagged = FALSE; // flag will be set if field filled in or it matches supplied value\n\n			if (isset($zem_contact_values)) // NULL if all fields empty on submit!\n				if (array_key_exists($name, $zem_contact_values)) { // see if field has been submitted\n					if ($value == \'\') { // anything (or a tick) will do\n						if (array_key_exists($name,$adi_contact_is_checkbox)) { // it\'s a checkbox\n							if (strcasecmp($zem_contact_values[$name],gTxt(\'yes\')) == 0) // it\'s ticked\n								$flagged = TRUE;\n						}\n						else\n							if ($zem_contact_values[$name] != \'\') // field filled in\n								$flagged = TRUE;\n					}\n					else { // specific value required\n						if ($case_sensitive)\n							$flagged = ($zem_contact_values[$name] == $value);\n						else\n							$flagged = (strtolower($zem_contact_values[$name]) == strtolower($value));\n					}\n				}\n\n			if ($flagged)\n				if ($require && $restrict) { // SPECIAL CASE: require list contains \"secondary\" fields of interest\n					foreach ($require_list as $index => $required_field) { // see if they\'re filled\n						if (array_key_exists($required_field,$adi_contact_is_checkbox)) {\n							if (strcasecmp($zem_contact_values[$required_field],gTxt(\'yes\')) == 0) // \"filled in\" if set to \'Yes\', so that\'s OK then\n								$field_count += 1;\n						}\n						else if (array_key_exists($required_field, $zem_contact_values))\n								$field_count += 1;\n					}\n					$switch_name_require_list = TRUE; // will need to change list later for error message\n				}\n				else if ($require) { // SIMPLE REQUIREMENT\n					foreach ($require_list as $index => $required_field) {\n						if (array_key_exists($required_field,$adi_contact_is_checkbox)) {\n							if (strcasecmp($zem_contact_values[$required_field],gTxt(\'yes\')) == 0) // \"filled in\" if set to \'Yes\', so that\'s OK then\n								continue;\n						}\n						else if (array_key_exists($required_field, $zem_contact_values))\n							continue;\n						// not filled in then!\n						$require_error = TRUE;\n						$missing_required[] = $required_field; // list the missing\n					}\n				}\n				else if ($restrict) { // SIMPLE RESTRICTION\n					$field_count += 1;\n				}\n\n		}\n\n		// SPECIAL CASE: require list contains \"secondary\" fields of interest\n		if ($require && $restrict)\n			if ($switch_name_require_list) { // \"secondary\" fields of interest have come into play\n				$name_list = $require_list;\n				$label_list = adi_contact_get_labels($require_list);\n			}\n			else // forget about restriction, coz it don\'t apply i.e. no flags raised\n				$restrict = \'\';\n\n		// \'restrict\' mode error messages\n		switch ($restrict) {\n			case \'min\':\n				if ($field_count < $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"At least $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			case \'max\':\n				if ($field_count > $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"Maximum $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			case \'equal\':\n				if ($field_count != $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"Exactly $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			default:\n				break;\n		}\n\n		if ($debug) {\n			adi_contact_debug(__FUNCTION__,$atts,$name_list);\n			echo \"LABELS OF INTEREST:<br/>\";\n			dmp($label_list);\n			if ($restrict) {\n				if ($number) {\n					echo \"VALID FIELD COUNT:<br/>\";\n					echo \'<pre>\'.$field_count.\'</pre>\';\n				}\n			}\n			if ($require) {\n				echo \"REQUIRED FIELDS:<br/>\";\n				dmp($require_list);\n				if (isset($missing_required))\n				{\n					echo \"MISSING REQUIRED FIELDS:<br/>\";\n					dmp($missing_required);\n				}\n			}\n		}\n\n		// finally some javascript action\n		if ($restrict_error) {\n			$missing_list = adi_contact_literal(join(\',\',$name_list)); // don\'t want spaces in list\n			if ($debug)\n				echo \"MISSING LIST:<br/><pre>$missing_list</pre>\";\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$missing_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n		else if ($require_error) { // \'require\' error messages\n			adi_contact_message(\"If &#8220;<strong>$label_list</strong>&#8221; supplied then &#8220;<strong>$require_label_list</strong>&#8221; required.\",$message);\n			$missing_list = adi_contact_literal(join(\',\',$missing_required)); // don\'t want spaces in list\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$missing_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n	}\n	return $return;\n}\n\nfunction adi_contact_headers($atts) {\n// tag for extra headers, e.g. CC or BCC\n// adi_contact_deliver() does the actual doing\n	global $adi_contact_headers;\n\n	extract(lAtts(array(\n		\'cc\'	=> \'\',		// CC email address\n		\'bcc\'	=> \'\',		// BCC email address\n	), $atts));\n\n	if (!isset($adi_contact_headers))\n		$adi_contact_headers = array();\n\n	$adi_contact_headers[\'cc\'] = $cc;\n	$adi_contact_headers[\'bcc\'] = $bcc;\n\n	return;\n}\n\nfunction adi_contact_body($atts) {\n// for appending extra information onto end of email\n// accepts string (internal function use) OR array (tag mode)\n// adi_contact_deliver() does the actual doing\n	global $adi_contact_body,$zem_contact_submit;\n\n	if (!isset($adi_contact_body))\n		$adi_contact_body = array(\'\'); // start a new line otherwise extra body tacked onto end of last field/value line\n\n	if (is_array($atts))\n		extract(lAtts(array(\n			\'line\'		=> \'\',	// a line to add to the body\n		), $atts));\n	else\n		$line =$atts;\n\n	if($zem_contact_submit) // don\'t add unless form submitted (to avoid combining lines from separate forms on same page)\n		$adi_contact_body[] = $line;\n\n	return;\n}\n\nfunction adi_contact_deliver($event,$step,&$payload) {\n// modify the email $payload (event=\"zemcontact.deliver\",step=\"send/copysender\")\n	global $adi_contact_headers,$adi_contact_body;\n\n	// extra headers - provided by adi_contact_headers()\n	if ($adi_contact_headers[\'cc\'] != \'\')\n		$payload[\'headers\'][\'Cc\'] = $adi_contact_headers[\'cc\'];\n	if ($adi_contact_headers[\'bcc\'] != \'\')\n		$payload[\'headers\'][\'Bcc\'] = $adi_contact_headers[\'bcc\'];\n\n	if ($step !== \'copysender\') { // making big assumption that copy of email going to form submitter shouldn\'t contain extra body (e.g. MailChimp diags)\n		// extra body (message text already formed at this point, so simply adding extra lines to email)\n		if ($adi_contact_body) {\n			foreach ($adi_contact_body as $line)\n				$payload[\'body\'] .= $line.\"\\n\";\n		}\n	}\n\n    return \'zemcontact.send\'; // I command thee to send thy email\n}\n\nfunction adi_contact_db($atts,$thing=\'\') {\n// database mechanism enclosing tag\n	global $zem_contact_submit,$zem_contact_error,$adi_contact_db_table,$production_status;\n\n	// CREATE TABLE contacts (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`timestamp` datetime NOT NULL default \'0000-00-00 00:00:00\',`fullname` VARCHAR(255) NOT NULL,`email` VARCHAR(255) NOT NULL,`mailout` VARCHAR(32) NOT NULL default \'\',`general` VARCHAR(16) NOT NULL default \'\',`sales` VARCHAR(16) NOT NULL default \'\',`technical` VARCHAR(16) NOT NULL default \'\',`feedback` VARCHAR(16) NOT NULL default \'\',`region` VARCHAR(32) NOT NULL default \'\');\n\n	extract(lAtts(array(\n		\'table\'		=> \'\',			// the database table\n 		\'form\'		=> \'\',			// the adi_contact_db form\n// 		\'mode\'		=> \'insert\',	// insert or update\n	), $atts));\n\n	$diags = array();\n\n	$adi_contact_db_data = array(); // array indexed by db_field name: [\'contact_field\'] => contact field name, [\'default\'] => default value (if supplied)\n	$adi_contact_db_table = $table;\n\n	// validate supplied table\n	if (($production_status === \'debug\') && ($table != \'\')) {\n		$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n		$a = nextRow($rs);\n		if ($a)\n			$diags[] = \"table - \'$table\' found in database\";\n		else\n			$diags[] = \"table - \'$table\' NOT FOUND in database\";\n	}\n	if ($table == \'\')\n		$diags[] = \'table not set\';\n\n	// form submit status\n	if ($zem_contact_submit)\n		$diags[] = \'form - submitted\';\n	else\n		$diags[] = \'form - not submitted\';\n	if ($zem_contact_error)\n		$diags[] = \'form - submit errors\';\n\n	adi_contact_trace($diags);\n\n	// do the doing\n	$form = ($form) ? fetch_form($form) : $thing; // only get tag errors about invalid DB fields if using form (i.e. not enclosing tag)?\n\n	return parse($form);\n}\n\nfunction adi_contact_db_data($atts) {\n// contact form/database field mapping tag - updates $adi_contact_db_data\n	global $zem_contact_submit,$zem_contact_labels,$production_status,$adi_contact_db_table,$adi_contact_db_data;\n\n	extract(lAtts(array(\n		\'db_field\'		=> \'\',	// the database field (required)\n		\'contact_field\'	=> \'\',	// the contact form input field (default same name as db_field)\n		\'default\'		=> \'\',	// optional default value\n		\'timestamp\'		=> \'\'	// database field is a timestamp (datetime, date or timestamp)\n	), $atts));\n\n	$diags = array();\n\n	// contact field same as database field?\n	if (($contact_field == \'\') && !$timestamp)\n		$contact_field = $db_field;\n\n	// validate contact form field\n	if ($zem_contact_submit) {\n		if (isset($zem_contact_labels[$contact_field]))\n			$diags[] = \"field - \'$contact_field\' found in contact form\";\n		else\n			$diags[] = \"field - \'$contact_field\' NOT FOUND OR BLANK in contact form\";\n	}\n\n	// check required attributes\n	if ($db_field == \'\')\n		$diags[] = \"db_field - ATTRIBUTE MISSING\";\n	else {\n		// db column -> contact field mapping\n		$adi_contact_db_data[$db_field][\'contact_field\'] = $contact_field;\n\n		// default values\n		if (isset($atts[\'default\'])) // default value supplied\n			$adi_contact_db_data[$db_field][\'default\'] = $default;\n		if (isset($atts[\'timestamp\'])) { // set timestamp according to MySQL formats: DATETIME, DATE, TIMESTAMP\n			$timestamp = strtolower($timestamp);\n			switch ($timestamp) {\n				case \'date\': // yyyy-mm-dd\n					$adi_contact_db_data[$db_field][\'default\'] = date(\"Y-m-d\",time()+tz_offset());\n					break;\n				case \'timestamp\': // UTC yyyy-mm-dd hh:mm:ss\n					$adi_contact_db_data[$db_field][\'default\'] = gmstrftime(\"%Y-%m-%d %H:%M:%S\");\n					break;\n				default: // default to \"datetime\" - yyyy-mm-dd hh:mm:ss\n					$adi_contact_db_data[$db_field][\'default\'] = date(\"Y-m-d H:i:s\",time()+tz_offset());\n					break;\n			}\n		}\n\n		$diags[] = \"map - \'\".$adi_contact_db_data[$db_field][\'contact_field\'].\"\' -> db \'$db_field\'\".(isset($adi_contact_db_data[$db_field][\'default\']) ? \' (default = \"\'.$adi_contact_db_data[$db_field][\'default\'].\'\")\' : \'\');\n\n		// validate database column\n		if ($production_status === \'debug\') {\n			if (isset($adi_contact_db_table)) {\n				$rs = safe_query(\'SHOW COLUMNS FROM `\'.safe_pfx($adi_contact_db_table).\"` WHERE FIELD = \'$db_field\'\");\n				if ($rs !== FALSE) { // table exists\n					$a = nextRow($rs);\n					if ($a)\n						$diags[] = \"column - \'$db_field\' found in table \'$adi_contact_db_table\'\";\n					else\n						$diags[] = \"column - \'$db_field\' NOT FOUND in table \'$adi_contact_db_table\'\";\n				}\n			}\n		}\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_db_update() {\n// update database - called back during form submit\n	global $zem_contact_values,$adi_contact_db_table,$adi_contact_db_data;\n\n	$diags = array();\n\n	if (!isset($adi_contact_db_table)) return; // adi_contact_db hasn\'t been called so bail out\n\n	// check spammage\n	$evaluation =& get_zemcontact_evaluator();\n	if ($evaluation->get_zemcontact_status()) {\n		$diags[] = \"spam evaluation - DIRTY\";\n		return;\n	}\n\n	// build set\n	$set = array();\n	if ($adi_contact_db_data) {\n		foreach ($adi_contact_db_data as $db_field => $map) {\n			if (isset($zem_contact_values[$map[\'contact_field\']]))\n				$set[]= \"`$db_field`=\'\".doSlash($zem_contact_values[$map[\'contact_field\']]).\"\'\"; // use value from contact form\n			else if (isset($map[\'default\']))\n				$set[]= \"`$db_field`=\'\".doSlash($map[\'default\']).\"\'\"; // use supplied default value\n// 			else\n// 				let the database fill in its own default\n		}\n	}\n	else\n		$diags[] = \"map - EMPTY\";\n\n	// update the database\n	if ($set && $adi_contact_db_table) {\n		$diags[] = \'set - \'.implode(\', \',$set);\n		if (!safe_insert($adi_contact_db_table,implode(\',\',$set)))\n			$diags[] = \"safe_insert: FAIL\";\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp($atts,$thing=NULL) {\n// MailChimp subscription container tag\n	global $zem_contact_submit,$zem_contact_error,$adi_contact_mailchimp_data,$adi_contact_mailchimp_info;\n\n	extract(lAtts(array(\n		\'api_key\'		=> \'\',				// MailChimp API key (required)\n 		\'list_id\'		=> \'\',				// MailChimp list ID (required)\n 		\'status\'		=> \'pending\',		// \'subscribed\', \'unsubscribed\', \'pending\', \'cleaned\'\n 		\'opt_in_field\'	=> \'mailing_list\',	// contact form field name, used to trigger MailChimp action\n  		\'opt_in_value\'	=> gTxt(\'yes\'),		// contact form field value, used to trigger MailChimp action (defaults to checkbox value)\n		\'form\'			=> \'\',				// adi_contact_mailchimp form\n	), $atts));\n\n	// status values\n	// 		\'pending\' - confirmation email will be sent to user\n	//		\'subscribed\' - subscribed immediately, no confirmation sent/required\n\n	$diags = array();\n\n	$default_form = \'<txp:adi_contact_mailchimp_email />\';\n\n	// array containing data required for MailChimp connect (api_key, list_id, email, status, merge_fields) as well as opt-in info\n	$adi_contact_mailchimp_info = array(\'api_key\'=>$api_key,\'list_id\'=>$list_id,\'status\'=>$status,\'opt_in_field\'=>$opt_in_field,\'opt_in_value\'=>$opt_in_value);\n	// array indexed by MailChimp field (email,FNAME,LNAME): [\'contact_field\'] => contact field name, [\'default\'] => default value (DEFAULT VALUES NOT CURRENTLY USED)\n	$adi_contact_mailchimp_data = array();\n\n	// check attributes\n	if ($api_key == \'\')\n		$diags[] = \'API key missing\';\n	if ($list_id == \'\')\n		$diags[] = \'list ID missing\';\n	if ($status == \'\')\n		$diags[] = \'status missing\';\n\n	// form submit status\n	if ($zem_contact_submit)\n		$diags[] = \'form - submitted\';\n	else\n		$diags[] = \'form - not submitted\';\n	if ($zem_contact_error)\n		$diags[] = \'form - submit errors\';\n\n	adi_contact_trace($diags);\n\n	// form guide\n	$form = ($form) ? fetch_form($form) : ($thing === NULL ? $default_form : $thing);\n\n	return parse($form);\n}\n\nfunction adi_contact_mailchimp_data($atts) {\n// contact form/mailchimp field mapping tag - updates $adi_contact_mailchimp_data\n	global $zem_contact_submit,$zem_contact_labels,$adi_contact_mailchimp_data;\n\n	extract(lAtts(array(\n		\'mailchimp_field\'	=> \'\',	// MailChimp field, e.g. FNAME, LNAME (required)\n		\'contact_field\'		=> \'\',	// the contact form input field (required)\n	), $atts));\n\n	$diags = array();\n\n	// validate contact form field\n	if ($zem_contact_submit) {\n		if (isset($zem_contact_labels[$contact_field]))\n			$diags[] = \"field - \'$contact_field\' found in contact form\";\n		else\n			$diags[] = \"field - \'$contact_field\' NOT FOUND OR EMPTY in contact form\";\n	}\n\n	// check required attributes\n	if ($mailchimp_field == \'\')\n		$diags[] = \"mailchimp_field - ATTRIBUTE MISSING\";\n	else if ($contact_field == \'\')\n		$diags[] = \"contact_field - ATTRIBUTE MISSING\";\n	else {\n		// mailchimp column -> contact field mapping\n		$adi_contact_mailchimp_data[$mailchimp_field][\'contact_field\'] = $contact_field;\n\n		// default values (NOT CURRENTLY USED)\n		if (isset($atts[\'default\'])) // default value supplied\n			$adi_contact_mailchimp_data[$mailchimp_field][\'default\'] = $default;\n\n		$diags[] = \"map - \'\".$adi_contact_mailchimp_data[$mailchimp_field][\'contact_field\'].\"\' -> mailchimp \'$mailchimp_field\'\".(isset($adi_contact_mailchimp_data[$mailchimp_field][\'default\']) ? \' (default = \"\'.$adi_contact_mailchimp_data[$mailchimp_field][\'default\'].\'\")\' : \'\');\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp_email($atts) {\n// mailchimp email field mapping tag\n\n	extract(lAtts(array(\n		\'contact_field\'	=> \'email\',	// the contact form input field\n	), $atts));\n\n	adi_contact_mailchimp_data(array(\'mailchimp_field\'=>\'email\', \'contact_field\'=>$contact_field));\n}\n\nfunction adi_contact_mailchimp_update() {\n// update MailChimp list - called back during form submit\n	global $zem_contact_values,$adi_contact_mailchimp_info,$adi_contact_mailchimp_data;\n\n	$diags = array();\n\n	if (!isset($adi_contact_mailchimp_info)) return; // adi_contact_mailchimp hasn\'t been called so bail out\n\n	// check spammage\n	$evaluation =& get_zemcontact_evaluator();\n	if ($evaluation->get_zemcontact_status()) {\n		adi_contact_trace(\"FAIL - spam evaluation, DIRTY\");\n		return;\n	}\n\n	// check opt-in\n	$opt_in_field = $adi_contact_mailchimp_info[\'opt_in_field\'];\n	$opt_in_value = $adi_contact_mailchimp_info[\'opt_in_value\'];\n	if ($opt_in_field != \'\') { // if opt_in_field attribute set, check it exists in contact form\n		if (!isset($zem_contact_values[$opt_in_field])) {\n			adi_contact_trace(\"FAIL - opt-in field \'$opt_in_field\' NOT FOUND\");\n			return;\n		}\n		if (strcasecmp($zem_contact_values[$opt_in_field],$opt_in_value) != 0) { // opt_in_field value matches opt_in_value attribute?\n			$opt_in_field_value = $zem_contact_values[$opt_in_field];\n			adi_contact_trace(\"DISABLED - opt-in field \'$opt_in_field\' value \'$opt_in_field_value\', doesn\'t match opt_in_value \'$opt_in_value\'\");\n			return;\n		}\n	}\n\n	// build up the chimp\n	$adi_contact_mailchimp_info[\'email\'] = \'\';\n	$adi_contact_mailchimp_info[\'merge_fields\'] = array();\n	if ($adi_contact_mailchimp_data) {\n		foreach ($adi_contact_mailchimp_data as $mailchimp_field => $map) {\n			if ($mailchimp_field == \'email\') { // the email\n				if (isset($zem_contact_values[$map[\'contact_field\']]))\n					$adi_contact_mailchimp_info[\'email\'] = $zem_contact_values[$map[\'contact_field\']]; // use email from contact form;\n			}\n			else { // merge fields\n				$value = (isset($zem_contact_values[$map[\'contact_field\']]) ? $zem_contact_values[$map[\'contact_field\']] : \'\');\n				$adi_contact_mailchimp_info[\'merge_fields\'][$mailchimp_field] = $value;\n			}\n		}\n	}\n	else\n		$diags[] = \"map - EMPTY\";\n\n	// update the chimp\n	if ($adi_contact_mailchimp_info[\'email\']) { // need an email address as bare minimum\n		$diag_info = $adi_contact_mailchimp_info;\n		unset($diag_info[\'merge_fields\']);\n		$diags[] = \'info - \'.implode(\', \',$diag_info);\n		$diags[] = \'merge_fields - \'.implode(\', \',$adi_contact_mailchimp_info[\'merge_fields\']);\n		$mc_result = adi_contact_mailchimp_connect($adi_contact_mailchimp_info);\n		$email_msg = \"MailChimp:\\n\";\n		// see adi_contact_mailchimp_connect for returns\n		if ($mc_result === FALSE) {\n			$reason = \'unable to connect\';\n			$diags[] = \'FAIL - \'.$reason;\n			$email_msg .= \"- subscribe failed ($reason)\\n\";\n		}\n		if ($mc_result === NULL) {\n			$reason = \'software error\';\n			$diags[] = \'FAIL - \'.$reason;\n			$email_msg .= \"- subscribe failed ($reason)\\n\";\n		}\n		if (is_array($mc_result))\n			if (isset($mc_result[\'title\'])) {\n				$reason = \'status \'.$mc_result[\'status\'].\', \'.$mc_result[\'title\'];\n				$diags[] = \'FAIL - \'.$reason;\n				$email_msg .= \"- subscribe failed ($reason)\\n\";\n				$diags[] = \'FAIL - \'.trim($mc_result[\'detail\']);\n				$email_msg .= \'- \'.trim($mc_result[\'detail\']).\"\\n\";\n				foreach ($mc_result[\'errors\'] as $error) {\n					$email_msg .= \"- \";\n					$error_msg = \'\';\n					foreach ($error as $index => $value)\n						$error_msg .= \"$index: $value; \";\n					$diags[] = \"ERROR - $error_msg\";\n					$email_msg .= \"$error_msg\\n\";\n				}\n			}\n			else {\n				$reason = \'status \'.$mc_result[\'status\'];\n				$diags[] = \'success - \'.$mc_result[\'status\'];\n				$email_msg .= \"- subscribed ($reason)\\n\";\n			}\n\n		// maybe attributed option to include mc diags in email or not?\n		adi_contact_body(\"\\n\"); // two blank lines\n		adi_contact_body($email_msg);\n\n	}\n	else\n		$diags[] = \'FAIL - no contact field to MailChimp \"email\" field mapping\';\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp_connect($mailchimp_info) {\n// connect to MailChimp & update mailing list\n// - much code taken from stackoverflow.com/questions/30481979/adding-subscribers-to-a-list-using-mailchimps-api-v3\n//\n// - success\n//		- return array(\'status\')\n//			status = \'pending\' or \'subscribed\' etc\n// - failure\n//		- return array(\'status\',\'title\')\n//			status = 400, 401, 404 etc\n//			title = informative summary\n//				400 - Member Exists\n//				401 - API Key Invalid\n//				404 - Resource Not Found (dodgy URL)\n//			detail = further explanation\n//			errors = optional array of field/error message\n//  	- return FALSE\n//			connection failure (e.g. timeout)\n//  	- return NULL\n//			software problem (e.g. curl functions not available)\n\n	extract($mailchimp_info); // i.e. $api_key, $list_id, $status, $email, $merge_fields[...]\n	list(,$datacentre) = explode(\'-\',$api_key); // datacentre is the \"us\" bit at end of API key\n	$url = \'https://\'.$datacentre.\'.api.mailchimp.com/3.0/lists/\'.$list_id.\'/members/\';\n\n	$data = array(\n		\'apikey\'		=> $api_key,\n		\'email_address\'	=> $email,\n		\'status\'		=> $status,\n		\'merge_fields\'	=> $merge_fields\n	);\n\n	// there seems to be an intermittant issue with MailChimp API v3.0\n	//		scenario: when merge_fields is an empty array (i.e. there are no adi_contact_mailchimp_data mappings other than \"email\")\n	//		symptoms/diags:\n	//			subscribe failed (status 400, Invalid Resource)\n	//			\"The resource submitted could not be validated. For field-specific details, see the \'errors\' array.\"\n	//			field: merge_fields; message: Schema describes object, array found instead;\n	//		intermittant: may only occur on certain servers (e.g. local & staging OK, live COL fails), possibly a PHP version/server config thing\n	//		discussed here: https://github.com/drewm/mailchimp-api/issues/54#issuecomment-171263356\n	//		fix: omit \'merge_fields\' item from $data if empty\n	if (empty($merge_fields)) unset($data[\'merge_fields\']);\n\n// 	echo \'adi_contact_mailchimp_connect:\';\n// 	dmp($data);\n\n	$json_data = json_encode($data);\n\n	// curly wurly check\n	//??? IMPLEMENT ALT CONNECTION METHOD using file_get_contents() - see smd_mailchimp\n	if (!function_exists(\'curl_init\') || !function_exists(\'curl_setopt\')) {\n		adi_contact_trace(\"FAIL - curl_init(), curl_setopt() not available\");\n		return NULL;\n	}\n\n	$ch = curl_init();\n	curl_setopt($ch, CURLOPT_URL, $url);\n	curl_setopt($ch, CURLOPT_HTTPHEADER, array(\'Content-Type: application/json\'));\n	curl_setopt($ch, CURLOPT_USERPWD, \"user:\" . $api_key);\n	curl_setopt($ch, CURLOPT_USERAGENT, \'PHP-MCAPI/2.0\');\n	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n	curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n	curl_setopt($ch, CURLOPT_POST, true);\n	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n	curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);\n\n	$result = curl_exec($ch); // FALSE = connection error, JSON data if contact made with MAilChimp\n\n	$arr = json_decode($result,TRUE); // json_decode into array\n\n// 	echo __FUNCTION__.\' response/status:\';\n// 	dmp($arr);\n\n	// not using extract() coz there\'s a shed load of variously named indexes in $arr (especially in a successful subscribe)\n	$status = \'\';\n	if (isset($arr[\'status\'])) $status = $arr[\'status\'];\n\n	$title = \'\';\n	if (isset($arr[\'title\'])) $title = $arr[\'title\'];\n\n	$detail = \'\';\n	if (isset($arr[\'detail\'])) $detail = $arr[\'detail\'];\n\n	$errors = array();\n	if (isset($arr[\'errors\'])) $errors = $arr[\'errors\'];\n\n// 	echo __FUNCTION__.\' errors:\';\n// 	dmp($errors);\n\n	// final analysis of results\n	if ($result === FALSE) // connection error, return FALSE\n		return $result;\n	else\n		if (is_int($status)) // status is a number, return descriptive error\n			return array(\'status\'=>$status,\'title\'=>$title,\'detail\'=>$detail,\'errors\'=>$errors);\n		else // return status (string)\n			return array(\'status\'=>$status);\n}\n\nfunction adi_contact_debug($function=\'\',$atts=array(),$name_list=array(),$diags=array()) {\n// debug function for debug=\"1\" attributes or can be used as a standalone tag (<txp:adi_contact_debug />) to output general diags\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$prefs,$adi_contact_is_checkbox,$adi_contact_zcr_4500;\n\n	if ($function) {\n		echo \'********** \'.strtoupper($function).\' **********\'.br.br;\n		if ($atts) {\n			echo \'SUPPLIED ATTRIBUTES:\'.br;\n			dmp($atts);\n		}\n		if ($name_list) {\n			echo \'FIELDS OF INTEREST:\'.br;\n			dmp($name_list);\n		}\n		if ($diags) {\n			echo \'DIAGNOSTICS:\'.br;\n			echo tag(implode(n,$diags),\'pre\');\n		}\n	}\n	else {\n		echo \'********** ADI_CONTACT DEBUG **********\'.br.br;\n		echo \'VERSIONS:\'.br;\n		echo \'<pre>\';\n		$plugin_list = array(\'adi_contact\',\'zem_contact_reborn\',\'pap_contact_cleaner\');\n		foreach ($plugin_list as $index => $plugin) {\n			$row = safe_row(\'version,status,load_order\',\'txp_plugin\',\"name=\'$plugin\'\");\n			if ($row) {\n				extract($row);\n				echo \"$plugin: $version\".($status?\' (active)\':\' (not active)\').\", load order $load_order\".br;\n			}\n			if (!$row || !$status) {\n				$func_list = array(\'adi_contact_combo\',\'zem_contact\',\'pap_zemcontact_form\');\n				if (function_exists($func_list[$index]))\n					echo $plugin.\': in plugin cache, load order 0\'.br;\n				else\n					echo $plugin.\': not installed\'.br;\n			}\n		}\n		echo \'TXP: \'.$prefs[\'version\'].br;\n		echo \'PHP: \'.phpversion().br;\n		echo \'MySQL: \'.mysql_get_server_info().br;\n		echo \'Language: yes = \'.gTxt(\'yes\').br;\n		echo \'TXP date/time = \'.date(\"Y-m-d H:i:s\",time()+tz_offset()); // this is actual (TXP adjusted) local time (incl DST) regardless of server timezone\n		echo \'</pre>\';\n		echo \'ZEM CONTACT FORM:\'.br;\n		echo \'<pre>\';\n		if (isset($_POST[\'zem_contact_form_id\']))\n			echo \'id = \'.$_POST[\'zem_contact_form_id\'].br;\n		if (isset($_POST[\'zem_contact_nonce\']))\n			echo \'nonce = \'.$_POST[\'zem_contact_nonce\'].br;\n		echo \'</pre>\';\n		echo \'SUBMITTED FORM LABEL/VALUE ($zem_contact_form):\'.br;\n		dmp($zem_contact_form); // array keyed by labels (null if no fields filled in or not submitted)\n		echo \'SUBMITTED FORM NAME/LABEL ($zem_contact_labels):\'.br;\n		dmp($zem_contact_labels); // array keyed by names (null if no fields filled in or not submitted)\n		echo \'SUBMITTED FORM NAME/VALUE ($zem_contact_values):\'.br;\n		dmp($zem_contact_values); // array keyed by names (null if no fields filled in or not submitted)\n		if (isset($zem_contact_error)) {\n			echo \'ERROR LIST ($zem_contact_error):\'.br;\n			dmp($zem_contact_error);\n		}\n		if (isset($adi_contact_labels)) {\n			echo \'SUPPLIED LABELS FROM adi_contact_labels:\'.br;\n			dmp($adi_contact_labels);\n		}\n		echo \'CHECKBOXES ($adi_contact_is_checkbox):\'.br;\n		dmp($adi_contact_is_checkbox);\n		if ($diags) {\n			echo \'DIAGNOSTICS:\'.br;\n			echo tag(implode(n,$diags),\'pre\');\n		}\n	}\n}\n\nfunction adi_contact_trace($diags) {\n// captain\'s log\n	global $trace;\n\n	if (is_string($diags))\n		$diags = str_split($diags,9999); // convert string to array\n\n	// get meaningful calling function\n	if (is_callable(\'debug_backtrace\')) {\n		$backtrace = debug_backtrace();\n		$function = isset($backtrace[1][\'function\']) ? $backtrace[1][\'function\'] : __FUNCTION__;\n	}\n	else\n		$function = __FUNCTION__;\n\n	if ($function == \"include\") $function = __FUNCTION__; // plugin cache\n	if ($function == \"eval\") $function = __FUNCTION__; // plugin installed\n\n	if (is_object($trace)) // TXP 4.6\n		foreach ($diags as $diag)\n			$trace->log(\'[\'.$function.\': \'.$diag.\']\');\n	else\n		foreach ($diags as $diag)\n			trace_add(\'[\'.$function.\': \'.$diag.\']\');\n}\n', '\n/*\n	adi_contact - Form field validation, spam prevention, email header/body extras, database update, MailChimp subscription\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- fixed: problem with validate type=\"equal\" error message\n			- enhancement: database update functionality\n			- enhancement: MailChimp subscription\n			- TXP 4.6 compatibility\n			- new tags: adi_contact_headers & adi_contact_body\n			- ZCR 4.5+ compatibility: \"errorElement\" class used in addition to \"zemRequirederrorElement\"\n	0.5		- fixed: javascript doesn\'t find contact <form> if zcr input & error forms are separate (thanks jakob)\n			- fixed: error message if no fields filled in when using \"value\" attribute\n			- fixed: errors in checkbox processing (thanks jakob)\n			- enhancement: extended \"name\"+\"value\" dependency to \"restrict\" & \"number\" (for jakob)\n	0.4		- change: new adi_contact_checkboxes tag (adi_contact_combo \'checkbox\' attribute withdrawn)\n			- enhancement: adi_contact_combo if \"value\" then \"required\" (for sacripant)\n			- fix: internationalised \'Yes\' & \'No\' in checkboxes (thanks sacripant)\n			- help section formatting errors (thanks net-carver) !!!!!!!! HAVEN\'T ACTUALLY FIXED ANYTHING !!!!!!!!!\n			- fix: no more duplicate javascript insertion\n			- class action: \'adi_contact_combo_error\', \'adi_contact_validate_error\', \'adi_contact_spam_error\' (for net-carver)\n			- new adi_contact_validate type \'base64\' (for net-carver)\n			- javascript code rewrite (& bug fixes: <select>, multiple forms)\n	0.3		- enhancement: new adi_contact_validate option type=\"equal\" (for kevinpotts)\n			- fix: adi_contact_combo now processes an unticked checkbox correctly (as not being a field of interest) (thanks Logoleptic)\n			- fix: adi_contact_validate coding error (thanks planeth)\n	0.2		- enhancement: new adi_contact_combo attribute \'required\'; equivalent to restrict=\"equal\" number=\"1\", enables custom error messages (inspired by Bloke)\n			- enhancement: new adi_contact_validate attribute & option type=\"custom\" pattern=\"pattern\" (for Bloke)\n			- enhancement: new adi_contact_validate attribute option type=\"url\" (inspired by Bloke)\n			- enhancement: new adi_contact_spam attribute \'exclude\' (e.g. use in conjunction with adi_contact_validate type=\"url\")\n			- fix: adi_contact_combo fails to cope with checkboxes (new attribute \'checkbox\') (thanks valpc)\n	0.1.1	- fix: adi_contact_spam error message field lists incorrectly generated (both standard & custom)\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_contact_body\')\n		->register(\'adi_contact_checkboxes\')\n		->register(\'adi_contact_combo\')\n		->register(\'adi_contact_db_data\')\n		->register(\'adi_contact_db\')\n		->register(\'adi_contact_debug\')\n		->register(\'adi_contact_headers\')\n		->register(\'adi_contact_javascript\')\n		->register(\'adi_contact_labels\')\n		->register(\'adi_contact_mailchimp_data\')\n		->register(\'adi_contact_mailchimp_email\')\n		->register(\'adi_contact_mailchimp\')\n		->register(\'adi_contact_spam\')\n		->register(\'adi_contact_validate\')\n	;\n}\n\nglobal $prefs,$zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_labels,$zem_contact_values,$adi_contact_js_added,$txp_using_svn;\n\nif (@txpinterface == \'admin\') {\n	register_callback(\'adi_contact_lifecycle\',\'plugin_lifecycle.adi_contact\');\n}\n\nif (txpinterface === \'public\') {\n	register_callback(\'adi_contact_db_update\',\'zemcontact.submit\');\n	register_callback(\'adi_contact_mailchimp_update\',\'zemcontact.submit\');\n    register_callback(\'adi_contact_deliver\',\'zemcontact.deliver\');\n}\n\nfunction adi_contact_lifecycle($event,$step) {\n// enforce the load order\n	if ($step == \'installed\')\n		$result = safe_update(\'txp_plugin\',\"load_order = 9\",\"name = \'adi_contact\'\");\n}\n\nfunction adi_contact_javascript() {\n// Javascript function for injecting error classes\n\n	$javascript = <<<END_SCRIPT\n<script type=\"text/javascript\">\nfunction addErrorClass(list,thisFunction) {\n\n	var zemRequiredClass = \'zemRequirederrorElement errorElement\', zemErrorElementClass = \'errorElement\' , adiContactClass = \'adi_contact_error\';\n\n	if (thisFunction === undefined)\n		thisClass = \'\';\n	else\n		thisClass = thisFunction + \'_error\';\n	thisClass = thisClass + \' \' + adiContactClass;\n	if (thisFunction == \'adi_contact_combo\')\n		thisClass = thisClass + \' \' + zemRequiredClass;\n	if (thisFunction == \'adi_contact_validate\')\n		thisClass = thisClass + \' \' + zemErrorElementClass;\n	if (thisFunction == \'adi_contact_spam\')\n		thisClass = thisClass + \' \' + zemErrorElementClass;\n\n	// former method of identifying form don\'t work all the time - if zcr input & error forms are separate then the <form> tags lose their id!\n	// var formId = window.location.hash.substr(1); // convert #zcr... to zcr...\n	// var thisForm = document.getElementById(formId);\n\n	// new method\n	// get #zcr... anchor from URL\n	var anchor = window.location.hash;\n\n	// find <form> that has the action=\"\" that matches the #zcr\n	var formsArray = document.getElementsByTagName(\"form\");\n	for (i=0; i<formsArray.length; i++) {\n		var action = formsArray[i].getAttribute(\'action\');\n		var pattern = RegExp(anchor,\'i\'); // i.e. /#zcr.../i\n		if (pattern.test(action)) // is #zcr... in form action=\"blah blah\"?\n			thisForm = formsArray[i];\n    }\n\n	// apply error class to list\n	var labels = thisForm.getElementsByTagName(\'label\'); // find all <label> in this form\n	var types = [\"input\",\"textarea\",\"select\"]; // tag types we\'re interested in\n	for (var x = 0; (thisType = types[x]); x++) { // for each tag type\n		var tags = thisForm.getElementsByTagName(thisType); // get all instances of tag type in this form\n		for (var i = 0; (thisTag = tags[i]); i++) { // for each tag of this type\n			if (list[thisTag.name]) { // name is on the list\n				for (var j = 0; (label = labels[j]); j++) {\n					if (label.htmlFor == thisTag.id) { // label \'for\' matches input id\n						label.className += \' \' + thisClass;\n						thisTag.className += \' \' + thisClass;\n// 						console.log(\"type=\",thisTag.type,\"name=\",thisTag.name,\"id=\",thisTag.id,\"label for=\",label.htmlFor,\"classes=\",thisClass);\n					}\n				}\n			}\n		}\n	}\n\n}\n</script>\nEND_SCRIPT;\n\n	return $javascript;\n}\n\nfunction adi_contact_insert_javascript() {\n// insert javascript function\n	global $adi_contact_js_added;\n\n	// only want to insert javascript function once\n	if (isset($adi_contact_js_added))\n		return \'\';\n	else {\n		$adi_contact_js_added = \'\';\n		return adi_contact_javascript();\n	}\n}\n\nfunction adi_contact_literal($list) {\n// create string in form of an object literal {\"field1\":1,\"field2\":1 ... }\n	$list = explode(\',\',$list); // convert comma separated list to array\n	foreach ($list as $index => $value) // create literal as array\n		$literal[] = \'\"\'.$value.\'\":1\';\n\n	return \'{\'.join(\',\',$literal).\'}\'; // return as {comma separated list}\n}\n\nfunction adi_contact_message($default_msg,$user_msg) {\n// generate zem_contact error message\n// ZCR will strip out duplicate error messages in $zem_contact_error\n	global $zem_contact_error,$label_list,$number,$require_label_list;\n\n	if ($user_msg) {\n		$user_msg = str_replace(\'[NAMES]\',$label_list,$user_msg);\n		$user_msg = str_replace(\'[REQUIRE]\',$require_label_list,$user_msg);\n		$user_msg = str_replace(\'[NUMBER]\',$number,$user_msg);\n		$zem_contact_error[] = $user_msg;\n	}\n	else\n		$zem_contact_error[] = $default_msg;\n}\n\nfunction adi_contact_get_labels($name_list) {\n// translate list of names into list of labels\n	global $adi_contact_labels,$zem_contact_labels;\n\n	$label_list = array();\n\n	if (!isset($adi_contact_labels)) // initialise to empty array (<txp:adi_contact_labels not used or not placed before <txp:adi_contact_combo /> etc.\n		$adi_contact_labels = array();\n\n	foreach ($name_list as $index => $value) // try three options\n		if (array_key_exists($value,$adi_contact_labels)) // 1. user supplied label via adi_contact_labels tag\n			$label_list[] = $adi_contact_labels[$value];\n		else if (isset($zem_contact_labels)) // check for presence of label supplied by submitted form\n			if (array_key_exists($value,$zem_contact_labels)) // 2. use label supplied by submitted form\n				$label_list[] = $zem_contact_labels[$value];\n			else // 3. use the name itself, but pretty it up\n				$label_list[] = ucfirst(strtolower($value));\n		else // 3. use the name itself, but pretty it up\n			$label_list[] = ucfirst(strtolower($value));\n\n	return join(\', \',$label_list);\n}\n\nfunction adi_contact_spam($atts) {\n// tag to check form fields for mail headers and link tags\n	global $zem_contact_form,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$adi_contact_labels,$label_list;\n\n	extract(lAtts(array(\n		\'names\'					=> \'\',	// list of form fields we\'re interested in (default = all submitted fields)\n		\'exclude\'				=> \'\',	// list of form fields to be ignored (default = none)\n		\'message\'				=> \'\',	// alternative error message (with [NAMES] & [NUMBER])\n		\'detect_mail_headers\'	=> \'1\',\n		\'detect_link_tags\'		=> \'1\',\n		\'allow_www\'				=> \'0\',	// allow presence of simple www.... links\n		\'debug\'					=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	if ($names) {\n		$name_list = explode(\',\',$names); // convert comma separated list to array\n		foreach ($name_list as $index => $field) // remove extraneous spaces\n			$name_list[$index] = trim($field);\n	}\n	else // get list of all submitted fields\n		isset($zem_contact_values) ?\n			$name_list = array_keys($zem_contact_values) :\n			$name_list = array();\n	if ($exclude) {\n		$exclude_list = explode(\',\',$exclude); // convert comma separated list to array\n		foreach ($exclude_list as $index => $field) // remove extraneous spaces\n			$exclude_list[$index] = trim($field);\n	}\n	else\n		$exclude_list = array();\n	$original_name_list = $name_list; // remember original list of fields\n	$name_list = array_diff($name_list,$exclude_list); // include minus exclude\n	if ($name_list) { // there\'s work to do\n		// link tags\n		$allow_www ?\n			$link_tags = \'/(\\[url|http|href)/i\' :\n			$link_tags = \'/(\\[url|http|www|href)/i\';\n		$link_tag_list = str_replace(\'/(\\\\\',\'\',$link_tags);\n		$link_tag_list = str_replace(\')/i\',\'\',$link_tag_list);\n		$link_tag_list = str_replace(\'|\',\', \',$link_tag_list);\n		// mail headers\n		$mail_headers = \'/(to\\:|from\\:|bcc\\:|multipart|cc\\:|Content\\-Transfer\\-Encoding\\:|Content\\-Type\\:|Mime\\-Version\\:)/i\';\n		$mail_headers_list = str_replace(\'/(\',\'\',$mail_headers);\n		$mail_headers_list = str_replace(\')/i\',\'\',$mail_headers_list);\n		$mail_headers_list = str_replace(\'\\\\\',\'\',$mail_headers_list);\n		$mail_headers_list = str_replace(\'|\',\', \',$mail_headers_list);\n		$mail_header_error = FALSE;\n		$link_tag_error = FALSE;\n		$error_list = array();\n		foreach ($name_list as $field => $value) { // scan fields for spam\n			if ($detect_mail_headers)\n				if (preg_match($mail_headers,$zem_contact_values[$value])) {\n					$mail_header_error = TRUE;\n					$error_list[] = $value; // add field name to list\n				}\n			if ($detect_link_tags)\n				if (preg_match($link_tags,$zem_contact_values[$value])) {\n					$link_tag_error = TRUE;\n					$error_list[] = $value; // add field name to list\n				}\n		}\n		$label_list = adi_contact_get_labels($error_list); // create list of labels for spam fields\n		if ($mail_header_error)\n			adi_contact_message(\"Mail header injection detected in &#8220;<strong>$label_list</strong>&#8221; ($mail_headers_list not allowed).\",$message);\n		if ($link_tag_error)\n			adi_contact_message(\"Link detected in &#8220;<strong>$label_list</strong>&#8221; ($link_tag_list not allowed).\",$message);\n		$error_fields = adi_contact_literal(join(\',\',$error_list)); // don\'t want spaces in list\n		if ($mail_header_error || $link_tag_error)\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$error_fields.\',\"\'.__FUNCTION__.\'\")</script>\';\n	}\n	if ($debug) {\n		adi_contact_debug(__FUNCTION__,$atts,$name_list);\n		echo \"FIELD LIST:<br/>\";\n		dmp($original_name_list);\n		echo \"EXCLUDE LIST:<br/>\";\n		dmp($exclude_list);\n		if (isset($error_list)) {\n			echo \"FIELD NAMES WITH SPAM ERRORS:<br/>\";\n			dmp($error_list);\n			echo \"SPAM FIELD LABELS:<br/>\";\n			dmp($label_list);\n		}\n	}\n	return $return;\n\n}\n\nfunction adi_contact_labels($atts) {\n// tag to record name/label mappings\n	global $adi_contact_labels;\n\n	$adi_contact_labels = $atts;\n}\n\nfunction adi_contact_checkboxes($atts) {\n// tag to manually define which fields are checkboxes\n	global $zem_contact_submit,$adi_contact_is_checkbox,$zem_contact_values;\n\n	extract(lAtts(array(\n		\'names\'	=> \'\',	// list of form fields that are checkboxes\n		\'debug\'	=> \'0\'\n	), $atts));\n\n	$adi_contact_is_checkbox = array();\n\n	if ($names) {\n		$name_list = explode(\',\',$names); // convert comma separated list to array\n		foreach ($name_list as $index => $field) // remove extraneous spaces\n			$name_list[$index] = trim($field);\n		foreach ($name_list as $index => $field_name)\n			$adi_contact_is_checkbox[$field_name] = TRUE;\n	}\n\n	if ($debug)\n		adi_contact_debug(__FUNCTION__,$atts,$name_list);\n}\n\nfunction adi_contact_error($atts) {\n// insert an extra <span>item</span> at the beginning of $zem_contact_error array - to masquerade as a introductory message (will be output as the first <li>)\n	global $zem_contact_error;\n\n	extract(lAtts(array(\n		\'preamble\'	=> \'\',		// text to precede error messages\n	), $atts));\n\n	if ($preamble && $zem_contact_error)\n		array_unshift($zem_contact_error,\'<span>\'.htmlentities($preamble).\'</span\');\n}\n\nfunction adi_contact_sed_is_base64_encoded($text) {\n	$text = strtr($text, array(\"\\n\"=>\'\', \"\\r\"=>\'\', \"\\t\"=>\'\', \' \'=>\'\'));\n	$b64_all   = \'A-Za-z0-9+/\';\n	$b64_mini  = \'AEIMQUYcgkosw048\';\n	$b64_micro = \'AQgw\';\n\n	$b64_pat = \"@\n	^                                         # Start of text followed by...\n	(?: [$b64_all]{4})*                       # Any number of groups of 4 chars from all chars.\n	(?:                                       # ...optionally (see ? at end of this subpattern) followed by...\n		[$b64_all]{2} [$b64_mini] =           #   Any two chars from all, then one from mini, then \'=\'\n	|                                         #   -- or --\n		[$b64_all]    [$b64_micro] ==         #   one from all, then one from micro then two \'=\'s.\n	)?                                        # ...followed by...\n	\\$                                        # End of text\n	@x\";\n\n	$b64 = (bool)preg_match($b64_pat, $text);\n	return $b64;\n}\n\nfunction adi_contact_validate($atts) {\n// tag to validate form field contents\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_error,$zem_contact_values,$adi_contact_js_added;\n\n	extract(lAtts(array(\n		\'names\'				=> \'\',		// list of form fields we\'re interested in\n		\'type\'				=> \'\',		// \'alpha\', \'alphanum\', \'integer\', \'numeric\', \'equal\', \'base64\' etc\n		\'values\'			=> \'\',		// list of acceptable values\n		\'allow_whitespace\'	=> \'0\',		// allow spaces as well as \'type\'\n		\'pattern\'			=> \'\',		// custom pattern used with type=\"custom\'\n		\'message\'			=> \'\',		// alternative error message (with [NAMES])\n		\'debug\'				=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	// tidy up supplied attributes\n	$name_list = explode(\',\',$names); // convert comma separated list to array\n	foreach ($name_list as $index => $field) // remove extraneous spaces\n		$name_list[$index] = trim($field);\n\n	$invalids = array();\n	$valid_values = array();\n\n	if ($zem_contact_submit) { // form has been submitted\n\n		$ok = TRUE; // benefit of the doubt\n		if (isset($zem_contact_form))\n			if ($type == \'equal\') {\n				$undefined_field_value = \'thechancesofsomeonetypingthisinisextremelyremote...bim\';\n				$ok = (count($name_list) > 1); // must be at least two fields supplied\n				if ($ok) {\n					// remember first field value\n					if (array_key_exists($name_list[0], $zem_contact_values))\n						$first_field_value = $zem_contact_values[$name_list[0]];\n					else\n						$first_field_value = $undefined_field_value;\n					// compare all field values with the first\n					foreach ($name_list as $index => $field) {\n						if (array_key_exists($field, $zem_contact_values))\n							$this_field_value = $zem_contact_values[$field];\n						else\n							$this_field_value = $undefined_field_value;\n						$ok &= strcmp($this_field_value,$first_field_value) == 0;\n					}\n					if (!$ok) { // add to list of invalids ??? TEST THIS - ORIGINALLY BODGIED UP IN OBI\n						$invalids[] = $name_list[0]; // first field\n						$invalids[] = $field; // this field\n					}\n				}\n			}\n			else if ($values != \'\') {\n				$type = \'\';\n				$valid_values = explode(\',\',$values);\n				foreach ($name_list as $field) {\n					if (array_key_exists($field, $zem_contact_values))\n						$this_field_value = $zem_contact_values[$field];\n					else\n						$this_field_value = \'\';\n					$ok = (array_search($this_field_value,$valid_values) !== FALSE);\n					if (!$ok) { // add to list of invalids\n						$invalids[] = $field; // this field\n					}\n				}\n			}\n			else\n				foreach ($name_list as $index => $field) {\n					if (array_key_exists($field, $zem_contact_values)) {\n						$field_value = $zem_contact_values[$field];\n						if ($allow_whitespace)\n							$field_value = preg_replace(\'/[\\s]/\',\'\', $field_value);\n						switch ($type) {\n							case \'alpha\':\n								$ok &= ctype_alpha($field_value);\n								break;\n							case \'alphanum\':\n								$ok &= ctype_alnum($field_value);\n								break;\n							case \'integer\':\n								$ok &= ctype_digit($field_value);\n								break;\n							case \'numeric\':\n								$ok &= is_numeric($field_value);\n								break;\n							case \'phone\':\n								$ok &= preg_match(\"/^[0-9x\\ \\+\\-\\(\\)\\.,]*$/i\",$field_value);\n								break;\n							case \'url\':\n								$ok &= preg_match(\"/^(?:http:\\/\\/)?(?:[\\w-]+\\.)+[a-z]{2,6}$/i\",$field_value);\n								break;\n							case \'base64\':\n								$ok &= adi_contact_sed_is_base64_encoded($field_value);\n								break;\n							case \'custom\':\n								if ($pattern)\n									$ok &= preg_match($pattern,$field_value);\n								break;\n							default:\n								break;\n						}\n						if (!$ok)\n							$invalids[] = $field; // add to list of invalids\n					}\n				}\n		if (!$ok) {\n			$label_list = adi_contact_get_labels($invalids); // create list of labels\n			switch ($type) {\n				case \'alpha\':\n					adi_contact_message(\"Non-alphabetical characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'alphanum\':\n					adi_contact_message(\"Non-alphanumeric characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'integer\':\n					adi_contact_message(\"Non-digits found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'numeric\':\n					adi_contact_message(\"Non-numeric characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'phone\':\n					adi_contact_message(\"Only 0-9 + - . , () xX allowed in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'url\':\n					adi_contact_message(\"Invalid URL in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'base64\':\n					adi_contact_message(\"Invalid Base64 encoded characters found in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'custom\':\n					adi_contact_message(\"Invalid data in &#8220;<strong>$label_list</strong>&#8221;\",$message);\n					break;\n				case \'equal\':\n					adi_contact_message(\"&#8220;<strong>$label_list</strong>&#8221; are not the same\",$message);\n					break;\n				default:\n					adi_contact_message(\"Data in &#8220;<strong>$label_list</strong>&#8221; is not recognised\",$message); //??? IMPROVE THIS\n					break;\n			}\n			$invalid_list = adi_contact_literal(join(\',\',$invalids));\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$invalid_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n		if ($debug) {\n			adi_contact_debug(__FUNCTION__,$atts,$name_list);\n			if ($valid_values) {\n				echo \'VALID VALUES:<br/>\';\n				dmp($valid_values);\n			}\n			echo \'INVALID LIST:<br/>\';\n			dmp($invalids);\n		}\n	}\n	return $return;\n}\n\nfunction adi_contact_combo($atts) {\n// tag to check for specified field combinations\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$adi_contact_js_added,$adi_contact_is_checkbox;\n\n	extract(lAtts(array(\n		\'names\'				=> \'\',		// list of form fields we\'re interested in\n		\'require\'			=> \'\',		// list of form fields required\n		\'restrict\'			=> \'\',		// \'min\', \'max\' or \'equal\'\n		\'number\'			=> \'0\',		// number of fields required\n		\'value\'				=> \'\',		// pertinent value to trigger a \'require\'\n		\'case_sensitive\'	=> \'0\',		// when checking value\n		\'message\'			=> \'\',		// alternative error message (with [NAMES],[NUMBER],[REQUIRE])\n		\'required\'			=> \'0\',		// forces restrict=\"equal\" number=\"1\", e.g. for custom error messages or checkboxes that must be ticked\n		\'checkbox\'			=> \'0\',		// indicates that fields are checkboxes (i.e. check for \'Yes\' or \'No\' - in the appropriate language) - DEPRECATED\n		\'debug\'				=> \'0\'\n	), $atts));\n\n	$return = adi_contact_insert_javascript();\n\n	// just in case adi_contact_checkboxes hasn\'t been called\n	if (!isset($adi_contact_is_checkbox))\n		$adi_contact_is_checkbox = array();\n\n	// tidy up supplied attributes\n	$name_list = explode(\',\',$names); // convert comma separated list to array\n	foreach ($name_list as $index => $field) // process fields of interest\n		$name_list[$index] = trim($field); // remove extraneous spaces\n	$label_list = adi_contact_get_labels($name_list); // put field list back together tidily spaced out: field1, field2, field3\n	$require_list = explode(\',\',$require); // convert comma separated list to array\n	foreach ($require_list as $index => $field) // remove extraneous spaces\n		$require_list[$index] = trim($field);\n	$require_label_list = adi_contact_get_labels($require_list); // put field list back together tidily spaced out: field1, field2, field3\n	$restrict = strtolower($restrict);\n	if ($required) { // set other attribute values\n		$restrict = \'equal\';\n		$number = \'1\';\n	}\n	if ($checkbox) // old checkbox attribute is no longer used, so force the issue\n		echo \"<b>adi_contact_combo tag error:</b> The checkbox attribute is no longer valid (use adi_contact_checkboxes tag instead).\";\n\n	// initial settings\n	$field_count = 0;\n	$restrict_error = FALSE;\n	$require_error = FALSE;\n	$switch_name_require_list = FALSE;\n	$missing_required = array();\n\n	if ($zem_contact_submit) { // form has been submitted\n\n		foreach ($name_list as $index => $name) { // go through supplied fields of interest\n\n			$flagged = FALSE; // flag will be set if field filled in or it matches supplied value\n\n			if (isset($zem_contact_values)) // NULL if all fields empty on submit!\n				if (array_key_exists($name, $zem_contact_values)) { // see if field has been submitted\n					if ($value == \'\') { // anything (or a tick) will do\n						if (array_key_exists($name,$adi_contact_is_checkbox)) { // it\'s a checkbox\n							if (strcasecmp($zem_contact_values[$name],gTxt(\'yes\')) == 0) // it\'s ticked\n								$flagged = TRUE;\n						}\n						else\n							if ($zem_contact_values[$name] != \'\') // field filled in\n								$flagged = TRUE;\n					}\n					else { // specific value required\n						if ($case_sensitive)\n							$flagged = ($zem_contact_values[$name] == $value);\n						else\n							$flagged = (strtolower($zem_contact_values[$name]) == strtolower($value));\n					}\n				}\n\n			if ($flagged)\n				if ($require && $restrict) { // SPECIAL CASE: require list contains \"secondary\" fields of interest\n					foreach ($require_list as $index => $required_field) { // see if they\'re filled\n						if (array_key_exists($required_field,$adi_contact_is_checkbox)) {\n							if (strcasecmp($zem_contact_values[$required_field],gTxt(\'yes\')) == 0) // \"filled in\" if set to \'Yes\', so that\'s OK then\n								$field_count += 1;\n						}\n						else if (array_key_exists($required_field, $zem_contact_values))\n								$field_count += 1;\n					}\n					$switch_name_require_list = TRUE; // will need to change list later for error message\n				}\n				else if ($require) { // SIMPLE REQUIREMENT\n					foreach ($require_list as $index => $required_field) {\n						if (array_key_exists($required_field,$adi_contact_is_checkbox)) {\n							if (strcasecmp($zem_contact_values[$required_field],gTxt(\'yes\')) == 0) // \"filled in\" if set to \'Yes\', so that\'s OK then\n								continue;\n						}\n						else if (array_key_exists($required_field, $zem_contact_values))\n							continue;\n						// not filled in then!\n						$require_error = TRUE;\n						$missing_required[] = $required_field; // list the missing\n					}\n				}\n				else if ($restrict) { // SIMPLE RESTRICTION\n					$field_count += 1;\n				}\n\n		}\n\n		// SPECIAL CASE: require list contains \"secondary\" fields of interest\n		if ($require && $restrict)\n			if ($switch_name_require_list) { // \"secondary\" fields of interest have come into play\n				$name_list = $require_list;\n				$label_list = adi_contact_get_labels($require_list);\n			}\n			else // forget about restriction, coz it don\'t apply i.e. no flags raised\n				$restrict = \'\';\n\n		// \'restrict\' mode error messages\n		switch ($restrict) {\n			case \'min\':\n				if ($field_count < $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"At least $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			case \'max\':\n				if ($field_count > $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"Maximum $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			case \'equal\':\n				if ($field_count != $number) {\n					$restrict_error = TRUE;\n					adi_contact_message(\"Exactly $number of &#8220;<strong>$label_list</strong>&#8221; required.\",$message);\n				}\n				break;\n			default:\n				break;\n		}\n\n		if ($debug) {\n			adi_contact_debug(__FUNCTION__,$atts,$name_list);\n			echo \"LABELS OF INTEREST:<br/>\";\n			dmp($label_list);\n			if ($restrict) {\n				if ($number) {\n					echo \"VALID FIELD COUNT:<br/>\";\n					echo \'<pre>\'.$field_count.\'</pre>\';\n				}\n			}\n			if ($require) {\n				echo \"REQUIRED FIELDS:<br/>\";\n				dmp($require_list);\n				if (isset($missing_required))\n				{\n					echo \"MISSING REQUIRED FIELDS:<br/>\";\n					dmp($missing_required);\n				}\n			}\n		}\n\n		// finally some javascript action\n		if ($restrict_error) {\n			$missing_list = adi_contact_literal(join(\',\',$name_list)); // don\'t want spaces in list\n			if ($debug)\n				echo \"MISSING LIST:<br/><pre>$missing_list</pre>\";\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$missing_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n		else if ($require_error) { // \'require\' error messages\n			adi_contact_message(\"If &#8220;<strong>$label_list</strong>&#8221; supplied then &#8220;<strong>$require_label_list</strong>&#8221; required.\",$message);\n			$missing_list = adi_contact_literal(join(\',\',$missing_required)); // don\'t want spaces in list\n			$return .= \'<script type=\"text/javascript\">addErrorClass(\'.$missing_list.\',\"\'.__FUNCTION__.\'\")</script>\';\n		}\n	}\n	return $return;\n}\n\nfunction adi_contact_headers($atts) {\n// tag for extra headers, e.g. CC or BCC\n// adi_contact_deliver() does the actual doing\n	global $adi_contact_headers;\n\n	extract(lAtts(array(\n		\'cc\'	=> \'\',		// CC email address\n		\'bcc\'	=> \'\',		// BCC email address\n	), $atts));\n\n	if (!isset($adi_contact_headers))\n		$adi_contact_headers = array();\n\n	$adi_contact_headers[\'cc\'] = $cc;\n	$adi_contact_headers[\'bcc\'] = $bcc;\n\n	return;\n}\n\nfunction adi_contact_body($atts) {\n// for appending extra information onto end of email\n// accepts string (internal function use) OR array (tag mode)\n// adi_contact_deliver() does the actual doing\n	global $adi_contact_body,$zem_contact_submit;\n\n	if (!isset($adi_contact_body))\n		$adi_contact_body = array(\'\'); // start a new line otherwise extra body tacked onto end of last field/value line\n\n	if (is_array($atts))\n		extract(lAtts(array(\n			\'line\'		=> \'\',	// a line to add to the body\n		), $atts));\n	else\n		$line =$atts;\n\n	if($zem_contact_submit) // don\'t add unless form submitted (to avoid combining lines from separate forms on same page)\n		$adi_contact_body[] = $line;\n\n	return;\n}\n\nfunction adi_contact_deliver($event,$step,&$payload) {\n// modify the email $payload (event=\"zemcontact.deliver\",step=\"send/copysender\")\n	global $adi_contact_headers,$adi_contact_body;\n\n	// extra headers - provided by adi_contact_headers()\n	if ($adi_contact_headers[\'cc\'] != \'\')\n		$payload[\'headers\'][\'Cc\'] = $adi_contact_headers[\'cc\'];\n	if ($adi_contact_headers[\'bcc\'] != \'\')\n		$payload[\'headers\'][\'Bcc\'] = $adi_contact_headers[\'bcc\'];\n\n	if ($step !== \'copysender\') { // making big assumption that copy of email going to form submitter shouldn\'t contain extra body (e.g. MailChimp diags)\n		// extra body (message text already formed at this point, so simply adding extra lines to email)\n		if ($adi_contact_body) {\n			foreach ($adi_contact_body as $line)\n				$payload[\'body\'] .= $line.\"\\n\";\n		}\n	}\n\n    return \'zemcontact.send\'; // I command thee to send thy email\n}\n\nfunction adi_contact_db($atts,$thing=\'\') {\n// database mechanism enclosing tag\n	global $zem_contact_submit,$zem_contact_error,$adi_contact_db_table,$production_status;\n\n	// CREATE TABLE contacts (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,`timestamp` datetime NOT NULL default \'0000-00-00 00:00:00\',`fullname` VARCHAR(255) NOT NULL,`email` VARCHAR(255) NOT NULL,`mailout` VARCHAR(32) NOT NULL default \'\',`general` VARCHAR(16) NOT NULL default \'\',`sales` VARCHAR(16) NOT NULL default \'\',`technical` VARCHAR(16) NOT NULL default \'\',`feedback` VARCHAR(16) NOT NULL default \'\',`region` VARCHAR(32) NOT NULL default \'\');\n\n	extract(lAtts(array(\n		\'table\'		=> \'\',			// the database table\n 		\'form\'		=> \'\',			// the adi_contact_db form\n// 		\'mode\'		=> \'insert\',	// insert or update\n	), $atts));\n\n	$diags = array();\n\n	$adi_contact_db_data = array(); // array indexed by db_field name: [\'contact_field\'] => contact field name, [\'default\'] => default value (if supplied)\n	$adi_contact_db_table = $table;\n\n	// validate supplied table\n	if (($production_status === \'debug\') && ($table != \'\')) {\n		$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n		$a = nextRow($rs);\n		if ($a)\n			$diags[] = \"table - \'$table\' found in database\";\n		else\n			$diags[] = \"table - \'$table\' NOT FOUND in database\";\n	}\n	if ($table == \'\')\n		$diags[] = \'table not set\';\n\n	// form submit status\n	if ($zem_contact_submit)\n		$diags[] = \'form - submitted\';\n	else\n		$diags[] = \'form - not submitted\';\n	if ($zem_contact_error)\n		$diags[] = \'form - submit errors\';\n\n	adi_contact_trace($diags);\n\n	// do the doing\n	$form = ($form) ? fetch_form($form) : $thing; // only get tag errors about invalid DB fields if using form (i.e. not enclosing tag)?\n\n	return parse($form);\n}\n\nfunction adi_contact_db_data($atts) {\n// contact form/database field mapping tag - updates $adi_contact_db_data\n	global $zem_contact_submit,$zem_contact_labels,$production_status,$adi_contact_db_table,$adi_contact_db_data;\n\n	extract(lAtts(array(\n		\'db_field\'		=> \'\',	// the database field (required)\n		\'contact_field\'	=> \'\',	// the contact form input field (default same name as db_field)\n		\'default\'		=> \'\',	// optional default value\n		\'timestamp\'		=> \'\'	// database field is a timestamp (datetime, date or timestamp)\n	), $atts));\n\n	$diags = array();\n\n	// contact field same as database field?\n	if (($contact_field == \'\') && !$timestamp)\n		$contact_field = $db_field;\n\n	// validate contact form field\n	if ($zem_contact_submit) {\n		if (isset($zem_contact_labels[$contact_field]))\n			$diags[] = \"field - \'$contact_field\' found in contact form\";\n		else\n			$diags[] = \"field - \'$contact_field\' NOT FOUND OR BLANK in contact form\";\n	}\n\n	// check required attributes\n	if ($db_field == \'\')\n		$diags[] = \"db_field - ATTRIBUTE MISSING\";\n	else {\n		// db column -> contact field mapping\n		$adi_contact_db_data[$db_field][\'contact_field\'] = $contact_field;\n\n		// default values\n		if (isset($atts[\'default\'])) // default value supplied\n			$adi_contact_db_data[$db_field][\'default\'] = $default;\n		if (isset($atts[\'timestamp\'])) { // set timestamp according to MySQL formats: DATETIME, DATE, TIMESTAMP\n			$timestamp = strtolower($timestamp);\n			switch ($timestamp) {\n				case \'date\': // yyyy-mm-dd\n					$adi_contact_db_data[$db_field][\'default\'] = date(\"Y-m-d\",time()+tz_offset());\n					break;\n				case \'timestamp\': // UTC yyyy-mm-dd hh:mm:ss\n					$adi_contact_db_data[$db_field][\'default\'] = gmstrftime(\"%Y-%m-%d %H:%M:%S\");\n					break;\n				default: // default to \"datetime\" - yyyy-mm-dd hh:mm:ss\n					$adi_contact_db_data[$db_field][\'default\'] = date(\"Y-m-d H:i:s\",time()+tz_offset());\n					break;\n			}\n		}\n\n		$diags[] = \"map - \'\".$adi_contact_db_data[$db_field][\'contact_field\'].\"\' -> db \'$db_field\'\".(isset($adi_contact_db_data[$db_field][\'default\']) ? \' (default = \"\'.$adi_contact_db_data[$db_field][\'default\'].\'\")\' : \'\');\n\n		// validate database column\n		if ($production_status === \'debug\') {\n			if (isset($adi_contact_db_table)) {\n				$rs = safe_query(\'SHOW COLUMNS FROM `\'.safe_pfx($adi_contact_db_table).\"` WHERE FIELD = \'$db_field\'\");\n				if ($rs !== FALSE) { // table exists\n					$a = nextRow($rs);\n					if ($a)\n						$diags[] = \"column - \'$db_field\' found in table \'$adi_contact_db_table\'\";\n					else\n						$diags[] = \"column - \'$db_field\' NOT FOUND in table \'$adi_contact_db_table\'\";\n				}\n			}\n		}\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_db_update() {\n// update database - called back during form submit\n	global $zem_contact_values,$adi_contact_db_table,$adi_contact_db_data;\n\n	$diags = array();\n\n	if (!isset($adi_contact_db_table)) return; // adi_contact_db hasn\'t been called so bail out\n\n	// check spammage\n	$evaluation =& get_zemcontact_evaluator();\n	if ($evaluation->get_zemcontact_status()) {\n		$diags[] = \"spam evaluation - DIRTY\";\n		return;\n	}\n\n	// build set\n	$set = array();\n	if ($adi_contact_db_data) {\n		foreach ($adi_contact_db_data as $db_field => $map) {\n			if (isset($zem_contact_values[$map[\'contact_field\']]))\n				$set[]= \"`$db_field`=\'\".doSlash($zem_contact_values[$map[\'contact_field\']]).\"\'\"; // use value from contact form\n			else if (isset($map[\'default\']))\n				$set[]= \"`$db_field`=\'\".doSlash($map[\'default\']).\"\'\"; // use supplied default value\n// 			else\n// 				let the database fill in its own default\n		}\n	}\n	else\n		$diags[] = \"map - EMPTY\";\n\n	// update the database\n	if ($set && $adi_contact_db_table) {\n		$diags[] = \'set - \'.implode(\', \',$set);\n		if (!safe_insert($adi_contact_db_table,implode(\',\',$set)))\n			$diags[] = \"safe_insert: FAIL\";\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp($atts,$thing=NULL) {\n// MailChimp subscription container tag\n	global $zem_contact_submit,$zem_contact_error,$adi_contact_mailchimp_data,$adi_contact_mailchimp_info;\n\n	extract(lAtts(array(\n		\'api_key\'		=> \'\',				// MailChimp API key (required)\n 		\'list_id\'		=> \'\',				// MailChimp list ID (required)\n 		\'status\'		=> \'pending\',		// \'subscribed\', \'unsubscribed\', \'pending\', \'cleaned\'\n 		\'opt_in_field\'	=> \'mailing_list\',	// contact form field name, used to trigger MailChimp action\n  		\'opt_in_value\'	=> gTxt(\'yes\'),		// contact form field value, used to trigger MailChimp action (defaults to checkbox value)\n		\'form\'			=> \'\',				// adi_contact_mailchimp form\n	), $atts));\n\n	// status values\n	// 		\'pending\' - confirmation email will be sent to user\n	//		\'subscribed\' - subscribed immediately, no confirmation sent/required\n\n	$diags = array();\n\n	$default_form = \'<txp:adi_contact_mailchimp_email />\';\n\n	// array containing data required for MailChimp connect (api_key, list_id, email, status, merge_fields) as well as opt-in info\n	$adi_contact_mailchimp_info = array(\'api_key\'=>$api_key,\'list_id\'=>$list_id,\'status\'=>$status,\'opt_in_field\'=>$opt_in_field,\'opt_in_value\'=>$opt_in_value);\n	// array indexed by MailChimp field (email,FNAME,LNAME): [\'contact_field\'] => contact field name, [\'default\'] => default value (DEFAULT VALUES NOT CURRENTLY USED)\n	$adi_contact_mailchimp_data = array();\n\n	// check attributes\n	if ($api_key == \'\')\n		$diags[] = \'API key missing\';\n	if ($list_id == \'\')\n		$diags[] = \'list ID missing\';\n	if ($status == \'\')\n		$diags[] = \'status missing\';\n\n	// form submit status\n	if ($zem_contact_submit)\n		$diags[] = \'form - submitted\';\n	else\n		$diags[] = \'form - not submitted\';\n	if ($zem_contact_error)\n		$diags[] = \'form - submit errors\';\n\n	adi_contact_trace($diags);\n\n	// form guide\n	$form = ($form) ? fetch_form($form) : ($thing === NULL ? $default_form : $thing);\n\n	return parse($form);\n}\n\nfunction adi_contact_mailchimp_data($atts) {\n// contact form/mailchimp field mapping tag - updates $adi_contact_mailchimp_data\n	global $zem_contact_submit,$zem_contact_labels,$adi_contact_mailchimp_data;\n\n	extract(lAtts(array(\n		\'mailchimp_field\'	=> \'\',	// MailChimp field, e.g. FNAME, LNAME (required)\n		\'contact_field\'		=> \'\',	// the contact form input field (required)\n	), $atts));\n\n	$diags = array();\n\n	// validate contact form field\n	if ($zem_contact_submit) {\n		if (isset($zem_contact_labels[$contact_field]))\n			$diags[] = \"field - \'$contact_field\' found in contact form\";\n		else\n			$diags[] = \"field - \'$contact_field\' NOT FOUND OR EMPTY in contact form\";\n	}\n\n	// check required attributes\n	if ($mailchimp_field == \'\')\n		$diags[] = \"mailchimp_field - ATTRIBUTE MISSING\";\n	else if ($contact_field == \'\')\n		$diags[] = \"contact_field - ATTRIBUTE MISSING\";\n	else {\n		// mailchimp column -> contact field mapping\n		$adi_contact_mailchimp_data[$mailchimp_field][\'contact_field\'] = $contact_field;\n\n		// default values (NOT CURRENTLY USED)\n		if (isset($atts[\'default\'])) // default value supplied\n			$adi_contact_mailchimp_data[$mailchimp_field][\'default\'] = $default;\n\n		$diags[] = \"map - \'\".$adi_contact_mailchimp_data[$mailchimp_field][\'contact_field\'].\"\' -> mailchimp \'$mailchimp_field\'\".(isset($adi_contact_mailchimp_data[$mailchimp_field][\'default\']) ? \' (default = \"\'.$adi_contact_mailchimp_data[$mailchimp_field][\'default\'].\'\")\' : \'\');\n	}\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp_email($atts) {\n// mailchimp email field mapping tag\n\n	extract(lAtts(array(\n		\'contact_field\'	=> \'email\',	// the contact form input field\n	), $atts));\n\n	adi_contact_mailchimp_data(array(\'mailchimp_field\'=>\'email\', \'contact_field\'=>$contact_field));\n}\n\nfunction adi_contact_mailchimp_update() {\n// update MailChimp list - called back during form submit\n	global $zem_contact_values,$adi_contact_mailchimp_info,$adi_contact_mailchimp_data;\n\n	$diags = array();\n\n	if (!isset($adi_contact_mailchimp_info)) return; // adi_contact_mailchimp hasn\'t been called so bail out\n\n	// check spammage\n	$evaluation =& get_zemcontact_evaluator();\n	if ($evaluation->get_zemcontact_status()) {\n		adi_contact_trace(\"FAIL - spam evaluation, DIRTY\");\n		return;\n	}\n\n	// check opt-in\n	$opt_in_field = $adi_contact_mailchimp_info[\'opt_in_field\'];\n	$opt_in_value = $adi_contact_mailchimp_info[\'opt_in_value\'];\n	if ($opt_in_field != \'\') { // if opt_in_field attribute set, check it exists in contact form\n		if (!isset($zem_contact_values[$opt_in_field])) {\n			adi_contact_trace(\"FAIL - opt-in field \'$opt_in_field\' NOT FOUND\");\n			return;\n		}\n		if (strcasecmp($zem_contact_values[$opt_in_field],$opt_in_value) != 0) { // opt_in_field value matches opt_in_value attribute?\n			$opt_in_field_value = $zem_contact_values[$opt_in_field];\n			adi_contact_trace(\"DISABLED - opt-in field \'$opt_in_field\' value \'$opt_in_field_value\', doesn\'t match opt_in_value \'$opt_in_value\'\");\n			return;\n		}\n	}\n\n	// build up the chimp\n	$adi_contact_mailchimp_info[\'email\'] = \'\';\n	$adi_contact_mailchimp_info[\'merge_fields\'] = array();\n	if ($adi_contact_mailchimp_data) {\n		foreach ($adi_contact_mailchimp_data as $mailchimp_field => $map) {\n			if ($mailchimp_field == \'email\') { // the email\n				if (isset($zem_contact_values[$map[\'contact_field\']]))\n					$adi_contact_mailchimp_info[\'email\'] = $zem_contact_values[$map[\'contact_field\']]; // use email from contact form;\n			}\n			else { // merge fields\n				$value = (isset($zem_contact_values[$map[\'contact_field\']]) ? $zem_contact_values[$map[\'contact_field\']] : \'\');\n				$adi_contact_mailchimp_info[\'merge_fields\'][$mailchimp_field] = $value;\n			}\n		}\n	}\n	else\n		$diags[] = \"map - EMPTY\";\n\n	// update the chimp\n	if ($adi_contact_mailchimp_info[\'email\']) { // need an email address as bare minimum\n		$diag_info = $adi_contact_mailchimp_info;\n		unset($diag_info[\'merge_fields\']);\n		$diags[] = \'info - \'.implode(\', \',$diag_info);\n		$diags[] = \'merge_fields - \'.implode(\', \',$adi_contact_mailchimp_info[\'merge_fields\']);\n		$mc_result = adi_contact_mailchimp_connect($adi_contact_mailchimp_info);\n		$email_msg = \"MailChimp:\\n\";\n		// see adi_contact_mailchimp_connect for returns\n		if ($mc_result === FALSE) {\n			$reason = \'unable to connect\';\n			$diags[] = \'FAIL - \'.$reason;\n			$email_msg .= \"- subscribe failed ($reason)\\n\";\n		}\n		if ($mc_result === NULL) {\n			$reason = \'software error\';\n			$diags[] = \'FAIL - \'.$reason;\n			$email_msg .= \"- subscribe failed ($reason)\\n\";\n		}\n		if (is_array($mc_result))\n			if (isset($mc_result[\'title\'])) {\n				$reason = \'status \'.$mc_result[\'status\'].\', \'.$mc_result[\'title\'];\n				$diags[] = \'FAIL - \'.$reason;\n				$email_msg .= \"- subscribe failed ($reason)\\n\";\n				$diags[] = \'FAIL - \'.trim($mc_result[\'detail\']);\n				$email_msg .= \'- \'.trim($mc_result[\'detail\']).\"\\n\";\n				foreach ($mc_result[\'errors\'] as $error) {\n					$email_msg .= \"- \";\n					$error_msg = \'\';\n					foreach ($error as $index => $value)\n						$error_msg .= \"$index: $value; \";\n					$diags[] = \"ERROR - $error_msg\";\n					$email_msg .= \"$error_msg\\n\";\n				}\n			}\n			else {\n				$reason = \'status \'.$mc_result[\'status\'];\n				$diags[] = \'success - \'.$mc_result[\'status\'];\n				$email_msg .= \"- subscribed ($reason)\\n\";\n			}\n\n		// maybe attributed option to include mc diags in email or not?\n		adi_contact_body(\"\\n\"); // two blank lines\n		adi_contact_body($email_msg);\n\n	}\n	else\n		$diags[] = \'FAIL - no contact field to MailChimp \"email\" field mapping\';\n\n	adi_contact_trace($diags);\n}\n\nfunction adi_contact_mailchimp_connect($mailchimp_info) {\n// connect to MailChimp & update mailing list\n// - much code taken from stackoverflow.com/questions/30481979/adding-subscribers-to-a-list-using-mailchimps-api-v3\n//\n// - success\n//		- return array(\'status\')\n//			status = \'pending\' or \'subscribed\' etc\n// - failure\n//		- return array(\'status\',\'title\')\n//			status = 400, 401, 404 etc\n//			title = informative summary\n//				400 - Member Exists\n//				401 - API Key Invalid\n//				404 - Resource Not Found (dodgy URL)\n//			detail = further explanation\n//			errors = optional array of field/error message\n//  	- return FALSE\n//			connection failure (e.g. timeout)\n//  	- return NULL\n//			software problem (e.g. curl functions not available)\n\n	extract($mailchimp_info); // i.e. $api_key, $list_id, $status, $email, $merge_fields[...]\n	list(,$datacentre) = explode(\'-\',$api_key); // datacentre is the \"us\" bit at end of API key\n	$url = \'https://\'.$datacentre.\'.api.mailchimp.com/3.0/lists/\'.$list_id.\'/members/\';\n\n	$data = array(\n		\'apikey\'		=> $api_key,\n		\'email_address\'	=> $email,\n		\'status\'		=> $status,\n		\'merge_fields\'	=> $merge_fields\n	);\n\n	// there seems to be an intermittant issue with MailChimp API v3.0\n	//		scenario: when merge_fields is an empty array (i.e. there are no adi_contact_mailchimp_data mappings other than \"email\")\n	//		symptoms/diags:\n	//			subscribe failed (status 400, Invalid Resource)\n	//			\"The resource submitted could not be validated. For field-specific details, see the \'errors\' array.\"\n	//			field: merge_fields; message: Schema describes object, array found instead;\n	//		intermittant: may only occur on certain servers (e.g. local & staging OK, live COL fails), possibly a PHP version/server config thing\n	//		discussed here: https://github.com/drewm/mailchimp-api/issues/54#issuecomment-171263356\n	//		fix: omit \'merge_fields\' item from $data if empty\n	if (empty($merge_fields)) unset($data[\'merge_fields\']);\n\n// 	echo \'adi_contact_mailchimp_connect:\';\n// 	dmp($data);\n\n	$json_data = json_encode($data);\n\n	// curly wurly check\n	//??? IMPLEMENT ALT CONNECTION METHOD using file_get_contents() - see smd_mailchimp\n	if (!function_exists(\'curl_init\') || !function_exists(\'curl_setopt\')) {\n		adi_contact_trace(\"FAIL - curl_init(), curl_setopt() not available\");\n		return NULL;\n	}\n\n	$ch = curl_init();\n	curl_setopt($ch, CURLOPT_URL, $url);\n	curl_setopt($ch, CURLOPT_HTTPHEADER, array(\'Content-Type: application/json\'));\n	curl_setopt($ch, CURLOPT_USERPWD, \"user:\" . $api_key);\n	curl_setopt($ch, CURLOPT_USERAGENT, \'PHP-MCAPI/2.0\');\n	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n	curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n	curl_setopt($ch, CURLOPT_POST, true);\n	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n	curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);\n\n	$result = curl_exec($ch); // FALSE = connection error, JSON data if contact made with MAilChimp\n\n	$arr = json_decode($result,TRUE); // json_decode into array\n\n// 	echo __FUNCTION__.\' response/status:\';\n// 	dmp($arr);\n\n	// not using extract() coz there\'s a shed load of variously named indexes in $arr (especially in a successful subscribe)\n	$status = \'\';\n	if (isset($arr[\'status\'])) $status = $arr[\'status\'];\n\n	$title = \'\';\n	if (isset($arr[\'title\'])) $title = $arr[\'title\'];\n\n	$detail = \'\';\n	if (isset($arr[\'detail\'])) $detail = $arr[\'detail\'];\n\n	$errors = array();\n	if (isset($arr[\'errors\'])) $errors = $arr[\'errors\'];\n\n// 	echo __FUNCTION__.\' errors:\';\n// 	dmp($errors);\n\n	// final analysis of results\n	if ($result === FALSE) // connection error, return FALSE\n		return $result;\n	else\n		if (is_int($status)) // status is a number, return descriptive error\n			return array(\'status\'=>$status,\'title\'=>$title,\'detail\'=>$detail,\'errors\'=>$errors);\n		else // return status (string)\n			return array(\'status\'=>$status);\n}\n\nfunction adi_contact_debug($function=\'\',$atts=array(),$name_list=array(),$diags=array()) {\n// debug function for debug=\"1\" attributes or can be used as a standalone tag (<txp:adi_contact_debug />) to output general diags\n	global $zem_contact_form,$zem_contact_submit,$zem_contact_labels,$zem_contact_values,$zem_contact_error,$label_list,$number,$require_label_list,$adi_contact_labels,$prefs,$adi_contact_is_checkbox,$adi_contact_zcr_4500;\n\n	if ($function) {\n		echo \'********** \'.strtoupper($function).\' **********\'.br.br;\n		if ($atts) {\n			echo \'SUPPLIED ATTRIBUTES:\'.br;\n			dmp($atts);\n		}\n		if ($name_list) {\n			echo \'FIELDS OF INTEREST:\'.br;\n			dmp($name_list);\n		}\n		if ($diags) {\n			echo \'DIAGNOSTICS:\'.br;\n			echo tag(implode(n,$diags),\'pre\');\n		}\n	}\n	else {\n		echo \'********** ADI_CONTACT DEBUG **********\'.br.br;\n		echo \'VERSIONS:\'.br;\n		echo \'<pre>\';\n		$plugin_list = array(\'adi_contact\',\'zem_contact_reborn\',\'pap_contact_cleaner\');\n		foreach ($plugin_list as $index => $plugin) {\n			$row = safe_row(\'version,status,load_order\',\'txp_plugin\',\"name=\'$plugin\'\");\n			if ($row) {\n				extract($row);\n				echo \"$plugin: $version\".($status?\' (active)\':\' (not active)\').\", load order $load_order\".br;\n			}\n			if (!$row || !$status) {\n				$func_list = array(\'adi_contact_combo\',\'zem_contact\',\'pap_zemcontact_form\');\n				if (function_exists($func_list[$index]))\n					echo $plugin.\': in plugin cache, load order 0\'.br;\n				else\n					echo $plugin.\': not installed\'.br;\n			}\n		}\n		echo \'TXP: \'.$prefs[\'version\'].br;\n		echo \'PHP: \'.phpversion().br;\n		echo \'MySQL: \'.mysql_get_server_info().br;\n		echo \'Language: yes = \'.gTxt(\'yes\').br;\n		echo \'TXP date/time = \'.date(\"Y-m-d H:i:s\",time()+tz_offset()); // this is actual (TXP adjusted) local time (incl DST) regardless of server timezone\n		echo \'</pre>\';\n		echo \'ZEM CONTACT FORM:\'.br;\n		echo \'<pre>\';\n		if (isset($_POST[\'zem_contact_form_id\']))\n			echo \'id = \'.$_POST[\'zem_contact_form_id\'].br;\n		if (isset($_POST[\'zem_contact_nonce\']))\n			echo \'nonce = \'.$_POST[\'zem_contact_nonce\'].br;\n		echo \'</pre>\';\n		echo \'SUBMITTED FORM LABEL/VALUE ($zem_contact_form):\'.br;\n		dmp($zem_contact_form); // array keyed by labels (null if no fields filled in or not submitted)\n		echo \'SUBMITTED FORM NAME/LABEL ($zem_contact_labels):\'.br;\n		dmp($zem_contact_labels); // array keyed by names (null if no fields filled in or not submitted)\n		echo \'SUBMITTED FORM NAME/VALUE ($zem_contact_values):\'.br;\n		dmp($zem_contact_values); // array keyed by names (null if no fields filled in or not submitted)\n		if (isset($zem_contact_error)) {\n			echo \'ERROR LIST ($zem_contact_error):\'.br;\n			dmp($zem_contact_error);\n		}\n		if (isset($adi_contact_labels)) {\n			echo \'SUPPLIED LABELS FROM adi_contact_labels:\'.br;\n			dmp($adi_contact_labels);\n		}\n		echo \'CHECKBOXES ($adi_contact_is_checkbox):\'.br;\n		dmp($adi_contact_is_checkbox);\n		if ($diags) {\n			echo \'DIAGNOSTICS:\'.br;\n			echo tag(implode(n,$diags),\'pre\');\n		}\n	}\n}\n\nfunction adi_contact_trace($diags) {\n// captain\'s log\n	global $trace;\n\n	if (is_string($diags))\n		$diags = str_split($diags,9999); // convert string to array\n\n	// get meaningful calling function\n	if (is_callable(\'debug_backtrace\')) {\n		$backtrace = debug_backtrace();\n		$function = isset($backtrace[1][\'function\']) ? $backtrace[1][\'function\'] : __FUNCTION__;\n	}\n	else\n		$function = __FUNCTION__;\n\n	if ($function == \"include\") $function = __FUNCTION__; // plugin cache\n	if ($function == \"eval\") $function = __FUNCTION__; // plugin installed\n\n	if (is_object($trace)) // TXP 4.6\n		foreach ($diags as $diag)\n			$trace->log(\'[\'.$function.\': \'.$diag.\']\');\n	else\n		foreach ($diags as $diag)\n			trace_add(\'[\'.$function.\': \'.$diag.\']\');\n}\n', 'bdcfa196ec68d8b5e6b8de4f4241219f', '', '', 0, 9, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_file_tab', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Enhanced file tab', '<h1><strong>adi_file_tab</strong> &#8211; Enhanced file tab</h1>\n\n<p>This plugin provides some enhancements to the standard <span class=\"caps\">TXP</span> file edit tab:</p>\n\n<ul>\n	<li>display file ID, file size, author, last modified timestamp, status, download link</li>\n	<li>optionally replace filename upon upload</li>\n	<li>replace from existing file (with filter option)</li>\n	<li>rename file</li>\n	<li>delete file</li>\n	<li>populate title with filename</li>\n	<li>populate description with title</li>\n	<li>change author</li>\n	<li>view/change downloads count</li>\n</ul>\n\n<h2><strong>Usage</strong></h2>\n\n<p>After installing &amp; activating the plugin, select the file you wish to edit from the Files tab as normal.</p>\n\n<h2><strong>Plugin options</strong></h2>\n\n<ul>\n	<li><i>Plugin privileges</i><br />\n- specify who gets to use the plugin</li>\n	<li><i>Filename replace selected by default?</i><br />\n- automatically replace filename when replacing file<br />\n- default = Yes</li>\n	<li><i>Exclude filename filter</i><br />\n- filter out filenames, containing a specified string, from “Replace from existing file” list<br />\n- default = “.sql”</li>\n</ul>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_file_tab\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=46105\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_file_tab\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '/*\n	adi_file_tab - Enhanced file tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.3		- updated for TXP 4.6 (again)\n				- enhancement: author edit (4.5.7+ only)\n		0.2		- fixes for 4.6 (thanks jpdupont)\n				- enhancement: plugin privs\n		0.1		- initial release\n\n*/\n\n/* TODO\n	- strip out TXP 4.5 code\n	- tidy up styles\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $event,$step,$adi_file_tab_debug,$adi_file_tab_message,$filename;\n\n	$adi_file_tab_debug = 0;\n\n	// bored with anything pre-4.5\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_file_tab_init($event,$step);\n}\n\nfunction adi_file_tab_init($event,$step) {\n	global $textarray,$step,$prefs,$adi_file_tab_debug,$adi_file_tab_url,$adi_file_tab_prefs,$adi_file_tab_outlaws,$adi_file_tab_admin_outlaws,$adi_file_tab_message,$adi_file_tab_txp460,$adi_file_tab_plugin_status;\n\n	// holy crap TXP 4.6!\n	$adi_file_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// preferences & defaults\n	$adi_file_tab_prefs = array(\n		\'adi_file_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n		\'adi_file_tab_replace_default\' => array(\'value\' => \'1\', \'blank\' => 0, \'input\' => \'yesnoradio\', \'label\' => \'adi_file_tab_replace_default\', \'tab\' => \'options\'),\n		\'adi_file_tab_exclude_filter\' => array(\'value\' => \'.sql\', \'blank\' => 1, \'input\' => \'text_input\', \'label\' => \'adi_file_tab_exclude_filter\', \'tab\' => \'options\'),\n	);\n\n	// privs to be excluded (list of group ids)\n	$adi_file_tab_outlaws = array(0);\n	$adi_file_tab_admin_outlaws = array(0);\n\n	// Textpack\n	$adi_file_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_file_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_file_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_file_tab_gtxt = array(\n		\'adi_auto_update_filename\' => \'Automatically update filename\',\n		\'adi_copy_filename_title\' => \'Copy from filename\',\n		\'adi_copy_title_description\' => \'Copy from title\',\n		\'adi_copy_update_filename_uploaded\' => \'Update filename to match uploaded file?\', // typo in string_name!\n		\'adi_delete_file\' => \'Delete file\',\n		\'adi_file_details\' => \'File details\',\n		\'adi_file_exists_dir\' => \'File <strong>{name}</strong> already exists in {dir}\',\n		\'adi_file_renamed\' => \'File renamed\',\n		\'adi_file_rename_fail\' => \'File rename failed\',\n		\'adi_file_replaced\' => \'File replaced\',\n		\'adi_file_tab_confirm_update_filename\' => \'Update filename?\',\n		\'adi_file_tab_exclude_filter\' => \'Exclude filename filter\',\n		\'adi_file_tab_replace_default\' => \'Filename update selected by default?\',\n		\'adi_filtered\' => \'Filtered\',\n		\'adi_name_is_dir\' => \'<strong>{name}</strong> is a directory\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_rename_file\' => \'Rename file\',\n		\'adi_rename\' => \'Rename\',\n		\'adi_replace_from_existing\' => \"Replace from existing file\",\n		\'adi_reset_downloads\' => \'Reset download count\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_timestamp_invalid\' => \'Invalid timestamp\',\n		\'adi_unfiltered\' => \'Unfiltered\',\n		\'adi_update_filename\' => \'Update filename\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_file_tab_gtxt;\n\n	// plugin lifecycle\n	register_callback(\'adi_file_tab_lifecycle\',\'plugin_lifecycle.adi_file_tab\');\n\n	// who gets to play?\n	add_privs(\'adi_file_tab\',adi_file_tab_pref(\'adi_file_tab_privs\'));\n\n	if (has_privs(\'adi_file_tab\')) {\n\n		// lethal injection - TXP 4.5\n		if (($event == \'file\') && !$adi_file_tab_txp460)\n			ob_start(\'adi_file_tab_inject_details\');\n\n		// callbacks for modern times\n		register_callback(\'adi_file_tab\',\'admin_side\',\'body_end\');\n		register_callback(\'adi_file_tab_extend_detail\',\'file_ui\',\'extend_detail_form\');\n\n		// style\n		if (($event == \'file\') && ($step)) // not for file list tab\n			register_callback(\'adi_file_tab_style\',\'admin_side\',\'head_end\');\n\n		// scripts\n		if ($event == \'file\')\n			register_callback(\'adi_file_tab_general_script\',\'admin_side\',\'body_end\');\n		if (($event == \'file\') && (($step == \'file_edit\') || ($step == \'file_insert\') || ($step == \'file_replace\')))\n			register_callback(\'adi_file_tab_edit_script\',\'admin_side\',\'body_end\');\n\n		// get file ID (irrelevant in file list tab)\n		if ($event == \'file\')\n			$id = adi_file_tab_id();\n\n		// new operations\n		if (($event == \'file\') && ($step == \'file_save\')) {\n			// modify file downloads count\n			if (gps(\'adi_file_tab_downloads\') != \'\')\n				adi_file_tab_update_downloads(adi_file_tab_id(),gps(\'adi_file_tab_downloads\'));\n			// modify author (THIS DOESN\'T WORK IN 4.5.5 COZ TXP FILE SAVE OVERWRITES FILE AUTHOR WITH LOGGED IN USER - FIXED IN 4.5.7)\n			if (gps(\'adi_file_tab_author\') != \'\')\n				adi_file_tab_update_author(adi_file_tab_id(),gps(\'adi_file_tab_author\'));\n		}\n\n		// update filename - interfere with standard file replace operation\n		if (gps(\'adi_file_tab_update_filename\')) { // update filename option selected\n			if ($_FILES) // there\'s been a file upload\n				if ($_FILES[\'thefile\'][\'error\'])\n					$adi_file_tab_message = array();\n				else { // automatic filename update during standard file replace\n					// uploaded file\n					$new_filename = \'\';\n					$new_filename = $_FILES[\'thefile\'][\'name\'];\n					$adi_file_tab_message = adi_file_tab_rename_file($id,$new_filename);\n				}\n			else { // last chance filename update\n				$new_filename = gps(\'adi_file_tab_new_filename\');\n				$adi_file_tab_message = adi_file_tab_rename_file($id,$new_filename);\n			}\n			if (count($adi_file_tab_message) > 1) // there\'s a problem, so return user to file edit page\n				$step = \'file_edit\';\n		}\n\n		// rename file - new operation\n		if (gps(\'adi_file_tab_rename_file\')) { // update filename option selected\n			$adi_file_tab_new_filename = gps(\'adi_file_tab_new_filename\');\n			$adi_file_tab_message = adi_file_tab_rename_file($id,$adi_file_tab_new_filename);\n		}\n\n		// replace from existing file - new operation\n		if (gps(\'adi_file_tab_replace_from_existing\')) { // update filename option selected\n			$adi_file_tab_new_filename = gps(\'filename\');\n			$adi_file_tab_message = adi_file_tab_replace_file($id,$adi_file_tab_new_filename);\n		}\n\n	}\n\n	// plugin options\n	$adi_file_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_file_tab\',$adi_file_tab_debug);\n	if ($adi_file_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_file_tab\',\'1,2,6\');\n		register_callback(\'adi_file_tab_options\',\'plugin_prefs.adi_file_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_file_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_file_tab_options\',\'adi_file_tab options\');\n		register_callback(\'adi_file_tab_options\',\'adi_file_tab_options\');\n	}\n}\n\nfunction adi_file_tab_style() {\n// some style action\n	global $prefs;\n\n	echo\n		\'<style type=\"text/css\">\n			/* adi_file_tab - general style */\n			/* new file heading */\n			.adi_file_tab_heading { margin:0; font-weight:bold }\n			.adi_file_tab_details { margin-top:0; padding-bottom:0.2em; border-bottom:0.1em solid #111 }\n			.adi_file_tab_details span { text-transform:lowercase }\n			/* replace file section (& upload file if MISSING) */\n			.replace-file, .upload-file { padding-bottom:1em; border-bottom:0.1em solid #ccc }\n			.replace-file h3 { margin-bottom:0.5em }\n			.replace-file a.pophelp { display:none } /* lose misleading popup help button */\n			.edit-file-replace { margin:0 }\n			/* auto update filename checkbox */\n			input#adi_file_tab_update_filename { margin-left:0 }\n			/* last-chance filename update button */\n			.adi_file_tab_update_filename_button { clear:both; overflow:hidden }\n			.adi_file_tab_update_filename_button p { float:left; margin-bottom:0 }\n			.adi_file_tab_update_filename_button form { float:right; margin-top:0 }\n			/* replace from existing file */\n			#adi_file_tab_replace_from_existing { overflow:hidden; padding-bottom:1em; margin-top:1em; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_replace_from_existing h3 { margin:0 }\n			#adi_file_tab_replace_from_existing p.existing-file { float:left; margin-top:0 }\n			#adi_file_tab_replace_from_existing p.existing-file + p { margin-bottom:0 }\n			#adi_file_tab_replace_from_existing select { margin:0 0 0 1em }\n			#adi_file_tab_replace_from_existing input.smallerbox { float:right }\n			/* rename file section */\n			.summary-details { margin-bottom:1em }\n			#adi_file_tab_rename_file { overflow:hidden; padding-bottom:1em; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_rename_file span.edit-value { float:left; width:auto }\n			#adi_file_tab_rename_file span.edit-value input { margin:0 0 0 1em; width:26em }\n			#adi_file_tab_rename_file input.smallerbox { float:right }\n			#adi_file_tab_rename_file p { margin-bottom:0 }\n			/* delete file section */\n			#adi_file_tab_delete_file { margin:1em 0; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_delete_file h3 { float:left; margin:0 }\n			#adi_file_tab_delete_file p { text-align:right }\n			/* file details area */\n			div.adi_file_tab_details_heading { border-bottom:0 }\n			p.edit-file-description { margin-bottom:0 }\n			/* copy filename to title checkbox */\n			p.adi_file_tab_copy_file_title { float:right; margin:0.5em 0 0; text-align:right }\n			/* copy title to description checkbox */\n			label.adi_file_tab_copy_title_desc { float:right; margin-top:0.3em }\n			/* file created heading */\n			h3#file_created-label { margin-top:0 }\n			/* rearrange date/time inputs (TXP 4.5 only) */\n			p.edit-file-publish-now input { margin-left:0 }\n			p.edit-file-published { float:left; margin-top:0 }\n			p.edit-file-created { float:left; margin-top:0; margin-left:1em }\n			section#file_created { overflow:hidden } /* TXP 4.6 */\n			/* downloads input */\n			p.adi_file_tab_downloads { margin-bottom:0 }\n			p.adi_file_tab_downloads input { width:4em }\n			p.adi_file_tab_downloads span.edit-label { width:auto; margin:1em 0 }\n			p.adi_file_tab_downloads span.edit-value { float:left; margin:1em 0 1em 1em; width:auto }\n			/* author input */\n			p.adi_file_tab_author select { margin:0 0 0 1em }\n			/* TXP 4.6 */\n			div.file-uploader { margin-bottom:0 } /* auto update filename checkbox */\n			div.file-uploader label { font-size:1.15em; font-weight:bold } /* make label look like other h3 */\n			div.file-uploader input[type=submit] { float:right } /* line up standard upload button with others */\n			form.file-detail p.adi_file_tab_copy_file_title { float:none; margin:0 }\n			form.file-detail div.edit-file-title { margin-bottom:0 }\n			div.edit-file-description { overflow:hidden }\n			p.adi_file_tab_file_info { margin:0 }\n			.file-uploader .adi_file_tab_update_filename { outline:1px solid red;}\n		\';\n\n	if ($prefs[\'theme_name\'] == \'hive\')\n		echo \'\n			/* adi_file_tab - Hive style */\n			#adi_file_tab_delete_file h3 { float:none }\n			#adi_file_tab_rename_file span.edit-value { margin-left:1em }\n			#adi_file_tab_rename_file input.smallerbox { margin-right:1em }\n		\';\n\n	echo \'</style>\'.n;\n}\n\nfunction adi_file_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_file_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// install/reinstall: \"installed\"; make active: \"enabled\"; make inactive: \"disabled\"; delete: \"disabled\" then \"deleted\"\n// (as of TXP 4.5, reinstall only triggers \"installed\" event)\n// adi_lifecycle: \"installed\" -> install() regardless, upgrade() if required; \"deleted\" -> uninstall(); \"enabled\" & disabled\" -> do nothing\n	global $adi_file_tab_debug;\n\n	$result = $upgrade_required = \'?\';\n	if ($step == \'installed\') {\n// 		$result = adi_file_tab_install();\n		$result = TRUE;\n		$upgrade_required = adi_file_tab_upgrade();\n		if ($upgrade_required)\n			$result &= adi_file_tab_upgrade(TRUE);\n	}\n	if ($step == \'deleted\')\n		$result = adi_file_tab_uninstall();\n	if ($adi_file_tab_debug)\n		echo \"event=$event,step=$step,result=$result,upgrade_required=$upgrade_required\";\n}\n\nfunction adi_file_tab_upgrade($do_upgrade=FALSE) {\n// check/perform upgrade\n	global $adi_file_tab_debug;\n\n	// these prefs should be non-user (i.e. not private)\n	$pref1_found = safe_row(\'*\',\'txp_prefs\',\"name =\'adi_file_tab_replace_default\' AND user_name != \'\'\",$adi_file_tab_debug);\n	$pref2_found = safe_row(\'*\',\'txp_prefs\',\"name =\'adi_file_tab_exclude_filter\' AND user_name != \'\'\",$adi_file_tab_debug);\n\n	$upgrade_required = $pref1_found || $pref2_found;\n\n	if ($do_upgrade) {\n		if ($upgrade_required) {\n			$res = TRUE;\n			// repair prefs (lose this code if prefs get privatised in future)\n			if ($pref1_found) { // \"user\" pref found\n				$value = adi_file_tab_pref(\'adi_file_tab_replace_default\'); // get current value (i.e. current user or current global or default - in that order)\n				$res &= safe_delete(\'txp_prefs\',\"name = \'adi_file_tab_replace_default\'\",$adi_file_tab_debug); // delete pref\n				$res &= adi_file_tab_pref(\'adi_file_tab_replace_default\',$value); // re-save pref as non-user\n			}\n			if ($pref2_found) {\n				$value = adi_file_tab_pref(\'adi_file_tab_exclude_filter\');\n				$res &= safe_delete(\'txp_prefs\',\"name = \'adi_file_tab_exclude_filter\'\",$adi_file_tab_debug);\n				$res &= adi_file_tab_pref(\'adi_file_tab_exclude_filter\',$value);\n			}\n			return $res;\n		}\n		else\n			return TRUE;\n	}\n	else\n		return $upgrade_required;\n}\n\nfunction adi_file_tab_uninstall() {\n// uninstall adi_file_tab\n	global $adi_file_tab_debug;\n\n	// delete preferences\n	$res = safe_delete(\'txp_prefs\',\"name LIKE \'adi_file_tab_%\'\",$adi_file_tab_debug);\n	return $res;\n}\n\nfunction adi_file_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_file_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_file_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_file_tab_prefs))\n			$html = $adi_file_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_file_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_file_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_file_tab_id() {\n// return file id\n	global $adi_file_tab_debug;\n\n	$id = gps(\'id\'); // existing file\n\n	if (empty($id)) // newly added file\n		$id = safe_field(\'id\',\'txp_file\',\"1=1 ORDER BY id DESC LIMIT 1\",$adi_file_tab_debug); // get largest ID (i.e. last one added)\n\n	return $id;\n}\n\nfunction adi_file_tab_inject_details($buffer) {\n// time for your injection Mister 4.5\n	global $DB;\n\n	if (!isset($DB))\n		$DB = new db;\n\n	$pattern = \'#<p class=\"edit-condition\">.*<div class=\"summary-details#sU\';\n	$insert = \'adi_file_tab_details\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n\n	return $buffer;\n}\n\nfunction adi_file_tab_details() {\n// markup for file details - TXP 4.5\n	global $event,$step,$adi_file_tab_debug,$file_base_path,$adi_file_tab_message;\n\n	$id = adi_file_tab_id();\n\n	$out = \'\';\n	$message = \'\';\n\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\");\n\n	if ($rs) {\n\n		extract($rs);\n\n		if ($message) { // set up message within File Edit pane\n			$message = strong($adi_file_tab_message[0]);\n			$class = \"success\";\n			if (isset($adi_file_tab_message[1])) {\n				// translate E_ERROR, E_WARNING into class\n				if ($adi_file_tab_message[1] == 1)\n					$class = \"error\";\n				if ($adi_file_tab_message[1] == 2)\n					$class = \"warning\";\n				}\n			$message = graf($message,\' class=\"\'.$class.\'\"\');\n		}\n\n		// new file details display\n		$filename = sanitizeForFile($filename);\n		$file_exists = file_exists(build_file_path($file_base_path,$filename));\n		$condition =\n			\'<span class=\"\'\n			.(($file_exists) ? \'success\' : \'error\')\n			.\'\">\'\n			.(($file_exists) ? gTxt(\'file_status_ok\') : gTxt(\'file_status_missing\'))\n			.\'</span>\';\n		$out .= gTxt(\'file\').\' #\'.$id.\' &mdash; \'.$filename;\n		$out = tag($out,\'h3\',\' class=\"adi_file_tab_heading\" title=\"\'.$filename.\'\"\');\n		$out .=\n			graf(\n				\'(\'\n				.adi_file_tab_bytesize($size)\n				.\', \'\n				.tag(gTxt(\'last_modified\'),\'span\')\n				.\' \'\n				.$modified\n				.\', \'\n				.$condition\n				.\') \'\n				.span(\'[\'.make_download_link($id,gTxt(\'download\'),$filename).\']\',\' class=\"files_detail\"\')\n				,\' class=\"adi_file_tab_details\"\'\n			);\n	}\n\n	return $out.$message.\'<div class=\"summary-details\';\n}\n\nfunction adi_file_tab_file_info($id) {\n// TXP 4.6 replacement for adi_file_tab_details()\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message,$adi_file_tab_txp460;\n\n	$out = \'\';\n	$message = \'\';\n\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\");\n	if ($rs) {\n		extract($rs);\n		if ($adi_file_tab_debug) {\n			echo __FUNCTION__.\'():\'.br;\n			dmp($rs);\n		}\n\n		// file details markup\n		$filename = sanitizeForFile($filename);\n		$file_exists = file_exists(build_file_path($file_base_path,$filename));\n		$out =\n			graf(\n				tag(\n					adi_file_tab_bytesize($size)\n					.\', \'\n					.gTxt(\'last_modified\')\n					.\' \'\n					.$modified\n					,\'em\'\n				)\n				,\' class=\"adi_file_tab_file_info\"\'\n			);\n	}\n\n	echo $out.$message;\n}\n\nfunction adi_file_tab_title_checkbox() {\n// markup for \"copy from filename\"\n\n	return\n		graf(\n			tag(\n				checkbox(\'adi_file_tab_copy_file_title\',\'1\',\'0\',\'\', \'adi_file_tab_copy_file_title\').sp.gTxt(\'adi_copy_filename_title\')\n				,\'label\'\n			)\n			,\' class=\"adi_file_tab_checkbox adi_file_tab_copy_file_title\"\'\n		);\n}\n\nfunction adi_file_tab_desc_checkbox() {\n// markup for \"copy from description\"\n\n	return\n		tag(\n			checkbox(\'adi_file_tab_copy_title_desc\',\'1\',\'0\',\'\', \'adi_file_tab_copy_title_desc\').sp.gTxt(\'adi_copy_title_description\')\n			,\'label\'\n			,\' class=\"adi_file_tab_checkbox adi_file_tab_copy_title_desc\"\'\n		);\n}\n\nfunction adi_file_tab($event,$step) {\n// display file info & links\n// e.g. $event = article_ui, $step = file_edit\n	global $prefs,$adi_file_tab_debug,$adi_file_tab_message;\n\n	if ($adi_file_tab_debug) {\n		echo __FUNCTION__.\'():\'.br;\n		echo \'$adi_file_tab_message\';\n		dmp($adi_file_tab_message);\n		echo \'lastmod: \'.$prefs[\'lastmod\'];\n	}\n\n	// homegrown messages\n	adi_file_tab_messagepane();\n}\n\nfunction adi_file_tab_extend_detail($event,$step,$default,$rs) {\n// add extras to bottom of file edit form\n	global $adi_file_tab_txp460;\n\n	$out = \'\';\n\n	if ($rs) {\n		extract($rs);\n		$out .= adi_file_tab_update_filename_checkbox(); // shifted up into standard file replace form (or upload form if file missing)\n		$out .= adi_file_tab_update_filename_button($rs); // shift \"last chance\" update filename button to after file replace area\n		adi_file_tab_rename($rs); // shifted after standard file replace <form> (removed if file missing)\n		$out .= adi_file_tab_replace_from_existing($rs); // shifted to after standard file replace form (or upload form if file missing)\n		$out .= adi_file_tab_delete($rs); // shifted to after replace from existing form\n		if ((version_compare(txp_version,\'4.5\',\'>=\')) && (version_compare(txp_version,\'4.5.7\',\'<=\'))) $out .= adi_file_tab_details_heading(); // jQuery shifted up to top of file details area\n		if ($adi_file_tab_txp460) $out .= adi_file_tab_file_info($id); // shifted to file id area\n		$out .= adi_file_tab_downloads($downloads); // TXP 4.5 - shifted to after description, TXP 4.6 replaces download count (non-input) text\n		$out .= adi_file_tab_title_checkbox(); // shifted to after title input field\n		$out .= adi_file_tab_desc_checkbox(); // shifted to after description textarea\n 		if (version_compare(txp_version,\'4.5.7\',\'>=\')) $out .= adi_file_tab_author($id); // (author update won\'t work in pre 4.5.7 due to bug)\n	}\n\n	return $out;\n}\n\nfunction adi_file_tab_rename($rs) {\n// rename option\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n// 	if ($adi_file_tab_txp460)\n// 		$out = \'<section id=\"adi_file_tab_rename_file\" class=\"txp-details\">\';\n// 	else\n		$out = \'<div id=\"adi_file_tab_rename_file\" class=\"summary-details\">\'; // class=\"summary-details\" added for 4.5 Hive\n\n	extract($rs);\n\n	$out .=\n		tag(gTxt(\'adi_rename_file\'),\'h3\')\n		.form(\n			graf(\n				tag(\n					tag(gTxt(\'filename\'),\'label\',\' for=\"adi_file_tab_new_filename\"\')\n					.fInput(\'text\',\'adi_file_tab_new_filename\',$filename,\'\',\'\',\'\',\'\',\'\',\'adi_file_tab_new_filename\')\n					,\'span\'\n					,\' class=\"edit-value\"\'\n				)\n				.fInput(\n					\'submit\'\n					,\'do_something\'\n					,gTxt(\'adi_rename\')\n					,\'smallerbox\'\n				)\n			)\n			.eInput(\'file\')\n			.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n			.hInput(\'adi_file_tab_rename_file\',\'1\')\n			.hInput(\'id\',$id)\n			.hInput(\'adi_file_tab_old_filename\',$filename)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_file_tab_rename_form\'\n		);\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_update_filename_button($rs) {\n// button to update filename - another chance after a \"auto-update-filename-unchecked\" replace file operation\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message;\n\n	if (gps(\'adi_file_tab_update_filename\'))  // don\'t want button if filename auto update has just been performed\n		return;\n\n	$debug = \'\';\n	$out = \'\';\n	if (isset($_FILES[\'thefile\'])) {\n		$id = adi_file_tab_id();\n		$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n		extract($rs);\n		// new file\n		$new_filename = $_FILES[\'thefile\'][\'name\'];\n		$new_path = build_file_path($file_base_path,$new_filename);\n		$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n		// original file\n		$original_path = build_file_path($file_base_path,$filename);\n		$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n		// create button, but only if a differently named file has been uploaded\n		if ($filename != $new_filename) {\n			$out .= \'<div class=\"adi_file_tab_update_filename_button warning\">\';\n			$out .= graf(strong(gTxt(\'adi_copy_update_filename_uploaded\')).br.\'(\'.$new_filename.\')\');\n			$out .=\n				form(\n					graf(\n						fInput(\n							\'submit\'\n							,\'do_something\'\n							,gTxt(\'adi_update_filename\')\n							,\'smallerbox\'\n						)\n						.eInput(\'file\')\n						.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n						.hInput(\'adi_file_tab_update_filename\',\'1\')\n						.hInput(\'id\',$id)\n						.hInput(\'adi_file_tab_old_filename\',$filename)\n						.hInput(\'adi_file_tab_new_filename\',$new_filename)\n					)\n					,\'\'\n					,\'\'\n					,\'post\'\n					,\'adi_file_tab_update_button\'\n				);\n			$out .= \'</div>\';\n		}\n	}\n\n	if ($adi_file_tab_debug)\n		echo $debug;\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_update_filename_checkbox() {\n// select whether to update filename as part of replace file function or not\n\n	return\n		tag(\n			checkbox(\'adi_file_tab_update_filename\',\'1\',adi_file_tab_pref(\'adi_file_tab_replace_default\'),\'\', \'adi_file_tab_update_filename\')\n			.sp\n			.gTxt(\'adi_auto_update_filename\')\n			,\'label\'\n			,\' class=\"adi_file_tab_update_filename\"\'\n		);\n}\n\nfunction adi_file_tab_rename_file($id,$new_filename) {\n// rename file\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message,$txp_user;\n\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$id = assert_int($id);\n	$out = \'\';\n	$message = array();\n	$new_filename = trim($new_filename);\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	extract($rs);\n\n	$debug .= \"FILE ID = $id\".br;\n	$original_path = build_file_path($file_base_path,$filename);\n	$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n	$debug .= \"TXP USER = $txp_user\".br;\n	$debug .= \"FILE AUTHOR = $author\".br;\n\n	if (!has_privs(\'file.edit\') && !($author == $txp_user && has_privs(\'file.edit.own\')))\n		return gTxt(\'restricted_area\');\n\n	// make up error message (acts as a flag to do nuffin later)\n	if ((strpbrk($new_filename,\"\\\\/?%*:|\\\"<>\") === FALSE) && ($new_filename != \'\')) { // no dodgy characters & not blank\n		if (is_file($new_path))	{ // filename already exists so check if it\'s a different file\n			$rs = safe_row(\"id as other_id\",\"txp_file\",\"filename = \'\".doSlash($new_filename).\"\'\",$adi_file_tab_debug);\n			extract($rs);\n			if ($rs) {\n				$debug .= \"OTHER FILE ID = $other_id\".br;\n				if ($id != $other_id) { // new filename already in use in database\n					$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'file_already_exists\',array(\'{name}\'=>$new_filename)),E_ERROR);\n					$debug .= \'NEW FILENAME ALREADY EXISTS IN DB\'.br;\n				}\n			}\n			else { // found in files directory (but not in database)\n					$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'adi_file_exists_dir\',array(\'{name}\'=>$new_filename,\'{dir}\'=>$file_base_path)),E_ERROR);\n				$debug .= \"NEW FILENAME ALREADY EXISTS IN DIR (BUT NOT IN DB)\".br;\n			}\n		}\n		else if (is_dir($new_path)) { // new filename is a directory\n			$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'adi_name_is_dir\',array(\'{name}\'=>$new_filename)),E_ERROR);\n			$debug .= \'NEW FILENAME IS A DIRECTORY\'.br;\n		}\n	}\n	else\n		$message = array(gTxt(\'invalid_filename\'),E_ERROR);\n\n	// do the doing\n	if (!$message) { // so far so good, so do rename/replace\n		$rename_ok = rename($original_path,$new_path);\n		if ($rename_ok) {\n			// update database accordingly\n			safe_update(\'txp_file\',\"filename = \'\".doSlash($new_filename).\"\', modified = now()\",\"id = $id\",$adi_file_tab_debug);\n			update_lastmod();\n			// get new filename from database (as a double check)\n			$rs = safe_row(\"filename\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n			extract($rs);\n			$message = array(gTxt(\'adi_file_renamed\'));\n		}\n		else {\n			$message = array(gTxt(\'adi_file_rename_fail\'),E_ERROR);\n			$debug .= \'rename() FAILED\';\n		}\n	}\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	return $message;\n}\n\nfunction adi_file_tab_filter_list($filename) {\n// return TRUE if filename doesn\'t contain filter\n\n    return strpos($filename,adi_file_tab_pref(\'adi_file_tab_exclude_filter\')) === FALSE ;\n}\n\nfunction adi_file_tab_replace_from_existing($rs) {\n// replace file markup\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$existing_files = get_filenames();\n\n	if (count($existing_files) > 0) {\n\n// 		if ($adi_file_tab_txp460)\n// 			$out = \'<section id=\"adi_file_tab_replace_from_existing\" class=\"txp-details\">\';\n// 		else\n			$out = \'<div id=\"adi_file_tab_replace_from_existing\" class=\"summary-details\">\';\n\n		extract($rs);\n\n		$out .=\n			tag(gTxt(\'adi_replace_from_existing\'),\'h3\')\n			.\'<div role=\"group\">\'\n			.form(\n				graf(\n					\'<label for=\"file-existing\">\'.gTxt(\'existing_file\').\'</label>\'\n					.selectInput(\'filename\', $existing_files, \'\', 1, \'\', \'file-existing\')\n					, \' class=\"existing-file\"\'\n				)\n				.graf(\n					fInput(\n						\'submit\'\n						,\'do_something\'\n						,gTxt(\'replace_file\')\n						,\'smallerbox\'\n					)\n				)\n				.eInput(\'file\')\n				.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n				.hInput(\'adi_file_tab_replace_from_existing\',\'1\')\n				.hInput(\'id\',$id)\n				.hInput(\'adi_file_tab_old_filename\',$filename)\n			)\n			.\'</div>\';\n\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n\n		echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n	}\n}\n\nfunction adi_file_tab_replace_file($id,$new_filename) {\n// replace file\n	global $adi_file_tab_debug,$file_base_path;\n\n	$message = array();\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$id = assert_int($id);\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	extract($rs);\n\n	$debug .= \"FILE ID = $id\".br;\n	$original_path = build_file_path($file_base_path,$filename);\n	$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n\n	if (trim($new_filename) == \'\')\n		// new filename blank\n		$message = array(gTxt(\'invalid_filename\'),E_ERROR);\n	else {\n		// replace file\n		@chmod($new_path,0644); // taken from file_set_perm() in txp_file.php\n		$size = filesize($new_path);\n		// update database accordingly\n		safe_update(\'txp_file\',\"filename = \'\".doSlash($new_filename).\"\', modified = now()\".\", size = $size\",\"id = $id\",$adi_file_tab_debug);\n		update_lastmod();\n		$message = array(gTxt(\'adi_file_replaced\'));\n	}\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	return $message;\n}\n\nfunction adi_file_tab_delete($rs) {\n// delete file markup - actual delete done by standard functionality\n	global $txp_user,$adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$author = \'\';\n	extract($rs);\n\n	$debug .= \"TXP USER = $txp_user\".br;\n	$debug .= \"FILE AUTHOR = $author\".br;\n\n	$can_delete = has_privs(\'file.delete\') || ($author == $txp_user && has_privs(\'file.delete.own\'));\n\n	$out = \'\';\n	if ($can_delete) {\n		$debug .= \"CAN DELETE FILE\".br;\n// 		if ($adi_file_tab_txp460)\n// 			$out = \'<section id=\"adi_file_tab_delete_file\" class=\"txp-details\">\';\n// 		else\n			$out = \'<div id=\"adi_file_tab_delete_file\" class=\"summary-details\">\';\n\n		$out .=\n			tag(gTxt(\'adi_delete_file\'),\'h3\')\n			.\'<div role=\"group\">\'\n			.form(\n				graf(\n					fInput(\'submit\',\'do_something\',gTxt(\'delete\'),\'smallerbox\',\"\",\'return verify(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\')\')\n					.eInput(\'file\')\n					.sInput(\'file_multi_edit\')\n					.hInput(\'edit_method\',\'delete\')\n					.hInput(\'selected[]\',$id)\n				)\n			)\n			.\'</div>\';\n\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n	}\n	else\n		$debug .= \"CAN\'T DELETE FILE\".br;\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_details_heading() {\n// file details heading markup - 4.5 only\n\n	return \'<div class=\"adi_file_tab_details_heading txp-details\">\'.tag(gTxt(\'adi_file_details\'),\'h3\',\' class=\"adi_file_tab_details_heading\"\').\'</div>\';\n}\n\nfunction adi_file_tab_author($id) {\n// markup for author select\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$id = assert_int($id);\n\n	$rs = safe_row(\"author\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	if ($rs) {\n		extract($rs);\n		if ($author) {\n			$rs = safe_column(\'name\',\'txp_users\',\'privs > 0\',$adi_file_tab_debug);\n			if ($rs) {\n				if ($adi_file_tab_txp460) {\n					echo\n						inputLabel(\n							\'adi_file_tab_author\',\n							selectInput(\'adi_file_tab_author\',$rs,$author,\'\',\'\',\'adi_file_tab_author\'),\n							\'author\', \'\', array(\'class\' => \'txp-form-field adi_file_tab_author\')\n						);\n				}\n				else\n					echo\n						graf(\n							tag(gTxt(\'author\'),\'label\',\' for=\"adi_file_tab_author\"\')\n							.selectInput(\'adi_file_tab_author\',$rs,$author,\'\',\'\',\'adi_file_tab_author\')\n							,\' class=\"adi_file_tab_author\"\'\n						);\n			}\n		}\n	}\n}\n\nfunction adi_file_tab_update_author($id=NULL,$author=\'\') {\n// update author\n	global $adi_file_tab_debug;\n\n	$id = assert_int($id);\n	update_lastmod();\n\n	return safe_update(\'txp_file\',\"author = \'$author\'\",\"id = $id\",$adi_file_tab_debug);\n}\n\nfunction adi_file_tab_downloads($downloads) {\n// input field for downloads count\n	global $adi_file_tab_txp460;\n\n	if ($adi_file_tab_txp460) {\n		return\n			inputLabel(\n				\'adi_file_tab_downloads\',\n				fInput(\'text\', \'adi_file_tab_downloads\', $downloads, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'adi_file_tab_downloads\'),\n				\'download_count\', \'\', array(\'class\' => \'txp-form-field adi_file_tab_edit_download_count\')\n			);\n	}\n	else {\n		$out =\n			tag(\n				tag(\n					gTxt(\'downloads\')\n					,\'label\'\n					,\' for=\"adi_file_tab_downloads\"\'\n				)\n				,\'span\'\n				,\' class=\"edit-label\"\'\n			)\n			.tag(\n				fInput(\'text\',\'adi_file_tab_downloads\',$downloads,\'\',\'\',\'\',\'\',\'\',\'adi_file_tab_downloads\')\n				,\'span\'\n				,\' class=\"edit-value\"\'\n			);\n\n		return graf($out,\' class=\"adi_file_tab_downloads\"\');\n	}\n}\n\nfunction adi_file_tab_update_downloads($id=NULL,$downloads=\'\') {\n// update downloads count\n	global $adi_file_tab_debug;\n\n	$id = assert_int($id);\n	$downloads = trim($downloads);\n\n	if (($id != NULL) && ctype_digit($downloads)) {\n		update_lastmod();\n		return safe_update(\'txp_file\',\"downloads=\'$downloads\'\",\'id=\'.$id,$adi_file_tab_debug);\n	}\n}\n\nfunction adi_file_tab_messagepane() {\n// generate messagepane & message\n	global $adi_file_tab_message;\n\n	if ($adi_file_tab_message) {\n		$class = \'success\';\n		if (is_array($adi_file_tab_message)) {\n			$message = $adi_file_tab_message[0];\n			if (isset($adi_file_tab_message[1])) {\n				// translate E_ERROR, E_WARNING into class\n				if ($adi_file_tab_message[1] == 1)\n					$class = \"error\";\n				if ($adi_file_tab_message[1] == 2)\n					$class = \"warning\";\n				}\n			}\n		else { // message from adi_file_tab_rename_file\n			$message = strip_tags($adi_file_tab_message);\n		}\n\n		$html = \'<span id=\"message\" class=\"messageflash \'.$class.\'\">\'.$message.\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n\n$message_script = <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n		$(\'#messagepane\').append(\'$html\');\n		$(\"#messagepane\").css({\n			left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n		});\n		$(window).resize();\n	// -->\n	</script>\nEND_SCRIPT;\n\n		echo $message_script;\n	}\n}\n\nfunction adi_file_tab_bytesize($bytes) {\n// transform bytes into killer-bytes ...\n\n	if ($bytes < 1024) {\n		$size = $bytes.\' bytes\';\n	}\n	else if ($bytes < pow(1024,2)) {\n			$size = round($bytes / 1024, 2);\n			$size .= \' KB\';\n	}\n	else if ($bytes < pow(1024,3)) {\n			$size = round($bytes / pow(1024,2), 2);\n			$size .= \' MB\';\n	}\n	else {\n			$size = round($bytes / pow(1024,3), 2);\n			$size .= \' GB\';\n	}\n	return $size;\n}\n\nfunction adi_file_tab_edit_script() {\n// jQuery magic for file edit tab\n	global $adi_file_tab_debug,$adi_file_tab_message;\n\n	$id = adi_file_tab_id();\n\n	if (!empty($id)) {\n		$rs = safe_row(\"filename\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n		if ($rs) {\n			extract($rs);\n\n			// filename is POSTed by standard Save button, so if there\'s a failure (in an adi_file_tab function), have to make sure the filename from database is used\n			$save_button_tweak = \'// do nothing\';\n			if (count($adi_file_tab_message) > 1)\n				$save_button_tweak = \'$(\"input[name=filename]\").val(\"\'.$filename.\'\");\';\n\n			if ($adi_file_tab_debug) {\n				$debug = __FUNCTION__.\'():\'.br;\n				$debug .= \"FILENAME = $filename\".br;\n				$debug .= \"save_button_tweak = $save_button_tweak\".br;\n				echo $debug;\n			}\n\n			echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n\n   		// copy filename to title\n		var old_title = $(\"input[name=title]\").val();\n		$(\"#adi_file_tab_copy_file_title\").click(function(){\n// 			console.log(\"Old title = \" + old_title);\n			// TXP 4.5 - filename recorded in title attribute (added by plugin)\n			var filename = $(\"h3.adi_file_tab_heading\").attr(\"title\");\n			if (filename == null) { // TXP 4.6\n				var filename = $(\"div.edit-file-name div.txp-form-field-value a\").html();\n			}\n// 			console.log(\"Filename = \" + filename);\n			if ($(this).prop(\"checked\")) {\n				$(\"input[name=title]\").val(filename);\n			}\n			else {\n 				$(\"input[name=title]\").val(old_title);\n			}\n		});\n\n		// copy title to description\n		var old_desc = $(\"textarea\").val();\n		$(\"#adi_file_tab_copy_title_desc\").click(function(){\n// 			console.log(\"Old desc = \" + old_desc);\n			var title = $(\"input[name=title]\").val();\n// 			console.log(\"Title = \" + title);\n			if ($(this).prop(\"checked\")) {\n				$(\"textarea\").val(title);\n			}\n			else {\n 				$(\"textarea\").val(old_desc);\n			}\n		});\n\n		// tweak Save button filename value?\n		$save_button_tweak\n\n		// do stuff depending on whether file ok or missing (with TXP 4.5 & 4.6 variants)\n		if ($(\'.edit-file-title\').length) { // FILE OK (using file title field to indicate whether file ok)\n			if ($(\'div.txp-body\').length) { // TXP 4.5\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"div.replace-file form\");\n 				$(\"#adi_file_tab_rename_file\").insertAfter(\"div.replace-file\");\n				$(\"#adi_file_tab_replace_from_existing\").prependTo(\"div.file-detail\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_details_heading\").prependTo(\"form#file_details\");\n				$(\"p.adi_file_tab_copy_file_title\").appendTo(\"p.edit-file-title\");\n				$(\"label.adi_file_tab_copy_title_desc\").appendTo(\"p.edit-file-description\");\n				$(\"p.adi_file_tab_downloads\").appendTo(\"p.edit-file-description\");\n				$(\"p.adi_file_tab_author\").insertAfter(\"p.adi_file_tab_downloads\");\n			}\n			else { // TXP 4.6\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"form.replace-file\");\n 				$(\"#adi_file_tab_rename_file\").insertAfter(\"form.replace-file\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\"form.replace-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"#adi_file_tab_replace_from_existing\");\n				$(\"p.adi_file_tab_copy_file_title\").insertAfter(\"div.edit-file-title\");\n				$(\"label.adi_file_tab_copy_title_desc\").appendTo(\"div.edit-file-description\");\n				$(\"div.edit-file-download-count\").remove();\n				$(\"div.adi_file_tab_edit_download_count\").prependTo(\"form#file_details\");\n			}\n			$(\"div.adi_file_tab_author\").prependTo(\"form#file_details\");\n		}\n		else { // FILE MISSING\n			if ($(\'div.txp-body\').length) { // TXP 4.5\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"div.upload-file form\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\"div.upload-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_details_heading\").appendTo(\"div.not-exists\");\n				$(\"p.adi_file_tab_downloads\").appendTo(\"div.adi_file_tab_details_heading\"); // put the downloads field somewhere else (TXP 4.5)\n				$(\"p.adi_file_tab_author\").insertAfter(\"div.adi_file_tab_details_heading\");\n			}\n			else { // TXP 4.6\n				$(\"label.adi_file_tab_update_filename\").appendTo(\".upload-file\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\".upload-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"#adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_author\").insertAfter(\".edit-file-name\");\n			}\n			$(\"#adi_file_tab_rename_file\").remove();\n			$(\"p.adi_file_tab_copy_file_title\").remove();\n			$(\"label.adi_file_tab_copy_title_desc\").remove();\n			$(\"form#assign_file\").remove(); // lose standard \"replace from existing\" list (in favour of adi_file_tab\'s version)\n		}\n\n		// do regardless of ok or missing\n		if ($(\'div.txp-body\').length) { // TXP 4.5\n			$(\"div.adi_file_tab_update_filename_button\").appendTo(\"div.replace-file\");\n		}\n		else { // TXP 4.6\n			$(\"p.adi_file_tab_file_info\").appendTo(\"div.edit-file-id .txp-form-field-value\");\n			$(\"div.adi_file_tab_update_filename_button\").insertAfter(\"form.replace-file\");\n		}\n\n	});\n\n</script>\nEND_SCRIPT;\n		}\n	}\n}\n\nfunction adi_file_tab_general_script() {\n// jQuery magic for all file tabs\n	global $adi_file_tab_debug,$adi_file_tab_plugin_status;\n\n	// filtration\n	$filter = adi_file_tab_pref(\'adi_file_tab_exclude_filter\');\n\n	if ($filter == \'\') {\n		$link_text = gTxt(\'adi_unfiltered\');\n		$link_title = \'Add filter\';\n	}\n	else {\n		$link_text = gTxt(\'adi_filtered\');\n		$link_title = \'Edit filter\';\n	}\n\n	if ($adi_file_tab_plugin_status)  // proper install (options under Plugins tab)\n		$event = \'plugin_prefs.adi_file_tab\';\n	else // txpdev (options under Extensions tab)\n		$event = \'adi_file_tab_options\';\n\n	$calling_card = eLink($event,\'\',\'\',\'\',$link_text,\'\',\'\',$link_title);\n\n	if ($filter != \'\') {\n		echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n		// remove items according to filter\n		$(\"#file-existing option[value*=\'$filter\']\").remove();\n	});\n\n</script>\nEND_SCRIPT;\n	}\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n		// leave calling card\n		$(\"#file-existing\").after(\' ($calling_card)\');\n	});\n\n</script>\nEND_SCRIPT;\n\n}\n\nfunction adi_file_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_file_tab_prefs,$adi_file_tab_outlaws,$adi_file_tab_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_file_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_file_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_file_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_file_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_file_tab_pref(\'adi_file_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_file_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_file_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_file_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out);\n					}\n					// plugin privs\n					if ($pref_name == \'adi_file_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_file_tab_pref(\'adi_file_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_file_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_file_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_file_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out);\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_file_tab_pref($pref_name)),$pref_name)\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\"\'\n				)\n			);\n}\n\nfunction adi_file_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_file_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		// run through all prefs according to tab\n		foreach ($adi_file_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) {\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_file_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_file_tab_prefs[$pref_name][\'value\'];\n				$result &= adi_file_tab_pref($pref_name,$value);\n			}\n		}\n		return $result;\n	}\n}\n\nfunction adi_file_tab_options($event,$step) {\n// plugin options page\n	global $adi_file_tab_debug,$adi_file_tab_url,$textarray;\n\n	$message = \'\';\n\n	if (adi_file_tab_upgrade())\n		$step = \'upgrade\';\n\n	// steppers\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_file_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_menu_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_file_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_file_tab_upgrade(TRUE);\n		$result ? $message = gTxt(\'adi_upgraded\') : $message = array(gTxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_file_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_file_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// link to file list tab\n			.graf(href(gTxt(\'tab_file\'),\'?event=file\'))\n			// prefs\n			.adi_file_tab_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_file_tab_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_file_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n}\n', '/*\n	adi_file_tab - Enhanced file tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.3		- updated for TXP 4.6 (again)\n				- enhancement: author edit (4.5.7+ only)\n		0.2		- fixes for 4.6 (thanks jpdupont)\n				- enhancement: plugin privs\n		0.1		- initial release\n\n*/\n\n/* TODO\n	- strip out TXP 4.5 code\n	- tidy up styles\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $event,$step,$adi_file_tab_debug,$adi_file_tab_message,$filename;\n\n	$adi_file_tab_debug = 0;\n\n	// bored with anything pre-4.5\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_file_tab_init($event,$step);\n}\n\nfunction adi_file_tab_init($event,$step) {\n	global $textarray,$step,$prefs,$adi_file_tab_debug,$adi_file_tab_url,$adi_file_tab_prefs,$adi_file_tab_outlaws,$adi_file_tab_admin_outlaws,$adi_file_tab_message,$adi_file_tab_txp460,$adi_file_tab_plugin_status;\n\n	// holy crap TXP 4.6!\n	$adi_file_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// preferences & defaults\n	$adi_file_tab_prefs = array(\n		\'adi_file_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n		\'adi_file_tab_replace_default\' => array(\'value\' => \'1\', \'blank\' => 0, \'input\' => \'yesnoradio\', \'label\' => \'adi_file_tab_replace_default\', \'tab\' => \'options\'),\n		\'adi_file_tab_exclude_filter\' => array(\'value\' => \'.sql\', \'blank\' => 1, \'input\' => \'text_input\', \'label\' => \'adi_file_tab_exclude_filter\', \'tab\' => \'options\'),\n	);\n\n	// privs to be excluded (list of group ids)\n	$adi_file_tab_outlaws = array(0);\n	$adi_file_tab_admin_outlaws = array(0);\n\n	// Textpack\n	$adi_file_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_file_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_file_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_file_tab_gtxt = array(\n		\'adi_auto_update_filename\' => \'Automatically update filename\',\n		\'adi_copy_filename_title\' => \'Copy from filename\',\n		\'adi_copy_title_description\' => \'Copy from title\',\n		\'adi_copy_update_filename_uploaded\' => \'Update filename to match uploaded file?\', // typo in string_name!\n		\'adi_delete_file\' => \'Delete file\',\n		\'adi_file_details\' => \'File details\',\n		\'adi_file_exists_dir\' => \'File <strong>{name}</strong> already exists in {dir}\',\n		\'adi_file_renamed\' => \'File renamed\',\n		\'adi_file_rename_fail\' => \'File rename failed\',\n		\'adi_file_replaced\' => \'File replaced\',\n		\'adi_file_tab_confirm_update_filename\' => \'Update filename?\',\n		\'adi_file_tab_exclude_filter\' => \'Exclude filename filter\',\n		\'adi_file_tab_replace_default\' => \'Filename update selected by default?\',\n		\'adi_filtered\' => \'Filtered\',\n		\'adi_name_is_dir\' => \'<strong>{name}</strong> is a directory\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_rename_file\' => \'Rename file\',\n		\'adi_rename\' => \'Rename\',\n		\'adi_replace_from_existing\' => \"Replace from existing file\",\n		\'adi_reset_downloads\' => \'Reset download count\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_timestamp_invalid\' => \'Invalid timestamp\',\n		\'adi_unfiltered\' => \'Unfiltered\',\n		\'adi_update_filename\' => \'Update filename\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_file_tab_gtxt;\n\n	// plugin lifecycle\n	register_callback(\'adi_file_tab_lifecycle\',\'plugin_lifecycle.adi_file_tab\');\n\n	// who gets to play?\n	add_privs(\'adi_file_tab\',adi_file_tab_pref(\'adi_file_tab_privs\'));\n\n	if (has_privs(\'adi_file_tab\')) {\n\n		// lethal injection - TXP 4.5\n		if (($event == \'file\') && !$adi_file_tab_txp460)\n			ob_start(\'adi_file_tab_inject_details\');\n\n		// callbacks for modern times\n		register_callback(\'adi_file_tab\',\'admin_side\',\'body_end\');\n		register_callback(\'adi_file_tab_extend_detail\',\'file_ui\',\'extend_detail_form\');\n\n		// style\n		if (($event == \'file\') && ($step)) // not for file list tab\n			register_callback(\'adi_file_tab_style\',\'admin_side\',\'head_end\');\n\n		// scripts\n		if ($event == \'file\')\n			register_callback(\'adi_file_tab_general_script\',\'admin_side\',\'body_end\');\n		if (($event == \'file\') && (($step == \'file_edit\') || ($step == \'file_insert\') || ($step == \'file_replace\')))\n			register_callback(\'adi_file_tab_edit_script\',\'admin_side\',\'body_end\');\n\n		// get file ID (irrelevant in file list tab)\n		if ($event == \'file\')\n			$id = adi_file_tab_id();\n\n		// new operations\n		if (($event == \'file\') && ($step == \'file_save\')) {\n			// modify file downloads count\n			if (gps(\'adi_file_tab_downloads\') != \'\')\n				adi_file_tab_update_downloads(adi_file_tab_id(),gps(\'adi_file_tab_downloads\'));\n			// modify author (THIS DOESN\'T WORK IN 4.5.5 COZ TXP FILE SAVE OVERWRITES FILE AUTHOR WITH LOGGED IN USER - FIXED IN 4.5.7)\n			if (gps(\'adi_file_tab_author\') != \'\')\n				adi_file_tab_update_author(adi_file_tab_id(),gps(\'adi_file_tab_author\'));\n		}\n\n		// update filename - interfere with standard file replace operation\n		if (gps(\'adi_file_tab_update_filename\')) { // update filename option selected\n			if ($_FILES) // there\'s been a file upload\n				if ($_FILES[\'thefile\'][\'error\'])\n					$adi_file_tab_message = array();\n				else { // automatic filename update during standard file replace\n					// uploaded file\n					$new_filename = \'\';\n					$new_filename = $_FILES[\'thefile\'][\'name\'];\n					$adi_file_tab_message = adi_file_tab_rename_file($id,$new_filename);\n				}\n			else { // last chance filename update\n				$new_filename = gps(\'adi_file_tab_new_filename\');\n				$adi_file_tab_message = adi_file_tab_rename_file($id,$new_filename);\n			}\n			if (count($adi_file_tab_message) > 1) // there\'s a problem, so return user to file edit page\n				$step = \'file_edit\';\n		}\n\n		// rename file - new operation\n		if (gps(\'adi_file_tab_rename_file\')) { // update filename option selected\n			$adi_file_tab_new_filename = gps(\'adi_file_tab_new_filename\');\n			$adi_file_tab_message = adi_file_tab_rename_file($id,$adi_file_tab_new_filename);\n		}\n\n		// replace from existing file - new operation\n		if (gps(\'adi_file_tab_replace_from_existing\')) { // update filename option selected\n			$adi_file_tab_new_filename = gps(\'filename\');\n			$adi_file_tab_message = adi_file_tab_replace_file($id,$adi_file_tab_new_filename);\n		}\n\n	}\n\n	// plugin options\n	$adi_file_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_file_tab\',$adi_file_tab_debug);\n	if ($adi_file_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_file_tab\',\'1,2,6\');\n		register_callback(\'adi_file_tab_options\',\'plugin_prefs.adi_file_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_file_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_file_tab_options\',\'adi_file_tab options\');\n		register_callback(\'adi_file_tab_options\',\'adi_file_tab_options\');\n	}\n}\n\nfunction adi_file_tab_style() {\n// some style action\n	global $prefs;\n\n	echo\n		\'<style type=\"text/css\">\n			/* adi_file_tab - general style */\n			/* new file heading */\n			.adi_file_tab_heading { margin:0; font-weight:bold }\n			.adi_file_tab_details { margin-top:0; padding-bottom:0.2em; border-bottom:0.1em solid #111 }\n			.adi_file_tab_details span { text-transform:lowercase }\n			/* replace file section (& upload file if MISSING) */\n			.replace-file, .upload-file { padding-bottom:1em; border-bottom:0.1em solid #ccc }\n			.replace-file h3 { margin-bottom:0.5em }\n			.replace-file a.pophelp { display:none } /* lose misleading popup help button */\n			.edit-file-replace { margin:0 }\n			/* auto update filename checkbox */\n			input#adi_file_tab_update_filename { margin-left:0 }\n			/* last-chance filename update button */\n			.adi_file_tab_update_filename_button { clear:both; overflow:hidden }\n			.adi_file_tab_update_filename_button p { float:left; margin-bottom:0 }\n			.adi_file_tab_update_filename_button form { float:right; margin-top:0 }\n			/* replace from existing file */\n			#adi_file_tab_replace_from_existing { overflow:hidden; padding-bottom:1em; margin-top:1em; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_replace_from_existing h3 { margin:0 }\n			#adi_file_tab_replace_from_existing p.existing-file { float:left; margin-top:0 }\n			#adi_file_tab_replace_from_existing p.existing-file + p { margin-bottom:0 }\n			#adi_file_tab_replace_from_existing select { margin:0 0 0 1em }\n			#adi_file_tab_replace_from_existing input.smallerbox { float:right }\n			/* rename file section */\n			.summary-details { margin-bottom:1em }\n			#adi_file_tab_rename_file { overflow:hidden; padding-bottom:1em; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_rename_file span.edit-value { float:left; width:auto }\n			#adi_file_tab_rename_file span.edit-value input { margin:0 0 0 1em; width:26em }\n			#adi_file_tab_rename_file input.smallerbox { float:right }\n			#adi_file_tab_rename_file p { margin-bottom:0 }\n			/* delete file section */\n			#adi_file_tab_delete_file { margin:1em 0; border-bottom:0.1em solid #ccc }\n			#adi_file_tab_delete_file h3 { float:left; margin:0 }\n			#adi_file_tab_delete_file p { text-align:right }\n			/* file details area */\n			div.adi_file_tab_details_heading { border-bottom:0 }\n			p.edit-file-description { margin-bottom:0 }\n			/* copy filename to title checkbox */\n			p.adi_file_tab_copy_file_title { float:right; margin:0.5em 0 0; text-align:right }\n			/* copy title to description checkbox */\n			label.adi_file_tab_copy_title_desc { float:right; margin-top:0.3em }\n			/* file created heading */\n			h3#file_created-label { margin-top:0 }\n			/* rearrange date/time inputs (TXP 4.5 only) */\n			p.edit-file-publish-now input { margin-left:0 }\n			p.edit-file-published { float:left; margin-top:0 }\n			p.edit-file-created { float:left; margin-top:0; margin-left:1em }\n			section#file_created { overflow:hidden } /* TXP 4.6 */\n			/* downloads input */\n			p.adi_file_tab_downloads { margin-bottom:0 }\n			p.adi_file_tab_downloads input { width:4em }\n			p.adi_file_tab_downloads span.edit-label { width:auto; margin:1em 0 }\n			p.adi_file_tab_downloads span.edit-value { float:left; margin:1em 0 1em 1em; width:auto }\n			/* author input */\n			p.adi_file_tab_author select { margin:0 0 0 1em }\n			/* TXP 4.6 */\n			div.file-uploader { margin-bottom:0 } /* auto update filename checkbox */\n			div.file-uploader label { font-size:1.15em; font-weight:bold } /* make label look like other h3 */\n			div.file-uploader input[type=submit] { float:right } /* line up standard upload button with others */\n			form.file-detail p.adi_file_tab_copy_file_title { float:none; margin:0 }\n			form.file-detail div.edit-file-title { margin-bottom:0 }\n			div.edit-file-description { overflow:hidden }\n			p.adi_file_tab_file_info { margin:0 }\n			.file-uploader .adi_file_tab_update_filename { outline:1px solid red;}\n		\';\n\n	if ($prefs[\'theme_name\'] == \'hive\')\n		echo \'\n			/* adi_file_tab - Hive style */\n			#adi_file_tab_delete_file h3 { float:none }\n			#adi_file_tab_rename_file span.edit-value { margin-left:1em }\n			#adi_file_tab_rename_file input.smallerbox { margin-right:1em }\n		\';\n\n	echo \'</style>\'.n;\n}\n\nfunction adi_file_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_file_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// install/reinstall: \"installed\"; make active: \"enabled\"; make inactive: \"disabled\"; delete: \"disabled\" then \"deleted\"\n// (as of TXP 4.5, reinstall only triggers \"installed\" event)\n// adi_lifecycle: \"installed\" -> install() regardless, upgrade() if required; \"deleted\" -> uninstall(); \"enabled\" & disabled\" -> do nothing\n	global $adi_file_tab_debug;\n\n	$result = $upgrade_required = \'?\';\n	if ($step == \'installed\') {\n// 		$result = adi_file_tab_install();\n		$result = TRUE;\n		$upgrade_required = adi_file_tab_upgrade();\n		if ($upgrade_required)\n			$result &= adi_file_tab_upgrade(TRUE);\n	}\n	if ($step == \'deleted\')\n		$result = adi_file_tab_uninstall();\n	if ($adi_file_tab_debug)\n		echo \"event=$event,step=$step,result=$result,upgrade_required=$upgrade_required\";\n}\n\nfunction adi_file_tab_upgrade($do_upgrade=FALSE) {\n// check/perform upgrade\n	global $adi_file_tab_debug;\n\n	// these prefs should be non-user (i.e. not private)\n	$pref1_found = safe_row(\'*\',\'txp_prefs\',\"name =\'adi_file_tab_replace_default\' AND user_name != \'\'\",$adi_file_tab_debug);\n	$pref2_found = safe_row(\'*\',\'txp_prefs\',\"name =\'adi_file_tab_exclude_filter\' AND user_name != \'\'\",$adi_file_tab_debug);\n\n	$upgrade_required = $pref1_found || $pref2_found;\n\n	if ($do_upgrade) {\n		if ($upgrade_required) {\n			$res = TRUE;\n			// repair prefs (lose this code if prefs get privatised in future)\n			if ($pref1_found) { // \"user\" pref found\n				$value = adi_file_tab_pref(\'adi_file_tab_replace_default\'); // get current value (i.e. current user or current global or default - in that order)\n				$res &= safe_delete(\'txp_prefs\',\"name = \'adi_file_tab_replace_default\'\",$adi_file_tab_debug); // delete pref\n				$res &= adi_file_tab_pref(\'adi_file_tab_replace_default\',$value); // re-save pref as non-user\n			}\n			if ($pref2_found) {\n				$value = adi_file_tab_pref(\'adi_file_tab_exclude_filter\');\n				$res &= safe_delete(\'txp_prefs\',\"name = \'adi_file_tab_exclude_filter\'\",$adi_file_tab_debug);\n				$res &= adi_file_tab_pref(\'adi_file_tab_exclude_filter\',$value);\n			}\n			return $res;\n		}\n		else\n			return TRUE;\n	}\n	else\n		return $upgrade_required;\n}\n\nfunction adi_file_tab_uninstall() {\n// uninstall adi_file_tab\n	global $adi_file_tab_debug;\n\n	// delete preferences\n	$res = safe_delete(\'txp_prefs\',\"name LIKE \'adi_file_tab_%\'\",$adi_file_tab_debug);\n	return $res;\n}\n\nfunction adi_file_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_file_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_file_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_file_tab_prefs))\n			$html = $adi_file_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_file_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_file_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_file_tab_id() {\n// return file id\n	global $adi_file_tab_debug;\n\n	$id = gps(\'id\'); // existing file\n\n	if (empty($id)) // newly added file\n		$id = safe_field(\'id\',\'txp_file\',\"1=1 ORDER BY id DESC LIMIT 1\",$adi_file_tab_debug); // get largest ID (i.e. last one added)\n\n	return $id;\n}\n\nfunction adi_file_tab_inject_details($buffer) {\n// time for your injection Mister 4.5\n	global $DB;\n\n	if (!isset($DB))\n		$DB = new db;\n\n	$pattern = \'#<p class=\"edit-condition\">.*<div class=\"summary-details#sU\';\n	$insert = \'adi_file_tab_details\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n\n	return $buffer;\n}\n\nfunction adi_file_tab_details() {\n// markup for file details - TXP 4.5\n	global $event,$step,$adi_file_tab_debug,$file_base_path,$adi_file_tab_message;\n\n	$id = adi_file_tab_id();\n\n	$out = \'\';\n	$message = \'\';\n\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\");\n\n	if ($rs) {\n\n		extract($rs);\n\n		if ($message) { // set up message within File Edit pane\n			$message = strong($adi_file_tab_message[0]);\n			$class = \"success\";\n			if (isset($adi_file_tab_message[1])) {\n				// translate E_ERROR, E_WARNING into class\n				if ($adi_file_tab_message[1] == 1)\n					$class = \"error\";\n				if ($adi_file_tab_message[1] == 2)\n					$class = \"warning\";\n				}\n			$message = graf($message,\' class=\"\'.$class.\'\"\');\n		}\n\n		// new file details display\n		$filename = sanitizeForFile($filename);\n		$file_exists = file_exists(build_file_path($file_base_path,$filename));\n		$condition =\n			\'<span class=\"\'\n			.(($file_exists) ? \'success\' : \'error\')\n			.\'\">\'\n			.(($file_exists) ? gTxt(\'file_status_ok\') : gTxt(\'file_status_missing\'))\n			.\'</span>\';\n		$out .= gTxt(\'file\').\' #\'.$id.\' &mdash; \'.$filename;\n		$out = tag($out,\'h3\',\' class=\"adi_file_tab_heading\" title=\"\'.$filename.\'\"\');\n		$out .=\n			graf(\n				\'(\'\n				.adi_file_tab_bytesize($size)\n				.\', \'\n				.tag(gTxt(\'last_modified\'),\'span\')\n				.\' \'\n				.$modified\n				.\', \'\n				.$condition\n				.\') \'\n				.span(\'[\'.make_download_link($id,gTxt(\'download\'),$filename).\']\',\' class=\"files_detail\"\')\n				,\' class=\"adi_file_tab_details\"\'\n			);\n	}\n\n	return $out.$message.\'<div class=\"summary-details\';\n}\n\nfunction adi_file_tab_file_info($id) {\n// TXP 4.6 replacement for adi_file_tab_details()\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message,$adi_file_tab_txp460;\n\n	$out = \'\';\n	$message = \'\';\n\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\");\n	if ($rs) {\n		extract($rs);\n		if ($adi_file_tab_debug) {\n			echo __FUNCTION__.\'():\'.br;\n			dmp($rs);\n		}\n\n		// file details markup\n		$filename = sanitizeForFile($filename);\n		$file_exists = file_exists(build_file_path($file_base_path,$filename));\n		$out =\n			graf(\n				tag(\n					adi_file_tab_bytesize($size)\n					.\', \'\n					.gTxt(\'last_modified\')\n					.\' \'\n					.$modified\n					,\'em\'\n				)\n				,\' class=\"adi_file_tab_file_info\"\'\n			);\n	}\n\n	echo $out.$message;\n}\n\nfunction adi_file_tab_title_checkbox() {\n// markup for \"copy from filename\"\n\n	return\n		graf(\n			tag(\n				checkbox(\'adi_file_tab_copy_file_title\',\'1\',\'0\',\'\', \'adi_file_tab_copy_file_title\').sp.gTxt(\'adi_copy_filename_title\')\n				,\'label\'\n			)\n			,\' class=\"adi_file_tab_checkbox adi_file_tab_copy_file_title\"\'\n		);\n}\n\nfunction adi_file_tab_desc_checkbox() {\n// markup for \"copy from description\"\n\n	return\n		tag(\n			checkbox(\'adi_file_tab_copy_title_desc\',\'1\',\'0\',\'\', \'adi_file_tab_copy_title_desc\').sp.gTxt(\'adi_copy_title_description\')\n			,\'label\'\n			,\' class=\"adi_file_tab_checkbox adi_file_tab_copy_title_desc\"\'\n		);\n}\n\nfunction adi_file_tab($event,$step) {\n// display file info & links\n// e.g. $event = article_ui, $step = file_edit\n	global $prefs,$adi_file_tab_debug,$adi_file_tab_message;\n\n	if ($adi_file_tab_debug) {\n		echo __FUNCTION__.\'():\'.br;\n		echo \'$adi_file_tab_message\';\n		dmp($adi_file_tab_message);\n		echo \'lastmod: \'.$prefs[\'lastmod\'];\n	}\n\n	// homegrown messages\n	adi_file_tab_messagepane();\n}\n\nfunction adi_file_tab_extend_detail($event,$step,$default,$rs) {\n// add extras to bottom of file edit form\n	global $adi_file_tab_txp460;\n\n	$out = \'\';\n\n	if ($rs) {\n		extract($rs);\n		$out .= adi_file_tab_update_filename_checkbox(); // shifted up into standard file replace form (or upload form if file missing)\n		$out .= adi_file_tab_update_filename_button($rs); // shift \"last chance\" update filename button to after file replace area\n		adi_file_tab_rename($rs); // shifted after standard file replace <form> (removed if file missing)\n		$out .= adi_file_tab_replace_from_existing($rs); // shifted to after standard file replace form (or upload form if file missing)\n		$out .= adi_file_tab_delete($rs); // shifted to after replace from existing form\n		if ((version_compare(txp_version,\'4.5\',\'>=\')) && (version_compare(txp_version,\'4.5.7\',\'<=\'))) $out .= adi_file_tab_details_heading(); // jQuery shifted up to top of file details area\n		if ($adi_file_tab_txp460) $out .= adi_file_tab_file_info($id); // shifted to file id area\n		$out .= adi_file_tab_downloads($downloads); // TXP 4.5 - shifted to after description, TXP 4.6 replaces download count (non-input) text\n		$out .= adi_file_tab_title_checkbox(); // shifted to after title input field\n		$out .= adi_file_tab_desc_checkbox(); // shifted to after description textarea\n 		if (version_compare(txp_version,\'4.5.7\',\'>=\')) $out .= adi_file_tab_author($id); // (author update won\'t work in pre 4.5.7 due to bug)\n	}\n\n	return $out;\n}\n\nfunction adi_file_tab_rename($rs) {\n// rename option\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n// 	if ($adi_file_tab_txp460)\n// 		$out = \'<section id=\"adi_file_tab_rename_file\" class=\"txp-details\">\';\n// 	else\n		$out = \'<div id=\"adi_file_tab_rename_file\" class=\"summary-details\">\'; // class=\"summary-details\" added for 4.5 Hive\n\n	extract($rs);\n\n	$out .=\n		tag(gTxt(\'adi_rename_file\'),\'h3\')\n		.form(\n			graf(\n				tag(\n					tag(gTxt(\'filename\'),\'label\',\' for=\"adi_file_tab_new_filename\"\')\n					.fInput(\'text\',\'adi_file_tab_new_filename\',$filename,\'\',\'\',\'\',\'\',\'\',\'adi_file_tab_new_filename\')\n					,\'span\'\n					,\' class=\"edit-value\"\'\n				)\n				.fInput(\n					\'submit\'\n					,\'do_something\'\n					,gTxt(\'adi_rename\')\n					,\'smallerbox\'\n				)\n			)\n			.eInput(\'file\')\n			.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n			.hInput(\'adi_file_tab_rename_file\',\'1\')\n			.hInput(\'id\',$id)\n			.hInput(\'adi_file_tab_old_filename\',$filename)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_file_tab_rename_form\'\n		);\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_update_filename_button($rs) {\n// button to update filename - another chance after a \"auto-update-filename-unchecked\" replace file operation\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message;\n\n	if (gps(\'adi_file_tab_update_filename\'))  // don\'t want button if filename auto update has just been performed\n		return;\n\n	$debug = \'\';\n	$out = \'\';\n	if (isset($_FILES[\'thefile\'])) {\n		$id = adi_file_tab_id();\n		$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n		extract($rs);\n		// new file\n		$new_filename = $_FILES[\'thefile\'][\'name\'];\n		$new_path = build_file_path($file_base_path,$new_filename);\n		$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n		// original file\n		$original_path = build_file_path($file_base_path,$filename);\n		$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n		// create button, but only if a differently named file has been uploaded\n		if ($filename != $new_filename) {\n			$out .= \'<div class=\"adi_file_tab_update_filename_button warning\">\';\n			$out .= graf(strong(gTxt(\'adi_copy_update_filename_uploaded\')).br.\'(\'.$new_filename.\')\');\n			$out .=\n				form(\n					graf(\n						fInput(\n							\'submit\'\n							,\'do_something\'\n							,gTxt(\'adi_update_filename\')\n							,\'smallerbox\'\n						)\n						.eInput(\'file\')\n						.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n						.hInput(\'adi_file_tab_update_filename\',\'1\')\n						.hInput(\'id\',$id)\n						.hInput(\'adi_file_tab_old_filename\',$filename)\n						.hInput(\'adi_file_tab_new_filename\',$new_filename)\n					)\n					,\'\'\n					,\'\'\n					,\'post\'\n					,\'adi_file_tab_update_button\'\n				);\n			$out .= \'</div>\';\n		}\n	}\n\n	if ($adi_file_tab_debug)\n		echo $debug;\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_update_filename_checkbox() {\n// select whether to update filename as part of replace file function or not\n\n	return\n		tag(\n			checkbox(\'adi_file_tab_update_filename\',\'1\',adi_file_tab_pref(\'adi_file_tab_replace_default\'),\'\', \'adi_file_tab_update_filename\')\n			.sp\n			.gTxt(\'adi_auto_update_filename\')\n			,\'label\'\n			,\' class=\"adi_file_tab_update_filename\"\'\n		);\n}\n\nfunction adi_file_tab_rename_file($id,$new_filename) {\n// rename file\n	global $adi_file_tab_debug,$file_base_path,$adi_file_tab_message,$txp_user;\n\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$id = assert_int($id);\n	$out = \'\';\n	$message = array();\n	$new_filename = trim($new_filename);\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	extract($rs);\n\n	$debug .= \"FILE ID = $id\".br;\n	$original_path = build_file_path($file_base_path,$filename);\n	$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n	$debug .= \"TXP USER = $txp_user\".br;\n	$debug .= \"FILE AUTHOR = $author\".br;\n\n	if (!has_privs(\'file.edit\') && !($author == $txp_user && has_privs(\'file.edit.own\')))\n		return gTxt(\'restricted_area\');\n\n	// make up error message (acts as a flag to do nuffin later)\n	if ((strpbrk($new_filename,\"\\\\/?%*:|\\\"<>\") === FALSE) && ($new_filename != \'\')) { // no dodgy characters & not blank\n		if (is_file($new_path))	{ // filename already exists so check if it\'s a different file\n			$rs = safe_row(\"id as other_id\",\"txp_file\",\"filename = \'\".doSlash($new_filename).\"\'\",$adi_file_tab_debug);\n			extract($rs);\n			if ($rs) {\n				$debug .= \"OTHER FILE ID = $other_id\".br;\n				if ($id != $other_id) { // new filename already in use in database\n					$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'file_already_exists\',array(\'{name}\'=>$new_filename)),E_ERROR);\n					$debug .= \'NEW FILENAME ALREADY EXISTS IN DB\'.br;\n				}\n			}\n			else { // found in files directory (but not in database)\n					$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'adi_file_exists_dir\',array(\'{name}\'=>$new_filename,\'{dir}\'=>$file_base_path)),E_ERROR);\n				$debug .= \"NEW FILENAME ALREADY EXISTS IN DIR (BUT NOT IN DB)\".br;\n			}\n		}\n		else if (is_dir($new_path)) { // new filename is a directory\n			$message = array(gTxt(\'adi_file_rename_fail\').\' &mdash; \'.gTxt(\'adi_name_is_dir\',array(\'{name}\'=>$new_filename)),E_ERROR);\n			$debug .= \'NEW FILENAME IS A DIRECTORY\'.br;\n		}\n	}\n	else\n		$message = array(gTxt(\'invalid_filename\'),E_ERROR);\n\n	// do the doing\n	if (!$message) { // so far so good, so do rename/replace\n		$rename_ok = rename($original_path,$new_path);\n		if ($rename_ok) {\n			// update database accordingly\n			safe_update(\'txp_file\',\"filename = \'\".doSlash($new_filename).\"\', modified = now()\",\"id = $id\",$adi_file_tab_debug);\n			update_lastmod();\n			// get new filename from database (as a double check)\n			$rs = safe_row(\"filename\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n			extract($rs);\n			$message = array(gTxt(\'adi_file_renamed\'));\n		}\n		else {\n			$message = array(gTxt(\'adi_file_rename_fail\'),E_ERROR);\n			$debug .= \'rename() FAILED\';\n		}\n	}\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	return $message;\n}\n\nfunction adi_file_tab_filter_list($filename) {\n// return TRUE if filename doesn\'t contain filter\n\n    return strpos($filename,adi_file_tab_pref(\'adi_file_tab_exclude_filter\')) === FALSE ;\n}\n\nfunction adi_file_tab_replace_from_existing($rs) {\n// replace file markup\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$existing_files = get_filenames();\n\n	if (count($existing_files) > 0) {\n\n// 		if ($adi_file_tab_txp460)\n// 			$out = \'<section id=\"adi_file_tab_replace_from_existing\" class=\"txp-details\">\';\n// 		else\n			$out = \'<div id=\"adi_file_tab_replace_from_existing\" class=\"summary-details\">\';\n\n		extract($rs);\n\n		$out .=\n			tag(gTxt(\'adi_replace_from_existing\'),\'h3\')\n			.\'<div role=\"group\">\'\n			.form(\n				graf(\n					\'<label for=\"file-existing\">\'.gTxt(\'existing_file\').\'</label>\'\n					.selectInput(\'filename\', $existing_files, \'\', 1, \'\', \'file-existing\')\n					, \' class=\"existing-file\"\'\n				)\n				.graf(\n					fInput(\n						\'submit\'\n						,\'do_something\'\n						,gTxt(\'replace_file\')\n						,\'smallerbox\'\n					)\n				)\n				.eInput(\'file\')\n				.sInput(\'file_edit\') // file_edit $step returns user to file edit panel\n				.hInput(\'adi_file_tab_replace_from_existing\',\'1\')\n				.hInput(\'id\',$id)\n				.hInput(\'adi_file_tab_old_filename\',$filename)\n			)\n			.\'</div>\';\n\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n\n		echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n	}\n}\n\nfunction adi_file_tab_replace_file($id,$new_filename) {\n// replace file\n	global $adi_file_tab_debug,$file_base_path;\n\n	$message = array();\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$id = assert_int($id);\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$rs = safe_row(\"*\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	extract($rs);\n\n	$debug .= \"FILE ID = $id\".br;\n	$original_path = build_file_path($file_base_path,$filename);\n	$debug .= \'ORIGINAL FILENAME: \'.$filename.br.\'ORIGINAL PATH: \'.$original_path.br;\n	$new_path = build_file_path($file_base_path,$new_filename);\n	$debug .= \'NEW FILENAME: \'.$new_filename.br.\'NEW PATH: \'.$new_path.br;\n\n	if (trim($new_filename) == \'\')\n		// new filename blank\n		$message = array(gTxt(\'invalid_filename\'),E_ERROR);\n	else {\n		// replace file\n		@chmod($new_path,0644); // taken from file_set_perm() in txp_file.php\n		$size = filesize($new_path);\n		// update database accordingly\n		safe_update(\'txp_file\',\"filename = \'\".doSlash($new_filename).\"\', modified = now()\".\", size = $size\",\"id = $id\",$adi_file_tab_debug);\n		update_lastmod();\n		$message = array(gTxt(\'adi_file_replaced\'));\n	}\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	return $message;\n}\n\nfunction adi_file_tab_delete($rs) {\n// delete file markup - actual delete done by standard functionality\n	global $txp_user,$adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$debug = __FUNCTION__.\'():\'.br;\n\n	$author = \'\';\n	extract($rs);\n\n	$debug .= \"TXP USER = $txp_user\".br;\n	$debug .= \"FILE AUTHOR = $author\".br;\n\n	$can_delete = has_privs(\'file.delete\') || ($author == $txp_user && has_privs(\'file.delete.own\'));\n\n	$out = \'\';\n	if ($can_delete) {\n		$debug .= \"CAN DELETE FILE\".br;\n// 		if ($adi_file_tab_txp460)\n// 			$out = \'<section id=\"adi_file_tab_delete_file\" class=\"txp-details\">\';\n// 		else\n			$out = \'<div id=\"adi_file_tab_delete_file\" class=\"summary-details\">\';\n\n		$out .=\n			tag(gTxt(\'adi_delete_file\'),\'h3\')\n			.\'<div role=\"group\">\'\n			.form(\n				graf(\n					fInput(\'submit\',\'do_something\',gTxt(\'delete\'),\'smallerbox\',\"\",\'return verify(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\')\')\n					.eInput(\'file\')\n					.sInput(\'file_multi_edit\')\n					.hInput(\'edit_method\',\'delete\')\n					.hInput(\'selected[]\',$id)\n				)\n			)\n			.\'</div>\';\n\n// 		if ($adi_file_tab_txp460)\n// 			$out .= \'</section>\';\n// 		else\n			$out .= \'</div>\';\n	}\n	else\n		$debug .= \"CAN\'T DELETE FILE\".br;\n\n	if ($adi_file_tab_debug) echo $debug;\n\n	echo $out; // don\'t use return (otherwise forms get nested & growser interferes with markup, making jQuery shift fail)\n}\n\nfunction adi_file_tab_details_heading() {\n// file details heading markup - 4.5 only\n\n	return \'<div class=\"adi_file_tab_details_heading txp-details\">\'.tag(gTxt(\'adi_file_details\'),\'h3\',\' class=\"adi_file_tab_details_heading\"\').\'</div>\';\n}\n\nfunction adi_file_tab_author($id) {\n// markup for author select\n	global $adi_file_tab_debug,$adi_file_tab_txp460;\n\n	$id = assert_int($id);\n\n	$rs = safe_row(\"author\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n	if ($rs) {\n		extract($rs);\n		if ($author) {\n			$rs = safe_column(\'name\',\'txp_users\',\'privs > 0\',$adi_file_tab_debug);\n			if ($rs) {\n				if ($adi_file_tab_txp460) {\n					echo\n						inputLabel(\n							\'adi_file_tab_author\',\n							selectInput(\'adi_file_tab_author\',$rs,$author,\'\',\'\',\'adi_file_tab_author\'),\n							\'author\', \'\', array(\'class\' => \'txp-form-field adi_file_tab_author\')\n						);\n				}\n				else\n					echo\n						graf(\n							tag(gTxt(\'author\'),\'label\',\' for=\"adi_file_tab_author\"\')\n							.selectInput(\'adi_file_tab_author\',$rs,$author,\'\',\'\',\'adi_file_tab_author\')\n							,\' class=\"adi_file_tab_author\"\'\n						);\n			}\n		}\n	}\n}\n\nfunction adi_file_tab_update_author($id=NULL,$author=\'\') {\n// update author\n	global $adi_file_tab_debug;\n\n	$id = assert_int($id);\n	update_lastmod();\n\n	return safe_update(\'txp_file\',\"author = \'$author\'\",\"id = $id\",$adi_file_tab_debug);\n}\n\nfunction adi_file_tab_downloads($downloads) {\n// input field for downloads count\n	global $adi_file_tab_txp460;\n\n	if ($adi_file_tab_txp460) {\n		return\n			inputLabel(\n				\'adi_file_tab_downloads\',\n				fInput(\'text\', \'adi_file_tab_downloads\', $downloads, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'adi_file_tab_downloads\'),\n				\'download_count\', \'\', array(\'class\' => \'txp-form-field adi_file_tab_edit_download_count\')\n			);\n	}\n	else {\n		$out =\n			tag(\n				tag(\n					gTxt(\'downloads\')\n					,\'label\'\n					,\' for=\"adi_file_tab_downloads\"\'\n				)\n				,\'span\'\n				,\' class=\"edit-label\"\'\n			)\n			.tag(\n				fInput(\'text\',\'adi_file_tab_downloads\',$downloads,\'\',\'\',\'\',\'\',\'\',\'adi_file_tab_downloads\')\n				,\'span\'\n				,\' class=\"edit-value\"\'\n			);\n\n		return graf($out,\' class=\"adi_file_tab_downloads\"\');\n	}\n}\n\nfunction adi_file_tab_update_downloads($id=NULL,$downloads=\'\') {\n// update downloads count\n	global $adi_file_tab_debug;\n\n	$id = assert_int($id);\n	$downloads = trim($downloads);\n\n	if (($id != NULL) && ctype_digit($downloads)) {\n		update_lastmod();\n		return safe_update(\'txp_file\',\"downloads=\'$downloads\'\",\'id=\'.$id,$adi_file_tab_debug);\n	}\n}\n\nfunction adi_file_tab_messagepane() {\n// generate messagepane & message\n	global $adi_file_tab_message;\n\n	if ($adi_file_tab_message) {\n		$class = \'success\';\n		if (is_array($adi_file_tab_message)) {\n			$message = $adi_file_tab_message[0];\n			if (isset($adi_file_tab_message[1])) {\n				// translate E_ERROR, E_WARNING into class\n				if ($adi_file_tab_message[1] == 1)\n					$class = \"error\";\n				if ($adi_file_tab_message[1] == 2)\n					$class = \"warning\";\n				}\n			}\n		else { // message from adi_file_tab_rename_file\n			$message = strip_tags($adi_file_tab_message);\n		}\n\n		$html = \'<span id=\"message\" class=\"messageflash \'.$class.\'\">\'.$message.\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n\n$message_script = <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n		$(\'#messagepane\').append(\'$html\');\n		$(\"#messagepane\").css({\n			left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n		});\n		$(window).resize();\n	// -->\n	</script>\nEND_SCRIPT;\n\n		echo $message_script;\n	}\n}\n\nfunction adi_file_tab_bytesize($bytes) {\n// transform bytes into killer-bytes ...\n\n	if ($bytes < 1024) {\n		$size = $bytes.\' bytes\';\n	}\n	else if ($bytes < pow(1024,2)) {\n			$size = round($bytes / 1024, 2);\n			$size .= \' KB\';\n	}\n	else if ($bytes < pow(1024,3)) {\n			$size = round($bytes / pow(1024,2), 2);\n			$size .= \' MB\';\n	}\n	else {\n			$size = round($bytes / pow(1024,3), 2);\n			$size .= \' GB\';\n	}\n	return $size;\n}\n\nfunction adi_file_tab_edit_script() {\n// jQuery magic for file edit tab\n	global $adi_file_tab_debug,$adi_file_tab_message;\n\n	$id = adi_file_tab_id();\n\n	if (!empty($id)) {\n		$rs = safe_row(\"filename\",\"txp_file\",\"id = $id\",$adi_file_tab_debug);\n		if ($rs) {\n			extract($rs);\n\n			// filename is POSTed by standard Save button, so if there\'s a failure (in an adi_file_tab function), have to make sure the filename from database is used\n			$save_button_tweak = \'// do nothing\';\n			if (count($adi_file_tab_message) > 1)\n				$save_button_tweak = \'$(\"input[name=filename]\").val(\"\'.$filename.\'\");\';\n\n			if ($adi_file_tab_debug) {\n				$debug = __FUNCTION__.\'():\'.br;\n				$debug .= \"FILENAME = $filename\".br;\n				$debug .= \"save_button_tweak = $save_button_tweak\".br;\n				echo $debug;\n			}\n\n			echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n\n   		// copy filename to title\n		var old_title = $(\"input[name=title]\").val();\n		$(\"#adi_file_tab_copy_file_title\").click(function(){\n// 			console.log(\"Old title = \" + old_title);\n			// TXP 4.5 - filename recorded in title attribute (added by plugin)\n			var filename = $(\"h3.adi_file_tab_heading\").attr(\"title\");\n			if (filename == null) { // TXP 4.6\n				var filename = $(\"div.edit-file-name div.txp-form-field-value a\").html();\n			}\n// 			console.log(\"Filename = \" + filename);\n			if ($(this).prop(\"checked\")) {\n				$(\"input[name=title]\").val(filename);\n			}\n			else {\n 				$(\"input[name=title]\").val(old_title);\n			}\n		});\n\n		// copy title to description\n		var old_desc = $(\"textarea\").val();\n		$(\"#adi_file_tab_copy_title_desc\").click(function(){\n// 			console.log(\"Old desc = \" + old_desc);\n			var title = $(\"input[name=title]\").val();\n// 			console.log(\"Title = \" + title);\n			if ($(this).prop(\"checked\")) {\n				$(\"textarea\").val(title);\n			}\n			else {\n 				$(\"textarea\").val(old_desc);\n			}\n		});\n\n		// tweak Save button filename value?\n		$save_button_tweak\n\n		// do stuff depending on whether file ok or missing (with TXP 4.5 & 4.6 variants)\n		if ($(\'.edit-file-title\').length) { // FILE OK (using file title field to indicate whether file ok)\n			if ($(\'div.txp-body\').length) { // TXP 4.5\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"div.replace-file form\");\n 				$(\"#adi_file_tab_rename_file\").insertAfter(\"div.replace-file\");\n				$(\"#adi_file_tab_replace_from_existing\").prependTo(\"div.file-detail\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_details_heading\").prependTo(\"form#file_details\");\n				$(\"p.adi_file_tab_copy_file_title\").appendTo(\"p.edit-file-title\");\n				$(\"label.adi_file_tab_copy_title_desc\").appendTo(\"p.edit-file-description\");\n				$(\"p.adi_file_tab_downloads\").appendTo(\"p.edit-file-description\");\n				$(\"p.adi_file_tab_author\").insertAfter(\"p.adi_file_tab_downloads\");\n			}\n			else { // TXP 4.6\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"form.replace-file\");\n 				$(\"#adi_file_tab_rename_file\").insertAfter(\"form.replace-file\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\"form.replace-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"#adi_file_tab_replace_from_existing\");\n				$(\"p.adi_file_tab_copy_file_title\").insertAfter(\"div.edit-file-title\");\n				$(\"label.adi_file_tab_copy_title_desc\").appendTo(\"div.edit-file-description\");\n				$(\"div.edit-file-download-count\").remove();\n				$(\"div.adi_file_tab_edit_download_count\").prependTo(\"form#file_details\");\n			}\n			$(\"div.adi_file_tab_author\").prependTo(\"form#file_details\");\n		}\n		else { // FILE MISSING\n			if ($(\'div.txp-body\').length) { // TXP 4.5\n				$(\"label.adi_file_tab_update_filename\").appendTo(\"div.upload-file form\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\"div.upload-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_details_heading\").appendTo(\"div.not-exists\");\n				$(\"p.adi_file_tab_downloads\").appendTo(\"div.adi_file_tab_details_heading\"); // put the downloads field somewhere else (TXP 4.5)\n				$(\"p.adi_file_tab_author\").insertAfter(\"div.adi_file_tab_details_heading\");\n			}\n			else { // TXP 4.6\n				$(\"label.adi_file_tab_update_filename\").appendTo(\".upload-file\");\n				$(\"#adi_file_tab_replace_from_existing\").insertAfter(\".upload-file\");\n				$(\"#adi_file_tab_delete_file\").insertAfter(\"#adi_file_tab_replace_from_existing\");\n				$(\"div.adi_file_tab_author\").insertAfter(\".edit-file-name\");\n			}\n			$(\"#adi_file_tab_rename_file\").remove();\n			$(\"p.adi_file_tab_copy_file_title\").remove();\n			$(\"label.adi_file_tab_copy_title_desc\").remove();\n			$(\"form#assign_file\").remove(); // lose standard \"replace from existing\" list (in favour of adi_file_tab\'s version)\n		}\n\n		// do regardless of ok or missing\n		if ($(\'div.txp-body\').length) { // TXP 4.5\n			$(\"div.adi_file_tab_update_filename_button\").appendTo(\"div.replace-file\");\n		}\n		else { // TXP 4.6\n			$(\"p.adi_file_tab_file_info\").appendTo(\"div.edit-file-id .txp-form-field-value\");\n			$(\"div.adi_file_tab_update_filename_button\").insertAfter(\"form.replace-file\");\n		}\n\n	});\n\n</script>\nEND_SCRIPT;\n		}\n	}\n}\n\nfunction adi_file_tab_general_script() {\n// jQuery magic for all file tabs\n	global $adi_file_tab_debug,$adi_file_tab_plugin_status;\n\n	// filtration\n	$filter = adi_file_tab_pref(\'adi_file_tab_exclude_filter\');\n\n	if ($filter == \'\') {\n		$link_text = gTxt(\'adi_unfiltered\');\n		$link_title = \'Add filter\';\n	}\n	else {\n		$link_text = gTxt(\'adi_filtered\');\n		$link_title = \'Edit filter\';\n	}\n\n	if ($adi_file_tab_plugin_status)  // proper install (options under Plugins tab)\n		$event = \'plugin_prefs.adi_file_tab\';\n	else // txpdev (options under Extensions tab)\n		$event = \'adi_file_tab_options\';\n\n	$calling_card = eLink($event,\'\',\'\',\'\',$link_text,\'\',\'\',$link_title);\n\n	if ($filter != \'\') {\n		echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n		// remove items according to filter\n		$(\"#file-existing option[value*=\'$filter\']\").remove();\n	});\n\n</script>\nEND_SCRIPT;\n	}\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n   	$().ready(function() {\n		// leave calling card\n		$(\"#file-existing\").after(\' ($calling_card)\');\n	});\n\n</script>\nEND_SCRIPT;\n\n}\n\nfunction adi_file_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_file_tab_prefs,$adi_file_tab_outlaws,$adi_file_tab_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_file_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_file_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_file_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_file_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_file_tab_pref(\'adi_file_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_file_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_file_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_file_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out);\n					}\n					// plugin privs\n					if ($pref_name == \'adi_file_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_file_tab_pref(\'adi_file_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_file_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_file_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_file_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out);\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_file_tab_pref($pref_name)),$pref_name)\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\"\'\n				)\n			);\n}\n\nfunction adi_file_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_file_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		// run through all prefs according to tab\n		foreach ($adi_file_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) {\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_file_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_file_tab_prefs[$pref_name][\'value\'];\n				$result &= adi_file_tab_pref($pref_name,$value);\n			}\n		}\n		return $result;\n	}\n}\n\nfunction adi_file_tab_options($event,$step) {\n// plugin options page\n	global $adi_file_tab_debug,$adi_file_tab_url,$textarray;\n\n	$message = \'\';\n\n	if (adi_file_tab_upgrade())\n		$step = \'upgrade\';\n\n	// steppers\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_file_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(adi_menu_gtxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_file_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_file_tab_upgrade(TRUE);\n		$result ? $message = gTxt(\'adi_upgraded\') : $message = array(gTxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_file_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_file_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// link to file list tab\n			.graf(href(gTxt(\'tab_file\'),\'?event=file\'))\n			// prefs\n			.adi_file_tab_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_file_tab_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_file_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n}\n', '005cfad6805cf1a40f81c71a78f7fd9f', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_form_links', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.4', 'Admin-side form links', '<h1><strong>adi_form_links</strong> &#8211; Admin-side form links</h1>\n\n<p>This plugin is an enhancement to the standard <span class=\"caps\">TXP</span> Page and Form tabs designed to help speed up workflow.  It lists forms that are referenced within the current page or form. From an <a href=\"http://forum.textpattern.com/viewtopic.php?id=36961\" rel=\"nofollow\">idea by Edoardo</a>.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>After installing &amp; activating the plugin, go to the Pages or Forms tab and you&#8217;ll find a list of forms referenced by the current page or form.</p>\n\n<p>The list can either be a simple list of links or a popup &#8211; choose your preference in the plugin options.</p>\n\n<p>Forms are listed in the order they&#8217;re found and their plugin tag is shown also.</p>\n\n<p>Forms shown in grey haven&#8217;t been found in the database.</p>\n\n<p>Clicking or selecting a form in the list will take you to the Form Edit tab for that form. If the form doesn&#8217;t exist then you&#8217;ll be taken to a Form create tab.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on ‘Install textpack’.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <em><span class=\"caps\">TXP</span> Admin &#8211; Language</em> tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_form_links\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=37006\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_form_links\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_form_links - Admin-side form links\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.4		- refixes for TXP 4.6\n	0.3.1	- fix: cope with default page not being called \"default\" in 4.6 (thanks jpdupont)\n	0.3		- fixes: to back up the wild claim made about TXP 4.6 in version 0.2\n			- code tidy up\n	0.2		- ignore invalid form names\n			- blacklist\n			- tested on TXP 4.6\n	0.1		- initial release (from an idea by Edoardo @wornout & thoroughly tested by Uli)\n\n*/\n\nglobal $event,$step,$adi_form_links_debug;\n\nif (@txpinterface == \'admin\') {\n\n	$adi_form_links_debug = 0;\n\n	adi_form_links_init($event,$step);\n\n}\n\nfunction adi_form_links_init($event,$step) {\n	global $prefs,$textarray,$adi_form_links_debug,$adi_form_links_url,$adi_form_links_prefs,$adi_form_links_blacklist,$adi_form_links_newname,$adi_form_links_txp460;\n\n	$adi_form_links_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	$adi_form_links_blacklist[] = \'smd_wrap,transform\';\n// 	$adi_form_links_blacklist[] = \'another_plugin,another_attribute\';\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	// plugin lifecycle\n	register_callback(\'adi_form_links_lifecycle\',\'plugin_lifecycle.adi_form_links\');\n\n	// plugin options\n	$adi_form_links_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_form_links\',$adi_form_links_debug);\n	if ($adi_form_links_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_form_links\',\'1,2,6\');\n		register_callback(\'adi_form_links_options\',\'plugin_prefs.adi_form_links\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_form_links_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_form_links_options\',\'adi_form_links options\');\n		register_callback(\'adi_form_links_options\',\'adi_form_links_options\');\n	}\n\n	// Textpack\n	$adi_form_links_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_form_links\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_form_links_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_form_links_gtxt = array(\n		\'adi_edit_form\' => \'Edit form\',\n		\'adi_forms_referenced\' => \'Forms referenced\',\n		\'adi_forms_used\' => \'Forms used\',\n		\'adi_link_list\' => \'Link list\',\n		\'adi_list_format\' => \'List format\',\n		\'adi_none_found\' => \'None found\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_form_links_gtxt;\n\n	// preferences & defaults\n	$adi_form_links_prefs = array(\n		\'adi_form_links_type\'		=> array(\'value\' => \'list\', \'input\' => \'radio\'), // \'list\' or \'popup\'\n	);\n	foreach ($adi_form_links_prefs as $adi_form_links_pref => $this_pref) {\n		$get_value = get_pref($adi_form_links_pref,\'?\'); // returns \'?\' if not set (but beware of cacheing)\n		if ($get_value != \'?\')\n			$adi_form_links_prefs[$adi_form_links_pref][\'value\'] = $get_value;\n	}\n\n	// some action\n	if (($event == \"page\") || ($event == \'form\')) { // \'ere we go\n\n		// inject markup\n		if ($adi_form_links_txp460)\n			register_callback(\'adi_form_links_markup_46\',\'admin_side\',\'footer\'); // markup in the footer, shifted by jQuery\n		else {\n			// callbacks - inject markup into Pages/Forms tab\n			register_callback(\'adi_form_links_list\',\'page\',\'\',1);\n			register_callback(\'adi_form_links_list\',\'form\',\'\',1);\n		}\n\n		// script\n		register_callback(\'adi_form_links_script\',\'admin_side\',\'head_end\');\n\n		// new form link clicked in TXP 4.6?\n		$adi_form_links_newname = gps(\'adi_form_links_newname\');\n		if ($adi_form_links_newname)\n			register_callback(\'adi_form_links_script2\',\'admin_side\',\'head_end\');\n		if ($adi_form_links_debug) {\n			echo \'Event=\'.$event.\',Step=\'.$step.br.br;\n			echo \'$_GET=\';\n			dmp($_GET);\n			echo \'$_POST=\';\n			dmp($_POST);\n		}\n\n		// style\n		register_callback(\'adi_form_links_style\',\'admin_side\',\'head_end\');\n	}\n}\n\nfunction adi_form_links_style() {\n// practical & stylish\n	echo\n		\'<style type=\"text/css\">\n			#adi_form_links { margin-top:1em }\n			#adi_form_links label { width:auto; float:left; margin:0 }\n			#adi_form_links select { float:left; margin:0 }\n			#adi_form_links input.smallerbox { margin:0 0 0 0.5em }\n			#adi_form_links .adi_form_links_disabled { color:#777 } /* explicitly grey out disabled options (for IE6/7) - still no good for iOS Safari NOW REDUNDANT? */\n			#adi_form_links p { margin:0 }\n			#adi_form_links ul { list-style:none; margin:0.5em 0 0; padding:0 0 1em }\n			#adi_form_links li { margin:0; padding:0 }\n			#adi_form_links li.adi_form_links_new a,\n			#adi_form_links option.adi_form_links_new { color:#777 }\n			/* TXP 4.6 */\n			@media (max-width: 47em) {\n				#adi_form_links label { float:none; display:block }\n				#adi_form_links select { float:none; margin:0.5em 0 1em }\n				#adi_form_links input.smallerbox { display:block; margin:0 }\n			}\n		</style>\';\n}\n\nfunction adi_form_links_options($event,$step) {\n// plugin options page\n	global $adi_form_links_debug,$adi_form_links_url,$textarray,$adi_form_links_prefs;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_form_links_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = gTxt(\'adi_textpack_fail\');\n		}\n	}\n	if ($step == \'update_prefs\') {\n		$result = adi_form_links_update_prefs();\n		$result ? $message = gTxt(\'preferences_saved\') : $message = gTxt(\'adi_pref_update_fail\');\n	}\n\n	// generate page\n	if (!empty($message))\n		$message = \'<strong>\'.$message.\'</strong>\';\n\n	pagetop(\'adi_form_links \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_form_links \'.gTxt(\'plugin_prefs\'),\'h2\')\n 		// preferences\n 	  	.form(\n			tag(gTxt(\'edit_preferences\'),\'h3\')\n			.graf(\n				tag(gTxt(\'adi_list_format\'),\'label\',\' for=\"adi_form_links_type\"\')\n				.sp.sp\n				.tag(\n					radio(\'adi_form_links_type\',\'list\',($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\'))\n					.sp\n					.gTxt(\'adi_link_list\')\n					,\'label\'\n				)\n				.sp.sp\n				.tag(\n					radio(\'adi_form_links_type\',\'popup\',($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'popup\'))\n					.sp\n					.gTxt(\'tag_popup\')\n					,\'label\'\n				)\n			)\n			.graf(fInput(\"submit\",\"do_something\",gTxt(\'adi_update_prefs\'),\"smallerbox\"))\n			.eInput($event)\n			.sInput(\"update_prefs\")\n		)\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'),\' style=\"margin-top:3em\"\')\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_form_links_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_form_links_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n	if ($adi_form_links_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_form_links_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_form_links_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n\nfunction adi_form_links_lifecycle($event,$step) {\n// from cradle to grave\n// $event:	\"plugin_lifecycle.adi_form_links\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n	global $adi_form_links_debug,$adi_form_links_prefs;\n\n	$result = \'?\';\n	if ($step == \'deleted\') {\n		// delete preferences\n		$res = TRUE;\n		foreach ($adi_form_links_prefs as $this_pref => $value)\n			$res = $res && safe_delete(\'txp_prefs\',\"name = \'$this_pref\'\",$adi_form_links_debug);\n		$result = $res;\n	}\n	if ($adi_form_links_debug)\n		echo \"Event=$event Step=$step Result=$result\";\n}\n\nfunction adi_form_links_update_prefs() {\n// trawl $_POST & update adi_form_links preferences\n	global $adi_form_links_prefs;\n\n	$per_user_prefs = TRUE;\n\n	$res = TRUE;\n	foreach ($adi_form_links_prefs as $adi_form_links_pref => $this_pref) {\n		if (array_key_exists($adi_form_links_pref,$_POST))\n			$new_value = $_POST[$adi_form_links_pref];\n		else\n			$new_value = \'\';\n		$res = $res && set_pref($adi_form_links_pref,$new_value,\'adi_form_links\',2,$adi_form_links_prefs[$adi_form_links_pref][\'input\'],0,$per_user_prefs);\n		$adi_form_links_prefs[$adi_form_links_pref][\'value\'] = $new_value;\n	}\n	return $res;\n}\n\nfunction adi_form_links_forms($name) {\n// find the forms & generate the markup\n	global $event,$step,$adi_form_links_debug,$adi_form_links_prefs,$adi_form_links_blacklist,$adi_form_links_txp460;\n\n	$debug = \'\';\n	if ($adi_form_links_debug) $debug.= \"FORM NAME:\".br.\"supplied=$name\";\n\n	if ($event == \'page\') {\n		/*							$step		$name		$newname\n			Initial page tab visit:	-			-			-			(the \"default\" page is the one linked to section \'default\' - which may not be called \'default\' in 4.6)\n			Edit page:				-			abc			-\n			Save page:				page_save	abc			-\n			Create page:			page_new	-			-\n			Save new page:			page_save	-			abc\n			Delete page:			page_delete	abc			-\n			Copy/Duplicate page*:	page_save	abc			xyz			(4.6: $newname contains old name if user doesn\'t enter new one, even though name field contains \"abc_copy\")\n				*with/without modifications on the way\n			Rename page:												(there is no rename page functionality)\n		*/\n		if (empty($step) && empty($name)) // initial page tab visit\n			$name = safe_field(\'page\',\'txp_section\',\"name=\'default\'\"); // get $name from page assigned to section \"default\"\n		if ($step == \'page_new\')\n			return \'\'; // otherwise it\'ll list default\'s form\n		$newname = gps(\'newname\');\n		if ($step == \'page_save\') { // save, add, copy or duplicate page\n			if (empty($name)) // add new page\n				$name = gps(\'newname\');\n			if ($newname) // 4.6: duplicate page, need to retrieve new oage (user may have modified it before hitting duplicate)\n				$name = gps(\'newname\');\n		}\n		if ($step == \'page_delete\') // page delete, so default to default\n			$name = safe_field(\'page\',\'txp_section\',\"name=\'default\'\"); // get $name from page assigned to section \"default\"\n		// retrieve page contents from DB\n		$row = safe_row(\'user_html\',\'txp_page\',\" name=\'\".$name.\"\'\");\n		$data = $row[\'user_html\'];\n	}\n	else if ($event == \'form\') {\n		/*							$step			$name		$oldname (4.5)	$newname (4.6)\n			Initial form tab visit:	-				-			-				-				(== default)\n			Edit form:				form_edit		abc			-				-\n			Save form:				form_save		abc			abc				abc\n			Create form:			form_create		-			-				-\n			Save new form:			form_save		abc*		-				abc				(* 4.6: $name empty)\n			Delete form:			form_multi_edit	-			-				-\n			Rename form*:			form_save		xyc*		abc				xyz				(* 4.6: $name contains old name)\n				*with/without modifications on the way\n		*/\n		if (empty($step) && empty($name)) // initial form tab visit\n			$name = \'default\';\n		if ($step == \'form_multi_edit\') // form delete, so default to default\n			$name = \'default\';\n		$newname = gps(\'newname\');\n		if ($newname) // TXP 4.6 - save new or rename\n			$name = $newname;\n		// retrieve form contents from DB\n		$row = safe_row(\'Form\',\'txp_form\',\" name=\'\".$name.\"\'\");\n		if ($row)\n			$data = $row[\'Form\'];\n		else\n			$data = \'\';\n	}\n	if ($adi_form_links_debug) $debug.= \", deduced=$name\".br;\n\n	// get all tags from current page/form\n	$all_tags = adi_form_links_parse($data);\n\n	// sort out wheat from chaff\n	$forms = array();\n	$debug .= \'BLACKLIST:\'.br;\n	$debug .= implode(br,$adi_form_links_blacklist);\n	$debug .= br.\'TAGS, ATTS & VALUES (** is a pass):\'.br;\n	foreach ($all_tags as $taginfo) {\n		$atts = adi_form_links_splat($taginfo[\'atts\']); // get array of atts & values\n		if ($atts)\n			foreach ($atts as $att => $value) {\n				$this_tag = array();\n				if (preg_match(\"/.*form$/i\",$att)) { // * \"[something]form\" attribute is a good start\n					$debug .= \'*\';\n					if (!preg_match(\"/[<>]/i\",$value)) { // ** no embedded tags is the business\n						$pattern = \'/[<>&\"\\\']/\';\n						// officially: anything goes as long as it\'s a string, and with out <>&\"\\\'\n						// unofficially: Holy Cr@p! (fixed in 4.6)\n						if (trim($value) && is_string($value) && !preg_match($pattern,$value)) { // check valid name: basic format, but not blanks or single zero\n							$debug .= \'*\';\n							$this_tag[\'tag\'] = $taginfo[\'tag\'];\n							$this_tag[\'att\'] = $att;\n							$this_tag[\'form\'] = htmlentities($value);\n							if (array_search($this_tag[\'tag\'].\',\'.$this_tag[\'att\'],$adi_form_links_blacklist) === FALSE)\n								$forms[] = $this_tag;\n							else\n								$debug .= \' (blacklisted) \';\n						}\n						else\n							$debug .= \' (invalid form name) \';\n					}\n				}\n				$debug .= \' [tag:\'.$taginfo[\'tag\'].\', attr:\'.$att.\', form name:\'.htmlentities($value).\']\'.br;\n			}\n		else // no attributes\n			$debug .= $taginfo[\'tag\'].br;\n	}\n\n	if (!$adi_form_links_debug) $debug = \'\';\n\n	// generate select list & link list markup\n	$select_list = \'\';\n	$link_list = \'<ul>\';\n	foreach ($forms as $this_tag) {\n		$link_text = $this_tag[\'form\'];\n		if (safe_row(\'name\',\'txp_form\',\" name=\'\".$this_tag[\'form\'].\"\'\",$adi_form_links_debug)) { // existing form\n			$option_class = \'\';\n			$link_list .= \'<li>\'.elink(\'form\',\'form_edit\',\'name\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n		}\n		else { // form doesn\'t exist\n			$option_class = \' class=\"adi_form_links_new\"\';\n			if ($adi_form_links_txp460) // \"name\" var causes TXP 4.6 to go into form edit mode rather than form create, so jQuery will come into play\n				$link_list .= \'<li class=\"adi_form_links_new\">\'.elink(\'form\',\'form_create\',\'adi_form_links_newname\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n			else\n				$link_list .= \'<li class=\"adi_form_links_new\">\'.elink(\'form\',\'form_create\',\'name\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n		}\n		$select_list .= \'<option\'.$option_class.\' value=\"\'.$this_tag[\'form\'].\'\">\'.$this_tag[\'form\'].\' (\'.$this_tag[\'tag\'].\')</option>\';\n	}\n	$link_list .= \'</ul>\';\n\n	if ($forms)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\')\n			return $debug.$link_list;\n		else // popup\n			return $debug.tag(\'<option value=\"\">&nbsp;</option>\'.$select_list,\'select\',\' id=\"adi_form_links_forms\" name=\"name\"\');\n	else\n		return $debug;\n}\n\nfunction adi_form_links_parse($thing) {\n// get all tags & their attributes\n// ripped off (& modified) from publish.php\n\n	$f = \'@(</?txp:\\w+(?:\\s+\\w+\\s*=\\s*(?:\"(?:[^\"]|\"\")*\"|\\\'(?:[^\\\']|\\\'\\\')*\\\'|[^\\s\\\'\"/>]+))*\\s*/?\'.chr(62).\')@s\';\n	$t = \'@:(\\w+)(.*?)/?.$@s\';\n\n	$parsed = preg_split($f, $thing, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n	$level  = 0;\n	$out    = array();\n	$inside = \'\';\n	$istag  = FALSE;\n\n	foreach ($parsed as $chunk)\n	{\n		if ($istag)\n		{\n			if ($level === 0) {\n				preg_match($t, $chunk, $tag);\n\n				if (substr($chunk, -2, 1) === \'/\')\n				{ # self closing\n					TRUE; // do nothing but keep for reference\n				}\n				else\n				{ # opening\n					$level++;\n				}\n				// $tag[1] = the tag, $tag[2] = the attributes\n				$out[] = array(\'tag\' => trim($tag[1]), \'atts\' => trim($tag[2]));\n				$out = array_merge($out,adi_form_links_parse($tag[2])); // parse tags within tags\n			}\n			else\n			{\n				if (substr($chunk, 1, 1) === \'/\')\n				{ # closing\n					if (--$level === 0)\n					{\n						$out = array_merge($out,adi_form_links_parse($inside)); // parse contained tags\n						$inside = \'\';\n					}\n					else\n					{\n						$inside .= $chunk;\n					}\n				}\n				elseif (substr($chunk, -2, 1) !== \'/\')\n				{ # opening inside open\n					++$level;\n					$inside .= $chunk;\n				}\n				else\n				{\n					$inside .= $chunk;\n				}\n			}\n		}\n		else\n		{\n			if ($level)\n			{\n				$inside .= $chunk;\n			}\n		}\n\n		$istag = !$istag;\n	}\n\n	return $out;\n}\n\nfunction adi_form_links_splat($text) {\n// get tag attributes\n// ripped off (& modified) from txplib_misc.php\n	$atts  = array();\n\n	if (preg_match_all(\'@(\\w+)\\s*=\\s*(?:\"((?:[^\"]|\"\")*)\"|\\\'((?:[^\\\']|\\\'\\\')*)\\\'|([^\\s\\\'\"/>]+))@s\', $text, $match, PREG_SET_ORDER))\n	{\n		foreach ($match as $m)\n		{\n			switch (count($m))\n			{\n				case 3:\n					$val = str_replace(\'\"\"\', \'\"\', $m[2]);\n					break;\n				case 4:\n					$val = str_replace(\"\'\'\", \"\'\", $m[3]);\n					break;\n				case 5:\n					$val = $m[4];\n					break;\n			}\n\n			$atts[strtolower($m[1])] = $val;\n		}\n\n	}\n\n	return $atts;\n}\n\nfunction adi_form_links_list() {\n// arcane magic that gets adi_form_links\'s list into standard TXP tabs\n	ob_start(\'adi_form_links_inject\');\n}\n\nfunction adi_form_links_inject($buffer) {\n// another bit of the magic that gets adi_form_links\'s section popup into standard TXP tabs\n	global $DB,$event;\n\n	if(!isset($DB)) $DB = new db;\n	if ($event == \'page\')\n		$pattern = \'#id=\"page_form\".*</form>#sU\';\n	else if ($event == \'form\')\n		$pattern = \'#id=\"form_form\".*</form>#sU\';\n	else\n		return \'\';\n	$insert = \'adi_form_links_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n	return $buffer;\n}\n\nfunction adi_form_links_markup($matches) {\n// generate markup\n	global $adi_form_links_prefs;\n\n	$name = gps(\'name\');\n\n	$forms_found = adi_form_links_forms($name);\n\n	if ($forms_found)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\') // link list\n			$markup = graf(strong(gTxt(\'adi_forms_referenced\'))).$forms_found;\n		else // popup\n			$markup =\n				form(\n					tag(\n						tag(strong(gTxt(\'adi_forms_referenced\')),\'label\',\' for=\"adi_form_links_forms\"\')\n						.$forms_found\n						.fInput(\"submit\",\"edit_form\",gTxt(\'adi_edit_form\'),\"smallerbox\")\n						.eInput(\"form\")\n						.sInput(\"form_edit\")\n					,\'p\')\n				);\n	else // none found\n		$markup = strong(gTxt(\'adi_forms_referenced\')).br.gTxt(\'adi_none_found\');\n\n	return $matches[0] // the admin page chunk, plus:\n		.tag(\n			$markup\n			,\'div\'\n			,\' id=\"adi_form_links\"\'\n		);\n}\n\nfunction adi_form_links_markup_46($matches) {\n// generate markup\n	global $adi_form_links_prefs;\n\n	$name = gps(\'name\');\n\n	$forms_found = adi_form_links_forms($name);\n\n	if ($forms_found)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\') // link list\n			$markup = graf(strong(gTxt(\'adi_forms_referenced\'))).$forms_found;\n		else // popup\n			$markup =\n				form(\n					tag(\n						tag(strong(gTxt(\'adi_forms_referenced\')),\'label\',\' for=\"adi_form_links_forms\"\')\n						.br\n						.$forms_found\n						.fInput(\"submit\",\"edit_form\",gTxt(\'adi_edit_form\'),\"smallerbox\")\n						.eInput(\"form\")\n						.sInput(\"form_edit\")\n					,\'p\')\n				);\n	else // none found\n		$markup = strong(gTxt(\'adi_forms_referenced\')).br.gTxt(\'adi_none_found\');\n\n	echo tag(\n		$markup\n		,\'div\'\n		,\' id=\"adi_form_links\"\'\n	);\n}\n\nfunction adi_form_links_script() {\n// jQuery-ism\n	global $adi_form_links_debug,$adi_form_links_newname;\n\n	// markup shifty\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_form_links\n		$(\"div#adi_form_links\").appendTo($(\"#main_content\")); // shift form links markup from footer\n	});\n</script>\nEND_SCRIPT;\n\n}\n\n\nfunction adi_form_links_script2() {\n// jQuery-ism\n	global $adi_form_links_debug,$adi_form_links_newname;\n\n	// new form name field inject for TXP 4.6\n	if ($adi_form_links_debug) echo \"adi_form_links_newname=$adi_form_links_newname\".n;\n\n	if ($adi_form_links_newname)\n		echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	// adi_form_links\n   	$().ready(function() {\n   		console.log(\"$adi_form_links_newname\");\n		$(\"input#new_form\").val(\"$adi_form_links_newname\");\n	});\n</script>\nEND_SCRIPT;\n\n}\n', '\n/*\n	adi_form_links - Admin-side form links\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.4		- refixes for TXP 4.6\n	0.3.1	- fix: cope with default page not being called \"default\" in 4.6 (thanks jpdupont)\n	0.3		- fixes: to back up the wild claim made about TXP 4.6 in version 0.2\n			- code tidy up\n	0.2		- ignore invalid form names\n			- blacklist\n			- tested on TXP 4.6\n	0.1		- initial release (from an idea by Edoardo @wornout & thoroughly tested by Uli)\n\n*/\n\nglobal $event,$step,$adi_form_links_debug;\n\nif (@txpinterface == \'admin\') {\n\n	$adi_form_links_debug = 0;\n\n	adi_form_links_init($event,$step);\n\n}\n\nfunction adi_form_links_init($event,$step) {\n	global $prefs,$textarray,$adi_form_links_debug,$adi_form_links_url,$adi_form_links_prefs,$adi_form_links_blacklist,$adi_form_links_newname,$adi_form_links_txp460;\n\n	$adi_form_links_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	$adi_form_links_blacklist[] = \'smd_wrap,transform\';\n// 	$adi_form_links_blacklist[] = \'another_plugin,another_attribute\';\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	// plugin lifecycle\n	register_callback(\'adi_form_links_lifecycle\',\'plugin_lifecycle.adi_form_links\');\n\n	// plugin options\n	$adi_form_links_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_form_links\',$adi_form_links_debug);\n	if ($adi_form_links_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_form_links\',\'1,2,6\');\n		register_callback(\'adi_form_links_options\',\'plugin_prefs.adi_form_links\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_form_links_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_form_links_options\',\'adi_form_links options\');\n		register_callback(\'adi_form_links_options\',\'adi_form_links_options\');\n	}\n\n	// Textpack\n	$adi_form_links_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_form_links\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_form_links_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_form_links_gtxt = array(\n		\'adi_edit_form\' => \'Edit form\',\n		\'adi_forms_referenced\' => \'Forms referenced\',\n		\'adi_forms_used\' => \'Forms used\',\n		\'adi_link_list\' => \'Link list\',\n		\'adi_list_format\' => \'List format\',\n		\'adi_none_found\' => \'None found\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_form_links_gtxt;\n\n	// preferences & defaults\n	$adi_form_links_prefs = array(\n		\'adi_form_links_type\'		=> array(\'value\' => \'list\', \'input\' => \'radio\'), // \'list\' or \'popup\'\n	);\n	foreach ($adi_form_links_prefs as $adi_form_links_pref => $this_pref) {\n		$get_value = get_pref($adi_form_links_pref,\'?\'); // returns \'?\' if not set (but beware of cacheing)\n		if ($get_value != \'?\')\n			$adi_form_links_prefs[$adi_form_links_pref][\'value\'] = $get_value;\n	}\n\n	// some action\n	if (($event == \"page\") || ($event == \'form\')) { // \'ere we go\n\n		// inject markup\n		if ($adi_form_links_txp460)\n			register_callback(\'adi_form_links_markup_46\',\'admin_side\',\'footer\'); // markup in the footer, shifted by jQuery\n		else {\n			// callbacks - inject markup into Pages/Forms tab\n			register_callback(\'adi_form_links_list\',\'page\',\'\',1);\n			register_callback(\'adi_form_links_list\',\'form\',\'\',1);\n		}\n\n		// script\n		register_callback(\'adi_form_links_script\',\'admin_side\',\'head_end\');\n\n		// new form link clicked in TXP 4.6?\n		$adi_form_links_newname = gps(\'adi_form_links_newname\');\n		if ($adi_form_links_newname)\n			register_callback(\'adi_form_links_script2\',\'admin_side\',\'head_end\');\n		if ($adi_form_links_debug) {\n			echo \'Event=\'.$event.\',Step=\'.$step.br.br;\n			echo \'$_GET=\';\n			dmp($_GET);\n			echo \'$_POST=\';\n			dmp($_POST);\n		}\n\n		// style\n		register_callback(\'adi_form_links_style\',\'admin_side\',\'head_end\');\n	}\n}\n\nfunction adi_form_links_style() {\n// practical & stylish\n	echo\n		\'<style type=\"text/css\">\n			#adi_form_links { margin-top:1em }\n			#adi_form_links label { width:auto; float:left; margin:0 }\n			#adi_form_links select { float:left; margin:0 }\n			#adi_form_links input.smallerbox { margin:0 0 0 0.5em }\n			#adi_form_links .adi_form_links_disabled { color:#777 } /* explicitly grey out disabled options (for IE6/7) - still no good for iOS Safari NOW REDUNDANT? */\n			#adi_form_links p { margin:0 }\n			#adi_form_links ul { list-style:none; margin:0.5em 0 0; padding:0 0 1em }\n			#adi_form_links li { margin:0; padding:0 }\n			#adi_form_links li.adi_form_links_new a,\n			#adi_form_links option.adi_form_links_new { color:#777 }\n			/* TXP 4.6 */\n			@media (max-width: 47em) {\n				#adi_form_links label { float:none; display:block }\n				#adi_form_links select { float:none; margin:0.5em 0 1em }\n				#adi_form_links input.smallerbox { display:block; margin:0 }\n			}\n		</style>\';\n}\n\nfunction adi_form_links_options($event,$step) {\n// plugin options page\n	global $adi_form_links_debug,$adi_form_links_url,$textarray,$adi_form_links_prefs;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_form_links_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = gTxt(\'adi_textpack_fail\');\n		}\n	}\n	if ($step == \'update_prefs\') {\n		$result = adi_form_links_update_prefs();\n		$result ? $message = gTxt(\'preferences_saved\') : $message = gTxt(\'adi_pref_update_fail\');\n	}\n\n	// generate page\n	if (!empty($message))\n		$message = \'<strong>\'.$message.\'</strong>\';\n\n	pagetop(\'adi_form_links \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_form_links \'.gTxt(\'plugin_prefs\'),\'h2\')\n 		// preferences\n 	  	.form(\n			tag(gTxt(\'edit_preferences\'),\'h3\')\n			.graf(\n				tag(gTxt(\'adi_list_format\'),\'label\',\' for=\"adi_form_links_type\"\')\n				.sp.sp\n				.tag(\n					radio(\'adi_form_links_type\',\'list\',($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\'))\n					.sp\n					.gTxt(\'adi_link_list\')\n					,\'label\'\n				)\n				.sp.sp\n				.tag(\n					radio(\'adi_form_links_type\',\'popup\',($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'popup\'))\n					.sp\n					.gTxt(\'tag_popup\')\n					,\'label\'\n				)\n			)\n			.graf(fInput(\"submit\",\"do_something\",gTxt(\'adi_update_prefs\'),\"smallerbox\"))\n			.eInput($event)\n			.sInput(\"update_prefs\")\n		)\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'),\' style=\"margin-top:3em\"\')\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_form_links_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_form_links_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n	if ($adi_form_links_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_form_links_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_form_links_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n\nfunction adi_form_links_lifecycle($event,$step) {\n// from cradle to grave\n// $event:	\"plugin_lifecycle.adi_form_links\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n	global $adi_form_links_debug,$adi_form_links_prefs;\n\n	$result = \'?\';\n	if ($step == \'deleted\') {\n		// delete preferences\n		$res = TRUE;\n		foreach ($adi_form_links_prefs as $this_pref => $value)\n			$res = $res && safe_delete(\'txp_prefs\',\"name = \'$this_pref\'\",$adi_form_links_debug);\n		$result = $res;\n	}\n	if ($adi_form_links_debug)\n		echo \"Event=$event Step=$step Result=$result\";\n}\n\nfunction adi_form_links_update_prefs() {\n// trawl $_POST & update adi_form_links preferences\n	global $adi_form_links_prefs;\n\n	$per_user_prefs = TRUE;\n\n	$res = TRUE;\n	foreach ($adi_form_links_prefs as $adi_form_links_pref => $this_pref) {\n		if (array_key_exists($adi_form_links_pref,$_POST))\n			$new_value = $_POST[$adi_form_links_pref];\n		else\n			$new_value = \'\';\n		$res = $res && set_pref($adi_form_links_pref,$new_value,\'adi_form_links\',2,$adi_form_links_prefs[$adi_form_links_pref][\'input\'],0,$per_user_prefs);\n		$adi_form_links_prefs[$adi_form_links_pref][\'value\'] = $new_value;\n	}\n	return $res;\n}\n\nfunction adi_form_links_forms($name) {\n// find the forms & generate the markup\n	global $event,$step,$adi_form_links_debug,$adi_form_links_prefs,$adi_form_links_blacklist,$adi_form_links_txp460;\n\n	$debug = \'\';\n	if ($adi_form_links_debug) $debug.= \"FORM NAME:\".br.\"supplied=$name\";\n\n	if ($event == \'page\') {\n		/*							$step		$name		$newname\n			Initial page tab visit:	-			-			-			(the \"default\" page is the one linked to section \'default\' - which may not be called \'default\' in 4.6)\n			Edit page:				-			abc			-\n			Save page:				page_save	abc			-\n			Create page:			page_new	-			-\n			Save new page:			page_save	-			abc\n			Delete page:			page_delete	abc			-\n			Copy/Duplicate page*:	page_save	abc			xyz			(4.6: $newname contains old name if user doesn\'t enter new one, even though name field contains \"abc_copy\")\n				*with/without modifications on the way\n			Rename page:												(there is no rename page functionality)\n		*/\n		if (empty($step) && empty($name)) // initial page tab visit\n			$name = safe_field(\'page\',\'txp_section\',\"name=\'default\'\"); // get $name from page assigned to section \"default\"\n		if ($step == \'page_new\')\n			return \'\'; // otherwise it\'ll list default\'s form\n		$newname = gps(\'newname\');\n		if ($step == \'page_save\') { // save, add, copy or duplicate page\n			if (empty($name)) // add new page\n				$name = gps(\'newname\');\n			if ($newname) // 4.6: duplicate page, need to retrieve new oage (user may have modified it before hitting duplicate)\n				$name = gps(\'newname\');\n		}\n		if ($step == \'page_delete\') // page delete, so default to default\n			$name = safe_field(\'page\',\'txp_section\',\"name=\'default\'\"); // get $name from page assigned to section \"default\"\n		// retrieve page contents from DB\n		$row = safe_row(\'user_html\',\'txp_page\',\" name=\'\".$name.\"\'\");\n		$data = $row[\'user_html\'];\n	}\n	else if ($event == \'form\') {\n		/*							$step			$name		$oldname (4.5)	$newname (4.6)\n			Initial form tab visit:	-				-			-				-				(== default)\n			Edit form:				form_edit		abc			-				-\n			Save form:				form_save		abc			abc				abc\n			Create form:			form_create		-			-				-\n			Save new form:			form_save		abc*		-				abc				(* 4.6: $name empty)\n			Delete form:			form_multi_edit	-			-				-\n			Rename form*:			form_save		xyc*		abc				xyz				(* 4.6: $name contains old name)\n				*with/without modifications on the way\n		*/\n		if (empty($step) && empty($name)) // initial form tab visit\n			$name = \'default\';\n		if ($step == \'form_multi_edit\') // form delete, so default to default\n			$name = \'default\';\n		$newname = gps(\'newname\');\n		if ($newname) // TXP 4.6 - save new or rename\n			$name = $newname;\n		// retrieve form contents from DB\n		$row = safe_row(\'Form\',\'txp_form\',\" name=\'\".$name.\"\'\");\n		if ($row)\n			$data = $row[\'Form\'];\n		else\n			$data = \'\';\n	}\n	if ($adi_form_links_debug) $debug.= \", deduced=$name\".br;\n\n	// get all tags from current page/form\n	$all_tags = adi_form_links_parse($data);\n\n	// sort out wheat from chaff\n	$forms = array();\n	$debug .= \'BLACKLIST:\'.br;\n	$debug .= implode(br,$adi_form_links_blacklist);\n	$debug .= br.\'TAGS, ATTS & VALUES (** is a pass):\'.br;\n	foreach ($all_tags as $taginfo) {\n		$atts = adi_form_links_splat($taginfo[\'atts\']); // get array of atts & values\n		if ($atts)\n			foreach ($atts as $att => $value) {\n				$this_tag = array();\n				if (preg_match(\"/.*form$/i\",$att)) { // * \"[something]form\" attribute is a good start\n					$debug .= \'*\';\n					if (!preg_match(\"/[<>]/i\",$value)) { // ** no embedded tags is the business\n						$pattern = \'/[<>&\"\\\']/\';\n						// officially: anything goes as long as it\'s a string, and with out <>&\"\\\'\n						// unofficially: Holy Cr@p! (fixed in 4.6)\n						if (trim($value) && is_string($value) && !preg_match($pattern,$value)) { // check valid name: basic format, but not blanks or single zero\n							$debug .= \'*\';\n							$this_tag[\'tag\'] = $taginfo[\'tag\'];\n							$this_tag[\'att\'] = $att;\n							$this_tag[\'form\'] = htmlentities($value);\n							if (array_search($this_tag[\'tag\'].\',\'.$this_tag[\'att\'],$adi_form_links_blacklist) === FALSE)\n								$forms[] = $this_tag;\n							else\n								$debug .= \' (blacklisted) \';\n						}\n						else\n							$debug .= \' (invalid form name) \';\n					}\n				}\n				$debug .= \' [tag:\'.$taginfo[\'tag\'].\', attr:\'.$att.\', form name:\'.htmlentities($value).\']\'.br;\n			}\n		else // no attributes\n			$debug .= $taginfo[\'tag\'].br;\n	}\n\n	if (!$adi_form_links_debug) $debug = \'\';\n\n	// generate select list & link list markup\n	$select_list = \'\';\n	$link_list = \'<ul>\';\n	foreach ($forms as $this_tag) {\n		$link_text = $this_tag[\'form\'];\n		if (safe_row(\'name\',\'txp_form\',\" name=\'\".$this_tag[\'form\'].\"\'\",$adi_form_links_debug)) { // existing form\n			$option_class = \'\';\n			$link_list .= \'<li>\'.elink(\'form\',\'form_edit\',\'name\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n		}\n		else { // form doesn\'t exist\n			$option_class = \' class=\"adi_form_links_new\"\';\n			if ($adi_form_links_txp460) // \"name\" var causes TXP 4.6 to go into form edit mode rather than form create, so jQuery will come into play\n				$link_list .= \'<li class=\"adi_form_links_new\">\'.elink(\'form\',\'form_create\',\'adi_form_links_newname\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n			else\n				$link_list .= \'<li class=\"adi_form_links_new\">\'.elink(\'form\',\'form_create\',\'name\',$this_tag[\'form\'],$link_text).\' (\'.$this_tag[\'tag\'].\')\'.\'</li>\';\n		}\n		$select_list .= \'<option\'.$option_class.\' value=\"\'.$this_tag[\'form\'].\'\">\'.$this_tag[\'form\'].\' (\'.$this_tag[\'tag\'].\')</option>\';\n	}\n	$link_list .= \'</ul>\';\n\n	if ($forms)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\')\n			return $debug.$link_list;\n		else // popup\n			return $debug.tag(\'<option value=\"\">&nbsp;</option>\'.$select_list,\'select\',\' id=\"adi_form_links_forms\" name=\"name\"\');\n	else\n		return $debug;\n}\n\nfunction adi_form_links_parse($thing) {\n// get all tags & their attributes\n// ripped off (& modified) from publish.php\n\n	$f = \'@(</?txp:\\w+(?:\\s+\\w+\\s*=\\s*(?:\"(?:[^\"]|\"\")*\"|\\\'(?:[^\\\']|\\\'\\\')*\\\'|[^\\s\\\'\"/>]+))*\\s*/?\'.chr(62).\')@s\';\n	$t = \'@:(\\w+)(.*?)/?.$@s\';\n\n	$parsed = preg_split($f, $thing, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n	$level  = 0;\n	$out    = array();\n	$inside = \'\';\n	$istag  = FALSE;\n\n	foreach ($parsed as $chunk)\n	{\n		if ($istag)\n		{\n			if ($level === 0) {\n				preg_match($t, $chunk, $tag);\n\n				if (substr($chunk, -2, 1) === \'/\')\n				{ # self closing\n					TRUE; // do nothing but keep for reference\n				}\n				else\n				{ # opening\n					$level++;\n				}\n				// $tag[1] = the tag, $tag[2] = the attributes\n				$out[] = array(\'tag\' => trim($tag[1]), \'atts\' => trim($tag[2]));\n				$out = array_merge($out,adi_form_links_parse($tag[2])); // parse tags within tags\n			}\n			else\n			{\n				if (substr($chunk, 1, 1) === \'/\')\n				{ # closing\n					if (--$level === 0)\n					{\n						$out = array_merge($out,adi_form_links_parse($inside)); // parse contained tags\n						$inside = \'\';\n					}\n					else\n					{\n						$inside .= $chunk;\n					}\n				}\n				elseif (substr($chunk, -2, 1) !== \'/\')\n				{ # opening inside open\n					++$level;\n					$inside .= $chunk;\n				}\n				else\n				{\n					$inside .= $chunk;\n				}\n			}\n		}\n		else\n		{\n			if ($level)\n			{\n				$inside .= $chunk;\n			}\n		}\n\n		$istag = !$istag;\n	}\n\n	return $out;\n}\n\nfunction adi_form_links_splat($text) {\n// get tag attributes\n// ripped off (& modified) from txplib_misc.php\n	$atts  = array();\n\n	if (preg_match_all(\'@(\\w+)\\s*=\\s*(?:\"((?:[^\"]|\"\")*)\"|\\\'((?:[^\\\']|\\\'\\\')*)\\\'|([^\\s\\\'\"/>]+))@s\', $text, $match, PREG_SET_ORDER))\n	{\n		foreach ($match as $m)\n		{\n			switch (count($m))\n			{\n				case 3:\n					$val = str_replace(\'\"\"\', \'\"\', $m[2]);\n					break;\n				case 4:\n					$val = str_replace(\"\'\'\", \"\'\", $m[3]);\n					break;\n				case 5:\n					$val = $m[4];\n					break;\n			}\n\n			$atts[strtolower($m[1])] = $val;\n		}\n\n	}\n\n	return $atts;\n}\n\nfunction adi_form_links_list() {\n// arcane magic that gets adi_form_links\'s list into standard TXP tabs\n	ob_start(\'adi_form_links_inject\');\n}\n\nfunction adi_form_links_inject($buffer) {\n// another bit of the magic that gets adi_form_links\'s section popup into standard TXP tabs\n	global $DB,$event;\n\n	if(!isset($DB)) $DB = new db;\n	if ($event == \'page\')\n		$pattern = \'#id=\"page_form\".*</form>#sU\';\n	else if ($event == \'form\')\n		$pattern = \'#id=\"form_form\".*</form>#sU\';\n	else\n		return \'\';\n	$insert = \'adi_form_links_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n	return $buffer;\n}\n\nfunction adi_form_links_markup($matches) {\n// generate markup\n	global $adi_form_links_prefs;\n\n	$name = gps(\'name\');\n\n	$forms_found = adi_form_links_forms($name);\n\n	if ($forms_found)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\') // link list\n			$markup = graf(strong(gTxt(\'adi_forms_referenced\'))).$forms_found;\n		else // popup\n			$markup =\n				form(\n					tag(\n						tag(strong(gTxt(\'adi_forms_referenced\')),\'label\',\' for=\"adi_form_links_forms\"\')\n						.$forms_found\n						.fInput(\"submit\",\"edit_form\",gTxt(\'adi_edit_form\'),\"smallerbox\")\n						.eInput(\"form\")\n						.sInput(\"form_edit\")\n					,\'p\')\n				);\n	else // none found\n		$markup = strong(gTxt(\'adi_forms_referenced\')).br.gTxt(\'adi_none_found\');\n\n	return $matches[0] // the admin page chunk, plus:\n		.tag(\n			$markup\n			,\'div\'\n			,\' id=\"adi_form_links\"\'\n		);\n}\n\nfunction adi_form_links_markup_46($matches) {\n// generate markup\n	global $adi_form_links_prefs;\n\n	$name = gps(\'name\');\n\n	$forms_found = adi_form_links_forms($name);\n\n	if ($forms_found)\n		if ($adi_form_links_prefs[\'adi_form_links_type\'][\'value\'] == \'list\') // link list\n			$markup = graf(strong(gTxt(\'adi_forms_referenced\'))).$forms_found;\n		else // popup\n			$markup =\n				form(\n					tag(\n						tag(strong(gTxt(\'adi_forms_referenced\')),\'label\',\' for=\"adi_form_links_forms\"\')\n						.br\n						.$forms_found\n						.fInput(\"submit\",\"edit_form\",gTxt(\'adi_edit_form\'),\"smallerbox\")\n						.eInput(\"form\")\n						.sInput(\"form_edit\")\n					,\'p\')\n				);\n	else // none found\n		$markup = strong(gTxt(\'adi_forms_referenced\')).br.gTxt(\'adi_none_found\');\n\n	echo tag(\n		$markup\n		,\'div\'\n		,\' id=\"adi_form_links\"\'\n	);\n}\n\nfunction adi_form_links_script() {\n// jQuery-ism\n	global $adi_form_links_debug,$adi_form_links_newname;\n\n	// markup shifty\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_form_links\n		$(\"div#adi_form_links\").appendTo($(\"#main_content\")); // shift form links markup from footer\n	});\n</script>\nEND_SCRIPT;\n\n}\n\n\nfunction adi_form_links_script2() {\n// jQuery-ism\n	global $adi_form_links_debug,$adi_form_links_newname;\n\n	// new form name field inject for TXP 4.6\n	if ($adi_form_links_debug) echo \"adi_form_links_newname=$adi_form_links_newname\".n;\n\n	if ($adi_form_links_newname)\n		echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	// adi_form_links\n   	$().ready(function() {\n   		console.log(\"$adi_form_links_newname\");\n		$(\"input#new_form\").val(\"$adi_form_links_newname\");\n	});\n</script>\nEND_SCRIPT;\n\n}\n', '971acd993faf7f83ce6153b3f170c7c9', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_globals', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Global variables', '<h1><strong>adi_globals</strong> &#8211; Global variables</h1>\n\n<p>This is designed to make getting hold of <span class=\"caps\">TXP</span> globals a little easier.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Just specify the variable you&#8217;re interested in &amp; <strong>adi_globals</strong> will find it &amp; return it&#8217;s value.</p>\n\n<p>For example:</p>\n\n<p><code>&lt;txp:adi_globals name=&quot;page&quot; /&gt;</code></p>\n\n<p>will return the current Page template name from <code>$pretext</code>.</p>\n\n<p><strong>adi_globals</strong> automatically searches through <code>$prefs</code>, <code>$pretext</code>, <code>$thisarticle</code>, <code>$thispage</code>, <code>thissection</code>, <code>thisimage</code>, <code>$thiscomment</code>, <code>thiscategory</code>, <code>$thislink</code> &amp; <code>$thisfile</code>.</p>\n\n<p>And, just for good measure, a <span class=\"caps\">TXP</span> variable of the same name will be set as well.</p>\n\n<p><em>If the global variable is not available then nothing will be output &amp; no <span class=\"caps\">TXP</span> variable will be set.</em></p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>name=&quot;var name&quot;</code></p>\n\n<p>- a <span class=\"caps\">TXP</span> global variable to extract.</p>\n\n<p><code>txpvar=&quot;boolean&quot;</code></p>\n\n<p>- duplicate the global variable as a <span class=\"caps\">TXP</span> variable of the same name. Default = “1” (Yes).</p>\n\n<p><code>quiet=&quot;boolean&quot;</code></p>\n\n<p>- don&#8217;t display variable value. Default = “0” (display variable value).</p>\n\n<h2><strong>Sneaky peek &#8230;</strong></h2>\n\n<p>If you&#8217;re curious what global variables are available, try:</p>\n\n<p><code>&lt;txp:adi_globals debug=&quot;1&quot; /&gt;</code></p>\n\n<p><em>This option is not available if <span class=\"caps\">PHP</span> is disallowed in articles.</em></p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=37270\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_globals\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_globals - Global variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- TXP 4.6 tag registration\n	0.2		- added thisimage, thissection, thiscategory (thanks bloke)\n			- removed txpcfg (thanks joebaich)\n			- disabled debug option if PHP is disallowed in articles\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_globals\')\n	;\n}\n\nfunction adi_globals($atts) {\n\n	$globals = array(\'prefs\',\'thispage\',\'thissection\',\'pretext\',\'thisarticle\',\'thisimage\',\'thislink\',\'thisfile\',\'thiscomment\',\'thiscategory\');\n	foreach ($globals as $thisglobal)\n		global $$thisglobal;\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// the global variable we\'re looking for\n		\'quiet\'		=> \'0\',		// don\'t display value (default = no)\n		\'txpvar\'	=> \'1\',		// set TXP variable or not (default = yes)\n		\'debug\'		=> \'0\',\n	), $atts));\n\n	if (!$prefs[\'allow_article_php_scripting\']) // no peeking if PHP disallowed in articles\n		$debug = 0;\n\n	if ($name || $debug)\n		foreach ($globals as $thisglobal)\n			if (is_array($$thisglobal))\n				if ($debug) {\n					echo \'<pre>$\'.$thisglobal.\'</pre>\';\n					dmp($$thisglobal);\n				}\n				else\n					if (array_key_exists($name,$$thisglobal)) { // variable found\n						if ($txpvar) { // set TXP var\n							if (!isset($GLOBALS[\'variable\'])) // initialise TXP variables array, if required\n								$GLOBALS[\'variable\'] = array();\n							$GLOBALS[\'variable\'][$name] = ${$thisglobal}[$name];\n						}\n						if (!$quiet) // shout it out\n							return ${$thisglobal}[$name];\n					}\n	return \'\';\n}\n', '\n/*\n	adi_globals - Global variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- TXP 4.6 tag registration\n	0.2		- added thisimage, thissection, thiscategory (thanks bloke)\n			- removed txpcfg (thanks joebaich)\n			- disabled debug option if PHP is disallowed in articles\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_globals\')\n	;\n}\n\nfunction adi_globals($atts) {\n\n	$globals = array(\'prefs\',\'thispage\',\'thissection\',\'pretext\',\'thisarticle\',\'thisimage\',\'thislink\',\'thisfile\',\'thiscomment\',\'thiscategory\');\n	foreach ($globals as $thisglobal)\n		global $$thisglobal;\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// the global variable we\'re looking for\n		\'quiet\'		=> \'0\',		// don\'t display value (default = no)\n		\'txpvar\'	=> \'1\',		// set TXP variable or not (default = yes)\n		\'debug\'		=> \'0\',\n	), $atts));\n\n	if (!$prefs[\'allow_article_php_scripting\']) // no peeking if PHP disallowed in articles\n		$debug = 0;\n\n	if ($name || $debug)\n		foreach ($globals as $thisglobal)\n			if (is_array($$thisglobal))\n				if ($debug) {\n					echo \'<pre>$\'.$thisglobal.\'</pre>\';\n					dmp($$thisglobal);\n				}\n				else\n					if (array_key_exists($name,$$thisglobal)) { // variable found\n						if ($txpvar) { // set TXP var\n							if (!isset($GLOBALS[\'variable\'])) // initialise TXP variables array, if required\n								$GLOBALS[\'variable\'] = array();\n							$GLOBALS[\'variable\'][$name] = ${$thisglobal}[$name];\n						}\n						if (!$quiet) // shout it out\n							return ${$thisglobal}[$name];\n					}\n	return \'\';\n}\n', 'c1431dc0aaeb566c261bab12c265704c', '', '', 0, 5, 0),
('adi_gps', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.4', 'Extract GET & POST variables', '<h1><strong>adi_gps</strong> &#8211; Extract <span class=\"caps\">GET</span> &amp; <span class=\"caps\">POST</span> variables</h1>\n\n<p>This plugin extracts <span class=\"caps\">GET</span>/POST variables and assigns them to <span class=\"caps\">TXP</span> variables.</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>name=&quot;var name&quot;</code></p>\n\n<p>- a comma separated list of <span class=\"caps\">GET</span>/POST variables to extract. Default = &#8220;&#8221; (extract all <span class=\"caps\">GET</span>/POST variables). See <code>type</code> attribute for restricting retrieval to <span class=\"caps\">POST</span> variables only.</p>\n\n<p><code>new=&quot;var name&quot;</code></p>\n\n<p>- create a differently named variable. Default = &#8220;&#8221; (replicate variable name). Only applies when retrieving a single <span class=\"caps\">GET</span>/POST variable.</p>\n\n<p><code>type=&quot;var type&quot;</code></p>\n\n<p>- type of variable to extract. Default = “all” (extract <span class=\"caps\">GET</span> &amp; <span class=\"caps\">POST</span> variables). Use <code>type=&quot;post&quot;</code> to extract <span class=\"caps\">POST</span> variables only.</p>\n\n<p><code>quiet=&quot;boolean&quot;</code></p>\n\n<p>- don&#8217;t display variable value. Default = “0” (display variable value). Only applies if retrieving a single <span class=\"caps\">GET</span>/POST variable. Quiet mode automatically switched on if retrieving multiple variables.</p>\n\n<p><code>escape=&quot;mode&quot;</code></p>\n\n<p>- escape <span class=\"caps\">HTML</span> entities in variable value. Default = “html” (escape <span class=\"caps\">HTML</span> entities). Use <code>escape=&quot;&quot;</code> to switch off.</p>\n\n<p><code>decode=&quot;boolean&quot;</code></p>\n\n<p>- decodes any remaining %## encoding in the variable values. Default = “0” (No). Not normally required but may be needed if <span class=\"caps\">URL</span> has been processed by htaccess.</p>\n\n<p><code>glue=&quot;separator&quot;</code></p>\n\n<p>- separator to use when retrieving checkbox values. Default = “,” (comma).</p>\n\n<p><code>post=&quot;boolean&quot;</code></p>\n\n<p>- attribute deprecated and ignored. Default behaviour (v0.2+) now extracts both <span class=\"caps\">GET</span> &amp; <span class=\"caps\">POST</span> vars. Use <code>type=&quot;post&quot;</code> if you want to extract <span class=\"caps\">POST</span> variables only.</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p><code>&lt;txp:adi_gps /&gt;</code></p>\n\n<p>- extract all <span class=\"caps\">GET</span>/POST variables and duplicate them as <span class=\"caps\">TXP</span> variables. No values are displayed.</p>\n\n<p><code>&lt;txp:adi_gps type=&quot;post&quot; /&gt;</code></p>\n\n<p>- extract only <span class=\"caps\">POST</span> variables and duplicate them as <span class=\"caps\">TXP</span> variables. No values are displayed.</p>\n\n<p><code>&lt;txp:adi_gps name=&quot;myvar1&quot; /&gt;</code></p>\n\n<p>- extract a single <span class=\"caps\">GET</span>/POST variable called <em>myvar1</em>, create a <span class=\"caps\">TXP</span> variable of the same name and display it&#8217;s value.</p>\n\n<p><code>&lt;txp:adi_gps name=&quot;myvar2&quot; new=&quot;newvar2&quot; /&gt;</code></p>\n\n<p>- extract a single <span class=\"caps\">GET</span>/POST variable called <em>myvar2</em>, but create it as a <span class=\"caps\">TXP</span> variable called <em>newvar2</em>, and display it&#8217;s value.</p>\n\n<p><code>&lt;txp:adi_gps single name=&quot;myvar3&quot; quiet=&quot;1&quot; /&gt;</code></p>\n\n<p>- extract a <span class=\"caps\">GET</span>/POST variable called <em>myvar3</em>, create a <span class=\"caps\">TXP</span> variable of the same name but don&#8217;t display it&#8217;s value.</p>\n\n<p><code>&lt;txp:adi_gps name=&quot;myvar1,myvar2,myvar3&quot; /&gt;</code></p>\n\n<p>- extract three named <span class=\"caps\">GET</span>/POST variables, where <span class=\"caps\">TXP</span> variables of the same name are created, but no values displayed.</p>\n\n<h2><strong>Retrieving multiple values from checkboxes</strong></h2>\n\n<p>For example, if you have the following form:</p>\n\n<pre><code>&lt;form&gt;\n	&lt;input name=&quot;multiple[]&quot; type=&quot;checkbox&quot; value=&quot;first&quot;/&gt;First&lt;br&gt;\n	&lt;input name=&quot;multiple[]&quot; type=&quot;checkbox&quot; value=&quot;second&quot;/&gt;Second&lt;br&gt;\n	&lt;input name=&quot;multiple[]&quot; type=&quot;checkbox&quot; value=&quot;third&quot;/&gt;Third&lt;br&gt;\n	&lt;input type=&quot;submit&quot; value=&quot;Submit&quot; /&gt;\n&lt;/form&gt;</code></pre>\n\n<p>and a user ticks the first and second checkbox, then the <span class=\"caps\">TXP</span> variable called <em>multiple</em> will contain “first,second”.  The comma is the glue which can be changed using an attribute.</p>\n\n<h2><strong>Using the <span class=\"caps\">GET</span>/POST variables in pages or forms</strong></h2>\n\n<p>Because, by default, <strong>adi_gps</strong> duplicates the extracted <span class=\"caps\">URL</span> variables as <span class=\"caps\">TXP</span> variables you can use all the standard <span class=\"caps\">TXP</span> variable tags to process them:</p>\n\n<ul>\n	<li><code>&lt;txp:variable /&gt;</code></li>\n	<li><code>&lt;txp:if_variable /&gt;</code></li>\n</ul>\n\n<p>For example:</p>\n\n<pre><code>&lt;txp:adi_gps name=&quot;myvar&quot; quiet=&quot;1&quot; /&gt;\n&lt;txp:if_variable name=&quot;myvar&quot; value=&quot;some_value&quot;&gt;\n    do something different ...\n&lt;txp:else /&gt;\n    the same old thing ...\n&lt;/txp:if_variable&gt;</code></pre>\n\n<h2><strong>Using <span class=\"caps\">URL</span> variables in articles</strong></h2>\n\n<p>You can also use <code>&lt;txp:adi_gps /&gt;</code> within articles.  For example:</p>\n\n<blockquote>\n	<p><i>The value of myvar is <code>&lt;txp:adi_gps name=&quot;myvar&quot; /&gt;</code> &#8230;</i></p>\n</blockquote>\n\n<p>or if you&#8217;ve already extracted the variable elsewhere:</p>\n\n<blockquote>\n	<p><i>The value of myvar is <code>&lt;txp:variable name=&quot;myvar&quot; /&gt;</code> &#8230;</i></p>\n</blockquote>\n\n<p>No escaping from Textile is required (e.g. using <code>notextile.</code> or <code>== ... ==</code>).</p>', '\n/*\n	adi_gps - Extract GET & POST variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.4		- updated for TXP 4.6 trace\n	0.3		- copes with forms which supply data as arrays (checkboxes)\n			- new attribute \'glue\'\n			- TXP 4.6 tag registration\n	0.2		- multiple security & functionality improvements (thanks to Gocom)\n			- \'name\' attribute now accepts comma separated list\n			- removed \'txpvar\' attribute - TXP variables now always set\n			- \'post\' attribute deprecated & now ignored (default behaviour now retrieves POST vars)\n			- removed \'global\' attribute & functionality - not secure\n			- \'escape\' now enabled by default\n			- removed \'list\' & \'debug\' attributes - debug info now in TXP tag trace (when Production Status = debugging)\n			- new attribute \'type\'\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_gps\')\n	;\n}\n\nfunction adi_gps($atts) {\n\n	global $variable,$trace; // i.e. $GLOBALS[\'variable\']\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// comma separated list of GET/POST vars\n		\'new\'		=> \'\',		// new variable name\n		\'quiet\'		=> \'0\',		// return value or not\n		\'type\'		=> \'all\',	// \'all\' = GET & POST, \'post\' = POST only\n		\'post\'		=> \'0\',		// deprecated & ignored - POST vars now auto extracted\n		\'escape\'	=> \'html\',	// escape HTML entities\n		\'decode\'	=> \'0\',		// perform a urldecode\n		\'glue\'		=> \',\',		// join array values together\n	), $atts));\n\n	if ($name == \'\') // all vars extracted (GET/POST filtered later)\n		$name_list = array_keys(array_merge((array) $_GET, (array) $_POST));\n	else\n		$name_list = doArray(explode(\',\',$name),\'trim\');\n\n	// remove blanks from list (e.g if \"var1,var2,\" supplied)\n	$name_list = array_filter($name_list,\'strlen\');\n\n	// massage options if retrieving all vars or multiple vars requested\n	if (($name == \'\') || (count($name_list) > 1)) {\n		$quiet = 1; // force quiet mode\n		$new = \'\'; // disable var rename\n	}\n\n	// gps() or ps()\n	if (strtolower($type) == \'post\')\n		$func = \'ps\';\n	else\n		$func = \'gps\';\n\n	// process var list\n	foreach ($name_list as $index => $name) {\n		$value = $func($name); // extract value\n		if (is_array($value)) // if array then glue it together as a string\n			$value = implode($glue,$value);\n		$name = htmlspecialchars($new !== \'\' ? $new : $name);\n		if ($decode) // convert %chars\n			$value = urldecode($value);\n		if ($escape) // encode chars (e.g. ampersand becomes &amp;)\n			$value = htmlspecialchars($value);\n		if ($new) // rename var (single var mode only)\n			$name = $new;\n		$variable[$name] = $value; // set $GLOBALS[\'variable\'][$name]\n		// trace\n		if (is_object($trace)) // TXP 4.6\n			$trace->log(\'[\'.__FUNCTION__.\': \'.$name.\' = \'.$value.\']\');\n		else\n			trace_add(\'[\'.__FUNCTION__.\': \'.$name.\' = \'.$value.\']\');\n	}\n\n	if (!$quiet && isset($value)) // return value\n		return $value;\n\n	return \'\';\n}\n', '\n/*\n	adi_gps - Extract GET & POST variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.4		- updated for TXP 4.6 trace\n	0.3		- copes with forms which supply data as arrays (checkboxes)\n			- new attribute \'glue\'\n			- TXP 4.6 tag registration\n	0.2		- multiple security & functionality improvements (thanks to Gocom)\n			- \'name\' attribute now accepts comma separated list\n			- removed \'txpvar\' attribute - TXP variables now always set\n			- \'post\' attribute deprecated & now ignored (default behaviour now retrieves POST vars)\n			- removed \'global\' attribute & functionality - not secure\n			- \'escape\' now enabled by default\n			- removed \'list\' & \'debug\' attributes - debug info now in TXP tag trace (when Production Status = debugging)\n			- new attribute \'type\'\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_gps\')\n	;\n}\n\nfunction adi_gps($atts) {\n\n	global $variable,$trace; // i.e. $GLOBALS[\'variable\']\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// comma separated list of GET/POST vars\n		\'new\'		=> \'\',		// new variable name\n		\'quiet\'		=> \'0\',		// return value or not\n		\'type\'		=> \'all\',	// \'all\' = GET & POST, \'post\' = POST only\n		\'post\'		=> \'0\',		// deprecated & ignored - POST vars now auto extracted\n		\'escape\'	=> \'html\',	// escape HTML entities\n		\'decode\'	=> \'0\',		// perform a urldecode\n		\'glue\'		=> \',\',		// join array values together\n	), $atts));\n\n	if ($name == \'\') // all vars extracted (GET/POST filtered later)\n		$name_list = array_keys(array_merge((array) $_GET, (array) $_POST));\n	else\n		$name_list = doArray(explode(\',\',$name),\'trim\');\n\n	// remove blanks from list (e.g if \"var1,var2,\" supplied)\n	$name_list = array_filter($name_list,\'strlen\');\n\n	// massage options if retrieving all vars or multiple vars requested\n	if (($name == \'\') || (count($name_list) > 1)) {\n		$quiet = 1; // force quiet mode\n		$new = \'\'; // disable var rename\n	}\n\n	// gps() or ps()\n	if (strtolower($type) == \'post\')\n		$func = \'ps\';\n	else\n		$func = \'gps\';\n\n	// process var list\n	foreach ($name_list as $index => $name) {\n		$value = $func($name); // extract value\n		if (is_array($value)) // if array then glue it together as a string\n			$value = implode($glue,$value);\n		$name = htmlspecialchars($new !== \'\' ? $new : $name);\n		if ($decode) // convert %chars\n			$value = urldecode($value);\n		if ($escape) // encode chars (e.g. ampersand becomes &amp;)\n			$value = htmlspecialchars($value);\n		if ($new) // rename var (single var mode only)\n			$name = $new;\n		$variable[$name] = $value; // set $GLOBALS[\'variable\'][$name]\n		// trace\n		if (is_object($trace)) // TXP 4.6\n			$trace->log(\'[\'.__FUNCTION__.\': \'.$name.\' = \'.$value.\']\');\n		else\n			trace_add(\'[\'.__FUNCTION__.\': \'.$name.\' = \'.$value.\']\');\n	}\n\n	if (!$quiet && isset($value)) // return value\n		return $value;\n\n	return \'\';\n}\n', '1a384514648c109ec37993b237348e82', '', '', 0, 5, 0),
('adi_if_content', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Content informed conditional', '<h4>Usage</h4>\n\n<pre><code>&lt;txp:adi_if_content [test=&quot;tag1,tag2&quot; ignore=&quot;tag3,tag4&quot; insert=&quot;tag5,tag6&quot;]&gt;\n	...\n	&lt;txp:adi_if_content_ignore&gt;&lt;txp:tag7 /&gt;&lt;/txp:adi_if_content_ignore&gt;\n	...\n	&lt;txp:tag8 /&gt;\n	...\n	&lt;txp:adi_if_content_insert&gt;&lt;txp:tag9 /&gt;&lt;/txp:adi_if_content_insert&gt;\n	...\n&lt;/txp:adi_if_content&gt;\n</code></pre>', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n        ->register(\'adi_if_content\')\r\n        ->register(\'adi_if_content_insert\')\r\n        ->register(\'adi_if_content_ignore\')\r\n    ;\r\n}\r\n\r\n/*\r\nglobal $prefs;\r\nif (!isset($prefs[\'adi_if_content_ignore\']))\r\n	set_pref(\'adi_if_content_ignore\', $prefs[\'adi_if_content_ignore\'] = \'site_name,site_slogan,site_url,text\', \'adi_if_cnt\', PREF_ADVANCED);\r\n*/\r\n\r\nfunction adi_if_content_insert($atts, $thing=\'\') {\r\n    return parse($thing);\r\n}\r\n\r\nfunction adi_if_content_ignore($atts, $thing=\'\') {\r\n    return parse($thing);\r\n}\r\n\r\nfunction adi_if_content($atts, $thing=\'\') {\r\n\r\n    global $variable, $txp_parsed, $txp_else;\r\n    extract(lAtts(array(\r\n        \'name\'		=> NULL,\r\n        \'test\'		=> \'\',\r\n        \'insert\'	=> \'\',\r\n        \'ignore\'	=> get_pref(\'adi_if_content_ignore\')\r\n    ), $atts));\r\n\r\n    if ($name !== null) { /* variable mode */\r\n        return parse($thing, isset($variable[$name]) && trim($variable[$name]) !== \'\');\r\n    }\r\n\r\n    $test = $test ? do_list_unique($test) : array();\r\n    $insert = $insert ? do_list_unique($insert) : array();\r\n    $insert[] = \'adi_if_content_insert\';\r\n    $ignore = $ignore ? do_list_unique($ignore) : array();\r\n    $ignore[] = \'adi_if_content_ignore\';\r\n\r\n    $tag = $txp_parsed[$hash = sha1($thing)];\r\n    if (empty($tag)) return $thing;\r\n    $nr = $txp_else[$hash][0] - 2;\r\n    $n = 1;\r\n\r\n    for ($isempty  = TRUE, $tags = array(), $out = $tag[0], $i = 0; $n <= $nr; $n++) {\r\n        $t = $tag[$n];\r\n        \r\n        if (in_array($t[1], $insert)) {\r\n            $out .= \"<txp:$i />\";\r\n            $tags[$i++] = $t;\r\n        } else {\r\n            $out .= ($nextag = processTags($t[1], $t[2], $t[3]));\r\n            $isempty &= trim($nextag) === \'\' || ($test ? !in_array($t[1], $test) : in_array($t[1], $ignore));\r\n        }\r\n\r\n        $out .= $tag[++$n];\r\n    }\r\n\r\n    if (!$isempty) {\r\n        $replacements = array();\r\n        foreach ($tags as $i => $t) {\r\n            $replacements[\"<txp:$i />\"] = processTags($t[1], $t[2], $t[3]);\r\n        }\r\n        $out = strtr($out, $replacements);\r\n    }\r\n\r\n    return $isempty ? parse($thing, false) : $out;\r\n}', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n        ->register(\'adi_if_content\')\r\n        ->register(\'adi_if_content_insert\')\r\n        ->register(\'adi_if_content_ignore\')\r\n    ;\r\n}\r\n\r\n/*\r\nglobal $prefs;\r\nif (!isset($prefs[\'adi_if_content_ignore\']))\r\n	set_pref(\'adi_if_content_ignore\', $prefs[\'adi_if_content_ignore\'] = \'site_name,site_slogan,site_url,text\', \'adi_if_cnt\', PREF_ADVANCED);\r\n*/\r\n\r\nfunction adi_if_content_insert($atts, $thing=\'\') {\r\n    return parse($thing);\r\n}\r\n\r\nfunction adi_if_content_ignore($atts, $thing=\'\') {\r\n    return parse($thing);\r\n}\r\n\r\nfunction adi_if_content($atts, $thing=\'\') {\r\n\r\n    global $variable, $txp_parsed, $txp_else;\r\n    extract(lAtts(array(\r\n        \'name\'		=> NULL,\r\n        \'test\'		=> \'\',\r\n        \'insert\'	=> \'\',\r\n        \'ignore\'	=> get_pref(\'adi_if_content_ignore\')\r\n    ), $atts));\r\n\r\n    if ($name !== null) { /* variable mode */\r\n        return parse($thing, isset($variable[$name]) && trim($variable[$name]) !== \'\');\r\n    }\r\n\r\n    $test = $test ? do_list_unique($test) : array();\r\n    $insert = $insert ? do_list_unique($insert) : array();\r\n    $insert[] = \'adi_if_content_insert\';\r\n    $ignore = $ignore ? do_list_unique($ignore) : array();\r\n    $ignore[] = \'adi_if_content_ignore\';\r\n\r\n    $tag = $txp_parsed[$hash = sha1($thing)];\r\n    if (empty($tag)) return $thing;\r\n    $nr = $txp_else[$hash][0] - 2;\r\n    $n = 1;\r\n\r\n    for ($isempty  = TRUE, $tags = array(), $out = $tag[0], $i = 0; $n <= $nr; $n++) {\r\n        $t = $tag[$n];\r\n        \r\n        if (in_array($t[1], $insert)) {\r\n            $out .= \"<txp:$i />\";\r\n            $tags[$i++] = $t;\r\n        } else {\r\n            $out .= ($nextag = processTags($t[1], $t[2], $t[3]));\r\n            $isempty &= trim($nextag) === \'\' || ($test ? !in_array($t[1], $test) : in_array($t[1], $ignore));\r\n        }\r\n\r\n        $out .= $tag[++$n];\r\n    }\r\n\r\n    if (!$isempty) {\r\n        $replacements = array();\r\n        foreach ($tags as $i => $t) {\r\n            $replacements[\"<txp:$i />\"] = processTags($t[1], $t[2], $t[3]);\r\n        }\r\n        $out = strtr($out, $replacements);\r\n    }\r\n\r\n    return $isempty ? parse($thing, false) : $out;\r\n}', '44e7de927aeb2ce8b252efa1508ef397', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_image_tab', 1, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Enhanced image tab', '<h1><strong>adi_image_tab</strong> &#8211; Enhanced image tab</h1>\n\n<p>This plugin provides an enhancement to the standard <span class=\"caps\">TXP</span> Image Edit tab:</p>\n\n<ul>\n	<li>displays image ID, dimensions, file size, author, timestamp</li>\n	<li>previous/next links</li>\n	<li>dropdown menus for selecting images to edit</li>\n	<li>image delete button</li>\n	<li>thumbnail dimensions &amp; file size</li>\n	<li>option to modify the image&#8217;s timestamp</li>\n	<li>option to populate caption with alternate text</li>\n	<li>changes tab behaviour so that the Save button returns the user to the current Image Edit tab rather than the Images List tab</li>\n</ul>\n\n<h2><strong>Usage</strong></h2>\n\n<p>After installing &amp; activating the plugin, select the image you wish to edit from the Images tab as normal.</p>\n\n<h2><strong>Dropdown menus</strong></h2>\n\n<p>Two dropdown menus are provided, to help speed up access to images:</p>\n\n<ul>\n	<li>Recent images &#8211; contains a list of the recently updated images</li>\n	<li>Images &#8211; is a link to the Images list tab plus a dropdown containing images surrounding the current image in the list</li>\n</ul>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_image_tab\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36094\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_image_tab\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '/*\n	adi_image_tab - Enhanced image tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.2		- TXP 4.5-ified\n				- code tidy up\n				- fix: styling only generated for event=image tabs (thanks uli)\n				- enhancement: image delete button\n				- enhancement: thumbnail info\n				- changed: recent image list now stored as \"user\" pref\n		0.1.1	- fixed: errors after image deleted (thanks uli)\n				- fixed: spurious timestamp error message when save image directly after upload\n				- fixed: timestamp fields now visible after uploading image\n		0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $event,$step,$adi_image_tab_debug,$adi_image_tab_message,$adi_image_tab_txp450;\n\n	$adi_image_tab_debug = 0;\n\n	// using pluggable_ui (4.3.0) & lifecyle/plugin options (4.2.0), so Elvis should leave the building if need be\n	if (!version_compare(txp_version,\'4.3.0\',\'>=\')) return;\n\n	$adi_image_tab_txp450 = (version_compare(txp_version,\'4.4.1\',\'>\'));\n\n	adi_image_tab_init($event,$step);\n\n	// divert standard Image Tab step flow\n	if (($event == \'image\') && ($step == \'image_save\')) {\n		$adi_image_tab_message[] = adi_image_tab_save();\n		$adi_image_tab_timestamp_message = adi_image_tab_timestamp_save();\n		if ($adi_image_tab_timestamp_message)\n			$adi_image_tab_message[] = \' (\'.$adi_image_tab_timestamp_message.\')\';\n		$step = \'image_edit\'; // force TXP tab to go to image edit page, rather than back to image list\n	}\n}\n\nfunction adi_image_tab_init($event,$step) {\n\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_gtxt,$adi_image_tab_url,$adi_image_tab_prefs,$adi_image_tab_txp450;\n\n	// Textpack\n	$adi_image_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_image_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_image_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_image_tab_gtxt = array(\n		\'adi_copy_alt_caption\' => \'Copy alternate text to caption\',\n		\'adi_delete_image\' => \'Delete image\',\n		\'adi_recent_images\' => \'Recent images\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_timestamp_invalid\' => \'Invalid timestamp\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// plugin lifecycle\n	register_callback(\'adi_image_tab_lifecycle\',\'plugin_lifecycle.adi_image_tab\');\n\n	// preferences & defaults\n	$adi_image_tab_prefs = array(\n		\'adi_image_tab_recent\'			=> array(\'value\' => \'\',		\'input\' => \'text_input\'), // start off with clean slate (comma separated list of IDs - set to \'0\' to disable?)\n		\'adi_image_tab_recent_thumb\'	=> array(\'value\' => \'1\',	\'input\' => \'yesnoradio\'), // show thumbnails in recent images menu?\n		\'adi_image_tab_menu_limit\'		=> array(\'value\' => \'10\',	\'input\' => \'text_input\'), // max number of recent menu items\n	);\n	foreach ($adi_image_tab_prefs as $adi_image_tab_pref => $this_pref) {\n		$get_value = get_pref($adi_image_tab_pref,\'?\'); // returns \'?\' if not set (but beware of cacheing)\n		if ($get_value != \'?\')\n			$adi_image_tab_prefs[$adi_image_tab_pref][\'value\'] = $get_value;\n	}\n\n	// callbacks\n	register_callback(\'adi_image_tab\',\'image_ui\',\'image_edit\');\n	register_callback(\'adi_image_tab_extend_detail\',\'image_ui\',\'extend_detail_form\');\n	if ($adi_image_tab_txp450) // thumb image has its own little cupboard\n		register_callback(\'adi_image_tab_thumbnail\',\'image_ui\',\'thumbnail_image\');\n	else\n		register_callback(\'adi_image_tab_thumbnail\',\'image_ui\',\'thumbnail_edit\');\n\n	// style\n	if ($event == \'image\')\n		register_callback(\'adi_image_tab_style\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_image_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_image_tab\',$adi_image_tab_debug);\n	if ($adi_image_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_image_tab\',\'1,2,6\');\n		register_callback(\'adi_image_tab_options\',\'plugin_prefs.adi_image_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_image_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_image_tab_options\',\'adi_image_tab options\');\n		register_callback(\'adi_image_tab_options\',\'adi_image_tab_options\');\n	}\n\n	// single image\n	$id = gps(\'id\');\n\n	// multiple images\n	$selected = gps(\'selected\');\n\n	// update recent images list depending on $step\n	if (in_array($step,array(\'image_insert\',\'image_replace\',\'image_save\',\'thumbnail_insert\',\'thumbnail_create\',\'thumbnail_delete\',\'image_multi_edit\')))\n		if ($selected)\n			adi_image_tab_recent_list($selected);\n		else\n			adi_image_tab_recent_list($id);\n\n	if ($adi_image_tab_debug) {\n		echo \"Event=$event, Step=$step, ID=$id<br/>\";\n		if ($selected) {\n			echo \'Selected IDs:\';\n			dmp($selected);\n		}\n		echo \'<br/>$_POST:\';\n		dmp($_POST);\n	}\n}\n\nfunction adi_image_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_image_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n	global $adi_image_tab_debug,$adi_image_tab_prefs;\n\n	$result = \'?\';\n	if ($step == \'enabled\') {\n		// get old pref (i.e. \"system\" - if upgrading from v0.1)\n		$value = get_pref(\'adi_image_tab_recent\',\'?\');\n		if ($value != \"?\") {\n			// delete old pref\n			safe_delete(\'txp_prefs\',\"name = \'adi_image_tab_recent\'\",$adi_image_tab_debug);\n			// resave as \"user\" pref\n			$result = set_pref(\'adi_image_tab_recent\',$value,\'adi_image_tab\',2,$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'input\'],0,TRUE);\n		}\n	}\n	else if ($step == \'deleted\') {\n		// delete preferences\n		$result = safe_delete(\'txp_prefs\',\"name LIKE \'adi_image_tab_%\'\",$adi_image_tab_debug);\n		return $res;\n	}\n	if ($adi_image_tab_debug)\n		echo \"Event=$event Step=$step Result=$result\";\n}\n\nfunction adi_image_tab_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_image_tab_gtxt() are functionally equivalent\n	global $adi_image_tab_gtxt;\n\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_image_tab_gtxt)) // adi translation found\n			return $adi_image_tab_gtxt[$phrase];\n		else // last resort\n			return $phrase;\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n\nfunction adi_image_tab_style() {\n// some style action\n	echo\n		\'<style type=\"text/css\">\n			.adi_image_tab_info { overflow:hidden; margin:0 0 0.5em 0; border-bottom:1px solid #ddd }\n			.adi_image_tab_info p { margin:0 0 0.3em 0 }\n			.adi_image_tab_timestamp .checkbox { margin:0 }\n			.adi_image_tab_timestamp input.edit { width:2.3em }\n			.adi_image_tab_timestamp input.year { width:3.3em }\n			.adi_image_tab_non_link { color:#c0c0c0 }\n			table#list td { padding-top:10px; padding-bottom:5px }\n			table#list .adi_image_tab_message { padding:0 0 0.5em 0; text-align:center }\n			table#list .adi_image_tab_menu li { margin:0 }\n			table#list tr.thumbnail-edit td { border-bottom:0 }\n			table#list #thumbnail_alter_form p.label { margin-bottom:0 }\n			table#list tr.thumbnail-edit p { margin-bottom:0 }\n			table#list tr.thumbnail-edit br { display:none }\n			.adi_image_tab_options { text-align:center }\n			.adi_image_tab_menu, .adi_image_tab_menu ul { list-style:none; float:left }\n			.adi_image_tab_menu a { display:block }\n			.adi_image_tab_menu li { float:left; margin:0 }\n			.adi_image_tab_menu li ul { padding:0; position:absolute; width:100px; left:-999em; z-index:999 }\n			.adi_image_tab_menu li:hover, .adi_image_tab_menu li.sfhover { position:static }\n			.adi_image_tab_menu li:hover ul, .adi_image_tab_menu li.sfhover ul { left:auto }\n			.adi_image_tab_menu li ul li { width:100px; margin:0; padding:0; border:solid #c0c0c0; border-width:0 1px 1px 1px; background-color:white }\n			.adi_image_tab_menu li ul li.adi_image_tab_menu_first { width:100px; margin:0; padding:0; border-top-width:1px }\n			.adi_image_tab_menu li ul li div { padding:2px 5px; color:#444 }\n			.adi_image_tab_menu li ul li a { padding:2px 5px; text-decoration:none; word-wrap:break-word; color:#444 }\n			.adi_image_tab_menu_thumbnail_height { height:50px }\n			.adi_image_tab_menu_thumbnail_width { width:90px }\n			.adi_image_tab_links { overflow:hidden }\n			.adi_image_tab_classic .adi_image_tab_prev { float:left }\n			.adi_image_tab_classic .adi_image_tab_next { float:left; margin-left:1em }\n			.adi_image_tab_classic .adi_image_tab_recent_menu { float:left; margin-left:8em }\n			.adi_image_tab_classic .adi_image_tab_images_menu { float:right }\n			.adi_image_tab_new .adi_image_tab_prev { float:left }\n			.adi_image_tab_new .adi_image_tab_next { float:right }\n			.adi_image_tab_new .adi_image_tab_recent_menu { float:left; margin-left:6em }\n			.adi_image_tab_new .adi_image_tab_images_menu { float:left; margin-left:6em }\n			/* TXP 4.5.0 tweaks */\n			.txp-body .adi_image_tab_links ul { margin-top:0; margin-bottom:0.5em; padding:0 }\n			.txp-body .adi_image_tab_timestamp input.year { width:3.7em }\n		</style>\';\n}\n\nfunction adi_image_tab($event,$step,$default) {\n// display image info & links\n// e.g. $event = article_ui, $step = image_edit\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_txp450;\n\n	$img_dir = $prefs[\'path_to_site\'].\'/\'.$prefs[\'img_dir\'];\n	$id = gps(\'id\');\n\n	// homegrown messages\n	adi_image_tab_messagepane();\n\n	// newly added image?\n	if (empty($id)) {\n		$id = adi_image_tab_get_new_id();\n		adi_image_tab_recent_list($id); // update recent list\n	}\n\n	// create image info & links\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\",$adi_image_tab_debug);\n	if ($rs) {\n		extract($rs);\n		if ($adi_image_tab_debug) {\n			echo \'This image:\';\n			dmp($rs);\n			echo \'getimagesize():\';\n			$path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$id.$ext;\n			dmp(getimagesize($path));\n		}\n		$filesize = filesize($img_dir.\'/\'.$id.$ext);\n		if ($adi_image_tab_txp450) // bugger me it\'s now semantic\n			$info = tag(\n				tag(gTxt(\'tag_image\').\' #\'.$id.\' (\'.$w.\' X \'.$h.\', \'.adi_image_tab_bytesize($filesize).\', \'.$author.\')\',\'h3\')\n				.adi_image_tab_links()\n				,\'div\'\n				,\' class=\"adi_image_tab_info\"\'\n			);\n		else\n			$info = tag(\n				graf(\'<b>\'.gTxt(\'tag_image\').\' #\'.$id.\'</b> (\'.$w.\' X \'.$h.\', \'.adi_image_tab_bytesize($filesize).\', \'.$author.\')\')\n				.adi_image_tab_links()\n				,\'div\'\n				,\' class=\"adi_image_tab_info\"\'\n			);\n\n		return $info.$default.adi_image_tab_delete_button($id);\n	}\n}\n\nfunction adi_image_tab_delete_button($image_id) {\n// delete button\n\n	return form(\n		tag(\n			fInput(\'submit\',\'do_something\',adi_image_tab_gtxt(\'adi_delete_image\'),\'smallerbox\',\"\",\'return verify(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\')\')\n			.eInput(\'image\').sInput(\'image_multi_edit\').hInput(\'edit_method\',\'delete\').hInput(\'selected[]\',$image_id)\n			,\'p\'\n		)\n		,\'\'\n		,\'\'\n		,\'post\'\n	);\n}\n\nfunction adi_image_tab_links() {\n// output useful image links\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_prefs,$adi_image_tab_txp450;\n\n	extract(doSlash(gpsa(array(\'id\',\'sort\',\'dir\',\'search_method\',\'page\',\'crit\'))));\n\n	// get from prefs\n	$recent_list = $adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'];\n	$recent_limit = $adi_image_tab_prefs[\'adi_image_tab_menu_limit\'][\'value\'];\n\n	if ($adi_image_tab_debug) {\n		echo \'URL vars:\';\n		dmp(gpsa(array(\'id\',\'sort\',\'dir\',\'search_method\',\'page\',\'crit\')));\n	}\n\n	// mostly from include/txp_image.php\n	if ($sort === \'\') $sort = get_pref(\'image_sort_column\', \'id\');\n	if ($dir === \'\') $dir = get_pref(\'image_sort_dir\', \'desc\');\n	$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\n	switch ($sort) {\n		case \'name\':\n			$sort_sql = \'name \'.$dir;\n			break;\n		case \'thumbnail\':\n			$sort_sql = \'thumbnail \'.$dir.\', id asc\';\n			break;\n		case \'category\':\n			$sort_sql = \'category \'.$dir.\', id asc\';\n			break;\n		case \'date\':\n			$sort_sql = \'date \'.$dir.\', id asc\';\n			break;\n		case \'author\':\n			$sort_sql = \'author \'.$dir.\', id asc\';\n			break;\n		default:\n			$sort = \'id\';\n			$sort_sql = \'id \'.$dir;\n			break;\n	}\n	$criteria = 1; // ?\n	if ($search_method and $crit) {\n		$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\n\n		$critsql = array(\n			\'id\'		=> \"ID in (\'\" .join(\"\',\'\", do_list($crit_escaped)). \"\')\",\n			\'name\'		=> \"name like \'%$crit_escaped%\'\",\n			\'category\' 	=> \"category like \'%$crit_escaped%\'\",\n			\'author\'	=> \"author like \'%$crit_escaped%\'\",\n			\'alt\'		=> \"alt like \'%$crit_escaped%\'\",\n			\'caption\'	=> \"caption like \'%$crit_escaped%\'\"\n		);\n\n		if (array_key_exists($search_method, $critsql)) {\n			$criteria = $critsql[$search_method];\n			$limit = 500;\n		}\n		else {\n			$search_method = \'\';\n			$crit = \'\';\n		}\n	}\n	else {\n		$search_method = \'\';\n		$crit = \'\';\n	}\n\n	// make up full list of image IDs\n	$rs = safe_rows(\'id\', \'txp_image\', \"$criteria order by $sort_sql\",$adi_image_tab_debug);\n	// convert array(0 => array(\'id\' => xx)) to array(0 => xx)\n	$id_list = array();\n	foreach ($rs as $index => $value)\n		$id_list[] = $value[\'id\'];\n\n	$link_preamble = \'index.php?event=image\'.a.\'step=image_edit\';\n	$link_postamble = a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n\n	// prev & next links\n	$adi_image_tab_txp450 ? $prev_text = gTxt(\'prev\') : $prev_text = \'&#8249;\'.gTxt(\'prev\');\n	$adi_image_tab_txp450 ? $next_text = gTxt(\'next\') : $next_text = gTxt(\'next\').\'&#8250;\';\n	$this_key = array_search($id,$id_list);\n	$prev_key = $this_key - 1;\n	$next_key = $this_key + 1;\n	if ($prev_key < 0)\n		$prev_link = tag($prev_text,\'div\',\' class=\"adi_image_tab_prev adi_image_tab_non_link\"\');\n	else\n		$prev_link = adi_image_tab_list_markup($id_list[$prev_key],$prev_text,\'\',$link_preamble,$link_postamble,\'adi_image_tab_prev\');\n	if ($next_key > count($id_list) - 1)\n		$next_link = tag($next_text,\'div\',\' class=\"adi_image_tab_next adi_image_tab_non_link\"\');\n	else\n		$next_link = adi_image_tab_list_markup($id_list[$next_key],$next_text,\'\',$link_preamble,$link_postamble,\'adi_image_tab_next\');\n\n	// link to Images tab & dropdown menu\n	$start_key = $this_key - $recent_limit / 2; // want current image in middle of list\n	if ($start_key < 0)\n		$start_key = 0;\n	$image_list = array_slice($id_list,$start_key,$recent_limit);\n	if (count($image_list) < $recent_limit)\n		$image_list = array_slice($id_list,$recent_limit*-1,$recent_limit);\n	$image_menu = adi_image_tab_list_markup(implode(\',\',$image_list),gTxt(\'images\'),\'?event=image\',$link_preamble,$link_postamble,\'adi_image_tab_images_menu\');\n\n	// recent images dropdown menu\n	$recent_menu = adi_image_tab_list_markup($recent_list,adi_image_tab_gtxt(\'adi_recent_images\'),\'#\',$link_preamble,$link_postamble,\'adi_image_tab_recent_menu\');\n\n	if ($adi_image_tab_debug) {\n		echo \'this_key=\'.$this_key.\', prev_key=\'.$prev_key.\', next_key=\'.$next_key.\', start=\'.$start_key.\'<br/>\';\n		echo \'<br/>$id_list:\';\n		dmp($id_list);\n		echo \'recent list: \'.$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'].\'<br/>\';\n		echo \'<br/>$image_list:\';\n		dmp($image_list);\n	}\n\n	if (true)\n		$out = tag($prev_link.$next_link.$recent_menu.$image_menu,\'div\',\' class=\"adi_image_tab_links adi_image_tab_classic\"\');\n	else\n		$out = tag($prev_link.$recent_menu.$image_menu.$next_link,\'div\',\' class=\"adi_image_tab_links adi_image_tab_new\"\');\n\n	return $out;\n}\n\nfunction adi_image_tab_recent_list($id=\'\') {\n// update or return recent images list\n	global $adi_image_tab_prefs,$adi_image_tab_debug;\n\n	// get from prefs\n	$recent_list = $adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'];\n	$recent_limit = $adi_image_tab_prefs[\'adi_image_tab_menu_limit\'][\'value\'];\n\n	if ($id == \'\') // simply return current list\n		return $recent_list;\n\n	if ($recent_list)\n		$recents = explode(\',\',$recent_list);\n	else // don\'t want empty value in array\n		$recents = array();\n\n	// update recent list\n	if (is_array($id))\n		$recents = array_merge($id,$recents); // add multiple IDs to front of list\n	else if ($id == \'0\')\n		; // set list to \'0\', i.e. disabled ... if required one day\n	else // single id\n		array_unshift($recents,$id); // add single ID to front of list\n	// remove duplicates\n	$recents = array_unique($recents);\n	// trim to length\n	$recents = array_slice($recents, 0, $recent_limit);\n\n	// save in preferences\n	$new_list = implode(\',\',$recents);\n	$res = set_pref(\'adi_image_tab_recent\',$new_list,\'adi_image_tab\',2,$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'input\'],0,TRUE);\n	$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'] = $new_list;\n\n	if ($adi_image_tab_debug) {\n		if (is_array($id))\n			$id = implode(\',\',$id);\n		echo \'Recent list:<br/>update id=\'.$id.\'<br/>\';\n		echo \'old list:\'.$recent_list.\'<br/>\';\n		echo \'new list:\'.$new_list.\'<br/>\';\n	}\n}\n\nfunction adi_image_tab_list_markup($this_list,$this_name,$top_link,$link_preamble,$link_postamble,$extra_class=\'\') {\n// dropdown list of images\n	global $adi_image_tab_prefs,$adi_image_tab_debug,$prefs;\n\n	if ($this_list) {\n		$li = \'\';\n		$these = explode(\',\',$this_list);\n		foreach ($these as $index => $image_id) {\n			$this_item = \'\';\n			// get some image & thumbnail details\n			$row = safe_row(\'name,ext,alt,caption,thumbnail,thumb_w,thumb_h\',\'txp_image\',\'id=\'.$image_id);\n			extract($row);\n			if ($row) { // image found in database\n				// populate menu with thumbnail (if wanted & it exists)\n				if ($adi_image_tab_prefs[\'adi_image_tab_recent_thumb\'][\'value\']) {\n					if ($thumbnail) {\n						$thumb_path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$image_id.\'t\'.$ext;\n						// work whether need to adjust thumbnail height or width\n						if (!$thumb_h) // thumb_w & thumb_h introduced in 4.2.0, dimensions of thumbs created prior to that are set to zero in database!\n							list($thumb_w,$thumb_h,$type,$attr) = getimagesize($thumb_path); // measure actual thumbnail file\n						$css_width = $thumb_w * 50 / $thumb_h;\n						if ($css_width > 90)\n							$class = \'adi_image_tab_menu_thumbnail_width\';\n						else\n							$class = \'adi_image_tab_menu_thumbnail_height\';\n						// image markup\n						$this_item = \'<img src=\"\'.$thumb_path.\'\" alt=\"\'.$alt.\'\" class=\"\'.$class.\'\" />\';\n					}\n				}\n			}\n			else // image not found in db - probably deleted\n				$this_item = gTxt(\'unknown_image\');\n			// find something else useful to put in menu\n			if (!$this_item)\n				$this_item = $alt;\n			if (!$this_item)\n				$this_item = $name;\n			// class\n			if ($index)\n				$class = \'\';\n			else\n				$class = \'adi_image_tab_menu_first\';\n			$class = \' class=\"\'.$class.\'\"\';\n			// menu item\n			if ($row) // image found in database\n				$li .= tag(\n					\'<a href=\"\'.$link_preamble.a.\'id=\'.$image_id.$link_postamble.\'\">\'.\'#\'.$image_id.\'<br/>\'.$this_item.\'</a>\'\n					,\'li\'\n					,$class\n					);\n			else\n				$li .= tag(\n					tag(\'#\'.$image_id.\'<br/>\'.$this_item,\'div\')\n					,\'li\'\n					,$class\n					);\n		}\n		// class for image menu\n		$class = \'adi_image_tab_menu\';\n		if ($extra_class)\n			$class = $class.\' \'.$extra_class;\n		if (empty($top_link)) // set top link to first image in list (must be prev/next \"list\")\n			$top_link = $link_preamble.a.\'id=\'.$these[0].$link_postamble;\n		$list_preamble = \'<ul class=\"\'.$class.\'\"><li><a href=\"\'.$top_link.\'\">\'.$this_name.\'</a>\';\n		$list_postamble = \'</li></ul>\';\n		return $list_preamble.tag($li,\'ul\').$list_postamble;\n	}\n	else\n		return graf($this_name,\' class=\"adi_image_tab_non_link\"\');\n}\n\nfunction adi_image_tab_thumbnail($event,$step,$default,$rs) {\n// add thumbnail title & info\n	global $prefs,$adi_image_tab_txp450;\n\n	extract($rs);\n	$img_dir = $prefs[\'path_to_site\'].\'/\'.$prefs[\'img_dir\'];\n	$out = \'\';\n	if ($thumbnail) {\n		$thumb_path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$id.\'t\'.$ext;\n		if (!$thumb_h) // thumb_w & thumb_h introduced in 4.2.0, dimensions of thumbs created prior to that are set to zero in database!\n			list($thumb_w,$thumb_h,$type,$attr) = getimagesize($thumb_path); // measure actual thumbnail file\n		$filesize = filesize($img_dir.\'/\'.$id.\'t\'.$ext);\n		$out = gTxt(\'thumbnail\').\' (\'.$thumb_w.\' X \'.$thumb_h.\', \'.adi_image_tab_bytesize($filesize).\')\';\n	}\n	if ($adi_image_tab_txp450)\n		return tag($out,\'h3\').$default;\n	else\n		return tag($out,\'label\').$default;\n}\n\nfunction adi_image_tab_extend_detail($event,$step,$default,$rs) {\n// add the juicy bits to bottom of image edit form\n	$out = adi_image_tab_copy_alt();\n	$out .= adi_image_tab_timestamp($rs);\n	return $out;\n}\n\nfunction adi_image_tab_copy_alt() {\n// option to copy \"Alternate text\" to \"Caption\"\n	$copy_alt_caption = \'\';\n	$out =\n		graf(\n			sp.sp\n			.tag(adi_image_tab_gtxt(\'adi_copy_alt_caption\').\':\'.sp,\'label\')\n			.checkbox2(\'copy_alt_caption\',$copy_alt_caption)\n		);\n	return $out;\n}\n\nfunction adi_image_tab_timestamp($rs) {\n// input fields for changing timestamp\n	global $sort, $dir, $page, $search_method, $crit, $adi_image_tab_debug;\n\n	$id = gps(\'id\'); // image ID\n\n	// newly added image?\n	if (empty($id)) {\n		$id = adi_image_tab_get_new_id();\n	}\n\n	if ($id != \'\') {\n		if ($rs) { // $rs supplied by pluggable_ui\n			extract($rs);\n			$reset_time = \'\';\n\n			// date/time input fields code stolen from include/txp_article.php\n			$out =\n				tag(\n					graf(gTxt(\'timestamp\'),\' class=\"label\"\')\n					.graf(\n						sp.sp\n						.\'<span class=\"label\">\'.gtxt(\'date\').\'</span>\'.sp\n						.tsi(\'year\', \'%Y\', $uDate).\' /\'\n						.tsi(\'month\', \'%m\', $uDate).\' /\'\n						.tsi(\'day\', \'%d\', $uDate)\n						.\'<span class=\"label\">\'.sp.sp.gTxt(\'time\').\'</span>\'.sp\n						.tsi(\'hour\', \'%H\', $uDate).\' :\'\n						.tsi(\'minute\', \'%M\', $uDate).\' :\'\n						.tsi(\'second\', \'%S\', $uDate)\n						,\' class=\"date posted created\"\'\n					)\n					.graf(\n						sp.sp\n						.tag(gTxt(\'set_to_now\'),\'label\',\' for=\"reset_time\" class=\"reset_time-now\"\')\n						.sp.sp\n						.checkbox(\'reset_time\', \'1\', $reset_time, \'\', \'reset_time\')\n					)\n					,\'div\'\n					,\' class=\"adi_image_tab_timestamp\"\'\n				);\n\n			return $out;\n		}\n	}\n}\n\nfunction adi_image_tab_save() {\n// essentially a copy of include/txp_image.php - but without \'return to image_list()\' bit\n	global $txp_user,$adi_image_tab_debug;\n\n	extract(doSlash(gpsa(array(\'id\',\'category\',\'caption\',\'alt\',\'copy_alt_caption\'))));\n	$name = gps(\'name\');\n	$safename = doSlash($name);\n	$id = assert_int($id);\n\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n		return gTxt(\'restricted_area\');\n	}\n\n	if ($copy_alt_caption) // copy alternate text to caption\n		$caption = $alt;\n\n	safe_update(\"txp_image\",\"name = \'$safename\',category = \'$category\',alt = \'$alt\',caption = \'$caption\'\",\"id = $id\",$adi_image_tab_debug);\n\n	$message = gTxt(\'image_updated\', array(\'{name}\' => $name));\n\n	update_lastmod(); // TXP housekeeping\n\n	return $message;\n}\n\nfunction adi_image_tab_timestamp_save() {\n// save timestamp to database\n	global $txp_user,$adi_image_tab_debug;\n\n	$timestamp = gpsa(array(\'id\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'second\',\'reset_time\'));\n	extract(doSlash($timestamp));\n\n	$id = assert_int($id);\n\n	// make sure it\'s allowed\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n		return; // adi_image_tab_save() does same check & returns error message if required\n	}\n\n	if ($reset_time) {\n		$when_uploaded = \"date=now()\";\n		$when_ts = time();\n	}\n	else {\n		$date_error = FALSE;\n		if (!is_numeric($year) || !is_numeric($month) || !is_numeric($day) || !is_numeric($hour)  || !is_numeric($minute) || !is_numeric($second) ) {\n			$date_error = TRUE;\n		}\n		$ts = strtotime($year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second);\n		if ($ts === false || $ts === -1) {\n			$date_error = TRUE;\n		}\n		if ($date_error) {\n			return adi_image_tab_gtxt(\'adi_timestamp_invalid\');\n		}\n		$when = $when_ts = $ts - tz_offset($ts);\n		$when_uploaded = \"date=from_unixtime($when)\";\n	}\n\n	safe_update(\"txp_image\",\"$when_uploaded\",\"id = $id\",$adi_image_tab_debug);\n}\n\n\nfunction adi_image_tab_messagepane() {\n// generate messagepane & message\n	global $adi_image_tab_message,$adi_image_tab_txp450;\n\n	if ($adi_image_tab_message) {\n		if ($adi_image_tab_txp450) {\n			$message = implode(\' \',$adi_image_tab_message);\n			if (count($adi_image_tab_message) > 1) // assume it\'s a warning\n				$class = \' warning\';\n			else\n				$class = \' success\';\n			$html = \'<span id=\"message\" class=\"messageflash\'.$class.\'\">\'.$message.\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n\n$message_script = <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n		$(\'#messagepane\').append(\'$html\');\n		$(\"#messagepane\").css({\n			left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n		});\n		$(window).resize();\n	// -->\n	</script>\nEND_SCRIPT;\n\n			echo $message_script;\n		}\n		else { // pre-TXP 4.5.0\n			$message = implode(\' \',$adi_image_tab_message);\n			if (count($adi_image_tab_message) > 1) // assume there\'s a warning, so flash it\n\n$message .= <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n	$(document).ready( function(){\n				$(\'.adi_image_tab_message\').fadeOut(800).fadeIn(800);\n				$(\'.adi_image_tab_message\').fadeOut(800).fadeIn(800);\n			} )\n	// -->\n	</script>\nEND_SCRIPT;\n\n		echo tr(tda($message,\' class=\"adi_image_tab_message\"\'));\n\n		}\n	}\n}\n\nfunction adi_image_tab_bytesize($bytes) {\n// transform bytes into killer-bytes ...\n	if ($bytes < 1024) {\n		$size = $bytes.\' bytes\';\n	}\n	else if ($bytes < pow(1024,2)) {\n			$size = round($bytes / 1024, 2);\n			$size .= \' KB\';\n	}\n	else if ($bytes < pow(1024,3)) {\n			$size = round($bytes / pow(1024,2), 2);\n			$size .= \' MB\';\n	}\n	else {\n			$size = round($bytes / pow(1024,3), 2);\n			$size .= \' GB\';\n	}\n	return $size;\n}\n\nfunction adi_image_tab_get_new_id() {\n// work out image ID for newly added image (PROBABLY AN UNRELIABLE METHOD)\nglobal $adi_image_tab_debug;\n	$result = safe_query(\"SELECT `id` FROM `\".PFX.\"txp_image` ORDER BY id DESC LIMIT 1\",$adi_image_tab_debug); // get largest ID (last one added)\n	$row = mysql_fetch_row($result);\n	$id = $row[0];\n	if ($adi_image_tab_debug)\n		echo \"<br/>New added image ID = $id<br/>\";\n	return $id;\n}\n\nfunction adi_image_tab_options($event,$step) {\n// plugin options page\n	global $adi_image_tab_debug,$adi_image_tab_url,$textarray;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_image_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = adi_image_tab_gtxt(\'adi_textpack_fail\');\n		}\n	}\n\n	// generate page\n	if (!empty($message))\n		$message = \'adi_image_tab: <strong>\'.$message.\'</strong>\';\n	pagetop(\'adi_image_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_image_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(adi_image_tab_gtxt(\'adi_textpack_online\'),$adi_image_tab_url[\'textpack_download\']))\n		.graf(href(adi_image_tab_gtxt(\'adi_textpack_feedback\'),$adi_image_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_image_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_image_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_image_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', '/*\n	adi_image_tab - Enhanced image tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.2		- TXP 4.5-ified\n				- code tidy up\n				- fix: styling only generated for event=image tabs (thanks uli)\n				- enhancement: image delete button\n				- enhancement: thumbnail info\n				- changed: recent image list now stored as \"user\" pref\n		0.1.1	- fixed: errors after image deleted (thanks uli)\n				- fixed: spurious timestamp error message when save image directly after upload\n				- fixed: timestamp fields now visible after uploading image\n		0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $event,$step,$adi_image_tab_debug,$adi_image_tab_message,$adi_image_tab_txp450;\n\n	$adi_image_tab_debug = 0;\n\n	// using pluggable_ui (4.3.0) & lifecyle/plugin options (4.2.0), so Elvis should leave the building if need be\n	if (!version_compare(txp_version,\'4.3.0\',\'>=\')) return;\n\n	$adi_image_tab_txp450 = (version_compare(txp_version,\'4.4.1\',\'>\'));\n\n	adi_image_tab_init($event,$step);\n\n	// divert standard Image Tab step flow\n	if (($event == \'image\') && ($step == \'image_save\')) {\n		$adi_image_tab_message[] = adi_image_tab_save();\n		$adi_image_tab_timestamp_message = adi_image_tab_timestamp_save();\n		if ($adi_image_tab_timestamp_message)\n			$adi_image_tab_message[] = \' (\'.$adi_image_tab_timestamp_message.\')\';\n		$step = \'image_edit\'; // force TXP tab to go to image edit page, rather than back to image list\n	}\n}\n\nfunction adi_image_tab_init($event,$step) {\n\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_gtxt,$adi_image_tab_url,$adi_image_tab_prefs,$adi_image_tab_txp450;\n\n	// Textpack\n	$adi_image_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_image_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_image_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_image_tab_gtxt = array(\n		\'adi_copy_alt_caption\' => \'Copy alternate text to caption\',\n		\'adi_delete_image\' => \'Delete image\',\n		\'adi_recent_images\' => \'Recent images\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_timestamp_invalid\' => \'Invalid timestamp\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// plugin lifecycle\n	register_callback(\'adi_image_tab_lifecycle\',\'plugin_lifecycle.adi_image_tab\');\n\n	// preferences & defaults\n	$adi_image_tab_prefs = array(\n		\'adi_image_tab_recent\'			=> array(\'value\' => \'\',		\'input\' => \'text_input\'), // start off with clean slate (comma separated list of IDs - set to \'0\' to disable?)\n		\'adi_image_tab_recent_thumb\'	=> array(\'value\' => \'1\',	\'input\' => \'yesnoradio\'), // show thumbnails in recent images menu?\n		\'adi_image_tab_menu_limit\'		=> array(\'value\' => \'10\',	\'input\' => \'text_input\'), // max number of recent menu items\n	);\n	foreach ($adi_image_tab_prefs as $adi_image_tab_pref => $this_pref) {\n		$get_value = get_pref($adi_image_tab_pref,\'?\'); // returns \'?\' if not set (but beware of cacheing)\n		if ($get_value != \'?\')\n			$adi_image_tab_prefs[$adi_image_tab_pref][\'value\'] = $get_value;\n	}\n\n	// callbacks\n	register_callback(\'adi_image_tab\',\'image_ui\',\'image_edit\');\n	register_callback(\'adi_image_tab_extend_detail\',\'image_ui\',\'extend_detail_form\');\n	if ($adi_image_tab_txp450) // thumb image has its own little cupboard\n		register_callback(\'adi_image_tab_thumbnail\',\'image_ui\',\'thumbnail_image\');\n	else\n		register_callback(\'adi_image_tab_thumbnail\',\'image_ui\',\'thumbnail_edit\');\n\n	// style\n	if ($event == \'image\')\n		register_callback(\'adi_image_tab_style\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_image_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_image_tab\',$adi_image_tab_debug);\n	if ($adi_image_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_image_tab\',\'1,2,6\');\n		register_callback(\'adi_image_tab_options\',\'plugin_prefs.adi_image_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_image_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_image_tab_options\',\'adi_image_tab options\');\n		register_callback(\'adi_image_tab_options\',\'adi_image_tab_options\');\n	}\n\n	// single image\n	$id = gps(\'id\');\n\n	// multiple images\n	$selected = gps(\'selected\');\n\n	// update recent images list depending on $step\n	if (in_array($step,array(\'image_insert\',\'image_replace\',\'image_save\',\'thumbnail_insert\',\'thumbnail_create\',\'thumbnail_delete\',\'image_multi_edit\')))\n		if ($selected)\n			adi_image_tab_recent_list($selected);\n		else\n			adi_image_tab_recent_list($id);\n\n	if ($adi_image_tab_debug) {\n		echo \"Event=$event, Step=$step, ID=$id<br/>\";\n		if ($selected) {\n			echo \'Selected IDs:\';\n			dmp($selected);\n		}\n		echo \'<br/>$_POST:\';\n		dmp($_POST);\n	}\n}\n\nfunction adi_image_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_image_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n	global $adi_image_tab_debug,$adi_image_tab_prefs;\n\n	$result = \'?\';\n	if ($step == \'enabled\') {\n		// get old pref (i.e. \"system\" - if upgrading from v0.1)\n		$value = get_pref(\'adi_image_tab_recent\',\'?\');\n		if ($value != \"?\") {\n			// delete old pref\n			safe_delete(\'txp_prefs\',\"name = \'adi_image_tab_recent\'\",$adi_image_tab_debug);\n			// resave as \"user\" pref\n			$result = set_pref(\'adi_image_tab_recent\',$value,\'adi_image_tab\',2,$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'input\'],0,TRUE);\n		}\n	}\n	else if ($step == \'deleted\') {\n		// delete preferences\n		$result = safe_delete(\'txp_prefs\',\"name LIKE \'adi_image_tab_%\'\",$adi_image_tab_debug);\n		return $res;\n	}\n	if ($adi_image_tab_debug)\n		echo \"Event=$event Step=$step Result=$result\";\n}\n\nfunction adi_image_tab_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_image_tab_gtxt() are functionally equivalent\n	global $adi_image_tab_gtxt;\n\n	if (gTxt($phrase, $atts) == $phrase) // no TXP translation found\n		if (array_key_exists($phrase,$adi_image_tab_gtxt)) // adi translation found\n			return $adi_image_tab_gtxt[$phrase];\n		else // last resort\n			return $phrase;\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n\nfunction adi_image_tab_style() {\n// some style action\n	echo\n		\'<style type=\"text/css\">\n			.adi_image_tab_info { overflow:hidden; margin:0 0 0.5em 0; border-bottom:1px solid #ddd }\n			.adi_image_tab_info p { margin:0 0 0.3em 0 }\n			.adi_image_tab_timestamp .checkbox { margin:0 }\n			.adi_image_tab_timestamp input.edit { width:2.3em }\n			.adi_image_tab_timestamp input.year { width:3.3em }\n			.adi_image_tab_non_link { color:#c0c0c0 }\n			table#list td { padding-top:10px; padding-bottom:5px }\n			table#list .adi_image_tab_message { padding:0 0 0.5em 0; text-align:center }\n			table#list .adi_image_tab_menu li { margin:0 }\n			table#list tr.thumbnail-edit td { border-bottom:0 }\n			table#list #thumbnail_alter_form p.label { margin-bottom:0 }\n			table#list tr.thumbnail-edit p { margin-bottom:0 }\n			table#list tr.thumbnail-edit br { display:none }\n			.adi_image_tab_options { text-align:center }\n			.adi_image_tab_menu, .adi_image_tab_menu ul { list-style:none; float:left }\n			.adi_image_tab_menu a { display:block }\n			.adi_image_tab_menu li { float:left; margin:0 }\n			.adi_image_tab_menu li ul { padding:0; position:absolute; width:100px; left:-999em; z-index:999 }\n			.adi_image_tab_menu li:hover, .adi_image_tab_menu li.sfhover { position:static }\n			.adi_image_tab_menu li:hover ul, .adi_image_tab_menu li.sfhover ul { left:auto }\n			.adi_image_tab_menu li ul li { width:100px; margin:0; padding:0; border:solid #c0c0c0; border-width:0 1px 1px 1px; background-color:white }\n			.adi_image_tab_menu li ul li.adi_image_tab_menu_first { width:100px; margin:0; padding:0; border-top-width:1px }\n			.adi_image_tab_menu li ul li div { padding:2px 5px; color:#444 }\n			.adi_image_tab_menu li ul li a { padding:2px 5px; text-decoration:none; word-wrap:break-word; color:#444 }\n			.adi_image_tab_menu_thumbnail_height { height:50px }\n			.adi_image_tab_menu_thumbnail_width { width:90px }\n			.adi_image_tab_links { overflow:hidden }\n			.adi_image_tab_classic .adi_image_tab_prev { float:left }\n			.adi_image_tab_classic .adi_image_tab_next { float:left; margin-left:1em }\n			.adi_image_tab_classic .adi_image_tab_recent_menu { float:left; margin-left:8em }\n			.adi_image_tab_classic .adi_image_tab_images_menu { float:right }\n			.adi_image_tab_new .adi_image_tab_prev { float:left }\n			.adi_image_tab_new .adi_image_tab_next { float:right }\n			.adi_image_tab_new .adi_image_tab_recent_menu { float:left; margin-left:6em }\n			.adi_image_tab_new .adi_image_tab_images_menu { float:left; margin-left:6em }\n			/* TXP 4.5.0 tweaks */\n			.txp-body .adi_image_tab_links ul { margin-top:0; margin-bottom:0.5em; padding:0 }\n			.txp-body .adi_image_tab_timestamp input.year { width:3.7em }\n		</style>\';\n}\n\nfunction adi_image_tab($event,$step,$default) {\n// display image info & links\n// e.g. $event = article_ui, $step = image_edit\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_txp450;\n\n	$img_dir = $prefs[\'path_to_site\'].\'/\'.$prefs[\'img_dir\'];\n	$id = gps(\'id\');\n\n	// homegrown messages\n	adi_image_tab_messagepane();\n\n	// newly added image?\n	if (empty($id)) {\n		$id = adi_image_tab_get_new_id();\n		adi_image_tab_recent_list($id); // update recent list\n	}\n\n	// create image info & links\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\",$adi_image_tab_debug);\n	if ($rs) {\n		extract($rs);\n		if ($adi_image_tab_debug) {\n			echo \'This image:\';\n			dmp($rs);\n			echo \'getimagesize():\';\n			$path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$id.$ext;\n			dmp(getimagesize($path));\n		}\n		$filesize = filesize($img_dir.\'/\'.$id.$ext);\n		if ($adi_image_tab_txp450) // bugger me it\'s now semantic\n			$info = tag(\n				tag(gTxt(\'tag_image\').\' #\'.$id.\' (\'.$w.\' X \'.$h.\', \'.adi_image_tab_bytesize($filesize).\', \'.$author.\')\',\'h3\')\n				.adi_image_tab_links()\n				,\'div\'\n				,\' class=\"adi_image_tab_info\"\'\n			);\n		else\n			$info = tag(\n				graf(\'<b>\'.gTxt(\'tag_image\').\' #\'.$id.\'</b> (\'.$w.\' X \'.$h.\', \'.adi_image_tab_bytesize($filesize).\', \'.$author.\')\')\n				.adi_image_tab_links()\n				,\'div\'\n				,\' class=\"adi_image_tab_info\"\'\n			);\n\n		return $info.$default.adi_image_tab_delete_button($id);\n	}\n}\n\nfunction adi_image_tab_delete_button($image_id) {\n// delete button\n\n	return form(\n		tag(\n			fInput(\'submit\',\'do_something\',adi_image_tab_gtxt(\'adi_delete_image\'),\'smallerbox\',\"\",\'return verify(\\\'\'.gTxt(\'confirm_delete_popup\').\'\\\')\')\n			.eInput(\'image\').sInput(\'image_multi_edit\').hInput(\'edit_method\',\'delete\').hInput(\'selected[]\',$image_id)\n			,\'p\'\n		)\n		,\'\'\n		,\'\'\n		,\'post\'\n	);\n}\n\nfunction adi_image_tab_links() {\n// output useful image links\n	global $prefs,$adi_image_tab_debug,$adi_image_tab_prefs,$adi_image_tab_txp450;\n\n	extract(doSlash(gpsa(array(\'id\',\'sort\',\'dir\',\'search_method\',\'page\',\'crit\'))));\n\n	// get from prefs\n	$recent_list = $adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'];\n	$recent_limit = $adi_image_tab_prefs[\'adi_image_tab_menu_limit\'][\'value\'];\n\n	if ($adi_image_tab_debug) {\n		echo \'URL vars:\';\n		dmp(gpsa(array(\'id\',\'sort\',\'dir\',\'search_method\',\'page\',\'crit\')));\n	}\n\n	// mostly from include/txp_image.php\n	if ($sort === \'\') $sort = get_pref(\'image_sort_column\', \'id\');\n	if ($dir === \'\') $dir = get_pref(\'image_sort_dir\', \'desc\');\n	$dir = ($dir == \'asc\') ? \'asc\' : \'desc\';\n	switch ($sort) {\n		case \'name\':\n			$sort_sql = \'name \'.$dir;\n			break;\n		case \'thumbnail\':\n			$sort_sql = \'thumbnail \'.$dir.\', id asc\';\n			break;\n		case \'category\':\n			$sort_sql = \'category \'.$dir.\', id asc\';\n			break;\n		case \'date\':\n			$sort_sql = \'date \'.$dir.\', id asc\';\n			break;\n		case \'author\':\n			$sort_sql = \'author \'.$dir.\', id asc\';\n			break;\n		default:\n			$sort = \'id\';\n			$sort_sql = \'id \'.$dir;\n			break;\n	}\n	$criteria = 1; // ?\n	if ($search_method and $crit) {\n		$crit_escaped = doSlash(str_replace(array(\'\\\\\',\'%\',\'_\',\'\\\'\'), array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'), $crit));\n\n		$critsql = array(\n			\'id\'		=> \"ID in (\'\" .join(\"\',\'\", do_list($crit_escaped)). \"\')\",\n			\'name\'		=> \"name like \'%$crit_escaped%\'\",\n			\'category\' 	=> \"category like \'%$crit_escaped%\'\",\n			\'author\'	=> \"author like \'%$crit_escaped%\'\",\n			\'alt\'		=> \"alt like \'%$crit_escaped%\'\",\n			\'caption\'	=> \"caption like \'%$crit_escaped%\'\"\n		);\n\n		if (array_key_exists($search_method, $critsql)) {\n			$criteria = $critsql[$search_method];\n			$limit = 500;\n		}\n		else {\n			$search_method = \'\';\n			$crit = \'\';\n		}\n	}\n	else {\n		$search_method = \'\';\n		$crit = \'\';\n	}\n\n	// make up full list of image IDs\n	$rs = safe_rows(\'id\', \'txp_image\', \"$criteria order by $sort_sql\",$adi_image_tab_debug);\n	// convert array(0 => array(\'id\' => xx)) to array(0 => xx)\n	$id_list = array();\n	foreach ($rs as $index => $value)\n		$id_list[] = $value[\'id\'];\n\n	$link_preamble = \'index.php?event=image\'.a.\'step=image_edit\';\n	$link_postamble = a.\'sort=\'.$sort.a.\'dir=\'.$dir.a.\'page=\'.$page.a.\'search_method=\'.$search_method.a.\'crit=\'.$crit;\n\n	// prev & next links\n	$adi_image_tab_txp450 ? $prev_text = gTxt(\'prev\') : $prev_text = \'&#8249;\'.gTxt(\'prev\');\n	$adi_image_tab_txp450 ? $next_text = gTxt(\'next\') : $next_text = gTxt(\'next\').\'&#8250;\';\n	$this_key = array_search($id,$id_list);\n	$prev_key = $this_key - 1;\n	$next_key = $this_key + 1;\n	if ($prev_key < 0)\n		$prev_link = tag($prev_text,\'div\',\' class=\"adi_image_tab_prev adi_image_tab_non_link\"\');\n	else\n		$prev_link = adi_image_tab_list_markup($id_list[$prev_key],$prev_text,\'\',$link_preamble,$link_postamble,\'adi_image_tab_prev\');\n	if ($next_key > count($id_list) - 1)\n		$next_link = tag($next_text,\'div\',\' class=\"adi_image_tab_next adi_image_tab_non_link\"\');\n	else\n		$next_link = adi_image_tab_list_markup($id_list[$next_key],$next_text,\'\',$link_preamble,$link_postamble,\'adi_image_tab_next\');\n\n	// link to Images tab & dropdown menu\n	$start_key = $this_key - $recent_limit / 2; // want current image in middle of list\n	if ($start_key < 0)\n		$start_key = 0;\n	$image_list = array_slice($id_list,$start_key,$recent_limit);\n	if (count($image_list) < $recent_limit)\n		$image_list = array_slice($id_list,$recent_limit*-1,$recent_limit);\n	$image_menu = adi_image_tab_list_markup(implode(\',\',$image_list),gTxt(\'images\'),\'?event=image\',$link_preamble,$link_postamble,\'adi_image_tab_images_menu\');\n\n	// recent images dropdown menu\n	$recent_menu = adi_image_tab_list_markup($recent_list,adi_image_tab_gtxt(\'adi_recent_images\'),\'#\',$link_preamble,$link_postamble,\'adi_image_tab_recent_menu\');\n\n	if ($adi_image_tab_debug) {\n		echo \'this_key=\'.$this_key.\', prev_key=\'.$prev_key.\', next_key=\'.$next_key.\', start=\'.$start_key.\'<br/>\';\n		echo \'<br/>$id_list:\';\n		dmp($id_list);\n		echo \'recent list: \'.$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'].\'<br/>\';\n		echo \'<br/>$image_list:\';\n		dmp($image_list);\n	}\n\n	if (true)\n		$out = tag($prev_link.$next_link.$recent_menu.$image_menu,\'div\',\' class=\"adi_image_tab_links adi_image_tab_classic\"\');\n	else\n		$out = tag($prev_link.$recent_menu.$image_menu.$next_link,\'div\',\' class=\"adi_image_tab_links adi_image_tab_new\"\');\n\n	return $out;\n}\n\nfunction adi_image_tab_recent_list($id=\'\') {\n// update or return recent images list\n	global $adi_image_tab_prefs,$adi_image_tab_debug;\n\n	// get from prefs\n	$recent_list = $adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'];\n	$recent_limit = $adi_image_tab_prefs[\'adi_image_tab_menu_limit\'][\'value\'];\n\n	if ($id == \'\') // simply return current list\n		return $recent_list;\n\n	if ($recent_list)\n		$recents = explode(\',\',$recent_list);\n	else // don\'t want empty value in array\n		$recents = array();\n\n	// update recent list\n	if (is_array($id))\n		$recents = array_merge($id,$recents); // add multiple IDs to front of list\n	else if ($id == \'0\')\n		; // set list to \'0\', i.e. disabled ... if required one day\n	else // single id\n		array_unshift($recents,$id); // add single ID to front of list\n	// remove duplicates\n	$recents = array_unique($recents);\n	// trim to length\n	$recents = array_slice($recents, 0, $recent_limit);\n\n	// save in preferences\n	$new_list = implode(\',\',$recents);\n	$res = set_pref(\'adi_image_tab_recent\',$new_list,\'adi_image_tab\',2,$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'input\'],0,TRUE);\n	$adi_image_tab_prefs[\'adi_image_tab_recent\'][\'value\'] = $new_list;\n\n	if ($adi_image_tab_debug) {\n		if (is_array($id))\n			$id = implode(\',\',$id);\n		echo \'Recent list:<br/>update id=\'.$id.\'<br/>\';\n		echo \'old list:\'.$recent_list.\'<br/>\';\n		echo \'new list:\'.$new_list.\'<br/>\';\n	}\n}\n\nfunction adi_image_tab_list_markup($this_list,$this_name,$top_link,$link_preamble,$link_postamble,$extra_class=\'\') {\n// dropdown list of images\n	global $adi_image_tab_prefs,$adi_image_tab_debug,$prefs;\n\n	if ($this_list) {\n		$li = \'\';\n		$these = explode(\',\',$this_list);\n		foreach ($these as $index => $image_id) {\n			$this_item = \'\';\n			// get some image & thumbnail details\n			$row = safe_row(\'name,ext,alt,caption,thumbnail,thumb_w,thumb_h\',\'txp_image\',\'id=\'.$image_id);\n			extract($row);\n			if ($row) { // image found in database\n				// populate menu with thumbnail (if wanted & it exists)\n				if ($adi_image_tab_prefs[\'adi_image_tab_recent_thumb\'][\'value\']) {\n					if ($thumbnail) {\n						$thumb_path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$image_id.\'t\'.$ext;\n						// work whether need to adjust thumbnail height or width\n						if (!$thumb_h) // thumb_w & thumb_h introduced in 4.2.0, dimensions of thumbs created prior to that are set to zero in database!\n							list($thumb_w,$thumb_h,$type,$attr) = getimagesize($thumb_path); // measure actual thumbnail file\n						$css_width = $thumb_w * 50 / $thumb_h;\n						if ($css_width > 90)\n							$class = \'adi_image_tab_menu_thumbnail_width\';\n						else\n							$class = \'adi_image_tab_menu_thumbnail_height\';\n						// image markup\n						$this_item = \'<img src=\"\'.$thumb_path.\'\" alt=\"\'.$alt.\'\" class=\"\'.$class.\'\" />\';\n					}\n				}\n			}\n			else // image not found in db - probably deleted\n				$this_item = gTxt(\'unknown_image\');\n			// find something else useful to put in menu\n			if (!$this_item)\n				$this_item = $alt;\n			if (!$this_item)\n				$this_item = $name;\n			// class\n			if ($index)\n				$class = \'\';\n			else\n				$class = \'adi_image_tab_menu_first\';\n			$class = \' class=\"\'.$class.\'\"\';\n			// menu item\n			if ($row) // image found in database\n				$li .= tag(\n					\'<a href=\"\'.$link_preamble.a.\'id=\'.$image_id.$link_postamble.\'\">\'.\'#\'.$image_id.\'<br/>\'.$this_item.\'</a>\'\n					,\'li\'\n					,$class\n					);\n			else\n				$li .= tag(\n					tag(\'#\'.$image_id.\'<br/>\'.$this_item,\'div\')\n					,\'li\'\n					,$class\n					);\n		}\n		// class for image menu\n		$class = \'adi_image_tab_menu\';\n		if ($extra_class)\n			$class = $class.\' \'.$extra_class;\n		if (empty($top_link)) // set top link to first image in list (must be prev/next \"list\")\n			$top_link = $link_preamble.a.\'id=\'.$these[0].$link_postamble;\n		$list_preamble = \'<ul class=\"\'.$class.\'\"><li><a href=\"\'.$top_link.\'\">\'.$this_name.\'</a>\';\n		$list_postamble = \'</li></ul>\';\n		return $list_preamble.tag($li,\'ul\').$list_postamble;\n	}\n	else\n		return graf($this_name,\' class=\"adi_image_tab_non_link\"\');\n}\n\nfunction adi_image_tab_thumbnail($event,$step,$default,$rs) {\n// add thumbnail title & info\n	global $prefs,$adi_image_tab_txp450;\n\n	extract($rs);\n	$img_dir = $prefs[\'path_to_site\'].\'/\'.$prefs[\'img_dir\'];\n	$out = \'\';\n	if ($thumbnail) {\n		$thumb_path = \'http://\'.$prefs[\'siteurl\'].\'/\'.$prefs[\'img_dir\'].\'/\'.$id.\'t\'.$ext;\n		if (!$thumb_h) // thumb_w & thumb_h introduced in 4.2.0, dimensions of thumbs created prior to that are set to zero in database!\n			list($thumb_w,$thumb_h,$type,$attr) = getimagesize($thumb_path); // measure actual thumbnail file\n		$filesize = filesize($img_dir.\'/\'.$id.\'t\'.$ext);\n		$out = gTxt(\'thumbnail\').\' (\'.$thumb_w.\' X \'.$thumb_h.\', \'.adi_image_tab_bytesize($filesize).\')\';\n	}\n	if ($adi_image_tab_txp450)\n		return tag($out,\'h3\').$default;\n	else\n		return tag($out,\'label\').$default;\n}\n\nfunction adi_image_tab_extend_detail($event,$step,$default,$rs) {\n// add the juicy bits to bottom of image edit form\n	$out = adi_image_tab_copy_alt();\n	$out .= adi_image_tab_timestamp($rs);\n	return $out;\n}\n\nfunction adi_image_tab_copy_alt() {\n// option to copy \"Alternate text\" to \"Caption\"\n	$copy_alt_caption = \'\';\n	$out =\n		graf(\n			sp.sp\n			.tag(adi_image_tab_gtxt(\'adi_copy_alt_caption\').\':\'.sp,\'label\')\n			.checkbox2(\'copy_alt_caption\',$copy_alt_caption)\n		);\n	return $out;\n}\n\nfunction adi_image_tab_timestamp($rs) {\n// input fields for changing timestamp\n	global $sort, $dir, $page, $search_method, $crit, $adi_image_tab_debug;\n\n	$id = gps(\'id\'); // image ID\n\n	// newly added image?\n	if (empty($id)) {\n		$id = adi_image_tab_get_new_id();\n	}\n\n	if ($id != \'\') {\n		if ($rs) { // $rs supplied by pluggable_ui\n			extract($rs);\n			$reset_time = \'\';\n\n			// date/time input fields code stolen from include/txp_article.php\n			$out =\n				tag(\n					graf(gTxt(\'timestamp\'),\' class=\"label\"\')\n					.graf(\n						sp.sp\n						.\'<span class=\"label\">\'.gtxt(\'date\').\'</span>\'.sp\n						.tsi(\'year\', \'%Y\', $uDate).\' /\'\n						.tsi(\'month\', \'%m\', $uDate).\' /\'\n						.tsi(\'day\', \'%d\', $uDate)\n						.\'<span class=\"label\">\'.sp.sp.gTxt(\'time\').\'</span>\'.sp\n						.tsi(\'hour\', \'%H\', $uDate).\' :\'\n						.tsi(\'minute\', \'%M\', $uDate).\' :\'\n						.tsi(\'second\', \'%S\', $uDate)\n						,\' class=\"date posted created\"\'\n					)\n					.graf(\n						sp.sp\n						.tag(gTxt(\'set_to_now\'),\'label\',\' for=\"reset_time\" class=\"reset_time-now\"\')\n						.sp.sp\n						.checkbox(\'reset_time\', \'1\', $reset_time, \'\', \'reset_time\')\n					)\n					,\'div\'\n					,\' class=\"adi_image_tab_timestamp\"\'\n				);\n\n			return $out;\n		}\n	}\n}\n\nfunction adi_image_tab_save() {\n// essentially a copy of include/txp_image.php - but without \'return to image_list()\' bit\n	global $txp_user,$adi_image_tab_debug;\n\n	extract(doSlash(gpsa(array(\'id\',\'category\',\'caption\',\'alt\',\'copy_alt_caption\'))));\n	$name = gps(\'name\');\n	$safename = doSlash($name);\n	$id = assert_int($id);\n\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n		return gTxt(\'restricted_area\');\n	}\n\n	if ($copy_alt_caption) // copy alternate text to caption\n		$caption = $alt;\n\n	safe_update(\"txp_image\",\"name = \'$safename\',category = \'$category\',alt = \'$alt\',caption = \'$caption\'\",\"id = $id\",$adi_image_tab_debug);\n\n	$message = gTxt(\'image_updated\', array(\'{name}\' => $name));\n\n	update_lastmod(); // TXP housekeeping\n\n	return $message;\n}\n\nfunction adi_image_tab_timestamp_save() {\n// save timestamp to database\n	global $txp_user,$adi_image_tab_debug;\n\n	$timestamp = gpsa(array(\'id\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'second\',\'reset_time\'));\n	extract(doSlash($timestamp));\n\n	$id = assert_int($id);\n\n	// make sure it\'s allowed\n	$author = fetch(\'author\', \'txp_image\', \'id\', $id);\n	if (!has_privs(\'image.edit\') && !($author == $txp_user && has_privs(\'image.edit.own\'))) {\n		return; // adi_image_tab_save() does same check & returns error message if required\n	}\n\n	if ($reset_time) {\n		$when_uploaded = \"date=now()\";\n		$when_ts = time();\n	}\n	else {\n		$date_error = FALSE;\n		if (!is_numeric($year) || !is_numeric($month) || !is_numeric($day) || !is_numeric($hour)  || !is_numeric($minute) || !is_numeric($second) ) {\n			$date_error = TRUE;\n		}\n		$ts = strtotime($year.\'-\'.$month.\'-\'.$day.\' \'.$hour.\':\'.$minute.\':\'.$second);\n		if ($ts === false || $ts === -1) {\n			$date_error = TRUE;\n		}\n		if ($date_error) {\n			return adi_image_tab_gtxt(\'adi_timestamp_invalid\');\n		}\n		$when = $when_ts = $ts - tz_offset($ts);\n		$when_uploaded = \"date=from_unixtime($when)\";\n	}\n\n	safe_update(\"txp_image\",\"$when_uploaded\",\"id = $id\",$adi_image_tab_debug);\n}\n\n\nfunction adi_image_tab_messagepane() {\n// generate messagepane & message\n	global $adi_image_tab_message,$adi_image_tab_txp450;\n\n	if ($adi_image_tab_message) {\n		if ($adi_image_tab_txp450) {\n			$message = implode(\' \',$adi_image_tab_message);\n			if (count($adi_image_tab_message) > 1) // assume it\'s a warning\n				$class = \' warning\';\n			else\n				$class = \' success\';\n			$html = \'<span id=\"message\" class=\"messageflash\'.$class.\'\">\'.$message.\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n\n$message_script = <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n		$(\'#messagepane\').append(\'$html\');\n		$(\"#messagepane\").css({\n			left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n		});\n		$(window).resize();\n	// -->\n	</script>\nEND_SCRIPT;\n\n			echo $message_script;\n		}\n		else { // pre-TXP 4.5.0\n			$message = implode(\' \',$adi_image_tab_message);\n			if (count($adi_image_tab_message) > 1) // assume there\'s a warning, so flash it\n\n$message .= <<<END_SCRIPT\n	<script type=\"text/javascript\">\n	<!--\n	$(document).ready( function(){\n				$(\'.adi_image_tab_message\').fadeOut(800).fadeIn(800);\n				$(\'.adi_image_tab_message\').fadeOut(800).fadeIn(800);\n			} )\n	// -->\n	</script>\nEND_SCRIPT;\n\n		echo tr(tda($message,\' class=\"adi_image_tab_message\"\'));\n\n		}\n	}\n}\n\nfunction adi_image_tab_bytesize($bytes) {\n// transform bytes into killer-bytes ...\n	if ($bytes < 1024) {\n		$size = $bytes.\' bytes\';\n	}\n	else if ($bytes < pow(1024,2)) {\n			$size = round($bytes / 1024, 2);\n			$size .= \' KB\';\n	}\n	else if ($bytes < pow(1024,3)) {\n			$size = round($bytes / pow(1024,2), 2);\n			$size .= \' MB\';\n	}\n	else {\n			$size = round($bytes / pow(1024,3), 2);\n			$size .= \' GB\';\n	}\n	return $size;\n}\n\nfunction adi_image_tab_get_new_id() {\n// work out image ID for newly added image (PROBABLY AN UNRELIABLE METHOD)\nglobal $adi_image_tab_debug;\n	$result = safe_query(\"SELECT `id` FROM `\".PFX.\"txp_image` ORDER BY id DESC LIMIT 1\",$adi_image_tab_debug); // get largest ID (last one added)\n	$row = mysql_fetch_row($result);\n	$id = $row[0];\n	if ($adi_image_tab_debug)\n		echo \"<br/>New added image ID = $id<br/>\";\n	return $id;\n}\n\nfunction adi_image_tab_options($event,$step) {\n// plugin options page\n	global $adi_image_tab_debug,$adi_image_tab_url,$textarray;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_image_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = adi_image_tab_gtxt(\'adi_textpack_fail\');\n		}\n	}\n\n	// generate page\n	if (!empty($message))\n		$message = \'adi_image_tab: <strong>\'.$message.\'</strong>\';\n	pagetop(\'adi_image_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_image_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(adi_image_tab_gtxt(\'adi_textpack_online\'),$adi_image_tab_url[\'textpack_download\']))\n		.graf(href(adi_image_tab_gtxt(\'adi_textpack_feedback\'),$adi_image_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_image_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_image_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_image_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', 'a45c6a499d60e06279ba73dfdc5cb581', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_join', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Join stuff together', '<h1><strong>adi_join</strong> &#8211; Join stuff together</h1>\n\n<p>This plugin processes lines of code &amp; joins their output together with separators.</p>\n\n<p>Useful for creating comma-separated lists of data.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Simply put the <strong>adi_join</strong> tag around the contents you need joined:</p>\n\n<pre><code>&lt;txp:adi_join&gt;\n	...statement 1...\n	...statement 2...\n	...statement 3...\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>and, by default, the output will be:</p>\n\n<p><code>statement 1 output, statement 2 output, statement 3 output</code></p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>separator=&quot;text&quot;</code></p>\n\n<p>- the glue used to join your data together. Default = “, “.</p>\n\n<p><code>last_separator=&quot;text&quot;</code></p>\n\n<p>- if you need a different separator for the last one. Default =”“  (none).</p>\n\n<p><code>delimiter=&quot;text&quot;</code></p>\n\n<p>- the delimiter to distinguish the different chunks of the content. Default = newline.</p>\n\n<p><code>ignore=&quot;boolean&quot;</code></p>\n\n<p>- set to “1” to ensure that contained tags are not preparsed.  Default = “0” (parse contained tags).</p>\n\n<p><code>unique=&quot;boolean&quot;</code></p>\n\n<p>- set to “1” to strip out duplicates in the contained content.  Default = “0” (allow duplicates).</p>\n\n<p><code>no_widow=&quot;boolean&quot;</code></p>\n\n<p>- set to “1” to ensure that the last word of the sequence doesn&#8217;t appear on its own line.  Default = “0” (allow widows).</p>\n\n<p><code>prefix=&quot;text&quot;</code></p>\n\n<p>- text to insert immediately <em>before</em> each item. Default = &#8220;&#8221; (no text inserted).</p>\n\n<p><code>suffix=&quot;text&quot;</code></p>\n\n<p>- text to insert immediately <em>after</em> each item. Default = &#8220;&#8221; (no text inserted).</p>\n\n<p><code>sort=&quot;sort type&quot;</code></p>\n\n<p>- sort the items. Allowed sort types: ‘sort’,‘rsort’,‘natsort’,‘natcasesort’, ‘asc’, ‘desc’ (‘asc’ &amp; ‘desc’ are equivalent to ‘sort’ &amp; ‘rsort’). Default = not sorted.</p>\n\n<p><code>itemtag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around each individual item in list, and their prefix/suffixes if present. Default = &#8220;&#8221; (none).</p>\n\n<p><code>label=&quot;text&quot;</code></p>\n\n<p>- some text to precede the list. Default = &#8220;&#8221; (no label). <em>If there is no output then no label is generated.</em></p>\n\n<p><code>labeltag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the label. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>label_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the label&#8217;s tag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>label_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the label&#8217;s tag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the whole list. Default = &#8220;&#8221; (none).</p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- the <span class=\"caps\">CSS</span> class to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>html_id=&quot;id name&quot;</code></p>\n\n<p>- the <span class=\"caps\">HTML</span> ID to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p>To output article categories as a list:</p>\n\n<pre><code>&lt;txp:adi_join&gt;\n	&lt;txp:category1 /&gt;\n	&lt;txp:category2 /&gt;\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>If both categories are set, then the output is:</p>\n\n<p><code>category 1 value, category 2 value</code></p>\n\n<p>If only one catagory is set then only that category value is output. If neither categories are set then nothing is output.</p>\n\n<p>To output a series of variables separated by commas but with an ampersand at the end:</p>\n\n<pre><code>&lt;txp:adi_join last_separator=&quot; &amp; &quot;&gt;\n	&lt;txp:variable name=&quot;var1&quot; /&gt;\n	&lt;txp:variable name=&quot;var2&quot; /&gt;\n	&lt;txp:variable name=&quot;var3&quot; /&gt;\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>If all variable are non-blank then the output will be:</p>\n\n<p><code>var1 value, var2 value &amp; var3 value</code></p>\n\n<p>And even if you just want to prevent a widow on something, then this:</p>\n\n<pre><code>&lt;txp:adi_join no_widow=&quot;1&quot;&gt;\n	&lt;txp:site_slogan /&gt;\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>will output:</p>\n\n<p><code>My pithy&amp;nbsp;slogan</code></p>\n\n<p>To list items in brackets, then this:</p>\n\n<pre><code>&lt;txp:adi_join prefix=&quot;(&quot; suffix=&quot;)&quot;&gt;\n	one\n	two\n	three\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>will output:</p>\n\n<p><code>(one), (two), (three)</code></p>\n\n<p>To wrap the whole list as well as individual items in tags, then this:</p>\n\n<pre><code>&lt;txp:adi_join wraptag=&quot;p&quot; itemtag=&quot;em&quot;&gt;\n	first\n	second\n	third\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>will output:</p>\n\n<p><code>&lt;p&gt;&lt;em&gt;first&lt;/em&gt;, &lt;em&gt;second&lt;/em&gt;, &lt;em&gt;third&lt;/em&gt;&lt;/p&gt;</code></p>\n\n<p>Using a different delimiter:</p>\n\n<pre><code>&lt;txp:adi_join separator=&quot; - &quot; delimiter=&quot;,&quot; wraptag=&quot;p&quot;&gt;\n	one,\n	two, three\n	,&lt;txp:site_slogan/&gt;\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>wil output:</p>\n\n<p><code>&lt;p&gt;one - two - three - My pithy slogan&lt;/p&gt;</code></p>\n\n<p>To stop preparsing content, for example if there are delimiters inside tags which you don&#8217;t want to be interpreted as such:</p>\n\n<pre><code>&lt;txp:variable name=&quot;comma_inside&quot; value=&quot;comma,sandwich&quot; /&gt;\n&lt;txp:adi_join separator=&quot; - &quot; delimiter=&quot;,&quot; ignore=&quot;1&quot; wraptag=&quot;p&quot;&gt;\n	one,\n	two, three\n	,&lt;txp:site_slogan/&gt;,\n	&lt;txp:variable name=&quot;comma_inside&quot; /&gt;\n&lt;/txp:adi_join&gt;</code></pre>\n\n<p>will output:</p>\n\n<p><code>&lt;p&gt;one - two - three - My pithy slogan - comma,sandwich&lt;/p&gt;</code></p>\n\n<p>Without the <code>ignore=&quot;1&quot;</code> attribute, the output would&#8217;ve been:</p>\n\n<p><code>&lt;p&gt;one - two - three - My pithy slogan - comma - sandwich&lt;/p&gt;</code></p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=37064\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_join\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_join - Join stuff together\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- new attributes: \'delimiter\', \'ignore\' (for pieman)\n			- new attribute: \'unique\'\n			- TXP 4.6 tag registration\n	0.2		- new attributes: \'prefix\', \'suffix\', \'itemtag\' (for jakob)\n			- new attribute: \'sort\' (for milosevic)\n			- new attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_join\')\n	;\n}\n\nfunction adi_join($atts,$thing=NULL) {\n\n	extract(lAtts(array(\n		\'delimiter\'		=> \'\',		// delimiter (default = newline)\n		\'ignore\'		=> \'0\',		// ignore delimiters embedded within tag\'s output (i.e. don\'t pre-parse)\n		\'separator\'		=> \', \',	// default \"comma\" separator\n		\'last_separator\'=> \'\',		// & finally - e.g. \" & \"\n		\'unique\'		=> \'0\',		// strip duplicates?\n		\'no_widow\'		=> \'0\',		// widow prevention\n		\'prefix\'		=> \'\',		// text to go before each item e.g. \"(\"\n		\'suffix\'		=> \'\',		// text to go after each item e.g. \")\"\n		\'itemtag\'		=> \'\',		// tag to put around each prefix-item-suffix\n		\'sort\'			=> \'\',		// allowed: \'sort\',\'rsort\',\'natsort\',\'natcasesort\' (standard PHP functions)\n		\'label\'			=> \'\',		// label string to precede list\n		\'labeltag\'		=> \'\',		// tag to wrap around label\n		\'label_class\'	=> \'\',		// CSS class for label\'s tag\n		\'label_id\'		=> \'\',		// HTML ID for label\'s tag\n		\'wraptag\'		=> \'\',		// the wraptag\n		\'class\'			=> \'\',		// class for wraptag\n		\'html_id\'		=> \'\',		// HTML id for wraptag\n		\'debug\'			=> 0,\n	), $atts));\n\n	// massage attributes\n	if ($delimiter == \'\')\n		$delimiter = \"\\n\"; // newline\n	if ($last_separator == \'\')\n		$last_separator = $separator;\n\n	// HTML\n	$separator = htmlentities($separator);\n	$last_separator = htmlentities($last_separator);\n\n	// split contents into separate items\n	if ($ignore) { // post-parse\n		$new_thing = explode($delimiter,$thing);\n		$lines = doArray($new_thing,\'parse\');\n	}\n	else // pre-parse\n		$lines = explode($delimiter,parse($thing));\n\n	// get rid of all the trimmings\n	$trimmed_items = array();\n	foreach ($lines as $line) {\n		$line = trim($line);\n		if ($line)\n			$trimmed_items[] = $line;\n	}\n\n	// all sorts\n	if ($sort) {\n		$allowed_sorts = array(\'sort\',\'rsort\',\'natsort\',\'natcasesort\',\'asc\',\'desc\');\n		if (array_search($sort,$allowed_sorts) !== FALSE) {\n			if ($sort == \'asc\') $sort = \'sort\';\n			if ($sort == \'desc\') $sort = \'rsort\';\n			$sort($trimmed_items);\n		}\n	}\n\n	// one of a kind\n	if ($unique)\n		$trimmed_items = array_unique($trimmed_items);\n\n	if ($debug)\n		dmp($trimmed_items);\n\n	// prefix/suffix\n	if ($prefix || $suffix)\n		foreach ($trimmed_items as $index => $item)\n			$trimmed_items[$index] = $prefix.$item.$suffix;\n\n	// item tags\n	if ($itemtag)\n		foreach ($trimmed_items as $index => $item)\n			$trimmed_items[$index] = tag($item,$itemtag);\n\n	// carve off last item\n	$last_item = array_pop($trimmed_items);\n\n	// \"separator\" separated items\n	$out = implode($separator,$trimmed_items);\n\n	// last separator\n	if ($out)\n		$out .= $last_separator;\n\n	// reunification\n	$out .= $last_item;\n\n	// no widows?\n	if ($no_widow && (strrpos($out,\' \') !== FALSE)) // no widows wanted & there\'s a gap to fill\n		$out = substr_replace($out,\'&nbsp;\',strrpos($out,\' \'),1); // let\'s stick together (& let\'s hope the last gap is only one space wide)\n\n	// wrap it all up\n	if ($out) {\n		if ($label)\n			$labeltag ?\n				$out = doTag($label,$labeltag,$label_class,\'\',$label_id).$out :\n				$out = $label.$out;\n		if ($wraptag)\n			return doTag($out,$wraptag,$class,\'\',$html_id);\n		else\n			return $out;\n	}\n\n}\n', '\n/*\n	adi_join - Join stuff together\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- new attributes: \'delimiter\', \'ignore\' (for pieman)\n			- new attribute: \'unique\'\n			- TXP 4.6 tag registration\n	0.2		- new attributes: \'prefix\', \'suffix\', \'itemtag\' (for jakob)\n			- new attribute: \'sort\' (for milosevic)\n			- new attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_join\')\n	;\n}\n\nfunction adi_join($atts,$thing=NULL) {\n\n	extract(lAtts(array(\n		\'delimiter\'		=> \'\',		// delimiter (default = newline)\n		\'ignore\'		=> \'0\',		// ignore delimiters embedded within tag\'s output (i.e. don\'t pre-parse)\n		\'separator\'		=> \', \',	// default \"comma\" separator\n		\'last_separator\'=> \'\',		// & finally - e.g. \" & \"\n		\'unique\'		=> \'0\',		// strip duplicates?\n		\'no_widow\'		=> \'0\',		// widow prevention\n		\'prefix\'		=> \'\',		// text to go before each item e.g. \"(\"\n		\'suffix\'		=> \'\',		// text to go after each item e.g. \")\"\n		\'itemtag\'		=> \'\',		// tag to put around each prefix-item-suffix\n		\'sort\'			=> \'\',		// allowed: \'sort\',\'rsort\',\'natsort\',\'natcasesort\' (standard PHP functions)\n		\'label\'			=> \'\',		// label string to precede list\n		\'labeltag\'		=> \'\',		// tag to wrap around label\n		\'label_class\'	=> \'\',		// CSS class for label\'s tag\n		\'label_id\'		=> \'\',		// HTML ID for label\'s tag\n		\'wraptag\'		=> \'\',		// the wraptag\n		\'class\'			=> \'\',		// class for wraptag\n		\'html_id\'		=> \'\',		// HTML id for wraptag\n		\'debug\'			=> 0,\n	), $atts));\n\n	// massage attributes\n	if ($delimiter == \'\')\n		$delimiter = \"\\n\"; // newline\n	if ($last_separator == \'\')\n		$last_separator = $separator;\n\n	// HTML\n	$separator = htmlentities($separator);\n	$last_separator = htmlentities($last_separator);\n\n	// split contents into separate items\n	if ($ignore) { // post-parse\n		$new_thing = explode($delimiter,$thing);\n		$lines = doArray($new_thing,\'parse\');\n	}\n	else // pre-parse\n		$lines = explode($delimiter,parse($thing));\n\n	// get rid of all the trimmings\n	$trimmed_items = array();\n	foreach ($lines as $line) {\n		$line = trim($line);\n		if ($line)\n			$trimmed_items[] = $line;\n	}\n\n	// all sorts\n	if ($sort) {\n		$allowed_sorts = array(\'sort\',\'rsort\',\'natsort\',\'natcasesort\',\'asc\',\'desc\');\n		if (array_search($sort,$allowed_sorts) !== FALSE) {\n			if ($sort == \'asc\') $sort = \'sort\';\n			if ($sort == \'desc\') $sort = \'rsort\';\n			$sort($trimmed_items);\n		}\n	}\n\n	// one of a kind\n	if ($unique)\n		$trimmed_items = array_unique($trimmed_items);\n\n	if ($debug)\n		dmp($trimmed_items);\n\n	// prefix/suffix\n	if ($prefix || $suffix)\n		foreach ($trimmed_items as $index => $item)\n			$trimmed_items[$index] = $prefix.$item.$suffix;\n\n	// item tags\n	if ($itemtag)\n		foreach ($trimmed_items as $index => $item)\n			$trimmed_items[$index] = tag($item,$itemtag);\n\n	// carve off last item\n	$last_item = array_pop($trimmed_items);\n\n	// \"separator\" separated items\n	$out = implode($separator,$trimmed_items);\n\n	// last separator\n	if ($out)\n		$out .= $last_separator;\n\n	// reunification\n	$out .= $last_item;\n\n	// no widows?\n	if ($no_widow && (strrpos($out,\' \') !== FALSE)) // no widows wanted & there\'s a gap to fill\n		$out = substr_replace($out,\'&nbsp;\',strrpos($out,\' \'),1); // let\'s stick together (& let\'s hope the last gap is only one space wide)\n\n	// wrap it all up\n	if ($out) {\n		if ($label)\n			$labeltag ?\n				$out = doTag($label,$labeltag,$label_class,\'\',$label_id).$out :\n				$out = $label.$out;\n		if ($wraptag)\n			return doTag($out,$wraptag,$class,\'\',$html_id);\n		else\n			return $out;\n	}\n\n}\n', 'f3315cbfd252a7ed4782342211601daa', '', '', 0, 5, 0),
('adi_link', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.5', 'Enhanced links', '<h1><strong>adi_link</strong> &#8211; Enhanced links</h1>\n\n<p>This plugin provides a new tag, <code>&lt;txp:adi_link /&gt;</code>, which will output links from various sources, with custom attributes.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p><strong>adi_link</strong> can be used as follows:</p>\n\n<ul>\n	<li>as a single tag: <code>&lt;txp:adi_link /&gt;</code></li>\n	<li>as a container tag: <code>&lt;txp:adi_link&gt; ... &lt;/txp:adi_link&gt;</code></li>\n</ul>\n\n<p>in Pages, Forms and Articles.</p>\n\n<h2><strong>Which link?</strong></h2>\n\n<p><strong>adi_link</strong> will take a <span class=\"caps\">URL</span> from a number of sources, depending where it is used &amp; which attributes are specified.</p>\n\n<p>The following can be used to explicitly specify a <span class=\"caps\">URL</span>:</p>\n\n<ul>\n	<li><code>id</code> attribute</li>\n	<li><code>article_id</code> attribute</li>\n	<li><code>section</code> attribute</li>\n	<li><code>category</code> attribute</li>\n	<li><code>url</code> attribute</li>\n	<li>container tag mode &#8211; <strong>adi_link</strong> tag encloses the <span class=\"caps\">URL</span></li>\n</ul>\n\n<p>Alternatively, <strong>adi_link</strong> will take a <span class=\"caps\">URL</span> from context:</p>\n\n<ul>\n	<li>Link form &#8211; <span class=\"caps\">URL</span> taken from current link</li>\n	<li>Article form &#8211; <span class=\"caps\">URL</span> taken from link ID in “Article link” custom field (<em>must be manually added</em>)</li>\n</ul>\n\n<p>The following can be used to modify a <span class=\"caps\">URL</span>:</p>\n\n<ul>\n	<li><code>anchor</code> attribute</li>\n	<li><code>urlvars</code> attribute</li>\n	<li><code>suppress_href</code> attribute</li>\n	<li><code>url_text_only</code> attribute</li>\n</ul>\n\n<h2><strong>Tweaking Link Text</strong></h2>\n\n<p><strong>adi_link</strong> provides various attributes to manipulate the displayed link text:</p>\n\n<ul>\n	<li><code>link_text</code></li>\n	<li><code>no_wrap</code></li>\n	<li><code>link_text_suffix</code></li>\n	<li><code>pretty_url</code></li>\n	<li><code>url_link_text</code></li>\n	<li><code>short_url_text</code></li>\n</ul>\n\n<h2><strong>Examples</strong></h2>\n\n<p><i>Simple link:</i></p>\n\n<p><code>&lt;txp:adi_link url=&quot;http://www.example.com/&quot; /&gt;</code></p>\n\n<p>- creates link to “http://www.example.com/”, with link text of “www.example.com”</p>\n\n<p><i>External link:</i></p>\n\n<p><code>&lt;txp:adi_link url=&quot;www.example.com&quot; ext_link=&quot;1&quot; /&gt;</code></p>\n\n<p>- creates link with <span class=\"caps\">HTML</span> attributes: class=“ext_link” &amp; rel=“external”</p>\n\n<p><i>Using the <span class=\"caps\">URL</span> as link text</i></p>\n\n<p><code>&lt;txp:adi_link id=&quot;4&quot; url_link_text=&quot;1&quot; /&gt;</code></p>\n\n<p>- outputs  link #4 using the <span class=\"caps\">URL</span> itself as the link text rather than the title</p>\n\n<p><code>&lt;txp:adi_link url_link_text=&quot;1&quot; short_url_text=&quot;2&quot; /&gt;</code></p>\n\n<p>- outputs a link using a shortened version of the <span class=\"caps\">URL</span> as the link text, e.g. if the link is “http://www.example.com/section/subsection/article” then the link text will be “www.example.com/section”.  <em>Note that the actual link <span class=\"caps\">URL</span> is left unchanged.</em></p>\n\n<p><i>Container mode &#8211; link is enclosed:</i></p>\n\n<p><code>&lt;txp:adi_link&gt;&lt;txp:variable name=&quot;my_url&quot;/&gt;&lt;/txp:adi_link&gt;</code></p>\n\n<p>- creates link from <span class=\"caps\">URL</span> stored in a <span class=\"caps\">TXP</span> variable</p>\n\n<p><i>Container mode &#8211; image is enclosed:</i></p>\n\n<p><code>&lt;txp:adi_link url=&quot;www.example.com&quot; image=&quot;1&quot;&gt;&lt;txp:image id=&quot;42&quot; /&gt;&lt;/txp:adi_link&gt;</code></p>\n\n<p>- creates link to www.example.com, using an image instead of text</p>\n\n<p><i>Enhance links in an article:</i></p>\n\n<p>&lt;span style=“font-family:monospace”&gt;\n notextile. &amp;lt;adi_link&amp;gt;&lt;br/&gt;\n &lt;br/&gt;\n * &amp;ldquo;Link 1&amp;rdquo;:http://www.linkone.com&lt;br/&gt;\n * &amp;ldquo;Link 2&amp;rdquo;:http://www.linktwo.com&lt;br/&gt;\n * &amp;ldquo;Link 3&amp;rdquo;:http://www.linkthree.com&lt;br/&gt;\n &lt;br/&gt;\n notextile. &amp;lt;/adi_link&amp;gt;<br />\n&lt;/span&gt;</p>\n\n<p>Converts links listed within an article to external links. Note: separate <code>adi_link</code> tags from content with blank lines &amp; use “notextile” to prevent empty <code>&lt;p&gt;&lt;/p&gt;</code> being generated.</p>\n\n<p><i><span class=\"caps\">TXP</span> link:</i></p>\n\n<p><code>&lt;txp:adi_link id=&quot;23&quot; link_text=&quot;New text&quot;/&gt;</code></p>\n\n<p>- outputs a specific <span class=\"caps\">TXP</span> link, overriding the stored link text/title</p>\n\n<p><i>In a link form:</i></p>\n\n<p><code>&lt;txp:adi_link class=&quot;myclass&quot; /&gt;</code></p>\n\n<p>- when used in a Link Form, will output link with class of “myclass”</p>\n\n<p><i>Link to page anchor:</i></p>\n\n<p><code>&lt;txp:adi_link anchor=&quot;top&quot; link_text=&quot;back to top&quot; /&gt;</code></p>\n\n<p>- outputs a link to <code>#top</code> on current page</p>\n\n<p><i>Link to section with additional <span class=\"caps\">URL</span> vars:</i></p>\n\n<p><code>&lt;txp:adi_link section=&quot;mysection&quot; urlvars=&quot;var1=value1&amp;var2=value2&quot; link_text=&quot;My Section with extras&quot; /&gt;</code></p>\n\n<p>- outputs a link to section “mysection”, with extra <span class=\"caps\">URL</span> vars appended</p>\n\n<p><i>In an article form:</i></p>\n\n<p><code>&lt;txp:adi_link image=&quot;1&quot;&gt;&lt;txp:article_image /&gt;&lt;/txp:adi_link&gt;</code></p>\n\n<p>- link <span class=\"caps\">URL</span> taken from “Article link” custom field, using image from “Article image” custom field</p>\n\n<p><i>Linking to an article</i></p>\n\n<p><code>&lt;txp:adi_link url=\'&lt;txp:permlink /&gt;\' /&gt;</code></p>\n\n<p>- if you need an article link with extras</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>id=&quot;link ID number&quot;</code></p>\n\n<p>- generate a specific <span class=\"caps\">TXP</span> link. If not specified &amp; <strong>adi_link</strong> is being used in an article then an attempt will be made to find a link ID in a custom field called “Article link”.</p>\n\n<p><code>article_id=&quot;article ID number&quot;</code></p>\n\n<p>- generate a link to an article.</p>\n\n<p><code>section=&quot;section name&quot;</code></p>\n\n<p>- output link to specified section. Default = not used. For current section, use <code>section=&quot;&quot;</code>.</p>\n\n<p><code>category=&quot;category name&quot;</code></p>\n\n<p>- output link with specified category. Default = &#8220;&#8221; (No category).</p>\n\n<p><code>url=&quot;url&quot;</code></p>\n\n<p>- <span class=\"caps\">URL</span> (with or without “http://” prefix) used for link. Default = &#8220;&#8221; (<span class=\"caps\">URL</span> taken from some other source).</p>\n\n<p><code>link_text=&quot;text&quot;</code></p>\n\n<p>- text to be used for link. Default = &#8220;&#8221; (use <span class=\"caps\">URL</span>). Will override value from database when used with <span class=\"caps\">TXP</span> link id.</p>\n\n<p><code>no_wrap=&quot;boolean&quot;</code></p>\n\n<p>- replaces spaces in the link text with <code>&amp;nbsp;</code> to prevent wrapping. Default = “0” (link text will wrap).</p>\n\n<p><code>link_text_suffix=&quot;text&quot;</code></p>\n\n<p>- string to be appended to link text. Default = &#8220;&#8221; (none). Can be used to make link text unique (avoiding accessibility errors).</p>\n\n<p><code>title=&quot;text&quot;</code></p>\n\n<p>- text to be used for <span class=\"caps\">HTML</span> title attribute. Default = &#8220;&#8221; (use link text).</p>\n\n<p><code>title_suffix=&quot;text&quot;</code></p>\n\n<p>- string to be appended to title. Default = &#8220;&#8221; (none). Can be used to make link titles unique (avoiding accessibility errors).</p>\n\n<p><code>pretty_url=&quot;boolean&quot;</code></p>\n\n<p>- strip “http://” prefix &amp; trailing slash from <span class=\"caps\">URL</span>. Default = “1” (Yes).  For use when link_text attribute not specified.</p>\n\n<p><code>ext_link=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the link should be rendered as an “external” link or not &#8211; see ext_class &amp; rel_name. Default = “0” (No).</p>\n\n<p><code>ext_class=&quot;class name&quot;</code></p>\n\n<p>- <span class=\"caps\">CSS</span> class applied to the link if ext_link=1. Default = “ext_link”.</p>\n\n<p><code>rel_name=&quot;rel name&quot;</code></p>\n\n<p>- value for the <span class=\"caps\">HTML</span> rel attribute. Default = “bookmark” (or “external” if ext_link=“1”).</p>\n\n<p><code>escape=&quot;html&quot;</code></p>\n\n<p>- escape <span class=\"caps\">HTML</span> entities in link text &amp; link title. Default = “html” (Yes).</p>\n\n<p><code>single_url=&quot;boolean&quot;</code></p>\n\n<p>- force single <span class=\"caps\">URL</span> mode. Default = “0” (No).  For use in container tag mode when enclosing a <span class=\"caps\">URL</span> that doesn&#8217;t have “http://” prefix.</p>\n\n<p><code>url_link_text=&quot;boolean&quot;</code></p>\n\n<p>- use <span class=\"caps\">URL</span> for the link text. Default = “0” (No).  For use with <span class=\"caps\">TXP</span> links, as normally the stored link name/title is displayed.</p>\n\n<p><code>short_url_text=&quot;integer&quot;</code></p>\n\n<p>- For use with <code>url_link_text</code> to shorten the displayed <span class=\"caps\">URL</span> text. Default = “0” (full <span class=\"caps\">URL</span>). <em>The actual link <span class=\"caps\">URL</span> is not affected.</em> The specified integer defines the number of slash-separated chunks to output.</p>\n\n<p><code>suppress_href=&quot;boolean&quot;</code></p>\n\n<p>- suppress output of the href attribute. Default = “0” (No).</p>\n\n<p><code>url_text_only=&quot;boolean&quot;</code></p>\n\n<p>-  output the <span class=\"caps\">URL</span> as text only &#8211; i.e. not enclosed in <code>&lt;a&gt;...&lt;/a&gt;</code>. Default = “0” (Output as a normal link).</p>\n\n<p><code>anchor=&quot;page anchor&quot;</code></p>\n\n<p>- output link with page anchor. Default = &#8220;&#8221; (not used).  If no <span class=\"caps\">URL</span> or section specified then link to current page used.</p>\n\n<p><code>urlvars=&quot;URL variables&quot;</code></p>\n\n<p>- append some <span class=\"caps\">URL</span> vars. Default = &#8220;&#8221; (none). For example <code>urlvars=&quot;var1=value1&amp;var2=value2&quot;</code>.</p>\n\n<p><code>quiet=&quot;boolean&quot;</code></p>\n\n<p>- suppress output of an error message if <span class=\"caps\">URL</span> not found or supplied. Default = “0” (Noisy).</p>\n\n<p><code>html_id=&quot;html id&quot;</code></p>\n\n<p>- the <span class=\"caps\">HTML</span> ID applied to the link. Default = &#8220;&#8221; (no ID).</p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- <span class=\"caps\">CSS</span> class applied to the link. Default = &#8220;&#8221; (no class).</p>\n\n<p><code>link_span=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the contents of the links should be wrapped in a span, i.e. <code>&lt;a&gt;&lt;span&gt;...&lt;/span&gt;&lt;/a&gt;</code>. Default = “0” (No).</p>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the link. Default = &#8220;&#8221; (no tag). <em>If there is no link to output then no wraptag is generated.</em></p>\n\n<p><code>wraptag_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>wraptag_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>label=&quot;text&quot;</code></p>\n\n<p>- a string to prefix the link.  Default = &#8220;&#8221; (none). Text will be enclosed inside wraptag (if supplied).</p>\n\n<p><code>tail=&quot;text&quot;</code></p>\n\n<p>- a string to follow the link.  Default = &#8220;&#8221; (none). Text will be enclosed inside wraptag (if supplied).</p>\n\n<p><code>image=&quot;boolean&quot;</code></p>\n\n<p>- used in container tag mode only, to specify that the adi_link tag encloses an image rather than a <span class=\"caps\">URL</span>. Enables an image to be output for the link, instead of text. Default = “0” (adi_link tag contains <span class=\"caps\">URL</span>).</p>\n\n<h2><strong>For external use only</strong></h2>\n\n<p>Here&#8217;s a jQuery snippet to open “external” links in a new browser window:</p>\n\n<pre><code>$(document).ready(function() {\n	$(\'a[rel=&quot;external&quot;]\').each(function() {\n		$(this).attr(\'target\', \'_blank\');\n	});\n});</code></pre>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=28224\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_link&amp;version=0.2\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_link - Enhanced links\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.5		- new attribute: \'link_span\'\n				- fixed lack of recognition for https://\n				- fixed up parse problem in container mode\n				- cater for URLs in \"Article link\" custom field\n				- now fails more subtly if id not found\n				- auto-detects external links\n				- new \'article_id\' attribute\n				- TXP 4.6 tag registration\n		0.4		- new attribute: \'short_url_text\'\n				- new attribute: \'url_text_only\'\n				- fix: coding faux pas when setting ext_class (thanks jakob)\n		0.3		- new attribute: \'quiet\'\n				- new attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n				- new attributes: \'label\', \'tail\'\n				- new attribute: \'title_suffix\'\n				- new attribute: \'image\'\n				- new attribute: \'no_wrap\'\n				- enhancement: support for \"Article link\" custom field\n		0.2		- fixed: no error message if URL missing\n				- new attribute: \'suppress_href\' (for Phlip)\n				- new attribute: \'anchor\'\n				- new attribute: \'section\'\n				- new attribute: \'category\'\n				- new attribute: \'urlvars\'\n		0.1		- initial release\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_link\')\n	;\n}\n\nfunction adi_link($atts, $thing=\'\') {\n	global $prefs,$pretext,$thislink,$thisarticle;\n\n	extract(lAtts(array(\n		\'html_id\'		=> \'\',			// HTML ID\n		\'class\'			=> \'\',			// CSS class\n		\'title\'			=> \'\',			// Title attribute\n		\'ext_link\'		=> \'\',			// external link or not\n		\'ext_class\'		=> \'ext_link\',	// default external class\n		\'rel_name\'		=> \'\',			// HTML \"rel\" attribute\n		\'url\'			=> \'\',			// the URL\n		\'link_text\'		=> \'\',			// link text\n		\'link_span\'		=> \'0\',			// <span>link text</span>\n		\'no_wrap\'		=> \'0\',			// substitute spaces for &nbsp; to prevent link text wrapping\n		\'title_suffix\'	=> \'\',			// append to title\n		\'pretty_url\'	=> \'1\',			// strip \"http://\" & trailing slash if URL used as link text\n		\'escape\'		=> \'html\',		// escape HTML entities\n		\'single_url\'	=> \'0\',			// force single url mode\n		\'url_link_text\'	=> \'0\',			// use URL for the link text\n		\'short_url_text\'	=> \'0\',		// shorten the URL link text (e.g. www.example.com/section/subsection/article -> www.example.com)\n		\'id\'			=> \'\',			// TXP link ID\n		\'article_id\'	=> \'\',			// TXP article ID\n		\'suppress_href\'	=> \'0\',			// suppress output of \"href\" attribute\n		\'url_text_only\'	=> \'0\',			// output the URL as text only (i.e. no <a>)\n		\'section\'		=> \'\',			// section name (if specified & empty then current section)\n		\'anchor\'		=> \'\',			// page anchor\n		\'urlvars\'		=> \'\',			// URL variables e.g. \'var1=value1&var2=value2\'\n		\'category\'		=> \'\',			// category name (if specified & empty then current category)\n		\'quiet\'			=> \'0\',			// don\'t moan if no URL can be found\n		\'wraptag\'		=> \'\',			// wrap a tag around link\n		\'wraptag_class\'	=> \'\',			// class for wraptag\n		\'wraptag_id\'	=> \'\',			// id for wraptag\n		\'label\'			=> \'\',			// string to precede link output\n		\'tail\'			=> \'\',			// string to follow link output\n		\'image\'			=> \'0\',			// container tag mode only, the image is enclosed (rather than the URL)\n	), $atts));\n\n	// check if \'section\' attribute specified\n	$section_mode = array_key_exists(\'section\', $atts);\n	if ($section_mode) {\n		if ($section == \'\')\n			$url_array[\'s\'] = $pretext[\'s\'];\n		else\n			$url_array[\'s\'] = $section;\n	}\n\n	// check if \'category\' attribute specified\n	$category_mode = array_key_exists(\'category\', $atts);\n	if ($category_mode) {\n		if ($category == \'\')\n			$url_array[\'c\'] = $pretext[\'c\'];\n		else\n			$url_array[\'c\'] = $category;\n	}\n\n	// get section/category URL\n	if (($section_mode) || ($category_mode))\n		$url = pagelinkurl($url_array);\n\n	// all alone at the singles bar?\n	$single_link = FALSE;\n	if ($single_url) { // force \"single url enclosed\"\n		$single_link = TRUE;\n		$url = trim(parse($thing));\n	}\n\n	// check if we\'re in a link form\n	if (isset($thislink) && !empty($thislink)) { // assume link form\n		$url = $thislink[\'url\'];\n		if ($link_text == \"\") $link_text = $thislink[\'linkname\']; // link_text attribute overrides DB value\n	}\n\n	// enclosure acts?\n	if (($thing === NULL) || $image) // not enclosing tag or image is enclosed, so url is supplied from elsewhere\n		$single_link = TRUE;\n	else { // enclosing tag so parse it & see what it becomes\n		$this_thing = parse($thing);\n		if (preg_match(\'@^http.*://@\',trim($this_thing))) { // single url enclosed\n			$url = trim($this_thing);\n			$single_link = TRUE;\n		}\n	}\n\n	// check if we\'re in an article (either form or body)\n	if ($thisarticle && !isset($atts[\'id\']) && !$url && !isset($atts[\'article_id\'])) {\n		// we\'re in an article, no URL/link id/article id supplied, so try \"Article link\" custom field instead\n		if (!empty($thisarticle[\'article link\'])) {\n			$id_list = explode(\',\',$thisarticle[\'article link\']); // comma separated list ready\n			$id = $id_list[0]; // but only recognise first item\n			if (!is_numeric($id)) { // not a number, so assume it\'s a URL\n				$id = \'\';\n				$url = trim($thisarticle[\'article link\']);\n			}\n			$single_link = TRUE;\n		}\n		else {\n			// must be within article body\n		}\n	}\n\n	// link id supplied?\n	if ($id) {\n		$a = safe_row(\'url,linkname,description\',\'txp_link\',\'id = \'.$id);\n		if ($a) { // ID found in database\n			$url = $a[\'url\'];\n			if ($link_text == \"\") // use link name\n				$link_text = $a[\'linkname\'];\n		}\n		else\n			$id =\'\';\n	}\n\n	// article id supplied?\n	if ($article_id) {\n		$url = permlinkurl_id($article_id);\n		if ($url) {\n			$a = safe_row(\'title\',\'textpattern\',\'id=\'.$article_id);\n			extract($a); // set title\n			$title = html_entity_decode($title);\n			if ($link_text == \"\") // use article title\n				$link_text = $title;\n		}\n	}\n\n	// fix up some values\n	if ($anchor) { // anchor supplied\n		$anchor = \'#\'.$anchor;\n		// if no URL & no section then use current page\n		if (!$url && !$section_mode) {\n			$url = $prefs[\'siteurl\'].$pretext[\'req\']; // make up URL for current page\n		}\n	}\n	if ($urlvars) // check if there\'s some URL vars already present\n		strstr($url,\'?\') !== FALSE ?\n			$urlvars = \'&\'.$urlvars : // tack onto end\n			$urlvars = \'?\'.$urlvars; // start a new one\n\n	// check if supplied url is missing \"http://\" or \"https://\"\n	if (!preg_match(\'@^http.*://@\',$url))\n		$url = \'http://\'.$url;\n\n	// auto-detect external links\n	if ($ext_link == \'\') { // auto detect external links\n		$this_url = preg_replace(\'@^http.*://@\',\'\',$url); // strip \"http(s)://\"\n		if (strpos($this_url,$prefs[\'siteurl\']) === FALSE)\n			$ext_link = \'1\';\n	}\n\n	// class action\n	if ($class == \'\') // if \'class\' not specified, set some defaults\n		$ext_link ? $class = $ext_class : $class = \"\";\n\n	// relatively speaking\n	if ($rel_name == \'\') // if \'rel\' not specified, set some defaults\n		$ext_link ? $rel_name = \"external\" : $rel_name = \"bookmark\";\n\n	// fix up link text\n	if (($link_text == \"\") || ($url_link_text)) $link_text = $url; // default \'link_text\' to URL\n	if ($pretty_url) {\n		if (preg_match(\'@^http.*://@\',$link_text)) // strip \"http(s)://\"\n// 			$link_text = str_replace(\'http://\', \'\',$link_text);\n			$link_text = preg_replace(\'@^http.*://@\',\'\',$link_text); // strip \"http(s)://\"\n		if (substr($link_text, -1, 1) == \"/\") // strip trailing slash\n			$link_text = substr($link_text,0,-1);\n	}\n	if ($short_url_text) { // shorten the URL link text to required number of \'/\' separated chunks\n		$loadsachunks = explode(\"/\", $link_text);\n		for ($i = 1; $i <= $short_url_text; $i++)\n			if (!empty($loadsachunks)) $somechunks[] = array_shift($loadsachunks);\n		$link_text = implode(\"/\", $somechunks);\n	}\n\n	// title action\n	if ($title == \"\")\n		if ($id) // take title from database\n			$title = $a[\'linkname\'];\n		else\n			$title = $link_text; // default \'title\' to \'link_text\'\n	if ($title_suffix)\n		$title .= $title_suffix;\n\n	// escape to victory\n	if ($escape == \"html\") {\n		$url = htmlspecialchars($url);\n		$urlvars = htmlspecialchars($urlvars);\n		$link_text = htmlspecialchars($link_text);\n		$title = htmlspecialchars($title);\n	}\n\n	// no rap & less chat\n	if ($no_wrap)\n		$link_text = str_replace(\' \',\'&nbsp;\',$link_text);\n\n	// generate markup\n	if ($single_link) { // single link mode\n		if ((($url == \'http://\') || ($url == \'https://\')) && !($suppress_href)) // note that \'http://\' automatically inserted above\n			if ($quiet) // don\'t say nuffin ... don\'t even get label etc\n				return \'\';\n			else\n				return \'no URL\';\n		else {\n			$attr = \'\';\n			$url .= $urlvars.$anchor;\n			if (!$suppress_href) $attr = \' href=\"\'.$url.\'\"\';\n			if ($title) $attr .= \' title=\"\'.$title.\'\"\';\n			if ($rel_name) $attr .= \' rel=\"\'.$rel_name.\'\"\';\n			if ($image) // substitute link text for an image\n				$link_text = parse($thing);\n			else // not an image\n				if ($link_span)\n					$link_text = tag($link_text,\'span\');\n			if ($url_text_only) // look no links!\n				$out = $url;\n			else\n				$out = doTag($link_text,\'a\',$class,$attr,$html_id);\n			$out = $label.$out.$tail;\n			if ($wraptag) { // add wraptag stuff\n				$wrap_attr = \'\';\n				if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n				if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n				$out = tag($out,$wraptag,$wrap_attr);\n			}\n			return $out;\n		}\n	}\n	else { // assume a block of pre-parsed links (from Textile) is enclosed\n		$this_thing = parse($thing);\n		if ($class == \"\")\n			$replace = \"\";\n		else\n			$replace = \'class=\"\'.$class.\'\" \';\n		$replace .= \'rel=\"\'.$rel_name.\'\" href=\"\'.\'$1\';\n		$out = preg_replace(\'/href=\"/\',$replace,$this_thing); // this assumes all links are external regardless (TODO)!\n		return doTag($out,\"\");\n	}\n}\n', '\n/*\n	adi_link - Enhanced links\n\n	Written by Adi Gilbert\n\n	Released under the GNU Lesser General Public License\n\n	Version history:\n		0.5		- new attribute: \'link_span\'\n				- fixed lack of recognition for https://\n				- fixed up parse problem in container mode\n				- cater for URLs in \"Article link\" custom field\n				- now fails more subtly if id not found\n				- auto-detects external links\n				- new \'article_id\' attribute\n				- TXP 4.6 tag registration\n		0.4		- new attribute: \'short_url_text\'\n				- new attribute: \'url_text_only\'\n				- fix: coding faux pas when setting ext_class (thanks jakob)\n		0.3		- new attribute: \'quiet\'\n				- new attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n				- new attributes: \'label\', \'tail\'\n				- new attribute: \'title_suffix\'\n				- new attribute: \'image\'\n				- new attribute: \'no_wrap\'\n				- enhancement: support for \"Article link\" custom field\n		0.2		- fixed: no error message if URL missing\n				- new attribute: \'suppress_href\' (for Phlip)\n				- new attribute: \'anchor\'\n				- new attribute: \'section\'\n				- new attribute: \'category\'\n				- new attribute: \'urlvars\'\n		0.1		- initial release\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_link\')\n	;\n}\n\nfunction adi_link($atts, $thing=\'\') {\n	global $prefs,$pretext,$thislink,$thisarticle;\n\n	extract(lAtts(array(\n		\'html_id\'		=> \'\',			// HTML ID\n		\'class\'			=> \'\',			// CSS class\n		\'title\'			=> \'\',			// Title attribute\n		\'ext_link\'		=> \'\',			// external link or not\n		\'ext_class\'		=> \'ext_link\',	// default external class\n		\'rel_name\'		=> \'\',			// HTML \"rel\" attribute\n		\'url\'			=> \'\',			// the URL\n		\'link_text\'		=> \'\',			// link text\n		\'link_span\'		=> \'0\',			// <span>link text</span>\n		\'no_wrap\'		=> \'0\',			// substitute spaces for &nbsp; to prevent link text wrapping\n		\'title_suffix\'	=> \'\',			// append to title\n		\'pretty_url\'	=> \'1\',			// strip \"http://\" & trailing slash if URL used as link text\n		\'escape\'		=> \'html\',		// escape HTML entities\n		\'single_url\'	=> \'0\',			// force single url mode\n		\'url_link_text\'	=> \'0\',			// use URL for the link text\n		\'short_url_text\'	=> \'0\',		// shorten the URL link text (e.g. www.example.com/section/subsection/article -> www.example.com)\n		\'id\'			=> \'\',			// TXP link ID\n		\'article_id\'	=> \'\',			// TXP article ID\n		\'suppress_href\'	=> \'0\',			// suppress output of \"href\" attribute\n		\'url_text_only\'	=> \'0\',			// output the URL as text only (i.e. no <a>)\n		\'section\'		=> \'\',			// section name (if specified & empty then current section)\n		\'anchor\'		=> \'\',			// page anchor\n		\'urlvars\'		=> \'\',			// URL variables e.g. \'var1=value1&var2=value2\'\n		\'category\'		=> \'\',			// category name (if specified & empty then current category)\n		\'quiet\'			=> \'0\',			// don\'t moan if no URL can be found\n		\'wraptag\'		=> \'\',			// wrap a tag around link\n		\'wraptag_class\'	=> \'\',			// class for wraptag\n		\'wraptag_id\'	=> \'\',			// id for wraptag\n		\'label\'			=> \'\',			// string to precede link output\n		\'tail\'			=> \'\',			// string to follow link output\n		\'image\'			=> \'0\',			// container tag mode only, the image is enclosed (rather than the URL)\n	), $atts));\n\n	// check if \'section\' attribute specified\n	$section_mode = array_key_exists(\'section\', $atts);\n	if ($section_mode) {\n		if ($section == \'\')\n			$url_array[\'s\'] = $pretext[\'s\'];\n		else\n			$url_array[\'s\'] = $section;\n	}\n\n	// check if \'category\' attribute specified\n	$category_mode = array_key_exists(\'category\', $atts);\n	if ($category_mode) {\n		if ($category == \'\')\n			$url_array[\'c\'] = $pretext[\'c\'];\n		else\n			$url_array[\'c\'] = $category;\n	}\n\n	// get section/category URL\n	if (($section_mode) || ($category_mode))\n		$url = pagelinkurl($url_array);\n\n	// all alone at the singles bar?\n	$single_link = FALSE;\n	if ($single_url) { // force \"single url enclosed\"\n		$single_link = TRUE;\n		$url = trim(parse($thing));\n	}\n\n	// check if we\'re in a link form\n	if (isset($thislink) && !empty($thislink)) { // assume link form\n		$url = $thislink[\'url\'];\n		if ($link_text == \"\") $link_text = $thislink[\'linkname\']; // link_text attribute overrides DB value\n	}\n\n	// enclosure acts?\n	if (($thing === NULL) || $image) // not enclosing tag or image is enclosed, so url is supplied from elsewhere\n		$single_link = TRUE;\n	else { // enclosing tag so parse it & see what it becomes\n		$this_thing = parse($thing);\n		if (preg_match(\'@^http.*://@\',trim($this_thing))) { // single url enclosed\n			$url = trim($this_thing);\n			$single_link = TRUE;\n		}\n	}\n\n	// check if we\'re in an article (either form or body)\n	if ($thisarticle && !isset($atts[\'id\']) && !$url && !isset($atts[\'article_id\'])) {\n		// we\'re in an article, no URL/link id/article id supplied, so try \"Article link\" custom field instead\n		if (!empty($thisarticle[\'article link\'])) {\n			$id_list = explode(\',\',$thisarticle[\'article link\']); // comma separated list ready\n			$id = $id_list[0]; // but only recognise first item\n			if (!is_numeric($id)) { // not a number, so assume it\'s a URL\n				$id = \'\';\n				$url = trim($thisarticle[\'article link\']);\n			}\n			$single_link = TRUE;\n		}\n		else {\n			// must be within article body\n		}\n	}\n\n	// link id supplied?\n	if ($id) {\n		$a = safe_row(\'url,linkname,description\',\'txp_link\',\'id = \'.$id);\n		if ($a) { // ID found in database\n			$url = $a[\'url\'];\n			if ($link_text == \"\") // use link name\n				$link_text = $a[\'linkname\'];\n		}\n		else\n			$id =\'\';\n	}\n\n	// article id supplied?\n	if ($article_id) {\n		$url = permlinkurl_id($article_id);\n		if ($url) {\n			$a = safe_row(\'title\',\'textpattern\',\'id=\'.$article_id);\n			extract($a); // set title\n			$title = html_entity_decode($title);\n			if ($link_text == \"\") // use article title\n				$link_text = $title;\n		}\n	}\n\n	// fix up some values\n	if ($anchor) { // anchor supplied\n		$anchor = \'#\'.$anchor;\n		// if no URL & no section then use current page\n		if (!$url && !$section_mode) {\n			$url = $prefs[\'siteurl\'].$pretext[\'req\']; // make up URL for current page\n		}\n	}\n	if ($urlvars) // check if there\'s some URL vars already present\n		strstr($url,\'?\') !== FALSE ?\n			$urlvars = \'&\'.$urlvars : // tack onto end\n			$urlvars = \'?\'.$urlvars; // start a new one\n\n	// check if supplied url is missing \"http://\" or \"https://\"\n	if (!preg_match(\'@^http.*://@\',$url))\n		$url = \'http://\'.$url;\n\n	// auto-detect external links\n	if ($ext_link == \'\') { // auto detect external links\n		$this_url = preg_replace(\'@^http.*://@\',\'\',$url); // strip \"http(s)://\"\n		if (strpos($this_url,$prefs[\'siteurl\']) === FALSE)\n			$ext_link = \'1\';\n	}\n\n	// class action\n	if ($class == \'\') // if \'class\' not specified, set some defaults\n		$ext_link ? $class = $ext_class : $class = \"\";\n\n	// relatively speaking\n	if ($rel_name == \'\') // if \'rel\' not specified, set some defaults\n		$ext_link ? $rel_name = \"external\" : $rel_name = \"bookmark\";\n\n	// fix up link text\n	if (($link_text == \"\") || ($url_link_text)) $link_text = $url; // default \'link_text\' to URL\n	if ($pretty_url) {\n		if (preg_match(\'@^http.*://@\',$link_text)) // strip \"http(s)://\"\n// 			$link_text = str_replace(\'http://\', \'\',$link_text);\n			$link_text = preg_replace(\'@^http.*://@\',\'\',$link_text); // strip \"http(s)://\"\n		if (substr($link_text, -1, 1) == \"/\") // strip trailing slash\n			$link_text = substr($link_text,0,-1);\n	}\n	if ($short_url_text) { // shorten the URL link text to required number of \'/\' separated chunks\n		$loadsachunks = explode(\"/\", $link_text);\n		for ($i = 1; $i <= $short_url_text; $i++)\n			if (!empty($loadsachunks)) $somechunks[] = array_shift($loadsachunks);\n		$link_text = implode(\"/\", $somechunks);\n	}\n\n	// title action\n	if ($title == \"\")\n		if ($id) // take title from database\n			$title = $a[\'linkname\'];\n		else\n			$title = $link_text; // default \'title\' to \'link_text\'\n	if ($title_suffix)\n		$title .= $title_suffix;\n\n	// escape to victory\n	if ($escape == \"html\") {\n		$url = htmlspecialchars($url);\n		$urlvars = htmlspecialchars($urlvars);\n		$link_text = htmlspecialchars($link_text);\n		$title = htmlspecialchars($title);\n	}\n\n	// no rap & less chat\n	if ($no_wrap)\n		$link_text = str_replace(\' \',\'&nbsp;\',$link_text);\n\n	// generate markup\n	if ($single_link) { // single link mode\n		if ((($url == \'http://\') || ($url == \'https://\')) && !($suppress_href)) // note that \'http://\' automatically inserted above\n			if ($quiet) // don\'t say nuffin ... don\'t even get label etc\n				return \'\';\n			else\n				return \'no URL\';\n		else {\n			$attr = \'\';\n			$url .= $urlvars.$anchor;\n			if (!$suppress_href) $attr = \' href=\"\'.$url.\'\"\';\n			if ($title) $attr .= \' title=\"\'.$title.\'\"\';\n			if ($rel_name) $attr .= \' rel=\"\'.$rel_name.\'\"\';\n			if ($image) // substitute link text for an image\n				$link_text = parse($thing);\n			else // not an image\n				if ($link_span)\n					$link_text = tag($link_text,\'span\');\n			if ($url_text_only) // look no links!\n				$out = $url;\n			else\n				$out = doTag($link_text,\'a\',$class,$attr,$html_id);\n			$out = $label.$out.$tail;\n			if ($wraptag) { // add wraptag stuff\n				$wrap_attr = \'\';\n				if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n				if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n				$out = tag($out,$wraptag,$wrap_attr);\n			}\n			return $out;\n		}\n	}\n	else { // assume a block of pre-parsed links (from Textile) is enclosed\n		$this_thing = parse($thing);\n		if ($class == \"\")\n			$replace = \"\";\n		else\n			$replace = \'class=\"\'.$class.\'\" \';\n		$replace .= \'rel=\"\'.$rel_name.\'\" href=\"\'.\'$1\';\n		$out = preg_replace(\'/href=\"/\',$replace,$this_thing); // this assumes all links are external regardless (TODO)!\n		return doTag($out,\"\");\n	}\n}\n', '92453aa700d1255e38c612fba57e6b37', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_menu', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '1.4beta13', 'Section hierarchy, section menu and breadcrumb trail', '<h1><strong>adi_menu</strong> &#8211; Section hierarchy, section menu and breadcrumb trail</h1>\n\n<p>This plugin provides:</p>\n\n<ul>\n	<li>a new tab under Presentation, to set up the section hierarchy</li>\n	<li>some tags:\n	<ul>\n		<li><a href=\"#adi_menu\" rel=\"nofollow\">adi_menu</a> &#8211; output the menu markup</li>\n		<li><a href=\"#adi_menu_info\" rel=\"nofollow\">adi_menu_info</a> &#8211; output adi_menu information</li>\n		<li><a href=\"#adi_menu_if_info\" rel=\"nofollow\">adi_menu_if_info</a> &#8211; conditional based on adi_menu information</li>\n		<li><a href=\"#adi_menu_breadcrumb\" rel=\"nofollow\">adi_menu_breadcrumb</a> &#8211; output the breadcrumb markup</li>\n	</ul></li>\n	</ul>\n\n<h2><strong>Installation</strong></h2>\n\n<p><em>Installation of <strong>adi_menu</strong> will add some columns to the ‘txp_section’ table in your Textpattern database, as well as new ‘adi_menu’ table.  The extra columns &amp; table should not interfere with anything else.  That said, if you are of a cautious frame of mind then I can thoroughly recommend <a href=\"http://forum.textpattern.com/viewtopic.php?id=10395\" rel=\"nofollow\">rss_admin_db_manager</a> to do database backups before installation.</em></p>\n\n<p>Once the plugin is installed and activated, go to the <strong>adi_menu</strong> tab under Presentation to assign parents, alternative titles, organise sorting etc. Then add the <strong>adi_menu</strong> tags to your pages or forms.</p>\n\n<p>Style the menu using <span class=\"caps\">CSS</span>.</p>\n\n<h2><strong>Admin tab</strong></h2>\n\n<p>Users with sufficient privileges will see the <strong>adi_menu</strong> admin tab, under Presentation.  This enables:</p>\n\n<ul>\n	<li>Section hierarchy &#8211; created by assigning parents</li>\n	<li>Excluding sections &#8211; define which sections should be permanently excluded from the rendered section menu (sections can also be excluded using a <code>&lt;txp:adi_menu /&gt;</code> tag attribute)</li>\n	<li>Sorting &#8211; specify a custom sort order</li>\n	<li>Cloning &#8211; specify that a section must appear as a child in its own subsection list (more on that below)</li>\n	<li>Redirection &#8211; redirect to another section or link</li>\n	<li>Virtual sections</li>\n	<li>A summary of the configured section hierarchy</li>\n	<li>Preferences</li>\n</ul>\n\n<h2><strong>Submenus</strong></h2>\n\n<p>If required, <strong>adi_menu</strong> can be used more than once on a page. For example, to render a submenu separately from a top-level menu you could use:</p>\n\n<pre><code>&lt;txp:adi_menu menu_id=&quot;top&quot; include_children=&quot;0&quot; active_ancestors=&quot;1&quot; /&gt;\n&lt;txp:adi_menu menu_id=&quot;sub&quot; sub_menu_level=&quot;2&quot; /&gt;</code></pre>\n\n<p>For three tiers of menus:</p>\n\n<pre><code>&lt;txp:adi_menu menu_id=&quot;top&quot; include_children=&quot;0&quot; active_ancestors=&quot;1&quot; /&gt;\n&lt;txp:adi_menu menu_id=&quot;sub&quot; sub_menu_level=&quot;2&quot; include_children=&quot;0&quot; active_ancestors=&quot;1&quot; /&gt;\n&lt;txp:adi_menu menu_id=&quot;subsub&quot; sub_menu_level=&quot;3&quot; /&gt;</code></pre>\n\n<p>If you simply want a submenu starting with children of the current section:</p>\n\n<pre><code>&lt;txp:adi_menu menu_id=&quot;sub&quot; sub_menu=&quot;1&quot; /&gt;</code></pre>\n\n<p>Submenus are just that &#8211; they contain items <em>under</em> the currently active section. If you want a menu to consist of the current section and its children try:</p>\n\n<pre><code>&lt;txp:adi_menu sections=&quot;&quot; include_current=&quot;1&quot; /&gt;</code></pre>\n\n<p>Notes:</p>\n\n<ul>\n	<li>for styling purposes, you can use the <code>menu_id</code> attribute to set unique ID&#8217;s on the menu <code>&lt;ul&gt;</code></li>\n	<li><code>sub_menu_level</code> is section-sensitive, so submenus are based on the currently active section</li>\n	<li>on the upper levels, <code>include_children=&quot;0&quot;</code> prevents child sections from being rendered</li>\n	<li>on the lowest submenu, all children &amp; grandchildren are rendered (unless attributes specify otherwise)</li>\n	<li><code>active_ancestors=&quot;1&quot;</code> enables the “active_class” on the upper levels</li>\n</ul>\n\n<p>The old method of rendering submenus (using a combination of <code>include_current=&quot;1&quot;</code> &amp; <code>include_parent=&quot;0&quot;</code>), which only really worked for second level submenus, has been deprecated. However for backwards compatibility, the associated attributes and functionality have been retained. See <a href=\"http://forum.textpattern.com/viewtopic.php?id=27778\" rel=\"nofollow\">the forum</a> for details.</p>\n\n<h2><strong>Speaking blocks</strong></h2>\n\n<p>A <a href=\"http://www.smashingmagazine.com/2008/02/26/navigation-menus-trends-and-examples/\" rel=\"nofollow\">speaking block</a> is some additional information added to menu items to enhance usability. When you switch on speaking blocks in <strong>adi_menu</strong> (<code>speaking_block=&quot;1&quot;</code>), each section&#8217;s sticky article excerpt is added into the menu, enclosed within a <code>&lt;span&gt;...&lt;/span&gt;</code> inside the <code>&lt;a&gt;...&lt;/a&gt;</code> link.</p>\n\n<p>Note that because <code>&lt;a&gt;</code> is by default an inline element, block elements such as <code>&lt;p&gt;</code> are not allowed within. <em>Consequently you should <a href=\"http://txptips.com/turn-off-textile-in-your-article\" rel=\"nofollow\">turn off Textile</a> in the excerpt</em>.</p>\n\n<p>The default speaking block form contains <code>&lt;txp:excerpt /&gt;</code>, and does not need to be added manually. If you want to output more than just the sticky article excerpt, for example to include an image, then create a new form containing the required tags and use the <code>speaking_block_form</code> attribute.</p>\n\n<h2><strong>Articles</strong></h2>\n\n<p>Articles can be included in the menu using the <code>articles=&quot;1&quot;</code> attribute.  By default all live articles are included.</p>\n\n<p>With the help of the <code>article_position</code> attribute articles will be placed “before”, “after” or “dovetail”.  By selecting “dovetail” you can interleave articles and sections in a submenu.</p>\n\n<p>There are a number of additional attributes for controlling: which sections/category articles are to included, article sorting, and article selection. See the <strong>adi_menu</strong> tag help below.</p>\n\n<h2><strong>Virtual sections</strong></h2>\n\n<p><strong>adi_menu</strong> is all about sections &#8211; Textpattern sections.  Sometimes that&#8217;s not enough, so it&#8217;s also possible to create “virtual sections”.</p>\n\n<p>Virtual sections are an <strong>adi_menu</strong> construct:</p>\n\n<ul>\n	<li>they are defined in the adi_menu admin tab</li>\n	<li>are only relevant to adi_menu menus</li>\n	<li>will not clash with standard <span class=\"caps\">TXP</span> sections, nor can be accessed using <code>&lt;txp:section /&gt;</code> and the like</li>\n</ul>\n\n<p>Virtual sections can be given names, titles, clones etc. &#8211; just like standard sections. They can be parents or children of standard or virtual sections. The main difference is that they <em>need to be redirected to something</em> &#8211; either a section, a link or a category.</p>\n\n<p>One subtle difference is that a virtual section name, when referencing it explicitly in adi_menu tags, must be prefixed with “v_”.  For example:</p>\n\n<pre><code>&lt;txp:adi_menu sections=&quot;standard,v_virtual&quot; /&gt;</code></pre>\n\n<p>- will output a menu containing a <span class=\"caps\">TXP</span> section called “standard” and a virtual section called “virtual”.</p>\n\n<p>Don&#8217;t use the “v_” prefix in the admin tab though!</p>\n\n<h2><strong>Categories</strong></h2>\n\n<p><strong>adi_menu</strong> is all about sections but it&#8217;s also possible to bring categories into the mix.  You can create menu items that redirect to article category pages.  These menu items can be standard sections or virtual sections.</p>\n\n<p>These category-redirected menu items can have articles shown under them.  This is switched on as normal by the attribute <code>articles=&quot;1&quot;</code>, and can be controlled by a number of others, such as <code>cat_article_attr</code>, <code>cat_article_include</code>, <code>cat_article_exclude</code> &amp; <code>cat_article_sort</code>.</p>\n\n<h2><strong>adi_menu tag</strong></h2>\n\n<p>Place the <code>&lt;txp:adi_menu /&gt;</code> tag where you want the menu to appear.</p>\n\n<h2><strong>adi_menu &#8211; tag attributes</strong></h2>\n\n<h3><strong>Explicit inclusion/exclusion:</strong></h3>\n\n<p><code>sections=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections (&amp; their children) to be included in menu. Default = &#8220;&#8221; (all sections, except those excluded in the adi_menu admin tab).</p>\n\n<p><code>exclude=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections to be excluded from the menu. Default = &#8220;&#8221; (don&#8217;t exclude any extra sections). <em>Use if you want to exclude sections over and above what has already been excluded in the adi_menu admin tab.</em></p>\n\n<p><code>override_exclude=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of excluded sections that must be included in the menu. Default = &#8220;&#8221; (don&#8217;t include any excluded sections). <em>Only use if you want to explicitly include sections that have been excluded in the adi_menu admin tab.</em></p>\n\n<h3><strong>Implicit inclusion/exclusion:</strong></h3>\n\n<p><code>include_default=&quot;boolean&quot;</code></p>\n\n<p>- include ‘default’ section in menu. Default = “1” (Yes).</p>\n\n<p><code>exclude_clone=&quot;boolean&quot;</code></p>\n\n<p>- use if you don&#8217;t want clones appearing. Default = “0” (No).</p>\n\n<p><code>include_children=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether children should be included in the menu. Default = “1” (Yes).</p>\n\n<p><code>current_children_only=&quot;boolean&quot;</code></p>\n\n<p>- only the immediate children (sections/articles) of the current section will be output. Default = “0” (all children). <em>Note that siblings of the current section will also be output.</em></p>\n\n<p><code>current_descendants_only=&quot;boolean&quot;</code></p>\n\n<p>- only descendants (sections/articles) of the current section will be output. Default = “0” (all descendants). <em>Note that siblings of the current section &amp; its ancestors will also be output.</em></p>\n\n<p><code>include_childless=&quot;boolean&quot;</code></p>\n\n<p>- overrides the <code>include_parent</code> attribute for childless top level sections. Default = “0” (No). <em>Use if you want to output sections that have no children along with the children of other sections.</em></p>\n\n<p><code>include_current=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the current section should be added to the sections list. Default = “0” (No). <em>Not normally required, unless you&#8217;re doing something special.</em> Note that if the <code>sections</code> attribute is empty then <code>include_current=&quot;1&quot;</code> will result in <em>only</em> the current section (&amp; possibly its children) being output.</p>\n\n<p><code>include_parent=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether top level sections are output. Default = “1” (Yes). <em>Switch off if you want to output the children of a section but not the parent section itself.</em> Childless top level sections are not output in this case either (see <code>include_childless</code> attribute). If you&#8217;re wanting to generate submenus, then <code>sub_menu</code> or <code>sub_menu_level</code> will be better options.</p>\n\n<h3><strong>Active indication</strong></h3>\n\n<p><code>active_parent=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the immediate parent of the currently active section should be made “active” also. Default = “0” (No). If switched on then <code>active_class</code> will be applied to the parent&#8217;s section link and the class defined using <code>active_li_class</code> will be applied to the parent&#8217;s <code>&lt;li&gt;</code>.</p>\n\n<p><code>active_ancestors=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether all ancestors of the currently active section should be made “active”. Default = “0” (No). If switched on then <code>active_class</code> will be applied to the ancestor&#8217;s section link and the class defined using <code>active_li_class</code> will be applied to the ancestor&#8217;s <code>&lt;li&gt;</code>. <em>Note that parents are ancestors, so <code>active_ancestors=&quot;1&quot;</code> gives you an active parent too</em>.</p>\n\n<h3><strong>Titles</strong></h3>\n\n<p><code>default_title=&quot;text&quot;</code></p>\n\n<p>- title to be used for default section. Default = “Home”.</p>\n\n<p><code>clone_title=&quot;text&quot;</code></p>\n\n<p>- the default title to be used for cloned sections, if no title has been specified in the admin tab. Default = “Summary”. <em><span class=\"caps\">DEPRECATED</span> <span class=\"caps\">ATTRIBUTE</span> &#8211; future versions will only use Clone titles entered in adi_menu admin.</em></p>\n\n<p><code>ignore_alt_title=&quot;boolean&quot;</code></p>\n\n<p>- alternative titles, if configured, normally take precedence.  Setting this attribute will force normal titles to be output. Default = “0” (use alternative title if available).</p>\n\n<p><code>escape=&quot;method&quot;</code></p>\n\n<p>- convert characters in section titles to <span class=\"caps\">HTML</span> entities. Default = &#8220;&#8221; (convert all applicable characters, using <span class=\"caps\">UTF</span>-8 character set). Use “html” to convert all applicable characters using <span class=\"caps\">ISO</span>-8859-1 character set only or “htmlspecial” to only convert special characters (&amp;, “, ‘, &lt;, &gt;).</p>\n\n<h3><strong>Sorting</strong></h3>\n\n<p><code>default_first=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the default section should be listed first. Default = “1” (Yes).</p>\n\n<p><code>sort=&quot;sort values&quot;</code></p>\n\n<p>- the sort method to be used for sections. Default = “adi_menu_sort”, i.e. the order specified in adi_menu admin tab (or database order if all set to zero). Other options: &#8220;&#8221; - the order in which sections were originally added; “name” &#8211; alphabetical order; “adi_menu_sort, name” &#8211; use sort order, then if same use alphabetical order.</p>\n\n<h3><strong>Submenus</strong></h3>\n\n<p><code>sub_menu=&quot;boolean&quot;</code></p>\n\n<p>- output a submenu starting with children of the currently active section . Default=“0” (complete menu generated).</p>\n\n<p><code>sub_menu_level=&quot;level&quot;</code></p>\n\n<p>- the submenu level to be output, where “level” is 2 or more. Default=“0” (complete menu generated). This attribute is section-sensitive, so submenus are based on the ancestry of the currently active section. <em>Note that the first submenu is level 2.</em></p>\n\n<h3><strong>Articles</strong></h3>\n\n<p><code>articles=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether articles should be included in the menu. Default = “0” (No).</p>\n\n<p><code>article_position=&quot;position&quot;</code></p>\n\n<p>- defines where the articles should be displayed in relation to sections in submenus. Values: “after” (place articles after sections), “before” (place articles before sections), “dovetail” (interleaves articles and subsections alphabetically according to title). Default = “after”.</p>\n\n<p><code>active_articles_only=&quot;boolean&quot;</code></p>\n\n<p>- set if you want articles listed for the currently active section/category only. Default = “0” (articles in all sections listed).</p>\n\n<p><code>article_include=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections that should have their articles included in the menu. Default = &#8220;&#8221; (all).</p>\n\n<p><code>article_exclude=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections that won&#8217;t have their articles included in the menu. Default = &#8220;&#8221; (none).</p>\n\n<p><code>article_sort=&quot;sort values&quot;</code></p>\n\n<p>- specifies how articles under menu items should be sorted. Default = “Posted desc”. For database order use <code>article_sort=&quot;&quot;</code>. If used in conjunction with <code>article_position=&quot;dovetail&quot;</code> then only “Title”, “Title asc” or “Title desc” are recognised. <em>New article mode only.</em></p>\n\n<p>- section-specific article sorting is also available:</p>\n\n<ul>\n	<li><code>article_sort=&quot;section1:title&quot;</code>	- default sort “Posted desc”, articles in section1 sorted by “title”</li>\n	<li><code>article_sort=&quot;title;section2:posted desc&quot;</code>	- default sort “title”, articles in section2 sorted by “Posted desc”</li>\n	<li><code>article_sort=&quot;;section1:posted desc;section2:custom_5&quot;</code>	- default sort is database order, articles in section1 sorted by “Posted desc” &amp; section2 articles sorted by custom field “custom_5”</li>\n</ul>\n\n<p><code>article_attr=\'article_custom attributes\'</code></p>\n\n<p>- a list of <code>&lt;txp:article_custom /&gt;</code> attributes, separated by spaces, for controlling articles in the menu. Default = &#8216;&#8217; (i.e. <code>&lt;txp:article_custom /&gt;</code> defaults, except <code>limit</code> which is 9999). For example: <code>article_attr=\'excerpted=&quot;y&quot; limit=&quot;20&quot;\'</code>. <em>Note the use of single quotes.</em></p>\n\n<p><code>cat_article_include=&quot;category list&quot;</code></p>\n\n<p>- comma separated list of categories that should have their articles included in the menu (placed under “sections” that have been redirected to those categories). Default = &#8220;&#8221; (all).</p>\n\n<p><code>cat_article_exclude=&quot;category list&quot;</code></p>\n\n<p>- comma separated list of categories that won&#8217;t have their articles included in the menu (placed under “sections” that have been redirected to those categories). Default = &#8220;&#8221; (none).</p>\n\n<p><code>cat_article_sort=&quot;sort values&quot;</code></p>\n\n<p>- specifies how articles under category parents should be sorted. Default = &#8220;&#8221; (i.e. same as ‘article_sort’ attribute). For database order use <code>cat_article_sort=&quot;&quot;</code>. If used in conjunction with <code>article_position=&quot;dovetail&quot;</code> then only “Title”, “Title asc” or “Title desc” are recognised.</p>\n\n<p>- category-specific article sorting is also available:</p>\n\n<ul>\n	<li><code>cat_article_sort=&quot;cat1:title&quot;</code>	- default sort same as ‘article_sort’ attribute, articles in cat1 sorted by “title”</li>\n	<li><code>cat_article_sort=&quot;title;cat2:posted desc&quot;</code>	- default sort “title”, articles in cat2 sorted by “Posted desc”</li>\n	<li><code>cat_article_sort=&quot;;cat1:posted desc;cat2:custom_5&quot;</code>	- default sort is database order, articles in cat1 sorted by “Posted desc” &amp; cat2 articles sorted by custom field “custom_5”</li>\n</ul>\n\n<p><code>cat_article_attr=\'article_custom attributes\'</code></p>\n\n<p>- a list of <code>&lt;txp:article_custom /&gt;</code> attributes, separated by spaces, for controlling the appearance of articles taken from categories. Default = &#8216;&#8217; (i.e. same as ‘article_attr’ attribute). For example: <code>article_attr=\'sort=&quot;Title&quot; limit=&quot;20&quot;\'</code>. <em>Note the use of single quotes.</em></p>\n\n<h3><strong>Markup</strong></h3>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the whole menu (including the label). Default = &#8220;&#8221; (no tag). <em>If there is no menu to output then no wraptag is generated.</em></p>\n\n<p><code>label=&quot;text&quot;</code></p>\n\n<p>- some text to precede the menu. Default = &#8220;&#8221; (no label). <em>If there is no menu to output then no label is generated.</em></p>\n\n<p><code>labeltag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the label. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>link_span=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the contents of the links should be wrapped in a span, i.e. <code>&lt;a&gt;&lt;span&gt;...&lt;/span&gt;&lt;/a&gt;</code>. Default = “0” (No).</p>\n\n<p><code>list_span=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the contents of the <code>&lt;li&gt;</code> should be wrapped in a span, i.e. <code>&lt;li&gt;&lt;span&gt;&lt;a&gt;...&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;</code>. Default = “0” (No).</p>\n\n<p><code>speaking_block=&quot;boolean&quot;</code></p>\n\n<p>- enables speaking blocks. Default = “0” (no speaking blocks). By default, speaking blocks are generated from the section&#8217;s <em>sticky</em> article excerpt.</p>\n\n<p><code>speaking_block_form=&quot;form name&quot;</code></p>\n\n<p>- alternative form to be used for the speaking block. Default = &#8220;&#8221; (none).</p>\n\n<p><code>suppress_url=&quot;mode&quot;</code></p>\n\n<p>- suppress the <span class=\"caps\">URL</span> of the <code>href</code> attribute in a parent section link (i.e. set it to “#”). Set to “all” to suppress all parent section links, “top” to suppress top-level parent section links only or “active” to suppress currently active parents. Default= &#8220;&#8221; (do not suppress <span class=\"caps\">URL</span>).</p>\n\n<p><code>role=&quot;role name&quot;</code></p>\n\n<p>- <span class=\"caps\">ARIA</span> role attribute automatically added to menu&#8217;s wraptag when admin preference “Doctype” is HTML5. Default = “navigation”.</p>\n\n<h3><strong>Classes/IDs</strong></h3>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- class applied to the top level <code>&lt;ul&gt;</code>. Default = “section_list”.</p>\n\n<p><code>menu_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the top level <code>&lt;ul&gt;</code>.  Default = “mainmenu”.</p>\n\n<p><code>wraptag_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the wraptag.  Default = “menu_wrapper”.</p>\n\n<p><code>wraptag_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>label_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the label&#8217;s tag.  Default = “menu_label”.</p>\n\n<p><code>label_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the label&#8217;s tag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>active_class=&quot;class name&quot;</code></p>\n\n<p>- class applied to the current section (or category) link. Default = “active_class”.</p>\n\n<p><code>active_li_class=&quot;class name&quot;</code></p>\n\n<p>- class applied to the current section (or category) <code>&lt;li&gt;</code> element. Default = &#8220;&#8221; (no class).</p>\n\n<p><code>parent_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on section <code>&lt;li&gt;</code> that are parents. Default = “menuparent”.</p>\n\n<p><code>list_id=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the <code>&lt;li&gt;</code> elements should have unique IDs applied. IDs are based on the section names. Default = “0” (No). Note that cloned section IDs will have a suffix of “_clone” added.</p>\n\n<p><code>list_id_prefix=&quot;text&quot;</code></p>\n\n<p>- the prefix to be used for the <code>&lt;li&gt;</code> IDs. Default = “menu_”.</p>\n\n<p><code>list_prefix=&quot;string&quot;</code></p>\n\n<p>- string to be used as a prefix. Default = &#8220;&#8221; (no prefix). <em>Note: prefix is inserted within its own <code>&lt;span&gt;</code> (and <code>class</code>)</em>. For example:<br />\n<code>&lt;li&gt;&lt;span class=&quot;menu_prefix&quot;&gt;PREFIX&lt;/span&gt;&lt;a&gt;...&lt;/a&gt;&lt;/li&gt;</code><br />\nIf <code>link_span</code> and <code>list_span</code> are also switched on, then the markup becomes:<br />\n<code>&lt;li&gt;&lt;span&gt;&lt;span class=&quot;menu_prefix&quot;&gt;PREFIX&lt;/span&gt;&lt;a&gt;&lt;span&gt;...&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;</code><br />\n<em>Use in conjunction with <code>first_class</code> attribute to turn prefix into a separator.</em></p>\n\n<p><code>prefix_class=&quot;class name&quot;</code></p>\n\n<p>- class added to the list prefix <code>&lt;span&gt;...&lt;/span&gt;</code>. Default = “menu_prefix”.</p>\n\n<p><code>first_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the <em>first</em> <code>&lt;li&gt;</code> within a <code>&lt;ul&gt;</code>. Default = &#8220;&#8221; (no class). <em>Note that this class is applied to the first item of every level of the menu.</em></p>\n\n<p><code>last_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the <em>last</em> <code>&lt;li&gt;</code> within a <code>&lt;ul&gt;</code>. Default = &#8220;&#8221; (no class). <em>Note that this class is applied to the last item of every level of the menu.</em></p>\n\n<p><code>odd_even=&quot;boolean&quot;</code></p>\n\n<p>- add “menu_odd” &amp; “menu_even” classes to <code>&lt;li&gt;</code>.  Default = “0” (no odd/even classes).</p>\n\n<p><code>article_class=&quot;class name&quot;</code></p>\n\n<p>- the class applied to <code>&lt;li&gt;</code> elements that contain articles. Default = “menu_article”.</p>\n\n<h2><strong>adi_menu_info tag</strong></h2>\n\n<p>Use the <code>&lt;txp:adi_menu_info /&gt;</code> tag when you need to access adi_menu information.  This tag is section sensitive by default and would normally be used in this way in speaking blocks.  However, the tag can be used anywhere if you explicitly specify the section(s) in the “sections” attribute.</p>\n\n<h2><strong>adi_menu_info &#8211; tag attributes</strong></h2>\n\n<p><code>sections=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections to display info about. Default = &#8220;&#8221; (current section).</p>\n\n<p><code>type=&quot;information type&quot;</code></p>\n\n<p>- use one or more of the following types, to display pieces of information about the section(s) defined by the ‘sections’ attribute. Default: “title”.</p>\n\n<ul>\n	<li>title (title)</li>\n	<li>alt_title (alternative title)</li>\n	<li>menu_title (title used in menu &#8211; i.e. based on alternative title availability and in speaking blocks, the ‘ignore_alt_title’ attribute as well)</li>\n	<li>parent (parent&#8217;s section name)</li>\n	<li>ancestors (list of ancestors, starting with parent)</li>\n	<li>exclude (“1” if excluded in adi_menu admin tab)</li>\n	<li>clone (“1” if cloned in adi_menu admin tab)</li>\n	<li>clone_title (cloned section&#8217;s title &#8211; if defined in adi_menu admin)</li>\n	<li>redirect_section (redirection &#8211; section name)</li>\n	<li>redirect_link (redirection &#8211; link ID)</li>\n	<li>redirect_category (redirection &#8211; category)</li>\n</ul>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- <span class=\"caps\">HTML</span> tag (without ‘&lt;&gt;’ brackets) to wrap the items output from the <i>type</i> attribute. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- <span class=\"caps\">CSS</span> class attribute applied to the wraptag. Default = &#8220;&#8221; (no class).</p>\n\n<p><code>break=&quot;tag&quot;</code></p>\n\n<p>- the <span class=\"caps\">HTML</span> tag (without ‘&lt;&gt;’ brackets) or string to separate each type item. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>breakclass=&quot;class name&quot;</code></p>\n\n<p>- <span class=\"caps\">CSS</span> class attribute applied to each break tag. Default = &#8220;&#8221; (no class).</p>\n\n<p><code>escape=&quot;method&quot;</code></p>\n\n<p>- convert characters in section titles to <span class=\"caps\">HTML</span> entities. Default = &#8220;&#8221; (convert all applicable characters, using <span class=\"caps\">UTF</span>-8 character set). Use “html” to convert all applicable characters using <span class=\"caps\">ISO</span>-8859-1 character set only or “htmlspecial” to only convert special characters (&amp;, “, ‘, &lt;, &gt;).</p>\n\n<h2><strong>adi_menu_if_info tag</strong></h2>\n\n<p>Use the <code>&lt;txp:adi_menu_if_info /&gt;</code> conditional tag when you need test adi_menu information.  This tag is section sensitive by default and would normally be used in this way in speaking blocks.  However, the tag can be used anywhere if you explicitly specify the section(s) in the “sections” attribute.</p>\n\n<h2><strong>adi_menu_if_info &#8211; tag attributes</strong></h2>\n\n<p><code>sections=&quot;section list&quot;</code></p>\n\n<p>- comma separated list of sections to be tested. Default = &#8220;&#8221; (current section).</p>\n\n<p><code>type=&quot;information type&quot;</code></p>\n\n<p>- use one or more of the following types, to test pieces of information about the section(s) defined by the ‘sections’ attribute. Default: “title”.</p>\n\n<ul>\n	<li>title (title)</li>\n	<li>alt_title (alternative title)</li>\n	<li>menu_title (title used in menu &#8211; i.e. based on alternative title availability and in speaking blocks, the ‘ignore_alt_title’ attribute as well)</li>\n	<li>parent (parent&#8217;s section name)</li>\n	<li>ancestors (ancestors, including parent)</li>\n	<li>exclude (if excluded in adi_menu admin tab)</li>\n	<li>clone (if cloned in adi_menu admin tab)</li>\n	<li>clone_title (if clone has a title defined in adi_menu admin)</li>\n	<li>redirect_section (if redirected to a section)</li>\n	<li>redirect_link (if redirected to a link)</li>\n	<li>redirect_category (if redirected to a category)</li>\n	<li>redirect (if redirected to either a section, link or category)</li>\n</ul>\n\n<p><code>value=&quot;value text&quot;</code></p>\n\n<p>- the value that you want to check against.  Default = &#8220;&#8221; (any non-blank value).</p>\n\n<h2><strong>adi_menu_info/adi_menu_if_info examples</strong></h2>\n\n<p>To output the name of the current section&#8217;s parent:</p>\n\n<pre><code>&lt;txp:adi_menu_info type=&quot;parent&quot; /&gt;</code></pre>\n\n<p>To output the current section&#8217;s alternative title (if defined in adi_menu admin) instead of the section title:</p>\n\n<pre><code>&lt;txp:adi_menu_if_info type=&quot;alt_title&quot;&gt;\n	&lt;h1&gt;&lt;txp:adi_menu_info type=&quot;alt_title&quot; /&gt;&lt;/h1&gt;\n&lt;txp:else /&gt;\n	&lt;h1&gt;&lt;txp:section title=&quot;1&quot; /&gt;&lt;/h1&gt;\n&lt;/txp:adi_menu_if_info&gt;</code></pre>\n\n<p>To see if a virtual section “myvirtualsection” exists:</p>\n\n<pre><code>&lt;txp:adi_menu_if_info sections=&quot;v_myvirtualsection&quot;&gt;\n	Virtual section &quot;myvirtualsection&quot; exists\n&lt;txp:else /&gt;\n	Virtual section &quot;myvirtualsection&quot; not defined\n&lt;/txp:adi_menu_if_info&gt;</code></pre>\n\n<p>To test if section “mysection” is redirected to a particular category:</p>\n\n<pre><code>&lt;txp:adi_menu_if_info sections=&quot;mysection&quot; type=&quot;redirect_category&quot; value=&quot;hope-for-the-future&quot;&gt;\n	Here\'s hoping for the best!\n&lt;txp:else /&gt;\n	Assume the worst ...\n&lt;/txp:adi_menu_if_info&gt;</code></pre>\n\n<p>To test if section “mysection” is an ancestor of current section:</p>\n\n<pre><code>&lt;txp:adi_menu_if_info type=&quot;redirect_category&quot; value=&quot;mysection&quot;&gt;\n	&quot;mysection&quot; is an ancestor\n&lt;txp:else /&gt;\n	&quot;mysection&quot; is not an ancestor\n&lt;/txp:adi_menu_if_info&gt;</code></pre>\n\n<h2><strong>adi_menu_breadcrumb tag</strong></h2>\n\n<p>Use the <code>&lt;txp:adi_menu_breadcrumb /&gt;</code> tag to display a breadcrumb trail for the current section, based on your menu hierarchy defined in the adi_menu admin tab.</p>\n\n<h2><strong>adi_menu_breadcrumb &#8211; tag attributes</strong></h2>\n\n<p><code>label=&quot;text&quot;</code></p>\n\n<p>- the text to precede the breadcrumb trail output. Default = “You are here: “.</p>\n\n<p><code>labeltag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the label. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>separator=&quot;text&quot;</code></p>\n\n<p>- the text to use as a separator between the crumbs. Default = “ &amp;#187; “.</p>\n\n<p><code>title=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the section titles or alternative titles should be used. Default = “1” (Yes).</p>\n\n<p><code>default_title=&quot;text&quot;</code></p>\n\n<p>- the title to be used for the ‘default’ section. Default = “Home”</p>\n\n<p><code>ignore_alt_title=&quot;boolean&quot;</code></p>\n\n<p>- alternative section titles, if configured, are normally used. Default = “0” (No).</p>\n\n<p><code>link=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the sections should links or not. Default = “1” (Yes).</p>\n\n<p><code>linkclass=&quot;class name&quot;</code></p>\n\n<p>- the <span class=\"caps\">CSS</span> class assigned to the breadcrumb links. Default = “noline”.</p>\n\n<p><code>link_last=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the last section crumb in list (i.e. the current section) should be a link or not. Only applies in article list mode. Default = “0” (No).</p>\n\n<p><code>include_default=&quot;boolean&quot;</code></p>\n\n<p>- specifies whether the ‘default’ section should be output. Default = “1” (Yes).</p>\n\n<p><code>escape=&quot;method&quot;</code></p>\n\n<p>- convert characters in section titles to <span class=\"caps\">HTML</span> entities. Default = &#8220;&#8221; (convert all applicable characters, using <span class=\"caps\">UTF</span>-8 character set). Use “html” to convert all applicable characters using <span class=\"caps\">ISO</span>-8859-1 character set only or “htmlspecial” to only convert special characters (&amp;, “, ‘, &lt;, &gt;).</p>\n\n<h3><strong>Breadcrumb trail example</strong></h3>\n\n<p>To output a breadcrumb trail, including the article&#8217;s title, try the following:</p>\n\n<pre><code>&lt;div id=&quot;breadcrumb&quot;&gt;\n	&lt;txp:adi_menu_breadcrumb /&gt;\n	&lt;txp:if_individual_article&gt;&amp;#187;&amp;#160;&lt;txp:title/&gt;&lt;/txp:if_individual_article&gt;\n&lt;/div&gt;</code></pre>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s options page and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Updates and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_menu\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=27778\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_menu\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n// NOTE\n\n/*\n	adi_menu - Section hierarchy, section menu and breadcrumb trail\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	1.4		- new feature: virtual sections\n			- new categories features:\n				- redirect to Article category option\n				- categoried articles in menu\n				- new attributes \'cat_article_include\', \'cat_article_exclude\', \'cat_article_sort\', \'cat_article_attr\'\n			- new admin feature: drag & drop section ordering\n			- new classes \'menu_virtual\', \'menu_category\', \'menu_clone\'\n			- new attribute \'ignore_alt_title\' for adi_menu & adi_breadcrumb tags\n			- new attributes \'exclude_clone\', \'role\' **, \'override_exclude\' & suppress_url_sections for adi_menu tag\n			- new attribute \'labeltag\' for adi_breadcrumb\n			- new attributes \'force_current\' & \'current_descendants_only\' (for detail)\n			- new tags: adi_menu_info & adi_menu_if_info\n			- fix: stop clone inheriting an alternative title\n			- fix: adi_breadcrumb now uses alternative title\n			- fix: article list defaults to live only (use status=\"\" for both live & sticky) **\n			- fix: active class applied to menu items that are redirected to active section **\n			- fix: disallow sections to be selected as parents of, or redirect to, themselves\n			- fix: disabled submenus on error pages\n			- fix: breadcrumb less crummy on error pages\n			- fix: prevent empty HTML class attribute when adi_breadcrumb \'linkclass\' is empty (thanks CeBe)\n			- fix: table prefix issue (thanks sochicomputerRU)\n			- deprecated attributes: \'clone_title\', \'test\', \'new_article_mode\' **\n			- deprecated \"old article mode\" **\n			- now updates lastmod\n			- TXP 4.5+ only\n			- updated for TXP 4.6\n	beta13	- removed versions output from debug\n			- fix: blank adi_menu_redirect_link now set to default (0) not \'\'\n			- fixed dodgy 4.6 fix: restored greyness to menu key item\n			** upgrade considerations\n			*** beta users visit Admin tab after install\n	1.3.1	- lifecycle \"upgrade\" pseudo-event\n			- fixed links to section tab in TXP 4.5\n	1.3		- TXP 4.5-ified\n			- moved install/uninstall to plugin options\n			- Textpack added\n			- tooltips thrown in the tip\n			- fix: preferences fully deleted on uninstall\n			- enhancement: adi_menu admin section redirect warning message\n			- enhancement: admin preference \"Prevent widowed words in article titles\" now obeyed (for springworks)\n			- code tidy up & old code removed\n			- change: removed import functionality\n			- change: admin tab name changed \"adi_menu\" -> \"Menu\"\n			- change: use pluggable_ui instead of old inject method in Article tab\n	1.2		- section-specific article sort (for bg)\n			- enhancement: adi_menu admin sections link to TXP section tab\n			- fix: ampersands in article titles (thanks renobird)\n			- fix: admin styling for Hive\n			- now uses lifecycle events for install/uninstall\n	1.1.1	- fixed: one adi_menu tag affecting operation of another (thanks Teemu)\n			- fix: renamed some globals\n			- fix: restrict style to adi_menu_admin tab only\n	1.1		- enhancement: section redirection (for me & caruso_g)\n			- enhancement: alternative section titles\n			- enhancement: adi_menu admin tab now only shows admin options (i.e. install/uninstall/import) if relevant\n			- enhancement: admin tab sanity checks - sections renamed or links deleted\n			- enhancement: write tab section popup list format & indent preferences (for Bloke)\n			- enhancement: new article method, attribute \'new_article_mode\' to switch off (\'test\' attribute ignored)\n			- new attribute: \'odd_even\' to generate <li> classes on odd/even items\n			- change: section sort now defaults to \"adi_menu_sort\" (use sort=\"\" to override)\n			- fix: dodgy bold in hierarchy summary, added italics & \"default\" now \"Home\"\n			- fix: errors in admin tab if sections renamed\n			- fix: submenus not being generated properly (again)\n	1.0		- testing only, not officially released\n	0.12.1	- fix: error on error page when sub_menu_level > 1 (thanks CeBe)\n			- fix: error with submenu if active section excluded in admin\n	0.12	- new attribute \'current_children_only\' (for ttr & caruso_g)\n			- enhancement: new (automatic) way of coping with accented characters (thanks ttr)\n			- enhancement: minor admin tab improvements - admin options (i.e. install/uninstall/import) only shown if relevant\n			- fix: error when adi_menu_breadcrumb used more than once on a page (thanks jcd)\n			- fix: current_children_only not functioning correctly (thanks jcd)\n			- fix: couple of uninitialised variables generating error_log messages (lines 827, 837)\n	0.11	- new adi_menu attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n			- enhancement: sections attribute now overrides admin excluded sections\n	0.10	- enhancement: speaking blocks\n			- new adi_menu attributes: \'speaking_block\' & \'speaking_block_form\'\n			- new adi_menu attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n			- fix: empty <ul></ul> output for section-less submenus\n	0.9.2	- fix: static sections (sed_section_fields) visible to non-Publisher users in Write tab (thanks Didjee)\n			- fix: sections with clones wrongly indented in Write tab section popup\n	0.9.1	- fix: accented characters getting escaped in admin (for jpdupont)\n			- enhancement: new \'escape\' attribute option: htmlspecial\n	0.9		- new adi_menu attribute: \'sub_menu_level\' (for emerald)\n			- fix: selected section in Write tab changes to first section in list after saving new article\n	0.8.1	- fix: submenus not being generated properly\n			- fix: suppressed output of \'id=\"\"\' & \'class=\"\"\' in top level <ul> when menu_id=\"\" & class=\"\"\n	0.8		- enhancement: section hierarchy in Write tab section popup\n			- new adi_menu attributes: \'first_class\' & \'last_class\' (for renobird)\n			- new adi_menu attributes: \'list_prefix\' & \'prefix_class\' (for nubian)\n			- new adi_menu attribute: suppress_url (for renobird)\n			- enhancement: adi_menu & adi_menu_breadcrumb tags now output error message if adi_menu not installed\n			- fix: MySQL v4 errors in adi_menu admin (for debegray)\n				- used \'TINYINT(1) DEFAULT 0\' instead of \'BOOLEAN DEFAULT FALSE\'\n				- where clause \'1=1\' instead of \'TRUE\'\n			- fix: loop detection failure with A->B->C->B sub-loops\n			- optimisation: removed section_list[\'name\'] & $hierarchy[\'name\'] array elements\n			- articles enhancement (beta) new attributes: \'article_class\', \'article_position\', \'article_sort\'\n	0.7		- new adi_menu & adi_menu_breadcrumb attribute: \'escape\'\n			- new adi_menu attribute: \'active_ancestors\' (for Didjee)\n			- fix: adi_menu admin hierarchy summary now shows children again\n			- fix: no longer get double row of admin page tabs before adi_menu installed\n	0.6		- enhancement: article capability added\n			- new adi_menu attributes: \'articles\', \'article_attr\', \'article_include\', \'article_exclude\'\n			- new adi_menu attributes: \'include_children\', \'include_current\' & \'active_parent\' (for macTigers)\n			- new adi_menu attribute: \'active_articles_only\' (for FireFusion)\n			- new adi_menu attribute: \'list_span\' (sIFR support, for Lasse Gyrn)\n			- code optimisations (thanks to net-carver)\n	0.5		- fix: adi_menu errors if \'default\' section excluded (thanks Uli)\n			- fix: adi_menu tab section hierarchy would generate lots of errors if adi_menu not installed\n			- enhancement: sections attribute functionality improved (for Si & freischwimmen)\n				- children now output automatically\n				- new tags \'include_parent\' & \'include_childless\'\n	0.4		- enhancement: adi_breadcrumb tag attribute \'link_last\' - last section crumb in list displayed in plain text (now the default behaviour)\n			- fix: adi_menu_breadcrumb error when visiting sections/pages that don\'t exist when in clean URL mode\n			- fix: adi_menu_breadcrumb displayed default section as link regardless of \'link\' attribute setting\n	0.3		- enhancement: new adi_menu tag attribute \'link_span\' - wrap <span>...</span> around contents of links\n			- enhancement: new adi_menu tag attributes \'list_id\' & \'list_id_prefix\' - output unique IDs to <li> elements\n			- enhancement: new adi_menu tag attribute \'active_li_class\' - output class on active <li>\n			- enhancement: adi_menu admin now displays summary of configured section hierarchy\n			- modification: adi_breadcrumb tag attribute \'sep\' deprecated for \'separator\'\n	0.2		- fix: adi_menu_breadcrumb error when visiting pages that are excluded in adi_menu admin\n			- fix: adi_menu_breadcrumb now copes with section loops, error message output\n			- fix: adi_menu tag can now be used more than once on a page\n			- enhancement: adi_menu admin section loop warning message\n			- enhancement: adi_menu admin now displays sections in alphabetical order\n	0.1		- initial release\n\n	DOWNGRADE (from 1.4 to 1.1+ only)\n	- go to adi_menu plugin options tab\n	- add \"&step=downgrade\" to end of URL & hit return\n	- then immediately install previous version of adi_menu\n\n*/\n\n/* TODO\n\n	- kill off clone column (presence of clone title will signify clone or not)\n	- kill off attributes deprecated in 1.4\n	- use pluggable_ui for adi_menu_article_tab()\n	- general purpose custom html attribute, e.g. data-xyz=\"abc\" (configurable per section in admin)\n	- deprecate old submenu method (probably just need to stop talking about it)\n	- make section_list array element names consistent (\'adi_menu_parent\' -> \'parent\')\n	- adi_menu_reborn\n		- cull attributes\n		- a_m_prune does all the heavy lifting, lose the acres of globals\n		- use generic term \"menu item\" instead of section\n		- container tag/user defineable form\n		- well defined menu/submenu scenarios & a_m recipes\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_menu\')\n		->register(\'adi_menu_breadcrumb\')\n		->register(\'adi_menu_info\')\n		->register(\'adi_menu_if_info\')\n	;\n}\n\n// variables used by both admin tab AND public side\nglobal $adi_menu_debug,$adi_menu_db_debug,$adi_menu_article_form,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_vs_prefix,$adi_menu_info_types;\n\n$adi_menu_debug = 0; // display admin debug info\n$adi_menu_db_debug = 0; // display database debug info\n\n$adi_menu_article_form = \'adi_menu_articles\'; //  for old article mode\n\n$adi_menu_vs_prefix = \'v_\'; // prefix for virtual sections (used in parent field and in markup)\n\n// DB fields			txp_section					adi_menu\n//	section name		name						name\n//	section title		title						title\n//	parent section		adi_menu_parent				parent\n//	alternative title	adi_menu_alt_title			alt_title\n//	cloned?				adi_menu_clone				clone\n//	clone title			adi_menu_title				clone_title\n//	section redirect	adi_menu_redirect_section	section\n//	link redirect		adi_menu_redirect_link		link\n//	category redirect	adi_menu_redirect_category	category\n//	sort				adi_menu_sort				sort\n\n$adi_menu_sql_fields = \'name,title,adi_menu_parent,adi_menu_title,adi_menu_exclude,adi_menu_clone,adi_menu_sort,adi_menu_redirect_section,adi_menu_redirect_link,adi_menu_redirect_category,adi_menu_alt_title\';\n\n$adi_menu_vs_sql_fields = \'id as adi_menu_id,name,title,parent as adi_menu_parent,exclude as adi_menu_exclude,sort as adi_menu_sort,section as adi_menu_redirect_section,link as adi_menu_redirect_link,category as adi_menu_redirect_category,alt_title as adi_menu_alt_title,clone as adi_menu_clone,clone_title as adi_menu_title\';\n\n// menu info type to DB column mapping - used by <txp:adi_menu_info /> & <txp:adi_menu_if_info /> tags\n$adi_menu_info_types =\n	array(\n	// indexed by user-supplied \"type\": \'column\' => DB column(s) to reference, \'blank\' => what constitutes a blank/empty/not-set value (column lists only used by <txp:adi_menu_if_info /> - TRUE if any not blank)\n		// directly mapped\n		\'parent\'			=> array(\'column\' => \'adi_menu_parent\', \'blank\' => \'\'),\n		\'title\'				=> array(\'column\' => \'title\', \'blank\' => \'\'),\n		\'alt_title\'			=> array(\'column\' => \'adi_menu_alt_title\', \'blank\' => \'\'),\n		\'exclude\'			=> array(\'column\' => \'adi_menu_exclude\', \'blank\' => \'0\'), // boolean\n		\'clone\'				=> array(\'column\' => \'adi_menu_clone\', \'blank\' => \'0\'), // boolean\n		\'clone_title\'		=> array(\'column\' => \'adi_menu_title\', \'blank\' => \'\'),\n		\'redirect_section\'	=> array(\'column\' => \'adi_menu_redirect_section\', \'blank\' => \'\'),\n		\'redirect_link\'		=> array(\'column\' => \'adi_menu_redirect_link\', \'blank\' => \'0\'), // column DEFAULT is zero\n		\'redirect_category\'	=> array(\'column\' => \'adi_menu_redirect_category\', \'blank\' => \'\'),\n		// special types\n		\'redirect\'			=> array(\'column\' => \'adi_menu_redirect_section,adi_menu_redirect_link,adi_menu_redirect_category\', \'blank\' => \'\'), // column list: TRUE if any not blank\n		\'menu_title\'		=> array(\'column\' => \'title\', \'blank\' => \'\'), // \"deduced\" information, based on alternative title availability & ignore_alt_title attribute\n		\'ancestors\'			=> array(\'column\' => \'adi_menu_parent\', \'blank\' => \'\') // find ancestors, starting with parent\n	);\n\nif (@txpinterface == \'admin\') {\n	global $adi_menu_txp460;\n\n	// it\'s a modern world\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	$adi_menu_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	adi_menu_init();\n}\n\nfunction adi_menu_init() {\n// general admin setup\n	global $prefs,$event,$textarray,$adi_menu_url,$adi_menu_prefs,$adi_menu_debug,$adi_menu_db_debug,$adi_menu_sed_sf_installed,$adi_menu_plugin_status;\n\n	$adi_menu_installed = adi_menu_installed();\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_menu_gtxt = array(\n		\'adi_alt_title\' => \'Alternative title\',\n		\'adi_clone_title\' => \'Clone title\',\n		\'adi_clone\' => \'Clone\',\n		\'adi_for_example\' => \'For example\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_menu\' => \'Menu\',\n		\'adi_menu_duplicate_warning\' => \'Standard/virtual section name duplicates\',\n		\'adi_menu_loop_warning\' => \'Parent/child loops found\',\n		\'adi_menu_order_update_failed\' => \'Menu order update failed\',\n		\'adi_menu_order_updated\' => \'Menu order updated\',\n		\'adi_menu_parent_warning\' => \'Sections with non-existant parents\',\n		\'adi_menu_redirect_category_warning\' => \'Sections redirected to non-existant categories\',\n		\'adi_menu_redirect_link_warning\' => \'Sections redirected to non-existant links\',\n		\'adi_menu_redirect_section_warning\' => \'Sections redirected to non-existant sections\',\n		\'adi_menu_summary_footnote\' => \'The menu may be modified further using adi_menu tag attributes\',\n		\'adi_menu_summary_note_drag_drop\' => \'Drag and drop menu items to change order\',\n		\'adi_menu_summary_note_excluded\' => \'Excluded sections are shown in <span>grey</span>\',\n		\'adi_menu_summary_note_key\' => \'sections in <b>bold</b> are redirected, sections in <i>italics</i> have alternative titles\',\n		\'adi_menu_summary_note_key2\' => \'Key: <span><b>Redirected sections</b>, <i>Alternative titles</i>, \"Virtual sections\", <span>Excluded sections</span></span>\',\n		\'adi_menu_summary_note_prefix\' => \'In adi_menu tags, virtual sections must be referenced using a prefix\',\n		\'adi_menu_summary_note_virtual\' => \'Virtual sections are shown in &quot;quotes&quot;\',\n		\'adi_menu_summary_note\' => \'The above configuration will generate the following section hierarchy\',\n		\'adi_menu_update_fail\' => \'Menu update failed\',\n		\'adi_menu_update_order\' => \'Update menu order\',\n		\'adi_menu_updated\' => \'Menu updated\',\n		\'adi_menu_virtual_section_deleted\' => \'Virtual section deleted\',\n		\'adi_menu_virtual_section_exists\' => \'Virtual section already exists\',\n		\'adi_menu_virtual_sections\' => \'Virtual sections\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_redirect_category\' => \'Redirect category\',\n		\'adi_redirect_link_id\' => \'Redirect link ID\',\n		\'adi_redirect_link\' => \'Redirect link\',\n		\'adi_redirect_section\' => \'Redirect section\',\n		\'adi_summary\' => \'Summary\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_menu\' => \'Update menu\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_write_tab_select_format\' => \'Write tab section list format\',\n		\'adi_write_tab_select_indent\' => \'Write tab section list indent\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_menu_gtxt;\n\n	// Textpack\n	$adi_menu_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_menu\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_menu_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_menu_lifecycle\',\'plugin_lifecycle.adi_menu\');\n\n	// set the privilege levels\n	add_privs(\'adi_menu_admin\',\'1,2,3,6\');\n\n	// default plugin preference settings\n	$adi_menu_prefs = array(\n		\'write_tab_select_indent\'	=> \'0\', // or 0\n		\'write_tab_select_format\'	=> \'name\', // or \'title\'\n		\'write_tab_select_default\'	=> \'0\', // or 1\n	);\n\n	// adi_menu admin tab under \'Presentation\'\n	if ($adi_menu_installed) {\n		register_tab(\'presentation\',\'adi_menu_admin\',gtxt(\'adi_menu\'));\n		register_callback(\'adi_menu_admin\',\'adi_menu_admin\');\n		if ((adi_menu_prefs(\'write_tab_select_format\') != \'name\') || adi_menu_prefs(\'write_tab_select_indent\')) //??? TEST ON NEW INSTALLS\n			register_callback(\'adi_menu_article_tab\',\'article_ui\',\'section\');\n	}\n\n	// check out other plugins & their versions\n	if ($adi_menu_installed)\n		$adi_menu_sed_sf_installed = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'sed_section_fields\'\",$adi_menu_db_debug);\n\n	// plugin options\n	$adi_menu_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_menu\',$adi_menu_db_debug);\n	if ($adi_menu_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_menu\'); // defaults to priv \'1\' only\n		register_callback(\'adi_menu_options\',\'plugin_prefs.adi_menu\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_menu_options\');\n		register_tab(\'extensions\',\'adi_menu_options\',\'adi_menu options\');\n		register_callback(\'adi_menu_options\',\'adi_menu_options\');\n	}\n\n	// style\n	if ($event == \'adi_menu_admin\')\n		register_callback(\'adi_menu_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if ($event == \'adi_menu_admin\')\n		register_callback(\'adi_menu_admin_script\',\'admin_side\',\'head_end\');\n}\n\nfunction adi_menu_admin($event, $step) {\n// adi_menu admin action!\n	global $prefs,$adi_menu_sed_sf_installed,$adi_menu_prefs,$adi_menu_debug,$adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$installed = adi_menu_installed();\n\n	$something = gps(\"something\");\n	$res = FALSE;\n\n	if ($installed) {\n		if ($step == \"pref_update\") {\n			foreach ($adi_menu_prefs as $name => $value) {\n				if ($adi_menu_debug)\n					echo $name.\'=\'.ps($name).\' \';\n				adi_menu_prefs($name,doStripTags(ps($name)));\n			}\n		   	pagetop(\"adi_menu admin\",gTxt(\'preferences_saved\'));\n		}\n		else if ($step == \"update\") {\n			$sections = adi_menu_section_list(\'\',\'\',TRUE);\n			$message = adi_menu_update($sections);\n	   		pagetop(\"adi_menu admin\",$message);\n		}\n		else if ($step == \"sort_update\") {\n			$message = adi_menu_sort_update();\n	   		pagetop(\"adi_menu admin\",$message);\n		}\n		else if ($step == \"delete\") {\n			$name = gps(\'name\');\n			safe_delete(\'adi_menu\',\'name=\"\'.$name.\'\"\',$adi_menu_db_debug);\n	   		pagetop(\"adi_menu admin\",gTxt(\'adi_menu_virtual_section_deleted\'));\n		}\n		else // do nothing\n		   	pagetop(\'adi_menu admin\');\n	}\n	else // not installed\n		pagetop(\"adi_menu admin\",array(gTxt(\'adi_not_installed\'),E_ERROR));\n\n	if ($installed) {\n\n		adi_menu_upgrade(); // txpdev\n\n		// signal fiddle\n		if (($step == \'update\') || ($step == \'sort_update\') || ($step == \'delete\'))\n			update_lastmod();\n\n		// get to work\n		$db_sections = adi_menu_section_list(\'\',\'\',TRUE,\'name\');\n\n		if ($adi_menu_debug) {\n			$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n			dmp($section_list);\n			echo \'HIERARCHY:\';\n			$hierarchy = adi_menu_hierarchy($section_list);\n			dmp($hierarchy);\n			echo \'DESCENDANT LIST:\';\n			$descendant_list = array();\n			$descendant_list = adi_menu_descendants($hierarchy,\'adi_menu_root\');\n			dmp($descendant_list);\n			echo \'SECTION LEVELS:\';\n			$section_levels = adi_menu_section_levels($hierarchy);\n			dmp($section_levels);\n			echo \'CATEGORY/SECTION MAP:\';\n			$cat_section_map = adi_menu_cat_section_map($section_list);\n			dmp($cat_section_map);\n			if ($adi_menu_sed_sf_installed)\n				print \'sed_section_fields v\'.$adi_menu_sed_sf_installed[\'version\'].\' is installed & active\';\n		}\n\n		// SANITY CHECKS\n		// check for section loops\n		$out = array();\n		foreach ($db_sections as $section => $section_data)\n			if (adi_menu_loop_check($db_sections,$section_data[\'adi_menu_parent\']))\n				$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\');\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_loop_warning\').\': \'.implode(\', \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing parents\n		$missing_parents = adi_menu_parent_check($db_sections);\n		$out = array();\n		foreach ($missing_parents as $section => $parent)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$parent;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_parent_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect sections\n		$missing_sections = adi_menu_redirect_section_check($db_sections);\n		$out = array();\n		foreach ($missing_sections as $section => $redirect_section)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$redirect_section;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_section_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect links\n		$missing_links = adi_menu_redirect_link_check($db_sections);\n		$out = array();\n		foreach ($missing_links as $section => $link)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$link;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_link_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect categories\n		$missing_categories = adi_menu_redirect_category_check($db_sections);\n		$out = array();\n		foreach ($missing_categories as $section => $category)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section)? \' (virtual)\' : \'\').\' -> \'.$category;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_category_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for real section names clashing with virtual section names (e.g. real \'v_asd\' & virtual \'asd\')\n// 		$rs = safe_query(\'SELECT \'.safe_pfx(\'txp_section\').\'.name, \'.safe_pfx(\'adi_menu\').\'.name FROM \'.safe_pfx(\'txp_section\').\', adi_menu WHERE \'.safe_pfx(\'txp_section\').\".name = concat(\'\".$adi_menu_vs_prefix.\"\',\".safe_pfx(\'adi_menu\').\'.name);\',$adi_menu_db_debug);\n		$rs = safe_query(\'SELECT \'.safe_pfx(\'txp_section\').\'.name, \'.safe_pfx(\'adi_menu\').\'.name FROM \'.safe_pfx(\'txp_section\').\', \'.safe_pfx(\'adi_menu\').\' WHERE \'.safe_pfx(\'txp_section\').\".name = concat(\'\".$adi_menu_vs_prefix.\"\',\".safe_pfx(\'adi_menu\').\'.name);\',$adi_menu_db_debug);\n		$out = array();\n		while ($a = nextRow($rs))\n			$out[] = $adi_menu_vs_prefix.$a[\'name\'].\' (virtual \'.$a[\'name\'].\')\';\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_duplicate_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// hide redirect categories column if there\'re no categories defined\n		$categories_found = adi_menu_category_popup(\'test\',\'\');\n		if ($categories_found)\n			$hide_cats = \'\';\n		else\n			$hide_cats = \' class=\"adi_menu_hide\"\';\n\n		// output adi_menu settings table\n		echo form(\n			hed(gTxt(\'adi_menu\'),1,\' class=\"txp-heading\"\')\n			.startTable(\'list\',\'\',\"edit-table txp-list\")\n			.tag(\n				tr(\n					hcell(gTxt(\'section_name\'))\n					.hcell(gTxt(\'title\'))\n					.hcell(gTxt(\'adi_alt_title\'))\n					.hcell(gTxt(\'exclude\'))\n					.hcell(gTxt(\'parent\'))\n					.hcell(gTxt(\'sort_value\'),\'\',\' class=\"adi_menu_sort\"\') // hidden if jQuery-UI is available\n					.hcell(gTxt(\'adi_clone\'))\n					.hcell(gTxt(\'adi_clone_title\'))\n					.hcell(gTxt(\'adi_redirect_section\'))\n					.hcell(gTxt(\'adi_redirect_link\'))\n					.hcell(gTxt(\'adi_redirect_category\'),\'\',$hide_cats) // may be hidden if there\'re no categories defined\n					.hcell(sp) // delete buttons\n				)\n				,\'thead\'\n			)\n			.adi_menu_display_settings($db_sections)\n			.endTable()\n			.tag(\n				fInput(\"submit\",\"update\",gtxt(\'save\'),\"publish\").\n				eInput(\"adi_menu_admin\").sInput(\"update\"),\n				\'div\'\n			)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_menu_form\'\n		);\n	}\n\n	// output hierarchy summary\n	global $default_first,$include_children,$default_title,$menu_id,$escape,$clone_title,$parent_class,$list_id; // globals: yuk!\n	if ($installed) {\n		$sections=$exclude=\"\";\n		$default_first=\"1\";\n		$list_id=\"1\";\n		$include_children=\"1\";\n		$menu_id = \"mainmenu\";\n		$escape = \'\';\n		$default_title = \'Home\';\n		$clone_title = \'Summary\';\n		$parent_class = \'menuparent\';\n		$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n		$hierarchy = adi_menu_hierarchy($section_list);\n		$out = adi_menu_markup($hierarchy,0);\n		echo \'<div id=\"adi_menu_summary\">\';\n		echo tag(gTxt(\'adi_summary\'),\'h2\');\n		$summary_note = \' (\'.gTxt(\'adi_menu_summary_note_key\').\').\';\n		$vs = safe_rows(\'name\',\'adi_menu\',\'1=1 ORDER BY RAND() LIMIT 1\');\n		echo graf(gTxt(\'adi_menu_summary_note\').\'.\'.sp.gTxt(\'adi_menu_summary_footnote\').\'.\');\n		if ($vs)\n			echo graf(gTxt(\'adi_menu_summary_note_prefix\').\' \"\'.$adi_menu_vs_prefix.\'\", \'.strtolower(gTxt(\'adi_for_example\')).\' \'.tag(\'sections=\"\'.$adi_menu_vs_prefix.$vs[0][\'name\'].\'\"\',\'em\').\'.\');\n		echo graf(gTxt(\'adi_menu_summary_note_drag_drop\').\'.\',\' class=\"adi_menu_summary_note_drag_drop\"\');\n		echo graf(gTxt(\'adi_menu_summary_note_key2\'),\' class=\"adi_menu_key\"\');\n		echo implode($out);\n		echo \'</div>\';\n	}\n\n	// plugin preferences\n	if ($installed)\n	    echo form(\n			tag(gTxt(\'edit_preferences\'),\"h2\")\n			.graf(\n				gTxt(\'adi_write_tab_select_format\')\n				.sp.sp\n				.tag(\n					radio(\'write_tab_select_format\',\'name\',(adi_menu_prefs(\'write_tab_select_format\') == \'name\'))\n					.sp\n					.gTxt(\'name\')\n					,\'label\'\n				)\n				.sp\n				.tag(\n					radio(\'write_tab_select_format\',\'title\',(adi_menu_prefs(\'write_tab_select_format\') == \'title\'))\n					.sp\n					.gTxt(\'title\')\n					,\'label\'\n				)\n			)\n			.graf(\n				gTxt(\'adi_write_tab_select_indent\')\n				.sp.sp\n				.tag(\n					radio(\'write_tab_select_indent\',\'0\',(adi_menu_prefs(\'write_tab_select_indent\') == \'0\'))\n					.sp\n					.gTxt(\'no\')\n					,\'label\'\n				)\n				.sp\n				.tag(\n					radio(\'write_tab_select_indent\',\'1\',(adi_menu_prefs(\'write_tab_select_indent\') == \'1\'))\n					.sp\n					.gTxt(\'yes\')\n					,\'label\'\n				)\n			)\n	        .fInput(\"submit\", \"do_something\",gTxt(\'adi_update_prefs\'), \"smallerbox\",\"\",\'\')\n	        .eInput(\"adi_menu_admin\")\n	        .sInput(\"pref_update\")\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_menu_form\'\n		);\n\n	if ($adi_menu_debug) {\n		echo \'PREFS:\'.br; // should create list automatically\n		foreach ($adi_menu_prefs as $name => $value)\n			echo $name.\': \'.adi_menu_prefs($name).br;\n	}\n}\n\nfunction adi_menu_options($event,$step) {\n// plugin options\n	global $adi_menu_debug,$adi_menu_db_debug,$adi_menu_url,$adi_menu_plugin_status;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_menu_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'downgrade\') {\n		$result = adi_menu_downgrade();\n		$result ? $message = gTxt(\'adi_downgraded\') : $message = array(gTxt(\'adi_downgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_menu_install();\n		$result ? $message = gTxt(\'adi_installed\') : $message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_menu_uninstall();\n		$result ? $message = gTxt(\'adi_uninstalled\') : $message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_menu - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		tag(\n			form(\n				fInput(\"submit\", \"do_something\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"install\")\n				,\'\',\'\',\'post\'\n			)\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	$uninstall_button =\n		tag(\n	    	form(\n				fInput(\"submit\", \"do_something\", gTxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"uninstall\")\n				,\'\',\'\',\'post\'\n			)\n			,\'div\'\n			,\' style=\"margin-top:5em\"\');\n\n	if ($adi_menu_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_menu_installed();\n\n	if ($installed) {\n		adi_menu_upgrade();\n		// options\n		echo tag(\n			tag(\'adi_menu \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_menu_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_menu_url[\'textpack_feedback\']))\n			.$uninstall_button\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	}\n	else // install button\n	    echo $install_button;\n\n	if ($adi_menu_debug) {\n		echo \"<p>Event: \".$event.\", Step: \".$step.\"</p>\";\n		echo \'<p>$adi_textpack (\'.$adi_menu_url[\'textpack\'].\'):</p>\';\n		$adi_textpack = file_get_contents($adi_menu_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n}\n\nfunction adi_menu_admin_script() {\n// jQuery magic for admin tab\n	global $adi_menu_txp460;\n\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$sort_value_jquery = \'\';\n	foreach ($section_list as $name => $section) {\n		$sort_value_jquery .= \'$(\"#mainmenu li#\'.$name.\' > input\").attr(\"value\",\"\'.$section[\'adi_menu_sort\'].\'\");\'.n;\n		if ($section[\'adi_menu_exclude\'])\n			$sort_value_jquery .= \'$(\"#mainmenu li#\'.$name.\'\").addClass(\"excluded\");\'.n;\n	}\n\n	$button_text = gTxt(\'adi_menu_update_order\');\n\n	$ui_script = \'\';\n	if (!$adi_menu_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<!-- drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	// adi_menu\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'form.adi_menu_form #list .adi_menu_sort\').hide(); // hide sort value column\n			$(\'#adi_menu_summary\').addClass(\'adi_menu_drag_drop\');\n\n			$(\"#mainmenu\").wrap(\'<form class=\"adi_menu_summary\" action=\"index.php#adi_menu_summary\" method=\"post\"></form>\');\n			$(\"#mainmenu li\").each(function(){\n				var this_id = $(this).attr(\'id\');\n				$(this).append(\'<input type=\"hidden\" value=\"\" class=\"sort_value\" />\');\n				$(\'input\',this).attr(\'name\',\'sort[\'+this_id+\']\');\n			});\n			$(\"#mainmenu li.menu_virtual\").each(function(){\n				var this_id = $(this).attr(\'id\');\n				$(this).children(\'input\').attr(\'name\',\'virtual_sort[\'+this_id+\']\'); // immediate children\n			});\n\n			$sort_value_jquery\n\n			$(\"form.adi_menu_summary\").append(\'<input type=\"hidden\" name=\"event\" value=\"adi_menu_admin\" />\');\n			$(\"form.adi_menu_summary\").append(\'<input type=\"hidden\" name=\"step\" value=\"sort_update\" />\');\n			$(\"form.adi_menu_summary\").append(\'<input class=\"smallerbox\" type=\"submit\" name=\"do_something\" value=\"$button_text\" />\');\n			$(\"#mainmenu\").sortable({\n				items: \"li\",\n				opacity: 0.6,\n// 				cursor: \'move\',\n				containment: \'form.adi_menu_summary\',\n				grid: [10, 10],\n// 				connectWith: \".connectedSortable\"\n			});\n			// update sort values\n			$(\"#mainmenu\").bind(\"sortstop\",function(event, ui) {\n				$(\'input.sort_value\').each(function(i) { // renumber sort values\n					var this_input = $(this);\n					this_input.val(i+1);\n				});\n			});\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_menu_style() {\n// some style for the admin page\n	echo\n		\'<style type=\"text/css\">\n			/* adi_menu */\n			.txp-body { max-width:none }\n			h2 { font-weight:bold }\n			#adi_menu_summary { margin:2em 10em 2em; padding:1em 0 2em; border:solid #ccc; border-width:1px 0 }\n			#adi_menu_summary ul li li { margin-left:2em }\n			#adi_menu_summary .menu_redirect > a { font-weight:bold }\n			#adi_menu_summary .menu_alt_title > a { font-style:italic }\n			#adi_menu_summary .menu_virtual > a:before { content:\"\\0022\" }\n			#adi_menu_summary .menu_virtual > a:after { content:\"\\0022\" }\n			#adi_menu_summary #mainmenu { margin-bottom:2em } /* helps drag&drop */\n			#adi_menu_summary ul { margin-bottom:0.5em } /* helps drag&drop */\n			#adi_menu_summary.adi_menu_drag_drop ul li { list-style-type:none }\n			#adi_menu_summary.adi_menu_drag_drop ul li:before { content: \"\\2195\\00a0\"; }\n			#adi_menu_summary .adi_menu_key span { color:#963 }\n			#adi_menu_summary .adi_menu_key span span, #adi_menu_summary li.excluded a { color:grey }\n			.adi_menu_summary_note_drag_drop { display:none }\n			#adi_menu_summary.adi_menu_drag_drop .adi_menu_summary_note_drag_drop { display:block }\n			.adi_menu_warning { margin:1em; text-align:center; font-weight:bold }\n			.adi_menu_form { margin-top:2em; text-align:center }\n			.adi_menu_form div { margin-top:2em }\n			.adi_menu_hide { display:none }\n			option.adi_menu_virtual:before, option.adi_menu_virtual:after { content:\\\'\"\\\' }\n			a.dlink { font-size:120% }\n			form h1 { display:none }\n			/* TXP 4.6 */\n			main.txp-body #adi_menu_summary .adi_menu_key span { color:#004cbf }\n			main.txp-body #adi_menu_summary .adi_menu_key span span { color:grey }\n			main.txp-body #adi_menu_summary { margin:2em 0 2em }\n\n		</style>\';\n}\n\nfunction adi_menu_column_found($column,$table=\'txp_section\') {\n// check for presence of a column in a table\n	global $adi_menu_db_debug;\n\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($table).\" LIKE \'\".$column.\"\'\",$adi_menu_db_debug); // find out if column exists\n	$a = nextRow($rs);\n	return !empty($a);\n}\n\nfunction adi_menu_installed() {\n// if \'adi_menu_parent\' column present then assume adi_menu is installed\n	return(adi_menu_column_found(\'adi_menu_parent\'));\n}\n\nfunction adi_menu_truncate_text($text,$limit) {\n// not currently used\n	$words = do_list($text,\' \');\n	$truncated_text = \'\';\n	foreach ($words as $index => $this_word) {\n		$truncated_text .= $this_word.\' \';\n		if (strlen($truncated_text) >= $limit)\n			break;\n	}\n	$truncated_text = trim($truncated_text);\n	if ($truncated_text != $text)\n		$truncated_text .= \' ...\';\n	return $truncated_text;\n}\n\nfunction adi_menu_strip_prefix($text) {\n// strip \"v_\" from front of supplied text\n	global $adi_menu_vs_prefix;\n\n	$text = trim($text);\n	return (strpos($text,$adi_menu_vs_prefix) === 0) ? substr($text,strlen($adi_menu_vs_prefix)) : $text;\n}\n\nfunction adi_menu_is_virtual($name) {\n// section deemed to be virtual if:\n//		- its \'adi_menu_id\' value in section_list is not NULL (i.e. set to \'adi_menu\' table row id )\n//		- OR if not found in $section_list, its name begins with \"v_\"\n	global $section_list,$adi_menu_vs_prefix;\n\n	$name = trim($name);\n	if (isset($section_list[$name])) // requested section found is known\n		return ($section_list[$name][\'adi_menu_id\'] !== NULL);\n	else // requested section not found, so based verdict on its name\n		return (strpos(trim($name),$adi_menu_vs_prefix) === 0);\n}\n\nfunction adi_menu_display_settings($sections) {\n// generate section\'s table row in admin settings table\n	global $prefs;\n\n	$out = \'\';\n\n	// hide redirect categories column if there\'re no categories defined\n	$categories_found = adi_menu_category_popup(\'test\',\'\');\n	if ($categories_found)\n		$hide_cats = \'\';\n	else\n		$hide_cats = \' class=\"adi_menu_hide\"\';\n\n	// add new virtual section input fields\n	$sections[0][\'adi_menu_id\'] = 0; // zero = add new virtual section\n	$sections[0][\'name\'] = \'\';\n	$sections[0][\'title\'] = \'\';\n	$sections[0][\'adi_menu_parent\'] = \'\';\n	$sections[0][\'adi_menu_title\'] = \'\';\n	$sections[0][\'adi_menu_exclude\'] = 0;\n	$sections[0][\'adi_menu_clone\'] = 0;\n	$sections[0][\'adi_menu_sort\'] = 255; // the maximum value for an INT(3)\n	$sections[0][\'adi_menu_redirect_section\'] = \'\';\n	$sections[0][\'adi_menu_redirect_link\'] = \'\';\n	$sections[0][\'adi_menu_alt_title\'] = \'\';\n	$sections[0][\'adi_menu_redirect_category\'] = \'\';\n\n	$vs_found = FALSE;\n	foreach ($sections as $index => $section) {\n		$id = $section[\'adi_menu_id\']; // NULL = normal section, number = virtual section, zero = add new virtual section\n		$name = $section[\'name\'];\n		$title = $section[\'title\'];\n		$parent = $section[\'adi_menu_parent\'];\n		$clone_title = $section[\'adi_menu_title\'];\n		$exclude = $section[\'adi_menu_exclude\'];\n		$clone = $section[\'adi_menu_clone\'];\n		$sort = $section[\'adi_menu_sort\'];\n		$redirect_section = $section[\'adi_menu_redirect_section\'];\n		$redirect_link = $section[\'adi_menu_redirect_link\'];\n		$alt_title = $section[\'adi_menu_alt_title\'];\n		$redirect_category = $section[\'adi_menu_redirect_category\'];\n		if (!$vs_found && ($id !== NULL)) { // virtual sections subheading\n			$vs_found = TRUE;\n			$out.= tr(tda(strong(gTxt(\'adi_menu_virtual_sections\')),\' colspan=\"12\"\'));\n		}\n		$out .= tr(\n			// section name/link to section tab (normal sections) OR input field (virtual sections)\n			($id === NULL ?\n				tda(\'<a href=\"http://\'.$prefs[\'siteurl\'].\'/textpattern/?event=section&amp;step=section_edit&amp;name=\'.$index.\'\">\'.$index.\'</a>\')\n				:\n				tda(finput(\'text\',\"name[$index]\",$name))\n			)\n//			.tda(htmlspecialchars(adi_menu_truncate_text($title,12)))\n			// section title (input field for virtual sections)\n			.tda(\n				($id !== NULL ?\n					finput(\"text\",\"title[$index]\",$title)\n					:\n					htmlspecialchars($title)\n				)\n				.hinput(\"id[$index]\",$id)\n			)\n// 			.tda(htmlspecialchars($title))\n			.tda(finput(\"text\",\"alt_title[$index]\",$alt_title))\n			.tda(checkbox(\"exclude[$index]\", \"1\", $exclude))\n			.tda(adi_menu_section_popup($sections,$index,\"parent[$index]\",$parent))\n			.tda(finput(\"text\",\"sort[$index]\",$sort,\'\',\'\',\'\',4),\' class=\"adi_menu_sort\"\') // hidden if jQuery-UI is available\n			.tda(checkbox(\"clone[$index]\", \"1\", $clone))\n			.tda(finput(\"text\",\"clone_title[$index]\",$clone_title))\n			.tda(adi_menu_section_popup($sections,$index,\"redirect_section[$index]\",$redirect_section))\n			.tda(adi_menu_link_popup(\"redirect_link[$index]\",$redirect_link))\n			.tda(adi_menu_category_popup(\"redirect_category[$index]\",$redirect_category),$hide_cats) // may be hidden if there\'re no categories defined\n			.tda(adi_menu_delete_button($id,$name))\n		);\n	}\n	return $out;\n}\n\nfunction adi_menu_section_popup($section_list,$this_section,$select_name,$value) {\n// generate section/virtual section popup list for admin settings table\n	global $adi_menu_vs_prefix;\n\n	$out = \'<option value=\"\"></option>\';\n\n	$vs_found = FALSE;\n\n	if ($section_list) {\n		$out .= \'<optgroup label=\"Sections\">\';\n		foreach ($section_list as $section_name => $section) {\n			if ($section_name) { // don\'t want section \"0\" (i.e. the \"new\" virtual section)\n				$disabled = $selected = \'\';\n				if ($this_section && ($section_name == $this_section)) // if it\'s not new virtual section ($this_section = 0), prevent loop\n					$disabled = \' disabled=\"disabled\"\'; // don\'t want to offer section as a parent of (or redirect to) itself\n				if ($section_name == $value)\n					$selected = \' selected=\"selected\"\';\n				if (!$vs_found && ($section[\'adi_menu_id\'] !== NULL)) { // virtual sections subheading\n					$vs_found = TRUE;\n					$out .= \'</optgroup>\';\n					$out .= \'<optgroup label=\"Virtual Sections\">\';\n				}\n				if ($vs_found)\n					// value includes prefix (i.e. virtual section name), label shouldn\'t\n					$out .= \'<option value=\"\'.$section_name.\'\"\'.$disabled.$selected.\' class=\"adi_menu_virtual\">\'.adi_menu_strip_prefix($section_name).\'</option>\';\n				else\n					$out .= \'<option value=\"\'.$section_name.\'\"\'.$disabled.$selected.\'>\'.$section_name.\'</option>\';\n			}\n		}\n		$out .= \'</optgroup>\';\n	}\n\n	return tag($out,\'select\',\' name=\"\'.$select_name.\'\"\');\n}\n\nfunction adi_menu_link_popup($select_name,$value) {\n// generate link popup list for admin settings table\n// TODO - make this non-DB like section_popup\n	$rs = safe_column(\'id\', \'txp_link\', \'1=1\');\n	if ($rs)\n		return selectInput($select_name, $rs, $value, TRUE);\n	return false;\n}\n\nfunction adi_menu_category_popup($select_name,$value) {\n// generate category popup list for admin settings table\n// TODO - make this non-DB like section_popup\n	$rs = safe_column(\'name\',\'txp_category\',\'name != \"root\" AND type=\"article\"\');\n	if ($rs)\n		return selectInput($select_name, $rs, $value, TRUE);\n	return false;\n}\n\nfunction adi_menu_delete_button($id,$name) {\n// virtual section delete button [X]\n	global $adi_menu_txp460;\n\n	$event = \'adi_menu_admin\';\n	$step = \'delete\';\n// 	$url = \'?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name;\n//\n// 	if ($id)\n// 		return\n// 			\'<a href=\"\'\n// 			.$url\n// 			.\'\" class=\"dlink\" title=\"Delete?\" onclick=\"return verify(\\\'\'\n// 			.$name\n// 			.\' - \'\n// 			.gTxt(\'confirm_delete_popup\')\n// 			.\'\\\')\">&#215;</a>\';\n// 	else // don\'t want delete button (id=\"0\") for real sections or new section (id=\"\")\n// 		return sp;\n\n	if ($id) {\n		if ($adi_menu_txp460)\n			return\n				href(\n					span(\'Delete\',\' class=\"ui-icon ui-icon-trash\"\')\n					,array(\n						\'event\' => $event,\n						\'step\' => $step,\n						\'name\' => $name,\n						\'_txp_token\'    => form_token(),\n					)\n					,array(\n						\'class\'       => \'dlink destroy\',\n						\'title\'       => gTxt(\'delete\'),\n						\'data-verify\' => gTxt(\'confirm_delete_popup\'),\n					)\n				);\n		else\n			return\n				\'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name\n				.\'\" class=\"dlink\" title=\"\'.gTxt(\'delete\').\'\" onclick=\"return verify(\\\'\'\n				.gTxt(\'confirm_delete_popup\')\n				.\'\\\')\">\'\n				.\'&#215;\'\n				.\'</a>\';\n	}\n	else // don\'t want delete button (id=\"0\") for real sections or new section (id=\"\")\n		return sp;\n\n}\n\nfunction adi_menu_update($sections) {\n// update DB tables:\n//		adi_menu		txp_section\n//		id				-\n//		name			name\n// 		title			title\n// 		alt_title		adi_menu_alt_title\n// 		parent			adi_menu_parent\n// 		exclude			adi_menu_exclude\n// 		sort			adi_menu_sort\n// 		section			adi_menu_redirect_section\n// 		link			adi_menu_redirect_link\n// 		category		adi_menu_redirect_category\n//		clone			adi_menu_clone (clone yes/no)\n//		clone_title		adi_menu_title (clone title)\n	global $adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$message = gTxt(\'adi_menu_updated\');\n\n	$id = ps(\'id\'); // hidden field (NULL = normal section, number = virtual section, zero = add new virtual section)\n	$name = doStripTags(ps(\'name\')); // virtual sections only\n	$title = doStripTags(ps(\'title\')); // virtual sections only\n	$parent = doStripTags(ps(\'parent\'));\n	$clone_title = doStripTags(ps(\'clone_title\'));\n	$exclude = doStripTags(ps(\'exclude\'));\n	$clone = doStripTags(ps(\'clone\'));\n	$sort = doStripTags(ps(\'sort\'));\n	$redirect_section = doStripTags(ps(\'redirect_section\'));\n	$redirect_link = doStripTags(ps(\'redirect_link\'));\n	$alt_title = doStripTags(ps(\'alt_title\'));\n	$redirect_category = doStripTags(ps(\'redirect_category\'));\n\n	// force update to look at new section \"0\" input\n	$sections[0] = array();\n\n	$name_pairs = array(); // for recording name changes\n\n	foreach ($sections as $index => $section) {\n		if ($id[$index] != \'\') { // VIRTUAL SECTIONS, update adi_menu table\n			$where = \'id=\"\'.$id[$index].\'\"\';\n			$set = array();\n			$this_name = strtolower(sanitizeForUrl($name[$index]));\n			$this_name = adi_menu_strip_prefix($this_name);\n			$set[] = \'name=\"\'.doSlash($this_name).\'\"\';\n			if (trim($title[$index]) == \'\') // don\'t want blank title, default to section name\n				$set[] = \'title=\"\'.doSlash($name[$index]).\'\"\';\n			else\n				$set[] = \'title=\"\'.doSlash($title[$index]).\'\"\';\n			$set[] = \'alt_title=\"\'.doSlash($alt_title[$index]).\'\"\';\n			$set[] = \'parent=\"\'.doSlash($parent[$index]).\'\"\';\n			$set[] = \'clone_title=\"\'.doSlash($clone_title[$index]).\'\"\';\n			empty($exclude[$index]) ? $set[] = \'exclude=\"0\"\' : $set[] = \'exclude=\"1\"\';\n			empty($clone[$index]) ? $set[] = \'clone=\"0\"\' : $set[] = \'clone=\"1\"\';\n			$set[] = \'sort=\"\'.doSlash($sort[$index]).\'\"\';\n			$set[] = \'section=\"\'.doSlash($redirect_section[$index]).\'\"\';\n			if (!empty($redirect_link)) { // there might not be any links defined in the TXP database!\n				if ($redirect_link[$index] != \'\') // don\'t want to set integer field as \'\' (uses default 0 instead)\n					$set[] = \'link=\"\'.doSlash($redirect_link[$index]).\'\"\';\n			}\n			if (!empty($redirect_category)) { // there might not be any categories defined in the TXP database!\n				$set[] = \'category=\"\'.doSlash($redirect_category[$index]).\'\"\';\n			}\n			// update database\n			$set = implode(\', \',$set);\n			if ($id[$index] == \'0\') { // new virtual section\n				if ($this_name) { // it has a name\n					$found = safe_row(\'name\',\'adi_menu\',\'name=\"\'.$this_name.\'\"\');\n					if ($found)\n						$message = array(gTxt(\'adi_menu_virtual_section_exists\').\' - \'.$this_name,E_ERROR);\n					else\n						safe_insert(\'adi_menu\',$set,$adi_menu_db_debug);\n				}\n			}\n			else { // update existing virtual section\n				$old_name = safe_field(\'name\',\'adi_menu\',$where,$adi_menu_db_debug);\n				$res = safe_update(\'adi_menu\',$set,$where,$adi_menu_db_debug);\n				if ($res && ($old_name != $this_name)) { // record name change for later parent updates\n					$name_pairs[] = array(\'old\' => $old_name, \'new\' => $this_name);\n				}\n			}\n		}\n		else { // NORMAL SECTIONS, update txp_section table\n			$where = \'name=\"\'.$index.\'\"\';\n			$set = array();\n			$set[] = \'adi_menu_parent=\"\'.doSlash($parent[$index]).\'\"\';\n			$set[] = \'adi_menu_title=\"\'.doSlash($clone_title[$index]).\'\"\';\n			empty($exclude[$index]) ? $set[] = \'adi_menu_exclude=\"0\"\' : $set[] = \'adi_menu_exclude=\"1\"\';\n			empty($clone[$index]) ? $set[] = \'adi_menu_clone=\"0\"\' : $set[] = \'adi_menu_clone=\"1\"\';\n			$set[] = \'adi_menu_sort=\"\'.doSlash($sort[$index]).\'\"\';\n			$set[] = \'adi_menu_alt_title=\"\'.doSlash($alt_title[$index]).\'\"\';\n			$set[] = \'adi_menu_redirect_section=\"\'.doSlash($redirect_section[$index]).\'\"\';\n			if (!empty($redirect_link)) { // there might not be any links defined in the TXP database!\n				if ($redirect_link[$index] != \'\') // don\'t want to set integer field as \'\' (uses default 0 instead)\n					$set[] = \'adi_menu_redirect_link=\"\'.doSlash($redirect_link[$index]).\'\"\';\n			}\n			if (!empty($redirect_category)) { // there might not be any categories defined in the TXP database!\n				$set[] = \'adi_menu_redirect_category=\"\'.doSlash($redirect_category[$index]).\'\"\';\n			}\n			// update database\n			$set = implode(\', \',$set);\n			safe_update(\'txp_section\',$set,$where,$adi_menu_db_debug);\n		}\n	}\n\n	// name changes? meet the new parents\n	foreach ($name_pairs as $pair) {\n		safe_update(\'adi_menu\',\'parent=\"\'.$adi_menu_vs_prefix.$pair[\'new\'].\'\"\',\'parent=\"\'.$adi_menu_vs_prefix.$pair[\'old\'].\'\"\',$adi_menu_db_debug);\n		safe_update(\'txp_section\',\'adi_menu_parent=\"\'.$adi_menu_vs_prefix.$pair[\'new\'].\'\"\',\'adi_menu_parent=\"\'.$adi_menu_vs_prefix.$pair[\'old\'].\'\"\',$adi_menu_db_debug);\n	}\n\n	return $message;\n}\n\nfunction adi_menu_sort_update() {\n// update database with drag & drop sort values\n	global $adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$sort = ps(\'sort\');\n	$virtual_sort = ps(\'virtual_sort\');\n	if ($sort)\n		foreach ($sort as $name => $value)\n			$res = safe_update(\'txp_section\',\'adi_menu_sort=\"\'.$value.\'\"\',\'name=\"\'.$name.\'\"\',$adi_menu_db_debug);\n	if ($virtual_sort)\n		foreach ($virtual_sort as $name => $value)\n			$res = $res & safe_update(\'adi_menu\',\'sort=\"\'.$value.\'\"\',\'name=\"\'.adi_menu_strip_prefix($name).\'\"\',$adi_menu_db_debug);\n\n	if ($res)\n		$message = gTxt(\'adi_menu_order_updated\');\n	else\n		$message = gTxt(\'adi_menu_order_update_failed\');\n\n	return $message;\n}\n\nfunction adi_menu_install() {\n// add adi_menu\'s columns to txp_section table\n// note: TINYINT(1) DEFAULT 0 = BOOLEAN DEFAULT FALSE\n// sort value only goes up to 255 - INT(3)\n	global $adi_menu_db_debug;\n\n	if (adi_menu_installed())\n		return TRUE;\n	else // add basic columns to txp_section\n		return safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_parent VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_title VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_exclude TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_clone TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_sort TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL;\",$adi_menu_db_debug);\n}\n\nfunction adi_menu_uninstall() {\n// remove adi_menu\'s columns from txp_section table\n	global $adi_menu_db_debug;\n\n	// remove traditional columns\n	$res = safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_parent;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_title;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_exclude;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_clone;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_sort;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_redirect_section\')) // remove version 1.0 columns\n		$res = $res\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_section;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_link;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_accesskey\')) // remove version 1.0beta only columns\n		$res = $res\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_accesskey;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_tabindex;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_alt_title\')) // remove version 1.1 columns\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_alt_title;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_redirect_category\')) { // remove version 1.4 column & adi_menu_table\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_category;\",$adi_menu_db_debug);\n		$res = $res && safe_query(\"DROP TABLE \".safe_pfx(\'adi_menu\').\";\",$adi_menu_db_debug);\n	}\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_menu_%\'\",$adi_menu_db_debug);\n	return $res;\n}\n\nfunction adi_menu_upgrade() {\n// add additional adi_menu columns to txp_section table, if required\n	global $adi_menu_db_debug,$adi_menu_sql_fields;\n\n	// record the number of \'default\' articles\n	$rs = safe_rows(\'id\',\'textpattern\',\"section=\'default\'\");\n	adi_menu_prefs(\'write_tab_select_default\',count($rs));\n	// upgrade actions\n	$res = TRUE;\n	// version 1.0\n	if (!adi_menu_column_found(\'adi_menu_redirect_section\'))\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_section VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_link TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL;\",$adi_menu_db_debug);\n	// version 1.1\n	if (!adi_menu_column_found(\'adi_menu_alt_title\'))\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_alt_title VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug);\n	// version 1.4\n	if (!adi_menu_column_found(\'adi_menu_redirect_category\')) {\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_category VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug);\n		$res = $res && safe_query(\n			\"CREATE TABLE IF NOT EXISTS \"\n			.safe_pfx(\'adi_menu\')\n			.\" (\n				`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n				`name` VARCHAR(128) NOT NULL,\n				`title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`alt_title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`parent` VARCHAR(128) DEFAULT \'\' NOT NULL,\n				`clone` TINYINT(1) DEFAULT 0 NOT NULL,\n				`clone_title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`exclude` TINYINT(1) DEFAULT 0 NOT NULL,\n				`sort` TINYINT(3) UNSIGNED DEFAULT 255 NOT NULL,\n				`section` VARCHAR(128) DEFAULT \'\' NOT NULL,\n				`link` TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL,\n				`category` VARCHAR(128) DEFAULT \'\' NOT NULL\n			);\"\n			,$adi_menu_db_debug\n			);\n		// force unique sort values (using $rs array index from \"0\") to ensure later use of (unstable) uasort doesn\'t cause problems\n		$rs = safe_rows($adi_menu_sql_fields, \'txp_section\', \"1=1 ORDER BY adi_menu_sort\",$adi_menu_db_debug);\n		if ($rs)\n			foreach($rs as $index => $row)\n				$res = $res && safe_update(\'txp_section\',\'adi_menu_sort=\"\'.$index.\'\"\',\'name=\"\'.$row[\'name\'].\'\"\',$adi_menu_db_debug);\n		// new sections default to end of list (255 assigned to new virtual sections by adi_menu_update())\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" MODIFY COLUMN adi_menu_sort TINYINT(3) UNSIGNED DEFAULT 255 NOT NULL;\",$adi_menu_db_debug);\n	}\n	// version 1.4 supplemental\n	if (!adi_menu_column_found(\'clone\',\'adi_menu\'))\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\'adi_menu\').\" ADD clone TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\'adi_menu\').\" ADD clone_title VARCHAR(255) DEFAULT \'\' NOT NULL;\",$adi_menu_db_debug);\n\n	return $res;\n}\n\nfunction adi_menu_downgrade() {\n// downgrade to previous version - 1.4 to 1.1+ only\n	global $adi_menu_db_debug;\n\n	$res = TRUE;\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_category;\",$adi_menu_db_debug);\n	$res = $res && safe_query(\"DROP TABLE \".safe_pfx(\'adi_menu\').\";\",$adi_menu_db_debug);\n\n	return $res;\n}\n\nfunction adi_menu_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_menu\"\n// $step:	\"installed\", \"enabled\", \"disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_menu_debug;\n\n	$result = \'?\';\n	$upgrade = (($step == \"installed\") && adi_menu_installed());\n	if ($step == \'enabled\') {\n			$result = $upgrade = adi_menu_install();\n//		if (adi_menu_installed())\n//			$result = $upgrade = adi_menu_upgrade();\n//		else { // install, then upgrade\n//			if (adi_menu_install())\n//				$result = adi_menu_upgrade();\n//		}\n	}\n	else if ($step == \'deleted\')\n		$result = adi_menu_uninstall();\n	if ($upgrade)\n		$result = $result && adi_menu_upgrade();\n	if ($adi_menu_debug)\n		echo \"Event=$event, Step=$step, Result=$result, Upgrade=$upgrade\";\n}\n\nfunction adi_menu_add_form() {\n// add adi_menu\'s article form for old article mode\n	global $adi_menu_article_form;\n\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'\".$adi_menu_article_form.\"\'\")) {\n		$form = <<<EOF\n<li class=\"menu_article\"><txp:permlink><txp:title/></txp:permlink></li>\nEOF;\n		safe_insert(\'txp_form\',\n			\"name=\'\".$adi_menu_article_form.\"\',\n			type=\'article\',\n			Form=\'\".doSlash($form).\"\'\");\n	}\n}\n\nfunction adi_menu_prefs($name,$new_value=NULL) {\n// set/read preferences\n	global $prefs,$adi_menu_prefs;\n\n	if ($new_value !== NULL) { // save new value\n		set_pref(\'adi_menu_\'.$name,$new_value,\'adi_menu_admin\',2);\n		$prefs[\'adi_menu_\'.$name] = get_pref(\'adi_menu_\'.$name,$new_value,TRUE);\n	}\n	// take value from $prefs or, if not set, from $adi_menu_prefs[])\n	isset($prefs[\'adi_menu_\'.$name]) ? $value = $prefs[\'adi_menu_\'.$name] : $value = $adi_menu_prefs[$name];\n	return $value;\n}\n\nfunction adi_menu_loop_check($section_list,$parent,$ancestors=array()) {\n// check for section parent/child loops, return TRUE if loop found\n	if (empty($parent)) // no more ancestors\n		return FALSE;\n	else {\n		if (array_key_exists($parent,$ancestors)) // loop found\n			return TRUE;\n		else {\n			$ancestors[$parent]=\'\'; // add parent to list of ancestors\n			if (array_key_exists($parent,$section_list)) // check that parent exists (section rename issue)\n				return adi_menu_loop_check($section_list,$section_list[$parent][\'adi_menu_parent\'],$ancestors);\n			else\n				return FALSE;\n		}\n	}\n}\n\nfunction adi_menu_parent_check($db_sections) {\n// check for missing parents or link IDs\n	$missing_parents = array();\n	foreach($db_sections as $section => $value) {\n		$parent = $value[\'adi_menu_parent\'];\n		if (!empty($parent))\n			if (!array_key_exists($parent,$db_sections)) // check that parent exists (section deleted/renamed?)\n				$missing_parents[$section] = $parent;\n	}\n	return $missing_parents;\n}\n\nfunction adi_menu_redirect_section_check($db_sections) {\n// check section redirects\n	$missing_sections = array();\n	foreach($db_sections as $section => $value) {\n		$redirect_section = $value[\'adi_menu_redirect_section\'];\n		if (!empty($redirect_section))\n			if (!array_key_exists($redirect_section,$db_sections)) // check that redirect section exists (section deleted/renamed?)\n				$missing_sections[$section] = $redirect_section;\n	}\n	return $missing_sections;\n}\n\nfunction adi_menu_redirect_category_check($db_sections) {\n// check category redirects\n// TODO - make this non-DB (& for category_popup as well)\n\n	$missing_categories = array();\n\n	$db_cats = safe_column(\'name\',\'txp_category\',\'name != \"root\" AND type=\"article\"\');\n\n	if ($db_cats)\n		foreach($db_sections as $section => $value) {\n			$redirect_category = $value[\'adi_menu_redirect_category\'];\n			if (!empty($redirect_category))\n				if (!array_key_exists($redirect_category,$db_cats)) // check that redirect section exists (section deleted/renamed?)\n					$missing_categories[$section] = $redirect_category;\n		}\n\n	return $missing_categories;\n}\n\nfunction adi_menu_redirect_link_check($db_sections) {\n// check link redirects\n	$missing_links = array();\n	foreach($db_sections as $section => $value) {\n		$link = $value[\'adi_menu_redirect_link\'];\n		if ($link)\n			if (!adi_menu_get_link($link)) // check that link exists (link deleted?)\n				$missing_links[$section] = $link;\n	}\n	return $missing_links;\n}\n\nfunction adi_menu_get_link($link_id) {\n// read link from TXP database\n	$url = \'\';\n	if ($link_id) {\n		$sql_fields = \'url\';\n		$sql_table = \'txp_link\';\n		$sql_where = \'id=\'.$link_id;\n		$a = safe_row($sql_fields,$sql_table,$sql_where);\n		if ($a) // check if link ID actually exists\n			$url = $a[\'url\'];\n		else\n			$url = \'\';\n	}\n	return $url;\n}\n\nfunction adi_menu_section_sort($a,$b) {\n	return $a[\'adi_menu_sort\'] - $b[\'adi_menu_sort\'];\n}\n\nfunction adi_menu_section_list($sections=\'\',$exclude_list=\'\',$override_exclude=FALSE,$sort=\'adi_menu_sort\') {\n// create list of required sections in array indexed by section/v_section name =>\n// 		\'name\' => \'section/v_section name\'\n// 		\'title\' => \'Section title\'\n// 		\'adi_menu_parent\' => \'section/v_section name\'\n// 		\'adi_menu_title\' => \'Clone title\'\n// 		\'adi_menu_exclude\' => \'0/1\'\n// 		\'adi_menu_clone\' => \'0/1\'\n// 		\'adi_menu_sort\' => \'x\'\n// 		\'adi_menu_redirect_section\' => \'section name\'\n// 		\'adi_menu_redirect_link\' => \'link id#\'\n// 		\'adi_menu_redirect_category\' => \'article category name\'\n// 		\'adi_menu_alt_title\' => \'Alternative title\'\n// 		\'url\' => \'generated link, based on section or redirects\'\n// 		\'adi_menu_id\' => NULL for section, id# for v_section\n\n// $sections - BLANK = all, LIST = only include these in menu (others pruned out of hierarchy later - after adi_menu_section_list() call in adi_menu()) CONFUSING!\n// $exclude_list - LIST = explicitly excluded\n// $override_exclude - TRUE = blanket override exclude for all, FALSE = obey exclude, LIST = explicit list of section to override exclude\n\n	global $adi_menu_debug,$default_first,$default_title,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_vs_prefix;\n\n	$section_list = array();\n\n	// decide how big the net\'s going to be\n	if ($override_exclude && ($override_exclude === TRUE))\n		$bulkq = $bulkvq = \"1=1\"; // all sections regardless\n	else { // only sections that haven\'t been excluded in admin (with the possibility of an explicit override later)\n		$bulkq = \"adi_menu_exclude = 0\";\n		$bulkvq = \"exclude = 0\";\n	}\n\n	if ($adi_menu_debug) {\n		echo \"ADI_MENU_SECTION_LIST(): sections=\'$sections\'; exclude_list=\'$exclude_list\'; override_exclude=\'$override_exclude\'\".br;\n	}\n\n	// it\'s a start\n	$includeq = $bulkq;\n	$includevq = $bulkvq;\n\n	// explicit list of included sections (will override admin excluded)\n	if ($sections) {\n		$includeq = do_list($sections);\n		$includevq = array();\n		foreach ($includeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$includevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($includeq[$index]);\n			}\n		$includeq = implode(\"\',\'\",doSlash($includeq)); // \"\',\'\" ensures that each section name is single-quoted in list (first & last quote added below)\n		if ($includeq)\n			$includeq = \"($bulkq OR name IN (\'$includeq\'))\"; // brackets required to protect from operator precedence\n		else\n			$includeq = $bulkq;\n		$includevq = implode(\"\',\'\",doSlash($includevq));\n		if ($includevq)\n			$includevq = \"($bulkvq OR name IN (\'$includevq\'))\"; // brackets required to protect from operator precedence\n		else\n			$includevq = $bulkvq;\n	}\n\n	$excludeq = $excludevq = \'\';\n\n	// explicit list of excluded sections\n	if ($exclude_list) {\n		$excludeq = do_list($exclude_list);\n		$excludevq = array();\n		foreach ($excludeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$excludevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($excludeq[$index]);\n			}\n		$excludeq = implode(\"\',\'\",doSlash($excludeq));\n		if ($excludeq)\n			$excludeq = \" AND name NOT IN (\'$excludeq\')\";\n		else\n			$excludeq = \'\';\n		$excludevq = implode(\"\',\'\",doSlash($excludevq));\n		if ($excludevq)\n			$excludevq = \" AND name NOT IN (\'$excludevq\')\";\n		else\n			$excludevq = \'\';\n	}\n\n	$override_excludeq = $override_excludevq = \'\';\n\n	// explicit list of override-exclude sections\n	if ($override_exclude && ($override_exclude !== TRUE)) {\n		$override_excludeq = do_list($override_exclude);\n		$override_excludevq = array();\n		foreach ($override_excludeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$override_excludevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($override_excludeq[$index]);\n			}\n		$override_excludeq = implode(\"\',\'\",doSlash($override_excludeq));\n		if ($override_excludeq)\n			$override_excludeq = \" OR name IN (\'$override_excludeq\')\";\n		else\n			$override_excludeq = \'\';\n		$override_excludevq = implode(\"\',\'\",doSlash($override_excludevq));\n		if ($override_excludevq)\n			$override_excludevq = \" OR name IN (\'$override_excludevq\')\";\n		else\n			$override_excludevq = \'\';\n	}\n\n	$sortq = \" ORDER BY $sort\";\n\n	$q = $includeq.$excludeq.$override_excludeq.$sortq;\n	$vq = $includevq.$excludevq.$override_excludevq.$sortq;\n\n	if ($adi_menu_debug) {\n		echo \"NORMAL QUERY: bulkq=\'$bulkq\'; includeq=\'$includeq\'; excludeq=\'$excludeq\'; override_excludeq=\'$override_excludeq\'; sortq=\'$sortq\'\".br;\n		echo $q.br;\n		echo \"VIRTUAL QUERY: bulkvq=\'$bulkvq\'; includevq=\'$includevq\'; excludeq=\'$excludevq\'; override_excludevq=\'$override_excludevq\'; sortq=\'$sortq\'\".br;\n		echo $vq.br;\n	}\n\n	$rs = safe_rows_start($adi_menu_sql_fields,\'txp_section\',$q);\n	if ($rs) {\n\n		// normal sections\n		if ($adi_menu_debug) echo \"NORMAL SECTIONS: \";\n		while ($a = nextRow($rs)) {\n			if ($adi_menu_debug) echo $a[\'name\'].sp;\n			$found_as_virtual_section = FALSE;\n			if (preg_match(\'/\\A\'.$adi_menu_vs_prefix.\'/\',$a[\'name\'])) // look for clash of name in adi_menu, in the (hopefully) unlikely event that someone adds a section prefixed with \"v_\"\n				$found_as_virtual_section = safe_row(\'name\',\'adi_menu\',\' name=\"\'.adi_menu_strip_prefix($a[\'name\']).\'\"\');\n			if (!$found_as_virtual_section) {\n				$a[\'adi_menu_id\'] = NULL;\n				// create URL & add it to $a, noting any redirection along the way\n				if ($a[\'adi_menu_redirect_section\'])\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'adi_menu_redirect_section\']));\n				else if ($a[\'adi_menu_redirect_link\'])\n						$a[\'url\'] = adi_menu_get_link($a[\'adi_menu_redirect_link\']);\n				else if ($a[\'adi_menu_redirect_category\'])\n					$a[\'url\'] = pagelinkurl(array(\'c\' => $a[\'adi_menu_redirect_category\']));\n				else\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'name\']));\n				$section_list[$a[\'name\']] = $a;\n			}\n		}\n		if ($adi_menu_debug) echo br;\n\n		// virtual sections\n		$vs_rs = safe_rows_start($adi_menu_vs_sql_fields,\'adi_menu\',$vq);\n		if ($vs_rs) {\n			if ($adi_menu_debug) echo \"VIRTUAL SECTIONS: \";\n			while ($a = nextRow($vs_rs)) {\n				if ($adi_menu_debug) echo $a[\'name\'].sp;\n				// create URL & add it to $a, noting any redirection along the way\n				if ($a[\'adi_menu_redirect_section\'])\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'adi_menu_redirect_section\']));\n				else if ($a[\'adi_menu_redirect_link\'])\n						$a[\'url\'] = adi_menu_get_link($a[\'adi_menu_redirect_link\']);\n				else if ($a[\'adi_menu_redirect_category\'])\n					$a[\'url\'] = pagelinkurl(array(\'c\' => $a[\'adi_menu_redirect_category\']));\n				else\n					$a[\'url\'] = \'#\'; // set null link URL if no section/link/category\n				$section_list[$adi_menu_vs_prefix.$a[\'name\']] = $a; // virtual sections are prefixed\n			}\n			if ($adi_menu_debug) echo br;\n		}\n\n		// merge real & virtual sections\n		if ($sort == \'adi_menu_sort\') // sort array based on adi_menu_sort (otherwise virtual sections always come last)\n			uasort($section_list,\'adi_menu_section_sort\');\n\n		// deal with default section\n		if (array_key_exists(\'default\',$section_list)) { // default section included in menu\n			if ($section_list && $default_title) // set default section title\n				$section_list[\'default\'][\'title\'] = $default_title;\n			if ($section_list && $default_first) { // shift default section to beginning\n				$remember[\'default\'] = $section_list[\'default\']; // remember \'default\' element\n				unset($section_list[\'default\']); // remove \'default\' element\n				$section_list = array_merge($remember, $section_list); // join together, \'default\' now at beginning\n			}\n		}\n\n		if ($adi_menu_debug) echo br;\n	}\n\n	return $section_list;\n}\n\nfunction adi_menu_article_tab($event,$step,$default,$rs) {\n// tweak the article tab (section popup list)\n\n// 	if ((adi_menu_prefs(\'write_tab_select_format\') != \'name\') || adi_menu_prefs(\'write_tab_select_indent\')) {\n		$pattern = \'#name=\"Section\".*</select>#sU\';\n		$insert = \'adi_menu_article_section_popup\';\n		$out = preg_replace_callback($pattern,$insert,$default);\n		return $out;\n// 	}\n// 	else // don\'t fiddle with anything\n// 		return $default;\n}\n\nfunction adi_menu_section_indent($level) {\n// create indent for section in popup\n	$level -= 1;\n	$out = \'\';\n	if ($level)\n		for ($x=1; $x <= $level; $x++)\n			$out .= sp.sp;\n	return $out;\n}\n\nfunction adi_menu_article_section_popup() {\n// generate markup for section popup menu for Article/Write tab\n	global $step,$section_list,$adi_menu_sed_sf_installed,$adi_menu_vs_prefix;\n\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$hierarchy = adi_menu_hierarchy($section_list);\n	$section_levels = adi_menu_section_levels($hierarchy);\n	if ($step == \'edit\') { // editing existing ($step = GET var) or creating new article ($step = POST var)\n		if (!empty($GLOBALS[\'ID\'])) // newly-saved article, get section from POST vars\n			$select_section = gps(\'Section\');\n		else { // existing article, get ID from GET vars & section from database\n			$article_id = gps(\'ID\');\n			$select_section = safe_field(\"section\", \"textpattern\",\"id=\".$article_id);\n		}\n	}\n	else // empty article\n		$select_section = getDefaultSection(); // default section for articles (defined in Sections tab)\n	$out = \'name=\"Section\" class=\"list\">\';\n	foreach ($section_levels as $name => $level) { // create indented section popup list\n		if ($adi_menu_sed_sf_installed) { // sed_sections_fields installed & active so check if static section\n			$data = _sed_sf_get_data($name);\n			$data_array = sed_lib_extract_name_value_pairs($data);\n			if (isset($data_array[\'ss\']))\n				$ss = $data_array[\'ss\']; // 0 or 1 from prefs\n			else\n				$ss = 0; // not found in prefs\n			if ($ss) // static section according to sed_section_fields\n				if (!has_privs(\'sed_sf.static_sections\')) // only Publisher should see static sections\n					continue; // omit from section select list\n		}\n		if ($name == \'default\') // shouldn\'t really have \'default\' in the list\n			if (!adi_menu_prefs(\'write_tab_select_default\')) // only allow it if there\'re some \'default\' articles\n				continue;\n		if (adi_menu_is_virtual($name)) // exclude virtual sections\n			continue;\n		if( strcasecmp($name,$select_section) == 0)\n			$selected = \' selected=\"selected\"\';\n		else\n			$selected = \'\';\n		if (adi_menu_prefs(\'write_tab_select_format\') == \'name\')\n			$display = $name;\n		else // must be \'title\' then\n			$display = $section_list[$name][\'title\'];\n		$out .= \'<option value=\"\'.$name.\'\"\'.$selected.\'>\';\n		if (adi_menu_prefs(\'write_tab_select_indent\')) // indent\n			$out .= adi_menu_section_indent($level);\n		$out .= $display.\'</option>\';\n	}\n	$out .= \'</select>\';\n	return $out;\n}\n\nfunction adi_menu_section_levels($hierarchy,$level=1) {\n// create list, indexed by section, of level in hierarchy (top = 1 etc.)\n	$section_levels = array();\n	foreach ($hierarchy as $index => $section)\n		if (!$section[\'clone\']) { // ignore clones\n			$section_levels[$index] = $level; // set level in array, indexed by section name\n			$section_levels = array_merge($section_levels,adi_menu_section_levels($section[\'child\'],$level+1));\n		}\n	return $section_levels;\n}\n\nfunction adi_menu_lineage($section_list,$child) {\n// determine the ancestry & generate markup\n	global $s,$adi_menu_lineage_count,$adi_menu_breadcrumb_atts,$is_article_list;\n\n	extract($adi_menu_breadcrumb_atts);\n\n	$out = array();\n	if (!array_key_exists($child, $section_list)) { // bomb out if section not found (e.g. if $s is blank on error page)\n		$out[] = \'\';\n		return $out;\n	}\n\n	if ($s == $child) $adi_menu_lineage_count++;\n	if ($adi_menu_lineage_count > 1) { // bomb out if loop found\n		$out[] = \"Warning, section loop found: \";\n		return $out;\n	}\n	if ($section_list[$child][\'adi_menu_parent\']) // has parent\n		$out = array_merge($out,adi_menu_lineage($section_list,$section_list[$child][\'adi_menu_parent\']));\n	else { // top of the food chain\n		if (($include_default) && ($child != \'default\')) // if (include default) AND (not at \'default\' yet)\n			$out = array_merge($out,adi_menu_lineage($section_list,\'default\')); // do extra, \'default\' iteration\n		else\n			$out[] = doTag($label,$labeltag); // add the \"You are here\" bit\n	}\n	// entitlements\n	$crumb = $child; // default to section name\n	if ($title) {\n		$crumb = $section_list[$child][\'title\'];\n		if (($crumb == \'default\') && ($default_title))\n			$crumb = $default_title;\n		if (!$ignore_alt_title && $section_list[$child][\'adi_menu_alt_title\'])\n			$crumb = $section_list[$child][\'adi_menu_alt_title\'];\n	}\n	$crumb = adi_menu_htmlentities($crumb,$escape);\n	if (($s == $child) && (!$link_last) && ($is_article_list)) // if (last breadcrumb) AND (link_last=0) AND (not single article), switch off link mode\n		$link = FALSE;\n	$link ? // output section link, maybe\n// 		$out[] = tag($crumb,\'a\',\' class=\"\'.$linkclass.\'\" href=\"\'.$section_list[$child][\'url\'].\'\"\') :\n		$out[] = tag($crumb,\'a\',\' \'.($linkclass ? \'class=\"\'.$linkclass.\'\"\' : \'\').\' href=\"\'.$section_list[$child][\'url\'].\'\"\') :\n		$out[] = $crumb;\n\n	if ($s != $child) $out[] = $separator; // add separator if not last crumb\n\n	return $out;\n}\n\nfunction adi_menu_breadcrumb($atts) {\n// <txp:adi_menu_breadcrumb /> tag\n	global $s,$default_first,$adi_menu_lineage_count,$adi_menu_breadcrumb_atts;\n\n	$default_first=\"1\"; // used by adi_menu_section_list()\n\n// 	extract(lAtts(array(\n// 		\'label\'				=> \'You are here: \',	// string to prepend to the output\n// 		\'labeltag\'			=>	\'\',					// tag to wrap around label\n// 		\'separator\'			=> \' &#187; \',			// string to be used as the breadcrumb separator (default: >>)\n// 		\'sep\'				=> \'\',					// deprecated - use \'separator\'\n// 		\'title\'				=> \'1\',					// display section titles or not\n// 		\'link\'				=> \'1\',					// output sections as links or not\n// 		\'linkclass\'			=> \'noline\',			// class for breadcrumb links\n// 		\'link_last\'			=> \'0\',					// display last section crumb as link or not\n// 		\'include_default\'	=> \'1\',					// include \'default\' section or not\n// 		\'default_title\'		=> \'Home\',				// title for \'default\' section\n// 		\'ignore_alt_title\'	=> \'0\',					// choose to ignore the alternatve titles\n// 		\'escape\'			=> \'\',					// escape HTML entities in section titles\n// 	), $atts));\n\n	if (!adi_menu_installed()) return \"<em>adi_menu not installed!</em>\";\n\n	$adi_menu_breadcrumb_atts = lAtts(array(\n		\'label\'				=> \'You are here: \',	// string to prepend to the output\n		\'labeltag\'			=>	\'\',					// tag to wrap around label\n		\'separator\'			=> \' &#187; \',			// string to be used as the breadcrumb separator (default: >>)\n		\'sep\'				=> \'\',					// deprecated - use \'separator\'\n		\'title\'				=> \'1\',					// display section titles or not\n		\'link\'				=> \'1\',					// output sections as links or not\n		\'linkclass\'			=> \'noline\',			// class for breadcrumb links\n		\'link_last\'			=> \'0\',					// display last section crumb as link or not\n		\'include_default\'	=> \'1\',					// include \'default\' section or not\n		\'default_title\'		=> \'Home\',				// title for \'default\' section\n		\'ignore_alt_title\'	=> \'0\',					// choose to ignore the alternatve titles\n		\'escape\'			=> \'\',					// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n	), $atts);\n	extract($adi_menu_breadcrumb_atts);\n\n	$default_title = trim($default_title);\n	if ($sep) $separator = $sep; // deprecated attribute \'sep\', use \'separator\' instead\n\n	/* adi_menu_breadcrumb - main procedure */\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$adi_menu_lineage_count = 0; // global variable used instead of static, so that adi_menu_breadcrumb can have multiple instances\n	$out = adi_menu_lineage($section_list,$s);\n	return implode($out);\n}\n\nfunction adi_menu_info($atts,$thing,$if_tag=0) {\n// combined <txp:adi_menu_info /> & <txp:adi_menu_if_info /> tag function\n// <txp:adi_menu_info /> returns specific menu information for current/specified section(s)\n// <txp:adi_menu_if_info /> - if/then/else conditional\n	global $s,$thisarticle,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_info_types,$adi_menu_vs_prefix,$ignore_alt_title;\n\n	// if or not if\n	if ($if_tag) {\n		extract(lAtts(array(\n			\'sections\'		=> \'\',		// defaults to current section (or article\'s section if in speaking block)\n			\'type\'			=> \'title\', // defaults to title (useful for testing if section exists)\n			\'value\'			=> \'\',\n			\'debug\'			=> 0,\n		),$atts));\n		$condition = 0;\n	}\n	else {\n		extract(lAtts(array(\n			\'sections\'			=> \'\',		// defaults to current section (or article\'s section if in speaking block)\n			\'type\'				=> \'title\', // defaults to title (for standard sections analogous to <txp:section title=\"1\" />\n			\'escape\'			=> \'\',		// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n			\'wraptag\'			=> \'\',\n			\'class\'				=> \'\',\n			\'break\'				=> \'\',\n			\'breakclass\'		=> \'\',\n			\'debug\'				=> 0,\n			// LABEL etc?\n		), $atts));\n		$out = array();\n	}\n\n	// sensitivities\n	if ($sections == \'\')\n		if ($thisarticle) // we\'re in an article (e.g. when used in speaking block forms)\n			$sections = $thisarticle[\'section\'];\n		else\n			$sections = $s; // current section\n\n	$sections = do_list($sections);\n	$types = do_list($type);\n\n	if (array_search(\'ancestors\',$types) !== FALSE) // who do you think you are?\n		$section_list = adi_menu_section_list(\'\',\'\',TRUE); // all sections, no exclusions, override excluded\n\n	// go through supplied list of sections\n	foreach ($sections as $section) {\n		if (adi_menu_is_virtual($section))\n			$row = safe_row($adi_menu_vs_sql_fields,\'adi_menu\',\"name=\'\".adi_menu_strip_prefix($section).\"\'\",$debug);\n		else\n			$row = safe_row($adi_menu_sql_fields,\'txp_section\',\"name=\'$section\'\",$debug);\n		if ($row) { // section found in DB\n			if ($debug) {\n				echo \"DB row:\";\n				dmp($row);\n			}\n			// go through supplied list of types\n			foreach ($types as $type) {\n				$valid_types = array_keys($adi_menu_info_types);\n				if (in_array($type,$valid_types)) { // valid type?\n					if (isset($adi_menu_info_types[$type])) { // known column?\n						if (($type == \'clone_title\') && !$row[$adi_menu_info_types[\'clone\'][\'column\']]) continue; // don\'t output or find clone title if clone isn\'t switched on (future proofing)\n						if ($if_tag) { // CONDITIONAL INFO TAG (<txp:adi_menu_if_info />)\n							// run through columns to see if any are non-blanks or values that match\n							$columns = do_list($adi_menu_info_types[$type][\'column\']);\n							foreach ($columns as $column) {\n								$this_val = $row[$column];\n								if ($type == \'menu_title\') { // menu_title is a special case\n									if ($row[\'adi_menu_alt_title\'] && !$ignore_alt_title) // alternative title overrides normal title & check ignore_alt_title attribute\n										$this_val = $row[\'adi_menu_alt_title\']; // use deduced value\n								}\n								if ($value == \'\') { // test value not supplied\n									if ($this_val != $adi_menu_info_types[$type][\'blank\']) // condition TRUE if column is non-blank\n										$condition += 1;\n								}\n								else {\n									if ($value == $this_val) // condition TRUE if column matches supplied value\n										$condition += 1;\n								}\n							}\n						}\n						else { // OUTPUT INFO TAG (<txp:adi_menu_info />)\n							// output non-blank value\n							$column = $adi_menu_info_types[$type][\'column\'];\n							if (strpos($column,\',\') === FALSE) { // ignore lists of columns (used by <txp:adi_menu_if_info /> only)\n								$this_val = $row[$column];\n								if ($type == \'menu_title\') { // menu_title is a special case\n									if ($row[\'adi_menu_alt_title\'] && !$ignore_alt_title) // alternative title overrides normal title & check ignore_alt_title attribute\n										$this_val = $row[\'adi_menu_alt_title\']; // use deduced value\n								}\n								if ($type == \'ancestors\') { // ancestors is another special case\n									$ancestors = adi_menu_get_ancestors($section_list,$section);\n									$this_val = implode(\',\',$ancestors);\n								}\n								if ($this_val != $adi_menu_info_types[$type][\'blank\']) // only output value if not \"blank\"\n									$out[] = adi_menu_htmlentities($this_val,$escape);\n							}\n						}\n					}\n				}\n			}\n		}\n	}\n\n	if ($debug) {\n		if ($if_tag)\n			echo \"adi_menu_if_info \";\n		else\n			echo \"adi_menu_info \";\n		echo \"attributes:\";\n		dmp($atts);\n		echo \"sections:\";\n		dmp($sections);\n		echo \"type mapping:\";\n		dmp($adi_menu_info_types);\n		echo \"types:\";\n		dmp($types);\n		if ($if_tag)\n			echo \"condition=$condition\".br;\n		echo \"ignore_alt_title=$ignore_alt_title\".br; // if we\'re in a speaking block ignore_alt_title=0/1, otherwise it\'s blank (i.e. vague global unset value)\n	}\n\n	if ($if_tag)\n		return parse(EvalElse($thing,$condition));\n	else\n		return doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\nfunction adi_menu_if_info($atts,$thing) {\n// <txp:adi_menu_if_info /> tag == <txp:adi_menu_info if_tag=\"1\" />\n\n	return adi_menu_info($atts,$thing,1);\n}\n\nfunction adi_menu_get_article_attr($article_attr) {\n// parse article_attr and return list, indexed by attribute name\n// e.g. article_attr=\'time=\"any\" limit=\"5\"\'\n	// THERE MUST BE A BETTER WAY!\n	$attr_list = preg_replace(\"#([a-z_]+=)#\", \",\\\\1\", $article_attr) . \",\"; // creates e.g. \',time=\"any\",limit=\"5\"\'\n	$attr_list = do_list($attr_list);\n	$attributes = array();\n	foreach ($attr_list as $index => $attr) {\n		if ($attr) {\n			$a = do_list($attr,\'=\');\n			if (count($a) == 2) { // safety valve\n				$attribute = trim($a[0]); // remove whitespace\n				$value = trim($a[1]); // remove whitespace\n				$value = trim($value,\'\"\'); // remove double quotes\n				$attributes[$attribute] = $value;\n			}\n		}\n	}\n	return $attributes;\n}\n\nfunction adi_menu_get_articles($section_list,$section_article_list,$category_article_list=array(),$debug=FALSE) {\n// create list of articles from database, indexed by section/category & sub-indexed by article id (prefixed with \'article_\')\n// - sort value taken from article_sort attribute\n// - much code taken from publish.php doArticles()\n	global $s,$c,$adi_menu_debug,$article_sort,$section_article_sort,$article_attr,$active_articles_only,$new_article_mode,$cat_article_sort,$category_article_sort,$cat_article_attr,$adi_menu_vs_prefix;\n\n	// anything to do?\n	if (!($section_article_list || $category_article_list))\n		return array();\n\n	$article_list = $this_article_list = $attributes = array();\n\n	// section/category - attributes\n	if ($section_article_list) {\n		$this_article_list = $section_article_list;\n		$attributes = adi_menu_get_article_attr($article_attr);\n	}\n	else if ($category_article_list) {\n		$this_article_list = $category_article_list;\n		$attributes = adi_menu_get_article_attr($cat_article_attr);\n	}\n\n	// blankety blank\n	$section=$category=$search=$id=$excerpted=$month=$author=$keywords=$custom=$frontpage=\'\';\n\n	// set defaults\n	$statusq = \' AND Status = 4\';\n	$time = \" AND Posted <= NOW()\";\n	if ($new_article_mode)\n		$limit = 9999;\n	else\n		$limit = 10;\n	$offset = 0;\n\n	// analyse article attributes\n	foreach ($attributes as $attribute => $value) {\n		switch ($attribute) {\n			case \'author\':\n				if ($value)\n					$author = (!$value) ? \'\' : \" AND AuthorID IN (\'\".implode(\"\',\'\", doSlash(do_list($value))).\"\')\";\n				break;\n			case \'category\':\n				if ($value) {\n					$category = implode(\"\',\'\", doSlash(do_list($value)));\n					$category = (!$category)  ? \'\' : \" AND (Category1 IN (\'\".$category.\"\') OR Category2 IN (\'\".$category.\"\'))\";\n				}\n				break;\n			case \'excerpted\':\n				if ($value)\n					$excerpted = ($value==\'y\')  ? \" AND Excerpt !=\'\'\" : \'\';\n				break;\n			case \'keywords\':\n				if ($value) {\n					$keys = doSlash(do_list($value));\n					foreach ($keys as $key) {\n						$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n					}\n					$keywords = \" AND (\" . implode(\' or \',$keyparts) . \")\";\n				}\n				break;\n			case \'limit\':\n				if ($value)\n					$limit = $value;\n				break;\n			case \'month\':\n				if ($value)\n					$month = (!$value) ? \'\' : \" AND Posted LIKE \'\".doSlash($value).\"%\'\";\n				break;\n			case \'offset\':\n				if ($value)\n					$offset = $value;\n				break;\n			case \'sort\': // will override article_sort attribute\n				if ($value)\n					$article_sort = $value;\n				break;\n			case \'status\':\n				if ($value) {\n					$status = in_array(strtolower($value), array(\'sticky\', \'5\')) ? 5 : 4;\n					$statusq = \' AND Status = \'.intval($status);\n				}\n				else // status attr value blank = live and stickies\n					$statusq = \' AND Status IN (4,5)\';\n				break;\n			case \'time\':\n				switch ($value) {\n					case \'any\':\n						$time = \"\"; break;\n					case \'future\':\n						$time = \" AND Posted > NOW()\"; break;\n				}\n				break;\n		}\n	}\n\n	// custom fields\n	$customFields = getCustomFields();\n	$customlAtts = array_null(array_flip($customFields));\n	if ($customFields) {\n		foreach($customFields as $cField)\n			if (isset($attributes[$cField]))\n				$customPairs[$cField] = $attributes[$cField];\n		if(!empty($customPairs))\n			$custom = buildCustomSql($customFields,$customPairs);\n	}\n\n	if ($debug) {\n		echo \"adi_menu_get_articles()\";\n		if ($section_article_list) echo \" - SECTION:\".br;\n		if ($category_article_list) echo \" - CATEGORY:\".br;\n	}\n\n	// retrieve articles from database\n	foreach ($this_article_list as $index => $this_item) {\n		// section/category - select\n		$this_section = \'\';\n		if ($section_article_list) {\n			$this_section = $this_item;\n			if (adi_menu_is_virtual($this_item)) { // virtual section, so think about it\n				if ($section_list[$this_item][\'adi_menu_redirect_section\']) // only interested if redirected to a section\n					$this_section = $section_list[$this_item][\'adi_menu_redirect_section\'];\n				else\n					continue;\n			}\n			$section = \" AND Section = \'\".doSlash($this_section).\"\'\";\n		}\n		else if ($category_article_list)\n			$category = \" AND (Category1 = \'\".$this_item.\"\' OR Category2 = \'\".$this_item.\"\')\";\n\n		// section/category - sorting\n		$sortq = $this_sort = \'\';\n		if ($section_article_list)\n			if (array_key_exists($this_item,$section_article_sort)) // section-specific article sort\n				$this_sort = $section_article_sort[$this_item];\n			else\n				$this_sort = $article_sort; // default sort\n		else if ($category_article_list)\n			if (array_key_exists($this_item,$category_article_sort)) // category-specific article sort\n				$this_sort = $category_article_sort[$this_item];\n			else\n				$this_sort = $cat_article_sort; // default sort\n		if ($this_sort)\n			$sortq = \' ORDER BY \'.doSlash($this_sort);\n\n		// WHERE clause\n		$where = \"1=1\".$statusq.$time.$search.$id.$category.$section.$excerpted.$month.$author.$keywords.$custom.$frontpage;\n\n		if ($debug) echo \"$this_item\".($this_section && ($this_section != $this_item) ? \" (>>>$this_section)\" : \"\").\": $where $sortq\".br;\n\n		// database action\n		$rs = safe_rows_start(\n				\"ID,Title,url_title,Posted,Section\" // needed for permlinkurl()\n				,\'textpattern\',\n				$where.$sortq.\' LIMIT \'.intval($offset).\', \'.intval($limit)\n			);\n		while($a = nextRow($rs)) {\n			$this_index = \'article_\'.$a[\'ID\']; // create unique array index - for ID 23, index is article_23\n			$article_list[$this_item][$this_index][\'title\'] = html_entity_decode($a[\'Title\']); // ampersands are escaped in article titles (but not in section titles)\n			$article_list[$this_item][$this_index][\'url\'] = permlinkurl($a);\n			$article_list[$this_item][$this_index][\'section\'] = FALSE; // FALSE=article\n			$article_list[$this_item][$this_index][\'virtual_section\'] = FALSE;\n			$article_list[$this_item][$this_index][\'sort\'] = rand(); // THIS MAY TAKE SORT NUMBER FROM SOMEWHERE ELSE EVENTUALLY\n			$article_list[$this_item][$this_index][\'clone\'] = FALSE; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_section\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_link\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_category\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'alt_title\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'parent\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'child\'] = array(); // dummy filler\n		}\n	}\n\n	if ($debug) echo br;\n\n	return $article_list;\n}\n\n\nfunction adi_menu_speaking_block($name,$redirect_section) {\n// retrieve specified section\'s sticky article excerpt for the speaking block\n	global $speaking_block_form,$adi_menu_vs_prefix;\n\n	if ($redirect_section) // massage section name to match redirection\n		$name = $redirect_section;\n\n	$sb_tag_attr = \'section=\"\'.$name.\'\" status=\"sticky\"\';\n	if ($speaking_block_form) // use specified form\n		$sb_article_tag = \'<txp:article_custom \'.$sb_tag_attr.\' form=\"\'.$speaking_block_form.\'\"/>\';\n	else // use default form\n		$sb_article_tag = \'<txp:article_custom \'.$sb_tag_attr.\'><txp:excerpt /></txp:article_custom>\';\n\n	$sb_text = trim(parse($sb_article_tag));\n	if ($sb_text) // don\'t want empty spans\n		return \'<span class=\"speaking_block\">\'.$sb_text.\'</span>\';\n}\n\nfunction adi_menu_sort_hierarchy($a,$b) {\n// comparison function for uasort()\n	global $article_sort,$article_position;\n\n	if ($article_position == \'dovetail\')\n		$this_sort = \'title\';\n	else\n		$this_sort = $article_sort;\n	$reverse = preg_match(\"/ desc/i\",$this_sort);\n	$this_sort = preg_replace(\"/ asc/i\",\"\",$this_sort); // remove \" asc\" MULTIPLE SPACES!!!\n	$this_sort = preg_replace(\"/ desc/i\",\"\",$this_sort); // remove \" desc\" MULTIPLE SPACES!!!\n	if (strcasecmp($this_sort,\'title\') == 0)\n    	$reverse ?\n			$res = strcasecmp($b[\"title\"], $a[\"title\"]) :\n			$res = strcasecmp($a[\"title\"], $b[\"title\"]);\n	else if (strcasecmp($this_sort,\'adi_menu_sort\') == 0)\n    	$res = strcasecmp($a[\"sort\"], $b[\"sort\"]);\n	else // no sort (i.e. database order)\n    	$res = 0;\n	return $res;\n}\n\nfunction adi_menu_hierarchy($section_list,$article_list=array(),$cat_article_list=array(),$this_section=NULL,$clone=0) {\n// create $hierarchy, indexed by section/article_ID, using information from $section_list (and possibly article lists too)\n	global $clone_title,$new_article_mode,$article_position,$default_first,$articles,$exclude_clone,$active_articles_only;\n\n	$hierarchy = array();\n\n	$not_restricted = TRUE;\n\n	// active articles only?\n	if (($this_section !== NULL) && $active_articles_only)\n		$not_restricted = adi_menu_is_current($this_section,$section_list[$this_section][\'adi_menu_redirect_category\']);\n\n	// insert \"BEFORE\" articles\n	if (($this_section !== NULL) && ($article_position == \'before\') && $new_article_mode && $articles && $not_restricted) {\n		$this_category = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		if ($this_category) { // category articles\n			if (array_key_exists($this_category,$cat_article_list))\n				$hierarchy = $cat_article_list[$this_category];\n		}\n		else\n			if (array_key_exists($this_section,$article_list)) // section articles\n				$hierarchy = $article_list[$this_section];\n	}\n\n	// clone parent as its child\n	if ($clone && !$exclude_clone) {\n		$hierarchy[$this_section][\'title\'] =\n			$section_list[$this_section][\'adi_menu_title\'] ?\n				$section_list[$this_section][\'adi_menu_title\'] :\n				$clone_title; // use default clone title\n		$hierarchy[$this_section][\'url\'] = $section_list[$this_section][\'url\'];\n		$hierarchy[$this_section][\'section\'] = TRUE; // I\'m a section\n		$hierarchy[$this_section][\'virtual_section\'] = FALSE; // clones can\'t be virtual\n		$hierarchy[$this_section][\'sort\'] = rand(); // sort number\n		$hierarchy[$this_section][\'clone\'] = TRUE; // I\'m a clone\n		$hierarchy[$this_section][\'parent\'] = $this_section;\n		$hierarchy[$this_section][\'redirect_section\'] = $section_list[$this_section][\'adi_menu_redirect_section\'];\n		$hierarchy[$this_section][\'redirect_link\'] = $section_list[$this_section][\'adi_menu_redirect_link\'];\n		$hierarchy[$this_section][\'redirect_category\'] = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		$hierarchy[$this_section][\'alt_title\'] = \'\'; // a clone already has an \"alternative title\"!\n		$hierarchy[$this_section][\'child\'] = array(); // that\'s enough inbreeding\n	}\n\n	// find children\n	foreach ($section_list as $index => $section) {\n		if ($section[\'adi_menu_parent\'] == $this_section) {\n			$hierarchy[$index][\'title\'] = $section[\'title\'];\n			$hierarchy[$index][\'url\'] = $section[\'url\'];\n			$hierarchy[$index][\'section\'] = TRUE; // I\'m a section\n			$hierarchy[$index][\'virtual_section\'] = $section[\'adi_menu_id\'] !== NULL; // virtual section?\n			$hierarchy[$index][\'sort\'] = $section[\'adi_menu_sort\']; // adi_menu admin sort order\n			$hierarchy[$index][\'clone\'] = FALSE; // I\'m not a clone\n			$hierarchy[$index][\'parent\'] = $section[\'adi_menu_parent\'];\n			$hierarchy[$index][\'redirect_section\'] = $section[\'adi_menu_redirect_section\'];\n			$hierarchy[$index][\'redirect_link\'] = $section[\'adi_menu_redirect_link\'];\n			$hierarchy[$index][\'redirect_category\'] = $section[\'adi_menu_redirect_category\'];\n			$hierarchy[$index][\'alt_title\'] = $section[\'adi_menu_alt_title\'];\n			$hierarchy[$index][\'child\'] = adi_menu_hierarchy($section_list,$article_list,$cat_article_list,$index,$section[\'adi_menu_clone\']); // delve deper\n		}\n	}\n\n	// insert \"AFTER/DOVETAIL\" articles\n	if (($this_section !== NULL) && !($article_position == \'before\') && $new_article_mode && $articles && $not_restricted) { // default to inserting articles AFTER sections (i.e. not \"before\")\n		$this_category = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		if ($this_category) { // category articles\n			if (array_key_exists($this_category,$cat_article_list))\n				$hierarchy = array_merge($hierarchy,$cat_article_list[$this_category]);\n		}\n		else\n			if (array_key_exists($this_section,$article_list)) // section articles\n				$hierarchy = array_merge($hierarchy,$article_list[$this_section]);\n		if ($article_position == \'dovetail\') // sort current level of hierarchy (sections AND articles)\n			uasort($hierarchy,\"adi_menu_sort_hierarchy\");\n	}\n\n	return $hierarchy;\n}\n\nfunction adi_menu_prune($old_hierarchy,$include_list,$section_levels,$siblings) {\n// remove sections from the hierarchy that are not on the include list (taking notice of: include_parent, include_childless, include_top_level)\n	global $include_parent,$include_childless,$include_top_level,$include_siblings,$current_descendants_only;\n\n	$hierarchy = array();\n	foreach ($old_hierarchy as $section => $section_data) {\n\n		if (array_search($section,$include_list) !== FALSE) { // IN LIST: copy section/children and look no further\n			if (!empty($section_data[\'child\']) && (!$include_parent)) // copy children only\n				$hierarchy += $section_data[\'child\'];\n			else if (($include_parent) || ($include_childless)) // copy section (& children)\n				$hierarchy[$section] = $section_data;\n		}\n		else if ($include_top_level && ($section_levels[$section] == 1)) { // NOT IN LIST: but check top level requirements\n			$section_data[\'child\'] = array(); // remove the kids\n			$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n		}\n		else if ($include_siblings) { // NOT IN LIST: but check if sibling\n			foreach ($include_list as $this_section) { //??? adi_menu2 not coming up\n				if (array_search($section,$siblings[$this_section])) {\n						$section_data[\'child\'] = array(); // remove the kids\n						$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n					}\n			}\n		}\n		else if ($current_descendants_only) { // NOT IN LIST: add as parent only\n			$section_data[\'child\'] = array(); // remove the kids\n			$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n		}\n		else if (!empty($section_data[\'child\'])) // NOT IN LIST: delve deeper into hierarchy\n			$hierarchy += adi_menu_prune($section_data[\'child\'],$include_list,$section_levels,$siblings);\n\n	}\n\n	return $hierarchy;\n}\n\nfunction adi_menu_siblings($section_list) {\n// create list of siblings\n\n	$siblings = array();\n	foreach ($section_list as $section => $section_data)\n		$siblings[$section] = array();\n\n	foreach ($section_list as $section => $section_data) {\n		$parent = $section_data[\'adi_menu_parent\'];\n		if ($parent == \'\')\n			$parents[\'adi_menu_root\'][] = $section;\n		else\n			$parents[$parent][] = $section;\n	}\n\n	foreach ($parents as $parent => $children) {\n		foreach ($children as $child) {\n			$siblings[$child] = $children;\n			if (($key = array_search($child,$siblings[$child])) !== FALSE) {\n    			unset($siblings[$child][$key]);\n    			$siblings[$child] = array_values($siblings[$child]); // re-index array\n			}\n		}\n	}\n\n	return $siblings;\n}\n\nfunction adi_menu_descendants($hierarchy,$parent) {\n// create list of descendants, indexed by section/article ID\n	global $descendant_list;\n\n	foreach ($hierarchy as $index => $section) {\n		// add child to parent\'s list\n		$descendant_list[$parent][] = $index;\n		if ($section[\'child\']) { // found some grandchildren\n			// follow the family tree\n			adi_menu_descendants($section[\'child\'],$index);\n			// add child\'s descendants to parent\'s list\n			$descendant_list[$parent] = array_merge($descendant_list[$parent],$descendant_list[$index]);\n		}\n		else // create empty list for the childless\n			$descendant_list[$index] = array();\n	}\n\n	return $descendant_list;\n}\n\nfunction adi_menu_get_ancestors($section_list,$this_generation,$ancestor_list=array()) {\n// find all ancestors of given section (in order: parent, grandparent, great-grandparent ...)\n\n	$parent = $section_list[$this_generation][\'adi_menu_parent\'];\n	if ($parent != \'\') {\n		if (array_search($parent,$ancestor_list) !== FALSE) // loop found, so bomb out\n			return $ancestor_list;\n		$ancestor_list[] = $parent;\n		return adi_menu_get_ancestors($section_list,$parent,$ancestor_list);\n	}\n	else\n		return $ancestor_list;\n}\n\nfunction adi_menu_find_ancestor($level) {\n// find ancestor of current section\n	global $descendant_list,$section_levels,$s,$adi_menu_debug;\n\n	$found = \'\';\n\n	foreach ($descendant_list as $index => $list) {\n		$index == \'adi_menu_root\' ? // special case\n			$this_level = 0 :\n			$this_level = $section_levels[$index];\n		if (array_search($s,$list) !== FALSE) {\n			if ($level == $this_level) {\n				$found = $index;\n			}\n		}\n	}\n\n	return $found;\n}\n\nfunction adi_menu_find_descendant($level) {\n// find descendant of current section at specified level\n	global $descendant_list,$section_levels,$s;\n\n	$found = array();\n\n	foreach ($descendant_list[$s] as $descendant) {\n		$this_level = $section_levels[$descendant];\n		if ($level == $this_level) {\n			$found[] = $descendant;\n		}\n	}\n\n	return $found;\n}\n\nfunction adi_menu_cat_section_map($section_list) {\n// generate list of categories & sections that redirect to them\n	$cat_section_map = array();\n\n	foreach ($section_list as $name => $section) {\n		if ($section[\'adi_menu_redirect_category\'])\n			$cat_section_map[$section[\'adi_menu_redirect_category\']][] = $name;\n	}\n\n	return $cat_section_map;\n}\n\nfunction adi_menu_active($index,$hierarchy,$active_section,$active_parent,$active_article) {\n// determines whether current element should be active or not (but is pre-informed about active_section, active_parent, active_article)\n// 		in articles mode - lowest parent is parent of active article\n//		in non-articles mode - lowest parent is parent of active section\n	global $s,$c,$descendant_list,$active_ancestors,$pretext,$articles,$cat_section_map;\n\n	if ($articles) { // articles part of menu structure\n		$active_section = ($active_section && !$pretext[\'id\']); // active section (but not if displaying article) BEWARE: resetting $active_section here\n		$active_section_parent = ($active_parent && !$pretext[\'id\'] && array_key_exists($s,$hierarchy[$index][\'child\'])); // parent section of active section (but not if displaying an article)\n		$active_article_parent = ($active_parent && (strcasecmp($s, $index) == 0)); // parent of active article\n		$active_section_ancestor = ($active_ancestors && (array_search($s,$descendant_list[$index]) !== FALSE)); // ancestor of active section\n		$active_article_ancestor = ($active_ancestors && (array_search(\'article_\'.$pretext[\'id\'],$descendant_list[$index]) !== FALSE)); // ancestor of active article\n		//TODO - category parent\n		//TODO - category ancestor\n		return $active_section || $active_section_parent || $active_article_parent || $active_section_ancestor || $active_article_ancestor || $active_article;\n	}\n	else { // don\'t care about articles\n		$active_section_parent = ($active_parent && array_key_exists($s,$hierarchy[$index][\'child\'])); // parent section of active section (this seems to work for category sections too)\n		$active_section_ancestor = ($active_ancestors && (array_search($s,$descendant_list[$index]) !== FALSE)); // ancestor of active section\n		$active_category_ancestor = FALSE;\n		if ($c) { // category page?\n			if (isset($cat_section_map[$c])) { // category used as redirect in menu hierarchy?\n				foreach ($cat_section_map[$c] as $section) { // see if we\'re an ancestor of any sections redirecting to category\n					$active_category_ancestor = ($active_ancestors && ($active_category_ancestor || (array_search($section,$descendant_list[$index]) !== FALSE))); // ancestor of active category\n				}\n			}\n		}\n		return $active_section || $active_section_parent || $active_section_ancestor || $active_category_ancestor;\n	}\n}\n\nfunction adi_menu_is_current($this_section,$this_category=\'\',$debug=FALSE) {\n// determine whether supplied section or catgeory ($is_section=FALSE) is current or not\n// i.e. section = $s OR force_current attribute has supplied a match\n	global $s,$c,$force_current;\n\n	if ($debug) echo __FUNCTION__.\"(): s=$s, c=$c, this_section=$this_section, this_category=$this_category\";\n\n	$current = FALSE;\n\n	if ($this_section)\n		$current = $current || (strcasecmp($s,$this_section) == 0);\n\n	if ($this_category)\n		$current = $current || (strcasecmp($c,$this_category) == 0);\n\n	if ($force_current) {\n		if ($debug) echo \", force_current\";\n		if (isset($force_current[$this_section]))\n			$current = $current || (array_search($s,$force_current[$this_section]) !== FALSE);\n		if (isset($force_current[$this_category]))\n			$current = $current || (array_search($c,$force_current[$this_category]) !== FALSE);\n	}\n\n	if ($debug) echo \", current=$current\".br;\n\n	return $current;\n}\n\nfunction adi_menu_markup($hierarchy,$level) {\n// output <ul>/<li> markup from given $hierarchy\n	global $prefs,$menu_id,$parent_class,$active_class,$s,$c,$class,$link_span,$list_id,$list_id_prefix,$active_li_class,$articles,$include_children,$active_parent,$list_span,$active_ancestors,$descendant_list,$first_class,$last_class,$list_prefix,$prefix_class,$suppress_url,$suppress_url_sections,$new_article_mode,$article_class,$pretext,$active_article_class,$speaking_block,$label,$labeltag,$label_class,$label_id,$current_children_only,$adi_menu_escape,$odd_even,$ignore_alt_title,$adi_menu_vs_prefix,$exclude_niblings;\n\n	$out = array();\n\n	if (empty($hierarchy))\n		return $out; // bomb out if hierarchy is empty\n\n	$css_id = $css_class = \'\';\n	if (!$level) { // set HTML ID & CSS class on top level <ul> only\n		if ($menu_id) $css_id = \' id=\"\'.$menu_id.\'\"\';\n		if ($class) $css_class = \' class=\"\'.$class.\'\"\';\n		if ($label)\n			$labeltag ?\n				$out[] = doTag($label,$labeltag,$label_class,\'\',$label_id) :\n				$out[] = $label;\n	}\n\n	$out[] = \'<ul\'.$css_id.$css_class.\'>\';\n\n	// get list of section names from $hierarchy and determine first & last\n	$keys = array_keys($hierarchy);\n	$first_section = reset($keys);\n	$last_section = end($keys);\n\n	$odd = FALSE;\n	foreach ($hierarchy as $section => $data) {\n		$odd = !$odd;\n		$parent = !empty($data[\'child\']);\n		$li_class_list = \'\';\n		if ($parent && $include_children)  // not a parent if not including the children\n			$li_class_list .= \' \'.$parent_class;\n		$active_section = FALSE;\n		$active_article = FALSE;\n		if ($data[\'section\']) {\n			if ($data[\'redirect_category\']) {\n				$active_section = adi_menu_is_current($section,$data[\'redirect_category\']); // redirected to current category?\n			}\n			else if (!$c) { // active section? (avoid false-positive - category page\'s section is \"default\")\n				$active_section = adi_menu_is_current($section);\n				if ($data[\'redirect_section\']) // section redirected to active section?\n					$active_section = adi_menu_is_current($data[\'redirect_section\']);\n			}\n		}\n		else { // $section is an article!\n			$li_class_list .= \' \'.$article_class;\n			$article_id = preg_replace(\'/article_/\',\'\',$section); // convert \'article_id#\' to \'id#\'\n			$active_article = ($article_id == $pretext[\'id\']); // is the article active?\n		}\n		if ($data[\'redirect_section\'] || $data[\'redirect_link\'] || $data[\'redirect_category\']) // redirected (to section/link/category)\n			$li_class_list .= \' menu_redirect\';\n		if ($data[\'redirect_link\']) // redirected to link\n			$li_class_list .= \' menu_ext_link\';\n		if ($data[\'redirect_category\']) // redirected to category\n			$li_class_list .= \' menu_category\';\n		if ($data[\'virtual_section\']) // virtual section\n			$li_class_list .= \' menu_virtual\';\n		$title = adi_menu_htmlentities($data[\'title\'],$adi_menu_escape);\n		if ($data[\'alt_title\'] && !$ignore_alt_title) { // alternative title specified\n			$li_class_list .= \' menu_alt_title\';\n			$title = adi_menu_htmlentities($data[\'alt_title\'],$adi_menu_escape);\n		}\n		if (!($data[\'section\']) && ($prefs[\'title_no_widow\'])) // dewidow article title if required\n			$title = noWidow($title);\n		$url = $data[\'url\']; // although URL may get suppressed later ...\n		$clone = $data[\'clone\'];\n		$first = ($section == $first_section) && $first_class; // only flag first section if required\n		$last = ($section == $last_section) && $last_class; // only flag last section if required\n		$link_span ?\n			$link_content = \'<span>\'.$title.\'</span>\' :\n			$link_content = $title;\n		if ($speaking_block)\n			$link_content = $link_content.adi_menu_speaking_block($section,$data[\'redirect_section\']);\n		if ($clone) { // section/category is a clone\n			$clone_suffix = \'_clone\'; // make ID unique\n			$li_class_list .= \' menu_clone\';\n		}\n		else\n			$clone_suffix = \'\';\n		$list_id ?\n			$li_id = \' id=\"\'.$list_id_prefix.$section.$clone_suffix.\'\"\' :\n			$li_id = \'\';\n		$active_li = $active_li_class && adi_menu_active($section,$hierarchy,$active_section,$active_parent,$active_article);\n		if ($active_li)\n			$li_class_list .= \' \'.$active_li_class;\n		$article_list = \'\';\n		$article_parent = FALSE; // NOT NEEDED IN NEW SYSTEM: $PARENT WORKS FOR SECTION & ARTICLE PARENTS\n		if (!$parent && $articles && !$new_article_mode) // not a section parent so check for articles in advance\n			if (!$clone) { // but don\'t want duplicates\n				$article_list = adi_menu_articles($section,\'ul\');\n				if ($article_list != \"\") { // articles found, so add parent class (i.e. article parent)\n					$li_class_list .= \' \'.$parent_class;\n					$article_parent = TRUE;\n				}\n			}\n		if ($parent || $article_parent) // suppress URL on parents only\n			switch ($suppress_url) {\n				case \'all\':\n					$url = \'#\';\n					break;\n				case \'top\':\n					if (!$level) $url = \'#\';\n					break;\n				case \'active\':\n					if ($active_section) $url = \'#\';\n					break;\n				/*case \'section\':\n					if ($parent) $url = \'#\'; break;\n				case \'article\':\n					if ($article_parent) $url = \'#\'; break;*/\n				default:\n					/* do nothing */\n					break;\n			}\n		if ($suppress_url_sections) { // check list of suppression orders\n			$suppressions = do_list($suppress_url_sections);\n			if (array_search($section,$suppressions) !== FALSE) $url = \'#\'; // no link for you chummy\n		}\n		if ($first) $li_class_list .= \' \'.$first_class;\n		if ($last) $li_class_list .= \' \'.$last_class;\n		if ($odd_even) $li_class_list .= \' \'.(($odd) ? \'menu_odd\' : \'menu_even\');\n		$li_class_list ?\n			$css_class = \' class=\"\'.trim($li_class_list).\'\"\' :\n			$css_class = \'\';\n		$out[] = \'<li\'.$li_id.$css_class.\'>\';\n		$active_a = $active_class && adi_menu_active($section,$hierarchy,$active_section,$active_parent,$active_article);\n		if ($list_span) $out[] = \'<span>\';\n		if ($list_prefix) $out[] = \'<span class=\"\'.$prefix_class.\'\">\'.$list_prefix.\'</span>\';\n		$out[] = tag($link_content,\'a\',($active_a ? \' class=\"\'.$active_class.\'\"\' : \'\').\' href=\"\'.$url.\'\"\');\n		if ($list_span) $out[] = \'</span>\';\n		// the fact that we\'re here means that siblings are already included, so now decide if kids should be sought out\n		$find_the_kids = TRUE;\n		if ($current_children_only) // only immediate children of active (i.e. no grandchildren or niblings)\n// 			$find_the_kids = $active_section || array_key_exists($s,$data[\'child\']) || (array_search($s,$descendant_list[$section]) !== FALSE); // active section, parent of active, or ancestor of active\n			$find_the_kids = $active_section || (array_search($s,$descendant_list[$section]) !== FALSE); // active section or ancestor of active\n		if ($exclude_niblings) // all descendants of active (i.e. no niblings)\n			$find_the_kids = $active_section || (array_search($s,$descendant_list[$section]) !== FALSE) || (array_search($section,$descendant_list[$s]) !== FALSE); // active section, ancestor of active or descendant of active\n		if ($parent && $include_children && $find_the_kids) // find the kids (but only if include_children=1)\n			$out = array_merge($out,adi_menu_markup($data[\'child\'],$level+1));\n		else if ($articles && !$new_article_mode) // not a section parent but an article parent so insert articles here\n			$out[] = $article_list;\n		$out[] = \"</li>\";\n	}\n\n	if (isset($section) && $articles && !$new_article_mode)\n	// above condition prevents tag errors with attribute combinations that result in no sections\n	//  e.g. (include_parent=\"0\" && sections=\"all childless\") OR (sections=\"list\" && exclude=\"same list\")\n		if ($data[\'parent\'] != \'\') // last sibling was a child, so check for parent\'s articles here\n			$out[] = adi_menu_articles($data[\'parent\'],\'\');\n\n	$out[] = \"</ul>\";\n\n	return $out;\n}\n\nfunction adi_menu_articles($section,$wraptag) {\n// generate list of articles/markup using <txp:article_custom />\n	global $s,$articles,$article_attr,$adi_menu_article_form,$section_article_list,$active_articles_only;\n\n	if (@txpinterface != \'admin\') { // articles not relevent in admin interface\n		$allowed = array_search($section, $section_article_list) !== FALSE; // check if articles allowed\n		if ($active_articles_only) // then only allow articles if section is active\n			$allowed &= ($section == $s);\n		if (!$allowed) return \'\';\n		$article_attr == \'\' ?\n			$attr = \'\' :\n			$attr = \' \'.$article_attr;\n		$article_list = trim(parse(\'<txp:article_custom section=\"\'.$section.\'\"\'.$attr.\' form=\"\'.$adi_menu_article_form.\'\" />\'));\n		if ($article_list == \"\")\n			return \'\';\n		else // articles found\n			if ($wraptag == \"\")\n				return $article_list;\n			else\n				return tag($article_list,$wraptag,\'\');\n	}\n}\n\nfunction adi_menu_array_subtract($array1,$array2) {\n// values in array2 that are not in array1 are removed\n	$new = array();\n	$count1 = count($array1);\n	$count2 = count($array2);\n	foreach ($array1 as $index1 => $value1) {\n		$found = FALSE;\n		foreach ($array2 as $index2 => $value2) {\n			$found = $value1 == $value2;\n			if ($found) break;\n		}\n		$found ? $found = FALSE : $new[] = $value1;\n	}\n	return $new;\n}\n\nfunction adi_menu_htmlentities($string,$method) {\n// escape supplied string using:\n// 	new:\n//		\"\" = default: ENT_COMPAT,\'UTF-8\' (thanks ttr)\n// 	old:\n//		\"html\" = htmlentities (ALL special characters translated)\n//		\"htmlspecial\" htmlspecialchars (only & \" \' < > translated)\n	if ($method) { // use manual sledgehammer\n		if ($method == \'html\') $string = htmlentities($string);\n		if ($method == \'htmlspecial\') $string = htmlspecialchars($string);\n	}\n	else\n		$string = htmlentities($string,ENT_COMPAT,\'UTF-8\');\n\n	return $string;\n}\n\nfunction adi_menu($atts) {\n// the <txp:adi_menu /> tag\n	global $prefs,$s,$pretext,$out,$menu_id,$parent_class,$active_class,$include_parent,$include_childless,$default_title,$default_first,$clone_title,$class,$link_span,$list_id,$list_id_prefix,$active_li_class,$articles,$article_attr,$section_article_list,$include_children,$active_parent,$active_articles_only,$list_span,$active_ancestors,$descendant_list,$first_class,$last_class,$list_prefix,$prefix_class,$suppress_url,$suppress_url_sections,$new_article_mode,$article_class,$article_position,$active_article_class,$article_sort,$section_levels,$speaking_block,$speaking_block_form,$label,$labeltag,$label_class,$label_id,$current_children_only,$adi_menu_escape,$adi_menu_prefs,$odd_even,$section_article_sort,$category_article_sort,$ignore_alt_title,$cat_section_map,$exclude_clone,$cat_article_sort,$cat_article_attr,$force_current,$include_top_level,$include_siblings,$current_descendants_only,$exclude_niblings;\n\n	extract(lAtts(array(\n		\'active_ancestors\'		=> \'0\',				// set active class on all ancestors of current section\n		\'active_articles_only\'	=> \'\',				// list articles in the current active section only\n		\'active_class\'			=> \'active_class\',	// CSS class for current section (<a>)\n		\'active_li_class\'		=> \'\',				// CSS class for current section (<li>)\n		\'active_parent\'			=> \'0\',				// set active class on parent of current section\n		\'article_attr\'			=> \'\',				// comma separated list of additional article_custom attributes (section & category articles)\n		\'article_class\'			=> \'menu_article\',	// CSS class on <li> containing article link\n		\'article_exclude\'		=> \'\',				// list of sections not to have articles\n		\'article_include\'		=> \'\',				// exclusive list of sections to have articles (default = all)\n		\'article_position\'		=> \'after\',			// where to place articles: before, after or dovetail with sections\n		\'article_sort\'			=> \'Posted desc\',	// article sort (sections & categories)\n		\'articles\'				=> \'0\',				// include articles in menu\n		\'cat_article_attr\'		=> \'\',				// comma separated list of additional article_custom attributes (category articles only)\n		\'cat_article_exclude\'	=> \'\',				// list of categories not to have articles\n		\'cat_article_include\'	=> \'\',				// exclusive list of categories to have articles (default = all)\n		\'cat_article_sort\'		=> \'\',				// article sort (categories only)\n		\'class\'					=> \'section_list\',	// CSS class for top level <ul>\n		\'clone_title\'			=> \'Summary\',		// default title of child clone **DEPRECATED**\n		\'current_children_only\'	=> \'0\',				// only output child sections/categories/articles of current section\n		\'current_descendants_only\' => \'0\',			// ignore child sections/categories/articles which don\'t belong to current section or its decendants\n		\'exclude_niblings\'		 => \'0\',			// ignore child sections/categories/articles which don\'t belong to current section or its decendants SHOULD BE NIBLINGS/COUSINS\n		\'default_first\'			=> \'1\',				// section \'default\' to be listed first\n		\'default_title\'			=> \'Home\',			// title for \'default\' section\n		\'escape\'				=> \'\',				// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n		\'exclude_clone\'			=> \'0\',				// stop clone appearing\n		\'exclude\'				=> \'\',				// list of sections to be excluded (over and above what\'s excluded in admin)\n		\'first_class\'			=> \'\',				// CSS class on first <li> of list\n		\'force_current\'			=> \'\',				// list of sections defining when they should be considered as \"current\" (sec1,sec2:sec3,sec4;sec5,sec6:sec7,sec8), use for virtuals redirecting to categories\n		\'ignore_alt_title\'		=> \'0\',				// choose to ignore the alternatve titles\n		\'include_childless\'		=> \'0\',				// output childless section\n		\'include_children\'		=> \'1\',				// output children as well as parents\n		\'include_current\'		=> \'0\',				// include the current section (used with sections=\"...\")\n		\'include_default\'		=> \'1\',				// include \'default\' section (more of a \"don\'t exclude default\" setting!)\n		\'include_parent\'		=> \'1\',				// output parent of included sections\' children\n		\'include_siblings\'		=> \'0\',				// sections at same level (without children)\n		\'include_top_level\'		=> \'0\',				// include top level sections (without children)\n		\'label_class\'			=> \'menu_label\',	// CSS class for label\'s tag\n		\'label_id\'				=> \'\',				// HTML ID for label\'s tag\n		\'label\'					=> \'\',				// label string to precede menu\n		\'labeltag\'				=> \'\',				// tag to wrap around label\n		\'last_class\'			=> \'\',				// CSS class on last <li> of list\n		\'link_span\'				=> \'0\',				// <span> contents of link or not\n		\'list_id_prefix\'		=> \'menu_\',			// <li> ID prefix\n		\'list_id\'				=> \'0\',				// output <li> IDs or not\n		\'list_prefix\'			=> \'\',				// prefix added to menu list items\n		\'list_span\'				=> \'0\',				// <span> contents of <li> or not\n		\'menu_id\'				=> \'mainmenu\',		// CSS ID for top level <ul>\n		\'new_article_mode\'		=> \'1\',				// new article mode on by default\n		\'odd_even\'				=> \'0\',				// CSS classes applied to odd/even list items\n		\'override_exclude\'		=> \'\',				// list of sections, excluded in admin, to be included\n		\'parent_class\'			=> \'menuparent\',	// CSS class for parent <li>\n		\'prefix_class\'			=> \'menu_prefix\',	// class added to <span> around prefixes\n		\'role\'					=> \'navigation\',	// ARIA role applied to wraptag\n		\'sections\'				=> \'\',				// list of sections in menu (default = all)\n		\'sort\'					=> \'adi_menu_sort\',	// section sort (use \'\' for database order)\n		\'speaking_block_form\'	=> \'\',				// alternative form for speaking block\n		\'speaking_block\'		=> \'0\',				// enable <span>speaking ... block</span>\n		\'sub_menu_level\'		=> \'0\',				// the level of the submenu to output\n		\'sub_menu\'				=> \'0\',				// generate submenu of current section\n		\'suppress_url_sections\'	=> \'\',				// list of sections to get URL \"#\"\n		\'suppress_url\'			=> \'0\',				// set URL to \"#\" on section links\n		\'test\'					=> \'0\',				// switch on new article mode in 1.0 (now ignored) **DEPRECATED**\n		\'wraptag_class\'			=> \'menu_wrapper\',	// class for wraptag\n		\'wraptag_id\'			=> \'\',				// id for wraptag\n		\'wraptag\'				=> \'\',				// wrap a tag around <ul>\n		\'debug\'					=> \'0\'\n	), $atts));\n\n	if (!adi_menu_installed()) return \"adi_menu not installed\";\n\n	// deprecated attributes\n	if (isset($atts[\'clone_title\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'clone_title\')), E_USER_NOTICE);\n	if (isset($atts[\'test\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'test\')), E_USER_NOTICE);\n	if (isset($atts[\'new_article_mode\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'new_article_mode\')), E_USER_NOTICE);\n	// deprecated mode\n	if (!$new_article_mode) trigger_error(gTxt(\'deprecated_configuration\', array(\'{name}\' => \'new_article_mode=\"0\"\')), E_USER_NOTICE);\n\n	// tidy up supplied attribute values\n	$children_only = 0; // ex-attribute\n	if ($sub_menu_level == \"1\") // switch to standard mode\n		$sub_menu_level = 0;\n\n	// error page\n	if ($sub_menu_level && !if_status(array(),TRUE)) { // submenus don\'t make sense on error pages\n		if ($debug) echo \"Submenu switched off on error page\".br;\n		return \'\';\n	}\n\n	// don\'t have to worry about escaping default title because it\'s stored in $section_list & processed later\n	$clone_title = trim($clone_title);\n	if (empty($clone_title)) // don\'t want it to be empty\n		$clone_title = \'Summary\';\n\n	// menu item sort\n	$sort = trim($sort); // MySQL error if sort = \" \"\n	empty($sort) ? $sort = \'NULL\' : $sort = doSlash($sort); // = database order by default\n\n	// article sorting\n	$fallback_sort = \'Posted desc\';\n	// section article sort\n	empty($article_sort) ? $article_sort = \'NULL\' : $article_sort = doSlash(trim($article_sort)); // set to NULL if article_sort=\"\" supplied\n	$article_sort_list = do_list($article_sort,\';\'); // split article_sort \"title;section1:posted desc;section2:custom_2\"\n	$section_article_sort = array(); // section specific sorting\n	$default_sort = \'\';\n	foreach ($article_sort_list as $this_sort) {\n		$this_section_sort = do_list($this_sort,\':\');\n		if (count($this_section_sort) > 1) // e.g. \"section1:posted desc\"\n			$section_article_sort[$this_section_sort[0]] = doSlash($this_section_sort[1]);\n		else // e.g. \"title; ...\"\n			if ($this_sort)\n				$default_sort = doSlash($this_sort);\n			else // e.g. \";section1:title ...\"\n				$default_sort = \'NULL\';\n	}\n	empty($default_sort) ? $article_sort = doSlash($fallback_sort) : $article_sort = $default_sort; // $article_sort is default section sort, $section_article_sort contains category specific sorts\n	// category article sort\n	empty($cat_article_sort) ? $cat_article_sort = $article_sort : $cat_article_sort = doSlash(trim($cat_article_sort)); // if not supplied, cat_article_sort follows article_sort\n	$cat_article_sort_list = do_list($cat_article_sort,\';\'); // split cat_article_sort \"title;cat1:posted desc;cat2:custom_2\"\n	$category_article_sort = array(); // category specific sorting\n	$default_sort = \'\';\n	foreach ($cat_article_sort_list as $this_sort) {\n		$this_category_sort = do_list($this_sort,\':\');\n		if (count($this_category_sort) > 1) // e.g. \"cat1:posted desc\"\n			$category_article_sort[$this_category_sort[0]] = doSlash($this_category_sort[1]);\n		else // e.g. \"title; ...\"\n			if ($this_sort)\n				$default_sort = doSlash($this_sort);\n			else // e.g. \";cat1:title ...\"\n				$default_sort = \'NULL\';\n	}\n	empty($default_sort) ? $cat_article_sort = $article_sort : $cat_article_sort = $default_sort; // $cat_article_sort is default category specific, $cat_article_sort contains category specific sorts\n\n	// article attributes\n	if (trim($cat_article_attr) == \'\') // if not supplied, cat_article_attr follows article_attr\n		$cat_article_attr = $article_attr;\n\n	// seem to recall this is required to avoid interference from adi_menu_breadcrumb escape attribute ??? FIXED BY new a_m_breadcrumb code?\n	$adi_menu_escape = $escape;\n\n	// massage sections list\n	if ($sub_menu_level) // make sure currently active section IS included e.g. when section normally excluded from main menu but a submenu is required on it\'s own (separately linked-to) page\n		$include_current = 1;\n	if ($include_current) // add current section to list\n		empty($sections) ? $sections = $s : $sections = $sections.\",$s\";\n	// section-sensitive current-level-specific submenu\n	if ($sub_menu) { // override sections list with current section\n		$sections = $s;\n		$children_only = 1;\n	}\n\n	// fiddle with default\n	if (!$include_default) // add default to exclude list\n		empty($exclude) ? $exclude = \"default\" : $exclude .= \",default\";\n\n	// get bulk section info from database (using $sections & $exclude attributes)\n	$section_list = adi_menu_section_list($sections,$exclude,$override_exclude,$sort);\n\n	// make up list of siblings\n	$siblings = adi_menu_siblings($section_list);\n\n	// include list (used by prune later)\n	$include_list = array();\n	if (!empty($sections)) // specific sections (& hence their children) selected\n		$include_list = do_list($sections); // trim spaces around section names in array\n\n	// dark forces\n	if ($force_current) {\n		// force_current=\"sec1,sec2:sec3,sec4; sec5,sec6:sec7,sec8\" - sec1 or sec2 considered current if sec3 or sec4 are current\n		// generate array indexed by section => array of current sections (when to be considered current itself)\n		$force_lists = do_list($force_current,\';\'); // (sec1,sec2:sec3,sec4) (sec5,sec6:sec7,sec8)\n		$force_current = array();\n		foreach ($force_lists as $force) {\n			$forced = do_list($force,\':\'); // (sec1,sec2) (sec3,sec4)\n			if (count($forced) == 2) {\n				$f1 = do_list($forced[0]); // (sec1,sec2)\n				$f2 = do_list($forced[1]); // (sec3,sec4)\n				if ($forced[1])\n					foreach ($f1 as $section)\n						$force_current[$section] = $f2; // sec1 -> (sec3,sec4)\n			}\n		}\n	}\n\n	// do articles bit\n	$article_list = $cat_article_list = array();\n	if ($articles) {\n		// SECTIONS\n		// sections that are allowed articles\n		if (empty($article_include))\n			$section_article_include = array_keys($section_list); // all sections\n		else\n			$section_article_include = do_list($article_include);\n		// sections that are NOT allowed articles\n		if (empty($article_exclude))\n			$section_article_exclude = array();\n		else\n			$section_article_exclude = do_list($article_exclude);\n		// section_article_list = section_article_include minus section_article_exclude\n		$section_article_list = adi_menu_array_subtract($section_article_include,$section_article_exclude);\n		if ($new_article_mode)\n			$article_list = adi_menu_get_articles($section_list,$section_article_list,array(),$debug);\n		// CATEGORIES\n		// categories that are allowed articles\n		$category_list = safe_column(\'name\',\'txp_category\',\"name != \'root\' AND type=\'article\'\");\n		if (empty($cat_article_include))\n			$category_article_include = array_keys($category_list); // all categories\n		else\n			$category_article_include = do_list($cat_article_include);\n		// categories that are NOT allowed articles\n		if (empty($cat_article_exclude))\n			$category_article_exclude = array();\n		else\n			$category_article_exclude = do_list($cat_article_exclude);\n		// category_article_list = category_article_include minus category_article_exclude\n		$category_article_list = adi_menu_array_subtract($category_article_include,$category_article_exclude);\n		if ($new_article_mode)\n			$cat_article_list = adi_menu_get_articles($section_list,array(),$category_article_list,$debug);\n	}\n\n	// set up hierarchy\n	$hierarchy = $original_hierarchy = adi_menu_hierarchy($section_list,$article_list,$cat_article_list);\n\n	// descendant list\n	$descendant_list = array();\n	$descendant_list = adi_menu_descendants($hierarchy,\'adi_menu_root\');\n\n	// sub or normal menu?\n	$section_levels = adi_menu_section_levels($hierarchy);\n	if ($sub_menu_level) { // set up a new include list for level-specific submenus\n		$include_list = array();\n		$ancestor = adi_menu_find_ancestor($sub_menu_level);\n		$descendants = adi_menu_find_descendant($sub_menu_level);\n		$this_section = $s; // don\'t want to muck with $s\n		if (empty($this_section)) $this_section = \'default\'; // to cope with error page (thanks CeBe)\n		// submenu scenarios\n		if ($section_levels[$this_section] == $sub_menu_level) { // ONE: current section at required level\n			$include_list[] = $section_list[$this_section][\'adi_menu_parent\']; // add current\'s parent to list to get siblings\n			$children_only = \"1\"; // but don\'t want current section\'s parent itself\n			if ($debug) echo \"Submenu scenario ONE\".br.br;\n		}\n		else if ($section_levels[$this_section] == ($sub_menu_level - 1)) { // TWO: current sections\' children at required level\n			$include_list[] = $this_section; // add current section to list to get children\n			$children_only = \"1\"; // but don\'t want current section itself\n			if ($debug) echo \"Submenu scenario TWO\".br.br;\n		}\n		else if ($ancestor) { // THREE: there\'re ancestors at required level\n				// add ancestor\'s parent to list to get siblings\n				$include_list[] = $section_list[adi_menu_find_ancestor($sub_menu_level)][\'adi_menu_parent\'];\n				$children_only = \"1\"; // don\'t want ancestor\'s parent itself\n			if ($debug) echo \"Submenu scenario THREE\".br.br;\n		}\n		// FOUR: descendants (beyond immediate children) at right level\n		else if ($descendants) {\n			foreach ($descendants as $descendant)\n				$include_list[] = $section_list[$descendant][\'adi_menu_parent\']; // add each descendant\'s parent to list to get children (multiple descendants may be siblings or cousins)\n			$children_only = \"1\"; // but don\'t want current section\'s parent itself\n			if ($debug) echo \"Submenu scenario FOUR\".br.br;\n		}\n		else { // FIVE: can\'t find anything at requested level, so kill the menu\n			$hierarchy = array();\n			if ($debug) echo \"Submenu scenario FIVE\".br.br;\n		}\n	}\n	else // normal menu\n		if (empty($sections) && !$include_parent) // make up list of top level sections (& cater for old submenu mode)\n			$include_list = array_keys($hierarchy); // all top level sections\n\n	// prune hierarchy\n	if ($include_list) {\n		if ($children_only) $include_parent = \"0\"; // OLD SUBMENU?\n		$hierarchy = adi_menu_prune($hierarchy,$include_list,$section_levels,$siblings);\n	}\n\n	//??? TEST ON LOWER LEVELS - NO COZ LOSING THIS\n	if ($current_descendants_only)\n		$hierarchy = adi_menu_prune($hierarchy,array($s),$section_levels,$siblings);\n\n	// map sections to categories\n	$cat_section_map = adi_menu_cat_section_map($section_list);\n\n	// DEBUG\n	if ($debug) {\n		echo \"SECTION INCLUDE LIST (all if empty):\".br;\n		dmp($include_list);\n		echo \"SECTION EXCLUDE LIST (none if empty):\".br;\n		echo $exclude.br.br;\n		echo \"SECTION LIST:\";\n		dmp($section_list);\n		if ($articles) {\n			echo \"SECTION ARTICLE INCLUDE LIST:\";\n			dmp($section_article_include);\n			echo \"SECTION ARTICLE EXCLUDE LIST:\";\n			dmp($section_article_exclude);\n			echo \"SECTION ARTICLE LIST (include minus exclude):\";\n			dmp($section_article_list);\n			echo \"CATEGORY ARTICLE INCLUDE LIST:\";\n			dmp($category_article_include);\n			echo \"CATEGORY ARTICLE EXCLUDE LIST:\";\n			dmp($category_article_exclude);\n			echo \"CATEGORY ARTICLE LIST (include minus exclude):\";\n			dmp($category_article_list);\n			if ($new_article_mode) {\n				echo \"ARTICLE LIST (by section):\";\n				dmp($article_list);\n				echo \"ARTICLE LIST (by category):\";\n				dmp($cat_article_list);\n			}\n		}\n		else\n			echo \'(ARTICLE MODE NOT SELECTED)\'.br.br;\n		echo \'SECTION ARTICLE SORT (default=\'.$article_sort.\'):\';\n		dmp($section_article_sort);\n		echo \'CATEGORY ARTICLE SORT (default=\'.$cat_article_sort.\'):\';\n		dmp($category_article_sort);\n		echo \"ORIGINAL HIERARCHY:\";\n		dmp($original_hierarchy);\n		if (isset($include_list)) {\n			echo \"PRUNED HIERARCHY:\";\n			dmp($hierarchy);\n		}\n		else\n			echo \'(HIERARCHY NOT PRUNED)\'.br.br;\n		if (isset($section_levels)) {\n			echo \"SECTION LEVELS:\";\n			dmp($section_levels);\n		}\n		echo \"DESCENDANT LIST:\";\n		dmp($descendant_list);\n		if (isset($siblings)) {\n			echo \"SIBLINGS:\";\n			dmp($siblings);\n		}\n		echo \'CATEGORY-SECTION MAP:\';\n		$cat_section_map = adi_menu_cat_section_map($section_list);\n		dmp($cat_section_map);\n		echo \"FORCE CURRENT MAPPING:\";\n		dmp($force_current);\n		echo \"VARIOUS:\".br;\n		echo \"Current section (\\$s) = $s\".br;\n		echo \"Current section\'s level = $section_levels[$s]\".br;\n		echo \"Submenu level = $sub_menu_level\".br;\n		echo \"Current article id = \".$pretext[\'id\'].\"\".br;\n		echo \"Articles = $articles\".br;\n		echo \"New article mode = $new_article_mode\".br;\n		$page_status = !empty($GLOBALS[\'txp_error_code\']) ? $GLOBALS[\'txp_error_code\'] : $pretext[\'status\'];\n		echo \"Page status: $page_status\";\n		echo if_status(array(),TRUE) ? \" (Normal page)\".br : \" (Error page)\".br;\n		echo br.\"SUPPLIED ATTRIBUTES:\";\n		dmp($atts);\n		echo \"ARTICLE ATTRIBUTES:\";\n		dmp(adi_menu_get_article_attr($article_attr));\n		echo \"CATEGORY ARTICLE ATTRIBUTES:\";\n		dmp(adi_menu_get_article_attr($cat_article_attr));\n		if ($adi_menu_prefs) { // admin interface only\n			echo \"PREFS\".br;\n			foreach ($adi_menu_prefs as $name => $value)\n				echo $name.\': \'.adi_menu_prefs($name).br;\n		}\n	}\n\n	// HTML5 awareness\n	$role = \'\';\n	if (isset($prefs[\'doctype\']))\n		if ($prefs[\'doctype\'] == \'html5\') {\n			if ($wraptag && $role)\n				$role = \' role=\"\'.$role.\'\"\';\n		}\n\n	// generate markup\n	$out = adi_menu_markup($hierarchy,0);\n	$out = trim(implode($out));\n	if ($out && $wraptag) { // add wraptag stuff\n		$wrap_attr = \'\';\n		if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n		if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n		$wrap_attr .= $role;\n		$out = tag($out,$wraptag,$wrap_attr);\n	}\n\n	// output markup to page\n	return $out;\n\n}\n', '\n// NOTE\n\n/*\n	adi_menu - Section hierarchy, section menu and breadcrumb trail\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	1.4		- new feature: virtual sections\n			- new categories features:\n				- redirect to Article category option\n				- categoried articles in menu\n				- new attributes \'cat_article_include\', \'cat_article_exclude\', \'cat_article_sort\', \'cat_article_attr\'\n			- new admin feature: drag & drop section ordering\n			- new classes \'menu_virtual\', \'menu_category\', \'menu_clone\'\n			- new attribute \'ignore_alt_title\' for adi_menu & adi_breadcrumb tags\n			- new attributes \'exclude_clone\', \'role\' **, \'override_exclude\' & suppress_url_sections for adi_menu tag\n			- new attribute \'labeltag\' for adi_breadcrumb\n			- new attributes \'force_current\' & \'current_descendants_only\' (for detail)\n			- new tags: adi_menu_info & adi_menu_if_info\n			- fix: stop clone inheriting an alternative title\n			- fix: adi_breadcrumb now uses alternative title\n			- fix: article list defaults to live only (use status=\"\" for both live & sticky) **\n			- fix: active class applied to menu items that are redirected to active section **\n			- fix: disallow sections to be selected as parents of, or redirect to, themselves\n			- fix: disabled submenus on error pages\n			- fix: breadcrumb less crummy on error pages\n			- fix: prevent empty HTML class attribute when adi_breadcrumb \'linkclass\' is empty (thanks CeBe)\n			- fix: table prefix issue (thanks sochicomputerRU)\n			- deprecated attributes: \'clone_title\', \'test\', \'new_article_mode\' **\n			- deprecated \"old article mode\" **\n			- now updates lastmod\n			- TXP 4.5+ only\n			- updated for TXP 4.6\n	beta13	- removed versions output from debug\n			- fix: blank adi_menu_redirect_link now set to default (0) not \'\'\n			- fixed dodgy 4.6 fix: restored greyness to menu key item\n			** upgrade considerations\n			*** beta users visit Admin tab after install\n	1.3.1	- lifecycle \"upgrade\" pseudo-event\n			- fixed links to section tab in TXP 4.5\n	1.3		- TXP 4.5-ified\n			- moved install/uninstall to plugin options\n			- Textpack added\n			- tooltips thrown in the tip\n			- fix: preferences fully deleted on uninstall\n			- enhancement: adi_menu admin section redirect warning message\n			- enhancement: admin preference \"Prevent widowed words in article titles\" now obeyed (for springworks)\n			- code tidy up & old code removed\n			- change: removed import functionality\n			- change: admin tab name changed \"adi_menu\" -> \"Menu\"\n			- change: use pluggable_ui instead of old inject method in Article tab\n	1.2		- section-specific article sort (for bg)\n			- enhancement: adi_menu admin sections link to TXP section tab\n			- fix: ampersands in article titles (thanks renobird)\n			- fix: admin styling for Hive\n			- now uses lifecycle events for install/uninstall\n	1.1.1	- fixed: one adi_menu tag affecting operation of another (thanks Teemu)\n			- fix: renamed some globals\n			- fix: restrict style to adi_menu_admin tab only\n	1.1		- enhancement: section redirection (for me & caruso_g)\n			- enhancement: alternative section titles\n			- enhancement: adi_menu admin tab now only shows admin options (i.e. install/uninstall/import) if relevant\n			- enhancement: admin tab sanity checks - sections renamed or links deleted\n			- enhancement: write tab section popup list format & indent preferences (for Bloke)\n			- enhancement: new article method, attribute \'new_article_mode\' to switch off (\'test\' attribute ignored)\n			- new attribute: \'odd_even\' to generate <li> classes on odd/even items\n			- change: section sort now defaults to \"adi_menu_sort\" (use sort=\"\" to override)\n			- fix: dodgy bold in hierarchy summary, added italics & \"default\" now \"Home\"\n			- fix: errors in admin tab if sections renamed\n			- fix: submenus not being generated properly (again)\n	1.0		- testing only, not officially released\n	0.12.1	- fix: error on error page when sub_menu_level > 1 (thanks CeBe)\n			- fix: error with submenu if active section excluded in admin\n	0.12	- new attribute \'current_children_only\' (for ttr & caruso_g)\n			- enhancement: new (automatic) way of coping with accented characters (thanks ttr)\n			- enhancement: minor admin tab improvements - admin options (i.e. install/uninstall/import) only shown if relevant\n			- fix: error when adi_menu_breadcrumb used more than once on a page (thanks jcd)\n			- fix: current_children_only not functioning correctly (thanks jcd)\n			- fix: couple of uninitialised variables generating error_log messages (lines 827, 837)\n	0.11	- new adi_menu attributes: \'wraptag\', \'wraptag_id\', \'wraptag_class\'\n			- enhancement: sections attribute now overrides admin excluded sections\n	0.10	- enhancement: speaking blocks\n			- new adi_menu attributes: \'speaking_block\' & \'speaking_block_form\'\n			- new adi_menu attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n			- fix: empty <ul></ul> output for section-less submenus\n	0.9.2	- fix: static sections (sed_section_fields) visible to non-Publisher users in Write tab (thanks Didjee)\n			- fix: sections with clones wrongly indented in Write tab section popup\n	0.9.1	- fix: accented characters getting escaped in admin (for jpdupont)\n			- enhancement: new \'escape\' attribute option: htmlspecial\n	0.9		- new adi_menu attribute: \'sub_menu_level\' (for emerald)\n			- fix: selected section in Write tab changes to first section in list after saving new article\n	0.8.1	- fix: submenus not being generated properly\n			- fix: suppressed output of \'id=\"\"\' & \'class=\"\"\' in top level <ul> when menu_id=\"\" & class=\"\"\n	0.8		- enhancement: section hierarchy in Write tab section popup\n			- new adi_menu attributes: \'first_class\' & \'last_class\' (for renobird)\n			- new adi_menu attributes: \'list_prefix\' & \'prefix_class\' (for nubian)\n			- new adi_menu attribute: suppress_url (for renobird)\n			- enhancement: adi_menu & adi_menu_breadcrumb tags now output error message if adi_menu not installed\n			- fix: MySQL v4 errors in adi_menu admin (for debegray)\n				- used \'TINYINT(1) DEFAULT 0\' instead of \'BOOLEAN DEFAULT FALSE\'\n				- where clause \'1=1\' instead of \'TRUE\'\n			- fix: loop detection failure with A->B->C->B sub-loops\n			- optimisation: removed section_list[\'name\'] & $hierarchy[\'name\'] array elements\n			- articles enhancement (beta) new attributes: \'article_class\', \'article_position\', \'article_sort\'\n	0.7		- new adi_menu & adi_menu_breadcrumb attribute: \'escape\'\n			- new adi_menu attribute: \'active_ancestors\' (for Didjee)\n			- fix: adi_menu admin hierarchy summary now shows children again\n			- fix: no longer get double row of admin page tabs before adi_menu installed\n	0.6		- enhancement: article capability added\n			- new adi_menu attributes: \'articles\', \'article_attr\', \'article_include\', \'article_exclude\'\n			- new adi_menu attributes: \'include_children\', \'include_current\' & \'active_parent\' (for macTigers)\n			- new adi_menu attribute: \'active_articles_only\' (for FireFusion)\n			- new adi_menu attribute: \'list_span\' (sIFR support, for Lasse Gyrn)\n			- code optimisations (thanks to net-carver)\n	0.5		- fix: adi_menu errors if \'default\' section excluded (thanks Uli)\n			- fix: adi_menu tab section hierarchy would generate lots of errors if adi_menu not installed\n			- enhancement: sections attribute functionality improved (for Si & freischwimmen)\n				- children now output automatically\n				- new tags \'include_parent\' & \'include_childless\'\n	0.4		- enhancement: adi_breadcrumb tag attribute \'link_last\' - last section crumb in list displayed in plain text (now the default behaviour)\n			- fix: adi_menu_breadcrumb error when visiting sections/pages that don\'t exist when in clean URL mode\n			- fix: adi_menu_breadcrumb displayed default section as link regardless of \'link\' attribute setting\n	0.3		- enhancement: new adi_menu tag attribute \'link_span\' - wrap <span>...</span> around contents of links\n			- enhancement: new adi_menu tag attributes \'list_id\' & \'list_id_prefix\' - output unique IDs to <li> elements\n			- enhancement: new adi_menu tag attribute \'active_li_class\' - output class on active <li>\n			- enhancement: adi_menu admin now displays summary of configured section hierarchy\n			- modification: adi_breadcrumb tag attribute \'sep\' deprecated for \'separator\'\n	0.2		- fix: adi_menu_breadcrumb error when visiting pages that are excluded in adi_menu admin\n			- fix: adi_menu_breadcrumb now copes with section loops, error message output\n			- fix: adi_menu tag can now be used more than once on a page\n			- enhancement: adi_menu admin section loop warning message\n			- enhancement: adi_menu admin now displays sections in alphabetical order\n	0.1		- initial release\n\n	DOWNGRADE (from 1.4 to 1.1+ only)\n	- go to adi_menu plugin options tab\n	- add \"&step=downgrade\" to end of URL & hit return\n	- then immediately install previous version of adi_menu\n\n*/\n\n/* TODO\n\n	- kill off clone column (presence of clone title will signify clone or not)\n	- kill off attributes deprecated in 1.4\n	- use pluggable_ui for adi_menu_article_tab()\n	- general purpose custom html attribute, e.g. data-xyz=\"abc\" (configurable per section in admin)\n	- deprecate old submenu method (probably just need to stop talking about it)\n	- make section_list array element names consistent (\'adi_menu_parent\' -> \'parent\')\n	- adi_menu_reborn\n		- cull attributes\n		- a_m_prune does all the heavy lifting, lose the acres of globals\n		- use generic term \"menu item\" instead of section\n		- container tag/user defineable form\n		- well defined menu/submenu scenarios & a_m recipes\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_menu\')\n		->register(\'adi_menu_breadcrumb\')\n		->register(\'adi_menu_info\')\n		->register(\'adi_menu_if_info\')\n	;\n}\n\n// variables used by both admin tab AND public side\nglobal $adi_menu_debug,$adi_menu_db_debug,$adi_menu_article_form,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_vs_prefix,$adi_menu_info_types;\n\n$adi_menu_debug = 0; // display admin debug info\n$adi_menu_db_debug = 0; // display database debug info\n\n$adi_menu_article_form = \'adi_menu_articles\'; //  for old article mode\n\n$adi_menu_vs_prefix = \'v_\'; // prefix for virtual sections (used in parent field and in markup)\n\n// DB fields			txp_section					adi_menu\n//	section name		name						name\n//	section title		title						title\n//	parent section		adi_menu_parent				parent\n//	alternative title	adi_menu_alt_title			alt_title\n//	cloned?				adi_menu_clone				clone\n//	clone title			adi_menu_title				clone_title\n//	section redirect	adi_menu_redirect_section	section\n//	link redirect		adi_menu_redirect_link		link\n//	category redirect	adi_menu_redirect_category	category\n//	sort				adi_menu_sort				sort\n\n$adi_menu_sql_fields = \'name,title,adi_menu_parent,adi_menu_title,adi_menu_exclude,adi_menu_clone,adi_menu_sort,adi_menu_redirect_section,adi_menu_redirect_link,adi_menu_redirect_category,adi_menu_alt_title\';\n\n$adi_menu_vs_sql_fields = \'id as adi_menu_id,name,title,parent as adi_menu_parent,exclude as adi_menu_exclude,sort as adi_menu_sort,section as adi_menu_redirect_section,link as adi_menu_redirect_link,category as adi_menu_redirect_category,alt_title as adi_menu_alt_title,clone as adi_menu_clone,clone_title as adi_menu_title\';\n\n// menu info type to DB column mapping - used by <txp:adi_menu_info /> & <txp:adi_menu_if_info /> tags\n$adi_menu_info_types =\n	array(\n	// indexed by user-supplied \"type\": \'column\' => DB column(s) to reference, \'blank\' => what constitutes a blank/empty/not-set value (column lists only used by <txp:adi_menu_if_info /> - TRUE if any not blank)\n		// directly mapped\n		\'parent\'			=> array(\'column\' => \'adi_menu_parent\', \'blank\' => \'\'),\n		\'title\'				=> array(\'column\' => \'title\', \'blank\' => \'\'),\n		\'alt_title\'			=> array(\'column\' => \'adi_menu_alt_title\', \'blank\' => \'\'),\n		\'exclude\'			=> array(\'column\' => \'adi_menu_exclude\', \'blank\' => \'0\'), // boolean\n		\'clone\'				=> array(\'column\' => \'adi_menu_clone\', \'blank\' => \'0\'), // boolean\n		\'clone_title\'		=> array(\'column\' => \'adi_menu_title\', \'blank\' => \'\'),\n		\'redirect_section\'	=> array(\'column\' => \'adi_menu_redirect_section\', \'blank\' => \'\'),\n		\'redirect_link\'		=> array(\'column\' => \'adi_menu_redirect_link\', \'blank\' => \'0\'), // column DEFAULT is zero\n		\'redirect_category\'	=> array(\'column\' => \'adi_menu_redirect_category\', \'blank\' => \'\'),\n		// special types\n		\'redirect\'			=> array(\'column\' => \'adi_menu_redirect_section,adi_menu_redirect_link,adi_menu_redirect_category\', \'blank\' => \'\'), // column list: TRUE if any not blank\n		\'menu_title\'		=> array(\'column\' => \'title\', \'blank\' => \'\'), // \"deduced\" information, based on alternative title availability & ignore_alt_title attribute\n		\'ancestors\'			=> array(\'column\' => \'adi_menu_parent\', \'blank\' => \'\') // find ancestors, starting with parent\n	);\n\nif (@txpinterface == \'admin\') {\n	global $adi_menu_txp460;\n\n	// it\'s a modern world\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	$adi_menu_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	adi_menu_init();\n}\n\nfunction adi_menu_init() {\n// general admin setup\n	global $prefs,$event,$textarray,$adi_menu_url,$adi_menu_prefs,$adi_menu_debug,$adi_menu_db_debug,$adi_menu_sed_sf_installed,$adi_menu_plugin_status;\n\n	$adi_menu_installed = adi_menu_installed();\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_menu_gtxt = array(\n		\'adi_alt_title\' => \'Alternative title\',\n		\'adi_clone_title\' => \'Clone title\',\n		\'adi_clone\' => \'Clone\',\n		\'adi_for_example\' => \'For example\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_menu\' => \'Menu\',\n		\'adi_menu_duplicate_warning\' => \'Standard/virtual section name duplicates\',\n		\'adi_menu_loop_warning\' => \'Parent/child loops found\',\n		\'adi_menu_order_update_failed\' => \'Menu order update failed\',\n		\'adi_menu_order_updated\' => \'Menu order updated\',\n		\'adi_menu_parent_warning\' => \'Sections with non-existant parents\',\n		\'adi_menu_redirect_category_warning\' => \'Sections redirected to non-existant categories\',\n		\'adi_menu_redirect_link_warning\' => \'Sections redirected to non-existant links\',\n		\'adi_menu_redirect_section_warning\' => \'Sections redirected to non-existant sections\',\n		\'adi_menu_summary_footnote\' => \'The menu may be modified further using adi_menu tag attributes\',\n		\'adi_menu_summary_note_drag_drop\' => \'Drag and drop menu items to change order\',\n		\'adi_menu_summary_note_excluded\' => \'Excluded sections are shown in <span>grey</span>\',\n		\'adi_menu_summary_note_key\' => \'sections in <b>bold</b> are redirected, sections in <i>italics</i> have alternative titles\',\n		\'adi_menu_summary_note_key2\' => \'Key: <span><b>Redirected sections</b>, <i>Alternative titles</i>, \"Virtual sections\", <span>Excluded sections</span></span>\',\n		\'adi_menu_summary_note_prefix\' => \'In adi_menu tags, virtual sections must be referenced using a prefix\',\n		\'adi_menu_summary_note_virtual\' => \'Virtual sections are shown in &quot;quotes&quot;\',\n		\'adi_menu_summary_note\' => \'The above configuration will generate the following section hierarchy\',\n		\'adi_menu_update_fail\' => \'Menu update failed\',\n		\'adi_menu_update_order\' => \'Update menu order\',\n		\'adi_menu_updated\' => \'Menu updated\',\n		\'adi_menu_virtual_section_deleted\' => \'Virtual section deleted\',\n		\'adi_menu_virtual_section_exists\' => \'Virtual section already exists\',\n		\'adi_menu_virtual_sections\' => \'Virtual sections\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_redirect_category\' => \'Redirect category\',\n		\'adi_redirect_link_id\' => \'Redirect link ID\',\n		\'adi_redirect_link\' => \'Redirect link\',\n		\'adi_redirect_section\' => \'Redirect section\',\n		\'adi_summary\' => \'Summary\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_menu\' => \'Update menu\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_write_tab_select_format\' => \'Write tab section list format\',\n		\'adi_write_tab_select_indent\' => \'Write tab section list indent\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_menu_gtxt;\n\n	// Textpack\n	$adi_menu_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_menu\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_menu_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_menu_lifecycle\',\'plugin_lifecycle.adi_menu\');\n\n	// set the privilege levels\n	add_privs(\'adi_menu_admin\',\'1,2,3,6\');\n\n	// default plugin preference settings\n	$adi_menu_prefs = array(\n		\'write_tab_select_indent\'	=> \'0\', // or 0\n		\'write_tab_select_format\'	=> \'name\', // or \'title\'\n		\'write_tab_select_default\'	=> \'0\', // or 1\n	);\n\n	// adi_menu admin tab under \'Presentation\'\n	if ($adi_menu_installed) {\n		register_tab(\'presentation\',\'adi_menu_admin\',gtxt(\'adi_menu\'));\n		register_callback(\'adi_menu_admin\',\'adi_menu_admin\');\n		if ((adi_menu_prefs(\'write_tab_select_format\') != \'name\') || adi_menu_prefs(\'write_tab_select_indent\')) //??? TEST ON NEW INSTALLS\n			register_callback(\'adi_menu_article_tab\',\'article_ui\',\'section\');\n	}\n\n	// check out other plugins & their versions\n	if ($adi_menu_installed)\n		$adi_menu_sed_sf_installed = safe_row(\"version\",\"txp_plugin\",\"status = 1 AND name=\'sed_section_fields\'\",$adi_menu_db_debug);\n\n	// plugin options\n	$adi_menu_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_menu\',$adi_menu_db_debug);\n	if ($adi_menu_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_menu\'); // defaults to priv \'1\' only\n		register_callback(\'adi_menu_options\',\'plugin_prefs.adi_menu\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_menu_options\');\n		register_tab(\'extensions\',\'adi_menu_options\',\'adi_menu options\');\n		register_callback(\'adi_menu_options\',\'adi_menu_options\');\n	}\n\n	// style\n	if ($event == \'adi_menu_admin\')\n		register_callback(\'adi_menu_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if ($event == \'adi_menu_admin\')\n		register_callback(\'adi_menu_admin_script\',\'admin_side\',\'head_end\');\n}\n\nfunction adi_menu_admin($event, $step) {\n// adi_menu admin action!\n	global $prefs,$adi_menu_sed_sf_installed,$adi_menu_prefs,$adi_menu_debug,$adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$installed = adi_menu_installed();\n\n	$something = gps(\"something\");\n	$res = FALSE;\n\n	if ($installed) {\n		if ($step == \"pref_update\") {\n			foreach ($adi_menu_prefs as $name => $value) {\n				if ($adi_menu_debug)\n					echo $name.\'=\'.ps($name).\' \';\n				adi_menu_prefs($name,doStripTags(ps($name)));\n			}\n		   	pagetop(\"adi_menu admin\",gTxt(\'preferences_saved\'));\n		}\n		else if ($step == \"update\") {\n			$sections = adi_menu_section_list(\'\',\'\',TRUE);\n			$message = adi_menu_update($sections);\n	   		pagetop(\"adi_menu admin\",$message);\n		}\n		else if ($step == \"sort_update\") {\n			$message = adi_menu_sort_update();\n	   		pagetop(\"adi_menu admin\",$message);\n		}\n		else if ($step == \"delete\") {\n			$name = gps(\'name\');\n			safe_delete(\'adi_menu\',\'name=\"\'.$name.\'\"\',$adi_menu_db_debug);\n	   		pagetop(\"adi_menu admin\",gTxt(\'adi_menu_virtual_section_deleted\'));\n		}\n		else // do nothing\n		   	pagetop(\'adi_menu admin\');\n	}\n	else // not installed\n		pagetop(\"adi_menu admin\",array(gTxt(\'adi_not_installed\'),E_ERROR));\n\n	if ($installed) {\n\n		adi_menu_upgrade(); // txpdev\n\n		// signal fiddle\n		if (($step == \'update\') || ($step == \'sort_update\') || ($step == \'delete\'))\n			update_lastmod();\n\n		// get to work\n		$db_sections = adi_menu_section_list(\'\',\'\',TRUE,\'name\');\n\n		if ($adi_menu_debug) {\n			$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n			dmp($section_list);\n			echo \'HIERARCHY:\';\n			$hierarchy = adi_menu_hierarchy($section_list);\n			dmp($hierarchy);\n			echo \'DESCENDANT LIST:\';\n			$descendant_list = array();\n			$descendant_list = adi_menu_descendants($hierarchy,\'adi_menu_root\');\n			dmp($descendant_list);\n			echo \'SECTION LEVELS:\';\n			$section_levels = adi_menu_section_levels($hierarchy);\n			dmp($section_levels);\n			echo \'CATEGORY/SECTION MAP:\';\n			$cat_section_map = adi_menu_cat_section_map($section_list);\n			dmp($cat_section_map);\n			if ($adi_menu_sed_sf_installed)\n				print \'sed_section_fields v\'.$adi_menu_sed_sf_installed[\'version\'].\' is installed & active\';\n		}\n\n		// SANITY CHECKS\n		// check for section loops\n		$out = array();\n		foreach ($db_sections as $section => $section_data)\n			if (adi_menu_loop_check($db_sections,$section_data[\'adi_menu_parent\']))\n				$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\');\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_loop_warning\').\': \'.implode(\', \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing parents\n		$missing_parents = adi_menu_parent_check($db_sections);\n		$out = array();\n		foreach ($missing_parents as $section => $parent)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$parent;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_parent_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect sections\n		$missing_sections = adi_menu_redirect_section_check($db_sections);\n		$out = array();\n		foreach ($missing_sections as $section => $redirect_section)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$redirect_section;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_section_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect links\n		$missing_links = adi_menu_redirect_link_check($db_sections);\n		$out = array();\n		foreach ($missing_links as $section => $link)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section) ? \' (virtual)\' : \'\').\' -> \'.$link;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_link_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for missing redirect categories\n		$missing_categories = adi_menu_redirect_category_check($db_sections);\n		$out = array();\n		foreach ($missing_categories as $section => $category)\n			$out[] = adi_menu_strip_prefix($section).(adi_menu_is_virtual($section)? \' (virtual)\' : \'\').\' -> \'.$category;\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_redirect_category_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// check for real section names clashing with virtual section names (e.g. real \'v_asd\' & virtual \'asd\')\n// 		$rs = safe_query(\'SELECT \'.safe_pfx(\'txp_section\').\'.name, \'.safe_pfx(\'adi_menu\').\'.name FROM \'.safe_pfx(\'txp_section\').\', adi_menu WHERE \'.safe_pfx(\'txp_section\').\".name = concat(\'\".$adi_menu_vs_prefix.\"\',\".safe_pfx(\'adi_menu\').\'.name);\',$adi_menu_db_debug);\n		$rs = safe_query(\'SELECT \'.safe_pfx(\'txp_section\').\'.name, \'.safe_pfx(\'adi_menu\').\'.name FROM \'.safe_pfx(\'txp_section\').\', \'.safe_pfx(\'adi_menu\').\' WHERE \'.safe_pfx(\'txp_section\').\".name = concat(\'\".$adi_menu_vs_prefix.\"\',\".safe_pfx(\'adi_menu\').\'.name);\',$adi_menu_db_debug);\n		$out = array();\n		while ($a = nextRow($rs))\n			$out[] = $adi_menu_vs_prefix.$a[\'name\'].\' (virtual \'.$a[\'name\'].\')\';\n		if ($out)\n			echo tag(\'** \'.gTxt(\'adi_menu_duplicate_warning\').\': \'.implode(\'; \',$out).\' **\',\'p\',\' class=\"adi_menu_warning warning\"\');\n\n		// hide redirect categories column if there\'re no categories defined\n		$categories_found = adi_menu_category_popup(\'test\',\'\');\n		if ($categories_found)\n			$hide_cats = \'\';\n		else\n			$hide_cats = \' class=\"adi_menu_hide\"\';\n\n		// output adi_menu settings table\n		echo form(\n			hed(gTxt(\'adi_menu\'),1,\' class=\"txp-heading\"\')\n			.startTable(\'list\',\'\',\"edit-table txp-list\")\n			.tag(\n				tr(\n					hcell(gTxt(\'section_name\'))\n					.hcell(gTxt(\'title\'))\n					.hcell(gTxt(\'adi_alt_title\'))\n					.hcell(gTxt(\'exclude\'))\n					.hcell(gTxt(\'parent\'))\n					.hcell(gTxt(\'sort_value\'),\'\',\' class=\"adi_menu_sort\"\') // hidden if jQuery-UI is available\n					.hcell(gTxt(\'adi_clone\'))\n					.hcell(gTxt(\'adi_clone_title\'))\n					.hcell(gTxt(\'adi_redirect_section\'))\n					.hcell(gTxt(\'adi_redirect_link\'))\n					.hcell(gTxt(\'adi_redirect_category\'),\'\',$hide_cats) // may be hidden if there\'re no categories defined\n					.hcell(sp) // delete buttons\n				)\n				,\'thead\'\n			)\n			.adi_menu_display_settings($db_sections)\n			.endTable()\n			.tag(\n				fInput(\"submit\",\"update\",gtxt(\'save\'),\"publish\").\n				eInput(\"adi_menu_admin\").sInput(\"update\"),\n				\'div\'\n			)\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_menu_form\'\n		);\n	}\n\n	// output hierarchy summary\n	global $default_first,$include_children,$default_title,$menu_id,$escape,$clone_title,$parent_class,$list_id; // globals: yuk!\n	if ($installed) {\n		$sections=$exclude=\"\";\n		$default_first=\"1\";\n		$list_id=\"1\";\n		$include_children=\"1\";\n		$menu_id = \"mainmenu\";\n		$escape = \'\';\n		$default_title = \'Home\';\n		$clone_title = \'Summary\';\n		$parent_class = \'menuparent\';\n		$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n		$hierarchy = adi_menu_hierarchy($section_list);\n		$out = adi_menu_markup($hierarchy,0);\n		echo \'<div id=\"adi_menu_summary\">\';\n		echo tag(gTxt(\'adi_summary\'),\'h2\');\n		$summary_note = \' (\'.gTxt(\'adi_menu_summary_note_key\').\').\';\n		$vs = safe_rows(\'name\',\'adi_menu\',\'1=1 ORDER BY RAND() LIMIT 1\');\n		echo graf(gTxt(\'adi_menu_summary_note\').\'.\'.sp.gTxt(\'adi_menu_summary_footnote\').\'.\');\n		if ($vs)\n			echo graf(gTxt(\'adi_menu_summary_note_prefix\').\' \"\'.$adi_menu_vs_prefix.\'\", \'.strtolower(gTxt(\'adi_for_example\')).\' \'.tag(\'sections=\"\'.$adi_menu_vs_prefix.$vs[0][\'name\'].\'\"\',\'em\').\'.\');\n		echo graf(gTxt(\'adi_menu_summary_note_drag_drop\').\'.\',\' class=\"adi_menu_summary_note_drag_drop\"\');\n		echo graf(gTxt(\'adi_menu_summary_note_key2\'),\' class=\"adi_menu_key\"\');\n		echo implode($out);\n		echo \'</div>\';\n	}\n\n	// plugin preferences\n	if ($installed)\n	    echo form(\n			tag(gTxt(\'edit_preferences\'),\"h2\")\n			.graf(\n				gTxt(\'adi_write_tab_select_format\')\n				.sp.sp\n				.tag(\n					radio(\'write_tab_select_format\',\'name\',(adi_menu_prefs(\'write_tab_select_format\') == \'name\'))\n					.sp\n					.gTxt(\'name\')\n					,\'label\'\n				)\n				.sp\n				.tag(\n					radio(\'write_tab_select_format\',\'title\',(adi_menu_prefs(\'write_tab_select_format\') == \'title\'))\n					.sp\n					.gTxt(\'title\')\n					,\'label\'\n				)\n			)\n			.graf(\n				gTxt(\'adi_write_tab_select_indent\')\n				.sp.sp\n				.tag(\n					radio(\'write_tab_select_indent\',\'0\',(adi_menu_prefs(\'write_tab_select_indent\') == \'0\'))\n					.sp\n					.gTxt(\'no\')\n					,\'label\'\n				)\n				.sp\n				.tag(\n					radio(\'write_tab_select_indent\',\'1\',(adi_menu_prefs(\'write_tab_select_indent\') == \'1\'))\n					.sp\n					.gTxt(\'yes\')\n					,\'label\'\n				)\n			)\n	        .fInput(\"submit\", \"do_something\",gTxt(\'adi_update_prefs\'), \"smallerbox\",\"\",\'\')\n	        .eInput(\"adi_menu_admin\")\n	        .sInput(\"pref_update\")\n			,\'\'\n			,\'\'\n			,\'post\'\n			,\'adi_menu_form\'\n		);\n\n	if ($adi_menu_debug) {\n		echo \'PREFS:\'.br; // should create list automatically\n		foreach ($adi_menu_prefs as $name => $value)\n			echo $name.\': \'.adi_menu_prefs($name).br;\n	}\n}\n\nfunction adi_menu_options($event,$step) {\n// plugin options\n	global $adi_menu_debug,$adi_menu_db_debug,$adi_menu_url,$adi_menu_plugin_status;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_menu_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'downgrade\') {\n		$result = adi_menu_downgrade();\n		$result ? $message = gTxt(\'adi_downgraded\') : $message = array(gTxt(\'adi_downgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_menu_install();\n		$result ? $message = gTxt(\'adi_installed\') : $message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_menu_uninstall();\n		$result ? $message = gTxt(\'adi_uninstalled\') : $message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_menu - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		tag(\n			form(\n				fInput(\"submit\", \"do_something\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"install\")\n				,\'\',\'\',\'post\'\n			)\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	$uninstall_button =\n		tag(\n	    	form(\n				fInput(\"submit\", \"do_something\", gTxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n				.eInput($event).sInput(\"uninstall\")\n				,\'\',\'\',\'post\'\n			)\n			,\'div\'\n			,\' style=\"margin-top:5em\"\');\n\n	if ($adi_menu_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_menu_installed();\n\n	if ($installed) {\n		adi_menu_upgrade();\n		// options\n		echo tag(\n			tag(\'adi_menu \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_menu_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_menu_url[\'textpack_feedback\']))\n			.$uninstall_button\n			,\'div\'\n			,\' style=\"text-align:center\"\'\n		);\n	}\n	else // install button\n	    echo $install_button;\n\n	if ($adi_menu_debug) {\n		echo \"<p>Event: \".$event.\", Step: \".$step.\"</p>\";\n		echo \'<p>$adi_textpack (\'.$adi_menu_url[\'textpack\'].\'):</p>\';\n		$adi_textpack = file_get_contents($adi_menu_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n}\n\nfunction adi_menu_admin_script() {\n// jQuery magic for admin tab\n	global $adi_menu_txp460;\n\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$sort_value_jquery = \'\';\n	foreach ($section_list as $name => $section) {\n		$sort_value_jquery .= \'$(\"#mainmenu li#\'.$name.\' > input\").attr(\"value\",\"\'.$section[\'adi_menu_sort\'].\'\");\'.n;\n		if ($section[\'adi_menu_exclude\'])\n			$sort_value_jquery .= \'$(\"#mainmenu li#\'.$name.\'\").addClass(\"excluded\");\'.n;\n	}\n\n	$button_text = gTxt(\'adi_menu_update_order\');\n\n	$ui_script = \'\';\n	if (!$adi_menu_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<!-- drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	// adi_menu\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'form.adi_menu_form #list .adi_menu_sort\').hide(); // hide sort value column\n			$(\'#adi_menu_summary\').addClass(\'adi_menu_drag_drop\');\n\n			$(\"#mainmenu\").wrap(\'<form class=\"adi_menu_summary\" action=\"index.php#adi_menu_summary\" method=\"post\"></form>\');\n			$(\"#mainmenu li\").each(function(){\n				var this_id = $(this).attr(\'id\');\n				$(this).append(\'<input type=\"hidden\" value=\"\" class=\"sort_value\" />\');\n				$(\'input\',this).attr(\'name\',\'sort[\'+this_id+\']\');\n			});\n			$(\"#mainmenu li.menu_virtual\").each(function(){\n				var this_id = $(this).attr(\'id\');\n				$(this).children(\'input\').attr(\'name\',\'virtual_sort[\'+this_id+\']\'); // immediate children\n			});\n\n			$sort_value_jquery\n\n			$(\"form.adi_menu_summary\").append(\'<input type=\"hidden\" name=\"event\" value=\"adi_menu_admin\" />\');\n			$(\"form.adi_menu_summary\").append(\'<input type=\"hidden\" name=\"step\" value=\"sort_update\" />\');\n			$(\"form.adi_menu_summary\").append(\'<input class=\"smallerbox\" type=\"submit\" name=\"do_something\" value=\"$button_text\" />\');\n			$(\"#mainmenu\").sortable({\n				items: \"li\",\n				opacity: 0.6,\n// 				cursor: \'move\',\n				containment: \'form.adi_menu_summary\',\n				grid: [10, 10],\n// 				connectWith: \".connectedSortable\"\n			});\n			// update sort values\n			$(\"#mainmenu\").bind(\"sortstop\",function(event, ui) {\n				$(\'input.sort_value\').each(function(i) { // renumber sort values\n					var this_input = $(this);\n					this_input.val(i+1);\n				});\n			});\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_menu_style() {\n// some style for the admin page\n	echo\n		\'<style type=\"text/css\">\n			/* adi_menu */\n			.txp-body { max-width:none }\n			h2 { font-weight:bold }\n			#adi_menu_summary { margin:2em 10em 2em; padding:1em 0 2em; border:solid #ccc; border-width:1px 0 }\n			#adi_menu_summary ul li li { margin-left:2em }\n			#adi_menu_summary .menu_redirect > a { font-weight:bold }\n			#adi_menu_summary .menu_alt_title > a { font-style:italic }\n			#adi_menu_summary .menu_virtual > a:before { content:\"\\0022\" }\n			#adi_menu_summary .menu_virtual > a:after { content:\"\\0022\" }\n			#adi_menu_summary #mainmenu { margin-bottom:2em } /* helps drag&drop */\n			#adi_menu_summary ul { margin-bottom:0.5em } /* helps drag&drop */\n			#adi_menu_summary.adi_menu_drag_drop ul li { list-style-type:none }\n			#adi_menu_summary.adi_menu_drag_drop ul li:before { content: \"\\2195\\00a0\"; }\n			#adi_menu_summary .adi_menu_key span { color:#963 }\n			#adi_menu_summary .adi_menu_key span span, #adi_menu_summary li.excluded a { color:grey }\n			.adi_menu_summary_note_drag_drop { display:none }\n			#adi_menu_summary.adi_menu_drag_drop .adi_menu_summary_note_drag_drop { display:block }\n			.adi_menu_warning { margin:1em; text-align:center; font-weight:bold }\n			.adi_menu_form { margin-top:2em; text-align:center }\n			.adi_menu_form div { margin-top:2em }\n			.adi_menu_hide { display:none }\n			option.adi_menu_virtual:before, option.adi_menu_virtual:after { content:\\\'\"\\\' }\n			a.dlink { font-size:120% }\n			form h1 { display:none }\n			/* TXP 4.6 */\n			main.txp-body #adi_menu_summary .adi_menu_key span { color:#004cbf }\n			main.txp-body #adi_menu_summary .adi_menu_key span span { color:grey }\n			main.txp-body #adi_menu_summary { margin:2em 0 2em }\n\n		</style>\';\n}\n\nfunction adi_menu_column_found($column,$table=\'txp_section\') {\n// check for presence of a column in a table\n	global $adi_menu_db_debug;\n\n	$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($table).\" LIKE \'\".$column.\"\'\",$adi_menu_db_debug); // find out if column exists\n	$a = nextRow($rs);\n	return !empty($a);\n}\n\nfunction adi_menu_installed() {\n// if \'adi_menu_parent\' column present then assume adi_menu is installed\n	return(adi_menu_column_found(\'adi_menu_parent\'));\n}\n\nfunction adi_menu_truncate_text($text,$limit) {\n// not currently used\n	$words = do_list($text,\' \');\n	$truncated_text = \'\';\n	foreach ($words as $index => $this_word) {\n		$truncated_text .= $this_word.\' \';\n		if (strlen($truncated_text) >= $limit)\n			break;\n	}\n	$truncated_text = trim($truncated_text);\n	if ($truncated_text != $text)\n		$truncated_text .= \' ...\';\n	return $truncated_text;\n}\n\nfunction adi_menu_strip_prefix($text) {\n// strip \"v_\" from front of supplied text\n	global $adi_menu_vs_prefix;\n\n	$text = trim($text);\n	return (strpos($text,$adi_menu_vs_prefix) === 0) ? substr($text,strlen($adi_menu_vs_prefix)) : $text;\n}\n\nfunction adi_menu_is_virtual($name) {\n// section deemed to be virtual if:\n//		- its \'adi_menu_id\' value in section_list is not NULL (i.e. set to \'adi_menu\' table row id )\n//		- OR if not found in $section_list, its name begins with \"v_\"\n	global $section_list,$adi_menu_vs_prefix;\n\n	$name = trim($name);\n	if (isset($section_list[$name])) // requested section found is known\n		return ($section_list[$name][\'adi_menu_id\'] !== NULL);\n	else // requested section not found, so based verdict on its name\n		return (strpos(trim($name),$adi_menu_vs_prefix) === 0);\n}\n\nfunction adi_menu_display_settings($sections) {\n// generate section\'s table row in admin settings table\n	global $prefs;\n\n	$out = \'\';\n\n	// hide redirect categories column if there\'re no categories defined\n	$categories_found = adi_menu_category_popup(\'test\',\'\');\n	if ($categories_found)\n		$hide_cats = \'\';\n	else\n		$hide_cats = \' class=\"adi_menu_hide\"\';\n\n	// add new virtual section input fields\n	$sections[0][\'adi_menu_id\'] = 0; // zero = add new virtual section\n	$sections[0][\'name\'] = \'\';\n	$sections[0][\'title\'] = \'\';\n	$sections[0][\'adi_menu_parent\'] = \'\';\n	$sections[0][\'adi_menu_title\'] = \'\';\n	$sections[0][\'adi_menu_exclude\'] = 0;\n	$sections[0][\'adi_menu_clone\'] = 0;\n	$sections[0][\'adi_menu_sort\'] = 255; // the maximum value for an INT(3)\n	$sections[0][\'adi_menu_redirect_section\'] = \'\';\n	$sections[0][\'adi_menu_redirect_link\'] = \'\';\n	$sections[0][\'adi_menu_alt_title\'] = \'\';\n	$sections[0][\'adi_menu_redirect_category\'] = \'\';\n\n	$vs_found = FALSE;\n	foreach ($sections as $index => $section) {\n		$id = $section[\'adi_menu_id\']; // NULL = normal section, number = virtual section, zero = add new virtual section\n		$name = $section[\'name\'];\n		$title = $section[\'title\'];\n		$parent = $section[\'adi_menu_parent\'];\n		$clone_title = $section[\'adi_menu_title\'];\n		$exclude = $section[\'adi_menu_exclude\'];\n		$clone = $section[\'adi_menu_clone\'];\n		$sort = $section[\'adi_menu_sort\'];\n		$redirect_section = $section[\'adi_menu_redirect_section\'];\n		$redirect_link = $section[\'adi_menu_redirect_link\'];\n		$alt_title = $section[\'adi_menu_alt_title\'];\n		$redirect_category = $section[\'adi_menu_redirect_category\'];\n		if (!$vs_found && ($id !== NULL)) { // virtual sections subheading\n			$vs_found = TRUE;\n			$out.= tr(tda(strong(gTxt(\'adi_menu_virtual_sections\')),\' colspan=\"12\"\'));\n		}\n		$out .= tr(\n			// section name/link to section tab (normal sections) OR input field (virtual sections)\n			($id === NULL ?\n				tda(\'<a href=\"http://\'.$prefs[\'siteurl\'].\'/textpattern/?event=section&amp;step=section_edit&amp;name=\'.$index.\'\">\'.$index.\'</a>\')\n				:\n				tda(finput(\'text\',\"name[$index]\",$name))\n			)\n//			.tda(htmlspecialchars(adi_menu_truncate_text($title,12)))\n			// section title (input field for virtual sections)\n			.tda(\n				($id !== NULL ?\n					finput(\"text\",\"title[$index]\",$title)\n					:\n					htmlspecialchars($title)\n				)\n				.hinput(\"id[$index]\",$id)\n			)\n// 			.tda(htmlspecialchars($title))\n			.tda(finput(\"text\",\"alt_title[$index]\",$alt_title))\n			.tda(checkbox(\"exclude[$index]\", \"1\", $exclude))\n			.tda(adi_menu_section_popup($sections,$index,\"parent[$index]\",$parent))\n			.tda(finput(\"text\",\"sort[$index]\",$sort,\'\',\'\',\'\',4),\' class=\"adi_menu_sort\"\') // hidden if jQuery-UI is available\n			.tda(checkbox(\"clone[$index]\", \"1\", $clone))\n			.tda(finput(\"text\",\"clone_title[$index]\",$clone_title))\n			.tda(adi_menu_section_popup($sections,$index,\"redirect_section[$index]\",$redirect_section))\n			.tda(adi_menu_link_popup(\"redirect_link[$index]\",$redirect_link))\n			.tda(adi_menu_category_popup(\"redirect_category[$index]\",$redirect_category),$hide_cats) // may be hidden if there\'re no categories defined\n			.tda(adi_menu_delete_button($id,$name))\n		);\n	}\n	return $out;\n}\n\nfunction adi_menu_section_popup($section_list,$this_section,$select_name,$value) {\n// generate section/virtual section popup list for admin settings table\n	global $adi_menu_vs_prefix;\n\n	$out = \'<option value=\"\"></option>\';\n\n	$vs_found = FALSE;\n\n	if ($section_list) {\n		$out .= \'<optgroup label=\"Sections\">\';\n		foreach ($section_list as $section_name => $section) {\n			if ($section_name) { // don\'t want section \"0\" (i.e. the \"new\" virtual section)\n				$disabled = $selected = \'\';\n				if ($this_section && ($section_name == $this_section)) // if it\'s not new virtual section ($this_section = 0), prevent loop\n					$disabled = \' disabled=\"disabled\"\'; // don\'t want to offer section as a parent of (or redirect to) itself\n				if ($section_name == $value)\n					$selected = \' selected=\"selected\"\';\n				if (!$vs_found && ($section[\'adi_menu_id\'] !== NULL)) { // virtual sections subheading\n					$vs_found = TRUE;\n					$out .= \'</optgroup>\';\n					$out .= \'<optgroup label=\"Virtual Sections\">\';\n				}\n				if ($vs_found)\n					// value includes prefix (i.e. virtual section name), label shouldn\'t\n					$out .= \'<option value=\"\'.$section_name.\'\"\'.$disabled.$selected.\' class=\"adi_menu_virtual\">\'.adi_menu_strip_prefix($section_name).\'</option>\';\n				else\n					$out .= \'<option value=\"\'.$section_name.\'\"\'.$disabled.$selected.\'>\'.$section_name.\'</option>\';\n			}\n		}\n		$out .= \'</optgroup>\';\n	}\n\n	return tag($out,\'select\',\' name=\"\'.$select_name.\'\"\');\n}\n\nfunction adi_menu_link_popup($select_name,$value) {\n// generate link popup list for admin settings table\n// TODO - make this non-DB like section_popup\n	$rs = safe_column(\'id\', \'txp_link\', \'1=1\');\n	if ($rs)\n		return selectInput($select_name, $rs, $value, TRUE);\n	return false;\n}\n\nfunction adi_menu_category_popup($select_name,$value) {\n// generate category popup list for admin settings table\n// TODO - make this non-DB like section_popup\n	$rs = safe_column(\'name\',\'txp_category\',\'name != \"root\" AND type=\"article\"\');\n	if ($rs)\n		return selectInput($select_name, $rs, $value, TRUE);\n	return false;\n}\n\nfunction adi_menu_delete_button($id,$name) {\n// virtual section delete button [X]\n	global $adi_menu_txp460;\n\n	$event = \'adi_menu_admin\';\n	$step = \'delete\';\n// 	$url = \'?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name;\n//\n// 	if ($id)\n// 		return\n// 			\'<a href=\"\'\n// 			.$url\n// 			.\'\" class=\"dlink\" title=\"Delete?\" onclick=\"return verify(\\\'\'\n// 			.$name\n// 			.\' - \'\n// 			.gTxt(\'confirm_delete_popup\')\n// 			.\'\\\')\">&#215;</a>\';\n// 	else // don\'t want delete button (id=\"0\") for real sections or new section (id=\"\")\n// 		return sp;\n\n	if ($id) {\n		if ($adi_menu_txp460)\n			return\n				href(\n					span(\'Delete\',\' class=\"ui-icon ui-icon-trash\"\')\n					,array(\n						\'event\' => $event,\n						\'step\' => $step,\n						\'name\' => $name,\n						\'_txp_token\'    => form_token(),\n					)\n					,array(\n						\'class\'       => \'dlink destroy\',\n						\'title\'       => gTxt(\'delete\'),\n						\'data-verify\' => gTxt(\'confirm_delete_popup\'),\n					)\n				);\n		else\n			return\n				\'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name\n				.\'\" class=\"dlink\" title=\"\'.gTxt(\'delete\').\'\" onclick=\"return verify(\\\'\'\n				.gTxt(\'confirm_delete_popup\')\n				.\'\\\')\">\'\n				.\'&#215;\'\n				.\'</a>\';\n	}\n	else // don\'t want delete button (id=\"0\") for real sections or new section (id=\"\")\n		return sp;\n\n}\n\nfunction adi_menu_update($sections) {\n// update DB tables:\n//		adi_menu		txp_section\n//		id				-\n//		name			name\n// 		title			title\n// 		alt_title		adi_menu_alt_title\n// 		parent			adi_menu_parent\n// 		exclude			adi_menu_exclude\n// 		sort			adi_menu_sort\n// 		section			adi_menu_redirect_section\n// 		link			adi_menu_redirect_link\n// 		category		adi_menu_redirect_category\n//		clone			adi_menu_clone (clone yes/no)\n//		clone_title		adi_menu_title (clone title)\n	global $adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$message = gTxt(\'adi_menu_updated\');\n\n	$id = ps(\'id\'); // hidden field (NULL = normal section, number = virtual section, zero = add new virtual section)\n	$name = doStripTags(ps(\'name\')); // virtual sections only\n	$title = doStripTags(ps(\'title\')); // virtual sections only\n	$parent = doStripTags(ps(\'parent\'));\n	$clone_title = doStripTags(ps(\'clone_title\'));\n	$exclude = doStripTags(ps(\'exclude\'));\n	$clone = doStripTags(ps(\'clone\'));\n	$sort = doStripTags(ps(\'sort\'));\n	$redirect_section = doStripTags(ps(\'redirect_section\'));\n	$redirect_link = doStripTags(ps(\'redirect_link\'));\n	$alt_title = doStripTags(ps(\'alt_title\'));\n	$redirect_category = doStripTags(ps(\'redirect_category\'));\n\n	// force update to look at new section \"0\" input\n	$sections[0] = array();\n\n	$name_pairs = array(); // for recording name changes\n\n	foreach ($sections as $index => $section) {\n		if ($id[$index] != \'\') { // VIRTUAL SECTIONS, update adi_menu table\n			$where = \'id=\"\'.$id[$index].\'\"\';\n			$set = array();\n			$this_name = strtolower(sanitizeForUrl($name[$index]));\n			$this_name = adi_menu_strip_prefix($this_name);\n			$set[] = \'name=\"\'.doSlash($this_name).\'\"\';\n			if (trim($title[$index]) == \'\') // don\'t want blank title, default to section name\n				$set[] = \'title=\"\'.doSlash($name[$index]).\'\"\';\n			else\n				$set[] = \'title=\"\'.doSlash($title[$index]).\'\"\';\n			$set[] = \'alt_title=\"\'.doSlash($alt_title[$index]).\'\"\';\n			$set[] = \'parent=\"\'.doSlash($parent[$index]).\'\"\';\n			$set[] = \'clone_title=\"\'.doSlash($clone_title[$index]).\'\"\';\n			empty($exclude[$index]) ? $set[] = \'exclude=\"0\"\' : $set[] = \'exclude=\"1\"\';\n			empty($clone[$index]) ? $set[] = \'clone=\"0\"\' : $set[] = \'clone=\"1\"\';\n			$set[] = \'sort=\"\'.doSlash($sort[$index]).\'\"\';\n			$set[] = \'section=\"\'.doSlash($redirect_section[$index]).\'\"\';\n			if (!empty($redirect_link)) { // there might not be any links defined in the TXP database!\n				if ($redirect_link[$index] != \'\') // don\'t want to set integer field as \'\' (uses default 0 instead)\n					$set[] = \'link=\"\'.doSlash($redirect_link[$index]).\'\"\';\n			}\n			if (!empty($redirect_category)) { // there might not be any categories defined in the TXP database!\n				$set[] = \'category=\"\'.doSlash($redirect_category[$index]).\'\"\';\n			}\n			// update database\n			$set = implode(\', \',$set);\n			if ($id[$index] == \'0\') { // new virtual section\n				if ($this_name) { // it has a name\n					$found = safe_row(\'name\',\'adi_menu\',\'name=\"\'.$this_name.\'\"\');\n					if ($found)\n						$message = array(gTxt(\'adi_menu_virtual_section_exists\').\' - \'.$this_name,E_ERROR);\n					else\n						safe_insert(\'adi_menu\',$set,$adi_menu_db_debug);\n				}\n			}\n			else { // update existing virtual section\n				$old_name = safe_field(\'name\',\'adi_menu\',$where,$adi_menu_db_debug);\n				$res = safe_update(\'adi_menu\',$set,$where,$adi_menu_db_debug);\n				if ($res && ($old_name != $this_name)) { // record name change for later parent updates\n					$name_pairs[] = array(\'old\' => $old_name, \'new\' => $this_name);\n				}\n			}\n		}\n		else { // NORMAL SECTIONS, update txp_section table\n			$where = \'name=\"\'.$index.\'\"\';\n			$set = array();\n			$set[] = \'adi_menu_parent=\"\'.doSlash($parent[$index]).\'\"\';\n			$set[] = \'adi_menu_title=\"\'.doSlash($clone_title[$index]).\'\"\';\n			empty($exclude[$index]) ? $set[] = \'adi_menu_exclude=\"0\"\' : $set[] = \'adi_menu_exclude=\"1\"\';\n			empty($clone[$index]) ? $set[] = \'adi_menu_clone=\"0\"\' : $set[] = \'adi_menu_clone=\"1\"\';\n			$set[] = \'adi_menu_sort=\"\'.doSlash($sort[$index]).\'\"\';\n			$set[] = \'adi_menu_alt_title=\"\'.doSlash($alt_title[$index]).\'\"\';\n			$set[] = \'adi_menu_redirect_section=\"\'.doSlash($redirect_section[$index]).\'\"\';\n			if (!empty($redirect_link)) { // there might not be any links defined in the TXP database!\n				if ($redirect_link[$index] != \'\') // don\'t want to set integer field as \'\' (uses default 0 instead)\n					$set[] = \'adi_menu_redirect_link=\"\'.doSlash($redirect_link[$index]).\'\"\';\n			}\n			if (!empty($redirect_category)) { // there might not be any categories defined in the TXP database!\n				$set[] = \'adi_menu_redirect_category=\"\'.doSlash($redirect_category[$index]).\'\"\';\n			}\n			// update database\n			$set = implode(\', \',$set);\n			safe_update(\'txp_section\',$set,$where,$adi_menu_db_debug);\n		}\n	}\n\n	// name changes? meet the new parents\n	foreach ($name_pairs as $pair) {\n		safe_update(\'adi_menu\',\'parent=\"\'.$adi_menu_vs_prefix.$pair[\'new\'].\'\"\',\'parent=\"\'.$adi_menu_vs_prefix.$pair[\'old\'].\'\"\',$adi_menu_db_debug);\n		safe_update(\'txp_section\',\'adi_menu_parent=\"\'.$adi_menu_vs_prefix.$pair[\'new\'].\'\"\',\'adi_menu_parent=\"\'.$adi_menu_vs_prefix.$pair[\'old\'].\'\"\',$adi_menu_db_debug);\n	}\n\n	return $message;\n}\n\nfunction adi_menu_sort_update() {\n// update database with drag & drop sort values\n	global $adi_menu_db_debug,$adi_menu_vs_prefix;\n\n	$sort = ps(\'sort\');\n	$virtual_sort = ps(\'virtual_sort\');\n	if ($sort)\n		foreach ($sort as $name => $value)\n			$res = safe_update(\'txp_section\',\'adi_menu_sort=\"\'.$value.\'\"\',\'name=\"\'.$name.\'\"\',$adi_menu_db_debug);\n	if ($virtual_sort)\n		foreach ($virtual_sort as $name => $value)\n			$res = $res & safe_update(\'adi_menu\',\'sort=\"\'.$value.\'\"\',\'name=\"\'.adi_menu_strip_prefix($name).\'\"\',$adi_menu_db_debug);\n\n	if ($res)\n		$message = gTxt(\'adi_menu_order_updated\');\n	else\n		$message = gTxt(\'adi_menu_order_update_failed\');\n\n	return $message;\n}\n\nfunction adi_menu_install() {\n// add adi_menu\'s columns to txp_section table\n// note: TINYINT(1) DEFAULT 0 = BOOLEAN DEFAULT FALSE\n// sort value only goes up to 255 - INT(3)\n	global $adi_menu_db_debug;\n\n	if (adi_menu_installed())\n		return TRUE;\n	else // add basic columns to txp_section\n		return safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_parent VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_title VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_exclude TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_clone TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_sort TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL;\",$adi_menu_db_debug);\n}\n\nfunction adi_menu_uninstall() {\n// remove adi_menu\'s columns from txp_section table\n	global $adi_menu_db_debug;\n\n	// remove traditional columns\n	$res = safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_parent;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_title;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_exclude;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_clone;\",$adi_menu_db_debug)\n		&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_sort;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_redirect_section\')) // remove version 1.0 columns\n		$res = $res\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_section;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_link;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_accesskey\')) // remove version 1.0beta only columns\n		$res = $res\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_accesskey;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_tabindex;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_alt_title\')) // remove version 1.1 columns\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_alt_title;\",$adi_menu_db_debug);\n	if (adi_menu_column_found(\'adi_menu_redirect_category\')) { // remove version 1.4 column & adi_menu_table\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_category;\",$adi_menu_db_debug);\n		$res = $res && safe_query(\"DROP TABLE \".safe_pfx(\'adi_menu\').\";\",$adi_menu_db_debug);\n	}\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_menu_%\'\",$adi_menu_db_debug);\n	return $res;\n}\n\nfunction adi_menu_upgrade() {\n// add additional adi_menu columns to txp_section table, if required\n	global $adi_menu_db_debug,$adi_menu_sql_fields;\n\n	// record the number of \'default\' articles\n	$rs = safe_rows(\'id\',\'textpattern\',\"section=\'default\'\");\n	adi_menu_prefs(\'write_tab_select_default\',count($rs));\n	// upgrade actions\n	$res = TRUE;\n	// version 1.0\n	if (!adi_menu_column_found(\'adi_menu_redirect_section\'))\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_section VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_link TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL;\",$adi_menu_db_debug);\n	// version 1.1\n	if (!adi_menu_column_found(\'adi_menu_alt_title\'))\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_alt_title VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug);\n	// version 1.4\n	if (!adi_menu_column_found(\'adi_menu_redirect_category\')) {\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" ADD adi_menu_redirect_category VARCHAR(128) DEFAULT \'\';\",$adi_menu_db_debug);\n		$res = $res && safe_query(\n			\"CREATE TABLE IF NOT EXISTS \"\n			.safe_pfx(\'adi_menu\')\n			.\" (\n				`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n				`name` VARCHAR(128) NOT NULL,\n				`title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`alt_title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`parent` VARCHAR(128) DEFAULT \'\' NOT NULL,\n				`clone` TINYINT(1) DEFAULT 0 NOT NULL,\n				`clone_title` VARCHAR(255) DEFAULT \'\' NOT NULL,\n				`exclude` TINYINT(1) DEFAULT 0 NOT NULL,\n				`sort` TINYINT(3) UNSIGNED DEFAULT 255 NOT NULL,\n				`section` VARCHAR(128) DEFAULT \'\' NOT NULL,\n				`link` TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL,\n				`category` VARCHAR(128) DEFAULT \'\' NOT NULL\n			);\"\n			,$adi_menu_db_debug\n			);\n		// force unique sort values (using $rs array index from \"0\") to ensure later use of (unstable) uasort doesn\'t cause problems\n		$rs = safe_rows($adi_menu_sql_fields, \'txp_section\', \"1=1 ORDER BY adi_menu_sort\",$adi_menu_db_debug);\n		if ($rs)\n			foreach($rs as $index => $row)\n				$res = $res && safe_update(\'txp_section\',\'adi_menu_sort=\"\'.$index.\'\"\',\'name=\"\'.$row[\'name\'].\'\"\',$adi_menu_db_debug);\n		// new sections default to end of list (255 assigned to new virtual sections by adi_menu_update())\n		$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" MODIFY COLUMN adi_menu_sort TINYINT(3) UNSIGNED DEFAULT 255 NOT NULL;\",$adi_menu_db_debug);\n	}\n	// version 1.4 supplemental\n	if (!adi_menu_column_found(\'clone\',\'adi_menu\'))\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\'adi_menu\').\" ADD clone TINYINT(1) DEFAULT 0 NOT NULL;\",$adi_menu_db_debug)\n			&& safe_query(\"ALTER TABLE \".safe_pfx(\'adi_menu\').\" ADD clone_title VARCHAR(255) DEFAULT \'\' NOT NULL;\",$adi_menu_db_debug);\n\n	return $res;\n}\n\nfunction adi_menu_downgrade() {\n// downgrade to previous version - 1.4 to 1.1+ only\n	global $adi_menu_db_debug;\n\n	$res = TRUE;\n	$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"txp_section\").\" DROP COLUMN adi_menu_redirect_category;\",$adi_menu_db_debug);\n	$res = $res && safe_query(\"DROP TABLE \".safe_pfx(\'adi_menu\').\";\",$adi_menu_db_debug);\n\n	return $res;\n}\n\nfunction adi_menu_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_menu\"\n// $step:	\"installed\", \"enabled\", \"disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_menu_debug;\n\n	$result = \'?\';\n	$upgrade = (($step == \"installed\") && adi_menu_installed());\n	if ($step == \'enabled\') {\n			$result = $upgrade = adi_menu_install();\n//		if (adi_menu_installed())\n//			$result = $upgrade = adi_menu_upgrade();\n//		else { // install, then upgrade\n//			if (adi_menu_install())\n//				$result = adi_menu_upgrade();\n//		}\n	}\n	else if ($step == \'deleted\')\n		$result = adi_menu_uninstall();\n	if ($upgrade)\n		$result = $result && adi_menu_upgrade();\n	if ($adi_menu_debug)\n		echo \"Event=$event, Step=$step, Result=$result, Upgrade=$upgrade\";\n}\n\nfunction adi_menu_add_form() {\n// add adi_menu\'s article form for old article mode\n	global $adi_menu_article_form;\n\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'\".$adi_menu_article_form.\"\'\")) {\n		$form = <<<EOF\n<li class=\"menu_article\"><txp:permlink><txp:title/></txp:permlink></li>\nEOF;\n		safe_insert(\'txp_form\',\n			\"name=\'\".$adi_menu_article_form.\"\',\n			type=\'article\',\n			Form=\'\".doSlash($form).\"\'\");\n	}\n}\n\nfunction adi_menu_prefs($name,$new_value=NULL) {\n// set/read preferences\n	global $prefs,$adi_menu_prefs;\n\n	if ($new_value !== NULL) { // save new value\n		set_pref(\'adi_menu_\'.$name,$new_value,\'adi_menu_admin\',2);\n		$prefs[\'adi_menu_\'.$name] = get_pref(\'adi_menu_\'.$name,$new_value,TRUE);\n	}\n	// take value from $prefs or, if not set, from $adi_menu_prefs[])\n	isset($prefs[\'adi_menu_\'.$name]) ? $value = $prefs[\'adi_menu_\'.$name] : $value = $adi_menu_prefs[$name];\n	return $value;\n}\n\nfunction adi_menu_loop_check($section_list,$parent,$ancestors=array()) {\n// check for section parent/child loops, return TRUE if loop found\n	if (empty($parent)) // no more ancestors\n		return FALSE;\n	else {\n		if (array_key_exists($parent,$ancestors)) // loop found\n			return TRUE;\n		else {\n			$ancestors[$parent]=\'\'; // add parent to list of ancestors\n			if (array_key_exists($parent,$section_list)) // check that parent exists (section rename issue)\n				return adi_menu_loop_check($section_list,$section_list[$parent][\'adi_menu_parent\'],$ancestors);\n			else\n				return FALSE;\n		}\n	}\n}\n\nfunction adi_menu_parent_check($db_sections) {\n// check for missing parents or link IDs\n	$missing_parents = array();\n	foreach($db_sections as $section => $value) {\n		$parent = $value[\'adi_menu_parent\'];\n		if (!empty($parent))\n			if (!array_key_exists($parent,$db_sections)) // check that parent exists (section deleted/renamed?)\n				$missing_parents[$section] = $parent;\n	}\n	return $missing_parents;\n}\n\nfunction adi_menu_redirect_section_check($db_sections) {\n// check section redirects\n	$missing_sections = array();\n	foreach($db_sections as $section => $value) {\n		$redirect_section = $value[\'adi_menu_redirect_section\'];\n		if (!empty($redirect_section))\n			if (!array_key_exists($redirect_section,$db_sections)) // check that redirect section exists (section deleted/renamed?)\n				$missing_sections[$section] = $redirect_section;\n	}\n	return $missing_sections;\n}\n\nfunction adi_menu_redirect_category_check($db_sections) {\n// check category redirects\n// TODO - make this non-DB (& for category_popup as well)\n\n	$missing_categories = array();\n\n	$db_cats = safe_column(\'name\',\'txp_category\',\'name != \"root\" AND type=\"article\"\');\n\n	if ($db_cats)\n		foreach($db_sections as $section => $value) {\n			$redirect_category = $value[\'adi_menu_redirect_category\'];\n			if (!empty($redirect_category))\n				if (!array_key_exists($redirect_category,$db_cats)) // check that redirect section exists (section deleted/renamed?)\n					$missing_categories[$section] = $redirect_category;\n		}\n\n	return $missing_categories;\n}\n\nfunction adi_menu_redirect_link_check($db_sections) {\n// check link redirects\n	$missing_links = array();\n	foreach($db_sections as $section => $value) {\n		$link = $value[\'adi_menu_redirect_link\'];\n		if ($link)\n			if (!adi_menu_get_link($link)) // check that link exists (link deleted?)\n				$missing_links[$section] = $link;\n	}\n	return $missing_links;\n}\n\nfunction adi_menu_get_link($link_id) {\n// read link from TXP database\n	$url = \'\';\n	if ($link_id) {\n		$sql_fields = \'url\';\n		$sql_table = \'txp_link\';\n		$sql_where = \'id=\'.$link_id;\n		$a = safe_row($sql_fields,$sql_table,$sql_where);\n		if ($a) // check if link ID actually exists\n			$url = $a[\'url\'];\n		else\n			$url = \'\';\n	}\n	return $url;\n}\n\nfunction adi_menu_section_sort($a,$b) {\n	return $a[\'adi_menu_sort\'] - $b[\'adi_menu_sort\'];\n}\n\nfunction adi_menu_section_list($sections=\'\',$exclude_list=\'\',$override_exclude=FALSE,$sort=\'adi_menu_sort\') {\n// create list of required sections in array indexed by section/v_section name =>\n// 		\'name\' => \'section/v_section name\'\n// 		\'title\' => \'Section title\'\n// 		\'adi_menu_parent\' => \'section/v_section name\'\n// 		\'adi_menu_title\' => \'Clone title\'\n// 		\'adi_menu_exclude\' => \'0/1\'\n// 		\'adi_menu_clone\' => \'0/1\'\n// 		\'adi_menu_sort\' => \'x\'\n// 		\'adi_menu_redirect_section\' => \'section name\'\n// 		\'adi_menu_redirect_link\' => \'link id#\'\n// 		\'adi_menu_redirect_category\' => \'article category name\'\n// 		\'adi_menu_alt_title\' => \'Alternative title\'\n// 		\'url\' => \'generated link, based on section or redirects\'\n// 		\'adi_menu_id\' => NULL for section, id# for v_section\n\n// $sections - BLANK = all, LIST = only include these in menu (others pruned out of hierarchy later - after adi_menu_section_list() call in adi_menu()) CONFUSING!\n// $exclude_list - LIST = explicitly excluded\n// $override_exclude - TRUE = blanket override exclude for all, FALSE = obey exclude, LIST = explicit list of section to override exclude\n\n	global $adi_menu_debug,$default_first,$default_title,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_vs_prefix;\n\n	$section_list = array();\n\n	// decide how big the net\'s going to be\n	if ($override_exclude && ($override_exclude === TRUE))\n		$bulkq = $bulkvq = \"1=1\"; // all sections regardless\n	else { // only sections that haven\'t been excluded in admin (with the possibility of an explicit override later)\n		$bulkq = \"adi_menu_exclude = 0\";\n		$bulkvq = \"exclude = 0\";\n	}\n\n	if ($adi_menu_debug) {\n		echo \"ADI_MENU_SECTION_LIST(): sections=\'$sections\'; exclude_list=\'$exclude_list\'; override_exclude=\'$override_exclude\'\".br;\n	}\n\n	// it\'s a start\n	$includeq = $bulkq;\n	$includevq = $bulkvq;\n\n	// explicit list of included sections (will override admin excluded)\n	if ($sections) {\n		$includeq = do_list($sections);\n		$includevq = array();\n		foreach ($includeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$includevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($includeq[$index]);\n			}\n		$includeq = implode(\"\',\'\",doSlash($includeq)); // \"\',\'\" ensures that each section name is single-quoted in list (first & last quote added below)\n		if ($includeq)\n			$includeq = \"($bulkq OR name IN (\'$includeq\'))\"; // brackets required to protect from operator precedence\n		else\n			$includeq = $bulkq;\n		$includevq = implode(\"\',\'\",doSlash($includevq));\n		if ($includevq)\n			$includevq = \"($bulkvq OR name IN (\'$includevq\'))\"; // brackets required to protect from operator precedence\n		else\n			$includevq = $bulkvq;\n	}\n\n	$excludeq = $excludevq = \'\';\n\n	// explicit list of excluded sections\n	if ($exclude_list) {\n		$excludeq = do_list($exclude_list);\n		$excludevq = array();\n		foreach ($excludeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$excludevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($excludeq[$index]);\n			}\n		$excludeq = implode(\"\',\'\",doSlash($excludeq));\n		if ($excludeq)\n			$excludeq = \" AND name NOT IN (\'$excludeq\')\";\n		else\n			$excludeq = \'\';\n		$excludevq = implode(\"\',\'\",doSlash($excludevq));\n		if ($excludevq)\n			$excludevq = \" AND name NOT IN (\'$excludevq\')\";\n		else\n			$excludevq = \'\';\n	}\n\n	$override_excludeq = $override_excludevq = \'\';\n\n	// explicit list of override-exclude sections\n	if ($override_exclude && ($override_exclude !== TRUE)) {\n		$override_excludeq = do_list($override_exclude);\n		$override_excludevq = array();\n		foreach ($override_excludeq as $index => $this_section) // split real & virtual sections\n			if (adi_menu_is_virtual($this_section)) { // have to check name prefix\n				$override_excludevq[] = adi_menu_strip_prefix($this_section); // strip prefix & add to list\n				unset($override_excludeq[$index]);\n			}\n		$override_excludeq = implode(\"\',\'\",doSlash($override_excludeq));\n		if ($override_excludeq)\n			$override_excludeq = \" OR name IN (\'$override_excludeq\')\";\n		else\n			$override_excludeq = \'\';\n		$override_excludevq = implode(\"\',\'\",doSlash($override_excludevq));\n		if ($override_excludevq)\n			$override_excludevq = \" OR name IN (\'$override_excludevq\')\";\n		else\n			$override_excludevq = \'\';\n	}\n\n	$sortq = \" ORDER BY $sort\";\n\n	$q = $includeq.$excludeq.$override_excludeq.$sortq;\n	$vq = $includevq.$excludevq.$override_excludevq.$sortq;\n\n	if ($adi_menu_debug) {\n		echo \"NORMAL QUERY: bulkq=\'$bulkq\'; includeq=\'$includeq\'; excludeq=\'$excludeq\'; override_excludeq=\'$override_excludeq\'; sortq=\'$sortq\'\".br;\n		echo $q.br;\n		echo \"VIRTUAL QUERY: bulkvq=\'$bulkvq\'; includevq=\'$includevq\'; excludeq=\'$excludevq\'; override_excludevq=\'$override_excludevq\'; sortq=\'$sortq\'\".br;\n		echo $vq.br;\n	}\n\n	$rs = safe_rows_start($adi_menu_sql_fields,\'txp_section\',$q);\n	if ($rs) {\n\n		// normal sections\n		if ($adi_menu_debug) echo \"NORMAL SECTIONS: \";\n		while ($a = nextRow($rs)) {\n			if ($adi_menu_debug) echo $a[\'name\'].sp;\n			$found_as_virtual_section = FALSE;\n			if (preg_match(\'/\\A\'.$adi_menu_vs_prefix.\'/\',$a[\'name\'])) // look for clash of name in adi_menu, in the (hopefully) unlikely event that someone adds a section prefixed with \"v_\"\n				$found_as_virtual_section = safe_row(\'name\',\'adi_menu\',\' name=\"\'.adi_menu_strip_prefix($a[\'name\']).\'\"\');\n			if (!$found_as_virtual_section) {\n				$a[\'adi_menu_id\'] = NULL;\n				// create URL & add it to $a, noting any redirection along the way\n				if ($a[\'adi_menu_redirect_section\'])\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'adi_menu_redirect_section\']));\n				else if ($a[\'adi_menu_redirect_link\'])\n						$a[\'url\'] = adi_menu_get_link($a[\'adi_menu_redirect_link\']);\n				else if ($a[\'adi_menu_redirect_category\'])\n					$a[\'url\'] = pagelinkurl(array(\'c\' => $a[\'adi_menu_redirect_category\']));\n				else\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'name\']));\n				$section_list[$a[\'name\']] = $a;\n			}\n		}\n		if ($adi_menu_debug) echo br;\n\n		// virtual sections\n		$vs_rs = safe_rows_start($adi_menu_vs_sql_fields,\'adi_menu\',$vq);\n		if ($vs_rs) {\n			if ($adi_menu_debug) echo \"VIRTUAL SECTIONS: \";\n			while ($a = nextRow($vs_rs)) {\n				if ($adi_menu_debug) echo $a[\'name\'].sp;\n				// create URL & add it to $a, noting any redirection along the way\n				if ($a[\'adi_menu_redirect_section\'])\n					$a[\'url\'] = pagelinkurl(array(\'s\' => $a[\'adi_menu_redirect_section\']));\n				else if ($a[\'adi_menu_redirect_link\'])\n						$a[\'url\'] = adi_menu_get_link($a[\'adi_menu_redirect_link\']);\n				else if ($a[\'adi_menu_redirect_category\'])\n					$a[\'url\'] = pagelinkurl(array(\'c\' => $a[\'adi_menu_redirect_category\']));\n				else\n					$a[\'url\'] = \'#\'; // set null link URL if no section/link/category\n				$section_list[$adi_menu_vs_prefix.$a[\'name\']] = $a; // virtual sections are prefixed\n			}\n			if ($adi_menu_debug) echo br;\n		}\n\n		// merge real & virtual sections\n		if ($sort == \'adi_menu_sort\') // sort array based on adi_menu_sort (otherwise virtual sections always come last)\n			uasort($section_list,\'adi_menu_section_sort\');\n\n		// deal with default section\n		if (array_key_exists(\'default\',$section_list)) { // default section included in menu\n			if ($section_list && $default_title) // set default section title\n				$section_list[\'default\'][\'title\'] = $default_title;\n			if ($section_list && $default_first) { // shift default section to beginning\n				$remember[\'default\'] = $section_list[\'default\']; // remember \'default\' element\n				unset($section_list[\'default\']); // remove \'default\' element\n				$section_list = array_merge($remember, $section_list); // join together, \'default\' now at beginning\n			}\n		}\n\n		if ($adi_menu_debug) echo br;\n	}\n\n	return $section_list;\n}\n\nfunction adi_menu_article_tab($event,$step,$default,$rs) {\n// tweak the article tab (section popup list)\n\n// 	if ((adi_menu_prefs(\'write_tab_select_format\') != \'name\') || adi_menu_prefs(\'write_tab_select_indent\')) {\n		$pattern = \'#name=\"Section\".*</select>#sU\';\n		$insert = \'adi_menu_article_section_popup\';\n		$out = preg_replace_callback($pattern,$insert,$default);\n		return $out;\n// 	}\n// 	else // don\'t fiddle with anything\n// 		return $default;\n}\n\nfunction adi_menu_section_indent($level) {\n// create indent for section in popup\n	$level -= 1;\n	$out = \'\';\n	if ($level)\n		for ($x=1; $x <= $level; $x++)\n			$out .= sp.sp;\n	return $out;\n}\n\nfunction adi_menu_article_section_popup() {\n// generate markup for section popup menu for Article/Write tab\n	global $step,$section_list,$adi_menu_sed_sf_installed,$adi_menu_vs_prefix;\n\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$hierarchy = adi_menu_hierarchy($section_list);\n	$section_levels = adi_menu_section_levels($hierarchy);\n	if ($step == \'edit\') { // editing existing ($step = GET var) or creating new article ($step = POST var)\n		if (!empty($GLOBALS[\'ID\'])) // newly-saved article, get section from POST vars\n			$select_section = gps(\'Section\');\n		else { // existing article, get ID from GET vars & section from database\n			$article_id = gps(\'ID\');\n			$select_section = safe_field(\"section\", \"textpattern\",\"id=\".$article_id);\n		}\n	}\n	else // empty article\n		$select_section = getDefaultSection(); // default section for articles (defined in Sections tab)\n	$out = \'name=\"Section\" class=\"list\">\';\n	foreach ($section_levels as $name => $level) { // create indented section popup list\n		if ($adi_menu_sed_sf_installed) { // sed_sections_fields installed & active so check if static section\n			$data = _sed_sf_get_data($name);\n			$data_array = sed_lib_extract_name_value_pairs($data);\n			if (isset($data_array[\'ss\']))\n				$ss = $data_array[\'ss\']; // 0 or 1 from prefs\n			else\n				$ss = 0; // not found in prefs\n			if ($ss) // static section according to sed_section_fields\n				if (!has_privs(\'sed_sf.static_sections\')) // only Publisher should see static sections\n					continue; // omit from section select list\n		}\n		if ($name == \'default\') // shouldn\'t really have \'default\' in the list\n			if (!adi_menu_prefs(\'write_tab_select_default\')) // only allow it if there\'re some \'default\' articles\n				continue;\n		if (adi_menu_is_virtual($name)) // exclude virtual sections\n			continue;\n		if( strcasecmp($name,$select_section) == 0)\n			$selected = \' selected=\"selected\"\';\n		else\n			$selected = \'\';\n		if (adi_menu_prefs(\'write_tab_select_format\') == \'name\')\n			$display = $name;\n		else // must be \'title\' then\n			$display = $section_list[$name][\'title\'];\n		$out .= \'<option value=\"\'.$name.\'\"\'.$selected.\'>\';\n		if (adi_menu_prefs(\'write_tab_select_indent\')) // indent\n			$out .= adi_menu_section_indent($level);\n		$out .= $display.\'</option>\';\n	}\n	$out .= \'</select>\';\n	return $out;\n}\n\nfunction adi_menu_section_levels($hierarchy,$level=1) {\n// create list, indexed by section, of level in hierarchy (top = 1 etc.)\n	$section_levels = array();\n	foreach ($hierarchy as $index => $section)\n		if (!$section[\'clone\']) { // ignore clones\n			$section_levels[$index] = $level; // set level in array, indexed by section name\n			$section_levels = array_merge($section_levels,adi_menu_section_levels($section[\'child\'],$level+1));\n		}\n	return $section_levels;\n}\n\nfunction adi_menu_lineage($section_list,$child) {\n// determine the ancestry & generate markup\n	global $s,$adi_menu_lineage_count,$adi_menu_breadcrumb_atts,$is_article_list;\n\n	extract($adi_menu_breadcrumb_atts);\n\n	$out = array();\n	if (!array_key_exists($child, $section_list)) { // bomb out if section not found (e.g. if $s is blank on error page)\n		$out[] = \'\';\n		return $out;\n	}\n\n	if ($s == $child) $adi_menu_lineage_count++;\n	if ($adi_menu_lineage_count > 1) { // bomb out if loop found\n		$out[] = \"Warning, section loop found: \";\n		return $out;\n	}\n	if ($section_list[$child][\'adi_menu_parent\']) // has parent\n		$out = array_merge($out,adi_menu_lineage($section_list,$section_list[$child][\'adi_menu_parent\']));\n	else { // top of the food chain\n		if (($include_default) && ($child != \'default\')) // if (include default) AND (not at \'default\' yet)\n			$out = array_merge($out,adi_menu_lineage($section_list,\'default\')); // do extra, \'default\' iteration\n		else\n			$out[] = doTag($label,$labeltag); // add the \"You are here\" bit\n	}\n	// entitlements\n	$crumb = $child; // default to section name\n	if ($title) {\n		$crumb = $section_list[$child][\'title\'];\n		if (($crumb == \'default\') && ($default_title))\n			$crumb = $default_title;\n		if (!$ignore_alt_title && $section_list[$child][\'adi_menu_alt_title\'])\n			$crumb = $section_list[$child][\'adi_menu_alt_title\'];\n	}\n	$crumb = adi_menu_htmlentities($crumb,$escape);\n	if (($s == $child) && (!$link_last) && ($is_article_list)) // if (last breadcrumb) AND (link_last=0) AND (not single article), switch off link mode\n		$link = FALSE;\n	$link ? // output section link, maybe\n// 		$out[] = tag($crumb,\'a\',\' class=\"\'.$linkclass.\'\" href=\"\'.$section_list[$child][\'url\'].\'\"\') :\n		$out[] = tag($crumb,\'a\',\' \'.($linkclass ? \'class=\"\'.$linkclass.\'\"\' : \'\').\' href=\"\'.$section_list[$child][\'url\'].\'\"\') :\n		$out[] = $crumb;\n\n	if ($s != $child) $out[] = $separator; // add separator if not last crumb\n\n	return $out;\n}\n\nfunction adi_menu_breadcrumb($atts) {\n// <txp:adi_menu_breadcrumb /> tag\n	global $s,$default_first,$adi_menu_lineage_count,$adi_menu_breadcrumb_atts;\n\n	$default_first=\"1\"; // used by adi_menu_section_list()\n\n// 	extract(lAtts(array(\n// 		\'label\'				=> \'You are here: \',	// string to prepend to the output\n// 		\'labeltag\'			=>	\'\',					// tag to wrap around label\n// 		\'separator\'			=> \' &#187; \',			// string to be used as the breadcrumb separator (default: >>)\n// 		\'sep\'				=> \'\',					// deprecated - use \'separator\'\n// 		\'title\'				=> \'1\',					// display section titles or not\n// 		\'link\'				=> \'1\',					// output sections as links or not\n// 		\'linkclass\'			=> \'noline\',			// class for breadcrumb links\n// 		\'link_last\'			=> \'0\',					// display last section crumb as link or not\n// 		\'include_default\'	=> \'1\',					// include \'default\' section or not\n// 		\'default_title\'		=> \'Home\',				// title for \'default\' section\n// 		\'ignore_alt_title\'	=> \'0\',					// choose to ignore the alternatve titles\n// 		\'escape\'			=> \'\',					// escape HTML entities in section titles\n// 	), $atts));\n\n	if (!adi_menu_installed()) return \"<em>adi_menu not installed!</em>\";\n\n	$adi_menu_breadcrumb_atts = lAtts(array(\n		\'label\'				=> \'You are here: \',	// string to prepend to the output\n		\'labeltag\'			=>	\'\',					// tag to wrap around label\n		\'separator\'			=> \' &#187; \',			// string to be used as the breadcrumb separator (default: >>)\n		\'sep\'				=> \'\',					// deprecated - use \'separator\'\n		\'title\'				=> \'1\',					// display section titles or not\n		\'link\'				=> \'1\',					// output sections as links or not\n		\'linkclass\'			=> \'noline\',			// class for breadcrumb links\n		\'link_last\'			=> \'0\',					// display last section crumb as link or not\n		\'include_default\'	=> \'1\',					// include \'default\' section or not\n		\'default_title\'		=> \'Home\',				// title for \'default\' section\n		\'ignore_alt_title\'	=> \'0\',					// choose to ignore the alternatve titles\n		\'escape\'			=> \'\',					// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n	), $atts);\n	extract($adi_menu_breadcrumb_atts);\n\n	$default_title = trim($default_title);\n	if ($sep) $separator = $sep; // deprecated attribute \'sep\', use \'separator\' instead\n\n	/* adi_menu_breadcrumb - main procedure */\n	$section_list = adi_menu_section_list(\'\',\'\',TRUE);\n	$adi_menu_lineage_count = 0; // global variable used instead of static, so that adi_menu_breadcrumb can have multiple instances\n	$out = adi_menu_lineage($section_list,$s);\n	return implode($out);\n}\n\nfunction adi_menu_info($atts,$thing,$if_tag=0) {\n// combined <txp:adi_menu_info /> & <txp:adi_menu_if_info /> tag function\n// <txp:adi_menu_info /> returns specific menu information for current/specified section(s)\n// <txp:adi_menu_if_info /> - if/then/else conditional\n	global $s,$thisarticle,$adi_menu_sql_fields,$adi_menu_vs_sql_fields,$adi_menu_info_types,$adi_menu_vs_prefix,$ignore_alt_title;\n\n	// if or not if\n	if ($if_tag) {\n		extract(lAtts(array(\n			\'sections\'		=> \'\',		// defaults to current section (or article\'s section if in speaking block)\n			\'type\'			=> \'title\', // defaults to title (useful for testing if section exists)\n			\'value\'			=> \'\',\n			\'debug\'			=> 0,\n		),$atts));\n		$condition = 0;\n	}\n	else {\n		extract(lAtts(array(\n			\'sections\'			=> \'\',		// defaults to current section (or article\'s section if in speaking block)\n			\'type\'				=> \'title\', // defaults to title (for standard sections analogous to <txp:section title=\"1\" />\n			\'escape\'			=> \'\',		// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n			\'wraptag\'			=> \'\',\n			\'class\'				=> \'\',\n			\'break\'				=> \'\',\n			\'breakclass\'		=> \'\',\n			\'debug\'				=> 0,\n			// LABEL etc?\n		), $atts));\n		$out = array();\n	}\n\n	// sensitivities\n	if ($sections == \'\')\n		if ($thisarticle) // we\'re in an article (e.g. when used in speaking block forms)\n			$sections = $thisarticle[\'section\'];\n		else\n			$sections = $s; // current section\n\n	$sections = do_list($sections);\n	$types = do_list($type);\n\n	if (array_search(\'ancestors\',$types) !== FALSE) // who do you think you are?\n		$section_list = adi_menu_section_list(\'\',\'\',TRUE); // all sections, no exclusions, override excluded\n\n	// go through supplied list of sections\n	foreach ($sections as $section) {\n		if (adi_menu_is_virtual($section))\n			$row = safe_row($adi_menu_vs_sql_fields,\'adi_menu\',\"name=\'\".adi_menu_strip_prefix($section).\"\'\",$debug);\n		else\n			$row = safe_row($adi_menu_sql_fields,\'txp_section\',\"name=\'$section\'\",$debug);\n		if ($row) { // section found in DB\n			if ($debug) {\n				echo \"DB row:\";\n				dmp($row);\n			}\n			// go through supplied list of types\n			foreach ($types as $type) {\n				$valid_types = array_keys($adi_menu_info_types);\n				if (in_array($type,$valid_types)) { // valid type?\n					if (isset($adi_menu_info_types[$type])) { // known column?\n						if (($type == \'clone_title\') && !$row[$adi_menu_info_types[\'clone\'][\'column\']]) continue; // don\'t output or find clone title if clone isn\'t switched on (future proofing)\n						if ($if_tag) { // CONDITIONAL INFO TAG (<txp:adi_menu_if_info />)\n							// run through columns to see if any are non-blanks or values that match\n							$columns = do_list($adi_menu_info_types[$type][\'column\']);\n							foreach ($columns as $column) {\n								$this_val = $row[$column];\n								if ($type == \'menu_title\') { // menu_title is a special case\n									if ($row[\'adi_menu_alt_title\'] && !$ignore_alt_title) // alternative title overrides normal title & check ignore_alt_title attribute\n										$this_val = $row[\'adi_menu_alt_title\']; // use deduced value\n								}\n								if ($value == \'\') { // test value not supplied\n									if ($this_val != $adi_menu_info_types[$type][\'blank\']) // condition TRUE if column is non-blank\n										$condition += 1;\n								}\n								else {\n									if ($value == $this_val) // condition TRUE if column matches supplied value\n										$condition += 1;\n								}\n							}\n						}\n						else { // OUTPUT INFO TAG (<txp:adi_menu_info />)\n							// output non-blank value\n							$column = $adi_menu_info_types[$type][\'column\'];\n							if (strpos($column,\',\') === FALSE) { // ignore lists of columns (used by <txp:adi_menu_if_info /> only)\n								$this_val = $row[$column];\n								if ($type == \'menu_title\') { // menu_title is a special case\n									if ($row[\'adi_menu_alt_title\'] && !$ignore_alt_title) // alternative title overrides normal title & check ignore_alt_title attribute\n										$this_val = $row[\'adi_menu_alt_title\']; // use deduced value\n								}\n								if ($type == \'ancestors\') { // ancestors is another special case\n									$ancestors = adi_menu_get_ancestors($section_list,$section);\n									$this_val = implode(\',\',$ancestors);\n								}\n								if ($this_val != $adi_menu_info_types[$type][\'blank\']) // only output value if not \"blank\"\n									$out[] = adi_menu_htmlentities($this_val,$escape);\n							}\n						}\n					}\n				}\n			}\n		}\n	}\n\n	if ($debug) {\n		if ($if_tag)\n			echo \"adi_menu_if_info \";\n		else\n			echo \"adi_menu_info \";\n		echo \"attributes:\";\n		dmp($atts);\n		echo \"sections:\";\n		dmp($sections);\n		echo \"type mapping:\";\n		dmp($adi_menu_info_types);\n		echo \"types:\";\n		dmp($types);\n		if ($if_tag)\n			echo \"condition=$condition\".br;\n		echo \"ignore_alt_title=$ignore_alt_title\".br; // if we\'re in a speaking block ignore_alt_title=0/1, otherwise it\'s blank (i.e. vague global unset value)\n	}\n\n	if ($if_tag)\n		return parse(EvalElse($thing,$condition));\n	else\n		return doWrap($out, $wraptag, $break, $class, $breakclass);\n}\n\nfunction adi_menu_if_info($atts,$thing) {\n// <txp:adi_menu_if_info /> tag == <txp:adi_menu_info if_tag=\"1\" />\n\n	return adi_menu_info($atts,$thing,1);\n}\n\nfunction adi_menu_get_article_attr($article_attr) {\n// parse article_attr and return list, indexed by attribute name\n// e.g. article_attr=\'time=\"any\" limit=\"5\"\'\n	// THERE MUST BE A BETTER WAY!\n	$attr_list = preg_replace(\"#([a-z_]+=)#\", \",\\\\1\", $article_attr) . \",\"; // creates e.g. \',time=\"any\",limit=\"5\"\'\n	$attr_list = do_list($attr_list);\n	$attributes = array();\n	foreach ($attr_list as $index => $attr) {\n		if ($attr) {\n			$a = do_list($attr,\'=\');\n			if (count($a) == 2) { // safety valve\n				$attribute = trim($a[0]); // remove whitespace\n				$value = trim($a[1]); // remove whitespace\n				$value = trim($value,\'\"\'); // remove double quotes\n				$attributes[$attribute] = $value;\n			}\n		}\n	}\n	return $attributes;\n}\n\nfunction adi_menu_get_articles($section_list,$section_article_list,$category_article_list=array(),$debug=FALSE) {\n// create list of articles from database, indexed by section/category & sub-indexed by article id (prefixed with \'article_\')\n// - sort value taken from article_sort attribute\n// - much code taken from publish.php doArticles()\n	global $s,$c,$adi_menu_debug,$article_sort,$section_article_sort,$article_attr,$active_articles_only,$new_article_mode,$cat_article_sort,$category_article_sort,$cat_article_attr,$adi_menu_vs_prefix;\n\n	// anything to do?\n	if (!($section_article_list || $category_article_list))\n		return array();\n\n	$article_list = $this_article_list = $attributes = array();\n\n	// section/category - attributes\n	if ($section_article_list) {\n		$this_article_list = $section_article_list;\n		$attributes = adi_menu_get_article_attr($article_attr);\n	}\n	else if ($category_article_list) {\n		$this_article_list = $category_article_list;\n		$attributes = adi_menu_get_article_attr($cat_article_attr);\n	}\n\n	// blankety blank\n	$section=$category=$search=$id=$excerpted=$month=$author=$keywords=$custom=$frontpage=\'\';\n\n	// set defaults\n	$statusq = \' AND Status = 4\';\n	$time = \" AND Posted <= NOW()\";\n	if ($new_article_mode)\n		$limit = 9999;\n	else\n		$limit = 10;\n	$offset = 0;\n\n	// analyse article attributes\n	foreach ($attributes as $attribute => $value) {\n		switch ($attribute) {\n			case \'author\':\n				if ($value)\n					$author = (!$value) ? \'\' : \" AND AuthorID IN (\'\".implode(\"\',\'\", doSlash(do_list($value))).\"\')\";\n				break;\n			case \'category\':\n				if ($value) {\n					$category = implode(\"\',\'\", doSlash(do_list($value)));\n					$category = (!$category)  ? \'\' : \" AND (Category1 IN (\'\".$category.\"\') OR Category2 IN (\'\".$category.\"\'))\";\n				}\n				break;\n			case \'excerpted\':\n				if ($value)\n					$excerpted = ($value==\'y\')  ? \" AND Excerpt !=\'\'\" : \'\';\n				break;\n			case \'keywords\':\n				if ($value) {\n					$keys = doSlash(do_list($value));\n					foreach ($keys as $key) {\n						$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\n					}\n					$keywords = \" AND (\" . implode(\' or \',$keyparts) . \")\";\n				}\n				break;\n			case \'limit\':\n				if ($value)\n					$limit = $value;\n				break;\n			case \'month\':\n				if ($value)\n					$month = (!$value) ? \'\' : \" AND Posted LIKE \'\".doSlash($value).\"%\'\";\n				break;\n			case \'offset\':\n				if ($value)\n					$offset = $value;\n				break;\n			case \'sort\': // will override article_sort attribute\n				if ($value)\n					$article_sort = $value;\n				break;\n			case \'status\':\n				if ($value) {\n					$status = in_array(strtolower($value), array(\'sticky\', \'5\')) ? 5 : 4;\n					$statusq = \' AND Status = \'.intval($status);\n				}\n				else // status attr value blank = live and stickies\n					$statusq = \' AND Status IN (4,5)\';\n				break;\n			case \'time\':\n				switch ($value) {\n					case \'any\':\n						$time = \"\"; break;\n					case \'future\':\n						$time = \" AND Posted > NOW()\"; break;\n				}\n				break;\n		}\n	}\n\n	// custom fields\n	$customFields = getCustomFields();\n	$customlAtts = array_null(array_flip($customFields));\n	if ($customFields) {\n		foreach($customFields as $cField)\n			if (isset($attributes[$cField]))\n				$customPairs[$cField] = $attributes[$cField];\n		if(!empty($customPairs))\n			$custom = buildCustomSql($customFields,$customPairs);\n	}\n\n	if ($debug) {\n		echo \"adi_menu_get_articles()\";\n		if ($section_article_list) echo \" - SECTION:\".br;\n		if ($category_article_list) echo \" - CATEGORY:\".br;\n	}\n\n	// retrieve articles from database\n	foreach ($this_article_list as $index => $this_item) {\n		// section/category - select\n		$this_section = \'\';\n		if ($section_article_list) {\n			$this_section = $this_item;\n			if (adi_menu_is_virtual($this_item)) { // virtual section, so think about it\n				if ($section_list[$this_item][\'adi_menu_redirect_section\']) // only interested if redirected to a section\n					$this_section = $section_list[$this_item][\'adi_menu_redirect_section\'];\n				else\n					continue;\n			}\n			$section = \" AND Section = \'\".doSlash($this_section).\"\'\";\n		}\n		else if ($category_article_list)\n			$category = \" AND (Category1 = \'\".$this_item.\"\' OR Category2 = \'\".$this_item.\"\')\";\n\n		// section/category - sorting\n		$sortq = $this_sort = \'\';\n		if ($section_article_list)\n			if (array_key_exists($this_item,$section_article_sort)) // section-specific article sort\n				$this_sort = $section_article_sort[$this_item];\n			else\n				$this_sort = $article_sort; // default sort\n		else if ($category_article_list)\n			if (array_key_exists($this_item,$category_article_sort)) // category-specific article sort\n				$this_sort = $category_article_sort[$this_item];\n			else\n				$this_sort = $cat_article_sort; // default sort\n		if ($this_sort)\n			$sortq = \' ORDER BY \'.doSlash($this_sort);\n\n		// WHERE clause\n		$where = \"1=1\".$statusq.$time.$search.$id.$category.$section.$excerpted.$month.$author.$keywords.$custom.$frontpage;\n\n		if ($debug) echo \"$this_item\".($this_section && ($this_section != $this_item) ? \" (>>>$this_section)\" : \"\").\": $where $sortq\".br;\n\n		// database action\n		$rs = safe_rows_start(\n				\"ID,Title,url_title,Posted,Section\" // needed for permlinkurl()\n				,\'textpattern\',\n				$where.$sortq.\' LIMIT \'.intval($offset).\', \'.intval($limit)\n			);\n		while($a = nextRow($rs)) {\n			$this_index = \'article_\'.$a[\'ID\']; // create unique array index - for ID 23, index is article_23\n			$article_list[$this_item][$this_index][\'title\'] = html_entity_decode($a[\'Title\']); // ampersands are escaped in article titles (but not in section titles)\n			$article_list[$this_item][$this_index][\'url\'] = permlinkurl($a);\n			$article_list[$this_item][$this_index][\'section\'] = FALSE; // FALSE=article\n			$article_list[$this_item][$this_index][\'virtual_section\'] = FALSE;\n			$article_list[$this_item][$this_index][\'sort\'] = rand(); // THIS MAY TAKE SORT NUMBER FROM SOMEWHERE ELSE EVENTUALLY\n			$article_list[$this_item][$this_index][\'clone\'] = FALSE; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_section\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_link\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'redirect_category\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'alt_title\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'parent\'] = \'\'; // dummy filler\n			$article_list[$this_item][$this_index][\'child\'] = array(); // dummy filler\n		}\n	}\n\n	if ($debug) echo br;\n\n	return $article_list;\n}\n\n\nfunction adi_menu_speaking_block($name,$redirect_section) {\n// retrieve specified section\'s sticky article excerpt for the speaking block\n	global $speaking_block_form,$adi_menu_vs_prefix;\n\n	if ($redirect_section) // massage section name to match redirection\n		$name = $redirect_section;\n\n	$sb_tag_attr = \'section=\"\'.$name.\'\" status=\"sticky\"\';\n	if ($speaking_block_form) // use specified form\n		$sb_article_tag = \'<txp:article_custom \'.$sb_tag_attr.\' form=\"\'.$speaking_block_form.\'\"/>\';\n	else // use default form\n		$sb_article_tag = \'<txp:article_custom \'.$sb_tag_attr.\'><txp:excerpt /></txp:article_custom>\';\n\n	$sb_text = trim(parse($sb_article_tag));\n	if ($sb_text) // don\'t want empty spans\n		return \'<span class=\"speaking_block\">\'.$sb_text.\'</span>\';\n}\n\nfunction adi_menu_sort_hierarchy($a,$b) {\n// comparison function for uasort()\n	global $article_sort,$article_position;\n\n	if ($article_position == \'dovetail\')\n		$this_sort = \'title\';\n	else\n		$this_sort = $article_sort;\n	$reverse = preg_match(\"/ desc/i\",$this_sort);\n	$this_sort = preg_replace(\"/ asc/i\",\"\",$this_sort); // remove \" asc\" MULTIPLE SPACES!!!\n	$this_sort = preg_replace(\"/ desc/i\",\"\",$this_sort); // remove \" desc\" MULTIPLE SPACES!!!\n	if (strcasecmp($this_sort,\'title\') == 0)\n    	$reverse ?\n			$res = strcasecmp($b[\"title\"], $a[\"title\"]) :\n			$res = strcasecmp($a[\"title\"], $b[\"title\"]);\n	else if (strcasecmp($this_sort,\'adi_menu_sort\') == 0)\n    	$res = strcasecmp($a[\"sort\"], $b[\"sort\"]);\n	else // no sort (i.e. database order)\n    	$res = 0;\n	return $res;\n}\n\nfunction adi_menu_hierarchy($section_list,$article_list=array(),$cat_article_list=array(),$this_section=NULL,$clone=0) {\n// create $hierarchy, indexed by section/article_ID, using information from $section_list (and possibly article lists too)\n	global $clone_title,$new_article_mode,$article_position,$default_first,$articles,$exclude_clone,$active_articles_only;\n\n	$hierarchy = array();\n\n	$not_restricted = TRUE;\n\n	// active articles only?\n	if (($this_section !== NULL) && $active_articles_only)\n		$not_restricted = adi_menu_is_current($this_section,$section_list[$this_section][\'adi_menu_redirect_category\']);\n\n	// insert \"BEFORE\" articles\n	if (($this_section !== NULL) && ($article_position == \'before\') && $new_article_mode && $articles && $not_restricted) {\n		$this_category = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		if ($this_category) { // category articles\n			if (array_key_exists($this_category,$cat_article_list))\n				$hierarchy = $cat_article_list[$this_category];\n		}\n		else\n			if (array_key_exists($this_section,$article_list)) // section articles\n				$hierarchy = $article_list[$this_section];\n	}\n\n	// clone parent as its child\n	if ($clone && !$exclude_clone) {\n		$hierarchy[$this_section][\'title\'] =\n			$section_list[$this_section][\'adi_menu_title\'] ?\n				$section_list[$this_section][\'adi_menu_title\'] :\n				$clone_title; // use default clone title\n		$hierarchy[$this_section][\'url\'] = $section_list[$this_section][\'url\'];\n		$hierarchy[$this_section][\'section\'] = TRUE; // I\'m a section\n		$hierarchy[$this_section][\'virtual_section\'] = FALSE; // clones can\'t be virtual\n		$hierarchy[$this_section][\'sort\'] = rand(); // sort number\n		$hierarchy[$this_section][\'clone\'] = TRUE; // I\'m a clone\n		$hierarchy[$this_section][\'parent\'] = $this_section;\n		$hierarchy[$this_section][\'redirect_section\'] = $section_list[$this_section][\'adi_menu_redirect_section\'];\n		$hierarchy[$this_section][\'redirect_link\'] = $section_list[$this_section][\'adi_menu_redirect_link\'];\n		$hierarchy[$this_section][\'redirect_category\'] = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		$hierarchy[$this_section][\'alt_title\'] = \'\'; // a clone already has an \"alternative title\"!\n		$hierarchy[$this_section][\'child\'] = array(); // that\'s enough inbreeding\n	}\n\n	// find children\n	foreach ($section_list as $index => $section) {\n		if ($section[\'adi_menu_parent\'] == $this_section) {\n			$hierarchy[$index][\'title\'] = $section[\'title\'];\n			$hierarchy[$index][\'url\'] = $section[\'url\'];\n			$hierarchy[$index][\'section\'] = TRUE; // I\'m a section\n			$hierarchy[$index][\'virtual_section\'] = $section[\'adi_menu_id\'] !== NULL; // virtual section?\n			$hierarchy[$index][\'sort\'] = $section[\'adi_menu_sort\']; // adi_menu admin sort order\n			$hierarchy[$index][\'clone\'] = FALSE; // I\'m not a clone\n			$hierarchy[$index][\'parent\'] = $section[\'adi_menu_parent\'];\n			$hierarchy[$index][\'redirect_section\'] = $section[\'adi_menu_redirect_section\'];\n			$hierarchy[$index][\'redirect_link\'] = $section[\'adi_menu_redirect_link\'];\n			$hierarchy[$index][\'redirect_category\'] = $section[\'adi_menu_redirect_category\'];\n			$hierarchy[$index][\'alt_title\'] = $section[\'adi_menu_alt_title\'];\n			$hierarchy[$index][\'child\'] = adi_menu_hierarchy($section_list,$article_list,$cat_article_list,$index,$section[\'adi_menu_clone\']); // delve deper\n		}\n	}\n\n	// insert \"AFTER/DOVETAIL\" articles\n	if (($this_section !== NULL) && !($article_position == \'before\') && $new_article_mode && $articles && $not_restricted) { // default to inserting articles AFTER sections (i.e. not \"before\")\n		$this_category = $section_list[$this_section][\'adi_menu_redirect_category\'];\n		if ($this_category) { // category articles\n			if (array_key_exists($this_category,$cat_article_list))\n				$hierarchy = array_merge($hierarchy,$cat_article_list[$this_category]);\n		}\n		else\n			if (array_key_exists($this_section,$article_list)) // section articles\n				$hierarchy = array_merge($hierarchy,$article_list[$this_section]);\n		if ($article_position == \'dovetail\') // sort current level of hierarchy (sections AND articles)\n			uasort($hierarchy,\"adi_menu_sort_hierarchy\");\n	}\n\n	return $hierarchy;\n}\n\nfunction adi_menu_prune($old_hierarchy,$include_list,$section_levels,$siblings) {\n// remove sections from the hierarchy that are not on the include list (taking notice of: include_parent, include_childless, include_top_level)\n	global $include_parent,$include_childless,$include_top_level,$include_siblings,$current_descendants_only;\n\n	$hierarchy = array();\n	foreach ($old_hierarchy as $section => $section_data) {\n\n		if (array_search($section,$include_list) !== FALSE) { // IN LIST: copy section/children and look no further\n			if (!empty($section_data[\'child\']) && (!$include_parent)) // copy children only\n				$hierarchy += $section_data[\'child\'];\n			else if (($include_parent) || ($include_childless)) // copy section (& children)\n				$hierarchy[$section] = $section_data;\n		}\n		else if ($include_top_level && ($section_levels[$section] == 1)) { // NOT IN LIST: but check top level requirements\n			$section_data[\'child\'] = array(); // remove the kids\n			$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n		}\n		else if ($include_siblings) { // NOT IN LIST: but check if sibling\n			foreach ($include_list as $this_section) { //??? adi_menu2 not coming up\n				if (array_search($section,$siblings[$this_section])) {\n						$section_data[\'child\'] = array(); // remove the kids\n						$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n					}\n			}\n		}\n		else if ($current_descendants_only) { // NOT IN LIST: add as parent only\n			$section_data[\'child\'] = array(); // remove the kids\n			$hierarchy[$section] = $section_data; // copy section (WITHOUT children)\n		}\n		else if (!empty($section_data[\'child\'])) // NOT IN LIST: delve deeper into hierarchy\n			$hierarchy += adi_menu_prune($section_data[\'child\'],$include_list,$section_levels,$siblings);\n\n	}\n\n	return $hierarchy;\n}\n\nfunction adi_menu_siblings($section_list) {\n// create list of siblings\n\n	$siblings = array();\n	foreach ($section_list as $section => $section_data)\n		$siblings[$section] = array();\n\n	foreach ($section_list as $section => $section_data) {\n		$parent = $section_data[\'adi_menu_parent\'];\n		if ($parent == \'\')\n			$parents[\'adi_menu_root\'][] = $section;\n		else\n			$parents[$parent][] = $section;\n	}\n\n	foreach ($parents as $parent => $children) {\n		foreach ($children as $child) {\n			$siblings[$child] = $children;\n			if (($key = array_search($child,$siblings[$child])) !== FALSE) {\n    			unset($siblings[$child][$key]);\n    			$siblings[$child] = array_values($siblings[$child]); // re-index array\n			}\n		}\n	}\n\n	return $siblings;\n}\n\nfunction adi_menu_descendants($hierarchy,$parent) {\n// create list of descendants, indexed by section/article ID\n	global $descendant_list;\n\n	foreach ($hierarchy as $index => $section) {\n		// add child to parent\'s list\n		$descendant_list[$parent][] = $index;\n		if ($section[\'child\']) { // found some grandchildren\n			// follow the family tree\n			adi_menu_descendants($section[\'child\'],$index);\n			// add child\'s descendants to parent\'s list\n			$descendant_list[$parent] = array_merge($descendant_list[$parent],$descendant_list[$index]);\n		}\n		else // create empty list for the childless\n			$descendant_list[$index] = array();\n	}\n\n	return $descendant_list;\n}\n\nfunction adi_menu_get_ancestors($section_list,$this_generation,$ancestor_list=array()) {\n// find all ancestors of given section (in order: parent, grandparent, great-grandparent ...)\n\n	$parent = $section_list[$this_generation][\'adi_menu_parent\'];\n	if ($parent != \'\') {\n		if (array_search($parent,$ancestor_list) !== FALSE) // loop found, so bomb out\n			return $ancestor_list;\n		$ancestor_list[] = $parent;\n		return adi_menu_get_ancestors($section_list,$parent,$ancestor_list);\n	}\n	else\n		return $ancestor_list;\n}\n\nfunction adi_menu_find_ancestor($level) {\n// find ancestor of current section\n	global $descendant_list,$section_levels,$s,$adi_menu_debug;\n\n	$found = \'\';\n\n	foreach ($descendant_list as $index => $list) {\n		$index == \'adi_menu_root\' ? // special case\n			$this_level = 0 :\n			$this_level = $section_levels[$index];\n		if (array_search($s,$list) !== FALSE) {\n			if ($level == $this_level) {\n				$found = $index;\n			}\n		}\n	}\n\n	return $found;\n}\n\nfunction adi_menu_find_descendant($level) {\n// find descendant of current section at specified level\n	global $descendant_list,$section_levels,$s;\n\n	$found = array();\n\n	foreach ($descendant_list[$s] as $descendant) {\n		$this_level = $section_levels[$descendant];\n		if ($level == $this_level) {\n			$found[] = $descendant;\n		}\n	}\n\n	return $found;\n}\n\nfunction adi_menu_cat_section_map($section_list) {\n// generate list of categories & sections that redirect to them\n	$cat_section_map = array();\n\n	foreach ($section_list as $name => $section) {\n		if ($section[\'adi_menu_redirect_category\'])\n			$cat_section_map[$section[\'adi_menu_redirect_category\']][] = $name;\n	}\n\n	return $cat_section_map;\n}\n\nfunction adi_menu_active($index,$hierarchy,$active_section,$active_parent,$active_article) {\n// determines whether current element should be active or not (but is pre-informed about active_section, active_parent, active_article)\n// 		in articles mode - lowest parent is parent of active article\n//		in non-articles mode - lowest parent is parent of active section\n	global $s,$c,$descendant_list,$active_ancestors,$pretext,$articles,$cat_section_map;\n\n	if ($articles) { // articles part of menu structure\n		$active_section = ($active_section && !$pretext[\'id\']); // active section (but not if displaying article) BEWARE: resetting $active_section here\n		$active_section_parent = ($active_parent && !$pretext[\'id\'] && array_key_exists($s,$hierarchy[$index][\'child\'])); // parent section of active section (but not if displaying an article)\n		$active_article_parent = ($active_parent && (strcasecmp($s, $index) == 0)); // parent of active article\n		$active_section_ancestor = ($active_ancestors && (array_search($s,$descendant_list[$index]) !== FALSE)); // ancestor of active section\n		$active_article_ancestor = ($active_ancestors && (array_search(\'article_\'.$pretext[\'id\'],$descendant_list[$index]) !== FALSE)); // ancestor of active article\n		//TODO - category parent\n		//TODO - category ancestor\n		return $active_section || $active_section_parent || $active_article_parent || $active_section_ancestor || $active_article_ancestor || $active_article;\n	}\n	else { // don\'t care about articles\n		$active_section_parent = ($active_parent && array_key_exists($s,$hierarchy[$index][\'child\'])); // parent section of active section (this seems to work for category sections too)\n		$active_section_ancestor = ($active_ancestors && (array_search($s,$descendant_list[$index]) !== FALSE)); // ancestor of active section\n		$active_category_ancestor = FALSE;\n		if ($c) { // category page?\n			if (isset($cat_section_map[$c])) { // category used as redirect in menu hierarchy?\n				foreach ($cat_section_map[$c] as $section) { // see if we\'re an ancestor of any sections redirecting to category\n					$active_category_ancestor = ($active_ancestors && ($active_category_ancestor || (array_search($section,$descendant_list[$index]) !== FALSE))); // ancestor of active category\n				}\n			}\n		}\n		return $active_section || $active_section_parent || $active_section_ancestor || $active_category_ancestor;\n	}\n}\n\nfunction adi_menu_is_current($this_section,$this_category=\'\',$debug=FALSE) {\n// determine whether supplied section or catgeory ($is_section=FALSE) is current or not\n// i.e. section = $s OR force_current attribute has supplied a match\n	global $s,$c,$force_current;\n\n	if ($debug) echo __FUNCTION__.\"(): s=$s, c=$c, this_section=$this_section, this_category=$this_category\";\n\n	$current = FALSE;\n\n	if ($this_section)\n		$current = $current || (strcasecmp($s,$this_section) == 0);\n\n	if ($this_category)\n		$current = $current || (strcasecmp($c,$this_category) == 0);\n\n	if ($force_current) {\n		if ($debug) echo \", force_current\";\n		if (isset($force_current[$this_section]))\n			$current = $current || (array_search($s,$force_current[$this_section]) !== FALSE);\n		if (isset($force_current[$this_category]))\n			$current = $current || (array_search($c,$force_current[$this_category]) !== FALSE);\n	}\n\n	if ($debug) echo \", current=$current\".br;\n\n	return $current;\n}\n\nfunction adi_menu_markup($hierarchy,$level) {\n// output <ul>/<li> markup from given $hierarchy\n	global $prefs,$menu_id,$parent_class,$active_class,$s,$c,$class,$link_span,$list_id,$list_id_prefix,$active_li_class,$articles,$include_children,$active_parent,$list_span,$active_ancestors,$descendant_list,$first_class,$last_class,$list_prefix,$prefix_class,$suppress_url,$suppress_url_sections,$new_article_mode,$article_class,$pretext,$active_article_class,$speaking_block,$label,$labeltag,$label_class,$label_id,$current_children_only,$adi_menu_escape,$odd_even,$ignore_alt_title,$adi_menu_vs_prefix,$exclude_niblings;\n\n	$out = array();\n\n	if (empty($hierarchy))\n		return $out; // bomb out if hierarchy is empty\n\n	$css_id = $css_class = \'\';\n	if (!$level) { // set HTML ID & CSS class on top level <ul> only\n		if ($menu_id) $css_id = \' id=\"\'.$menu_id.\'\"\';\n		if ($class) $css_class = \' class=\"\'.$class.\'\"\';\n		if ($label)\n			$labeltag ?\n				$out[] = doTag($label,$labeltag,$label_class,\'\',$label_id) :\n				$out[] = $label;\n	}\n\n	$out[] = \'<ul\'.$css_id.$css_class.\'>\';\n\n	// get list of section names from $hierarchy and determine first & last\n	$keys = array_keys($hierarchy);\n	$first_section = reset($keys);\n	$last_section = end($keys);\n\n	$odd = FALSE;\n	foreach ($hierarchy as $section => $data) {\n		$odd = !$odd;\n		$parent = !empty($data[\'child\']);\n		$li_class_list = \'\';\n		if ($parent && $include_children)  // not a parent if not including the children\n			$li_class_list .= \' \'.$parent_class;\n		$active_section = FALSE;\n		$active_article = FALSE;\n		if ($data[\'section\']) {\n			if ($data[\'redirect_category\']) {\n				$active_section = adi_menu_is_current($section,$data[\'redirect_category\']); // redirected to current category?\n			}\n			else if (!$c) { // active section? (avoid false-positive - category page\'s section is \"default\")\n				$active_section = adi_menu_is_current($section);\n				if ($data[\'redirect_section\']) // section redirected to active section?\n					$active_section = adi_menu_is_current($data[\'redirect_section\']);\n			}\n		}\n		else { // $section is an article!\n			$li_class_list .= \' \'.$article_class;\n			$article_id = preg_replace(\'/article_/\',\'\',$section); // convert \'article_id#\' to \'id#\'\n			$active_article = ($article_id == $pretext[\'id\']); // is the article active?\n		}\n		if ($data[\'redirect_section\'] || $data[\'redirect_link\'] || $data[\'redirect_category\']) // redirected (to section/link/category)\n			$li_class_list .= \' menu_redirect\';\n		if ($data[\'redirect_link\']) // redirected to link\n			$li_class_list .= \' menu_ext_link\';\n		if ($data[\'redirect_category\']) // redirected to category\n			$li_class_list .= \' menu_category\';\n		if ($data[\'virtual_section\']) // virtual section\n			$li_class_list .= \' menu_virtual\';\n		$title = adi_menu_htmlentities($data[\'title\'],$adi_menu_escape);\n		if ($data[\'alt_title\'] && !$ignore_alt_title) { // alternative title specified\n			$li_class_list .= \' menu_alt_title\';\n			$title = adi_menu_htmlentities($data[\'alt_title\'],$adi_menu_escape);\n		}\n		if (!($data[\'section\']) && ($prefs[\'title_no_widow\'])) // dewidow article title if required\n			$title = noWidow($title);\n		$url = $data[\'url\']; // although URL may get suppressed later ...\n		$clone = $data[\'clone\'];\n		$first = ($section == $first_section) && $first_class; // only flag first section if required\n		$last = ($section == $last_section) && $last_class; // only flag last section if required\n		$link_span ?\n			$link_content = \'<span>\'.$title.\'</span>\' :\n			$link_content = $title;\n		if ($speaking_block)\n			$link_content = $link_content.adi_menu_speaking_block($section,$data[\'redirect_section\']);\n		if ($clone) { // section/category is a clone\n			$clone_suffix = \'_clone\'; // make ID unique\n			$li_class_list .= \' menu_clone\';\n		}\n		else\n			$clone_suffix = \'\';\n		$list_id ?\n			$li_id = \' id=\"\'.$list_id_prefix.$section.$clone_suffix.\'\"\' :\n			$li_id = \'\';\n		$active_li = $active_li_class && adi_menu_active($section,$hierarchy,$active_section,$active_parent,$active_article);\n		if ($active_li)\n			$li_class_list .= \' \'.$active_li_class;\n		$article_list = \'\';\n		$article_parent = FALSE; // NOT NEEDED IN NEW SYSTEM: $PARENT WORKS FOR SECTION & ARTICLE PARENTS\n		if (!$parent && $articles && !$new_article_mode) // not a section parent so check for articles in advance\n			if (!$clone) { // but don\'t want duplicates\n				$article_list = adi_menu_articles($section,\'ul\');\n				if ($article_list != \"\") { // articles found, so add parent class (i.e. article parent)\n					$li_class_list .= \' \'.$parent_class;\n					$article_parent = TRUE;\n				}\n			}\n		if ($parent || $article_parent) // suppress URL on parents only\n			switch ($suppress_url) {\n				case \'all\':\n					$url = \'#\';\n					break;\n				case \'top\':\n					if (!$level) $url = \'#\';\n					break;\n				case \'active\':\n					if ($active_section) $url = \'#\';\n					break;\n				/*case \'section\':\n					if ($parent) $url = \'#\'; break;\n				case \'article\':\n					if ($article_parent) $url = \'#\'; break;*/\n				default:\n					/* do nothing */\n					break;\n			}\n		if ($suppress_url_sections) { // check list of suppression orders\n			$suppressions = do_list($suppress_url_sections);\n			if (array_search($section,$suppressions) !== FALSE) $url = \'#\'; // no link for you chummy\n		}\n		if ($first) $li_class_list .= \' \'.$first_class;\n		if ($last) $li_class_list .= \' \'.$last_class;\n		if ($odd_even) $li_class_list .= \' \'.(($odd) ? \'menu_odd\' : \'menu_even\');\n		$li_class_list ?\n			$css_class = \' class=\"\'.trim($li_class_list).\'\"\' :\n			$css_class = \'\';\n		$out[] = \'<li\'.$li_id.$css_class.\'>\';\n		$active_a = $active_class && adi_menu_active($section,$hierarchy,$active_section,$active_parent,$active_article);\n		if ($list_span) $out[] = \'<span>\';\n		if ($list_prefix) $out[] = \'<span class=\"\'.$prefix_class.\'\">\'.$list_prefix.\'</span>\';\n		$out[] = tag($link_content,\'a\',($active_a ? \' class=\"\'.$active_class.\'\"\' : \'\').\' href=\"\'.$url.\'\"\');\n		if ($list_span) $out[] = \'</span>\';\n		// the fact that we\'re here means that siblings are already included, so now decide if kids should be sought out\n		$find_the_kids = TRUE;\n		if ($current_children_only) // only immediate children of active (i.e. no grandchildren or niblings)\n// 			$find_the_kids = $active_section || array_key_exists($s,$data[\'child\']) || (array_search($s,$descendant_list[$section]) !== FALSE); // active section, parent of active, or ancestor of active\n			$find_the_kids = $active_section || (array_search($s,$descendant_list[$section]) !== FALSE); // active section or ancestor of active\n		if ($exclude_niblings) // all descendants of active (i.e. no niblings)\n			$find_the_kids = $active_section || (array_search($s,$descendant_list[$section]) !== FALSE) || (array_search($section,$descendant_list[$s]) !== FALSE); // active section, ancestor of active or descendant of active\n		if ($parent && $include_children && $find_the_kids) // find the kids (but only if include_children=1)\n			$out = array_merge($out,adi_menu_markup($data[\'child\'],$level+1));\n		else if ($articles && !$new_article_mode) // not a section parent but an article parent so insert articles here\n			$out[] = $article_list;\n		$out[] = \"</li>\";\n	}\n\n	if (isset($section) && $articles && !$new_article_mode)\n	// above condition prevents tag errors with attribute combinations that result in no sections\n	//  e.g. (include_parent=\"0\" && sections=\"all childless\") OR (sections=\"list\" && exclude=\"same list\")\n		if ($data[\'parent\'] != \'\') // last sibling was a child, so check for parent\'s articles here\n			$out[] = adi_menu_articles($data[\'parent\'],\'\');\n\n	$out[] = \"</ul>\";\n\n	return $out;\n}\n\nfunction adi_menu_articles($section,$wraptag) {\n// generate list of articles/markup using <txp:article_custom />\n	global $s,$articles,$article_attr,$adi_menu_article_form,$section_article_list,$active_articles_only;\n\n	if (@txpinterface != \'admin\') { // articles not relevent in admin interface\n		$allowed = array_search($section, $section_article_list) !== FALSE; // check if articles allowed\n		if ($active_articles_only) // then only allow articles if section is active\n			$allowed &= ($section == $s);\n		if (!$allowed) return \'\';\n		$article_attr == \'\' ?\n			$attr = \'\' :\n			$attr = \' \'.$article_attr;\n		$article_list = trim(parse(\'<txp:article_custom section=\"\'.$section.\'\"\'.$attr.\' form=\"\'.$adi_menu_article_form.\'\" />\'));\n		if ($article_list == \"\")\n			return \'\';\n		else // articles found\n			if ($wraptag == \"\")\n				return $article_list;\n			else\n				return tag($article_list,$wraptag,\'\');\n	}\n}\n\nfunction adi_menu_array_subtract($array1,$array2) {\n// values in array2 that are not in array1 are removed\n	$new = array();\n	$count1 = count($array1);\n	$count2 = count($array2);\n	foreach ($array1 as $index1 => $value1) {\n		$found = FALSE;\n		foreach ($array2 as $index2 => $value2) {\n			$found = $value1 == $value2;\n			if ($found) break;\n		}\n		$found ? $found = FALSE : $new[] = $value1;\n	}\n	return $new;\n}\n\nfunction adi_menu_htmlentities($string,$method) {\n// escape supplied string using:\n// 	new:\n//		\"\" = default: ENT_COMPAT,\'UTF-8\' (thanks ttr)\n// 	old:\n//		\"html\" = htmlentities (ALL special characters translated)\n//		\"htmlspecial\" htmlspecialchars (only & \" \' < > translated)\n	if ($method) { // use manual sledgehammer\n		if ($method == \'html\') $string = htmlentities($string);\n		if ($method == \'htmlspecial\') $string = htmlspecialchars($string);\n	}\n	else\n		$string = htmlentities($string,ENT_COMPAT,\'UTF-8\');\n\n	return $string;\n}\n\nfunction adi_menu($atts) {\n// the <txp:adi_menu /> tag\n	global $prefs,$s,$pretext,$out,$menu_id,$parent_class,$active_class,$include_parent,$include_childless,$default_title,$default_first,$clone_title,$class,$link_span,$list_id,$list_id_prefix,$active_li_class,$articles,$article_attr,$section_article_list,$include_children,$active_parent,$active_articles_only,$list_span,$active_ancestors,$descendant_list,$first_class,$last_class,$list_prefix,$prefix_class,$suppress_url,$suppress_url_sections,$new_article_mode,$article_class,$article_position,$active_article_class,$article_sort,$section_levels,$speaking_block,$speaking_block_form,$label,$labeltag,$label_class,$label_id,$current_children_only,$adi_menu_escape,$adi_menu_prefs,$odd_even,$section_article_sort,$category_article_sort,$ignore_alt_title,$cat_section_map,$exclude_clone,$cat_article_sort,$cat_article_attr,$force_current,$include_top_level,$include_siblings,$current_descendants_only,$exclude_niblings;\n\n	extract(lAtts(array(\n		\'active_ancestors\'		=> \'0\',				// set active class on all ancestors of current section\n		\'active_articles_only\'	=> \'\',				// list articles in the current active section only\n		\'active_class\'			=> \'active_class\',	// CSS class for current section (<a>)\n		\'active_li_class\'		=> \'\',				// CSS class for current section (<li>)\n		\'active_parent\'			=> \'0\',				// set active class on parent of current section\n		\'article_attr\'			=> \'\',				// comma separated list of additional article_custom attributes (section & category articles)\n		\'article_class\'			=> \'menu_article\',	// CSS class on <li> containing article link\n		\'article_exclude\'		=> \'\',				// list of sections not to have articles\n		\'article_include\'		=> \'\',				// exclusive list of sections to have articles (default = all)\n		\'article_position\'		=> \'after\',			// where to place articles: before, after or dovetail with sections\n		\'article_sort\'			=> \'Posted desc\',	// article sort (sections & categories)\n		\'articles\'				=> \'0\',				// include articles in menu\n		\'cat_article_attr\'		=> \'\',				// comma separated list of additional article_custom attributes (category articles only)\n		\'cat_article_exclude\'	=> \'\',				// list of categories not to have articles\n		\'cat_article_include\'	=> \'\',				// exclusive list of categories to have articles (default = all)\n		\'cat_article_sort\'		=> \'\',				// article sort (categories only)\n		\'class\'					=> \'section_list\',	// CSS class for top level <ul>\n		\'clone_title\'			=> \'Summary\',		// default title of child clone **DEPRECATED**\n		\'current_children_only\'	=> \'0\',				// only output child sections/categories/articles of current section\n		\'current_descendants_only\' => \'0\',			// ignore child sections/categories/articles which don\'t belong to current section or its decendants\n		\'exclude_niblings\'		 => \'0\',			// ignore child sections/categories/articles which don\'t belong to current section or its decendants SHOULD BE NIBLINGS/COUSINS\n		\'default_first\'			=> \'1\',				// section \'default\' to be listed first\n		\'default_title\'			=> \'Home\',			// title for \'default\' section\n		\'escape\'				=> \'\',				// escape HTML entities in section titles: \"\", \"html\", \"htmlspecial\" (default=\"\" - htmlentities/ENT_COMPAT/UTF-8)\n		\'exclude_clone\'			=> \'0\',				// stop clone appearing\n		\'exclude\'				=> \'\',				// list of sections to be excluded (over and above what\'s excluded in admin)\n		\'first_class\'			=> \'\',				// CSS class on first <li> of list\n		\'force_current\'			=> \'\',				// list of sections defining when they should be considered as \"current\" (sec1,sec2:sec3,sec4;sec5,sec6:sec7,sec8), use for virtuals redirecting to categories\n		\'ignore_alt_title\'		=> \'0\',				// choose to ignore the alternatve titles\n		\'include_childless\'		=> \'0\',				// output childless section\n		\'include_children\'		=> \'1\',				// output children as well as parents\n		\'include_current\'		=> \'0\',				// include the current section (used with sections=\"...\")\n		\'include_default\'		=> \'1\',				// include \'default\' section (more of a \"don\'t exclude default\" setting!)\n		\'include_parent\'		=> \'1\',				// output parent of included sections\' children\n		\'include_siblings\'		=> \'0\',				// sections at same level (without children)\n		\'include_top_level\'		=> \'0\',				// include top level sections (without children)\n		\'label_class\'			=> \'menu_label\',	// CSS class for label\'s tag\n		\'label_id\'				=> \'\',				// HTML ID for label\'s tag\n		\'label\'					=> \'\',				// label string to precede menu\n		\'labeltag\'				=> \'\',				// tag to wrap around label\n		\'last_class\'			=> \'\',				// CSS class on last <li> of list\n		\'link_span\'				=> \'0\',				// <span> contents of link or not\n		\'list_id_prefix\'		=> \'menu_\',			// <li> ID prefix\n		\'list_id\'				=> \'0\',				// output <li> IDs or not\n		\'list_prefix\'			=> \'\',				// prefix added to menu list items\n		\'list_span\'				=> \'0\',				// <span> contents of <li> or not\n		\'menu_id\'				=> \'mainmenu\',		// CSS ID for top level <ul>\n		\'new_article_mode\'		=> \'1\',				// new article mode on by default\n		\'odd_even\'				=> \'0\',				// CSS classes applied to odd/even list items\n		\'override_exclude\'		=> \'\',				// list of sections, excluded in admin, to be included\n		\'parent_class\'			=> \'menuparent\',	// CSS class for parent <li>\n		\'prefix_class\'			=> \'menu_prefix\',	// class added to <span> around prefixes\n		\'role\'					=> \'navigation\',	// ARIA role applied to wraptag\n		\'sections\'				=> \'\',				// list of sections in menu (default = all)\n		\'sort\'					=> \'adi_menu_sort\',	// section sort (use \'\' for database order)\n		\'speaking_block_form\'	=> \'\',				// alternative form for speaking block\n		\'speaking_block\'		=> \'0\',				// enable <span>speaking ... block</span>\n		\'sub_menu_level\'		=> \'0\',				// the level of the submenu to output\n		\'sub_menu\'				=> \'0\',				// generate submenu of current section\n		\'suppress_url_sections\'	=> \'\',				// list of sections to get URL \"#\"\n		\'suppress_url\'			=> \'0\',				// set URL to \"#\" on section links\n		\'test\'					=> \'0\',				// switch on new article mode in 1.0 (now ignored) **DEPRECATED**\n		\'wraptag_class\'			=> \'menu_wrapper\',	// class for wraptag\n		\'wraptag_id\'			=> \'\',				// id for wraptag\n		\'wraptag\'				=> \'\',				// wrap a tag around <ul>\n		\'debug\'					=> \'0\'\n	), $atts));\n\n	if (!adi_menu_installed()) return \"adi_menu not installed\";\n\n	// deprecated attributes\n	if (isset($atts[\'clone_title\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'clone_title\')), E_USER_NOTICE);\n	if (isset($atts[\'test\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'test\')), E_USER_NOTICE);\n	if (isset($atts[\'new_article_mode\'])) trigger_error(gTxt(\'deprecated_attribute\', array(\'{name}\' => \'new_article_mode\')), E_USER_NOTICE);\n	// deprecated mode\n	if (!$new_article_mode) trigger_error(gTxt(\'deprecated_configuration\', array(\'{name}\' => \'new_article_mode=\"0\"\')), E_USER_NOTICE);\n\n	// tidy up supplied attribute values\n	$children_only = 0; // ex-attribute\n	if ($sub_menu_level == \"1\") // switch to standard mode\n		$sub_menu_level = 0;\n\n	// error page\n	if ($sub_menu_level && !if_status(array(),TRUE)) { // submenus don\'t make sense on error pages\n		if ($debug) echo \"Submenu switched off on error page\".br;\n		return \'\';\n	}\n\n	// don\'t have to worry about escaping default title because it\'s stored in $section_list & processed later\n	$clone_title = trim($clone_title);\n	if (empty($clone_title)) // don\'t want it to be empty\n		$clone_title = \'Summary\';\n\n	// menu item sort\n	$sort = trim($sort); // MySQL error if sort = \" \"\n	empty($sort) ? $sort = \'NULL\' : $sort = doSlash($sort); // = database order by default\n\n	// article sorting\n	$fallback_sort = \'Posted desc\';\n	// section article sort\n	empty($article_sort) ? $article_sort = \'NULL\' : $article_sort = doSlash(trim($article_sort)); // set to NULL if article_sort=\"\" supplied\n	$article_sort_list = do_list($article_sort,\';\'); // split article_sort \"title;section1:posted desc;section2:custom_2\"\n	$section_article_sort = array(); // section specific sorting\n	$default_sort = \'\';\n	foreach ($article_sort_list as $this_sort) {\n		$this_section_sort = do_list($this_sort,\':\');\n		if (count($this_section_sort) > 1) // e.g. \"section1:posted desc\"\n			$section_article_sort[$this_section_sort[0]] = doSlash($this_section_sort[1]);\n		else // e.g. \"title; ...\"\n			if ($this_sort)\n				$default_sort = doSlash($this_sort);\n			else // e.g. \";section1:title ...\"\n				$default_sort = \'NULL\';\n	}\n	empty($default_sort) ? $article_sort = doSlash($fallback_sort) : $article_sort = $default_sort; // $article_sort is default section sort, $section_article_sort contains category specific sorts\n	// category article sort\n	empty($cat_article_sort) ? $cat_article_sort = $article_sort : $cat_article_sort = doSlash(trim($cat_article_sort)); // if not supplied, cat_article_sort follows article_sort\n	$cat_article_sort_list = do_list($cat_article_sort,\';\'); // split cat_article_sort \"title;cat1:posted desc;cat2:custom_2\"\n	$category_article_sort = array(); // category specific sorting\n	$default_sort = \'\';\n	foreach ($cat_article_sort_list as $this_sort) {\n		$this_category_sort = do_list($this_sort,\':\');\n		if (count($this_category_sort) > 1) // e.g. \"cat1:posted desc\"\n			$category_article_sort[$this_category_sort[0]] = doSlash($this_category_sort[1]);\n		else // e.g. \"title; ...\"\n			if ($this_sort)\n				$default_sort = doSlash($this_sort);\n			else // e.g. \";cat1:title ...\"\n				$default_sort = \'NULL\';\n	}\n	empty($default_sort) ? $cat_article_sort = $article_sort : $cat_article_sort = $default_sort; // $cat_article_sort is default category specific, $cat_article_sort contains category specific sorts\n\n	// article attributes\n	if (trim($cat_article_attr) == \'\') // if not supplied, cat_article_attr follows article_attr\n		$cat_article_attr = $article_attr;\n\n	// seem to recall this is required to avoid interference from adi_menu_breadcrumb escape attribute ??? FIXED BY new a_m_breadcrumb code?\n	$adi_menu_escape = $escape;\n\n	// massage sections list\n	if ($sub_menu_level) // make sure currently active section IS included e.g. when section normally excluded from main menu but a submenu is required on it\'s own (separately linked-to) page\n		$include_current = 1;\n	if ($include_current) // add current section to list\n		empty($sections) ? $sections = $s : $sections = $sections.\",$s\";\n	// section-sensitive current-level-specific submenu\n	if ($sub_menu) { // override sections list with current section\n		$sections = $s;\n		$children_only = 1;\n	}\n\n	// fiddle with default\n	if (!$include_default) // add default to exclude list\n		empty($exclude) ? $exclude = \"default\" : $exclude .= \",default\";\n\n	// get bulk section info from database (using $sections & $exclude attributes)\n	$section_list = adi_menu_section_list($sections,$exclude,$override_exclude,$sort);\n\n	// make up list of siblings\n	$siblings = adi_menu_siblings($section_list);\n\n	// include list (used by prune later)\n	$include_list = array();\n	if (!empty($sections)) // specific sections (& hence their children) selected\n		$include_list = do_list($sections); // trim spaces around section names in array\n\n	// dark forces\n	if ($force_current) {\n		// force_current=\"sec1,sec2:sec3,sec4; sec5,sec6:sec7,sec8\" - sec1 or sec2 considered current if sec3 or sec4 are current\n		// generate array indexed by section => array of current sections (when to be considered current itself)\n		$force_lists = do_list($force_current,\';\'); // (sec1,sec2:sec3,sec4) (sec5,sec6:sec7,sec8)\n		$force_current = array();\n		foreach ($force_lists as $force) {\n			$forced = do_list($force,\':\'); // (sec1,sec2) (sec3,sec4)\n			if (count($forced) == 2) {\n				$f1 = do_list($forced[0]); // (sec1,sec2)\n				$f2 = do_list($forced[1]); // (sec3,sec4)\n				if ($forced[1])\n					foreach ($f1 as $section)\n						$force_current[$section] = $f2; // sec1 -> (sec3,sec4)\n			}\n		}\n	}\n\n	// do articles bit\n	$article_list = $cat_article_list = array();\n	if ($articles) {\n		// SECTIONS\n		// sections that are allowed articles\n		if (empty($article_include))\n			$section_article_include = array_keys($section_list); // all sections\n		else\n			$section_article_include = do_list($article_include);\n		// sections that are NOT allowed articles\n		if (empty($article_exclude))\n			$section_article_exclude = array();\n		else\n			$section_article_exclude = do_list($article_exclude);\n		// section_article_list = section_article_include minus section_article_exclude\n		$section_article_list = adi_menu_array_subtract($section_article_include,$section_article_exclude);\n		if ($new_article_mode)\n			$article_list = adi_menu_get_articles($section_list,$section_article_list,array(),$debug);\n		// CATEGORIES\n		// categories that are allowed articles\n		$category_list = safe_column(\'name\',\'txp_category\',\"name != \'root\' AND type=\'article\'\");\n		if (empty($cat_article_include))\n			$category_article_include = array_keys($category_list); // all categories\n		else\n			$category_article_include = do_list($cat_article_include);\n		// categories that are NOT allowed articles\n		if (empty($cat_article_exclude))\n			$category_article_exclude = array();\n		else\n			$category_article_exclude = do_list($cat_article_exclude);\n		// category_article_list = category_article_include minus category_article_exclude\n		$category_article_list = adi_menu_array_subtract($category_article_include,$category_article_exclude);\n		if ($new_article_mode)\n			$cat_article_list = adi_menu_get_articles($section_list,array(),$category_article_list,$debug);\n	}\n\n	// set up hierarchy\n	$hierarchy = $original_hierarchy = adi_menu_hierarchy($section_list,$article_list,$cat_article_list);\n\n	// descendant list\n	$descendant_list = array();\n	$descendant_list = adi_menu_descendants($hierarchy,\'adi_menu_root\');\n\n	// sub or normal menu?\n	$section_levels = adi_menu_section_levels($hierarchy);\n	if ($sub_menu_level) { // set up a new include list for level-specific submenus\n		$include_list = array();\n		$ancestor = adi_menu_find_ancestor($sub_menu_level);\n		$descendants = adi_menu_find_descendant($sub_menu_level);\n		$this_section = $s; // don\'t want to muck with $s\n		if (empty($this_section)) $this_section = \'default\'; // to cope with error page (thanks CeBe)\n		// submenu scenarios\n		if ($section_levels[$this_section] == $sub_menu_level) { // ONE: current section at required level\n			$include_list[] = $section_list[$this_section][\'adi_menu_parent\']; // add current\'s parent to list to get siblings\n			$children_only = \"1\"; // but don\'t want current section\'s parent itself\n			if ($debug) echo \"Submenu scenario ONE\".br.br;\n		}\n		else if ($section_levels[$this_section] == ($sub_menu_level - 1)) { // TWO: current sections\' children at required level\n			$include_list[] = $this_section; // add current section to list to get children\n			$children_only = \"1\"; // but don\'t want current section itself\n			if ($debug) echo \"Submenu scenario TWO\".br.br;\n		}\n		else if ($ancestor) { // THREE: there\'re ancestors at required level\n				// add ancestor\'s parent to list to get siblings\n				$include_list[] = $section_list[adi_menu_find_ancestor($sub_menu_level)][\'adi_menu_parent\'];\n				$children_only = \"1\"; // don\'t want ancestor\'s parent itself\n			if ($debug) echo \"Submenu scenario THREE\".br.br;\n		}\n		// FOUR: descendants (beyond immediate children) at right level\n		else if ($descendants) {\n			foreach ($descendants as $descendant)\n				$include_list[] = $section_list[$descendant][\'adi_menu_parent\']; // add each descendant\'s parent to list to get children (multiple descendants may be siblings or cousins)\n			$children_only = \"1\"; // but don\'t want current section\'s parent itself\n			if ($debug) echo \"Submenu scenario FOUR\".br.br;\n		}\n		else { // FIVE: can\'t find anything at requested level, so kill the menu\n			$hierarchy = array();\n			if ($debug) echo \"Submenu scenario FIVE\".br.br;\n		}\n	}\n	else // normal menu\n		if (empty($sections) && !$include_parent) // make up list of top level sections (& cater for old submenu mode)\n			$include_list = array_keys($hierarchy); // all top level sections\n\n	// prune hierarchy\n	if ($include_list) {\n		if ($children_only) $include_parent = \"0\"; // OLD SUBMENU?\n		$hierarchy = adi_menu_prune($hierarchy,$include_list,$section_levels,$siblings);\n	}\n\n	//??? TEST ON LOWER LEVELS - NO COZ LOSING THIS\n	if ($current_descendants_only)\n		$hierarchy = adi_menu_prune($hierarchy,array($s),$section_levels,$siblings);\n\n	// map sections to categories\n	$cat_section_map = adi_menu_cat_section_map($section_list);\n\n	// DEBUG\n	if ($debug) {\n		echo \"SECTION INCLUDE LIST (all if empty):\".br;\n		dmp($include_list);\n		echo \"SECTION EXCLUDE LIST (none if empty):\".br;\n		echo $exclude.br.br;\n		echo \"SECTION LIST:\";\n		dmp($section_list);\n		if ($articles) {\n			echo \"SECTION ARTICLE INCLUDE LIST:\";\n			dmp($section_article_include);\n			echo \"SECTION ARTICLE EXCLUDE LIST:\";\n			dmp($section_article_exclude);\n			echo \"SECTION ARTICLE LIST (include minus exclude):\";\n			dmp($section_article_list);\n			echo \"CATEGORY ARTICLE INCLUDE LIST:\";\n			dmp($category_article_include);\n			echo \"CATEGORY ARTICLE EXCLUDE LIST:\";\n			dmp($category_article_exclude);\n			echo \"CATEGORY ARTICLE LIST (include minus exclude):\";\n			dmp($category_article_list);\n			if ($new_article_mode) {\n				echo \"ARTICLE LIST (by section):\";\n				dmp($article_list);\n				echo \"ARTICLE LIST (by category):\";\n				dmp($cat_article_list);\n			}\n		}\n		else\n			echo \'(ARTICLE MODE NOT SELECTED)\'.br.br;\n		echo \'SECTION ARTICLE SORT (default=\'.$article_sort.\'):\';\n		dmp($section_article_sort);\n		echo \'CATEGORY ARTICLE SORT (default=\'.$cat_article_sort.\'):\';\n		dmp($category_article_sort);\n		echo \"ORIGINAL HIERARCHY:\";\n		dmp($original_hierarchy);\n		if (isset($include_list)) {\n			echo \"PRUNED HIERARCHY:\";\n			dmp($hierarchy);\n		}\n		else\n			echo \'(HIERARCHY NOT PRUNED)\'.br.br;\n		if (isset($section_levels)) {\n			echo \"SECTION LEVELS:\";\n			dmp($section_levels);\n		}\n		echo \"DESCENDANT LIST:\";\n		dmp($descendant_list);\n		if (isset($siblings)) {\n			echo \"SIBLINGS:\";\n			dmp($siblings);\n		}\n		echo \'CATEGORY-SECTION MAP:\';\n		$cat_section_map = adi_menu_cat_section_map($section_list);\n		dmp($cat_section_map);\n		echo \"FORCE CURRENT MAPPING:\";\n		dmp($force_current);\n		echo \"VARIOUS:\".br;\n		echo \"Current section (\\$s) = $s\".br;\n		echo \"Current section\'s level = $section_levels[$s]\".br;\n		echo \"Submenu level = $sub_menu_level\".br;\n		echo \"Current article id = \".$pretext[\'id\'].\"\".br;\n		echo \"Articles = $articles\".br;\n		echo \"New article mode = $new_article_mode\".br;\n		$page_status = !empty($GLOBALS[\'txp_error_code\']) ? $GLOBALS[\'txp_error_code\'] : $pretext[\'status\'];\n		echo \"Page status: $page_status\";\n		echo if_status(array(),TRUE) ? \" (Normal page)\".br : \" (Error page)\".br;\n		echo br.\"SUPPLIED ATTRIBUTES:\";\n		dmp($atts);\n		echo \"ARTICLE ATTRIBUTES:\";\n		dmp(adi_menu_get_article_attr($article_attr));\n		echo \"CATEGORY ARTICLE ATTRIBUTES:\";\n		dmp(adi_menu_get_article_attr($cat_article_attr));\n		if ($adi_menu_prefs) { // admin interface only\n			echo \"PREFS\".br;\n			foreach ($adi_menu_prefs as $name => $value)\n				echo $name.\': \'.adi_menu_prefs($name).br;\n		}\n	}\n\n	// HTML5 awareness\n	$role = \'\';\n	if (isset($prefs[\'doctype\']))\n		if ($prefs[\'doctype\'] == \'html5\') {\n			if ($wraptag && $role)\n				$role = \' role=\"\'.$role.\'\"\';\n		}\n\n	// generate markup\n	$out = adi_menu_markup($hierarchy,0);\n	$out = trim(implode($out));\n	if ($out && $wraptag) { // add wraptag stuff\n		$wrap_attr = \'\';\n		if ($wraptag_id) $wrap_attr .= \' id=\"\'.$wraptag_id.\'\"\';\n		if ($wraptag_class) $wrap_attr .= \' class=\"\'.$wraptag_class.\'\"\';\n		$wrap_attr .= $role;\n		$out = tag($out,$wraptag,$wrap_attr);\n	}\n\n	// output markup to page\n	return $out;\n\n}\n', 'b2bad2f0c43a834466b84cedebdcb79f', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_mobile', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Mobile browser detection', '<h1><strong>adi_mobile</strong> &#8211; Mobile browser detection</h1>\n\n<p>This plugin implements the mobile browser detection capabilities of <a href=\"http://code.google.com/p/php-mobile-detect/\" rel=\"nofollow\">php-mobile-detect</a> (by Victor Stanciu), together with some website mode switch functionality and viewport measurement.</p>\n\n<p>Some new tags are included:</p>\n\n<ul>\n	<li><code>&lt;txp:adi_mobile /&gt;</code> to perform the mobile browser detection &amp; website mode function</li>\n	<li><code>&lt;txp:adi_if_mobile /&gt;</code> to control content delivery</li>\n	<li><code>&lt;txp:adi_mobile_script /&gt;</code> to add some jQuery to the page</li>\n	<li><code>&lt;txp:adi_mobile_viewport /&gt;</code> to cope with subtle viewport width changes</li>\n</ul>\n\n<h2><strong>Which Mobile?</strong></h2>\n\n<p><strong>adi_mobile</strong> will detect the following types:</p>\n\n<ul>\n	<li><strong>Mobile</strong> &#8211; any mobile</li>\n	<li><strong>Tablet</strong> &#8211; any tablet</li>\n	<li><strong>Android</strong></li>\n	<li><strong>AndroidTablet</strong></li>\n	<li><strong>Blackberry</strong></li>\n	<li><strong>BlackberryTablet</strong></li>\n	<li><strong>Opera</strong> &#8211; Mini &amp; Mobile</li>\n	<li><strong>Palm</strong></li>\n	<li><strong>Windows</strong></li>\n	<li><strong>WindowsPhone</strong></li>\n	<li><strong>iPhone</strong> &#8211; or iPod</li>\n	<li><strong>iPad</strong></li>\n	<li><strong>Generic</strong> &#8211; any mobile that doesn&#8217;t fall into the above camps</li>\n</ul>\n\n<p>The above types can be used in the <code>type</code> and <code>include</code> attributes.</p>\n\n<p>Note that tablets (e.g. iPad) are detected but are not advertised as a mobiles. This behaviour can be changed using the <code>include</code> attribute &#8211; see the <strong>Tablet examples</strong> below.</p>\n\n<h2><strong>Installation &amp; Requirements</strong></h2>\n\n<p>Install the plugin in the usual way.</p>\n\n<p>The <code>&lt;txp:adi_mobile /&gt;</code> tag must be placed at the <em>top</em> of your pages.</p>\n\n<p>Use <code>&lt;txp:adi_if_mobile /&gt;</code> wherever required.</p>\n\n<p>If you want to use the viewport detection mechanism then place the <code>&lt;txp:adi_mobile_script /&gt;</code> tag somewhere in the <code>&lt;head&gt;</code> and make sure you&#8217;ve got jQuery fired up as well.</p>\n\n<p>Cookies are used to store the mobile “mode” and the viewport information.  Note that if cookies are disabled then:</p>\n\n<ul>\n	<li>it will not be possible to disable the mobile detection functionality</li>\n	<li>the viewport detection mechanism will refresh every page</li>\n</ul>\n\n<p>If javascript is disabled then <strong>adi_mobile</strong> won&#8217;t be able to measure the viewport.</p>\n\n<h2><strong>Mobile Detection Mode</strong></h2>\n\n<p><strong>adi_mobile</strong> provides a mechanism whereby a user can select the “mode” of a website. By default, if a mobile browser is detected then <strong>adi_if_mobile</strong> reports it as such.</p>\n\n<p>Some mobile users may prefer to see the website in all its desktop glory instead. This can be achieved by setting <span class=\"caps\">URL</span> variable “mobile_mode”, for example:</p>\n\n<p><code>www.mywebsite.com/?mobile_mode=disabled</code></p>\n\n<p>The <code>&lt;txp:adi_mobile /&gt;</code> tag will pick this up and set a cookie. Further visits to the website by the user will be flagged as not being from a mobile. To re-enable mobile mode detection, use:</p>\n\n<p><code>www.mywebsite.com/?mobile_mode=not_disabled</code></p>\n\n<p>- the cookie will be cleared and mobile business resumed.</p>\n\n<p>The <strong>Examples</strong> show a way of implementing this using <a href=\"http://forum.textpattern.com/viewtopic.php?id=28224\" rel=\"nofollow\">adi_link</a>.</p>\n\n<p>If you&#8217;re using the viewport measurement mechanism then it&#8217;s advisable to force a reset at the same time:</p>\n\n<p><code>www.mywebsite.com/?mobile_mode=not_disabled&amp;viewport_reset</code></p>\n\n<h2><strong>Viewport information</strong></h2>\n\n<p>If required, <strong>adi_mobile</strong> will automatically attempt to measure the user&#8217;s viewport dimensions (width &amp; height in pixels) plus orientation (in degrees).  It does this by running a little bit of jQuery (see <code>&lt;txp:adi_mobile_script /&gt;</code>), refreshing the page &amp; recording the information in a cookie.</p>\n\n<p>Ranges of widths can be defined and tested by <strong>adi_if_mobile</strong>.  The defaults are “narrow” (320 or less) and “wide” (800+).  These defaults may be modified using attributes in the <strong>adi_mobile</strong> tag.</p>\n\n<p>Common mobile screen physical widths are:</p>\n\n<ul>\n	<li>Basic mobile &#8211; 320px</li>\n	<li>Android &#8211; 800px</li>\n	<li>Opera mobile &#8211; 850px</li>\n	<li>iPhone &#8211; 980px</li>\n	<li>Windows Phone 7 &#8211; 1024px</li>\n</ul>\n\n<p>But these are often advertised as 320px when meta tags are used, e.g.</p>\n\n<p><code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;</code></p>\n\n<p>The width, height and orientation are also stored in <span class=\"caps\">TXP</span> variables: “adi_mobile_width”, “adi_mobile_height” &amp; “adi_mobile_orientation”.</p>\n\n<p>Once the viewport dimensions have been stored in a cookie, they will not be measured again.  You can force a re-measurement, by setting <span class=\"caps\">URL</span> variable “viewport_reset”, for example:</p>\n\n<p><code>www.mywebsite.com/?viewport_reset</code></p>\n\n<p>The <strong>Examples</strong> show a way of implementing this using <a href=\"http://forum.textpattern.com/viewtopic.php?id=28224\" rel=\"nofollow\">adi_link</a> as well as ways of delivering different content depending on viewport width or orientation.</p>\n\n<p><strong>Beware</strong> &#8211; sometimes the viewport dimensions can change without <strong>adi_mobile</strong> detecting it.  Consider the following code:</p>\n\n<pre><code>&lt;txp:adi_if_mobile orientation=&quot;v&quot;&gt;\n	&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;\n	&lt;txp:adi_mobile_viewport width=&quot;320&quot; /&gt;\n&lt;txp:else /&gt;\n	&lt;meta name=&quot;viewport&quot; content=&quot;width=480&quot; /&gt;\n	&lt;txp:adi_mobile_viewport width=&quot;480&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Here, the viewport width is being changed by the <code>&lt;meta&gt;</code> tag <em>during</em> the page load, so the <strong>adi_mobile_viewport</strong> tag has to be used to override what was detected before the page loaded.  The new width is then picked up by <strong>adi_if_mobile</strong> as normal.</p>\n\n<h2><strong>Conditions of Use</strong></h2>\n\n<p>The <strong>adi_if_mobile</strong> conditional tag works in a number of ways:</p>\n\n<pre><code>&lt;txp:adi_if_mobile&gt;\n	- mobile browser detected (&amp; mobile detection has not been disabled)\n&lt;txp:else /&gt;\n	- no mobile detected (or mobile detection has not been disabled)\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<pre><code>&lt;txp:adi_if_mobile disabled=&quot;1&quot;&gt;\n	- mobile detection has been disabled\n&lt;txp:else /&gt;\n	- mobile detection not disabled\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>You can deliver content according to the viewport width:</p>\n\n<pre><code>&lt;txp:adi_if_mobile width=&quot;?&quot;&gt;\n	- viewport width unknown\n&lt;txp:else /&gt;\n	&lt;txp:adi_if_mobile width=&quot;narrow&quot;&gt;\n		- viewport is narrow\n	&lt;txp:else /&gt;\n		&lt;txp:adi_if_mobile width=&quot;wide&quot;&gt;\n			- viewport is wide\n		&lt;txp:else /&gt;\n			- viewport is medium\n		&lt;/txp:adi_if_mobile&gt;\n	&lt;/txp:adi_if_mobile&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Or to target a specific width:</p>\n\n<pre><code>&lt;txp:adi_if_mobile width=&quot;885&quot;&gt;\n	- the viewport is exactly 885px wide\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>To check the orientation:</p>\n\n<pre><code>&lt;txp:adi_if_mobile orientation=&quot;v&quot;&gt;\n	- the mobile is being held vertically (portrait mode)\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>You can negate the <strong>adi_if_mobile</strong> condition easily:</p>\n\n<ul>\n	<li><code>&lt;txp:adi_if_mobile not=&quot;1&quot;&gt;</code></li>\n	<li><code>&lt;txp:adi_if_not_mobile&gt;</code></li>\n</ul>\n\n<p>Use whichever you&#8217;re comfortable with &#8211; they&#8217;re functionally equivalent.</p>\n\n<p><strong>A note on mobile context:</strong> When testing viewport &amp; orientation <strong>adi_if_mobile</strong> <em>will not return any output, regardless if the condition is true or false, if no mobile has been detected or if mobile mode has been disabled.</em>  For example:</p>\n\n<pre><code>&lt;txp:adi_if_mobile width=&quot;narrow&quot;&gt;\n	- the viewport is narrow\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>is functionally equivalent to:</p>\n\n<pre><code>&lt;txp:adi_if_mobile&gt;\n	&lt;txp:adi_if_mobile width=&quot;narrow&quot;&gt;\n		- the viewport is narrow\n	&lt;/txp:adi_if_mobile&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<h2><strong>Known Unknowns</strong></h2>\n\n<p>If the viewport width or orientation can&#8217;t be determined, then <strong>adi_mobile</strong> will invent some defaults:</p>\n\n<ul>\n	<li>Default width = narrow</li>\n	<li>Default orientation = vertical</li>\n</ul>\n\n<p>The assumption being that if the mobile can&#8217;t run javascript then it must be a basic one or if it won&#8217;t run javascript then we have to use something.</p>\n\n<h2><strong>Styling</strong></h2>\n\n<p>Styling for mobiles is best served up using <span class=\"caps\">CSS</span> media queries and media types (e.g. <code>media=&quot;handheld&quot;</code>), but <strong>adi_mobile</strong> does provide additional options if required.</p>\n\n<p>Deliver a specific stylesheet to a mobile browser:</p>\n\n<pre><code>&lt;txp:adi_if_mobile&gt;\n	&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;mobile.css&quot; media=&quot;handheld&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>There&#8217;s a <span class=\"caps\">TXP</span> variable, automatically set by <strong>adi_mobile</strong>, which can be used to embed classes, e.g.</p>\n\n<pre><code>&lt;txp:if_variable name=&quot;adi_mobile_types&quot; value=&quot;&quot;&gt;\n	&lt;body&gt;\n&lt;txp:else /&gt;\n	&lt;body class=&quot;&lt;txp:variable name=&quot;adi_mobile_types&quot; /&gt;&quot;&gt;\n&lt;/txp:if_variable&gt;</code></pre>\n\n<p>The variable contains the list of types detected by <strong>adi_mobile</strong> (see above, all lowercase).</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p>Serve up specific code to any mobile:</p>\n\n<pre><code>&lt;txp:adi_if_mobile&gt;\n	&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Serve up specific code to an iPhone:</p>\n\n<pre><code>&lt;txp:adi_if_mobile type=&quot;iphone&quot;&gt;\n	&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1,user-scalable=no&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Serve up specific code to an iPhone OR an Android:</p>\n\n<pre><code>&lt;txp:adi_if_mobile type=&quot;iphone,android&quot;&gt;\n	&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Prevent content from being presented to mobiles:</p>\n\n<pre><code>&lt;txp:adi_if_not_mobile&gt;\n	... stuff to be show to non-mobiles only ...\n&lt;/txp:adi_if_not_mobile&gt;</code></pre>\n\n<p>To change the lower limit of a “wide” viewport to 980px:</p>\n\n<pre><code>&lt;txp:adi_mobile wide=&quot;980&quot; /&gt;</code></pre>\n\n<p>Links to switch between “Normal” and “Mobile” mode:</p>\n\n<pre><code>&lt;txp:adi_if_mobile&gt;\n	Change to: &lt;txp:adi_link section=&quot;&quot; urlvars=&quot;mobile_mode=disabled&quot; link_text=&quot;Normal view&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;\n&lt;txp:adi_if_mobile disabled=&quot;1&quot;&gt;\n	Change to: &lt;txp:adi_link section=&quot;&quot; urlvars=&quot;mobile_mode=not_disabled&amp;viewport_reset&quot; link_text=&quot;Mobile view&quot; /&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<h2><strong>Tablet examples</strong></h2>\n\n<p>As mentioned earlier, the default behaviour is to detect tablets but not to flag them as mobiles.  For example:</p>\n\n<pre><code>&lt;txp:adi_mobile /&gt;\n&lt;txp:adi_if_mobile&gt;\n	MOBILES ONLY\n&lt;txp:else /&gt;\n	DESKTOP &amp; TABLETS\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>If you want tablets to be bundled with mobiles (e.g. in order to detect orientation) then use:</p>\n\n<pre><code>&lt;txp:adi_mobile include=&quot;tablet&quot; /&gt;\n&lt;txp:adi_if_mobile&gt;\n	MOBILES &amp; TABLETS\n&lt;txp:else /&gt;\n	DESKTOP ONLY\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<p>Whichever method you use, you can refine things even further. For example:</p>\n\n<pre><code>&lt;txp:adi_mobile /&gt;\n&lt;txp:adi_if_mobile&gt;\n	MOBILES ONLY\n&lt;txp:else /&gt;\n	DESKTOP &amp; TABLETS\n	&lt;txp:adi_if_mobile type=&quot;tablet&quot;&gt;\n		TABLETS ONLY\n	&lt;txp:else /&gt;\n		DESKTOP ONLY\n	&lt;/txp:adi_if_mobile&gt;\n&lt;/txp:adi_if_mobile&gt;</code></pre>\n\n<h2><strong>adi_mobile &#8211; tag attributes</strong></h2>\n\n<p><code>include=&quot;type&quot;</code></p>\n\n<p>- a comma separated list of device types that should be reported as being mobiles. Default = &#8220;&#8221; (default behaviour).</p>\n\n<p><code>type=&quot;type&quot;</code></p>\n\n<p>- force <strong>adi_mobile</strong> to report that a specified device type is accessing the website. Default = &#8220;&#8221; (report actual mobile).</p>\n\n<p><code>useragent=&quot;string&quot;</code></p>\n\n<p>- override the actual user agent string. Default = &#8220;&#8221; (use reported user agent).</p>\n\n<p><code>narrow=&quot;integer&quot;</code></p>\n\n<p>- define the <em>upper</em> limit for a “narrow” viewport.  Default = 320 (pixels).</p>\n\n<p><code>wide=&quot;integer&quot;</code></p>\n\n<p>- define the <em>lower</em> limit for a “wide” viewport.  Default = 800 (pixels).</p>\n\n<h2><strong>adi_if_mobile &#8211; tag attributes</strong></h2>\n\n<p><code>type=&quot;type&quot;</code></p>\n\n<p>- a comma separated list of mobile types to test for, i.e. “type1,type2,type3” means <em>type1 OR type2 OR type3</em>. Default = “mobile” (any mobile).</p>\n\n<p><code>width=&quot;width&quot;</code></p>\n\n<p>- test if viewport width is “narrow”, “wide” or an exact width (in pixels).  Default = &#8220;&#8221; (do not test viewport width). <em>adi_if_mobile tags using this option only generate output in a mobile context.</em></p>\n\n<p><code>orientation=&quot;value&quot;</code></p>\n\n<p>- test viewport orientation.  Default = &#8220;&#8221; (do not test viewport orientation). Valid values are:</p>\n\n<ul>\n	<li><code>&quot;v&quot;, &quot;p&quot;, &quot;vertical&quot;, &quot;portrait&quot;</code></li>\n	<li><code>&quot;h&quot;, &quot;l&quot;, &quot;horizontal&quot;, &quot;landscape&quot;</code></li>\n	<li>or a +/- value in degrees (<code>0</code> = vertical; <code>90</code> = horizontal, turned anti-clockwise; <code>-90</code> = horizontal, turned clockwise)</li>\n</ul>\n\n<p><em>adi_if_mobile tags using this option only generate output in a mobile context.</em></p>\n\n<p><code>disabled=&quot;type&quot;</code></p>\n\n<p>- test if mobile mode has been disabled. Default = &#8220;&#8221; (do not check mobile mode).</p>\n\n<p><code>not=&quot;boolean&quot;</code></p>\n\n<p>- negate the condition. Default = “0” (normal condition).</p>\n\n<h2><strong>adi_mobile_viewport &#8211; tag attributes</strong></h2>\n\n<p><code>width=&quot;width&quot;</code></p>\n\n<p>- force the width to be a specific number of pixels. Default = &#8220;&#8221; (do not change width).</p>\n\n<h2><strong>Testing &amp; Diagnostics</strong></h2>\n\n<p>The best way of testing your mobile-enhanced website is to view it on lots of different mobile phones.  This not being practical there&#8217;re some other options:</p>\n\n<ul>\n	<li><strong>Fake it</strong> &#8211; use the <code>type</code> attribute in <code>&lt;txp:adi_mobile /&gt;</code>\n	<ul>\n		<li><code>&lt;txp:adi_mobile type=&quot;mobile&quot;/&gt;</code> &#8211; pretend you&#8217;re a mobile, any mobile</li>\n		<li><code>&lt;txp:adi_mobile type=&quot;iphone&quot;/&gt;</code> &#8211; pretend you&#8217;re an iPhone</li>\n	</ul></li>\n	<li><strong>Spoof it</strong> &#8211; use the <code>useragent</code> attribute in <code>&lt;txp:adi_mobile /&gt;</code></li>\n	<li><strong>Display diagnostics</strong> &#8211; these are automatically stored in <span class=\"caps\">TXP</span> variables:\n	<ul>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_useragent&quot; /&gt;</code> &#8211; the user agent</li>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_viewport&quot; /&gt;</code> &#8211; the viewport dimensions &amp; orientation (w,h,o)</li>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_width&quot; /&gt;</code> &#8211; the viewport width in pixels</li>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_height&quot; /&gt;</code> &#8211; the viewport height in pixels</li>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_orientation&quot; /&gt;</code> &#8211; the orientation in degrees</li>\n		<li><code>&lt;txp:variable name=&quot;adi_mobile_types&quot; /&gt;</code> &#8211; the mobile types that have been detected</li>\n	</ul></li>\n	</ul>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=35811\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_mobile\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_mobile - Mobile browser detection\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License, incorporating php=mobile-detect release under the MIT license\n\n	Version history:\n	0.2		- new adi_mobile attribute \'useragent\'\n			- new adi_mobile attribute \'include\'\n			- upgraded Mobile_Detect class (now detects Android Tablet/Blackberry Tablet/Windows Phone)\n			- improved tablet detection functionality\n	0.1		- initial release\n\n*/\n\nglobal $adi_mobile_list,$adi_mobile_tablet_list;\n\n$adi_mobile_list = array(\'Mobile\',\'Android\',\'Androidtablet\',\'Iphone\',\'Ipad\',\'Blackberry\',\'Blackberrytablet\',\'Opera\',\'Palm\',\'Windows\',\'Windowsphone\',\'Generic\');\n$adi_mobile_tablet_list = array(\'Androidtablet\',\'Ipad\',\'Blackberrytablet\');\n\n//***********************************************************************************************************************************\n// http://code.google.com/p/php-mobile-detect/ by Victor Stanciu, vic.stanciu@gmail.com\n//\n// This version: 2 Nov, 2011 - with Adi modifications:\n// - \"opera\" restored to $devices list\n// - function __construct() user agent parameter added\n//\n//***********************************************************************************************************************************\n\n/**\n * Mobile Detect\n * @license    http://www.opensource.org/licenses/mit-license.php The MIT License\n */\n\nclass Mobile_Detect\n{\n\n	protected $accept;\n	protected $userAgent;\n	protected $isMobile = false;\n	protected $isAndroid = null;\n	protected $isAndroidtablet = null;\n	protected $isIphone = null;\n	protected $isIpad = null;\n	protected $isBlackberry = null;\n	protected $isBlackberrytablet = null;\n	protected $isOpera = null;\n	protected $isPalm = null;\n	protected $isWindows = null;\n	protected $isWindowsphone = null;\n	protected $isGeneric = null;\n	protected $devices = array(\n		\"android\" => \"android.*mobile\",\n		\"androidtablet\" => \"android(?!.*mobile)\",\n		\"blackberry\" => \"blackberry\",\n		\"blackberrytablet\" => \"rim tablet os\",\n		\"iphone\" => \"(iphone|ipod)\",\n		\"ipad\" => \"(ipad)\",\n        \"opera\" => \"(opera mini|opera mobi)\",\n		\"palm\" => \"(avantgo|blazer|elaine|hiptop|palm|plucker|xiino)\",\n		\"windows\" => \"windows ce; (iemobile|ppc|smartphone)\",\n		\"windowsphone\" => \"windows phone os\",\n		\"generic\" => \"(kindle|mobile|mmp|midp|pocket|psp|symbian|smartphone|treo|up.browser|up.link|vodafone|wap|opera mini)\"\n	);\n\n//	public function __construct()\n//	{\n//		$this->userAgent = $_SERVER[\'HTTP_USER_AGENT\'];\n//		$this->accept = $_SERVER[\'HTTP_ACCEPT\'];\n\n    public function __construct($spoof_ua) {\n\n		if ($spoof_ua == \'\') {\n			$this->userAgent = $_SERVER[\'HTTP_USER_AGENT\'];\n			$this->accept    = $_SERVER[\'HTTP_ACCEPT\'];\n		}\n		else {\n			$this->userAgent = $spoof_ua;\n			$this->accept    = \'\';\n		}\n\n		if (isset($_SERVER[\'HTTP_X_WAP_PROFILE\']) || isset($_SERVER[\'HTTP_PROFILE\'])) {\n			$this->isMobile = true;\n		} elseif (strpos($this->accept, \'text/vnd.wap.wml\') > 0 || strpos($this->accept, \'application/vnd.wap.xhtml+xml\') > 0) {\n			$this->isMobile = true;\n		} else {\n			foreach ($this->devices as $device => $regexp) {\n				if ($this->isDevice($device)) {\n					$this->isMobile = true;\n				}\n			}\n		}\n	}\n\n	/**\n	 * Overloads isAndroid() | isAndroidtablet() | isIphone() | isIpad() | isBlackberry() | isBlackberrytablet() | isPalm() | isWindowsphone() | isWindows() | isGeneric() through isDevice()\n	 *\n	 * @param string $name\n	 * @param array $arguments\n	 * @return bool\n	 */\n	public function __call($name, $arguments)\n	{\n		$device = substr($name, 2);\n		if ($name == \"is\" . ucfirst($device) && array_key_exists(strtolower($device), $this->devices)) {\n			return $this->isDevice($device);\n		} else {\n			trigger_error(\"Method $name not defined\", E_USER_WARNING);\n		}\n	}\n\n	/**\n	 * Returns true if any type of mobile device detected, including special ones\n	 * @return bool\n	 */\n	public function isMobile()\n	{\n		return $this->isMobile;\n	}\n\n	protected function isDevice($device)\n	{\n		$var = \"is\" . ucfirst($device);\n		$return = $this->$var === null ? (bool) preg_match(\"/\" . $this->devices[strtolower($device)] . \"/i\", $this->userAgent) : $this->$var;\n		if ($device != \'generic\' && $return == true) {\n			$this->isGeneric = false;\n		}\n\n		return $return;\n	}\n\n}\n\n/***********************************************************************************************************************************/\n\nfunction adi_mobile($atts) {\n// <txp:adi_mobile /> tag\n\n	global $prefs,$variable,$adi_mobile,$adi_mobile_list,$adi_mobile_tablet_list,$default_width,$default_orientation;\n\n	extract(lAtts(array(\n		\'type\'			=> \'\',		// to fake it e.g. \'iphone\', \'android\', \'windows\'\n		\'narrow\'		=> \'320\',	// upper limit of \"narrow\"\n		\'wide\'			=> \'800\',	// lower limit of \"wide\"\n		\'include\'		=> \'\',		// comma separated list of devices to be treated as mobiles\n		\'useragent\'		=> \'\',		// spoof user agent string\n		\'debug\'			=> \'0\',\n	),$atts));\n\n	$include_list = array();\n	if ($include)\n		$include_list = doArray(explode(\',\',$include),\'trim\');\n\n	// set some defaults\n	$default_width = \'narrow\';\n	$default_orientation = 0; // i.e. vertical\n\n	// check if any TXP variable has ever been set ever, ever, ever (else may get error)\n	if (!isset($variable))\n		$variable = array();\n\n	// initialise adi_mobile GLOBAL\n	if (!isset($adi_mobile))\n		$adi_mobile = array();\n\n	// set the default \"mode\" position\n	$adi_mobile[\'disabled\'] = 0;\n\n	// store some attributes in $GLOBALS\n	$adi_mobile[\'narrow\'] = $narrow;\n	$adi_mobile[\'wide\'] = $wide;\n\n	// Mobile Mode: check the cookie jar\n	if (isset($_COOKIE[\'adi_mobile_disabled\']))\n		$adi_mobile[\'disabled\'] = 1;\n\n	// Mobile Mode: check URL var ?mobile_mode=disabled\n	if (isset($_GET[\'mobile_mode\'])) // throw zer sveetch Igor\n		if ($_GET[\'mobile_mode\'] == \'disabled\') {\n			setcookie(\'adi_mobile_disabled\',\'1\',time()+60*60*24*365,\'/\'); // expires in a year\n			$adi_mobile[\'disabled\'] = 1;\n		}\n		else {\n			setcookie(\'adi_mobile_disabled\',\'0\',time()-3600,\'/\'); // unset cookie by sending it back in time\n			$adi_mobile[\'disabled\'] = 0;\n		}\n\n	// Viewport: check URL var ?viewport=width,height,orientation\n	if (isset($_GET[\'viewport\'])) { // viewport found by jQuery\n		if ($_GET[\'viewport\'] != \'\') {\n			$viewport = htmlspecialchars($_GET[\'viewport\']);\n			setcookie(\'adi_mobile_viewport\',$viewport,0,\'/\'); // expires at end of session\n			$adi_mobile[\'viewport\'] = $viewport;\n			$viewport_array = explode(\',\',$viewport);\n			$adi_mobile[\'width\'] = $viewport_array[0];\n			$adi_mobile[\'height\'] = $viewport_array[1];\n			$adi_mobile[\'orientation\'] = $viewport_array[2]; // = \"90\" or \"-90\" on mobiles, \"undefined\" on desktop\n		}\n	}\n	else // see if it\'s in a cookie\n		if (isset($_COOKIE[\'adi_mobile_viewport\'])) { // already tested viewport, so retrieve value (or \"?\") from cookie\n			$adi_mobile[\'viewport\'] = $_COOKIE[\'adi_mobile_viewport\'];\n			$viewport_array = explode(\',\',$_COOKIE[\'adi_mobile_viewport\']);\n			$adi_mobile[\'width\'] = $viewport_array[0];\n			$adi_mobile[\'height\'] = $viewport_array[1];\n			$adi_mobile[\'orientation\'] = $viewport_array[2];\n		}\n		else { // more questions than answers\n			setcookie(\'adi_mobile_viewport\',\'?,?,?\',0,\'/\'); // expires at end of session\n			$adi_mobile[\'viewport\'] = \'?,?,?\';\n			$adi_mobile[\'width\'] = \'?\';\n			$adi_mobile[\'height\'] = \'?\';\n			$adi_mobile[\'orientation\'] = \'?\';\n		}\n	if ($adi_mobile[\'orientation\'] == \'undefined\') // let\'s be consistent\n		$adi_mobile[\'orientation\'] = \'?\';\n\n	// bung viewport information in TXP variables\n	$variable[\'adi_mobile_viewport\'] = $adi_mobile[\'viewport\'];\n	$variable[\'adi_mobile_width\'] = $adi_mobile[\'width\'];\n	if ($adi_mobile[\'width\'] == \'?\')\n		$variable[\'adi_mobile_width\'] .= \' (\'.$default_width.\')\';\n	$variable[\'adi_mobile_height\'] = $adi_mobile[\'height\'];\n	$variable[\'adi_mobile_orientation\'] = $adi_mobile[\'orientation\'];\n	if ($adi_mobile[\'orientation\'] == \'?\')\n		$variable[\'adi_mobile_orientation\'] .= \' (\'.$default_orientation.\')\';\n\n	// sniff that\n	$detect = new Mobile_Detect($useragent);\n	foreach ($adi_mobile_list as $device) {\n		$this_device = \'is\'.$device;\n		$adi_mobile[\'is\'.$device] = $detect->$this_device();\n	}\n\n	// faking it\n	if ($type) {\n		$type = ucfirst(strtolower($type)); // convert sOMEthiNg to Something\n		$adi_mobile[\'is\'.$type] = TRUE;\n		$adi_mobile[\'isMobile\'] = TRUE;\n	}\n\n	// record user agent (real or spoof) in TXP variable\n	if ($useragent)\n		$variable[\'adi_mobile_useragent\'] = $useragent;\n	else\n		$variable[\'adi_mobile_useragent\'] = $_SERVER[\'HTTP_USER_AGENT\'];\n\n	// time for your tablets\n	$adi_mobile[\'isTablet\'] = FALSE;\n	foreach ($adi_mobile_tablet_list as $tablet)\n		$adi_mobile[\'isTablet\'] = $adi_mobile[\'isTablet\'] || $adi_mobile[\'is\'.$tablet];\n\n	// by default, tablets are not \"mobiles\"\n	if ($adi_mobile[\'isTablet\'])\n			$adi_mobile[\'isMobile\'] = FALSE;\n\n	// mobile wannabees\n	foreach ($include_list as $device) {\n		$this_device = \'is\'.ucfirst(strtolower($device));\n		if (isset($adi_mobile[$this_device]))\n			if ($adi_mobile[$this_device])\n				$adi_mobile[\'isMobile\'] = TRUE;\n	}\n\n	// make a note of what\'s been detected - in a TXP variable\n	if ($adi_mobile[\'isTablet\'])\n		$variable[\'adi_mobile_types\'] = \'tablet\'; // added here coz there\'s no \'isTablet\' in Mobile Detect\n	else\n		$variable[\'adi_mobile_types\'] = \'\';\n	foreach ($adi_mobile_list as $device) {\n		if ($adi_mobile[\'is\'.$device])\n			$variable[\'adi_mobile_types\'] .= \' \'.strtolower($device);\n	}\n	$variable[\'adi_mobile_types\'] = trim($variable[\'adi_mobile_types\']);\n\n	if ($debug) adi_mobile_debug($atts);\n}\n\nfunction adi_if_mobile($atts, $thing) {\n// <txp:adi_if_mobile /> tag\n// 		default 		- if/else mobile (any mobile)\n// 		type=\"\"			- if/else specific mobile type\n// 		disabled=\"1\"	- if/else mobile mode\n// 		not=\"1\"			- negate if/else\n\n	global $adi_mobile,$adi_mobile_list,$adi_mobile_tablet_list,$default_width,$default_orientation;\n\n	extract(lAtts(array(\n		\'type\'			=> \'Mobile\',	// comma separated list of types\n		\'width\'			=> \'\',			// i.e. \"narrow\", \"wide\" or exact number of pixels\n		\'orientation\'	=> \'\',\n		\'disabled\'		=> \'0\',			// test if mobile mode disabled\n		\'not\'			=> \'0\'			// negate condition\n	),$atts));\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	$condition = FALSE; // default condition result\n\n	if ($width != \'\') { // viewport width conditional\n		if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) { // don\'t want to generate ANY viewport width opinions if not a mobile or mobile mode disabled\n			$detected_width = $adi_mobile[\'width\'];\n			if ($detected_width == \'?\') $detected_width = $adi_mobile[$default_width];\n			switch ($width) {\n				case \'narrow\':\n					if ($detected_width == \'?\') break; // i.e. probably \'?\'\n					if ($detected_width <= $adi_mobile[\'narrow\']) $condition = TRUE;\n					break;\n				case \'wide\':\n					if ($detected_width == \'?\') break; // i.e. probably \'?\'\n					if ($detected_width >= $adi_mobile[\'wide\']) $condition = TRUE;\n					break;\n				default: // test exact width (incl. \'?\')\n					if ($detected_width == $width) $condition = TRUE;\n			}\n		}\n		else\n			return;\n	}\n	else if ($orientation != \'\') { // viewport orientation conditional\n		if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) { // don\'t want to generate ANY viewport orientation opinions if not a mobile or mobile mode disabled\n			$detected_orientation = $adi_mobile[\'orientation\'];\n			if ($detected_orientation == \'?\') $detected_orientation = $default_orientation;\n			switch ($orientation) {\n				case \'p\':\n				case \'portrait\':\n				case \'v\':\n				case \'vertical\':\n					if (($detected_orientation == \'0\') || ($detected_orientation == \'180\')) $condition = TRUE;\n					break;\n				case \'l\':\n				case \'h\':\n				case \'landscape\':\n				case \'horizontal\':\n					if (($detected_orientation == \'90\') || ($detected_orientation == \'-90\')) $condition = TRUE;\n					break;\n				default: // test exact orientation (incl. \'?\')\n					if ($detected_orientation == $orientation) $condition = TRUE;\n			}\n		}\n		else\n			return;\n	}\n	else if ($disabled) // mode conditional\n		if ($adi_mobile[\'disabled\'])\n			$condition = TRUE;\n		else\n			$condition = FALSE;\n	else // type conditional\n		if ($adi_mobile[\'disabled\']) // mobile mode disabled, so fail condition regardless\n			$condition = FALSE;\n		else { // check specific type(s)\n			$type_list = explode(\',\',$type);\n			foreach ($type_list as $type ) { // scan user supplied types\n				$type = ucfirst(strtolower(trim($type))); // convert tYPe to Type\n				if (isset($adi_mobile[\'is\'.$type])) // will find normal adi_mobile types as well as user-made-up ones\n					$condition = $condition || $adi_mobile[\'is\'.$type];\n			}\n		}\n\n	if ($not) $condition = $condition ^ TRUE; // negate result, if required\n\n	return parse(EvalElse($thing,$condition));\n\n}\n\nfunction adi_if_not_mobile($atts,$thing) {\n	$atts[\'not\'] = \'1\';\n	return adi_if_mobile($atts,$thing);\n}\n\nfunction adi_mobile_script() {\n// jQuery - to pass viewport width via URL: ?viewport=xyz\n// if javascript switched on in middle of session then have to restart browser\n\n	global $adi_mobile;\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) // only think about this if you\'re a mobile and mobile mode not disabled\n		// fire up jQuery and redirect page if (viewport=xyz not in URL AND viewport cookie not set) OR (viewport_reset in URL but viewport=xyz isn\'t)\n		if ((!isset($_GET[\'viewport\']) && !isset($_COOKIE[\'adi_mobile_viewport\'])) || (isset($_GET[\'viewport_reset\']) && !isset($_GET[\'viewport\']))) {\n\n			$javascript = <<<END_SCRIPT\n<script type=\"text/javascript\">\n$(function() {\n	// NEED TO COPE WITH HASH ANCHORS TOO (window.location.hash)???\n	if (window.location.search == \'\') // check if there\'s any URL vars //SET UP & OR ? PREAMBLE IN A VAR\n		preamble = \"?\";\n	else\n		preamble = \"&\";\n	window.location.replace(window.location.href+preamble+\"viewport=\"+$(window).width()+\",\"+$(window).height()+\",\"+window.orientation);\n});\n</script>\nEND_SCRIPT;\n\n			return $javascript;\n		}\n}\n\nfunction adi_mobile_viewport($atts) {\n// <adi_mobile_viewport /> tag\n\n	global $variable, $adi_mobile;\n\n	extract(lAtts(array(\n		\'width\'			=> \'\',	// user specified width\n	),$atts));\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	if ($width != \'\') $adi_mobile[\'width\'] = $width;\n\n	// do TXP variables again\n	$variable[\'adi_mobile_width\'] = $adi_mobile[\'width\'];\n	$variable[\'adi_mobile_height\'] = $adi_mobile[\'height\'];\n	$variable[\'adi_mobile_orientation\'] = $adi_mobile[\'orientation\'];\n	$variable[\'adi_mobile_viewport\'] = $adi_mobile[\'width\'].\',\'.$adi_mobile[\'height\'].\',\'.$adi_mobile[\'orientation\'];\n}\n\nfunction adi_mobile_debug($atts) {\n	global $prefs,$variable,$adi_mobile;\n\n	echo \"SUPPLIED ATTRIBUTES<br/>\";\n	dmp($atts);\n	echo \"VERSIONS<br/>\";\n	echo \'<pre>\';\n	$version = safe_field(\"version\", \"txp_plugin\", \"name=\'adi_mobile\'\");\n	$status = safe_field(\"status\", \"txp_plugin\", \"name=\'adi_mobile\'\");\n	echo \'adi_mobile: \'.(empty($version) ? \'not installed\':$version.($status?\' (active)\' : \' (not active)\')).\'<br/>\';\n	echo \'TXP: \'.$prefs[\'version\'].\'<br/>\';\n	echo \'PHP: \'.phpversion().\'<br/>\';\n	echo \'</pre>\';\n	echo \'USER AGENT<br/>\';\n	echo \'<pre>\';\n	echo $variable[\'adi_mobile_useragent\'].\'<br/>\';\n	echo \'</pre>\';\n	echo \"GET VARS:<br/>\";\n	dmp($_GET);\n	echo \"ADI_MOBILE GLOBALS\";\n	dmp($adi_mobile);\n	echo \"COOKIES\";\n	echo \'<pre>\';\n	if (isset($_COOKIE[\'adi_mobile_disabled\']))\n		echo \'adi_mobile_disabled = \'.$_COOKIE[\'adi_mobile_disabled\'];\n	else\n		echo \'adi_mobile_disabled cookie not set (yet?)\';\n	echo \'<br/>\';\n	if (isset($_COOKIE[\'adi_mobile_viewport\']))\n		echo \'adi_mobile_viewport = \'.$_COOKIE[\'adi_mobile_viewport\'];\n	else\n		echo \'adi_mobile_viewport cookie not set (yet?)\';\n	echo \'</pre>\';\n}\n', '\n/*\n	adi_mobile - Mobile browser detection\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License, incorporating php=mobile-detect release under the MIT license\n\n	Version history:\n	0.2		- new adi_mobile attribute \'useragent\'\n			- new adi_mobile attribute \'include\'\n			- upgraded Mobile_Detect class (now detects Android Tablet/Blackberry Tablet/Windows Phone)\n			- improved tablet detection functionality\n	0.1		- initial release\n\n*/\n\nglobal $adi_mobile_list,$adi_mobile_tablet_list;\n\n$adi_mobile_list = array(\'Mobile\',\'Android\',\'Androidtablet\',\'Iphone\',\'Ipad\',\'Blackberry\',\'Blackberrytablet\',\'Opera\',\'Palm\',\'Windows\',\'Windowsphone\',\'Generic\');\n$adi_mobile_tablet_list = array(\'Androidtablet\',\'Ipad\',\'Blackberrytablet\');\n\n//***********************************************************************************************************************************\n// http://code.google.com/p/php-mobile-detect/ by Victor Stanciu, vic.stanciu@gmail.com\n//\n// This version: 2 Nov, 2011 - with Adi modifications:\n// - \"opera\" restored to $devices list\n// - function __construct() user agent parameter added\n//\n//***********************************************************************************************************************************\n\n/**\n * Mobile Detect\n * @license    http://www.opensource.org/licenses/mit-license.php The MIT License\n */\n\nclass Mobile_Detect\n{\n\n	protected $accept;\n	protected $userAgent;\n	protected $isMobile = false;\n	protected $isAndroid = null;\n	protected $isAndroidtablet = null;\n	protected $isIphone = null;\n	protected $isIpad = null;\n	protected $isBlackberry = null;\n	protected $isBlackberrytablet = null;\n	protected $isOpera = null;\n	protected $isPalm = null;\n	protected $isWindows = null;\n	protected $isWindowsphone = null;\n	protected $isGeneric = null;\n	protected $devices = array(\n		\"android\" => \"android.*mobile\",\n		\"androidtablet\" => \"android(?!.*mobile)\",\n		\"blackberry\" => \"blackberry\",\n		\"blackberrytablet\" => \"rim tablet os\",\n		\"iphone\" => \"(iphone|ipod)\",\n		\"ipad\" => \"(ipad)\",\n        \"opera\" => \"(opera mini|opera mobi)\",\n		\"palm\" => \"(avantgo|blazer|elaine|hiptop|palm|plucker|xiino)\",\n		\"windows\" => \"windows ce; (iemobile|ppc|smartphone)\",\n		\"windowsphone\" => \"windows phone os\",\n		\"generic\" => \"(kindle|mobile|mmp|midp|pocket|psp|symbian|smartphone|treo|up.browser|up.link|vodafone|wap|opera mini)\"\n	);\n\n//	public function __construct()\n//	{\n//		$this->userAgent = $_SERVER[\'HTTP_USER_AGENT\'];\n//		$this->accept = $_SERVER[\'HTTP_ACCEPT\'];\n\n    public function __construct($spoof_ua) {\n\n		if ($spoof_ua == \'\') {\n			$this->userAgent = $_SERVER[\'HTTP_USER_AGENT\'];\n			$this->accept    = $_SERVER[\'HTTP_ACCEPT\'];\n		}\n		else {\n			$this->userAgent = $spoof_ua;\n			$this->accept    = \'\';\n		}\n\n		if (isset($_SERVER[\'HTTP_X_WAP_PROFILE\']) || isset($_SERVER[\'HTTP_PROFILE\'])) {\n			$this->isMobile = true;\n		} elseif (strpos($this->accept, \'text/vnd.wap.wml\') > 0 || strpos($this->accept, \'application/vnd.wap.xhtml+xml\') > 0) {\n			$this->isMobile = true;\n		} else {\n			foreach ($this->devices as $device => $regexp) {\n				if ($this->isDevice($device)) {\n					$this->isMobile = true;\n				}\n			}\n		}\n	}\n\n	/**\n	 * Overloads isAndroid() | isAndroidtablet() | isIphone() | isIpad() | isBlackberry() | isBlackberrytablet() | isPalm() | isWindowsphone() | isWindows() | isGeneric() through isDevice()\n	 *\n	 * @param string $name\n	 * @param array $arguments\n	 * @return bool\n	 */\n	public function __call($name, $arguments)\n	{\n		$device = substr($name, 2);\n		if ($name == \"is\" . ucfirst($device) && array_key_exists(strtolower($device), $this->devices)) {\n			return $this->isDevice($device);\n		} else {\n			trigger_error(\"Method $name not defined\", E_USER_WARNING);\n		}\n	}\n\n	/**\n	 * Returns true if any type of mobile device detected, including special ones\n	 * @return bool\n	 */\n	public function isMobile()\n	{\n		return $this->isMobile;\n	}\n\n	protected function isDevice($device)\n	{\n		$var = \"is\" . ucfirst($device);\n		$return = $this->$var === null ? (bool) preg_match(\"/\" . $this->devices[strtolower($device)] . \"/i\", $this->userAgent) : $this->$var;\n		if ($device != \'generic\' && $return == true) {\n			$this->isGeneric = false;\n		}\n\n		return $return;\n	}\n\n}\n\n/***********************************************************************************************************************************/\n\nfunction adi_mobile($atts) {\n// <txp:adi_mobile /> tag\n\n	global $prefs,$variable,$adi_mobile,$adi_mobile_list,$adi_mobile_tablet_list,$default_width,$default_orientation;\n\n	extract(lAtts(array(\n		\'type\'			=> \'\',		// to fake it e.g. \'iphone\', \'android\', \'windows\'\n		\'narrow\'		=> \'320\',	// upper limit of \"narrow\"\n		\'wide\'			=> \'800\',	// lower limit of \"wide\"\n		\'include\'		=> \'\',		// comma separated list of devices to be treated as mobiles\n		\'useragent\'		=> \'\',		// spoof user agent string\n		\'debug\'			=> \'0\',\n	),$atts));\n\n	$include_list = array();\n	if ($include)\n		$include_list = doArray(explode(\',\',$include),\'trim\');\n\n	// set some defaults\n	$default_width = \'narrow\';\n	$default_orientation = 0; // i.e. vertical\n\n	// check if any TXP variable has ever been set ever, ever, ever (else may get error)\n	if (!isset($variable))\n		$variable = array();\n\n	// initialise adi_mobile GLOBAL\n	if (!isset($adi_mobile))\n		$adi_mobile = array();\n\n	// set the default \"mode\" position\n	$adi_mobile[\'disabled\'] = 0;\n\n	// store some attributes in $GLOBALS\n	$adi_mobile[\'narrow\'] = $narrow;\n	$adi_mobile[\'wide\'] = $wide;\n\n	// Mobile Mode: check the cookie jar\n	if (isset($_COOKIE[\'adi_mobile_disabled\']))\n		$adi_mobile[\'disabled\'] = 1;\n\n	// Mobile Mode: check URL var ?mobile_mode=disabled\n	if (isset($_GET[\'mobile_mode\'])) // throw zer sveetch Igor\n		if ($_GET[\'mobile_mode\'] == \'disabled\') {\n			setcookie(\'adi_mobile_disabled\',\'1\',time()+60*60*24*365,\'/\'); // expires in a year\n			$adi_mobile[\'disabled\'] = 1;\n		}\n		else {\n			setcookie(\'adi_mobile_disabled\',\'0\',time()-3600,\'/\'); // unset cookie by sending it back in time\n			$adi_mobile[\'disabled\'] = 0;\n		}\n\n	// Viewport: check URL var ?viewport=width,height,orientation\n	if (isset($_GET[\'viewport\'])) { // viewport found by jQuery\n		if ($_GET[\'viewport\'] != \'\') {\n			$viewport = htmlspecialchars($_GET[\'viewport\']);\n			setcookie(\'adi_mobile_viewport\',$viewport,0,\'/\'); // expires at end of session\n			$adi_mobile[\'viewport\'] = $viewport;\n			$viewport_array = explode(\',\',$viewport);\n			$adi_mobile[\'width\'] = $viewport_array[0];\n			$adi_mobile[\'height\'] = $viewport_array[1];\n			$adi_mobile[\'orientation\'] = $viewport_array[2]; // = \"90\" or \"-90\" on mobiles, \"undefined\" on desktop\n		}\n	}\n	else // see if it\'s in a cookie\n		if (isset($_COOKIE[\'adi_mobile_viewport\'])) { // already tested viewport, so retrieve value (or \"?\") from cookie\n			$adi_mobile[\'viewport\'] = $_COOKIE[\'adi_mobile_viewport\'];\n			$viewport_array = explode(\',\',$_COOKIE[\'adi_mobile_viewport\']);\n			$adi_mobile[\'width\'] = $viewport_array[0];\n			$adi_mobile[\'height\'] = $viewport_array[1];\n			$adi_mobile[\'orientation\'] = $viewport_array[2];\n		}\n		else { // more questions than answers\n			setcookie(\'adi_mobile_viewport\',\'?,?,?\',0,\'/\'); // expires at end of session\n			$adi_mobile[\'viewport\'] = \'?,?,?\';\n			$adi_mobile[\'width\'] = \'?\';\n			$adi_mobile[\'height\'] = \'?\';\n			$adi_mobile[\'orientation\'] = \'?\';\n		}\n	if ($adi_mobile[\'orientation\'] == \'undefined\') // let\'s be consistent\n		$adi_mobile[\'orientation\'] = \'?\';\n\n	// bung viewport information in TXP variables\n	$variable[\'adi_mobile_viewport\'] = $adi_mobile[\'viewport\'];\n	$variable[\'adi_mobile_width\'] = $adi_mobile[\'width\'];\n	if ($adi_mobile[\'width\'] == \'?\')\n		$variable[\'adi_mobile_width\'] .= \' (\'.$default_width.\')\';\n	$variable[\'adi_mobile_height\'] = $adi_mobile[\'height\'];\n	$variable[\'adi_mobile_orientation\'] = $adi_mobile[\'orientation\'];\n	if ($adi_mobile[\'orientation\'] == \'?\')\n		$variable[\'adi_mobile_orientation\'] .= \' (\'.$default_orientation.\')\';\n\n	// sniff that\n	$detect = new Mobile_Detect($useragent);\n	foreach ($adi_mobile_list as $device) {\n		$this_device = \'is\'.$device;\n		$adi_mobile[\'is\'.$device] = $detect->$this_device();\n	}\n\n	// faking it\n	if ($type) {\n		$type = ucfirst(strtolower($type)); // convert sOMEthiNg to Something\n		$adi_mobile[\'is\'.$type] = TRUE;\n		$adi_mobile[\'isMobile\'] = TRUE;\n	}\n\n	// record user agent (real or spoof) in TXP variable\n	if ($useragent)\n		$variable[\'adi_mobile_useragent\'] = $useragent;\n	else\n		$variable[\'adi_mobile_useragent\'] = $_SERVER[\'HTTP_USER_AGENT\'];\n\n	// time for your tablets\n	$adi_mobile[\'isTablet\'] = FALSE;\n	foreach ($adi_mobile_tablet_list as $tablet)\n		$adi_mobile[\'isTablet\'] = $adi_mobile[\'isTablet\'] || $adi_mobile[\'is\'.$tablet];\n\n	// by default, tablets are not \"mobiles\"\n	if ($adi_mobile[\'isTablet\'])\n			$adi_mobile[\'isMobile\'] = FALSE;\n\n	// mobile wannabees\n	foreach ($include_list as $device) {\n		$this_device = \'is\'.ucfirst(strtolower($device));\n		if (isset($adi_mobile[$this_device]))\n			if ($adi_mobile[$this_device])\n				$adi_mobile[\'isMobile\'] = TRUE;\n	}\n\n	// make a note of what\'s been detected - in a TXP variable\n	if ($adi_mobile[\'isTablet\'])\n		$variable[\'adi_mobile_types\'] = \'tablet\'; // added here coz there\'s no \'isTablet\' in Mobile Detect\n	else\n		$variable[\'adi_mobile_types\'] = \'\';\n	foreach ($adi_mobile_list as $device) {\n		if ($adi_mobile[\'is\'.$device])\n			$variable[\'adi_mobile_types\'] .= \' \'.strtolower($device);\n	}\n	$variable[\'adi_mobile_types\'] = trim($variable[\'adi_mobile_types\']);\n\n	if ($debug) adi_mobile_debug($atts);\n}\n\nfunction adi_if_mobile($atts, $thing) {\n// <txp:adi_if_mobile /> tag\n// 		default 		- if/else mobile (any mobile)\n// 		type=\"\"			- if/else specific mobile type\n// 		disabled=\"1\"	- if/else mobile mode\n// 		not=\"1\"			- negate if/else\n\n	global $adi_mobile,$adi_mobile_list,$adi_mobile_tablet_list,$default_width,$default_orientation;\n\n	extract(lAtts(array(\n		\'type\'			=> \'Mobile\',	// comma separated list of types\n		\'width\'			=> \'\',			// i.e. \"narrow\", \"wide\" or exact number of pixels\n		\'orientation\'	=> \'\',\n		\'disabled\'		=> \'0\',			// test if mobile mode disabled\n		\'not\'			=> \'0\'			// negate condition\n	),$atts));\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	$condition = FALSE; // default condition result\n\n	if ($width != \'\') { // viewport width conditional\n		if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) { // don\'t want to generate ANY viewport width opinions if not a mobile or mobile mode disabled\n			$detected_width = $adi_mobile[\'width\'];\n			if ($detected_width == \'?\') $detected_width = $adi_mobile[$default_width];\n			switch ($width) {\n				case \'narrow\':\n					if ($detected_width == \'?\') break; // i.e. probably \'?\'\n					if ($detected_width <= $adi_mobile[\'narrow\']) $condition = TRUE;\n					break;\n				case \'wide\':\n					if ($detected_width == \'?\') break; // i.e. probably \'?\'\n					if ($detected_width >= $adi_mobile[\'wide\']) $condition = TRUE;\n					break;\n				default: // test exact width (incl. \'?\')\n					if ($detected_width == $width) $condition = TRUE;\n			}\n		}\n		else\n			return;\n	}\n	else if ($orientation != \'\') { // viewport orientation conditional\n		if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) { // don\'t want to generate ANY viewport orientation opinions if not a mobile or mobile mode disabled\n			$detected_orientation = $adi_mobile[\'orientation\'];\n			if ($detected_orientation == \'?\') $detected_orientation = $default_orientation;\n			switch ($orientation) {\n				case \'p\':\n				case \'portrait\':\n				case \'v\':\n				case \'vertical\':\n					if (($detected_orientation == \'0\') || ($detected_orientation == \'180\')) $condition = TRUE;\n					break;\n				case \'l\':\n				case \'h\':\n				case \'landscape\':\n				case \'horizontal\':\n					if (($detected_orientation == \'90\') || ($detected_orientation == \'-90\')) $condition = TRUE;\n					break;\n				default: // test exact orientation (incl. \'?\')\n					if ($detected_orientation == $orientation) $condition = TRUE;\n			}\n		}\n		else\n			return;\n	}\n	else if ($disabled) // mode conditional\n		if ($adi_mobile[\'disabled\'])\n			$condition = TRUE;\n		else\n			$condition = FALSE;\n	else // type conditional\n		if ($adi_mobile[\'disabled\']) // mobile mode disabled, so fail condition regardless\n			$condition = FALSE;\n		else { // check specific type(s)\n			$type_list = explode(\',\',$type);\n			foreach ($type_list as $type ) { // scan user supplied types\n				$type = ucfirst(strtolower(trim($type))); // convert tYPe to Type\n				if (isset($adi_mobile[\'is\'.$type])) // will find normal adi_mobile types as well as user-made-up ones\n					$condition = $condition || $adi_mobile[\'is\'.$type];\n			}\n		}\n\n	if ($not) $condition = $condition ^ TRUE; // negate result, if required\n\n	return parse(EvalElse($thing,$condition));\n\n}\n\nfunction adi_if_not_mobile($atts,$thing) {\n	$atts[\'not\'] = \'1\';\n	return adi_if_mobile($atts,$thing);\n}\n\nfunction adi_mobile_script() {\n// jQuery - to pass viewport width via URL: ?viewport=xyz\n// if javascript switched on in middle of session then have to restart browser\n\n	global $adi_mobile;\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	if ($adi_mobile[\'isMobile\'] && !$adi_mobile[\'disabled\']) // only think about this if you\'re a mobile and mobile mode not disabled\n		// fire up jQuery and redirect page if (viewport=xyz not in URL AND viewport cookie not set) OR (viewport_reset in URL but viewport=xyz isn\'t)\n		if ((!isset($_GET[\'viewport\']) && !isset($_COOKIE[\'adi_mobile_viewport\'])) || (isset($_GET[\'viewport_reset\']) && !isset($_GET[\'viewport\']))) {\n\n			$javascript = <<<END_SCRIPT\n<script type=\"text/javascript\">\n$(function() {\n	// NEED TO COPE WITH HASH ANCHORS TOO (window.location.hash)???\n	if (window.location.search == \'\') // check if there\'s any URL vars //SET UP & OR ? PREAMBLE IN A VAR\n		preamble = \"?\";\n	else\n		preamble = \"&\";\n	window.location.replace(window.location.href+preamble+\"viewport=\"+$(window).width()+\",\"+$(window).height()+\",\"+window.orientation);\n});\n</script>\nEND_SCRIPT;\n\n			return $javascript;\n		}\n}\n\nfunction adi_mobile_viewport($atts) {\n// <adi_mobile_viewport /> tag\n\n	global $variable, $adi_mobile;\n\n	extract(lAtts(array(\n		\'width\'			=> \'\',	// user specified width\n	),$atts));\n\n	if (!isset($adi_mobile)) return; // decamp quietly if global not set (i.e. adi_mobile tag not called)\n\n	if ($width != \'\') $adi_mobile[\'width\'] = $width;\n\n	// do TXP variables again\n	$variable[\'adi_mobile_width\'] = $adi_mobile[\'width\'];\n	$variable[\'adi_mobile_height\'] = $adi_mobile[\'height\'];\n	$variable[\'adi_mobile_orientation\'] = $adi_mobile[\'orientation\'];\n	$variable[\'adi_mobile_viewport\'] = $adi_mobile[\'width\'].\',\'.$adi_mobile[\'height\'].\',\'.$adi_mobile[\'orientation\'];\n}\n\nfunction adi_mobile_debug($atts) {\n	global $prefs,$variable,$adi_mobile;\n\n	echo \"SUPPLIED ATTRIBUTES<br/>\";\n	dmp($atts);\n	echo \"VERSIONS<br/>\";\n	echo \'<pre>\';\n	$version = safe_field(\"version\", \"txp_plugin\", \"name=\'adi_mobile\'\");\n	$status = safe_field(\"status\", \"txp_plugin\", \"name=\'adi_mobile\'\");\n	echo \'adi_mobile: \'.(empty($version) ? \'not installed\':$version.($status?\' (active)\' : \' (not active)\')).\'<br/>\';\n	echo \'TXP: \'.$prefs[\'version\'].\'<br/>\';\n	echo \'PHP: \'.phpversion().\'<br/>\';\n	echo \'</pre>\';\n	echo \'USER AGENT<br/>\';\n	echo \'<pre>\';\n	echo $variable[\'adi_mobile_useragent\'].\'<br/>\';\n	echo \'</pre>\';\n	echo \"GET VARS:<br/>\";\n	dmp($_GET);\n	echo \"ADI_MOBILE GLOBALS\";\n	dmp($adi_mobile);\n	echo \"COOKIES\";\n	echo \'<pre>\';\n	if (isset($_COOKIE[\'adi_mobile_disabled\']))\n		echo \'adi_mobile_disabled = \'.$_COOKIE[\'adi_mobile_disabled\'];\n	else\n		echo \'adi_mobile_disabled cookie not set (yet?)\';\n	echo \'<br/>\';\n	if (isset($_COOKIE[\'adi_mobile_viewport\']))\n		echo \'adi_mobile_viewport = \'.$_COOKIE[\'adi_mobile_viewport\'];\n	else\n		echo \'adi_mobile_viewport cookie not set (yet?)\';\n	echo \'</pre>\';\n}\n', '4dff242bff1583a0740ce8d34dc3bc7f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_page_tab', 1, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.5', 'Enhanced Page Tab', '<h1><strong>adi_page_tab</strong> &#8211; Enhanced Page Tab</h1>\n\n<p>This plugin gives you an enhancement to the standard <span class=\"caps\">TXP</span> Page templates tab.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>After installing &amp; activating the plugin, go to the Pages tab and you&#8217;ll find a new section popup list.</p>\n\n<p>This will give you:</p>\n\n<ul>\n	<li>an indication which sections are using the current page template (i.e. the ones that are greyed out)</li>\n	<li>the ability to assign the current page to a new section (select one of the non-greyed out sections &amp; click Update)</li>\n</ul>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on ‘Install textpack’.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <em><span class=\"caps\">TXP</span> Admin &#8211; Language tab</em>.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_page_tab\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36870\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_page_tab\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_page_tab - Enhanced Page Tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.5		- fixes for TXP 4.6\n	0.4		- TXP 4.5-ified\n			- TXP 4.5+ only\n			- code tidy up\n	0.3		- fix to ignore new page name if \"copy page as\" filled in and Save button clicked (or Enter key used)\n	0.2		- fixed to cope with page create, delete & copy properly\n			- \"Update\" button now has its own language string (thanks Pat64)\n	0.1		- initial release\n\n*/\n\nglobal $adi_page_tab_debug;\n\nif (@txpinterface == \'admin\') {\n\n	$adi_page_tab_debug = 0;\n\n	// it\'s a modern world\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_page_tab_init();\n}\n\nfunction adi_page_tab_init() {\n	# it\'s a setup\n	global $event,$step,$prefs,$textarray,$adi_page_tab_debug,$adi_page_tab_message,$adi_page_tab_url;\n\n	// the new world order?\n	$adi_page_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// plugin options\n	$adi_page_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_page_tab\',$adi_page_tab_debug);\n	if ($adi_page_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_page_tab\',\'1,2,6\');\n		register_callback(\'adi_page_tab_options\',\'plugin_prefs.adi_page_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_page_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_page_tab_options\',\'adi_page_tab options\');\n		register_callback(\'adi_page_tab_options\',\'adi_page_tab_options\');\n	}\n\n	// Textpack\n	$adi_page_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_page_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_matrix_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_page_tab_gtxt = array(\n		\'adi_use_page_section\' => \'Use <strong>{name}</strong> for section\',\n		\'adi_section_updated\' => \'Section <strong>{name}</strong> updated\',\n		\'adi_section_update_fail\' => \'Section <strong>{name}</strong> not updated\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_update\' => \'Update\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_page_tab_gtxt;\n\n	if ($event == \"page\") { // go, go, go\n		// style\n		register_callback(\'adi_page_tab_style\',\'admin_side\',\'head_end\');\n		// some action\n		$adi_page_tab_message = array();\n		$assign = gps(\'adi_page_tab_step\');\n		if ($assign) { // someone has hit the section update button\n			$this_page = gps(\'name\'); // page name\n			if ($adi_page_tab_debug)\n				echo \'This page=\'.$this_page;\n			$new_section = gps(\'adi_page_tab_new_section\'); // section name\n			if (!empty($new_section)) { // update database\n				if ($adi_page_tab_debug)\n					echo \', assigned to \'.$new_section;\n				$res = safe_update(\'txp_section\',\'page=\"\'.doSlash($this_page).\'\"\',\'name=\"\'.$new_section.\'\"\',$adi_page_tab_debug);\n				if ($res) {\n					update_lastmod();\n					$adi_page_tab_message[\'text\'] = gTxt(\'adi_section_updated\',array(\'{name}\' => $new_section));\n					$adi_page_tab_message[\'status\'] = \'success\';\n				}\n				else {\n					$adi_page_tab_message[\'text\'] = gTxt(\'adi_section_update_fail\',array(\'{name}\' => $new_section));\n					$adi_page_tab_message[\'status\'] = \'error\';\n				}\n			}\n		}\n		// inject markup\n		if ($adi_page_tab_txp460)\n			register_callback(\'adi_page_tab_markup_46\',\'admin_side\',\'main_content_end\'); // markup in the footer, shifted by jQuery\n		else\n			register_callback(\'adi_page_tab_sections\',\'page\',\'\',1);	// inject markup directly into Pages tab\n\n		// inject message markup into Pages tab\n		register_callback(\'adi_page_tab_messagepane\',\'admin_side\',\'footer\');\n\n		// script\n		register_callback(\'adi_page_tab_script\',\'admin_side\',\'head_end\');\n	}\n}\n\nfunction adi_page_tab_style() {\n// practical & stylish\n	echo\n		\'<style type=\"text/css\">\n			#adi_page_tab_sections { margin-top:1em }\n			#adi_page_tab_sections label { width:auto; float:left; margin:0 }\n			#adi_page_tab_sections select { float:left; margin:0 0 0 0.5em }\n			#adi_page_tab_sections input.smallerbox { margin:0 0 0 0.5em }\n			#adi_page_tab_sections .adi_page_tab_disabled { color:gray } /* explicitly grey out disabled options (for IE6/7) - still no good for iOS Safari NOW REDUNDANT? */\n			/* TXP 4.6 */\n			p.txp-actions #adi_page_tab_sections label { float:none }\n			p.txp-actions #adi_page_tab_sections select { float:none; margin:0.5em 0.5em 0.5em 0 }\n			p.txp-actions #adi_page_tab_sections input.smallerbox { margin:0 }\n			@media (max-width: 47em) {\n				#adi_page_tab_sections label { float:none; display:block }\n				#adi_page_tab_sections select { float:none; margin:0.5em 0 1em }\n				#adi_page_tab_sections input.smallerbox { display:block; margin:0 }\n			}\n		</style>\';\n}\n\nfunction adi_page_tab_script() {\n// scripticle\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_page_tab\n		$(\"form#adi_page_tab\").insertAfter($(\"form#page_form\")); // shift page tag markup from footer\n// 		$(\"p.txp-actions\").append($(\"form#adi_page_tab\")); // shift page tag markup from footer\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_page_tab_options($event,$step) {\n// plugin options page\n	global $adi_page_tab_debug,$adi_page_tab_url,$textarray;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_page_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n\n	// generate page\n	pagetop(\'adi_page_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_page_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_page_tab_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_page_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_page_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_page_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_page_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n\nfunction adi_page_tab_section_popup($this_page) {\n// get section information from txp_section\n	$rs = safe_query(\"SELECT name,page FROM \".safe_pfx(\'txp_section\').\" ORDER BY name\");\n	$out = \'\';\n	while ($a = nextRow($rs)) {\n		extract($a); // set \'name\',\'title\',\'parent\' etc in $a\n		$disabled = \'\';\n		if ($page == $this_page)\n			$disabled = \' disabled=\"disabled\" class=\"adi_page_tab_disabled\"\';\n		$out .= tag($name,\'option\',$disabled);\n	}\n	return tag(\'<option></option>\'.$out,\'select\',\' id=\"adi_page_tab_new_section\" name=\"adi_page_tab_new_section\"\');\n}\n\nfunction adi_page_tab_sections() {\n// the old-fashioned magic that gets adi_page_tab\'s section popup into the standard Page tab\n	ob_start(\'adi_page_tab_inject\');\n}\n\nfunction adi_page_tab_inject($buffer) {\n// another bit of magic that gets adi_page_tab\'s section popup into the standard Page tab\n	global $DB;\n\n	if(!isset($DB)) $DB = new db;\n	$pattern = \'#id=\"page_form\".*</form>#sU\';\n	$insert = \'adi_page_tab_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n	return $buffer;\n}\n\nfunction adi_page_tab_markup($matches) {\n// generate markup for adi_page_tab section list\n	global $step;\n\n	$this_page = gps(\'name\');\n	$newname = gps(\'newname\');\n	$copy = gps(\'copy\');\n	$savenew = gps(\'savenew\');\n\n	if ($step == \"page_new\") // don\'t output anything\n		return $matches[0];\n	if ($savenew || $copy) // new page or page copy\n		$this_page = $newname;\n	if ($step == \'page_delete\') // default to default\n		$this_page = \'default\';\n	if (empty($this_page)) // default visit to Page tab\n		$this_page = \'default\';\n\n	return $matches[0]\n		.form(\n			tag(\n				tag(\n					tag(gTxt(\'adi_use_page_section\',array(\'{name}\' => $this_page)),\'label\',\' for=\"adi_page_tab_new_section\"\')\n					.adi_page_tab_section_popup($this_page,FALSE)\n					.fInput(\"submit\", \"use\",gTxt(\'adi_update\'), \"smallerbox\")\n					.hInput(\'adi_page_tab_step\',\'update\') // secret step\n					.hInput(\'name\',$this_page) // provide page name back to Page tab\n					.eInput(\"page\")\n				,\'p\')\n			,\'div\'\n			,\' id=\"adi_page_tab_sections\"\')\n		);\n}\n\nfunction adi_page_tab_markup_46() {\n// generate markup for adi_page_tab section list\n	global $step;\n\n	$this_page = gps(\'name\');\n	$newname = gps(\'newname\'); // set on page save (incl. rename) & duplicate\n	$savenew = gps(\'savenew\');\n\n	if ($step == \"page_new\") // don\'t output anything\n		return;\n	if (($step == \"page_save\") || $savenew) // page save/rename/copy or new\n		$this_page = $newname;\n	if (empty($this_page) || ($step == \'page_delete\')) // use \"default\" if default visit to Page tab or page delete\n		$this_page = \'default\';\n\n	echo form(\n		tag(\n			tag(\n				tag(gTxt(\'adi_use_page_section\',array(\'{name}\' => $this_page)),\'label\',\' for=\"adi_page_tab_new_section\"\')\n				.adi_page_tab_section_popup($this_page,FALSE)\n				.fInput(\"submit\", \"use\",gTxt(\'adi_update\'), \"smallerbox\")\n				.hInput(\'adi_page_tab_step\',\'update\') // secret step\n				.hInput(\'name\',$this_page) // provide page name back to Page tab\n				.eInput(\"page\")\n			,\'p\')\n		,\'div\'\n		,\' id=\"adi_page_tab_sections\"\')\n		,\'\',\'\',\'post\',\'\',\'\',\'adi_page_tab\'\n	);\n\n}\n\nfunction adi_page_tab_messagepane() {\n// generate messagepane & message\n	global $adi_page_tab_message;\n\n	$adi_page_tab_txp450 = (version_compare(txp_version,\'4.4.1\',\'>\'));\n\n	if ($adi_page_tab_message) {\n		if ($adi_page_tab_txp450) {\n			$html = \'<span id=\"message\" class=\"messageflash \'.$adi_page_tab_message[\'status\'].\'\">\'.$adi_page_tab_message[\'text\'].\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n			$message_script = <<<END_SCRIPT\n				<script type=\"text/javascript\">\n				<!--\n					$(\'#messagepane\').append(\'$html\');\n					$(\"#messagepane\").css({\n						left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n					});\n					$(window).resize();\n				// -->\n				</script>\nEND_SCRIPT;\n		}\n		else { // pre-TXP 4.5.0\n			$html = $adi_page_tab_message[\'text\'];\n			$message_script = <<<END_SCRIPT\n				<script type=\"text/javascript\">\n				<!--\n				$(\'#messagepane\').append(\'$html\');\nEND_SCRIPT;\n			if ($adi_page_tab_message[\'status\'] != \'success\') { // it\'s a warning or error, so flash it\n				$message_script .= <<<END_SCRIPT\n					$(\'#messagepane\').fadeOut(800).fadeIn(800);\n					$(\'#messagepane\').fadeOut(800).fadeIn(800);\nEND_SCRIPT;\n			}\n			$message_script .= <<<END_SCRIPT\n				// -->\n				</script>\nEND_SCRIPT;\n		}\n		echo $message_script;\n	}\n}\n', '\n/*\n	adi_page_tab - Enhanced Page Tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.5		- fixes for TXP 4.6\n	0.4		- TXP 4.5-ified\n			- TXP 4.5+ only\n			- code tidy up\n	0.3		- fix to ignore new page name if \"copy page as\" filled in and Save button clicked (or Enter key used)\n	0.2		- fixed to cope with page create, delete & copy properly\n			- \"Update\" button now has its own language string (thanks Pat64)\n	0.1		- initial release\n\n*/\n\nglobal $adi_page_tab_debug;\n\nif (@txpinterface == \'admin\') {\n\n	$adi_page_tab_debug = 0;\n\n	// it\'s a modern world\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_page_tab_init();\n}\n\nfunction adi_page_tab_init() {\n	# it\'s a setup\n	global $event,$step,$prefs,$textarray,$adi_page_tab_debug,$adi_page_tab_message,$adi_page_tab_url;\n\n	// the new world order?\n	$adi_page_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// plugin options\n	$adi_page_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_page_tab\',$adi_page_tab_debug);\n	if ($adi_page_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_page_tab\',\'1,2,6\');\n		register_callback(\'adi_page_tab_options\',\'plugin_prefs.adi_page_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_page_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_page_tab_options\',\'adi_page_tab options\');\n		register_callback(\'adi_page_tab_options\',\'adi_page_tab_options\');\n	}\n\n	// Textpack\n	$adi_page_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_page_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_matrix_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_page_tab_gtxt = array(\n		\'adi_use_page_section\' => \'Use <strong>{name}</strong> for section\',\n		\'adi_section_updated\' => \'Section <strong>{name}</strong> updated\',\n		\'adi_section_update_fail\' => \'Section <strong>{name}</strong> not updated\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_update\' => \'Update\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_page_tab_gtxt;\n\n	if ($event == \"page\") { // go, go, go\n		// style\n		register_callback(\'adi_page_tab_style\',\'admin_side\',\'head_end\');\n		// some action\n		$adi_page_tab_message = array();\n		$assign = gps(\'adi_page_tab_step\');\n		if ($assign) { // someone has hit the section update button\n			$this_page = gps(\'name\'); // page name\n			if ($adi_page_tab_debug)\n				echo \'This page=\'.$this_page;\n			$new_section = gps(\'adi_page_tab_new_section\'); // section name\n			if (!empty($new_section)) { // update database\n				if ($adi_page_tab_debug)\n					echo \', assigned to \'.$new_section;\n				$res = safe_update(\'txp_section\',\'page=\"\'.doSlash($this_page).\'\"\',\'name=\"\'.$new_section.\'\"\',$adi_page_tab_debug);\n				if ($res) {\n					update_lastmod();\n					$adi_page_tab_message[\'text\'] = gTxt(\'adi_section_updated\',array(\'{name}\' => $new_section));\n					$adi_page_tab_message[\'status\'] = \'success\';\n				}\n				else {\n					$adi_page_tab_message[\'text\'] = gTxt(\'adi_section_update_fail\',array(\'{name}\' => $new_section));\n					$adi_page_tab_message[\'status\'] = \'error\';\n				}\n			}\n		}\n		// inject markup\n		if ($adi_page_tab_txp460)\n			register_callback(\'adi_page_tab_markup_46\',\'admin_side\',\'main_content_end\'); // markup in the footer, shifted by jQuery\n		else\n			register_callback(\'adi_page_tab_sections\',\'page\',\'\',1);	// inject markup directly into Pages tab\n\n		// inject message markup into Pages tab\n		register_callback(\'adi_page_tab_messagepane\',\'admin_side\',\'footer\');\n\n		// script\n		register_callback(\'adi_page_tab_script\',\'admin_side\',\'head_end\');\n	}\n}\n\nfunction adi_page_tab_style() {\n// practical & stylish\n	echo\n		\'<style type=\"text/css\">\n			#adi_page_tab_sections { margin-top:1em }\n			#adi_page_tab_sections label { width:auto; float:left; margin:0 }\n			#adi_page_tab_sections select { float:left; margin:0 0 0 0.5em }\n			#adi_page_tab_sections input.smallerbox { margin:0 0 0 0.5em }\n			#adi_page_tab_sections .adi_page_tab_disabled { color:gray } /* explicitly grey out disabled options (for IE6/7) - still no good for iOS Safari NOW REDUNDANT? */\n			/* TXP 4.6 */\n			p.txp-actions #adi_page_tab_sections label { float:none }\n			p.txp-actions #adi_page_tab_sections select { float:none; margin:0.5em 0.5em 0.5em 0 }\n			p.txp-actions #adi_page_tab_sections input.smallerbox { margin:0 }\n			@media (max-width: 47em) {\n				#adi_page_tab_sections label { float:none; display:block }\n				#adi_page_tab_sections select { float:none; margin:0.5em 0 1em }\n				#adi_page_tab_sections input.smallerbox { display:block; margin:0 }\n			}\n		</style>\';\n}\n\nfunction adi_page_tab_script() {\n// scripticle\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_page_tab\n		$(\"form#adi_page_tab\").insertAfter($(\"form#page_form\")); // shift page tag markup from footer\n// 		$(\"p.txp-actions\").append($(\"form#adi_page_tab\")); // shift page tag markup from footer\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_page_tab_options($event,$step) {\n// plugin options page\n	global $adi_page_tab_debug,$adi_page_tab_url,$textarray;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_page_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n\n	// generate page\n	pagetop(\'adi_page_tab \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_page_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_page_tab_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_page_tab_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_page_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_page_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_page_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n\nfunction adi_page_tab_section_popup($this_page) {\n// get section information from txp_section\n	$rs = safe_query(\"SELECT name,page FROM \".safe_pfx(\'txp_section\').\" ORDER BY name\");\n	$out = \'\';\n	while ($a = nextRow($rs)) {\n		extract($a); // set \'name\',\'title\',\'parent\' etc in $a\n		$disabled = \'\';\n		if ($page == $this_page)\n			$disabled = \' disabled=\"disabled\" class=\"adi_page_tab_disabled\"\';\n		$out .= tag($name,\'option\',$disabled);\n	}\n	return tag(\'<option></option>\'.$out,\'select\',\' id=\"adi_page_tab_new_section\" name=\"adi_page_tab_new_section\"\');\n}\n\nfunction adi_page_tab_sections() {\n// the old-fashioned magic that gets adi_page_tab\'s section popup into the standard Page tab\n	ob_start(\'adi_page_tab_inject\');\n}\n\nfunction adi_page_tab_inject($buffer) {\n// another bit of magic that gets adi_page_tab\'s section popup into the standard Page tab\n	global $DB;\n\n	if(!isset($DB)) $DB = new db;\n	$pattern = \'#id=\"page_form\".*</form>#sU\';\n	$insert = \'adi_page_tab_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer);\n	return $buffer;\n}\n\nfunction adi_page_tab_markup($matches) {\n// generate markup for adi_page_tab section list\n	global $step;\n\n	$this_page = gps(\'name\');\n	$newname = gps(\'newname\');\n	$copy = gps(\'copy\');\n	$savenew = gps(\'savenew\');\n\n	if ($step == \"page_new\") // don\'t output anything\n		return $matches[0];\n	if ($savenew || $copy) // new page or page copy\n		$this_page = $newname;\n	if ($step == \'page_delete\') // default to default\n		$this_page = \'default\';\n	if (empty($this_page)) // default visit to Page tab\n		$this_page = \'default\';\n\n	return $matches[0]\n		.form(\n			tag(\n				tag(\n					tag(gTxt(\'adi_use_page_section\',array(\'{name}\' => $this_page)),\'label\',\' for=\"adi_page_tab_new_section\"\')\n					.adi_page_tab_section_popup($this_page,FALSE)\n					.fInput(\"submit\", \"use\",gTxt(\'adi_update\'), \"smallerbox\")\n					.hInput(\'adi_page_tab_step\',\'update\') // secret step\n					.hInput(\'name\',$this_page) // provide page name back to Page tab\n					.eInput(\"page\")\n				,\'p\')\n			,\'div\'\n			,\' id=\"adi_page_tab_sections\"\')\n		);\n}\n\nfunction adi_page_tab_markup_46() {\n// generate markup for adi_page_tab section list\n	global $step;\n\n	$this_page = gps(\'name\');\n	$newname = gps(\'newname\'); // set on page save (incl. rename) & duplicate\n	$savenew = gps(\'savenew\');\n\n	if ($step == \"page_new\") // don\'t output anything\n		return;\n	if (($step == \"page_save\") || $savenew) // page save/rename/copy or new\n		$this_page = $newname;\n	if (empty($this_page) || ($step == \'page_delete\')) // use \"default\" if default visit to Page tab or page delete\n		$this_page = \'default\';\n\n	echo form(\n		tag(\n			tag(\n				tag(gTxt(\'adi_use_page_section\',array(\'{name}\' => $this_page)),\'label\',\' for=\"adi_page_tab_new_section\"\')\n				.adi_page_tab_section_popup($this_page,FALSE)\n				.fInput(\"submit\", \"use\",gTxt(\'adi_update\'), \"smallerbox\")\n				.hInput(\'adi_page_tab_step\',\'update\') // secret step\n				.hInput(\'name\',$this_page) // provide page name back to Page tab\n				.eInput(\"page\")\n			,\'p\')\n		,\'div\'\n		,\' id=\"adi_page_tab_sections\"\')\n		,\'\',\'\',\'post\',\'\',\'\',\'adi_page_tab\'\n	);\n\n}\n\nfunction adi_page_tab_messagepane() {\n// generate messagepane & message\n	global $adi_page_tab_message;\n\n	$adi_page_tab_txp450 = (version_compare(txp_version,\'4.4.1\',\'>\'));\n\n	if ($adi_page_tab_message) {\n		if ($adi_page_tab_txp450) {\n			$html = \'<span id=\"message\" class=\"messageflash \'.$adi_page_tab_message[\'status\'].\'\">\'.$adi_page_tab_message[\'text\'].\' <a href=\"#close\" class=\"close\">&times;</a></span>\';\n			$message_script = <<<END_SCRIPT\n				<script type=\"text/javascript\">\n				<!--\n					$(\'#messagepane\').append(\'$html\');\n					$(\"#messagepane\").css({\n						left: ($(window).width() - $(\"#messagepane\").outerWidth()) / 2\n					});\n					$(window).resize();\n				// -->\n				</script>\nEND_SCRIPT;\n		}\n		else { // pre-TXP 4.5.0\n			$html = $adi_page_tab_message[\'text\'];\n			$message_script = <<<END_SCRIPT\n				<script type=\"text/javascript\">\n				<!--\n				$(\'#messagepane\').append(\'$html\');\nEND_SCRIPT;\n			if ($adi_page_tab_message[\'status\'] != \'success\') { // it\'s a warning or error, so flash it\n				$message_script .= <<<END_SCRIPT\n					$(\'#messagepane\').fadeOut(800).fadeIn(800);\n					$(\'#messagepane\').fadeOut(800).fadeIn(800);\nEND_SCRIPT;\n			}\n			$message_script .= <<<END_SCRIPT\n				// -->\n				</script>\nEND_SCRIPT;\n		}\n		echo $message_script;\n	}\n}\n', '4dc096917ecef41fa10cb9d7aa3e5239', '', '', 3, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_prefs', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3.2', 'Preference Sets', '<h1><strong>adi_prefs</strong> &#8211; Preference Sets</h1>\n\n<p>This plugin gives you a new tab under Admin where you can define different sets of preference values and apply them at will.</p>\n\n<h2><strong>The Preference Sets tab</strong></h2>\n\n<p>Users with sufficient privileges will see the <em>Preference Sets</em> tab, under Admin. This gives you a view of the different sets of preference values:</p>\n\n<ul>\n	<li>Current</li>\n	<li>Default</li>\n	<li>Local</li>\n	<li>Staging</li>\n	<li>Testing</li>\n	<li>Live</li>\n</ul>\n\n<p>The <strong>Current</strong> set is the actual preference values currently in use by Textpattern on your site.</p>\n\n<p>The <strong>Default</strong> set is a snapshot of the preference values taken when <strong>adi_prefs</strong> was installed.  This can be seen as a view of how things were when you started, e.g. a “fallback” position.</p>\n\n<p>The <strong>Local</strong>, <strong>Staging</strong>, <strong>Testing</strong> &amp; <strong>Live</strong> sets may be modified.  The list of preferences of interest may be changed (i.e. added to or subtracted from) and the individual preference values may be altered.</p>\n\n<p>The titles of the sets may be changed to whatever you like.</p>\n\n<h2><strong>Adding or removing preferences</strong></h2>\n\n<p>You can add preferences to the sets by selecting them from the popup list and clicking <strong>Add</strong>. The initial value of a new preference is taken from the current value.</p>\n\n<p>You can remove preferences from the sets by clicking on the ‘X’ button. All set values of deleted preferences will be forgotten. Deleting a preference does not affect it&#8217;s value in the actual website.</p>\n\n<h2><strong>Applying the preference sets</strong></h2>\n\n<p>You can individually apply the <strong>Default</strong>, <strong>Local</strong>, <strong>Staging</strong>, <strong>Testing</strong> &amp; <strong>Live</strong> sets &#8211; i.e. the values contained in these sets are then used on your website for real.</p>\n\n<h2><strong>A word of caution</strong></h2>\n\n<p>No validation of “free-format” preference values is performed, so ensure that the values you enter are valid for use &#8211; otherwise you might break your site!  If things go pear-shaped try applying the <strong>Default</strong> set.</p>\n\n<h2><strong>Uninstalling</strong></h2>\n\n<p>Simply delete the plugin in the Plugins tab.  No preferences will be harmed in the process.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s options page and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Updates and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_prefs\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=34532\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_prefs\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_prefs - Preference Sets\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3.2	- minor layout fixes\n			- TXP 4.5+ only\n			- tested in 4.6\n	0.3.1	- fixed issue when upgrading from version 0.1\n			- fixed Textpack download\n	0.3		- TXP 4.5-ified\n			- moved install/uninstall to plugin options\n			- Textpack added\n			- tooltips thrown in the tip\n			- code tidy up\n			- now uses lifecycle events\n	0.2.1	- fixed: injudicious use of global variables, causing conflict with adi_matrix\n	0.2		- enhancement: added new set \"Testing\" (for Maniqui)\n			- enhancement: preference set visibility\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $adi_prefs_debug;\n\n	$adi_prefs_debug = 0; // display admin debug info\n\n	// to infinity & beyond ...\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_prefs_init();\n\n}\n\nfunction adi_prefs_init() {\n// initial setup\n	global $event,$prefs,$adi_prefs_debug,$adi_prefs_gtxt,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_plugin_status,$adi_prefs_default_list,$adi_prefs_list,$adi_prefs_url;\n\n	$adi_prefs_privs = \'1,6\';\n\n	// default pref set titles\n	$adi_prefs_default_set_list = array(\n		\'current\' => array(\'title\' => \'Current\'),\n		\'def\' => array(\'title\' => \'Default\'),\n		\'dev\' => array(\'title\' => \'Local\'),\n		\'demo\' => array(\'title\' => \'Staging\'),\n		\'test\' => array(\'title\' => \'Testing\'),\n		\'live\' => array(\'title\' => \'Live\'),\n	);\n\n	// default list of prefs\n	$adi_prefs_default_list = \'siteurl,production_status,file_base_path,tempdir,rss_dbbk_path,rss_dbbk_mysql,rss_dbbk_dump,xxx\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_prefs_gtxt = array(\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_prefs\' => \'Preference Sets\',\n		\'adi_prefs_add\' => \'Add Preference\',\n		\'adi_prefs_no_set_match\' => \'No sets match the current preferences\',\n		\'adi_prefs_multiple_set_match\' => \'The <b>{set}</b> sets match the current preferences\',\n		\'adi_prefs_one_set_match\' => \'The <b>{set}</b> set matches the current preferences\',\n		\'adi_prefs_pref_add_fail\' => \'Unable to add {pref} preference\',\n		\'adi_prefs_pref_added\' => \'{pref} preference added\',\n		\'adi_prefs_pref_not_selected\' => \'No new preference selected\',\n		\'adi_prefs_pref_remove_fail\' => \'Unable to remove {pref} preference\',\n		\'adi_prefs_pref_removed\' => \'{pref} preference removed\',\n		\'adi_prefs_remove_pref\' => \'Remove preference?\',\n		\'adi_prefs_remove_pref_query\' => \'Remove {pref} from preference sets?\',\n		\'adi_prefs_set_applied\' => \'{set} preference set applied\',\n		\'adi_prefs_set_apply_fail\' => \'Unable to apply {set} preference set\',\n		\'adi_prefs_set_not_selected\' => \'No preference set selected\',\n		\'adi_prefs_set_titles_updated\' => \'Preference set titles updated\',\n		\'adi_prefs_set_titles_update_fail\' => \'Unable to update preference set titles\',\n		\'adi_prefs_set_visibility_updated\' => \'Preference set visibility updated\',\n		\'adi_prefs_set_visibility_update_fail\' => \'Unable to update preference set visibility\',\n		\'adi_prefs_sets_update_fail\' => \'Unable to update preference sets\',\n		\'adi_prefs_sets_updated\' => \'Preference sets updated\',\n		\'adi_prefs_titles\' => \'Preference Set Titles\',\n		\'adi_prefs_upgraded\' => \'adi_prefs upgraded successfully\',\n		\'adi_prefs_upgrade_fail\' => \'Unable to upgrade adi_prefs\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_visibility\' => \'Visibility\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_prefs_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_prefs\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_prefs_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_prefs_lifecycle\',\'plugin_lifecycle.adi_prefs\');\n\n	// set the privilege levels\n	add_privs(\'adi_prefs_admin\',$adi_prefs_privs);\n\n	// add new tab under \'Admin\'\n	register_tab(\'admin\',\'adi_prefs_admin\',adi_prefs_gtxt(\'adi_prefs\'));\n	register_callback(\'adi_prefs_admin\',\'adi_prefs_admin\');	// adi_prefs admin tab\n\n	// style\n	if ($event == \'adi_prefs_admin\')\n		register_callback(\'adi_prefs_style\',\'admin_side\',\'head_end\');	// style for admin\n\n	// plugin options\n	$adi_prefs_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_prefs\',$adi_prefs_debug);\n	if ($adi_prefs_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_prefs\'); // defaults to priv \'1\' only\n		register_callback(\'adi_prefs_options\',\'plugin_prefs.adi_prefs\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_prefs_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_prefs_options\',\'adi_prefs options\');\n		register_callback(\'adi_prefs_options\',\'adi_prefs_options\');\n	}\n\n	// get current active prefs\n	$adi_prefs_current_prefs = get_prefs();\n\n	// get stored prefs\n	$adi_prefs_list = array();\n	if (adi_prefs_installed())\n		$adi_prefs_list = adi_prefs_get_list();\n}\n\nfunction adi_prefs_admin($event, $step) {\n// adi_prefs admin tab\n\n	global $adi_prefs_default_list,$adi_prefs_exclude_list,$adi_prefs_list,$adi_prefs_debug,$adi_prefs_set_list,$adi_prefs_default_set_list,$h,$adi_prefs_current_prefs,$plugin_status;\n\n	// list of prefs to exclude\n	$adi_prefs_exclude_list = \"dbupdatetime,gmtoffset,lastmod,prefs_id,timeoffset,timezone_key,version\";\n	$adi_prefs_exclude_list = explode(\',\',$adi_prefs_exclude_list);\n\n	// replace set titles with user defined, if any\n	foreach ($adi_prefs_default_set_list as $set_name => $value) {\n		$this_set_pref = doSlash(\'adi_prefs_title_\'.$set_name);\n		$val = safe_field(\'val\', \'txp_prefs\', \"name=\'$this_set_pref\'\");\n		if (empty($val)) // use default set title\n			$adi_prefs_set_list[$set_name][\'title\'] = $adi_prefs_default_set_list[$set_name][\'title\'];\n		else // read set title from database\n			$adi_prefs_set_list[$set_name][\'title\'] = $val;\n	}\n\n	$message = \'\';\n	$installed = adi_prefs_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_prefs_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_prefs_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else {\n			if ($adi_prefs_debug) {\n				echo \'$adi_prefs_list\'.n;\n				dmp($adi_prefs_list);\n			}\n		}\n	}\n	else\n		$message = array(adi_prefs_gtxt(\'adi_not_installed\'),E_ERROR);\n\n\n	$something = doSlash(doStripTags(gps(\'something\')));\n\n	if ($adi_prefs_debug) echo \'<p>Event=\'.$event.\', Step=\'.$step.\', Something=\'.$something.\'</p>\';\n\n	// stepping out\n	if ($step == \'update\') {\n		foreach ($adi_prefs_list as $pref_name => $value) {\n			$this_pref = doStripTags(ps($pref_name));\n			$result = TRUE;\n			foreach ($adi_prefs_set_list as $set_name => $value)\n				if (($set_name != \'def\') && ($set_name != \'current\')) { // ignore \'def\' & \'current\' because they\'re for display only\n					if ($adi_prefs_debug)\n						echo \'<p>\'.$pref_name.\'[\'.$set_name.\'] = \'.$this_pref[$set_name].\'</p>\';\n					$result = $result && adi_prefs_set($pref_name,$set_name,$this_pref[$set_name]);\n				}\n		}\n		if ($result)\n			$message = adi_prefs_gtxt(\'adi_prefs_sets_updated\');\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_sets_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'apply\') {\n		if ($something) { // i.e. set name\n			$result = TRUE;\n			foreach ($adi_prefs_list as $pref_name => $value) // apply \"set\" to the TXP database\n				$result = $result && safe_update(\'txp_prefs\',\"val = \'\".doSlash(adi_prefs_get($pref_name,$something)).\"\'\",\"name = \'\".doSlash($pref_name).\"\'\");\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_set_applied\',array(\'{set}\'=>$adi_prefs_set_list[$something][\'title\']));\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_set_apply_fail\',array(\'{set}\'=>$adi_prefs_set_list[$something][\'title\'])),E_ERROR);\n		}\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_set_not_selected\'),E_WARNING);\n	}\n	else if ($step == \'add\') {\n		if ($something) { // $something = new pref from list\n			if ($something == \'!none!\')\n				$message = array(adi_prefs_gtxt(\'adi_prefs_pref_not_selected\'),E_WARNING);\n			else {\n				$result = FALSE;\n				if (array_key_exists($something,$adi_prefs_current_prefs)) { // can\'t add what doesn\'t exist!\n					$result = TRUE;\n					$current_value = doSlash($adi_prefs_current_prefs[$something]);\n					foreach ($adi_prefs_default_set_list as $set_name => $set_title) { // all set to current value\n						$set_name = doSlash($set_name);\n						if ($set_name != \'current\') $result = $result && safe_upsert(\'adi_prefs\',\"$set_name=\'\".$current_value.\"\'\",\"name=\'$something\'\",$adi_prefs_debug);\n					}\n				}\n				if ($result)\n					$message = adi_prefs_gtxt(\'adi_prefs_pref_added\',array(\'{pref}\'=>$something));\n				else\n					$message = array(adi_prefs_gtxt(\'adi_prefs_pref_add_fail\',array(\'{pref}\'=>$something)),E_ERROR);\n			}\n		}\n	}\n	else if ($step == \'remove\') {\n		if ($something) { // $something = the pref to remove\n			$result = safe_delete(\'adi_prefs\',\"name=\'$something\'\",$adi_prefs_debug); // remove pref from database\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_pref_removed\',array(\'{pref}\'=>$something));\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_pref_remove_fail\',array(\'{pref}\'=>$something)),E_ERROR);\n		}\n	}\n	else if ($step == \'change_title\') {\n		if ($something) {\n			$result = TRUE;\n			foreach ($something as $set_name => $value) {\n				if (empty($value)) $value = $adi_prefs_default_set_list[$set_name][\'title\']; // revert to default title if blank\n				$adi_prefs_set_list[$set_name][\'title\'] = $value; // update sets array\n				$this_set_pref = \'adi_prefs_title_\'.$set_name;\n				set_pref($this_set_pref,$value,\'adi_prefs_admin\',2); // set\'s title pref\n			}\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_set_titles_updated\');\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_set_titles_update_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'visibility\') {\n		$visibility = \'current,def\'; // always visible\n		$result = TRUE;\n		if ($something) {\n			foreach ($something as $set_name => $value)\n				if ($value) $visibility .= \',\'.$set_name;\n		}\n		$result = set_pref(\'adi_prefs_visibility\',$visibility,\'adi_prefs_admin\',2); // visibility preference\n		if ($result)\n			$message = adi_prefs_gtxt(\'adi_prefs_set_visibility_updated\');\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_set_visibility_update_fail\'),E_ERROR);\n	}\n\n	if (($step == \'add\') || ($step == \'remove\') || ($step == \'update\'))\n		safe_repair(\'adi_prefs\'); // free up deleted space, so that new prefs go at end of list\n\n	pagetop(adi_prefs_gtxt(\'adi_prefs\'),$message);\n\n	if ($installed && !$upgrade_required) {\n\n		echo \'<div class=\"adi_prefs\">\';\n\n		// read visibility from preference & store in general set array\n		$visibility = safe_field(\'val\', \'txp_prefs\', \"name=\'adi_prefs_visibility\'\");\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			$pos = strpos($visibility, $set_name);\n			$adi_prefs_set_list[$set_name][\'visible\'] = !($pos === false);\n		}\n\n		// get stored prefs\n		$adi_prefs_list = adi_prefs_get_list();\n\n		// get current active prefs\n		$adi_prefs_current_prefs = get_prefs();\n\n		// find & display matching sets\n		$matches = adi_prefs_set_match();\n		foreach ($adi_prefs_set_list as $set_name => $value) // remove invisible sets from list\n			if (!$value[\'visible\']) {\n				$key = array_search($set_name, $matches);\n				if (!($key === FALSE))\n					unset($matches[$key]);\n			}\n		if (count($matches) == 0) // no match\n			echo graf(strong(adi_prefs_gtxt(\'adi_prefs_no_set_match\').\'.\'),\' class=\"adi_prefs_message\"\');\n		else if (count($matches) == 1) // one match\n			echo graf(adi_prefs_gtxt(\'adi_prefs_one_set_match\',array(\'{set}\'=>$adi_prefs_set_list[$matches[0]][\'title\'])).\'.\',\' class=\"adi_prefs_message\"\');\n		else { // multiple matches\n			end($matches);\n			$last_index = key($matches);\n			$match_list = \'\';\n			foreach ($matches as $index => $set_name) {\n				($index == $last_index) ? $glue = \' &amp; \' : $glue = \', \';\n				$index ? $match_list .= $glue.$adi_prefs_set_list[$set_name][\'title\'] : $match_list .= $adi_prefs_set_list[$set_name][\'title\'];\n			}\n			echo graf(adi_prefs_gtxt(\'adi_prefs_multiple_set_match\',array(\'{set}\'=>$match_list)).\'.\',\' class=\"adi_prefs_message\"\');\n		}\n\n		// apply radio buttons\n		$set_radios = \'\';\n		foreach ($adi_prefs_set_list as $set_name => $value)\n			if ($set_name != \'current\') { // don\'t need to apply current!\n				if ($adi_prefs_set_list[$set_name][\'visible\'])\n					$set_radios .=\n						tag(\n							$value[\'title\']\n							.sp\n							.fInput(\"radio\", \"something\", $set_name, \"edit\", \"\", \"\", \"20\", \"1\")\n							,\'label\'\n						);\n			}\n	    echo form(\n			$set_radios\n			.fInput(\"submit\", \"do_something\", \"Apply set\", \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput(\"adi_prefs_admin\").sInput(\"apply\")\n			,\'\',\'\',\'post\',\'adi_prefs_apply_set\'\n		);\n\n		// preference table\n		$set_headings = \'\';\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			if ($adi_prefs_set_list[$set_name][\'visible\'])\n				$class = \'\';\n			else\n				$class = \' class=\"adi_prefs_invisible\"\';\n			$set_headings .= hcell($value[\'title\'],\'\',$class);\n		}\n		echo form(\n			tag(adi_prefs_gtxt(\'adi_prefs\'),\'h2\')\n			.startTable(\'list\',\'\',\'adi_prefs_table txp-list\')\n			.tag(\n				tr(\n					hcell(sp) // filler for pref column\n					.$set_headings\n					.hcell(sp) // filler for delete button column\n				)\n				,\'thead\'\n			)\n			.tag(\n				adi_prefs_display_prefs()\n				,\'tbody\'\n			)\n			.endTable()\n			.graf(\n				fInput(\"submit\", \"update\", \'Update sets\', \"smallerbox\")\n				.eInput(\"adi_prefs_admin\")\n				.sInput(\"update\")\n			)\n			,\'\',\'\',\'post\',\'adi_prefs_form\'\n		);\n\n		// adi_prefs preferences\n		echo tag(\n			adi_prefs_prefs()\n			,\'div\'\n			,\' class=\"adi_prefs_prefs\"\'\n		);\n	}\n\n	echo \'</div>\';\n\n	if ($adi_prefs_debug) {\n		echo \'SET LIST:\'.br;\n		dmp($adi_prefs_set_list);\n		echo \'PREFERENCE LIST:\'.br;\n		foreach ($adi_prefs_list as $pref_name => $value)\n			echo $pref_name.\' - \'.$value[\'title\'].\' - \'.$value[\'html\'].br;\n		echo \'STORED PREFERENCES:\'.br;\n		foreach ($adi_prefs_list as $pref_name => $value)\n			foreach ($adi_prefs_set_list as $set_name => $value)\n			if ($set_name != \'current\')\n				echo $pref_name.\' (\'.$set_name.\') = \'.adi_prefs_get($pref_name,$set_name).br;\n		echo \'CURRENT PREFERENCES:\'.br;\n		foreach ($adi_prefs_list as $name => $value)\n			echo $name.\' = \'.$adi_prefs_current_prefs[$name].br;\n	}\n}\n\nfunction adi_prefs_prefs() {\n// preference set preferences\n	global $adi_prefs_set_list;\n\n	$out = \'\';\n\n	// add new preference\n    $out .= form(\n		tag(adi_prefs_gtxt(\'adi_prefs_add\'),\"h2\")\n		.adi_prefs_select_pref()\n		.sp\n		.fInput(\"submit\", \"do_something\",gTxt(\'add\'),\"smallerbox\")\n		.eInput(\"adi_prefs_admin\")\n		.sInput(\"add\")\n		,\'\',\'\',\'post\',\'adi_prefs_form\'\n	);\n\n	// user-editable set titles\n	$out .= tag(adi_prefs_gtxt(\'adi_prefs_titles\'),\'h2\');\n	$set_titles = \'\';\n	foreach ($adi_prefs_set_list as $set_name => $value) {\n		if ($adi_prefs_set_list[$set_name][\'visible\'])\n			$set_titles .= fInput(\"text\",\"something[$set_name]\",$value[\'title\'],\'\',\'\',\'\',25);\n	}\n    $out .= form(\n				$set_titles\n				.graf(\n					fInput(\"submit\", \"do_something\", \"Update titles\", \"smallerbox\")\n					.eInput(\"adi_prefs_admin\")\n					.sInput(\"change_title\")\n				)\n				,\'\',\'\',\'post\',\'adi_prefs_titles\'\n			);\n\n	// preference set visibility\n	$out .= tag(adi_prefs_gtxt(\'adi_visibility\'),\'h2\');\n	$visibility = safe_field(\'val\',\'txp_prefs\',\"name=\'adi_prefs_visibility\'\"); // read actual preference again, just in case\n	$set_checkboxes = \'\';\n	foreach ($adi_prefs_set_list as $set_name => $value)\n		if (($set_name != \'current\') && ($set_name != \'def\')) { // you can\'t hide these ones\n			$pos = strpos($visibility, $set_name);\n			$checked = !($pos === false);\n			$set_checkboxes .=\n				tag(\n					$value[\'title\']\n					.sp\n					.checkbox(\"something[$set_name]\", TRUE, $checked)\n					,\'label\'\n				);\n		}\n    $out .= form(\n		$set_checkboxes\n		.graf(\n			fInput(\"submit\", \"do_something\", \"Update visibility\", \"smallerbox\")\n			.eInput(\"adi_prefs_admin\")\n			.sInput(\"visibility\")\n		)\n		,\'\',\'\',\'post\',\'adi_prefs_visibility\'\n	);\n\n	return $out;\n}\n\nfunction adi_prefs_options($event,$step) {\n// adi_prefs options page\n	global $adi_prefs_debug,$adi_prefs_url,$adi_prefs_plugin_status,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_list;\n\n	$message = \'\';\n\n	$installed = adi_prefs_installed();\n	if ($installed) {\n		// get stored prefs\n		$adi_prefs_list = adi_prefs_get_list();\n		// get current active prefs\n		$adi_prefs_current_prefs = get_prefs();\n		// check for upgrade\n		if (adi_prefs_upgrade())\n			$step = \'upgrade\';\n	}\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_prefs_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(adi_prefs_gtxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_prefs_upgrade(TRUE);\n		$result ? $message = adi_prefs_gtxt(\'adi_upgraded\') : $message = array(adi_prefs_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_prefs_install();\n		$result ? $message = adi_prefs_gtxt(\'adi_installed\') : $message = array(adi_prefs_gtxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_prefs_uninstall();\n		$result ? $message = adi_prefs_gtxt(\'adi_uninstalled\') : $message = array(adi_prefs_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_prefs - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_prefs_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", adi_prefs_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n		);\n\n	if ($adi_prefs_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_prefs_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_prefs \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_prefs_gtxt(\'adi_textpack_online\'),$adi_prefs_url[\'textpack_download\']))\n			.graf(href(adi_prefs_gtxt(\'adi_textpack_feedback\'),$adi_prefs_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_prefs_debug) {\n		dmp($adi_prefs_default_set_list);\n		dmp($adi_prefs_current_prefs);\n	}\n\n}\n\nfunction adi_prefs_installed($table=\'adi_prefs\') {\n// test if supplied table is present\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_prefs_install() {\n// create adi_prefs database table\n	global $adi_prefs_default_list,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_debug;\n\n	if (adi_prefs_installed())\n		return TRUE;\n	else {\n		// create list of \"set\" columns\n		$set_cols = \'\';\n		foreach ($adi_prefs_default_set_list as $set_name => $set_title) {\n			$set_name = doSlash($set_name);\n			if ($set_name != \'current\') $set_cols .= \', \'.\"`$set_name` varchar(255) NOT NULL default \'\'\";\n		}\n		// create database table\n		$res = safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_prefs\').\" (\n			`name` varchar(255) NOT NULL\"\n			.$set_cols\n			.\");\"\n			,$adi_prefs_debug);\n		// add default list of prefs & initialise with current values\n		$pref_list = explode(\',\',$adi_prefs_default_list);\n		foreach ($pref_list as $index => $pref_name) {\n			if (array_key_exists($pref_name,$adi_prefs_current_prefs)) { // check that default pref actually exists\n				$current_value = doSlash($adi_prefs_current_prefs[$pref_name]);\n				$pref_name = doSlash($pref_name);\n				foreach ($adi_prefs_default_set_list as $set_name => $set_title) {\n					$set_name = doSlash($set_name);\n					if ($set_name != \'current\') $res = $res && safe_upsert(\'adi_prefs\',\"$set_name=\'\".$current_value.\"\'\",\"name=\'$pref_name\'\",$adi_prefs_debug);\n				}\n			}\n		}\n		// set titles in preferences\n		foreach ($adi_prefs_default_set_list as $set_name => $set)\n			$res = $res && set_pref(\'adi_prefs_title_\'.$set_name,$set[\'title\'],\'adi_prefs_admin\',2);\n		$res = $res && set_pref(\'adi_prefs_visibility\',implode(\',\',array_keys($adi_prefs_default_set_list)),\'adi_prefs_admin\',2);\n	 	return $res;\n	}\n}\n\nfunction adi_prefs_upgrade($do_upgrade=FALSE) {\n// check to see if upgrade required\n	global $adi_prefs_default_set_list,$adi_prefs_list,$adi_prefs_current_prefs,$adi_prefs_debug;\n\n	// check if upgrade required\n	$rs = safe_query(\'SELECT * FROM \'.safe_pfx(\'adi_prefs\'),$adi_prefs_debug);\n	$a = nextRow($rs);\n	$upgrade_required = !array_key_exists(\'test\', $a);\n\n	if ($do_upgrade && $upgrade_required) {\n		// version 0.2 - add \'test\' set\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\"adi_prefs\").\" ADD test VARCHAR(255) NOT NULL default \'\';\");\n		// initialise prefs with current values\n		foreach ($adi_prefs_list as $pref_name => $value) {\n			if (array_key_exists($pref_name,$adi_prefs_current_prefs)) { // check that pref actually exists (possibly pointless here?)\n				$current_value = doSlash($adi_prefs_current_prefs[$pref_name]);\n				$pref_name = doSlash($pref_name);\n				$res = $res && safe_upsert(\'adi_prefs\',\"test=\'\".$current_value.\"\'\",\"name=\'$pref_name\'\",$adi_prefs_debug);\n			}\n		}\n		// set title preference\n		$res = $res && set_pref(\'adi_prefs_title_test\',$adi_prefs_default_set_list[\'test\'][\'title\'],\'adi_prefs_admin\',2);\n		$set_list = $adi_prefs_default_set_list;\n		unset($set_list[\'test\']); // Testing set invisible by default\n		$res = $res && set_pref(\'adi_prefs_visibility\',implode(\',\',array_keys($set_list)),\'adi_prefs_admin\',2);\n	 	return $res;\n		}\n	else\n		return $upgrade_required;\n}\n\nfunction adi_prefs_uninstall() {\n// drop adi_prefs table\n	global $adi_prefs_debug;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_prefs\').\";\",$adi_prefs_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_prefs_%\'\",$adi_prefs_debug);\n	return $res;\n}\n\nfunction adi_prefs_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_prefs_debug;\n\n	$result = \'?\';\n	// set upgrade flag if reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_prefs_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_prefs_install();\n	else if ($step == \'deleted\')\n		$result = adi_prefs_uninstall();\n	if ($upgrade)\n		$result = $result && adi_prefs_upgrade(TRUE);\n	if ($adi_prefs_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_prefs_get_list() {\n// create preferences array - indexed by pref name, containing pref title & input control\n	$pref_list = array();\n	$rs = safe_query(\"SELECT name FROM \".safe_pfx(\'adi_prefs\'));\n	while ($a = nextRow($rs)) {\n		extract($a); // so $name contains pref name\n		$name = doSlash($name);\n		$b = safe_row(\"*\",\'txp_prefs\',\"name = \'$name\'\"); // read pref from txp_prefs table\n		if (!empty($b)) { // create array \'pref_name\' => array(\'html\' => input control)\n			$pref_list[$name][\'title\'] = gTxt($b[\'name\']);\n			$pref_list[$name][\'html\'] = $b[\'html\'];\n		}\n	}\n	return $pref_list;\n}\n\nfunction adi_prefs_display_prefs() {\n// generate table rows in admin tab\n	global $adi_prefs_list,$h,$adi_prefs_set_list,$adi_prefs_current_prefs;\n\n	$out = \'\';\n	foreach ($adi_prefs_list as $pref_name => $pref_value) {\n		$site_prefs =\'\';\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			if ($adi_prefs_set_list[$set_name][\'visible\'])\n				$class = \'\';\n			else\n				$class = \' class=\"adi_prefs_invisible\"\';\n			if ($set_name == \'def\')\n				$site_prefs .= tda(adi_prefs_display(adi_prefs_get($pref_name,$set_name),$pref_value[\'html\']));\n			else if ($set_name == \'current\')\n				$site_prefs .= tda(adi_prefs_display($adi_prefs_current_prefs[$pref_name],$pref_value[\'html\']));\n			else\n				$site_prefs .= tda(adi_prefs_input($pref_name.\"[$set_name]\",adi_prefs_get($pref_name,$set_name),$pref_value[\'html\']),$class);\n		}\n		$out .= tr(\n			tda($adi_prefs_list[$pref_name][\'title\'],\' class=\"adi_prefs_pref_name\"\')\n			.$site_prefs\n			.tda(adi_prefs_delete_button($pref_name))\n		);\n	}\n	return $out;\n}\n\nfunction adi_prefs_set($name=\'\',$set=\'\',$new_value=\'\') {\n// set preference\n	global $adi_prefs_current_prefs,$adi_prefs_list,$adi_prefs_debug;\n\n	$set = doSlash($set);\n	$new_value = doSlash($new_value);\n	$name = doSlash($name);\n	$res = TRUE;\n	if ($name && $set) { // need pref name and set name\n		$res = safe_update(\'adi_prefs\',\"$set = \'$new_value\'\",\"name = \'$name\'\",$adi_prefs_debug);\n	}\n	return $res;\n}\n\nfunction adi_prefs_get($name=\'\',$set=\'\') {\n// read preference\n	$name = doSlash($name);\n	$a = safe_row($set,\'adi_prefs\',\"name = \'$name\'\"); // read preference from TXP database\n	if ($a)\n		return $a[$set];\n}\n\nfunction adi_prefs_input($name,$value,$input_control) {\n// display pref value in human form\n	if ($name == \'\')\n		return \'blank input name\'; // better display something!\n	else {\n		$func = \'adi_prefs_\'.$input_control; // translate input control into function name - adi_prefs_prod_levels, adi_prefs_yesnoradio etc\n		if (is_callable($func))\n			return call_user_func($func,$name,$value);\n		else // no specific function for input control, so use text input\n			return finput(\"text\",$name,$value,\'\',\'\',\'\',25);\n	}\n}\n\nfunction adi_prefs_display($value,$input_control) {\n// display pref value in human form\n	if ($value == \'\')\n		return \'&nbsp;\'; // don\'t want table cell collapsing!\n	else {\n		$func = \'adi_prefs_\'.$input_control; // translate input control into function name - adi_prefs_prod_levels, adi_prefs_yesnoradio etc\n		if (is_callable($func))\n			return call_user_func($func,\'\',$value);\n		else // no specific function for input control, so just display value\n			return $value;\n	}\n}\n\nfunction adi_prefs_style() {\n// some style for the page\n	echo\n		\'<style type=\"text/css\">\n			/* adi_prefs */\n			.txp-body { max-width:none }\n			.txp-list { margin:0 auto; width:auto }\n			.adi_prefs { text-align:center }\n			.adi_prefs h2 { margin-top:1em }\n			.adi_prefs th, .adi_prefs td { padding:0.5em }\n			.adi_prefs_invisible { display:none }\n			.adi_prefs_pref_name { font-style:italic }\n			.adi_prefs_prefs { margin-top:6em }\n			.adi_prefs_prefs h2 { margin-top:1.5em }\n			.adi_prefs_apply_set label, .adi_prefs_visibility label { margin-left:2em }\n			.adi_prefs_apply_set label:first-child, .adi_prefs_visibility label:first-child { margin-left:0 }\n			.adi_prefs_apply_set input.publish { margin-left:2em }\n			.adi_prefs_titles input { margin-left:2em }\n			.adi_prefs_titles input:first-child { margin-left:0 }\n			.adi_prefs input.smallerbox { margin:0 }\n		</style>\';\n}\n\nfunction adi_prefs_select_pref() {\n// create select list from txp_prefs - ignoring adi_prefs prefs, prefs in exclude list and prefs already in preference sets\n	global $adi_prefs_list,$adi_prefs_exclude_list;\n\n	$event = doSlash(substr(\'adi_prefs_admin\',0,12)); // event name truncated to 12 chars in database\n	$pref_select_list = array();\n	$pref_select_list[\'!none!\'] = \'Select preference ...\';\n	$rs = safe_rows_start(\'*\', \'txp_prefs\', \"event != \'$event\' and prefs_id = 1 order by name\");\n	while ($a = nextRow($rs)) {\n		extract($a);\n		if (!array_key_exists($name,$adi_prefs_list)) // don\'t show prefs already in $adi_prefs_list\n			if (array_search($name,$adi_prefs_exclude_list) === FALSE) // check exclude list\n				$pref_select_list[$name] = strip_tags(gTxt($name)); // get rid of embedded HTML tags from titles\n	}\n	return selectInput(\'something\', $pref_select_list,\'\');\n}\n\nfunction adi_prefs_delete_button($pref) {\n// X marks the delete spot\n	$step = \'remove\';\n	$url = \'?event=\'.\'adi_prefs_admin\'.a.\'step=\'.$step.a.\'something=\'.$pref;\n	return\n		\'<a href=\"\'\n		.$url\n		.\'\" class=\"dlink\" title=\"\'.adi_prefs_gtxt(\'adi_prefs_remove_pref\').\'\" onclick=\"return verify(\\\'\'\n		.adi_prefs_gtxt(\'adi_prefs_remove_pref_query\',array(\'{pref}\'=>$pref))\n		.\'\\\')\">&#215;</a>\';\n}\n\nfunction adi_prefs_set_match() {\n// create list of sets whose pref values match the current values\n	global $adi_prefs_current_prefs,$adi_prefs_list,$adi_prefs_set_list;\n\n	$matches = array();\n	foreach ($adi_prefs_set_list as $set_name => $value) {\n		$set_match = TRUE;\n		if ($set_name != \'current\') {\n			foreach ($adi_prefs_list as $pref_name => $value) {\n				$set_match = $set_match && adi_prefs_get($pref_name,$set_name) == $adi_prefs_current_prefs[$pref_name];\n			}\n			if ($set_match)\n				$matches[] = $set_name;\n		}\n	}\n	return $matches;\n}\n\nfunction adi_prefs_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_prefs_gtxt() are functionally equivalent\n	global $adi_prefs_gtxt;\n\n	if (strpos(gTxt($phrase,$atts),$phrase) !== FALSE) { // no TXP translation found\n		if (array_key_exists($phrase,$adi_prefs_gtxt)) // adi translation found\n			return strtr($adi_prefs_gtxt[$phrase],$atts);\n		else // last resort\n			return $phrase;\n		}\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n\n/*\n	Input control functions copied from standard TXP\n	- renamed\n	- modified\n		- to just return value of <input> name is blank\n		- removed <input> IDs\n*/\n\nfunction adi_prefs_prod_levels($name,$val) {\n// based on prod_levels() from txp_prefs.php\n	$vals = array(\n		\'debug\'		=> gTxt(\'production_debug\'),\n		\'testing\' => gTxt(\'production_test\'),\n		\'live\'		=> gTxt(\'production_live\'),\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_yesnoradio($field,$var) {\n// based on yesnoradio() and radioSet() from txplib_forms()\n	$vals = array(\n		\'0\' => gTxt(\'no\'),\n		\'1\' => gTxt(\'yes\')\n	);\n	if ($field) { // input name supplied\n		$out = array();\n		foreach ($vals as $a => $b) {\n			$out[] = \'<input type=\"radio\" name=\"\'.$field.\'\" value=\"\'.$a.\'\" class=\"radio\"\';\n			$out[] = ($a == $var) ? \' checked=\"checked\"\' : \'\';\n			$out[] = \' /><label>\'.$b.\'</label> \';\n		}\n		return join(\'\', $out);\n	}\n	else // display only\n		return $vals[$var];\n}\n\nfunction adi_prefs_commentmode($name,$val) {\n// based on commentmode() from txp_prefs.php\n	$vals = array(\n		\'0\' => gTxt(\'nopopup\'),\n		\'1\' => gTxt(\'popup\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_weeks($name,$val) {\n// based on weeks() from txp_prefs.php\n	$weeks = gTxt(\'weeks\');\n	$vals = array(\n		\'0\' => gTxt(\'never\'),\n		7	=> \'1 \'.gTxt(\'week\'),\n		14	=> \'2 \'.$weeks,\n		21	=> \'3 \'.$weeks,\n		28	=> \'4 \'.$weeks,\n		35	=> \'5 \'.$weeks,\n		42	=> \'6 \'.$weeks\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val, \'\', \'\', $name);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_logging($name,$val) {\n// based on logging() from txp_prefs.php\n	$vals = array(\n		\'all\'	=> gTxt(\'all_hits\'),\n		\'refer\' => gTxt(\'referrers_only\'),\n		\'none\'	=> gTxt(\'none\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_dateformats($name,$val) {\n// based on dateformats() from txp_prefs.php\n	$dayname = \'%A\';\n	$dayshort = \'%a\';\n	$daynum = is_numeric(@strftime(\'%e\')) ? \'%e\' : \'%d\';\n	$daynumlead = \'%d\';\n	$daynumord = is_numeric(substr(trim(@strftime(\'%Oe\')), 0, 1)) ? \'%Oe\' : $daynum;\n	$monthname = \'%B\';\n	$monthshort = \'%b\';\n	$monthnum = \'%m\';\n	$year = \'%Y\';\n	$yearshort = \'%y\';\n	$time24 = \'%H:%M\';\n	$time12 = @strftime(\'%p\') ? \'%I:%M %p\' : $time24;\n	$date = @strftime(\'%x\') ? \'%x\' : \'%Y-%m-%d\';\n	$formats = array(\n            \"$monthshort $daynumord, $time12\",\n            \"$daynum.$monthnum.$yearshort\",\n            \"$daynumord $monthname, $time12\",\n            \"$yearshort.$monthnum.$daynumlead, $time12\",\n            \"$dayshort $monthshort $daynumord, $time12\",\n            \"$dayname $monthname $daynumord, $year\",\n            \"$monthshort $daynumord\",\n            \"$daynumord $monthname $yearshort\",\n            \"$daynumord $monthnum $year - $time24\",\n            \"$daynumord $monthname $year\",\n            \"$daynumord $monthname $year, $time24\",\n            \"$daynumord. $monthname $year\",\n            \"$daynumord. $monthname $year, $time24\",\n            \"$year-$monthnum-$daynumlead\",\n            \"$year-$daynumlead-$monthnum\",\n            \"$date $time12\",\n            \"$date\",\n            \"$time24\",\n            \"$time12\",\n            \"$year-$monthnum-$daynumlead $time24\",\n	);\n	$ts = time();\n	$vals = array();\n	foreach ($formats as $f) {\n		if ($d = safe_strftime($f, $ts)) {\n			$vals[$f] = $d;\n		}\n	}\n	$vals[\'since\'] = gTxt(\'hours_days_ago\');\n	if ($name) // input name supplied\n		return selectInput($name, array_unique($vals), $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_commentsendmail($name,$val) {\n// based on commentsendmail() from txp_prefs.php\n	$vals = array(\n	\'1\'	=> gTxt(\'all\'),\n	\'0\' => gTxt(\'none\'),\n	\'2\'	=> gTxt(\'ham\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_permlinkmodes($name,$val) {\n// based on permlinkmodes() from txp_prefs.php\n	$vals = array(\n		\'messy\' => gTxt(\'messy\'),\n		\'id_title\' => gTxt(\'id_title\'),\n		\'section_id_title\' => gTxt(\'section_id_title\'),\n		\'year_month_day_title\' => gTxt(\'year_month_day_title\'),\n		\'section_title\' => gTxt(\'section_title\'),\n		\'title_only\' => gTxt(\'title_only\'),\n		// \'category_subcategory\' => gTxt(\'category_subcategory\')\n        );\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_gmtoffset_select($name,$val) {\n// based on gmtoffset_select() from txp_prefs.php\n	$key = safe_field(\'val\', \'txp_prefs\', \"name=\'timezone_key\'\");\n	$tz = new timezone;\n	$ui = $tz->selectInput(\'timezone_key\', $key, true, \'\', \'gmtoffset\');\n	$tzd = $tz->details();\n	if ($name) // input name supplied\n		return $ui;\n	else // display only\n		return $tzd[$key][\'offset\']; // TO BE FIXED UP\n}\n\nfunction adi_prefs_themename($name,$val) {\n// based on themename() from txp_prefs.php\n	$themes = theme::names();\n	foreach ($themes as $t) {\n		$theme = theme::factory($t);\n		if ($theme) {\n			$m = $theme->manifest();\n			$title = empty($m[\'title\']) ? ucwords($theme->name) : $m[\'title\'];\n			$vals[$t] = $title;\n			unset($theme);\n		}\n	}\n	asort($vals, SORT_STRING);\n	if ($name)\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_languages($name,$val) {\n// based on languages() from txp_prefs.php\n	$installed_langs = safe_column(\'lang\', \'txp_lang\', \"1 = 1 group by lang\");\n	$vals = array();\n	foreach ($installed_langs as $lang) {\n		$vals[$lang] = safe_field(\'data\', \'txp_lang\', \"name = \'\".doSlash($lang).\"\' AND lang = \'\".doSlash($lang).\"\'\");\n		if (trim($vals[$lang]) == \'\')\n			$vals[$lang] = $lang;\n	}\n	asort($vals);\n	reset($vals);\n	$out = n.\'<select name=\"\'.$name.\'\" class=\"list\">\';\n	foreach ($vals as $avalue => $alabel) {\n		$selected = ($avalue == $val || $alabel == $val) ?\n		\' selected=\"selected\"\' :\n		\'\';\n		$out .= n.t.\'<option value=\"\'.htmlspecialchars($avalue).\'\"\'.$selected.\'>\'.htmlspecialchars($alabel).\'</option>\'.n;\n	}\n	$out .= n.\'</select>\';\n	if ($name) // input name supplied\n		return $out;\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_pref_text($name,$val) {\n// based on pref_text() from txplib_html.php\n	$vals = array(\n		USE_TEXTILE          => gTxt(\'use_textile\'),\n		CONVERT_LINEBREAKS   => gTxt(\'convert_linebreaks\'),\n		LEAVE_TEXT_UNTOUCHED => gTxt(\'leave_text_untouched\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_default_event($name,$val) {\n// based on default_event() from txp_prefs.php\n	$vals = areas();\n	$out = array();\n	foreach ($vals as $a => $b) {\n		if (count($b) > 0) {\n			$out[] = n.t.\'<optgroup label=\"\'.gTxt(\'tab_\'.$a).\'\">\';\n			foreach ($b as $c => $d) {\n				$out[] = n.t.t.\'<option value=\"\'.$d.\'\"\'.( $val == $d ? \' selected=\"selected\"\' : \'\' ).\'>\'.$c.\'</option>\';\n				if ($val == $d) $this_event = $c; // note the event\n			}\n			$out[] = n.t.\'</optgroup>\';\n		}\n	}\n	if ($name) // input name supplied\n		return n.\'<select name=\"\'.$name.\'\" class=\"list\">\'. join(\'\', $out). n.\'</select>\';\n	else // display only\n		return $this_event;\n}\n\nfunction adi_prefs_is_dst($name,$val) {\n// original does some sexy javascript - gloss over for now!\n	return adi_prefs_yesnoradio($name,$val);\n}\n', '\n/*\n	adi_prefs - Preference Sets\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3.2	- minor layout fixes\n			- TXP 4.5+ only\n			- tested in 4.6\n	0.3.1	- fixed issue when upgrading from version 0.1\n			- fixed Textpack download\n	0.3		- TXP 4.5-ified\n			- moved install/uninstall to plugin options\n			- Textpack added\n			- tooltips thrown in the tip\n			- code tidy up\n			- now uses lifecycle events\n	0.2.1	- fixed: injudicious use of global variables, causing conflict with adi_matrix\n	0.2		- enhancement: added new set \"Testing\" (for Maniqui)\n			- enhancement: preference set visibility\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $adi_prefs_debug;\n\n	$adi_prefs_debug = 0; // display admin debug info\n\n	// to infinity & beyond ...\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_prefs_init();\n\n}\n\nfunction adi_prefs_init() {\n// initial setup\n	global $event,$prefs,$adi_prefs_debug,$adi_prefs_gtxt,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_plugin_status,$adi_prefs_default_list,$adi_prefs_list,$adi_prefs_url;\n\n	$adi_prefs_privs = \'1,6\';\n\n	// default pref set titles\n	$adi_prefs_default_set_list = array(\n		\'current\' => array(\'title\' => \'Current\'),\n		\'def\' => array(\'title\' => \'Default\'),\n		\'dev\' => array(\'title\' => \'Local\'),\n		\'demo\' => array(\'title\' => \'Staging\'),\n		\'test\' => array(\'title\' => \'Testing\'),\n		\'live\' => array(\'title\' => \'Live\'),\n	);\n\n	// default list of prefs\n	$adi_prefs_default_list = \'siteurl,production_status,file_base_path,tempdir,rss_dbbk_path,rss_dbbk_mysql,rss_dbbk_dump,xxx\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_prefs_gtxt = array(\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_prefs\' => \'Preference Sets\',\n		\'adi_prefs_add\' => \'Add Preference\',\n		\'adi_prefs_no_set_match\' => \'No sets match the current preferences\',\n		\'adi_prefs_multiple_set_match\' => \'The <b>{set}</b> sets match the current preferences\',\n		\'adi_prefs_one_set_match\' => \'The <b>{set}</b> set matches the current preferences\',\n		\'adi_prefs_pref_add_fail\' => \'Unable to add {pref} preference\',\n		\'adi_prefs_pref_added\' => \'{pref} preference added\',\n		\'adi_prefs_pref_not_selected\' => \'No new preference selected\',\n		\'adi_prefs_pref_remove_fail\' => \'Unable to remove {pref} preference\',\n		\'adi_prefs_pref_removed\' => \'{pref} preference removed\',\n		\'adi_prefs_remove_pref\' => \'Remove preference?\',\n		\'adi_prefs_remove_pref_query\' => \'Remove {pref} from preference sets?\',\n		\'adi_prefs_set_applied\' => \'{set} preference set applied\',\n		\'adi_prefs_set_apply_fail\' => \'Unable to apply {set} preference set\',\n		\'adi_prefs_set_not_selected\' => \'No preference set selected\',\n		\'adi_prefs_set_titles_updated\' => \'Preference set titles updated\',\n		\'adi_prefs_set_titles_update_fail\' => \'Unable to update preference set titles\',\n		\'adi_prefs_set_visibility_updated\' => \'Preference set visibility updated\',\n		\'adi_prefs_set_visibility_update_fail\' => \'Unable to update preference set visibility\',\n		\'adi_prefs_sets_update_fail\' => \'Unable to update preference sets\',\n		\'adi_prefs_sets_updated\' => \'Preference sets updated\',\n		\'adi_prefs_titles\' => \'Preference Set Titles\',\n		\'adi_prefs_upgraded\' => \'adi_prefs upgraded successfully\',\n		\'adi_prefs_upgrade_fail\' => \'Unable to upgrade adi_prefs\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_upgrade_fail\' => \'Unable to upgrade\',\n		\'adi_upgrade_required\' => \'Upgrade required\',\n		\'adi_upgraded\' => \'Upgraded\',\n		\'adi_visibility\' => \'Visibility\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// Textpack\n	$adi_prefs_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_prefs\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_prefs_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_prefs_lifecycle\',\'plugin_lifecycle.adi_prefs\');\n\n	// set the privilege levels\n	add_privs(\'adi_prefs_admin\',$adi_prefs_privs);\n\n	// add new tab under \'Admin\'\n	register_tab(\'admin\',\'adi_prefs_admin\',adi_prefs_gtxt(\'adi_prefs\'));\n	register_callback(\'adi_prefs_admin\',\'adi_prefs_admin\');	// adi_prefs admin tab\n\n	// style\n	if ($event == \'adi_prefs_admin\')\n		register_callback(\'adi_prefs_style\',\'admin_side\',\'head_end\');	// style for admin\n\n	// plugin options\n	$adi_prefs_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_prefs\',$adi_prefs_debug);\n	if ($adi_prefs_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_prefs\'); // defaults to priv \'1\' only\n		register_callback(\'adi_prefs_options\',\'plugin_prefs.adi_prefs\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_prefs_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_prefs_options\',\'adi_prefs options\');\n		register_callback(\'adi_prefs_options\',\'adi_prefs_options\');\n	}\n\n	// get current active prefs\n	$adi_prefs_current_prefs = get_prefs();\n\n	// get stored prefs\n	$adi_prefs_list = array();\n	if (adi_prefs_installed())\n		$adi_prefs_list = adi_prefs_get_list();\n}\n\nfunction adi_prefs_admin($event, $step) {\n// adi_prefs admin tab\n\n	global $adi_prefs_default_list,$adi_prefs_exclude_list,$adi_prefs_list,$adi_prefs_debug,$adi_prefs_set_list,$adi_prefs_default_set_list,$h,$adi_prefs_current_prefs,$plugin_status;\n\n	// list of prefs to exclude\n	$adi_prefs_exclude_list = \"dbupdatetime,gmtoffset,lastmod,prefs_id,timeoffset,timezone_key,version\";\n	$adi_prefs_exclude_list = explode(\',\',$adi_prefs_exclude_list);\n\n	// replace set titles with user defined, if any\n	foreach ($adi_prefs_default_set_list as $set_name => $value) {\n		$this_set_pref = doSlash(\'adi_prefs_title_\'.$set_name);\n		$val = safe_field(\'val\', \'txp_prefs\', \"name=\'$this_set_pref\'\");\n		if (empty($val)) // use default set title\n			$adi_prefs_set_list[$set_name][\'title\'] = $adi_prefs_default_set_list[$set_name][\'title\'];\n		else // read set title from database\n			$adi_prefs_set_list[$set_name][\'title\'] = $val;\n	}\n\n	$message = \'\';\n	$installed = adi_prefs_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_prefs_upgrade();\n		if ($upgrade_required)\n			$message = array(adi_prefs_gtxt(\'adi_upgrade_required\'),E_WARNING);\n		else {\n			if ($adi_prefs_debug) {\n				echo \'$adi_prefs_list\'.n;\n				dmp($adi_prefs_list);\n			}\n		}\n	}\n	else\n		$message = array(adi_prefs_gtxt(\'adi_not_installed\'),E_ERROR);\n\n\n	$something = doSlash(doStripTags(gps(\'something\')));\n\n	if ($adi_prefs_debug) echo \'<p>Event=\'.$event.\', Step=\'.$step.\', Something=\'.$something.\'</p>\';\n\n	// stepping out\n	if ($step == \'update\') {\n		foreach ($adi_prefs_list as $pref_name => $value) {\n			$this_pref = doStripTags(ps($pref_name));\n			$result = TRUE;\n			foreach ($adi_prefs_set_list as $set_name => $value)\n				if (($set_name != \'def\') && ($set_name != \'current\')) { // ignore \'def\' & \'current\' because they\'re for display only\n					if ($adi_prefs_debug)\n						echo \'<p>\'.$pref_name.\'[\'.$set_name.\'] = \'.$this_pref[$set_name].\'</p>\';\n					$result = $result && adi_prefs_set($pref_name,$set_name,$this_pref[$set_name]);\n				}\n		}\n		if ($result)\n			$message = adi_prefs_gtxt(\'adi_prefs_sets_updated\');\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_sets_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'apply\') {\n		if ($something) { // i.e. set name\n			$result = TRUE;\n			foreach ($adi_prefs_list as $pref_name => $value) // apply \"set\" to the TXP database\n				$result = $result && safe_update(\'txp_prefs\',\"val = \'\".doSlash(adi_prefs_get($pref_name,$something)).\"\'\",\"name = \'\".doSlash($pref_name).\"\'\");\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_set_applied\',array(\'{set}\'=>$adi_prefs_set_list[$something][\'title\']));\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_set_apply_fail\',array(\'{set}\'=>$adi_prefs_set_list[$something][\'title\'])),E_ERROR);\n		}\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_set_not_selected\'),E_WARNING);\n	}\n	else if ($step == \'add\') {\n		if ($something) { // $something = new pref from list\n			if ($something == \'!none!\')\n				$message = array(adi_prefs_gtxt(\'adi_prefs_pref_not_selected\'),E_WARNING);\n			else {\n				$result = FALSE;\n				if (array_key_exists($something,$adi_prefs_current_prefs)) { // can\'t add what doesn\'t exist!\n					$result = TRUE;\n					$current_value = doSlash($adi_prefs_current_prefs[$something]);\n					foreach ($adi_prefs_default_set_list as $set_name => $set_title) { // all set to current value\n						$set_name = doSlash($set_name);\n						if ($set_name != \'current\') $result = $result && safe_upsert(\'adi_prefs\',\"$set_name=\'\".$current_value.\"\'\",\"name=\'$something\'\",$adi_prefs_debug);\n					}\n				}\n				if ($result)\n					$message = adi_prefs_gtxt(\'adi_prefs_pref_added\',array(\'{pref}\'=>$something));\n				else\n					$message = array(adi_prefs_gtxt(\'adi_prefs_pref_add_fail\',array(\'{pref}\'=>$something)),E_ERROR);\n			}\n		}\n	}\n	else if ($step == \'remove\') {\n		if ($something) { // $something = the pref to remove\n			$result = safe_delete(\'adi_prefs\',\"name=\'$something\'\",$adi_prefs_debug); // remove pref from database\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_pref_removed\',array(\'{pref}\'=>$something));\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_pref_remove_fail\',array(\'{pref}\'=>$something)),E_ERROR);\n		}\n	}\n	else if ($step == \'change_title\') {\n		if ($something) {\n			$result = TRUE;\n			foreach ($something as $set_name => $value) {\n				if (empty($value)) $value = $adi_prefs_default_set_list[$set_name][\'title\']; // revert to default title if blank\n				$adi_prefs_set_list[$set_name][\'title\'] = $value; // update sets array\n				$this_set_pref = \'adi_prefs_title_\'.$set_name;\n				set_pref($this_set_pref,$value,\'adi_prefs_admin\',2); // set\'s title pref\n			}\n			if ($result)\n				$message = adi_prefs_gtxt(\'adi_prefs_set_titles_updated\');\n			else\n				$message = array(adi_prefs_gtxt(\'adi_prefs_set_titles_update_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'visibility\') {\n		$visibility = \'current,def\'; // always visible\n		$result = TRUE;\n		if ($something) {\n			foreach ($something as $set_name => $value)\n				if ($value) $visibility .= \',\'.$set_name;\n		}\n		$result = set_pref(\'adi_prefs_visibility\',$visibility,\'adi_prefs_admin\',2); // visibility preference\n		if ($result)\n			$message = adi_prefs_gtxt(\'adi_prefs_set_visibility_updated\');\n		else\n			$message = array(adi_prefs_gtxt(\'adi_prefs_set_visibility_update_fail\'),E_ERROR);\n	}\n\n	if (($step == \'add\') || ($step == \'remove\') || ($step == \'update\'))\n		safe_repair(\'adi_prefs\'); // free up deleted space, so that new prefs go at end of list\n\n	pagetop(adi_prefs_gtxt(\'adi_prefs\'),$message);\n\n	if ($installed && !$upgrade_required) {\n\n		echo \'<div class=\"adi_prefs\">\';\n\n		// read visibility from preference & store in general set array\n		$visibility = safe_field(\'val\', \'txp_prefs\', \"name=\'adi_prefs_visibility\'\");\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			$pos = strpos($visibility, $set_name);\n			$adi_prefs_set_list[$set_name][\'visible\'] = !($pos === false);\n		}\n\n		// get stored prefs\n		$adi_prefs_list = adi_prefs_get_list();\n\n		// get current active prefs\n		$adi_prefs_current_prefs = get_prefs();\n\n		// find & display matching sets\n		$matches = adi_prefs_set_match();\n		foreach ($adi_prefs_set_list as $set_name => $value) // remove invisible sets from list\n			if (!$value[\'visible\']) {\n				$key = array_search($set_name, $matches);\n				if (!($key === FALSE))\n					unset($matches[$key]);\n			}\n		if (count($matches) == 0) // no match\n			echo graf(strong(adi_prefs_gtxt(\'adi_prefs_no_set_match\').\'.\'),\' class=\"adi_prefs_message\"\');\n		else if (count($matches) == 1) // one match\n			echo graf(adi_prefs_gtxt(\'adi_prefs_one_set_match\',array(\'{set}\'=>$adi_prefs_set_list[$matches[0]][\'title\'])).\'.\',\' class=\"adi_prefs_message\"\');\n		else { // multiple matches\n			end($matches);\n			$last_index = key($matches);\n			$match_list = \'\';\n			foreach ($matches as $index => $set_name) {\n				($index == $last_index) ? $glue = \' &amp; \' : $glue = \', \';\n				$index ? $match_list .= $glue.$adi_prefs_set_list[$set_name][\'title\'] : $match_list .= $adi_prefs_set_list[$set_name][\'title\'];\n			}\n			echo graf(adi_prefs_gtxt(\'adi_prefs_multiple_set_match\',array(\'{set}\'=>$match_list)).\'.\',\' class=\"adi_prefs_message\"\');\n		}\n\n		// apply radio buttons\n		$set_radios = \'\';\n		foreach ($adi_prefs_set_list as $set_name => $value)\n			if ($set_name != \'current\') { // don\'t need to apply current!\n				if ($adi_prefs_set_list[$set_name][\'visible\'])\n					$set_radios .=\n						tag(\n							$value[\'title\']\n							.sp\n							.fInput(\"radio\", \"something\", $set_name, \"edit\", \"\", \"\", \"20\", \"1\")\n							,\'label\'\n						);\n			}\n	    echo form(\n			$set_radios\n			.fInput(\"submit\", \"do_something\", \"Apply set\", \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput(\"adi_prefs_admin\").sInput(\"apply\")\n			,\'\',\'\',\'post\',\'adi_prefs_apply_set\'\n		);\n\n		// preference table\n		$set_headings = \'\';\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			if ($adi_prefs_set_list[$set_name][\'visible\'])\n				$class = \'\';\n			else\n				$class = \' class=\"adi_prefs_invisible\"\';\n			$set_headings .= hcell($value[\'title\'],\'\',$class);\n		}\n		echo form(\n			tag(adi_prefs_gtxt(\'adi_prefs\'),\'h2\')\n			.startTable(\'list\',\'\',\'adi_prefs_table txp-list\')\n			.tag(\n				tr(\n					hcell(sp) // filler for pref column\n					.$set_headings\n					.hcell(sp) // filler for delete button column\n				)\n				,\'thead\'\n			)\n			.tag(\n				adi_prefs_display_prefs()\n				,\'tbody\'\n			)\n			.endTable()\n			.graf(\n				fInput(\"submit\", \"update\", \'Update sets\', \"smallerbox\")\n				.eInput(\"adi_prefs_admin\")\n				.sInput(\"update\")\n			)\n			,\'\',\'\',\'post\',\'adi_prefs_form\'\n		);\n\n		// adi_prefs preferences\n		echo tag(\n			adi_prefs_prefs()\n			,\'div\'\n			,\' class=\"adi_prefs_prefs\"\'\n		);\n	}\n\n	echo \'</div>\';\n\n	if ($adi_prefs_debug) {\n		echo \'SET LIST:\'.br;\n		dmp($adi_prefs_set_list);\n		echo \'PREFERENCE LIST:\'.br;\n		foreach ($adi_prefs_list as $pref_name => $value)\n			echo $pref_name.\' - \'.$value[\'title\'].\' - \'.$value[\'html\'].br;\n		echo \'STORED PREFERENCES:\'.br;\n		foreach ($adi_prefs_list as $pref_name => $value)\n			foreach ($adi_prefs_set_list as $set_name => $value)\n			if ($set_name != \'current\')\n				echo $pref_name.\' (\'.$set_name.\') = \'.adi_prefs_get($pref_name,$set_name).br;\n		echo \'CURRENT PREFERENCES:\'.br;\n		foreach ($adi_prefs_list as $name => $value)\n			echo $name.\' = \'.$adi_prefs_current_prefs[$name].br;\n	}\n}\n\nfunction adi_prefs_prefs() {\n// preference set preferences\n	global $adi_prefs_set_list;\n\n	$out = \'\';\n\n	// add new preference\n    $out .= form(\n		tag(adi_prefs_gtxt(\'adi_prefs_add\'),\"h2\")\n		.adi_prefs_select_pref()\n		.sp\n		.fInput(\"submit\", \"do_something\",gTxt(\'add\'),\"smallerbox\")\n		.eInput(\"adi_prefs_admin\")\n		.sInput(\"add\")\n		,\'\',\'\',\'post\',\'adi_prefs_form\'\n	);\n\n	// user-editable set titles\n	$out .= tag(adi_prefs_gtxt(\'adi_prefs_titles\'),\'h2\');\n	$set_titles = \'\';\n	foreach ($adi_prefs_set_list as $set_name => $value) {\n		if ($adi_prefs_set_list[$set_name][\'visible\'])\n			$set_titles .= fInput(\"text\",\"something[$set_name]\",$value[\'title\'],\'\',\'\',\'\',25);\n	}\n    $out .= form(\n				$set_titles\n				.graf(\n					fInput(\"submit\", \"do_something\", \"Update titles\", \"smallerbox\")\n					.eInput(\"adi_prefs_admin\")\n					.sInput(\"change_title\")\n				)\n				,\'\',\'\',\'post\',\'adi_prefs_titles\'\n			);\n\n	// preference set visibility\n	$out .= tag(adi_prefs_gtxt(\'adi_visibility\'),\'h2\');\n	$visibility = safe_field(\'val\',\'txp_prefs\',\"name=\'adi_prefs_visibility\'\"); // read actual preference again, just in case\n	$set_checkboxes = \'\';\n	foreach ($adi_prefs_set_list as $set_name => $value)\n		if (($set_name != \'current\') && ($set_name != \'def\')) { // you can\'t hide these ones\n			$pos = strpos($visibility, $set_name);\n			$checked = !($pos === false);\n			$set_checkboxes .=\n				tag(\n					$value[\'title\']\n					.sp\n					.checkbox(\"something[$set_name]\", TRUE, $checked)\n					,\'label\'\n				);\n		}\n    $out .= form(\n		$set_checkboxes\n		.graf(\n			fInput(\"submit\", \"do_something\", \"Update visibility\", \"smallerbox\")\n			.eInput(\"adi_prefs_admin\")\n			.sInput(\"visibility\")\n		)\n		,\'\',\'\',\'post\',\'adi_prefs_visibility\'\n	);\n\n	return $out;\n}\n\nfunction adi_prefs_options($event,$step) {\n// adi_prefs options page\n	global $adi_prefs_debug,$adi_prefs_url,$adi_prefs_plugin_status,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_list;\n\n	$message = \'\';\n\n	$installed = adi_prefs_installed();\n	if ($installed) {\n		// get stored prefs\n		$adi_prefs_list = adi_prefs_get_list();\n		// get current active prefs\n		$adi_prefs_current_prefs = get_prefs();\n		// check for upgrade\n		if (adi_prefs_upgrade())\n			$step = \'upgrade\';\n	}\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_prefs_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(adi_prefs_gtxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'upgrade\') {\n		$result = adi_prefs_upgrade(TRUE);\n		$result ? $message = adi_prefs_gtxt(\'adi_upgraded\') : $message = array(adi_prefs_gtxt(\'adi_upgrade_fail\'),E_ERROR);\n	}\n	else if ($step == \'install\') {\n		$result = adi_prefs_install();\n		$result ? $message = adi_prefs_gtxt(\'adi_installed\') : $message = array(adi_prefs_gtxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_prefs_uninstall();\n		$result ? $message = adi_prefs_gtxt(\'adi_uninstalled\') : $message = array(adi_prefs_gtxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_prefs - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_prefs_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", adi_prefs_gtxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n		);\n\n	if ($adi_prefs_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_prefs_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_prefs \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(adi_prefs_gtxt(\'adi_textpack_online\'),$adi_prefs_url[\'textpack_download\']))\n			.graf(href(adi_prefs_gtxt(\'adi_textpack_feedback\'),$adi_prefs_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_prefs_debug) {\n		dmp($adi_prefs_default_set_list);\n		dmp($adi_prefs_current_prefs);\n	}\n\n}\n\nfunction adi_prefs_installed($table=\'adi_prefs\') {\n// test if supplied table is present\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_prefs_install() {\n// create adi_prefs database table\n	global $adi_prefs_default_list,$adi_prefs_default_set_list,$adi_prefs_current_prefs,$adi_prefs_debug;\n\n	if (adi_prefs_installed())\n		return TRUE;\n	else {\n		// create list of \"set\" columns\n		$set_cols = \'\';\n		foreach ($adi_prefs_default_set_list as $set_name => $set_title) {\n			$set_name = doSlash($set_name);\n			if ($set_name != \'current\') $set_cols .= \', \'.\"`$set_name` varchar(255) NOT NULL default \'\'\";\n		}\n		// create database table\n		$res = safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_prefs\').\" (\n			`name` varchar(255) NOT NULL\"\n			.$set_cols\n			.\");\"\n			,$adi_prefs_debug);\n		// add default list of prefs & initialise with current values\n		$pref_list = explode(\',\',$adi_prefs_default_list);\n		foreach ($pref_list as $index => $pref_name) {\n			if (array_key_exists($pref_name,$adi_prefs_current_prefs)) { // check that default pref actually exists\n				$current_value = doSlash($adi_prefs_current_prefs[$pref_name]);\n				$pref_name = doSlash($pref_name);\n				foreach ($adi_prefs_default_set_list as $set_name => $set_title) {\n					$set_name = doSlash($set_name);\n					if ($set_name != \'current\') $res = $res && safe_upsert(\'adi_prefs\',\"$set_name=\'\".$current_value.\"\'\",\"name=\'$pref_name\'\",$adi_prefs_debug);\n				}\n			}\n		}\n		// set titles in preferences\n		foreach ($adi_prefs_default_set_list as $set_name => $set)\n			$res = $res && set_pref(\'adi_prefs_title_\'.$set_name,$set[\'title\'],\'adi_prefs_admin\',2);\n		$res = $res && set_pref(\'adi_prefs_visibility\',implode(\',\',array_keys($adi_prefs_default_set_list)),\'adi_prefs_admin\',2);\n	 	return $res;\n	}\n}\n\nfunction adi_prefs_upgrade($do_upgrade=FALSE) {\n// check to see if upgrade required\n	global $adi_prefs_default_set_list,$adi_prefs_list,$adi_prefs_current_prefs,$adi_prefs_debug;\n\n	// check if upgrade required\n	$rs = safe_query(\'SELECT * FROM \'.safe_pfx(\'adi_prefs\'),$adi_prefs_debug);\n	$a = nextRow($rs);\n	$upgrade_required = !array_key_exists(\'test\', $a);\n\n	if ($do_upgrade && $upgrade_required) {\n		// version 0.2 - add \'test\' set\n		$res = safe_query(\"ALTER TABLE \".safe_pfx(\"adi_prefs\").\" ADD test VARCHAR(255) NOT NULL default \'\';\");\n		// initialise prefs with current values\n		foreach ($adi_prefs_list as $pref_name => $value) {\n			if (array_key_exists($pref_name,$adi_prefs_current_prefs)) { // check that pref actually exists (possibly pointless here?)\n				$current_value = doSlash($adi_prefs_current_prefs[$pref_name]);\n				$pref_name = doSlash($pref_name);\n				$res = $res && safe_upsert(\'adi_prefs\',\"test=\'\".$current_value.\"\'\",\"name=\'$pref_name\'\",$adi_prefs_debug);\n			}\n		}\n		// set title preference\n		$res = $res && set_pref(\'adi_prefs_title_test\',$adi_prefs_default_set_list[\'test\'][\'title\'],\'adi_prefs_admin\',2);\n		$set_list = $adi_prefs_default_set_list;\n		unset($set_list[\'test\']); // Testing set invisible by default\n		$res = $res && set_pref(\'adi_prefs_visibility\',implode(\',\',array_keys($set_list)),\'adi_prefs_admin\',2);\n	 	return $res;\n		}\n	else\n		return $upgrade_required;\n}\n\nfunction adi_prefs_uninstall() {\n// drop adi_prefs table\n	global $adi_prefs_debug;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_prefs\').\";\",$adi_prefs_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_prefs_%\'\",$adi_prefs_debug);\n	return $res;\n}\n\nfunction adi_prefs_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_prefs_debug;\n\n	$result = \'?\';\n	// set upgrade flag if reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && adi_prefs_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_prefs_install();\n	else if ($step == \'deleted\')\n		$result = adi_prefs_uninstall();\n	if ($upgrade)\n		$result = $result && adi_prefs_upgrade(TRUE);\n	if ($adi_prefs_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_prefs_get_list() {\n// create preferences array - indexed by pref name, containing pref title & input control\n	$pref_list = array();\n	$rs = safe_query(\"SELECT name FROM \".safe_pfx(\'adi_prefs\'));\n	while ($a = nextRow($rs)) {\n		extract($a); // so $name contains pref name\n		$name = doSlash($name);\n		$b = safe_row(\"*\",\'txp_prefs\',\"name = \'$name\'\"); // read pref from txp_prefs table\n		if (!empty($b)) { // create array \'pref_name\' => array(\'html\' => input control)\n			$pref_list[$name][\'title\'] = gTxt($b[\'name\']);\n			$pref_list[$name][\'html\'] = $b[\'html\'];\n		}\n	}\n	return $pref_list;\n}\n\nfunction adi_prefs_display_prefs() {\n// generate table rows in admin tab\n	global $adi_prefs_list,$h,$adi_prefs_set_list,$adi_prefs_current_prefs;\n\n	$out = \'\';\n	foreach ($adi_prefs_list as $pref_name => $pref_value) {\n		$site_prefs =\'\';\n		foreach ($adi_prefs_set_list as $set_name => $value) {\n			if ($adi_prefs_set_list[$set_name][\'visible\'])\n				$class = \'\';\n			else\n				$class = \' class=\"adi_prefs_invisible\"\';\n			if ($set_name == \'def\')\n				$site_prefs .= tda(adi_prefs_display(adi_prefs_get($pref_name,$set_name),$pref_value[\'html\']));\n			else if ($set_name == \'current\')\n				$site_prefs .= tda(adi_prefs_display($adi_prefs_current_prefs[$pref_name],$pref_value[\'html\']));\n			else\n				$site_prefs .= tda(adi_prefs_input($pref_name.\"[$set_name]\",adi_prefs_get($pref_name,$set_name),$pref_value[\'html\']),$class);\n		}\n		$out .= tr(\n			tda($adi_prefs_list[$pref_name][\'title\'],\' class=\"adi_prefs_pref_name\"\')\n			.$site_prefs\n			.tda(adi_prefs_delete_button($pref_name))\n		);\n	}\n	return $out;\n}\n\nfunction adi_prefs_set($name=\'\',$set=\'\',$new_value=\'\') {\n// set preference\n	global $adi_prefs_current_prefs,$adi_prefs_list,$adi_prefs_debug;\n\n	$set = doSlash($set);\n	$new_value = doSlash($new_value);\n	$name = doSlash($name);\n	$res = TRUE;\n	if ($name && $set) { // need pref name and set name\n		$res = safe_update(\'adi_prefs\',\"$set = \'$new_value\'\",\"name = \'$name\'\",$adi_prefs_debug);\n	}\n	return $res;\n}\n\nfunction adi_prefs_get($name=\'\',$set=\'\') {\n// read preference\n	$name = doSlash($name);\n	$a = safe_row($set,\'adi_prefs\',\"name = \'$name\'\"); // read preference from TXP database\n	if ($a)\n		return $a[$set];\n}\n\nfunction adi_prefs_input($name,$value,$input_control) {\n// display pref value in human form\n	if ($name == \'\')\n		return \'blank input name\'; // better display something!\n	else {\n		$func = \'adi_prefs_\'.$input_control; // translate input control into function name - adi_prefs_prod_levels, adi_prefs_yesnoradio etc\n		if (is_callable($func))\n			return call_user_func($func,$name,$value);\n		else // no specific function for input control, so use text input\n			return finput(\"text\",$name,$value,\'\',\'\',\'\',25);\n	}\n}\n\nfunction adi_prefs_display($value,$input_control) {\n// display pref value in human form\n	if ($value == \'\')\n		return \'&nbsp;\'; // don\'t want table cell collapsing!\n	else {\n		$func = \'adi_prefs_\'.$input_control; // translate input control into function name - adi_prefs_prod_levels, adi_prefs_yesnoradio etc\n		if (is_callable($func))\n			return call_user_func($func,\'\',$value);\n		else // no specific function for input control, so just display value\n			return $value;\n	}\n}\n\nfunction adi_prefs_style() {\n// some style for the page\n	echo\n		\'<style type=\"text/css\">\n			/* adi_prefs */\n			.txp-body { max-width:none }\n			.txp-list { margin:0 auto; width:auto }\n			.adi_prefs { text-align:center }\n			.adi_prefs h2 { margin-top:1em }\n			.adi_prefs th, .adi_prefs td { padding:0.5em }\n			.adi_prefs_invisible { display:none }\n			.adi_prefs_pref_name { font-style:italic }\n			.adi_prefs_prefs { margin-top:6em }\n			.adi_prefs_prefs h2 { margin-top:1.5em }\n			.adi_prefs_apply_set label, .adi_prefs_visibility label { margin-left:2em }\n			.adi_prefs_apply_set label:first-child, .adi_prefs_visibility label:first-child { margin-left:0 }\n			.adi_prefs_apply_set input.publish { margin-left:2em }\n			.adi_prefs_titles input { margin-left:2em }\n			.adi_prefs_titles input:first-child { margin-left:0 }\n			.adi_prefs input.smallerbox { margin:0 }\n		</style>\';\n}\n\nfunction adi_prefs_select_pref() {\n// create select list from txp_prefs - ignoring adi_prefs prefs, prefs in exclude list and prefs already in preference sets\n	global $adi_prefs_list,$adi_prefs_exclude_list;\n\n	$event = doSlash(substr(\'adi_prefs_admin\',0,12)); // event name truncated to 12 chars in database\n	$pref_select_list = array();\n	$pref_select_list[\'!none!\'] = \'Select preference ...\';\n	$rs = safe_rows_start(\'*\', \'txp_prefs\', \"event != \'$event\' and prefs_id = 1 order by name\");\n	while ($a = nextRow($rs)) {\n		extract($a);\n		if (!array_key_exists($name,$adi_prefs_list)) // don\'t show prefs already in $adi_prefs_list\n			if (array_search($name,$adi_prefs_exclude_list) === FALSE) // check exclude list\n				$pref_select_list[$name] = strip_tags(gTxt($name)); // get rid of embedded HTML tags from titles\n	}\n	return selectInput(\'something\', $pref_select_list,\'\');\n}\n\nfunction adi_prefs_delete_button($pref) {\n// X marks the delete spot\n	$step = \'remove\';\n	$url = \'?event=\'.\'adi_prefs_admin\'.a.\'step=\'.$step.a.\'something=\'.$pref;\n	return\n		\'<a href=\"\'\n		.$url\n		.\'\" class=\"dlink\" title=\"\'.adi_prefs_gtxt(\'adi_prefs_remove_pref\').\'\" onclick=\"return verify(\\\'\'\n		.adi_prefs_gtxt(\'adi_prefs_remove_pref_query\',array(\'{pref}\'=>$pref))\n		.\'\\\')\">&#215;</a>\';\n}\n\nfunction adi_prefs_set_match() {\n// create list of sets whose pref values match the current values\n	global $adi_prefs_current_prefs,$adi_prefs_list,$adi_prefs_set_list;\n\n	$matches = array();\n	foreach ($adi_prefs_set_list as $set_name => $value) {\n		$set_match = TRUE;\n		if ($set_name != \'current\') {\n			foreach ($adi_prefs_list as $pref_name => $value) {\n				$set_match = $set_match && adi_prefs_get($pref_name,$set_name) == $adi_prefs_current_prefs[$pref_name];\n			}\n			if ($set_match)\n				$matches[] = $set_name;\n		}\n	}\n	return $matches;\n}\n\nfunction adi_prefs_gtxt($phrase,$atts=array()) {\n// will check installed language strings before embedded English strings - to pick up Textpack\n// - for TXP standard strings gTxt() & adi_prefs_gtxt() are functionally equivalent\n	global $adi_prefs_gtxt;\n\n	if (strpos(gTxt($phrase,$atts),$phrase) !== FALSE) { // no TXP translation found\n		if (array_key_exists($phrase,$adi_prefs_gtxt)) // adi translation found\n			return strtr($adi_prefs_gtxt[$phrase],$atts);\n		else // last resort\n			return $phrase;\n		}\n	else // TXP translation\n		return gTxt($phrase,$atts);\n}\n\n/*\n	Input control functions copied from standard TXP\n	- renamed\n	- modified\n		- to just return value of <input> name is blank\n		- removed <input> IDs\n*/\n\nfunction adi_prefs_prod_levels($name,$val) {\n// based on prod_levels() from txp_prefs.php\n	$vals = array(\n		\'debug\'		=> gTxt(\'production_debug\'),\n		\'testing\' => gTxt(\'production_test\'),\n		\'live\'		=> gTxt(\'production_live\'),\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_yesnoradio($field,$var) {\n// based on yesnoradio() and radioSet() from txplib_forms()\n	$vals = array(\n		\'0\' => gTxt(\'no\'),\n		\'1\' => gTxt(\'yes\')\n	);\n	if ($field) { // input name supplied\n		$out = array();\n		foreach ($vals as $a => $b) {\n			$out[] = \'<input type=\"radio\" name=\"\'.$field.\'\" value=\"\'.$a.\'\" class=\"radio\"\';\n			$out[] = ($a == $var) ? \' checked=\"checked\"\' : \'\';\n			$out[] = \' /><label>\'.$b.\'</label> \';\n		}\n		return join(\'\', $out);\n	}\n	else // display only\n		return $vals[$var];\n}\n\nfunction adi_prefs_commentmode($name,$val) {\n// based on commentmode() from txp_prefs.php\n	$vals = array(\n		\'0\' => gTxt(\'nopopup\'),\n		\'1\' => gTxt(\'popup\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_weeks($name,$val) {\n// based on weeks() from txp_prefs.php\n	$weeks = gTxt(\'weeks\');\n	$vals = array(\n		\'0\' => gTxt(\'never\'),\n		7	=> \'1 \'.gTxt(\'week\'),\n		14	=> \'2 \'.$weeks,\n		21	=> \'3 \'.$weeks,\n		28	=> \'4 \'.$weeks,\n		35	=> \'5 \'.$weeks,\n		42	=> \'6 \'.$weeks\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val, \'\', \'\', $name);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_logging($name,$val) {\n// based on logging() from txp_prefs.php\n	$vals = array(\n		\'all\'	=> gTxt(\'all_hits\'),\n		\'refer\' => gTxt(\'referrers_only\'),\n		\'none\'	=> gTxt(\'none\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_dateformats($name,$val) {\n// based on dateformats() from txp_prefs.php\n	$dayname = \'%A\';\n	$dayshort = \'%a\';\n	$daynum = is_numeric(@strftime(\'%e\')) ? \'%e\' : \'%d\';\n	$daynumlead = \'%d\';\n	$daynumord = is_numeric(substr(trim(@strftime(\'%Oe\')), 0, 1)) ? \'%Oe\' : $daynum;\n	$monthname = \'%B\';\n	$monthshort = \'%b\';\n	$monthnum = \'%m\';\n	$year = \'%Y\';\n	$yearshort = \'%y\';\n	$time24 = \'%H:%M\';\n	$time12 = @strftime(\'%p\') ? \'%I:%M %p\' : $time24;\n	$date = @strftime(\'%x\') ? \'%x\' : \'%Y-%m-%d\';\n	$formats = array(\n            \"$monthshort $daynumord, $time12\",\n            \"$daynum.$monthnum.$yearshort\",\n            \"$daynumord $monthname, $time12\",\n            \"$yearshort.$monthnum.$daynumlead, $time12\",\n            \"$dayshort $monthshort $daynumord, $time12\",\n            \"$dayname $monthname $daynumord, $year\",\n            \"$monthshort $daynumord\",\n            \"$daynumord $monthname $yearshort\",\n            \"$daynumord $monthnum $year - $time24\",\n            \"$daynumord $monthname $year\",\n            \"$daynumord $monthname $year, $time24\",\n            \"$daynumord. $monthname $year\",\n            \"$daynumord. $monthname $year, $time24\",\n            \"$year-$monthnum-$daynumlead\",\n            \"$year-$daynumlead-$monthnum\",\n            \"$date $time12\",\n            \"$date\",\n            \"$time24\",\n            \"$time12\",\n            \"$year-$monthnum-$daynumlead $time24\",\n	);\n	$ts = time();\n	$vals = array();\n	foreach ($formats as $f) {\n		if ($d = safe_strftime($f, $ts)) {\n			$vals[$f] = $d;\n		}\n	}\n	$vals[\'since\'] = gTxt(\'hours_days_ago\');\n	if ($name) // input name supplied\n		return selectInput($name, array_unique($vals), $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_commentsendmail($name,$val) {\n// based on commentsendmail() from txp_prefs.php\n	$vals = array(\n	\'1\'	=> gTxt(\'all\'),\n	\'0\' => gTxt(\'none\'),\n	\'2\'	=> gTxt(\'ham\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_permlinkmodes($name,$val) {\n// based on permlinkmodes() from txp_prefs.php\n	$vals = array(\n		\'messy\' => gTxt(\'messy\'),\n		\'id_title\' => gTxt(\'id_title\'),\n		\'section_id_title\' => gTxt(\'section_id_title\'),\n		\'year_month_day_title\' => gTxt(\'year_month_day_title\'),\n		\'section_title\' => gTxt(\'section_title\'),\n		\'title_only\' => gTxt(\'title_only\'),\n		// \'category_subcategory\' => gTxt(\'category_subcategory\')\n        );\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_gmtoffset_select($name,$val) {\n// based on gmtoffset_select() from txp_prefs.php\n	$key = safe_field(\'val\', \'txp_prefs\', \"name=\'timezone_key\'\");\n	$tz = new timezone;\n	$ui = $tz->selectInput(\'timezone_key\', $key, true, \'\', \'gmtoffset\');\n	$tzd = $tz->details();\n	if ($name) // input name supplied\n		return $ui;\n	else // display only\n		return $tzd[$key][\'offset\']; // TO BE FIXED UP\n}\n\nfunction adi_prefs_themename($name,$val) {\n// based on themename() from txp_prefs.php\n	$themes = theme::names();\n	foreach ($themes as $t) {\n		$theme = theme::factory($t);\n		if ($theme) {\n			$m = $theme->manifest();\n			$title = empty($m[\'title\']) ? ucwords($theme->name) : $m[\'title\'];\n			$vals[$t] = $title;\n			unset($theme);\n		}\n	}\n	asort($vals, SORT_STRING);\n	if ($name)\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_languages($name,$val) {\n// based on languages() from txp_prefs.php\n	$installed_langs = safe_column(\'lang\', \'txp_lang\', \"1 = 1 group by lang\");\n	$vals = array();\n	foreach ($installed_langs as $lang) {\n		$vals[$lang] = safe_field(\'data\', \'txp_lang\', \"name = \'\".doSlash($lang).\"\' AND lang = \'\".doSlash($lang).\"\'\");\n		if (trim($vals[$lang]) == \'\')\n			$vals[$lang] = $lang;\n	}\n	asort($vals);\n	reset($vals);\n	$out = n.\'<select name=\"\'.$name.\'\" class=\"list\">\';\n	foreach ($vals as $avalue => $alabel) {\n		$selected = ($avalue == $val || $alabel == $val) ?\n		\' selected=\"selected\"\' :\n		\'\';\n		$out .= n.t.\'<option value=\"\'.htmlspecialchars($avalue).\'\"\'.$selected.\'>\'.htmlspecialchars($alabel).\'</option>\'.n;\n	}\n	$out .= n.\'</select>\';\n	if ($name) // input name supplied\n		return $out;\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_pref_text($name,$val) {\n// based on pref_text() from txplib_html.php\n	$vals = array(\n		USE_TEXTILE          => gTxt(\'use_textile\'),\n		CONVERT_LINEBREAKS   => gTxt(\'convert_linebreaks\'),\n		LEAVE_TEXT_UNTOUCHED => gTxt(\'leave_text_untouched\')\n	);\n	if ($name) // input name supplied\n		return selectInput($name, $vals, $val);\n	else // display only\n		return $vals[$val];\n}\n\nfunction adi_prefs_default_event($name,$val) {\n// based on default_event() from txp_prefs.php\n	$vals = areas();\n	$out = array();\n	foreach ($vals as $a => $b) {\n		if (count($b) > 0) {\n			$out[] = n.t.\'<optgroup label=\"\'.gTxt(\'tab_\'.$a).\'\">\';\n			foreach ($b as $c => $d) {\n				$out[] = n.t.t.\'<option value=\"\'.$d.\'\"\'.( $val == $d ? \' selected=\"selected\"\' : \'\' ).\'>\'.$c.\'</option>\';\n				if ($val == $d) $this_event = $c; // note the event\n			}\n			$out[] = n.t.\'</optgroup>\';\n		}\n	}\n	if ($name) // input name supplied\n		return n.\'<select name=\"\'.$name.\'\" class=\"list\">\'. join(\'\', $out). n.\'</select>\';\n	else // display only\n		return $this_event;\n}\n\nfunction adi_prefs_is_dst($name,$val) {\n// original does some sexy javascript - gloss over for now!\n	return adi_prefs_yesnoradio($name,$val);\n}\n', '38131a7b1086bcf4dd42141c8148d7c9', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_recent_tab', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.6', 'Recent Items Tab', '<h1><strong>adi_recent_tab</strong> &#8211; Recent Items</h1>\n\n<p>To help speed up workflow &#8211; especially during website construction &#8211; this plugin gives you a menu of recently accessed Textpattern Admin items.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Install &amp; activate the plugin in the normal way. A new top level Admin tab will then be available.</p>\n\n<p>The Recent dropdown menu shows a list of articles/images/links/pages/forms etc that you have recently visited in the Admin interface.</p>\n\n<p>This menu contains items that you have <em>visited</em> &#8211; as opposed to the ‘Recent articles’ list in the Article tab which only contains articles that have been recently modified.</p>\n\n<h2><strong>The Menu</strong></h2>\n\n<p>The Recent menu gives you direct access to:</p>\n\n<ul>\n	<li>recently visited items</li>\n	<li>and their tabs</li>\n</ul>\n\n<h2><strong>The Tab</strong></h2>\n\n<p>The recent items are also available on a standalone page &#8211; in <span class=\"caps\">TXP</span> 4.5 simply click on the Recent tab itself, in <span class=\"caps\">TXP</span> 4.6 look in the Extensions menu.</p>\n\n<p>Here you will also find preferences to:</p>\n\n<ul>\n	<li>specify which recently visited items you want to record</li>\n	<li>set the number of items to remember</li>\n	<li>choose whether to have article/image/link/file IDs included in the menu</li>\n	<li>choose whether to have image alt used in the menu</li>\n</ul>\n\n<h2><strong>Privileges</strong></h2>\n\n<p>You can adjust who gets to use the plugin by setting the privileges in options.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on ‘Install textpack’.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <em>Admin &#8211; Language</em> tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_recent_tab\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=36928\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_recent_tab\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_recent_tab - Recent Items Tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- refixed for critical, admin interface killing, bug in TXP 4.6\n			- responsive layout & styling for recent items tab\n			- fix: recognises \"upload file from existing\"\n			- enhancement: pref for image alt text (for Uli)\n	0.5		- change: IDs display switched on by default\n			- change: articles heading link now goes to Articles (list) tab rather than Write\n			- fix: error when reordering images/files in list\n			- fix: error when changing number of items per page\n			- fix: error when file upload failed (e.g. file too big/file already exists)\n			- fix: article titles etc that are blank with multiple spaces are treated as Untitled\n			- fix: unslashed quotes in DB queries\n			- fix: tabs no longer listed if user lacks privs\n			- fix: upload thumbnail now ignored\n			- fix: all new section names sanitised & recognised\n			- enhancement: plugin privs\n			- code tidy up (thanks gocom)\n			- TXP 4.5+\n			- tested & fixed for TXP 4.6\n	0.4		- TXP 4.5-ified (particularly to cope with new Sections tab & new-style Links tab)\n			- fix: record new links properly\n			- fix: losing image (file) name if both alt & caption blank\n	0.3		- style improvements to cope with long titles\n			- fix: style for latest version of Hive theme (thanks Uli)\n			- fix: error message in images & files search result page (thanks Uli)\n	0.2		- fix: cope with image replace & file replace properly\n			- fix: cope with style create/save/delete properly\n			- new pref: include article/image/link/file IDs\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $adi_recent_tab_debug;\n\n	$adi_recent_tab_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_recent_tab_init();\n\n}\n\nfunction adi_recent_tab_init() {\n	global $event,$step,$textarray,$txp_user,$prefs,$adi_recent_tab_outlaws,$adi_recent_tab_url,$adi_recent_tab_plugin_status,$adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_exclude_combo,$adi_recent_tab_include_combo,$adi_recent_tab_prefs,$adi_recent_tab_debug,$adi_recent_tab_txp460;\n\n	// meet the new boss?\n	$adi_recent_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// plugin lifecycle\n	register_callback(\'adi_recent_tab_lifecycle\',\'plugin_lifecycle.adi_recent_tab\');\n\n	// Textpack\n	$adi_recent_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_recent_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_recent_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_recent_tab_gtxt = array(\n		\'adi_include\' => \'Include:\',\n		\'adi_include_ids\' => \'Include IDs\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_recent_items\' => \'Recent items\',\n		\'adi_recent_tab\' => \'Recent\',\n		\'adi_recent_tab_max_items\' => \'Maximum number of items\',\n		\'adi_tab\' => \'Tab\',\n		\'adi_textpack\' => \'Textpack\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_recent_tab_gtxt;\n\n	/*\n		Events & Steps - A minefield of inconsistencies ...\n\n						Step			other vars\n	Event=article\n		Write			create			-			(empty create tab)\n		Edit			edit			ID\n		Save			edit			ID\n		Publish			create -> edit	$GLOBALS[\'ID\']\n\n	Event=image\n		Images list		-\n			(search)	image_list\n			(sort)		list\n			(paging)	image_change_pageby\n		Image upload	image_insert	$GLOBALS[\'ID\']\n		Image edit		image_edit		id\n		Image save		image_edit		id\n		Image replace	image_replace	id\n		Thumbnail upld	thumbnail_insert\n\n	Event=page\n		Pages/default	-\n		Page edit		-				name\n		Save			page_save		name\n		Copy			page_save		name, newname\n		Create page		page_save		newname\n\n	Event=form\n		Forms/default	-\n		Form edit		form_edit		name\n		Form save		form_save\n\n	Event=link\n		Links list		- -> link_edit\n			(search)	link_list\n			(sort)		list\n			(paging)	link_change_pageby\n		Link edit		link_edit		id\n		Link save		link_save		id\n		Link create		link_post		$GLOBALS[\'ID\']	(pre-4.5.0)\n		Link create		link_save		$GLOBALS[\'ID\']	(post-4.5.0)\n\n	Event=file\n		Files list		-\n			(search)	file_list\n			(sort)		list\n			(paging)	file_change_pageby\n		Upload			file_insert		$GLOBALS[\'ID\']\n		Save			file_save		?\n		File edit		file_edit		id\n		File replace	file_replace	id\n		From existing	file_create		name\n\n	Event=css\n		Style/default	-\n		Style edit		-				name\n		Save			css_save		name\n		New style		pour			newname\n		Copy style		css_save		name\n\n	Event=section\n		Section list	-\n			(search)	sec_section_list\n			(sort)		list\n			(paging)	section_change_pageby\n		Section edit	section_edit	name\n		Section create	section_edit	-		(empty create tab)\n		Section save	section_save	name\n		Section create	section_save	newname	(post-4.5.0)\n	*/\n\n	// the privileged few ...\n	$adi_recent_tab_include = array(\'article\',\'image\',\'page\',\'form\',\'link\',\'file\',\'css\',\'section\'); // if you\'re not on the list, you\'re not getting in\n\n	// privileges for the privileged\n	$adi_recent_tab_include_privs = array(\n		\'article\' => \'list\',\n		\'image\' => \'image\',\n		\'page\' => \'page\',\n		\'form\' => \'form\',\n		\'link\' => \'link\',\n		\'file\' => \'file\',\n		\'css\' => \'css\',\n		\'section\' => \'section\',\n	);\n\n	if ($adi_recent_tab_debug)\n		foreach ($adi_recent_tab_include as $index => $tab)\n			if (has_privs($adi_recent_tab_include_privs[$tab]))\n				echo $tab.sp.\'- has privs\'.br;\n			else\n				echo $tab.sp.\'- no privs\'.br;\n\n	// weed out the unworthy\n	foreach ($adi_recent_tab_include as $index => $tab)\n		if (!has_privs($adi_recent_tab_include_privs[$tab]))\n			unset($adi_recent_tab_include[$index]);\n\n	// it\'s what we call \'em\n	$adi_recent_tab_event_gtxt = array(\n		\'article\'	=> \'tab_list\',\n		\'css\'		=> \'tab_style\',\n		\'file\'		=> \'tab_file\',\n		\'form\'		=> \'tab_forms\',\n		\'image\'		=> \'tab_image\',\n		\'link\'		=> \'tab_link\',\n		\'page\'		=> \'tab_pages\',\n		\'section\'	=> \'tab_sections\',\n	);\n\n	// not so fast ...\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^article$\', \'step\' => \'^$\'); // empty create article tab - event=\"article\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'^$\'); // image list tab - event=\"image\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'image_multi_edit\'); // image list multi-edit - event=\"image\", step contains image_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'image_replace\'); // image replace - event=\"image\", step contains image_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'list\'); // image list - event=\"image\", step contains list (search results=image_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'^thumbnail_insert$\'); // thumbnail upload - event=\"image\", step=thumbnail_insert\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^page$\', \'step\' => \'page_new\'); // new page tab - event=\"page\", step contains page_new\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^form$\', \'step\' => \'form_multi_edit\'); // form list multi-edit - event=\"form\", step contains form_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'step\' => \'link_edit\', \'id\' => \'^$\'); // link list tab\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'id\' => \'^$\'); // TXP 4.5 link list tab\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'step\' => \'link_multi_edit\'); // link list tab - event=\"link\", step contains link_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'^$\'); // file list tab - event=\"file\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'file_replace\'); // file replace - event=\"file\", step contains file_replace\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'list\'); // file list - event=\"file\", step contains list (search results=file_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^css$\', \'step\' => \'pour\'); // style list tab - event=\"css\", step=pour (create style)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'section_edit\', \'name\' => \'^$\'); // empty section create tab (TXP 4.5.0)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'list\'); // section list - event=\"section\", step contains list (search results=sec_section_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'save\'); // any step containing \"save\" (e.g. page_save, form_save, link_save)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'create\'); // any step containing \"create\" (e.g. new article create, form create)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'apply\'); // any step containing \"apply\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'update\'); // any step containing \"update\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'delete\'); // any step containing \"delete\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'change_pageby\'); // any step containing \"change_pageby\", e.g. image_change_pageby, when changing # per page\n	// don\'t log initial page/form/style tab visit (i.e. default by default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^page$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default page tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^form$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default form tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^css$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default style tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default section list tab (TXP 4.5.0)\n\n	// oh, go on then ...\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'page\', \'step\' => \'page_save\'); // create new page\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'form\', \'step\' => \'form_save\'); // create new form\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'css\', \'step\' => \'css_save\'); // create new form\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'file\', \'step\' => \'file_create\'); // create file from existing\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'link\', \'step\' => \'link_post\'); // create new link (pre-4.5.0)\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'link\', \'step\' => \'link_save\'); // create new link (post-4.5.0)\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'section\', \'step\' => \'section_save\'); // create new section (TXP 4.5.0+)\n\n	// preferences & defaults\n	$adi_recent_tab_prefs = array(\n		\'adi_recent_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'private\' => 0, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n		\'adi_recent_tab_list\' => array(\'value\' => implode(\',\',$adi_recent_tab_include), \'blank\' => 0, \'private\' => 1, \'input\' => \'custom\', \'label\' => \'adi_include\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_max_items\' => array(\'value\' => \'5\', \'blank\' => 0, \'private\' => 1, \'input\' => \'text_input\', \'label\' => \'adi_recent_tab_max_items\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_ids\' => array(\'value\' => \'1\', \'blank\' => 1, \'private\' => 1, \'input\' => \'yesnoradio\', \'label\' => \'adi_include_ids\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_img_alt\' => array(\'value\' => \'1\', \'blank\' => 1, \'private\' => 1, \'input\' => \'yesnoradio\', \'label\' => \'alt_text\', \'tab\' => \'admin\'),\n	);\n\n	// plugin privs to be excluded\n	$adi_recent_tab_outlaws = array(0);\n\n	// who\'s playing today?\n	add_privs(\'adi_recent_tab\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items tab\n	add_privs(\'tab.recent\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items menu\n\n	// do the business\n	if (adi_recent_tab_installed() && has_privs(\'adi_recent_tab\')) {\n		// menu style for all\n		register_callback(\'adi_recent_tab_menu_style\',\'admin_side\',\'head_end\');\n		// tab style for tab only\n		if ($event == \"adi_recent_tab\")\n			register_callback(\'adi_recent_tab_style\',\'admin_side\',\'head_end\');\n		// adi_recent_tab tab\n		register_callback(\"adi_recent_tab\",\"adi_recent_tab\");\n		$textarray[\'tab_recent\'] = gTxt(\'adi_recent_tab\'); // to get something meaningful into tab\n		// put logger in the footer - to catch the elusive newly created article/image IDs etc\n		register_callback(\'adi_recent_tab_logger\',\'admin_side\',\'footer\');\n		// TXP 4.5: recent tab under recent items menu; TXP 4.6: recent tab under extensions menu (due to lack of top-level click-through)\n		if ($adi_recent_tab_txp460) {\n			// recent items tab under extensions\n			register_tab(\'extensions\',\'adi_recent_tab\',gTxt(\'adi_recent_items\'));\n			// separate recent items menu\n			add_privs(\'adi_recent_tab_menu\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items tab\n			register_tab(\'recent\',\'adi_recent_tab_menu\',gTxt(\'adi_recent_items\'));\n			register_callback(\'adi_recent_tab_menu\',\'adi_recent_tab_menu\');\n			// inject markup to create recent items menu\n			register_callback(\'adi_recent_tab_menu_markup_mega\',\'admin_side\',\'footer\'); // markup in the footer, shifted by jQuery\n		}\n		else {\n			// recent items tab under separate recent items menu\n			register_tab(\'recent\',\'adi_recent_tab\',gTxt(\'adi_recent_items\'));\n			// inject markup to create recent items menu\n			adi_recent_tab_menu_ob();\n		}\n	}\n\n	// script\n	if ($adi_recent_tab_txp460)\n		register_callback(\'adi_recent_tab_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_recent_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_recent_tab\');\n	if ($adi_recent_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_recent_tab\',\'1,2,6\');\n		register_callback(\'adi_recent_tab_options\',\'plugin_prefs.adi_recent_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_recent_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_recent_tab_options\',\'adi_recent_tab options\');\n		register_callback(\'adi_recent_tab_options\',\'adi_recent_tab_options\');\n	}\n}\n\nfunction adi_recent_tab_style() {\n// style for tab\n	echo\n		\'<style type=\"text/css\">\n			/* adi_recent_tab */\n			#adi_recent_tab_tab, #adi_recent_tab_tab ul { overflow:hidden; list-style:none; margin:0; padding:0 }\n			#adi_recent_tab_tab li { list-style:none; margin:0; padding:0 }\n			#adi_recent_tab_tab > li { float:left; width:47%; margin:0 6% 1% 0 }\n			#adi_recent_tab_tab > li:nth-child(2n) { margin-right:0 }\n			#adi_recent_tab .adi_recent_tab_prefs { margin:4em 0 2em; border-top:1px solid #ccc }\n			#adi_recent_tab input.adi_recent_tab_max_items { width:3em }\n			/* responsive */\n			@media (max-width: 47em) {\n				#adi_recent_tab_tab > li { float:none; width:100% }\n				.adi_recent_tab_prefs p.adi_recent_tab_list label { display:block; margin-top:0.5em }\n			}\n			/* TXP 4.5 */\n			div.txp-body h1, div.txp-body h2 { font-weight:bold }\n			div.txp-body .adi_recent_tab_prefs h2 { margin-top:0.5em }\n			/* Hive TXP 4.5 adjustments - recent tab page given class=\"recent\" which is Hive styling for Recent Articles! */\n			.txp-nav li { margin-left:0 }\n		</style>\';\n}\n\nfunction adi_recent_tab_menu_style() {\n// style for dropdown menu\n	echo\n		\'<style type=\"text/css\">\n			/* adi_recent_tab */\n			/* TXP 4.5 Remora */\n			#nav .adi_recent_tab_menu_title { font-weight:bold }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul { width:25em; padding-top:0.5em; padding-bottom:0.5em }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li { width:auto; height:auto }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li:first-child { display:none }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:23em; height:auto; line-height:1.2; padding-top:0.3em; padding-bottom:0.3em }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li a span { font-weight:bold }\n			.txp-header #nav a[href=\"?event=adi_recent_tab\"] + ul { z-index:9999 }\n			/* TXP 4.5 Hive */\n			div.txp-nav .adi_recent_tab_menu_title { font-weight:bold }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { padding-bottom:0.5em }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:25em }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li { float:none; width:auto }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:hover { background-color:#ffefaf }\n			/* TXP 4.6 Hive */\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:first-child { display:none }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:50em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li { float:left; width:50% }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:hover { background-color:transparent }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:nth-child(2n) { clear:left }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2 { margin:0.5em 0 0; font-size:1em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2:hover { background-color:#ffefaf }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:auto; padding-top:0.3em; padding-bottom:0.3em; word-wrap:break-word }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul ul { width:auto }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { left:-20em; border-radius:0.5em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul:before { left:18em } /* the pointy bit */\n			/* bot_write_tab_customize tab overrides */\n			body#page-bot_wtc_tab .txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2 { margin:0.5em 0 0; padding:0; text-align:left }\n			/* responsive */\n			@media (max-width: 47em) {\n				nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:auto }\n				nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:auto; padding-top:0.5em; padding-bottom:0.5em }\n			}\n		</style>\';\n}\n\nfunction adi_recent_tab_script() {\n// scriptimus\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_recent_tab menu\n		var closest_ul = $(\'a[href=\"?event=adi_recent_tab_menu\"]\').closest(\"ul\"); // find (grand)parent ul\n		var menu_id = $(closest_ul).attr(\'id\'); // i.e. \"#txp-nav-dropX-menu\"\n		$(\"ul#adi_recent_tab_inject > li\").insertAfter($(\"ul#\" + menu_id + \" li\")); // shift recent menu items from footer\n		$(\"ul#adi_recent_tab_inject\").remove(); // footer housekeeping\n		var top_link = $(closest_ul).prev(); // get top level menu item link\n		$(top_link).attr(\"href\",\"?event=adi_recent_tab\"); // restore click-through to recent_tab tab (also allows some styles to kick in)\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_recent_tab_logger() {\n// monitor each TXP admin page visit & decide what\'s to be recorded by adi_recent_tab\n	global $DB,$adi_recent_tab_debug,$event,$step,$txp_user,$adi_recent_tab_include,$adi_recent_tab_exclude_combo,$adi_recent_tab_include_combo,$adi_recent_tab_txp460;\n\n	if (!adi_recent_tab_installed()) // in case it\'s just been uninstalled\n		return;\n\n	$id = gps(\'id\'); // image id\n	$name = gps(\'name\'); // section/page/form/image name\n\n	if ($adi_recent_tab_debug) {\n		echo \'Event=\'.$event.\', Final Step=\'.$step.\', supplied id=\'.$id.\', supplied name=\'.$name.\'<br/>\';\n		echo \'Include event list:\';\n		dmp($adi_recent_tab_include);\n		echo \'Exclude combo list:\';\n		dmp($adi_recent_tab_exclude_combo);\n		echo \'Include combo list:\';\n		dmp($adi_recent_tab_include_combo);\n	}\n\n	// process included & excluded events/steps\n	// events of interest\n	$exclude = array_search($event,$adi_recent_tab_include) === FALSE;\n	if ($adi_recent_tab_debug && $exclude)\n		echo \'($adi_recent_tab_include) EXCLUDE EVENT: \'.$event.\'<br/>\';\n	// sort the men from the boys\n	if (!$exclude) {\n		// event/step combos to be discounted\n		$exclude = adi_recent_tab_filter($adi_recent_tab_exclude_combo,$event,$step,$id,$name);\n		if ($adi_recent_tab_debug && $exclude)\n			echo \'($adi_recent_tab_exclude_combo) \'.\"EXCLUDED EVENT/STEP/ID/NAME: $event,$step,$id,$name<br/>\";\n\n		// explicitly include these combos\n		$include = adi_recent_tab_filter($adi_recent_tab_include_combo,$event,$step,$id,$name);\n		if ($adi_recent_tab_debug && $include)\n			echo \'($adi_recent_tab_include_combo) \'.\"INCLUDED EVENT/STEP/ID/NAME: $event,$step,$id,$name<br/>\";\n		if ($include)\n			$exclude = FALSE;\n	}\n\n	if (!$exclude) { // massage stuff\n		// ID\n		if (empty($id)) // try finding an article ID\n			$id = gps(\'ID\');\n		if (isset($GLOBALS[\'ID\'])) // set the \"newly created\" ID to something\n			$globals_id = $GLOBALS[\'ID\'];\n		else\n			$globals_id = \'\';\n		if (empty($id) && ($event == \'article\')) // i.e. a newly created article\n			$id = $globals_id;\n		if (empty($id) && ($event == \'image\')) // i.e. a newly created image\n			$id = $globals_id;\n		if (empty($id) && ($event == \'link\')) // i.e. a newly created link\n			$id = $globals_id;\n		if (empty($id) && ($event == \'file\')) // i.e. a newly uploaded file\n			$id = $globals_id;\n		// NAME\n		if (($event == \'section\') && ($step == \'section_save\'))\n			$name = strtolower(sanitizeForUrl($name)); // sanitise (create/edit) section name (from txp_section.php)\n		if ((($event == \'page\') || ($event == \'form\')) && empty($name)) // fill in the \"default\" blank - needed if logging initial page/form tab visit\n			$name = \'default\';\n		if ($event == \'image\') // don\'t want to store image name coz we\'ve got the ID (& the name might change)\n			$name = \'\';\n		$newname = sanitizeForPage(gps(\'newname\')); // sanitise (create/copy) page name\n		if ($newname)\n			$name = $newname;\n		// STEP\n		$this_step = $step;\n		if (($event== \'image\') && ($step == \'image_insert\')) // tweak step - new image\n			$this_step = \'image_edit\';\n		if (($event== \'page\') && ($step == \'page_save\')) // new/save page\n			$this_step = \'\';\n		if (($event== \'form\') && ($step == \'form_save\')) // new/save form\n			$this_step = \'form_edit\';\n		if (($event== \'css\') && ($step == \'css_save\')) // new style/save style\n			$this_step = \'\';\n		if (($event== \'form\') && ($step == \'\')) // tweak step - click between forms\n			$this_step = \'form_edit\';\n		if (($event== \'link\') && ($step == \'link_post\')) // new link (pre-4.5.0)\n			$this_step = \'link_edit\';\n		if (($event== \'link\') && ($step == \'link_save\')) // new link (post-4.5.0)\n			$this_step = \'link_edit\';\n		if (($event== \'section\') && ($step == \'section_save\')) // new section (post-4.5.0)\n			$this_step = \'section_edit\';\n		if (($event== \'file\') && ($step == \'file_insert\')) // new file\n			$this_step = \'file_edit\';\n		if (($event== \'file\') && ($step == \'file_create\')) // new file from existing\n			$this_step = \'file_edit\';\n		if ($adi_recent_tab_debug)\n			echo \'Event=\'.$event.\', this_step=\'.$this_step.\', massaged id=\'.$id.\', massaged name=\'.$name.\'<br/>\';\n\n		if ($id == \'\' && (($event == \'article\') || ($event == \'image\') || ($event == \'link\') || ($event == \'file\'))) {\n			if ($adi_recent_tab_debug)\n				echo \'No ID, no update!\';\n		}\n		else {\n			// update adi_recent_tab DB table\n			$table = \'adi_recent_tab\';\n			$set = \'timestamp=now(), user=\"\'.doSlash($txp_user).\'\", event=\"\'.doSlash($event).\'\", step=\"\'.doSlash($this_step).\'\", id=\"\'.doSlash($id).\'\", name=\"\'.doSlash($name).\'\"\';\n			$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$this_step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.$name.\'\"\';\n			// it\'s me own safe_upsert ...\n			$r = safe_update($table,$set,$where,$adi_recent_tab_debug);\n			if ($adi_recent_tab_txp460) {\n				if (!($r and (mysqli_affected_rows($DB->link) or safe_count($table,$where,$adi_recent_tab_debug))))\n					safe_insert($table,$set,$adi_recent_tab_debug);\n			}\n			else {\n				if (!($r and (mysql_affected_rows() or safe_count($table,$where,$adi_recent_tab_debug))))\n					safe_insert($table,$set,$adi_recent_tab_debug);\n			}\n		}\n	}\n\n	// tidy up\n	adi_recent_tab_housekeeping();\n}\n\nfunction adi_recent_tab_filter($pattern_list,$event,$step,$id=\'\',$name=\'\') {\n// look for combination matches\n\n	$match = FALSE;\n	foreach ($pattern_list as $pair) {\n		$this_match = TRUE;\n		foreach ($pair as $var => $pattern)\n			$this_match = $this_match && preg_match(\'/\'.$pattern.\'/\',$$var);\n		$match = $match || $this_match;\n	}\n	return $match;\n}\n\nfunction adi_recent_tab_installed($table=\'adi_recent_tab\') {\n// test if table is present\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_recent_tab_install() {\n// install adi_recent_tab table in database\n	global $adi_recent_tab_debug;\n\n	$res = safe_query(\n		// \'id\' is a VARCHAR (rather than an INT) coz I need it to be blank sometimes\n		\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_recent_tab\')\n		.\"(	`timestamp` datetime NOT NULL default \'0000-00-00 00:00:00\',\n			`user` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`event` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`step` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`id` VARCHAR(12) NOT NULL DEFAULT \'\',\n			`name` VARCHAR(64) NOT NULL DEFAULT \'\'\n		);\"\n		,$adi_recent_tab_debug);\n 	return $res;\n}\n\nfunction adi_recent_tab_uninstall() {\n// uninstall adi_recent_tab\n	global $adi_recent_tab_debug,$adi_recent_tab_prefs;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_recent_tab\').\";\",$adi_recent_tab_debug);\n	// delete preferences\n	foreach ($adi_recent_tab_prefs as $this_pref => $value)\n		$res = $res && safe_delete(\'txp_prefs\',\"name = \'$this_pref\'\",$adi_recent_tab_debug);\n	return $res;\n}\n\nfunction adi_recent_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_recent_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// install/reinstall: \"installed\"; make active: \"enabled\"; make inactive: \"disabled\"; delete: \"disabled\" then \"deleted\"\n// (as of TXP 4.5, reinstall only triggers \"installed\" event)\n// adi_lifecycle: \"installed\" -> install() regardless, upgrade() if required; \"deleted\" -> uninstall(); \"enabled\" & disabled\" -> do nothing\n	global $adi_recent_tab_debug;\n\n	$result = $upgrade_required = \'?\';\n	if ($step == \'installed\') {\n		$result = adi_recent_tab_install();\n// 		$upgrade_required = adi_recent_tab_upgrade();\n// 		if ($upgrade_required)\n// 			$result &= adi_recent_tab_upgrade(TRUE);\n	}\n	if ($step == \'deleted\')\n		$result = adi_recent_tab_uninstall();\n	if ($adi_recent_tab_debug)\n		echo \"event=$event,step=$step,result=$result,upgrade_required=$upgrade_required\";\n}\n\nfunction adi_recent_tab_housekeeping() {\n// delete oldest recents that exceed max count; make sure items still exist in DB\n	global $adi_recent_tab_debug,$txp_user,$adi_recent_tab_include,$adi_recent_tab_prefs;\n\n	$max_recent = adi_recent_tab_pref(\'adi_recent_tab_max_items\');\n\n	$sort = \' ORDER BY timestamp desc\';\n	$default_where = \" AND user=\'$txp_user\'\";\n\n	if ($adi_recent_tab_debug)\n		echo \'<br/>Housekeeping (deceased entries):<br/>\';\n\n	// delete entries that don\'t exist ... the recently deceased\n	foreach ($adi_recent_tab_include as $recent) {\n		$table = \'txp_\'.$recent;\n		if ($recent == \'article\')\n			$table = \'textpattern\';\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'\".$recent.\"\'\".$default_where.$sort,$adi_recent_tab_debug);\n		foreach ($rs as $index => $row) {\n			extract($row);\n			// \"id\" entries\n			if (($recent == \'article\') || ($recent == \'image\') || ($recent == \'link\') || ($recent == \'file\')) {\n				$row = safe_row(\'*\',$table,\"id=\'$id\'\",$adi_recent_tab_debug);\n				if (empty($row)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\';\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n			// \"name\" entries\n			if (($recent == \'page\') || ($recent == \'form\') || ($recent == \'css\') || ($recent == \'section\')) {\n				$row = safe_row(\'*\',$table,\"name=\'\".doSlash($name).\"\'\",$adi_recent_tab_debug);\n				if (empty($row)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\';\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n		}\n	}\n\n	if ($adi_recent_tab_debug)\n		echo \'Housekeeping (remove surplus):<br/>\';\n\n	// lose the surplus\n	foreach ($adi_recent_tab_include as $recent) {\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'\".$recent.\"\'\".$default_where.$sort,$adi_recent_tab_debug);\n		if (count($rs) > $max_recent)\n			foreach ($rs as $index => $row) {\n				extract($row);\n				if (!($index < $max_recent)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\'; // BIG GLOBAL FOR THIS WHERE???\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n	}\n}\n\nfunction adi_recent_tab_links($event,$wrap=TRUE) {\n// return a <ul><li> or <li> of all the \"recent\" links\n	global $adi_recent_tab_debug,$adi_recent_tab_prefs,$txp_user;\n\n//	$sort_method = \'timestamp\';\n	$sort_method = \'alphanumeric\';\n\n	$default_where = \" AND user = \'$txp_user\'\";\n\n	if ($sort_method == \'timestamp\') {\n		$sort = \' ORDER BY timestamp desc\';\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event = \'$event\'\".$default_where.$sort,$adi_recent_tab_debug);\n	}\n	else if ($sort_method == \'alphanumeric\') {\n		if ($event == \'article\') {\n			$sort = \' ORDER BY Title\';\n			$rs = safe_rows(\'Title,textpattern.ID,adi_recent_tab.*\',\'textpattern, adi_recent_tab\',\"event = \'$event\' AND textpattern.ID = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'image\') {\n			$sort = \' ORDER BY alt,txp_image.name\';\n			// note that actual name is last in list of fields - image name not stored by adi_recent_tab, so don\'t want blank to override proper name\n			$rs = safe_rows(\'adi_recent_tab.*,alt,txp_image.name\',\'txp_image, adi_recent_tab\',\"event = \'$event\' AND txp_image.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'file\') {\n			$sort = \' ORDER BY title,filename\';\n			$rs = safe_rows(\'title,filename,adi_recent_tab.*\',\'txp_file, adi_recent_tab\',\"event = \'$event\' AND txp_file.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'link\') {\n			$sort = \' ORDER BY linkname,url\';\n			$rs = safe_rows(\'linkname,url,adi_recent_tab.*\',\'txp_link, adi_recent_tab\',\"event = \'$event\' AND txp_link.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else { // page, form, style, section\n			$sort = \' ORDER BY name\';\n			$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'$event\'\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n	}\n\n	if ($adi_recent_tab_debug)\n		dmp($rs);\n\n	$out = \'\';\n\n	foreach ($rs as $index => $row) {\n		$item = \'\';\n		extract($row);\n		// link text fiddling\n		if ($event == \'article\') { // use article title, or Untitled if empty\n			$Title = trim($Title);\n			if (empty($Title))\n				$link_text = gTxt(\'untitled\');\n			else\n				$link_text = $Title;\n		}\n		else if ($event == \'image\') { // use alt text, or name if blank\n			$alt = trim($alt);\n			if (empty($alt) || !adi_recent_tab_pref(\'adi_recent_tab_img_alt\'))\n				$link_text = $name;\n			else\n				$link_text = $alt;\n		}\n		else if ($event == \'link\') { // use link name, or url if blank\n			$linkname = trim($linkname);\n			if (empty($linkname))\n				$link_text = $url;\n			else\n				$link_text = $linkname;\n		}\n		else if ($event == \'file\') { // use title, or filename if blank\n			$title = trim($title);\n			if (empty($title))\n				$link_text = $filename;\n			else\n				$link_text = $title;\n		}\n		else if ($event == \'section\') // use name\n				$link_text = $name;\n		// include IDs?\n		if (adi_recent_tab_pref(\'adi_recent_tab_ids\') && ($id != \'\'))\n			$link_text .= \' (\'.tag($id,\'span\').\')\';\n		// links to items\n		if ($event == \'article\')\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'ID=\'.$id.\'\">\'.$link_text.\'</a>\';\n		else if (($event == \'image\') || ($event == \'link\') || ($event == \'file\'))\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'id=\'.$id.\'\">\'.$link_text.\'</a>\';\n		else if ($event == \'section\')\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name.\'\">\'.$link_text.\'</a>\';\n		else\n			$item .= \'<a href=\"?event=\'.$event.a.\'name=\'.$name.\'\">\'.$name.\'</a>\';\n		$out .= tag($item,\'li\',\' class=\"adi_recent_tab_menu_item\"\').n;\n	}\n\n	if ($wrap)\n		return tag($out,\'ul\');\n	else\n		return $out;\n}\n\nfunction adi_recent_tab($event,$step) {\n// the page that displays all the \"recent\" links\n	global $adi_recent_tab_debug,$adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs,$adi_recent_tab_txp460;\n\n	$message= \'\';\n	if ($step == \'update_prefs\') {\n		$result = adi_recent_tab_update_prefs(\'admin\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		adi_recent_tab_housekeeping(); // do extra housekeeping to reflect possible change in max items (habitually done at bottom of page)\n	}\n\n	// generate page\n	pagetop(\'adi_recent_tab \'.gTxt(\'adi_recent_tab\'),$message);\n\n	echo \'<div id=\"adi_recent_tab\">\';\n\n	echo tag(gTxt(\'adi_recent_items\'),\'h1\');\n\n	// generate list of recents\n	echo tag(implode(n,adi_recent_tab_list_markup()),\'ul\',\' id=\"adi_recent_tab_tab\"\');\n\n	// preferences\n	echo adi_recent_tab_pref_inputs($event,\'admin\');\n\n	echo \'</div>\';\n\n	if ($adi_recent_tab_debug)\n		echo adi_recent_tab_db_dump();\n}\n\nfunction adi_recent_tab_menu_ob() {\n// the old-fashioned magic that gets the recent items into adi_recent_tab\'s menu - TXP 4.5\n 	ob_start(\'adi_recent_tab_menu_inject\');\n}\n\nfunction adi_recent_tab_menu_inject($buffer) {\n// another bit of arcane magic - TXP 4.5\n	global $DB, $adi_recent_tab_txp460;\n\n	if(!isset($DB)) $DB = new db;\n\n	$pattern = \'#\'.gTxt(\'adi_recent_items\').\'</a></li>#sU\';\n	$insert = \'adi_recent_tab_menu_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer); // this causes grief in 4.6 (seemingly just for me!)\n	return $buffer;\n}\n\nfunction adi_recent_tab_menu_markup($matches) {\n// generate markup for adi_recent_tab recent item lists - TXP 4.5\n	global $adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs;\n\n	$out = \'\';\n	$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n	foreach ($tab_list as $this_event) {\n		$link_event = $this_event;\n		if ($this_event == \'article\')\n			$link_event = \'list\';\n		$out .=\n			\'<li><a class=\"adi_recent_tab_menu_title\" href=\"?event=\'.$link_event.\'\">\'\n			.gTxt($adi_recent_tab_event_gtxt[$this_event])\n			.\'</a></li>\'\n			.adi_recent_tab_links($this_event,FALSE);\n	}\n	return $matches[0].$out;\n}\n\nfunction adi_recent_tab_list_markup() {\n// generate markup for dropdown or tab\n// <ul>\n//		<li>\n//			<h2><a>TAB</a></h2>\n//			<a>RECENT LINK 1</a>\n//			<a>RECENT LINK 2</a> etc\n//		</li>\n	global $adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs;\n	$out = array();\n	$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n	foreach ($tab_list as $index => $this_event) {\n		$tab_event = $this_event;\n		if ($this_event == \'article\')\n			$tab_event = \'list\';\n		$out[] =\n			tag(\n				tag(\n					\'<a href=\"?event=\'.$tab_event.\'\">\'.gTxt($adi_recent_tab_event_gtxt[$this_event]).\'</a>\'\n					,\'h2\'\n				)\n				.adi_recent_tab_links($this_event)\n				,\'li\'\n			);\n	}\n	return $out;\n}\n\nfunction adi_recent_tab_menu_markup_mega() {\n// generate mega menu markup - TXP 4.6\n	echo tag(implode(n,adi_recent_tab_list_markup()),\'ul\',\' id=\"adi_recent_tab_inject\"\');\n}\n\nfunction adi_recent_tab_db_dump($table=\'adi_recent_tab\') {\n// print out contents of database table\n\n	$result = mysql_query(\"SELECT * FROM {$table}\");\n	$fields_num = mysql_num_fields($result);\n	$out = \"<br/><table><tr>\";\n	// table headers\n	for ($i = 0; $i < $fields_num; $i++) {\n	    $field = mysql_fetch_field($result);\n	    $out .= \"<td><b>{$field->name}</b>&nbsp;</td>\";\n	}\n	$out .= \"</tr>\";\n	// table rows\n	while ($row = mysql_fetch_row($result)) {\n	    $out .= \"<tr>\";\n	    foreach($row as $cell)\n	        $out .= \"<td>$cell&nbsp;</td>\";\n	    $out .= \"</tr>\";\n	}\n	$out .= \'</table><br/>\';\n	return $out;\n}\n\nfunction adi_recent_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_recent_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_recent_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_recent_tab_prefs))\n			$html = $adi_recent_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_recent_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_recent_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_recent_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_recent_tab_prefs,$adi_recent_tab_outlaws,$adi_recent_tab_admin_outlaws,$adi_recent_tab_include,$adi_recent_tab_event_gtxt;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_recent_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_recent_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_recent_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_recent_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_recent_tab_pref(\'adi_recent_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_recent_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_recent_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_recent_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// plugin privs\n					if ($pref_name == \'adi_recent_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_recent_tab_pref(\'adi_recent_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_recent_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_recent_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_recent_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// tab visibility\n					if ($pref_name == \'adi_recent_tab_list\') {\n						$checkboxes = gTxt($pref_info[\'label\']);\n						$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n						foreach ($adi_recent_tab_include as $tab_name) {\n							$checked = array_search($tab_name,$tab_list) !== FALSE;\n							$checkboxes .=\n								tag(\n									sp.sp\n									.checkbox(\"adi_recent_tab_list[$tab_name]\",TRUE,$checked)\n									.sp\n									.gTxt($adi_recent_tab_event_gtxt[$tab_name])\n									,\'label\'\n								);\n						}\n						$out .= graf($checkboxes,\' class=\"\'.$pref_name.\'\"\');\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_recent_tab_pref($pref_name)),$pref_name)\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return form(\n			tag(\n				tag(gTxt(\'edit_preferences\'),\'h2\')\n				.$out\n				.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n				.eInput($event)\n				.sInput(\'update_prefs\')\n				,\'div\'\n				,\'  class=\"adi_recent_tab_prefs\"\'\n			)\n		);\n}\n\nfunction adi_recent_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_recent_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		foreach ($adi_recent_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) { // tab match?\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_recent_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_recent_tab_prefs[$pref_name][\'value\'];\n				// update pref\n				$private = ($pref_info[\'tab\'] == \'options\' ? FALSE : $pref_info[\'private\']); // \"options\" prefs can\'t be private\n				$result &= adi_recent_tab_pref($pref_name,$value,$private);\n			}\n		}\n		return $result;\n	}\n}\n\nfunction adi_recent_tab_options($event,$step) {\n// plugin options page\n	global $adi_recent_tab_debug,$adi_recent_tab_url,$textarray,$adi_recent_tab_prefs,$adi_recent_tab_include,$adi_recent_tab_event_gtxt,$adi_recent_tab_plugin_status;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_recent_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'install\') {\n		$result = adi_recent_tab_install();\n		$result ? $message = gTxt(\'adi_installed\') : $message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_recent_tab_uninstall();\n		$result ? $message = gTxt(\'adi_uninstalled\') : $message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_recent_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_recent_tab - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// *nstall buttons\n	$install_button =\n		form(\n			fInput(\'submit\',\'do_something\',gTxt(\'install\'),\'publish\',\'\',\"return verify(\'\".gTxt(\'are_you_sure\').\"\')\")\n			.eInput($event).sInput(\'install\')\n		);\n	$uninstall_button =\n		form(\n			fInput(\'submit\',\'do_something\',gTxt(\'adi_uninstall\'),\'publish\',\'\',\"return verify(\'\".gTxt(\'are_you_sure\').\"\')\")\n			.eInput($event).sInput(\'uninstall\')\n			,\'margin-top:3em\'\n		);\n	if ($adi_recent_tab_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_recent_tab_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_recent_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// link to plugin admin tab\n			graf(href(gTxt(\'adi_recent_items\'),\'?event=adi_recent_tab\'))\n			// prefs\n			.adi_recent_tab_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_recent_tab_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_recent_tab_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n	if ($adi_recent_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_recent_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_recent_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n}\n', '\n/*\n	adi_recent_tab - Recent Items Tab\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.6		- refixed for critical, admin interface killing, bug in TXP 4.6\n			- responsive layout & styling for recent items tab\n			- fix: recognises \"upload file from existing\"\n			- enhancement: pref for image alt text (for Uli)\n	0.5		- change: IDs display switched on by default\n			- change: articles heading link now goes to Articles (list) tab rather than Write\n			- fix: error when reordering images/files in list\n			- fix: error when changing number of items per page\n			- fix: error when file upload failed (e.g. file too big/file already exists)\n			- fix: article titles etc that are blank with multiple spaces are treated as Untitled\n			- fix: unslashed quotes in DB queries\n			- fix: tabs no longer listed if user lacks privs\n			- fix: upload thumbnail now ignored\n			- fix: all new section names sanitised & recognised\n			- enhancement: plugin privs\n			- code tidy up (thanks gocom)\n			- TXP 4.5+\n			- tested & fixed for TXP 4.6\n	0.4		- TXP 4.5-ified (particularly to cope with new Sections tab & new-style Links tab)\n			- fix: record new links properly\n			- fix: losing image (file) name if both alt & caption blank\n	0.3		- style improvements to cope with long titles\n			- fix: style for latest version of Hive theme (thanks Uli)\n			- fix: error message in images & files search result page (thanks Uli)\n	0.2		- fix: cope with image replace & file replace properly\n			- fix: cope with style create/save/delete properly\n			- new pref: include article/image/link/file IDs\n	0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n\n	global $adi_recent_tab_debug;\n\n	$adi_recent_tab_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_recent_tab_init();\n\n}\n\nfunction adi_recent_tab_init() {\n	global $event,$step,$textarray,$txp_user,$prefs,$adi_recent_tab_outlaws,$adi_recent_tab_url,$adi_recent_tab_plugin_status,$adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_exclude_combo,$adi_recent_tab_include_combo,$adi_recent_tab_prefs,$adi_recent_tab_debug,$adi_recent_tab_txp460;\n\n	// meet the new boss?\n	$adi_recent_tab_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// plugin lifecycle\n	register_callback(\'adi_recent_tab_lifecycle\',\'plugin_lifecycle.adi_recent_tab\');\n\n	// Textpack\n	$adi_recent_tab_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_recent_tab\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_recent_tab_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_recent_tab_gtxt = array(\n		\'adi_include\' => \'Include:\',\n		\'adi_include_ids\' => \'Include IDs\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_pref_update_fail\' => \'Preference update failed\',\n		\'adi_recent_items\' => \'Recent items\',\n		\'adi_recent_tab\' => \'Recent\',\n		\'adi_recent_tab_max_items\' => \'Maximum number of items\',\n		\'adi_tab\' => \'Tab\',\n		\'adi_textpack\' => \'Textpack\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_recent_tab_gtxt;\n\n	/*\n		Events & Steps - A minefield of inconsistencies ...\n\n						Step			other vars\n	Event=article\n		Write			create			-			(empty create tab)\n		Edit			edit			ID\n		Save			edit			ID\n		Publish			create -> edit	$GLOBALS[\'ID\']\n\n	Event=image\n		Images list		-\n			(search)	image_list\n			(sort)		list\n			(paging)	image_change_pageby\n		Image upload	image_insert	$GLOBALS[\'ID\']\n		Image edit		image_edit		id\n		Image save		image_edit		id\n		Image replace	image_replace	id\n		Thumbnail upld	thumbnail_insert\n\n	Event=page\n		Pages/default	-\n		Page edit		-				name\n		Save			page_save		name\n		Copy			page_save		name, newname\n		Create page		page_save		newname\n\n	Event=form\n		Forms/default	-\n		Form edit		form_edit		name\n		Form save		form_save\n\n	Event=link\n		Links list		- -> link_edit\n			(search)	link_list\n			(sort)		list\n			(paging)	link_change_pageby\n		Link edit		link_edit		id\n		Link save		link_save		id\n		Link create		link_post		$GLOBALS[\'ID\']	(pre-4.5.0)\n		Link create		link_save		$GLOBALS[\'ID\']	(post-4.5.0)\n\n	Event=file\n		Files list		-\n			(search)	file_list\n			(sort)		list\n			(paging)	file_change_pageby\n		Upload			file_insert		$GLOBALS[\'ID\']\n		Save			file_save		?\n		File edit		file_edit		id\n		File replace	file_replace	id\n		From existing	file_create		name\n\n	Event=css\n		Style/default	-\n		Style edit		-				name\n		Save			css_save		name\n		New style		pour			newname\n		Copy style		css_save		name\n\n	Event=section\n		Section list	-\n			(search)	sec_section_list\n			(sort)		list\n			(paging)	section_change_pageby\n		Section edit	section_edit	name\n		Section create	section_edit	-		(empty create tab)\n		Section save	section_save	name\n		Section create	section_save	newname	(post-4.5.0)\n	*/\n\n	// the privileged few ...\n	$adi_recent_tab_include = array(\'article\',\'image\',\'page\',\'form\',\'link\',\'file\',\'css\',\'section\'); // if you\'re not on the list, you\'re not getting in\n\n	// privileges for the privileged\n	$adi_recent_tab_include_privs = array(\n		\'article\' => \'list\',\n		\'image\' => \'image\',\n		\'page\' => \'page\',\n		\'form\' => \'form\',\n		\'link\' => \'link\',\n		\'file\' => \'file\',\n		\'css\' => \'css\',\n		\'section\' => \'section\',\n	);\n\n	if ($adi_recent_tab_debug)\n		foreach ($adi_recent_tab_include as $index => $tab)\n			if (has_privs($adi_recent_tab_include_privs[$tab]))\n				echo $tab.sp.\'- has privs\'.br;\n			else\n				echo $tab.sp.\'- no privs\'.br;\n\n	// weed out the unworthy\n	foreach ($adi_recent_tab_include as $index => $tab)\n		if (!has_privs($adi_recent_tab_include_privs[$tab]))\n			unset($adi_recent_tab_include[$index]);\n\n	// it\'s what we call \'em\n	$adi_recent_tab_event_gtxt = array(\n		\'article\'	=> \'tab_list\',\n		\'css\'		=> \'tab_style\',\n		\'file\'		=> \'tab_file\',\n		\'form\'		=> \'tab_forms\',\n		\'image\'		=> \'tab_image\',\n		\'link\'		=> \'tab_link\',\n		\'page\'		=> \'tab_pages\',\n		\'section\'	=> \'tab_sections\',\n	);\n\n	// not so fast ...\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^article$\', \'step\' => \'^$\'); // empty create article tab - event=\"article\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'^$\'); // image list tab - event=\"image\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'image_multi_edit\'); // image list multi-edit - event=\"image\", step contains image_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'image_replace\'); // image replace - event=\"image\", step contains image_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'list\'); // image list - event=\"image\", step contains list (search results=image_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^image$\', \'step\' => \'^thumbnail_insert$\'); // thumbnail upload - event=\"image\", step=thumbnail_insert\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^page$\', \'step\' => \'page_new\'); // new page tab - event=\"page\", step contains page_new\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^form$\', \'step\' => \'form_multi_edit\'); // form list multi-edit - event=\"form\", step contains form_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'step\' => \'link_edit\', \'id\' => \'^$\'); // link list tab\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'id\' => \'^$\'); // TXP 4.5 link list tab\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^link$\', \'step\' => \'link_multi_edit\'); // link list tab - event=\"link\", step contains link_multi_edit\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'^$\'); // file list tab - event=\"file\", step=blank\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'file_replace\'); // file replace - event=\"file\", step contains file_replace\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^file$\', \'step\' => \'list\'); // file list - event=\"file\", step contains list (search results=file_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^css$\', \'step\' => \'pour\'); // style list tab - event=\"css\", step=pour (create style)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'section_edit\', \'name\' => \'^$\'); // empty section create tab (TXP 4.5.0)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'list\'); // section list - event=\"section\", step contains list (search results=sec_section_list,sort=list)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'save\'); // any step containing \"save\" (e.g. page_save, form_save, link_save)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'create\'); // any step containing \"create\" (e.g. new article create, form create)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'apply\'); // any step containing \"apply\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'update\'); // any step containing \"update\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'delete\'); // any step containing \"delete\" - just in case!\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'.*\', \'step\' => \'change_pageby\'); // any step containing \"change_pageby\", e.g. image_change_pageby, when changing # per page\n	// don\'t log initial page/form/style tab visit (i.e. default by default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^page$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default page tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^form$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default form tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^css$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default style tab (i.e. edit default)\n	$adi_recent_tab_exclude_combo[] = array(\'event\' => \'^section$\', \'step\' => \'^$\', \'name\' => \'^$\'); // default section list tab (TXP 4.5.0)\n\n	// oh, go on then ...\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'page\', \'step\' => \'page_save\'); // create new page\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'form\', \'step\' => \'form_save\'); // create new form\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'css\', \'step\' => \'css_save\'); // create new form\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'file\', \'step\' => \'file_create\'); // create file from existing\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'link\', \'step\' => \'link_post\'); // create new link (pre-4.5.0)\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'link\', \'step\' => \'link_save\'); // create new link (post-4.5.0)\n	$adi_recent_tab_include_combo[] = array(\'event\' => \'section\', \'step\' => \'section_save\'); // create new section (TXP 4.5.0+)\n\n	// preferences & defaults\n	$adi_recent_tab_prefs = array(\n		\'adi_recent_tab_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'private\' => 0, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n		\'adi_recent_tab_list\' => array(\'value\' => implode(\',\',$adi_recent_tab_include), \'blank\' => 0, \'private\' => 1, \'input\' => \'custom\', \'label\' => \'adi_include\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_max_items\' => array(\'value\' => \'5\', \'blank\' => 0, \'private\' => 1, \'input\' => \'text_input\', \'label\' => \'adi_recent_tab_max_items\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_ids\' => array(\'value\' => \'1\', \'blank\' => 1, \'private\' => 1, \'input\' => \'yesnoradio\', \'label\' => \'adi_include_ids\', \'tab\' => \'admin\'),\n		\'adi_recent_tab_img_alt\' => array(\'value\' => \'1\', \'blank\' => 1, \'private\' => 1, \'input\' => \'yesnoradio\', \'label\' => \'alt_text\', \'tab\' => \'admin\'),\n	);\n\n	// plugin privs to be excluded\n	$adi_recent_tab_outlaws = array(0);\n\n	// who\'s playing today?\n	add_privs(\'adi_recent_tab\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items tab\n	add_privs(\'tab.recent\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items menu\n\n	// do the business\n	if (adi_recent_tab_installed() && has_privs(\'adi_recent_tab\')) {\n		// menu style for all\n		register_callback(\'adi_recent_tab_menu_style\',\'admin_side\',\'head_end\');\n		// tab style for tab only\n		if ($event == \"adi_recent_tab\")\n			register_callback(\'adi_recent_tab_style\',\'admin_side\',\'head_end\');\n		// adi_recent_tab tab\n		register_callback(\"adi_recent_tab\",\"adi_recent_tab\");\n		$textarray[\'tab_recent\'] = gTxt(\'adi_recent_tab\'); // to get something meaningful into tab\n		// put logger in the footer - to catch the elusive newly created article/image IDs etc\n		register_callback(\'adi_recent_tab_logger\',\'admin_side\',\'footer\');\n		// TXP 4.5: recent tab under recent items menu; TXP 4.6: recent tab under extensions menu (due to lack of top-level click-through)\n		if ($adi_recent_tab_txp460) {\n			// recent items tab under extensions\n			register_tab(\'extensions\',\'adi_recent_tab\',gTxt(\'adi_recent_items\'));\n			// separate recent items menu\n			add_privs(\'adi_recent_tab_menu\',adi_recent_tab_pref(\'adi_recent_tab_privs\')); // recent items tab\n			register_tab(\'recent\',\'adi_recent_tab_menu\',gTxt(\'adi_recent_items\'));\n			register_callback(\'adi_recent_tab_menu\',\'adi_recent_tab_menu\');\n			// inject markup to create recent items menu\n			register_callback(\'adi_recent_tab_menu_markup_mega\',\'admin_side\',\'footer\'); // markup in the footer, shifted by jQuery\n		}\n		else {\n			// recent items tab under separate recent items menu\n			register_tab(\'recent\',\'adi_recent_tab\',gTxt(\'adi_recent_items\'));\n			// inject markup to create recent items menu\n			adi_recent_tab_menu_ob();\n		}\n	}\n\n	// script\n	if ($adi_recent_tab_txp460)\n		register_callback(\'adi_recent_tab_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_recent_tab_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_recent_tab\');\n	if ($adi_recent_tab_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_recent_tab\',\'1,2,6\');\n		register_callback(\'adi_recent_tab_options\',\'plugin_prefs.adi_recent_tab\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_recent_tab_options\',\'1,2,6\');\n		register_tab(\'extensions\',\'adi_recent_tab_options\',\'adi_recent_tab options\');\n		register_callback(\'adi_recent_tab_options\',\'adi_recent_tab_options\');\n	}\n}\n\nfunction adi_recent_tab_style() {\n// style for tab\n	echo\n		\'<style type=\"text/css\">\n			/* adi_recent_tab */\n			#adi_recent_tab_tab, #adi_recent_tab_tab ul { overflow:hidden; list-style:none; margin:0; padding:0 }\n			#adi_recent_tab_tab li { list-style:none; margin:0; padding:0 }\n			#adi_recent_tab_tab > li { float:left; width:47%; margin:0 6% 1% 0 }\n			#adi_recent_tab_tab > li:nth-child(2n) { margin-right:0 }\n			#adi_recent_tab .adi_recent_tab_prefs { margin:4em 0 2em; border-top:1px solid #ccc }\n			#adi_recent_tab input.adi_recent_tab_max_items { width:3em }\n			/* responsive */\n			@media (max-width: 47em) {\n				#adi_recent_tab_tab > li { float:none; width:100% }\n				.adi_recent_tab_prefs p.adi_recent_tab_list label { display:block; margin-top:0.5em }\n			}\n			/* TXP 4.5 */\n			div.txp-body h1, div.txp-body h2 { font-weight:bold }\n			div.txp-body .adi_recent_tab_prefs h2 { margin-top:0.5em }\n			/* Hive TXP 4.5 adjustments - recent tab page given class=\"recent\" which is Hive styling for Recent Articles! */\n			.txp-nav li { margin-left:0 }\n		</style>\';\n}\n\nfunction adi_recent_tab_menu_style() {\n// style for dropdown menu\n	echo\n		\'<style type=\"text/css\">\n			/* adi_recent_tab */\n			/* TXP 4.5 Remora */\n			#nav .adi_recent_tab_menu_title { font-weight:bold }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul { width:25em; padding-top:0.5em; padding-bottom:0.5em }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li { width:auto; height:auto }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li:first-child { display:none }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:23em; height:auto; line-height:1.2; padding-top:0.3em; padding-bottom:0.3em }\n			#nav a[href=\"?event=adi_recent_tab\"] + ul li a span { font-weight:bold }\n			.txp-header #nav a[href=\"?event=adi_recent_tab\"] + ul { z-index:9999 }\n			/* TXP 4.5 Hive */\n			div.txp-nav .adi_recent_tab_menu_title { font-weight:bold }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { padding-bottom:0.5em }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:25em }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li { float:none; width:auto }\n			div.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:hover { background-color:#ffefaf }\n			/* TXP 4.6 Hive */\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:first-child { display:none }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:50em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li { float:left; width:50% }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:hover { background-color:transparent }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul > li:nth-child(2n) { clear:left }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2 { margin:0.5em 0 0; font-size:1em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2:hover { background-color:#ffefaf }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:auto; padding-top:0.3em; padding-bottom:0.3em; word-wrap:break-word }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul ul { width:auto }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { left:-20em; border-radius:0.5em }\n			nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul:before { left:18em } /* the pointy bit */\n			/* bot_write_tab_customize tab overrides */\n			body#page-bot_wtc_tab .txp-nav a[href=\"?event=adi_recent_tab\"] + ul li h2 { margin:0.5em 0 0; padding:0; text-align:left }\n			/* responsive */\n			@media (max-width: 47em) {\n				nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul { width:auto }\n				nav.txp-nav a[href=\"?event=adi_recent_tab\"] + ul li a { width:auto; padding-top:0.5em; padding-bottom:0.5em }\n			}\n		</style>\';\n}\n\nfunction adi_recent_tab_script() {\n// scriptimus\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function() {\n		// adi_recent_tab menu\n		var closest_ul = $(\'a[href=\"?event=adi_recent_tab_menu\"]\').closest(\"ul\"); // find (grand)parent ul\n		var menu_id = $(closest_ul).attr(\'id\'); // i.e. \"#txp-nav-dropX-menu\"\n		$(\"ul#adi_recent_tab_inject > li\").insertAfter($(\"ul#\" + menu_id + \" li\")); // shift recent menu items from footer\n		$(\"ul#adi_recent_tab_inject\").remove(); // footer housekeeping\n		var top_link = $(closest_ul).prev(); // get top level menu item link\n		$(top_link).attr(\"href\",\"?event=adi_recent_tab\"); // restore click-through to recent_tab tab (also allows some styles to kick in)\n	});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_recent_tab_logger() {\n// monitor each TXP admin page visit & decide what\'s to be recorded by adi_recent_tab\n	global $DB,$adi_recent_tab_debug,$event,$step,$txp_user,$adi_recent_tab_include,$adi_recent_tab_exclude_combo,$adi_recent_tab_include_combo,$adi_recent_tab_txp460;\n\n	if (!adi_recent_tab_installed()) // in case it\'s just been uninstalled\n		return;\n\n	$id = gps(\'id\'); // image id\n	$name = gps(\'name\'); // section/page/form/image name\n\n	if ($adi_recent_tab_debug) {\n		echo \'Event=\'.$event.\', Final Step=\'.$step.\', supplied id=\'.$id.\', supplied name=\'.$name.\'<br/>\';\n		echo \'Include event list:\';\n		dmp($adi_recent_tab_include);\n		echo \'Exclude combo list:\';\n		dmp($adi_recent_tab_exclude_combo);\n		echo \'Include combo list:\';\n		dmp($adi_recent_tab_include_combo);\n	}\n\n	// process included & excluded events/steps\n	// events of interest\n	$exclude = array_search($event,$adi_recent_tab_include) === FALSE;\n	if ($adi_recent_tab_debug && $exclude)\n		echo \'($adi_recent_tab_include) EXCLUDE EVENT: \'.$event.\'<br/>\';\n	// sort the men from the boys\n	if (!$exclude) {\n		// event/step combos to be discounted\n		$exclude = adi_recent_tab_filter($adi_recent_tab_exclude_combo,$event,$step,$id,$name);\n		if ($adi_recent_tab_debug && $exclude)\n			echo \'($adi_recent_tab_exclude_combo) \'.\"EXCLUDED EVENT/STEP/ID/NAME: $event,$step,$id,$name<br/>\";\n\n		// explicitly include these combos\n		$include = adi_recent_tab_filter($adi_recent_tab_include_combo,$event,$step,$id,$name);\n		if ($adi_recent_tab_debug && $include)\n			echo \'($adi_recent_tab_include_combo) \'.\"INCLUDED EVENT/STEP/ID/NAME: $event,$step,$id,$name<br/>\";\n		if ($include)\n			$exclude = FALSE;\n	}\n\n	if (!$exclude) { // massage stuff\n		// ID\n		if (empty($id)) // try finding an article ID\n			$id = gps(\'ID\');\n		if (isset($GLOBALS[\'ID\'])) // set the \"newly created\" ID to something\n			$globals_id = $GLOBALS[\'ID\'];\n		else\n			$globals_id = \'\';\n		if (empty($id) && ($event == \'article\')) // i.e. a newly created article\n			$id = $globals_id;\n		if (empty($id) && ($event == \'image\')) // i.e. a newly created image\n			$id = $globals_id;\n		if (empty($id) && ($event == \'link\')) // i.e. a newly created link\n			$id = $globals_id;\n		if (empty($id) && ($event == \'file\')) // i.e. a newly uploaded file\n			$id = $globals_id;\n		// NAME\n		if (($event == \'section\') && ($step == \'section_save\'))\n			$name = strtolower(sanitizeForUrl($name)); // sanitise (create/edit) section name (from txp_section.php)\n		if ((($event == \'page\') || ($event == \'form\')) && empty($name)) // fill in the \"default\" blank - needed if logging initial page/form tab visit\n			$name = \'default\';\n		if ($event == \'image\') // don\'t want to store image name coz we\'ve got the ID (& the name might change)\n			$name = \'\';\n		$newname = sanitizeForPage(gps(\'newname\')); // sanitise (create/copy) page name\n		if ($newname)\n			$name = $newname;\n		// STEP\n		$this_step = $step;\n		if (($event== \'image\') && ($step == \'image_insert\')) // tweak step - new image\n			$this_step = \'image_edit\';\n		if (($event== \'page\') && ($step == \'page_save\')) // new/save page\n			$this_step = \'\';\n		if (($event== \'form\') && ($step == \'form_save\')) // new/save form\n			$this_step = \'form_edit\';\n		if (($event== \'css\') && ($step == \'css_save\')) // new style/save style\n			$this_step = \'\';\n		if (($event== \'form\') && ($step == \'\')) // tweak step - click between forms\n			$this_step = \'form_edit\';\n		if (($event== \'link\') && ($step == \'link_post\')) // new link (pre-4.5.0)\n			$this_step = \'link_edit\';\n		if (($event== \'link\') && ($step == \'link_save\')) // new link (post-4.5.0)\n			$this_step = \'link_edit\';\n		if (($event== \'section\') && ($step == \'section_save\')) // new section (post-4.5.0)\n			$this_step = \'section_edit\';\n		if (($event== \'file\') && ($step == \'file_insert\')) // new file\n			$this_step = \'file_edit\';\n		if (($event== \'file\') && ($step == \'file_create\')) // new file from existing\n			$this_step = \'file_edit\';\n		if ($adi_recent_tab_debug)\n			echo \'Event=\'.$event.\', this_step=\'.$this_step.\', massaged id=\'.$id.\', massaged name=\'.$name.\'<br/>\';\n\n		if ($id == \'\' && (($event == \'article\') || ($event == \'image\') || ($event == \'link\') || ($event == \'file\'))) {\n			if ($adi_recent_tab_debug)\n				echo \'No ID, no update!\';\n		}\n		else {\n			// update adi_recent_tab DB table\n			$table = \'adi_recent_tab\';\n			$set = \'timestamp=now(), user=\"\'.doSlash($txp_user).\'\", event=\"\'.doSlash($event).\'\", step=\"\'.doSlash($this_step).\'\", id=\"\'.doSlash($id).\'\", name=\"\'.doSlash($name).\'\"\';\n			$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$this_step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.$name.\'\"\';\n			// it\'s me own safe_upsert ...\n			$r = safe_update($table,$set,$where,$adi_recent_tab_debug);\n			if ($adi_recent_tab_txp460) {\n				if (!($r and (mysqli_affected_rows($DB->link) or safe_count($table,$where,$adi_recent_tab_debug))))\n					safe_insert($table,$set,$adi_recent_tab_debug);\n			}\n			else {\n				if (!($r and (mysql_affected_rows() or safe_count($table,$where,$adi_recent_tab_debug))))\n					safe_insert($table,$set,$adi_recent_tab_debug);\n			}\n		}\n	}\n\n	// tidy up\n	adi_recent_tab_housekeeping();\n}\n\nfunction adi_recent_tab_filter($pattern_list,$event,$step,$id=\'\',$name=\'\') {\n// look for combination matches\n\n	$match = FALSE;\n	foreach ($pattern_list as $pair) {\n		$this_match = TRUE;\n		foreach ($pair as $var => $pattern)\n			$this_match = $this_match && preg_match(\'/\'.$pattern.\'/\',$$var);\n		$match = $match || $this_match;\n	}\n	return $match;\n}\n\nfunction adi_recent_tab_installed($table=\'adi_recent_tab\') {\n// test if table is present\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\");\n	$a = nextRow($rs);\n	if ($a)\n		return TRUE;\n	else\n		return FALSE;\n}\n\nfunction adi_recent_tab_install() {\n// install adi_recent_tab table in database\n	global $adi_recent_tab_debug;\n\n	$res = safe_query(\n		// \'id\' is a VARCHAR (rather than an INT) coz I need it to be blank sometimes\n		\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'adi_recent_tab\')\n		.\"(	`timestamp` datetime NOT NULL default \'0000-00-00 00:00:00\',\n			`user` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`event` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`step` VARCHAR(64) NOT NULL DEFAULT \'\',\n			`id` VARCHAR(12) NOT NULL DEFAULT \'\',\n			`name` VARCHAR(64) NOT NULL DEFAULT \'\'\n		);\"\n		,$adi_recent_tab_debug);\n 	return $res;\n}\n\nfunction adi_recent_tab_uninstall() {\n// uninstall adi_recent_tab\n	global $adi_recent_tab_debug,$adi_recent_tab_prefs;\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_recent_tab\').\";\",$adi_recent_tab_debug);\n	// delete preferences\n	foreach ($adi_recent_tab_prefs as $this_pref => $value)\n		$res = $res && safe_delete(\'txp_prefs\',\"name = \'$this_pref\'\",$adi_recent_tab_debug);\n	return $res;\n}\n\nfunction adi_recent_tab_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_recent_tab\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// install/reinstall: \"installed\"; make active: \"enabled\"; make inactive: \"disabled\"; delete: \"disabled\" then \"deleted\"\n// (as of TXP 4.5, reinstall only triggers \"installed\" event)\n// adi_lifecycle: \"installed\" -> install() regardless, upgrade() if required; \"deleted\" -> uninstall(); \"enabled\" & disabled\" -> do nothing\n	global $adi_recent_tab_debug;\n\n	$result = $upgrade_required = \'?\';\n	if ($step == \'installed\') {\n		$result = adi_recent_tab_install();\n// 		$upgrade_required = adi_recent_tab_upgrade();\n// 		if ($upgrade_required)\n// 			$result &= adi_recent_tab_upgrade(TRUE);\n	}\n	if ($step == \'deleted\')\n		$result = adi_recent_tab_uninstall();\n	if ($adi_recent_tab_debug)\n		echo \"event=$event,step=$step,result=$result,upgrade_required=$upgrade_required\";\n}\n\nfunction adi_recent_tab_housekeeping() {\n// delete oldest recents that exceed max count; make sure items still exist in DB\n	global $adi_recent_tab_debug,$txp_user,$adi_recent_tab_include,$adi_recent_tab_prefs;\n\n	$max_recent = adi_recent_tab_pref(\'adi_recent_tab_max_items\');\n\n	$sort = \' ORDER BY timestamp desc\';\n	$default_where = \" AND user=\'$txp_user\'\";\n\n	if ($adi_recent_tab_debug)\n		echo \'<br/>Housekeeping (deceased entries):<br/>\';\n\n	// delete entries that don\'t exist ... the recently deceased\n	foreach ($adi_recent_tab_include as $recent) {\n		$table = \'txp_\'.$recent;\n		if ($recent == \'article\')\n			$table = \'textpattern\';\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'\".$recent.\"\'\".$default_where.$sort,$adi_recent_tab_debug);\n		foreach ($rs as $index => $row) {\n			extract($row);\n			// \"id\" entries\n			if (($recent == \'article\') || ($recent == \'image\') || ($recent == \'link\') || ($recent == \'file\')) {\n				$row = safe_row(\'*\',$table,\"id=\'$id\'\",$adi_recent_tab_debug);\n				if (empty($row)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\';\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n			// \"name\" entries\n			if (($recent == \'page\') || ($recent == \'form\') || ($recent == \'css\') || ($recent == \'section\')) {\n				$row = safe_row(\'*\',$table,\"name=\'\".doSlash($name).\"\'\",$adi_recent_tab_debug);\n				if (empty($row)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\';\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n		}\n	}\n\n	if ($adi_recent_tab_debug)\n		echo \'Housekeeping (remove surplus):<br/>\';\n\n	// lose the surplus\n	foreach ($adi_recent_tab_include as $recent) {\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'\".$recent.\"\'\".$default_where.$sort,$adi_recent_tab_debug);\n		if (count($rs) > $max_recent)\n			foreach ($rs as $index => $row) {\n				extract($row);\n				if (!($index < $max_recent)) {\n					$where = \'user=\"\'.$txp_user.\'\" AND event=\"\'.$event.\'\" AND step=\"\'.$step.\'\" AND id=\"\'.$id.\'\" AND name=\"\'.doSlash($name).\'\"\'; // BIG GLOBAL FOR THIS WHERE???\n					safe_delete(\'adi_recent_tab\',$where,$adi_recent_tab_debug);\n				}\n			}\n	}\n}\n\nfunction adi_recent_tab_links($event,$wrap=TRUE) {\n// return a <ul><li> or <li> of all the \"recent\" links\n	global $adi_recent_tab_debug,$adi_recent_tab_prefs,$txp_user;\n\n//	$sort_method = \'timestamp\';\n	$sort_method = \'alphanumeric\';\n\n	$default_where = \" AND user = \'$txp_user\'\";\n\n	if ($sort_method == \'timestamp\') {\n		$sort = \' ORDER BY timestamp desc\';\n		$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event = \'$event\'\".$default_where.$sort,$adi_recent_tab_debug);\n	}\n	else if ($sort_method == \'alphanumeric\') {\n		if ($event == \'article\') {\n			$sort = \' ORDER BY Title\';\n			$rs = safe_rows(\'Title,textpattern.ID,adi_recent_tab.*\',\'textpattern, adi_recent_tab\',\"event = \'$event\' AND textpattern.ID = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'image\') {\n			$sort = \' ORDER BY alt,txp_image.name\';\n			// note that actual name is last in list of fields - image name not stored by adi_recent_tab, so don\'t want blank to override proper name\n			$rs = safe_rows(\'adi_recent_tab.*,alt,txp_image.name\',\'txp_image, adi_recent_tab\',\"event = \'$event\' AND txp_image.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'file\') {\n			$sort = \' ORDER BY title,filename\';\n			$rs = safe_rows(\'title,filename,adi_recent_tab.*\',\'txp_file, adi_recent_tab\',\"event = \'$event\' AND txp_file.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else if ($event == \'link\') {\n			$sort = \' ORDER BY linkname,url\';\n			$rs = safe_rows(\'linkname,url,adi_recent_tab.*\',\'txp_link, adi_recent_tab\',\"event = \'$event\' AND txp_link.id = adi_recent_tab.id\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n		else { // page, form, style, section\n			$sort = \' ORDER BY name\';\n			$rs = safe_rows(\'*\',\'adi_recent_tab\',\"event=\'$event\'\".$default_where.$sort,$adi_recent_tab_debug);\n		}\n	}\n\n	if ($adi_recent_tab_debug)\n		dmp($rs);\n\n	$out = \'\';\n\n	foreach ($rs as $index => $row) {\n		$item = \'\';\n		extract($row);\n		// link text fiddling\n		if ($event == \'article\') { // use article title, or Untitled if empty\n			$Title = trim($Title);\n			if (empty($Title))\n				$link_text = gTxt(\'untitled\');\n			else\n				$link_text = $Title;\n		}\n		else if ($event == \'image\') { // use alt text, or name if blank\n			$alt = trim($alt);\n			if (empty($alt) || !adi_recent_tab_pref(\'adi_recent_tab_img_alt\'))\n				$link_text = $name;\n			else\n				$link_text = $alt;\n		}\n		else if ($event == \'link\') { // use link name, or url if blank\n			$linkname = trim($linkname);\n			if (empty($linkname))\n				$link_text = $url;\n			else\n				$link_text = $linkname;\n		}\n		else if ($event == \'file\') { // use title, or filename if blank\n			$title = trim($title);\n			if (empty($title))\n				$link_text = $filename;\n			else\n				$link_text = $title;\n		}\n		else if ($event == \'section\') // use name\n				$link_text = $name;\n		// include IDs?\n		if (adi_recent_tab_pref(\'adi_recent_tab_ids\') && ($id != \'\'))\n			$link_text .= \' (\'.tag($id,\'span\').\')\';\n		// links to items\n		if ($event == \'article\')\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'ID=\'.$id.\'\">\'.$link_text.\'</a>\';\n		else if (($event == \'image\') || ($event == \'link\') || ($event == \'file\'))\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'id=\'.$id.\'\">\'.$link_text.\'</a>\';\n		else if ($event == \'section\')\n			$item .= \'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'name=\'.$name.\'\">\'.$link_text.\'</a>\';\n		else\n			$item .= \'<a href=\"?event=\'.$event.a.\'name=\'.$name.\'\">\'.$name.\'</a>\';\n		$out .= tag($item,\'li\',\' class=\"adi_recent_tab_menu_item\"\').n;\n	}\n\n	if ($wrap)\n		return tag($out,\'ul\');\n	else\n		return $out;\n}\n\nfunction adi_recent_tab($event,$step) {\n// the page that displays all the \"recent\" links\n	global $adi_recent_tab_debug,$adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs,$adi_recent_tab_txp460;\n\n	$message= \'\';\n	if ($step == \'update_prefs\') {\n		$result = adi_recent_tab_update_prefs(\'admin\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		adi_recent_tab_housekeeping(); // do extra housekeeping to reflect possible change in max items (habitually done at bottom of page)\n	}\n\n	// generate page\n	pagetop(\'adi_recent_tab \'.gTxt(\'adi_recent_tab\'),$message);\n\n	echo \'<div id=\"adi_recent_tab\">\';\n\n	echo tag(gTxt(\'adi_recent_items\'),\'h1\');\n\n	// generate list of recents\n	echo tag(implode(n,adi_recent_tab_list_markup()),\'ul\',\' id=\"adi_recent_tab_tab\"\');\n\n	// preferences\n	echo adi_recent_tab_pref_inputs($event,\'admin\');\n\n	echo \'</div>\';\n\n	if ($adi_recent_tab_debug)\n		echo adi_recent_tab_db_dump();\n}\n\nfunction adi_recent_tab_menu_ob() {\n// the old-fashioned magic that gets the recent items into adi_recent_tab\'s menu - TXP 4.5\n 	ob_start(\'adi_recent_tab_menu_inject\');\n}\n\nfunction adi_recent_tab_menu_inject($buffer) {\n// another bit of arcane magic - TXP 4.5\n	global $DB, $adi_recent_tab_txp460;\n\n	if(!isset($DB)) $DB = new db;\n\n	$pattern = \'#\'.gTxt(\'adi_recent_items\').\'</a></li>#sU\';\n	$insert = \'adi_recent_tab_menu_markup\';\n	$buffer = preg_replace_callback($pattern, $insert, $buffer); // this causes grief in 4.6 (seemingly just for me!)\n	return $buffer;\n}\n\nfunction adi_recent_tab_menu_markup($matches) {\n// generate markup for adi_recent_tab recent item lists - TXP 4.5\n	global $adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs;\n\n	$out = \'\';\n	$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n	foreach ($tab_list as $this_event) {\n		$link_event = $this_event;\n		if ($this_event == \'article\')\n			$link_event = \'list\';\n		$out .=\n			\'<li><a class=\"adi_recent_tab_menu_title\" href=\"?event=\'.$link_event.\'\">\'\n			.gTxt($adi_recent_tab_event_gtxt[$this_event])\n			.\'</a></li>\'\n			.adi_recent_tab_links($this_event,FALSE);\n	}\n	return $matches[0].$out;\n}\n\nfunction adi_recent_tab_list_markup() {\n// generate markup for dropdown or tab\n// <ul>\n//		<li>\n//			<h2><a>TAB</a></h2>\n//			<a>RECENT LINK 1</a>\n//			<a>RECENT LINK 2</a> etc\n//		</li>\n	global $adi_recent_tab_event_gtxt,$adi_recent_tab_include,$adi_recent_tab_prefs;\n	$out = array();\n	$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n	foreach ($tab_list as $index => $this_event) {\n		$tab_event = $this_event;\n		if ($this_event == \'article\')\n			$tab_event = \'list\';\n		$out[] =\n			tag(\n				tag(\n					\'<a href=\"?event=\'.$tab_event.\'\">\'.gTxt($adi_recent_tab_event_gtxt[$this_event]).\'</a>\'\n					,\'h2\'\n				)\n				.adi_recent_tab_links($this_event)\n				,\'li\'\n			);\n	}\n	return $out;\n}\n\nfunction adi_recent_tab_menu_markup_mega() {\n// generate mega menu markup - TXP 4.6\n	echo tag(implode(n,adi_recent_tab_list_markup()),\'ul\',\' id=\"adi_recent_tab_inject\"\');\n}\n\nfunction adi_recent_tab_db_dump($table=\'adi_recent_tab\') {\n// print out contents of database table\n\n	$result = mysql_query(\"SELECT * FROM {$table}\");\n	$fields_num = mysql_num_fields($result);\n	$out = \"<br/><table><tr>\";\n	// table headers\n	for ($i = 0; $i < $fields_num; $i++) {\n	    $field = mysql_fetch_field($result);\n	    $out .= \"<td><b>{$field->name}</b>&nbsp;</td>\";\n	}\n	$out .= \"</tr>\";\n	// table rows\n	while ($row = mysql_fetch_row($result)) {\n	    $out .= \"<tr>\";\n	    foreach($row as $cell)\n	        $out .= \"<td>$cell&nbsp;</td>\";\n	    $out .= \"</tr>\";\n	}\n	$out .= \'</table><br/>\';\n	return $out;\n}\n\nfunction adi_recent_tab_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_recent_tab_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_recent_tab_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_recent_tab_prefs))\n			$html = $adi_recent_tab_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_recent_tab_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_recent_tab_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_recent_tab_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_recent_tab_prefs,$adi_recent_tab_outlaws,$adi_recent_tab_admin_outlaws,$adi_recent_tab_include,$adi_recent_tab_event_gtxt;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_recent_tab_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_recent_tab_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_recent_tab_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_recent_tab_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$admin_privs = do_list(adi_recent_tab_pref(\'adi_recent_tab_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_recent_tab_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_recent_tab_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_recent_tab_admin_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// plugin privs\n					if ($pref_name == \'adi_recent_tab_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']);\n						$plugin_privs = do_list(adi_recent_tab_pref(\'adi_recent_tab_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_recent_tab_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(sp.sp.checkbox2(\"adi_recent_tab_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_recent_tab_privs\').sp.gTxt($group),\'label\');\n						$out .= graf($priv_out,\' class=\"\'.$pref_name.\'\"\');\n					}\n					// tab visibility\n					if ($pref_name == \'adi_recent_tab_list\') {\n						$checkboxes = gTxt($pref_info[\'label\']);\n						$tab_list = do_list(adi_recent_tab_pref(\'adi_recent_tab_list\'));\n						foreach ($adi_recent_tab_include as $tab_name) {\n							$checked = array_search($tab_name,$tab_list) !== FALSE;\n							$checkboxes .=\n								tag(\n									sp.sp\n									.checkbox(\"adi_recent_tab_list[$tab_name]\",TRUE,$checked)\n									.sp\n									.gTxt($adi_recent_tab_event_gtxt[$tab_name])\n									,\'label\'\n								);\n						}\n						$out .= graf($checkboxes,\' class=\"\'.$pref_name.\'\"\');\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_recent_tab_pref($pref_name)),$pref_name)\n							,\' class=\"\'.$pref_name.\'\"\'\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return form(\n			tag(\n				tag(gTxt(\'edit_preferences\'),\'h2\')\n				.$out\n				.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n				.eInput($event)\n				.sInput(\'update_prefs\')\n				,\'div\'\n				,\'  class=\"adi_recent_tab_prefs\"\'\n			)\n		);\n}\n\nfunction adi_recent_tab_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_recent_tab_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		foreach ($adi_recent_tab_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) { // tab match?\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_recent_tab_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_recent_tab_prefs[$pref_name][\'value\'];\n				// update pref\n				$private = ($pref_info[\'tab\'] == \'options\' ? FALSE : $pref_info[\'private\']); // \"options\" prefs can\'t be private\n				$result &= adi_recent_tab_pref($pref_name,$value,$private);\n			}\n		}\n		return $result;\n	}\n}\n\nfunction adi_recent_tab_options($event,$step) {\n// plugin options page\n	global $adi_recent_tab_debug,$adi_recent_tab_url,$textarray,$adi_recent_tab_prefs,$adi_recent_tab_include,$adi_recent_tab_event_gtxt,$adi_recent_tab_plugin_status;\n\n	$message = \'\';\n\n	// step-tastic\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_recent_tab_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'install\') {\n		$result = adi_recent_tab_install();\n		$result ? $message = gTxt(\'adi_installed\') : $message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n	}\n	else if ($step == \'uninstall\') {\n		$result = adi_recent_tab_uninstall();\n		$result ? $message = gTxt(\'adi_uninstalled\') : $message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_recent_tab_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_recent_tab - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// *nstall buttons\n	$install_button =\n		form(\n			fInput(\'submit\',\'do_something\',gTxt(\'install\'),\'publish\',\'\',\"return verify(\'\".gTxt(\'are_you_sure\').\"\')\")\n			.eInput($event).sInput(\'install\')\n		);\n	$uninstall_button =\n		form(\n			fInput(\'submit\',\'do_something\',gTxt(\'adi_uninstall\'),\'publish\',\'\',\"return verify(\'\".gTxt(\'are_you_sure\').\"\')\")\n			.eInput($event).sInput(\'uninstall\')\n			,\'margin-top:3em\'\n		);\n	if ($adi_recent_tab_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_recent_tab_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_recent_tab \'.gTxt(\'plugin_prefs\'),\'h2\')\n		.( $installed ?\n			// link to plugin admin tab\n			graf(href(gTxt(\'adi_recent_items\'),\'?event=adi_recent_tab\'))\n			// prefs\n			.adi_recent_tab_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_recent_tab_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_recent_tab_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n	if ($adi_recent_tab_debug) {\n		echo \'<b>$adi_textpack (\'.$adi_recent_tab_url[\'textpack\'].\'):</b>\';\n		$adi_textpack = file_get_contents($adi_recent_tab_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n}\n', '328b0eebe8e970b4b851ac73fef8e6a1', '', '', 3, 1, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_scripts', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Script File Management', '<h1>adi_scripts &#8211; Script File Management</h1>\n\n<p>This plugin provides a system for managing external Javascript/jQuery script files from within <span class=\"caps\">TXP</span> Admin.</p>\n\n<p>There&#8217;s an admin tab, under Extensions, to organise your scripts and a tag to be used on your pages.</p>\n\n<h2><strong>Quick start guide</strong></h2>\n\n<ol>\n	<li>Install <strong>adi_scripts</strong>.</li>\n	<li>Go to the “Scripts” admin tab, choose your directories &amp; manage your scripts.</li>\n	<li>Put the <code>&lt;txp:adi_scripts /&gt;</code> tag in the <code>&lt;head&gt;...&lt;/head&gt;</code> section of your pages.</li>\n</ol>\n\n<h2><strong>Scripts tab</strong></h2>\n\n<p>Here you can:</p>\n\n<ul>\n	<li>select script directories which contain the scripts</li>\n	<li>select which scripts you want to include on your pages</li>\n	<li>tag individual scripts</li>\n	<li>define the script order</li>\n	<li>restrict scripts to certain sections</li>\n	<li>specify scripts within IE conditional comments</li>\n	<li>temporarily prevent scripts from being linked</li>\n</ul>\n\n<h3><strong>Script directories</strong></h3>\n\n<p>These are the directories where your script files are located.</p>\n\n<p>In a new installation, certain directories are automatically listed &#8211; <em>textpattern</em> (<span class=\"caps\">TXP</span> 4.5) or <em>textpattern/vendors/jquery/jquery</em> &amp; <em>textpattern/vendors/jquery/jquery-ui</em> (<span class=\"caps\">TXP</span> 4.6).</p>\n\n<p>Enter new directories (path relative to website root directory, <em>without</em> leading “.” or “/”); or remove (i.e. “Forget”) directories which are no longer relevant.</p>\n\n<h3><strong>Managing scripts</strong></h3>\n\n<p>Script files have to be “managed” before they are included in pages.  Script files, found in the directories selected above, are listed in the “Unmanaged scripts” section.  Select (i.e. Manage) which scripts you want to be linked into your pages.</p>\n\n<p>Markup to link your “managed” script files is generated by the <code>&lt;txp:adi_scripts /&gt;</code> tag.</p>\n\n<p>You can restrict/tailor things if required:</p>\n\n<ul>\n	<li>specify which sections they will be used in (default all)</li>\n	<li>define IE conditionals to wrap around them</li>\n	<li>remove scripts (i.e. “Forget”)</li>\n	<li>temporarily disable/enable scripts</li>\n	<li>tag individual scripts</li>\n</ul>\n\n<h3><strong>Sorting</strong></h3>\n\n<p>Drag &amp; drop the scripts into whichever order you want &amp; click “Save”.</p>\n\n<p>Note that in <span class=\"caps\">TXP</span> 4.5 drag &amp; drop is implemented using a hosted version of jQuery UI, which requires a network connection. If you don&#8217;t see a “↕” symbol but do see a “Sort order” column don&#8217;t panic &#8211; you can still specify the order by tweaking the sort numbers.</p>\n\n<h3><strong>Tagging</strong></h3>\n\n<p>By default, <code>&lt;txp:adi_scripts /&gt;</code> will output links to all enabled scripts. You can modify this behaviour by assigning tags to scripts in admin and then using the ‘tags’ attribute in your code.</p>\n\n<p><code>&lt;txp:adi_scripts /&gt;</code> &#8211; will output all enabled scripts (i.e. tagging is ignored)<br />\n<code>&lt;txp:adi_scripts tags=&quot;tom&quot; /&gt;</code> &#8211; will only output enabled scripts if they have been tagged with “tom”<br />\n<code>&lt;txp:adi_scripts tags=&quot;dick,harry&quot; /&gt;</code> &#8211; will only output enabled scripts if they have been tagged with either “dick” or “harry”</p>\n\n<p>To tag a script, add the tag into the mix and then assign it to a script.  Unused tags will be removed.</p>\n\n<h3><strong>Removing scripts or directories</strong></h3>\n\n<p>“Forgetting” a script from the Managed List will delete it&#8217;s settings &amp; relegate it to the Unmanaged List &#8211; i.e. it will not longer be included in pages by the <code>&lt;txp:adi_scripts /&gt;</code> tag.</p>\n\n<p>The actual script files are not affected &#8211; they will still be present in the file system.</p>\n\n<p>If you just want to temporarily disable a script, you can disable it in the admin tab.</p>\n\n<h2><strong>HTML5</strong></h2>\n\n<p>The <code>&lt;txp:adi_scripts /&gt;</code> tag is HTML5 aware &amp; will output the <code>&lt;script&gt;...&lt;/script&gt;</code> markup in the appropriate format.</p>\n\n<h2><strong>Uninstalling adi_scripts</strong></h2>\n\n<p>Simply delete the plugin &amp; remove the <code>&lt;txp:adi_scripts /&gt;</code> tag from your pages.</p>\n\n<h2><strong>Textpattern 4.6</strong></h2>\n\n<p><strong>adi_scripts</strong> v0.3 contains code to help you migrate from <span class=\"caps\">TXP</span> 4.5 to 4.6:</p>\n\n<ul>\n	<li>if you upgrade to <strong>adi_scripts</strong> v0.3 <em>after</em> you&#8217;ve upgraded to <span class=\"caps\">TXP</span> 4.6, the plugin install process will automatically convert your <em>textpattern/jquery.js</em> script entry to <em>textpattern/vendors/jquery/jquery/jquery.js</em> and also add directories <em>textpattern/vendors/jquery/jquery</em> &amp; <em>textpattern/vendors/jquery/jquery-ui</em> into the mix.</li>\n	<li>you can upgrade to <strong>adi_scripts</strong> v0.3 whilst you&#8217;re still on <span class=\"caps\">TXP</span> 4.5, but when you eventually get to 4.6 you&#8217;ll get an upgrade prompt when you first visit the Scripts tab.  Simply do a quick visit to the <strong>adi_scripts</strong> plugin options page to kick off the automatic upgrade steps (detailed above).</li>\n</ul>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s options page and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Updates and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_scripts\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=38834\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_scripts\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_scripts - Script File Management\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- Updated for TXP 4.6\n			- minimum supported release 4.5\n			- all script files must have \".js\" extension\n	0.2		- introduced tagging\n			- security fix\n			- updates lastmod\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_scripts\')\n	;\n}\n\nif (@txpinterface == \'admin\') {\n	global $adi_scripts_debug;\n\n	$adi_scripts_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_scripts_init();\n}\n\nfunction adi_scripts_init() {\n// initial set up\n	global $prefs,$event,$adi_scripts_tab_list,$adi_scripts_prefs,$adi_scripts_debug,$adi_scripts_plugin_status,$adi_scripts_url,$adi_scripts_priv_levels,$adi_scripts_txp450,$textarray,$adi_scripts_txp460;\n\n	$adi_scripts_txp450 = (version_compare(txp_version,\'4.5\',\'>=\'));\n	$adi_scripts_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// default preferences\n	$adi_scripts_prefs = array(\n		\'adi_scripts_dir\' => array(\'value\' => \'textpattern\', \'blank\' => 1, \'private\' => 0, \'input\' => \'text_input\', \'tab\' => \'admin\'),\n		\'adi_scripts_new_dir\' => array(\'value\' => \'textpattern/vendors/jquery/jquery,textpattern/vendors/jquery/jquery-ui\', \'blank\' => 1, \'private\' => 0, \'input\' => \'text_input\', \'tab\' => \'\'),\n	);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_scripts_gtxt = array(\n		\'adi_add_tag\' => \'Add tag\',\n		\'adi_directory\' => \'Directory\',\n		\'adi_directories\' => \'Directories\',\n		\'adi_enabled\' => \'Enabled\',\n		\'adi_ie_conditionals\' => \'IE conditionals\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_invalid_input\' => \'Invalid input\',\n		\'adi_none_found\' => \'None found\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_not_readable\' => \'Not readable\',\n		\'adi_script\' => \'Script\',\n		\'adi_scripts\' => \'Scripts\',\n		\'adi_scripts_dir\' => \'Scripts directory\',\n		\'adi_scripts_dir_not_found\' => \'Directory not found\',\n		\'adi_scripts_general_warning\' => \'Some directories/files not readable\',\n		\'adi_scripts_manage_fail\' => \'Unable to manage scripts\',\n		\'adi_scripts_manage_selected\' => \'Manage selected scripts\',\n		\'adi_scripts_managed\' => \'Selected scripts now managed\',\n		\'adi_scripts_managed_scripts\' => \'Managed scripts\',\n		\'adi_scripts_unmanaged_scripts\' => \'Unmanaged scripts\',\n		\'adi_scripts_updated\' => \'Script settings updated\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_update_directories\' => \'Update directories\',\n		\'adi_update_scripts\' => \'Update script settings\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_scripts_gtxt;\n\n	// Textpack\n	$adi_scripts_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_scripts\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_scripts_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_scripts_lifecycle\',\'plugin_lifecycle.adi_scripts\');\n\n	// adi_scripts Admin tab\n	add_privs(\"adi_scripts_admin\"); // defaults to priv \'1\' only\n	register_tab(\"extensions\",\"adi_scripts_admin\",gTxt(\'adi_scripts\')); // add new tab under \'Extensions\'\n	register_callback(\"adi_scripts_admin\",\"adi_scripts_admin\");\n\n	// style\n	if ($event == \"adi_scripts_admin\")\n		register_callback(\'adi_scripts_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_scripts_admin\'))\n		register_callback(\'adi_scripts_admin_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_scripts_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_scripts\',$adi_scripts_debug);\n	if ($adi_scripts_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_scripts\'); // defaults to priv \'1\' only\n		register_callback(\'adi_scripts_options\',\'plugin_prefs.adi_scripts\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_scripts_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_scripts_options\',\'adi_scripts options\');\n		register_callback(\'adi_scripts_options\',\'adi_scripts_options\');\n	}\n\n}\n\nfunction adi_scripts_style() {\n// some style for the admin page\n\n	echo\n		\'<style type=\"text/css\">\n			#adi_scripts h1, #adi_scripts h2 { text-align:center }\n			#adi_scripts p { text-align:center }\n			#adi_scripts form table + p { margin-top:0.5em }\n			#adi_scripts table.txp-list { width:auto; margin:0 auto }\n			#adi_scripts label { margin-right:0.5em }\n			.adi_scripts_admin_managed, .adi_scripts_admin_unmanaged, .adi_scripts_prefs { margin-bottom:3em }\n			.adi_scripts_prefs .adi_scripts_text_input { margin-right:0.5em }\n			.adi_scripts_prefs .adi_scripts_new_dir { width:20em }\n			.adi_scripts_button { margin-top:1em; text-align:center }\n			td.adi_scripts_enabled, td.adi_scripts_forget, td.adi_scripts_manage { text-align:center }\n			.adi_scripts_ie input { width:6em; margin-left:0.3em; margin-right:0.3em; font-family:courier,monospace }\n			.adi_scripts_ie span { color:#000; font-weight:bold; font-family:courier,monospace }\n			.adi_scripts_disabled { color:#ccc }\n			span.adi_scripts_disabled { color:#ccc; font-weight:normal }\n			.adi_scripts_sort input { width:2.5em }\n			.adi_scripts_tag_header_header { text-align:center; border-bottom:1px solid #ddd }\n			.adi_scripts_tag_header { text-align:center; font-weight:normal }\n			.adi_scripts_tags { text-align:center }\n			.adi_scripts_sections { width:16em }\n			.adi_scripts_sections label { margin-right:1em }\n			.adi_scripts_multi_checkboxes { margin:0.3em 0 0.5em; width:15em; height:5em; padding:0.2em; overflow:auto; border:1px solid #ccc }\n			.adi_scripts_multi_checkboxes label { display:block; float:none; width:auto }\n			.adi_scripts_drag_drop { display:none } /* revealed by jQuery later */\n		</style>\';\n}\n\nfunction adi_scripts_admin($event, $step) {\n// the adi_scripts admin tab\n	global $prefs,$adi_scripts_debug,$adi_scripts_plugin_status,$adi_scripts_prefs,$adi_scripts_txp450,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\'.br;\n\n	$message = \'\';\n	$installed = adi_scripts_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_scripts_upgrade();\n		if ($upgrade_required)\n			$message = array(gTxt(\'adi_upgrade_required\'),E_WARNING);\n		else {\n			/* stuff */\n		}\n	}\n	else\n		$message = array(gTxt(\'adi_not_installed\'),E_ERROR);\n\n	// admin steps\n	$new_tag=\'\';\n	if ($step == \'update\') {\n		$new_settings = ps(\'adi_scripts_settings\');\n		$result = adi_scripts_update_settings($new_settings);\n		$result ? $message = gTxt(\'adi_scripts_updated\') : $message = array(gTxt(\'adi_scripts_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		// process prefs\n		foreach ($adi_scripts_prefs as $pref_name => $pref_info) { // awkward to convert to std adi_admin_plugin_update_prefs() - two result conditions - return TRUE/FALSE/message?\n			// get/massage pref value\n			if (array_key_exists($pref_name,$_POST)) // pref present in POST\n				$value = $_POST[$pref_name];\n			else if ($pref_info[\'input\'] == \'yesnoradio\') // pref missing from POST, make it zero\n				$value = \'0\';\n// 			else if ($pref_info[\'blank\']) // pref missing from POST, pref allowed to be blank, make it blank\n// 				$value = \'\';\n			else\n				$value = $pref_info[\'value\']; // pref missing from POST, reset value\n			// special processing & validation\n			if ($pref_name == \'adi_scripts_dir\') {\n				// compare stored dir list with pref changes\n				if (adi_scripts_pref(\'adi_scripts_dir\'))\n					$old_list = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n				else\n					$old_list = array();\n				$new_list = $value;\n				$updated_list = array();\n				// see if existing dir needs to be removed in dir list\n				foreach ($old_list as $dir) {\n					if (array_search($dir,$new_list) === FALSE)\n						$updated_list[] = $dir;\n				}\n				// add a newcomer (subject to terms & conditions)\n				$input_ok = TRUE;\n				if ($new_list[\'new\']) {\n// 					if (strpbrk($new_list[\'new\'],\"\\\\/?%*:|\\\"<>\") === FALSE) { /* check directory is kosher (i.e. doesn\'t contain illegal characters) */\n					if (strpbrk($new_list[\'new\'],\"\\\\?%*:|\\\"<>\") === FALSE) { /* check directory doesn\'t contain illegal characters; as of v0.3 & TXP 4.6 now allowing \"/\" to cater for subdirectories */\n						if (preg_match(\"/^[.\\/]/\",$new_list[\'new\']) === 0) { /* check directory doesn\'t start with . or / ... STRIP INSTEAD? */\n							$updated_list[] = $new_list[\'new\'];\n						}\n						else\n							$input_ok = FALSE;\n					}\n					else\n						$input_ok = FALSE;\n				}\n				// lose les oubliettees from the database (NOT SURE WHY DID I DID THIS ANYWAY ... i.e. delete scripts when \"forget\" their directories)\n// 				foreach ($new_list as $dir) {\n// 					$where = \"directory=\'\".doSlash($dir).\"\'\";\n// 					$result = $result && safe_delete(\'adi_scripts\',$where,$adi_scripts_debug);\n// 				}\n				$value = implode(\',\',$updated_list);\n			}\n			if ($pref_name == \'adi_scripts_new_dir\') // internal use only, so don\'t think it should be blank (tab=\'\')\n				$value = adi_scripts_pref(\'adi_scripts_new_dir\');\n			// some values not allowed to be blank, reset to default\n			if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n					$value = $adi_scripts_prefs[$pref_name][\'value\'];\n			$result = $result && adi_scripts_pref($pref_name,$value);\n		}\n		// it\'s a result\n		if ($result)\n			$message = gTxt(\'preferences_saved\');\n		else\n			$message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		// DRS\n		if ($result && !$input_ok)\n			$message = array(gTxt(\'adi_invalid_input\'),E_ERROR);\n	}\n	else if ($step == \'new_scripts\') {\n		$new_scripts = ps(\'adi_scripts_new_list\');\n		$result = TRUE;\n		if ($new_scripts)\n			foreach ($new_scripts as $directory => $files)\n				foreach ($files as $file)\n					$result = $result && adi_scripts_manage_script($file,$directory);\n		$result ? $message = gTxt(\'adi_scripts_managed\') : $message = array(gTxt(\'adi_scripts_manage_fail\'),E_ERROR);\n	}\n	else if ($step == \'new_tag\') {\n		$new_tag = sanitizeForURL(assert_string(ps(\'adi_scripts_new_tag\')));\n	}\n\n	$installed = adi_scripts_installed();\n\n	$adi_scripts_problem_count = 0; // may morph into a list of dodgy dirs/files one day\n\n	// see if scripts directories are ok & gather list of script files from each\n	if ($installed) {\n		if (adi_scripts_pref(\'adi_scripts_dir\'))\n			$directories = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n		else\n			$directories = array();\n		if ($adi_scripts_debug) {\n			echo \"SCRIPT DIRECTORIES:\".br;\n			dmp($directories);\n		}\n		$file_list = array();\n		foreach ($directories as $dir) {\n			if ($adi_scripts_debug) echo \"CHECKING DIRECTORY $dir\".br;\n			$dir_message = adi_scripts_check_dir($dir);\n			if (empty($dir_message))\n				$file_list[$dir] = adi_scripts_get_files($dir);\n			else\n				$adi_scripts_problem_count += 1;\n		}\n		if ($adi_scripts_debug) {\n			echo br.\"COMBINED FILE LIST\".br;\n			dmp($file_list);\n			if ($new_tag != \'\')\n				echo br.\"NEW TAG = $new_tag\".br.br;\n		}\n	}\n\n	// admin tab markup\n	$out = \'\';\n	if ($installed && !$upgrade_required) {\n		$out .= \'<div id=\"adi_scripts\">\';\n		// managed scripts\n		$out .= tag(\n			adi_scripts_managed_list($new_tag)\n			,\'div\'\n			,\' class=\"adi_scripts_admin_managed\"\'\n		);\n		// unmanaged scripts\n		$out .= tag(\n			adi_scripts_unmanaged_list($file_list)\n			,\'div\'\n			,\' class=\"adi_scripts_admin_unmanaged\"\'\n		);\n		// directories\n		$out .= tag(\n			adi_scripts_directories($directories)\n			,\'div\'\n			,\' class=\"adi_scripts_prefs\"\'\n		);\n		// new tag\n		$out .= tag(\n			adi_scripts_new_tag()\n			,\'div\'\n			,\' class=\"adi_scripts_prefs\"\'\n		);\n		$out .= \'</div>\';\n	}\n\n	if (!$message && $adi_scripts_problem_count)\n		$message = array(gTxt(\'adi_scripts_general_warning\'),E_WARNING);\n\n	// generate page\n	pagetop(gTxt(\'adi_scripts\').\' \'.gTxt(\'admin\'),$message);\n\n	echo $out;\n\n	if ($adi_scripts_debug) {\n		if ($installed) {\n			echo \'<b>Prefs:</b><br/>\';\n			foreach ($adi_scripts_prefs as $name => $this_pref)\n				echo $name.\' = \'.adi_scripts_pref($name).br;\n			echo \"adi_scripts_problem_count = $adi_scripts_problem_count\";\n			echo \'<br/>\';\n		}\n	}\n}\n\nfunction adi_scripts_admin_script() {\n// jQuery magic for admin tab\n	global $prefs,$adi_scripts_txp460;\n\n	$ui_script = \'\';\n	if (!$adi_scripts_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function(){\n		// section list peekaboo\n		$(\'td.adi_scripts_sections\').each(function(){\n			var this_td = $(this);\n			// hide sections if \"All\" ticked\n			$(\'input[name*=\"all_sections\"][value=\"1\"]:checked\',this).each(function(){\n				$(\'div\',this_td).hide();\n			});\n			// an \"All\" has been deselected\n			$(\'input[name*=\"all_sections\"]:radio:eq(1)\',this).change(function(){\n				$(\'div\',this_td).show();\n			});\n			// an \"All\" has been selected\n			$(\'input[name*=\"all_sections\"]:radio:eq(0)\',this).change(function(){\n				$(\'div\',this_td).hide();\n			});\n		});\n		// disabled script ghosting\n		$(\'td.adi_scripts_enabled\').each(function(){\n 			var this_td = $(this);\n			// dim scripts if not enabled\n			$(\'input:checkbox:not(:checked)\',this).each(function(){\n				$(this_td).parent().addClass(\"adi_scripts_disabled\");\n			});\n			// script enabled/disabled\n			$(\'input\',this).change(function(){\n				if (this.checked)\n					$(this_td).parent().removeClass(\"adi_scripts_disabled\");\n				else\n 					$(this_td).parent().addClass(\"adi_scripts_disabled\");\n			});\n		});\n	});\n</script>\n<!-- drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'.adi_scripts_admin_managed table tbody tr\').css(\'cursor\',\'pointer\'); // make like a pointer\n			$(\'.adi_scripts_admin_managed table .adi_scripts_drag_drop\').show(); // reveal drag & drop icon\n			$(\'.adi_scripts_admin_managed table .adi_scripts_sort\').hide(); // lose sort value column\n			$(\".adi_scripts_admin_managed table tbody\").sortable();\n			$(\".adi_scripts_admin_managed table tbody\").bind(\"sortstop\",function(event, ui) {\n				$(\'td.adi_scripts_sort input\').each(function(i) { // renumber sort values\n					var this_td = $(this);\n					this_td.val(i+10);\n				});\n			});\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_scripts_options($event,$step) {\n// display adi_scripts options: install/uninstall/upgrade\n	global $adi_scripts_debug,$adi_scripts_url,$adi_scripts_plugin_status;\n\n	$message = \'\';\n	$installed = adi_scripts_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_scripts_upgrade();\n		if ($upgrade_required) { // upgrade\n			$res = adi_scripts_upgrade(TRUE);\n			if ($res)\n				$message = gTxt(\'adi_upgraded\');\n			else\n				$message = array(gTxt(\'adi_upgrade_fail\'),E_ERROR);\n		}\n		// $stepping out\n		if ($step == \"uninstall\") { // uninstall adi_scripts\n			$res = adi_scripts_uninstall();\n			if ($res)\n	    		$message = gTxt(\'adi_uninstalled\');\n			else\n	    		$message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n		}\n		else if ($step == \'textpack\') {\n			$adi_textpack = file_get_contents($adi_scripts_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n\n		else if ($step == \'update_prefs\') {\n			$result = TRUE;\n			// run through all prefs\n			foreach ($adi_scripts_prefs as $name => $data) {\n				if (array_key_exists($name,$_POST))\n					$value = $_POST[$name];\n				else if ($data[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $data[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($name == \'adi_scripts_admin_privs\') {\n						// IF NO ADMIN TAB THEN LOSE THIS (I.E. ALLOWED TO SWITCH SELF & PUBLISHER OFF)\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				$non_blanks = array();\n				foreach ($non_blanks as $non_blank)\n					if (($name == $non_blank) && (trim($value) == \'\'))\n						$value = $adi_scripts_prefs[$non_blank][\'value\'];\n				$result &= adi_scripts_pref($name,$value);\n			}\n			$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		}\n\n	}\n	else { // not installed\n		if ($step == \"install\") { // install adi_scripts\n			$res = adi_scripts_install();\n			if ($res)\n				$message = gTxt(\'adi_installed\');\n			else\n				$message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n		}\n		else\n			$message = array(gTxt(\'adi_not_installed\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_scripts - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_scripts_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n			,\'\',\'\',\'post\',\'adi_scripts_nstall_button\'\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", gTxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n			,\'\',\'\',\'post\',\'adi_scripts_nstall_button adi_scripts_uninstall_button\'\n		);\n\n	if ($adi_scripts_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_scripts_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_scripts \'.gTxt(\'plugin_prefs\'),\'h1\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_scripts_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_scripts_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n\nfunction adi_scripts_install() {\n// install adi_scripts in database\n	global $adi_scripts_debug,$adi_scripts_prefs,$adi_scripts_txp460;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$res = safe_query(\n		\"CREATE TABLE IF NOT EXISTS \"\n		.safe_pfx(\'adi_scripts\')\n		.\" (\n			`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n			`filename` VARCHAR(255) NOT NULL,\n			`directory` VARCHAR(255) NOT NULL,\n			`enabled` TINYINT(1) DEFAULT 0 NOT NULL,\n			`tags` VARCHAR(255) NOT NULL,\n			`sections` VARCHAR(255) NOT NULL,\n			`ie` VARCHAR(255) NOT NULL,\n			`sort` TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL\n		);\"\n		,$adi_scripts_debug);\n	// fix up default pref values for 4.6\n	if ($adi_scripts_txp460) {\n		$res = $res && adi_scripts_pref(\'adi_scripts_dir\',adi_scripts_pref(\'adi_scripts_new_dir\')); // create dirs pref with 4.6 dirs\n		$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',\'\'); // flush out new dirs pref (to prevent triggering upgrade again)\n	}\n\n 	return $res;\n}\n\nfunction adi_scripts_uninstall() {\n// uninstall adi_scripts\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_scripts\').\";\",$adi_scripts_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_scripts_%\'\",$adi_scripts_debug);\n\n	return $res;\n}\n\nfunction adi_scripts_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_scripts_debug,$adi_scripts_txp450;\n\n	$result = \'?\';\n	// set upgrade flag if upgrading/reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && $adi_scripts_txp450 && adi_scripts_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_scripts_install();\n	else if ($step == \'deleted\')\n		$result = adi_scripts_uninstall();\n	if ($upgrade)\n		$result = $result && adi_scripts_upgrade(TRUE);\n	if ($adi_scripts_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_scripts_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_scripts_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_scripts_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_scripts_prefs))\n			$html = $adi_scripts_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_scripts_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_scripts_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_scripts_installed($table=\'adi_scripts\') {\n// see if supplied table is present\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$found = FALSE;\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\",$adi_scripts_debug);\n	$a = nextRow($rs);\n	if ($a)\n		$found = TRUE;\n\n	return $found;\n}\n\nfunction adi_scripts_upgrade($do_upgrade=FALSE) {\n// internal upgrade required?\n	global $adi_scripts_debug,$adi_scripts_txp450,$adi_scripts_txp460;\n\n	$upgrade_required = FALSE;\n	if (adi_scripts_installed()) { // find out what needs upgrading\n		// version 0.2 - tagging\n		$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_scripts\').\" LIKE \'tags\'\",$adi_scripts_debug); // find out if column exists\n		$a = nextRow($rs);\n		$v0_2 = empty($a);\n		// version 0.3 flags on different TXP versions\n		$v0_3_45 = !get_pref(\'adi_scripts_new_dir\',\'\',TRUE) && (version_compare(txp_version,\'4.5\',\'>=\')) && (version_compare(txp_version,\'4.5.7\',\'<=\'));\n		$v0_3_46 = adi_scripts_pref(\'adi_scripts_new_dir\') && $adi_scripts_txp460;\n		$upgrade_required = $upgrade_required || $v0_2 || $v0_3_45 || $v0_3_46;\n		if ($do_upgrade) {\n			if ($upgrade_required) {\n				$res = TRUE;\n				if ($v0_2)\n					$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_scripts\").\" ADD `tags` VARCHAR(255) NOT NULL\",$adi_scripts_debug); // add new column\n				if ($v0_3_45) // set up \"new dirs\" flag for later (when TXP is upgraded to 4.6)\n					$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',adi_scripts_pref(\'adi_scripts_new_dir\')); // create new dirs pref\n				if ($v0_3_46) { // new \"default dirs\" to add? - i.e. TXP 4.6 jQuery/jQuery-UI paths\n					// shift new \"default dirs\" to current directories list\n					$current = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n					$new = explode(\',\',adi_scripts_pref(\'adi_scripts_new_dir\'));\n					$combined = array_filter(array_unique(array_merge($current,$new))); // combine current with new, lose duplicates & blanks\n					$res = $res && adi_scripts_pref(\'adi_scripts_dir\',implode(\',\',$combined)); // update dirs pref\n					$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',\'\'); // flush out new dirs pref (to prevent triggering upgrade again)\n					// modify standard jQuery script entry: \"textpattern/jquery.js\" -> \"textpattern/vendors/jquery/jquery/jquery.js\"\n					$set = \"directory=\'textpattern/vendors/jquery/jquery\'\";\n					$where = \"filename=\'jquery.js\' AND directory=\'textpattern\'\";\n					$res = $res && safe_update(\'adi_scripts\',$set,$where,$adi_scripts_debug);\n\n				}\n				return $res;\n			}\n			else\n				return FALSE;\n		}\n		else\n			return $upgrade_required;\n	}\n	else\n		return FALSE;\n}\n\nfunction adi_scripts_managed_list($new_tag) {\n// managed scripts\n	global $adi_scripts_debug,$prefs,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$out = \'\';\n	$script_settings = \'\';\n	$unreadable_count = 0;\n	$new_sort = 0;\n\n	// get stored scripts info\n	$rows = safe_rows(\'*\',\'adi_scripts\',\'1=1 ORDER BY sort,id\', $adi_scripts_debug);\n	$out .= tag(gTxt(\'adi_scripts\'),\'h1\');\n	if ($adi_scripts_debug)\n		dmp($rows);\n\n	// tags\n	$tag_list = adi_scripts_tag_list($new_tag);\n\n	if ($rows) { // managed scripts found in the database\n		foreach ($rows as $row) {\n			$new_sort += 10;\n			extract($row);\n			$file_path = $prefs[\'path_to_site\'].\'/\'.$directory.\'/\'.$filename;\n			if (file_exists($file_path) && is_readable($file_path))\n				$asterisk = \'\';\n			else {\n				$asterisk = \'*\';\n				$unreadable_count += 1;\n			}\n			if ($ie)\n				$class = \'\';\n			else\n				$class = \' class=\"adi_scripts_disabled\"\';\n			// tags\n			$tag_settings = \'\';\n			foreach ($tag_list as $tag)\n				$tag_settings .= tda(checkbox2(\"adi_scripts_settings[$id][tags][$tag]\",(array_search($tag,explode(\',\',$tags)) !== FALSE)),\' class=\"adi_scripts_tags\"\');\n			// scripts settings\n			$script_settings .=\n				tr(\n					tda(tag(\'&#x2195;\',\'span\',\' class=\"adi_scripts_drag_drop\"\'))\n					// script filename\n					.tda(\n						tag(\n							$directory\n							.\'/\'\n							.$filename\n							.$asterisk\n							,\'span\'\n							,($asterisk ? \' class=\"warning\"\' : \'\')\n						)\n						.hInput(\"adi_scripts_settings[$id][filename]\",$filename)\n						.hInput(\"adi_scripts_settings[$id][directory]\",$directory)\n\n\n					)\n					// enabled?\n					.tda(checkbox2(\"adi_scripts_settings[$id][enabled]\",$enabled),\' class=\"adi_scripts_enabled\"\')\n					// tags\n					.$tag_settings\n					// sections\n					.tda(\n						tag(gTxt(\'all\'),\'label\')\n						.radio(\"adi_scripts_settings[$id][all_sections]\",\'1\',($sections == \'\'))\n						.sp.sp\n						.tag(gTxt(\'selected\'),\'label\')\n						.radio(\"adi_scripts_settings[$id][all_sections]\",\'0\',($sections != \'\'))\n						.tag(adi_scripts_section_checkboxes(\"adi_scripts_settings[$id][sections]\",$sections),\'div\',\' class=\"adi_scripts_multi_checkboxes\"\')\n						,\' class=\"adi_scripts_sections\"\'\n					)\n					// IE conditionals\n					.tda(\n						tag(htmlentities(\'<!--[if\'),\'span\',$class).finput(\'text\',\"adi_scripts_settings[$id][ie]\",$ie).tag(htmlentities(\']>\'),\'span\',$class)\n						,\' class=\"adi_scripts_ie\"\'\n					)\n					// (sort)\n					.tda(finput(\"text\",\"adi_scripts_settings[$id][sort]\",$new_sort),\' class=\"adi_scripts_sort\"\')\n					// forget?\n					.tda(checkbox2(\"adi_scripts_settings[$id][forget]\",0),\' class=\"adi_scripts_forget\"\')\n				);\n		}\n\n		// tag headers\n		$tag_header_header = \'\';\n		if ($tag_list) {\n			if (count($tag_list) == 1)\n				$header_text = gTxt(\'tag\');\n			else\n				$header_text = gTxt(\'tags\');\n			$tag_header_header = tag(hcell(sp,\'\',\' colspan=\"3\"\').hcell($header_text,\'\',\' colspan=\"\'.count($tag_list).\'\" class=\"adi_scripts_tag_header_header\"\').hcell(sp,\'\',\' colspan=\"4\"\'),\'tr\');\n		}\n		$tag_headers = \'\';\n		foreach ($tag_list as $tag)\n			$tag_headers .= hcell($tag,\'\',\' class=\"adi_scripts_tag_header\"\');\n\n		// generate table\n		$out .= form(\n			startTable(\'\',\'\',\'txp-list\')\n			.tag(\n				$tag_header_header\n				.tag(\n					hcell(sp) // filler for drag/drop icon\n					.hcell(gTxt(\'adi_script\'))\n					.hcell(gTxt(\'adi_enabled\'))\n					.$tag_headers\n					.hcell(gTxt(\'sections\'))\n					.hcell(gTxt(\'adi_ie_conditionals\'))\n					.hcell(gTxt(\'sort\'),\'\',\' class=\"adi_scripts_sort\"\')\n					.hcell(gTxt(\'forget\'))\n					,\'tr\'\n					)\n				,\'thead\'\n			)\n			.tag($script_settings,\'tbody\')\n			.endTable()\n			.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n			.tag(\n				fInput(\"submit\", \"do_something\",gTxt(\'save\'),\'publish\').\n				eInput(\"adi_scripts_admin\").sInput(\"update\")\n				,\'div\'\n				,\' class=\"adi_scripts_button\"\'\n			)\n		);\n	}\n	else\n		$out .= graf(tag(gTxt(\'adi_none_found\'),\'em\'));\n\n	if ($adi_scripts_debug)\n		echo \"UNREADABLE COUNT = $unreadable_count\".br;\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_unmanaged_list($file_list) {\n// unmanaged scripts\n	global $prefs,$adi_scripts_debug,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$out = \'\';\n	$checkboxes = \'\';\n	$unreadable_count = 0;\n	foreach ($file_list as $dir => $files) {\n		$scripts_path = $prefs[\'path_to_site\'].\'/\'.$dir;\n		foreach ($files as $file) {\n			$row = safe_row(\'*\',\'adi_scripts\',\"filename=\'\".doSlash($file).\"\' AND directory=\'\".doSlash($dir).\"\'\",$adi_scripts_debug);\n			if (!$row) { // not being managed\n				$file_path = $scripts_path.\'/\'.$file;\n				if (is_readable($file_path))\n					$asterisk = \'\';\n				else {\n					$asterisk = \'*\';\n					$unreadable_count += 1;\n				}\n				$checkboxes .=\n					tr(\n						tda(\n							tag(\n								$dir.\'/\'.$file.$asterisk\n								,\'label\'\n								,($asterisk ? \' class=\"warning\"\' : \'\')\n							)\n						)\n						.tda(\n							checkbox(\"adi_scripts_new_list[$dir][]\",$file,FALSE)\n							,\' class=\"adi_scripts_manage\"\'\n						)\n					);\n			}\n		}\n	}\n	$out .= tag(gTxt(\'adi_scripts_unmanaged_scripts\'),\'h2\');\n	if ($checkboxes) { // unmanaged scripts found in scripts directory\n		$out .= form(\n				startTable(\'\',\'\',\'txp-list\')\n				.tag(\n					tag(\n						hcell(gTxt(\'adi_script\'))\n						.hcell(gTxt(\'manage\'))\n						,\'tr\'\n					)\n					,\'thead\'\n				)\n				.tag($checkboxes,\'tbody\')\n				.endTable()\n				.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n				.tag(\n					fInput(\"submit\", \"do_something\", gTxt(\'adi_scripts_manage_selected\'), \"smallerbox\")\n					.eInput(\"adi_scripts_admin\").sInput(\"new_scripts\")\n					,\'div\'\n					,\' class=\"adi_scripts_button\"\'\n				)\n			);\n	}\n	else\n		$out .= graf(tag(gTxt(\'adi_none_found\'),\'em\'));\n\n	if ($adi_scripts_debug)\n		echo \"UNREADABLE COUNT = $unreadable_count\".br;\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_directories($directories) {\n// directories of interest\n	global $prefs,$adi_scripts_problem_count;\n\n	$out = \'\';\n	$markup = \'\';\n	$unreadable_count = 0;\n\n	// directories of interest\n	foreach ($directories as $dir) {\n		$scripts_path = $prefs[\'path_to_site\'].\'/\'.$dir;\n		if (is_readable($scripts_path))\n			$asterisk = \'\';\n		else {\n			$asterisk = \'*\';\n			$unreadable_count += 1;\n		}\n		$markup .=\n			tr(\n				tda(n) // spacer for \'+\' column\n				.tda(\n					tag(\n						$dir.$asterisk\n						,\'label\'\n						,($asterisk ? \' class=\"warning\"\' : \'\')\n					)\n				)\n				.tda(\n					checkbox(\"adi_scripts_dir[]\",$dir,FALSE)\n					,\' class=\"adi_scripts_manage\"\'\n				)\n			);\n	}\n	$markup .=\n		tr(\n			tda(\n				tag(\'+\',\'b\')\n			)\n			.tda(\n				finput(\"text\",\'adi_scripts_dir[new]\',\'\',\'adi_scripts_new_dir\')\n			)\n			.tda(n)\n		);\n\n	$out = tag(gTxt(\'adi_directories\'),\'h2\')\n	.form(\n		startTable(\'\',\'\',\'txp-list\')\n		.tag(\n			tag(\n				hcell(n) // spacer for \'+\' column\n				.hcell(gTxt(\'adi_directory\'))\n				.hcell(gTxt(\'forget\'))\n				,\'tr\'\n			)\n			,\'thead\'\n		)\n		.tag($markup,\'tbody\')\n		.endTable()\n		.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n		.tag(\n			fInput(\"submit\", \"do_something\", gTxt(\'adi_update_directories\'), \"smallerbox\")\n			.eInput(\"adi_scripts_admin\")\n			.sInput(\"update_prefs\")\n			,\'div\'\n			,\' class=\"adi_scripts_button\"\'\n		)\n	);\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_new_tag() {\n// introduce new tag in the mix\n\n	$out =\n		tag(gTxt(\'tags\'),\'h2\')\n		.form(\n			graf(\n				finput(\"text\",\'adi_scripts_new_tag\',\'\',\'adi_scripts_text_input\')\n				.fInput(\"submit\", \"do_something\", gTxt(\'adi_add_tag\'), \"smallerbox\")\n				.eInput(\"adi_scripts_admin\").sInput(\"new_tag\")\n			)\n		);\n	return $out;\n}\n\nfunction adi_scripts_manage_script($file,$directory) {\n// bring a script into the fold\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n	$set = \"filename=\'\".doSlash($file).\"\', directory=\'\".doSlash($directory).\"\', enabled=1, tags=\'\', sections=\'\', ie=\'\', sort=\'255\'\";\n	return safe_insert(\'adi_scripts\',$set,$adi_scripts_debug);\n}\n\nfunction adi_scripts_update_settings($settings) {\n// update script settings in the database\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n	$res = TRUE;\n	foreach ($settings as $setting) {\n		extract($setting);\n		if (isset($setting[\'forget\'])) {\n			$where = \"filename=\'\".doSlash($filename).\"\' AND directory=\'\".doSlash($directory).\"\'\";\n			$res = $res && safe_delete(\'adi_scripts\',$where,$adi_scripts_debug);\n		}\n		else {\n			if (isset($setting[\'enabled\']))\n				$enabled = $setting[\'enabled\'];\n			else\n				$enabled = 0;\n			if ($sort == \'\') // set blank to zero\n				$sort = 0;\n			if (array_key_exists(\'tags\',$setting))\n				$tag_list = doSlash(strtolower(implode(\',\',array_keys($setting[\'tags\']))));\n			else\n				$tag_list = \'\';\n			if (array_key_exists(\'sections\',$setting))\n				$section_list = doSlash(implode(\',\',$setting[\'sections\']));\n			else\n				$section_list = \'\';\n			if ($setting[\'all_sections\']) // radio button\n				$section_list = \'\';\n			$set = \"enabled=$enabled, tags=\'\".$tag_list.\"\', sections=\'\".$section_list.\"\', ie=\'\".doSlash($ie).\"\', sort=\'\".doSlash($sort).\"\'\";\n			$where = \"filename=\'\".doSlash($filename).\"\' AND directory=\'\".doSlash($directory).\"\'\";\n			$res = $res && safe_update(\'adi_scripts\',$set,$where,$adi_scripts_debug);\n		}\n	}\n\n	update_lastmod();\n	return $res;\n}\n\nfunction adi_scripts_check_dir($directory) {\n// nobody expects the Spanish Inquisition\n	global $prefs,$adi_scripts_debug;\n\n	$scripts_path = $prefs[\'path_to_site\'].\'/\'.$directory;\n	$message = \'\';\n	// check scripts directory\n	if (file_exists($scripts_path)) {\n		if ($adi_scripts_debug) echo \"$scripts_path found\".br;\n		if (is_dir($scripts_path)) {\n			if ($adi_scripts_debug) echo \"$scripts_path is a directory\".br;\n			if (is_readable($scripts_path)) {\n				if ($adi_scripts_debug) echo \"$scripts_path is readable\".br;\n			}\n			else {\n				if ($adi_scripts_debug) echo \"$scripts_path is not readable\".br;\n				$message = array(gTxt(\'adi_scripts_dir_not_readable\'),E_ERROR);\n			}\n		}\n		else {\n			if ($adi_scripts_debug) echo \"$scripts_path not a directory\".br;\n			$message = array(gTxt(\'adi_scripts_dir_not_dir\'),E_ERROR);\n		}\n	}\n	else {\n		if ($adi_scripts_debug) echo \"$scripts_path not found\".br;\n		$message = array(gTxt(\'adi_scripts_dir_not_found\'),E_ERROR);\n	}\n\n	return $message;\n}\n\nfunction adi_scripts_get_files($directory) {\n// generate list of files in given directory\n	global $prefs,$adi_scripts_debug;\n\n	$file_list = array();\n	$scripts_path = $prefs[\'path_to_site\'].\'/\'.$directory;\n	if ($adi_scripts_debug)\n		echo br.\"CONTENTS OF $directory\".br;\n\n	// get filenames from directory & decide whether to ignore or not\n	if ($handle = opendir($scripts_path)) {\n		while (false !== ($file = readdir($handle))) {\n			$ok = FALSE;\n			$diags = $file. \' (\';\n			if (is_dir($scripts_path.\'/\'.$file)) // directory\n				$diags .=  \'DIR\';\n			else if (strpos($file,\'.\') === 0) // hidden file\n				$diags .=  \'HIDDEN FILE\';\n			else if (is_file($scripts_path.\'/\'.$file)) { // file\n				$diags .=  \'FILE\';\n				$path_parts = pathinfo($file);\n				if (isset($path_parts[\'extension\'])) {\n					if ($path_parts[\'extension\'] != \'js\')\n						$diags .=  \' - WRONG EXTENSION\';\n					else\n						$ok = TRUE;\n				}\n				else\n					$diags .=  \' - NO EXTENSION\';\n			}\n			if (!$ok)\n				$diags .=  \' - IGNORED\';\n			if ($ok && !is_readable($scripts_path.\'/\'.$file)) // script no readable\n				$diags .=  \' - NOT READABLE\';\n			if ($adi_scripts_debug)\n				echo $diags.\')\'.br;\n			if ($ok)\n				$file_list[] = $file;\n		}\n		closedir($handle);\n	}\n\n	if ($adi_scripts_debug) {\n		echo br.\"FILTERED FILE LIST FROM $directory:\".br;\n		dmp($file_list);\n	}\n\n	return $file_list;\n}\n\nfunction adi_scripts_section_checkboxes($field_name,$value) {\n// generate section checkboxes\n\n	$section_list = explode(\',\',$value);\n	$out = \'\';\n	$rs = safe_column(\'name\', \'txp_section\', \'1=1\');\n	if ($rs) {\n		foreach ($rs as $section)\n			$out .= tag(checkbox($field_name.\'[]\',$section,(array_search($section,$section_list) !== FALSE ? \'1\' : \'0\')).$section,\'label\');\n		return $out;\n	}\n\n	return FALSE;\n}\n\nfunction adi_scripts_tag_list($new_tag=\'\') {\n// create list of tags from DB\n	global $adi_scripts_debug;\n\n	// get all tags in DB\n	$rows = safe_rows(\'tags\',\'adi_scripts\',\'1=1\');\n	$tag_list = array();\n	foreach ($rows as $row) {\n		$r = explode(\',\',$row[\'tags\']);\n		$tag_list = array_merge($tag_list,$r);\n	}\n	// new tag?\n	if ($new_tag != \'\')\n		$tag_list[] = $new_tag;\n	// create unique list\n	$tag_list = array_unique($tag_list);\n	// sort\n	sort($tag_list);\n	// find empty \"tag\" (i.e. untagged) & lose it\n	$blank_key = array_search(\'\',$tag_list);\n	if ($blank_key !== FALSE)\n		unset($tag_list[$blank_key]);\n\n	if ($adi_scripts_debug) {\n		echo \'TAG LIST:\';\n		dmp($tag_list);\n	}\n\n	return $tag_list;\n}\n\nfunction adi_scripts($atts) {\n// <txp:adi_scripts /> tag\n	global $prefs,$s;\n\n	extract(lAtts(array(\n		\'tags\'	=> \'\',		// restrict to specified tagged scripts\n		\'debug\' => 0		// to also output <script> code\n	), $atts));\n\n	$out = array();\n\n	// HTML5 adjustment\n	$type = \' type=\"text/javascript\"\';\n	if (isset($prefs[\'doctype\']))\n		if ($prefs[\'doctype\'] == \'html5\')\n			$type = \'\';\n\n	// tagged?\n	if ($tags == \'\')\n		$tags_q = \'\';\n	else { // (stolen from keywords in publish.php)\n		$tag_list = doSlash(do_list($tags));\n		foreach ($tag_list as $tag) {\n			$tagparts[] = \"FIND_IN_SET(\'\".$tag.\"\',tags)\";\n		}\n		$tags_q = \" (\" . join(\' OR \',$tagparts) . \") AND \";\n	}\n\n	// get script filenames from database which are \"enabled\" ... and, if sections non-blank, match current section\n	$rows = safe_rows(\'*\',\'adi_scripts\',\"enabled=1 AND \".$tags_q.\"IF(sections=\'\',1,FIND_IN_SET(\'\".doSlash($s).\"\',sections)) ORDER BY sort\");\n\n	if ($rows) { // output <script>...</script> markup\n		foreach ($rows as $row) {\n			extract($row);\n			$ie_start = $ie_end = \'\';\n			if ($ie) {\n				$ie_start = \"<!--[if $ie]>\";\n				$ie_end = \'<![endif]-->\';\n			}\n			$out[] = $ie_start.\'<script\'.$type.\' src=\"http://\'.$prefs[\'siteurl\'].\'/\'.$directory.\'/\'.$filename.\'\"></script>\'.$ie_end;\n		}\n	}\n\n	if ($debug) // output script markup as plain text\n		foreach ($out as $line)\n			$out[] = htmlentities($line).br;\n\n	return implode(\"\\n\",$out);\n}\n', '\n/*\n	adi_scripts - Script File Management\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.3		- Updated for TXP 4.6\n			- minimum supported release 4.5\n			- all script files must have \".js\" extension\n	0.2		- introduced tagging\n			- security fix\n			- updates lastmod\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_scripts\')\n	;\n}\n\nif (@txpinterface == \'admin\') {\n	global $adi_scripts_debug;\n\n	$adi_scripts_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_scripts_init();\n}\n\nfunction adi_scripts_init() {\n// initial set up\n	global $prefs,$event,$adi_scripts_tab_list,$adi_scripts_prefs,$adi_scripts_debug,$adi_scripts_plugin_status,$adi_scripts_url,$adi_scripts_priv_levels,$adi_scripts_txp450,$textarray,$adi_scripts_txp460;\n\n	$adi_scripts_txp450 = (version_compare(txp_version,\'4.5\',\'>=\'));\n	$adi_scripts_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	// default preferences\n	$adi_scripts_prefs = array(\n		\'adi_scripts_dir\' => array(\'value\' => \'textpattern\', \'blank\' => 1, \'private\' => 0, \'input\' => \'text_input\', \'tab\' => \'admin\'),\n		\'adi_scripts_new_dir\' => array(\'value\' => \'textpattern/vendors/jquery/jquery,textpattern/vendors/jquery/jquery-ui\', \'blank\' => 1, \'private\' => 0, \'input\' => \'text_input\', \'tab\' => \'\'),\n	);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_scripts_gtxt = array(\n		\'adi_add_tag\' => \'Add tag\',\n		\'adi_directory\' => \'Directory\',\n		\'adi_directories\' => \'Directories\',\n		\'adi_enabled\' => \'Enabled\',\n		\'adi_ie_conditionals\' => \'IE conditionals\',\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_invalid_input\' => \'Invalid input\',\n		\'adi_none_found\' => \'None found\',\n		\'adi_not_installed\' => \'Not installed\',\n		\'adi_not_readable\' => \'Not readable\',\n		\'adi_script\' => \'Script\',\n		\'adi_scripts\' => \'Scripts\',\n		\'adi_scripts_dir\' => \'Scripts directory\',\n		\'adi_scripts_dir_not_found\' => \'Directory not found\',\n		\'adi_scripts_general_warning\' => \'Some directories/files not readable\',\n		\'adi_scripts_manage_fail\' => \'Unable to manage scripts\',\n		\'adi_scripts_manage_selected\' => \'Manage selected scripts\',\n		\'adi_scripts_managed\' => \'Selected scripts now managed\',\n		\'adi_scripts_managed_scripts\' => \'Managed scripts\',\n		\'adi_scripts_unmanaged_scripts\' => \'Unmanaged scripts\',\n		\'adi_scripts_updated\' => \'Script settings updated\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_uninstall\' => \'Uninstall\',\n		\'adi_uninstall_fail\' => \'Unable to uninstall\',\n		\'adi_uninstalled\' => \'Uninstalled\',\n		\'adi_update_prefs\' => \'Update preferences\',\n		\'adi_update_directories\' => \'Update directories\',\n		\'adi_update_scripts\' => \'Update script settings\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_scripts_gtxt;\n\n	// Textpack\n	$adi_scripts_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_scripts\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_scripts_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	// plugin lifecycle\n	register_callback(\'adi_scripts_lifecycle\',\'plugin_lifecycle.adi_scripts\');\n\n	// adi_scripts Admin tab\n	add_privs(\"adi_scripts_admin\"); // defaults to priv \'1\' only\n	register_tab(\"extensions\",\"adi_scripts_admin\",gTxt(\'adi_scripts\')); // add new tab under \'Extensions\'\n	register_callback(\"adi_scripts_admin\",\"adi_scripts_admin\");\n\n	// style\n	if ($event == \"adi_scripts_admin\")\n		register_callback(\'adi_scripts_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_scripts_admin\'))\n		register_callback(\'adi_scripts_admin_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_scripts_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_scripts\',$adi_scripts_debug);\n	if ($adi_scripts_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_scripts\'); // defaults to priv \'1\' only\n		register_callback(\'adi_scripts_options\',\'plugin_prefs.adi_scripts\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_scripts_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_scripts_options\',\'adi_scripts options\');\n		register_callback(\'adi_scripts_options\',\'adi_scripts_options\');\n	}\n\n}\n\nfunction adi_scripts_style() {\n// some style for the admin page\n\n	echo\n		\'<style type=\"text/css\">\n			#adi_scripts h1, #adi_scripts h2 { text-align:center }\n			#adi_scripts p { text-align:center }\n			#adi_scripts form table + p { margin-top:0.5em }\n			#adi_scripts table.txp-list { width:auto; margin:0 auto }\n			#adi_scripts label { margin-right:0.5em }\n			.adi_scripts_admin_managed, .adi_scripts_admin_unmanaged, .adi_scripts_prefs { margin-bottom:3em }\n			.adi_scripts_prefs .adi_scripts_text_input { margin-right:0.5em }\n			.adi_scripts_prefs .adi_scripts_new_dir { width:20em }\n			.adi_scripts_button { margin-top:1em; text-align:center }\n			td.adi_scripts_enabled, td.adi_scripts_forget, td.adi_scripts_manage { text-align:center }\n			.adi_scripts_ie input { width:6em; margin-left:0.3em; margin-right:0.3em; font-family:courier,monospace }\n			.adi_scripts_ie span { color:#000; font-weight:bold; font-family:courier,monospace }\n			.adi_scripts_disabled { color:#ccc }\n			span.adi_scripts_disabled { color:#ccc; font-weight:normal }\n			.adi_scripts_sort input { width:2.5em }\n			.adi_scripts_tag_header_header { text-align:center; border-bottom:1px solid #ddd }\n			.adi_scripts_tag_header { text-align:center; font-weight:normal }\n			.adi_scripts_tags { text-align:center }\n			.adi_scripts_sections { width:16em }\n			.adi_scripts_sections label { margin-right:1em }\n			.adi_scripts_multi_checkboxes { margin:0.3em 0 0.5em; width:15em; height:5em; padding:0.2em; overflow:auto; border:1px solid #ccc }\n			.adi_scripts_multi_checkboxes label { display:block; float:none; width:auto }\n			.adi_scripts_drag_drop { display:none } /* revealed by jQuery later */\n		</style>\';\n}\n\nfunction adi_scripts_admin($event, $step) {\n// the adi_scripts admin tab\n	global $prefs,$adi_scripts_debug,$adi_scripts_plugin_status,$adi_scripts_prefs,$adi_scripts_txp450,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\'.br;\n\n	$message = \'\';\n	$installed = adi_scripts_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_scripts_upgrade();\n		if ($upgrade_required)\n			$message = array(gTxt(\'adi_upgrade_required\'),E_WARNING);\n		else {\n			/* stuff */\n		}\n	}\n	else\n		$message = array(gTxt(\'adi_not_installed\'),E_ERROR);\n\n	// admin steps\n	$new_tag=\'\';\n	if ($step == \'update\') {\n		$new_settings = ps(\'adi_scripts_settings\');\n		$result = adi_scripts_update_settings($new_settings);\n		$result ? $message = gTxt(\'adi_scripts_updated\') : $message = array(gTxt(\'adi_scripts_update_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		// process prefs\n		foreach ($adi_scripts_prefs as $pref_name => $pref_info) { // awkward to convert to std adi_admin_plugin_update_prefs() - two result conditions - return TRUE/FALSE/message?\n			// get/massage pref value\n			if (array_key_exists($pref_name,$_POST)) // pref present in POST\n				$value = $_POST[$pref_name];\n			else if ($pref_info[\'input\'] == \'yesnoradio\') // pref missing from POST, make it zero\n				$value = \'0\';\n// 			else if ($pref_info[\'blank\']) // pref missing from POST, pref allowed to be blank, make it blank\n// 				$value = \'\';\n			else\n				$value = $pref_info[\'value\']; // pref missing from POST, reset value\n			// special processing & validation\n			if ($pref_name == \'adi_scripts_dir\') {\n				// compare stored dir list with pref changes\n				if (adi_scripts_pref(\'adi_scripts_dir\'))\n					$old_list = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n				else\n					$old_list = array();\n				$new_list = $value;\n				$updated_list = array();\n				// see if existing dir needs to be removed in dir list\n				foreach ($old_list as $dir) {\n					if (array_search($dir,$new_list) === FALSE)\n						$updated_list[] = $dir;\n				}\n				// add a newcomer (subject to terms & conditions)\n				$input_ok = TRUE;\n				if ($new_list[\'new\']) {\n// 					if (strpbrk($new_list[\'new\'],\"\\\\/?%*:|\\\"<>\") === FALSE) { /* check directory is kosher (i.e. doesn\'t contain illegal characters) */\n					if (strpbrk($new_list[\'new\'],\"\\\\?%*:|\\\"<>\") === FALSE) { /* check directory doesn\'t contain illegal characters; as of v0.3 & TXP 4.6 now allowing \"/\" to cater for subdirectories */\n						if (preg_match(\"/^[.\\/]/\",$new_list[\'new\']) === 0) { /* check directory doesn\'t start with . or / ... STRIP INSTEAD? */\n							$updated_list[] = $new_list[\'new\'];\n						}\n						else\n							$input_ok = FALSE;\n					}\n					else\n						$input_ok = FALSE;\n				}\n				// lose les oubliettees from the database (NOT SURE WHY DID I DID THIS ANYWAY ... i.e. delete scripts when \"forget\" their directories)\n// 				foreach ($new_list as $dir) {\n// 					$where = \"directory=\'\".doSlash($dir).\"\'\";\n// 					$result = $result && safe_delete(\'adi_scripts\',$where,$adi_scripts_debug);\n// 				}\n				$value = implode(\',\',$updated_list);\n			}\n			if ($pref_name == \'adi_scripts_new_dir\') // internal use only, so don\'t think it should be blank (tab=\'\')\n				$value = adi_scripts_pref(\'adi_scripts_new_dir\');\n			// some values not allowed to be blank, reset to default\n			if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n					$value = $adi_scripts_prefs[$pref_name][\'value\'];\n			$result = $result && adi_scripts_pref($pref_name,$value);\n		}\n		// it\'s a result\n		if ($result)\n			$message = gTxt(\'preferences_saved\');\n		else\n			$message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		// DRS\n		if ($result && !$input_ok)\n			$message = array(gTxt(\'adi_invalid_input\'),E_ERROR);\n	}\n	else if ($step == \'new_scripts\') {\n		$new_scripts = ps(\'adi_scripts_new_list\');\n		$result = TRUE;\n		if ($new_scripts)\n			foreach ($new_scripts as $directory => $files)\n				foreach ($files as $file)\n					$result = $result && adi_scripts_manage_script($file,$directory);\n		$result ? $message = gTxt(\'adi_scripts_managed\') : $message = array(gTxt(\'adi_scripts_manage_fail\'),E_ERROR);\n	}\n	else if ($step == \'new_tag\') {\n		$new_tag = sanitizeForURL(assert_string(ps(\'adi_scripts_new_tag\')));\n	}\n\n	$installed = adi_scripts_installed();\n\n	$adi_scripts_problem_count = 0; // may morph into a list of dodgy dirs/files one day\n\n	// see if scripts directories are ok & gather list of script files from each\n	if ($installed) {\n		if (adi_scripts_pref(\'adi_scripts_dir\'))\n			$directories = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n		else\n			$directories = array();\n		if ($adi_scripts_debug) {\n			echo \"SCRIPT DIRECTORIES:\".br;\n			dmp($directories);\n		}\n		$file_list = array();\n		foreach ($directories as $dir) {\n			if ($adi_scripts_debug) echo \"CHECKING DIRECTORY $dir\".br;\n			$dir_message = adi_scripts_check_dir($dir);\n			if (empty($dir_message))\n				$file_list[$dir] = adi_scripts_get_files($dir);\n			else\n				$adi_scripts_problem_count += 1;\n		}\n		if ($adi_scripts_debug) {\n			echo br.\"COMBINED FILE LIST\".br;\n			dmp($file_list);\n			if ($new_tag != \'\')\n				echo br.\"NEW TAG = $new_tag\".br.br;\n		}\n	}\n\n	// admin tab markup\n	$out = \'\';\n	if ($installed && !$upgrade_required) {\n		$out .= \'<div id=\"adi_scripts\">\';\n		// managed scripts\n		$out .= tag(\n			adi_scripts_managed_list($new_tag)\n			,\'div\'\n			,\' class=\"adi_scripts_admin_managed\"\'\n		);\n		// unmanaged scripts\n		$out .= tag(\n			adi_scripts_unmanaged_list($file_list)\n			,\'div\'\n			,\' class=\"adi_scripts_admin_unmanaged\"\'\n		);\n		// directories\n		$out .= tag(\n			adi_scripts_directories($directories)\n			,\'div\'\n			,\' class=\"adi_scripts_prefs\"\'\n		);\n		// new tag\n		$out .= tag(\n			adi_scripts_new_tag()\n			,\'div\'\n			,\' class=\"adi_scripts_prefs\"\'\n		);\n		$out .= \'</div>\';\n	}\n\n	if (!$message && $adi_scripts_problem_count)\n		$message = array(gTxt(\'adi_scripts_general_warning\'),E_WARNING);\n\n	// generate page\n	pagetop(gTxt(\'adi_scripts\').\' \'.gTxt(\'admin\'),$message);\n\n	echo $out;\n\n	if ($adi_scripts_debug) {\n		if ($installed) {\n			echo \'<b>Prefs:</b><br/>\';\n			foreach ($adi_scripts_prefs as $name => $this_pref)\n				echo $name.\' = \'.adi_scripts_pref($name).br;\n			echo \"adi_scripts_problem_count = $adi_scripts_problem_count\";\n			echo \'<br/>\';\n		}\n	}\n}\n\nfunction adi_scripts_admin_script() {\n// jQuery magic for admin tab\n	global $prefs,$adi_scripts_txp460;\n\n	$ui_script = \'\';\n	if (!$adi_scripts_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n	$(function(){\n		// section list peekaboo\n		$(\'td.adi_scripts_sections\').each(function(){\n			var this_td = $(this);\n			// hide sections if \"All\" ticked\n			$(\'input[name*=\"all_sections\"][value=\"1\"]:checked\',this).each(function(){\n				$(\'div\',this_td).hide();\n			});\n			// an \"All\" has been deselected\n			$(\'input[name*=\"all_sections\"]:radio:eq(1)\',this).change(function(){\n				$(\'div\',this_td).show();\n			});\n			// an \"All\" has been selected\n			$(\'input[name*=\"all_sections\"]:radio:eq(0)\',this).change(function(){\n				$(\'div\',this_td).hide();\n			});\n		});\n		// disabled script ghosting\n		$(\'td.adi_scripts_enabled\').each(function(){\n 			var this_td = $(this);\n			// dim scripts if not enabled\n			$(\'input:checkbox:not(:checked)\',this).each(function(){\n				$(this_td).parent().addClass(\"adi_scripts_disabled\");\n			});\n			// script enabled/disabled\n			$(\'input\',this).change(function(){\n				if (this.checked)\n					$(this_td).parent().removeClass(\"adi_scripts_disabled\");\n				else\n 					$(this_td).parent().addClass(\"adi_scripts_disabled\");\n			});\n		});\n	});\n</script>\n<!-- drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'.adi_scripts_admin_managed table tbody tr\').css(\'cursor\',\'pointer\'); // make like a pointer\n			$(\'.adi_scripts_admin_managed table .adi_scripts_drag_drop\').show(); // reveal drag & drop icon\n			$(\'.adi_scripts_admin_managed table .adi_scripts_sort\').hide(); // lose sort value column\n			$(\".adi_scripts_admin_managed table tbody\").sortable();\n			$(\".adi_scripts_admin_managed table tbody\").bind(\"sortstop\",function(event, ui) {\n				$(\'td.adi_scripts_sort input\').each(function(i) { // renumber sort values\n					var this_td = $(this);\n					this_td.val(i+10);\n				});\n			});\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_scripts_options($event,$step) {\n// display adi_scripts options: install/uninstall/upgrade\n	global $adi_scripts_debug,$adi_scripts_url,$adi_scripts_plugin_status;\n\n	$message = \'\';\n	$installed = adi_scripts_installed();\n\n	if ($installed) {\n		$upgrade_required = adi_scripts_upgrade();\n		if ($upgrade_required) { // upgrade\n			$res = adi_scripts_upgrade(TRUE);\n			if ($res)\n				$message = gTxt(\'adi_upgraded\');\n			else\n				$message = array(gTxt(\'adi_upgrade_fail\'),E_ERROR);\n		}\n		// $stepping out\n		if ($step == \"uninstall\") { // uninstall adi_scripts\n			$res = adi_scripts_uninstall();\n			if ($res)\n	    		$message = gTxt(\'adi_uninstalled\');\n			else\n	    		$message = array(gTxt(\'adi_uninstall_fail\'),E_ERROR);\n		}\n		else if ($step == \'textpack\') {\n			$adi_textpack = file_get_contents($adi_scripts_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n\n		else if ($step == \'update_prefs\') {\n			$result = TRUE;\n			// run through all prefs\n			foreach ($adi_scripts_prefs as $name => $data) {\n				if (array_key_exists($name,$_POST))\n					$value = $_POST[$name];\n				else if ($data[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $data[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($name == \'adi_scripts_admin_privs\') {\n						// IF NO ADMIN TAB THEN LOSE THIS (I.E. ALLOWED TO SWITCH SELF & PUBLISHER OFF)\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				$non_blanks = array();\n				foreach ($non_blanks as $non_blank)\n					if (($name == $non_blank) && (trim($value) == \'\'))\n						$value = $adi_scripts_prefs[$non_blank][\'value\'];\n				$result &= adi_scripts_pref($name,$value);\n			}\n			$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n		}\n\n	}\n	else { // not installed\n		if ($step == \"install\") { // install adi_scripts\n			$res = adi_scripts_install();\n			if ($res)\n				$message = gTxt(\'adi_installed\');\n			else\n				$message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n		}\n		else\n			$message = array(gTxt(\'adi_not_installed\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_scripts - \'.gTxt(\'plugin_prefs\'),$message);\n\n	$install_button =\n		form(\n			fInput(\"submit\", \"adi_scripts_options_submit\", gTxt(\'install\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"install\")\n			,\'\',\'\',\'post\',\'adi_scripts_nstall_button\'\n		);\n	$uninstall_button =\n		form(\n			fInput(\"submit\", \"do_something\", gTxt(\'adi_uninstall\'), \"publish\",\"\",\'return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\')\n			.eInput($event).sInput(\"uninstall\")\n			,\'\',\'\',\'post\',\'adi_scripts_nstall_button adi_scripts_uninstall_button\'\n		);\n\n	if ($adi_scripts_plugin_status) // proper plugin install, so lifecycle takes care of install/uninstall\n		$install_button = $uninstall_button = \'\';\n\n	$installed = adi_scripts_installed();\n\n	// options\n	echo tag(\n		tag(\'adi_scripts \'.gTxt(\'plugin_prefs\'),\'h1\')\n		.( $installed ?\n			// textpack links\n			graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_scripts_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_scripts_url[\'textpack_feedback\']))\n	    	.$uninstall_button\n			: $install_button\n		)\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n}\n\nfunction adi_scripts_install() {\n// install adi_scripts in database\n	global $adi_scripts_debug,$adi_scripts_prefs,$adi_scripts_txp460;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$res = safe_query(\n		\"CREATE TABLE IF NOT EXISTS \"\n		.safe_pfx(\'adi_scripts\')\n		.\" (\n			`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n			`filename` VARCHAR(255) NOT NULL,\n			`directory` VARCHAR(255) NOT NULL,\n			`enabled` TINYINT(1) DEFAULT 0 NOT NULL,\n			`tags` VARCHAR(255) NOT NULL,\n			`sections` VARCHAR(255) NOT NULL,\n			`ie` VARCHAR(255) NOT NULL,\n			`sort` TINYINT(3) UNSIGNED DEFAULT 0 NOT NULL\n		);\"\n		,$adi_scripts_debug);\n	// fix up default pref values for 4.6\n	if ($adi_scripts_txp460) {\n		$res = $res && adi_scripts_pref(\'adi_scripts_dir\',adi_scripts_pref(\'adi_scripts_new_dir\')); // create dirs pref with 4.6 dirs\n		$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',\'\'); // flush out new dirs pref (to prevent triggering upgrade again)\n	}\n\n 	return $res;\n}\n\nfunction adi_scripts_uninstall() {\n// uninstall adi_scripts\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	// delete table\n	$res = safe_query(\"DROP TABLE \".safe_pfx(\'adi_scripts\').\";\",$adi_scripts_debug);\n	// delete preferences\n	$res = $res && safe_delete(\'txp_prefs\',\"name LIKE \'adi_scripts_%\'\",$adi_scripts_debug);\n\n	return $res;\n}\n\nfunction adi_scripts_lifecycle($event,$step) {\n// a matter of life & death\n// $event:	\"plugin_lifecycle.adi_plugin\"\n// $step:	\"installed\", \"enabled\", disabled\", \"deleted\"\n// TXP 4.5: reinstall/upgrade only triggers \"installed\" event (now have to manually detect whether upgrade required)\n	global $adi_scripts_debug,$adi_scripts_txp450;\n\n	$result = \'?\';\n	// set upgrade flag if upgrading/reinstalling in TXP 4.5+\n	$upgrade = (($step == \"installed\") && $adi_scripts_txp450 && adi_scripts_installed());\n	if ($step == \'enabled\')\n		$result = $upgrade = adi_scripts_install();\n	else if ($step == \'deleted\')\n		$result = adi_scripts_uninstall();\n	if ($upgrade)\n		$result = $result && adi_scripts_upgrade(TRUE);\n	if ($adi_scripts_debug)\n		echo \"Event=$event Step=$step Result=$result Upgrade=$upgrade\";\n}\n\nfunction adi_scripts_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_scripts_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_scripts_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_scripts_prefs))\n			$html = $adi_scripts_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_scripts_admin\',2,$html,0,$private);\n		$prefs[$name] = get_pref($name,$adi_scripts_prefs[$name][\'value\'],TRUE);\n		return $res;\n	}\n}\n\nfunction adi_scripts_installed($table=\'adi_scripts\') {\n// see if supplied table is present\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$found = FALSE;\n	$rs = safe_query(\"SHOW TABLES LIKE \'\".safe_pfx($table).\"\'\",$adi_scripts_debug);\n	$a = nextRow($rs);\n	if ($a)\n		$found = TRUE;\n\n	return $found;\n}\n\nfunction adi_scripts_upgrade($do_upgrade=FALSE) {\n// internal upgrade required?\n	global $adi_scripts_debug,$adi_scripts_txp450,$adi_scripts_txp460;\n\n	$upgrade_required = FALSE;\n	if (adi_scripts_installed()) { // find out what needs upgrading\n		// version 0.2 - tagging\n		$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx(\'adi_scripts\').\" LIKE \'tags\'\",$adi_scripts_debug); // find out if column exists\n		$a = nextRow($rs);\n		$v0_2 = empty($a);\n		// version 0.3 flags on different TXP versions\n		$v0_3_45 = !get_pref(\'adi_scripts_new_dir\',\'\',TRUE) && (version_compare(txp_version,\'4.5\',\'>=\')) && (version_compare(txp_version,\'4.5.7\',\'<=\'));\n		$v0_3_46 = adi_scripts_pref(\'adi_scripts_new_dir\') && $adi_scripts_txp460;\n		$upgrade_required = $upgrade_required || $v0_2 || $v0_3_45 || $v0_3_46;\n		if ($do_upgrade) {\n			if ($upgrade_required) {\n				$res = TRUE;\n				if ($v0_2)\n					$res = $res && safe_query(\"ALTER TABLE \".safe_pfx(\"adi_scripts\").\" ADD `tags` VARCHAR(255) NOT NULL\",$adi_scripts_debug); // add new column\n				if ($v0_3_45) // set up \"new dirs\" flag for later (when TXP is upgraded to 4.6)\n					$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',adi_scripts_pref(\'adi_scripts_new_dir\')); // create new dirs pref\n				if ($v0_3_46) { // new \"default dirs\" to add? - i.e. TXP 4.6 jQuery/jQuery-UI paths\n					// shift new \"default dirs\" to current directories list\n					$current = explode(\',\',adi_scripts_pref(\'adi_scripts_dir\'));\n					$new = explode(\',\',adi_scripts_pref(\'adi_scripts_new_dir\'));\n					$combined = array_filter(array_unique(array_merge($current,$new))); // combine current with new, lose duplicates & blanks\n					$res = $res && adi_scripts_pref(\'adi_scripts_dir\',implode(\',\',$combined)); // update dirs pref\n					$res = $res && adi_scripts_pref(\'adi_scripts_new_dir\',\'\'); // flush out new dirs pref (to prevent triggering upgrade again)\n					// modify standard jQuery script entry: \"textpattern/jquery.js\" -> \"textpattern/vendors/jquery/jquery/jquery.js\"\n					$set = \"directory=\'textpattern/vendors/jquery/jquery\'\";\n					$where = \"filename=\'jquery.js\' AND directory=\'textpattern\'\";\n					$res = $res && safe_update(\'adi_scripts\',$set,$where,$adi_scripts_debug);\n\n				}\n				return $res;\n			}\n			else\n				return FALSE;\n		}\n		else\n			return $upgrade_required;\n	}\n	else\n		return FALSE;\n}\n\nfunction adi_scripts_managed_list($new_tag) {\n// managed scripts\n	global $adi_scripts_debug,$prefs,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$out = \'\';\n	$script_settings = \'\';\n	$unreadable_count = 0;\n	$new_sort = 0;\n\n	// get stored scripts info\n	$rows = safe_rows(\'*\',\'adi_scripts\',\'1=1 ORDER BY sort,id\', $adi_scripts_debug);\n	$out .= tag(gTxt(\'adi_scripts\'),\'h1\');\n	if ($adi_scripts_debug)\n		dmp($rows);\n\n	// tags\n	$tag_list = adi_scripts_tag_list($new_tag);\n\n	if ($rows) { // managed scripts found in the database\n		foreach ($rows as $row) {\n			$new_sort += 10;\n			extract($row);\n			$file_path = $prefs[\'path_to_site\'].\'/\'.$directory.\'/\'.$filename;\n			if (file_exists($file_path) && is_readable($file_path))\n				$asterisk = \'\';\n			else {\n				$asterisk = \'*\';\n				$unreadable_count += 1;\n			}\n			if ($ie)\n				$class = \'\';\n			else\n				$class = \' class=\"adi_scripts_disabled\"\';\n			// tags\n			$tag_settings = \'\';\n			foreach ($tag_list as $tag)\n				$tag_settings .= tda(checkbox2(\"adi_scripts_settings[$id][tags][$tag]\",(array_search($tag,explode(\',\',$tags)) !== FALSE)),\' class=\"adi_scripts_tags\"\');\n			// scripts settings\n			$script_settings .=\n				tr(\n					tda(tag(\'&#x2195;\',\'span\',\' class=\"adi_scripts_drag_drop\"\'))\n					// script filename\n					.tda(\n						tag(\n							$directory\n							.\'/\'\n							.$filename\n							.$asterisk\n							,\'span\'\n							,($asterisk ? \' class=\"warning\"\' : \'\')\n						)\n						.hInput(\"adi_scripts_settings[$id][filename]\",$filename)\n						.hInput(\"adi_scripts_settings[$id][directory]\",$directory)\n\n\n					)\n					// enabled?\n					.tda(checkbox2(\"adi_scripts_settings[$id][enabled]\",$enabled),\' class=\"adi_scripts_enabled\"\')\n					// tags\n					.$tag_settings\n					// sections\n					.tda(\n						tag(gTxt(\'all\'),\'label\')\n						.radio(\"adi_scripts_settings[$id][all_sections]\",\'1\',($sections == \'\'))\n						.sp.sp\n						.tag(gTxt(\'selected\'),\'label\')\n						.radio(\"adi_scripts_settings[$id][all_sections]\",\'0\',($sections != \'\'))\n						.tag(adi_scripts_section_checkboxes(\"adi_scripts_settings[$id][sections]\",$sections),\'div\',\' class=\"adi_scripts_multi_checkboxes\"\')\n						,\' class=\"adi_scripts_sections\"\'\n					)\n					// IE conditionals\n					.tda(\n						tag(htmlentities(\'<!--[if\'),\'span\',$class).finput(\'text\',\"adi_scripts_settings[$id][ie]\",$ie).tag(htmlentities(\']>\'),\'span\',$class)\n						,\' class=\"adi_scripts_ie\"\'\n					)\n					// (sort)\n					.tda(finput(\"text\",\"adi_scripts_settings[$id][sort]\",$new_sort),\' class=\"adi_scripts_sort\"\')\n					// forget?\n					.tda(checkbox2(\"adi_scripts_settings[$id][forget]\",0),\' class=\"adi_scripts_forget\"\')\n				);\n		}\n\n		// tag headers\n		$tag_header_header = \'\';\n		if ($tag_list) {\n			if (count($tag_list) == 1)\n				$header_text = gTxt(\'tag\');\n			else\n				$header_text = gTxt(\'tags\');\n			$tag_header_header = tag(hcell(sp,\'\',\' colspan=\"3\"\').hcell($header_text,\'\',\' colspan=\"\'.count($tag_list).\'\" class=\"adi_scripts_tag_header_header\"\').hcell(sp,\'\',\' colspan=\"4\"\'),\'tr\');\n		}\n		$tag_headers = \'\';\n		foreach ($tag_list as $tag)\n			$tag_headers .= hcell($tag,\'\',\' class=\"adi_scripts_tag_header\"\');\n\n		// generate table\n		$out .= form(\n			startTable(\'\',\'\',\'txp-list\')\n			.tag(\n				$tag_header_header\n				.tag(\n					hcell(sp) // filler for drag/drop icon\n					.hcell(gTxt(\'adi_script\'))\n					.hcell(gTxt(\'adi_enabled\'))\n					.$tag_headers\n					.hcell(gTxt(\'sections\'))\n					.hcell(gTxt(\'adi_ie_conditionals\'))\n					.hcell(gTxt(\'sort\'),\'\',\' class=\"adi_scripts_sort\"\')\n					.hcell(gTxt(\'forget\'))\n					,\'tr\'\n					)\n				,\'thead\'\n			)\n			.tag($script_settings,\'tbody\')\n			.endTable()\n			.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n			.tag(\n				fInput(\"submit\", \"do_something\",gTxt(\'save\'),\'publish\').\n				eInput(\"adi_scripts_admin\").sInput(\"update\")\n				,\'div\'\n				,\' class=\"adi_scripts_button\"\'\n			)\n		);\n	}\n	else\n		$out .= graf(tag(gTxt(\'adi_none_found\'),\'em\'));\n\n	if ($adi_scripts_debug)\n		echo \"UNREADABLE COUNT = $unreadable_count\".br;\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_unmanaged_list($file_list) {\n// unmanaged scripts\n	global $prefs,$adi_scripts_debug,$adi_scripts_problem_count;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n\n	$out = \'\';\n	$checkboxes = \'\';\n	$unreadable_count = 0;\n	foreach ($file_list as $dir => $files) {\n		$scripts_path = $prefs[\'path_to_site\'].\'/\'.$dir;\n		foreach ($files as $file) {\n			$row = safe_row(\'*\',\'adi_scripts\',\"filename=\'\".doSlash($file).\"\' AND directory=\'\".doSlash($dir).\"\'\",$adi_scripts_debug);\n			if (!$row) { // not being managed\n				$file_path = $scripts_path.\'/\'.$file;\n				if (is_readable($file_path))\n					$asterisk = \'\';\n				else {\n					$asterisk = \'*\';\n					$unreadable_count += 1;\n				}\n				$checkboxes .=\n					tr(\n						tda(\n							tag(\n								$dir.\'/\'.$file.$asterisk\n								,\'label\'\n								,($asterisk ? \' class=\"warning\"\' : \'\')\n							)\n						)\n						.tda(\n							checkbox(\"adi_scripts_new_list[$dir][]\",$file,FALSE)\n							,\' class=\"adi_scripts_manage\"\'\n						)\n					);\n			}\n		}\n	}\n	$out .= tag(gTxt(\'adi_scripts_unmanaged_scripts\'),\'h2\');\n	if ($checkboxes) { // unmanaged scripts found in scripts directory\n		$out .= form(\n				startTable(\'\',\'\',\'txp-list\')\n				.tag(\n					tag(\n						hcell(gTxt(\'adi_script\'))\n						.hcell(gTxt(\'manage\'))\n						,\'tr\'\n					)\n					,\'thead\'\n				)\n				.tag($checkboxes,\'tbody\')\n				.endTable()\n				.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n				.tag(\n					fInput(\"submit\", \"do_something\", gTxt(\'adi_scripts_manage_selected\'), \"smallerbox\")\n					.eInput(\"adi_scripts_admin\").sInput(\"new_scripts\")\n					,\'div\'\n					,\' class=\"adi_scripts_button\"\'\n				)\n			);\n	}\n	else\n		$out .= graf(tag(gTxt(\'adi_none_found\'),\'em\'));\n\n	if ($adi_scripts_debug)\n		echo \"UNREADABLE COUNT = $unreadable_count\".br;\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_directories($directories) {\n// directories of interest\n	global $prefs,$adi_scripts_problem_count;\n\n	$out = \'\';\n	$markup = \'\';\n	$unreadable_count = 0;\n\n	// directories of interest\n	foreach ($directories as $dir) {\n		$scripts_path = $prefs[\'path_to_site\'].\'/\'.$dir;\n		if (is_readable($scripts_path))\n			$asterisk = \'\';\n		else {\n			$asterisk = \'*\';\n			$unreadable_count += 1;\n		}\n		$markup .=\n			tr(\n				tda(n) // spacer for \'+\' column\n				.tda(\n					tag(\n						$dir.$asterisk\n						,\'label\'\n						,($asterisk ? \' class=\"warning\"\' : \'\')\n					)\n				)\n				.tda(\n					checkbox(\"adi_scripts_dir[]\",$dir,FALSE)\n					,\' class=\"adi_scripts_manage\"\'\n				)\n			);\n	}\n	$markup .=\n		tr(\n			tda(\n				tag(\'+\',\'b\')\n			)\n			.tda(\n				finput(\"text\",\'adi_scripts_dir[new]\',\'\',\'adi_scripts_new_dir\')\n			)\n			.tda(n)\n		);\n\n	$out = tag(gTxt(\'adi_directories\'),\'h2\')\n	.form(\n		startTable(\'\',\'\',\'txp-list\')\n		.tag(\n			tag(\n				hcell(n) // spacer for \'+\' column\n				.hcell(gTxt(\'adi_directory\'))\n				.hcell(gTxt(\'forget\'))\n				,\'tr\'\n			)\n			,\'thead\'\n		)\n		.tag($markup,\'tbody\')\n		.endTable()\n		.($unreadable_count ? graf(tag(\'* \'.gTxt(\'adi_not_readable\'),\'em\',\' class=\"warning\"\')) : \'\')\n		.tag(\n			fInput(\"submit\", \"do_something\", gTxt(\'adi_update_directories\'), \"smallerbox\")\n			.eInput(\"adi_scripts_admin\")\n			.sInput(\"update_prefs\")\n			,\'div\'\n			,\' class=\"adi_scripts_button\"\'\n		)\n	);\n\n	$adi_scripts_problem_count += $unreadable_count;\n\n	return $out;\n}\n\nfunction adi_scripts_new_tag() {\n// introduce new tag in the mix\n\n	$out =\n		tag(gTxt(\'tags\'),\'h2\')\n		.form(\n			graf(\n				finput(\"text\",\'adi_scripts_new_tag\',\'\',\'adi_scripts_text_input\')\n				.fInput(\"submit\", \"do_something\", gTxt(\'adi_add_tag\'), \"smallerbox\")\n				.eInput(\"adi_scripts_admin\").sInput(\"new_tag\")\n			)\n		);\n	return $out;\n}\n\nfunction adi_scripts_manage_script($file,$directory) {\n// bring a script into the fold\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n	$set = \"filename=\'\".doSlash($file).\"\', directory=\'\".doSlash($directory).\"\', enabled=1, tags=\'\', sections=\'\', ie=\'\', sort=\'255\'\";\n	return safe_insert(\'adi_scripts\',$set,$adi_scripts_debug);\n}\n\nfunction adi_scripts_update_settings($settings) {\n// update script settings in the database\n	global $adi_scripts_debug;\n\n	if ($adi_scripts_debug) echo __FUNCTION__.\':\';\n	$res = TRUE;\n	foreach ($settings as $setting) {\n		extract($setting);\n		if (isset($setting[\'forget\'])) {\n			$where = \"filename=\'\".doSlash($filename).\"\' AND directory=\'\".doSlash($directory).\"\'\";\n			$res = $res && safe_delete(\'adi_scripts\',$where,$adi_scripts_debug);\n		}\n		else {\n			if (isset($setting[\'enabled\']))\n				$enabled = $setting[\'enabled\'];\n			else\n				$enabled = 0;\n			if ($sort == \'\') // set blank to zero\n				$sort = 0;\n			if (array_key_exists(\'tags\',$setting))\n				$tag_list = doSlash(strtolower(implode(\',\',array_keys($setting[\'tags\']))));\n			else\n				$tag_list = \'\';\n			if (array_key_exists(\'sections\',$setting))\n				$section_list = doSlash(implode(\',\',$setting[\'sections\']));\n			else\n				$section_list = \'\';\n			if ($setting[\'all_sections\']) // radio button\n				$section_list = \'\';\n			$set = \"enabled=$enabled, tags=\'\".$tag_list.\"\', sections=\'\".$section_list.\"\', ie=\'\".doSlash($ie).\"\', sort=\'\".doSlash($sort).\"\'\";\n			$where = \"filename=\'\".doSlash($filename).\"\' AND directory=\'\".doSlash($directory).\"\'\";\n			$res = $res && safe_update(\'adi_scripts\',$set,$where,$adi_scripts_debug);\n		}\n	}\n\n	update_lastmod();\n	return $res;\n}\n\nfunction adi_scripts_check_dir($directory) {\n// nobody expects the Spanish Inquisition\n	global $prefs,$adi_scripts_debug;\n\n	$scripts_path = $prefs[\'path_to_site\'].\'/\'.$directory;\n	$message = \'\';\n	// check scripts directory\n	if (file_exists($scripts_path)) {\n		if ($adi_scripts_debug) echo \"$scripts_path found\".br;\n		if (is_dir($scripts_path)) {\n			if ($adi_scripts_debug) echo \"$scripts_path is a directory\".br;\n			if (is_readable($scripts_path)) {\n				if ($adi_scripts_debug) echo \"$scripts_path is readable\".br;\n			}\n			else {\n				if ($adi_scripts_debug) echo \"$scripts_path is not readable\".br;\n				$message = array(gTxt(\'adi_scripts_dir_not_readable\'),E_ERROR);\n			}\n		}\n		else {\n			if ($adi_scripts_debug) echo \"$scripts_path not a directory\".br;\n			$message = array(gTxt(\'adi_scripts_dir_not_dir\'),E_ERROR);\n		}\n	}\n	else {\n		if ($adi_scripts_debug) echo \"$scripts_path not found\".br;\n		$message = array(gTxt(\'adi_scripts_dir_not_found\'),E_ERROR);\n	}\n\n	return $message;\n}\n\nfunction adi_scripts_get_files($directory) {\n// generate list of files in given directory\n	global $prefs,$adi_scripts_debug;\n\n	$file_list = array();\n	$scripts_path = $prefs[\'path_to_site\'].\'/\'.$directory;\n	if ($adi_scripts_debug)\n		echo br.\"CONTENTS OF $directory\".br;\n\n	// get filenames from directory & decide whether to ignore or not\n	if ($handle = opendir($scripts_path)) {\n		while (false !== ($file = readdir($handle))) {\n			$ok = FALSE;\n			$diags = $file. \' (\';\n			if (is_dir($scripts_path.\'/\'.$file)) // directory\n				$diags .=  \'DIR\';\n			else if (strpos($file,\'.\') === 0) // hidden file\n				$diags .=  \'HIDDEN FILE\';\n			else if (is_file($scripts_path.\'/\'.$file)) { // file\n				$diags .=  \'FILE\';\n				$path_parts = pathinfo($file);\n				if (isset($path_parts[\'extension\'])) {\n					if ($path_parts[\'extension\'] != \'js\')\n						$diags .=  \' - WRONG EXTENSION\';\n					else\n						$ok = TRUE;\n				}\n				else\n					$diags .=  \' - NO EXTENSION\';\n			}\n			if (!$ok)\n				$diags .=  \' - IGNORED\';\n			if ($ok && !is_readable($scripts_path.\'/\'.$file)) // script no readable\n				$diags .=  \' - NOT READABLE\';\n			if ($adi_scripts_debug)\n				echo $diags.\')\'.br;\n			if ($ok)\n				$file_list[] = $file;\n		}\n		closedir($handle);\n	}\n\n	if ($adi_scripts_debug) {\n		echo br.\"FILTERED FILE LIST FROM $directory:\".br;\n		dmp($file_list);\n	}\n\n	return $file_list;\n}\n\nfunction adi_scripts_section_checkboxes($field_name,$value) {\n// generate section checkboxes\n\n	$section_list = explode(\',\',$value);\n	$out = \'\';\n	$rs = safe_column(\'name\', \'txp_section\', \'1=1\');\n	if ($rs) {\n		foreach ($rs as $section)\n			$out .= tag(checkbox($field_name.\'[]\',$section,(array_search($section,$section_list) !== FALSE ? \'1\' : \'0\')).$section,\'label\');\n		return $out;\n	}\n\n	return FALSE;\n}\n\nfunction adi_scripts_tag_list($new_tag=\'\') {\n// create list of tags from DB\n	global $adi_scripts_debug;\n\n	// get all tags in DB\n	$rows = safe_rows(\'tags\',\'adi_scripts\',\'1=1\');\n	$tag_list = array();\n	foreach ($rows as $row) {\n		$r = explode(\',\',$row[\'tags\']);\n		$tag_list = array_merge($tag_list,$r);\n	}\n	// new tag?\n	if ($new_tag != \'\')\n		$tag_list[] = $new_tag;\n	// create unique list\n	$tag_list = array_unique($tag_list);\n	// sort\n	sort($tag_list);\n	// find empty \"tag\" (i.e. untagged) & lose it\n	$blank_key = array_search(\'\',$tag_list);\n	if ($blank_key !== FALSE)\n		unset($tag_list[$blank_key]);\n\n	if ($adi_scripts_debug) {\n		echo \'TAG LIST:\';\n		dmp($tag_list);\n	}\n\n	return $tag_list;\n}\n\nfunction adi_scripts($atts) {\n// <txp:adi_scripts /> tag\n	global $prefs,$s;\n\n	extract(lAtts(array(\n		\'tags\'	=> \'\',		// restrict to specified tagged scripts\n		\'debug\' => 0		// to also output <script> code\n	), $atts));\n\n	$out = array();\n\n	// HTML5 adjustment\n	$type = \' type=\"text/javascript\"\';\n	if (isset($prefs[\'doctype\']))\n		if ($prefs[\'doctype\'] == \'html5\')\n			$type = \'\';\n\n	// tagged?\n	if ($tags == \'\')\n		$tags_q = \'\';\n	else { // (stolen from keywords in publish.php)\n		$tag_list = doSlash(do_list($tags));\n		foreach ($tag_list as $tag) {\n			$tagparts[] = \"FIND_IN_SET(\'\".$tag.\"\',tags)\";\n		}\n		$tags_q = \" (\" . join(\' OR \',$tagparts) . \") AND \";\n	}\n\n	// get script filenames from database which are \"enabled\" ... and, if sections non-blank, match current section\n	$rows = safe_rows(\'*\',\'adi_scripts\',\"enabled=1 AND \".$tags_q.\"IF(sections=\'\',1,FIND_IN_SET(\'\".doSlash($s).\"\',sections)) ORDER BY sort\");\n\n	if ($rows) { // output <script>...</script> markup\n		foreach ($rows as $row) {\n			extract($row);\n			$ie_start = $ie_end = \'\';\n			if ($ie) {\n				$ie_start = \"<!--[if $ie]>\";\n				$ie_end = \'<![endif]-->\';\n			}\n			$out[] = $ie_start.\'<script\'.$type.\' src=\"http://\'.$prefs[\'siteurl\'].\'/\'.$directory.\'/\'.$filename.\'\"></script>\'.$ie_end;\n		}\n	}\n\n	if ($debug) // output script markup as plain text\n		foreach ($out as $line)\n			$out[] = htmlentities($line).br;\n\n	return implode(\"\\n\",$out);\n}\n', 'f9608718a87330eb1beeec9f3aa13105', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_search_db', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.3', 'Search the TXP database', '<h1><strong>adi_search_db</strong> &#8211; Search the <span class=\"caps\">TXP</span> database</h1>\n\n<p>This plugin allows you to search the <span class=\"caps\">TXP</span> database from within the Admin interface.</p>\n\n<h2><strong>Installation</strong></h2>\n\n<p>Install the plugin in the usual way and a new item, <em>Search DB</em>, will be available under the Extensions tab.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Simply enter the string you&#8217;re looking for and <strong>adi_search_db</strong> will return all occurrences found in the database.</p>\n\n<h2><strong>All occurrences?</strong></h2>\n\n<p>Yes, if they occur in the following Textpattern database fields (as well as some specific plugin fields):</p>\n\n<p><strong>Articles</strong></p>\n\n<ul>\n	<li>Title</li>\n	<li>Body</li>\n	<li>Excerpt</li>\n	<li>Custom fields</li>\n	<li>Article image</li>\n	<li>Keywords</li>\n	<li>Override form</li>\n	<li><span class=\"caps\">URL</span>-only title</li>\n	<li>Invitation</li>\n</ul>\n\n<p><strong>Categories</strong></p>\n\n<ul>\n	<li>Title</li>\n</ul>\n\n<p><strong>Style</strong></p>\n\n<ul>\n	<li>Stylesheet</li>\n</ul>\n\n<p><strong>Comments</strong></p>\n\n<ul>\n	<li>Name</li>\n	<li>Email</li>\n	<li>Website</li>\n	<li>Message</li>\n</ul>\n\n<p><strong>Files</strong></p>\n\n<ul>\n	<li>Filename</li>\n	<li>Title</li>\n	<li>Description</li>\n</ul>\n\n<p><strong>Forms</strong></p>\n\n<ul>\n	<li>Form</li>\n</ul>\n\n<p><strong>Images</strong></p>\n\n<ul>\n	<li>Image name</li>\n	<li>Alternate text</li>\n	<li>Caption</li>\n</ul>\n\n<p><strong>Links</strong></p>\n\n<ul>\n	<li>Title</li>\n	<li><span class=\"caps\">URL</span></li>\n	<li>Description</li>\n</ul>\n\n<p><strong>Pages</strong></p>\n\n<ul>\n	<li>Page template</li>\n</ul>\n\n<p><strong>Preferences</strong></p>\n\n<ul>\n	<li>Name</li>\n	<li>Value</li>\n</ul>\n\n<p><strong>Sections</strong></p>\n\n<ul>\n	<li>Title</li>\n</ul>\n\n<p><strong>Users</strong></p>\n\n<ul>\n	<li>Name</li>\n	<li>email address</li>\n</ul>\n\n<p><strong>adi_menu</strong></p>\n\n<ul>\n	<li>Alternative title</li>\n	<li>Clone title</li>\n	<li>Redirects</li>\n</ul>\n\n<p><strong>adi_scripts</strong></p>\n\n<ul>\n	<li>Directory</li>\n	<li>Filename</li>\n</ul>\n\n<p>If you would like <strong>adi_search_db</strong> to poke around in any other plugin tables, let me know in the support forum and I&#8217;ll see what I can do.</p>\n\n<h2><strong>Search results</strong></h2>\n\n<p>The results of your search are listed with links to the appropriate edit pages.</p>\n\n<p>Results are automatically filtered according to user privileges &#8211; for instance, if you don&#8217;t have access to Forms then you won&#8217;t see any results from there.</p>\n\n<p>Results can also be filtered further in the Filter list.</p>\n\n<h2><strong>Privileges</strong></h2>\n\n<p>You can adjust who gets to use the plugin by setting the privileges in options.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s options page and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Updates and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_search_db\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Uninstalling adi_search_db</strong></h2>\n\n<p>To uninstall <strong>adi_search_db</strong>, simply go to the Plugins tab and delete it.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=39528\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_search_db\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_search_db - Search the TXP database\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n		0.3		- fixes for 4.6 (thanks jpdupont)\n				- fix: table prefixes (thanks jpdupont)\n		0.2		- minor UI improvements\n				- behind the scenes search improvements\n				- tightened up on privileges\n				- plugin privileges in options\n				- tested on TXP 4.6\n		0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $prefs,$event,$adi_search_db_debug;\n\n	$adi_search_db_debug = 0;\n\n	if ($adi_search_db_debug && ($event != \"adi_search_db_admin\")) $adi_search_db_debug = 0;\n\n	// stick with the modern stuff\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_search_db_init();\n}\n\nfunction adi_search_db_init() {\n// initial set up\n	global $prefs,$event,$textarray,$txp_user,$adi_search_db_debug,$adi_search_db_plugin_status,$adi_search_db_url,$adi_search_db_list,$adi_search_db_cfs,$adi_search_db_prefs,$adi_search_db_outlaws,$adi_search_db_admin_outlaws;\n\n	// default preferences\n	$adi_search_db_prefs = array(\n		\'adi_search_db_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n	);\n\n	// privs to be excluded\n	$adi_search_db_outlaws = array(0);\n	$adi_search_db_admin_outlaws = array(0,5);\n\n	// Textpack\n	$adi_search_db_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_search_db\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_search_db_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_search_db_gtxt = array(\n		\'adi_filter\' => \'Filter\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_search_db\' => \'Search DB\',\n		\'adi_search_database\' => \'Search Database\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_search_db_gtxt;\n\n	// who gets to play?\n	add_privs(\'adi_search_db\',adi_search_db_pref(\'adi_search_db_privs\'));\n\n	if (has_privs(\'adi_search_db\')) {\n\n		// search config list\n		$adi_search_db_list[\'articles\'] = array( // ARTICLES\n								\'table\'		=>	\'textpattern\',							// table to be searched\n								\'title\'		=>	gTxt(\'articles\'),						// human friendly title\n								\'key\'		=>	\'ID\',									// index key field\n								\'thing\'		=>	\'\',										// use instead of key name in edit link (i.e. thing=value, instead of key=value)\n								\'hide_key\'	=>	\'0\',									// whether to hide index key in link text or not\n								\'text\'		=>	\'Title\',								// field used for link text (defaults to key)\n								\'text_alt\'	=>	\'\',										// alternative field for link text, if key value is empty\n								\'fields\'	=> 	array(\'Title\',\'Body\',\'Excerpt\',			// fields of interest\n												\'Image\',\'Keywords\',\'url_title\',\n												\'override_form\',\'AnnotateInvite\'),\n								\'labels\'	=> 	array(\'title\',\'body\',\'excerpt\',			// field labels used in filter list (will be gTxted)\n												\'article_image\',\'keywords\',\'url_title\',\n												\'override_default_form\',\'comment_invitation\'),\n								\'event\'		=>	\'article\',								// event for edit link\n								\'step\'		=>	\'edit\',									// step for edit link\n								\'plugin\'	=>	\'\',										// plugin dependency\n								\'column\'	=>	\'\',										// column dependency/check (format: table_name|column_name)\n								\'pref\'		=>	array(									// pref dependency (\'pref\' = whole table, array(\'pref\'=>\'field\') = individual fields)\n													array(\'use_comments\'=> \'AnnotateInvite\')\n												),\n								\'privs\'		=>	\'article\',								// privs required for search (from admin_config.php)\n								\'elink\'		=>	\'\',										// privs required to get direct edit link (else default to normal privs)\n								\'replace\'	=>	\'\',										// replace item (i.e. existing $adi_search_db_list[\'item\'])\n								);\n		$adi_search_db_list[\'categories\'] = array( // CATEGORIES\n								\'table\'		=>	\'txp_category\',\n								\'title\'		=>	gTxt(\'categories\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\'),\n								\'labels\'	=>	array(\'title\'),\n								\'event\'		=>	\'category\',\n								\'step\'		=>	\'?\',	// have to generate step according to category type, e.g. cat_article_edit\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'category\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'comments\'] = array( // COMMENTS\n								\'table\'		=>	\'txp_discuss\',\n								\'title\'		=>	gTxt(\'comments\'),\n								\'key\'		=>	\'discussid\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'email\',\'web\',\'message\'),\n								\'labels\'	=>	array(\'name\',\'email\',\'web\',\'message\'),\n								\'event\'		=>	\'discuss\',\n								\'step\'		=>	\'discuss_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	array(\'use_comments\'),	// no search if comments switched off\n								\'privs\'		=>	\'discuss\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'files\'] = array( // FILES\n								\'table\'		=>	\'txp_file\',\n								\'title\'		=>	gTxt(\'tab_file\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'filename\',\n								\'fields\'	=>	array(\'filename\',\'title\',\'description\'),\n								\'labels\'	=>	array(\'filename\',\'title\',\'description\'),\n								\'event\'		=>	\'file\',\n								\'step\'		=>	\'file_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'file\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'forms\'] = array( // FORMS\n								\'table\'		=>	\'txp_form\',\n								\'title\'		=>	gTxt(\'forms\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'form\'),\n								\'labels\'	=>	array(\'form\'),\n								\'event\'		=>	\'form\',\n								\'step\'		=>	\'form_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'form\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'images\'] = array( // IMAGES\n								\'table\'		=>	\'txp_image\',\n								\'title\'		=>	gTxt(\'images\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'alt\',\n								\'text_alt\'	=>	\'name\',\n								\'fields\'	=>	array(\'name\',\'alt\',\'caption\',\'adi_image_tab_keywords\'),\n								\'labels\'	=>	array(\'name\',\'alt_text\',\'caption\',\'keywords\'),\n								\'event\'		=>	\'image\',\n								\'step\'		=>	\'image_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'image\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'links\'] = array( // LINKS\n								\'table\'		=>	\'txp_link\',\n								\'title\'		=>	gTxt(\'links\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'linkname\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'url\',\'linkname\',\'description\'),\n								\'labels\'	=>	array(\'url\',\'name\',\'description\'),\n								\'event\'		=>	\'link\',\n								\'step\'		=>	\'link_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'link\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'pages\'] = array( // PAGES\n								\'table\'		=>	\'txp_page\',\n								\'title\'		=>	gTxt(\'pages\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'user_html\'),\n								\'labels\'	=>	array(\'page\'),\n								\'event\'		=>	\'page\',\n								\'step\'		=>	\'\',	// step not used\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'page\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'prefs\'] = array( // PREFS\n								\'table\'		=>	\'txp_prefs\',\n								\'title\'		=>	gTxt(\'prefs\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'val\'),\n								\'labels\'	=>	array(\'name\',\'value\'),\n								\'event\'		=>	\'prefs\',\n								\'step\'		=>	\'\',	// step not used\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'prefs\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'sections\'] = array( // SECTIONS\n								\'table\'		=>	\'txp_section\',\n								\'title\'		=>	gTxt(\'sections\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\'),\n								\'labels\'	=>	array(\'title\'),\n								\'event\'		=>	\'section\',\n								\'step\'		=>	\'section_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'section\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'style\'] = array( // STYLE\n								\'table\'		=>	\'txp_css\',\n								\'title\'		=>	gTxt(\'tab_style\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'css\'),\n								\'labels\'	=>	array(\'css\'),\n								\'event\'		=>	\'css\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'css\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'users\'] = array( // USERS\n								\'table\'		=>	\'txp_users\',\n								\'title\'		=>	gTxt(\'site_administration\'),\n								\'key\'		=>	\'user_id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'RealName\',\'email\'),\n								\'labels\'	=>	array(\'name\',\'real_name\',\'email\'),\n								\'event\'		=>	\'admin\',\n								\'step\'		=>	\'author_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'admin\',\n								\'elink\'		=>	\'admin.edit\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_menu\'] = array( // ADI_MENU\n								\'table\'		=>	\'txp_section\',\n								\'title\'		=>	\'adi_menu\',\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'adi_menu_title\',\'adi_menu_alt_title\',\'adi_menu_parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\'),\n								\'labels\'	=>	array(\'adi_alt_title\',\'adi_clone_title\',\'parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\'),\n								\'event\'		=>	\'adi_menu_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_menu\',\n								\'column\'	=>	\'txp_section|adi_menu_parent\', // e.g. if in plugin cache but not installed\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_menu_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_menu_virtual\'] = array( // ADI_MENU (virtual sections)\n								\'table\'		=>	\'adi_menu\',\n								\'title\'		=>	\'adi_menu (\'.gTxt(\'adi_menu_virtual_sections\').\')\',\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'title\',\'parent\',\'alt_title\',\'parent\',\'section\',\'category\',\'clone_title\'),\n								\'labels\'	=>	array(\'name\',\'title\',\'parent\',\'adi_alt_title\',\'parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\',\'adi_clone_title\'),\n								\'event\'		=>	\'adi_menu_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_menu\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_menu_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_scripts\'] = array( // ADI_SCRIPTS\n								\'table\'		=>	\'adi_scripts\',\n								\'title\'		=>	\'adi_scripts\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'filename\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'filename\',\'directory\'),\n								\'labels\'	=>	array(\'filename\',\'adi_directory\'),\n								\'event\'		=>	\'adi_scripts_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_scripts\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_scripts_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_bio\'] = array( // SMD_BIO\n								\'table\'		=>	\'smd_bio\',\n								\'title\'		=>	\'smd_bio\',\n								\'key\'		=>	\'user_ref\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'bioname\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'user_ref\',\'bioname\'),\n								\'labels\'	=>	array(\'user_ref\',\'bioname\'),\n								\'event\'		=>	\'admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'smd_bio\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_bio\',\n								\'elink\'		=>	\'\', // too-hard-basket (edit link requires userid, smd\'s user_ref is username)\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_bio_meta\'] = array( // SMD_BIO (META)\n								\'table\'		=>	\'smd_bio_meta\',\n								\'title\'		=>	\'smd_bio_meta\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\',\'name\'),\n								\'labels\'	=>	array(\'title\',\'name\'),\n								\'event\'		=>	\'smd_bio\',\n								\'step\'		=>	\'meta_edit\',\n								\'plugin\'	=>	\'smd_bio\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_bio\',\n								\'elink\'		=>	\'smd_bio\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_user_manager\'] = array( // SMD_USER_MANAGER\n								\'table\'		=>	\'txp_users\',\n								\'title\'		=>	\'smd_user_manager (\'.gTxt(\'authors\').\')\',\n								\'key\'		=>	\'user_id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'RealName\',\'email\'),\n								\'labels\'	=>	array(\'name\',\'real_name\',\'email\'),\n								\'event\'		=>	\'smd_um\',\n								\'step\'		=>	\'smd_um_edit\',\n								\'plugin\'	=>	\'smd_user_manager\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'admin\',\n								\'elink\'		=>	\'admin.edit\',\n								\'replace\'	=>	\'users\',\n								);\n		$adi_search_db_list[\'smd_um_groups\'] = array( // SMD_USER_MANAGER (GROUPS)\n								\'table\'		=>	\'smd_um_groups\',\n								\'title\'		=>	\'smd_user_manager (\'.gTxt(\'groups\').\')\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\'),\n								\'labels\'	=>	array(\'group\'),\n								\'event\'		=>	\'smd_um\',\n								\'step\'		=>	\'smd_um_groups\',\n								\'plugin\'	=>	\'smd_user_manager\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_um.grp\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n\n		// add custom fields to \'textpattern\' fields of interest & labels\n		$adi_search_db_cfs = getCustomFields();\n		foreach ($adi_search_db_cfs as $index => $value) { // this assumes that \'textpattern\' table is first in the list (i.e. index zero)\n			$adi_search_db_list[\'articles\'][\'fields\'][] = \'custom_\'.$index;\n			$adi_search_db_list[\'articles\'][\'labels\'][] = $prefs[\'custom_\'.$index.\'_set\'];\n		}\n\n		// adi_search_db tab\n		add_privs(\'adi_search_db_admin\',adi_search_db_pref(\'adi_search_db_privs\')); // the people who get to play (but search results may be priv-filtered later on)\n		register_tab(\'extensions\',\'adi_search_db_admin\',gTxt(\'adi_search_db\')); // add tab under \'Extensions\'\n		register_callback(\'adi_search_db_admin\',\'adi_search_db_admin\');\n\n		// style\n		if ($event == \'adi_search_db_admin\')\n			register_callback(\'adi_search_db_style\',\'admin_side\',\'head_end\');\n\n		// script\n		if ($event == \'adi_search_db_admin\')\n			register_callback(\'adi_search_db_script\',\'admin_side\',\'head_end\');\n\n	}\n\n	// plugin options\n	$adi_search_db_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_search_db\',$adi_search_db_debug);\n	if ($adi_search_db_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_search_db\'); // defaults to priv \'1\' only\n		register_callback(\'adi_search_db_options\',\'plugin_prefs.adi_search_db\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_search_db_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_search_db_options\',\'adi_search_db options\');\n		register_callback(\'adi_search_db_options\',\'adi_search_db_options\');\n	}\n\n}\n\nfunction adi_search_db_style() {\n// schnappy und stylish\n	echo\n		\'<style type=\"text/css\">\n			/* adi_search_db */\n			h1 { float:left; margin-right:2em }\n			.adi_search_db_form input.smallerbox { float:left; margin:0.3em 0 0 0 }\n			.adi_search_db_form input.adi_search_db_input { float:left; margin:0.3em 2em 0 0; width:20em }\n			.adi_search_db_results { clear:left; float:left }\n			.adi_search_db_results h2 { clear:both }\n			.adi_search_db_results h3 { margin-bottom:0 }\n			.adi_search_db_results ul { list-style:none; margin-top:0.5em }\n			.adi_search_db_filter { float:right; margin-top:0.5em }\n			.adi_search_db_filter ul { list-style:none; margin-top:0 }\n			.adi_search_db_filter ul p { margin:0; font-weight:bold }\n			.adi_search_db_filter > p { text-align:right }\n		</style>\';\n}\n\nfunction adi_search_db_dependencies($search_db_list) {\n// examine search DB list & strip out according to dependencies - plugin, preference, privs, table, column - and strips out non-existant columns from search list\n	global $txp_user,$prefs,$txp_permissions,$adi_search_db_debug,$adi_search_db_cfs;\n\n	// discover tables in database\n	$db_tables = array();\n    $rows = getRows(\"SHOW TABLES\");\n    foreach ($rows as $row)\n        $db_tables[] = array_shift($row);\n\n	if ($adi_search_db_debug) {\n		echo \'All tables:\';\n		dmp($db_tables);\n		echo \'$adi_search_db_cfs:\';\n		dmp($adi_search_db_cfs);\n		echo \'$search_db_list:\';\n		dmp($search_db_list);\n		$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n		echo \"User=$txp_user, privs=$user_priv\".br.br;\n		echo \'txp_permissions:\';\n		dmp($txp_permissions);\n		echo \'Dependency removals:\'.br;\n	}\n\n	// dependencies\n	foreach ($search_db_list as $index => $config) {\n\n		$ok = TRUE;\n		$reason = \'\';\n\n		// plugin dependency\n		if ($config[\'plugin\']) {\n			$ok &= (load_plugin($config[\'plugin\']) || function_exists($config[\'plugin\']) || function_exists($config[\'plugin\'].\'_admin\'));\n			// load_plugin only detects plugins in cache if filename is plugin_name.php not plugin_name-version.php,\n			// so see if there\'s a function with the same name as the plugin loaded (works for adi_menu & adi_scripts)\n			$reason .= sp.\'plugin\';\n		}\n\n		// preference dependency\n		if ($config[\'pref\']) {\n			foreach ($config[\'pref\'] as $pref_index => $data) {\n				if (is_array($data)) { // field/preference dependency\n					foreach ($data as $this_pref => $this_field)\n						if (!$prefs[$this_pref]) {\n							$field_index = array_search($this_field,$config[\'fields\']);\n							if ($field_index !== FALSE)\n								unset($search_db_list[$index][\'fields\'][$field_index]);\n						}\n				}\n				else { // lose the whole thing\n					$ok &= $prefs[$data]; // assume boolean preference value\n					$reason .= sp.\'pref\';\n				}\n			}\n		}\n\n		// privs dependency\n		if (!has_privs($config[\'privs\'])) {\n			$ok &= FALSE;\n			$reason .= sp.\'privs\';\n		}\n\n		// db table dependency\n		if (array_search(safe_pfx($config[\'table\']),$db_tables) === FALSE) {\n			$ok &= FALSE;\n			$reason .= sp.\'table\';\n		}\n\n		// db column dependency\n		if ($config[\'column\']) {\n			$check_data = explode(\'|\',$config[\'column\']);\n			list($table,$column) = $check_data;\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($table).\" LIKE \'\".$column.\"\'\");\n			$a = nextRow($rs);\n			if (empty($a)) {\n				$ok &= FALSE;\n				$reason .= sp.\'column\';\n			}\n		}\n\n		if (!$ok) { // no search for you, me laddo\n			unset($search_db_list[$index]);\n			if ($adi_search_db_debug)\n				echo $config[\'title\'].\' - \'.$reason.\' [\'.($config[\'column\'] ? $config[\'column\'] : $config[\'table\']).\']\'.br;\n		}\n\n	}\n\n	// filter out non-existant columns (allows for different versions of plugin)\n	if ($adi_search_db_debug) echo br.\'Column filter:\'.br;\n	foreach ($search_db_list as $x => $config) {\n		foreach ($config[\'fields\'] as $y => $column) {\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($config[\'table\']).\" LIKE \'\".$column.\"\'\");\n			$a = nextRow($rs);\n			if (empty($a)) {\n				unset($search_db_list[$x][\'fields\'][$y]);\n				if ($adi_search_db_debug) echo \"Table $table, column $column - not found\".br;\n			}\n		}\n	}\n\n	// any plugins that replace standard tabs?\n	if ($adi_search_db_debug) echo br.\'Replace filter:\'.br;\n	foreach ($search_db_list as $x => $config) {\n		if ($config[\'replace\']) {\n			if ($adi_search_db_debug) echo \"$x replaces \".$config[\'replace\'].br;\n			if (isset($search_db_list[$config[\'replace\']])) {\n				unset($search_db_list[$config[\'replace\']]);\n			}\n		}\n	}\n\n	return $search_db_list;\n}\n\nfunction adi_search_db_admin($event, $step) {\n// the search DB tab\n	global $adi_search_db_debug,$adi_search_db_list;\n\n	$search_db_list = adi_search_db_dependencies($adi_search_db_list); // do this here, so that bot_privs() has had its wicked way\n\n	if ($adi_search_db_debug) {\n		echo \"<p><b>Event:</b> \".$event.\", <b>Step:</b> \".$step.\"</p>\";\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n	}\n\n	$message = \'\';\n	$search_term = \'\';\n\n	// admin steps\n	if ($step == \'search\')\n		$search_term = doSlash(gps(\'search\'));\n\n	$adi_search_db_filter = gps(\'adi_search_db_filter\');\n\n	// generate page\n	pagetop(gTxt(\'adi_search_database\'),$message);\n\n	$autofocus = ($search_term ? \'\' : \' autofocus\'); // autofocus search field for first visit to tab\n	$input = \'<input type=\"text\" value=\"\'.str_replace(\'\"\',\'&quot;\',stripslashes($search_term)).\'\" name=\"search\" class=\"adi_search_db_input\"\'.$autofocus.\' />\';\n\n	// the search box\n    echo form(\n		tag(\n			tag(gTxt(\'adi_search_database\'),\'h1\')\n			.$input\n			.fInput(\'submit\',\'adi_search_db_admin_submit\',gTxt(\'search\'),\"smallerbox\",\"\",\'\')\n			.eInput(\"adi_search_db_admin\").sInput(\"search\")\n			.tag(adi_search_db_filter($adi_search_db_filter),\'div\',\' class=\"adi_search_db_filter\"\')\n			,\'div\'\n\n		)\n		,\'\',\'\',\'post\',\'adi_search_db_form\'\n	);\n\n	// do some searching\n	if ($search_term != \'\') {\n\n		echo \'<div class=\"adi_search_db_results\">\';\n\n		echo tag(gTxt(\'search_results\').\' \"\'.htmlspecialchars(stripslashes($search_term)).\'\" &hellip;\',\'h2\');\n\n		// process list\n		foreach ($search_db_list as $index => $config) {\n\n			if (!isset($adi_search_db_filter[$index])) // all field checkboxes switched off\n				continue;\n\n			echo tag($config[\'title\'],\'h3\');\n\n			// fields of interest\n			$fields = $config[\'fields\'];\n\n			// query bits\n			$conditions = array();\n			foreach ($fields as $field) {\n				if (array_key_exists($field,$adi_search_db_filter[$index]))\n					$conditions[] = \"`\".$field.\"`\".\' LIKE \"%\'.$search_term.\'%\"\';\n			}\n			$where = implode(\' OR \',$conditions);\n			if ($adi_search_db_debug)\n				echo $where.br;\n\n			// search table & output results\n			$this_table = $config[\'table\'];\n			$text_field = $config[\'text\'];\n			$text_alt_field = $config[\'text_alt\'];\n			$rs = safe_rows(\'*\',$this_table,$where,$adi_search_db_debug);\n			if ($rs) {\n				echo \'<ul>\';\n				foreach ($rs as $row) {\n					extract($row);\n					echo \'<li>\';\n					$this_key = $config[\'key\'];\n					$this_thing = $config[\'thing\'];\n					if (isset($config[\'event\']) && isset($config[\'step\'])) {\n						$link_text = $$this_key;\n						if ($text_field)\n							$link_text = $$text_field;\n						if (empty($link_text) && $text_alt_field)\n							$link_text = $$text_alt_field;\n						if ($this_table == \'txp_prefs\') // translate pref name into something human\n							$link_text = gTxt($link_text);\n						if (!$config[\'hide_key\'])\n							$link_text .= \' (\'.$$this_key.\')\';\n						$this_step = $config[\'step\'];\n						if ($this_table == \'txp_category\') // each category type has a unique step (maybe do placeholder replacement in the future)\n							$this_step = \'cat_\'.$type.\'_edit\';\n						if ($config[\'elink\']) // edit link has special privs\n							if (!has_privs($config[\'elink\'])) { // redirect link to event\'s general/list tab by stripping $step\n								$this_step = \'\';\n							}\n						echo eLink(\n							$config[\'event\'],	// event\n							$this_step,			// step\n							($this_thing ? $this_thing : $this_key), // thing\n							$$this_key,			// value\n							$link_text			// linktext\n						);\n					}\n					else\n						echo $$this_key;\n					echo \'</li>\';\n				}\n				echo \'</ul>\';\n			}\n			else\n				echo graf(tag(gTxt(\'no_results_found\'),\'em\'));\n		}\n\n		echo \'</div>\';\n\n	}\n\n	echo \'<div class=\"clearfix\"></div>\'; // blargh\n\n}\n\nfunction adi_search_db_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_search_db_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_search_db_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_search_db_prefs))\n			$html = $adi_search_db_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_search_db_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_search_db_filter($adi_search_db_filter=array()) {\n// field filter list\n	global $adi_search_db_debug,$adi_search_db_list;\n\n	$out = \'\';\n	foreach ($adi_search_db_list as $index => $config) {\n		$out .= \'<li>\';\n		$out .= graf($config[\'title\']);\n		$out .= \'<ul>\';\n		foreach ($config[\'fields\'] as $field_index => $field) {\n			$checked = 0;\n			if (is_array($adi_search_db_filter)) { // search already performed\n				if (isset($adi_search_db_filter[$index]))\n					$checked = array_key_exists($field,$adi_search_db_filter[$index]);\n			}\n			else // first visit to tab\n				$checked = 1;\n			$out .= tag(\n						tag(\n							checkbox(\'adi_search_db_filter[\'.$index.\'][\'.$field.\']\',1,$checked)\n							.sp\n							.gTxt($adi_search_db_list[$index][\'labels\'][$field_index])\n							,\'label\'\n						)\n						,\'li\'\n					);\n		}\n		$out .= \'</ul></li>\';\n	}\n	return graf(\'<a href=\"#\" id=\"adi_search_db_toggle\">\'.gTxt(\'adi_filter\').\'</a>\').tag($out,\'ul\');\n}\n\nfunction adi_search_db_script() {\n// script for filter peepo & auto focus/select\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n$(function() {\n	$(\".adi_search_db_input\").focus(function(){\n		this.select();\n	});\n	$(\".adi_search_db_filter ul\").hide();\n	$(\"#adi_search_db_toggle\").click(function() {\n		$(\".adi_search_db_filter ul\").toggle(\'fast\');\n	});\n});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_search_db_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_search_db_prefs,$adi_search_db_outlaws,$adi_search_db_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_search_db_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_search_db_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_search_db_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_search_db_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']).sp.sp;\n						$admin_privs = do_list(adi_search_db_pref(\'adi_search_db_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_search_db_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(checkbox2(\"adi_search_db_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_search_db_admin_privs\').sp.gTxt($group).sp.sp,\'label\');\n						$out .= graf($priv_out);\n					}\n					// plugin privs\n					if ($pref_name == \'adi_search_db_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']).sp.sp;\n						$plugin_privs = do_list(adi_search_db_pref(\'adi_search_db_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_search_db_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(checkbox2(\"adi_search_db_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_search_db_privs\').sp.gTxt($group).sp.sp,\'label\');\n						$out .= graf($priv_out);\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_search_db_pref($pref_name)),$pref_name)\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\"\'\n				)\n			);\n\n}\n\nfunction adi_search_db_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_search_db_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		// run through all prefs according to tab\n		foreach ($adi_search_db_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) {\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_search_db_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_search_db_prefs[$pref_name][\'value\'];\n				$result &= adi_search_db_pref($pref_name,$value);\n			}\n		}\n		return $result;\n	}\n\n}\n\nfunction adi_search_db_options($event,$step) {\n// adi_search_db options page\n	global $adi_search_db_debug,$adi_search_db_url,$adi_search_db_plugin_status;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_search_db_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_search_db_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_search_db - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_search_db \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// link to file list tab\n			.graf(href(gTxt(\'adi_search_db\'),\'?event=adi_search_db_admin\'))\n			// prefs\n			.adi_search_db_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_search_db_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_search_db_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n}\n', '\n/*\n	adi_search_db - Search the TXP database\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n		0.3		- fixes for 4.6 (thanks jpdupont)\n				- fix: table prefixes (thanks jpdupont)\n		0.2		- minor UI improvements\n				- behind the scenes search improvements\n				- tightened up on privileges\n				- plugin privileges in options\n				- tested on TXP 4.6\n		0.1		- initial release\n\n*/\n\nif (@txpinterface == \'admin\') {\n	global $prefs,$event,$adi_search_db_debug;\n\n	$adi_search_db_debug = 0;\n\n	if ($adi_search_db_debug && ($event != \"adi_search_db_admin\")) $adi_search_db_debug = 0;\n\n	// stick with the modern stuff\n	if (!version_compare(txp_version,\'4.5.0\',\'>=\')) return;\n\n	adi_search_db_init();\n}\n\nfunction adi_search_db_init() {\n// initial set up\n	global $prefs,$event,$textarray,$txp_user,$adi_search_db_debug,$adi_search_db_plugin_status,$adi_search_db_url,$adi_search_db_list,$adi_search_db_cfs,$adi_search_db_prefs,$adi_search_db_outlaws,$adi_search_db_admin_outlaws;\n\n	// default preferences\n	$adi_search_db_prefs = array(\n		\'adi_search_db_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'blank\' => 1, \'input\' => \'custom\', \'label\' => \'adi_plugin_privs\', \'tab\' => \'options\'), // allowed to use plugin\n	);\n\n	// privs to be excluded\n	$adi_search_db_outlaws = array(0);\n	$adi_search_db_admin_outlaws = array(0,5);\n\n	// Textpack\n	$adi_search_db_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_search_db\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_search_db_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_search_db_gtxt = array(\n		\'adi_filter\' => \'Filter\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_search_db\' => \'Search DB\',\n		\'adi_search_database\' => \'Search Database\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_search_db_gtxt;\n\n	// who gets to play?\n	add_privs(\'adi_search_db\',adi_search_db_pref(\'adi_search_db_privs\'));\n\n	if (has_privs(\'adi_search_db\')) {\n\n		// search config list\n		$adi_search_db_list[\'articles\'] = array( // ARTICLES\n								\'table\'		=>	\'textpattern\',							// table to be searched\n								\'title\'		=>	gTxt(\'articles\'),						// human friendly title\n								\'key\'		=>	\'ID\',									// index key field\n								\'thing\'		=>	\'\',										// use instead of key name in edit link (i.e. thing=value, instead of key=value)\n								\'hide_key\'	=>	\'0\',									// whether to hide index key in link text or not\n								\'text\'		=>	\'Title\',								// field used for link text (defaults to key)\n								\'text_alt\'	=>	\'\',										// alternative field for link text, if key value is empty\n								\'fields\'	=> 	array(\'Title\',\'Body\',\'Excerpt\',			// fields of interest\n												\'Image\',\'Keywords\',\'url_title\',\n												\'override_form\',\'AnnotateInvite\'),\n								\'labels\'	=> 	array(\'title\',\'body\',\'excerpt\',			// field labels used in filter list (will be gTxted)\n												\'article_image\',\'keywords\',\'url_title\',\n												\'override_default_form\',\'comment_invitation\'),\n								\'event\'		=>	\'article\',								// event for edit link\n								\'step\'		=>	\'edit\',									// step for edit link\n								\'plugin\'	=>	\'\',										// plugin dependency\n								\'column\'	=>	\'\',										// column dependency/check (format: table_name|column_name)\n								\'pref\'		=>	array(									// pref dependency (\'pref\' = whole table, array(\'pref\'=>\'field\') = individual fields)\n													array(\'use_comments\'=> \'AnnotateInvite\')\n												),\n								\'privs\'		=>	\'article\',								// privs required for search (from admin_config.php)\n								\'elink\'		=>	\'\',										// privs required to get direct edit link (else default to normal privs)\n								\'replace\'	=>	\'\',										// replace item (i.e. existing $adi_search_db_list[\'item\'])\n								);\n		$adi_search_db_list[\'categories\'] = array( // CATEGORIES\n								\'table\'		=>	\'txp_category\',\n								\'title\'		=>	gTxt(\'categories\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\'),\n								\'labels\'	=>	array(\'title\'),\n								\'event\'		=>	\'category\',\n								\'step\'		=>	\'?\',	// have to generate step according to category type, e.g. cat_article_edit\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'category\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'comments\'] = array( // COMMENTS\n								\'table\'		=>	\'txp_discuss\',\n								\'title\'		=>	gTxt(\'comments\'),\n								\'key\'		=>	\'discussid\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'email\',\'web\',\'message\'),\n								\'labels\'	=>	array(\'name\',\'email\',\'web\',\'message\'),\n								\'event\'		=>	\'discuss\',\n								\'step\'		=>	\'discuss_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	array(\'use_comments\'),	// no search if comments switched off\n								\'privs\'		=>	\'discuss\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'files\'] = array( // FILES\n								\'table\'		=>	\'txp_file\',\n								\'title\'		=>	gTxt(\'tab_file\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'filename\',\n								\'fields\'	=>	array(\'filename\',\'title\',\'description\'),\n								\'labels\'	=>	array(\'filename\',\'title\',\'description\'),\n								\'event\'		=>	\'file\',\n								\'step\'		=>	\'file_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'file\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'forms\'] = array( // FORMS\n								\'table\'		=>	\'txp_form\',\n								\'title\'		=>	gTxt(\'forms\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'form\'),\n								\'labels\'	=>	array(\'form\'),\n								\'event\'		=>	\'form\',\n								\'step\'		=>	\'form_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'form\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'images\'] = array( // IMAGES\n								\'table\'		=>	\'txp_image\',\n								\'title\'		=>	gTxt(\'images\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'alt\',\n								\'text_alt\'	=>	\'name\',\n								\'fields\'	=>	array(\'name\',\'alt\',\'caption\',\'adi_image_tab_keywords\'),\n								\'labels\'	=>	array(\'name\',\'alt_text\',\'caption\',\'keywords\'),\n								\'event\'		=>	\'image\',\n								\'step\'		=>	\'image_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'image\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'links\'] = array( // LINKS\n								\'table\'		=>	\'txp_link\',\n								\'title\'		=>	gTxt(\'links\'),\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'linkname\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'url\',\'linkname\',\'description\'),\n								\'labels\'	=>	array(\'url\',\'name\',\'description\'),\n								\'event\'		=>	\'link\',\n								\'step\'		=>	\'link_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'link\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'pages\'] = array( // PAGES\n								\'table\'		=>	\'txp_page\',\n								\'title\'		=>	gTxt(\'pages\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'user_html\'),\n								\'labels\'	=>	array(\'page\'),\n								\'event\'		=>	\'page\',\n								\'step\'		=>	\'\',	// step not used\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'page\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'prefs\'] = array( // PREFS\n								\'table\'		=>	\'txp_prefs\',\n								\'title\'		=>	gTxt(\'prefs\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'0\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'val\'),\n								\'labels\'	=>	array(\'name\',\'value\'),\n								\'event\'		=>	\'prefs\',\n								\'step\'		=>	\'\',	// step not used\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'prefs\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'sections\'] = array( // SECTIONS\n								\'table\'		=>	\'txp_section\',\n								\'title\'		=>	gTxt(\'sections\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\'),\n								\'labels\'	=>	array(\'title\'),\n								\'event\'		=>	\'section\',\n								\'step\'		=>	\'section_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'section\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'style\'] = array( // STYLE\n								\'table\'		=>	\'txp_css\',\n								\'title\'		=>	gTxt(\'tab_style\'),\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'css\'),\n								\'labels\'	=>	array(\'css\'),\n								\'event\'		=>	\'css\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'css\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'users\'] = array( // USERS\n								\'table\'		=>	\'txp_users\',\n								\'title\'		=>	gTxt(\'site_administration\'),\n								\'key\'		=>	\'user_id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'RealName\',\'email\'),\n								\'labels\'	=>	array(\'name\',\'real_name\',\'email\'),\n								\'event\'		=>	\'admin\',\n								\'step\'		=>	\'author_edit\',\n								\'plugin\'	=>	\'\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'admin\',\n								\'elink\'		=>	\'admin.edit\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_menu\'] = array( // ADI_MENU\n								\'table\'		=>	\'txp_section\',\n								\'title\'		=>	\'adi_menu\',\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'adi_menu_title\',\'adi_menu_alt_title\',\'adi_menu_parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\'),\n								\'labels\'	=>	array(\'adi_alt_title\',\'adi_clone_title\',\'parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\'),\n								\'event\'		=>	\'adi_menu_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_menu\',\n								\'column\'	=>	\'txp_section|adi_menu_parent\', // e.g. if in plugin cache but not installed\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_menu_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_menu_virtual\'] = array( // ADI_MENU (virtual sections)\n								\'table\'		=>	\'adi_menu\',\n								\'title\'		=>	\'adi_menu (\'.gTxt(\'adi_menu_virtual_sections\').\')\',\n								\'key\'		=>	\'name\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'title\',\'parent\',\'alt_title\',\'parent\',\'section\',\'category\',\'clone_title\'),\n								\'labels\'	=>	array(\'name\',\'title\',\'parent\',\'adi_alt_title\',\'parent\',\'adi_menu_redirect_section\',\'adi_menu_redirect_category\',\'adi_clone_title\'),\n								\'event\'		=>	\'adi_menu_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_menu\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_menu_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'adi_scripts\'] = array( // ADI_SCRIPTS\n								\'table\'		=>	\'adi_scripts\',\n								\'title\'		=>	\'adi_scripts\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'filename\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'filename\',\'directory\'),\n								\'labels\'	=>	array(\'filename\',\'adi_directory\'),\n								\'event\'		=>	\'adi_scripts_admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'adi_scripts\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'adi_scripts_admin\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_bio\'] = array( // SMD_BIO\n								\'table\'		=>	\'smd_bio\',\n								\'title\'		=>	\'smd_bio\',\n								\'key\'		=>	\'user_ref\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'bioname\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'user_ref\',\'bioname\'),\n								\'labels\'	=>	array(\'user_ref\',\'bioname\'),\n								\'event\'		=>	\'admin\',\n								\'step\'		=>	\'\',\n								\'plugin\'	=>	\'smd_bio\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_bio\',\n								\'elink\'		=>	\'\', // too-hard-basket (edit link requires userid, smd\'s user_ref is username)\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_bio_meta\'] = array( // SMD_BIO (META)\n								\'table\'		=>	\'smd_bio_meta\',\n								\'title\'		=>	\'smd_bio_meta\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'title\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'title\',\'name\'),\n								\'labels\'	=>	array(\'title\',\'name\'),\n								\'event\'		=>	\'smd_bio\',\n								\'step\'		=>	\'meta_edit\',\n								\'plugin\'	=>	\'smd_bio\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_bio\',\n								\'elink\'		=>	\'smd_bio\',\n								\'replace\'	=>	\'\',\n								);\n		$adi_search_db_list[\'smd_user_manager\'] = array( // SMD_USER_MANAGER\n								\'table\'		=>	\'txp_users\',\n								\'title\'		=>	\'smd_user_manager (\'.gTxt(\'authors\').\')\',\n								\'key\'		=>	\'user_id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\',\'RealName\',\'email\'),\n								\'labels\'	=>	array(\'name\',\'real_name\',\'email\'),\n								\'event\'		=>	\'smd_um\',\n								\'step\'		=>	\'smd_um_edit\',\n								\'plugin\'	=>	\'smd_user_manager\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'admin\',\n								\'elink\'		=>	\'admin.edit\',\n								\'replace\'	=>	\'users\',\n								);\n		$adi_search_db_list[\'smd_um_groups\'] = array( // SMD_USER_MANAGER (GROUPS)\n								\'table\'		=>	\'smd_um_groups\',\n								\'title\'		=>	\'smd_user_manager (\'.gTxt(\'groups\').\')\',\n								\'key\'		=>	\'id\',\n								\'thing\'		=>	\'\',\n								\'hide_key\'	=>	\'1\',\n								\'text\'		=>	\'name\',\n								\'text_alt\'	=>	\'\',\n								\'fields\'	=>	array(\'name\'),\n								\'labels\'	=>	array(\'group\'),\n								\'event\'		=>	\'smd_um\',\n								\'step\'		=>	\'smd_um_groups\',\n								\'plugin\'	=>	\'smd_user_manager\',\n								\'column\'	=>	\'\',\n								\'pref\'		=>	\'\',\n								\'privs\'		=>	\'smd_um.grp\',\n								\'elink\'		=>	\'\',\n								\'replace\'	=>	\'\',\n								);\n\n		// add custom fields to \'textpattern\' fields of interest & labels\n		$adi_search_db_cfs = getCustomFields();\n		foreach ($adi_search_db_cfs as $index => $value) { // this assumes that \'textpattern\' table is first in the list (i.e. index zero)\n			$adi_search_db_list[\'articles\'][\'fields\'][] = \'custom_\'.$index;\n			$adi_search_db_list[\'articles\'][\'labels\'][] = $prefs[\'custom_\'.$index.\'_set\'];\n		}\n\n		// adi_search_db tab\n		add_privs(\'adi_search_db_admin\',adi_search_db_pref(\'adi_search_db_privs\')); // the people who get to play (but search results may be priv-filtered later on)\n		register_tab(\'extensions\',\'adi_search_db_admin\',gTxt(\'adi_search_db\')); // add tab under \'Extensions\'\n		register_callback(\'adi_search_db_admin\',\'adi_search_db_admin\');\n\n		// style\n		if ($event == \'adi_search_db_admin\')\n			register_callback(\'adi_search_db_style\',\'admin_side\',\'head_end\');\n\n		// script\n		if ($event == \'adi_search_db_admin\')\n			register_callback(\'adi_search_db_script\',\'admin_side\',\'head_end\');\n\n	}\n\n	// plugin options\n	$adi_search_db_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_search_db\',$adi_search_db_debug);\n	if ($adi_search_db_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_search_db\'); // defaults to priv \'1\' only\n		register_callback(\'adi_search_db_options\',\'plugin_prefs.adi_search_db\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_search_db_options\'); // defaults to priv \'1\' only\n		register_tab(\'extensions\',\'adi_search_db_options\',\'adi_search_db options\');\n		register_callback(\'adi_search_db_options\',\'adi_search_db_options\');\n	}\n\n}\n\nfunction adi_search_db_style() {\n// schnappy und stylish\n	echo\n		\'<style type=\"text/css\">\n			/* adi_search_db */\n			h1 { float:left; margin-right:2em }\n			.adi_search_db_form input.smallerbox { float:left; margin:0.3em 0 0 0 }\n			.adi_search_db_form input.adi_search_db_input { float:left; margin:0.3em 2em 0 0; width:20em }\n			.adi_search_db_results { clear:left; float:left }\n			.adi_search_db_results h2 { clear:both }\n			.adi_search_db_results h3 { margin-bottom:0 }\n			.adi_search_db_results ul { list-style:none; margin-top:0.5em }\n			.adi_search_db_filter { float:right; margin-top:0.5em }\n			.adi_search_db_filter ul { list-style:none; margin-top:0 }\n			.adi_search_db_filter ul p { margin:0; font-weight:bold }\n			.adi_search_db_filter > p { text-align:right }\n		</style>\';\n}\n\nfunction adi_search_db_dependencies($search_db_list) {\n// examine search DB list & strip out according to dependencies - plugin, preference, privs, table, column - and strips out non-existant columns from search list\n	global $txp_user,$prefs,$txp_permissions,$adi_search_db_debug,$adi_search_db_cfs;\n\n	// discover tables in database\n	$db_tables = array();\n    $rows = getRows(\"SHOW TABLES\");\n    foreach ($rows as $row)\n        $db_tables[] = array_shift($row);\n\n	if ($adi_search_db_debug) {\n		echo \'All tables:\';\n		dmp($db_tables);\n		echo \'$adi_search_db_cfs:\';\n		dmp($adi_search_db_cfs);\n		echo \'$search_db_list:\';\n		dmp($search_db_list);\n		$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n		echo \"User=$txp_user, privs=$user_priv\".br.br;\n		echo \'txp_permissions:\';\n		dmp($txp_permissions);\n		echo \'Dependency removals:\'.br;\n	}\n\n	// dependencies\n	foreach ($search_db_list as $index => $config) {\n\n		$ok = TRUE;\n		$reason = \'\';\n\n		// plugin dependency\n		if ($config[\'plugin\']) {\n			$ok &= (load_plugin($config[\'plugin\']) || function_exists($config[\'plugin\']) || function_exists($config[\'plugin\'].\'_admin\'));\n			// load_plugin only detects plugins in cache if filename is plugin_name.php not plugin_name-version.php,\n			// so see if there\'s a function with the same name as the plugin loaded (works for adi_menu & adi_scripts)\n			$reason .= sp.\'plugin\';\n		}\n\n		// preference dependency\n		if ($config[\'pref\']) {\n			foreach ($config[\'pref\'] as $pref_index => $data) {\n				if (is_array($data)) { // field/preference dependency\n					foreach ($data as $this_pref => $this_field)\n						if (!$prefs[$this_pref]) {\n							$field_index = array_search($this_field,$config[\'fields\']);\n							if ($field_index !== FALSE)\n								unset($search_db_list[$index][\'fields\'][$field_index]);\n						}\n				}\n				else { // lose the whole thing\n					$ok &= $prefs[$data]; // assume boolean preference value\n					$reason .= sp.\'pref\';\n				}\n			}\n		}\n\n		// privs dependency\n		if (!has_privs($config[\'privs\'])) {\n			$ok &= FALSE;\n			$reason .= sp.\'privs\';\n		}\n\n		// db table dependency\n		if (array_search(safe_pfx($config[\'table\']),$db_tables) === FALSE) {\n			$ok &= FALSE;\n			$reason .= sp.\'table\';\n		}\n\n		// db column dependency\n		if ($config[\'column\']) {\n			$check_data = explode(\'|\',$config[\'column\']);\n			list($table,$column) = $check_data;\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($table).\" LIKE \'\".$column.\"\'\");\n			$a = nextRow($rs);\n			if (empty($a)) {\n				$ok &= FALSE;\n				$reason .= sp.\'column\';\n			}\n		}\n\n		if (!$ok) { // no search for you, me laddo\n			unset($search_db_list[$index]);\n			if ($adi_search_db_debug)\n				echo $config[\'title\'].\' - \'.$reason.\' [\'.($config[\'column\'] ? $config[\'column\'] : $config[\'table\']).\']\'.br;\n		}\n\n	}\n\n	// filter out non-existant columns (allows for different versions of plugin)\n	if ($adi_search_db_debug) echo br.\'Column filter:\'.br;\n	foreach ($search_db_list as $x => $config) {\n		foreach ($config[\'fields\'] as $y => $column) {\n			$rs = safe_query(\'SHOW FIELDS FROM \'.safe_pfx($config[\'table\']).\" LIKE \'\".$column.\"\'\");\n			$a = nextRow($rs);\n			if (empty($a)) {\n				unset($search_db_list[$x][\'fields\'][$y]);\n				if ($adi_search_db_debug) echo \"Table $table, column $column - not found\".br;\n			}\n		}\n	}\n\n	// any plugins that replace standard tabs?\n	if ($adi_search_db_debug) echo br.\'Replace filter:\'.br;\n	foreach ($search_db_list as $x => $config) {\n		if ($config[\'replace\']) {\n			if ($adi_search_db_debug) echo \"$x replaces \".$config[\'replace\'].br;\n			if (isset($search_db_list[$config[\'replace\']])) {\n				unset($search_db_list[$config[\'replace\']]);\n			}\n		}\n	}\n\n	return $search_db_list;\n}\n\nfunction adi_search_db_admin($event, $step) {\n// the search DB tab\n	global $adi_search_db_debug,$adi_search_db_list;\n\n	$search_db_list = adi_search_db_dependencies($adi_search_db_list); // do this here, so that bot_privs() has had its wicked way\n\n	if ($adi_search_db_debug) {\n		echo \"<p><b>Event:</b> \".$event.\", <b>Step:</b> \".$step.\"</p>\";\n		echo \'<b>$_POST:</b>\';\n		dmp($_POST);\n	}\n\n	$message = \'\';\n	$search_term = \'\';\n\n	// admin steps\n	if ($step == \'search\')\n		$search_term = doSlash(gps(\'search\'));\n\n	$adi_search_db_filter = gps(\'adi_search_db_filter\');\n\n	// generate page\n	pagetop(gTxt(\'adi_search_database\'),$message);\n\n	$autofocus = ($search_term ? \'\' : \' autofocus\'); // autofocus search field for first visit to tab\n	$input = \'<input type=\"text\" value=\"\'.str_replace(\'\"\',\'&quot;\',stripslashes($search_term)).\'\" name=\"search\" class=\"adi_search_db_input\"\'.$autofocus.\' />\';\n\n	// the search box\n    echo form(\n		tag(\n			tag(gTxt(\'adi_search_database\'),\'h1\')\n			.$input\n			.fInput(\'submit\',\'adi_search_db_admin_submit\',gTxt(\'search\'),\"smallerbox\",\"\",\'\')\n			.eInput(\"adi_search_db_admin\").sInput(\"search\")\n			.tag(adi_search_db_filter($adi_search_db_filter),\'div\',\' class=\"adi_search_db_filter\"\')\n			,\'div\'\n\n		)\n		,\'\',\'\',\'post\',\'adi_search_db_form\'\n	);\n\n	// do some searching\n	if ($search_term != \'\') {\n\n		echo \'<div class=\"adi_search_db_results\">\';\n\n		echo tag(gTxt(\'search_results\').\' \"\'.htmlspecialchars(stripslashes($search_term)).\'\" &hellip;\',\'h2\');\n\n		// process list\n		foreach ($search_db_list as $index => $config) {\n\n			if (!isset($adi_search_db_filter[$index])) // all field checkboxes switched off\n				continue;\n\n			echo tag($config[\'title\'],\'h3\');\n\n			// fields of interest\n			$fields = $config[\'fields\'];\n\n			// query bits\n			$conditions = array();\n			foreach ($fields as $field) {\n				if (array_key_exists($field,$adi_search_db_filter[$index]))\n					$conditions[] = \"`\".$field.\"`\".\' LIKE \"%\'.$search_term.\'%\"\';\n			}\n			$where = implode(\' OR \',$conditions);\n			if ($adi_search_db_debug)\n				echo $where.br;\n\n			// search table & output results\n			$this_table = $config[\'table\'];\n			$text_field = $config[\'text\'];\n			$text_alt_field = $config[\'text_alt\'];\n			$rs = safe_rows(\'*\',$this_table,$where,$adi_search_db_debug);\n			if ($rs) {\n				echo \'<ul>\';\n				foreach ($rs as $row) {\n					extract($row);\n					echo \'<li>\';\n					$this_key = $config[\'key\'];\n					$this_thing = $config[\'thing\'];\n					if (isset($config[\'event\']) && isset($config[\'step\'])) {\n						$link_text = $$this_key;\n						if ($text_field)\n							$link_text = $$text_field;\n						if (empty($link_text) && $text_alt_field)\n							$link_text = $$text_alt_field;\n						if ($this_table == \'txp_prefs\') // translate pref name into something human\n							$link_text = gTxt($link_text);\n						if (!$config[\'hide_key\'])\n							$link_text .= \' (\'.$$this_key.\')\';\n						$this_step = $config[\'step\'];\n						if ($this_table == \'txp_category\') // each category type has a unique step (maybe do placeholder replacement in the future)\n							$this_step = \'cat_\'.$type.\'_edit\';\n						if ($config[\'elink\']) // edit link has special privs\n							if (!has_privs($config[\'elink\'])) { // redirect link to event\'s general/list tab by stripping $step\n								$this_step = \'\';\n							}\n						echo eLink(\n							$config[\'event\'],	// event\n							$this_step,			// step\n							($this_thing ? $this_thing : $this_key), // thing\n							$$this_key,			// value\n							$link_text			// linktext\n						);\n					}\n					else\n						echo $$this_key;\n					echo \'</li>\';\n				}\n				echo \'</ul>\';\n			}\n			else\n				echo graf(tag(gTxt(\'no_results_found\'),\'em\'));\n		}\n\n		echo \'</div>\';\n\n	}\n\n	echo \'<div class=\"clearfix\"></div>\'; // blargh\n\n}\n\nfunction adi_search_db_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_search_db_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_search_db_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_search_db_prefs))\n			$html = $adi_search_db_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_search_db_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_search_db_filter($adi_search_db_filter=array()) {\n// field filter list\n	global $adi_search_db_debug,$adi_search_db_list;\n\n	$out = \'\';\n	foreach ($adi_search_db_list as $index => $config) {\n		$out .= \'<li>\';\n		$out .= graf($config[\'title\']);\n		$out .= \'<ul>\';\n		foreach ($config[\'fields\'] as $field_index => $field) {\n			$checked = 0;\n			if (is_array($adi_search_db_filter)) { // search already performed\n				if (isset($adi_search_db_filter[$index]))\n					$checked = array_key_exists($field,$adi_search_db_filter[$index]);\n			}\n			else // first visit to tab\n				$checked = 1;\n			$out .= tag(\n						tag(\n							checkbox(\'adi_search_db_filter[\'.$index.\'][\'.$field.\']\',1,$checked)\n							.sp\n							.gTxt($adi_search_db_list[$index][\'labels\'][$field_index])\n							,\'label\'\n						)\n						,\'li\'\n					);\n		}\n		$out .= \'</ul></li>\';\n	}\n	return graf(\'<a href=\"#\" id=\"adi_search_db_toggle\">\'.gTxt(\'adi_filter\').\'</a>\').tag($out,\'ul\');\n}\n\nfunction adi_search_db_script() {\n// script for filter peepo & auto focus/select\n\n	echo <<<END_SCRIPT\n<script type=\"text/javascript\">\n$(function() {\n	$(\".adi_search_db_input\").focus(function(){\n		this.select();\n	});\n	$(\".adi_search_db_filter ul\").hide();\n	$(\"#adi_search_db_toggle\").click(function() {\n		$(\".adi_search_db_filter ul\").toggle(\'fast\');\n	});\n});\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_search_db_pref_inputs($event,$tab=NULL) {\n// output preference input fields accoring to options/admin setting\n	global $txp_groups,$adi_search_db_prefs,$adi_search_db_outlaws,$adi_search_db_admin_outlaws;\n\n	if ($tab === NULL) return;\n\n	$out = \'\';\n\n	foreach ($adi_search_db_prefs as $pref_name => $pref_info) {\n		if ($pref_info[\'tab\'] == $tab) {\n			switch ($pref_info[\'input\']) {\n				case \'yesnoradio\':\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'1\',(adi_search_db_pref($pref_name) == \'1\')).sp.gTxt(\'yes\'),\'label\')\n							.sp.sp\n							.tag(radio($pref_name,\'0\',(adi_search_db_pref($pref_name) == \'0\')).sp.gTxt(\'no\'),\'label\')\n						);\n					break;\n				case \'custom\':\n					// admin privs\n					if ($pref_name == \'adi_search_db_admin_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']).sp.sp;\n						$admin_privs = do_list(adi_search_db_pref(\'adi_search_db_admin_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_search_db_admin_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(checkbox2(\"adi_search_db_admin_privs[$index]\",(array_search($index,$admin_privs) !== FALSE),\'\',\'adi_search_db_admin_privs\').sp.gTxt($group).sp.sp,\'label\');\n						$out .= graf($priv_out);\n					}\n					// plugin privs\n					if ($pref_name == \'adi_search_db_privs\') {\n						$priv_out = gTxt($pref_info[\'label\']).sp.sp;\n						$plugin_privs = do_list(adi_search_db_pref(\'adi_search_db_privs\'));\n						foreach ($txp_groups as $index => $group)\n							if (array_search($index,$adi_search_db_outlaws) === FALSE) // ignore the outlaws\n								$priv_out .= tag(checkbox2(\"adi_search_db_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_search_db_privs\').sp.gTxt($group).sp.sp,\'label\');\n						$out .= graf($priv_out);\n					}\n					break;\n				default: // text_input\n					$out .=\n						graf(\n							tag(gTxt($pref_info[\'label\']),\'label\')\n							.sp.sp\n							.finput(\'text\',$pref_name,stripslashes(adi_search_db_pref($pref_name)),$pref_name)\n						);\n					break;\n			}\n		}\n	}\n\n	if ($out)\n		return\n			form(\n				tag(\n					tag(gTxt(\'edit_preferences\'),\'h2\')\n					.$out\n					.fInput(\'submit\',\'do_something\',gTxt(\'adi_update_prefs\'),\'smallerbox\')\n					.eInput($event)\n					.sInput(\'update_prefs\')\n					,\'div\'\n					,\' style=\"text-align:center\"\'\n				)\n			);\n\n}\n\nfunction adi_search_db_update_prefs($tab=NULL) {\n// update prefs\n	global $txp_user,$adi_search_db_prefs;\n\n	if ($tab === NULL)\n		return FALSE;\n	else {\n		$result = TRUE;\n		// run through all prefs according to tab\n		foreach ($adi_search_db_prefs as $pref_name => $pref_info) {\n			if ($pref_info[\'tab\'] == $tab) {\n				if (array_key_exists($pref_name,$_POST))\n					$value = $_POST[$pref_name];\n				else if ($pref_info[\'input\'] == \'yesnoradio\')\n					$value = \'0\';\n				else\n					$value = $pref_info[\'value\'];\n				if (is_array($value)) { // string-ify arrays\n					$values = array_keys($value); // need index values not 1s\n					if ($pref_name == \'adi_search_db_admin_privs\') {\n						$user_priv = safe_field(\'privs\',\'txp_users\',\"name=\'\".doSlash($txp_user).\"\'\");\n						$values = array_unique(array_merge($values,array(1,$user_priv))); // can\'t kill off Publisher or self\n					}\n					$value = implode(\',\',$values);\n				}\n				// some values not allowed to be blank, reset to default\n				if ((!$pref_info[\'blank\']) && (trim($value) == \'\'))\n						$value = $adi_search_db_prefs[$pref_name][\'value\'];\n				$result &= adi_search_db_pref($pref_name,$value);\n			}\n		}\n		return $result;\n	}\n\n}\n\nfunction adi_search_db_options($event,$step) {\n// adi_search_db options page\n	global $adi_search_db_debug,$adi_search_db_url,$adi_search_db_plugin_status;\n\n	$message = \'\';\n\n	// dance steps\n	if ($step == \'textpack\') {\n		$adi_textpack = file_get_contents($adi_search_db_url[\'textpack\']);\n		if ($adi_textpack) {\n			$result = install_textpack($adi_textpack);\n			$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n			$textarray = load_lang(LANG); // load in new strings\n		}\n		else\n			$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n	}\n	else if ($step == \'update_prefs\') {\n		$result = adi_search_db_update_prefs(\'options\');\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_search_db - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// options\n	echo tag(\n		tag(\'adi_search_db \'.gTxt(\'plugin_prefs\'),\'h2\')\n			// link to file list tab\n			.graf(href(gTxt(\'adi_search_db\'),\'?event=adi_search_db_admin\'))\n			// prefs\n			.adi_search_db_pref_inputs($event,\'options\')\n			// textpack links\n			.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.a.\'step=textpack\'),\' style=\"margin-top:3em\"\')\n			.graf(href(gTxt(\'adi_textpack_online\'),$adi_search_db_url[\'textpack_download\']))\n			.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_search_db_url[\'textpack_feedback\']))\n		,\'div\'\n		,\' style=\"text-align:center; margin-bottom:3em\"\'\n	);\n\n}\n', '2f9ad7b0b25267d6c4f0647f0f281164', '', '', 3, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_title', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Abbreviate Titles', '<h1><strong>adi_title</strong> &#8211; Abbreviate Titles</h1>\n\n<p>This plugin will output an abbreviated version of the current article&#8217;s title.</p>\n\n<h2><strong>adi_title tag</strong></h2>\n\n<p>Use <code>&lt;txp:adi_title /&gt;</code> to replace <code>&lt;txp:title /&gt;</code>.</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>separator=&quot;string&quot;</code></p>\n\n<p>specifies the string that delineates the slices. Default = “ “, i.e. a slice = a word.</p>\n\n<p><code>offset=&quot;n&quot;</code></p>\n\n<ul>\n	<li>if positive, then it defines the number of slices to be skipped at the start of the title.</li>\n	<li>if negative, then the sequence will start that far from the end of the title.</li>\n</ul>\n\n<p>Default = 0, i.e. not used.</p>\n\n<p><code>length=&quot;n&quot;</code></p>\n\n<ul>\n	<li>if specified and positive, then it defines the number of slices to output.</li>\n	<li>if specified and negative, then the sequence will stop that many slices from the end of the title.</li>\n</ul>\n\n<p>Default is everything up until the end of the title.</p>\n\n<p><code>append=&quot;string&quot;</code></p>\n\n<p>specifies a string to be appended to the end of the <em>abbreviated</em> title. Default = “ &#8230;”.</p>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the link. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- the <span class=\"caps\">CSS</span> class to be used on the wraptag.  Default = &#8220;&#8221; (no class).</p>\n\n<p><code>html_id=&quot;id name&quot;</code></p>\n\n<p>- the <span class=\"caps\">HTML</span> ID to be used on the wraptag.  Default = &#8220;&#8221; (no <span class=\"caps\">HTML</span> id).</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p><code>&lt;txp:adi_title /&gt;</code></p>\n\n<p>- will output the title unchanged</p>\n\n<p><code>&lt;txp:adi_title length=&quot;2&quot; /&gt;</code></p>\n\n<p>- output the first 2 words<br />\ne.g. <em>“This title is a bit too long”</em> becomes <em>“This title &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;-3&quot; /&gt;</code></p>\n\n<p>- remove the last 3 words<br />\ne.g. <em>“This title could be chopped at the end”</em> becomes <em>“This title could be chopped &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title offset=&quot;1&quot; /&gt;</code></p>\n\n<p>- skip the first word<br />\ne.g. <em>“The One”</em> becomes <em>“One &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title offset=&quot;-2&quot; /&gt;</code></p>\n\n<p>- output the last 2 words<br />\ne.g. <em>“Not The End”</em> becomes <em>“The End &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;3&quot; offset=&quot;3&quot; /&gt;</code></p>\n\n<p>- skip the first 3 words &amp; output 3 words of what&#8217;s left<br />\ne.g.  <em>“This title has a nice middle bit”</em> becomes <em>“a nice middle &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;-2&quot; offset=&quot;4&quot; /&gt;</code></p>\n\n<p>- skip the first 4 words &amp; output the rest, stopping 2 words from the end<br />\ne.g. <em>“The best bit is somewhere near the end”</em> becomes <em>“somewhere near &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;2&quot; offset=&quot;-4&quot; /&gt;</code></p>\n\n<p>- output 2 words, starting 4 from the end<br />\ne.g. <em>“Once upon a time, in a land far away”</em> to <em>“a land &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;-2&quot; offset=&quot;-5&quot; /&gt;</code></p>\n\n<p>output words starting 5 from the end &amp; finishing 2 from the end<br />\ne.g. <em>“There&#8217;re not too many examples listed here”</em> becomes <em>“too many examples &#8230;”</em></p>\n\n<p><code>&lt;txp:adi_title length=&quot;5&quot; append=&quot;!&quot; /&gt;</code></p>\n\n<p>output the first 5 words &amp; append exclamation mark<br />\ne.g.  <em>“I bet you can&#8217;t guess what this says”</em> becomes <em>“I bet you can&#8217;t guess!”</em></p>\n\n<p><code>&lt;txp:adi_title offset=&quot;1&quot; append=&quot;&quot; separator=&quot;-&quot; /&gt;</code></p>\n\n<p>use a hyphen as the separator &amp; skip the first slice<br />\ne.g. <em>“Good morning Smithers-Jones”</em> becomes <em>“Jones”</em></p>\n\n<p><code>&lt;txp:adi_title offset=&quot;1&quot; length=&quot;-1&quot; append=&quot;ed&quot;/&gt;</code></p>\n\n<p>remove the first &amp; last words and append a string<br />\ne.g. <em>“Lose top and tail please”</em> becomes <em>“top and tailed”</em></p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=27672\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_title\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_title - Abbreviate Titles\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- TXP 4.6 tag registration\n			- new attributes: wraptag, class & html_id\n			- fix to ignore appended string if title not changed\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_title\')\n	;\n}\n\nfunction adi_title($atts) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'length\'	=> \'\',		// number of chunks\n		\'separator\'	=> \' \',		// chunk separator\n		\'offset\'	=> \'0\',		// chunk offset\n		\'append\'	=> \' ...\',	// string to append to truncated title\n		\'wraptag\'	=> \'\',		// the wraptag\n		\'class\'		=> \'\',		// class for wraptag\n		\'html_id\'	=> \'\',		// HTML id for wraptag\n		\'debug\'		=> \'\',\n		),$atts));\n\n	empty($debug) ?\n		$title = $thisarticle[\'title\'] :\n		$title = $debug;\n\n	$array = explode($separator,$title);\n\n	empty($length) ?\n		$array = array_slice($array,$offset) :\n		$array = array_slice($array,$offset,$length);\n\n	$new_title = implode($separator,$array);\n\n	if ($new_title == $title)\n		 $append = \'\';\n\n	return doTag(trim($new_title.\"$append\"),$wraptag,$class,\'\',$html_id);\n\n}\n\n', '\n/*\n	adi_title - Abbreviate Titles\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- TXP 4.6 tag registration\n			- new attributes: wraptag, class & html_id\n			- fix to ignore appended string if title not changed\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_title\')\n	;\n}\n\nfunction adi_title($atts) {\n	global $thisarticle;\n\n	extract(lAtts(array(\n		\'length\'	=> \'\',		// number of chunks\n		\'separator\'	=> \' \',		// chunk separator\n		\'offset\'	=> \'0\',		// chunk offset\n		\'append\'	=> \' ...\',	// string to append to truncated title\n		\'wraptag\'	=> \'\',		// the wraptag\n		\'class\'		=> \'\',		// class for wraptag\n		\'html_id\'	=> \'\',		// HTML id for wraptag\n		\'debug\'		=> \'\',\n		),$atts));\n\n	empty($debug) ?\n		$title = $thisarticle[\'title\'] :\n		$title = $debug;\n\n	$array = explode($separator,$title);\n\n	empty($length) ?\n		$array = array_slice($array,$offset) :\n		$array = array_slice($array,$offset,$length);\n\n	$new_title = implode($separator,$array);\n\n	if ($new_title == $title)\n		 $append = \'\';\n\n	return doTag(trim($new_title.\"$append\"),$wraptag,$class,\'\',$html_id);\n\n}\n\n', '9cea2eb6d76c2dead48f83fb8bca578e', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_variables', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.7', 'Manage TXP variables', '<h1><strong>adi_variables</strong> &#8211; Manage <span class=\"caps\">TXP</span> variables</h1>\n\n<p>This plugin provides:</p>\n\n<ul>\n	<li>a new Variables tab under Content to manage your variables</li>\n	<li>a new form, <i>adi_variables</i>, to store the variables</li>\n</ul>\n\n<h2><strong>Installation</strong></h2>\n\n<p>A new form, called <i>adi_variables</i>, will be installed when you visit the Variables tab for the first time.</p>\n\n<p>Delete/edit the example variables and/or add your own.</p>\n\n<p>To bring them into operation put this line of code:</p>\n\n<p><code>&lt;txp:output_form form=&quot;adi_variables&quot; /&gt;</code></p>\n\n<p>at the top of all your web pages.</p>\n\n<h2><strong>Admin tab</strong></h2>\n\n<p>All users will see the Variables tab under Content.  This provides:</p>\n\n<ul>\n	<li>a list of variables &amp; their current values</li>\n	<li>the ability to add &amp; delete variables</li>\n	<li>options to make variables Admin or Public</li>\n	<li>drag &amp; drop reordering*</li>\n	<li>an admin function to take a backup copy of the <i>adi_variables</i> form in <i>adi_variables_copy</i></li>\n</ul>\n\n<p>*Note that the variables will still be in two groups &#8211; Admin or Public.</p>\n\n<h2><strong>Admin &amp; Public</strong></h2>\n\n<p>Because <span class=\"caps\">TXP</span> variables have been implemented as a tag and hence used in forms/pages etc, the distinction between ‘Admin’ and ‘Public’ kicks in a long way down the food chain. Basically, anybody apart from Staff Writer &amp; Freelancer has access to Forms &#8211; so it&#8217;s only these two user privs that can sensibly be restricted.</p>\n\n<p>Admin users can add, delete, rename or update any of the variables.</p>\n\n<p>Public users (i.e. Staff Writer &amp; Freelancer) have visibility of all variables but can only update values of the ‘Public’ ones.</p>\n\n<p>By making variables ‘Public’, you&#8217;re giving Staff Writer &amp; Freelancer users the ability to modify them. Normally they wouldn&#8217;t be able to edit the forms containing the <code>&lt;txp:variable /&gt;</code> entries. They still, however, can&#8217;t delete, rename or add variables.</p>\n\n<p>It is possible, however, to restrict access to the Variables tab by changing the privilege settings in plugin options.</p>\n\n<h2><strong>Textpack</strong></h2>\n\n<p>To install the Textpack, go to the plugin&#8217;s Options tab and click on “Install textpack”.  This will copy &amp; install it from a remote server. The number of language strings installed for your language will be displayed.</p>\n\n<p>If the Textpack installation fails (possibly due to an error accessing the remote site), the alternative is to click the <a href=\"http://www.greatoceanmedia.com.au/textpack\" rel=\"nofollow\">Textpack also available online</a> link.  This will take you to a website where the Textpack can be manually copied &amp; pasted into the <span class=\"caps\">TXP</span> Admin &#8211; Language tab.</p>\n\n<p>Additions and corrections to the Textpack are welcome &#8211; please use the <a href=\"http://www.greatoceanmedia.com.au/textpack/?plugin=adi_variables\" rel=\"nofollow\">Textpack feedback</a> form.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=28720\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_variables\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_variables - Manage TXP variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.7		- minor updates for TXP 4.6\n	0.6		- TXP 4.5+ only\n			- tested on TXP 4.6\n			- plugin privileges in options\n			- drag & drop reordering\n			- minor UI improvements\n	0.5		- TXP 4.5-ified\n			- tooltips no more\n			- Textpack\n			- code tidy up\n			- fix: update button now not shown for non privileged users when there\'re no public variables\n			- enhancement: variable rename for Admin users\n	0.4		- use update_lastmod() (thanks maniqui)\n			- global variables tidy up\n	0.3.1	- fix: unable to see variables in admin after manual edit (thanks woof)\n	0.3		- adi_variables form converted from single to container <txp:variable> tags\n	0.2		- enhancement: cope with TXP tags in variable values (for johnstephens)\n	0.1		- initial release\n\n*/\n\nif (txpinterface === \'admin\') {\n	global $adi_variables_debug,$adi_variables_txp460;\n\n	$adi_variables_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5\',\'>=\')) return;\n\n	$adi_variables_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	adi_variables_init();\n}\n\nfunction adi_variables_init() {\n// initial set up\n	global $event,$prefs,$textarray,$adi_variables_boundary,$adi_variables_prefs,$adi_variables_outlawed_privs,$adi_variables_admin_privs,$adi_variables_debug,$adi_variables_url;\n\n	$adi_variables_boundary = \'<txp:hide>PUBLIC VARIABLES BELOW, ADMIN VARIABLES ABOVE</txp:hide>\';\n\n	// default preferences\n	$adi_variables_prefs = array(\n		\'adi_variables_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'input\' => \'text_input\'),\n	);\n\n	// privs to be excluded\n	$adi_variables_outlawed_privs = array(0);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_variables_gtxt = array(\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_variable\' => \'Variable\',\n		\'adi_variables\' => \'Variables\',\n		\'adi_variables_already_exists\' => \'Variable already exists\',\n		\'adi_variables_copy_form\' => \'Copy variables form\',\n		\'adi_variables_delete_fail\' => \'Variable not deleted\',\n		\'adi_variables_deleted\' => \'Variable deleted\',\n		\'adi_variables_form_copied\' => \'Variables form copied\',\n		\'adi_variables_form_copy_fail\' => \'Variables form not copied\',\n		\'adi_variables_form_empty\' => \'Variables form empty\',\n		\'adi_variables_none_found\' => \'No variables found\',\n		\'adi_variables_update_fail\' => \'Variable update failed\',\n		\'adi_variables_updated\' => \'Variables updated\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_variables_gtxt;\n\n	// Textpack\n	$adi_variables_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_variables\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_variables_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	/*	User privilege summary:\n		0 - none			- can\'t even login\n		1 - publisher		- full capability\n		2 - manager			- full capability\n		3 - copy editor		- full capability\n		4 - staff writer	- public variable update only\n		5 - freelancer		- public variable update only\n		6 - designer		- full capability\n	*/\n\n	// admin variable update, add/delete, admin buttons (i.e. anybody who can access Forms!)\n	$adi_variables_admin_privs = \'form\'; // see lib/admin_config.php: \'1,2,3,6\' publisher, manager, copy writer, designer\n\n	// register adi_variables tab\n	add_privs(\'adi_variables_admin\',adi_variables_pref(\'adi_variables_privs\'));\n	register_tab(\'content\',\'adi_variables_admin\',gTxt(\'adi_variables\'));\n	register_callback(\'adi_variables_admin\',\'adi_variables_admin\');\n\n	// style\n	if ($event == \'adi_variables_admin\')\n		register_callback(\'adi_variables_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_variables_admin\'))\n		register_callback(\'adi_variables_admin_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_variables_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_variables\',$adi_variables_debug);\n	if ($adi_variables_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_variables\'); // defaults to priv \'1\' only\n		register_callback(\'adi_variables_options\',\'plugin_prefs.adi_variables\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_variables_options\');\n		register_tab(\'extensions\',\'adi_variables_options\',\'adi_variables options\');\n		register_callback(\'adi_variables_options\',\'adi_variables_options\');\n	}\n}\n\nfunction adi_variables_style() {\n// some style\n	echo \'<style type=\"text/css\">\n		/* adi_variables */\n 		form h1 {text-align:center }\n 		.txp-list { width:auto; margin:0 auto }\n 		.txp-list th, .txp-list td { padding:0.5em }\n 		form div { margin-top:1.5em; text-align:center }\n 		.adi_td_center { text-align:center }\n 		td.adi_td_center { font-size:120%; line-height:1.4 }\n 		.adi_variables_prefs { margin-top:3em }\n 		.adi_variables_drag_drop { display:none } /* revealed by jQuery later */\n	</style>\';\n}\n\nfunction adi_variables_install($mode=\'\') {\n// create vanilla adi_variables form\n	$form = <<<EOF\n<txp:variable name=\"example-admin\" value=\"Only admin can change this\" />\n<txp:hide>PUBLIC VARIABLES BELOW, ADMIN VARIABLES ABOVE</txp:hide>\n<txp:variable name=\"example-public1\" value=\"Anybody can change this one\" />\n<txp:variable name=\"example-public2\" value=\"Anybody can change this too\" />\nEOF;\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) // install default form\n		return safe_insert(\'txp_form\',\n			\"name=\'adi_variables\',\n			type=\'misc\',\n			Form=\'\".doSlash($form).\"\'\"\n		);\n	else if ($mode == \'copy\') { // copy adi_variables form to adi_variables_copy CONSOLIDATE CODE\n		$raw = adi_variables_read_form(); // read form\n		if (empty($raw))\n			return FALSE;\n		if (safe_field(\'name\', \'txp_form\', \"name=\'adi_variables_copy\'\")) // update existing form copy\n			return safe_update(\'txp_form\',\n				\"Form=\'\".doSlash($raw[\'Form\']).\"\'\",\n				\"name=\'adi_variables_copy\'\"\n			);\n		else // add new form copy\n			return safe_insert(\'txp_form\',\n				\"name=\'adi_variables_copy\',\n				type=\'misc\',\n				Form=\'\".doSlash($raw[\'Form\']).\"\'\"\n			);\n	}\n}\n\nfunction adi_variables_read_form() {\n// read in adi_variables form\n	$rs = safe_query(\'SELECT Form FROM \'.safe_pfx(\'txp_form\').\" WHERE name=\'adi_variables\'\");\n	if ($rs)\n		return nextRow($rs); // $a = array(\"Form\' => \'<txp:variable name=\"example_admin\" ..... )\n	else\n		return array();\n}\n\nfunction adi_variables_validate($txp_var_tag) {\n// v0.1 format: <txp:variable name=\"var\" value=\"valuable\" />\n// v0.2 format: <txp:variable name=\"var\" value=\'valuable\' />\n// v0.3+ format: <txp:variable name=\"var\">valuable</txp:variable>\n	$txp_var_tag = trim($txp_var_tag); // remove extraneous whitespace\n	$old_1_2_valid = preg_match(\'@^<txp:variable\\ @\',$txp_var_tag); // line starts \"<txp:variable \"\n	$old_1_2_valid = $old_1_2_valid && preg_match(\'@/>@\',$txp_var_tag); // line ends \"/>\" (not a proper \"ends with\" match!)\n	$new_3_valid = preg_match(\'@^<txp:variable\\ @\',$txp_var_tag); // line starts \"<txp:variable \"\n	$new_3_valid = $new_3_valid && preg_match(\'@</txp:variable>$@\',$txp_var_tag); // line ends \"</txp:variable>\" (not a proper \"ends with\" match!)\n	return $old_1_2_valid || $new_3_valid;\n}\n\nfunction adi_variables_get_vars($lines) {\n// extract variables\n	$vars = array();\n	$public = FALSE;\n	foreach ($lines as $index => $line) {\n		$ok = adi_variables_validate($line);\n		if (preg_match(\'@^(<txp:hide>PUBLIC)@\',$line)) $public = TRUE; // look for demarcation\n		if ($ok) {\n			$var = adi_variables_extract($line);\n			$var[\'public\'] = $public;\n			$vars[] = $var;\n		}\n	}\n	return $vars;\n}\n\nfunction adi_variables_extract($line) {\n// extract variables & values from adi_variables form\n// v0.1 format: <txp:variable name=\"var\" value=\"valuable\" />\n// v0.2 format: <txp:variable name=\"var\" value=\'valuable\' />\n// v0.3+ format: <txp:variable name=\"var\">valuable</txp:variable>\n// check 0.3 format first then 0.1 then 0.2 format (copes with 0.1 -> 0.2 -> 0.3+ migration)\n	$var = array();\n	$line = trim($line);  // strip whitespace - especially LF/CR at end\n	if (adi_variables_validate($line))\n		if (preg_match(\'@</txp:variable>$@\',$line)) { // line ends \"</txp:variable>\" - 0.3 format\n			$diced = explode(\'\"\',$line);\n			$var[\'name\'] = $diced[1]; // var name will be second slice\n			$var[\'format\'] = \'0.3\';\n			$value = substr($line,22+strlen($var[\'name\'])); // remove <txp:variable name=\"var\"> from front\n			$var[\'value\'] = substr($value,0,-15); // remove </txp:variable> from end\n		}\n		else { // 0.1 or 0.2 format\n			$diced = explode(\'\"\',$line); // use double quote delimiter first (v0.1)\n			if (!empty($diced)) { // check that there\'s something there\n				$var[\'name\'] = $diced[1]; // var name will be second slice\n				if (count($diced) != 5) { // must be new format (v0.2)\n					$diced = explode(\"\'\",$line); // single quote delimiter\n					$var[\'value\'] = $diced[1]; // new format, value will be now be second slice\n					$var[\'format\'] = \'0.2\';\n				}\n				else { // old format, value will be fourth slice\n					$var[\'value\'] = $diced[3];\n					$var[\'format\'] = \'0.1\';\n				}\n			}\n		}\n	return $var;\n}\n\nfunction adi_variables_display_settings($vars) {\n// display table for updating/adding variables\n	global $adi_variables_admin_privs;\n\n	$out = \'\';\n\n	foreach ($vars as $index => $var) {\n		$out .= \'<tr>\';\n		$out .= tda(tag(\'&#x2195;\',\'span\',\' class=\"adi_variables_drag_drop\"\'),\' class=\"adi_td_center\"\'); // drag&drop symbol\n		if (has_privs($adi_variables_admin_privs)) // see everything, do anything\n			$out .=\n				tda(finput(\"text\",\"adi_variables[$index][name]\",$var[\'name\'],\'\',\'\',\'\',20))\n				.tda(finput(\"text\",\"adi_variables[$index][value]\",$var[\'value\'],\'\',\'\',\'\',80))\n				.tda(checkbox(\"adi_variables[$index][public]\",\"1\",$var[\'public\']),\' class=\"adi_td_center\"\')\n				.tda(adi_variables_delete_button($var[\'name\']),\' class=\"adi_td_center\"\');\n		else // restricted capabilities: no rename, no delete, edit public variable values only\n			if ($var[\'public\']) // display variable\n				$out .=\n					tda(\n						$var[\'name\']\n						.hinput(\"adi_variables[$index][name]\",$var[\'name\']) // hidden name\n					)\n					.tda(\n						finput(\"text\",\"adi_variables[$index][value]\",$var[\'value\'],\'\',\'\',\'\',80)\n						.hinput(\"adi_variables[$index][public]\",$var[\'public\']) // hidden public setting\n					);\n			else // hide variable input but display variable name/value\n				$out .=\n					tda(\n						$var[\'name\']\n						.hinput(\"adi_variables[$index][name]\",$var[\'name\']) // hidden name\n					)\n					.tda(\n						(empty($var[\'value\']) ? sp : htmlspecialchars($var[\'value\']))\n						.hinput(\"adi_variables[$index][value]\",$var[\'value\']) // hidden value\n						.hinput(\"adi_variables[$index][public]\", $var[\'public\']) // hidden public setting\n					);\n		$out .= \'</tr>\';\n	}\n\n	if (has_privs($adi_variables_admin_privs)) // restrict access to adding new variables\n		$out .=\n			tr(\n				tag(\'&#43;\',\'td\',\' class=\"adi_variables_plus adi_td_center\"\') // plus\n				.tda(finput(\"text\",\"adi_variables[new][name]\",\'\',\'\',\'\',\'\',20))\n				.tda(finput(\"text\",\"adi_variables[new][value]\",\'\',\'\',\'\',\'\',80))\n				.tda(checkbox(\"adi_variables[new][public]\", \"1\", \'\'),\' class=\"adi_td_center\"\')\n				.tda(sp)\n			);\n\n	return $out;\n}\n\nfunction adi_variables_delete_button($var) {\n// delete button\n	global $adi_variables_txp460;\n\n	$event = \'adi_variables_admin\';\n	$step = \'delete\';\n\n	if ($adi_variables_txp460)\n		return\n			href(\n				span(\'Delete\',\' class=\"ui-icon ui-icon-trash\"\')\n				,array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'adi_variable_delete\' => $var,\n					\'_txp_token\'    => form_token(),\n				)\n				,array(\n					\'class\'       => \'dlink destroy\',\n					\'title\'       => gTxt(\'delete\'),\n					\'data-verify\' => gTxt(\'confirm_delete_popup\'),\n				)\n			);\n	else\n		return\n			\'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'adi_variable_delete=\'.$var\n			.\'\" class=\"dlink\" title=\"\'.gTxt(\'delete\').\'\" onclick=\"return verify(\\\'\'\n			.gTxt(\'confirm_delete_popup\')\n			.\'\\\')\">\'\n			.\'&#215;\'\n			.\'</a>\';\n}\n\nfunction adi_variables_update($form_contents) {\n// update adi_variables form in db\n	global $adi_variables_debug;\n\n	if (safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) {\n		$res = safe_update(\'txp_form\',\n				\"Form=\'\".doSlash($form_contents).\"\'\",\n				\"name=\'adi_variables\'\"\n				);\n	}\n	else // Form not found\n		return FALSE;\n	return $res;\n}\n\nfunction adi_variables_variable_tag($name,$value) {\n// generate TXP variable tag\n	return \'<txp:variable name=\"\'.$name.\'\">\'.$value.\'</txp:variable>\'; // container tag - 0.3 format\n}\n\nfunction adi_variables_form_update($variables) {\n// generate new adi_variables form\n	global $adi_variables_boundary;\n\n	$new_form = array();\n	$admin_vars = $public_vars = $ordered_vars = array();\n	if ($variables) {\n		foreach ($variables as $index => $variable) {\n			if ($index == \'new\')\n				if ($variable[\'name\']) {\n					// remove nasties from new variable name\n					$variable[\'name\'] = strip_tags(trim($variable[\'name\'])); // no tags\n					$variable[\'name\'] = str_replace(\' \',\'\',$variable[\'name\']); // no embedded spaces\n					$variable[\'name\'] = str_replace(\'\"\',\'\',$variable[\'name\']); // no double quotes\n					$variable[\'name\'] = str_replace(\"\'\",\'\',$variable[\'name\']); // no single quotes\n				}\n				else\n					continue; // ignore new variable if name blank\n			if (isset($variable[\'public\']))\n				$public_vars[] = $variable;\n			else {\n				$variable[\'public\'] = 0; // coz checkbox don\'t set it\n				$admin_vars[] = $variable;\n			}\n		}\n		// reorder vars: admin first, public last\n		$ordered_vars = array_merge($admin_vars,$public_vars);\n		// generate new form\n		$public = FALSE;\n		foreach ($ordered_vars as $index => $variable) {\n			if (!$public && $variable[\'public\']) { // start of public vars found\n				$public = TRUE; // toggle public\n				$new_form[] = $adi_variables_boundary; // demarcation\n			}\n			$new_form[] = adi_variables_variable_tag($variable[\'name\'],$variable[\'value\']);\n		}\n	}\n	return $new_form;\n}\n\nfunction adi_variables_form_delete($delete_me) {\n// remove variable from form\n	global $adi_variables_boundary;\n\n	$new_form = array();\n	$raw = adi_variables_read_form(); // raw data from form\n	$lines = explode(\"\\n\",$raw[\'Form\']); // break up into lines\n	$vars = adi_variables_get_vars($lines); // extract variables\n	// generate new form\n	$public = FALSE;\n	foreach ($vars as $index => $var) {\n		if ($var[\'name\'] == $delete_me)\n			continue;\n		if (!$public && $var[\'public\']) { // start of public vars found\n			$public = TRUE; // toggle public\n			$new_form[] = $adi_variables_boundary; // demarcation\n		}\n		$new_form[] = adi_variables_variable_tag($var[\'name\'],$var[\'value\']);\n	}\n	return $new_form;\n}\n\nfunction adi_variables_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_variables_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_variables_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_variables_prefs))\n			$html = $adi_variables_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_variables_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_variables_debug($event,$step,$something,$raw,$lines,$vars,$posted_vars,$new_form,$public_count) {\n// debug info\n	global $adi_variables_debug;\n\n	echo \"event: \".$event.\", step: \".$step.\", something: \".$something.\", adi_variable_delete: \".gps(\'adi_variable_delete\').br.br;\n	echo \"POSTED DATA:\";\n	dmp($posted_vars);\n	echo \"RAW FORM:\";\n	dmp($raw);\n	echo \"LINE BY LINE:\".br;\n	foreach ($lines as $index => $line) {\n		$line = htmlspecialchars($line); // replace angle brackets so tags not parsed\n		echo \"Line \".$index.\". \".$line.br;\n	}\n	$public = FALSE;\n	echo br.\"ANALYSIS:\".br;\n	foreach ($lines as $index => $line) {\n		$this_line = trim($line);\n		if (empty($this_line))\n			echo \"BLANK LINE\".br;\n		else {\n			$ok = adi_variables_validate($line);\n			$ok ? print \"VAR \" : print \"IGNORE \";\n			if (preg_match(\'@^(<txp:hide>PUBLIC)@\',$line)) $public = TRUE; // look for demarcation\n			if ($ok)\n				$public ? print \'(PUBLIC): \' : print \'(ADMIN): \';\n			$line = htmlspecialchars($line); // replace angle brackets so tags not parsed\n			print $line.br;\n		}\n	}\n	echo br.\"STORED DATA:\";\n	dmp($vars);\n	if ($step == \"update\") {\n		print \"NEW FORM:\";\n		dmp($new_form);\n	}\n	echo \'Public variable count = \'.$public_count.br;\n}\n\nfunction adi_variables_admin_script() {\n// jQuery magic for admin tab\n	global $adi_variables_txp460;\n\n	$ui_script = \'\';\n	if (!$adi_variables_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<!-- adi_variables - drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'#list tbody tr\').css(\'cursor\',\'pointer\'); // make like a pointer\n			$(\'#list .adi_variables_drag_drop\').show(); // reveal drag & drop icon\n			$(\"#list tbody\").sortable();\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_variables_admin($event,$step) {\n// variables tab\n	global $adi_variables_debug,$adi_variables_admin_privs,$adi_variables_txp460;\n\n	$message = \'\';\n	$something = ps(\'something\');\n	$installed = TRUE;\n	$new_form = array();\n\n	$posted_vars = gps(\'adi_variables\');\n	$delete_variable = gps(\'adi_variable_delete\');\n\n	// install adi_variables form if required\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) // form not found\n		if (adi_variables_install())\n			$message = gTxt(\'adi_installed\');\n		else {\n			$message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n			$installed = FALSE;\n		}\n\n	if ($installed) {\n		if ($step == \'update\') {\n			// check for duplicates\n			$duplicates = 0;\n			if (isset($posted_vars[\'new\'])) // non-privileged users can\'t post new variable\n				if ($posted_vars[\'new\'][\'name\']) {\n					$duplicates = -1;\n					foreach ($posted_vars as $var)\n						if ($var[\'name\'] == $posted_vars[\'new\'][\'name\'])\n							$duplicates += 1;\n				}\n			if ($duplicates)\n				$message = array(gTxt(\'adi_variables_already_exists\'),E_ERROR);\n			if ($message == \'\') {\n				$new_form = adi_variables_form_update($posted_vars);\n				$ok = adi_variables_update(join(\"\\n\",$new_form));\n				if ($ok)\n					$message = gTxt(\'adi_variables_updated\');\n				else\n					$message = array(gTxt(\'adi_variables_update_fail\'),E_ERROR);\n				if ($ok)\n					update_lastmod();\n			}\n		}\n		if ($step == \'delete\') {\n			if (has_privs($adi_variables_admin_privs)) { // double check privileges because this operation uses GET VARS\n				$new_form = adi_variables_form_delete($delete_variable);\n				$ok = adi_variables_update(join(\"\\n\",$new_form));\n				if ($ok)\n					$message = gTxt(\'adi_variables_deleted\');\n				else\n					$message = array(gTxt(\'adi_variables_delete_fail\'),E_ERROR);\n				if ($ok)\n					update_lastmod();\n			}\n			else\n				$message = gTxt(\'adi_variables_delete_fail\');\n		}\n		if ($something == \"copy\") {\n			$ok = adi_variables_install(\'copy\');\n			if ($ok)\n				$message = gTxt(\'adi_variables_form_copied\');\n			else\n				$message = array(gTxt(\'adi_variables_form_copy_fail\'),E_ERROR);\n		}\n\n		// read contents of form\n		$raw = adi_variables_read_form();\n		if (empty($raw)) // shouldn\'t ever happen coz adi_variables_install should\'ve kicked in\n			gTxt(\'form_not_found\');\n\n		// break up into individual lines\n		$lines = explode(\"\\n\",$raw[\'Form\']);\n\n		// extract variables & values from form\n		$vars = adi_variables_get_vars($lines);\n		if ((trim($raw[\'Form\']) == \'\') || empty($vars)) // form blank OR no vars (i.e. $adi_variables_boundary line only)\n			$message = array(gTxt(\'adi_variables_none_found\'),E_WARNING);\n\n		// count public variables\n		$public_count = 0;\n		foreach ($vars as $variable)\n			if ($variable[\'public\'])\n				$public_count += 1;\n	}\n\n	// generate page\n	pagetop(\'adi_variables admin\',$message);\n	if ($adi_variables_debug)\n		adi_variables_debug($event,$step,$something,$raw,$lines,$vars,$posted_vars,$new_form,$public_count);\n\n	if ($installed) {\n		// only allowed update button if privileged or there\'re public variables present\n		if (has_privs($adi_variables_admin_privs) || $public_count)\n			$update_button =\n				tag(\n					($adi_variables_txp460 ? fInput(\"submit\",\"update\",gTxt(\'Save\'),\'publish\') : fInput(\"submit\",\"update\",gTxt(\'Update\'),\"smallerbox\")) // 4.6 has publish style\n					.eInput(\"adi_variables_admin\")\n					.sInput(\"update\")\n					,\'div\'\n				);\n		else\n			$update_button = \'\';\n		// output table & input form\n		echo\n			form(\n				hed(gTxt(\'adi_variables\'),1,\' class=\"txp-heading\"\')\n				.startTable(\'list\',\'\',\'txp-list\')\n				.tag(\n					tr(\n						hcell(sp) // spacer for plus/drag&drop\n						.hcell(gTxt(\'name\'))\n						.hcell(gTxt(\'value\'))\n						.(has_privs($adi_variables_admin_privs) ? hcell(gTxt(\'public\'),\'\',\' class=\"adi_td_center\"\') : \'\') // hide Public heading if required\n						.(has_privs($adi_variables_admin_privs) ? hcell(sp) : \'\') // filler for delete buttons\n					)\n					,\'thead\'\n				)\n				.adi_variables_display_settings($vars)\n				.endTable()\n				.$update_button\n			);\n		// admin functions\n		if (has_privs($adi_variables_admin_privs)) { // restrict access to admin functions\n		    echo form(\n				tag(\n					hInput(\'something\',\'copy\')\n		        	.fInput(\'submit\',\'do_something\',gTxt(\'adi_variables_copy_form\'),\'smallerbox\')\n			        .eInput(\'adi_variables_admin\')\n			        .sInput(\'admin\')\n					,\'div\'\n					,\' class=\"adi_variables_prefs\"\'\n				)\n			);\n		}\n		// link to form\n// 		if ($adi_variables_txp460)\n// 			echo\n// 				graf(\n// 					href(\n// 						gTxt(\'view\')\n// 						,array(\n// 							\'event\' => \'form\',\n// 							\'step\' => \'form_edit\',\n// 							\'name\' => \'adi_variables\',\n// 							\'_txp_token\' => form_token()\n// 						)\n// 					)\n// 					,\' class=\"adi_td_center\"\'\n// 				);\n	}\n}\n\nfunction adi_variables_options($event,$step) {\n// plugin options page\n	global $txp_groups,$adi_variables_prefs,$adi_variables_outlawed_privs,$adi_variables_debug,$adi_variables_url,$textarray;\n\n	$message = \'\';\n\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_variables_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		// run through all prefs\n		foreach ($adi_variables_prefs as $name => $data) {\n			if (array_key_exists($name,$_POST))\n				$value = $_POST[$name];\n			else if ($data[\'input\'] == \'yesnoradio\')\n				$value = \'0\';\n			else\n				$value = $data[\'value\'];\n			if (is_array($value)) { // string-ify arrays\n				$value = array_keys($value); // need index values not 1s\n				if ($name == \'adi_variables_privs\') {\n					if (array_search(1,$value) === FALSE) // can\'t kill off Publisher\n						array_unshift($value,1);\n				}\n				$value = implode(\',\',$value);\n			}\n			// some values not allowed to be blank, reset to default\n			$non_blanks = array();\n			foreach ($non_blanks as $non_blank)\n				if (($name == $non_blank) && (trim($value) == \'\'))\n					$value = $adi_variables_prefs[$non_blank][\'value\'];\n			$result &= adi_variables_pref($name,$value);\n		}\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_variables - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// plugin priv checkboxes\n	$plugin_privs = do_list(adi_variables_pref(\'adi_variables_privs\'));\n	$priv_checkboxes = \'\';\n	foreach ($txp_groups as $index => $group)\n		if (array_search($index,$adi_variables_outlawed_privs) === FALSE) // ignore the outlaws\n			$priv_checkboxes .= tag(gTxt($group).checkbox2(\"adi_variables_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_variables_privs\').sp.sp,\'label\');\n\n	// options\n	echo tag(\n		tag(\'adi_variables \'.gTxt(\'plugin_prefs\'),\'h1\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_variables_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_variables_url[\'textpack_feedback\']))\n		// plugin privs\n		.form(\n			graf(\n				gTxt(\'adi_plugin_privs\')\n				.sp.sp.sp\n				.$priv_checkboxes\n				,\' style=\"margin-top:2em\"\'\n			)\n			.fInput(\'submit\',\'do_something\',gTxt(\'update\'),\'smallerbox\')\n			.eInput($event)\n			.sInput(\"update_prefs\")\n		)\n		// link to plugin admin tab\n		.graf(href(gTxt(\'adi_variables\'),\'?event=adi_variables_admin\'))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_variables_debug) {\n		echo \"<p>Event: \".$event.\", Step: \".$step.\"</p>\";\n		echo \'$adi_textpack (\'.$adi_variables_url[\'textpack\'].\'):\';\n		$adi_textpack = file_get_contents($adi_variables_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', '\n/*\n	adi_variables - Manage TXP variables\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.7		- minor updates for TXP 4.6\n	0.6		- TXP 4.5+ only\n			- tested on TXP 4.6\n			- plugin privileges in options\n			- drag & drop reordering\n			- minor UI improvements\n	0.5		- TXP 4.5-ified\n			- tooltips no more\n			- Textpack\n			- code tidy up\n			- fix: update button now not shown for non privileged users when there\'re no public variables\n			- enhancement: variable rename for Admin users\n	0.4		- use update_lastmod() (thanks maniqui)\n			- global variables tidy up\n	0.3.1	- fix: unable to see variables in admin after manual edit (thanks woof)\n	0.3		- adi_variables form converted from single to container <txp:variable> tags\n	0.2		- enhancement: cope with TXP tags in variable values (for johnstephens)\n	0.1		- initial release\n\n*/\n\nif (txpinterface === \'admin\') {\n	global $adi_variables_debug,$adi_variables_txp460;\n\n	$adi_variables_debug = 0;\n\n	if (!version_compare(txp_version,\'4.5\',\'>=\')) return;\n\n	$adi_variables_txp460 = (version_compare(txp_version,\'4.6-dev\',\'>=\'));\n\n	adi_variables_init();\n}\n\nfunction adi_variables_init() {\n// initial set up\n	global $event,$prefs,$textarray,$adi_variables_boundary,$adi_variables_prefs,$adi_variables_outlawed_privs,$adi_variables_admin_privs,$adi_variables_debug,$adi_variables_url;\n\n	$adi_variables_boundary = \'<txp:hide>PUBLIC VARIABLES BELOW, ADMIN VARIABLES ABOVE</txp:hide>\';\n\n	// default preferences\n	$adi_variables_prefs = array(\n		\'adi_variables_privs\' => array(\'value\' => \'1,2,3,4,5,6\', \'input\' => \'text_input\'),\n	);\n\n	// privs to be excluded\n	$adi_variables_outlawed_privs = array(0);\n\n# --- BEGIN PLUGIN TEXTPACK ---\n	$adi_variables_gtxt = array(\n		\'adi_install_fail\' => \'Unable to install\',\n		\'adi_installed\' => \'Installed\',\n		\'adi_textpack_fail\' => \'Textpack installation failed\',\n		\'adi_textpack_feedback\' => \'Textpack feedback\',\n		\'adi_textpack_online\' => \'Textpack also available online\',\n		\'adi_plugin_privs\' => \'Plugin privileges:\',\n		\'adi_variable\' => \'Variable\',\n		\'adi_variables\' => \'Variables\',\n		\'adi_variables_already_exists\' => \'Variable already exists\',\n		\'adi_variables_copy_form\' => \'Copy variables form\',\n		\'adi_variables_delete_fail\' => \'Variable not deleted\',\n		\'adi_variables_deleted\' => \'Variable deleted\',\n		\'adi_variables_form_copied\' => \'Variables form copied\',\n		\'adi_variables_form_copy_fail\' => \'Variables form not copied\',\n		\'adi_variables_form_empty\' => \'Variables form empty\',\n		\'adi_variables_none_found\' => \'No variables found\',\n		\'adi_variables_update_fail\' => \'Variable update failed\',\n		\'adi_variables_updated\' => \'Variables updated\',\n	);\n# --- END PLUGIN TEXTPACK ---\n\n	// update $textarray\n	$textarray += $adi_variables_gtxt;\n\n	// Textpack\n	$adi_variables_url = array(\n		\'textpack\' => \'http://www.greatoceanmedia.com.au/files/adi_textpack.txt\',\n		\'textpack_download\' => \'http://www.greatoceanmedia.com.au/textpack/download\',\n		\'textpack_feedback\' => \'http://www.greatoceanmedia.com.au/textpack/?plugin=adi_variables\',\n	);\n	if (strpos($prefs[\'plugin_cache_dir\'],\'adi\') !== FALSE) // use Adi\'s local version\n		$adi_variables_url[\'textpack\'] = $prefs[\'plugin_cache_dir\'].\'/adi_textpack.txt\';\n\n	/*	User privilege summary:\n		0 - none			- can\'t even login\n		1 - publisher		- full capability\n		2 - manager			- full capability\n		3 - copy editor		- full capability\n		4 - staff writer	- public variable update only\n		5 - freelancer		- public variable update only\n		6 - designer		- full capability\n	*/\n\n	// admin variable update, add/delete, admin buttons (i.e. anybody who can access Forms!)\n	$adi_variables_admin_privs = \'form\'; // see lib/admin_config.php: \'1,2,3,6\' publisher, manager, copy writer, designer\n\n	// register adi_variables tab\n	add_privs(\'adi_variables_admin\',adi_variables_pref(\'adi_variables_privs\'));\n	register_tab(\'content\',\'adi_variables_admin\',gTxt(\'adi_variables\'));\n	register_callback(\'adi_variables_admin\',\'adi_variables_admin\');\n\n	// style\n	if ($event == \'adi_variables_admin\')\n		register_callback(\'adi_variables_style\',\'admin_side\',\'head_end\');\n\n	// script\n	if (strstr($event,\'adi_variables_admin\'))\n		register_callback(\'adi_variables_admin_script\',\'admin_side\',\'head_end\');\n\n	// plugin options\n	$adi_variables_plugin_status = fetch(\'status\',\'txp_plugin\',\'name\',\'adi_variables\',$adi_variables_debug);\n	if ($adi_variables_plugin_status) { // proper install - options under Plugins tab\n		add_privs(\'plugin_prefs.adi_variables\'); // defaults to priv \'1\' only\n		register_callback(\'adi_variables_options\',\'plugin_prefs.adi_variables\');\n	}\n	else { // txpdev - options under Extensions tab\n		add_privs(\'adi_variables_options\');\n		register_tab(\'extensions\',\'adi_variables_options\',\'adi_variables options\');\n		register_callback(\'adi_variables_options\',\'adi_variables_options\');\n	}\n}\n\nfunction adi_variables_style() {\n// some style\n	echo \'<style type=\"text/css\">\n		/* adi_variables */\n 		form h1 {text-align:center }\n 		.txp-list { width:auto; margin:0 auto }\n 		.txp-list th, .txp-list td { padding:0.5em }\n 		form div { margin-top:1.5em; text-align:center }\n 		.adi_td_center { text-align:center }\n 		td.adi_td_center { font-size:120%; line-height:1.4 }\n 		.adi_variables_prefs { margin-top:3em }\n 		.adi_variables_drag_drop { display:none } /* revealed by jQuery later */\n	</style>\';\n}\n\nfunction adi_variables_install($mode=\'\') {\n// create vanilla adi_variables form\n	$form = <<<EOF\n<txp:variable name=\"example-admin\" value=\"Only admin can change this\" />\n<txp:hide>PUBLIC VARIABLES BELOW, ADMIN VARIABLES ABOVE</txp:hide>\n<txp:variable name=\"example-public1\" value=\"Anybody can change this one\" />\n<txp:variable name=\"example-public2\" value=\"Anybody can change this too\" />\nEOF;\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) // install default form\n		return safe_insert(\'txp_form\',\n			\"name=\'adi_variables\',\n			type=\'misc\',\n			Form=\'\".doSlash($form).\"\'\"\n		);\n	else if ($mode == \'copy\') { // copy adi_variables form to adi_variables_copy CONSOLIDATE CODE\n		$raw = adi_variables_read_form(); // read form\n		if (empty($raw))\n			return FALSE;\n		if (safe_field(\'name\', \'txp_form\', \"name=\'adi_variables_copy\'\")) // update existing form copy\n			return safe_update(\'txp_form\',\n				\"Form=\'\".doSlash($raw[\'Form\']).\"\'\",\n				\"name=\'adi_variables_copy\'\"\n			);\n		else // add new form copy\n			return safe_insert(\'txp_form\',\n				\"name=\'adi_variables_copy\',\n				type=\'misc\',\n				Form=\'\".doSlash($raw[\'Form\']).\"\'\"\n			);\n	}\n}\n\nfunction adi_variables_read_form() {\n// read in adi_variables form\n	$rs = safe_query(\'SELECT Form FROM \'.safe_pfx(\'txp_form\').\" WHERE name=\'adi_variables\'\");\n	if ($rs)\n		return nextRow($rs); // $a = array(\"Form\' => \'<txp:variable name=\"example_admin\" ..... )\n	else\n		return array();\n}\n\nfunction adi_variables_validate($txp_var_tag) {\n// v0.1 format: <txp:variable name=\"var\" value=\"valuable\" />\n// v0.2 format: <txp:variable name=\"var\" value=\'valuable\' />\n// v0.3+ format: <txp:variable name=\"var\">valuable</txp:variable>\n	$txp_var_tag = trim($txp_var_tag); // remove extraneous whitespace\n	$old_1_2_valid = preg_match(\'@^<txp:variable\\ @\',$txp_var_tag); // line starts \"<txp:variable \"\n	$old_1_2_valid = $old_1_2_valid && preg_match(\'@/>@\',$txp_var_tag); // line ends \"/>\" (not a proper \"ends with\" match!)\n	$new_3_valid = preg_match(\'@^<txp:variable\\ @\',$txp_var_tag); // line starts \"<txp:variable \"\n	$new_3_valid = $new_3_valid && preg_match(\'@</txp:variable>$@\',$txp_var_tag); // line ends \"</txp:variable>\" (not a proper \"ends with\" match!)\n	return $old_1_2_valid || $new_3_valid;\n}\n\nfunction adi_variables_get_vars($lines) {\n// extract variables\n	$vars = array();\n	$public = FALSE;\n	foreach ($lines as $index => $line) {\n		$ok = adi_variables_validate($line);\n		if (preg_match(\'@^(<txp:hide>PUBLIC)@\',$line)) $public = TRUE; // look for demarcation\n		if ($ok) {\n			$var = adi_variables_extract($line);\n			$var[\'public\'] = $public;\n			$vars[] = $var;\n		}\n	}\n	return $vars;\n}\n\nfunction adi_variables_extract($line) {\n// extract variables & values from adi_variables form\n// v0.1 format: <txp:variable name=\"var\" value=\"valuable\" />\n// v0.2 format: <txp:variable name=\"var\" value=\'valuable\' />\n// v0.3+ format: <txp:variable name=\"var\">valuable</txp:variable>\n// check 0.3 format first then 0.1 then 0.2 format (copes with 0.1 -> 0.2 -> 0.3+ migration)\n	$var = array();\n	$line = trim($line);  // strip whitespace - especially LF/CR at end\n	if (adi_variables_validate($line))\n		if (preg_match(\'@</txp:variable>$@\',$line)) { // line ends \"</txp:variable>\" - 0.3 format\n			$diced = explode(\'\"\',$line);\n			$var[\'name\'] = $diced[1]; // var name will be second slice\n			$var[\'format\'] = \'0.3\';\n			$value = substr($line,22+strlen($var[\'name\'])); // remove <txp:variable name=\"var\"> from front\n			$var[\'value\'] = substr($value,0,-15); // remove </txp:variable> from end\n		}\n		else { // 0.1 or 0.2 format\n			$diced = explode(\'\"\',$line); // use double quote delimiter first (v0.1)\n			if (!empty($diced)) { // check that there\'s something there\n				$var[\'name\'] = $diced[1]; // var name will be second slice\n				if (count($diced) != 5) { // must be new format (v0.2)\n					$diced = explode(\"\'\",$line); // single quote delimiter\n					$var[\'value\'] = $diced[1]; // new format, value will be now be second slice\n					$var[\'format\'] = \'0.2\';\n				}\n				else { // old format, value will be fourth slice\n					$var[\'value\'] = $diced[3];\n					$var[\'format\'] = \'0.1\';\n				}\n			}\n		}\n	return $var;\n}\n\nfunction adi_variables_display_settings($vars) {\n// display table for updating/adding variables\n	global $adi_variables_admin_privs;\n\n	$out = \'\';\n\n	foreach ($vars as $index => $var) {\n		$out .= \'<tr>\';\n		$out .= tda(tag(\'&#x2195;\',\'span\',\' class=\"adi_variables_drag_drop\"\'),\' class=\"adi_td_center\"\'); // drag&drop symbol\n		if (has_privs($adi_variables_admin_privs)) // see everything, do anything\n			$out .=\n				tda(finput(\"text\",\"adi_variables[$index][name]\",$var[\'name\'],\'\',\'\',\'\',20))\n				.tda(finput(\"text\",\"adi_variables[$index][value]\",$var[\'value\'],\'\',\'\',\'\',80))\n				.tda(checkbox(\"adi_variables[$index][public]\",\"1\",$var[\'public\']),\' class=\"adi_td_center\"\')\n				.tda(adi_variables_delete_button($var[\'name\']),\' class=\"adi_td_center\"\');\n		else // restricted capabilities: no rename, no delete, edit public variable values only\n			if ($var[\'public\']) // display variable\n				$out .=\n					tda(\n						$var[\'name\']\n						.hinput(\"adi_variables[$index][name]\",$var[\'name\']) // hidden name\n					)\n					.tda(\n						finput(\"text\",\"adi_variables[$index][value]\",$var[\'value\'],\'\',\'\',\'\',80)\n						.hinput(\"adi_variables[$index][public]\",$var[\'public\']) // hidden public setting\n					);\n			else // hide variable input but display variable name/value\n				$out .=\n					tda(\n						$var[\'name\']\n						.hinput(\"adi_variables[$index][name]\",$var[\'name\']) // hidden name\n					)\n					.tda(\n						(empty($var[\'value\']) ? sp : htmlspecialchars($var[\'value\']))\n						.hinput(\"adi_variables[$index][value]\",$var[\'value\']) // hidden value\n						.hinput(\"adi_variables[$index][public]\", $var[\'public\']) // hidden public setting\n					);\n		$out .= \'</tr>\';\n	}\n\n	if (has_privs($adi_variables_admin_privs)) // restrict access to adding new variables\n		$out .=\n			tr(\n				tag(\'&#43;\',\'td\',\' class=\"adi_variables_plus adi_td_center\"\') // plus\n				.tda(finput(\"text\",\"adi_variables[new][name]\",\'\',\'\',\'\',\'\',20))\n				.tda(finput(\"text\",\"adi_variables[new][value]\",\'\',\'\',\'\',\'\',80))\n				.tda(checkbox(\"adi_variables[new][public]\", \"1\", \'\'),\' class=\"adi_td_center\"\')\n				.tda(sp)\n			);\n\n	return $out;\n}\n\nfunction adi_variables_delete_button($var) {\n// delete button\n	global $adi_variables_txp460;\n\n	$event = \'adi_variables_admin\';\n	$step = \'delete\';\n\n	if ($adi_variables_txp460)\n		return\n			href(\n				span(\'Delete\',\' class=\"ui-icon ui-icon-trash\"\')\n				,array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'adi_variable_delete\' => $var,\n					\'_txp_token\'    => form_token(),\n				)\n				,array(\n					\'class\'       => \'dlink destroy\',\n					\'title\'       => gTxt(\'delete\'),\n					\'data-verify\' => gTxt(\'confirm_delete_popup\'),\n				)\n			);\n	else\n		return\n			\'<a href=\"?event=\'.$event.a.\'step=\'.$step.a.\'adi_variable_delete=\'.$var\n			.\'\" class=\"dlink\" title=\"\'.gTxt(\'delete\').\'\" onclick=\"return verify(\\\'\'\n			.gTxt(\'confirm_delete_popup\')\n			.\'\\\')\">\'\n			.\'&#215;\'\n			.\'</a>\';\n}\n\nfunction adi_variables_update($form_contents) {\n// update adi_variables form in db\n	global $adi_variables_debug;\n\n	if (safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) {\n		$res = safe_update(\'txp_form\',\n				\"Form=\'\".doSlash($form_contents).\"\'\",\n				\"name=\'adi_variables\'\"\n				);\n	}\n	else // Form not found\n		return FALSE;\n	return $res;\n}\n\nfunction adi_variables_variable_tag($name,$value) {\n// generate TXP variable tag\n	return \'<txp:variable name=\"\'.$name.\'\">\'.$value.\'</txp:variable>\'; // container tag - 0.3 format\n}\n\nfunction adi_variables_form_update($variables) {\n// generate new adi_variables form\n	global $adi_variables_boundary;\n\n	$new_form = array();\n	$admin_vars = $public_vars = $ordered_vars = array();\n	if ($variables) {\n		foreach ($variables as $index => $variable) {\n			if ($index == \'new\')\n				if ($variable[\'name\']) {\n					// remove nasties from new variable name\n					$variable[\'name\'] = strip_tags(trim($variable[\'name\'])); // no tags\n					$variable[\'name\'] = str_replace(\' \',\'\',$variable[\'name\']); // no embedded spaces\n					$variable[\'name\'] = str_replace(\'\"\',\'\',$variable[\'name\']); // no double quotes\n					$variable[\'name\'] = str_replace(\"\'\",\'\',$variable[\'name\']); // no single quotes\n				}\n				else\n					continue; // ignore new variable if name blank\n			if (isset($variable[\'public\']))\n				$public_vars[] = $variable;\n			else {\n				$variable[\'public\'] = 0; // coz checkbox don\'t set it\n				$admin_vars[] = $variable;\n			}\n		}\n		// reorder vars: admin first, public last\n		$ordered_vars = array_merge($admin_vars,$public_vars);\n		// generate new form\n		$public = FALSE;\n		foreach ($ordered_vars as $index => $variable) {\n			if (!$public && $variable[\'public\']) { // start of public vars found\n				$public = TRUE; // toggle public\n				$new_form[] = $adi_variables_boundary; // demarcation\n			}\n			$new_form[] = adi_variables_variable_tag($variable[\'name\'],$variable[\'value\']);\n		}\n	}\n	return $new_form;\n}\n\nfunction adi_variables_form_delete($delete_me) {\n// remove variable from form\n	global $adi_variables_boundary;\n\n	$new_form = array();\n	$raw = adi_variables_read_form(); // raw data from form\n	$lines = explode(\"\\n\",$raw[\'Form\']); // break up into lines\n	$vars = adi_variables_get_vars($lines); // extract variables\n	// generate new form\n	$public = FALSE;\n	foreach ($vars as $index => $var) {\n		if ($var[\'name\'] == $delete_me)\n			continue;\n		if (!$public && $var[\'public\']) { // start of public vars found\n			$public = TRUE; // toggle public\n			$new_form[] = $adi_variables_boundary; // demarcation\n		}\n		$new_form[] = adi_variables_variable_tag($var[\'name\'],$var[\'value\']);\n	}\n	return $new_form;\n}\n\nfunction adi_variables_pref($name,$value=NULL,$private=FALSE) {\n// read or set pref\n	global $prefs,$adi_variables_prefs;\n\n	if ($value === NULL)\n		return get_pref($name,$adi_variables_prefs[$name][\'value\']);\n	else {\n		if (array_key_exists($name,$adi_variables_prefs))\n			$html = $adi_variables_prefs[$name][\'input\'];\n		else\n			$html = \'text_input\';\n		$res = set_pref($name,$value,\'adi_variables_admin\',2,$html,0,$private);\n		$prefs = get_prefs();\n		return $res;\n	}\n}\n\nfunction adi_variables_debug($event,$step,$something,$raw,$lines,$vars,$posted_vars,$new_form,$public_count) {\n// debug info\n	global $adi_variables_debug;\n\n	echo \"event: \".$event.\", step: \".$step.\", something: \".$something.\", adi_variable_delete: \".gps(\'adi_variable_delete\').br.br;\n	echo \"POSTED DATA:\";\n	dmp($posted_vars);\n	echo \"RAW FORM:\";\n	dmp($raw);\n	echo \"LINE BY LINE:\".br;\n	foreach ($lines as $index => $line) {\n		$line = htmlspecialchars($line); // replace angle brackets so tags not parsed\n		echo \"Line \".$index.\". \".$line.br;\n	}\n	$public = FALSE;\n	echo br.\"ANALYSIS:\".br;\n	foreach ($lines as $index => $line) {\n		$this_line = trim($line);\n		if (empty($this_line))\n			echo \"BLANK LINE\".br;\n		else {\n			$ok = adi_variables_validate($line);\n			$ok ? print \"VAR \" : print \"IGNORE \";\n			if (preg_match(\'@^(<txp:hide>PUBLIC)@\',$line)) $public = TRUE; // look for demarcation\n			if ($ok)\n				$public ? print \'(PUBLIC): \' : print \'(ADMIN): \';\n			$line = htmlspecialchars($line); // replace angle brackets so tags not parsed\n			print $line.br;\n		}\n	}\n	echo br.\"STORED DATA:\";\n	dmp($vars);\n	if ($step == \"update\") {\n		print \"NEW FORM:\";\n		dmp($new_form);\n	}\n	echo \'Public variable count = \'.$public_count.br;\n}\n\nfunction adi_variables_admin_script() {\n// jQuery magic for admin tab\n	global $adi_variables_txp460;\n\n	$ui_script = \'\';\n	if (!$adi_variables_txp460)\n		$ui_script = \'<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js\"></script>\';\n\n	echo <<<END_SCRIPT\n<!-- adi_variables - drag and drop -->\n$ui_script\n<script type=\"text/javascript\">\n	if (jQuery.ui) { // jQuery UI available\n		$(function() {\n			$(\'#list tbody tr\').css(\'cursor\',\'pointer\'); // make like a pointer\n			$(\'#list .adi_variables_drag_drop\').show(); // reveal drag & drop icon\n			$(\"#list tbody\").sortable();\n	    });\n	}\n</script>\nEND_SCRIPT;\n}\n\nfunction adi_variables_admin($event,$step) {\n// variables tab\n	global $adi_variables_debug,$adi_variables_admin_privs,$adi_variables_txp460;\n\n	$message = \'\';\n	$something = ps(\'something\');\n	$installed = TRUE;\n	$new_form = array();\n\n	$posted_vars = gps(\'adi_variables\');\n	$delete_variable = gps(\'adi_variable_delete\');\n\n	// install adi_variables form if required\n	if (!safe_field(\'name\', \'txp_form\', \"name=\'adi_variables\'\")) // form not found\n		if (adi_variables_install())\n			$message = gTxt(\'adi_installed\');\n		else {\n			$message = array(gTxt(\'adi_install_fail\'),E_ERROR);\n			$installed = FALSE;\n		}\n\n	if ($installed) {\n		if ($step == \'update\') {\n			// check for duplicates\n			$duplicates = 0;\n			if (isset($posted_vars[\'new\'])) // non-privileged users can\'t post new variable\n				if ($posted_vars[\'new\'][\'name\']) {\n					$duplicates = -1;\n					foreach ($posted_vars as $var)\n						if ($var[\'name\'] == $posted_vars[\'new\'][\'name\'])\n							$duplicates += 1;\n				}\n			if ($duplicates)\n				$message = array(gTxt(\'adi_variables_already_exists\'),E_ERROR);\n			if ($message == \'\') {\n				$new_form = adi_variables_form_update($posted_vars);\n				$ok = adi_variables_update(join(\"\\n\",$new_form));\n				if ($ok)\n					$message = gTxt(\'adi_variables_updated\');\n				else\n					$message = array(gTxt(\'adi_variables_update_fail\'),E_ERROR);\n				if ($ok)\n					update_lastmod();\n			}\n		}\n		if ($step == \'delete\') {\n			if (has_privs($adi_variables_admin_privs)) { // double check privileges because this operation uses GET VARS\n				$new_form = adi_variables_form_delete($delete_variable);\n				$ok = adi_variables_update(join(\"\\n\",$new_form));\n				if ($ok)\n					$message = gTxt(\'adi_variables_deleted\');\n				else\n					$message = array(gTxt(\'adi_variables_delete_fail\'),E_ERROR);\n				if ($ok)\n					update_lastmod();\n			}\n			else\n				$message = gTxt(\'adi_variables_delete_fail\');\n		}\n		if ($something == \"copy\") {\n			$ok = adi_variables_install(\'copy\');\n			if ($ok)\n				$message = gTxt(\'adi_variables_form_copied\');\n			else\n				$message = array(gTxt(\'adi_variables_form_copy_fail\'),E_ERROR);\n		}\n\n		// read contents of form\n		$raw = adi_variables_read_form();\n		if (empty($raw)) // shouldn\'t ever happen coz adi_variables_install should\'ve kicked in\n			gTxt(\'form_not_found\');\n\n		// break up into individual lines\n		$lines = explode(\"\\n\",$raw[\'Form\']);\n\n		// extract variables & values from form\n		$vars = adi_variables_get_vars($lines);\n		if ((trim($raw[\'Form\']) == \'\') || empty($vars)) // form blank OR no vars (i.e. $adi_variables_boundary line only)\n			$message = array(gTxt(\'adi_variables_none_found\'),E_WARNING);\n\n		// count public variables\n		$public_count = 0;\n		foreach ($vars as $variable)\n			if ($variable[\'public\'])\n				$public_count += 1;\n	}\n\n	// generate page\n	pagetop(\'adi_variables admin\',$message);\n	if ($adi_variables_debug)\n		adi_variables_debug($event,$step,$something,$raw,$lines,$vars,$posted_vars,$new_form,$public_count);\n\n	if ($installed) {\n		// only allowed update button if privileged or there\'re public variables present\n		if (has_privs($adi_variables_admin_privs) || $public_count)\n			$update_button =\n				tag(\n					($adi_variables_txp460 ? fInput(\"submit\",\"update\",gTxt(\'Save\'),\'publish\') : fInput(\"submit\",\"update\",gTxt(\'Update\'),\"smallerbox\")) // 4.6 has publish style\n					.eInput(\"adi_variables_admin\")\n					.sInput(\"update\")\n					,\'div\'\n				);\n		else\n			$update_button = \'\';\n		// output table & input form\n		echo\n			form(\n				hed(gTxt(\'adi_variables\'),1,\' class=\"txp-heading\"\')\n				.startTable(\'list\',\'\',\'txp-list\')\n				.tag(\n					tr(\n						hcell(sp) // spacer for plus/drag&drop\n						.hcell(gTxt(\'name\'))\n						.hcell(gTxt(\'value\'))\n						.(has_privs($adi_variables_admin_privs) ? hcell(gTxt(\'public\'),\'\',\' class=\"adi_td_center\"\') : \'\') // hide Public heading if required\n						.(has_privs($adi_variables_admin_privs) ? hcell(sp) : \'\') // filler for delete buttons\n					)\n					,\'thead\'\n				)\n				.adi_variables_display_settings($vars)\n				.endTable()\n				.$update_button\n			);\n		// admin functions\n		if (has_privs($adi_variables_admin_privs)) { // restrict access to admin functions\n		    echo form(\n				tag(\n					hInput(\'something\',\'copy\')\n		        	.fInput(\'submit\',\'do_something\',gTxt(\'adi_variables_copy_form\'),\'smallerbox\')\n			        .eInput(\'adi_variables_admin\')\n			        .sInput(\'admin\')\n					,\'div\'\n					,\' class=\"adi_variables_prefs\"\'\n				)\n			);\n		}\n		// link to form\n// 		if ($adi_variables_txp460)\n// 			echo\n// 				graf(\n// 					href(\n// 						gTxt(\'view\')\n// 						,array(\n// 							\'event\' => \'form\',\n// 							\'step\' => \'form_edit\',\n// 							\'name\' => \'adi_variables\',\n// 							\'_txp_token\' => form_token()\n// 						)\n// 					)\n// 					,\' class=\"adi_td_center\"\'\n// 				);\n	}\n}\n\nfunction adi_variables_options($event,$step) {\n// plugin options page\n	global $txp_groups,$adi_variables_prefs,$adi_variables_outlawed_privs,$adi_variables_debug,$adi_variables_url,$textarray;\n\n	$message = \'\';\n\n	if ($step == \'textpack\') {\n		if (function_exists(\'install_textpack\')) {\n			$adi_textpack = file_get_contents($adi_variables_url[\'textpack\']);\n			if ($adi_textpack) {\n				$result = install_textpack($adi_textpack);\n				$message = gTxt(\'textpack_strings_installed\', array(\'{count}\' => $result));\n				$textarray = load_lang(LANG); // load in new strings\n			}\n			else\n				$message = array(gTxt(\'adi_textpack_fail\'),E_ERROR);\n		}\n	}\n	else if ($step == \'update_prefs\') {\n		$result = TRUE;\n		// run through all prefs\n		foreach ($adi_variables_prefs as $name => $data) {\n			if (array_key_exists($name,$_POST))\n				$value = $_POST[$name];\n			else if ($data[\'input\'] == \'yesnoradio\')\n				$value = \'0\';\n			else\n				$value = $data[\'value\'];\n			if (is_array($value)) { // string-ify arrays\n				$value = array_keys($value); // need index values not 1s\n				if ($name == \'adi_variables_privs\') {\n					if (array_search(1,$value) === FALSE) // can\'t kill off Publisher\n						array_unshift($value,1);\n				}\n				$value = implode(\',\',$value);\n			}\n			// some values not allowed to be blank, reset to default\n			$non_blanks = array();\n			foreach ($non_blanks as $non_blank)\n				if (($name == $non_blank) && (trim($value) == \'\'))\n					$value = $adi_variables_prefs[$non_blank][\'value\'];\n			$result &= adi_variables_pref($name,$value);\n		}\n		$result ? $message = gTxt(\'preferences_saved\') : $message = array(gTxt(\'adi_pref_update_fail\'),E_ERROR);\n	}\n\n	// generate page\n	pagetop(\'adi_variables - \'.gTxt(\'plugin_prefs\'),$message);\n\n	// plugin priv checkboxes\n	$plugin_privs = do_list(adi_variables_pref(\'adi_variables_privs\'));\n	$priv_checkboxes = \'\';\n	foreach ($txp_groups as $index => $group)\n		if (array_search($index,$adi_variables_outlawed_privs) === FALSE) // ignore the outlaws\n			$priv_checkboxes .= tag(gTxt($group).checkbox2(\"adi_variables_privs[$index]\",(array_search($index,$plugin_privs) !== FALSE),\'\',\'adi_variables_privs\').sp.sp,\'label\');\n\n	// options\n	echo tag(\n		tag(\'adi_variables \'.gTxt(\'plugin_prefs\'),\'h1\')\n		// textpack links\n		.graf(href(gTxt(\'install_textpack\'),\'?event=\'.$event.\'&amp;step=textpack\'))\n		.graf(href(gTxt(\'adi_textpack_online\'),$adi_variables_url[\'textpack_download\']))\n		.graf(href(gTxt(\'adi_textpack_feedback\'),$adi_variables_url[\'textpack_feedback\']))\n		// plugin privs\n		.form(\n			graf(\n				gTxt(\'adi_plugin_privs\')\n				.sp.sp.sp\n				.$priv_checkboxes\n				,\' style=\"margin-top:2em\"\'\n			)\n			.fInput(\'submit\',\'do_something\',gTxt(\'update\'),\'smallerbox\')\n			.eInput($event)\n			.sInput(\"update_prefs\")\n		)\n		// link to plugin admin tab\n		.graf(href(gTxt(\'adi_variables\'),\'?event=adi_variables_admin\'))\n		,\'div\'\n		,\' style=\"text-align:center\"\'\n	);\n\n	if ($adi_variables_debug) {\n		echo \"<p>Event: \".$event.\", Step: \".$step.\"</p>\";\n		echo \'$adi_textpack (\'.$adi_variables_url[\'textpack\'].\'):\';\n		$adi_textpack = file_get_contents($adi_variables_url[\'textpack\']);\n		dmp($adi_textpack);\n	}\n\n}\n', '00a81e5dc8c70419a1369cc1266f29d0', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_webhook', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.1', 'Webhooker', '<h1><strong>adi_webhook</strong> &#8211; Webhooker</h1>\n\n<p><strong>adi_webhook</strong> provides a mechanism to easily implement <a href=\"https://en.wikipedia.org/wiki/Webhook\" rel=\"nofollow\">webhooks</a> on Textpattern websites.</p>\n\n<p><strong>adi_webhook</strong> waits for incoming connections and, if <span class=\"caps\">URL</span> variables (and values) match predefined settings, will execute the code of your choice.</p>\n\n<p>It&#8217;s a bit like <a href=\"http://forum.textpattern.com/viewtopic.php?id=28932\" rel=\"nofollow\">adi_gps</a> in that it takes notice of <span class=\"caps\">URL</span> vars, but unlike <strong>adi_gps</strong> it provides a mechanism to selectively act on the data received.</p>\n\n<p>Some new tags:</p>\n\n<ul>\n	<li><a href=\"#adi_webhook\" rel=\"nofollow\">adi_webhook</a> &#8211; main container tag</li>\n	<li><a href=\"#adi_webhook_action\" rel=\"nofollow\">adi_webhook_action</a> &#8211; container tag for specific actions</li>\n	<li><a href=\"#adi_webhook_variable\" rel=\"nofollow\">adi_webhook_variable</a> &#8211; returns value of a <span class=\"caps\">URL</span> var</li>\n	<li><a href=\"#adi_webhook_if_variable\" rel=\"nofollow\">adi_webhook_if_variable</a> &#8211; conditional for <span class=\"caps\">URL</span> vars</li>\n	<li><a href=\"#adi_webhook_log\" rel=\"nofollow\">adi_webhook_log</a> &#8211; add message to log file</li>\n</ul>\n\n<p>The help includes <a href=\"#example\" rel=\"nofollow\">example code</a>, information for <a href=\"#website\" rel=\"nofollow\">setting up your website</a> and tips for <a href=\"#diagnostics\" rel=\"nofollow\">diagnostics &amp; testing</a>.</p>\n\n<p>The plugin was created in response to a <a href=\"http://forum.textpattern.com/viewtopic.php?id=47187\" rel=\"nofollow\">request from Gary</a> (<em>giz</em>) to process MailChimp callbacks &#8211; hence the <a href=\"#mailchimp\" rel=\"nofollow\">simian examples</a>.</p>\n\n\n<h2><strong>Usage summary</strong></h2>\n\n<p>Place the <code>&lt;txp:adi_webhook&gt; ... &lt;/txp:adi_webhook&gt;</code> tag at the top of a special section/page (<a href=\"#website\" rel=\"nofollow\">read more</a>).</p>\n\n\n<h2><strong>Tags</strong></h2>\n\n<h3><strong>adi_webhook</strong></h3>\n\n<p>This tag is the main container tag.  It contains the tags to be executed when incoming connections are received. For example:</p>\n\n<pre><code>&lt;txp:adi_webhook name=&quot;myvar1&quot;&gt;\n	...\n&lt;/txp:adi_webhook&gt;</code></pre>\n\n<p>- will execute the enclosed tags when an incoming connection contains a <span class=\"caps\">POST</span> variable <em>myvar1</em></p>\n\n<h4><strong>Attributes</strong></h4>\n\n<p><code>name=&quot;URL variable name&quot;</code></p>\n\n<p>- the <em>optional</em> <span class=\"caps\">URL</span> variable to be present in the incoming connection. Default=”“ (all incoming connections accepted).</p>\n\n<p><code>value=&quot;some value&quot;</code></p>\n\n<p>- the <em>optional</em> value of the <span class=\"caps\">URL</span> var. Default = &#8220;&#8221; (any value acceptable). Ignored if <code>name=&quot;&quot;</code>.</p>\n\n<p><code>method=&quot;HTTP method&quot;</code></p>\n\n<p>- comma-separated list of methods &#8211; i.e. where to look for the <span class=\"caps\">URL</span> var. Default = “post” (<span class=\"caps\">POST</span>). Other possibilities: “get” (<span class=\"caps\">GET</span>) or “post,get” (<span class=\"caps\">POST</span> &amp; <span class=\"caps\">GET</span>). <em>If both methods specified, <span class=\"caps\">POST</span> vars take preference.</em></p>\n\n<p><code>form=&quot;form name&quot;</code></p>\n\n<p>- form to use when not in container mode. Default = &#8220;&#8221; (container mode).</p>\n\n<p><code>debug=&quot;integer&quot;</code></p>\n\n<p>- generate diagnostic messages. Default = “0” (no diagnostics). Set to “1” if testing using cURL, or “2” if using browser (“2” uses <code>&lt;br/&gt;</code> line breaks for easier reading).</p>\n\n\n<h3><strong>adi_webhook_action</strong></h3>\n\n<p>This tag contains code to be executed when a <span class=\"caps\">URL</span> var &amp; value criteria are satisfied.  The <span class=\"caps\">URL</span> var can be different to the one specified in the <strong>adi_webhook</strong> tag and there may be more than one instance of the tag. For example:</p>\n\n<pre><code>&lt;txp:adi_webhook name=&quot;myvar1&quot;&gt;\n	&lt;txp:adi_webhook_action name=&quot;myvar2&quot;&gt;\n		...\n	&lt;/txp:adi_webhook_action&gt;\n	&lt;txp:adi_webhook_action name=&quot;myvar3&quot;&gt;\n		...\n	&lt;/txp:adi_webhook_action&gt;\n&lt;/txp:adi_webhook&gt;</code></pre>\n\n<p>- this will wait for an incoming connection containing a variable <em>myvar1</em>, and run tags if vars <em>myvar2</em> or <em>myvar3</em> are present.</p>\n\n<h4><strong>Attributes</strong></h4>\n\n<p><code>name=&quot;URL variable name&quot;</code></p>\n\n<p>- this attribute is mandatory and specifies a <span class=\"caps\">URL</span> variable that must be present in the incoming connection. Default=”“ (all vars match).</p>\n\n<p><code>value=&quot;some value&quot;</code></p>\n\n<p>- the <em>optional</em> value of the <span class=\"caps\">URL</span> var. Default = &#8220;&#8221; (any value acceptable).</p>\n\n<p><code>method=&quot;HTTP method&quot;</code></p>\n\n<p>- comma-separated list of methods &#8211; i.e. where to look for the <span class=\"caps\">URL</span> var. Default = “post” (<span class=\"caps\">POST</span>). Other possibilities: “get” or “post,get”. <em>If both methods specified, <span class=\"caps\">POST</span> vars take preference.</em></p>\n\n<p><code>form=&quot;form name&quot;</code></p>\n\n<p>- form to use when not in container mode. Default = &#8220;&#8221; (container mode).</p>\n\n\n<h3><strong>adi_webhook_variable</strong></h3>\n\n<p>This tag outputs the value of a specified <span class=\"caps\">URL</span> variable, for example:</p>\n\n<pre><code>&lt;txp:adi_webhook_variable name=&quot;myvar2&quot; /&gt;</code></pre>\n\n<h4><strong>Attributes</strong></h4>\n\n<p><code>name=&quot;URL variable name&quot;</code></p>\n\n<p>- <em>this attribute is mandatory</em> and specifies a <span class=\"caps\">URL</span> variable. Default=”“ (none).</p>\n\n<p><code>method=&quot;HTTP method&quot;</code></p>\n\n<p>- comma-separated list of methods &#8211; i.e. where to look for the <span class=\"caps\">URL</span> var. Default = “post” (<span class=\"caps\">POST</span>). Other possibilities: “get” or “post,get”. <em>If both methods specified, <span class=\"caps\">POST</span> vars take preference.</em></p>\n\n<h3><strong>adi_webhook_if_variable</strong></h3>\n\n<p>This is a conditional tag and it tests the existence and/or value of a <span class=\"caps\">URL</span> variable. For example, to test that <span class=\"caps\">GET</span> var <em>myvar2</em> has a value of “myval2”:</p>\n\n<pre><code>&lt;txp:adi_webhook_if_variable name=&quot;myvar2&quot; value=&quot;myval2&quot; method=&quot;get&quot;&gt;\n	...\n&lt;/txp:adi_webhook_if_variable&gt;</code></pre>\n\n<h4><strong>Attributes</strong></h4>\n\n<p><code>name=&quot;URL variable name&quot;</code></p>\n\n<p>- <em>this attribute is mandatory</em> and specifies a <span class=\"caps\">URL</span> variable. Default=”“ (none).</p>\n\n<p><code>value=&quot;some value&quot;</code></p>\n\n<p>- the <em>optional</em> value of the <span class=\"caps\">URL</span> var. Default = &#8220;&#8221; (any value acceptable).</p>\n\n<p><code>method=&quot;HTTP method&quot;</code></p>\n\n<p>- comma-separated list of methods &#8211; i.e. where to look for the <span class=\"caps\">URL</span> var. Default = “post” (<span class=\"caps\">POST</span>). Other possibilities: “get” or “post,get”. <em>If both methods specified, <span class=\"caps\">POST</span> vars take preference.</em></p>\n\n\n<h3><strong>adi_webhook_log</strong></h3>\n\n<p>This is use for diagnostic purposes to enter a message into the logfile and, in debug mode, output the message for viewing when using <em>cURL</em> or with browser testing.</p>\n\n<h4><strong>Attributes</strong></h4>\n\n<p><code>msg=&quot;text&quot;</code></p>\n\n<p>- <em>this attribute is required</em> and specifies a line of text to enter into the log. Default=”“ (no message).</p>\n\n\n<h2><strong>Simple example</strong></h2>\n\n<pre><code>&lt;txp:adi_webhook name=&quot;myvar1&quot; value=&quot;myval1&quot;&gt;\n\n	&lt;txp:adi_webhook_action name=&quot;myvar2&quot;&gt;\n\n		&lt;txp:adi_webhook_log msg=&quot;*** MYVAR2 FOUND **&quot; /&gt;\n\n		MYVAR2 = &lt;txp:adi_webhook_variable name=&quot;myvar2&quot; /&gt;\n\n		&lt;txp:adi_webhook_if_variable name=&quot;myvar2&quot; value=&quot;myval2&quot;&gt;\n			DO THIS ...\n		&lt;txp:else /&gt;\n			DO SOMETHING ELSE ...\n		&lt;/txp:adi_webhook_if_variable&gt;\n\n	&lt;/txp:adi_webhook_action&gt;\n\n&lt;/txp:adi_webhook&gt;</code></pre>\n\n<p>In the above example:</p>\n\n<ul>\n	<li><strong>adi_webhook</strong> will wait for an incoming connection containing var <em>myvar1</em> that has a value of “myval1”</li>\n	<li>if the connection contains var <em>myvar2</em> as well, the tags enclosed by <strong>adi_webhook_action</strong> are executed &#8211; i.e. a diagnostic message generated, &amp; the value of <em>myvar2</em> output</li>\n	<li>in addition, the value of <em>myvar2</em> is tested for further processing</li>\n</ul>\n\n\n<h2><strong>Website setup</strong></h2>\n\n<p>Your <strong>adi_webhook</strong> tags need to go at the top of a Textpattern Page, so that they can process the incoming <span class=\"caps\">URL</span> vars.</p>\n\n<p>It&#8217;d probably work OK if the Page went on to generate a page of <span class=\"caps\">HTML</span> markup, but a tidier &amp; safer solution would be to set up a dedicated Section/Page for receiving webhook callbacks:</p>\n\n<ol>\n	<li>Create a new Section, e.g. “webhook”</li>\n	<li>Create a new Page, containing your <strong>adi_webhook</strong> tags, and link it to the new section</li>\n	<li>Make sure that the new section doesn&#8217;t appear in your website navigation menu!</li>\n</ol>\n\n\n<h2><strong>Diagnostics</strong></h2>\n\n<h3><strong>Testing using cURL</strong></h3>\n\n<p>Fire off a connection to the basic example code (set up in a “webhook” section on your website):</p>\n\n<pre><code>curl --request POST --url \'http://www.example.com/webhook\' \'-d myvar1=myval1\' \'-d myvar2=myval2\'</code></pre>\n\n<p>and you&#8217;ll get a response something like:</p>\n\n<blockquote>\n	<p>MYVAR2 = myval2<br />\nDO <span class=\"caps\">THIS</span> &#8230;</p>\n</blockquote>\n\n<p>To see some useful diagnostics, use <code>debug=&quot;1&quot;</code> on the <strong>adi_webhook</strong> tag and the same cURL command will output a shed-load of diagnostic messages.</p>\n\n<h3><strong>Testing using a browser</strong></h3>\n\n<p>If you connect to <em>www.example.com/webhook</em> in a browser, you wouldn&#8217;t normally see anything (other than a blank page) but if you set <code>debug=&quot;2&quot;</code> on the <strong>adi_webhook</strong> tag, you will get something like:</p>\n\n<blockquote>\n	<p>2016-07-19 23:22:04 adi_webhook: ****************************************<br />\n2016-07-19 23:22:04 adi_webhook: <span class=\"caps\">ATTRIBUTES</span> Array ( [var] =&gt; myvar1 [value] =&gt; myval1 [debug] =&gt; 2 )<br />\n2016-07-19 23:22:04 adi_webhook: <span class=\"caps\">POST</span> Array ( )<br />\n2016-07-19 23:22:04 adi_webhook: <span class=\"caps\">GET</span> Array ( )<br />\n2016-07-19 23:22:04 adi_webhook_get_var: <span class=\"caps\">LOOKING</span> <span class=\"caps\">FOR</span> myvar1 in <span class=\"caps\">POST</span><br />\n2016-07-19 23:22:04 adi_webhook_get_var: <span class=\"caps\">VAR</span> myvar1 <span class=\"caps\">NOT</span> <span class=\"caps\">SET</span></p>\n</blockquote>\n\n<p>Notes:</p>\n\n<ul>\n	<li>in this case the <strong>adi_webhook</strong> tag won&#8217;t run its enclosed tags because in the browser you&#8217;re not <span class=\"caps\">POST</span>ing the <em>myvar1</em> variable</li>\n</ul>\n\n<p>Make sure you switch off debug for production environments.</p>\n\n<h3><strong>Logfile</strong></h3>\n\n<p>Regardless of the ‘debug’ attribute setting, <strong>adi_webhook</strong> maintains a diagnostic logfile in Textpattern&#8217;s temporary directory (probably <em>textpattern/tmp</em>).</p>\n\n<p>The logfile name is based on the section in which <strong>adi_webhook</strong> is used, as well as the day of the week (0 = Sunday), e.g. “adi_webhook.my_webhook_section.3.log”.</p>\n\n\n<h2><strong>MailChimp</strong></h2>\n\n<h3><strong>Setup</strong></h3>\n\n<p>Log in to your MailChimp admin:</p>\n\n<ol>\n	<li>Go to the list in question</li>\n	<li>Go to the list&#8217;s Settings</li>\n	<li>Select Webhooks\n	<ol>\n		<li>Enter the ‘Webhook <span class=\"caps\">URL</span>’, e.g. “http://www.mywebsite.com/my_webhook_section”</li>\n		<li>Select the “what” and “when”</li>\n		<li>Click Update</li>\n	</ol></li>\n	<li>Whilst you&#8217;re in MailChimp, make a note of the ‘List ID’</li>\n</ol>\n\n<h3><strong>Example 1</strong></h3>\n\n<p>The code in your dedicated MailChimp webhook section/page could be:</p>\n\n<pre><code>&lt;txp:adi_webhook name=&quot;data[list_id]&quot; value=&quot;xxxxxxxxxx&quot;&gt;\n\n	&lt;txp:adi_webhook_log msg=\'Incoming email: &lt;txp:adi_webhook_variable var=&quot;data[email]&quot; /&gt;\' /&gt;\n\n	&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;subscribe&quot;&gt;\n		TAGS FOR SUBSCRIPTION ACTIONS ...\n	&lt;/txp:adi_webhook_action&gt;\n\n	&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;unsubscribe&quot;&gt;\n		TAGS FOR UNSUBSCRIPTION ACTIONS ...\n	&lt;/txp:adi_webhook_action&gt;\n\n&lt;/txp:adi_webhook&gt;</code></pre>\n\n<p>Notes:</p>\n\n<ul>\n	<li>“xxxxxxxxxx” is the MailChimp list ID, and is used as the initial criteria for the <strong>adi_webhook</strong> tag to continue processing</li>\n	<li>all vars presented by MailChimp are <span class=\"caps\">POST</span>ed (the ‘method’ attribute defaults to “post”)</li>\n</ul>\n\n<h3><strong>Example 2</strong></h3>\n\n<p>If you have more than one list, that you want to process with the same <strong>adi_webhook</strong> code, try this:</p>\n\n<pre><code>&lt;txp:adi_webhook name=&quot;key&quot; value=&quot;my_secret_key&quot; method=&quot;get&quot; debug=&quot;1&quot;&gt;\n\n	&lt;txp:adi_webhook_action name=&quot;data[list_id]&quot; value=&quot;xxxxxxxxxx&quot;&gt;\n\n		&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;subscribe&quot;&gt;\n			TAGS FOR SUBSCRIPTION ACTIONS ...\n		&lt;/txp:adi_webhook_action&gt;\n\n		&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;unsubscribe&quot;&gt;\n			TAGS FOR UNSUBSCRIPTION ACTIONS ...\n		&lt;/txp:adi_webhook_action&gt;\n\n	&lt;/txp:adi_webhook_action&gt;\n\n	&lt;txp:adi_webhook_action name=&quot;data[list_id]&quot; value=&quot;yyyyyyyyyy&quot;&gt;\n\n		&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;subscribe&quot;&gt;\n			TAGS FOR SUBSCRIPTION ACTIONS ...\n		&lt;/txp:adi_webhook_action&gt;\n\n		&lt;txp:adi_webhook_action name=&quot;type&quot; value=&quot;unsubscribe&quot;&gt;\n			TAGS FOR UNSUBSCRIPTION ACTIONS ...\n		&lt;/txp:adi_webhook_action&gt;\n\n	&lt;/txp:adi_webhook_action&gt;\n\n&lt;/txp:adi_webhook&gt;</code></pre>\n\n\n<p>Notes:</p>\n\n<ul>\n	<li>the MailChimp webhook <span class=\"caps\">URL</span> becomes: “http://www.mywebsite.com/my_webhook_section?key=my_secret_key”</li>\n	<li>the <span class=\"caps\">URL</span> variable <em>key</em> and its value (“my_secret_key”) are used as the initial criteria for the <strong>adi_webhook</strong> tag to continue processing</li>\n	<li><em>key</em> is a <span class=\"caps\">GET</span> variable, hence <code>method=&quot;get&quot;</code>, but all other vars presented by MailChimp are <span class=\"caps\">POST</span>ed (the ‘method’ attribute defaults to “post”)</li>\n	<li>“xxxxxxxxxx” &amp; “yyyyyyyyyy” identify the different MailChimp lists</li>\n</ul>\n\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=47211\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_webhook\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n// NOTE\n\n/*\n	adi_webhook - Webhooker\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.1		- initial release\n\n	TODO:\n	- admin view logfile\n	- logfile management\n\n*/\n\nfunction adi_webhook($atts,$thing=NULL) {\n// <txp:adi_webhook /> container tag\n	global $adi_webhook_debug;\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// (optional) URL var to trigger actions\n		\'value\'		=> \'\',		// (optional) value of var\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n		\'form\'		=> \'\',		// form to use if not in container mode\n// 		\'log\'		=> 1,		// log file\n		\'debug\'		=> 0,\n	),$atts));\n\n	$adi_webhook_debug = $debug;\n\n	if (array_key_exists(\'value\',$atts) === FALSE) $value = NULL; // NULL = \"not looking for a match\"\n\n	adi_webhook_log_rotate();\n	adi_webhook_write_log(\'****************************************\');\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	// log contents of POST and GET\n	adi_webhook_write_log($GLOBALS[\'_POST\'],\'POST\');\n	adi_webhook_write_log($GLOBALS[\'_GET\'],\'GET\');\n\n	// var specified?\n	if ($name != \'\') {\n		$actual_value = adi_webhook_get_var($name,$method,$value);\n		if (($actual_value === FALSE) || ($actual_value === NULL)) return; // var not found OR value not match\n	}\n\n	// form or contained tags\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	// walk the walk\n	return parse($form);\n}\n\nfunction adi_webhook_action($atts,$thing=NULL) {\n// <txp:adi_webhook_action /> container tag\n// effectively the same function as adi_webhook, except\n//	- doesn\'t restart log\n//	- doesn\'t dump POST/GET\n//	- URL var is required\n//	- uses global debug\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// (mandatory) URL var to trigger actions\n		\'value\'		=> \'\',		// (optional) value of var\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n		\'form\'		=> \'\',		// form to use if not in container mode\n	),$atts));\n\n	if (array_key_exists(\'value\',$atts) === FALSE) $value = NULL; // NULL = \"not looking for a match\"\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	$actual_value = adi_webhook_get_var($name,$method,$value);\n\n	if (($actual_value === FALSE) || ($actual_value === NULL)) return; // var not found OR value not match\n\n	// form or contained tags\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	// talk the talk\n	return parse($form);\n}\n\nfunction adi_webhook_variable($atts) {\n// <txp:adi_webhook_variable /> tag\n// returns value of supplied var\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// URL var to output\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n	),$atts));\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	$value = adi_webhook_get_var($name,$method);\n\n	if ($value === FALSE) return; // var not found\n\n	adi_webhook_write_log(\"VAR FOUND $name VALUE=\".$value);\n\n	return $value;\n}\n\nfunction adi_webhook_if_variable($atts, $thing=NULL) {\n// <txp:adi_webhook_if_variable /> tag\n// check if var exists (or var exists & value matches)\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// URL var to look for\n		\'value\'		=> NULL,	// value of var to test\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n	),$atts));\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	// do the do\n	if ($value === NULL) { // just check that var exists\n		$value = adi_webhook_get_var($name,$method); // FALSE if not found\n		return parse(EvalElse($thing, ($value !== FALSE)));\n	}\n	else { // check that var found & value matches\n		$value = adi_webhook_get_var($name,$method,$value); //  FALSE if not found, NULL if not match\n		return parse(EvalElse($thing, (($value !== FALSE) && ($value !== NULL))));\n	}\n}\n\nfunction adi_webhook_log($atts) {\n// <txp:adi_webhook_log /> tag\n// write message to log\n\n	extract(lAtts(array(\n		\'msg\'		=> \'\',		// the message\n	),$atts));\n\n	adi_webhook_write_log($msg);\n}\n\nfunction adi_webhook_get_var($supplied_var,$method,$value=NULL) {\n// returns:	FALSE	- var not found\n//			NULL	- value doesn\'t match\n//			value	- var exists (and value matches)\n\n	// methodical\n	$methods = do_list($method); // create methods array\n	$methods = array_intersect(array(\'post\',\'get\'),$methods); // validate list of methods & enforce \'post\' first\n	$methods = array_values($methods); // reindex\n// 	adi_webhook_write_log(\"** METHODS: \".implode(\',\',$methods).\" **\");\n\n	// parse supplied variable name\n	if (strpos($supplied_var,\'[\') !== FALSE) { // supplied variable is array e.g. \"data[merges][EMAIL]\"\n// 		adi_webhook_write_log(\'*** SUPPLIED \'.$supplied_var.\' IS ARRAY\');\n		preg_match(\"/(.*?)\\[/\", $supplied_var, $matches);\n		$name = $matches[1]; // the array name (e.g. \"data\")\n		preg_match_all(\"/\\[([^\\]]*)\\]/\", $supplied_var, $matches); // the keys (e.g. \"merges\", \"EMAIL\")\n	}\n	else // variable ordinaire\n		$name = $supplied_var;\n\n	// search each method in turn for first occurrence of supplied variable (POST is always first)\n	foreach ($methods as $index => $method) {\n		adi_webhook_write_log(\"LOOKING FOR $name IN \".strtoupper($method));\n		$this_global = $GLOBALS[\'_\'.strtoupper($method)]; // i.e $_POST or $_GET\n		// find required variable, & get value\n		if (isset($this_global[$name])) {\n// 	 		adi_webhook_write_log(\"**$method** \".$name.\' IS SET\');\n			$this_value = $this_global[$name];\n			if (is_array($this_value)) {\n// 	 			adi_webhook_write_log(\"**$method** \".$name.\' IS ARRAY\');\n				$key_str = \'\';\n				foreach ($matches[1] as $key) {\n					$key_str .= \'[\'.$key.\']\';\n					if (isset($this_value[$key])) {\n// 	 					adi_webhook_write_log(\"**$method** \".\"$name$key_str IS SET\");\n						$this_value = $this_value[$key];\n						if (is_array($this_value)) {\n// 	 						adi_webhook_write_log(\"**$method** \".\"$name$key_str IS ARRAY\");\n						}\n						else {\n							adi_webhook_write_log(\"VAR $name$key_str VALUE=\".$this_value);\n							break;\n						}\n					}\n					else {\n						if ($index == (count($methods)-1)) { // last method, so give up\n							adi_webhook_write_log(\"VAR $name$key_str is NOT set\");\n							return FALSE;\n						}\n						else\n							continue; // try next method\n					}\n				}\n			}\n			else {\n				adi_webhook_write_log(\"VAR $name VALUE=\".$this_value);\n			}\n		}\n		else {\n			if ($index == (count($methods)-1)) { // last method, so give up\n				adi_webhook_write_log(\"VAR $name NOT SET\");\n				return FALSE;\n			}\n			else\n				continue; // try next method\n		}\n\n		// need to match a value?\n		if (($value !== NULL)) {\n			if ($this_value != $value) {\n				adi_webhook_write_log(\'NO MATCH\');\n				return NULL;\n			}\n			else\n				adi_webhook_write_log(\'VALUE MATCH\');\n		}\n\n		return $this_value;\n	}\n}\n\nfunction adi_webhook_write_log($msg,$prefix=\'?\') {\n// log message to file (and maybe output as well)\n// logfile: TXP_TEMPDIR/adi_webhook.SECTION.DAY_NUM.log\n	global $prefs,$s,$adi_webhook_debug;\n\n	$logfile = $prefs[\'tempdir\'].\'/adi_webhook.\'.$s.\'.\'.get_pref(\'adi_webhook_log\').\'.log\';\n\n	// get meaningful calling function\n	if (is_callable(\'debug_backtrace\')) {\n		$backtrace = debug_backtrace();\n		$function = isset($backtrace[1][\'function\']) ? $backtrace[1][\'function\'] : __FUNCTION__;\n	}\n	else\n		$function = __FUNCTION__;\n\n	// give meaning to the unmeaningful\n	if ($function == \"include\") $function = __FUNCTION__; // plugin cache\n	if ($function == \"eval\") $function = __FUNCTION__; // plugin installed\n\n	if (is_array($msg)) // dump array values\n		$msg = \"$prefix \".print_r($msg,TRUE);\n\n	// full log entry\n	$log_entry = date(\'Y-m-d H:i:s\',time()+tz_offset()).\' \'.$function.\": $msg\\n\";\n\n	if ($adi_webhook_debug)\n		echo $log_entry.($adi_webhook_debug == 2 ? br : \'\'); // <br/> line break if debug = 2\n\n	file_put_contents($logfile,$log_entry,FILE_APPEND);\n}\n\nfunction adi_webhook_log_rotate() {\n// rotate logfiles, if due\n// logfile: TXP_TEMPDIR/adi_webhook.SECTION.DAY_NUM.log\n	global $prefs,$s;\n\n	$today_num = date(\'w\'); // today\'s day number (0 Sunday - 6 Saturday)\n	$current_num = get_pref(\'adi_webhook_log\',\'x\'); // current log day number\n\n	// today now tomorrow?\n	if ($today_num != $current_num) {\n		// preference today\'s log number\n		set_pref(\'adi_webhook_log\',$prefs[\'adi_webhook_log\']=$today_num,\'adi_webhook_admin\');\n		// delete old today logfile\n  		$todays_logfile = $prefs[\'tempdir\'].\'/adi_webhook.\'.$s.\'.\'.$today_num.\'.log\';\n  		@unlink($todays_logfile);\n	}\n}\n', '\n// NOTE\n\n/*\n	adi_webhook - Webhooker\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.1		- initial release\n\n	TODO:\n	- admin view logfile\n	- logfile management\n\n*/\n\nfunction adi_webhook($atts,$thing=NULL) {\n// <txp:adi_webhook /> container tag\n	global $adi_webhook_debug;\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// (optional) URL var to trigger actions\n		\'value\'		=> \'\',		// (optional) value of var\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n		\'form\'		=> \'\',		// form to use if not in container mode\n// 		\'log\'		=> 1,		// log file\n		\'debug\'		=> 0,\n	),$atts));\n\n	$adi_webhook_debug = $debug;\n\n	if (array_key_exists(\'value\',$atts) === FALSE) $value = NULL; // NULL = \"not looking for a match\"\n\n	adi_webhook_log_rotate();\n	adi_webhook_write_log(\'****************************************\');\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	// log contents of POST and GET\n	adi_webhook_write_log($GLOBALS[\'_POST\'],\'POST\');\n	adi_webhook_write_log($GLOBALS[\'_GET\'],\'GET\');\n\n	// var specified?\n	if ($name != \'\') {\n		$actual_value = adi_webhook_get_var($name,$method,$value);\n		if (($actual_value === FALSE) || ($actual_value === NULL)) return; // var not found OR value not match\n	}\n\n	// form or contained tags\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	// walk the walk\n	return parse($form);\n}\n\nfunction adi_webhook_action($atts,$thing=NULL) {\n// <txp:adi_webhook_action /> container tag\n// effectively the same function as adi_webhook, except\n//	- doesn\'t restart log\n//	- doesn\'t dump POST/GET\n//	- URL var is required\n//	- uses global debug\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// (mandatory) URL var to trigger actions\n		\'value\'		=> \'\',		// (optional) value of var\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n		\'form\'		=> \'\',		// form to use if not in container mode\n	),$atts));\n\n	if (array_key_exists(\'value\',$atts) === FALSE) $value = NULL; // NULL = \"not looking for a match\"\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	$actual_value = adi_webhook_get_var($name,$method,$value);\n\n	if (($actual_value === FALSE) || ($actual_value === NULL)) return; // var not found OR value not match\n\n	// form or contained tags\n	$form = ($form) ? fetch_form($form) : $thing;\n\n	// talk the talk\n	return parse($form);\n}\n\nfunction adi_webhook_variable($atts) {\n// <txp:adi_webhook_variable /> tag\n// returns value of supplied var\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// URL var to output\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n	),$atts));\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	$value = adi_webhook_get_var($name,$method);\n\n	if ($value === FALSE) return; // var not found\n\n	adi_webhook_write_log(\"VAR FOUND $name VALUE=\".$value);\n\n	return $value;\n}\n\nfunction adi_webhook_if_variable($atts, $thing=NULL) {\n// <txp:adi_webhook_if_variable /> tag\n// check if var exists (or var exists & value matches)\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',		// URL var to look for\n		\'value\'		=> NULL,	// value of var to test\n		\'method\'	=> \'post\',	// \'post\', \'get\' or \'post,get\'\n	),$atts));\n\n	adi_webhook_write_log($atts,\'ATTRIBUTES\');\n\n	if ($name == \'\') return; // must have a url var\n\n	// do the do\n	if ($value === NULL) { // just check that var exists\n		$value = adi_webhook_get_var($name,$method); // FALSE if not found\n		return parse(EvalElse($thing, ($value !== FALSE)));\n	}\n	else { // check that var found & value matches\n		$value = adi_webhook_get_var($name,$method,$value); //  FALSE if not found, NULL if not match\n		return parse(EvalElse($thing, (($value !== FALSE) && ($value !== NULL))));\n	}\n}\n\nfunction adi_webhook_log($atts) {\n// <txp:adi_webhook_log /> tag\n// write message to log\n\n	extract(lAtts(array(\n		\'msg\'		=> \'\',		// the message\n	),$atts));\n\n	adi_webhook_write_log($msg);\n}\n\nfunction adi_webhook_get_var($supplied_var,$method,$value=NULL) {\n// returns:	FALSE	- var not found\n//			NULL	- value doesn\'t match\n//			value	- var exists (and value matches)\n\n	// methodical\n	$methods = do_list($method); // create methods array\n	$methods = array_intersect(array(\'post\',\'get\'),$methods); // validate list of methods & enforce \'post\' first\n	$methods = array_values($methods); // reindex\n// 	adi_webhook_write_log(\"** METHODS: \".implode(\',\',$methods).\" **\");\n\n	// parse supplied variable name\n	if (strpos($supplied_var,\'[\') !== FALSE) { // supplied variable is array e.g. \"data[merges][EMAIL]\"\n// 		adi_webhook_write_log(\'*** SUPPLIED \'.$supplied_var.\' IS ARRAY\');\n		preg_match(\"/(.*?)\\[/\", $supplied_var, $matches);\n		$name = $matches[1]; // the array name (e.g. \"data\")\n		preg_match_all(\"/\\[([^\\]]*)\\]/\", $supplied_var, $matches); // the keys (e.g. \"merges\", \"EMAIL\")\n	}\n	else // variable ordinaire\n		$name = $supplied_var;\n\n	// search each method in turn for first occurrence of supplied variable (POST is always first)\n	foreach ($methods as $index => $method) {\n		adi_webhook_write_log(\"LOOKING FOR $name IN \".strtoupper($method));\n		$this_global = $GLOBALS[\'_\'.strtoupper($method)]; // i.e $_POST or $_GET\n		// find required variable, & get value\n		if (isset($this_global[$name])) {\n// 	 		adi_webhook_write_log(\"**$method** \".$name.\' IS SET\');\n			$this_value = $this_global[$name];\n			if (is_array($this_value)) {\n// 	 			adi_webhook_write_log(\"**$method** \".$name.\' IS ARRAY\');\n				$key_str = \'\';\n				foreach ($matches[1] as $key) {\n					$key_str .= \'[\'.$key.\']\';\n					if (isset($this_value[$key])) {\n// 	 					adi_webhook_write_log(\"**$method** \".\"$name$key_str IS SET\");\n						$this_value = $this_value[$key];\n						if (is_array($this_value)) {\n// 	 						adi_webhook_write_log(\"**$method** \".\"$name$key_str IS ARRAY\");\n						}\n						else {\n							adi_webhook_write_log(\"VAR $name$key_str VALUE=\".$this_value);\n							break;\n						}\n					}\n					else {\n						if ($index == (count($methods)-1)) { // last method, so give up\n							adi_webhook_write_log(\"VAR $name$key_str is NOT set\");\n							return FALSE;\n						}\n						else\n							continue; // try next method\n					}\n				}\n			}\n			else {\n				adi_webhook_write_log(\"VAR $name VALUE=\".$this_value);\n			}\n		}\n		else {\n			if ($index == (count($methods)-1)) { // last method, so give up\n				adi_webhook_write_log(\"VAR $name NOT SET\");\n				return FALSE;\n			}\n			else\n				continue; // try next method\n		}\n\n		// need to match a value?\n		if (($value !== NULL)) {\n			if ($this_value != $value) {\n				adi_webhook_write_log(\'NO MATCH\');\n				return NULL;\n			}\n			else\n				adi_webhook_write_log(\'VALUE MATCH\');\n		}\n\n		return $this_value;\n	}\n}\n\nfunction adi_webhook_write_log($msg,$prefix=\'?\') {\n// log message to file (and maybe output as well)\n// logfile: TXP_TEMPDIR/adi_webhook.SECTION.DAY_NUM.log\n	global $prefs,$s,$adi_webhook_debug;\n\n	$logfile = $prefs[\'tempdir\'].\'/adi_webhook.\'.$s.\'.\'.get_pref(\'adi_webhook_log\').\'.log\';\n\n	// get meaningful calling function\n	if (is_callable(\'debug_backtrace\')) {\n		$backtrace = debug_backtrace();\n		$function = isset($backtrace[1][\'function\']) ? $backtrace[1][\'function\'] : __FUNCTION__;\n	}\n	else\n		$function = __FUNCTION__;\n\n	// give meaning to the unmeaningful\n	if ($function == \"include\") $function = __FUNCTION__; // plugin cache\n	if ($function == \"eval\") $function = __FUNCTION__; // plugin installed\n\n	if (is_array($msg)) // dump array values\n		$msg = \"$prefix \".print_r($msg,TRUE);\n\n	// full log entry\n	$log_entry = date(\'Y-m-d H:i:s\',time()+tz_offset()).\' \'.$function.\": $msg\\n\";\n\n	if ($adi_webhook_debug)\n		echo $log_entry.($adi_webhook_debug == 2 ? br : \'\'); // <br/> line break if debug = 2\n\n	file_put_contents($logfile,$log_entry,FILE_APPEND);\n}\n\nfunction adi_webhook_log_rotate() {\n// rotate logfiles, if due\n// logfile: TXP_TEMPDIR/adi_webhook.SECTION.DAY_NUM.log\n	global $prefs,$s;\n\n	$today_num = date(\'w\'); // today\'s day number (0 Sunday - 6 Saturday)\n	$current_num = get_pref(\'adi_webhook_log\',\'x\'); // current log day number\n\n	// today now tomorrow?\n	if ($today_num != $current_num) {\n		// preference today\'s log number\n		set_pref(\'adi_webhook_log\',$prefs[\'adi_webhook_log\']=$today_num,\'adi_webhook_admin\');\n		// delete old today logfile\n  		$todays_logfile = $prefs[\'tempdir\'].\'/adi_webhook.\'.$s.\'.\'.$today_num.\'.log\';\n  		@unlink($todays_logfile);\n	}\n}\n', '778ea0b4d5f74c02e49305bf87912e49', '', '', 0, 5, 0),
('adi_wrap', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Automatic wrapper', '<h1><strong>adi_wrap</strong> &#8211; Automatic wrapper</h1>\n\n<p>This plugin will wrap its contents in an <span class=\"caps\">HTML</span> tag &#8211; but only if there&#8217;s something to wrap.</p>\n\n<h2><strong>Usage</strong></h2>\n\n<p>Simply put the <strong>adi_wrap</strong> tag around the contents you need wrapped up:</p>\n\n<pre><code>&lt;txp:adi_wrap wraptag=&quot;HTML tag&quot;&gt;\n	...contained statements...\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p>and if the “&#8230;contained statements&#8230;” generate some output then they&#8217;ll be surrounded by the <span class=\"caps\">HTML</span> tag of your choice.</p>\n\n<p>Using <strong>adi_wrap</strong> will help avoid the creation of empty tags in your markup.</p>\n\n<p>And if you need some extra <span class=\"caps\">HTML</span> attributes, it&#8217;ll do that too.  Have a look in the examples.</p>\n\n<h2><strong>Attributes</strong></h2>\n\n<p><code>wraptag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the link. Default = “div”.<br />\n<em>If there is no content to output then no wraptag is generated.</em></p>\n\n<p><code>class=&quot;class name&quot;</code></p>\n\n<p>- the <span class=\"caps\">CSS</span> class to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>html_id=&quot;id name&quot;</code></p>\n\n<p>- the <span class=\"caps\">HTML</span> ID to be used on the wraptag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>label=&quot;text&quot;</code></p>\n\n<p>- some text to precede the wrapped content. Default = &#8220;&#8221; (no label).</p>\n\n<p><code>labeltag=&quot;tag&quot;</code></p>\n\n<p>- the (X)HTML tag (without ‘&lt;&gt;’ brackets) to wrap around the label. Default = &#8220;&#8221; (no tag).</p>\n\n<p><code>label_class=&quot;class name&quot;</code></p>\n\n<p>- the class to be used on the label&#8217;s tag.  Default = “menu_label”.</p>\n\n<p><code>label_id=&quot;id name&quot;</code></p>\n\n<p>- the ID to be used on the label&#8217;s tag.  Default = &#8220;&#8221; (none).</p>\n\n<p><code>prefix=&quot;text&quot;</code></p>\n\n<p>- some text to precede the content <em>inside</em> the wraptag. Default = &#8220;&#8221; (no prefix).</p>\n\n<p><code>suffix=&quot;text&quot;</code></p>\n\n<p>- some text to appended to the end of the content <em>inside</em> the wraptag. Default = &#8220;&#8221; (no suffix).</p>\n\n<p><code>html_attribute=&quot;text&quot;</code></p>\n\n<p>- an additional <span class=\"caps\">HTML</span> attribute to add to the wraptag. <em>Underscores are converted to hyphens</em>. Default = &#8220;&#8221; (no extra <span class=\"caps\">HTML</span> attribute).</p>\n\n<p><code>trim=&quot;boolean&quot;</code></p>\n\n<p>- set to “1” to trim whitespace off the ends of your wrapped content.  Default for wraptag = “0” (no trim).  Default for prefix/suffix = “1” (trim).</p>\n\n<h2><strong>Examples</strong></h2>\n\n<p>Typically <strong>adi_wrap</strong> is of most use when you need something wrapped up in an <span class=\"caps\">HTML</span> tag &#8230;</p>\n\n<p>&#8230; but the contained code doesn&#8217;t have its own wraptag attribute, e.g.:</p>\n\n<pre><code>&lt;txp:adi_wrap wraptag=&quot;em&quot;&gt;\n	&lt;txp:custom_field name=&quot;might_be_blank&quot; /&gt;\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p>&#8230; or that the contained statements may not produce any output, e.g.:</p>\n\n<pre><code>&lt;txp:adi_wrap wraptag=&quot;p&quot;&gt;\n	&lt;txp:custom_field name=&quot;might_be_blank&quot; /&gt;\n	&lt;txp:custom_field name=&quot;might_be_blank_too&quot; /&gt;\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p>&#8230; or you want to double wrap something:</p>\n\n<pre><code>&lt;txp:adi_wrap&gt;\n	&lt;txp:article wraptag=&quot;ul&quot; break=&quot;li&quot; keywords=&quot;uncommon&quot;&gt;\n		...contained statements...\n	&lt;/txp:article&gt;\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p><em>Note that <strong>adi_wrap</strong>, in the above example, will use the default wraptag of “div”.</em></p>\n\n<p>You can add additional <span class=\"caps\">HTML</span> attributes to the wrap tag.  For example <code>role</code>:</p>\n\n<pre><code>&lt;txp:adi_wrap wraptag=&quot;nav&quot; role=&quot;navigation&quot;&gt;\n		...contained statements...\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p>will generate <code>&lt;nav role=&quot;navigation&quot;&gt; ... &lt;/nav&gt;</code></p>\n\n<pre><code>&lt;txp:adi_wrap wraptag=&quot;li&quot; data_theme=&quot;classic&quot;&gt;\n		...contained statements...\n&lt;/txp:adi_wrap&gt;</code></pre>\n\n<p>will generate <code>&lt;li data-theme=&quot;classic&quot;&gt; ... &lt;/li&gt;</code></p>\n\n<p><em>Note that you need to specify the attribute using underscores in place of hyphens, these are converted automatically for the markup.</em></p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=33976\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_wrap\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n/*\n	adi_wrap - Automatic wrapper\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- general purpose attributes, e.g. role=\"\", data_thingy=\"\" (hypens not allowed in attribute names, auto converted)\n			- \'prefix\' & \'suffix\' attributes\n			- \'trim\' attribute (default off for wraptag, on for prefix/suffix)\n			- attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n			- TXP 4.6 tag registration\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_wrap\')\n	;\n}\n\nfunction adi_wrap($atts,$thing=NULL) {\n\n	$pairs = array(\n		\'wraptag\'		=> \'\',		// the wraptag\n		\'class\'			=> \'\',		// class for wraptag\n		\'html_id\'		=> \'\',		// HTML id for wraptag\n		\'label\'			=> \'\',		// label string to precede wrap\n		\'labeltag\'		=> \'\',		// tag to wrap around label\n		\'label_class\'	=> \'\',		// CSS class for label\'s tag\n		\'label_id\'		=> \'\',		// HTML ID for label\'s tag\n		\'prefix\'		=> \'\',		// in the begining\n		\'suffix\'		=> \'\',		// at the end of the day\n		\'trim\'			=> 0,		// trim\n	);\n\n	extract(lAtts($pairs, $atts,FALSE));\n\n	$other_atts = array_diff_key($atts,$pairs); // carve off non-standard atts\n\n	$output = parse($thing);\n\n	if (trim($output)) { // there\'s something to output\n\n		if ($trim)\n			$output = trim($output);\n\n		if (!($wraptag || $prefix || $suffix)) // default wrap tag\n			$wraptag = \'div\';\n\n		if ($label)\n			if (empty($labeltag))\n				$label_output = $label;\n			else\n				$label_output = doTag($label,$labeltag,$label_class,\'\',$label_id);\n		else\n			$label_output = \'\';\n\n		if ($wraptag) {\n			$attributes = \'\';\n			foreach ($other_atts as $other_att => $other_value) // bundle up \"free_form\" attributes, convert underscore to hyphen: \"free-form\"\n				$attributes .= \' \'.str_replace(\'_\',\'-\',$other_att).\'=\"\'.trim($other_value).\'\"\';\n			return $label_output.doTag($prefix.$output.$suffix,$wraptag,$class,$attributes,$html_id); // it\'s a wrap\n		}\n		else if (!(($prefix == \'\') && ($suffix == \'\'))) {\n			if (array_key_exists(\'trim\',$atts) === FALSE) // trim not specified, so default on\n				$output = trim($output);\n\n			return $label_output.$prefix.$output.$suffix; // low-calorie wrap\n		}\n		else\n			return $label_output.$output;\n\n	}\n\n}\n', '\n/*\n	adi_wrap - Automatic wrapper\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- general purpose attributes, e.g. role=\"\", data_thingy=\"\" (hypens not allowed in attribute names, auto converted)\n			- \'prefix\' & \'suffix\' attributes\n			- \'trim\' attribute (default off for wraptag, on for prefix/suffix)\n			- attributes: \'label\', \'labeltag\', \'label_class\', \'label_id\'\n			- TXP 4.6 tag registration\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_wrap\')\n	;\n}\n\nfunction adi_wrap($atts,$thing=NULL) {\n\n	$pairs = array(\n		\'wraptag\'		=> \'\',		// the wraptag\n		\'class\'			=> \'\',		// class for wraptag\n		\'html_id\'		=> \'\',		// HTML id for wraptag\n		\'label\'			=> \'\',		// label string to precede wrap\n		\'labeltag\'		=> \'\',		// tag to wrap around label\n		\'label_class\'	=> \'\',		// CSS class for label\'s tag\n		\'label_id\'		=> \'\',		// HTML ID for label\'s tag\n		\'prefix\'		=> \'\',		// in the begining\n		\'suffix\'		=> \'\',		// at the end of the day\n		\'trim\'			=> 0,		// trim\n	);\n\n	extract(lAtts($pairs, $atts,FALSE));\n\n	$other_atts = array_diff_key($atts,$pairs); // carve off non-standard atts\n\n	$output = parse($thing);\n\n	if (trim($output)) { // there\'s something to output\n\n		if ($trim)\n			$output = trim($output);\n\n		if (!($wraptag || $prefix || $suffix)) // default wrap tag\n			$wraptag = \'div\';\n\n		if ($label)\n			if (empty($labeltag))\n				$label_output = $label;\n			else\n				$label_output = doTag($label,$labeltag,$label_class,\'\',$label_id);\n		else\n			$label_output = \'\';\n\n		if ($wraptag) {\n			$attributes = \'\';\n			foreach ($other_atts as $other_att => $other_value) // bundle up \"free_form\" attributes, convert underscore to hyphen: \"free-form\"\n				$attributes .= \' \'.str_replace(\'_\',\'-\',$other_att).\'=\"\'.trim($other_value).\'\"\';\n			return $label_output.doTag($prefix.$output.$suffix,$wraptag,$class,$attributes,$html_id); // it\'s a wrap\n		}\n		else if (!(($prefix == \'\') && ($suffix == \'\'))) {\n			if (array_key_exists(\'trim\',$atts) === FALSE) // trim not specified, so default on\n				$output = trim($output);\n\n			return $label_output.$prefix.$output.$suffix; // low-calorie wrap\n		}\n		else\n			return $label_output.$output;\n\n	}\n\n}\n', '4aebe83e4d4926598f0f9af1b25093d4', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('adi_list', 0, 'Adi Gilbert', 'http://www.greatoceanmedia.com.au/', '0.2', 'Use lists like arrays', '<h1><strong>adi_list</strong> &#8211; Use lists like arrays</h1>\n\n<p>This plugin enables you to use lists like you would arrays:</p>\n\n<ul>\n	<li>access individual elements directly</li>\n	<li>move forward &amp; backward through the list</li>\n	<li>repeat a list ad infinitum</li>\n</ul>\n\n<h2><strong>What&#8217;s the difference between adi_list and smd_each or rah_repeat?</strong></h2>\n\n<p><a href=\"http://forum.textpattern.com/viewtopic.php?id=27464\" rel=\"nofollow\">smd_each</a> and <a href=\"http://forum.textpattern.com/viewtopic.php?id=32384\" rel=\"nofollow\">rah_repeat</a> both drive the iteration loop through a list. <strong>adi_list</strong> doesn&#8217;t &#8211; it&#8217;s designed to be used in conjunction with some other looping process, e.g. <code>&lt;txp:article&gt;</code>, and you nudge through the list manually.</p>\n\n<p><strong>adi_list</strong> can also extend the “life” of a list by repeating it, supplying a default value, or by jumping backwards &amp; forwards at will.</p>\n\n<p>Have a look at the <a href=\"#examples\" rel=\"nofollow\">examples</a> to see what&#8217;s possible.</p>\n\n<h2><strong>Usage summary</strong></h2>\n\n<p>To set up a list, <strong>adi_list</strong> may be used as a single or container tag:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,b,c&quot; /&gt;</code></p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot;&gt;a,b,c&lt;/txp:adi_list&gt;</code></p>\n\n<p>- these are functionally equivalent and both initialise the list.</p>\n\n<p>To access items in the list or to move through the list, use <strong>adi_list</strong> as a single tag, for example:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>- returns the whole list</p>\n\n<p><code>&lt;txp:adi_list_item name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>- returns the value of the current item</p>\n\n<p><code>&lt;txp:adi_list_next name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>- moves the pointer forward through the list</p>\n\n<h2><strong>Tags</strong></h2>\n\n<p>Setting up or displaying information about lists:</p>\n\n<ul>\n	<li>adi_list</li>\n	<li>adi_list_count</li>\n	<li>adi_list_default</li>\n</ul>\n\n<p>Retrieving list item values:</p>\n\n<ul>\n	<li>adi_list_item</li>\n	<li>adi_list_first</li>\n	<li>adi_list_last</li>\n</ul>\n\n<p>Moving through lists:</p>\n\n<ul>\n	<li>adi_list_next</li>\n	<li>adi_list_prev</li>\n	<li>adi_list_reset</li>\n	<li>adi_list_end</li>\n</ul>\n\n<p>Conditionals:</p>\n\n<ul>\n	<li>adi_if_list</li>\n</ul>\n\n<h2><strong>Setting up the list</strong></h2>\n\n<p>Use the <em>adi_list</em> tag for this.</p>\n\n<p>Attributes:</p>\n\n<p><code>name=&quot;list name&quot;</code></p>\n\n<p>- the name of the list. This attribute is mandatory.</p>\n\n<p>Give the list some values using <em>one</em> of the following (or use it as a container tag):</p>\n\n<p><code>value=&quot;list items&quot;</code></p>\n\n<p>- the contents of the list.</p>\n\n<p><code>txpvar=&quot;TXP variable name&quot;</code></p>\n\n<p>- set the contents of the list from a <span class=\"caps\">TXP</span> variable.</p>\n\n<p><code>field=&quot;value&quot;</code></p>\n\n<p>- set the contents of the list from an article&#8217;s custom field or article image field.</p>\n\n<p><code>separator=&quot;text&quot;</code></p>\n\n<p>- this separates the different elements of the list. Default = “,” (comma).</p>\n\n<p><code>default_separator=&quot;text&quot;</code></p>\n\n<p>- used to delineate the default value from the other items in the list. Default = “|” (bar). For example: <code>value=&quot;a,b,c|z&quot;</code> &#8211; is a list “a,b,c” with a default value of “z”.</p>\n\n<p>Examples:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,b,c&quot; /&gt;</code></p>\n\n<p>- simple list set up</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; txpvar=&quot;myvar&quot; /&gt;</code></p>\n\n<p>- get list from previously assigned <span class=\"caps\">TXP</span> variable</p>\n\n<h2><strong>Accessing items in the list</strong></h2>\n\n<p>You can access items iteratively:</p>\n\n<p>- when the list is set up a pointer will point at the first item<br />\n- the pointer can then be moved through the list using <strong>adi_list</strong> tags</p>\n\n<p>Or directly:</p>\n\n<p>- access a list item according to its index<br />\n- access the first or last items</p>\n\n<p>To get the current item value in the list:</p>\n\n<p><code>&lt;txp:adi_list_item name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>To get a specific item value from the list, using index:</p>\n\n<p><code>&lt;txp:adi_list_item name=&quot;mylist&quot; index=&quot;1&quot; /&gt;</code></p>\n\n<p>- returns the <em>second</em> item (<em>indexes start at zero</em>), without moving pointer</p>\n\n<p>Get the first item value:</p>\n\n<p><code>&lt;txp:adi_list_first name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>- returns first item value, without moving pointer</p>\n\n<p>Get the last item value:</p>\n\n<p><code>&lt;txp:adi_list_last name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>- returns last item value, without moving pointer</p>\n\n<h2><strong>Moving through the list</strong></h2>\n\n<p>Move the list pointer forward:</p>\n\n<p><code>&lt;txp:adi_list_next name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>Move the list pointer backward:</p>\n\n<p><code>&lt;txp:adi_list_prev name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>Move the list pointer to the start:</p>\n\n<p><code>&lt;txp:adi_list_reset name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>Move the list pointer to the end:</p>\n\n<p><code>&lt;txp:adi_list_end name=&quot;mylist&quot; /&gt;</code></p>\n\n<h2><strong>Manipulating the list</strong></h2>\n\n<p>To repeat the list sequence indefinitely use:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,b,c&quot; repeat=&quot;1&quot; /&gt;</code></p>\n\n<p>- the list becomes: “a,b,c,a,b,c,a,b,c,a,b,c &#8230;”</p>\n\n<p>You can set a default value as a fallback in two ways, firstly using an attribute:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,b,c&quot; default=&quot;z&quot; /&gt;</code></p>\n\n<p>- the list becomes: “a,b,c,z,z,z,z,z &#8230;”</p>\n\n<p>and secondly, by embedding it at the end of the list:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot;&gt;a,b,c|z&lt;/txp:adi_list&gt;</code></p>\n\n<p>- the default default-separator is “|” but can be changed using the ‘default_separator’ attribute.</p>\n\n<p>Randomising the list:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,b,c,d,e&quot; random=&quot;1&quot; /&gt;</code></p>\n\n<p>- the list becomes: “a,e,d,c,b” or “c,a,e,d,b” or “c,d,e,b,a” &#8230; etc. etc.</p>\n\n<h2><strong>Getting information about the list</strong></h2>\n\n<p>To get the original list:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>The number of items in list:</p>\n\n<p><code>&lt;txp:adi_list_count name=&quot;mylist&quot; /&gt;</code></p>\n\n<p>The list&#8217;s default value:</p>\n\n<p><code>&lt;txp:adi_list_default name=&quot;mylist&quot; /&gt;</code></p>\n\n<h2><strong>A list of notes</strong></h2>\n\n<ul>\n	<li>Indexes start at zero</li>\n	<li>If no default is set, an empty value will be returned if you fall off either end of the list or try to directly access an item that&#8217;s not there</li>\n	<li>To iterate though the list you must do it explicitly e.g. <code>&lt;txp:adi_list_next name=&quot;mylist&quot; /&gt;</code></li>\n</ul>\n\n<h2><strong>Examples</strong></h2>\n\n<p>As mentioned before, <strong>adi_list</strong> doesn&#8217;t do the looping.  The following example uses an article tag for this.</p>\n\n<h3><em>Accessing a simple repeating list</em></h3>\n\n<p>List set up:</p>\n\n<p><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;red,green,blue&quot; repeat=&quot;1&quot; /&gt;</code></p>\n\n<p>Using the list:</p>\n\n<pre><code>&lt;txp:article&gt;\n	&lt;h2 class=&quot;&lt;txp:adi_list_item name=&quot;mylist&quot; /&gt;&quot;&gt;&lt;txp:title /&gt;&lt;/h2&gt;\n	&lt;txp:adi_list_next name=&quot;mylist&quot; /&gt;\n&lt;/txp:article&gt;</code></pre>\n\n<p>The article titles will have classes of “red”, “green”, “blue”,  “red”, “green”, “blue” and so on.</p>\n\n<p>In real life you&#8217;d probably use <span class=\"caps\">CSS</span> selectors to assign classes, but it works as an illustration.</p>\n\n<h3><em>A more likely scenario</em></h3>\n\n<p>Where <strong>adi_list</strong> comes into it&#8217;s own is when you&#8217;re dealing with a non-sequential list and/or you want to output different content or markup at different positions.</p>\n\n<p>Consider the following:</p>\n\n<pre><code>&lt;txp:adi_list name=&quot;mylist&quot; value=&quot;a,c,b,b,a,a,d&quot; repeat=&quot;1&quot; /&gt;\n\n&lt;txp:article&gt;\n	&lt;txp:adi_if_list name=&quot;mylist&quot; value=&quot;a&quot;&gt;\n		... tags for &quot;a&quot;\n	&lt;txp:else /&gt;\n		&lt;txp:adi_if_list name=&quot;mylist&quot; value=&quot;b&quot;&gt;\n			... tags for &quot;b&quot;\n		&lt;txp:else /&gt;\n			... tags for others\n		&lt;/txp:adi_if_list&gt;\n	&lt;/txp:adi_if_list&gt;\n	&lt;txp:adi_list_next name=&quot;mylist&quot; /&gt;\n&lt;/txp:article&gt;</code></pre>\n\n<p>Here you can alter what&#8217;s output based on criteria which is <em>independent</em> of the content.</p>\n\n<h2><strong>Additional information</strong></h2>\n\n<p>Support and further information can be obtained from the <a href=\"http://forum.textpattern.com/viewtopic.php?id=41313\" rel=\"nofollow\">Textpattern support forum</a>. A copy of this help is also available <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_list\" rel=\"nofollow\">online</a>.  More adi_plugins can be found <a href=\"http://www.greatoceanmedia.com.au/txp/\" rel=\"nofollow\">here</a>.</p>', '\n// NOTE\n\n/*\n	adi_list - Use lists like arrays\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- TXP 4.6 tag registration\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_if_list\')\n		->register(\'adi_list_count\')\n		->register(\'adi_list_debug\')\n		->register(\'adi_list_default\')\n		->register(\'adi_list_end\')\n		->register(\'adi_list_first\')\n		->register(\'adi_list_item\')\n		->register(\'adi_list_last\')\n		->register(\'adi_list_next\')\n		->register(\'adi_list_prev\')\n		->register(\'adi_list_reset\')\n		->register(\'adi_list\')\n	;\n}\n\nfunction adi_list_count($atts) {\n// return number of items in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'count\' => 1));\n}\n\nfunction adi_list_item($atts) {\n// return value of specific (or current) item\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n		\'index\'			=> \'\',	// item index\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'index\' => $index));\n}\n\nfunction adi_list_first($atts) {\n// return first value in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'index\' => 0));\n}\n\nfunction adi_list_last($atts) {\n// return last value in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'last\' => 1));\n}\n\nfunction adi_list_next($atts) {\n// advance pointer\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'next\' => 1));\n}\n\nfunction adi_list_prev($atts) {\n// rewind pointer\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'prev\' => 1));\n}\n\nfunction adi_list_reset($atts) {\n// move pointer to start of list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'reset\' => 1));\n}\n\nfunction adi_list_end($atts) {\n// move pointer to end of list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'end\' => 1));\n}\n\nfunction adi_list_default($atts) {\n// return default value\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	if (${$list}[\'default\'] !== NULL)\n		return ${$list}[\'default\'];\n}\n\nfunction adi_if_list($atts,$thing) {\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n		\'value\'			=> \'\',	// value to test\n		\'index\'			=> \'\',	// index to test\n// 		\'overrun\'		=> \'\',	// test if gone past end of original list\n	),$atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	if ($value !== \'\')\n		return parse(EvalElse($thing,($value == current(${$list}[\'value\']))));\n	if ($index !== \'\')\n		return parse(EvalElse($thing,($index == key(${$list}[\'value\']))));\n// 	if ($overrun)\n// 		return parse(EvalElse($thing,(key($$list) > count($$list))));\n}\n\nfunction adi_list($atts,$thing=NULL) {\n// set up list or return item values\n	global $variable,$thisarticle;\n\n	extract(lAtts(array(\n		\'name\'				=> \'\',		// the list name\n		\'value\'				=> \'\',		// set value of list\n		\'txpvar\'			=> \'\',		// get list from TXP variable\n		\'field\'				=> \'\',		// get list from custom field (incl. Article image)\n		\'separator\'			=> \',\',		// list separator\n		\'default_separator\'	=> \'|\',		// default value separator\n		\'index\'				=> \'\',		// return specific item index (default = current)\n		\'default\'			=> NULL,	// default value\n		\'last\'				=> \'0\',		// last value\n		\'count\'				=> \'0\',		// number of items in list\n		\'next\'				=> \'0\',		// advance pointer by one\n		\'prev\'				=> \'0\',		// rewind pointer by one\n		\'reset\'				=> \'0\',		// rewind pointer to start\n		\'end\'				=> \'0\',		// advance pointer to end\n		\'repeat\'			=> \'0\',		// repeat list sequence\n		\'random\'			=> \'0\',		// randomise the list\n	), $atts));\n\n	// clean up attribute values\n	$name = trim($name);\n	$index = trim($index);\n	$field = strtolower($field);\n\n	// mandatory attribute\n	if (empty($name)) {\n		echo \"adi_list: list name required\".br;\n		return;\n	}\n\n	// adi_list global\n	// $adi_list_list_mylist(\'value\' => \"this,is,the,list\", \'default\' => \"value\", \'repeat\' => 0/1, \'separator\' => \"value\")\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	// if value attribute supplied - set things up\n	if (array_key_exists(\'value\', $atts) || ($thing !== NULL) || $txpvar || $field) { // list supplied as attribute OR container mode OR TXP variable\n		$$list = array();\n		// TXP variable\n		if ($txpvar)\n			$value = (isset($variable[$txpvar]) ? $variable[$txpvar] : \'\');\n		// article field\n		if ($field) {\n			if ($field == \'article image\')\n				$field = \'article_image\';\n			$value = (isset($thisarticle[$field]) ? $thisarticle[$field] : \'\');\n		}\n		// container mode\n		if ($thing !== NULL)\n			$value = parse($thing);\n		// default\n		$embedded_default = explode($default_separator,$value);\n		if (isset($embedded_default[1])) { // look for embedded default at end, e.g. a,b,c|z\n			${$list}[\'default\'] = $embedded_default[1];\n			$value = $embedded_default[0]; // reassign value from begining\n		}\n		else\n			${$list}[\'default\'] = $default;\n		// convert value to array\n		${$list}[\'value\'] = explode($separator,$value);\n		// randomise?\n		if ($random)\n			shuffle(${$list}[\'value\']);\n		// repeat\n		${$list}[\'repeat\'] = $repeat;\n		// list separator\n		${$list}[\'separator\'] = $separator;\n		// all done\n		return;\n	}\n\n	// check list exists\n	if (isset($$list))\n		$repeat = ${$list}[\'repeat\'];\n	else {\n		echo \'adi_list: list \"\'.$name.\'\" (\'.$list.\') not found\'.br;\n		return;\n	}\n\n	// list info\n	if ($count) {\n		return count(${$list}[\'value\']); // number of items in list\n	}\n\n	// pointer stuff\n	if ($next) {\n		$res = next(${$list}[\'value\']); // advance pointer by one\n		if ($repeat && ($res === FALSE)) // fallen off the end\n			reset(${$list}[\'value\']); // go back to the beginning\n		return;\n	}\n	if ($prev) {\n		$res = prev(${$list}[\'value\']); // rewind pointer by one\n		if ($repeat && ($res === FALSE)) // tripped backward over the start\n			end(${$list}[\'value\']); // go to the end\n		return;\n	}\n	if ($reset) {\n		reset(${$list}[\'value\']); // rewind pointer to start\n		return;\n	}\n	if ($end) {\n		end(${$list}[\'value\']); // advance pointer to end\n		return;\n	}\n\n	// return something\n	if ($last) { // last item\n		return implode(array_slice(${$list}[\'value\'],-1,1));\n	}\n	else if (array_key_exists(\'index\', $atts)) { // index attribute supplied\n		if ($index === \'\') { // empty index supplied, look for current (or default)\n			if (current(${$list}[\'value\']) === FALSE) {\n				if (${$list}[\'default\'] !== NULL) // return default\n					return ${$list}[\'default\'];\n				else\n					return \'\'; // fire a blank\n			}\n			else\n				return current(${$list}[\'value\']); // return current value\n		}\n		else if (isset(${$list}[\'value\'][$index])) // index found\n			return ${$list}[\'value\'][$index];\n		else {\n			if ($repeat) { // massage index & repeat\n				$new_index = $index % count(${$list}[\'value\']);\n				return ${$list}[\'value\'][$new_index];\n			}\n			else if (${$list}[\'default\'] !== NULL) // return default\n				return ${$list}[\'default\'];\n			else\n				return \'\'; // fire a blank\n		}\n	}\n	else { // return the whole list\n		return implode(${$list}[\'separator\'],${$list}[\'value\']);\n	}\n}\n\nfunction adi_list_debug($atts) {\n// diagnostics\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',	// the list name\n	), $atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	echo \'adi_list_debug: list \"\'.$name.\'\" (\'.$list.\') \';\n\n	if (isset($$list)) {\n		echo \'= \"\'\n			.implode(${$list}[\'separator\'],${$list}[\'value\'])\n			.\'\"; \'\n			.(${$list}[\'default\'] === NULL ? \'no default\' : \'default = \"\'.${$list}[\'default\'].\'\"\')\n			.\'; repeat = \'\n			.${$list}[\'repeat\']\n			.\'; separator = \"\'\n			.${$list}[\'separator\']\n			.\'\"; pointer = \'\n			.key(${$list}[\'value\'])\n			;\n		dmp($$list);\n	}\n	else\n		echo \'not found\';\n\n	echo br;\n}\n', '\n// NOTE\n\n/*\n	adi_list - Use lists like arrays\n\n	Written by Adi Gilbert\n\n	Released under the GNU General Public License\n\n	Version history:\n	0.2		- TXP 4.6 tag registration\n	0.1		- initial release\n\n*/\n\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'adi_if_list\')\n		->register(\'adi_list_count\')\n		->register(\'adi_list_debug\')\n		->register(\'adi_list_default\')\n		->register(\'adi_list_end\')\n		->register(\'adi_list_first\')\n		->register(\'adi_list_item\')\n		->register(\'adi_list_last\')\n		->register(\'adi_list_next\')\n		->register(\'adi_list_prev\')\n		->register(\'adi_list_reset\')\n		->register(\'adi_list\')\n	;\n}\n\nfunction adi_list_count($atts) {\n// return number of items in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'count\' => 1));\n}\n\nfunction adi_list_item($atts) {\n// return value of specific (or current) item\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n		\'index\'			=> \'\',	// item index\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'index\' => $index));\n}\n\nfunction adi_list_first($atts) {\n// return first value in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'index\' => 0));\n}\n\nfunction adi_list_last($atts) {\n// return last value in list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'last\' => 1));\n}\n\nfunction adi_list_next($atts) {\n// advance pointer\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'next\' => 1));\n}\n\nfunction adi_list_prev($atts) {\n// rewind pointer\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'prev\' => 1));\n}\n\nfunction adi_list_reset($atts) {\n// move pointer to start of list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'reset\' => 1));\n}\n\nfunction adi_list_end($atts) {\n// move pointer to end of list\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	return adi_list(array(\'name\' => $name,\'end\' => 1));\n}\n\nfunction adi_list_default($atts) {\n// return default value\n\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n	), $atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	if (${$list}[\'default\'] !== NULL)\n		return ${$list}[\'default\'];\n}\n\nfunction adi_if_list($atts,$thing) {\n	extract(lAtts(array(\n		\'name\'			=> \'\',	// the list name\n		\'value\'			=> \'\',	// value to test\n		\'index\'			=> \'\',	// index to test\n// 		\'overrun\'		=> \'\',	// test if gone past end of original list\n	),$atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	if ($value !== \'\')\n		return parse(EvalElse($thing,($value == current(${$list}[\'value\']))));\n	if ($index !== \'\')\n		return parse(EvalElse($thing,($index == key(${$list}[\'value\']))));\n// 	if ($overrun)\n// 		return parse(EvalElse($thing,(key($$list) > count($$list))));\n}\n\nfunction adi_list($atts,$thing=NULL) {\n// set up list or return item values\n	global $variable,$thisarticle;\n\n	extract(lAtts(array(\n		\'name\'				=> \'\',		// the list name\n		\'value\'				=> \'\',		// set value of list\n		\'txpvar\'			=> \'\',		// get list from TXP variable\n		\'field\'				=> \'\',		// get list from custom field (incl. Article image)\n		\'separator\'			=> \',\',		// list separator\n		\'default_separator\'	=> \'|\',		// default value separator\n		\'index\'				=> \'\',		// return specific item index (default = current)\n		\'default\'			=> NULL,	// default value\n		\'last\'				=> \'0\',		// last value\n		\'count\'				=> \'0\',		// number of items in list\n		\'next\'				=> \'0\',		// advance pointer by one\n		\'prev\'				=> \'0\',		// rewind pointer by one\n		\'reset\'				=> \'0\',		// rewind pointer to start\n		\'end\'				=> \'0\',		// advance pointer to end\n		\'repeat\'			=> \'0\',		// repeat list sequence\n		\'random\'			=> \'0\',		// randomise the list\n	), $atts));\n\n	// clean up attribute values\n	$name = trim($name);\n	$index = trim($index);\n	$field = strtolower($field);\n\n	// mandatory attribute\n	if (empty($name)) {\n		echo \"adi_list: list name required\".br;\n		return;\n	}\n\n	// adi_list global\n	// $adi_list_list_mylist(\'value\' => \"this,is,the,list\", \'default\' => \"value\", \'repeat\' => 0/1, \'separator\' => \"value\")\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	// if value attribute supplied - set things up\n	if (array_key_exists(\'value\', $atts) || ($thing !== NULL) || $txpvar || $field) { // list supplied as attribute OR container mode OR TXP variable\n		$$list = array();\n		// TXP variable\n		if ($txpvar)\n			$value = (isset($variable[$txpvar]) ? $variable[$txpvar] : \'\');\n		// article field\n		if ($field) {\n			if ($field == \'article image\')\n				$field = \'article_image\';\n			$value = (isset($thisarticle[$field]) ? $thisarticle[$field] : \'\');\n		}\n		// container mode\n		if ($thing !== NULL)\n			$value = parse($thing);\n		// default\n		$embedded_default = explode($default_separator,$value);\n		if (isset($embedded_default[1])) { // look for embedded default at end, e.g. a,b,c|z\n			${$list}[\'default\'] = $embedded_default[1];\n			$value = $embedded_default[0]; // reassign value from begining\n		}\n		else\n			${$list}[\'default\'] = $default;\n		// convert value to array\n		${$list}[\'value\'] = explode($separator,$value);\n		// randomise?\n		if ($random)\n			shuffle(${$list}[\'value\']);\n		// repeat\n		${$list}[\'repeat\'] = $repeat;\n		// list separator\n		${$list}[\'separator\'] = $separator;\n		// all done\n		return;\n	}\n\n	// check list exists\n	if (isset($$list))\n		$repeat = ${$list}[\'repeat\'];\n	else {\n		echo \'adi_list: list \"\'.$name.\'\" (\'.$list.\') not found\'.br;\n		return;\n	}\n\n	// list info\n	if ($count) {\n		return count(${$list}[\'value\']); // number of items in list\n	}\n\n	// pointer stuff\n	if ($next) {\n		$res = next(${$list}[\'value\']); // advance pointer by one\n		if ($repeat && ($res === FALSE)) // fallen off the end\n			reset(${$list}[\'value\']); // go back to the beginning\n		return;\n	}\n	if ($prev) {\n		$res = prev(${$list}[\'value\']); // rewind pointer by one\n		if ($repeat && ($res === FALSE)) // tripped backward over the start\n			end(${$list}[\'value\']); // go to the end\n		return;\n	}\n	if ($reset) {\n		reset(${$list}[\'value\']); // rewind pointer to start\n		return;\n	}\n	if ($end) {\n		end(${$list}[\'value\']); // advance pointer to end\n		return;\n	}\n\n	// return something\n	if ($last) { // last item\n		return implode(array_slice(${$list}[\'value\'],-1,1));\n	}\n	else if (array_key_exists(\'index\', $atts)) { // index attribute supplied\n		if ($index === \'\') { // empty index supplied, look for current (or default)\n			if (current(${$list}[\'value\']) === FALSE) {\n				if (${$list}[\'default\'] !== NULL) // return default\n					return ${$list}[\'default\'];\n				else\n					return \'\'; // fire a blank\n			}\n			else\n				return current(${$list}[\'value\']); // return current value\n		}\n		else if (isset(${$list}[\'value\'][$index])) // index found\n			return ${$list}[\'value\'][$index];\n		else {\n			if ($repeat) { // massage index & repeat\n				$new_index = $index % count(${$list}[\'value\']);\n				return ${$list}[\'value\'][$new_index];\n			}\n			else if (${$list}[\'default\'] !== NULL) // return default\n				return ${$list}[\'default\'];\n			else\n				return \'\'; // fire a blank\n		}\n	}\n	else { // return the whole list\n		return implode(${$list}[\'separator\'],${$list}[\'value\']);\n	}\n}\n\nfunction adi_list_debug($atts) {\n// diagnostics\n\n	extract(lAtts(array(\n		\'name\'		=> \'\',	// the list name\n	), $atts));\n\n	// adi_list global\n	$list = \'$adi_list_list_\'.$name;\n	global $$list;\n\n	echo \'adi_list_debug: list \"\'.$name.\'\" (\'.$list.\') \';\n\n	if (isset($$list)) {\n		echo \'= \"\'\n			.implode(${$list}[\'separator\'],${$list}[\'value\'])\n			.\'\"; \'\n			.(${$list}[\'default\'] === NULL ? \'no default\' : \'default = \"\'.${$list}[\'default\'].\'\"\')\n			.\'; repeat = \'\n			.${$list}[\'repeat\']\n			.\'; separator = \"\'\n			.${$list}[\'separator\']\n			.\'\"; pointer = \'\n			.key(${$list}[\'value\'])\n			;\n		dmp($$list);\n	}\n	else\n		echo \'not found\';\n\n	echo br;\n}\n', '2d40ca9280ce25d0627578f088709cc8', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_image_upload', 1, 'redbot', 'http://www.redbot.it/txp', '0.6.4beta', 'Upload, edit, sort and show images within the \'write\' tab', '<h2>Image upload help </h2>\n  <h3>Overview</h3>\n  <p>This plugin allows to insert, upload, sort, show and edit  images within the ‘write’ tab.</p>\n<h3>Features</h3>\n  <ul>\n    <li>Multiple fields (\"article image\" or  any other custom field) can be set for the plugin to operate.</li>\n    <li>A field can be set to accept single or multiple  images</li>\n    <li>Image thumbnails associated with an article are shown in the \'write\' tab </li>\n    <li>Drag\'n\'drop support for easy images sorting in the \'write\' tab\'</li>\n  </ul>\n  <h3>Examples</h3>\n  <p>Set the \"article image\" field to accept   only one image,  custom field 2 to accept a list of them for a gallery,  custom field 3 for another gallery  and so on.</p>\n  <h3>Installation | configuration </h3>\n  <p> Paste the code into the  Admin &gt; Plugins tab, install and enable the plugin. If you want to sort images  (optional) you must upload the jquery ui script which comes with the  plugin. Default location is in \'textpattern\' directory. If you choose  another you must change the path in the plugins preferences  (bot_iu_ui_path).</p>\n  <p>If you want to change some default settings configure the plugin  directly in the plugins code. Go to plugins &gt; edit and  search for  ‘Global preferences - Configuration’ in the first lines. <br/>\n    You\'ll see something like this: </p>\n  <p><code>\'bot_iu_fields\' =&gt; \'#article-image // fields to be used<br/>\n    \'bot_iu_mono_list\' =&gt; \'\', // filelds with single image<br/>\n    \'bot_choose_image_text\' =&gt; gTxt(\'Choose image\'), // text for \"Choose images\" link and button<br/>\n    \'bot_image_delete_text\' =&gt; gTxt(\'Delete image\'), // text and title for \"delete image\" button<br/>\n    \'bot_image_edit_text\' =&gt; gTxt(\'Edit image\'), // text and title for \"edit image\" button\"<br/>\n    \'bot_add_image_text\' =&gt; gTxt(\'Add\'), // text for \"add image\" checkbox<br/>\n    \'bot_iu_save_text\' =&gt; gTxt(\'save\'), // text for \"Save\" button\"<br/>\n    \'bot_iu_cancel_text\' =&gt; gTxt(\'cancel\'), // text for \"Cancel\" button\"<br/>\n    \'bot_iu_row_bg\' =&gt; \'#eaeaea\', // selected row background<br/>\n    \'bot_iu_ui_path\' =&gt; \'jquery-ui-1.8.9.custom.min.js\', // path to the  jQuery ui script. Defaults to the \'textpattern\' directory</code></p>\n  <p>To configure change the part that comes after the \"=&gt;\" and its enclosed in single quotes. Read comments for help.</p>\n  <p>The most important configuration options are the first two: </p>\n  <ol>\n    <li>\"bot_iu_fields\" determines  the fields which will be used (can be \"article image\" or/and  any other custom field)</li>\n    <li>\'bot_iu_mono_list\' sets the fields which will accept only a single image</li>\n  </ol>\n  <p>Each of them takes a comma separated list of values which <strong>must be</strong> in this form:</p>\n  <p>#article-image for article image and <br/>\n    #custom-n for custom fields where n is the custom field id </p>\n  <p>For an example I could configure them this way:</p>\n  <p><code>\'bot_iu_fields\' =&gt; \'#article-image, #custom-3, #custom-2\' // fields to be used<br/>\n    \'bot_iu_mono_list\' =&gt; \'#article-image, #custom-3,\', // fields with single image </code></p>\n  <p>This plugin can be quite easily adapted to your needs, from text to  aspect. If you want to modify its look edit the css in the function  \'bot_image_css\'. For more complex mods you will save some time if you  have a look at the underlying html structure first.</p>\n  <p>Keep in mind that the iframe content style cannot be addressed throught this css: you\'ll have to add or change some js lines.</p>\n  <h3>Tips </h3>\n  <p>This isn\'t strictly related to the plugin but can be useful to know.   If you want to output a list of images in the same sort order you input  them in a custom field (or in the \'article_image\' field) use this code:</p>\n  <p><code>&lt;txp:images id=\'&lt;txp:custom_field name=\"article_image\"  /&gt;\'  wraptag=\"ul\" break=\"li\" sort=\' field(id, &lt;txp:custom_field  name=\'\'article_image\'\' /&gt;)\'&gt;</code></p>\n  <p>Of course replace \'article_image\' with the custom field name if you are using one.</p>\n  <p>If you are associating a lot of images with an article I suggest to use my other plugin <a href=\"http://forum.textpattern.com/viewtopic.php?id=32175\">bot_wtc</a> to move the field in the central colum for a nicer look</p>\n  <h3>Notes | Other plugins compatibility </h3>\n  <p>If you are using another plugin which already loads <strong>the entire </strong>jQuery  ui script (such as msd_admin_datepicker) you don\'t want to load it  again causing issues so you must set bot_iu_ui_path to \'\' (two single quotes). Mind that the  other plugin must load <strong>the complete </strong>ui script  (including sortables), if it is another custom ui which does not include  sortables you still have to use the script provided with the download  and set bot_iu_ui_path accordingly. </p>\n  <h3>Changelog</h3>\n   <p>v. 0.6.4</p>\n    <ul>\n    <li>Compatibility with txp 4.5.7</li>\n    </ul>\n  <p>v. 0.6.3</p>\n    <ul>\n    <li>Compatibility with txp 4.5 (thanks photonomad)</li>\n    <li>Updated jquery ui to 1.8.23</li>\n    <li>Some other minor tweaks</li>\n    </ul>\n  <p>v. 0.6.2</p>\n  <ul>\n    <li> Bug fixed - image thumbnails wheren\'t deleted when unchecked in iframe (cf values where correctly deleted though - was only a visual issue)</li>\n    <li> Bug fixed - When hitting the cancel button the sortable ability from the UL list was gone (thanks <a href=\"http://www.redbot.it/txp/plugin-bot_image_upload#c000365\">superfly</a>)</li>\n    <li> Iimprovement - replaced jquery \"children()\" with \"find()\" for theme compatibility (\"vitraux\" theme) </li>\n    <li>Iimprovement - better theme compatibility (\"Hive\",  \"Din\" and probably others) </li>\n    <li> Iimprovement - addressed issue with different number of columns depending on the number of authors and privileges (thanks <a href=\"http://forum.textpattern.com/viewtopic.php?pid=251030#p251030\">Jukka</a>) .</li>\n<li>Updated jquery ui to 1.8.16</li>\n    <li>Help updated </li>', '//<?\r\n\r\n	if(@txpinterface == \'admin\') {\r\n		register_callback(\'bot_image_upload\',\'article\');\r\n		register_callback(\'bot_image_css\',\'admin_side\',\'head_end\');\r\n 		bot_image_img();\r\n	}\r\n\r\n/*******************************************************************************\r\n	Global preferences - Configuration. Read comments and modify to your needs.\r\n*******************************************************************************/\r\n\r\n	function bot_iu_prefs() {\r\n		return\r\n			array(\r\n				\'bot_iu_fields\' => \'#article-image\', // fields to be used (comma separated | use #custom-n for custom fields)\r\n				\'bot_iu_mono_list\' => \'#article-image\', // fields with single image (comma separated | use #custom-n for cfs)\r\n				\'bot_choose_image_text\' => gTxt(\'Aggiungi immagine\'), // text for \"Choose images\" link and button\r\n				\'bot_image_delete_text\' => gTxt(\'Delete image\'), // text and title for \"delete image\" button\r\n				\'bot_image_edit_text\' => gTxt(\'Edit image\'), // text and title for \"edit image\" button\"\r\n				\'bot_add_image_text\' => gTxt(\'Add\'), // text for \"add image\" checkbox\r\n				\'bot_iu_save_text\' => gTxt(\'save\'), // text for \"Save\" button\"\r\n				\'bot_iu_cancel_text\' => gTxt(\'cancel\'), // text for \"Cancel\" button\"\r\n				\'bot_iu_row_bg\' => \'#eaeaea\', // selected row background\r\n				\'bot_iu_ui_path\' => \'jquery-ui.min.js\', // path to the jQuery ui script. Defaults to \'textpattern\' directory. Set to \'\' if ui already loaded\r\n			);\r\n	}\r\n\r\n/**\r\n	CSS for the image upload. You are free to modify these\r\n	to match your own backend theme.\r\n*/\r\n\r\n	function bot_image_css() {\r\n\r\n		global $event;\r\n		if($event != \'article\') { // Outputs css only in \'write\' tab.\r\n			return;\r\n		}\r\n\r\n		echo \'<style type=\"text/css\">\r\n				.bot_image_delete {\r\n					background: url(\"?bot_image_img=delete\") top center no-repeat;\r\n					display:inline-block;\r\n					text-indent: -99999px;\r\n					width:15px;\r\n					height:20px;\r\n				}\r\n				.bot_image_edit {\r\n					background:url(\"?bot_image_img=edit\") top center no-repeat;\r\n					display:inline-block;\r\n					text-indent:-99999px;\r\n					width:15px;\r\n					height:20px;\r\n					margin-right: 5px;\r\n				}\r\n				#bot_iu_loading {\r\n					background:#000 url(\"?bot_image_img=bot_iu_loading\") center center no-repeat;\r\n					text-indent:-99999px;\r\n					width:50px;\r\n					height:50px;\r\n					padding:10px;\r\n					position:fixed;\r\n					left:50%;\r\n					margin-left:-30px;\r\n					top:50%;\r\n					margin-top:-30px;\r\n					z-index:9999;\r\n					-moz-border-radius:10px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n				}\r\n				#bot_iu_fade {\r\n					display: none;\r\n					background: #000;\r\n					position: fixed; left: 0; top: 0;\r\n					width: 100%; height: 100%;\r\n					opacity: .80;\r\n					z-index: 999;\r\n				}\r\n				#bot_iu_iframe {\r\n					width:100%;\r\n				}\r\n				#bot_iu_iframe_container {\r\n				    /* height and margin-top are set by js */\r\n				    display:none;\r\n				    position:fixed;\r\n				    left:50%;\r\n				    z-index: 99999;\r\n					overflow: hidden;\r\n					background: #fff;\r\n					width:80%;\r\n					padding:0 2%;\r\n					margin-left: -42%;\r\n					text-align:right;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n					-moz-box-shadow: 0 0 20px #000;\r\n					-webkit-box-shadow: 0 0 20px #000;\r\n					box-shadow: 0 0 20px #000;\r\n				}\r\n				#bot_iu_save,\r\n				#bot_iu_cancel {\r\n					background:#666;\r\n					color:#fff;\r\n					font-weight:bold;\r\n					padding:5px 10px;\r\n					margin-left:5px;\r\n					display:inline-block;\r\n					cursor:pointer;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:5px;\r\n					border-radius:5px;\r\n				}\r\n				#bot_iu_save:hover,\r\n				#bot_iu_cancel:hover {\r\n					background:#333;\r\n					text-decoration:none;\r\n				}\r\n				.bot_iu_ul_container {\r\n					padding:0;\r\n				}\r\n				.bot_iu_image_container {\r\n					display:inline-block;\r\n					border:solid #eaeaea 1px;\r\n					background: #fff;\r\n					margin:0 10px 10px 0;\r\n					padding:10px 10px 0;\r\n					-moz-box-shadow:2px 2px 5px #ccc;\r\n					-webkit-box-shadow:2px 2px 5px #ccc;\r\n					box-shadow:2px 2px 5px #ccc;\r\n				}\r\n				.bot_iu_image_container span {\r\n					display:block;\r\n					overflow:hidden;\r\n					padding-top:5px;\r\n				}\r\n				.bot_add_image {\r\n					display:block;\r\n				}\r\n			</style>\r\n			\r\n			<!--[if lte IE 7]>\r\n				<style>\r\n					/*-- styling for fucking ie7 and below --*/\r\n					.bot_iu_image_container {\r\n						display:inline;\r\n					}\r\n					.bot_image_delete,\r\n					.bot_image_edit {\r\n						float:left;\r\n					}\r\n				</style>\r\n			<![endif]-->\r\n			\r\n			<!--[if lte IE 6]>\r\n				<style>\r\n					/*-- minimal styling for fucking ie6 and below --*/\r\n					#bot_iu_fade,\r\n					#bot_iu_loading,\r\n					#bot_iu_iframe_container {\r\n						position: absolute;\r\n					}\r\n					#bot_iu_iframe_container {\r\n						border: solid #ccc thick;\r\n					}\r\n				</style>\r\n			<![endif]-->\';\r\n	}\r\n\r\n/**\r\n	Generates and stores the images required by the design.\r\n	Image URI is: http://example.com/textpattern/?bot_image_img=delete|edit|file\r\n*/\r\n\r\n	function bot_image_img() {\r\n\r\n 	global $event;\r\n		if($event != \'article\') { // Generates images only in \'write\' tab.\r\n			return;\r\n		}\r\n		$image = gps(\'bot_image_img\');\r\n\r\n		if(!$image)\r\n			return;\r\n\r\n		switch($image) {\r\n			case \'delete\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABEElEQVR42mL4//8/Ayn4zJkzXMh8gABiYkACZ8+elQdibQYcACqXCqTtYGIAAcQIMgWmGUg9gIrrGBsbX8Wi+QqUuxKIG4FqrgMEENgANM0M6IagaYaBAiCeDBBAMAM4gJyvQMyEbgiURtf8F4g5gRb8BgggZC+ADPkCxMwM+AFIMw9Q8w8QByCA4DZCBXigCojSDAIAAYTiZCRD/mHR/A9dMwgABBATFoXKOMSZoHIoACCAmPBEFTZwBT2dAAQQEwHNf7F4B8UQgABiIqAZFB7cWAIWbghAAMFcIIYrtPHEDlgPQAAxQUN/P5BywhXaWAxxguphAAgg9JzmCMQceHIiB0gNshhAAMFTIrkAIMAA7k7JUu0YckQAAAAASUVORK5CYII=\';\r\n				break;\r\n			case \'edit\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABMAAAAUCAMAAABYi/ZGAAAABGdBTUEAAK/INwWK6QAAABl0RVh0 U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACiUExURf7+/v39/bOzs+rq6uHh4dXV1c3N zb6+vqioqLKysq6urqmpqfb29tDQ0O7u7vPz8+3t7d7e3q2trc/Pz7CwsLm5udPT09HR0d3d3fHx 8fT09MXFxaqqqsDAwNra2tTU1Ovr67GxsfLy8qysrNfX17i4uLe3t8HBwaenp/r6+uzs7ODg4N/f 3+/v78nJyenp6bu7u7+/v8zMzNnZ2dLS0v///181u00AAAA2dFJOU/////////////////////// ////////////////////////////////////////////////AKGPTjEAAADaSURBVHjaYjDFBAAB xIDEZuRl5wPRAAGEJMYgziVixANkAAQQkpi4mqoGkzSQARBACDE2WX0TIQ4mIAsggOBiwoZaPLrS 3CxAJkAAwcSE2QX4FYxFJUB8gACCirGx8ykxyzMJgrkAAQQR41UXkGRm5RRkBPMAAggsJmYgKcXM qigBETIFCCCwmLKKnhSriA5UyBQggMBiHKIyLFzaMCFTgAACi3ELMXGyIFwKEEBgFpsYvxyShwAC CGK5Jkq4AAQQA5awAgggbGIAAYRNDCCAsIkBBBA2MYAAAwCXW0H+487c7QAAAABJRU5ErkJggg==\';\r\n				break;\r\n			default:\r\n				$file = \'R0lGODlhHwAfAPUAAAAAAP///xYWFiwsLEJCQlBQUFxcXCIiIkZGRmRkZBoaGiYmJlRUVF5eXk5OTjIyMggICFZWVioqKhgYGMjIyNjY2K6urjo6Oo6OjmxsbKioqAQEBJaWlri4uDg4OAYGBra2tszMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA4BiwSQexKh0eEAkrldAZbvlOD5TqYKALWu5XIwnPFwwymY0GsRgAxrwuJwbCi8aAHlYZ3sVdwtRCm8JgVgODwoQAAIXGRpojQwKRGSDCRESYRsGHYZlBFR5AJt2a3kHQlZlERN2QxMRcAiTeaG2QxJ5RnAOv1EOcEdwUMZDD3BIcKzNq3BJcJLUABBwStrNBtjf3GUGBdLfCtadWMzUz6cDxN/IZQMCvdTBcAIAsli0jOHSJeSAqmlhNr0awo7RJ19TJORqdAXVEEVZyjyKtE3Bg3oZE2iK8oeiKkFZGiCaggelSTiA2LhxiZLBSjZjBL2siNBOFQ84LxHA+mYEiRJzBO7ZCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82YAIQxRCm14Ww4PChAAEAoPDlsAFRUgHkRiZAkREmoSEXiVlRgfQgeBaXRpo6MOQlZbERN0Qx4drRUcAAJmnrVDBrkVDwNjr8BDGxq5Z2MPyUQZuRgFY6rRABe5FgZjjdm8uRTh2d5b4NkQY0zX5QpjTc/lD2NOx+WSW0++2RJmUGJhmZVsQqgtCE6lqpXGjBchmt50+hQKEAEiht5gUcTIESR9GhlgE9IH0BiTkxrMmWIHDkose9SwcQlHDsOIk9ygiVbl5JgMLuV4HUmypMkTOkEAACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2LQV3t4UBcvcF9/eFpdYxdgZ5hUYA73YGxruCbVjt78G7hXFqlhY/fLQwR0HIQdGuUrTz5eQdIc0cfIEwByGD0MKvcGSaFGjR8GyeAPhIUofQGNQSgrB4IsdOCqx7FHDBiYcOQshYjKDxliVDpRjunCjdSTJkiZP6AQBACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2I3WBcvczltNxNzIW0693MFYT7bTumNQqlisv7BjswAHo64egFdQAbj0RtOXDQY6VAAUakihN1gSLaJ1IYOGChgXXqEUpQ9ASRlDYhT0xQ4cACJDhqDD5mRKjCAYuArjBmVKDP9+VRljMyMHDwcfuBlBooSCBQwJiqkJAgAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA8BQIAwExKh0eEAkrlcA9oo4TKcKwharHScIiu9wwTBn3QnGQg1owBNld+O72N/zZnVzRApteFsODwoQABAKDw5bZQxpQ2JkCRESahIRh1gEVIGVamlmXgBWWxETdEMTnlsIAAJmm65DEmZGYw64UZFbR2MPv0QPY0hjpMYKY0ljjMZCEGNK09MG0diN1gXL3M5bTcTcyFtOvdzBWE+207pjUKpYrL+wY7MAB4EerqZjUAG4lKVCBwMbvnT6dCXUkEIFK0jUkOECFEeQJF2hFKUPAIkgQwIaI+hLiJAoR27Zo4YBCJQgVW4cpMYDBpgVZKL59cEBhw+U+QROQ4bBAoUlTZ7QCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82Z1c0QKbXhbDg8KEAAQCg8OW2UMaUNiZAkREmoSEYdYBFSBlWppZl4AVlsRE3RDE55bCAACZpuuQxJmRmMOuFGRW0djD79ED2NIY6TGCmNJY4zGQhBjStPTFBXb21DY1VsGFtzbF9gAzlsFGOQVGefIW2LtGhvYwVgDD+0V17+6Y6BwaNfBwy9YY2YBcMAPnStTY1B9YMdNiyZOngCFGuIBxDZAiRY1eoTvE6UoDEIAGrNSUoNBUuzAaYlljxo2M+HIeXiJpRsRNMaq+JSFCpsRJEqYOPH2JQgAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfjywjlzX9jdXNEHiAVFX8ODwoQABAKDw5bZQxpQh8YiIhaERJqEhF4WwRDDpubAJdqaWZeAByoFR0edEMTolsIAA+yFUq2QxJmAgmyGhvBRJNbA5qoGcpED2MEFrIX0kMKYwUUslDaj2PA4soGY47iEOQFY6vS3FtNYw/m1KQDYw7mzFhPZj5JGzYGipUtESYowzVmF4ADgOCBCZTgFQAxZBJ4AiXqT6ltbUZhWdToUSR/Ii1FWbDnDkUyDQhJsQPn5ZU9atjUhCPHVhgTNy/RSKsiqKFFbUaQKGHiJNyXIAAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEh8JDAWCsBQIAwExKhU+HFwKlgsIMHlIg7TqQeTLW+7XYIiPGSAymY0mrFgA0LwuLzbCC/6eVlnewkADXVECgxcAGUaGRdQEAoPDmhnDGtDBJcVHQYbYRIRhWgEQwd7AB52AGt7YAAIchETrUITpGgIAAJ7ErdDEnsCA3IOwUSWaAOcaA/JQ0amBXKa0QpyBQZyENFCEHIG39HcaN7f4WhM1uTZaE1y0N/TacZoyN/LXU+/0cNyoMxCUytYLjm8AKSS46rVKzmxADhjlCACMFGkBiU4NUQRxS4OHijwNqnSJS6ZovzRyJAQo0NhGrgs5bIPmwWLCLHsQsfhxBWTe9QkOzCwC8sv5Ho127akyRM7QQAAOwAAAAAAAAAAAA==\';\r\n		}\r\n\r\n		ob_start();\r\n		ob_end_clean();\r\n		header(\'Content-type: image/gif\');\r\n		echo base64_decode($file);\r\n		exit();\r\n	}\r\n\r\n\r\n    function bot_image_generate_thumbnail()\r\n    {\r\n		global $prefs, $step;\r\n		extract(bot_iu_prefs());\r\n		$image_path = hu.$prefs[\'img_dir\'];  \r\n		$article_id = gps(\'ID\'); // Fetch article id from $_GET, if any, or sets it to \'\'\r\n    	$fields_array = explode(\",\", $bot_iu_fields); // tranforms in array\r\n		for ($i =0; $i < count($fields_array); $i++) { // changes array values to be used with urls and db\r\n			$current_field = trim($fields_array[$i]);  \r\n			$current_field = ($current_field == \'#article-image\') ? \'Image\': str_replace(\'#custom-\', \'custom_\', $current_field);\r\n			$fields_array[$i] = $current_field; // reinserts modified value\r\n		}\r\n\r\n        if ($step == \'edit\' && $article_id) { // when opening an already existant article\r\n			$fields_string = implode(\',\', $fields_array);\r\n            $values = safe_row($fields_string, \'textpattern\', \'ID = \'. $article_id);\r\n    	}\r\n\r\n    	elseif($step == \'create\' || $step == \'edit\') { // when hitting \'save\'\r\n			for ($i =0; $i < count($fields_array); $i++) { // fetch all image ids and put in array \'values\'\r\n				$current = $fields_array[$i];  \r\n				$values[] = gps($current_field);\r\n			}\r\n    	}\r\n		\r\n		// creates a numerical array of all images for current article\r\n		$values = array_filter(array_values($values)); // convert to numerical array and remove empty values		\r\n		$values = explode(\',\', implode(\',\', $values)); // only one value per array key\r\n		// Sanitizes values\r\n      	foreach($values as $value) {\r\n		    if(is_numeric($value)) {\r\n			    $ids[] = \"\'\".doSlash($value).\"\'\";\r\n            }\r\n	    }\r\n\r\n        // Proceed only if an article image exists.\r\n        if (isset($ids)) {\r\n\r\n            $ids = implode(\',\',$ids);\r\n          	$rs = safe_rows(\r\n				\'ext,id,thumbnail\',\r\n				\'txp_image\',\r\n				\'id in (\'.$ids.\')\'\r\n			);\r\n\r\n			if ($rs) { // If image exist\r\n				$rnd_number = \'?\'.time(); // Append to src to force reload.\r\n				foreach($rs as $a) {\r\n					extract($a);\r\n					if($thumbnail == 0){  \r\n						$image = $id.$ext.$rnd_number;\r\n					}\r\n					else {\r\n						$image = $id.\'t\'.$ext.$rnd_number;\r\n					}\r\n					$str[] =\r\n						\'<li class=\"bot_iu_image_container id\'.$id.\'\">\'.\r\n							\'<img src=\"\'.$image_path.\'/\'.$image.\'\" alt=\"\" />\'. // smd_mod - prima era: \'<img src=\"\'.$image_path.\'/\'.$image.\'\" alt=\"\" />\'.\r\n							\'<span>\'.\r\n								\'<a class=\"bot_image_delete\" href=\"#\" title=\"\'.$bot_image_delete_text.\'\">\'.$bot_image_delete_text.\'</a>\'.\r\n								\' <a class=\"bot_image_edit\" href=\"#\" title=\"\'.$bot_image_edit_text.\'\">\'.$bot_image_edit_text.\'</a>\'.\r\n							\'</span>\'.\r\n						\'</li>\';\r\n				}\r\n				return implode(\'\',$str);\r\n			}\r\n        }\r\n    }\r\n\r\n\r\n/**\r\n	Generates js code\r\n*/\r\n\r\n    function bot_image_upload()\r\n    {\r\n		global $event;\r\n\r\n		if($event != \'article\') {\r\n			return;\r\n		}\r\n\r\n		extract(bot_iu_prefs());		\r\n		$bot_iu_saved_image = bot_image_generate_thumbnail();\r\n\r\n		echo <<<JS_CODE\r\n		<script type=\"text/javascript\" src=\"$bot_iu_ui_path\"></script>\r\n		<script type=\"text/javascript\">\r\n				\r\n			$(document).ready(function() {\r\n				// Modify \'write\' tab\r\n				$(\"$bot_iu_fields\").parents(\"p\").append(\'<ul class=\"bot_iu_ul_container\"></ul><a class=\"bot_add_image\" href=\"#\" title=\"$bot_choose_image_text\">$bot_choose_image_text</a>\'); // Creates an \'add image\' link.\r\n				$(\"body\").append(\'<div id=\"bot_temporary\">$bot_iu_saved_image</div>\');\r\n				$(\".bot_add_image\").each(function(){ // first display all saved images in a temporary div and then clone each to assigned field\r\n	     			var value = $(this).parents(\"p\").find(\"input\").val();\r\n		            var container = $(this).prev(\'.bot_iu_ul_container\');\r\n					if(value) {\r\n						var ids = value.split(\",\");\r\n						for(var i = 0 ; i < ids.length ; i++) {\r\n		                    var imageId = ids[i];\r\n							if (ids[i]) {\r\n			                    var idClass = \".id\" + ids[i];\r\n								$(\"#bot_temporary \"+idClass).clone().appendTo(container); // clone cos otherwise duplicate images are appended only once\r\n	                        }\r\n     					}\r\n					}\r\n				});\r\n				$(\"#bot_temporary\").remove();\r\n				$(\"$bot_iu_fields\").hide(); // Hide article image input.\r\n\r\n				if (jQuery.ui) {				\r\n			    	$(\".bot_iu_ul_container\").sortable({\r\n						update: function(event, ui) {\r\n							var imgOrder = $(this).sortable(\'toArray\',{attribute:\"class\"}).toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n							$(this).siblings(\"input\").val(imgOrder);\r\n						}\r\n					});\r\n			    }\r\n\r\n				// When clicking \'add\' or \'edit\' link...\r\n				$(\"body\").on(\"click\", \'.bot_add_image, .bot_image_edit\', function(){\r\n				\r\n					// Set variables\r\n					var selector = $(this); // Clicked link\r\n					var input = selector.parents(\"p\").find(\"input\"); // ...corresponding input\r\n					var p = selector.parents(\"p\"); // ...surrounding p\r\n					var values = input.val().split(\",\"); // existant ids array\r\n		            var backup = p.clone(); // backup for undo\r\n		            \r\n					// Determines if clicked item is single-image or multi\r\n					var clickedId = input.prop(\'id\');\r\n					var monoList = \'$bot_iu_mono_list\';\r\n		            if (monoList.indexOf(clickedId)!= -1) { // if clicked input id is in $bot_iu_mono_list string\r\n		            	var type = \'mono\';\r\n					}\r\n\r\n					// Set the url the iframe must point to.\r\n					if (selector.prop(\"class\") == \'bot_add_image\') { // If .bot_add_image is clicked...\r\n						var iframeUrl = \"index.php?event=image\";\r\n					}\r\n					else {\r\n						// If an edit link is clicked...\r\n						var imageId = $(this).parents(\".bot_iu_image_container\").prop(\"class\").toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n						var iframeUrl = \"index.php?event=image&step=image_edit&id=\"+imageId;\r\n					}\r\n\r\n					// Fade in Background\r\n				    $(\'body\').append(\'<div id=\"bot_iu_fade\"></div>\'); //Add the fade layer to bottom of the body tag.\r\n				    $(\'#bot_iu_fade\').show().css({\'filter\' : \'alpha(opacity=80)\'}); // show the fade layer -  fix the IE Bug on fading transparencies\r\n\r\n					// Creates an hidden iframe.\r\n					$(\"body\").append(\r\n						\'<div id=\"bot_iu_iframe_container\">\' +\r\n							\'<iframe id=\"bot_iu_iframe\" src =\"\' + iframeUrl + \'\" frameborder=\"0\"></iframe>\' +\r\n						\'</div>\'\r\n					);\r\n\r\n	           		// creates \'cancel\' and \'save\' buttons\r\n		            $(\"#bot_iu_iframe\").after(\"<p><a id=\\\"bot_iu_save\\\">$bot_iu_save_text</a><a id=\\\"bot_iu_cancel\\\">$bot_iu_cancel_text</a></p>\");\r\n		            \r\n					// Calculates heights based on window size for vertical centering.\r\n					var windowHeight = $(window).height();\r\n					var containerHeight = (windowHeight * 80) / 100;\r\n					var containerTopMargin = (windowHeight - containerHeight) / 2;\r\n					var iframeHeight = (containerHeight * 78) / 100;\r\n					var loadingTopMargin = (windowHeight / 2) - 30;\r\n					$(\"#bot_iu_iframe_container\").height(containerHeight).css(\"top\", containerTopMargin);\r\n					$(\"#bot_iu_iframe\").height(iframeHeight).css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"#bot_iu_save\").css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"body\").append(\'<p id=\"bot_iu_loading\">loading</p>\');\r\n\r\n\r\n					// Iframe interactions\r\n					$(\"#bot_iu_iframe\").load(function(){\r\n\r\n						// Hides in iframe\r\n						var iframe = $(\"#bot_iu_iframe\").contents();\r\n						iframe.find(\".txp-header, .txp-footer, #pagetop, #end_page, #nav, #messagepane, #txp-head\").hide(); // some are kept even if now no more used\r\n						iframe.find(\"#image-search option[value=author]\").remove(); // remove some options from search\r\n						iframe.find(\"#image-search option[value=id]\").remove();\r\n						iframe.find(\"#image-search option[value=alt]\").remove();\r\n						iframe.find(\"#image-search option[value=caption]\").remove();\r\n						iframe.find(\".txp-list .id a\").contents().unwrap(); // unlinks the id #\r\n						iframe.find(\".txp-list .date\").hide();\r\n						iframe.find(\".txp-list .tag-build\").hide();\r\n						iframe.find(\".txp-list .author\").hide();\r\n						iframe.find(\".multi-edit\").hide();\r\n						iframe.find(\".thumbnail-edit\").hide(); // Hides \'upload thumb\'.\r\n						iframe.find(\".thumbnail-alter\").hide(); // Hides \'create thumb\'.  (Comment this out if you have images without thumbs so they are easily accessible)\r\n						iframe.find(\"#image-name\").parent().hide(); // Hides \'image name\'\r\n						iframe.find(\"input.publish\").hide(); // Hides original save button\r\n						iframe.find(\"#jbx_div\").remove(); // Removes jbd_multiple_upload\r\n						iframe.find(\".txp-list .id .images_detail\").remove();  \r\n\r\n						// Adds \'add\' column\r\n						iframe.find(\'.txp-list thead tr\').append(\'<th>$bot_add_image_text</th>\');\r\n						iframe.find(\'.txp-list tbody tr\').append(\r\n							\'<td class=\"add\">\'+\r\n								\'<input type=\"checkbox\" name=\"bot_image_checkbox[]\" class=\"bot_image_checkbox\" />\'+\r\n							\'</td>\');\r\n\r\n						// Checks checkboxes depending on already set cfs.\r\n						if (type == \'mono\') { // if mono reduce values array to last item\r\n							var imageId = values.pop();\r\n			            	values=[];\r\n							values.push(imageId);\r\n					    }\r\n	                    for(var i = 0 ; i < values.length ; i++) { \r\n	                        imageId = values[i];\r\n	                        iframe.find(\".txp-list .multi-edit input\").filter(function(){\r\n								return $(this).val() == imageId\r\n							;}).parents(\"tr\").find(\'input.bot_image_checkbox\').prop(\'checked\', true).parents(\"tr\").css(\"background\",\"$bot_iu_row_bg\");\r\n						}\r\n\r\n      					// everything is done, now loader can be hidden and iframe can be shown.\r\n						$(\"#bot_iu_loading\").hide();\r\n						$(\"#bot_iu_iframe_container\").show();\r\n\r\n						// on click\r\n						iframe.find(\".bot_image_checkbox[type=checkbox]\").click(function(){\r\n							var imageId = $(this).parents(\"tr\").find(\".multi-edit input\").val(); // Grabs image id.\r\n							var imageUrl = $(this).parents(\"tr\").find(\"td.thumbnail img\").prop(\'src\'); // Grabs url of currently checked thumb\r\n\r\n							if (this.checked){\r\n                                if (type == \'mono\') {\r\n									iframe.find(\"tr\").css(\"background\",\"none\"); // Eliminates all rows bg.\r\n									iframe.find(\".bot_image_checkbox\").not($(this)).prop(\'checked\', false); // Unchecks all other checkboxes.\r\n									p.find(\".bot_iu_image_container\").remove(); // Removes image container\r\n							    }\r\n							    $(this).parents(\"tr\").css(\"background\",\"$bot_iu_row_bg\"); // Changes checked row bg.\r\n								values.push(imageId); // Inserts new image id.\r\n								p.find(\".bot_iu_ul_container\").append(\r\n									\'<li class=\"bot_iu_image_container id\' + imageId + \'\">\' +\r\n										\'<img src=\"\' + imageUrl + \'\" />\' +\r\n										\'<span>\' +\r\n											\'<a class=\"bot_image_delete\" href=\"#\" title=\"$bot_image_delete_text\">$bot_image_delete_text</a>\' +\r\n											\' <a class=\"bot_image_edit\" href=\"#\" title=\"$bot_image_edit_text\">$bot_image_edit_text</a>\' +\r\n										\'</span>\' +\r\n									\'</li>\'\r\n								);\r\n							}\r\n\r\n							else {\r\n								var arrayIndex = $.inArray(imageId, values); // checks this id index in array\r\n		                        values.splice(arrayIndex,1); // eliminates this id from array\r\n								p.find(\".bot_iu_image_container.id\"+imageId).remove(); // Removes thumbnail container.\r\n								$(this).parents(\"tr\").css(\"background\",\"none\"); // Removes background.\r\n							}\r\n						});\r\n\r\n						// Sets again background for selected row.\r\n						iframe.find(\".txp-list tr:has(input.bot_image_checkbox:checked)\").addClass(\"bot_iu_row_background\");\r\n\r\n						// Avoid FOUC when clicking links and submits.\r\n						iframe.find(\".txp-list a, input.smallerbox, input.publish\").not(\"#eblcropui a, #eblcropui input\").click(function(){\r\n							 $(\"#bot_iu_iframe_container\").hide();\r\n							 $(\"#bot_iu_loading\").show();\r\n						});\r\n\r\n						// When clicking \'save\' button...\r\n						$(\"#bot_iu_save\").click(function(){\r\n\r\n							var iframe = $(\"#bot_iu_iframe\").contents();\r\n                            if (iframe.find(\".publish\").length) { // we are on edit pane\r\n                            	iframe.find(\".publish\").click();\r\n								var editedImageId = iframe.find(\".publish\").parent().next(\"input[name=id]\").val();\r\n								var imageUrl = iframe.find(\".thumbnail\").prop(\'src\'); // Grabs the new image thumbnail. smd_mod - prima era: var imageUrl = iframe.find(\".thumbnail-edit img\").prop(\'src\');\r\n                                if (type == \'mono\') {\r\n					            	values.length = 0;\r\n					            	p.find(\".bot_iu_ul_container li\").remove();\r\n							    }								\r\n								if ($.inArray(editedImageId, values)!=-1) { // When editing an already selected image just refresh image.\r\n									$(\".id\"+editedImageId+\" img\").prop(\'src\', imageUrl);\r\n								}\r\n								else {\r\n                            		values.push(editedImageId);\r\n									p.find(\".bot_iu_ul_container\").append(\r\n										\'<li class=\"bot_iu_image_container id\' + editedImageId + \'\">\' +\r\n											\'<img src=\"\' + imageUrl + \'\" />\' +\r\n											\'<span>\' +\r\n												\'<a class=\"bot_image_delete\" href=\"#\" title=\"$bot_image_delete_text\">$bot_image_delete_text</a>\' +\r\n												\' <a class=\"bot_image_edit\" href=\"#\" title=\"$bot_image_edit_text\">$bot_image_edit_text</a>\' +\r\n											\'</span>\' +\r\n										\'</li>\'\r\n									);\r\n        						}\r\n                            }\r\n                            else { // we are on img list\r\n                                var oldValues = input.val();\r\n                                if (!oldValues[0] && type != \'mono\') { // eliminates first (blank) array item if oldValue is empty\r\n									values.shift();\r\n                                }\r\n                               if (type == \'mono\') {\r\n									var imageId = values.pop();\r\n					            	values=[];\r\n									values.push(imageId);\r\n							    }                             \r\n								input.val(values); // update cf values\r\n    							$(\"#bot_iu_iframe_container, #bot_iu_loading, #bot_iu_fade\").remove();\r\n                            }\r\n						})\r\n						\r\n						// When clicking \'cancel\' button...\r\n						$(\"#bot_iu_cancel\").click(function(){\r\n							p.after(backup).next().remove(); //solution!\r\n							if (jQuery.ui) {\r\n						    	$(\".bot_iu_ul_container\").sortable({\r\n									update: function(event, ui) {\r\n										var imgOrder = $(this).sortable(\'toArray\',{attribute:\"class\"}).toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n										$(this).siblings(\"input\").val(imgOrder);\r\n									}\r\n								});\r\n						    }\r\n			                $(\"#bot_iu_iframe_container, #bot_iu_loading, #bot_iu_fade\").remove();\r\n						})\r\n\r\n					});\r\n					return false;\r\n				});\r\n\r\n			   $(\"body\").on(\'click\',\'.bot_image_delete\',function(){ // changed \'live\' with \'on\' as it is now deprecated\r\n					var input = $(this).parents(\"p\").find(\"input\");\r\n					var values = input.val().split(\",\"); // existant ids array\r\n					var imageId = $(this).parents(\".bot_iu_image_container\").prop(\"class\").replace(\"bot_iu_image_container id\",\"\"); // current id\r\n					var arrayIndex = $.inArray(imageId, values); // checks this id index in array\r\n		            values.splice(arrayIndex,1); // eliminates this id from array\r\n					input.val(values);  // updates values\r\n					$(this).parents(\".bot_iu_image_container\").remove(); // Removes image container\r\n					return false;\r\n				});\r\n			});\r\n		</script>\r\nJS_CODE;\r\n    }\r\n', '//<?\r\n\r\n	if(@txpinterface == \'admin\') {\r\n		register_callback(\'bot_image_upload\',\'article\');\r\n		register_callback(\'bot_image_css\',\'admin_side\',\'head_end\');\r\n 		bot_image_img();\r\n	}\r\n\r\n/*******************************************************************************\r\n	Global preferences - Configuration. Read comments and modify to your needs.\r\n*******************************************************************************/\r\n\r\n	function bot_iu_prefs() {\r\n		return\r\n			array(\r\n				\'bot_iu_fields\' => \'#article-image\', // fields to be used (comma separated | use #custom-n for custom fields)\r\n				\'bot_iu_mono_list\' => \'#article-image\', // fields with single image (comma separated | use #custom-n for cfs)\r\n				\'bot_choose_image_text\' => gTxt(\'Aggiungi immagine\'), // text for \"Choose images\" link and button\r\n				\'bot_image_delete_text\' => gTxt(\'Delete image\'), // text and title for \"delete image\" button\r\n				\'bot_image_edit_text\' => gTxt(\'Edit image\'), // text and title for \"edit image\" button\"\r\n				\'bot_add_image_text\' => gTxt(\'Add\'), // text for \"add image\" checkbox\r\n				\'bot_iu_save_text\' => gTxt(\'save\'), // text for \"Save\" button\"\r\n				\'bot_iu_cancel_text\' => gTxt(\'cancel\'), // text for \"Cancel\" button\"\r\n				\'bot_iu_row_bg\' => \'#eaeaea\', // selected row background\r\n				\'bot_iu_ui_path\' => \'jquery-ui.min.js\', // path to the jQuery ui script. Defaults to \'textpattern\' directory. Set to \'\' if ui already loaded\r\n			);\r\n	}\r\n\r\n/**\r\n	CSS for the image upload. You are free to modify these\r\n	to match your own backend theme.\r\n*/\r\n\r\n	function bot_image_css() {\r\n\r\n		global $event;\r\n		if($event != \'article\') { // Outputs css only in \'write\' tab.\r\n			return;\r\n		}\r\n\r\n		echo \'<style type=\"text/css\">\r\n				.bot_image_delete {\r\n					background: url(\"?bot_image_img=delete\") top center no-repeat;\r\n					display:inline-block;\r\n					text-indent: -99999px;\r\n					width:15px;\r\n					height:20px;\r\n				}\r\n				.bot_image_edit {\r\n					background:url(\"?bot_image_img=edit\") top center no-repeat;\r\n					display:inline-block;\r\n					text-indent:-99999px;\r\n					width:15px;\r\n					height:20px;\r\n					margin-right: 5px;\r\n				}\r\n				#bot_iu_loading {\r\n					background:#000 url(\"?bot_image_img=bot_iu_loading\") center center no-repeat;\r\n					text-indent:-99999px;\r\n					width:50px;\r\n					height:50px;\r\n					padding:10px;\r\n					position:fixed;\r\n					left:50%;\r\n					margin-left:-30px;\r\n					top:50%;\r\n					margin-top:-30px;\r\n					z-index:9999;\r\n					-moz-border-radius:10px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n				}\r\n				#bot_iu_fade {\r\n					display: none;\r\n					background: #000;\r\n					position: fixed; left: 0; top: 0;\r\n					width: 100%; height: 100%;\r\n					opacity: .80;\r\n					z-index: 999;\r\n				}\r\n				#bot_iu_iframe {\r\n					width:100%;\r\n				}\r\n				#bot_iu_iframe_container {\r\n				    /* height and margin-top are set by js */\r\n				    display:none;\r\n				    position:fixed;\r\n				    left:50%;\r\n				    z-index: 99999;\r\n					overflow: hidden;\r\n					background: #fff;\r\n					width:80%;\r\n					padding:0 2%;\r\n					margin-left: -42%;\r\n					text-align:right;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n					-moz-box-shadow: 0 0 20px #000;\r\n					-webkit-box-shadow: 0 0 20px #000;\r\n					box-shadow: 0 0 20px #000;\r\n				}\r\n				#bot_iu_save,\r\n				#bot_iu_cancel {\r\n					background:#666;\r\n					color:#fff;\r\n					font-weight:bold;\r\n					padding:5px 10px;\r\n					margin-left:5px;\r\n					display:inline-block;\r\n					cursor:pointer;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:5px;\r\n					border-radius:5px;\r\n				}\r\n				#bot_iu_save:hover,\r\n				#bot_iu_cancel:hover {\r\n					background:#333;\r\n					text-decoration:none;\r\n				}\r\n				.bot_iu_ul_container {\r\n					padding:0;\r\n				}\r\n				.bot_iu_image_container {\r\n					display:inline-block;\r\n					border:solid #eaeaea 1px;\r\n					background: #fff;\r\n					margin:0 10px 10px 0;\r\n					padding:10px 10px 0;\r\n					-moz-box-shadow:2px 2px 5px #ccc;\r\n					-webkit-box-shadow:2px 2px 5px #ccc;\r\n					box-shadow:2px 2px 5px #ccc;\r\n				}\r\n				.bot_iu_image_container span {\r\n					display:block;\r\n					overflow:hidden;\r\n					padding-top:5px;\r\n				}\r\n				.bot_add_image {\r\n					display:block;\r\n				}\r\n			</style>\r\n			\r\n			<!--[if lte IE 7]>\r\n				<style>\r\n					/*-- styling for fucking ie7 and below --*/\r\n					.bot_iu_image_container {\r\n						display:inline;\r\n					}\r\n					.bot_image_delete,\r\n					.bot_image_edit {\r\n						float:left;\r\n					}\r\n				</style>\r\n			<![endif]-->\r\n			\r\n			<!--[if lte IE 6]>\r\n				<style>\r\n					/*-- minimal styling for fucking ie6 and below --*/\r\n					#bot_iu_fade,\r\n					#bot_iu_loading,\r\n					#bot_iu_iframe_container {\r\n						position: absolute;\r\n					}\r\n					#bot_iu_iframe_container {\r\n						border: solid #ccc thick;\r\n					}\r\n				</style>\r\n			<![endif]-->\';\r\n	}\r\n\r\n/**\r\n	Generates and stores the images required by the design.\r\n	Image URI is: http://example.com/textpattern/?bot_image_img=delete|edit|file\r\n*/\r\n\r\n	function bot_image_img() {\r\n\r\n 	global $event;\r\n		if($event != \'article\') { // Generates images only in \'write\' tab.\r\n			return;\r\n		}\r\n		$image = gps(\'bot_image_img\');\r\n\r\n		if(!$image)\r\n			return;\r\n\r\n		switch($image) {\r\n			case \'delete\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABEElEQVR42mL4//8/Ayn4zJkzXMh8gABiYkACZ8+elQdibQYcACqXCqTtYGIAAcQIMgWmGUg9gIrrGBsbX8Wi+QqUuxKIG4FqrgMEENgANM0M6IagaYaBAiCeDBBAMAM4gJyvQMyEbgiURtf8F4g5gRb8BgggZC+ADPkCxMwM+AFIMw9Q8w8QByCA4DZCBXigCojSDAIAAYTiZCRD/mHR/A9dMwgABBATFoXKOMSZoHIoACCAmPBEFTZwBT2dAAQQEwHNf7F4B8UQgABiIqAZFB7cWAIWbghAAMFcIIYrtPHEDlgPQAAxQUN/P5BywhXaWAxxguphAAgg9JzmCMQceHIiB0gNshhAAMFTIrkAIMAA7k7JUu0YckQAAAAASUVORK5CYII=\';\r\n				break;\r\n			case \'edit\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABMAAAAUCAMAAABYi/ZGAAAABGdBTUEAAK/INwWK6QAAABl0RVh0 U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACiUExURf7+/v39/bOzs+rq6uHh4dXV1c3N zb6+vqioqLKysq6urqmpqfb29tDQ0O7u7vPz8+3t7d7e3q2trc/Pz7CwsLm5udPT09HR0d3d3fHx 8fT09MXFxaqqqsDAwNra2tTU1Ovr67GxsfLy8qysrNfX17i4uLe3t8HBwaenp/r6+uzs7ODg4N/f 3+/v78nJyenp6bu7u7+/v8zMzNnZ2dLS0v///181u00AAAA2dFJOU/////////////////////// ////////////////////////////////////////////////AKGPTjEAAADaSURBVHjaYjDFBAAB xIDEZuRl5wPRAAGEJMYgziVixANkAAQQkpi4mqoGkzSQARBACDE2WX0TIQ4mIAsggOBiwoZaPLrS 3CxAJkAAwcSE2QX4FYxFJUB8gACCirGx8ykxyzMJgrkAAQQR41UXkGRm5RRkBPMAAggsJmYgKcXM qigBETIFCCCwmLKKnhSriA5UyBQggMBiHKIyLFzaMCFTgAACi3ELMXGyIFwKEEBgFpsYvxyShwAC CGK5Jkq4AAQQA5awAgggbGIAAYRNDCCAsIkBBBA2MYAAAwCXW0H+487c7QAAAABJRU5ErkJggg==\';\r\n				break;\r\n			default:\r\n				$file = \'R0lGODlhHwAfAPUAAAAAAP///xYWFiwsLEJCQlBQUFxcXCIiIkZGRmRkZBoaGiYmJlRUVF5eXk5OTjIyMggICFZWVioqKhgYGMjIyNjY2K6urjo6Oo6OjmxsbKioqAQEBJaWlri4uDg4OAYGBra2tszMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA4BiwSQexKh0eEAkrldAZbvlOD5TqYKALWu5XIwnPFwwymY0GsRgAxrwuJwbCi8aAHlYZ3sVdwtRCm8JgVgODwoQAAIXGRpojQwKRGSDCRESYRsGHYZlBFR5AJt2a3kHQlZlERN2QxMRcAiTeaG2QxJ5RnAOv1EOcEdwUMZDD3BIcKzNq3BJcJLUABBwStrNBtjf3GUGBdLfCtadWMzUz6cDxN/IZQMCvdTBcAIAsli0jOHSJeSAqmlhNr0awo7RJ19TJORqdAXVEEVZyjyKtE3Bg3oZE2iK8oeiKkFZGiCaggelSTiA2LhxiZLBSjZjBL2siNBOFQ84LxHA+mYEiRJzBO7ZCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82YAIQxRCm14Ww4PChAAEAoPDlsAFRUgHkRiZAkREmoSEXiVlRgfQgeBaXRpo6MOQlZbERN0Qx4drRUcAAJmnrVDBrkVDwNjr8BDGxq5Z2MPyUQZuRgFY6rRABe5FgZjjdm8uRTh2d5b4NkQY0zX5QpjTc/lD2NOx+WSW0++2RJmUGJhmZVsQqgtCE6lqpXGjBchmt50+hQKEAEiht5gUcTIESR9GhlgE9IH0BiTkxrMmWIHDkose9SwcQlHDsOIk9ygiVbl5JgMLuV4HUmypMkTOkEAACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2LQV3t4UBcvcF9/eFpdYxdgZ5hUYA73YGxruCbVjt78G7hXFqlhY/fLQwR0HIQdGuUrTz5eQdIc0cfIEwByGD0MKvcGSaFGjR8GyeAPhIUofQGNQSgrB4IsdOCqx7FHDBiYcOQshYjKDxliVDpRjunCjdSTJkiZP6AQBACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2I3WBcvczltNxNzIW0693MFYT7bTumNQqlisv7BjswAHo64egFdQAbj0RtOXDQY6VAAUakihN1gSLaJ1IYOGChgXXqEUpQ9ASRlDYhT0xQ4cACJDhqDD5mRKjCAYuArjBmVKDP9+VRljMyMHDwcfuBlBooSCBQwJiqkJAgAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA8BQIAwExKh0eEAkrlcA9oo4TKcKwharHScIiu9wwTBn3QnGQg1owBNld+O72N/zZnVzRApteFsODwoQABAKDw5bZQxpQ2JkCRESahIRh1gEVIGVamlmXgBWWxETdEMTnlsIAAJmm65DEmZGYw64UZFbR2MPv0QPY0hjpMYKY0ljjMZCEGNK09MG0diN1gXL3M5bTcTcyFtOvdzBWE+207pjUKpYrL+wY7MAB4EerqZjUAG4lKVCBwMbvnT6dCXUkEIFK0jUkOECFEeQJF2hFKUPAIkgQwIaI+hLiJAoR27Zo4YBCJQgVW4cpMYDBpgVZKL59cEBhw+U+QROQ4bBAoUlTZ7QCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82Z1c0QKbXhbDg8KEAAQCg8OW2UMaUNiZAkREmoSEYdYBFSBlWppZl4AVlsRE3RDE55bCAACZpuuQxJmRmMOuFGRW0djD79ED2NIY6TGCmNJY4zGQhBjStPTFBXb21DY1VsGFtzbF9gAzlsFGOQVGefIW2LtGhvYwVgDD+0V17+6Y6BwaNfBwy9YY2YBcMAPnStTY1B9YMdNiyZOngCFGuIBxDZAiRY1eoTvE6UoDEIAGrNSUoNBUuzAaYlljxo2M+HIeXiJpRsRNMaq+JSFCpsRJEqYOPH2JQgAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfjywjlzX9jdXNEHiAVFX8ODwoQABAKDw5bZQxpQh8YiIhaERJqEhF4WwRDDpubAJdqaWZeAByoFR0edEMTolsIAA+yFUq2QxJmAgmyGhvBRJNbA5qoGcpED2MEFrIX0kMKYwUUslDaj2PA4soGY47iEOQFY6vS3FtNYw/m1KQDYw7mzFhPZj5JGzYGipUtESYowzVmF4ADgOCBCZTgFQAxZBJ4AiXqT6ltbUZhWdToUSR/Ii1FWbDnDkUyDQhJsQPn5ZU9atjUhCPHVhgTNy/RSKsiqKFFbUaQKGHiJNyXIAAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEh8JDAWCsBQIAwExKhU+HFwKlgsIMHlIg7TqQeTLW+7XYIiPGSAymY0mrFgA0LwuLzbCC/6eVlnewkADXVECgxcAGUaGRdQEAoPDmhnDGtDBJcVHQYbYRIRhWgEQwd7AB52AGt7YAAIchETrUITpGgIAAJ7ErdDEnsCA3IOwUSWaAOcaA/JQ0amBXKa0QpyBQZyENFCEHIG39HcaN7f4WhM1uTZaE1y0N/TacZoyN/LXU+/0cNyoMxCUytYLjm8AKSS46rVKzmxADhjlCACMFGkBiU4NUQRxS4OHijwNqnSJS6ZovzRyJAQo0NhGrgs5bIPmwWLCLHsQsfhxBWTe9QkOzCwC8sv5Ho127akyRM7QQAAOwAAAAAAAAAAAA==\';\r\n		}\r\n\r\n		ob_start();\r\n		ob_end_clean();\r\n		header(\'Content-type: image/gif\');\r\n		echo base64_decode($file);\r\n		exit();\r\n	}\r\n\r\n\r\n    function bot_image_generate_thumbnail()\r\n    {\r\n		global $prefs, $step;\r\n		extract(bot_iu_prefs());\r\n		$image_path = hu.$prefs[\'img_dir\'];  \r\n		$article_id = gps(\'ID\'); // Fetch article id from $_GET, if any, or sets it to \'\'\r\n    	$fields_array = explode(\",\", $bot_iu_fields); // tranforms in array\r\n		for ($i =0; $i < count($fields_array); $i++) { // changes array values to be used with urls and db\r\n			$current_field = trim($fields_array[$i]);  \r\n			$current_field = ($current_field == \'#article-image\') ? \'Image\': str_replace(\'#custom-\', \'custom_\', $current_field);\r\n			$fields_array[$i] = $current_field; // reinserts modified value\r\n		}\r\n\r\n        if ($step == \'edit\' && $article_id) { // when opening an already existant article\r\n			$fields_string = implode(\',\', $fields_array);\r\n            $values = safe_row($fields_string, \'textpattern\', \'ID = \'. $article_id);\r\n    	}\r\n\r\n    	elseif($step == \'create\' || $step == \'edit\') { // when hitting \'save\'\r\n			for ($i =0; $i < count($fields_array); $i++) { // fetch all image ids and put in array \'values\'\r\n				$current = $fields_array[$i];  \r\n				$values[] = gps($current_field);\r\n			}\r\n    	}\r\n		\r\n		// creates a numerical array of all images for current article\r\n		$values = array_filter(array_values($values)); // convert to numerical array and remove empty values		\r\n		$values = explode(\',\', implode(\',\', $values)); // only one value per array key\r\n		// Sanitizes values\r\n      	foreach($values as $value) {\r\n		    if(is_numeric($value)) {\r\n			    $ids[] = \"\'\".doSlash($value).\"\'\";\r\n            }\r\n	    }\r\n\r\n        // Proceed only if an article image exists.\r\n        if (isset($ids)) {\r\n\r\n            $ids = implode(\',\',$ids);\r\n          	$rs = safe_rows(\r\n				\'ext,id,thumbnail\',\r\n				\'txp_image\',\r\n				\'id in (\'.$ids.\')\'\r\n			);\r\n\r\n			if ($rs) { // If image exist\r\n				$rnd_number = \'?\'.time(); // Append to src to force reload.\r\n				foreach($rs as $a) {\r\n					extract($a);\r\n					if($thumbnail == 0){  \r\n						$image = $id.$ext.$rnd_number;\r\n					}\r\n					else {\r\n						$image = $id.\'t\'.$ext.$rnd_number;\r\n					}\r\n					$str[] =\r\n						\'<li class=\"bot_iu_image_container id\'.$id.\'\">\'.\r\n							\'<img src=\"\'.$image_path.\'/\'.$image.\'\" alt=\"\" />\'. // smd_mod - prima era: \'<img src=\"\'.$image_path.\'/\'.$image.\'\" alt=\"\" />\'.\r\n							\'<span>\'.\r\n								\'<a class=\"bot_image_delete\" href=\"#\" title=\"\'.$bot_image_delete_text.\'\">\'.$bot_image_delete_text.\'</a>\'.\r\n								\' <a class=\"bot_image_edit\" href=\"#\" title=\"\'.$bot_image_edit_text.\'\">\'.$bot_image_edit_text.\'</a>\'.\r\n							\'</span>\'.\r\n						\'</li>\';\r\n				}\r\n				return implode(\'\',$str);\r\n			}\r\n        }\r\n    }\r\n\r\n\r\n/**\r\n	Generates js code\r\n*/\r\n\r\n    function bot_image_upload()\r\n    {\r\n		global $event;\r\n\r\n		if($event != \'article\') {\r\n			return;\r\n		}\r\n\r\n		extract(bot_iu_prefs());		\r\n		$bot_iu_saved_image = bot_image_generate_thumbnail();\r\n\r\n		echo <<<JS_CODE\r\n		<script type=\"text/javascript\" src=\"$bot_iu_ui_path\"></script>\r\n		<script type=\"text/javascript\">\r\n				\r\n			$(document).ready(function() {\r\n				// Modify \'write\' tab\r\n				$(\"$bot_iu_fields\").parents(\"p\").append(\'<ul class=\"bot_iu_ul_container\"></ul><a class=\"bot_add_image\" href=\"#\" title=\"$bot_choose_image_text\">$bot_choose_image_text</a>\'); // Creates an \'add image\' link.\r\n				$(\"body\").append(\'<div id=\"bot_temporary\">$bot_iu_saved_image</div>\');\r\n				$(\".bot_add_image\").each(function(){ // first display all saved images in a temporary div and then clone each to assigned field\r\n	     			var value = $(this).parents(\"p\").find(\"input\").val();\r\n		            var container = $(this).prev(\'.bot_iu_ul_container\');\r\n					if(value) {\r\n						var ids = value.split(\",\");\r\n						for(var i = 0 ; i < ids.length ; i++) {\r\n		                    var imageId = ids[i];\r\n							if (ids[i]) {\r\n			                    var idClass = \".id\" + ids[i];\r\n								$(\"#bot_temporary \"+idClass).clone().appendTo(container); // clone cos otherwise duplicate images are appended only once\r\n	                        }\r\n     					}\r\n					}\r\n				});\r\n				$(\"#bot_temporary\").remove();\r\n				$(\"$bot_iu_fields\").hide(); // Hide article image input.\r\n\r\n				if (jQuery.ui) {				\r\n			    	$(\".bot_iu_ul_container\").sortable({\r\n						update: function(event, ui) {\r\n							var imgOrder = $(this).sortable(\'toArray\',{attribute:\"class\"}).toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n							$(this).siblings(\"input\").val(imgOrder);\r\n						}\r\n					});\r\n			    }\r\n\r\n				// When clicking \'add\' or \'edit\' link...\r\n				$(\"body\").on(\"click\", \'.bot_add_image, .bot_image_edit\', function(){\r\n				\r\n					// Set variables\r\n					var selector = $(this); // Clicked link\r\n					var input = selector.parents(\"p\").find(\"input\"); // ...corresponding input\r\n					var p = selector.parents(\"p\"); // ...surrounding p\r\n					var values = input.val().split(\",\"); // existant ids array\r\n		            var backup = p.clone(); // backup for undo\r\n		            \r\n					// Determines if clicked item is single-image or multi\r\n					var clickedId = input.prop(\'id\');\r\n					var monoList = \'$bot_iu_mono_list\';\r\n		            if (monoList.indexOf(clickedId)!= -1) { // if clicked input id is in $bot_iu_mono_list string\r\n		            	var type = \'mono\';\r\n					}\r\n\r\n					// Set the url the iframe must point to.\r\n					if (selector.prop(\"class\") == \'bot_add_image\') { // If .bot_add_image is clicked...\r\n						var iframeUrl = \"index.php?event=image\";\r\n					}\r\n					else {\r\n						// If an edit link is clicked...\r\n						var imageId = $(this).parents(\".bot_iu_image_container\").prop(\"class\").toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n						var iframeUrl = \"index.php?event=image&step=image_edit&id=\"+imageId;\r\n					}\r\n\r\n					// Fade in Background\r\n				    $(\'body\').append(\'<div id=\"bot_iu_fade\"></div>\'); //Add the fade layer to bottom of the body tag.\r\n				    $(\'#bot_iu_fade\').show().css({\'filter\' : \'alpha(opacity=80)\'}); // show the fade layer -  fix the IE Bug on fading transparencies\r\n\r\n					// Creates an hidden iframe.\r\n					$(\"body\").append(\r\n						\'<div id=\"bot_iu_iframe_container\">\' +\r\n							\'<iframe id=\"bot_iu_iframe\" src =\"\' + iframeUrl + \'\" frameborder=\"0\"></iframe>\' +\r\n						\'</div>\'\r\n					);\r\n\r\n	           		// creates \'cancel\' and \'save\' buttons\r\n		            $(\"#bot_iu_iframe\").after(\"<p><a id=\\\"bot_iu_save\\\">$bot_iu_save_text</a><a id=\\\"bot_iu_cancel\\\">$bot_iu_cancel_text</a></p>\");\r\n		            \r\n					// Calculates heights based on window size for vertical centering.\r\n					var windowHeight = $(window).height();\r\n					var containerHeight = (windowHeight * 80) / 100;\r\n					var containerTopMargin = (windowHeight - containerHeight) / 2;\r\n					var iframeHeight = (containerHeight * 78) / 100;\r\n					var loadingTopMargin = (windowHeight / 2) - 30;\r\n					$(\"#bot_iu_iframe_container\").height(containerHeight).css(\"top\", containerTopMargin);\r\n					$(\"#bot_iu_iframe\").height(iframeHeight).css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"#bot_iu_save\").css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"body\").append(\'<p id=\"bot_iu_loading\">loading</p>\');\r\n\r\n\r\n					// Iframe interactions\r\n					$(\"#bot_iu_iframe\").load(function(){\r\n\r\n						// Hides in iframe\r\n						var iframe = $(\"#bot_iu_iframe\").contents();\r\n						iframe.find(\".txp-header, .txp-footer, #pagetop, #end_page, #nav, #messagepane, #txp-head\").hide(); // some are kept even if now no more used\r\n						iframe.find(\"#image-search option[value=author]\").remove(); // remove some options from search\r\n						iframe.find(\"#image-search option[value=id]\").remove();\r\n						iframe.find(\"#image-search option[value=alt]\").remove();\r\n						iframe.find(\"#image-search option[value=caption]\").remove();\r\n						iframe.find(\".txp-list .id a\").contents().unwrap(); // unlinks the id #\r\n						iframe.find(\".txp-list .date\").hide();\r\n						iframe.find(\".txp-list .tag-build\").hide();\r\n						iframe.find(\".txp-list .author\").hide();\r\n						iframe.find(\".multi-edit\").hide();\r\n						iframe.find(\".thumbnail-edit\").hide(); // Hides \'upload thumb\'.\r\n						iframe.find(\".thumbnail-alter\").hide(); // Hides \'create thumb\'.  (Comment this out if you have images without thumbs so they are easily accessible)\r\n						iframe.find(\"#image-name\").parent().hide(); // Hides \'image name\'\r\n						iframe.find(\"input.publish\").hide(); // Hides original save button\r\n						iframe.find(\"#jbx_div\").remove(); // Removes jbd_multiple_upload\r\n						iframe.find(\".txp-list .id .images_detail\").remove();  \r\n\r\n						// Adds \'add\' column\r\n						iframe.find(\'.txp-list thead tr\').append(\'<th>$bot_add_image_text</th>\');\r\n						iframe.find(\'.txp-list tbody tr\').append(\r\n							\'<td class=\"add\">\'+\r\n								\'<input type=\"checkbox\" name=\"bot_image_checkbox[]\" class=\"bot_image_checkbox\" />\'+\r\n							\'</td>\');\r\n\r\n						// Checks checkboxes depending on already set cfs.\r\n						if (type == \'mono\') { // if mono reduce values array to last item\r\n							var imageId = values.pop();\r\n			            	values=[];\r\n							values.push(imageId);\r\n					    }\r\n	                    for(var i = 0 ; i < values.length ; i++) { \r\n	                        imageId = values[i];\r\n	                        iframe.find(\".txp-list .multi-edit input\").filter(function(){\r\n								return $(this).val() == imageId\r\n							;}).parents(\"tr\").find(\'input.bot_image_checkbox\').prop(\'checked\', true).parents(\"tr\").css(\"background\",\"$bot_iu_row_bg\");\r\n						}\r\n\r\n      					// everything is done, now loader can be hidden and iframe can be shown.\r\n						$(\"#bot_iu_loading\").hide();\r\n						$(\"#bot_iu_iframe_container\").show();\r\n\r\n						// on click\r\n						iframe.find(\".bot_image_checkbox[type=checkbox]\").click(function(){\r\n							var imageId = $(this).parents(\"tr\").find(\".multi-edit input\").val(); // Grabs image id.\r\n							var imageUrl = $(this).parents(\"tr\").find(\"td.thumbnail img\").prop(\'src\'); // Grabs url of currently checked thumb\r\n\r\n							if (this.checked){\r\n                                if (type == \'mono\') {\r\n									iframe.find(\"tr\").css(\"background\",\"none\"); // Eliminates all rows bg.\r\n									iframe.find(\".bot_image_checkbox\").not($(this)).prop(\'checked\', false); // Unchecks all other checkboxes.\r\n									p.find(\".bot_iu_image_container\").remove(); // Removes image container\r\n							    }\r\n							    $(this).parents(\"tr\").css(\"background\",\"$bot_iu_row_bg\"); // Changes checked row bg.\r\n								values.push(imageId); // Inserts new image id.\r\n								p.find(\".bot_iu_ul_container\").append(\r\n									\'<li class=\"bot_iu_image_container id\' + imageId + \'\">\' +\r\n										\'<img src=\"\' + imageUrl + \'\" />\' +\r\n										\'<span>\' +\r\n											\'<a class=\"bot_image_delete\" href=\"#\" title=\"$bot_image_delete_text\">$bot_image_delete_text</a>\' +\r\n											\' <a class=\"bot_image_edit\" href=\"#\" title=\"$bot_image_edit_text\">$bot_image_edit_text</a>\' +\r\n										\'</span>\' +\r\n									\'</li>\'\r\n								);\r\n							}\r\n\r\n							else {\r\n								var arrayIndex = $.inArray(imageId, values); // checks this id index in array\r\n		                        values.splice(arrayIndex,1); // eliminates this id from array\r\n								p.find(\".bot_iu_image_container.id\"+imageId).remove(); // Removes thumbnail container.\r\n								$(this).parents(\"tr\").css(\"background\",\"none\"); // Removes background.\r\n							}\r\n						});\r\n\r\n						// Sets again background for selected row.\r\n						iframe.find(\".txp-list tr:has(input.bot_image_checkbox:checked)\").addClass(\"bot_iu_row_background\");\r\n\r\n						// Avoid FOUC when clicking links and submits.\r\n						iframe.find(\".txp-list a, input.smallerbox, input.publish\").not(\"#eblcropui a, #eblcropui input\").click(function(){\r\n							 $(\"#bot_iu_iframe_container\").hide();\r\n							 $(\"#bot_iu_loading\").show();\r\n						});\r\n\r\n						// When clicking \'save\' button...\r\n						$(\"#bot_iu_save\").click(function(){\r\n\r\n							var iframe = $(\"#bot_iu_iframe\").contents();\r\n                            if (iframe.find(\".publish\").length) { // we are on edit pane\r\n                            	iframe.find(\".publish\").click();\r\n								var editedImageId = iframe.find(\".publish\").parent().next(\"input[name=id]\").val();\r\n								var imageUrl = iframe.find(\".thumbnail\").prop(\'src\'); // Grabs the new image thumbnail. smd_mod - prima era: var imageUrl = iframe.find(\".thumbnail-edit img\").prop(\'src\');\r\n                                if (type == \'mono\') {\r\n					            	values.length = 0;\r\n					            	p.find(\".bot_iu_ul_container li\").remove();\r\n							    }								\r\n								if ($.inArray(editedImageId, values)!=-1) { // When editing an already selected image just refresh image.\r\n									$(\".id\"+editedImageId+\" img\").prop(\'src\', imageUrl);\r\n								}\r\n								else {\r\n                            		values.push(editedImageId);\r\n									p.find(\".bot_iu_ul_container\").append(\r\n										\'<li class=\"bot_iu_image_container id\' + editedImageId + \'\">\' +\r\n											\'<img src=\"\' + imageUrl + \'\" />\' +\r\n											\'<span>\' +\r\n												\'<a class=\"bot_image_delete\" href=\"#\" title=\"$bot_image_delete_text\">$bot_image_delete_text</a>\' +\r\n												\' <a class=\"bot_image_edit\" href=\"#\" title=\"$bot_image_edit_text\">$bot_image_edit_text</a>\' +\r\n											\'</span>\' +\r\n										\'</li>\'\r\n									);\r\n        						}\r\n                            }\r\n                            else { // we are on img list\r\n                                var oldValues = input.val();\r\n                                if (!oldValues[0] && type != \'mono\') { // eliminates first (blank) array item if oldValue is empty\r\n									values.shift();\r\n                                }\r\n                               if (type == \'mono\') {\r\n									var imageId = values.pop();\r\n					            	values=[];\r\n									values.push(imageId);\r\n							    }                             \r\n								input.val(values); // update cf values\r\n    							$(\"#bot_iu_iframe_container, #bot_iu_loading, #bot_iu_fade\").remove();\r\n                            }\r\n						})\r\n						\r\n						// When clicking \'cancel\' button...\r\n						$(\"#bot_iu_cancel\").click(function(){\r\n							p.after(backup).next().remove(); //solution!\r\n							if (jQuery.ui) {\r\n						    	$(\".bot_iu_ul_container\").sortable({\r\n									update: function(event, ui) {\r\n										var imgOrder = $(this).sortable(\'toArray\',{attribute:\"class\"}).toString().replace(/bot_iu_image_container id/g,\"\").replace(/ ui-sortable-handle/g,\"\");\r\n										$(this).siblings(\"input\").val(imgOrder);\r\n									}\r\n								});\r\n						    }\r\n			                $(\"#bot_iu_iframe_container, #bot_iu_loading, #bot_iu_fade\").remove();\r\n						})\r\n\r\n					});\r\n					return false;\r\n				});\r\n\r\n			   $(\"body\").on(\'click\',\'.bot_image_delete\',function(){ // changed \'live\' with \'on\' as it is now deprecated\r\n					var input = $(this).parents(\"p\").find(\"input\");\r\n					var values = input.val().split(\",\"); // existant ids array\r\n					var imageId = $(this).parents(\".bot_iu_image_container\").prop(\"class\").replace(\"bot_iu_image_container id\",\"\"); // current id\r\n					var arrayIndex = $.inArray(imageId, values); // checks this id index in array\r\n		            values.splice(arrayIndex,1); // eliminates this id from array\r\n					input.val(values);  // updates values\r\n					$(this).parents(\".bot_iu_image_container\").remove(); // Removes image container\r\n					return false;\r\n				});\r\n			});\r\n		</script>\r\nJS_CODE;\r\n    }\r\n', '0a4e4ab0b0d1d8c275cd1e13dec942f1', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_write_tab_customize', 1, 'redbot (+ jools-r)', 'https://github.com/jools-r/bot_wtc', '8.0', 'Rearrange and conditionally show items in the Write panel (for v.4.7)', '<h1>Write tab customize</h1>\n\n<p><strong>Note: This version is for Textpattern v4.7 and is not compatible with earlier versions of Textpattern.</strong></p>\n\n<p>This plugin provides the ability to customize Textpattern&#8217;s “write” panel. It makes it possible to rearrange UI items, hide parts of the UI on a per-section basis, hide specific sections from the <em>Sections</em> dropdown and more.</p>\n\n<p>By combining its features you can re-arrange the <em>write</em> tab differently depending on the section you choose in the <em>sections</em> dropdown. Used on its own or together with other plugins (e.g. glz_custom_fields and bot_admin_body_class), it will help you customize the Textpattern admin area to meet your clients needs.</p>\n\n<h3>Features</h3>\n\n<ul>\n	<li>Single UI items (custom fields, body, excerpt etc.) or whole regions (main content, sidebar etc.) can be moved around relative to other single items or regions</li>\n	<li>UI Items can be hidden on a per-section basis</li>\n	<li>Sections can be hidden from the “write” panel sections dropdown (for static sections like “about us” or “search”)</li>\n	<li>A custom css class can be set for each item. This let&#8217;s you add classes to items to target with your own javascript.</li>\n	<li>Javascript code can be set directly through the plugin interface. Particularly useful for use with an external jquery plugin and in conjunction with the ability to add a css class to any item on the page. The script will be executed only in the <em>Write</em> panel.</li>\n	<li>Advanced settings for hiding / attributing classes to multiple items – helpful on sites with lots of custom fields.</li>\n	<li>Compatible with glz_custom_fields.</li>\n</ul>\n\n<p>bot_wtc&#8217;s rearranging capability offers a simple way of customizing Textpattern, but is just one tool for customizing the admin area. More extensive modifications, e.g adapting Textpattern your customer&#8217;s branding, can be achieved by creating an own admin theme.</p>\n\n\n<h2>Installation</h2>\n\n<p>Paste the .txt or .zip.txt package code into the Admin &gt; Plugins panel, install and enable the plugin. Visit the “Extensions” tab and customize your write tab settings.</p>\n\n<h3>Upgrade notes</h3>\n\n<p>The plugin should take care of updating existing data to match the v4.7. panel layout on activation. Nevertheless, it is strongly recommended to back up your database prior to updating so that you can restore your settings should something go awry.</p>\n\n\n<h2>Examples</h2>\n\n<h3>Example: Adding a <em>menu title</em> beneath the regular <em>Title</em> field</h3>\n\n<ol>\n	<li>Define a custom field called ‘menu_title’ in ‘Admin › Preferences » Custom fields’ or using the glz_custom_fields plugin. If using glz_custom_fields, you can give it a title text of ‘Menu title’. It will appear in the ‘custom fields’ group on the write panel.</li>\n	<li>Switch to the ‘Write Tab Customize’ panel, create a rule ‘Menu title’ &gt; After &gt; ‘Title’. It will now appear beneath the title.</li>\n</ol>\n\n<h3>Example: Showing fields on certain sections only</h3>\n\n<p>Suppose you have an ‘events’ section and an ‘organisers’ section, and custom fields called ‘event_location’, ‘event_time’, ‘event_duration’ and ‘organiser_address’, ‘organiser_tel’, ‘organiser_email’.</p>\n\n<ol>\n	<li>Switch to the ‘Write Tab Customize’ panel, create a rule ‘event_location’, then click on ‘Section list’ and place a checkbox next to all the section <em>except</em> ‘events’. Click the ‘Update’ button, and repeat for each event custom fields. The fields will now only show when “events” is chosen in the Section dropdown.</li>\n	<li>Now create a rule ‘organiser_address’, then click on ‘Section list’ and place a checkmark next to all the section <em>except</em> ‘organisers’. Click the ‘Update’ button, and repeat for each organiser custom fields. The fields will now only show when “organiser” is chosen in the Section dropdown.</li>\n</ol>\n\n<p>Alternative: If you have a lot of custom fields, it can be tiresome to set these settings for each custom field individually. A quicker way to set several custom_fields at once is to choose “Toggle advanced” at the top right. Now put a checkmark next to all the “event_” custom fields and checkmarks next to all the sections <em>except</em> the ‘events’ section. Click the ‘Update’ button and they will all be set at once.</p>\n\n<h3>Example: Using classes to address fields with javascript / jQuery</h3>\n\n<p>Suppose you want to draw attention to some specific fields to ensure they are not overlooked by making them pulsate briefly when the ‘Write’ panel loads.</p>\n\n<ol>\n	<li>In the ‘Write Tab Customize’ panel, assign a class <code>required</code> (without a dot) to all the items that need to be filled out.</li>\n	<li>Add the appropriate javascript code in the ‘Add javascript’ box as follows (use the buttons to insert boilerplate code):</li>\n</ol>\n\n<pre><code>&lt;script src= &quot;/js/jquery.pulsate.js&quot;&gt;&lt;/script&gt;\n&lt;script&gt;\n$(document).ready(function() {\n  $(&quot;.required&quot;).pulsate({times:3});\n});\n&lt;/script&gt;</code></pre>\n\n<p>For this simple example you&#8217;ll need to have the file <code>jquery.pulsate.js</code> in the <code>js</code> directory.<br />\nYou can extend this principle, for example to highlight required fields that have no content and still need to be filled out, or to validate that field data has been entered correctly.</p>\n\n<h3>Tip: Making collapsible groups permanently open</h3>\n\n<p>Suppose you wish to prevent your users from overlooking fields in collapsible groups. You can use ‘Write Tab Customize’ to hide the label headings for each collapsible group, so that the “twisty” arrow that make it possible to show/hide the group will no longer be accessible. You can also hide the “expand all/collapse all” buttons. Now all you need is the following tiny jQuery rule in the ‘Add javascript’ box to ensure every collapsible group is automatically opened when the page opens:</p>\n\n<pre><code>&lt;script&gt;\n$(document).ready(function() {\n  $(&quot;.toggle&quot;).show();\n});\n&lt;/script&gt;</code></pre>\n\n\n<h2>Notes and known issues</h2>\n\n<ul>\n	<li><strong>Order does matter</strong>: The rules for moving items are executed from top to bottom so if the sequence gets garbled it&#8217;s advisable to delete them all and start over.</li>\n	<li><strong>Class names</strong> must be inserted <strong>without the dot</strong>.</li>\n	<li>If you want to hide an item in <strong>all sections</strong>, it is more efficient to hide items using css than it is to hide them using this plugin. See below.</li>\n	<li>This plugin may not function properly if you have the Suhosing module (a security-related module for <span class=\"caps\">PHP</span>) installed. In this case follow <a href=\"http://forum.textpattern.io/viewtopic.php?pid=243861#p243861\" rel=\"nofollow\">these instructions</a> (thanks maniqui!).</li>\n</ul>\n\n<h3>Other ways of customising the admin UI</h3>\n\n<p>Successive versions of Textpattern have brought extensive enhancements to the “write” panel and almost every item now has an id or a class which means snappier jquery execution but more importantly also makes it possible to hide these elements with simple css in a theme. Hiding items using <span class=\"caps\">CSS</span> is more efficient than using javascript / jQuery but cannot respond conditionally to section changes.</p>\n\n<p>If you have elements you will consistently hide or restyle or javascript you consistently use – i.e. that is not dependent on the chosen section – you can add an optional user-defined custom <span class=\"caps\">CSS</span> and JavaScript file to your textpattern installation directory. By default, these are located at:</p>\n\n<pre><code>assets/css/custom.css\nassets/js/custom.js</code></pre>\n\n<p>You may use different file names by adding the following line(s) within your Textpattern ‘config.php’ file (before the closing <code>?&gt;</code> line):</p>\n\n<pre><code>define(\'admin_custom_css\', \'your_custom_rules.css\');\ndefine(\'admin_custom_js\', \'your_custom_javascript.js\');</code></pre>\n\n<p>For examples, see the <a href=\"https://github.com/textpattern/textpattern/tree/master/textpattern/admin-themes/hive\" rel=\"nofollow\">Readme</a> and <a href=\"https://github.com/textpattern/textpattern/tree/master/textpattern/admin-themes/hive/assets/css\" rel=\"nofollow\">custom-example.css</a> in the admin-themes.</p>\n\n\n<h2>Changelog</h2>\n\n<h3>8.0 – 2018-03-26 (jools-r)</h3>\n\n<ul>\n	<li><strong>Note: for Textpattern version 4.7 only.</strong> No longer compatible with earlier versions of Textpattern.</li>\n	<li>Write tab area classes and id updates to match Textpattern v4.7&#8217;s ‘Write’ panel layout.</li>\n	<li>Revised custom field handling to reflect new UI pattern, including migration of previous settings on installation.</li>\n	<li>Revised ‘Write Tab Customize’ panel using txp&#8217;s in-built UI functions.</li>\n	<li>Compatibility with glz_custom_fields v2 (e.g. custom field titles).</li>\n	<li>Translatable UI text (see Textpack).</li>\n	<li>Error notices now use Txp&#8217;s own in-built messaging functions.</li>\n	<li>Revised help.</li>\n</ul>\n\n\n<h3>7.5 – 2016-05-22</h3>\n\n<ul>\n	<li>Further write tab area classes and id updates for Textpattern v4.6. (jools-r)</li>\n	<li>Updated install routine to use Textpattern’s built-in functions (jools-r) for better <span class=\"caps\">PHP</span> 7 compatibility.</li>\n</ul>\n\n\n<h3>7.4 – 2015-12-07</h3>\n\n<ul>\n	<li><strong>Note: for Textpattern version 4.6 only.</strong> No longer compatible with earlier versions of Textpattern.</li>\n	<li>Updated write tab area classes and id for Textpattern v4.6 (jools-r).</li>\n</ul>\n\n\n<h3>7.3 – 2014-06-14</h3>\n\n<ul>\n	<li>Updated to address “Sorry, the form could not be submitted” error in 4.6 (by mrdale).</li>\n	<li>Update bot_wtc extension panel to use new “hive” admin controls.</li>\n</ul>\n\n\n<h3>7.1</h3>\n\n<ul>\n	<li>Fixed <a href=\"http://forum.textpattern.io/viewtopic.php?pid=245511#p245511\" rel=\"nofollow\">issue with multi-select and checkboxes</a> custom fields.</li>\n	<li>Fixed deprecated jquery.</li>\n	<li>Changed ‘sections’ field <a href=\"http://forum.textpattern.io/viewtopic.php?pid=254132#p254132\" rel=\"nofollow\">from varchar to text</a>.</li>\n	<li>Removed deprecated language attribute from ‘script’ tag.</li>\n	<li>Updated help.</li>\n	<li>Cleaned up code.</li>\n</ul>', '\n\nif (@txpinterface == \'admin\') {\n\n    global $event;\n\n    add_privs(\'bot_wtc_tab\', \'1,2\');\n    register_tab(\'extensions\', \'bot_wtc_tab\', \'Write tab customize\');\n    register_callback(\'bot_wtc_tab\', \'bot_wtc_tab\');\n\n    if ($event === \'article\') {\n        register_callback(\'bot_wtc\', \'admin_side\', \'head_end\');\n        register_callback(\'bot_hide_per_section\', \'admin_side\', \'head_end\');\n        register_callback(\'bot_hidden_sections\', \'admin_side\', \'head_end\');\n    }\n\n    if ($event === \'bot_wtc_tab\') {\n        register_callback(\'bot_wtc_css\', \'admin_side\', \'head_end\');\n    }\n\n    register_callback(\'bot_wtc_welcome\', \'plugin_lifecycle.bot_write_tab_customize\');\n\n}\n\n// UPDATE these two arrays to match Textpattern UI layout / gtxt string names\n\n// ===========================================\n// Array of Write Panel UI items\n// Key: gTxt name => Value: Jquery selector query\n// ===========================================\n\n$bot_arr_selectors = array(\n\n    \'writetab_main_content\' => \'$(\"#main_content\")\',\n\n        \'writetab_view_modes\' => \'$(\"#view_modes\")\',\n        \'title\'               => \'$(\".title\")\',\n        \'author\'              => \'$(\".author\")\',\n        \'body\'                => \'$(\".body\")\',\n        \'excerpt\'             => \'$(\".excerpt\")\',\n        \'article_markup\'      => \'$(\".body .txp-textfilter-options\")\',\n        \'excerpt_markup\'      => \'$(\".excerpt .txp-textfilter-options\")\',\n        \'textile_help\'        => \'$(\".textfilter-help\")\',\n\n    \'writetab_sidebar\' => \'$(\"#supporting_content\")\',\n\n        \'save\'                => \'$(\".txp-save\")\',\n\n        \'writetab_article_actions\' => \'$(\"#txp-article-actions\")\',\n\n            \'add_new_article\' => \'$(\".txp-new\")\',\n            \'duplicate\'       => \'$(\".txp-clone\")\',\n            \'view\'            => \'$(\".txp-article-view\")\',\n\n        \'page_article_nav_hed\' => \'$(\".nav-tertiary\")\',\n\n        \'sort_display\'        => \'$(\"#txp-write-sort-group\")\',\n\n            \'status\'          => \'$(\"#txp-container-status\")\',\n            \'section\'         => \'$(\".section\")\',\n            \'override_default_form\' => \'$(\".override-form\")\',\n\n        \'date_settings\'       => \'$(\"#txp-dates-group\")\',\n\n            \'publish\'         => \'$(\"#publish-datetime-group\")\',\n            \'publish_date\'    => \'$(\".posted.date\")\',\n            \'publish_time\'    => \'$(\".posted.time\")\',\n            \'reset_time\'      => \'$(\".reset-time\")\',\n\n            \'expired\'         => \'$(\"#expires-datetime-group\")\',\n            \'expire_date\'     => \'$(\".expires.date\")\',\n            \'expire_time\'     => \'$(\".expires.time\")\',\n            \'set_expire_now\'  => \'$(\".expire-now\")\',\n\n        \'categories\'          =>  \'$(\"#txp-categories-group\")\',\n\n            \'category1\'       => \'$(\".category-1\")\',\n            \'category2\'       => \'$(\".category-2\")\',\n\n        \'meta\'                => \'$(\"#txp-meta-group\")\',\n\n            \'url_title\'       => \'$(\".url-title\")\',\n            \'description\'     => \'$(\".description\")\',\n            \'keywords\'        => \'$(\".keywords\")\',\n\n        \'comment_settings\'    => \'$(\"#txp-comments-group\")\',\n\n            \'use_comments\'    => \'$(\".comments-annotate\")\',\n            \'comment_invitation\' => \'$(\".comment-invite\")\',\n\n        \'article_image_group\' => \'$(\"#txp-image-group\")\',\n\n            \'article_image\'   => \'$(\".article-image\")\',\n\n        \'custom\'              => \'$(\"#txp-custom-field-group\")\',\n\n        \'recent_articles\'     => \'$(\"#txp-recent-group\")\'\n\n);\n\n// ===========================================\n// Array of Write Panel UI item -> container/group allocation\n// Says where UI items originally reside for bot_wtc_jquery_cleanrefresh_rows()\n//\n// Format: \'#container-id/class\' => array (\n//              \'.item-id/class\',\n//              \'.item-id/class\',\n//          )\n//\n// Note: Not all panels and not all subcontainers are refreshed\n// They are included below but (intentionally) commented out.\n// ===========================================\n\nglobal $bot_arr_item_groups;\n$bot_arr_item_groups = array (\n\n/*\n    \'.txp-save-zone\' => array(\n        \'.txp-save\',\n        \'#txp-article-actions\'\n    ),\n*/\n    \'#txp-article-actions\' => array (\n        \'.txp-new\',\n        \'.txp-clone\',\n        \'.txp-article-view\'\n    ),\n/*\n    \'#txp-write-sort-group\' => array (\n        \'#txp-container-status\',\n        \'.section\',\n        \'.override-form\'\n    ),\n*/\n/*\n    \'#txp-dates-group\' => array (\n        \'#publish-datetime-group\',\n        \'#expires-datetime-group\',\n    ),\n*/\n    \'#publish-datetime-group\' => array (\n        \'.posted.date\',\n        \'.posted.time\',\n        \'.reset-time\'\n    ),\n    \'#expires-datetime-group\' => array (\n        \'.expires.date\',\n        \'.expires.time\',\n        \'.expire-now\'\n    ),\n/*\n    \'#txp-categories-group\' => array (\n        \'.category-1\',\n        \'.category-2\'\n    ),\n*/\n/*\n    \'#txp-meta-group\' => array (\n        \'.url-title\',\n        \'.description\',\n        \'.keywords\'\n    ),\n*/\n    \'#txp-comments-group\' => array (\n        \'.comments-annotate\',\n        \'.comment-invite\'\n    ),\n    \'#txp-image-group\' => array (\n        \'.article-image\'\n    )\n\n);\n\n\n    // Get language strings not in plugin for write tab item dropdowns\n    $ui_language = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n    $langObject = \\Txp::get(\'\\Textpattern\\L10n\\Lang\'); // Fetch ref to language class if not already done\n    // Extract strings from the given groups in the current UI language.\n    $strings = $langObject->extract($ui_language, \'article, tag, prefs, public\');\n\n    // Get just the relevant subset of strings that corresponds to dropdown content\n    $strings = array_intersect_key($strings, array_flip(array_keys($bot_arr_selectors)));\n\n    // Set the internal strings to use them (true = append to what\'s already loaded)\n    $langObject->setPack($strings, true);\n\n    // Creates the translated main plugins array ($bot_items)\n    global $bot_items;\n    foreach ($bot_arr_selectors as $title => $selector) {\n        bot_wtc_insert_in_main_array($title, $selector);\n    }\n    natcasesort($bot_items);\n\n\n\n// ===========================================================\n// Helper functions\n// ===========================================================\n\n// Helps build the main array\n\nfunction bot_wtc_insert_in_main_array($title, $selector)\n{\n    global $bot_items;\n\n    if (strpos($title, \'!bot!\')) {\n        $split_titles = explode(\"!bot!\", $title);\n        $title = \'\';\n        for ($i = 0; $i < count($split_titles); $i++) {\n            $title .= gTxt($split_titles[$i]); // split and build translated title\n        }\n    } else {\n        $title = gTxt($title); // Gets the title to allow translation\n    }\n    $bot_items [$selector] = gTxt($title);\n    return $bot_items;\n}\n\n\n// ===========================================\n// Creates an array of values extracted from the database\n\nfunction bot_wtc_fetch_db()\n{\n    if (bot_wtc_check_install()) {\n        $out = safe_rows(\'id, item, position, destination, sections, class\', \'bot_wtc \', \'1=1\');\n        return $out;\n    }\n}\n\n\n// ===========================================================\n// Creates an array of all cfs for selectInput\n\nfunction bot_get_cfs()\n{\n    $r = safe_rows_start(\'name, val, html\', \'txp_prefs\', \'event = \"custom\" AND val != \"\"\');\n    if ($r) {\n        global $arr_custom_fields;\n        while ($a = nextRow($r)) {\n            // Convert custom_XX_set into custom-XX\n            $name = str_replace(\'_\', \'-\', substr($a[\'name\'], 0, -4));\n            $html = $a[\'html\'];\n            $selector = \'$(\".\'.$name.\'\")\';\n\n            // If glz_cf_gtxt function exists, use custom_field title if one exists\n            $cf_title = (function_exists(\'glz_cf_gtxt\')) ? glz_cf_gtxt($a[\'val\']) : \'\';\n            $val = (!empty($cf_title)) ? $cf_title : $a[\'val\'];\n            $arr_custom_fields[$selector] = $val;\n        }\n    }\n    if ($arr_custom_fields) {\n        natcasesort($arr_custom_fields); // Sort cfs - used instead of asort because is case-insensitive\n        return $arr_custom_fields;\n    }\n};\n\n\n// ===========================================================\n// Creates an array of all sections for selectInput\n\nfunction bot_get_sections()\n{\n    $r = safe_rows_start(\'name, title\', \'txp_section\', \'1=1\');\n    if ($r) {\n        while ($a = nextRow($r)) {\n            $name = $a[\'name\'];\n            $title = $a[\'title\'];\n            $sections[$name] = $title;\n        }\n    }\n    natcasesort($sections);\n    return $sections;\n}\n\n\n// ===========================================================\n// Update button\n\nfunction bot_update_button($btn_class=\'publish\')\n{\n    return n.\'<div class=\"bot_update_button\">\'\n        .n.eInput(\'bot_wtc_tab\')\n        .n.sInput(\'update\')\n        .n.fInput(\'submit\', \'update\', gTxt(\'bot_wtc_update_button\'), $btn_class)\n        .\'</div>\';\n}\n\n\n\n// ===========================================================\n// Checks if item is a layout region\n\nfunction bot_wtc_is_region($item)\n{\n    $item = get_magic_quotes_gpc() ? $item : doSlash($item) ;\n\n    if ($item == \'$(\\\"#main_content\\\")\'\n     || $item == \'$(\\\"#supporting_content\\\")\'\n     || $item == \'$(\\\"#view_modes\\\")\'\n    ) {\n        return 1;\n    }\n    return 0;\n}\n\n\n\n// ===========================================================\n\n\n//===========================================\n// Kickstart the plugin after installation / activation / deletion\n\nfunction bot_wtc_welcome($evt, $stp)\n{\n    switch ($stp) {\n        case \'installed\':\n            bot_wtc_install();\n            break;\n        case \'enabled\':\n            bot_wtc_update();\n            break;\n        case \'deleted\':\n            bot_wtc_deinstall();\n            break;\n    }\n\n    return;\n}\n\n\n//===========================================\n// Set up bot_wtc tables\n\nfunction bot_wtc_install()\n{\n    // Create the bot_wtc table\n    safe_create(\'bot_wtc\', \'\n        id            INT          NOT NULL AUTO_INCREMENT,\n        item          VARCHAR(255) NOT NULL,\n        position      VARCHAR(255) NOT NULL,\n        destination   VARCHAR(255) NOT NULL,\n        sections      TEXT         NOT NULL,\n        class         VARCHAR(255) NOT NULL,\n        PRIMARY KEY (id)\n    \');\n\n    // Set pref entries in txp_prefs table\n    set_pref(\'bot_wtc_script\', \'\', \'bot_wtc_\', \'2\');\n    set_pref(\'bot_wtc_static_sections\', \'\', \'bot_wtc_\', \'2\');\n}\n\n\n//===========================================\n// Remove bot_wtc tables\n\nfunction bot_wtc_deinstall()\n{\n    // Drop the bot_wtc table\n    safe_drop(\"bot_wtc\");\n    // Remove bot_wtc prefs\n    safe_delete(\'txp_prefs\', \'event = \"bot_wtc_\"\');\n}\n\n\n// ===========================================================\n// Updates cfs selectors in db\n\nfunction bot_wtc_update()\n{\n    // proceeds only if plugin is already installed\n    if (!bot_wtc_check_install()) {\n        return;\n    }\n    safe_alter(\'bot_wtc\', \'CHANGE sections sections TEXT\', 0);\n    $db_values = bot_wtc_fetch_db(); // array\n    for ($i =0; $i < count($db_values); $i++) {\n        $id = $db_values[$i][\'id\'];\n        $item = $db_values[$i][\'item\'];\n        $destination = $db_values[$i][\'destination\'];\n        // Updates cfs\n        // If item contains the substring \'custom\'\n        if (strpos($item, \'custom\')) {\n            // Get cf number from selector string (i.e. just the numbers)\n            $cf_number = preg_replace(\"/[^0-9]/\", \'\', $item);\n            // New selector\n            $selector = \'$(\".custom-\'.$cf_number.\'\")\';\n            safe_update(\'bot_wtc\', \'item = \"\'.doslash($selector).\'\"\', \'id = \"\'.$id.\'\"\');\n        }\n        // If destination contains the substring \'custom\'\n        if (strpos($destination, \'custom\')) {\n            // Get cf number from selector string (i.e. just the numbers)\n            $cf_number = preg_replace(\"/[^0-9]/\", \'\', $destination);\n            // New selector\n            $selector = \'$(\".custom-\'.$cf_number.\'\")\';\n            safe_update(\'bot_wtc\', \'destination = \"\'.doslash($selector).\'\"\', \'id = \"\'.$id.\'\"\');\n        }\n    }\n}\n\n\n// ===========================================================\n// Check if the bot_wtc table exists\n\nfunction bot_wtc_check_install()\n{\n    // If number of rows is false, table does not exist\n    if (safe_count(\"bot_wtc\", \"1 = 1\") !== false) {\n        return true;\n    }\n    return false;\n}\n\n\n//===========================================\n// Outputs all items for selectInput() (used for destination dropdown)\n\nfunction bot_all_items_selectinput()\n{\n    global $bot_items;\n    // Get cfs array in the form: cf_selector => cf_name\n    $cfs = bot_get_cfs();\n    // Final values for the txp function selectInput (including cfs if any)\n    // If there is at least one custom field set adds cfs to $bot_items array\n    if (is_array($cfs)) {\n        $all_items_select = array_merge($cfs, $bot_items);\n    } else {\n        $all_items_select = $bot_items;\n    }\n    return $all_items_select;\n    natcasesort($all_items_select);\n}\n\n\n//===========================================\n// Outputs only \'not-yet-used\' items for selectInput() (used for items dropdown)\n\nfunction bot_contextual_selectinput($current = \"\")\n{\n    global $bot_items;\n\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n    $all_items = bot_all_items_selectinput();\n    if (bot_wtc_check_install()) {\n        // Numeric array of item values from the db\n        $used_items = safe_column(\'item\', \'bot_wtc\', \'1=1\');\n        foreach ($all_items as $item => $title) {\n            if (!in_array($item, $used_items)) {\n                $items_selectInput[$item] = $title;\n            }\n        }\n    } else {\n        $items_selectInput = $all_items;\n    }\n    // If the parameter is given adds current value to array\n    if ($current) {\n        $items_selectInput[$current] = $all_items[$current];\n    }\n    return  $items_selectInput;\n}\n\n\n\n// ===========================================================\n// bot_wtc tab\n// ===========================================================\n\n// Outputs the rows for the html table in the bot_wtc_tab\n\nfunction bot_wtc_output_rows()\n{\n    global $bot_items;\n\n    $selectInput_for_position = array(\'insertBefore\'=>gTxt(\'bot_wtc_before\'),\'insertAfter\'=>gTxt(\'bot_wtc_after\')); // position values for the txp function selectInput\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $destination_selectInput = bot_all_items_selectinput();\n    $items_selectInput = bot_contextual_selectinput();\n\n    // Builds rows for new item sections list\n    $sections= bot_get_sections(); // Get sections array\n    $new_item_sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        $new_item_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'new_item_sections[]\', $key, \'0\', 0, $key.\'_0\').\n            n.tag($value, \'label\', array(\'for\' => $key.\'_0\')).\n            n.\'</div>\';\n        $new_item_sections_rows .= $new_item_sections_row;\n    }\n    $new_item_sections_rows .= \'<p ><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p>\'; // hide all/none\n\n    // New item insertion\n    $rows = \"\";\n    $input_row = tr(\n        td(selectInput(\'new_item\', bot_contextual_selectinput(), \'\', \'1\'), \'\', \'\').\n        td(selectInput(\'new_item_position\', $selectInput_for_position, \'\', \'1\')).\n        td(selectInput(\'new_item_destination\', bot_all_items_selectinput(), \'\', \'1\')).\n        td(\'<p><a href=\"#\" class=\"bot_push\">\'.gTxt(\'bot_wtc_section_list\').\'<span class=\"ui-icon ui-icon-caret-1-s\"></span></a></p><div class=\"bot_collapse\">\'.$new_item_sections_rows.\'</div>\').\n        td(finput(\'text\', \'new_item_class\', \'\')).\n        td()\n        );\n    $rows .= $input_row;\n\n    // Other rows - output if at least one record was already set\n    if ($db_values) {\n        for ($i = 0; $i < count($db_values); $i++) {\n            // data for \"sections to show\" selectinput - decides wether a section is checked or not\n            $bot_hide_in_this_sections_array = explode(\'|\', $db_values[$i][\'sections\']);\n            $item_sections_rows = \'\';\n            // If section is in db mark as checked\n            foreach ($sections as $key => $value) {\n                $checked = in_array($key, $bot_hide_in_this_sections_array) ? \'1\': \'0\';\n                $item_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n                    n.checkbox(\'bot_wtc_sections_for_id_\'.$db_values[$i][\'id\'].\'[]\', $key, $checked, 0, $key.\'_\'.$db_values[$i][\'id\']).\n                    n.tag($value, \'label\', array(\'for\' => $key.\'_\'.$db_values[$i][\'id\'])).\n                    n.\'</div>\';\n                $item_sections_rows .= $item_sections_row;\n            }\n            $item_sections_rows .= \'<p><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p>\'; // hide all/none\n            $single_row = tr(\n            td(selectInput(\'item[]\', bot_contextual_selectinput($db_values[$i][\'item\']), $db_values[$i][\'item\'], \'0\'), \'\', \'\')\n            .td(selectInput(\'item_position[]\', $selectInput_for_position, $db_values[$i][\'position\'], \'1\'))\n            .td(selectInput(\'item_destination[]\', bot_all_items_selectinput(), $db_values[$i][\'destination\'], \'1\'))\n            .td(\'<p><a href=\"#\" class=\"bot_push\">\'.gTxt(\'bot_wtc_section_list\').\'<span class=\"ui-icon ui-icon-caret-1-s\"></span></a></p><div class=\"bot_collapse\">\'.$item_sections_rows.\'</div>\')\n            .td(finput(\'text\', \'item_class[]\', $db_values[$i][\'class\']))\n            .td(tag(\n                checkbox(\'bot_delete_id[]\', $db_values[$i][\'id\'], \'0\', 0, \'bot_delete_id_\'.$db_values[$i][\'id\']).\'<label for=\"bot_delete_id_\'.$db_values[$i][\'id\'].\'\"> \'.gTxt(\'delete\').\'</label>\',\n                \'div\',\n                array(\'class\' => \'txp-form-checkbox\')\n                )\n                .hInput(\'bot_wtc_id[]\', $db_values[$i][\'id\'])\n                )\n            );\n\n            $rows .= $single_row;\n        }\n    };\n    return $rows;\n}\n\n\n\n//===========================================\n// Builds rows for sections list\n\nfunction bot_wtc_static_sections_select()\n{\n    // Get sections array\n    $sections= bot_get_sections();\n    // Fetch prefs value for bot_wtc_static_sections\n    $static_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n    // Creates an array of static sections from the string in txp_prefs\n    $static_sections = explode(\'|\', $static_sections);\n    $static_sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        // If section is in db mark as checked\n        $checked = in_array($key, $static_sections) ? \'1\': \'0\';\n        $static_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'static_sections[]\', $key, $checked, 0, \'section_\'.$key).\n            n.tag($value, \'label\', array(\'for\' => \'section_\'.$key)).\n            n.\'</div>\';\n        $static_sections_rows .= $static_sections_row;\n    }\n    return $static_sections_rows;\n}\n\n\n\n//===========================================\n// Advanced multiple selection on bot_wtc tab\n\nfunction bot_advanced()\n{\n    global $bot_items;\n    // Get items array\n    $items = bot_all_items_selectinput();\n    $item_rows = \'\';\n    foreach ($items as $key => $value) {\n        $item_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'bot_adv_items[]\', $key, \'0\', 0, $value).\n            n.tag($value, \'label\', array(\'for\' => $value)).\n            n.\'</div>\';\n        $item_rows .= $item_row;\n        // Get sections array\n        $sections= bot_get_sections();\n    }\n    $sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        $sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'bot_adv_sections[]\', $key, \'0\', 0, $key).\n            n.tag($value, \'label\', array(\'for\' => $key)).\n            n.\'</div>\';\n        $sections_rows .= $sections_row;\n    }\n\n    return n.wrapRegion(\'bot_advanced\',\n            form(\n                bot_update_button(\'publish-advanced\').\n                n.\'<div class=\"txp-grid\">\'.\n                n.\'<div id=\"bot_adv_items\" class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_items\').\'</h4>\'.$item_rows.\'</div>\'.\n                  \'<div id=\"bot_adv_hide\"  class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_hide_in_section\').\'</h4>\'.$sections_rows.\'<p><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p></div>\'.\n                  \'<div id=\"bot_adv_class\" class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_set_css_class\').\'</h4>\'.finput(\'text\', \'bot_adv_class\', \'\').\'</div>\'.\n                n.\'</div>\'\n            ),\n        \'bot_advanced-details\',\n        \'bot_wtc_advanced_multiple_group\',\n        \'bot_advanced_multiple\',\n        \'\',\n        \'\',\n        1\n    );\n\n}\n\n//===========================================\n\n\n\nfunction bot_wtc_tab($event, $step, $msg=\'\')\n{\n    global $bot_items;\n    $cfs = bot_get_cfs();\n\n    if ($step == \'update\') {\n        // Set function variables\n        $new_item = ps(\'new_item\'); //variable\n        $new_item_position = ps(\'new_item_position\'); //variable\n        $new_item_destination = ps(\'new_item_destination\'); //variable\n        $new_item_sections = ps(\'new_item_sections\'); //array\n        $new_item_class = ps(\'new_item_class\'); //variable\n        $bot_wtc_script = ps(\'bot_wtc_script\'); //variable\n        $static_sections = ps(\'static_sections\'); //variable\n        $item = ps(\'item\'); //array\n        $item_position = ps(\'item_position\'); //array\n        $item_destination = ps(\'item_destination\'); //array\n        $item_class = ps(\'item_class\'); //array\n        $bot_wtc_id = ps(\'bot_wtc_id\'); //array\n        $delete_id = ps(\'bot_delete_id\'); //array\n        $bot_adv_items = ps(\'bot_adv_items\'); //array\n        $bot_adv_sections = ps(\'bot_adv_sections\'); //array\n        $bot_adv_class = ps(\'bot_adv_class\'); //variable\n\n        // db update for existing items\n\n        // If at least a saved item exists\n        if ($item) {\n            // Array of values from the db\n            $db_values = bot_wtc_fetch_db();\n            for ($i = 0; $i < count($item); $i++) {\n                // Builds the posted variable name for current item sections\n                $item_posted_sections_name = \'bot_wtc_sections_for_id_\'.$db_values[$i][\'id\'];\n                $item_sections = isset($_POST[$item_posted_sections_name]) ? $_POST[$item_posted_sections_name] : \'\'; //array\n                // Builds sections string for current item\n                $item_sections_string = $item_sections ? implode(\'|\', $item_sections): \'\';\n                // Allowed input data combinations\n                if (($item[$i] && $item_destination[$i] && $item_position[$i])\n                || ($item[$i] && $item_class[$i] && !$item_destination[$i] && !$item_position[$i])\n                || ($item[$i] && $item_sections_string && !$item_destination[$i] && !$item_position[$i])) {\n                    // Check if a column/region is linked with a non-column item BUT ONLY IF both items are set (otherwise couldn\'t apply i.e. class to a single region)\n                    if (!((bot_wtc_is_region($item[$i]) xor bot_wtc_is_region($item_destination[$i])) && $item_destination[$i])) {\n                        // Check if item is different from destination\n                        if ($item[$i] != $item_destination[$i]) {\n                            safe_update(\n                                \"bot_wtc\",\n                                \"position = \'\".doslash($item_position[$i]).\"\',\n                                 destination = \'\".doslash($item_destination[$i]).\"\',\n                                 item = \'\".doslash($item[$i]).\"\',\n                                 sections = \'\".doslash($item_sections_string).\"\',\n                                 class = \'\".doslash($item_class[$i]).\"\'\",\n                                \"id = \'\".$bot_wtc_id[$i].\"\'\"\n                            );\n                        } else {\n                            $msg = array(gTxt(\'bot_wtc_same_item_warning\'), E_ERROR);\n                        }\n                    } else {\n                        $msg = array(gTxt(\'bot_wtc_region_warning\'), E_ERROR);\n                    }\n                } else {\n                    $msg = array(gTxt(\'bot_wtc_combo_warning\'), E_ERROR);\n                }\n            }\n        }\n\n        // db insert for new item\n\n        // Allowed input combinations\n        if (($new_item && $new_item_destination && $new_item_position)\n        || ($new_item && $new_item_class && !$new_item_destination && !$new_item_position)\n        || ($new_item && $new_item_sections && !$new_item_destination && !$new_item_position)) {\n            // Check if a column is linked with a non-column item\n            if (!((bot_wtc_is_region($new_item) xor bot_wtc_is_region($new_item_destination)) &&  $new_item_destination)) {\n                // Check items are not the same\n                if ($new_item != $new_item_destination) {\n                    // Transforms the sections array in a string\n                    $new_item_sections_string = $new_item_sections ? implode(\'|\', $new_item_sections) : \'\';\n                    safe_insert(\n                        \"bot_wtc\",\n                    \"position = \'\"\n                    .doslash($new_item_position)\n                    .\"\', destination = \'\"\n                    .doslash($new_item_destination)\n                    .\"\', class = \'\"\n                    .doslash($new_item_class)\n                    .\"\', sections = \'\"\n                    .doslash($new_item_sections_string)\n                    .\"\', item = \'\"\n                    .doslash($new_item)\n                    .\"\'\"\n                    );\n                } else {\n                    $msg = array(gTxt(\'bot_wtc_same_item_warning\'), E_ERROR);\n                }\n            } else {\n                $msg = array(gTxt(\'bot_wtc_td_warning\'), E_ERROR);\n            }\n        } elseif ($new_item || $new_item_destination || $new_item_position || $new_item_class || $new_item_sections) {\n            $msg = array(gTxt(\'bot_wtc_combo_warning\'), E_ERROR);\n        }\n\n        // Checks if there is something to delete\n        if ($delete_id) {\n            foreach ($delete_id as $id) {\n                safe_delete(\'bot_wtc\', \'id =\"\'.$id.\'\"\');\n            }\n        }\n\n        // Update advanced preferences\n\n        // Check if item AND section OR class is selected\n        if ($bot_adv_items and ($bot_adv_sections || $bot_adv_class)) {\n            // First array: all values from db\n            $db_values = bot_wtc_fetch_db();\n\n            if ($bot_adv_sections) {\n                // More specific array: only item => sections\n                $bot_db_sections = array();\n                for ($i =0; $i < count($db_values); $i++) {\n                    $bot_db_sections[$db_values[$i][\'item\']] = $db_values[$i][\'sections\'];\n                }\n\n                // Iterates posted items\n                foreach ($bot_adv_items as $item) {\n                    // Fetch any existing sections from db for current item and merges arrays eliminating duplicates\n                    if (is_array($bot_db_sections) and array_key_exists($item, $bot_db_sections)) {\n                        $db_sect_array = explode(\'|\', $bot_db_sections[$item]);\n                        $final_array = array_unique(array_merge($db_sect_array, $bot_adv_sections));\n                        // New sections string\n                        $bot_adv_sections_string = implode(\'|\', $final_array);\n                    } else {\n                        $bot_adv_sections_string = implode(\'|\', $bot_adv_sections);\n                    }\n                    safe_upsert(\n                        \"bot_wtc\",\n                        \"sections = \'\"\n                        .doslash($bot_adv_sections_string)\n                        .\"\'\",\n                        \"item = \'\".doslash($item).\"\'\"\n                    );\n                }\n            }\n\n            if ($bot_adv_class) {\n                // More specific array: only item => classes\n                $bot_db_classes = array();\n                for ($i =0; $i < count($db_values); $i++) {\n                    $bot_db_classes[$db_values[$i][\'item\']] = $db_values[$i][\'class\'];\n                }\n                // Iterates posted items\n                foreach ($bot_adv_items as $item) {\n                    // Fetch any existing class from db for current item and merges arrays eliminating duplicates\n                    if (is_array($bot_db_classes) and array_key_exists($item, $bot_db_classes)) {\n                        $db_class_array = explode(\' \', $bot_db_classes[$item]);\n                        $posted_class_array = explode(\' \', $bot_adv_class);\n                        $final_array = array_unique(array_merge($db_class_array, $posted_class_array));\n                        // New sections string\n                        $bot_adv_classes_string = implode(\' \', $final_array);\n                    } else {\n                        $bot_adv_classes_string = $bot_adv_class;\n                    }\n                    safe_upsert(\n                        \"bot_wtc\",\n                        \"class = \'\"\n                        .doslash($bot_adv_classes_string)\n                        .\"\'\",\n                        \"item = \'\".doslash($item).\"\'\"\n                    );\n                }\n            }\n        } elseif ($bot_adv_sections || $bot_adv_class) {\n            $msg = array(gTxt(\'bot_wtc_no_items_warning\'), E_ERROR);\n        }\n\n        // Updates static sections prefs (including when all deselected)\n        $db_static_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n        if ($static_sections || (empty($static_sections) && !empty($db_static_sections)) ) {\n            if ($static_sections) {\n                $static_sections_string = implode(\'|\', $static_sections);\n            }\n            safe_update(\'txp_prefs\', \'val= \"\'.doslash($static_sections_string).\'\", html=\"text_input\" \', \'name = \"bot_wtc_static_sections\"\');\n        }\n\n        // Updates script prefs\n        if ($bot_wtc_script) {\n            safe_update(\'txp_prefs\', \'val= \\\'\'.trim(doslash($bot_wtc_script)).\'\\\', html=\\\'textarea\\\' \', \'name = \\\'bot_wtc_script\\\'\');\n        }\n\n        // No error message? Then announce success\n        if (empty($msg)) {\n            $msg = gTxt(\'preferences_saved\');\n        }\n    }\n\n    // Top of page\n    pagetop(gTxt(\'bot_wtc_tab_name\'), $msg);\n\n    // Page heading and \'toggle advanced\'\n    echo tag_start(\'div\', array(\'class\' => \'txp-layout\')).\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                hed(gTxt(\'bot_wtc_tab_name\'), 1, array(\'class\' => \'txp-heading\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                tag_start(\'div\', array(\'class\' => \'txp-list-options\')).\n                    href(\'<span class=\"ui-icon ui-icon-gear\"></span>\'.gTxt(\'bot_wtc_advanced\'), \'#\', array(\'id\' => \'bot_advanced_open\')).\n                tag_end(\'div\').\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-1col\', \'id\' => $event.\'_container\'));\n\n    // What to show when accessing tab\n    if (bot_wtc_check_install()) {\n\n        // Fetch prefs value for bot_wtc_script\n        $bot_wtc_script = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_script\"\');\n\n        // Advanced / multiple panel\n        echo n.t.bot_advanced();\n\n        // Main div\n        echo n.t.\'<div id=\"bot_main\" class=\"txp-layout-1col\">\';\n\n        // \'Expand all\' and \'Collapse all\' controls\n        echo graf(\n            href(\'<span class=\"ui-icon ui-icon-arrowthickstop-1-s\"></span> \'.gTxt(\'expand_all\'), \'#\', array(\n                \'id\'            => \'bot_expand_all\',\n                \'class\'         => \'txp-expand-all\',\n                \'aria-controls\' => \'bot_main\',\n            )).\n            href(\'<span class=\"ui-icon ui-icon-arrowthickstop-1-n\"></span> \'.gTxt(\'collapse_all\'), \'#\', array(\n                \'id\'            => \'bot_collapse_all\',\n                \'class\'         => \'txp-collapse-all\',\n                \'aria-controls\' => \'bot_main\',\n            )), array(\'id\' => \'bot_controls\', \'class\' => \'txp-actions txp-list-options\')\n        );\n\n        // Table\n        echo form( // Begin form\n\n            tag_start(\'table\', array(\'id\' => \'bot_wtc_table\', \'class\' => \'txp-list--no-options\')).\n\n            n.tag_start(\'thead\').\n                tr(\n                    hCell(gTxt(\'bot_wtc_item\'), \'\', array(\'class\' => \'txp-list-col-item\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_position\'), \'\', array(\'class\' => \'txp-list-col-position\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_destination\'), \'\', array(\'class\' => \'txp-list-col-destination\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_hide\'), \'\', array(\'class\' => \'txp-list-col-hide\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_class\'), \'\', array(\'class\' => \'txp-list-col-class\', \'scope\' => \'col\')).\n                    hcell()\n                ).\n            n.tag_end(\'thead\').\n                bot_wtc_output_rows(). // HTML rows generated by \"bot_wtc_output_rows()\"\n            n.tag_end(\'table\').\n\n            bot_update_button().\n\n            n.wrapRegion(\'bot_static_sections\',\n                    bot_wtc_static_sections_select().\n                    bot_update_button(),\n                \'bot_static_sections-details\',\n                \'bot_wtc_static_sections_group\',\n                \'bot_static_sections\',\n                \'bot_collapse\').\n\n            n.wrapRegion(\'bot_js_box\',\n                    \'<div class=\"txp-actions\">\'.\n                    n.\'<a id=\"bot_js_link\" href=\"#\"><span class=\"ui-icon ui-icon-circlesmall-plus\"></span> \'.gTxt(\'bot_wtc_add_external_script\').\'</a>\'.\n                    n.\'<a id=\"bot_jq_link\" href=\"#\"><span class=\"ui-icon ui-icon-circlesmall-plus\"></span> \'.gTxt(\'bot_wtc_add_inline_code\').\'</a>\'.\n                    n.\'</div>\'.\n                    n.\'<textarea id=\"bot_wtc_script\" name=\"bot_wtc_script\" cols=\"60\" rows=\"10\">\'.$bot_wtc_script.\'</textarea>\'. // script textarea\n                    n.bot_update_button(),\n                \'bot_js_box-details\',\n                \'bot_wtc_add_javascript_group\',\n                \'bot_add_javascript\',\n                \'bot_collapse\').\n\n            n.tag_end(\'div\')\n\n        );  // End form\n    }\n\n    // Snippets to insert in the script box\n    $bot_jquery_snippet = \'<script>\\n    $(document).ready(function() {\\n        // your code here\\n    });\\n<\\/script>\\n\';\n    $bot_js_snippet = \'<script src=\\\"path_to_script\\\"><\\/script>\\n\';\n\n    // Add jquery action\n    echo\n    \'<script>\'.n.\n    \'    $(document).ready(function() {\'.n.\n            \'$(\"div.bot_collapse\").hide()\'.n.\n            \'$(\"section#bot_advanced\").hide()\'.n.\n            \'$(\"a.bot_push\").click(function(){\'.n.\n            \'  $(this).toggleClass(\"bot_arrow\").parent().next().slideToggle(\"fast\");\'.n.\n            \'  $(this).children(\".ui-icon\").toggleClass(\"ui-icon-caret-1-s\").toggleClass(\"ui-icon-caret-1-n\");\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"#bot_collapse_all\").click(function(){\'.n.\n            \'  $(\"div.bot_collapse\").slideUp(\"fast\").parent().find(\".ui-icon\").removeClass(\"ui-icon-caret-1-n\").addClass(\"ui-icon-caret-1-s\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"#bot_expand_all\").click(function(){\'.n.\n            \'  $(\"div.bot_collapse\").slideDown(\"fast\").parent().find(\".ui-icon\").removeClass(\"ui-icon-caret-1-s\").addClass(\"ui-icon-caret-1-n\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"#bot_advanced_open\").click(function(){\'.n.\n            \'  $(\"section#bot_advanced\").slideToggle(\"fast\");\'.n.\n            \'  $(\"div#bot_main\").toggle(\"fast\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"a.bot_all\").click(function(){\'.n.\n            \'  $(this).parent().parent().find(\"input\").prop(\"checked\", true);\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"a.bot_none\").click(function(){\'.n.\n            \'  $(this).parent().parent().find(\"input\").prop(\"checked\", false);\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"#bot_jq_link\").click(function(){\'.n.\n            \'  var areaValue = $(\"#bot_wtc_script\").val();\'.n.\n            \'  $(\"#bot_wtc_script\").val(areaValue + \"\'.$bot_jquery_snippet.\'\");\'.n.\n            \'  return(false);\'.n.\n            \'});\'.n.\n            \'$(\"#bot_js_link\").click(function(){\'.n.\n            \'  var areaValue = $(\"#bot_wtc_script\").val();\'.n.\n            \'  $(\"#bot_wtc_script\").val(areaValue + \"\'.$bot_js_snippet.\'\");\'.n.\n            \'  return(false);\'.n.\n            \'});\'.n.\n    \'    });\'.n.\n    \'</script>\';\n}\n\n\n\n// ===========================================================\n// plugins output\n// ===========================================================\n\n// CSS for the plugin tab under extensions\n\nfunction bot_wtc_css()\n{\n    global $event;\n    // Output css only in \'bot_wtc\' extensions tab.\n    if ($event != \'bot_wtc_tab\') {\n        return;\n    }\n\n    echo \'<style>\n\n    #bot_wtc_table {\n        padding: 10px 0 20px;\n        margin-left: 0;\n    }\n\n    #bot_wtc_table td {\n        vertical-align: top;\n        padding: 5px;\n        white-space: nowrap;\n    }\n\n    #bot_wtc_table td p {\n        margin: 3px 0 0 0;\n    }\n    #bot_wtc_table td:last-child .txp-form-checkbox {\n        line-height: 2.3;\n    }\n\n    #bot_wtc_table td select {\n        width: 100%;\n        background-color: transparent;\n    }\n\n    .txp-form-checkbox * {\n        cursor: pointer;\n    }\n\n    #bot_advanced {}\n\n    #bot_advanced .txp-grid-cell {\n        border: none;\n        margin-bottom: 20px;\n    }\n    input[name=\"bot_adv_class\"] {\n        width: 100%;\n    }\n\n    #bot_uninstall-details, #bot_static_sections-details, #bot_js_box-details {\n        padding: 0 20px;\n    }\n\n    #bot_uninstall-details {\n        padding-bottom: 20px;\n    }\n\n    #bot_wtc_script {\n        width: 100%;\n        border: dotted #ccc 1px;\n    }\n\n    .bot_update_button {\n        margin: 20px 0;\n        clear: both;\n    }\n\n    [type=\"submit\"].publish-advanced, a.publish-advanced, button.publish-advanced {\n        background-color: #ffd21b;\n        background-image: linear-gradient(#ffe168,#ffd21b);\n        border: 1px solid #f7c600;\n        font-weight: 700;\n        height: 2.76923076923077em;\n        padding: .61538461538462em 1em;\n    }\n\n    a.bot_push {\n        padding-right: 13px;\n        line-height: 1.85;\n    }\n\n    a.bot_push.bot_arrow span.ui-icon {\n        background-position: 0 0;\n    }\n    #bot_static_sections-details {\n        padding-top: 20px;\n    }\n    #bot_js_box-details .txp-actions {\n        padding-top: 5px;\n        padding-bottom: 10px;\n    }\n\n</style>\';\n}\n\n\n// ===========================================================\n// Builds array of sections to hide\n\nfunction bot_hide_per_section_array()\n{\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    for ($i =0; $i<count($db_values); $i++) {\n        if ($db_values[$i][\'sections\']) {\n            $sections_to_hide = explode(\'|\', $db_values[$i][\'sections\']);\n            foreach ($sections_to_hide as $section) {\n                $bot_hide_per_section[$section][] = $db_values[$i][\'item\'];\n            }\n        }\n    }\n\n    // Return array only if values exist\n    if (isset($bot_hide_per_section)) {\n        return $bot_hide_per_section;\n    }\n}\n\n\n\n// ===========================================================\n// js rows dealing with items to hide on section change AND on page load\n\nfunction bot_wtc_jquery_hide_sections_rows()\n{\n    $bot_hide_per_section = bot_hide_per_section_array();\n    foreach ($bot_hide_per_section as $section => $fields) {\n        echo n.\'            if (value==\"\'.$section.\'\"){\'.n;\n        for ($i =0; $i<count($fields); $i++) {\n            echo \'                \'.$fields[$i].\'.hide();\'.n;\n        }\n        echo \'            }\'.n;\n    }\n}\n\n\n\n// ===========================================================\n// js rows to restore every previously hidden item on section change\n\nfunction bot_wtc_jquery_restore_rows()\n{\n    $bot_hide_per_section = bot_hide_per_section_array();\n    foreach ($bot_hide_per_section as $section => $fields) {\n        for ($i =0; $i<count($fields); $i++) {\n            $out[] = $fields[$i];\n        }\n    }\n    $out = array_unique($out);\n    foreach ($out as $value) {\n        echo \'            \'.$value.\'.show();\'.n;\n    }\n}\n\n\n\n// ===========================================================\n//  Builds the script\n\nfunction bot_hide_per_section()\n{\n    global $event;\n    if ($event !== \'article\') {\n        return;\n    }\n\n    $bot_hide_per_section = bot_hide_per_section_array();\n    // Output js only if values exist\n    if ($bot_hide_per_section) {\n        echo\n                \'<script>\'.n.\n                \'    $(document).ready(function() {\'.n;\n        echo\n                \'        $(\"select#section\").change(function(){\'.n;\n        bot_wtc_jquery_restore_rows();\n        echo\n                \'            var value = $(\"select#section\").val();\';\n        bot_wtc_jquery_hide_sections_rows();\n        echo\n                \'        }).change();\'.n.\n                \'    });\'.n.\n                \'</script>\';\n    }\n}\n\n\n\n// ===========================================================\n// Invisible sections in section list\n\nfunction bot_hidden_sections()\n{\n    global $event;\n    if ($event !== \'article\') {\n        return;\n    }\n\n    // Fetch prefs value for bot_wtc_static_sections\n    $bot_hidden_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n\n    // Output js only if values exist\n    if ($bot_hidden_sections) {\n        $sections = explode(\"|\", $bot_hidden_sections);\n        echo\n        \'<script>\'.n.\n        \'    $(document).ready(function() {\'.n;\n        foreach ($sections as $value) {\n            echo    \'           $(\"select#section option:not(:selected)[value=\\\'\'.$value.\'\\\']\").remove();\'.n;\n        }\n        echo\n        \'    });\'.n.\n        \'</script>\';\n    }\n}\n\n// ===========================================================\n// Helper function: retrieves parent key of an array item\n\nfunction search_key($needle, $haystack)\n{\n    foreach($haystack as $key => $val) {\n         if(in_array($needle, $val)) {\n            return $key;\n         }\n   }\n}\n\n// ===========================================================\n// js rows: reposition items and add classes to items\n\nfunction bot_wtc_jquery_rows()\n{\n    global $bot_items;\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $rows = \'\';\n    for ($i = 0; $i <count($db_values); $i++) {\n        $item = ($db_values[$i][\'item\'] != \'\') ? $db_values[$i][\'item\'] : \'\';\n        $position = ($db_values[$i][\'position\'] != \'\') ? \'.\'.$db_values[$i][\'position\'] : \'\';\n        $destination = ($db_values[$i][\'destination\'] != \'\') ? \'(\'.$db_values[$i][\'destination\'].\')\' : \'\';\n        $class = ($db_values[$i][\'class\'] != \'\') ? \'.addClass(\"\'.$db_values[$i][\'class\'].\'\")\' : \'\';\n        $row = $item.$position.$destination.$class.\';\'.n;\n        $rows .= $row;\n    }\n    return $rows;\n};\n\n\n// ===========================================================\n// js rows: Clean up all elements outside their original containers (e.g. after async save)\n// requires $bot_arr_item_groups array\n\nfunction bot_wtc_jquery_cleanrefresh_rows()\n{\n    global $bot_items, $bot_arr_item_groups;\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $rows = \'\';\n    for ($i = 0; $i <count($db_values); $i++) {\n\n        $item = ($db_values[$i][\'item\'] != \'\') ? $db_values[$i][\'item\'] : \'\';\n        $item_selector = substr($item, 3, -2); // Remove \'$(\"\' and \'\")\'\n        // Find item\'s home container\n        $item_home = search_key($item_selector, $bot_arr_item_groups);\n\n        if (!empty($item_home)) {\n            $row = $item.\'.not(\"\'.$item_home.\' \'.$item_selector.\'\").remove();\'.n;\n            $rows .= $row;\n        }\n    }\n    return $rows;\n};\n\n\n\n// ===========================================================\n// Output customize and async refresh javascript\n\nfunction bot_wtc()\n{\n    // Fetch prefs value for bot_wtc_script\n    $bot_wtc_script = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_script\"\');\n    // Fetch \'position\' from db to check if a move is saved\n    $position = safe_column(\'position\', \'bot_wtc\', \'1=1\');\n    // Fetch \'class\' from db to check if a class is saved\n    $class = safe_column(\'class\', \'bot_wtc\', \'1=1\');\n\n    // Output code only if a preference is saved\n    if (isset($position) || isset($class)) {\n        echo\n        \'<script>\'.n.\n        \'    $(document).ready(function() {\'.n.\n        // Call cleanUp function on successful async save\n        \'       textpattern.Relay.register(\"txpAsyncForm.success\", botWtcDoRefresh);\'.n.\n\n        // CUSTOMIZE function\n        \'       function botWtcDoCustomize() {\'.n.\n                    // Position, class and hide rules\n                    bot_wtc_jquery_rows().n.\n        \'       }\'.n.\n                // Run once after page load\n        \'       botWtcDoCustomize();\'.n.\n\n        // CLEANUP function (called after async save)\n        \'       function botWtcDoRefresh() {\'.n.\n                    // Clean up all elements outside their original containers\n                    bot_wtc_jquery_cleanrefresh_rows().n.\n                    // Clean up all duplicate custom fields outside the custom fields group\n        \'           $(\"#txp-custom-field-group-content\").find(\".custom-field\").each(function(){\n                        var pattern = /\\bcustom-[0-9]+\\b/;\n                        var matchResult = $(this).attr(\"class\").match(pattern);\n                        if (matchResult && $(\".\"+matchResult[0]).length > 1) {\n                            $this= $(this);\n                            $(\".\"+matchResult[0]).not($this).remove();\n                        }\n                    })\'.n.\n                    // (Re-)perform write tab customize\n        \'           botWtcDoCustomize();\'.n.\n                    // Get selected section and use it to effect section change\n                    // to trigger hide by section rules\n        \'           var value = $(\"select#section\").val();\'.n.\n        \'           $(\"select#section\").val(value).change();\'.n.\n        \'       }\'.n.\n        \'    });\'.n.\n        \'</script>\';\n    }\n    if ($bot_wtc_script) {\n        echo n.$bot_wtc_script.n;\n    };\n}\n', '\n\nif (@txpinterface == \'admin\') {\n\n    global $event;\n\n    add_privs(\'bot_wtc_tab\', \'1,2\');\n    register_tab(\'extensions\', \'bot_wtc_tab\', \'Write tab customize\');\n    register_callback(\'bot_wtc_tab\', \'bot_wtc_tab\');\n\n    if ($event === \'article\') {\n        register_callback(\'bot_wtc\', \'admin_side\', \'head_end\');\n        register_callback(\'bot_hide_per_section\', \'admin_side\', \'head_end\');\n        register_callback(\'bot_hidden_sections\', \'admin_side\', \'head_end\');\n    }\n\n    if ($event === \'bot_wtc_tab\') {\n        register_callback(\'bot_wtc_css\', \'admin_side\', \'head_end\');\n    }\n\n    register_callback(\'bot_wtc_welcome\', \'plugin_lifecycle.bot_write_tab_customize\');\n\n}\n\n// UPDATE these two arrays to match Textpattern UI layout / gtxt string names\n\n// ===========================================\n// Array of Write Panel UI items\n// Key: gTxt name => Value: Jquery selector query\n// ===========================================\n\n$bot_arr_selectors = array(\n\n    \'writetab_main_content\' => \'$(\"#main_content\")\',\n\n        \'writetab_view_modes\' => \'$(\"#view_modes\")\',\n        \'title\'               => \'$(\".title\")\',\n        \'author\'              => \'$(\".author\")\',\n        \'body\'                => \'$(\".body\")\',\n        \'excerpt\'             => \'$(\".excerpt\")\',\n        \'article_markup\'      => \'$(\".body .txp-textfilter-options\")\',\n        \'excerpt_markup\'      => \'$(\".excerpt .txp-textfilter-options\")\',\n        \'textile_help\'        => \'$(\".textfilter-help\")\',\n\n    \'writetab_sidebar\' => \'$(\"#supporting_content\")\',\n\n        \'save\'                => \'$(\".txp-save\")\',\n\n        \'writetab_article_actions\' => \'$(\"#txp-article-actions\")\',\n\n            \'add_new_article\' => \'$(\".txp-new\")\',\n            \'duplicate\'       => \'$(\".txp-clone\")\',\n            \'view\'            => \'$(\".txp-article-view\")\',\n\n        \'page_article_nav_hed\' => \'$(\".nav-tertiary\")\',\n\n        \'sort_display\'        => \'$(\"#txp-write-sort-group\")\',\n\n            \'status\'          => \'$(\"#txp-container-status\")\',\n            \'section\'         => \'$(\".section\")\',\n            \'override_default_form\' => \'$(\".override-form\")\',\n\n        \'date_settings\'       => \'$(\"#txp-dates-group\")\',\n\n            \'publish\'         => \'$(\"#publish-datetime-group\")\',\n            \'publish_date\'    => \'$(\".posted.date\")\',\n            \'publish_time\'    => \'$(\".posted.time\")\',\n            \'reset_time\'      => \'$(\".reset-time\")\',\n\n            \'expired\'         => \'$(\"#expires-datetime-group\")\',\n            \'expire_date\'     => \'$(\".expires.date\")\',\n            \'expire_time\'     => \'$(\".expires.time\")\',\n            \'set_expire_now\'  => \'$(\".expire-now\")\',\n\n        \'categories\'          =>  \'$(\"#txp-categories-group\")\',\n\n            \'category1\'       => \'$(\".category-1\")\',\n            \'category2\'       => \'$(\".category-2\")\',\n\n        \'meta\'                => \'$(\"#txp-meta-group\")\',\n\n            \'url_title\'       => \'$(\".url-title\")\',\n            \'description\'     => \'$(\".description\")\',\n            \'keywords\'        => \'$(\".keywords\")\',\n\n        \'comment_settings\'    => \'$(\"#txp-comments-group\")\',\n\n            \'use_comments\'    => \'$(\".comments-annotate\")\',\n            \'comment_invitation\' => \'$(\".comment-invite\")\',\n\n        \'article_image_group\' => \'$(\"#txp-image-group\")\',\n\n            \'article_image\'   => \'$(\".article-image\")\',\n\n        \'custom\'              => \'$(\"#txp-custom-field-group\")\',\n\n        \'recent_articles\'     => \'$(\"#txp-recent-group\")\'\n\n);\n\n// ===========================================\n// Array of Write Panel UI item -> container/group allocation\n// Says where UI items originally reside for bot_wtc_jquery_cleanrefresh_rows()\n//\n// Format: \'#container-id/class\' => array (\n//              \'.item-id/class\',\n//              \'.item-id/class\',\n//          )\n//\n// Note: Not all panels and not all subcontainers are refreshed\n// They are included below but (intentionally) commented out.\n// ===========================================\n\nglobal $bot_arr_item_groups;\n$bot_arr_item_groups = array (\n\n/*\n    \'.txp-save-zone\' => array(\n        \'.txp-save\',\n        \'#txp-article-actions\'\n    ),\n*/\n    \'#txp-article-actions\' => array (\n        \'.txp-new\',\n        \'.txp-clone\',\n        \'.txp-article-view\'\n    ),\n/*\n    \'#txp-write-sort-group\' => array (\n        \'#txp-container-status\',\n        \'.section\',\n        \'.override-form\'\n    ),\n*/\n/*\n    \'#txp-dates-group\' => array (\n        \'#publish-datetime-group\',\n        \'#expires-datetime-group\',\n    ),\n*/\n    \'#publish-datetime-group\' => array (\n        \'.posted.date\',\n        \'.posted.time\',\n        \'.reset-time\'\n    ),\n    \'#expires-datetime-group\' => array (\n        \'.expires.date\',\n        \'.expires.time\',\n        \'.expire-now\'\n    ),\n/*\n    \'#txp-categories-group\' => array (\n        \'.category-1\',\n        \'.category-2\'\n    ),\n*/\n/*\n    \'#txp-meta-group\' => array (\n        \'.url-title\',\n        \'.description\',\n        \'.keywords\'\n    ),\n*/\n    \'#txp-comments-group\' => array (\n        \'.comments-annotate\',\n        \'.comment-invite\'\n    ),\n    \'#txp-image-group\' => array (\n        \'.article-image\'\n    )\n\n);\n\n\n    // Get language strings not in plugin for write tab item dropdowns\n    $ui_language = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n    $langObject = \\Txp::get(\'\\Textpattern\\L10n\\Lang\'); // Fetch ref to language class if not already done\n    // Extract strings from the given groups in the current UI language.\n    $strings = $langObject->extract($ui_language, \'article, tag, prefs, public\');\n\n    // Get just the relevant subset of strings that corresponds to dropdown content\n    $strings = array_intersect_key($strings, array_flip(array_keys($bot_arr_selectors)));\n\n    // Set the internal strings to use them (true = append to what\'s already loaded)\n    $langObject->setPack($strings, true);\n\n    // Creates the translated main plugins array ($bot_items)\n    global $bot_items;\n    foreach ($bot_arr_selectors as $title => $selector) {\n        bot_wtc_insert_in_main_array($title, $selector);\n    }\n    natcasesort($bot_items);\n\n\n\n// ===========================================================\n// Helper functions\n// ===========================================================\n\n// Helps build the main array\n\nfunction bot_wtc_insert_in_main_array($title, $selector)\n{\n    global $bot_items;\n\n    if (strpos($title, \'!bot!\')) {\n        $split_titles = explode(\"!bot!\", $title);\n        $title = \'\';\n        for ($i = 0; $i < count($split_titles); $i++) {\n            $title .= gTxt($split_titles[$i]); // split and build translated title\n        }\n    } else {\n        $title = gTxt($title); // Gets the title to allow translation\n    }\n    $bot_items [$selector] = gTxt($title);\n    return $bot_items;\n}\n\n\n// ===========================================\n// Creates an array of values extracted from the database\n\nfunction bot_wtc_fetch_db()\n{\n    if (bot_wtc_check_install()) {\n        $out = safe_rows(\'id, item, position, destination, sections, class\', \'bot_wtc \', \'1=1\');\n        return $out;\n    }\n}\n\n\n// ===========================================================\n// Creates an array of all cfs for selectInput\n\nfunction bot_get_cfs()\n{\n    $r = safe_rows_start(\'name, val, html\', \'txp_prefs\', \'event = \"custom\" AND val != \"\"\');\n    if ($r) {\n        global $arr_custom_fields;\n        while ($a = nextRow($r)) {\n            // Convert custom_XX_set into custom-XX\n            $name = str_replace(\'_\', \'-\', substr($a[\'name\'], 0, -4));\n            $html = $a[\'html\'];\n            $selector = \'$(\".\'.$name.\'\")\';\n\n            // If glz_cf_gtxt function exists, use custom_field title if one exists\n            $cf_title = (function_exists(\'glz_cf_gtxt\')) ? glz_cf_gtxt($a[\'val\']) : \'\';\n            $val = (!empty($cf_title)) ? $cf_title : $a[\'val\'];\n            $arr_custom_fields[$selector] = $val;\n        }\n    }\n    if ($arr_custom_fields) {\n        natcasesort($arr_custom_fields); // Sort cfs - used instead of asort because is case-insensitive\n        return $arr_custom_fields;\n    }\n};\n\n\n// ===========================================================\n// Creates an array of all sections for selectInput\n\nfunction bot_get_sections()\n{\n    $r = safe_rows_start(\'name, title\', \'txp_section\', \'1=1\');\n    if ($r) {\n        while ($a = nextRow($r)) {\n            $name = $a[\'name\'];\n            $title = $a[\'title\'];\n            $sections[$name] = $title;\n        }\n    }\n    natcasesort($sections);\n    return $sections;\n}\n\n\n// ===========================================================\n// Update button\n\nfunction bot_update_button($btn_class=\'publish\')\n{\n    return n.\'<div class=\"bot_update_button\">\'\n        .n.eInput(\'bot_wtc_tab\')\n        .n.sInput(\'update\')\n        .n.fInput(\'submit\', \'update\', gTxt(\'bot_wtc_update_button\'), $btn_class)\n        .\'</div>\';\n}\n\n\n\n// ===========================================================\n// Checks if item is a layout region\n\nfunction bot_wtc_is_region($item)\n{\n    $item = get_magic_quotes_gpc() ? $item : doSlash($item) ;\n\n    if ($item == \'$(\\\"#main_content\\\")\'\n     || $item == \'$(\\\"#supporting_content\\\")\'\n     || $item == \'$(\\\"#view_modes\\\")\'\n    ) {\n        return 1;\n    }\n    return 0;\n}\n\n\n\n// ===========================================================\n\n\n//===========================================\n// Kickstart the plugin after installation / activation / deletion\n\nfunction bot_wtc_welcome($evt, $stp)\n{\n    switch ($stp) {\n        case \'installed\':\n            bot_wtc_install();\n            break;\n        case \'enabled\':\n            bot_wtc_update();\n            break;\n        case \'deleted\':\n            bot_wtc_deinstall();\n            break;\n    }\n\n    return;\n}\n\n\n//===========================================\n// Set up bot_wtc tables\n\nfunction bot_wtc_install()\n{\n    // Create the bot_wtc table\n    safe_create(\'bot_wtc\', \'\n        id            INT          NOT NULL AUTO_INCREMENT,\n        item          VARCHAR(255) NOT NULL,\n        position      VARCHAR(255) NOT NULL,\n        destination   VARCHAR(255) NOT NULL,\n        sections      TEXT         NOT NULL,\n        class         VARCHAR(255) NOT NULL,\n        PRIMARY KEY (id)\n    \');\n\n    // Set pref entries in txp_prefs table\n    set_pref(\'bot_wtc_script\', \'\', \'bot_wtc_\', \'2\');\n    set_pref(\'bot_wtc_static_sections\', \'\', \'bot_wtc_\', \'2\');\n}\n\n\n//===========================================\n// Remove bot_wtc tables\n\nfunction bot_wtc_deinstall()\n{\n    // Drop the bot_wtc table\n    safe_drop(\"bot_wtc\");\n    // Remove bot_wtc prefs\n    safe_delete(\'txp_prefs\', \'event = \"bot_wtc_\"\');\n}\n\n\n// ===========================================================\n// Updates cfs selectors in db\n\nfunction bot_wtc_update()\n{\n    // proceeds only if plugin is already installed\n    if (!bot_wtc_check_install()) {\n        return;\n    }\n    safe_alter(\'bot_wtc\', \'CHANGE sections sections TEXT\', 0);\n    $db_values = bot_wtc_fetch_db(); // array\n    for ($i =0; $i < count($db_values); $i++) {\n        $id = $db_values[$i][\'id\'];\n        $item = $db_values[$i][\'item\'];\n        $destination = $db_values[$i][\'destination\'];\n        // Updates cfs\n        // If item contains the substring \'custom\'\n        if (strpos($item, \'custom\')) {\n            // Get cf number from selector string (i.e. just the numbers)\n            $cf_number = preg_replace(\"/[^0-9]/\", \'\', $item);\n            // New selector\n            $selector = \'$(\".custom-\'.$cf_number.\'\")\';\n            safe_update(\'bot_wtc\', \'item = \"\'.doslash($selector).\'\"\', \'id = \"\'.$id.\'\"\');\n        }\n        // If destination contains the substring \'custom\'\n        if (strpos($destination, \'custom\')) {\n            // Get cf number from selector string (i.e. just the numbers)\n            $cf_number = preg_replace(\"/[^0-9]/\", \'\', $destination);\n            // New selector\n            $selector = \'$(\".custom-\'.$cf_number.\'\")\';\n            safe_update(\'bot_wtc\', \'destination = \"\'.doslash($selector).\'\"\', \'id = \"\'.$id.\'\"\');\n        }\n    }\n}\n\n\n// ===========================================================\n// Check if the bot_wtc table exists\n\nfunction bot_wtc_check_install()\n{\n    // If number of rows is false, table does not exist\n    if (safe_count(\"bot_wtc\", \"1 = 1\") !== false) {\n        return true;\n    }\n    return false;\n}\n\n\n//===========================================\n// Outputs all items for selectInput() (used for destination dropdown)\n\nfunction bot_all_items_selectinput()\n{\n    global $bot_items;\n    // Get cfs array in the form: cf_selector => cf_name\n    $cfs = bot_get_cfs();\n    // Final values for the txp function selectInput (including cfs if any)\n    // If there is at least one custom field set adds cfs to $bot_items array\n    if (is_array($cfs)) {\n        $all_items_select = array_merge($cfs, $bot_items);\n    } else {\n        $all_items_select = $bot_items;\n    }\n    return $all_items_select;\n    natcasesort($all_items_select);\n}\n\n\n//===========================================\n// Outputs only \'not-yet-used\' items for selectInput() (used for items dropdown)\n\nfunction bot_contextual_selectinput($current = \"\")\n{\n    global $bot_items;\n\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n    $all_items = bot_all_items_selectinput();\n    if (bot_wtc_check_install()) {\n        // Numeric array of item values from the db\n        $used_items = safe_column(\'item\', \'bot_wtc\', \'1=1\');\n        foreach ($all_items as $item => $title) {\n            if (!in_array($item, $used_items)) {\n                $items_selectInput[$item] = $title;\n            }\n        }\n    } else {\n        $items_selectInput = $all_items;\n    }\n    // If the parameter is given adds current value to array\n    if ($current) {\n        $items_selectInput[$current] = $all_items[$current];\n    }\n    return  $items_selectInput;\n}\n\n\n\n// ===========================================================\n// bot_wtc tab\n// ===========================================================\n\n// Outputs the rows for the html table in the bot_wtc_tab\n\nfunction bot_wtc_output_rows()\n{\n    global $bot_items;\n\n    $selectInput_for_position = array(\'insertBefore\'=>gTxt(\'bot_wtc_before\'),\'insertAfter\'=>gTxt(\'bot_wtc_after\')); // position values for the txp function selectInput\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $destination_selectInput = bot_all_items_selectinput();\n    $items_selectInput = bot_contextual_selectinput();\n\n    // Builds rows for new item sections list\n    $sections= bot_get_sections(); // Get sections array\n    $new_item_sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        $new_item_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'new_item_sections[]\', $key, \'0\', 0, $key.\'_0\').\n            n.tag($value, \'label\', array(\'for\' => $key.\'_0\')).\n            n.\'</div>\';\n        $new_item_sections_rows .= $new_item_sections_row;\n    }\n    $new_item_sections_rows .= \'<p ><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p>\'; // hide all/none\n\n    // New item insertion\n    $rows = \"\";\n    $input_row = tr(\n        td(selectInput(\'new_item\', bot_contextual_selectinput(), \'\', \'1\'), \'\', \'\').\n        td(selectInput(\'new_item_position\', $selectInput_for_position, \'\', \'1\')).\n        td(selectInput(\'new_item_destination\', bot_all_items_selectinput(), \'\', \'1\')).\n        td(\'<p><a href=\"#\" class=\"bot_push\">\'.gTxt(\'bot_wtc_section_list\').\'<span class=\"ui-icon ui-icon-caret-1-s\"></span></a></p><div class=\"bot_collapse\">\'.$new_item_sections_rows.\'</div>\').\n        td(finput(\'text\', \'new_item_class\', \'\')).\n        td()\n        );\n    $rows .= $input_row;\n\n    // Other rows - output if at least one record was already set\n    if ($db_values) {\n        for ($i = 0; $i < count($db_values); $i++) {\n            // data for \"sections to show\" selectinput - decides wether a section is checked or not\n            $bot_hide_in_this_sections_array = explode(\'|\', $db_values[$i][\'sections\']);\n            $item_sections_rows = \'\';\n            // If section is in db mark as checked\n            foreach ($sections as $key => $value) {\n                $checked = in_array($key, $bot_hide_in_this_sections_array) ? \'1\': \'0\';\n                $item_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n                    n.checkbox(\'bot_wtc_sections_for_id_\'.$db_values[$i][\'id\'].\'[]\', $key, $checked, 0, $key.\'_\'.$db_values[$i][\'id\']).\n                    n.tag($value, \'label\', array(\'for\' => $key.\'_\'.$db_values[$i][\'id\'])).\n                    n.\'</div>\';\n                $item_sections_rows .= $item_sections_row;\n            }\n            $item_sections_rows .= \'<p><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p>\'; // hide all/none\n            $single_row = tr(\n            td(selectInput(\'item[]\', bot_contextual_selectinput($db_values[$i][\'item\']), $db_values[$i][\'item\'], \'0\'), \'\', \'\')\n            .td(selectInput(\'item_position[]\', $selectInput_for_position, $db_values[$i][\'position\'], \'1\'))\n            .td(selectInput(\'item_destination[]\', bot_all_items_selectinput(), $db_values[$i][\'destination\'], \'1\'))\n            .td(\'<p><a href=\"#\" class=\"bot_push\">\'.gTxt(\'bot_wtc_section_list\').\'<span class=\"ui-icon ui-icon-caret-1-s\"></span></a></p><div class=\"bot_collapse\">\'.$item_sections_rows.\'</div>\')\n            .td(finput(\'text\', \'item_class[]\', $db_values[$i][\'class\']))\n            .td(tag(\n                checkbox(\'bot_delete_id[]\', $db_values[$i][\'id\'], \'0\', 0, \'bot_delete_id_\'.$db_values[$i][\'id\']).\'<label for=\"bot_delete_id_\'.$db_values[$i][\'id\'].\'\"> \'.gTxt(\'delete\').\'</label>\',\n                \'div\',\n                array(\'class\' => \'txp-form-checkbox\')\n                )\n                .hInput(\'bot_wtc_id[]\', $db_values[$i][\'id\'])\n                )\n            );\n\n            $rows .= $single_row;\n        }\n    };\n    return $rows;\n}\n\n\n\n//===========================================\n// Builds rows for sections list\n\nfunction bot_wtc_static_sections_select()\n{\n    // Get sections array\n    $sections= bot_get_sections();\n    // Fetch prefs value for bot_wtc_static_sections\n    $static_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n    // Creates an array of static sections from the string in txp_prefs\n    $static_sections = explode(\'|\', $static_sections);\n    $static_sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        // If section is in db mark as checked\n        $checked = in_array($key, $static_sections) ? \'1\': \'0\';\n        $static_sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'static_sections[]\', $key, $checked, 0, \'section_\'.$key).\n            n.tag($value, \'label\', array(\'for\' => \'section_\'.$key)).\n            n.\'</div>\';\n        $static_sections_rows .= $static_sections_row;\n    }\n    return $static_sections_rows;\n}\n\n\n\n//===========================================\n// Advanced multiple selection on bot_wtc tab\n\nfunction bot_advanced()\n{\n    global $bot_items;\n    // Get items array\n    $items = bot_all_items_selectinput();\n    $item_rows = \'\';\n    foreach ($items as $key => $value) {\n        $item_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'bot_adv_items[]\', $key, \'0\', 0, $value).\n            n.tag($value, \'label\', array(\'for\' => $value)).\n            n.\'</div>\';\n        $item_rows .= $item_row;\n        // Get sections array\n        $sections= bot_get_sections();\n    }\n    $sections_rows = \'\';\n    foreach ($sections as $key => $value) {\n        $sections_row = \'<div class=\"txp-form-checkbox\">\'.\n            n.checkbox(\'bot_adv_sections[]\', $key, \'0\', 0, $key).\n            n.tag($value, \'label\', array(\'for\' => $key)).\n            n.\'</div>\';\n        $sections_rows .= $sections_row;\n    }\n\n    return n.wrapRegion(\'bot_advanced\',\n            form(\n                bot_update_button(\'publish-advanced\').\n                n.\'<div class=\"txp-grid\">\'.\n                n.\'<div id=\"bot_adv_items\" class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_items\').\'</h4>\'.$item_rows.\'</div>\'.\n                  \'<div id=\"bot_adv_hide\"  class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_hide_in_section\').\'</h4>\'.$sections_rows.\'<p><a href=\"#\" class=\"bot_all\">\'.gTxt(\"all\").\'</a> | <a href=\"#\" class=\"bot_none\">\'.gTxt(\"none\").\'</a></p></div>\'.\n                  \'<div id=\"bot_adv_class\" class=\"txp-grid-cell\"><h4>\'.gTxt(\'bot_wtc_set_css_class\').\'</h4>\'.finput(\'text\', \'bot_adv_class\', \'\').\'</div>\'.\n                n.\'</div>\'\n            ),\n        \'bot_advanced-details\',\n        \'bot_wtc_advanced_multiple_group\',\n        \'bot_advanced_multiple\',\n        \'\',\n        \'\',\n        1\n    );\n\n}\n\n//===========================================\n\n\n\nfunction bot_wtc_tab($event, $step, $msg=\'\')\n{\n    global $bot_items;\n    $cfs = bot_get_cfs();\n\n    if ($step == \'update\') {\n        // Set function variables\n        $new_item = ps(\'new_item\'); //variable\n        $new_item_position = ps(\'new_item_position\'); //variable\n        $new_item_destination = ps(\'new_item_destination\'); //variable\n        $new_item_sections = ps(\'new_item_sections\'); //array\n        $new_item_class = ps(\'new_item_class\'); //variable\n        $bot_wtc_script = ps(\'bot_wtc_script\'); //variable\n        $static_sections = ps(\'static_sections\'); //variable\n        $item = ps(\'item\'); //array\n        $item_position = ps(\'item_position\'); //array\n        $item_destination = ps(\'item_destination\'); //array\n        $item_class = ps(\'item_class\'); //array\n        $bot_wtc_id = ps(\'bot_wtc_id\'); //array\n        $delete_id = ps(\'bot_delete_id\'); //array\n        $bot_adv_items = ps(\'bot_adv_items\'); //array\n        $bot_adv_sections = ps(\'bot_adv_sections\'); //array\n        $bot_adv_class = ps(\'bot_adv_class\'); //variable\n\n        // db update for existing items\n\n        // If at least a saved item exists\n        if ($item) {\n            // Array of values from the db\n            $db_values = bot_wtc_fetch_db();\n            for ($i = 0; $i < count($item); $i++) {\n                // Builds the posted variable name for current item sections\n                $item_posted_sections_name = \'bot_wtc_sections_for_id_\'.$db_values[$i][\'id\'];\n                $item_sections = isset($_POST[$item_posted_sections_name]) ? $_POST[$item_posted_sections_name] : \'\'; //array\n                // Builds sections string for current item\n                $item_sections_string = $item_sections ? implode(\'|\', $item_sections): \'\';\n                // Allowed input data combinations\n                if (($item[$i] && $item_destination[$i] && $item_position[$i])\n                || ($item[$i] && $item_class[$i] && !$item_destination[$i] && !$item_position[$i])\n                || ($item[$i] && $item_sections_string && !$item_destination[$i] && !$item_position[$i])) {\n                    // Check if a column/region is linked with a non-column item BUT ONLY IF both items are set (otherwise couldn\'t apply i.e. class to a single region)\n                    if (!((bot_wtc_is_region($item[$i]) xor bot_wtc_is_region($item_destination[$i])) && $item_destination[$i])) {\n                        // Check if item is different from destination\n                        if ($item[$i] != $item_destination[$i]) {\n                            safe_update(\n                                \"bot_wtc\",\n                                \"position = \'\".doslash($item_position[$i]).\"\',\n                                 destination = \'\".doslash($item_destination[$i]).\"\',\n                                 item = \'\".doslash($item[$i]).\"\',\n                                 sections = \'\".doslash($item_sections_string).\"\',\n                                 class = \'\".doslash($item_class[$i]).\"\'\",\n                                \"id = \'\".$bot_wtc_id[$i].\"\'\"\n                            );\n                        } else {\n                            $msg = array(gTxt(\'bot_wtc_same_item_warning\'), E_ERROR);\n                        }\n                    } else {\n                        $msg = array(gTxt(\'bot_wtc_region_warning\'), E_ERROR);\n                    }\n                } else {\n                    $msg = array(gTxt(\'bot_wtc_combo_warning\'), E_ERROR);\n                }\n            }\n        }\n\n        // db insert for new item\n\n        // Allowed input combinations\n        if (($new_item && $new_item_destination && $new_item_position)\n        || ($new_item && $new_item_class && !$new_item_destination && !$new_item_position)\n        || ($new_item && $new_item_sections && !$new_item_destination && !$new_item_position)) {\n            // Check if a column is linked with a non-column item\n            if (!((bot_wtc_is_region($new_item) xor bot_wtc_is_region($new_item_destination)) &&  $new_item_destination)) {\n                // Check items are not the same\n                if ($new_item != $new_item_destination) {\n                    // Transforms the sections array in a string\n                    $new_item_sections_string = $new_item_sections ? implode(\'|\', $new_item_sections) : \'\';\n                    safe_insert(\n                        \"bot_wtc\",\n                    \"position = \'\"\n                    .doslash($new_item_position)\n                    .\"\', destination = \'\"\n                    .doslash($new_item_destination)\n                    .\"\', class = \'\"\n                    .doslash($new_item_class)\n                    .\"\', sections = \'\"\n                    .doslash($new_item_sections_string)\n                    .\"\', item = \'\"\n                    .doslash($new_item)\n                    .\"\'\"\n                    );\n                } else {\n                    $msg = array(gTxt(\'bot_wtc_same_item_warning\'), E_ERROR);\n                }\n            } else {\n                $msg = array(gTxt(\'bot_wtc_td_warning\'), E_ERROR);\n            }\n        } elseif ($new_item || $new_item_destination || $new_item_position || $new_item_class || $new_item_sections) {\n            $msg = array(gTxt(\'bot_wtc_combo_warning\'), E_ERROR);\n        }\n\n        // Checks if there is something to delete\n        if ($delete_id) {\n            foreach ($delete_id as $id) {\n                safe_delete(\'bot_wtc\', \'id =\"\'.$id.\'\"\');\n            }\n        }\n\n        // Update advanced preferences\n\n        // Check if item AND section OR class is selected\n        if ($bot_adv_items and ($bot_adv_sections || $bot_adv_class)) {\n            // First array: all values from db\n            $db_values = bot_wtc_fetch_db();\n\n            if ($bot_adv_sections) {\n                // More specific array: only item => sections\n                $bot_db_sections = array();\n                for ($i =0; $i < count($db_values); $i++) {\n                    $bot_db_sections[$db_values[$i][\'item\']] = $db_values[$i][\'sections\'];\n                }\n\n                // Iterates posted items\n                foreach ($bot_adv_items as $item) {\n                    // Fetch any existing sections from db for current item and merges arrays eliminating duplicates\n                    if (is_array($bot_db_sections) and array_key_exists($item, $bot_db_sections)) {\n                        $db_sect_array = explode(\'|\', $bot_db_sections[$item]);\n                        $final_array = array_unique(array_merge($db_sect_array, $bot_adv_sections));\n                        // New sections string\n                        $bot_adv_sections_string = implode(\'|\', $final_array);\n                    } else {\n                        $bot_adv_sections_string = implode(\'|\', $bot_adv_sections);\n                    }\n                    safe_upsert(\n                        \"bot_wtc\",\n                        \"sections = \'\"\n                        .doslash($bot_adv_sections_string)\n                        .\"\'\",\n                        \"item = \'\".doslash($item).\"\'\"\n                    );\n                }\n            }\n\n            if ($bot_adv_class) {\n                // More specific array: only item => classes\n                $bot_db_classes = array();\n                for ($i =0; $i < count($db_values); $i++) {\n                    $bot_db_classes[$db_values[$i][\'item\']] = $db_values[$i][\'class\'];\n                }\n                // Iterates posted items\n                foreach ($bot_adv_items as $item) {\n                    // Fetch any existing class from db for current item and merges arrays eliminating duplicates\n                    if (is_array($bot_db_classes) and array_key_exists($item, $bot_db_classes)) {\n                        $db_class_array = explode(\' \', $bot_db_classes[$item]);\n                        $posted_class_array = explode(\' \', $bot_adv_class);\n                        $final_array = array_unique(array_merge($db_class_array, $posted_class_array));\n                        // New sections string\n                        $bot_adv_classes_string = implode(\' \', $final_array);\n                    } else {\n                        $bot_adv_classes_string = $bot_adv_class;\n                    }\n                    safe_upsert(\n                        \"bot_wtc\",\n                        \"class = \'\"\n                        .doslash($bot_adv_classes_string)\n                        .\"\'\",\n                        \"item = \'\".doslash($item).\"\'\"\n                    );\n                }\n            }\n        } elseif ($bot_adv_sections || $bot_adv_class) {\n            $msg = array(gTxt(\'bot_wtc_no_items_warning\'), E_ERROR);\n        }\n\n        // Updates static sections prefs (including when all deselected)\n        $db_static_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n        if ($static_sections || (empty($static_sections) && !empty($db_static_sections)) ) {\n            if ($static_sections) {\n                $static_sections_string = implode(\'|\', $static_sections);\n            }\n            safe_update(\'txp_prefs\', \'val= \"\'.doslash($static_sections_string).\'\", html=\"text_input\" \', \'name = \"bot_wtc_static_sections\"\');\n        }\n\n        // Updates script prefs\n        if ($bot_wtc_script) {\n            safe_update(\'txp_prefs\', \'val= \\\'\'.trim(doslash($bot_wtc_script)).\'\\\', html=\\\'textarea\\\' \', \'name = \\\'bot_wtc_script\\\'\');\n        }\n\n        // No error message? Then announce success\n        if (empty($msg)) {\n            $msg = gTxt(\'preferences_saved\');\n        }\n    }\n\n    // Top of page\n    pagetop(gTxt(\'bot_wtc_tab_name\'), $msg);\n\n    // Page heading and \'toggle advanced\'\n    echo tag_start(\'div\', array(\'class\' => \'txp-layout\')).\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                hed(gTxt(\'bot_wtc_tab_name\'), 1, array(\'class\' => \'txp-heading\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                tag_start(\'div\', array(\'class\' => \'txp-list-options\')).\n                    href(\'<span class=\"ui-icon ui-icon-gear\"></span>\'.gTxt(\'bot_wtc_advanced\'), \'#\', array(\'id\' => \'bot_advanced_open\')).\n                tag_end(\'div\').\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-1col\', \'id\' => $event.\'_container\'));\n\n    // What to show when accessing tab\n    if (bot_wtc_check_install()) {\n\n        // Fetch prefs value for bot_wtc_script\n        $bot_wtc_script = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_script\"\');\n\n        // Advanced / multiple panel\n        echo n.t.bot_advanced();\n\n        // Main div\n        echo n.t.\'<div id=\"bot_main\" class=\"txp-layout-1col\">\';\n\n        // \'Expand all\' and \'Collapse all\' controls\n        echo graf(\n            href(\'<span class=\"ui-icon ui-icon-arrowthickstop-1-s\"></span> \'.gTxt(\'expand_all\'), \'#\', array(\n                \'id\'            => \'bot_expand_all\',\n                \'class\'         => \'txp-expand-all\',\n                \'aria-controls\' => \'bot_main\',\n            )).\n            href(\'<span class=\"ui-icon ui-icon-arrowthickstop-1-n\"></span> \'.gTxt(\'collapse_all\'), \'#\', array(\n                \'id\'            => \'bot_collapse_all\',\n                \'class\'         => \'txp-collapse-all\',\n                \'aria-controls\' => \'bot_main\',\n            )), array(\'id\' => \'bot_controls\', \'class\' => \'txp-actions txp-list-options\')\n        );\n\n        // Table\n        echo form( // Begin form\n\n            tag_start(\'table\', array(\'id\' => \'bot_wtc_table\', \'class\' => \'txp-list--no-options\')).\n\n            n.tag_start(\'thead\').\n                tr(\n                    hCell(gTxt(\'bot_wtc_item\'), \'\', array(\'class\' => \'txp-list-col-item\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_position\'), \'\', array(\'class\' => \'txp-list-col-position\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_destination\'), \'\', array(\'class\' => \'txp-list-col-destination\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_hide\'), \'\', array(\'class\' => \'txp-list-col-hide\', \'scope\' => \'col\')).\n                    hCell(gTxt(\'bot_wtc_class\'), \'\', array(\'class\' => \'txp-list-col-class\', \'scope\' => \'col\')).\n                    hcell()\n                ).\n            n.tag_end(\'thead\').\n                bot_wtc_output_rows(). // HTML rows generated by \"bot_wtc_output_rows()\"\n            n.tag_end(\'table\').\n\n            bot_update_button().\n\n            n.wrapRegion(\'bot_static_sections\',\n                    bot_wtc_static_sections_select().\n                    bot_update_button(),\n                \'bot_static_sections-details\',\n                \'bot_wtc_static_sections_group\',\n                \'bot_static_sections\',\n                \'bot_collapse\').\n\n            n.wrapRegion(\'bot_js_box\',\n                    \'<div class=\"txp-actions\">\'.\n                    n.\'<a id=\"bot_js_link\" href=\"#\"><span class=\"ui-icon ui-icon-circlesmall-plus\"></span> \'.gTxt(\'bot_wtc_add_external_script\').\'</a>\'.\n                    n.\'<a id=\"bot_jq_link\" href=\"#\"><span class=\"ui-icon ui-icon-circlesmall-plus\"></span> \'.gTxt(\'bot_wtc_add_inline_code\').\'</a>\'.\n                    n.\'</div>\'.\n                    n.\'<textarea id=\"bot_wtc_script\" name=\"bot_wtc_script\" cols=\"60\" rows=\"10\">\'.$bot_wtc_script.\'</textarea>\'. // script textarea\n                    n.bot_update_button(),\n                \'bot_js_box-details\',\n                \'bot_wtc_add_javascript_group\',\n                \'bot_add_javascript\',\n                \'bot_collapse\').\n\n            n.tag_end(\'div\')\n\n        );  // End form\n    }\n\n    // Snippets to insert in the script box\n    $bot_jquery_snippet = \'<script>\\n    $(document).ready(function() {\\n        // your code here\\n    });\\n<\\/script>\\n\';\n    $bot_js_snippet = \'<script src=\\\"path_to_script\\\"><\\/script>\\n\';\n\n    // Add jquery action\n    echo\n    \'<script>\'.n.\n    \'    $(document).ready(function() {\'.n.\n            \'$(\"div.bot_collapse\").hide()\'.n.\n            \'$(\"section#bot_advanced\").hide()\'.n.\n            \'$(\"a.bot_push\").click(function(){\'.n.\n            \'  $(this).toggleClass(\"bot_arrow\").parent().next().slideToggle(\"fast\");\'.n.\n            \'  $(this).children(\".ui-icon\").toggleClass(\"ui-icon-caret-1-s\").toggleClass(\"ui-icon-caret-1-n\");\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"#bot_collapse_all\").click(function(){\'.n.\n            \'  $(\"div.bot_collapse\").slideUp(\"fast\").parent().find(\".ui-icon\").removeClass(\"ui-icon-caret-1-n\").addClass(\"ui-icon-caret-1-s\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"#bot_expand_all\").click(function(){\'.n.\n            \'  $(\"div.bot_collapse\").slideDown(\"fast\").parent().find(\".ui-icon\").removeClass(\"ui-icon-caret-1-s\").addClass(\"ui-icon-caret-1-n\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"#bot_advanced_open\").click(function(){\'.n.\n            \'  $(\"section#bot_advanced\").slideToggle(\"fast\");\'.n.\n            \'  $(\"div#bot_main\").toggle(\"fast\");\'.n.\n            \'  return false;\'.n.\n             \'});\'.n.\n            \'$(\"a.bot_all\").click(function(){\'.n.\n            \'  $(this).parent().parent().find(\"input\").prop(\"checked\", true);\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"a.bot_none\").click(function(){\'.n.\n            \'  $(this).parent().parent().find(\"input\").prop(\"checked\", false);\'.n.\n            \'  return false;\'.n.\n            \'});\'.n.\n            \'$(\"#bot_jq_link\").click(function(){\'.n.\n            \'  var areaValue = $(\"#bot_wtc_script\").val();\'.n.\n            \'  $(\"#bot_wtc_script\").val(areaValue + \"\'.$bot_jquery_snippet.\'\");\'.n.\n            \'  return(false);\'.n.\n            \'});\'.n.\n            \'$(\"#bot_js_link\").click(function(){\'.n.\n            \'  var areaValue = $(\"#bot_wtc_script\").val();\'.n.\n            \'  $(\"#bot_wtc_script\").val(areaValue + \"\'.$bot_js_snippet.\'\");\'.n.\n            \'  return(false);\'.n.\n            \'});\'.n.\n    \'    });\'.n.\n    \'</script>\';\n}\n\n\n\n// ===========================================================\n// plugins output\n// ===========================================================\n\n// CSS for the plugin tab under extensions\n\nfunction bot_wtc_css()\n{\n    global $event;\n    // Output css only in \'bot_wtc\' extensions tab.\n    if ($event != \'bot_wtc_tab\') {\n        return;\n    }\n\n    echo \'<style>\n\n    #bot_wtc_table {\n        padding: 10px 0 20px;\n        margin-left: 0;\n    }\n\n    #bot_wtc_table td {\n        vertical-align: top;\n        padding: 5px;\n        white-space: nowrap;\n    }\n\n    #bot_wtc_table td p {\n        margin: 3px 0 0 0;\n    }\n    #bot_wtc_table td:last-child .txp-form-checkbox {\n        line-height: 2.3;\n    }\n\n    #bot_wtc_table td select {\n        width: 100%;\n        background-color: transparent;\n    }\n\n    .txp-form-checkbox * {\n        cursor: pointer;\n    }\n\n    #bot_advanced {}\n\n    #bot_advanced .txp-grid-cell {\n        border: none;\n        margin-bottom: 20px;\n    }\n    input[name=\"bot_adv_class\"] {\n        width: 100%;\n    }\n\n    #bot_uninstall-details, #bot_static_sections-details, #bot_js_box-details {\n        padding: 0 20px;\n    }\n\n    #bot_uninstall-details {\n        padding-bottom: 20px;\n    }\n\n    #bot_wtc_script {\n        width: 100%;\n        border: dotted #ccc 1px;\n    }\n\n    .bot_update_button {\n        margin: 20px 0;\n        clear: both;\n    }\n\n    [type=\"submit\"].publish-advanced, a.publish-advanced, button.publish-advanced {\n        background-color: #ffd21b;\n        background-image: linear-gradient(#ffe168,#ffd21b);\n        border: 1px solid #f7c600;\n        font-weight: 700;\n        height: 2.76923076923077em;\n        padding: .61538461538462em 1em;\n    }\n\n    a.bot_push {\n        padding-right: 13px;\n        line-height: 1.85;\n    }\n\n    a.bot_push.bot_arrow span.ui-icon {\n        background-position: 0 0;\n    }\n    #bot_static_sections-details {\n        padding-top: 20px;\n    }\n    #bot_js_box-details .txp-actions {\n        padding-top: 5px;\n        padding-bottom: 10px;\n    }\n\n</style>\';\n}\n\n\n// ===========================================================\n// Builds array of sections to hide\n\nfunction bot_hide_per_section_array()\n{\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    for ($i =0; $i<count($db_values); $i++) {\n        if ($db_values[$i][\'sections\']) {\n            $sections_to_hide = explode(\'|\', $db_values[$i][\'sections\']);\n            foreach ($sections_to_hide as $section) {\n                $bot_hide_per_section[$section][] = $db_values[$i][\'item\'];\n            }\n        }\n    }\n\n    // Return array only if values exist\n    if (isset($bot_hide_per_section)) {\n        return $bot_hide_per_section;\n    }\n}\n\n\n\n// ===========================================================\n// js rows dealing with items to hide on section change AND on page load\n\nfunction bot_wtc_jquery_hide_sections_rows()\n{\n    $bot_hide_per_section = bot_hide_per_section_array();\n    foreach ($bot_hide_per_section as $section => $fields) {\n        echo n.\'            if (value==\"\'.$section.\'\"){\'.n;\n        for ($i =0; $i<count($fields); $i++) {\n            echo \'                \'.$fields[$i].\'.hide();\'.n;\n        }\n        echo \'            }\'.n;\n    }\n}\n\n\n\n// ===========================================================\n// js rows to restore every previously hidden item on section change\n\nfunction bot_wtc_jquery_restore_rows()\n{\n    $bot_hide_per_section = bot_hide_per_section_array();\n    foreach ($bot_hide_per_section as $section => $fields) {\n        for ($i =0; $i<count($fields); $i++) {\n            $out[] = $fields[$i];\n        }\n    }\n    $out = array_unique($out);\n    foreach ($out as $value) {\n        echo \'            \'.$value.\'.show();\'.n;\n    }\n}\n\n\n\n// ===========================================================\n//  Builds the script\n\nfunction bot_hide_per_section()\n{\n    global $event;\n    if ($event !== \'article\') {\n        return;\n    }\n\n    $bot_hide_per_section = bot_hide_per_section_array();\n    // Output js only if values exist\n    if ($bot_hide_per_section) {\n        echo\n                \'<script>\'.n.\n                \'    $(document).ready(function() {\'.n;\n        echo\n                \'        $(\"select#section\").change(function(){\'.n;\n        bot_wtc_jquery_restore_rows();\n        echo\n                \'            var value = $(\"select#section\").val();\';\n        bot_wtc_jquery_hide_sections_rows();\n        echo\n                \'        }).change();\'.n.\n                \'    });\'.n.\n                \'</script>\';\n    }\n}\n\n\n\n// ===========================================================\n// Invisible sections in section list\n\nfunction bot_hidden_sections()\n{\n    global $event;\n    if ($event !== \'article\') {\n        return;\n    }\n\n    // Fetch prefs value for bot_wtc_static_sections\n    $bot_hidden_sections = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_static_sections\"\');\n\n    // Output js only if values exist\n    if ($bot_hidden_sections) {\n        $sections = explode(\"|\", $bot_hidden_sections);\n        echo\n        \'<script>\'.n.\n        \'    $(document).ready(function() {\'.n;\n        foreach ($sections as $value) {\n            echo    \'           $(\"select#section option:not(:selected)[value=\\\'\'.$value.\'\\\']\").remove();\'.n;\n        }\n        echo\n        \'    });\'.n.\n        \'</script>\';\n    }\n}\n\n// ===========================================================\n// Helper function: retrieves parent key of an array item\n\nfunction search_key($needle, $haystack)\n{\n    foreach($haystack as $key => $val) {\n         if(in_array($needle, $val)) {\n            return $key;\n         }\n   }\n}\n\n// ===========================================================\n// js rows: reposition items and add classes to items\n\nfunction bot_wtc_jquery_rows()\n{\n    global $bot_items;\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $rows = \'\';\n    for ($i = 0; $i <count($db_values); $i++) {\n        $item = ($db_values[$i][\'item\'] != \'\') ? $db_values[$i][\'item\'] : \'\';\n        $position = ($db_values[$i][\'position\'] != \'\') ? \'.\'.$db_values[$i][\'position\'] : \'\';\n        $destination = ($db_values[$i][\'destination\'] != \'\') ? \'(\'.$db_values[$i][\'destination\'].\')\' : \'\';\n        $class = ($db_values[$i][\'class\'] != \'\') ? \'.addClass(\"\'.$db_values[$i][\'class\'].\'\")\' : \'\';\n        $row = $item.$position.$destination.$class.\';\'.n;\n        $rows .= $row;\n    }\n    return $rows;\n};\n\n\n// ===========================================================\n// js rows: Clean up all elements outside their original containers (e.g. after async save)\n// requires $bot_arr_item_groups array\n\nfunction bot_wtc_jquery_cleanrefresh_rows()\n{\n    global $bot_items, $bot_arr_item_groups;\n    // Array of values from the db\n    $db_values = bot_wtc_fetch_db();\n\n    $rows = \'\';\n    for ($i = 0; $i <count($db_values); $i++) {\n\n        $item = ($db_values[$i][\'item\'] != \'\') ? $db_values[$i][\'item\'] : \'\';\n        $item_selector = substr($item, 3, -2); // Remove \'$(\"\' and \'\")\'\n        // Find item\'s home container\n        $item_home = search_key($item_selector, $bot_arr_item_groups);\n\n        if (!empty($item_home)) {\n            $row = $item.\'.not(\"\'.$item_home.\' \'.$item_selector.\'\").remove();\'.n;\n            $rows .= $row;\n        }\n    }\n    return $rows;\n};\n\n\n\n// ===========================================================\n// Output customize and async refresh javascript\n\nfunction bot_wtc()\n{\n    // Fetch prefs value for bot_wtc_script\n    $bot_wtc_script = safe_field(\'val\', \'txp_prefs\', \'name = \"bot_wtc_script\"\');\n    // Fetch \'position\' from db to check if a move is saved\n    $position = safe_column(\'position\', \'bot_wtc\', \'1=1\');\n    // Fetch \'class\' from db to check if a class is saved\n    $class = safe_column(\'class\', \'bot_wtc\', \'1=1\');\n\n    // Output code only if a preference is saved\n    if (isset($position) || isset($class)) {\n        echo\n        \'<script>\'.n.\n        \'    $(document).ready(function() {\'.n.\n        // Call cleanUp function on successful async save\n        \'       textpattern.Relay.register(\"txpAsyncForm.success\", botWtcDoRefresh);\'.n.\n\n        // CUSTOMIZE function\n        \'       function botWtcDoCustomize() {\'.n.\n                    // Position, class and hide rules\n                    bot_wtc_jquery_rows().n.\n        \'       }\'.n.\n                // Run once after page load\n        \'       botWtcDoCustomize();\'.n.\n\n        // CLEANUP function (called after async save)\n        \'       function botWtcDoRefresh() {\'.n.\n                    // Clean up all elements outside their original containers\n                    bot_wtc_jquery_cleanrefresh_rows().n.\n                    // Clean up all duplicate custom fields outside the custom fields group\n        \'           $(\"#txp-custom-field-group-content\").find(\".custom-field\").each(function(){\n                        var pattern = /\\bcustom-[0-9]+\\b/;\n                        var matchResult = $(this).attr(\"class\").match(pattern);\n                        if (matchResult && $(\".\"+matchResult[0]).length > 1) {\n                            $this= $(this);\n                            $(\".\"+matchResult[0]).not($this).remove();\n                        }\n                    })\'.n.\n                    // (Re-)perform write tab customize\n        \'           botWtcDoCustomize();\'.n.\n                    // Get selected section and use it to effect section change\n                    // to trigger hide by section rules\n        \'           var value = $(\"select#section\").val();\'.n.\n        \'           $(\"select#section\").val(value).change();\'.n.\n        \'       }\'.n.\n        \'    });\'.n.\n        \'</script>\';\n    }\n    if ($bot_wtc_script) {\n        echo n.$bot_wtc_script.n;\n    };\n}\n', 'f1e1466c307f73d50c89f098dec7ea18', '#@admin\r\n#@language de, de-de\r\narticle_image_group => Artikelbild-Gruppe\r\nwritetab_main_content => Hauptspalte\r\nwritetab_sidebar => Seitenspalte\r\nwritetab_view_modes => Modus-Umschaltung anzeigen\r\nwritetab_article_actions => Artikelaktionen\r\nbot_wtc_tab_name => Write Tab Customize\r\nbot_wtc_advanced => Erweiterte Auswahl ein/aus\r\nbot_wtc_item => Element\r\nbot_wtc_position => Anordnung\r\nbot_wtc_destination => Zielelement\r\nbot_wtc_hide => Verbergen in\r\nbot_wtc_class => CSS-Klasse\r\nbot_wtc_before => vor\r\nbot_wtc_after => nach\r\nbot_wtc_section_list => Sektionen\r\nbot_wtc_update_button => Aktualisieren\r\nbot_wtc_advanced_multiple_group => Erweiterte Auswahl / Mehrfachauswahlen\r\nbot_wtc_items => Elemente\r\nbot_wtc_hide_in_section => In diesen Sektionen verbergen\r\nbot_wtc_set_css_class => CSS-Klasse festlegen\r\nbot_wtc_static_sections_group => Ausgewählte Sektionen nicht im Sektionsmenü anzeigen\r\nbot_wtc_add_javascript_group => Benutzerdefiniertes JavaScript hinzufügen\r\nbot_wtc_add_external_script => Externes Script einbinden\r\nbot_wtc_add_inline_code => jQuery-Code hinzufügen\r\nbot_wtc_region_warning => Hinweis: Ganze Seitenregionen (Haupt-/Nebenspalte) können nicht relativ zu einzelnen Elementen positioniert werden (und umgekehrt)\r\nbot_wtc_same_item_warning => Hinweis: Ein Element kann nicht relativ zu sich selbst positioniert werden.\r\nbot_wtc_combo_warning => Hinweis: Die eingefügte Regel war nicht vollständig.\r\nbot_wtc_no_items_warning => Hinweis: Es muss wenigstens ein Element ausgewählt sein.\r\n\n#@admin\n#@language en, en-gb, en-us\narticle_image_group => Article image group\nwritetab_main_content => Main content\nwritetab_sidebar => Sidebar\nwritetab_view_modes => View mode switcher\nwritetab_article_actions => Article actions\nbot_wtc_tab_name => Write Tab Customize\nbot_wtc_advanced => Toggle advanced\nbot_wtc_item => Item\nbot_wtc_position => Position\nbot_wtc_destination => Destination\nbot_wtc_hide => Hide in\nbot_wtc_class => Class\nbot_wtc_before => Before\nbot_wtc_after => After\nbot_wtc_section_list => Section list\nbot_wtc_update_button => Update\nbot_wtc_advanced_multiple_group => Advanced / Multiple selection\nbot_wtc_items => Items\nbot_wtc_hide_in_section => Hide in section\nbot_wtc_set_css_class => Set CSS class\nbot_wtc_static_sections_group => Hide sections in sections dropdown\nbot_wtc_add_javascript_group => Add javascript to write panel\nbot_wtc_add_external_script => Add external script\nbot_wtc_add_inline_code => Add inline jQuery code\nbot_wtc_region_warning => Entire column or sidebar regions cannot be moved relative to single items and vice-versa\nbot_wtc_same_item_warning => Oops! You are trying to move an item relative to itself\nbot_wtc_combo_warning => Oops! You tried to insert an incomplete rule\nbot_wtc_no_items_warning => Warning: at least an item must be selected\n\n#@admin\n#@language fr, fr-fr\narticle_image_group => Groupe d´images d´article\nwritetab_main_content => Contenu principal\nwritetab_sidebar => Colonne latérale\nwritetab_view_modes => Commutateur du mode d´affichage\nwritetab_article_actions => Actions sur les articles\nbot_wtc_tab_name => Personnalisation de la page «Rédiger»\nbot_wtc_advanced => Basculer les options avancées\nbot_wtc_item => Élément\nbot_wtc_position => Position\nbot_wtc_destination => Destination\nbot_wtc_hide => Masqué dans\nbot_wtc_class => Classe\nbot_wtc_before => Avant\nbot_wtc_after => Après\nbot_wtc_section_list => Liste des sections\nbot_wtc_update_button => Actualiser\nbot_wtc_advanced_multiple_group => Paramètres Avancés / Sélection Multiple\nbot_wtc_items => Éléments\nbot_wtc_hide_in_section => Masquer dans la section\nbot_wtc_set_css_class => Déclarer une classe CSS\nbot_wtc_static_sections_group => Masquer les sections dans la liste déroulante «Section»\nbot_wtc_add_javascript_group => Ajouter du javascript à la page «Rédiger»\nbot_wtc_add_external_script => Ajouter un script externe\nbot_wtc_add_inline_code => Ajouter du code jQuery incorporé\nbot_wtc_region_warning => La colonne entière ou des zones de la colonne latérale ne peuvent être déplacées relativement à un élément individuel et vice-versa.\nbot_wtc_same_item_warning => Oups. Vous cherchez à déplacer un élément par rapport à lui-même !\nbot_wtc_combo_warning => Oups. Vous cherchez à inclure une règle incomplète !\nbot_wtc_no_items_warning => Attention : au moins un élément doit être sélectionné !\n', '', 3, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_admin_tooltips', 0, 'redbot', 'http://www.redbot.it/txp', '0.4', 'Create tooltips in admin interface.', '<h2>bot_admin_tooltips</h2>\n<p>This plugin allows to insert tooltips in txp admin using Craig Thompson\'s jquery plugin <a href=\"http://craigsworks.com/projects/qtip/\">qtips</a>.  It can be useful to show customized instructions for your clients.</p>\n<h3>Features</h3>\n<ul>\n  <li>Generated tooltips can be customized in background, border and text color. </li>\n  <li>By default standard txp &quot;pophelps&quot; are hidden but can cohexist with the plugin </li>\n  <li>Tooltips can be activated on click (default) or hover.</li>\n  <li>Tooltips can be associated with almost every page element under the &quot;content&quot; tab </li>\n  <li>Tooltips accept html and recognize line breaks (for  faster text insertion) </li>\n  <li>Tooltips anchors have a class so can be customized via css </li>\n  <li>glz_custom_fields compatible </li>\n</ul>\n<h3>Instructions</h3>\n<p>Once inatalled and activated visit: &quot;extensions&quot; &gt; &quot;bot_tips&quot;. Here you can set preferences, enter tooltips text and associate it with a specific item. </p>\n<h3>Preferences:</h3>\n<p><strong>Background color:</strong><br />\nSets the tooltip background (default #fdfdfd) </p>\n<p>\n  <strong>Text color:</strong>  <br />\nSets the tooltip color (default #454545) </p>\n<p>\n  <strong>Border color:  </strong><br />\nSets the tooltip border color(default #e0e0e0) </p>\n<p>\n  <strong>Show tip on hover: </strong><br />\ndefault is on click </p>\n<p>\n<strong>Hide txp pophelps: </strong><br />\nHides all native txp \'pophelps\' </p>\n<p>\n  <strong>Path to js:</strong>\n  <br />\n  Path to  qtips script. Defaults to \'../js/\' </p>\n<h3>Notes</h3>\n<ol>\n  <li> This plugin has been tested to work without conflicts with many other plugins but seems to have a slight incompatibility with hak_titnymce.<br />\n    If you set &quot;Show editor toggle:&quot; to &quot;yes&quot; in its preferences then  tooltips won\'t show  up  under the &quot;advanced&quot; section of the &quot;write&quot; tab - all the rest will work ok though.</li>\n  <li>If &quot;Hide txp pophelps&quot; is ticked standard Txp pophelps are hidden for all subtabs under the &quot;content&quot; tab and for the &quot;users&quot; subtab.<br />\n    This will suffice most of the times but if you want to hide <strong>every</strong> native pophelp you can simply add this rule  at the end of your  textpattern.css file, which - depending on your txp version - can be found in /textpattern (pre 4.2) or in /textpattern/theme/yourtheme (v 4.2): <br />\n    <code>a.pophelp {display:none;}</code></li>\n</ol>', '//<?php\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'bot_admin_tooltips_tab\', \'1,2\');\r\n	register_tab(\'extensions\', \'bot_admin_tooltips_tab\', \'bot_admin_tooltips\');\r\n	register_callback(\'bot_admin_tooltips_tab\', \'bot_admin_tooltips_tab\');\r\n	register_callback(\'bot_admin_tooltips\', \'article\');\r\n	register_callback(\'bot_admin_tooltips\', \'category\');\r\n	register_callback(\'bot_admin_tooltips\', \'list\');\r\n	register_callback(\'bot_admin_tooltips\', \'image\');\r\n	register_callback(\'bot_admin_tooltips\', \'file\');\r\n	register_callback(\'bot_admin_tooltips\', \'link\');\r\n	register_callback(\'bot_admin_tooltips\', \'discuss\');\r\n	register_callback(\'bot_admin_tooltips\', \'admin\');\r\n} \r\n\r\nglobal $bot_admin_tooltips, $bot_admin_tooltips_main_array; //set globals\r\n\r\nif (getThings(\"Show tables like \'\".PFX.\"bot_admin_tooltips\'\")) { // if plugin is installed loads items and tips from db\r\n	$r = safe_rows_start(\'item, tip\', \'bot_admin_tooltips\',\'1=1\');\r\n	if ($r) {\r\n		$bot_admin_tooltips = array();\r\n		while ($a = nextRow($r)) {\r\n			$bot_admin_tooltips[$a[\'item\']] = $a[\'tip\'];\r\n		}   \r\n	}\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n \r\nfunction bot_in_main_array($item, $page, $hook, $type) { // helps build the main array\r\n	global $bot_admin_tooltips_main_array;\r\n	$bot_admin_tooltips_main_array[$item][\'page\'] = $page;\r\n	$bot_admin_tooltips_main_array[$item][\'hook\'] = $hook; \r\n	$bot_admin_tooltips_main_array[$item][\'type\'] = $type; \r\n	return $bot_admin_tooltips_main_array;\r\n}\r\n\r\nbot_in_main_array(\'tab_write|advanced_options\',\'article\',\'advanced\',\'link\');\r\nbot_in_main_array(\'tab_write|override_default_form\',\'article\',\'override-form\',\'label\');\r\nbot_in_main_array(\'tab_write|keywords\',\'article\',\'keywords\',\'label\');\r\nbot_in_main_array(\'tab_write|article_image\',\'article\',\'article-image\',\'label\');\r\nbot_in_main_array(\'tab_write|URL-only title\',\'article\',\'url-title\',\'label\');\r\nbot_in_main_array(\'tab_write|recent_articles\',\'article\',\'recent\',\'link\');\r\nbot_in_main_array(\'tab_write|title\',\'article\',\'title\',\'label\');\r\nbot_in_main_array(\'tab_write|body\',\'article\',\'body\',\'label\');\r\nbot_in_main_array(\'tab_write|excerpt\',\'article\',\'excerpt\',\'label\');\r\nbot_in_main_array(\'tab_write|status\',\'article\',\'write-status\',\'legend\');\r\nbot_in_main_array(\'tab_write|sort_display\',\'article\',\'write-sort\',\'legend\');\r\nbot_in_main_array(\'tab_write|category1\',\'article\',\'category-1\',\'label\');\r\nbot_in_main_array(\'tab_write|category2\',\'article\',\'category-2\',\'label\');\r\nbot_in_main_array(\'tab_write|section\',\'article\',\'section\',\'label\');\r\nbot_in_main_array(\'tab_write|more\',\'article\',\'more\',\'link\');\r\nbot_in_main_array(\'tab_write|timestamp\',\'article\',\'write-timestamp\',\'legend\');\r\nbot_in_main_array(\'tab_write|expires\',\'article\',\'write-expires\',\'legend\');\r\nbot_in_main_array(\'tab_organise|article_head\',\'category\',\'article_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|link_head\',\'category\',\'link_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|image_head\',\'category\',\'image_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|file_head\',\'category\',\'file_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|name\',\'category\',\'name\',\'td\');\r\n// bot_in_main_array(\'tab_organise|title\',\'category\',\'title\',\'td\');\r\nbot_in_main_array(\'tab_organise|parent\',\'category\',\'parent\',\'td\');\r\nbot_in_main_array(\'tab_list|search\',\'list\',\'list-search\',\'label\');\r\nbot_in_main_array(\'tab_list|with_selected\',\'list\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_image|upload_image\',\'image\',\'image-upload\',\'label\');\r\nbot_in_main_array(\'tab_image|search\',\'image\',\'image-search\',\'label\');\r\nbot_in_main_array(\'tab_image|with_selected\',\'image\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_image|replace_image\',\'image\',\'image-replace\',\'label\');\r\nbot_in_main_array(\'tab_image|upload_thumbnail\',\'image\',\'upload-thumbnail\',\'label\');\r\nbot_in_main_array(\'tab_image|image_category\',\'image\',\'image-category\',\'label\');\r\nbot_in_main_array(\'tab_image|alt_text\',\'image\',\'alt-text\',\'label\');\r\nbot_in_main_array(\'tab_image|caption\',\'image\',\'caption\',\'label\');\r\nbot_in_main_array(\'tab_file|upload_file\',\'file\',\'file-upload\',\'label\');\r\nbot_in_main_array(\'tab_file|search\',\'file\',\'file-search\',\'label\');\r\nbot_in_main_array(\'tab_file|with_selected\',\'file\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_file|category\',\'file\',\'category\',\'p\');\r\nbot_in_main_array(\'tab_file|description\',\'file\',\'description\',\'p\');\r\nbot_in_main_array(\'tab_file|file_status\',\'file\',\'file-status\',\'legend\');\r\nbot_in_main_array(\'tab_file|timestamp\',\'file\',\'file-created\',\'legend\');\r\nbot_in_main_array(\'tab_link|title\',\'link\',\'link-title\',\'label\');\r\nbot_in_main_array(\'tab_link|sort_value\',\'link\',\'link-sort\',\'label\');\r\nbot_in_main_array(\'tab_link|url\',\'link\',\'link-url\',\'label\');\r\nbot_in_main_array(\'tab_link|category\',\'link\',\'link-category\',\'label\');\r\nbot_in_main_array(\'tab_link|description\',\'link\',\'link-description\',\'label\');\r\nbot_in_main_array(\'tab_link|search\',\'link\',\'link-search\',\'label\');\r\nbot_in_main_array(\'tab_link|with_selected\',\'link\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_comments|search\',\'discuss\',\'discuss-search\',\'label\');\r\nbot_in_main_array(\'tab_comments|with_selected\',\'discuss\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_admin|login\',\'admin\',\'name\',\'td\');\r\nbot_in_main_array(\'tab_admin|real_name\',\'admin\',\'RealName\',\'td\');\r\nbot_in_main_array(\'tab_admin|email\',\'admin\',\'email\',\'td\');\r\nbot_in_main_array(\'tab_admin|privileges\',\'admin\',\'privs\',\'td\');\r\nbot_in_main_array(\'tab_admin|with_selected\',\'admin\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_admin|new_password\',\'admin\',\'new_pass\',\'label\');\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n\r\n// when first login onto txp, event is not specified. So if txp version < 4.08 $event = \'article\'\r\n// if txp version > 4.08 $event = \'$default_event\' in $prefs (cos 4.2 allows to choose default event)\r\n\r\nfunction bot_check_event() {\r\n    global $event, $prefs;\r\n   	if (gps(\'event\')) {\r\n    	$event = gps(\'event\');\r\n    }\r\n    else {\r\n        $event=(($prefs[\'version\']) < \'4.2.0\') ? \'article\' : $prefs[\'default_event\'];\r\n    }\r\n    return $event;\r\n} \r\n\r\n\r\n\r\n\r\n\r\n// ===========================================================\r\n//	jquery\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_js_rows() {\r\n	//set function variables\r\n	global $bot_admin_tooltips_per_page, $bot_admin_tooltips_main_array, $plugins;\r\n	extract(bot_admin_tooltips_get_prefs());\r\n	$bot_admin_tooltips_js_show_event = $bot_admin_tooltips_js_event ? \'mouseover\' : \'click\'; // set the \'$bot_admin_tooltips_js_event\' variable depending on the posted values\r\n	$bot_admin_tooltips_js_hide_event = $bot_admin_tooltips_js_event ? \'mouseout\' : \'unfocus\'; \r\n	if (in_array(\'glz_custom_fields\', $plugins)) {\r\n	$bot_glz_version = safe_field(\"version\",\"txp_plugin\",\"name=\'glz_custom_fields\'\"); //  fetch glz version, if installed\r\n	$bot_glz_version = str_replace(\".\", \"\", $bot_glz_version);\r\n	};\r\n\r\n    // when first login onto txp event is not specified. So if txp version < 4.08 $event = \'article\'\r\n	// if txp version > 4.08 $event =  $default_event in $prefs (4.2 allows to choose default event)\r\n	$event = bot_check_event();\r\n	\r\n	foreach ($bot_admin_tooltips_per_page as $item => $tip) {\r\n		if (strpos($item, \'custom_\') !== false) { // if is a custom field use this values\r\n			$hook = substr($item, strpos($item, \'custom_\')); // gets the hook: removes the  part before \"|\"\r\n			$hook = in_array(\'glz_custom_fields\', $plugins) && $bot_glz_version < 122 ? $hook : str_replace(\'custom_\', \'custom-\', $hook); // if glz_custom_fields is NOT installed  OR is varsion 1.2.2. replaces \"_\" with \"-\"\r\n			$page = \'article\';\r\n			$type = \'label\';\r\n			$position = \'topRight\';\r\n		}\r\n		else {\r\n			$hook = $bot_admin_tooltips_main_array[$item][\'hook\'];\r\n			$page = $bot_admin_tooltips_main_array[$item][\'page\'];\r\n			$type = $bot_admin_tooltips_main_array[$item][\'type\'];\r\n		}; \r\n		$tip = addslashes($tip);\r\n		switch($event){\r\n			case \'article\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\"; \r\n					break;\r\n					case \'legend\':\r\n						$selector = \"$(\\\"fieldset#\".$hook.\" legend\\\")\";\r\n					break;\r\n					case \'link\':\r\n						$selector = \"$(\\\"a[href*=\'\".$hook.\"\']\\\")\"; // for \"more\" and \"recent articles\"\r\n					break;\r\n				}\r\n			break;				\r\n			case \'category\':\r\n				switch($type){\r\n					case \'h3\':\r\n						$selector = \"$(\\\"h3:has(a[href*=\'\".$hook.\"\'])\\\")\"; \r\n					break;\r\n					case \'td\': // for category \"title\", \"parent\" etc.\r\n						$selector = \"$(\\\"td:has(*[name=\'\".$hook.\"\'])\\\").prev()\"; \r\n					break;\r\n				}\r\n			break;\r\n			case \'list\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'image\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'file\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n					break;\r\n					case \'legend\':\r\n						$selector = \"$(\\\"fieldset#\".$hook.\" legend\\\")\";\r\n					break;	\r\n					case \'p\': // for file \"description\", \"category\" \r\n						$selector = \"$(\\\"p:has(*[name=\'\".$hook.\"\'])\\\")\";\r\n					break;\r\n				}\r\n			break;\r\n			case \'link\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;	\r\n			case \'discuss\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'admin\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n					break;	\r\n					case \'td\': // for category \"Privileges\", \"Login\" etc.\r\n						$selector = \"$(\\\"td:has(*[name=\'\".$hook.\"\'])\\\").prev()\";  \r\n					break;\r\n				}\r\n			break;	\r\n		}\r\n		echo \r\n			$selector.\'.append(\"<span class=\\\"bot_tip\\\" style=\\\"cursor:pointer;\\\">&nbsp;[ ? ]&nbsp;</span>\");\'.n. // append question mark\r\n			$selector.\'.qtip({  \r\n				content: \"\'.preg_replace(\"/\\r?\\n/\", \"<br />\", $tip).\'\", \r\n				show: { when: {event: \"\'.$bot_admin_tooltips_js_show_event.\'\", target: \'.$selector.\'.children(\"span.bot_tip\")}, effect: { type: \"fade\", length: 0}, solo: true, delay: 0},\r\n				hide: { when: {event: \"\'.$bot_admin_tooltips_js_hide_event.\'\", target: \'.$selector.\'.children(\"span.bot_tip\")}, effect: { type: \"fade\", length: 200}, fixed: true},\r\n				style:{border: {width: 6, radius: 8, color: \"\'.$bot_admin_tooltips_border_color.\'\"}, width: 200, background: \"\'.$bot_admin_tooltips_bg.\'\", color: \"\'.$bot_admin_tooltips_color.\'\", tip: \"bottomLeft\"},\r\n				position: {target: \'.$selector.\'.children(\"span.bot_tip\"), corner: {target: \"topRight\", tooltip: \"bottomLeft\"}, adjust: { screen: true }}\r\n			});	\';	\r\n		\r\n	};	\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips(){\r\n\r\n	global $bot_admin_tooltips, $bot_admin_tooltips_main_array;\r\n	global $bot_admin_tooltips_per_page; // tips for the actual page (event)\r\n	extract(bot_admin_tooltips_get_prefs());\r\n	\r\n	    // when first login onto txp event is not specified. So if txp version < 4.08 $event = \'article\'\r\n	// if txp version > 4.08 $event =  $default_event in $prefs (4.2 allows to choose default event)\r\n	$event = bot_check_event();\r\n\r\n	foreach ($bot_admin_tooltips as $item => $tip) { // builds the \"tips per page\" array\r\n        if ((strpos($item, \'custom_\') !== false) && $event ==\"article\") { // if is a cf and event is \"article\"\r\n			$bot_admin_tooltips_per_page[$item] = $tip; \r\n		}\r\n		elseif(strpos($item, \'custom_\') !== false){\r\n        // do nothing\r\n		}\r\n		elseif ($bot_admin_tooltips_main_array[$item][\'page\'] == $event) {\r\n			$bot_admin_tooltips_per_page[$item] = $tip; \r\n		};\r\n	}\r\n	\r\n	if ($bot_admin_tooltips_per_page) { // prints js code only if there are tips\r\n		echo\r\n				\'<script type=\"text/javascript\" src= \"\'.$bot_admin_tooltips_path.\'jquery.qtip-1.0.0-rc3.min.js\"></script>\'.n.\r\n				\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n				\'/* <![CDATA[ */\'.n.\r\n				\'	$(document).ready(function() {\';\r\n				bot_admin_tooltips_js_rows();\r\n		echo\r\n				\'	});\'.n.\r\n				\'/* ]]> */\'.n.\r\n				\'</script>\';\r\n	}\r\n	if ($bot_admin_tooltips_hide_pophelp == \'1\') {\r\n		echo		\r\n				\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n				\'/* <![CDATA[ */\'.n.\r\n				\'	$(document).ready(function() {\'.n.\r\n				\'	$(\"a.pophelp\").hide()\'.n.\r\n				\'	});\'.n.\r\n				\'/* ]]> */\'.n.\r\n				\'</script>\';		\r\n	}\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n//	Interface and db\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_gTxt($what) {\r\n\r\n	global $language;\r\n	\r\n	$en_us = array(\r\n		\'install_message\' => \'bot_admin_tooltips is not yet properly initialized.  Use the button below to create the preferences table.\',\r\n		\'upgrade_message\' => \'bot_admin_tooltips must be upgraded. Use the button below to add the new fields to the preferences table.\',\r\n		\'uninstall\' => \'Uninstall\',\r\n		\'uninstall_message\' => \'Using the button below will remove the bot_admin_tooltips preferences table (and all other entries made by the plugin).\',\r\n		\'uninstall_confirm\' => \'Are you sure you want to delete the preferences table?\',\r\n		);\r\n	\r\n	$lang = array(\r\n		\'en-us\' => $en_us\r\n		);\r\n		\r\n		$language = (isset($lang[$language])) ? $language : \'en-us\';\r\n		$msg = (isset($lang[$language][$what])) ? $lang[$language][$what] : $what;\r\n		return $msg;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_check_install() {\r\n\r\n	// Check if the bot_admin_tooltips table already exists\r\n	if (getThings(\"Show tables like \'\".PFX.\"bot_admin_tooltips\'\")) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//=========================================== \r\n\r\n\r\n\r\nfunction bot_admin_tooltips_install() {	\r\n	\r\n	// figure out what MySQL version we are using (from _update.php)\r\n	$mysqlversion = mysql_get_server_info();\r\n	$tabletype = (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.(0\\.[2-9]|(1[89]))|(1\\.[2-9])#\',$mysqlversion)) \r\n		? \" ENGINE=MyISAM \"\r\n		: \" TYPE=MyISAM \";\r\n	if (isset($txpcfg[\'dbcharset\']) && (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.[1-9]#\',$mysqlversion))) {\r\n		$tabletype .= \" CHARACTER SET = \". $txpcfg[\'dbcharset\'] .\" \";\r\n	}\r\n	\r\n	// Create the bot_admin_tooltips table\r\n	$bot_admin_tooltips_prefs_table = safe_query(\"CREATE TABLE `\".PFX.\"bot_admin_tooltips` (\r\n		`id` INT NOT NULL AUTO_INCREMENT , \r\n		`item` VARCHAR(255) NOT NULL, \r\n		`tip` TEXT NOT NULL,\r\n		PRIMARY KEY (`id`)\r\n		) $tabletype\");\r\n	\r\n	set_pref (\'bot_admin_tooltips_hide_pophelp\',\'1\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_js_event\',\'0\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_bg\',\'#fdfdfd\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_color\',\'#454545\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_border_color\',\'#e0e0e0\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_path\',\'../js/\', \'bot_tips_\',\'2\');\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_get_tips() { // creates an array of values extracted from the db \r\n\r\n	$r = safe_rows_start(\'id, item, tip\', \'bot_admin_tooltips\',\'1=1\');\r\n	if ($r) {\r\n		$out = array();\r\n		while ($a = nextRow($r)) {\r\n			$out[$a[\'item\']][\'item\'] = $a[\'item\'];\r\n			$out[$a[\'item\']][\'id\'] = $a[\'id\'];\r\n			$out[$a[\'item\']][\'tip\'] = $a[\'tip\'];\r\n		}\r\n		asort($out);\r\n		return $out;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_get_prefs() { // creates an array of values extracted from the prefs in db \r\n	\r\n	$r = safe_rows_start(\'name, val\', \'txp_prefs\', \'event = \"bot_tips_\"\');\r\n	if ($r) {\r\n		$public_prefs = array();\r\n		while ($a = nextRow($r)) {\r\n			$public_prefs[$a[\'name\']] = $a[\'val\'];\r\n		}\r\n		return $public_prefs;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction selectInput_values ($current_item = \'\') { // generates context-sensitive array for selectInput values\r\n	global $bot_admin_tooltips_main_array, $arr_custom_fields;\r\n	$prefs = bot_get_tips(); // values from the db \r\n    if ($arr_custom_fields) {\r\n        $bot_admin_tooltips_main_array_with_cf = array_merge($bot_admin_tooltips_main_array, $arr_custom_fields); // mega array with existing cfs\r\n    }\r\n    else {\r\n        $bot_admin_tooltips_main_array_with_cf = $bot_admin_tooltips_main_array; // if no cfs are set - var name should be changed but I\'m lazy\r\n    }\r\n    $values_for_selectInput = array(); // creates a new array\r\n	foreach ($bot_admin_tooltips_main_array_with_cf as $item => $values) { // filters values for txp selectInput function (behaves differently if cf or not)...\r\n		if (!array_key_exists($item, $prefs) || $item == $current_item) { // ... insert in  \"values_for_selectInput\" only if not yet used or value is given as parameter \"$current_item\" \r\n			if (strpos($item, \'custom_\') !== false) { // if $item IS a cf insert in this specific format...\r\n				$values_for_selectInput[$item] = gTxt(\'tab_write\').\'|\'.$values; // value is modified to translate and to allow alphabetical ordering\r\n			}\r\n			else { // ...if is not a cf insert in this format instead\r\n				$split_items = explode(\'|\', $item); // begin translation process > split item name\r\n				for ($i = 0; $i < count($split_items); $i++) {\r\n				$translated_value[] = gTxt($split_items[$i]); // translates each word and add to an array	\r\n				}			\r\n				$joint_items = implode(\'|\', $translated_value); // joins together single words\r\n				unset($translated_value); // so every time is empty\r\n				$values_for_selectInput[$item] = $joint_items; // key => ugly name; $value => nice name\r\n			}\r\n		}\r\n	}\r\n	asort($values_for_selectInput);\r\n	return $values_for_selectInput;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_output_rows(){ // outputs the rows for the html table in the bot_admin_tooltips_tab\r\n\r\n	global $bot_admin_tooltips_main_array;\r\n	$prefs = bot_get_tips(); // array of values from the db \r\n	\r\n	$rows = \"\";\r\n	$values_for_selectInput = selectInput_values();\r\n	\r\n	if ($values_for_selectInput){ // if there are free values left for select input...\r\n		$input_row = tr(td(selectInput(\'bot_new_item\', $values_for_selectInput, \'\', \'1\' )).td(\'<textarea name=\"bot_new_tip\" cols=\"40\" rows=\"5\" style=\"width:300px; height:70px\"></textarea>\').td()); // ...outputs the \'new tip\' input\r\n		$rows .= $input_row;\r\n	}\r\n	foreach ($prefs as $item => $values) {	// rows for values coming from the db\r\n		$tip = $values[\'tip\'];\r\n		$id = $values[\'id\'];\r\n		$values_for_selectInput = selectInput_values($item);\r\n		$single_row = tr(td(selectInput(\'bot_item[]\', $values_for_selectInput, $item))\r\n						.td(\'<textarea name=\"bot_saved_tip[]\" cols=\"40\" rows=\"5\" style=\"width:300px; height:70px\">\'.$tip.\'</textarea>\')\r\n						.td(\'<label for=\"bot_delete_id\">Delete</label>\'.checkbox(\'bot_delete_id[]\', $id, \'0\'))) // TODO add translation\r\n						.hInput(\'bot_id[]\', $id);\r\n		$rows .= $single_row;\r\n	}	\r\n	return $rows;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_tab($event, $step) {\r\n\r\n	global $bot_admin_tooltips_main_array, $plugins;\r\n	\r\n	if (isset($_POST[\'bot_item\'])) { // if there are preferences\r\n		$prefs = bot_get_tips(); // array of values from the db table\r\n	};\r\n\r\n	$r = safe_rows_start(\'name, val\', \'txp_prefs\',\'event = \"custom\" AND val != \"\"\'); // creates an array of all cfs for selectInput in bot_admin_tooltips_tab\r\n	if ($r) {\r\n		global $arr_custom_fields;\r\n		while ($a = nextRow($r)) { \r\n			$name = \'tab_write|\'.str_replace(\'_set\', \'\', $a[\'name\']);\r\n			$val = $a[\'val\'];\r\n			$arr_custom_fields[$name] = $val;\r\n		}\r\n	}\r\n  \r\n	pagetop(\'bot_admin_tooltips \'.gTxt(\'preferences\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\r\n	echo hed(\'bot | admin tooltips\',\'2\', \' style=\"text-align: center; margin:20px auto;   padding-bottom:10px;\"\');\r\n\r\n	if ($step == \'install\') {\r\n		// Install the preferences table.\r\n		bot_admin_tooltips_install();\r\n	}\r\n	\r\n	if ($step == \'uninstall\') {\r\n		//remove table\r\n		safe_query(\"DROP TABLE \".PFX.\"bot_admin_tooltips\");\r\n		safe_delete(\'txp_prefs\', \'event = \"bot_tips_\"\' );\r\n	}\r\n	\r\n	if ($step == \'update\') { \r\n	\r\n		// set function variables\r\n		$new_item = doslash(ps(\'bot_new_item\'));\r\n		$new_tip = doslash(ps(\'bot_new_tip\'));\r\n		$item = doslash(ps(\'bot_item\'));\r\n		$tip = doslash(ps(\'bot_saved_tip\'));\r\n		$tip_id = ps(\'bot_id\');\r\n		$delete_id = ps(\'bot_delete_id\');\r\n		$hide_pophelp = ps(\'bot_admin_tooltips_hide_pophelp\');\r\n		$js_event = ps(\'bot_admin_tooltips_js_event\');\r\n		$tips_bg = doslash(ps(\'bot_admin_tooltips_bg\'));\r\n		$tips_color = doslash(ps(\'bot_admin_tooltips_color\'));\r\n		$tips_border_color = doslash(ps(\'bot_admin_tooltips_border_color\'));\r\n		$js_path = doslash(ps(\'bot_admin_tooltips_path\'));\r\n\r\n        if ($delete_id) { // checks if there is something to delete\r\n			foreach ($delete_id as $id) {\r\n				safe_delete(\'bot_admin_tooltips\', \'id =\"\'.$id.\'\"\' );\r\n			}\r\n		};\r\n		\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$hide_pophelp.\'\"\', \'name = \"bot_admin_tooltips_hide_pophelp\"\' ); // updates pophelp prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$js_event.\'\"\', \'name = \"bot_admin_tooltips_js_event\"\' ); // updates click/hover prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_bg.\'\"\', \'name = \"bot_admin_tooltips_bg\"\' ); // updates bg prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_color.\'\"\', \'name = \"bot_admin_tooltips_color\"\' ); // updates color prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_border_color.\'\"\', \'name = \"bot_admin_tooltips_border_color\"\' ); // updates border color prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$js_path.\'\"\', \'name = \"bot_admin_tooltips_path\"\' ); // updates path prefs\r\n				\r\n		if (($item != \'\') && ($tip != \'\')) { // when tips are set			\r\n			for($i = 0; $i < count($item); $i++) { // creates the \"posted_variables\" array containing item, tip, tip_id\r\n				$posted_variables[$item[$i]][\'item\'] = $item[$i];\r\n				$posted_variables[$item[$i]][\'tip\'] = $tip[$i];\r\n				$posted_variables[$item[$i]][\'id\'] = $tip_id[$i]; \r\n			}\r\n			foreach($posted_variables as $item => $values) { // for each posted variable (item, tip, tip_id) updates the db\r\n				$tip = $values[\'tip\'];\r\n				$id = $values[\'id\'];\r\n				if (($item != \'\') && ($tip != \'\')) { // if there is item AND tip\r\n					safe_update(\'bot_admin_tooltips\', \'item = \"\'.$item.\'\", tip = \"\'.$tip.\'\"\', \'id = \"\'.$id.\'\"\');\r\n				}\r\n				elseif ($tip == \'\'){ // if there is no tip, tip is deleted from db\r\n				safe_delete(\'bot_admin_tooltips\', \'item =\"\'.$item.\'\"\' );\r\n				}\r\n			}\r\n			if(($new_item != \'\') && ($new_tip != \'\')) { // if there is a new tip is inserted in db\r\n				safe_insert(\'bot_admin_tooltips\', \'item = \"\'.$new_item.\'\", tip = \"\'.$new_tip.\'\"\');\r\n			}\r\n		}\r\n		elseif(($new_item != \'\') && ($new_tip != \'\')) { // if no tips are set yet deals only with new tip\r\n			safe_insert(\'bot_admin_tooltips\', \'item = \"\'.$new_item.\'\", tip = \"\'.$new_tip.\'\"\');\r\n		}\r\n 	}\r\n	\r\n	\r\n	if (bot_admin_tooltips_check_install()) {		\r\n		extract(bot_admin_tooltips_get_prefs());\r\n		\r\n		// beginning of the form \r\n		echo form( \r\n		\r\n		// preferences\r\n		\'<div style=\"text-align:center; background:#f2f2f2; margin:20px auto 40px; padding:10px; border-bottom:solid #ccc 1px; border-top:solid #ccc 1px; \">\'\r\n		.n.\'<label for=\"bot_admin_tooltips_bg\">Background color </label>\'.finput(\'text\', \'bot_admin_tooltips_bg\', $bot_admin_tooltips_bg) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_color\">Text color </label>\'.finput(\'text\', \'bot_admin_tooltips_color\', $bot_admin_tooltips_color) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_border_color\">Border color </label>\'.finput(\'text\', \'bot_admin_tooltips_border_color\', $bot_admin_tooltips_border_color) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_js_event\">Show tip on hover </label>\'.checkbox2(\'bot_admin_tooltips_js_event\', $bot_admin_tooltips_js_event) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_hide_pophelp\">Hide txp pophelps </label>\'.checkbox2(\'bot_admin_tooltips_hide_pophelp\', $bot_admin_tooltips_hide_pophelp) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_path\">Path to js </label>\'.finput(\'text\', \'bot_admin_tooltips_path\', $bot_admin_tooltips_path) \r\n		.n.\'</div>\'\r\n\r\n		// update button\r\n		.\'<div style=\"margin: 0 auto 20px; width:580px; padding:0 10px;\">\'\r\n		.n.eInput(\'bot_admin_tooltips_tab\')\r\n		.n.sInput(\'update\')\r\n		.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n		.\'</div>\'\r\n		\r\n		// beginning of the tips table\r\n		.n.startTable(\'list\') \r\n		.n.tr(td(strong(\'Item\')).td(strong(\'Tip\'))) // table header\r\n		.n.bot_admin_tooltips_output_rows() // html rows generated by \"bot_admin_tooltips_output_rows()\"\r\n		.n.endTable() // end of the tips table\r\n		\r\n		// second update button\r\n		.\'<div style=\"margin: 20px auto; width:580px; padding:0 10px;\">\'\r\n		.n.eInput(\'bot_admin_tooltips_tab\')\r\n		.n.sInput(\'update\')\r\n		.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n		.\'</div>\');\r\n		\r\n		// uninstall button\r\n		echo n.t.\'<div style=\"margin: 20px auto 0; width:580px; border-top:dashed #ccc 1px; margin-top:40px; padding:10px 10px 0;\">\'.\r\n		n.t.t.graf(bot_admin_tooltips_gTxt(\'uninstall_message\')).\r\n		n.hed(bot_admin_tooltips_gTxt(\'uninstall\'), \'1\').\r\n		n.n.form(\r\n		n.eInput(\'bot_admin_tooltips_tab\').\r\n		n.sInput(\'uninstall\').\r\n		n.n.fInput(\'submit\', \'uninstall\', \'Uninstall \', \'smallerbox\'),\"\",\"confirm(\'\".bot_admin_tooltips_gTxt(\'uninstall_confirm\').\"\')\"\r\n		)\r\n		.\'</div>\'; \r\n	} \r\n	\r\n	else { \r\n		// install message\r\n		echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n			n.t.t.hed(\'bot_admin_tooltips \'.gTxt(\'Preferences\'), \'1\').\r\n			n.graf(bot_admin_tooltips_gTxt(\'install_message\')).\r\n			n.n.form(\r\n				n.eInput(\'bot_admin_tooltips_tab\').\r\n				n.sInput(\'install\').\r\n				n.n.fInput(\'submit\', \'install\', \'Install \', \'publish\')\r\n				).\r\n			\'</div>\';\r\n	}\r\n}', '//<?php\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'bot_admin_tooltips_tab\', \'1,2\');\r\n	register_tab(\'extensions\', \'bot_admin_tooltips_tab\', \'bot_admin_tooltips\');\r\n	register_callback(\'bot_admin_tooltips_tab\', \'bot_admin_tooltips_tab\');\r\n	register_callback(\'bot_admin_tooltips\', \'article\');\r\n	register_callback(\'bot_admin_tooltips\', \'category\');\r\n	register_callback(\'bot_admin_tooltips\', \'list\');\r\n	register_callback(\'bot_admin_tooltips\', \'image\');\r\n	register_callback(\'bot_admin_tooltips\', \'file\');\r\n	register_callback(\'bot_admin_tooltips\', \'link\');\r\n	register_callback(\'bot_admin_tooltips\', \'discuss\');\r\n	register_callback(\'bot_admin_tooltips\', \'admin\');\r\n} \r\n\r\nglobal $bot_admin_tooltips, $bot_admin_tooltips_main_array; //set globals\r\n\r\nif (getThings(\"Show tables like \'\".PFX.\"bot_admin_tooltips\'\")) { // if plugin is installed loads items and tips from db\r\n	$r = safe_rows_start(\'item, tip\', \'bot_admin_tooltips\',\'1=1\');\r\n	if ($r) {\r\n		$bot_admin_tooltips = array();\r\n		while ($a = nextRow($r)) {\r\n			$bot_admin_tooltips[$a[\'item\']] = $a[\'tip\'];\r\n		}   \r\n	}\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n \r\nfunction bot_in_main_array($item, $page, $hook, $type) { // helps build the main array\r\n	global $bot_admin_tooltips_main_array;\r\n	$bot_admin_tooltips_main_array[$item][\'page\'] = $page;\r\n	$bot_admin_tooltips_main_array[$item][\'hook\'] = $hook; \r\n	$bot_admin_tooltips_main_array[$item][\'type\'] = $type; \r\n	return $bot_admin_tooltips_main_array;\r\n}\r\n\r\nbot_in_main_array(\'tab_write|advanced_options\',\'article\',\'advanced\',\'link\');\r\nbot_in_main_array(\'tab_write|override_default_form\',\'article\',\'override-form\',\'label\');\r\nbot_in_main_array(\'tab_write|keywords\',\'article\',\'keywords\',\'label\');\r\nbot_in_main_array(\'tab_write|article_image\',\'article\',\'article-image\',\'label\');\r\nbot_in_main_array(\'tab_write|URL-only title\',\'article\',\'url-title\',\'label\');\r\nbot_in_main_array(\'tab_write|recent_articles\',\'article\',\'recent\',\'link\');\r\nbot_in_main_array(\'tab_write|title\',\'article\',\'title\',\'label\');\r\nbot_in_main_array(\'tab_write|body\',\'article\',\'body\',\'label\');\r\nbot_in_main_array(\'tab_write|excerpt\',\'article\',\'excerpt\',\'label\');\r\nbot_in_main_array(\'tab_write|status\',\'article\',\'write-status\',\'legend\');\r\nbot_in_main_array(\'tab_write|sort_display\',\'article\',\'write-sort\',\'legend\');\r\nbot_in_main_array(\'tab_write|category1\',\'article\',\'category-1\',\'label\');\r\nbot_in_main_array(\'tab_write|category2\',\'article\',\'category-2\',\'label\');\r\nbot_in_main_array(\'tab_write|section\',\'article\',\'section\',\'label\');\r\nbot_in_main_array(\'tab_write|more\',\'article\',\'more\',\'link\');\r\nbot_in_main_array(\'tab_write|timestamp\',\'article\',\'write-timestamp\',\'legend\');\r\nbot_in_main_array(\'tab_write|expires\',\'article\',\'write-expires\',\'legend\');\r\nbot_in_main_array(\'tab_organise|article_head\',\'category\',\'article_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|link_head\',\'category\',\'link_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|image_head\',\'category\',\'image_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|file_head\',\'category\',\'file_category\',\'h3\');\r\nbot_in_main_array(\'tab_organise|name\',\'category\',\'name\',\'td\');\r\n// bot_in_main_array(\'tab_organise|title\',\'category\',\'title\',\'td\');\r\nbot_in_main_array(\'tab_organise|parent\',\'category\',\'parent\',\'td\');\r\nbot_in_main_array(\'tab_list|search\',\'list\',\'list-search\',\'label\');\r\nbot_in_main_array(\'tab_list|with_selected\',\'list\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_image|upload_image\',\'image\',\'image-upload\',\'label\');\r\nbot_in_main_array(\'tab_image|search\',\'image\',\'image-search\',\'label\');\r\nbot_in_main_array(\'tab_image|with_selected\',\'image\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_image|replace_image\',\'image\',\'image-replace\',\'label\');\r\nbot_in_main_array(\'tab_image|upload_thumbnail\',\'image\',\'upload-thumbnail\',\'label\');\r\nbot_in_main_array(\'tab_image|image_category\',\'image\',\'image-category\',\'label\');\r\nbot_in_main_array(\'tab_image|alt_text\',\'image\',\'alt-text\',\'label\');\r\nbot_in_main_array(\'tab_image|caption\',\'image\',\'caption\',\'label\');\r\nbot_in_main_array(\'tab_file|upload_file\',\'file\',\'file-upload\',\'label\');\r\nbot_in_main_array(\'tab_file|search\',\'file\',\'file-search\',\'label\');\r\nbot_in_main_array(\'tab_file|with_selected\',\'file\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_file|category\',\'file\',\'category\',\'p\');\r\nbot_in_main_array(\'tab_file|description\',\'file\',\'description\',\'p\');\r\nbot_in_main_array(\'tab_file|file_status\',\'file\',\'file-status\',\'legend\');\r\nbot_in_main_array(\'tab_file|timestamp\',\'file\',\'file-created\',\'legend\');\r\nbot_in_main_array(\'tab_link|title\',\'link\',\'link-title\',\'label\');\r\nbot_in_main_array(\'tab_link|sort_value\',\'link\',\'link-sort\',\'label\');\r\nbot_in_main_array(\'tab_link|url\',\'link\',\'link-url\',\'label\');\r\nbot_in_main_array(\'tab_link|category\',\'link\',\'link-category\',\'label\');\r\nbot_in_main_array(\'tab_link|description\',\'link\',\'link-description\',\'label\');\r\nbot_in_main_array(\'tab_link|search\',\'link\',\'link-search\',\'label\');\r\nbot_in_main_array(\'tab_link|with_selected\',\'link\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_comments|search\',\'discuss\',\'discuss-search\',\'label\');\r\nbot_in_main_array(\'tab_comments|with_selected\',\'discuss\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_admin|login\',\'admin\',\'name\',\'td\');\r\nbot_in_main_array(\'tab_admin|real_name\',\'admin\',\'RealName\',\'td\');\r\nbot_in_main_array(\'tab_admin|email\',\'admin\',\'email\',\'td\');\r\nbot_in_main_array(\'tab_admin|privileges\',\'admin\',\'privs\',\'td\');\r\nbot_in_main_array(\'tab_admin|with_selected\',\'admin\',\'withselected\',\'label\');\r\nbot_in_main_array(\'tab_admin|new_password\',\'admin\',\'new_pass\',\'label\');\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n\r\n// when first login onto txp, event is not specified. So if txp version < 4.08 $event = \'article\'\r\n// if txp version > 4.08 $event = \'$default_event\' in $prefs (cos 4.2 allows to choose default event)\r\n\r\nfunction bot_check_event() {\r\n    global $event, $prefs;\r\n   	if (gps(\'event\')) {\r\n    	$event = gps(\'event\');\r\n    }\r\n    else {\r\n        $event=(($prefs[\'version\']) < \'4.2.0\') ? \'article\' : $prefs[\'default_event\'];\r\n    }\r\n    return $event;\r\n} \r\n\r\n\r\n\r\n\r\n\r\n// ===========================================================\r\n//	jquery\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_js_rows() {\r\n	//set function variables\r\n	global $bot_admin_tooltips_per_page, $bot_admin_tooltips_main_array, $plugins;\r\n	extract(bot_admin_tooltips_get_prefs());\r\n	$bot_admin_tooltips_js_show_event = $bot_admin_tooltips_js_event ? \'mouseover\' : \'click\'; // set the \'$bot_admin_tooltips_js_event\' variable depending on the posted values\r\n	$bot_admin_tooltips_js_hide_event = $bot_admin_tooltips_js_event ? \'mouseout\' : \'unfocus\'; \r\n	if (in_array(\'glz_custom_fields\', $plugins)) {\r\n	$bot_glz_version = safe_field(\"version\",\"txp_plugin\",\"name=\'glz_custom_fields\'\"); //  fetch glz version, if installed\r\n	$bot_glz_version = str_replace(\".\", \"\", $bot_glz_version);\r\n	};\r\n\r\n    // when first login onto txp event is not specified. So if txp version < 4.08 $event = \'article\'\r\n	// if txp version > 4.08 $event =  $default_event in $prefs (4.2 allows to choose default event)\r\n	$event = bot_check_event();\r\n	\r\n	foreach ($bot_admin_tooltips_per_page as $item => $tip) {\r\n		if (strpos($item, \'custom_\') !== false) { // if is a custom field use this values\r\n			$hook = substr($item, strpos($item, \'custom_\')); // gets the hook: removes the  part before \"|\"\r\n			$hook = in_array(\'glz_custom_fields\', $plugins) && $bot_glz_version < 122 ? $hook : str_replace(\'custom_\', \'custom-\', $hook); // if glz_custom_fields is NOT installed  OR is varsion 1.2.2. replaces \"_\" with \"-\"\r\n			$page = \'article\';\r\n			$type = \'label\';\r\n			$position = \'topRight\';\r\n		}\r\n		else {\r\n			$hook = $bot_admin_tooltips_main_array[$item][\'hook\'];\r\n			$page = $bot_admin_tooltips_main_array[$item][\'page\'];\r\n			$type = $bot_admin_tooltips_main_array[$item][\'type\'];\r\n		}; \r\n		$tip = addslashes($tip);\r\n		switch($event){\r\n			case \'article\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\"; \r\n					break;\r\n					case \'legend\':\r\n						$selector = \"$(\\\"fieldset#\".$hook.\" legend\\\")\";\r\n					break;\r\n					case \'link\':\r\n						$selector = \"$(\\\"a[href*=\'\".$hook.\"\']\\\")\"; // for \"more\" and \"recent articles\"\r\n					break;\r\n				}\r\n			break;				\r\n			case \'category\':\r\n				switch($type){\r\n					case \'h3\':\r\n						$selector = \"$(\\\"h3:has(a[href*=\'\".$hook.\"\'])\\\")\"; \r\n					break;\r\n					case \'td\': // for category \"title\", \"parent\" etc.\r\n						$selector = \"$(\\\"td:has(*[name=\'\".$hook.\"\'])\\\").prev()\"; \r\n					break;\r\n				}\r\n			break;\r\n			case \'list\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'image\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'file\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n					break;\r\n					case \'legend\':\r\n						$selector = \"$(\\\"fieldset#\".$hook.\" legend\\\")\";\r\n					break;	\r\n					case \'p\': // for file \"description\", \"category\" \r\n						$selector = \"$(\\\"p:has(*[name=\'\".$hook.\"\'])\\\")\";\r\n					break;\r\n				}\r\n			break;\r\n			case \'link\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;	\r\n			case \'discuss\':\r\n				$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n			break;\r\n			case \'admin\':\r\n				switch($type){\r\n					case \'label\':\r\n						$selector = \"$(\\\"label[for=\'\".$hook.\"\']\\\")\";\r\n					break;	\r\n					case \'td\': // for category \"Privileges\", \"Login\" etc.\r\n						$selector = \"$(\\\"td:has(*[name=\'\".$hook.\"\'])\\\").prev()\";  \r\n					break;\r\n				}\r\n			break;	\r\n		}\r\n		echo \r\n			$selector.\'.append(\"<span class=\\\"bot_tip\\\" style=\\\"cursor:pointer;\\\">&nbsp;[ ? ]&nbsp;</span>\");\'.n. // append question mark\r\n			$selector.\'.qtip({  \r\n				content: \"\'.preg_replace(\"/\\r?\\n/\", \"<br />\", $tip).\'\", \r\n				show: { when: {event: \"\'.$bot_admin_tooltips_js_show_event.\'\", target: \'.$selector.\'.children(\"span.bot_tip\")}, effect: { type: \"fade\", length: 0}, solo: true, delay: 0},\r\n				hide: { when: {event: \"\'.$bot_admin_tooltips_js_hide_event.\'\", target: \'.$selector.\'.children(\"span.bot_tip\")}, effect: { type: \"fade\", length: 200}, fixed: true},\r\n				style:{border: {width: 6, radius: 8, color: \"\'.$bot_admin_tooltips_border_color.\'\"}, width: 200, background: \"\'.$bot_admin_tooltips_bg.\'\", color: \"\'.$bot_admin_tooltips_color.\'\", tip: \"bottomLeft\"},\r\n				position: {target: \'.$selector.\'.children(\"span.bot_tip\"), corner: {target: \"topRight\", tooltip: \"bottomLeft\"}, adjust: { screen: true }}\r\n			});	\';	\r\n		\r\n	};	\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips(){\r\n\r\n	global $bot_admin_tooltips, $bot_admin_tooltips_main_array;\r\n	global $bot_admin_tooltips_per_page; // tips for the actual page (event)\r\n	extract(bot_admin_tooltips_get_prefs());\r\n	\r\n	    // when first login onto txp event is not specified. So if txp version < 4.08 $event = \'article\'\r\n	// if txp version > 4.08 $event =  $default_event in $prefs (4.2 allows to choose default event)\r\n	$event = bot_check_event();\r\n\r\n	foreach ($bot_admin_tooltips as $item => $tip) { // builds the \"tips per page\" array\r\n        if ((strpos($item, \'custom_\') !== false) && $event ==\"article\") { // if is a cf and event is \"article\"\r\n			$bot_admin_tooltips_per_page[$item] = $tip; \r\n		}\r\n		elseif(strpos($item, \'custom_\') !== false){\r\n        // do nothing\r\n		}\r\n		elseif ($bot_admin_tooltips_main_array[$item][\'page\'] == $event) {\r\n			$bot_admin_tooltips_per_page[$item] = $tip; \r\n		};\r\n	}\r\n	\r\n	if ($bot_admin_tooltips_per_page) { // prints js code only if there are tips\r\n		echo\r\n				\'<script type=\"text/javascript\" src= \"\'.$bot_admin_tooltips_path.\'jquery.qtip-1.0.0-rc3.min.js\"></script>\'.n.\r\n				\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n				\'/* <![CDATA[ */\'.n.\r\n				\'	$(document).ready(function() {\';\r\n				bot_admin_tooltips_js_rows();\r\n		echo\r\n				\'	});\'.n.\r\n				\'/* ]]> */\'.n.\r\n				\'</script>\';\r\n	}\r\n	if ($bot_admin_tooltips_hide_pophelp == \'1\') {\r\n		echo		\r\n				\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n				\'/* <![CDATA[ */\'.n.\r\n				\'	$(document).ready(function() {\'.n.\r\n				\'	$(\"a.pophelp\").hide()\'.n.\r\n				\'	});\'.n.\r\n				\'/* ]]> */\'.n.\r\n				\'</script>\';		\r\n	}\r\n}\r\n\r\n\r\n\r\n// ===========================================================\r\n//	Interface and db\r\n// ===========================================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_gTxt($what) {\r\n\r\n	global $language;\r\n	\r\n	$en_us = array(\r\n		\'install_message\' => \'bot_admin_tooltips is not yet properly initialized.  Use the button below to create the preferences table.\',\r\n		\'upgrade_message\' => \'bot_admin_tooltips must be upgraded. Use the button below to add the new fields to the preferences table.\',\r\n		\'uninstall\' => \'Uninstall\',\r\n		\'uninstall_message\' => \'Using the button below will remove the bot_admin_tooltips preferences table (and all other entries made by the plugin).\',\r\n		\'uninstall_confirm\' => \'Are you sure you want to delete the preferences table?\',\r\n		);\r\n	\r\n	$lang = array(\r\n		\'en-us\' => $en_us\r\n		);\r\n		\r\n		$language = (isset($lang[$language])) ? $language : \'en-us\';\r\n		$msg = (isset($lang[$language][$what])) ? $lang[$language][$what] : $what;\r\n		return $msg;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_check_install() {\r\n\r\n	// Check if the bot_admin_tooltips table already exists\r\n	if (getThings(\"Show tables like \'\".PFX.\"bot_admin_tooltips\'\")) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//=========================================== \r\n\r\n\r\n\r\nfunction bot_admin_tooltips_install() {	\r\n	\r\n	// figure out what MySQL version we are using (from _update.php)\r\n	$mysqlversion = mysql_get_server_info();\r\n	$tabletype = (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.(0\\.[2-9]|(1[89]))|(1\\.[2-9])#\',$mysqlversion)) \r\n		? \" ENGINE=MyISAM \"\r\n		: \" TYPE=MyISAM \";\r\n	if (isset($txpcfg[\'dbcharset\']) && (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.[1-9]#\',$mysqlversion))) {\r\n		$tabletype .= \" CHARACTER SET = \". $txpcfg[\'dbcharset\'] .\" \";\r\n	}\r\n	\r\n	// Create the bot_admin_tooltips table\r\n	$bot_admin_tooltips_prefs_table = safe_query(\"CREATE TABLE `\".PFX.\"bot_admin_tooltips` (\r\n		`id` INT NOT NULL AUTO_INCREMENT , \r\n		`item` VARCHAR(255) NOT NULL, \r\n		`tip` TEXT NOT NULL,\r\n		PRIMARY KEY (`id`)\r\n		) $tabletype\");\r\n	\r\n	set_pref (\'bot_admin_tooltips_hide_pophelp\',\'1\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_js_event\',\'0\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_bg\',\'#fdfdfd\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_color\',\'#454545\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_border_color\',\'#e0e0e0\', \'bot_tips_\',\'2\');\r\n	set_pref (\'bot_admin_tooltips_path\',\'../js/\', \'bot_tips_\',\'2\');\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_get_tips() { // creates an array of values extracted from the db \r\n\r\n	$r = safe_rows_start(\'id, item, tip\', \'bot_admin_tooltips\',\'1=1\');\r\n	if ($r) {\r\n		$out = array();\r\n		while ($a = nextRow($r)) {\r\n			$out[$a[\'item\']][\'item\'] = $a[\'item\'];\r\n			$out[$a[\'item\']][\'id\'] = $a[\'id\'];\r\n			$out[$a[\'item\']][\'tip\'] = $a[\'tip\'];\r\n		}\r\n		asort($out);\r\n		return $out;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_get_prefs() { // creates an array of values extracted from the prefs in db \r\n	\r\n	$r = safe_rows_start(\'name, val\', \'txp_prefs\', \'event = \"bot_tips_\"\');\r\n	if ($r) {\r\n		$public_prefs = array();\r\n		while ($a = nextRow($r)) {\r\n			$public_prefs[$a[\'name\']] = $a[\'val\'];\r\n		}\r\n		return $public_prefs;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction selectInput_values ($current_item = \'\') { // generates context-sensitive array for selectInput values\r\n	global $bot_admin_tooltips_main_array, $arr_custom_fields;\r\n	$prefs = bot_get_tips(); // values from the db \r\n    if ($arr_custom_fields) {\r\n        $bot_admin_tooltips_main_array_with_cf = array_merge($bot_admin_tooltips_main_array, $arr_custom_fields); // mega array with existing cfs\r\n    }\r\n    else {\r\n        $bot_admin_tooltips_main_array_with_cf = $bot_admin_tooltips_main_array; // if no cfs are set - var name should be changed but I\'m lazy\r\n    }\r\n    $values_for_selectInput = array(); // creates a new array\r\n	foreach ($bot_admin_tooltips_main_array_with_cf as $item => $values) { // filters values for txp selectInput function (behaves differently if cf or not)...\r\n		if (!array_key_exists($item, $prefs) || $item == $current_item) { // ... insert in  \"values_for_selectInput\" only if not yet used or value is given as parameter \"$current_item\" \r\n			if (strpos($item, \'custom_\') !== false) { // if $item IS a cf insert in this specific format...\r\n				$values_for_selectInput[$item] = gTxt(\'tab_write\').\'|\'.$values; // value is modified to translate and to allow alphabetical ordering\r\n			}\r\n			else { // ...if is not a cf insert in this format instead\r\n				$split_items = explode(\'|\', $item); // begin translation process > split item name\r\n				for ($i = 0; $i < count($split_items); $i++) {\r\n				$translated_value[] = gTxt($split_items[$i]); // translates each word and add to an array	\r\n				}			\r\n				$joint_items = implode(\'|\', $translated_value); // joins together single words\r\n				unset($translated_value); // so every time is empty\r\n				$values_for_selectInput[$item] = $joint_items; // key => ugly name; $value => nice name\r\n			}\r\n		}\r\n	}\r\n	asort($values_for_selectInput);\r\n	return $values_for_selectInput;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_output_rows(){ // outputs the rows for the html table in the bot_admin_tooltips_tab\r\n\r\n	global $bot_admin_tooltips_main_array;\r\n	$prefs = bot_get_tips(); // array of values from the db \r\n	\r\n	$rows = \"\";\r\n	$values_for_selectInput = selectInput_values();\r\n	\r\n	if ($values_for_selectInput){ // if there are free values left for select input...\r\n		$input_row = tr(td(selectInput(\'bot_new_item\', $values_for_selectInput, \'\', \'1\' )).td(\'<textarea name=\"bot_new_tip\" cols=\"40\" rows=\"5\" style=\"width:300px; height:70px\"></textarea>\').td()); // ...outputs the \'new tip\' input\r\n		$rows .= $input_row;\r\n	}\r\n	foreach ($prefs as $item => $values) {	// rows for values coming from the db\r\n		$tip = $values[\'tip\'];\r\n		$id = $values[\'id\'];\r\n		$values_for_selectInput = selectInput_values($item);\r\n		$single_row = tr(td(selectInput(\'bot_item[]\', $values_for_selectInput, $item))\r\n						.td(\'<textarea name=\"bot_saved_tip[]\" cols=\"40\" rows=\"5\" style=\"width:300px; height:70px\">\'.$tip.\'</textarea>\')\r\n						.td(\'<label for=\"bot_delete_id\">Delete</label>\'.checkbox(\'bot_delete_id[]\', $id, \'0\'))) // TODO add translation\r\n						.hInput(\'bot_id[]\', $id);\r\n		$rows .= $single_row;\r\n	}	\r\n	return $rows;\r\n}\r\n\r\n\r\n\r\n//===========================================\r\n\r\n\r\n\r\nfunction bot_admin_tooltips_tab($event, $step) {\r\n\r\n	global $bot_admin_tooltips_main_array, $plugins;\r\n	\r\n	if (isset($_POST[\'bot_item\'])) { // if there are preferences\r\n		$prefs = bot_get_tips(); // array of values from the db table\r\n	};\r\n\r\n	$r = safe_rows_start(\'name, val\', \'txp_prefs\',\'event = \"custom\" AND val != \"\"\'); // creates an array of all cfs for selectInput in bot_admin_tooltips_tab\r\n	if ($r) {\r\n		global $arr_custom_fields;\r\n		while ($a = nextRow($r)) { \r\n			$name = \'tab_write|\'.str_replace(\'_set\', \'\', $a[\'name\']);\r\n			$val = $a[\'val\'];\r\n			$arr_custom_fields[$name] = $val;\r\n		}\r\n	}\r\n  \r\n	pagetop(\'bot_admin_tooltips \'.gTxt(\'preferences\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\r\n	echo hed(\'bot | admin tooltips\',\'2\', \' style=\"text-align: center; margin:20px auto;   padding-bottom:10px;\"\');\r\n\r\n	if ($step == \'install\') {\r\n		// Install the preferences table.\r\n		bot_admin_tooltips_install();\r\n	}\r\n	\r\n	if ($step == \'uninstall\') {\r\n		//remove table\r\n		safe_query(\"DROP TABLE \".PFX.\"bot_admin_tooltips\");\r\n		safe_delete(\'txp_prefs\', \'event = \"bot_tips_\"\' );\r\n	}\r\n	\r\n	if ($step == \'update\') { \r\n	\r\n		// set function variables\r\n		$new_item = doslash(ps(\'bot_new_item\'));\r\n		$new_tip = doslash(ps(\'bot_new_tip\'));\r\n		$item = doslash(ps(\'bot_item\'));\r\n		$tip = doslash(ps(\'bot_saved_tip\'));\r\n		$tip_id = ps(\'bot_id\');\r\n		$delete_id = ps(\'bot_delete_id\');\r\n		$hide_pophelp = ps(\'bot_admin_tooltips_hide_pophelp\');\r\n		$js_event = ps(\'bot_admin_tooltips_js_event\');\r\n		$tips_bg = doslash(ps(\'bot_admin_tooltips_bg\'));\r\n		$tips_color = doslash(ps(\'bot_admin_tooltips_color\'));\r\n		$tips_border_color = doslash(ps(\'bot_admin_tooltips_border_color\'));\r\n		$js_path = doslash(ps(\'bot_admin_tooltips_path\'));\r\n\r\n        if ($delete_id) { // checks if there is something to delete\r\n			foreach ($delete_id as $id) {\r\n				safe_delete(\'bot_admin_tooltips\', \'id =\"\'.$id.\'\"\' );\r\n			}\r\n		};\r\n		\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$hide_pophelp.\'\"\', \'name = \"bot_admin_tooltips_hide_pophelp\"\' ); // updates pophelp prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$js_event.\'\"\', \'name = \"bot_admin_tooltips_js_event\"\' ); // updates click/hover prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_bg.\'\"\', \'name = \"bot_admin_tooltips_bg\"\' ); // updates bg prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_color.\'\"\', \'name = \"bot_admin_tooltips_color\"\' ); // updates color prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$tips_border_color.\'\"\', \'name = \"bot_admin_tooltips_border_color\"\' ); // updates border color prefs\r\n		safe_update(\'txp_prefs\', \'val= \"\'.$js_path.\'\"\', \'name = \"bot_admin_tooltips_path\"\' ); // updates path prefs\r\n				\r\n		if (($item != \'\') && ($tip != \'\')) { // when tips are set			\r\n			for($i = 0; $i < count($item); $i++) { // creates the \"posted_variables\" array containing item, tip, tip_id\r\n				$posted_variables[$item[$i]][\'item\'] = $item[$i];\r\n				$posted_variables[$item[$i]][\'tip\'] = $tip[$i];\r\n				$posted_variables[$item[$i]][\'id\'] = $tip_id[$i]; \r\n			}\r\n			foreach($posted_variables as $item => $values) { // for each posted variable (item, tip, tip_id) updates the db\r\n				$tip = $values[\'tip\'];\r\n				$id = $values[\'id\'];\r\n				if (($item != \'\') && ($tip != \'\')) { // if there is item AND tip\r\n					safe_update(\'bot_admin_tooltips\', \'item = \"\'.$item.\'\", tip = \"\'.$tip.\'\"\', \'id = \"\'.$id.\'\"\');\r\n				}\r\n				elseif ($tip == \'\'){ // if there is no tip, tip is deleted from db\r\n				safe_delete(\'bot_admin_tooltips\', \'item =\"\'.$item.\'\"\' );\r\n				}\r\n			}\r\n			if(($new_item != \'\') && ($new_tip != \'\')) { // if there is a new tip is inserted in db\r\n				safe_insert(\'bot_admin_tooltips\', \'item = \"\'.$new_item.\'\", tip = \"\'.$new_tip.\'\"\');\r\n			}\r\n		}\r\n		elseif(($new_item != \'\') && ($new_tip != \'\')) { // if no tips are set yet deals only with new tip\r\n			safe_insert(\'bot_admin_tooltips\', \'item = \"\'.$new_item.\'\", tip = \"\'.$new_tip.\'\"\');\r\n		}\r\n 	}\r\n	\r\n	\r\n	if (bot_admin_tooltips_check_install()) {		\r\n		extract(bot_admin_tooltips_get_prefs());\r\n		\r\n		// beginning of the form \r\n		echo form( \r\n		\r\n		// preferences\r\n		\'<div style=\"text-align:center; background:#f2f2f2; margin:20px auto 40px; padding:10px; border-bottom:solid #ccc 1px; border-top:solid #ccc 1px; \">\'\r\n		.n.\'<label for=\"bot_admin_tooltips_bg\">Background color </label>\'.finput(\'text\', \'bot_admin_tooltips_bg\', $bot_admin_tooltips_bg) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_color\">Text color </label>\'.finput(\'text\', \'bot_admin_tooltips_color\', $bot_admin_tooltips_color) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_border_color\">Border color </label>\'.finput(\'text\', \'bot_admin_tooltips_border_color\', $bot_admin_tooltips_border_color) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_js_event\">Show tip on hover </label>\'.checkbox2(\'bot_admin_tooltips_js_event\', $bot_admin_tooltips_js_event) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_hide_pophelp\">Hide txp pophelps </label>\'.checkbox2(\'bot_admin_tooltips_hide_pophelp\', $bot_admin_tooltips_hide_pophelp) \r\n		.n.\'&nbsp; &nbsp;\'\r\n		.n.\'<label for=\"bot_admin_tooltips_path\">Path to js </label>\'.finput(\'text\', \'bot_admin_tooltips_path\', $bot_admin_tooltips_path) \r\n		.n.\'</div>\'\r\n\r\n		// update button\r\n		.\'<div style=\"margin: 0 auto 20px; width:580px; padding:0 10px;\">\'\r\n		.n.eInput(\'bot_admin_tooltips_tab\')\r\n		.n.sInput(\'update\')\r\n		.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n		.\'</div>\'\r\n		\r\n		// beginning of the tips table\r\n		.n.startTable(\'list\') \r\n		.n.tr(td(strong(\'Item\')).td(strong(\'Tip\'))) // table header\r\n		.n.bot_admin_tooltips_output_rows() // html rows generated by \"bot_admin_tooltips_output_rows()\"\r\n		.n.endTable() // end of the tips table\r\n		\r\n		// second update button\r\n		.\'<div style=\"margin: 20px auto; width:580px; padding:0 10px;\">\'\r\n		.n.eInput(\'bot_admin_tooltips_tab\')\r\n		.n.sInput(\'update\')\r\n		.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n		.\'</div>\');\r\n		\r\n		// uninstall button\r\n		echo n.t.\'<div style=\"margin: 20px auto 0; width:580px; border-top:dashed #ccc 1px; margin-top:40px; padding:10px 10px 0;\">\'.\r\n		n.t.t.graf(bot_admin_tooltips_gTxt(\'uninstall_message\')).\r\n		n.hed(bot_admin_tooltips_gTxt(\'uninstall\'), \'1\').\r\n		n.n.form(\r\n		n.eInput(\'bot_admin_tooltips_tab\').\r\n		n.sInput(\'uninstall\').\r\n		n.n.fInput(\'submit\', \'uninstall\', \'Uninstall \', \'smallerbox\'),\"\",\"confirm(\'\".bot_admin_tooltips_gTxt(\'uninstall_confirm\').\"\')\"\r\n		)\r\n		.\'</div>\'; \r\n	} \r\n	\r\n	else { \r\n		// install message\r\n		echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n			n.t.t.hed(\'bot_admin_tooltips \'.gTxt(\'Preferences\'), \'1\').\r\n			n.graf(bot_admin_tooltips_gTxt(\'install_message\')).\r\n			n.n.form(\r\n				n.eInput(\'bot_admin_tooltips_tab\').\r\n				n.sInput(\'install\').\r\n				n.n.fInput(\'submit\', \'install\', \'Install \', \'publish\')\r\n				).\r\n			\'</div>\';\r\n	}\r\n}', '1761053d09cb1a6fd22f12ed4022d1aa', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_admin_body_class', 0, 'redbot', 'www.redbot.it', '0.1', 'Hide and style admin items on a per user or privileges level basis via css.', '	<h2>bot_admin_body_class</h2>\n\n	<p>This micro-plugin let&#8217;s you hide items in  txp backend based  on the logged user name and privilege level, via css.<br />\nOnce activated it simply adds two classes to the body tag of each tab via jquery:</p>\n\n	<ul>\n		<li>One indicates the user group of the current logged user</li>\n		<li>Another indicates the logged user</li>\n	</ul>\n\n	<p>For example if I log in as &#8220;Donald Swain&#8221; with  &#8220;managing editor&#8221; privileges, the &#8220;body&#8221; tag for every txp admin page will now have two more classes: &#8220;managing_editor&#8221; and &#8220;donald_swain&#8221;.<br />\nPlease note that the user name will be lowercased and spaces will be replaced with an underscore.</p>\n\n	<p>Privileges can be one of the following:</p>\n\n	<ul>\n		<li>publisher</li>\n		<li>managing_editor</li>\n		<li>copy_editor</li>\n		<li>staff_writer</li>\n		<li>freelancer</li>\n		<li>designer</li>\n		<li>none</li>\n	</ul>\n\n	<p>Once done that, you can easily hide items in  the admin adding dome css rules in your textpattern.css file.</p>\n\n	<p>For example:</p>\n\n<pre><code>body.managing_editor .override-form {display:none;}\n</code></pre>\n\n	<p>will hide the &#8220;override form&#8221; field for each user logged as managing editor.<br />\nIn the same vein</p>\n\n<pre><code>body.donald_swain .override-form {display:none;}\n</code></pre>\n\n	<p>Will do the same but only if you are logged in as Donald Swain.</p>\n\n	<p>The idea for this plugin comes from <a href=\"http://forum.textpattern.com/viewtopic.php?pid=241718#p241718\">sacripant</a></p>', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_body_class\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction bot_body_class(){\r\n	\r\n	global $txp_user, $txp_groups ;\r\n	$privilege = fetch(\'privs\', \'txp_users\', \'name\', $txp_user);\r\n	$level = $txp_groups[$privilege];\r\n	$user = strtolower(str_replace(\' \', \'_\', $txp_user)); // Lowercases and replaces spaces with \'_\'\r\n	\r\n	echo <<<JS_CODE\r\n	\r\n	<script type=\"text/javascript\">\r\n	    $(document).ready(function() {\r\n			$(\"body\").addClass(\"$level $user\");\r\n	    });\r\n	</script>\r\n	\r\nJS_CODE;\r\n}\r\n\r\n', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_body_class\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction bot_body_class(){\r\n	\r\n	global $txp_user, $txp_groups ;\r\n	$privilege = fetch(\'privs\', \'txp_users\', \'name\', $txp_user);\r\n	$level = $txp_groups[$privilege];\r\n	$user = strtolower(str_replace(\' \', \'_\', $txp_user)); // Lowercases and replaces spaces with \'_\'\r\n	\r\n	echo <<<JS_CODE\r\n	\r\n	<script type=\"text/javascript\">\r\n	    $(document).ready(function() {\r\n			$(\"body\").addClass(\"$level $user\");\r\n	    });\r\n	</script>\r\n	\r\nJS_CODE;\r\n}\r\n\r\n', '2cf2f719b770236b35c403c154224dc8', '', '', 3, 5, 0),
('bot_privs', 0, 'redbot', '', '0.3.2', 'Edit user level prilvileges ', '<h1>bot_privs</h1>\n\n<p>The plugin allows to modify user level prilvileges with the help of a graphical interface located under the \"extensions\" tab. </p>\n\n<h2>Istallation</h2>\n<p>Once the plugin is activated go to extensions=>bot_privs and click \"install\" to install the plugin table.</p>\n\n <h2>Ugrade</h2>\n<p>\n  Just reinstall the plugin (no need to remove the old version). A message should appear informing you about the upgrade. All existing preferences will be mantained.\n</p>\n\n<h2>Uninstallation</h2>\n<p>Before unistalling the plugin it\'s advisable to delete the table the plugin creates (if you omit this step no harm will be done to your installation,  you will only have a useless table in your db).</p>\n\n<h2>Reset values</h2>\n<p>To reset  the privileges to txp defaults simply uninstall the table (without unistalling the whole plugin). A new \"fresh\" table  will be recreated automatically.</p>\n\n<h2> Changes v0.3.2 </h2>\n<ul>\n <li> Slight change. You can now add new privileges by adding a single line in plugins code</li>\n</ul>\n\n<h2> Changes v0.3.1 </h2>\n<ul>\n <li>Fixes a bug with the privileges added with txp 4.2.0</li>\n</ul>\n\n<h2> Changes v0.3 </h2>\n<ul>\n  <li>This version works only with Txp 4.2.0. </li>\n  <li>More privileges are added following the changes in admin_config.php </li>\n  <li>I\'ve removed the sloppy  &quot;can see all articles&quot; feature added by me in v. 0.2. If you need such functionality use <a href=\"http://forum.textpattern.com/viewtopic.php?pid=201995#p201995\">this</a> plugin by Variaas instead</li>\n</ul>\n<h2>Credits</h2>\n<p>The plugin code was mostly copied from hak_tinymce (expecially the plugin structure) and ied_hide_in_admin (for the interface).\nThis plugin simply adds a graphical interface to  \"tame_privs\".</p>\n\n</style>\n<!-- *** END PLUGIN CSS *** -->\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\nh2{\nmargin-bottom:0;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '// <?\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'bot_privs_tab\', \'1,2\');\r\n	register_tab(\'extensions\', \'bot_privs_tab\', \'bot_privs\');\r\n	register_callback(\'bot_privs_tab\', \'bot_privs_tab\');\r\n} \r\n\r\nglobal $bot_used_privs, $txp_permissions;\r\n// privs used by the plugin\r\n$bot_used_privs = array(\r\n	\'tab.admin\' => \'User can access the \"Admin\" tab\',\r\n	\'tab.content\' => \'User can access the \"Content\" tab\',\r\n	\'tab.extensions\' => \'User can access the \"Extensions\" tab\',\r\n	\'tab.presentation\' => \'User can access the \"Presentation\" tab\',\r\n\r\n	\'diag\' => \'User can access the \"Diagnostics\" sub-tab\',\r\n	\'prefs\' => \'User can access the \"Preferences\" sub-tab\',\r\n	\'admin\' => \'User can access the \"Users\" sub-tab\',\r\n	\'admin.edit\' => \'User can edit users in the \"Users\" sub-tab\',\r\n	\'admin.list\' => \'User can see the authors list in the \"Users\" sub-tab\',\r\n	\'log\' => \'User can access the \"Visitors logs\" sub-tab\',\r\n	\'plugin\' => \'User can access the \"Plugin\" sub-tab\',\r\n	\'import\' => \'User can access the \"Import\" sub-tab\',\r\n	\r\n	\'category\' => \'User can access the \"Categories\" sub-tab\',\r\n	\'article\' => \'User can access the \"Write\" sub-tab\',\r\n	\'list\' => \'User can access the \"Articles\" sub-tab\',\r\n	\'image\' => \'User can access the \"Images\" sub-tab\',\r\n	\'file\' =>\'User can access the \"Files\" sub-tab\' ,\r\n	\'link\' => \'User can access the \"Links\" sub-tab\',\r\n	\'discuss\' => \'User can access the \"Comments\" sub-tab\',\r\n\r\n	\'section\' => \'User can access the \"Sections\" sub-tab\',\r\n	\'page\' => \'User can access the \"Pages\" sub-tab\',\r\n	\'form\' => \'User can access the \"Forms\" sub-tab\',\r\n	\'css\' => \'User can access the \"Style\" sub-tab\',\r\n\r\n	\'article.edit\' => \'User can edit articles\',\r\n	\'article.edit.published\' => \'User can edit only published articles\',\r\n	\'article.edit.own\' => \'User can edit only his articles\',\r\n	\'article.edit.own.published\' => \'User can edit only his published articles\',\r\n	\'article.publish\' => \'User can publish new articles\',\r\n	\'article.delete\' => \'User can delete articles\',\r\n	\'article.delete.own\' => \'User can delete only his articles\',\r\n	\'article.php\' => \'User can insert php in articles\',\r\n		\r\n	\'file.edit\' => \'User can edit files\',\r\n	\'file.edit.own\' => \'User can edit only his files\',\r\n	\'file.delete\' => \'User can delete files\',\r\n	\'file.delete.own\' => \'User can delete only his files\',\r\n	\'file.publish\' => \'User can publish files\',\r\n	\r\n	\'image.edit\' => \'User can edit images\',\r\n	\'image.edit.own\' => \'User can edit only his images\',\r\n	\'image.delete\' => \'User can delete images\',\r\n	\'image.delete.own\'=> \'User can delete only his images\',\r\n	\r\n	\'link.edit\' => \'User can edit links\',\r\n	\'link.edit.own\' => \'User can edit only his links\',\r\n	\'link.delete\' => \'User can delete links\',\r\n	\'link.delete.own\' => \'User can delete only his links\'\r\n	// To add privileges for other plugin tabs insert a new line for each plugin like the examples below. \r\n	// Pay attention to the end comma which must be present in each line but the last one.\r\n	// Usage: first part before the \'arrow\' is the function we want to include (varies depending on which plugin we are targeting). \r\n	// The second part is the text you want to show on bot_privs tab\r\n	// Example:\r\n	// \'bot_privs_tab\' => \'User can access the \"bot_privs\" tab\'\r\n); \r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_gTxt($what) {\r\n\r\n	global $language;\r\n	\r\n	$en_us = array(\r\n		\'install_message\' => \'bot_privs is not yet properly initialized.  Use the button below to create the preferences table.\',\r\n		\'upgrade_message\' => \'bot_privs must be upgraded. Use the button below to add the new fields to the preferences table.\',\r\n		\'uninstall\' => \'Uninstall\',\r\n		\'uninstall_message\' => \'Using the button below will remove the bot_privs preferences table. Useful also to reset the privs to defaults. \',\r\n		\'uninstall_confirm\' => \'Are you sure you want to delete the preferences table?\',\r\n		);\r\n	\r\n	$lang = array(\r\n		\'en-us\' => $en_us\r\n		);\r\n		\r\n		$language = (isset($lang[$language])) ? $language : \'en-us\';\r\n		$msg = (isset($lang[$language][$what])) ? $lang[$language][$what] : $what;\r\n		return $msg;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// transforms the privs string into a sequence of 0 and 1 for easing the use of checkboxes\r\nfunction bot_decimal_to_binary($privs) {\r\n	\r\n	// takes a string of privs, eliminates white spaces and transforms it in an array  \r\n	$privs_array = explode(\",\",str_replace(\" \", \"\", $privs));\r\n	\r\n	// creates an array of six zero values with key beginning at 1\r\n	$yes_no_array[1] = 0;\r\n	$yes_no_array[2] = 0;\r\n	$yes_no_array[3] = 0;\r\n	$yes_no_array[4] = 0;\r\n	$yes_no_array[5] = 0;\r\n	$yes_no_array[6] = 0;\r\n	\r\n	// for each number checks if it is present as a key in \"$yes_no_array\"\r\n	// in this case changes the corresponding 0 value to 1\r\n	foreach ($privs_array as $key => $value){\r\n	\r\n		if(array_key_exists($value,$yes_no_array)){\r\n			$yes_no_array[$value] = 1;\r\n		}\r\n	}\r\n	// returns a six digit comma separated string made of 1 and 0 (yes or no)\r\n	$new_value=implode(\",\", $yes_no_array);\r\n	\r\n	return $new_value;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// re transforms each privs string taken from the \"bot_privs\" table to a format suitable for the \"txp_permissions\" array\r\n// does the contrary of  \"bot_decimal_to_binary\"\r\nfunction bot_binary_to_decimal($privs) {\r\n\r\n	$bot_privs_array = explode(\",\",$privs);\r\n	$new_array = array();\r\n	foreach ($bot_privs_array as $key => $value) {\r\n		if($value == \"1\"){\r\n			// array keys start from 0 so we add +1 to make it begin with 1 \r\n			$new_array[] = $key + 1;\r\n		}\r\n	}\r\n	\r\n	$new_value = implode(\",\", $new_array);\r\n	return $new_value;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// creates an array of values extracted from the database (the values are in the \"pseudo binary\" format)\r\nfunction bot_get_privs() {\r\n\r\n	$r = safe_rows_start(\'pref_name, pref_value\', \'txp_bot_privs\',\'1=1\');\r\n	if ($r) {\r\n		while ($a = nextRow($r)) {\r\n			$out[$a[\'pref_name\']] = $a[\'pref_value\'];\r\n		}\r\n		return $out;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_check_install() {\r\n\r\n	// Check if the txp_bot_privs table already exists\r\n	if (getThings(\"Show tables like \'\".PFX.\"txp_bot_privs\'\")) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// checks if an upgrade is needed\r\nfunction bot_privs_check_upgrade() {\r\n\r\n	// Check if the \"file.edit\" entry does exist in txp_bot_privs table\r\n	$privs_in_db = bot_get_privs();\r\n	if (array_key_exists(\"file.edit\", $privs_in_db)) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_install() {	\r\n	\r\n	// figure out what MySQL version we are using (from _update.php)\r\n	$mysqlversion = mysql_get_server_info();\r\n	$tabletype = (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.(0\\.[2-9]|(1[89]))|(1\\.[2-9])#\',$mysqlversion)) \r\n		? \" ENGINE=MyISAM \"\r\n		: \" TYPE=MyISAM \";\r\n	if (isset($txpcfg[\'dbcharset\']) && (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.[1-9]#\',$mysqlversion))) {\r\n		$tabletype .= \" CHARACTER SET = \". $txpcfg[\'dbcharset\'] .\" \";\r\n	}\r\n	\r\n	// Create the bot_privs table\r\n	$bot_privs_prefs_table = safe_query(\"CREATE TABLE `\".PFX.\"txp_bot_privs` (\r\n		`pref_name` VARCHAR(255) NOT NULL, \r\n		`pref_value` TEXT NOT NULL,\r\n		PRIMARY KEY (`pref_name`)\r\n		) $tabletype\");\r\n	\r\n	// if the table creation succeeds populate with values\r\n	global $txp_permissions, $bot_used_privs;\r\n	// we must replace the numbers coming from the hard coded \"txp_permissions\" array with a 0 and 1 sequence (useful for checkboxes) \r\n	// so we apply the function bot_decimal_to_binary() to each element of the array before inserting the new values in the table\r\n	if ($bot_privs_prefs_table) {		\r\n		foreach ($bot_used_privs as $fieldname => $caption) {\r\n			if (array_key_exists($fieldname, $txp_permissions)) {\r\n				$transformed_value = bot_decimal_to_binary($txp_permissions[$fieldname]);\r\n				safe_insert(\"txp_bot_privs\",\"pref_name=\'\".$fieldname.\"\', pref_value=\'\".$transformed_value.\"\'\");\r\n			}\r\n		}	\r\n	}\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// outputs the rows for the html table in the bot_privs_tab\r\nfunction bot_output_rows(){\r\n\r\n	global $bot_used_privs;\r\n	// array of names + values from the db table	\r\n	$prefs = bot_get_privs();\r\n	$rows = \"\";\r\n	foreach ($bot_used_privs as $fieldname => $caption) {\r\n		// privs string corresponding to the argument \"$fieldname\"\r\n		$privs = $prefs[\"$fieldname\"];\r\n		// transforms the string in an array \r\n		$a = explode(\",\",$privs);\r\n		// modify the $fieldname string to avoid dots in posted variables\r\n		$fieldname = str_replace(\".\", \"_dot_\", $fieldname);\r\n		$single_row = tr(td($caption)\r\n		//  elements of $a can be 0 or 1 \r\n		.td(checkbox2($fieldname.\'6\',$a[0]))\r\n		.td(checkbox2($fieldname.\'5\',$a[1]))\r\n		.td(checkbox2($fieldname.\'4\',$a[2]))\r\n		.td(checkbox2($fieldname.\'3\',$a[3]))\r\n		.td(checkbox2($fieldname.\'2\',$a[4]))\r\n		.td(checkbox2($fieldname.\'1\',$a[5]))\r\n		);\r\n		$rows .= $single_row;\r\n	}\r\n	return $rows;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_tab($event, $step) {\r\n\r\n	global $txp_permissions, $bot_used_privs;\r\n	pagetop(\'bot_privs \'.gTxt(\'preferences\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\r\n\r\n	if ($step == \'install\') {\r\n		// Install the preferences table.\r\n		bot_privs_install();\r\n	}\r\n\r\n	if ($step == \'upgrade\') {\r\n		// Install the missing field.\r\n		safe_delete(\"txp_bot_privs\",\"1=1\");\r\n		foreach ($bot_used_privs as $fieldname => $caption) {\r\n			if (array_key_exists($fieldname, $txp_permissions)) {\r\n				$transformed_value = bot_decimal_to_binary($txp_permissions[$fieldname]);\r\n				safe_insert(\"txp_bot_privs\",\"pref_name=\'\".$fieldname.\"\', pref_value=\'\".$transformed_value.\"\'\");\r\n			}\r\n		}	\r\n	}\r\n	\r\n	if ($step == \'uninstall\') {\r\n		//remove table\r\n		safe_query(\"DROP TABLE \".PFX.\"txp_bot_privs\");\r\n	}\r\n\r\n	if ($step == \'update\') {\r\n		// for each field name rebuilds the correct value to be inserted in the db \r\n		foreach($bot_used_privs as $fieldname => $caption) {\r\n			// replaces dots with \"_dot_\" (cos php replaces automatically dots with underscores in posted variable names)\r\n			$fieldname = str_replace(\'.\',\'_dot_\',$fieldname); \r\n			$val=(gps($fieldname.\'6\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'5\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'4\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'3\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'2\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'1\')?\'1\':\'0\');\r\n			// replaces again the \"_dot_\" substring with an actual dot \r\n			$fieldname = str_replace(\'_dot_\',\'.\',$fieldname);\r\n			safe_upsert(\'txp_bot_privs\', \"pref_value = \'$val\'\", \"pref_name = \'$fieldname\'\");\r\n		}\r\n 	}\r\n\r\n	if (bot_privs_check_install()) {\r\n		if (bot_privs_check_upgrade()){			\r\n			// beginning of the privs table\r\n			echo form(startTable(\'list\')\r\n			.tr(td(\'&nbsp;\')\r\n			.td(gTxt(\'publisher\'))\r\n			.td(gTxt(\'managing_editor\'))\r\n			.td(gTxt(\'copy_editor\'))\r\n			.td(gTxt(\'writer\'))\r\n			.td(gTxt(\'freelancer\'))\r\n			.td(gTxt(\'designer\')))\r\n			// html rows generated by \"bot_output_rows()\"\r\n			.bot_output_rows()\r\n			// end of the privs table\r\n			.endTable()\r\n			// update button\r\n			.n.t.\'<div style=\"margin: 20px auto 0; width:40%;\">\'\r\n			.n.eInput(\'bot_privs_tab\')\r\n			.n.sInput(\'update\')\r\n			.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n			.\'</div>\');\r\n			// uninstall button\r\n			echo n.t.\'<div style=\"margin: 20px auto 0; width:40%;\">\'.\r\n			n.hed(bot_privs_gTxt(\'uninstall\'), \'1\').\r\n			n.t.t.graf(bot_privs_gTxt(\'uninstall_message\')).\r\n			n.n.form(\r\n			n.eInput(\'bot_privs_tab\').\r\n			n.sInput(\'uninstall\').\r\n			n.n.fInput(\'submit\', \'uninstall\', \'Uninstall \', \'smallerbox\'),\"\",\"confirm(\'\".bot_privs_gTxt(\'uninstall_confirm\').\"\')\"\r\n			)\r\n			.\'</div>\'; \r\n		}\r\n		\r\n		else {\r\n			echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n				n.t.t.hed(\'bot_privs upgrade\', \'1\').\r\n				n.graf(bot_privs_gTxt(\'upgrade_message\')).\r\n				n.n.form(\r\n					n.eInput(\'bot_privs_tab\').\r\n					n.sInput(\'upgrade\').\r\n					n.n.fInput(\'submit\', \'upgrade\', \'Upgrade \', \'publish\')\r\n					).\r\n				\'</div>\';\r\n		}\r\n	} \r\n	\r\n	else {\r\n		echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n			n.t.t.hed(\'bot_privs \'.gTxt(\'Preferences\'), \'1\').\r\n			n.graf(bot_privs_gTxt(\'install_message\')).\r\n			n.n.form(\r\n				n.eInput(\'bot_privs_tab\').\r\n				n.sInput(\'install\').\r\n				n.n.fInput(\'submit\', \'install\', \'Install \', \'publish\')\r\n				).\r\n			\'</div>\';\r\n	}\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs() {\r\n\r\n	// replaces the default array of permissions defined by txp with the array of permissions stored in the \"txp_bot_privs\" table \r\n	global $txp_permissions;\r\n\r\n	if (bot_privs_check_install()) {\r\n		$prefs_in_db = bot_get_privs();\r\n		// overwrite existing privs\r\n		foreach($prefs_in_db as $fieldname => $privs) {\r\n			//extract the privs values and inserts them in \"$txp_permissions\" array\r\n			$privs = bot_binary_to_decimal($privs);\r\n			$txp_permissions [$fieldname]= $privs; \r\n		}\r\n	}\r\n}\r\n\r\nbot_privs();\r\n', '// <?\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'bot_privs_tab\', \'1,2\');\r\n	register_tab(\'extensions\', \'bot_privs_tab\', \'bot_privs\');\r\n	register_callback(\'bot_privs_tab\', \'bot_privs_tab\');\r\n} \r\n\r\nglobal $bot_used_privs, $txp_permissions;\r\n// privs used by the plugin\r\n$bot_used_privs = array(\r\n	\'tab.admin\' => \'User can access the \"Admin\" tab\',\r\n	\'tab.content\' => \'User can access the \"Content\" tab\',\r\n	\'tab.extensions\' => \'User can access the \"Extensions\" tab\',\r\n	\'tab.presentation\' => \'User can access the \"Presentation\" tab\',\r\n\r\n	\'diag\' => \'User can access the \"Diagnostics\" sub-tab\',\r\n	\'prefs\' => \'User can access the \"Preferences\" sub-tab\',\r\n	\'admin\' => \'User can access the \"Users\" sub-tab\',\r\n	\'admin.edit\' => \'User can edit users in the \"Users\" sub-tab\',\r\n	\'admin.list\' => \'User can see the authors list in the \"Users\" sub-tab\',\r\n	\'log\' => \'User can access the \"Visitors logs\" sub-tab\',\r\n	\'plugin\' => \'User can access the \"Plugin\" sub-tab\',\r\n	\'import\' => \'User can access the \"Import\" sub-tab\',\r\n	\r\n	\'category\' => \'User can access the \"Categories\" sub-tab\',\r\n	\'article\' => \'User can access the \"Write\" sub-tab\',\r\n	\'list\' => \'User can access the \"Articles\" sub-tab\',\r\n	\'image\' => \'User can access the \"Images\" sub-tab\',\r\n	\'file\' =>\'User can access the \"Files\" sub-tab\' ,\r\n	\'link\' => \'User can access the \"Links\" sub-tab\',\r\n	\'discuss\' => \'User can access the \"Comments\" sub-tab\',\r\n\r\n	\'section\' => \'User can access the \"Sections\" sub-tab\',\r\n	\'page\' => \'User can access the \"Pages\" sub-tab\',\r\n	\'form\' => \'User can access the \"Forms\" sub-tab\',\r\n	\'css\' => \'User can access the \"Style\" sub-tab\',\r\n\r\n	\'article.edit\' => \'User can edit articles\',\r\n	\'article.edit.published\' => \'User can edit only published articles\',\r\n	\'article.edit.own\' => \'User can edit only his articles\',\r\n	\'article.edit.own.published\' => \'User can edit only his published articles\',\r\n	\'article.publish\' => \'User can publish new articles\',\r\n	\'article.delete\' => \'User can delete articles\',\r\n	\'article.delete.own\' => \'User can delete only his articles\',\r\n	\'article.php\' => \'User can insert php in articles\',\r\n		\r\n	\'file.edit\' => \'User can edit files\',\r\n	\'file.edit.own\' => \'User can edit only his files\',\r\n	\'file.delete\' => \'User can delete files\',\r\n	\'file.delete.own\' => \'User can delete only his files\',\r\n	\'file.publish\' => \'User can publish files\',\r\n	\r\n	\'image.edit\' => \'User can edit images\',\r\n	\'image.edit.own\' => \'User can edit only his images\',\r\n	\'image.delete\' => \'User can delete images\',\r\n	\'image.delete.own\'=> \'User can delete only his images\',\r\n	\r\n	\'link.edit\' => \'User can edit links\',\r\n	\'link.edit.own\' => \'User can edit only his links\',\r\n	\'link.delete\' => \'User can delete links\',\r\n	\'link.delete.own\' => \'User can delete only his links\'\r\n	// To add privileges for other plugin tabs insert a new line for each plugin like the examples below. \r\n	// Pay attention to the end comma which must be present in each line but the last one.\r\n	// Usage: first part before the \'arrow\' is the function we want to include (varies depending on which plugin we are targeting). \r\n	// The second part is the text you want to show on bot_privs tab\r\n	// Example:\r\n	// \'bot_privs_tab\' => \'User can access the \"bot_privs\" tab\'\r\n); \r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_gTxt($what) {\r\n\r\n	global $language;\r\n	\r\n	$en_us = array(\r\n		\'install_message\' => \'bot_privs is not yet properly initialized.  Use the button below to create the preferences table.\',\r\n		\'upgrade_message\' => \'bot_privs must be upgraded. Use the button below to add the new fields to the preferences table.\',\r\n		\'uninstall\' => \'Uninstall\',\r\n		\'uninstall_message\' => \'Using the button below will remove the bot_privs preferences table. Useful also to reset the privs to defaults. \',\r\n		\'uninstall_confirm\' => \'Are you sure you want to delete the preferences table?\',\r\n		);\r\n	\r\n	$lang = array(\r\n		\'en-us\' => $en_us\r\n		);\r\n		\r\n		$language = (isset($lang[$language])) ? $language : \'en-us\';\r\n		$msg = (isset($lang[$language][$what])) ? $lang[$language][$what] : $what;\r\n		return $msg;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// transforms the privs string into a sequence of 0 and 1 for easing the use of checkboxes\r\nfunction bot_decimal_to_binary($privs) {\r\n	\r\n	// takes a string of privs, eliminates white spaces and transforms it in an array  \r\n	$privs_array = explode(\",\",str_replace(\" \", \"\", $privs));\r\n	\r\n	// creates an array of six zero values with key beginning at 1\r\n	$yes_no_array[1] = 0;\r\n	$yes_no_array[2] = 0;\r\n	$yes_no_array[3] = 0;\r\n	$yes_no_array[4] = 0;\r\n	$yes_no_array[5] = 0;\r\n	$yes_no_array[6] = 0;\r\n	\r\n	// for each number checks if it is present as a key in \"$yes_no_array\"\r\n	// in this case changes the corresponding 0 value to 1\r\n	foreach ($privs_array as $key => $value){\r\n	\r\n		if(array_key_exists($value,$yes_no_array)){\r\n			$yes_no_array[$value] = 1;\r\n		}\r\n	}\r\n	// returns a six digit comma separated string made of 1 and 0 (yes or no)\r\n	$new_value=implode(\",\", $yes_no_array);\r\n	\r\n	return $new_value;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// re transforms each privs string taken from the \"bot_privs\" table to a format suitable for the \"txp_permissions\" array\r\n// does the contrary of  \"bot_decimal_to_binary\"\r\nfunction bot_binary_to_decimal($privs) {\r\n\r\n	$bot_privs_array = explode(\",\",$privs);\r\n	$new_array = array();\r\n	foreach ($bot_privs_array as $key => $value) {\r\n		if($value == \"1\"){\r\n			// array keys start from 0 so we add +1 to make it begin with 1 \r\n			$new_array[] = $key + 1;\r\n		}\r\n	}\r\n	\r\n	$new_value = implode(\",\", $new_array);\r\n	return $new_value;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// creates an array of values extracted from the database (the values are in the \"pseudo binary\" format)\r\nfunction bot_get_privs() {\r\n\r\n	$r = safe_rows_start(\'pref_name, pref_value\', \'txp_bot_privs\',\'1=1\');\r\n	if ($r) {\r\n		while ($a = nextRow($r)) {\r\n			$out[$a[\'pref_name\']] = $a[\'pref_value\'];\r\n		}\r\n		return $out;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_check_install() {\r\n\r\n	// Check if the txp_bot_privs table already exists\r\n	if (getThings(\"Show tables like \'\".PFX.\"txp_bot_privs\'\")) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// checks if an upgrade is needed\r\nfunction bot_privs_check_upgrade() {\r\n\r\n	// Check if the \"file.edit\" entry does exist in txp_bot_privs table\r\n	$privs_in_db = bot_get_privs();\r\n	if (array_key_exists(\"file.edit\", $privs_in_db)) {\r\n		return true;\r\n	}\r\n	return false;\r\n}\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_install() {	\r\n	\r\n	// figure out what MySQL version we are using (from _update.php)\r\n	$mysqlversion = mysql_get_server_info();\r\n	$tabletype = (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.(0\\.[2-9]|(1[89]))|(1\\.[2-9])#\',$mysqlversion)) \r\n		? \" ENGINE=MyISAM \"\r\n		: \" TYPE=MyISAM \";\r\n	if (isset($txpcfg[\'dbcharset\']) && (intval($mysqlversion[0]) >= 5 || preg_match(\'#^4\\.[1-9]#\',$mysqlversion))) {\r\n		$tabletype .= \" CHARACTER SET = \". $txpcfg[\'dbcharset\'] .\" \";\r\n	}\r\n	\r\n	// Create the bot_privs table\r\n	$bot_privs_prefs_table = safe_query(\"CREATE TABLE `\".PFX.\"txp_bot_privs` (\r\n		`pref_name` VARCHAR(255) NOT NULL, \r\n		`pref_value` TEXT NOT NULL,\r\n		PRIMARY KEY (`pref_name`)\r\n		) $tabletype\");\r\n	\r\n	// if the table creation succeeds populate with values\r\n	global $txp_permissions, $bot_used_privs;\r\n	// we must replace the numbers coming from the hard coded \"txp_permissions\" array with a 0 and 1 sequence (useful for checkboxes) \r\n	// so we apply the function bot_decimal_to_binary() to each element of the array before inserting the new values in the table\r\n	if ($bot_privs_prefs_table) {		\r\n		foreach ($bot_used_privs as $fieldname => $caption) {\r\n			if (array_key_exists($fieldname, $txp_permissions)) {\r\n				$transformed_value = bot_decimal_to_binary($txp_permissions[$fieldname]);\r\n				safe_insert(\"txp_bot_privs\",\"pref_name=\'\".$fieldname.\"\', pref_value=\'\".$transformed_value.\"\'\");\r\n			}\r\n		}	\r\n	}\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\n// outputs the rows for the html table in the bot_privs_tab\r\nfunction bot_output_rows(){\r\n\r\n	global $bot_used_privs;\r\n	// array of names + values from the db table	\r\n	$prefs = bot_get_privs();\r\n	$rows = \"\";\r\n	foreach ($bot_used_privs as $fieldname => $caption) {\r\n		// privs string corresponding to the argument \"$fieldname\"\r\n		$privs = $prefs[\"$fieldname\"];\r\n		// transforms the string in an array \r\n		$a = explode(\",\",$privs);\r\n		// modify the $fieldname string to avoid dots in posted variables\r\n		$fieldname = str_replace(\".\", \"_dot_\", $fieldname);\r\n		$single_row = tr(td($caption)\r\n		//  elements of $a can be 0 or 1 \r\n		.td(checkbox2($fieldname.\'6\',$a[0]))\r\n		.td(checkbox2($fieldname.\'5\',$a[1]))\r\n		.td(checkbox2($fieldname.\'4\',$a[2]))\r\n		.td(checkbox2($fieldname.\'3\',$a[3]))\r\n		.td(checkbox2($fieldname.\'2\',$a[4]))\r\n		.td(checkbox2($fieldname.\'1\',$a[5]))\r\n		);\r\n		$rows .= $single_row;\r\n	}\r\n	return $rows;\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs_tab($event, $step) {\r\n\r\n	global $txp_permissions, $bot_used_privs;\r\n	pagetop(\'bot_privs \'.gTxt(\'preferences\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\r\n\r\n	if ($step == \'install\') {\r\n		// Install the preferences table.\r\n		bot_privs_install();\r\n	}\r\n\r\n	if ($step == \'upgrade\') {\r\n		// Install the missing field.\r\n		safe_delete(\"txp_bot_privs\",\"1=1\");\r\n		foreach ($bot_used_privs as $fieldname => $caption) {\r\n			if (array_key_exists($fieldname, $txp_permissions)) {\r\n				$transformed_value = bot_decimal_to_binary($txp_permissions[$fieldname]);\r\n				safe_insert(\"txp_bot_privs\",\"pref_name=\'\".$fieldname.\"\', pref_value=\'\".$transformed_value.\"\'\");\r\n			}\r\n		}	\r\n	}\r\n	\r\n	if ($step == \'uninstall\') {\r\n		//remove table\r\n		safe_query(\"DROP TABLE \".PFX.\"txp_bot_privs\");\r\n	}\r\n\r\n	if ($step == \'update\') {\r\n		// for each field name rebuilds the correct value to be inserted in the db \r\n		foreach($bot_used_privs as $fieldname => $caption) {\r\n			// replaces dots with \"_dot_\" (cos php replaces automatically dots with underscores in posted variable names)\r\n			$fieldname = str_replace(\'.\',\'_dot_\',$fieldname); \r\n			$val=(gps($fieldname.\'6\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'5\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'4\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'3\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'2\')?\'1\':\'0\').\',\'\r\n			.(gps($fieldname.\'1\')?\'1\':\'0\');\r\n			// replaces again the \"_dot_\" substring with an actual dot \r\n			$fieldname = str_replace(\'_dot_\',\'.\',$fieldname);\r\n			safe_upsert(\'txp_bot_privs\', \"pref_value = \'$val\'\", \"pref_name = \'$fieldname\'\");\r\n		}\r\n 	}\r\n\r\n	if (bot_privs_check_install()) {\r\n		if (bot_privs_check_upgrade()){			\r\n			// beginning of the privs table\r\n			echo form(startTable(\'list\')\r\n			.tr(td(\'&nbsp;\')\r\n			.td(gTxt(\'publisher\'))\r\n			.td(gTxt(\'managing_editor\'))\r\n			.td(gTxt(\'copy_editor\'))\r\n			.td(gTxt(\'writer\'))\r\n			.td(gTxt(\'freelancer\'))\r\n			.td(gTxt(\'designer\')))\r\n			// html rows generated by \"bot_output_rows()\"\r\n			.bot_output_rows()\r\n			// end of the privs table\r\n			.endTable()\r\n			// update button\r\n			.n.t.\'<div style=\"margin: 20px auto 0; width:40%;\">\'\r\n			.n.eInput(\'bot_privs_tab\')\r\n			.n.sInput(\'update\')\r\n			.n.fInput(\'submit\', \'update\', \'Update\', \'publish\')\r\n			.\'</div>\');\r\n			// uninstall button\r\n			echo n.t.\'<div style=\"margin: 20px auto 0; width:40%;\">\'.\r\n			n.hed(bot_privs_gTxt(\'uninstall\'), \'1\').\r\n			n.t.t.graf(bot_privs_gTxt(\'uninstall_message\')).\r\n			n.n.form(\r\n			n.eInput(\'bot_privs_tab\').\r\n			n.sInput(\'uninstall\').\r\n			n.n.fInput(\'submit\', \'uninstall\', \'Uninstall \', \'smallerbox\'),\"\",\"confirm(\'\".bot_privs_gTxt(\'uninstall_confirm\').\"\')\"\r\n			)\r\n			.\'</div>\'; \r\n		}\r\n		\r\n		else {\r\n			echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n				n.t.t.hed(\'bot_privs upgrade\', \'1\').\r\n				n.graf(bot_privs_gTxt(\'upgrade_message\')).\r\n				n.n.form(\r\n					n.eInput(\'bot_privs_tab\').\r\n					n.sInput(\'upgrade\').\r\n					n.n.fInput(\'submit\', \'upgrade\', \'Upgrade \', \'publish\')\r\n					).\r\n				\'</div>\';\r\n		}\r\n	} \r\n	\r\n	else {\r\n		echo n.t.\'<div style=\"margin: auto; width:40%;\">\'.\r\n			n.t.t.hed(\'bot_privs \'.gTxt(\'Preferences\'), \'1\').\r\n			n.graf(bot_privs_gTxt(\'install_message\')).\r\n			n.n.form(\r\n				n.eInput(\'bot_privs_tab\').\r\n				n.sInput(\'install\').\r\n				n.n.fInput(\'submit\', \'install\', \'Install \', \'publish\')\r\n				).\r\n			\'</div>\';\r\n	}\r\n}\r\n\r\n\r\n//===========================================\r\n\r\n\r\nfunction bot_privs() {\r\n\r\n	// replaces the default array of permissions defined by txp with the array of permissions stored in the \"txp_bot_privs\" table \r\n	global $txp_permissions;\r\n\r\n	if (bot_privs_check_install()) {\r\n		$prefs_in_db = bot_get_privs();\r\n		// overwrite existing privs\r\n		foreach($prefs_in_db as $fieldname => $privs) {\r\n			//extract the privs values and inserts them in \"$txp_permissions\" array\r\n			$privs = bot_binary_to_decimal($privs);\r\n			$txp_permissions [$fieldname]= $privs; \r\n		}\r\n	}\r\n}\r\n\r\nbot_privs();\r\n', '3a39d79e889d8fe8fd7d32f4b49f82b3', '', '', 3, 5, 0),
('bot_hide_per_section', 0, 'redbot', '', '0.1', ' ', '<h2>bot_hide_per_section</h2><p>This plugin allows to hide various elements in the &quot;write&quot; tab based on the selection in the sections list. </p><p>The plugin was born as a barebone substitute for sed_section_fields. <br />It&#39;s raw, lacks an interface and - though easily - you must configure it directly in the plugin code but, unlike sed_section_fields, it allows to hide elements other than custom fields such as excerpt, body, keywords and so on. Virtually any html form with a name attribute.</p><p>Elements that can be hidden: </p><p>All custom fields (should work beyond 10), keywords, category1, category2, article image, body, excerpt, url title, override form, article markup, excerpt markup, status, category1, category2 plus others.</p><p>Anoter extra feature is you can choose a jquery &#39;slide&#39; effect (disabled by default). </p><p>Like sed_section_fields can hide some sections in the sections list.</p><p>To configure just&nbsp; press &#39;edit&#39; in the &quot;plugins&quot; tab and modify the commented lines following the (very easy) instructions.</p>', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_hide_per_section\',\'article\');\r\n}\r\n\r\nglobal $bot_hide_per_section, $bot_hidden_sections, $bot_slide;\r\n\r\n########################################\r\n\r\n# C O N F I G U R A T I O N \r\n/* Uncomment the sample lines below,  add  one for every section  you wish to customize  and change values accordingly.\r\nUse exactly the values listed below (case sensitive - just copy and paste). For custom fields change the number as you wish (can also be > 10). Don\'t use space after comma.\r\nPossible values : \r\ncustom_1 | Keywords | Category1 | Category2 | Image | Body | Excerpt | url_title | override_form | textile_body | textile_excerpt | Status | Category1 | Category2 | Annotate | AnnotateInvite */\r\n\r\n# $bot_hide_per_section[\"my_section_x\"] = \"custom_1,custom_2,Image\";\r\n# $bot_hide_per_section[\"my_section_y\"] = \"Keywords,url_title,Category1,Excerpt\";\r\n\r\n// hidden sections in section list. Uncomment and replace.\r\n# $bot_hidden_sections=\"a-section-to-hide,another_section,a_last_one\"; \r\n\r\n// Set to 1 for a \'slide\' effect.\r\n$bot_slide = 0;\r\n\r\n########################################\r\n\r\n\r\nfunction bot_fields_rows(){ // script rows dealing with elements to hide on section change AND on page load\r\n\r\n	global $bot_hide_per_section, $bot_slide;\r\n	($bot_slide == 0) ? $effect = \'hide()\' : $effect = \'slideUp(\"slow\")\';\r\n	foreach ($bot_hide_per_section as $section => $fields_list) {\r\n		echo n.\'if (value==\"\'.$section.\'\"){\'.n;\r\n		$fields = explode(\",\", $fields_list);\r\n		foreach ($fields as $value ){\r\n			echo \'		$(\"*[name*=\'.$value.\']\").parent().\'.$effect.\';\'.n;\r\n		}\r\n		echo \'}\'.n.n;\r\n	}\r\n}\r\n\r\n\r\nfunction bot_fields_restore_rows(){ // script rows to restore every previously hidden element on section change\r\n\r\n	global $bot_hide_per_section;\r\n	foreach ($bot_hide_per_section as $section => $fields_list) {\r\n		$fields = explode(\",\", $fields_list);\r\n		foreach ($fields as $value ){\r\n			$out[] = $value;		\r\n		}\r\n	}\r\n	$out = array_unique($out);\r\n	foreach ($out as $value) {\r\n	echo \'		$(\"*[name*=\'.$value.\']\").parent().show();\'.n;\r\n	}	\r\n}\r\n\r\n\r\nfunction bot_hide_sections(){ // script rows dealing with hidden sections in section list\r\n\r\n	global $bot_hidden_sections;\r\n	if ($bot_hidden_sections) {\r\n		$sections = explode(\",\", $bot_hidden_sections);\r\n		foreach ($sections as $value) {\r\n			echo \'		$(\"select#section.list option:contains(\'.$value.\'):not(:selected)\").remove();\'.n;\r\n		}\r\n	}\r\n}\r\n\r\n\r\nfunction bot_hide_per_section(){ //  builds the script\r\n\r\n	echo\r\n		\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n		\'	$(document).ready(function() {\'.n.\r\n		\'		$(\"#advanced\").show();\'.n.\r\n		\'		var value = $(\"select#section.list option:selected\").val();\';\r\n				bot_fields_rows();\r\n				bot_hide_sections();\r\n				\r\n	echo\r\n		\'		$(\"select#section.list\").change(function(){\'.n;\r\n					bot_fields_restore_rows();\r\n	echo				\r\n		\'			var value = $(\"select#section.list\").val();\';\r\n					bot_fields_rows();\r\n	echo				\r\n		\'			}\'.n.\r\n		\'		);\'.n.				\r\n		\'	});\'.n.\r\n		\'</script>\'; \r\n}', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_hide_per_section\',\'article\');\r\n}\r\n\r\nglobal $bot_hide_per_section, $bot_hidden_sections, $bot_slide;\r\n\r\n########################################\r\n\r\n# C O N F I G U R A T I O N \r\n/* Uncomment the sample lines below,  add  one for every section  you wish to customize  and change values accordingly.\r\nUse exactly the values listed below (case sensitive - just copy and paste). For custom fields change the number as you wish (can also be > 10). Don\'t use space after comma.\r\nPossible values : \r\ncustom_1 | Keywords | Category1 | Category2 | Image | Body | Excerpt | url_title | override_form | textile_body | textile_excerpt | Status | Category1 | Category2 | Annotate | AnnotateInvite */\r\n\r\n# $bot_hide_per_section[\"my_section_x\"] = \"custom_1,custom_2,Image\";\r\n# $bot_hide_per_section[\"my_section_y\"] = \"Keywords,url_title,Category1,Excerpt\";\r\n\r\n// hidden sections in section list. Uncomment and replace.\r\n# $bot_hidden_sections=\"a-section-to-hide,another_section,a_last_one\"; \r\n\r\n// Set to 1 for a \'slide\' effect.\r\n$bot_slide = 0;\r\n\r\n########################################\r\n\r\n\r\nfunction bot_fields_rows(){ // script rows dealing with elements to hide on section change AND on page load\r\n\r\n	global $bot_hide_per_section, $bot_slide;\r\n	($bot_slide == 0) ? $effect = \'hide()\' : $effect = \'slideUp(\"slow\")\';\r\n	foreach ($bot_hide_per_section as $section => $fields_list) {\r\n		echo n.\'if (value==\"\'.$section.\'\"){\'.n;\r\n		$fields = explode(\",\", $fields_list);\r\n		foreach ($fields as $value ){\r\n			echo \'		$(\"*[name*=\'.$value.\']\").parent().\'.$effect.\';\'.n;\r\n		}\r\n		echo \'}\'.n.n;\r\n	}\r\n}\r\n\r\n\r\nfunction bot_fields_restore_rows(){ // script rows to restore every previously hidden element on section change\r\n\r\n	global $bot_hide_per_section;\r\n	foreach ($bot_hide_per_section as $section => $fields_list) {\r\n		$fields = explode(\",\", $fields_list);\r\n		foreach ($fields as $value ){\r\n			$out[] = $value;		\r\n		}\r\n	}\r\n	$out = array_unique($out);\r\n	foreach ($out as $value) {\r\n	echo \'		$(\"*[name*=\'.$value.\']\").parent().show();\'.n;\r\n	}	\r\n}\r\n\r\n\r\nfunction bot_hide_sections(){ // script rows dealing with hidden sections in section list\r\n\r\n	global $bot_hidden_sections;\r\n	if ($bot_hidden_sections) {\r\n		$sections = explode(\",\", $bot_hidden_sections);\r\n		foreach ($sections as $value) {\r\n			echo \'		$(\"select#section.list option:contains(\'.$value.\'):not(:selected)\").remove();\'.n;\r\n		}\r\n	}\r\n}\r\n\r\n\r\nfunction bot_hide_per_section(){ //  builds the script\r\n\r\n	echo\r\n		\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n		\'	$(document).ready(function() {\'.n.\r\n		\'		$(\"#advanced\").show();\'.n.\r\n		\'		var value = $(\"select#section.list option:selected\").val();\';\r\n				bot_fields_rows();\r\n				bot_hide_sections();\r\n				\r\n	echo\r\n		\'		$(\"select#section.list\").change(function(){\'.n;\r\n					bot_fields_restore_rows();\r\n	echo				\r\n		\'			var value = $(\"select#section.list\").val();\';\r\n					bot_fields_rows();\r\n	echo				\r\n		\'			}\'.n.\r\n		\'		);\'.n.				\r\n		\'	});\'.n.\r\n		\'</script>\'; \r\n}', '8655c9ea57b7bc4b75d805c64b802aa4', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_file_upload', 0, 'redbot', 'http://www.redbot.it/txp', '0.5.1', 'Upload and edit files from the write tab', '<h2>bot | file upload</h2>\n\n	<h3>Introduction</h3>\n\n	<p>When you need to associate files with an article you’ll probably do the same as me: create a custom field called ‘files’ and manually insert file ids in it. Then you call a file list in your artcle with something like:</p>\n\n    <p><code>&lt;txp:file_download_list id=&#39;&lt;txp:custom_field name=\'\'your_files_cf\'\' /&gt;&#39;  wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;<br />\n    </code><code>&lt;txp:file_download_name  title=&quot;1&quot; /&gt;<br />\n    </code><code>&lt;/txp:file_download_list&gt;</code> </p>\n    <p>(or use upm_file for txp versions pre 4.2.0).</p>\n\n	<p>This plugin doesen’t change this internally, it just operates at a visual level hiding the custom field and replacing it with an interface which lets you also upload and edit files from within the “write” tab – all without even touching the keyboard.</p>\n\n    <p>Another feature let\'s you sort files in the write tab.  To list the files  in that specific order in the frontend you just need to use the usual &lt;txp:file_download_list /&gt; tag but with an hackish \'sort\' attribute (this is not really a hack, it\'s only an ...ehm... \'undocumented feature\'). Your code would then be:</p>\n    <p><code>&lt;txp:file_download_list id=&#39;&lt;txp:custom_field name=\'\'files\'\' /&gt;&#39; sort=\'field(id, &lt;txp:custom_field name=\'\'your_files_cf_name\'\' /&gt;)\'  wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;<br />\n    </code><code>&lt;txp:file_download_name title=&quot;1&quot; /&gt;<br />\n    </code><code>&lt;/txp:file_download_list&gt;</code></p>\n    <p>Of course you must replace \'your_files_cf_name\' with your real custom field name. </p>\n    <h3>Installation</h3>\n\n	<p>Just install the plugin and\ncreate a custom field for your files. Default is ‘files’ otherwise change it the configuration. </p>\n\n	<h3>Configuration</h3>\n\n	<p>To (optionally) configure the plugin head to extensions -&gt; plugins -&gt; bot_file_upload -&gt; edit.<br />\n    At the beginning you’ll see a section called ‘Configuration’. The first line is where you set your custom field name in case it’s different from the default ‘files’. The following lines will allow to change some text and style various items (read the comments for details).</p>\n	<p>Right after the \'Configuration\' you\'ll find a function called \'bot_file_css\'. Don\'t be scared, its mainly  css. You can modify it as you like.</p>\n    <p>If you want to use some customized icons change the css accordingly. In this case - for a minimal performance  enhancement - you can also delete the row with the \'bot_file_img\' function call at the beginning (around line 8) and the whole bot_file_img function altogether (from around line 160 to 190) </p>', '//<?\r\n\r\n\r\n\r\n	if(@txpinterface == \'admin\') {\r\n		register_callback(\'bot_file_css\',\'admin_side\',\'head_end\');\r\n		register_callback(\'bot_file_upload\',\'article\');\r\n 		bot_file_img();\r\n	}\r\n\r\n\r\n/**\r\n	Global preferences - Configuration. Read comments and modify to your needs.\r\n*/\r\n\r\n	function bot_fu_prefs() {\r\n		return\r\n			array(\r\n				\'bot_cf_name\' => gTxt(\'files\'), // name of used custom field\r\n				\'bot_add_files_text\' => gTxt(\'Add files\'), // text for \"add files\" link\r\n				\'bot_add_file_text\' => gTxt(\'Add\'), // text for \"add file\" checkbox\r\n				\'bot_file_delete_text\' => gTxt(\'Delete file\'), // text and title for \"delete file\" button\r\n				\'bot_file_moveup_text\' => gTxt(\'Move up\'), // text and title for \"delete file\" button\r\n				\'bot_file_movedown_text\' => gTxt(\'Move down\'), // text and title for \"delete file\" button\r\n				\'bot_fu_row_bg\' => \'#eaeaea\', // selected row background\r\n				\'bot_fu_save_text\' => gTxt(\'save\'), // text for \"Save\" button\"\r\n				\'bot_fu_cancel_text\' => gTxt(\'cancel\'), // text for \"Cancel\" button\"\r\n				\'bot_filelist_position\' => \'.title\', // css selector for the item the file list is put after\r\n			);\r\n	}\r\n\r\n\r\n\r\n\r\n/**\r\n	CSS for the file upload. You are free to modify these\r\n	to match your own backend theme.\r\n*/\r\n\r\n	function bot_file_css() {\r\n\r\n		global $event;\r\n		if($event != \'article\') { // Outputs css only in \'write\' tab.\r\n			return;\r\n		}\r\n\r\n		echo \'<style type=\"text/css\">\r\n				.bot_file_delete,\r\n				.bot_file_moveup,\r\n				.bot_file_movedown {\r\n					background: url(\"\'.hu.\'textpattern/?bot_file_img=delete\") top center no-repeat;\r\n     				float:right;\r\n					text-indent: -99999px;\r\n					width:20px;\r\n					height:20px;\r\n				}\r\n				.bot_file_moveup {\r\n					background:url(\"\'.hu.\'textpattern/txp_img/arrowupdn.gif\") center 2px  no-repeat;\r\n					width:10px;\r\n				}\r\n				.bot_file_movedown {\r\n					background:url(\"\'.hu.\'textpattern/txp_img/arrowupdn.gif\") center -18px  no-repeat;\r\n					width:10px;\r\n				}\r\n				#loading {\r\n					background:#000 url(\"\'.hu.\'textpattern/?bot_file_img=loading\") center center no-repeat;\r\n					text-indent:-99999px;\r\n					width:50px;\r\n					height:50px;\r\n					padding:10px;\r\n					position:fixed;\r\n					left:50%;\r\n					margin-left:-30px;\r\n					top:50%;\r\n					margin-top:-30px;\r\n					z-index:9999;\r\n					-moz-border-radius:10px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n				}\r\n				#bot_fu_fade {\r\n					display: none;\r\n					background: #000;\r\n					position: fixed; left: 0; top: 0;\r\n					width: 100%; height: 100%;\r\n					opacity: .80;\r\n					z-index: 999;\r\n				}\r\n				/*-- minimal styling for ie6--*/\r\n				*html #bot_fu_fade,\r\n				*html #loading,\r\n				*html #bot_fu_iframe_container {\r\n					position: absolute;\r\n				}\r\n				*html #bot_fu_iframe_container {\r\n					border: solid #ccc thick;\r\n				}\r\n\r\n				#bot_fu_iframe {\r\n					width:100%;\r\n				}\r\n				#bot_fu_iframe_container {\r\n				    /* height and margin-top are set by js */\r\n				    display:none;\r\n				    position:fixed;\r\n				    left:50%;\r\n				    z-index: 99999;\r\n					overflow: hidden;\r\n					background: #fff;\r\n					width:80%;\r\n					padding:0 2%;\r\n					margin-left: -42%;\r\n					text-align:right;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n					-moz-box-shadow: 0 0 20px #000;\r\n					-webkit-box-shadow: 0 0 20px #000;\r\n					box-shadow: 0 0 20px #000;\r\n				}\r\n				#save,\r\n				#cancel {\r\n					background:#666;\r\n					color:#fff;\r\n					font-weight:bold;\r\n					padding:5px 10px;\r\n					margin-left:5px;\r\n					display:inline-block;\r\n					cursor:pointer;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:5px;\r\n					border-radius:5px;\r\n				}\r\n				#save:hover,\r\n				#cancel:hover {\r\n					background:#333;\r\n					text-decoration:none;\r\n				}\r\n				#bot_file_list {\r\n				    margin:0;\r\n				    padding:0;\r\n					border-top:solid #ddd 1px;\r\n				}\r\n				#bot_file_list li {\r\n				    color:#ccc;\r\n					overflow:hidden;\r\n					margin:0;\r\n					padding: 2px 0 0;\r\n					border-bottom:dotted  #ddd 1px;\r\n				}\r\n				#bot_add_file {\r\n				    display:block;\r\n					background: url(\"\'.hu.\'textpattern/?bot_file_img=add\") right center  no-repeat;\r\n					padding: 0 0 10px 0;\r\n				}\r\n				\r\n			</style>\';\r\n	}\r\n\r\n\r\n\r\n\r\n/**\r\n	Generates and stores the files required by the design.\r\n	file URI is: http://example.com/textpattern/?bot_file_img=delete|edit|file\r\n*/\r\n\r\n	function bot_file_img() {\r\n\r\n 	global $event;\r\n		if($event != \'article\') { // Generates files only in \'write\' tab.\r\n			return;\r\n		}\r\n		$file = gps(\'bot_file_img\');\r\n\r\n		if(!$file)\r\n			return;\r\n\r\n		switch($file) {\r\n			case \'delete\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABEElEQVR42mL4//8/Ayn4zJkzXMh8gABiYkACZ8+elQdibQYcACqXCqTtYGIAAcQIMgWmGUg9gIrrGBsbX8Wi+QqUuxKIG4FqrgMEENgANM0M6IagaYaBAiCeDBBAMAM4gJyvQMyEbgiURtf8F4g5gRb8BgggZC+ADPkCxMwM+AFIMw9Q8w8QByCA4DZCBXigCojSDAIAAYTiZCRD/mHR/A9dMwgABBATFoXKOMSZoHIoACCAmPBEFTZwBT2dAAQQEwHNf7F4B8UQgABiIqAZFB7cWAIWbghAAMFcIIYrtPHEDlgPQAAxQUN/P5BywhXaWAxxguphAAgg9JzmCMQceHIiB0gNshhAAMFTIrkAIMAA7k7JUu0YckQAAAAASUVORK5CYII=\';\r\n			break;\r\n			case \'add\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAGUExURWZmZv///3NLWagAAAACdFJOU/8A5bcwSgAAAHBJREFUeNpiYMQCAAKIAZsgQABhFQQIIKyCAAEEF2QAAhgbIICwCgIEEFZBgADCKggQQFgFAQKIAcpHAKAAQABhFQQIIKyCAAGE1UyAAMIqCBBAWAUBAgirIEAAYRUECCCsoQQQQFgFAQIIqyBAgAEACewBP0Pv320AAAAASUVORK5CYII=\';\r\n			break;\r\n			default:\r\n				$file = \'R0lGODlhHwAfAPUAAAAAAP///xYWFiwsLEJCQlBQUFxcXCIiIkZGRmRkZBoaGiYmJlRUVF5eXk5OTjIyMggICFZWVioqKhgYGMjIyNjY2K6urjo6Oo6OjmxsbKioqAQEBJaWlri4uDg4OAYGBra2tszMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA4BiwSQexKh0eEAkrldAZbvlOD5TqYKALWu5XIwnPFwwymY0GsRgAxrwuJwbCi8aAHlYZ3sVdwtRCm8JgVgODwoQAAIXGRpojQwKRGSDCRESYRsGHYZlBFR5AJt2a3kHQlZlERN2QxMRcAiTeaG2QxJ5RnAOv1EOcEdwUMZDD3BIcKzNq3BJcJLUABBwStrNBtjf3GUGBdLfCtadWMzUz6cDxN/IZQMCvdTBcAIAsli0jOHSJeSAqmlhNr0awo7RJ19TJORqdAXVEEVZyjyKtE3Bg3oZE2iK8oeiKkFZGiCaggelSTiA2LhxiZLBSjZjBL2siNBOFQ84LxHA+mYEiRJzBO7ZCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82YAIQxRCm14Ww4PChAAEAoPDlsAFRUgHkRiZAkREmoSEXiVlRgfQgeBaXRpo6MOQlZbERN0Qx4drRUcAAJmnrVDBrkVDwNjr8BDGxq5Z2MPyUQZuRgFY6rRABe5FgZjjdm8uRTh2d5b4NkQY0zX5QpjTc/lD2NOx+WSW0++2RJmUGJhmZVsQqgtCE6lqpXGjBchmt50+hQKEAEiht5gUcTIESR9GhlgE9IH0BiTkxrMmWIHDkose9SwcQlHDsOIk9ygiVbl5JgMLuV4HUmypMkTOkEAACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2LQV3t4UBcvcF9/eFpdYxdgZ5hUYA73YGxruCbVjt78G7hXFqlhY/fLQwR0HIQdGuUrTz5eQdIc0cfIEwByGD0MKvcGSaFGjR8GyeAPhIUofQGNQSgrB4IsdOCqx7FHDBiYcOQshYjKDxliVDpRjunCjdSTJkiZP6AQBACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2I3WBcvczltNxNzIW0693MFYT7bTumNQqlisv7BjswAHo64egFdQAbj0RtOXDQY6VAAUakihN1gSLaJ1IYOGChgXXqEUpQ9ASRlDYhT0xQ4cACJDhqDD5mRKjCAYuArjBmVKDP9+VRljMyMHDwcfuBlBooSCBQwJiqkJAgAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA8BQIAwExKh0eEAkrlcA9oo4TKcKwharHScIiu9wwTBn3QnGQg1owBNld+O72N/zZnVzRApteFsODwoQABAKDw5bZQxpQ2JkCRESahIRh1gEVIGVamlmXgBWWxETdEMTnlsIAAJmm65DEmZGYw64UZFbR2MPv0QPY0hjpMYKY0ljjMZCEGNK09MG0diN1gXL3M5bTcTcyFtOvdzBWE+207pjUKpYrL+wY7MAB4EerqZjUAG4lKVCBwMbvnT6dCXUkEIFK0jUkOECFEeQJF2hFKUPAIkgQwIaI+hLiJAoR27Zo4YBCJQgVW4cpMYDBpgVZKL59cEBhw+U+QROQ4bBAoUlTZ7QCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82Z1c0QKbXhbDg8KEAAQCg8OW2UMaUNiZAkREmoSEYdYBFSBlWppZl4AVlsRE3RDE55bCAACZpuuQxJmRmMOuFGRW0djD79ED2NIY6TGCmNJY4zGQhBjStPTFBXb21DY1VsGFtzbF9gAzlsFGOQVGefIW2LtGhvYwVgDD+0V17+6Y6BwaNfBwy9YY2YBcMAPnStTY1B9YMdNiyZOngCFGuIBxDZAiRY1eoTvE6UoDEIAGrNSUoNBUuzAaYlljxo2M+HIeXiJpRsRNMaq+JSFCpsRJEqYOPH2JQgAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfjywjlzX9jdXNEHiAVFX8ODwoQABAKDw5bZQxpQh8YiIhaERJqEhF4WwRDDpubAJdqaWZeAByoFR0edEMTolsIAA+yFUq2QxJmAgmyGhvBRJNbA5qoGcpED2MEFrIX0kMKYwUUslDaj2PA4soGY47iEOQFY6vS3FtNYw/m1KQDYw7mzFhPZj5JGzYGipUtESYowzVmF4ADgOCBCZTgFQAxZBJ4AiXqT6ltbUZhWdToUSR/Ii1FWbDnDkUyDQhJsQPn5ZU9atjUhCPHVhgTNy/RSKsiqKFFbUaQKGHiJNyXIAAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEh8JDAWCsBQIAwExKhU+HFwKlgsIMHlIg7TqQeTLW+7XYIiPGSAymY0mrFgA0LwuLzbCC/6eVlnewkADXVECgxcAGUaGRdQEAoPDmhnDGtDBJcVHQYbYRIRhWgEQwd7AB52AGt7YAAIchETrUITpGgIAAJ7ErdDEnsCA3IOwUSWaAOcaA/JQ0amBXKa0QpyBQZyENFCEHIG39HcaN7f4WhM1uTZaE1y0N/TacZoyN/LXU+/0cNyoMxCUytYLjm8AKSS46rVKzmxADhjlCACMFGkBiU4NUQRxS4OHijwNqnSJS6ZovzRyJAQo0NhGrgs5bIPmwWLCLHsQsfhxBWTe9QkOzCwC8sv5Ho127akyRM7QQAAOwAAAAAAAAAAAA==\';\r\n		}\r\n\r\n		ob_start();\r\n		ob_end_clean();\r\n		header(\'Content-type: file/gif\');\r\n		echo base64_decode($file);\r\n		exit();\r\n	}\r\n\r\n\r\n\r\n\r\n\r\n	function bot_generate_list()\r\n	{\r\n		global $prefs, $step;\r\n		extract(bot_fu_prefs());\r\n	    $cf_id = str_replace(\"_set\", \"\", array_search($bot_cf_name, $prefs)); // fetch cf internal name (custom_1 etc.)\r\n		$article_id =  gps(\'ID\'); // Fetch article id from $_GET, if any, or sets it to \'\'\r\n\r\n	    if ($step == \'edit\' && $article_id) {\r\n	        $cf_value = fetch($cf_id, \'textpattern\', \'ID\', $article_id);\r\n		}\r\n		elseif($step == \'create\' || $step == \'edit\'){\r\n			$cf_value = gps($cf_id);\r\n		}\r\n	    else {\r\n	    	$cf_value = \'\';\r\n	    }\r\n\r\n	    // proceed only if editing a saved article, a cf for files is set and has a value\r\n	    if ($cf_id && $cf_value) {\r\n	        $arr = safe_rows_start(\'id, filename, title\', \'txp_file\', \'id IN(\'.$cf_value.\') ORDER BY field(id, \'.$cf_value.\')\');\r\n\r\n	        $files = array();\r\n	        if ($arr) {\r\n	            while ($a = nextRow($arr)) {\r\n	                $id = $a[\'id\'];\r\n	                $filename = $a[\'filename\'];\r\n	                $title = $a[\'title\'];\r\n	                $title_or_name = $title == \'\'? $filename : $title;\r\n	                $files[$id] = $title_or_name;\r\n	            }\r\n	            $str = \'\';\r\n	            foreach ($files as $fileid => $title_or_name) {\r\n	                $str .=\r\n					\'<li class=\\\"id\'.$fileid.\'\\\">\'.\r\n						\'<a class=\\\"bot_file_delete\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_delete_text.\'\\\"> [\'.$bot_file_delete_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_moveup\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_moveup_text.\'\\\"> [\'.$bot_file_moveup_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_movedown\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_movedown_text.\'\\\"> [\'.$bot_file_movedown_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_edit\\\" href=\\\"#\\\" title=\\\"\\\">\'.$title_or_name.\'</a>\'.\r\n					\'</li>\';\r\n	            }\r\n	            return $str;\r\n	        }\r\n	    }\r\n	}\r\n\r\n\r\n\r\n\r\n/*\r\n	generate js code\r\n*/\r\n\r\n    function bot_file_upload()\r\n    {\r\n		global $event, $prefs;\r\n\r\n		if($event != \'article\') {\r\n			return;\r\n		}\r\n\r\n		extract(bot_fu_prefs());\r\n 		$bot_saved_files = bot_generate_list();\r\n\r\n		$bot_cf_id = str_replace(\"_set\", \"\", array_search($bot_cf_name, $prefs)); // fetch cf internal name (custom_1 etc.)\r\n\r\n		echo <<<JS_CODE\r\n		<script type=\"text/javascript\">\r\n		    $(document).ready(function() {\r\n\r\n		        // modify \'write\' tab\r\n		        $(\'input[name=\"$bot_cf_id\"]\').parent().append(\r\n						\'<ul id=\"bot_file_list\"></ul>\' +\r\n						\'<a id=\"bot_add_file\" href=\"#\">$bot_add_files_text</a>\'\r\n					); // creates \'file list\' ul and \'add\' button\r\n		        $(\'input[name=\"$bot_cf_id\"]\').parent().insertAfter(\"$bot_filelist_position\"); // put after set field\r\n		        $(\"#bot_file_list\").append(\"$bot_saved_files\"); // if cf is already filled inserts files\r\n 		        $(\'input[name=\"$bot_cf_id\"]\').parent().children(\"input\").hide(); // hide cf\r\n\r\n		        // when clicking \'add\' or \'edit\' links\r\n		        $(\"#bot_add_file, #bot_file_list .bot_file_edit\").live(\"click\",function(){\r\n\r\n					// set variables\r\n        			var cf = $(\'input[name=\"$bot_cf_id\"]\');\r\n	                var ids = cf.val();\r\n                    var values = ids.split(\",\"); // existant ids array\r\n\r\n					// set the url the iframe must point to\r\n		            if ($(this).attr(\"id\")) {\r\n		            	var iframeUrl = \"index.php?event=file\";\r\n		            }\r\n		            else {\r\n						var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n		            	var iframeUrl = \"index.php?event=file&step=file_edit&id=\"+fileId;\r\n		            }\r\n		            \r\n					// Fade in Background\r\n				    $(\'body\').append(\'<div id=\"bot_fu_fade\"></div>\'); //Add the fade layer to bottom of the body tag.\r\n				    $(\'#bot_fu_fade\').show().css({\'filter\' : \'alpha(opacity=80)\'}); // show the fade layer -  fix the IE Bug on fading transparencies\r\n\r\n		            // creates an hidden iframe\r\n		            $(\"body\").append(\r\n						\'<div id=\"bot_fu_iframe_container\">\' +\r\n							\'<iframe id=\"bot_fu_iframe\" src =\"\'+iframeUrl+\'\" frameborder=\"0\"></iframe>\' +\r\n						\'</div>\');\r\n\r\n            		// creates \'cancel\' and \'save\' buttons\r\n		            $(\"#bot_fu_iframe\").after(\"<p><a id=\\\"save\\\">$bot_fu_save_text</a><a id=\\\"cancel\\\">$bot_fu_cancel_text</a></p>\");\r\n\r\n		            // backup filelist for undo\r\n		            var oldFileList = $(\"#bot_file_list li\").clone();\r\n		            \r\n        			// Calculates heights based on window size for vertical centering.\r\n					var windowHeight = $(window).height();\r\n					var containerHeight = (windowHeight * 80) / 100;\r\n					var containerTopMargin = (windowHeight - containerHeight) / 2;\r\n					var iframeHeight = (containerHeight * 78) / 100;\r\n					var loadingTopMargin = (windowHeight / 2) - 30;\r\n					$(\"#bot_fu_iframe_container\").height(containerHeight).css(\"top\", containerTopMargin);\r\n					$(\"#bot_fu_iframe\").height(iframeHeight).css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"#save\").css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"body\").append(\'<p id=\"loading\">loading</p>\');\r\n\r\n		            // iframe interactions\r\n		            $(\"#bot_fu_iframe\").load(function(){\r\n		                var iframe = $(\"#bot_fu_iframe\").contents();\r\n		                // hide in iframe\r\n		                iframe.find(\".txp-header, .txp-footer, #pagetop, #end_page, #nav, #messagepane, #txp-head\").hide();\r\n		                iframe.find(\".multi-edit\").hide();\r\n		                iframe.find(\".existing-file\").hide();\r\n		                iframe.find(\"#file-search option[value=author]\").remove();\r\n		                iframe.find(\"#file-search option[value=id]\").remove();\r\n		                iframe.find(\".detail-toggle\").hide();\r\n		                iframe.find(\".description\").show(); \r\n		                iframe.find(\"#files_form .category\").hide();\r\n		                iframe.find(\"#files_form .id .files_detail\").hide();\r\n		                iframe.find(\"#files_form .tag-build\").hide();\r\n		                iframe.find(\"#files_form .status\").hide();\r\n		                iframe.find(\"#files_form .condition\").hide();\r\n		                iframe.find(\"#files_form .downloads\").hide();\r\n		                iframe.find(\"#files_form .author\").hide();\r\n		                iframe.find(\"#files_form .multi-edit\").hide();\r\n		                // hide in iframe - edit pane\r\n		                iframe.find(\"#file-status\").hide();\r\n		                iframe.find(\"#file-created\").hide();\r\n		                iframe.find(\"#file_details.edit-form .publish\").parent().hide(); // Hides original save button\r\n						var linkText = iframe.find(\".file-info .name a\").text(); // eliminate download link maintaining text\r\n		                iframe.find(\".file-info .name a\").replaceWith(linkText);\r\n\r\n		                // add \'add\' checkbox\r\n						iframe.find(\'.txp-list thead tr\').append(\'<th>$bot_add_file_text</th>\');\r\n						iframe.find(\'.txp-list tbody tr\').append(\r\n							\'<td class=\"add\">\'+\r\n								\'<input type=\"checkbox\" name=\"bot_file_checkbox[]\" class=\"bot_file_checkbox\" />\'+\r\n							\'</td>\');\r\n\r\n		                // check checkboxes depending on already set cfs\r\n	                    for(var i = 0 ; i < values.length ; i++) { \r\n	                        fileId = values[i];\r\n	                        iframe.find(\".txp-list .multi-edit input\").filter(function(){\r\n								return $(this).val() == fileId\r\n							;}).parents(\"tr\").find(\'input.bot_file_checkbox\').attr(\'checked\', true).parents(\"tr\").css(\"background\",\"$bot_fu_row_bg\");\r\n						}\r\n\r\n		                // everything is done, now loader can be hidden and iframe can be shown\r\n		                $(\"#loading\").hide();\r\n		                $(\"#bot_fu_iframe_container\").show();\r\n\r\n		                // on click\r\n		                $(\"#bot_fu_iframe\").contents().find(\".bot_file_checkbox[type=checkbox]\").click(function(){\r\n		                    var fileId = $(this).parents(\"tr\").find(\".id a:first\").text();\r\n		                    var fileTitleOrName = $(this).parents(\"tr\").find(\".title\").html();\r\n                      		if (fileTitleOrName == \'&nbsp;\' || fileTitleOrName.charCodeAt() == 160) { // if title contains &nbsp; use file name. Second part is for stupid opera bug\r\n		                        var fileTitleOrName = $(this).parents(\"tr\").find(\".name\").text();\r\n		                    }\r\n		                    if (this.checked){\r\n		                        $(this).parents(\"tr\").css(\"background\",\"$bot_fu_row_bg\"); // change row background\r\n        						values.push(fileId);\r\n        						$(\"#bot_file_list\").append(\r\n         							\'<li class=\"id\' + fileId + \'\">\' +\r\n										\'<a class=\"bot_file_delete\" href=\"#\" title=\"$bot_file_delete_text\"> $bot_file_delete_text</a> \' +\r\n										\'<a class=\"bot_file_moveup\" href=\"#\" title=\"$bot_file_moveup_text\"> $bot_file_moveup_text</a> \' +\r\n										\'<a class=\"bot_file_movedown\" href=\"#\" title=\"$bot_file_movedown_text\"> $bot_file_movedown_text</a> \' +\r\n										\'<a class=\"bot_file_edit\" href=\"#\">\'+fileTitleOrName+\'</a>\' +\r\n									\'</li>\'\r\n								);\r\n		                    }\r\n		                    else {\r\n		                        $(this).parents(\"tr\").css(\"background\",\"none\"); // restore background on uncheck\r\n		                        var arrayIndex = $.inArray(fileId, values); // checks this id index in array\r\n		                        values.splice(arrayIndex,1); // eliminates this id from array\r\n		                        $(\"#bot_file_list .bot_file_edit\").each(function(){\r\n		                            if($(this).text() == fileTitleOrName){\r\n		                                $(this).parent().remove();\r\n		                            }\r\n		                        });\r\n		                    }\r\n		                });\r\n\r\n		                // check again background for selected rows\r\n		                iframe.find(\"#list tr:has(input.bot_file_checkbox:checked)\").css(\"background\",\"$bot_fu_row_bg\");\r\n\r\n		                // avoid FOUC when clicking links and submits\r\n		                iframe.find(\"a, input.smallerbox, #file_details.edit-form .publish\").click(function(){\r\n		                     $(\"#bot_fu_iframe_container\").hide();\r\n		                     $(\"#loading\").show();\r\n		                });\r\n\r\n		                // when editing a file\r\n		                iframe.find(\"#file_details.edit-form .publish\").click(function(){ //find the save button\r\n		                    var id = iframe.find(\"input[name=id]\").val(); // get edited file id\r\n\r\n 							var fileTitleOrName = iframe.find(\"#file_details #file_title\").val(); // get title \r\n                      		if (fileTitleOrName == \'\') { // if title is empty use file name. \r\n		                        var fileTitleOrName = iframe.find(\".edit-name a\").text();\r\n		                    }\r\n	                        $(\"#bot_file_list li\").filter(function(){return $(this).attr(\"class\").replace(\"id\",\"\") == id}).children(\".bot_file_edit\").text(fileTitleOrName); // update title\r\n							oldFileList.filter(function(){return $(this).attr(\"class\").replace(\"id\",\"\") == id}).children(\".bot_file_edit\").text(fileTitleOrName); // update title in bkup list also\r\n					    });\r\n\r\n		                // When clicking \'save\' button...\r\n						$(\"#save\").click(function(){\r\n							var iframe = $(\"#bot_fu_iframe\").contents();\r\n                            if (iframe.find(\"#file_details.edit-form .publish\").length) {\r\n                            	iframe.find(\"#file_details.edit-form .publish\").click();\r\n                            }\r\n                            else {\r\n                                var oldValues = cf.val();\r\n                                if (!oldValues[0]) { // eliminates first (blank) array item if oldValue is empty\r\n									values.shift();\r\n                                }\r\n								cf.val(values); // update cf values\r\n			                    $(\"#bot_fu_iframe_container, #loading, #bot_fu_fade\").remove();\r\n                            }\r\n						})\r\n\r\n		                // When clicking \'cancel\' button...\r\n						$(\"#cancel\").click(function(){\r\n							$(\"#bot_file_list li\").remove();\r\n					        $(\"#bot_file_list\").append(oldFileList); \r\n			                $(\"#bot_fu_iframe_container, #loading, #bot_fu_fade\").remove();\r\n						})\r\n					});\r\n		            return false;\r\n		        });\r\n		        \r\n               // When clicking \'delete\' button...\r\n			   $(\"#bot_file_list .bot_file_delete\").live(\'click\',function(){\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\");\r\n					for(var i = 0 ; i < idValues.length ; i++) {\r\n						if($.trim(idValues[i]) == fileId) {\r\n							idValues.splice(i, 1);\r\n							ids = idValues.join(\",\");\r\n						}\r\n					}\r\n					cf.val(ids);\r\n					$(this).parent().remove();\r\n					return false;\r\n				});\r\n\r\n               // When clicking \'up\' button...\r\n			   $(\"#bot_file_list .bot_file_moveup\").live(\'click\',function(){\r\n			        var up = $(this).parent().prev();\r\n			        $(this).parent().insertBefore(up);\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\"); // creates array \r\n					var arrayIndex = $.inArray(fileId,idValues); // checks this id index in array\r\n					idValues.splice(arrayIndex,1); // eliminates this id from array\r\n     				var newPosition = (arrayIndex - 1 > 0) ? arrayIndex - 1 : 0; // checks if new position is positive or set to 0\r\n					idValues.splice(newPosition, 0, fileId); // inserts id in new position\r\n					cf.val(idValues);\r\n					return false;\r\n				});\r\n				\r\n               // When clicking \'down\' button...\r\n			   $(\"#bot_file_list .bot_file_movedown\").live(\'click\',function(){\r\n			        var down = $(this).parent().next();\r\n			        $(this).parent().insertAfter(down);\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\"); // creates array \r\n					var arrayIndex = $.inArray(fileId,idValues); // checks this id index in array\r\n					idValues.splice(arrayIndex,1); // eliminates this id from array\r\n					idValues.splice((arrayIndex + 1), 0, fileId); // inserts id in new position\r\n					cf.val(idValues);\r\n					return false;\r\n				});\r\n		    });\r\n		</script>\r\nJS_CODE;\r\n	}\r\n', '//<?\r\n\r\n\r\n\r\n	if(@txpinterface == \'admin\') {\r\n		register_callback(\'bot_file_css\',\'admin_side\',\'head_end\');\r\n		register_callback(\'bot_file_upload\',\'article\');\r\n 		bot_file_img();\r\n	}\r\n\r\n\r\n/**\r\n	Global preferences - Configuration. Read comments and modify to your needs.\r\n*/\r\n\r\n	function bot_fu_prefs() {\r\n		return\r\n			array(\r\n				\'bot_cf_name\' => gTxt(\'files\'), // name of used custom field\r\n				\'bot_add_files_text\' => gTxt(\'Add files\'), // text for \"add files\" link\r\n				\'bot_add_file_text\' => gTxt(\'Add\'), // text for \"add file\" checkbox\r\n				\'bot_file_delete_text\' => gTxt(\'Delete file\'), // text and title for \"delete file\" button\r\n				\'bot_file_moveup_text\' => gTxt(\'Move up\'), // text and title for \"delete file\" button\r\n				\'bot_file_movedown_text\' => gTxt(\'Move down\'), // text and title for \"delete file\" button\r\n				\'bot_fu_row_bg\' => \'#eaeaea\', // selected row background\r\n				\'bot_fu_save_text\' => gTxt(\'save\'), // text for \"Save\" button\"\r\n				\'bot_fu_cancel_text\' => gTxt(\'cancel\'), // text for \"Cancel\" button\"\r\n				\'bot_filelist_position\' => \'.title\', // css selector for the item the file list is put after\r\n			);\r\n	}\r\n\r\n\r\n\r\n\r\n/**\r\n	CSS for the file upload. You are free to modify these\r\n	to match your own backend theme.\r\n*/\r\n\r\n	function bot_file_css() {\r\n\r\n		global $event;\r\n		if($event != \'article\') { // Outputs css only in \'write\' tab.\r\n			return;\r\n		}\r\n\r\n		echo \'<style type=\"text/css\">\r\n				.bot_file_delete,\r\n				.bot_file_moveup,\r\n				.bot_file_movedown {\r\n					background: url(\"\'.hu.\'textpattern/?bot_file_img=delete\") top center no-repeat;\r\n     				float:right;\r\n					text-indent: -99999px;\r\n					width:20px;\r\n					height:20px;\r\n				}\r\n				.bot_file_moveup {\r\n					background:url(\"\'.hu.\'textpattern/txp_img/arrowupdn.gif\") center 2px  no-repeat;\r\n					width:10px;\r\n				}\r\n				.bot_file_movedown {\r\n					background:url(\"\'.hu.\'textpattern/txp_img/arrowupdn.gif\") center -18px  no-repeat;\r\n					width:10px;\r\n				}\r\n				#loading {\r\n					background:#000 url(\"\'.hu.\'textpattern/?bot_file_img=loading\") center center no-repeat;\r\n					text-indent:-99999px;\r\n					width:50px;\r\n					height:50px;\r\n					padding:10px;\r\n					position:fixed;\r\n					left:50%;\r\n					margin-left:-30px;\r\n					top:50%;\r\n					margin-top:-30px;\r\n					z-index:9999;\r\n					-moz-border-radius:10px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n				}\r\n				#bot_fu_fade {\r\n					display: none;\r\n					background: #000;\r\n					position: fixed; left: 0; top: 0;\r\n					width: 100%; height: 100%;\r\n					opacity: .80;\r\n					z-index: 999;\r\n				}\r\n				/*-- minimal styling for ie6--*/\r\n				*html #bot_fu_fade,\r\n				*html #loading,\r\n				*html #bot_fu_iframe_container {\r\n					position: absolute;\r\n				}\r\n				*html #bot_fu_iframe_container {\r\n					border: solid #ccc thick;\r\n				}\r\n\r\n				#bot_fu_iframe {\r\n					width:100%;\r\n				}\r\n				#bot_fu_iframe_container {\r\n				    /* height and margin-top are set by js */\r\n				    display:none;\r\n				    position:fixed;\r\n				    left:50%;\r\n				    z-index: 99999;\r\n					overflow: hidden;\r\n					background: #fff;\r\n					width:80%;\r\n					padding:0 2%;\r\n					margin-left: -42%;\r\n					text-align:right;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:10px;\r\n					border-radius: 10px;\r\n					-moz-box-shadow: 0 0 20px #000;\r\n					-webkit-box-shadow: 0 0 20px #000;\r\n					box-shadow: 0 0 20px #000;\r\n				}\r\n				#save,\r\n				#cancel {\r\n					background:#666;\r\n					color:#fff;\r\n					font-weight:bold;\r\n					padding:5px 10px;\r\n					margin-left:5px;\r\n					display:inline-block;\r\n					cursor:pointer;\r\n					-moz-border-radius:5px;\r\n					-webkit-border-radius:5px;\r\n					border-radius:5px;\r\n				}\r\n				#save:hover,\r\n				#cancel:hover {\r\n					background:#333;\r\n					text-decoration:none;\r\n				}\r\n				#bot_file_list {\r\n				    margin:0;\r\n				    padding:0;\r\n					border-top:solid #ddd 1px;\r\n				}\r\n				#bot_file_list li {\r\n				    color:#ccc;\r\n					overflow:hidden;\r\n					margin:0;\r\n					padding: 2px 0 0;\r\n					border-bottom:dotted  #ddd 1px;\r\n				}\r\n				#bot_add_file {\r\n				    display:block;\r\n					background: url(\"\'.hu.\'textpattern/?bot_file_img=add\") right center  no-repeat;\r\n					padding: 0 0 10px 0;\r\n				}\r\n				\r\n			</style>\';\r\n	}\r\n\r\n\r\n\r\n\r\n/**\r\n	Generates and stores the files required by the design.\r\n	file URI is: http://example.com/textpattern/?bot_file_img=delete|edit|file\r\n*/\r\n\r\n	function bot_file_img() {\r\n\r\n 	global $event;\r\n		if($event != \'article\') { // Generates files only in \'write\' tab.\r\n			return;\r\n		}\r\n		$file = gps(\'bot_file_img\');\r\n\r\n		if(!$file)\r\n			return;\r\n\r\n		switch($file) {\r\n			case \'delete\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABEElEQVR42mL4//8/Ayn4zJkzXMh8gABiYkACZ8+elQdibQYcACqXCqTtYGIAAcQIMgWmGUg9gIrrGBsbX8Wi+QqUuxKIG4FqrgMEENgANM0M6IagaYaBAiCeDBBAMAM4gJyvQMyEbgiURtf8F4g5gRb8BgggZC+ADPkCxMwM+AFIMw9Q8w8QByCA4DZCBXigCojSDAIAAYTiZCRD/mHR/A9dMwgABBATFoXKOMSZoHIoACCAmPBEFTZwBT2dAAQQEwHNf7F4B8UQgABiIqAZFB7cWAIWbghAAMFcIIYrtPHEDlgPQAAxQUN/P5BywhXaWAxxguphAAgg9JzmCMQceHIiB0gNshhAAMFTIrkAIMAA7k7JUu0YckQAAAAASUVORK5CYII=\';\r\n			break;\r\n			case \'add\':\r\n				$file = \'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAGUExURWZmZv///3NLWagAAAACdFJOU/8A5bcwSgAAAHBJREFUeNpiYMQCAAKIAZsgQABhFQQIIKyCAAEEF2QAAhgbIICwCgIEEFZBgADCKggQQFgFAQKIAcpHAKAAQABhFQQIIKyCAAGE1UyAAMIqCBBAWAUBAgirIEAAYRUECCCsoQQQQFgFAQIIqyBAgAEACewBP0Pv320AAAAASUVORK5CYII=\';\r\n			break;\r\n			default:\r\n				$file = \'R0lGODlhHwAfAPUAAAAAAP///xYWFiwsLEJCQlBQUFxcXCIiIkZGRmRkZBoaGiYmJlRUVF5eXk5OTjIyMggICFZWVioqKhgYGMjIyNjY2K6urjo6Oo6OjmxsbKioqAQEBJaWlri4uDg4OAYGBra2tszMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA4BiwSQexKh0eEAkrldAZbvlOD5TqYKALWu5XIwnPFwwymY0GsRgAxrwuJwbCi8aAHlYZ3sVdwtRCm8JgVgODwoQAAIXGRpojQwKRGSDCRESYRsGHYZlBFR5AJt2a3kHQlZlERN2QxMRcAiTeaG2QxJ5RnAOv1EOcEdwUMZDD3BIcKzNq3BJcJLUABBwStrNBtjf3GUGBdLfCtadWMzUz6cDxN/IZQMCvdTBcAIAsli0jOHSJeSAqmlhNr0awo7RJ19TJORqdAXVEEVZyjyKtE3Bg3oZE2iK8oeiKkFZGiCaggelSTiA2LhxiZLBSjZjBL2siNBOFQ84LxHA+mYEiRJzBO7ZCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82YAIQxRCm14Ww4PChAAEAoPDlsAFRUgHkRiZAkREmoSEXiVlRgfQgeBaXRpo6MOQlZbERN0Qx4drRUcAAJmnrVDBrkVDwNjr8BDGxq5Z2MPyUQZuRgFY6rRABe5FgZjjdm8uRTh2d5b4NkQY0zX5QpjTc/lD2NOx+WSW0++2RJmUGJhmZVsQqgtCE6lqpXGjBchmt50+hQKEAEiht5gUcTIESR9GhlgE9IH0BiTkxrMmWIHDkose9SwcQlHDsOIk9ygiVbl5JgMLuV4HUmypMkTOkEAACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2LQV3t4UBcvcF9/eFpdYxdgZ5hUYA73YGxruCbVjt78G7hXFqlhY/fLQwR0HIQdGuUrTz5eQdIc0cfIEwByGD0MKvcGSaFGjR8GyeAPhIUofQGNQSgrB4IsdOCqx7FHDBiYcOQshYjKDxliVDpRjunCjdSTJkiZP6AQBACH5BAkKAAAALAAAAAAfAB8AAAb/QIBwSBQMCAUDwFAgDATEqHR4QCSuVwD2ijhMpwrCFqsdJwiK73DBMGfdCcZCDWjAE2V347vY3/NmdXNECm14Ww4PChAAEAoPDltlDGlDYmQJERJqEhGHWARUgZVqaWZeAFZbERN0QxOeWwgAAmabrkMSZkZjDrhRkVtHYw+/RA9jSGOkxgpjSWOMxkIQY0rT0wbR2I3WBcvczltNxNzIW0693MFYT7bTumNQqlisv7BjswAHo64egFdQAbj0RtOXDQY6VAAUakihN1gSLaJ1IYOGChgXXqEUpQ9ASRlDYhT0xQ4cACJDhqDD5mRKjCAYuArjBmVKDP9+VRljMyMHDwcfuBlBooSCBQwJiqkJAgAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEgUDAgFA8BQIAwExKh0eEAkrlcA9oo4TKcKwharHScIiu9wwTBn3QnGQg1owBNld+O72N/zZnVzRApteFsODwoQABAKDw5bZQxpQ2JkCRESahIRh1gEVIGVamlmXgBWWxETdEMTnlsIAAJmm65DEmZGYw64UZFbR2MPv0QPY0hjpMYKY0ljjMZCEGNK09MG0diN1gXL3M5bTcTcyFtOvdzBWE+207pjUKpYrL+wY7MAB4EerqZjUAG4lKVCBwMbvnT6dCXUkEIFK0jUkOECFEeQJF2hFKUPAIkgQwIaI+hLiJAoR27Zo4YBCJQgVW4cpMYDBpgVZKL59cEBhw+U+QROQ4bBAoUlTZ7QCQIAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfju9jf82Z1c0QKbXhbDg8KEAAQCg8OW2UMaUNiZAkREmoSEYdYBFSBlWppZl4AVlsRE3RDE55bCAACZpuuQxJmRmMOuFGRW0djD79ED2NIY6TGCmNJY4zGQhBjStPTFBXb21DY1VsGFtzbF9gAzlsFGOQVGefIW2LtGhvYwVgDD+0V17+6Y6BwaNfBwy9YY2YBcMAPnStTY1B9YMdNiyZOngCFGuIBxDZAiRY1eoTvE6UoDEIAGrNSUoNBUuzAaYlljxo2M+HIeXiJpRsRNMaq+JSFCpsRJEqYOPH2JQgAIfkECQoAAAAsAAAAAB8AHwAABv9AgHBIFAwIBQPAUCAMBMSodHhAJK5XAPaKOEynCsIWqx0nCIrvcMEwZ90JxkINaMATZXfjywjlzX9jdXNEHiAVFX8ODwoQABAKDw5bZQxpQh8YiIhaERJqEhF4WwRDDpubAJdqaWZeAByoFR0edEMTolsIAA+yFUq2QxJmAgmyGhvBRJNbA5qoGcpED2MEFrIX0kMKYwUUslDaj2PA4soGY47iEOQFY6vS3FtNYw/m1KQDYw7mzFhPZj5JGzYGipUtESYowzVmF4ADgOCBCZTgFQAxZBJ4AiXqT6ltbUZhWdToUSR/Ii1FWbDnDkUyDQhJsQPn5ZU9atjUhCPHVhgTNy/RSKsiqKFFbUaQKGHiJNyXIAAh+QQJCgAAACwAAAAAHwAfAAAG/0CAcEh8JDAWCsBQIAwExKhU+HFwKlgsIMHlIg7TqQeTLW+7XYIiPGSAymY0mrFgA0LwuLzbCC/6eVlnewkADXVECgxcAGUaGRdQEAoPDmhnDGtDBJcVHQYbYRIRhWgEQwd7AB52AGt7YAAIchETrUITpGgIAAJ7ErdDEnsCA3IOwUSWaAOcaA/JQ0amBXKa0QpyBQZyENFCEHIG39HcaN7f4WhM1uTZaE1y0N/TacZoyN/LXU+/0cNyoMxCUytYLjm8AKSS46rVKzmxADhjlCACMFGkBiU4NUQRxS4OHijwNqnSJS6ZovzRyJAQo0NhGrgs5bIPmwWLCLHsQsfhxBWTe9QkOzCwC8sv5Ho127akyRM7QQAAOwAAAAAAAAAAAA==\';\r\n		}\r\n\r\n		ob_start();\r\n		ob_end_clean();\r\n		header(\'Content-type: file/gif\');\r\n		echo base64_decode($file);\r\n		exit();\r\n	}\r\n\r\n\r\n\r\n\r\n\r\n	function bot_generate_list()\r\n	{\r\n		global $prefs, $step;\r\n		extract(bot_fu_prefs());\r\n	    $cf_id = str_replace(\"_set\", \"\", array_search($bot_cf_name, $prefs)); // fetch cf internal name (custom_1 etc.)\r\n		$article_id =  gps(\'ID\'); // Fetch article id from $_GET, if any, or sets it to \'\'\r\n\r\n	    if ($step == \'edit\' && $article_id) {\r\n	        $cf_value = fetch($cf_id, \'textpattern\', \'ID\', $article_id);\r\n		}\r\n		elseif($step == \'create\' || $step == \'edit\'){\r\n			$cf_value = gps($cf_id);\r\n		}\r\n	    else {\r\n	    	$cf_value = \'\';\r\n	    }\r\n\r\n	    // proceed only if editing a saved article, a cf for files is set and has a value\r\n	    if ($cf_id && $cf_value) {\r\n	        $arr = safe_rows_start(\'id, filename, title\', \'txp_file\', \'id IN(\'.$cf_value.\') ORDER BY field(id, \'.$cf_value.\')\');\r\n\r\n	        $files = array();\r\n	        if ($arr) {\r\n	            while ($a = nextRow($arr)) {\r\n	                $id = $a[\'id\'];\r\n	                $filename = $a[\'filename\'];\r\n	                $title = $a[\'title\'];\r\n	                $title_or_name = $title == \'\'? $filename : $title;\r\n	                $files[$id] = $title_or_name;\r\n	            }\r\n	            $str = \'\';\r\n	            foreach ($files as $fileid => $title_or_name) {\r\n	                $str .=\r\n					\'<li class=\\\"id\'.$fileid.\'\\\">\'.\r\n						\'<a class=\\\"bot_file_delete\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_delete_text.\'\\\"> [\'.$bot_file_delete_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_moveup\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_moveup_text.\'\\\"> [\'.$bot_file_moveup_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_movedown\\\" href=\\\"#\\\" title=\\\"\'.$bot_file_movedown_text.\'\\\"> [\'.$bot_file_movedown_text.\']</a> \'.\r\n						\'<a class=\\\"bot_file_edit\\\" href=\\\"#\\\" title=\\\"\\\">\'.$title_or_name.\'</a>\'.\r\n					\'</li>\';\r\n	            }\r\n	            return $str;\r\n	        }\r\n	    }\r\n	}\r\n\r\n\r\n\r\n\r\n/*\r\n	generate js code\r\n*/\r\n\r\n    function bot_file_upload()\r\n    {\r\n		global $event, $prefs;\r\n\r\n		if($event != \'article\') {\r\n			return;\r\n		}\r\n\r\n		extract(bot_fu_prefs());\r\n 		$bot_saved_files = bot_generate_list();\r\n\r\n		$bot_cf_id = str_replace(\"_set\", \"\", array_search($bot_cf_name, $prefs)); // fetch cf internal name (custom_1 etc.)\r\n\r\n		echo <<<JS_CODE\r\n		<script type=\"text/javascript\">\r\n		    $(document).ready(function() {\r\n\r\n		        // modify \'write\' tab\r\n		        $(\'input[name=\"$bot_cf_id\"]\').parent().append(\r\n						\'<ul id=\"bot_file_list\"></ul>\' +\r\n						\'<a id=\"bot_add_file\" href=\"#\">$bot_add_files_text</a>\'\r\n					); // creates \'file list\' ul and \'add\' button\r\n		        $(\'input[name=\"$bot_cf_id\"]\').parent().insertAfter(\"$bot_filelist_position\"); // put after set field\r\n		        $(\"#bot_file_list\").append(\"$bot_saved_files\"); // if cf is already filled inserts files\r\n 		        $(\'input[name=\"$bot_cf_id\"]\').parent().children(\"input\").hide(); // hide cf\r\n\r\n		        // when clicking \'add\' or \'edit\' links\r\n		        $(\"#bot_add_file, #bot_file_list .bot_file_edit\").live(\"click\",function(){\r\n\r\n					// set variables\r\n        			var cf = $(\'input[name=\"$bot_cf_id\"]\');\r\n	                var ids = cf.val();\r\n                    var values = ids.split(\",\"); // existant ids array\r\n\r\n					// set the url the iframe must point to\r\n		            if ($(this).attr(\"id\")) {\r\n		            	var iframeUrl = \"index.php?event=file\";\r\n		            }\r\n		            else {\r\n						var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n		            	var iframeUrl = \"index.php?event=file&step=file_edit&id=\"+fileId;\r\n		            }\r\n		            \r\n					// Fade in Background\r\n				    $(\'body\').append(\'<div id=\"bot_fu_fade\"></div>\'); //Add the fade layer to bottom of the body tag.\r\n				    $(\'#bot_fu_fade\').show().css({\'filter\' : \'alpha(opacity=80)\'}); // show the fade layer -  fix the IE Bug on fading transparencies\r\n\r\n		            // creates an hidden iframe\r\n		            $(\"body\").append(\r\n						\'<div id=\"bot_fu_iframe_container\">\' +\r\n							\'<iframe id=\"bot_fu_iframe\" src =\"\'+iframeUrl+\'\" frameborder=\"0\"></iframe>\' +\r\n						\'</div>\');\r\n\r\n            		// creates \'cancel\' and \'save\' buttons\r\n		            $(\"#bot_fu_iframe\").after(\"<p><a id=\\\"save\\\">$bot_fu_save_text</a><a id=\\\"cancel\\\">$bot_fu_cancel_text</a></p>\");\r\n\r\n		            // backup filelist for undo\r\n		            var oldFileList = $(\"#bot_file_list li\").clone();\r\n		            \r\n        			// Calculates heights based on window size for vertical centering.\r\n					var windowHeight = $(window).height();\r\n					var containerHeight = (windowHeight * 80) / 100;\r\n					var containerTopMargin = (windowHeight - containerHeight) / 2;\r\n					var iframeHeight = (containerHeight * 78) / 100;\r\n					var loadingTopMargin = (windowHeight / 2) - 30;\r\n					$(\"#bot_fu_iframe_container\").height(containerHeight).css(\"top\", containerTopMargin);\r\n					$(\"#bot_fu_iframe\").height(iframeHeight).css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"#save\").css(\"margin-top\", containerTopMargin / 2);\r\n					$(\"body\").append(\'<p id=\"loading\">loading</p>\');\r\n\r\n		            // iframe interactions\r\n		            $(\"#bot_fu_iframe\").load(function(){\r\n		                var iframe = $(\"#bot_fu_iframe\").contents();\r\n		                // hide in iframe\r\n		                iframe.find(\".txp-header, .txp-footer, #pagetop, #end_page, #nav, #messagepane, #txp-head\").hide();\r\n		                iframe.find(\".multi-edit\").hide();\r\n		                iframe.find(\".existing-file\").hide();\r\n		                iframe.find(\"#file-search option[value=author]\").remove();\r\n		                iframe.find(\"#file-search option[value=id]\").remove();\r\n		                iframe.find(\".detail-toggle\").hide();\r\n		                iframe.find(\".description\").show(); \r\n		                iframe.find(\"#files_form .category\").hide();\r\n		                iframe.find(\"#files_form .id .files_detail\").hide();\r\n		                iframe.find(\"#files_form .tag-build\").hide();\r\n		                iframe.find(\"#files_form .status\").hide();\r\n		                iframe.find(\"#files_form .condition\").hide();\r\n		                iframe.find(\"#files_form .downloads\").hide();\r\n		                iframe.find(\"#files_form .author\").hide();\r\n		                iframe.find(\"#files_form .multi-edit\").hide();\r\n		                // hide in iframe - edit pane\r\n		                iframe.find(\"#file-status\").hide();\r\n		                iframe.find(\"#file-created\").hide();\r\n		                iframe.find(\"#file_details.edit-form .publish\").parent().hide(); // Hides original save button\r\n						var linkText = iframe.find(\".file-info .name a\").text(); // eliminate download link maintaining text\r\n		                iframe.find(\".file-info .name a\").replaceWith(linkText);\r\n\r\n		                // add \'add\' checkbox\r\n						iframe.find(\'.txp-list thead tr\').append(\'<th>$bot_add_file_text</th>\');\r\n						iframe.find(\'.txp-list tbody tr\').append(\r\n							\'<td class=\"add\">\'+\r\n								\'<input type=\"checkbox\" name=\"bot_file_checkbox[]\" class=\"bot_file_checkbox\" />\'+\r\n							\'</td>\');\r\n\r\n		                // check checkboxes depending on already set cfs\r\n	                    for(var i = 0 ; i < values.length ; i++) { \r\n	                        fileId = values[i];\r\n	                        iframe.find(\".txp-list .multi-edit input\").filter(function(){\r\n								return $(this).val() == fileId\r\n							;}).parents(\"tr\").find(\'input.bot_file_checkbox\').attr(\'checked\', true).parents(\"tr\").css(\"background\",\"$bot_fu_row_bg\");\r\n						}\r\n\r\n		                // everything is done, now loader can be hidden and iframe can be shown\r\n		                $(\"#loading\").hide();\r\n		                $(\"#bot_fu_iframe_container\").show();\r\n\r\n		                // on click\r\n		                $(\"#bot_fu_iframe\").contents().find(\".bot_file_checkbox[type=checkbox]\").click(function(){\r\n		                    var fileId = $(this).parents(\"tr\").find(\".id a:first\").text();\r\n		                    var fileTitleOrName = $(this).parents(\"tr\").find(\".title\").html();\r\n                      		if (fileTitleOrName == \'&nbsp;\' || fileTitleOrName.charCodeAt() == 160) { // if title contains &nbsp; use file name. Second part is for stupid opera bug\r\n		                        var fileTitleOrName = $(this).parents(\"tr\").find(\".name\").text();\r\n		                    }\r\n		                    if (this.checked){\r\n		                        $(this).parents(\"tr\").css(\"background\",\"$bot_fu_row_bg\"); // change row background\r\n        						values.push(fileId);\r\n        						$(\"#bot_file_list\").append(\r\n         							\'<li class=\"id\' + fileId + \'\">\' +\r\n										\'<a class=\"bot_file_delete\" href=\"#\" title=\"$bot_file_delete_text\"> $bot_file_delete_text</a> \' +\r\n										\'<a class=\"bot_file_moveup\" href=\"#\" title=\"$bot_file_moveup_text\"> $bot_file_moveup_text</a> \' +\r\n										\'<a class=\"bot_file_movedown\" href=\"#\" title=\"$bot_file_movedown_text\"> $bot_file_movedown_text</a> \' +\r\n										\'<a class=\"bot_file_edit\" href=\"#\">\'+fileTitleOrName+\'</a>\' +\r\n									\'</li>\'\r\n								);\r\n		                    }\r\n		                    else {\r\n		                        $(this).parents(\"tr\").css(\"background\",\"none\"); // restore background on uncheck\r\n		                        var arrayIndex = $.inArray(fileId, values); // checks this id index in array\r\n		                        values.splice(arrayIndex,1); // eliminates this id from array\r\n		                        $(\"#bot_file_list .bot_file_edit\").each(function(){\r\n		                            if($(this).text() == fileTitleOrName){\r\n		                                $(this).parent().remove();\r\n		                            }\r\n		                        });\r\n		                    }\r\n		                });\r\n\r\n		                // check again background for selected rows\r\n		                iframe.find(\"#list tr:has(input.bot_file_checkbox:checked)\").css(\"background\",\"$bot_fu_row_bg\");\r\n\r\n		                // avoid FOUC when clicking links and submits\r\n		                iframe.find(\"a, input.smallerbox, #file_details.edit-form .publish\").click(function(){\r\n		                     $(\"#bot_fu_iframe_container\").hide();\r\n		                     $(\"#loading\").show();\r\n		                });\r\n\r\n		                // when editing a file\r\n		                iframe.find(\"#file_details.edit-form .publish\").click(function(){ //find the save button\r\n		                    var id = iframe.find(\"input[name=id]\").val(); // get edited file id\r\n\r\n 							var fileTitleOrName = iframe.find(\"#file_details #file_title\").val(); // get title \r\n                      		if (fileTitleOrName == \'\') { // if title is empty use file name. \r\n		                        var fileTitleOrName = iframe.find(\".edit-name a\").text();\r\n		                    }\r\n	                        $(\"#bot_file_list li\").filter(function(){return $(this).attr(\"class\").replace(\"id\",\"\") == id}).children(\".bot_file_edit\").text(fileTitleOrName); // update title\r\n							oldFileList.filter(function(){return $(this).attr(\"class\").replace(\"id\",\"\") == id}).children(\".bot_file_edit\").text(fileTitleOrName); // update title in bkup list also\r\n					    });\r\n\r\n		                // When clicking \'save\' button...\r\n						$(\"#save\").click(function(){\r\n							var iframe = $(\"#bot_fu_iframe\").contents();\r\n                            if (iframe.find(\"#file_details.edit-form .publish\").length) {\r\n                            	iframe.find(\"#file_details.edit-form .publish\").click();\r\n                            }\r\n                            else {\r\n                                var oldValues = cf.val();\r\n                                if (!oldValues[0]) { // eliminates first (blank) array item if oldValue is empty\r\n									values.shift();\r\n                                }\r\n								cf.val(values); // update cf values\r\n			                    $(\"#bot_fu_iframe_container, #loading, #bot_fu_fade\").remove();\r\n                            }\r\n						})\r\n\r\n		                // When clicking \'cancel\' button...\r\n						$(\"#cancel\").click(function(){\r\n							$(\"#bot_file_list li\").remove();\r\n					        $(\"#bot_file_list\").append(oldFileList); \r\n			                $(\"#bot_fu_iframe_container, #loading, #bot_fu_fade\").remove();\r\n						})\r\n					});\r\n		            return false;\r\n		        });\r\n		        \r\n               // When clicking \'delete\' button...\r\n			   $(\"#bot_file_list .bot_file_delete\").live(\'click\',function(){\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\");\r\n					for(var i = 0 ; i < idValues.length ; i++) {\r\n						if($.trim(idValues[i]) == fileId) {\r\n							idValues.splice(i, 1);\r\n							ids = idValues.join(\",\");\r\n						}\r\n					}\r\n					cf.val(ids);\r\n					$(this).parent().remove();\r\n					return false;\r\n				});\r\n\r\n               // When clicking \'up\' button...\r\n			   $(\"#bot_file_list .bot_file_moveup\").live(\'click\',function(){\r\n			        var up = $(this).parent().prev();\r\n			        $(this).parent().insertBefore(up);\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\"); // creates array \r\n					var arrayIndex = $.inArray(fileId,idValues); // checks this id index in array\r\n					idValues.splice(arrayIndex,1); // eliminates this id from array\r\n     				var newPosition = (arrayIndex - 1 > 0) ? arrayIndex - 1 : 0; // checks if new position is positive or set to 0\r\n					idValues.splice(newPosition, 0, fileId); // inserts id in new position\r\n					cf.val(idValues);\r\n					return false;\r\n				});\r\n				\r\n               // When clicking \'down\' button...\r\n			   $(\"#bot_file_list .bot_file_movedown\").live(\'click\',function(){\r\n			        var down = $(this).parent().next();\r\n			        $(this).parent().insertAfter(down);\r\n					var cf = $(\'input[name=\"$bot_cf_id\"]\').parent().find(\"input\");\r\n					var fileId = $(this).parent().attr(\"class\").replace(\"id\",\"\");\r\n					var ids = cf.val();\r\n					var idValues = ids.split(\",\"); // creates array \r\n					var arrayIndex = $.inArray(fileId,idValues); // checks this id index in array\r\n					idValues.splice(arrayIndex,1); // eliminates this id from array\r\n					idValues.splice((arrayIndex + 1), 0, fileId); // inserts id in new position\r\n					cf.val(idValues);\r\n					return false;\r\n				});\r\n		    });\r\n		</script>\r\nJS_CODE;\r\n	}\r\n', '5808fd226a5a34105af8caacbaddd0a1', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('bot_cf_titles', 0, 'redbot', 'http://www.redbot.it/txp', '0.3', 'Titles for custom fields in the \"write\" tab', 'To configure the plugin go to admin > plugins > bot_cf_titles > edit and change code following the instructions', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_cf_titles\',\'article\');\r\n}\r\n\r\nglobal $bot_cf_titles,$plugins;\r\n\r\n########################################\r\n\r\n# C O N F I G U R A T I O N \r\n/* Change the sample lines below. Add a new line for each title you wish to add. \r\nValues above 10 are accepted (for use with glz_custom_fields).  \r\nif you need to insert double quotes in the title use &quot; otherwise you\'ll get an error*/\r\n\r\n$bot_cf_titles[\"custom-1\"] = \"My nice title for the first custom field\";\r\n$bot_cf_titles[\"custom-2\"] = \"Another title, this time for custom field 2 and with a &quot;quoted&quot; word\";\r\n\r\n########################################\r\n\r\n\r\nfunction bot_cf_titles_rows(){ // builds the rows\r\n	global $bot_cf_titles, $plugins;\r\n	if (in_array(\'glz_custom_fields\', $plugins)) {\r\n		$bot_glz_version = safe_field(\"version\",\"txp_plugin\",\"name=\'glz_custom_fields\'\"); //  fetch glz version, if installed\r\n		$bot_glz_version = str_replace(\".\", \"\", $bot_glz_version);\r\n	};\r\n	if (in_array(\"glz_custom_fields\", $plugins) && $bot_glz_version < 122){ // checs if glz_custom_fields is installed\r\n		$bot_glz_check = \"1\";\r\n	}\r\n	else{\r\n		$bot_glz_check = \"0\";\r\n	}\r\n	foreach ($bot_cf_titles as $name => $title) {\r\n		if ($bot_glz_check == \"1\") { // if glz_custom_fields is installed \"-\" are replaced by \"_\"\r\n		$name = str_replace(\"-\", \"_\", $name);\r\n		}\r\n		echo \'		$(\"label[for=\'.$name.\']\").contents().replaceWith(\"\'.$title.\'\")\'.n;\r\n	}\r\n}\r\n\r\n\r\nfunction bot_cf_titles(){ //  builds the script\r\n\r\n	echo\r\n		\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n		\'	$(document).ready(function() {\'.n;\r\n				bot_cf_titles_rows();\r\n	echo			\r\n		\'	});\'.n.\r\n		\'</script>\'; \r\n}', '//<?php\r\nif( @txpinterface === \'admin\' ){\r\n	register_callback(\'bot_cf_titles\',\'article\');\r\n}\r\n\r\nglobal $bot_cf_titles,$plugins;\r\n\r\n########################################\r\n\r\n# C O N F I G U R A T I O N \r\n/* Change the sample lines below. Add a new line for each title you wish to add. \r\nValues above 10 are accepted (for use with glz_custom_fields).  \r\nif you need to insert double quotes in the title use &quot; otherwise you\'ll get an error*/\r\n\r\n$bot_cf_titles[\"custom-1\"] = \"My nice title for the first custom field\";\r\n$bot_cf_titles[\"custom-2\"] = \"Another title, this time for custom field 2 and with a &quot;quoted&quot; word\";\r\n\r\n########################################\r\n\r\n\r\nfunction bot_cf_titles_rows(){ // builds the rows\r\n	global $bot_cf_titles, $plugins;\r\n	if (in_array(\'glz_custom_fields\', $plugins)) {\r\n		$bot_glz_version = safe_field(\"version\",\"txp_plugin\",\"name=\'glz_custom_fields\'\"); //  fetch glz version, if installed\r\n		$bot_glz_version = str_replace(\".\", \"\", $bot_glz_version);\r\n	};\r\n	if (in_array(\"glz_custom_fields\", $plugins) && $bot_glz_version < 122){ // checs if glz_custom_fields is installed\r\n		$bot_glz_check = \"1\";\r\n	}\r\n	else{\r\n		$bot_glz_check = \"0\";\r\n	}\r\n	foreach ($bot_cf_titles as $name => $title) {\r\n		if ($bot_glz_check == \"1\") { // if glz_custom_fields is installed \"-\" are replaced by \"_\"\r\n		$name = str_replace(\"-\", \"_\", $name);\r\n		}\r\n		echo \'		$(\"label[for=\'.$name.\']\").contents().replaceWith(\"\'.$title.\'\")\'.n;\r\n	}\r\n}\r\n\r\n\r\nfunction bot_cf_titles(){ //  builds the script\r\n\r\n	echo\r\n		\'<script language=\"javascript\" type=\"text/javascript\">\'.n.\r\n		\'	$(document).ready(function() {\'.n;\r\n				bot_cf_titles_rows();\r\n	echo			\r\n		\'	});\'.n.\r\n		\'</script>\'; \r\n}', '2a4017a11f98512efb4f9df08665acb8', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('com_connect', 0, 'Textpattern Community', 'https://forum.textpattern.io/viewtopic.php?id=47913', '4.6.0', 'Form and contact mailer for Textpattern', '<h1>com_connect</h1>\n\n<h2>Introduction</h2>\n\n<p>A Textpattern <span class=\"caps\">CMS</span> form mailer plugin. <code>&lt;txp:com_connect /&gt;</code> produces a flexible, customisable email contact form. It is intended for use as an enquiry form for commercial and private sites, and includes several features to help reduce common problems with such forms (invalid email addresses, missing information).</p>\n\n<p>Please report bugs and problems with this plugin at <a href=\"https://github.com/textpattern/com_connect/issues\" rel=\"nofollow\">the GitHub project&#8217;s issues page</a>.</p>\n\n<h3>Features</h3>\n\n<ul>\n	<li>Arbitrary HTML5 text fields can be specified, with min/max/required settings for validation.</li>\n	<li>Email address validation, including a check for a valid MX record (Unix only).</li>\n	<li>Safe escaping of input data.</li>\n	<li><span class=\"caps\">UTF</span>-8 safe.</li>\n	<li>Accessible form layout, including <code>&lt;label&gt;</code>, <code>&lt;legend&gt;</code> and <code>&lt;fieldset&gt;</code> tags.</li>\n	<li>Various classes and ids to allow easy styling of all parts of the form.</li>\n	<li>Spam prevention <span class=\"caps\">API</span> (used by Tranquillo&#8217;s <code>pap_contact_cleaner</code> plugin) and delivery <span class=\"caps\">API</span> for altering or extending the plugin&#8217;s capabilities.</li>\n</ul>\n\n<h3>History</h3>\n\n<p>Please see the <a href=\"https://github.com/textpattern/com_connect/blob/master/CHANGELOG.textile\" rel=\"nofollow\">changelog on GitHub</a>.</p>\n\n<h2>Installing and upgrading</h2>\n\n<p><strong>Requires Textpattern 4.5.0+</strong></p>\n\n<p>Download the latest release of the plugin from <a href=\"https://github.com/textpattern/com_connect/releases\" rel=\"nofollow\">the GitHub project page</a>, paste the code into the Textpattern Plugins administration panel, install and enable the plugin. Visit the <a href=\"https://forum.textpattern.io/viewtopic.php?id=47913\" rel=\"nofollow\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n<p>To uninstall, delete from the Plugins administration panel.</p>\n\n<p>Alternatively, this plugin can be installed using <a href=\"https://getcomposer.org\" rel=\"nofollow\">Composer</a>:</p>\n\n<pre><code>$ composer require textpattern/com_connect:*</code></pre>\n\n<h2>Migrating from zem_contact_reborn</h2>\n\n<p>If upgrading from zem_contact_reborn (the previous incarnation of this plugin), please note these differences:</p>\n\n<ul>\n	<li>Tags have been globally renamed from <code>&lt;txp:zem_contact ... /&gt;</code> to <code>&lt;txp:com_connect ... /&gt;</code> &#8211; please adjust your code accordingly.</li>\n	<li>Classes <code>zemConnectForm</code>, <code>zemError</code>, <code>zemRequired</code>, <code>zemThanks</code>, <code>zemText</code>, <code>zemEmail</code>, <code>zemTextarea</code>, <code>zemSubmit</code>, <code>zemSelect</code>, <code>zemOption</code>, <code>zemCheckbox</code> and <code>zemRadio</code> have been renamed to <code>comConnectForm</code>, <code>comError</code>, <code>comRequired</code>, <code>comThanks</code>, <code>comText</code>, <code>comEmail</code>, <code>comTextarea</code>, <code>comSubmit</code>, <code>comSelect</code>, <code>comOption</code>, <code>comCheckbox</code> and <code>comRadio</code> respectively &#8211; please adjust your code accordingly.</li>\n	<li>Disable or remove the zem_contact_lang plugin. Language strings are now bundled as part of the plugin itself. If you have a translation Textpack available that is not yet bundled, please submit it for inclusion.</li>\n	<li>Classes based on the input element <code>name</code> are no longer automatically applied. Only default class names beginning with <code>com</code> are set. To employ custom classes, use the <code>class</code> attribute for each tag, or the global <code>classes</code> attribute to set names for error and information messages.</li>\n	<li>If your site&#8217;s ‘Doctype’ preference is set to <code>html5</code> you may use HTML5 attributes in your tags. Otherwise, they will be ignored.</li>\n	<li>Validation of required elements and min/max constraints is done by the browser first, and the plugin second. So if you specify a field is required and it is left empty, the browser will usually prevent the form being submitted. To bypass (most of) the browser checks, specify <code>browser_validate=&quot;0&quot;</code> in your <code>&lt;txp:com_connect /&gt;</code> tag.</li>\n</ul>\n\n<h2>Usage</h2>\n\n<h3>Contact form</h3>\n\n<p>The simplest form is shown below, which produces a default form with ‘Name’, ‘Email’ and ‘Message’ fields. Email will be delivered to &lt;code&gt;recipient@example.com&lt;/code&gt;, with the user&#8217;s supplied email as the <code>From:</code> address.</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot; /&gt;</code></pre>\n\n<p>To specify fields explicitly, use something like this:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot;&gt;\n    &lt;txp:com_connect_email /&gt;\n    &lt;txp:com_connect_text label=&quot;Phone&quot; min=&quot;7&quot; max=&quot;15&quot; /&gt;\n    &lt;txp:com_connect_textarea label=&quot;Your question&quot; /&gt;\n    &lt;txp:com_connect_submit label=&quot;Send&quot; /&gt;\n&lt;/txp:com_connect&gt;</code></pre>\n\n<p>Alternatively, place the field specifications in a Textpattern form, and call it like this:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot; form=&quot;my-contact-form&quot; /&gt;</code></pre>\n\n<h3>Send article</h3>\n\n<p>Within the context of an individual article, this plugin can be used to send the article (or excerpt, if it exists) to an email address specified by the visitor. This requires at least two tags:</p>\n\n<ol>\n	<li><code>com_connect</code>, to create form that is initially hidden by setting the <code>send_article</code> attribute.</li>\n	<li><code>com_connect_send_article</code>, to create a ‘Send article’ link which reveals the aforementioned form when clicked.</li>\n</ol>\n\n<pre><code>&lt;txp:com_connect send_article=&quot;1&quot; /&gt;\n&lt;txp:com_connect_send_article /&gt;</code></pre>\n\n<p>By default the form contains fields for your name and email address, the recipient&#8217;s email address and a personal message, but similar to contact forms you can create your own form layout. Some things you need to know:</p>\n\n<ol>\n	<li>Set the <code>send_article</code> attribute to <code>1</code> in the <code>com_connect</code> tag.</li>\n	<li>Use a <code>com_connect_email</code> tag with the <code>send_article</code> attribute set to <code>1</code>. This field will be used as the recipient email address.</li>\n</ol>\n\n<pre><code>&lt;txp:com_connect to=&quot;you@example.com&quot; send_article=&quot;1&quot;&gt;\n    &lt;txp:com_connect_email label=&quot;Recipient Email&quot; send_article=&quot;1&quot; /&gt;\n    &lt;txp:com_connect_email label=&quot;Your Email&quot; /&gt;\n    &lt;txp:com_connect_submit label=&quot;Send Article&quot; /&gt;\n&lt;/txp:com_connect&gt;\n\n&lt;txp:com_connect_send_article /&gt;</code></pre>\n\n<h2>Tags</h2>\n\n<p><code>&lt;txp:com_connect_send_article /&gt;</code> can be used to create a ‘Send article’ link within an article form, connecting it to the contact form.</p>\n\n<p>All other tags provided by this plugin can only be used inside a <code>&lt;txp:com_connect&gt;</code> &#8211; <code>&lt;/txp:com_connect&gt;</code> container tag or in a Textpattern form used as the <code>form</code> attribute in the <code>&lt;txp:com_connect /&gt;</code> tag.</p>\n\n<p>In addition to the tags detailed in the following sections, every tag accepts a core set of common attributes. These are:</p>\n\n<ul>\n	<li><code>accesskey=&quot;character&quot;</code>&lt;br /&gt;Shortcut key to set focus on the field.</li>\n	<li><code>autofocus=&quot;boolean&quot;</code>&lt;br /&gt;To automatically focus the cursor in this field on page load. Only one field may have this property.</li>\n	<li><code>dir=&quot;value&quot;</code>&lt;br /&gt;Text direction (<code>ltr</code>, <code>rtl</code> or <code>auto</code>).</li>\n	<li><code>disabled=&quot;boolean&quot;</code>&lt;br /&gt;Whether the input control accepts user input. If set, the element does not get submitted with the form, nor is it subject to any <code>checkValidity()</code> JavaScript calls.</li>\n	<li><code>hidden=&quot;boolean&quot;</code>&lt;br /&gt;The visibility of the input control.</li>\n	<li><code>id=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> identifier for the control.</li>\n	<li><code>lang=&quot;value&quot;</code>&lt;br /&gt;The <span class=\"caps\">ISO</span> 639 language short code (e.g. <code>en-gb</code>, <code>de-de</code>) that govern the field.</li>\n	<li><code>list=&quot;id&quot;</code>&lt;br /&gt;Used in conjunction with the <code>&lt;datalist&gt;</code> tag to specify a set of options. The id is the reference to the datalist to use.</li>\n	<li><code>readonly=&quot;boolean&quot;</code>&lt;br /&gt;Control does not accept user input, but will be processed on form submission and can be validated.</li>\n	<li><code>spellcheck=&quot;value&quot;</code>&lt;br /&gt;Whether the field is subject to spell checking (<code>true</code> = yes, <code>default</code> = browser decides, or <code>false</code> = no).</li>\n	<li><code>style=&quot;style rules&quot;</code>&lt;br /&gt;Inline <span class=\"caps\">CSS</span> <code>style</code> rules to apply to the input control.</li>\n	<li><code>tabindex=&quot;number&quot;</code>&lt;br /&gt;The order in which the cursor jumps between elements when using the tab key.</li>\n	<li><code>title=&quot;value&quot;</code>&lt;br /&gt;Usually used for hover tooltip describing the input control&#8217;s use in your application.</li>\n	<li><code>translate=&quot;boolean&quot;</code>&lt;br /&gt;Whether to subject the attribute content to language translation.</li>\n</ul>\n\n<h3>com_connect tag</h3>\n\n<pre><code>&lt;txp:com_connect /&gt;</code></pre>\n\n<p>May be used as a single (self-closing) or container tag. Place this where you want the input form to go. Status and error messages, if any, will be displayed before the form.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>body_form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form for the message body text.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt;Set the <span class=\"caps\">CSS</span> <code>class</code> name of the tag. Default: <code>comConnectForm</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>classes=&quot;comma-separated key:value pairs&quot;</code>&lt;br /&gt;Set the <span class=\"caps\">CSS</span> classes for error / information conditions. Specify each as a pair of values separated by a colon, e.g. <code>classes=&quot;required: req_field, element: warn_field&quot;</code>. There are up to four available to customise:\n	<ul>\n		<li><code>element</code>: Set for each form field that fails validation for any reason. Default: <code>errorElement</code>.</li>\n		<li><code>wrapper</code>: The class to surround the list of errors shown above the form. Default: <code>comError</code>.</li>\n		<li><code>required</code>: Class assigned when a required element is not completed. Default: <code>comRequired</code>.</li>\n		<li><code>thanks</code>: Class applied to the wrapper around the <code>thanks_form</code>. Default: <code>comThanks</code>.</li>\n	</ul></li>\n	<li><code>copysender=&quot;boolean&quot;</code>&lt;br /&gt;Whether to send a copy of the email to the sender&#8217;s address. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>0</code>.</li>\n	<li><code>expire=&quot;number&quot;</code>&lt;br /&gt;Number of seconds after which the form will expire, thus requiring a page refresh before sending. Default is <code>600</code>.</li>\n	<li><code>form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form, containing the layout of the contact form fields.</li>\n	<li><code>from=&quot;email address&quot;</code>&lt;br /&gt;Email address used in the “From:” field when sending email. Defaults to the sender&#8217;s email address. If specified, the sender&#8217;s email address will be placed in the “Reply-To:” field instead.</li>\n	<li><code>from_form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form (overrides <code>from</code> attribute).</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Label for the contact form. If set to an empty string, display of the fieldset and legend tags will be suppressed. Default is <code>Contact</code>.</li>\n	<li><code>lang=&quot;lang-code&quot;</code>&lt;br /&gt;Override the language strings that would normally be used from the current admin-side language in force. e.g. <code>lang=&quot;fr-fr&quot;</code> would load the French language strings. A Textpack must already exist for the chosen language.</li>\n	<li><code>browser_validate=&quot;boolean&quot;</code>&lt;br /&gt;Set to 0 if you wish to stop the browser from validating form field values and ‘required’ status of input elements. The plugin itself is then solely responsible for validation and will indicate error conditions after submission. Default is <code>1</code>.</li>\n	<li><code>redirect=&quot;URL&quot;</code>&lt;br /&gt;Redirect to specified <span class=\"caps\">URL</span> (overrides <code>thanks</code> and <code>thanks_form</code> attributes). <span class=\"caps\">URL</span> must be relative to the Textpattern site <span class=\"caps\">URL</span>. Example: <code>redirect=&quot;monkey&quot;</code> would redirect to <code>http://example.com/monkey</code>.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether to require all tags in this contact form to be completed before the form can be submitted. Can be overridden on a field-by-field basis by using the <code>required</code> attribute in the relevant tag. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>1</code>.</li>\n	<li><code>send_article=&quot;boolean&quot;</code>&lt;br /&gt;Whether to use this form to send an article. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>0</code>.</li>\n	<li><code>show_error=&quot;boolean&quot;</code>&lt;br /&gt;Whether to display error and status messages. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>1</code>.</li>\n	<li><code>show_input=&quot;boolean&quot;</code>&lt;br /&gt;Whether to display the form <code>&lt;input&gt;</code> fields. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>1</code>.</li>\n	<li><code>subject=&quot;subject text&quot;</code>&lt;br /&gt;Subject used when sending an email. Default is the site name.</li>\n	<li><code>subject_form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form (overrides <code>subject</code> attribute).</li>\n	<li><code>thanks=&quot;text&quot;</code>&lt;br /&gt;Message shown after successfully submitting a message. Default is <code>Thank you, your message has been sent</code>.</li>\n	<li><code>thanks_form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form (overrides <code>thanks</code> attribute).</li>\n	<li><code>to=&quot;email address&quot;</code> %(warning)required%&lt;br /&gt;Recipient email address. Multiple recipients can be specified, separated by commas.</li>\n	<li><code>to_form=&quot;form name&quot;</code>&lt;br /&gt;Use specified form (overrides <code>to</code> attribute).</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>When used as a single tag, produces a default form with ‘Name’, ‘Email’ and ‘Message’ fields. Email will be delivered to &lt;code&gt;recipient@example.com&lt;/code&gt;, with the user&#8217;s supplied email as the <code>From:</code> address:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<p>When used as a container tag, much more flexibility is allowed, for example:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot;&gt;\n    &lt;txp:com_connect_email /&gt;\n    &lt;txp:com_connect_text type=&quot;tel&quot; label=&quot;Phone&quot; min=&quot;7&quot; max=&quot;15&quot; /&gt;\n    &lt;txp:com_connect_textarea label=&quot;Your question&quot; /&gt;\n    &lt;txp:com_connect_submit label=&quot;Send&quot; /&gt;\n&lt;/txp:com_connect&gt;</code></pre>\n\n<h5>Example 3</h5>\n\n<p>Example with custom email message formatting, called via the <code>body_form</code> attribute:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;recipient@example.com&quot; body_form=&quot;message-formatting&quot; /&gt;</code></pre>\n\n<p>And the <code>body_form</code> form template named <code>message-formatting</code> is as follows:</p>\n\n<pre><code>============\nEmail received.\n\n&lt;txp:com_connect_if name=&quot;email&quot;&gt;&lt;txp:com_connect_label name=&quot;email&quot; /&gt;: &lt;txp:com_connect_value name=&quot;email&quot; /&gt;&lt;txp:else /&gt;Mr. Nobody&lt;/txp:com_connect_if&gt; wrote:\n\n&lt;txp:com_connect_if name=&quot;message&quot;&gt;&lt;txp:com_connect_value name=&quot;message&quot; /&gt;&lt;txp:else /&gt;Nothing much :(&lt;/txp:com_connect_if&gt;\n\n============</code></pre>\n\n<h3>com_connect_text tag</h3>\n\n<pre><code>&lt;txp:com_connect_text /&gt;</code></pre>\n\n<p>Creates a text <code>&lt;input&gt;</code> field and corresponding <code>&lt;label&gt;</code> tag. The input value will be included in the email, preceded by the label.<br />\nCreates a text <code>&lt;input&gt;</code> field. The input value will be included in the email, preceded by the label.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>autocomplete=&quot;value&quot;</code>&lt;br /&gt;Indicates whether the value of the control can be automatically completed by the browser. This attribute is ignored if the value of the type attribute is <code>password</code>. Possible values are: <code>off</code> (the browser does not automatically complete the entry) or <code>on</code> (the browser can automatically complete the value based on values that the user has entered during previous uses). Default is unset.</li>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the <code>&lt;label&gt;</code> and <code>&lt;input&gt;</code> field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the tag. Default: <code>comText</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>default=&quot;value&quot;</code>&lt;br /&gt;Default value when no input is provided.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the field is attached. Associated with the contained form by default.</li>\n	<li><code>inputmode=&quot;value&quot;</code>&lt;br /&gt;A hint to the browser for which keyboard to display. This attribute applies when the value of the type attribute is <code>text</code>, <code>password</code> or <code>url</code>. Possible values are:\n	<ul>\n		<li><code>verbatim</code>: Alphanumeric, non-prose content such as usernames and passwords.</li>\n		<li><code>latin</code>: Latin-script input in the user&#8217;s preferred language with typing aids such as text prediction enabled. For human-to-computer communication such as search boxes.</li>\n		<li><code>latin-name</code>: As latin, but for human names.</li>\n		<li><code>latin-prose</code>: As latin, but with more aggressive typing aids. For human-to-human communication such as instant messaging for email.</li>\n		<li><code>full-width-latin</code>: As latin-prose, but for the user&#8217;s secondary languages.</li>\n		<li><code>kana</code>: Kana or romaji input, typically hiragana input, using full-width characters, with support for converting to kanji. Intended for Japanese text input.</li>\n		<li><code>katakana</code>: Katakana input, using full-width characters, with support for converting to kanji. Intended for Japanese text input.</li>\n		<li><code>numeric</code>: Numeric input, including keys for the digits 0 to 9, the user&#8217;s preferred thousands separator character, and the character for indicating negative numbers. Intended for numeric codes, e.g. credit card numbers. For actual numbers, prefer using <code>type=&quot;number&quot;</code>.</li>\n		<li><code>tel</code>: Telephone input, including asterisk and pound key. Use <code>type=&quot;tel&quot;</code> if possible instead.</li>\n		<li><code>email</code>: Email input. Use <code>&lt;txp:com_connect_email /&gt;</code> if possible instead.</li>\n		<li><code>url</code>: <span class=\"caps\">URL</span> input. Use <code>type=&quot;url&quot;</code> if possible instead.</li>\n	</ul></li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text label displayed to the user. Default is <code>Text</code>.</li>\n	<li><code>label_position=&quot;text&quot;</code>&lt;br /&gt;Position of the label in relation to the <code>&lt;input&gt;</code> field. Available values: <code>before</code> or <code>after</code>. Default is <code>before</code>.</li>\n	<li><code>max=value&quot;</code>&lt;br /&gt;For character-based inputs, the maximum input value length in characters, using the HTML5 <code>maxlength</code> attribute. To remove <code>maxlength</code> attribute from the element entirely (not recommended), use <code>max=&quot;&quot;</code>. For numeric-based inputs, the maximum input value the field accepts, using the HTML5 <code>max</code> attribute (can be a negative value). Default is <code>100</code>. To remove <code>max</code> attribute from the numerical input element entirely, use <code>max=&quot;&quot;</code>.</li>\n	<li><code>min=&quot;value&quot;</code>&lt;br /&gt;For character-based inputs, the minimum input value length in characters, using the HTML5 <code>minlength</code> attribute. Default is unset, i.e., no minimum limit. For numeric-based inputs, the minimum input value the field accepts, using the HTML5 <code>min</code> attribute (can be a negative value). Default is <code>0</code>. To remove <code>min</code> attribute from the numerical input element entirely, use <code>min=&quot;&quot;</code>.</li>\n	<li><code>name=&quot;value&quot;</code>&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;input&gt;</code> tag.</li>\n	<li><code>pattern=&quot;regex&quot;</code>&lt;br /&gt;Regular expression that governs the format in which the field data is expected. Only used for character-based inputs.</li>\n	<li><code>placeholder=&quot;text&quot;</code>&lt;br /&gt;Text to show as a guide, when the <code>&lt;input&gt;</code> field is empty.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this field must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>.</li>\n	<li><code>size=&quot;value&quot;</code>&lt;br/&gt;The size, in characters, of the <code>&lt;input&gt;</code> field.</li>\n	<li><code>step=&quot;value&quot;</code>&lt;br /&gt;For numeric-based inputs, the interval between min and max.</li>\n	<li><code>type=&quot;value&quot;</code>&lt;br /&gt;Type of text input. Default is <code>text</code>. Choose from:\n	<ul>\n		<li><code>color</code></li>\n		<li><code>date</code></li>\n		<li><code>datetime</code></li>\n		<li><code>datetime-local</code></li>\n		<li><code>month</code></li>\n		<li><code>number</code></li>\n		<li><code>password</code></li>\n		<li><code>range</code></li>\n		<li><code>search</code></li>\n		<li><code>tel</code></li>\n		<li><code>text</code></li>\n		<li><code>time</code></li>\n		<li><code>url</code></li>\n		<li><code>week</code></li>\n	</ul></li>\n	</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:com_connect_text label=&quot;Your name&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<pre><code>&lt;txp:com_connect_text type=&quot;range&quot; label=&quot;UK shoe size&quot; min=&quot;1&quot; max=&quot;15&quot; /&gt;</code></pre>\n\n<h5>Example 3</h5>\n\n<p>Create a telephone field with a <a href=\"http://html5pattern.com/Phones\" rel=\"nofollow\">validation pattern for UK telephone number</a> format:</p>\n\n<pre><code>&lt;txp:com_connect_text type=&quot;tel&quot; label=&quot;Telephone&quot; pattern=&quot;^\\s*\\(?(020[7,8]{1}\\)?[ ]?[1-9]{1}[0-9{2}[ ]?[0-9]{4})|(0[1-8]{1}[0-9]{3}\\)?[ ]?[1-9]{1}[0-9]{2}[ ]?[0-9]{3})\\s*$&quot; required=&quot;1&quot; /&gt;</code></pre>\n\n<h3>com_connect_email tag</h3>\n\n<pre><code>&lt;txp:com_connect_email /&gt;</code></pre>\n\n<p><code>&lt;input&gt;</code> field for user&#8217;s email address.</p>\n\n<p>The entered email address will automatically be validated to make sure it is of the form “abc@xxx.yyy[.zzz]”. On non-Windows servers, a test will be done to verify that an A or MX record exists for the domain. Neither test prevents spam, but it does help detect accidental typing errors.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>autocomplete=&quot;value&quot;</code>&lt;br /&gt;Indicates whether the value of the control can be automatically completed by the browser. Possible values are: <code>off</code> (the browser does not automatically complete the entry) or <code>on</code> (the browser can automatically complete the value based on values that the user has entered during previous uses). Default is unset.</li>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the <code>&lt;label&gt;</code> and <code>&lt;input&gt;</code> field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the tag. Default: <code>comEmail</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>default=&quot;value&quot;</code>&lt;br /&gt;Default value when no input is provided.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the field is attached. Associated with the contained form by default.</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text label displayed to the user. Default is <code>Email</code>.</li>\n	<li><code>label_position=&quot;text&quot;</code>&lt;br /&gt;Position of the label in relation to the <code>&lt;input&gt;</code> field. Available values: <code>before</code> or <code>after</code>. Default is <code>before</code>.</li>\n	<li><code>max=&quot;integer&quot;</code>&lt;br /&gt;Maximum input value length in characters, using the HTML5 <code>maxlength</code> attribute. Default is <code>100</code>. To remove <code>maxlength</code> attribute from the element entirely (not recommended), use <code>max=&quot;&quot;</code>.</li>\n	<li><code>min=&quot;integer&quot;</code>&lt;br /&gt;Minimum input value length in characters, using the HTML5 <code>minlength</code> attribute. Default is unset, i.e., no minimum limit.</li>\n	<li><code>name=&quot;value&quot;</code>&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;input&gt;</code> tag.</li>\n	<li><code>placeholder=&quot;text&quot;</code>&lt;br /&gt;Text to show as a guide, when the <code>&lt;input&gt;</code> field is empty.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this field must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>.</li>\n	<li><code>send_article=&quot;boolean&quot;</code>&lt;br /&gt;Whether this field is used as the recipient email address when using the send_article function. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>0</code>.</li>\n	<li><code>size=&quot;value&quot;</code>&lt;br/&gt;The size, in characters, of the <code>&lt;input&gt;</code> field.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:com_connect_email label=&quot;Your email address&quot; /&gt;</code></pre>\n\n<h3>com_connect_textarea tag</h3>\n\n<pre><code>&lt;txp:com_connect_textarea /&gt;</code></pre>\n\n<p>Creates a <code>&lt;textarea&gt;</code>.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>autocomplete=&quot;value&quot;</code>&lt;br /&gt;Indicates whether the value of the control can be automatically completed by the browser. Possible values are: <code>off</code> (the browser does not automatically complete the entry) or <code>on</code> (the browser can automatically complete the value based on values that the user has entered during previous uses). Default is unset.</li>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the <code>&lt;label&gt;</code> and <code>&lt;textarea&gt;</code>. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the <code>&lt;label&gt;</code> and <code>&lt;input&gt;</code> field on the same line.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the  <code>&lt;textarea&gt;</code>. Default: <code>comTextarea</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>cols=&quot;integer&quot;</code>&lt;br/&gt;Number of columns, in characters, of the <code>&lt;textarea&gt;</code>. Default is <code>58</code>.</li>\n	<li><code>default=&quot;value&quot;</code>&lt;br /&gt;Default value when no input is provided.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the <code>&lt;textarea&gt;</code> is attached. Associated with the contained form by default.</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text label displayed to the user. Default is <code>Message</code>.</li>\n	<li><code>label_position=&quot;text&quot;</code>&lt;br /&gt;Position of the label in relation to the <code>&lt;textarea&gt;</code> field. Available values: <code>before</code> or <code>after</code>. Default is <code>before</code>.</li>\n	<li><code>max=&quot;integer&quot;</code>&lt;br /&gt;Maximum input value length in characters, using the HTML5 <code>maxlength</code> attribute. Default is <code>10000</code>. To remove <code>maxlength</code> attribute from the element entirely (not recommended), use <code>max=&quot;&quot;</code>.</li>\n	<li><code>min=&quot;integer&quot;</code>&lt;br /&gt;Minimum input value length in characters, using the HTML5 <code>minlength</code> attribute. Default is unset, i.e., no minimum limit.</li>\n	<li><code>name=&quot;value&quot;</code>&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;input&gt;</code> tag.</li>\n	<li><code>placeholder=&quot;text&quot;</code>&lt;br /&gt;Text to show as a guide to users, when the  <code>&lt;textarea&gt;</code> is empty.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this field must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>.</li>\n	<li><code>rows=&quot;integer&quot;</code>&lt;br/&gt;Number of rows, in characters, of the <code>&lt;textarea&gt;</code>. Default is <code>8</code>.</li>\n	<li><code>wrap=&quot;value&quot;</code>&lt;br/&gt;Governs word-wrap. Available values: <code>hard</code> or <code>soft</code>. If this attribute is not specified, <code>soft</code> is its default value.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Create a text area that is 40 characters wide, 10 lines high, with a customised label:</p>\n\n<pre><code>&lt;txp:com_connect_textarea cols=&quot;40&quot; rows=&quot;10&quot; label=&quot;Your question&quot; /&gt;</code></pre>\n\n<h3>com_connect_submit tag</h3>\n\n<pre><code>&lt;txp:com_connect_submit /&gt;</code></pre>\n\n<p>Creates a submit button. When used as a container tag, a <code>&lt;button&gt;</code> element will be used instead of an <code>&lt;input&gt;</code> element.</p>\n\n<h4>Attributes:</h4>\n\n<ul>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the tag. Default: <code>comSubmit</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the button is attached. Associated with the contained form by default.</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text shown on the submit button. Default is <code>Send</code>.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Standard submit button:</p>\n\n<pre><code>&lt;txp:com_connect_submit /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<p>Submit button with your own text:</p>\n\n<pre><code>&lt;txp:com_connect_submit label=&quot;To the moooon&quot; /&gt;</code></pre>\n\n<h5>Example 3</h5>\n\n<p>Usage as a container tag, which allows you to use Textpattern tags and <span class=\"caps\">HTML</span> markup in the submit button:</p>\n\n<pre><code>&lt;txp:com_connect_submit&gt;&lt;strong&gt;Send&lt;/strong&gt; question&lt;/txp:com_connect_submit&gt;</code></pre>\n\n<h5>Example 4</h5>\n\n<p>As example 3 above, but using an image as the button:</p>\n\n<pre><code>&lt;txp:com_connect_submit&gt;\n    &lt;img src=&quot;path/to/img.png&quot; alt=&quot;submit&quot;&gt;\n&lt;/txp:com_connect_submit&gt;</code></pre>\n\n<h3>com_connect_select tag</h3>\n\n<pre><code>&lt;txp:com_connect_select /&gt;</code></pre>\n\n<p>Container tag that creates a drop-down selection <code>&lt;select&gt;</code> list, or scrolled <code>&lt;select&gt;</code> list box (by utilising the <code>size</code> attribute).</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the label and <code>&lt;select&gt;</code> field. Default is <code>&lt;br /&gt;</code>. Use <code>break=&quot;&quot;</code> to put the label and input field on the same line.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the list. Default: <code>comSelect</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>delimiter=&quot;character&quot;</code>&lt;br /&gt;Separator character between list items if using the <code>options</code> attribute. Default is <code>,</code> (comma). Ignored if this tag is used as a container.</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text label displayed to the user. Default is <code>Option</code>.</li>\n	<li><code>label_position=&quot;text&quot;</code>&lt;br /&gt;Position of the label in relation to the <code>&lt;select&gt;</code> field. Available values: <code>before</code> or <code>after</code>. Default is <code>before</code>.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the <code>&lt;select&gt;</code> is attached. Associated with the contained form by default.</li>\n	<li><code>name=&quot;value&quot;</code>&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;select&gt;</code> tag.</li>\n	<li><code>options=&quot;comma-separated values&quot;</code>&lt;br /&gt; List of items (previously <code>list</code>) to show in the select box. May also use the <code>&lt;txp:com_connect_option /&gt;</code> tag inside this tag&#8217;s container.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this field must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>.</li>\n	<li><code>selected=&quot;value&quot;</code>&lt;br /&gt;List item that is selected by default.</li>\n	<li><code>size=&quot;value&quot;</code>&lt;br/&gt;If the <code>&lt;select&gt;</code> is to be presented as a scrolled list box, this attribute represents the number of rows in the list that should be visible at one time. Default is unset (i.e., a drop-down selection <code>&lt;select&gt;</code> list).</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Drop-down selection list labeled ‘Department’, containing three options and a blank option (due to the comma before ‘Marketing’) shown by default, forcing the user to make a selection.</p>\n\n<pre><code>&lt;txp:com_connect_select label=&quot;Department&quot; options=&quot;,Marketing,Sales,Support&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<pre><code>&lt;txp:com_connect_select label=&quot;Department&quot; selected=&quot;Sales&quot;&gt;\n    &lt;txp:com_connect_option /&gt;\n    &lt;txp:com_connect_option label=&quot;Marketing&quot; /&gt;\n    &lt;txp:com_connect_option label=&quot;Sales&quot; /&gt;\n    &lt;txp:com_connect_option label=&quot;Support&quot; /&gt;\n&lt;/txp:com_connect_select&gt;</code></pre>\n\n<p>Drop-down selection list containing three options plus a blank option (also see <code>com_connect_option</code> tag below), with ‘Sales’ selected by default.</p>\n\n<h3>com_connect_option tag</h3>\n\n<pre><code>&lt;txp:com_connect_option /&gt;</code></pre>\n\n<p>Creates a drop-down selection option. May be used as a single (self-closing) or container tag. Also see <code>com_connect_select</code> tag above.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the option. Default: <code>comOption</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>label=&quot;text&quot;</code> %(warning)required%&lt;br /&gt;Text label of this option displayed to the user.</li>\n	<li><code>selected=&quot;boolean&quot;</code>&lt;br /&gt;Whether this item is selected, May also be specified in the container tag&#8217;s <code>selected</code> attribute. Available values: <code>1</code> (yes) or <code>0</code> (no).</li>\n	<li><code>value=&quot;text&quot;</code>&lt;br /&gt;The value associated with this option when submitted. Default is the label.</li>\n</ul>\n\n<h5>Example 1</h5>\n\n<pre><code>&lt;txp:com_connect_select label=&quot;Department&quot;&gt;\n    &lt;txp:com_connect_option label=&quot;Marketing&quot; /&gt;\n    &lt;txp:com_connect_option label=&quot;Sales&quot; /&gt;\n    &lt;txp:com_connect_option label=&quot;Support&quot; /&gt;\n&lt;/txp:com_connect_select&gt;</code></pre>\n\n<p>Drop-down selection list containing three options as single tags.</p>\n\n<h5>Example 2</h5>\n\n<pre><code>&lt;txp:com_connect_select label=&quot;Department&quot;&gt;\n    &lt;txp:com_connect_option value=&quot;contact-marketing&quot;&gt;Marketing&lt;/txp:com_connect_option&gt;\n    &lt;txp:com_connect_option value=&quot;contact-sales&quot; selected=&quot;1&quot;&gt;Sales&lt;/txp:com_connect_option&gt;\n    &lt;txp:com_connect_option value=&quot;contact-support&quot;&gt;Support&lt;/txp:com_connect_option&gt;\n&lt;/txp:com_connect_select&gt;</code></pre>\n\n<p>Drop-down selection list containing three options as container tags with ‘Sales’ selected by default.</p>\n\n<h3>com_connect_checkbox tag</h3>\n\n<pre><code>&lt;txp:com_connect_checkbox /&gt;</code></pre>\n\n<p>Creates a checkbox.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the checkbox button and label. Default is one space. Use <code>break=&quot;br&quot;</code> to put the checkbox and label on separate lines.</li>\n	<li><code>checked=&quot;boolean&quot;</code>&lt;br /&gt;Whether this box is checked when first displayed. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>0</code>.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the option. Default: <code>comCheckbox</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the checkbox is attached. Associated with the contained form by default.</li>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Text label displayed to the user. Default is <code>Checkbox</code>.</li>\n	<li><code>label_position=&quot;text&quot;</code>&lt;br /&gt;Position of the label in relation to the <code>&lt;input&gt;</code> field. Available values: <code>before</code> or <code>after</code>. Default is <code>after</code>.</li>\n	<li><code>name=&quot;value&quot;</code>&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;input&gt;</code> tag.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this checkbox must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>.</li>\n	<li><code>value=&quot;text&quot;</code>&lt;br /&gt;Value to send in the email if the option is checked. Uses yes/no if not set.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Shrink-wrap agreement which must be checked by the user before the email will be sent.</p>\n\n<pre><code>&lt;txp:com_connect_checkbox label=&quot;I accept the terms and conditions&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<p>Optional checkboxes:</p>\n\n<pre><code>With which operating systems are you familiar?&lt;br /&gt;\n&lt;txp:com_connect_checkbox label=&quot;Windows&quot; required=&quot;0&quot; /&gt;&lt;br /&gt;\n&lt;txp:com_connect_checkbox label=&quot;Unix/Linux/BSD&quot; required=&quot;0&quot; /&gt;&lt;br /&gt;\n&lt;txp:com_connect_checkbox label=&quot;MacOS&quot; required=&quot;0&quot; /&gt;</code></pre>\n\n<h3>com_connect_radio tag</h3>\n\n<pre><code>&lt;txp:com_connect_radio /&gt;</code></pre>\n\n<p>Creates a radio button.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>break=&quot;tag&quot;</code>&lt;br /&gt;Break tag between the radio button and label. Default is one space. Use <code>break=&quot;br&quot;</code> to put the radio button and label on separate lines.</li>\n	<li><code>checked=&quot;boolean&quot;</code>&lt;br /&gt;Whether this radio option is checked when the form is first displayed. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is <code>0</code>.</li>\n	<li><code>class=&quot;space-separated values&quot;</code>&lt;br /&gt; Set the <span class=\"caps\">CSS</span> <code>class</code> name of the radio button. Default: <code>comRadio</code>. To remove <code>class</code> attribute from the element entirely, use <code>class=&quot;&quot;</code>.</li>\n	<li><code>group=&quot;text&quot;</code> %(warning)required%&lt;br /&gt;Text used in the email to describe this group of radio buttons. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. Default is <code>Radio</code>.</li>\n	<li><code>html_form=&quot;id&quot;</code>&lt;br /&gt;The <span class=\"caps\">HTML</span> <code>id</code> of the <code>&lt;form&gt;</code> tag to which the radio button is attached. Associated with the contained form by default.</li>\n	<li><code>label=&quot;text&quot;</code> %(warning)required%&lt;br /&gt;Text label displayed to the user as radio button option.</li>\n	<li><code>name=&quot;value&quot;</code> %(warning)recommended%&lt;br /&gt;Field name, as used in the <span class=\"caps\">HTML</span> <code>&lt;input&gt;</code> tag. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. If it hasn&#8217;t been set at all, it will be derived from the <code>group</code> attribute.</li>\n	<li><code>required=&quot;boolean&quot;</code>&lt;br /&gt;Whether this radio set must be filled out. Available values: <code>1</code> (yes) or <code>0</code> (no). Default is whatever is set in the <code>&lt;txp:com_connect&gt;</code> tag&#8217;s <code>required</code> attribute &#8211; if neither attribute is set then default is <code>1</code>. <strong>You should set the <code>required</code> attribute on only the first radio button of the group, or set the same identical attribute value on all radio buttons in the group.</strong></li>\n	<li><code>value=&quot;text&quot;</code>&lt;br /&gt;Value to send in the email if the option is checked. Uses label if not set.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Group mutually exclusive radio buttons by setting the <code>group</code> attribute on the first radio button in a group. Only the chosen radio button from each group will be used in the email message. The message will be output in the form <code>group: label</code> for each of the chosen radio buttons.</p>\n\n<pre><code>&lt;txp:com_connect_radio label=&quot;Medium&quot; group=&quot;I like my steak&quot; /&gt;\n&lt;txp:com_connect_radio label=&quot;Rare&quot; /&gt;\n&lt;txp:com_connect_radio label=&quot;Well done&quot; /&gt;\n&lt;txp:com_connect_radio label=&quot;Wine&quot; group=&quot;With a glass of&quot; /&gt;\n&lt;txp:com_connect_radio label=&quot;Beer&quot; /&gt;\n&lt;txp:com_connect_radio label=&quot;Water&quot; /&gt;</code></pre>\n\n<h3>com_connect_secret tag</h3>\n\n<pre><code>&lt;txp:com_connect_secret /&gt;</code></pre>\n\n<p>This tag has no effect on the form or <span class=\"caps\">HTML</span> output, but will include additional information in the email. It can be used as a single (self-closing) tag or as a container tag.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Used to identify the field in the email. Default is <code>Secret</code>.</li>\n	<li><code>name=&quot;text&quot;</code>&lt;br /&gt;Used internally. Set this only if you have multiple ‘secret’ form elements with identical labels.</li>\n	<li><code>value=&quot;value&quot;</code>&lt;br /&gt;Some text you want to add to the email.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Usage as a single (self-closing) tag:</p>\n\n<pre><code>&lt;txp:com_connect_secret value=&quot;The answer is 42&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<p>Usage as a container tag:</p>\n\n<pre><code>&lt;txp:com_connect_secret label=&quot;Dear user&quot;&gt;\n    Please provide a useful example for this tag!\n&lt;/txp:com_connect_secret&gt;</code></pre>\n\n<h3>com_connect_serverinfo tag</h3>\n\n<pre><code>&lt;txp:com_connect_serverinfo /&gt;</code></pre>\n\n<p>This tag has no effect on the form or <span class=\"caps\">HTML</span> output, but will include additional information in the email based on the <span class=\"caps\">PHP</span> <code>$_SERVER</code> variable.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;Used to identify the field in the email. Defaults to the value of the <code>name</code> attribute.</li>\n	<li><code>name=&quot;value&quot;</code> %(warning)required%&lt;br /&gt;Name of the server variable. See the <a href=\"https://php.net/manual/reserved.variables.php#reserved.variables.server\" rel=\"nofollow\"><span class=\"caps\">PHP</span> manual</a> for a full list.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Add the IP address of the visitor to the email:</p>\n\n<pre><code>&lt;txp:com_connect_serverinfo name=&quot;REMOTE_ADDR&quot; label=&quot;IP number&quot; /&gt;</code></pre>\n\n<h5>Example 2</h5>\n\n<p>Add the name of the visitor&#8217;s browser to the email:</p>\n\n<pre><code>&lt;txp:com_connect_serverinfo name=&quot;HTTP_USER_AGENT&quot; label=&quot;Browser&quot; /&gt;</code></pre>\n\n<h3>com_connect_send_article tag</h3>\n\n<pre><code>&lt;txp:com_connect_send_article /&gt;</code></pre>\n\n<p>Use this tag in your individual article form, where you want the “send article” link to be displayed.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>linktext=&quot;text&quot;</code>&lt;br /&gt;Text displayed for the link. Default is <code>send article</code>.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>On an article form:</p>\n\n<pre><code>&lt;txp:com_connect_send_article linktext=&quot;Send this article&quot; /&gt;</code></pre>\n\n<h3>com_connect_label tag</h3>\n\n<pre><code>&lt;txp:com_connect_label /&gt;</code></pre>\n\n<p>Return the label for the given attribute name.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name=&quot;text&quot;</code>&lt;br /&gt;The name of the field for which you wish to retrieve the label.</li>\n</ul>\n\n<h3>com_connect_value tag</h3>\n\n<pre><code>&lt;txp:com_connect_value /&gt;</code></pre>\n\n<p>Return the value of the given attribute, by name or its label.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;The label of the field for which you wish to retrieve the value.</li>\n	<li><code>name=&quot;text&quot;</code>&lt;br /&gt;The name of the field for which you wish to retrieve the value.</li>\n</ul>\n\n<h3>com_connect_if tag</h3>\n\n<pre><code>&lt;txp:com_connect_if /&gt;</code></pre>\n\n<p>Conditional tag for checking variable conditions, either by name or label.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>label=&quot;text&quot;</code>&lt;br /&gt;The label of the field you wish to check.</li>\n	<li><code>name=&quot;text&quot;</code>&lt;br /&gt;The name of the field you wish to check.</li>\n	<li><code>value=&quot;text&quot;</code>&lt;br /&gt;The value against which to test the given field. Leave blank to just test if there is any value assigned to the field.</li>\n</ul>\n\n<h4>Examples</h4>\n\n<h5>Example 1</h5>\n\n<p>Take action if the visitor has entered a particular value.</p>\n\n<pre><code>&lt;txp:com_connect_if name=&quot;delivery&quot; value=&quot;courier&quot;&gt;</code></pre>\n\n<p>Please note, this option incurs an additional charge, <code>&lt;/txp:com_connect_if&gt;</code>.</p>\n\n<h2>Advanced examples</h2>\n\n<h3>Separate input and error forms</h3>\n\n<p>Using <code>show_input</code> and <code>show_error</code> to display the form and error messages on different parts of a page. A form is used to make sure the contents of both forms are identical, otherwise they would be seen as two independent forms. The first form only shows errors (no input), the second form only shows the input fields (no errors).</p>\n\n<pre><code>&lt;div id=&quot;error&quot;&gt;\n    &lt;txp:com_connect form=&quot;contact_form&quot; show_input=&quot;0&quot; /&gt;\n&lt;/div&gt;\n&lt;div id=&quot;inputform&quot;&gt;\n    &lt;txp:com_connect form=&quot;contact_form&quot; show_error=&quot;0&quot; /&gt;\n&lt;/div&gt;</code></pre>\n\n<p>Apart from the <code>show_error</code> and <code>show_input</code> attributes, all other attributes must be 100% identical in both forms, otherwise they would be seen as two unrelated forms.</p>\n\n<h3>User selectable subject field</h3>\n\n<p>Specify the <code>subject_form</code> attribute and create a form which includes a <code>com_connect_select</code> tag:</p>\n\n<pre><code>&lt;txp:com_connect to=&quot;you@example.com&quot; subject_form=&quot;my_subject_form&quot; /&gt;\n    &lt;txp:com_connect_text label=&quot;Name&quot; /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_email /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_select label=&quot;Choose Subject&quot; options=&quot;,Question,Feedback&quot; /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_textarea label=&quot;Message&quot; /&gt;&lt;br /&gt;\n&lt;/txp:com_connect&gt;</code></pre>\n\n<p>Create a Textpattern form called “my_subject_form”, containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n    global $com_connect_form;\n    echo $com_connect_form[\'Choose Subject\'];\n&lt;/txp:php&gt;</code></pre>\n\n<p>The <code>label</code> used in the <code>com_connect_select</code> tag must be identical to the corresponding variable in the <code>subject_form</code>. Here we used <code>Choose subject</code>.</p>\n\n<p>If you&#8217;d prefer to add a common prefix for all subjects, use a <code>subject_form</code> containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n    global $com_connect_form;\n    echo \'My common prefix - \' . $com_connect_form[\'Choose Subject\'];\n&lt;/txp:php&gt;</code></pre>\n\n<h3>User selectable recipient, without showing email address</h3>\n\n<p>Specify the <code>to_form</code> attribute and create a form which includes a <code>com_connect_select</code> tag:</p>\n\n<pre><code>&lt;txp:com_connect to_form=&quot;my_com_connect_to_form&quot;&gt;\n    &lt;txp:com_connect_text label=&quot;Name&quot; /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_email /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_select label=&quot;Department&quot; options=&quot;,Support,Sales&quot; /&gt;&lt;br /&gt;\n    &lt;txp:com_connect_textarea label=&quot;Message&quot; /&gt;&lt;br /&gt;\n&lt;/txp:com_connect&gt;</code></pre>\n\n<p>Create a Textpattern form called “my_com_connect_to_form”, containing:</p>\n\n<pre><code>&lt;txp:php&gt;\n    global $com_connect_form;\n    switch($com_connect_form[\'Department\'])\n    {\n        case \'Support\':\n            echo \'crew@example.com\';\n            break;\n        case \'Sales\':\n            echo \'showmethemoney@example.com\';\n            break;\n        default:\n            echo \'someone@example.com\';\n    }\n&lt;/txp:php&gt;</code></pre>\n\n<p>The <code>label</code> used in the <code>com_connect_select</code> tag must be identical to the corresponsing variable in the <code>to_form</code>. Here we used <code>Department</code>.</p>\n\n<p>A ‘default’ email address in the <code>to_form</code> is specified to ensure that a valid email address is used in cases where you add or change a select/radio option and forget to update the <code>to_form</code>.</p>\n\n<p><strong>Warning:</strong> Never use tags like <code>com_connect_text</code>, <code>com_connect_email</code> or <code>com_connect_textarea</code> for setting the recipient address, otherwise your form can be abused to send spam to any email address!</p>\n\n<h2>Styling</h2>\n\n<p>The form itself has a default <code>class</code> of <code>comConnectForm</code> set on the <code>&lt;form&gt;</code> <span class=\"caps\">HTML</span> tag.</p>\n\n<p>If a <code>thanks_form</code> template is used, the wrapper around that template has a default <code>class</code> of <code>comThanks</code>.</p>\n\n<p>The list of error messages (if any) has a default <code>class</code> of <code>comError</code> set on the <code>ul</code> <span class=\"caps\">HTML</span> tag that encloses the list of errors. This class name may be overridden using the <code>classes</code> attribute of the <code>com_connect</code> tag.</p>\n\n<p>All form elements and corresponding labels have the following classes (or ids) set by default:</p>\n\n<ol>\n	<li>One of <code>comText</code>, <code>comEmail</code>, <code>comTextarea</code>, <code>comSelect</code>, <code>comOption</code>, <code>comRadio</code>, <code>comCheckbox</code> or <code>comSubmit</code>. By default, it should be obvious which <code>class</code> is used for which form element (and corresponding label). You can override these names by using your own <code>class</code> attribute.</li>\n	<li><code>comRequired</code> and/or <code>errorElement</code>, depending on whether the form element is required, an error was found in whatever the visitor entered, or both. Override these using the <code>classes</code> attribute in the <code>com_connect</code> tag.</li>\n	<li>An individual <code>id</code> or <code>class</code> set to the value of the <code>name</code> attribute of the corresponding tag. When styling forms based on this <code>class</code>, you should explicitly set the <code>name</code> attribute because automatically generated names may change in newer com_connect versions.</li>\n</ol>\n\n<h2>Com Connect&#8217;s <span class=\"caps\">API</span></h2>\n\n<p>The plugin <span class=\"caps\">API</span> of com_connect, originally developed by Tranquillo, allows other plugins to interact with contact forms. This permits extra functionality such as combatting comment cpam, <span class=\"caps\">HTML</span> email, newsletter delivery and so forth to be bolted onto the base plugin.</p>\n\n<p>Four callback events exist in com_connect:</p>\n\n<ul>\n	<li><code>comconnect.submit</code> is called after the form is submitted and the values are checked if empty or valid email addresses, but before the mail is sent.</li>\n	<li><code>comconnect.form</code> lets you inject content (fields) in the contact form as displayed to the visitor.</li>\n	<li><code>comconnect.render</code> lets you inject or alter markup of the entire <code>&lt;form&gt;</code>. Useful for editing things like <code>enctype</code> (e.g. for file attachment modules that link into this plugin).</li>\n	<li><code>comconnect.deliver</code> is called immediately prior to delivery and advertises the intended payload so you may manipulate it. For example, you could do something as simple as adding CC: or <span class=\"caps\">BCC</span>: fields. Or change the <span class=\"caps\">MIME</span> type header to <code>text/html</code> and add some <span class=\"caps\">HTML</span> content based on the given body data, then let com_connect handle the mailing. Or you could intercept the entire mail process, handle mailing yourself with a third party system, and tell com_connect to skip its internal mailing process.</li>\n</ul>\n\n<p>For reference here are the commands that will be interesting to plugin developers:</p>\n\n<pre><code>// This will call your function before the form is submitted so you can analyse the submitted data\nregister_callback(\'abc_myfunction\', \'comconnect.submit\');\n\n// This will call your function and add the output (use @return $mystuff;@) to the contact-form.\nregister_callback(\'abc_myotherfunction2\', \'comconnect.form\');\n\n// To get hold of the form-variables you can use global com_connect_form;\n// With the following two lines you can tell com_connect if your plugin found spam.\n$evaluator =&amp; get_comconnect_evaluator();\n\n// The passed value must be non-zero to mark the content as spam.\n// Value must be a number between 0 and 1.\n$evaluator -&gt; add_comconnect_status(1);</code></pre>\n\n<p>Multiple plugins can be active at the same time and each of them can mark the submitted content as spam and prevent the form from being submitted.</p>\n\n<h4>Examples</h4>\n\n<pre><code>register_callback(\'pap_comconnect_form\',\'comconnect.form\');\nregister_callback(\'pap_comconnect_submit\',\'comconnect.submit\');\n\nfunction pap_comconnect_form()\n{\n    $field = \'&lt;div style=&quot;display:none&quot;&gt;\'.\n        finput(\'text\',\'phone\',ps(\'phone\'),\'\',\'\',\'\',\'\',\'\',\'phone\').\'&lt;br /&gt;\'.\n        finput(\'text\',\'mail\',ps(\'mail\'),\'\',\'\',\'\',\'\',\'\',\'mail\').\'&lt;/div&gt;\';\n    return $field;&lt;/code&gt;\n}\n\nfunction pap_comconnect_submit()\n{\n    $checking_mail_field = trim(ps(\'mail\'));\n    $checking_phone_field = trim(ps(\'phone\'));\n    $evaluation =&amp; get_comconnect_evaluator();\n\n    // If the hidden fields are filled out, the contact form won\'t be submitted!\n    if ($checking_mail_field != \'\' or $checking_phone_field != \'\') {\n        $evaluation -&gt; add_comconnect_status(1);\n    }\n\n    return;\n}</code></pre>\n\n<p>For the delivery callback, you signal back to the plugin your intentions so that Com Connect knows what to do after your delivery plugin has executed. Return the following strings:</p>\n\n<ul>\n	<li><code>comconnect.send</code> (or no return value) to allow Com Connect to continue mailing the content.</li>\n	<li><code>comconnect.skip</code> to skip com_connect&#8217;s mailing (i.e., the third party handles the mail process) and return ‘success’ to the visitor.</li>\n	<li><code>comconnect.fail</code> to skip com_connect&#8217;s mailing and return ‘fail’ to the visitor.</li>\n</ul>\n\n<p>Or simply <code>exit</code> your plugin to halt the entire operation so no Com Connect feedback is given.</p>\n\n<h2>Frequently asked questions</h2>\n\n<h5>How do I remove the legend and fieldset surrounding the contact form?</h5>\n\n<p>Set the <code>label</code> attribute to an empty value (<code>label=&quot;&quot;</code>) in the <code>com_connect</code> tag.</p>\n\n<h5>No email is sent. How do I diagnose and fix the problem?</h5>\n\n<p>First try a simple contact form, using only the <code>com_connect</code> tag with the <code>to</code> attribute set to a valid email address. If that doesn&#8217;t send email, fill out the ‘SMTP envelope sender address’ field in Textpattern&#8217;s Preferences administration panel on the admin tab. If that doesn&#8217;t help either, take a look at your mail server log files to see what the problem is.</p>\n\n<h5>Which tag do I use to create the submit button?</h5>\n\n<p>Just use normal <span class=\"caps\">HTML</span> code to create a submit button. For historical reasons this plugin still provides the <code>com_connect_submit</code> tag, but it provides little extra functionality.</p>\n\n<h5>How can I get a unique (order) number in the subject of each email?</h5>\n\n<p>Try using the <a href=\"http://vanmelick.com/txp\" rel=\"nofollow\">rvm_counter</a> tag in the <code>subject</code> attribute of the <code>com_connect</code> tag.</p>\n\n<h5>I want to use the contact form in an article list (one form for each article), but how do I make each form unique?</h5>\n\n<p>You can make each form unique by making one of its attribute values unique. See previous question for an example of how to do this with the <code>subject</code> attribute.</p>\n\n<h5>Send article: can I let people specify multiple recipients?</h5>\n\n<p>No. The ‘send article’ functionality is spammy enough as it is right now.</p>\n\n<h5>Send article: can I show the contact form without having to click a link first?</h5>\n\n<p>Sure, put this just above the <code>com_connect</code> tag:</p>\n\n<pre><code>&lt;txp:php&gt;$_GET[\'com_connect_send_article\']=\'yes\';&lt;/txp:php&gt;</code></pre>\n\n<h5>How can I use this form to upload files?</h5>\n\n<p>You can&#8217;t, but have a look at the <a href=\"https://github.com/Bloke/ext_file_attach/releases/\" rel=\"nofollow\">ext_file_attach</a> module, or <a href=\"https://bitbucket.org/Manfre/txp-plugins/downloads/\" rel=\"nofollow\">mem_form</a> plugins.</p>\n\n<h5>Can I use this plugin to send <span class=\"caps\">HTML</span> email?</h5>\n\n<p>Not without a plugin like <a href=\"https://bitbucket.org/Manfre/txp-plugins/downloads\" rel=\"nofollow\">mem_form</a> or using the delivery callback.</p>\n\n<h5>Can I use this plugin to send newsletters?</h5>\n\n<p>Not without a plugin, such as <a href=\"https://bitbucket.org/Manfre/txp-plugins/downloads/\" rel=\"nofollow\">mem_postmaster</a>.</p>\n\n<h5>I have a question that&#8217;s not listed here</h5>\n\n<p>First read the plugin documentation (the page you&#8217;re on right now) once more. If that doesn&#8217;t answer your question, visit the <a href=\"https://forum.textpattern.io\" rel=\"nofollow\">Textpattern forum</a>.</p>\n\n<h2>Authors/credits</h2>\n\n<ul>\n	<li><strong>zem</strong> wrote the zem_contact 0.6 plugin on which this plugin was initially based.</li>\n	<li><strong>Mary</strong> completely revised the plugin code.</li>\n	<li><strong>Stuart</strong> turned it into a plugin, added a revised help text and additional code. Maintained all plugin versions until 4.0.3.17.</li>\n	<li><strong>wet</strong> added the <code>com_connect_radio</code> tag.</li>\n	<li><strong>Tranquillo</strong> added the anti-spam <span class=\"caps\">API</span> and <code>zem_contact_send_article</code> functionality.</li>\n	<li><strong>aslsw66</strong>, <strong>jdykast</strong> and others provided additional code.</li>\n	<li><strong>Ruud</strong> cleaned up and audited the code to weed out bugs and completely revised the help text. Maintained all versions until 4.0.3.20.</li>\n	<li><strong>Bloke</strong> was the maintainer of v4.5.0.0.</li>\n	<li>Adopted as a community plugin and now maintained by the core development team.</li>\n	<li>Supported and tested to destruction by the Textpattern community. Many thanks to <a href=\"https://github.com/textpattern/com_connect/graphs/contributors\" rel=\"nofollow\">all additional contributors</a>.</li>\n</ul>', '//<?php\n/**\n * com_connect: A Textpattern CMS plugin for mail delivery of contact forms.\n */\n\n// Register tags if necessary.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'com_connect\')\n        ->register(\'com_connect_text\')\n        ->register(\'com_connect_email\')\n        ->register(\'com_connect_textarea\')\n        ->register(\'com_connect_select\')\n        ->register(\'com_connect_option\')\n        ->register(\'com_connect_checkbox\')\n        ->register(\'com_connect_radio\')\n        ->register(\'com_connect_serverinfo\')\n        ->register(\'com_connect_secret\')\n        ->register(\'com_connect_submit\')\n        ->register(\'com_connect_send_article\')\n        ->register(\'com_connect_value\')\n        ->register(\'com_connect_label\')\n        ->register(\'com_connect_if\');\n}\n\n/**\n * Tag: encapsulate a contact form.\n *\n * Triggers the following callbacks:\n *  -> \'comconnect.form\' during form rendering so additional fields (e.g. spam honeypots) can be injected.\n *  -> \'comconnect.render\' immediately prior to form rendering so other parts of the form content may be altered.\n *  -> \'comconnect.submit\' on successful posting of form data. Primarily of use for spam\n *     plugins: they can return a non-zero value to signal that the form should NOT be sent.\n *\n * @param array  $atts  Tag attributes\n * @param string $thing Tag\'s container content\n */\nfunction com_connect($atts, $thing = null)\n{\n    global $sitename, $textarray, $com_connect_flags, $com_connect_from,\n        $com_connect_recipient, $com_connect_error, $com_connect_submit,\n        $com_connect_form, $com_connect_labels, $com_connect_values;\n\n    extract(com_connect_lAtts(array(\n        \'body_form\'        => \'\',\n        \'class\'            => \'comConnectForm\',\n        \'classes\'          => \'\',\n        \'copysender\'       => 0,\n        \'expire\'           => 600,\n        \'form\'             => \'\',\n        \'from\'             => \'\',\n        \'from_form\'        => \'\',\n        \'label\'            => null,\n        \'browser_validate\' => 1,\n        \'redirect\'         => \'\',\n        \'required\'         => \'1\',\n        \'show_error\'       => 1,\n        \'show_input\'       => 1,\n        \'send_article\'     => 0,\n        \'subject\'          => null,\n        \'subject_form\'     => \'\',\n        \'to\'               => \'\',\n        \'to_form\'          => \'\',\n        \'thanks\'           => null,\n        \'thanks_form\'      => \'\'\n    ), $atts));\n\n    if (!empty($lang)) {\n        $strings = com_connect_load_lang($lang);\n        $textarray = array_merge($textarray, $strings);\n    }\n\n    // Set defaults, in the local language if necessary.\n    if ($label === null) {\n        $label = gTxt(\'com_connect_contact\');\n    }\n\n    if ($subject === null) {\n        $subject = gTxt(\'com_connect_email_subject\', array(\'{site}\' => html_entity_decode($sitename,ENT_QUOTES)));\n    }\n\n    if ($thanks === null) {\n        $thanks = graf(gTxt(\'com_connect_email_thanks\'));\n    }\n\n    unset($atts[\'show_error\'], $atts[\'show_input\']);\n\n    $defaultClassNames = array(\n        \'element\'  => \'errorElement\',\n        \'wrapper\'  => \'comError\',\n        \'required\' => \'comRequired\',\n        \'thanks\'   => \'comThanks\',\n        );\n\n    $com_connect_form_id = md5(serialize($atts) . preg_replace(\'/[\\t\\s\\r\\n]/\', \'\', $thing));\n    $com_connect_submit = (ps(\'com_connect_form_id\') == $com_connect_form_id);\n    $override_email_charset = (get_pref(\'override_emailcharset\') && is_callable(\'utf8_decode\'));\n    $userClassNames = do_list($classes);\n\n    foreach (array_merge($defaultClassNames, $userClassNames) as $classKey => $classValue) {\n        if (strpos($classValue, \':\') !== false) {\n            $classParts = do_list($classValue, \':\');\n\n            if (count($classParts) === 2) {\n                $com_connect_flags[\'cls_\' . $classParts[0]] = $classParts[1];\n            }\n        } elseif ($classKey && $classValue) {\n            $com_connect_flags[\'cls_\' . $classKey] = $classValue;\n        }\n    }\n\n    // The $com_connect_flags[\'this_form\'] global is set if an id is supplied for the <form>.\n    // This value then becomes the default value for all html_form (a.k.a. form=)\n    // attributes for any input tags in this tag\'s container, providing HTML5 is in use.\n    $com_connect_flags[\'this_form\'] = \'com\' . $com_connect_form_id;\n\n    // Global toggle for required attribute.\n    $com_connect_flags[\'required\'] = $required;\n\n    $now = time();\n    $now_date = date(\'Y-m-d H:i:s\', $now);\n\n    $expire = abs(assert_int($expire));\n\n    static $headers_sent = false;\n    if (!$headers_sent) {\n        header(\'Last-Modified: \' . gmdate(\'D, d M Y H:i:s\', $now - (3600 * 24 * 7)) . \' GMT\');\n        header(\'Expires: \' . gmdate(\'D, d M Y H:i:s\', $now + $expire) . \' GMT\');\n        header(\'Cache-Control: no-cache, must-revalidate\');\n        $headers_sent = true;\n    }\n\n    $nonce = doSlash(ps(\'com_connect_nonce\'));\n    $renonce = false;\n\n    if ($com_connect_submit) {\n        // Could use \"interval $expire second\" but multiple com_connect forms could delete data\n        // that might be in use by other forms.\n        // TODO: use max(600, $expire)? Not perfect, but ensures a safe minimum deletion rate.\n        safe_delete(\'txp_discuss_nonce\', \"issue_time < date_sub(\'$now_date\', interval 10 minute)\");\n        if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\")) {\n            if ($rs[\'used\']) {\n                unset($com_connect_error);\n                $com_connect_error[] = gTxt(\'com_connect_form_used\');\n                $renonce = true;\n                $_POST = array();\n                $_POST[\'com_connect_submit\'] = true;\n                $_POST[\'com_connect_form_id\'] = $com_connect_form_id;\n                $_POST[\'com_connect_nonce\'] = $nonce;\n            }\n        } else {\n            $com_connect_error[] = gTxt(\'com_connect_form_expired\');\n            $renonce = true;\n        }\n    }\n\n    if ($com_connect_submit && $nonce && !$renonce) {\n        $com_connect_nonce = $nonce;\n    } elseif (!$show_error || $show_input) {\n        $com_connect_nonce = md5(uniqid(rand(), true));\n        safe_insert(\'txp_discuss_nonce\', \"issue_time = \'\" . $now_date . \"\', nonce = \'$com_connect_nonce\'\");\n    }\n\n    $form = ($form) ? fetch_form($form) : $thing;\n\n    if (empty($form)) {\n        $form = \'\n<txp:com_connect_text label=\"\'.gTxt(\'com_connect_name\').\'\" /><br />\n<txp:com_connect_email /><br />\'.\n($send_article ? \'<txp:com_connect_email send_article=\"1\" label=\"\'.gTxt(\'com_connect_recipient\').\'\" /><br />\' : \'\').\n\'<txp:com_connect_textarea /><br />\n<txp:com_connect_submit />\n\';\n    }\n\n    $form = parse($form);\n\n    // Perform aggregate functions for checking radio sets.\n    if ($com_connect_submit) {\n        com_connect_group_validate();\n    }\n\n    if ($to_form) {\n        $to = parse_form($to_form);\n    }\n\n    if (!$to && !$send_article) {\n        return gTxt(\'com_connect_to_missing\');\n    }\n\n    $out = \'\';\n\n    if (!$com_connect_submit) {\n        // Don\'t show errors or send mail.\n    } elseif (!empty($com_connect_error)) {\n        if ($show_error || !$show_input) {\n            $out .= n.doWrap(array_unique($com_connect_error), \'ul\', \'li\', $com_connect_flags[\'cls_wrapper\']).n;\n\n            if (!$show_input) {\n                return $out;\n            }\n        }\n    } elseif ($show_input && is_array($com_connect_form)) {\n        // Load and check spam plugins.\n        callback_event(\'comconnect.submit\');\n        $evaluation =& get_comconnect_evaluator();\n        $clean = $evaluation->get_comconnect_status();\n\n        if ($clean != 0) {\n            return gTxt(\'com_connect_spam\');\n        }\n\n        if ($from_form) {\n            $from = parse_form($from_form);\n        }\n\n        if ($subject_form) {\n            $subject = parse_form($subject_form);\n        }\n\n        $sep = IS_WIN ? \"\\r\\n\" : \"\\n\";\n        $msg = array();\n        $fields = array();\n\n        foreach ($com_connect_labels as $name => $lbl) {\n            if (!is_array($com_connect_values[$name])) {\n                if (trim($com_connect_values[$name]) === false) {\n                    continue;\n                }\n\n                $msg[] = $lbl . \': \' . $com_connect_values[$name];\n            }\n\n            $fields[$name] = $com_connect_values[$name];\n        }\n\n        if ($send_article) {\n            global $thisarticle;\n\n            $subject = str_replace(\'&#38;\', \'&\', $thisarticle[\'title\']);\n            $msg[] = permlinkurl($thisarticle);\n            $msg[] = $subject;\n            $s_ar = array(\'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8217;\', \'&#8242;\', \'&#8243;\', \'&#8230;\', \'&#8211;\', \'&#8212;\', \'&#215;\', \'&#8482;\', \'&#174;\', \'&#169;\', \'&lt;\', \'&gt;\', \'&quot;\', \'&amp;\', \'&#38;\', \"\\t\", \'<p\');\n\n            if ($override_email_charset) {\n                $r_ar = array(\"\'\", \"\'\", \'\"\', \'\"\', \"\'\", \"\'\", \'\"\', \'...\', \'-\', \'--\', \'x\', \'[tm]\', \'(r)\', \'(c)\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n            } else {\n                $r_ar = array(\'‘\', \'’\', \'“\', \'”\', \'’\', \'?\', \'?\', \'…\', \'–\', \'—\', \'×\', \'™\', \'®\', \'©\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n            }\n\n            $msg[] = trim(strip_tags(str_replace($s_ar, $r_ar, (trim(strip_tags($thisarticle[\'excerpt\'])) ? $thisarticle[\'excerpt\'] : $thisarticle[\'body\']))));\n\n            if (empty($com_connect_recipient)) {\n                return gTxt(\'com_connect_field_missing\', array(\'{field}\' => gTxt(\'com_connect_recipient\')));\n            } else {\n                $to = $com_connect_recipient;\n            }\n        }\n\n        $msg = implode(\"\\n\\n\", $msg);\n\n        if ($body_form) {\n            $msg = parse_form($body_form);\n        }\n\n        $msg = str_replace(array(\"\\r\\n\", \"\\r\", \"\\n\"), array(\"\\n\", \"\\n\", $sep), $msg);\n\n        $reply   = com_connect_strip($from ? $com_connect_from : \'\');\n        $from    = com_connect_strip($from ? $from : $com_connect_from);\n        $to      = com_connect_strip($to);\n        $subject = com_connect_strip($subject);\n        $msg     = com_connect_strip($msg, false);\n\n        if ($override_email_charset) {\n            $charset = \'ISO-8859-1\';\n            $subject = utf8_decode($subject);\n            $msg     = utf8_decode($msg);\n        } else {\n            $charset = \'UTF-8\';\n        }\n\n        // TODO: function deprecated in 4.6.0.\n        $subject = encode_mailheader($subject, \'text\');\n\n        $headers = array(\n            \'from\'          => $from,\n            \'separator\'     => $sep,\n            \'reply\'         => $reply,\n            \'charset\'       => $charset,\n            \'content_type\'  => \'text/plain\',\n            \'xfer_encoding\' => \'8bit\',\n        );\n\n        safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = \'$now_date\'\", \"nonce = \'$nonce\'\");\n\n        if (com_connect_deliver($to, $subject, $msg, $headers, $fields, array(\'isCopy\' => false))) {\n            $_POST = array();\n\n            if ($copysender && $com_connect_from) {\n                com_connect_deliver(com_connect_strip($com_connect_from), $subject, $msg, $headers, $fields, array(\'isCopy\' => true));\n            }\n\n            if ($redirect) {\n                while (@ob_end_clean());\n                $uri = hu.ltrim($redirect,\'/\');\n\n                if (empty($_SERVER[\'FCGI_ROLE\']) && empty($_ENV[\'FCGI_ROLE\'])) {\n                    txp_status_header(\'303 See Other\');\n                    header(\'Location: \'.$uri);\n                    header(\'Connection: close\');\n                    header(\'Content-Length: 0\');\n                } else {\n                    $uri = txpspecialchars($uri);\n                    $refresh = gTxt(\'com_connect_refresh\');\n                    echo <<<END\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>$sitename</title>\n    <meta http-equiv=\"refresh\" content=\"0;url=$uri\">\n</head>\n<body>\n    <a href=\"$uri\">$refresh</a>\n</body>\n</html>\nEND;\n                }\n\n                exit;\n            } else {\n                return \'<div class=\"\' . $com_connect_flags[\'cls_thanks\'] . \'\" id=\"com\'.$com_connect_form_id.\'\">\' .\n                    ($thanks_form ? parse_form($thanks_form) : $thanks) .\n                    \'</div>\';\n            }\n        } else {\n            // Plugin modules may have set error messages: display if appropriate.\n            if ($com_connect_error) {\n                $out .= n.doWrap(array_unique($com_connect_error), \'ul\', \'li\', $com_connect_flags[\'cls_wrapper\']).n;\n            } else {\n                $out .= graf(gTxt(\'com_connect_mail_sorry\'));\n            }\n        }\n    }\n\n    if ($show_input && !$send_article || gps(\'com_connect_send_article\')) {\n        $contactForm = \'<form method=\"post\"\' . ((!$show_error && $com_connect_error) ? \'\' : \' id=\"com\' . $com_connect_form_id . \'\"\') .\n            ($class ? \' class=\"\' . $class . \'\"\' : \'\') .\n            ($browser_validate ? \'\' : \' novalidate\') .\n            \' action=\"\' . txpspecialchars(serverSet(\'REQUEST_URI\')) . \'#com\' . $com_connect_form_id . \'\">\' .\n            ($label ? n . \'<fieldset>\' : \'\') .\n            ($label ? n . \'<legend>\' . txpspecialchars($label) . \'</legend>\' : \'\') .\n            $out .\n            n . \'<input type=\"hidden\" name=\"com_connect_nonce\" value=\"\' . $com_connect_nonce . \'\" />\' .\n            n . \'<input type=\"hidden\" name=\"com_connect_form_id\" value=\"\' . $com_connect_form_id . \'\" />\' .\n            $form .\n            callback_event(\'comconnect.form\') .\n            ($label ? (n . \'</fieldset>\') : \'\') .\n            n . \'</form>\';\n\n        callback_event_ref(\'comconnect.render\', \'\', 0, $contactForm, $atts);\n\n        return $contactForm;\n    }\n\n    return \'\';\n}\n\n/**\n * Tag: Render a text input field.\n *\n * Many different $types are allowed. In fact, the $type attribute is not\n * restricted in any way to allow for any future types. This does bring some\n * developer responsibility if you wish your document to validate because\n * not all attributes will be valid for all input types, despite the plugin\n * faithfully rendering whatever attributes it is given.\n *\n * A lot of invalid situations are recognised (e.g. only permitting max/min/step\n * to occur on \'numeric\' input types) but some are not easily trappable so they\n * are left to user discretion.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_text($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comText\',\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'inputmode\'      => \'\',\n        \'label\'          => gTxt(\'com_connect_text\'),\n        \'label_position\' => \'before\',\n        \'max\'            => null,\n        \'min\'            => null,\n        \'name\'           => \'\',\n        \'pattern\'        => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'size\'           => \'\',\n        \'step\'           => \'\',\n        \'type\'           => \'text\',\n    ), $atts));\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    $datetime_types = array(\n        \'date\',\n        \'datetime\',\n        \'datetime-local\',\n        \'month\',\n        \'time\',\n        \'week\',\n    );\n\n    $numeric_types = array(\n        \'number\',\n        \'range\',\n    );\n\n    $is_datetime = (in_array($type, $datetime_types));\n    $is_numeric = (in_array($type, $numeric_types));\n\n    // Dates / times get special treatment: no default min/max if not set by tag.\n    if (!$is_datetime && $min === null) {\n        $min = 0;\n    }\n\n    if (!$is_datetime && $max === null) {\n        $max = 100;\n    }\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    if ($com_connect_submit) {\n        $value = trim(ps($name));\n        $utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\n        $hlabel = txpspecialchars($label);\n        $datetime_ok = true;\n\n        if ($is_datetime) {\n            $minval = $min;\n            $maxval = $max;\n            $cmpval = $value;\n\n            try {\n                $dt = new DateTime($cmpval);\n                $cmpval = $dt->format(\'U\');\n\n                if ($min) {\n                    $dt = new DateTime($min);\n                    $minval = $dt->format(\'U\');\n                }\n\n                if ($max) {\n                    $dt = new DateTime($max);\n                    $maxval = $dt->format(\'U\');\n                }\n            } catch (Exception $e) {\n                $datetime_ok = false;\n            }\n        }\n\n        if (strlen($value)) {\n            if (!$utf8len) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_utf8\', array(\'{field}\' => $hlabel));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($is_datetime && !$datetime_ok) {\n                $com_connect_error[] = gTxt(\'com_connect_format_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $value));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && !$is_numeric && !$is_datetime && $utf8len < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_min_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && !$is_numeric && !$is_datetime && $utf8len > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_max_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $is_datetime && $cmpval < $minval) {\n                $com_connect_error[] = gTxt(\'com_connect_minval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $is_datetime && $cmpval > $maxval) {\n                $com_connect_error[] = gTxt(\'com_connect_maxval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $is_numeric && $value < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_minval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $is_numeric && $value > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_maxval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($pattern and !preg_match(\'/^\'.$pattern.\'$/\', $value)) {\n                $com_connect_error[] = gTxt(\'com_connect_pattern_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $pattern));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                com_connect_store($name, $label, $value);\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => $hlabel));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $default;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => (isset($id) ? $id : $name),\n        \'name\'  => $name,\n        \'type\'  => $type,\n        \'value\' => $value,\n    ));\n\n    if ($size && !$is_numeric) {\n        $attr[\'size\'] = \'size=\"\' . intval($size) . \'\"\';\n    }\n\n    if ($min && !$is_numeric) {\n        $attr[\'minlength\'] = \'minlength=\"\' . intval($min) . \'\"\';\n    }\n\n    if ($max && !$is_numeric) {\n        $attr[\'maxlength\'] = \'maxlength=\"\' . intval($max) . \'\"\';\n    }\n\n    if ($doctype !== \'xhtml\' && ($is_numeric || $is_datetime)) {\n        // Not using intval() because min/max/step can be floating point values.\n        $attr += com_connect_build_atts(array(\n            \'min\'  => $min,\n            \'max\'  => $max,\n            \'step\' => $step,\n        ));\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'autocomplete\' => $autocomplete,\n            \'form\'         => $html_form,\n            \'inputmode\'    => $inputmode,\n            \'pattern\'      => $pattern,\n            \'placeholder\'  => $placeholder,\n            \'required\'     => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render an email input field.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_email($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_from, $com_connect_recipient, $com_connect_flags;\n\n    // TODO: \'multiple\' attribute?\n    $defaults = array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comEmail\',\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_email\'),\n        \'label_position\' => \'before\',\n        \'max\'            => 100,\n        \'min\'            => 0,\n        \'name\'           => \'\',\n        \'pattern\'        => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'send_article\'   => 0,\n        \'size\'           => \'\',\n        \'type\'           => \'email\',\n    );\n\n    extract(com_connect_lAtts($defaults, $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $email = $com_connect_submit ? trim(ps($name)) : $default;\n\n    if ($com_connect_submit && strlen($email)) {\n        if (!is_valid_email($email)) {\n            $com_connect_error[] = gTxt(\'com_connect_invalid_email\', array(\'{email}\' => txpspecialchars($email)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        } else {\n            preg_match(\"/@(.+)$/\", $email, $match);\n            $domain = $match[1];\n\n            if (is_callable(\'checkdnsrr\') && checkdnsrr(\'textpattern.com.\',\'A\') && !checkdnsrr($domain.\'.\',\'MX\') && !checkdnsrr($domain.\'.\',\'A\')) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_host\', array(\'{host}\' => txpspecialchars($domain)));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                if ($send_article) {\n                    $com_connect_recipient = $email;\n                } else {\n                    $com_connect_from = $email;\n                }\n            }\n        }\n    }\n\n    $passed_atts = array();\n\n    foreach ($defaults as $key => $value) {\n        $passed_atts[$key] = $$key;\n    }\n\n    $passed_atts[\'isError\'] = $isError;\n    unset ($passed_atts[\'send_article\']);\n\n    return com_connect_text($passed_atts);\n}\n\n/**\n * Tag: Render a textarea input field.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_textarea($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comTextarea\',\n        \'cols\'           => 58,\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_message\'),\n        \'label_position\' => \'before\',\n        \'max\'            => 10000,\n        \'min\'            => 0,\n        \'name\'           => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'rows\'           => 8,\n        \'wrap\'           => \'\',\n    ), $atts));\n\n    $min = intval($min);\n    $max = intval($max);\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\n        $utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\n        $hlabel = txpspecialchars($label);\n\n        if (strlen(ltrim($value))) {\n            if (!$utf8len) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_utf8\', array(\'{field}\' => $hlabel));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $utf8len < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_min_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $utf8len > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_max_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                com_connect_store($name, $label, $value);\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => $hlabel));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $default;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'        => (isset($id) ? $id : $name),\n        \'name\'      => $name,\n        \'cols\'      => intval($cols),\n        \'rows\'      => intval($rows),\n        \'maxlength\' => $max,\n    ));\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'autocomplete\' => $autocomplete,\n            \'form\'         => $html_form,\n            \'placeholder\'  => $placeholder,\n            \'required\'     => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<textarea\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . txpspecialchars($value) . \'</textarea>\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a select/option input list.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_select($atts, $thing = null)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    // TODO: multiple attribute?\n    extract(com_connect_lAtts(array(\n        \'break\'          => br,\n        \'class\'          => \'comSelect\',\n        \'delimiter\'      => \',\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_option\'),\n        \'label_position\' => \'before\',\n        \'list\'           => \'\', // TODO: remove from here in favour of the global list attribute.\n        \'options\'        => gTxt(\'com_connect_general_inquiry\'),\n        \'name\'           => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'selected\'       => \'\',\n        \'size\'           => \'\',\n    ), $atts));\n\n    // Detect old-school use of the list attribute. Note that deprecated_function_with\n    // isn\'t strictly the correct error to throw, but it\'s close enough until core has\n    // a dedicated deprecated_attribute_with string.\n    if (!empty($list) && strpos($list, $delimiter) !== false) {\n        $options = $list;\n        trigger_error(gTxt(\'deprecated_function_with\', array(\'{name}\' => \'list\', \'{with}\' => \'options\')), E_USER_NOTICE);\n        unset($list, $atts[\'list\']);\n    }\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $value = ($com_connect_submit) ? trim(ps($name)) : $selected;\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($thing) {\n        com_connect_option(null, $value);\n        $out = parse($thing);\n        $options = com_connect_option(null, null);\n    } else {\n        $options = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \', $options)));\n        $out = \'\';\n\n        foreach ($options as $item) {\n            $out .= n.t.\'<option\' . ($item == $value ? \' selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\') . \'>\' : \'>\') . (strlen($item) ? txpspecialchars($item) : \' \') . \'</option>\';\n        }\n    }\n\n    if ($com_connect_submit) {\n        if (strlen($value)) {\n            if (in_array($value, $options)) {\n                com_connect_store($name, $label, $value);\n            } else {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_value\', array(\'{field}\' => txpspecialchars($label), \'{value}\' => txpspecialchars($value)));\n                $isError = $com_connect_flags[\'cls_element\'];\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($label)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $selected;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'   => (isset($id) ? $id : $name),\n        \'name\' => $name,\n    ));\n\n    if ($size && is_numeric($size)) {\n        $attr[\'size\'] = \'size=\"\' . intval($size) . \'\"\';\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        n . \'<select\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' .\n            $out .\n        n . \'</select>\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a set of select options.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Tag\'s container\n * @return string HTML\n */\nfunction com_connect_option($atts, $thing = null)\n{\n    static $options;\n    static $match;\n\n    if ($atts === null) {\n        if ($thing === null) {\n            return $options;\n        } else {\n            $match = $thing;\n            return;\n        }\n    }\n\n    global $com_connect_error, $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'class\'    => \'comOption\',\n        \'label\'    => null,\n        \'selected\' => null,\n        \'value\'    => null,\n    ), $atts));\n\n    $val = ($value === null) ? (!empty($thing) ? $thing : (!empty($label) ? $label : null)) : $value;\n    $label = ($label === null) ? (!empty($thing) ? $thing : (!empty($value) ? $value : null)) : $label;\n\n    $attr = array();\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $options[] = $val;\n\n        if ($val !== null && ((string)$val === (string)$match)) {\n            $attr[] = \'selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\');\n        }\n    } elseif ($selected || ($val !== null && ((string)$val === (string)$match))) {\n        $attr[] = \'selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\');\n    }\n\n    // Core attributes.\n    $attr += com_connect_build_atts(array(\n        \'value\' => $val,\n    ));\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classStr = (($class) ? \' class=\"\' . $class . \'\"\' : \'\');\n\n    return \'<option\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . txpspecialchars($label) . \'</option>\';\n}\n\n/**\n * Tag: Render a checkbox.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n * @todo checkbox groups?\n */\nfunction com_connect_checkbox($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'break\'          => \' \',\n        \'checked\'        => 0,\n        \'class\'          => \'comCheckbox\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_checkbox\'),\n        \'label_position\' => \'after\',\n        \'name\'           => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'value\'          => null,\n    ), $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $theValue = (bool) ps($name);\n\n        if ($required && !$theValue) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($label)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        } else {\n            $toStore = (($value !== null && $theValue) ? $value : ($theValue ? gTxt(\'yes\') : gTxt(\'no\')));\n            com_connect_store($name, $label, $toStore);\n        }\n    } else {\n        $theValue = $checked;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => (isset($id) ? $id : $name),\n        \'name\'  => $name,\n    ));\n\n    if ($value !== null) {\n        $attr += com_connect_build_atts(array(\n            \'value\' => $value,\n        ));\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input type=\"checkbox\"\' . $classStr .\n            ($theValue ? \' checked\' . (($doctype === \'xhtml\') ? \'=\"checked\"\' : \'\') : \'\') . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a radio button.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n */\nfunction com_connect_radio($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_values, $com_connect_flags, $com_connect_group;\n\n    extract(com_connect_lAtts(array(\n        \'break\'          => \' \',\n        \'checked\'        => 0,\n        \'class\'          => \'comRadio\',\n        \'group\'          => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_option\'),\n        \'label_position\' => \'after\',\n        \'name\'           => \'\',\n        \'required\'       => null,\n        \'value\'          => null,\n    ), $atts));\n\n    static $cur_name = array();\n    static $cur_req = array();\n    static $cur_group = null;\n\n    if (!$group) {\n        if ($cur_group === null) {\n            $group = gTxt(\'com_connect_radio\');\n        } else {\n            $group = $cur_group;\n        }\n    } else {\n        if (!$name) {\n            $name = $group;\n        }\n    }\n\n    if (isset($cur_name[$group])) {\n        if (!$name) {\n            $name = $cur_name[$group];\n        }\n    } else {\n        if ($name) {\n            $cur_name[$group] = $name;\n        } else {\n            $name = $cur_name[$group] = gTxt(\'com_connect_radio\');\n        }\n    }\n\n    if (isset($cur_req[$group])) {\n        if ($required === null) {\n            $required = $cur_req[$group];\n        }\n    } else {\n        if ($required === null) {\n            $required = $cur_req[$group] = $com_connect_flags[\'required\'];\n        } else {\n            $cur_req[$group] = $required;\n        }\n    }\n\n    $cur_group = $group;\n\n    $id = \'q\' . md5($name . \'=>\' . $label);\n    $name = com_connect_label2name($name);\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n    $com_connect_group[$name][$id][\'req\'] = $required;\n    $com_connect_group[$name][$id][\'label\'] = $group;\n\n    if ($com_connect_submit) {\n        $toCompare = ($value === null ? $id : $value);\n        $is_checked = (ps($name) == $toCompare);\n        $com_connect_group[$name][$id][\'isSet\'] = $is_checked;\n\n        if ($is_checked || $checked && !isset($com_connect_values[$name])) {\n            com_connect_store($name, $group, ($value !== null ? $value : $label));\n        }\n    } else {\n        $is_checked = $checked;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => $id,\n        \'name\'  => $name,\n        \'value\' => ($value !== null ? $value : $id),\n    ));\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $id . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input type=\"radio\"\'. $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') .\n            ( $is_checked ? \' checked\' . (($doctype === \'xhtml\') ? \'=\"checked\"\' : \'\'). \' />\' : \' />\') .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Store server information in the payload.\n *\n * @param  array  $atts  Tag attributes\n */\nfunction com_connect_serverinfo($atts)\n{\n    global $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n    ), $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    if (strlen($name) && $com_connect_submit) {\n        if (!$label) {\n            $label = $name;\n        }\n\n        com_connect_store($name, $label, serverSet($name));\n    }\n}\n\n/**\n * Tag: Store a secret value in the payload.\n *\n * @param  array  $atts  Tag attributes\n */\nfunction com_connect_secret($atts, $thing = null)\n{\n    global $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'label\' => gTxt(\'com_connect_secret\'),\n        \'name\'  => \'\',\n        \'value\' => \'\',\n    ), $atts));\n\n    $name = com_connect_label2name($name ? $name : $label);\n\n    if ($com_connect_submit) {\n        if ($thing) {\n            $value = trim(parse($thing));\n        }\n\n        com_connect_store($name, $label, $value);\n    }\n\n    return \'\';\n}\n\n/**\n * Tag: Render a submit button.\n *\n * If the container is used, a button tag is rendered, otherwise a\n * standard input type=submit button is rendered.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Tag\'s container\n * @return string HTML\n */\nfunction com_connect_submit($atts, $thing = null)\n{\n    global $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'class\'     => \'comSubmit\',\n        \'html_form\' => $com_connect_flags[\'this_form\'],\n        \'label\'     => gTxt(\'com_connect_send\'),\n    ), $atts));\n\n    $label = txpspecialchars($label);\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    $attr = array();\n\n    // HTML5 attributes.\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\' => $html_form,\n        ));\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classStr = ($class ? \' class=\"\' . $class . \'\"\' : \'\');\n\n    if (strlen($thing)) {\n        return \'<button type=\"submit\"\' . $classStr . \' name=\"com_connect_submit\" value=\"\' . $label . \'\"\' . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . ($thing ? trim(parse($thing)) : $label) . \'</button>\';\n    } else {\n        return \'<input type=\"submit\"\' . $classStr . \' name=\"com_connect_submit\" value=\"\' . $label . \'\"\' . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\';\n    }\n}\n\n/**\n * Tag: Render a link to allow someone to forward a page to a friend.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n */\nfunction com_connect_send_article($atts)\n{\n    if (!isset($_REQUEST[\'com_connect_send_article\'])) {\n        $linktext = (empty($atts[\'linktext\'])) ? gTxt(\'com_connect_send_article\') : $atts[\'linktext\'];\n        $join = (empty($_SERVER[\'QUERY_STRING\'])) ? \'?\' : \'&\';\n        $href = $_SERVER[\'REQUEST_URI\'] . $join . \'com_connect_send_article=yes\';\n\n        return \'<a href=\"\' . txpspecialchars($href) . \'\" rel=\"nofollow\">\' . txpspecialchars($linktext) . \'</a>\';\n    }\n\n    return;\n}\n\n/**\n * Perform post-processing for aggregate (group) controls like radio sets.\n *\n * @todo Can this be done any neater?\n * @todo Should this be exposed as a callback to allow plugins to extend the functionality?\n */\nfunction com_connect_group_validate()\n{\n    global $com_connect_group, $com_connect_error;\n\n    $flags = array();\n\n    if ($com_connect_group) {\n        foreach ($com_connect_group as $key => $grp) {\n            foreach ($grp as $id => $atts) {\n                $flags[$key][\'label\'] = $atts[\'label\'];\n\n                if (!empty($atts[\'req\']) && !isset($flags[$key][\'req\'])) {\n                    $flags[$key][\'req\'] = 1;\n                }\n\n                if (!empty($atts[\'isSet\']) && !isset($flags[$key][\'isSet\'])) {\n                    $flags[$key][\'isSet\'] = 1;\n                }\n            }\n        }\n\n        foreach ($flags as $key => $data) {\n            if (!empty($data[\'req\']) && !isset($data[\'isSet\'])) {\n                $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($data[\'label\'])));\n            }\n        }\n    }\n}\n\n/**\n * Process tag attributes and set defaults.\n *\n * Handles HTML \'global\' attributes too, though what we consider global differs slightly\n * from the W3C: any attributes that apply to most of the <input> tags is considered\n * global. Just saves repetitive code in each tag handler.\n *\n * Note that \'class\' is omitted, because that has different default values per input\n * widget, so it is dealt with explicitly in each tag handler.\n *\n * @param  array $arr  Acceptable tag attributes\n * @param  array $atts User-submitted attribute values\n * @return array\n */\nfunction com_connect_lAtts($pairs, $atts)\n{\n    lAtts(array(\'label\' => \'\'), $atts, false);\n\n    foreach (array(\'button\', \'copysender\', \'checked\', \'required\', \'send_article\', \'show_input\', \'show_error\') as $key) {\n        if (isset($atts[$key])) {\n            $atts[$key] = ($atts[$key] === \'yes\' || intval($atts[$key])) ? 1 : 0;\n        }\n    }\n\n    if (isset($atts[\'break\']) && $atts[\'break\'] === \'br\') {\n        $atts[\'break\'] = \'<br />\';\n    }\n\n    $com_connect_globals = array(\n        \'accesskey\'  => \'\',\n        \'autofocus\'  => \'\',\n        \'dir\'        => \'\',\n        \'disabled\'   => \'\',\n        \'hidden\'     => \'\',\n        \'id\'         => \'\',\n        \'lang\'       => \'\',\n        \'list\'       => \'\',\n        \'readonly\'   => \'\',\n        \'spellcheck\' => \'\',\n        \'style\'      => \'\',\n        \'tabindex\'   => \'\',\n        \'title\'      => \'\',\n        \'translate\'  => \'\',\n    );\n\n    foreach ($atts as $name => $value) {\n        if (array_key_exists($name, $pairs) || substr($name, 0, 2) === \'on\' || array_key_exists($name, $com_connect_globals)) {\n            $pairs[$name] = $value;\n        } elseif (get_pref(\'production_status\') != \'live\') {\n            trigger_error(gTxt(\'unknown_attribute\', array(\'{att}\' => $name)));\n        }\n    }\n\n    $pairs[\'com_connect_globals\'] = $com_connect_globals;\n\n    return ($pairs) ? $pairs : false;\n}\n\n/**\n * Build a sanitized, unique attribute array from the given pairs.\n *\n * If called multiple times, earlier values supersede later values\n * if the keys clash.\n *\n * @param  array $pairs    Attribute tuples to compile\n * @param  array $defaults Attribute values to use if the $pairs tuple value is empty\n * @return array\n */\nfunction com_connect_build_atts($pairs, $defaults = array())\n{\n    $attr = array();\n\n    $booleans = array(\n        \'autofocus\',\n        \'checked\',\n        \'disabled\',\n        \'hidden\',\n        \'multiple\',\n        \'readonly\',\n        \'required\',\n        \'selected\',\n        );\n\n    foreach ($pairs as $key => $value) {\n        if ($value !== \'\' && $value !== null) {\n            $attr[$key] = $key . (in_array($key, $booleans) ? \'\' : \'=\"\' . txpspecialchars($value) . \'\"\');\n        } else {\n            if (isset($defaults[$key])) {\n                $attr[$key] = $key . (in_array($key, $booleans) ? \'\' : \'=\"\' . txpspecialchars($defaults[$key]) . \'\"\');\n            }\n        }\n    }\n\n    return $attr;\n}\n\n/**\n * Replace nulls and newlines in content with spaces.\n *\n * @param string $str    String to sanitize\n * @param bool   $header Whether the string is a header or not\n */\nfunction com_connect_strip($str, $header = true)\n{\n    if ($header) {\n        // TODO: strip_rn will be deprecated in 4.6.0.\n        $str = strip_rn($str);\n    }\n\n    return preg_replace(\'/[\\x00]/\', \' \', $str);\n}\n\n/**\n * Handle content delivery of payload.\n *\n * Triggers a \'comconnect.deliver\' callback event to override or augment the\n * delivery mechanism. Third party plugins can make alterations to the $payload,\n * then return one of the strings:\n *  -> \"comconnect.send\" (or no return value) to allow the plugin to continue mailing after the 3rd party plugin completes\n *  -> \"comconnect.skip\" to skip com_connect\'s mailing (i.e. the 3rd party handles mailing) and return \'success\'\n *  -> \"comconnect.fail\" to skip com_connect\'s mailing and return \'fail\'\n *\n * By hooking into the callback\'s step you can target either the main \'send\'\n * process or the \'copysender\' process. Examples of things you could do:\n * -> Add Multi-part MIME headers for HTML emails.\n * -> Add CC: or BCC: fields.\n * -> Subscribe people to mailing lists.\n * -> Handle the mailing process independently of Textpattern.\n *\n * @param string $to      Delivery address\n * @param string $subject Subject of message\n * @param string $body    Message content\n * @param array  $headers Message headers as tuples\n * @param array  $fields  Message field names and content as tuples\n * @param array  $flags   Signals to govern delivery / callback behaviour\n */\nfunction com_connect_deliver($to, $subject, $body, $headers, $fields, $flags)\n{\n    $payload = array(\n        \'to\'      => $to,\n        \'subject\' => $subject,\n        \'headers\' => $headers,\n        \'body\'    => $body,\n        \'fields\'  => $fields,\n    );\n\n    $flavour = ($flags[\'isCopy\'] === true) ? \'copysender\' : \'send\';\n\n    // Allow plugins to override or alter default action (mail) if required.\n    // TODO: use has_handler() from 4.6.0+.\n    $ret = callback_event_ref(\'comconnect.deliver\', $flavour, 0, $payload);\n\n    if (in_array(\'comconnect.fail\', $ret)) {\n        return false;\n    } elseif (in_array(\'comconnect.skip\', $ret)) {\n        return true;\n    }\n\n    extract($payload);\n\n    $smtp_from = get_pref(\'smtp_from\');\n\n    if (!is_callable(\'mail\')) {\n        return (get_pref(\'production_status\') === \'live\')\n            ? gTxt(\'com_connect_mail_sorry\')\n            : gTxt(\'warn_mail_unavailable\');\n    }\n\n    $sep = (!empty($headers[\'separator\'])) ? $headers[\'separator\'] : (IS_WIN ? \"\\r\\n\" : \"\\n\");\n    $xfer_encoding = (!empty($headers[\'xfer_encoding\'])) ? $headers[\'xfer_encoding\'] : \'8bit\';\n    $content_type = (!empty($headers[\'content_type\'])) ? $headers[\'content_type\'] : \'text/plain\';\n    $reply = (!empty($headers[\'reply\'])) ? $headers[\'reply\'] : \'\';\n    $charset = (!empty($headers[\'charset\'])) ? $headers[\'charset\'] : \'UTF-8\';\n    $x_mailer = (!empty($headers[\'x_mailer\'])) ? $headers[\'x_mailer\'] : \'Textpattern (com_connect)\';\n\n    $header_string = \'From: \' . $headers[\'from\'] .\n        ($reply ? ($sep . \'Reply-To: \' . $reply) : \'\') .\n        $sep . \'X-Mailer: \' . $x_mailer .\n        $sep . \'X-Originating-IP: \' . com_connect_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'] . \' via \' : \'\') . $_SERVER[\'REMOTE_ADDR\']) .\n        $sep . \'Content-Transfer-Encoding: \' . $xfer_encoding .\n        $sep . \'Content-Type: \' . $content_type . \'; charset=\"\' . $charset . \'\"\';\n\n    // Remove all the header entries that have already been handled.\n    unset(\n        $headers[\'separator\'],\n        $headers[\'xfer_encoding\'],\n        $headers[\'content_type\'],\n        $headers[\'reply\'],\n        $headers[\'charset\'],\n        $headers[\'x_mailer\'],\n        $headers[\'from\']\n    );\n\n    // Any remaining headers set by plugins are appended as-is.\n    foreach ($headers as $name => $value) {\n        $header_string .= $sep . $name . \': \' . $value;\n    }\n\n    if (is_valid_email($smtp_from)) {\n        if (IS_WIN) {\n            ini_set(\'sendmail_from\', $smtp_from);\n        } elseif (!ini_get(\'safe_mode\')) {\n            return mail($to, $subject, $body, $header_string, \'-f\'.$smtp_from);\n        }\n    }\n\n    return mail($to, $subject, $body, $header_string);\n}\n\n/**\n * Evaluate return values from plugins.\n */\nclass comconnect_evaluation\n{\n    private $status;\n\n    /**\n     * Constructor.\n     */\n    function __construct()\n    {\n        $this->status = 0;\n    }\n\n    /**\n     * Append the given status to the counter.\n     */\n    function add_comconnect_status($check)\n    {\n        $this->status += $check;\n    }\n\n    /**\n     * Fetch the current evaluator status.\n     */\n    function get_comconnect_status()\n    {\n        return $this->status;\n    }\n}\n\n/**\n * Evaluator singleton for checking return values from plugins.\n */\nfunction &get_comconnect_evaluator()\n{\n    static $instance;\n\n    if (!isset($instance)) {\n        $instance = new comconnect_evaluation();\n    }\n\n    return $instance;\n}\n\n/**\n * Convert the given label to a suitably unique name.\n *\n * @param string $label Label to convert to name.\n */\nfunction com_connect_label2name($label)\n{\n    $label = trim($label);\n\n    if (strlen($label) == 0) {\n        return \'invalid\';\n    }\n\n    if (strlen($label) <= 32 && preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) {\n        return $label;\n    } else {\n        return \'q\' . md5($label);\n    }\n}\n\n/**\n * Store the given names/values in the global arrays.\n *\n * @param  string $name  Parameter name\n * @param  string $label Parameter label\n * @param  string $value Parameter value\n */\nfunction com_connect_store($name, $label, $value)\n{\n    global $com_connect_form, $com_connect_labels, $com_connect_values;\n\n    $com_connect_form[$label] = $value;\n    $com_connect_labels[$name] = $label;\n    $com_connect_values[$name] = $value;\n}\n\n/**\n * Override the language strings if necessary.\n *\n * @param  string $lang Language designator (e.g. fr-fr)\n * @return array        Partial language array to merge with $textarray\n */\nfunction com_connect_load_lang($lang = LANG)\n{\n    $out = array();\n\n    if ($lang != LANG) {\n\n        $rs = safe_rows(\"name, data\", \'txp_lang\', \"lang = \'\" . doSlash($lang) . \"\' AND name like \'com\\_connect\\_%\'\");\n\n        if (!empty($rs)) {\n            foreach ($rs as $a) {\n                $out[$a[\'name\']] = $a[\'data\'];\n            }\n        }\n    }\n\n    return $out;\n}\n\n/**\n * Return the value of the given attribute, by name or its label.\n *\n * @param  array $atts Attribute to return\n * @return string\n */\nfunction com_connect_value($atts)\n{\n    global $com_connect_values, $com_connect_form;\n\n    extract(lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n    ), $atts));\n\n    $str = \'\';\n\n    if ($name) {\n        $str = isset($com_connect_values[$name]) ? $com_connect_values[$name] : \'\';\n    } elseif ($label) {\n        $str = isset($com_connect_form[$name]) ? $com_connect_form[$name] : \'\';\n    }\n\n    return trim($str);\n}\n\n/**\n * Return the label for the given attribute.\n *\n * @param  array $atts Attribute name to return\n * @return string\n */\nfunction com_connect_label($atts)\n{\n    global $com_connect_labels;\n\n    extract(lAtts(array(\n        \'name\' => \'\',\n    ), $atts));\n\n    if ($name) {\n        return isset($com_connect_labels[$name]) ? $com_connect_labels[$name] : \'\';\n    }\n\n    return \'\';\n}\n\n/**\n * Conditional tag for checking variable conditions.\n *\n * @param  array  $atts  Attributes to check\n * @param  string $thing Tag\'s container content\n * @return string\n */\nfunction com_connect_if($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n        \'value\' => null,\n    ), $atts));\n\n    $val = com_connect_value(array(\n        \'label\' => $label,\n        \'name\'  => $name,\n    ));\n\n    if ($val) {\n        $cond = ($value === null || $val == $value);\n    } else {\n        $cond = false;\n    }\n\n    return parse(EvalElse($thing, $cond));\n}', '//<?php\n/**\n * com_connect: A Textpattern CMS plugin for mail delivery of contact forms.\n */\n\n// Register tags if necessary.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'com_connect\')\n        ->register(\'com_connect_text\')\n        ->register(\'com_connect_email\')\n        ->register(\'com_connect_textarea\')\n        ->register(\'com_connect_select\')\n        ->register(\'com_connect_option\')\n        ->register(\'com_connect_checkbox\')\n        ->register(\'com_connect_radio\')\n        ->register(\'com_connect_serverinfo\')\n        ->register(\'com_connect_secret\')\n        ->register(\'com_connect_submit\')\n        ->register(\'com_connect_send_article\')\n        ->register(\'com_connect_value\')\n        ->register(\'com_connect_label\')\n        ->register(\'com_connect_if\');\n}\n\n/**\n * Tag: encapsulate a contact form.\n *\n * Triggers the following callbacks:\n *  -> \'comconnect.form\' during form rendering so additional fields (e.g. spam honeypots) can be injected.\n *  -> \'comconnect.render\' immediately prior to form rendering so other parts of the form content may be altered.\n *  -> \'comconnect.submit\' on successful posting of form data. Primarily of use for spam\n *     plugins: they can return a non-zero value to signal that the form should NOT be sent.\n *\n * @param array  $atts  Tag attributes\n * @param string $thing Tag\'s container content\n */\nfunction com_connect($atts, $thing = null)\n{\n    global $sitename, $textarray, $com_connect_flags, $com_connect_from,\n        $com_connect_recipient, $com_connect_error, $com_connect_submit,\n        $com_connect_form, $com_connect_labels, $com_connect_values;\n\n    extract(com_connect_lAtts(array(\n        \'body_form\'        => \'\',\n        \'class\'            => \'comConnectForm\',\n        \'classes\'          => \'\',\n        \'copysender\'       => 0,\n        \'expire\'           => 600,\n        \'form\'             => \'\',\n        \'from\'             => \'\',\n        \'from_form\'        => \'\',\n        \'label\'            => null,\n        \'browser_validate\' => 1,\n        \'redirect\'         => \'\',\n        \'required\'         => \'1\',\n        \'show_error\'       => 1,\n        \'show_input\'       => 1,\n        \'send_article\'     => 0,\n        \'subject\'          => null,\n        \'subject_form\'     => \'\',\n        \'to\'               => \'\',\n        \'to_form\'          => \'\',\n        \'thanks\'           => null,\n        \'thanks_form\'      => \'\'\n    ), $atts));\n\n    if (!empty($lang)) {\n        $strings = com_connect_load_lang($lang);\n        $textarray = array_merge($textarray, $strings);\n    }\n\n    // Set defaults, in the local language if necessary.\n    if ($label === null) {\n        $label = gTxt(\'com_connect_contact\');\n    }\n\n    if ($subject === null) {\n        $subject = gTxt(\'com_connect_email_subject\', array(\'{site}\' => html_entity_decode($sitename,ENT_QUOTES)));\n    }\n\n    if ($thanks === null) {\n        $thanks = graf(gTxt(\'com_connect_email_thanks\'));\n    }\n\n    unset($atts[\'show_error\'], $atts[\'show_input\']);\n\n    $defaultClassNames = array(\n        \'element\'  => \'errorElement\',\n        \'wrapper\'  => \'comError\',\n        \'required\' => \'comRequired\',\n        \'thanks\'   => \'comThanks\',\n        );\n\n    $com_connect_form_id = md5(serialize($atts) . preg_replace(\'/[\\t\\s\\r\\n]/\', \'\', $thing));\n    $com_connect_submit = (ps(\'com_connect_form_id\') == $com_connect_form_id);\n    $override_email_charset = (get_pref(\'override_emailcharset\') && is_callable(\'utf8_decode\'));\n    $userClassNames = do_list($classes);\n\n    foreach (array_merge($defaultClassNames, $userClassNames) as $classKey => $classValue) {\n        if (strpos($classValue, \':\') !== false) {\n            $classParts = do_list($classValue, \':\');\n\n            if (count($classParts) === 2) {\n                $com_connect_flags[\'cls_\' . $classParts[0]] = $classParts[1];\n            }\n        } elseif ($classKey && $classValue) {\n            $com_connect_flags[\'cls_\' . $classKey] = $classValue;\n        }\n    }\n\n    // The $com_connect_flags[\'this_form\'] global is set if an id is supplied for the <form>.\n    // This value then becomes the default value for all html_form (a.k.a. form=)\n    // attributes for any input tags in this tag\'s container, providing HTML5 is in use.\n    $com_connect_flags[\'this_form\'] = \'com\' . $com_connect_form_id;\n\n    // Global toggle for required attribute.\n    $com_connect_flags[\'required\'] = $required;\n\n    $now = time();\n    $now_date = date(\'Y-m-d H:i:s\', $now);\n\n    $expire = abs(assert_int($expire));\n\n    static $headers_sent = false;\n    if (!$headers_sent) {\n        header(\'Last-Modified: \' . gmdate(\'D, d M Y H:i:s\', $now - (3600 * 24 * 7)) . \' GMT\');\n        header(\'Expires: \' . gmdate(\'D, d M Y H:i:s\', $now + $expire) . \' GMT\');\n        header(\'Cache-Control: no-cache, must-revalidate\');\n        $headers_sent = true;\n    }\n\n    $nonce = doSlash(ps(\'com_connect_nonce\'));\n    $renonce = false;\n\n    if ($com_connect_submit) {\n        // Could use \"interval $expire second\" but multiple com_connect forms could delete data\n        // that might be in use by other forms.\n        // TODO: use max(600, $expire)? Not perfect, but ensures a safe minimum deletion rate.\n        safe_delete(\'txp_discuss_nonce\', \"issue_time < date_sub(\'$now_date\', interval 10 minute)\");\n        if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\")) {\n            if ($rs[\'used\']) {\n                unset($com_connect_error);\n                $com_connect_error[] = gTxt(\'com_connect_form_used\');\n                $renonce = true;\n                $_POST = array();\n                $_POST[\'com_connect_submit\'] = true;\n                $_POST[\'com_connect_form_id\'] = $com_connect_form_id;\n                $_POST[\'com_connect_nonce\'] = $nonce;\n            }\n        } else {\n            $com_connect_error[] = gTxt(\'com_connect_form_expired\');\n            $renonce = true;\n        }\n    }\n\n    if ($com_connect_submit && $nonce && !$renonce) {\n        $com_connect_nonce = $nonce;\n    } elseif (!$show_error || $show_input) {\n        $com_connect_nonce = md5(uniqid(rand(), true));\n        safe_insert(\'txp_discuss_nonce\', \"issue_time = \'\" . $now_date . \"\', nonce = \'$com_connect_nonce\'\");\n    }\n\n    $form = ($form) ? fetch_form($form) : $thing;\n\n    if (empty($form)) {\n        $form = \'\n<txp:com_connect_text label=\"\'.gTxt(\'com_connect_name\').\'\" /><br />\n<txp:com_connect_email /><br />\'.\n($send_article ? \'<txp:com_connect_email send_article=\"1\" label=\"\'.gTxt(\'com_connect_recipient\').\'\" /><br />\' : \'\').\n\'<txp:com_connect_textarea /><br />\n<txp:com_connect_submit />\n\';\n    }\n\n    $form = parse($form);\n\n    // Perform aggregate functions for checking radio sets.\n    if ($com_connect_submit) {\n        com_connect_group_validate();\n    }\n\n    if ($to_form) {\n        $to = parse_form($to_form);\n    }\n\n    if (!$to && !$send_article) {\n        return gTxt(\'com_connect_to_missing\');\n    }\n\n    $out = \'\';\n\n    if (!$com_connect_submit) {\n        // Don\'t show errors or send mail.\n    } elseif (!empty($com_connect_error)) {\n        if ($show_error || !$show_input) {\n            $out .= n.doWrap(array_unique($com_connect_error), \'ul\', \'li\', $com_connect_flags[\'cls_wrapper\']).n;\n\n            if (!$show_input) {\n                return $out;\n            }\n        }\n    } elseif ($show_input && is_array($com_connect_form)) {\n        // Load and check spam plugins.\n        callback_event(\'comconnect.submit\');\n        $evaluation =& get_comconnect_evaluator();\n        $clean = $evaluation->get_comconnect_status();\n\n        if ($clean != 0) {\n            return gTxt(\'com_connect_spam\');\n        }\n\n        if ($from_form) {\n            $from = parse_form($from_form);\n        }\n\n        if ($subject_form) {\n            $subject = parse_form($subject_form);\n        }\n\n        $sep = IS_WIN ? \"\\r\\n\" : \"\\n\";\n        $msg = array();\n        $fields = array();\n\n        foreach ($com_connect_labels as $name => $lbl) {\n            if (!is_array($com_connect_values[$name])) {\n                if (trim($com_connect_values[$name]) === false) {\n                    continue;\n                }\n\n                $msg[] = $lbl . \': \' . $com_connect_values[$name];\n            }\n\n            $fields[$name] = $com_connect_values[$name];\n        }\n\n        if ($send_article) {\n            global $thisarticle;\n\n            $subject = str_replace(\'&#38;\', \'&\', $thisarticle[\'title\']);\n            $msg[] = permlinkurl($thisarticle);\n            $msg[] = $subject;\n            $s_ar = array(\'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8217;\', \'&#8242;\', \'&#8243;\', \'&#8230;\', \'&#8211;\', \'&#8212;\', \'&#215;\', \'&#8482;\', \'&#174;\', \'&#169;\', \'&lt;\', \'&gt;\', \'&quot;\', \'&amp;\', \'&#38;\', \"\\t\", \'<p\');\n\n            if ($override_email_charset) {\n                $r_ar = array(\"\'\", \"\'\", \'\"\', \'\"\', \"\'\", \"\'\", \'\"\', \'...\', \'-\', \'--\', \'x\', \'[tm]\', \'(r)\', \'(c)\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n            } else {\n                $r_ar = array(\'‘\', \'’\', \'“\', \'”\', \'’\', \'?\', \'?\', \'…\', \'–\', \'—\', \'×\', \'™\', \'®\', \'©\', \'<\', \'>\', \'\"\', \'&\', \'&\', \' \', \"\\n<p\");\n            }\n\n            $msg[] = trim(strip_tags(str_replace($s_ar, $r_ar, (trim(strip_tags($thisarticle[\'excerpt\'])) ? $thisarticle[\'excerpt\'] : $thisarticle[\'body\']))));\n\n            if (empty($com_connect_recipient)) {\n                return gTxt(\'com_connect_field_missing\', array(\'{field}\' => gTxt(\'com_connect_recipient\')));\n            } else {\n                $to = $com_connect_recipient;\n            }\n        }\n\n        $msg = implode(\"\\n\\n\", $msg);\n\n        if ($body_form) {\n            $msg = parse_form($body_form);\n        }\n\n        $msg = str_replace(array(\"\\r\\n\", \"\\r\", \"\\n\"), array(\"\\n\", \"\\n\", $sep), $msg);\n\n        $reply   = com_connect_strip($from ? $com_connect_from : \'\');\n        $from    = com_connect_strip($from ? $from : $com_connect_from);\n        $to      = com_connect_strip($to);\n        $subject = com_connect_strip($subject);\n        $msg     = com_connect_strip($msg, false);\n\n        if ($override_email_charset) {\n            $charset = \'ISO-8859-1\';\n            $subject = utf8_decode($subject);\n            $msg     = utf8_decode($msg);\n        } else {\n            $charset = \'UTF-8\';\n        }\n\n        // TODO: function deprecated in 4.6.0.\n        $subject = encode_mailheader($subject, \'text\');\n\n        $headers = array(\n            \'from\'          => $from,\n            \'separator\'     => $sep,\n            \'reply\'         => $reply,\n            \'charset\'       => $charset,\n            \'content_type\'  => \'text/plain\',\n            \'xfer_encoding\' => \'8bit\',\n        );\n\n        safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = \'$now_date\'\", \"nonce = \'$nonce\'\");\n\n        if (com_connect_deliver($to, $subject, $msg, $headers, $fields, array(\'isCopy\' => false))) {\n            $_POST = array();\n\n            if ($copysender && $com_connect_from) {\n                com_connect_deliver(com_connect_strip($com_connect_from), $subject, $msg, $headers, $fields, array(\'isCopy\' => true));\n            }\n\n            if ($redirect) {\n                while (@ob_end_clean());\n                $uri = hu.ltrim($redirect,\'/\');\n\n                if (empty($_SERVER[\'FCGI_ROLE\']) && empty($_ENV[\'FCGI_ROLE\'])) {\n                    txp_status_header(\'303 See Other\');\n                    header(\'Location: \'.$uri);\n                    header(\'Connection: close\');\n                    header(\'Content-Length: 0\');\n                } else {\n                    $uri = txpspecialchars($uri);\n                    $refresh = gTxt(\'com_connect_refresh\');\n                    echo <<<END\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>$sitename</title>\n    <meta http-equiv=\"refresh\" content=\"0;url=$uri\">\n</head>\n<body>\n    <a href=\"$uri\">$refresh</a>\n</body>\n</html>\nEND;\n                }\n\n                exit;\n            } else {\n                return \'<div class=\"\' . $com_connect_flags[\'cls_thanks\'] . \'\" id=\"com\'.$com_connect_form_id.\'\">\' .\n                    ($thanks_form ? parse_form($thanks_form) : $thanks) .\n                    \'</div>\';\n            }\n        } else {\n            // Plugin modules may have set error messages: display if appropriate.\n            if ($com_connect_error) {\n                $out .= n.doWrap(array_unique($com_connect_error), \'ul\', \'li\', $com_connect_flags[\'cls_wrapper\']).n;\n            } else {\n                $out .= graf(gTxt(\'com_connect_mail_sorry\'));\n            }\n        }\n    }\n\n    if ($show_input && !$send_article || gps(\'com_connect_send_article\')) {\n        $contactForm = \'<form method=\"post\"\' . ((!$show_error && $com_connect_error) ? \'\' : \' id=\"com\' . $com_connect_form_id . \'\"\') .\n            ($class ? \' class=\"\' . $class . \'\"\' : \'\') .\n            ($browser_validate ? \'\' : \' novalidate\') .\n            \' action=\"\' . txpspecialchars(serverSet(\'REQUEST_URI\')) . \'#com\' . $com_connect_form_id . \'\">\' .\n            ($label ? n . \'<fieldset>\' : \'\') .\n            ($label ? n . \'<legend>\' . txpspecialchars($label) . \'</legend>\' : \'\') .\n            $out .\n            n . \'<input type=\"hidden\" name=\"com_connect_nonce\" value=\"\' . $com_connect_nonce . \'\" />\' .\n            n . \'<input type=\"hidden\" name=\"com_connect_form_id\" value=\"\' . $com_connect_form_id . \'\" />\' .\n            $form .\n            callback_event(\'comconnect.form\') .\n            ($label ? (n . \'</fieldset>\') : \'\') .\n            n . \'</form>\';\n\n        callback_event_ref(\'comconnect.render\', \'\', 0, $contactForm, $atts);\n\n        return $contactForm;\n    }\n\n    return \'\';\n}\n\n/**\n * Tag: Render a text input field.\n *\n * Many different $types are allowed. In fact, the $type attribute is not\n * restricted in any way to allow for any future types. This does bring some\n * developer responsibility if you wish your document to validate because\n * not all attributes will be valid for all input types, despite the plugin\n * faithfully rendering whatever attributes it is given.\n *\n * A lot of invalid situations are recognised (e.g. only permitting max/min/step\n * to occur on \'numeric\' input types) but some are not easily trappable so they\n * are left to user discretion.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_text($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comText\',\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'inputmode\'      => \'\',\n        \'label\'          => gTxt(\'com_connect_text\'),\n        \'label_position\' => \'before\',\n        \'max\'            => null,\n        \'min\'            => null,\n        \'name\'           => \'\',\n        \'pattern\'        => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'size\'           => \'\',\n        \'step\'           => \'\',\n        \'type\'           => \'text\',\n    ), $atts));\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    $datetime_types = array(\n        \'date\',\n        \'datetime\',\n        \'datetime-local\',\n        \'month\',\n        \'time\',\n        \'week\',\n    );\n\n    $numeric_types = array(\n        \'number\',\n        \'range\',\n    );\n\n    $is_datetime = (in_array($type, $datetime_types));\n    $is_numeric = (in_array($type, $numeric_types));\n\n    // Dates / times get special treatment: no default min/max if not set by tag.\n    if (!$is_datetime && $min === null) {\n        $min = 0;\n    }\n\n    if (!$is_datetime && $max === null) {\n        $max = 100;\n    }\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    if ($com_connect_submit) {\n        $value = trim(ps($name));\n        $utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\n        $hlabel = txpspecialchars($label);\n        $datetime_ok = true;\n\n        if ($is_datetime) {\n            $minval = $min;\n            $maxval = $max;\n            $cmpval = $value;\n\n            try {\n                $dt = new DateTime($cmpval);\n                $cmpval = $dt->format(\'U\');\n\n                if ($min) {\n                    $dt = new DateTime($min);\n                    $minval = $dt->format(\'U\');\n                }\n\n                if ($max) {\n                    $dt = new DateTime($max);\n                    $maxval = $dt->format(\'U\');\n                }\n            } catch (Exception $e) {\n                $datetime_ok = false;\n            }\n        }\n\n        if (strlen($value)) {\n            if (!$utf8len) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_utf8\', array(\'{field}\' => $hlabel));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($is_datetime && !$datetime_ok) {\n                $com_connect_error[] = gTxt(\'com_connect_format_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $value));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && !$is_numeric && !$is_datetime && $utf8len < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_min_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && !$is_numeric && !$is_datetime && $utf8len > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_max_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $is_datetime && $cmpval < $minval) {\n                $com_connect_error[] = gTxt(\'com_connect_minval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $is_datetime && $cmpval > $maxval) {\n                $com_connect_error[] = gTxt(\'com_connect_maxval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $is_numeric && $value < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_minval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $is_numeric && $value > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_maxval_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($pattern and !preg_match(\'/^\'.$pattern.\'$/\', $value)) {\n                $com_connect_error[] = gTxt(\'com_connect_pattern_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $pattern));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                com_connect_store($name, $label, $value);\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => $hlabel));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $default;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => (isset($id) ? $id : $name),\n        \'name\'  => $name,\n        \'type\'  => $type,\n        \'value\' => $value,\n    ));\n\n    if ($size && !$is_numeric) {\n        $attr[\'size\'] = \'size=\"\' . intval($size) . \'\"\';\n    }\n\n    if ($min && !$is_numeric) {\n        $attr[\'minlength\'] = \'minlength=\"\' . intval($min) . \'\"\';\n    }\n\n    if ($max && !$is_numeric) {\n        $attr[\'maxlength\'] = \'maxlength=\"\' . intval($max) . \'\"\';\n    }\n\n    if ($doctype !== \'xhtml\' && ($is_numeric || $is_datetime)) {\n        // Not using intval() because min/max/step can be floating point values.\n        $attr += com_connect_build_atts(array(\n            \'min\'  => $min,\n            \'max\'  => $max,\n            \'step\' => $step,\n        ));\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'autocomplete\' => $autocomplete,\n            \'form\'         => $html_form,\n            \'inputmode\'    => $inputmode,\n            \'pattern\'      => $pattern,\n            \'placeholder\'  => $placeholder,\n            \'required\'     => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render an email input field.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_email($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_from, $com_connect_recipient, $com_connect_flags;\n\n    // TODO: \'multiple\' attribute?\n    $defaults = array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comEmail\',\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_email\'),\n        \'label_position\' => \'before\',\n        \'max\'            => 100,\n        \'min\'            => 0,\n        \'name\'           => \'\',\n        \'pattern\'        => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'send_article\'   => 0,\n        \'size\'           => \'\',\n        \'type\'           => \'email\',\n    );\n\n    extract(com_connect_lAtts($defaults, $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $email = $com_connect_submit ? trim(ps($name)) : $default;\n\n    if ($com_connect_submit && strlen($email)) {\n        if (!is_valid_email($email)) {\n            $com_connect_error[] = gTxt(\'com_connect_invalid_email\', array(\'{email}\' => txpspecialchars($email)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        } else {\n            preg_match(\"/@(.+)$/\", $email, $match);\n            $domain = $match[1];\n\n            if (is_callable(\'checkdnsrr\') && checkdnsrr(\'textpattern.com.\',\'A\') && !checkdnsrr($domain.\'.\',\'MX\') && !checkdnsrr($domain.\'.\',\'A\')) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_host\', array(\'{host}\' => txpspecialchars($domain)));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                if ($send_article) {\n                    $com_connect_recipient = $email;\n                } else {\n                    $com_connect_from = $email;\n                }\n            }\n        }\n    }\n\n    $passed_atts = array();\n\n    foreach ($defaults as $key => $value) {\n        $passed_atts[$key] = $$key;\n    }\n\n    $passed_atts[\'isError\'] = $isError;\n    unset ($passed_atts[\'send_article\']);\n\n    return com_connect_text($passed_atts);\n}\n\n/**\n * Tag: Render a textarea input field.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_textarea($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'autocomplete\'   => \'\',\n        \'break\'          => br,\n        \'class\'          => \'comTextarea\',\n        \'cols\'           => 58,\n        \'default\'        => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_message\'),\n        \'label_position\' => \'before\',\n        \'max\'            => 10000,\n        \'min\'            => 0,\n        \'name\'           => \'\',\n        \'placeholder\'    => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'rows\'           => 8,\n        \'wrap\'           => \'\',\n    ), $atts));\n\n    $min = intval($min);\n    $max = intval($max);\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\n        $utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\n        $hlabel = txpspecialchars($label);\n\n        if (strlen(ltrim($value))) {\n            if (!$utf8len) {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_utf8\', array(\'{field}\' => $hlabel));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($min && $utf8len < $min) {\n                $com_connect_error[] = gTxt(\'com_connect_min_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $min));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } elseif ($max && $utf8len > $max) {\n                $com_connect_error[] = gTxt(\'com_connect_max_warning\', array(\'{field}\' => $hlabel, \'{value}\' => $max));\n                $isError = $com_connect_flags[\'cls_element\'];\n            } else {\n                com_connect_store($name, $label, $value);\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => $hlabel));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $default;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'        => (isset($id) ? $id : $name),\n        \'name\'      => $name,\n        \'cols\'      => intval($cols),\n        \'rows\'      => intval($rows),\n        \'maxlength\' => $max,\n    ));\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'autocomplete\' => $autocomplete,\n            \'form\'         => $html_form,\n            \'placeholder\'  => $placeholder,\n            \'required\'     => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<textarea\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . txpspecialchars($value) . \'</textarea>\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a select/option input list.\n *\n * @param  array  $atts Tag attributes\n * @return string HTML\n */\nfunction com_connect_select($atts, $thing = null)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    // TODO: multiple attribute?\n    extract(com_connect_lAtts(array(\n        \'break\'          => br,\n        \'class\'          => \'comSelect\',\n        \'delimiter\'      => \',\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_option\'),\n        \'label_position\' => \'before\',\n        \'list\'           => \'\', // TODO: remove from here in favour of the global list attribute.\n        \'options\'        => gTxt(\'com_connect_general_inquiry\'),\n        \'name\'           => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'selected\'       => \'\',\n        \'size\'           => \'\',\n    ), $atts));\n\n    // Detect old-school use of the list attribute. Note that deprecated_function_with\n    // isn\'t strictly the correct error to throw, but it\'s close enough until core has\n    // a dedicated deprecated_attribute_with string.\n    if (!empty($list) && strpos($list, $delimiter) !== false) {\n        $options = $list;\n        trigger_error(gTxt(\'deprecated_function_with\', array(\'{name}\' => \'list\', \'{with}\' => \'options\')), E_USER_NOTICE);\n        unset($list, $atts[\'list\']);\n    }\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $value = ($com_connect_submit) ? trim(ps($name)) : $selected;\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($thing) {\n        com_connect_option(null, $value);\n        $out = parse($thing);\n        $options = com_connect_option(null, null);\n    } else {\n        $options = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \', $options)));\n        $out = \'\';\n\n        foreach ($options as $item) {\n            $out .= n.t.\'<option\' . ($item == $value ? \' selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\') . \'>\' : \'>\') . (strlen($item) ? txpspecialchars($item) : \' \') . \'</option>\';\n        }\n    }\n\n    if ($com_connect_submit) {\n        if (strlen($value)) {\n            if (in_array($value, $options)) {\n                com_connect_store($name, $label, $value);\n            } else {\n                $com_connect_error[] = gTxt(\'com_connect_invalid_value\', array(\'{field}\' => txpspecialchars($label), \'{value}\' => txpspecialchars($value)));\n                $isError = $com_connect_flags[\'cls_element\'];\n            }\n        } elseif ($required) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($label)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        }\n    } else {\n        $value = $selected;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'   => (isset($id) ? $id : $name),\n        \'name\' => $name,\n    ));\n\n    if ($size && is_numeric($size)) {\n        $attr[\'size\'] = \'size=\"\' . intval($size) . \'\"\';\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        n . \'<select\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' .\n            $out .\n        n . \'</select>\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a set of select options.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Tag\'s container\n * @return string HTML\n */\nfunction com_connect_option($atts, $thing = null)\n{\n    static $options;\n    static $match;\n\n    if ($atts === null) {\n        if ($thing === null) {\n            return $options;\n        } else {\n            $match = $thing;\n            return;\n        }\n    }\n\n    global $com_connect_error, $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'class\'    => \'comOption\',\n        \'label\'    => null,\n        \'selected\' => null,\n        \'value\'    => null,\n    ), $atts));\n\n    $val = ($value === null) ? (!empty($thing) ? $thing : (!empty($label) ? $label : null)) : $value;\n    $label = ($label === null) ? (!empty($thing) ? $thing : (!empty($value) ? $value : null)) : $label;\n\n    $attr = array();\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $options[] = $val;\n\n        if ($val !== null && ((string)$val === (string)$match)) {\n            $attr[] = \'selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\');\n        }\n    } elseif ($selected || ($val !== null && ((string)$val === (string)$match))) {\n        $attr[] = \'selected\' . (($doctype === \'xhtml\') ? \'=\"selected\"\' : \'\');\n    }\n\n    // Core attributes.\n    $attr += com_connect_build_atts(array(\n        \'value\' => $val,\n    ));\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classStr = (($class) ? \' class=\"\' . $class . \'\"\' : \'\');\n\n    return \'<option\' . $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . txpspecialchars($label) . \'</option>\';\n}\n\n/**\n * Tag: Render a checkbox.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n * @todo checkbox groups?\n */\nfunction com_connect_checkbox($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'break\'          => \' \',\n        \'checked\'        => 0,\n        \'class\'          => \'comCheckbox\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_checkbox\'),\n        \'label_position\' => \'after\',\n        \'name\'           => \'\',\n        \'required\'       => $com_connect_flags[\'required\'],\n        \'value\'          => null,\n    ), $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    if ($com_connect_submit) {\n        $theValue = (bool) ps($name);\n\n        if ($required && !$theValue) {\n            $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($label)));\n            $isError = $com_connect_flags[\'cls_element\'];\n        } else {\n            $toStore = (($value !== null && $theValue) ? $value : ($theValue ? gTxt(\'yes\') : gTxt(\'no\')));\n            com_connect_store($name, $label, $toStore);\n        }\n    } else {\n        $theValue = $checked;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => (isset($id) ? $id : $name),\n        \'name\'  => $name,\n    ));\n\n    if ($value !== null) {\n        $attr += com_connect_build_atts(array(\n            \'value\' => $value,\n        ));\n    }\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n\n        if ($isError) {\n            $attr += com_connect_build_atts(array(\n                \'aria-invalid\' => \'true\',\n            ));\n        }\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $name . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input type=\"checkbox\"\' . $classStr .\n            ($theValue ? \' checked\' . (($doctype === \'xhtml\') ? \'=\"checked\"\' : \'\') : \'\') . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\' .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Render a radio button.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n */\nfunction com_connect_radio($atts)\n{\n    global $com_connect_error, $com_connect_submit, $com_connect_values, $com_connect_flags, $com_connect_group;\n\n    extract(com_connect_lAtts(array(\n        \'break\'          => \' \',\n        \'checked\'        => 0,\n        \'class\'          => \'comRadio\',\n        \'group\'          => \'\',\n        \'html_form\'      => $com_connect_flags[\'this_form\'],\n        \'isError\'        => \'\',\n        \'label\'          => gTxt(\'com_connect_option\'),\n        \'label_position\' => \'after\',\n        \'name\'           => \'\',\n        \'required\'       => null,\n        \'value\'          => null,\n    ), $atts));\n\n    static $cur_name = array();\n    static $cur_req = array();\n    static $cur_group = null;\n\n    if (!$group) {\n        if ($cur_group === null) {\n            $group = gTxt(\'com_connect_radio\');\n        } else {\n            $group = $cur_group;\n        }\n    } else {\n        if (!$name) {\n            $name = $group;\n        }\n    }\n\n    if (isset($cur_name[$group])) {\n        if (!$name) {\n            $name = $cur_name[$group];\n        }\n    } else {\n        if ($name) {\n            $cur_name[$group] = $name;\n        } else {\n            $name = $cur_name[$group] = gTxt(\'com_connect_radio\');\n        }\n    }\n\n    if (isset($cur_req[$group])) {\n        if ($required === null) {\n            $required = $cur_req[$group];\n        }\n    } else {\n        if ($required === null) {\n            $required = $cur_req[$group] = $com_connect_flags[\'required\'];\n        } else {\n            $cur_req[$group] = $required;\n        }\n    }\n\n    $cur_group = $group;\n\n    $id = \'q\' . md5($name . \'=>\' . $label);\n    $name = com_connect_label2name($name);\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n    $com_connect_group[$name][$id][\'req\'] = $required;\n    $com_connect_group[$name][$id][\'label\'] = $group;\n\n    if ($com_connect_submit) {\n        $toCompare = ($value === null ? $id : $value);\n        $is_checked = (ps($name) == $toCompare);\n        $com_connect_group[$name][$id][\'isSet\'] = $is_checked;\n\n        if ($is_checked || $checked && !isset($com_connect_values[$name])) {\n            com_connect_store($name, $group, ($value !== null ? $value : $label));\n        }\n    } else {\n        $is_checked = $checked;\n    }\n\n    // Core attributes.\n    $attr = com_connect_build_atts(array(\n        \'id\'    => $id,\n        \'name\'  => $name,\n        \'value\' => ($value !== null ? $value : $id),\n    ));\n\n    // HTML5 attributes.\n    $required = ($required) ? \'required\' : \'\';\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\'     => $html_form,\n            \'required\' => $required,\n        ));\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classes = array();\n\n    foreach (array($class, ($required ? $com_connect_flags[\'cls_required\'] : \'\'), $isError) as $cls) {\n        if ($cls) {\n            $classes[] = $cls;\n        }\n    }\n\n    $classStr = ($classes ? \' class=\"\' . implode(\' \', $classes) . \'\"\' : \'\');\n    $labelStr = \'<label for=\"\' . $id . \'\"\' . $classStr . \'>\' . txpspecialchars($label) . \'</label>\';\n\n    return ($label_position === \'before\' ? $labelStr . $break : \'\') .\n        \'<input type=\"radio\"\'. $classStr . ($attr ? \' \' . implode(\' \', $attr) : \'\') .\n            ( $is_checked ? \' checked\' . (($doctype === \'xhtml\') ? \'=\"checked\"\' : \'\'). \' />\' : \' />\') .\n        ($label_position === \'after\' ? $break . $labelStr : \'\');\n}\n\n/**\n * Tag: Store server information in the payload.\n *\n * @param  array  $atts  Tag attributes\n */\nfunction com_connect_serverinfo($atts)\n{\n    global $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n    ), $atts));\n\n    if (empty($name)) {\n        $name = com_connect_label2name($label);\n    }\n\n    if (strlen($name) && $com_connect_submit) {\n        if (!$label) {\n            $label = $name;\n        }\n\n        com_connect_store($name, $label, serverSet($name));\n    }\n}\n\n/**\n * Tag: Store a secret value in the payload.\n *\n * @param  array  $atts  Tag attributes\n */\nfunction com_connect_secret($atts, $thing = null)\n{\n    global $com_connect_submit;\n\n    extract(com_connect_lAtts(array(\n        \'label\' => gTxt(\'com_connect_secret\'),\n        \'name\'  => \'\',\n        \'value\' => \'\',\n    ), $atts));\n\n    $name = com_connect_label2name($name ? $name : $label);\n\n    if ($com_connect_submit) {\n        if ($thing) {\n            $value = trim(parse($thing));\n        }\n\n        com_connect_store($name, $label, $value);\n    }\n\n    return \'\';\n}\n\n/**\n * Tag: Render a submit button.\n *\n * If the container is used, a button tag is rendered, otherwise a\n * standard input type=submit button is rendered.\n *\n * @param  array  $atts  Tag attributes\n * @param  string $thing Tag\'s container\n * @return string HTML\n */\nfunction com_connect_submit($atts, $thing = null)\n{\n    global $com_connect_flags;\n\n    extract(com_connect_lAtts(array(\n        \'class\'     => \'comSubmit\',\n        \'html_form\' => $com_connect_flags[\'this_form\'],\n        \'label\'     => gTxt(\'com_connect_send\'),\n    ), $atts));\n\n    $label = txpspecialchars($label);\n    $doctype = get_pref(\'doctype\', \'xhtml\');\n\n    $attr = array();\n\n    // HTML5 attributes.\n    if ($doctype !== \'xhtml\') {\n        $attr += com_connect_build_atts(array(\n            \'form\' => $html_form,\n        ));\n    }\n\n    // Global attributes.\n    $attr += com_connect_build_atts($com_connect_globals, $atts);\n\n    $classStr = ($class ? \' class=\"\' . $class . \'\"\' : \'\');\n\n    if (strlen($thing)) {\n        return \'<button type=\"submit\"\' . $classStr . \' name=\"com_connect_submit\" value=\"\' . $label . \'\"\' . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \'>\' . ($thing ? trim(parse($thing)) : $label) . \'</button>\';\n    } else {\n        return \'<input type=\"submit\"\' . $classStr . \' name=\"com_connect_submit\" value=\"\' . $label . \'\"\' . ($attr ? \' \' . implode(\' \', $attr) : \'\') . \' />\';\n    }\n}\n\n/**\n * Tag: Render a link to allow someone to forward a page to a friend.\n *\n * @param  array  $atts  Tag attributes\n * @return string HTML\n */\nfunction com_connect_send_article($atts)\n{\n    if (!isset($_REQUEST[\'com_connect_send_article\'])) {\n        $linktext = (empty($atts[\'linktext\'])) ? gTxt(\'com_connect_send_article\') : $atts[\'linktext\'];\n        $join = (empty($_SERVER[\'QUERY_STRING\'])) ? \'?\' : \'&\';\n        $href = $_SERVER[\'REQUEST_URI\'] . $join . \'com_connect_send_article=yes\';\n\n        return \'<a href=\"\' . txpspecialchars($href) . \'\" rel=\"nofollow\">\' . txpspecialchars($linktext) . \'</a>\';\n    }\n\n    return;\n}\n\n/**\n * Perform post-processing for aggregate (group) controls like radio sets.\n *\n * @todo Can this be done any neater?\n * @todo Should this be exposed as a callback to allow plugins to extend the functionality?\n */\nfunction com_connect_group_validate()\n{\n    global $com_connect_group, $com_connect_error;\n\n    $flags = array();\n\n    if ($com_connect_group) {\n        foreach ($com_connect_group as $key => $grp) {\n            foreach ($grp as $id => $atts) {\n                $flags[$key][\'label\'] = $atts[\'label\'];\n\n                if (!empty($atts[\'req\']) && !isset($flags[$key][\'req\'])) {\n                    $flags[$key][\'req\'] = 1;\n                }\n\n                if (!empty($atts[\'isSet\']) && !isset($flags[$key][\'isSet\'])) {\n                    $flags[$key][\'isSet\'] = 1;\n                }\n            }\n        }\n\n        foreach ($flags as $key => $data) {\n            if (!empty($data[\'req\']) && !isset($data[\'isSet\'])) {\n                $com_connect_error[] = gTxt(\'com_connect_field_missing\', array(\'{field}\' => txpspecialchars($data[\'label\'])));\n            }\n        }\n    }\n}\n\n/**\n * Process tag attributes and set defaults.\n *\n * Handles HTML \'global\' attributes too, though what we consider global differs slightly\n * from the W3C: any attributes that apply to most of the <input> tags is considered\n * global. Just saves repetitive code in each tag handler.\n *\n * Note that \'class\' is omitted, because that has different default values per input\n * widget, so it is dealt with explicitly in each tag handler.\n *\n * @param  array $arr  Acceptable tag attributes\n * @param  array $atts User-submitted attribute values\n * @return array\n */\nfunction com_connect_lAtts($pairs, $atts)\n{\n    lAtts(array(\'label\' => \'\'), $atts, false);\n\n    foreach (array(\'button\', \'copysender\', \'checked\', \'required\', \'send_article\', \'show_input\', \'show_error\') as $key) {\n        if (isset($atts[$key])) {\n            $atts[$key] = ($atts[$key] === \'yes\' || intval($atts[$key])) ? 1 : 0;\n        }\n    }\n\n    if (isset($atts[\'break\']) && $atts[\'break\'] === \'br\') {\n        $atts[\'break\'] = \'<br />\';\n    }\n\n    $com_connect_globals = array(\n        \'accesskey\'  => \'\',\n        \'autofocus\'  => \'\',\n        \'dir\'        => \'\',\n        \'disabled\'   => \'\',\n        \'hidden\'     => \'\',\n        \'id\'         => \'\',\n        \'lang\'       => \'\',\n        \'list\'       => \'\',\n        \'readonly\'   => \'\',\n        \'spellcheck\' => \'\',\n        \'style\'      => \'\',\n        \'tabindex\'   => \'\',\n        \'title\'      => \'\',\n        \'translate\'  => \'\',\n    );\n\n    foreach ($atts as $name => $value) {\n        if (array_key_exists($name, $pairs) || substr($name, 0, 2) === \'on\' || array_key_exists($name, $com_connect_globals)) {\n            $pairs[$name] = $value;\n        } elseif (get_pref(\'production_status\') != \'live\') {\n            trigger_error(gTxt(\'unknown_attribute\', array(\'{att}\' => $name)));\n        }\n    }\n\n    $pairs[\'com_connect_globals\'] = $com_connect_globals;\n\n    return ($pairs) ? $pairs : false;\n}\n\n/**\n * Build a sanitized, unique attribute array from the given pairs.\n *\n * If called multiple times, earlier values supersede later values\n * if the keys clash.\n *\n * @param  array $pairs    Attribute tuples to compile\n * @param  array $defaults Attribute values to use if the $pairs tuple value is empty\n * @return array\n */\nfunction com_connect_build_atts($pairs, $defaults = array())\n{\n    $attr = array();\n\n    $booleans = array(\n        \'autofocus\',\n        \'checked\',\n        \'disabled\',\n        \'hidden\',\n        \'multiple\',\n        \'readonly\',\n        \'required\',\n        \'selected\',\n        );\n\n    foreach ($pairs as $key => $value) {\n        if ($value !== \'\' && $value !== null) {\n            $attr[$key] = $key . (in_array($key, $booleans) ? \'\' : \'=\"\' . txpspecialchars($value) . \'\"\');\n        } else {\n            if (isset($defaults[$key])) {\n                $attr[$key] = $key . (in_array($key, $booleans) ? \'\' : \'=\"\' . txpspecialchars($defaults[$key]) . \'\"\');\n            }\n        }\n    }\n\n    return $attr;\n}\n\n/**\n * Replace nulls and newlines in content with spaces.\n *\n * @param string $str    String to sanitize\n * @param bool   $header Whether the string is a header or not\n */\nfunction com_connect_strip($str, $header = true)\n{\n    if ($header) {\n        // TODO: strip_rn will be deprecated in 4.6.0.\n        $str = strip_rn($str);\n    }\n\n    return preg_replace(\'/[\\x00]/\', \' \', $str);\n}\n\n/**\n * Handle content delivery of payload.\n *\n * Triggers a \'comconnect.deliver\' callback event to override or augment the\n * delivery mechanism. Third party plugins can make alterations to the $payload,\n * then return one of the strings:\n *  -> \"comconnect.send\" (or no return value) to allow the plugin to continue mailing after the 3rd party plugin completes\n *  -> \"comconnect.skip\" to skip com_connect\'s mailing (i.e. the 3rd party handles mailing) and return \'success\'\n *  -> \"comconnect.fail\" to skip com_connect\'s mailing and return \'fail\'\n *\n * By hooking into the callback\'s step you can target either the main \'send\'\n * process or the \'copysender\' process. Examples of things you could do:\n * -> Add Multi-part MIME headers for HTML emails.\n * -> Add CC: or BCC: fields.\n * -> Subscribe people to mailing lists.\n * -> Handle the mailing process independently of Textpattern.\n *\n * @param string $to      Delivery address\n * @param string $subject Subject of message\n * @param string $body    Message content\n * @param array  $headers Message headers as tuples\n * @param array  $fields  Message field names and content as tuples\n * @param array  $flags   Signals to govern delivery / callback behaviour\n */\nfunction com_connect_deliver($to, $subject, $body, $headers, $fields, $flags)\n{\n    $payload = array(\n        \'to\'      => $to,\n        \'subject\' => $subject,\n        \'headers\' => $headers,\n        \'body\'    => $body,\n        \'fields\'  => $fields,\n    );\n\n    $flavour = ($flags[\'isCopy\'] === true) ? \'copysender\' : \'send\';\n\n    // Allow plugins to override or alter default action (mail) if required.\n    // TODO: use has_handler() from 4.6.0+.\n    $ret = callback_event_ref(\'comconnect.deliver\', $flavour, 0, $payload);\n\n    if (in_array(\'comconnect.fail\', $ret)) {\n        return false;\n    } elseif (in_array(\'comconnect.skip\', $ret)) {\n        return true;\n    }\n\n    extract($payload);\n\n    $smtp_from = get_pref(\'smtp_from\');\n\n    if (!is_callable(\'mail\')) {\n        return (get_pref(\'production_status\') === \'live\')\n            ? gTxt(\'com_connect_mail_sorry\')\n            : gTxt(\'warn_mail_unavailable\');\n    }\n\n    $sep = (!empty($headers[\'separator\'])) ? $headers[\'separator\'] : (IS_WIN ? \"\\r\\n\" : \"\\n\");\n    $xfer_encoding = (!empty($headers[\'xfer_encoding\'])) ? $headers[\'xfer_encoding\'] : \'8bit\';\n    $content_type = (!empty($headers[\'content_type\'])) ? $headers[\'content_type\'] : \'text/plain\';\n    $reply = (!empty($headers[\'reply\'])) ? $headers[\'reply\'] : \'\';\n    $charset = (!empty($headers[\'charset\'])) ? $headers[\'charset\'] : \'UTF-8\';\n    $x_mailer = (!empty($headers[\'x_mailer\'])) ? $headers[\'x_mailer\'] : \'Textpattern (com_connect)\';\n\n    $header_string = \'From: \' . $headers[\'from\'] .\n        ($reply ? ($sep . \'Reply-To: \' . $reply) : \'\') .\n        $sep . \'X-Mailer: \' . $x_mailer .\n        $sep . \'X-Originating-IP: \' . com_connect_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'] . \' via \' : \'\') . $_SERVER[\'REMOTE_ADDR\']) .\n        $sep . \'Content-Transfer-Encoding: \' . $xfer_encoding .\n        $sep . \'Content-Type: \' . $content_type . \'; charset=\"\' . $charset . \'\"\';\n\n    // Remove all the header entries that have already been handled.\n    unset(\n        $headers[\'separator\'],\n        $headers[\'xfer_encoding\'],\n        $headers[\'content_type\'],\n        $headers[\'reply\'],\n        $headers[\'charset\'],\n        $headers[\'x_mailer\'],\n        $headers[\'from\']\n    );\n\n    // Any remaining headers set by plugins are appended as-is.\n    foreach ($headers as $name => $value) {\n        $header_string .= $sep . $name . \': \' . $value;\n    }\n\n    if (is_valid_email($smtp_from)) {\n        if (IS_WIN) {\n            ini_set(\'sendmail_from\', $smtp_from);\n        } elseif (!ini_get(\'safe_mode\')) {\n            return mail($to, $subject, $body, $header_string, \'-f\'.$smtp_from);\n        }\n    }\n\n    return mail($to, $subject, $body, $header_string);\n}\n\n/**\n * Evaluate return values from plugins.\n */\nclass comconnect_evaluation\n{\n    private $status;\n\n    /**\n     * Constructor.\n     */\n    function __construct()\n    {\n        $this->status = 0;\n    }\n\n    /**\n     * Append the given status to the counter.\n     */\n    function add_comconnect_status($check)\n    {\n        $this->status += $check;\n    }\n\n    /**\n     * Fetch the current evaluator status.\n     */\n    function get_comconnect_status()\n    {\n        return $this->status;\n    }\n}\n\n/**\n * Evaluator singleton for checking return values from plugins.\n */\nfunction &get_comconnect_evaluator()\n{\n    static $instance;\n\n    if (!isset($instance)) {\n        $instance = new comconnect_evaluation();\n    }\n\n    return $instance;\n}\n\n/**\n * Convert the given label to a suitably unique name.\n *\n * @param string $label Label to convert to name.\n */\nfunction com_connect_label2name($label)\n{\n    $label = trim($label);\n\n    if (strlen($label) == 0) {\n        return \'invalid\';\n    }\n\n    if (strlen($label) <= 32 && preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) {\n        return $label;\n    } else {\n        return \'q\' . md5($label);\n    }\n}\n\n/**\n * Store the given names/values in the global arrays.\n *\n * @param  string $name  Parameter name\n * @param  string $label Parameter label\n * @param  string $value Parameter value\n */\nfunction com_connect_store($name, $label, $value)\n{\n    global $com_connect_form, $com_connect_labels, $com_connect_values;\n\n    $com_connect_form[$label] = $value;\n    $com_connect_labels[$name] = $label;\n    $com_connect_values[$name] = $value;\n}\n\n/**\n * Override the language strings if necessary.\n *\n * @param  string $lang Language designator (e.g. fr-fr)\n * @return array        Partial language array to merge with $textarray\n */\nfunction com_connect_load_lang($lang = LANG)\n{\n    $out = array();\n\n    if ($lang != LANG) {\n\n        $rs = safe_rows(\"name, data\", \'txp_lang\', \"lang = \'\" . doSlash($lang) . \"\' AND name like \'com\\_connect\\_%\'\");\n\n        if (!empty($rs)) {\n            foreach ($rs as $a) {\n                $out[$a[\'name\']] = $a[\'data\'];\n            }\n        }\n    }\n\n    return $out;\n}\n\n/**\n * Return the value of the given attribute, by name or its label.\n *\n * @param  array $atts Attribute to return\n * @return string\n */\nfunction com_connect_value($atts)\n{\n    global $com_connect_values, $com_connect_form;\n\n    extract(lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n    ), $atts));\n\n    $str = \'\';\n\n    if ($name) {\n        $str = isset($com_connect_values[$name]) ? $com_connect_values[$name] : \'\';\n    } elseif ($label) {\n        $str = isset($com_connect_form[$name]) ? $com_connect_form[$name] : \'\';\n    }\n\n    return trim($str);\n}\n\n/**\n * Return the label for the given attribute.\n *\n * @param  array $atts Attribute name to return\n * @return string\n */\nfunction com_connect_label($atts)\n{\n    global $com_connect_labels;\n\n    extract(lAtts(array(\n        \'name\' => \'\',\n    ), $atts));\n\n    if ($name) {\n        return isset($com_connect_labels[$name]) ? $com_connect_labels[$name] : \'\';\n    }\n\n    return \'\';\n}\n\n/**\n * Conditional tag for checking variable conditions.\n *\n * @param  array  $atts  Attributes to check\n * @param  string $thing Tag\'s container content\n * @return string\n */\nfunction com_connect_if($atts, $thing = null)\n{\n    extract(lAtts(array(\n        \'label\' => \'\',\n        \'name\'  => \'\',\n        \'value\' => null,\n    ), $atts));\n\n    $val = com_connect_value(array(\n        \'label\' => $label,\n        \'name\'  => $name,\n    ));\n\n    if ($val) {\n        $cond = ($value === null || $val == $value);\n    } else {\n        $cond = false;\n    }\n\n    return parse(EvalElse($thing, $cond));\n}', '13ce587331a18f42753f81bb56a80f85', '#@public\ncom_connect_checkbox => Checkbox\ncom_connect_contact => Contact\ncom_connect_email => Email\ncom_connect_email_subject => {site} > Enquiry\ncom_connect_email_thanks => Thank you, your message has been sent.\ncom_connect_field_missing => Required field <strong>{field}</strong> is missing.\ncom_connect_format_warning => Value {value} in <strong>{field}</strong> is not of the expected format.\ncom_connect_form_expired => The form has expired, please try again.\ncom_connect_form_used => The form was already submitted, please fill out a new form.\ncom_connect_general_inquiry => General enquiry\ncom_connect_invalid_email => <strong>{email}</strong> is not a valid email address.\ncom_connect_invalid_host => <strong>{host}</strong> is not a valid email host.\ncom_connect_invalid_utf8 => <strong>{field}</strong> contains invalid UTF-8 characters.\ncom_connect_invalid_value => Invalid value for <strong>{field}</strong>: <strong>{value}</strong> is not one of the available options.\ncom_connect_mail_sorry => Sorry, unable to send email.\ncom_connect_maxval_warning => <strong>{field}</strong> must not exceed {value}.\ncom_connect_max_warning => <strong>{field}</strong> must not contain more than {value} characters.\ncom_connect_message => Message\ncom_connect_minval_warning => <strong>{field}</strong> must be at least {value}.\ncom_connect_min_warning => <strong>{field}</strong> must contain at least {value} characters.\ncom_connect_name => Name\ncom_connect_option => Option\ncom_connect_pattern_warning => <strong>{field}</strong> does not match the pattern {value}.\ncom_connect_radio => Radio\ncom_connect_recipient => Recipient\ncom_connect_refresh => Follow this link if the page does not refresh automatically.\ncom_connect_secret => Secret\ncom_connect_send => Send\ncom_connect_send_article => Send article\ncom_connect_spam => We do not accept spam, thank you!\ncom_connect_text => Text\ncom_connect_to_missing => <strong>To</strong> email address is missing.\n#@public\n#@language es-es\ncom_connect_checkbox => Casilla de verificación\ncom_connect_contact => Contacto\ncom_connect_email => Correo electrónico\ncom_connect_email_subject => {site} > Consulta\ncom_connect_email_thanks => Gracias, tu mensaje ha sido enviado.\ncom_connect_field_missing => falta el campo obligatorio <strong>{field}</strong>.\ncom_connect_format_warning => El valor {value} en <strong>{field}</strong> no está en el formato esperado.\ncom_connect_form_expired => El formulario ha caducado, por favor inténtalo de nuevo.\ncom_connect_form_used => El formulario ya había sido enviado, por favor rellena el formulario de nuevo.\ncom_connect_general_inquiry => Consulta general\ncom_connect_invalid_email => La dirección de correo electrónico <strong>{email}</strong> no es válida.\ncom_connect_invalid_host => El dominio de correo electrónico <strong>{host}</strong> no es válido.\ncom_connect_invalid_utf8 => <strong>{field}</strong> contiene caracteres UTF-8 no válidos.\ncom_connect_invalid_value => Valor incorrecto para <strong>{field}</strong>: <strong>{value}</strong> no es una de las opciones disponibles.\ncom_connect_mail_sorry => Lo siento, el correo electrónico no pudo ser enviado.\ncom_connect_maxval_warning => <strong>{field}</strong> no debe exceder {value}.\ncom_connect_max_warning => <strong>{field}</strong> no debe contener más de {value} caracteres.\ncom_connect_message => Mensaje\ncom_connect_minval_warning => <strong>{field}</strong> debe tener al menos {value}.\ncom_connect_min_warning => <strong>{field}</strong> debe contener al menos {value} caracteres.\ncom_connect_name => Nombre\ncom_connect_option => Opción\ncom_connect_pattern_warning => <strong>{field}</strong> no encaja con el patrón {value}.\ncom_connect_radio => Botón de opción\ncom_connect_recipient => Destinatario\ncom_connect_refresh => Siga este enlace si la página no se recarga automáticamente.\ncom_connect_secret => Secreto\ncom_connect_send => Enviar\ncom_connect_send_article => Enviar artículo\ncom_connect_spam => Gracias, ¡pero no aceptamos correo basura!\ncom_connect_text => Texto\ncom_connect_to_missing => Falta la dirección de correo electrónico del <strong>destinatario</strong>.\n#@public\n#@language fr-fr\ncom_connect_checkbox => Case à cocher\ncom_connect_contact => Contact\ncom_connect_email => Email\ncom_connect_email_subject => {site} > Demande\ncom_connect_email_thanks => Merci, votre message a bien été envoyé.\ncom_connect_field_missing => Champ obligatoire <strong>{field}</strong> manquant.\ncom_connect_form_expired => Le délai du formulaire vient d’expirer. Veuillez recommencer.\ncom_connect_form_used => Le formulaire a déjà été soumis. Veuillez en remplir un nouveau.\ncom_connect_general_inquiry => Demande d’ordre général\ncom_connect_invalid_email => <strong>{email}</strong> n’est pas une adresse email valide.\ncom_connect_invalid_host => <strong>{host}</strong> n’est pas correctement rédigé.\ncom_connect_invalid_utf8 => <strong>{field}</strong> contient des caractères invalides.\ncom_connect_invalid_value => Cette valeur : <strong>{value}</strong> n’est pas correcte pour <strong>{field}</strong>.\ncom_connect_mail_sorry => Désolé, impossible d’envoyer votre message dans l’immédiat.\ncom_connect_maxval_warning => <strong>{field}</strong> ne peux pas être plus grand que {value}.\ncom_connect_max_warning => <strong>{field}</strong> dépasse {value} caractères.\ncom_connect_message => Message\ncom_connect_minval_warning => <strong>{field}</strong> doit être au moins {value}.\ncom_connect_min_warning => <strong>{field}</strong> doit contenir au moins {value} caractères.\ncom_connect_name => Nom\ncom_connect_option => Option\ncom_connect_pattern_warning =><strong>{field}</strong> doit correspondre à ce modèle {value}.\ncom_connect_radio => Bouton radio\ncom_connect_recipient => Destinataire\ncom_connect_refresh => Cliquez sur ce lien si la page ne se rafraîchissait pas automatiquement.\ncom_connect_secret => Secret\ncom_connect_send => Envoyer\ncom_connect_send_article => Envoyer l’article\ncom_connect_spam => Nous refusons catégoriquement les spam. Bien à vous!\ncom_connect_text => Texte\ncom_connect_to_missing => l’adresse mail <strong>To</strong> est manquante.\n#@public\n#@language nl-nl\ncom_connect_checkbox => Keuze\ncom_connect_contact => Contact\ncom_connect_email => E-mail adres\ncom_connect_email_subject => {site} > bericht via de site\ncom_connect_email_thanks => Hartelijk dank, je bericht is verzonden.\ncom_connect_field_missing => Je hebt bij <strong>{field}</strong> nog niets ingevuld.\ncom_connect_format_warning => De invoer {value} in <strong>{field}</strong> heeft niet de juiste vorm.\ncom_connect_form_expired => Het formulier is verlopen, probeer het opnieuw.\ncom_connect_form_used => Het formulier is reeds verstuurd, vul het opnieuw in.\ncom_connect_general_inquiry => Bericht van de site\ncom_connect_invalid_email => <strong>{email}</strong> is geen geldig e-mail addres.\ncom_connect_invalid_host => <strong>{host}</strong> is geen geldige e-mail host.\ncom_connect_invalid_utf8 => <strong>{field}</strong> bevat ongeldige lettertekens.\ncom_connect_invalid_value => Ongeldige invoer bij <strong>{field}</strong>, <strong>{value}</strong> is niet mogelijk.\ncom_connect_mail_sorry => Sorry, er kan geen e-mail verzonden worden.\ncom_connect_maxval_warning => <strong>{field}</strong> mag niet groter zijn dan {value}.\ncom_connect_max_warning => <strong>{field}</strong> mag niet meer dan {value} lettertekes bevatten.\ncom_connect_message => Berichttekst\ncom_connect_minval_warning => <strong>{field}</strong> moet minstens {value} zijn.\ncom_connect_min_warning => <strong>{field}</strong> moet minstens {value} lettertekens bevatten.\ncom_connect_name => Naam\ncom_connect_option => Optie\ncom_connect_pattern_warning => <strong>{field}</strong> komt niet overeen met de volgorde {value}.\ncom_connect_radio => Keuzeknop\ncom_connect_recipient => Ontvanger\ncom_connect_refresh => Je kunt deze link gebruiken als de pagina niet automatisch ververst.\ncom_connect_secret => Geheim\ncom_connect_send => Verzenden\ncom_connect_send_article => Artikel verzenden\ncom_connect_spam => We accepteren geen spam!\ncom_connect_text => Tekst\ncom_connect_to_missing => <strong>Aan</strong> e-mailadres ontbreekt.\n#@public\n#@language pt-br\ncom_connect_checkbox => Checkbox\ncom_connect_contact => Contato\ncom_connect_email => Email\ncom_connect_email_subject => {site} > Contato\ncom_connect_email_thanks => Obrigado, sua mensagem foi enviada.\ncom_connect_field_missing => Faltou preencher o campo requerido <strong>{field}</strong>.\ncom_connect_format_warning => O valor {value} em <strong>{field}</strong> não está formato esperado.\ncom_connect_form_expired => O formulário expirou, por favor tente novamente.\ncom_connect_form_used => O formulário já foi enviado, por favor preencha o formulário novamente.\ncom_connect_general_inquiry => Assuntos gerais\ncom_connect_invalid_email => <strong>{email}</strong> não é um endereço de email válido.\ncom_connect_invalid_host => <strong>{host}</strong> não é um domínio de email válido.\ncom_connect_invalid_utf8 => <strong>{field}</strong> contém caracteres UTF-8 inválidos.\ncom_connect_invalid_value => Valor incorreto para <strong>{field}</strong>, <strong>{value}</strong> não é uma das opções disponíveis.\ncom_connect_mail_sorry => Desculpe, não foi possível enviar o email.\ncom_connect_maxval_warning => <strong>{field}</strong> não pode exceder {value}.\ncom_connect_max_warning => <strong>{field}</strong> não pode conter mais que {value} caracteres.\ncom_connect_message => Mensagem\ncom_connect_minval_warning => <strong>{field}</strong> deve ter ao menos {value}.\ncom_connect_min_warning => <strong>{field}</strong> deve conter ao menos {value} caracteres.\ncom_connect_name => Nome\ncom_connect_option => Opção\ncom_connect_pattern_warning => <strong>{field}</strong> não se encaixa no formato {value}.\ncom_connect_radio => Rádio\ncom_connect_recipient => Destinatário\ncom_connect_refresh => Clique neste link caso a página não se atualize automaticamente.\ncom_connect_secret => Secreto\ncom_connect_send => Enviar\ncom_connect_send_article => Enviar artigo\ncom_connect_spam => Não aceitamos spam, obrigado!\ncom_connect_text => Texto\ncom_connect_to_missing => <strong>To</strong> falta o endereço de email.\n#@public\n#@language de-de\ncom_connect_checkbox => Checkbox\ncom_connect_contact => Kontakt\ncom_connect_email => E-Mail\ncom_connect_email_subject => {site} > Anfrage\ncom_connect_email_thanks => Vielen Dank, Ihre Nachricht wurde gesendet.\ncom_connect_field_missing => Erforderliche Eingabe im Feld <strong>{field}</strong> fehlt.\ncom_connect_format_warning => Eingabe {value} im Feld <strong>{field}</strong> entspricht nicht dem erwarteten Format.\ncom_connect_form_expired => Dieses Formular ist abgelaufen, bitte versuchen Sie es erneut.\ncom_connect_form_used => Dieses Formular wurde bereits gesendet. Bitte laden Sie das Formular noch einmal.\ncom_connect_general_inquiry => Allgemeine Anfrage\ncom_connect_invalid_email => <strong>{email}</strong> ist keine gültige E-Mailadresse.\ncom_connect_invalid_host => <strong>{host}</strong> ist kein gültiger E-Mail-Server.\ncom_connect_invalid_utf8 => <strong>{field}</strong> enthält ungültige UTF-8-Zeichen.\ncom_connect_invalid_value => Ungültiger Wert für <strong>{field}</strong>, <strong>{value}</strong> ist keine verfügbare Option.\ncom_connect_mail_sorry => Leider kann keine E-Mail gesendet werden.\ncom_connect_maxval_warning => <strong>{field}</strong> darf {value} nicht überschreiten.\ncom_connect_max_warning => <strong>{field}</strong> darf nicht länger als {value} Zeichen sein.\ncom_connect_message => Nachricht\ncom_connect_minval_warning => <strong>{field}</strong> darf {value} nicht unterschreiten.\ncom_connect_min_warning => <strong>{field}</strong> darf nicht kürzer als {value} Zeichen sein.\ncom_connect_name => Name\ncom_connect_option => Option\ncom_connect_pattern_warning => <strong>{field}</strong> entspricht nicht dem Muster {value}.\ncom_connect_radio => Radio\ncom_connect_recipient => Empfänger\ncom_connect_refresh => Bitte folgen Sie diesem Link, falls die Seite icht automatisch neu geladen wird.\ncom_connect_secret => Geheimnis\ncom_connect_send => Senden\ncom_connect_send_article => Artikel senden\ncom_connect_spam => Danke, wir brauchen keinen Spam!\ncom_connect_text => Text\ncom_connect_to_missing => <strong>To</strong> E-Mail-Adresse fehlt.\n', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_custom_feed', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.2', 'Customize feeds\' body content', '<h1>rah_custom_feed</h1>\n\n<p>Customize the output of your feeds with Textpattern tags, like any other form.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern v4.2.0 or newer.</li>\n	<li>PHP5</li>\n</ul>\n\n<h2>Usage</h2>\n\n<p>The plugin uses a <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">Form partial</a> called <code>rah_feed_body</code> customize feeds output. The automatic setup should have created it, but if it didn&#8217;t, you can create the form as normal. This form will hold your <span class=\"caps\">RSS</span> and Atom feed&#8217;s item body contents. You can use Textpattern tags, including individual article tags, as you would with a normal page template.</p>\n\n<h2>Example</h2>\n\n<p>A form partial named <code>rah_feed_body</code> can be used to customize feed items’ output. For example, the form markup could look something like this:</p>\n\n<pre><code>&lt;txp:excerpt /&gt;\n&lt;p&gt;&lt;txp:permlink&gt;Read more...&lt;/txp:permlink&gt;&lt;/p&gt;</code></pre>\n\n<p>Snippet above outputs an excerpt and a read more link.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.2 &#8211; 2012/07/12</h3>\n\n<ul>\n	<li>Improved help file.</li>\n	<li>Use plugin-lifecycle callbacks to remove the added form during uninstall.</li>\n	<li>Now requires PHP5 or newer.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/01/22</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_custom_feed plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_custom_feed\n *\n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	register_callback(array(\'rah_custom_feed\', \'install\'), \'plugin_lifecycle.rah_custom_feed\');\n	register_callback(array(\'rah_custom_feed\', \'feed_item\'), \'atom_entry\');\n	register_callback(array(\'rah_custom_feed\', \'feed_item\'), \'rss_entry\');\n\nclass rah_custom_feed {\n	\n	/**\n	 * Installer\n	 * @param string $event Admin-side event.\n	 * @param string $step Admin-side, plugin-lifecycle step.\n	 */\n	\n	static public function install($event=\'\', $step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_form\',\n				\"name=\'rah_feed_body\'\"\n			);\n			\n			return;\n		}\n		\n		if(!safe_row(\'name\', \'txp_form\', \"name=\'rah_feed_body\'\")) {\n			safe_insert(\n				\'txp_form\',\n				\"name=\'rah_feed_body\', type=\'article\', Form=\'<txp:body />\'\"\n			);\n		}\n	}\n\n	/**\n	 * Modifies feeds excerpt and body content\n	 */\n\n	static public function feed_item() {\n		global $thisarticle;\n		@$form = fetch_form(\'rah_feed_body\');\n		\n		if($form !== \'\') {\n			$form = parse($form);\n			$thisarticle[\'excerpt\'] = $form;\n			$thisarticle[\'body\'] = $form;\n		}\n	}\n}\n\n', '\n\n/**\n * Rah_custom_feed plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_custom_feed\n *\n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	register_callback(array(\'rah_custom_feed\', \'install\'), \'plugin_lifecycle.rah_custom_feed\');\n	register_callback(array(\'rah_custom_feed\', \'feed_item\'), \'atom_entry\');\n	register_callback(array(\'rah_custom_feed\', \'feed_item\'), \'rss_entry\');\n\nclass rah_custom_feed {\n	\n	/**\n	 * Installer\n	 * @param string $event Admin-side event.\n	 * @param string $step Admin-side, plugin-lifecycle step.\n	 */\n	\n	static public function install($event=\'\', $step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_form\',\n				\"name=\'rah_feed_body\'\"\n			);\n			\n			return;\n		}\n		\n		if(!safe_row(\'name\', \'txp_form\', \"name=\'rah_feed_body\'\")) {\n			safe_insert(\n				\'txp_form\',\n				\"name=\'rah_feed_body\', type=\'article\', Form=\'<txp:body />\'\"\n			);\n		}\n	}\n\n	/**\n	 * Modifies feeds excerpt and body content\n	 */\n\n	static public function feed_item() {\n		global $thisarticle;\n		@$form = fetch_form(\'rah_feed_body\');\n		\n		if($form !== \'\') {\n			$form = parse($form);\n			$thisarticle[\'excerpt\'] = $form;\n			$thisarticle[\'body\'] = $form;\n		}\n	}\n}\n\n', 'f95c6e3d21dc57892704d8d77379a608', '', '', 1, 5, 2),
('rah_wrach', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.3', 'Sections come first', '<h1>rah_wrach</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_wrach\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_wrach\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=27755\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_wrach\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_wrach is a tiny admin-side plugin for <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a> that makes users to choose an article&#8217;s designated <a href=\"http://textpattern.net/wiki/index.php?title=Sections\" rel=\"nofollow\">section</a> before going to the actual editor on the <a href=\"http://textpattern.net/wiki/index.php?title=Write\" rel=\"nofollow\">Write panel</a>.</p>\n\n<h2>Basics</h2>\n\n<p>Rah_wrach adds an extra step to the article publishing process. The plugin presents a prompt and makes site authors pick the section before going to the actual editor present on the <a href=\"http://textpattern.net/wiki/index.php?title=Write\" rel=\"nofollow\">Write panel</a>. The extra step will hopefully increase the change of articles actually going to correct sections.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_wrach&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.5.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_wrach&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Preferences</h2>\n\n<p>Rah_wrach introduces few settings. All the available settings can be found from Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Advanced_Preferences\" rel=\"nofollow\">Advanced Preferences</a> panel, organized under a <strong>Wrach</strong> section. The following settings will be present.</p>\n\n<h3>Display the following sections (comma-separated)</h3>\n\n<p>Sets which sections are listed in the section prompt. The field takes a comma-separated list of section names. Sections will be listed in the prompt in the order they are specified in the field.</p>\n\n<pre><code>news, articles, design</code></pre>\n\n<p>The above would bring News section to the top of the prompt and list Design the last. If the field is left empty, all sections are present in the prompt, sorted in an alphabetical order.</p>\n\n<h3>Hide the section input?</h3>\n\n<p>If set to <em>Yes</em>, the <em>Sections</em> selection field on the <a href=\"http://textpattern.net/wiki/index.php?title=Write\" rel=\"nofollow\">Write panel&#8217;s</a> editor is permanently hidden. Once a section is chosen from the prompt, the editor won&#8217;t offer an option to change the section. You will still be able to change the section for an existing article with the multi-editing tool residing on the <a href=\"http://textpattern.net/wiki/index.php?title=Articles\" rel=\"nofollow\">article</a> list.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.3 &#8211; 2012/08/31</h3>\n\n<ul>\n	<li>Changed: New look and feel for the prompt. Uses a native grid layout, and displays extra information in the prompt.</li>\n	<li>Added: Settings to the Preferences panel.</li>\n	<li>Added: Finnish translation.</li>\n	<li>Added: French translation by <a href=\"https://twitter.com/lowel\" rel=\"nofollow\">Patrick Lefevre</a>.</li>\n	<li>Textpattern v4.5.0 compatibility.</li>\n	<li>Now requires Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/12/22</h3>\n\n<ul>\n	<li>Now compatible with <span class=\"caps\">TXP</span> 4.3.0. Thank you, <a href=\"http://forum.textpattern.com/viewtopic.php?pid=238736#p238736\" rel=\"nofollow\">Phil Wareham</a>, for reporting.</li>\n	<li>Moved JavaScript and inline styles to the <code>&lt;head&gt;</code>.</li>\n	<li>Now requires <span class=\"caps\">TXP</span> 4.0.7 or newer.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/07/13</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_wrach plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_wrach\n * \n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_wrach();\n\nclass rah_wrach {\n	\n	static public $version = \'0.3\';\n	\n	/**\n	 * @var bool Skip the prompt\n	 */\n	\n	public $skip = true;\n	\n	/**\n	 * Installer\n	 */\n	\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_wrach\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$position = 250;\n		\n		foreach(\n			array(\n				\'show_sections\' => array(\'text_input\', \'\'),\n				\'hide_section_input\' => array(\'yesnoradio\', 0),\n			) as $name => $val\n		) {\n			$n = __CLASS__.\'_\'.$name;\n			\n			if(!isset($prefs[$n])) {\n				set_pref($n, $val[1], __CLASS__, PREF_ADVANCED, $val[0], $position);\n				$prefs[$n] = $val[1];\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array($this, \'prompt\'), \'article\', \'\', 1);\n		register_callback(array($this, \'select\'), \'article\', \'\', 0);\n		register_callback(array($this, \'head\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Add styles and JavaScript to the <head>\n	 */\n\n	public function head() {\n		\n		global $event;\n		\n		if($event != \'article\') {\n			return;\n		}\n\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_wrach .txp-grid-cell {\n					width: 294px;\n				}\n				#rah_wrach .information,\n				#rah_wrach .success {\n					float: right;\n					margin-left: 0.3em;\n				}\n			</style>\nEOF;\n\n		if(get_pref(\'rah_wrach_hide_section_input\')) {\n			echo <<<EOF\n				<style type=\"text/css\">\n					#write-sort .section {\n						display: none;\n					}\n				</style>\nEOF;\n		}\n\n	}\n\n	/**\n	 * Check prompt\'s visiblity\n	 */\n\n	public function prompt() {\n		\n		global $step;\n		\n		extract(gpsa(array(\n			\'ID\',\n			\'Section\',\n			\'view\'\n		)));\n		\n		$this->skip = ($Section || $ID || $view || $step);\n	}\n\n	/**\n	 * Section selection panel\n	 */\n\n	public function select() {\n		\n		global $txp_user;\n		\n		if($this->skip) {\n			return;\n		}\n		\n		$sql = array();\n		$sql[] = \"name != \'default\'\";\n		\n		$sections = get_pref(\'rah_wrach_s.\'.$txp_user, get_pref(\'rah_wrach_show_sections\'));\n		\n		if($sections) {\n			$sections = implode(\',\', quote_list(do_list($sections)));\n			$sql[] = \"name IN({$sections})\";\n		}\n		\n		$rs = \n			safe_rows(\n				\'title, name, (SELECT count(*) FROM \'.safe_pfx(\'textpattern\').\' articles WHERE articles.Section = txp_section.name) AS article_count, in_rss, on_frontpage\',\n				\'txp_section\',\n				implode(\' and \', $sql).\' order by \'.($sections ? \'FIELD(name,\'.$sections.\')\' : \'title ASC\')\n			);\n		\n		if(!$rs) {\n			return;\n		}\n		\n		ob_clean();\n		pagetop(gTxt(\'tab_write\'));\n		$out = array();\n\n		foreach($rs as $a) {\n			$out[] = \n				\'<div class=\"txp-grid-cell\">\'.\n					\'<p class=\"clearfix\">\'.\n						\'<a href=\"?event=article&amp;Section=\'.txpspecialchars($a[\'name\']).\'\">\'.\n							txpspecialchars($a[\'title\']).\n						\'</a>\'.\n						($a[\'article_count\'] ? \'<a href=\"?event=list&amp;search_method=section&amp;crit=&quot;\'.txpspecialchars($a[\'name\']).\'&quot;\" class=\"information\"><small>\'.$a[\'article_count\'].\'</small></a>\' : \'\').\n						\'<br />\'.\n						preg_replace(\'#^/index\\.php\\?#\', \'/?\', substr(pagelinkurl(array(\'s\' => $a[\'name\'])), strlen(hu)-1)).\n						($a[\'on_frontpage\'] ? \'<small title=\"\'.gTxt(\'rah_wrach_frontpage_tooltip\').\'\" class=\"success\">\'.gTxt(\'rah_wrach_frontpage_label\').\'</small>\' : \'\').\n						($a[\'in_rss\'] ? \'<small title=\"\'.gTxt(\'rah_wrach_rss_tooltip\').\'\" class=\"success\">\'.gTxt(\'rah_wrach_rss_label\').\'</small>\' : \'\').\n					\'</p>\'.\n				\'</div>\';\n		}\n		\n		echo \n			\'<h1 class=\"txp-heading\">\'.gTxt(\'tab_write\').\'</h1>\'.\n			\'<div id=\"rah_wrach\" class=\"txp-grid\">\'.implode(\'\', $out).\'</div>\';\n	}\n\n	/**\n	 * Options page\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_wrach_show_sections\');\n		\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_wrach_show_sections\">\'.gTxt(\'continue\').\'</a>\'.\n			\'</p>\';\n	}\n}\n\n', '\n\n/**\n * Rah_wrach plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_wrach\n * \n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_wrach();\n\nclass rah_wrach {\n	\n	static public $version = \'0.3\';\n	\n	/**\n	 * @var bool Skip the prompt\n	 */\n	\n	public $skip = true;\n	\n	/**\n	 * Installer\n	 */\n	\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_wrach\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$position = 250;\n		\n		foreach(\n			array(\n				\'show_sections\' => array(\'text_input\', \'\'),\n				\'hide_section_input\' => array(\'yesnoradio\', 0),\n			) as $name => $val\n		) {\n			$n = __CLASS__.\'_\'.$name;\n			\n			if(!isset($prefs[$n])) {\n				set_pref($n, $val[1], __CLASS__, PREF_ADVANCED, $val[0], $position);\n				$prefs[$n] = $val[1];\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array($this, \'prompt\'), \'article\', \'\', 1);\n		register_callback(array($this, \'select\'), \'article\', \'\', 0);\n		register_callback(array($this, \'head\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Add styles and JavaScript to the <head>\n	 */\n\n	public function head() {\n		\n		global $event;\n		\n		if($event != \'article\') {\n			return;\n		}\n\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_wrach .txp-grid-cell {\n					width: 294px;\n				}\n				#rah_wrach .information,\n				#rah_wrach .success {\n					float: right;\n					margin-left: 0.3em;\n				}\n			</style>\nEOF;\n\n		if(get_pref(\'rah_wrach_hide_section_input\')) {\n			echo <<<EOF\n				<style type=\"text/css\">\n					#write-sort .section {\n						display: none;\n					}\n				</style>\nEOF;\n		}\n\n	}\n\n	/**\n	 * Check prompt\'s visiblity\n	 */\n\n	public function prompt() {\n		\n		global $step;\n		\n		extract(gpsa(array(\n			\'ID\',\n			\'Section\',\n			\'view\'\n		)));\n		\n		$this->skip = ($Section || $ID || $view || $step);\n	}\n\n	/**\n	 * Section selection panel\n	 */\n\n	public function select() {\n		\n		global $txp_user;\n		\n		if($this->skip) {\n			return;\n		}\n		\n		$sql = array();\n		$sql[] = \"name != \'default\'\";\n		\n		$sections = get_pref(\'rah_wrach_s.\'.$txp_user, get_pref(\'rah_wrach_show_sections\'));\n		\n		if($sections) {\n			$sections = implode(\',\', quote_list(do_list($sections)));\n			$sql[] = \"name IN({$sections})\";\n		}\n		\n		$rs = \n			safe_rows(\n				\'title, name, (SELECT count(*) FROM \'.safe_pfx(\'textpattern\').\' articles WHERE articles.Section = txp_section.name) AS article_count, in_rss, on_frontpage\',\n				\'txp_section\',\n				implode(\' and \', $sql).\' order by \'.($sections ? \'FIELD(name,\'.$sections.\')\' : \'title ASC\')\n			);\n		\n		if(!$rs) {\n			return;\n		}\n		\n		ob_clean();\n		pagetop(gTxt(\'tab_write\'));\n		$out = array();\n\n		foreach($rs as $a) {\n			$out[] = \n				\'<div class=\"txp-grid-cell\">\'.\n					\'<p class=\"clearfix\">\'.\n						\'<a href=\"?event=article&amp;Section=\'.txpspecialchars($a[\'name\']).\'\">\'.\n							txpspecialchars($a[\'title\']).\n						\'</a>\'.\n						($a[\'article_count\'] ? \'<a href=\"?event=list&amp;search_method=section&amp;crit=&quot;\'.txpspecialchars($a[\'name\']).\'&quot;\" class=\"information\"><small>\'.$a[\'article_count\'].\'</small></a>\' : \'\').\n						\'<br />\'.\n						preg_replace(\'#^/index\\.php\\?#\', \'/?\', substr(pagelinkurl(array(\'s\' => $a[\'name\'])), strlen(hu)-1)).\n						($a[\'on_frontpage\'] ? \'<small title=\"\'.gTxt(\'rah_wrach_frontpage_tooltip\').\'\" class=\"success\">\'.gTxt(\'rah_wrach_frontpage_label\').\'</small>\' : \'\').\n						($a[\'in_rss\'] ? \'<small title=\"\'.gTxt(\'rah_wrach_rss_tooltip\').\'\" class=\"success\">\'.gTxt(\'rah_wrach_rss_label\').\'</small>\' : \'\').\n					\'</p>\'.\n				\'</div>\';\n		}\n		\n		echo \n			\'<h1 class=\"txp-heading\">\'.gTxt(\'tab_write\').\'</h1>\'.\n			\'<div id=\"rah_wrach\" class=\"txp-grid\">\'.implode(\'\', $out).\'</div>\';\n	}\n\n	/**\n	 * Options page\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_wrach_show_sections\');\n		\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_wrach_show_sections\">\'.gTxt(\'continue\').\'</a>\'.\n			\'</p>\';\n	}\n}\n\n', '9ebe81d35167fe274f4e0a1a38505d17', '#@admin\n#Default language, en-us, en-gb\nrah_wrach_frontpage_label => FRONT\nrah_wrach_frontpage_tooltip => Published on the frontpage\nrah_wrach_rss_label => RSS\nrah_wrach_rss_tooltip => Included in RSS feeds\nrah_wrach => Wrach\nrah_wrach_show_sections => Display the following sections (comma-separated)\nrah_wrach_hide_section_input => Hide the section input?\n#@admin\n#@language fi-fi\nrah_wrach_frontpage_label => ETU\nrah_wrach_frontpage_tooltip => Julkaistaan etusivulla\nrah_wrach_rss_label => RSS\nrah_wrach_rss_tooltip => Mukana RSS-syötteissä\nrah_wrach => Wrach\nrah_wrach_show_sections => Näytä seuraavat osiot (pilkuin eroteltuna)\nrah_wrach_hide_section_input => Piilota osiot-kenttä?\n#@admin\n#@language fr-fr\nrah_wrach_frontpage_label => Accueil\nrah_wrach_frontpage_tooltip => Publié en page d\'accueil\nrah_wrach_rss_label => RSS\nrah_wrach_rss_tooltip => Inclus dans les flux RSS\nrah_wrach => Wrach\nrah_wrach_show_sections => Afficher les sections (liste séparée par une virgule)\nrah_wrach_hide_section_input => Masquer le choix de section ?', '', 3, 5, 3),
('rah_bitly', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4.2', 'Bitly integration', '<h1>rah_bitly</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_bitly\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_bitly\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=36497\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_bitly\" rel=\"nofollow\">Donate</a></p>\n\n<p>Gives <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern</a> <span class=\"caps\">CMS</span>’ articles tiny <a href=\"http://bitly.com\" rel=\"nofollow\">Bitly</a> powered short links. These links are generated automatically when an article is published and stored in a custom field of your choosing. Easy to setup, just define your credentials via graphical user interface. Adds zero more steps to article publishing. Made by <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Jukka Svahn&#8217;s</a> tiny hand, using and loving Bitly.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_bitly&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.5.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer.\n	<ul>\n		<li><a href=\"http://php.net/manual/en/book.curl.php\" rel=\"nofollow\">cURL</a> library.</li>\n	</ul></li>\n	<li>Installed on a web server with an internet connection and outgoing <span class=\"caps\">HTTP</span> connections allowed.</li>\n	<li><a href=\"http://bit.ly\" rel=\"nofollow\">Bitly</a> account.</li>\n	<li>End user: a modern web browser with JavaScript support.</li>\n</ul>\n\n<p>Most servers, including shared hosting spaces, should be able to run <strong>rah_bitly</strong> just fine as long as Textpattern 4.5.0 or newer is installed.</p>\n\n<h2>Installing</h2>\n\n<p>Rah_bitly&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Configuring</h2>\n\n<p>To start using rah_bitly you will first need to define your <strong>Bitly login</strong>,  <strong><span class=\"caps\">API</span> key</strong> and the <a href=\"http://textpattern.net/wiki/index.php?title=custom_field\" rel=\"nofollow\">custom field</a> you want to use to store shortened links.</p>\n\n<p>Setting the preferences is easy. You can find all you need in Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Advanced_Preferences\" rel=\"nofollow\">Advanced Preferences</a> panel, neatly organized under <strong>Bitly integration</strong> section. The following three options will be present.</p>\n\n<ul>\n	<li><strong>Bitly login</strong> is where your <a href=\"http://bit.ly\" rel=\"nofollow\">Bitly</a> username would go.</li>\n	<li><strong><span class=\"caps\">API</span> key</strong> is your <span class=\"caps\">API</span> key. <a href=\"http://bitly.com/a/your_api_key\" rel=\"nofollow\">Get your own <span class=\"caps\">API</span> key here</a>. You can also find and reset your <span class=\"caps\">API</span> key from your Bitly account&#8217;s settings page.</li>\n	<li><strong>Store in a custom field</strong> is the custom field that will be used to store shortened links.</li>\n</ul>\n\n<h2>Generating links</h2>\n\n<p>Bitly links are generated for an article when one of the three conditions is met.</p>\n\n<ul>\n	<li>When the article is published.</li>\n	<li>When already published article&#8217;s <span class=\"caps\">URL</span> title is modified.</li>\n	<li>When the custom field used to store the link is emptied.</li>\n</ul>\n\n<p>All three conditions only apply while the article is in, or is saved with, <em>live</em> or <em>sticky</em> status. Short links won&#8217;t be generated for unpublished articles.</p>\n\n<p>All generated links will follow your Bitly account&#8217;s preferences, including your custom domain or preferred domain.</p>\n\n<p>If requesting a short link from Bitly fails due to connection error, or because Bitly&#8217;s servers are temporarily unreachable, a new request is sent when the article is saved again. You will notice if the request has been successful when there is a shortened link in the custom field.</p>\n\n<p>The link that the plugin will shorten is the article&#8217;s permanent link, the real location of the individual article page that Textpattern responds to. It&#8217;s the same location as where the <em>View</em> link points on the <a href=\"http://textpattern.net/wiki/index.php?title=Write\" rel=\"nofollow\">Write</a> panel.</p>\n\n<h2>Displaying links</h2>\n\n<p>Displaying Bitly links on your page templates and articles is easy and customizable due to the fact that a <a href=\"http://textpattern.net/wiki/index.php?title=custom_field\" rel=\"nofollow\">custom field</a> is used to store the shortened links.</p>\n\n<pre><code>&lt;txp:if_custom_field name=&quot;short_url&quot;&gt;\n	&lt;a href=&quot;&lt;txp:custom_field name=&quot;short_url&quot; /&gt;&quot;&gt;\n		&lt;txp:custom_field name=&quot;short_url&quot; /&gt;\n	&lt;/a&gt;\n&lt;txp:else /&gt;\n	&lt;p&gt;No short link.&lt;/p&gt;\n&lt;/txp:if_custom_field&gt;</code></pre>\n\n<p>The above snippet would display shortened Bitly link when placed to article <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">form</a> or <a href=\"http://textpattern.net/wiki/index.php?title=Textpattern:_Context_In_Depth\" rel=\"nofollow\">context</a>. When no link is available <em>“No short link”</em> message is shown. The <code>short_url</code> is the name of the custom field.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.4.2 &#8211; 2012/12/21</h3>\n\n<ul>\n	<li>Fixed: Undefined variable notices during article updates.</li>\n	<li>Added: Displays preferences group on Textpattern 4.6-dev&#8217;s Preferences panel.</li>\n</ul>\n\n<h3>Version 0.4.1 &#8211; 2012/09/20</h3>\n\n<ul>\n	<li>Improved: Cleaned up the installer.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2012/08/27</h3>\n\n<ul>\n	<li>Added: Textpattern 4.5.0 compatibility. Thank you, <a href=\"https://github.com/rwetzlmayr\" rel=\"nofollow\">Robert Wetzlmayr</a>.</li>\n	<li>Added: Finnish translation.</li>\n	<li>Fixed: A potential issue with uninstaller <span class=\"caps\">SQL</span> statement&#8217;s wildcards.</li>\n	<li>Changed: Textpacks aren&#8217;t embedded to the code, but uses installer.</li>\n	<li>Removed: Unnecessary <span class=\"caps\">HTML</span> escaping in one of the preference controls.</li>\n	<li>Removed: Functionality added in v0.2 that allowed picking new custom fields from <span class=\"caps\">HTTP</span> <span class=\"caps\">POST</span> data. This is for future compatibility.</li>\n	<li>Removed: Dropped support to file_get_contents() and <a href=\"http://php.net/manual/en/filesystem.configuration.php\" rel=\"nofollow\">allow_url_fopen</a>. cURL is now required in favor of future compliance and move to oAuth.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/12/05</h3>\n\n<ul>\n	<li>Changed: Now uses Textpattern&#8217;s <code>script_js()</code> to output JavaScript blocks.</li>\n	<li>Improved: Escape <span class=\"caps\">URL</span>s returned by Bitly so that it can not break JavaScript string. This is to prevent potential JavaScript injections.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2011/07/21</h3>\n\n<ul>\n	<li>Changed: Preference section&#8217;s heading now uses title case, and option labels sentence case.</li>\n	<li>Changed: Now picks up new custom fields from <span class=\"caps\">POST</span> data.</li>\n	<li><a href=\"http://forum.textpattern.com/profile.php?id=8181\" rel=\"nofollow\">Stef</a>, <a href=\"http://forum.textpattern.com/profile.php?id=9144\" rel=\"nofollow\">Markus</a>, thank you for suggestions.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2011/07/20</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_bitly plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2011-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_bitly\n * \n * Copyright (C) 2011 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	rah_bitly::get();\n\n/**\n * The plugin class.\n */\n\nclass rah_bitly\n{\n	/**\n	 * Version number.\n	 *\n	 * @var string\n	 */\n\n	static public $version = \'0.4.2\';\n\n	/**\n	 * Stores instances.\n	 *\n	 * @var rah_bitly\n	 */\n\n	static public $instance = null;\n\n	/**\n	 * Article\'s current permlink.\n	 *\n	 * @var string\n	 */\n\n	public $permlink;\n\n	/**\n	 * Article\'s previous permlink.\n	 *\n	 * @var string\n	 */\n\n	public $prev_permlink;\n\n	/**\n	 * Article\'s previous status.\n	 *\n	 * @var int\n	 */\n\n	private $prev_status;\n	\n	/**\n	 * Bitly login.\n	 *\n	 * @var string\n	 */\n\n	private $login;\n\n	/**\n	 * Bitly API key.\n	 *\n	 * @var string\n	 */\n\n	private $apikey;\n\n	/**\n	 * Custom field ID.\n	 *\n	 * @var int\n	 */\n\n	private $field;\n\n	/**\n	 * Installer.\n	 *\n	 * @param string $event Admin-side event.\n	 * @param string $step  Admin-side, plugin-lifecycle step.\n	 */\n\n	static public function install($event = \'\', $step = \'\')\n	{	\n		global $prefs;\n\n		if ($step == \'deleted\')\n		{	\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_bitly\\_%\'\"\n			);\n\n			return;\n		}\n\n		if ((string) get_pref(__CLASS__.\'_version\') === self::$version)\n		{\n			return;\n		}\n\n		$position = 250;\n\n		foreach (\n			array(\n				\'login\'  => array(\'text_input\', \'\'),\n				\'apikey\' => array(\'text_input\', \'\'),\n				\'field\'  => array(\'rah_bitly_fields\', \'\'),\n			) as $name => $val\n		) {\n			$n =  __CLASS__.\'_\'.$name;\n\n			if (!isset($prefs[$n]))\n			{\n				set_pref($n, $val[1], __CLASS__, PREF_ADVANCED, $val[0], $position);\n			}\n\n			$position++;\n		}\n\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, PREF_HIDDEN);\n	}\n\n	/**\n	 * Gets an instance.\n	 *\n	 * @return rah_bitly\n	 */\n\n	static public function get()\n	{\n		if (self::$instance === null)\n		{\n			self::$instance = new rah_bitly();\n		}\n\n		return self::$instance;\n	}\n\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		add_privs(\'prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'initialize\'), \'article\', \'\', 1);\n	}\n\n	/**\n	 * Initializes the plugin.\n	 */\n\n	public function initialize()\n	{	\n		foreach (array(\'login\', \'apikey\', \'field\') as $name)\n		{\n			$this->$name = get_pref(__CLASS__.\'_\'.$name);\n		}\n\n		$this->field = (int) $this->field;\n\n		if (!$this->login || !$this->apikey || !$this->field)\n		{\n			return;\n		}\n\n		include_once txpath.\'/publish/taghandlers.php\';\n\n		$this->previous_state();\n		register_callback(array($this, \'update\'), \'article_saved\');\n		register_callback(array($this, \'update\'), \'article_posted\');\n	}\n\n	/**\n	 * Fetches old article data.\n	 */\n\n	public function previous_state()\n	{	\n		$id = (int) ps(\'ID\');\n\n		if (!$id)\n		{\n			return;\n		}\n\n		$this->prev_permlink = permlinkurl_id($id);\n		$this->prev_status = fetch(\'Status\', \'textpattern\', \'ID\', $id);\n		unset($GLOBALS[\'permlinks\'][$id]);\n	}\n\n	/**\n	 * Hooks to article saving process and updates short URLs.\n	 *\n	 * @param string $event Callback event\n	 * @param string $step  Callback step\n	 * @param array  $r     Article data\n	 */\n\n	public function update($event, $step, $r)\n	{	\n		global $app_mode;\n\n		$this->permlink = permlinkurl_id($r[\'ID\']);\n\n		callback_event(\'rah_bitly.update\', \'\', false, $r);\n\n		if (!$this->permlink || $r[\'Status\'] < STATUS_LIVE)\n		{\n			return;\n		}\n\n		if (\n			$this->prev_permlink !== $this->permlink ||\n			empty($r[\'custom_\'.$this->field]) ||\n			$this->prev_status != $r[\'Status\']\n		) {\n			$uri = $this->fetch($this->permlink);\n		}\n\n		if (empty($uri))\n		{\n			return;\n		}\n\n		$fields = getCustomFields();\n\n		if (!isset($fields[$this->field]))\n		{\n			return;\n		}\n\n		safe_update(\n			\'textpattern\',\n			\'custom_\'.intval($this->field).\"=\'\".doSlash($uri).\"\'\",\n			\"ID=\'\".doSlash($r[\'ID\']).\"\'\"\n		);\n\n		$js = \n			\'$(document).ready(function(){\'.\n				\'$(\\\'input[name=\"custom_\'.$this->field.\'\"]\\\').val(\"\'.escape_js($uri).\'\");\'.\n			\'});\';\n\n		if ($app_mode == \'async\')\n		{\n			send_script_response($js);\n		}\n	}\n\n	/**\n	 * Fetches a Bitly short URL.\n	 *\n	 * @param  string $permlink The long URL to shorten\n	 * @param  int    $timeout  Timeout in seconds\n	 * @return string\n	 */\n\n	protected function fetch($permlink, $timeout = 10)\n	{\n		if (!$permlink || !function_exists(\'curl_init\'))\n		{\n			return;\n		}\n\n		$uri = \n			\'https://api-ssl.bitly.com/v3/shorten\'.\n				\'?login=\'.urlencode($this->login).\n				\'&apiKey=\'.urlencode($this->apikey).\n				\'&longUrl=\'.urlencode($permlink).\n				\'&format=txt\';\n\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $uri);\n		curl_setopt($ch, CURLOPT_FAILONERROR, 1);\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\n		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);\n		curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\n		$bitcode = curl_exec($ch);\n		curl_close($ch);\n\n		return $bitcode && strpos($bitcode, \'http\') === 0 ? txpspecialchars(trim($bitcode)) : \'\';\n	}\n\n	/**\n	 * Redirect to the admin-side interface.\n	 */\n\n	static public function prefs()\n	{\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_bitly_login\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_bitly_login\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n/**\n * Lists all available custom fields.\n *\n * @param  string $name Preference field\'s name\n * @param  string $val  Current value\n * @return string HTML select field\n */\n\n	function rah_bitly_fields($name, $val)\n	{\n		return selectInput($name, getCustomFields(), $val, true, \'\', $name);\n	}\n', '\n\n/**\n * Rah_bitly plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2011-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_bitly\n * \n * Copyright (C) 2011 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	rah_bitly::get();\n\n/**\n * The plugin class.\n */\n\nclass rah_bitly\n{\n	/**\n	 * Version number.\n	 *\n	 * @var string\n	 */\n\n	static public $version = \'0.4.2\';\n\n	/**\n	 * Stores instances.\n	 *\n	 * @var rah_bitly\n	 */\n\n	static public $instance = null;\n\n	/**\n	 * Article\'s current permlink.\n	 *\n	 * @var string\n	 */\n\n	public $permlink;\n\n	/**\n	 * Article\'s previous permlink.\n	 *\n	 * @var string\n	 */\n\n	public $prev_permlink;\n\n	/**\n	 * Article\'s previous status.\n	 *\n	 * @var int\n	 */\n\n	private $prev_status;\n	\n	/**\n	 * Bitly login.\n	 *\n	 * @var string\n	 */\n\n	private $login;\n\n	/**\n	 * Bitly API key.\n	 *\n	 * @var string\n	 */\n\n	private $apikey;\n\n	/**\n	 * Custom field ID.\n	 *\n	 * @var int\n	 */\n\n	private $field;\n\n	/**\n	 * Installer.\n	 *\n	 * @param string $event Admin-side event.\n	 * @param string $step  Admin-side, plugin-lifecycle step.\n	 */\n\n	static public function install($event = \'\', $step = \'\')\n	{	\n		global $prefs;\n\n		if ($step == \'deleted\')\n		{	\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_bitly\\_%\'\"\n			);\n\n			return;\n		}\n\n		if ((string) get_pref(__CLASS__.\'_version\') === self::$version)\n		{\n			return;\n		}\n\n		$position = 250;\n\n		foreach (\n			array(\n				\'login\'  => array(\'text_input\', \'\'),\n				\'apikey\' => array(\'text_input\', \'\'),\n				\'field\'  => array(\'rah_bitly_fields\', \'\'),\n			) as $name => $val\n		) {\n			$n =  __CLASS__.\'_\'.$name;\n\n			if (!isset($prefs[$n]))\n			{\n				set_pref($n, $val[1], __CLASS__, PREF_ADVANCED, $val[0], $position);\n			}\n\n			$position++;\n		}\n\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, PREF_HIDDEN);\n	}\n\n	/**\n	 * Gets an instance.\n	 *\n	 * @return rah_bitly\n	 */\n\n	static public function get()\n	{\n		if (self::$instance === null)\n		{\n			self::$instance = new rah_bitly();\n		}\n\n		return self::$instance;\n	}\n\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		add_privs(\'prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'initialize\'), \'article\', \'\', 1);\n	}\n\n	/**\n	 * Initializes the plugin.\n	 */\n\n	public function initialize()\n	{	\n		foreach (array(\'login\', \'apikey\', \'field\') as $name)\n		{\n			$this->$name = get_pref(__CLASS__.\'_\'.$name);\n		}\n\n		$this->field = (int) $this->field;\n\n		if (!$this->login || !$this->apikey || !$this->field)\n		{\n			return;\n		}\n\n		include_once txpath.\'/publish/taghandlers.php\';\n\n		$this->previous_state();\n		register_callback(array($this, \'update\'), \'article_saved\');\n		register_callback(array($this, \'update\'), \'article_posted\');\n	}\n\n	/**\n	 * Fetches old article data.\n	 */\n\n	public function previous_state()\n	{	\n		$id = (int) ps(\'ID\');\n\n		if (!$id)\n		{\n			return;\n		}\n\n		$this->prev_permlink = permlinkurl_id($id);\n		$this->prev_status = fetch(\'Status\', \'textpattern\', \'ID\', $id);\n		unset($GLOBALS[\'permlinks\'][$id]);\n	}\n\n	/**\n	 * Hooks to article saving process and updates short URLs.\n	 *\n	 * @param string $event Callback event\n	 * @param string $step  Callback step\n	 * @param array  $r     Article data\n	 */\n\n	public function update($event, $step, $r)\n	{	\n		global $app_mode;\n\n		$this->permlink = permlinkurl_id($r[\'ID\']);\n\n		callback_event(\'rah_bitly.update\', \'\', false, $r);\n\n		if (!$this->permlink || $r[\'Status\'] < STATUS_LIVE)\n		{\n			return;\n		}\n\n		if (\n			$this->prev_permlink !== $this->permlink ||\n			empty($r[\'custom_\'.$this->field]) ||\n			$this->prev_status != $r[\'Status\']\n		) {\n			$uri = $this->fetch($this->permlink);\n		}\n\n		if (empty($uri))\n		{\n			return;\n		}\n\n		$fields = getCustomFields();\n\n		if (!isset($fields[$this->field]))\n		{\n			return;\n		}\n\n		safe_update(\n			\'textpattern\',\n			\'custom_\'.intval($this->field).\"=\'\".doSlash($uri).\"\'\",\n			\"ID=\'\".doSlash($r[\'ID\']).\"\'\"\n		);\n\n		$js = \n			\'$(document).ready(function(){\'.\n				\'$(\\\'input[name=\"custom_\'.$this->field.\'\"]\\\').val(\"\'.escape_js($uri).\'\");\'.\n			\'});\';\n\n		if ($app_mode == \'async\')\n		{\n			send_script_response($js);\n		}\n	}\n\n	/**\n	 * Fetches a Bitly short URL.\n	 *\n	 * @param  string $permlink The long URL to shorten\n	 * @param  int    $timeout  Timeout in seconds\n	 * @return string\n	 */\n\n	protected function fetch($permlink, $timeout = 10)\n	{\n		if (!$permlink || !function_exists(\'curl_init\'))\n		{\n			return;\n		}\n\n		$uri = \n			\'https://api-ssl.bitly.com/v3/shorten\'.\n				\'?login=\'.urlencode($this->login).\n				\'&apiKey=\'.urlencode($this->apikey).\n				\'&longUrl=\'.urlencode($permlink).\n				\'&format=txt\';\n\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $uri);\n		curl_setopt($ch, CURLOPT_FAILONERROR, 1);\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\n		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);\n		curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\n		$bitcode = curl_exec($ch);\n		curl_close($ch);\n\n		return $bitcode && strpos($bitcode, \'http\') === 0 ? txpspecialchars(trim($bitcode)) : \'\';\n	}\n\n	/**\n	 * Redirect to the admin-side interface.\n	 */\n\n	static public function prefs()\n	{\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_bitly_login\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_bitly_login\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n/**\n * Lists all available custom fields.\n *\n * @param  string $name Preference field\'s name\n * @param  string $val  Current value\n * @return string HTML select field\n */\n\n	function rah_bitly_fields($name, $val)\n	{\n		return selectInput($name, getCustomFields(), $val, true, \'\', $name);\n	}\n', '131492dad3e4b973bcb0d24ab870b9ee', '#@admin\n#Default language, en-us, en-gb\nrah_bitly => Bitly integration\nrah_bitly_login => Bitly login\nrah_bitly_apikey => API key\nrah_bitly_field => Store in a custom field\n#@admin\n#@language fi-fi\nrah_bitly => Bitly integraatio\nrah_bitly_login => Bitly tunnus\nrah_bitly_apikey => API-avain\nrah_bitly_field => Tallennataan muokattavaan kenttään', '', 4, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_external_output', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.0.4', 'External content-type snippets', '<h1>rah_external_output</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_external_output\" rel=\"nofollow\">Project page</a> | <a href=\"https://packagist.org/packages/rah/rah_external_output\" rel=\"nofollow\">Packagist</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_external_output\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=30664\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_external_output\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_external_output is a <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern</a> plugin that can be used create custom content-type snippets. These snippets can be requested with a publicly accessible <span class=\"caps\">URL</span>, and used as pages or to provide site&#8217;s content over in form of <span class=\"caps\">JSON</span> or <span class=\"caps\">XML</span> to JavaScript or across domains.</p>\n\n<p>While these snippets can act as pages, they are independent from Textpattern&#8217;s normal content model. Snippets won&#8217;t require or overpopulate <a href=\"http://textpattern.net/wiki/index.php?title=Pages\" rel=\"nofollow\">page templates</a> or <a href=\"http://textpattern.net/wiki/index.php?title=Sections\" rel=\"nofollow\">sections</a>.</p>\n\n<p>This makes the plugin ideal for generating various publicly accessible snippets, including <span class=\"caps\">XML</span> feeds or JavaScript Object Notations that can be then used to feed site&#8217;s content to JavaScript applications, build application layers or to transmit content across domains.</p>\n\n<p>Rah_external_output uses Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">form partials</a> to power its snippets. A form partial that&#8217;s name is prefixed with a <code>rah_eo_</code> can be accessed directly with a <span class=\"caps\">URL</span>, creating its own, independent, page. The term <em>external</em> directly refers to separateness and independency from sections and page templates while still serving client-side content.</p>\n\n<h2>List of features</h2>\n\n<ul>\n	<li><a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">Form partials</a> prefixed with a <code>rah_eo_</code> can be requested, and their output accessed, directly using a public <span class=\"caps\">URL</span>.</li>\n	<li>In addition to normal form partials, these special forms, snippets, can contain <span class=\"caps\">HTTP</span> header lines. Lines at the beginning of the form staring with a semicolon are treated as headers.</li>\n	<li>Form partials can be served using different content-type by appending file extension to the name.</li>\n</ul>\n\n<h2>Requirements</h2>\n\n<p>Rah_external_output&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.4.1 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>The general behavior stands when it comes to installing rah_external_ouput. No extra steps or configuration that variates from normal is needed.</p>\n\n<ol>\n	<li>Download plugin installation file and copy its contents to clipboard.</li>\n	<li>Navigate to <a href=\"http://textpattern.net/wiki/index.php?title=Plugins\" rel=\"nofollow\">Plugins</a> panel and paste the copied code into the <em>Install plugin</em> field, and click <strong>Upload</strong>.</li>\n	<li>You will be shown a preview of the plugin and its documentation. Click <strong>Install</strong> at the bottom of the page.</li>\n	<li>You will be returned to the plugin list when the plugin has been installed. Now activate the plugin by clicking its <strong>No</strong> link, which toggles the status to <strong>Yes</strong> and actives it.</li>\n	<li>The plugin is now installed and activated. All done.</li>\n</ol>\n\n<h2>Basics</h2>\n\n<p>Rah_external_output treats any misc type <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">form partial</a> that name us prefixed with a <code>rah_eo_</code> prefix as a “external” content-type snippet. These special prefixed forms can be accessed using a public callback <span class=\"caps\">URL</span>. This <span class=\"caps\">URL</span> follows the pattern:</p>\n\n<pre><code>http://example.com/?rah_external_output=FormNameWithoutPrefix</code></pre>\n\n<p>Where <code>example.com</code> is the site&#8217;s <span class=\"caps\">URL</span> and <code>FormNameWithoutPrefix</code> is the form&#8217;s name excluding the prefix. The above <span class=\"caps\">URL</span> would output a misc form named as <code>rah_eo_FormNameWithoutPrefix</code>.</p>\n\n<h2>Creating your first snippet</h2>\n\n<p>Creating a new rah_external_output&#8217;s custom form, a snippet, would follow the same steps as <a href=\"http://textpattern.net/wiki/index.php?title=Forms#Creating_a_New_Form\" rel=\"nofollow\">any other form</a>. Go to Forms panel, located under <a href=\"http://textpattern.net/wiki/index.php?title=Presentation\" rel=\"nofollow\">Presentation</a>, and click the <em>Create new form</em> button at the top of the column on the right. This will open a new empty editor.</p>\n\n<p>As with any form partial, you can type in any Textpattern markup you wish to the large code field. This field contains what your snippet will output when accessed using its <span class=\"caps\">URL</span>. For now, type in the field this:</p>\n\n<pre><code>; Content-type: text/plain\n&lt;&lt;txp:site_name /&gt; says&gt; Hello World!</code></pre>\n\n<p>You can then give the snippet a name by typing it in the <em>Name</em> field. This name will be also used for the snippet&#8217;s <span class=\"caps\">URL</span>. To register a form partial as a rah_external_output&#8217;s snippet, the name should be prefixed with <code>rah_eo_</code>. For now, give the form a name <code>rah_eo_hello_world</code>.</p>\n\n<p>Last but not least is a <em>Type</em>. The most appropriate type for a snippet is a <em>misc</em>, but it can be anything if you so wish. Form types are for the most part visual, used for organizing things on the admin-side, from the Write panel&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Write#Override_Form\" rel=\"nofollow\">Override Form</a> field to the Tag Builder.</p>\n\n<p>When you have done with the code, giving it a name and selecting a type, hit the <strong>Save</strong> button. The snippet is now ready for being accessed. After you have saved the editor, you will notice that there is a <em>View</em> link next to the <em>Name</em> field. By clicking the link you will be taken to the snippet&#8217;s generated page.</p>\n\n<p>That page is the snippets publicly accessible <span class=\"caps\">URL</span>, which you can freely use as a page, to serve content to applications and use in client-side scripts. For the snippet <code>rah_eo_hello_world</code> you just created, this <span class=\"caps\">URL</span> would be:</p>\n\n<pre><code>http://example.com/?rah_external_output=hello_world</code></pre>\n\n<p>Where <code>example.com</code> is your site&#8217;s <span class=\"caps\">URL</span> and <code>hello_world</code> the name of the snippet. When accessing the snippet, you should see the following output:</p>\n\n<pre><code>&lt;MySiteName says&gt; Hello World!</code></pre>\n\n<p>Where <code>MySiteName</code> is the name of the site. As you may notice, the page is served as a plain text opposed to <span class=\"caps\">HTML</span>. This is due to a <span class=\"caps\">HTTP</span> header line the snippet&#8217;s code included:</p>\n\n<pre><code>; Content-type: text/plain</code></pre>\n\n<p>All rah_external_output&#8217;s snippets support few unique features including custom <span class=\"caps\">HTTP</span> header lines and file extensions.</p>\n\n<h2>Custom <span class=\"caps\">HTTP</span> headers</h2>\n\n<p>In addition to a form partial&#8217;s normal features, rah_external_output&#8217;s special forms, snippets, support sending custom <span class=\"caps\">HTTP</span> headers, including a content-type header.</p>\n\n<p>All lines at the beginning of the form starting with a semicolon (<code>;</code>) are sent as <span class=\"caps\">HTTP</span> headers. These lines should be placed at the very beginning of a form, starting from the first line. <span class=\"caps\">HTTP</span> header lines can not be preceded by any white-space or empty lines.</p>\n\n<p><span class=\"caps\">HTTP</span> header lines follow the normal syntax you would expect from a <span class=\"caps\">HTTP</span> header field definitions. A line consist of a header property, also commonly referred as a field, followed by a colon (<code>:</code>) and a value.</p>\n\n<pre><code>; Property: Value</code></pre>\n\n<p>A form supports unlimited number of header lines. Each subsequent line starting with a semicolon is sent as a <span class=\"caps\">HTTP</span> header.</p>\n\n<pre><code>; Content-type: text/javascript\n; Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\n; Expires: Sat, 24 Jul 2003 05:00:00 GMT\n; Last-Modified: Wed, 1 Jan 2025 05:00:00 GMT\n; Pragma: no-cache</code></pre>\n\n<p>Using these <span class=\"caps\">HTTP</span> header lines visitor can be redirected, or document&#8217;s content-type, encoding, caching or timestamp attributes changed. A form that starts with the following header line would be sent as a plain-text:</p>\n\n<pre><code>; Content-type: text/plain\n\n&lt;txp:article_custom limit=&quot;15&quot;&gt;\n	&lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;\n&lt;/txp:article_custom&gt;</code></pre>\n\n<p>Note that for security reasons <span class=\"caps\">HTTP</span> header lines can not contain Textpattern tags. Headers are collected from the source code before any Textpattern tags are processed. This is to prevent any type of header injections that could occur otherwise.</p>\n\n<h2>Content-types and file extensions</h2>\n\n<p>In addition to custom <span class=\"caps\">HTTP</span> headers, a snippet&#8217;s content-type can be set using a file extension, appended to the form&#8217;s name. Supported extensions are <code>.json</code>, <code>.js</code>, <code>.xml</code>, <code>.css</code>, <code>.txt</code> and <code>.html</code>.</p>\n\n<p>If a form is named as a <code>rah_eo_jsontable.json</code>, the snippet would be served as a <span class=\"caps\">JSON</span> presentation when <code>http://example.com/?rah_external_output=jsontable.json</code> is accessed.</p>\n\n<h2>Tag trace and debugging</h2>\n\n<p>As with Textpattern&#8217;s normal page templates, rah_external_output&#8217;s snippets support <a href=\"http://textpattern.net/wiki/index.php?title=tag_trace\" rel=\"nofollow\">tag traces</a>. In <a href=\"http://textpattern.net/wiki/index.php?title=Basic_Preferences#Production_Status\" rel=\"nofollow\">debugging mode</a>, a tag trace is appended to the <span class=\"caps\">HTML</span> output of each snippet that ends with a <code>.html</code> extension. As a tag trace on a normal page, the trace tells you which Textpattern tags are being executed and their outcome.</p>\n\n<h2>Example snippets</h2>\n\n<p>As the plugin can be used to serve any type of snippet and content there is no to say what you should do with it or not. To give out a simple idea how you could benefit from the plugin, please see our <a href=\"https://github.com/gocom/rah_external_output/tree/master/examples\" rel=\"nofollow\">examples directory on GitHub</a>. The directory contains some basic, yet real-world proof examples. Examples include <span class=\"caps\">XML</span> sitemap generation and some <span class=\"caps\">JSON</span> serving.</p>\n\n<h2>For developers</h2>\n\n<p>The plugin comes with few features targeted to developers.</p>\n\n<h3>Callback</h3>\n\n<p>One of these developer features is a callback event which is fired when a snippet is viewed. The callback in question is named <code>rah_external_output.snippet_end</code> and is executed at the very end of a snippet. This callback can be used like any other Textpattern&#8217;s callback event. Hooking to the event happens using <code>register_callback()</code> function.</p>\n\n<pre><code>register_callback(callback, \'rah_external_output.snippet_end\');</code></pre>\n\n<p>Please see <a href=\"http://textpattern.net/wiki/index.php?title=Plugin_basics\" rel=\"nofollow\">Plugin Basics</a> for more information about plugin development and callbacks.</p>\n\n<h3>File extensions</h3>\n\n<p>Rah_external_output allows extending the recognized <span class=\"caps\">MIME</span> types and extensions. This can be done by modifying a global variable. The variable is named <code>$rah_external_output_mime</code> and it takes an array map of <code>extension =&gt; mime/type</code> key-value pairs.</p>\n\n<pre><code>global $rah_external_output_mime;\n$rah_external_output_mime[\'png\'] = \'image/png\';\n$rah_external_output_mime[\'svg\'] = \'image/svg+xml\';</code></pre>\n\n<h2>Changelog</h2>\n\n<h3>Version 1.0.4 &#8211; 2013/05/07</h3>\n\n<ul>\n	<li>Changed: The installer doesn&#8217;t use embedded version numbers. The only purpose for the installer is to handle migrations anyway.</li>\n</ul>\n\n<h3>Version 1.0.3 &#8211; 2013/05/06</h3>\n\n<ul>\n	<li>Composer package now uses <a href=\"https://packagist.org/packages/textpattern/lock\" rel=\"nofollow\">textpattern/lock</a> and <a href=\"https://packagist.org/packages/textpattern/installer\" rel=\"nofollow\">textpattern/installer</a>. The package installs to Textpattern without any extra configuration.</li>\n</ul>\n\n<h3>Version 1.0.2 &#8211; 2013/04/23</h3>\n\n<ul>\n	<li>Fixed: <span class=\"caps\">PHP</span> namespace compatibility.</li>\n	<li>Changed: Under Textpattern 4.6.0-dev adds the ‘View’ link to the new action bar.</li>\n	<li>Improved: Internal clean up.</li>\n	<li>Improved: Use Textpattern&#8217;s assigned flags to ensure future compatibility.</li>\n	<li>Improved: Creating preferences to the memory isn&#8217;t necessary.</li>\n</ul>\n\n<h3>Version 1.0.1 &#8211; 2012/08/31</h3>\n\n<ul>\n	<li>Changed: Now doesn&#8217;t uninstall <code>rah_eo_</code> prefixed form partials with the plugin. These forms could be used for something else than just as the plugin&#8217;s snippets.</li>\n	<li>Dropped: code path used as a plugin cache fallback. Now relays on existence of plugin-lifecycle callbacks.</li>\n	<li>Dropped: migration cleaner deployed in v0.6. Is no longer relevant.</li>\n</ul>\n\n<h3>Version 1.0 &#8211; 2012/07/14</h3>\n\n<ul>\n	<li>Removed: Plugin&#8217;s own user interface. The plugin now uses <code>rah_eo_</code> prefixed form partials and integrates with Forms panel.</li>\n	<li>Removed: <code>&lt;txp:rah_external_output /&gt;</code> tag. As forms are used, normal and more flexible <a href=\"http://textpattern.net/wiki/index.php?title=output_form\" rel=\"nofollow\">output_form</a> tag can be used.</li>\n	<li>Removed: Raw <span class=\"caps\">PHP</span> support to comply with <a href=\"http://code.google.com/p/textpattern/source/detail?r=3706\" rel=\"nofollow\">r3706.</a></li>\n	<li>Added: Ability to set a snippet&#8217;s content-type using a file extension in the name.</li>\n	<li>Added: Migration assistant script. The script is run automatically on install and migrates rah_external_output snippets from the old interface to Forms.</li>\n	<li>Added: <code>rah_external_output.snippet_end</code> callback event for developers.</li>\n	<li>Changed: Returns a 404 page instead of the home page when requesting a nonexistent snippet.</li>\n	<li>Changed: <a href=\"http://textpattern.net/wiki/index.php?title=tag_trace\" rel=\"nofollow\">Tag trace</a> can no longer be controlled using a <span class=\"caps\">URL</span> parameter. A tag trace is added when the snippet name has a <code>.html</code> extension and the site is in debugging mode.</li>\n	<li>Now requires PHP5 or newer.</li>\n	<li>Compatibility with Textpattern v4.5.0.</li>\n</ul>\n\n<h3>Version 0.9 &#8211; 2011/09/03</h3>\n\n<ul>\n	<li>Fixed: now handles raw <span class=\"caps\">PHP</span> tags.</li>\n	<li>Changed: now parses tag structure in same fashion as core. Do it twice. Provides identical results with core in every scenario.</li>\n	<li>Added: ability to display tag trace, and error reporting, by adding <code>rah_external_output_trace</code> parameter (<code>&amp;rah_external_output_trace=1</code>) to the snippet <span class=\"caps\">URL</span> when site&#8217;s production status is set to <em>debugging</em>.</li>\n</ul>\n\n<h3>Version 0.8 &#8211; 2011/07/26</h3>\n\n<ul>\n	<li>Added: <span class=\"caps\">CSRF</span> (session riding) protection using Textpattern&#8217;s core functions introduced in v4.4.1.</li>\n	<li>Changed: Make sure the plugin interfaces is all in one language.</li>\n	<li>Changed: Only try to drop old database tables when humanly possible that there is old leftovers. Don&#8217;t run queries when updating from clean to clean.</li>\n	<li>Changed: set temporary version number when installing. Removes the possibility of running the installer twice for no reason.</li>\n	<li>Now requires Textpattern version 4.4.1 or newer.</li>\n</ul>\n\n<h3>Version 0.7 &#8211; 2011/06/12</h3>\n\n<ul>\n	<li>Fixed: Error in <code>&lt;txp:rah_external_output /&gt;</code> tag.</li>\n	<li>Fixed: Closed open <code>&lt;thead&gt;</code> tag in the main list view.</li>\n	<li>Fixed: Now admin-side page title uses language strings.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2011/04/15</h3>\n\n<ul>\n	<li>Fixed: Saving snippets while changing name.</li>\n	<li>Fixed: Now keeps the sent data in the editor if error occurs during saving instead of fetching the old data from the database.</li>\n	<li>Added: Translation support. Interface now uses language strings.</li>\n	<li>Added: <code>&lt;txp:rah_external_output /&gt;</code> tag now caches fetched results.</li>\n	<li>Added: Now uses plugin_lifecycle callbacks, and includes uninstaller.</li>\n	<li>Added: Adds an options link to the Plugins pane which directs to the plugin&#8217;s admin-interface.</li>\n	<li>Removed: Mime-interface. Was confusing and not so many used it. Trying to simplify the user interface.</li>\n	<li>Changed: The user-interface and markup. Removed heading, removed inline styles, added <code>&lt;tbody&gt;</code> and <code>&lt;thead&gt;</code> tags to the tables.</li>\n	<li>Changed: Now uses new, improved multi-selection/edit feature, seen in other rah-plugins.</li>\n	<li>Changed: now uses <code>textpattern</code> callback instead of outputting the snippets right away when the plugin is loaded.</li>\n	<li>Changed: Now checks if the saving/updating succeeds, instead of expecting.</li>\n	<li>Now requires Textpattern 4.2.0 (or newer) for full feature compatibility.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2010/04/11</h3>\n\n<ul>\n	<li>Added <em>disable</em> and <em>activate</em> actions to the multiedit feature.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2009/06/08</h3>\n\n<ul>\n	<li>Fixed forgotten insert query escaping.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2009/05/10</h3>\n\n<ul>\n	<li>Fixed error caused by non-set <code>$pretext</code> (note: <span class=\"caps\">TXP</span> load order).</li>\n	<li>Set <code>$pretext</code> indexes to empty: we are not on page template.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2009/05/09</h3>\n\n<ul>\n	<li>Fixed forgotten parse call.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/05/09</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_external_output plugin for Textpattern CMS\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_external_output\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nclass rah_external_output\n{\n	/**\n	 * The installer.\n	 */\n\n	public function install()\n	{\n		@$rs = safe_rows(\n			\'name, content_type, code, allow\',\n			\'rah_external_output\',\n			\'1 = 1\'\n		);\n\n		if ($rs)\n		{\n			foreach ($rs as $a)\n			{\n				extract($a);\n\n				$name = ($allow != \'Yes\' ? \'_\' : \'\') . \'rah_eo_\'.$name;\n\n				if (safe_count(\'txp_form\', \"name=\'\".doSlash($name).\"\'\"))\n				{\n					continue;\n				}\n\n				$code = ($content_type ? \'; Content-type: \'.$content_type.n : \'\') . $code;\n	\n				@safe_insert(\n					\'txp_form\',\n					\"name = \'\".doSlash($name).\"\', type = \'misc\', Form = \'\".doSlash($code).\"\'\"\n				);\n			}\n\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_external_output\'));\n		}\n	}\n\n	/**\n	 * Uninstaller.\n	 */\n\n	public function uninstall()\n	{\n		safe_delete(\'txp_prefs\', \"name like \'rah\\_external\\_output\\_%\'\");\n	}\n\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		register_callback(array($this, \'install\'), \'plugin_lifecycle.rah_external_output\', \'installed\');\n		register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.rah_external_output\', \'deleted\');\n		register_callback(array($this, \'view\'), \'form\');\n		register_callback(array($this, \'get_snippet\'), \'textpattern\');\n	}\n\n	/**\n	 * Outputs external snippets.\n	 */\n\n	public function get_snippet()\n	{	\n		global $microstart, $qcount, $qtime, $txptrace, $rah_external_output_mime;\n\n		$name = gps(\'rah_external_output\');\n\n		if ($name === \'\' || !is_string($name))\n		{\n			return;\n		}\n\n		$r = safe_field(\n			\'Form\', \n			\'txp_form\', \n			\"name=\'\".doSlash(\'rah_eo_\'.$name).\"\'\"\n		);\n\n		if ($r === false)\n		{\n			txp_die(gTxt(\'404_not_found\'), 404);\n		}\n\n		$mime = array(\n			\'json\' => \'application/json\',\n			\'js\'   => \'text/javascript\',\n			\'xml\'  => \'text/xml\',\n			\'css\'  => \'text/css\',\n			\'txt\'  => \'text/plain\',\n			\'html\' => \'text/html\',\n		) + (array) $rah_external_output_mime;\n\n		ob_clean();\n		txp_status_header(\'200 OK\');\n		$ext = pathinfo($name, PATHINFO_EXTENSION);\n\n		if ($ext && isset($mime[$ext]))\n		{\n			header(\'Content-type: \'.$mime[$ext].\'; charset=utf-8\');\n		}\n\n		$lines = explode(n, $r);\n\n		foreach ($lines as $line)\n		{	\n			if (strpos($line, \';\') !== 0)\n			{\n				break;\n			}\n\n			header(trim(substr(array_shift($lines), 1)));\n		}\n\n		set_error_handler(\'tagErrorHandler\');\n		echo parse(parse(implode(n, $lines)));\n		restore_error_handler();\n\n		if ($ext == \'html\' && get_pref(\'production_status\') == \'debug\')\n		{\n			echo \n				n.comment(\'Runtime: \'.substr(getmicrotime() - $microstart, 0, 6)).\n				n.comment(\'Query time: \'.sprintf(\'%02.6f\', $qtime)).\n				n.comment(\'Queries: \'.$qcount).\n				maxMemUsage(\'end of textpattern()\', 1).\n				n.comment(\'txp tag trace: \'.n.str_replace(\'--\', \'&shy;&shy;\', implode(n, (array) $txptrace)));\n		}\n\n		callback_event(\'rah_external_output.snippet_end\');\n		exit;\n	}\n\n	/**\n	 * Adds a view link to the form editor.\n	 */\n\n	public function view()\n	{	\n		$view = escape_js(gTxt(\'view\'));\n		$hu = escape_js(hu);\n\n		$js = <<<EOF\n			$(document).ready(function ()\n			{\n				var input = $(\'input[name=\"name\"]\');\n\n				if (input.val().indexOf(\'rah_eo_\') !== 0)\n				{\n					return;\n				}\n\n				var uri = \'{$hu}?rah_external_output=\' + input.val().substr(7);\n				var link = $(\'<a href=\"#\">{$view}</a>\').attr(\'href\', uri);\n				var actions = $(\'.txp-actions\');\n\n				if (actions.length)\n				{\n					actions.append(\' \').append(link);\n				}\n				else\n				{\n					input.after(link).after(\' \');\n				}\n\n				link.click(function (e)\n				{\n					e.preventDefault();\n					window.open(uri);\n				});\n			});\nEOF;\n\n		echo script_js($js);\n	}\n}\n\nnew rah_external_output();', '\n\n/**\n * Rah_external_output plugin for Textpattern CMS\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_external_output\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nclass rah_external_output\n{\n	/**\n	 * The installer.\n	 */\n\n	public function install()\n	{\n		@$rs = safe_rows(\n			\'name, content_type, code, allow\',\n			\'rah_external_output\',\n			\'1 = 1\'\n		);\n\n		if ($rs)\n		{\n			foreach ($rs as $a)\n			{\n				extract($a);\n\n				$name = ($allow != \'Yes\' ? \'_\' : \'\') . \'rah_eo_\'.$name;\n\n				if (safe_count(\'txp_form\', \"name=\'\".doSlash($name).\"\'\"))\n				{\n					continue;\n				}\n\n				$code = ($content_type ? \'; Content-type: \'.$content_type.n : \'\') . $code;\n	\n				@safe_insert(\n					\'txp_form\',\n					\"name = \'\".doSlash($name).\"\', type = \'misc\', Form = \'\".doSlash($code).\"\'\"\n				);\n			}\n\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_external_output\'));\n		}\n	}\n\n	/**\n	 * Uninstaller.\n	 */\n\n	public function uninstall()\n	{\n		safe_delete(\'txp_prefs\', \"name like \'rah\\_external\\_output\\_%\'\");\n	}\n\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		register_callback(array($this, \'install\'), \'plugin_lifecycle.rah_external_output\', \'installed\');\n		register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.rah_external_output\', \'deleted\');\n		register_callback(array($this, \'view\'), \'form\');\n		register_callback(array($this, \'get_snippet\'), \'textpattern\');\n	}\n\n	/**\n	 * Outputs external snippets.\n	 */\n\n	public function get_snippet()\n	{	\n		global $microstart, $qcount, $qtime, $txptrace, $rah_external_output_mime;\n\n		$name = gps(\'rah_external_output\');\n\n		if ($name === \'\' || !is_string($name))\n		{\n			return;\n		}\n\n		$r = safe_field(\n			\'Form\', \n			\'txp_form\', \n			\"name=\'\".doSlash(\'rah_eo_\'.$name).\"\'\"\n		);\n\n		if ($r === false)\n		{\n			txp_die(gTxt(\'404_not_found\'), 404);\n		}\n\n		$mime = array(\n			\'json\' => \'application/json\',\n			\'js\'   => \'text/javascript\',\n			\'xml\'  => \'text/xml\',\n			\'css\'  => \'text/css\',\n			\'txt\'  => \'text/plain\',\n			\'html\' => \'text/html\',\n		) + (array) $rah_external_output_mime;\n\n		ob_clean();\n		txp_status_header(\'200 OK\');\n		$ext = pathinfo($name, PATHINFO_EXTENSION);\n\n		if ($ext && isset($mime[$ext]))\n		{\n			header(\'Content-type: \'.$mime[$ext].\'; charset=utf-8\');\n		}\n\n		$lines = explode(n, $r);\n\n		foreach ($lines as $line)\n		{	\n			if (strpos($line, \';\') !== 0)\n			{\n				break;\n			}\n\n			header(trim(substr(array_shift($lines), 1)));\n		}\n\n		set_error_handler(\'tagErrorHandler\');\n		echo parse(parse(implode(n, $lines)));\n		restore_error_handler();\n\n		if ($ext == \'html\' && get_pref(\'production_status\') == \'debug\')\n		{\n			echo \n				n.comment(\'Runtime: \'.substr(getmicrotime() - $microstart, 0, 6)).\n				n.comment(\'Query time: \'.sprintf(\'%02.6f\', $qtime)).\n				n.comment(\'Queries: \'.$qcount).\n				maxMemUsage(\'end of textpattern()\', 1).\n				n.comment(\'txp tag trace: \'.n.str_replace(\'--\', \'&shy;&shy;\', implode(n, (array) $txptrace)));\n		}\n\n		callback_event(\'rah_external_output.snippet_end\');\n		exit;\n	}\n\n	/**\n	 * Adds a view link to the form editor.\n	 */\n\n	public function view()\n	{	\n		$view = escape_js(gTxt(\'view\'));\n		$hu = escape_js(hu);\n\n		$js = <<<EOF\n			$(document).ready(function ()\n			{\n				var input = $(\'input[name=\"name\"]\');\n\n				if (input.val().indexOf(\'rah_eo_\') !== 0)\n				{\n					return;\n				}\n\n				var uri = \'{$hu}?rah_external_output=\' + input.val().substr(7);\n				var link = $(\'<a href=\"#\">{$view}</a>\').attr(\'href\', uri);\n				var actions = $(\'.txp-actions\');\n\n				if (actions.length)\n				{\n					actions.append(\' \').append(link);\n				}\n				else\n				{\n					input.after(link).after(\' \');\n				}\n\n				link.click(function (e)\n				{\n					e.preventDefault();\n					window.open(uri);\n				});\n			});\nEOF;\n\n		echo script_js($js);\n	}\n}\n\nnew rah_external_output();', '578ce9aa50f5256685f3fbf99af75ab9', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_function', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.7.1', 'Every function is a Textpattern tag', '<h1>rah_function</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_function\" rel=\"nofollow\">Project page</a> | <a href=\"https://packagist.org/packages/rah/rah_function\" rel=\"nofollow\">Packagist</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_function\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=32389\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_function\" rel=\"nofollow\">Donate</a></p>\n\n<p>Every function is a <code>&lt;txp:tag /&gt;</code>. Rah_function allows you to call any <span class=\"caps\">PHP</span> function in Textpattern&#8217;s tag syntax. No need for <span class=\"caps\">PHP</span> snippets, ending nor closing <span class=\"caps\">PHP</span> tags &#8212; the only thing you need is a <span class=\"caps\">XML</span> like tag.</p>\n\n<h2>List of features</h2>\n\n<ul>\n	<li>Call any <span class=\"caps\">PHP</span> function with a single Textpattern tag.</li>\n	<li>Freely use functions attributes, flags and parameters.</li>\n	<li>Supports both a container- and a single-tag format.</li>\n</ul>\n\n<h2>Requirements</h2>\n\n<p>Rah_function&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.0.7 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_function&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<p>The plugin respects Textpattern&#8217;s scripting preferences. After installation make sure that you have allowed <span class=\"caps\">PHP</span> scripting:</p>\n\n<ol>\n	<li>Go to Textpattern&#8217;s Advanced Preferences (Textpattern &gt; Admin &gt; Preferences &gt; Advanced).</li>\n	<li>Toggle <em>allow</em> on <span class=\"caps\">PHP</span> scripting for Pages and Articles (or for where you want to use the plugin).</li>\n</ol>\n\n<h2>Basics</h2>\n\n<pre><code>&lt;txp:rah_function call=&quot;function&quot; thing=&quot;here&quot; parameter1=&quot;value&quot; parameter2=&quot;value&quot;&gt;\n	Contained statement\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>The plugin introduces a <code>&lt;txp:rah_function /&gt;</code> tag to Textpattern&#8217;s arsenal. It&#8217;s a multi-purpose tag that enables using public <span class=\"caps\">PHP</span> functions and methods as Textpattern tags.</p>\n\n<p>Rah_function is almost like a bridge between <span class=\"caps\">PHP</span> and Textpattern&#8217;s tag syntax. It allows calling almost any <span class=\"caps\">PHP</span> function as a Textpattern tag. Want to encode something? You can. Want use <span class=\"caps\">PHP</span>&#8217;s string functions to truncate, count or otherwise bend strings at your will? You can. All without needing to write any code for new tags or add raw <span class=\"caps\">PHP</span> blocks in page templates.</p>\n\n<p>The tag takes a <code>call</code> attribute which sets the function that is called by the tag. Apart from a <code>thing</code>, all attributes used in a tag are passed to the called <span class=\"caps\">PHP</span> function as its arguments. The tag then processes the request and returns the results.</p>\n\n<h2>Tag attributes</h2>\n\n<p>Attributes for <code>&lt;txp:rah_function /&gt;</code> are as follows:</p>\n\n<p><strong>call</strong><br />\nName of the function or the class method you want to use with the tag. Comma-separated if multiple. This attribute is required.<br />\nExample: <code>call=&quot;base64_encode&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>thing</strong><br />\nDefines the argument position of a container tag&#8217;s contained statement. If a <code>thing=&quot;here&quot;</code> is the last defined attribute in the tag, then the contained statement is used as the last argument too for the <span class=\"caps\">PHP</span> function. If <code>thing</code> is left undefined, the contained statement is used as the first argument.<br />\nExample: <code>foo=&quot;bar&quot; thing=&quot;here&quot; bar=&quot;foo&quot;</code> Default: <em>undefined</em>.</p>\n\n<p><strong>_is</strong><br />\nConverts the tag into a <a href=\"http://textpattern.net/wiki/index.php?title=Self-closed_versus_Container_Tags\" rel=\"nofollow\">conditional</a>. If set, the tag&#8217;s results are compared against the value of the <code>_is</code> attribute. If they match, the tag&#8217;s contained statement is shown.<br />\nExample: <code>_is=&quot;FALSE&quot;</code> Default: <em>undefined</em>.</p>\n\n<p><strong>_assign</strong><br />\nCreates a <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variable</a> containing the tag&#8217;s returned value. The value is used as the variable&#8217;s name.<br />\nExample: <code>_assign=&quot;variable&quot;</code> Default: <em>undefined</em>.</p>\n\n<p><strong>parameter1=”“ , parameter2=”“ , parameter3=”“,[..]</strong><br />\nZero or more parameters passed to the called function as arguments. Parameters are assigned to the function in the order they are defined in the tag. These additional tag attributes can be named anything. A valid attribute name can contain letters (A-z) and numbers (0-9). The names <code>thing</code> and <code>call</code> are reserved.<br />\nExample: <code>foo2=&quot;bar&quot; foo1=&quot;bar&quot; foo4=&quot;bar&quot;</code> Default: <em>undefined</em>.</p>\n\n<h2>Function parameters</h2>\n\n<p>Apart from the tag&#8217;s own reserved attributes, including <code>thing</code> and <code>call</code>, all attributes are passed to the called function as arguments in the order in which they are defined. If no additional attributes are used, then the called <span class=\"caps\">PHP</span> function is used as is without arguments.</p>\n\n<p>When calling <a href=\"http://php.net/manual/en/function.md5.php\" rel=\"nofollow\">md5</a>, the first attribute would be used as the string from which a hash is calculated.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;md5&quot; str=&quot;apple&quot; /&gt;</code></pre>\n\n<p>Above returns <code>1f3870be274f6c49b3e31a0c6728957f</code>, a MD5 hash of an apple.</p>\n\n<h2>Containers and self-closing tags</h2>\n\n<p>Rah_function supports both container and self-closing, single tag use.</p>\n\n<h3>As a container tag</h3>\n\n<p>The tag supports both a container and self-closing usage. When using the tag as a container tag, the contained statement is used in the parameter position specified by the tag&#8217;s <code>thing=&quot;here&quot;</code> attribute. If the <code>thing</code> attribute is undefined, the contained statement is used as the first parameter of the <span class=\"caps\">PHP</span> function.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;str_replace&quot; from=&quot;Hello&quot; to=&quot;Hi&quot; thing=&quot;here&quot;&gt;\n	Hello World!\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>In the snippet above, the contained statement <code>Hi World!</code> is used as the third parameter of the <a href=\"http://php.net/manual/en/function.str-replace.php\" rel=\"nofollow\">str_replace</a> function because that is where the <code>thing=&quot;here&quot;</code> is ordered. The snippet returns <code>Hello World!</code>, as expected. If <code>thing</code> attribute wasn&#8217;t used, the contained statement would be applied as first argument, leading to different results.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;str_replace&quot; from=&quot;Hello&quot; to=&quot;Hi&quot;&gt;\n	Hello World!\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Still looks pretty much the same, but unlike the previous example where <code>thing</code> was used, now the tag returns <code>Hello</code>. Instead of the <code>to</code> attribute, the contained statement would be used as the searched needle in str_replace.</p>\n\n<h3>As a self-closing tag</h3>\n\n<p>A container translates seamlessly to a singular self-closing tag. The contained statement is a function parameter just like tag attributes, and as such, can be substituted with a tag attribute. The following does exactly same thing as the previous str_replace examples that used containers.</p>\n\n<pre><code>&lt;txp:rah_function \n	call=&quot;str_replace&quot; \n	from=&quot;Hi&quot; \n	to=&quot;Hello&quot; \n	string=&quot;Hi World!&quot; \n/&gt;</code></pre>\n\n<p>These two <a href=\"http://php.net/manual/en/function.htmlspecialchars.php\" rel=\"nofollow\">htmlspecialchars</a> snippets give the same results, but do it with different syntaxes. As a self-closing tag, a string would be passed to the function as a tag attribute.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;htmlspecialchars&quot; string=&quot;&lt;p class=&quot;&quot;msg&quot;&quot;&gt;Hello World!&lt;/p&gt;&quot; /&gt;</code></pre>\n\n<p>But that string can also be passed as a contained statement. Since the string is htmlspecialchars function&#8217;s first parameter, the tag doesn&#8217;t need a <code>thing</code> attribute.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;htmlspecialchars&quot;&gt;\n	&lt;p class=&quot;msg&quot;&gt;Hello World!&lt;/p&gt;\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Gives a relatively readable formatting and avoids <a href=\"http://textpattern.net/wiki/index.php?title=Parsing_Tag_Attributes\" rel=\"nofollow\">quote escaping</a> that comes with tag attributes.</p>\n\n<h2>Conditionals</h2>\n\n<p>A rah_function tag can transform into a conditional by applying the <code>_is</code> attribute. When <code>_is</code> is used, the tag&#8217;s results are compared against the attribute&#8217;s value. If they match, the tag&#8217;s contained statement is shown. If compared values do not match, an <a href=\"http://textpattern.net/wiki/index.php?title=else\" rel=\"nofollow\">else</a> statement is shown, if defined.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;cs&quot; name=&quot;theme&quot; _is=&quot;blue&quot;&gt;\n	Theme is set as blue.\n&lt;txp:else /&gt;\n	No blue?\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>The above snippet checks if an <span class=\"caps\">HTTP</span> cookie named <code>theme</code> is set as <code>blue</code>.</p>\n\n<h2>Calling multiple functions at once</h2>\n\n<p>Since version 0.5, a single tag can call multiple functions. This allows to further process output returned by a function with a second or more functions. Calling multiple functions with a single tag instance is done by simply using comma-separated (<code>,</code>) list of functions in the <code>call</code> attribute.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;strip_tags, trim&quot;&gt;\n	&lt;p&gt;Some markup to &lt;strong&gt;strip&lt;/strong&gt; and surrounding white-space to trim.&lt;/p&gt;\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Specified functions are processed from left to right. In the above snippet, <a href=\"http://php.net/manual/en/function.strip-tags.php\" rel=\"nofollow\">strip_tags</a> is ran first and its output is then passed on to <a href=\"http://php.net/manual/en/function.trim.php\" rel=\"nofollow\">trim</a>.</p>\n\n<p>The first function in the list acts as the primary one. Its output is passed by reference to the following functions and all tag attributes apply only to it. The second or later functions do not get passed any attributes and the output from the first function is assigned as the consecutive functions’ first parameter.</p>\n\n<p>Calling multiple functions works only if the following functions expect a single parameter. If they require more than a one parameter, more than one <code>&lt;txp:rah_function /&gt;</code> tag is needed.</p>\n\n<p>Calling both <a href=\"http://www.php.net/manual/en/function.str-replace.php\" rel=\"nofollow\">str_replace</a> and <a href=\"http://www.php.net/manual/en/function.substr.php\" rel=\"nofollow\">substr</a> would require two tags as both require two or more parameters.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;str_replace&quot; from=&quot;Hi&quot; to=&quot;Hello&quot;&gt;\n	&lt;txp:rah_function call=&quot;substr&quot; string=&quot;Hello World!&quot; start=&quot;0&quot; end=7&quot; /&gt;\n&lt;/txp:rah_function&gt;</code></pre>\n\n<h2>Returned values and legal types</h2>\n\n<p>Due to how Textpattern&#8217;s template language and <span class=\"caps\">PHP</span> work, not every function&#8217;s output can be returned to the template in its original format. The limitation comes in the form of <a href=\"http://php.net/manual/en/language.types.php\" rel=\"nofollow\">types</a>.</p>\n\n<h3>Integers, floats and strings</h3>\n\n<p>Textpattern&#8217;s markup language expects strings, and that is what we must give it. Rah_function returns values of types <em>integer</em>, <em>float</em> and <em>string</em> to the template as is, in their true presentation, with the expectation that the returned type is converted to a string by Textpattern.</p>\n\n<h3>Booleans</h3>\n\n<p>The last from the scalars, boolean, is converted to an uppercase string <code>TRUE</code> or <code>FALSE</code>. This is to allow differentiating an empty string (<code>&quot;&quot;</code>), numbers <code>1</code>, <code>0</code> and booleans from one another, otherwise in Textpattern&#8217;s template context you would have no idea which is which.</p>\n\n<p>For instance, <span class=\"caps\">PHP</span>&#8217;s <a href=\"http://www.php.net/manual/en/function.strpos.php\" rel=\"nofollow\">strpos</a> returns an integer starting from zero or a boolean <code>FALSE</code> when no matches are found. When the output gets converted to a string, that zero and <span class=\"caps\">FALSE</span> become the same, and there would be no way of knowing whether there were any matches.</p>\n\n<pre><code>&lt;txp:rah_function\n	call=&quot;strpos&quot;\n	value=&quot;ABC, zero or false?&quot;\n	search=&quot;A&quot;\n/&gt;</code></pre>\n\n<p>If no conversion was done, the above would return a zero/empty, but so it would act as if there were no matches. Through the conversion, the position and boolean are distinguishable.</p>\n\n<pre><code>&lt;txp:rah_function\n	call=&quot;strpos&quot;\n	value=&quot;ABC, zero or false?&quot;\n	search=&quot;A&quot;\n	_assign=&quot;matches&quot;\n/&gt;\n\n&lt;txp:if_variable name=&quot;matches&quot; value=&quot;FALSE&quot;&gt;\n	No matches.\n&lt;txp:else /&gt;\n	First match at &lt;txp:variable name=&quot;matches&quot; /&gt;\n&lt;/txp:if_variable&gt;</code></pre>\n\n<h3>Arrays</h3>\n\n<p>Returned <a href=\"http://www.php.net/manual/en/language.types.array.php\" rel=\"nofollow\">arrays</a> will be converted to <a href=\"http://php.net/manual/en/function.json-encode.php\" rel=\"nofollow\"><span class=\"caps\">JSON</span></a> representations. The following snippet would split the list of values into an array.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;explode&quot; delimiter=&quot;, &quot; values=&quot;value1, value2, value3&quot; /&gt;</code></pre>\n\n<p>The numeric array returned by <a href=\"http://php.net/manual/en/function.explode.php\" rel=\"nofollow\">explode</a> would be converted and returned as a literal JavaScript array.</p>\n\n<pre><code>[&quot;value1&quot;,&quot;value2&quot;,&quot;value3&quot;]</code></pre>\n\n<h3>Discarded</h3>\n\n<p>The rest of types, including object, resource, <span class=\"caps\">NULL</span> and callable will be discarded and a notice will be issued. Discarding is done to prevent issues. The types that are not returned do not translate to the markup language and are not usable in string context. Thus, they are not returned. While the illegal output will not be returned, the functions will still be executed. For instance, a class method that returns an object can still be executed just fine with rah_function. There just won&#8217;t be any output apart from a harmless, informative error message.</p>\n\n<h2>Type casting and special attribute prefixes</h2>\n\n<pre><code>&lt;txp:rah_function \n	_boolAttr=&quot;boolean&quot;\n	_nullAttr=&quot;NULL&quot;\n	_intAttr=&quot;integer&quot;\n	_arrayAttr=&quot;array&quot;\n/&gt;</code></pre>\n\n<p>To get around template language&#8217;s type juggling limitations, rah_function packs a couple special attribute prefixes. These prefixes can be added before tag attribute names, allowing to cast values to different types before passing the attributes to the called function as arguments. These special attribute prefixes are <code>_bool</code>, <code>_null</code>, <code>_int</code> and <code>_array</code>.</p>\n\n<h3>_bool</h3>\n\n<p>If a tag attribute is prefixed with <code>_bool</code>, the value is converted to a boolean type. The value is converted to <code>FALSE</code> if it is empty (<code>&quot;&quot;</code>), <code>0</code> or uppercase string <code>FALSE</code>. If it&#8217;s anything else, it becomes <code>TRUE</code>.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;function&quot; _boolsilent=&quot;I become TRUE&quot; _boolfalse=&quot;FALSE&quot; /&gt;</code></pre>\n\n<h3>_null</h3>\n\n<p>The <code>_null</code> prefix converts the value to <span class=\"caps\">NULL</span>, no matter what the supplied value is.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;function&quot; _nullvalue=&quot;Always NULL, no matter what&quot; /&gt;</code></pre>\n\n<h3>_int</h3>\n\n<p>The <code>_int</code> prefix converts the value to integer, ensuring that the value is a safe, valid integer. Numeric values will be rounded towards zero and non-numerical strings will be converted based on the initial portion of the string. If the attribute value starts with valid numeric data, this will be used as the value. Otherwise, the value becomes <code>0</code> (zero).</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;function&quot; _intid=&quot;I will be zero&quot; _intnumericval=&quot;247&quot; /&gt;</code></pre>\n\n<h3>_array</h3>\n\n<p>The <code>_array</code> prefix is used to generate and pass arrays. An attribute prefixed with an <code>_array</code> takes a <a href=\"http://json.org/\" rel=\"nofollow\"><span class=\"caps\">JSON</span></a> string (JavaScript Object Notation) and converts it to a <span class=\"caps\">PHP</span>&#8217;s array.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;implode&quot; _arrayValue=&quot;[1, 2, 3]&quot; /&gt;</code></pre>\n\n<h2>Security related features</h2>\n\n<p>Considering the plugin&#8217;s nature, it comes with few options to limit its access. Limiting extends to both what a rah_function tag can do and where the tag can be used. The plugin has a whitelisting option for enabling only certain functions, and it follows Textpattern&#8217;s <span class=\"caps\">PHP</span> evaluation rules and user privileges, the same rules that affect <a href=\"http://textpattern.net/wiki/index.php?title=php\" rel=\"nofollow\">php</a> tags.</p>\n\n<h3>Privileges in articles</h3>\n\n<p>User group privileges limit which users can use <code>&lt;txp:rah_function /&gt;</code> tags within articles. Only user-groups that have privileges granted an <code>article.php</code> level can use the tags in an article body, custom fields or excerpt. By default only the two highest groups, Publishers and Managing Editors (groups with ID 1 and 2) have access to <code>article.php</code> and will be able to publish articles with the plugin&#8217;s tags in them. For more information about user-privileges, please see:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.net/wiki/index.php?title=Modifying_User_Account_Roles_and_Privileges\" rel=\"nofollow\">Modifying User Account Roles and Privileges.</a></li>\n	<li><a href=\"http://textpattern.net/wiki/index.php?title=Users\" rel=\"nofollow\">Users</a></li>\n	<li><a href=\"http://textpattern.net/wiki/index.php?title=User_Roles_and_Permissions\" rel=\"nofollow\">User Roles and Permissions</a></li>\n</ul>\n\n<h3>Advanced <span class=\"caps\">PHP</span> preferences</h3>\n\n<p>Rah_function follows the two <span class=\"caps\">PHP</span> options that can be found from Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Advanced_Preferences\" rel=\"nofollow\">Advanced Preferences</a> panel, <strong>Allow <span class=\"caps\">PHP</span> in pages</strong> and <strong>Allow <span class=\"caps\">PHP</span> in articles</strong>.</p>\n\n<p>When <strong>Allow <span class=\"caps\">PHP</span> in pages</strong> option is disabled, a rah_function tag can not be used in a <a href=\"http://textpattern.net/wiki/index.php?title=Pages\" rel=\"nofollow\">page template</a> or a <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">form partial</a>. When <strong>Allow <span class=\"caps\">PHP</span> in articles</strong> option is disabled, rah_function tag can not be used in articles. The tags won&#8217;t be executed, no matter what permissions the article&#8217;s author has.</p>\n\n<h3>Function whitelisting</h3>\n\n<p>For added optional security, certain functions can be explicitly whitelisted. If the whitelisting option is defined, then only those whitelisted functions can be called with a rah_function tag.</p>\n\n<p>Whitelisting options can be set from Textpattern&#8217;s <code>config.php</code> (e.g. /textpattern/config.php) file. Setting a whitelist up doesn&#8217;t take more than adding a single line to the config. Rah_function expects a constant named <code>rah_function_whitelist</code>, populated with a comma-separated list of function names. Function names and class methods should be formatted in the same way as when used in a rah_function tag&#8217;s <code>call</code> attribute.</p>\n\n<pre><code>define(\'rah_function_whitelist\', \'gps, ps, htmlspecialchars, str_replace, Class::StaticMethod, Class-&gt;Method\');</code></pre>\n\n<p>Where <code>gps</code>, <code>ps</code>, <code>htmlspecialchars</code>, <code>str_replace</code>, <code>Class::StaticMethod</code> and <code>Class::Method</code> would be the allowed functions and class methods.</p>\n\n<p>This whitelisting option is completely optional, and it doesn&#8217;t need to be configured. It should only be used if you want to enable certain functions for added security.</p>\n\n<h2>Examples</h2>\n\n<h3>Replacing content using str_replace in a single tag mode</h3>\n\n<pre><code>&lt;txp:rah_function call=&quot;str_replace&quot; search=&quot;Hello&quot; replace=&quot;Hi&quot; subject=&quot;Hello world!&quot; /&gt;</code></pre>\n\n<p>Returns: <code>Hi world!</code></p>\n\n<h3>Replacing content using str_replace and containers</h3>\n\n<p>A contained statement is used as <a href=\"http://php.net/manual/en/function.str-replace.php\" rel=\"nofollow\">str_replace</a>&#8217;s <em>subject</em> parameter. Wrapped content is positioned to the correct location by using <code>thing=&quot;here&quot;</code> in the tag.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;str_replace&quot; search=&quot;Hello&quot; replace=&quot;Hi&quot; thing=&quot;here&quot;&gt;\n	Hello world!\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Returns: <code>Hi world!</code></p>\n\n<h3>Sanitizing and returning <span class=\"caps\">HTTP</span> <span class=\"caps\">GET</span> value</h3>\n\n<p>Returning <span class=\"caps\">GET</span>/POST values and sanitizing the output can be done with a single rah_function tag instance by using the plugin&#8217;s multi-function call feature. First we would use Textpattern&#8217;s <code>gps</code> function to get a specific value, e.g. <code>theme</code>, and then prepare it for the page template by converting <span class=\"caps\">HTML</span>&#8217;s special characters to entities with <a href=\"http://php.net/manual/en/function.htmlspecialchars.php\" rel=\"nofollow\">htmlspecialchars</a>.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;gps, htmlspecialchars&quot; name=&quot;theme&quot; /&gt;</code></pre>\n\n<p>The above would return <span class=\"caps\">HTTP</span> <span class=\"caps\">GET</span>/POST param&#8217;s, named <code>theme</code>, value. If the requested value is <code>?theme=I&lt;3TXP</code>, the above would return a safe string of <code>I&amp;lt;3TXP</code>.</p>\n\n<h3>Getting and checking site preferences</h3>\n\n<p>Textpattern&#8217;s <code>get_pref()</code> function can be used to return site&#8217;s preference settings. Following would return site&#8217;s production status.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;get_pref&quot; name=&quot;production_status&quot; /&gt;</code></pre>\n\n<p>The above can be used as a conditional by applying <code>_is</code> attribute:</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;get_pref&quot; name=&quot;production_status&quot; _is=&quot;debug&quot;&gt;\n	Site is in debugging mode.\n&lt;txp:else /&gt;\n	Either in Testing or Live.\n&lt;/txp:rah_function&gt;</code></pre>\n\n<h3>Removing, appending and prepending whitespace or other characters</h3>\n\n<p><span class=\"caps\">PHP</span> comes with a couple useful functions for removing whitespace and other characters from the beginning and end of a string: <a href=\"http://php.net/manual/en/function.trim.php\" rel=\"nofollow\">trim</a>, <a href=\"http://php.net/manual/en/function.ltrim.php\" rel=\"nofollow\">ltrim</a> and <a href=\"http://php.net/manual/en/function.rtrim.php\" rel=\"nofollow\">rtrim.</a> Trim removes characters from both ends, while ltrim only touches the beginning and rtrim() wants to be all rightful. All three can take up to two arguments. The first one is the string which will be trimmed, and the second, a list of characters that are stripped. If no characters are specified, white-space is stripped.</p>\n\n<h4>Stripping zeros from the beginning</h4>\n\n<p>Wrapped content is passed to ltrim, which is set to strip zeros (<code>0</code>) from the beginning of the string. The original value <code>000150</code> is converted to <code>150</code>.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;ltrim&quot; strip=&quot;0&quot;&gt;000150&lt;/txp:rah_function&gt;</code></pre>\n\n<h4>Stripping whitespace from the beginning and the end</h4>\n\n<p>When no extra arguments are given to trim, it will strip any whitespace from the beginning and the end.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;trim&quot;&gt;\n	Hello World!\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Returns <code>Hello World!</code> without the indentation or linefeed at the end.</p>\n\n<h3>Generating a valid JavaScript array</h3>\n\n<p>The following example generates a valid <a href=\"https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array\" rel=\"nofollow\">Javascript Array</a> from a comma-separated list of values. The code splits the values to a <span class=\"caps\">PHP</span> array using do_list(). Since the function returns array, rah_function encodes and returns as a valid <span class=\"caps\">JSON</span> presentation. Rah_function returns any array as a <span class=\"caps\">JSON</span>.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;do_list&quot;&gt;\n	value1, value2, value3\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>The above returns <code>[&quot;value1&quot;,&quot;value2&quot;,&quot;value3&quot;]</code>.</p>\n\n<h3>Generating a valid JavaScript string</h3>\n\n<p>Like in the previous example, encoding non-arrays is possible too. There are couple ways of doing it, if you want an valid and safe JavaScript string. One option is calling <code>escape_js()</code> and other is <code>json_encode()</code>. The first one strictly returns valid JavaScript while the latter takes <span class=\"caps\">JSON</span> specifications into account.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;escape_js&quot;&gt;\n	Soon to be valid JavaScript string.\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Or for <span class=\"caps\">JSON</span>:</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;json_encode&quot;&gt;\n	Soon to be valid for JSON.\n&lt;/txp:rah_function&gt;</code></pre>\n\n<h3>Returning time using safe_strftime()</h3>\n\n<pre><code>&lt;txp:rah_function call=&quot;safe_strftime&quot;&gt;\n	%Y\n&lt;/txp:rah_function&gt;</code></pre>\n\n<p>Returns: <code>2009</code>.</p>\n\n<h3>Fetching a single field from the database with fetch() function</h3>\n\n<pre><code>&lt;txp:rah_function call=&quot;fetch&quot; what=&quot;last_access&quot; from=&quot;txp_users&quot; where=&quot;user_id&quot; is=&quot;1&quot; /&gt;</code></pre>\n\n<p>Returns: Last access time for site admin with user ID of 1.</p>\n\n<h3>Counting number of articles</h3>\n\n<p>Counting is done with Textpattern&#8217;s <code>safe_count()</code> function.</p>\n\n<pre><code>&lt;txp:rah_function call=&quot;safe_count&quot; table=&quot;textpattern&quot; where=&quot;Status IN(4, 5)&quot; /&gt;</code></pre>\n\n<p>Returns: number of articles.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.7.1 &#8211; 2013/05/06</h3>\n\n<ul>\n	<li>Changed: Updated the Composer package to use <a href=\"https://github.com/gocom/textpattern-installer\" rel=\"nofollow\">textpattern/installer</a>.</li>\n</ul>\n\n<h3>Version 0.7.0 &#8211; 2013/04/25</h3>\n\n<ul>\n	<li>Added: <code>_constant</code> attribute prefix.</li>\n	<li>Added: <code>_assign</code> attribute.</li>\n	<li>Released as a <a href=\"https://packagist.org/packages/rah/rah_function\" rel=\"nofollow\">composer package</a>.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2012/07/19</h3>\n\n<ul>\n	<li>Updated: Help file (readme). Thanks you, <a href=\"http://www.inkscar.de/en/\" rel=\"nofollow\">Ralitza</a>.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2012/07/19</h3>\n\n<ul>\n	<li>Added: Ability to call class methods (<code>call=&quot;Class-&gt;Method&quot;</code> and <code>call=&quot;Class::StaticMethod&quot;</code>).</li>\n	<li>Added: Multiple functions can be called with a single tag instance (<code>call=&quot;func1, func2, func3&quot;</code>). Output is passed by reference from function to function. First function in the list is treated as the primary and given tag attributes only apply to it.</li>\n	<li>Added: Arrays are returned as <span class=\"caps\">JSON</span> string and can be passed from one tag instance to other. <span class=\"caps\">JSON</span> can be used as a value by prefixing used tag attribute name with <code>_array</code>.</li>\n	<li>Added: Converts returned booleans to uppercase strings, <code>TRUE</code> and <code>FALSE</code>. This makes it possible to identify integers from booleans (e.g. strpos).</li>\n	<li>Added: Function whitelisting option.</li>\n	<li>Improved: Prevent passing non-scalars to the template.</li>\n	<li>Improved: Moved away from sanitization and <code>eval()</code>. Now uses callbacks.</li>\n	<li>Improved: Show some error messages when needed.</li>\n	<li>Updated: Help file (readme). Thanks to <a href=\"https://github.com/aguatye\" rel=\"nofollow\">Tye</a> for help.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2011/12/16</h3>\n\n<ul>\n	<li>Improved: Do not use attributes real names in the function call, but use an temp array. Makes sure attributes get resolved as valid variables no matter what is passed by Textpattern&#8217;s parser to the plugin.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/07/08</h3>\n\n<ul>\n	<li>Fixed: Now an empty, nothing-at-all string can be used as the container-mode&#8217;s wrapped statement.</li>\n	<li>Added: Now makes sure that the called function is really defined before executing anything.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2009/11/28</h3>\n\n<ul>\n	<li>Added attribute: <code>thing</code>. Thanks you <a href=\"http://forum.textpattern.com/viewtopic.php?pid=220042#p220042\" rel=\"nofollow\">Ruud</a> for the suggestion.</li>\n</ul>\n\n<h3>Version 0.1.1 &#8211; 2009/11/21</h3>\n\n<ul>\n	<li>Added <code>has_privs()</code> and <code>allow_scripting</code> checks.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/11/21</h3>\n\n<ul>\n	<li>First release</li>\n</ul>', '\n\n/**\n * Rah_function plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_function\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_function($atts, $thing = null)\n	{\n		global $is_article_body, $thisarticle, $variable;\n		static $whitelist = null;\n\n		extract(lAtts(array(\n			\'call\'    => null,\n			\'_is\'     => null,\n			\'_assign\' => null,\n		), $atts, 0));\n\n		if ($whitelist === null)\n		{\n			$whitelist = defined(\'rah_function_whitelist\') ? \n				do_list(rah_function_whitelist) : array();\n		}\n\n		if (!$call)\n		{\n			trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'call\')));\n			return;\n		}\n\n		if ($is_article_body)\n		{\n			if (!get_pref(\'allow_article_php_scripting\'))\n			{\n				trigger_error(gTxt(\'php_code_disabled_article\'));\n				return;\n			}\n\n			if (!has_privs(\'article.php\', $thisarticle[\'authorid\']))\n			{\n				trigger_error(gTxt(\'php_code_forbidden_user\'));\n				return;\n			}\n		}\n\n		else if (!get_pref(\'allow_page_php_scripting\'))\n		{\n			trigger_error(gTxt(\'php_code_disabled_page\'));\n			return;\n		}\n\n		unset($atts[\'call\'], $atts[\'_is\'], $atts[\'_assign\']);\n\n		foreach ($atts as $name => $value)\n		{\n			if (strpos($name, \'_\') !== 0 && $name != \'thing\')\n			{\n				continue;\n			}\n\n			if ($thing !== null && substr($name, -5) == \'thing\' && $_is === null)\n			{\n				$value = $atts[$name] = parse($thing);\n				$thing = null;\n			}\n\n			$value = trim($value);\n\n			if (strpos($name, \'_bool\') === 0)\n			{\n				$atts[$name] = $value && $value != \'FALSE\';\n			}\n\n			else if (strpos($name, \'_int\') === 0)\n			{\n				$atts[$name] = (int) $value;\n			}\n\n			else if (strpos($name, \'_null\') === 0)\n			{\n				$atts[$name] = null;\n			}\n\n			else if (strpos($name, \'_array\') === 0)\n			{	\n				$atts[$name] = $value === \'\' ? array() : @json_decode($value, true);\n\n				if (!is_array($atts[$name]))\n				{\n					trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $name)));\n					return;\n				}\n			}\n\n			else if (strpos($name, \'_constant\') === 0)\n			{\n				if (!defined($value))\n				{\n					trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $name)));\n					return;\n				}\n\n				$atts[$name] = constant($value);\n			}\n		}\n\n		if ($thing !== null && $_is === null)\n		{\n			array_unshift($atts, parse($thing));\n		}\n\n		foreach (do_list($call) as $index => $name)\n		{	\n			$f = $name;\n\n			if (strpos($f, \'::\'))\n			{\n				$f = explode(\'::\', $f);\n			}\n			else if (strpos($f, \'->\'))\n			{\n				$f = explode(\'->\', $f);\n				\n				if (class_exists($f[0]))\n				{\n					$f[0] = new $f[0];\n				}\n			}\n\n			if (!is_callable($f) || ($whitelist && !in_array($name, $whitelist)))\n			{\n				trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'call\')));\n				return;\n			}\n\n			$atts = call_user_func_array($f, !$index ? $atts : array($atts));\n		}\n\n		if (!is_scalar($atts) && !is_array($atts))\n		{\n			trigger_error(gTxt(\'rah_function_illegal_type\', array(\'{type}\' => gettype($atts))));\n			$atts = \'\';\n		}\n		else if (is_bool($atts))\n		{\n			$atts = $atts ? \'TRUE\' : \'FALSE\';\n		}\n		else if (is_array($atts))\n		{\n			if ($_assign)\n			{\n				foreach (do_list($_assign) as $name)\n				{\n					$variable[$name] = (string) current($atts);\n					next($atts);\n				}\n\n				$_assign = null;\n			}\n\n			$atts = json_encode($atts);\n		}\n\n		if ($_assign)\n		{\n			$variable[$_assign] = $atts;\n		}\n\n		if ($_is !== null && $thing !== null)\n		{\n			return parse(EvalElse($thing, $atts === $_is));\n		}\n\n		return $atts;\n	}', '\n\n/**\n * Rah_function plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_function\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_function($atts, $thing = null)\n	{\n		global $is_article_body, $thisarticle, $variable;\n		static $whitelist = null;\n\n		extract(lAtts(array(\n			\'call\'    => null,\n			\'_is\'     => null,\n			\'_assign\' => null,\n		), $atts, 0));\n\n		if ($whitelist === null)\n		{\n			$whitelist = defined(\'rah_function_whitelist\') ? \n				do_list(rah_function_whitelist) : array();\n		}\n\n		if (!$call)\n		{\n			trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'call\')));\n			return;\n		}\n\n		if ($is_article_body)\n		{\n			if (!get_pref(\'allow_article_php_scripting\'))\n			{\n				trigger_error(gTxt(\'php_code_disabled_article\'));\n				return;\n			}\n\n			if (!has_privs(\'article.php\', $thisarticle[\'authorid\']))\n			{\n				trigger_error(gTxt(\'php_code_forbidden_user\'));\n				return;\n			}\n		}\n\n		else if (!get_pref(\'allow_page_php_scripting\'))\n		{\n			trigger_error(gTxt(\'php_code_disabled_page\'));\n			return;\n		}\n\n		unset($atts[\'call\'], $atts[\'_is\'], $atts[\'_assign\']);\n\n		foreach ($atts as $name => $value)\n		{\n			if (strpos($name, \'_\') !== 0 && $name != \'thing\')\n			{\n				continue;\n			}\n\n			if ($thing !== null && substr($name, -5) == \'thing\' && $_is === null)\n			{\n				$value = $atts[$name] = parse($thing);\n				$thing = null;\n			}\n\n			$value = trim($value);\n\n			if (strpos($name, \'_bool\') === 0)\n			{\n				$atts[$name] = $value && $value != \'FALSE\';\n			}\n\n			else if (strpos($name, \'_int\') === 0)\n			{\n				$atts[$name] = (int) $value;\n			}\n\n			else if (strpos($name, \'_null\') === 0)\n			{\n				$atts[$name] = null;\n			}\n\n			else if (strpos($name, \'_array\') === 0)\n			{	\n				$atts[$name] = $value === \'\' ? array() : @json_decode($value, true);\n\n				if (!is_array($atts[$name]))\n				{\n					trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $name)));\n					return;\n				}\n			}\n\n			else if (strpos($name, \'_constant\') === 0)\n			{\n				if (!defined($value))\n				{\n					trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $name)));\n					return;\n				}\n\n				$atts[$name] = constant($value);\n			}\n		}\n\n		if ($thing !== null && $_is === null)\n		{\n			array_unshift($atts, parse($thing));\n		}\n\n		foreach (do_list($call) as $index => $name)\n		{	\n			$f = $name;\n\n			if (strpos($f, \'::\'))\n			{\n				$f = explode(\'::\', $f);\n			}\n			else if (strpos($f, \'->\'))\n			{\n				$f = explode(\'->\', $f);\n				\n				if (class_exists($f[0]))\n				{\n					$f[0] = new $f[0];\n				}\n			}\n\n			if (!is_callable($f) || ($whitelist && !in_array($name, $whitelist)))\n			{\n				trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'call\')));\n				return;\n			}\n\n			$atts = call_user_func_array($f, !$index ? $atts : array($atts));\n		}\n\n		if (!is_scalar($atts) && !is_array($atts))\n		{\n			trigger_error(gTxt(\'rah_function_illegal_type\', array(\'{type}\' => gettype($atts))));\n			$atts = \'\';\n		}\n		else if (is_bool($atts))\n		{\n			$atts = $atts ? \'TRUE\' : \'FALSE\';\n		}\n		else if (is_array($atts))\n		{\n			if ($_assign)\n			{\n				foreach (do_list($_assign) as $name)\n				{\n					$variable[$name] = (string) current($atts);\n					next($atts);\n				}\n\n				$_assign = null;\n			}\n\n			$atts = json_encode($atts);\n		}\n\n		if ($_assign)\n		{\n			$variable[$_assign] = $atts;\n		}\n\n		if ($_is !== null && $thing !== null)\n		{\n			return parse(EvalElse($thing, $atts === $_is));\n		}\n\n		return $atts;\n	}', '07573948171f6b3e9e18ceceead11a31', '#@public\n#Default language, en-us, en-gb\nrah_function_illegal_type => Resulted illegal type. \"{type}\" in not a scalar or an array.', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_expanding', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.3', 'Expanding admin-side textareas', '<h1>rah_expanding</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_expanding\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_expanding\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=27646\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_expanding\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_expanding brings dynamically expanding textareas to <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span>’</a> admin-side interfaces. With the plugin installed, textarea elements will grow with a user&#8217;s input. While the user types, the field&#8217;s height will increase to fit the content.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_expanding&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.5.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer.</li>\n</ul>\n\n<h2>Supported web browsers</h2>\n\n<p>Rah_expanding is compatible with the following web browsers:</p>\n\n<ul>\n	<li>Firefox 12 and newer.</li>\n	<li>Internet Explorer 9 and newer.</li>\n	<li>Safari 5.1 and newer.</li>\n	<li>Chrome 14 and newer.</li>\n	<li>Opera 12 and newer.</li>\n</ul>\n\n<p>Browsers that outright miss required features will gracefully fall back to normal textareas with visible scrollbars.</p>\n\n<h2>Installing</h2>\n\n<p>Rah_expanding&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<p>Admin-side textareas are now armed with the plugin&#8217;s functionality and should expand with the content.</p>\n\n<h2>For developers</h2>\n\n<p>Rah_expanding gives some additional tools for developers to play with. The plugin allows admin-side themes and plugins to set rules for the dynamic expanding textareas. Themes have the ability to limit the field&#8217;s dimensions or disable the plugin on a field basis. All this is doable with some basic styling.</p>\n\n<h3><span class=\"caps\">CSS</span> and field sizes</h3>\n\n<p>A field&#8217;s starting height is decided by a visual size set with <span class=\"caps\">CSS</span>, or the actual height of the element if no <span class=\"caps\">CSS</span> height is specified. The starting size used in an empty state can be set with the <a href=\"https://developer.mozilla.org/en/CSS/height\" rel=\"nofollow\">height</a> <span class=\"caps\">CSS</span> property.</p>\n\n<p>A field&#8217;s maximum height that limits the field&#8217;s expanding, can be set with a <a href=\"https://developer.mozilla.org/en/CSS/max-height\" rel=\"nofollow\">max-height</a> <span class=\"caps\">CSS</span> property. If a textarea doesn&#8217;t have max-height, it expands all the way up to 99999 pixels.</p>\n\n<p>Consider the following <span class=\"caps\">CSS</span>:</p>\n\n<pre><code>textarea#abc_field\n{\n	height: 1em;\n	max-height: 5em;\n}</code></pre>\n\n<p>With the above, the textarea with the ID <code>abc_field</code> will not get any smaller than <code>1em</code> or bigger than <code>5em</code>.</p>\n\n<h3>Disabling for a field</h3>\n\n<p>There are two ways to disable dynamic expanding on a field basis. Any textarea element that is armed with a class <code>rah_expanding_disable</code> will not be affected by the plugin.</p>\n\n<pre><code>&lt;textarea class=&quot;rah_expanding_disable&quot; name=&quot;foo&quot; rows=&quot;10&quot;&gt;\n	I don\'t grow.\n&lt;/textarea&gt;</code></pre>\n\n<p>A field&#8217;s expanding can also be disabled by setting the element&#8217;s <span class=\"caps\">CSS</span> property <code>max-height</code> to a equal or a lower value than the <code>height</code>.</p>\n\n<h3>Attaching to a textarea</h3>\n\n<p>By default the plugin works for textareas present on initial page load. If the <span class=\"caps\">DOM</span> is modified after that and a new textarea is added, the the plugin needs to be attached to those element to take advantage of the functionality. Rah_expanding sports a familiar jQuery interface, and introduces a method named <code>rah_expanding</code>.</p>\n\n<pre><code>$(\'textarea\').rah_expanding();</code></pre>\n\n<p>The above would attach the resizing magic to any new textarea present on the page.</p>\n\n<h3>Triggering resize</h3>\n\n<p>A textarea&#8217;s resizing can be invoked manually incase the field&#8217;s contents are changed programmatically. These type of automated changes are not otherwise tracked without the plugin being specifically informed about the change. Rah_expanding introduces a new custom event named <code>rah_expanding_resize</code> which can be fired using jQuery&#8217;s <a href=\"http://api.jquery.com/trigger/\" rel=\"nofollow\">trigger</a> method.</p>\n\n<pre><code>$(\'textarea\').trigger(\'rah_expanding_resize\');</code></pre>\n\n<p>The above would update each textarea&#8217;s height.</p>\n\n<h2>Thanks to</h2>\n\n<p>Along the years rah_expanding has used and found help from few open source projects and resources.</p>\n\n<ul>\n	<li><a href=\"https://github.com/akaihola/jquery-autogrow\" rel=\"nofollow\">Auto Growing Textarea</a> by Chrys Bader.</li>\n	<li><a href=\"https://github.com/jackmoore/autosize\" rel=\"nofollow\">Autoresize</a> by Jack Moore.</li>\n</ul>\n\n<h2>Changelog</h2>\n\n<h3>Version 1.3 &#8211; 2012/11/22</h3>\n\n<ul>\n	<li>Fixed: Box-sizing handling compatibility on jQuery 1.8 and newer.</li>\n	<li>Fixed: Workaround for a height calculation bug in Firefox on Mac OS when the field&#8217;s initial height is set as zero pixels.</li>\n</ul>\n\n<h3>Version 1.2 &#8211; 2012/09/03</h3>\n\n<ul>\n	<li>Fixed: Text flowing workaround for Chrome. It assumably had its set of side-effects.</li>\n</ul>\n\n<h3>Version 1.1 &#8211; 2012/08/30</h3>\n\n<ul>\n	<li>Fixed: Text flowing workaround for Chrome. Tries to improve the text wrapping of the initial textarea contents.</li>\n	<li>Changed: Replaced jQuery.bind method with jQuery.on.</li>\n	<li>Now requires Textpattern v4.5.0 or newer.</li>\n</ul>\n\n<h3>Version 1.0 &#8211; 2012/08/08</h3>\n\n<ul>\n	<li>Changed: Now affects all admin-side textareas by default.</li>\n	<li>Removed: Dropped preferences panel and extra options. Slims the installation significantly.</li>\n	<li>Replaced Chrys Bader&#8217;s Auto Grown Textarea JavaScript solution with a fork of <a href=\"http://www.jacklmoore.com/\" rel=\"nofollow\">Jack Moore&#8217;s</a> <a href=\"https://github.com/jackmoore/autosize\" rel=\"nofollow\">Autoresize</a>.</li>\n	<li>Dropped support for IE6, IE7, IE8, Safari &lt;5.1, Chrome &lt;14, Firefox &lt;12 and Opera &lt;12.</li>\n	<li>Dropped PHP4 compatibility. Now requires <span class=\"caps\">PHP</span> v5.2 or newer.</li>\n	<li>Is now compatible with Textpattern v4.5.0.</li>\n	<li>Rebranded the project as rah_expanding.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2011/07/26</h3>\n\n<ul>\n	<li>Added: <span class=\"caps\">CSRF</span> (session riding) protection using Textpattern&#8217;s new core functions introduced in v4.4.1.</li>\n	<li>Fixed: only adds default definition rows to the database when plugin hasn&#8217;t been installed before, or when the old installed version was v0.4 or older.</li>\n	<li>Changed: set temporary version number after installer has been run. Prevents possibility of running the installer twice.</li>\n	<li>Now requires Textpattern version 4.4.1 or newer.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2011/06/05</h3>\n\n<ul>\n	<li>Added: now includes uninstaller that uses plugin-lifecycle callbacks, drops the plugin&#8217;s tables from the database when deleting the plugin.</li>\n	<li>Added: informs the user if updating database records failed.</li>\n	<li>Added: language string support. Now the interface can be translated to different languages using TextPacks.</li>\n	<li>Added: “Options” link to the Plugins pane.</li>\n	<li>Added: rah_ui class selectors to the elements, <code>&lt;tbody&gt;</code> and <code>&lt;thead&gt;</code> containers to the list table.</li>\n	<li>Changed: Improved JavaScript based multi-edit script.</li>\n	<li>Changed: During first install, set the database table&#8217;s default charset to <span class=\"caps\">UTF</span>-8.</li>\n	<li>Changed: Only accept <span class=\"caps\">POST</span>s when saving changes. Requests send as <span class=\"caps\">GET</span> are ignored.</li>\n	<li>Changed: shortened the admin-side step names, and renamed step <em>form</em> to <em>edit</em>.</li>\n	<li>Removed: replaced <code>rah_autogrowing_textarea_check()</code> function with new method of checking if the plugin is up-to-date. Now uses simple version number to confirm the status.</li>\n	<li>Removed: Some markup, left-over inline styles and pane heading.</li>\n	<li>Now requires Textpattern version 4.2.0 or newer for full feature support.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2010/11/11</h3>\n\n<ul>\n	<li>Added: now prompts users if required fields are left empty when creating new rules.</li>\n	<li>Interface improvements: removed unnecessary table-layouts, moved all <span class=\"caps\">CSS</span> lines to <code>&lt;head&gt;</code>.</li>\n	<li>Changed: plugin type to the newer <em>Admin only</em>.</li>\n	<li>Changed: tries to run the installer only if there are no preferences available.</li>\n	<li>Changed: now uses different method for building list of available events.</li>\n	<li>Changed: shows events/panels label instead of name in the listings.</li>\n	<li>Changed: preferences pane shows multiedit controls only when something is selected. As seen in <a href=\"http://rahforum.biz/plugins/rah_post_versions\" rel=\"nofollow\">rah_post_versions</a>.</li>\n	<li>Removed default values from the installer <span class=\"caps\">SQL</span>.</li>\n</ul>\n\n<h3>Version 0.3.3 &#8211; 2009/06/03</h3>\n\n<ul>\n	<li>Removed useless queries.</li>\n	<li>Improved callbacks.</li>\n</ul>\n\n<h3>Version 0.3.2 &#8211; 2009/02/22</h3>\n\n<ul>\n	<li>Fixed: Removed lost child skeleton function; Fixes error: <code>Fatal error: Call to undefined function rah_plugin_download_install()</code>.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2009/02/21</h3>\n\n<ul>\n	<li>Added: Admin interface under Extensions -tab.</li>\n	<li>Added: Able to define which textareas auto-grow.</li>\n	<li>Added: Max. height limit, line height and other optional style settings are now adjustable.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2008/07/03</h3>\n\n<ul>\n	<li>Changed <code>line-height</code> from EMs to pixels: fixes the small-to-smaller-by-line-to-line <code>line-height</code>-bug that occurs in some browsers that cause some problems with textareas.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/07/03</h3>\n\n<ul>\n	<li>First release.</li>\n</ul>', '\n\n/**\n * rah_expanding plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2008-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_expanding\n *\n * Copyright (C) 2012 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_expanding();\n\n/**\n * The plugin class.\n */\n\nclass rah_expanding\n{\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		register_callback(array($this, \'jquery\'), \'admin_side\', \'head_end\');\n		register_callback(array($this, \'initialize\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Initializes the JavaScript.\n	 */\n\n	public function initialize()\n	{\n		$js = <<<EOF\n			$(document).ready(function(){\n				$(\'textarea:not(.rah_expanding_disable)\').rah_expanding();\n			});\nEOF;\n\n		echo script_js($js);\n	}\n\n	/**\n 	 * The resizer JavaScript.\n 	 */\n\n	public function jquery()\n	{\n		$js = <<<EOF\n			/**\n			 * Forked from Jack Moore\'s Autosize project.\n			 *\n			 * Autosize 1.10 - jQuery plugin for textareas\n			 * by Jack Moore\n			 * <http://www.jacklmoore.com/autosize>\n			 *\n			 * (c) 2012 Jack Moore - jacklmoore.com\n			 * license: www.opensource.org/licenses/mit-license.php\n			 */\n\n			(function($) {\n\n				var test = $(\'<textarea/>\').attr(\'oninput\', \'return\').css(\'line-height\', \'99px\');\n\n				if (\n					$.isFunction(test.prop(\'oninput\')) === false ||\n					test.css(\'line-height\') !== \'99px\'\n				)\n				{\n					$.fn.rah_expanding = function ()\n					{\n						return this;\n					};\n					return;\n				}\n\n				$.fn.rah_expanding = function ()\n				{\n					var copy = \'<textarea tabindex=\"-1\" style=\"position:absolute; top:-9999px; left:-9999px; right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; min-width:0 !important; overflow:hidden\">\',\n\n					copyStyle = [\n						\'font-family\',\n						\'font-size\',\n						\'font-weight\',\n						\'font-style\',\n						\'font-variant\',\n						\'letter-spacing\',\n						\'text-transform\',\n						\'word-spacing\',\n						\'text-indent\',\n						\'line-height\',\n						\'tab-size\',\n						\'text-align\',\n						\'text-rendering\',\n						\'zoom\'\n					];\n\n					return this.each(function ()\n					{\n						var textarea = $(this);\n\n						if (textarea.data(\'rah_expanding_mirror\') || textarea.data(\'rah_expanding_is_mirror\'))\n						{\n							return;\n						}\n\n						var opt =\n						{\n							min : textarea.height(),\n							max : parseInt(textarea.css(\'max-height\'), 10),\n							offset : 0,\n							mirror : $(copy).data(\'rah_expanding_is_mirror\', true),\n							active : false\n						};\n\n						if (!opt.max || opt.max < 0 || opt.max > 99999)\n						{\n							opt.max = 99999;\n						}\n\n						if (opt.max <= opt.min)\n						{\n							return;\n						}\n\n						if (\n							textarea.css(\'box-sizing\') === \'border-box\' || \n							textarea.css(\'-moz-box-sizing\') === \'border-box\'\n						)\n						{\n							opt.offset = textarea.outerHeight() - textarea.height();\n						}\n\n						textarea.data(\'rah_expanding_mirror\', opt.mirror).css(\n						{\n							\'overflow\'   : \'hidden\',\n							\'overflow-x\' : \'hidden\',\n							\'overflow-y\' : \'hidden\',\n							\'word-wrap\'  : \'break-word\',\n							\'resize\'     : \'none\'\n						});\n\n						var methods =\n						{\n							resize : function ()\n							{\n								if (opt.active)\n								{\n									return;\n								}\n\n								opt.active = true;\n\n								var height = Math.max(opt.min, Math.min(opt.max, \n									opt.mirror\n									.val(textarea.val())\n									.css({\n										\'overflow-y\' : textarea.css(\'overflow-y\'),\n										\'width\' : textarea.width() + \'px\'\n									})\n									.scrollTop(0)\n									.scrollTop(99999)\n									.scrollTop()\n								));\n\n								textarea.css({\n									\'overflow-y\' : height < opt.max ? \'hidden\' : \'scroll\',\n									\'height\' : (height + opt.offset) + \'px\',\n									\'max-height\' : (height + opt.offset) + \'px\',\n									\'min-height\' : (height + opt.offset) + \'px\'\n								});\n\n								setTimeout(function () {\n									opt.active = false;\n								}, 1);\n							},\n							copyStyles : function ()\n							{\n								$.each(copyStyle, function (key, value)\n								{\n									var rule = textarea.css(value);\n									opt.mirror.css(value, rule);\n									textarea.css(value, rule);\n								});\n\n								$.each([\'rows\', \'cols\'], function (key, value)\n								{\n									opt.mirror.attr(value, textarea.attr(value));\n								});\n							}\n						};\n\n						methods.copyStyles();\n						$(\'body\').append(opt.mirror);\n						textarea.on(\'input keyup blur focus resize rah_expanding_resize\', methods.resize);\n						$(window).on(\'orientationchange resize\', methods.resize);\n\n						var val = textarea.val();\n						textarea.val(\'\');\n						textarea.val(val);\n\n						methods.resize();\n					});\n				};\n			}(jQuery));\nEOF;\n\n		echo script_js($js);\n	}\n}\n\n', '\n\n/**\n * rah_expanding plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2008-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_expanding\n *\n * Copyright (C) 2012 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_expanding();\n\n/**\n * The plugin class.\n */\n\nclass rah_expanding\n{\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		register_callback(array($this, \'jquery\'), \'admin_side\', \'head_end\');\n		register_callback(array($this, \'initialize\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Initializes the JavaScript.\n	 */\n\n	public function initialize()\n	{\n		$js = <<<EOF\n			$(document).ready(function(){\n				$(\'textarea:not(.rah_expanding_disable)\').rah_expanding();\n			});\nEOF;\n\n		echo script_js($js);\n	}\n\n	/**\n 	 * The resizer JavaScript.\n 	 */\n\n	public function jquery()\n	{\n		$js = <<<EOF\n			/**\n			 * Forked from Jack Moore\'s Autosize project.\n			 *\n			 * Autosize 1.10 - jQuery plugin for textareas\n			 * by Jack Moore\n			 * <http://www.jacklmoore.com/autosize>\n			 *\n			 * (c) 2012 Jack Moore - jacklmoore.com\n			 * license: www.opensource.org/licenses/mit-license.php\n			 */\n\n			(function($) {\n\n				var test = $(\'<textarea/>\').attr(\'oninput\', \'return\').css(\'line-height\', \'99px\');\n\n				if (\n					$.isFunction(test.prop(\'oninput\')) === false ||\n					test.css(\'line-height\') !== \'99px\'\n				)\n				{\n					$.fn.rah_expanding = function ()\n					{\n						return this;\n					};\n					return;\n				}\n\n				$.fn.rah_expanding = function ()\n				{\n					var copy = \'<textarea tabindex=\"-1\" style=\"position:absolute; top:-9999px; left:-9999px; right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; min-width:0 !important; overflow:hidden\">\',\n\n					copyStyle = [\n						\'font-family\',\n						\'font-size\',\n						\'font-weight\',\n						\'font-style\',\n						\'font-variant\',\n						\'letter-spacing\',\n						\'text-transform\',\n						\'word-spacing\',\n						\'text-indent\',\n						\'line-height\',\n						\'tab-size\',\n						\'text-align\',\n						\'text-rendering\',\n						\'zoom\'\n					];\n\n					return this.each(function ()\n					{\n						var textarea = $(this);\n\n						if (textarea.data(\'rah_expanding_mirror\') || textarea.data(\'rah_expanding_is_mirror\'))\n						{\n							return;\n						}\n\n						var opt =\n						{\n							min : textarea.height(),\n							max : parseInt(textarea.css(\'max-height\'), 10),\n							offset : 0,\n							mirror : $(copy).data(\'rah_expanding_is_mirror\', true),\n							active : false\n						};\n\n						if (!opt.max || opt.max < 0 || opt.max > 99999)\n						{\n							opt.max = 99999;\n						}\n\n						if (opt.max <= opt.min)\n						{\n							return;\n						}\n\n						if (\n							textarea.css(\'box-sizing\') === \'border-box\' || \n							textarea.css(\'-moz-box-sizing\') === \'border-box\'\n						)\n						{\n							opt.offset = textarea.outerHeight() - textarea.height();\n						}\n\n						textarea.data(\'rah_expanding_mirror\', opt.mirror).css(\n						{\n							\'overflow\'   : \'hidden\',\n							\'overflow-x\' : \'hidden\',\n							\'overflow-y\' : \'hidden\',\n							\'word-wrap\'  : \'break-word\',\n							\'resize\'     : \'none\'\n						});\n\n						var methods =\n						{\n							resize : function ()\n							{\n								if (opt.active)\n								{\n									return;\n								}\n\n								opt.active = true;\n\n								var height = Math.max(opt.min, Math.min(opt.max, \n									opt.mirror\n									.val(textarea.val())\n									.css({\n										\'overflow-y\' : textarea.css(\'overflow-y\'),\n										\'width\' : textarea.width() + \'px\'\n									})\n									.scrollTop(0)\n									.scrollTop(99999)\n									.scrollTop()\n								));\n\n								textarea.css({\n									\'overflow-y\' : height < opt.max ? \'hidden\' : \'scroll\',\n									\'height\' : (height + opt.offset) + \'px\',\n									\'max-height\' : (height + opt.offset) + \'px\',\n									\'min-height\' : (height + opt.offset) + \'px\'\n								});\n\n								setTimeout(function () {\n									opt.active = false;\n								}, 1);\n							},\n							copyStyles : function ()\n							{\n								$.each(copyStyle, function (key, value)\n								{\n									var rule = textarea.css(value);\n									opt.mirror.css(value, rule);\n									textarea.css(value, rule);\n								});\n\n								$.each([\'rows\', \'cols\'], function (key, value)\n								{\n									opt.mirror.attr(value, textarea.attr(value));\n								});\n							}\n						};\n\n						methods.copyStyles();\n						$(\'body\').append(opt.mirror);\n						textarea.on(\'input keyup blur focus resize rah_expanding_resize\', methods.resize);\n						$(window).on(\'orientationchange resize\', methods.resize);\n\n						var val = textarea.val();\n						textarea.val(\'\');\n						textarea.val(val);\n\n						methods.resize();\n					});\n				};\n			}(jQuery));\nEOF;\n\n		echo script_js($js);\n	}\n}\n\n', '226c185bb1413f03030c5ee83cc2f0e3', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_post_versions', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.9', 'Revision control', '	<h1>rah_post_versions</h1>\n\n	<p>Store and revive old versions of articles, categories, sections, forms and pages. Rah_post_versions embeds revision control to <a href=\"http://www.textpattern.com\">Textpattern&#8217;s</a> admin interface. The plugin monitors changes made thru the web-based admin interface and stores the versions to repository.</p>\n\n	<ul>\n		<li>Version: 0.9</li>\n		<li>Updated: 2011/06/18 7:00 AM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#basic-concept\">Basic concept</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation\">Installation</a></li>\n		<li><a href=\"#interface\">Interface</a></li>\n		<li><a href=\"#limitations\">Limitations</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Tracks changes made via Textpattern&#8217;s interface.</li>\n		<li>By default the plugin tracks edits and saves of articles, pages, forms, preferences, categories, sections, styles, discussion and links. The plugin&#8217;s tracking can be extended to other areas from the plugin&#8217;s preferences.</li>\n		<li>Ability to revert edits to a previous revision.</li>\n	</ul>\n\n	<h3 id=\"basic-concept\">Basic concept</h3>\n\n	<p>The rah_post_versions tracks the changes made via Textpattern&#8217;s interface. Instead it tracking the database, it tracks the data sent by your browser and received by the server. After it has collected the appropriate data and filtered it to be valid, it starts identifying process. After it has the exact info what the change is and where it should go, it stores the information to the revision control, threaded under appropriate item. </p>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Rah_post_versions&#8217; minimum requirements:</p>\n\n	<ul>\n		<li>Textpattern 4.0.7+</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li>Textpattern 4.4.1+</li>\n		<li>Optional:\n	<ul>\n		<li>jQuery (bundled with <span class=\"caps\">TXP</span>)</li>\n		<li>JavaScript supporting client browser</li>\n	</ul></li>\n	</ul>\n\n	<h3 id=\"installation\">Installation</h3>\n\n	<p>The general behavior stands:</p>\n\n	<ol>\n		<li>Go to your Textpattern installation&#8217;s admin panel.</li>\n		<li>Navigate to <em>Admin / Plugins</em>.</li>\n		<li>Download and open the plugin code.</li>\n		<li>Copy the plugin code to the install plugin box.</li>\n		<li>Click install and go thru the automated installation process.</li>\n		<li>After installation find the plugin from the plugin list and click Activate.</li>\n		<li>Now the plugin is installed and ready to be used. The plugin&#8217;s interface can be now found from <em>Extensions/Post Versions</em>.</li>\n	</ol>\n\n	<h3 id=\"interface\">Interface</h3>\n\n	<p>After a successful installation you can find rah_post_versions&#8217; interface under <em>Extensions/Post Versions</em>. The interface is divided to two main areas; <em>Subversion repository browser</em>, that is the <em>main</em> tab, and <em>Preferences</em>.</p>\n\n	<h4>The Main area, repository browser</h4>\n\n	<p>When you open the Main area you will see the default view; a list of items. The list consists the items that have been modified or saved, and revisioned. When you click one of these items, the item&#8217;s changeset will expand, and you will see the list of individual changes made to that specific item. The changes can be viewed too.</p>\n\n	<h4>Preferences panel</h4>\n\n	<p>Preferences panel lets the user change some of the common settings. These include: exclude form fields from tracking, exclude changes made by specific authors, set which pages, or events, to track, send email notification about changes, and set identification code. When you open the Preferences panel, you will see that the <em>identification code</em> is small <span class=\"caps\">PHP</span> snippet. It will generate the required identification for a posted change. Without proper ID, a change can not be threaded to correct set of changes.</p>\n\n	<h3 id=\"limitations\">Limitations</h3>\n\n	<p>There are some limitations, because the way the plugin works is what it is. Most obvious is that it will only track the form submits. If some interface doesn&#8217;t use form submits, or works outside your browser (desktop client or similar), it won&#8217;t track it. Neither the plugin is informed about deletations, and renaming forms, or pages, will start an new set of changes under a new name.</p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.9</strong></p>\n\n	<ul>\n		<li>Fixed: Doesn&#8217;t spit out extra content to the top of the page if JavaScript is disabled.</li>\n		<li>Fixed: Error in condition that chose if JavaScript should be added to the page or not.</li>\n		<li>Added: Now informs the user if removing items from the database failed.</li>\n		<li>Added: Uninstaller that is ran when plugin is deleted. Drops plugin specific tables.</li>\n		<li>Changed: New database tables get <span class=\"caps\">UTF</span>-8 as their charset by default instead of using server default.</li>\n		<li>Changed: Make sure selection isn&#8217;t empty.</li>\n		<li>Changed: Improved JavaScript based multi-edit script.</li>\n		<li>Now compatible with Textpattern 4.4.1 and newer; supports <span class=\"caps\">CSRF</span> protection added in 4.4.1, from now on doesn&#8217;t store tokens and uses tokens when reverting to older version.</li>\n	</ul>\n\n	<p><strong>Version 0.8</strong></p>\n\n	<ul>\n		<li>Added tool-tips to the pagination&#8217;s jump links (<em>Go to the first page</em> and <em>Go to the last page</em>).</li>\n		<li>Removed username from showing up in the mails.</li>\n		<li>Unified conditional statements; now all use <code>||</code> and <code>&amp;&amp;</code> operators instead of synonym mashmixmess.</li>\n	</ul>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Fix/change: Diff tool shows/compares array data as blocks. Array&#8217;s are imploded and values are separated with line breaks.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Fixed: instances of missing <span class=\"caps\">HTML</span> escaping (<span class=\"caps\">POST</span> keys).</li>\n		<li>Fixed: now the plugin checks if the revisioned data contains an array (<code>&lt;input name=&quot;foo[bar]&quot; /&gt;</code> etc) and shows it correctly. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=230541#p230541\">Thank you for reporting, TJ</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Removed twice presented conditional.</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added diff tool. The feature is found from the list view&#8217;s multiedit: check two revisions and select <em>&#8220;Diff&#8221;</em> from the drop-down. The diff feature uses <a href=\"http://www.paulbutler.org\">Paul Butler&#8217;s</a> <a href=\"http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/\">Simple Diff Algorithm</a>.</li>\n		<li>Added <em>Are you sure?</em> checks to the multiedit.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Fixed: no longer unsets ID and Title from the forms. These were leftovers from the development period. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=229943#p229943\">Thank you for reporting, Jan</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Updated original installer to add more monitored pages. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=229852#p229852\">Thank you for re-posting, Dale</a>. Current users can add to the field following code: <code>article:edit, article:create, article:publish, article:save, page:page_create, page:page_edit, page:page_save, form:form_create, form:form_edit, form:form_save, prefs:prefs_save, prefs:advanced_prefs_save, category:cat_article_create, category:cat_image_create, category:cat_file_create, category:cat_link_create, category:cat_article_save, category:cat_image_save, category:cat_file_save, category:cat_link_save, section:section_create, section:section_save, link:link_post, link:link_save, discuss:discuss_save, image:image_save, file:file_save, css:css_save</code></li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>Initial release.</li>\n	</ul>', '	##################\n	#\n	#	rah_post_versions-plugin for Textpattern\n	#	version 0.9\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (c) 2011 Jukka Svahn\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_post_versions_install();\n		add_privs(\'rah_post_versions\',\'1,2\');\n		add_privs(\'plugin_prefs.rah_post_versions\',\'1,2\');\n		register_tab(\'extensions\',\'rah_post_versions\',\'Post versions\');\n		register_callback(\'rah_post_versions_page\',\'rah_post_versions\');\n		register_callback(\'rah_post_versions_css\',\'admin_side\',\'head_end\');\n		register_callback(\'rah_post_versions_messager\',\'admin_side\',\'pagetop_end\');\n		register_callback(\'rah_post_versions_install\',\'plugin_lifecycle.rah_post_versions\');\n		register_callback(\'rah_post_versions_options\',\'plugin_prefs.rah_post_versions\');\n		rah_post_versions_register();\n	}\n\n/**\n	Registers backend events\n*/\n\n	function rah_post_versions_register() {\n		\n		global $txp_user;\n		\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$authors = explode(\',\',$authors);\n		\n		if(in_array($txp_user,$authors))\n			return;\n\n		foreach(explode(\',\',$events) as $event) {\n			$item = explode(\':\',$event);\n			if(isset($item[1]))\n				register_callback(\'rah_post_versions\',trim($item[0]),trim($item[1]),0);\n		}\n	}\n\n/**\n	Creates the tables required for the plugin to operate.\n*/\n\n	function rah_post_versions_install($event=\'\',$step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.\n				safe_pfx(\'rah_post_versions\').\', \'.\n				safe_pfx(\'rah_post_versions_sets\').\', \'.\n				safe_pfx(\'rah_post_versions_prefs\')\n			);\n			\n			return;\n		}\n		\n		/*\n			Stores the changesets\n			\n			* id: The row primary key\n			* event: The event the post was recorded form.\n			* step: Same, but the step.\n			* title: The identifier (Article title, form name) that is used to identify the changeset record.\n			* grid: The ident used to group the commit to sets (form name, article id etc).\n			* modified: Time of the last change to the changeset.\n			* changes: The number of changes the set holds.\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions_sets\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`event` VARCHAR(255) NOT NULL,\n				`step` VARCHAR(255) NOT NULL,\n				`grid` VARCHAR(255) NOT NULL,\n				`title` VARCHAR(255) NOT NULL,\n				`modified` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\n				`changes` INT(12) NOT NULL default 0,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Stores the changes (versions) in changesets\n			\n			* id: The row primary key\n			* event: The event the post was recorded form.\n			* step: Same, but the step.\n			* title: The identifier (Article title, form name) that is used to identify the changeset record.\n			* grid: The ident used to group the commit to sets (form name, article id etc).\n			* author: The user posted the post.\n			* posted: Time of the post.\n			* data: All the post data\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`event` VARCHAR(255) NOT NULL,\n				`step` VARCHAR(255) NOT NULL,\n				`title` VARCHAR(255) NOT NULL,\n				`grid` VARCHAR(255) NOT NULL,\n				`setid` INT(11) NOT NULL,\n				`author` VARCHAR(255) NOT NULL,\n				`posted` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\n				`data` LONGTEXT NOT NULL,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Creates preferences table\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions_prefs\').\" (\n				`name` VARCHAR(255) NOT NULL,\n				`value` LONGTEXT NOT NULL,\n				PRIMARY KEY(`name`)\n			) CHARSET=utf8\"\n		);\n		\n		/*\n			Add default settings\n		*/\n		\n		rah_post_versions_add_pref(\n			array(\n				\'exclude\' => \'\',\n				\'statuses\' => \'\',\n				\'authors\' => \'\',\n				\'email\' => \'No\',\n				\'email_additional\' => \'\',\n				\'posting_now\' => \'No\',\n				\'events\' => \'article:edit, article:create, article:publish, article:save, page:page_create, page:page_edit, page:page_save, form:form_create, form:form_edit, form:form_save, prefs:prefs_save, prefs:advanced_prefs_save, category:cat_article_create, category:cat_image_create, category:cat_file_create, category:cat_link_create, category:cat_article_save, category:cat_image_save, category:cat_file_save, category:cat_link_save, section:section_create, section:section_save, link:link_post, link:link_save, discuss:discuss_save, image:image_save, file:file_save, css:css_save\',\n				\'hidden\' => \'event,step,save\',\n				\'ident\' => \n					\'switch($event) {\'.n.\n					\'	case \"article\":\'.n.\n					\'		$grid = rah_post_versions_id();\'.n.\n					\'		$title = gps(\"Title\");\'.n.\n					\'	break;\'.n.\n					\'	case \"category\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = gps(\"title\");\'.n.\n					\'	break;\'.n.\n					\'	case \"link\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = gps(\"linkname\");\'.n.\n					\'	break;\'.n.\n					\'	case \"prefs\":\'.n.\n					\'		$grid = $title = ($step == \"advanced_prefs_save\") ? \"Advanced prefs\" : \"Preferences\";\'.n.\n					\'	break;\'.n.\n					\'	case \"discuss\":\'.n.\n					\'		$grid = gps(\"discussid\");\'.n.\n					\'		$title = gps(\"name\");\'.n.\n					\'	break;\'.n.\n					\'	case \"section\":\'.n.\n					\'		$grid = gps(\"name\");\'.n.\n					\'		$title = (gps(\"title\")) ? gps(\"title\") : gps(\"name\");\'.n.\n					\'	break;\'.n.\n					\'	case \"image\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = (gps(\"name\")) ? gps(\"name\") : $grid;\'.n.\n					\'	break;\'.n.\n					\'	case \"file\":\'.n.\n					\'		$grid = gps(\"id\");\'.n.\n					\'		$title = gps(\"filename\");\'.n.\n					\'	break;\'.n.\n					\'	default:\'.n.\n					\'		$grid = $title = gps(\"name\");\'.n.\n					\'}\',\n				\'version\' => \'0.1\'\n			)\n		);\n	}\n\n/**\n	Deliver panel\n*/\n\n	function rah_post_versions_page() {\n		require_privs(\'rah_post_versions\');\n		global $step;\n		if(in_array($step,array(\n			\'rah_post_versions_group\',\n			\'rah_post_versions_group_delete\',\n			\'rah_post_versions_diff\',\n			\'rah_post_versions_view\',\n			\'rah_post_versions_delete\',\n			\'rah_post_versions_prefs\',\n			\'rah_post_versions_prefs_save\',\n		))) $step();\n		else rah_post_versions_list();\n	}\n\n/**\n	Adds the panel\'s CSS to the head segment.\n*/\n\n	function rah_post_versions_css() {\n		global $event;\n		\n		if($event != \'rah_post_versions\')\n			return;\n			\n		$msg = gTxt(\'are_you_sure\');\n		\n		echo <<<EOF\n			<script type=\"text/javascript\">\n				<!--\n				\n				/*\n					Multi-edit function, auto-hiden dropdown\n				*/\n				\n				function rah_post_versions_stepper() {\n					if($(\'#rah_post_versions_step\').length < 1)\n						return;\n					\n					$(\'#rah_post_versions_step .smallerbox\').hide();\n\n					if($(\'#rah_post_versions_container input[type=checkbox]:checked\').val() == null)\n						$(\'#rah_post_versions_step\').hide();\n\n					/*\n						Reset the value\n					*/\n\n					$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n\n					/*\n						Every time something is checked, check if\n						the dropdown should be shown\n					*/\n\n					$(\'#rah_post_versions_container input[type=checkbox], #rah_post_versions_container td\').click(\n						function(){\n							$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n							if($(\'table#list input[type=checkbox]:checked\').val() != null)	\n								$(\'#rah_post_versions_step\').slideDown();\n							else\n								$(\'#rah_post_versions_step\').slideUp();\n						}\n					);\n\n					/*\n						If value is changed, send the form\n					*/\n\n					$(\'#rah_post_versions_container select[name=\"step\"]\').change(\n						function(){\n							$(this).parent(\'p\').parent(\'form\').submit();\n						}\n					);\n\n					/*\n						Verify if the sent is allowed\n					*/\n\n					$(\'select[name=\"step\"]\').parent(\'p\').parent(\'form\').submit(\n						function() {\n							if(!verify(\'{$msg}\')) {\n								$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n								return false;\n							}\n						}\n					);\n				}\n				\n				/*\n					Filtering link\n				*/\n				\n				function rah_post_versions_filter() {\n					if($(\'#rah_post_versions_filter\').length < 1)\n						return;\n					\n					$(\'#rah_post_versions_nav\').append(\' <span class=\"rah_ui_sep\">#</span> <a id=\"rah_post_versions_filter_link\">Filter results</a>\');\n					$(\'#rah_post_versions_filter\').hide();\n					$(\'#rah_post_versions_filter_link\').click(\n						function(){\n							$(\'#rah_post_versions_filter\').slideToggle();\n							return false;\n						}\n					);\n				}\n			\n				$(document).ready(function(){\n					rah_post_versions_filter();\n					rah_post_versions_stepper();\n				});\n				-->\n			</script>\n			<style type=\"text/css\">\n				#rah_post_versions_container {\n					width: 950px;\n					margin: 0 auto;\n				}\n				#rah_post_versions_container table input {\n					padding: 0;\n					margin: 0;\n				}\n				#rah_post_versions_container table td {\n					padding: 4px 3px;\n				}\n				#rah_post_versions_container #rah_post_versions_step {\n					text-align: right;\n					padding: 10px 0 0 0;\n				}\n				#rah_post_versions_actions {\n					border-top: 1px solid #ccc;\n					border-bottom: 1px solid #ccc;\n					padding: 5px 0;\n				}\n				#rah_post_versions_container table {\n					width:100%;\n				}\n				#rah_post_versions_container input.edit {\n					width: 940px;\n					padding: 1px;\n				}\n				#rah_post_versions_container textarea {\n					width: 940px;\n					padding: 1px;\n				}\n				#rah_post_versions_container select {\n					width: 640px;\n				}\n				#rah_post_versions_container #rah_post_versions_step select {\n					width: 120px;\n				}\n				#rah_post_versions_filter {\n					text-align: center;\n					border-bottom: 1px solid #ccc;\n					border-top: 1px solid #ccc;\n					padding: 5px 20px;\n					background: #f5f5f5;\n					margin: 0 0 5px 0;\n				}\n				#rah_post_versions_filter select {\n					width: 200px;\n					margin: 0 2px;\n					padding: 0;\n				}\n				#rah_post_versions_pages {\n					text-align: center;\n					padding: 10px 10px 0 10px;\n				}\n				#rah_post_versions_pages .rah_post_version_active {\n					color: #000;\n					text-decoration: underline;\n				}\n				#rah_post_versions_container .rah_post_versions_diff {\n					overflow: auto;\n					white-space: pre;\n					font: 1.1em monospace;\n					line-height: 1.2em;\n					background: #f5f5f5;\n					border: 1px solid #ccc;\n					padding: 10px;\n					margin: 0 0 10px 0;\n				}\n				#rah_post_versions_container .rah_post_versions_diff .rah_post_versions_add {\n					background: #a7d726;\n					border: 1px solid #829b3e;\n					display: block;\n					margin-bottom: -1.1em;\n				}\n				#rah_post_versions_container .rah_post_versions_diff .rah_post_versions_del {\n					background: #c54e4e;\n					border: 1px solid #8b3e3e;\n					display: block;\n					margin-bottom: -1.1em;\n				}\n			</style>\n\nEOF;\n	}\n\n/**\n	Builds URI\n*/\n\n	function rah_post_versions_uri($defaults=array(),$segments=array(\'filter_event\',\'filter_limit\',\'filter_order\',\'filter_page\',\'group_step\',\'group_limit\',\'group_order\')) {\n		foreach($segments as $segment)\n			if(gps($segment))\n				$get[$segment] = $segment.\'=\'.htmlspecialchars(gps($segment));\n		\n		foreach($defaults as $key => $val)\n			if(!empty($val)) $get[$key] = $key.\'=\'.htmlspecialchars($val);\n		\n		return \n			\'?\'.implode(\'&amp;\',$get);\n	}\n\n/**\n	Builds inputs\n*/\n\n	function rah_post_versions_input($defaults=array(),$segments=array(\'filter_event\',\'filter_limit\',\'filter_order\',\'filter_page\',\'group_step\',\'group_limit\',\'group_order\')) {\n		foreach($segments as $segment)\n			if(gps($segment))\n				$get[$segment] = \'			<input type=\"hidden\" name=\"\'.$segment.\'\" value=\"\'.htmlspecialchars(gps($segment)).\'\" />\';\n		\n		foreach($defaults as $key => $val)\n			if(!empty($val))\n				$get[$key] = \'			<input type=\"hidden\" name=\"\'.$key.\'\" value=\"\'.htmlspecialchars($val).\'\" />\';\n		\n		return \n			implode(n,$get);\n	}\n\n/**\n	Builds pagination\n*/\n\n	function rah_post_versions_pagination($total=0,$limit=\'filter_limit\',$page_att=\'filter_page\') {\n		\n		if($total == 0)\n			return;\n		\n		global $event, $step;\n		\n		$limit = gps($limit);\n		$page = gps($page_att);\n		$group_id = gps(\'group_id\');\n		\n		if(trim($limit,\'0123456789\') || empty($limit) || $limit <= 0)\n			$limit = 20;\n		\n		$pages = ceil(($total/$limit));\n		\n		if(trim($page,\'0123456789\') || empty($page) || !is_numeric($page) || $page <= 0 || $page > $pages)\n			$page = 1;\n		\n		$end = (($page+3) > $pages) ? $pages : ($page+3);\n		$start = (($end-3) < 1) ? 1 : ($end-3);\n		\n		if($start > 1) \n			$out[] = \n				\'<a title=\"Go to the first page\" href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => $step,\n						$page_att => 1,\n						\'group_id\' => $group_id\n					)\n				).\n				\'\">&#171;</a>\';\n\n		\n		for($i=$start;$i<=$end;$i++)\n			$out[] = \n				\'<a\'.(($i == $page) ? \' class=\"rah_post_version_active\"\' : \'\').\' href=\"\'.\n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => $step,\n							$page_att => $i,\n							\'group_id\' => $group_id\n						)\n					).\n				\'\">\'.$i.\'</a>\';\n		\n		if($end < $pages)\n			$out[] = \n				\'<a title=\"Go to the last page\" href=\"\'.\n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => $step,\n							$page_att => $pages,\n							\'group_id\' => $group_id\n						)\n					).\n				\'\">&#187;</a>\';\n		\n		return \n			\'			<div id=\"rah_post_versions_pages\">\'.n.\n			implode(\' \',$out).n.\n			\'			</div>\';\n	}\n\n/**\n	The main listing\n*/\n\n	function rah_post_versions_list($message=\'\') {\n	\n		global $event;\n		\n		$events = rah_post_versions_eventer();\n		\n		extract(doSlash(gpsa(array(\n			\'filter_event\',\n			\'filter_title\',\n			\'filter_page\',\n			\'filter_limit\',\n			\'filter_order\'\n		))));\n		\n		$q[] = \'1=1\';\n		\n		if($filter_event)\n			$q[] = \"event=\'\".doSlash($filter_event).\"\'\";\n		\n		if(!$filter_limit || !is_numeric($filter_limit) || trim($filter_limit,\'0123456789\'))\n			$filter_limit = 20;\n		\n		if(is_numeric($filter_page) && $filter_page > 0 && !trim($filter_limit,\'0123456789\'))\n			$offset = ($filter_page*$filter_limit)-$filter_limit;\n		else \n			$offset = 0;\n		\n		if(!in_array($filter_order,array(\n			\'id\',\n			\'event\',\n			\'title\',\n			\'changes\'\n		)))\n			$filter_order = \'modified\';\n		\n		$rs = \n			safe_rows(\n				\'id,event,step,grid,title,modified,changes\',\n				\'rah_post_versions_sets\',\n				implode(\' and \',$q).\" order by $filter_order desc LIMIT $offset, $filter_limit\"\n			);\n		\n		$total = \n			safe_count(\n				\'rah_post_versions_sets\',\n				implode(\' and \',$q)\n			);\n		\n		$out[] = \n			\n			\'		<form id=\"rah_post_versions_filter\" method=\"get\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event\n				),\n				array(\n					\'group_step\',\n					\'group_limit\',\n					\'group_order\',\n					\'group_page\'\n				)\n			).n.\n			\n			\'			<select name=\"filter_event\">\'.n.\n			\'				<option value=\"\">Filter by the event...</option>\'.n;\n		\n		foreach($events as $key => $value) \n			$out[] = \'			<option value=\"\'.htmlspecialchars($key).\'\">\'.htmlspecialchars($value).\'</option>\'.n;\n		\n		\n		$out[] = \n			\'			</select>\'.n.\n			\'			<select name=\"filter_limit\">\'.n.\n			\'				<option value=\"\">Items per page...</option>\'.n.\n			\'				<option value=\"10\">10</option>\'.n.\n			\'				<option value=\"25\">25</option>\'.n.\n			\'				<option value=\"50\">50</option>\'.n.\n			\'				<option value=\"100\">100</option>\'.n.\n			\'			</select>\'.n.\n			\'			<select name=\"filter_order\">\'.n.\n			\'				<option value=\"\">Sort by...</option>\'.n.\n			\'				<option value=\"id\">ID</option>\'.n.\n			\'				<option value=\"event\">Event</option>\'.n.\n			\'				<option value=\"title\">Title</option>\'.n.\n			\'				<option value=\"modified\">Modified</option>\'.n.\n			\'				<option value=\"changes\">Changes</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" value=\"Filter\" class=\"smallerbox\" />\'.n.\n			\'		</form>\'.n.\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event\n				)\n			).n.\n			\n			\'			<table id=\"list\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'				<tr>\'.n.\n			\'					<th>#ID</th>\'.n.\n			\'					<th>Event</th>\'.n.\n			\'					<th>Title</th>\'.n.\n			\'					<th>Modified</th>\'.n.\n			\'					<th>Changes</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n;\n		\n		if($rs){\n		\n			foreach($rs as $a) {\n				\n				if(!$a[\'title\'])\n					$a[\'title\'] = gTxt(\'untitled\');\n				\n				if(isset($events[$a[\'event\']]))\n					$a[\'event\'] = $events[$a[\'event\']];\n				\n				$url = \n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => \'rah_post_versions_group\',\n							\'group_id\' => $a[\'id\']\n						)\n					);\n				\n				$out[] =  \n					\n					\'				<tr>\'.n.\n					\'					<td>\'.$a[\'id\'].\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'event\']).\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.htmlspecialchars($a[\'title\']).\'</a></td>\'.n.\n					\'					<td>\'.safe_strftime(\'%b %d %Y %H:%M:%S\',strtotime($a[\'modified\'])).\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.$a[\'changes\'].\'</a></td>\'.n.\n					\'					<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				</tr>\'.n;\n			}\n			\n		} else \n			$out[] = \n				\'				<tr>\'.n.\n				\'					<td colspan=\"6\">No article version data found.</td>\'.n.\n				\'				</tr>\'.n;\n		\n		$out[] = \n			\'			</table>\'.n.\n			rah_post_versions_pagination($total).n.\n			\n			\'			<p id=\"rah_post_versions_step\" class=\"rah_ui_step\">\'.n.\n			\'				<select name=\"step\">\'.n.\n			\'					<option value=\"\">With selected...</option>\'.n.\n			\'					<option value=\"rah_post_versions_group_delete\">Delete changeset</option>\'.n.\n			\'				</select>\'.n.\n			\'				<input type=\"submit\" class=\"smallerbox\" value=\"\'.gTxt(\'go\').\'\" />\'.n.\n			\'			</p>\'.n.\n			\'		</form>\'.n.\n			\'	</div>\'.n;\n		\n		rah_post_versions_header(\n			$out,\n			$message,\n			\'Revision control\'\n		);\n		\n	}\n\n/**\n	Remove whole groups\n*/\n\n	function rah_post_versions_group_delete() {\n		\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected) || empty($selected)) {\n			rah_post_versions_list(\'Nothing selected.\');\n			return;\n		}\n		\n		foreach($selected as $id)\n			$in[] = \"\'\".doSlash($id).\"\'\";\n		\n		$in = implode(\',\',$in);\n		\n		if(\n			safe_delete(\n				\'rah_post_versions\',\n				\'setid in(\'.$in.\')\'\n			) == false ||\n			safe_delete(\n				\'rah_post_versions_sets\',\n				\'id in(\'.$in.\')\'\n			) == false\n		) {\n			rah_post_versions_list(\'Database error occured when removing items. Please try again.\');\n			return;	\n		}\n		\n		rah_post_versions_list(\'Selection removed.\');\n	}\n\n/**\n	View list of items in a group\n*/\n\n	function rah_post_versions_group($message=\'\') {\n		\n		global $event,$step;\n		\n		$id = \n			gps(\'group_id\');\n		\n		extract(doSlash(gpsa(array(\n			\'group_step\',\n			\'group_page\',\n			\'group_limit\',\n			\'group_order\'\n		))));\n		\n		$q[] = \"setid=\'\".doSlash($id).\"\'\";\n		\n		if($group_step)\n			$q[] = \"step=\'\".doSlash($group_step).\"\'\";\n		\n		if(!$group_limit || !is_numeric($group_limit) || trim($group_limit,\'0123456789\'))\n			$group_limit = 20;\n		\n		if(is_numeric($group_page) && $group_page > 0 && !trim($group_page,\'0123456789\'))\n			$offset = ($group_page*$group_limit)-$group_limit;\n		else \n			$offset = 0;\n		\n		if(!in_array($group_order,array(\n			\'title\',\n			\'posted\',\n			\'step\',\n			\'author\',\n		)))\n			$group_order = \'id\';\n		\n		$rs = \n			safe_rows(\n				\'id,title,posted,author,step\',\n				\'rah_post_versions\',\n				implode(\' and \',$q).\" order by $group_order desc LIMIT $offset, $group_limit\"\n			);\n		\n		$total = \n			safe_count(\n				\'rah_post_versions\',\n				implode(\' and \',$q)\n			);\n		\n		$out[] = \n			\n			\'		<form id=\"rah_post_versions_filter\" method=\"get\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'group_id\' => $id\n				),\n				array(\n					\'filter_step\',\n					\'filter_limit\',\n					\'filter_order\',\n					\'filter_page\'\n				)\n			).n.\n			\n			\'			<select name=\"group_step\">\'.n.\n			\'				<option value=\"\">Filter by the step...</option>\'.n.\n			rah_post_versions_steper().n.\n			\'			</select>\'.n.\n			\'			<select name=\"group_limit\">\'.n.\n			\'				<option value=\"\">Items per page...</option>\'.n.\n			\'				<option value=\"10\">10</option>\'.n.\n			\'				<option value=\"25\">25</option>\'.n.\n			\'				<option value=\"50\">50</option>\'.n.\n			\'				<option value=\"100\">100</option>\'.n.\n			\'			</select>\'.n.\n			\'			<select name=\"group_order\">\'.n.\n			\'				<option value=\"\">Sort by...</option>\'.n.\n			\'				<option value=\"id\">ID</option>\'.n.\n			\'				<option value=\"title\">Title</option>\'.n.\n			\'				<option value=\"posted\">Posted</option>\'.n.\n			\'				<option value=\"step\">Step</option>\'.n.\n			\'				<option value=\"author\">Author</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" value=\"Filter\" class=\"smallerbox\" />\'.n.\n			\'		</form>\'.n.\n			\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'group_id\' => $id\n				)\n			).n.\n			\n			\'			<table id=\"list\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'				<tr>\'.n.\n			\'					<th>#ID</th>\'.n.\n			\'					<th>Title</th>\'.n.\n			\'					<th>Posted</th>\'.n.\n			\'					<th>Step</th>\'.n.\n			\'					<th>Author</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n;\n		\n		if($rs){\n		\n			foreach($rs as $a) {\n			\n				$url = \n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => \'rah_post_versions_view\',\n							\'group_id\' => $id,\n							\'id\' => $a[\'id\']\n						)\n					);\n			\n				$out[] =  \n					\n					\'				<tr>\'.n.\n					\'					<td>\'.$a[\'id\'].\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.htmlspecialchars($a[\'title\']).\'</a></td>\'.n.\n					\'					<td>\'.safe_strftime(\'%b %d %Y %H:%M:%S\',strtotime($a[\'posted\'])).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'step\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'author\']).\'</td>\'.n.\n					\'					<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				</tr>\'.n;\n			}\n			\n		} else \n			$out[] = \n				\'				<tr>\'.n.\n				\'					<td colspan=\"6\">No version data found.</td>\'.n.\n				\'				</tr>\'.n;\n		\n		$out[] = \n			\'			</table>\'.n.\n			\n			rah_post_versions_pagination($total,\'group_limit\',\'group_page\').n.\n			\n			\'			<p id=\"rah_post_versions_step\" class=\"rah_ui_step\">\'.n.\n			\'				<select name=\"step\">\'.n.\n			\'					<option value=\"\">With selected...</option>\'.n.\n			\'					<option value=\"rah_post_versions_diff\">Diff</option>\'.n.\n			\'					<option value=\"rah_post_versions_delete\">Delete</option>\'.n.\n			\'				</select>\'.n.\n			\'				<input type=\"submit\" class=\"smallerbox\" value=\"Go\" />\'.n.\n			\'			</p>\'.n.\n			\'		</form>\'.n.\n			\'	</div>\'.n;\n		\n		rah_post_versions_header(\n			$out,$message,\'Viewing set #\'.htmlspecialchars($id)\n		);\n		\n	}\n\n/**\n	Deletes individual item from a group\n*/\n\n	function rah_post_versions_delete() {\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected) || empty($selected)) {\n			rah_post_versions_group(\'Nothing selected.\');\n			return;\n		}\n		\n		foreach($selected as $id)\n			$in[] = \"\'\".doSlash($id).\"\'\";\n		\n		$in = implode(\',\',$in);\n		\n		if(\n			safe_delete(\n				\'rah_post_versions\',\n				\'id in(\'.$in.\')\'\n			) == false\n		) {\n			rah_post_versions_group(\'Database error occured when removing items. Please try again.\');\n			return;	\n		}\n		\n		rah_post_versions_group(\'Selection removed.\');\n	}\n\n/**\n	View differences\n*/\n\n	function rah_post_versions_diff($message=\'\') {\n		\n		global $event;\n		\n		$selection = ps(\'selected\');\n		\n		if(!is_array($selection) || count($selection) != 2) {\n			rah_post_versions_group(\'Invalid selection. Select two items to compare.\');\n			return;\n		}\n		\n		sort($selection);\n		\n		$results = rah_post_versions_diff_data($selection[0],$selection[1]);\n		\n		if(empty($results)) {\n			rah_post_versions_group(\'Selected items not found.\');\n			return;\n		}\n		\n		rah_post_versions_header(\n			$results,$message,\'Diff of <a href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_view\',\n						\'group_id\' => gps(\'group_id\'),\n						\'id\' => $selection[0]\n					)\n				).\'\">r\'.$selection[0].\'</a> and <a href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_view\',\n						\'group_id\' => gps(\'group_id\'),\n						\'id\' => $selection[1]\n					)\n				).\'\">r\'.$selection[1].\'</a>\'\n		);\n	}\n\n/**\n	Build comparison of two data array()s\n*/\n\n	function rah_post_versions_diff_data($old,$new) {\n		\n		$old = \n			safe_field(\n				\'data\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($old).\"\' and setid=\'\".doSlash(gps(\'group_id\')).\"\'\"\n			);\n		\n		$new = \n			safe_field(\n				\'data\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($new).\"\' and setid=\'\".doSlash(gps(\'group_id\')).\"\'\"\n			);\n		\n		if(!$new || !$old)\n			return;\n		\n		$new = unserialize(base64_decode($new));\n		$old = unserialize(base64_decode($old));\n		\n		unset(\n			$old[\'rah_article_versions_repost_is\'],\n			$old[\'rah_article_versions_repost_uri\'],\n			$old[\'rah_article_versions_repost_id\'],\n			$old[\'_txp_token\'],\n			$new[\'rah_article_versions_repost_id\'],\n			$new[\'rah_article_versions_repost_is\'],\n			$new[\'rah_article_versions_repost_uri\'],\n			$new[\'_txp_token\']\n		);\n		\n		foreach($new as $key => $val) {\n			\n			if(!isset($old[$key]))\n				$old[$key] = \'\';\n			\n			/*\n				If the field is exact same do not show it at all\n			*/\n			\n			if($old[$key] == $val) {\n				unset($old[$key]);\n				continue;\n			}\n			\n			/*\n				Check if array. If true make the array a single block\n			*/\n			\n			if(is_array($old[$key]))\n				$old[$key] = implode(n,$old[$key]);\n			if(is_array($val))\n				$val = implode(n,$val);\n			\n			$out[] = \n				\'<p><strong>\'.htmlspecialchars($key).\':</strong></p>\'.n.\n				\'<div class=\"rah_post_versions_diff\">\'.\n					rah_post_versions_diff_lib(\n						$old[$key],\n						$val\n					).n.\n				\'</div>\';\n			\n			unset($old[$key]);\n		}\n		\n		/*\n			List removed fields (removed custom-fields / updated TXP) as removed\n		*/\n		\n		if($old)\n			foreach($old as $key => $val) \n				$out[] = \n					\'<p><strong><del>\'.htmlspecialchars($key).\'</del>:</strong></p>\'.n.\n					\'<div class=\"rah_post_versions_diff\"><span class=\"rah_post_versions_del\">\'.htmlspecialchars($val).\'</span></div>\'.n\n				;\n		\n		if(!isset($out))\n			$out[] = \'<p>The revisions are exact match. No changes to show.</p>\';\n		\n		return implode(\'\',$out);\n	}\n\n/**\n	Starts splitting the lines, and then implodes the results\n*/\n\n	function rah_post_versions_diff_lib($old, $new){\n		\n		foreach(\n			rah_post_versions_diff_compare(\n				rah_post_versions_explode($old),\n				rah_post_versions_explode($new)\n			) as $key => $line\n		){\n			if(is_array($line)) {\n				if(!empty($line[\'d\'])) \n					$out[] = \'<span class=\"rah_post_versions_del\">\'.htmlspecialchars(implode(n,$line[\'d\'])).\'</span>\';\n				if(!empty($line[\'i\']))\n					$out[] = \'<span class=\"rah_post_versions_add\">\'.htmlspecialchars(implode(n,$line[\'i\'])).\'</span>\';\n			} else\n				$out[] = htmlspecialchars($line);\n		}\n		\n		return implode(n,$out);\n	}\n\n/**\n	Compares lines we just split\n*/\n\n	function rah_post_versions_diff_compare($old, $new, $maxlen = 0){\n\n		/*\n			rah_post_version_diff_compare() function\'s contents are based on:\n			\n			Paul\'s Simple Diff Algorithm v 0.1\n			(C) Paul Butler 2007 <http://www.paulbutler.org/>\n			Licensed under GNU GPL compatible zlib/libpng license.\n			\n			***\n\n				This software is provided \'as-is\', without any express or implied\n				warranty. In no event will the authors be held liable for any damages\n				arising from the use of this software.\n\n				Permission is granted to anyone to use this software for any purpose,\n				including commercial applications, and to alter it and redistribute it\n				freely, subject to the following restrictions:\n\n					1. The origin of this software must not be misrepresented; you must not\n					claim that you wrote the original software. If you use this software\n					in a product, an acknowledgment in the product documentation would be\n					appreciated but is not required.\n\n					2. Altered source versions must be plainly marked as such, and must not be\n					misrepresented as being the original software.\n\n					3. This notice may not be removed or altered from any source\n					distribution.\n			\n			***\n		*/\n\n		foreach($old as $oindex => $ovalue){\n			$nkeys = array_keys($new, $ovalue);\n			foreach($nkeys as $nindex){\n				$matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ? $matrix[$oindex - 1][$nindex - 1] + 1 : 1;\n				if($matrix[$oindex][$nindex] > $maxlen){\n					$maxlen = $matrix[$oindex][$nindex];\n					$omax = $oindex + 1 - $maxlen;\n					$nmax = $nindex + 1 - $maxlen;\n				}\n			}\n		}\n		\n		if($maxlen == 0)\n			return array(array(\'d\'=>$old, \'i\'=>$new));\n		\n		return \n			array_merge(\n				rah_post_versions_diff_compare(\n					array_slice($old, 0, $omax),\n					array_slice($new, 0, $nmax)\n				),\n				array_slice($new, $nmax, $maxlen),\n				rah_post_versions_diff_compare(\n					array_slice($old, $omax + $maxlen),\n					array_slice($new, $nmax + $maxlen)\n				)\n			)\n		;\n	}\n\n/**\n	Clean line breaks and do explode.\n*/\n\n	function rah_post_versions_explode($string=\'\') {\n		$string = str_replace(array(\"\\r\\n\",\"\\r\"), n, $string);\n		return explode(n,$string);\n	}\n\n/**\n	Shows input fields\n*/\n\n	function rah_post_versions_field($value=\'\',$key=\'\',$i=1,$hidden=false) {\n		if($hidden == true) \n			return \n				\'						<input type=\"hidden\" name=\"\'.htmlspecialchars($key).\'\" value=\"\'.htmlspecialchars($value).\'\" />\';\n		\n		return\n			\'				<p>\'.n.\n			\'					<label for=\"rah_post_versions_label_\'.$i.\'\">\'.htmlspecialchars($key).\'</label><br />\'.n.\n			((substr_count($value,n) == 0) ?\n				\'						<input id=\"rah_post_versions_label_\'.$i.\'\" type=\"text\" name=\"\'.htmlspecialchars($key).\'\" class=\"edit\" value=\"\'.htmlspecialchars($value).\'\" />\'\n			:\n				\'						<textarea id=\"rah_post_versions_label_\'.$i.\'\" name=\"\'.htmlspecialchars($key).\'\" class=\"code\" cols=\"100\" rows=\"6\">\'.htmlspecialchars($value).\'</textarea>\'\n			).n.\n			\'				</p>\'.n\n		;\n	}\n\n/**\n	Views individual changeset\n*/\n\n	function rah_post_versions_view($message=\'\') {\n		\n		global $event;\n		\n		$id = \n			gps(\'id\');\n			\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$hidden = explode(\',\',$hidden);\n		\n		$rs = \n			safe_row(\n				\'*\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($id).\"\'\"\n			);\n			\n		if($rs) {\n			$data = base64_decode($rs[\'data\']);\n			$data = unserialize($data);\n			\n			$back = \n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_group\',\n						\'group_id\' => gps(\'group_id\')\n					)\n				);\n			\n			$out[] = \n				\'		<form method=\"post\" action=\"index.php\" onsubmit=\"return verify(\\\'Are you sure? There is no going back.\\\')\">\'.n.\n				\'			<p id=\"rah_post_versions_actions\">&#171; <a href=\"\'.$back.\'\">Go back</a> <strong>Ident:</strong> \'.$rs[\'title\'].\' <strong>Saved:</strong> \'.$rs[\'posted\'].\' by \'.$rs[\'author\'].\'. <strong>Event:</strong> <a href=\"?event=\'.$rs[\'event\'].\'\">\'.$rs[\'event\'].\'</a>, \'.$rs[\'step\'].\'</p>\'.n;\n			\n			$i = 0;\n			\n			foreach($data as $key => $value) {\n\n				$i++;\n				\n				if(is_array($value)) {\n					foreach($value as $needle => $selection) \n						$out[] = \n							rah_post_versions_field(\n								$selection,\n								$key.\'[\'.$needle.\']\',\n								$i,\n								in_array($key,$hidden)\n							);\n					continue;\n				}\n				\n				$out[] = \n					rah_post_versions_field(\n						$value,\n						$key,\n						$i,\n						in_array($key,$hidden)\n					);\n				\n			}\n\n			$out[] = \n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_is\" value=\"1\" />\'.n.\n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_uri\" value=\"\'.htmlspecialchars($back).\'\" />\'.n.\n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_id\" value=\"\'.htmlspecialchars($id).\'\" />\'.n.\n				\n				(function_exists(\'form_token\') ? \n					\'			<input type=\"hidden\" name=\"_txp_token\" value=\"\'.form_token().\'\" />\'.n : \'\'\n				).\n				\n				\'			<p id=\"rah_post_versions_warning\"><strong>Notice:</strong> Only click the <em>Re-post this</em> button when you are certain what you are about to do. Clicking the button will redo the exact posting, and depending of the information state stored, it might either overwrite, partially replace or dublicate something.</p>\'.n.\n				\'			<p><input type=\"submit\" class=\"publish\" value=\"Re-post this\" /></p>\'.n.\n				\'		</form>\';\n		} else\n			$out[] = \'<p>Nothing to show</p>\';\n		\n		$slogan = \'Viewing change #\'.htmlspecialchars($id);\n			\n		rah_post_versions_header(\n			$out,$message,$slogan\n		);\n		\n	}\n\n/**\n	Shows message after repost\n*/\n\n	function rah_post_versions_messager() {\n		\n		if(!ps(\'rah_article_versions_repost_is\'))\n			return;\n		\n		extract(gpsa(array(\n			\'rah_article_versions_repost_uri\',\n			\'rah_article_versions_repost_id\'\n		)));\n		\n		echo \n			n.\n			\'	<p id=\"rah_post_versions_messager\" style=\"text-align:center;padding: 5px 20px;\">\'.\n			\'		Reposted the form #\'.htmlspecialchars($rah_article_versions_repost_id).\'. \'.n.\n			\'		<a href=\"\'.$rah_article_versions_repost_uri.\'\">Go back to the version listing</a>.\'.n.\n			\'	</p>\'.n;\n	}\n\n/**\n	Fetch preferences to usable format from the database\n*/\n\n	function rah_post_versions_do_prefs() {\n		\n		$rs = \n			safe_rows(\n				\'name,value\',\n				\'rah_post_versions_prefs\',\n				\'1=1\'\n			);\n			\n		if(!$rs)\n			return;\n		\n		foreach($rs as $a) \n			$out[$a[\'name\']] = $a[\'value\'];\n		\n		return $out;\n	}\n\n/**\n	Adds preferences array to database\n*/\n\n	function rah_post_versions_add_pref($array) {\n		foreach($array as $name => $value) {\n			if(\n				safe_count(\n					\'rah_post_versions_prefs\',\n					\"name=\'\".doSlash($name).\"\'\"\n				) == 0\n			)\n				safe_insert(\n					\'rah_post_versions_prefs\',\n					\"name=\'\".doSlash($name).\"\',\n					value=\'\".doSlash($value).\"\'\"\n				);\n		}\n	}\n\n/**\n	Preferences panel\n*/\n\n	function rah_post_versions_prefs($message=\'\') {\n\n		global $event;\n		\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$out = \n			\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => \'rah_post_versions_prefs_save\'\n				)\n			).n.\n			\n			\'			<p>\'.n.\n			\'				<label>Excluded fields. Comma separated list if multiple.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"exclude\" value=\"\'.htmlspecialchars($exclude).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Set fields hidden. Comma separated list if multiple. These fields are stored, but hid.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"hidden\" value=\"\'.htmlspecialchars($hidden).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Monitor following pages. Comma separated list if multiple. The format of item is <code>event:step</code>.</label><br />\'.n.\n			\'				<textarea name=\"events\" rows=\"4\" cols=\"30\">\'.htmlspecialchars($events).\'</textarea>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Excluded authors. Comma separated list if multiple. Uses login names.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"authors\" value=\"\'.htmlspecialchars($authors).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Send versions to author\\\'s email?</label><br />\'.n.\n			\'				<select name=\"email\">\'.n.\n			\'					<option value=\"\"\'.(($email == \'No\') ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'					<option value=\"\"\'.(($email == \'Yes\') ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'				</select>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Additional email addresses. Sends changes to this address. Comma separated list if multiple.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"email_additional\" value=\"\'.htmlspecialchars($email_additional).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Ident code. Code that generates the Ident for a change.</label><br />\'.n.\n			\'				<textarea class=\"code\" cols=\"30\" rows=\"18\" name=\"ident\">\'.htmlspecialchars($ident).\'</textarea>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p><input type=\"submit\" class=\"publish\" value=\"Save settings\" /></p>\'.n.\n			\'		</form>\'.n;\n			\n		\n		rah_post_versions_header(\n			$out,\n			$message,\n			\'Preferences\'\n		);\n		\n	}\n\n/**\n	Saves preferences\n*/\n\n	function rah_post_versions_prefs_save() {\n		\n		extract(\n			gpsa(\n				array(\n					\'exclude\',\n					\'events\',\n					\'email_additional\',\n					\'authors\',\n					\'hidden\',\n					\'ident\',\n					\'email\'\n				)\n			)\n		);\n		\n		$authors = rah_post_versions_list_trim($authors);\n		$exclude = rah_post_versions_list_trim($exclude);\n		$hidden = rah_post_versions_list_trim($hidden);\n		$email_additional = rah_post_versions_list_trim($email_additional);\n		\n		rah_post_versions_pref_update(\n			array(\n				\'exclude\' => $exclude,\n				\'events\' => $events,\n				\'email_additional\' => $email_additional,\n				\'email\' => $email,\n				\'authors\' => $authors,\n				\'hidden\' => $hidden,\n				\'ident\' => $ident\n			)\n		);\n\n		rah_post_versions_prefs(\n			\'Preferences saved.\'\n		);\n		\n	}\n	\n/**\n	Goes thru an array and pushes it to the preferences table.\n*/\n\n	function rah_post_versions_pref_update($array) {\n		foreach($array as $name => $value)\n			safe_update(\n				\'rah_post_versions_prefs\',\n				\"value=\'\".doSlash($value).\"\'\",\n				\"name=\'\".doSlash($name).\"\'\"\n			);\n	}\n\n/**\n	Trims a $delim separated list of items.\n*/\n\n	function rah_post_versions_list_trim($array=\'\',$delim=\',\') {\n		\n		if(!$array)\n			return;\n		\n		if(!is_array($array))\n			$array = explode($delim,$array);\n		\n		foreach($array as $item) {\n			$item = trim($item);\n			if(!empty($item))\n				$out[] = $item;\n		}\n		\n		if(!isset($out))\n			return;\n		\n		return implode($delim,$out);\n		\n	}\n\n/**\n	Returns the ID\n*/\n\n	function rah_post_versions_id($key=\'ID\') {\n		$id = gps($key);\n		if(!$id && isset($GLOBALS[\'ID\']))\n			$id = $GLOBALS[\'ID\'];\n		return $id;\n	}\n\n/**\n	Saves versions from POSTs\n*/\n\n	function rah_post_versions() {\n		\n		global $txp_user,$event,$step,$sitename;\n		\n		/*\n			Check if the step has post\n		*/\n		\n		$post = (isset($_POST) && is_array($_POST)) ? $_POST : \'\';\n		\n		if(empty($post))\n			return;\n			\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		/*\n			Evaluates ident\n		*/\n		\n		eval(\n			$ident\n		);\n		\n		/*\n			Check if the ident returned the data we want\n		*/\n		\n		if(isset($kill) || !isset($grid) || !isset($title))\n			return;\n		\n		if(empty($title) || empty($grid))\n			return;\n		\n		/*\n			Excludes fields\n		*/\n		\n		$exclude = explode(\',\',$exclude);\n		\n		foreach($post as $key => $value) {\n			\n			if(in_array($key,array(\n				\'rah_article_versions_repost_is\',\n				\'rah_article_versions_repost_uri\',\n				\'rah_article_versions_repost_id\',\n				\'_txp_token\'\n			)))\n				continue;\n			\n			if(in_array($key,$exclude))\n				continue;\n			\n			$out[$key] = ps($key);\n		}\n		\n		if(!isset($out))\n			return;\n		\n		$data = doSlash(base64_encode(serialize($out)));\n		\n		if(\n			safe_count(\n				\'rah_post_versions_sets\',\n				\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n			) == 0\n		) {\n			safe_insert(\n				\'rah_post_versions_sets\',\n				\"modified=now(),\n				changes=1,\n				title=\'\".doSlash($title).\"\',\n				event=\'\".doSlash($event).\"\',\n				step=\'\".doSlash($step).\"\',\n				grid=\'\".doSlash($grid).\"\'\"\n			);\n			$setid = mysql_insert_id();\n		}\n		else {\n			safe_update(\n				\'rah_post_versions_sets\',\n				\'modified=now(),changes=changes+1\',\n				\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n			);\n			$setid = \n				safe_field(\n					\'id\',\n					\'rah_post_versions_sets\',\n					\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n				);\n		}\n		\n		safe_insert(\n			\'rah_post_versions\',\n			\"grid=\'\".doSlash($grid).\"\',\n			setid=\'\".doSlash($setid).\"\',\n			title=\'\".doSlash($title).\"\',\n			posted=now(),\n			author=\'\".doSlash($txp_user).\"\',\n			event=\'\".doSlash($event).\"\',\n			step=\'\".doSlash($step).\"\',\n			data=\'$data\'\"\n		);\n		\n		/*\n			Emailing\n		*/\n		\n		if($email != \'Yes\' && empty($email_additional))\n			return;\n		\n		if(!empty($email_additional))\n			$mails = explode(\',\',$email_additional);\n		\n		if($email == \'Yes\') {\n			$mail = trim(fetch(\'email\',\'txp_users\',\'name\',$txp_user));\n			if(!empty($mail))\n				$mails[] = $mail;\n		}\n		\n		if(!isset($mails))\n			return;\n		\n		$message = \n			gTxt(\'greeting\').n.n.\n			\'View the changes at:\'.n.\n			hu.\'textpattern/index.php?event=rah_post_versions\'\n		;\n		\n		foreach($mails as $mail)\n			txpMail(\n				$mail,\n				\"[$sitename] Change in the content\",\n				$message\n			);\n	}\n\n/**\n	Echoes the panels and header\n*/\n\n	function rah_post_versions_header($content=\'\',$message=\'\',$slogan=\'Version index\') {\n		\n		global $event;\n		pagetop(\'rah_post_versions\',$message);\n		\n		if(is_array($content))\n			$content = implode(\'\',$content);\n		\n		echo \n			n.\n			\'	<div id=\"rah_post_versions_container\" class=\"rah_ui_container\">\'.n.\n			\'		<h1><strong>rah_post_versions</strong> | \'.$slogan.\'</h1>\'.n.\n			\n			\'		<p id=\"rah_post_versions_nav\" class=\"rah_ui_nav\">\'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"\'.rah_post_versions_uri(array(\'event\' => $event)).\'\">Main</a> \'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"\'.rah_post_versions_uri(array(\'event\' => $event,\'step\' => \'rah_post_versions_prefs\')).\'\">Preferences</a> \'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"?event=plugin&amp;step=plugin_help&amp;name=rah_post_versions\">Documentation</a>\'.\n					\n			\'</p>\'.n.\n			$content.n.\n			\'	</div>\'.n;\n	}\n\n/**\n	Lists event names\n*/\n\n	function rah_post_versions_eventer() {\n		global $plugin_areas;\n		\n		$out = array();\n		\n		$areas[\'content\'] = array(\n			gTxt(\'tab_organise\') => \'category\',\n			gTxt(\'tab_write\') => \'article\',\n			gTxt(\'tab_list\') =>  \'list\',\n			gTxt(\'tab_image\') => \'image\',\n			gTxt(\'tab_file\') => \'file\',					 \n			gTxt(\'tab_link\') => \'link\',\n			gTxt(\'tab_comments\') => \'discuss\'\n		);\n		$areas[\'presentation\'] = array(\n			gTxt(\'tab_sections\') => \'section\',\n			gTxt(\'tab_pages\') => \'page\',\n			gTxt(\'tab_forms\') => \'form\',\n			gTxt(\'tab_style\') => \'css\'\n		);\n		$areas[\'admin\'] = array(\n			gTxt(\'tab_diagnostics\') => \'diag\',\n			gTxt(\'tab_preferences\') => \'prefs\',\n			gTxt(\'tab_site_admin\')  => \'admin\',\n			gTxt(\'tab_logs\') => \'log\',\n			gTxt(\'tab_import\') => \'import\'\n		);\n		\n		$areas[\'extensions\'] = array();\n		if(is_array($plugin_areas))\n			$areas = array_merge_recursive($areas, $plugin_areas);\n		\n		foreach($areas as $group) \n			foreach($group as $title => $name) \n				$out[$name] = $title;\n		\n		return $out;\n	}\n\n/**\n	Generates the list of available steps\n*/\n	\n	function rah_post_versions_steper() {\n		\n		$id = gps(\'group_id\');\n		\n		$rs = \n			safe_rows(\n				\'step\',\n				\'rah_post_versions\',\n				\"setid=\'\".doSlash($id).\"\' GROUP BY step ORDER BY step asc\"\n			);\n			\n		if(!$rs)\n			return;\n		\n		foreach($rs as $a)\n			$out[] = \'				<option value=\"\'.htmlspecialchars($a[\'step\']).\'\">\'.htmlspecialchars($a[\'step\']).\'</option>\';\n		\n		return\n			implode(n,$out);\n	}\n\n/**\n	Redirects to the preferences panel\n*/\n\n	function rah_post_versions_options() {\n		header(\'Location: ?event=rah_post_versions\');\n		echo \n			\'<p id=\"message\">\'.n.\n			\'	<a href=\"?event=rah_post_versions\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', '	##################\n	#\n	#	rah_post_versions-plugin for Textpattern\n	#	version 0.9\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (c) 2011 Jukka Svahn\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_post_versions_install();\n		add_privs(\'rah_post_versions\',\'1,2\');\n		add_privs(\'plugin_prefs.rah_post_versions\',\'1,2\');\n		register_tab(\'extensions\',\'rah_post_versions\',\'Post versions\');\n		register_callback(\'rah_post_versions_page\',\'rah_post_versions\');\n		register_callback(\'rah_post_versions_css\',\'admin_side\',\'head_end\');\n		register_callback(\'rah_post_versions_messager\',\'admin_side\',\'pagetop_end\');\n		register_callback(\'rah_post_versions_install\',\'plugin_lifecycle.rah_post_versions\');\n		register_callback(\'rah_post_versions_options\',\'plugin_prefs.rah_post_versions\');\n		rah_post_versions_register();\n	}\n\n/**\n	Registers backend events\n*/\n\n	function rah_post_versions_register() {\n		\n		global $txp_user;\n		\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$authors = explode(\',\',$authors);\n		\n		if(in_array($txp_user,$authors))\n			return;\n\n		foreach(explode(\',\',$events) as $event) {\n			$item = explode(\':\',$event);\n			if(isset($item[1]))\n				register_callback(\'rah_post_versions\',trim($item[0]),trim($item[1]),0);\n		}\n	}\n\n/**\n	Creates the tables required for the plugin to operate.\n*/\n\n	function rah_post_versions_install($event=\'\',$step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.\n				safe_pfx(\'rah_post_versions\').\', \'.\n				safe_pfx(\'rah_post_versions_sets\').\', \'.\n				safe_pfx(\'rah_post_versions_prefs\')\n			);\n			\n			return;\n		}\n		\n		/*\n			Stores the changesets\n			\n			* id: The row primary key\n			* event: The event the post was recorded form.\n			* step: Same, but the step.\n			* title: The identifier (Article title, form name) that is used to identify the changeset record.\n			* grid: The ident used to group the commit to sets (form name, article id etc).\n			* modified: Time of the last change to the changeset.\n			* changes: The number of changes the set holds.\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions_sets\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`event` VARCHAR(255) NOT NULL,\n				`step` VARCHAR(255) NOT NULL,\n				`grid` VARCHAR(255) NOT NULL,\n				`title` VARCHAR(255) NOT NULL,\n				`modified` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\n				`changes` INT(12) NOT NULL default 0,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Stores the changes (versions) in changesets\n			\n			* id: The row primary key\n			* event: The event the post was recorded form.\n			* step: Same, but the step.\n			* title: The identifier (Article title, form name) that is used to identify the changeset record.\n			* grid: The ident used to group the commit to sets (form name, article id etc).\n			* author: The user posted the post.\n			* posted: Time of the post.\n			* data: All the post data\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`event` VARCHAR(255) NOT NULL,\n				`step` VARCHAR(255) NOT NULL,\n				`title` VARCHAR(255) NOT NULL,\n				`grid` VARCHAR(255) NOT NULL,\n				`setid` INT(11) NOT NULL,\n				`author` VARCHAR(255) NOT NULL,\n				`posted` DATETIME NOT NULL default \'0000-00-00 00:00:00\',\n				`data` LONGTEXT NOT NULL,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Creates preferences table\n		*/\n		\n		safe_query(\n			\'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'rah_post_versions_prefs\').\" (\n				`name` VARCHAR(255) NOT NULL,\n				`value` LONGTEXT NOT NULL,\n				PRIMARY KEY(`name`)\n			) CHARSET=utf8\"\n		);\n		\n		/*\n			Add default settings\n		*/\n		\n		rah_post_versions_add_pref(\n			array(\n				\'exclude\' => \'\',\n				\'statuses\' => \'\',\n				\'authors\' => \'\',\n				\'email\' => \'No\',\n				\'email_additional\' => \'\',\n				\'posting_now\' => \'No\',\n				\'events\' => \'article:edit, article:create, article:publish, article:save, page:page_create, page:page_edit, page:page_save, form:form_create, form:form_edit, form:form_save, prefs:prefs_save, prefs:advanced_prefs_save, category:cat_article_create, category:cat_image_create, category:cat_file_create, category:cat_link_create, category:cat_article_save, category:cat_image_save, category:cat_file_save, category:cat_link_save, section:section_create, section:section_save, link:link_post, link:link_save, discuss:discuss_save, image:image_save, file:file_save, css:css_save\',\n				\'hidden\' => \'event,step,save\',\n				\'ident\' => \n					\'switch($event) {\'.n.\n					\'	case \"article\":\'.n.\n					\'		$grid = rah_post_versions_id();\'.n.\n					\'		$title = gps(\"Title\");\'.n.\n					\'	break;\'.n.\n					\'	case \"category\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = gps(\"title\");\'.n.\n					\'	break;\'.n.\n					\'	case \"link\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = gps(\"linkname\");\'.n.\n					\'	break;\'.n.\n					\'	case \"prefs\":\'.n.\n					\'		$grid = $title = ($step == \"advanced_prefs_save\") ? \"Advanced prefs\" : \"Preferences\";\'.n.\n					\'	break;\'.n.\n					\'	case \"discuss\":\'.n.\n					\'		$grid = gps(\"discussid\");\'.n.\n					\'		$title = gps(\"name\");\'.n.\n					\'	break;\'.n.\n					\'	case \"section\":\'.n.\n					\'		$grid = gps(\"name\");\'.n.\n					\'		$title = (gps(\"title\")) ? gps(\"title\") : gps(\"name\");\'.n.\n					\'	break;\'.n.\n					\'	case \"image\":\'.n.\n					\'		$grid = rah_post_versions_id(\"id\");\'.n.\n					\'		$title = (gps(\"name\")) ? gps(\"name\") : $grid;\'.n.\n					\'	break;\'.n.\n					\'	case \"file\":\'.n.\n					\'		$grid = gps(\"id\");\'.n.\n					\'		$title = gps(\"filename\");\'.n.\n					\'	break;\'.n.\n					\'	default:\'.n.\n					\'		$grid = $title = gps(\"name\");\'.n.\n					\'}\',\n				\'version\' => \'0.1\'\n			)\n		);\n	}\n\n/**\n	Deliver panel\n*/\n\n	function rah_post_versions_page() {\n		require_privs(\'rah_post_versions\');\n		global $step;\n		if(in_array($step,array(\n			\'rah_post_versions_group\',\n			\'rah_post_versions_group_delete\',\n			\'rah_post_versions_diff\',\n			\'rah_post_versions_view\',\n			\'rah_post_versions_delete\',\n			\'rah_post_versions_prefs\',\n			\'rah_post_versions_prefs_save\',\n		))) $step();\n		else rah_post_versions_list();\n	}\n\n/**\n	Adds the panel\'s CSS to the head segment.\n*/\n\n	function rah_post_versions_css() {\n		global $event;\n		\n		if($event != \'rah_post_versions\')\n			return;\n			\n		$msg = gTxt(\'are_you_sure\');\n		\n		echo <<<EOF\n			<script type=\"text/javascript\">\n				<!--\n				\n				/*\n					Multi-edit function, auto-hiden dropdown\n				*/\n				\n				function rah_post_versions_stepper() {\n					if($(\'#rah_post_versions_step\').length < 1)\n						return;\n					\n					$(\'#rah_post_versions_step .smallerbox\').hide();\n\n					if($(\'#rah_post_versions_container input[type=checkbox]:checked\').val() == null)\n						$(\'#rah_post_versions_step\').hide();\n\n					/*\n						Reset the value\n					*/\n\n					$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n\n					/*\n						Every time something is checked, check if\n						the dropdown should be shown\n					*/\n\n					$(\'#rah_post_versions_container input[type=checkbox], #rah_post_versions_container td\').click(\n						function(){\n							$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n							if($(\'table#list input[type=checkbox]:checked\').val() != null)	\n								$(\'#rah_post_versions_step\').slideDown();\n							else\n								$(\'#rah_post_versions_step\').slideUp();\n						}\n					);\n\n					/*\n						If value is changed, send the form\n					*/\n\n					$(\'#rah_post_versions_container select[name=\"step\"]\').change(\n						function(){\n							$(this).parent(\'p\').parent(\'form\').submit();\n						}\n					);\n\n					/*\n						Verify if the sent is allowed\n					*/\n\n					$(\'select[name=\"step\"]\').parent(\'p\').parent(\'form\').submit(\n						function() {\n							if(!verify(\'{$msg}\')) {\n								$(\'#rah_post_versions_container select[name=\"step\"]\').val(\'\');\n								return false;\n							}\n						}\n					);\n				}\n				\n				/*\n					Filtering link\n				*/\n				\n				function rah_post_versions_filter() {\n					if($(\'#rah_post_versions_filter\').length < 1)\n						return;\n					\n					$(\'#rah_post_versions_nav\').append(\' <span class=\"rah_ui_sep\">#</span> <a id=\"rah_post_versions_filter_link\">Filter results</a>\');\n					$(\'#rah_post_versions_filter\').hide();\n					$(\'#rah_post_versions_filter_link\').click(\n						function(){\n							$(\'#rah_post_versions_filter\').slideToggle();\n							return false;\n						}\n					);\n				}\n			\n				$(document).ready(function(){\n					rah_post_versions_filter();\n					rah_post_versions_stepper();\n				});\n				-->\n			</script>\n			<style type=\"text/css\">\n				#rah_post_versions_container {\n					width: 950px;\n					margin: 0 auto;\n				}\n				#rah_post_versions_container table input {\n					padding: 0;\n					margin: 0;\n				}\n				#rah_post_versions_container table td {\n					padding: 4px 3px;\n				}\n				#rah_post_versions_container #rah_post_versions_step {\n					text-align: right;\n					padding: 10px 0 0 0;\n				}\n				#rah_post_versions_actions {\n					border-top: 1px solid #ccc;\n					border-bottom: 1px solid #ccc;\n					padding: 5px 0;\n				}\n				#rah_post_versions_container table {\n					width:100%;\n				}\n				#rah_post_versions_container input.edit {\n					width: 940px;\n					padding: 1px;\n				}\n				#rah_post_versions_container textarea {\n					width: 940px;\n					padding: 1px;\n				}\n				#rah_post_versions_container select {\n					width: 640px;\n				}\n				#rah_post_versions_container #rah_post_versions_step select {\n					width: 120px;\n				}\n				#rah_post_versions_filter {\n					text-align: center;\n					border-bottom: 1px solid #ccc;\n					border-top: 1px solid #ccc;\n					padding: 5px 20px;\n					background: #f5f5f5;\n					margin: 0 0 5px 0;\n				}\n				#rah_post_versions_filter select {\n					width: 200px;\n					margin: 0 2px;\n					padding: 0;\n				}\n				#rah_post_versions_pages {\n					text-align: center;\n					padding: 10px 10px 0 10px;\n				}\n				#rah_post_versions_pages .rah_post_version_active {\n					color: #000;\n					text-decoration: underline;\n				}\n				#rah_post_versions_container .rah_post_versions_diff {\n					overflow: auto;\n					white-space: pre;\n					font: 1.1em monospace;\n					line-height: 1.2em;\n					background: #f5f5f5;\n					border: 1px solid #ccc;\n					padding: 10px;\n					margin: 0 0 10px 0;\n				}\n				#rah_post_versions_container .rah_post_versions_diff .rah_post_versions_add {\n					background: #a7d726;\n					border: 1px solid #829b3e;\n					display: block;\n					margin-bottom: -1.1em;\n				}\n				#rah_post_versions_container .rah_post_versions_diff .rah_post_versions_del {\n					background: #c54e4e;\n					border: 1px solid #8b3e3e;\n					display: block;\n					margin-bottom: -1.1em;\n				}\n			</style>\n\nEOF;\n	}\n\n/**\n	Builds URI\n*/\n\n	function rah_post_versions_uri($defaults=array(),$segments=array(\'filter_event\',\'filter_limit\',\'filter_order\',\'filter_page\',\'group_step\',\'group_limit\',\'group_order\')) {\n		foreach($segments as $segment)\n			if(gps($segment))\n				$get[$segment] = $segment.\'=\'.htmlspecialchars(gps($segment));\n		\n		foreach($defaults as $key => $val)\n			if(!empty($val)) $get[$key] = $key.\'=\'.htmlspecialchars($val);\n		\n		return \n			\'?\'.implode(\'&amp;\',$get);\n	}\n\n/**\n	Builds inputs\n*/\n\n	function rah_post_versions_input($defaults=array(),$segments=array(\'filter_event\',\'filter_limit\',\'filter_order\',\'filter_page\',\'group_step\',\'group_limit\',\'group_order\')) {\n		foreach($segments as $segment)\n			if(gps($segment))\n				$get[$segment] = \'			<input type=\"hidden\" name=\"\'.$segment.\'\" value=\"\'.htmlspecialchars(gps($segment)).\'\" />\';\n		\n		foreach($defaults as $key => $val)\n			if(!empty($val))\n				$get[$key] = \'			<input type=\"hidden\" name=\"\'.$key.\'\" value=\"\'.htmlspecialchars($val).\'\" />\';\n		\n		return \n			implode(n,$get);\n	}\n\n/**\n	Builds pagination\n*/\n\n	function rah_post_versions_pagination($total=0,$limit=\'filter_limit\',$page_att=\'filter_page\') {\n		\n		if($total == 0)\n			return;\n		\n		global $event, $step;\n		\n		$limit = gps($limit);\n		$page = gps($page_att);\n		$group_id = gps(\'group_id\');\n		\n		if(trim($limit,\'0123456789\') || empty($limit) || $limit <= 0)\n			$limit = 20;\n		\n		$pages = ceil(($total/$limit));\n		\n		if(trim($page,\'0123456789\') || empty($page) || !is_numeric($page) || $page <= 0 || $page > $pages)\n			$page = 1;\n		\n		$end = (($page+3) > $pages) ? $pages : ($page+3);\n		$start = (($end-3) < 1) ? 1 : ($end-3);\n		\n		if($start > 1) \n			$out[] = \n				\'<a title=\"Go to the first page\" href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => $step,\n						$page_att => 1,\n						\'group_id\' => $group_id\n					)\n				).\n				\'\">&#171;</a>\';\n\n		\n		for($i=$start;$i<=$end;$i++)\n			$out[] = \n				\'<a\'.(($i == $page) ? \' class=\"rah_post_version_active\"\' : \'\').\' href=\"\'.\n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => $step,\n							$page_att => $i,\n							\'group_id\' => $group_id\n						)\n					).\n				\'\">\'.$i.\'</a>\';\n		\n		if($end < $pages)\n			$out[] = \n				\'<a title=\"Go to the last page\" href=\"\'.\n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => $step,\n							$page_att => $pages,\n							\'group_id\' => $group_id\n						)\n					).\n				\'\">&#187;</a>\';\n		\n		return \n			\'			<div id=\"rah_post_versions_pages\">\'.n.\n			implode(\' \',$out).n.\n			\'			</div>\';\n	}\n\n/**\n	The main listing\n*/\n\n	function rah_post_versions_list($message=\'\') {\n	\n		global $event;\n		\n		$events = rah_post_versions_eventer();\n		\n		extract(doSlash(gpsa(array(\n			\'filter_event\',\n			\'filter_title\',\n			\'filter_page\',\n			\'filter_limit\',\n			\'filter_order\'\n		))));\n		\n		$q[] = \'1=1\';\n		\n		if($filter_event)\n			$q[] = \"event=\'\".doSlash($filter_event).\"\'\";\n		\n		if(!$filter_limit || !is_numeric($filter_limit) || trim($filter_limit,\'0123456789\'))\n			$filter_limit = 20;\n		\n		if(is_numeric($filter_page) && $filter_page > 0 && !trim($filter_limit,\'0123456789\'))\n			$offset = ($filter_page*$filter_limit)-$filter_limit;\n		else \n			$offset = 0;\n		\n		if(!in_array($filter_order,array(\n			\'id\',\n			\'event\',\n			\'title\',\n			\'changes\'\n		)))\n			$filter_order = \'modified\';\n		\n		$rs = \n			safe_rows(\n				\'id,event,step,grid,title,modified,changes\',\n				\'rah_post_versions_sets\',\n				implode(\' and \',$q).\" order by $filter_order desc LIMIT $offset, $filter_limit\"\n			);\n		\n		$total = \n			safe_count(\n				\'rah_post_versions_sets\',\n				implode(\' and \',$q)\n			);\n		\n		$out[] = \n			\n			\'		<form id=\"rah_post_versions_filter\" method=\"get\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event\n				),\n				array(\n					\'group_step\',\n					\'group_limit\',\n					\'group_order\',\n					\'group_page\'\n				)\n			).n.\n			\n			\'			<select name=\"filter_event\">\'.n.\n			\'				<option value=\"\">Filter by the event...</option>\'.n;\n		\n		foreach($events as $key => $value) \n			$out[] = \'			<option value=\"\'.htmlspecialchars($key).\'\">\'.htmlspecialchars($value).\'</option>\'.n;\n		\n		\n		$out[] = \n			\'			</select>\'.n.\n			\'			<select name=\"filter_limit\">\'.n.\n			\'				<option value=\"\">Items per page...</option>\'.n.\n			\'				<option value=\"10\">10</option>\'.n.\n			\'				<option value=\"25\">25</option>\'.n.\n			\'				<option value=\"50\">50</option>\'.n.\n			\'				<option value=\"100\">100</option>\'.n.\n			\'			</select>\'.n.\n			\'			<select name=\"filter_order\">\'.n.\n			\'				<option value=\"\">Sort by...</option>\'.n.\n			\'				<option value=\"id\">ID</option>\'.n.\n			\'				<option value=\"event\">Event</option>\'.n.\n			\'				<option value=\"title\">Title</option>\'.n.\n			\'				<option value=\"modified\">Modified</option>\'.n.\n			\'				<option value=\"changes\">Changes</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" value=\"Filter\" class=\"smallerbox\" />\'.n.\n			\'		</form>\'.n.\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event\n				)\n			).n.\n			\n			\'			<table id=\"list\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'				<tr>\'.n.\n			\'					<th>#ID</th>\'.n.\n			\'					<th>Event</th>\'.n.\n			\'					<th>Title</th>\'.n.\n			\'					<th>Modified</th>\'.n.\n			\'					<th>Changes</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n;\n		\n		if($rs){\n		\n			foreach($rs as $a) {\n				\n				if(!$a[\'title\'])\n					$a[\'title\'] = gTxt(\'untitled\');\n				\n				if(isset($events[$a[\'event\']]))\n					$a[\'event\'] = $events[$a[\'event\']];\n				\n				$url = \n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => \'rah_post_versions_group\',\n							\'group_id\' => $a[\'id\']\n						)\n					);\n				\n				$out[] =  \n					\n					\'				<tr>\'.n.\n					\'					<td>\'.$a[\'id\'].\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'event\']).\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.htmlspecialchars($a[\'title\']).\'</a></td>\'.n.\n					\'					<td>\'.safe_strftime(\'%b %d %Y %H:%M:%S\',strtotime($a[\'modified\'])).\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.$a[\'changes\'].\'</a></td>\'.n.\n					\'					<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				</tr>\'.n;\n			}\n			\n		} else \n			$out[] = \n				\'				<tr>\'.n.\n				\'					<td colspan=\"6\">No article version data found.</td>\'.n.\n				\'				</tr>\'.n;\n		\n		$out[] = \n			\'			</table>\'.n.\n			rah_post_versions_pagination($total).n.\n			\n			\'			<p id=\"rah_post_versions_step\" class=\"rah_ui_step\">\'.n.\n			\'				<select name=\"step\">\'.n.\n			\'					<option value=\"\">With selected...</option>\'.n.\n			\'					<option value=\"rah_post_versions_group_delete\">Delete changeset</option>\'.n.\n			\'				</select>\'.n.\n			\'				<input type=\"submit\" class=\"smallerbox\" value=\"\'.gTxt(\'go\').\'\" />\'.n.\n			\'			</p>\'.n.\n			\'		</form>\'.n.\n			\'	</div>\'.n;\n		\n		rah_post_versions_header(\n			$out,\n			$message,\n			\'Revision control\'\n		);\n		\n	}\n\n/**\n	Remove whole groups\n*/\n\n	function rah_post_versions_group_delete() {\n		\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected) || empty($selected)) {\n			rah_post_versions_list(\'Nothing selected.\');\n			return;\n		}\n		\n		foreach($selected as $id)\n			$in[] = \"\'\".doSlash($id).\"\'\";\n		\n		$in = implode(\',\',$in);\n		\n		if(\n			safe_delete(\n				\'rah_post_versions\',\n				\'setid in(\'.$in.\')\'\n			) == false ||\n			safe_delete(\n				\'rah_post_versions_sets\',\n				\'id in(\'.$in.\')\'\n			) == false\n		) {\n			rah_post_versions_list(\'Database error occured when removing items. Please try again.\');\n			return;	\n		}\n		\n		rah_post_versions_list(\'Selection removed.\');\n	}\n\n/**\n	View list of items in a group\n*/\n\n	function rah_post_versions_group($message=\'\') {\n		\n		global $event,$step;\n		\n		$id = \n			gps(\'group_id\');\n		\n		extract(doSlash(gpsa(array(\n			\'group_step\',\n			\'group_page\',\n			\'group_limit\',\n			\'group_order\'\n		))));\n		\n		$q[] = \"setid=\'\".doSlash($id).\"\'\";\n		\n		if($group_step)\n			$q[] = \"step=\'\".doSlash($group_step).\"\'\";\n		\n		if(!$group_limit || !is_numeric($group_limit) || trim($group_limit,\'0123456789\'))\n			$group_limit = 20;\n		\n		if(is_numeric($group_page) && $group_page > 0 && !trim($group_page,\'0123456789\'))\n			$offset = ($group_page*$group_limit)-$group_limit;\n		else \n			$offset = 0;\n		\n		if(!in_array($group_order,array(\n			\'title\',\n			\'posted\',\n			\'step\',\n			\'author\',\n		)))\n			$group_order = \'id\';\n		\n		$rs = \n			safe_rows(\n				\'id,title,posted,author,step\',\n				\'rah_post_versions\',\n				implode(\' and \',$q).\" order by $group_order desc LIMIT $offset, $group_limit\"\n			);\n		\n		$total = \n			safe_count(\n				\'rah_post_versions\',\n				implode(\' and \',$q)\n			);\n		\n		$out[] = \n			\n			\'		<form id=\"rah_post_versions_filter\" method=\"get\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'group_id\' => $id\n				),\n				array(\n					\'filter_step\',\n					\'filter_limit\',\n					\'filter_order\',\n					\'filter_page\'\n				)\n			).n.\n			\n			\'			<select name=\"group_step\">\'.n.\n			\'				<option value=\"\">Filter by the step...</option>\'.n.\n			rah_post_versions_steper().n.\n			\'			</select>\'.n.\n			\'			<select name=\"group_limit\">\'.n.\n			\'				<option value=\"\">Items per page...</option>\'.n.\n			\'				<option value=\"10\">10</option>\'.n.\n			\'				<option value=\"25\">25</option>\'.n.\n			\'				<option value=\"50\">50</option>\'.n.\n			\'				<option value=\"100\">100</option>\'.n.\n			\'			</select>\'.n.\n			\'			<select name=\"group_order\">\'.n.\n			\'				<option value=\"\">Sort by...</option>\'.n.\n			\'				<option value=\"id\">ID</option>\'.n.\n			\'				<option value=\"title\">Title</option>\'.n.\n			\'				<option value=\"posted\">Posted</option>\'.n.\n			\'				<option value=\"step\">Step</option>\'.n.\n			\'				<option value=\"author\">Author</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" value=\"Filter\" class=\"smallerbox\" />\'.n.\n			\'		</form>\'.n.\n			\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => $step,\n					\'group_id\' => $id\n				)\n			).n.\n			\n			\'			<table id=\"list\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'				<tr>\'.n.\n			\'					<th>#ID</th>\'.n.\n			\'					<th>Title</th>\'.n.\n			\'					<th>Posted</th>\'.n.\n			\'					<th>Step</th>\'.n.\n			\'					<th>Author</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n;\n		\n		if($rs){\n		\n			foreach($rs as $a) {\n			\n				$url = \n					rah_post_versions_uri(\n						array(\n							\'event\' => $event,\n							\'step\' => \'rah_post_versions_view\',\n							\'group_id\' => $id,\n							\'id\' => $a[\'id\']\n						)\n					);\n			\n				$out[] =  \n					\n					\'				<tr>\'.n.\n					\'					<td>\'.$a[\'id\'].\'</td>\'.n.\n					\'					<td><a href=\"\'.$url.\'\">\'.htmlspecialchars($a[\'title\']).\'</a></td>\'.n.\n					\'					<td>\'.safe_strftime(\'%b %d %Y %H:%M:%S\',strtotime($a[\'posted\'])).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'step\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'author\']).\'</td>\'.n.\n					\'					<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				</tr>\'.n;\n			}\n			\n		} else \n			$out[] = \n				\'				<tr>\'.n.\n				\'					<td colspan=\"6\">No version data found.</td>\'.n.\n				\'				</tr>\'.n;\n		\n		$out[] = \n			\'			</table>\'.n.\n			\n			rah_post_versions_pagination($total,\'group_limit\',\'group_page\').n.\n			\n			\'			<p id=\"rah_post_versions_step\" class=\"rah_ui_step\">\'.n.\n			\'				<select name=\"step\">\'.n.\n			\'					<option value=\"\">With selected...</option>\'.n.\n			\'					<option value=\"rah_post_versions_diff\">Diff</option>\'.n.\n			\'					<option value=\"rah_post_versions_delete\">Delete</option>\'.n.\n			\'				</select>\'.n.\n			\'				<input type=\"submit\" class=\"smallerbox\" value=\"Go\" />\'.n.\n			\'			</p>\'.n.\n			\'		</form>\'.n.\n			\'	</div>\'.n;\n		\n		rah_post_versions_header(\n			$out,$message,\'Viewing set #\'.htmlspecialchars($id)\n		);\n		\n	}\n\n/**\n	Deletes individual item from a group\n*/\n\n	function rah_post_versions_delete() {\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected) || empty($selected)) {\n			rah_post_versions_group(\'Nothing selected.\');\n			return;\n		}\n		\n		foreach($selected as $id)\n			$in[] = \"\'\".doSlash($id).\"\'\";\n		\n		$in = implode(\',\',$in);\n		\n		if(\n			safe_delete(\n				\'rah_post_versions\',\n				\'id in(\'.$in.\')\'\n			) == false\n		) {\n			rah_post_versions_group(\'Database error occured when removing items. Please try again.\');\n			return;	\n		}\n		\n		rah_post_versions_group(\'Selection removed.\');\n	}\n\n/**\n	View differences\n*/\n\n	function rah_post_versions_diff($message=\'\') {\n		\n		global $event;\n		\n		$selection = ps(\'selected\');\n		\n		if(!is_array($selection) || count($selection) != 2) {\n			rah_post_versions_group(\'Invalid selection. Select two items to compare.\');\n			return;\n		}\n		\n		sort($selection);\n		\n		$results = rah_post_versions_diff_data($selection[0],$selection[1]);\n		\n		if(empty($results)) {\n			rah_post_versions_group(\'Selected items not found.\');\n			return;\n		}\n		\n		rah_post_versions_header(\n			$results,$message,\'Diff of <a href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_view\',\n						\'group_id\' => gps(\'group_id\'),\n						\'id\' => $selection[0]\n					)\n				).\'\">r\'.$selection[0].\'</a> and <a href=\"\'.\n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_view\',\n						\'group_id\' => gps(\'group_id\'),\n						\'id\' => $selection[1]\n					)\n				).\'\">r\'.$selection[1].\'</a>\'\n		);\n	}\n\n/**\n	Build comparison of two data array()s\n*/\n\n	function rah_post_versions_diff_data($old,$new) {\n		\n		$old = \n			safe_field(\n				\'data\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($old).\"\' and setid=\'\".doSlash(gps(\'group_id\')).\"\'\"\n			);\n		\n		$new = \n			safe_field(\n				\'data\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($new).\"\' and setid=\'\".doSlash(gps(\'group_id\')).\"\'\"\n			);\n		\n		if(!$new || !$old)\n			return;\n		\n		$new = unserialize(base64_decode($new));\n		$old = unserialize(base64_decode($old));\n		\n		unset(\n			$old[\'rah_article_versions_repost_is\'],\n			$old[\'rah_article_versions_repost_uri\'],\n			$old[\'rah_article_versions_repost_id\'],\n			$old[\'_txp_token\'],\n			$new[\'rah_article_versions_repost_id\'],\n			$new[\'rah_article_versions_repost_is\'],\n			$new[\'rah_article_versions_repost_uri\'],\n			$new[\'_txp_token\']\n		);\n		\n		foreach($new as $key => $val) {\n			\n			if(!isset($old[$key]))\n				$old[$key] = \'\';\n			\n			/*\n				If the field is exact same do not show it at all\n			*/\n			\n			if($old[$key] == $val) {\n				unset($old[$key]);\n				continue;\n			}\n			\n			/*\n				Check if array. If true make the array a single block\n			*/\n			\n			if(is_array($old[$key]))\n				$old[$key] = implode(n,$old[$key]);\n			if(is_array($val))\n				$val = implode(n,$val);\n			\n			$out[] = \n				\'<p><strong>\'.htmlspecialchars($key).\':</strong></p>\'.n.\n				\'<div class=\"rah_post_versions_diff\">\'.\n					rah_post_versions_diff_lib(\n						$old[$key],\n						$val\n					).n.\n				\'</div>\';\n			\n			unset($old[$key]);\n		}\n		\n		/*\n			List removed fields (removed custom-fields / updated TXP) as removed\n		*/\n		\n		if($old)\n			foreach($old as $key => $val) \n				$out[] = \n					\'<p><strong><del>\'.htmlspecialchars($key).\'</del>:</strong></p>\'.n.\n					\'<div class=\"rah_post_versions_diff\"><span class=\"rah_post_versions_del\">\'.htmlspecialchars($val).\'</span></div>\'.n\n				;\n		\n		if(!isset($out))\n			$out[] = \'<p>The revisions are exact match. No changes to show.</p>\';\n		\n		return implode(\'\',$out);\n	}\n\n/**\n	Starts splitting the lines, and then implodes the results\n*/\n\n	function rah_post_versions_diff_lib($old, $new){\n		\n		foreach(\n			rah_post_versions_diff_compare(\n				rah_post_versions_explode($old),\n				rah_post_versions_explode($new)\n			) as $key => $line\n		){\n			if(is_array($line)) {\n				if(!empty($line[\'d\'])) \n					$out[] = \'<span class=\"rah_post_versions_del\">\'.htmlspecialchars(implode(n,$line[\'d\'])).\'</span>\';\n				if(!empty($line[\'i\']))\n					$out[] = \'<span class=\"rah_post_versions_add\">\'.htmlspecialchars(implode(n,$line[\'i\'])).\'</span>\';\n			} else\n				$out[] = htmlspecialchars($line);\n		}\n		\n		return implode(n,$out);\n	}\n\n/**\n	Compares lines we just split\n*/\n\n	function rah_post_versions_diff_compare($old, $new, $maxlen = 0){\n\n		/*\n			rah_post_version_diff_compare() function\'s contents are based on:\n			\n			Paul\'s Simple Diff Algorithm v 0.1\n			(C) Paul Butler 2007 <http://www.paulbutler.org/>\n			Licensed under GNU GPL compatible zlib/libpng license.\n			\n			***\n\n				This software is provided \'as-is\', without any express or implied\n				warranty. In no event will the authors be held liable for any damages\n				arising from the use of this software.\n\n				Permission is granted to anyone to use this software for any purpose,\n				including commercial applications, and to alter it and redistribute it\n				freely, subject to the following restrictions:\n\n					1. The origin of this software must not be misrepresented; you must not\n					claim that you wrote the original software. If you use this software\n					in a product, an acknowledgment in the product documentation would be\n					appreciated but is not required.\n\n					2. Altered source versions must be plainly marked as such, and must not be\n					misrepresented as being the original software.\n\n					3. This notice may not be removed or altered from any source\n					distribution.\n			\n			***\n		*/\n\n		foreach($old as $oindex => $ovalue){\n			$nkeys = array_keys($new, $ovalue);\n			foreach($nkeys as $nindex){\n				$matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ? $matrix[$oindex - 1][$nindex - 1] + 1 : 1;\n				if($matrix[$oindex][$nindex] > $maxlen){\n					$maxlen = $matrix[$oindex][$nindex];\n					$omax = $oindex + 1 - $maxlen;\n					$nmax = $nindex + 1 - $maxlen;\n				}\n			}\n		}\n		\n		if($maxlen == 0)\n			return array(array(\'d\'=>$old, \'i\'=>$new));\n		\n		return \n			array_merge(\n				rah_post_versions_diff_compare(\n					array_slice($old, 0, $omax),\n					array_slice($new, 0, $nmax)\n				),\n				array_slice($new, $nmax, $maxlen),\n				rah_post_versions_diff_compare(\n					array_slice($old, $omax + $maxlen),\n					array_slice($new, $nmax + $maxlen)\n				)\n			)\n		;\n	}\n\n/**\n	Clean line breaks and do explode.\n*/\n\n	function rah_post_versions_explode($string=\'\') {\n		$string = str_replace(array(\"\\r\\n\",\"\\r\"), n, $string);\n		return explode(n,$string);\n	}\n\n/**\n	Shows input fields\n*/\n\n	function rah_post_versions_field($value=\'\',$key=\'\',$i=1,$hidden=false) {\n		if($hidden == true) \n			return \n				\'						<input type=\"hidden\" name=\"\'.htmlspecialchars($key).\'\" value=\"\'.htmlspecialchars($value).\'\" />\';\n		\n		return\n			\'				<p>\'.n.\n			\'					<label for=\"rah_post_versions_label_\'.$i.\'\">\'.htmlspecialchars($key).\'</label><br />\'.n.\n			((substr_count($value,n) == 0) ?\n				\'						<input id=\"rah_post_versions_label_\'.$i.\'\" type=\"text\" name=\"\'.htmlspecialchars($key).\'\" class=\"edit\" value=\"\'.htmlspecialchars($value).\'\" />\'\n			:\n				\'						<textarea id=\"rah_post_versions_label_\'.$i.\'\" name=\"\'.htmlspecialchars($key).\'\" class=\"code\" cols=\"100\" rows=\"6\">\'.htmlspecialchars($value).\'</textarea>\'\n			).n.\n			\'				</p>\'.n\n		;\n	}\n\n/**\n	Views individual changeset\n*/\n\n	function rah_post_versions_view($message=\'\') {\n		\n		global $event;\n		\n		$id = \n			gps(\'id\');\n			\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$hidden = explode(\',\',$hidden);\n		\n		$rs = \n			safe_row(\n				\'*\',\n				\'rah_post_versions\',\n				\"id=\'\".doSlash($id).\"\'\"\n			);\n			\n		if($rs) {\n			$data = base64_decode($rs[\'data\']);\n			$data = unserialize($data);\n			\n			$back = \n				rah_post_versions_uri(\n					array(\n						\'event\' => $event,\n						\'step\' => \'rah_post_versions_group\',\n						\'group_id\' => gps(\'group_id\')\n					)\n				);\n			\n			$out[] = \n				\'		<form method=\"post\" action=\"index.php\" onsubmit=\"return verify(\\\'Are you sure? There is no going back.\\\')\">\'.n.\n				\'			<p id=\"rah_post_versions_actions\">&#171; <a href=\"\'.$back.\'\">Go back</a> <strong>Ident:</strong> \'.$rs[\'title\'].\' <strong>Saved:</strong> \'.$rs[\'posted\'].\' by \'.$rs[\'author\'].\'. <strong>Event:</strong> <a href=\"?event=\'.$rs[\'event\'].\'\">\'.$rs[\'event\'].\'</a>, \'.$rs[\'step\'].\'</p>\'.n;\n			\n			$i = 0;\n			\n			foreach($data as $key => $value) {\n\n				$i++;\n				\n				if(is_array($value)) {\n					foreach($value as $needle => $selection) \n						$out[] = \n							rah_post_versions_field(\n								$selection,\n								$key.\'[\'.$needle.\']\',\n								$i,\n								in_array($key,$hidden)\n							);\n					continue;\n				}\n				\n				$out[] = \n					rah_post_versions_field(\n						$value,\n						$key,\n						$i,\n						in_array($key,$hidden)\n					);\n				\n			}\n\n			$out[] = \n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_is\" value=\"1\" />\'.n.\n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_uri\" value=\"\'.htmlspecialchars($back).\'\" />\'.n.\n				\'			<input type=\"hidden\" name=\"rah_article_versions_repost_id\" value=\"\'.htmlspecialchars($id).\'\" />\'.n.\n				\n				(function_exists(\'form_token\') ? \n					\'			<input type=\"hidden\" name=\"_txp_token\" value=\"\'.form_token().\'\" />\'.n : \'\'\n				).\n				\n				\'			<p id=\"rah_post_versions_warning\"><strong>Notice:</strong> Only click the <em>Re-post this</em> button when you are certain what you are about to do. Clicking the button will redo the exact posting, and depending of the information state stored, it might either overwrite, partially replace or dublicate something.</p>\'.n.\n				\'			<p><input type=\"submit\" class=\"publish\" value=\"Re-post this\" /></p>\'.n.\n				\'		</form>\';\n		} else\n			$out[] = \'<p>Nothing to show</p>\';\n		\n		$slogan = \'Viewing change #\'.htmlspecialchars($id);\n			\n		rah_post_versions_header(\n			$out,$message,$slogan\n		);\n		\n	}\n\n/**\n	Shows message after repost\n*/\n\n	function rah_post_versions_messager() {\n		\n		if(!ps(\'rah_article_versions_repost_is\'))\n			return;\n		\n		extract(gpsa(array(\n			\'rah_article_versions_repost_uri\',\n			\'rah_article_versions_repost_id\'\n		)));\n		\n		echo \n			n.\n			\'	<p id=\"rah_post_versions_messager\" style=\"text-align:center;padding: 5px 20px;\">\'.\n			\'		Reposted the form #\'.htmlspecialchars($rah_article_versions_repost_id).\'. \'.n.\n			\'		<a href=\"\'.$rah_article_versions_repost_uri.\'\">Go back to the version listing</a>.\'.n.\n			\'	</p>\'.n;\n	}\n\n/**\n	Fetch preferences to usable format from the database\n*/\n\n	function rah_post_versions_do_prefs() {\n		\n		$rs = \n			safe_rows(\n				\'name,value\',\n				\'rah_post_versions_prefs\',\n				\'1=1\'\n			);\n			\n		if(!$rs)\n			return;\n		\n		foreach($rs as $a) \n			$out[$a[\'name\']] = $a[\'value\'];\n		\n		return $out;\n	}\n\n/**\n	Adds preferences array to database\n*/\n\n	function rah_post_versions_add_pref($array) {\n		foreach($array as $name => $value) {\n			if(\n				safe_count(\n					\'rah_post_versions_prefs\',\n					\"name=\'\".doSlash($name).\"\'\"\n				) == 0\n			)\n				safe_insert(\n					\'rah_post_versions_prefs\',\n					\"name=\'\".doSlash($name).\"\',\n					value=\'\".doSlash($value).\"\'\"\n				);\n		}\n	}\n\n/**\n	Preferences panel\n*/\n\n	function rah_post_versions_prefs($message=\'\') {\n\n		global $event;\n		\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		$out = \n			\n			\'		<form method=\"post\" action=\"index.php\">\'.n.\n			\n			rah_post_versions_input(\n				array(\n					\'event\' => $event,\n					\'step\' => \'rah_post_versions_prefs_save\'\n				)\n			).n.\n			\n			\'			<p>\'.n.\n			\'				<label>Excluded fields. Comma separated list if multiple.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"exclude\" value=\"\'.htmlspecialchars($exclude).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Set fields hidden. Comma separated list if multiple. These fields are stored, but hid.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"hidden\" value=\"\'.htmlspecialchars($hidden).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Monitor following pages. Comma separated list if multiple. The format of item is <code>event:step</code>.</label><br />\'.n.\n			\'				<textarea name=\"events\" rows=\"4\" cols=\"30\">\'.htmlspecialchars($events).\'</textarea>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Excluded authors. Comma separated list if multiple. Uses login names.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"authors\" value=\"\'.htmlspecialchars($authors).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Send versions to author\\\'s email?</label><br />\'.n.\n			\'				<select name=\"email\">\'.n.\n			\'					<option value=\"\"\'.(($email == \'No\') ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'					<option value=\"\"\'.(($email == \'Yes\') ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'				</select>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Additional email addresses. Sends changes to this address. Comma separated list if multiple.</label><br />\'.n.\n			\'				<input class=\"edit\" type=\"text\" name=\"email_additional\" value=\"\'.htmlspecialchars($email_additional).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label>Ident code. Code that generates the Ident for a change.</label><br />\'.n.\n			\'				<textarea class=\"code\" cols=\"30\" rows=\"18\" name=\"ident\">\'.htmlspecialchars($ident).\'</textarea>\'.n.\n			\'			</p>\'.n.\n			\n			\'			<p><input type=\"submit\" class=\"publish\" value=\"Save settings\" /></p>\'.n.\n			\'		</form>\'.n;\n			\n		\n		rah_post_versions_header(\n			$out,\n			$message,\n			\'Preferences\'\n		);\n		\n	}\n\n/**\n	Saves preferences\n*/\n\n	function rah_post_versions_prefs_save() {\n		\n		extract(\n			gpsa(\n				array(\n					\'exclude\',\n					\'events\',\n					\'email_additional\',\n					\'authors\',\n					\'hidden\',\n					\'ident\',\n					\'email\'\n				)\n			)\n		);\n		\n		$authors = rah_post_versions_list_trim($authors);\n		$exclude = rah_post_versions_list_trim($exclude);\n		$hidden = rah_post_versions_list_trim($hidden);\n		$email_additional = rah_post_versions_list_trim($email_additional);\n		\n		rah_post_versions_pref_update(\n			array(\n				\'exclude\' => $exclude,\n				\'events\' => $events,\n				\'email_additional\' => $email_additional,\n				\'email\' => $email,\n				\'authors\' => $authors,\n				\'hidden\' => $hidden,\n				\'ident\' => $ident\n			)\n		);\n\n		rah_post_versions_prefs(\n			\'Preferences saved.\'\n		);\n		\n	}\n	\n/**\n	Goes thru an array and pushes it to the preferences table.\n*/\n\n	function rah_post_versions_pref_update($array) {\n		foreach($array as $name => $value)\n			safe_update(\n				\'rah_post_versions_prefs\',\n				\"value=\'\".doSlash($value).\"\'\",\n				\"name=\'\".doSlash($name).\"\'\"\n			);\n	}\n\n/**\n	Trims a $delim separated list of items.\n*/\n\n	function rah_post_versions_list_trim($array=\'\',$delim=\',\') {\n		\n		if(!$array)\n			return;\n		\n		if(!is_array($array))\n			$array = explode($delim,$array);\n		\n		foreach($array as $item) {\n			$item = trim($item);\n			if(!empty($item))\n				$out[] = $item;\n		}\n		\n		if(!isset($out))\n			return;\n		\n		return implode($delim,$out);\n		\n	}\n\n/**\n	Returns the ID\n*/\n\n	function rah_post_versions_id($key=\'ID\') {\n		$id = gps($key);\n		if(!$id && isset($GLOBALS[\'ID\']))\n			$id = $GLOBALS[\'ID\'];\n		return $id;\n	}\n\n/**\n	Saves versions from POSTs\n*/\n\n	function rah_post_versions() {\n		\n		global $txp_user,$event,$step,$sitename;\n		\n		/*\n			Check if the step has post\n		*/\n		\n		$post = (isset($_POST) && is_array($_POST)) ? $_POST : \'\';\n		\n		if(empty($post))\n			return;\n			\n		extract(\n			rah_post_versions_do_prefs()\n		);\n		\n		/*\n			Evaluates ident\n		*/\n		\n		eval(\n			$ident\n		);\n		\n		/*\n			Check if the ident returned the data we want\n		*/\n		\n		if(isset($kill) || !isset($grid) || !isset($title))\n			return;\n		\n		if(empty($title) || empty($grid))\n			return;\n		\n		/*\n			Excludes fields\n		*/\n		\n		$exclude = explode(\',\',$exclude);\n		\n		foreach($post as $key => $value) {\n			\n			if(in_array($key,array(\n				\'rah_article_versions_repost_is\',\n				\'rah_article_versions_repost_uri\',\n				\'rah_article_versions_repost_id\',\n				\'_txp_token\'\n			)))\n				continue;\n			\n			if(in_array($key,$exclude))\n				continue;\n			\n			$out[$key] = ps($key);\n		}\n		\n		if(!isset($out))\n			return;\n		\n		$data = doSlash(base64_encode(serialize($out)));\n		\n		if(\n			safe_count(\n				\'rah_post_versions_sets\',\n				\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n			) == 0\n		) {\n			safe_insert(\n				\'rah_post_versions_sets\',\n				\"modified=now(),\n				changes=1,\n				title=\'\".doSlash($title).\"\',\n				event=\'\".doSlash($event).\"\',\n				step=\'\".doSlash($step).\"\',\n				grid=\'\".doSlash($grid).\"\'\"\n			);\n			$setid = mysql_insert_id();\n		}\n		else {\n			safe_update(\n				\'rah_post_versions_sets\',\n				\'modified=now(),changes=changes+1\',\n				\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n			);\n			$setid = \n				safe_field(\n					\'id\',\n					\'rah_post_versions_sets\',\n					\"event=\'\".doSlash($event).\"\' and grid=\'\".doSlash($grid).\"\'\"\n				);\n		}\n		\n		safe_insert(\n			\'rah_post_versions\',\n			\"grid=\'\".doSlash($grid).\"\',\n			setid=\'\".doSlash($setid).\"\',\n			title=\'\".doSlash($title).\"\',\n			posted=now(),\n			author=\'\".doSlash($txp_user).\"\',\n			event=\'\".doSlash($event).\"\',\n			step=\'\".doSlash($step).\"\',\n			data=\'$data\'\"\n		);\n		\n		/*\n			Emailing\n		*/\n		\n		if($email != \'Yes\' && empty($email_additional))\n			return;\n		\n		if(!empty($email_additional))\n			$mails = explode(\',\',$email_additional);\n		\n		if($email == \'Yes\') {\n			$mail = trim(fetch(\'email\',\'txp_users\',\'name\',$txp_user));\n			if(!empty($mail))\n				$mails[] = $mail;\n		}\n		\n		if(!isset($mails))\n			return;\n		\n		$message = \n			gTxt(\'greeting\').n.n.\n			\'View the changes at:\'.n.\n			hu.\'textpattern/index.php?event=rah_post_versions\'\n		;\n		\n		foreach($mails as $mail)\n			txpMail(\n				$mail,\n				\"[$sitename] Change in the content\",\n				$message\n			);\n	}\n\n/**\n	Echoes the panels and header\n*/\n\n	function rah_post_versions_header($content=\'\',$message=\'\',$slogan=\'Version index\') {\n		\n		global $event;\n		pagetop(\'rah_post_versions\',$message);\n		\n		if(is_array($content))\n			$content = implode(\'\',$content);\n		\n		echo \n			n.\n			\'	<div id=\"rah_post_versions_container\" class=\"rah_ui_container\">\'.n.\n			\'		<h1><strong>rah_post_versions</strong> | \'.$slogan.\'</h1>\'.n.\n			\n			\'		<p id=\"rah_post_versions_nav\" class=\"rah_ui_nav\">\'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"\'.rah_post_versions_uri(array(\'event\' => $event)).\'\">Main</a> \'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"\'.rah_post_versions_uri(array(\'event\' => $event,\'step\' => \'rah_post_versions_prefs\')).\'\">Preferences</a> \'.\n			\' <span class=\"rah_ui_sep\">&#187;</span> <a href=\"?event=plugin&amp;step=plugin_help&amp;name=rah_post_versions\">Documentation</a>\'.\n					\n			\'</p>\'.n.\n			$content.n.\n			\'	</div>\'.n;\n	}\n\n/**\n	Lists event names\n*/\n\n	function rah_post_versions_eventer() {\n		global $plugin_areas;\n		\n		$out = array();\n		\n		$areas[\'content\'] = array(\n			gTxt(\'tab_organise\') => \'category\',\n			gTxt(\'tab_write\') => \'article\',\n			gTxt(\'tab_list\') =>  \'list\',\n			gTxt(\'tab_image\') => \'image\',\n			gTxt(\'tab_file\') => \'file\',					 \n			gTxt(\'tab_link\') => \'link\',\n			gTxt(\'tab_comments\') => \'discuss\'\n		);\n		$areas[\'presentation\'] = array(\n			gTxt(\'tab_sections\') => \'section\',\n			gTxt(\'tab_pages\') => \'page\',\n			gTxt(\'tab_forms\') => \'form\',\n			gTxt(\'tab_style\') => \'css\'\n		);\n		$areas[\'admin\'] = array(\n			gTxt(\'tab_diagnostics\') => \'diag\',\n			gTxt(\'tab_preferences\') => \'prefs\',\n			gTxt(\'tab_site_admin\')  => \'admin\',\n			gTxt(\'tab_logs\') => \'log\',\n			gTxt(\'tab_import\') => \'import\'\n		);\n		\n		$areas[\'extensions\'] = array();\n		if(is_array($plugin_areas))\n			$areas = array_merge_recursive($areas, $plugin_areas);\n		\n		foreach($areas as $group) \n			foreach($group as $title => $name) \n				$out[$name] = $title;\n		\n		return $out;\n	}\n\n/**\n	Generates the list of available steps\n*/\n	\n	function rah_post_versions_steper() {\n		\n		$id = gps(\'group_id\');\n		\n		$rs = \n			safe_rows(\n				\'step\',\n				\'rah_post_versions\',\n				\"setid=\'\".doSlash($id).\"\' GROUP BY step ORDER BY step asc\"\n			);\n			\n		if(!$rs)\n			return;\n		\n		foreach($rs as $a)\n			$out[] = \'				<option value=\"\'.htmlspecialchars($a[\'step\']).\'\">\'.htmlspecialchars($a[\'step\']).\'</option>\';\n		\n		return\n			implode(n,$out);\n	}\n\n/**\n	Redirects to the preferences panel\n*/\n\n	function rah_post_versions_options() {\n		header(\'Location: ?event=rah_post_versions\');\n		echo \n			\'<p id=\"message\">\'.n.\n			\'	<a href=\"?event=rah_post_versions\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', '87ab87511f6f43702fbdb84c3b2afef5', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_change_passwords', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.0.1', 'Maintain and change user passwords', '<h1>rah_change_passwords</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_change_passwords\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_change_passwords\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=28696\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_change_passwords\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_change_passwords is a <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern</a> plugin that allows <a href=\"http://textpattern.net/wiki/index.php?title=User_Roles_and_Permissions\" rel=\"nofollow\">Publishers</a> to freely change any user&#8217;s password and resetting login sessions directly from the <a href=\"http://textpattern.net/wiki/index.php?title=Users\" rel=\"nofollow\">Users</a> panel.</p>\n\n<p>The main difference to Textpattern <span class=\"caps\">CMS</span>’ built in password resetting feature is that you as a Publisher, and the site&#8217;s main administrator, can change any user&#8217;s passwords to anything you want. As such, users do not need to receive emails containing automatically generated passwords or do password restoring themselves.</p>\n\n<h2>Requirements</h2>\n\n<p>Minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_change_passwords’ installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Usage</h2>\n\n<p>Rah_change_password will add extra options to the <a href=\"http://textpattern.net/wiki/index.php?title=Users\" rel=\"nofollow\">Users</a> panel&#8217;s editor. When logged in with a <a href=\"http://textpattern.net/wiki/index.php?title=User_Roles_and_Permissions\" rel=\"nofollow\">Publisher</a> account, you will see options to change the password when editing any user account. These added extra options include a way to reset login sessions, to change and optionally email passwords.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 1.0.1 &#8211; 2012/09/19</h3>\n\n<ul>\n	<li>Added: Support links to the help file.</li>\n</ul>\n\n<h3>Version 1.0 &#8211; 2012/09/19</h3>\n\n<ul>\n	<li>Added: Finnish translation.</li>\n	<li>Changed: Integrates with the native user editor located at the Users panel.</li>\n	<li>Changed: Removed embedded language strings and moved them to textpacks.</li>\n	<li>Changed: Now uses class methods, and requires <span class=\"caps\">PHP</span> 5.</li>\n	<li>Improved: Textpattern 4.5.0 compatibility, look and feel.</li>\n	<li>Now requires Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h3>Version 0.8 &#8211; 2011/11/22</h3>\n\n<ul>\n	<li>Fixed: Missing language string.</li>\n	<li>Changed: Menu label to title case.</li>\n</ul>\n\n<h3>Version 0.7 &#8211; 2011/11/19</h3>\n\n<ul>\n	<li>Added: <code>autocomplete=&quot;off&quot;</code> to the inputs too.</li>\n	<li>Changed: Shortened admin-side step names to simple <code>edit</code> and <code>save</code>.</li>\n	<li>Removed: That header and documentation link in effort to make the pane look more native.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2011/06/18</h3>\n\n<ul>\n	<li>Fixed: Changed “Yes” and “No” to language strings.</li>\n	<li>Added: Now takes advantage of <span class=\"caps\">CSRF</span> protection introduced in Textpattern v4.4.1.</li>\n	<li>Dropped backwards compatibility layer used from v0.4 to 0.5. Users using older Textpattern version can still keep using <a href=\"http://rahforum.biz/plugins/rah_change_passwords/versions\" rel=\"nofollow\">older versions of rah_change_passwords</a>.</li>\n	<li>Now requires Textpattern version 4.4.1 or newer.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2011/06/01</h3>\n\n<ul>\n	<li>Added: Disabled password autocompletion by adding <code>autocomplete=&quot;off&quot;</code> to the form.</li>\n	<li>Added: The interface now has language string support.</li>\n	<li>Changed: Now only accepts requests sent as <span class=\"caps\">POST</span>. <span class=\"caps\">GET</span>s are ignored by the password changer.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2011/03/23</h3>\n\n<ul>\n	<li>Added <a href=\"http://code.google.com/p/textpattern/source/detail?r=3488\" rel=\"nofollow\"><span class=\"caps\">PHP</span>ass</a> support while keeping compatibility with older <span class=\"caps\">TXP</span> versions (v4.3.0 and older).</li>\n	<li>Added option to reset user&#8217;s current session.</li>\n	<li>Remembers the field values if returned to the form because of an error.</li>\n	<li>Small wording and styling changes.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2010/10/25</h3>\n\n<ul>\n	<li>Changed the <em>Email the password to the user?</em> dropdown (select field) to radio buttons.</li>\n	<li>Changed the plugin type to the newer <em>Admin only</em>.</li>\n	<li>Removed inline <span class=\"caps\">CSS</span>.</li>\n	<li>Removed unused <span class=\"caps\">HTML</span> IDs.</li>\n	<li>Now requires Textpattern version 4.0.7 or newer.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/06/01</h3>\n\n<ul>\n	<li>Added check against if password, user or confirmation password is empty.</li>\n	<li>Added option to not email the password.</li>\n	<li>Now checks that the database update was successful before sending email.</li>\n	<li>Now by default the user dropdown has no user selected. Reduces the change of accidentally resetting Joe instead of John.</li>\n	<li>Updated the styling.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/10/24</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_change_passwords plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_change_passwords\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_change_passwords();\n\nclass rah_change_passwords {\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'rah_change_passwords\', \'1\');\n		register_callback(array($this, \'pane\'), \'author_ui\', \'extend_detail_form\');\n		register_callback(array($this, \'save\'), \'admin\', \'author_save\');\n		register_callback(array($this, \'remove_newpass\'), \'admin_side\', \'head_end\');\n	}\n	\n	/**\n	 * Removes the \"Change your password\" button\n	 */\n	\n	public function remove_newpass() {\n		\n		global $event;\n		\n		if($event != \'admin\' || !has_privs(\'admin.edit\')) {\n			return;\n		}\n		\n		$js = <<<EOF\n			$(document).ready(function() {\n				$(\'#users_control a\').filter(function() {\n					return $(this).attr(\'href\') === \'?event=admin&step=new_pass_form\';\n				}).remove();\n			});\nEOF;\n\n		echo script_js($js);\n	}\n\n	/**\n	 * Adds options to the Users panel\n	 */\n	\n	public function pane($event, $step, $void, $r) {\n		\n		global $theme, $txp_user;\n		\n		if(!$r || !isset($r[\'user_id\'])) {\n			return;\n		}\n		\n		if(!has_privs(\'rah_change_passwords\') && $txp_user !== $r[\'name\']) {\n			return;\n		}\n		\n		$msg = escape_js($theme->announce_async(array(gTxt(\'rah_change_passwords_confirm_error\'), E_ERROR)));\n		\n		$js = <<<EOF\n			$(document).ready(function(){\n				$(\'#user_edit, #rah_change_passwords_confirm, #rah_change_passwords_confirm_pass\').attr(\'autocomplete\', \'off\');\n				\n				function validate_pass() {\n					var form = {\n						pass : $(\'#rah_change_passwords_pass\').val(),\n						conf : $(\'#rah_change_passwords_confirm\').val()\n					};\n					\n					if(form.pass === \'\' && form.conf === \'\') {\n						return true;\n					}\n					\n					return (form.pass === form.conf);\n				}\n\n				$(\'#user_edit\').submit(function(){\n					if(!validate_pass()) {\n						$.globalEval(\"{$msg}\");\n						return false;\n					}\n				});\n			});\nEOF;\n\n		return \n			tag(gTxt(\'rah_change_passwords\'), \'h3\').n.\n			\n			inputLabel(__CLASS__.\'_pass\', fInput(\'password\', __CLASS__.\'_pass\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', __CLASS__.\'_pass\'), __CLASS__.\'_pass\').n.\n				\n			inputLabel(__CLASS__.\'_confirm\', fInput(\'password\', __CLASS__.\'_confirm\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', __CLASS__.\'_confirm\'), __CLASS__.\'_confirm\').n.\n			\n			inputLabel(__CLASS__.\'_email_pass\', yesnoradio(__CLASS__.\'_email_pass\', 0, \'\', __CLASS__.\'_email_pass\'), \'\', __CLASS__.\'_email_pass\').n.\n			\n			($txp_user !== $r[\'name\'] ?\n				inputLabel(__CLASS__.\'_reset_session\', yesnoradio(__CLASS__.\'_reset_session\', 0, \'\', __CLASS__.\'_reset_session\'), \'\', __CLASS__.\'_reset_session\').n : \'\'\n			).\n			\n			script_js($js);\n	}\n\n	/**\n	 * Changes a password\n	 */\n\n	public function save() {\n		\n		extract(doSlash(psa(array(\n			\'user_id\',\n		))));\n		\n		global $sitename, $txp_user, $theme;\n		\n		foreach(array(\'pass\', \'confirm\', \'email_pass\', \'reset_session\') as $name) {\n			$$name = ps(__CLASS__.\'_\'.$name);\n		}\n		\n		if(!$user_id) {\n			return;\n		}\n		\n		$rs = \n			safe_row(\n				\'email, name\',\n				\'txp_users\',\n				\"user_id=\'\".doSlash($user_id).\"\' LIMIT 0, 1\"\n			);\n		\n		if(!$rs || (!has_privs(\'rah_change_passwords\') && $txp_user !== $rs[\'name\'])) {\n			return;\n		}\n		\n		$sql = array();\n		\n		if($reset_session && $txp_user !== $rs[\'name\']) {\n			$sql[] = \"nonce=\'\".doSlash(md5(uniqid(mt_rand(), TRUE))).\"\'\";\n		}\n		\n		if($pass) {\n		\n			if($pass !== $confirm) {\n				echo $theme->announce(array(gTxt(\'rah_change_passwords_confirm_error\'), E_ERROR));\n				return;\n			}\n			\n			include_once txpath.\'/include/txp_auth.php\';\n			$sql[] = \"pass=\'\".doSlash(txp_hash_password($pass)).\"\'\";\n		}\n		\n		if(!$sql) {\n			return;\n		}\n		\n		if(\n			safe_update(\n				\'txp_users\',\n				implode(\',\', $sql),\n				\"user_id=\'\".doSlash($user_id).\"\'\"\n			) === false\n		) {\n			echo $theme->announce(array(gTxt(\'rah_change_passwords_update_failed\'), E_ERROR));\n			return;\n		}\n		\n		if(!$pass) {\n			return;\n		}\n		\n		if(!$email_pass) {\n			echo $theme->announce(gTxt(\'rah_change_passwords_changed\'));\n			return;\n		}\n		\n		extract($rs);\n		\n		$message = \n			gTxt(\'greeting\').\' \'.$name.\',\'.n.n.\n			gTxt(\'your_password_is\').\': \'.$pass.n.n.\n			gTxt(\'log_in_at\').\': \'.hu.\'textpattern/index.php\'\n		;\n\n		if(txpMail($email, \"[$sitename] \".gTxt(\'your_new_password\'), $message) === false) {\n			echo $theme->announce(array(gTxt(\'rah_change_passwords_mailing_failed\', array(\'{email}\' => $email)), E_ERROR));\n			return;\n		}\n\n		echo $theme->announce(gTxt(\'rah_change_passwords_mailed\', array(\'{email}\' => $email)));\n	}\n}\n\n', '\n\n/**\n * Rah_change_passwords plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_change_passwords\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_change_passwords();\n\nclass rah_change_passwords {\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'rah_change_passwords\', \'1\');\n		register_callback(array($this, \'pane\'), \'author_ui\', \'extend_detail_form\');\n		register_callback(array($this, \'save\'), \'admin\', \'author_save\');\n		register_callback(array($this, \'remove_newpass\'), \'admin_side\', \'head_end\');\n	}\n	\n	/**\n	 * Removes the \"Change your password\" button\n	 */\n	\n	public function remove_newpass() {\n		\n		global $event;\n		\n		if($event != \'admin\' || !has_privs(\'admin.edit\')) {\n			return;\n		}\n		\n		$js = <<<EOF\n			$(document).ready(function() {\n				$(\'#users_control a\').filter(function() {\n					return $(this).attr(\'href\') === \'?event=admin&step=new_pass_form\';\n				}).remove();\n			});\nEOF;\n\n		echo script_js($js);\n	}\n\n	/**\n	 * Adds options to the Users panel\n	 */\n	\n	public function pane($event, $step, $void, $r) {\n		\n		global $theme, $txp_user;\n		\n		if(!$r || !isset($r[\'user_id\'])) {\n			return;\n		}\n		\n		if(!has_privs(\'rah_change_passwords\') && $txp_user !== $r[\'name\']) {\n			return;\n		}\n		\n		$msg = escape_js($theme->announce_async(array(gTxt(\'rah_change_passwords_confirm_error\'), E_ERROR)));\n		\n		$js = <<<EOF\n			$(document).ready(function(){\n				$(\'#user_edit, #rah_change_passwords_confirm, #rah_change_passwords_confirm_pass\').attr(\'autocomplete\', \'off\');\n				\n				function validate_pass() {\n					var form = {\n						pass : $(\'#rah_change_passwords_pass\').val(),\n						conf : $(\'#rah_change_passwords_confirm\').val()\n					};\n					\n					if(form.pass === \'\' && form.conf === \'\') {\n						return true;\n					}\n					\n					return (form.pass === form.conf);\n				}\n\n				$(\'#user_edit\').submit(function(){\n					if(!validate_pass()) {\n						$.globalEval(\"{$msg}\");\n						return false;\n					}\n				});\n			});\nEOF;\n\n		return \n			tag(gTxt(\'rah_change_passwords\'), \'h3\').n.\n			\n			inputLabel(__CLASS__.\'_pass\', fInput(\'password\', __CLASS__.\'_pass\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', __CLASS__.\'_pass\'), __CLASS__.\'_pass\').n.\n				\n			inputLabel(__CLASS__.\'_confirm\', fInput(\'password\', __CLASS__.\'_confirm\', \'\', \'\', \'\', \'\', INPUT_REGULAR, \'\', __CLASS__.\'_confirm\'), __CLASS__.\'_confirm\').n.\n			\n			inputLabel(__CLASS__.\'_email_pass\', yesnoradio(__CLASS__.\'_email_pass\', 0, \'\', __CLASS__.\'_email_pass\'), \'\', __CLASS__.\'_email_pass\').n.\n			\n			($txp_user !== $r[\'name\'] ?\n				inputLabel(__CLASS__.\'_reset_session\', yesnoradio(__CLASS__.\'_reset_session\', 0, \'\', __CLASS__.\'_reset_session\'), \'\', __CLASS__.\'_reset_session\').n : \'\'\n			).\n			\n			script_js($js);\n	}\n\n	/**\n	 * Changes a password\n	 */\n\n	public function save() {\n		\n		extract(doSlash(psa(array(\n			\'user_id\',\n		))));\n		\n		global $sitename, $txp_user, $theme;\n		\n		foreach(array(\'pass\', \'confirm\', \'email_pass\', \'reset_session\') as $name) {\n			$$name = ps(__CLASS__.\'_\'.$name);\n		}\n		\n		if(!$user_id) {\n			return;\n		}\n		\n		$rs = \n			safe_row(\n				\'email, name\',\n				\'txp_users\',\n				\"user_id=\'\".doSlash($user_id).\"\' LIMIT 0, 1\"\n			);\n		\n		if(!$rs || (!has_privs(\'rah_change_passwords\') && $txp_user !== $rs[\'name\'])) {\n			return;\n		}\n		\n		$sql = array();\n		\n		if($reset_session && $txp_user !== $rs[\'name\']) {\n			$sql[] = \"nonce=\'\".doSlash(md5(uniqid(mt_rand(), TRUE))).\"\'\";\n		}\n		\n		if($pass) {\n		\n			if($pass !== $confirm) {\n				echo $theme->announce(array(gTxt(\'rah_change_passwords_confirm_error\'), E_ERROR));\n				return;\n			}\n			\n			include_once txpath.\'/include/txp_auth.php\';\n			$sql[] = \"pass=\'\".doSlash(txp_hash_password($pass)).\"\'\";\n		}\n		\n		if(!$sql) {\n			return;\n		}\n		\n		if(\n			safe_update(\n				\'txp_users\',\n				implode(\',\', $sql),\n				\"user_id=\'\".doSlash($user_id).\"\'\"\n			) === false\n		) {\n			echo $theme->announce(array(gTxt(\'rah_change_passwords_update_failed\'), E_ERROR));\n			return;\n		}\n		\n		if(!$pass) {\n			return;\n		}\n		\n		if(!$email_pass) {\n			echo $theme->announce(gTxt(\'rah_change_passwords_changed\'));\n			return;\n		}\n		\n		extract($rs);\n		\n		$message = \n			gTxt(\'greeting\').\' \'.$name.\',\'.n.n.\n			gTxt(\'your_password_is\').\': \'.$pass.n.n.\n			gTxt(\'log_in_at\').\': \'.hu.\'textpattern/index.php\'\n		;\n\n		if(txpMail($email, \"[$sitename] \".gTxt(\'your_new_password\'), $message) === false) {\n			echo $theme->announce(array(gTxt(\'rah_change_passwords_mailing_failed\', array(\'{email}\' => $email)), E_ERROR));\n			return;\n		}\n\n		echo $theme->announce(gTxt(\'rah_change_passwords_mailed\', array(\'{email}\' => $email)));\n	}\n}\n\n', '36973c5ef5360e023e880a5a5fc883b0', '#@admin\n#Default language, en-us, en-gb\nrah_change_passwords => Change password\nrah_change_passwords_pass => New password\nrah_change_passwords_confirm => Confirm new password\nrah_change_passwords_email_pass => Email the password?\nrah_change_passwords_reset_session => Reset session?\nrah_change_passwords_confirm_error => Passwords do not match.\nrah_change_passwords_update_failed => Saving changes failed.\nrah_change_passwords_changed => Password changed.\nrah_change_passwords_mailed => Password changed and sent to <strong>{email}</strong>.\nrah_change_passwords_mailing_failed => Mailing password to <strong>{email}</strong> failed.\n#@admin\n#@language fi-fi\nrah_change_passwords => Vaihda salasana\nrah_change_passwords_pass => Uusi salasana\nrah_change_passwords_confirm => Salasana uudelleen\nrah_change_passwords_email_pass => Lähetä salasana sähköpostilla?\nrah_change_passwords_reset_session => Nollaa aktiivinen sessio?\nrah_change_passwords_confirm_error => Salasanat eivät täsmää.\nrah_change_passwords_update_failed => Muutoksien tallentaminen epäonnistui.\nrah_change_passwords_changed => Salasana vaihdettu.\nrah_change_passwords_mailed => Salasana vaihdettu ja lähetetty osoitteeseen <strong>{email}</strong>.\nrah_change_passwords_mailing_failed => Salasanan lähettäminen osoitteeseen <strong>{email}</strong> epäonnistui.', '', 3, 5, 0),
('rah_comments', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4', 'Paginated comments', '	<h1>rah_comments</h1>\n\n	<p>Allows you to paginate comments like articles. In other words, it outputs pagination and pg numbers below comments. You can style pagination with various attributes and ofcorse with a <abbr title=\"Cascading Style Sheets\"><span class=\"caps\">CSS</span></abbr>-styling magic.</p>\n\n	<ul>\n		<li>Version: 0.4</li>\n		<li>Updated: 2008/09/14 6:00 AM <span class=\"caps\">GMT</span>+2</li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#intro-and-description\">Intro and description</a></li>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes</a>\n	<ul>\n		<li><a href=\"#rah_comments\">rah_comments</a></li>\n		<li><a href=\"#rah_recent_comments\">rah_recent_comments</a></li>\n	</ul></li>\n		<li><a href=\"#example\">Example</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"intro-and-description\">Intro and description</h3>\n\n	<p>The plugin, rah_comments, works exatly same way like the in build comment listing, txp:comments, but with one addition: pagination, navigation and pagenumbers. Yes, the plugin can be used to paginate comments. You can style pagination with various attributes and ofcorse with a <abbr title=\"Cascading Style Sheets\"><span class=\"caps\">CSS</span></abbr>-styling magic. Works only in individual article pages, not in a section, nor an article list pages.</p>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Paginated comment listing for individual articles</li>\n		<li>Pagenumbers and prev &amp; next links</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<ul>\n		<li><span class=\"caps\">TXP</span> 4.0.6+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The basic method stands: paste plugin code to the plugin install textarea and run the automatic setup. Activate the plugin, and you are ready to use the new tag, <code>&lt;txp:rah_comments /&gt;</code> like others.</p>\n\n	<p>After the installation, the tag, <code>&lt;txp:rah_comments /&gt;</code>, should be used instead of the regular core comments tag, <code>&lt;txp:comments /&gt;</code> &#8211; just replace it with <code>&lt;txp:rah_comments /&gt;</code> with your desired attributes, styles and so on.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<h4 id=\"rah_comments\">rah_comments</h4>\n\n	<p>The tag is <code>&lt;txp:rah_comments /&gt;</code> and attributes for it follow:</p>\n\n	<h5>Normal comment related attributes</h5>\n\n	<p><strong>form</strong><br />\nForm to output the comments.<br />\nDefault: <code>form=&quot;comments&quot;</code> Example: <code>&quot;rah_comments&quot;</code>.</p>\n\n	<p><strong>sort</strong><br />\nHow to sort the resulting list. Available values: <code>discussid</code> (comment ID#), <code>parentid</code> (article ID#), <code>name</code>, <code>email</code>, <code>web</code>, <code>ip</code> (IP address), <code>posted</code>, <code>message</code>, <code>rand()</code> (random).<br />\nDefault: <code>sort=&quot;posted asc&quot;</code> Example: <code>&quot;name desc&quot;</code>.</p>\n\n	<p><strong>wraptag</strong><br />\n(X)HTML tag (without brackets) to wrap around the comment list.<br />\nDefault: <code>wraptag=&quot;&quot;</code> (unset) Example: <code>&quot;ul&quot;</code>.</p>\n\n	<p><strong>break</strong><br />\n(X)HTML tag (without brackets) or string used to separate comments.<br />\nDefault: <code>break=&quot;&quot;</code> (unset) Example: <code>&quot;li&quot;</code>.</p>\n\n	<p><strong>class</strong><br />\n(X)HTML class attribute to be applied to wraptag.<br />\nDefault: <code>class=&quot;&quot;</code> Example: <code>&quot;comment&quot;</code></p>\n\n	<p><strong>breakclass</strong><br />\n(X)HTML class attribute to be applied to break (when value supplied is a tag).<br />\nDefault: <code>breakclass=&quot;&quot;</code> (unset) Example: <code>&quot;comment&quot;</code>.</p>\n\n	<p><strong>offset</strong><br />\nThe number of comments to skip at the first page. Should always be zero (0).<br />\nDefault: <code>offset=&quot;0&quot;</code> Example: <code>&quot;5&quot;</code>.</p>\n\n	<p><strong>limit</strong><br />\nThe number of comments to display per page.<br />\nDefault: <code>limit=&quot;10&quot;</code> Example: <code>&quot;5&quot;</code>.</p>\n\n	<h5>Pagination related attributes</h5>\n\n	<p><strong>prepend</strong><br />\nShow the page and navigation links before comments.<br />\nDefault: <code>prepend=&quot;1&quot;</code> (yes) Example: <code>&quot;&quot;</code> (no).</p>\n\n	<p><strong>append</strong><br />\nShow the page and navigation links after comment list.<br />\nDefault: <code>append=&quot;1&quot;</code> (yes) Example: <code>&quot;&quot;</code> (no).</p>\n\n	<p><strong>pg_wraptag</strong><br />\n(X)HTML tag (without brackets) to wrap around the pagination links and numbers. Default is unset.<br />\nDefault: <code>pg_wraptag=&quot;ul&quot;</code> Example: <code>&quot;p&quot;</code>.</p>\n\n	<p><strong>pg_break</strong><br />\n(X)HTML tag (without brackets) or string used to separate pagination links. Default is unset.<br />\nDefault: <code>pg_wraptag=&quot;li&quot;</code> Example: <code>&quot;&quot;</code>.</p>\n\n	<p><strong>pg_class</strong><br />\n(X)HTML class attribute to be applied to <code>pg_wraptag</code> element.<br />\nDefault: <code>class=&quot;&quot;</code> Example: <code>&quot;pg-class&quot;</code>.</p>\n\n	<p><strong>pg_breakclass</strong><br />\n(X)HTML class attribute to be applied to <code>pg_break</code> element.<br />\nDefault: <code>class=&quot;&quot;</code> Example: <code>&quot;myclass&quot;</code>.</p>\n\n	<p><strong>label_next</strong><br />\nLabel text applied for next link.<br />\nDefault: <code>label_next=&quot;&gt;&gt;&quot;</code> Example: <code>&quot;Next comments&quot;</code>.</p>\n\n	<p><strong>label_prev</strong><br />\nLabel text applied for prev link.<br />\nDefault: <code>label_prev=&quot;&lt;&lt;&quot;</code> Example: <code>&quot;Prev comments&quot;</code>.</p>\n\n	<p><strong>class_next</strong><br />\n(X)HTML class attribute to be applied to next link.<br />\nDefault: <code>class_next=&quot;&quot;</code> Example: <code>&quot;myclass&quot;</code>.</p>\n\n	<p><strong>class_prev</strong><br />\n(X)HTML class attribute to be applied to prev link.<br />\nDefault: <code>class_prev=&quot;&quot;</code> Example: <code>&quot;myclass&quot;</code>.</p>\n\n	<p><strong>showalways</strong><br />\nShow page number links always, even if there is only a one page.<br />\nDefault: <code>showalways=&quot;1&quot;</code> (yes) Example: <code>&quot;&quot;</code> (no).</p>\n\n	<h4 id=\"rah_recent_comments\">rah_recent_comments</h4>\n\n	<p>Outputs list of recent comments, like the default inbuild <code>recent_comments</code>. The tag is <code>&lt;txp:rah_recent_comments /&gt;</code> and attributes for it follow:</p>\n\n	<p><strong>comments_limit</strong><br />\nSet this to be same as the <code>limit</code>-value in the <code>&lt;txp:rah_comments /&gt;</code>. This tells to the tag how many comments there is in a one page.<br />\nDefault: <code>comments_limit=&quot;10&quot;</code> Example: <code>&quot;5&quot;</code></p>\n\n	<p><strong>comments_sort</strong><br />\nSet this to be same as the <code>sort</code>-value in the <code>&lt;txp:rah_comments /&gt;</code>. This tells to the tag in which order the comments are.<br />\nDefault: <code>comments_sort=&quot;posted asc&quot;</code> Example: <code>&quot;posted desc&quot;</code></p>\n\n	<p><strong>wraptag</strong><br />\n(X)HTML tag (without brackets) to wrap around the comment list.<br />\nDefault: <code>wraptag=&quot;&quot;</code> (unset) Example: <code>&quot;ul&quot;</code>.</p>\n\n	<p><strong>break</strong><br />\n(X)HTML tag (without brackets) or string used to separate comments.<br />\nDefault: <code>break=&quot;br&quot;</code> Example: <code>&quot;&quot;</code>.</p>\n\n	<p><strong>class</strong><br />\n(X)HTML class attribute to be applied to wraptag.<br />\nDefault: <code>class=&quot;recent_comments&quot;</code> Example: <code>&quot;comment&quot;</code></p>\n\n	<p><strong>label</strong><br />\nLabel for the top of the list.<br />\nDefault <code>label=&quot;&quot;</code> (unset) Example: <code>&quot;recent_comments&quot;</code></p>\n\n	<p><strong>labeltag</strong><br />\n(X)HTML tag (without brackets) to wrap around label.<br />\nDefault: <code>labeltag=&quot;&quot;</code> (unset) Example: <code>&quot;h3&quot;</code>.</p>\n\n	<p><strong>limit</strong><br />\nNumber of comments to display.<br />\nDefault: <code>limit=&quot;10&quot;</code> Example: <code>&quot;5&quot;</code>.</p>\n\n	<h3 id=\"example\">Example</h3>\n\n	<p><code>&lt;txp:rah_comments wraptag=&quot;div&quot; pg_wraptag=&quot;p&quot; /&gt;</code></p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added <code>&lt;txp:rah_recent_comments /&gt;</code> tag.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Removed attributes: <code>break_nav</code> and <code>break_pages</code>.</li>\n		<li>Fixed messy url mode&#8217;s error: now works fully on messy.</li>\n		<li>Added attributes: <code>pg_break</code>, <code>pg_break_class</code>, <code>prepend</code> and <code>append</code>.</li>\n		<li>Now works in article lists, same way like <code>&lt;txp:comments /&gt;</code>.</li>\n		<li>Better documention.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Some minor bug fixes.</li>\n		<li>Removed some typos from documention.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>', '	################\n	#\n	#	rah_comments-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	################\n\n	function rah_comments($atts) {\n		global $thisarticle;\n		extract(lAtts(array(\n			\'form\' => \'comments\',\n			\'sort\' => \'posted asc\',\n			\'wraptag\' => \'\',\n			\'break\' => \'\',\n			\'class\' => \'\',\n			\'breakclass\' => \'\',\n			\'offset\' => \'\',\n			\'limit\' => 10,\n			\'prepend\' => 1,\n			\'append\' => 1,\n			\'pg_wraptag\' => \'ul\',\n			\'pg_break\' => \'li\',\n			\'pg_class\' => \'\',\n			\'pg_breakclass\' => \'\',\n			\'label_next\' => \'&gt;&gt;\',\n			\'label_prev\' => \'&lt;&lt;\',\n			\'class_next\' => \'\',\n			\'class_prev\' => \'\',\n			\'showalways\' => 1,\n		),$atts));\n		$number = array();\n		if(is_numeric(gps(\'pg\'))) $pg = gps(\'pg\');\n		if(empty($pg)) $pg = 1;\n		$i = 0;\n		$prevpg = $pg-1;\n		$nextpg = $pg+1;\n		$next = \'\';\n		$prev = \'\';\n		$total = $thisarticle[\'comments_count\'];\n		$totalpg = $total/$limit;\n		$totalpg = ceil($totalpg);\n		$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&amp;\' : \'?\';\n		if(1<$pg && 1<$totalpg) $number[] = doTag($label_prev,\'a\',$class_prev,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$prevpg.\'\"\');\n		while ($i<$totalpg) {\n			$i++;\n			$number[] = doTag($i,\'a\',\'page-\'.$i,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$i.\'\"\');\n		}\n		if($pg<$totalpg) $number[] = doTag($label_next,\'a\',$class_next,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$nextpg.\'\"\');\n		$pagination = ($showalways != 1 && $totalpg < 2) ? \'\' : doWrap($number, $pg_wraptag, $pg_break, $pg_class, $pg_breakclass);\n		if(1<$pg){\n			$offset = $limit*$pg;\n			$offset = $offset-$limit;\n		}\n		return (($prepend) ? $pagination : \'\').comments(array(\n			\'sort\' => $sort,\n			\'form\' => $form,\n			\'wraptag\' => $wraptag,\n			\'breakclass\' => $breakclass,\n			\'break\' => $break,\n			\'class\' => $class,\n			\'offset\' => $offset,\n			\'limit\' => $limit\n		)).(($append) ? $pagination : \'\');\n	}\n\n	function rah_recent_comments($atts) {\n		extract(lAtts(array(\n			\'comments_limit\' => 10,\n			\'comments_sort\' => \'posted asc\',\n			\'break\' => \'br\',\n			\'class\' => \'recent_comments\',\n			\'label\' => \'\',\n			\'labeltag\' => \'\',\n			\'limit\' => 10,\n			\'sort\' => \'posted desc\',\n			\'wraptag\' => \'\'\n		),$atts));\n\n		$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&amp;\' : \'?\';\n		$sort = preg_replace(\'/\\bposted\\b/\', \'d.posted\', $sort);\n		$rs = startRows(\'select d.name, d.parentid, d.discussid, t.ID as thisid, unix_timestamp(t.Posted) as posted, t.Title as title, t.Section as section, t.url_title \'.\'from \'. safe_pfx(\'txp_discuss\') .\' as d inner join \'. safe_pfx(\'textpattern\') .\' as t on d.parentid = t.ID \'.\'where t.Status >= 4 and d.visible = \'.VISIBLE.\' order by \'.doSlash($sort).\' limit 0,\'.intval($limit));\n\n		if ($rs) {\n			$out = array();\n			while ($c = nextRow($rs)) {\n				$position = 0;\n				$pg = 0;	\n				$rs2 = safe_rows_start(\'discussid\',\'txp_discuss\',\"visible = \".VISIBLE.\" and parentid = \".$c[\'parentid\'].\" order by $comments_sort\");\n				while ($x = nextRow($rs2)) {\n					extract($x);\n					$position++;\n					if($discussid == $c[\'discussid\']) break;\n				}\n				$pg = ceil($position/$comments_limit);\n				$out[] = href($c[\'name\'].\' (\'.escape_title($c[\'title\']).\')\',permlinkurl($c).$sep.\'pg=\'.$pg.\'#c\'.$c[\'discussid\']);\n			}\n			if ($out) return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n		}\n		return \'\';\n	}', '	################\n	#\n	#	rah_comments-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	################\n\n	function rah_comments($atts) {\n		global $thisarticle;\n		extract(lAtts(array(\n			\'form\' => \'comments\',\n			\'sort\' => \'posted asc\',\n			\'wraptag\' => \'\',\n			\'break\' => \'\',\n			\'class\' => \'\',\n			\'breakclass\' => \'\',\n			\'offset\' => \'\',\n			\'limit\' => 10,\n			\'prepend\' => 1,\n			\'append\' => 1,\n			\'pg_wraptag\' => \'ul\',\n			\'pg_break\' => \'li\',\n			\'pg_class\' => \'\',\n			\'pg_breakclass\' => \'\',\n			\'label_next\' => \'&gt;&gt;\',\n			\'label_prev\' => \'&lt;&lt;\',\n			\'class_next\' => \'\',\n			\'class_prev\' => \'\',\n			\'showalways\' => 1,\n		),$atts));\n		$number = array();\n		if(is_numeric(gps(\'pg\'))) $pg = gps(\'pg\');\n		if(empty($pg)) $pg = 1;\n		$i = 0;\n		$prevpg = $pg-1;\n		$nextpg = $pg+1;\n		$next = \'\';\n		$prev = \'\';\n		$total = $thisarticle[\'comments_count\'];\n		$totalpg = $total/$limit;\n		$totalpg = ceil($totalpg);\n		$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&amp;\' : \'?\';\n		if(1<$pg && 1<$totalpg) $number[] = doTag($label_prev,\'a\',$class_prev,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$prevpg.\'\"\');\n		while ($i<$totalpg) {\n			$i++;\n			$number[] = doTag($i,\'a\',\'page-\'.$i,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$i.\'\"\');\n		}\n		if($pg<$totalpg) $number[] = doTag($label_next,\'a\',$class_next,\' href=\"\'.permlink(array()).$sep.\'pg=\'.$nextpg.\'\"\');\n		$pagination = ($showalways != 1 && $totalpg < 2) ? \'\' : doWrap($number, $pg_wraptag, $pg_break, $pg_class, $pg_breakclass);\n		if(1<$pg){\n			$offset = $limit*$pg;\n			$offset = $offset-$limit;\n		}\n		return (($prepend) ? $pagination : \'\').comments(array(\n			\'sort\' => $sort,\n			\'form\' => $form,\n			\'wraptag\' => $wraptag,\n			\'breakclass\' => $breakclass,\n			\'break\' => $break,\n			\'class\' => $class,\n			\'offset\' => $offset,\n			\'limit\' => $limit\n		)).(($append) ? $pagination : \'\');\n	}\n\n	function rah_recent_comments($atts) {\n		extract(lAtts(array(\n			\'comments_limit\' => 10,\n			\'comments_sort\' => \'posted asc\',\n			\'break\' => \'br\',\n			\'class\' => \'recent_comments\',\n			\'label\' => \'\',\n			\'labeltag\' => \'\',\n			\'limit\' => 10,\n			\'sort\' => \'posted desc\',\n			\'wraptag\' => \'\'\n		),$atts));\n\n		$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&amp;\' : \'?\';\n		$sort = preg_replace(\'/\\bposted\\b/\', \'d.posted\', $sort);\n		$rs = startRows(\'select d.name, d.parentid, d.discussid, t.ID as thisid, unix_timestamp(t.Posted) as posted, t.Title as title, t.Section as section, t.url_title \'.\'from \'. safe_pfx(\'txp_discuss\') .\' as d inner join \'. safe_pfx(\'textpattern\') .\' as t on d.parentid = t.ID \'.\'where t.Status >= 4 and d.visible = \'.VISIBLE.\' order by \'.doSlash($sort).\' limit 0,\'.intval($limit));\n\n		if ($rs) {\n			$out = array();\n			while ($c = nextRow($rs)) {\n				$position = 0;\n				$pg = 0;	\n				$rs2 = safe_rows_start(\'discussid\',\'txp_discuss\',\"visible = \".VISIBLE.\" and parentid = \".$c[\'parentid\'].\" order by $comments_sort\");\n				while ($x = nextRow($rs2)) {\n					extract($x);\n					$position++;\n					if($discussid == $c[\'discussid\']) break;\n				}\n				$pg = ceil($position/$comments_limit);\n				$out[] = href($c[\'name\'].\' (\'.escape_title($c[\'title\']).\')\',permlinkurl($c).$sep.\'pg=\'.$pg.\'#c\'.$c[\'discussid\']);\n			}\n			if ($out) return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n		}\n		return \'\';\n	}', 'ce88b0f55334ba05c476ac02088da593', '', '', 0, 5, 0),
('rah_comment_form', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.3', 'Comment submitting without comment previews', '	<h1>rah_comment_form</h1>\n\n	<p>Comment submiting with out comment preview, instead we use alternative spam protection: registeration. Registeration and login can be done with <a href=\"http://forum.textpattern.com/viewtopic.php?id=8799\">ign_password_protect</a> and <a href=\"http://forum.textpattern.com/viewtopic.php?id=8520\">mem_self_register</a> &#8211; with out these plugins, this plugin, rah_comment_form is &#8220;useless&#8221;, or it just doesn&#8217;t work. Atleast, in words of code, rah_comment_form requires <a href=\"http://forum.textpattern.com/viewtopic.php?id=8799\">ign_password_protect-plugin.</a></p>\n\n	<ul>\n		<li>Version: 0.3</li>\n		<li>Updated: 2009/08/09 3:50 PM <span class=\"caps\">GMT</span>+3</li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#intro-and-description\">Intro and description</a></li>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes and tags</a></li>\n		<li><a href=\"#examples\">Examples</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n		<li><a href=\"#thanks-to\">Thanks to</a></li>\n	</ul>\n\n	<h3 id=\"intro-and-description\">Intro and description</h3>\n\n	<p>A Textpattern plugin used to remove the comment preview and allowing commenting only for the registered users. This plugin requires <a href=\"http://forum.textpattern.com/viewtopic.php?id=8799\">ign_password_protect</a> (allows loging in) and also <a href=\"http://forum.textpattern.com/viewtopic.php?id=8520\">mem_self_register</a> is kinda important to be able to automaticate the registeration. To be able to comment or see the commenting form, user must be logged in via ign_password_protect.</p>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Removes comment preview.</li>\n		<li>Protection: only registered users can comment.</li>\n		<li>Comes with simple tag that outputs form &#8211; can also be used as container tag.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Minimum requirements:</p>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 4.3.0</li>\n		<li>Textpattern 4.0.4+</li>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=8799\">ign_password_protect</a></li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 5.1.2+</li>\n		<li>Textpattern 4.0.6+</li>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=8799\">ign_password_protect</a></li>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=8520\">mem_self_register</a></li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use new tags that plugin includes like others.</p>\n\n	<p>Basically the plugin&#8217;s tags can only be used in individual article page, &#8216;cause those output commenting form. The tag, <code>&lt;txp:rah_comment_form /&gt;</code>, would ideally be used instead of the stannard <code>&lt;txp:comments_form /&gt;</code> that is by default located inside <code>comments_display</code> form.</p>\n\n	<h3 id=\"attributes\">Attributes and tags</h3>\n\n	<p>The plugin comes with two tags:</p>\n\n	<h4><code>&lt;txp:rah_comment_form /&gt;</code></h4>\n\n	<p>This tag, <code>&lt;txp:rah_comment_form /&gt;</code>, outputs the commenting form. The tag can be used as container or as a sinle tag when it uses premade <span class=\"caps\">XHTML</span> code.</p>\n\n	<h4><code>&lt;txp:rah_comment_message /&gt;</code></h4>\n\n	<p>Outputs comment message textarea. Can be used inside <code>&lt;txp:rah_comment_form /&gt;</code> when it is used as container tag. In example:</p>\n\n<pre><code>&lt;txp:rah_comment_form&gt;&lt;txp:rah_comment_message /&gt;&lt;/txp:rah_comment_form&gt;\n</code></pre>\n\n	<h3 id=\"examples\">Examples</h3>\n\n	<h4>Example #1</h4>\n\n	<p>Code: <code>&lt;txp:rah_comment_form /&gt;</code></p>\n\n	<p>Outputs:</p>\n\n<pre><code>&lt;form id=&quot;rah_comment_form&quot; action=&quot;&quot; method=&quot;post&quot;&gt;\n	&lt;ul&gt;\n		&lt;li class=&quot;label&quot;&gt;&lt;label for=&quot;rah_message&quot;&gt;Message&lt;/label&gt;&lt;/li&gt;\n		&lt;li class=&quot;textarea&quot;&gt;&lt;textarea name=&quot;rah_comment_message&quot; id=&quot;rah_message&quot; rows=&quot;6&quot; cols=&quot;20&quot;&gt;&lt;/textarea&gt;&lt;/li&gt;\n		&lt;li class=&quot;button&quot;&gt;&lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;&lt;/li&gt;\n	&lt;/ul&gt;\n&lt;/form&gt;\n</code></pre>\n\n	<h4>Example #2</h4>\n\n<pre><code>&lt;txp:rah_comment_form&gt;&lt;txp:rah_comment_message /&gt;&lt;/txp:rah_comment_form&gt;\n</code></pre>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Bug fix: Now uses the alternative container markup.</li>\n		<li>Bug fix: Changes form&#8217;s <code>action</code> based on permlink mode.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Bug fix: Now updates comments_count in the textpattern-table.\n	<ul>\n		<li>Textpattern&#8217;s core in build tags do use it to check if article has comments.</li>\n	</ul></li>\n		<li>Works on both: messy and clean <span class=\"caps\">URL</span>s.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>\n\n	<h3 id=\"thanks-to\">Thanks to</h3>\n\n	<p>Thanks to Manfre for making mem_self_register and thanks to igner for making ign_password_protect. Great plugins, dudes.</p>', '	##################\r\n	#\r\n	#	rah_comment_form-plugin for Textpattern\r\n	#	version 0.3\r\n	#	by Jukka Svahn\r\n	#	http://rahforum.biz\r\n	#\r\n	###################\r\n\r\n	function rah_comment_form($atts=array(),$thing=\'\') {\r\n		global $is_article_list, $ign_user;\r\n		if($ign_user) {\r\n			$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&\' : \'?\';\r\n			if($is_article_list == false) {\r\n				$form =\r\n					\'	<form id=\"rah_comment_form\" action=\"\'.permlink(array()).$sep.\'rah_comment_save=1\" method=\"post\">\'.n.\r\n					(($thing) ? parse($thing) : \r\n					\'		<ul>\'.n.\r\n					\'			<li class=\"label\"><label for=\"rah_message\">\'.gTxt(\'comment_message\').\'</label></li>\'.n.\r\n					\'			<li class=\"textarea\"><textarea name=\"rah_comment_message\" id=\"rah_message\" rows=\"6\" cols=\"20\"></textarea></li>\'.n.\r\n					\'			<li class=\"button\"><button type=\"submit\">\'.gTxt(\'submit\').\'</button></li>\'.n.\r\n					\'		</ul>\'.n).\r\n					\'	</form>\'.n;\r\n				\r\n				if(gps(\'rah_comment_save\') == \'1\' && ps(\'rah_comment_message\')){\r\n					$id = article_id(array());\r\n					$ip = doSlash(serverset(\'REMOTE_ADDR\'));\r\n					$message = ps(\'rah_comment_message\');\r\n					$message = substr(trim($message), 0, 65535);\r\n					$message = doSlash(markup_comment($message));\r\n					$name = doSlash($ign_user);\r\n\r\n					$check = safe_field(\"message\",\"txp_discuss\",\"name=\'$name\' and parentid=\'$id\' and message=\'$message\' and ip=\'$ip\'\");\r\n					if($check) header(\'Location: \'.permlink(array()));\r\n					else {\r\n						safe_insert(\r\n							\"txp_discuss\",\"\r\n								parentid = $id,\r\n								name = \'$name\',\r\n								ip = \'$ip\',\r\n								message = \'$message\',\r\n								visible = 1,\r\n								posted = now()\"\r\n						);\r\n						$count = fetch(\'comments_count\',\'textpattern\',\'ID\',$id);\r\n						$count = ($count) ? $count : 0;\r\n						$count = $count+1;\r\n						safe_update(\r\n							\'textpattern\',\r\n							\"comments_count=\'\".doSlash($count).\"\'\",\r\n							\"ID=\'$id\'\"\r\n						);\r\n\r\n						//Lets redirect\r\n\r\n						$cid = safe_field(\"discussid\",\"txp_discuss\",\"name=\'$name\' and parentid=\'$id\' and message=\'$message\' and ip=\'$ip\'\");\r\n						header(\'Location: \'.permlink(array()).$sep.\'commented=1#c\'.$cid);\r\n					}\r\n				}\r\n				return $form;\r\n			}\r\n		}\r\n	}\r\n\r\n	function rah_comment_message() {\r\n		return \'<textarea name=\"rah_comment_message\" rows=\"6\" cols=\"20\"></textarea>\';\r\n	}', '	##################\r\n	#\r\n	#	rah_comment_form-plugin for Textpattern\r\n	#	version 0.3\r\n	#	by Jukka Svahn\r\n	#	http://rahforum.biz\r\n	#\r\n	###################\r\n\r\n	function rah_comment_form($atts=array(),$thing=\'\') {\r\n		global $is_article_list, $ign_user;\r\n		if($ign_user) {\r\n			$sep = (safe_field(\'val\',\'txp_prefs\',\"name=\'permlink_mode\'\") == \'messy\') ? \'&\' : \'?\';\r\n			if($is_article_list == false) {\r\n				$form =\r\n					\'	<form id=\"rah_comment_form\" action=\"\'.permlink(array()).$sep.\'rah_comment_save=1\" method=\"post\">\'.n.\r\n					(($thing) ? parse($thing) : \r\n					\'		<ul>\'.n.\r\n					\'			<li class=\"label\"><label for=\"rah_message\">\'.gTxt(\'comment_message\').\'</label></li>\'.n.\r\n					\'			<li class=\"textarea\"><textarea name=\"rah_comment_message\" id=\"rah_message\" rows=\"6\" cols=\"20\"></textarea></li>\'.n.\r\n					\'			<li class=\"button\"><button type=\"submit\">\'.gTxt(\'submit\').\'</button></li>\'.n.\r\n					\'		</ul>\'.n).\r\n					\'	</form>\'.n;\r\n				\r\n				if(gps(\'rah_comment_save\') == \'1\' && ps(\'rah_comment_message\')){\r\n					$id = article_id(array());\r\n					$ip = doSlash(serverset(\'REMOTE_ADDR\'));\r\n					$message = ps(\'rah_comment_message\');\r\n					$message = substr(trim($message), 0, 65535);\r\n					$message = doSlash(markup_comment($message));\r\n					$name = doSlash($ign_user);\r\n\r\n					$check = safe_field(\"message\",\"txp_discuss\",\"name=\'$name\' and parentid=\'$id\' and message=\'$message\' and ip=\'$ip\'\");\r\n					if($check) header(\'Location: \'.permlink(array()));\r\n					else {\r\n						safe_insert(\r\n							\"txp_discuss\",\"\r\n								parentid = $id,\r\n								name = \'$name\',\r\n								ip = \'$ip\',\r\n								message = \'$message\',\r\n								visible = 1,\r\n								posted = now()\"\r\n						);\r\n						$count = fetch(\'comments_count\',\'textpattern\',\'ID\',$id);\r\n						$count = ($count) ? $count : 0;\r\n						$count = $count+1;\r\n						safe_update(\r\n							\'textpattern\',\r\n							\"comments_count=\'\".doSlash($count).\"\'\",\r\n							\"ID=\'$id\'\"\r\n						);\r\n\r\n						//Lets redirect\r\n\r\n						$cid = safe_field(\"discussid\",\"txp_discuss\",\"name=\'$name\' and parentid=\'$id\' and message=\'$message\' and ip=\'$ip\'\");\r\n						header(\'Location: \'.permlink(array()).$sep.\'commented=1#c\'.$cid);\r\n					}\r\n				}\r\n				return $form;\r\n			}\r\n		}\r\n	}\r\n\r\n	function rah_comment_message() {\r\n		return \'<textarea name=\"rah_comment_message\" rows=\"6\" cols=\"20\"></textarea>\';\r\n	}', '022ac7faf4b9a76558d887afc0fa8c94', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_comment_spam', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.7', 'Anti-spam tools to prevent comment spam', '	<h1>rah_comment_spam</h1>\n\n	<p>Rah_comment_spam provides customizable anti-spam tools for <a href=\"http://www.textpattern.com\">Textpattern&#8217;s</a> comment system. Set minimum and maximum message length, filter spam words, validate email addresses, limit posting activity and set hidden spam traps. All from simple admin-side graphical user interface, no head first dive to code.</p>\n\n	<ul>\n		<li>Version: 0.7</li>\n		<li>Updated: 2011/11/03 11:40 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation\">Installation</a></li>\n		<li><a href=\"#configuration\">Configuration</a></li>\n		<li><a href=\"#preferences\">Preferences</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Set comment message length limitations: minimum and maximum, words and characters.</li>\n		<li>Limit link posting.</li>\n		<li>Define spam words, and set the limit how many spam words are needed until comment becomes spam.</li>\n		<li>Show customizable error messages when comment is blocked.</li>\n		<li>Set a custom hidden spam trap field.</li>\n		<li>Change the spam protection method.</li>\n		<li>Set quotas, limit users&#8217; comment posting activity.</li>\n		<li>Integrates with Textpattern&#8217;s native preferences panel; no diving into code needed at all.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Rah_comment_spam&#8217;s minimum requirements:</p>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 5 or newer</li>\n		<li>Textpattern 4.2.0 or newer.</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li>Textpattern 4.4.1 or newer.</li>\n	</ul>\n\n	<h3 id=\"installation\">Installation</h3>\n\n	<ol>\n		<li>Navigate to your Textpattern installation&#8217;s Plugins panel (<em>Textpattern / Admin / Plugins</em>).</li>\n		<li>Copy and paste rah_comment_spam&#8217;s plugin package code to the plugin installer textarea and run the automated setup.</li>\n		<li>After the setup is completed, activate the plugin and you&#8217;re set.</li>\n		<li>You can customize the plugin&#8217;s settings in Preferences panel&#8217;s comment section (<em>Textpattern / Admin / Preferences</em>).</li>\n	</ol>\n\n	<h3 id=\"configuration\">Configuration</h3>\n\n	<p>Once the plugin is installed, you can find set of new preferences in your Textpattern installation&#8217;s Preferences panel. To configure rah_comment_spam&#8217;s preferences navigate to <em>Admin/Preferences</em> and take a look at the <em>Comments</em> section. If you have commenting disabled on your site, you will have to toggle the <em>Enable comments</em> option to see the section.</p>\n\n	<h3 id=\"preferences\">Preferences</h3>\n\n	<p>Looking at the Preference&#8217;s panel&#8217;s <em>Comments</em> section, you should see set of options that weren&#8217;t there before. Those are the options that the plugin offers. In total, there should be around 18 new toggle-able items. The offered plugin&#8217;s options are as follows.</p>\n\n	<p><strong>What to do comments detected as spam?</strong><br />\nThe option sets the action that is taken towards to comments flagged as a spam. There are three options you can choose from. The default option is <em>Save to moderation queue</em> which saves the comments to moderation queue. The detected comments won&#8217;t be visible to public, but are saved to moderation queue, and can be manually confirmed. The second option <em>Ignore, do not save</em> blocks the comments. The comments won&#8217;t be saved and the user is greeted with the message set in <em>Message displayed to the user when the comment was blocked</em> option. The third and last option <em>Save and flag as spam</em> saves the detected comment to the database as spam.</p>\n\n	<p><strong>Message displayed to the user when the comment was blocked</strong><br />\nSets the message that is shown to users when their comment is blocked by the filters. The message is only used when the protection method above is set to <em>Ignore, do not save</em>. With others, Textpattern&#8217;s build in language strings are used.</p>\n\n	<p><strong>List of spam words</strong><br />\nSets words/terms that are considered as something that spam would contain. Separate multiple words with commas (<code>,</code>). Used words are incase-sensitive. Example value: <code>viagra, wares, wowgold</code>.</p>\n\n	<p><strong>Search spam words from following fields</strong><br />\nSets which fields are filtered for spam words. Use comma-separated list of field names. Available values are <code>name</code>, <code>email</code>, <code>web</code> and <code>message</code>. Default option is <code>name, email, web, message</code>.</p>\n\n	<p><strong>Maximum number of spam words until the comment is considered as spam</strong><br />\nSets the maximum number of spam words a comment can have until it&#8217;s considered as spam. The lower the number, the stricter the filter is. Recommended value is between 2 to 10.</p>\n\n	<p><strong>Number of allowed <span class=\"caps\">URL</span>s</strong><br />\nOption sets comment message&#8217;s <span class=\"caps\">URL</span> limit. If a comment contains more <span class=\"caps\">URL</span>s than the options allows, the comment is considered as spam.</p>\n\n	<p><strong>Minimum number of words</strong><br />\nRequired minimum length of comment message in words. Shorter messages are considered as spam. Recommended value is <code>1</code> to <code>3</code>.</p>\n\n	<p><strong>Maximum number of words</strong><br />\nMaximum length of comment message in words. Longer messages are considered as spam. Note that message&#8217;s total size is limited to 65535 bytes (equals to around 65535 <span class=\"caps\">ASCII</span> characters).</p>\n\n	<p><strong>Minimum number of characters</strong><br />\nRequired minimum length of comment in characters. Shorter messages are considers as spam. Recommended setting is <code>1</code> to <code>10</code>.</p>\n\n	<p><strong>Maximum number of characters</strong><br />\nMaximum length of comment in characters. Longer messages are considers as spam. Note that message&#8217;s total size is limited to 65535 bytes. Valid values range from 0 to 65535 (equals to around 65535 <span class=\"caps\">ASCII</span> characters).</p>\n\n	<p><strong>Name of hidden spam trap field</strong><br />\nIf set, hidden spam trap field is added to the comment form. The field will be hidden with <span class=\"caps\">CSS</span>, and if the field is filled, the comment is considered as spam. Default value is <em>phone</em>. Empty the field to disable the spam trap.</p>\n\n	<p><strong>Use quotas, limit users&#8217; comment posting activity?</strong><br />\nIf set to <em>Yes</em>, users comment posting activity is limited. Comments going over the quota are considered as spam. Users are identified by IP addresses.</p>\n\n	<p><strong>Users&#8217; comment quota limit</strong><br />\nSets the user&#8217;s quota limit. Default is ten (<code>10</code>) comments per user.</p>\n\n	<p><strong>Count users&#8217; comments towards quota in</strong><br />\nSets which comments in which articles are counted towards the quota limit. By default user&#8217;s comments in any article are taken into count. Available settings are <em>In all articles</em>, counts comments site-wide, and <em>In current article</em> which counts only comments in the current article the new comment is being posted to.</p>\n\n	<p><strong>Quota resets every number of seconds</strong><br />\nSets the timespan used in the activity limiting. Comments older than this are not counted towards the quota limit. The time is set in seconds. For example setting the option to <code>600</code>, would count an user&#8217;s comments from the last ten minutes, and if the number goes over the quota, then the comment is considered as spam.</p>\n\n	<p><strong>Check email&#8217;s <span class=\"caps\">DNS</span> records?</strong><br />\nIf set <em>Yes</em>, email address&#8217; <span class=\"caps\">DNS</span> records are checked. If the domain is found to be non-existent, the comment is considered as spam. The feature requires <a href=\"http://php.net/manual/en/function.checkdnsrr.php\">checkdnsrr()</a> function and the server must be allowed to make outgoing connections.</p>\n\n	<p><strong>Check the time user spent writing the comment?</strong><br />\nMakes sure that the user spent set amount of time writing the comment. Instant posters will be treated as spammers. Usually it&#8217;s recommended to keep this feature disabled. It can accidentally block fast writers if the required time is set to too high, but it can also successfully block spam bots which might try to post comments instantly, without interacting with the comment form. The time is counted from the initial article page load to the comment&#8217;s submitting.</p>\n\n	<p><strong>Minimum time required to be spent writing a comment?</strong><br />\nSets the required interaction time in seconds. Default is <code>5</code>. Higher values are not recommended usability-wise. If you set the value higher than 5 seconds, consider adding JavaScript based counter to the comment form that informs the users how much they have to wait. For counters I recommend checking out <a href=\"http://plugins.jquery.com/plugin-tags/countdown\">jQuery&#8217;s plugin repository</a>.</p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Fixed: Update cached version number after running the installer.</li>\n		<li>Updated help file.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Fixed: Comment quota limit&#8217;s <em>In current article</em> option. Previously counting didn&#8217;t work when the <em>in current article</em> option was selected.</li>\n		<li>Added: language string support. The interface now is translatable using Textpacks.</li>\n		<li>Added: option to freely add any field to the list of that are checked for spam words. Used fields need to be defined in getComment().</li>\n		<li>Improved: <span class=\"caps\">UTF</span>-8 compatibility. The plugin now uses multibyte safe methods when possible.</li>\n		<li>Changed: Migrated the preferences from own admin-interface and database table, to Textpattern&#8217;s native preferences panel. Preferences are now located at <em>Admin/Preferences</em>, in the <em>Comments</em> group.</li>\n		<li>Changed: For new installations protection method now defaults to <em>moderate</em> instead of <em>blocking</em>.</li>\n		<li>Changed: For new installations, default message length limits have been increased, and the example spam words have been removed.</li>\n		<li>Now requires PHP5 or newer. <span class=\"caps\">PHP</span> version 5.3 or newer is recommended. <span class=\"caps\">PHP</span> 4 support has been dropped.</li>\n		<li>For full feature compatibility Textpattern version 4.2.0 or newer is required. Textpattern v4.4.1 or newer is recommended.</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Added option to check the time the user used to write the comment and block instant posters. <a href=\"http://forum.textpattern.com/viewtopic.php?id=34535\">As discussed here</a>.</li>\n		<li>Removed unused <span class=\"caps\">HTML</span> field ids and sizes from the backend pane.</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added: now also filters <code>https://</code>, <code>ftp://</code> and <code>ftps://</code> protocols, not just links starting with <code>http://</code>.</li>\n		<li>Changed posting limit feature&#8217;s maximum post setting from <em>&#8220;more than&#8221;</em> to <em>&#8220;equals or more than&#8221;</em>. Setting the value to zero/or leaving it empty will disable the feature.</li>\n		<li>Changed filtering method from <em>and</em> to <em>or</em>. This means, when the comment gets flagged by a filter for the first time, the spam filtering process ends instead going thru all the checks which are no longer needed. Gives slight performance boost.</li>\n		<li>From version 0.4 onwards requires <span class=\"caps\">TXP</span> 4.0.7 or newer.</li>\n		<li>Now posting limits are counted barely by MySQL. No involment of <span class=\"caps\">PHP</span>.</li>\n		<li>Merged <code>rah_comment_spam_urlcount()</code> and <code>rah_comment_spam_spamwords()</code>.</li>\n		<li>Now admin panel uses same type of sliding panels as seen in <a href=\"http://rahforum.biz/plugins/rah_sitemap\">rah_sitemap</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Added email <span class=\"caps\">DNS</span> validation.</li>\n	</ul>\n\n	<p><strong>Version 0.2.1</strong></p>\n\n	<ul>\n		<li>Made writing spam definition list less strict. Now allows double commas, spaces and so on. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=212943#p212943\">Thanks Robert.</a></li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Added user based time relative posting limit.</li>\n		<li>Fixed hidden spam trap input&#8217;s escaping.</li>\n		<li>Improved comment posting evaluating speed.</li>\n		<li>Merged install, save, update and fetch arrays.</li>\n		<li>Improved installation script.</li>\n		<li>Merged <span class=\"caps\">SQL</span> queries.</li>\n	</ul>\n\n	<p><strong>Version 0.1.3</strong></p>\n\n	<ul>\n		<li>Fixed forgotten <code>strtolower()</code>.</li>\n	</ul>\n\n	<p><strong>Version 0.1.2</strong></p>\n\n	<ul>\n		<li>Removed typo from admin panel: &#8220;Documention&#8221; to &#8220;Documentation&#8221;.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>', '	##################\n	#\n	#	rah_comment_spam-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_comment_spam_install();\n		add_privs(\'plugin_prefs.rah_comment_spam\',\'1,2\');\n		register_callback(\'rah_comment_spam_prefs\',\'plugin_prefs.rah_comment_spam\');\n		register_callback(\'rah_comment_spam_install\',\'plugin_lifecycle.rah_comment_spam\');\n	} else {\n		register_callback(\'rah_comment_spam_register\',\'comment.save\');\n		register_callback(\'rah_comment_spam_register\',\'comment.form\');\n	}\n\n/**\n	Installer\n	@param $event string Admin-side event.\n	@param $step string Admin-side, plugin-lifecycle step.\n*/\n\n	function rah_comment_spam_install($event=\'\',$step=\'\') {\n\n		if($step == \'deleted\') {\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_comment_spam_%\'\"\n			);\n			return;\n		}\n\n		global $prefs, $textarray;\n\n		/*\n			Make sure language strings are set\n		*/\n\n		foreach(\n			array(\n				\'method\' => \'What to do comments detected as spam?\',\n				\'method_block\' => \'Ignore, do not save\',\n				\'method_moderate\' => \'Save to moderation queue\',\n				\'method_spam\' => \'Save and flag as spam\',\n				\'message\' => \'Message displayed to the user when the comment was blocked\',\n				\'spamwords\' => \'List of spam words (comma-separated)\',\n				\'check\' => \'Search spam words from following fields (comma-separated)\',\n				\'maxspamwords\' => \'Maximum number of spam words until the comment is considered as spam\',\n				\'urlcount\' => \'Number of allowed URLs\',\n				\'minwords\' => \'Minimum number of words\',\n				\'maxwords\' => \'Maximum number of words\',\n				\'minchars\' => \'Minimum number of characters\',\n				\'maxchars\' => \'Maximum number of characters\',\n				\'field\' => \'Name of hidden spam trap field (empty to disable the trap)\',\n				\'commentuse\' => \'Use quotas, limit users\\\' comment posting activity?\',\n				\'commentlimit\' => \'Users\\\' comment quota limit\',\n				\'commentin\' => \'Count users\\\' comments towards quota in\',\n				\'commentin_all\' => \'In all articles\',\n				\'commentin_this\' => \'In current article\',\n				\'commenttime\' => \'Quota resets every number of seconds\',\n				\'emaildns\' => \'Check email\\\'s DNS records?\',\n				\'use_type_detect\' => \'Check the time user spent writing the comment?\',\n				\'type_interval\' => \'Minimum time required to be spent writing a comment (in seconds)?\'\n			) as $string => $translation\n		) {\n			$name = strpos($string, \'rah_\') === 0 ? $string : \'rah_comment_spam_\'.$string;\n\n			if(!isset($textarray[$name]))\n				$textarray[$name] = $translation;\n		}\n\n		$version = \'0.7\';\n\n		$current = isset($prefs[\'rah_comment_spam_version\']) ? \n			$prefs[\'rah_comment_spam_version\'] : \'base\';\n\n		if($version == $current)\n			return;\n\n		$ini = \n			array(\n				\'method\' => \'moderate\',\n				\'message\' => \'Your comment was marked as spam.\',\n				\'spamwords\' => \'\',\n				\'maxspamwords\' => 3,\n				\'check\' => \'name, email, web, message\',\n				\'urlcount\' => 6,\n				\'minwords\' => 1,\n				\'maxwords\' => 10000,\n				\'minchars\' => 1,\n				\'maxchars\' => 65535,\n				\'field\' => \'phone\',\n				\'commentuse\' => 1,\n				\'commentlimit\' => 10,\n				\'commentin\' => \'this\',\n				\'commenttime\' => 300,\n				\'emaildns\' => 0,\n				\'use_type_detect\' => 0,\n				\'type_interval\' => 5,\n				\'nonce\' => md5(uniqid(rand(),true))\n			);\n\n		/*\n			Migrate preferences format from <= 0.5 to >= 0.6\n		*/\n\n		if($current == \'base\') {\n			@$rs = \n				safe_rows(\n					\'name, value\',\n					\'rah_comment_spam\',\n					\'1=1\'\n				);\n\n			if(!empty($rs) && is_array($rs)) {\n				foreach($rs as $a) {\n					if(!isset($ini[$a[\'name\']]))\n						continue;\n					\n					if(in_array($a[\'name\'], array(\n						\'use_type_detect\',\n						\'emaildns\',\n						\'commentuse\',\n					)))\n						$a[\'value\'] = $a[\'value\'] == \'no\' ? 0 : 1;\n					\n					$ini[$a[\'name\']] = $a[\'value\'];\n				}\n\n				@safe_query(\n					\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_comment_spam\')\n				);\n			}\n		}\n\n		$position = 250;\n\n		/*\n			Add preference strings\n		*/\n		\n		foreach($ini as $name => $val) {\n\n			$n = \'rah_comment_spam_\' . $name;\n			\n			if(!isset($prefs[$n])) {\n				\n				if($name == \'nonce\') {\n					set_pref(\'rah_comment_spam_nonce\',$val,\'rah_cspam\',2,\'\',0);\n					continue;\n				}\n				\n				switch($name) {\n					case \'commentuse\':\n					case \'emaildns\':\n					case \'use_type_detect\':\n						$html = \'yesnoradio\';\n						break;\n					case \'commentin\':\n						$html = \'rah_comment_spam_select_commentin\';\n						break;\n					case \'method\':\n						$html = \'rah_comment_spam_select_method\';\n						break;\n					case \'spamwords\':\n						$html = \'rah_comment_spam_textarea\';\n						break;\n					default:\n						$html = \'text_input\';\n				}\n\n				safe_insert(\n					\'txp_prefs\',\n					\"prefs_id=1,\n					name=\'\".doSlash($n).\"\',\n					val=\'\".doSlash($val).\"\',\n					type=0,\n					event=\'comments\',\n					html=\'$html\',\n					position=\".$position\n				);\n				\n				$prefs[$n] = $val;\n			}\n			\n			$position++;\n		}\n		\n		/*\n			Set version\n		*/\n		\n		set_pref(\'rah_comment_spam_version\',$version,\'rah_cspam\',2,\'\',0);\n		$prefs[\'rah_comment_spam_version\'] = $version;\n	}\n\n/**\n	Check for spam\n	@param $event string Callback event.\n*/\n\n	function rah_comment_spam_register($event=\'\') {\n		global $prefs;\n\n		rah_comment_spam_install();\n\n		/*\n			Add spam trap field to the comment form\n		*/\n\n		if($event == \'comment.form\') {\n\n			$out = array();\n\n			if($prefs[\'rah_comment_spam_use_type_detect\']) {\n				\n				$nonce = ps(\'rah_comment_spam_nonce\');\n				$time = ps(\'rah_comment_spam_time\');\n				\n				if(!$nonce && !$time) {\n					@$time = strtotime(\'now\');\n					$nonce = md5($prefs[\'rah_comment_spam_nonce\'].$time);\n				}\n				\n				$out[] =\n					hInput(\'rah_comment_spam_nonce\', $nonce).\n					hInput(\'rah_comment_spam_time\', $time);\n			}\n\n			if($prefs[\'rah_comment_spam_field\'])\n				$out[] = \n					\'<div style=\"display:none;\">\'.\n						fInput(\n							\'text\',\n							htmlspecialchars($prefs[\'rah_comment_spam_field\']),\n							ps($prefs[\'rah_comment_spam_field\'])\n						).\n					\'</div>\';\n\n			return n.implode(\'\',$out).n;\n		}\n\n		/*\n			Check sent comments before saving\n		*/\n\n		$comment = new rah_comment_spam();\n\n		if($comment->is_spam()) {\n			$evaluator =& get_comment_evaluator();\n			switch($prefs[\'rah_comment_spam_method\']) {\n				case \'block\' :\n					$evaluator->add_estimate(RELOAD,1, gTxt($prefs[\'rah_comment_spam_message\']));\n				break;\n				case \'moderate\' :\n					$evaluator->add_estimate(MODERATE,0.75);\n				break;\n				default :\n					$evaluator->add_estimate(SPAM,0.75);\n			}\n		}\n	}\n\n/**\n	Contains anti-spam tools\n*/\n\nclass rah_comment_spam {\n\n	private $form = array();\n\n/**\n	Filter comment\n	@return bool TRUE if comment is detected as spam.\n*/\n\n	public function is_spam() {\n		global $prefs;\n\n		$this->form = getComment();\n\n		foreach(do_list($prefs[\'rah_comment_spam_check\']) as $f) \n			if(isset($this->form[$f]) && !isset($stack[$f]))\n				$stack[$f] = $this->form[$f];\n\n		$stack = isset($stack) ? implode(\' \', $stack) : \'\';\n\n		return \n			(\n				(\n					$prefs[\'rah_comment_spam_field\'] && \n					trim(ps($prefs[\'rah_comment_spam_field\']))\n				) ||\n				$this->wordcount($this->form[\'message\']) ||\n				$this->charcount($this->form[\'message\']) ||\n				$this->search(\n					$prefs[\'rah_comment_spam_spamwords\'],\n					$stack,\n					$prefs[\'rah_comment_spam_maxspamwords\']\n				) ||\n				$this->search(\n					array(\n						\'https://\',\n						\'http://\',\n						\'ftp://\',\n						\'ftps://\'\n					),\n					$this->form[\'message\'],\n					$prefs[\'rah_comment_spam_urlcount\']\n				) ||\n				$this->commentquota() || \n				$this->typespeed() || \n				$this->emaildns()\n			);\n	}\n\n/**\n	Finds needles from haystack. If $max is exceeded returns TRUE.\n	@param $needle mixed Needle to search for. Either comma-separated string or array.\n	@param $string string String to search.\n	@param $max int Maximum occurrences.\n	@param $count int Starting value.\n	@return bool\n*/\n\n	private function search($needle,$string,$max=0,$count=0) {\n\n		if(!$needle || !$string)\n			return false;\n	\n		/*\n			Get the status of multibyte functions\n		*/\n\n		$mb = function_exists(\'mb_strtolower\') && function_exists(\'mb_substr_count\');\n\n		/*\n			Convert cases, turn needle to\n			array if string was provided\n		*/\n\n		$string = $mb ? mb_strtolower(\' \'.$string.\' \',\'UTF-8\') : strtolower(\' \'.$string.\' \');\n\n		if(!is_array($needle)) {\n			$needle = $mb ? mb_strtolower($needle,\'UTF-8\') : strtolower($needle);\n			$needle = do_list($needle);\n		}\n\n		foreach($needle as $find)\n			if(!empty($find))\n				$count += $mb ? mb_substr_count($string,$find,\'UTF-8\') : substr_count($string,$find);\n\n		if($count > $max)\n			return true;\n	}\n\n/**\n	Count characters, return TRUE if exceeds the limit.\n	@param $string string String to count\n	@return bool\n*/\n\n	private function charcount($string) {\n		global $prefs;\n\n		if(!$string || (!$prefs[\'rah_comment_spam_minchars\'] && !$prefs[\'rah_comment_spam_maxchars\']))\n			return false;\n\n		$chars = function_exists(\'mb_strlen\') ? mb_strlen($string, \'UTF-8\') : strlen($string);\n		\n		if(\n			($prefs[\'rah_comment_spam_maxchars\'] && $prefs[\'rah_comment_spam_maxchars\'] < $chars) || \n			($prefs[\'rah_comment_spam_minchars\'] && $chars <= $prefs[\'rah_comment_spam_minchars\'])\n		)\n			return true;\n	}\n\n/**\n	Count words in string, return TRUE if exeeds the limit.\n	@param $string string String to count\n	@return bool\n*/\n\n	private function wordcount($string) {\n		global $prefs;\n		\n		if(!$string || (!$prefs[\'rah_comment_spam_maxwords\'] && !$prefs[\'rah_comment_spam_minwords\']))\n			return false;\n		\n		$words = count(explode(chr(32),$string));\n		\n		if(\n			($prefs[\'rah_comment_spam_maxwords\'] && $prefs[\'rah_comment_spam_maxwords\'] < $words) || \n			($prefs[\'rah_comment_spam_minwords\'] && $words <= $prefs[\'rah_comment_spam_minwords\'])\n		)\n			return true;\n	}\n\n/**\n	Limit user\'s comment posting.\n	@return bool TRUE when activity exceeds quota.\n*/\n\n	private function commentquota() {\n		global $thisarticle, $prefs;\n		\n		if(\n			!$prefs[\'rah_comment_spam_commentuse\'] || \n			!$prefs[\'rah_comment_spam_commentlimit\'] ||\n			($prefs[\'rah_comment_spam_commentin\'] == \'this\' && !isset($thisarticle[\'thisid\'])) ||\n			(($ip = doSlash(remote_addr())) && !$ip)\n		)\n			return false;\n		\n		$preriod = (int) $prefs[\'rah_comment_spam_commenttime\'];\n		\n		if(\n			safe_count(\n				\'txp_discuss\',\n				\"ip=\'$ip\' and UNIX_TIMESTAMP(posted) > (UNIX_TIMESTAMP(now())-$preriod)\".\n				($prefs[\'rah_comment_spam_commentin\'] == \'this\' ? \" and parentid=\'\".doSlash($thisarticle[\'thisid\']).\"\'\" : \'\')\n			) >= $prefs[\'rah_comment_spam_commentlimit\']\n		)\n			return true;\n	}\n\n/**\n	Check typing speed, make sure the user fidled with the comment form.\n	@return bool TRUE when comment is considered as spam.\n*/\n\n	private function typespeed() {\n		global $prefs;\n\n		if(!$prefs[\'rah_comment_spam_use_type_detect\'])\n			return false;\n		\n		$type_interval = (int) $prefs[\'rah_comment_spam_type_interval\'];\n		$time = (int) ps(\'rah_comment_spam_time\');\n\n		@$barrier = strtotime(\'now\')-$type_interval;\n		$md5 = md5($prefs[\'rah_comment_spam_nonce\'].$time);\n\n		if($md5 != ps(\'rah_comment_spam_nonce\') || $time >= $barrier)\n			return true;\n	}\n\n/**\n	Check DNS records of the email address.\n	@return bool TRUE when comment is considered as spam.\n*/\n\n	private function emaildns() {\n		global $prefs;\n		\n		if(!$prefs[\'rah_comment_spam_emaildns\'] || !trim($this->form[\'email\']))\n			return false;\n		\n		$domain = trim(end(explode(\'@\',$this->form[\'email\'])));\n\n		if(!$domain)\n			return true;\n		\n		if(!function_exists(\'checkdnsrr\'))\n			return false;\n		\n		if(!(checkdnsrr($domain,\'MX\') || checkdnsrr($domain,\'A\')))\n			return true;\n	}\n}\n\n/**\n	Spam protection method option\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML select field.\n*/\n\n	function rah_comment_spam_select_method($name, $val) {\n\n		foreach(array(\'block\', \'moderate\', \'spam\') as $opt)\n			$out[$opt] = gTxt(\'rah_comment_spam_method_\'.$opt);\n\n		return selectInput($name, $out, $val, \'\', \'\', $name);\n	}\n\n/**\n	Comment count range option\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML select field.\n*/\n\n	function rah_comment_spam_select_commentin($name, $val) {\n\n		foreach(array(\'all\', \'this\') as $opt)\n			$out[$opt] = gTxt(\'rah_comment_spam_commentin_\'.$opt);\n		\n		return selectInput($name, $out, $val, \'\', \'\', $name);\n	}\n\n/**\n	Textarea for preferences panel\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML textarea.\n*/\n\n	function rah_comment_spam_textarea($name, $val) {\n		return text_area($name, 100, 300, $val, $name);\n	}\n\n/**\n	Redirect to preferences panel\n*/\n\n	function rah_comment_spam_prefs() {\n		header(\'Location: ?event=prefs#prefs-rah_comment_spam_method\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs#prefs-rah_comment_spam_method\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', '	##################\n	#\n	#	rah_comment_spam-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_comment_spam_install();\n		add_privs(\'plugin_prefs.rah_comment_spam\',\'1,2\');\n		register_callback(\'rah_comment_spam_prefs\',\'plugin_prefs.rah_comment_spam\');\n		register_callback(\'rah_comment_spam_install\',\'plugin_lifecycle.rah_comment_spam\');\n	} else {\n		register_callback(\'rah_comment_spam_register\',\'comment.save\');\n		register_callback(\'rah_comment_spam_register\',\'comment.form\');\n	}\n\n/**\n	Installer\n	@param $event string Admin-side event.\n	@param $step string Admin-side, plugin-lifecycle step.\n*/\n\n	function rah_comment_spam_install($event=\'\',$step=\'\') {\n\n		if($step == \'deleted\') {\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_comment_spam_%\'\"\n			);\n			return;\n		}\n\n		global $prefs, $textarray;\n\n		/*\n			Make sure language strings are set\n		*/\n\n		foreach(\n			array(\n				\'method\' => \'What to do comments detected as spam?\',\n				\'method_block\' => \'Ignore, do not save\',\n				\'method_moderate\' => \'Save to moderation queue\',\n				\'method_spam\' => \'Save and flag as spam\',\n				\'message\' => \'Message displayed to the user when the comment was blocked\',\n				\'spamwords\' => \'List of spam words (comma-separated)\',\n				\'check\' => \'Search spam words from following fields (comma-separated)\',\n				\'maxspamwords\' => \'Maximum number of spam words until the comment is considered as spam\',\n				\'urlcount\' => \'Number of allowed URLs\',\n				\'minwords\' => \'Minimum number of words\',\n				\'maxwords\' => \'Maximum number of words\',\n				\'minchars\' => \'Minimum number of characters\',\n				\'maxchars\' => \'Maximum number of characters\',\n				\'field\' => \'Name of hidden spam trap field (empty to disable the trap)\',\n				\'commentuse\' => \'Use quotas, limit users\\\' comment posting activity?\',\n				\'commentlimit\' => \'Users\\\' comment quota limit\',\n				\'commentin\' => \'Count users\\\' comments towards quota in\',\n				\'commentin_all\' => \'In all articles\',\n				\'commentin_this\' => \'In current article\',\n				\'commenttime\' => \'Quota resets every number of seconds\',\n				\'emaildns\' => \'Check email\\\'s DNS records?\',\n				\'use_type_detect\' => \'Check the time user spent writing the comment?\',\n				\'type_interval\' => \'Minimum time required to be spent writing a comment (in seconds)?\'\n			) as $string => $translation\n		) {\n			$name = strpos($string, \'rah_\') === 0 ? $string : \'rah_comment_spam_\'.$string;\n\n			if(!isset($textarray[$name]))\n				$textarray[$name] = $translation;\n		}\n\n		$version = \'0.7\';\n\n		$current = isset($prefs[\'rah_comment_spam_version\']) ? \n			$prefs[\'rah_comment_spam_version\'] : \'base\';\n\n		if($version == $current)\n			return;\n\n		$ini = \n			array(\n				\'method\' => \'moderate\',\n				\'message\' => \'Your comment was marked as spam.\',\n				\'spamwords\' => \'\',\n				\'maxspamwords\' => 3,\n				\'check\' => \'name, email, web, message\',\n				\'urlcount\' => 6,\n				\'minwords\' => 1,\n				\'maxwords\' => 10000,\n				\'minchars\' => 1,\n				\'maxchars\' => 65535,\n				\'field\' => \'phone\',\n				\'commentuse\' => 1,\n				\'commentlimit\' => 10,\n				\'commentin\' => \'this\',\n				\'commenttime\' => 300,\n				\'emaildns\' => 0,\n				\'use_type_detect\' => 0,\n				\'type_interval\' => 5,\n				\'nonce\' => md5(uniqid(rand(),true))\n			);\n\n		/*\n			Migrate preferences format from <= 0.5 to >= 0.6\n		*/\n\n		if($current == \'base\') {\n			@$rs = \n				safe_rows(\n					\'name, value\',\n					\'rah_comment_spam\',\n					\'1=1\'\n				);\n\n			if(!empty($rs) && is_array($rs)) {\n				foreach($rs as $a) {\n					if(!isset($ini[$a[\'name\']]))\n						continue;\n					\n					if(in_array($a[\'name\'], array(\n						\'use_type_detect\',\n						\'emaildns\',\n						\'commentuse\',\n					)))\n						$a[\'value\'] = $a[\'value\'] == \'no\' ? 0 : 1;\n					\n					$ini[$a[\'name\']] = $a[\'value\'];\n				}\n\n				@safe_query(\n					\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_comment_spam\')\n				);\n			}\n		}\n\n		$position = 250;\n\n		/*\n			Add preference strings\n		*/\n		\n		foreach($ini as $name => $val) {\n\n			$n = \'rah_comment_spam_\' . $name;\n			\n			if(!isset($prefs[$n])) {\n				\n				if($name == \'nonce\') {\n					set_pref(\'rah_comment_spam_nonce\',$val,\'rah_cspam\',2,\'\',0);\n					continue;\n				}\n				\n				switch($name) {\n					case \'commentuse\':\n					case \'emaildns\':\n					case \'use_type_detect\':\n						$html = \'yesnoradio\';\n						break;\n					case \'commentin\':\n						$html = \'rah_comment_spam_select_commentin\';\n						break;\n					case \'method\':\n						$html = \'rah_comment_spam_select_method\';\n						break;\n					case \'spamwords\':\n						$html = \'rah_comment_spam_textarea\';\n						break;\n					default:\n						$html = \'text_input\';\n				}\n\n				safe_insert(\n					\'txp_prefs\',\n					\"prefs_id=1,\n					name=\'\".doSlash($n).\"\',\n					val=\'\".doSlash($val).\"\',\n					type=0,\n					event=\'comments\',\n					html=\'$html\',\n					position=\".$position\n				);\n				\n				$prefs[$n] = $val;\n			}\n			\n			$position++;\n		}\n		\n		/*\n			Set version\n		*/\n		\n		set_pref(\'rah_comment_spam_version\',$version,\'rah_cspam\',2,\'\',0);\n		$prefs[\'rah_comment_spam_version\'] = $version;\n	}\n\n/**\n	Check for spam\n	@param $event string Callback event.\n*/\n\n	function rah_comment_spam_register($event=\'\') {\n		global $prefs;\n\n		rah_comment_spam_install();\n\n		/*\n			Add spam trap field to the comment form\n		*/\n\n		if($event == \'comment.form\') {\n\n			$out = array();\n\n			if($prefs[\'rah_comment_spam_use_type_detect\']) {\n				\n				$nonce = ps(\'rah_comment_spam_nonce\');\n				$time = ps(\'rah_comment_spam_time\');\n				\n				if(!$nonce && !$time) {\n					@$time = strtotime(\'now\');\n					$nonce = md5($prefs[\'rah_comment_spam_nonce\'].$time);\n				}\n				\n				$out[] =\n					hInput(\'rah_comment_spam_nonce\', $nonce).\n					hInput(\'rah_comment_spam_time\', $time);\n			}\n\n			if($prefs[\'rah_comment_spam_field\'])\n				$out[] = \n					\'<div style=\"display:none;\">\'.\n						fInput(\n							\'text\',\n							htmlspecialchars($prefs[\'rah_comment_spam_field\']),\n							ps($prefs[\'rah_comment_spam_field\'])\n						).\n					\'</div>\';\n\n			return n.implode(\'\',$out).n;\n		}\n\n		/*\n			Check sent comments before saving\n		*/\n\n		$comment = new rah_comment_spam();\n\n		if($comment->is_spam()) {\n			$evaluator =& get_comment_evaluator();\n			switch($prefs[\'rah_comment_spam_method\']) {\n				case \'block\' :\n					$evaluator->add_estimate(RELOAD,1, gTxt($prefs[\'rah_comment_spam_message\']));\n				break;\n				case \'moderate\' :\n					$evaluator->add_estimate(MODERATE,0.75);\n				break;\n				default :\n					$evaluator->add_estimate(SPAM,0.75);\n			}\n		}\n	}\n\n/**\n	Contains anti-spam tools\n*/\n\nclass rah_comment_spam {\n\n	private $form = array();\n\n/**\n	Filter comment\n	@return bool TRUE if comment is detected as spam.\n*/\n\n	public function is_spam() {\n		global $prefs;\n\n		$this->form = getComment();\n\n		foreach(do_list($prefs[\'rah_comment_spam_check\']) as $f) \n			if(isset($this->form[$f]) && !isset($stack[$f]))\n				$stack[$f] = $this->form[$f];\n\n		$stack = isset($stack) ? implode(\' \', $stack) : \'\';\n\n		return \n			(\n				(\n					$prefs[\'rah_comment_spam_field\'] && \n					trim(ps($prefs[\'rah_comment_spam_field\']))\n				) ||\n				$this->wordcount($this->form[\'message\']) ||\n				$this->charcount($this->form[\'message\']) ||\n				$this->search(\n					$prefs[\'rah_comment_spam_spamwords\'],\n					$stack,\n					$prefs[\'rah_comment_spam_maxspamwords\']\n				) ||\n				$this->search(\n					array(\n						\'https://\',\n						\'http://\',\n						\'ftp://\',\n						\'ftps://\'\n					),\n					$this->form[\'message\'],\n					$prefs[\'rah_comment_spam_urlcount\']\n				) ||\n				$this->commentquota() || \n				$this->typespeed() || \n				$this->emaildns()\n			);\n	}\n\n/**\n	Finds needles from haystack. If $max is exceeded returns TRUE.\n	@param $needle mixed Needle to search for. Either comma-separated string or array.\n	@param $string string String to search.\n	@param $max int Maximum occurrences.\n	@param $count int Starting value.\n	@return bool\n*/\n\n	private function search($needle,$string,$max=0,$count=0) {\n\n		if(!$needle || !$string)\n			return false;\n	\n		/*\n			Get the status of multibyte functions\n		*/\n\n		$mb = function_exists(\'mb_strtolower\') && function_exists(\'mb_substr_count\');\n\n		/*\n			Convert cases, turn needle to\n			array if string was provided\n		*/\n\n		$string = $mb ? mb_strtolower(\' \'.$string.\' \',\'UTF-8\') : strtolower(\' \'.$string.\' \');\n\n		if(!is_array($needle)) {\n			$needle = $mb ? mb_strtolower($needle,\'UTF-8\') : strtolower($needle);\n			$needle = do_list($needle);\n		}\n\n		foreach($needle as $find)\n			if(!empty($find))\n				$count += $mb ? mb_substr_count($string,$find,\'UTF-8\') : substr_count($string,$find);\n\n		if($count > $max)\n			return true;\n	}\n\n/**\n	Count characters, return TRUE if exceeds the limit.\n	@param $string string String to count\n	@return bool\n*/\n\n	private function charcount($string) {\n		global $prefs;\n\n		if(!$string || (!$prefs[\'rah_comment_spam_minchars\'] && !$prefs[\'rah_comment_spam_maxchars\']))\n			return false;\n\n		$chars = function_exists(\'mb_strlen\') ? mb_strlen($string, \'UTF-8\') : strlen($string);\n		\n		if(\n			($prefs[\'rah_comment_spam_maxchars\'] && $prefs[\'rah_comment_spam_maxchars\'] < $chars) || \n			($prefs[\'rah_comment_spam_minchars\'] && $chars <= $prefs[\'rah_comment_spam_minchars\'])\n		)\n			return true;\n	}\n\n/**\n	Count words in string, return TRUE if exeeds the limit.\n	@param $string string String to count\n	@return bool\n*/\n\n	private function wordcount($string) {\n		global $prefs;\n		\n		if(!$string || (!$prefs[\'rah_comment_spam_maxwords\'] && !$prefs[\'rah_comment_spam_minwords\']))\n			return false;\n		\n		$words = count(explode(chr(32),$string));\n		\n		if(\n			($prefs[\'rah_comment_spam_maxwords\'] && $prefs[\'rah_comment_spam_maxwords\'] < $words) || \n			($prefs[\'rah_comment_spam_minwords\'] && $words <= $prefs[\'rah_comment_spam_minwords\'])\n		)\n			return true;\n	}\n\n/**\n	Limit user\'s comment posting.\n	@return bool TRUE when activity exceeds quota.\n*/\n\n	private function commentquota() {\n		global $thisarticle, $prefs;\n		\n		if(\n			!$prefs[\'rah_comment_spam_commentuse\'] || \n			!$prefs[\'rah_comment_spam_commentlimit\'] ||\n			($prefs[\'rah_comment_spam_commentin\'] == \'this\' && !isset($thisarticle[\'thisid\'])) ||\n			(($ip = doSlash(remote_addr())) && !$ip)\n		)\n			return false;\n		\n		$preriod = (int) $prefs[\'rah_comment_spam_commenttime\'];\n		\n		if(\n			safe_count(\n				\'txp_discuss\',\n				\"ip=\'$ip\' and UNIX_TIMESTAMP(posted) > (UNIX_TIMESTAMP(now())-$preriod)\".\n				($prefs[\'rah_comment_spam_commentin\'] == \'this\' ? \" and parentid=\'\".doSlash($thisarticle[\'thisid\']).\"\'\" : \'\')\n			) >= $prefs[\'rah_comment_spam_commentlimit\']\n		)\n			return true;\n	}\n\n/**\n	Check typing speed, make sure the user fidled with the comment form.\n	@return bool TRUE when comment is considered as spam.\n*/\n\n	private function typespeed() {\n		global $prefs;\n\n		if(!$prefs[\'rah_comment_spam_use_type_detect\'])\n			return false;\n		\n		$type_interval = (int) $prefs[\'rah_comment_spam_type_interval\'];\n		$time = (int) ps(\'rah_comment_spam_time\');\n\n		@$barrier = strtotime(\'now\')-$type_interval;\n		$md5 = md5($prefs[\'rah_comment_spam_nonce\'].$time);\n\n		if($md5 != ps(\'rah_comment_spam_nonce\') || $time >= $barrier)\n			return true;\n	}\n\n/**\n	Check DNS records of the email address.\n	@return bool TRUE when comment is considered as spam.\n*/\n\n	private function emaildns() {\n		global $prefs;\n		\n		if(!$prefs[\'rah_comment_spam_emaildns\'] || !trim($this->form[\'email\']))\n			return false;\n		\n		$domain = trim(end(explode(\'@\',$this->form[\'email\'])));\n\n		if(!$domain)\n			return true;\n		\n		if(!function_exists(\'checkdnsrr\'))\n			return false;\n		\n		if(!(checkdnsrr($domain,\'MX\') || checkdnsrr($domain,\'A\')))\n			return true;\n	}\n}\n\n/**\n	Spam protection method option\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML select field.\n*/\n\n	function rah_comment_spam_select_method($name, $val) {\n\n		foreach(array(\'block\', \'moderate\', \'spam\') as $opt)\n			$out[$opt] = gTxt(\'rah_comment_spam_method_\'.$opt);\n\n		return selectInput($name, $out, $val, \'\', \'\', $name);\n	}\n\n/**\n	Comment count range option\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML select field.\n*/\n\n	function rah_comment_spam_select_commentin($name, $val) {\n\n		foreach(array(\'all\', \'this\') as $opt)\n			$out[$opt] = gTxt(\'rah_comment_spam_commentin_\'.$opt);\n		\n		return selectInput($name, $out, $val, \'\', \'\', $name);\n	}\n\n/**\n	Textarea for preferences panel\n	@param $name string Field name.\n	@param $val int Current value.\n	@return string HTML textarea.\n*/\n\n	function rah_comment_spam_textarea($name, $val) {\n		return text_area($name, 100, 300, $val, $name);\n	}\n\n/**\n	Redirect to preferences panel\n*/\n\n	function rah_comment_spam_prefs() {\n		header(\'Location: ?event=prefs#prefs-rah_comment_spam_method\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs#prefs-rah_comment_spam_method\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', 'ea47b45f6ba1f34e5801327f421ff198', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_default_category', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.6', 'Select a default article category', '<h1>rah_default_category</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_default_category\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_default_category\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=29154\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_default_category\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_default_category is a tiny admin-extension for <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a>. The plugin brings the default section feature to categories. The extension adds new options to Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Advanced_Preferences\" rel=\"nofollow\">Advanced Preferences</a> that allow site admins set default article categories, just like one can select a default article section. Selected default categories are then used as the default option on the <a href=\"http://textpattern.net/wiki/index.php?title=Write\" rel=\"nofollow\">Write</a> panel.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_default_category&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.4.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_default_category&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.6 &#8211; 2012/08/27</h3>\n\n<ul>\n	<li>Added: Finnish translation.</li>\n	<li>Added: French translation by <a href=\"https://twitter.com/lowel\" rel=\"nofollow\">Patrick Lefevre</a>.</li>\n	<li>Improved: Now uses <code>escape_js()</code> to make sure JavaScript strings are valid.</li>\n	<li>Improved: Language strings aren&#8217;t embedded to the source code, but uses external Textpack that are ran during install.</li>\n	<li>Changed: Wrapped functions to a class.</li>\n	<li>Changed: Renamed Preference group to <em>Default article categories</em>.</li>\n	<li>Changed: In preferences displays categories as a tree. Child categories are listed under the parent.</li>\n	<li>Fully compatible with Textpattern v4.5.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2011/06/29</h3>\n\n<ul>\n	<li>Fixed: the issue where default values didn&#8217;t show up directly after logging in. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=242061#p242061\" rel=\"nofollow\">Thank you for reporting the issue Husain</a>.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2011/06/29</h3>\n\n<ul>\n	<li>Changed: Integrated the plugin&#8217;s preferences pane to Textpattern&#8217;s build in Advanced Preferences interface. The plugin no longer has own tab, but instead the preferences are located at <em><span class=\"caps\">TXP</span>/Admin/Preferences/Advanced</em>.</li>\n	<li>Changed: Preferences are now stored in txp_prefs table instead of its own table. The plugin comes with migration script that makes transition automatic.</li>\n	<li>Fixed: Doesn&#8217;t throw up JavaScript if browser&#8217;s JavaScript support is disabled.</li>\n	<li>Added: Language string support.</li>\n	<li>Added: Options link to the Textpattern&#8217;s plugins pane.</li>\n	<li>For full feature compatibility requires Textpattern 4.2.0 or newer.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2010/11/02</h3>\n\n<ul>\n	<li>Removed unnecessary default values from the installation query.</li>\n	<li>Removed no-longer used <span class=\"caps\">HTML</span> IDs.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/08/11</h3>\n\n<ul>\n	<li>Fixed: conditional logic. Now doesn&#8217;t reselected default categories after <em>article post + empty categories</em> combination.</li>\n	<li>Removed tables from the plugin&#8217;s prefs panel and changed to better layout.</li>\n	<li>Removed inline styles.</li>\n	<li>Improved installer.</li>\n	<li>Now uses same coding style as all newer rah-plugins.</li>\n	<li>Centralized preferences fetcher.</li>\n	<li>Changed prefs panel event from <code>rah_default_category_page</code> to <code>rah_default_category</code>.</li>\n	<li>Now requires Textpattern version 4.0.7 or newer.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/12/11</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_default_caterogry plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_default_category\n *\n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_default_category();\n\nclass rah_default_category {\n\n	static public $version = \'0.6\';\n\n	/**\n	 * Installer\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side, plugin-lifecycle step.	\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_default\\_category\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$opt = \n			array(\n				\'default_category_1\' => \'\',\n				\'default_category_2\' => \'\'\n			);\n		\n		@$rs = safe_rows(\'name, value\', \'rah_default_category\', \'1=1\');\n		\n		if($rs && is_array($rs)) {\n			foreach($rs as $a) {\n				if(isset($opt[$a[\'name\']])) {\n					$opt[$a[\'name\']] = $a[\'value\'];\n				}\n			}\n			\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(__CLASS__));\n		}\n	\n		$position = 245;\n		\n		foreach($opt as $name => $value) {\n			$name = \'rah_\'.$name;\n			\n			if(!isset($prefs[$name])) {\n				set_pref($name, $value, \'rah_defcat\', PREF_ADVANCED, \'rah_default_category_list\', $position);\n				$prefs[$name] = $value;\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, \'rah_defcat\', 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n\n	/**\n	 * Constructor\n	 */\n\n	public function __construct() {\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array($this, \'head\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Adds the selection script to Write panel\'s <head>\n	 */\n\n	public function head() {\n		\n		global $event, $prefs;\n		\n		if($event != \'article\' || ps(\'event\') || gps(\'ID\'))\n			return;\n		\n		$js = array();\n		\n		if(!empty($prefs[\'rah_default_category_1\'])) {\n			$js[] =\n				\'$(\"#category-1 option[value=\\\'\'.\n					escape_js($prefs[\'rah_default_category_1\']).\n				\'\\\']\").attr(\"selected\",\"selected\");\';\n		}\n		\n		if(!empty($prefs[\'rah_default_category_2\'])) {\n			$js[] = \n				\'$(\"#category-2 option[value=\\\'\'.\n					escape_js($prefs[\'rah_default_category_2\']).\n				\'\\\']\").attr(\"selected\",\"selected\");\';\n		}\n		\n		if(!$js)\n			return;\n\n		echo \n			script_js(\n				\'$(document).ready(function() {\'.n.\n					implode(n, $js).n.\n				\'});\'\n			);\n	}\n\n	/**\n	 * Redirects to the preferences panel\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_default_category_1\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_default_category_1\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n/**\n * Lists all available categories\n * @param string $name Preferences field\'s name.\n * @param string $val Currently saved value\n * @return string HTML select field.\n */\n\n	function rah_default_category_list($name, $val) {\n		return treeSelectInput($name, getTree(\'root\', \'article\'), $val, $name, 35);\n	}\n', '\n\n/**\n * Rah_default_caterogry plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_default_category\n *\n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_default_category();\n\nclass rah_default_category {\n\n	static public $version = \'0.6\';\n\n	/**\n	 * Installer\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side, plugin-lifecycle step.	\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_default\\_category\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$opt = \n			array(\n				\'default_category_1\' => \'\',\n				\'default_category_2\' => \'\'\n			);\n		\n		@$rs = safe_rows(\'name, value\', \'rah_default_category\', \'1=1\');\n		\n		if($rs && is_array($rs)) {\n			foreach($rs as $a) {\n				if(isset($opt[$a[\'name\']])) {\n					$opt[$a[\'name\']] = $a[\'value\'];\n				}\n			}\n			\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(__CLASS__));\n		}\n	\n		$position = 245;\n		\n		foreach($opt as $name => $value) {\n			$name = \'rah_\'.$name;\n			\n			if(!isset($prefs[$name])) {\n				set_pref($name, $value, \'rah_defcat\', PREF_ADVANCED, \'rah_default_category_list\', $position);\n				$prefs[$name] = $value;\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, \'rah_defcat\', 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n\n	/**\n	 * Constructor\n	 */\n\n	public function __construct() {\n		add_privs(\'plugin_prefs.\'.__CLASS__, \'1,2\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array($this, \'head\'), \'admin_side\', \'head_end\');\n	}\n\n	/**\n	 * Adds the selection script to Write panel\'s <head>\n	 */\n\n	public function head() {\n		\n		global $event, $prefs;\n		\n		if($event != \'article\' || ps(\'event\') || gps(\'ID\'))\n			return;\n		\n		$js = array();\n		\n		if(!empty($prefs[\'rah_default_category_1\'])) {\n			$js[] =\n				\'$(\"#category-1 option[value=\\\'\'.\n					escape_js($prefs[\'rah_default_category_1\']).\n				\'\\\']\").attr(\"selected\",\"selected\");\';\n		}\n		\n		if(!empty($prefs[\'rah_default_category_2\'])) {\n			$js[] = \n				\'$(\"#category-2 option[value=\\\'\'.\n					escape_js($prefs[\'rah_default_category_2\']).\n				\'\\\']\").attr(\"selected\",\"selected\");\';\n		}\n		\n		if(!$js)\n			return;\n\n		echo \n			script_js(\n				\'$(document).ready(function() {\'.n.\n					implode(n, $js).n.\n				\'});\'\n			);\n	}\n\n	/**\n	 * Redirects to the preferences panel\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_default_category_1\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_default_category_1\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n/**\n * Lists all available categories\n * @param string $name Preferences field\'s name.\n * @param string $val Currently saved value\n * @return string HTML select field.\n */\n\n	function rah_default_category_list($name, $val) {\n		return treeSelectInput($name, getTree(\'root\', \'article\'), $val, $name, 35);\n	}\n', '6bfe901b6b2bb95ec4559ce7182f371d', '#@admin\n#Default language, en-us, en-gb\nrah_defcat => Default article categories\nrah_default_category_1 => Default category 1\nrah_default_category_2 => Default category 2\n#@admin\n#@language fi-fi\nrah_defcat => Oletus artikkelikategoriat\nrah_default_category_1 => Oletus kategoria 1\nrah_default_category_2 => Oletus kategoria 2\n#@admin\n#@language fr-fr\nrah_defcat => Catégories d\'articles par défaut\nrah_default_category_1 => Catégorie 1 par défaut\nrah_default_category_2 => Catégorie 2 par défaut', '', 3, 5, 3),
('rah_file_search', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.7', 'Search files like articles', '	<h1>rah_file_search</h1>\n\n	<p>Simple public file search; combines the power of an <a href=\"http://textpattern.net/wiki/index.php?title=article\">article</a> and a <a href=\"http://textpattern.net/wiki/index.php?title=file_download_list\">file_download_list</a>. Searches matching results from files&#8217; descriptions and filenames, and retunes the results as nicely formatted list.</p>\n\n	<ul>\n		<li>Version: 0.7</li>\n		<li>Updated: 2011/03/24 12:20 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#example\">Example</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Customize search results with Textpattern file forms and various attributes.</li>\n		<li>A simple tag, <code>&lt;txp:rah_file_search /&gt;</code>. The tag shows search results.</li>\n		<li>The search works with the standard core search. Core conditionals and forms can work too, including <code>&lt;txp:if_search /&gt;</code>.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 4.3.0+</li>\n		<li>Textpattern 4.2.0 or newer.</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste the plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use new tags that plugin includes like others.</p>\n\n	<p>For usage, basically use <code>&lt;txp:rah_file_search /&gt;</code> to display the search results. Works kinda identically as <code>&lt;txp:article /&gt;</code>.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<p>The tag is <code>&lt;txp:rah_file_search /&gt;</code> and it has the same attributes as <code>&lt;txp:file_download_list /&gt;</code> (and one additional). See <a href=\"http://textpattern.net/wiki/index.php?title=file_download_list\">file_download_list</a> for the list of attributes. In addition to, the tag has:</p>\n\n	<p><strong>grand_total</strong><br />\nOmit setting result count. If the attribute is set to <code>&quot;0&quot;</code> (zero), <a href=\"http://textpattern.net/wiki/index.php?title=search_result_count\">search_result_count</a> won&#8217;t be updated with the number of results. This attribute could be helpful if rah_file_search is used in conjunction with <a href=\"http://textpattern.net/wiki/index.php?title=article\">article</a> tags, as article tags won&#8217;t update the count once it&#8217;s already set.<br />\nDefault: <code>grand_total=&quot;1&quot;</code> Example: <code>&quot;0&quot;</code>.</p>\n\n	<h3 id=\"example\">Example</h3>\n\n	<p>Outputs search form and results (if any).</p>\n\n<pre><code>&lt;txp:search_input /&gt;\n&lt;txp:rah_file_search /&gt;\n</code></pre>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Now filters by all words, not any. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=244003#p244003\">Thank you ax</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Now searches words individually. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=243987#p243987\">Thank you for notifying Marc</a>.</li>\n		<li>Searches are case-insensitive.</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Added <em>title</em> to searchable fields in addition to <em>filename</em> and <em>description</em>.</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added: option to omit updating <a href=\"http://textpattern.net/wiki/index.php?title=search_result_count\">search_result_count</a>. If <code>grand_total</code> attribute is set to <code>0</code>, the count wont be set.</li>\n		<li>Changed: defaults <a href=\"http://textpattern.net/wiki/index.php?title=search_result_count\">search_result_count</a> to <code>0</code> (zero).</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Added: Now populates <a href=\"http://textpattern.net/wiki/index.php?title=search_result_count\">search_result_count</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Now only causes one extra query; uses <code>file_download_list()</code> instead <code>file_download()</code>.</li>\n		<li>Now accepts all the same attributes as <a href=\"http://textpattern.net/wiki/index.php?title=file_download_list\">file_download_list</a> tag.</li>\n		<li>Now suppresses <em>&#8220;Page template doesn&#8217;t contain article tag.&#8221;</em> notice when on search context.</li>\n		<li>Now uses uses same type of matching pattern as Textpattern.</li>\n		<li>From now on requires Textpattern version 4.2.0 or newer.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>Initial release.</li>\n	</ul>', '	##################\n	#\n	#	rah_file_search-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n/**\n	The tag. Returns the list of matching files.\n*/\n\n	function rah_file_search($atts, $thing = NULL) {\n		\n		global $pretext, $has_article_tag, $thispage;\n		\n		/*\n			Allow turn grand_total counting off\n			by setting \"grand_total\" attribute to 0\n		*/\n		\n		$grand_total = isset($atts[\'grand_total\']) ? $atts[\'grand_total\'] : 1;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = 0;\n		\n		if(empty($pretext[\'q\']))\n			return;\n		\n		/*\n			We are an article tag,\n			well kinda.\n		*/\n		\n		$has_article_tag = true;\n		$q = $pretext[\'q\'];\n		\n		/*\n			Quotes should be stripped\n			from quote surrounded string\n		*/\n		\n		$q = trim($q);\n		$quoted = ($q[0] === \'\"\') && ($q[strlen($q)-1] === \'\"\');\n		$q = doSlash($quoted ? trim(trim($q, \'\"\')) : $q);\n\n		/*\n			Clean whitespace and escape the\n			special syntax MySQL\'s like operator uses\n		*/\n		\n		$q = \n			preg_replace(\'/\\s+/\', \' \', \n				str_replace(\n					array(\'\\\\\',\'%\',\'_\',\'\\\'\'),\n					array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'),\n					$q\n				)\n			);\n		\n		/*\n			Searchable fields\n		*/\n		\n		$fields =\n			array(\n				\'filename\',\n				\'title\',\n				\'description\'\n			);\n		\n		/*\n			If quoted\n		*/\n		\n		if($quoted)\n			foreach($fields as $field)\n				$sql[] = \"lower($field) like lower(\'%$q%\')\";\n		else {\n			\n			/*\n				Go thru the words\n			*/\n			\n			$words = explode(\' \', $q);\n			foreach($words as $word) {\n				$fsql = array();\n				foreach($fields as $field)\n					$fsql[] = \"lower($field) like lower(\'%$word%\')\";\n				$sql[] = \'(\'.implode(\' or \',$fsql).\')\';\n			}\n		}\n		\n		/*\n			Get matching IDs\n		*/\n		\n		$rs = \n			safe_rows(\n				\'id\',\n				\'txp_file\',\n				\'(\'.implode(($quoted ? \' or \' : \' and \'),$sql).\') and status=4\'\n			);\n		\n		if(!$rs)\n			return;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = count($rs);\n		\n		$ids = array();\n		\n		foreach($rs as $a) \n			$ids[] = $a[\'id\'];\n		\n		/*\n			Return the file list\n		*/\n		\n		$atts[\'id\'] = implode(\',\',$ids);\n		\n		unset(\n			$atts[\'grand_total\']\n		);\n		\n		return\n			file_download_list($atts, $thing);\n	}', '	##################\n	#\n	#	rah_file_search-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n/**\n	The tag. Returns the list of matching files.\n*/\n\n	function rah_file_search($atts, $thing = NULL) {\n		\n		global $pretext, $has_article_tag, $thispage;\n		\n		/*\n			Allow turn grand_total counting off\n			by setting \"grand_total\" attribute to 0\n		*/\n		\n		$grand_total = isset($atts[\'grand_total\']) ? $atts[\'grand_total\'] : 1;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = 0;\n		\n		if(empty($pretext[\'q\']))\n			return;\n		\n		/*\n			We are an article tag,\n			well kinda.\n		*/\n		\n		$has_article_tag = true;\n		$q = $pretext[\'q\'];\n		\n		/*\n			Quotes should be stripped\n			from quote surrounded string\n		*/\n		\n		$q = trim($q);\n		$quoted = ($q[0] === \'\"\') && ($q[strlen($q)-1] === \'\"\');\n		$q = doSlash($quoted ? trim(trim($q, \'\"\')) : $q);\n\n		/*\n			Clean whitespace and escape the\n			special syntax MySQL\'s like operator uses\n		*/\n		\n		$q = \n			preg_replace(\'/\\s+/\', \' \', \n				str_replace(\n					array(\'\\\\\',\'%\',\'_\',\'\\\'\'),\n					array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'),\n					$q\n				)\n			);\n		\n		/*\n			Searchable fields\n		*/\n		\n		$fields =\n			array(\n				\'filename\',\n				\'title\',\n				\'description\'\n			);\n		\n		/*\n			If quoted\n		*/\n		\n		if($quoted)\n			foreach($fields as $field)\n				$sql[] = \"lower($field) like lower(\'%$q%\')\";\n		else {\n			\n			/*\n				Go thru the words\n			*/\n			\n			$words = explode(\' \', $q);\n			foreach($words as $word) {\n				$fsql = array();\n				foreach($fields as $field)\n					$fsql[] = \"lower($field) like lower(\'%$word%\')\";\n				$sql[] = \'(\'.implode(\' or \',$fsql).\')\';\n			}\n		}\n		\n		/*\n			Get matching IDs\n		*/\n		\n		$rs = \n			safe_rows(\n				\'id\',\n				\'txp_file\',\n				\'(\'.implode(($quoted ? \' or \' : \' and \'),$sql).\') and status=4\'\n			);\n		\n		if(!$rs)\n			return;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = count($rs);\n		\n		$ids = array();\n		\n		foreach($rs as $a) \n			$ids[] = $a[\'id\'];\n		\n		/*\n			Return the file list\n		*/\n		\n		$atts[\'id\'] = implode(\',\',$ids);\n		\n		unset(\n			$atts[\'grand_total\']\n		);\n		\n		return\n			file_download_list($atts, $thing);\n	}', '23bb449c1ae8cbe339fe65840340a19f', '', '', 0, 5, 0),
('rah_get_date', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.2', 'Outputs date and time from the requested url', '	<h1>rah_get_date</h1>\n\n	<p>The plugin, rah_get_date, outputs date and time from the requested url. Supports <a href=\"http://www.textpattern.com\">Textpattern&#8217;s</a> build in <code>date</code> and <code>month</code> -<span class=\"caps\">GET</span> variables, and outputs formatted date based on those two <span class=\"caps\">GET</span> values. That way you can, for example, output month header to month separated article listing pages.</p>\n\n	<ul>\n		<li>Version: 0.2</li>\n		<li>Updated: 2011/05/22 6:55 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#examples\">Examples</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Output date from <code>?date=</code> and/or <code>?month=</code> get values.</li>\n		<li>Format date just like you can with <code>&lt;txp:posted /&gt;</code>.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Minimum requirements:</p>\n\n	<ul>\n		<li><span class=\"caps\">TXP</span> 4.x.x+</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li><span class=\"caps\">TXP</span> 4.4.0+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The basic method stands: paste plugin code to the plugin install textarea and run the automatic setup. Activate the plugin, and you are ready to use the new tag, <code>&lt;txp:rah_get_date /&gt;</code> like others.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<p>The tag is <code>&lt;txp:rah_get_date /&gt;</code> and attributes for it follow. All attributes are optional.</p>\n\n	<p><strong>format</strong><br />\nDate format used to output the date. Any valid <a href=\"http://php.net/strftime\">strftime()</a> string value since <a href=\"http://en.wikipedia.org/wiki/ISO_8601\">iso8601,</a> w3cdtf, or <a href=\"http://www.w3.org/Protocols/rfc822/#z28\">rfc822.</a><br />\nDefault: <code>format=&quot;%Y %B&quot;</code> Example: <code>&quot;%Y %m %d&quot;</code>.</p>\n\n	<p><strong>format_year</strong><br />\nAllows specific formatting for dates that only specify the year (<code>YYYY</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_year=&quot;Articles posted during %Y&quot;</code></p>\n\n	<p><strong>format_month</strong><br />\nAllows specific formatting for dates that only specify the year and the month of the year (<code>YYYY-mm</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_month=&quot;%B, %Y&quot;</code></p>\n\n	<p><strong>format_date</strong><br />\nAllows specific formatting for dates that only specify the year, the month and the day of the month (<code>YYYY-mm-dd</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_date=&quot;%B %d, %Y&quot;</code></p>\n\n	<p><strong>format_hour</strong><br />\nAllows specific formatting for dates that only specify the year, month, day and hour of the day (<code>YYYY-mm-dd HH</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_hour=&quot;Posted on %H of %d&quot;</code></p>\n\n	<p><strong>format_minute</strong><br />\nAllows specific formatting for dates that only specify the year, month, day, hours and minutes (<code>YYYY-mm-dd HH:MM</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_minute=&quot;%H:%M&quot;</code></p>\n\n	<p><strong>format_second</strong><br />\nAllows specific formatting for dates that only specify the year, month, day, hours, minutes and seconds (<code>YYYY-mm-dd HH:MM:SS</code>). If the attribute is set, the additional formatting is used. Same formatting rules apply.<br />\nDefault: <code>unset</code> Example: <code>format_second=&quot;%Y %B, %d %H:%M:%S&quot;</code></p>\n\n	<p><strong>format_now</strong><br />\nFormatting used if the requested date is the current time. Current time&#8217;s condition is set with the <code>now_condition</code> attribute. By default, if the requested date/time is within the current day, the formatting defined in <code>format_now</code> is used.<br />\nDefault: <code>unset</code> Example: <code>format_now=&quot;Articles posted today&quot;</code></p>\n\n	<p><strong>now_condition</strong><br />\nDefines what dates are considered as current time. Set the date format that is used in matching current and requested dates. By default matches days (today).<br />\nDefault: <code>now_condition=&quot;%Y-%m-%d&quot;</code> Example: <code>&quot;%Y-%m&quot;</code></p>\n\n	<h3 id=\"examples\">Examples</h3>\n\n	<p><strong>Example #1: Basic usage</strong></p>\n\n<pre><code>&lt;txp:rah_get_date format=&quot;%Y %B&quot; /&gt;\n</code></pre>\n\n	<p>Outputs <code>&quot;2009 March&quot;</code> if requested <span class=\"caps\">URI</span> is <code>http://mysite.com/path/?month=2009-3</code>.</p>\n\n	<p><strong>Example #2: Use specific formatting for dates that only define the year</strong></p>\n\n	<p>The example uses <code>format_year</code> attribute to set the formatting for the dates that only specify the year.</p>\n\n<pre><code>&lt;txp:rah_get_date format=&quot;%Y %B&quot; format_year=&quot;Posted during %Y&quot; /&gt;\n</code></pre>\n\n	<p>Outputs <code>&quot;2009 March&quot;</code> if the requested <span class=\"caps\">URL</span> is <code>http://mysite.com/path/?month=2009-3</code> and <code>&quot;Posted during 2008&quot;</code> if the requested <span class=\"caps\">URL</span> is <code>http://mysite.com/path/?month=2008</code></p>\n\n	<p>*Example #3: Output <code>&quot;Posted today&quot;</code>, if the requested timestamp is today.</p>\n\n<pre><code>&lt;txp:rah_get_date now_condition=&quot;%Y-%m-%d&quot; format_now=&quot;Posted today&quot; /&gt;\n</code></pre>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Added: support for additional formatting rules applied based on timestamp&#8217;s accuracy. Added attributes: <code>format_year</code>, <code>format_month</code>, <code>format_date</code>, <code>format_hour</code>, <code>format_minute</code>, <code>format_second</code>, <code>format_now</code>, <code>now_condition</code> and <code>date</code>.</li>\n		<li>Added: Ability to set the date using <code>date</code> attribute in addition to the <span class=\"caps\">URL</span> parameter.</li>\n		<li>Now uses safe_strtotime() instead of strtotime() to format the dates.</li>\n		<li>Now checks if the provided date is in valid strtotime() format.</li>\n		<li>Skip date formatting if the resulting formatting string is empty.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>', '	##################\n	#\n	#	rah_get_date-plugin for Textpattern\n	#	version 0.2\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n	function rah_get_date($atts) {\n		extract(lAtts(array(\n			\'format\' => \'%Y %B\',\n			\'format_year\' => \'\',\n			\'format_month\' => \'\',\n			\'format_date\' => \'\',\n			\'format_hour\' => \'\',\n			\'format_minute\' => \'\',\n			\'format_second\' => \'\',\n			\'format_now\' => \'\',\n			\'now_condition\' => \'%Y-%m-%d\',\n			\'date\' => \'\'\n		),$atts));\n		\n		$date = trim($date ? $date : (gps(\'month\') ? gps(\'month\') : gps(\'date\')));\n		\n		if(!$date || ($valid = strtotime($date)) == -1 || $valid === false)\n			return;\n		\n		$udate = safe_strtotime($date);\n		\n		if(\n			$format_now && $now_condition &&\n			safe_strftime($now_condition, safe_strtotime(\'now\')) === \n			safe_strftime($now_condition, $udate)\n		)\n			$format = $format_now;\n		else {\n		\n			$ds = \n				substr_count($date, \'-\') + \n				substr_count($date, \' \') + \n				substr_count($date, \':\');\n		\n			$formats = \n				array(\n					0 => \'year\',\n					1 => \'month\',\n					2 => \'date\',\n					3 => \'hour\',\n					4 => \'minute\',\n					5 => \'second\'\n				);\n		\n			if(isset($formats[$ds]) && isset($atts[\'format_\'.$formats[$ds]]))\n				$format = $atts[\'format_\'.$formats[$ds]];\n		}\n		\n		return $format ? safe_strftime($format, $udate) : \'\';\n	}', '	##################\n	#\n	#	rah_get_date-plugin for Textpattern\n	#	version 0.2\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n	function rah_get_date($atts) {\n		extract(lAtts(array(\n			\'format\' => \'%Y %B\',\n			\'format_year\' => \'\',\n			\'format_month\' => \'\',\n			\'format_date\' => \'\',\n			\'format_hour\' => \'\',\n			\'format_minute\' => \'\',\n			\'format_second\' => \'\',\n			\'format_now\' => \'\',\n			\'now_condition\' => \'%Y-%m-%d\',\n			\'date\' => \'\'\n		),$atts));\n		\n		$date = trim($date ? $date : (gps(\'month\') ? gps(\'month\') : gps(\'date\')));\n		\n		if(!$date || ($valid = strtotime($date)) == -1 || $valid === false)\n			return;\n		\n		$udate = safe_strtotime($date);\n		\n		if(\n			$format_now && $now_condition &&\n			safe_strftime($now_condition, safe_strtotime(\'now\')) === \n			safe_strftime($now_condition, $udate)\n		)\n			$format = $format_now;\n		else {\n		\n			$ds = \n				substr_count($date, \'-\') + \n				substr_count($date, \' \') + \n				substr_count($date, \':\');\n		\n			$formats = \n				array(\n					0 => \'year\',\n					1 => \'month\',\n					2 => \'date\',\n					3 => \'hour\',\n					4 => \'minute\',\n					5 => \'second\'\n				);\n		\n			if(isset($formats[$ds]) && isset($atts[\'format_\'.$formats[$ds]]))\n				$format = $atts[\'format_\'.$formats[$ds]];\n		}\n		\n		return $format ? safe_strftime($format, $udate) : \'\';\n	}', '8c6bf4a84c8ae0b78d72648cc5a6d975', '', '', 0, 5, 0),
('rah_ip_range', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4', 'Allow and block visitors based on an IP range', '<h1>rah_ip_range</h1>\n\n<p>Rah_ip_range is a <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a> plugin for restricting visitors’ access to certain parts of site content based on their IP addresses. The plugin introduces a little Textpattern tag that takes an IP address range and an executed action. The tag can either be used to hide parts of a page template or to invoke a customizable error page.</p>\n\n<h2>List of features</h2>\n\n<ul>\n	<li>Deny and allow visitors from accessing site content with IP ranges.</li>\n	<li>Comes with a simple tag, <code>&lt;txp:rah_ip_range /&gt;</code>. Include the tag anywhere in your <a href=\"http://textpattern.net/wiki/index.php?title=Pages\" rel=\"nofollow\">page templates</a> and <a href=\"http://textpattern.net/wiki/index.php?title=Forms\" rel=\"nofollow\">form partials</a> you want restricting to take place.</li>\n	<li>Blocked visitors can be set to see a Textpattern&#8217;s native, customizable error page. Error messages and status codes are fully configurable.</li>\n	<li>Alternatively, the plugin can be used to hide certain parts of a page, without invoking an error page.</li>\n</ul>\n\n<h2>Basics</h2>\n\n<pre><code>&lt;txp:rah_ip_range fromip=&quot;127.0.0.1&quot; toip=&quot;127.0.0.1&quot; method=&quot;deny&quot;&gt;\n	...contained statements...\n&lt;/txp:rah_ip_range&gt;</code></pre>\n\n<p>The plugin introduces a new Textpattern tag, <code>&lt;txp:rah_ip_range /&gt;</code>. The tag can be used to restrict visitor&#8217;s access to certain parts of page template content or full pages based on their IP address. The tag takes an IP range, set by <code>fromip</code> and <code>toip</code> attributes. A visitor that is in the specified range will be either denied or allowed access to the content based on <code>method</code> attribute&#8217;s setting.</p>\n\n<p>The tag can be used both as a container and as a self-closing single tag. When used as single tag, the tag invokes an error page when the visitor is within denied range. As a container, the tag hides the wrapped content, but doesn&#8217;t invoke an error page. A container can also take a <a href=\"http://textpattern.net/wiki/index.php?title=else\" rel=\"nofollow\">else</a> statement, which is served as an alternative content to denied visitors.</p>\n\n<h2>Attributes</h2>\n\n<p>Attributes for <code>&lt;txp:rah_ip_range /&gt;</code> are as follows.</p>\n\n<p><strong>message</strong><br />\nError message shown to blocked visitors. The message can be displayed with the <a href=\"http://textpattern.net/wiki/index.php?title=error_message\" rel=\"nofollow\">error_message</a> tag.<br />\nExample: <code>message=&quot;Sorry, we can not do that.&quot;</code> Default: <code>&quot;Forbidden.&quot;</code></p>\n\n<p><strong>status</strong><br />\n<span class=\"caps\">HTTP</span> error status code returned to blocked visitors. The option is ignored when the tag is used as a container.<br />\nExample: <code>status=&quot;404&quot;</code> Default: <code>&quot;403&quot;</code></p>\n\n<p><strong>fromip</strong><br />\nIP-range&#8217;s starting IP (or partial IP). Default is unset.<br />\nExample: <code>fromip=&quot;127.0.0.1&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>toip</strong><br />\nIP-range&#8217;s ending IP (or partial IP). Default is unset.<br />\nExample: <code>toip=&quot;127.0.0.100&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>method</strong><br />\nSets what to do to the visitor when IP is within the range. Either <code>deny</code> or <code>allow</code>. When set to <code>deny</code>, all visitors within the range are blocked, and others outside the range are allowed to see the content. Value <code>allow</code> is opposite to the <code>deny</code>.<br />\nExample: <code>method=&quot;deny&quot;</code> Default: <code>&quot;allow&quot;</code></p>\n\n<h2>Examples</h2>\n\n<h3>Using the self-closing single tag</h3>\n\n<pre><code>&lt;txp:rah_ip_range fromip=&quot;127.0.0.1&quot; toip=&quot;127.0.0.100&quot; method=&quot;deny&quot; message=&quot;Access denied.&quot; /&gt;</code></pre>\n\n<p>Visitors within the range of 127.0.0.1-127.0.0.100 are blocked will see an error page.</p>\n\n<h3>Using the tags as a container</h3>\n\n<pre><code>&lt;txp:rah_ip_range fromip=&quot;127.0.0.1&quot; toip=&quot;127.0.0.100&quot;&gt;\n	&lt;a href=&quot;http://example.com&quot;&gt;Important link&lt;/a&gt;.\n&lt;txp:else /&gt;\n	Link is hidden.\n&lt;/txp:rah_ip_range&gt;</code></pre>\n\n<p>Visitors within the range 127.0.0.1-127.0.0.100 will see an important link, while others won&#8217;t. Instead they are treated with <em>“Link is hidden.”</em> message where the link would be.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.4 &#8211; 2012/07/13</h3>\n\n<ul>\n	<li>Added: <a href=\"http://textpattern.net/wiki/index.php?title=else\" rel=\"nofollow\">else</a> (<code>&lt;txp:else /&gt;</code>) tag support for container mode.</li>\n	<li>Changed: default <span class=\"caps\">HTTP</span> status to <em>403 Forbidden</em> from <em>503 Service Unavailable</em>.</li>\n	<li>Changed: default message to <code>Forbidden.</code>, <code>403_forbidden</code> l18n string.</li>\n	<li>Changed: <code>message</code> attribute doesn&#8217;t apply when tag is used as a container, instead of <a href=\"http://textpattern.net/wiki/index.php?title=else\" rel=\"nofollow\">else</a> can be used.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/06/29</h3>\n\n<ul>\n	<li>Changed: parsing contents of <code>message</code> isn&#8217;t needed. Parser&#8217;s straight quote feature can be used instead.</li>\n	<li>Changed: pass parameters to <code>txp_die()</code> as strings instead of an array.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2011/06/25</h3>\n\n<ul>\n	<li>Added: container tag mode.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/10/31</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_ip_range plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_ip_range\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_ip_range($atts, $thing=NULL) {\n\n		extract(lAtts(array(\n			\'message\' => gTxt(\'403_forbidden\'),\n			\'status\' => \'403\',\n			\'fromip\' => \'\',\n			\'toip\' => \'\',\n			\'method\' => \'allow\'\n		), $atts));\n\n		$from = ip2long($fromip);\n		$to = ip2long($toip);\n		$user_ip = ip2long(remote_addr());\n		$check = $user_ip >= $from && $user_ip <= $to;\n\n		if(\n			($method != \'allow\' && $check) ||\n			($method == \'allow\' && !$check)\n		) {\n			if($thing === NULL) {\n				txp_die($message, $status);\n			}\n\n			return parse(EvalElse($thing, false));\n		}\n\n		return $thing !== NULL ? parse(EvalElse($thing, true)) : \'\';\n	}\n', '\n\n/**\n * Rah_ip_range plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_ip_range\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_ip_range($atts, $thing=NULL) {\n\n		extract(lAtts(array(\n			\'message\' => gTxt(\'403_forbidden\'),\n			\'status\' => \'403\',\n			\'fromip\' => \'\',\n			\'toip\' => \'\',\n			\'method\' => \'allow\'\n		), $atts));\n\n		$from = ip2long($fromip);\n		$to = ip2long($toip);\n		$user_ip = ip2long(remote_addr());\n		$check = $user_ip >= $from && $user_ip <= $to;\n\n		if(\n			($method != \'allow\' && $check) ||\n			($method == \'allow\' && !$check)\n		) {\n			if($thing === NULL) {\n				txp_die($message, $status);\n			}\n\n			return parse(EvalElse($thing, false));\n		}\n\n		return $thing !== NULL ? parse(EvalElse($thing, true)) : \'\';\n	}\n', '03c32a6865ad1e6b3725fcf4559a6beb', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_link_search', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4', 'Search links like articles', '	<h1>rah_link_search</h1>\n\n	<p>Extends <a href=\"http://textpattern.com\">Textpattern&#8217;s</a> search to links.</p>\n\n	<ul>\n		<li>Version: 0.4</li>\n		<li>Updated: 2012/01/29 10:30 AM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#examples\">Examples</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Search results are customizable with Textpattern&#8217;s link forms.</li>\n		<li>A simple tag, <code>&lt;txp:rah_link_search /&gt;</code>. The tag shows search results.</li>\n		<li>The search works with the standard core <code>?q</code> <span class=\"caps\">HTTP</span> <span class=\"caps\">GET</span> search. Core conditionals and forms can work too, including <code>&lt;txp:if_search /&gt;</code>.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 4.3.0+</li>\n		<li>Textpattern 4.3.0+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste plugin code to the plugin installer textarea and run the automatic setup. When done, just activate the plugin and you are ready to use the new tag, <code>&lt;txp:rah_link_search /&gt;</code>.</p>\n\n	<p>You can now use <code>&lt;txp:rah_link_search /&gt;</code> to display links on your search results page. It works same as <code>&lt;txp:article /&gt;</code> would.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<p>The tag is <code>&lt;txp:rah_link_search /&gt;</code> and attributes for it follow.</p>\n\n	<p><strong>form</strong><br />\nTextpattern&#8217;s link form used to display search results.<br />\nExample: <code>form=&quot;linkform&quot;</code> Default: <code>&quot;plainlinks&quot;</code></p>\n\n	<p><strong>wraptag</strong><br />\nThe (X)HTML tag, without brackets used to wrap the output. Default is unset.<br />\nDefault: <code>wraptag=&quot;&quot;</code> Example: <code>&quot;div&quot;</code></p>\n\n	<p><strong>break</strong><br />\nThe (X)HTML tag (without brackets) or string to separate list items. Default is unset.<br />\nDefault: <code>break=&quot;&quot;</code> Example: <code>&quot;br&quot;</code></p>\n\n	<p><strong>class</strong><br />\nThe (X)HTML class applied to the wraptag. Default is unset.<br />\nDefault: <code>class=&quot;&quot;</code> Example: <code>&quot;plugin&quot;</code></p>\n\n	<p><strong>grand_total</strong><br />\nOmit setting result count. If the attribute is set to <code>&quot;0&quot;</code> (zero), <a href=\"http://textpattern.net/wiki/index.php?title=search_result_count\">search_result_count</a> won&#8217;t be updated with the number of results. This attribute could be helpful if rah_link_search is used in conjunction with <a href=\"http://textpattern.net/wiki/index.php?title=article\">article</a> tags, as article tags won&#8217;t update the count once it&#8217;s already set.<br />\nDefault: <code>grand_total=&quot;1&quot;</code> Example: <code>&quot;0&quot;</code>.</p>\n\n	<h3 id=\"examples\">Examples</h3>\n\n	<p><strong>Example #1:</strong> Outputs search results (if any) and the search form.</p>\n\n<pre><code>&lt;txp:search_input /&gt;\n&lt;txp:rah_link_search /&gt;\n</code></pre>\n\n	<p><strong>Example #2:</strong> Used as a container tag.</p>\n\n<pre><code>&lt;txp:rah_link_search&gt;\n	&lt;h3&gt;&lt;txp:link /&gt;&lt;/h3&gt;\n	&lt;p&gt;&lt;txp:link_description /&gt;&lt;/p&gt;\n&lt;/txp:rah_link_search&gt;\n</code></pre>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Word mathcing is now identical with <a href=\"http://rahforum.biz/plugins/rah_file_search\">rah_file_search</a>. Entries are matched for all searched words, not for any.</li>\n		<li>In a container mode empty string is accepted as a wrapped content. Form isn&#8217;t fetched even when nothing is wrapped, as long as the tag is used as a container.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Searches are case-insensitive.</li>\n		<li>Searches can match multiple words.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Added new attributes: <code>grand_total</code>, <code>wraptag</code>, <code>class</code> and <code>break</code>.</li>\n		<li>Changed the matching syntax.</li>\n		<li>Fixed query escaping.</li>\n		<li>Now requires Textpattern version 4.3.0 or newer.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>Initial release.</li>\n	</ul>', '	##################\n	#\n	#	rah_link_search-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	function rah_link_search($atts, $thing = NULL) {\n		\n		extract(lAtts(array(\n			\'form\' => \'plainlinks\',\n			\'grand_total\' => 1,\n			\'wraptag\' => \'\',\n			\'break\' => \'\',\n			\'class\' => \'\'\n		),$atts));\n		\n		global $pretext, $has_article_tag, $thispage, $thislink;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = 0;\n		\n		if(empty($pretext[\'q\']))\n			return;\n		\n		$has_article_tag = true;\n		$q = trim($pretext[\'q\']);\n		$quoted = ($q[0] === \'\"\') && ($q[strlen($q)-1] === \'\"\');\n		$q = doSlash($quoted ? trim(trim($q, \'\"\')) : $q);\n		\n		$q = \n			preg_replace(\'/\\s+/\', \' \', \n				str_replace(\n					array(\'\\\\\',\'%\',\'_\',\'\\\'\'),\n					array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'),\n					$q\n				)\n			);\n		\n		$fields =\n			array(\n				\'linkname\',\n				\'description\',\n				\'url\'\n			);\n		\n		if($quoted)\n			foreach($fields as $field)\n				$sql[] = \"lower($field) like lower(\'%$q%\')\";\n		else {\n			$words = explode(\' \', $q);\n\n			foreach($words as $word) {\n				$fsql = array();\n				\n				foreach($fields as $field)\n					$fsql[] = \"lower($field) like lower(\'%$word%\')\";\n				\n				$sql[] = \'(\'.implode(\' or \', $fsql).\')\';\n			}\n		}\n\n		$rs = \n			safe_rows(\n				\'*, unix_timestamp(date) as uDate\',\n				\'txp_link\',\n				implode(($quoted ? \' or \' : \' and \'), $sql)\n			);\n		\n		if(!$rs)\n			return;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = count($rs);\n			\n		if($thing === NULL)\n			$thing = fetch_form($form);\n		\n		$out = array();\n		\n		foreach($rs as $a) {\n			extract($a);\n			$thislink = \n				array(\n					\'id\' => $id,\n					\'linkname\' => $linkname,\n					\'url\' => $url,\n					\'description\' => $description,\n					\'date\' => $uDate,\n					\'category\' => $category,\n					\'author\' => $author\n				)\n			;\n			$out[] = parse($thing);\n			$thislink = \'\';\n		}\n\n		return doWrap($out, $wraptag, $break, $class);\n	}', '	##################\n	#\n	#	rah_link_search-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	function rah_link_search($atts, $thing = NULL) {\n		\n		extract(lAtts(array(\n			\'form\' => \'plainlinks\',\n			\'grand_total\' => 1,\n			\'wraptag\' => \'\',\n			\'break\' => \'\',\n			\'class\' => \'\'\n		),$atts));\n		\n		global $pretext, $has_article_tag, $thispage, $thislink;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = 0;\n		\n		if(empty($pretext[\'q\']))\n			return;\n		\n		$has_article_tag = true;\n		$q = trim($pretext[\'q\']);\n		$quoted = ($q[0] === \'\"\') && ($q[strlen($q)-1] === \'\"\');\n		$q = doSlash($quoted ? trim(trim($q, \'\"\')) : $q);\n		\n		$q = \n			preg_replace(\'/\\s+/\', \' \', \n				str_replace(\n					array(\'\\\\\',\'%\',\'_\',\'\\\'\'),\n					array(\'\\\\\\\\\',\'\\\\%\',\'\\\\_\', \'\\\\\\\'\'),\n					$q\n				)\n			);\n		\n		$fields =\n			array(\n				\'linkname\',\n				\'description\',\n				\'url\'\n			);\n		\n		if($quoted)\n			foreach($fields as $field)\n				$sql[] = \"lower($field) like lower(\'%$q%\')\";\n		else {\n			$words = explode(\' \', $q);\n\n			foreach($words as $word) {\n				$fsql = array();\n				\n				foreach($fields as $field)\n					$fsql[] = \"lower($field) like lower(\'%$word%\')\";\n				\n				$sql[] = \'(\'.implode(\' or \', $fsql).\')\';\n			}\n		}\n\n		$rs = \n			safe_rows(\n				\'*, unix_timestamp(date) as uDate\',\n				\'txp_link\',\n				implode(($quoted ? \' or \' : \' and \'), $sql)\n			);\n		\n		if(!$rs)\n			return;\n		\n		if($grand_total == 1)\n			$thispage[\'grand_total\'] = count($rs);\n			\n		if($thing === NULL)\n			$thing = fetch_form($form);\n		\n		$out = array();\n		\n		foreach($rs as $a) {\n			extract($a);\n			$thislink = \n				array(\n					\'id\' => $id,\n					\'linkname\' => $linkname,\n					\'url\' => $url,\n					\'description\' => $description,\n					\'date\' => $uDate,\n					\'category\' => $category,\n					\'author\' => $author\n				)\n			;\n			$out[] = parse($thing);\n			$thislink = \'\';\n		}\n\n		return doWrap($out, $wraptag, $break, $class);\n	}', 'cdbf2ea6ff171606a0244d754a9865cc', '', '', 0, 5, 0),
('rah_metas', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.5', 'Outputs simple metas and prevents double content', '	<h1>rah_metas</h1>\n\n	<p>A tiny <abbr title=\"Search engine optimization\"><span class=\"caps\">SEO</span></abbr>- and meta-tool for <a href=\"http://www.textpattern.com\">Textpattern <span class=\"caps\">CMS</span>.</a> Build descriptions, simple meta info, rel links and prevent double content.</p>\n\n	<ul>\n		<li>Version: 1.5</li>\n		<li>Updated: 2011/10/29 11:20 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#intro-and-description\">Intro and description</a></li>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#example\">Examples</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n		<li><a href=\"#known-issues\">Known issues</a></li>\n		<li><a href=\"#thanks-to\">Thanks to</a></li>\n	</ul>\n\n	<h3 id=\"intro-and-description\">Intro and description</h3>\n\n	<p>A Textpattern plugin used for <abbr title=\"Search engine optimization\"><span class=\"caps\">SEO</span></abbr>. The plugin includes basic <span class=\"caps\">SEO</span> tools, from semi-automatic metainformation generation to useful redirections that prevent double content as rah_metas is also able redirect requested messy urls to cleans, leaving no space for the double content fault.</p>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li><strong>Outputs automatic metadescriptions</strong> from article&#8217;s body, excerpt or custom field.</li>\n		<li><strong>Limits and customizes the automatic metadescription</strong> by word or character limit, strips all <span class=\"caps\">XHTML</span> and invalid characters.</li>\n		<li><strong>Redirects messy urls to clean ones, if cleans are in use</strong>. In example, <code>?id=xx</code> and <code>?s=xx</code> would be redirect to the correct place, preventing possible double content.</li>\n		<li><strong>Outputs other useful meta</strong> as author, description and language.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Minimum requirements:</p>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 5+</li>\n		<li>Textpattern 4.0.4+</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 5.1.2+</li>\n		<li>Textpattern 4.4.1+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use new tags that plugin includes like others.</p>\n\n	<p>For usage, basically just put <code>&lt;txp:rah_metas /&gt;</code>-tag with wanted attributes to inside your site&#8217;s <code>&lt;head&gt;</code>-tags. As the plugin&#8217;s tag outputs metainformation, and meta-tags, it shouldn&#8217;t be used outside <code>&lt;head&gt;</code>-tags.</p>\n\n	<h3 id=\"attributes\">Attributes</h3>\n\n	<p>The tag is <code>&lt;txp:rah_metas /&gt;</code> and attributes for it follow. Default value <code>&quot;&quot;</code> means that the attribute is unset and by default not used.</p>\n\n	<p><strong>imagetoolbar</strong><br />\nTo block an browser&#8217;s image toolbar. Allowed attributes are <code>true</code> and <code>false</code>. By default not used.<br />\nExample: <code>imagetoolbar=&quot;false&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>language</strong><br />\nThe language used by the site. Value should be a language code, for example <code>en</code>, <code>de</code>, <code>it</code> or <code>se</code>.<br />\nExample: <code>language=&quot;en&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>robots</strong><br />\nDirects how robots should navigate on the page. Valid values include <code>index</code>, <code>noindex</code>, <code>follow</code>, <code>nofollow</code>. The values can be combined.<br />\nExample: <code>robots=&quot;index, follow&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>author</strong><br />\nAuthor name that you want to show as the page&#8217;s author. Usually you, company or the website&#8217;s name. This value is used as the fallback when <code>useauthor</code> is used, but no author is available.<br />\nExample: <code>author=&quot;John Doe&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>useauthor</strong><br />\nIf set to <code>1</code>, <code>&lt;txp:author /&gt;</code> is used to populate author metadata when possible. The value set with <code>author</code> attribute will be used as a fallback in list context and such.<br />\nExample: <code>useauthor=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n	<p><strong>copyright</strong><br />\nOutputs copyright meta. Value is used as the content.<br />\nExample: <code>copyright=&quot;Copyrihts &amp;copy; 2007 John Doe&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<h4>Description related attributes</h4>\n\n	<p><strong>description</strong><br />\nA default meta description that will be shown in list context, or when there isn&#8217;t an article description to show and <code>description_replacement</code> is set on.<br />\nExample: <code>description=&quot;Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce at dui eget est congue aliquet. Quisque sed nunc&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>description_from</strong><br />\nWhich artile field is used for the automatic meta description. The attribute takes comma separated list of fields. The first field in the list that isn&#8217;t empty is used as the meta description. Available values: <code>body</code>, <code>excerpt</code> or custom field&#8217;s name. If set empty, the automatic description isn&#8217;t used.<br />\nExample: <code>description_from=&quot;body,excerpt,metadescription&quot;</code>  Default: <code>&quot;body,excerpt&quot;</code></p>\n\n	<p><strong>words</strong><br />\nLimits the amount of words shown in the automatic meta description.<br />\nExample: <code>words=&quot;15&quot;</code> Default: <code>&quot;25&quot;</code></p>\n\n	<p><strong>maxchars</strong><br />\nThe maximum number of characters used in the generated automatic meta description.<br />\nExample: <code>maxchars=&quot;170&quot;</code> Default: <code>&quot;250&quot;</code></p>\n\n	<p><strong>description_trail</strong><br />\nEnding trail used in the end of the automatic metadescription. Trail is only shown if the description is longer than the limit set by <code>maxchars</code> and/or <code>words</code>.<br />\nExample: <code>description_trail=&quot;&quot;</code> Default:  <code>&quot;&amp;hellip;&quot;</code></p>\n\n	<p><strong>description_replacement</strong><br />\nIf set on (<code>1</code>) and there isn&#8217;t automatic description available, <code>description</code> attribute is used as the description instead. By default this feature is not used.<br />\nExample: <code>description_replacement=&quot;1&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>escape</strong><br />\nIf set to <code>1</code>, will remove raw Textile markup from the description. By default the attribute is unset, and the feature is not used. Usually this feature isn&#8217;t required, but it might be good if you are supplying the description in a custom field and you are using Textile there. Textile in custom fields isn&#8217;t, by default, parsed by Textpattern.<br />\nExample: <code>escape=&quot;1&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<h4>Keyword related attributes</h4>\n\n	<p><strong>keywords</strong><br />\nDefault keywords that will be used as keyword meta in a list context, or when there isn&#8217;t any keywords set for an article and <code>keywords_replacement</code> attribute is set on.<br />\nExample: <code>keywords=&quot;life, Doe, cat, dog&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>keywords_from</strong><br />\nWhich field is used for the keyword meta. The attribute takes comma separated list of fields. The first field in the list that isn&#8217;t empty is used as the keywords. Available values, any article field: <code>keywords</code>, <code>body</code>, <code>excerpt</code> or custom field&#8217;s name. If empty, the feature isn&#8217;t used.<br />\nExample: <code>keywords_from=&quot;keywords,excerpt&quot;</code>  Default: <code>&quot;keywords&quot;</code></p>\n\n	<p><strong>keywords_replacement</strong><br />\nIf there isn&#8217;t keywords available for an article, use default defined in <code>keywords</code> attribute instead.<br />\nExample: <code>keywords_replacement=&quot;1&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>keywords_limit</strong><br />\nLimits the words used in the keywords. Takes a number.<br />\nExample: <code>keywords_limit=&quot;5&quot;</code> Default: <code>&quot;25&quot;</code></p>\n\n	<h4>Redirect related attributes</h4>\n\n	<p><strong>messy_to_clean_redirect</strong><br />\nUse messy to clean redirect. Redirects messy requrests to clean urls. Use this only if you use clean urls. With messy urls this only causes eternal loop.<br />\nExample: <code>messy_to_clean_redirect=&quot;1&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>redirect_code</strong><br />\nThe code of redirection, value should be <a href=\"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\">three numeric stannard status code protocol.</a><br />\nExample: <code>redirect_code=&quot;302&quot;</code> Default: <code>&quot;301&quot;</code></p>\n\n	<h4>Rel link related attributes</h4>\n\n	<p><strong>relnext</strong><br />\nUse rel link to point next page&#8217;s location to the browser. <code>relnext</code> attribute&#8217;s value is used as <code>&lt;link rel=&quot;next&quot; /&gt;</code> tag&#8217;s <code>title</code>. Ideal values are <code>Next</code>, <code>Go to next</code> and so on. The attribute, <code>relnext</code>, adds a next rel link meta that points to the next section page or article by posting date. If attribute is used (filled) then used, if unset (empty) then not.<br />\nExample: <code>relnext=&quot;Next&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>relprev</strong><br />\nUse rel link to point previous page&#8217;s location to the browser. <code>relprev</code> attribute&#8217;s value is used as <code>&lt;link rel=&quot;prev&quot; /&gt;</code> tag&#8217;s <code>title</code>. Ideal values are <code>Previous</code>, <code>Go to prev</code> and so on. The attribute, <code>relprev</code>, adds a next rel link meta that points to the previous section page or article by posting date. If attribute is used (filled) then used, if unset (empty) then not.<br />\nExample: <code>relprev=&quot;Previous&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>prev_url</strong><br />\nManually defines relative prev url. Value should be a <span class=\"caps\">URL</span> address. The <span class=\"caps\">URL</span> is only used if automatic <span class=\"caps\">URL</span> isn&#8217;t found.<br />\nExample: <code>prev_url=&quot;http://www.example.com/path/to/id&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<p><strong>next_url</strong><br />\nManually defines relative next url. Value should be a <span class=\"caps\">URL</span> address. The <span class=\"caps\">URL</span> is only used if automatic <span class=\"caps\">URL</span> isn&#8217;t found.<br />\nExample: <code>prev_url=&quot;http://www.example.com/path/to/id&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n	<h3 id=\"example\">Examples</h3>\n\n	<p><strong>#1: Basic usage</strong></p>\n\n<pre><code>&lt;txp:rah_metas keywords=&quot;dog, cat, house&quot; description=&quot;Lorem lipsum&quot; maxchars=&quot;170&quot; words=&quot;15&quot; /&gt;\n</code></pre>\n\n	<p><strong>#2: Using content preferring.</strong> First look at the custom field, then main article (excerpt, body) fields.</p>\n\n<pre><code>&lt;txp:rah_metas description_from=&quot;MyCustomFieldName,excerpt,body&quot; /&gt;\n</code></pre>\n\n	<p><strong>#3: Use rah_metas tag inside <a href=\"http://textpattern.net/wiki/index.php?title=article_custom\">article_custom</a> block</strong></p>\n\n	<p>If rah_metas is placed directly inside article_custom (or article) tag, it can be used to pull the meta data from that specific article.</p>\n\n<pre><code>&lt;txp:article_custom id=&quot;5&quot;&gt;\n	&lt;txp:rah_metas /&gt;\n&lt;/txp:article_custom&gt;\n</code></pre>\n\n	<p>In the code above, we took the meta data from the article where the ID is 5.</p>\n\n	<p><strong>#4: How to not output description at all, but keep all other features intact?</strong></p>\n\n<pre><code>&lt;txp:rah_metas description=&quot;&quot; description_from=&quot;&quot; /&gt;\n</code></pre>\n\n	<p>If <code>description</code> and <code>description_from</code> are set empty, no description will be generated. Same applies to keywords (see <code>keywords</code> and <code>keywords_from</code> attributes).</p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 1.5</strong></p>\n\n	<ul>\n		<li>Wrapped set of extra functions into a class, and removed the prefixing from the new methods.</li>\n		<li>Added comments, cleaned up some code.</li>\n		<li>Removed <code>rah_metas_textile()</code> function.</li>\n	</ul>\n\n	<p><strong>Version 1.4</strong></p>\n\n	<ul>\n		<li>Now automatically converts values supplied in <code>keywords_from</code> and <code>description_from</code> to lowercase. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=236051#p236051\">Thank you for reporting, pompilos</a>.</li>\n		<li>Now uses <code>lAtts()</code> just once.</li>\n		<li>Removed dublicated <code>trim()</code> from <code>rah_metas_strip()</code>.</li>\n		<li>Removed <code>rah_metas_atts()</code>.</li>\n	</ul>\n\n	<p><strong>Version 1.3</strong></p>\n\n	<ul>\n		<li>Now <code>description_from</code> and <code>keywords_from</code> take comma separated list of fields. The first field in the list that isn&#8217;t empty is used as keywords or description.</li>\n		<li>Removed attribute <code>prefercontent</code>. Using the attribute will throw notice in testing or debugging mode.</li>\n	</ul>\n\n	<p><strong>Version 1.2</strong></p>\n\n	<ul>\n		<li>Now the <code>&lt;txp:rah_metas/&gt;</code> tag can be also used <em>directly inside</em> <code>&lt;txp:article_custom/&gt;</code> and <code>&lt;txp:article/&gt;</code> blocks. That means that the tag now works in article lists. You just have to point it to the article. See example number 3.</li>\n		<li>Now <em>any</em> article field can be used in <code>description_from</code> and <code>keywords_from</code> attributes. Even title, authorid, override_form or status.</li>\n		<li>Now dublicate keyword &#8220;uniqueing&#8221; is done with <code>array_unique()</code>.</li>\n		<li>Fixed keywords code escaping.</li>\n	</ul>\n\n	<p><strong>Version 1.1</strong></p>\n\n	<ul>\n		<li>Added: attribute <code>keywords_limit</code>.</li>\n		<li>Added: removes doublicated words from keywords.</li>\n		<li>Added: list view&#8217;s description is effected by word and character limits.</li>\n		<li>Fixed double <code>&quot;=&quot;</code> define bug.</li>\n		<li>Now <code>escape</code> attribute effects also body and expert, not only custom fields.</li>\n		<li>Now keywords are escaped, striped for tags and parsed.</li>\n		<li>Counts white space into chars.</li>\n		<li>Removed imagetoolbar attribute validation.</li>\n	</ul>\n\n	<p><strong>Version 1.0.5</strong></p>\n\n	<ul>\n		<li>Added attribute:  <code>escape</code>.</li>\n		<li>Rebuild description&#8217;s word and chars counting: now always counts correctly: doesn&#8217;t ever cut words.</li>\n		<li>Optimized parsing and speed: plugin is around 20% faster than before.</li>\n		<li>Removes <code>&amp;#8230;</code> from end of the description if trails are used and description is cut from context and shortened.</li>\n	</ul>\n\n	<p><strong>Version 1.0.4</strong></p>\n\n	<ul>\n		<li>Added attribute:  <code>relnext</code>.</li>\n		<li>Added attribute: <code>relprev</code>.</li>\n		<li>Minified amount of used globals by one.</li>\n	</ul>\n\n	<p><strong>Version 1.0.3</strong></p>\n\n	<ul>\n		<li>Fixed last words truncate bug.</li>\n	</ul>\n\n	<p><strong>Version 1.0.2</strong></p>\n\n	<ul>\n		<li>Fixed some typos in documention.</li>\n		<li>Now <code>prefercontent</code> also works with custom_fields.</li>\n	</ul>\n\n	<p><strong>Version 1.0.1</strong></p>\n\n	<ul>\n		<li>Debugged version 1.0: status now stable.</li>\n		<li>Fixed a bug: now whitespace is stripped from beginning and end of the description content.</li>\n	</ul>\n\n	<p><strong>Version 1.0</strong></p>\n\n	<ul>\n		<li>Improved documentation.</li>\n		<li>Little speed improvement.</li>\n		<li>Removed <code>charset</code>-attribute, it ain&#8217;t needed as Textpattern sends charset via headers &#8211; and it must not differ.</li>\n		<li>Merged <code>customfieldexcerpt</code>-attribute with <code>description_from</code>.</li>\n		<li>Included redirecting functions that prevent double content when using clean urls.</li>\n		<li>Validates <code>imagetoolbar</code>-attribute to false or true.</li>\n		<li>Added attribute: <code>description_replacement</code>.</li>\n		<li>Added attribute: <code>keywords_replacement</code>.</li>\n		<li>Added attribute: <code>description_from</code></li>\n		<li>Added attribute: <code>messy_to_clean_redirect</code>.</li>\n		<li>Added attribute: <code>redirect_code</code>.</li>\n		<li>Added attribute: <code>description_trail</code>.</li>\n		<li>Added invalid charasters removing for custom fields.</li>\n		<li>Removed Textile removing from body and excerpt, that was meant to remove Textile + <span class=\"caps\">XHTML</span> bugs caused by user&#8217;s invalid article code, and used instead body_html, and just stripped <span class=\"caps\">HTML</span>: it&#8217;s faster.</li>\n		<li>Some users wanted to be able input invalid characters to description: now it is possible, if you turn Textile off: why should we validate meta description, if article&#8217;s body itself is invalid, because writer doesn&#8217;t use valid <span class=\"caps\">HTML</span> special characters.</li>\n	</ul>\n\n	<p><strong>Version minor 0.9.2</strong></p>\n\n	<ul>\n		<li>One bug fix, created by little one chars typo in the code.</li>\n	</ul>\n\n	<p><strong>Version minor 0.9.1</strong></p>\n\n	<ul>\n		<li>Removed a debugging error, caused by undefined variable if there were no description.</li>\n	</ul>\n\n	<p><strong>Version 0.9</strong></p>\n\n	<ul>\n		<li>Added attribute: <code>copyright</code>.</li>\n		<li>Added attribute: <code>useauthor</code>.</li>\n		<li>Added attribute: <code>customfieldkeywords</code>.</li>\n		<li>Added attribute: <code>customfieldexcerpt</code>.</li>\n	</ul>\n\n	<p><strong>Version 0.8</strong></p>\n\n	<ul>\n		<li>Added Textile remover: removes Textile completely from description</li>\n		<li>Cleaned up the code</li>\n	</ul>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Fixed bug that appears if single article has no body or excerpt. Only shown in debugging.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Fixed a code-removing bug</li>\n		<li>Now removes all code from automatic metadescription</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Finished bug-testing</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Add missing variable</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Compined two separated functions</li>\n		<li>Improved the code by fixing globals</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Replaced automatic-medescription&#8217;s ending points <code>&amp;#8230;</code> with <code>&amp;hellip;</code></li>\n		<li>Added new attribute: <code>imagetoolbar</code></li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release</li>\n	</ul>\n\n	<h3 id=\"known-issues\">Known issues</h3>\n\n	<p>Redirects might not always work on old fast-cgi (requires clarifation), but on other systems fine.</p>\n\n	<h3 id=\"thanks-to\">Thanks to</h3>\n\n	<ul>\n		<li>All folks that thanked me by making this plugin available.</li>\n		<li>rolograaf and pat64 for great ideas of keywords.</li>\n		<li>fuls, strawberryfusion, jim_01, pompilos and ian_ep for reporting me some bugs.</li>\n		<li>maniqui for the ideas.</li>\n		<li>And all the other great Textpatternists.</li>\n	</ul>', '	##################\n	#\n	#	rah_metas-plugin for Textpattern\n	#	version 1.5\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n/**\n	The tag, output meta tags\n*/\n\n	function rah_metas($atts=array()) {\n		global $is_article_list, $thisarticle;\n		\n		$atts = \n			lAtts(array(\n				\'keywords\' => \'\',\n				\'keywords_from\' => \'keywords\',\n				\'keywords_replacement\' => \'\',\n				\'keywords_limit\' => \'25\',\n				\'description\' => \'\',\n				\'description_from\' => \'body,excerpt\',\n				\'description_trail\' => \'&hellip;\',\n				\'description_replacement\' => \'\',\n				\'escape\' => \'\',\n				\'maxchars\' => \'250\',\n				\'words\' => \'25\',\n				\'author\' => \'\',\n				\'useauthor\' => 0,\n				\'robots\' => \'\',\n				\'imagetoolbar\' => \'\',\n				\'copyright\' => \'\',\n				\'language\' => \'\',\n				\'messy_to_clean_redirect\' => 0,\n				\'redirect_code\' => \'301\',\n				\'relnext\' => \'\',\n				\'relprev\' => \'\',\n				\'prev_url\' => \'\',\n				\'next_url\' => \'\',\n			),$atts)\n		;\n\n		extract($atts);\n\n		$r = new rah_metas_pkg();\n		$author = $useauthor && !empty($thisarticle) ? author(array()) : $author;\n		$description = $r->description($atts);\n		$keywords = $r->keywords($atts);\n		\n		if($is_article_list == true) {\n			if($relprev)\n				$prev_url = older(array(),false);\n			if($relnext)\n				$next_url = newer(array(),false);\n		} else {\n			if($relprev)\n				$prev_url = link_to_prev(array(),false);\n			if($relnext)\n				$next_url = link_to_next(array(),false);\n		}\n\n		$out = array();\n\n		if($imagetoolbar)\n			$out[] = \'<meta http-equiv=\"imagetoolbar\" content=\"\'.$imagetoolbar.\'\" />\';\n		if($language)\n			$out[] = \'<meta http-equiv=\"content-language\" content=\"\'.$language.\'\" />\';\n		if($copyright) \n			$out[] = \'<meta name=\"copyright\" content=\"\'.$copyright.\'\" />\';\n		if($robots) \n			$out[] = \'<meta name=\"robots\" content=\"\'.$robots.\'\" />\';\n		if($author)\n			$out[] = \'<meta name=\"author\" content=\"\'.$author.\'\" />\';\n		if($keywords) \n			$out[] = \'<meta name=\"keywords\" content=\"\'.$keywords.\'\" />\';\n		if($description)\n			$out[] = \'<meta name=\"description\" content=\"\'.$description.\'\" />\';\n		if($prev_url)\n			$out[] = \'<link rel=\"prev\" href=\"\'.$prev_url.\'\" title=\"\'.$relprev.\'\" />\';\n		if($next_url)\n			$out[] = \'<link rel=\"next\" href=\"\'.$next_url.\'\" title=\"\'.$relnext.\'\" />\';\n		\n		if($messy_to_clean_redirect) {\n			if(gps(\'s\')) \n				header(\'Location: \'.pagelinkurl(array(\'s\' => gps(\'s\'))),TRUE,$redirect_code);\n			elseif(is_numeric(gps(\'id\'))) \n				header(\'Location: \'.permlink(array(\'id\' => gps(\'id\'))),TRUE,$redirect_code);\n		}\n\n		return implode(n,$out);\n	}\n\nclass rah_metas_pkg {\n	\n/**\n	Builds keywords\n	@param $atts array Tag attributes.\n	@return string List of keywords.\n*/\n\n	public function keywords($atts) {\n		extract($atts);\n		\n		$out = array();\n		$count = 0;\n		\n		$content = \n			$this->content(\n				$keywords_from,\n				$keywords_replacement,\n				$keywords\n			);\n		\n		if($content) {\n			$content = $this->strip($content);\n			$keywords = explode(\',\',$content);\n			$keywords = array_unique($keywords);\n			\n			foreach($keywords as $keyword) {\n				$keyword = trim($keyword);\n				if(!empty($keyword)) {\n					$count++;\n					$out[] = $keyword;\n				}\n				if($keywords_limit <= $count)\n					return implode(\', \',$out);\n			}\n			\n			$content = implode(\', \',$out);\n		}\n		return $content;\n	}\n\n/**\n	Builds description\n	@param $atts array Tag attributes.\n	@return string Meta description.\n*/\n\n	public function description($atts) {\n		extract($atts);\n\n		$content = \n			$this->content(\n				$description_from,\n				$description_replacement,\n				$description\n			);\n		\n		if($content) {\n			\n			/*\n				Escape Textile\n			*/\n			\n			if($escape) {\n				@include_once txpath.\'/lib/classTextile.php\';\n				$textile = new Textile();\n				$content = $textile->TextileThis($content);\n			}\n			\n			$content = $this->strip($content);\n			$word = array();\n			$count_char = 0;\n			$count_word = 0;\n			$tokens = explode(\' \',$content);\n			foreach($tokens as $token) {\n				$token = trim($token);\n				if(empty($token))\n					continue;\n				if($count_char <= $maxchars && $count_word <= $words)\n					$word[] = $token;\n				else \n					return $this->trail($word).$description_trail;\n				$count_char = strlen($token)+$count_char+1;\n				$count_word++;\n			}\n			$content = implode(\' \',$word);\n		}\n		return $content;\n	}\n	\n/**\n	Gets the content from $thisarticle global variable\n	@param $string string Property to search for.\n	@param $replacement bool Whether fallback is used or not.\n	@param $default string Default if requested property isn\'t found.\n	@return mixed Either the $default, or requested content.\n*/\n\n	protected function content($string,$replacement,$default) {\n		global $thisarticle;\n		\n		if(empty($thisarticle) || empty($string))\n			return $default;\n		\n		$string = strtolower($string);\n		$array = explode(\',\',$string);\n		\n		foreach($array as $field) {\n			$field = trim($field);\n			if(!empty($field) && isset($thisarticle[$field]) && !empty($thisarticle[$field]))\n				return $thisarticle[$field];\n		}\n		\n		if($replacement)\n			return $default;\n	}\n\n/**\n	Removes trail (&#8230;) from the end of the string\n	@param $out string String to check and clean.\n	@return string\n*/\n\n	protected function trail($out) {\n		$content = implode(\' \',$out);\n		if(\n			substr($content, -7, 7) == \'&#8230;\'\n		)\n			$content = substr($content,0,-7);\n		return $content;\n	}\n\n/**\n	Parses TXP markup, and strips valid HTML, invalid code, exceeding whitespace and line breaks.\n	@param $out string String to clean.\n	@return string\n*/\n\n	protected function strip($out) {\n		return \n			trim(\n				str_replace(\n					array(\"\\n\",\"\\r\",\"\\t\",\'\"\',\'>\',\'<\'),\n					array(\' \',\' \',\' \',\'&quot;\',\'&gt;\',\'&lt;\'),\n					strip_tags(\n						parse($out)\n					)\n				)\n			)\n		;\n	}\n}', '	##################\n	#\n	#	rah_metas-plugin for Textpattern\n	#	version 1.5\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n/**\n	The tag, output meta tags\n*/\n\n	function rah_metas($atts=array()) {\n		global $is_article_list, $thisarticle;\n		\n		$atts = \n			lAtts(array(\n				\'keywords\' => \'\',\n				\'keywords_from\' => \'keywords\',\n				\'keywords_replacement\' => \'\',\n				\'keywords_limit\' => \'25\',\n				\'description\' => \'\',\n				\'description_from\' => \'body,excerpt\',\n				\'description_trail\' => \'&hellip;\',\n				\'description_replacement\' => \'\',\n				\'escape\' => \'\',\n				\'maxchars\' => \'250\',\n				\'words\' => \'25\',\n				\'author\' => \'\',\n				\'useauthor\' => 0,\n				\'robots\' => \'\',\n				\'imagetoolbar\' => \'\',\n				\'copyright\' => \'\',\n				\'language\' => \'\',\n				\'messy_to_clean_redirect\' => 0,\n				\'redirect_code\' => \'301\',\n				\'relnext\' => \'\',\n				\'relprev\' => \'\',\n				\'prev_url\' => \'\',\n				\'next_url\' => \'\',\n			),$atts)\n		;\n\n		extract($atts);\n\n		$r = new rah_metas_pkg();\n		$author = $useauthor && !empty($thisarticle) ? author(array()) : $author;\n		$description = $r->description($atts);\n		$keywords = $r->keywords($atts);\n		\n		if($is_article_list == true) {\n			if($relprev)\n				$prev_url = older(array(),false);\n			if($relnext)\n				$next_url = newer(array(),false);\n		} else {\n			if($relprev)\n				$prev_url = link_to_prev(array(),false);\n			if($relnext)\n				$next_url = link_to_next(array(),false);\n		}\n\n		$out = array();\n\n		if($imagetoolbar)\n			$out[] = \'<meta http-equiv=\"imagetoolbar\" content=\"\'.$imagetoolbar.\'\" />\';\n		if($language)\n			$out[] = \'<meta http-equiv=\"content-language\" content=\"\'.$language.\'\" />\';\n		if($copyright) \n			$out[] = \'<meta name=\"copyright\" content=\"\'.$copyright.\'\" />\';\n		if($robots) \n			$out[] = \'<meta name=\"robots\" content=\"\'.$robots.\'\" />\';\n		if($author)\n			$out[] = \'<meta name=\"author\" content=\"\'.$author.\'\" />\';\n		if($keywords) \n			$out[] = \'<meta name=\"keywords\" content=\"\'.$keywords.\'\" />\';\n		if($description)\n			$out[] = \'<meta name=\"description\" content=\"\'.$description.\'\" />\';\n		if($prev_url)\n			$out[] = \'<link rel=\"prev\" href=\"\'.$prev_url.\'\" title=\"\'.$relprev.\'\" />\';\n		if($next_url)\n			$out[] = \'<link rel=\"next\" href=\"\'.$next_url.\'\" title=\"\'.$relnext.\'\" />\';\n		\n		if($messy_to_clean_redirect) {\n			if(gps(\'s\')) \n				header(\'Location: \'.pagelinkurl(array(\'s\' => gps(\'s\'))),TRUE,$redirect_code);\n			elseif(is_numeric(gps(\'id\'))) \n				header(\'Location: \'.permlink(array(\'id\' => gps(\'id\'))),TRUE,$redirect_code);\n		}\n\n		return implode(n,$out);\n	}\n\nclass rah_metas_pkg {\n	\n/**\n	Builds keywords\n	@param $atts array Tag attributes.\n	@return string List of keywords.\n*/\n\n	public function keywords($atts) {\n		extract($atts);\n		\n		$out = array();\n		$count = 0;\n		\n		$content = \n			$this->content(\n				$keywords_from,\n				$keywords_replacement,\n				$keywords\n			);\n		\n		if($content) {\n			$content = $this->strip($content);\n			$keywords = explode(\',\',$content);\n			$keywords = array_unique($keywords);\n			\n			foreach($keywords as $keyword) {\n				$keyword = trim($keyword);\n				if(!empty($keyword)) {\n					$count++;\n					$out[] = $keyword;\n				}\n				if($keywords_limit <= $count)\n					return implode(\', \',$out);\n			}\n			\n			$content = implode(\', \',$out);\n		}\n		return $content;\n	}\n\n/**\n	Builds description\n	@param $atts array Tag attributes.\n	@return string Meta description.\n*/\n\n	public function description($atts) {\n		extract($atts);\n\n		$content = \n			$this->content(\n				$description_from,\n				$description_replacement,\n				$description\n			);\n		\n		if($content) {\n			\n			/*\n				Escape Textile\n			*/\n			\n			if($escape) {\n				@include_once txpath.\'/lib/classTextile.php\';\n				$textile = new Textile();\n				$content = $textile->TextileThis($content);\n			}\n			\n			$content = $this->strip($content);\n			$word = array();\n			$count_char = 0;\n			$count_word = 0;\n			$tokens = explode(\' \',$content);\n			foreach($tokens as $token) {\n				$token = trim($token);\n				if(empty($token))\n					continue;\n				if($count_char <= $maxchars && $count_word <= $words)\n					$word[] = $token;\n				else \n					return $this->trail($word).$description_trail;\n				$count_char = strlen($token)+$count_char+1;\n				$count_word++;\n			}\n			$content = implode(\' \',$word);\n		}\n		return $content;\n	}\n	\n/**\n	Gets the content from $thisarticle global variable\n	@param $string string Property to search for.\n	@param $replacement bool Whether fallback is used or not.\n	@param $default string Default if requested property isn\'t found.\n	@return mixed Either the $default, or requested content.\n*/\n\n	protected function content($string,$replacement,$default) {\n		global $thisarticle;\n		\n		if(empty($thisarticle) || empty($string))\n			return $default;\n		\n		$string = strtolower($string);\n		$array = explode(\',\',$string);\n		\n		foreach($array as $field) {\n			$field = trim($field);\n			if(!empty($field) && isset($thisarticle[$field]) && !empty($thisarticle[$field]))\n				return $thisarticle[$field];\n		}\n		\n		if($replacement)\n			return $default;\n	}\n\n/**\n	Removes trail (&#8230;) from the end of the string\n	@param $out string String to check and clean.\n	@return string\n*/\n\n	protected function trail($out) {\n		$content = implode(\' \',$out);\n		if(\n			substr($content, -7, 7) == \'&#8230;\'\n		)\n			$content = substr($content,0,-7);\n		return $content;\n	}\n\n/**\n	Parses TXP markup, and strips valid HTML, invalid code, exceeding whitespace and line breaks.\n	@param $out string String to clean.\n	@return string\n*/\n\n	protected function strip($out) {\n		return \n			trim(\n				str_replace(\n					array(\"\\n\",\"\\r\",\"\\t\",\'\"\',\'>\',\'<\'),\n					array(\' \',\' \',\' \',\'&quot;\',\'&gt;\',\'&lt;\'),\n					strip_tags(\n						parse($out)\n					)\n				)\n			)\n		;\n	}\n}', '4284d8b60b49a9c46153ed994c5623a9', '', '', 0, 5, 0),
('rah_nocache', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4.0', 'Prevent client-side caching on admin-side panels', '<h1>rah_nocache</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_nocache\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_nocache\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=30963\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_nocache\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_nocache is a simple plugin for <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a> which tries to prevent client-side and proxy caching of admin-side editor forms by sending no-cache <span class=\"caps\">HTTP</span> response headers. The plugin tries to remove all unwanted browser and proxy caching that could break admin-side panels functionality by redirecting requests to your local cache instead of showing the live pages. Usually important control panels and dynamically changing, informative pages shouldn&#8217;t be cached, but should contain the most up-to-date information.</p>\n\n<p>Getting rah_nocache up and running is super simple. Just upload the plugin to your Textpattern installation and activate it. Zero configuration or extra steps required.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_nocache&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_nocache&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<p>The plugin is now installed and activated, and is doing its job.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.4.0 &#8211; 2013/04/17</h3>\n\n<ul>\n	<li>Improved: HTML5 validity.</li>\n	<li>Improved: Prevents caching on Ajax, asynchronous and other script responses.</li>\n	<li>Now requires Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2012/07/12</h3>\n\n<ul>\n	<li>Improved: Some minor clean up, and updated to new source structure.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2011/10/30</h3>\n\n<ul>\n	<li>Added: License to the header comment block.</li>\n	<li>Added: Some comments to the code.</li>\n	<li>Updated: Help file.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/06/03</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_nocache plugin for Textpattern CMS\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_nocache\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nclass rah_nocache\n{\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		$this->send_headers();\n	}\n\n	/**\n	 * Send no-cache HTTP headers.\n	 */\n\n	public function send_headers()\n	{\n		header(\'Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\');\n		header(\'Expires: Sat, 24 Jul 2003 05:00:00 GMT\');\n		header(\'Last-Modified: \'.gmdate(\'D, d M Y H:i:s\').\' GMT\');\n		header(\'Pragma: no-cache\');\n	}\n}\n\nnew rah_nocache();', '\n\n/**\n * Rah_nocache plugin for Textpattern CMS\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_nocache\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nclass rah_nocache\n{\n	/**\n	 * Constructor.\n	 */\n\n	public function __construct()\n	{\n		$this->send_headers();\n	}\n\n	/**\n	 * Send no-cache HTTP headers.\n	 */\n\n	public function send_headers()\n	{\n		header(\'Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\');\n		header(\'Expires: Sat, 24 Jul 2003 05:00:00 GMT\');\n		header(\'Last-Modified: \'.gmdate(\'D, d M Y H:i:s\').\' GMT\');\n		header(\'Pragma: no-cache\');\n	}\n}\n\nnew rah_nocache();', 'ec14b5fa3a41687880ac698647560bd5', '', '', 4, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_plugin_installer', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4', 'Plugin installer for rah-prefixed plugins.', '	<h1>rah_plugin_installer</h1>\n\n	<p>Install and update rah-plugins directly from your <a href=\"http://www.textpattern.com\">Textpattern</a> installation. Click and install, ready to go.</p>\n\n	<ul>\n		<li>Version: 0.4</li>\n		<li>Updated: 2011/07/19 12:20 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#intro-and-description\">Intro and description</a></li>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Simple install: just run the automatic plugin install. No need to upload any files nor set permissions.</li>\n		<li>Download and install rah-family plugin directly from your Textpattern backend.</li>\n		<li>Server side caching of the update list.</li>\n		<li>Admin panel under Textpattern &gt; Extensions &gt; <em>Plugin Installer</em>.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Rah_plugin_installer&#8217;s minimum requirement:</p>\n\n	<ul>\n		<li>Textpattern 4.4.1+</li>\n		<li><span class=\"caps\">PHP</span> 4.3.0+\n	<ul>\n		<li><a href=\"http://php.net/manual/en/book.curl.php\">cURL</a> library or <a href=\"http://php.net/manual/en/filesystem.configuration.php\">allow_url_fopen</a> set true</li>\n		<li>Internet connection for communications with the server</li>\n	</ul></li>\n		<li>Optional: Zlib library.</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use the functionalities.</p>\n\n	<p>You can download and install rah-plugins from Textpattern &gt; Extensions &gt; Plugin Installer.</p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Fixed small bug that appeared with the action links.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Compatibility with Textpattern v4.4.1.</li>\n		<li>Added: Support for cURL library and prefers it if installed. If not installed, uses regular file functions as fallback.</li>\n		<li>Added: Localization and language string support. TextPacks can be used to translate the admin interface.</li>\n		<li>Added: Build in uninstaller.</li>\n		<li>Changed: Moved the preference strings to Textpattern&#8217;s core txp_prefs table.</li>\n		<li>Changed: HTML5 ready markup, added <code>&lt;tbody&gt;</code> and <code>&lt;thead&gt;</code> elements to the <span class=\"caps\">HTML</span> table and removed inline styles.</li>\n		<li>Improved: New update file format. The syntax is similar to Textpattern&#8217;s language files.</li>\n		<li>Improved: Additional validation when downloading update files.</li>\n		<li>Improved: Now update files are parsed and inserted to database as individual rows instead of cached and parsed on every page request. Allows future expansions.</li>\n		<li>Now requires Textpattern v4.4.1 or newer.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Added more validity and error checks.</li>\n		<li>Added timeout support for update downloads. 3 seconds for update checks and 10 seconds for actual files.</li>\n		<li>Added option to turn automatic-updates off.</li>\n		<li>Cleaned the admin panel.</li>\n		<li>Fixed manual update check.</li>\n		<li>Fixed conflicts with other extension that included <code>txp_plugin.php</code>.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>Initial release.</li>\n	</ul>', '	##################\n	#\n	#	rah_plugin_installer-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	##################\n\n	if(@txpinterface == \'admin\') {\n		add_privs(\'rah_plugin_installer\',\'1\');\n		add_privs(\'plugin_prefs.rah_plugin_installer\',\'1,2\');\n		register_tab(\'extensions\',\'rah_plugin_installer\',gTxt(\'rah_plugin_installer\') == \'rah_plugin_installer\' ? \'Plugin Installer\' : gTxt(\'rah_plugin_installer\'));\n		register_callback(\'rah_plugin_installer_head\',\'admin_side\',\'head_end\');\n		register_callback(\'rah_plugin_installer\',\'rah_plugin_installer\');\n		register_callback(\'rah_plugin_installer_options\',\'plugin_prefs.rah_plugin_installer\');\n		register_callback(\'rah_plugin_installer_install\',\'plugin_lifecycle.rah_plugin_installer\');\n	}\n\n/**\n	Installer script\n	@param $event string Admin-side event.\n	@param $step string Admin-side, plugin-lifecycle step.\n*/\n\n	function rah_plugin_installer_install($event=\'\', $step=\'\') {\n		\n		/*\n			Uninstall if uninstalling the\n			plugin\n		*/\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_plugin_installer_def\')\n			);\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_plugin_installer_%\'\"\n			);\n			\n			return;\n		}\n		\n		global $prefs, $textarray;\n		\n		/*\n			Make sure language strings are set\n		*/\n		\n		foreach(\n			array(\n				\'\' => \'Plugin installer\',\n				\'check_for_updates\' => \'Check for updates\',\n				\'name\' => \'Name\',\n				\'version\' => \'Version\',\n				\'description\' => \'Description\',\n				\'installed_version\' => \'Installed version\',\n				\'no_plugins\' => \'No plugin update definitions downloaded yet.\',\n				\'already_up_to_date\' => \'Plugin update definitions are up-to-date.\',\n				\'already_installed\' => \'Plugin is already installed.\',\n				\'incorrect_selection\' => \'Incorrect selection.\',\n				\'open_ports_or_install_curl\' => \'Can not connect to server due to unsupported server configuration. Please either install cURL or set allow_url_fopen directive in PHP configuration file to true.\',\n				\'downloading_plugin_failed\' => \'Downloading plugin failed\',\n				\'in_plugin_cache\' => \'Initialized from plugin cache\',\n				\'update\' => \'Update\',\n				\'download\' => \'Download\',\n				\'install\' => \'Install\',\n				\'definition_updates_checked\' => \'New plugin update definitions downloaded successfully.\'\n			) as $string => $translation\n		) {\n			$string = \'rah_plugin_installer\' . ($string ? \'_\' . $string : \'\');\n			\n			if(!isset($textarray[$string]))\n				$textarray[$string] = $translation;\n		}\n				\n		$version = \'0.4\';\n		\n		$current = \n			isset($prefs[\'rah_plugin_installer_version\']) ?\n			$prefs[\'rah_plugin_installer_version\'] : \'\';\n		\n		if($current == $version)\n			return;\n		\n		if(!$current)\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_plugin_installer\'));\n		\n		/*\n			Stores plugin\'s version and update details.\n			\n			* name: Plugin\'s name. Primary key.\n			* author: Author\'s name.\n			* author_uri: Author\'s website address.\n			* version: Plugin\'s latest version number.\n			* description: Plugin\'s description.\n			* help: Cached help file.\n			* type: Plugin\'s type.\n			* md5_checksum: MD5 checksum for installer file.\n		*/\n		\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_plugin_installer_def\').\" (\n				`name` varchar(64) NOT NULL default \'\',\n				`author` varchar(128) NOT NULL default \'\',\n				`author_uri` varchar(128) NOT NULL default \'\',\n				`version` varchar(10) NOT NULL default \'1.0\',\n				`description` text NOT NULL,\n				`help` text NOT NULL,\n				`type` int(2) NOT NULL default 0,\n				`md5_checksum` varchar(32) NOT NULL default \'\',\n				PRIMARY KEY(`name`)\n			) PACK_KEYS=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Add preferences strings\n		*/\n		\n		foreach(\n			array(\n				\'version\' => $version,\n				\'updated\' => 0,\n				\'checksum\' => \'\'\n			) as $name => $value\n		) {\n			if(!isset($prefs[\'rah_plugin_installer_\'.$name]) || $name == \'version\') {\n				set_pref(\'rah_plugin_installer_\'.$name,$value,\'rah_pins\',2,\'\',0);\n				$prefs[\'rah_plugin_installer_\'.$name] = $value;\n			}\n		}\n	}\n\n/**\n	Delivers the panes\n*/\n\n	function rah_plugin_installer() {\n		require_privs(\'rah_plugin_installer\');\n		require_privs(\'plugin\');\n		\n		rah_plugin_installer_install();\n		\n		global $step;\n		\n		$steps = \n			array(\n				\'download\' => true,\n				\'update\' => true,\n			);\n		\n		if(!$step || !bouncer($step, $steps))\n			$step = \'list\';\n		\n		$func = \'rah_plugin_installer_\' . $step;\n		$func();\n	}\n\n/**\n	The main pane; the plugin listing\n	@param $message string Activity message.\n	@param $check bool Check for updates.\n*/\n\n	function rah_plugin_installer_list($message=\'\',$check=false) {\n		\n		global $event;\n		\n		pagetop(\'Plugin Installer\',$message);\n		\n		if(($updates = rah_plugin_installer_check($check)) && $updates)\n			$updates = gTxt(\'rah_plugin_installer_\' . $updates);\n		\n		$installed = rah_plugin_installer_installed();\n		\n		$rs = \n			safe_rows(\n				\'name, version, description\',\n				\'rah_plugin_installer_def\',\n				\'1=1\'\n			);\n		\n		$out[] =\n		\n			\'	<div id=\"rah_plugin_installer_container\" class=\"rah_ui_container\">\'.n.\n			\n			\'		<p class=\"rah_ui_nav\">\'.\n						\'<span class=\"rah_ui_sep\">&#187;</span> \'.\n						\'<a id=\"rah_plugin_installer_update\" href=\"?event=\'.$event.\'&amp;step=update&amp;_txp_token=\'.form_token().\'\">\'.\n							gTxt(\'rah_plugin_installer_check_for_updates\').\n						\'</a>\'.\n			\'		</p>\'.\n			\n			($updates && $rs ? \'		<p id=\"warning\">\'.$updates.\'</p>\' : \'\').\n			\n			\'		<table cellspacing=\"0\" cellpadding=\"0\" id=\"list\">\'.n.\n			\'			<thead>\'.n.\n			\'				<tr>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_name\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_version\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_description\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_installed_version\').\'</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n.\n			\'			</thead>\'.n.\n			\'			<tbody>\'.n;\n		\n		if($rs) {\n			foreach($rs as $a) {\n\n				$action = \'install\';\n				$ins = \'&#160;\';\n			 	\n			 	if(isset($installed[$a[\'name\']])) {\n			 		$ins = $installed[ $a[\'name\']];\n			 		$action = $ins == $a[\'version\'] ? \'\' : \'update\';\n			 	}\n			\n				$out[] = \n					\'				<tr>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'name\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'version\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'description\']).\'</td>\'.n.\n					\'					<td>\'.$ins.\'</td>\'.n.\n					\'					<td>\'.($action ? \'<a href=\"?event=\'.$event.\'&amp;step=download&amp;name=\'.$a[\'name\'].\'&amp;_txp_token=\'.form_token().\'\">\'.gTxt(\'rah_plugin_installer_\'.$action).\'</a>\' : \'&#160;\').\'</td>\'.n.\n					\'				</tr>\'.n;\n				}\n		} else\n			$out[] =\n				\'			<tr>\'.n.\n				\'				<td colspan=\"5\">\'.($updates ? $updates : gTxt(\'rah_plugin_installer_no_plugins\')).\'</td>\'.n.\n				\'			</tr>\'.n;\n		\n		$out[] = \n			\n			\'			</tbody>\'.n.\n			\'		</table>\'.n.\n			\'	</div>\'.n;\n			\n		echo implode(\'\', $out);\n	}\n\n/**\n	Get list of installed plugins\n*/\n\n	function rah_plugin_installer_installed() {\n\n		static $cache = NULL;\n		\n		if($cache !== NULL) {\n			return $cache;\n		}\n		\n		$cache = array();\n			\n		$rs = \n			safe_rows(\n				\'name, version\',\n				\'txp_plugin\',\n				\'1=1\'\n			);\n			\n		foreach($rs as $a)\n			$cache[$a[\'name\']] = $a[\'version\'];\n		\n		return $cache;\n	}\n\n/**\n	Checks for updates\n	@param $manual bool If user-launched update check, or auto.\n	@return string Returned message as a language string.\n*/\n\n	function rah_plugin_installer_check($manual=false) {\n		\n		global $prefs;\n		\n		@$disabled = !ini_get(\'allow_url_fopen\') && !function_exists(\'curl_init\');\n		\n		if($disabled)\n			return \'open_ports_or_install_curl\';\n		\n		$now = strtotime(\'now\');\n		\n		$wait = !$manual ? 604800 : 1800;\n		\n		if($prefs[\'rah_plugin_installer_updated\'] + $wait >= $now) {\n			return $manual ? \'already_up_to_date\' : \'\';\n		}\n		\n		$def = rah_plugin_installer_fget(\'http://rahforum.biz/?rah_plugin_installer=1&rah_version=2\' , $manual ? 30 : 5);\n		\n		/*\n			Update the last-update timestamp if we got payload\n		*/\n		\n		if($def) {\n			\n			safe_update(\n				\'txp_prefs\',\n				\"val=\'$now\'\",\n				\"name=\'rah_plugin_installer_updated\'\"\n			);\n		\n			$prefs[\'rah_plugin_installer_updated\'] = $now;\n		}\n		\n		if(!$def || !preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\',$def)) {\n			return \'could_not_fetch\';\n		}\n		\n		$def = base64_decode($def);\n		$md5 = md5($def);\n		\n		if($md5 == $prefs[\'rah_plugin_installer_checksum\'])\n			return \'already_up_to_date\';\n		\n		safe_update(\n			\'txp_prefs\',\n			\"val=\'$md5\'\",\n			\"name=\'rah_plugin_installer_checksum\'\"\n		);\n		\n		rah_plugin_installer_import(rah_plugin_installer_parser($def));\n		\n		return \'definition_updates_checked\';\n	}\n\n/**\n	Fire manual listing refresh\n*/\n\n	function rah_plugin_installer_update() {\n		rah_plugin_installer_list(\'\',true);\n	}\n\n/**\n	Parses update file\n	@param $file string File to parse.\n	@return array\n*/\n\n	function rah_plugin_installer_parser($file) {\n\n		$file = explode(n, $file);\n		$plugin = \'\';\n		$out = array();\n\n		foreach($file as $line) {\n			\n			$line = trim($line);\n			\n			if(!$line || strpos($line,\'#\') === 0)\n				continue;\n\n			/*\n				Set the plugin name\n			*/\n			\n			if(strpos($line,\'@\') === 0 && strpos($line,\'_\') == 4) {\n				$plugin = substr($line, 1);\n				continue;\n			}\n			\n			if(!$plugin)\n				continue;\n			\n			if(!preg_match(\'/^(\\w+)\\s*=>\\s*(.+)$/\', $line, $m))\n				continue;\n				\n			if(empty($m[1]) || empty($m[2]))\n				continue;\n					\n			$out[$plugin][$m[1]] = $m[2];\n		}\n		\n		return $out;\n	}\n	\n/**\n	Imports update file to the database\n	@param $inc array Definitions to import.\n	@return Nothing.\n*/\n\n	function rah_plugin_installer_import($inc) {\n		\n		$plugin = array();\n		\n		$rs = \n			safe_rows(\n				\'name, version\',\n				\'rah_plugin_installer_def\',\n				\'1=1\'\n			);\n		\n		foreach($rs as $a)\n			$plugin[$a[\'name\']] = $a[\'version\'];\n		\n		foreach($inc as $name => $a) {\n			\n			if(!isset($a[\'description\']) || !isset($a[\'version\']))\n				continue;\n			\n			if(!isset($plugin[$name])) {\n				\n				safe_insert(\n					\'rah_plugin_installer_def\',\n					\"name=\'\".doSlash($name).\"\',\n					version=\'\".doSlash($a[\'version\']).\"\',\n					description=\'\".doSlash($a[\'description\']).\"\'\"\n				);\n					\n			}\n			else if($plugin[$name] != $a[\'version\']) {\n			\n				safe_update(\n					\'rah_plugin_installer_def\',\n					\"version=\'\".doSlash($a[\'version\']).\"\',\n					description=\'\".doSlash($a[\'description\']).\"\'\",\n					\"name=\'\".doSlash($name).\"\'\"\n				);\n				\n			}\n			\n			unset($plugin[$name]);\n		}\n		\n		if(!empty($plugin) && is_array($plugin)) {\n			\n			foreach($plugin as $name => $version)\n				$remove[] = \"\'\".doSlash($name).\"\'\";\n				\n			safe_delete(\n				\'rah_plugin_installer_def\',\n				\'name in(\'. implode(\',\', $remove) . \')\'\n			);\n			\n		}\n		\n	}\n\n/**\n	Download the plugin code and run Textpattern\'s plugin installer\n*/\n\n	function rah_plugin_installer_download() {\n		\n		@$disabled = !ini_get(\'allow_url_fopen\') && !function_exists(\'curl_init\');\n		\n		if($disabled) {\n			rah_plugin_installer_list(\'open_ports_or_install_curl\');\n			return;\n		}\n		\n		$name = gps(\'name\');\n		\n		$def = \n			safe_row(\n				\'name, version\',\n				\'rah_plugin_installer_def\',\n				\"name=\'\".doSlash($name).\"\' LIMIT 0, 1\"\n			);\n		\n		if(!$name || !$def) {\n			rah_plugin_installer_list(\'rah_plugin_installer_incorrect_selection\');\n			return;\n		}\n		\n		if(fetch(\'version\', \'txp_plugin\', \'name\', $name) == $def[\'version\']) {\n			rah_plugin_installer_list(\'rah_plugin_installer_already_installed\');\n			return;\n		}\n		\n		$url = \'http://rahforum.biz/?rah_plugin_download=\'.$name;\n		$url = function_exists(\'gzencode\') ? $url . \'&rah_type=zip\' : $url;\n			\n		$plugin = rah_plugin_installer_fget($url);\n		\n		if(empty($plugin)) {\n			rah_plugin_installer_list(\'rah_plugin_installer_downloading_plugin_failed\');\n			return;\n		}\n\n		$_POST[\'install_new\'] = \'Upload\';	\n		$_POST[\'plugin\'] = $plugin;\n		$_POST[\'plugin64\'] = $plugin;\n		$_POST[\'event\'] = \'plugin\';\n		$_POST[\'step\'] = \'plugin_verify\';\n		$_POST[\'_txp_token\'] = form_token();\n		\n		$step = \'plugin_verify\';\n		$event = \'plugin\';\n		\n		include_once txpath.\'/include/txp_plugin.php\';\n		exit;\n	}\n\n/**\n	Downloads remote file\n	@param $url string URL to download.\n	@param $timeout int Connection timeout in seconds.\n	@return string Contents of the file. False on failure.\n*/\n\n	function rah_plugin_installer_fget($url, $timeout=10) {\n		\n		/*\n			If cURL isn\'t available,\n			use file_get_contents if possible\n		*/\n			\n		if(!function_exists(\'curl_init\')) {\n			\n			if(!ini_get(\'allow_url_fopen\'))\n				return false;\n			\n			$context = \n				stream_context_create(\n					array(\n					\'http\' => \n						array(\n							\'timeout\' => $timeout\n						)\n					)\n				);\n\n			@$file = file_get_contents($url, 0, $context);\n			return !$file ? false : trim($file);\n		}\n		\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $url);\n		curl_setopt($ch, CURLOPT_FAILONERROR, 1);\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\n		$file = curl_exec($ch);\n		$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n		curl_close($ch);\n		\n		return $file !== false && $http == \'200\' ? trim($file) : false;\n	}\n\n/**\n	Redirect to the admin-side interface\n*/\n\n	function rah_plugin_installer_options() {\n		header(\'Location: ?event=rah_plugin_installer\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=rah_plugin_installer\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n\n/**\n	Adds styles to the <head>\n*/\n\n	function rah_plugin_installer_head() {\n		global $event;\n		\n		if($event != \'rah_plugin_installer\')\n			return;\n		\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_plugin_installer_container {\n					width: 950px;\n					margin: 0 auto;	\n				}\n				#rah_plugin_installer_container table {\n					width: 100%;	\n				}\n			</style>\nEOF;\n	}', '	##################\n	#\n	#	rah_plugin_installer-plugin for Textpattern\n	#	version 0.4\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	##################\n\n	if(@txpinterface == \'admin\') {\n		add_privs(\'rah_plugin_installer\',\'1\');\n		add_privs(\'plugin_prefs.rah_plugin_installer\',\'1,2\');\n		register_tab(\'extensions\',\'rah_plugin_installer\',gTxt(\'rah_plugin_installer\') == \'rah_plugin_installer\' ? \'Plugin Installer\' : gTxt(\'rah_plugin_installer\'));\n		register_callback(\'rah_plugin_installer_head\',\'admin_side\',\'head_end\');\n		register_callback(\'rah_plugin_installer\',\'rah_plugin_installer\');\n		register_callback(\'rah_plugin_installer_options\',\'plugin_prefs.rah_plugin_installer\');\n		register_callback(\'rah_plugin_installer_install\',\'plugin_lifecycle.rah_plugin_installer\');\n	}\n\n/**\n	Installer script\n	@param $event string Admin-side event.\n	@param $step string Admin-side, plugin-lifecycle step.\n*/\n\n	function rah_plugin_installer_install($event=\'\', $step=\'\') {\n		\n		/*\n			Uninstall if uninstalling the\n			plugin\n		*/\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_plugin_installer_def\')\n			);\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_plugin_installer_%\'\"\n			);\n			\n			return;\n		}\n		\n		global $prefs, $textarray;\n		\n		/*\n			Make sure language strings are set\n		*/\n		\n		foreach(\n			array(\n				\'\' => \'Plugin installer\',\n				\'check_for_updates\' => \'Check for updates\',\n				\'name\' => \'Name\',\n				\'version\' => \'Version\',\n				\'description\' => \'Description\',\n				\'installed_version\' => \'Installed version\',\n				\'no_plugins\' => \'No plugin update definitions downloaded yet.\',\n				\'already_up_to_date\' => \'Plugin update definitions are up-to-date.\',\n				\'already_installed\' => \'Plugin is already installed.\',\n				\'incorrect_selection\' => \'Incorrect selection.\',\n				\'open_ports_or_install_curl\' => \'Can not connect to server due to unsupported server configuration. Please either install cURL or set allow_url_fopen directive in PHP configuration file to true.\',\n				\'downloading_plugin_failed\' => \'Downloading plugin failed\',\n				\'in_plugin_cache\' => \'Initialized from plugin cache\',\n				\'update\' => \'Update\',\n				\'download\' => \'Download\',\n				\'install\' => \'Install\',\n				\'definition_updates_checked\' => \'New plugin update definitions downloaded successfully.\'\n			) as $string => $translation\n		) {\n			$string = \'rah_plugin_installer\' . ($string ? \'_\' . $string : \'\');\n			\n			if(!isset($textarray[$string]))\n				$textarray[$string] = $translation;\n		}\n				\n		$version = \'0.4\';\n		\n		$current = \n			isset($prefs[\'rah_plugin_installer_version\']) ?\n			$prefs[\'rah_plugin_installer_version\'] : \'\';\n		\n		if($current == $version)\n			return;\n		\n		if(!$current)\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_plugin_installer\'));\n		\n		/*\n			Stores plugin\'s version and update details.\n			\n			* name: Plugin\'s name. Primary key.\n			* author: Author\'s name.\n			* author_uri: Author\'s website address.\n			* version: Plugin\'s latest version number.\n			* description: Plugin\'s description.\n			* help: Cached help file.\n			* type: Plugin\'s type.\n			* md5_checksum: MD5 checksum for installer file.\n		*/\n		\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_plugin_installer_def\').\" (\n				`name` varchar(64) NOT NULL default \'\',\n				`author` varchar(128) NOT NULL default \'\',\n				`author_uri` varchar(128) NOT NULL default \'\',\n				`version` varchar(10) NOT NULL default \'1.0\',\n				`description` text NOT NULL,\n				`help` text NOT NULL,\n				`type` int(2) NOT NULL default 0,\n				`md5_checksum` varchar(32) NOT NULL default \'\',\n				PRIMARY KEY(`name`)\n			) PACK_KEYS=1 CHARSET=utf8\"\n		);\n		\n		/*\n			Add preferences strings\n		*/\n		\n		foreach(\n			array(\n				\'version\' => $version,\n				\'updated\' => 0,\n				\'checksum\' => \'\'\n			) as $name => $value\n		) {\n			if(!isset($prefs[\'rah_plugin_installer_\'.$name]) || $name == \'version\') {\n				set_pref(\'rah_plugin_installer_\'.$name,$value,\'rah_pins\',2,\'\',0);\n				$prefs[\'rah_plugin_installer_\'.$name] = $value;\n			}\n		}\n	}\n\n/**\n	Delivers the panes\n*/\n\n	function rah_plugin_installer() {\n		require_privs(\'rah_plugin_installer\');\n		require_privs(\'plugin\');\n		\n		rah_plugin_installer_install();\n		\n		global $step;\n		\n		$steps = \n			array(\n				\'download\' => true,\n				\'update\' => true,\n			);\n		\n		if(!$step || !bouncer($step, $steps))\n			$step = \'list\';\n		\n		$func = \'rah_plugin_installer_\' . $step;\n		$func();\n	}\n\n/**\n	The main pane; the plugin listing\n	@param $message string Activity message.\n	@param $check bool Check for updates.\n*/\n\n	function rah_plugin_installer_list($message=\'\',$check=false) {\n		\n		global $event;\n		\n		pagetop(\'Plugin Installer\',$message);\n		\n		if(($updates = rah_plugin_installer_check($check)) && $updates)\n			$updates = gTxt(\'rah_plugin_installer_\' . $updates);\n		\n		$installed = rah_plugin_installer_installed();\n		\n		$rs = \n			safe_rows(\n				\'name, version, description\',\n				\'rah_plugin_installer_def\',\n				\'1=1\'\n			);\n		\n		$out[] =\n		\n			\'	<div id=\"rah_plugin_installer_container\" class=\"rah_ui_container\">\'.n.\n			\n			\'		<p class=\"rah_ui_nav\">\'.\n						\'<span class=\"rah_ui_sep\">&#187;</span> \'.\n						\'<a id=\"rah_plugin_installer_update\" href=\"?event=\'.$event.\'&amp;step=update&amp;_txp_token=\'.form_token().\'\">\'.\n							gTxt(\'rah_plugin_installer_check_for_updates\').\n						\'</a>\'.\n			\'		</p>\'.\n			\n			($updates && $rs ? \'		<p id=\"warning\">\'.$updates.\'</p>\' : \'\').\n			\n			\'		<table cellspacing=\"0\" cellpadding=\"0\" id=\"list\">\'.n.\n			\'			<thead>\'.n.\n			\'				<tr>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_name\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_version\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_description\').\'</th>\'.n.\n			\'					<th>\'.gTxt(\'rah_plugin_installer_installed_version\').\'</th>\'.n.\n			\'					<th>&#160;</th>\'.n.\n			\'				</tr>\'.n.\n			\'			</thead>\'.n.\n			\'			<tbody>\'.n;\n		\n		if($rs) {\n			foreach($rs as $a) {\n\n				$action = \'install\';\n				$ins = \'&#160;\';\n			 	\n			 	if(isset($installed[$a[\'name\']])) {\n			 		$ins = $installed[ $a[\'name\']];\n			 		$action = $ins == $a[\'version\'] ? \'\' : \'update\';\n			 	}\n			\n				$out[] = \n					\'				<tr>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'name\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'version\']).\'</td>\'.n.\n					\'					<td>\'.htmlspecialchars($a[\'description\']).\'</td>\'.n.\n					\'					<td>\'.$ins.\'</td>\'.n.\n					\'					<td>\'.($action ? \'<a href=\"?event=\'.$event.\'&amp;step=download&amp;name=\'.$a[\'name\'].\'&amp;_txp_token=\'.form_token().\'\">\'.gTxt(\'rah_plugin_installer_\'.$action).\'</a>\' : \'&#160;\').\'</td>\'.n.\n					\'				</tr>\'.n;\n				}\n		} else\n			$out[] =\n				\'			<tr>\'.n.\n				\'				<td colspan=\"5\">\'.($updates ? $updates : gTxt(\'rah_plugin_installer_no_plugins\')).\'</td>\'.n.\n				\'			</tr>\'.n;\n		\n		$out[] = \n			\n			\'			</tbody>\'.n.\n			\'		</table>\'.n.\n			\'	</div>\'.n;\n			\n		echo implode(\'\', $out);\n	}\n\n/**\n	Get list of installed plugins\n*/\n\n	function rah_plugin_installer_installed() {\n\n		static $cache = NULL;\n		\n		if($cache !== NULL) {\n			return $cache;\n		}\n		\n		$cache = array();\n			\n		$rs = \n			safe_rows(\n				\'name, version\',\n				\'txp_plugin\',\n				\'1=1\'\n			);\n			\n		foreach($rs as $a)\n			$cache[$a[\'name\']] = $a[\'version\'];\n		\n		return $cache;\n	}\n\n/**\n	Checks for updates\n	@param $manual bool If user-launched update check, or auto.\n	@return string Returned message as a language string.\n*/\n\n	function rah_plugin_installer_check($manual=false) {\n		\n		global $prefs;\n		\n		@$disabled = !ini_get(\'allow_url_fopen\') && !function_exists(\'curl_init\');\n		\n		if($disabled)\n			return \'open_ports_or_install_curl\';\n		\n		$now = strtotime(\'now\');\n		\n		$wait = !$manual ? 604800 : 1800;\n		\n		if($prefs[\'rah_plugin_installer_updated\'] + $wait >= $now) {\n			return $manual ? \'already_up_to_date\' : \'\';\n		}\n		\n		$def = rah_plugin_installer_fget(\'http://rahforum.biz/?rah_plugin_installer=1&rah_version=2\' , $manual ? 30 : 5);\n		\n		/*\n			Update the last-update timestamp if we got payload\n		*/\n		\n		if($def) {\n			\n			safe_update(\n				\'txp_prefs\',\n				\"val=\'$now\'\",\n				\"name=\'rah_plugin_installer_updated\'\"\n			);\n		\n			$prefs[\'rah_plugin_installer_updated\'] = $now;\n		}\n		\n		if(!$def || !preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\',$def)) {\n			return \'could_not_fetch\';\n		}\n		\n		$def = base64_decode($def);\n		$md5 = md5($def);\n		\n		if($md5 == $prefs[\'rah_plugin_installer_checksum\'])\n			return \'already_up_to_date\';\n		\n		safe_update(\n			\'txp_prefs\',\n			\"val=\'$md5\'\",\n			\"name=\'rah_plugin_installer_checksum\'\"\n		);\n		\n		rah_plugin_installer_import(rah_plugin_installer_parser($def));\n		\n		return \'definition_updates_checked\';\n	}\n\n/**\n	Fire manual listing refresh\n*/\n\n	function rah_plugin_installer_update() {\n		rah_plugin_installer_list(\'\',true);\n	}\n\n/**\n	Parses update file\n	@param $file string File to parse.\n	@return array\n*/\n\n	function rah_plugin_installer_parser($file) {\n\n		$file = explode(n, $file);\n		$plugin = \'\';\n		$out = array();\n\n		foreach($file as $line) {\n			\n			$line = trim($line);\n			\n			if(!$line || strpos($line,\'#\') === 0)\n				continue;\n\n			/*\n				Set the plugin name\n			*/\n			\n			if(strpos($line,\'@\') === 0 && strpos($line,\'_\') == 4) {\n				$plugin = substr($line, 1);\n				continue;\n			}\n			\n			if(!$plugin)\n				continue;\n			\n			if(!preg_match(\'/^(\\w+)\\s*=>\\s*(.+)$/\', $line, $m))\n				continue;\n				\n			if(empty($m[1]) || empty($m[2]))\n				continue;\n					\n			$out[$plugin][$m[1]] = $m[2];\n		}\n		\n		return $out;\n	}\n	\n/**\n	Imports update file to the database\n	@param $inc array Definitions to import.\n	@return Nothing.\n*/\n\n	function rah_plugin_installer_import($inc) {\n		\n		$plugin = array();\n		\n		$rs = \n			safe_rows(\n				\'name, version\',\n				\'rah_plugin_installer_def\',\n				\'1=1\'\n			);\n		\n		foreach($rs as $a)\n			$plugin[$a[\'name\']] = $a[\'version\'];\n		\n		foreach($inc as $name => $a) {\n			\n			if(!isset($a[\'description\']) || !isset($a[\'version\']))\n				continue;\n			\n			if(!isset($plugin[$name])) {\n				\n				safe_insert(\n					\'rah_plugin_installer_def\',\n					\"name=\'\".doSlash($name).\"\',\n					version=\'\".doSlash($a[\'version\']).\"\',\n					description=\'\".doSlash($a[\'description\']).\"\'\"\n				);\n					\n			}\n			else if($plugin[$name] != $a[\'version\']) {\n			\n				safe_update(\n					\'rah_plugin_installer_def\',\n					\"version=\'\".doSlash($a[\'version\']).\"\',\n					description=\'\".doSlash($a[\'description\']).\"\'\",\n					\"name=\'\".doSlash($name).\"\'\"\n				);\n				\n			}\n			\n			unset($plugin[$name]);\n		}\n		\n		if(!empty($plugin) && is_array($plugin)) {\n			\n			foreach($plugin as $name => $version)\n				$remove[] = \"\'\".doSlash($name).\"\'\";\n				\n			safe_delete(\n				\'rah_plugin_installer_def\',\n				\'name in(\'. implode(\',\', $remove) . \')\'\n			);\n			\n		}\n		\n	}\n\n/**\n	Download the plugin code and run Textpattern\'s plugin installer\n*/\n\n	function rah_plugin_installer_download() {\n		\n		@$disabled = !ini_get(\'allow_url_fopen\') && !function_exists(\'curl_init\');\n		\n		if($disabled) {\n			rah_plugin_installer_list(\'open_ports_or_install_curl\');\n			return;\n		}\n		\n		$name = gps(\'name\');\n		\n		$def = \n			safe_row(\n				\'name, version\',\n				\'rah_plugin_installer_def\',\n				\"name=\'\".doSlash($name).\"\' LIMIT 0, 1\"\n			);\n		\n		if(!$name || !$def) {\n			rah_plugin_installer_list(\'rah_plugin_installer_incorrect_selection\');\n			return;\n		}\n		\n		if(fetch(\'version\', \'txp_plugin\', \'name\', $name) == $def[\'version\']) {\n			rah_plugin_installer_list(\'rah_plugin_installer_already_installed\');\n			return;\n		}\n		\n		$url = \'http://rahforum.biz/?rah_plugin_download=\'.$name;\n		$url = function_exists(\'gzencode\') ? $url . \'&rah_type=zip\' : $url;\n			\n		$plugin = rah_plugin_installer_fget($url);\n		\n		if(empty($plugin)) {\n			rah_plugin_installer_list(\'rah_plugin_installer_downloading_plugin_failed\');\n			return;\n		}\n\n		$_POST[\'install_new\'] = \'Upload\';	\n		$_POST[\'plugin\'] = $plugin;\n		$_POST[\'plugin64\'] = $plugin;\n		$_POST[\'event\'] = \'plugin\';\n		$_POST[\'step\'] = \'plugin_verify\';\n		$_POST[\'_txp_token\'] = form_token();\n		\n		$step = \'plugin_verify\';\n		$event = \'plugin\';\n		\n		include_once txpath.\'/include/txp_plugin.php\';\n		exit;\n	}\n\n/**\n	Downloads remote file\n	@param $url string URL to download.\n	@param $timeout int Connection timeout in seconds.\n	@return string Contents of the file. False on failure.\n*/\n\n	function rah_plugin_installer_fget($url, $timeout=10) {\n		\n		/*\n			If cURL isn\'t available,\n			use file_get_contents if possible\n		*/\n			\n		if(!function_exists(\'curl_init\')) {\n			\n			if(!ini_get(\'allow_url_fopen\'))\n				return false;\n			\n			$context = \n				stream_context_create(\n					array(\n					\'http\' => \n						array(\n							\'timeout\' => $timeout\n						)\n					)\n				);\n\n			@$file = file_get_contents($url, 0, $context);\n			return !$file ? false : trim($file);\n		}\n		\n		$ch = curl_init();\n		curl_setopt($ch, CURLOPT_URL, $url);\n		curl_setopt($ch, CURLOPT_FAILONERROR, 1);\n		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\n		$file = curl_exec($ch);\n		$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n		curl_close($ch);\n		\n		return $file !== false && $http == \'200\' ? trim($file) : false;\n	}\n\n/**\n	Redirect to the admin-side interface\n*/\n\n	function rah_plugin_installer_options() {\n		header(\'Location: ?event=rah_plugin_installer\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=rah_plugin_installer\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n\n/**\n	Adds styles to the <head>\n*/\n\n	function rah_plugin_installer_head() {\n		global $event;\n		\n		if($event != \'rah_plugin_installer\')\n			return;\n		\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_plugin_installer_container {\n					width: 950px;\n					margin: 0 auto;	\n				}\n				#rah_plugin_installer_container table {\n					width: 100%;	\n				}\n			</style>\nEOF;\n	}', 'b9bf2d2463688208ba75ee38b777080a', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_repeat', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.0.1', 'Iterations made easy', '<h1>rah_repeat</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_repeat\" rel=\"nofollow\">Project page</a> | <a href=\"https://packagist.org/packages/rah/rah_repeat\" rel=\"nofollow\">Packagist</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_repeat\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=32384\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_repeat\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_repeat is a <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a> plugin used for iterations. The plugin splits a provided value to smaller chunks and iterates overs, just like you would expect from a for each loop in any programming language. With the plugin you can turn a simple comma-separated list of values into advanced <span class=\"caps\">HTML</span> output, or extract parts of a value as <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variables</a>.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_repeat&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_repeat&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Basics</h2>\n\n<pre><code>&lt;txp:rah_repeat range=&quot;min, max, step&quot; value=&quot;value1, value2, ...&quot; assign=&quot;variable1, variable2, ...&quot;&gt;\n	...contained statement...\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Rah_repeat&#8217;s main job is primely iterating over values. Its iteration power can used to create lists or extract subsets of data. The plugin can come very handy when you have a <a href=\"http://textpattern.net/wiki/index.php?title=custom_field\" rel=\"nofollow\">custom field</a> that contains comma-separated list of values which you want to present as a <span class=\"caps\">HTML</span> list or extract as individual separate values.</p>\n\n<p>The values you want to iterate over are provided to the tag with the <code>value</code> attribute, each individual subset value separated from each other with the <code>delimiter</code>, defaulting to a comma. The current value that is being iterated over can be returned using the <code>rah_repeat_value</code> tag, wrapped in <code>rah_repeat</code> block. The following would generate a <span class=\"caps\">HTML</span> list from comma-separated list of <code>red, blue, green</code>.</p>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;red, blue, green&quot; wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>In addition to iterating over values and creating lists, the tag can also be used to extract values and assign each one to a <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variable</a> tag. This can be done using the <code>rah_repeat</code> tag&#8217;s <code>assign</code> attribute. The attribute takes a comma-separated list of variable names that will be created, each containing one of the values.</p>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;red, blue, green&quot; assign=&quot;color1, color2, color3&quot; /&gt;</code></pre>\n\n<p>The above would extra each of the colors as a variable. These variables would be named as <code>color1</code>, <code>color2</code> and <code>color3</code>. Using <code>&lt;txp:variable name=&quot;color1&quot; /&gt;</code> would return <code>red</code>.</p>\n\n<h2>Tags and attributes</h2>\n\n<p>The plugin comes with a total of four tags. The main tag <code>rah_repeat</code>, a single tag <code>rah_repeat_value</code>, and two conditionals <code>rah_repeat_if_first</code> and <code>rah_repeat_if_last</code>.</p>\n\n<h3>rah_repeat</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;value1, value2, ...&quot;&gt;\n	...contained statement...\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>The <code>&lt;txp:rah_repeat&gt;</code> tag is the plugin&#8217;s main tag. It&#8217;s a container tag used for iterations. Attributes for it are as follows.</p>\n\n<p><strong>value</strong><br />\nSets the values that are passed to the tag. Multiple values are separated with the <code>delimiter</code> which by default is a comma (<code>,</code>). This attribute or either <code>range</code> is required.<br />\nExample: <code>value=&quot;dog,cat,human&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>range</strong><br />\nCreates a list of values containing a range of elements. Using <code>range</code> overrides <code>value</code> attribute. It works identically to <span class=\"caps\">PHP</span>&#8217;s <a href=\"http://php.net/manual/en/function.range.php\" rel=\"nofollow\">range</a> function and uses same sequence syntax as it. The attribute&#8217;s value consists of three parts: <code>minimum</code>, <code>maximum</code> and <code>step</code>, which are separated by a comma. All but <code>step</code> are required.<br />\nExample: <code>range=&quot;1, 10&quot;</code> Default: undefined</p>\n\n<p><strong>delimiter</strong><br />\nSets the delimiter that is used to split the provided <code>value</code> into a list. Default delimiter is comma (<code>,</code>).<br />\nExample: <code>delimiter=&quot;|&quot;</code> Default: <code>&quot;,&quot;</code></p>\n\n<p><strong>assign</strong><br />\nAssigns values as Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variables</a>. Takes a comma-separated list of variable names: <code>variable1, variable2, variable3, ...</code>.<br />\nExample: <code>assign=&quot;label, value&quot;</code> Default: <code>unset</code></p>\n\n<p><strong>duplicates</strong><br />\nRemoves duplicate values from the list. If the attribute is set to <code>1</code>, only first occurrence of the value is used and duplicates are stripped off.<br />\nExample: <code>duplicates=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<p><strong>exclude</strong><br />\nExclude certain values from the list. The attribute takes a comma (or <code>delimiter</code>, if <code>delimiter</code> is changed) separated list of values.<br />\nExample: <code>exclude=&quot;foo,bar&quot;</code> Default: undefined</p>\n\n<p><strong>trim</strong><br />\nTrims values from extra whitespace. This can be particularly helpful if the provided values are from user-input (e.g. from an article field), or the values just have extra whitespace, and the resulting output has to be clean (i.e. used in <span class=\"caps\">XML</span>, JavaScript or to a <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variable</a> comparison). If you want to keep whitespace intact, you can use this attribute. By default the option is on, and values are trimmed.<br />\nExample: <code>trim=&quot;0&quot;</code> Default: <code>&quot;1&quot;</code></p>\n\n<p><strong>sort</strong><br />\nSorts the values. If the attribute is used, all values are rearranged to the specified order. Available options are <code>regular</code> (sorts without checking the type), <code>numeric</code> (sorts in a numeric order), <code>string</code> (sorts as strings) and <code>locale_string</code> (sorts according server&#8217;s locale settings). All the values can be  followed by the sorting direction, either <code>desc</code> and <code>asc</code>. By default the option isn&#8217;t used (unset), and the values are returned in the order they were supplied.<br />\nExample: <code>sort=&quot;regular asc&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>offset</strong><br />\nThe number of items to skip. Default is <code>0</code> (none).<br />\nExample: <code>offset=&quot;5&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<p><strong>limit</strong><br />\nThe number of items are displayed. By default there is no limit, and all items are returned.<br />\nExample: <code>limit=&quot;10&quot;</code> Default: undefined</p>\n\n<p><strong>form</strong><br />\nUse specified form partial. By default contained statement is used instead of a form.<br />\nExample: <code>form=&quot;price_column&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>wraptag</strong><br />\nThe (X)HTML tag (without brackets) used to wrap the output.<br />\nExample: <code>wraptag=&quot;div&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>break</strong><br />\nThe (X)HTML tag (without brackets) or a string used to separate list items.<br />\nExample: <code>&quot;break=&quot;br&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<p><strong>class</strong><br />\nThe (X)HTML class applied to the <code>wraptag</code>. Default is unset.<br />\nExample: <code>class=&quot;plugin&quot;</code> Default: <code>&quot;&quot;</code></p>\n\n<h3>rah_repeat_value</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;value1, value2, ...&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Rah_repeat_value a single tag, used to display a iterated value. The tag should be used inside a <code>&lt;txp:rah_repeat&gt;&lt;/txp:rah_repeat&gt;</code> block.</p>\n\n<p><strong>escape</strong><br />\nIf set to <code>1</code>, <span class=\"caps\">HTML</span> and Textpattern markup are escaped, and special characters are converted to <span class=\"caps\">HTML</span> entities. By default this option is off.<br />\nExample: <code>escape=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<p><strong>index</strong><br />\nIf set to <code>1</code>, the tag returns the iterated value&#8217;s index number. The index starts from 0.<br />\nExample: <code>index=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<h3>rah_repeat_if_first</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;value1, value2, ...&quot;&gt;\n	&lt;txp:rah_repeat_if_first&gt;\n		Fist item.\n	&lt;/txp:rah_repeat_if_first&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>The <code>&lt;txp:rah_repeat_if_first&gt;</code> tag is a container, and has no attributes. It&#8217;s a conditional tag that checks if the current item is the first one.</p>\n\n<h3>rah_repeat_if_last</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;value1, value2, ...&quot;&gt;\n	&lt;txp:rah_repeat_if_last&gt;\n		Last item.\n	&lt;/txp:rah_repeat_if_last&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>The <code>&lt;txp:rah_repeat_if_last&gt;</code> tag is a container, and has no attributes. It&#8217;s a conditional tag that checks if the current item is the last one.</p>\n\n<h2>Examples</h2>\n\n<h3>Simple usage example</h3>\n\n<p>This example turns simple comma separated list of <code>dog, cat, butterfly</code> into a <span class=\"caps\">HTML</span> list.</p>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;dog, cat, butterfly&quot; wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n	A &lt;txp:rah_repeat_value /&gt;.\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>The above returns:</p>\n\n<pre><code>&lt;ul&gt;\n	&lt;li&gt;A dog.&lt;/li&gt;\n	&lt;li&gt;A cat.&lt;/li&gt;\n	&lt;li&gt;A butterfly.&lt;/li&gt;\n&lt;/ul&gt;</code></pre>\n\n<h3>Using tags as values</h3>\n\n<p>As of Textpattern version 4.0.7, you can use tags inside tags.</p>\n\n<p>Let&#8217;s say that you have comma separated list of items stored inside article&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=custom_field\" rel=\"nofollow\">custom field</a>. For example, list of “Nameless” video service&#8217;s video IDs (<code>ID1, ID2, ID3, ID4</code>), and you want to embed each of those as a playable video.</p>\n\n<p>We pass the custom field hosting the video IDs to rah_repeat tag (with the <code>value</code> attribute), and place the video player code inside the container:</p>\n\n<pre><code>&lt;txp:rah_repeat value=\'&lt;txp:custom_field name=&quot;MyCustomFieldName&quot; /&gt;\'&gt;\n	&lt;object width=&quot;600&quot; height=&quot;380&quot;&gt;\n		&lt;param name=&quot;movie&quot; value=&quot;http://example.com/v/&lt;txp:rah_repeat_value /&gt;&quot;&gt;&lt;/param&gt;\n		&lt;embed src=&quot;http://example.com/v/&lt;txp:rah_repeat_value /&gt;&quot; width=&quot;600&quot; height=&quot;380&quot;&gt;&lt;/embed&gt;\n	&lt;/object&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>The above code would output 4 embedded players (one for each clip), displaying the videos specified with the custom field.</p>\n\n<h3>Taking advantage of offset and limit attributes</h3>\n\n<p>First display two items, then some text between, two more items, some more text and then the rest of the items.</p>\n\n<pre><code>&lt;txp:rah_repeat value=\'&lt;txp:custom_field name=&quot;MyCustomFieldName&quot; /&gt;\' limit=&quot;2&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;\n&lt;p&gt;Some text here.&lt;/p&gt;\n&lt;txp:rah_repeat value=\'&lt;txp:custom_field name=&quot;MyCustomFieldName&quot; /&gt;\' offset=&quot;2&quot; limit=&quot;4&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;\n&lt;p&gt;Some another cool phrase here.&lt;/p&gt;\n&lt;txp:rah_repeat value=\'&lt;txp:custom_field name=&quot;MyCustomFieldName&quot; /&gt;\' offset=&quot;4&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<h3>Repeat inside repeat</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;group1|item1|item2, group2|item1|item2&quot;&gt;\n	&lt;ul&gt;\n		&lt;txp:rah_repeat value=\'&lt;txp:rah_repeat_value /&gt;\' delimiter=&quot;|&quot;&gt;\n			&lt;li&gt;&lt;txp:rah_repeat_value /&gt;&lt;/li&gt;\n		&lt;/txp:rah_repeat&gt;\n	&lt;/ul&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Returns two <span class=\"caps\">HTML</span> lists:</p>\n\n<pre><code>&lt;ul&gt;\n	&lt;li&gt;group1&lt;/li&gt;\n	&lt;li&gt;item1&lt;/li&gt;\n	&lt;li&gt;item2&lt;/li&gt;\n&lt;/ul&gt;\n&lt;ul&gt;\n	&lt;li&gt;group2&lt;/li&gt;\n	&lt;li&gt;item1&lt;/li&gt;\n	&lt;li&gt;item2&lt;/li&gt;\n&lt;/ul&gt;</code></pre>\n\n<h3>Basic usage of the if_first and the if_last tags</h3>\n\n<p>With the conditional tags <code>&lt;txp:rah_repeat_if_first /&gt;</code> and <code>&lt;txp:rah_repeat_if_last&gt;</code> we can test which value is the first and which is the last.</p>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;item1, item2, item3, item4, item5&quot; wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n	&lt;txp:rah_repeat_if_first&gt;First: &lt;/txp:rah_repeat_if_first&gt;\n	&lt;txp:rah_repeat_if_last&gt;Last: &lt;/txp:rah_repeat_if_last&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Returns:</p>\n\n<pre><code>&lt;ul&gt;\n	&lt;li&gt;First: item1&lt;/li&gt;\n	&lt;li&gt;item2&lt;/li&gt;\n	&lt;li&gt;item3&lt;/li&gt;\n	&lt;li&gt;item4&lt;/li&gt;\n	&lt;li&gt;Last: item5&lt;/li&gt;\n&lt;/ul&gt;</code></pre>\n\n<h3>Remove duplicate values</h3>\n\n<pre><code>&lt;txp:rah_repeat duplicates=&quot;1&quot; value=&quot;foo, bar, bar, foo, bar, bar, foo, foobar&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Returns: <code>foo, bar, foobar</code></p>\n\n<h3>Arrange the values from lowest to highest</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;b, a, c&quot; sort=&quot;regular asc&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Returns: <code>a, b, c</code></p>\n\n<h3>Excluding values</h3>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;foo, bar, foobar&quot; exclude=&quot;foo, bar&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Returns: <code>foobar</code></p>\n\n<h3>Using range attribute</h3>\n\n<p>With the <code>range</code> it&#8217;s possible to create a range of elements with out specifying each. For example generating list of alphabet (A-z) can be done with range.</p>\n\n<pre><code>&lt;txp:rah_repeat range=&quot;a, z, 1&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Or listing number from 0 to 10.</p>\n\n<pre><code>&lt;txp:rah_repeat range=&quot;0, 10, 1&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<p>Or values <code>0</code>, <code>2</code>, <code>4</code>, and <code>6</code>.</p>\n\n<pre><code>&lt;txp:rah_repeat range=&quot;0, 6, 2&quot;&gt;\n	&lt;txp:rah_repeat_value /&gt;\n&lt;/txp:rah_repeat&gt;</code></pre>\n\n<h3>Assign variables with assign attribute</h3>\n\n<p>The <code>assign</code> attribute allows exporting split values as <a href=\"http://textpattern.net/wiki/index.php?title=variable\" rel=\"nofollow\">variables</a>.</p>\n\n<pre><code>&lt;txp:rah_repeat value=&quot;JavaScript, jQuery, 1.8.0&quot; assign=&quot;language, framework, version&quot; /&gt;\n\n&lt;txp:variable name=&quot;language&quot; /&gt;\n&lt;txp:variable name=&quot;framework&quot; /&gt;\n\n&lt;txp:if_variable name=&quot;version&quot; value=&quot;1.8.0&quot;&gt;\n	Version is 1.8.0.\n&lt;/txp:if_variable&gt;</code></pre>\n\n<h2>Changelog</h2>\n\n<h3>Version 1.0.1 &#8211; 2013/05/07</h3>\n\n<ul>\n	<li>Composer package now uses <a href=\"https://packagist.org/packages/textpattern/lock\" rel=\"nofollow\">textpattern/lock</a> and <a href=\"https://packagist.org/packages/textpattern/installer\" rel=\"nofollow\">textpattern/installer</a>. The package installs to Textpattern without any extra configuration.</li>\n</ul>\n\n<h3>Version 1.0.0 &#8211; 2013/04/23</h3>\n\n<ul>\n	<li>Fixed: Return a empty string instead of <span class=\"caps\">NULL</span> byte on halt.</li>\n	<li>Added: <code>form</code> attribute.</li>\n	<li>Added: <code>index</code> attribute to the <code>rah_repeat_value</code> tag.</li>\n	<li>Now requires Textpattern 4.5.0 or newer.</li>\n</ul>\n\n<h3>Version 0.8.1 &#8211; 2012/08/25</h3>\n\n<ul>\n	<li>Fixed: <code>range</code> attribute. It ignored any options and always created an list of 1-10.</li>\n</ul>\n\n<h3>Version 0.8 &#8211; 2012/08/24</h3>\n\n<ul>\n	<li>Fixed: made the <code>sort</code> attribute&#8217;s direction optional.</li>\n	<li>Added: <code>exclude</code> can now take and exclude empty strings (<code>&quot;&quot;</code>) and zeros (<code>0</code>).</li>\n	<li>Added: <code>range</code> attribute. Allows generating automated lists (<code>range=&quot;min, max, step&quot;</code>).</li>\n	<li>Added: <code>assign</code> attribute. Allows extracting values as variables.</li>\n	<li>Added: <code>escape</code> attribute to <code>&lt;txp:rah_repeat_value /&gt;</code>.</li>\n	<li>Added: Support for natural ordering (<code>sort=&quot;natural&quot;</code>).</li>\n	<li>Changed: Now <code>trim</code> is enabled by default. Previously values weren&#8217;t trimmed from white-space by default.</li>\n	<li>Changed: Renamed <code>locale</code> sorting option to <code>LOCALE_STRING</code>.</li>\n	<li>Changed: Order can be reversed with out re-sorting by using <code>sort=&quot;desc&quot;</code>.</li>\n	<li>Now requires <span class=\"caps\">PHP</span> 5.2 (or newer).</li>\n</ul>\n\n<h3>Version 0.7 &#8211; 2011/12/02</h3>\n\n<ul>\n	<li>Added: <code>trim</code> attribute. When set to <code>1</code>, provided values are trimmed from surrounding whitespace.</li>\n	<li>Fixed: “locale” sorting option. Previously it sorted values as a string, not by locale options.</li>\n	<li>Changed: limit&#8217;s default to <span class=\"caps\">NULL</span>. Leave limit unset if you only want offset without limit, or use a high value.</li>\n	<li>Improved: Better offset and limit functionality. Now slices the list of values before staring to build the markup.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2010/05/09</h3>\n\n<ul>\n	<li>Added: <code>exclude</code> attribute.</li>\n	<li>Fixed: <code>&lt;txp:rah_repeat_if_last&gt;</code> tag. Issue was caused by v0.5 update.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2010/05/08</h3>\n\n<ul>\n	<li>Changed offset&#8217;s default value from <code>unset</code> to <code>0</code>.</li>\n	<li>Added: <code>sort</code> attribute.</li>\n	<li>Added: <code>duplicates</code> attribute.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2009/11/30</h3>\n\n<ul>\n	<li>Fixed: now returns old parent global, if two tags are used inside each other, instead of defining it empty.</li>\n	<li>Added: <code>&lt;txp:rah_repeat_if_first&gt;</code>.</li>\n	<li>Added: <code>&lt;txp:rah_repeat_if_last&gt;</code>.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2009/11/28</h3>\n\n<ul>\n	<li>Added: <code>wraptag</code> attribute.</li>\n	<li>Added: <code>break</code> attribute.</li>\n	<li>Added: <code>class</code> attribute.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2009/11/23</h3>\n\n<ul>\n	<li>Added: <code>limit</code> attribute.</li>\n	<li>Added: <code>offset</code> attribute.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/11/20</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_repeat plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_repeat\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n/**\n * Creates a list from the given values.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat($atts, $thing = null)\n	{\n		global $rah_repeat, $variable;\n\n		extract(lAtts(array(\n			\'form\'       => \'\',\n			\'delimiter\'  => \',\',\n			\'value\'      => \'\',\n			\'limit\'      => null,\n			\'offset\'     => 0,\n			\'wraptag\'    => \'\',\n			\'break\'      => \'\',\n			\'class\'      => \'\',\n			\'duplicates\' => 0,\n			\'sort\'       => \'\',\n			\'exclude\'    => null,\n			\'trim\'       => 1,\n			\'range\'      => \'\',\n			\'assign\'     => null,\n		), $atts));\n\n		if ($range && strpos($range, \',\'))\n		{\n			$values = call_user_func_array(\'range\', do_list($range));\n		}\n		else\n		{\n			$values = explode($delimiter, $value);\n		}\n\n		if ($trim)\n		{\n			$values = doArray($values, \'trim\');\n		}\n\n		if ($duplicates)\n		{\n			$values = array_unique($values);\n		}\n\n		if ($exclude !== null)\n		{\n			$exclude = explode($delimiter, $exclude);\n\n			if ($trim)\n			{\n				$exclude = doArray($exclude, \'trim\');\n			}\n\n			$values = array_diff($values, $exclude);\n		}\n\n		if ($sort && $sort = doArray(doArray(explode(\' \', trim($sort), 2), \'trim\'), \'strtoupper\'))\n		{\n			if (count($sort) == 2 && defined(\'SORT_\'.$sort[0]))\n			{\n				sort($values, constant(\'SORT_\'.$sort[0]));\n			}\n\n			if (end($sort) == \'DESC\')\n			{\n				$values = array_reverse($values);\n			}\n		}\n\n		$values = array_slice($values, $offset, $limit);\n\n		if ($assign !== null)\n		{\n			foreach (do_list($assign) as $key => $var)\n			{\n				$value = isset($values[$key]) ? $values[$key] : \'\';\n				$variable[$var] = $value;\n			}\n		}\n\n		if (!$values || ($thing === null && $form === \'\'))\n		{\n			return \'\';\n		}\n\n		$count = count($values);\n\n		$i = 0;\n		$out = array();\n\n		foreach ($values as $string)\n		{\n			$i++;\n			$parent = $rah_repeat;\n\n			$rah_repeat = array(\n				\'string\' => $string,\n				\'first\'  => ($i == 1),\n				\'last\'   => ($count == $i),\n				\'index\'  => $i - 1,\n			);\n\n			if ($thing === null && $form !== \'\')\n			{\n				$out[] = parse_form($form);\n			}\n			else\n			{\n				$out[] = parse($thing);\n			}\n\n			$rah_repeat = $parent;\n		}\n\n		unset($rah_repeat);\n		return doWrap($out, $wraptag, $break, $class);\n	}\n\n/**\n * Returns the current value.\n *\n * @param  array  $atts\n * @return string\n */\n\n	function rah_repeat_value($atts)\n	{\n		global $rah_repeat;\n\n		extract(lAtts(array(\n			\'escape\' => 0,\n			\'index\'  => 0,\n		), $atts));\n\n		if (!isset($rah_repeat[\'string\']))\n		{\n			return \'\';\n		}\n\n		if ($index)\n		{\n			return $rah_repeat[\'index\'];\n		}\n\n		if ($escape)\n		{\n			return txpspecialchars($rah_repeat[\'string\']);\n		}\n\n		return $rah_repeat[\'string\'];\n	}\n\n/**\n * Checks if the item is the first.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat_if_first($atts, $thing = \'\')\n	{\n		global $rah_repeat;\n		return parse(EvalElse($thing, $rah_repeat[\'first\'] == true));\n	}\n\n/**\n * Checks if the item is the last.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat_if_last($atts, $thing = \'\')\n	{\n		global $rah_repeat;\n		return parse(EvalElse($thing, $rah_repeat[\'last\'] == true));\n	}', '\n\n/**\n * Rah_repeat plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_repeat\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n/**\n * Creates a list from the given values.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat($atts, $thing = null)\n	{\n		global $rah_repeat, $variable;\n\n		extract(lAtts(array(\n			\'form\'       => \'\',\n			\'delimiter\'  => \',\',\n			\'value\'      => \'\',\n			\'limit\'      => null,\n			\'offset\'     => 0,\n			\'wraptag\'    => \'\',\n			\'break\'      => \'\',\n			\'class\'      => \'\',\n			\'duplicates\' => 0,\n			\'sort\'       => \'\',\n			\'exclude\'    => null,\n			\'trim\'       => 1,\n			\'range\'      => \'\',\n			\'assign\'     => null,\n		), $atts));\n\n		if ($range && strpos($range, \',\'))\n		{\n			$values = call_user_func_array(\'range\', do_list($range));\n		}\n		else\n		{\n			$values = explode($delimiter, $value);\n		}\n\n		if ($trim)\n		{\n			$values = doArray($values, \'trim\');\n		}\n\n		if ($duplicates)\n		{\n			$values = array_unique($values);\n		}\n\n		if ($exclude !== null)\n		{\n			$exclude = explode($delimiter, $exclude);\n\n			if ($trim)\n			{\n				$exclude = doArray($exclude, \'trim\');\n			}\n\n			$values = array_diff($values, $exclude);\n		}\n\n		if ($sort && $sort = doArray(doArray(explode(\' \', trim($sort), 2), \'trim\'), \'strtoupper\'))\n		{\n			if (count($sort) == 2 && defined(\'SORT_\'.$sort[0]))\n			{\n				sort($values, constant(\'SORT_\'.$sort[0]));\n			}\n\n			if (end($sort) == \'DESC\')\n			{\n				$values = array_reverse($values);\n			}\n		}\n\n		$values = array_slice($values, $offset, $limit);\n\n		if ($assign !== null)\n		{\n			foreach (do_list($assign) as $key => $var)\n			{\n				$value = isset($values[$key]) ? $values[$key] : \'\';\n				$variable[$var] = $value;\n			}\n		}\n\n		if (!$values || ($thing === null && $form === \'\'))\n		{\n			return \'\';\n		}\n\n		$count = count($values);\n\n		$i = 0;\n		$out = array();\n\n		foreach ($values as $string)\n		{\n			$i++;\n			$parent = $rah_repeat;\n\n			$rah_repeat = array(\n				\'string\' => $string,\n				\'first\'  => ($i == 1),\n				\'last\'   => ($count == $i),\n				\'index\'  => $i - 1,\n			);\n\n			if ($thing === null && $form !== \'\')\n			{\n				$out[] = parse_form($form);\n			}\n			else\n			{\n				$out[] = parse($thing);\n			}\n\n			$rah_repeat = $parent;\n		}\n\n		unset($rah_repeat);\n		return doWrap($out, $wraptag, $break, $class);\n	}\n\n/**\n * Returns the current value.\n *\n * @param  array  $atts\n * @return string\n */\n\n	function rah_repeat_value($atts)\n	{\n		global $rah_repeat;\n\n		extract(lAtts(array(\n			\'escape\' => 0,\n			\'index\'  => 0,\n		), $atts));\n\n		if (!isset($rah_repeat[\'string\']))\n		{\n			return \'\';\n		}\n\n		if ($index)\n		{\n			return $rah_repeat[\'index\'];\n		}\n\n		if ($escape)\n		{\n			return txpspecialchars($rah_repeat[\'string\']);\n		}\n\n		return $rah_repeat[\'string\'];\n	}\n\n/**\n * Checks if the item is the first.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat_if_first($atts, $thing = \'\')\n	{\n		global $rah_repeat;\n		return parse(EvalElse($thing, $rah_repeat[\'first\'] == true));\n	}\n\n/**\n * Checks if the item is the last.\n *\n * @param  array  $atts\n * @param  string $thing\n * @return string\n */\n\n	function rah_repeat_if_last($atts, $thing = \'\')\n	{\n		global $rah_repeat;\n		return parse(EvalElse($thing, $rah_repeat[\'last\'] == true));\n	}', '29f4f9bcab9009f28f731a6527065872', '', '', 0, 5, 0),
('rah_replace', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4.2', 'Search and replace', '<h1>rah_replace</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_replace\" rel=\"nofollow\">Project page</a> | <a href=\"https://packagist.org/packages/rah/rah_replace\" rel=\"nofollow\">Packagist</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_replace\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=30447\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_replace\" rel=\"nofollow\">Donate</a></p>\n\n<p>A tiny <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern</a> plugin that returns contained content with all searched occurrences replaced with given replacements. This ideally works in the same manner as <span class=\"caps\">PHP</span>&#8217;s <a href=\"http://php.net/manual/en/function.str-replace.php\" rel=\"nofollow\">str_replace</a> function, but with a safe and clean Textpattern template language tag.</p>\n\n<h2>Basics</h2>\n\n<p>The plugin, rah_replace, introduces a new container tag to Textpattern&#8217;s arsenal. The tag returns wrapped content with all found occurrences replaced with given replacements. A tag supports multiple searched occurrences and replacements.</p>\n\n<pre><code>&lt;txp:rah_replace from=&quot;value1, value2, ...&quot; to=&quot;value1, value2, ...&quot;&gt;\n	Searched content\n&lt;/txp:rah_replace&gt;</code></pre>\n\n<h2>Installing</h2>\n\n<p>Using <a href=\"http://getcomposer.org\" rel=\"nofollow\">Composer</a>:</p>\n\n<pre><code>$ composer.phar require rah/rah_replace</code></pre>\n\n<h2>Attributes</h2>\n\n<p>The tag is a container, <code>&lt;txp:rah_replace&gt; ...contained statement... &lt;/txp:rah_replace&gt;</code>, and attributes for it follow.</p>\n\n<p><strong>from</strong><br />\nStrings that will be searched and replaced with <code>to</code> attribute&#8217;s values. Separate multiple values with a comma (or <code>delimiter</code> if changed).<br />\nDefault: <code>from=&quot;&quot;</code> Example: <code>&quot;dog,cat,house&quot;</code></p>\n\n<p><strong>to</strong><br />\nReplacements that will be used to replace <code>from</code> attribute&#8217;s values. Comma (or <code>delimiter</code>) separated if multiple.<br />\nDefault: <code>to=&quot;&quot;</code> Example: <code>&quot;ship,home,hat&quot;</code></p>\n\n<p><strong>delimiter</strong><br />\nSets the delimiter used in <code>from</code> and <code>to</code> to separate multiple values. Default is a comma.<br />\nDefault: <code>delimiter=&quot;,&quot;</code> Example: <code>&quot;|&quot;</code></p>\n\n<h2>Examples</h2>\n\n<h3>Replaces a <em>dog</em> with a <em>cat</em></h3>\n\n<pre><code>&lt;txp:rah_replace from=&quot;dog&quot; to=&quot;cat&quot;&gt;\n	My favorite animal is a dog.\n&lt;/txp:rah_replace&gt;</code></pre>\n\n<p>Returns: <code>My favorite animal is a cat.</code></p>\n\n<h3>Replace multiple needles with different replacements</h3>\n\n<pre><code>&lt;txp:rah_replace from=&quot;house,dog,Mike&quot; to=&quot;boat,friend,wife&quot;&gt;\n	I live in a house with my dog and Mike.\n&lt;/txp:rah_replace&gt;</code></pre>\n\n<p>Returns: <code>I live in a boat with my friend and wife.</code></p>\n\n<h3>Replace multiple needles with a one replacement</h3>\n\n<pre><code>&lt;txp:rah_replace from=&quot;Mike,dad&quot; to=&quot;I&quot;&gt;\n	I remember when dad and Mike did go to fishing.\n&lt;/txp:rah_replace&gt;</code></pre>\n\n<p>Returns: <code>I remember when I and I did go to fishing.</code></p>\n\n<h3>Using a different delimiter</h3>\n\n<p>By default any comma is treated as a delimiter and can not be used as actual value. To use a comma (<code>,</code>) as a needle or a replacement, you would have to change the delimiter to something else. Like for instance to a vertical bar:</p>\n\n<pre><code>&lt;txp:rah_replace from=&quot;.|,&quot; to=&quot;!&quot; delimiter=&quot;|&quot;&gt;\n	A, B, C.\n&lt;/txp:rah_replace&gt;</code></pre>\n\n<p>Returns: <code>A! B! C!</code></p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.4.2 &#8211; 2013/05/07</h3>\n\n<ul>\n	<li>The Composer package uses <a href=\"https://packagist.org/packages/textpattern/installer\" rel=\"nofollow\">textpattern/installer</a> and <a href=\"https://packagist.org/packages/textpattern/installer\" rel=\"nofollow\">textpattern/lock</a>.</li>\n</ul>\n\n<h3>Version 0.4.1 &#8211; 2013/04/25</h3>\n\n<ul>\n	<li>Minor clean up.</li>\n	<li>Released as a <a href=\"https://packagist.org/packages/rah/rah_replace\" rel=\"nofollow\">Composer package</a>.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2012/07/12</h3>\n\n<ul>\n	<li>Performance optimization.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/04/22</h3>\n\n<ul>\n	<li>Performance optimization.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2009/04/16</h3>\n\n<ul>\n	<li>Added a new attribute: <code>delimiter</code>.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/04/16</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_replace plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_replace\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_replace($atts, $thing = \'\')\n	{\n		extract(lAtts(array(\n			\'from\'      => \'\',\n			\'to\'        => \'\',\n			\'delimiter\' => \',\',\n		), $atts));\n\n		if ($delimiter !== \'\')\n		{\n			$from = explode($delimiter, $from);\n\n			if (strpos($to, $delimiter) !== false)\n			{\n				$to = explode($delimiter, $to);\n			}\n		}\n\n		return str_replace($from, $to, parse($thing));\n	}', '\n\n/**\n * Rah_replace plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2009-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_replace\n *\n * Copyright (C) 2013 Jukka Svahn http://rahforum.biz\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_replace($atts, $thing = \'\')\n	{\n		extract(lAtts(array(\n			\'from\'      => \'\',\n			\'to\'        => \'\',\n			\'delimiter\' => \',\',\n		), $atts));\n\n		if ($delimiter !== \'\')\n		{\n			$from = explode($delimiter, $from);\n\n			if (strpos($to, $delimiter) !== false)\n			{\n				$to = explode($delimiter, $to);\n			}\n		}\n\n		return str_replace($from, $to, parse($thing));\n	}', '928a3e46a0450cd30253acb1cb2446a5', '', '', 0, 5, 0),
('rah_runtime', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.4', 'Diagnose execution times', '<h1>rah_runtime</h1>\n\n<p>Rah_runtime returns runtimes from point A to B in microseconds. The plugin can be used to diagnose runtimes of specific blocks of Textpattern&#8217;s markup code.</p>\n\n<h2>Basics</h2>\n\n<pre><code>&lt;txp:rah_runtime format=&quot;1&quot;&gt;\n	...contained statements...\n&lt;/txp:rah_runtime&gt;</code></pre>\n\n<p>The plugin introduces a new Textpattern tag, <code>&lt;txp:rah_runtime /&gt;</code>. The tag calculates execution runtimes for Textpattern template markup. The runtimes can be printed to the page template and are added to a <a href=\"http://textpattern.net/wiki/index.php?title=Tag_trace\" rel=\"nofollow\">tag trace</a>.</p>\n\n<p>The rah_runtime tag can be used both as a container and as a self-closing single tag. When the tag is used as a container, it calculates a runtime of the wrapped code. When used as a self-closing single tag, it calculates a runtime from the first tag instance to the second.</p>\n\n<h2>Attributes</h2>\n\n<p><code>&lt;txp:rah_runtime/&gt;</code> tag&#8217;s attributes are as follows.</p>\n\n<p><strong>return</strong><br />\nIf set to <code>1</code>, the tag outputs the runtime to the page template. If <code>0</code> the runtime is only added and visible in the page&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Tag_trace\" rel=\"nofollow\">tag trace</a> when the site is in debugging mode. By default <code>return</code> is <code>0</code>, and the runtimes are only added to the tag trace.<br />\nExample: <code>return=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<p><strong>format</strong><br />\nReturn formatted version of the time. When used, resulting value (float) is presented as a fixed decimal number. By default the option is used (<code>1</code>).<br />\nExample: <code>format=&quot;0&quot;</code> Default: <code>&quot;1&quot;</code></p>\n\n<p><strong>index</strong><br />\nWith this attribute you can nest several tag pairs and count several independent runtimes. The <code>index</code> attribute only applies when <code>&lt;txp:rah_runtime /&gt;</code> is used as a self-closing single tag.<br />\nExample: <code>index=&quot;articles&quot;</code> Default: <code>&quot;default&quot;</code></p>\n\n<p><strong>persistent</strong><br />\nIf set to <code>1</code>, the runtime is kept alive after returning. Normally the runtime pair is destroyed after using it, but with <code>persistent</code> the initial runtime is kept and still usable. The attribute is useful if you want to calculate multiple runtimes from same single starting point. The <code>persistent</code> attribute only applies when <code>&lt;txp:rah_runtime /&gt;</code> is used as a self-closing single tag.<br />\nExample: <code>persistent=&quot;1&quot;</code> Default: <code>&quot;0&quot;</code></p>\n\n<h2>Examples</h2>\n\n<h3>Using as a container</h3>\n\n<p>As a container, <code>rah_runtime</code> calculates a runtime for the wrapped content.</p>\n\n<pre><code>&lt;txp:rah_runtime&gt;\n	&lt;txp:article_custom /&gt;\n&lt;/txp:rah_runtime&gt;</code></pre>\n\n<p>Above snippet will return the contained statement&#8217;s results, as it would normally without the rah_runtime, and the runtime of the contained statement is added to the page&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Tag_trace\" rel=\"nofollow\">tag trace</a>. The tag trace should display a line similar to this:</p>\n\n<pre><code>[rah_runtime (default): 0.000010013580322]</code></pre>\n\n<h3>Using as a single tag</h3>\n\n<p>When the rah_runtime tag is used as a single, self-closing tag, the runtime is calculated from a <code>&lt;txp:rah_runtime/&gt;</code> tag instance to the next. Essentially the runtime is for the markup and code between two <code>&lt;txp:rah_runtime/&gt;</code> tags.</p>\n\n<pre><code>&lt;txp:rah_runtime /&gt;\n	&lt;!--\n		Some code here, for example:\n		&lt;txp:article /&gt;\n	--&gt;\n&lt;txp:rah_runtime /&gt;</code></pre>\n\n<p>Above will return all the markup normally, like there weren&#8217;t any traces of rah_runtime, and the runtime is added to the page&#8217;s tag trace as a line similar to this:</p>\n\n<pre><code>[rah_runtime (default): 0.000010013580322]</code></pre>\n\n<h3>Returning runtimes to the page</h3>\n\n<p>Normally the resulting runtimes are added to the <a href=\"http://textpattern.net/wiki/index.php?title=Tag_trace\" rel=\"nofollow\">tag trace</a> and are not visible in the normal page content. That is unless <code>return</code> attribute is set to <code>1</code>.</p>\n\n<pre><code>&lt;txp:rah_runtime /&gt;\nRuntime: &lt;txp:rah_runtime return=&quot;1&quot; /&gt;</code></pre>\n\n<p>The runtime is added to the tag trace and the above will return:</p>\n\n<pre><code>Runtime: 0.00021401458032</code></pre>\n\n<h3>Using multiple nested tag pairs</h3>\n\n<p>The rah_runtime tag&#8217;s <code>index</code> attribute can be used to nest and mix multiple <code>&lt;txp:rah_runtime/&gt;</code> tags and runtime pairs together. This will allow calculating simultaneous runtimes that overlap each others.</p>\n\n<pre><code>&lt;txp:rah_runtime index=&quot;2&quot; /&gt;\n&lt;txp:rah_runtime index=&quot;1&quot; /&gt;\nTime1: &lt;txp:rah_runtime index=&quot;1&quot; return=&quot;1&quot; /&gt;\nTime2: &lt;txp:rah_runtime index=&quot;2&quot; return=&quot;1&quot; /&gt;</code></pre>\n\n<p>The above will return two runtimes:</p>\n\n<pre><code>Time1: 0.000081\nTime2: 0.000184</code></pre>\n\n<p>The <em>time2</em> contains the runtime of <em>time1</em> and more.</p>\n\n<h3>Using persistent runtimes</h3>\n\n<p>Persistent runtimes are useful when several runtimes are to be calculated from a same starting point.</p>\n\n<pre><code>&lt;!--\n	Starting the runtime timer\n--&gt;\n\n&lt;txp:rah_runtime persistent=&quot;1&quot; return=&quot;1&quot; /&gt;\n\n\n&lt;!--\n	Some code here, for example &lt;txp:article /&gt; ...\n	then output a (first) runtime\n--&gt;\n\n&lt;txp:rah_runtime persistent=&quot;1&quot; return=&quot;1&quot; /&gt;\n\n&lt;!--\n	Some more code here, for example\n	&lt;txp:output_form form=&quot;myform&quot; /&gt;\n\n	Output runtime, calculated\n	from the same original starting point\n--&gt;\n\n&lt;txp:rah_runtime persistent=&quot;1&quot; return=&quot;1&quot; /&gt;\n\n&lt;!--\n	Even some more code.\n	Finally, return the final runtime still calculated \n	from the same original starting point.\n--&gt;\n\n&lt;txp:rah_runtime return=&quot;1&quot; /&gt;</code></pre>\n\n<p>Note that every one of the rah_runtime tags (except for the last) has <code>persistent</code> attribute set to <code>1</code>. If the tag doesn&#8217;t have it, then the runtime pair is destroyed after returning it. To finally destroy the runtime, set persistent to <code>&quot;0&quot;</code> (zero) or leave it undefined.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.4 &#8211; 2012/07/12</h3>\n\n<ul>\n	<li>Added: <code>return</code> attribute.</li>\n	<li>Added: Now the runtimes are added to the tag trace.</li>\n	<li>Added: Container tag mode support.</li>\n	<li>Changed: By default runtimes are only visible in a tag trace. Set <code>return</code> attribute to <code>1</code> (<code>return=&quot;1&quot;</code>) output runtimes to the page template.</li>\n	<li>Changed: Default <code>index</code> to <code>default</code>. This is to avoid potential type juggling side-effects.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/08/19</h3>\n\n<ul>\n	<li>Added: a new attribute <code>persistent</code>. Disables destroying of used runtime pairs.</li>\n	<li>Changed: Now stores the runtimes in a static variable instead of a global.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/06/18</h3>\n\n<ul>\n	<li>Fixed: now returns as float (compatible with pre-PHP5). <a href=\"http://forum.textpattern.com/viewtopic.php?pid=230410#p230410\" rel=\"nofollow\">Thank you for reporting, Andreas</a>.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2010/06/15</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_runtime plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2010-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_runtime\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_runtime($atts, $thing=NULL) {\n		static $runtime = array();\n\n		extract(lAtts(array(\n			\'format\' => 1,\n			\'index\' => \'default\',\n			\'persistent\' => 0,\n			\'return\' => 0,\n		), $atts));\n		\n		if($thing !== NULL) {\n			$time = getmicrotime();\n			$thing = parse($thing);\n		}\n		\n		else {\n			\n			if(!isset($runtime[$index])) {\n				$runtime[$index] = getmicrotime();\n				return;\n			}\n			\n			$time = $runtime[$index];\n			\n			if(!$persistent) {\n				unset($runtime[$index]);\n			}\n		}\n\n		$time = getmicrotime() - $time;\n\n		if($format) {\n			$time = rtrim(number_format($time, 15, \'.\', \'\'), 0);\n		}\n		\n		trace_add(\'[rah_runtime (\'.$index.\'): \'.$time.\']\');\n		\n		return \n			($thing !== NULL ? $thing : \'\').\n			($return ? $time : \'\');\n	}\n', '\n\n/**\n * Rah_runtime plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2010-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_runtime\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	function rah_runtime($atts, $thing=NULL) {\n		static $runtime = array();\n\n		extract(lAtts(array(\n			\'format\' => 1,\n			\'index\' => \'default\',\n			\'persistent\' => 0,\n			\'return\' => 0,\n		), $atts));\n		\n		if($thing !== NULL) {\n			$time = getmicrotime();\n			$thing = parse($thing);\n		}\n		\n		else {\n			\n			if(!isset($runtime[$index])) {\n				$runtime[$index] = getmicrotime();\n				return;\n			}\n			\n			$time = $runtime[$index];\n			\n			if(!$persistent) {\n				unset($runtime[$index]);\n			}\n		}\n\n		$time = getmicrotime() - $time;\n\n		if($format) {\n			$time = rtrim(number_format($time, 15, \'.\', \'\'), 0);\n		}\n		\n		trace_add(\'[rah_runtime (\'.$index.\'): \'.$time.\']\');\n		\n		return \n			($thing !== NULL ? $thing : \'\').\n			($return ? $time : \'\');\n	}\n', '5c743bc5391a0b206b11df4786a9b21f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_section_titles', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.7', 'Write panel\'s section list displays titles instead of names', '<h1>rah_section_titles</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_section_titles\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_section_titles\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=27749\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_section_titles\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_section_titles makes <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern&#8217;s</a> Write panel&#8217;s <em>section</em> drop-down list display section titles instead of names.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_section_title&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.5.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_section_titles’ installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<p>The plugin is now in use and the Write panel&#8217;s sections list displays titles.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.7 &#8211; 2012/08/27</h3>\n\n<ul>\n	<li>Load the plugin on async requests.</li>\n	<li>Now requires Textpattern v4.5.0.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2012/07/30</h3>\n\n<ul>\n	<li>Fixed: Reintroduced missing <span class=\"caps\">HTML</span> <code>id</code> attribute.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2012/07/30</h3>\n\n<ul>\n	<li>Fixed: Sorting. Sections are sorted by the title and not the name.</li>\n	<li>Changed: Now works server-side by using pluggable_ui instead of JavaScript.</li>\n	<li>Textpattern v4.5.0 compatibility.</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2011/11/25</h3>\n\n<ul>\n	<li>Now uses Textpattern&#8217;s <code>escape_js()</code> to escape the strings used in JavaScript, and <code>script_js()</code> to return the JavaScript wrapped in script tags.</li>\n	<li>As of version 0.4, requires Textpattern version 4.4.0 or newer.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/06/28</h3>\n\n<ul>\n	<li>Fixed: Don&#8217;t throw up JavaScript when the browser has JavaScript disabled.</li>\n	<li>Changed: Escape <span class=\"caps\">HTML</span> markup by using jQuery&#8217;s text() method instead of html().</li>\n	<li>Changed: Plugin type to the newer admin-side only.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/06/09</h3>\n\n<ul>\n	<li>Better compatibility with other plugins that play with the same drop-down, and better integration with <span class=\"caps\">TXP</span>. Now the plugin doesn&#8217;t replace the entire dropdown, but only the labels within each item.</li>\n	<li>Generally improved code.</li>\n	<li>Moved the JavaScript to <code>&lt;head&gt;</code>.</li>\n	<li>Now requires Textpattern 4.0.7 or newer.</li>\n</ul>\n\n<h3>Version 0.1.2 &#8211; 2008/09/04</h3>\n\n<ul>\n	<li>Added missing <code>&lt;br /&gt;</code>.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/07/12</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_section_titles plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_section_titles\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	if(@txpinterface == \'admin\') {\n		register_callback(\'rah_section_titles\', \'article_ui\', \'section\');\n	}\n\n/**\n * Replaces the section list\n * @param string $event\n * @param string $step\n * @param string $default\n * @param array $data\n * @return HTML\n */\n\n	function rah_section_titles($event, $step, $default, $data) {\n		\n		$rs = safe_rows(\'name, title\', \'txp_section\', \"name != \'default\' ORDER BY title ASC\");\n		\n		if(!$rs) {\n			return;\n		}\n		\n		$out = array();\n		\n		foreach($rs as $a) {\n			$out[$a[\'name\']] = $a[\'title\'];\n		}\n		\n		return \n			preg_replace(\n				\'/<select[^>]*?>[\\s\\S]*?<\\/select>/\',\n				selectInput(\'Section\', $out, $data[\'Section\'], \'\', \'\', \'section\'),\n				$default\n			);\n	}\n', '\n\n/**\n * Rah_section_titles plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2008-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_section_titles\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	if(@txpinterface == \'admin\') {\n		register_callback(\'rah_section_titles\', \'article_ui\', \'section\');\n	}\n\n/**\n * Replaces the section list\n * @param string $event\n * @param string $step\n * @param string $default\n * @param array $data\n * @return HTML\n */\n\n	function rah_section_titles($event, $step, $default, $data) {\n		\n		$rs = safe_rows(\'name, title\', \'txp_section\', \"name != \'default\' ORDER BY title ASC\");\n		\n		if(!$rs) {\n			return;\n		}\n		\n		$out = array();\n		\n		foreach($rs as $a) {\n			$out[$a[\'name\']] = $a[\'title\'];\n		}\n		\n		return \n			preg_replace(\n				\'/<select[^>]*?>[\\s\\S]*?<\\/select>/\',\n				selectInput(\'Section\', $out, $data[\'Section\'], \'\', \'\', \'section\'),\n				$default\n			);\n	}\n', 'd966d4b1dae27fcba30704ca4de85695', '', '', 4, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_sitemap', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.2', 'Build a valid advanced sitemap', '	<h1>rah_sitemap</h1>\n\n	<p>Rah_sitemap is a sitemap plugin for <a href=\"http://www.textpattern.com\">Textpattern <span class=\"caps\">CMS</span></a>. Easily build valid <a href=\"http://www.sitemap.org\">Sitemap.org</a> <acronym title=\"eXtensible Markup Language\"><span class=\"caps\">XML</span></acronym> sitemaps for search engines, including Google. Supports categories, sections, articles and even custom <span class=\"caps\">URL</span>s. All settings can be managed from clean interface. No dive to code required.</p>\n\n	<ul>\n		<li>Version: 1.2</li>\n		<li>Updated: 2011/03/09 12:20 AM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a>\n	<ul>\n		<li><a href=\"#permlink-rules\">Permlink rules</a></li>\n	</ul></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>Simple interface under <em>Textpattern &gt; Extensions &gt; Sitemap</em>: build, view, modify and customize sitemap.</li>\n		<li>Filter, include and exclude section, category and article <span class=\"caps\">URL</span>s from the sitemap.</li>\n		<li>Create and insert custom <span class=\"caps\">URL</span>s to the sitemap with couple simple clicks.</li>\n		<li>No need to create files nor set permissions, instead just use the plugin and eventually submit the sitemap <span class=\"caps\">URL</span> to Google (via Webmaster tools) or other searh engine.</li>\n		<li>Sitemap is automatically gzipped as much as possible &#8212; at least if server supports gzipping.</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Minimum:</p>\n\n	<ul>\n		<li>Textpattern 4.0.7+</li>\n		<li><span class=\"caps\">PHP</span> 5+ (or 4.3.0 when custom timestamp format is used)</li>\n		<li>Optional: <span class=\"caps\">PHP</span> zlib extension</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li>Textpattern 4.0.8+</li>\n		<li><span class=\"caps\">PHP</span> 5.1.2+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>The general behavior stands: paste the plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use the sitemap.</p>\n\n	<p>You can locate rah_sitemap&#8217;s user-interface panel from <a href=\"?event=rah_sitemap\">Textpattern &gt; Extensions &gt; Sitemap.</a> From there you can modify preferences and view the sitemap.</p>\n\n	<h4 id=\"permlink-rules\">Permlink schemes and rules</h4>\n\n	<p>Rah_sitemap version 0.4 included a new feature: permlink settings. These setting, found in the panel, will let you define the permlink form for the <span class=\"caps\">URL</span>s in the Sitemap. This is a ideal tool for matching the <span class=\"caps\">URL</span>s to your own .httaccess rules or a plugin created custom <span class=\"caps\">URL</span> rules. If you want to use in-build urls defined by Textpattern itself, you can leave these setting unset.</p>\n\n	<h5>Article permlinks</h5>\n\n	<p>These <span class=\"caps\">URL</span> settings are used for articles that appear in the sitemap. To form the <span class=\"caps\">URL</span>s you can use any individual article context&#8217;s <code>&lt;txp:/&gt;</code> tag. Big shots like <code>&lt;txp:permlink /&gt;</code>, <code>&lt;txp:posted /&gt;</code>, <code>&lt;txp:title /&gt;</code>, <code>&lt;txp:category1 /&gt;</code>, <code>&lt;txp:category2 /&gt;</code>, <code>&lt;txp:section /&gt;</code>, <code>&lt;txp:if_article_author /&gt;</code>, <code>&lt;txp:if_article_category /&gt;</code>, <code>&lt;txp:if_article_id /&gt;</code> and so on.</p>\n\n	<p>Example Article permlink <span class=\"caps\">URL</span>:</p>\n\n<pre><code>&lt;txp:site_url /&gt;sections/&lt;txp:section/&gt;/articles/&lt;txp:article_id /&gt;/&lt;txp:article_url_title /&gt;\n</code></pre>\n\n	<h5>Category permlinks</h5>\n\n	<p>These <span class=\"caps\">URL</span> settings are used for Category links that appear in the sitemap. To form the <span class=\"caps\">URL</span>s you can use plain category context <span class=\"caps\">TXP</span> tags <code>&lt;txp:category /&gt;</code> and <code>&lt;txp:if_category /&gt;</code>. The plugin also provides two extra tagish tags, <code>[type]</code> and <code>[id]</code> which will basically output category&#8217;s type and id.</p>\n\n	<p>Example Category permlink <span class=\"caps\">URL</span>:</p>\n\n<pre><code>&lt;txp:site_url /&gt;view/category/[id]/&lt;txp:category link=&quot;0&quot; title=&quot;0&quot; /&gt;\n</code></pre>\n\n	<h5>Section permlinks</h5>\n\n	<p>These <span class=\"caps\">URL</span> settings are used for section links that appear in the sitemap. To form the <span class=\"caps\">URL</span>s you can use the two section tags, <code>&lt;txp:section /&gt;</code> and <code>&lt;txp:if_section /&gt;</code>.</p>\n\n<pre><code>&lt;txp:site_url /&gt;section/&lt;txp:section /&gt;\n</code></pre>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 1.2</strong></p>\n\n	<ul>\n		<li>Added: adds site <span class=\"caps\">URL</span> to relative article permlinks. Basically a fix for gbp_permanent_links.</li>\n		<li>Changed: from permlinkurl_id() to permlinkurl(). Greatly reduced the amount of queries generating article permlinks makes.</li>\n	</ul>\n\n	<p><strong>Version 1.1</strong></p>\n\n	<ul>\n		<li>Fixed issues appearing with the installer when MySQL is in strict mode. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=236637#p236637\">Thank you for reporting, Gallex</a>.</li>\n	</ul>\n\n	<p><strong>Version 1.0</strong></p>\n\n	<ul>\n		<li>Slightly changed backend&#8217;s installer call; only check for installing if there is no preferences available.</li>\n	</ul>\n\n	<p><strong>Version 0.9</strong></p>\n\n	<ul>\n		<li>Fixed: now correctly parses category tags in category <span class=\"caps\">URL</span>s. Thank you for <a href=\"http://forum.textpattern.com/viewtopic.php?pid=233619#p233619\">reporting</a>, Andreas.</li>\n	</ul>\n\n	<p><strong>Version 0.8</strong></p>\n\n	<ul>\n		<li>Now compression level field&#8217;s label now links to the correct field id.</li>\n		<li>Now suppresses E_WARNING/E_STRICT notices in live mode caused by Textpattern&#8217;s timezone code when some conditions are met (<span class=\"caps\">TXP</span> 4.2.0, <span class=\"caps\">PHP</span> 5.1.0+, <span class=\"caps\">TXP</span>&#8217;s Auto-<span class=\"caps\">DST</span> feature disabled, <span class=\"caps\">TXP</span> in Live mode). Error suppression will be removed when <span class=\"caps\">TXP</span> version is released with fully working timezone settings.</li>\n		<li>Now generates <span class=\"caps\">UNIX</span> timestamps within the <span class=\"caps\">SQL</span> query, not with <span class=\"caps\">PHP</span>.</li>\n		<li>Changed sliding panels&#8217; links (<code>a</code> elements) into spans.</li>\n	</ul>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Fixed: now deleting custom url leads back to the list view, not to the editing form.</li>\n		<li>Removed some leftover inline styles from v0.6.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Rewritten the code that generates the sitemap.</li>\n		<li>New admin panel look.</li>\n		<li>Now custom permlink modes and custom urls are escaped. Users can input unescaped <span class=\"caps\">URL</span>s/markup from now on.</li>\n		<li>Now custom <span class=\"caps\">URL</span> list shows the full formatted <span class=\"caps\">URL</span> after auto-fill instead of the user input.</li>\n		<li>Now custom <span class=\"caps\">URL</span>s that start with www. are completed with http:// protocol.</li>\n		<li>Now all urls that do not start with either http, https, www, ftp or ftps protocol are auto-completed with the site&#8217;s address.</li>\n		<li>Custom url editor got own panel. No longer the form is above the <span class=\"caps\">URL</span> list.</li>\n		<li>Added ability to manually turn gzib compression off and change the compression level.</li>\n		<li>Added setting to set zlib.output_compression off. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=224931#p224931\">See here</a>, thank you for reporting superfly.</li>\n		<li>Preferences are now trimmed during save.</li>\n		<li>Merged <code>rah_sitemap_update()</code> with <code>rah_sitemap_save()</code>.</li>\n		<li>From now on all new installations have default settings defined that will automatically exclude link, file and image categories from the sitemap. This won&#8217;t effect updaters.</li>\n		<li>Changed sitemap&#8217;s callback register from pre <code>pretext</code> to callback after it (callback is now <code>textpattern</code>). Now <code>$pretext</code> is set before the sitemap and thus more plugins might work within permlink settings and custom urls.</li>\n		<li>When using <span class=\"caps\">TXP</span>&#8217;s clean <span class=\"caps\">URL</span>s, requesting <code>/sitemap.xml.gz</code> and <code>/sitemap.xml</code> <span class=\"caps\">URL</span>s will return the sitemap, not just the <code>/?rah_sitemap=sitemap</code>. This will of course require existing fully working clean urls.</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Added customizable timestamp formats.</li>\n		<li>Cleaned backend markup.</li>\n		<li>Combined individual preference queries.</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added support for custom permlink rules: Now you can easily set any kind of permlink rules for articles, section and categories.</li>\n		<li>Added option to exclude future articles.</li>\n		<li>Added option to exclude past articles.</li>\n		<li>Added option to exclude expired articles.</li>\n		<li>Moved Custom <span class=\"caps\">URL</span> UI to it&#8217;s own page.</li>\n		<li>Added multi-delete feature to Custom <span class=\"caps\">URL</span> UI.</li>\n		<li>Improved Custom <span class=\"caps\">URL</span> UI.</li>\n		<li>Removed default static appending domain from Custom <span class=\"caps\">URL</span> input field.</li>\n		<li>Changed <span class=\"caps\">TXP</span> minimum requirement to version 4.0.7 (and above). Note that the plugin still works with older <span class=\"caps\">TXP</span> versions (down to 4.0.5) if the <em>Exclude Expired articles</em> -option is left empty (unset).</li>\n	</ul>\n\n	<p><strong>Version 0.3.2</strong></p>\n\n	<ul>\n		<li>Fixed view url that still (from version 0.2) included installation address before link.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Added option to insert <span class=\"caps\">URL</span>s that are outside Textpattern install directory.</li>\n		<li>Fixed option to exclude categories directly by type: added forgotten link type.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Added option to exclude/include sticky articles.</li>\n		<li>Added option to exclude categories directly by type.</li>\n		<li>Fixed bug: now shows all categories, and not only article-type, in admin panel.</li>\n		<li>Fixed bug: removed double install query (didn&#8217;t do a thing, just checked table status twice).</li>\n	</ul>\n\n	<p><strong>Version 0.1.2</strong></p>\n\n	<ul>\n		<li>Fixed article listing bug caused by nasty little typo: now only 4 and 5 statuses are listed.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>', '	##################\n	#\n	#	rah_sitemap-plugin for Textpattern\n	#	version 1.2\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		add_privs(\'rah_sitemap\',\'1,2\');\n		register_tab(\'extensions\',\'rah_sitemap\',\'Sitemap\');\n		register_callback(\'rah_sitemap_page\',\'rah_sitemap\');\n		register_callback(\'rah_sitemap_head\',\'admin_side\',\'head_end\');\n	} else \n		register_callback(\'rah_sitemap\',\'textpattern\');\n\n/**\n	Checks if the sitemap should be returned\n*/\n\n	function rah_sitemap_delivery() {\n		global $pretext;\n		\n		$uri = $pretext[\'request_uri\'];\n		$uri = explode(\'/\',$uri);\n		$uri = array_reverse($uri);\n		\n		if(in_array($uri[0],array(\'sitemap.xml.gz\',\'sitemap.xml\')))\n			return true;\n		\n		if(gps(\'rah_sitemap\') == \'sitemap\')	\n			return true;\n		\n		return false;\n	}\n\n/**\n	The sitemap\n*/\n\n	function rah_sitemap() {\n		if(rah_sitemap_delivery() == false)\n			return;\n		\n		global $s, $thissection, $thiscategory, $c, $pretext, $thispage, $thisarticle;\n		\n		@$pref = rah_sitemap_prefs();\n		\n		if(!isset($pref[\'zlib_output\'])) {\n			rah_sitemap_install();\n			$pref = rah_sitemap_prefs();\n		}\n\n		header(\'Content-type: application/xml\');\n\n		if($pref[\'compress\'] == 1 && function_exists(\'gzencode\'))\n			header(\'Content-Encoding: gzip\');\n		\n		if($pref[\'zlib_output\'] == 1)\n			ini_set(\'zlib.output_compression\',\'Off\');\n	\n		$timestampformat = (!empty($pref[\'timestampformat\'])) ? $pref[\'timestampformat\'] : \'c\';\n		\n		$out[] = \n			\'<?xml version=\"1.0\" encoding=\"utf-8\"?>\'.\n			\'<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\'.\n			\'<url><loc>\'.hu.\'</loc></url>\';\n		\n		if($pref[\'nosections\'] != 1) {\n			\n			$rs = \n				safe_rows(\n					\'name\',\n					\'txp_section\',\n					rah_sitemap_in(\'name\',$pref[\'sections\'],\'default\') .\n					\" order by name asc\"\n				);\n			\n			$thisarticle[\'section\'] = \'\';\n			\n			foreach($rs as $a) {\n				\n				$s = $thispage[\'s\'] = $thissection[\'section\'] =  $pretext[\'s\'] = $a[\'name\'];\n				\n				if($pref[\'permlink_section\'])\n					$out[] = \'<url><loc>\'.parse($pref[\'permlink_section\']).\'</loc></url>\';\n				else \n					$out[] = \'<url><loc>\'.pagelinkurl(array(\'s\' => $a[\'name\'])).\'</loc></url>\';\n			}\n			\n			$s =  $thissection[\'section\'] = $thispage[\'s\'] = $pretext[\'s\'] = \'\';\n			\n		}\n		\n		$notypes = \'\';\n		\n		if($pref[\'nocategories\'] == 1)\n			$notypes[] = \'article\';\n		if($pref[\'nofile\'] == 1)\n			$notypes[] = \'file\';\n		if($pref[\'noimage\'] == 1)\n			$notypes[] = \'image\';\n		if($pref[\'nolink\'] == 1)\n			$notypes[] = \'link\';\n		\n		$not = explode(\',\',$pref[\'categories\']);\n		\n		$rs = \n			safe_rows(\n				\'name,type,id\',\n				\'txp_category\',\n				\"name != \'root\' \" . rah_sitemap_in(\'and type\',$notypes) . \" order by name asc\"\n			);\n		\n		foreach($rs as $a) {\n\n			if(in_array($a[\'type\'].\'_||_\'.$a[\'name\'],$not))\n				continue;\n\n			$c = $thiscategory[\'c\'] = $pretext[\'c\'] = $thispage[\'c\'] = $a[\'name\'];\n\n			if($pref[\'permlink_category\'])\n				$out[] = \n					\n					\'<url><loc>\'.\n					\n					htmlspecialchars(str_replace(\n						array(\n							\'[type]\',\n							\'[id]\'\n						),\n						array(\n							$a[\'type\'],\n							$a[\'id\']\n						),\n						parse($pref[\'permlink_category\'])\n					)).\n					\n					\'</loc></url>\';\n					\n			else \n				$out[] = \n					\'<url><loc>\'.pagelinkurl(array(\'c\' => $a[\'name\'])).\'</loc></url>\';\n		}\n		\n		$c = $thiscategory[\'c\'] = $pretext[\'c\'] = $thispage[\'c\'] =  \'\';\n		\n		if($pref[\'noarticles\'] != 1) {\n			\n			$sql[] = rah_sitemap_in(\' and Category1\',$pref[\'articlecategories\']);\n			$sql[] = rah_sitemap_in(\' and Category2\',$pref[\'articlecategories\']);\n			$sql[] = rah_sitemap_in(\' and Status\',$pref[\'articlestatus\'],\'1,2,3\');\n			$sql[] = rah_sitemap_in(\' and Section\',$pref[\'articlesections\']);\n			\n			if($pref[\'articlefuture\'])\n				$sql[] = \' and Posted <= now()\';\n			if($pref[\'articlepast\'])\n				$sql[] = \' and Posted > now()\';\n			if($pref[\'articleexpired\'])\n				$sql[] = \" and (Expires = \'0000-00-00 00:00:00\' or Expires >= now())\";\n			\n			\n			if($pref[\'permlink_article\'])\n				$columns = \n				 	\'*,  unix_timestamp(Posted) as posted, \'.\n				 	\'unix_timestamp(Expires) as uExpires, \'.\n				 	\'unix_timestamp(LastMod) as uLastMod\';\n			else\n				$columns = \n					\'ID as thisid, Section as section, \'.\n					\'Title as title, url_title, unix_timestamp(Posted)\'.\n					\' as posted, unix_timestamp(LastMod) as uLastMod\';\n			\n			$rs = \n				safe_rows(\n					$columns,\n					\'textpattern\',\n					\'1=1\' . implode(\'\',$sql). \' order by Posted desc\'\n				);\n			\n			foreach($rs as $a) {\n				extract($a);\n				\n				if($pref[\'permlink_article\']) {\n					$thisarticle = \n						array(\n							\'thisid\' => $ID,\n							\'posted\' => $posted,\n							\'modified\' => $uLastMod,\n							\'annotate\' => $Annotate,\n							\'comments_invite\' => $AnnotateInvite,\n							\'authorid\' => $AuthorID,\n							\'title\' => $Title,\n							\'url_title\' => $url_title,\n							\'category1\' => $Category1,\n							\'category2\' => $Category2,\n							\'section\' => $Section,\n							\'keywords\' => $Keywords,\n							\'article_image\' => $Image,\n							\'comments_count\' => $comments_count,\n							\'body\' => $Body_html,\n							\'excerpt\' => $Excerpt_html,\n							\'override_form\' => $override_form,\n							\'status\'=> $Status\n						)\n					;\n					\n					@$url = htmlspecialchars(parse($pref[\'permlink_article\']));\n				}\n				\n				else {\n					@$url = permlinkurl($a);\n					\n					/*\n						Fix for gbp_permanent_links\n					*/\n					\n					if(strpos($url,\'/\') === 0)\n						$url = hu . ltrim($url,\'/\');\n				}\n				\n				@$out[] = \n					\'<url><loc>\'.$url.\'</loc><lastmod>\'.\n					($uLastMod < $posted ? \n						date($timestampformat,$posted) : \n						date($timestampformat,$uLastMod)\n					).\n					\'</lastmod></url>\'\n				;\n				\n			}\n			$thisarticle = \'\';\n		}\n		\n		$rs = \n			safe_rows(\n				\'*, unix_timestamp(posted) as uposted\',\n				\'rah_sitemap\',\n				\'1=1 order by posted desc\'\n			);\n		\n		foreach($rs as $a) {\n			$url = parse($a[\'url\']);\n			$out[] = \'<url><loc>\'.rah_sitemap_uri($url,1).\'</loc>\';\n			if($a[\'include\'] == 1)\n				@$out[] = \'<lastmod>\'.date($timestampformat,$a[\'uposted\']).\'</lastmod>\';\n			$out[] = \'</url>\';\n		}\n		\n		$out[] = \'</urlset>\';\n		$out = implode(\'\',$out);\n		\n		echo (@$pref[\'compress\'] == 1 && function_exists(\'gzencode\')) ? gzencode($out,$pref[\'compression_level\']) : $out;\n		exit();\n	}\n\n/**\n	Delivers the panels\n*/\n\n	function rah_sitemap_page() {\n		require_privs(\'rah_sitemap\');\n		global $step;\n		if(in_array($step,array(\n			\'rah_sitemap_save\',\n			\'rah_sitemap_custom_list\',\n			\'rah_sitemap_custom_form\',\n			\'rah_sitemap_custom_save\',\n			\'rah_sitemap_delete\'\n		))) $step();\n		else rah_sitemap_list();\n	}\n\n/**\n	Preferences panel\n*/\n\n	function rah_sitemap_list($message=\'\') {\n		@$pref = rah_sitemap_prefs();\n\n		if(!isset($pref[\'zlib_output\'])) {\n			rah_sitemap_install();\n			$pref = rah_sitemap_prefs();\n		}\n\n		rah_sitemap_header(\n			\n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\n			\'		<p><strong>General preferences</strong></p>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Exclude sections and categories from the sitemap</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			rah_sitemap_listing(\'Exclude sections\',\'sections\',\'txp_section\',\"name != \'default\'\").\n			rah_sitemap_listing(\'Exclude categories\',\'categories\',\'txp_category\',\"name != \'root\' and title != \'root\'\").\n			\n			\'			<div class=\"rah_sitemap_column\">\'.n.\n			\n			\'				<strong>Advanced settings</strong><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nosections\" value=\"1\"\'.(($pref[\'nosections\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all section URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nofile\" value=\"1\"\'.(($pref[\'nofile\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all file-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"noimage\" value=\"1\"\'.(($pref[\'noimage\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all image-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nolink\" value=\"1\"\'.(($pref[\'nolink\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all link-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nocategories\" value=\"1\"\'.(($pref[\'nocategories\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all article-type category URLs</label><br />\'.n.\n			\n			\n			\'			</div>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Filter articles from the sitemap</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\n			rah_sitemap_listing(\'Exclude article sections\',\'articlesections\',\'txp_section\',\"name != \'default\'\").\n			rah_sitemap_listing(\'Exclude article categories\',\'articlecategories\',\'txp_category\',\"name != \'root\' and title != \'root\' and type = \'article\'\").\n			\n			\'			<div class=\"rah_sitemap_column\">\'.n.\n			\'				<strong>Advanced settings</strong><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"noarticles\" value=\"1\"\'.(($pref[\'noarticles\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Don\\\'t include articles in sitemap</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlestatus\" value=\"5\"\'.(($pref[\'articlestatus\'] == 5) ? \' checked=\"checked\"\' : \'\').\' /> Exclude sticky articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlefuture\" value=\"1\"\'.(($pref[\'articlefuture\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude future articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlepast\" value=\"1\"\'.(($pref[\'articlepast\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude past articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articleexpired\" value=\"1\"\'.(($pref[\'articleexpired\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude expired articles</label>\'.n.\n			\'			</div>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p><strong>Advanced settings</strong></p>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Compression methods</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Compression.</strong> With these settings you can control compression and even turn it off if it causes problems on your server. It is recommeded to leave compression on if possible. Compression level 0 is the mildest and 9 is the maximum compression.</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_compress\">Use Gzip compression:</label><br />\'.n.\n			\n			\'				<select name=\"compress\" id=\"rah_sitemap_compress\">\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'compress\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'compress\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_compression_level\">Compression level:</label><br />\'.n.\n			\n			\'				<select name=\"compression_level\" id=\"rah_sitemap_compression_level\">\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'compression_level\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>0</option>\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'compression_level\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>1</option>\'.n.\n			\'					<option value=\"2\"\'.(($pref[\'compression_level\'] == 2) ? \' selected=\"selected\"\' : \'\').\'>2</option>\'.n.\n			\'					<option value=\"3\"\'.(($pref[\'compression_level\'] == 3) ? \' selected=\"selected\"\' : \'\').\'>3</option>\'.n.\n			\'					<option value=\"4\"\'.(($pref[\'compression_level\'] == 4) ? \' selected=\"selected\"\' : \'\').\'>4</option>\'.n.\n			\'					<option value=\"5\"\'.(($pref[\'compression_level\'] == 5) ? \' selected=\"selected\"\' : \'\').\'>5</option>\'.n.\n			\'					<option value=\"6\"\'.(($pref[\'compression_level\'] == 6) ? \' selected=\"selected\"\' : \'\').\'>6</option>\'.n.\n			\'					<option value=\"7\"\'.(($pref[\'compression_level\'] == 7) ? \' selected=\"selected\"\' : \'\').\'>7</option>\'.n.\n			\'					<option value=\"8\"\'.(($pref[\'compression_level\'] == 8) ? \' selected=\"selected\"\' : \'\').\'>8</option>\'.n.\n			\'					<option value=\"9\"\'.(($pref[\'compression_level\'] == 9) ? \' selected=\"selected\"\' : \'\').\'>9</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_zlib_output\">Set zlib output compression off. If set to no, configuration is not modified:</label><br />\'.n.\n			\n			\'				<select name=\"zlib_output\" id=\"rah_sitemap_zlib_output\">\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'zlib_output\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'zlib_output\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\'		</div>\'.n.\n			\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Override timestamp format</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Timestamps.</strong> Customize the date format used in last modified timestamps. Use <a href=\"http://php.net/manual/en/function.date.php\">date()</a> string values. If unset (left empty) default ISO 8601 date (<code>c</code>) is used. Use this setting if you want to hard-code/override timestamps, timezones or if your server doesn\\\'t support <code>c</code> format.</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_timestampformat\">Format:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" style=\"width: 940px;\" name=\"timestampformat\" id=\"rah_sitemap_timestampformat\" value=\"\'.htmlspecialchars($pref[\'timestampformat\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Override permlink formats</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Permlinks.</strong> With these settings you can make the Sitemap\\\'s URLs to match your own URL rewriting rules, or permlinks made by a <em>custom permlink rule</em> plugin. You can leave these fields empty, if using TXP\\\'s inbuild permlink rules. Note that these setting do not rewrite TXP\\\'s permlinks for you, use only for matching not rewriting!</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_category\">Category URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_category\" id=\"rah_sitemap_permlink_category\" value=\"\'.htmlspecialchars($pref[\'permlink_category\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_section\">Section URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_section\" id=\"rah_sitemap_permlink_section\" value=\"\'.htmlspecialchars($pref[\'permlink_section\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_article\">Article URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_article\" id=\"rah_sitemap_permlink_article\" value=\"\'.htmlspecialchars($pref[\'permlink_article\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'		<input type=\"hidden\" name=\"step\" value=\"rah_sitemap_save\" />\'.n.\n			\n			\'		<p><input type=\"submit\" value=\"\'.gTxt(\'save\').\'\" class=\"publish\" /></p>\'.n.\n			\n			\'	</form>\',\n			\n			\'rah_sitemap\',\n			\'Manage your sitemap\',\n			\'Sitemap.org Sitemaps\',\n			$message\n			\n		);\n	}\n\n/**\n	Panel, lists custom URLs\n*/\n\n	function rah_sitemap_custom_list($message=\'\') {\n\n		$out[] = \n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\'		<table id=\"list\" class=\"list rah_sitemap_table\"  border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'			<tr>\'.n.\n			\'				<th>URL</th>\'.n.\n			\'				<th>LastMod</th>\'.n.\n			\'				<th>Include LastMod</th>\'.n.\n			\'				<th>View</th>\'.n.\n			\'				<th>&#160;</th>\'.n.\n			\'			</tr>\'.n;\n		\n		$rs =\n			safe_rows(\n				\'url,posted,include\',\n				\'rah_sitemap\',\n				\"1=1 order by posted desc\"\n			);\n		\n		if($rs) {\n			foreach($rs as $a) {\n				$uri = rah_sitemap_uri($a[\'url\'],1);\n				$out[] = \n					\'			<tr>\'.n.\n					\'				<td><a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_form&amp;edit=\'.urlencode($a[\'url\']).\'\">\'.$uri.\'</a></td>\'.n.\n					\'				<td>\'.$a[\'posted\'].\'</td>\'.n.\n					\'				<td>\'.(($a[\'include\'] == 1) ? \'Yes\' : \'No\').\'</td>\'.n.\n					\'				<td><a target=\"_blank\" href=\"\'.htmlspecialchars($uri).\'\">\'.gTxt(\'view\').\'</a></td>\'.n.\n					\'				<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.htmlspecialchars($a[\'url\']).\'\" /></td>\'.n.\n					\'			</tr>\'.n;\n			}\n		} else \n			$out[] =  \'			<tr><td colspan=\"5\">No custom URLs found.</td></tr>\'.n;\n		\n		$out[] =  \n			\'		</table>\'.n.\n			\'		<p id=\"rah_sitemap_step\">\'.n.\n			\'			<select name=\"step\">\'.n.\n			\'				<option value=\"\">With selected...</option>\'.n.\n			\'				<option value=\"rah_sitemap_delete\">Delete</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" class=\"smallerbox\" value=\"Go\" />\'.n.\n			\'		</p>\'.n.\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'	</form>\'.n;\n		\n		rah_sitemap_header(\n			$out,\n			\'rah_sitemap\',\n			\'List of custom URLs\',\n			\'Sitemap\',\n			$message\n		);\n	}\n\n/**\n	Panel to add custom URLs\n*/\n\n	function rah_sitemap_custom_form($message=\'\') {\n		\n		$edit = gps(\'edit\');\n		\n		if($edit) {\n			\n			$rs = \n				safe_row(\n					\'*\',\n					\'rah_sitemap\',\n					\"url=\'\".doSlash($edit).\"\'\"\n				);\n			\n			if(!$rs) {\n				rah_sitemap_custom_list(\'Selection not found.\');\n				return;\n			}\n			\n			extract($rs);\n		\n		}\n		\n		if(!isset($rs))\n			extract(gpsa(array(\n				\'url\',\'include\',\'posted\'\n			)));\n		\n		rah_sitemap_header(\n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_url\">URL:</label><br />\'.n.\n			\'			<input id=\"rah_sitemap_url\" class=\"edit\" type=\"text\" name=\"url\" value=\"\'.htmlspecialchars($url).\'\" />\'.n.\n			\'		</p>\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_posted\">LastMod (YYYY-mm-dd HH:MM:SS). Leave empty to use current time:</label><br />\'.n.\n			\'			<input id=\"rah_sitemap_posted\" class=\"edit\" type=\"text\" name=\"posted\" value=\"\'.htmlspecialchars($posted).\'\" />\'.n.\n			\'		</p>\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_lastmod\">Include LastMod:</label><br />\'.n.\n			\'			<select id=\"rah_sitemap_lastmod\" name=\"include\">\'.n.\n			\'					<option value=\"0\"\'.(($include == 0) ? \' selected=\"selected\"\' : \'\').\'>\'.gTxt(\'no\').\' (Recommended)</option>\'.n.\n			\'					<option value=\"1\"\'.(($include == 1) ? \' selected=\"selected\"\' : \'\').\'>\'.gTxt(\'yes\').\'</option>\'.n.\n			\'			</select>\'.n.\n			\'		</p>\'.n.\n			\'		<p><input type=\"submit\" value=\"\'.gTxt(\'save\').\'\" class=\"publish\" /></p>\'.n.\n			\n			(($edit) ? \n				\'		<input type=\"hidden\" name=\"edit\" value=\"\'.htmlspecialchars($edit).\'\" />\' : \'\'\n			).\n			\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'		<input type=\"hidden\" name=\"step\" value=\"rah_sitemap_custom_save\" />\'.n.\n			\n			\'	</form>\'\n			\n			,\'rah_sitemap\',\n			\'Add a new custom URL\',\n			\'Sitemap\',\n			$message\n			\n		);\n		\n	}\n\n/**\n	Saves custom URL\n*/\n\n	function rah_sitemap_custom_save() {\n		extract(doSlash(gpsa(array(\n			\'url\',\n			\'posted\',\n			\'include\',\n			\'reset\',\n			\'edit\'\n		))));\n		\n		if(empty($posted) or $reset == 1)\n			$posted = \'posted=now()\';\n		else \n			$posted = \"posted=\'$posted\'\";\n		\n		if(!$edit && safe_count(\'rah_sitemap\',\"url=\'$url\'\") != 0) {\n			rah_sitemap_custom_form(\'URL already exists.\');\n			return;\n		}\n		\n		if($edit && safe_count(\'rah_sitemap\',\"url=\'$edit\'\") == 1) {\n			\n			if($url != $edit && safe_count(\'rah_sitemap\',\"url=\'$url\'\") == 1) {\n				rah_sitemap_custom_form(\'New URL already exists.\');\n				return;\n			}\n			\n			safe_update(\n				\'rah_sitemap\',\n				\"$posted,\n				include=\'$include\',\n				url=\'$url\'\",\n				\"url=\'$edit\'\"\n			);\n			\n			rah_sitemap_custom_list(\'URL updated.\');\n			return;\n			\n		}\n		\n		safe_insert(\n			\'rah_sitemap\',\n			\"url=\'$url\',\n			$posted,\n			include=\'$include\'\"\n		);\n		\n		rah_sitemap_custom_list(\'URL added.\');\n		return;\n	}\n\n/**\n	Outputs the panel\'s CSS and JavaScript to page\'s <head> segment\n*/\n\n	function rah_sitemap_head() {\n		\n		global $event;\n		\n		if($event != \'rah_sitemap\')\n			return;\n		\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_sitemap_container {\n					width: 950px;\n					margin: 0 auto;\n				}\n				#rah_sitemap_container #rah_sitemap_step {\n					text-align: right;\n					padding-top: 10px;\n				}\n				#rah_sitemap_container .rah_sitemap_table {\n					width: 100%;\n				}\n				#rah_sitemap_container .rah_sitemap_column {\n					width: 315px;\n					float: left;\n					display: inline;\n					padding: 0 0 10px 0;\n				}\n				#rah_sitemap_container .rah_sitemap_heading {\n					font-weight: 900;\n					padding: 5px 0;\n					margin: 0 0 10px 0;\n					border-top: 1px solid #ccc;\n					border-bottom: 1px solid #ccc;\n				}\n				#rah_sitemap_container .rah_sitemap_heading span {\n					cursor: pointer;\n					color: #963;\n				}\n				#rah_sitemap_container .rah_sitemap_heading span:hover {\n					text-decoration: underline;\n				}\n				#rah_sitemap_container .rah_sitemap_more {\n					overflow: hidden;\n				}\n				#rah_sitemap_container input.edit {\n					width: 940px;\n				}\n				#rah_sitemap_zlib_output,\n				#rah_sitemap_compression_level,\n				#rah_sitemap_compress,\n				#rah_sitemap_lastmod {\n					width: 450px;\n				}\n				.rah_sitemap_paragraph {\n					margin: 0 0 10px 0;\n					padding: 0;\n				}\n			</style>\n			<script type=\"text/javascript\">\n				$(document).ready(function(){\n					$(\'.rah_sitemap_more\').hide();\n					$(\'.rah_sitemap_heading\').click(function(){\n						$(this).next(\'div.rah_sitemap_more\').slideToggle();\n					});\n				});\n			</script>\nEOF;\n	}\n\n/**\n	The panel\'s navigation bar\n*/\n\n	function rah_sitemap_header($content=\'\',$title=\'rah_sitemap\',$msg=\'Manage your sitemap\',$pagetop=\'\',$message=\'\') {\n		\n		pagetop($pagetop,$message);\n		\n		if(is_array($content))\n			$content = implode(\'\',$content);\n		\n		echo \n			n.\n			\'	<div id=\"rah_sitemap_container\">\'.n.\n			\'		<h1><strong>\'.$title.\'</strong> | \'.$msg.\'</h1>\'.n.\n			\'		<p id=\"rah_sitemap_nav\">\'.\n					\' &#187; <a href=\"?event=rah_sitemap\">Preferences</a>\'.\n					\' &#187; <a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_form\">Add custom URL</a>\'.\n					\' &#187; <a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_list\">List of custom URLs</a>\'.\n					\' &#187; <a target=\"_blank\" href=\"\'.hu.\'?rah_sitemap=sitemap\">View the sitemap</a>\'.\n					\'</p>\'.n.\n			$content.n.	\n			\'	</div>\'.n;\n	}\n\n/**\n	Builds the required in array for SQL statements\n*/\n\n	function rah_sitemap_in($field=\'\',$array=\'\',$default=\'\',$sql=\' not in\') {\n		\n		if(empty($array) && empty($default))\n			return;\n		\n		if(!is_array($array))\n			$array = explode(\',\',$array);\n		\n		if(!empty($default)) {\n			$default = explode(\',\',$default);\n			$array = \n				array_merge(\n					$array,\n					$default\n				);\n		}\n		\n		foreach($array as $value) \n			$out[] = \"\'\".doSlash(trim($value)).\"\'\";\n		\n		if(!isset($out))\n			return;\n	\n		return \n			$field . $sql . \'(\' . implode(\',\',$out) . \')\';\n		\n	}\n\n/**\n	Default settings\n*/\n\n	function rah_sitemap_pref_fields() {\n		return\n			array(\n				\'noarticles\' => \'\',\n				\'nosections\' => \'\',\n				\'nocategories\' => \'\',\n				\'articlecategories\' => \'\',\n				\'articlesections\' => \'\',\n				\'sections\' => \'\',\n				\'categories\' => \'\',\n				\'nofile\' => 1,\n				\'noimage\' => 1,\n				\'nolink\' => 1,\n				\'articlestatus\' => \'\',\n				\'articlefuture\' => \'\',\n				\'articlepast\' => \'\',\n				\'articleexpired\' => \'\',\n				\'permlink_category\' => \'\',\n				\'permlink_section\' => \'\',\n				\'permlink_article\' => \'\',\n				\'timestampformat\' => \'c\',\n				\'compress\' => 1,\n				\'compression_level\' => 9,\n				\'zlib_output\' => 0\n			);\n	}\n\n/**\n	Returns preferences as an array\n*/\n\n	function rah_sitemap_prefs() {\n		\n		$out = array();\n		\n		$rs = \n			safe_rows(\n				\'name,value\',\n				\'rah_sitemap_prefs\',\n				\'1=1\'\n			);\n		\n		foreach($rs as $row)\n			$out[$row[\'name\']] = $row[\'value\'];\n			\n		return $out;\n		\n	}\n\n/**\n	Build the custom URLs\n*/\n\n	function rah_sitemap_uri($uri=\'\',$escape=0) {\n		\n		if(\n			substr($uri,0,7) != \'http://\' && \n			substr($uri,0,8) != \'https://\' &&\n			substr($uri,0,6) != \'ftp://\' &&\n			substr($uri,0,7) != \'ftps://\' && \n			substr($uri,0,4) != \'www.\'\n		)\n			$uri =  hu . $uri;\n		\n		else if(substr($uri,0,4) == \'www.\')\n			$uri =  \'http://\' . $uri;\n		\n		if($escape == 1)\n			$uri = htmlspecialchars($uri);\n		\n		return $uri;\n	}\n\n/**\n	Installer. Creates tables and adds the default rows\n*/\n\n	function rah_sitemap_install() {\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_sitemap\').\" (\n				`url` VARCHAR(255) NOT NULL,\n				`posted` DATETIME NOT NULL,\n				`include` INT(1) NOT NULL,\n				PRIMARY KEY(`url`)\n			)\"\n		);\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_sitemap_prefs\').\" (\n				`name` VARCHAR(255) NOT NULL,\n				`value` LONGTEXT NOT NULL,\n				PRIMARY KEY(`name`)\n			)\"\n		);\n		\n		foreach(rah_sitemap_pref_fields() as $key => $val) {\n			if(\n				safe_count(\n					\'rah_sitemap_prefs\',\n					\"name=\'\".doSlash($key).\"\'\"\n				) == 0\n			) {\n				safe_insert(\n					\'rah_sitemap_prefs\',\n					\"name=\'\".doSlash($key).\"\', value=\'\".doSlash($val).\"\'\"\n				);\n			}\n		}\n	}\n\n/**\n	Builds the list of filters\n*/\n\n	function rah_sitemap_listing($label=\'\',$field=\'\',$table=\'\',$where=\'\') {\n		\n		$pref = \n			rah_sitemap_prefs();\n		\n		$exclude = explode(\',\',$pref[$field]);\n		\n		$rs = \n			safe_rows(\n				\'name,title\'.(($table == \'txp_category\') ? \',type\' : \'\'),\n				$table,\n				\"$where order by \".(($table == \'txp_category\') ? \'type asc, \' : \'\').\" name asc\"\n			);\n		\n		$out[] = \n			\'					<div class=\"rah_sitemap_column\">\'.n.\n			\'						<strong>\'.$label.\'</strong><br />\'.n;\n		\n		if($rs){\n			foreach($rs as $a) {\n				\n				$name = $a[\'name\'];\n				$title = $a[\'title\'];\n				\n				if($field == \'categories\') {\n					$name = $a[\'type\'].\'_||_\'.$a[\'name\'];\n					$title = ucfirst($a[\'type\']).$a[\'title\'];\n				}\n\n				$out[] = \n					\'						<label><input type=\"checkbox\" name=\"\'.$field.\'[]\" value=\"\'.$name.\'\"\'.((in_array($name,$exclude)) ? \' checked=\"checked\"\' : \'\').\' /> \'.$title .\'</label><br />\'.n;\n			}\n		} else $out[] = \'						Nothing found.\'.n;\n		$out[] = \n			\'					</div>\'.n;\n		return implode(\'\',$out);\n	}\n\n/**\n	Saves preferences\n*/\n\n	function rah_sitemap_save() {\n		foreach(rah_sitemap_pref_fields() as $key => $val) {\n			$ps = ps($key);\n			\n			if(is_array($ps))\n				$ps = implode(\',\',$ps);\n			\n			safe_update(\n				\'rah_sitemap_prefs\',\n				\"value=\'\".doSlash(trim($ps)).\"\'\",\n				\"name=\'\".$key.\"\'\"\n			);\n		}\n		rah_sitemap_list(\'Sitemap preferences saved\');\n	}\n\n/**\n	Deletes custom URIs\n*/\n\n	function rah_sitemap_delete() {\n		\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected)) {\n			rah_sitemap_custom_list(\'Nothing selected\');\n			return;\n		}\n		\n		foreach($selected as $name) \n			safe_delete(\n				\'rah_sitemap\',\n				\"url=\'\".doSlash($name).\"\'\"\n			);\n		\n		rah_sitemap_custom_list(\'Selection deleted\');\n	}', '	##################\n	#\n	#	rah_sitemap-plugin for Textpattern\n	#	version 1.2\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		add_privs(\'rah_sitemap\',\'1,2\');\n		register_tab(\'extensions\',\'rah_sitemap\',\'Sitemap\');\n		register_callback(\'rah_sitemap_page\',\'rah_sitemap\');\n		register_callback(\'rah_sitemap_head\',\'admin_side\',\'head_end\');\n	} else \n		register_callback(\'rah_sitemap\',\'textpattern\');\n\n/**\n	Checks if the sitemap should be returned\n*/\n\n	function rah_sitemap_delivery() {\n		global $pretext;\n		\n		$uri = $pretext[\'request_uri\'];\n		$uri = explode(\'/\',$uri);\n		$uri = array_reverse($uri);\n		\n		if(in_array($uri[0],array(\'sitemap.xml.gz\',\'sitemap.xml\')))\n			return true;\n		\n		if(gps(\'rah_sitemap\') == \'sitemap\')	\n			return true;\n		\n		return false;\n	}\n\n/**\n	The sitemap\n*/\n\n	function rah_sitemap() {\n		if(rah_sitemap_delivery() == false)\n			return;\n		\n		global $s, $thissection, $thiscategory, $c, $pretext, $thispage, $thisarticle;\n		\n		@$pref = rah_sitemap_prefs();\n		\n		if(!isset($pref[\'zlib_output\'])) {\n			rah_sitemap_install();\n			$pref = rah_sitemap_prefs();\n		}\n\n		header(\'Content-type: application/xml\');\n\n		if($pref[\'compress\'] == 1 && function_exists(\'gzencode\'))\n			header(\'Content-Encoding: gzip\');\n		\n		if($pref[\'zlib_output\'] == 1)\n			ini_set(\'zlib.output_compression\',\'Off\');\n	\n		$timestampformat = (!empty($pref[\'timestampformat\'])) ? $pref[\'timestampformat\'] : \'c\';\n		\n		$out[] = \n			\'<?xml version=\"1.0\" encoding=\"utf-8\"?>\'.\n			\'<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\'.\n			\'<url><loc>\'.hu.\'</loc></url>\';\n		\n		if($pref[\'nosections\'] != 1) {\n			\n			$rs = \n				safe_rows(\n					\'name\',\n					\'txp_section\',\n					rah_sitemap_in(\'name\',$pref[\'sections\'],\'default\') .\n					\" order by name asc\"\n				);\n			\n			$thisarticle[\'section\'] = \'\';\n			\n			foreach($rs as $a) {\n				\n				$s = $thispage[\'s\'] = $thissection[\'section\'] =  $pretext[\'s\'] = $a[\'name\'];\n				\n				if($pref[\'permlink_section\'])\n					$out[] = \'<url><loc>\'.parse($pref[\'permlink_section\']).\'</loc></url>\';\n				else \n					$out[] = \'<url><loc>\'.pagelinkurl(array(\'s\' => $a[\'name\'])).\'</loc></url>\';\n			}\n			\n			$s =  $thissection[\'section\'] = $thispage[\'s\'] = $pretext[\'s\'] = \'\';\n			\n		}\n		\n		$notypes = \'\';\n		\n		if($pref[\'nocategories\'] == 1)\n			$notypes[] = \'article\';\n		if($pref[\'nofile\'] == 1)\n			$notypes[] = \'file\';\n		if($pref[\'noimage\'] == 1)\n			$notypes[] = \'image\';\n		if($pref[\'nolink\'] == 1)\n			$notypes[] = \'link\';\n		\n		$not = explode(\',\',$pref[\'categories\']);\n		\n		$rs = \n			safe_rows(\n				\'name,type,id\',\n				\'txp_category\',\n				\"name != \'root\' \" . rah_sitemap_in(\'and type\',$notypes) . \" order by name asc\"\n			);\n		\n		foreach($rs as $a) {\n\n			if(in_array($a[\'type\'].\'_||_\'.$a[\'name\'],$not))\n				continue;\n\n			$c = $thiscategory[\'c\'] = $pretext[\'c\'] = $thispage[\'c\'] = $a[\'name\'];\n\n			if($pref[\'permlink_category\'])\n				$out[] = \n					\n					\'<url><loc>\'.\n					\n					htmlspecialchars(str_replace(\n						array(\n							\'[type]\',\n							\'[id]\'\n						),\n						array(\n							$a[\'type\'],\n							$a[\'id\']\n						),\n						parse($pref[\'permlink_category\'])\n					)).\n					\n					\'</loc></url>\';\n					\n			else \n				$out[] = \n					\'<url><loc>\'.pagelinkurl(array(\'c\' => $a[\'name\'])).\'</loc></url>\';\n		}\n		\n		$c = $thiscategory[\'c\'] = $pretext[\'c\'] = $thispage[\'c\'] =  \'\';\n		\n		if($pref[\'noarticles\'] != 1) {\n			\n			$sql[] = rah_sitemap_in(\' and Category1\',$pref[\'articlecategories\']);\n			$sql[] = rah_sitemap_in(\' and Category2\',$pref[\'articlecategories\']);\n			$sql[] = rah_sitemap_in(\' and Status\',$pref[\'articlestatus\'],\'1,2,3\');\n			$sql[] = rah_sitemap_in(\' and Section\',$pref[\'articlesections\']);\n			\n			if($pref[\'articlefuture\'])\n				$sql[] = \' and Posted <= now()\';\n			if($pref[\'articlepast\'])\n				$sql[] = \' and Posted > now()\';\n			if($pref[\'articleexpired\'])\n				$sql[] = \" and (Expires = \'0000-00-00 00:00:00\' or Expires >= now())\";\n			\n			\n			if($pref[\'permlink_article\'])\n				$columns = \n				 	\'*,  unix_timestamp(Posted) as posted, \'.\n				 	\'unix_timestamp(Expires) as uExpires, \'.\n				 	\'unix_timestamp(LastMod) as uLastMod\';\n			else\n				$columns = \n					\'ID as thisid, Section as section, \'.\n					\'Title as title, url_title, unix_timestamp(Posted)\'.\n					\' as posted, unix_timestamp(LastMod) as uLastMod\';\n			\n			$rs = \n				safe_rows(\n					$columns,\n					\'textpattern\',\n					\'1=1\' . implode(\'\',$sql). \' order by Posted desc\'\n				);\n			\n			foreach($rs as $a) {\n				extract($a);\n				\n				if($pref[\'permlink_article\']) {\n					$thisarticle = \n						array(\n							\'thisid\' => $ID,\n							\'posted\' => $posted,\n							\'modified\' => $uLastMod,\n							\'annotate\' => $Annotate,\n							\'comments_invite\' => $AnnotateInvite,\n							\'authorid\' => $AuthorID,\n							\'title\' => $Title,\n							\'url_title\' => $url_title,\n							\'category1\' => $Category1,\n							\'category2\' => $Category2,\n							\'section\' => $Section,\n							\'keywords\' => $Keywords,\n							\'article_image\' => $Image,\n							\'comments_count\' => $comments_count,\n							\'body\' => $Body_html,\n							\'excerpt\' => $Excerpt_html,\n							\'override_form\' => $override_form,\n							\'status\'=> $Status\n						)\n					;\n					\n					@$url = htmlspecialchars(parse($pref[\'permlink_article\']));\n				}\n				\n				else {\n					@$url = permlinkurl($a);\n					\n					/*\n						Fix for gbp_permanent_links\n					*/\n					\n					if(strpos($url,\'/\') === 0)\n						$url = hu . ltrim($url,\'/\');\n				}\n				\n				@$out[] = \n					\'<url><loc>\'.$url.\'</loc><lastmod>\'.\n					($uLastMod < $posted ? \n						date($timestampformat,$posted) : \n						date($timestampformat,$uLastMod)\n					).\n					\'</lastmod></url>\'\n				;\n				\n			}\n			$thisarticle = \'\';\n		}\n		\n		$rs = \n			safe_rows(\n				\'*, unix_timestamp(posted) as uposted\',\n				\'rah_sitemap\',\n				\'1=1 order by posted desc\'\n			);\n		\n		foreach($rs as $a) {\n			$url = parse($a[\'url\']);\n			$out[] = \'<url><loc>\'.rah_sitemap_uri($url,1).\'</loc>\';\n			if($a[\'include\'] == 1)\n				@$out[] = \'<lastmod>\'.date($timestampformat,$a[\'uposted\']).\'</lastmod>\';\n			$out[] = \'</url>\';\n		}\n		\n		$out[] = \'</urlset>\';\n		$out = implode(\'\',$out);\n		\n		echo (@$pref[\'compress\'] == 1 && function_exists(\'gzencode\')) ? gzencode($out,$pref[\'compression_level\']) : $out;\n		exit();\n	}\n\n/**\n	Delivers the panels\n*/\n\n	function rah_sitemap_page() {\n		require_privs(\'rah_sitemap\');\n		global $step;\n		if(in_array($step,array(\n			\'rah_sitemap_save\',\n			\'rah_sitemap_custom_list\',\n			\'rah_sitemap_custom_form\',\n			\'rah_sitemap_custom_save\',\n			\'rah_sitemap_delete\'\n		))) $step();\n		else rah_sitemap_list();\n	}\n\n/**\n	Preferences panel\n*/\n\n	function rah_sitemap_list($message=\'\') {\n		@$pref = rah_sitemap_prefs();\n\n		if(!isset($pref[\'zlib_output\'])) {\n			rah_sitemap_install();\n			$pref = rah_sitemap_prefs();\n		}\n\n		rah_sitemap_header(\n			\n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\n			\'		<p><strong>General preferences</strong></p>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Exclude sections and categories from the sitemap</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			rah_sitemap_listing(\'Exclude sections\',\'sections\',\'txp_section\',\"name != \'default\'\").\n			rah_sitemap_listing(\'Exclude categories\',\'categories\',\'txp_category\',\"name != \'root\' and title != \'root\'\").\n			\n			\'			<div class=\"rah_sitemap_column\">\'.n.\n			\n			\'				<strong>Advanced settings</strong><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nosections\" value=\"1\"\'.(($pref[\'nosections\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all section URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nofile\" value=\"1\"\'.(($pref[\'nofile\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all file-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"noimage\" value=\"1\"\'.(($pref[\'noimage\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all image-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nolink\" value=\"1\"\'.(($pref[\'nolink\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all link-type category URLs</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"nocategories\" value=\"1\"\'.(($pref[\'nocategories\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude all article-type category URLs</label><br />\'.n.\n			\n			\n			\'			</div>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Filter articles from the sitemap</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\n			rah_sitemap_listing(\'Exclude article sections\',\'articlesections\',\'txp_section\',\"name != \'default\'\").\n			rah_sitemap_listing(\'Exclude article categories\',\'articlecategories\',\'txp_category\',\"name != \'root\' and title != \'root\' and type = \'article\'\").\n			\n			\'			<div class=\"rah_sitemap_column\">\'.n.\n			\'				<strong>Advanced settings</strong><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"noarticles\" value=\"1\"\'.(($pref[\'noarticles\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Don\\\'t include articles in sitemap</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlestatus\" value=\"5\"\'.(($pref[\'articlestatus\'] == 5) ? \' checked=\"checked\"\' : \'\').\' /> Exclude sticky articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlefuture\" value=\"1\"\'.(($pref[\'articlefuture\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude future articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articlepast\" value=\"1\"\'.(($pref[\'articlepast\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude past articles</label><br />\'.n.\n			\'				<label><input type=\"checkbox\" name=\"articleexpired\" value=\"1\"\'.(($pref[\'articleexpired\'] == 1) ? \' checked=\"checked\"\' : \'\').\' /> Exclude expired articles</label>\'.n.\n			\'			</div>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p><strong>Advanced settings</strong></p>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Compression methods</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Compression.</strong> With these settings you can control compression and even turn it off if it causes problems on your server. It is recommeded to leave compression on if possible. Compression level 0 is the mildest and 9 is the maximum compression.</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_compress\">Use Gzip compression:</label><br />\'.n.\n			\n			\'				<select name=\"compress\" id=\"rah_sitemap_compress\">\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'compress\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'compress\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_compression_level\">Compression level:</label><br />\'.n.\n			\n			\'				<select name=\"compression_level\" id=\"rah_sitemap_compression_level\">\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'compression_level\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>0</option>\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'compression_level\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>1</option>\'.n.\n			\'					<option value=\"2\"\'.(($pref[\'compression_level\'] == 2) ? \' selected=\"selected\"\' : \'\').\'>2</option>\'.n.\n			\'					<option value=\"3\"\'.(($pref[\'compression_level\'] == 3) ? \' selected=\"selected\"\' : \'\').\'>3</option>\'.n.\n			\'					<option value=\"4\"\'.(($pref[\'compression_level\'] == 4) ? \' selected=\"selected\"\' : \'\').\'>4</option>\'.n.\n			\'					<option value=\"5\"\'.(($pref[\'compression_level\'] == 5) ? \' selected=\"selected\"\' : \'\').\'>5</option>\'.n.\n			\'					<option value=\"6\"\'.(($pref[\'compression_level\'] == 6) ? \' selected=\"selected\"\' : \'\').\'>6</option>\'.n.\n			\'					<option value=\"7\"\'.(($pref[\'compression_level\'] == 7) ? \' selected=\"selected\"\' : \'\').\'>7</option>\'.n.\n			\'					<option value=\"8\"\'.(($pref[\'compression_level\'] == 8) ? \' selected=\"selected\"\' : \'\').\'>8</option>\'.n.\n			\'					<option value=\"9\"\'.(($pref[\'compression_level\'] == 9) ? \' selected=\"selected\"\' : \'\').\'>9</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_zlib_output\">Set zlib output compression off. If set to no, configuration is not modified:</label><br />\'.n.\n			\n			\'				<select name=\"zlib_output\" id=\"rah_sitemap_zlib_output\">\'.n.\n			\'					<option value=\"1\"\'.(($pref[\'zlib_output\'] == 1) ? \' selected=\"selected\"\' : \'\').\'>Yes</option>\'.n.\n			\'					<option value=\"0\"\'.(($pref[\'zlib_output\'] == 0) ? \' selected=\"selected\"\' : \'\').\'>No</option>\'.n.\n			\'				</select>\'.n.\n			\n			\'			</p>\'.n.\n			\'		</div>\'.n.\n			\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Override timestamp format</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Timestamps.</strong> Customize the date format used in last modified timestamps. Use <a href=\"http://php.net/manual/en/function.date.php\">date()</a> string values. If unset (left empty) default ISO 8601 date (<code>c</code>) is used. Use this setting if you want to hard-code/override timestamps, timezones or if your server doesn\\\'t support <code>c</code> format.</p>\'.n.\n			\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_timestampformat\">Format:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" style=\"width: 940px;\" name=\"timestampformat\" id=\"rah_sitemap_timestampformat\" value=\"\'.htmlspecialchars($pref[\'timestampformat\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<p title=\"Click to expand\" class=\"rah_sitemap_heading\">\'.n.\n			\'			+ <span>Override permlink formats</span>\'.n.\n			\'		</p>\'.n.\n			\n			\'		<div class=\"rah_sitemap_more\">\'.n.\n			\n			\'			<p class=\"rah_sitemap_paragraph\"><strong>Permlinks.</strong> With these settings you can make the Sitemap\\\'s URLs to match your own URL rewriting rules, or permlinks made by a <em>custom permlink rule</em> plugin. You can leave these fields empty, if using TXP\\\'s inbuild permlink rules. Note that these setting do not rewrite TXP\\\'s permlinks for you, use only for matching not rewriting!</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_category\">Category URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_category\" id=\"rah_sitemap_permlink_category\" value=\"\'.htmlspecialchars($pref[\'permlink_category\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_section\">Section URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_section\" id=\"rah_sitemap_permlink_section\" value=\"\'.htmlspecialchars($pref[\'permlink_section\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\'			<p>\'.n.\n			\'				<label for=\"rah_sitemap_permlink_article\">Article URLs:</label><br />\'.n.\n			\'				<input type=\"text\" class=\"edit\" name=\"permlink_article\" id=\"rah_sitemap_permlink_article\" value=\"\'.htmlspecialchars($pref[\'permlink_article\']).\'\" />\'.n.\n			\'			</p>\'.n.\n			\n			\'		</div>\'.n.\n			\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'		<input type=\"hidden\" name=\"step\" value=\"rah_sitemap_save\" />\'.n.\n			\n			\'		<p><input type=\"submit\" value=\"\'.gTxt(\'save\').\'\" class=\"publish\" /></p>\'.n.\n			\n			\'	</form>\',\n			\n			\'rah_sitemap\',\n			\'Manage your sitemap\',\n			\'Sitemap.org Sitemaps\',\n			$message\n			\n		);\n	}\n\n/**\n	Panel, lists custom URLs\n*/\n\n	function rah_sitemap_custom_list($message=\'\') {\n\n		$out[] = \n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\'		<table id=\"list\" class=\"list rah_sitemap_table\"  border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\'.n.\n			\'			<tr>\'.n.\n			\'				<th>URL</th>\'.n.\n			\'				<th>LastMod</th>\'.n.\n			\'				<th>Include LastMod</th>\'.n.\n			\'				<th>View</th>\'.n.\n			\'				<th>&#160;</th>\'.n.\n			\'			</tr>\'.n;\n		\n		$rs =\n			safe_rows(\n				\'url,posted,include\',\n				\'rah_sitemap\',\n				\"1=1 order by posted desc\"\n			);\n		\n		if($rs) {\n			foreach($rs as $a) {\n				$uri = rah_sitemap_uri($a[\'url\'],1);\n				$out[] = \n					\'			<tr>\'.n.\n					\'				<td><a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_form&amp;edit=\'.urlencode($a[\'url\']).\'\">\'.$uri.\'</a></td>\'.n.\n					\'				<td>\'.$a[\'posted\'].\'</td>\'.n.\n					\'				<td>\'.(($a[\'include\'] == 1) ? \'Yes\' : \'No\').\'</td>\'.n.\n					\'				<td><a target=\"_blank\" href=\"\'.htmlspecialchars($uri).\'\">\'.gTxt(\'view\').\'</a></td>\'.n.\n					\'				<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.htmlspecialchars($a[\'url\']).\'\" /></td>\'.n.\n					\'			</tr>\'.n;\n			}\n		} else \n			$out[] =  \'			<tr><td colspan=\"5\">No custom URLs found.</td></tr>\'.n;\n		\n		$out[] =  \n			\'		</table>\'.n.\n			\'		<p id=\"rah_sitemap_step\">\'.n.\n			\'			<select name=\"step\">\'.n.\n			\'				<option value=\"\">With selected...</option>\'.n.\n			\'				<option value=\"rah_sitemap_delete\">Delete</option>\'.n.\n			\'			</select>\'.n.\n			\'			<input type=\"submit\" class=\"smallerbox\" value=\"Go\" />\'.n.\n			\'		</p>\'.n.\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'	</form>\'.n;\n		\n		rah_sitemap_header(\n			$out,\n			\'rah_sitemap\',\n			\'List of custom URLs\',\n			\'Sitemap\',\n			$message\n		);\n	}\n\n/**\n	Panel to add custom URLs\n*/\n\n	function rah_sitemap_custom_form($message=\'\') {\n		\n		$edit = gps(\'edit\');\n		\n		if($edit) {\n			\n			$rs = \n				safe_row(\n					\'*\',\n					\'rah_sitemap\',\n					\"url=\'\".doSlash($edit).\"\'\"\n				);\n			\n			if(!$rs) {\n				rah_sitemap_custom_list(\'Selection not found.\');\n				return;\n			}\n			\n			extract($rs);\n		\n		}\n		\n		if(!isset($rs))\n			extract(gpsa(array(\n				\'url\',\'include\',\'posted\'\n			)));\n		\n		rah_sitemap_header(\n			\'	<form method=\"post\" action=\"index.php\">\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_url\">URL:</label><br />\'.n.\n			\'			<input id=\"rah_sitemap_url\" class=\"edit\" type=\"text\" name=\"url\" value=\"\'.htmlspecialchars($url).\'\" />\'.n.\n			\'		</p>\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_posted\">LastMod (YYYY-mm-dd HH:MM:SS). Leave empty to use current time:</label><br />\'.n.\n			\'			<input id=\"rah_sitemap_posted\" class=\"edit\" type=\"text\" name=\"posted\" value=\"\'.htmlspecialchars($posted).\'\" />\'.n.\n			\'		</p>\'.n.\n			\'		<p>\'.n.\n			\'			<label for=\"rah_sitemap_lastmod\">Include LastMod:</label><br />\'.n.\n			\'			<select id=\"rah_sitemap_lastmod\" name=\"include\">\'.n.\n			\'					<option value=\"0\"\'.(($include == 0) ? \' selected=\"selected\"\' : \'\').\'>\'.gTxt(\'no\').\' (Recommended)</option>\'.n.\n			\'					<option value=\"1\"\'.(($include == 1) ? \' selected=\"selected\"\' : \'\').\'>\'.gTxt(\'yes\').\'</option>\'.n.\n			\'			</select>\'.n.\n			\'		</p>\'.n.\n			\'		<p><input type=\"submit\" value=\"\'.gTxt(\'save\').\'\" class=\"publish\" /></p>\'.n.\n			\n			(($edit) ? \n				\'		<input type=\"hidden\" name=\"edit\" value=\"\'.htmlspecialchars($edit).\'\" />\' : \'\'\n			).\n			\n			\'		<input type=\"hidden\" name=\"event\" value=\"rah_sitemap\" />\'.n.\n			\'		<input type=\"hidden\" name=\"step\" value=\"rah_sitemap_custom_save\" />\'.n.\n			\n			\'	</form>\'\n			\n			,\'rah_sitemap\',\n			\'Add a new custom URL\',\n			\'Sitemap\',\n			$message\n			\n		);\n		\n	}\n\n/**\n	Saves custom URL\n*/\n\n	function rah_sitemap_custom_save() {\n		extract(doSlash(gpsa(array(\n			\'url\',\n			\'posted\',\n			\'include\',\n			\'reset\',\n			\'edit\'\n		))));\n		\n		if(empty($posted) or $reset == 1)\n			$posted = \'posted=now()\';\n		else \n			$posted = \"posted=\'$posted\'\";\n		\n		if(!$edit && safe_count(\'rah_sitemap\',\"url=\'$url\'\") != 0) {\n			rah_sitemap_custom_form(\'URL already exists.\');\n			return;\n		}\n		\n		if($edit && safe_count(\'rah_sitemap\',\"url=\'$edit\'\") == 1) {\n			\n			if($url != $edit && safe_count(\'rah_sitemap\',\"url=\'$url\'\") == 1) {\n				rah_sitemap_custom_form(\'New URL already exists.\');\n				return;\n			}\n			\n			safe_update(\n				\'rah_sitemap\',\n				\"$posted,\n				include=\'$include\',\n				url=\'$url\'\",\n				\"url=\'$edit\'\"\n			);\n			\n			rah_sitemap_custom_list(\'URL updated.\');\n			return;\n			\n		}\n		\n		safe_insert(\n			\'rah_sitemap\',\n			\"url=\'$url\',\n			$posted,\n			include=\'$include\'\"\n		);\n		\n		rah_sitemap_custom_list(\'URL added.\');\n		return;\n	}\n\n/**\n	Outputs the panel\'s CSS and JavaScript to page\'s <head> segment\n*/\n\n	function rah_sitemap_head() {\n		\n		global $event;\n		\n		if($event != \'rah_sitemap\')\n			return;\n		\n		echo <<<EOF\n			<style type=\"text/css\">\n				#rah_sitemap_container {\n					width: 950px;\n					margin: 0 auto;\n				}\n				#rah_sitemap_container #rah_sitemap_step {\n					text-align: right;\n					padding-top: 10px;\n				}\n				#rah_sitemap_container .rah_sitemap_table {\n					width: 100%;\n				}\n				#rah_sitemap_container .rah_sitemap_column {\n					width: 315px;\n					float: left;\n					display: inline;\n					padding: 0 0 10px 0;\n				}\n				#rah_sitemap_container .rah_sitemap_heading {\n					font-weight: 900;\n					padding: 5px 0;\n					margin: 0 0 10px 0;\n					border-top: 1px solid #ccc;\n					border-bottom: 1px solid #ccc;\n				}\n				#rah_sitemap_container .rah_sitemap_heading span {\n					cursor: pointer;\n					color: #963;\n				}\n				#rah_sitemap_container .rah_sitemap_heading span:hover {\n					text-decoration: underline;\n				}\n				#rah_sitemap_container .rah_sitemap_more {\n					overflow: hidden;\n				}\n				#rah_sitemap_container input.edit {\n					width: 940px;\n				}\n				#rah_sitemap_zlib_output,\n				#rah_sitemap_compression_level,\n				#rah_sitemap_compress,\n				#rah_sitemap_lastmod {\n					width: 450px;\n				}\n				.rah_sitemap_paragraph {\n					margin: 0 0 10px 0;\n					padding: 0;\n				}\n			</style>\n			<script type=\"text/javascript\">\n				$(document).ready(function(){\n					$(\'.rah_sitemap_more\').hide();\n					$(\'.rah_sitemap_heading\').click(function(){\n						$(this).next(\'div.rah_sitemap_more\').slideToggle();\n					});\n				});\n			</script>\nEOF;\n	}\n\n/**\n	The panel\'s navigation bar\n*/\n\n	function rah_sitemap_header($content=\'\',$title=\'rah_sitemap\',$msg=\'Manage your sitemap\',$pagetop=\'\',$message=\'\') {\n		\n		pagetop($pagetop,$message);\n		\n		if(is_array($content))\n			$content = implode(\'\',$content);\n		\n		echo \n			n.\n			\'	<div id=\"rah_sitemap_container\">\'.n.\n			\'		<h1><strong>\'.$title.\'</strong> | \'.$msg.\'</h1>\'.n.\n			\'		<p id=\"rah_sitemap_nav\">\'.\n					\' &#187; <a href=\"?event=rah_sitemap\">Preferences</a>\'.\n					\' &#187; <a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_form\">Add custom URL</a>\'.\n					\' &#187; <a href=\"?event=rah_sitemap&amp;step=rah_sitemap_custom_list\">List of custom URLs</a>\'.\n					\' &#187; <a target=\"_blank\" href=\"\'.hu.\'?rah_sitemap=sitemap\">View the sitemap</a>\'.\n					\'</p>\'.n.\n			$content.n.	\n			\'	</div>\'.n;\n	}\n\n/**\n	Builds the required in array for SQL statements\n*/\n\n	function rah_sitemap_in($field=\'\',$array=\'\',$default=\'\',$sql=\' not in\') {\n		\n		if(empty($array) && empty($default))\n			return;\n		\n		if(!is_array($array))\n			$array = explode(\',\',$array);\n		\n		if(!empty($default)) {\n			$default = explode(\',\',$default);\n			$array = \n				array_merge(\n					$array,\n					$default\n				);\n		}\n		\n		foreach($array as $value) \n			$out[] = \"\'\".doSlash(trim($value)).\"\'\";\n		\n		if(!isset($out))\n			return;\n	\n		return \n			$field . $sql . \'(\' . implode(\',\',$out) . \')\';\n		\n	}\n\n/**\n	Default settings\n*/\n\n	function rah_sitemap_pref_fields() {\n		return\n			array(\n				\'noarticles\' => \'\',\n				\'nosections\' => \'\',\n				\'nocategories\' => \'\',\n				\'articlecategories\' => \'\',\n				\'articlesections\' => \'\',\n				\'sections\' => \'\',\n				\'categories\' => \'\',\n				\'nofile\' => 1,\n				\'noimage\' => 1,\n				\'nolink\' => 1,\n				\'articlestatus\' => \'\',\n				\'articlefuture\' => \'\',\n				\'articlepast\' => \'\',\n				\'articleexpired\' => \'\',\n				\'permlink_category\' => \'\',\n				\'permlink_section\' => \'\',\n				\'permlink_article\' => \'\',\n				\'timestampformat\' => \'c\',\n				\'compress\' => 1,\n				\'compression_level\' => 9,\n				\'zlib_output\' => 0\n			);\n	}\n\n/**\n	Returns preferences as an array\n*/\n\n	function rah_sitemap_prefs() {\n		\n		$out = array();\n		\n		$rs = \n			safe_rows(\n				\'name,value\',\n				\'rah_sitemap_prefs\',\n				\'1=1\'\n			);\n		\n		foreach($rs as $row)\n			$out[$row[\'name\']] = $row[\'value\'];\n			\n		return $out;\n		\n	}\n\n/**\n	Build the custom URLs\n*/\n\n	function rah_sitemap_uri($uri=\'\',$escape=0) {\n		\n		if(\n			substr($uri,0,7) != \'http://\' && \n			substr($uri,0,8) != \'https://\' &&\n			substr($uri,0,6) != \'ftp://\' &&\n			substr($uri,0,7) != \'ftps://\' && \n			substr($uri,0,4) != \'www.\'\n		)\n			$uri =  hu . $uri;\n		\n		else if(substr($uri,0,4) == \'www.\')\n			$uri =  \'http://\' . $uri;\n		\n		if($escape == 1)\n			$uri = htmlspecialchars($uri);\n		\n		return $uri;\n	}\n\n/**\n	Installer. Creates tables and adds the default rows\n*/\n\n	function rah_sitemap_install() {\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_sitemap\').\" (\n				`url` VARCHAR(255) NOT NULL,\n				`posted` DATETIME NOT NULL,\n				`include` INT(1) NOT NULL,\n				PRIMARY KEY(`url`)\n			)\"\n		);\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_sitemap_prefs\').\" (\n				`name` VARCHAR(255) NOT NULL,\n				`value` LONGTEXT NOT NULL,\n				PRIMARY KEY(`name`)\n			)\"\n		);\n		\n		foreach(rah_sitemap_pref_fields() as $key => $val) {\n			if(\n				safe_count(\n					\'rah_sitemap_prefs\',\n					\"name=\'\".doSlash($key).\"\'\"\n				) == 0\n			) {\n				safe_insert(\n					\'rah_sitemap_prefs\',\n					\"name=\'\".doSlash($key).\"\', value=\'\".doSlash($val).\"\'\"\n				);\n			}\n		}\n	}\n\n/**\n	Builds the list of filters\n*/\n\n	function rah_sitemap_listing($label=\'\',$field=\'\',$table=\'\',$where=\'\') {\n		\n		$pref = \n			rah_sitemap_prefs();\n		\n		$exclude = explode(\',\',$pref[$field]);\n		\n		$rs = \n			safe_rows(\n				\'name,title\'.(($table == \'txp_category\') ? \',type\' : \'\'),\n				$table,\n				\"$where order by \".(($table == \'txp_category\') ? \'type asc, \' : \'\').\" name asc\"\n			);\n		\n		$out[] = \n			\'					<div class=\"rah_sitemap_column\">\'.n.\n			\'						<strong>\'.$label.\'</strong><br />\'.n;\n		\n		if($rs){\n			foreach($rs as $a) {\n				\n				$name = $a[\'name\'];\n				$title = $a[\'title\'];\n				\n				if($field == \'categories\') {\n					$name = $a[\'type\'].\'_||_\'.$a[\'name\'];\n					$title = ucfirst($a[\'type\']).$a[\'title\'];\n				}\n\n				$out[] = \n					\'						<label><input type=\"checkbox\" name=\"\'.$field.\'[]\" value=\"\'.$name.\'\"\'.((in_array($name,$exclude)) ? \' checked=\"checked\"\' : \'\').\' /> \'.$title .\'</label><br />\'.n;\n			}\n		} else $out[] = \'						Nothing found.\'.n;\n		$out[] = \n			\'					</div>\'.n;\n		return implode(\'\',$out);\n	}\n\n/**\n	Saves preferences\n*/\n\n	function rah_sitemap_save() {\n		foreach(rah_sitemap_pref_fields() as $key => $val) {\n			$ps = ps($key);\n			\n			if(is_array($ps))\n				$ps = implode(\',\',$ps);\n			\n			safe_update(\n				\'rah_sitemap_prefs\',\n				\"value=\'\".doSlash(trim($ps)).\"\'\",\n				\"name=\'\".$key.\"\'\"\n			);\n		}\n		rah_sitemap_list(\'Sitemap preferences saved\');\n	}\n\n/**\n	Deletes custom URIs\n*/\n\n	function rah_sitemap_delete() {\n		\n		$selected = ps(\'selected\');\n		\n		if(!is_array($selected)) {\n			rah_sitemap_custom_list(\'Nothing selected\');\n			return;\n		}\n		\n		foreach($selected as $name) \n			safe_delete(\n				\'rah_sitemap\',\n				\"url=\'\".doSlash($name).\"\'\"\n			);\n		\n		rah_sitemap_custom_list(\'Selection deleted\');\n	}', 'db185e7dfa6b8ade051bb7527793a5f5', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_status_dropdown', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.6.2', 'Changes Write panel\'s status radio buttons to a select field', '<h1>rah_status_dropdown</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_status_dropdown\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_status_dropdown\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=28281\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_status_dropdown\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_status_dropdown is a visual admin-side extension for <a href=\"http://www.textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a>. The plugin changes <em>Write</em> panel&#8217;s <em>status</em> radio buttons to a select field. Changing the list of statuses to a dropdown will save some space, and hides the extra article statuses that might have no real use to a content author.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_status_dropdown&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.5.0 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_status_dropdown&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<p>The plugin is now in use and the article status show up as a select field.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.6.2 &#8211; 2012/10/31</h3>\n\n<ul>\n	<li>Reverts 0.6.1.</li>\n</ul>\n\n<h3>Version 0.6.1 &#8211; 2012/10/31</h3>\n\n<ul>\n	<li>Casts statuses as strings.</li>\n</ul>\n\n<h3>Version 0.6 &#8211; 2012/08/27</h3>\n\n<ul>\n	<li>Changed plugin type to 4, which allows the plugin to load correctly on Textpattern v4.5.0.</li>\n	<li>Now requires Textpattern 4.5.0 or newer.</li>\n	<li>Is now fully compatible with Textpattern v4.5.0 release version.</li>\n</ul>\n\n<h3>Version 0.5 &#8211; 2012/07/30</h3>\n\n<ul>\n	<li>Added: The select field is now wrapped in a paragraph.</li>\n	<li>Changed: Client-side dependency. Uses the pluggable_ui and server-side <span class=\"caps\">PHP</span> instead of JavaScript.</li>\n	<li>Is now compatible with Textpattern v4.5.0 (r3650 and newer).</li>\n</ul>\n\n<h3>Version 0.4 &#8211; 2011/08/06</h3>\n\n<ul>\n	<li>Now the plugin doesn&#8217;t use <span class=\"caps\">PHP</span> tie-in, and the all the action is powered by JavaScript.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2011/06/29</h3>\n\n<ul>\n	<li>Fixed: Don&#8217;t spit out JavaScript when the browser&#8217;s JavaScript support is disabled.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2010/08/08</h3>\n\n<ul>\n	<li>Moved the JavaScript to <code>&lt;head&gt;</code>.</li>\n	<li>Now gets the active status with plain JavaScript, not with <span class=\"caps\">PHP</span> from the saved article.</li>\n	<li>Because of the above change, the code is now shorter and less intensive.</li>\n	<li>Now requires Textpattern version 4.0.7 or newer.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2008/08/31</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_status_dropdown plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2008-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_status_dropdown\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	register_callback(\'rah_status_dropdown\', \'article_ui\', \'status\');\n\n/**\n * Replaces status radio buttons with a &lt;select&gt; input.\n *\n * @param  string $event\n * @param  string $step\n * @param  string $default\n * @param  array  $rs\n * @return string HTML\n */\n\n	function rah_status_dropdown($event, $step, $default, $rs)\n	{\n		global $statuses;\n\n		return \n			preg_replace(\n				\'/<ul[^>]*?>[\\s\\S]*?<\\/ul>/\',\n				graf(selectInput(\'Status\', doArray($statuses, \'strip_tags\'), !$rs[\'Status\'] ? 4 : $rs[\'Status\']), \' class=\"status\"\'),\n				$default\n			);\n	}\n\n', '\n\n/**\n * Rah_status_dropdown plugin for Textpattern CMS.\n *\n * @author  Jukka Svahn\n * @date    2008-\n * @license GNU GPLv2\n * @link    http://rahforum.biz/plugins/rah_status_dropdown\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	register_callback(\'rah_status_dropdown\', \'article_ui\', \'status\');\n\n/**\n * Replaces status radio buttons with a &lt;select&gt; input.\n *\n * @param  string $event\n * @param  string $step\n * @param  string $default\n * @param  array  $rs\n * @return string HTML\n */\n\n	function rah_status_dropdown($event, $step, $default, $rs)\n	{\n		global $statuses;\n\n		return \n			preg_replace(\n				\'/<ul[^>]*?>[\\s\\S]*?<\\/ul>/\',\n				graf(selectInput(\'Status\', doArray($statuses, \'strip_tags\'), !$rs[\'Status\'] ? 4 : $rs[\'Status\']), \' class=\"status\"\'),\n				$default\n			);\n	}\n\n', '83318c6f668a90524b9ed8b68699a100', '', '', 4, 5, 0),
('rah_tabtor', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.3.1', 'Exchange navigation links between tabs', '<h1>rah_tabtor</h1>\n\n<p><a href=\"http://rahforum.biz/plugins/rah_tabtor\" rel=\"nofollow\">Project page</a> | <a href=\"http://twitter.com/gocom\" rel=\"nofollow\">Twitter</a> | <a href=\"https://github.com/gocom/rah_tabtor\" rel=\"nofollow\">GitHub</a> | <a href=\"http://forum.textpattern.com/viewtopic.php?id=34815\" rel=\"nofollow\">Support forum</a> | <a href=\"http://rahforum.biz/donate/rah_tabtor\" rel=\"nofollow\">Donate</a></p>\n\n<p>Rah_tabtor makes it possible to move <a href=\"http://textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span>’</a> admin-side navigation items around in the in the main menubar. This allows moving admin-side panels from a menu to an another. For instance, with rah_tabtor you can move a plugin generated navigation item from the <strong>Extensions</strong> to the <strong>Content</strong> menu.</p>\n\n<h2>Basics</h2>\n\n<p>Rah_tabtor let&#8217;s you to clone and cut backend&#8217;s navigation links from a tab area or a dropdown menu to an another. Whether a menu item can be merely cloned or cut depends on the origin of the item. This comes down to technical and implementation limitations. A plugin generated menu item can be cut and moved from the original spot, while built-in core items can only be cloned, creating duplicates. Removing the original menu item is not possible when it comes to core menus.</p>\n\n<h3>Cutting plugin generated menu items</h3>\n\n<p>When rah_tabtor is used for a plugin generated menu item, the item can be cut or cloned by your choosing. The modified menu item is removed from the original spot and presented in the set target location or multiple locations. Multiple menu items that point to the same panel are created by creating additional rules for the same target menu item.</p>\n\n<h3>Cloning built-in menu items</h3>\n\n<p>Due to limitations, built-in core menus can only be cloned. Original menu items can not be removed or moved in a way or other. When rah_tabtor is used for a core menu item, a clone is created.</p>\n\n<h3>Hiding and restricting menus, creating dynamic panels</h3>\n\n<p>Rah_tabtor doesn&#8217;t hide or remove navigation items, but the plugin&#8217;s solely purpose is exchanging items between menus. If you wish to change permissions and hide tabs based on a privilege level, I suggest checking out Redbot&#8217;s <a href=\"http://www.redbot.it/txp/plugin-bot_privs\" rel=\"nofollow\">bot_privs</a> plugin.  For creating dynamic panels that allow use Textpattern&#8217;s tag markup, see Stef Dawson&#8217;s <a href=\"http://stefdawson.com/smd_tabber\" rel=\"nofollow\">smd_tabber</a>. These three plugins play well together.</p>\n\n<h2>Requirements</h2>\n\n<p>Rah_tabtor&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern v4.5.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2 or newer</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_tabtor&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Configuring</h2>\n\n<p>After you have installed the plugin, you can find its user-interface and options from <a href=\"http://textpattern.net/wiki/index.php?title=extensions\" rel=\"nofollow\">Extensions</a> menu, named as <strong>Tabtor</strong>. The plugin&#8217;s panel allows creating, editing and deleting navigation item rules.</p>\n\n<h2>Creating a new rule</h2>\n\n<p>New rules that move navigation items can be created easily directly from rah_tabtor&#8217;s admin-interface. From the <strong>Tabtor</strong> panel you can find a button labeled as <strong>Create rule</strong>. Pressing this button will bring up an editor. Present will be the following options.</p>\n\n<h3>Label</h3>\n\n<p>Is the name of the new navigation item. The option supports language strings. To use a existing localized language string as the label, type in the string&#8217;s name to the field. If the specified label doesn&#8217;t match any language strings, it&#8217;s displayed as it is.</p>\n\n<h3>Event</h3>\n\n<p>Is the moved menu item. From the list you can find any item that can be moved.</p>\n\n<h3>Group</h3>\n\n<p>Is the target navigation area, menu or tab where the menu item is moved to. Available options are Home, Content, Presentation, Admin and Extensions.</p>\n\n<h3>Position</h3>\n\n<p>Is the menu item&#8217;s sorting value, used to change the item&#8217;s position in the target menu. The position will not affect existing built-in core menu items. The position is only used among moved menu items. All items that are moved using rah_tabtor will always appear after existing core menu items. This is due to technical limitations.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.3.1 &#8211; 2012/08/30</h3>\n\n<ul>\n	<li>Added: Support links to the help file.</li>\n</ul>\n\n<h3>Version 0.3 &#8211; 2012/08/30</h3>\n\n<ul>\n	<li>Fixed: Full Textpattern 4.5 compatibility. Improved look and feel.</li>\n	<li>Fixed: Uninstall query&#8217;s wildcard escaping.</li>\n	<li>Added: <span class=\"caps\">CSRF</span> protection.</li>\n	<li>Added: Tell activity message its type (error, success, warning). Helps themes to style messages accordingly.</li>\n	<li>Added: Menu items created by plugins are now moved instead of copied. This doesn&#8217;t affect core tabs, which are protected and can not be modified.</li>\n	<li>Added: Finnish translation.</li>\n	<li>Added: French translation by <a href=\"https://twitter.com/lowel\" rel=\"nofollow\">Patrick Lefevre</a>.</li>\n	<li>Improved: Installer now defines default values for the varchar fields.</li>\n	<li>Improved: Sort items in in the list table by their label.</li>\n	<li>Removed: Embedded language strings from the source code in favor of external Textpacks.</li>\n	<li>Removed: Documentation link from navigation.</li>\n	<li>Now requires Textpattern v4.5.0 or newer, and <span class=\"caps\">PHP</span> 5.2 or newer.</li>\n</ul>\n\n<h3>Version 0.2 &#8211; 2011/06/06</h3>\n\n<ul>\n	<li>Fixed: Don&#8217;t spit beans if JavaScript is disabled.</li>\n	<li>Added: Uninstaller sporting plugin-lifecycle callbacks. Drops plugin created preference strings and tables when plugin is deleted.</li>\n	<li>Added: Now admin-side interface uses language strings and is translatable using TextPacks.</li>\n	<li>Added: “Options” link to the Plugin pane.</li>\n	<li>Changed: Moved preferences from own database table to txp_prefs table. Plugin includes automated clean up script.</li>\n	<li>Changed: Try to run installer only if the version number doesn&#8217;t match, instead of letting MySQL to decide.</li>\n	<li>Changed: Markup changes to the admin-side interface; <code>&lt;thead&gt;</code> and <code>&lt;tbody&gt;</code> containers to the tables, rah_ui selector classes to common elements for easier theming.</li>\n	<li>Changed: Shortened the admin-side interface step names.</li>\n	<li>Changed: Inform the user if deleting or updating item failed because of an database error.</li>\n	<li>Changed: Improved JavaScript based multie-edit script.</li>\n	<li>Changed: Requests sent as <span class=\"caps\">GET</span> are ignored when saving details.</li>\n	<li>Changed: Specify charset as <span class=\"caps\">UTF</span>-8 when creating new database tables.</li>\n	<li>Changed: Expanded the admin-side pane&#8217;s permitted privileges from <em>Publishers</em> to <em>Managing Editors</em>.</li>\n	<li>Removed: Plugin&#8217;s own preferences pane.</li>\n	<li>Now requires Textpattern version 4.2.0 or newer for full feature support.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2010/10/19</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/**\n * Rah_tabtor plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2010-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_tabtor\n * \n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nnew rah_tabtor();\n\nclass rah_tabtor {\n\n	static public $version = \'0.3.1\';\n	\n	/**\n	 * @var array Stores plugin areas\n	 */\n	\n	protected $plugin_areas = array();\n\n	/**\n	 * Does installing and uninstalling.\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side / plugin-lifecycle step.\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_tabtor\')\n			);\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_tabtor\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_tabtor\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`tabgroup` VARCHAR(255) NOT NULL default \'\',\n				`page` VARCHAR(255) NOT NULL default \'\',\n				`label` VARCHAR(255) NOT NULL default \'\',\n				`position` INT(2) NOT NULL default 1,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_tabtor_prefs\'));\n		\n		set_pref(__CLASS__.\'_version\', self::$version, \'rah_tabtor\', 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'rah_tabtor\', \'1,2\');\n		add_privs(\'plugin_prefs.rah_tabtor\', \'1,2\');\n		register_tab(\'extensions\', \'rah_tabtor\', gTxt(\'rah_tabtor\'));\n		register_callback(array($this, \'panes\'),\'rah_tabtor\');\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.rah_tabtor\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.rah_tabtor\');\n		$this->register();\n	}\n\n	/**\n	 * Registers the tabs\n	 */\n\n	public function register() {\n		\n		global $plugin_areas;\n		\n		@$rs = \n			safe_rows(\n				\'tabgroup, page, label\',\n				\'rah_tabtor\',\n				\'1=1 ORDER BY position asc\'\n			);\n		\n		if(!$rs) {\n			return;\n		}\n		\n		$this->plugin_areas = $plugin_areas;\n		$unset = array();\n		\n		foreach($rs as $a) {\n			\n			foreach($plugin_areas as $area => $items) {\n				foreach($items as $title => $event) {\n					if($a[\'page\'] === $event && !in_array($event, $unset)) {\n						unset($plugin_areas[$area][$title]);\n						$unset[] = $event;\n					}\n				}\n			}\n			\n			register_tab($a[\'tabgroup\'], $a[\'page\'], gTxt($a[\'label\']));\n		}\n	}\n\n	/**\n	 * Delivers panes\n	 */\n\n	public function panes() {\n		require_privs(\'rah_tabtor\');\n\n		global $step;\n		\n		$steps = \n			array(\n				\'browser\' => false,\n				\'edit\' => false,\n				\'save\' => true,\n				\'multi_edit\' => true,\n			);\n		\n		if(!$step || !bouncer($step, $steps)) {\n			$step = \'browser\';\n		}\n		\n		$this->$step();\n	}\n\n	/**\n	 * The main pane\n	 * @param string $message The message shown in the page header.\n	 */\n\n	public function browser($message=\'\') {\n		\n		global $event;\n		\n		$out[] = \n			\n			\'<form method=\"post\" action=\"index.php\" class=\"multi_edit_form\">\'.n.\n			tInput().\n			\n			\'<div class=\"txp-listtables\">\'.n.\n			\'	<table class=\"txp-list\">\'.n.\n			\'		<thead>\'.n.\n			\'			<tr>\'.n.\n			\'				<th title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"><input name=\"select_all\" type=\"checkbox\" value=\"0\" /></th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_label\').\'</th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_page\').\'</th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_group\').\'</th>\'.n.\n			\'			</tr>\'.n.\n			\'		</thead>\'.n.\n			\'		<tbody>\'.n;\n		\n		$rs = \n			safe_rows(\n				\'*\',\n				\'rah_tabtor\',\n				\'1=1 ORDER BY label asc, page asc, tabgroup asc\'\n			);\n			\n		if($rs) {\n			foreach($rs as $a) {\n				$out[] = \n					\'			<tr>\'.n.\n					\'				<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				<td><a href=\"?event=\'.$event.\'&amp;step=edit&amp;id=\'.$a[\'id\'].\'\">\'.txpspecialchars($a[\'label\']).\'</a></td>\'.n.\n					\'				<td>\'.txpspecialchars($a[\'page\']).\'</td>\'.n.\n					\'				<td>\'.txpspecialchars($a[\'tabgroup\']).\'</td>\'.n.\n					\'			</tr>\'.n;\n			}\n		}\n\n		else {\n			$out[] =\n				\'			<tr>\'.n.\n				\'				<td colspan=\"4\">\'.\n				\n				gTxt(\n					\'rah_tabtor_nothing_to_show\',\n					array(\n						\'{link}\' => \'<a href=\"?event=\'.$event.\'&amp;step=edit\">\'.gTxt(\'rah_tabtor_start_by_link\').\'</a>\'\n					), false\n				).\n				\n				\'</td>\'.n.\n				\'			</tr>\'.n;\n		}\n		\n		$out[] = \n			\'		</tbody>\'.n.\n			\'	</table>\'.n.\n			\'</div>\'.n.\n			multi_edit(array(\'delete\' => gTxt(\'rah_tabtor_delete\')), $event, \'multi_edit\').n.\n			\'</form>\';\n		\n		$this->pane($out, \'rah_tabtor\', $message);\n	}\n\n	/**\n	 * The editor pane\n	 * @param string $message The message shown in the page header.\n	 */\n\n	public function edit($message=\'\') {\n		\n		global $event;\n		\n		extract(psa(array(\n			\'label\',\n			\'page\',\n			\'tabgroup\',\n			\'position\'\n		)));\n		\n		if(($id = gps(\'id\')) && $id && !ps(\'id\')) {\n			\n			$rs = \n				safe_row(\n					\'*\',\n					\'rah_tabtor\',\n					\"id=\'\".doSlash($id).\"\'\"\n				);\n			\n			if(!$rs) {\n				$this->browser(array(gTxt(\'rah_tabtor_unknown_item\'), E_ERROR));\n				return;\n			}\n			\n			extract($rs);\n		}\n		\n		$advanced_editor = get_pref(\'rah_tabtor_advanced_editor\');\n		$tabs = $this->get_events();\n		\n		$out[] = \n			\'<form method=\"post\" action=\"index.php\">\'.n.\n			tInput().\n			eInput($event).\n			sInput(\'save\').\n			hInput(\'id\', $id).\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_label\').\'<br />\'.n.\n			\'			<input type=\"text\" name=\"label\" value=\"\'.txpspecialchars($label).\'\" />\'.n.\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_page\').\'<br />\'.n;\n		\n		if($tabs !== false && !$advanced_editor && (empty($page) || isset($tabs[\'events\'][$page]))) {\n			$out[] = selectInput(\'page\', $tabs[\'events\'], $page);\n		}\n		\n		else {\n			$out[] =\n				\'			<input type=\"text\" name=\"page\" value=\"\'.txpspecialchars($page).\'\" />\'.n;\n		}\n		\n		$out[] =\n			\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_group\').\'<br />\'.n;\n		\n		if($tabs !== false && !$advanced_editor && (empty($tabgroup) || isset($tabs[\'groups\'][$tabgroup]))) {\n			$out[] = selectInput(\'tabgroup\', $tabs[\'groups\'], $tabgroup);\n		}\n		\n		else {\n			$out[] =\n				\'			<input type=\"text\" name=\"tabgroup\" value=\"\'.txpspecialchars($tabgroup).\'\" />\'.n;\n		}\n		\n		$out[] =\n			\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_position\').\'<br />\'.n.\n			selectInput(\'position\', array_combine(range(1, 9), range(1, 9)), (int) $position);\n		\n		$out[] =\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<input type=\"submit\" value=\"\'.gTxt(\'rah_tabtor_save\').\'\" class=\"publish\" />\'.n.\n			\'	</p>\'.n.\n			\'</form>\'\n		;\n		\n		$this->pane($out, \'rah_tabtor\', $message);\n	}\n\n	/**\n	 * Does the saving work\n	 */\n\n	public function save() {\n		\n		extract(doSlash(doArray(psa(array(\n			\'label\',\n			\'page\',\n			\'tabgroup\',\n			\'id\',\n			\'position\'\n		)), \'trim\')));\n		\n		if(!$label || !$page || !$tabgroup || !in_array($position, range(1, 9))) {\n			$this->edit(array(gTxt(\'rah_tabtor_required_fields\'), E_ERROR));\n			return;\n		}\n		\n		if($id) {\n			\n			if(\n				!safe_row(\n					\'id\',\n					\'rah_tabtor\',\n					\"id=\'$id\' LIMIT 0, 1\"\n				)\n			) {\n				$this->browser(array(gTxt(\'rah_tabtor_unknown_item\'), E_ERROR));\n				return;\n			}\n			\n			if(\n				safe_update(\n					\'rah_tabtor\',\n					\"label=\'$label\',\n					page=\'$page\',\n					tabgroup=\'$tabgroup\',\n					position=\'$position\'\",\n					\"id=\'$id\'\"\n				) == false\n			) {\n				$this->edit(array(gTxt(\'rah_tabtor_save_failed\'), E_ERROR));\n				return;\n			}\n			\n			$this->browser(gTxt(\'rah_tabtor_updated\'));\n			return;\n		}\n		\n		if(\n			safe_count(\n				\'rah_tabtor\',\n				\"label=\'$label\' and \n				page=\'$page\' and \n				tabgroup=\'$tabgroup\' and \n				position=\'$position\'\"\n			) > 0\n		) {\n			$this->edit(array(gTxt(\'rah_tabtor_already_exists\'), E_WARNING));\n			return;\n		}\n		\n		if(\n			safe_insert(\n				\'rah_tabtor\',\n				\"label=\'$label\',\n				page=\'$page\',\n				tabgroup=\'$tabgroup\',\n				position=\'$position\'\"\n			) == false\n		) {\n			$this->edit(array(gTxt(\'rah_tabtor_save_failed\'), E_ERROR));\n			return;\n		}\n		\n		register_tab($tabgroup, $page, gTxt($label));\n		$this->browser(gTxt(\'rah_tabtor_saved\'));\n	}\n	\n	/**\n	 * Multi-edit handler\n	 */\n	\n	public function multi_edit() {\n		\n		extract(psa(array(\n			\'selected\',\n			\'edit_method\',\n		)));\n		\n		if(!is_string($edit_method) || empty($selected) || !is_array($selected)) {\n			$this->browser(array(gTxt(\'rah_tabtor_select_something\'), E_WARNING));\n			return;\n		}\n		\n		$method = \'multi_option_\' . $edit_method;\n		\n		if(!method_exists($this, $method)) {\n			$method = \'browser\';\n		}\n		\n		$this->$method();\n	}\n\n	/**\n	 * Delete selected items\n	 */\n\n	private function multi_option_delete() {\n		\n		if(\n			safe_delete(\n				\'rah_tabtor\',\n				\'id in(\'.implode(\',\', quote_list(ps(\'selected\'))).\')\'\n			) === false\n		) {\n			$this->browser(array(gTxt(\'rah_tabtor_delete_failed\'), E_ERROR));\n			return;\n		}\n		\n		$this->browser(gTxt(\'rah_tabtor_removed\'));\n	}\n\n	/**\n	 * Outputs the pane HTML markup and sets page title.\n	 * @param mixed $out Pane markup. Accepts arrays and strings.\n	 * @param string $pagetop Page title.\n	 * @param string $message Message shown in the header.\n	 */\n\n	private function pane($out, $pagetop, $message) {\n		\n		global $event;\n		\n		pagetop(gTxt($pagetop), $message);\n		\n		if(is_array($out)) {\n			$out = implode(\'\', $out);\n		}\n		\n		echo \n			n.\n			\'<h1 class=\"txp-heading\">\'.gTxt(\'rah_tabtor\').\'</h1>\'.n.\n			\'<div class=\"txp-container\">\'.n.\n			\'	<p class=\"txp-buttons\">\'.\n				\'<a href=\"?event=\'.$event.\'&amp;step=edit\">\'.gTxt(\'rah_tabtor_create_new\').\'</a>\'.\n			\'</p>\'.n.\n			$out.n.\n			\'</div>\'.n;\n	}\n\n	/**\n	 * Lists events and tab groups\n	 */\n\n	private function get_events() {\n		\n		global $plugin_areas;\n		\n		if(!function_exists(\'areas\') || !is_array(areas())) {\n			return false;\n		}\n		\n		$r = $plugin_areas;\n		\n		if($this->plugin_areas) {\n			$plugin_areas = $this->plugin_areas;\n		}\n		\n		$out = array();\n		\n		foreach(areas() as $key => $group) {\n			$out[\'groups\'][$key] = gTxt(\'tab_\'.$key);\n			\n			foreach($group as $title => $name) {\n				$out[\'events\'][$name] = $title;\n			}\n		}\n		\n		$out[\'events\'] = array_unique($out[\'events\']);\n		asort($out[\'events\']);\n		$plugin_areas = $r;\n		\n		return $out;\n	}\n\n	/**\n	 * Redirect to the admin-side interface\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=rah_tabtor\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=rah_tabtor\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n', '\n\n/**\n * Rah_tabtor plugin for Textpattern CMS.\n *\n * @author Jukka Svahn\n * @date 2010-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_tabtor\n * \n * Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\nnew rah_tabtor();\n\nclass rah_tabtor {\n\n	static public $version = \'0.3.1\';\n	\n	/**\n	 * @var array Stores plugin areas\n	 */\n	\n	protected $plugin_areas = array();\n\n	/**\n	 * Does installing and uninstalling.\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side / plugin-lifecycle step.\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			\n			@safe_query(\n				\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_tabtor\')\n			);\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah\\_tabtor\\_%\'\"\n			);\n			\n			return;\n		}\n		\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		safe_query(\n			\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'rah_tabtor\').\" (\n				`id` INT(11) NOT NULL auto_increment,\n				`tabgroup` VARCHAR(255) NOT NULL default \'\',\n				`page` VARCHAR(255) NOT NULL default \'\',\n				`label` VARCHAR(255) NOT NULL default \'\',\n				`position` INT(2) NOT NULL default 1,\n				PRIMARY KEY(`id`)\n			) PACK_KEYS=1 AUTO_INCREMENT=1 CHARSET=utf8\"\n		);\n		\n		@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_tabtor_prefs\'));\n		\n		set_pref(__CLASS__.\'_version\', self::$version, \'rah_tabtor\', 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n	\n	/**\n	 * Constructor\n	 */\n	\n	public function __construct() {\n		add_privs(\'rah_tabtor\', \'1,2\');\n		add_privs(\'plugin_prefs.rah_tabtor\', \'1,2\');\n		register_tab(\'extensions\', \'rah_tabtor\', gTxt(\'rah_tabtor\'));\n		register_callback(array($this, \'panes\'),\'rah_tabtor\');\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.rah_tabtor\');\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.rah_tabtor\');\n		$this->register();\n	}\n\n	/**\n	 * Registers the tabs\n	 */\n\n	public function register() {\n		\n		global $plugin_areas;\n		\n		@$rs = \n			safe_rows(\n				\'tabgroup, page, label\',\n				\'rah_tabtor\',\n				\'1=1 ORDER BY position asc\'\n			);\n		\n		if(!$rs) {\n			return;\n		}\n		\n		$this->plugin_areas = $plugin_areas;\n		$unset = array();\n		\n		foreach($rs as $a) {\n			\n			foreach($plugin_areas as $area => $items) {\n				foreach($items as $title => $event) {\n					if($a[\'page\'] === $event && !in_array($event, $unset)) {\n						unset($plugin_areas[$area][$title]);\n						$unset[] = $event;\n					}\n				}\n			}\n			\n			register_tab($a[\'tabgroup\'], $a[\'page\'], gTxt($a[\'label\']));\n		}\n	}\n\n	/**\n	 * Delivers panes\n	 */\n\n	public function panes() {\n		require_privs(\'rah_tabtor\');\n\n		global $step;\n		\n		$steps = \n			array(\n				\'browser\' => false,\n				\'edit\' => false,\n				\'save\' => true,\n				\'multi_edit\' => true,\n			);\n		\n		if(!$step || !bouncer($step, $steps)) {\n			$step = \'browser\';\n		}\n		\n		$this->$step();\n	}\n\n	/**\n	 * The main pane\n	 * @param string $message The message shown in the page header.\n	 */\n\n	public function browser($message=\'\') {\n		\n		global $event;\n		\n		$out[] = \n			\n			\'<form method=\"post\" action=\"index.php\" class=\"multi_edit_form\">\'.n.\n			tInput().\n			\n			\'<div class=\"txp-listtables\">\'.n.\n			\'	<table class=\"txp-list\">\'.n.\n			\'		<thead>\'.n.\n			\'			<tr>\'.n.\n			\'				<th title=\"\'.gTxt(\'toggle_all_selected\').\'\" class=\"multi-edit\"><input name=\"select_all\" type=\"checkbox\" value=\"0\" /></th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_label\').\'</th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_page\').\'</th>\'.n.\n			\'				<th>\'.gTxt(\'rah_tabtor_group\').\'</th>\'.n.\n			\'			</tr>\'.n.\n			\'		</thead>\'.n.\n			\'		<tbody>\'.n;\n		\n		$rs = \n			safe_rows(\n				\'*\',\n				\'rah_tabtor\',\n				\'1=1 ORDER BY label asc, page asc, tabgroup asc\'\n			);\n			\n		if($rs) {\n			foreach($rs as $a) {\n				$out[] = \n					\'			<tr>\'.n.\n					\'				<td><input type=\"checkbox\" name=\"selected[]\" value=\"\'.$a[\'id\'].\'\" /></td>\'.n.\n					\'				<td><a href=\"?event=\'.$event.\'&amp;step=edit&amp;id=\'.$a[\'id\'].\'\">\'.txpspecialchars($a[\'label\']).\'</a></td>\'.n.\n					\'				<td>\'.txpspecialchars($a[\'page\']).\'</td>\'.n.\n					\'				<td>\'.txpspecialchars($a[\'tabgroup\']).\'</td>\'.n.\n					\'			</tr>\'.n;\n			}\n		}\n\n		else {\n			$out[] =\n				\'			<tr>\'.n.\n				\'				<td colspan=\"4\">\'.\n				\n				gTxt(\n					\'rah_tabtor_nothing_to_show\',\n					array(\n						\'{link}\' => \'<a href=\"?event=\'.$event.\'&amp;step=edit\">\'.gTxt(\'rah_tabtor_start_by_link\').\'</a>\'\n					), false\n				).\n				\n				\'</td>\'.n.\n				\'			</tr>\'.n;\n		}\n		\n		$out[] = \n			\'		</tbody>\'.n.\n			\'	</table>\'.n.\n			\'</div>\'.n.\n			multi_edit(array(\'delete\' => gTxt(\'rah_tabtor_delete\')), $event, \'multi_edit\').n.\n			\'</form>\';\n		\n		$this->pane($out, \'rah_tabtor\', $message);\n	}\n\n	/**\n	 * The editor pane\n	 * @param string $message The message shown in the page header.\n	 */\n\n	public function edit($message=\'\') {\n		\n		global $event;\n		\n		extract(psa(array(\n			\'label\',\n			\'page\',\n			\'tabgroup\',\n			\'position\'\n		)));\n		\n		if(($id = gps(\'id\')) && $id && !ps(\'id\')) {\n			\n			$rs = \n				safe_row(\n					\'*\',\n					\'rah_tabtor\',\n					\"id=\'\".doSlash($id).\"\'\"\n				);\n			\n			if(!$rs) {\n				$this->browser(array(gTxt(\'rah_tabtor_unknown_item\'), E_ERROR));\n				return;\n			}\n			\n			extract($rs);\n		}\n		\n		$advanced_editor = get_pref(\'rah_tabtor_advanced_editor\');\n		$tabs = $this->get_events();\n		\n		$out[] = \n			\'<form method=\"post\" action=\"index.php\">\'.n.\n			tInput().\n			eInput($event).\n			sInput(\'save\').\n			hInput(\'id\', $id).\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_label\').\'<br />\'.n.\n			\'			<input type=\"text\" name=\"label\" value=\"\'.txpspecialchars($label).\'\" />\'.n.\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_page\').\'<br />\'.n;\n		\n		if($tabs !== false && !$advanced_editor && (empty($page) || isset($tabs[\'events\'][$page]))) {\n			$out[] = selectInput(\'page\', $tabs[\'events\'], $page);\n		}\n		\n		else {\n			$out[] =\n				\'			<input type=\"text\" name=\"page\" value=\"\'.txpspecialchars($page).\'\" />\'.n;\n		}\n		\n		$out[] =\n			\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_group\').\'<br />\'.n;\n		\n		if($tabs !== false && !$advanced_editor && (empty($tabgroup) || isset($tabs[\'groups\'][$tabgroup]))) {\n			$out[] = selectInput(\'tabgroup\', $tabs[\'groups\'], $tabgroup);\n		}\n		\n		else {\n			$out[] =\n				\'			<input type=\"text\" name=\"tabgroup\" value=\"\'.txpspecialchars($tabgroup).\'\" />\'.n;\n		}\n		\n		$out[] =\n			\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<label>\'.n.\n			\'			\'.gTxt(\'rah_tabtor_position\').\'<br />\'.n.\n			selectInput(\'position\', array_combine(range(1, 9), range(1, 9)), (int) $position);\n		\n		$out[] =\n			\'		</label>\'.n.\n			\'	</p>\'.n.\n			\n			\'	<p>\'.n.\n			\'		<input type=\"submit\" value=\"\'.gTxt(\'rah_tabtor_save\').\'\" class=\"publish\" />\'.n.\n			\'	</p>\'.n.\n			\'</form>\'\n		;\n		\n		$this->pane($out, \'rah_tabtor\', $message);\n	}\n\n	/**\n	 * Does the saving work\n	 */\n\n	public function save() {\n		\n		extract(doSlash(doArray(psa(array(\n			\'label\',\n			\'page\',\n			\'tabgroup\',\n			\'id\',\n			\'position\'\n		)), \'trim\')));\n		\n		if(!$label || !$page || !$tabgroup || !in_array($position, range(1, 9))) {\n			$this->edit(array(gTxt(\'rah_tabtor_required_fields\'), E_ERROR));\n			return;\n		}\n		\n		if($id) {\n			\n			if(\n				!safe_row(\n					\'id\',\n					\'rah_tabtor\',\n					\"id=\'$id\' LIMIT 0, 1\"\n				)\n			) {\n				$this->browser(array(gTxt(\'rah_tabtor_unknown_item\'), E_ERROR));\n				return;\n			}\n			\n			if(\n				safe_update(\n					\'rah_tabtor\',\n					\"label=\'$label\',\n					page=\'$page\',\n					tabgroup=\'$tabgroup\',\n					position=\'$position\'\",\n					\"id=\'$id\'\"\n				) == false\n			) {\n				$this->edit(array(gTxt(\'rah_tabtor_save_failed\'), E_ERROR));\n				return;\n			}\n			\n			$this->browser(gTxt(\'rah_tabtor_updated\'));\n			return;\n		}\n		\n		if(\n			safe_count(\n				\'rah_tabtor\',\n				\"label=\'$label\' and \n				page=\'$page\' and \n				tabgroup=\'$tabgroup\' and \n				position=\'$position\'\"\n			) > 0\n		) {\n			$this->edit(array(gTxt(\'rah_tabtor_already_exists\'), E_WARNING));\n			return;\n		}\n		\n		if(\n			safe_insert(\n				\'rah_tabtor\',\n				\"label=\'$label\',\n				page=\'$page\',\n				tabgroup=\'$tabgroup\',\n				position=\'$position\'\"\n			) == false\n		) {\n			$this->edit(array(gTxt(\'rah_tabtor_save_failed\'), E_ERROR));\n			return;\n		}\n		\n		register_tab($tabgroup, $page, gTxt($label));\n		$this->browser(gTxt(\'rah_tabtor_saved\'));\n	}\n	\n	/**\n	 * Multi-edit handler\n	 */\n	\n	public function multi_edit() {\n		\n		extract(psa(array(\n			\'selected\',\n			\'edit_method\',\n		)));\n		\n		if(!is_string($edit_method) || empty($selected) || !is_array($selected)) {\n			$this->browser(array(gTxt(\'rah_tabtor_select_something\'), E_WARNING));\n			return;\n		}\n		\n		$method = \'multi_option_\' . $edit_method;\n		\n		if(!method_exists($this, $method)) {\n			$method = \'browser\';\n		}\n		\n		$this->$method();\n	}\n\n	/**\n	 * Delete selected items\n	 */\n\n	private function multi_option_delete() {\n		\n		if(\n			safe_delete(\n				\'rah_tabtor\',\n				\'id in(\'.implode(\',\', quote_list(ps(\'selected\'))).\')\'\n			) === false\n		) {\n			$this->browser(array(gTxt(\'rah_tabtor_delete_failed\'), E_ERROR));\n			return;\n		}\n		\n		$this->browser(gTxt(\'rah_tabtor_removed\'));\n	}\n\n	/**\n	 * Outputs the pane HTML markup and sets page title.\n	 * @param mixed $out Pane markup. Accepts arrays and strings.\n	 * @param string $pagetop Page title.\n	 * @param string $message Message shown in the header.\n	 */\n\n	private function pane($out, $pagetop, $message) {\n		\n		global $event;\n		\n		pagetop(gTxt($pagetop), $message);\n		\n		if(is_array($out)) {\n			$out = implode(\'\', $out);\n		}\n		\n		echo \n			n.\n			\'<h1 class=\"txp-heading\">\'.gTxt(\'rah_tabtor\').\'</h1>\'.n.\n			\'<div class=\"txp-container\">\'.n.\n			\'	<p class=\"txp-buttons\">\'.\n				\'<a href=\"?event=\'.$event.\'&amp;step=edit\">\'.gTxt(\'rah_tabtor_create_new\').\'</a>\'.\n			\'</p>\'.n.\n			$out.n.\n			\'</div>\'.n;\n	}\n\n	/**\n	 * Lists events and tab groups\n	 */\n\n	private function get_events() {\n		\n		global $plugin_areas;\n		\n		if(!function_exists(\'areas\') || !is_array(areas())) {\n			return false;\n		}\n		\n		$r = $plugin_areas;\n		\n		if($this->plugin_areas) {\n			$plugin_areas = $this->plugin_areas;\n		}\n		\n		$out = array();\n		\n		foreach(areas() as $key => $group) {\n			$out[\'groups\'][$key] = gTxt(\'tab_\'.$key);\n			\n			foreach($group as $title => $name) {\n				$out[\'events\'][$name] = $title;\n			}\n		}\n		\n		$out[\'events\'] = array_unique($out[\'events\']);\n		asort($out[\'events\']);\n		$plugin_areas = $r;\n		\n		return $out;\n	}\n\n	/**\n	 * Redirect to the admin-side interface\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=rah_tabtor\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=rah_tabtor\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n', 'eecb03dcac60bb60c60c94f865b2681f', '#@admin\n#Default language, en-us, en-gb\nrah_tabtor => Tabtor\nrah_tabtor_create_new => Create rule\nrah_tabtor_label => Label\nrah_tabtor_page => Event\nrah_tabtor_group => Group\nrah_tabtor_nothing_to_show => Nothing to show yet. {link}.\nrah_tabtor_start_by_link => Start by creating your first rule\nrah_tabtor_delete => Delete\nrah_tabtor_position => Position\nrah_tabtor_save => Save\nrah_tabtor_unknown_item => Unknown item.\nrah_tabtor_required_fields => All fields are required.\nrah_tabtor_updated => Item updated.\nrah_tabtor_saved => Item saved.\nrah_tabtor_removed => Selected items removed.\nrah_tabtor_save_failed => The item was not saved due to an error. Please try again.\nrah_tabtor_delete_failed => Item(s) were not deleted due to an error. Please try again.\nrah_tabtor_select_something => Nothing selected.\nrah_tabtor_already_exists => Identical rule already exists.\n#@admin\n#@language fi-fi\nrah_tabtor => Tabtor\nrah_tabtor_create_new => Luo määritys\nrah_tabtor_label => Nimi\nrah_tabtor_page => Sivu\nrah_tabtor_group => Ryhmä\nrah_tabtor_nothing_to_show => Ei luotuja määrityksiä. {link}.\nrah_tabtor_start_by_link => Aloita luomalla ensimmäinen määritys\nrah_tabtor_delete => Poista\nrah_tabtor_position => Järjestys\nrah_tabtor_save => Tallenna\nrah_tabtor_unknown_item => Tuntematon valinta.\nrah_tabtor_required_fields => Kaikki kentät vaaditaan.\nrah_tabtor_updated => Määritys päivitetty.\nrah_tabtor_saved => Määritys tallennettu.\nrah_tabtor_removed => Valitut poistettu.\nrah_tabtor_save_failed => Muutoksia ei tallennettu virheen seurauksena. Ole hyvä ja yritä uudelleen.\nrah_tabtor_delete_failed => Mitään ei poistettu virheen seurauksena. Ole hyvä ja yritä uudelleen.\nrah_tabtor_select_something => Mitään ei valittu.\nrah_tabtor_already_exists => Identtinen määritys on jo olemassa.\n#@admin\n#@language fr-fr\nrah_tabtor => Tabtor\nrah_tabtor_create_new => Créer une règle\nrah_tabtor_label => Label\nrah_tabtor_page => Evénement\nrah_tabtor_group => Groupe\nrah_tabtor_nothing_to_show => Rien a afficher pour le moment. {link}.\nrah_tabtor_start_by_link => Commencez par créer votre première règle\nrah_tabtor_delete => Supprimer\nrah_tabtor_position => Position\nrah_tabtor_save => Sauver\nrah_tabtor_unknown_item => Element inconnu.\nrah_tabtor_required_fields => Tous les champs sont obligatoires.\nrah_tabtor_updated => Elément mis à jour.\nrah_tabtor_saved => Elément sauvegardé.\nrah_tabtor_removed => Les éléments sélectionnés ont été supprimés.\nrah_tabtor_save_failed => L\'élément n\'a pas été sauvegardé suite à une erreur interne. Veuillez essayer à nouveau.\nrah_tabtor_delete_failed => Elément(s) non supprimé(s) en raison d\'une erreur. Veuillez essayer à nouveau.\nrah_tabtor_select_something => Rien n\'a été sélectionné.\nrah_tabtor_already_exists => Une règle identique existe déjà.', '', 3, 9, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_textile_bar', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.7', 'Simple Textile inserting bar for the Write tab\'s Body-textarea', '	<h1>rah_textile_bar</h1>\n\n	<p>Brings a simple and minimal Quicktags Textile inserting bar to the backend. No, it&#8217;s not an heavy and buggy <span class=\"caps\">WYSIWYG</span> editor that screws your own <span class=\"caps\">XHTML</span> input, but instead it&#8217;s just a simple bar that insert Textile where you want. The Javascript code is forked from <a href=\"http://forum.textpattern.com/viewtopic.php?id=7470\">hak_textile_tags by great hakjoon.</a></p>\n\n	<ul>\n		<li>Version: 0.7</li>\n		<li>Updated: 2011/06/30 2:35 PM <span class=\"caps\">UTC</span></li>\n	</ul>\n\n	<h3>Table of Contents</h3>\n\n	<ul>\n		<li><a href=\"#list-of-features\">List of features</a></li>\n		<li><a href=\"#requirements\">Requirements</a></li>\n		<li><a href=\"#installation-and-usage\">Installation and usage</a></li>\n		<li><a href=\"#changelog\">Changelog</a></li>\n	</ul>\n\n	<h3 id=\"list-of-features\">List of features</h3>\n\n	<ul>\n		<li>A simple Textile inserting bar, offering the most common formatting options Textile has.</li>\n		<li>Easy to use and install: just run the automated plugin installation and activate.</li>\n		<li>Options can be configured via easy graphical user interface, located at Textpattern&#8217;s Advanced Preferences panel (Textpattern / Admin / Preferences / Advanced).</li>\n	</ul>\n\n	<h3 id=\"requirements\">Requirements</h3>\n\n	<p>Minimum requirements:</p>\n\n	<ul>\n		<li>Textpattern 4.0.7+</li>\n		<li>Textpattern with jQuery</li>\n		<li>A web browser that is jQuery compatible</li>\n	</ul>\n\n	<p>Recommended:</p>\n\n	<ul>\n		<li>Textpattern 4.4.1+</li>\n	</ul>\n\n	<h3 id=\"installation-and-usage\">Installation and usage</h3>\n\n	<p>Download and copy the plugin code to the plugin installer textarea and run the automatic setup. After activating the plugin, you will see the textilebar above Body-textarea in the Write-tab.</p>\n\n	<h3 id=\"changelog\">Changelog</h3>\n\n	<p><strong>Version 0.7</strong></p>\n\n	<ul>\n		<li>Fixed: Added back the missed h3. <a href=\"http://forum.textpattern.com/viewtopic.php?pid=248300#p248300\">Thanks Susanne</a>.</li>\n	</ul>\n\n	<p><strong>Version 0.6</strong></p>\n\n	<ul>\n		<li>Fixed: Don&#8217;t reserved global JavaScript variables and functions; use prefixes for everything.</li>\n		<li>Added: integrated uninstaller that cleans up the database when the plugin is deleted via the admin-side Plugins interface.</li>\n		<li>Added: Options link to the Textpattern&#8217;s Plugins interface.</li>\n		<li>Changed: integrated the plugin&#8217;s individual preferences tab to Textpattern&#8217;s native Preferences pane. Now all the plugin options can be configured in Advanced Preferences (<span class=\"caps\">TXP</span> / Admin / Preferences / Advanced).</li>\n		<li>Changed: Now preferences are stored in <span class=\"caps\">TXP</span>&#8217;s txp_prefs table instead of it&#8217;s own. Plugin includes automated migration script.</li>\n		<li>Changed: Plugin type to the newer admin-side only.</li>\n		<li>Changed: Now the included JavaScript and images are only available for logged in users. Others won&#8217;t be able to view the static files.</li>\n		<li>Requires 4.2.0 or newer for full feature support.</li>\n	</ul>\n\n	<p><strong>Version 0.5</strong></p>\n\n	<ul>\n		<li>Fix: doesn&#8217;t hook to textareas if all are excluded.</li>\n		<li>Updated the Preferences Panel&#8217;s look.</li>\n	</ul>\n\n	<p><strong>Version 0.4</strong></p>\n\n	<ul>\n		<li>Added support for multiple textareas. See new settings under rah_textile_bar&#8217;s extensions panel.</li>\n		<li>Changed the bar&#8217;s default styles so that the bar is bit smaller.</li>\n		<li>Rah_textile_bar version 0.3.5 and above require Textpattern version 4.0.7 or above.</li>\n		<li>Removed unrequired what-image-to-show feature. Leftover from version 0.2.</li>\n		<li>Compained required queries for button settings into one.</li>\n	</ul>\n\n	<p><strong>Version 0.3.5</strong></p>\n\n	<ul>\n		<li>Improved callbacks.</li>\n	</ul>\n\n	<p><strong>Version 0.3.4</strong></p>\n\n	<ul>\n		<li>Removed <code>default &#39;&#39;</code>. See <a href=\"http://forum.textpattern.com/viewtopic.php?pid=203039#p203039\">here.</a></li>\n	</ul>\n\n	<p><strong>Version 0.3.3</strong></p>\n\n	<ul>\n		<li>Fixed error that occured when running MySQL in strict mode, caused by undefaulted not null in install script.</li>\n	</ul>\n\n	<p><strong>Version 0.3.2</strong></p>\n\n	<ul>\n		<li>Minor: fixed slipped &#8220;abbr&#8221; to &#8220;acronym&#8221;.</li>\n	</ul>\n\n	<p><strong>Version 0.3.1</strong></p>\n\n	<ul>\n		<li>Minor: fixed word &#8220;Documention&#8221; to &#8220;Documentation&#8221;.</li>\n	</ul>\n\n	<p><strong>Version 0.3</strong></p>\n\n	<ul>\n		<li>Now uses <span class=\"caps\">CSS</span> sprite technique.</li>\n		<li>Changed image type from <span class=\"caps\">PNG</span> to <span class=\"caps\">GIF</span>: now renders fine in really old browsers.</li>\n		<li>Improved the code and loading speed.</li>\n	</ul>\n\n	<p><strong>Version 0.2</strong></p>\n\n	<ul>\n		<li>Re-organized the code.</li>\n		<li>Options under Extensions-tab to disable textile bar items.</li>\n		<li>Now inserts space after blockquotes, headers and list items.</li>\n	</ul>\n\n	<p><strong>Version 0.1</strong></p>\n\n	<ul>\n		<li>First release.</li>\n	</ul>', '	##################\n	#\n	#	rah_textile_bar-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_textile_bar_install();\n		rah_textile_bar_img();\n		add_privs(\'plugin_prefs.rah_textile_bar\',\'1,2\');\n		register_callback(\'rah_textile_bar_prefs\',\'plugin_prefs.rah_textile_bar\');\n		register_callback(\'rah_textile_bar_install\',\'plugin_lifecycle.rah_textile_bar\');\n		register_callback(\'rah_textile_bar\',\'admin_side\',\'head_end\');\n	}\n\n/**\n	Installer\n	@param $event string Admin-side event.\n	@param $step string Admin-side event, plugin-lifecycle step.\n*/\n\n	function rah_textile_bar_install($event=\'\',$step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_textile_bar_%\'\"\n			);\n			\n			return;\n		}\n		\n		global $prefs, $event, $textarray;\n		\n		if($event == \'prefs\') {\n			\n			/*\n				Generate language strings if\n				not existing\n			*/\n			\n			$strings = \n				array(\n					\'rah_txtbar\' => \'Textile Bar\',\n					\'rah_textile_bar_body\' => \'Attach to Body field\',\n					\'rah_textile_bar_excerpt\' => \'Attach to Excerpt field\'\n				);\n			\n			foreach(rah_textile_bar_buttons() as $att)\n				$strings[\'rah_textile_bar_\' . $att[0]] = \'Show \' . $att[0];\n			\n			foreach(\n				$strings as $string => $translation\n			)\n				if(!isset($textarray[$string]))\n					$textarray[$string] = $translation;\n		}\n		\n		$version = \'0.7\';\n		\n		$current = \n			isset($prefs[\'rah_textile_bar_version\']) ? \n				$prefs[\'rah_textile_bar_version\'] : \'\';\n		\n		if($current == $version)\n			return;\n			\n		$default = \n			array(\n				\'disable\' => \'\',\n				\'fields\' => \'\'\n			);\n		\n		if(!$current) {\n		\n			/*\n				Run migration and clean-up if older version was\n				installed\n			*/\n		\n			@$rs = \n				safe_rows(\n					\'name, value\',\n					\'rah_textile_bar\',\n					\'1=1\'\n				);\n		\n			if(!empty($rs) && is_array($rs)) {\n				foreach($rs as $a)\n					if(isset($default[$a[\'name\']]))\n						$default[$a[\'name\']] = $a[\'value\'];\n			\n				@safe_query(\n					\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_textile_bar\')\n				);\n			}\n		}\n		\n		/*\n			Add preference strings\n		*/\n		\n		$disabled = explode(\',\', $default[\'disable\']);\n		$position = 230;\n		$values = rah_textile_bar_buttons();\n		\n		foreach(array(\'body\',\'excerpt\') as $val)\n			$values[] = array($val);\n		\n		foreach($values as $att) {\n			\n			$val = \n				$att[0] == \'body\' || $att[0] == \'excerpt\' ? \n					(\n						strpos($default[\'fields\'], $att[0]) !== false || !$default[\'fields\'] ?\n							1 : 0\n					)\n				:\n					(\n						in_array(\'#textilebar .\'.$att[0], $disabled) ? \n							0 : 1\n					);\n			\n			if($att[0] == \'body\' || $att[0] == \'excerpt\')\n				$position = 225;\n			\n			$name = \'rah_textile_bar_\'.$att[0];\n			\n			if(!isset($prefs[$name])) {\n				safe_insert(\n					\'txp_prefs\',\n					\"prefs_id=1,\n					name=\'\".doSlash($name).\"\',\n					val=\'\".doSlash($val).\"\',\n					type=1,\n					event=\'rah_txtbar\',\n					html=\'yesnoradio\',\n					position=\".$position\n				);\n				\n				$prefs[$name] = $val;\n			}\n			\n			$position++;\n			\n		}\n		\n		set_pref(\'rah_textile_bar_version\',$version,\'rah_txtbar\',2,\'\',0);\n		$prefs[\'rah_textile_bar_version\'] = $version;\n	}\n\n/**\n	Lists buttons\n	@return mixed Array of buttons.\n*/\n	\n	function rah_textile_bar_buttons() {\n		$b[] = array(\'strong\',\'strong\',\'*\',\'*\');\n		$b[] = array(\'link\',\'url\',\'\',\'\');\n		$b[] = array(\'emphasis\',\'em\',\'_\',\'_\');\n		$b[] = array(\'ins\',\'ins\',\'+\',\'+\');\n		$b[] = array(\'del\',\'del\',\'-\',\'-\');\n		$b[] = array(\'h1\',\'h1\',\'h1. \',\'\');\n		$b[] = array(\'h2\',\'h2\',\'h2. \',\'\');\n		$b[] = array(\'h3\',\'h3\',\'h3. \',\'\');\n		$b[] = array(\'h4\',\'h4\',\'h4. \',\'\');\n		$b[] = array(\'h5\',\'h5\',\'h5. \',\'\');\n		$b[] = array(\'h6\',\'h6\',\'h6. \',\'\');\n		$b[] = array(\'image\',\'img\',\'\',\'\');\n		$b[] = array(\'codeline\',\'codeline\',\'@\',\'@\');\n		$b[] = array(\'ul\',\'ul\',\'* \',\'\\\\n\');\n		$b[] = array(\'ol\',\'ol\',\'# \',\'\\\\n\');\n		$b[] = array(\'sup\',\'sup\',\'^\',\'^\');\n		$b[] = array(\'sub\',\'sub\',\'~\',\'~\');\n		$b[] = array(\'bq\',\'bq\',\'bq. \',\'\\\\n\\\\n\');\n		$b[] = array(\'bc\',\'bc\',\'bc. \',\'\\\\n\\\\n\');\n		$b[] = array(\'acronym\',\'acronym\',\'\',\'\');\n		$b[] = array(\'output_form\',\'output_form\',\'\',\'\');\n		return $b;\n	}\n\n/**\n	All the required scripts and styles\n*/\n\n	function rah_textile_bar() {\n		global $event, $prefs;\n		\n		if($event != \'article\')\n			return;\n		\n		$buttons = array();\n		\n		foreach(rah_textile_bar_buttons()  as $att)\n			if(\n				isset($prefs[\'rah_textile_bar_\'.$att[0]]) &&\n				$prefs[\'rah_textile_bar_\'.$att[0]] == 1\n			)\n				$buttons[] = \n					\'				rah_textile_bar_theButtons[rah_textile_bar_theButtons.length] = new rah_textile_bar_edButton(\"\'.$att[0].\'\",\"\'.$att[1].\'\",\"\'.$att[2].\'\",\"\'.$att[3].\'\");\';\n			\n		if(!$buttons)\n			return;\n		\n		$buttons = trim(implode(n,$buttons));\n		$f = array();\n		$fields = array(\'body\',\'excerpt\');\n		\n		if(\n			isset($prefs[\'rah_textile_bar_additional_fields\']) &&\n			!empty($prefs[\'rah_textile_bar_additional_fields\'])\n		) {\n			foreach(explode(\',\',$prefs[\'rah_textile_bar_additional_fields\']) as $id)\n				$fields[] = $id;\n		}\n		\n		foreach($fields as $key => $field) \n			if(\n				isset($prefs[\'rah_textile_bar_\'.$field]) &&\n				$prefs[\'rah_textile_bar_\'.$field] == 1\n			)\n				$f[] = <<<EOF\n					$(\'textarea#{$field}\').before(\'<div class=\"rah_textile_bar\" id=\"rah_textile_bar_{$key}\"></div>\');\n					rah_textile_bar_addEvent(window, \'load\',\n						function() {\n							rah_textile_bar_initQuicktags(\'{$field}\',\'rah_textile_bar_{$key}\');\n						}\n					);\nEOF;\n\n\n		\n		if(!$f)\n			return;	\n		\n		$f = trim(implode(\'\',$f));\n		\n		echo <<<EOF\n			<script type=\"text/javascript\">\n				<!--\n\n				/*\n					Event listener\n				*/\n\n				function rah_textile_bar_addEvent(obj, evType, fn){\n					if (obj.addEventListener){\n						obj.addEventListener(evType, fn, true);\n						return true;\n					} else if (obj.attachEvent){\n						var r = obj.attachEvent(\"on\"+evType, fn);\n						return r;\n					} else {\n						return false;\n					}\n				}\n\n				/*\n					Init quicktags\n				*/\n\n				function rah_textile_bar_initQuicktags(identifier, textilebarid) {\n					var getCanvas = document.getElementsByTagName(\"textarea\");\n					for(var i = 0; i < getCanvas.length; i++) {\n						if(getCanvas[i].name == identifier  || getCanvas[i].id == identifier) {\n							var canvas = getCanvas[i];\n						}\n						if(canvas) {\n							var toolbar = document.getElementById(textilebarid);\n							toolbar.style.visibility = \"visible\";\n							var edButtons = new Array();\n							edButtons = rah_textile_bar_theButtons;\n							for (var i = 0; i < edButtons.length; i++) {\n								var thisButton = rah_textile_bar_edShowButton(edButtons[i], canvas);\n								toolbar.appendChild(thisButton);\n							}\n						}\n					}\n				}\n\n				/*\n					Spawn a button and it\'s actions\n				*/\n\n				function rah_textile_bar_edShowButton(button, edCanvas) {\n					var theButton = document.createElement(\"div\");\n					theButton.id = button.id;\n					theButton.title = button.id;\n					theButton.className = \'textilebutton\';\n					theButton.className += \' \' + button.id;\n					theButton.tagStart = button.tagStart;\n					theButton.tagEnd = button.tagEnd;\n					theButton.open = button.open;\n					if (button.id == \'image\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertImage(edCanvas); }\n					} else if (button.id == \'link\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertLink(edCanvas);}\n					} else if (button.id == \'output_form\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertForm(edCanvas);}\n					} else if (button.id == \'acronym\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertAcronym(edCanvas);}\n					} else {\n						theButton.onclick = function() { rah_textile_bar_edInsertTag(edCanvas,this); }\n					}\n					theButton.innerHTML = (button.display) + \"\";\n					return theButton;\n				}\n\n				/*\n					Add tag, active button\n				*/\n\n				function rah_textile_bar_edAddTag(button) {\n					if(button.tagEnd != \'\') {\n						rah_textile_bar_edOpenTags[rah_textile_bar_edOpenTags.length] = button;\n						button.innerHTML = \'/\' + button.innerHTML;\n						button.className = button.className.replace(\"textilebutton\", \"active\");\n					}\n				}\n\n				/*\n					Close tag, remove active state\n				*/\n\n				function rah_textile_bar_edRemoveTag(button) {\n					for(i = 0; i < rah_textile_bar_edOpenTags.length; i++) {\n						if(rah_textile_bar_edOpenTags[i] == button) {\n							rah_textile_bar_edOpenTags.splice(button, 1);\n							button.innerHTML = button.innerHTML.replace(\'/\', \'\');\n							button.className = button.className.replace(\"active\", \"textilebutton\");\n						}\n					}\n				}\n\n				/*\n					Check for open tags\n				*/\n\n				function rah_textile_bar_edCheckOpenTags(button) {\n					var tag = 0;\n					for(i = 0; i < rah_textile_bar_edOpenTags.length; i++) {\n						if(rah_textile_bar_edOpenTags[i] == button) {\n							tag++;\n						}\n					}\n					if(tag > 0) {\n						return true;\n					} else {\n						return false;\n					}\n				}\n\n				/*\n					Close all tags\n				*/\n\n				function rah_textile_bar_edCloseAllTags(edCanvas) {\n					var count = rah_textile_bar_edOpenTags.length;\n					for(o = 0; o < count; o++) {\n						rah_textile_bar_edInsertTag(edCanvas, rah_textile_bar_edOpenTags[rah_textile_bar_edOpenTags.length - 1]);\n					}\n				}\n\n				/*\n					Insert tag\n				*/\n\n				function rah_textile_bar_edInsertTag(myField, button) {\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						if (sel.text.length > 0) {\n							sel.text = button.tagStart + sel.text + button.tagEnd;\n						} else {\n							if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n								sel.text = button.tagStart;\n								rah_textile_bar_edAddTag(button);\n							} else {\n								sel.text = button.tagEnd;\n								rah_textile_bar_edRemoveTag(button);\n							}\n						}\n						myField.focus();\n					} else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						var cursorPos = endPos;\n						var scrollTop = myField.scrollTop;\n						if (startPos != endPos) {\n							myField.value = myField.value.substring(0, startPos) + button.tagStart + myField.value.substring(startPos, endPos) + button.tagEnd + myField.value.substring(endPos, myField.value.length);\n							cursorPos += button.tagStart.length + button.tagEnd.length;\n						}else {\n							if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n								myField.value = myField.value.substring(0, startPos) + button.tagStart + myField.value.substring(endPos, myField.value.length);\n								rah_textile_bar_edAddTag(button);\n								cursorPos = startPos + button.tagStart.length;\n							} else {\n								myField.value = myField.value.substring(0, startPos)+ button.tagEnd + myField.value.substring(endPos, myField.value.length);\n								rah_textile_bar_edRemoveTag(button);\n								cursorPos = startPos + button.tagEnd.length;\n							}\n						}\n						myField.focus();\n						myField.selectionStart = cursorPos;\n						myField.selectionEnd = cursorPos;\n						myField.scrollTop = scrollTop;\n					} else {\n						if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n							myField.value += button.tagStart;\n							rah_textile_bar_edAddTag(button);\n						} else {\n							myField.value += button.tagEnd;\n							rah_textile_bar_edRemoveTag(button);\n						}\n						myField.focus();\n					}\n				}\n				\n				/*\n					Insert content\n				*/\n			\n				function rah_textile_bar_edInsertContent(myField, myValue) {\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						sel.text = myValue;\n						myField.focus();\n					}\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						myField.value = myField.value.substring(0, startPos) + \n						myValue + myField.value.substring(endPos, \n						myField.value.length);\n						myField.focus();\n						myField.selectionStart = startPos + myValue.length;\n						myField.selectionEnd = startPos + myValue.length;\n					} else {\n						myField.value += myValue;\n						myField.focus();\n					}\n				}\n				\n				/*\n					Insert link\n				*/\n			\n				function rah_textile_bar_edInsertLink(myField) {\n					var myValue = prompt(\'URL:\', \'http://\');\n					var myText = prompt(\'Text:\', \'\');\n					var myTitle = prompt(\'Title:\', \'\');\n					var myRel = prompt(\'Rel:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						if(myRel) {\n							myValue = \'<a rel=\"\' + myRel + \'\" href=\"\' + myValue2 + \'\"\';\n							if(myTitle) {\n								myValue += \' title=\"\' + myTitle + \'\"\';\n							}\n							myValue += \'>\' + myText + \'</a>\';\n						} else {\n							myValue = \'\"\' + myText;\n							if(myTitle) {\n								myValue += \'(\' + myTitle + \')\';\n							}\n							myValue += \'\":\' + myValue2 + \' \';\n						}\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert acronym\n				*/\n			\n				function rah_textile_bar_edInsertAcronym(myField) {\n					var myValue = prompt(\'Acronym:\', \'\');\n					var myTitle = prompt(\'Comes from:\', \'\');\n					var myLanguage = prompt(\'Language:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						myValue = \'<acronym\';\n						if(myTitle) {\n							myValue += \' title=\"\' + myTitle + \'\"\';\n						}\n						if(myLanguage) {\n							myValue += \' lang=\"\' + myLanguage + \'\"\';\n						}\n						myValue += \'>\' + myValue2 + \'</acronym>\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert form\n				*/\n			\n				function rah_textile_bar_edInsertForm(myField) {\n					var myValue = prompt(\'Form:\', \'\');\n					if (myValue) {\n						myValue = \'<txp:output_form form=\"\' + myValue +\'\" />\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert image\n				*/\n			\n				function rah_textile_bar_edInsertImage(myField) {\n					var myValue = prompt(\'URL:\', \'/images/\');\n					var myTitle = prompt(\'Alt:\', \'\');\n					var myStyle = prompt(\'Style:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						myValue = \'!\';\n						if(myStyle) {\n							myValue += \'{\'+ myStyle +\'}\';\n						}\n						myValue += myValue2;\n						if(myTitle) {\n							myValue += \'(\' + myTitle + \')\';\n						}\n						myValue += \'!\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Adds button\n				*/\n			\n				function rah_textile_bar_edButton(id, display, tagStart, tagEnd, open) {\n					this.id = id;\n					this.display = display;\n					this.tagStart = tagStart;\n					this.tagEnd = tagEnd;\n					this.open = open;\n				}\n			\n				var rah_textile_bar_theButtons = new Array();\n				var rah_textile_bar_edOpenTags = new Array();\n				\n				{$buttons}\n				\n				$(document).ready(function() {\n					{$f}\n				});\n				\n				-->\n			</script>\n			<style type=\"text/css\">\n				.rah_textile_bar {\n					background: #fff;\n					border: 1px solid #ccc;\n					border-left: 0;\n					padding: 0;\n					overflow: hidden;\n					margin: 0 0 0 0;\n				}\n				.rah_textile_bar div {\n					display: inline;\n					width: auto;\n					border-left: 1px solid #ccc;\n					color: #333;\n					height: 23px;\n					width: 23px;\n					padding: 0;\n					cursor: pointer;\n					overflow: hidden;\n					outline: 0;\n					text-indent: -9000px;\n					float: left;\n					margin: 0 0 0 0;\n					background-color: #fff;\n					background-image: url(\"./?rah_textile_bar_img=image.gif\");\n					background-repeat: no-repeat;\n				}\n				.rah_textile_bar .strong {\n					background-position: 7px -182px;\n				}\n				.rah_textile_bar .link {\n					background-position: 4px -332px;\n				}\n				.rah_textile_bar .emphasis {\n					background-position: 7px -601px;\n				}\n				.rah_textile_bar .ins {\n					background-position: 7px -512px;\n				}\n				.rah_textile_bar .del {\n					background-position: 4px -540px;\n				}\n				.rah_textile_bar .h1 {\n					background-position: 5px -1px;\n				}\n				.rah_textile_bar .h2 {\n					background-position: 5px -32px;\n				}\n				.rah_textile_bar .h3 {\n					background-position: 5px -61px;\n				}\n				.rah_textile_bar .h4 {\n					background-position: 5px -91px;\n				}\n				.rah_textile_bar .h5 {\n					background-position: 5px -121px;\n				}\n				.rah_textile_bar .h6 {\n					background-position: 5px -151px;\n				}\n				.rah_textile_bar .image {\n					background-position: 5px -362px;\n				}\n				.rah_textile_bar .codeline {\n					background-position: 5px -392px;\n				}\n				.rah_textile_bar .ul{\n					background-position: 5px -302px;\n				}\n				.rah_textile_bar .ol{\n					background-position: 5px -271px;\n				}\n				.rah_textile_bar .sup {\n					background-position: 7px -212px;\n				}\n				.rah_textile_bar .sub {\n					background-position: 7px -242px;\n				}\n				.rah_textile_bar .bq {\n					background-position: 5px -452px;\n				}\n				.rah_textile_bar .bc {\n					background-position: 4px -482px;\n				}\n				.rah_textile_bar .output_form {\n					background-position: 5px -572px;\n				}\n				.rah_textile_bar .acronym {\n					background-position: 4px -422px;\n				}\n				.rah_textile_bar .active {\n					background-color: #ffc;\n				}\n			</style>\nEOF;\n	}\n\n/**\n	The image containing all the buttons graphs\n*/\n\n	function rah_textile_bar_img() {\n		\n		if(gps(\'rah_textile_bar_img\') != \'image.gif\')\n			return;\n		\n		ob_start();\n		ob_end_clean();\n		header(\'Content-type: image/gif\');\n		$code = \' \';\n		echo base64_decode($code);\n		exit();\n	}\n\n/**\n	Redirects to the preferences panel\n*/\n\n	function rah_textile_bar_prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_textile_bar_body\');\n		echo \n			\'<p id=\"message\">\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_textile_bar_body\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', '	##################\n	#\n	#	rah_textile_bar-plugin for Textpattern\n	#	version 0.7\n	#	by Jukka Svahn\n	#	http://rahforum.biz\n	#\n	#	Copyright (C) 2011 Jukka Svahn <http://rahforum.biz>\n	#	Licensed under GNU Genral Public License version 2\n	#	http://www.gnu.org/licenses/gpl-2.0.html\n	#\n	###################\n\n	if(@txpinterface == \'admin\') {\n		rah_textile_bar_install();\n		rah_textile_bar_img();\n		add_privs(\'plugin_prefs.rah_textile_bar\',\'1,2\');\n		register_callback(\'rah_textile_bar_prefs\',\'plugin_prefs.rah_textile_bar\');\n		register_callback(\'rah_textile_bar_install\',\'plugin_lifecycle.rah_textile_bar\');\n		register_callback(\'rah_textile_bar\',\'admin_side\',\'head_end\');\n	}\n\n/**\n	Installer\n	@param $event string Admin-side event.\n	@param $step string Admin-side event, plugin-lifecycle step.\n*/\n\n	function rah_textile_bar_install($event=\'\',$step=\'\') {\n		\n		if($step == \'deleted\') {\n			\n			safe_delete(\n				\'txp_prefs\',\n				\"name like \'rah_textile_bar_%\'\"\n			);\n			\n			return;\n		}\n		\n		global $prefs, $event, $textarray;\n		\n		if($event == \'prefs\') {\n			\n			/*\n				Generate language strings if\n				not existing\n			*/\n			\n			$strings = \n				array(\n					\'rah_txtbar\' => \'Textile Bar\',\n					\'rah_textile_bar_body\' => \'Attach to Body field\',\n					\'rah_textile_bar_excerpt\' => \'Attach to Excerpt field\'\n				);\n			\n			foreach(rah_textile_bar_buttons() as $att)\n				$strings[\'rah_textile_bar_\' . $att[0]] = \'Show \' . $att[0];\n			\n			foreach(\n				$strings as $string => $translation\n			)\n				if(!isset($textarray[$string]))\n					$textarray[$string] = $translation;\n		}\n		\n		$version = \'0.7\';\n		\n		$current = \n			isset($prefs[\'rah_textile_bar_version\']) ? \n				$prefs[\'rah_textile_bar_version\'] : \'\';\n		\n		if($current == $version)\n			return;\n			\n		$default = \n			array(\n				\'disable\' => \'\',\n				\'fields\' => \'\'\n			);\n		\n		if(!$current) {\n		\n			/*\n				Run migration and clean-up if older version was\n				installed\n			*/\n		\n			@$rs = \n				safe_rows(\n					\'name, value\',\n					\'rah_textile_bar\',\n					\'1=1\'\n				);\n		\n			if(!empty($rs) && is_array($rs)) {\n				foreach($rs as $a)\n					if(isset($default[$a[\'name\']]))\n						$default[$a[\'name\']] = $a[\'value\'];\n			\n				@safe_query(\n					\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_textile_bar\')\n				);\n			}\n		}\n		\n		/*\n			Add preference strings\n		*/\n		\n		$disabled = explode(\',\', $default[\'disable\']);\n		$position = 230;\n		$values = rah_textile_bar_buttons();\n		\n		foreach(array(\'body\',\'excerpt\') as $val)\n			$values[] = array($val);\n		\n		foreach($values as $att) {\n			\n			$val = \n				$att[0] == \'body\' || $att[0] == \'excerpt\' ? \n					(\n						strpos($default[\'fields\'], $att[0]) !== false || !$default[\'fields\'] ?\n							1 : 0\n					)\n				:\n					(\n						in_array(\'#textilebar .\'.$att[0], $disabled) ? \n							0 : 1\n					);\n			\n			if($att[0] == \'body\' || $att[0] == \'excerpt\')\n				$position = 225;\n			\n			$name = \'rah_textile_bar_\'.$att[0];\n			\n			if(!isset($prefs[$name])) {\n				safe_insert(\n					\'txp_prefs\',\n					\"prefs_id=1,\n					name=\'\".doSlash($name).\"\',\n					val=\'\".doSlash($val).\"\',\n					type=1,\n					event=\'rah_txtbar\',\n					html=\'yesnoradio\',\n					position=\".$position\n				);\n				\n				$prefs[$name] = $val;\n			}\n			\n			$position++;\n			\n		}\n		\n		set_pref(\'rah_textile_bar_version\',$version,\'rah_txtbar\',2,\'\',0);\n		$prefs[\'rah_textile_bar_version\'] = $version;\n	}\n\n/**\n	Lists buttons\n	@return mixed Array of buttons.\n*/\n	\n	function rah_textile_bar_buttons() {\n		$b[] = array(\'strong\',\'strong\',\'*\',\'*\');\n		$b[] = array(\'link\',\'url\',\'\',\'\');\n		$b[] = array(\'emphasis\',\'em\',\'_\',\'_\');\n		$b[] = array(\'ins\',\'ins\',\'+\',\'+\');\n		$b[] = array(\'del\',\'del\',\'-\',\'-\');\n		$b[] = array(\'h1\',\'h1\',\'h1. \',\'\');\n		$b[] = array(\'h2\',\'h2\',\'h2. \',\'\');\n		$b[] = array(\'h3\',\'h3\',\'h3. \',\'\');\n		$b[] = array(\'h4\',\'h4\',\'h4. \',\'\');\n		$b[] = array(\'h5\',\'h5\',\'h5. \',\'\');\n		$b[] = array(\'h6\',\'h6\',\'h6. \',\'\');\n		$b[] = array(\'image\',\'img\',\'\',\'\');\n		$b[] = array(\'codeline\',\'codeline\',\'@\',\'@\');\n		$b[] = array(\'ul\',\'ul\',\'* \',\'\\\\n\');\n		$b[] = array(\'ol\',\'ol\',\'# \',\'\\\\n\');\n		$b[] = array(\'sup\',\'sup\',\'^\',\'^\');\n		$b[] = array(\'sub\',\'sub\',\'~\',\'~\');\n		$b[] = array(\'bq\',\'bq\',\'bq. \',\'\\\\n\\\\n\');\n		$b[] = array(\'bc\',\'bc\',\'bc. \',\'\\\\n\\\\n\');\n		$b[] = array(\'acronym\',\'acronym\',\'\',\'\');\n		$b[] = array(\'output_form\',\'output_form\',\'\',\'\');\n		return $b;\n	}\n\n/**\n	All the required scripts and styles\n*/\n\n	function rah_textile_bar() {\n		global $event, $prefs;\n		\n		if($event != \'article\')\n			return;\n		\n		$buttons = array();\n		\n		foreach(rah_textile_bar_buttons()  as $att)\n			if(\n				isset($prefs[\'rah_textile_bar_\'.$att[0]]) &&\n				$prefs[\'rah_textile_bar_\'.$att[0]] == 1\n			)\n				$buttons[] = \n					\'				rah_textile_bar_theButtons[rah_textile_bar_theButtons.length] = new rah_textile_bar_edButton(\"\'.$att[0].\'\",\"\'.$att[1].\'\",\"\'.$att[2].\'\",\"\'.$att[3].\'\");\';\n			\n		if(!$buttons)\n			return;\n		\n		$buttons = trim(implode(n,$buttons));\n		$f = array();\n		$fields = array(\'body\',\'excerpt\');\n		\n		if(\n			isset($prefs[\'rah_textile_bar_additional_fields\']) &&\n			!empty($prefs[\'rah_textile_bar_additional_fields\'])\n		) {\n			foreach(explode(\',\',$prefs[\'rah_textile_bar_additional_fields\']) as $id)\n				$fields[] = $id;\n		}\n		\n		foreach($fields as $key => $field) \n			if(\n				isset($prefs[\'rah_textile_bar_\'.$field]) &&\n				$prefs[\'rah_textile_bar_\'.$field] == 1\n			)\n				$f[] = <<<EOF\n					$(\'textarea#{$field}\').before(\'<div class=\"rah_textile_bar\" id=\"rah_textile_bar_{$key}\"></div>\');\n					rah_textile_bar_addEvent(window, \'load\',\n						function() {\n							rah_textile_bar_initQuicktags(\'{$field}\',\'rah_textile_bar_{$key}\');\n						}\n					);\nEOF;\n\n\n		\n		if(!$f)\n			return;	\n		\n		$f = trim(implode(\'\',$f));\n		\n		echo <<<EOF\n			<script type=\"text/javascript\">\n				<!--\n\n				/*\n					Event listener\n				*/\n\n				function rah_textile_bar_addEvent(obj, evType, fn){\n					if (obj.addEventListener){\n						obj.addEventListener(evType, fn, true);\n						return true;\n					} else if (obj.attachEvent){\n						var r = obj.attachEvent(\"on\"+evType, fn);\n						return r;\n					} else {\n						return false;\n					}\n				}\n\n				/*\n					Init quicktags\n				*/\n\n				function rah_textile_bar_initQuicktags(identifier, textilebarid) {\n					var getCanvas = document.getElementsByTagName(\"textarea\");\n					for(var i = 0; i < getCanvas.length; i++) {\n						if(getCanvas[i].name == identifier  || getCanvas[i].id == identifier) {\n							var canvas = getCanvas[i];\n						}\n						if(canvas) {\n							var toolbar = document.getElementById(textilebarid);\n							toolbar.style.visibility = \"visible\";\n							var edButtons = new Array();\n							edButtons = rah_textile_bar_theButtons;\n							for (var i = 0; i < edButtons.length; i++) {\n								var thisButton = rah_textile_bar_edShowButton(edButtons[i], canvas);\n								toolbar.appendChild(thisButton);\n							}\n						}\n					}\n				}\n\n				/*\n					Spawn a button and it\'s actions\n				*/\n\n				function rah_textile_bar_edShowButton(button, edCanvas) {\n					var theButton = document.createElement(\"div\");\n					theButton.id = button.id;\n					theButton.title = button.id;\n					theButton.className = \'textilebutton\';\n					theButton.className += \' \' + button.id;\n					theButton.tagStart = button.tagStart;\n					theButton.tagEnd = button.tagEnd;\n					theButton.open = button.open;\n					if (button.id == \'image\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertImage(edCanvas); }\n					} else if (button.id == \'link\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertLink(edCanvas);}\n					} else if (button.id == \'output_form\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertForm(edCanvas);}\n					} else if (button.id == \'acronym\') {\n						theButton.onclick = function() { rah_textile_bar_edInsertAcronym(edCanvas);}\n					} else {\n						theButton.onclick = function() { rah_textile_bar_edInsertTag(edCanvas,this); }\n					}\n					theButton.innerHTML = (button.display) + \"\";\n					return theButton;\n				}\n\n				/*\n					Add tag, active button\n				*/\n\n				function rah_textile_bar_edAddTag(button) {\n					if(button.tagEnd != \'\') {\n						rah_textile_bar_edOpenTags[rah_textile_bar_edOpenTags.length] = button;\n						button.innerHTML = \'/\' + button.innerHTML;\n						button.className = button.className.replace(\"textilebutton\", \"active\");\n					}\n				}\n\n				/*\n					Close tag, remove active state\n				*/\n\n				function rah_textile_bar_edRemoveTag(button) {\n					for(i = 0; i < rah_textile_bar_edOpenTags.length; i++) {\n						if(rah_textile_bar_edOpenTags[i] == button) {\n							rah_textile_bar_edOpenTags.splice(button, 1);\n							button.innerHTML = button.innerHTML.replace(\'/\', \'\');\n							button.className = button.className.replace(\"active\", \"textilebutton\");\n						}\n					}\n				}\n\n				/*\n					Check for open tags\n				*/\n\n				function rah_textile_bar_edCheckOpenTags(button) {\n					var tag = 0;\n					for(i = 0; i < rah_textile_bar_edOpenTags.length; i++) {\n						if(rah_textile_bar_edOpenTags[i] == button) {\n							tag++;\n						}\n					}\n					if(tag > 0) {\n						return true;\n					} else {\n						return false;\n					}\n				}\n\n				/*\n					Close all tags\n				*/\n\n				function rah_textile_bar_edCloseAllTags(edCanvas) {\n					var count = rah_textile_bar_edOpenTags.length;\n					for(o = 0; o < count; o++) {\n						rah_textile_bar_edInsertTag(edCanvas, rah_textile_bar_edOpenTags[rah_textile_bar_edOpenTags.length - 1]);\n					}\n				}\n\n				/*\n					Insert tag\n				*/\n\n				function rah_textile_bar_edInsertTag(myField, button) {\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						if (sel.text.length > 0) {\n							sel.text = button.tagStart + sel.text + button.tagEnd;\n						} else {\n							if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n								sel.text = button.tagStart;\n								rah_textile_bar_edAddTag(button);\n							} else {\n								sel.text = button.tagEnd;\n								rah_textile_bar_edRemoveTag(button);\n							}\n						}\n						myField.focus();\n					} else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						var cursorPos = endPos;\n						var scrollTop = myField.scrollTop;\n						if (startPos != endPos) {\n							myField.value = myField.value.substring(0, startPos) + button.tagStart + myField.value.substring(startPos, endPos) + button.tagEnd + myField.value.substring(endPos, myField.value.length);\n							cursorPos += button.tagStart.length + button.tagEnd.length;\n						}else {\n							if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n								myField.value = myField.value.substring(0, startPos) + button.tagStart + myField.value.substring(endPos, myField.value.length);\n								rah_textile_bar_edAddTag(button);\n								cursorPos = startPos + button.tagStart.length;\n							} else {\n								myField.value = myField.value.substring(0, startPos)+ button.tagEnd + myField.value.substring(endPos, myField.value.length);\n								rah_textile_bar_edRemoveTag(button);\n								cursorPos = startPos + button.tagEnd.length;\n							}\n						}\n						myField.focus();\n						myField.selectionStart = cursorPos;\n						myField.selectionEnd = cursorPos;\n						myField.scrollTop = scrollTop;\n					} else {\n						if (!rah_textile_bar_edCheckOpenTags(button) || button.tagEnd == \'\') {\n							myField.value += button.tagStart;\n							rah_textile_bar_edAddTag(button);\n						} else {\n							myField.value += button.tagEnd;\n							rah_textile_bar_edRemoveTag(button);\n						}\n						myField.focus();\n					}\n				}\n				\n				/*\n					Insert content\n				*/\n			\n				function rah_textile_bar_edInsertContent(myField, myValue) {\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						sel.text = myValue;\n						myField.focus();\n					}\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						myField.value = myField.value.substring(0, startPos) + \n						myValue + myField.value.substring(endPos, \n						myField.value.length);\n						myField.focus();\n						myField.selectionStart = startPos + myValue.length;\n						myField.selectionEnd = startPos + myValue.length;\n					} else {\n						myField.value += myValue;\n						myField.focus();\n					}\n				}\n				\n				/*\n					Insert link\n				*/\n			\n				function rah_textile_bar_edInsertLink(myField) {\n					var myValue = prompt(\'URL:\', \'http://\');\n					var myText = prompt(\'Text:\', \'\');\n					var myTitle = prompt(\'Title:\', \'\');\n					var myRel = prompt(\'Rel:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						if(myRel) {\n							myValue = \'<a rel=\"\' + myRel + \'\" href=\"\' + myValue2 + \'\"\';\n							if(myTitle) {\n								myValue += \' title=\"\' + myTitle + \'\"\';\n							}\n							myValue += \'>\' + myText + \'</a>\';\n						} else {\n							myValue = \'\"\' + myText;\n							if(myTitle) {\n								myValue += \'(\' + myTitle + \')\';\n							}\n							myValue += \'\":\' + myValue2 + \' \';\n						}\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert acronym\n				*/\n			\n				function rah_textile_bar_edInsertAcronym(myField) {\n					var myValue = prompt(\'Acronym:\', \'\');\n					var myTitle = prompt(\'Comes from:\', \'\');\n					var myLanguage = prompt(\'Language:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						myValue = \'<acronym\';\n						if(myTitle) {\n							myValue += \' title=\"\' + myTitle + \'\"\';\n						}\n						if(myLanguage) {\n							myValue += \' lang=\"\' + myLanguage + \'\"\';\n						}\n						myValue += \'>\' + myValue2 + \'</acronym>\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert form\n				*/\n			\n				function rah_textile_bar_edInsertForm(myField) {\n					var myValue = prompt(\'Form:\', \'\');\n					if (myValue) {\n						myValue = \'<txp:output_form form=\"\' + myValue +\'\" />\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Insert image\n				*/\n			\n				function rah_textile_bar_edInsertImage(myField) {\n					var myValue = prompt(\'URL:\', \'/images/\');\n					var myTitle = prompt(\'Alt:\', \'\');\n					var myStyle = prompt(\'Style:\', \'\');\n					var myValue2 = myValue;\n					if (myValue) {\n						myValue = \'!\';\n						if(myStyle) {\n							myValue += \'{\'+ myStyle +\'}\';\n						}\n						myValue += myValue2;\n						if(myTitle) {\n							myValue += \'(\' + myTitle + \')\';\n						}\n						myValue += \'!\';\n						rah_textile_bar_edInsertContent(myField, myValue);\n					}\n				}\n				\n				/*\n					Adds button\n				*/\n			\n				function rah_textile_bar_edButton(id, display, tagStart, tagEnd, open) {\n					this.id = id;\n					this.display = display;\n					this.tagStart = tagStart;\n					this.tagEnd = tagEnd;\n					this.open = open;\n				}\n			\n				var rah_textile_bar_theButtons = new Array();\n				var rah_textile_bar_edOpenTags = new Array();\n				\n				{$buttons}\n				\n				$(document).ready(function() {\n					{$f}\n				});\n				\n				-->\n			</script>\n			<style type=\"text/css\">\n				.rah_textile_bar {\n					background: #fff;\n					border: 1px solid #ccc;\n					border-left: 0;\n					padding: 0;\n					overflow: hidden;\n					margin: 0 0 0 0;\n				}\n				.rah_textile_bar div {\n					display: inline;\n					width: auto;\n					border-left: 1px solid #ccc;\n					color: #333;\n					height: 23px;\n					width: 23px;\n					padding: 0;\n					cursor: pointer;\n					overflow: hidden;\n					outline: 0;\n					text-indent: -9000px;\n					float: left;\n					margin: 0 0 0 0;\n					background-color: #fff;\n					background-image: url(\"./?rah_textile_bar_img=image.gif\");\n					background-repeat: no-repeat;\n				}\n				.rah_textile_bar .strong {\n					background-position: 7px -182px;\n				}\n				.rah_textile_bar .link {\n					background-position: 4px -332px;\n				}\n				.rah_textile_bar .emphasis {\n					background-position: 7px -601px;\n				}\n				.rah_textile_bar .ins {\n					background-position: 7px -512px;\n				}\n				.rah_textile_bar .del {\n					background-position: 4px -540px;\n				}\n				.rah_textile_bar .h1 {\n					background-position: 5px -1px;\n				}\n				.rah_textile_bar .h2 {\n					background-position: 5px -32px;\n				}\n				.rah_textile_bar .h3 {\n					background-position: 5px -61px;\n				}\n				.rah_textile_bar .h4 {\n					background-position: 5px -91px;\n				}\n				.rah_textile_bar .h5 {\n					background-position: 5px -121px;\n				}\n				.rah_textile_bar .h6 {\n					background-position: 5px -151px;\n				}\n				.rah_textile_bar .image {\n					background-position: 5px -362px;\n				}\n				.rah_textile_bar .codeline {\n					background-position: 5px -392px;\n				}\n				.rah_textile_bar .ul{\n					background-position: 5px -302px;\n				}\n				.rah_textile_bar .ol{\n					background-position: 5px -271px;\n				}\n				.rah_textile_bar .sup {\n					background-position: 7px -212px;\n				}\n				.rah_textile_bar .sub {\n					background-position: 7px -242px;\n				}\n				.rah_textile_bar .bq {\n					background-position: 5px -452px;\n				}\n				.rah_textile_bar .bc {\n					background-position: 4px -482px;\n				}\n				.rah_textile_bar .output_form {\n					background-position: 5px -572px;\n				}\n				.rah_textile_bar .acronym {\n					background-position: 4px -422px;\n				}\n				.rah_textile_bar .active {\n					background-color: #ffc;\n				}\n			</style>\nEOF;\n	}\n\n/**\n	The image containing all the buttons graphs\n*/\n\n	function rah_textile_bar_img() {\n		\n		if(gps(\'rah_textile_bar_img\') != \'image.gif\')\n			return;\n		\n		ob_start();\n		ob_end_clean();\n		header(\'Content-type: image/gif\');\n		$code = \' \';\n		echo base64_decode($code);\n		exit();\n	}\n\n/**\n	Redirects to the preferences panel\n*/\n\n	function rah_textile_bar_prefs() {\n		header(\'Location: ?event=prefs&step=advanced_prefs#prefs-rah_textile_bar_body\');\n		echo \n			\'<p id=\"message\">\'.n.\n			\'	<a href=\"?event=prefs&amp;step=advanced_prefs#prefs-rah_textile_bar_body\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}', 'b20bd1b966a1633027b69fbba3002f06', '', '', 3, 1, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rah_unlog_me', 0, 'Jukka Svahn', 'http://rahforum.biz', '1.4.1', 'Exclude IPs from visitor logs', '<h1>rah_unlog_me</h1>\n\n<p>Exclude set of IP addresses from <a href=\"http://textpattern.com/\" rel=\"nofollow\">Textpattern&#8217;s</a> visitor logs. Can automatically filter out your own hits, site authors and hits made by nasty bots. The plugin adds easy-to-use logging options to Textpattern&#8217;s Preferences panel.</p>\n\n<h2>List of features</h2>\n\n<ul>\n	<li>Exclude list of IPs from visitor logs.</li>\n	<li>Automatically strip site editors’ visits from logs.</li>\n	<li>Integrates well with Textpattern&#8217;s native user-interface.</li>\n</ul>\n\n<h2>Requirements</h2>\n\n<p>Rah_unlog_me&#8217;s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.2.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5 or newer.</li>\n</ul>\n\n<h2>Installing</h2>\n\n<p>Rah_unlog_me&#8217;s installation follows the standard plugin installation steps.</p>\n\n<ol>\n	<li>Download the plugin installation code.</li>\n	<li>Copy and paste the installation code into the <em>Install plugin</em> box of your Textpattern Plugin pane.</li>\n	<li>Run the automated setup.</li>\n	<li>After the setup is done, activate the plugin. Done.</li>\n</ol>\n\n<h2>Configuring</h2>\n\n<p>After installation, you can find rah_unlog_me&#8217;s settings from Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Basic_Preferences\" rel=\"nofollow\">Preferences panel</a>, next to other logging related options.</p>\n\n<h2>Preferences</h2>\n\n<p>Rah_unlog_me will add couple new settings to Textpattern&#8217;s <a href=\"http://textpattern.net/wiki/index.php?title=Basic_Preferences\" rel=\"nofollow\">Preferences panel</a>. These include an option to automatically exclude admins from logs and a field to define a list of additional IPs to filter out. The settings are as follows.</p>\n\n<h3>Exclude site authors’ IPs from the logs?</h3>\n\n<p>If set to <em>Yes</em>, site admins’ IP addresses are removed from the visitor logs when the users logs in to Textpattern control panel.</p>\n\n<h3>Additional IPs to exclude from the logs (comma-separated)</h3>\n\n<p>The second option of rah_unlog_me is a field where you can define a comma-separated (<code>,</code>) list of IPs which will be excluded from the logs.</p>\n\n<p>This option supports wildcards. <code>%</code> matches any number of characters, and <code>_</code> matches exactly one character. With the wildcards it&#8217;s easy to exclude whole address blocks. Following would exclude local devices from the visitor log:</p>\n\n<pre><code>::_%, fc00::_%, 10._%, 127._%, 172.16._%, 192.168.0._%</code></pre>\n\n<h2>Changelog</h2>\n\n<h3>Version 1.4.1 &#8211; 2012/09/10</h3>\n\n<ul>\n	<li>Improved: Cleaned up the installer and the migration script.</li>\n</ul>\n\n<h3>Version 1.4 &#8211; 2012/07/11</h3>\n\n<ul>\n	<li>Changed: Moved embedded language strings to Textpacks.</li>\n	<li>Added: Finnish translation.</li>\n	<li>Added: Excluded IP list supports wildcard. <code>%</code> matches zero or more characters and <code>_</code> exactly one character.</li>\n	<li>Now requires PHP5 or newer.</li>\n</ul>\n\n<h3>Version 1.3 &#8211; 2011/12/06</h3>\n\n<ul>\n	<li>Changed: Use the exact same method as Textpattern to check logged in user&#8217;s IP. Just for consistency, doesn&#8217;t really affect anything.</li>\n	<li>Removed: JavaScript on Preferences panel. Causes more issues than adds. Not everyone wants everyone to navigate to the plugin&#8217;s pane.</li>\n</ul>\n\n<h3>Version 1.2 &#8211; 2011/06/03</h3>\n\n<ul>\n	<li>Imrpoved: Don&#8217;t throw up JavaScript to top of Preferences pane when JavaScript is disabled.</li>\n	<li>Improved: Sweep pre-defined list of IPs with a single query.</li>\n</ul>\n\n<h3>Version 1.1 &#8211; 2011/03/14</h3>\n\n<ul>\n	<li>Fixed: Typo in the uninstaller. Called step from “delete” to “deleted”.</li>\n	<li>Added: Forgotten flags.</li>\n	<li>Added: Missing privileges for the plugins pane&#8217;s options link.</li>\n	<li>Fixed: Typo in docs. Changed required <span class=\"caps\">TXP</span> version from 4.0.7 to 4.2.0.</li>\n</ul>\n\n<h3>Version 1.0 &#8211; 2011/03/13</h3>\n\n<ul>\n	<li>Integrated plugin&#8217;s preferences to Textpattern&#8217;s native preferences pane.</li>\n	<li>Better excluding/removing script. No longer searches IPs from the logs if it doesn&#8217;t need to; takes no action when logs are off, and removes the additional IPs from logs only when accessing the logs pane.</li>\n	<li>Language string support.</li>\n	<li>Now requires <span class=\"caps\">TXP</span> 4.2.0 for full feature support.</li>\n</ul>\n\n<h3>Version 0.1 &#8211; 2009/05/24</h3>\n\n<ul>\n	<li>First release</li>\n</ul>', '\n\n/**\n * Rah_unlog_me plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2009-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_unlog_me\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_unlog_me();\n\nclass rah_unlog_me {\n	\n	static public $version = \'1.4.1\';\n\n	/**\n	 * Constructor\n	 */\n\n	public function __construct() {\n		add_privs(\'plugin_prefs.rah_unlog_me\', \'1,2\');\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		$this->clean();\n	}\n\n	/**\n	 * Does installing and uninstalling.\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side, plugin-lifecycle step.	\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			safe_delete(\n				\'txp_prefs\',\n				\"name LIKE \'rah\\_unlog\\_me\\_%\'\"\n			);\n			return;\n		}\n\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$opt = \n			array(\n				\'auto\' => array(\'yesnoradio\', 1),\n				\'ip\' => array(\'text_input\', \'\'),\n			);\n		\n		if(in_array(PFX.\'rah_unlog_me\', getThings(\'SHOW TABLES\'))) {\n			$rs = safe_rows(\'*\', \'rah_unlog_me\', \'1=1\');\n\n			foreach($rs as $a) {\n				if(!isset($opt[$a[\'name\']])) {\n					continue;\n				}\n				\n				if($a[\'name\'] == \'auto\') {\n					$a[\'value\'] = $a[\'value\'] == \'no\' ? 0 : 1;\n				}\n				\n				$opt[$a[\'name\']][1] = $a[\'value\'];\n			}\n\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_unlog_me\'));\n		}\n		\n		$position = 221;\n		\n		foreach($opt as $name => $val) {\n			$n = __CLASS__.\'_\'.$name;\n			\n			if(!isset($prefs[$n])) {\n				set_pref($n, $val[1], \'publish\', PREF_BASIC, $val[0], $position);\n				$prefs[$n] = $val[1];\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n\n	/**\n	 * Removes IPs from the logs\n	 */\n\n	public function clean() {\n		global $logging, $event;\n		\n		if($logging == \'none\') {\n			return;\n		}\n		\n		if(get_pref(\'rah_unlog_me_auto\')) {\n			safe_delete(\n				\'txp_log\',\n				\"ip=\'\".doSlash(remote_addr()).\"\'\"\n			);\n		}\n		\n		if($event != \'log\' || !trim(get_pref(\'rah_unlog_me_ip\'))) {\n			return;\n		}\n		\n		$ips = quote_list(do_list(get_pref(\'rah_unlog_me_ip\')));\n		\n		safe_delete(\n			\'txp_log\',\n			\'ip LIKE \'.implode(\' OR ip LIKE \', $ips)\n		);\n	}\n\n	/**\n	 * Redirects to the preferences panel\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs#prefs-logging\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs#prefs-logging\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n', '\n\n/**\n * Rah_unlog_me plugin for Textpattern CMS\n *\n * @author Jukka Svahn\n * @date 2009-\n * @license GNU GPLv2\n * @link http://rahforum.biz/plugins/rah_unlog_me\n *\n * Copyright (C) 2012 Jukka Svahn <http://rahforum.biz>\n * Licensed under GNU Genral Public License version 2\n * http://www.gnu.org/licenses/gpl-2.0.html\n */\n\n	new rah_unlog_me();\n\nclass rah_unlog_me {\n	\n	static public $version = \'1.4.1\';\n\n	/**\n	 * Constructor\n	 */\n\n	public function __construct() {\n		add_privs(\'plugin_prefs.rah_unlog_me\', \'1,2\');\n		register_callback(array($this, \'prefs\'), \'plugin_prefs.\'.__CLASS__);\n		register_callback(array(__CLASS__, \'install\'), \'plugin_lifecycle.\'.__CLASS__);\n		$this->clean();\n	}\n\n	/**\n	 * Does installing and uninstalling.\n	 * @param string $event The admin-side event.\n	 * @param string $step The admin-side, plugin-lifecycle step.	\n	 */\n\n	static public function install($event=\'\', $step=\'\') {\n		\n		global $prefs;\n		\n		if($step == \'deleted\') {\n			safe_delete(\n				\'txp_prefs\',\n				\"name LIKE \'rah\\_unlog\\_me\\_%\'\"\n			);\n			return;\n		}\n\n		if((string) get_pref(__CLASS__.\'_version\') === self::$version) {\n			return;\n		}\n		\n		$opt = \n			array(\n				\'auto\' => array(\'yesnoradio\', 1),\n				\'ip\' => array(\'text_input\', \'\'),\n			);\n		\n		if(in_array(PFX.\'rah_unlog_me\', getThings(\'SHOW TABLES\'))) {\n			$rs = safe_rows(\'*\', \'rah_unlog_me\', \'1=1\');\n\n			foreach($rs as $a) {\n				if(!isset($opt[$a[\'name\']])) {\n					continue;\n				}\n				\n				if($a[\'name\'] == \'auto\') {\n					$a[\'value\'] = $a[\'value\'] == \'no\' ? 0 : 1;\n				}\n				\n				$opt[$a[\'name\']][1] = $a[\'value\'];\n			}\n\n			@safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'rah_unlog_me\'));\n		}\n		\n		$position = 221;\n		\n		foreach($opt as $name => $val) {\n			$n = __CLASS__.\'_\'.$name;\n			\n			if(!isset($prefs[$n])) {\n				set_pref($n, $val[1], \'publish\', PREF_BASIC, $val[0], $position);\n				$prefs[$n] = $val[1];\n			}\n			\n			$position++;\n		}\n		\n		set_pref(__CLASS__.\'_version\', self::$version, __CLASS__, 2, \'\', 0);\n		$prefs[__CLASS__.\'_version\'] = self::$version;\n	}\n\n	/**\n	 * Removes IPs from the logs\n	 */\n\n	public function clean() {\n		global $logging, $event;\n		\n		if($logging == \'none\') {\n			return;\n		}\n		\n		if(get_pref(\'rah_unlog_me_auto\')) {\n			safe_delete(\n				\'txp_log\',\n				\"ip=\'\".doSlash(remote_addr()).\"\'\"\n			);\n		}\n		\n		if($event != \'log\' || !trim(get_pref(\'rah_unlog_me_ip\'))) {\n			return;\n		}\n		\n		$ips = quote_list(do_list(get_pref(\'rah_unlog_me_ip\')));\n		\n		safe_delete(\n			\'txp_log\',\n			\'ip LIKE \'.implode(\' OR ip LIKE \', $ips)\n		);\n	}\n\n	/**\n	 * Redirects to the preferences panel\n	 */\n\n	public function prefs() {\n		header(\'Location: ?event=prefs#prefs-logging\');\n		echo \n			\'<p>\'.n.\n			\'	<a href=\"?event=prefs#prefs-logging\">\'.gTxt(\'continue\').\'</a>\'.n.\n			\'</p>\';\n	}\n}\n\n', 'a8cac5242bb222c6ef37c8a112b47fd1', '#@admin\n#Default language, en-us, en-gb\nrah_unlog_me_auto => Exclude site authors\' IPs from the logs?\nrah_unlog_me_ip =>  Additional IPs to exclude from the logs (comma-separated)\n#@admin\n#@language fi-fi\nrah_unlog_me_auto => Poislue lokeista käyttäjien IP-osoitteet?\nrah_unlog_me_ip => Poislue lokeista IP-osoitteiteet (pilkuin eroteltuna)', '', 3, 5, 3),
('pat_body_tweets', 0, 'Patrick LEFEVRE', 'http://twitter.com/lowel', '0.3.4', 'Add Twitter links into body text like Medium website.', '<h1>pat_body_tweets v 0.3.4</h1>\n<p>Add Twitter links into your text content like the comment system into the <a href=\"http://medium.com\" target=\"_blank\">Medium\'s website</a> (optional). Allow visitors to Tweet parts of text content on mouse selection as shown on <a href=\"http://next.theguardian.com\" target=\"_blank\">The (New) Guardian website</a> (optional). Note: These two tags are independents.</p>\n<h1>Add an icon for Twitter in front of each text parts (generate an individual anchor into each Tweets)</h1>\n<p><b>All followed tags are intended to be used as single ones, not container ones</b>.</p>\n<h2>Usage (all browsers capable)</h2>\n<p><code>&lt;txp:pat_body_tweets tag=\"\" full=\"\" sign=\"\" tooltip=\"\" class=\"\" content=\"\" /&gt;</code></p>\n<h2>Attributes</h2>\n<ul>\n<li><code>tag</code> string (optional): Choose the HTML tag in front of which to display the tweet link. Allowed tags: <b>p</b>, <b>li</b> and all heading levels <b>h</b>. Default: <b>p</b>.</li>\n<li><code>full</code> Boolean (optional): Choose if you want to display the text into the tweets (respect the 140 characters limit by Twitter). Default: <b>1</b> (true = yes).</li>\n<li><code>sign</code> string (optional): The sign to include into Twitter links. Default: <b>+</b>.</li>\n<li><code>tooltip</code> string (optional): Tooltip for the Twitter link. Default: none (empty).</li>\n<li><code>class</code> string (optional): Class attribute for the link markup. Default: <b>notes-marker</b>.</li>\n<li><code>content</code> string (optional): Available to include a TXP tag in replacement of the default body content.</li>\n</ul>\n<h2>CSS styling</h2>\n<p>With default class attribute values, here is a sample how to style your links for each your content paragraphs:</p>\n<p><pre><code>/* Container */\n.notes-marker {\n	visibility: hidden;\n	display: inline-block;\n	width: 1em;\n	height: 1em;\n	margin-left: .2em;\n	vertical-align: middle;\n	/* Aspect for the \"+\" sign */\n	font: bold 1.3em/1 Arial,sans-serif;\n	cursor: pointer\n}\n\n.notes-marker a,\n .notes-marker-icon a:hover {\n	text-decoration: none;\n	/* Same color as SVG icon */\n	color: #66cd00\n}\n\n.notes-marker a {\n	display: block;\n	width: 1em;\n	height: 1em;\n	padding: 0;\n	margin: 0;\n	vertical-align: middle;\n	/* Color image is included into this SVG. To change it, use a Base64 decoder (http://www.base64encode.org) */\n	background:url(data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGc+PC9nPjxwYXRoIGQ9Ik0yNS42IDMuMmgtMTkuMmMtMS43NiAwLTMuMiAxLjQ0LTMuMiAzLjJ2MTkuMmMwIDEuNzYgMS40NCAzLjIgMy4yIDMuMmgxOS4yYzEuNzYgMCAzLjItMS40NCAzLjItMy4ydi0xOS4yYzAtMS43Ni0xLjQ0LTMuMi0zLjItMy4yek0yNCAxNy42aC02LjR2Ni40aC0zLjJ2LTYuNGgtNi40di0zLjJoNi40di02LjRoMy4ydjYuNGg2LjR2My4yeiIgZmlsbD0iIzY2Q0QwMCIgLz48L3N2Zz4=) 0 0 no-repeat;\n	background-size:contain;\n	/* No image for IE6/7 */\n	*background: none;\n	/* SEO friendly indenting */\n	text-indent: 100%;\n	white-space: nowrap;\n	overflow: hidden;\n	/* No text-indent: the \"+\" sign is shown in IE6/7 instead */\n	*text-indent: 0%\n}\n\n.notes-marker:after {\n	position: relative;\n	top: .22em;\n	content: \"\";\n	width: 0;\n	height: 0;\n	margin: 0 0 0 .1em;\n	border-top: .35em solid #66cd00;\n	border-left: 0 solid transparent;\n	border-right: .5em solid transparent\n}\n\n/* Highlight results on over with effects */\n.notes {-webkit-transition: background-color 0.5s ease-in-out;transition: background-color 0.5s ease-in-out}\n\n/* ...In light yellow */\n.notes:hover {background-color: #fffff4}\n\n/* Show the links */\n.notes:hover .notes-marker {visibility: visible}\n</code></pre></p>\n<h1>Allow your visitors to Tweet parts of your text content on mouse selection (do not generate any anchors, just article URL into the Tweets)</h1>\n<h2>Usage (IE9+ &amp; all modern browsers capable)</h2>\n<p>Place this code into your page template just before the last <code>&lt;/body&gt;</code> HTML tag:</p>\n<p><code>&lt;txp:pat_body_tweets_live id=\"\" account=\"\" label=\"\" popup=\"\" info=\"\" tooltip=\"\" position=\"\" top=\"\" right=\"\" bottom=\"\" color=\"\" style=\"\" /&gt;</code></p>\n<h2>Attributes</h2>\n<ul>\n<li><code>id</code> (string) required. HTML attribute ID of your page template that wrap your main content text where mouse select must to be applied ( i.e.: \"wrapper\"). Default: <b>wrapper</b> (equal <code>&lt;div id=\"wrapper\"&gt;</code>).</li>\n<li><code>account</code> (string) required. The Twitter account (without the @ sign) shown in all tweets as a prefix (i.e.: <b>via @me</b>). Default: <b>none</b> (empty).</li>\n<li><code>label</code> (string) optional. The text label for the Twitter icon. Default: <b>Tweet it!</b>.</li>\n<li><code>popup</code> (boolean) optional. Choose how to open the Twitter window, popup style <em>or</em> new tab style (prevent browser\'s popup blocking). Default: new tab (0 = no).</li>\n<li><code>info</code> (boolean) optional. Choose if you want to display a Twitter icon in front of your container (see \"id\" attribute) in order to inform your visitors they can use the feature. Default: <b>none</b> (0 = no).</li>\n<li><code>tooltip</code> (string) optional. The text for the Twitter icon tooltip. Default: \" You can Tweet this text on mouse selection \".</li>\n<li><code>position</code> (boolean) optional. Choose the position of the Twitter icon <em>before</em> or <em>after</em> the container (see \"id\" and \"info\" attributes). Default: <b>false</b> (0 = after the container).</li>\n<li><code>top</code> (string) optional. The top position for the Twitter icon. Only available when the \"position\" attribute is set to 1. Default: 0.</li>\n<li><code>right</code> (string) optional. The CSS value right position for the Twitter icon (i.e.: right=\"2%\"). Default: 0.</li>\n<li><code>bottom</code> (string) optional. The CSS value bottom position for the Twitter icon (i.e.: bottom=\"2em\"). Only available when the \"position\" attribute is set to 0. Default: 0.</li>\n<li><code>color</code> (string) optional. The background color for the Twitter icon. Default: #00aced <span class=\"colored\"></span>.</li>\n<li><code>style</code> (string) optional. Allow you to add custom CSS style rules. Default: none (empty).</li>\n</ul>\n<h2>Style Rules to add into your CSS styles file (required)</h2>\n<p>Here is the CSS rules to render the Twitter icon:</p>\n<p><pre><code>\n#share {\n	position: absolute;\n	top: -9999px;\n	left: -9999px;\n	z-index: 9999;\n	width: 45px;\n	height: 30px;\n	padding: 0;\n	background-color: #00abf1;\n	border: 1px solid #00abf1;\n	box-shadow: 0 2px 5px rgba(0,0,0,.4)\n}\n#share span {\n	display: block;\n	position: absolute;\n	top: -6px;\n	left: 50%;\n	width: 0;\n	height: 0;\n	margin-left: -14px;\n	border-right: 14px solid transparent;\n	border-left: 14px solid transparent;\n	border-bottom: 7px solid #00abf1\n}\n#share button {\n	width: 47px;\n	height: 30px;\n	background: #00abf1 url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABiklEQVRIx2NgWP3xP03xqAU0sUB446f/JRe//597/9f/pms//itv/wyXE9v0CbcFroe+/vc7+hWv4Xq7vvx/+ePff2Tw/c///+3Xf/zf8eL3/5zz33Fb0HPz5/+ff///jzr5DavhjEB85ePf/7jA6Xd//lde/vFfYMMn7Bb03foJV7zwwa//EptRvauz8/N/fODL73//3Q59xe0Dj8NfUTR8A3p9HjCcPYHiIFdZ7/uC14LySz/wRzIo8lY8+oXTgM+//+G1IP3sd/wWdAPj4NHXv//JBRjBg26B6Z4v//+RafgfoEaUyMWVD0DhSI4lu4BJlKiMBoqHjhs/CIY3OvA58pU4C1jX4k/r2MCBV39IKyrktn7+f+LtH6IMf/fz33/FbZ/JK4tAOfrrn394k63N/i/EFXbswKARAoY/yDXBx76B8wMes/9f//QXXC4RXZoyrfn4P/H0t/9XCYT/Q2A+KQaWpBzrKCiudXd9/p957vv/XmDGm3n3J5gGlZKgfAJyyGiNNrQsAACr8MiQdBkRCwAAAABJRU5ErkJggg==\") center 2px no-repeat;\n	border: none;\n	color: #fff;\n	cursor: pointer;\n	border-radius: 0;\n	box-shadow: none\n}\n#share button b {\n	visibility: hidden;\n	display: block;\n	width: 100%;\n	text-align: center\n}\n</code></pre></p>\n<h2>Troubles</h2>\n<p>You may choose the right HTML node element to make mouse selection working. Otherwise, nothing works. For example, into the default \"archive\" TXP template, choose <code>&lt;main&gt;</code> tag and add <code>id=\"wrapper\"</code>.</p>\n<h2>Credits</h2>\n<p>Part of javascript limitation node code by <a href=\"http://www.timdown.co.uk\" target=\"_blank\">Tim Down</a>.</p>\n<h2>Changelog</h2>\n<ul>\n<li>10th February 2015: v 0.3.4. Attribute \"style\" added.</li>\n<li>30th December 2014: v 0.3.3. Attribute \"popup\" added.</li>\n<li>29th December 2014: v 0.3.2. Attributes \"info\", \"position\", \"top\", \"right\", \"bottom\", \"color\" added.</li>\n<li>26th December 2014: v 0.3.1. Better support for special characters on mouse selection.</li>\n<li>14 November 2014: v 0.3. attribute \"id\" added in order to limit the HTML document where to apply mouse selection.</li>\n<li>12 November 2014: v 0.2. add sharing content parts on text highlighting (on mouse select).</li>\n<li>25th March 2014: v 0.1.6. solves all characters entities conversion.</li>\n<li>24th March 2014: v 0.1.5. better Twitter URLs shortener support.</li>\n<li>24th February 2014: v 0.1.4. introduce content attribute.</li>\n<li>23rd February 2014: v 0.1.3. prevent non allowed attribute.</li>\n<li>21st February 2014: v 0.1.2. full attribute added.</li>\n<li>19th February 2014: v 0.1.1. No more inline SVG, CSS background property is in use now. CSS rewriting accordingly. sign attribute added. Public realize.</li>\n<li>18th February 2014: v 0.1.0. initial commit, confidential distribution.</li>\n</ul>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">.colored{display:inline-block;width:1em;height:1em;background:#00aced;vertical-align:text-bottom}</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*\r\n * pat_body_tweets\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-body-tweets.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.3.4\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * Generate additional markup into text\r\n *\r\n * @param  array    Tag attributes\r\n * @return string   HTML tags with an individual link\r\n */\r\nfunction pat_body_tweets($atts)\r\n{\r\n\r\n	global $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'tag\'		=> \'p\',\r\n		\'full\'		=> true,\r\n		\'sign\'		=> \'+\',\r\n		\'tooltip\'	=> NULL,\r\n		\'class\'		=> \'notes-marker\',\r\n		\'content\'	=> NULL,\r\n	), $atts));\r\n\r\n	$tag = strtolower($tag);\r\n	$words = \'\';\r\n\r\n	if ( preg_match(\'/^p|li|h[1-6]{1}?$/\', $tag) ) {\r\n\r\n		$body_array = explode( \"</$tag>\", ($content ? $content : $thisarticle[\'body\']) );\r\n\r\n		for ($i=0; $i <= count($body_array)-2; ++$i) {\r\n\r\n			$anchor = permlink(array()).\'#t\'.$thisarticle[\'thisid\'].$i;\r\n\r\n			if($full == true) {\r\n				$text = trim( strip_tags($body_array[$i]) );\r\n				$text = preg_replace(\'/\\s+/\', \' \', $text);\r\n				$words = urlencode( substr( html_entity_decode($text, ENT_NOQUOTES, \'UTF-8\'), 0, 114 ) ).\'...\';\r\n			}\r\n\r\n			$out .= str_replace( \"<$tag>\", \'<\'.$tag.\' class=\"notes\" id=\"t\'.$thisarticle[\'thisid\'].$i.\'\">\', $body_array[$i] ).tag( href($sign, \'https://twitter.com/share?url=\'.urlencode($anchor).\'&amp;text=\'.$words, \' target=\"_blank\"\'.($tooltip ? \' title=\"\'.$tooltip.\'\"\' : \'\')), \'span\', \' class=\"\'.$class.\'\"\' ).\"</$tag>\";\r\n\r\n		}\r\n\r\n		return $out;\r\n\r\n	} else {\r\n\r\n		return trigger_error(gTxt(\'invalid_attribute_value\', array(\'{tag}\' => \'tag\')), E_USER_WARNING);\r\n\r\n	}\r\n}\r\n\r\n\r\n/**\r\n * Generate additional HTML tags and script into public template\r\n *\r\n * @param  array    Tag attributes\r\n * @return string   HTML tags and custom script\r\n */\r\nfunction pat_body_tweets_live($atts)\r\n{\r\n\r\n	extract(lAtts(array(\r\n		\'id\' 		=> \'wrapper\',\r\n		\'account\' 	=> \'\',\r\n		\'label\' 		=> \'Tweet it!\',\r\n		\'popup\' 		=> false,\r\n		\'info\' 		=> false,\r\n		\'position\' 	=> false,\r\n		\'tooltip\' 	=> \' You can Tweet this text on mouse selection \',\r\n		\'top\' 		=> \'0\',\r\n		\'right\' 		=> \'0\',\r\n		\'bottom\' 	=> \'0\',\r\n		\'color\' 		=> \'#00aced\',\r\n		\'style\' 		=> NULL,\r\n	), $atts));\r\n\r\n	if ($account) {\r\n\r\n		return \'<div id=\"share\"><button><b>\'.$label.\'</b></button><span></span></div><script>var acc=\"\'._pat_validate_account($account).\'\",getNode=document.getElementById(\"\'.$id.\'\");function _isIn(e,t){while(e){if(e===t){return true;}e=e.parentNode}return false;}function _save(e){if(window.getSelection){var t=window.getSelection();if(t.getRangeAt&&t.rangeCount){for(var n=0;n<t.rangeCount;++n){if(!_isIn(t.getRangeAt(n).commonAncestorContainer,e)){return null;}}return t.getRangeAt(0);}}else if(document.selection&&document.selection.createRange){return _isIn(document.selection.createRange(),e);}return null;}function _trunc(e,t,n){el=n.toString().length;e=e.toString();e=e.replace(/\\u00AD/g,\"\");for(var r=0;r<e.length;++r)e[r].href=\"\";return e.length>=t?e.substr(0,t-el)+\"...\":e;}function _restore(e){if(e){if(window.getSelection){var t=window.getSelection();t.removeAllRanges();t.addRange(e).toString()}else if(ddocument.selection&&e.select){e.select();}}}var _tweet=null,_btn=document.getElementById(\"share\"),_share=_btn.children[0];document.onmouseup=function(e){_tweet=_save(getNode);setTimeout(function(){var t=_tweet.toString().length===0;_btn.style.top=(t?-9999:e.pageY+15)+\"px\";_btn.style.left=(t?-9999:e.pageX-30)+\"px\"},10)};_btn.onclick=function(e){if(!_tweet)return;var top=(screen.height/2)-170,left=(screen.width/2)-150,t=window.location.protocol+\"//\"+window.location.host+window.location.pathname;setTimeout(function(){_tweet=_trunc(_tweet,108,acc);_btn.style.top=\"-9999px\";_btn.style.left=\"-9999px\";window.open(\"https://twitter.com/intent/tweet?text=via @\"+acc+\" \"+encodeURIComponent(_tweet)+\" \"+t,\'.($popup ? \'\"shareWindow\",\"width=300,height=340,top=\"+top+\",left=\"+left+\"\"\' : \'\"_blank\"\').\').focus()},10);_restore(_tweet);return false;};var _target=document.getElementById(\"\'.$id.\'\");_target.style.position=\"relative\";\'.($info ? \'_target.insertAdjacentHTML(\\\'\'.($position ? \'afterbegin\' : \'beforeend\').\'\\\',\\\' <span id=\"tweety\" title=\"\'.$tooltip.\'\" style=\"position:absolute;\'.($position ? \'top:\'.$top : \'bottom:\'.$bottom).\';right:\'.$right.\';display:block;width:32px;height:32px;cursor:help;background:\'.$color.\' url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYCAMAAADat72NAAAAhFBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8g2+bRAAAAK3RSTlMABfuefDkC9vLkUCUKB+3ZzLKnjW1DFtS4rWFZPx8b3qSBcUgsEb66lzEve9FvTQAAAOpJREFUKM910eduwyAUhuGPZTveK15xnN117v/+WqhbQ0SeX0gvoCMdAAEcy6Xg0RAzIFs6IJmcfsvJqFU8VimCllqr7jmtQiqSFExQ2Gy5p3+RGCdkNRGVx7/HIW14zcAu5iQ7pvOVW1ngx0xG3tzuHVo7Q2OCVsW7eLMyNzVWFSefnc6ZoJC8Bp2DiV6Q0I7Fi/wJI4689cTwS5W+fA3WPMdnz9wJVgfvYNseP56StQUtk+FTjfawBElbOTcOcN1Lf03T9LGoszO09fOXrIeTHXORYMPmZme/bBSDgz2UHPuI530lVZc57RujiET2+03WRQAAAABJRU5ErkJggg==) center center no-repeat;\'.($style ? $style : \'\').\'\"></span>\\\')\' : \'\').\'</script>\';\r\n\r\n	} else {\r\n\r\n		return trigger_error(gTxt(\'invalid_attribute_value\', array(\'{tag}\' => \'account\')), E_USER_WARNING);\r\n\r\n	}\r\n\r\n}\r\n\r\n\r\n/**\r\n * Validate Twitter account\r\n *\r\n * @param  $entry  $att\r\n * @return string  string  User account without \'@\' sign\r\n */\r\nfunction _pat_validate_account($entry)\r\n{\r\n\r\n	if ( preg_match(\"/\\@[a-z0-9_]+/i\", $entry) )\r\n		$out = str_replace(\'@\', \'\', $entry);\r\n	else\r\n		$out = $entry;\r\n\r\n	return $out ? $out : trigger_error(gTxt(\'invalid_attribute_value\', array(\'{account}\' => \'Twitter account\')), E_USER_WARNING);\r\n\r\n}', '/*\r\n * pat_body_tweets\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-body-tweets.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.3.4\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * Generate additional markup into text\r\n *\r\n * @param  array    Tag attributes\r\n * @return string   HTML tags with an individual link\r\n */\r\nfunction pat_body_tweets($atts)\r\n{\r\n\r\n	global $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'tag\'		=> \'p\',\r\n		\'full\'		=> true,\r\n		\'sign\'		=> \'+\',\r\n		\'tooltip\'	=> NULL,\r\n		\'class\'		=> \'notes-marker\',\r\n		\'content\'	=> NULL,\r\n	), $atts));\r\n\r\n	$tag = strtolower($tag);\r\n	$words = \'\';\r\n\r\n	if ( preg_match(\'/^p|li|h[1-6]{1}?$/\', $tag) ) {\r\n\r\n		$body_array = explode( \"</$tag>\", ($content ? $content : $thisarticle[\'body\']) );\r\n\r\n		for ($i=0; $i <= count($body_array)-2; ++$i) {\r\n\r\n			$anchor = permlink(array()).\'#t\'.$thisarticle[\'thisid\'].$i;\r\n\r\n			if($full == true) {\r\n				$text = trim( strip_tags($body_array[$i]) );\r\n				$text = preg_replace(\'/\\s+/\', \' \', $text);\r\n				$words = urlencode( substr( html_entity_decode($text, ENT_NOQUOTES, \'UTF-8\'), 0, 114 ) ).\'...\';\r\n			}\r\n\r\n			$out .= str_replace( \"<$tag>\", \'<\'.$tag.\' class=\"notes\" id=\"t\'.$thisarticle[\'thisid\'].$i.\'\">\', $body_array[$i] ).tag( href($sign, \'https://twitter.com/share?url=\'.urlencode($anchor).\'&amp;text=\'.$words, \' target=\"_blank\"\'.($tooltip ? \' title=\"\'.$tooltip.\'\"\' : \'\')), \'span\', \' class=\"\'.$class.\'\"\' ).\"</$tag>\";\r\n\r\n		}\r\n\r\n		return $out;\r\n\r\n	} else {\r\n\r\n		return trigger_error(gTxt(\'invalid_attribute_value\', array(\'{tag}\' => \'tag\')), E_USER_WARNING);\r\n\r\n	}\r\n}\r\n\r\n\r\n/**\r\n * Generate additional HTML tags and script into public template\r\n *\r\n * @param  array    Tag attributes\r\n * @return string   HTML tags and custom script\r\n */\r\nfunction pat_body_tweets_live($atts)\r\n{\r\n\r\n	extract(lAtts(array(\r\n		\'id\' 		=> \'wrapper\',\r\n		\'account\' 	=> \'\',\r\n		\'label\' 		=> \'Tweet it!\',\r\n		\'popup\' 		=> false,\r\n		\'info\' 		=> false,\r\n		\'position\' 	=> false,\r\n		\'tooltip\' 	=> \' You can Tweet this text on mouse selection \',\r\n		\'top\' 		=> \'0\',\r\n		\'right\' 		=> \'0\',\r\n		\'bottom\' 	=> \'0\',\r\n		\'color\' 		=> \'#00aced\',\r\n		\'style\' 		=> NULL,\r\n	), $atts));\r\n\r\n	if ($account) {\r\n\r\n		return \'<div id=\"share\"><button><b>\'.$label.\'</b></button><span></span></div><script>var acc=\"\'._pat_validate_account($account).\'\",getNode=document.getElementById(\"\'.$id.\'\");function _isIn(e,t){while(e){if(e===t){return true;}e=e.parentNode}return false;}function _save(e){if(window.getSelection){var t=window.getSelection();if(t.getRangeAt&&t.rangeCount){for(var n=0;n<t.rangeCount;++n){if(!_isIn(t.getRangeAt(n).commonAncestorContainer,e)){return null;}}return t.getRangeAt(0);}}else if(document.selection&&document.selection.createRange){return _isIn(document.selection.createRange(),e);}return null;}function _trunc(e,t,n){el=n.toString().length;e=e.toString();e=e.replace(/\\u00AD/g,\"\");for(var r=0;r<e.length;++r)e[r].href=\"\";return e.length>=t?e.substr(0,t-el)+\"...\":e;}function _restore(e){if(e){if(window.getSelection){var t=window.getSelection();t.removeAllRanges();t.addRange(e).toString()}else if(ddocument.selection&&e.select){e.select();}}}var _tweet=null,_btn=document.getElementById(\"share\"),_share=_btn.children[0];document.onmouseup=function(e){_tweet=_save(getNode);setTimeout(function(){var t=_tweet.toString().length===0;_btn.style.top=(t?-9999:e.pageY+15)+\"px\";_btn.style.left=(t?-9999:e.pageX-30)+\"px\"},10)};_btn.onclick=function(e){if(!_tweet)return;var top=(screen.height/2)-170,left=(screen.width/2)-150,t=window.location.protocol+\"//\"+window.location.host+window.location.pathname;setTimeout(function(){_tweet=_trunc(_tweet,108,acc);_btn.style.top=\"-9999px\";_btn.style.left=\"-9999px\";window.open(\"https://twitter.com/intent/tweet?text=via @\"+acc+\" \"+encodeURIComponent(_tweet)+\" \"+t,\'.($popup ? \'\"shareWindow\",\"width=300,height=340,top=\"+top+\",left=\"+left+\"\"\' : \'\"_blank\"\').\').focus()},10);_restore(_tweet);return false;};var _target=document.getElementById(\"\'.$id.\'\");_target.style.position=\"relative\";\'.($info ? \'_target.insertAdjacentHTML(\\\'\'.($position ? \'afterbegin\' : \'beforeend\').\'\\\',\\\' <span id=\"tweety\" title=\"\'.$tooltip.\'\" style=\"position:absolute;\'.($position ? \'top:\'.$top : \'bottom:\'.$bottom).\';right:\'.$right.\';display:block;width:32px;height:32px;cursor:help;background:\'.$color.\' url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYCAMAAADat72NAAAAhFBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8g2+bRAAAAK3RSTlMABfuefDkC9vLkUCUKB+3ZzLKnjW1DFtS4rWFZPx8b3qSBcUgsEb66lzEve9FvTQAAAOpJREFUKM910eduwyAUhuGPZTveK15xnN117v/+WqhbQ0SeX0gvoCMdAAEcy6Xg0RAzIFs6IJmcfsvJqFU8VimCllqr7jmtQiqSFExQ2Gy5p3+RGCdkNRGVx7/HIW14zcAu5iQ7pvOVW1ngx0xG3tzuHVo7Q2OCVsW7eLMyNzVWFSefnc6ZoJC8Bp2DiV6Q0I7Fi/wJI4689cTwS5W+fA3WPMdnz9wJVgfvYNseP56StQUtk+FTjfawBElbOTcOcN1Lf03T9LGoszO09fOXrIeTHXORYMPmZme/bBSDgz2UHPuI530lVZc57RujiET2+03WRQAAAABJRU5ErkJggg==) center center no-repeat;\'.($style ? $style : \'\').\'\"></span>\\\')\' : \'\').\'</script>\';\r\n\r\n	} else {\r\n\r\n		return trigger_error(gTxt(\'invalid_attribute_value\', array(\'{tag}\' => \'account\')), E_USER_WARNING);\r\n\r\n	}\r\n\r\n}\r\n\r\n\r\n/**\r\n * Validate Twitter account\r\n *\r\n * @param  $entry  $att\r\n * @return string  string  User account without \'@\' sign\r\n */\r\nfunction _pat_validate_account($entry)\r\n{\r\n\r\n	if ( preg_match(\"/\\@[a-z0-9_]+/i\", $entry) )\r\n		$out = str_replace(\'@\', \'\', $entry);\r\n	else\r\n		$out = $entry;\r\n\r\n	return $out ? $out : trigger_error(gTxt(\'invalid_attribute_value\', array(\'{account}\' => \'Twitter account\')), E_USER_WARNING);\r\n\r\n}', 'ad2457dfbb743711fc8289fe731ac8c0', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_plugin_display', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.2.4', 'Display info about installed plugins', '<h1>soo_plugin_display</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>. Plugin authors can display code, help text, and other aspects of plugins directly from Textpattern.</p>\n\n<ul>\n	<li><a href=\"https://forum.textpattern.io/viewtopic.php?id=31866\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/109/soo_plugin_display\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_plugin_display\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#usage\">Usage</a></li>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#soo_plugin_display\">soo_plugin_display</a></li>\n		<li><a href=\"#soo_plugin_author_uri\">soo_plugin_author_uri, soo_plugin_version, soo_plugin_description</a></li>\n		<li><a href=\"#soo_plugin_author\">soo_plugin_author, soo_plugin_name</a></li>\n		<li><a href=\"#soo_plugin_size\">soo_plugin_size</a></li>\n		<li><a href=\"#soo_plugin_help\">soo_plugin_help</a></li>\n		<li><a href=\"#soo_plugin_code\">soo_plugin_code</a></li>\n	</ul></li>\n	<li><a href=\"#examples\">Examples</a></li>\n	<li><a href=\"#preferences\">Preferences</a></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>Display information about installed plugins.</p>\n\n<h2 id=\"usage\">Usage</h2>\n\n<p>The <code>soo_plugin_display</code> tag works as a container or with a form that retrieves most of the txp_plugin fields for the plugin specified in the <code>name</code> attribute. Place any of the other tags (<code>soo_plugin_author</code>, etc.) inside the container or form to display your choice of fields.</p>\n\n<p>If <code>name</code> is left blank (the <span class=\"default\">default</span>), <code>soo_plugin_display</code> will retrieve all plugins (optionally restricted by plugin prefix or status). In this case the plugin code and help will <strong>not</strong> be retrieved, so <code>soo_plugin_code</code> and <code>soo_plugin_help</code> will not produce output.</p>\n\n<p>The plugin is compatible with <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a>, allowing you to adjust some of the default tag attribute values.</p>\n\n<h2 id=\"tags\">Tags</h2>\n\n<p>All tags (except for <code>soo_plugin_display</code>) <span class=\"required\">must</span> be in a <code>soo_plugin_display</code> container or form to produce output.</p>\n\n<h3 id=\"soo_plugin_display\">soo_plugin_display</h3>\n\n<p>Retrieve a named plugin or a list of plugins; iterate over each plugin with the tag contents or specified form.</p>\n\n<pre>&lt;txp:soo_plugin_display form=&quot;foo&quot; /&gt;    &lt;!-- use with a form --&gt;\n\n&lt;txp:soo_plugin_display&gt;                 &lt;!-- or as a container --&gt;\n	...\n&lt;/txp:soo_plugin_display&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>name</code> <em>(plugin name)</em> Plugin to display. If blank (the <span class=\"default\">default</span>), retrieve a list of plugins (<dfn>list mode</dfn>)</li>\n	<li><code>form</code> <em>(Txp form name)</em> Form to display output. If empty, container tag required for output. <span class=\"default\">Default</span> is empty (can be changed in prefs).</li>\n</ul>\n\n<p>The remaining attributes are effective only in list mode (i.e., when <code>name</code> is left blank).</p>\n\n<ul>\n	<li><code>prefix</code> <em>(plugin author prefix)</em> select only plugins with this prefix</li>\n	<li><code>show_inactive</code> <em>(boolean)</em> <span class=\"default\">default</span> &#8220;0&#8221;, whether to include inactive plugins</li>\n	<li><code>sort</code> <em>(MySQL sort value)</em> <span class=\"default\">default</span> &#8220;name asc&#8221; (in addition to column names, &#8220;size&#8221; is also available for sorting)</li>\n	<li><code>wraptag</code> <em>(<span class=\"caps\">HTML</span> tag name, without brackets)</em></li>\n	<li><code>break</code> <em>(<span class=\"caps\">HTML</span> tag name, without brackets)</em></li>\n	<li><code>class</code> <em>(<span class=\"caps\">HTML</span> class attribute)</em> applied to <code>wraptag</code></li>\n	<li><code>html_id</code> <em>(<span class=\"caps\">HTML</span> ID attribute)</em> applied to <code>wraptag</code></li>\n</ul>\n\n<h3 id=\"soo_plugin_author_uri\">soo_plugin_author_uri, soo_plugin_version, soo_plugin_description</h3>\n\n<p>No attributes: each simply displays the corresponding field straight from <code>txp_plugin</code>.</p>\n\n<pre>&lt;txp:soo_plugin_author_uri /&gt;\n&lt;txp:soo_plugin_version /&gt;\n&lt;txp:soo_plugin_description /&gt;</pre>\n\n<h3 id=\"soo_plugin_author\">soo_plugin_author, soo_plugin_name</h3>\n\n<p>As above, but these accept an optional <code>link</code> attribute.</p>\n\n<pre>&lt;txp:soo_plugin_author /&gt;\n&lt;txp:soo_plugin_name /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>link</code> <em>(boolean)</em> whether to make output a link to the plugin author&#8217;s website, <span class=\"default\">default</span> &#8220;1&#8221;</li>\n</ul>\n\n<h3 id=\"soo_plugin_size\">soo_plugin_size</h3>\n\n<p>Display the plugin&#8217;s installed code size in KB (counting 1 character = 1 byte)</p>\n\n<pre>&lt;txp:soo_plugin_size /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>format</code> <em>(text)</em> <span class=\"default\">default</span> &#8216;{size}&amp;nbsp;KB&#8217;, can be changed in prefs.</li>\n</ul>\n\n<p>The plugin outputs the value of <code>format</code>, first replacing any occurrences of &#8216;{size}&#8217; with the size in KB.</p>\n\n<h3 id=\"soo_plugin_help\">soo_plugin_help</h3>\n\n<p>Display the plugin&#8217;s help text.</p>\n\n<pre>&lt;txp:soo_plugin_help /&gt;</pre>\n\n<p><span class=\"warning\">Does not work in list mode</span> (i.e., when <code>soo_plugin_display</code> has a blank <code>name</code> attribute).</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>strip_style</code> <em>(boolean)</em> whether or not to remove any leading <code>&lt;style&gt;</code> element (<span class=\"default\">default</span> &#8220;1&#8221;, can be changed in prefs)</li>\n	<li><code>strip_title</code> <em>(boolean)</em> whether or not to remove any leading <code>&lt;h1&gt;</code> element (<span class=\"default\">default</span> &#8220;1&#8221;, can be changed in prefs)</li>\n	<li><code>section_id</code> <em>(<span class=\"caps\">HTML</span> id value)</em> start display from identified header element, continue till next header of same or lower level</li>\n	<li><code>h_plus</code> <em>(integer)</em> transpose <span class=\"caps\">HTML</span> header levels by this amount</li>\n</ul>\n\n<p><code>strip_style</code> looks for an opening <code>&lt;style&gt;</code> tag at the very start of the Help section. <code>strip_title</code> looks for the first occurence of <code>&lt;h1&gt;</code>. Both do a non-greedy match looking for the closing tag.</p>\n\n<p>With <code>section_id</code>, display begins from the <span class=\"caps\">HTML</span> header element with the specified id. Display continues till the next <span class=\"caps\">HTML</span> header with the same or lower level (<code>&lt;h2&gt;</code> considered lower than <code>&lt;h3&gt;</code>, for example) or until the end if no such header is found.</p>\n\n<p><code>h_plus</code> can be helpful when breaking a long help text into several web pages, in conjuction with <code>section_id</code>. For example, <code>h_plus=&quot;-2&quot;</code> will transpose all <code>h6</code> elements to <code>h4</code>, all <code>h5</code> elements to <code>h3</code>, etc.</p>\n\n<h3 id=\"soo_plugin_code\">soo_plugin_code</h3>\n\n<p>Display plugin source code, by <span class=\"default\">default</span> the whole thing, or selected code determined by the <code>function</code> and/or <code>php_class</code> attributes.</p>\n\n<pre>&lt;txp:soo_plugin_code /&gt;</pre>\n\n<p><span class=\"warning\">Does not work in list mode</span> (i.e., when <code>soo_plugin_display</code> has a blank <code>name</code> attribute).</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>class</code> <em>(html class name)</em> for wrapping &lt;code&gt; and &lt;pre&gt; elements</li>\n	<li><code>html_id</code> <em>(html id name)</em> for wrapping &lt;code&gt; and &lt;pre&gt; elements</li>\n	<li><code>tab_stop</code> <em>(integer)</em> length of tab stop</li>\n	<li><code>function</code> <em>(text)</em> Show only this function. Use in combination with <code>php_class</code> to show only this method.</li>\n	<li><code>php_class</code> <em>(text)</em> Show only this <span class=\"caps\">PHP</span> class</li>\n	<li><code>highlight</code> <em>(boolean)</em> whether or not to add syntax highlighting to output. <span class=\"default\">Default</span> &#8220;1&#8221;, add highlighting (can be changed in prefs).</li>\n</ul>\n\n<p>The remaining attributes are effective only when highlighting is enabled.</p>\n\n<ul>\n	<li><code>show_line_numbers</code> <em>(text)</em> If set, text to append to each line number. If blank, do not show line numbers. <span class=\"default\">default</span> &#8220;:&#8221; (can be changed in prefs).</li>\n	<li><code>reindex_lines</code> <em>(integer)</em> With <code>function</code> and/or <code>php_class</code>, renumber lines starting from the value given. <span class=\"default\">Default</span> &#8220;0&#8221;, do not reindex.</li>\n</ul>\n\n<p>The function/class search isn&#8217;t thorough, and is based on my coding style. In the case of a function (outside a class) or class, it simply stops at the first non-indented closing brace (&#8220;}&#8221;) that occurs after the function or class name. Same for a method (function inside a class) but with the closing brace indented one tab.</p>\n\n<p>Tabs are converted to spaces, to stay aligned with tab stops as set in plugin preferences or the <code>tag_stop</code> attribute.</p>\n\n<h5>Highlighting</h5>\n\n<p>The code highlighting is based on the <span class=\"caps\">PHP</span> <code>highlight_string()</code> function. The <code>style</code> declarations produced by <code>highlight_string()</code> are replaced by <code>class</code> declarations. The important ones:</p>\n\n<ul>\n	<li><code>php_comment</code> comments</li>\n	<li><code>php_keyword</code> keywords, operators, brackets, semicolons, etc.</li>\n	<li><code>php_default</code> function names (including core <span class=\"caps\">PHP</span> functions)</li>\n	<li><code>php_string</code> strings</li>\n</ul>\n\n<p>Everything will be in a <code>span</code> with one of those class names. The whole thing is wrapped in a <code>code</code> element.</p>\n\n<p>If you prefer to use javascript-based highlighting, such as the excellent <a href=\"http://alexgorbatchev.com/SyntaxHighlighter/\">SyntaxHighlighter</a>, set <code>highlight=&quot;0&quot;</code> (or do this in prefs) to get raw (although still <span class=\"caps\">HTML</span>-escaped) code output. In this case line numbers will not be added.</p>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3>List all active plugins as a table</h3>\n\n<p>showing plugin author (as a link to the plugin author&#8217;s website) and description</p>\n\n<pre>&lt;txp:soo_plugin_display wraptag=&quot;table&quot; break=&quot;tr&quot;&gt;\n&lt;td&gt;&lt;txp:soo_plugin_name /&gt;&lt;/td&gt;&lt;td&gt;&lt;txp:soo_plugin_description /&gt;&lt;/td&gt;\n&lt;/txp:soo_plugin_display&gt;</pre>\n\n<h3>Display plugin help</h3>\n\n<pre>&lt;txp:soo_plugin_display name=&quot;soo_image&quot;&gt;\n&lt;txp:soo_plugin_help /&gt;\n&lt;/txp:soo_plugin_display&gt;</pre>\n\n<h3>Display full source code</h3>\n\n<p>leaving off the line numbers</p>\n\n<pre>&lt;txp:soo_plugin_display name=&quot;soo_plugin_pref&quot;&gt;\n&lt;txp:soo_plugin_code show_line_numbers=&quot;&quot; /&gt;\n&lt;/txp:soo_plugin_display&gt;</pre>\n\n<h3>Display a single method</h3>\n\n<p>renumbering the lines, starting from 1</p>\n\n<pre>&lt;txp:soo_plugin_display name=&quot;soo_txp_obj&quot;&gt;\n&lt;txp:soo_plugin_code php_class=&quot;soo_html&quot; function=&quot;tag&quot; reindex_lines=&quot;1&quot; /&gt;\n&lt;/txp:soo_plugin_display&gt;</pre>\n\n<h2 id=\"preferences\">Preferences</h2>\n\n<p>If you have the <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a> preference management system installed, you can adjust some of the default tag attribute values. Preference settings:</p>\n\n<ul>\n	<li>Default value for <code>soo_plugin_display</code>&#8217;s <code>form</code> attribute</li>\n	<li>Default value for <code>soo_plugin_help</code>&#8217;s <code>strip_style</code> attribute</li>\n	<li>Default value for <code>soo_plugin_help</code>&#8217;s <code>strip_title</code> attribute</li>\n	<li>Default value for <code>soo_plugin_size</code>&#8217;s <code>format</code> attribute</li>\n	<li>Default value for <code>soo_plugin_code</code>&#8217;s <code>highlight</code> attribute</li>\n	<li>Default value for <code>soo_plugin_code</code>&#8217;s <code>show_line_numbers</code> attribute</li>\n	<li>Default value for <code>soo_plugin_code</code>&#8217;s <code>tab_stop</code> attribute</li>\n</ul>\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>0.2.4 (2017-02-15)</h3>\n\n<ul>\n	<li>Textpattern 4.6 compatibility update</li>\n</ul>\n\n<h3>0.2.2 (12/20/2010), 0.2.3 (12/27/2010)</h3>\n\n<ul>\n	<li>Code cleaning only</li>\n</ul>\n\n<h3>0.2.1 (9/17/2010)</h3>\n\n<ul>\n	<li>Improved function/class pattern matching in <code>soo_plugin_code</code>.</li>\n</ul>\n\n<h3>0.2.0 (7/11/2010)</h3>\n\n<ul>\n	<li>New <code>highlight</code> attribute for <code>soo_plugin_code</code>, allows you to disable the standard syntax highlighting and output raw (but <span class=\"caps\">HTML</span>-escaped) code.</li>\n	<li>Bug fix: <code>soo_plugin_code</code> now handles multi-line comments correctly.</li>\n</ul>\n\n<p><span class=\"warning\">Note:</span> If you are upgrading from an earlier version, note that <code>soo_plugin_code</code> has some other format changes (e.g., highlighted output is now wrapped in a <code>pre</code> element), so some <span class=\"caps\">CSS</span> changes might be needed.</p>\n\n<h3>0.1.4 (7/4/2010)</h3>\n\n<ul>\n	<li><code>soo_plugin_help</code> output can have <span class=\"caps\">HTML</span> header levels transposed, using the <code>h_plus</code> attribute</li>\n</ul>\n\n<h3>0.1.3 (9/27/2009)</h3>\n\n<ul>\n	<li>For <code>soo_plugin_code</code>, tab to space conversion now maintains tab-stop alignment</li>\n</ul>\n\n<h3>0.1.2 (9/26/2009)</h3>\n\n<ul>\n	<li>New attribute for <code>soo_plugin_help</code>:\n	<ul>\n		<li><code>section_id</code>, start output from header element with specified <span class=\"caps\">HTML</span> id, continuing until next header element with same or lower level</li>\n	</ul></li>\n	</ul>\n\n<h3>0.1.1 (9/22/2009)</h3>\n\n<ul>\n	<li>Fixed: <span class=\"caps\">SQL</span> bug in list mode</li>\n</ul>\n\n<h3>0.1 (9/18/2009)</h3>\n\n<ul>\n	<li>Display most fields straight from the <code>txp_plugin</code> table. Also,\n	<ul>\n		<li>plugin name or author name can be automatically linked to plugin author&#8217;s website</li>\n		<li><code>soo_plugin_help</code> has options for stripping title and style first</li>\n		<li><code>soo_plugin_size</code> shows installed code size</li>\n		<li><code>soo_plugin_code</code> can display complete code or by function/class</li>\n		<li>compatible with <strong>soo_plugin_pref</strong> preference management system</li>\n	</ul></li>\n	</ul>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_plugin_display\')\n        ->register(\'soo_plugin_author_uri\')\n        ->register(\'soo_plugin_version\')\n        ->register(\'soo_plugin_description\')\n        ->register(\'soo_plugin_author\')\n        ->register(\'soo_plugin_name\')\n        ->register(\'soo_plugin_help\')\n        ->register(\'soo_plugin_size\')\n        ->register(\'soo_plugin_code\')\n        ;\n}\n\n@require_plugin(\'soo_plugin_pref\');             // optional\n\nif (@txpinterface == \'admin\') {\n    add_privs(\'plugin_prefs.soo_plugin_display\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_plugin_display\',\'1,2\');\n    register_callback(\'soo_plugin_display_manage_prefs\', \'plugin_prefs.soo_plugin_display\');\n    register_callback(\'soo_plugin_display_manage_prefs\', \'plugin_lifecycle.soo_plugin_display\');\n}\n\nfunction soo_plugin_display_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\')) {\n        return soo_plugin_pref($event, $step, soo_plugin_display_pref_spec());\n    }\n        // message to install soo_plugin_pref\n    if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\'.gTxt(\'edit\').\" $plugin \".\n            gTxt(\'edit_preferences\').\':</strong><br />\'.gTxt(\'install_plugin\').\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\').\" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_plugin_display_pref_spec()\n{\n    return array(\n        \'default_form\'      =>  array(\n            \'val\'   =>  \'\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Default output form for <b>soo_plugin_display</b> tag\',\n        ),\n        \'strip_style\'       =>  array(\n            \'val\'   =>  0,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Remove leading &lt;style&gt; element from Help text?\',\n        ),\n        \'strip_title\'       =>  array(\n            \'val\'   =>  0,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Remove first &lt;h1&gt; element from Help text?\',\n        ),\n        \'size_format\'       =>  array(\n            \'val\'   =>  \'{size}&nbsp;KB\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Default format string for <b>soo_plugin_size</b>\',\n        ),\n        \'highlight\'         =>  array(\n            \'val\'   =>  1,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Add syntax highlighting to <b>soo_plugin_code</b>?\',\n        ),\n        \'show_line_numbers\' =>  array(\n            \'val\'   =>  \':\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Text to append to line numbers (leave blank to hide numbers)\',\n        ),\n        \'tab_stop\'          =>  array(\n            \'val\'   =>  4,\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Spaces per tab in plugin code\',\n        ),\n    );\n}\n\nfunction soo_plugin_display_prefs($pref = null)\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_plugin_display_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_plugin_display\'));\n        }\n    }\n    return $pref ? $prefs[$pref] : $prefs;\n}\n\n  //---------------------------------------------------------------------//\n //                                 Tags                                //\n//---------------------------------------------------------------------//\n\n    // tag to select plugin(s) for display\n    // can be used as single w/form or container\n    // for plugin lists code and help are not retrieved\nfunction soo_plugin_display($atts, $thing = \'\')\n{\n    global $soo_plugin_display;\n    extract(lAtts(array(\n        \'name\'          =>  \'\',\n        \'prefix\'        =>  \'\',\n        \'form\'          =>  soo_plugin_display_prefs(\'default_form\'),\n        \'show_inactive\' =>  0,\n        \'sort\'          =>  \'name asc\',\n        \'wraptag\'       =>  \'\',\n        \'break\'         =>  \'\',\n        \'class\'         =>  \'\',\n        \'html_id\'       =>  \'\',\n    ), $atts));\n\n    $columns = \'author, author_uri, version, description, name, \'.\n        \'round(char_length(code)/1024, 1) as size\'.( $name ? \', help, code\' : \'\' );\n\n    if ($name or $prefix) {\n        $where = ( $name ? \"name = \'$name\'\" : \"name like \'$prefix%\'\" )\n            .( ( $show_inactive or $name ) ? \'\' : \' and status = 1\' );\n    } else {\n        $where = $show_inactive ? 1 : \'status = 1\';\n    }\n    $where .= \" order by $sort\";\n\n    if (! $data = safe_rows($columns, \'txp_plugin\', $where)) return;\n\n    foreach ($data as $r) {\n        $soo_plugin_display = $r;\n        $out[] = $thing ? parse($thing) : parse_form($form);\n    }\n\n    if (isset($out)) {\n        return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n    }\n}\n\n    // basic output tags: display field direct from database\nfunction soo_plugin_author_uri() { return _soo_plugin_field(\'author_uri\'); }\nfunction soo_plugin_version() { return _soo_plugin_field(\'version\'); }\nfunction soo_plugin_description() { return _soo_plugin_field(\'description\'); }\n\n    // as above, but with option to make output a link to author\'s website\nfunction soo_plugin_author($atts) { return _soo_plugin_field(\'author_uri\', $atts); }\nfunction soo_plugin_name($atts) { return _soo_plugin_field(\'name\', $atts); }\n\n    // display plugin help, with options to remove leading style and/or h1 elements,\n    // and option to restrict display to named section\nfunction soo_plugin_help($atts)\n{\n    $prefs = soo_plugin_display_prefs();\n    extract(lAtts(array(\n        \'strip_style\'   =>  $prefs[\'strip_style\'],\n        \'strip_title\'   =>  $prefs[\'strip_title\'],\n        \'section_id\'    =>  \'\',     // HTML id for header element to display\n        \'h_plus\'        =>  0,      // transpose header levels by this amount\n    ), $atts));\n    $help = _soo_plugin_field(\'help\');\n\n        // remove leading <style> element\n    if ($strip_style and preg_match(\'/^<style/\', $help))\n        $help = preg_replace(\'/^[\\s\\S]+?<\\/style>([\\s\\S]+)$/\', \'$1\', $help);\n\n        // remove leading <h1> element\n    if ($strip_title)\n        $help = preg_replace(\'/^([\\s\\S]+?)<h1[\\s\\S]+?<\\/h1>([\\s\\S]+)$/\', \'$1$2\', $help);\n\n        // display from named header element to next header with same or lower h-level\n    if ($section_id) {\n        $pattern = \"/^([\\s\\S]+)<h(\\d)([^>]+id=[\'\\\"]$section_id(\'|\\\").+?>[\\s\\S]+)$/\";\n        if (preg_match($pattern, $help, $match)) {\n            list( , , $h_num, $remainder) = $match;\n            $help = \'\';\n            $pattern = \'/^([\\s\\S]+?)<h(\\d)([\\s\\S]+)$/\';\n            $remainder = \"<h$h_num$remainder\";\n            while (preg_match($pattern, $remainder, $match)) {\n                list( , $keep, $next_h, $remainder) = $match;\n                $help .= $keep;\n                $remainder = \"<h$next_h$remainder\";\n                if ($h_num >= $next_h) $remainder = \'\';\n            }\n            if (! $help) $help = $remainder;\n        }\n    }\n\n        // transpose HTML header levels.\n    if ($h_plus) {\n        for ($i = 1; $i <= 6; $i++) {\n            $j = $i + $h_plus;\n            if ( $j < 1 ) $j = 1;\n            if ( $j > 6 ) $j = 6;\n            $old_tag[] = \"<h$i\";\n            $old_tag[] = \"</h$i\";\n            $new_tag[] = \"<h$j\";\n            $new_tag[] = \"</h$j\";\n        }\n        $help = str_replace($old_tag, $new_tag, $help);\n    }\n\n    return $help;\n}\n\n    // display installed code size in KB\nfunction soo_plugin_size($atts)\n{\n    extract(lAtts(array(\n        \'format\'    =>  soo_plugin_display_prefs(\'size_format\'),\n    ), $atts));\n    $size = _soo_plugin_field(\'size\');\n    return str_replace(\'{size}\', $size, $format);\n}\n\n    // display plugin source code\n    // code highlighting with named classes for styling\n    // various options for line numbering\n    // display full source or named function, class, or method\nfunction soo_plugin_code($atts)\n{\n    $prefs = soo_plugin_display_prefs();\n    extract(lAtts(array(\n        \'highlight\'             =>  $prefs[\'highlight\'],\n        \'show_line_numbers\'     =>  $prefs[\'show_line_numbers\'],\n        \'reindex_lines\'         =>  0,\n        \'tab_stop\'              =>  $prefs[\'tab_stop\'],\n        \'function\'              =>  \'\',\n        \'php_class\'             =>  \'\',\n        \'class\'                 =>  \'soo_plugin_code\',\n        \'html_id\'               =>  \'\',\n    ), $atts));\n\n    $raw_code = trim(_soo_plugin_field(\'code\'));\n    $match_index = 2;\n    $safety_check = true;\n\n        // find named method: assumes end brace one tab in from start of line\n    if ($php_class and $function) {\n        $pattern = \'/([\\s\\S]*(abstract|)\\s*class\\s+\'\n            . $php_class . \'\\s+[\\s\\S]*?\\{[\\s\\S]+?)(\\t(public|private|protected)?\\s*function\\s+\'\n            . $function . \'\\s*\\([\\s\\S]+?\\n\\t}.*)[\\s\\S]*/\';\n        $match_index ++;\n    }\n\n        // find named function: assumes end brace at start of line\n    elseif ($function)\n        $pattern = \'/([\\s\\S]*)((public|private|protected)?\\s*function\\s+\'\n            .$function.\'\\s*\\([\\s\\S]+?\\n}.*)/\';\n\n        // find named class: assumes end brace at start of line\n    elseif ($php_class)\n        $pattern = \'/([\\s\\S]*?)((abstract|)\\s*class\\s+\'\n            .$php_class.\'\\s+[\\s\\S]*?\\{[\\s\\S]+?\\n}.*)[\\s\\S]*/\';\n\n    else $safety_check = false;\n\n        // safety check is because the preg_match() can take a long time if there is no match\n    if ($safety_check)\n    {\n        if ($function and ! preg_match(\"/$function\\s*\\(/\", $raw_code)) return;\n        if ($php_class and ! preg_match(\"/class\\s*$php_class\\s+/\", $raw_code))\n            return;\n    }\n\n    if (isset($pattern)) {\n        if (! preg_match($pattern, $raw_code, $match)) return;\n        $raw_code = $match[$match_index];\n            // if not reindexing, find starting line number in full source code\n        $start_line = $reindex_lines ?\n            $reindex_lines : count(explode(n, $match[1]));\n    }\n\n        // convert tabs to spaces, retaining tab stop alignment\n    foreach (explode(n, $raw_code) as $line) {\n        $bucket = array();\n        foreach (str_split($line) as $char)\n            if ($char == t) {\n                $add = $tab_stop - fmod(count($bucket), $tab_stop);\n                while ( $add ) {\n                    $bucket[] = \' \';\n                    $add --;\n                }\n            } else\n                $bucket[] = $char;\n        $lines[] = implode(\'\', $bucket);\n    }\n\n    if (! $highlight)\n        return implode(n, array_map(\'htmlspecialchars\', $lines));\n\n    $find = array(sp, br, \'<code>\', \'</code>\');\n    $replace = array(\' \', n, \'\', \'\');\n        // names of code highlight types from ini_get()\n        // highlight_string() output style attributes will be replaced by named classes\n    foreach (array(\'bg\', \'comment\', \'default\', \'html\', \'keyword\', \'string\') as $k) {\n        $find[] = \'style=\"color: \' . ini_get(\"highlight.$k\") . \'\"\';\n        $replace[] = \"class=\\\"php_$k\\\"\";\n    }\n\n    $start_line = 1;\n\n        // run highlight_string(), clean up\n    $h_s = highlight_string(\"<?php\\n\".implode(n, $lines).\"\\n?>\", true);\n    $lines = str_replace($find, $replace, explode(br, $h_s));\n    array_shift($lines);\n    $lines[0] = \'<span class=\"php_html\">\' . $lines[0];\n    array_pop($lines);\n    $lines[count($lines)-1] .= \'</span>\';\n\n        // add line numbers\n    $total_lines = $start_line + count($lines) - 1;\n    foreach ($lines as $i => $line) {\n        if ($show_line_numbers) {\n            $i += $start_line;\n            $line = \"<span class=\\\"php_comment\\\">$i $show_line_numbers</span> $line\";\n            if ( $i < 1000 and $total_lines > 999 ) $line = sp . $line;\n            if ( $i < 100 and $total_lines > 99 ) $line = sp . $line;\n            if ( $i < 10 ) $line = sp . $line;\n        }\n        $code[] = $line;\n    }\n\n    $tag_atts = ( $class ? \" class=\\\"$class\\\"\" : \'\' ).( $html_id ? \" id=\\\"$html_id\\\"\" : \'\' );\n\n    return \"<pre$tag_atts><code$tag_atts>\".implode(n, $code).\'</code></pre>\';\n}\n\n  //---------------------------------------------------------------------//\n //                         Support Functions                           //\n//---------------------------------------------------------------------//\n\nfunction _soo_plugin_field($field, $atts = null)\n{\n    if (is_array($atts))\n        extract(lAtts(array(\n            \'link\'  =>  1,\n        ), $atts));\n    global $soo_plugin_display;\n    $author_uri = $soo_plugin_display[\'author_uri\'];\n    if (isset($soo_plugin_display[$field])) {\n        $out = $soo_plugin_display[$field];\n        return ( ! empty($link) and $author_uri ) ? href($out, $author_uri) : $out;\n    }\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_plugin_display\')\n        ->register(\'soo_plugin_author_uri\')\n        ->register(\'soo_plugin_version\')\n        ->register(\'soo_plugin_description\')\n        ->register(\'soo_plugin_author\')\n        ->register(\'soo_plugin_name\')\n        ->register(\'soo_plugin_help\')\n        ->register(\'soo_plugin_size\')\n        ->register(\'soo_plugin_code\')\n        ;\n}\n\n@require_plugin(\'soo_plugin_pref\');             // optional\n\nif (@txpinterface == \'admin\') {\n    add_privs(\'plugin_prefs.soo_plugin_display\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_plugin_display\',\'1,2\');\n    register_callback(\'soo_plugin_display_manage_prefs\', \'plugin_prefs.soo_plugin_display\');\n    register_callback(\'soo_plugin_display_manage_prefs\', \'plugin_lifecycle.soo_plugin_display\');\n}\n\nfunction soo_plugin_display_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\')) {\n        return soo_plugin_pref($event, $step, soo_plugin_display_pref_spec());\n    }\n        // message to install soo_plugin_pref\n    if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\'.gTxt(\'edit\').\" $plugin \".\n            gTxt(\'edit_preferences\').\':</strong><br />\'.gTxt(\'install_plugin\').\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\').\" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_plugin_display_pref_spec()\n{\n    return array(\n        \'default_form\'      =>  array(\n            \'val\'   =>  \'\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Default output form for <b>soo_plugin_display</b> tag\',\n        ),\n        \'strip_style\'       =>  array(\n            \'val\'   =>  0,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Remove leading &lt;style&gt; element from Help text?\',\n        ),\n        \'strip_title\'       =>  array(\n            \'val\'   =>  0,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Remove first &lt;h1&gt; element from Help text?\',\n        ),\n        \'size_format\'       =>  array(\n            \'val\'   =>  \'{size}&nbsp;KB\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Default format string for <b>soo_plugin_size</b>\',\n        ),\n        \'highlight\'         =>  array(\n            \'val\'   =>  1,\n            \'html\'  =>  \'yesnoradio\',\n            \'text\'  =>  \'Add syntax highlighting to <b>soo_plugin_code</b>?\',\n        ),\n        \'show_line_numbers\' =>  array(\n            \'val\'   =>  \':\',\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Text to append to line numbers (leave blank to hide numbers)\',\n        ),\n        \'tab_stop\'          =>  array(\n            \'val\'   =>  4,\n            \'html\'  =>  \'text_input\',\n            \'text\'  =>  \'Spaces per tab in plugin code\',\n        ),\n    );\n}\n\nfunction soo_plugin_display_prefs($pref = null)\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_plugin_display_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_plugin_display\'));\n        }\n    }\n    return $pref ? $prefs[$pref] : $prefs;\n}\n\n  //---------------------------------------------------------------------//\n //                                 Tags                                //\n//---------------------------------------------------------------------//\n\n    // tag to select plugin(s) for display\n    // can be used as single w/form or container\n    // for plugin lists code and help are not retrieved\nfunction soo_plugin_display($atts, $thing = \'\')\n{\n    global $soo_plugin_display;\n    extract(lAtts(array(\n        \'name\'          =>  \'\',\n        \'prefix\'        =>  \'\',\n        \'form\'          =>  soo_plugin_display_prefs(\'default_form\'),\n        \'show_inactive\' =>  0,\n        \'sort\'          =>  \'name asc\',\n        \'wraptag\'       =>  \'\',\n        \'break\'         =>  \'\',\n        \'class\'         =>  \'\',\n        \'html_id\'       =>  \'\',\n    ), $atts));\n\n    $columns = \'author, author_uri, version, description, name, \'.\n        \'round(char_length(code)/1024, 1) as size\'.( $name ? \', help, code\' : \'\' );\n\n    if ($name or $prefix) {\n        $where = ( $name ? \"name = \'$name\'\" : \"name like \'$prefix%\'\" )\n            .( ( $show_inactive or $name ) ? \'\' : \' and status = 1\' );\n    } else {\n        $where = $show_inactive ? 1 : \'status = 1\';\n    }\n    $where .= \" order by $sort\";\n\n    if (! $data = safe_rows($columns, \'txp_plugin\', $where)) return;\n\n    foreach ($data as $r) {\n        $soo_plugin_display = $r;\n        $out[] = $thing ? parse($thing) : parse_form($form);\n    }\n\n    if (isset($out)) {\n        return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n    }\n}\n\n    // basic output tags: display field direct from database\nfunction soo_plugin_author_uri() { return _soo_plugin_field(\'author_uri\'); }\nfunction soo_plugin_version() { return _soo_plugin_field(\'version\'); }\nfunction soo_plugin_description() { return _soo_plugin_field(\'description\'); }\n\n    // as above, but with option to make output a link to author\'s website\nfunction soo_plugin_author($atts) { return _soo_plugin_field(\'author_uri\', $atts); }\nfunction soo_plugin_name($atts) { return _soo_plugin_field(\'name\', $atts); }\n\n    // display plugin help, with options to remove leading style and/or h1 elements,\n    // and option to restrict display to named section\nfunction soo_plugin_help($atts)\n{\n    $prefs = soo_plugin_display_prefs();\n    extract(lAtts(array(\n        \'strip_style\'   =>  $prefs[\'strip_style\'],\n        \'strip_title\'   =>  $prefs[\'strip_title\'],\n        \'section_id\'    =>  \'\',     // HTML id for header element to display\n        \'h_plus\'        =>  0,      // transpose header levels by this amount\n    ), $atts));\n    $help = _soo_plugin_field(\'help\');\n\n        // remove leading <style> element\n    if ($strip_style and preg_match(\'/^<style/\', $help))\n        $help = preg_replace(\'/^[\\s\\S]+?<\\/style>([\\s\\S]+)$/\', \'$1\', $help);\n\n        // remove leading <h1> element\n    if ($strip_title)\n        $help = preg_replace(\'/^([\\s\\S]+?)<h1[\\s\\S]+?<\\/h1>([\\s\\S]+)$/\', \'$1$2\', $help);\n\n        // display from named header element to next header with same or lower h-level\n    if ($section_id) {\n        $pattern = \"/^([\\s\\S]+)<h(\\d)([^>]+id=[\'\\\"]$section_id(\'|\\\").+?>[\\s\\S]+)$/\";\n        if (preg_match($pattern, $help, $match)) {\n            list( , , $h_num, $remainder) = $match;\n            $help = \'\';\n            $pattern = \'/^([\\s\\S]+?)<h(\\d)([\\s\\S]+)$/\';\n            $remainder = \"<h$h_num$remainder\";\n            while (preg_match($pattern, $remainder, $match)) {\n                list( , $keep, $next_h, $remainder) = $match;\n                $help .= $keep;\n                $remainder = \"<h$next_h$remainder\";\n                if ($h_num >= $next_h) $remainder = \'\';\n            }\n            if (! $help) $help = $remainder;\n        }\n    }\n\n        // transpose HTML header levels.\n    if ($h_plus) {\n        for ($i = 1; $i <= 6; $i++) {\n            $j = $i + $h_plus;\n            if ( $j < 1 ) $j = 1;\n            if ( $j > 6 ) $j = 6;\n            $old_tag[] = \"<h$i\";\n            $old_tag[] = \"</h$i\";\n            $new_tag[] = \"<h$j\";\n            $new_tag[] = \"</h$j\";\n        }\n        $help = str_replace($old_tag, $new_tag, $help);\n    }\n\n    return $help;\n}\n\n    // display installed code size in KB\nfunction soo_plugin_size($atts)\n{\n    extract(lAtts(array(\n        \'format\'    =>  soo_plugin_display_prefs(\'size_format\'),\n    ), $atts));\n    $size = _soo_plugin_field(\'size\');\n    return str_replace(\'{size}\', $size, $format);\n}\n\n    // display plugin source code\n    // code highlighting with named classes for styling\n    // various options for line numbering\n    // display full source or named function, class, or method\nfunction soo_plugin_code($atts)\n{\n    $prefs = soo_plugin_display_prefs();\n    extract(lAtts(array(\n        \'highlight\'             =>  $prefs[\'highlight\'],\n        \'show_line_numbers\'     =>  $prefs[\'show_line_numbers\'],\n        \'reindex_lines\'         =>  0,\n        \'tab_stop\'              =>  $prefs[\'tab_stop\'],\n        \'function\'              =>  \'\',\n        \'php_class\'             =>  \'\',\n        \'class\'                 =>  \'soo_plugin_code\',\n        \'html_id\'               =>  \'\',\n    ), $atts));\n\n    $raw_code = trim(_soo_plugin_field(\'code\'));\n    $match_index = 2;\n    $safety_check = true;\n\n        // find named method: assumes end brace one tab in from start of line\n    if ($php_class and $function) {\n        $pattern = \'/([\\s\\S]*(abstract|)\\s*class\\s+\'\n            . $php_class . \'\\s+[\\s\\S]*?\\{[\\s\\S]+?)(\\t(public|private|protected)?\\s*function\\s+\'\n            . $function . \'\\s*\\([\\s\\S]+?\\n\\t}.*)[\\s\\S]*/\';\n        $match_index ++;\n    }\n\n        // find named function: assumes end brace at start of line\n    elseif ($function)\n        $pattern = \'/([\\s\\S]*)((public|private|protected)?\\s*function\\s+\'\n            .$function.\'\\s*\\([\\s\\S]+?\\n}.*)/\';\n\n        // find named class: assumes end brace at start of line\n    elseif ($php_class)\n        $pattern = \'/([\\s\\S]*?)((abstract|)\\s*class\\s+\'\n            .$php_class.\'\\s+[\\s\\S]*?\\{[\\s\\S]+?\\n}.*)[\\s\\S]*/\';\n\n    else $safety_check = false;\n\n        // safety check is because the preg_match() can take a long time if there is no match\n    if ($safety_check)\n    {\n        if ($function and ! preg_match(\"/$function\\s*\\(/\", $raw_code)) return;\n        if ($php_class and ! preg_match(\"/class\\s*$php_class\\s+/\", $raw_code))\n            return;\n    }\n\n    if (isset($pattern)) {\n        if (! preg_match($pattern, $raw_code, $match)) return;\n        $raw_code = $match[$match_index];\n            // if not reindexing, find starting line number in full source code\n        $start_line = $reindex_lines ?\n            $reindex_lines : count(explode(n, $match[1]));\n    }\n\n        // convert tabs to spaces, retaining tab stop alignment\n    foreach (explode(n, $raw_code) as $line) {\n        $bucket = array();\n        foreach (str_split($line) as $char)\n            if ($char == t) {\n                $add = $tab_stop - fmod(count($bucket), $tab_stop);\n                while ( $add ) {\n                    $bucket[] = \' \';\n                    $add --;\n                }\n            } else\n                $bucket[] = $char;\n        $lines[] = implode(\'\', $bucket);\n    }\n\n    if (! $highlight)\n        return implode(n, array_map(\'htmlspecialchars\', $lines));\n\n    $find = array(sp, br, \'<code>\', \'</code>\');\n    $replace = array(\' \', n, \'\', \'\');\n        // names of code highlight types from ini_get()\n        // highlight_string() output style attributes will be replaced by named classes\n    foreach (array(\'bg\', \'comment\', \'default\', \'html\', \'keyword\', \'string\') as $k) {\n        $find[] = \'style=\"color: \' . ini_get(\"highlight.$k\") . \'\"\';\n        $replace[] = \"class=\\\"php_$k\\\"\";\n    }\n\n    $start_line = 1;\n\n        // run highlight_string(), clean up\n    $h_s = highlight_string(\"<?php\\n\".implode(n, $lines).\"\\n?>\", true);\n    $lines = str_replace($find, $replace, explode(br, $h_s));\n    array_shift($lines);\n    $lines[0] = \'<span class=\"php_html\">\' . $lines[0];\n    array_pop($lines);\n    $lines[count($lines)-1] .= \'</span>\';\n\n        // add line numbers\n    $total_lines = $start_line + count($lines) - 1;\n    foreach ($lines as $i => $line) {\n        if ($show_line_numbers) {\n            $i += $start_line;\n            $line = \"<span class=\\\"php_comment\\\">$i $show_line_numbers</span> $line\";\n            if ( $i < 1000 and $total_lines > 999 ) $line = sp . $line;\n            if ( $i < 100 and $total_lines > 99 ) $line = sp . $line;\n            if ( $i < 10 ) $line = sp . $line;\n        }\n        $code[] = $line;\n    }\n\n    $tag_atts = ( $class ? \" class=\\\"$class\\\"\" : \'\' ).( $html_id ? \" id=\\\"$html_id\\\"\" : \'\' );\n\n    return \"<pre$tag_atts><code$tag_atts>\".implode(n, $code).\'</code></pre>\';\n}\n\n  //---------------------------------------------------------------------//\n //                         Support Functions                           //\n//---------------------------------------------------------------------//\n\nfunction _soo_plugin_field($field, $atts = null)\n{\n    if (is_array($atts))\n        extract(lAtts(array(\n            \'link\'  =>  1,\n        ), $atts));\n    global $soo_plugin_display;\n    $author_uri = $soo_plugin_display[\'author_uri\'];\n    if (isset($soo_plugin_display[$field])) {\n        $out = $soo_plugin_display[$field];\n        return ( ! empty($link) and $author_uri ) ? href($out, $author_uri) : $out;\n    }\n}\n', '2eba3f22f1a51397e150bd913e6c6a5b', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_txp_obj', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '1.1.2', 'Support library for Txp plugins', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help ul li {list-style-type: disc;}\ndiv#sed_help ul li li {list-style-type: circle;}\ndiv#sed_help ul li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\ndiv#sed_help sup {line-height:0;}\n</style>\n	<p><div id=\"sed_help\"></p>\n\n	<h1>soo_txp_obj</h1>\n\n	<p>A support library for Textpattern plugins.</p>\n\n	<ul>\n		<li><a href=\"http://ipsedixit.net/txp/21/soo-txp-obj\">Information and examples</a></li>\n		<li><a href=\"http://ipsedixit.net/api/soo_txp_obj/\"><span class=\"caps\">API</span> Documentation</a></li>\n	</ul>\n\n	<h2 id=\"history\">Version history</h2>\n\n	<h3 id=\"1_1_2\">1.1.2</h3>\n\n	<p>2017-02-15</p>\n\n	<ul>\n		<li>Textpattern 4.6 compatibility update. (Breaks compatibility with earlier versions of Textpattern.)</li>\n	</ul>\n\n	<h3 id=\"1_1_1\">1.1.1</h3>\n\n	<p>2012/01/25</p>\n\n	<ul>\n		<li>Moved soo_txp_left_join::count() to parent class (soo_txp_select)</li>\n		<li>Bugfix/update for soo_uri: ampersands now encoded/decoded appropriately</li>\n	</ul>\n\n	<h3 id=\"1_1_0\">1.1.0</h3>\n\n	<p>2011/01/07</p>\n\n	<ul>\n		<li>soo_txp_select::distinct() for <code>SELECT DISTINCT</code> queries</li>\n		<li>Bugfix: soo_txp_left_join now works with database table prefixes</li>\n	</ul>\n\n	<h3 id=\"b9\">1.1.b.1</h3>\n\n	<p>9/12/2010</p>\n\n	<p><em>Note: this changelist includes items from the 1.0.b.9 and 1.0.b.10 releases</em></p>\n\n	<ul>\n		<li>New class, <strong>soo_txp_left_join</strong>, for <code>SELECT ... LEFT JOIN</code> queries</li>\n		<li><strong>soo_txp_upsert</strong> new features:\n	<ul>\n		<li>properties and methods for <code>VALUES()</code> syntax</li>\n		<li>can be initialized with a <strong>soo_txp_rowset</strong> or <strong>soo_txp_row</strong> object</li>\n	</ul></li>\n		<li><strong>soo_txp_rowset</strong> new features:\n	<ul>\n		<li>can be initialized with a query result resource</li>\n		<li>new function <code>subset()</code> to create a new rowset object from an existing one</li>\n	</ul></li>\n		<li>New class, <strong>soo_nested_set</strong>, for Celko nested sets (modified preorder tree)</li>\n		<li><strong>soo_html_form</strong>, new features and related classes:\n	<ul>\n		<li>Constructor&#8217;s <code>atts</code> argument can include an <code>action</code> array for adding query parameters to the form&#8217;s <code>action</code> attribute</li>\n		<li>New classes:\n	<ul>\n		<li><strong>soo_html_label</strong> for labeling form controls</li>\n		<li><strong>soo_html_input</strong> for input elements</li>\n		<li><strong>soo_html_select</strong> for select elements (can be initialized with an array which will auto-create appropriate <code>option</code> elements)</li>\n		<li><strong>soo_html_option</strong> (see above)</li>\n		<li><strong>soo_html_textarea</strong> for textarea elements</li>\n	</ul></li>\n	</ul></li>\n		<li><strong>soo_html_img</strong> bugfix for pre Txp 4.2 compatibility</li>\n		<li><strong>soo_html_table</strong> can now be initialized with an array of values or table cells; these will automatically be formatted into rows and cells appropriately</li>\n		<li>New class, <strong>soo_html_caption</strong>, for table captions</li>\n		<li><strong>soo_html_ol</strong> and <strong>soo_html_ul</strong> can be initialized with nested arrays, automatically generating nested lists (see <strong>soo_nested_set</strong> for a possible source of the nested array)</li>\n	</ul>\n\n	<h3 id=\"b8\">1.0.b.8</h3>\n\n	<p>7/9/2010</p>\n\n	<ul>\n		<li>Documentation updates (DoxyGen compatibility)</li>\n	</ul>\n\n	<h3 id=\"b7\">1.0.b.7</h3>\n\n	<p>7/4/2010</p>\n\n	<ul>\n		<li><strong>soo_html_img</strong> now adds thumbnail <code>height</code> and <code>width</code> attributes (Txp 4.2.0 or later)</li>\n	</ul>\n\n	<h3 id=\"b6\">1.0.b.6</h3>\n\n	<p>7/1/2010</p>\n\n	<ul>\n		<li>New class: soo_util, for static utility methods</li>\n	</ul>\n\n	<h3 id=\"b5\">1.0.b.5</h3>\n\n	<p>1/27/2010</p>\n\n	<ul>\n		<li>new method: <code>where_clause()</code> in <strong>soo_txp_query</strong>, a catch-all for complex clauses</li>\n		<li>minor <span class=\"caps\">HTML</span> formatting change to <code>tag()</code> method in <strong>soo_html</strong></li>\n	</ul>\n\n	<h3 id=\"b4\">1.0.b.4</h3>\n\n	<p>10/3/2009</p>\n\n	<ul>\n		<li><strong>soo_uri</strong> updated for correct behavior in Txp sub-dir installations</li>\n	</ul>\n\n	<h3 id=\"b3\">1.0.b.3</h3>\n\n	<p>9/22/2009</p>\n\n	<ul>\n		<li><strong>soo_uri</strong> now gets query params by parsing $_SERVER[&#8216;<span class=\"caps\">QUERY</span>_STRING&#8217;] instead of $_GET</li>\n	</ul>\n\n	<h3 id=\"b2\">1.0.b.2</h3>\n\n	<p>9/16/2009</p>\n\n	<ul>\n		<li>New callback function for <span class=\"caps\">MLP</span> Pack compatibility with <strong>soo_uri</strong></li>\n		<li>New classes:\n	<ul>\n		<li>soo_txp_upsert for <span class=\"caps\">SQL</span> insert/update statements</li>\n		<li>soo_txp_delete for <span class=\"caps\">SQL</span> delete statements</li>\n		<li>soo_html_form for form elements</li>\n	</ul></li>\n		<li>soo_txp_rowset overrides parent::__get() method</li>\n	</ul>\n\n	<h3 id=\"b1\">1.0.b.1</h3>\n\n	<ul>\n		<li>Major re-organization and re-write of most classes.\n	<ul>\n		<li>The old <strong>Soo_Txp_Data</strong> family has been divided into separate classes for queries and data.</li>\n		<li>There are no longer separate classes for each Txp table.</li>\n		<li>All class names now lowercase (these aren&#8217;t case sensitive anyway).</li>\n		<li>Generic setting is now in the form <code>obj-&#62;property()</code> instead of <code>obj-&#62;set_property()</code>.</li>\n		<li>Various renaming, code cleaning, etc.</li>\n	</ul></li>\n	</ul>\n\n	<h3 id=\"a6\">1.0.a.6</h3>\n\n	<p>6/2/2009</p>\n\n	<p>What the heck, time to use the same naming convention I&#8217;m using with other plugins.</p>\n\n	<ul>\n		<li>Added <strong>Soo_Txp_Uri</strong> class, for <span class=\"caps\">URI</span> query string manipulation</li>\n		<li><strong>Soo_Html_P</strong> and <strong>soo_html_th</strong> can now have contents set during instantiation. Note that this will break compatibility with some previous uses, e.g. <code>new Soo_Html_P($atts)</code>.</li>\n		<li>Added <code>$in</code> argument to <code>where_in()</code> method (<strong>Soo_Txp_Data</strong>) to allow &#8220;<span class=\"caps\">NOT</span> IN&#8221; queries</li>\n	</ul>\n\n	<h3 id=\"a5\">1.0.a5</h3>\n\n	<p>5/13/2009</p>\n\n	<ul>\n		<li>Corrected <span class=\"caps\">SQL</span> syntax in <code>order_by_field()</code> function of <code>Soo_Txp_Data</code> class</li>\n		<li>Modified <code>tag()</code> function of <code>Soo_Html</code> class to handle a <span class=\"caps\">PHP</span> 5.2.0 bug</li>\n	</ul>\n\n	<h3 id=\"a4\">1.0.a4</h3>\n\n	<p>5/1/2009</p>\n\n	<p>Added <code>count()</code> and <code>field()</code> methods to the abstract <code>Soo_Txp_Data</code> class.</p>\n\n	<h3 id=\"a3\">1.0.a3</h3>\n\n	<p>2/19/2009</p>\n\n	<p>Added &#8220;Expires&#8221; property to <code>Soo_Txp_Article</code> and &#8220;load_order&#8221; property to <code>Soo_Txp_Plugin</code>. These fields were added in Textpattern version 4.0.7.</p>\n\n	<h3 id=\"a2\">1.0.a2</h3>\n\n	<p>2/5/2009</p>\n\n	<p>No significant changes, but added generic getters and setters (thanks to <strong>jm</strong> for the hint), making the file about 35% smaller.</p>\n\n	<h3 id=\"a1\">1.0.a1</h3>\n\n	<p>2/4/2009</p>\n\n	<p></div></p>', '\n/// Generic abstract base class, with a few low-level utility methods.\nabstract class soo_obj\n{\n	/** Generic getter.\n	 *  Allow calls in the form $obj->property for protected properties.\n	 */\n	public function __get( $property )\n	{\n		return isset($this->$property) ? $this->$property : null;\n	}\n\n	/** Method overloading for generic setters.\n	 *  Allow calls in the form $obj->property($value).\n	 *  Returns $this to allow method chaining.\n	 */\n	public function __call( $request, $args )\n	{\n		if ( isset($this->$request) )\n			$this->$request = array_shift($args);\n		return $this;\n	}\n\n	/** Return an object\'s class name.\n	 *  Allows object to be used in a string context.\n	 *  @return string Class name\n	 */\n	public function __toString()\n	{\n		return get_class($this);\n	}\n\n	/** Return an object\'s properties as an associative array.\n	 *  Note: includes protected and private properties.\n	 *  For public properties only, use get_object_vars($obj) directly.\n	 *  @return array\n	 */\n	public function properties()\n	{\n		return get_object_vars($this);\n	}\n\n	/** Return an object\'s properties as an indexed array.\n	 *  @return array\n	 */\n	public function property_names()\n	{\n		return array_keys($this->properties());\n	}\n\n}\n\n/// Abstract base class for SQL queries.\nabstract class soo_txp_query extends soo_obj\n{\n	/// Database table name.\n	protected $table		= \'\';\n	/// SQL WHERE expressions.\n	protected $where		= array();\n	/// SQL ORDER BY expressions.\n	protected $order_by		= array();\n	/// SQL LIMIT.\n	protected $limit		= 0;\n	/// SQL OFFSET.\n	protected $offset		= 0;\n\n	/** Constructor.\n	 *  Use $key to match a single row matching on appropriate key column\n	 *  If $key is an array, current key is the column and current value\n	 *  is the value. Otherwise column will be taken from self::key_column().\n	 *  @param table Table name\n	 *  @param key Key column value (or array) for WHERE expression\n	 */\n	public function __construct( $table, $key = null )\n	{\n		$this->table = trim($table);\n		if ( is_array($key) )\n			$this->where(key($key), current($key));\n		elseif ( $key )\n			$this->where($this->key_column($key), $key);\n	}\n\n	/** Add expression to WHERE clause.\n	 *  @param column Column name\n	 *  @param value Column value(s) (string, array, or soo_txp_select)\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		$join = $this->andor($join);\n\n		if ( is_array($value) )\n			$value = \'(\' . implode(\',\', quote_list(doSlash($value))) . \')\';\n		elseif ( $value instanceof soo_txp_select )\n			$value = \'(\' . $value->sql() . \')\';\n		else\n			$value = \"\'$value\'\";\n\n		$this->where[] = ( $join ? \"$join \" : \'\' ) .\n			self::quote($column) . \" $operator $value\";\n		return $this;\n	}\n\n	/** Add a raw expression to WHERE clause.\n	 *  Use instead of {@link where()} for complex (e.g. nested) expressions\n	 *  @param clause WHERE Expression\n	 *  @param join AND or OR\n	 */\n	public function where_clause( $clause, $join = \'\' )\n	{\n		$join = $this->andor($join);\n		$this->where[] = ( $join ? \"$join \" : \'\' ) . $clause;\n		return $this;\n	}\n\n	/** Add an IN expression to the WHERE clause.\n	 *  @param column Column name\n	 *  @param list Items to compare against\n	 *  @param join AND or OR\n	 *  @param in true = IN, false = NOT IN\n	 */\n	public function in( $column, $list, $join = \'\', $in = true )\n	{\n		$in = ( $in ? \'\' : \' not\' ) . \' in\';\n		if ( is_string($list) )\n			$list = do_list($list);\n		return $this->where($column, $list, $in, $join);\n	}\n\n	/** Alias of in() with $in = false (add a NOT IN expression).\n	 *  @param column Column name\n	 *  @param list Items to compare against\n	 *  @param join AND or OR\n	 */\n	public function not_in( $column, $list, $join = \'\' )\n	{\n		return $this->in( $column , $list , $join , false );\n	}\n\n	/** Add a MySQL REGEXP expression to the WHERE clause.\n	 *  @param pattern MySQL REGEXP pattern\n	 *  @param subject Column name or string to match\n	 *  @param join AND or OR\n	 */\n	public function regexp( $pattern, $subject, $join = \'\' )\n	{\n		return $this->where($subject, $pattern, \'regexp\', $join);\n	}\n\n	protected function andor( $join = \'and\' )\n	{\n		$join = strtolower($join);\n		return count($this->where) ?\n			( in_list($join, \'and,or\') ? $join : \'and\' ) : \'\';\n	}\n\n	/** Quote with backticks.\n	 *  Only quote items consisting of alphanumerics, $, and/or _\n	 *  @param identifier Item to quote\n	 *  @return string\n	 */\n	public static function quote( $identifier )\n	{\n		return preg_match(\'/^[a-z_$\\d]+$/i\', $identifier) ?\n			\"`$identifier`\" : $identifier;\n	}\n\n	/** Add an expression to the ORDER BY array.\n	 *  Example: $query->order_by(\'foo ASC, bar DESC\');\n	 *  @param expr Comma-separated list, or array, of expressions\n	 *  @param direction ASC or DESC\n	 */\n	public function order_by( $expr, $direction = \'\' )\n	{\n		if ( $expr )\n		{\n			if ( ! is_array($expr) ) $expr = do_list($expr);\n			foreach ( $expr as $x )\n			{\n				if ( preg_match(\'/(\\S+)\\s+(\\S+)/\', $x, $match) )\n					list( , $column, $direction) = $match;\n				else\n					$column = $x;\n				if ( in_array(strtolower($column), array(\'random\', \'rand\', \'rand()\')) )\n					$column = \'rand()\';\n				else\n					$direction = in_array(strtolower($direction), array(\'asc\', \'desc\')) ? $direction : \'\';\n				$this->order_by[] = $column . ( $direction ? \' \' . $direction : \'\');\n			}\n		}\n		return $this;\n	}\n\n	/** Alias of order_by() for a single column ASC.\n	 *  @param col Column name\n	 */\n	public function asc( $col )\n	{\n		return $this->order_by($col, \'asc\');\n	}\n\n	/** Alias of order_by() for a single column DESC.\n	 *  @param col Column name\n	 */\n	public function desc( $col )\n	{\n		return $this->order_by($col, \'desc\');\n	}\n\n	/** Add a FIELD() expression to the ORDER BY array.\n	 *  For preserving an arbitrary sort order, e.g. \'7,5,12,1\'\n	 *  Note that FIELD() is a MySQL-specific function (not standard SQL)\n	 *  @param field Column name\n	 *  @param list Comma-separated list, or array, of values in order\n	 */\n	public function order_by_field( $field, $list )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		if ( count($list) )\n			$this->order_by[] = \'field(\' . self::quote($field) . \', \' .\n				implode(\', \', quote_list(doSlash($list))) . \')\';\n		return $this;\n	}\n\n	/** Add a LIMIT to the query.\n	 *  @param limit Maximum number of items to return\n	 */\n	public function limit( $limit )\n	{\n		if ( $limit = intval($limit) )\n			$this->limit = \' limit \' . $limit;\n		return $this;\n	}\n\n	/** Add an OFFSET to the query.\n	 *  @param offset Number of items to skip\n	 */\n	public function offset( $offset )\n	{\n		if ( $offset = intval($offset) )\n			$this->offset = \' offset \' . $offset;\n		return $this;\n	}\n\n	/** Assemble and return the query clauses as a string.\n	 *  @return string\n	 */\n	protected function clause_string()\n	{\n		return implode(\' \', $this->where) .\n			( count($this->order_by) ? \' order by \' . implode(\', \', $this->order_by) : \'\' ) .\n			( $this->limit ? $this->limit : \'\' ) . ( $this->offset ? $this->offset : \'\' );\n	}\n\n	/** Number of items the query will return.\n	 *  Runs the query with COUNT() as the select expression\n	 *  @return int|false\n	 */\n	public function count()\n	{\n		return getCount($this->table, $this->clause_string() ? $this->clause_string() : \'1=1\');\n	}\n\n	/** Return the key column name for the current table.\n	 *  Some Txp tables have multiple indexes.\n	 *  If $key_value is provided, column of the matching type will be returned.\n	 *  Otherwise the numeric index will be returned in preference to the string index.\n	 *  @param key_value\n	 *  @return string\n	 */\n	public function key_column( $key_value = null )\n	{\n		$numeric_index	= array(\n			\'textpattern\'		=> \'ID\',\n			\'txp_category\'		=> \'id\',\n			\'txp_discuss\'		=> \'discussid\',\n			\'txp_file\'			=> \'id\',\n			\'txp_image\'			=> \'id\',\n			\'txp_lang\'			=> \'id\',\n			\'txp_link\'			=> \'id\',\n			\'txp_log\'			=> \'id\',\n			\'txp_users\'			=> \'user_id\',\n		);\n		$string_index		= array(\n			\'textpattern\'		=> \'Title\',\n			\'txp_category\'		=> \'name\',\n			\'txp_css\'			=> \'name\',\n			\'txp_discuss_ipban\'	=> \'ip\',\n			\'txp_discuss_nonce\'	=> \'nonce\',\n			\'txp_file\'			=> \'filename\',\n			\'txp_form\'			=> \'name\',\n			\'txp_image\'			=> \'name\',\n			\'txp_lang\'			=> \'lang\',\n			\'txp_page\'			=> \'name\',\n			\'txp_plugin\'		=> \'name\',\n			\'txp_prefs\'			=> \'name\',\n			\'txp_section\'		=> \'name\',\n			\'txp_users\'			=> \'name\',\n		);\n		if ( isset($numeric_index[$this->table]) )\n			$nx = $numeric_index[$this->table];\n		if ( isset($string_index[$this->table]) )\n			$sx = $string_index[$this->table];\n\n 		if ( is_numeric($key_value) )\n			return isset($nx) ? $nx : null;\n 		if ( is_string($key_value) )\n			return isset($sx) ? $sx : null;\n		return isset($nx) ? $nx : ( isset($sx) ? $sx : null );\n	}\n\n}\n\n/// Class for SELECT queries.\nclass soo_txp_select extends soo_txp_query\n{\n\n	/// SQL SELECT expressions.\n	protected $select		= array();\n	/// Whether to add DISTINCT\n	protected $distinct		= false;\n\n	/** Constructor.\n	 *  @param table Table name\n	 *  @param key Optional key for selecting a single record\n	 *  @param select item(s) to select\n	 */\n	public function __construct( $table, $key = null, $select = null )\n	{\n		parent::__construct($table, $key);\n		if ( $select ) $this->select($select);\n	}\n\n	/** Add items to the SELECT array.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select( $list = \'*\' )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		foreach ( $list as $col ) $this->select[] = parent::quote($col);\n		return $this;\n	}\n\n	/** Add the DISTINCT keyword to the query\n	 *  @return $this to allow method chaining\n	 */\n	public function distinct( )\n	{\n		$this->distinct = true;\n		return $this;\n	}\n\n	protected function init_query()\n	{\n		if ( ! count($this->select) ) $this->select();\n		if ( ! count($this->where) ) $this->where[] = \'1 = 1\';\n	}\n\n	/** Return a single record, or empty array if no matching records.\n	 *  @return array\n	 */\n	public function row()\n	{\n		$this->init_query();\n		return safe_row(implode(\',\', $this->select), $this->table,\n			$this->clause_string());\n	}\n\n	/** Return all records, or empty array if no matching records.\n	 *  @return array\n	 */\n	public function rows()\n	{\n		$this->init_query();\n		return safe_rows( ( $this->distinct ? \'distinct \' : \'\') .\n			implode(\',\', $this->select), $this->table, $this->clause_string());\n	}\n\n	/** Return the query as a string.\n	 *  @return string\n	 */\n	public function sql()\n	{\n		$this->init_query();\n		return \'select \' . implode(\',\', $this->select) . \' from \' . safe_pfx($this->table) . \' where \' . $this->clause_string();\n	}\n\n	/** Return result of a SELECT COUNT(*) query\n	 *  @int\n	 */\n	public function count()\n	{\n		$select = $this->select;\n		$this->select = array(\'count(*)\');\n		$r = safe_query($this->sql());\n		$this->select = $select;\n		if ( $r && $row = mysqli_fetch_row($r) )\n			return $row[0];\n	}\n}\n\n/// Class for SELECT ... LEFT JOIN queries.\n/// Currently very incomplete; needs to override most parent methods\n/// to specify which table each expression refers to.\nclass soo_txp_left_join extends soo_txp_select\n{\n	/// Join table name\n	protected $left_join;\n	/// ON expression for join\n	protected $join_on;\n	/// Left table alias\n	const t1 = \'t1\';\n	/// Join table alias\n	const t2 = \'t2\';\n\n	/** Constructor.\n	 *  @param table Left table\n	 *  @param left_join Join table\n	 *  @param col1 Key column name for left table\n	 *  @param col2 Key column name for join table\n	 */\n	public function __construct ( $table, $left_join, $col1, $col2 )\n	{\n		parent::__construct($table);\n		$this->left_join = $left_join;\n		$this->join_on = self::t1 . \'.\' . self::quote($col1) . \' = \' . self::t2 . \'.\' . self::quote($col2);\n	}\n\n	/** Like parent::quote, optionally prepending table name/alias.\n	 *  @param identifier Column name or alias\n	 *  @param prefix Table name or alias\n	 */\n	public static function quote( $identifier, $prefix = \'\' )\n	{\n		return ( $prefix ? $prefix . \'.\' : \'\' ) . parent::quote($identifier);\n	}\n\n	/** Add items to the SELECT array from the left table.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select( $list = \'*\' )\n	{\n		return self::select_from($list, self::t1);\n	}\n\n	/** Add items to the SELECT array from the join table.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select_join( $list = \'*\' )\n	{\n		return self::select_from($list, self::t2);\n	}\n\n	private function select_from( $list, $table )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		foreach ( $list as $col ) $this->select[] = self::quote($col, $table);\n		return $this;\n	}\n\n	/** Add expression to WHERE clause, referring to the left table.\n	 *  @param column Column name\n	 *  @param value Column value\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		return parent::where(self::quote($column, self::t1), $value, $operator, $join);\n	}\n\n	/** Add expression to WHERE clause, referring to the join table.\n	 *  @param column Column name\n	 *  @param value Column value\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where_join( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		return parent::where(self::quote($column, self::t2), $value, $operator, $join);\n	}\n\n	/** Add an IS NULL expression, for selecting only items not in the join table\n	 *  @param column Key column in join table\n	 */\n	public function where_join_null( $column )\n	{\n		$join = parent::andor(\'\');\n		$this->where[] = ( $join ? $join . \' \' : \'\' ) . self::quote($column, self::t2) . \' is null\';\n		return $this;\n	}\n\n	/** Add a column to the ORDER BY array.\n	 *  @param col Column name from left table\n	 *  @param direction ASC or DESC\n	 */\n	public function order_by( $cols, $direction = \'\' )\n	{\n		$direction = in_array(strtolower($direction), array(\'asc\', \'desc\')) ? $direction : \'\';\n		if ( is_string($cols) ) $cols = do_list($cols);\n		foreach ( $cols as $col )\n		{\n			if ( $col == \'random\' or $col == \'rand\' or $col == \'rand()\' )\n			{\n				$col = \'rand()\';\n				$direction = \'\';\n			}\n			$this->order_by[] = self::quote($col, self::t1) . ( $direction ? \' \' . $direction : \'\');\n		}\n		return $this;\n	}\n\n	/** Override parent function; return a single record.\n	 *  @return array\n	 */\n	public function row()\n	{\n		return getRow($this->sql());\n	}\n\n	/** Override parent function; return all records\n	 *  @return array\n	 */\n	public function rows()\n	{\n		return getRows($this->sql());\n	}\n\n	/** Assemble query.\n	 *  @return string\n	 */\n	public function sql()\n	{\n		parent::init_query();\n		return \'select \' . implode(\',\', $this->select) . \' from \' . self::quote(safe_pfx($this->table)) . \' as \' . self::t1 . \' left join \' . self::quote(safe_pfx($this->left_join)) . \' as \' . self::t2 . \' on \' . $this->join_on . \' where \' . $this->clause_string();\n	}\n}\n\n/// Class for INSERT and UPDATE queries.\nclass soo_txp_upsert extends soo_txp_query\n{\n	// For use with VALUES() syntax\n	/// Columns to be explicitly set.\n	public $columns			= array();\n	/// VALUES() values.\n	public $values			= array();\n	/// VALUES() clause\n	protected $values_clause	= \'\';\n\n	// For use with SET col_name=value syntax\n	/// SET columns and values.\n	public $set				= array();\n	/// SET clause.\n	protected $set_clause	= \'\';\n\n	/** Constructor.\n	 *  Use $col to update a single row matching on appropriate key column\n	 *  (usually `name` or `id`)\n	 *  @param init Table name, soo_txp_rowset, or soo_txp_row\n	 *  @param col Key column value for WHERE expression\n	 */\n	public function __construct( $init, $col = null )\n	{\n		if ( is_scalar($init) )\n			parent::__construct($init, $col);\n		elseif ( $init instanceof soo_txp_rowset )\n		{\n			$this->table = $init->table;\n			if ( $col )\n				$this->columns = is_array($col) ? $col : do_list($col);\n			else\n				$this->columns = array_keys(current($init->data));\n			foreach ( $init->rows as $r )\n				$this->values[] = $r->data;\n		}\n		elseif ( $init instanceof soo_txp_row )\n		{\n			$this->table = $init->table;\n			if ( $col )\n				$this->columns = is_array($col) ? $col : do_list($col);\n			else\n				$this->columns = array_keys($init->data);\n			$this->values[] = $init->data;\n		}\n	}\n\n	/** Add a column:value pair to the $set array.\n	 *  @param column Column name\n	 *  @param value Column value\n	 */\n	public function set( $column, $value )\n	{\n		$this->set[$column] = $value;\n		return $this;\n	}\n\n	private function init_query()\n	{\n		if ( count($this->set) )\n		{\n			foreach ( $this->set as $col => $val )\n			{\n				$val = is_numeric($val) ? $val : \"\'$val\'\";\n				$set_pairs[] = \"$col = $val\";\n			}\n			$this->set_clause = implode(\',\', $set_pairs);\n		}\n		elseif ( count($this->values) )\n		{\n			if ( count($this->columns) )\n				$this->values_clause = \'(`\' . implode(\'`,`\', $this->columns) . \'`) \';\n			$this->values_clause .= \' values \';\n			foreach ( $this->values as $vs )\n			{\n				$this->values_clause .= \'(\';\n				foreach ( $vs as $v )\n					$this->values_clause .= ( is_numeric($v) ? $v : \"\'$v\'\" ) . \',\';\n				$this->values_clause = rtrim($this->values_clause, \',\') . \'),\';\n			}\n			$this->values_clause = rtrim($this->values_clause, \',\') . \';\';\n		}\n	}\n\n	/** Run the query.\n	 *  Runs an UPDATE query if $where is set, otherwise INSERT\n	 *  @return bool success or failure\n	 */\n	public function upsert()\n	{\n		$this->init_query();\n		if ( count($this->where) )\n			return safe_upsert($this->table, $this->set_clause, $this->clause_string());\n		if ( $this->set_clause )\n			return safe_insert($this->table, $this->set_clause);\n		if ( $this->values_clause )\n			return safe_query(\'insert into \' . safe_pfx($this->table) . $this->values_clause);\n	}\n}\n\n/// Class for DELETE queries.\nclass soo_txp_delete extends soo_txp_query\n{\n	/** Execute the DELETE query.\n	 *  @return bool	Query success or failure\n	 */\n	public function delete()\n	{\n		if ( count($this->where) )\n			return safe_delete($this->table, $this->clause_string());\n	}\n}\n\n/// Class for data results sets.\nclass soo_txp_rowset extends soo_obj\n{\n\n	/// Database table name.\n	protected $table		= \'\';\n\n	/// Array of soo_txp_row objects.\n	public $rows			= array();\n\n	/** Constructor.\n	 *  $init can be a soo_txp_select object, mysql result resource,\n	 *  or an array of records.\n	 *  If $index is provided or if $init is a soo_txp_select object,\n	 *  the $rows array will be indexed by key column values.\n	 *  @param init Data array or query object to initialize rowset\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\', $index = null )\n	{\n		if ( $init instanceof soo_txp_select )\n		{\n			$table = $init->table;\n			$index = $init->key_column();\n			$init = $init->rows();\n		}\n		if ( is_resource($init) and mysql_num_rows($init) )\n		{\n			while ( $r = mysql_fetch_assoc($init) )\n				$data[] = $r;\n			mysql_free_result($init);\n			$init = $data;\n		}\n		$this->table = $table;\n		if ( is_array($init) and count($init) )\n		{\n			foreach ( $init as $r )\n				if ( $index )\n					$this->add_row($r, $table, $r[$index]);\n				else\n					$this->add_row($r, $table);\n		}\n	}\n\n	/** Generic getter, overriding parent method.\n	 *  If $property is not a property name, look for row object\n	 *  matching this index value\n	 *  @param property Property name, or rowset index\n	 */\n	public function __get( $property )\n	{\n		if ( property_exists($this, $property) )\n			return $this->$property;\n		if ( array_key_exists($property, $this->rows) )\n			return $this->rows[$property];\n	}\n\n	/** Return an array of all values for a particular column (field).\n	 *  If $key is set, make it an associative array, using the value\n	 *  of the key column as the array index\n	 *  @param field Column (field) name\n	 *  @param key Key column name\n	 */\n	public function field_vals( $field, $key = null )\n	{\n		foreach ( $this->rows as $r )\n			if ( ! is_null($key) )\n				$out[$r->$key] = $r->$field;\n			else\n				$out[] = $r->$field;\n		return isset($out) ? $out : array();\n	}\n\n	/** Add a soo_txp_row object to $rows.\n	 *  @param data soo_txp_row object or key value\n	 *  @param table Txp table name\n	 *  @param i index value for new row in $rows array\n	 */\n	public function add_row( $data, $table = null, $i = null )\n	{\n		$table = is_null($table) ? $this->table : $table;\n		$r = $data instanceof soo_txp_row ?\n			$data : ( $table == \'txp_image\' ?\n				new soo_txp_img($data) : new soo_txp_row($data, $table) );\n		if ( is_null($i) )\n			$this->rows[] = $r;\n		else\n			$this->rows[$i] = $r;\n		return $this;\n	}\n\n	/** Split off a subset of rows as a new soo_txp_rowset object\n	 *  @param key array key for finding rows for the new set\n	 *  @param value key column value to match for rows for the new set\n	 *  @param index array index for new rowset rows\n	 *  @return soo_txp_rowset\n	 */\n	public function subset( $key, $value, $index = null )\n	{\n		$out = new self;\n		foreach ( $this->rows as $row )\n			if ( $row->$key == $value )\n				$out->add_row($row, null, is_null($index) ? null : $row->$index);\n		return $out;\n	}\n}\n\n/// Class for Joe Celko nested sets, aka modified preorder tree\nclass soo_nested_set extends soo_txp_rowset\n{\n	/** Constructor.\n	 *  $init can be a soo_txp_rowset object,\n	 *  otherwise see parent::__construct()\n	 *  @param init Data array or query object to initialize rowset\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\', $index = null )\n	{\n		if ( $init instanceof soo_txp_rowset )\n		{\n			$this->table = $init->table;\n			$this->rows = $init->rows;\n		}\n		else\n			parent::__construct($init, $table);\n	}\n\n	/** Return all rows as a nested array of row objects.\n	 *  Each array item is either a soo_txp_row object,\n	 *  or an array of such. If an array, it is the children of the\n	 *  immediately preceding item.\n	 *  This is a recursive function.\n	 *  @param rows Internal use only.\n	 *  @param rgt Internal use only.\n	 */\n	public function as_object_array( &$rows = null, $rgt = null )\n	{\n		if ( is_null($rows) )\n		{\n			$rows = $this->rows;\n			$root = current($rows);\n			$rgt = $root->rgt;\n		}\n		while ( $out[] = $node = array_shift($rows) and $node->rgt <= $rgt )\n			if ( $node->rgt > $node->lft + 1 )\n				$out[] = $this->as_object_array($rows, $node->rgt);\n 		if ( $node and $node->rgt > $rgt )\n 			array_unshift($rows, array_pop($out));\n 		if ( is_null($out[count($out) - 1]) )\n 			array_pop($out);\n		return $out;\n	}\n\n	/** Return all rows as a nested array of values.\n	 *  Each array item is either a node, as $index_column => $value_column,\n	 *  or an array of such. If an array, it is the children of the\n	 *  immediately preceding item, and has the key \'x_c\' where \'x\' is\n	 *  the parent node\'s index.\n	 *  This is a recursive function.\n	 *  @param index_column Column for node index value\n	 *  @param index_column Column for node value\n	 *  @param rows Internal use only.\n	 *  @param rgt Internal use only.\n	 */\n	public function as_array( $index_column, $value_column, &$rows = null, $rgt = null )\n	{\n		if ( is_null($rows) )\n		{\n			$rows = $this->rows;\n			$root = current($rows);\n			$rgt = $root->rgt;\n		}\n		while ( $node = array_shift($rows) and $node->rgt <= $rgt )\n		{\n			$out[$node->$index_column] = $node->$value_column;\n			if ( $node->rgt > $node->lft + 1 )\n				$out[$node->$index_column . \'_c\'] = $this->as_array($index_column, $value_column, $rows, $node->rgt);\n		}\n 		if ( $node and $node->rgt > $rgt )\n 			array_unshift($rows, $node);\n		return $out;\n	}\n\n	/** Split off a subtree of rows as a new soo_nested_set object\n	 *  @param root id of subtree root node\n	 *  @return soo_txp_rowset\n	 */\n	public function subtree( $root, $index = null )\n	{\n		$out = new self;\n		$root = $this->rows[$root];\n		foreach ( $this->rows as $row )\n			if ( $row->lft >= $root->lft and $row->rgt <= $root->rgt )\n				$out->add_row($row, null, is_null($index) ? null : $row->$index);\n		return $out;\n	}\n}\n\n/// Class for single data records.\nclass soo_txp_row extends soo_obj\n{\n	/// Database table name.\n	protected $table		= \'\';\n	/// Database record.\n	protected $data			= array();\n\n	/** Constructor.\n	 *  @param init Key value, soo_txp_select object, or data array\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\' )\n	{\n		if ( is_scalar($init) and $table )\n			$init = new soo_txp_select($table, $init);\n		if ( $init instanceof soo_txp_select )\n		{\n			$table = $init->table;\n			$init = $init->row();\n		}\n		if ( is_array($init) )\n			foreach ( $init as $k => $v )\n				$this->data[$k] = $v;\n		$this->table = $table;\n	}\n\n	/** Generic getter, overriding parent method.\n	 *  Look for $property in the $data array first\n	 *  @param property Column or property name\n	 *  @return mixed Data field or object property\n	 */\n	public function __get( $property )\n	{\n		return isset($this->data[$property]) ? $this->data[$property]\n			: parent::__get($property);\n	}\n\n	/// Override parent method, to keep $data protected.\n	public function data( )\n	{\n		return;\n	}\n\n	/// @return array Database record (column:value array)\n	public function properties( )\n	{\n		return $this->data;\n	}\n}\n\n/// Class for Txp image records.\nclass soo_txp_img extends soo_txp_row\n{\n	/// URL of full-size image.\n	protected $full_url		= \'\';\n	/// URL of thumbnail image.\n	protected $thumb_url	= \'\';\n\n	/** Constructor.\n	 *  @param init Txp image id\n	 */\n	public function __construct( $init )\n	{\n		global $img_dir;\n		parent::__construct($init, \'txp_image\');\n		$this->full_url = hu . $img_dir . \'/\' . $this->id . $this->ext;\n		if ( $this->thumbnail )\n			$this->thumb_url = hu . $img_dir . \'/\' . $this->id . \'t\' . $this->ext;\n	}\n}\n\n/// Abstact base class for (X)HTML elements.\nabstract class soo_html extends soo_obj\n{\n// HTML element class. Instantiation takes a required \'name\' argument and an\n// optional \'atts\' array: items with keys matching HTML attributes\n// will be transferred to the new object.\n//\n// See the soo_html_img class for an example of how to extend this class.\n\n	/// @name Inherent properties\n	//@{\n	/// (X)HTML element name\n	protected $element_name	= \'\';\n	/// container (false) or empty element (true)\n	protected $is_empty		= 0;\n	/// Element content array (strings or soo_html objects)\n	protected $contents		= array();\n	//@}\n\n	/// @name Common (X)HTML attributes\n	//@{\n	protected $class			= \'\';\n	protected $dir				= \'\';\n	protected $id				= \'\';\n	protected $lang				= \'\';\n	protected $onclick			= \'\';\n	protected $ondblclick		= \'\';\n	protected $onkeydown		= \'\';\n	protected $onkeypress		= \'\';\n	protected $onkeyup			= \'\';\n	protected $onmousedown		= \'\';\n	protected $onmousemove		= \'\';\n	protected $onmouseout		= \'\';\n	protected $onmouseover		= \'\';\n	protected $onmouseup		= \'\';\n	protected $style			= \'\';\n	protected $title			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param element_name (X)HTML element name\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct($element_name, $atts, $content = null, $is_empty = 0)\n	{\n		$this->element_name($element_name)->is_empty($is_empty);\n		if ( empty($atts) )\n			$atts = array();\n		foreach ( $this as $property => $value )\n			if ( in_array($property, array_keys($atts)) )\n				$this->$property($atts[$property]);\n		if ( $content )\n			$this->contents($content);\n	}\n\n	/** Validate and set id attribute.\n	 *  Important: (X)HTML attribute, NOT a database ID!\n	 *  @param id (must begin with a letter)\n	 */\n	public function id($id)\n	{\n		if ( $id and !preg_match(\'/^[a-z]/\', strtolower(trim($id))) )\n		{\n			$this->id = \'invalid_HTML_ID_value_from_Soo_Txp_Obj\';\n			return false;\n		}\n		$this->id = $id;\n		return $this;\n	}\n\n	/** Add string|object|array to $contents array.\n	 *  @param content\n	 */\n	public function contents($content)\n	{\n		if ( ! $this->is_empty )\n		{\n			$content = is_array($content) ? $content : array($content);\n			foreach ( $content as $i => $item )\n				if ( is_null($item) )\n					unset($content[$i]);\n			$this->contents = array_merge($this->contents, $content);\n		}\n		return $this;\n	}\n\n	/** Return an attribute:value array of all (X)HTML attributes.\n	 *  Hard-coded list of excluded properties is rather lame,\n	 *  but I haven\'t thought of anything better yet.\n	 *  @return array\n	 */\n	private function html_attributes()\n	{\n		return array_diff_key($this->properties(), array_flip(array(\'element_name\', \'is_empty\', \'contents\')));\n	}\n\n	/** Create (X)HTML tag(s) string for this element.\n	 *  Recursively tags contained elements\n	 *  @return string\n	 */\n	public function tag()\n	{\n		$out = \'<\' . $this->element_name;\n\n		foreach ( $this->html_attributes() as $property => $value )\n			if ( $value or $property == \'alt\' )\n				$out .= \" $property=\\\"$value\\\"\";\n\n		if ( $this->is_empty )\n			return $out . \' />\';\n\n		$out .= \'>\' . $this->newline();\n\n		foreach ( $this->contents as $item )\n\n			if ( $item instanceof soo_html )\n				$out .= $item->tag();\n			else\n				$out .= $item;\n\n		return $out . $this->newline() . \"</$this->element_name>\" . $this->newline();\n	}\n\n	/** Convert $this->$property with htmlspecialchars().\n	 *  @param property Attribute name\n	 *  @return string (X)HTML-escaped attribute value\n	 */\n	protected function html_escape( $property )\n	{\n		$this->$property = htmlspecialchars($this->$property);\n		return $this;\n	}\n\n	private function newline()\n	{\n		return ( ! $this->is_empty and count($this->contents) > 1 ) ? n : \'\';\n	}\n}\n\n/// Class for (X)HTML anchor elements.\nclass soo_html_anchor extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $href				= \'\';\n	protected $name				= \'\';\n	protected $rel				= \'\';\n	protected $rev				= \'\';\n	protected $type				= \'\';\n	protected $hreflang			= \'\';\n	protected $charset			= \'\';\n	protected $accesskey		= \'\';\n	protected $tabindex			= \'\';\n	protected $shape			= \'\';\n	protected $coords			= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n\n	/** @param atts URI string (href value) or attribute array.\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		if ( ! is_array($atts) )\n			$atts = array(\'href\' => $atts);\n		parent::__construct( \'a\', $atts, $content );\n	}\n\n}\n\n/// Class for (X)HTML br elements.\nclass soo_html_br extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 */\n	public function __construct ( $atts = array() )\n	{\n		parent::__construct(\'br\', $atts, null, true);\n	}\n}\n\n/// Class for (X)HTML form elements\nclass soo_html_form extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $action			= \'\';\n	protected $method			= \'\';\n	protected $enctype			= \'\';\n	protected $accept_charset	= \'\';\n	protected $onsubmit			= \'\';\n	protected $onreset			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init Form action or attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $init = array(), $content = \'\' )\n	{\n		$atts = is_string($init) ? array(\'action\' => $init) : $init;\n		if ( ! isset($atts[\'method\']) )\n			$atts[\'method\'] = \'post\';\n		if ( is_array($atts[\'action\']) )\n		{\n			foreach ( $atts[\'action\'] as $k => $v )\n				$atts[\'action\'][$k] = \"$k=$v\";\n			$atts[\'action\'] = \'?\' . implode(a, $atts[\'action\']);\n		}\n		parent::__construct(\'form\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML label elements\nclass soo_html_label extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $for				= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init \'for\' attribute or array of name=>value pairs\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $init = array(), $content = \'\' )\n	{\n		if ( is_string($init) )\n			$init = array(\'for\' => $init);\n		parent::__construct(\'label\', $init, $content);\n	}\n}\n\n/// Abstract base class for (X)HTML form controls\nabstract class soo_html_form_control extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $name				= \'\';\n	protected $disabled			= \'\';\n	protected $tabindex			= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n}\n\n/// Class for (X)HTML input elements\nclass soo_html_input extends soo_html_form_control\n{\n\n	/// @name (X)HTML attributes\n	//@{\n	protected $type				= \'\';\n	protected $value			= \'\';\n	protected $checked			= \'\';\n	protected $size				= \'\';\n	protected $maxlength		= \'\';\n	protected $src				= \'\';\n	protected $alt				= \'\';\n	protected $usemap			= \'\';\n	protected $readonly			= \'\';\n	protected $accept			= \'\';\n	protected $onselect			= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param type Input type (text|checkbox|radio etc.)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 */\n	public function __construct ( $type = \'text\', $atts = array() )\n	{\n		$this->type($type);\n		parent::__construct(\'input\', $atts, null, true);\n	}\n}\n\n/// Class for (X)HTML select elements\nclass soo_html_select extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $multiple			= \'\';\n	protected $size				= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  If $content is an array, each item will be added as\n	 *  a soo_html_option element (assumes value=>text array)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (soo_html_option objects)\n	 */\n	public function __construct ( $atts = array(), $content = array() )\n	{\n		parent::__construct(\'select\', $atts);\n		if ( ! is_array($content) ) $content = array($content);\n		foreach ( $content as $i => $item )\n		{\n			if ( $item instanceof soo_html_option )\n				$this->contents($item);\n			else\n				$this->contents(new soo_html_option(array(\'value\' => $i), $item));\n		}\n	}\n}\n\n/// Class for (X)HTML option elements\nclass soo_html_option extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $value			= \'\';\n	protected $selected			= \'\';\n	protected $label			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (displayed option text)\n	 */\n	public function __construct ( $atts = array(), $content = array() )\n	{\n		parent::__construct(\'option\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML textarea elements\nclass soo_html_textarea extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $rows				= \'\';\n	protected $cols				= \'\';\n	protected $readonly			= \'\';\n	protected $onselect			= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'textarea\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML img elements\nclass soo_html_img extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $alt				= \'\';\n	protected $src				= \'\';\n	protected $width			= \'\';\n	protected $height			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init soo_txp_img object,  attribute array, or src value\n	 *  @param thumbnail Thumbnail or full image?\n	 *  @param escape HTML-escape title and alt attributes?\n	 */\n	public function __construct ( $init = array(), $thumbnail = false, $escape = true )\n	{\n		if ( $init instanceof soo_txp_img )\n		{\n			$src = $thumbnail ? $init->thumb_url : $init->full_url;\n			$init = $init->properties();\n			if ( $thumbnail )\n			{\n				if ( ! empty($init[\'thumb_h\']) )\n				{ // pre Txp 4.2 compatibility\n					$init[\'h\'] = $init[\'thumb_h\'];\n					$init[\'w\'] = $init[\'thumb_w\'];\n				}\n				else $init[\'h\'] = ( $init[\'w\'] = 0 );\n			}\n			$init[\'height\'] = $init[\'h\'];\n			$init[\'width\'] = $init[\'w\'];\n			$init[\'title\'] = $init[\'caption\'];\n			$init[\'src\'] = $src;\n			unset($init[\'id\']); // don\'t want database id as HTML id!\n		}\n		elseif ( ! is_array($init) )\n			$init[\'src\'] = $init;\n\n		parent::__construct(\'img\', $init, null, true);\n		if ( $escape )\n			$this->html_escape(\'title\')->html_escape(\'alt\');\n	}\n\n}\n\n/// Class for (X)HTML p elements\nclass soo_html_p extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'p\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML table elements\nclass soo_html_table extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $summary				= \'\';\n	protected $width				= \'\';\n	protected $border				= \'\';\n	protected $frame				= \'\';\n	protected $rules				= \'\';\n	protected $cellspacing			= \'\';\n	protected $cellpadding			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 *  @see contents() for $content options\n	 */\n	public function __construct ( $atts = array(), $content = null )\n	{\n		$this->contents($content);\n		parent::__construct( \'table\', $atts );\n	}\n\n	/** Add string|object|array to $contents array.\n	 *  If this is a soo_html_table_component object, or an array of such,\n	 *  it will be added directly. Otherwise it is assumed to be a single\n	 *  item or 2-dimensional array of such, and will be formed into a grid\n	 *  of table cells/rows.\n	 *  @param content\n	 */\n	public function contents($content)\n	{\n		if ( is_null($content) ) return $this;\n\n		$content = is_array($content) ? $content : array($content);\n		foreach ( $content as $item )\n		{\n			if ( is_object($item) and ( $item instanceof soo_html_table_component or $item instanceof soo_html_caption) )\n				$this->contents[] = $item;\n			else\n			{\n				$item = is_array($item) ? $item : array($item);\n				foreach ( $item as $i => $cell )\n					if ( ! $cell instanceof soo_html_table_component )\n						$item[$i] = new soo_html_td(array(), $cell);\n				$this->contents[] = new soo_html_tr(array(), $item);\n			}\n		}\n		return $this;\n	}\n}\n\n/// Class for (X)HTML caption elements\nclass soo_html_caption extends soo_html_table_component\n{\n	public function __construct ( $atts = array(), $content )\n	{\n		parent::__construct( \'caption\', $atts, $content );\n	}\n}\n\n/// Abstract base class for (X)HTML table components\nabstract class soo_html_table_component extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $align				= \'\';\n	protected $char					= \'\';\n	protected $charoff				= \'\';\n	protected $valign				= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param component (X)HTML element name\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $component, $atts = array(), $content = \'\' )\n	{\n		parent::__construct( $component, $atts, $content );\n	}\n}\n\n/// Class for (X)HTML thead elements\nclass soo_html_thead extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'thead\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tbody elements\nclass soo_html_tbody extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tbody\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tfoot elements\nclass soo_html_tfoot extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tfoot\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tr elements\nclass soo_html_tr extends soo_html_table_component\n{\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tr\', $atts, $content );\n	}\n}\n\n/// Abstract base class for (X)HTML table cells\nabstract class soo_html_table_cell extends soo_html_table_component\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $rowspan			= \'\';\n	protected $colspan			= \'\';\n	protected $headers			= \'\';\n	protected $abbr				= \'\';\n	protected $scope			= \'\';\n	protected $axis				= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param cell_type Element name (td, th)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $cell_type, $atts = array(), $content = \'\' )\n	{\n		parent::__construct( $cell_type, $atts, $content );\n	}\n}\n\n/// Class for (X)HTML th elements\nclass soo_html_th extends soo_html_table_cell\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'th\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML td elements\nclass soo_html_td extends soo_html_table_cell\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'td\', $atts, $content );\n	}\n\n}\n\n/// Base class for (X)HTML ol and ul elements\nabstract class soo_html_list extends soo_html\n{\n	/** Constructor.\n	 *  If $content is an array, each item that is not an array will be added\n	 *  as a soo_html_li object; each item that is an array will be added\n	 *  to the previous soo_html_li object as a new list of the same class\n	 *  (hence this is a recursive function).\n	 *  Any other content will be added as a soo_html_li object.\n	 *  @param element_name Element namd (ol or ul)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $element_name, $atts, $content, $class )\n	{\n		if ( ! is_array($content) )\n			$content = array($content);\n		$prev = null;\n		foreach ( $content as $i => &$item )\n		{\n			if ( is_array($item) )\n			{\n				if ( ! is_null($prev) and $content[$prev] instanceof soo_html_li )\n				{\n					$content[$prev]->contents(new $class($atts, $item));\n					unset($content[$i]);\n				}\n				else foreach ( $item as &$li )\n					$li = new soo_html_li(array(), $li);\n			}\n			elseif ( ! $item instanceof soo_html_li )\n				$item = new soo_html_li(array(), $item);\n			$prev = $i;\n		}\n		parent::__construct($element_name, $atts, $content);\n	}\n}\n\n/// Class for (X)HTML ol elements\nclass soo_html_ol extends soo_html_list\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'ol\', $atts, $content, __CLASS__);\n	}\n}\n\n/// Class for (X)HTML ul elements\nclass soo_html_ul extends soo_html_list\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'ul\', $atts, $content, __CLASS__);\n	}\n}\n\n/// Class for (X)HTML li elements\nclass soo_html_li extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'li\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML span elements\nclass soo_html_span extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'span\', $atts, $content);\n	}\n}\n\n/////////////////////// MLP Pack compatibility //////////////////////////\n// MLP Pack manipulates $_SERVER[\'REQUEST_URI\'], so grab it first\n\nglobal $plugin_callback;\nif( is_array($plugin_callback)\n	and $plugin_callback[0][\'function\'] == \'_l10n_pretext\' )\n		array_unshift($plugin_callback, array(\n			\'function\'	=>	\'soo_uri_mlp\',\n			\'event\'		=>	\'pretext\',\n			\'step\'		=>	\'\',\n			\'pre\'		=>	0 )\n		);\n\nfunction soo_uri_mlp()\n{\n	global $soo_request_uri;\n	$soo_request_uri =  $_SERVER[\'REQUEST_URI\'];\n}\n/////////////////////// end MLP Pack compatibility //////////////////////\n\n\n/// Class for URI query string manipulation\nclass soo_uri extends soo_obj\n{\n	/// Full URI\n	protected $full;\n\n	/// $_SERVER[\'REQUEST_URI\'] value\n	protected $request_uri;\n\n	/// $_SERVER[\'QUERY_STRING\'] value\n	protected $query_string;\n\n	/// URI query parameters\n	protected $query_params;\n\n	/** Constructor.\n	 *  Extract REQUEST_URI and QUERY_STRING from $_SERVER,\n	 *  and parse into query params and full URI.\n	 */\n	public function __construct ( )\n	{\n		global $soo_request_uri;	// MLP Pack compatibility\n		$this->request_uri = $soo_request_uri ? $soo_request_uri :\n			$_SERVER[\'REQUEST_URI\'];\n		$this->query_string = $_SERVER[\'QUERY_STRING\'];\n		$this->full = preg_replace(\'/\\/$/\', \'\', hu) . $this->request_uri();\n		parse_str($this->query_string, $this->query_params);\n	}\n\n	/// Override parent method to prevent direct property manipulation\n	public function __call( $request, $args )\n	{\n		return false;\n	}\n\n	/** Add, remove, or update a query parameter\n	 *  Then run update_from_params() to update $query_string and\n	 *  $request_uri (and the corresponding $_SERVER values) accordingly\n	 *  @param name Parameter name\n	 *  @param value Parameter value\n	 */\n	public function set_query_param ( $name, $value = null )\n	{\n		if ( is_null($value) )\n			unset($this->query_params[$name]);\n		else\n			$this->query_params[$name] = $value;\n		$this->update_from_params();\n		return $this;\n	}\n\n	/** Rebuild $query_string and $request_uri (and the corresponding\n	 *  $_SERVER values) based on the current $params array\n	 */\n	private function update_from_params ( )\n	{\n		// htmlencode ampersands now, because some servers will anyway\n		$this->query_string = http_build_query($this->query_params, \'\', \'&amp;\');\n\n		$this->request_uri = self::strip_query($this->request_uri) .\n			( $this->query_string ? \'?\' . $this->query_string : \'\' );\n		$this->full = preg_replace(\'/\\/$/\', \'\', hu) . $this->request_uri();\n\n		// then htmldecode before updating the $_SERVER array\n		$_SERVER[\'QUERY_STRING\'] = html_entity_decode($this->query_string);\n		$_SERVER[\'REQUEST_URI\'] = html_entity_decode($this->request_uri);\n	}\n\n	/** Remove the query string from a URI\n	 *  @return string\n	 */\n	public function strip_query ( $uri )\n	{\n		return preg_replace (\'/(.+)\\?.+/\', \'$1\', $uri);\n	}\n\n	/** Return the $request_uri after stripping any subdir\n	 *  (for Txp subdir installations)\n	 *  @return string\n	 */\n	private function request_uri ( )\n	{\n		if ( preg_match(\'&://[^/]+(/.+)/$&\', hu, $match) )\n		{\n			$sub_dir = $match[1];\n			return substr($this->request_uri, strlen($sub_dir));\n		}\n		return $this->request_uri;\n	}\n\n}\n\n/// Class for static utility methods\nclass soo_util\n{\n	/** Build a Txp tag string.\n	 *  @param func Txp tag name (e.g. \'article_custom\')\n	 *  @param atts Tag attributes\n	 *  @param thing Tag contents\n	 *  @return string Txp tag\n	 */\n	public static function txp_tag ( $func, $atts = array(), $thing = null )\n	{\n		$a = \'\';\n		foreach ( $atts as $k => $v )\n			$a .= \" $k=\\\"$v\\\"\";\n		return \"<txp:$func$a\" . ( is_null($thing) ? \' />\' : \">$thing</txp:$func>\" );\n	}\n\n	/** Return a Txp tag string, if it\'s still the first parse() pass.\n	 *  Allows placing a tag with dependencies before its associated controller,\n	 *  deferring parsing to the second parse() pass.\n	 *  E.g. placing a pagination tag before its associated article tag.\n	 *  @param func Txp tag name (e.g. \'article_custom\')\n	 *  @param atts Tag attributes\n	 *  @param thing Tag contents\n	 *  @return string Txp tag\n	 */\n	public static function secondpass ( $func, $atts = array(), $thing = null )\n	{\n		global $pretext;\n		if ( $pretext[\'secondpass\'] ) return; // you only live twice\n		return self::txp_tag($func, $atts, $thing);\n	}\n\n}\n', '\n/// Generic abstract base class, with a few low-level utility methods.\nabstract class soo_obj\n{\n	/** Generic getter.\n	 *  Allow calls in the form $obj->property for protected properties.\n	 */\n	public function __get( $property )\n	{\n		return isset($this->$property) ? $this->$property : null;\n	}\n\n	/** Method overloading for generic setters.\n	 *  Allow calls in the form $obj->property($value).\n	 *  Returns $this to allow method chaining.\n	 */\n	public function __call( $request, $args )\n	{\n		if ( isset($this->$request) )\n			$this->$request = array_shift($args);\n		return $this;\n	}\n\n	/** Return an object\'s class name.\n	 *  Allows object to be used in a string context.\n	 *  @return string Class name\n	 */\n	public function __toString()\n	{\n		return get_class($this);\n	}\n\n	/** Return an object\'s properties as an associative array.\n	 *  Note: includes protected and private properties.\n	 *  For public properties only, use get_object_vars($obj) directly.\n	 *  @return array\n	 */\n	public function properties()\n	{\n		return get_object_vars($this);\n	}\n\n	/** Return an object\'s properties as an indexed array.\n	 *  @return array\n	 */\n	public function property_names()\n	{\n		return array_keys($this->properties());\n	}\n\n}\n\n/// Abstract base class for SQL queries.\nabstract class soo_txp_query extends soo_obj\n{\n	/// Database table name.\n	protected $table		= \'\';\n	/// SQL WHERE expressions.\n	protected $where		= array();\n	/// SQL ORDER BY expressions.\n	protected $order_by		= array();\n	/// SQL LIMIT.\n	protected $limit		= 0;\n	/// SQL OFFSET.\n	protected $offset		= 0;\n\n	/** Constructor.\n	 *  Use $key to match a single row matching on appropriate key column\n	 *  If $key is an array, current key is the column and current value\n	 *  is the value. Otherwise column will be taken from self::key_column().\n	 *  @param table Table name\n	 *  @param key Key column value (or array) for WHERE expression\n	 */\n	public function __construct( $table, $key = null )\n	{\n		$this->table = trim($table);\n		if ( is_array($key) )\n			$this->where(key($key), current($key));\n		elseif ( $key )\n			$this->where($this->key_column($key), $key);\n	}\n\n	/** Add expression to WHERE clause.\n	 *  @param column Column name\n	 *  @param value Column value(s) (string, array, or soo_txp_select)\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		$join = $this->andor($join);\n\n		if ( is_array($value) )\n			$value = \'(\' . implode(\',\', quote_list(doSlash($value))) . \')\';\n		elseif ( $value instanceof soo_txp_select )\n			$value = \'(\' . $value->sql() . \')\';\n		else\n			$value = \"\'$value\'\";\n\n		$this->where[] = ( $join ? \"$join \" : \'\' ) .\n			self::quote($column) . \" $operator $value\";\n		return $this;\n	}\n\n	/** Add a raw expression to WHERE clause.\n	 *  Use instead of {@link where()} for complex (e.g. nested) expressions\n	 *  @param clause WHERE Expression\n	 *  @param join AND or OR\n	 */\n	public function where_clause( $clause, $join = \'\' )\n	{\n		$join = $this->andor($join);\n		$this->where[] = ( $join ? \"$join \" : \'\' ) . $clause;\n		return $this;\n	}\n\n	/** Add an IN expression to the WHERE clause.\n	 *  @param column Column name\n	 *  @param list Items to compare against\n	 *  @param join AND or OR\n	 *  @param in true = IN, false = NOT IN\n	 */\n	public function in( $column, $list, $join = \'\', $in = true )\n	{\n		$in = ( $in ? \'\' : \' not\' ) . \' in\';\n		if ( is_string($list) )\n			$list = do_list($list);\n		return $this->where($column, $list, $in, $join);\n	}\n\n	/** Alias of in() with $in = false (add a NOT IN expression).\n	 *  @param column Column name\n	 *  @param list Items to compare against\n	 *  @param join AND or OR\n	 */\n	public function not_in( $column, $list, $join = \'\' )\n	{\n		return $this->in( $column , $list , $join , false );\n	}\n\n	/** Add a MySQL REGEXP expression to the WHERE clause.\n	 *  @param pattern MySQL REGEXP pattern\n	 *  @param subject Column name or string to match\n	 *  @param join AND or OR\n	 */\n	public function regexp( $pattern, $subject, $join = \'\' )\n	{\n		return $this->where($subject, $pattern, \'regexp\', $join);\n	}\n\n	protected function andor( $join = \'and\' )\n	{\n		$join = strtolower($join);\n		return count($this->where) ?\n			( in_list($join, \'and,or\') ? $join : \'and\' ) : \'\';\n	}\n\n	/** Quote with backticks.\n	 *  Only quote items consisting of alphanumerics, $, and/or _\n	 *  @param identifier Item to quote\n	 *  @return string\n	 */\n	public static function quote( $identifier )\n	{\n		return preg_match(\'/^[a-z_$\\d]+$/i\', $identifier) ?\n			\"`$identifier`\" : $identifier;\n	}\n\n	/** Add an expression to the ORDER BY array.\n	 *  Example: $query->order_by(\'foo ASC, bar DESC\');\n	 *  @param expr Comma-separated list, or array, of expressions\n	 *  @param direction ASC or DESC\n	 */\n	public function order_by( $expr, $direction = \'\' )\n	{\n		if ( $expr )\n		{\n			if ( ! is_array($expr) ) $expr = do_list($expr);\n			foreach ( $expr as $x )\n			{\n				if ( preg_match(\'/(\\S+)\\s+(\\S+)/\', $x, $match) )\n					list( , $column, $direction) = $match;\n				else\n					$column = $x;\n				if ( in_array(strtolower($column), array(\'random\', \'rand\', \'rand()\')) )\n					$column = \'rand()\';\n				else\n					$direction = in_array(strtolower($direction), array(\'asc\', \'desc\')) ? $direction : \'\';\n				$this->order_by[] = $column . ( $direction ? \' \' . $direction : \'\');\n			}\n		}\n		return $this;\n	}\n\n	/** Alias of order_by() for a single column ASC.\n	 *  @param col Column name\n	 */\n	public function asc( $col )\n	{\n		return $this->order_by($col, \'asc\');\n	}\n\n	/** Alias of order_by() for a single column DESC.\n	 *  @param col Column name\n	 */\n	public function desc( $col )\n	{\n		return $this->order_by($col, \'desc\');\n	}\n\n	/** Add a FIELD() expression to the ORDER BY array.\n	 *  For preserving an arbitrary sort order, e.g. \'7,5,12,1\'\n	 *  Note that FIELD() is a MySQL-specific function (not standard SQL)\n	 *  @param field Column name\n	 *  @param list Comma-separated list, or array, of values in order\n	 */\n	public function order_by_field( $field, $list )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		if ( count($list) )\n			$this->order_by[] = \'field(\' . self::quote($field) . \', \' .\n				implode(\', \', quote_list(doSlash($list))) . \')\';\n		return $this;\n	}\n\n	/** Add a LIMIT to the query.\n	 *  @param limit Maximum number of items to return\n	 */\n	public function limit( $limit )\n	{\n		if ( $limit = intval($limit) )\n			$this->limit = \' limit \' . $limit;\n		return $this;\n	}\n\n	/** Add an OFFSET to the query.\n	 *  @param offset Number of items to skip\n	 */\n	public function offset( $offset )\n	{\n		if ( $offset = intval($offset) )\n			$this->offset = \' offset \' . $offset;\n		return $this;\n	}\n\n	/** Assemble and return the query clauses as a string.\n	 *  @return string\n	 */\n	protected function clause_string()\n	{\n		return implode(\' \', $this->where) .\n			( count($this->order_by) ? \' order by \' . implode(\', \', $this->order_by) : \'\' ) .\n			( $this->limit ? $this->limit : \'\' ) . ( $this->offset ? $this->offset : \'\' );\n	}\n\n	/** Number of items the query will return.\n	 *  Runs the query with COUNT() as the select expression\n	 *  @return int|false\n	 */\n	public function count()\n	{\n		return getCount($this->table, $this->clause_string() ? $this->clause_string() : \'1=1\');\n	}\n\n	/** Return the key column name for the current table.\n	 *  Some Txp tables have multiple indexes.\n	 *  If $key_value is provided, column of the matching type will be returned.\n	 *  Otherwise the numeric index will be returned in preference to the string index.\n	 *  @param key_value\n	 *  @return string\n	 */\n	public function key_column( $key_value = null )\n	{\n		$numeric_index	= array(\n			\'textpattern\'		=> \'ID\',\n			\'txp_category\'		=> \'id\',\n			\'txp_discuss\'		=> \'discussid\',\n			\'txp_file\'			=> \'id\',\n			\'txp_image\'			=> \'id\',\n			\'txp_lang\'			=> \'id\',\n			\'txp_link\'			=> \'id\',\n			\'txp_log\'			=> \'id\',\n			\'txp_users\'			=> \'user_id\',\n		);\n		$string_index		= array(\n			\'textpattern\'		=> \'Title\',\n			\'txp_category\'		=> \'name\',\n			\'txp_css\'			=> \'name\',\n			\'txp_discuss_ipban\'	=> \'ip\',\n			\'txp_discuss_nonce\'	=> \'nonce\',\n			\'txp_file\'			=> \'filename\',\n			\'txp_form\'			=> \'name\',\n			\'txp_image\'			=> \'name\',\n			\'txp_lang\'			=> \'lang\',\n			\'txp_page\'			=> \'name\',\n			\'txp_plugin\'		=> \'name\',\n			\'txp_prefs\'			=> \'name\',\n			\'txp_section\'		=> \'name\',\n			\'txp_users\'			=> \'name\',\n		);\n		if ( isset($numeric_index[$this->table]) )\n			$nx = $numeric_index[$this->table];\n		if ( isset($string_index[$this->table]) )\n			$sx = $string_index[$this->table];\n\n 		if ( is_numeric($key_value) )\n			return isset($nx) ? $nx : null;\n 		if ( is_string($key_value) )\n			return isset($sx) ? $sx : null;\n		return isset($nx) ? $nx : ( isset($sx) ? $sx : null );\n	}\n\n}\n\n/// Class for SELECT queries.\nclass soo_txp_select extends soo_txp_query\n{\n\n	/// SQL SELECT expressions.\n	protected $select		= array();\n	/// Whether to add DISTINCT\n	protected $distinct		= false;\n\n	/** Constructor.\n	 *  @param table Table name\n	 *  @param key Optional key for selecting a single record\n	 *  @param select item(s) to select\n	 */\n	public function __construct( $table, $key = null, $select = null )\n	{\n		parent::__construct($table, $key);\n		if ( $select ) $this->select($select);\n	}\n\n	/** Add items to the SELECT array.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select( $list = \'*\' )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		foreach ( $list as $col ) $this->select[] = parent::quote($col);\n		return $this;\n	}\n\n	/** Add the DISTINCT keyword to the query\n	 *  @return $this to allow method chaining\n	 */\n	public function distinct( )\n	{\n		$this->distinct = true;\n		return $this;\n	}\n\n	protected function init_query()\n	{\n		if ( ! count($this->select) ) $this->select();\n		if ( ! count($this->where) ) $this->where[] = \'1 = 1\';\n	}\n\n	/** Return a single record, or empty array if no matching records.\n	 *  @return array\n	 */\n	public function row()\n	{\n		$this->init_query();\n		return safe_row(implode(\',\', $this->select), $this->table,\n			$this->clause_string());\n	}\n\n	/** Return all records, or empty array if no matching records.\n	 *  @return array\n	 */\n	public function rows()\n	{\n		$this->init_query();\n		return safe_rows( ( $this->distinct ? \'distinct \' : \'\') .\n			implode(\',\', $this->select), $this->table, $this->clause_string());\n	}\n\n	/** Return the query as a string.\n	 *  @return string\n	 */\n	public function sql()\n	{\n		$this->init_query();\n		return \'select \' . implode(\',\', $this->select) . \' from \' . safe_pfx($this->table) . \' where \' . $this->clause_string();\n	}\n\n	/** Return result of a SELECT COUNT(*) query\n	 *  @int\n	 */\n	public function count()\n	{\n		$select = $this->select;\n		$this->select = array(\'count(*)\');\n		$r = safe_query($this->sql());\n		$this->select = $select;\n		if ( $r && $row = mysqli_fetch_row($r) )\n			return $row[0];\n	}\n}\n\n/// Class for SELECT ... LEFT JOIN queries.\n/// Currently very incomplete; needs to override most parent methods\n/// to specify which table each expression refers to.\nclass soo_txp_left_join extends soo_txp_select\n{\n	/// Join table name\n	protected $left_join;\n	/// ON expression for join\n	protected $join_on;\n	/// Left table alias\n	const t1 = \'t1\';\n	/// Join table alias\n	const t2 = \'t2\';\n\n	/** Constructor.\n	 *  @param table Left table\n	 *  @param left_join Join table\n	 *  @param col1 Key column name for left table\n	 *  @param col2 Key column name for join table\n	 */\n	public function __construct ( $table, $left_join, $col1, $col2 )\n	{\n		parent::__construct($table);\n		$this->left_join = $left_join;\n		$this->join_on = self::t1 . \'.\' . self::quote($col1) . \' = \' . self::t2 . \'.\' . self::quote($col2);\n	}\n\n	/** Like parent::quote, optionally prepending table name/alias.\n	 *  @param identifier Column name or alias\n	 *  @param prefix Table name or alias\n	 */\n	public static function quote( $identifier, $prefix = \'\' )\n	{\n		return ( $prefix ? $prefix . \'.\' : \'\' ) . parent::quote($identifier);\n	}\n\n	/** Add items to the SELECT array from the left table.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select( $list = \'*\' )\n	{\n		return self::select_from($list, self::t1);\n	}\n\n	/** Add items to the SELECT array from the join table.\n	 *  @param list	comma-separated list, or array, of items to select\n	 */\n	public function select_join( $list = \'*\' )\n	{\n		return self::select_from($list, self::t2);\n	}\n\n	private function select_from( $list, $table )\n	{\n		if ( is_string($list) ) $list = do_list($list);\n		foreach ( $list as $col ) $this->select[] = self::quote($col, $table);\n		return $this;\n	}\n\n	/** Add expression to WHERE clause, referring to the left table.\n	 *  @param column Column name\n	 *  @param value Column value\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		return parent::where(self::quote($column, self::t1), $value, $operator, $join);\n	}\n\n	/** Add expression to WHERE clause, referring to the join table.\n	 *  @param column Column name\n	 *  @param value Column value\n	 *  @param operator Comparison operator\n	 *  @param join AND or OR\n	 */\n	public function where_join( $column, $value, $operator = \'=\', $join = \'\' )\n	{\n		return parent::where(self::quote($column, self::t2), $value, $operator, $join);\n	}\n\n	/** Add an IS NULL expression, for selecting only items not in the join table\n	 *  @param column Key column in join table\n	 */\n	public function where_join_null( $column )\n	{\n		$join = parent::andor(\'\');\n		$this->where[] = ( $join ? $join . \' \' : \'\' ) . self::quote($column, self::t2) . \' is null\';\n		return $this;\n	}\n\n	/** Add a column to the ORDER BY array.\n	 *  @param col Column name from left table\n	 *  @param direction ASC or DESC\n	 */\n	public function order_by( $cols, $direction = \'\' )\n	{\n		$direction = in_array(strtolower($direction), array(\'asc\', \'desc\')) ? $direction : \'\';\n		if ( is_string($cols) ) $cols = do_list($cols);\n		foreach ( $cols as $col )\n		{\n			if ( $col == \'random\' or $col == \'rand\' or $col == \'rand()\' )\n			{\n				$col = \'rand()\';\n				$direction = \'\';\n			}\n			$this->order_by[] = self::quote($col, self::t1) . ( $direction ? \' \' . $direction : \'\');\n		}\n		return $this;\n	}\n\n	/** Override parent function; return a single record.\n	 *  @return array\n	 */\n	public function row()\n	{\n		return getRow($this->sql());\n	}\n\n	/** Override parent function; return all records\n	 *  @return array\n	 */\n	public function rows()\n	{\n		return getRows($this->sql());\n	}\n\n	/** Assemble query.\n	 *  @return string\n	 */\n	public function sql()\n	{\n		parent::init_query();\n		return \'select \' . implode(\',\', $this->select) . \' from \' . self::quote(safe_pfx($this->table)) . \' as \' . self::t1 . \' left join \' . self::quote(safe_pfx($this->left_join)) . \' as \' . self::t2 . \' on \' . $this->join_on . \' where \' . $this->clause_string();\n	}\n}\n\n/// Class for INSERT and UPDATE queries.\nclass soo_txp_upsert extends soo_txp_query\n{\n	// For use with VALUES() syntax\n	/// Columns to be explicitly set.\n	public $columns			= array();\n	/// VALUES() values.\n	public $values			= array();\n	/// VALUES() clause\n	protected $values_clause	= \'\';\n\n	// For use with SET col_name=value syntax\n	/// SET columns and values.\n	public $set				= array();\n	/// SET clause.\n	protected $set_clause	= \'\';\n\n	/** Constructor.\n	 *  Use $col to update a single row matching on appropriate key column\n	 *  (usually `name` or `id`)\n	 *  @param init Table name, soo_txp_rowset, or soo_txp_row\n	 *  @param col Key column value for WHERE expression\n	 */\n	public function __construct( $init, $col = null )\n	{\n		if ( is_scalar($init) )\n			parent::__construct($init, $col);\n		elseif ( $init instanceof soo_txp_rowset )\n		{\n			$this->table = $init->table;\n			if ( $col )\n				$this->columns = is_array($col) ? $col : do_list($col);\n			else\n				$this->columns = array_keys(current($init->data));\n			foreach ( $init->rows as $r )\n				$this->values[] = $r->data;\n		}\n		elseif ( $init instanceof soo_txp_row )\n		{\n			$this->table = $init->table;\n			if ( $col )\n				$this->columns = is_array($col) ? $col : do_list($col);\n			else\n				$this->columns = array_keys($init->data);\n			$this->values[] = $init->data;\n		}\n	}\n\n	/** Add a column:value pair to the $set array.\n	 *  @param column Column name\n	 *  @param value Column value\n	 */\n	public function set( $column, $value )\n	{\n		$this->set[$column] = $value;\n		return $this;\n	}\n\n	private function init_query()\n	{\n		if ( count($this->set) )\n		{\n			foreach ( $this->set as $col => $val )\n			{\n				$val = is_numeric($val) ? $val : \"\'$val\'\";\n				$set_pairs[] = \"$col = $val\";\n			}\n			$this->set_clause = implode(\',\', $set_pairs);\n		}\n		elseif ( count($this->values) )\n		{\n			if ( count($this->columns) )\n				$this->values_clause = \'(`\' . implode(\'`,`\', $this->columns) . \'`) \';\n			$this->values_clause .= \' values \';\n			foreach ( $this->values as $vs )\n			{\n				$this->values_clause .= \'(\';\n				foreach ( $vs as $v )\n					$this->values_clause .= ( is_numeric($v) ? $v : \"\'$v\'\" ) . \',\';\n				$this->values_clause = rtrim($this->values_clause, \',\') . \'),\';\n			}\n			$this->values_clause = rtrim($this->values_clause, \',\') . \';\';\n		}\n	}\n\n	/** Run the query.\n	 *  Runs an UPDATE query if $where is set, otherwise INSERT\n	 *  @return bool success or failure\n	 */\n	public function upsert()\n	{\n		$this->init_query();\n		if ( count($this->where) )\n			return safe_upsert($this->table, $this->set_clause, $this->clause_string());\n		if ( $this->set_clause )\n			return safe_insert($this->table, $this->set_clause);\n		if ( $this->values_clause )\n			return safe_query(\'insert into \' . safe_pfx($this->table) . $this->values_clause);\n	}\n}\n\n/// Class for DELETE queries.\nclass soo_txp_delete extends soo_txp_query\n{\n	/** Execute the DELETE query.\n	 *  @return bool	Query success or failure\n	 */\n	public function delete()\n	{\n		if ( count($this->where) )\n			return safe_delete($this->table, $this->clause_string());\n	}\n}\n\n/// Class for data results sets.\nclass soo_txp_rowset extends soo_obj\n{\n\n	/// Database table name.\n	protected $table		= \'\';\n\n	/// Array of soo_txp_row objects.\n	public $rows			= array();\n\n	/** Constructor.\n	 *  $init can be a soo_txp_select object, mysql result resource,\n	 *  or an array of records.\n	 *  If $index is provided or if $init is a soo_txp_select object,\n	 *  the $rows array will be indexed by key column values.\n	 *  @param init Data array or query object to initialize rowset\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\', $index = null )\n	{\n		if ( $init instanceof soo_txp_select )\n		{\n			$table = $init->table;\n			$index = $init->key_column();\n			$init = $init->rows();\n		}\n		if ( is_resource($init) and mysql_num_rows($init) )\n		{\n			while ( $r = mysql_fetch_assoc($init) )\n				$data[] = $r;\n			mysql_free_result($init);\n			$init = $data;\n		}\n		$this->table = $table;\n		if ( is_array($init) and count($init) )\n		{\n			foreach ( $init as $r )\n				if ( $index )\n					$this->add_row($r, $table, $r[$index]);\n				else\n					$this->add_row($r, $table);\n		}\n	}\n\n	/** Generic getter, overriding parent method.\n	 *  If $property is not a property name, look for row object\n	 *  matching this index value\n	 *  @param property Property name, or rowset index\n	 */\n	public function __get( $property )\n	{\n		if ( property_exists($this, $property) )\n			return $this->$property;\n		if ( array_key_exists($property, $this->rows) )\n			return $this->rows[$property];\n	}\n\n	/** Return an array of all values for a particular column (field).\n	 *  If $key is set, make it an associative array, using the value\n	 *  of the key column as the array index\n	 *  @param field Column (field) name\n	 *  @param key Key column name\n	 */\n	public function field_vals( $field, $key = null )\n	{\n		foreach ( $this->rows as $r )\n			if ( ! is_null($key) )\n				$out[$r->$key] = $r->$field;\n			else\n				$out[] = $r->$field;\n		return isset($out) ? $out : array();\n	}\n\n	/** Add a soo_txp_row object to $rows.\n	 *  @param data soo_txp_row object or key value\n	 *  @param table Txp table name\n	 *  @param i index value for new row in $rows array\n	 */\n	public function add_row( $data, $table = null, $i = null )\n	{\n		$table = is_null($table) ? $this->table : $table;\n		$r = $data instanceof soo_txp_row ?\n			$data : ( $table == \'txp_image\' ?\n				new soo_txp_img($data) : new soo_txp_row($data, $table) );\n		if ( is_null($i) )\n			$this->rows[] = $r;\n		else\n			$this->rows[$i] = $r;\n		return $this;\n	}\n\n	/** Split off a subset of rows as a new soo_txp_rowset object\n	 *  @param key array key for finding rows for the new set\n	 *  @param value key column value to match for rows for the new set\n	 *  @param index array index for new rowset rows\n	 *  @return soo_txp_rowset\n	 */\n	public function subset( $key, $value, $index = null )\n	{\n		$out = new self;\n		foreach ( $this->rows as $row )\n			if ( $row->$key == $value )\n				$out->add_row($row, null, is_null($index) ? null : $row->$index);\n		return $out;\n	}\n}\n\n/// Class for Joe Celko nested sets, aka modified preorder tree\nclass soo_nested_set extends soo_txp_rowset\n{\n	/** Constructor.\n	 *  $init can be a soo_txp_rowset object,\n	 *  otherwise see parent::__construct()\n	 *  @param init Data array or query object to initialize rowset\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\', $index = null )\n	{\n		if ( $init instanceof soo_txp_rowset )\n		{\n			$this->table = $init->table;\n			$this->rows = $init->rows;\n		}\n		else\n			parent::__construct($init, $table);\n	}\n\n	/** Return all rows as a nested array of row objects.\n	 *  Each array item is either a soo_txp_row object,\n	 *  or an array of such. If an array, it is the children of the\n	 *  immediately preceding item.\n	 *  This is a recursive function.\n	 *  @param rows Internal use only.\n	 *  @param rgt Internal use only.\n	 */\n	public function as_object_array( &$rows = null, $rgt = null )\n	{\n		if ( is_null($rows) )\n		{\n			$rows = $this->rows;\n			$root = current($rows);\n			$rgt = $root->rgt;\n		}\n		while ( $out[] = $node = array_shift($rows) and $node->rgt <= $rgt )\n			if ( $node->rgt > $node->lft + 1 )\n				$out[] = $this->as_object_array($rows, $node->rgt);\n 		if ( $node and $node->rgt > $rgt )\n 			array_unshift($rows, array_pop($out));\n 		if ( is_null($out[count($out) - 1]) )\n 			array_pop($out);\n		return $out;\n	}\n\n	/** Return all rows as a nested array of values.\n	 *  Each array item is either a node, as $index_column => $value_column,\n	 *  or an array of such. If an array, it is the children of the\n	 *  immediately preceding item, and has the key \'x_c\' where \'x\' is\n	 *  the parent node\'s index.\n	 *  This is a recursive function.\n	 *  @param index_column Column for node index value\n	 *  @param index_column Column for node value\n	 *  @param rows Internal use only.\n	 *  @param rgt Internal use only.\n	 */\n	public function as_array( $index_column, $value_column, &$rows = null, $rgt = null )\n	{\n		if ( is_null($rows) )\n		{\n			$rows = $this->rows;\n			$root = current($rows);\n			$rgt = $root->rgt;\n		}\n		while ( $node = array_shift($rows) and $node->rgt <= $rgt )\n		{\n			$out[$node->$index_column] = $node->$value_column;\n			if ( $node->rgt > $node->lft + 1 )\n				$out[$node->$index_column . \'_c\'] = $this->as_array($index_column, $value_column, $rows, $node->rgt);\n		}\n 		if ( $node and $node->rgt > $rgt )\n 			array_unshift($rows, $node);\n		return $out;\n	}\n\n	/** Split off a subtree of rows as a new soo_nested_set object\n	 *  @param root id of subtree root node\n	 *  @return soo_txp_rowset\n	 */\n	public function subtree( $root, $index = null )\n	{\n		$out = new self;\n		$root = $this->rows[$root];\n		foreach ( $this->rows as $row )\n			if ( $row->lft >= $root->lft and $row->rgt <= $root->rgt )\n				$out->add_row($row, null, is_null($index) ? null : $row->$index);\n		return $out;\n	}\n}\n\n/// Class for single data records.\nclass soo_txp_row extends soo_obj\n{\n	/// Database table name.\n	protected $table		= \'\';\n	/// Database record.\n	protected $data			= array();\n\n	/** Constructor.\n	 *  @param init Key value, soo_txp_select object, or data array\n	 *  @param table Txp table name\n	 */\n	public function __construct( $init = array(), $table = \'\' )\n	{\n		if ( is_scalar($init) and $table )\n			$init = new soo_txp_select($table, $init);\n		if ( $init instanceof soo_txp_select )\n		{\n			$table = $init->table;\n			$init = $init->row();\n		}\n		if ( is_array($init) )\n			foreach ( $init as $k => $v )\n				$this->data[$k] = $v;\n		$this->table = $table;\n	}\n\n	/** Generic getter, overriding parent method.\n	 *  Look for $property in the $data array first\n	 *  @param property Column or property name\n	 *  @return mixed Data field or object property\n	 */\n	public function __get( $property )\n	{\n		return isset($this->data[$property]) ? $this->data[$property]\n			: parent::__get($property);\n	}\n\n	/// Override parent method, to keep $data protected.\n	public function data( )\n	{\n		return;\n	}\n\n	/// @return array Database record (column:value array)\n	public function properties( )\n	{\n		return $this->data;\n	}\n}\n\n/// Class for Txp image records.\nclass soo_txp_img extends soo_txp_row\n{\n	/// URL of full-size image.\n	protected $full_url		= \'\';\n	/// URL of thumbnail image.\n	protected $thumb_url	= \'\';\n\n	/** Constructor.\n	 *  @param init Txp image id\n	 */\n	public function __construct( $init )\n	{\n		global $img_dir;\n		parent::__construct($init, \'txp_image\');\n		$this->full_url = hu . $img_dir . \'/\' . $this->id . $this->ext;\n		if ( $this->thumbnail )\n			$this->thumb_url = hu . $img_dir . \'/\' . $this->id . \'t\' . $this->ext;\n	}\n}\n\n/// Abstact base class for (X)HTML elements.\nabstract class soo_html extends soo_obj\n{\n// HTML element class. Instantiation takes a required \'name\' argument and an\n// optional \'atts\' array: items with keys matching HTML attributes\n// will be transferred to the new object.\n//\n// See the soo_html_img class for an example of how to extend this class.\n\n	/// @name Inherent properties\n	//@{\n	/// (X)HTML element name\n	protected $element_name	= \'\';\n	/// container (false) or empty element (true)\n	protected $is_empty		= 0;\n	/// Element content array (strings or soo_html objects)\n	protected $contents		= array();\n	//@}\n\n	/// @name Common (X)HTML attributes\n	//@{\n	protected $class			= \'\';\n	protected $dir				= \'\';\n	protected $id				= \'\';\n	protected $lang				= \'\';\n	protected $onclick			= \'\';\n	protected $ondblclick		= \'\';\n	protected $onkeydown		= \'\';\n	protected $onkeypress		= \'\';\n	protected $onkeyup			= \'\';\n	protected $onmousedown		= \'\';\n	protected $onmousemove		= \'\';\n	protected $onmouseout		= \'\';\n	protected $onmouseover		= \'\';\n	protected $onmouseup		= \'\';\n	protected $style			= \'\';\n	protected $title			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param element_name (X)HTML element name\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct($element_name, $atts, $content = null, $is_empty = 0)\n	{\n		$this->element_name($element_name)->is_empty($is_empty);\n		if ( empty($atts) )\n			$atts = array();\n		foreach ( $this as $property => $value )\n			if ( in_array($property, array_keys($atts)) )\n				$this->$property($atts[$property]);\n		if ( $content )\n			$this->contents($content);\n	}\n\n	/** Validate and set id attribute.\n	 *  Important: (X)HTML attribute, NOT a database ID!\n	 *  @param id (must begin with a letter)\n	 */\n	public function id($id)\n	{\n		if ( $id and !preg_match(\'/^[a-z]/\', strtolower(trim($id))) )\n		{\n			$this->id = \'invalid_HTML_ID_value_from_Soo_Txp_Obj\';\n			return false;\n		}\n		$this->id = $id;\n		return $this;\n	}\n\n	/** Add string|object|array to $contents array.\n	 *  @param content\n	 */\n	public function contents($content)\n	{\n		if ( ! $this->is_empty )\n		{\n			$content = is_array($content) ? $content : array($content);\n			foreach ( $content as $i => $item )\n				if ( is_null($item) )\n					unset($content[$i]);\n			$this->contents = array_merge($this->contents, $content);\n		}\n		return $this;\n	}\n\n	/** Return an attribute:value array of all (X)HTML attributes.\n	 *  Hard-coded list of excluded properties is rather lame,\n	 *  but I haven\'t thought of anything better yet.\n	 *  @return array\n	 */\n	private function html_attributes()\n	{\n		return array_diff_key($this->properties(), array_flip(array(\'element_name\', \'is_empty\', \'contents\')));\n	}\n\n	/** Create (X)HTML tag(s) string for this element.\n	 *  Recursively tags contained elements\n	 *  @return string\n	 */\n	public function tag()\n	{\n		$out = \'<\' . $this->element_name;\n\n		foreach ( $this->html_attributes() as $property => $value )\n			if ( $value or $property == \'alt\' )\n				$out .= \" $property=\\\"$value\\\"\";\n\n		if ( $this->is_empty )\n			return $out . \' />\';\n\n		$out .= \'>\' . $this->newline();\n\n		foreach ( $this->contents as $item )\n\n			if ( $item instanceof soo_html )\n				$out .= $item->tag();\n			else\n				$out .= $item;\n\n		return $out . $this->newline() . \"</$this->element_name>\" . $this->newline();\n	}\n\n	/** Convert $this->$property with htmlspecialchars().\n	 *  @param property Attribute name\n	 *  @return string (X)HTML-escaped attribute value\n	 */\n	protected function html_escape( $property )\n	{\n		$this->$property = htmlspecialchars($this->$property);\n		return $this;\n	}\n\n	private function newline()\n	{\n		return ( ! $this->is_empty and count($this->contents) > 1 ) ? n : \'\';\n	}\n}\n\n/// Class for (X)HTML anchor elements.\nclass soo_html_anchor extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $href				= \'\';\n	protected $name				= \'\';\n	protected $rel				= \'\';\n	protected $rev				= \'\';\n	protected $type				= \'\';\n	protected $hreflang			= \'\';\n	protected $charset			= \'\';\n	protected $accesskey		= \'\';\n	protected $tabindex			= \'\';\n	protected $shape			= \'\';\n	protected $coords			= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n\n	/** @param atts URI string (href value) or attribute array.\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		if ( ! is_array($atts) )\n			$atts = array(\'href\' => $atts);\n		parent::__construct( \'a\', $atts, $content );\n	}\n\n}\n\n/// Class for (X)HTML br elements.\nclass soo_html_br extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 */\n	public function __construct ( $atts = array() )\n	{\n		parent::__construct(\'br\', $atts, null, true);\n	}\n}\n\n/// Class for (X)HTML form elements\nclass soo_html_form extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $action			= \'\';\n	protected $method			= \'\';\n	protected $enctype			= \'\';\n	protected $accept_charset	= \'\';\n	protected $onsubmit			= \'\';\n	protected $onreset			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init Form action or attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $init = array(), $content = \'\' )\n	{\n		$atts = is_string($init) ? array(\'action\' => $init) : $init;\n		if ( ! isset($atts[\'method\']) )\n			$atts[\'method\'] = \'post\';\n		if ( is_array($atts[\'action\']) )\n		{\n			foreach ( $atts[\'action\'] as $k => $v )\n				$atts[\'action\'][$k] = \"$k=$v\";\n			$atts[\'action\'] = \'?\' . implode(a, $atts[\'action\']);\n		}\n		parent::__construct(\'form\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML label elements\nclass soo_html_label extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $for				= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init \'for\' attribute or array of name=>value pairs\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $init = array(), $content = \'\' )\n	{\n		if ( is_string($init) )\n			$init = array(\'for\' => $init);\n		parent::__construct(\'label\', $init, $content);\n	}\n}\n\n/// Abstract base class for (X)HTML form controls\nabstract class soo_html_form_control extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $name				= \'\';\n	protected $disabled			= \'\';\n	protected $tabindex			= \'\';\n	protected $onfocus			= \'\';\n	protected $onblur			= \'\';\n	//@}\n}\n\n/// Class for (X)HTML input elements\nclass soo_html_input extends soo_html_form_control\n{\n\n	/// @name (X)HTML attributes\n	//@{\n	protected $type				= \'\';\n	protected $value			= \'\';\n	protected $checked			= \'\';\n	protected $size				= \'\';\n	protected $maxlength		= \'\';\n	protected $src				= \'\';\n	protected $alt				= \'\';\n	protected $usemap			= \'\';\n	protected $readonly			= \'\';\n	protected $accept			= \'\';\n	protected $onselect			= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param type Input type (text|checkbox|radio etc.)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 */\n	public function __construct ( $type = \'text\', $atts = array() )\n	{\n		$this->type($type);\n		parent::__construct(\'input\', $atts, null, true);\n	}\n}\n\n/// Class for (X)HTML select elements\nclass soo_html_select extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $multiple			= \'\';\n	protected $size				= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  If $content is an array, each item will be added as\n	 *  a soo_html_option element (assumes value=>text array)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (soo_html_option objects)\n	 */\n	public function __construct ( $atts = array(), $content = array() )\n	{\n		parent::__construct(\'select\', $atts);\n		if ( ! is_array($content) ) $content = array($content);\n		foreach ( $content as $i => $item )\n		{\n			if ( $item instanceof soo_html_option )\n				$this->contents($item);\n			else\n				$this->contents(new soo_html_option(array(\'value\' => $i), $item));\n		}\n	}\n}\n\n/// Class for (X)HTML option elements\nclass soo_html_option extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $value			= \'\';\n	protected $selected			= \'\';\n	protected $label			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (displayed option text)\n	 */\n	public function __construct ( $atts = array(), $content = array() )\n	{\n		parent::__construct(\'option\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML textarea elements\nclass soo_html_textarea extends soo_html_form_control\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $rows				= \'\';\n	protected $cols				= \'\';\n	protected $readonly			= \'\';\n	protected $onselect			= \'\';\n	protected $onchange			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'textarea\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML img elements\nclass soo_html_img extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $alt				= \'\';\n	protected $src				= \'\';\n	protected $width			= \'\';\n	protected $height			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param init soo_txp_img object,  attribute array, or src value\n	 *  @param thumbnail Thumbnail or full image?\n	 *  @param escape HTML-escape title and alt attributes?\n	 */\n	public function __construct ( $init = array(), $thumbnail = false, $escape = true )\n	{\n		if ( $init instanceof soo_txp_img )\n		{\n			$src = $thumbnail ? $init->thumb_url : $init->full_url;\n			$init = $init->properties();\n			if ( $thumbnail )\n			{\n				if ( ! empty($init[\'thumb_h\']) )\n				{ // pre Txp 4.2 compatibility\n					$init[\'h\'] = $init[\'thumb_h\'];\n					$init[\'w\'] = $init[\'thumb_w\'];\n				}\n				else $init[\'h\'] = ( $init[\'w\'] = 0 );\n			}\n			$init[\'height\'] = $init[\'h\'];\n			$init[\'width\'] = $init[\'w\'];\n			$init[\'title\'] = $init[\'caption\'];\n			$init[\'src\'] = $src;\n			unset($init[\'id\']); // don\'t want database id as HTML id!\n		}\n		elseif ( ! is_array($init) )\n			$init[\'src\'] = $init;\n\n		parent::__construct(\'img\', $init, null, true);\n		if ( $escape )\n			$this->html_escape(\'title\')->html_escape(\'alt\');\n	}\n\n}\n\n/// Class for (X)HTML p elements\nclass soo_html_p extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'p\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML table elements\nclass soo_html_table extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $summary				= \'\';\n	protected $width				= \'\';\n	protected $border				= \'\';\n	protected $frame				= \'\';\n	protected $rules				= \'\';\n	protected $cellspacing			= \'\';\n	protected $cellpadding			= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 *  @see contents() for $content options\n	 */\n	public function __construct ( $atts = array(), $content = null )\n	{\n		$this->contents($content);\n		parent::__construct( \'table\', $atts );\n	}\n\n	/** Add string|object|array to $contents array.\n	 *  If this is a soo_html_table_component object, or an array of such,\n	 *  it will be added directly. Otherwise it is assumed to be a single\n	 *  item or 2-dimensional array of such, and will be formed into a grid\n	 *  of table cells/rows.\n	 *  @param content\n	 */\n	public function contents($content)\n	{\n		if ( is_null($content) ) return $this;\n\n		$content = is_array($content) ? $content : array($content);\n		foreach ( $content as $item )\n		{\n			if ( is_object($item) and ( $item instanceof soo_html_table_component or $item instanceof soo_html_caption) )\n				$this->contents[] = $item;\n			else\n			{\n				$item = is_array($item) ? $item : array($item);\n				foreach ( $item as $i => $cell )\n					if ( ! $cell instanceof soo_html_table_component )\n						$item[$i] = new soo_html_td(array(), $cell);\n				$this->contents[] = new soo_html_tr(array(), $item);\n			}\n		}\n		return $this;\n	}\n}\n\n/// Class for (X)HTML caption elements\nclass soo_html_caption extends soo_html_table_component\n{\n	public function __construct ( $atts = array(), $content )\n	{\n		parent::__construct( \'caption\', $atts, $content );\n	}\n}\n\n/// Abstract base class for (X)HTML table components\nabstract class soo_html_table_component extends soo_html\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $align				= \'\';\n	protected $char					= \'\';\n	protected $charoff				= \'\';\n	protected $valign				= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param component (X)HTML element name\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $component, $atts = array(), $content = \'\' )\n	{\n		parent::__construct( $component, $atts, $content );\n	}\n}\n\n/// Class for (X)HTML thead elements\nclass soo_html_thead extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'thead\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tbody elements\nclass soo_html_tbody extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tbody\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tfoot elements\nclass soo_html_tfoot extends soo_html_table_component\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tfoot\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML tr elements\nclass soo_html_tr extends soo_html_table_component\n{\n\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'tr\', $atts, $content );\n	}\n}\n\n/// Abstract base class for (X)HTML table cells\nabstract class soo_html_table_cell extends soo_html_table_component\n{\n	/// @name (X)HTML attributes\n	//@{\n	protected $rowspan			= \'\';\n	protected $colspan			= \'\';\n	protected $headers			= \'\';\n	protected $abbr				= \'\';\n	protected $scope			= \'\';\n	protected $axis				= \'\';\n	//@}\n\n	/** Constructor.\n	 *  @param cell_type Element name (td, th)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $cell_type, $atts = array(), $content = \'\' )\n	{\n		parent::__construct( $cell_type, $atts, $content );\n	}\n}\n\n/// Class for (X)HTML th elements\nclass soo_html_th extends soo_html_table_cell\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'th\', $atts, $content );\n	}\n}\n\n/// Class for (X)HTML td elements\nclass soo_html_td extends soo_html_table_cell\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct( \'td\', $atts, $content );\n	}\n\n}\n\n/// Base class for (X)HTML ol and ul elements\nabstract class soo_html_list extends soo_html\n{\n	/** Constructor.\n	 *  If $content is an array, each item that is not an array will be added\n	 *  as a soo_html_li object; each item that is an array will be added\n	 *  to the previous soo_html_li object as a new list of the same class\n	 *  (hence this is a recursive function).\n	 *  Any other content will be added as a soo_html_li object.\n	 *  @param element_name Element namd (ol or ul)\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $element_name, $atts, $content, $class )\n	{\n		if ( ! is_array($content) )\n			$content = array($content);\n		$prev = null;\n		foreach ( $content as $i => &$item )\n		{\n			if ( is_array($item) )\n			{\n				if ( ! is_null($prev) and $content[$prev] instanceof soo_html_li )\n				{\n					$content[$prev]->contents(new $class($atts, $item));\n					unset($content[$i]);\n				}\n				else foreach ( $item as &$li )\n					$li = new soo_html_li(array(), $li);\n			}\n			elseif ( ! $item instanceof soo_html_li )\n				$item = new soo_html_li(array(), $item);\n			$prev = $i;\n		}\n		parent::__construct($element_name, $atts, $content);\n	}\n}\n\n/// Class for (X)HTML ol elements\nclass soo_html_ol extends soo_html_list\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'ol\', $atts, $content, __CLASS__);\n	}\n}\n\n/// Class for (X)HTML ul elements\nclass soo_html_ul extends soo_html_list\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'ul\', $atts, $content, __CLASS__);\n	}\n}\n\n/// Class for (X)HTML li elements\nclass soo_html_li extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'li\', $atts, $content);\n	}\n}\n\n/// Class for (X)HTML span elements\nclass soo_html_span extends soo_html\n{\n	/** Constructor.\n	 *  @param atts Attributes (array of name=>value pairs)\n	 *  @param content Element content (string, soo_html object, or array thereof)\n	 */\n	public function __construct ( $atts = array(), $content = \'\' )\n	{\n		parent::__construct(\'span\', $atts, $content);\n	}\n}\n\n/////////////////////// MLP Pack compatibility //////////////////////////\n// MLP Pack manipulates $_SERVER[\'REQUEST_URI\'], so grab it first\n\nglobal $plugin_callback;\nif( is_array($plugin_callback)\n	and $plugin_callback[0][\'function\'] == \'_l10n_pretext\' )\n		array_unshift($plugin_callback, array(\n			\'function\'	=>	\'soo_uri_mlp\',\n			\'event\'		=>	\'pretext\',\n			\'step\'		=>	\'\',\n			\'pre\'		=>	0 )\n		);\n\nfunction soo_uri_mlp()\n{\n	global $soo_request_uri;\n	$soo_request_uri =  $_SERVER[\'REQUEST_URI\'];\n}\n/////////////////////// end MLP Pack compatibility //////////////////////\n\n\n/// Class for URI query string manipulation\nclass soo_uri extends soo_obj\n{\n	/// Full URI\n	protected $full;\n\n	/// $_SERVER[\'REQUEST_URI\'] value\n	protected $request_uri;\n\n	/// $_SERVER[\'QUERY_STRING\'] value\n	protected $query_string;\n\n	/// URI query parameters\n	protected $query_params;\n\n	/** Constructor.\n	 *  Extract REQUEST_URI and QUERY_STRING from $_SERVER,\n	 *  and parse into query params and full URI.\n	 */\n	public function __construct ( )\n	{\n		global $soo_request_uri;	// MLP Pack compatibility\n		$this->request_uri = $soo_request_uri ? $soo_request_uri :\n			$_SERVER[\'REQUEST_URI\'];\n		$this->query_string = $_SERVER[\'QUERY_STRING\'];\n		$this->full = preg_replace(\'/\\/$/\', \'\', hu) . $this->request_uri();\n		parse_str($this->query_string, $this->query_params);\n	}\n\n	/// Override parent method to prevent direct property manipulation\n	public function __call( $request, $args )\n	{\n		return false;\n	}\n\n	/** Add, remove, or update a query parameter\n	 *  Then run update_from_params() to update $query_string and\n	 *  $request_uri (and the corresponding $_SERVER values) accordingly\n	 *  @param name Parameter name\n	 *  @param value Parameter value\n	 */\n	public function set_query_param ( $name, $value = null )\n	{\n		if ( is_null($value) )\n			unset($this->query_params[$name]);\n		else\n			$this->query_params[$name] = $value;\n		$this->update_from_params();\n		return $this;\n	}\n\n	/** Rebuild $query_string and $request_uri (and the corresponding\n	 *  $_SERVER values) based on the current $params array\n	 */\n	private function update_from_params ( )\n	{\n		// htmlencode ampersands now, because some servers will anyway\n		$this->query_string = http_build_query($this->query_params, \'\', \'&amp;\');\n\n		$this->request_uri = self::strip_query($this->request_uri) .\n			( $this->query_string ? \'?\' . $this->query_string : \'\' );\n		$this->full = preg_replace(\'/\\/$/\', \'\', hu) . $this->request_uri();\n\n		// then htmldecode before updating the $_SERVER array\n		$_SERVER[\'QUERY_STRING\'] = html_entity_decode($this->query_string);\n		$_SERVER[\'REQUEST_URI\'] = html_entity_decode($this->request_uri);\n	}\n\n	/** Remove the query string from a URI\n	 *  @return string\n	 */\n	public function strip_query ( $uri )\n	{\n		return preg_replace (\'/(.+)\\?.+/\', \'$1\', $uri);\n	}\n\n	/** Return the $request_uri after stripping any subdir\n	 *  (for Txp subdir installations)\n	 *  @return string\n	 */\n	private function request_uri ( )\n	{\n		if ( preg_match(\'&://[^/]+(/.+)/$&\', hu, $match) )\n		{\n			$sub_dir = $match[1];\n			return substr($this->request_uri, strlen($sub_dir));\n		}\n		return $this->request_uri;\n	}\n\n}\n\n/// Class for static utility methods\nclass soo_util\n{\n	/** Build a Txp tag string.\n	 *  @param func Txp tag name (e.g. \'article_custom\')\n	 *  @param atts Tag attributes\n	 *  @param thing Tag contents\n	 *  @return string Txp tag\n	 */\n	public static function txp_tag ( $func, $atts = array(), $thing = null )\n	{\n		$a = \'\';\n		foreach ( $atts as $k => $v )\n			$a .= \" $k=\\\"$v\\\"\";\n		return \"<txp:$func$a\" . ( is_null($thing) ? \' />\' : \">$thing</txp:$func>\" );\n	}\n\n	/** Return a Txp tag string, if it\'s still the first parse() pass.\n	 *  Allows placing a tag with dependencies before its associated controller,\n	 *  deferring parsing to the second parse() pass.\n	 *  E.g. placing a pagination tag before its associated article tag.\n	 *  @param func Txp tag name (e.g. \'article_custom\')\n	 *  @param atts Tag attributes\n	 *  @param thing Tag contents\n	 *  @return string Txp tag\n	 */\n	public static function secondpass ( $func, $atts = array(), $thing = null )\n	{\n		global $pretext;\n		if ( $pretext[\'secondpass\'] ) return; // you only live twice\n		return self::txp_tag($func, $atts, $thing);\n	}\n\n}\n', '5175dc75c4ad32f7e660ebe7d2424cdd', '', '', 2, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_multidoc', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '2.0.3', 'Create structured multi-page documents', '<h1>soo_multidoc</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com/\">Textpattern</a>.</p>\n\n<p><strong>Multidoc</strong> is a system for creating and managing structured, multiple-page documents in the <a href=\"http://textpattern.com/\">Textpattern content management system</a>.</p>\n\n<p>This is just a bare-bones tag reference. All of these tags require that you have set up the <strong>Multidoc</strong> system on your Textpattern website. A full <a href=\"http://ipsedixit.net/txp/24/multidoc\">User Guide</a> is available at the <a href=\"http://ipsedixit.net/\">author&#8217;s website</a>.</p>\n\n <div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#soo_multidoc_link\">soo_multidoc_link</a></li>\n		<li><a href=\"#soo_multidoc_pager\">soo_multidoc_pager</a></li>\n		<li><a href=\"#soo_multidoc_page_number\">soo_multidoc_page_number</a></li>\n		<li><a href=\"#soo_multidoc_toc\">soo_multidoc_toc</a></li>\n		<li><a href=\"#soo_multidoc_page_title\">soo_multidoc_page_title</a></li>\n		<li><a href=\"#soo_multidoc_breadcrumbs\">soo_multidoc_breadcrumbs</a></li>\n		<li><a href=\"#soo_if_multidoc\">soo_if_multidoc</a></li>\n		<li><a href=\"#soo_if_multidoc_linear\">soo_if_multidoc_linear</a></li>\n		<li><a href=\"#soo_if_multidoc_link\">soo_if_multidoc_link</a></li>\n	</ul></li>\n	<li><a href=\"#history\">Version history</a></li>\n</ul>\n\n </div>\n\n<h2 id=\"tags\">Tags</h2>\n\n<h3 id=\"soo_multidoc_link\">soo_multidoc_link</h3>\n\n<h4>Usage</h4>\n\n<p>Generates an <span class=\"caps\">HTML</span> anchor element, based on document relationship. Can be used as a single or container tag. Requires individual article context.</p>\n\n<pre>&lt;txp:soo_multidoc_link rel=&quot;Prev&quot; /&gt;\n&lt;txp:soo_multidoc_link rel=&quot;Next&quot;&gt;Continue reading ... &lt;/txp:soo_multidoc_link&gt;</pre>\n\n<p>When used as a single tag, the <code>text</code> attribute value is used for the link text (see below).</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>rel=&quot;LinkType&quot;</code> The <a href=\"http://www.w3.org/TR/REC-html40/types.html#type-links\">link type</a> describing the linked document&#8217;s relationship to the current page</li>\n	<li><code>add_title=&quot;boolean&quot;</code> Add the linked document&#8217;s title to the link text</li>\n	<li><code>text=&quot;link text</code> Link text when in single-tag mode. The tokens <code>{rel}</code> and <code>{title}</code> will be replaced by their respective values for the link. The <span class=\"default\">default</span> is <code>{rel}</code>.</li>\n	<li><code>class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for the anchor tag</li>\n	<li><code>active_class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value if <code>rel</code> refers to the current page (tag will be a <code>span</code> instead of <code>a</code>)</li>\n	<li><code>html_id=&quot;HTML ID&quot;</code> <span class=\"caps\">HTML</span> ID attribute value for the anchor tag</li>\n	<li><code>wraptag=&quot;tag name&quot;</code> Tag name (no brackets) for element to wrap the anchor</li>\n</ul>\n\n\n<h3 id=\"soo_multidoc_pager\">soo_multidoc_pager</h3>\n\n<h4>Usage</h4>\n\n<p>Generates a numbered page navigation widget. Requires individual article context.</p>\n\n<pre>&lt;txp:soo_multidoc_pager /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>limit=&quot;integer&quot;</code> Number of pages to show before and after the current page before inserting the <code>placeholder</code> text. Default is <code>0</code>, no limit (show all).</li>\n	<li><code>placeholder=&quot;text&quot;</code> Text to show for missing pages (see <code>limit</code>). Default is <code>&amp;hellip;</code> (horizontal ellipsis).</li>\n	<li><code>class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for anchor tags</li>\n	<li><code>active_class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for the <code>span</code> tag for the current page</li>\n	<li><code>html_id=&quot;HTML ID&quot;</code> <span class=\"caps\">HTML</span> ID attribute value for the wraptag</li>\n	<li><code>wraptag=&quot;tag name&quot;</code> Tag name (no brackets) for element to wrap the anchor</li>\n	<li><code>break=&quot;mixed&quot;</code> Tag name (no brackets) or text to add between items</li>\n	<li><code>wrapclass=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute for the wraptag</li>\n	<li><code>breakclass=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute for the breaktag</li>\n</ul>\n\n<h3 id=\"soo_multidoc_page_number\">soo_multidoc_page_number</h3>\n\n<h4>Usage</h4>\n\n<p>Outputs current page number and total number of pages. Requires individual article context.</p>\n\n<pre>&lt;txp:soo_multidoc_page_number /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for the output tag</li>\n	<li><code>html_id=&quot;HTML ID&quot;</code> <span class=\"caps\">HTML</span> ID attribute value for the output tag</li>\n	<li><code>wraptag=&quot;tag name&quot;</code> Tag name (no brackets) for output tag. Default is &#8220;span&#8221;</li>\n	<li><code>format=&quot;format string&quot;</code> Default is &#8220;Page {page} of {total}&#8221;. The tag will output this string, after any occurences of &#8220;{page}&#8221; have been replaced with the current page number; likewise for &#8220;{total}&#8221; and the total number of pages.</li>\n</ul>\n\n<h3 id=\"soo_multidoc_toc\">soo_multidoc_toc</h3>\n\n<h4>Usage</h4>\n\n<p>Generates a structured table of contents. Requires individual article context.</p>\n\n<pre>&lt;txp:soo_multidoc_toc /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for the list tag</li>\n	<li><code>active_class=&quot;HTML class&quot;</code> <span class=\"caps\">HTML</span> class attribute value for the <code>span</code> tag for the current page</li>\n	<li><code>html_id=&quot;HTML ID&quot;</code> <span class=\"caps\">HTML</span> ID attribute value for the list tag</li>\n	<li><code>wraptag=&quot;tag name&quot;</code> Tag name (no brackets) for type of list. <strong>Must be &#8220;ul&#8221; or &#8220;ol&#8221;</strong>; the default is &#8220;ul&#8221;</li>\n	<li><code>root=&quot;mixed&quot;</code> starting point for the table of contents. If empty (the default), will show the entire table. If set to an article ID number, will show only the pages below that article in the document tree. If set to &#8220;this&#8221; or any other word, will use the current page as the root.</li>\n	<li><code>add_start=&quot;boolean&quot;</code> If set, the root page is added as the first item in the table. Unset by default.</li>\n</ul>\n\n<h3 id=\"soo_multidoc_page_title\">soo_multidoc_page_title</h3>\n\n<h4>Usage</h4>\n\n<p>Drop-in replacement for the core <code>title</code> tag. On a <strong>Multidoc</strong> page, adds the Start page title before the page title (unless it is the Start page). Otherwise the tag simply returns standard <code>title</code> output.</p>\n\n<pre>&lt;txp:soo_multidoc_page_title /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>separator=&quot;text&quot;</code> text between title segments; default is &#8220;: &#8220;</li>\n</ul>\n\n<h3 id=\"soo_multidoc_breadcrumbs\">soo_multidoc_breadcrumbs</h3>\n\n<h4>Usage</h4>\n\n<p>Output a linked breadcrumb trail, useful for hierarchical collections.</p>\n\n<pre>&lt;txp:soo_multidoc_breadcrumbs /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>separator=&quot;text&quot;</code> text between segments; default is &#8220;: &#8220;</li>\n</ul>\n\n<h3 id=\"soo_if_multidoc\">soo_if_multidoc</h3>\n\n<h4>Usage</h4>\n\n<p>Conditional tag.</p>\n\n<pre>&lt;txp:soo_if_multidoc&gt;\n    ...show if true...\n&lt;txp:else /&gt;\n    ...show if false...\n&lt;/txp:soo_if_multidoc&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>start_id=&quot;Txp article ID&quot;</code> comma-separated list of Txp article IDs. Empty by default.</li>\n</ul>\n\n<p><strong>soo_if_multidoc</strong> evaluates whether or not the current page belongs to a <strong>Multidoc</strong> collection. If <code>start_id</code> is set, it evaluates whether or not the current page belongs to one of the specified collections.</p>\n\n<p>Typically you would use this in an article form, or in a form called by an article form.</p>\n\n<h3 id=\"soo_if_multidoc_linear\">soo_if_multidoc_linear</h3>\n\n<h4>Usage</h4>\n\n<p>Conditional tag. Evaluates to true if the current page belongs to a linear <strong>Multidoc</strong> collection, i.e., a collection in which all pages except Start are immediate children of Start.</p>\n\n<pre>&lt;txp:soo_if_multidoc_linear&gt;\n    ...show if true...\n&lt;txp:else /&gt;\n    ...show if false...\n&lt;/txp:soo_if_multidoc_linear&gt;</pre>\n\n<h4>Attributes</h4>\n\n<p>None.</p>\n\n<h3 id=\"soo_if_multidoc_link\">soo_if_multidoc_link</h3>\n\n<h4>Usage</h4>\n\n<p>Conditional tag. Use to compare the current page&#8217;s <strong>up</strong>, <strong>next</strong>, or <strong>prev</strong> link to a specified ID or link type.</p>\n\n<pre>&lt;txp:soo_if_multidoc_link&gt;\n    ...show if true...\n&lt;txp:else /&gt;\n    ...show if false...\n&lt;/txp:soo_if_multidoc_link&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>rel=&quot;up|next|prev&quot;</code> The link (relative to the current page) to check</li>\n	<li><code>id=&quot;integer&quot;</code> The Txp article ID to compare against the link</li>\n	<li><code>link_type=&quot;LinkType&quot;</code> The <a href=\"http://www.w3.org/TR/REC-html40/types.html#type-links\">link type</a> to compare against the link</li>\n</ul>\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>2.0.3 (2020-02-04)</h3>\n\n<ul>\n	<li><span class=\"caps\">PHP</span> 7+ compatibility update (requires <span class=\"caps\">PHP</span> 5.4+)</li>\n</ul>\n\n<h3>2.0.2 (2017-02-15)</h3>\n\n<ul>\n	<li>Textpattern 4.6 compatibility update</li>\n</ul>\n\n<h3>2.0.1 (2011-01-18)</h3>\n\n<ul>\n	<li>New <code>text</code> attribute for <code>soo_multidoc_link</code>.</li>\n	<li>New tags:\n	<ul>\n		<li><code>soo_if_multidoc_linear</code>: Is this a linear (one-dimensional) collection?</li>\n		<li><code>soo_if_multidoc_link</code>: Compare the <strong>up</strong>, <strong>next</strong>, or <strong>prev</strong> link from the current article against link type or article ID</li>\n	</ul></li>\n	</ul>\n\n<h3>2.0.0 (2011-01-07)</h3>\n\n<ul>\n	<li>Bugfix for <code>soo_multidoc_link</code> when combining rel types (e.g. rel=&#8220;prev Section&#8221;)</li>\n</ul>\n\n<h3>2.0.b.3 (1/3/2011)</h3>\n\n<ul>\n	<li>Miscellaneous tag warning fixes</li>\n	<li>Code cleaning to take advantage of the version 2 move to storing Multidoc data in its own table</li>\n</ul>\n\n<h3>2.0.b.2 (9/14/2010)</h3>\n\n<ul>\n	<li>Bugfix: collections consisting solely of &#8220;only children&#8221; and with three or more articles triggered a recursion error.</li>\n</ul>\n\n<h3>2.0.b.1 (9/1/2010)</h3>\n\n<ul>\n	<li>Comprehensive backend overhaul, introducing:\n	<ul>\n		<li>Easier collection management, through the new companion soo_multidoc_admin plugin (required)</li>\n		<li>Faster page rendering</li>\n	</ul></li>\n	</ul>\n\n<h3>1.0.b.3 (1/27/2010)</h3>\n\n<ul>\n	<li>Articles without <code>live</code> or <code>sticky</code> status now excluded</li>\n	<li>Expired articles now excluded or not, according to site preference</li>\n	<li>New preference for showing past, future, or all articles</li>\n</ul>\n\n<h3>1.0.b.2 (9/18/2009)</h3>\n\n<ul>\n	<li>New tags:\n	<ul>\n		<li><code>soo_multidoc_page_title</code> (drop-in replacement for <code>page_title</code>)</li>\n		<li><code>soo_multidoc_breadcrumbs</code> breadcrumb trail within Collections</li>\n	</ul></li>\n	<li>changed to new <strong>soo_plugin_pref</strong> plugin for preference management</li>\n</ul>\n\n<h3>1.0.b.1 (7/5/2009)</h3>\n\n<ul>\n	<li>adapted to v1.0.b.1 of the underlying <strong>soo_txp_obj</strong> library</li>\n	<li>code cleaning</li>\n	<li>downshift to v2.0 of the <span class=\"caps\">GPL</span></li>\n</ul>\n\n<h3>1.0.a.7 (6/2/2009)</h3>\n\n<ul>\n	<li><code>soo_multidoc_pager</code>:\n	<ul>\n		<li>changed <code>html_id</code> to only apply to <code>wraptag</code></li>\n		<li>added <code>wrapclass</code> and <code>breakclass</code> attributes</li>\n		<li>generally improved <code>break</code> behavior</li>\n	</ul></li>\n	<li><code>soo_multidoc_article</code> and <code>soo_multidoc_article_custom</code> no longer needed, and have been eliminated (thanks to <a href=\"http://txp-plugins.netcarving.com/\">net-carver</a> for the suggestion to use MySQL temporary tables, allowing the replacement of about 270 lines of code with about 12)</li>\n</ul>\n\n<h3>1.0.a.6 (5/23/2009)</h3>\n\n<ul>\n	<li>Bug fix for using custom field #10 or higher</li>\n	<li>Bug fix for error message when there are no Multidoc articles</li>\n	<li>Now uses v0.2 of <strong>soo_plugin_prefs</strong></li>\n</ul>\n\n<h3>1.0.a.5 (5/1/2009)</h3>\n\n<ul>\n	<li>Added <code>&lt;txp:soo_multidoc_page_number /&gt;</code>.</li>\n	<li>Fixed a bug regarding multiple <code>soo_multidoc_pager</code> tags on the same page.</li>\n	<li>Added compatability with <strong>soo_plugin_prefs</strong> plugin.</li>\n</ul>\n\n<h3>1.0.a.4 (4/2/2009)</h3>\n\n<ul>\n	<li>Added <code>limit</code> and <code>placeholder</code> attributes to <code>&lt;txp:soo_multidoc_pager /&gt;</code>.</li>\n</ul>\n\n<h3>1.0.a.3 (2/6/2009)</h3>\n\n<ul>\n	<li>Fixed <code>add_title</code> bug in <code>&lt;txp:soo_multidoc_link /&gt;</code> (caused during upgrade to the newer version of the <strong>soo_txp_obj</strong> library).</li>\n</ul>\n\n<h3>1.0.a.2 (2/5/2009)</h3>\n\n<ul>\n	<li>No significant changes, but based on a newer version of the <strong>soo_txp_obj</strong> library.</li>\n</ul>\n\n<h3>1.0.a.1 (2/4/2009)</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_multidoc_link\')\n		->register(\'soo_multidoc_pager\')\n		->register(\'soo_multidoc_page_number\')\n		->register(\'soo_multidoc_toc\')\n		->register(\'soo_multidoc_page_title\')\n		->register(\'soo_multidoc_breadcrumbs\')\n		->register(\'soo_if_multidoc\')\n		->register(\'soo_if_multidoc_linear\')\n		->register(\'soo_if_multidoc_link\')\n		;\n}\n\nrequire_plugin(\'soo_txp_obj\');\n@require_plugin(\'soo_plugin_pref\');		// optional\n\n  //---------------------------------------------------------------------//\n //									Globals								//\n//---------------------------------------------------------------------//\n\nif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'plugin_prefs.soo_multidoc\',\'1,2\');\n	add_privs(\'plugin_lifecycle.soo_multidoc\',\'1,2\');\n	register_callback(\'soo_multidoc_prefs\', \'plugin_prefs.soo_multidoc\');\n	register_callback(\'soo_multidoc_prefs\', \'plugin_lifecycle.soo_multidoc\');\n}\n\nglobal $soo_multidoc;\n$soo_multidoc = function_exists(\'soo_plugin_pref_vals\') ?\n	array_merge(soo_multidoc_defaults(true), soo_plugin_pref_vals(\'soo_multidoc\'))\n	: soo_multidoc_defaults(true);\n$soo_multidoc = array_merge($soo_multidoc, array(\n	\'init\'		=>	false,\n	\'status\'	=>	false,\n	\'id_parent\'	=>	\'\',\n	\'data\'		=>	\'\',\n));\n\nfunction soo_multidoc_prefs( $event, $step )\n{\n	if ( function_exists(\'soo_plugin_pref\') )\n		return soo_plugin_pref($event, $step, soo_multidoc_defaults());\n	if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n		$plugin = substr($event, 12);\n		$message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n			gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\') .\n			\' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n		pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n	}\n}\n\nfunction soo_multidoc_defaults( $vals_only = false )\n{\n	$defaults = array(\n		\'list_all\'	=>	array(\n			\'val\'	=>	0,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Show Multidoc sub-pages in article lists?\',\n		),\n		\'posted_time\'	=>	array(\n			\'val\'	=>	\'past\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Show articles posted &lsquo;past&rsquo;, &lsquo;future&rsquo;, or &lsquo;any&rsquo;\',\n		),\n	);\n	if ( $vals_only )\n		foreach ( $defaults as $name => $arr )\n			$defaults[$name] = $arr[\'val\'];\n	return $defaults;\n}\n\n  //---------------------------------------------------------------------//\n //							MLP Pack definitions						//\n//---------------------------------------------------------------------//\n\ndefine(\'SOO_MULTIDOC_PREFIX\', \'soo_mdoc\');\nglobal $soo_multidoc_strings;\n$soo_multidoc_strings = array(\n	\'start\'	=>	\'start\',\n	\'up\'	=>	\'up\',\n	\'next\'	=>	\'next\',\n	\'prev\'	=>	\'prev\',\n);\n\nregister_callback(\'soo_multidoc_enumerate_strings\', \'l10n.enumerate_strings\');\n\nfunction soo_multidoc_enumerate_strings( $event, $step = \'\', $pre = 0 )\n{\n	global $soo_multidoc_strings;\n	$r = array(\n		\'owner\'		=> \'soo_multidoc\',\n		\'prefix\'	=> SOO_MULTIDOC_PREFIX,\n		\'lang\'		=> \'en-us\',\n		\'event\'		=> \'public\',\n		\'strings\'	=> $soo_multidoc_strings,\n				);\n	return $r;\n}\n\nfunction soo_multidoc_gTxt( $what , $args = array() )\n{\n	global $textarray;\n	global $soo_multidoc_strings;\n\n	$key = SOO_MULTIDOC_PREFIX . \'-\' . $what;\n	$key = strtolower($key);\n\n	if(isset($textarray[$key]))\n		$str = $textarray[$key];\n\n	else\n	{\n		$key = strtolower($what);\n\n		if( isset( $soo_multidoc_strings[$key] ) )\n			$str = $soo_multidoc_strings[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n\n\n  //---------------------------------------------------------------------//\n //									Classes								//\n//---------------------------------------------------------------------//\n\nclass soo_multidoc_rowset extends soo_nested_set\n{\n	public function has_subnode( $a, $b )\n	// Boolean: is $b a subnode of $a?\n	{\n		return ( $this->$b->lft > $this->$a->lft and $this->$b->rgt < $this->$a->rgt );\n	}\n\n	public function find_by_link_type( $link_type, $current = null, $dir = \'next\' )\n	{\n		$rs = $this->rows;\n		if ( $dir == \'prev\' )\n			$rs = array_reverse($rs, true);\n		$ids = array_keys($rs);\n		$link_type = strtolower($link_type);\n\n		if ( $current and in_array($current, $ids) )\n		{\n			$id_position = array_search($current, $ids);\n			if ( $dir == \'prev\' )\n				$id_position++;\n			$searchable_ids = array_flip(array_slice($ids, $id_position));\n			$rs = array_intersect_key($rs, $searchable_ids);\n		}\n\n		foreach ( $rs as $k => $r )\n			if ( $r->link_type == $link_type )\n				return $k;\n	}\n}\n\n  //---------------------------------------------------------------------//\n //									Tags								//\n//---------------------------------------------------------------------//\n\nfunction soo_multidoc_link( $atts, $thing = null )\n{\n// Output tag: display an HTML anchor\n// Requires article context\n\n	extract(lAtts(array(\n		\'rel\'			=>	\'\',\n		\'add_title\'		=>	\'\',\n\n		// {rel} and {title} available as tokens\n		\'text\'			=>	\'{rel}\',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n 		\'wraptag\'		=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	foreach ( array(\'start\', \'up\', \'next\', \'prev\') as $type )\n		$reserved_rel[$type] = $$type = strtolower(soo_multidoc_gTxt($type));\n\n	global $thisarticle;\n	$rowset = &$soo_multidoc[\'rowset\'];\n	$thisid = $thisarticle[\'thisid\'];\n	$rel = trim($rel);\n\n	// $rel value may be space-separated list of link types\n	// this tag allows only \'prev\' or \'next\' in combination with another type\n	if ( preg_match(\"/^($next|$prev)\\s+(\\w+)/i\", $rel, $match) )\n	{\n		$rel_dir = strtolower($match[1]);\n		$rel_type = strtolower($match[2]);\n		$link_id = $rowset->find_by_link_type($rel_type, $thisid, $rel_dir);\n\n		// if I have an ancestor of the requested link type, that ancestor will\n		// be the prev link of that type, which isn\'t what the user wants.\n		// So continue back one more step.\n		if ( $rel_dir == \'prev\' and $rowset->has_subnode($link_id, $thisid) )\n		{\n			$next_link = $rowset->find_by_link_type($rel_type, $link_id, $rel_dir);\n			if ( $next_link != $link_id and is_numeric($next_link) )\n				$link_id = $next_link;\n			else\n				unset($link_id);\n		}\n	}\n	elseif ( in_array($relcomp = strtolower($rel), $reserved_rel) )\n	{\n		if ( $relcomp == $start )\n			$link_id = $soo_multidoc[\'id_root\'][$thisid];\n		elseif ( $relcomp == $up )\n		{\n			$link_id = $soo_multidoc[\'id_parent\'][$thisid];\n			if ( ! intval($link_id) )\n				$link_id = 0;\n		}\n		else	// next or prev\n		{\n			$next_array = $soo_multidoc[\'next_array\'];\n			if ( $relcomp == $prev )\n				$next_array = array_reverse($next_array);\n			$i = array_search($thisid, $next_array);\n			$link_id = isset($next_array[$i + 1]) ? $next_array[$i + 1] : 0;\n		}\n\n	}\n	else\n		$link_id = $rowset->find_by_link_type($rel);\n\n	if ( ! empty($link_id) )\n		$url = $soo_multidoc[\'data\'][$link_id][\'url\'];\n\n	if ( ! isset($url) ) return false;\n\n	if ( $add_title )\n		$thing .= $rowset->$link_id->title;\n\n	if ( ! $thing )\n		$thing = str_replace(array(\'{rel}\', \'{title}\'), array($rel, $rowset->$link_id->title), $text);\n\n	if ( $link_id == $thisid )\n		$tag = new soo_html_span(array(\'class\' => $active_class));\n\n	else\n		$tag = new soo_html_anchor(array(\'href\' => $url, \'rel\' => $rel));\n\n	$tag->contents(parse($thing));\n\n	if ( $wraptag and class_exists($tag_class = \'soo_html_\' . $wraptag) )\n	{\n		$wraptag = new $tag_class;\n		return $wraptag->contents($tag)->class($class)->\n			id($html_id)->\n			tag();\n	}\n	else\n	{\n		$tag->id($html_id);\n		if ( $link_id != $thisid )\n			$tag->class($class);\n		return $tag->tag();\n	}\n}\n///////////////////// end of soo_multidoc_link() ///////////////////////\n\nfunction soo_multidoc_pager( $atts )\n{\n// Output tag: display a simple list of page links\n// Requires article context\n\n	extract(lAtts(array(\n		\'limit\'			=>	0,\n		\'placeholder\'	=>	\' &hellip; \',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n 		\'wraptag\'		=>	\'\',\n 		\'wrapclass\'		=>	\'\',\n 		\'break\'			=>	\'\',\n 		\'breakclass\'	=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n\n	$wraptag = trim(strtolower($wraptag));\n	if ( $wraptag == \'table\' )\n		$break = \'td\';\n	elseif ( $wraptag == \'ul\' or $wraptag == \'ol\' )\n		$break = \'li\';\n\n	if ( $break )\n	{\n		$break_obj = \'soo_html_\' . trim(strtolower($break));\n		if ( ! class_exists($break_obj) )\n			$break_obj = null;\n		else {\n			$break = \'\';\n			$test_obj = new $break_obj;\n			$empty_break = $test_obj->is_empty;\n		}\n	}\n	else\n		$break_obj = null;\n\n	$page_ids = $soo_multidoc[\'next_array\'];\n\n	$total = count($page_ids);\n	$page_nums = array_combine($page_ids, range(1, $total));\n	$this_num = $page_nums[$thisid];\n\n	$w_start = max(1,\n		min($this_num - $limit, $total - ( $limit * 2 ) + 1));\n	$w_end = min($w_start + ( $limit * 2 ) - 1, $total);\n\n	$show_nums = array_unique(array_merge(\n		array(1), range($w_start, $w_end), array($total)\n	));\n\n	$objs = array();\n\n	while ( $n = array_shift($show_nums) )\n	{\n		if ( $n == $this_num )\n			$objs[] = new soo_html_span(array(\'class\' => $active_class), $n);\n		else\n			$objs[] = new soo_html_anchor(array(\n				\'href\' => $soo_multidoc[\'data\'][$page_ids[$n - 1]][\'url\'],\n				\'class\' => $class), $n)\n			;\n\n		$fill = $show_nums ?\n			( $show_nums[0] > $n + 1 ? $placeholder : $break ) : \'\';\n		if ( $fill )\n			$objs[] = new soo_html_span(\'\', $fill);\n	}\n\n	if ( $break_obj )\n	{\n		if ( $empty_break )\n		{\n			while ( $objs )\n			{\n				$broken_objs[] = array_shift($objs);\n				$broken_objs[] = new $break_obj;\n			}\n			array_pop($broken_objs);\n			$objs = $broken_objs;\n		}\n		else\n			foreach ( $objs as $i => $obj )\n				$objs[$i] = new $break_obj(\'\', $obj);\n		foreach ( $objs as $obj )\n			if ( $obj instanceof $break_obj )\n				$obj->class($breakclass);\n	}\n\n	if ( $wraptag == \'table\' )\n		$wrap_obj = new soo_html_tr;\n\n	else {\n		$wrap_obj_class = \'soo_html_\' . $wraptag;\n		if ( class_exists($wrap_obj_class) )\n			$wrap_obj = new $wrap_obj_class;\n	}\n	if ( isset($wrap_obj) )\n	{\n		$wrap_obj->class($wrapclass);\n		foreach ( $objs as $obj )\n			$wrap_obj->contents($obj);\n\n		if ( $wraptag == \'table\' )\n		{\n			$table = new soo_html_table(array(\'id\' => $html_id),\n				new soo_html_tbody(\'\', $wrap_obj));\n			return $table->tag();\n		}\n		else\n			return $wrap_obj->id($html_id)->tag();\n	}\n	else\n	{\n		$out = array();\n		foreach ( $objs as $obj )\n			$out[] = $obj->tag();\n		return implode(\"\\n\", $out);\n	}\n}\n///////////////////// end of soo_multidoc_pager() ///////////////////////\n\nfunction soo_multidoc_page_number( $atts )\n{\n// Output tag: display current page number\n// Requires article context\n\n	extract(lAtts(array(\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'wraptag\'		=>	\'span\',\n 		\'format\'		=>	\'Page {page} of {total}\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n\n	$page_ids = $soo_multidoc[\'next_array\'];\n\n	$num_pages = count($page_ids);\n	$page_nums = array_flip($page_ids);\n	$this_page = $page_nums[$thisid] + 1;\n\n	$format = str_replace(\'{page}\', $this_page, $format);\n	$format = str_replace(\'{total}\', $num_pages, $format);\n\n	return doWrap(array($format), $wraptag, \'\', $class, \'\', \'\', \'\', $html_id);\n\n}\n\nfunction soo_multidoc_toc( $atts )\n{\n// Output tag: display table of contents as tiered list\n// Requires article context\n\n	extract(lAtts(array(\n 		\'wraptag\'		=>	\'ul\',\n 		\'root\'			=>	\'\',\n 		\'add_start\'		=>	false,\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n\n	if ( $root )\n	{\n		$start_id = is_numeric($root) ? intval($root) : $thisarticle[\'thisid\'];\n		$object_array = $soo_multidoc[\'rowset\']->subtree($start_id, \'id\')->as_object_array();\n	}\n	else\n		$object_array = $soo_multidoc[\'rowset\']->as_object_array();\n\n	if ( ! $add_start )\n		$object_array = $object_array[1];\n\n	array_walk_recursive($object_array, \'_soo_multidoc_toc_prep\', array(\'id\' => $thisarticle[\'thisid\'], \'class\' => $active_class));\n\n	$wraptag = trim(strtolower($wraptag));\n	if ( ! in_array($wraptag, array(\'ul\', \'ol\')) )\n		return false;\n	$wrap_obj_class = \'soo_html_\' . $wraptag;\n\n	$out = new $wrap_obj_class(array(), $object_array);\n	return $out->class($class)->id($html_id)->tag();\n\n}\n\nfunction soo_multidoc_page_title( $atts )\n{\n// Output tag: replacement for <txp:page_title />\n// Requires article context\n// If a Multidoc non-Start page, Start title will be added to output.\n// Otherwise standard page_title() is returned\n\n	extract(lAtts(array(\n 		\'separator\'		=>	\': \',\n	), $atts));\n\n	global $soo_multidoc, $sitename, $thisarticle;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return page_title($atts);\n\n	$thisid = $thisarticle[\'thisid\'];\n	$rs = &$soo_multidoc[\'rowset\']->rows;\n	$root = $soo_multidoc[\'id_root\'][$thisid];\n\n	return htmlspecialchars($sitename . $separator . $rs[$root]->title .\n		( $root != $thisid ? $separator . $rs[$thisid]->title : \'\' )\n	);\n}\n\nfunction soo_multidoc_breadcrumbs( $atts )\n{\n// Output tag: show higher levels in Collection\n// Requires article context\n\n	extract(lAtts(array(\n 		\'separator\'		=>	\': \',\n	), $atts));\n\n	global $soo_multidoc, $thisarticle;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return;\n\n	$data = &$soo_multidoc[\'data\'];\n	$this_id = $thisarticle[\'thisid\'];\n	$crumbs[] = $data[$this_id][\'title\'];\n\n	while ( $this_id = $soo_multidoc[\'id_parent\'][$this_id] and intval($this_id) )\n	{\n		$tag = new soo_html_anchor(array(\'href\' => $data[$this_id][\'url\']),\n			escape_title($data[$this_id][\'title\']));\n		array_unshift($crumbs, $tag->tag());\n	}\n\n	return implode($separator, $crumbs);\n}\n\nfunction soo_if_multidoc( $atts, $thing )\n{\n// Conditional: does current page belong to one of the specified Multidoc collections?\n// If no collections specified, looks for any Multidoc collection\n// Requires article context.\n\n	extract(lAtts(array(\n		\'start_id\'		=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return parse(EvalElse($thing, false));\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n	$start_ids = do_list($start_id);\n\n	$ok = ( ! $start_id or in_array($soo_multidoc[\'id_root\'][$thisid], $start_ids) );\n\n	return parse(EvalElse($thing, $ok));\n}\n\nfunction soo_if_multidoc_linear( $atts, $thing )\n{\n	global $soo_multidoc;\n\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return parse(EvalElse($thing, false));\n	$is_linear = count(array_unique($soo_multidoc[\'id_parent\'])) == 2;\n	return parse(EvalElse($thing, $is_linear));\n}\n\nfunction soo_if_multidoc_link( $atts, $thing )\n{\n	extract(lAtts(array(\n		\'rel\'			=>	\'\',\n		\'id\'			=>	\'\',\n		\'link_type\'		=>	\'\',\n	), $atts));\n\n	$rel = strtolower($rel);\n	global $soo_multidoc;\n\n	if ( ! (\n		$rel && in_array($rel, array(\'up\', \'next\', \'prev\')) &&\n		( $id || $link_type ) &&\n		_soo_multidoc_init() &&\n		$soo_multidoc[\'status\']\n	) )\n		return parse(EvalElse($thing, false));\n\n	global $thisarticle;\n	$this_id = $thisarticle[\'thisid\'];\n\n	switch ( $rel )\n	{\n		case \'up\':\n			$link_id = $soo_multidoc[\'id_parent\'][$this_id];\n			break;\n		case \'next\':\n			$link_id = $soo_multidoc[\'data\'][$this_id][\'next\'];\n			break;\n		case \'prev\':\n			$pos = array_search($this_id, $soo_multidoc[\'next_array\']);\n			$link_id = $pos ? $soo_multidoc[\'next_array\'][$pos - 1] : 0;\n	}\n	if ( $id )\n		$match = $link_id == $id;\n	elseif ( is_numeric($link_id) )\n		$match = strcasecmp($link_type, $soo_multidoc[\'rowset\']->$link_id->link_type) === 0;\n	else\n		$match = false;\n\n	return parse(EvalElse($thing, $match));\n}\n\n  //---------------------------------------------------------------------//\n //							Support Functions							//\n//---------------------------------------------------------------------//\n\nfunction _soo_multidoc_init()\n{\n// Initialization controller. Most Multidoc tags will return blank if this\n// function returns false.\n\n	global $soo_multidoc;\n\n	// only run init() once per page\n	if ( $soo_multidoc[\'init\'] ) return true;\n	$soo_multidoc[\'init\'] = true;\n\n	global $is_article_list, $thisarticle;\n\n	// Multidoc tags not allowed in lists\n	if ( $is_article_list ) return false;\n\n	// article context check\n	assert_article();\n	if ( empty($thisarticle) ) return false;\n\n	// populate global arrays of Multidoc article IDs\n	_soo_multidoc_ids_init($thisid = $thisarticle[\'thisid\']);\n\n	// is this a Multidoc article?\n	if ( empty($soo_multidoc[\'id_parent\'][$thisid]) ) return false;\n\n	// All systems go!!!\n	return $soo_multidoc[\'status\'] = true;\n}\n\nfunction _soo_multidoc_ids_init( $thisid = null, $force = false )\n{\n// populate $soo_multidoc global arrays\n// if $thisid is set, populate global data array for this article\'s collection\n\n	global $soo_multidoc;\n\n	if ( is_array($soo_multidoc[\'id_parent\']) and ! $force )\n		return true;\n\n	$query = new soo_txp_left_join(\'soo_multidoc\', \'textpattern\', \'id\', \'ID\');\n	$query->select(array(\'id\', \'root\', \'lft\', \'rgt\', \'children\', \'type as link_type\'))\n		->select_join(array(\'ID\', \'Title as title\', \'url_title\', \'Section\', \'Posted\'))\n		->order_by(array(\'root\', \'lft\'));\n\n	// Draft or hidden articles visible in admin only\n	if ( @txpinterface != \'admin\' )\n		$query->where_join(\'Status\', 3, \'>\');	// live or sticky status\n\n	if ( ! get_pref(\'publish_expired_articles\') )\n		$query->where_clause(\'(now() <= Expires OR Expires IS NULL)\');\n\n	if ( $thisid )\n	{\n		$root_query = new soo_txp_select(\'soo_multidoc\', array(\'id\' => $thisid), \'root\');\n		$query->in(\'root\', $root_query);\n	}\n\n	switch ( $soo_multidoc[\'posted_time\'] )\n	{\n		case \'past\':\n			$query->where_clause(\'Posted <= now()\');\n			break;\n		case \'future\':\n			$query->where_clause(\'Posted > now()\');\n	}\n\n	if ( ! $join_count = $query->count() )\n		return false;\n\n	// TODO: need to deal with missing articles\n// 	if ( $thisid )\n// 	{\n// 		$thisid_root = current($root_query->row());\n// 		$collection_query = new soo_txp_select(\'soo_multidoc\', array(\'root\' => $thisid_root), \'id\');\n// 		$collection_count = $collection_query->count();\n// 		if ( $diff = $collection_count - $join_count )\n// 			echo $diff, \' missing record(s)\';\n//\n// 		$collection_rowset = new soo_txp_rowset( $collection_query->order_by(\'lft\'));\n// 		$collection_records = $collection_rowset->field_vals(\'id\');\n// 	}\n\n	$rs = new soo_multidoc_rowset($query);\n	$ids = $rs->field_vals(\'id\');\n	$rs->rows = array_combine($ids, $rs->rows);\n	$id_parent = array();\n	$id_children = array();\n\n	foreach ( $rs->rows as $r )\n	{\n		if ( $r->link_type == \'start\' )\n			$id_parent[$r->id] = \'Start\';\n		if ( $r->children )\n		{\n			$id_children[$r->id] = do_list($r->children);\n			foreach ( $id_children[$r->id] as $child )\n				$id_parent[$child] = $r->id;\n		}\n		else\n			$id_children[$r->id] = array();\n	}\n\n	$soo_multidoc[\'id_parent\'] = $id_parent;	// key = id, value = parent id\n	$soo_multidoc[\'id_children\'] = $id_children;		// key = id, value = array\n	$soo_multidoc[\'id_root\'] = $rs->field_vals(\'root\', \'id\');	// key = id, value = root id\n	$soo_multidoc[\'id_link_type\'] = $rs->field_vals(\'link_type\', \'id\');	// key = id, value = link_type\n	$soo_multidoc[\'rowset\'] = $rs;\n\n	// if individual article context, populate data array for this collection\n	if ( $thisid and array_key_exists($thisid, $id_parent) )\n	{\n		$next = $soo_multidoc[\'rowset\']->field_vals(\'id\');\n		$soo_multidoc[\'next_array\'] = $next;\n		array_shift($next);\n		$soo_multidoc[\'data\'] = [];\n		foreach ( $soo_multidoc[\'rowset\']->rows as $id => $r )\n			$soo_multidoc[\'data\'][$id] = array_merge($r->data, array(\n				\'parent\' => $id_parent[$id],\n				\'url\' => permlinkurl($r->data),\n				\'next\' => array_shift($next),\n			));\n	}\n	return true;\n}\n\n/// Convert a row object to an html element object.\n//  Intended for use with array_walk_recursive()\nfunction _soo_multidoc_toc_prep ( &$row, $k, $active )\n{\n	global $soo_multidoc;\n	if ( $row->id == $active[\'id\'] )\n		$row = new soo_html_span(array(\'class\' => $active[\'class\']), $row->title);\n	else\n		$row = new soo_html_anchor(array(\'href\' => $soo_multidoc[\'data\'][$row->id][\'url\']), $row->title);\n}\n\nfunction _soo_multidoc_temp_table ( )\n{\n// MySQL temporary table to filter Multidoc interior pages from article lists\n	global $pretext, $is_article_list, $soo_multidoc;\n	if ( ! $is_article_list or\n		$pretext[\'q\'] or\n		! empty($soo_multidoc[\'list_all\']) or\n		! getRow(\"show tables like \'soo_multidoc\'\") or\n		! _soo_multidoc_ids_init()\n	)\n		return;\n	$t1 = safe_pfx(\'textpattern\');\n	$t2 = safe_pfx(\'soo_multidoc\');\n	safe_query(\n		\"create temporary table $t1 select * from $t1 where ID not in (select id from $t2 where id != root)\");\n}\n\nregister_callback(\'_soo_multidoc_temp_table\', \'pretext_end\');\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_multidoc_link\')\n		->register(\'soo_multidoc_pager\')\n		->register(\'soo_multidoc_page_number\')\n		->register(\'soo_multidoc_toc\')\n		->register(\'soo_multidoc_page_title\')\n		->register(\'soo_multidoc_breadcrumbs\')\n		->register(\'soo_if_multidoc\')\n		->register(\'soo_if_multidoc_linear\')\n		->register(\'soo_if_multidoc_link\')\n		;\n}\n\nrequire_plugin(\'soo_txp_obj\');\n@require_plugin(\'soo_plugin_pref\');		// optional\n\n  //---------------------------------------------------------------------//\n //									Globals								//\n//---------------------------------------------------------------------//\n\nif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'plugin_prefs.soo_multidoc\',\'1,2\');\n	add_privs(\'plugin_lifecycle.soo_multidoc\',\'1,2\');\n	register_callback(\'soo_multidoc_prefs\', \'plugin_prefs.soo_multidoc\');\n	register_callback(\'soo_multidoc_prefs\', \'plugin_lifecycle.soo_multidoc\');\n}\n\nglobal $soo_multidoc;\n$soo_multidoc = function_exists(\'soo_plugin_pref_vals\') ?\n	array_merge(soo_multidoc_defaults(true), soo_plugin_pref_vals(\'soo_multidoc\'))\n	: soo_multidoc_defaults(true);\n$soo_multidoc = array_merge($soo_multidoc, array(\n	\'init\'		=>	false,\n	\'status\'	=>	false,\n	\'id_parent\'	=>	\'\',\n	\'data\'		=>	\'\',\n));\n\nfunction soo_multidoc_prefs( $event, $step )\n{\n	if ( function_exists(\'soo_plugin_pref\') )\n		return soo_plugin_pref($event, $step, soo_multidoc_defaults());\n	if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n		$plugin = substr($event, 12);\n		$message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n			gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\') .\n			\' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n		pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n	}\n}\n\nfunction soo_multidoc_defaults( $vals_only = false )\n{\n	$defaults = array(\n		\'list_all\'	=>	array(\n			\'val\'	=>	0,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Show Multidoc sub-pages in article lists?\',\n		),\n		\'posted_time\'	=>	array(\n			\'val\'	=>	\'past\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Show articles posted &lsquo;past&rsquo;, &lsquo;future&rsquo;, or &lsquo;any&rsquo;\',\n		),\n	);\n	if ( $vals_only )\n		foreach ( $defaults as $name => $arr )\n			$defaults[$name] = $arr[\'val\'];\n	return $defaults;\n}\n\n  //---------------------------------------------------------------------//\n //							MLP Pack definitions						//\n//---------------------------------------------------------------------//\n\ndefine(\'SOO_MULTIDOC_PREFIX\', \'soo_mdoc\');\nglobal $soo_multidoc_strings;\n$soo_multidoc_strings = array(\n	\'start\'	=>	\'start\',\n	\'up\'	=>	\'up\',\n	\'next\'	=>	\'next\',\n	\'prev\'	=>	\'prev\',\n);\n\nregister_callback(\'soo_multidoc_enumerate_strings\', \'l10n.enumerate_strings\');\n\nfunction soo_multidoc_enumerate_strings( $event, $step = \'\', $pre = 0 )\n{\n	global $soo_multidoc_strings;\n	$r = array(\n		\'owner\'		=> \'soo_multidoc\',\n		\'prefix\'	=> SOO_MULTIDOC_PREFIX,\n		\'lang\'		=> \'en-us\',\n		\'event\'		=> \'public\',\n		\'strings\'	=> $soo_multidoc_strings,\n				);\n	return $r;\n}\n\nfunction soo_multidoc_gTxt( $what , $args = array() )\n{\n	global $textarray;\n	global $soo_multidoc_strings;\n\n	$key = SOO_MULTIDOC_PREFIX . \'-\' . $what;\n	$key = strtolower($key);\n\n	if(isset($textarray[$key]))\n		$str = $textarray[$key];\n\n	else\n	{\n		$key = strtolower($what);\n\n		if( isset( $soo_multidoc_strings[$key] ) )\n			$str = $soo_multidoc_strings[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n\n\n  //---------------------------------------------------------------------//\n //									Classes								//\n//---------------------------------------------------------------------//\n\nclass soo_multidoc_rowset extends soo_nested_set\n{\n	public function has_subnode( $a, $b )\n	// Boolean: is $b a subnode of $a?\n	{\n		return ( $this->$b->lft > $this->$a->lft and $this->$b->rgt < $this->$a->rgt );\n	}\n\n	public function find_by_link_type( $link_type, $current = null, $dir = \'next\' )\n	{\n		$rs = $this->rows;\n		if ( $dir == \'prev\' )\n			$rs = array_reverse($rs, true);\n		$ids = array_keys($rs);\n		$link_type = strtolower($link_type);\n\n		if ( $current and in_array($current, $ids) )\n		{\n			$id_position = array_search($current, $ids);\n			if ( $dir == \'prev\' )\n				$id_position++;\n			$searchable_ids = array_flip(array_slice($ids, $id_position));\n			$rs = array_intersect_key($rs, $searchable_ids);\n		}\n\n		foreach ( $rs as $k => $r )\n			if ( $r->link_type == $link_type )\n				return $k;\n	}\n}\n\n  //---------------------------------------------------------------------//\n //									Tags								//\n//---------------------------------------------------------------------//\n\nfunction soo_multidoc_link( $atts, $thing = null )\n{\n// Output tag: display an HTML anchor\n// Requires article context\n\n	extract(lAtts(array(\n		\'rel\'			=>	\'\',\n		\'add_title\'		=>	\'\',\n\n		// {rel} and {title} available as tokens\n		\'text\'			=>	\'{rel}\',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n 		\'wraptag\'		=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	foreach ( array(\'start\', \'up\', \'next\', \'prev\') as $type )\n		$reserved_rel[$type] = $$type = strtolower(soo_multidoc_gTxt($type));\n\n	global $thisarticle;\n	$rowset = &$soo_multidoc[\'rowset\'];\n	$thisid = $thisarticle[\'thisid\'];\n	$rel = trim($rel);\n\n	// $rel value may be space-separated list of link types\n	// this tag allows only \'prev\' or \'next\' in combination with another type\n	if ( preg_match(\"/^($next|$prev)\\s+(\\w+)/i\", $rel, $match) )\n	{\n		$rel_dir = strtolower($match[1]);\n		$rel_type = strtolower($match[2]);\n		$link_id = $rowset->find_by_link_type($rel_type, $thisid, $rel_dir);\n\n		// if I have an ancestor of the requested link type, that ancestor will\n		// be the prev link of that type, which isn\'t what the user wants.\n		// So continue back one more step.\n		if ( $rel_dir == \'prev\' and $rowset->has_subnode($link_id, $thisid) )\n		{\n			$next_link = $rowset->find_by_link_type($rel_type, $link_id, $rel_dir);\n			if ( $next_link != $link_id and is_numeric($next_link) )\n				$link_id = $next_link;\n			else\n				unset($link_id);\n		}\n	}\n	elseif ( in_array($relcomp = strtolower($rel), $reserved_rel) )\n	{\n		if ( $relcomp == $start )\n			$link_id = $soo_multidoc[\'id_root\'][$thisid];\n		elseif ( $relcomp == $up )\n		{\n			$link_id = $soo_multidoc[\'id_parent\'][$thisid];\n			if ( ! intval($link_id) )\n				$link_id = 0;\n		}\n		else	// next or prev\n		{\n			$next_array = $soo_multidoc[\'next_array\'];\n			if ( $relcomp == $prev )\n				$next_array = array_reverse($next_array);\n			$i = array_search($thisid, $next_array);\n			$link_id = isset($next_array[$i + 1]) ? $next_array[$i + 1] : 0;\n		}\n\n	}\n	else\n		$link_id = $rowset->find_by_link_type($rel);\n\n	if ( ! empty($link_id) )\n		$url = $soo_multidoc[\'data\'][$link_id][\'url\'];\n\n	if ( ! isset($url) ) return false;\n\n	if ( $add_title )\n		$thing .= $rowset->$link_id->title;\n\n	if ( ! $thing )\n		$thing = str_replace(array(\'{rel}\', \'{title}\'), array($rel, $rowset->$link_id->title), $text);\n\n	if ( $link_id == $thisid )\n		$tag = new soo_html_span(array(\'class\' => $active_class));\n\n	else\n		$tag = new soo_html_anchor(array(\'href\' => $url, \'rel\' => $rel));\n\n	$tag->contents(parse($thing));\n\n	if ( $wraptag and class_exists($tag_class = \'soo_html_\' . $wraptag) )\n	{\n		$wraptag = new $tag_class;\n		return $wraptag->contents($tag)->class($class)->\n			id($html_id)->\n			tag();\n	}\n	else\n	{\n		$tag->id($html_id);\n		if ( $link_id != $thisid )\n			$tag->class($class);\n		return $tag->tag();\n	}\n}\n///////////////////// end of soo_multidoc_link() ///////////////////////\n\nfunction soo_multidoc_pager( $atts )\n{\n// Output tag: display a simple list of page links\n// Requires article context\n\n	extract(lAtts(array(\n		\'limit\'			=>	0,\n		\'placeholder\'	=>	\' &hellip; \',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n 		\'wraptag\'		=>	\'\',\n 		\'wrapclass\'		=>	\'\',\n 		\'break\'			=>	\'\',\n 		\'breakclass\'	=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n\n	$wraptag = trim(strtolower($wraptag));\n	if ( $wraptag == \'table\' )\n		$break = \'td\';\n	elseif ( $wraptag == \'ul\' or $wraptag == \'ol\' )\n		$break = \'li\';\n\n	if ( $break )\n	{\n		$break_obj = \'soo_html_\' . trim(strtolower($break));\n		if ( ! class_exists($break_obj) )\n			$break_obj = null;\n		else {\n			$break = \'\';\n			$test_obj = new $break_obj;\n			$empty_break = $test_obj->is_empty;\n		}\n	}\n	else\n		$break_obj = null;\n\n	$page_ids = $soo_multidoc[\'next_array\'];\n\n	$total = count($page_ids);\n	$page_nums = array_combine($page_ids, range(1, $total));\n	$this_num = $page_nums[$thisid];\n\n	$w_start = max(1,\n		min($this_num - $limit, $total - ( $limit * 2 ) + 1));\n	$w_end = min($w_start + ( $limit * 2 ) - 1, $total);\n\n	$show_nums = array_unique(array_merge(\n		array(1), range($w_start, $w_end), array($total)\n	));\n\n	$objs = array();\n\n	while ( $n = array_shift($show_nums) )\n	{\n		if ( $n == $this_num )\n			$objs[] = new soo_html_span(array(\'class\' => $active_class), $n);\n		else\n			$objs[] = new soo_html_anchor(array(\n				\'href\' => $soo_multidoc[\'data\'][$page_ids[$n - 1]][\'url\'],\n				\'class\' => $class), $n)\n			;\n\n		$fill = $show_nums ?\n			( $show_nums[0] > $n + 1 ? $placeholder : $break ) : \'\';\n		if ( $fill )\n			$objs[] = new soo_html_span(\'\', $fill);\n	}\n\n	if ( $break_obj )\n	{\n		if ( $empty_break )\n		{\n			while ( $objs )\n			{\n				$broken_objs[] = array_shift($objs);\n				$broken_objs[] = new $break_obj;\n			}\n			array_pop($broken_objs);\n			$objs = $broken_objs;\n		}\n		else\n			foreach ( $objs as $i => $obj )\n				$objs[$i] = new $break_obj(\'\', $obj);\n		foreach ( $objs as $obj )\n			if ( $obj instanceof $break_obj )\n				$obj->class($breakclass);\n	}\n\n	if ( $wraptag == \'table\' )\n		$wrap_obj = new soo_html_tr;\n\n	else {\n		$wrap_obj_class = \'soo_html_\' . $wraptag;\n		if ( class_exists($wrap_obj_class) )\n			$wrap_obj = new $wrap_obj_class;\n	}\n	if ( isset($wrap_obj) )\n	{\n		$wrap_obj->class($wrapclass);\n		foreach ( $objs as $obj )\n			$wrap_obj->contents($obj);\n\n		if ( $wraptag == \'table\' )\n		{\n			$table = new soo_html_table(array(\'id\' => $html_id),\n				new soo_html_tbody(\'\', $wrap_obj));\n			return $table->tag();\n		}\n		else\n			return $wrap_obj->id($html_id)->tag();\n	}\n	else\n	{\n		$out = array();\n		foreach ( $objs as $obj )\n			$out[] = $obj->tag();\n		return implode(\"\\n\", $out);\n	}\n}\n///////////////////// end of soo_multidoc_pager() ///////////////////////\n\nfunction soo_multidoc_page_number( $atts )\n{\n// Output tag: display current page number\n// Requires article context\n\n	extract(lAtts(array(\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'wraptag\'		=>	\'span\',\n 		\'format\'		=>	\'Page {page} of {total}\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n\n	$page_ids = $soo_multidoc[\'next_array\'];\n\n	$num_pages = count($page_ids);\n	$page_nums = array_flip($page_ids);\n	$this_page = $page_nums[$thisid] + 1;\n\n	$format = str_replace(\'{page}\', $this_page, $format);\n	$format = str_replace(\'{total}\', $num_pages, $format);\n\n	return doWrap(array($format), $wraptag, \'\', $class, \'\', \'\', \'\', $html_id);\n\n}\n\nfunction soo_multidoc_toc( $atts )\n{\n// Output tag: display table of contents as tiered list\n// Requires article context\n\n	extract(lAtts(array(\n 		\'wraptag\'		=>	\'ul\',\n 		\'root\'			=>	\'\',\n 		\'add_start\'		=>	false,\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'active_class\'	=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) ) return false;\n\n	global $thisarticle;\n\n	if ( $root )\n	{\n		$start_id = is_numeric($root) ? intval($root) : $thisarticle[\'thisid\'];\n		$object_array = $soo_multidoc[\'rowset\']->subtree($start_id, \'id\')->as_object_array();\n	}\n	else\n		$object_array = $soo_multidoc[\'rowset\']->as_object_array();\n\n	if ( ! $add_start )\n		$object_array = $object_array[1];\n\n	array_walk_recursive($object_array, \'_soo_multidoc_toc_prep\', array(\'id\' => $thisarticle[\'thisid\'], \'class\' => $active_class));\n\n	$wraptag = trim(strtolower($wraptag));\n	if ( ! in_array($wraptag, array(\'ul\', \'ol\')) )\n		return false;\n	$wrap_obj_class = \'soo_html_\' . $wraptag;\n\n	$out = new $wrap_obj_class(array(), $object_array);\n	return $out->class($class)->id($html_id)->tag();\n\n}\n\nfunction soo_multidoc_page_title( $atts )\n{\n// Output tag: replacement for <txp:page_title />\n// Requires article context\n// If a Multidoc non-Start page, Start title will be added to output.\n// Otherwise standard page_title() is returned\n\n	extract(lAtts(array(\n 		\'separator\'		=>	\': \',\n	), $atts));\n\n	global $soo_multidoc, $sitename, $thisarticle;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return page_title($atts);\n\n	$thisid = $thisarticle[\'thisid\'];\n	$rs = &$soo_multidoc[\'rowset\']->rows;\n	$root = $soo_multidoc[\'id_root\'][$thisid];\n\n	return htmlspecialchars($sitename . $separator . $rs[$root]->title .\n		( $root != $thisid ? $separator . $rs[$thisid]->title : \'\' )\n	);\n}\n\nfunction soo_multidoc_breadcrumbs( $atts )\n{\n// Output tag: show higher levels in Collection\n// Requires article context\n\n	extract(lAtts(array(\n 		\'separator\'		=>	\': \',\n	), $atts));\n\n	global $soo_multidoc, $thisarticle;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return;\n\n	$data = &$soo_multidoc[\'data\'];\n	$this_id = $thisarticle[\'thisid\'];\n	$crumbs[] = $data[$this_id][\'title\'];\n\n	while ( $this_id = $soo_multidoc[\'id_parent\'][$this_id] and intval($this_id) )\n	{\n		$tag = new soo_html_anchor(array(\'href\' => $data[$this_id][\'url\']),\n			escape_title($data[$this_id][\'title\']));\n		array_unshift($crumbs, $tag->tag());\n	}\n\n	return implode($separator, $crumbs);\n}\n\nfunction soo_if_multidoc( $atts, $thing )\n{\n// Conditional: does current page belong to one of the specified Multidoc collections?\n// If no collections specified, looks for any Multidoc collection\n// Requires article context.\n\n	extract(lAtts(array(\n		\'start_id\'		=>	\'\',\n	), $atts));\n\n	global $soo_multidoc;\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return parse(EvalElse($thing, false));\n\n	global $thisarticle;\n	$thisid = $thisarticle[\'thisid\'];\n	$start_ids = do_list($start_id);\n\n	$ok = ( ! $start_id or in_array($soo_multidoc[\'id_root\'][$thisid], $start_ids) );\n\n	return parse(EvalElse($thing, $ok));\n}\n\nfunction soo_if_multidoc_linear( $atts, $thing )\n{\n	global $soo_multidoc;\n\n	if ( ! ( _soo_multidoc_init() and $soo_multidoc[\'status\'] ) )\n		return parse(EvalElse($thing, false));\n	$is_linear = count(array_unique($soo_multidoc[\'id_parent\'])) == 2;\n	return parse(EvalElse($thing, $is_linear));\n}\n\nfunction soo_if_multidoc_link( $atts, $thing )\n{\n	extract(lAtts(array(\n		\'rel\'			=>	\'\',\n		\'id\'			=>	\'\',\n		\'link_type\'		=>	\'\',\n	), $atts));\n\n	$rel = strtolower($rel);\n	global $soo_multidoc;\n\n	if ( ! (\n		$rel && in_array($rel, array(\'up\', \'next\', \'prev\')) &&\n		( $id || $link_type ) &&\n		_soo_multidoc_init() &&\n		$soo_multidoc[\'status\']\n	) )\n		return parse(EvalElse($thing, false));\n\n	global $thisarticle;\n	$this_id = $thisarticle[\'thisid\'];\n\n	switch ( $rel )\n	{\n		case \'up\':\n			$link_id = $soo_multidoc[\'id_parent\'][$this_id];\n			break;\n		case \'next\':\n			$link_id = $soo_multidoc[\'data\'][$this_id][\'next\'];\n			break;\n		case \'prev\':\n			$pos = array_search($this_id, $soo_multidoc[\'next_array\']);\n			$link_id = $pos ? $soo_multidoc[\'next_array\'][$pos - 1] : 0;\n	}\n	if ( $id )\n		$match = $link_id == $id;\n	elseif ( is_numeric($link_id) )\n		$match = strcasecmp($link_type, $soo_multidoc[\'rowset\']->$link_id->link_type) === 0;\n	else\n		$match = false;\n\n	return parse(EvalElse($thing, $match));\n}\n\n  //---------------------------------------------------------------------//\n //							Support Functions							//\n//---------------------------------------------------------------------//\n\nfunction _soo_multidoc_init()\n{\n// Initialization controller. Most Multidoc tags will return blank if this\n// function returns false.\n\n	global $soo_multidoc;\n\n	// only run init() once per page\n	if ( $soo_multidoc[\'init\'] ) return true;\n	$soo_multidoc[\'init\'] = true;\n\n	global $is_article_list, $thisarticle;\n\n	// Multidoc tags not allowed in lists\n	if ( $is_article_list ) return false;\n\n	// article context check\n	assert_article();\n	if ( empty($thisarticle) ) return false;\n\n	// populate global arrays of Multidoc article IDs\n	_soo_multidoc_ids_init($thisid = $thisarticle[\'thisid\']);\n\n	// is this a Multidoc article?\n	if ( empty($soo_multidoc[\'id_parent\'][$thisid]) ) return false;\n\n	// All systems go!!!\n	return $soo_multidoc[\'status\'] = true;\n}\n\nfunction _soo_multidoc_ids_init( $thisid = null, $force = false )\n{\n// populate $soo_multidoc global arrays\n// if $thisid is set, populate global data array for this article\'s collection\n\n	global $soo_multidoc;\n\n	if ( is_array($soo_multidoc[\'id_parent\']) and ! $force )\n		return true;\n\n	$query = new soo_txp_left_join(\'soo_multidoc\', \'textpattern\', \'id\', \'ID\');\n	$query->select(array(\'id\', \'root\', \'lft\', \'rgt\', \'children\', \'type as link_type\'))\n		->select_join(array(\'ID\', \'Title as title\', \'url_title\', \'Section\', \'Posted\'))\n		->order_by(array(\'root\', \'lft\'));\n\n	// Draft or hidden articles visible in admin only\n	if ( @txpinterface != \'admin\' )\n		$query->where_join(\'Status\', 3, \'>\');	// live or sticky status\n\n	if ( ! get_pref(\'publish_expired_articles\') )\n		$query->where_clause(\'(now() <= Expires OR Expires IS NULL)\');\n\n	if ( $thisid )\n	{\n		$root_query = new soo_txp_select(\'soo_multidoc\', array(\'id\' => $thisid), \'root\');\n		$query->in(\'root\', $root_query);\n	}\n\n	switch ( $soo_multidoc[\'posted_time\'] )\n	{\n		case \'past\':\n			$query->where_clause(\'Posted <= now()\');\n			break;\n		case \'future\':\n			$query->where_clause(\'Posted > now()\');\n	}\n\n	if ( ! $join_count = $query->count() )\n		return false;\n\n	// TODO: need to deal with missing articles\n// 	if ( $thisid )\n// 	{\n// 		$thisid_root = current($root_query->row());\n// 		$collection_query = new soo_txp_select(\'soo_multidoc\', array(\'root\' => $thisid_root), \'id\');\n// 		$collection_count = $collection_query->count();\n// 		if ( $diff = $collection_count - $join_count )\n// 			echo $diff, \' missing record(s)\';\n//\n// 		$collection_rowset = new soo_txp_rowset( $collection_query->order_by(\'lft\'));\n// 		$collection_records = $collection_rowset->field_vals(\'id\');\n// 	}\n\n	$rs = new soo_multidoc_rowset($query);\n	$ids = $rs->field_vals(\'id\');\n	$rs->rows = array_combine($ids, $rs->rows);\n	$id_parent = array();\n	$id_children = array();\n\n	foreach ( $rs->rows as $r )\n	{\n		if ( $r->link_type == \'start\' )\n			$id_parent[$r->id] = \'Start\';\n		if ( $r->children )\n		{\n			$id_children[$r->id] = do_list($r->children);\n			foreach ( $id_children[$r->id] as $child )\n				$id_parent[$child] = $r->id;\n		}\n		else\n			$id_children[$r->id] = array();\n	}\n\n	$soo_multidoc[\'id_parent\'] = $id_parent;	// key = id, value = parent id\n	$soo_multidoc[\'id_children\'] = $id_children;		// key = id, value = array\n	$soo_multidoc[\'id_root\'] = $rs->field_vals(\'root\', \'id\');	// key = id, value = root id\n	$soo_multidoc[\'id_link_type\'] = $rs->field_vals(\'link_type\', \'id\');	// key = id, value = link_type\n	$soo_multidoc[\'rowset\'] = $rs;\n\n	// if individual article context, populate data array for this collection\n	if ( $thisid and array_key_exists($thisid, $id_parent) )\n	{\n		$next = $soo_multidoc[\'rowset\']->field_vals(\'id\');\n		$soo_multidoc[\'next_array\'] = $next;\n		array_shift($next);\n		$soo_multidoc[\'data\'] = [];\n		foreach ( $soo_multidoc[\'rowset\']->rows as $id => $r )\n			$soo_multidoc[\'data\'][$id] = array_merge($r->data, array(\n				\'parent\' => $id_parent[$id],\n				\'url\' => permlinkurl($r->data),\n				\'next\' => array_shift($next),\n			));\n	}\n	return true;\n}\n\n/// Convert a row object to an html element object.\n//  Intended for use with array_walk_recursive()\nfunction _soo_multidoc_toc_prep ( &$row, $k, $active )\n{\n	global $soo_multidoc;\n	if ( $row->id == $active[\'id\'] )\n		$row = new soo_html_span(array(\'class\' => $active[\'class\']), $row->title);\n	else\n		$row = new soo_html_anchor(array(\'href\' => $soo_multidoc[\'data\'][$row->id][\'url\']), $row->title);\n}\n\nfunction _soo_multidoc_temp_table ( )\n{\n// MySQL temporary table to filter Multidoc interior pages from article lists\n	global $pretext, $is_article_list, $soo_multidoc;\n	if ( ! $is_article_list or\n		$pretext[\'q\'] or\n		! empty($soo_multidoc[\'list_all\']) or\n		! getRow(\"show tables like \'soo_multidoc\'\") or\n		! _soo_multidoc_ids_init()\n	)\n		return;\n	$t1 = safe_pfx(\'textpattern\');\n	$t2 = safe_pfx(\'soo_multidoc\');\n	safe_query(\n		\"create temporary table $t1 select * from $t1 where ID not in (select id from $t2 where id != root)\");\n}\n\nregister_callback(\'_soo_multidoc_temp_table\', \'pretext_end\');\n', '6cfcf61d15e48e6b572bc92d12215b79', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_multidoc_admin', 0, 'Jeff Soo', 'http://ipsedixit.net/', '0.1.3', 'Administer Multidoc collections', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help h6 {font-size: 1em;margin-left:2em;font-style:oblique;}\ndiv#sed_help li {list-style-type: disc;}\ndiv#sed_help li li {list-style-type: circle;}\ndiv#sed_help li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\n</style>\n <div id=\"sed_help\">\n\n	<h1>soo_multidoc_admin</h1>\n\n	<h2>Contents</h2>\n\n	<ul>\n		<li><a href=\"#overview\">Overview</a></li>\n		<li><a href=\"#panel\">The Multidoc admin panel</a>\n	<ul>\n		<li><a href=\"#collections\">The Collections mini-tab</a></li>\n		<li><a href=\"#admin\">The Admin mini-tab</a></li>\n		<li><a href=\"#help\">The Help mini-tab</a></li>\n	</ul></li>\n		<li><a href=\"#detail\">Detail view</a>\n	<ul>\n		<li><a href=\"#add\">Add pages</a></li>\n		<li><a href=\"#move\">Move current page</a></li>\n		<li><a href=\"#remove\">Remove current page</a></li>\n	</ul></li>\n		<li><a href=\"#installing\">Installing</a></li>\n		<li><a href=\"#uninstalling\">Uninstalling</a></li>\n		<li><a href=\"#upgrading\">Upgrading</a></li>\n		<li><a href=\"#history\">History</a></li>\n	</ul>\n\n	<h2 id=\"overview\">Overview</h2>\n\n	<p>This is an admin-side plugin for managing <a href=\"http://ipsedixit.net/txp/24/multidoc\">Multidoc</a> collections. If you&#8217;re new to <strong>Multidoc</strong>, look at the <a href=\"http://ipsedixit.net/txp/24/multidoc\">user guide</a> first.</p>\n\n	<h2 id=\"panel\">The Multidoc admin panel</h2>\n\n	<p>The <strong>Multidoc</strong> admin panel is located under the <a href=\"http://textpattern.net/wiki/index.php?title=Extensions\">Extensions tab</a>. It is divided into three areas, accessed by mini-tabs near the top of the page: Collections, Admin, and Help.</p>\n\n	<h3 id=\"collections\">The Collections mini-tab</h3>\n\n	<p>This is the default view, showing a list of all <strong>Multidoc</strong> Collections and the number of articles in each. Clicking a Collection name brings up the <a href=\"#detail\">Detail view</a> for that Collection.</p>\n\n	<p>Below the list are controls for adding and deleting Collections.</p>\n\n	<h3 id=\"admin\">The Admin mini-tab</h3>\n\n	<p>This allows you to install or uninstall the soo_multidoc database table.</p>\n\n	<h3 id=\"help\">The Help mini-tab</h3>\n\n	<p>This contains links to the help files for the soo_multidoc and soo_multidoc_admin plugins, plus a link to the online <a href=\"http://ipsedixit.net/txp/24/multidoc\">Multidoc user guide</a>.</p>\n\n	<h2 id=\"detail\">Detail view</h2>\n\n	<p>Click on a Collection name in the <a href=\"#collections\">Collections mini-tab</a> to bring up the Detail view. This shows all articles in the Collection, with indentation showing the document structure. Clicking an article title brings up that article for editing in the <a href=\"http://textpattern.net/wiki/index.php?title=Write\">Write panel</a>.</p>\n\n	<p>To the right of the title is the article&#8217;s link type as assigned in <strong>Multidoc</strong>. Clicking on this brings up a form for changing the link type for that article.</p>\n\n	<p>To the right of the link type is a set of controls for managing each article&#8217;s position in the Collection.</p>\n\n	<h3 id=\"add\">Add pages</h3>\n\n	<p>Clicking a [+] brings up a form for adding pages below that page. That is, new pages will be at the next level of indentation, with the current page as &#8220;parent&#8221;. The list of available pages shows the most recently modified articles that aren&#8217;t already part of a Collection. Choose an existing link type or create a new one.</p>\n\n	<h3 id=\"move\">Move current page</h3>\n\n	<p>Clicking one of the arrows ( [&larr;] [&uarr;] [&rarr;] [&darr;] ) moves that page. Moving a page up or down keeps it in the same sub-section (i.e., with the same parent article). Moving it left moves it out one level of indentation, and moving it right moves it in one level.</p>\n\n	<p>Some arrow controls are disabled. For example, a page can only be moved right if it has a &#8220;sibling&#8221; page immediately above. A page can only be moved up or down if it has at least one sibling.</p>\n\n	<h3 id=\"remove\">Remove current page and all sub-pages</h3>\n\n	<p>Clicking the [X] removes that page and all its sub-pages from the Collection. The articles themselves are not deleted.</p>\n\n	<h2 id=\"installing\">Installing</h2>\n\n	<p>These instructions are for new <strong>Multidoc</strong> installations. If you are upgrading an existing installation, see <a href=\"#upgrading\">Upgrading</a>, below.</p>\n\n	<p>The <strong>Multidoc</strong> system has two required plugins and two optional plugins; make sure to get the latest version of each. <a href=\"http://textpattern.net/wiki/index.php?title=Plugins\">Install and activate</a> in this order:</p>\n\n	<ul>\n		<li>soo_txp_obj <span class=\"required\">required</span></li>\n		<li>soo_multidoc <span class=\"required\">required</span></li>\n		<li>soo_multidoc_admin (technically optional, but you&#8217;ll want it)</li>\n	</ul>\n\n	<p>soo_plugin_pref is optional and can be installed at any point.</p>\n\n	<p>To begin creating and managing <strong>Multidoc</strong> Collections, go to the <strong>Multidoc</strong> sub-tab under the <a href=\"http://textpattern.net/wiki/index.php?title=Extensions\">Extensions tab</a>. (Note that you cannot see the Extensions tab on the <a href=\"http://textpattern.net/wiki/index.php?title=Plugins\">Plugins sub-tab</a>.) Click the &#8220;Install&#8221; button to add the soo_multidoc table to your site&#8217;s database.</p>\n\n	<p>Now you are ready to begin adding Collections.</p>\n\n	<h2 id=\"uninstalling\">Uninstalling</h2>\n\n	<p>For a clean uninstall follow these steps:</p>\n\n	<ul>\n		<li>Remove any <strong>Multidoc</strong> tags from your pages, forms, and articles (the <a href=\"http://stefdawson.com/sw/plugins/smd_where_used\">smd_where_used plugin</a> is helpful for this)</li>\n		<li>Go to the <strong>Multidoc</strong> sub-tab, click the &#8220;Admin&#8221; sub-sub-tab, and click &#8220;Uninstall&#8221;</li>\n		<li>Delete plugins in this order:\n	<ul>\n		<li>soo_multidoc_admin</li>\n		<li>soo_multidoc</li>\n		<li>soo_txp_obj (if not otherwise needed)</li>\n		<li>soo_plugin_pref (if not otherwise needed)</li>\n	</ul></li>\n	</ul>\n\n	<h2 id=\"upgrading\">Upgrading</h2>\n\n	<p>If your site already uses <strong>Multidoc</strong> and you are adding soo_multidoc_admin, follow these steps in order to convert your existing Collection data to the new system:</p>\n\n	<ul>\n		<li><a href=\"http://textpattern.net/wiki/index.php?title=Plugins\">Install and activate</a> the latest version of soo_txp_obj</li>\n		<li>Install and activate soo_multidoc_admin</li>\n		<li>Go to the <strong>Multidoc</strong> sub-tab under the <a href=\"http://textpattern.net/wiki/index.php?title=Extensions\">Extensions tab</a> and click &#8220;Install&#8221;</li>\n		<li>Install and activate the latest version of soo_multidoc</li>\n	</ul>\n\n	<p>If all goes correctly, after the third step you will see a message confirming successful conversion of your existing Collections to the new format. Confirm that your Collections work as expected, then you are free to remove the <strong>Multidoc</strong> custom field.</p>\n\n	<p>Note that removing the field name in <a href=\"http://textpattern.net/wiki/index.php?title=Advanced_Preferences#Custom_Fields\">Advanced Preferences</a> does not remove the data, which could cause unexpected results if you re-use the field for something else. To clear the data run the following query in your database manager, taking great care to get the correct custom field, and backing up your database first:</p>\n\n<pre><code>UPDATE textpattern SET custom_n = &#39;&#39;\n</code></pre>\n\n	<p><span class=\"required\">Notes:</span></p>\n\n	<ul>\n		<li>Substitute &#8220;custom_n&#8221; with the correct custom field name as used in the textpattern table (i.e., replace the &#8220;n&#8221; with the correct number).</li>\n		<li>If you use a database table prefix, prepend it to &#8220;textpattern&#8221; in the query.</li>\n	</ul>\n\n	<h2 id=\"history\">Version History</h2>\n\n	<h3>0.1.3 (2017/02/15)</h3>\n\n	<ul>\n		<li>Textpattern 4.6 (and MySQL 5.7.4) compatibility update</li>\n	</ul>\n\n	<h3>0.1.2 (2011/04/14)</h3>\n\n	<ul>\n		<li>Multi-select for adding articles to a collection now contains all available articles</li>\n	</ul>\n\n	<h3>0.1.1 (2011/01/07)</h3>\n\n	<ul>\n		<li>Critical soo_multidoc 2.0.b.3 compatibility update</li>\n		<li>UI improvements</li>\n	</ul>\n\n	<h3>0.1 (9/2010)</h3>\n\n	<p>Initial release. Features:</p>\n\n	<ul>\n		<li>Dedicated <strong>Multidoc</strong> admin page, under Extensions tab</li>\n		<li>List, create, and delete <strong>Multidoc</strong> Collections</li>\n		<li>Add, delete, and rearrange pages within Collections</li>\n		<li>Collection data now stored in a dedicated database table:\n	<ul>\n		<li>Custom field no longer required</li>\n		<li>Faster page rendering</li>\n	</ul></li>\n	</ul>\n\n </div>', '\nrequire_plugin(\'soo_multidoc\');\ninclude_once(txpath . \'/publish/taghandlers.php\'); // for permlinkurl()\n\nif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'soo_multidoc_admin\',\'1,2\');\n	register_tab(\'extensions\', \'soo_multidoc_admin\', \'Multidoc\');\n	register_callback(\'soo_multidoc_admin\', \'soo_multidoc_admin\');\n	register_callback(\'soo_multidoc_admin_css\', \'admin_side\', \'head_end\');\n}\n\n// controller: installation check and event router\nfunction soo_multidoc_admin( $event, $step, $message = \'\' )\n{\n	if ( soo_multidoc_table_exists() )\n	{\n		if ( $step == \'uninstall\' )\n			$message .= soo_multidoc_uninstall();\n		elseif ( ! soo_multidoc_version(2) )\n		{\n			$message .= soo_multidoc_gTxt(\'upgrade\');\n			$step = \'admin\';\n		}\n		else switch ( $step )\n		{\n			case \'edit\':\n			case \'detail\':\n				if ( $id = intval(gps(\'id\')) )\n				{\n					if ( ! _soo_multidoc_ids_init($id) )\n					{\n						$step = \'list\';\n						$message .= soo_multidoc_gTxt(\'init_fail\');\n					}\n					elseif ( $step == \'edit\' )\n						$message .= soo_multidoc_edit($id);\n				}\n				else\n				{\n					$message .= soo_multidoc_gTxt(\'invalid_id\');\n					_soo_multidoc_ids_init();\n					$step = \'list\';\n				}\n				break;\n			case \'admin\':\n				break;\n			case \'help\':\n				break;\n			case \'new\':\n				if ( $new_root = assert_int(gps(\'new_root\')) )\n					soo_multidoc_new($new_root);\n			case \'delete\':\n				if ( $delete_id = intval(gps(\'delete_id\')) )\n					soo_multidoc_delete($delete_id);\n			default:\n				$step = \'list\';\n				_soo_multidoc_ids_init();\n		}\n	}\n	else switch ( $step )\n	{\n		case \'install\':\n			$message .= soo_multidoc_install();\n			break;\n		default:\n			$step = \'admin\';\n	}\n\n	soo_multidoc_admin_ui($step, $message);\n}\n\n// view: admin interface\nfunction soo_multidoc_admin_ui( $step, $message )\n{\n	pagetop(\'Multidoc\', $message);\n	$display[] = hed(soo_multidoc_gTxt(\'admin_header\'), 1);\n\n	foreach ( array(\'list\', \'admin\', \'help\') as $tab )\n		$tabs[] = sLink(\'soo_multidoc_admin\', $tab, soo_multidoc_gTxt(\"tab_$tab\"), \'navlink\' . ( $tab == $step ? \'-active\' : \'\' ));\n	$display[] = implode(sp, $tabs);\n\n	if ( ! soo_multidoc_table_exists() )\n	{\n		$display[] = soo_multidoc_gTxt(\'table_missing\');\n		$display[] = new soo_html_form(\n			array(\n				\'id\' => \'soo_multidoc_install_form\',\n				\'action\'	=> array(\n					\'event\'		=> \'soo_multidoc_admin\',\n					\'step\'		=> \'install\',\n				),\n			),\n			sInput(\'install\') .\n			eInput(\'soo_multidoc_admin\') .\n			\'<label>\' .\n			fInput(\'submit\', \'soo_multidoc_install\', gTxt(\'install\')) .\n			soo_multidoc_gTxt(\'install_info\')\n		);\n	}\n\n	else switch ( $step )\n	{\n		case \'edit\':\n		case \'detail\':\n			global $soo_multidoc;\n			$collection = $soo_multidoc[\'rowset\']->as_object_array();\n			$start = $collection[0];\n			if ( $id = intval(gps(\'id\')) and $step == \'edit\' )\n			{\n				$extra[\'edit_id\'] = $id;\n				$title = $soo_multidoc[\'data\'][$id][\'title\'];\n			}\n			$extra[\'start_children\'] = do_list($start->children);\n			array_walk_recursive($collection, \'_soo_multidoc_detail\', $extra);\n\n			if ( $action = gps(\'action\') and in_array($action, array(\'add\', \'change_type\')) )\n			{\n				$form_content = array(\n					new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'select_link_type\'), 5),\n						new soo_html_select(array(\n							\'name\'		=> \'link_type\'\n						), soo_multidoc_all_link_types()),\n					)),\n					br,\n					new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'create_link_type\'), 5),\n						new soo_html_input(\'text\', array(\'name\' => \'new_link_type\')\n						),)\n					),\n					br,\n					br,\n					new soo_html_input(\'submit\', array(\'value\'	=> $action == \'add\' ? gTxt(\'add\') : gTxt(\'update\'))\n					),\n				);\n				if ( $action == \'add\' )\n					array_unshift($form_content, new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'select_to_add\') . \"&ldquo;$title&rdquo;\", 4),\n						new soo_html_select(array(\n							\'multiple\'	=> \'multiple\',\n							\'name\'		=> \'add_nodes[]\',\n							\'size\'		=> 10,\n						), soo_non_multidoc_articles(0)))\n					));\n				if ( $action == \'change_type\' )\n					array_unshift($form_content, hed(gTxt(\'update\') . \" &ldquo;$title&rdquo;\", 4));\n				$update_form = new soo_html_form(\n					array(	// form attributes\n						\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'edit\',\n							\'id\'		=> $id,\n							\'action\'	=> $action,\n						),\n						\'id\'		=> \'soo_multidoc_add_node\',\n					), $form_content\n				);\n				$cancel_form = new soo_html_form(\n					array(\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'detail\',\n							\'id\'		=> $id,\n					)), new soo_html_input(\'submit\', array(\'value\' => gTxt(\'Cancel\')))\n				);\n			}\n			$display[] = hed(soo_multidoc_gTxt(\'collection\') . \': \' .$start->title, 2);\n\n			$display[] = new soo_html_ul(array(), $collection);\n			if ( isset($update_form) )\n			{\n				$display[] = $update_form;\n				$display[] = $cancel_form;\n			}\n			break;\n\n		case \'admin\':\n			$display[] = new soo_html_form(\n				array(\n					\'id\'		=> \'soo_multidoc_uninstall_form\',\n					\'action\'	=> array(\n						\'event\'		=> \'soo_multidoc_admin\',\n						\'step\'		=> \'uninstall\',\n					),\n					\'onsubmit\'	=> \"return verify(\'\" . soo_multidoc_gTxt(\'uninstall_warning\') . \"\')\",\n				),\n					sInput(\'uninstall\') .\n					eInput(\'soo_multidoc_admin\') .\n					tag(fInput(\'submit\', \'soo_multidoc_uninstall\', \'Uninstall\') . soo_multidoc_gTxt(\'uninstall_info\'), \'label\')\n			);\n			break;\n\n		case \'help\':\n			$display[] = new soo_html_ul(array(), array(\n				new soo_html_anchor(\'?event=plugin&amp;step=plugin_help&amp;name=soo_multidoc\', \'soo_multidoc help file\'),\n				new soo_html_anchor(\'?event=plugin&amp;step=plugin_help&amp;name=soo_multidoc_admin\', \'soo_multidoc_admin help file\'),\n				new soo_html_anchor(\'http://ipsedixit.net/txp/24/multidoc\', \'Online Multidoc user guide\'),\n				));\n			break;\n\n		default:\n			global $soo_multidoc;\n			if ( isset($soo_multidoc[\'rowset\']) )\n			{\n				$rs =  $soo_multidoc[\'rowset\']->rows;\n				foreach ( array_unique($soo_multidoc[\'id_root\']) as $root )\n				{\n					$items[] = eLink(\'soo_multidoc_admin\', \'detail\', \'id\', $root, $rs[$root]->title) . \' (\' . $rs[$root]->rgt/2 . \')\';\n				}\n			}\n			if ( empty($items) )\n				$display[] = $step == \'install\' ? soo_multidoc_gTxt(\'upgrade\') : soo_multidoc_gTxt(\'no_records\');\n			else\n				$display[] = new soo_html_ul(array(), $items);\n			$display[] = new soo_html_form(\n				array(	// form attributes\n					\'id\'		=> \'soo_multidoc_create_new\',\n					\'action\'	=> array(\n						\'event\'		=> \'soo_multidoc_admin\',\n						\'step\'		=> \'new\',\n					),\n				), array(	// form contents\n					hed(soo_multidoc_gTxt(\'new_collection\'), 2),\n					new soo_html_select(array(\'name\' => \'new_root\'), soo_non_multidoc_articles()),\n					new soo_html_input(\'submit\', array(\n						\'value\' => \'Create\'\n					))\n				)\n			);\n			if ( ! empty($rs) )\n			{\n				$id_title = array(\'default\' => \'\');\n				foreach ( $rs as $i => $r )\n					if ( in_array($i, $soo_multidoc[\'id_root\']) )\n						$id_title[$i] = $r->title;\n				$display[] = new soo_html_form(\n					array(	// form attributes\n						\'id\'		=> \'soo_multidoc_delete\',\n						\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'delete\',\n						),\n						\'onsubmit\'	=> \"return verify(\'\" . soo_multidoc_gTxt(\'delete_warning\') . \"\')\",\n					), array(	// form contents\n						hed(soo_multidoc_gTxt(\'delete_collection\'), 2),\n						new soo_html_select(array(\'name\' => \'delete_id\'), $id_title),\n						new soo_html_input(\'submit\', array(\n							\'value\' => \'Delete\'\n						))\n					)\n				);\n			}\n	}\n\n	$table = new soo_html_table(array(\n		\'id\'		=> \'list\',\n		\'class\'		=> \'soo_multidoc_admin\',\n	), $display);\n	echo $table->tag();\n\n}\n\nfunction _soo_multidoc_detail( &$node, $k, $extra )\n{\n	if ( is_object($node) )\n	{\n		extract($extra);\n		global $soo_multidoc;\n		$id = $node->id;\n		$widget = new soo_html_span(array(\'class\' => \'edit_widget\'));\n		$disabled = array(\'class\' => \'disabled\');\n		if ( $node->link_type != \'start\' )\n		{\n			$parent = $soo_multidoc[\'id_parent\'][$id];\n			$me_and_my_sibs = $soo_multidoc[\'id_children\'][$parent];\n			$is_only_child = count($me_and_my_sibs) == 1;\n			$is_first_child = $me_and_my_sibs[0] == $id;\n			$is_primary = in_array($id, $start_children);\n			$type_edit = eLink(\'soo_multidoc_admin\', \'edit\', \'id\', $id, $node->link_type, \'action\', \'change_type\');\n		}\n		else\n		{\n			$parent = $me_and_my_sibs = $is_only_child = $is_first_child = $is_primary = null;\n			$type_edit = $node->link_type;\n		}\n\n		foreach ( array(\n			\'add\'		=> \'+\',\n			\'left\'		=> \'&larr;\',\n			\'up\'		=> \'&uarr;\',\n			\'right\'		=> \'&rarr;\',\n			\'down\'		=> \'&darr;\',\n			\'delete\'	=> \'X\',\n		) as $action => $glyph )\n\n			if ( ( empty($parent) and $action != \'add\' ) or ( $is_primary and $action == \'left\' ) or ( $is_only_child and in_array($action, array(\'right\', \'up\', \'down\')) ) or ( $is_first_child and $action == \'right\' ) )\n				$widget->contents(new soo_html_span($disabled, $glyph));\n			else\n			{\n				$atts = array(\'href\' => \'?\' . implode(a, array(\n					\'event=soo_multidoc_admin\',\n					\'step=edit\',\n					\"id=$id\",\n					\"action=$action\",\n				)));\n				if ( $action == \'delete\' )\n					$atts[\'onclick\'] = \"return verify(\'\" . soo_multidoc_gTxt(\'delete_warning\') . \"\')\";\n				$widget->contents(new soo_html_anchor($atts, $glyph));\n			}\n\n		$node = eLink(\'article\', \'edit\', \'ID\', $id, $node->title) . $widget->tag() . \'<span class=\"type\">[\' . $type_edit . \']</span>\';\n\n//		\'<span class=\"type\">[\' . $node->link_type . \']</span>\';\n		if ( isset($edit_id) and $edit_id == $id )\n			$node = tag($node, \'div\', \' class=\"highlight\"\');\n	}\n}\n\nfunction soo_multidoc_new($new_root)\n{\n	$new = new soo_txp_upsert(new soo_txp_row(array(\n		\'lft\'	=> 1,\n		\'rgt\'	=> 2,\n		\'id\'	=> $new_root,\n		\'root\'	=> $new_root,\n		\'type\'	=> \'start\',\n	), \'soo_multidoc\'));\n	$new->upsert();\n}\n\nfunction soo_multidoc_delete($delete_id)\n{\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$root_node = $query->where(\'id\', $delete_id)->row();\n	$query = new soo_txp_delete(\'soo_multidoc\');\n	$query->where(\'root\', $root_node[\'root\'])\n		->where(\'lft\', $root_node[\'lft\'], \'>=\')\n		->where(\'rgt\', $root_node[\'rgt\'], \'<=\')\n		->delete();\n}\n\nfunction soo_multidoc_edit( $id )\n{\n	global $soo_multidoc;\n	$parent = $soo_multidoc[\'id_parent\'][$id];\n	$root = $soo_multidoc[\'id_root\'][$id];\n	if ( is_numeric($parent) )\n	{\n		$lft = $soo_multidoc[\'rowset\']->rows[$parent]->lft;\n		$me_and_my_sibs = $soo_multidoc[\'id_children\'][$parent];\n		$my_position = array_search($id, $me_and_my_sibs);\n	}\n\n	$action = doSlash(gps(\'action\'));\n	switch ( $action )\n	{\n		case \'add\':\n			if ( $add_nodes = gps(\'add_nodes\') )\n			{\n				$add_nodes = array_map(\'intval\', $add_nodes);\n				$update_children[$id] = array_merge($soo_multidoc[\'id_children\'][$id], $add_nodes);\n				$lft = 1;\n				$parent = $root;\n				foreach ( $add_nodes as $add_node )\n				{\n					$row_data[] = array(\n						\'id\'	=> $add_node,\n						\'root\'	=> $root,\n						\'type\'	=> soo_multidoc_new_type(),\n					);\n				}\n				$insert = new soo_txp_upsert(new soo_txp_rowset($row_data, \'soo_multidoc\'), array(\'id\', \'root\', \'type\'));\n				$insert->upsert();\n			}\n			break;\n\n		case \'delete\':\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			soo_multidoc_delete($id);\n			$lft = 1;\n			$parent = $root;\n			break;\n\n		case \'left\':\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			$new_parent = $soo_multidoc[\'id_parent\'][$parent];\n			$new_sibs = $soo_multidoc[\'id_children\'][$new_parent];\n			$new_position = array_search($parent, $new_sibs);\n			$new_sibs = array_merge(\n				array_slice($new_sibs, 0, $new_position + 1),\n				array($id),\n				array_slice($new_sibs, $new_position + 1)\n			);\n			$update_children[$new_parent] = $new_sibs;\n			$parent = $new_parent;\n			$lft = $soo_multidoc[\'rowset\']->rows[$parent]->lft;\n			break;\n\n		case \'right\':\n			$next_older_sib = $me_and_my_sibs[$my_position - 1];\n			$new_sibs = $soo_multidoc[\'id_children\'][$next_older_sib];\n			array_push($new_sibs, $id);\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			$update_children[$next_older_sib] = $new_sibs;\n			break;\n\n		case \'up\':\n		case \'down\':\n			$older = array_slice($me_and_my_sibs, 0, $my_position);\n			$younger = array_slice($me_and_my_sibs, $my_position + 1);\n			if ( $action == \'up\' )\n			{\n				if ( empty($older) )\n					array_push($younger, $id);\n				else\n				{\n					array_unshift($younger, array_pop($older));\n					array_push($older, $id);\n				}\n			}\n			elseif ( $action == \'down\' )\n			{\n				if ( empty($younger) )\n					array_unshift($older, $id);\n				else\n				{\n					array_push($older, array_shift($younger));\n					array_unshift($younger, $id);\n				}\n			}\n			$new_order = array_merge($older, $younger);\n\n			$update_children[$parent] = $new_order;\n			break;\n		case \'change_type\':\n			if ( $new_type = soo_multidoc_new_type() )\n			{\n				$query = new soo_txp_upsert(\'soo_multidoc\');\n				$query->where(\'id\', $id)\n					->set(\'type\', $new_type)\n					->upsert();\n				_soo_multidoc_ids_init($id, true);\n			}\n			break;\n\n	}\n	if ( ! empty($update_children) )\n	{\n		foreach ( $update_children as $i => $children )\n		{\n			$query = new soo_txp_upsert(\'soo_multidoc\');\n			$query->where(\'id\', $i)\n				->set(\'children\', implode(\',\', $children))\n				->upsert();\n		}\n		soo_multidoc_rebuild_tree($parent, $lft, $root);\n		_soo_multidoc_ids_init($root, true);\n	}\n}\n\nfunction soo_multidoc_rebuild_tree($parent, $left, $root)\n{\n	// modified from txplib_db.php rebuild_tree()\n\n	$left  = assert_int($left);\n	$right = $left + 1;\n\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$result = $query->select(\'children\')->where(\'id\', $parent)->row();\n	if ( current($result) )\n		foreach ( do_list(current($result)) as $child )\n			$right = soo_multidoc_rebuild_tree($child, $right, $root);\n\n	safe_update(\n		\'soo_multidoc\',\n		\"lft=$left, rgt=$right\",\n		\"id=\'$parent\' and root=\'$root\'\"\n	);\n	return $right + 1;\n}\n\nfunction soo_non_multidoc_articles( $limit = 0 )\n// return array of non-Multidoc article titles indexed by ID\n{\n	$query = new soo_txp_left_join(\'textpattern\', \'soo_multidoc\', \'ID\', \'id\');\n	$rowset = new soo_txp_rowset($query->select(array(\'ID\', \'Title\'))\n		->where_join_null(\'id\')\n		->limit($limit)\n		->order_by(\'LastMod\', \'desc\'));\n	return $rowset->field_vals(\'Title\', \'ID\');\n}\n\nfunction soo_multidoc_link_types()\n{\n	static $link_types = null;\n	if ( is_null($link_types) )\n	{\n		global $soo_multidoc;\n		$link_types = $soo_multidoc[\'id_link_type\'];\n		if ( count($link_types) > 1 )\n		{\n			$link_types = array_diff(array_unique($link_types));\n			sort($link_types);\n			$link_types = array_combine($link_types, $link_types);\n		}\n	}\n	return $link_types;\n}\n\nfunction soo_multidoc_all_link_types()\n{\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$query->select(\'type\')->distinct()->order_by(\'type\');\n	$rowset = new soo_txp_rowset($query);\n	$types = $rowset->field_vals(\'type\', \'type\');\n	unset($types[\'start\']);\n	return $types;\n}\n\nfunction soo_multidoc_new_type()\n{\n	$link_type = doSlash(gps(\'new_link_type\'));\n	$link_type = $link_type ? $link_type : doSlash(gps(\'link_type\'));\n	return preg_match(\'/\\W/\', $link_type) ? null : $link_type;\n}\n\nfunction soo_multidoc_install()\n{\n	// soo_multidoc table structure\n	// id:	article id#\n	// root: root article id#\n	// children: comma-separated list of child id#s\n	// lft: preorder tree value\n	// rgt: as above\n	// type: link type (Page|Section|Contents etc.)\n	safe_query(\n		\"CREATE TABLE IF NOT EXISTS \" . safe_pfx(\'soo_multidoc\') . \" (\n			`id` int(11) NOT NULL DEFAULT 0,\n			`root` int(11) NOT NULL DEFAULT 0,\n			`children` varchar(255) NOT NULL DEFAULT \'\',\n			`lft` int(6) NOT NULL DEFAULT \'0\',\n			`rgt` int(6) NOT NULL DEFAULT \'0\',\n			`type` varchar(255) NOT NULL DEFAULT \'\',\n			PRIMARY KEY (`id`)\n		)\"\n	);\n	if ( ! soo_multidoc_table_exists() )\n		return soo_multidoc_gTxt(\'create_fail\');\n\n	$message = soo_multidoc_gTxt(\'create_success\');\n\n	$rs = new soo_txp_rowset(new soo_txp_select(\'soo_multidoc\'));\n	if ( count($rs->rows) )\n		return soo_multidoc_gTxt(\'table_exists\');\n	if ( ! ( function_exists(\'_soo_multidoc_ids_init\') and soo_multidoc_version() < 2 ) )\n		return $message . soo_multidoc_gTxt(\'convert_unable\');\n	if ( ! _soo_multidoc_ids_init() )\n		return $message . soo_multidoc_gTxt(\'nothing_to_convert\');\n\n	global $soo_multidoc;\n	extract($soo_multidoc);\n	$data = array(\'lft\' => 1, \'rgt\' => 2);\n	foreach ( array_combine(array_unique($id_root), array_unique($id_root)) + $id_root as $id => $root )\n	{\n		$data[\'id\'] = $id;\n		$data[\'root\'] = $root;\n		$data[\'type\'] = isset($id_link_type[$id]) ? $id_link_type[$id] : \'\';\n		$data[\'children\'] = ! empty($id_children[$id]) ? implode(\',\', $id_children[$id]) : \'\';\n		$rs->add_row($data);\n	}\n\n	$query = new soo_txp_upsert($rs, array_keys($data));\n\n	if ( $query->upsert() and $num_rows = mysql_affected_rows() )\n	{\n		foreach ( array_unique($id_root) as $root )\n			soo_multidoc_rebuild_tree($root, 1, $root);\n		$message .= \', \' . $num_rows . soo_multidoc_gTxt(\'converted_num\');\n	}\n	else\n		$message .= soo_multidoc_gTxt(\'convert_fail\');\n\n	return $message;\n}\n\nfunction soo_multidoc_uninstall()\n{\n	if ( safe_query(\'DROP TABLE IF EXISTS \' . safe_pfx(\'soo_multidoc\') . \';\') and ! soo_multidoc_table_exists() )\n		return soo_multidoc_gTxt(\'table_deleted\');\n	else\n		return soo_multidoc_gTxt(\'table_not_deleted\');\n}\n\nfunction soo_multidoc_table_exists( )\n{\n	return mysqli_fetch_row(safe_query(\"show tables like \'%soo_multidoc\'\"));\n}\n\nfunction soo_multidoc_version( $min = null )\n{\n	static $cache = null;\n	if ( is_null($cache) )\n	{\n		global $plugins_ver;					//var_dump($plugins_ver);\n		$cache = isset($plugins_ver[\'soo_multidoc\']) ? floatval($plugins_ver[\'soo_multidoc\']) : 0;\n	}\n	return $min = floatval($min) ? $cache >= $min : $cache;\n}\n\nglobal $soo_multidoc_strings;\nif ( ! is_array($soo_multidoc_strings) ) $soo_multidoc_strings = array();\n$soo_multidoc_strings = array_merge($soo_multidoc_strings, array(\n	\'admin_header\'		=>	\'Multidoc Admin\',\n	\'tab_list\'			=>	\'Collections\',\n	\'tab_detail\'		=>	\'Detail\',\n	\'tab_admin\'			=>	\'Admin\',\n	\'tab_help\'			=>	\'Help\',\n	\'table_missing\'		=>	\'<b>Multidoc&rsquo;s</b> table does not exist.\',\n	\'table_exists\'		=>	\'soo_multidoc table already exists.\',\n	\'table_deleted\'		=>	\'The soo_multidoc table has been deleted. To finish uninstalling, delete the soo_multidoc_admin and soo_multidoc plugins (and soo_plugin_pref and soo_txp_obj if not otherwise required).\',\n	\'table_not_deleted\'	=>	\'Table deletion failed.\',\n	\'install_info\'		=>	\' the soo_multidoc table</label>\',\n	\'create_fail\'		=>	\'Table creation failed.\',\n	\'create_success\'	=>	\'soo_multidoc table created\',\n	\'convert_unable\'		=>	\', but failed to check for existing Multidoc records. See \"Installation\" in plugin help.\',\n	\'nothing_to_convert\'=>	\'. No existing Multidoc records to convert.\',\n	\'converted_num\'		=>	\' entries automatically converted from old format.\',\n	\'convert_fail\'		=>	\', but automatic conversion of existing <b>Multidoc</b> entries failed. This might be because of invalid Multidoc data.\',\n	\'uninstall_info\'	=>	\' the <b>Multidoc</b> table\',\n	\'uninstall_warning\'	=>	\'Really delete the Multidoc table? You cannot undo this.\',\n	\'upgrade\'			=>	\' To complete the upgrade you must now upgrade the soo_multidoc plugin.\',\n	\'init_fail\'			=>	\'Data initialization failed.\',\n	\'invalid_id\'		=>	\'Invalid ID value\',\n	\'no_records\'		=>	\'No Multidoc collections found.\',\n	\'collection\'		=>	\'Collection\',\n	\'select_to_add\'		=>	\'Select articles to add under \',\n	\'select_link_type\'	=>	\'Select link type\',\n	\'create_link_type\'	=>	\'or create new link type\',\n	\'new_collection\'	=>	\'Create new Collection based on\',\n	\'delete_warning\'	=>	\'This article (and any children) will be removed from Multidoc; this cannot be undone. Proceed?\',\n	\'delete_collection\'	=>	\'Delete Collection\',\n));\n\n\nfunction soo_multidoc_admin_css( )\n{\n	if ( gps(\'event\') == \'soo_multidoc_admin\' )\n		echo <<<EOF\n<style type=\"text/css\"><!--\n.soo_multidoc_admin ul ul, .soo_multidoc_admin ol ol { margin-left: 2em; }\n.soo_multidoc_admin li { margin: 0.5em 0 !important; }\n.soo_multidoc_admin span.type\n{\n	font-size: smaller;\n	color: #aaa;\n	float: right;\n	margin: 0;\n}\ntable.soo_multidoc_admin  { margin: 0 0 0 32em; width: 45em; }\n.soo_multidoc_admin .edit_widget { float: right; margin-left: 2em; }\n.soo_multidoc_admin .edit_widget a, .soo_multidoc_admin .edit_widget .disabled\n{\n	border: 1px solid #bbb;\n	padding: 0 0.2em;\n	margin:  0 0.2em;\n}\n.soo_multidoc_admin .edit_widget .disabled { color: #fff; border-color: #fff }\n.soo_multidoc_admin .highlight { background: #dfc; }\n.left { color: blue; }\n--></style>\n\nEOF;\n}\n', '\nrequire_plugin(\'soo_multidoc\');\ninclude_once(txpath . \'/publish/taghandlers.php\'); // for permlinkurl()\n\nif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'soo_multidoc_admin\',\'1,2\');\n	register_tab(\'extensions\', \'soo_multidoc_admin\', \'Multidoc\');\n	register_callback(\'soo_multidoc_admin\', \'soo_multidoc_admin\');\n	register_callback(\'soo_multidoc_admin_css\', \'admin_side\', \'head_end\');\n}\n\n// controller: installation check and event router\nfunction soo_multidoc_admin( $event, $step, $message = \'\' )\n{\n	if ( soo_multidoc_table_exists() )\n	{\n		if ( $step == \'uninstall\' )\n			$message .= soo_multidoc_uninstall();\n		elseif ( ! soo_multidoc_version(2) )\n		{\n			$message .= soo_multidoc_gTxt(\'upgrade\');\n			$step = \'admin\';\n		}\n		else switch ( $step )\n		{\n			case \'edit\':\n			case \'detail\':\n				if ( $id = intval(gps(\'id\')) )\n				{\n					if ( ! _soo_multidoc_ids_init($id) )\n					{\n						$step = \'list\';\n						$message .= soo_multidoc_gTxt(\'init_fail\');\n					}\n					elseif ( $step == \'edit\' )\n						$message .= soo_multidoc_edit($id);\n				}\n				else\n				{\n					$message .= soo_multidoc_gTxt(\'invalid_id\');\n					_soo_multidoc_ids_init();\n					$step = \'list\';\n				}\n				break;\n			case \'admin\':\n				break;\n			case \'help\':\n				break;\n			case \'new\':\n				if ( $new_root = assert_int(gps(\'new_root\')) )\n					soo_multidoc_new($new_root);\n			case \'delete\':\n				if ( $delete_id = intval(gps(\'delete_id\')) )\n					soo_multidoc_delete($delete_id);\n			default:\n				$step = \'list\';\n				_soo_multidoc_ids_init();\n		}\n	}\n	else switch ( $step )\n	{\n		case \'install\':\n			$message .= soo_multidoc_install();\n			break;\n		default:\n			$step = \'admin\';\n	}\n\n	soo_multidoc_admin_ui($step, $message);\n}\n\n// view: admin interface\nfunction soo_multidoc_admin_ui( $step, $message )\n{\n	pagetop(\'Multidoc\', $message);\n	$display[] = hed(soo_multidoc_gTxt(\'admin_header\'), 1);\n\n	foreach ( array(\'list\', \'admin\', \'help\') as $tab )\n		$tabs[] = sLink(\'soo_multidoc_admin\', $tab, soo_multidoc_gTxt(\"tab_$tab\"), \'navlink\' . ( $tab == $step ? \'-active\' : \'\' ));\n	$display[] = implode(sp, $tabs);\n\n	if ( ! soo_multidoc_table_exists() )\n	{\n		$display[] = soo_multidoc_gTxt(\'table_missing\');\n		$display[] = new soo_html_form(\n			array(\n				\'id\' => \'soo_multidoc_install_form\',\n				\'action\'	=> array(\n					\'event\'		=> \'soo_multidoc_admin\',\n					\'step\'		=> \'install\',\n				),\n			),\n			sInput(\'install\') .\n			eInput(\'soo_multidoc_admin\') .\n			\'<label>\' .\n			fInput(\'submit\', \'soo_multidoc_install\', gTxt(\'install\')) .\n			soo_multidoc_gTxt(\'install_info\')\n		);\n	}\n\n	else switch ( $step )\n	{\n		case \'edit\':\n		case \'detail\':\n			global $soo_multidoc;\n			$collection = $soo_multidoc[\'rowset\']->as_object_array();\n			$start = $collection[0];\n			if ( $id = intval(gps(\'id\')) and $step == \'edit\' )\n			{\n				$extra[\'edit_id\'] = $id;\n				$title = $soo_multidoc[\'data\'][$id][\'title\'];\n			}\n			$extra[\'start_children\'] = do_list($start->children);\n			array_walk_recursive($collection, \'_soo_multidoc_detail\', $extra);\n\n			if ( $action = gps(\'action\') and in_array($action, array(\'add\', \'change_type\')) )\n			{\n				$form_content = array(\n					new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'select_link_type\'), 5),\n						new soo_html_select(array(\n							\'name\'		=> \'link_type\'\n						), soo_multidoc_all_link_types()),\n					)),\n					br,\n					new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'create_link_type\'), 5),\n						new soo_html_input(\'text\', array(\'name\' => \'new_link_type\')\n						),)\n					),\n					br,\n					br,\n					new soo_html_input(\'submit\', array(\'value\'	=> $action == \'add\' ? gTxt(\'add\') : gTxt(\'update\'))\n					),\n				);\n				if ( $action == \'add\' )\n					array_unshift($form_content, new soo_html_label(array(), array(\n						hed(soo_multidoc_gTxt(\'select_to_add\') . \"&ldquo;$title&rdquo;\", 4),\n						new soo_html_select(array(\n							\'multiple\'	=> \'multiple\',\n							\'name\'		=> \'add_nodes[]\',\n							\'size\'		=> 10,\n						), soo_non_multidoc_articles(0)))\n					));\n				if ( $action == \'change_type\' )\n					array_unshift($form_content, hed(gTxt(\'update\') . \" &ldquo;$title&rdquo;\", 4));\n				$update_form = new soo_html_form(\n					array(	// form attributes\n						\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'edit\',\n							\'id\'		=> $id,\n							\'action\'	=> $action,\n						),\n						\'id\'		=> \'soo_multidoc_add_node\',\n					), $form_content\n				);\n				$cancel_form = new soo_html_form(\n					array(\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'detail\',\n							\'id\'		=> $id,\n					)), new soo_html_input(\'submit\', array(\'value\' => gTxt(\'Cancel\')))\n				);\n			}\n			$display[] = hed(soo_multidoc_gTxt(\'collection\') . \': \' .$start->title, 2);\n\n			$display[] = new soo_html_ul(array(), $collection);\n			if ( isset($update_form) )\n			{\n				$display[] = $update_form;\n				$display[] = $cancel_form;\n			}\n			break;\n\n		case \'admin\':\n			$display[] = new soo_html_form(\n				array(\n					\'id\'		=> \'soo_multidoc_uninstall_form\',\n					\'action\'	=> array(\n						\'event\'		=> \'soo_multidoc_admin\',\n						\'step\'		=> \'uninstall\',\n					),\n					\'onsubmit\'	=> \"return verify(\'\" . soo_multidoc_gTxt(\'uninstall_warning\') . \"\')\",\n				),\n					sInput(\'uninstall\') .\n					eInput(\'soo_multidoc_admin\') .\n					tag(fInput(\'submit\', \'soo_multidoc_uninstall\', \'Uninstall\') . soo_multidoc_gTxt(\'uninstall_info\'), \'label\')\n			);\n			break;\n\n		case \'help\':\n			$display[] = new soo_html_ul(array(), array(\n				new soo_html_anchor(\'?event=plugin&amp;step=plugin_help&amp;name=soo_multidoc\', \'soo_multidoc help file\'),\n				new soo_html_anchor(\'?event=plugin&amp;step=plugin_help&amp;name=soo_multidoc_admin\', \'soo_multidoc_admin help file\'),\n				new soo_html_anchor(\'http://ipsedixit.net/txp/24/multidoc\', \'Online Multidoc user guide\'),\n				));\n			break;\n\n		default:\n			global $soo_multidoc;\n			if ( isset($soo_multidoc[\'rowset\']) )\n			{\n				$rs =  $soo_multidoc[\'rowset\']->rows;\n				foreach ( array_unique($soo_multidoc[\'id_root\']) as $root )\n				{\n					$items[] = eLink(\'soo_multidoc_admin\', \'detail\', \'id\', $root, $rs[$root]->title) . \' (\' . $rs[$root]->rgt/2 . \')\';\n				}\n			}\n			if ( empty($items) )\n				$display[] = $step == \'install\' ? soo_multidoc_gTxt(\'upgrade\') : soo_multidoc_gTxt(\'no_records\');\n			else\n				$display[] = new soo_html_ul(array(), $items);\n			$display[] = new soo_html_form(\n				array(	// form attributes\n					\'id\'		=> \'soo_multidoc_create_new\',\n					\'action\'	=> array(\n						\'event\'		=> \'soo_multidoc_admin\',\n						\'step\'		=> \'new\',\n					),\n				), array(	// form contents\n					hed(soo_multidoc_gTxt(\'new_collection\'), 2),\n					new soo_html_select(array(\'name\' => \'new_root\'), soo_non_multidoc_articles()),\n					new soo_html_input(\'submit\', array(\n						\'value\' => \'Create\'\n					))\n				)\n			);\n			if ( ! empty($rs) )\n			{\n				$id_title = array(\'default\' => \'\');\n				foreach ( $rs as $i => $r )\n					if ( in_array($i, $soo_multidoc[\'id_root\']) )\n						$id_title[$i] = $r->title;\n				$display[] = new soo_html_form(\n					array(	// form attributes\n						\'id\'		=> \'soo_multidoc_delete\',\n						\'action\'	=> array(\n							\'event\'		=> \'soo_multidoc_admin\',\n							\'step\'		=> \'delete\',\n						),\n						\'onsubmit\'	=> \"return verify(\'\" . soo_multidoc_gTxt(\'delete_warning\') . \"\')\",\n					), array(	// form contents\n						hed(soo_multidoc_gTxt(\'delete_collection\'), 2),\n						new soo_html_select(array(\'name\' => \'delete_id\'), $id_title),\n						new soo_html_input(\'submit\', array(\n							\'value\' => \'Delete\'\n						))\n					)\n				);\n			}\n	}\n\n	$table = new soo_html_table(array(\n		\'id\'		=> \'list\',\n		\'class\'		=> \'soo_multidoc_admin\',\n	), $display);\n	echo $table->tag();\n\n}\n\nfunction _soo_multidoc_detail( &$node, $k, $extra )\n{\n	if ( is_object($node) )\n	{\n		extract($extra);\n		global $soo_multidoc;\n		$id = $node->id;\n		$widget = new soo_html_span(array(\'class\' => \'edit_widget\'));\n		$disabled = array(\'class\' => \'disabled\');\n		if ( $node->link_type != \'start\' )\n		{\n			$parent = $soo_multidoc[\'id_parent\'][$id];\n			$me_and_my_sibs = $soo_multidoc[\'id_children\'][$parent];\n			$is_only_child = count($me_and_my_sibs) == 1;\n			$is_first_child = $me_and_my_sibs[0] == $id;\n			$is_primary = in_array($id, $start_children);\n			$type_edit = eLink(\'soo_multidoc_admin\', \'edit\', \'id\', $id, $node->link_type, \'action\', \'change_type\');\n		}\n		else\n		{\n			$parent = $me_and_my_sibs = $is_only_child = $is_first_child = $is_primary = null;\n			$type_edit = $node->link_type;\n		}\n\n		foreach ( array(\n			\'add\'		=> \'+\',\n			\'left\'		=> \'&larr;\',\n			\'up\'		=> \'&uarr;\',\n			\'right\'		=> \'&rarr;\',\n			\'down\'		=> \'&darr;\',\n			\'delete\'	=> \'X\',\n		) as $action => $glyph )\n\n			if ( ( empty($parent) and $action != \'add\' ) or ( $is_primary and $action == \'left\' ) or ( $is_only_child and in_array($action, array(\'right\', \'up\', \'down\')) ) or ( $is_first_child and $action == \'right\' ) )\n				$widget->contents(new soo_html_span($disabled, $glyph));\n			else\n			{\n				$atts = array(\'href\' => \'?\' . implode(a, array(\n					\'event=soo_multidoc_admin\',\n					\'step=edit\',\n					\"id=$id\",\n					\"action=$action\",\n				)));\n				if ( $action == \'delete\' )\n					$atts[\'onclick\'] = \"return verify(\'\" . soo_multidoc_gTxt(\'delete_warning\') . \"\')\";\n				$widget->contents(new soo_html_anchor($atts, $glyph));\n			}\n\n		$node = eLink(\'article\', \'edit\', \'ID\', $id, $node->title) . $widget->tag() . \'<span class=\"type\">[\' . $type_edit . \']</span>\';\n\n//		\'<span class=\"type\">[\' . $node->link_type . \']</span>\';\n		if ( isset($edit_id) and $edit_id == $id )\n			$node = tag($node, \'div\', \' class=\"highlight\"\');\n	}\n}\n\nfunction soo_multidoc_new($new_root)\n{\n	$new = new soo_txp_upsert(new soo_txp_row(array(\n		\'lft\'	=> 1,\n		\'rgt\'	=> 2,\n		\'id\'	=> $new_root,\n		\'root\'	=> $new_root,\n		\'type\'	=> \'start\',\n	), \'soo_multidoc\'));\n	$new->upsert();\n}\n\nfunction soo_multidoc_delete($delete_id)\n{\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$root_node = $query->where(\'id\', $delete_id)->row();\n	$query = new soo_txp_delete(\'soo_multidoc\');\n	$query->where(\'root\', $root_node[\'root\'])\n		->where(\'lft\', $root_node[\'lft\'], \'>=\')\n		->where(\'rgt\', $root_node[\'rgt\'], \'<=\')\n		->delete();\n}\n\nfunction soo_multidoc_edit( $id )\n{\n	global $soo_multidoc;\n	$parent = $soo_multidoc[\'id_parent\'][$id];\n	$root = $soo_multidoc[\'id_root\'][$id];\n	if ( is_numeric($parent) )\n	{\n		$lft = $soo_multidoc[\'rowset\']->rows[$parent]->lft;\n		$me_and_my_sibs = $soo_multidoc[\'id_children\'][$parent];\n		$my_position = array_search($id, $me_and_my_sibs);\n	}\n\n	$action = doSlash(gps(\'action\'));\n	switch ( $action )\n	{\n		case \'add\':\n			if ( $add_nodes = gps(\'add_nodes\') )\n			{\n				$add_nodes = array_map(\'intval\', $add_nodes);\n				$update_children[$id] = array_merge($soo_multidoc[\'id_children\'][$id], $add_nodes);\n				$lft = 1;\n				$parent = $root;\n				foreach ( $add_nodes as $add_node )\n				{\n					$row_data[] = array(\n						\'id\'	=> $add_node,\n						\'root\'	=> $root,\n						\'type\'	=> soo_multidoc_new_type(),\n					);\n				}\n				$insert = new soo_txp_upsert(new soo_txp_rowset($row_data, \'soo_multidoc\'), array(\'id\', \'root\', \'type\'));\n				$insert->upsert();\n			}\n			break;\n\n		case \'delete\':\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			soo_multidoc_delete($id);\n			$lft = 1;\n			$parent = $root;\n			break;\n\n		case \'left\':\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			$new_parent = $soo_multidoc[\'id_parent\'][$parent];\n			$new_sibs = $soo_multidoc[\'id_children\'][$new_parent];\n			$new_position = array_search($parent, $new_sibs);\n			$new_sibs = array_merge(\n				array_slice($new_sibs, 0, $new_position + 1),\n				array($id),\n				array_slice($new_sibs, $new_position + 1)\n			);\n			$update_children[$new_parent] = $new_sibs;\n			$parent = $new_parent;\n			$lft = $soo_multidoc[\'rowset\']->rows[$parent]->lft;\n			break;\n\n		case \'right\':\n			$next_older_sib = $me_and_my_sibs[$my_position - 1];\n			$new_sibs = $soo_multidoc[\'id_children\'][$next_older_sib];\n			array_push($new_sibs, $id);\n			unset($me_and_my_sibs[$my_position]);\n			$update_children[$parent] = $me_and_my_sibs;\n			$update_children[$next_older_sib] = $new_sibs;\n			break;\n\n		case \'up\':\n		case \'down\':\n			$older = array_slice($me_and_my_sibs, 0, $my_position);\n			$younger = array_slice($me_and_my_sibs, $my_position + 1);\n			if ( $action == \'up\' )\n			{\n				if ( empty($older) )\n					array_push($younger, $id);\n				else\n				{\n					array_unshift($younger, array_pop($older));\n					array_push($older, $id);\n				}\n			}\n			elseif ( $action == \'down\' )\n			{\n				if ( empty($younger) )\n					array_unshift($older, $id);\n				else\n				{\n					array_push($older, array_shift($younger));\n					array_unshift($younger, $id);\n				}\n			}\n			$new_order = array_merge($older, $younger);\n\n			$update_children[$parent] = $new_order;\n			break;\n		case \'change_type\':\n			if ( $new_type = soo_multidoc_new_type() )\n			{\n				$query = new soo_txp_upsert(\'soo_multidoc\');\n				$query->where(\'id\', $id)\n					->set(\'type\', $new_type)\n					->upsert();\n				_soo_multidoc_ids_init($id, true);\n			}\n			break;\n\n	}\n	if ( ! empty($update_children) )\n	{\n		foreach ( $update_children as $i => $children )\n		{\n			$query = new soo_txp_upsert(\'soo_multidoc\');\n			$query->where(\'id\', $i)\n				->set(\'children\', implode(\',\', $children))\n				->upsert();\n		}\n		soo_multidoc_rebuild_tree($parent, $lft, $root);\n		_soo_multidoc_ids_init($root, true);\n	}\n}\n\nfunction soo_multidoc_rebuild_tree($parent, $left, $root)\n{\n	// modified from txplib_db.php rebuild_tree()\n\n	$left  = assert_int($left);\n	$right = $left + 1;\n\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$result = $query->select(\'children\')->where(\'id\', $parent)->row();\n	if ( current($result) )\n		foreach ( do_list(current($result)) as $child )\n			$right = soo_multidoc_rebuild_tree($child, $right, $root);\n\n	safe_update(\n		\'soo_multidoc\',\n		\"lft=$left, rgt=$right\",\n		\"id=\'$parent\' and root=\'$root\'\"\n	);\n	return $right + 1;\n}\n\nfunction soo_non_multidoc_articles( $limit = 0 )\n// return array of non-Multidoc article titles indexed by ID\n{\n	$query = new soo_txp_left_join(\'textpattern\', \'soo_multidoc\', \'ID\', \'id\');\n	$rowset = new soo_txp_rowset($query->select(array(\'ID\', \'Title\'))\n		->where_join_null(\'id\')\n		->limit($limit)\n		->order_by(\'LastMod\', \'desc\'));\n	return $rowset->field_vals(\'Title\', \'ID\');\n}\n\nfunction soo_multidoc_link_types()\n{\n	static $link_types = null;\n	if ( is_null($link_types) )\n	{\n		global $soo_multidoc;\n		$link_types = $soo_multidoc[\'id_link_type\'];\n		if ( count($link_types) > 1 )\n		{\n			$link_types = array_diff(array_unique($link_types));\n			sort($link_types);\n			$link_types = array_combine($link_types, $link_types);\n		}\n	}\n	return $link_types;\n}\n\nfunction soo_multidoc_all_link_types()\n{\n	$query = new soo_txp_select(\'soo_multidoc\');\n	$query->select(\'type\')->distinct()->order_by(\'type\');\n	$rowset = new soo_txp_rowset($query);\n	$types = $rowset->field_vals(\'type\', \'type\');\n	unset($types[\'start\']);\n	return $types;\n}\n\nfunction soo_multidoc_new_type()\n{\n	$link_type = doSlash(gps(\'new_link_type\'));\n	$link_type = $link_type ? $link_type : doSlash(gps(\'link_type\'));\n	return preg_match(\'/\\W/\', $link_type) ? null : $link_type;\n}\n\nfunction soo_multidoc_install()\n{\n	// soo_multidoc table structure\n	// id:	article id#\n	// root: root article id#\n	// children: comma-separated list of child id#s\n	// lft: preorder tree value\n	// rgt: as above\n	// type: link type (Page|Section|Contents etc.)\n	safe_query(\n		\"CREATE TABLE IF NOT EXISTS \" . safe_pfx(\'soo_multidoc\') . \" (\n			`id` int(11) NOT NULL DEFAULT 0,\n			`root` int(11) NOT NULL DEFAULT 0,\n			`children` varchar(255) NOT NULL DEFAULT \'\',\n			`lft` int(6) NOT NULL DEFAULT \'0\',\n			`rgt` int(6) NOT NULL DEFAULT \'0\',\n			`type` varchar(255) NOT NULL DEFAULT \'\',\n			PRIMARY KEY (`id`)\n		)\"\n	);\n	if ( ! soo_multidoc_table_exists() )\n		return soo_multidoc_gTxt(\'create_fail\');\n\n	$message = soo_multidoc_gTxt(\'create_success\');\n\n	$rs = new soo_txp_rowset(new soo_txp_select(\'soo_multidoc\'));\n	if ( count($rs->rows) )\n		return soo_multidoc_gTxt(\'table_exists\');\n	if ( ! ( function_exists(\'_soo_multidoc_ids_init\') and soo_multidoc_version() < 2 ) )\n		return $message . soo_multidoc_gTxt(\'convert_unable\');\n	if ( ! _soo_multidoc_ids_init() )\n		return $message . soo_multidoc_gTxt(\'nothing_to_convert\');\n\n	global $soo_multidoc;\n	extract($soo_multidoc);\n	$data = array(\'lft\' => 1, \'rgt\' => 2);\n	foreach ( array_combine(array_unique($id_root), array_unique($id_root)) + $id_root as $id => $root )\n	{\n		$data[\'id\'] = $id;\n		$data[\'root\'] = $root;\n		$data[\'type\'] = isset($id_link_type[$id]) ? $id_link_type[$id] : \'\';\n		$data[\'children\'] = ! empty($id_children[$id]) ? implode(\',\', $id_children[$id]) : \'\';\n		$rs->add_row($data);\n	}\n\n	$query = new soo_txp_upsert($rs, array_keys($data));\n\n	if ( $query->upsert() and $num_rows = mysql_affected_rows() )\n	{\n		foreach ( array_unique($id_root) as $root )\n			soo_multidoc_rebuild_tree($root, 1, $root);\n		$message .= \', \' . $num_rows . soo_multidoc_gTxt(\'converted_num\');\n	}\n	else\n		$message .= soo_multidoc_gTxt(\'convert_fail\');\n\n	return $message;\n}\n\nfunction soo_multidoc_uninstall()\n{\n	if ( safe_query(\'DROP TABLE IF EXISTS \' . safe_pfx(\'soo_multidoc\') . \';\') and ! soo_multidoc_table_exists() )\n		return soo_multidoc_gTxt(\'table_deleted\');\n	else\n		return soo_multidoc_gTxt(\'table_not_deleted\');\n}\n\nfunction soo_multidoc_table_exists( )\n{\n	return mysqli_fetch_row(safe_query(\"show tables like \'%soo_multidoc\'\"));\n}\n\nfunction soo_multidoc_version( $min = null )\n{\n	static $cache = null;\n	if ( is_null($cache) )\n	{\n		global $plugins_ver;					//var_dump($plugins_ver);\n		$cache = isset($plugins_ver[\'soo_multidoc\']) ? floatval($plugins_ver[\'soo_multidoc\']) : 0;\n	}\n	return $min = floatval($min) ? $cache >= $min : $cache;\n}\n\nglobal $soo_multidoc_strings;\nif ( ! is_array($soo_multidoc_strings) ) $soo_multidoc_strings = array();\n$soo_multidoc_strings = array_merge($soo_multidoc_strings, array(\n	\'admin_header\'		=>	\'Multidoc Admin\',\n	\'tab_list\'			=>	\'Collections\',\n	\'tab_detail\'		=>	\'Detail\',\n	\'tab_admin\'			=>	\'Admin\',\n	\'tab_help\'			=>	\'Help\',\n	\'table_missing\'		=>	\'<b>Multidoc&rsquo;s</b> table does not exist.\',\n	\'table_exists\'		=>	\'soo_multidoc table already exists.\',\n	\'table_deleted\'		=>	\'The soo_multidoc table has been deleted. To finish uninstalling, delete the soo_multidoc_admin and soo_multidoc plugins (and soo_plugin_pref and soo_txp_obj if not otherwise required).\',\n	\'table_not_deleted\'	=>	\'Table deletion failed.\',\n	\'install_info\'		=>	\' the soo_multidoc table</label>\',\n	\'create_fail\'		=>	\'Table creation failed.\',\n	\'create_success\'	=>	\'soo_multidoc table created\',\n	\'convert_unable\'		=>	\', but failed to check for existing Multidoc records. See \"Installation\" in plugin help.\',\n	\'nothing_to_convert\'=>	\'. No existing Multidoc records to convert.\',\n	\'converted_num\'		=>	\' entries automatically converted from old format.\',\n	\'convert_fail\'		=>	\', but automatic conversion of existing <b>Multidoc</b> entries failed. This might be because of invalid Multidoc data.\',\n	\'uninstall_info\'	=>	\' the <b>Multidoc</b> table\',\n	\'uninstall_warning\'	=>	\'Really delete the Multidoc table? You cannot undo this.\',\n	\'upgrade\'			=>	\' To complete the upgrade you must now upgrade the soo_multidoc plugin.\',\n	\'init_fail\'			=>	\'Data initialization failed.\',\n	\'invalid_id\'		=>	\'Invalid ID value\',\n	\'no_records\'		=>	\'No Multidoc collections found.\',\n	\'collection\'		=>	\'Collection\',\n	\'select_to_add\'		=>	\'Select articles to add under \',\n	\'select_link_type\'	=>	\'Select link type\',\n	\'create_link_type\'	=>	\'or create new link type\',\n	\'new_collection\'	=>	\'Create new Collection based on\',\n	\'delete_warning\'	=>	\'This article (and any children) will be removed from Multidoc; this cannot be undone. Proceed?\',\n	\'delete_collection\'	=>	\'Delete Collection\',\n));\n\n\nfunction soo_multidoc_admin_css( )\n{\n	if ( gps(\'event\') == \'soo_multidoc_admin\' )\n		echo <<<EOF\n<style type=\"text/css\"><!--\n.soo_multidoc_admin ul ul, .soo_multidoc_admin ol ol { margin-left: 2em; }\n.soo_multidoc_admin li { margin: 0.5em 0 !important; }\n.soo_multidoc_admin span.type\n{\n	font-size: smaller;\n	color: #aaa;\n	float: right;\n	margin: 0;\n}\ntable.soo_multidoc_admin  { margin: 0 0 0 32em; width: 45em; }\n.soo_multidoc_admin .edit_widget { float: right; margin-left: 2em; }\n.soo_multidoc_admin .edit_widget a, .soo_multidoc_admin .edit_widget .disabled\n{\n	border: 1px solid #bbb;\n	padding: 0 0.2em;\n	margin:  0 0.2em;\n}\n.soo_multidoc_admin .edit_widget .disabled { color: #fff; border-color: #fff }\n.soo_multidoc_admin .highlight { background: #dfc; }\n.left { color: blue; }\n--></style>\n\nEOF;\n}\n', 'b47b36d338d9664bf2d33d09ddf909d2', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_plugin_pref', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.2.3', 'Plugin preference manager', '<h1>soo_plugin_pref</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>.</p>\n\n<ul>\n	<li><a href=\"https://forum.textpattern.io/viewtopic.php?id=31732\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_plugin_pref\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#usage\">Usage</a></li>\n	<li><a href=\"#authors\">Info for plugin authors</a>\n	<ul>\n		<li><a href=\"#config\">Configuration</a></li>\n		<li><a href=\"#limitations\">Limitations</a></li>\n	</ul></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>This is an admin-side plugin for managing plugin preferences.</p>\n\n<p>For users, it provides a consistent admin interface to set preferences for *soo_plugin_pref*-compatible plugins, and automatically installs or removes those preferences from the database as appropriate. (When you upgrade a plugin your existing preference values are retained.)</p>\n\n<p>For plugin authors, it allows you to add preference settings to your plugins without having to create the user interface or the preference-handling functions.</p>\n\n<p>It uses the plugin prefs/lifecycle features introduced in Txp 4.2.0, so <span class=\"required\">Txp 4.2.0 or greater is required</span>.</p>\n\n<h2 id=\"usage\">Usage</h2>\n\n<p><strong>soo_plugin_pref</strong> only works with plugins that are designed to use it. (See <a href=\"http://forum.textpattern.io/viewtopic.php?id=31732\">support forum thread</a>.) As of version 0.2.1, you can install plugins in any order. A compatible plugin&#8217;s preferences will be installed the first time you <a href=\"https://docs.textpattern.io/administration/plugins-panel\">activate it or click its <strong>Options</strong> link in the plugin list</a> while <strong>soo_plugin_pref</strong> is active. Its preferences will be removed from the database when you delete it while <strong>soo_plugin_pref</strong> is active.</p>\n\n<p>To set a plugin&#8217;s preferences, <a href=\"https://docs.textpattern.io/administration/plugins-panel\">click its <strong>Options</strong> link in the plugin list</a>.</p>\n\n<p>NB: A limitation is that if you upgrade an already-enabled plugin, and the new version of the plugin includes new pref settings, you must disable and re-enable it to pick up the changes.</p>\n\n<h2 id=\"authors\">Info for plugin authors</h2>\n\n<p><strong>If you are not a plugin author, you can safely ignore the rest of this help text.</strong></p>\n\n<h3 id=\"config\">Configuration</h3>\n\n<p>To configure a plugin to work with <strong>soo_plugin_pref</strong>:</p>\n\n<p>In the plugin manifest (i.e., the <code>$plugin</code> array at the top of the file):</p>\n\n<ul>\n	<li>Ensure the plugin will load on the admin side (i.e., if <code>type</code> is 0, change it to 1)</li>\n	<li>Set the plugin flags (the official plugin template at <a href=\"https://github.com/textpattern/textpattern-plugin-template/blob/master/zem_plugin_example.php\">https://github.com/textpattern/textpattern-plugin-template/blob/master/zem_plugin_example.php</a> already does this for you)</li>\n</ul>\n\n<p>Add this to the plugin code section (substituting your plugin&#8217;s name for <code>abc_my_plugin</code>, and whatever name you choose for the callback function):</p>\n\n<pre>@require_plugin(&#39;soo_plugin_pref&#39;);    // optional\nif ( @txpinterface == &#39;admin&#39; )\n{\n    add_privs(&#39;plugin_prefs.abc_my_plugin&#39;,&#39;1,2&#39;);\n    add_privs(&#39;plugin_lifecycle.abc_my_plugin&#39;,&#39;1,2&#39;);\n    register_callback(&#39;abc_my_plugin_prefs&#39;, &#39;plugin_prefs.abc_my_plugin&#39;);\n    register_callback(&#39;abc_my_plugin_prefs&#39;, &#39;plugin_lifecycle.abc_my_plugin&#39;);\n}</pre>\n\n<p>Define your plugin&#8217;s preference defaults. I like to do this with a function, with the option to output the multi-level array required by <code>soo_plugin_pref()</code> or a simple key:value array:</p>\n\n<pre>function abc_my_plugin_defaults( $vals_only = false ) {\n    $defaults = array(\n        &#39;foo&#39; =&gt; array(\n            &#39;val&#39;   =&gt; &#39;foo&#39;,\n            &#39;html&#39;  =&gt; &#39;text_input&#39;,\n            &#39;text&#39;  =&gt; &#39;Helpful description&#39;,\n        ),\n        &#39;bar&#39; =&gt; array(\n            &#39;val&#39;   =&gt; 1,\n            &#39;html&#39;  =&gt; &#39;yesnoradio&#39;,\n            &#39;text&#39;  =&gt; &#39;Equally helpful description&#39;,\n        ),\n    );\n    if ( $vals_only )\n        foreach ( $defaults as $name =&gt; $arr )\n            $defaults[$name] = $arr[&#39;val&#39;];\n    return $defaults;\n}</pre>\n\n<p>For each preference, <code>val</code> is the default value, and <code>html</code> is the type of <span class=\"caps\">HTML</span> input element used to display the preference in the admin interface; these go to the corresponding columns in <code>txp_prefs</code>. <code>text</code> is the label that will appear in the admin interface; it is not stored in the database.</p>\n\n<p>Preference names in the database are in the format &#8220;plugin_name.key&#8221;, where &#8220;plugin_name&#8221; is your plugin&#8217;s name, and &#8220;key&#8221; is the array key from the <code>$defaults</code> array.</p>\n\n<p>Each preference will be assigned a position value corresponding to its position in the defaults array, starting at 0. This determines its relative order in the admin interface.</p>\n\n<p>Other <code>txp_prefs</code> columns are set as follows:<br />\n<ul>\n	<li><code>event</code> is always set to &#8220;plugin_prefs&#8221;</li>\n	<li><code>prefs_id</code> is always set to <code>1</code></li>\n	<li><code>type</code> is always set to <code>2</code> (hidden from main Prefs page)</li><br />\n</ul></p>\n\n<p>Add the prefs callback:</p>\n\n<pre>function abc_my_plugin_prefs( $event, $step ) {\n    if ( function_exists(&#39;soo_plugin_pref&#39;) )\n        soo_plugin_pref($event, $step, abc_my_plugin_defaults());\n    else {\n        // any custom preference handling goes here\n    }\n}</pre>\n\n<p>If nothing else, you should display a message for a <code>plugin_prefs.abc_my_plugin</code> event if <strong>soo_plugin_pref</strong> is not installed. The version below checks the event type, then attempts to cobble together a meaningful message using <code>gTxt()</code> fragments:</p>\n\n<pre>function abc_my_plugin_prefs( $event, $step ) {\n    if ( function_exists(&#39;soo_plugin_pref&#39;) )\n        return soo_plugin_pref($event, $step, abc_my_plugin_defaults());\n    if ( substr($event, 0, 12) == &#39;plugin_prefs&#39; ) {\n        $plugin = substr($event, 13);\n        $message = &#39;&lt;p&gt;&lt;br /&gt;&lt;strong&gt;&#39; . gTxt(&#39;edit&#39;) . &quot; $plugin &quot; .\n            gTxt(&#39;edit_preferences&#39;) . &#39;:&lt;/strong&gt;&lt;br /&gt;&#39; .\n            gTxt(&#39;install_plugin&#39;) . &#39; &lt;a\n            href=&quot;http://ipsedixit.net/txp/92/soo_plugin_pref&quot;&gt;soo_plugin_pref&lt;/a&gt;&lt;/p&gt;&#39;;\n        pagetop(gTxt(&#39;edit_preferences&#39;) . &quot; &amp;#8250; $plugin&quot;, $message);\n    }\n}</pre>\n\n<p>Finally, fetch the plugin preferences for use by the rest of the plugin code. I usually put preferences into a global array:</p>\n\n<pre>global $abc_my_plugin;\n$abc_my_plugin = function_exists(&#39;soo_plugin_pref_vals&#39;) ?\n    array_merge(abc_my_plugin_defaults(true), soo_plugin_pref_vals(&#39;abc_my_plugin&#39;))\n    : abc_my_plugin_defaults(true);</pre>\n\n<p>Note the use of <code>soo_plugin_pref_vals()</code>, which returns your plugin&#8217;s preferences as an associative array.</p>\n\n<p>There are various ways you can code the above requirements, depending on your plugin&#8217;s exact needs. Some working examples:</p>\n\n<ul>\n	<li><a href=\"http://ipsedixit.net/txp/74/soo_required_files\">soo_required_files</a>, a public-side plugin</li>\n	<li><a href=\"http://ipsedixit.net/txp/124/soo_editarea\">soo_editarea</a>, an admin-side plugin</li>\n</ul>\n\n\n<h3 id=\"limitations\">Limitations</h3>\n\n<ul>\n	<li>Currently the only allowed values for <code>html</code> are <code>text_input</code> and <code>yesnoradio</code>.</li>\n	<li><strong>soo_plugin_pref</strong> only handles global preferences. If your plugin has a mix of global and per-user preferences, you will have to code all the handling of the per-user preferences.</li>\n	<li>Preferences are only installed on enabling a plugin. Upgrading an enabled plugin does not trigger <strong>soo_plugin_pref</strong> to install any preferences new to the upgraded version. The user must disable and re-enable it to pick up the new prefs. Plugin authors should include a note about this when releasing a new version with new prefs.</li>\n</ul>\n\n<h2 id=\"history\">Version History</h2>\n\n<h4>0.2.3 (2017-02-23)</h4>\n\n<p>Documentation update</p>\n\n<h3>0.2.2 (9/28/2009)</h3>\n\n<p>Fixed bug in pref position re-indexing</p>\n\n<h3>0.2.1 (9/26/2009)</h3>\n\n<ul>\n	<li>Pre-installing <strong>soo_plugin_pref</strong> is no longer required for automatic preference installation</li>\n	<li>Each preference is now assigned a position value that determines relative order in the admin interface, and that corresponds to its position in the defaults array</li>\n</ul>\n\n<h3>0.2 (9/17/2009)</h3>\n\n<p>This version uses a different identifying scheme for preferences and hence is not compatible with the previous version. The plugin name is no longer stored in the <code>event</code> column, so there is no longer any restriction on plugin name length.</p>\n\n<h3>0.1 (9/5/2009)</h3>\n\n<p>Basic plugin preference management:</p>\n\n<ul>\n	<li>Automatic installation/removal of preferences on plugin install/deletion</li>\n	<li>Simple admin interface for setting preferences</li>\n</ul>', '\n// event handler called by other plugins on plugin_lifecycle and plugin_prefs events\nfunction soo_plugin_pref( $event, $step, $defaults ) {\n    preg_match(\'/^(.+)\\.(.+)/\', $event, $match);\n    list( , $type, $plugin) = $match;\n    $message = $step ? soo_plugin_pref_query($plugin, $step, $defaults): \'\';\n    if ( $type == \'plugin_prefs\' )\n        soo_plugin_pref_ui($plugin, $defaults, $message);\n}\n\n// user interface for preference setting (plugin_prefs events)\nfunction soo_plugin_pref_ui( $plugin, $defaults, $message = \'\' ) {\n    $cols = 2;\n    $align_rm = \' style=\"text-align:right;vertical-align:middle\"\';\n    $prefs = soo_plugin_pref_query($plugin, \'select\');\n\n    // install prefs if necessary\n    if ( $defaults and ! $prefs ) {\n        soo_plugin_pref_query($plugin, \'enabled\', $defaults);\n        $prefs = soo_plugin_pref_query($plugin, \'select\');\n    }\n\n    pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n    echo\n        n. \'<form method=\"post\" name=\"soo_plugin_pref_form\">\' .\n        n. startTable(\'list\') .\n            tr(n. tdcs(hed(\n                gTxt(\'plugin\') .\' \'. gTxt(\'edit_preferences\') . \": $plugin\"\n                , 1),\n            $cols))\n    ;\n\n    foreach ( $prefs as $pref ) {\n        extract($pref);\n        $name = str_replace(\"$plugin.\", \'\', $name);\n        $input = $html == \'yesnoradio\' ?\n            yesnoRadio($name, $val) :\n            fInput(\'text\', $name, $val, \'edit\', \'\', \'\', 20);\n        echo\n            n. tr(\n            n.t. tda($defaults[$name][\'text\'], $align_rm) .\n            n. td($input)\n        );\n    }\n\n    echo\n        n. sInput(\'update\') .\n        n. eInput(\"plugin_prefs.$plugin\") .\n        n. tr(n. tdcs(fInput(\'submit\', \'soo_plugin_pref_update\',\n            gTxt(\'save\'), \'publish\'), $cols)) .\n        tr(n. tdcs(href(\n            gTxt(\'go_to\') .\' \'. gTxt(\'plugins\') .\' \'. gTxt(\'list\')\n            , \'?event=plugin\')\n            ,$cols)) .\n        endTable() . \'</form>\' .\n        n;\n}\n\n// preference CRUD\nfunction soo_plugin_pref_query( $plugin, $action, $defaults = array() ) {\n\n    if ( $action == \'select\' )\n        return safe_rows(\n            \'name, val, html, position\',\n            \'txp_prefs\',\n            \"name like \'$plugin.%\' order by position asc\"\n        );\n\n    elseif ( $action == \'update\' ) {\n        $post = doSlash(stripPost());\n        $allowed = array_keys(soo_plugin_pref_vals($plugin));\n        foreach ( $post as $name => $val )\n            if ( in_array($name, $allowed) )\n                if ( ! set_pref(\"$plugin.$name\", $val) )\n                    $error = true;\n        return empty($error) ? gTxt(\'preferences_saved\') : \'\';\n    }\n\n    elseif ( $action == \'enabled\' ) {\n        $prefs = soo_plugin_pref_vals($plugin);\n        $add = array_diff_key($defaults, $prefs);\n        $remove = array_diff_key($prefs, $defaults);\n        foreach ( $add as $name => $pref )\n            set_pref(\n                $plugin . \'.\' . $name,\n                $pref[\'val\'],\n                \'plugin_prefs\',\n                2,\n                $pref[\'html\']\n            );\n        foreach ( $remove as $name => $val )\n            safe_delete(\'txp_prefs\', \"name = \'$plugin.$name\'\");\n\n        // update position values\n        foreach ( array_keys($defaults) as $i => $name )\n            safe_update(\'txp_prefs\',\n                \"position = $i\",\n                \"name = \'$plugin.$name\'\");\n    }\n\n    elseif ( $action == \'deleted\' )\n        safe_delete(\'txp_prefs\', \"name like \'$plugin.%\'\");\n}\n\n// get a plugin\'s prefs; return as associative name:value array\nfunction soo_plugin_pref_vals( $plugin ) {\n    $rs = soo_plugin_pref_query($plugin, \'select\');\n    foreach ( $rs as $r ) {\n        extract ($r);\n        $name = str_replace(\"$plugin.\", \'\', $name);\n        $out[$name] = $val;\n    }\n    return isset($out) ? $out : array();\n}\n', '\n// event handler called by other plugins on plugin_lifecycle and plugin_prefs events\nfunction soo_plugin_pref( $event, $step, $defaults ) {\n    preg_match(\'/^(.+)\\.(.+)/\', $event, $match);\n    list( , $type, $plugin) = $match;\n    $message = $step ? soo_plugin_pref_query($plugin, $step, $defaults): \'\';\n    if ( $type == \'plugin_prefs\' )\n        soo_plugin_pref_ui($plugin, $defaults, $message);\n}\n\n// user interface for preference setting (plugin_prefs events)\nfunction soo_plugin_pref_ui( $plugin, $defaults, $message = \'\' ) {\n    $cols = 2;\n    $align_rm = \' style=\"text-align:right;vertical-align:middle\"\';\n    $prefs = soo_plugin_pref_query($plugin, \'select\');\n\n    // install prefs if necessary\n    if ( $defaults and ! $prefs ) {\n        soo_plugin_pref_query($plugin, \'enabled\', $defaults);\n        $prefs = soo_plugin_pref_query($plugin, \'select\');\n    }\n\n    pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n    echo\n        n. \'<form method=\"post\" name=\"soo_plugin_pref_form\">\' .\n        n. startTable(\'list\') .\n            tr(n. tdcs(hed(\n                gTxt(\'plugin\') .\' \'. gTxt(\'edit_preferences\') . \": $plugin\"\n                , 1),\n            $cols))\n    ;\n\n    foreach ( $prefs as $pref ) {\n        extract($pref);\n        $name = str_replace(\"$plugin.\", \'\', $name);\n        $input = $html == \'yesnoradio\' ?\n            yesnoRadio($name, $val) :\n            fInput(\'text\', $name, $val, \'edit\', \'\', \'\', 20);\n        echo\n            n. tr(\n            n.t. tda($defaults[$name][\'text\'], $align_rm) .\n            n. td($input)\n        );\n    }\n\n    echo\n        n. sInput(\'update\') .\n        n. eInput(\"plugin_prefs.$plugin\") .\n        n. tr(n. tdcs(fInput(\'submit\', \'soo_plugin_pref_update\',\n            gTxt(\'save\'), \'publish\'), $cols)) .\n        tr(n. tdcs(href(\n            gTxt(\'go_to\') .\' \'. gTxt(\'plugins\') .\' \'. gTxt(\'list\')\n            , \'?event=plugin\')\n            ,$cols)) .\n        endTable() . \'</form>\' .\n        n;\n}\n\n// preference CRUD\nfunction soo_plugin_pref_query( $plugin, $action, $defaults = array() ) {\n\n    if ( $action == \'select\' )\n        return safe_rows(\n            \'name, val, html, position\',\n            \'txp_prefs\',\n            \"name like \'$plugin.%\' order by position asc\"\n        );\n\n    elseif ( $action == \'update\' ) {\n        $post = doSlash(stripPost());\n        $allowed = array_keys(soo_plugin_pref_vals($plugin));\n        foreach ( $post as $name => $val )\n            if ( in_array($name, $allowed) )\n                if ( ! set_pref(\"$plugin.$name\", $val) )\n                    $error = true;\n        return empty($error) ? gTxt(\'preferences_saved\') : \'\';\n    }\n\n    elseif ( $action == \'enabled\' ) {\n        $prefs = soo_plugin_pref_vals($plugin);\n        $add = array_diff_key($defaults, $prefs);\n        $remove = array_diff_key($prefs, $defaults);\n        foreach ( $add as $name => $pref )\n            set_pref(\n                $plugin . \'.\' . $name,\n                $pref[\'val\'],\n                \'plugin_prefs\',\n                2,\n                $pref[\'html\']\n            );\n        foreach ( $remove as $name => $val )\n            safe_delete(\'txp_prefs\', \"name = \'$plugin.$name\'\");\n\n        // update position values\n        foreach ( array_keys($defaults) as $i => $name )\n            safe_update(\'txp_prefs\',\n                \"position = $i\",\n                \"name = \'$plugin.$name\'\");\n    }\n\n    elseif ( $action == \'deleted\' )\n        safe_delete(\'txp_prefs\', \"name like \'$plugin.%\'\");\n}\n\n// get a plugin\'s prefs; return as associative name:value array\nfunction soo_plugin_pref_vals( $plugin ) {\n    $rs = soo_plugin_pref_query($plugin, \'select\');\n    foreach ( $rs as $r ) {\n        extract ($r);\n        $name = str_replace(\"$plugin.\", \'\', $name);\n        $out[$name] = $val;\n    }\n    return isset($out) ? $out : array();\n}\n', '28a4e8ea1b9509b8847f93cb37d8f96e', '', '', 2, 1, 0),
('soo_article_filter', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.3.3', 'Create filtered list of articles before sending to txp:article or txp:article_custom', '<h1>soo_article_filter</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com/\">Textpattern</a>.</p>\n\n<p>Code preview and compiled (ready-to-install) downloads available <a href=\"http://ipsedixit.net/txp/80/soo_article_filter\">on the author&#8217;s website</a> (latest release only) and <a href=\"https://github.com/jsoo/soo_article_filter\">on GitHub</a> (all release versions).</p>\n\n <div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#requirements\">Requirements</a></li>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#usage\">Usage</a></li>\n	<li><a href=\"#attributes\">Attributes</a></li>\n	<li><a href=\"#examples\">Examples</a>\n	<ul>\n		<li><a href=\"#expired\">Expired articles</a></li>\n		<li><a href=\"#custom_set\">Custom field not empty</a></li>\n		<li><a href=\"#custom_not_set\">Custom field empty</a></li>\n		<li><a href=\"#custom_includes\">Custom field includes &#8230;</a></li>\n		<li><a href=\"#custom_regexp\">Custom field matches regular expression</a></li>\n		<li><a href=\"#image\">Articles with an assigned image</a></li>\n		<li><a href=\"#latest_image\">Latest article with an assigned image</a></li>\n		<li><a href=\"#range\">Select on a numeric range</a></li>\n		<li><a href=\"#index\">Alphabetical index</a></li>\n		<li><a href=\"#update\">Change article status</a></li>\n	</ul></li>\n	<li><a href=\"#notes\">Technical notes</a>\n	<ul>\n		<li><a href=\"#safety\">Safety</a></li>\n		<li><a href=\"#troubleshooting\">Troubleshooting</a></li>\n		<li><a href=\"#performance\">Performance considerations</a></li>\n		<li><a href=\"#multidoc\">Multidoc compatibility</a></li>\n	</ul></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n </div>\n\n<h2 id=\"requirements\">Requirements</h2>\n\n<p>This plugin relies on the trick of creating a temporary database table in memory. So the MySQL user you have assigned to Textpattern (indicated in config.php) must have <code>CREATE</code> privileges. If table creation fails, the plugin will return blank (as of version 0.2.5).</p>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>Contains one tag, <code>soo_article_filter</code>. It allows you to pre-select articles to limit the scope of an <code>article</code> or <code>article_custom</code> tag, using selection criteria not offered by those tags. In short, it&#8217;s like adding selection attributes to one of those tags.</p>\n\n<p>As of version 0.2.4 there is also the option to add index-style titles to a custom field. E.g., &#8220;The First Article&#8221; becomes &#8220;First Article, The&#8221;. You can then access that custom field with the usual Txp custom field tags, allowing you to create alphabetical indexes.</p>\n\n<p>As of version 0.2.6 you can do an additional <code>UPDATE</code> query on the temporary table, by using the <code>update_set</code> and <code>update_where</code> attributes. One use for this is to change article status from &#8220;Draft&#8221; to &#8220;Live&#8221; or &#8220;Sticky&#8221;, allowing you to display draft articles publicly.</p>\n\n<p>Thanks to <a href=\"https://github.com/netcarver\">net-carver</a> for clueing me in to MySQL temporary tables.</p>\n\n<h2 id=\"usage\">Usage</h2>\n\n<p><code>soo_article_filter</code> is a container tag, intended as a wrapper for <code>article</code> or <code>article_custom</code>.</p>\n\n<pre>&lt;txp:soo_article_filter&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<p><strong>Note:</strong> the filter will not be applied to search results.</p>\n\n<h2 id=\"attributes\">Attributes</h2>\n\n<p>None <span class=\"required\">required</span>, but without attributes the tag doesn&#8217;t do anything useful.</p>\n\n<ul>\n	<li><code>expires</code> <em>(&#8220;past&#8221;, &#8220;future&#8221;, &#8220;any&#8221;, or 0)</em><br />\nIf set to &#8220;past&#8221;, &#8220;future&#8221;, or &#8220;any&#8221;, only include articles with an <code>Expires</code> value. If  set to &#8220;0&#8221;, only include articles without an <code>Expires</code> value. (<span class=\"default\">Default</span> is <code>null</code>; no filter on <code>Expires</code>.)</li>\n	<li><code>customfieldname</code> <em>(empty or regexp pattern)</em> replace &#8220;customfieldname&#8221; with any custom field name as set in site preferences. If the attribute value is empty, only articles with an empty value for this custom field will be included. Otherwise the attribute value will be treated as a <a href=\"http://dev.mysql.com/doc/refman/5.1/en/regexp.html\">MySQL regular expression pattern</a>.</li>\n	<li><code>article_image</code> <em>(boolean)</em> If <code>1</code>, only show articles with an article image. If <code>0</code>, only show articles without an article image. If not set (the <span class=\"default\">default</span>), article image has no effect.</li>\n	<li><code>multidoc</code> <em>(boolean)</em> <span class=\"default\">default</span> false<br />\nFor use with the <a href=\"http://ipsedixit.net/txp/24/multidoc\">soo_multidoc</a> plugin. See <a href=\"#multidoc\">note</a> below.</li>\n	<li><code>index_ignore</code> <em>(list)</em> Comma-separated list of leading articles to transpose, when used in combination with <code>index_field</code> (<span class=\"default\">Default</span> &#8220;A,An,The&#8221;)</li>\n	<li><code>index_field</code> <em>(custom field name)</em> Set this to the name of an existing custom field to hold index-style titles (e.g. &#8220;The Title&#8221; becomes &#8220;Title, The&#8221;). <span class=\"default\">Default</span> empty.</li>\n	<li><code>update_set</code> <em>(<span class=\"caps\">SQL</span> clause)</em> <span class=\"default\">default</span> empty. Set this (and, optionally, <code>update_where</code>) to run an <code>UPDATE</code> query on the temporary table.</li>\n	<li><code>update_where</code> <em>(<span class=\"caps\">SQL</span> clause)</em> <span class=\"default\">default</span> &#8220;1=1&#8221;. Use this in conjunction with <code>update_where</code> to run an <code>UPDATE</code> query on the temporary table.</li>\n	<li><code>where</code> <em>(<span class=\"caps\">SQL</span> clause)</em> <span class=\"default\">default</span> empty. Any text here will be added to the end of the array of <code>WHERE</code> expressions used for article selection.</li>\n	<li><code>limit</code> <em>(integer)</em> Number of articles to select. <span class=\"default\">default</span> unset, no limit.</li>\n	<li><code>offset</code> <em>(integer)</em> Number of articles to skip. <span class=\"default\">default</span> unset, no offset.</li>\n	<li><code>sort</code> <em>(Column [direction])</em> Sort column (and optional sort direction), e.g. <code>Posted asc</code>. <span class=\"default\">default</span> unset.</li>\n</ul>\n\n<h2 id=\"complex\">Complex selection</h2>\n\n<p>The selection attributes (<code>expires</code>, <code>customfieldname</code>, <code>article_image</code>, <code>multidoc</code>, and <code>where</code>) may be used in combination. The result is a conjunctive (<code>AND</code>) search, i.e., each additional attribute makes the filter more restrictive.</p>\n\n<p>The <code>where</code> attribute allows you to enter MySQL expressions and functions for even greater power.</p>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3 id=\"expired\">Only show expired articles</h3>\n\n<p>You must set &#8220;Publish expired articles&#8221; to &#8220;Yes&#8221; in advanced site preferences.</p>\n\n<pre>&lt;txp:soo_article_filter expires=&quot;past&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"custom_set\">Only show articles with &#8220;my-custom-field&#8221; set</h3>\n\n<p>Note the value, <code>&quot;.+&quot;</code>. This will match any character. Note also that you can accomplish the same thing without a plugin, using the technique discussed in <a href=\"http://forum.textpattern.com/viewtopic.php?pid=210453#p210453\">this Txp forum thread</a>.</p>\n\n<pre>&lt;txp:soo_article_filter my-custom-field=&quot;.+&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"custom_not_set\">Only show articles with &#8220;my-custom-field&#8221; not set</h3>\n\n<pre>&lt;txp:soo_article_filter my-custom-field=&quot;&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"custom_includes\">Only show articles where &#8220;my-custom-field&#8221; includes &#8220;blue&#8221;</h3>\n\n<p>Note: this is not case sensitive. Would match &#8220;Blues&#8221;, &#8220;true blue&#8221;, etc. <code>article</code> and <code>article_custom</code> already work this way if you add the wildcard character <code>%</code> (e.g., <code>my-custom-field=&quot;%blue%&quot;</code>), so you don&#8217;t need this plugin just to do this.</p>\n\n<pre>&lt;txp:soo_article_filter my-custom-field=&quot;blue&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"custom_regexp\">Only show articles where &#8220;my-custom-field&#8221; contains only digits</h3>\n\n<p>Note the value, <code>&quot;^[[:digit:]]+$&quot;</code>. This is a <a href=\"http://dev.mysql.com/doc/refman/5.1/en/regexp.html\">MySQL regexp pattern</a>, <strong>not</strong> a <a href=\"http://us.php.net/manual/en/reference.pcre.pattern.syntax.php\"><span class=\"caps\">PCRE</span> pattern</a>.</p>\n\n<pre>&lt;txp:soo_article_filter my-custom-field=&quot;^[[:digit:]]+$&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"image\">Only show articles that have an article image</h3>\n\n<pre>&lt;txp:soo_article_filter article_image=&quot;1&quot;&gt;\n&lt;txp:article&gt;\n&lt;txp:permlink&gt;&lt;txp:article_image thumbnail=&quot;1&quot; /&gt;&lt;/txp:permlink&gt;\n&lt;/txp:article&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"latest_image\">Only show the most recent article with an article image</h3>\n\n<pre>&lt;txp:soo_article_filter article_image=&quot;1&quot; limit=&quot;1&quot; sort=&quot;Posted desc&quot;&gt;\n&lt;txp:article&gt;\n&lt;txp:permlink&gt;&lt;txp:article_image thumbnail=&quot;1&quot; /&gt;&lt;/txp:permlink&gt;\n&lt;/txp:article&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<p>(Note: while you could declare the <code>limit</code> in the <code>article</code> tag, this method uses less memory.)</p>\n\n<h3 id=\"range\">Use <code>where</code> to select on a numeric range</h3>\n\n<pre>&lt;txp:soo_article_filter where=&quot;image BETWEEN 4 AND 27&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h3 id=\"index\">An alphabetical index using <code>index_ignore</code> and <code>index_field</code></h3>\n\n<pre>&lt;txp:soo_article_filter index_field=&quot;index_title&quot;&gt;\n&lt;txp:article sort=&quot;custom_3 asc&quot; wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n&lt;txp:permlink&gt;&lt;txp:custom_field name=&quot;index_title&quot; /&gt;&lt;/txp:permlink&gt;\n&lt;/txp:article&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<p>When <code>index_field</code> is set to the name of an existing custom field, this field will receive an index-style version of the article title, e.g. &#8220;The Title&#8221; becomes &#8220;Title, The&#8221;. You can then sort on and/or display the index-style title with standard Textpattern custom field tags and attributes, as shown.</p>\n\n<p>Leading words getting special treatment are those listed in <code>index_ignore</code>. This defaults to English articles, i.e. &#8220;A,An,The&#8221;.</p>\n\n<p>The custom field has to be one you have actually created in site prefs. In the example it is called &#8220;index_title&#8221;, and it is custom field #3. When saving articles leave this field blank, otherwise <code>soo_article_filter</code> will leave it unchanged.</p>\n\n<p>The index-style title is created only within the <code>soo_article_filter</code> container &mdash; corresponding custom field in the database remains blank.</p>\n\n<h3 id=\"update\">Add an <code>UPDATE</code> query to change article status</h3>\n\n<p>The <code>update_set</code> attribute allows you to run an <code>UPDATE</code> query on the temporary table. For example, to temporarily change the status of all &#8220;draft&#8221; articles in the &#8220;News&#8221; section to &#8220;live&#8221; (Textpattern&#8217;s status code for &#8220;live&#8221; is 4 and for &#8220;draft&#8221; is 1):</p>\n\n<pre>&lt;txp:soo_article_filter update_set=&quot;Status=4&quot; update_where=&quot;Status=1 AND Section=&#39;News&#39;&quot;&gt;\n&lt;txp:article /&gt;\n&lt;/txp:soo_article_filter&gt;</pre>\n\n<h2 id=\"notes\">Technical notes</h2>\n\n<h3 id=\"safety\">Safety</h3>\n\n<p>This plugin attempts to run <code>CREATE</code>, <code>DROP</code>, and, optionally, <code>UPDATE</code> queries. There is a safety check: if the initial <code>CREATE TEMPORARY TABLE</code> query fails, the plugin exits immediately, without parsing the tag contents. However, the plugin has not been tested extensively, and you should certainly keep regular database backups if you are doing anything especially interesting with this plugin. Of course, you should keep regular database backups in any event.</p>\n\n<p>One thing you should absolutely <span class=\"warning\"><span class=\"caps\">NOT</span></span> do is assume that it is safe to do anything you like within the tag contents. The main issue is that if the page context is search results (i.e., the <code>q</code> query parameter is set), the tag will simply parse its contents and return them as is. If you stuck a raw query into the tag contents on the assumption the query would only run when the temporary textpattern table exists, you&#8217;d regret it. Maybe not today, maybe not tomorrow, but soon, and for the rest of your life.</p>\n\n<h3 id=\"troubleshooting\">Troubleshooting</h3>\n\n<p>You might get an error like this: <code>Textpattern Warning: Not unique table/alias: &#39;textpattern&#39;</code>. It seems that some configurations allow the shortcut of creating the temporary table and selecting from the actual table of the same name in the same statement, but some don&#8217;t. Performance-wise it is certainly better to use a single statement, but if this doesn&#8217;t work for you, <del>use the alternate version of the plugin, included in the download</del> please <a href=\"http://ipsedixit.net/info/2/contact\">contact me</a>.</p>\n\n<p>If you use <a href=\"http://ipsedixit.net/txp/24/multidoc\">Multidoc</a> and see an error message including <code>Table &#39;textpattern&#39; already exists</code>, see the <a href=\"#multidoc\">note on Multidoc compatibility</a>, below.</p>\n\n<h3 id=\"performance\">Performance considerations</h3>\n\n<p>Most plugins that give you a souped-up equivalent of <code>article</code> or <code>article_custom</code> have to duplicate and modify <code>doArticles()</code> plus a couple of other core Txp functions. Less than ideal, at least in terms of ease of plugin authoring, because <code>doArticles()</code> is quite a lot of code to duplicate and edit in a plugin.</p>\n\n<p>This plugin takes a very different approach&#8212;it creates a temporary table holding a filtered set of articles, thus allowing you to use <code>article</code> and/or <code>article_custom</code> normally. Code-wise this is very simple; performance-wise it might be inferior (I don&#8217;t know). In my limited and informal testing the extra time required for dealing with the temporary table is a non-issue. This could change with a very large <code>textpattern</code> (articles) table. But as long as you set <code>soo_article_filter</code>&#8217;s attributes so as to produce a relatively small number of articles, there shouldn&#8217;t be a problem in most cases. (Version 0.3.1 adds <code>limit</code>, <code>offset</code>, and <code>sort</code> attributes to make it easier to target the exact articles you want.)</p>\n\n<p>Might be another story on a highly-optimized, large, high-traffic website where maximum performance is a major concern.</p>\n\n<h3 id=\"multidoc\">Multidoc compatibility</h3>\n\n<p>The <a href=\"http://ipsedixit.net/txp/24/multidoc\">soo_multidoc</a> plugin also uses the temporary table trick to filter articles, which can only be done once per page load. To achieve compatibility between <strong>soo_article_filter</strong> and <strong>soo_multidoc</strong>, follow these steps:</p>\n\n<ul>\n	<li>In Multidoc prefs (click <strong>soo_multidoc</strong>&#8217;s &#8220;Options&#8221; link in the plugin list) set the &#8220;Show all&#8221; preference to &#8220;yes&#8221;.</li>\n	<li>As needed, use <code>soo_article_filter</code>, with <code>multidoc=&quot;1&quot;</code>, to filter Multidoc interior pages.</li>\n</ul>\n\n<p>Note that, unlike Multidoc&#8217;s built-in filter, <code>soo_article_filter</code> does not distinguish between list and individual article context, so if your Multidoc setup uses the same <code>article</code> tag for lists and individual articles you will have change this. (This is deliberate; it allows you to use <code>soo_article_filter</code> for an <code>article_custom</code> list on an individual article page.)</p>\n\n<h2 id=\"history\">Version history</h2>\n\n<h3>0.3.3 (2017/02/15)</h3>\n\n<p>Textpattern 4.6 compatibility update</p>\n\n<h3>0.3.2 (Jan 3, 2011)</h3>\n\n<p>Multidoc compatibility update</p>\n\n<h3>0.3.1 (Dec 9, 2010)</h3>\n\n<p>Added <code>limit</code>, <code>offset</code>, and <code>sort</code> attributes.</p>\n\n<h3>0.3.0 (Jul 11, 2010)</h3>\n\n<p>New attribute: <code>where</code>, allows you to add a raw <code>WHERE</code> expression to the selection criteria. (Thanks to Victor for the idea.)</p>\n\n<h3>0.2.7 (Jun 6, 2010)</h3>\n\n<p>Fixed bug in <code>index_ignore</code> attribute. (Thanks to th3lonius for spotting it.)</p>\n\n<h3>0.2.6 (Mar 8, 2010)</h3>\n\n<p>New attributes, <code>update_set</code> and <code>update_where</code>, allowing an additional <code>UPDATE</code> query on the temporary table.</p>\n\n<h3>0.2.5 (Feb 19, 2010)</h3>\n\n<p>Checks that temporary table creation was successful, else returns nothing.</p>\n\n<h3>0.2.4 (Feb 19, 2010)</h3>\n\n<p>Create properly-alphabetized indexes with the new <code>index_ignore</code> and <code>index_field</code> attributes.</p>\n\n<h3>0.2.3 (Jan 20, 2010)</h3>\n\n<p>Fixed custom field bug</p>\n\n<h3>0.2.2 (Sept 28, 2009)</h3>\n\n<p>New <code>article_image</code> attribute</p>\n\n<h3>0.2.1 (July 7, 2009)</h3>\n\n<p><a href=\"http://ipsedixit.net/txp/24/multidoc\">soo_multidoc</a> compatibility.</p>\n\n<h3>0.2 (July 4, 2009)</h3>\n\n<p>Fixed behavior of <code>expires</code> attribute.</p>\n\n<h3>0.1 (July 3, 2009)</h3>\n\n<p>Initial release. Allows filtering on the <code>Expires</code> field, and regexp pattern matching on any custom field.</p>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_article_filter\');\n}\n\nfunction soo_article_filter( $atts, $thing )\n{\n    global $pretext;\n    if ($pretext[\'q\']) return parse($thing);\n\n    $customFields = getCustomFields();                                              // field names\n    $customAtts = array_null(array_flip($customFields));\n    $standardAtts = array(\n        \'expires\'               => null,        // accept \'any\', \'past\', \'future\', or 0\n        \'article_image\' => null,        // boolean: 0 = no image, 1 = has image\n        \'multidoc\'              => null,        // for soo_multidoc compatibility\n        \'index_ignore\'  => \'a,an,the\',  // leading words to move in index titles\n        \'index_field\'   => null,        // custom field name for index-style title\n        \'update_set\'    => null,        // SET clause for custom update query\n        \'update_where\'  => null,        // WHERE clause for custom update query\n        \'where\'                 => null,        // raw WHERE expression for filter\n        \'limit\'                 => null,        // LIMIT value\n        \'offset\'                => null,        // OFFSET value\n        \'sort\'                  => null,        // ORDER BY expression\n    );\n    extract(lAtts($standardAtts + $customAtts, $atts));\n    if (! is_null($expires)) {\n        switch ( $expires ) {\n            case \'any\':\n                $where_exp[] = \'Expires > 0\';\n                break;\n            case \'past\':\n                $where_exp[] = \'Expires <= now() and Expires > 0\';\n                break;\n            case \'future\':\n                $where_exp[] = \'Expires > now()\';\n                break;\n            case 0:\n                $where_exp[] = \'Expires = 0\';\n        }\n    }\n\n    if (! is_null($article_image))\n        $where_exp[] = \'Image \'.($article_image ? \'!\' : \'\').\"= \'\'\";\n\n    if ($customFields)\n        foreach( $customFields as $i => $field )\n                    // to prevent conflicts between named atts and custom fields\n            if ( ! array_key_exists($field, $standardAtts) and isset($atts[$field]) ) {\n                $value = $atts[$field];\n                switch ( $value ) {\n                    case \'\':\n                        $where_exp[] = \"custom_$i = \'\'\";\n                        break;\n                    default:\n                        $where_exp[] = \"custom_$i regexp \'$value\'\";\n                }\n            }\n\n    if ( $multidoc and _soo_multidoc_ids_init() ) {\n            global $soo_multidoc;\n            $where_exp[] = \"ID not in (select id from \" . safe_pfx(\'soo_multidoc\') . \" where id != root)\";\n    }\n\n    if ($where) $where_exp[] = $where;\n\n    $select = \'*\';\n    $table = safe_pfx(\'textpattern\');\n    $where_exp = isset($where_exp) ? \' where \'.implode(\' and \', $where_exp) : \'\';\n    $sort = $sort ? \' order by \'.doSlash($sort) : \'\';\n    $limit = $limit ? \' limit \'.intval($offset).\',\'.intval($limit) : \'\';\n\n    if ($index_field) {\n        $i = array_search($index_field, $customFields);\n        if ( $i ) {\n            $regexp = \"\'^(\" . implode(\'|\', do_list($index_ignore)) . \")$\'\";\n            $select .= \", trim(Title) as index_title, substring_index(trim(Title),\' \',1) as first_word, substring(trim(Title), locate(\' \',trim(Title))+1) as remaining_words\";\n            $update[] = array(\n                \'set\' => \"custom_$i = concat(remaining_words, \', \', first_word)\",\n                \'where\' => \"first_word regexp $regexp and custom_$i = \'\'\",\n            );\n            $update[] = array(\n                \'set\' => \"custom_$i = trim(Title)\",\n                \'where\' => \"custom_$i = \'\'\",\n            );\n\n        }\n    }\n\n    if (! safe_query(\"create temporary table $table select $select from $table\".$where_exp.$sort.$limit))\n        return;\n\n    if ($update_set)\n        $update[] = array(\n            \'set\' => $update_set,\n            \'where\' => $update_where ? $update_where : \'1=1\',\n        );\n\n    if (! empty($update))\n        foreach ($update as $u)\n            safe_update(\'textpattern\', $u[\'set\'], $u[\'where\']);\n\n    $out = parse($thing);\n    safe_query(\"drop temporary table $table\");\n    return $out;\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_article_filter\');\n}\n\nfunction soo_article_filter( $atts, $thing )\n{\n    global $pretext;\n    if ($pretext[\'q\']) return parse($thing);\n\n    $customFields = getCustomFields();                                              // field names\n    $customAtts = array_null(array_flip($customFields));\n    $standardAtts = array(\n        \'expires\'               => null,        // accept \'any\', \'past\', \'future\', or 0\n        \'article_image\' => null,        // boolean: 0 = no image, 1 = has image\n        \'multidoc\'              => null,        // for soo_multidoc compatibility\n        \'index_ignore\'  => \'a,an,the\',  // leading words to move in index titles\n        \'index_field\'   => null,        // custom field name for index-style title\n        \'update_set\'    => null,        // SET clause for custom update query\n        \'update_where\'  => null,        // WHERE clause for custom update query\n        \'where\'                 => null,        // raw WHERE expression for filter\n        \'limit\'                 => null,        // LIMIT value\n        \'offset\'                => null,        // OFFSET value\n        \'sort\'                  => null,        // ORDER BY expression\n    );\n    extract(lAtts($standardAtts + $customAtts, $atts));\n    if (! is_null($expires)) {\n        switch ( $expires ) {\n            case \'any\':\n                $where_exp[] = \'Expires > 0\';\n                break;\n            case \'past\':\n                $where_exp[] = \'Expires <= now() and Expires > 0\';\n                break;\n            case \'future\':\n                $where_exp[] = \'Expires > now()\';\n                break;\n            case 0:\n                $where_exp[] = \'Expires = 0\';\n        }\n    }\n\n    if (! is_null($article_image))\n        $where_exp[] = \'Image \'.($article_image ? \'!\' : \'\').\"= \'\'\";\n\n    if ($customFields)\n        foreach( $customFields as $i => $field )\n                    // to prevent conflicts between named atts and custom fields\n            if ( ! array_key_exists($field, $standardAtts) and isset($atts[$field]) ) {\n                $value = $atts[$field];\n                switch ( $value ) {\n                    case \'\':\n                        $where_exp[] = \"custom_$i = \'\'\";\n                        break;\n                    default:\n                        $where_exp[] = \"custom_$i regexp \'$value\'\";\n                }\n            }\n\n    if ( $multidoc and _soo_multidoc_ids_init() ) {\n            global $soo_multidoc;\n            $where_exp[] = \"ID not in (select id from \" . safe_pfx(\'soo_multidoc\') . \" where id != root)\";\n    }\n\n    if ($where) $where_exp[] = $where;\n\n    $select = \'*\';\n    $table = safe_pfx(\'textpattern\');\n    $where_exp = isset($where_exp) ? \' where \'.implode(\' and \', $where_exp) : \'\';\n    $sort = $sort ? \' order by \'.doSlash($sort) : \'\';\n    $limit = $limit ? \' limit \'.intval($offset).\',\'.intval($limit) : \'\';\n\n    if ($index_field) {\n        $i = array_search($index_field, $customFields);\n        if ( $i ) {\n            $regexp = \"\'^(\" . implode(\'|\', do_list($index_ignore)) . \")$\'\";\n            $select .= \", trim(Title) as index_title, substring_index(trim(Title),\' \',1) as first_word, substring(trim(Title), locate(\' \',trim(Title))+1) as remaining_words\";\n            $update[] = array(\n                \'set\' => \"custom_$i = concat(remaining_words, \', \', first_word)\",\n                \'where\' => \"first_word regexp $regexp and custom_$i = \'\'\",\n            );\n            $update[] = array(\n                \'set\' => \"custom_$i = trim(Title)\",\n                \'where\' => \"custom_$i = \'\'\",\n            );\n\n        }\n    }\n\n    if (! safe_query(\"create temporary table $table select $select from $table\".$where_exp.$sort.$limit))\n        return;\n\n    if ($update_set)\n        $update[] = array(\n            \'set\' => $update_set,\n            \'where\' => $update_where ? $update_where : \'1=1\',\n        );\n\n    if (! empty($update))\n        foreach ($update as $u)\n            safe_update(\'textpattern\', $u[\'set\'], $u[\'where\']);\n\n    $out = parse($thing);\n    safe_query(\"drop temporary table $table\");\n    return $out;\n}\n', '5a456cbead203209cafcc12dc8b7e353', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_editarea', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.1.5', 'Integrate the EditArea admin-side code editor', '<h1>soo_editarea</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>.</p>\n\n<ul>\n	<li><a href=\"http://forum.textpattern.io/viewtopic.php?id=35143\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/124/soo_editarea\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_editarea\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#installation\">Installation</a></li>\n	<li><a href=\"#configuration\">Configuration options</a></li>\n	<li><a href=\"#txp_highlighting\">Txp tag highlighting</a></li>\n	<li><a href=\"#issues\">Known issues</a></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p><a href=\"http://www.cdolivet.com/editarea/\">EditArea</a> is a JavaScript-based code editor for browser textareas. Features include:</p>\n\n<ul>\n	<li>Code highlighting</li>\n	<li>Browser tab-key override for proper tabbing in the textarea</li>\n	<li>Full-screen mode</li>\n	<li>Find &amp; replace w/ regex support</li>\n	<li>Multiple undo/redo</li>\n</ul>\n\n<p><strong>soo_editarea</strong> provides easy integration of EditArea into Textpattern. (Well, pretty easy: there are a few steps involved.)</p>\n\n<p><em>Suggested by the (apparently defunct) <a href=\"http://forum.textpattern.com/viewtopic.php?id=33915\">atb_editarea</a> plugin, and <a href=\"http://forum.textpattern.com/viewtopic.php?id=21370\">discussion</a> on the Txp forum.</em></p>\n\n<h3>Features:</h3>\n\n<p>You can set plugin preferences for:</p>\n\n<ul>\n	<li>Syntax language for Page &amp; Form editing, allowing custom syntax file</li>\n	<li>Source path, making it easier to share one EditArea installation across multiple sites</li>\n	<li>Various EditArea options (tooltip language, font size &amp; family, &amp;c.)</li>\n</ul>\n\n<h2 id=\"installation\">Installation</h2>\n\n<p><a href=\"https://docs.textpattern.io/administration/plugins-panel\">Install and activate the plugin</a> in the usual way.</p>\n\n<p><a href=\"https://github.com/cdolivet/EditArea\">Clone or download EditArea</a> and place the <code>edit_area</code> directory in a server-accessible location of your choice. (The default is <code>/textpattern/edit_area</code>, but you can change this in the plugin&#8217;s Options settings. Given the changes in more recent versions of Textpatterns I should probably update this.)</p>\n\n<p>Optionally, download the <a href=\"http://ipsedixit.net/file_download/16/txp.js\">txp.js syntax file</a> (or <a href=\"http://www.cdolivet.com/editarea/editarea/docs/customization_syntax.html\">create your own</a>) and place it in <code>edit_area/reg_syntax</code>.</p>\n\n<h2 id=\"configuration\">Configuration options</h2>\n\n<p>The first two steps in Installation, above, are all you need to get EditArea working with standard <span class=\"caps\">HTML</span> syntax highlighting for Pages and Forms, and <span class=\"caps\">CSS</span> highlighting for Styles.</p>\n\n<p>To activate the Txp syntax file (from step 3, above), or to use a different location for the EditArea files, install and activate the <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a> plugin (Txp 4.2.0 or greater <span class=\"required\">required</span>). Then, in the <a href=\"https://docs.textpattern.io/administration/plugins-panel\">main plugin panel</a>, click the Options link for <strong>soo_editarea</strong> (look in the <strong>Manage</strong> column at right).</p>\n\n<p>To use the Txp syntax file, change the <strong>Page Template and Form syntax</strong> setting to &#8220;txp&#8221;.</p>\n\n<p>The <strong>EditArea directory</strong> setting is the <span class=\"caps\">URL</span> (relative to <code>/textpattern/index.php</code>) of the EditArea files. (Hint: for sharing one set of EditArea files across multiple sites, put the files in any server-accessible location you choose, then add a symbolic link to each site&#8217;s <code>/textpattern</code> directory.)</p>\n\n<h3>More options:</h3>\n\n<ul>\n	<li><strong>Language:</strong> for EditArea tooltips. Use the two-letter code corresponding to the file in <code>edit_area/langs</code>.</li>\n	<li><strong>Font size:</strong> default font size for the editor</li>\n	<li><strong>Font family:</strong> comma-separated list of font names (<span class=\"default\">default</span> &#8220;monospace&#8221;).</li>\n	<li><strong>Editor height:</strong> default height of the EditArea textarea</li>\n	<li><strong>Editor width:</strong> as above, for width</li>\n	<li><strong>Convert tab to spaces:</strong> convert tabs to this many spaces (leave at 0 for standard tabs)</li>\n	<li><strong>EditArea Plugins:</strong> comma-separated list of EditArea plugins (e.g. <a href=\"http://code.google.com/p/zen-coding/\">zencoding</a>). It&#8217;s up to you to install any such plugins appropriately.</li>\n</ul>\n\n<h2 id=\"txp_highlighting\">Txp tag highlighting</h2>\n\n<p>By default the plugin uses EditArea&#8217;s <span class=\"caps\">HTML</span> highlighting for Page Template and Form editing, giving Txp tags the same highlight colour as <span class=\"caps\">HTML</span> tags. To have Txp tags appear in a different colour, follow the installation/configuration instructions above for adding the txp.js file. (If you later upgrade the EditArea files you will have to remember to preserve this file.)</p>\n\n<p>The txp.js file linked above highlights Txp tags in a lovely orange colour. To change it (or any of the other colours), edit txp.js to suit (look toward the bottom of the file). If you&#8217;d prefer a soothing green for your Txp tags, uncomment the line near the bottom labeled &#8220;green&#8221;, and comment the line above it labeled &#8220;orange&#8221; (i.e., remove the two slashes at the start of the &#8220;green&#8221; line, and add two slashes to the start of the &#8220;orange&#8221; line).</p>\n\n<h2 id=\"issues\">Known issues</h2>\n\n<p>The EditArea project itself is no longer supported.</p>\n\n<ul>\n	<li><a href=\"http://www.cdolivet.com/editarea/\">Author page</a></li>\n	<li><a href=\"https://sourceforge.net/projects/editarea/\">SourceForge</a></li>\n	<li><a href=\"https://github.com/cdolivet/EditArea\">GitHub</a></li>\n</ul>\n\n<p>There are some browser compatibility issues regarding appearance. In order to get a usable appearance in WebKit-based browsers (e.g., Safari), I have had to modify <code>edit_area_full.js</code>. This modified file is available on the <strong>soo_editarea</strong> <a href=\"http://ipsedixit.net/txp/124/soo_editarea\">download page</a> and <a href=\"https://github.com/jsoo/soo_editarea\">GitHub repo</a>. There are undoubtedly issues with other browsers as well, and it may be a challenge to come up with a fix for a site with administrators using a variety of operating systems and browsers.</p>\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>0.1.5 (2012/01/24)</h3>\n\n<ul>\n	<li>Added pref for EditArea plugins (as requested by mrdale)</li>\n</ul>\n\n<h3>0.1.4 (2011/04/27)</h3>\n\n<ul>\n	<li>Added default height/width prefs (as requested by mrdale)</li>\n</ul>\n\n<h3>0.1.3 (2010/12/30)</h3>\n\n<ul>\n	<li>Fixed bug with Internet Explorer (<code>type</code> attribute in <code>script</code> tag)</li>\n	<li>General code cleaning</li>\n</ul>\n\n<h3>0.1.2 (2010/12/20)</h3>\n\n<ul>\n	<li>Added preference settings for several EditArea options</li>\n</ul>\n\n<h3>0.1.1 (2010/12/20)</h3>\n\n<ul>\n	<li>Documentation update <em>[thanks to Marc C. for the suggestions]</em></li>\n</ul>\n\n<h3>0.1.0 (2010/12/20)</h3>\n\n<ul>\n	<li>Initial release</li>\n	<li>EditArea integration for Txp&#8217;s Page Template, Form, and <span class=\"caps\">CSS</span> editors</li>\n</ul>', '\nif (@txpinterface == \'admin\') {\n    @require_plugin(\'soo_plugin_pref\');     // optional\n\n    add_privs(\'plugin_prefs.soo_editarea\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_editarea\',\'1,2\');\n    register_callback(\'soo_editarea_manage_prefs\', \'plugin_prefs.soo_editarea\');\n    register_callback(\'soo_editarea_manage_prefs\', \'plugin_lifecycle.soo_editarea\');\n\n    // Only activate plugin when needed\n    if (in_array(gps(\'event\'), array(\'page\', \'form\', \'css\'))) {\n        register_callback(\'soo_editarea\', \'admin_side\', \'head_end\');\n    }\n}\n\nfunction soo_editarea_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\')) {\n        return soo_plugin_pref($event, $step, soo_editarea_pref_spec());\n    }\n\n        // message to install soo_plugin_pref\n    if (substr($event, 0, 12) == \'plugin_prefs\') {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n            gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\') .\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">\' .\n            \'soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_editarea_pref_spec()\n{\n    return array(\n        \'page_form_lang\' => array(\n            \'val\'   => \'html\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Page Template and Form syntax\',\n        ),\n        \'editarea_dir\' => array(\n            \'val\'   => \'edit_area\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'EditArea directory\',\n        ),\n        \'language\' => array(\n            \'val\'   => \'en\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Language\',\n        ),\n        \'min_height\' => array(\n            \'val\'   => 500,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Editor height\',\n        ),\n        \'min_width\' => array(\n            \'val\'   => 400,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Editor width\',\n        ),\n        \'font_size\' => array(\n            \'val\'   => 10,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Font size\',\n        ),\n        \'font_family\' => array(\n            \'val\'   => \'monospace\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Font family\',\n        ),\n        \'replace_tab_by_spaces\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Convert tab to spaces\',\n        ),\n        \'plugins\' => array(\n            \'val\'   => \'\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'EditArea plugins (comma-separated)\',\n        ),\n    );\n}\n\nfunction soo_editarea_prefs()\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_editarea_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_editarea\'));\n        }\n    }\n    return $prefs;\n}\n\nfunction soo_editarea()\n{\n    $prefs = soo_editarea_prefs();\n    extract($prefs);\n    $exclude_from_init = array_flip(array(\'page_form_lang\', \'editarea_dir\'));\n    $init = array_diff_key($prefs, $exclude_from_init);\n    $textarea = array(\n        \'page\' => array(\n            \'id\' => \'html\',\n            \'syntax\' => $page_form_lang,\n        ),\n        \'form\' => array(\n            \'id\' => \'form\',\n            \'syntax\' => $page_form_lang,\n        ),\n        \'css\' => array(\n            \'id\' => \'css\',\n            \'syntax\' => \'css\',\n        ),\n    );\n    $init = array_merge($textarea[gps(\'event\')], $init);\n//  if ( substr(gps(\'name\'), -3) == \'.js\' )\n//      $init[\'syntax\'] = \'js\';\n\n    $func = \'editAreaLoader.init({\'.n.t.\'start_highlight: true\';\n    foreach ($init as $k => $v) if ($v) {\n        $func .= n.t.\",$k: \".( is_numeric($v) ? $v : \"\\\"$v\\\"\" );\n    }\n    $func .= n.\'});\';\n\n    echo\n        \'<script type=\"text/javascript\" src=\"\',\n        $editarea_dir,\n        \'/edit_area_full.js\"></script>\',\n        n, script_js($func), n\n    ;\n}\n', '\nif (@txpinterface == \'admin\') {\n    @require_plugin(\'soo_plugin_pref\');     // optional\n\n    add_privs(\'plugin_prefs.soo_editarea\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_editarea\',\'1,2\');\n    register_callback(\'soo_editarea_manage_prefs\', \'plugin_prefs.soo_editarea\');\n    register_callback(\'soo_editarea_manage_prefs\', \'plugin_lifecycle.soo_editarea\');\n\n    // Only activate plugin when needed\n    if (in_array(gps(\'event\'), array(\'page\', \'form\', \'css\'))) {\n        register_callback(\'soo_editarea\', \'admin_side\', \'head_end\');\n    }\n}\n\nfunction soo_editarea_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\')) {\n        return soo_plugin_pref($event, $step, soo_editarea_pref_spec());\n    }\n\n        // message to install soo_plugin_pref\n    if (substr($event, 0, 12) == \'plugin_prefs\') {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n            gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\') .\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">\' .\n            \'soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_editarea_pref_spec()\n{\n    return array(\n        \'page_form_lang\' => array(\n            \'val\'   => \'html\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Page Template and Form syntax\',\n        ),\n        \'editarea_dir\' => array(\n            \'val\'   => \'edit_area\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'EditArea directory\',\n        ),\n        \'language\' => array(\n            \'val\'   => \'en\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Language\',\n        ),\n        \'min_height\' => array(\n            \'val\'   => 500,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Editor height\',\n        ),\n        \'min_width\' => array(\n            \'val\'   => 400,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Editor width\',\n        ),\n        \'font_size\' => array(\n            \'val\'   => 10,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Font size\',\n        ),\n        \'font_family\' => array(\n            \'val\'   => \'monospace\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Font family\',\n        ),\n        \'replace_tab_by_spaces\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Convert tab to spaces\',\n        ),\n        \'plugins\' => array(\n            \'val\'   => \'\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'EditArea plugins (comma-separated)\',\n        ),\n    );\n}\n\nfunction soo_editarea_prefs()\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_editarea_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_editarea\'));\n        }\n    }\n    return $prefs;\n}\n\nfunction soo_editarea()\n{\n    $prefs = soo_editarea_prefs();\n    extract($prefs);\n    $exclude_from_init = array_flip(array(\'page_form_lang\', \'editarea_dir\'));\n    $init = array_diff_key($prefs, $exclude_from_init);\n    $textarea = array(\n        \'page\' => array(\n            \'id\' => \'html\',\n            \'syntax\' => $page_form_lang,\n        ),\n        \'form\' => array(\n            \'id\' => \'form\',\n            \'syntax\' => $page_form_lang,\n        ),\n        \'css\' => array(\n            \'id\' => \'css\',\n            \'syntax\' => \'css\',\n        ),\n    );\n    $init = array_merge($textarea[gps(\'event\')], $init);\n//  if ( substr(gps(\'name\'), -3) == \'.js\' )\n//      $init[\'syntax\'] = \'js\';\n\n    $func = \'editAreaLoader.init({\'.n.t.\'start_highlight: true\';\n    foreach ($init as $k => $v) if ($v) {\n        $func .= n.t.\",$k: \".( is_numeric($v) ? $v : \"\\\"$v\\\"\" );\n    }\n    $func .= n.\'});\';\n\n    echo\n        \'<script type=\"text/javascript\" src=\"\',\n        $editarea_dir,\n        \'/edit_area_full.js\"></script>\',\n        n, script_js($func), n\n    ;\n}\n', '464c3483cd2c7f845d0d8983b5180791', '', '', 3, 5, 3),
('soo_hide', 0, 'Jeff Soo', 'http://ipsedixit.net/', '0.1.3', 'Replacement for txp:hide', '<h1>soo_hide</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com/\">Textpattern</a>. It is a drop-in replacement for the native <code>hide</code> tag, with different output depending on site production status:</p>\n\n<ul>\n	<li><strong>Live</strong>: hide tag contents</li>\n	<li><strong>Testing</strong>: output tag contents, enclosed by <span class=\"caps\">HTML</span> comment delimiters</li>\n	<li><strong>Debugging</strong>: display tag contents</li>\n</ul>\n\n<p>To force the tag to hide its contents even when in Testing or Debugging mode, set the tag&#8217;s <code>force</code> attribute to <code>1</code>.</p>\n\n<h2>Download</h2>\n\n<ul>\n	<li><strong>Homepage:</strong> <a href=\"http://ipsedixit.net/txp/128/soo_hide\">http://ipsedixit.net/txp/128/soo_hide</a></li>\n	<li><strong>GitHub:</strong> <a href=\"https://github.com/jsoo/soo_hide/releases\">https://github.com/jsoo/soo_hide/releases</a></li>\n</ul>\n\n<h2 id=\"usage\">Usage</h2>\n\n<pre>&lt;txp:soo_hide&gt;\n	This will run when the site is in Debugging or Testing mode\n	(in Testing mode it will be enclosed in HTML comments).\n&lt;/txp:soo_hide&gt;\n\n&lt;txp:soo_hide force=&quot;1&quot;&gt;Your secret is safe with me.&lt;/txp:soo_hide&gt;</pre>\n\n<h2 id=\"history\">History</h2>\n\n<h4>0.1.3 (2017-03-11)</h4>\n\n<ul>\n	<li>Oops. Tag registration restored.</li>\n</ul>\n\n<h3>0.1.2 (2017-03-11)</h3>\n\n<ul>\n	<li>Documentation tidy (and new compile workflow)</li>\n</ul>\n\n<h3>0.1.1 (2017-03-11)</h3>\n\n<ul>\n	<li>Textpattern 4.6 compatibility update</li>\n</ul>\n\n<h3>0.1.0 (2011-01-13)</h3>\n\n<ul>\n	<li>Initial release.</li>\n	<li>Behaviour based on site production status.</li>\n	<li>Override test/debug behaviour with <code>force</code> attribute.</li>\n</ul>', '\n// Register public tags.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_hide\')\n        ;\n}\n\nfunction soo_hide ($atts, $thing)\n{\n    extract(lAtts(array(\n        \'force\' => 0,\n    ), $atts));\n\n    global $production_status;\n    if (! $force) switch ($production_status) {\n        case \'debug\': return parse($thing);\n        case \'testing\': return \'<!-- \'.parse($thing).\' -->\';\n    }\n\n    return \'\';\n}\n', '\n// Register public tags.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_hide\')\n        ;\n}\n\nfunction soo_hide ($atts, $thing)\n{\n    extract(lAtts(array(\n        \'force\' => 0,\n    ), $atts));\n\n    global $production_status;\n    if (! $force) switch ($production_status) {\n        case \'debug\': return parse($thing);\n        case \'testing\': return \'<!-- \'.parse($thing).\' -->\';\n    }\n\n    return \'\';\n}\n', '0cdd8b5887e6e2a40e7628cc6fd28728', '', '', 0, 5, 0),
('soo_if_frontpage', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.1.9', 'Check if page is a section front page', '<h1>soo_if_frontpage</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>.</p>\n\n<ul>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=31219\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/87/soo_if_frontpage\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_if_frontpage\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#usage\">Usage</a></li>\n	<li><a href=\"#attributes\">Attributes</a></li>\n	<li><a href=\"#examples\">Examples</a>\n	<ul>\n		<li><a href=\"#most\">Most restrictive case</a></li>\n		<li><a href=\"#least\">Least restrictive case</a></li>\n	</ul></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p><a href=\"http://www.markupartist.com/files/glx_if.txt\">Similar to the <code>glx_if_frontpage</code> tag from the no-longer-supported &#8220; <code>glx_if</code> plugin</a>. Most of that functionality is now available through core Txp tags, but not this (well, not without an absurd stack of conditional tags).</p>\n\n<h2 id=\"usage\">Usage</h2>\n\n<pre>&lt;txp:soo_if_frontpage&gt;\n...\n&lt;txp:else /&gt;\n...\n&lt;/txp:soo_if_frontpage&gt;</pre>\n\n<p><code>soo_if_frontpage</code> evaluates to <code>true</code> if the current page context is:</p>\n\n<ul>\n	<li>an article list, and;</li>\n	<li>not search results, and;</li>\n	<li>not a listing of articles by category, and;</li>\n	<li>not a listing of articles by author, and;</li>\n	<li>not a listing of articles by month, and;</li>\n	<li>not an image page, and;</li>\n	<li>in one of the sections listed in <code>section</code> (defaults to the &#8216;default&#8217; section only), and</li>\n	<li>(optionally) a single-page list or the first page of a multi-page list, if the <code>pg</code> attribute is set.</li>\n</ul>\n\n<h3 id=\"attributes\">Attributes</h3>\n\n<p>None <span class=\"required\">required</span>.</p>\n\n<ul>\n	<li><code>section</code> <em>(Txp section name[s])</em><br />\nComma-separated list of allowed sections. Leave empty to restrict condition to the default (i.e. home) section. Use <code>section=&quot;*&quot;</code> to include all sections.</li>\n	<li><code>pg</code> <em>(boolean)</em> <span class=\"default\">default</span> false<br />\nWhether or not to check for the &#8220;pg&#8221; <span class=\"caps\">URL</span> query param (e.g., <code>http://my-site.com/?pg=2</code>).<br />\nSet <code>pg=&quot;1&quot;</code> to allow only single-page lists or the first page of a multi-page list.</li>\n</ul>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3 id=\"most\">Most restrictive case</h3>\n\n<pre>&lt;txp:soo_if_frontpage pg=&quot;1&quot;&gt;\n... Home page ONLY\n&lt;txp:else /&gt;\n... any other page\n&lt;/txp:soo_if_frontpage&gt;</pre>\n\n<p>The above example returns true for the home page only. E.g., <code>http://example.com/</code> will cause the tag to return true, but <strong>any other standard Txp <span class=\"caps\">URL</span> will return false</strong>.</p>\n\n<h3 id=\"least\">Least restrictive case</h3>\n\n<pre>&lt;txp:soo_if_frontpage section=&quot;*&quot;&gt;\n... Home page, section front page, plus subsequent pages of a paginated list\n&lt;txp:else /&gt;\n... Indivdiual article; individual image; category, author, date, or search result lists\n&lt;/txp:soo_if_frontpage&gt;</pre>\n\n<p>The above example returns true on the home page, any section front page, and any paginated variation thereof. E.g., these pages would all return true:<br />\n<ul>\n	<li><code>http://example.com/</code></li>\n	<li><code>http://example.com/?pg=2</code></li>\n	<li><code>http://example.com/news/</code> (where &#8220;news&#8221; is a section name)</li>\n	<li><code>http://example.com/news/?pg=7</code></li><br />\n</ul></p>\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>0.1.9 (2017/02/13)</h3>\n\n<p>Txp 4.6 compatibility update</p>\n\n<h3>0.1.8 (1/14/2011)</h3>\n\n<p>Added check for global image context (i.e., the <code>p</code> query parameter)</p>\n\n<h3>0.1.7 (1/4/2011)</h3>\n\n<p>Documentation updated with descriptive examples. <em>(Thanks to Andre D for the suggestion.)</em></p>\n\n<h3>0.1.6 (10/7/2010)</h3>\n\n<p>More code cleaning, after I remembered that <code>$pretext</code> is already extracted in global scope</p>\n\n<h3>0.1.5 (10/7/2010)</h3>\n\n<p>Documentation update, code cleaning</p>\n\n<h3>0.1.4 (10/6/2010)</h3>\n\n<p>New feature: <code>section=&quot;*&quot;</code> is a shortcut for specifying all sections (including the default section)</p>\n\n<h3>0.1.3 (6/22/2010)</h3>\n\n<p>Fixed bug: section front pages now treated correctly (thanks, Victor)</p>\n\n<h3>0.1.2 (7/2009)</h3>\n\n<p>Added author and month search to the conditions to check against</p>\n\n<h3>0.1.1 (7/2009)</h3>\n\n<p>Added <code>pg</code> attribute; code and documentation cleaning; updated plugin template</p>\n\n<h3>0.1 (1/2008)</h3>\n\n<p><code>soo_if_frontpage</code> tag with <code>section</code> attribute</p>\n\n\n </div>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_if_frontpage\');\n}\n\nfunction soo_if_frontpage ($atts, $thing)\n{\n    $atts = lAtts(array(\n        \'section\'   =>  \'\',\n        \'pg\'        => false,\n    ), $atts);\n\n    global $s, $pg, $c, $q, $author, $month, $id, $p;\n\n    if (! $section = $atts[\'section\']) {\n        $section = \'default\';\n    }\n\n    return parse(EvalElse($thing,\n        ($section == \'*\' || in_list($s, $section)) &&\n        (! $atts[\'pg\'] || $pg < 2) &&\n        ! ($c or $q or $author or $month or $id or $p)\n    ));\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_if_frontpage\');\n}\n\nfunction soo_if_frontpage ($atts, $thing)\n{\n    $atts = lAtts(array(\n        \'section\'   =>  \'\',\n        \'pg\'        => false,\n    ), $atts);\n\n    global $s, $pg, $c, $q, $author, $month, $id, $p;\n\n    if (! $section = $atts[\'section\']) {\n        $section = \'default\';\n    }\n\n    return parse(EvalElse($thing,\n        ($section == \'*\' || in_list($s, $section)) &&\n        (! $atts[\'pg\'] || $pg < 2) &&\n        ! ($c or $q or $author or $month or $id or $p)\n    ));\n}\n', '258b69a675f842c64cf9be3d84ba4693', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_image', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '1.0.b.9', 'Simple yet powerful image tags', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help h6 {font-size: 1em;margin-left:2em;font-style:oblique;}\ndiv#sed_help li {list-style-type: disc;}\ndiv#sed_help li li {list-style-type: circle;}\ndiv#sed_help li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\n</style>\n <div id=\"sed_help\">\n\n	<h1>soo_image</h1>\n\n	<h2>Contents</h2>\n\n	<ul>\n		<li><a href=\"#overview\">Overview</a></li>\n		<li><a href=\"#usage\">Usage</a></li>\n		<li><a href=\"#image_context\">Image Context</a></li>\n		<li><a href=\"#tags\">Tags</a></li>\n		<li><a href=\"#controller_tag\">Controller tag</a>\n	<ul>\n		<li><a href=\"#soo_image_select\">soo_image_select</a></li>\n	</ul></li>\n	<ul>\n		<li><a href=\"#output_tags\">Output tags</a>\n	<ul>\n		<li><a href=\"#soo_image\">soo_image</a></li>\n		<li><a href=\"#soo_image_\">soo_image_*</a> <em>(-author, -caption, etc.)</em></li>\n		<li><a href=\"#soo_thumbnail_\">soo_thumbnail_*</a> <em>(-url, -height, -width)</em></li>\n		<li><a href=\"#soo_exif\">soo_exif</a></li>\n		<li><a href=\"#pagination\">Pagination</a></li>\n	</ul></li>\n		<li><a href=\"#conditional_tags\">Conditional tags</a>\n	<ul>\n		<li><a href=\"#soo_if_txp_image\">soo_if_txp_image</a></li>\n		<li><a href=\"#soo_if_image_\">soo_if_image_*</a> <em>(-author, -category, etc.)</em></li>\n		<li><a href=\"#soo_if_image_count\">soo_if_image_count</a></li>\n	</ul></li>\n	</ul></li>\n		<li><a href=\"#requirements\">Requirements &#38; Installation</a></li>\n		<li><a href=\"#preferences\">Preferences</a></li>\n		<li><a href=\"#examples\">Examples</a></li>\n		<li><a href=\"#history\">History</a></li>\n	</ul>\n\n	<p>A full <a href=\"http://ipsedixit.net/txp/55/soo_image-user-guide\">User Guide</a> is available at the <a href=\"http://ipsedixit.net/\">author&#8217;s website</a>.</p>\n\n	<h2 id=\"overview\">Overview</h2>\n\n	<p>Yes, it&#8217;s <em>Yet Another Image Plugin</em>&#174;. This one, though, fits right into <em>The Textpattern Way</em>&#8482;. Seriously, it does aim to be as Txp-ish as possible. No fancy stuff (with all the locked-in formatting that typically goes along with it), but simple tags that bring image display the same kind of flexible power Txp offers for article display.</p>\n\n	<p>Basic features include tags for displaying images and associated info (such as <code>author</code> &#38; <code>caption</code>). There are also <a href=\"#conditional_tags\">conditional tags</a> for checking against that info, and <a href=\"#soo_exif\">tags for reading and displaying <span class=\"caps\">EXIF</span> metadata</a> directly from image files.</p>\n\n	<p>The key feature is flexible image selection combined with Txp forms. Retrieve images singly or in groups. Select particular images or search on category, author, aspect ratio, and/or file type. Your selection works with <em>image forms</em> for your choice of formatting. Your automatically-formatted images can be placed anywhere, including directly into article body text. You can create and control galleries with your choice of Txp tags and <span class=\"caps\">HTML</span> markup. You can assign multiple images to an article.</p>\n\n	<p><strong>soo_image</strong> is designed for images managed through the Txp Images tab (it&#8217;s <em>The Textpattern Way</em>&#8482;, after all), but also allows direct selection by <span class=\"caps\">URL</span> (of course these won&#8217;t have captions etc.).</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>The <a href=\"#soo_image\"><code>soo_image</code></a> tag can be used standalone, in place of <a href=\"http://textbook.textpattern.net/wiki/index.php?title=image\"><code>image</code></a>, <a href=\"http://textbook.textpattern.net/wiki/index.php?title=thumbnail\"><code>thumbnail</code></a>, or <a href=\"http://textbook.textpattern.net/wiki/index.php?title=article_image\"><code>article_image</code></a>. One advantage is control over the <code>height</code> and <code>width</code> attributes in the <code>img</code> tag, making it easier to create layouts in which the images scale along with the text size, using only <span class=\"caps\">CSS</span>. There is a <code>title</code> attribute for control over the corresponding <code>title</code> in the <span class=\"caps\">HTML</span> output. <code>soo_image</code> also accepts <code>link_rel</code> and <code>onclick</code> attributes, helpful for hooking into javascript-based galleries.</p>\n\n	<p><code>soo_image</code> can go in an image form to display images retrieved by <a href=\"#soo_image_select\"><code>soo_image_select</code></a>. Txp doesn&#8217;t have image forms per se, so an <dfn>image form</dfn> is a form of type &#8220;misc&#8221; that is called by <code>soo_image_select</code> and that contains one or more <strong>soo_image</strong> tags.</p>\n\n	<p><code>soo_image_select</code> is generally used in conjunction with an image form. What <a href=\"http://textbook.textpattern.net/wiki/index.php?title=article\"><code>article</code></a> and <a href=\"http://textbook.textpattern.net/wiki/index.php?title=article_custom\"><code>article_custom</code></a> do for articles, <code>soo_image_select</code> does for images. The tag itself can go nearly anywhere (but <span class=\"warning\">not</span> in an image form). It retrieves images based on your criteria and then sends them to the image form for display. It plus an image form can replace <code>article_image</code>, allowing you to assign multiple images per article.</p>\n\n	<p>Instead of using an image form, you can also use <code>soo_image_select</code> as a container tag (in the same way <code>article</code> and <code>article_custom</code> work either as single or container tags). Sometimes this is more expedient than creating a new form.</p>\n\n	<p>The other tags work wherever there is <a href=\"#image_context\">image context</a>.</p>\n\n	<h2 id=\"image_context\">Image Context and Forms</h2>\n\n	<p>Most <strong>soo_image</strong> tags require image context to produce output. (The exceptions are <code>soo_image_select</code>, which is a controller rather than an output tag per se, and <code>soo_image</code> if it is used with a specified <code>id</code> or <code>name</code> value. Also, <strong>soo_image</strong> conditional tags work anywhere, but will always evaluate to false if there is no image context; note that <code>soo_if_image_count</code> uses a more expansive definition of image context.)</p>\n\n	<p><dfn>Image context</dfn> is provided by the first of, in descending order of priority:</p>\n\n	<ul>\n		<li>Inside an image form, the images selected by <code>soo_image_select</code>\n	<ul>\n		<li>specified <code>name</code> values, or;</li>\n		<li>specified <code>id</code> values, or;</li>\n		<li>specified <code>category</code>, <code>author</code>, <code>ext</code> (file type), or <code>aspect_ratio</code> values, or;</li>\n		<li>article images, or;</li>\n	</ul></li>\n		<li>Outside an image form but inside an article form (or tag)\n	<ul>\n		<li>(first) article image, or;</li>\n		<li>(optionally) first image selected by previous <code>soo_image_select</code> tag (persistent context)</li>\n	</ul></li>\n	</ul>\n\n	<p>Inside an <dfn>image form</dfn>, i.e. a form called by <code>soo_image_select</code> (or the contents of a <code>soo_image_select</code> container tag), all of the selected images will be passed, one at a time, to any <strong>soo_image</strong> tags. Outside an image form, a <strong>soo_image</strong> tag only sees the first image in the current context. This will be the first article image or, if there is no article image, the first image from the previous <code>soo_image_select</code> tag.</p>\n\n	<p>This last element, the first image from the previous <code>soo_image_select</code> tag, is called <dfn>persistent context</dfn>. It is optional because it can be disabled either in preferences or in <code>soo_image_select</code>&#8217;s attributes. See <a href=\"http://ipsedixit.net/txp/61/pagination#persistent_context\">pagination</a> for a useful example of persistent context.</p>\n\n	<p>Note that <code>soo_image_select</code> usually needs to appear before any other <strong>soo_image</strong> tags in order to provide this context. The <a href=\"#pagination\">pagination tags</a> are an exception; they can go anywhere on the page.</p>\n\n	<p><code>soo_if_image_count</code> works differently; see the <a href=\"#soo_if_image_count\">tag help</a> below.</p>\n\n	<h2 id=\"tags\">Tags</h2>\n\n	<h3 id=\"controller_tag\">Controller tag</h3>\n\n	<h4 id=\"soo_image_select\">soo_image_select</h4>\n\n	<p>Select images for display via other <strong>soo_image</strong> tags. Select specific images by name or id; search for images by author, category, aspect ratio, or file type, retrieve article image(s), or select images from Txp 4.3.0&#8217;s new image context. Each selected image will be passed to the image form or container contents.</p>\n\n	<p>Can be used almost anywhere, but <span class=\"warning\">do not use recursively</span>, i.e. inside another <strong>soo_image</strong> image form or <code>soo_image_select</code> container tag. In general you must place <code>soo_image_select</code> above any other <strong>soo_image</strong> tags if <code>soo_image_select</code> is expected to provide the image context for the other tags. (<strong>soo_image</strong> pagination tags are an exception, and can be placed anywhere.)</p>\n\n	<p>Use as a single tag (in which case the <code>form</code> attribute is <span class=\"required\">required</span>)</p>\n\n<pre>&#60;txp:soo_image_select /&#62;\n</pre>\n\n	<p>or as a container (in which case <code>form</code> and <code>listform</code> will be overridden)</p>\n\n<pre>&#60;txp:soo_image_select&#62;\n... *soo_image* tags and other content\n&#60;/txp:soo_image_select&#62;\n</pre>\n\n	<h5>Attributes</h5>\n\n	<p>Unless otherwise stated, all attributes default to unset (&#8220;0&#8221; or empty).</p>\n\n	<p>Note that <code>name</code> will override <code>id</code>, and either of these will override <code>author</code>, <code>category</code>, <code>aspect_ratio</code>, and <code>ext</code>. If none of these is declared, inside an article form <code>soo_image_select</code> looks for the article image(s) (and so <span class=\"required\">requires</span> an article context). And if neither applies, <code>soo_image_select</code> checks for global image context (new in Txp 4.3.0).</p>\n\n	<p><code>author</code>, <code>category</code>, <code>aspect_ratio</code>, and <code>ext</code> can be used together for a complex selection. Each attribute accepts a comma-separated list, and within each attribute the result is an OR (disjunctive) search, widening the search. However, across attributes the result is an <span class=\"caps\">AND</span> (conjunctive) search, narrowing the search. E.g., <code>&#60;txp:soo_image_select category=&#34;a,b&#34; author=&#34;c&#34; /&#62;</code> searches for images that are either in category &#8220;a&#8221; or &#8220;b&#8221;, but only those that are by author &#8220;c&#8221;.</p>\n\n	<p><code>form</code> and <code>listform</code> are overridden by the tag contents when <code>soo_image_select</code> is used as a container tag.</p>\n\n	<h6>Image selection attributes</h6>\n\n	<ul>\n		<li><code>name</code> <em>(text)</em><br />\nTxp name (from Content-&#62;Images) of image to select for display. Will accept a comma-separated list for multiple images.</li>\n		<li><code>id</code> <em>(integer)</em><br />\nTxp ID (from Content-&#62;Images) for image selection. Will accept a comma-separated list for multiple images; multiple id values can also be entered as ranges (&#8220;id:id&#8221; or &#8220;id-id&#8221;), including multiple ranges, preserving order (e.g., &#8220;44-46, 11:9&#8221; equivalent to &#8220;44,45,46,11,10,9&#8221;)</li>\n		<li><code>aspect_ratio</code> <em>(text)</em><br />\nImage aspect ratio (width:height), written as a number (e.g. &#8220;1.5&#8221;) or ratio (e.g. &#8220;3:2&#8221; or &#8220;3/2&#8221;). Optionally prefix &#8220;+&#8221; to get greater than or equal to specified aspect ratio, or &#8220;&minus;&#8221; to get less than or equal to (e.g., &#8220;+3:2&#8221; returns images with an aspect ratio of 3:2 or greater). Or, optionally give two ratios separated by &#8220;&minus;&#8221; to specify a range (e.g., &#8220;1-2&#8221;, &#8220;4:3-3:2&#8221;, etc.)</li>\n		<li><code>author</code> <em>(text)</em><br />\nComma-separated list of author names (login names) for image selection.</li>\n		<li><code>category</code> <em>(text)</em><br />\nComma-separated list of categories for image selection.</li>\n		<li><code>ext</code> <em>(filename extension)</em><br />\nComma-separated list of file types for image selection, e.g. &#8220;png,jpg&#8221;.</li>\n		<li><code>limit</code> <em>(integer)</em><br />\nLimit on number of images to return per page. Defaults to &#8220;0&#8221;, no limit.</li>\n		<li><code>sort</code> <em>(MySQL sort value)</em><br />\nSort order to use when selecting by <code>author</code>, <code>category</code>, <code>aspect_ratio</code> and/or <code>ext</code>. Defaults to &#8220;name asc&#8221; (alphabetical by image name). Other possibilities include &#8220;date desc&#8221; (most recent first) and &#8220;rand&#8221; or &#8220;random&#8221; (random). <code>sort</code> is overriden when selection is by <code>name</code>, <code>id</code>, or article image.</li>\n		<li><code>pagination</code> <em>(boolean)</em><br />\n<span class=\"default\">Enabled by default</span>. Set <code>pagination=&#34;0&#34;</code> to prevent this tag from changing or using pagination variables. In general this is only needed when using multiple <code>soo_image_select</code> tags on a single page, and where more than one of these tags has a <code>limit</code> set.</li>\n		<li><code>persistent_context</code> <em>(boolean)</em><br />\n<span class=\"default\">Enabled by default</span>, so that later <code>soo_image</code> tags will find the first selected image if they don&#8217;t find something else first. Set <code>persistent_context=&#34;0&#34;</code> to prevent this behavior.</li>\n	</ul>\n\n	<h6>Form output attributes</h6>\n\n	<ul>\n		<li><code>form</code> <em>(text)</em> <span class=\"required\">Required</span> (except when using <code>soo_image_select</code> as a container tag)<br />\nName of Txp form to apply to the image(s). Defaults to &#8220;soo_image&#8221; (or the plugin&#8217;s preference value, if you have assigned one).</li>\n		<li><code>listform</code> <em>(text)</em><br />\nName of Txp form to use if multiple images are retrieved. Defaults to the same value as <code>form</code> (or whatever value you have assigned to the global array).</li>\n		<li><code>break</code> <em>(text)</em><br />\n<span class=\"caps\">HTML</span> tag name (no brackets) or text to separate multiple images.</li>\n		<li><code>wraptag</code> <em>(<span class=\"caps\">HTML</span> tag name)</em><br />\n<span class=\"caps\">HTML</span> tag name (no brackets) for element to wrap the image(s).</li>\n		<li><code>class</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-class\" title=\"s\"><span class=\"caps\">HTML</span> class name</a> for the <code>wraptag</code>.</li>\n		<li><code>html_id</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-id\"><span class=\"caps\">HTML</span> id</a> for the <code>wraptag</code>.</li>\n	</ul>\n\n	<h3 id=\"output_tags\">Output tags</h3>\n\n	<h4 id=\"soo_image\">soo_image</h4>\n\n<pre>&#60;txp:soo_image /&#62;\n</pre>\n\n	<p>Display a single image.</p>\n\n	<h5>Attributes</h5>\n\n	<p>None required (but see paragraph below). Unless otherwise stated, all attributes default to unset (&#8220;0&#8221; or empty).</p>\n\n	<p>Note that <code>name</code> will override <code>id</code>. If neither is declared, <code>soo_image</code> <span class=\"required\">requires</span> article or image context.</p>\n\n	<p>By default, the <code>img</code> tag produced by <code>soo_image</code> has <code>height</code> and <code>width</code> attributes set to the actual image dimensions, in pixels. You can change this in the plugin&#8217;s preferences so that these attributes are not added. In either case you can override the default setting by specifying <code>height</code> and/or <code>width</code> in <code>soo_image</code>:</p>\n\n	<ul>\n		<li>If your preference setting is to display <code>height</code> and <code>width</code> by default, you can suppress this by declaring <code>height=&#34;0&#34;</code> or <code>width=&#34;0&#34;</code> in the <code>soo_image</code> tag. You don&#8217;t need to declare both; setting either of them will suppress both.</li>\n		<li>If your preference setting is to suppress both by default, you can override this by declaring <code>height=&#34;1&#34;</code> or <code>width=&#34;1&#34;</code> in the <code>soo_image</code> tag. Again, you don&#8217;t need to declare both.</li>\n		<li>As of version 1.0.b.7, thumbnail attributes are treated the same way (Txp version 4.2.0 or later; note that the Txp 4.2.0 upgrade does not automatically add thumbnail height and width to the database for existing images).</li>\n	</ul>\n\n	<p>You can also assign arbitrary <a href=\"http://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units\"><span class=\"caps\">CSS</span> length values</a> to either attribute. In this case, the value will be passed directly to the <code>img</code> tag. If you assign <code>height</code> such a value, but declare nothing for <code>width</code>, the <code>img</code> tag will have the specified <code>height</code> but will not have a <code>width</code> attribute (and vice versa); this preserves the correct aspect ratio.</p>\n\n	<p>Setting any of the link-related attributes (<code>link</code>, <code>link_class</code>, <code>link_id</code>, <code>link_rel</code>, <code>link_to</code>, or <code>onclick</code>) automatically enables both <code>link</code> and <code>thumbnail</code>.</p>\n\n	<h6>Image selection attributes</h6>\n\n	<ul>\n		<li><code>name</code> <em>(text)</em><br />\nTxp name (from Content-&#62;Images) of image to display.</li>\n		<li><code>id</code> <em>(integer)</em><br />\nTxp ID (from Content-&#62;Images) of image to display.</li>\n		<li><code>thumbnail</code> <em>(boolean)</em><br />\nSet to display the thumbnail instead of the full-size image.</li>\n	</ul>\n\n	<h6>Image tag attributes</h6>\n\n	<ul>\n		<li><code>height</code> <em>(mixed)</em>\n	<ul>\n		<li>empty (<span class=\"default\">default</span>): add standard <code>height</code> attribute (actual image height in pixels) if <code>default_dimensions</code> preference is on.</li>\n		<li><code>0</code> : Suppress <code>height</code> and <code>width</code> attributes if <code>default_dimensions</code> preference is on.</li>\n		<li><code>1</code> : force standard <code>height</code> and <code>width</code> attributes when <code>default_dimensions</code> preference is off.</li>\n		<li><a href=\"http://www.w3.org/TR/html401/types.html#type-length\"><span class=\"caps\">HTML</span> length value</a> for the <code>height</code> attribute (e.g. &#8220;50%&#8221;).</li>\n	</ul></li>\n		<li><code>width</code> <em>(mixed)</em><br />\nLike <code>height</code>.</li>\n		<li><code>escape</code> <em>(boolean)</em><br />\n<span class=\"default\">True by default</span>. Escape <span class=\"caps\">HTML</span> special characters in the <code>alt</code> and <code>title</code> attributes. Set to &#8220;0&#8221; to leave unescaped.</li>\n		<li><code>class</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-class\" title=\"s\"><span class=\"caps\">HTML</span> class name</a> for the <code>img</code> tag.</li>\n		<li><code>html_id</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-id\"><span class=\"caps\">HTML</span> id</a> for the <code>img</code> tag.</li>\n		<li><code>title</code> <em>(format string)</em><br />\n<a href=\"http://www.w3.org/TR/1999/REC-html401-19991224/struct/global.html#adef-title\"><span class=\"caps\">HTML</span> title attribute</a> for the <code>img</code> tag (and <code>a</code> tag for linked thumbnails). Any occurrences of &#8220;{caption}&#8221; will be replaced by the image caption from the Txp database; likewise for &#8220;{author}&#8221; and &#8220;{alt}&#8221;. <span class=\"default\">Default</span> is &#8220;{caption}&#8221;.</li>\n	</ul>\n\n	<h6>Linked thumbnail attributes</h6>\n\n	<p><span class=\"warning\">Note:</span> setting any of these automatically enables both <code>link</code> and <code>thumbnail</code>.</p>\n\n	<ul>\n		<li><code>link</code> <em>(boolean)</em><br />\nDisplay the thumbnail as a link to the full-size image.</li>\n		<li><code>link_class</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-class\" title=\"s\"><span class=\"caps\">HTML</span> class name</a> for the <code>a</code> tag.</li>\n		<li><code>link_id</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-id\"><span class=\"caps\">HTML</span> id</a> for the <code>a</code> tag.</li>\n		<li><code>link_rel</code> <em>(relation)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/links.html#adef-rel\"><span class=\"caps\">HTML</span> <code>rel</code> attribute</a> for the <code>a</code> tag.</li>\n		<li><code>onclick</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/1999/REC-html401-19991224/interact/scripts.html#adef-onclick\"><span class=\"caps\">HTML</span> <code>onclick</code> attribute</a> for the <code>a</code> tag.</li>\n		<li><code>link_to</code> <em>(<span class=\"caps\">URI</span>)</em><br />\nRoot-relative <span class=\"caps\">URI</span> to link to; if that <span class=\"caps\">URI</span> contains a <strong>soo_image</strong> gallery that includes this image, the link will be to that point in the gallery (works for multi-page galleries).</li>\n	</ul>\n\n	<h4 id=\"metadata\">Txp Metadata tags</h4>\n\n	<p>Display metadata fields from the Txp image table. These tags can be used anywhere, but require image context.</p>\n\n<pre>&#60;txp:soo_image_alt /&#62;\n&#60;txp:soo_image_author /&#62;\n&#60;txp:soo_image_caption /&#62;\n&#60;txp:soo_image_category /&#62;\n&#60;txp:soo_image_date /&#62;\n&#60;txp:soo_image_height /&#62;\n&#60;txp:soo_image_id /&#62;\n&#60;txp:soo_image_name /&#62;\n&#60;txp:soo_image_url /&#62;\n&#60;txp:soo_image_width /&#62;\n&#60;txp:soo_thumbnail_url /&#62;\n&#60;txp:soo_thumbnail_height /&#62;\n&#60;txp:soo_thumbnail_width /&#62;\n</pre>\n\n	<p><code>soo_image_date</code> returns the date stored by Txp, which is the image upload date, which is not all that useful. See <code>soo_exif</code> for how to show the exposure date for images containing <span class=\"caps\">EXIF</span> data.</p>\n\n	<p><code>soo_image_author</code> returns the author&#8217;s full name rather than the login name stored in the image table.</p>\n\n	<p>Note that thumbnail height and width are available only in Txp 4.2.0 or later. Also note that the Txp 4.2.0 update script does not update these fields for images already in the database (the <a href=\"http://ipsedixit.net/txp/91/soo_thumb_atts\">soo_thumb_atts</a> plugin offers a solution).</p>\n\n	<h5>Attributes</h5>\n\n	<p>All of these tags accept standard <code>wraptag</code>, <code>class</code>, and <code>html_id</code> attributes.</p>\n\n	<p>Additionally, <code>soo_image_alt</code> and <code>soo_image_caption</code> each accept the following optional attribute:</p>\n\n	<ul>\n		<li><code>escape</code> <em>(boolean)</em><br />\nSet by default, <span class=\"caps\">HTML</span> special characters will be converted. Set to &#8220;0&#8221; to leave the output unconverted.</li>\n	</ul>\n\n	<p><code>soo_image_caption</code> accepts the following attribute:</p>\n\n	<ul>\n		<li><code>no_widow</code> <em>(boolean)</em><br />\nSet by default, will insert a non-breaking space between the last two words in the caption. Set to &#8220;0&#8221; to leave the output unconverted.</li>\n	</ul>\n\n	<p><code>soo_image_date</code> accepts the following attribute:</p>\n\n	<ul>\n		<li><code>format</code> <em>(format string)</em><br />\nSet to a valid <a href=\"http://us2.php.net/strftime\">strftime()</a> value to override the default date format set in site preferences.</li>\n	</ul>\n\n	<h4 id=\"soo_exif\">Exif tags</h4>\n\n	<ul>\n		<li><strong>soo_exif</strong></li>\n		<li><strong>soo_exif_field</strong></li>\n		<li><strong>soo_exif_value</strong></li>\n	</ul>\n\n	<p>Retrieve and display <span class=\"caps\">EXIF</span> metadata directly from image files. In single-tag mode each field will be displayed in the format &#8220;field: value&#8221;. This can be customized with the <code>format</code> attribute.</p>\n\n	<p>In container-tag mode the tag contents are iterated over, once for each <span class=\"caps\">EXIF</span> field; use the <code>soo_exif_field</code> and <code>soo_exif_value</code> tags to display the data. That is, the tag contents are like a Txp form. The following two blocks are equivalent:</p>\n\n<pre>&#60;txp:soo_exif /&#62;\n</pre>\n\n<pre>&#60;txp:soo_exif&#62;\n&#60;txp:soo_exif_field /&#62;: &#60;txp:soo_exif_value /&#62;\n&#60;/txp:soo_exif&#62;\n</pre>\n\n	<h5>Requirements</h5>\n\n	<p><code>soo_exif</code> uses <span class=\"caps\">PHP</span>&#8217;s <code>exif_read_data()</code> function, which <span class=\"required\">requires</span> <span class=\"caps\">PHP</span> to be compiled with &minus;&minus;<code>enable-exif</code> (you can check this by running <a href=\"http://php.about.com/od/learnphp/p/PHP_info.htm\">phpinfo()</a>). It requires a full file path for the image; this plugin only works for image files in your default Txp image directory (they don&#8217;t have to be in the Txp database, though).</p>\n\n	<h5>Attributes</h5>\n\n	<ul>\n		<li><code>field</code> <em>(text)</em> Comma-separated list of fields to display. Use standard <span class=\"caps\">EXIF</span> field names or shortcuts (see below). Additional options:\n	<ul>\n		<li><span class=\"default\">default</span> <em>(empty)</em> : Display a pre-set list of common fields</li>\n		<li><code>dump</code> : Display all fields</li>\n	</ul></li>\n		<li><code>format=&#34;format string&#34;</code> Default is &#8220;{field}: {value}&#8221;. In single-tag mode the tag will output this string, after any occurences of &#8220;{field}&#8221; have been replaced with the <span class=\"caps\">EXIF</span> field name; likewise for &#8220;{value}&#8221; and the <span class=\"caps\">EXIF</span> field value.</li>\n		<li><code>wraptag=&#34;tag name&#34;</code> Tag name (no brackets) for element to wrap the anchor</li>\n		<li><code>break=&#34;mixed&#34;</code> Tag name (no brackets) or text to add between items</li>\n	</ul>\n\n	<h5>Field-name shortcuts</h5>\n\n	<p>These are the same as used by <a href=\"http://www.hugsan.com/EXIFutils/\"><span class=\"caps\">EXIFU</span>tils</a>, a command-line utility for manipulating <span class=\"caps\">EXIF</span> data.</p>\n\n	<table>\n		<tr>\n			<th><span class=\"caps\"><span class=\"caps\">EXIF</span></span> field </th>\n			<th>Shortcut</th>\n		</tr>\n		<tr>\n			<td>Model</td>\n			<td>model</td>\n		</tr>\n		<tr>\n			<td>ExposureTime</td>\n			<td>exp-time</td>\n		</tr>\n		<tr>\n			<td>FNumber</td>\n			<td>f-number</td>\n		</tr>\n		<tr>\n			<td>ISOSpeedRatings</td>\n			<td>iso-speed</td>\n		</tr>\n		<tr>\n			<td>DateTimeOriginal</td>\n			<td>date-taken</td>\n		</tr>\n		<tr>\n			<td>FocalLength</td>\n			<td>focal-len</td>\n		</tr>\n		<tr>\n			<td>ImageHistory</td>\n			<td>history</td>\n		</tr>\n	</table>\n\n	<h5>Additional fields</h5>\n\n	<p>If the <code>FocalLengthIn35mmFilm</code> field is available, <strong>soo_exif</strong> computes two additional values:</p>\n\n	<table>\n		<tr>\n			<th>Computed field </th>\n			<th>Shortcut </th>\n			<th>Description</th>\n		</tr>\n		<tr>\n			<td><span class=\"caps\"><span class=\"caps\">FOV</span></span></td>\n			<td>fov</td>\n			<td>Field of view (assumes rectilinear lens)</td>\n		</tr>\n		<tr>\n			<td>CropFactor</td>\n			<td>crop</td>\n			<td>Crop factor</td>\n		</tr>\n	</table>\n\n	<h5>Default fields</h5>\n\n	<p>If no <code>field</code> is specified, <code>soo_exif</code> will show the all fields listed above.</p>\n\n	<h5>soo_exif_field, soo_exif_value</h5>\n\n	<p>When placed in a <code>soo_exif</code> container tag, these tags display the <span class=\"caps\">EXIF</span> field name and value, respectively. Each is a single tag and accepts the standard <code>wraptag</code> attribute. As shown above, <code>soo_exif</code> converts some field names to a more readable format; the same is true for some field values. (The <code>DateTimeOriginal</code> field is converted to the date format from your Txp preferences.)</p>\n\n	<h4 id=\"pagination\">Pagination tags</h4>\n\n	<ul>\n		<li><strong>soo_image_prev</strong></li>\n		<li><strong>soo_image_next</strong></li>\n		<li><strong>soo_image_page_count</strong></li>\n	</ul>\n\n	<p>For use when <code>soo_image_select</code> has a <code>limit</code>. The first two can be used as single tags or containers. Any of these can be placed anywhere on the page, even before the associated <code>soo_image_select</code> tag. (Note that if there is more than one <code>soo_image_select</code> tag on the page, you should use the <code>pagination</code> attribute to disable pagination in all but one of the tags.)</p>\n\n<pre>&#60;txp:soo_image_prev /&#62;\n&#60;txp:soo_image_next&#62;Next&#60;/txp:soo_image_next&#62;\n&#60;txp:soo_image_page_count /&#62;\n</pre>\n\n	<h5>Attributes</h5>\n\n	<p><code>soo_image_prev</code> and <code>soo_image_next</code> each accept the following optional attributes:\n	<ul>\n		<li><code>class</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-class\" title=\"s\"><span class=\"caps\">HTML</span> class name</a> for the <code>a</code> tag.</li>\n		<li><code>html_id</code> <em>(text)</em><br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-id\"><span class=\"caps\">HTML</span> id</a> for the <code>a</code> tag.</li>\n		<li><code>link_text</code> <em>(text)</em><br />\nLink text. Default is &#8220;&larr;&#8221; for <code>soo_image_prev</code> and &#8220;&rarr;&#8221; for <code>soo_image_next</code>. It is overriden by the tag contents if it is a container tag.</li>\n	</ul></p>\n\n	<p><code>soo_image_page_count</code> accepts the following optional attributes:</p>\n\n	<ul>\n		<li><code>format</code> <em>(format string)</em> <span class=\"default\">default</span> <code> &#34;Page {current} of {total}&#34; </code><br />\nTag will output this string after replacing <code>{current}</code> and <code>{total}</code> with page numbers</li>\n		<li><code>showalways</code> <em>(boolean)</em> <span class=\"default\">default</span> <code>0</code><br />\nWhether or not to show output when the list is a single page</li>\n	</ul>\n\n	<h3 id=\"conditional_tags\">Conditional tags</h3>\n\n	<p>Standard conditional tag behavior, compatible with <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:else_/\"><code>else</code></a>. Can be used anywhere. They <span class=\"required\">require</span> image context or else they will always evaluate to <code>false</code> (or empty in the case of <code>soo_if_image_count</code>, which treats context somewhat differently).</p>\n\n	<h4 id=\"soo_if_txp_image\">soo_if_txp_image</h4>\n\n<pre>&#60;txp:soo_if_txp_image&#62;...&#60;txp:else /&#62;...&#60;/txp:soo_if_txp_image&#62;\n</pre>\n\n	<h5>Attributes</h5>\n\n	<p><code>soo_if_txp_image</code> does not accept any attributes; it simply checks whether or not the image is stored in the Txp image table (as opposed to an article image declared by <span class=\"caps\">URL</span>).</p>\n\n	<h4 id=\"soo_if_image_\">soo_if_image_* <em>(-author, -category, -thumbnail)</em></h4>\n\n<pre>&#60;txp:soo_if_image_author&#62;...&#60;txp:else /&#62;...&#60;/txp:soo_if_image_author&#62;\n&#60;txp:soo_if_image_category&#62;...&#60;txp:else /&#62;...&#60;/txp:soo_if_image_category&#62;\n&#60;txp:soo_if_image_thumbnail&#62;...&#60;txp:else /&#62;...&#60;/txp:soo_if_image_thumbnail&#62;\n</pre>\n\n	<h5>Attributes</h5>\n\n	<p><code>soo_if_image_thumbnail</code> does not accept any attributes; it simply checks whether or not the image has a thumbnail.</p>\n\n	<p><code>soo_if_image_author</code> and <code>soo_if_image_category</code> each accept the following attribute:</p>\n\n	<ul>\n		<li><code>name</code> <em>(text)</em><br />\nComma-separated list of values. Condition is true if the value from the image is in this list. <span class=\"warning\">Important:</span> for <code>soo_if_image_author</code>, use author login names, not full names. If unset (the default), condition is true if the value from the image is empty or 0.</li>\n	</ul>\n\n	<h4 id=\"soo_if_image_count\">soo_if_image_count</h4>\n\n<pre>&#60;txp:soo_if_image_count&#62;...&#60;txp:else /&#62;...&#60;/txp:soo_if_image_count&#62;\n</pre>\n\n	<p>Counts the images available and allows conditional checks against that number. It checks the following contexts, in order of priority:</p>\n\n	<ul>\n		<li>Inside a <code>soo_image_select</code> tag or form</li>\n		<li>Inside an <code>article</code> or <code>article_custom</code> tag or form (checks article images)</li>\n		<li>In an image category page (Txp 4.3.0 or later).</li>\n	</ul>\n\n	<p>Outside any of these contexts, the tag does not return any output.</p>\n\n	<h5>Attributes</h5>\n\n	<ul>\n		<li><code>exact</code> <em>(integer)</em> Exact number of images to check for</li>\n		<li><code>min</code> <em>(integer)</em> Minimum number of images to check for</li>\n		<li><code>max</code> <em>(integer)</em> Maximum number of images to check for</li>\n	</ul>\n\n	<p>All attributes default to empty; you must set one or the tag returns no output.</p>\n\n	<h2 id=\"requirements\">Requirements &#38; Installation</h2>\n\n	<p>This plugin <span class=\"required\">requires <span class=\"caps\">PHP</span> 5 or greater and the</span> <a href=\"http://ipsedixit.net/txp/21/soo-txp-obj\">soo_txp_obj library plugin</a>. For details see <a href=\"http://ipsedixit.net/txp/20/installation\">http://ipsedixit.net/txp/20/installation</a>.</p>\n\n	<h2 id=\"preferences\">Preferences</h2>\n\n	<p>This plugin is compatible with <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a>, allowing you to set default values for image form, image list form, whether or not image dimensions are included in <code>img</code> tag output by default, and whether or not to use <a href=\"#image_context\">persistent context</a> for isolated <code>soo_image</code> tags.</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<p>See <a href=\"http://ipsedixit.net/txp/55/soo_image-user-guide\">http://ipsedixit.net/txp/55/soo_image-user-guide</a> to see <strong>soo_image</strong> in action.</p>\n\n	<h2 id=\"history\">Version History</h2>\n\n	<h3>1.0.b.9 (2017/02/14)</h3>\n\n	<ul>\n		<li>Txp 4.6 compatibility update</li>\n		<li><span class=\"caps\">PHP</span> 5.3+ compatibility update</li>\n	</ul>\n\n	<h3>1.0.b.8 (12/18/2010)</h3>\n\n	<ul>\n		<li><code>soo_image_select</code>, when used without selection attributes (id, category, etc.) and outside article context will now check for image category context (requires Txp 4.3.0).</li>\n		<li>New conditional tag, <code>soo_if_image_count</code>.</li>\n	</ul>\n\n	<h3>1.0.b.7 (7/4/2010, <span class=\"caps\">USA</span> Independence Day)</h3>\n\n	<ul>\n		<li>Thumbnail <code>height</code> and <code>width</code> attributes (Txp 4.2.0 or later) added automatically, according to <code>default_dimensions</code> preference (requires soo_txp_obj v 1.0.b.7).</li>\n		<li>Help text updates.</li>\n	</ul>\n\n	<h3>1.0.b.6 (6/29/2010)</h3>\n\n	<ul>\n		<li>New behavior for <code>soo_image</code>: when used outside <code>soo_image_select</code>, and without <code>id</code> or <code>name</code>, article image now takes priority over any images from <code>soo_image_select</code> tags earlier on the page (i.e., persistent context). This gives more predictable behavior in an article list. (Thanks to Adi for spotting this.)</li>\n		<li>New preference setting to enable/disable persistent context.</li>\n		<li>Code cleaning (soo_txp_obj 1.0.b.6 now required).</li>\n	</ul>\n\n	<h3>1.0.b.5 (6/21/2010)</h3>\n\n	<ul>\n		<li>Fixed bug in <code>soo_image</code> where <code>width=&#34;0&#34;</code> was not suppressing dimension attributes as expected</li>\n		<li>Added <code>wraptag</code>, <code>class</code>, and <code>html_id</code> attributes to various tags (requested by Adi)</li>\n	</ul>\n\n	<h3>1.0.b.4 (4/9/2010)</h3>\n\n	<ul>\n		<li>New tag: <code>soo_image_id</code> (output image ID)</li>\n	</ul>\n\n	<h3>1.0.b.3 (3/29/2010)</h3>\n\n	<ul>\n		<li>Fixed default text for <code>soo_image_prev</code></li>\n		<li>Added <code>pagination</code> attribute to <code>soo_image_select</code>, for better pagination control when there are multiple <code>soo_image_select</code> tags on a page</li>\n	</ul>\n\n	<h3>1.0.b.2 (9/18/2009)</h3>\n\n	<ul>\n		<li>Fixed bug in <code>soo_image_date</code> (was displaying incorrect date or time zone in some cases)</li>\n		<li>New tags:\n	<ul>\n		<li><code>soo_thumbnail_url</code></li>\n		<li><code>soo_thumbnail_height</code> (Txp 4.2.0 or higher)</li>\n		<li><code>soo_thumbnail_width</code> (Txp 4.2.0 or higher)</li>\n		<li><code>soo_image_page_count</code></li>\n	</ul></li>\n		<li>Changed behavior: now uses (optionally) soo_plugin_pref. The old soo_plugin_prefs is no longer supported.</li>\n	</ul>\n\n	<h3>1.0.b.1 (7/5/2009)</h3>\n\n	<ul>\n		<li>New <code>link_to</code> attribute for <code>soo_image</code></li>\n		<li><code>soo_image_next</code> can now be used before its associated <code>soo_image_select</code> tag</li>\n		<li>Underlying soo_txp_obj library has been largely rewritten</li>\n		<li>downshift to v2.0 of the <span class=\"caps\">GPL</span></li>\n	</ul>\n\n	<h3>1.0.a.5 (5/18/2009) (not released publicly)</h3>\n\n	<ul>\n		<li>Changes to the <code>soo_image</code> tag:\n	<ul>\n		<li>New attributes: <code>title</code>, <code>link_class</code>, <code>link_id</code></li>\n		<li>New behavior: Setting any link-related attribute sets <code>thumbnail</code> and <code>link</code> to true</li>\n	</ul></li>\n	</ul>\n\n	<h3>1.0.a.4 (5/17/2009)</h3>\n\n	<ul>\n		<li>Added <code>soo_exif</code> and related tags for reading and displaying <span class=\"caps\">EXIF</span> metadata</li>\n		<li>Can now enter ranges of ids (e.g., &#8220;44-48, 12-9&#8221;) for article image or <code>soo_image_select</code> id</li>\n		<li>Fixed bug in aspect ratio using ratio syntax (e.g., &#8220;4:3&#8221; or &#8220;4/3&#8221;)</li>\n	</ul>\n\n	<h3>1.0.a.3 (5/14/2009)</h3>\n\n	<p><code>soo_image_select</code> can now be used as a single or container tag</p>\n\n	<h3>1.0.a.2 (5/13/2009)</h3>\n\n	<ul>\n		<li>Added <code>soo_image_url</code> tag</li>\n		<li>Fixed incorrect <span class=\"caps\">ORDER</span> BY <span class=\"caps\">FIELD</span> syntax</li>\n		<li>Upgraded <strong>soo_txp_obj</strong> to deal with a <span class=\"caps\">PHP</span> 5.2.0 &#8211; 5.2.3 bug</li>\n	</ul>\n\n	<h3>1.0.a.1 (5/2/2009)</h3>\n\n	<p>Initial release</p>\n\n </div>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_image_select\')\n		->register(\'soo_image\')\n		->register(\'soo_image_alt\')\n		->register(\'soo_image_author\')\n		->register(\'soo_image_caption\')\n		->register(\'soo_image_category\')\n		->register(\'soo_image_date\')\n		->register(\'soo_image_height\')\n		->register(\'soo_image_id\')\n		->register(\'soo_image_name\')\n		->register(\'soo_image_url\')\n		->register(\'soo_image_width\')\n		->register(\'soo_image_thumbnail_height\')\n		->register(\'soo_image_thumbnail_url\')\n		->register(\'soo_image_thumbnail_width\')\n		->register(\'soo_if_txp_image\')\n		->register(\'soo_if_image_author\')\n		->register(\'soo_if_image_category\')\n		->register(\'soo_if_image_thumbnail\')\n		->register(\'soo_if_image_count\')\n		->register(\'soo_image_next\')\n		->register(\'soo_image_prev\')\n		->register(\'soo_image_page_count\')\n		->register(\'soo_exif\')\n		->register(\'soo_exif_field\')\n		->register(\'soo_exif_value\')\n		;\n}\n\nrequire_plugin(\'soo_txp_obj\');\n@require_plugin(\'soo_plugin_pref\');		// optional\n\n// Plugin init not needed on admin side\nif ( @txpinterface == \'public\' )\n{\n	global $soo_image;\n	$soo_image = function_exists(\'soo_plugin_pref_vals\') ?\n		array_merge(soo_image_defaults(true), soo_plugin_pref_vals(\'soo_image\'))\n		: soo_image_defaults(true);\n	$soo_image[\'page_param\'] = \'soo_img_pg\';\n	$soo_image[\'jump_param\'] = \'soo_img_jp\';\n}\nelseif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'plugin_prefs.soo_image\',\'1,2\');\n	add_privs(\'plugin_lifecycle.soo_image\',\'1,2\');\n	register_callback(\'soo_image_prefs\', \'plugin_prefs.soo_image\');\n	register_callback(\'soo_image_prefs\', \'plugin_lifecycle.soo_image\');\n}\n\nfunction soo_image_prefs( $event, $step ) {\n	if ( function_exists(\'soo_plugin_pref\') )\n		return soo_plugin_pref($event, $step, soo_image_defaults());\n	if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n		$plugin = substr($event, 12);\n		$message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n			gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\')\n			. \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n		pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n	}\n}\n\nfunction soo_image_defaults( $vals_only = false ) {\n	$defaults = array(\n		\'default_form\'			=>	array(\n			\'val\'	=>	\'soo_image\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Default image form\',\n		),\n		\'default_listform\'		=>	array(\n			\'val\'	=>	\'\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Default image list form\',\n		),\n		\'default_dimensions\'	=>	array(\n			\'val\'	=>	1,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Add height and width in pixels to img tag by default?\',\n		),\n		\'persistent_context\'	=>	array(\n			\'val\'	=>	1,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Use persistent context for tags outside an image form?\',\n		),\n	);\n	if ( $vals_only )\n		foreach ( $defaults as $name => $arr )\n			$defaults[$name] = $arr[\'val\'];\n	return $defaults;\n}\n\n  //---------------------------------------------------------------------//\n //									Tags								//\n//---------------------------------------------------------------------//\n\nfunction soo_image_select( $atts, $thing = null ) {\n\n// Controller for the soo_image plugin.\n//\n// Determine what images the user is requesting, fetch image data objects\n// (or raw URLs, as appropriate) for those images, and pass this along\n// to an image form (i.e., a form called by this tag, containing one or\n// more of soo_image\'s output tags) for display.\n\n	global $soo_image, $thisarticle, $context, $c;\n\n	extract(lAtts(array(\n		\'aspect_ratio\'	=>	\'\',\n		\'author\'		=>	\'\',\n		\'break\'			=>	\'\',\n		\'category\'		=>	\'\',\n		\'class\'			=>	\'\',\n		\'ext\'			=>	\'\',\n		\'form\'			=>	$soo_image[\'default_form\'],		// required\n		\'html_id\'		=>	\'\',\n		\'id\'			=>	\'\',\n		\'limit\'			=>	0,\n		\'listform\'		=>	$soo_image[\'default_listform\'],\n		\'name\'			=>	\'\',\n		\'pagination\'	=>	true,\n		\'persistent_context\'	=> $soo_image[\'persistent_context\'],\n		\'sort\'			=>	\'name asc\',		// accepts lists, e.g. \"a asc,b,c desc\"\n		\'wraptag\'		=>	\'\',\n	), $atts));\n\n	$page = intval(gps($soo_image[\'page_param\']));\n	if ( $page < 1 ) $page = 1;\n	$offset = $pagination ? ($page - 1) * $limit : 0;\n\n	$jump_to = intval(gps($soo_image[\'jump_param\']));\n\n	$query = new soo_txp_select(\'txp_image\');\n\n// Selection priority: name; id; (category|author|ext|aspect_ratio); article image.\n// Comma-separated lists allowed for all criteria; preserve list order for name|id.\n\n	if ( $name or $id ) {\n		$sort = \'\';\n		if ( $name )\n			$query->in(\'name\', $name)->order_by_field(\'name\', $name);\n		else\n			$ids = _soo_range_to_list($id);\n	}\n\n	elseif ( $category or $author or $ext or $aspect_ratio ) {\n		if ( $category ) $query->in(\'category\', $category);\n		if ( $author ) $query->in(\'author\', $author);\n		if ( $ext ) {\n			if ( $ext = _soo_valid_ext($ext) )\n				$query->in(\'ext\', $ext);\n			else\n				return;\n		}\n		if ( $aspect_ratio ) _soo_image_aspect_ratio($query, trim($aspect_ratio));\n	}\n\n	elseif ( ! empty($thisarticle) ) {\n		$image_array = _soo_article_image_list();\n		if ( ! $image_array )\n			return false;\n		foreach ( $image_array as $img ) {\n			if ( is_numeric($img) )\n				$ids[] = $img;\n			else\n				$urls[] = $img;\n		}\n	}\n\n	elseif ( $context == gTxt(\'image_context\') and $c ) {\n		$query->in(\'category\', $c);\n	}\n\n	else return;\n\n	if ( isset($ids) ) {\n		$sort = \'\';\n		$query->in(\'id\', $ids)->order_by_field(\'id\', $ids);\n	}\n\n	$full_count = $query->count();\n	if ( $pagination && $full_count <= $offset ) {		// in case of direct URI manipulation and invalid #\n		$page = $limit ? ceil($full_count / $limit) : 1;	// go to last page\n		$offset = ($page - 1) * $limit;\n	}\n\n	$soo_image[\'this_page\'] = $page;\n	if ( $pagination ) $soo_image[\'total_pages\'] = $limit ? ceil($full_count / $limit) : 1;\n\n	if ( $jump_to and is_numeric($jump_to) ) {\n		$uri = new soo_uri;\n		$uri->set_query_param($soo_image[\'jump_param\']);\n		$all_ids = new soo_txp_rowset($query->order_by($sort));\n		$all_ids = array_flip($all_ids->field_vals(\'id\'));\n		if ( isset($all_ids[$jump_to]) ) {\n			$jump_index = $all_ids[$jump_to];\n			if ( $limit ) {\n				$page = ceil(($jump_index + 1) / $limit);\n				$offset = ($page - 1) * $limit;\n				$_POST[$soo_image[\'page_param\']] = $page;\n			}\n		}\n	}\n\n	$query->order_by($sort)->limit($limit)->offset($offset);\n	$rs = new soo_txp_rowset($query);\n	unset($query);\n\n	if ( isset($urls) )\n		foreach ( $image_array as $i => $img ) {\n			if ( isset($rs->rows[$img]) )\n				$image_array[$i] = $rs->rows[$img];\n			elseif ( ! in_array($img, $urls) )\n				unset($image_array[$i]);\n		}\n	else\n		$image_array = $rs->rows;\n\n	if ( empty($image_array) )\n		return false;\n\n	$soo_image[\'count\'] = count($image_array);\n\n	if ( $pagination && $limit && ( $offset + $limit < $full_count ) )\n		$soo_image[\'next\'] = $page + 1;\n\n	// $image_array now contains data objects and/or presumed URLs\n\n	if ( ( $soo_image[\'count\'] > 1 or $page > 1) and $listform )\n		$form = $listform;\n\n	$soo_image[\'first_selection\'] = isset($jump_index) ? new soo_txp_img($jump_to) : current($image_array);\n\n	foreach ( $image_array as $img ) {\n		if ( $img instanceof soo_txp_img )\n			$soo_image[\'data_obj\'] = $img;\n		else\n			$soo_image[\'url\'] = $img;\n		$parsed = is_null($thing) ? parse_form($form) : parse($thing);\n		if ( $parsed ) $out[] = $parsed;\n		unset($soo_image[\'data_obj\']);\n		unset($soo_image[\'url\']);\n	}\n\n	if ( ! $persistent_context )\n		unset($soo_image[\'first_selection\']);\n\n	unset($soo_image[\'count\']);\n\n	return isset($out) ? doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id) : false;\n\n}\n////////////////////// end of soo_image_select /////////////////////\n\nfunction soo_image( $atts ) {\n// Display an image. Selection by, in descending order of priority:\n	// the tag\'s \'name\' attribute;\n	// the tag\'s \'id\' attribute;\n	// the image context:\n		// id or url sent by $soo_image to an image form\n		// article image\n		// first selection from prior soo_image_select (subject to pref setting)\n\n	extract(lAtts(array(\n		\'id\'			=>	\'\',\n		\'name\'			=>	\'\',\n		\'thumbnail\'		=>	false,\n		\'height\'		=>	\'\',\n		\'width\'			=>	\'\',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n		\'title\'			=>	\'{caption}\',\n		\'link\'			=>	false,\n		\'link_class\'	=>	\'\',\n		\'link_id\'		=>	\'\',\n		\'link_rel\'		=>	\'\',\n		\'link_to\'		=>	\'\',\n		\'escape\'		=>	true,\n		\'onclick\'		=>	\'\',		// only for linked thumbnails\n	), $atts));\n\n	global $soo_image;\n\n	if ( $link_class or $link_id or $link_rel or $onclick or $link_to )\n		$link = true;\n\n	if ( $link )\n		$thumbnail = true;\n\n	if ( $name or $id )\n		$image = new soo_txp_img($name ? $name : $id);\n	else\n		$image = _soo_image_by_context();\n			// false if image context is empty\n			// otherwise a soo_txp_img or soo_html_img object\n			// if soo_html_img it will be URL only, no other properties\n\n	if ( $image instanceof soo_html_img and $thumbnail )\n		return false;\n\n	if ( $image instanceof soo_txp_img ) {\n		if ( $thumbnail and !$image->thumbnail )\n			return false;\n		$image_data = $image;\n		$image = new soo_html_img($image_data, $thumbnail, $escape);\n		if ( $title )\n		{\n			$tokens = array(\'{alt}\', \'{caption}\');\n			$replace = array($image->alt, $image->title);\n			if ( strpos($title, \'{author}\') !== false )\n			{\n				$tokens[] = \'{author}\';\n				$replace[] = get_author_name($image_data->author);\n			}\n			$title = str_replace($tokens, $replace, $title);\n		}\n	}\n\n	if ( ! ( $image instanceof soo_html_img ) )\n		return false;\n\n// Standard height and width attributes (i.e., actual size in pixels)\n// will be added to the img tag if the default_dimensions preference is true\n// and has not been overriden (by specifying height or width other than 1),\n// or if either attribute has been set to 1. Otherwise, height and width\n// attributes will be passed through as is (in which case a value of 0\n// or empty will result in no attribute in the img tag).\n\n	if ( $width === \'0\' or $height === \'0\' or $width > 1 or $height > 1 )\n		$soo_image[\'default_dimensions\'] = false;\n\n	if ( ! $soo_image[\'default_dimensions\'] and $height != 1 and $width != 1 )\n		$image->height($height)->width($width);\n\n	$image->class($class)\n		->id($html_id)\n		->title($title);\n\n	// Shadowbox-specific block to create galleries\n 	if ( strtolower($link_rel) == \"shadowbox\"\n 		and isset($soo_image[\'first_selection\'])\n 		and $soo_image[\'first_selection\'] instanceof soo_txp_img ) {\n 			$img_obj = $soo_image[\'first_selection\'];\n 			$link_rel .= \'[\' . $img_obj->id . \']\';\n	}\n\n	if ( $thumbnail and $link ) {\n		if ( $link_to )\n			$url = hu . $link_to . \'?\' . $soo_image[\'jump_param\']\n				. \'=\' . $image_data->id;\n		else\n			$url = $image_data->full_url;\n		$anchor = new soo_html_anchor($url, $image);\n		$anchor->title($title)\n			->onclick($onclick)\n			->rel($link_rel)\n			->class($link_class)\n			->id($link_id);\n	}\n\n	return isset($anchor) ? $anchor->tag() : $image->tag();\n}\n///////////////////////// end of soo_image //////////////////////////\n\nfunction soo_image_alt($atts) { return _soo_image_data(\'alt\', $atts); }\nfunction soo_image_author($atts) { return _soo_image_data(\'author\', $atts); }\nfunction soo_image_caption($atts) {	return _soo_image_data(\'caption\', $atts); }\nfunction soo_image_category($atts) { return _soo_image_data(\'category\', $atts); }\nfunction soo_image_date($atts) { return _soo_image_data(\'date\', $atts); }\nfunction soo_image_height($atts) { return _soo_image_data(\'h\', $atts); }\nfunction soo_image_id($atts) { return _soo_image_data(\'id\', $atts); }\nfunction soo_image_name($atts) { return _soo_image_data(\'name\', $atts); }\nfunction soo_image_url($atts) { return _soo_image_data(\'full_url\', $atts); }\nfunction soo_image_width($atts) { return _soo_image_data(\'w\', $atts); }\nfunction soo_thumbnail_height($atts) { return _soo_image_data(\'thumb_h\', $atts); }\nfunction soo_thumbnail_url($atts) { return _soo_image_data(\'thumb_url\', $atts); }\nfunction soo_thumbnail_width($atts) { return _soo_image_data(\'thumb_w\', $atts); }\n\nfunction _soo_image_data($field, $atts = array()) {\n	extract(lAtts(array(\n		\'format\'	=>	\'\',\n		\'escape\'	=>	1,\n		\'no_widow\'	=>	1,\n		\'wraptag\'	=> \'\',\n		\'class\'		=> \'\',\n		\'html_id\'	=> \'\',\n	), $atts));\n	$image = _soo_image_by_context();\n	if ( ! $image instanceof soo_txp_img )\n		$out = $field == \'url\' ? $image : \'\';\n	else\n		switch ( $field ) {\n			case \'alt\':\n				$out = $escape ? htmlspecialchars($image->alt) : $image->alt;\n				break;\n			case \'author\':\n				$out = get_author_name($image->author);\n				break;\n			case \'caption\':\n				$out = $escape ? htmlspecialchars($image->caption) : $image->caption;\n				$out = $no_widow ? preg_replace(\'/\\s+(\\S+)$/\', \'&nbsp;\\1\', $out) : $out;\n				break;\n			case \'date\':\n				global $dateformat;\n				$f = $format ? $format : $dateformat;\n				$utime = safe_strtotime($image->date);\n				$out = $image->date ? safe_strftime($f, $utime) : \'\';\n				break;\n			default:\n				$out = $image->$field;\n		}\n	return doWrap(array($out), $wraptag, \'\', $class, \'\', \'\', \'\', $html_id);\n}\n\nfunction soo_if_txp_image($atts, $thing) {\n\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img ));\n}\n\nfunction soo_if_image_author($atts, $thing) {\n\n	extract(lAtts(array(\'name\' => \'\'), $atts));\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and in_list($image->author, $name) ));\n}\n\nfunction soo_if_image_category($atts, $thing) {\n\n	extract(lAtts(array(\'name\' => \'\'), $atts));\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and in_list($image->category, $name) ));\n}\n\nfunction soo_if_image_thumbnail($atts, $thing) {\n\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and $image->thumbnail));\n}\n\nfunction soo_if_image_count($atts, $thing) {\n// Expected context:\n//   - Inside a soo_image_select tag/form, else\n//   - Inside an article/article_custom tag/form, else\n//   - On an image category page\n// Otherwise the tag returns empty.\n	extract(lAtts(array(\n		\'exact\'		=> null,\n		\'min\'		=> null,\n		\'max\'		=> null,\n	), $atts));\n	global $soo_image, $thisarticle, $context, $c;\n\n	if ( isset($soo_image[\'count\']) )	// inside a soo_image_select\n		$count = $soo_image[\'count\'];\n	elseif ( ! empty($thisarticle) )\n		$count = $thisarticle[\'article_image\'] ? count(do_list($thisarticle[\'article_image\'])) : 0;\n	elseif ( $context == gTxt(\'image_context\') and ! empty($c) )\n		$count = safe_count(\'txp_image\', \'category = \"\' . $c . \'\"\');\n	else return;\n\n	if ( ! is_null($exact) )\n		$condition = $count == $exact;\n	elseif ( ! is_null($min) )\n		$condition = $count >= $min;\n	elseif ( ! is_null($max) )\n		$condition = $count <= $max;\n	else return;\n\n	return parse(EvalElse($thing, $condition));\n}\n\nfunction soo_image_next($atts, $thing = null) {\n\n	extract(lAtts(array(\n		\'link_text\'		=> \'&rarr;\',\n		\'class\'			=> \'\',\n		\'html_id\'		=> \'\',\n	), $atts));\n	global $soo_image;\n\n	if ( ! isset($soo_image[\'next\']) )\n		return soo_util::secondpass(__FUNCTION__, $atts, $thing);\n	$uri = new soo_uri;\n	$uri->set_query_param($soo_image[\'jump_param\']);\n	$uri->set_query_param($soo_image[\'page_param\'], $soo_image[\'next\']);\n	$out = new soo_html_anchor($uri->full, $thing ? $thing : $link_text);\n	return $out->class($class)->id($html_id)->tag();\n}\n\nfunction soo_image_prev($atts, $thing = null) {\n\n	extract(lAtts(array(\n		\'link_text\'		=> \'&larr;\',\n		\'class\'			=> \'\',\n		\'html_id\'		=> \'\',\n	), $atts));\n	global $soo_image;\n\n	$prev = intval(gps($soo_image[\'page_param\'])) - 1;\n	if ( $prev < 1 )\n		return soo_util::secondpass(__FUNCTION__, $atts, $thing);\n	$uri = new soo_uri;\n	$uri->set_query_param($soo_image[\'jump_param\']);\n	$uri->set_query_param($soo_image[\'page_param\'], $prev > 1 ? $prev : null);\n	$out = new soo_html_anchor($uri->full, $thing ? $thing : $link_text);\n	return $out->class($class)->id($html_id)->tag();\n}\n\nfunction soo_image_page_count($atts) {\n\n	extract(lAtts(array(\n		\'format\' 		=>	\'Page {current} of {total}\',\n		\'showalways\'	=>	false,\n	), $atts));\n	global $soo_image;\n\n	if ( ! isset($soo_image[\'this_page\']) )\n		return soo_util::secondpass(__FUNCTION__, $atts);\n\n	if ( ! $showalways and $soo_image[\'total_pages\'] <= 1 ) return;\n\n	return str_replace(array(\'{current}\', \'{total}\'),\n		array($soo_image[\'this_page\'], $soo_image[\'total_pages\']), $format);\n}\n\nfunction soo_exif($atts, $thing) {\n\n	global $dateformat, $soo_exif_field, $soo_exif_value;\n	extract(lAtts(array(\n		\'field\'		=>	\'\',\n		\'format\'	=>	\'{field}: {value}\',\n		\'wraptag\'	=>	\'\',\n		\'break\'		=>	\'\',\n	), $atts));\n\n	$display = array(\n		\'Model\'					=>	\'Camera\',\n		\'ExposureTime\'			=>	\'Shutter speed\',\n		\'FNumber\'				=>	\'F-stop\',\n		\'ISOSpeedRatings\'		=>	\'ISO speed\',\n		\'DateTimeOriginal\'		=>	\'Exposure date\',\n		\'FocalLength\'			=>	\'Focal length\',\n		\'CropFactor\'			=>	\'Crop factor\',\n		\'FOV\'					=>	\'Computed FOV\',\n		\'ImageHistory\'			=>	\'History\',\n		);\n\n	$shortcuts = array(\n		\'model\'					=>	\'Model\',\n		\'exp-time\'				=>	\'ExposureTime\',\n		\'f-number\'				=>	\'FNumber\',\n		\'iso-speed\'				=>	\'ISOSpeedRatings\',\n		\'date-taken\'			=>	\'DateTimeOriginal\',\n		\'focal-len\'				=>	\'FocalLength\',\n		\'crop\'					=>	\'CropFactor\',\n		\'history\'				=>	\'ImageHistory\',\n	);\n\n	if ( empty($field) )\n		$fields = array_keys($display);\n\n	else {\n		$fields = do_list($field);\n		foreach ( $fields as $i => $f )\n			if ( array_key_exists(strtolower($f), $shortcuts) )\n				$fields[$i] = $shortcuts[$f];\n	}\n\n	$exif = exif_read_data(_soo_image_path());\n\n	extract($exif);\n\n	if ( isset($FNumber) )\n		$exif[\'FNumber\'] = \'&fnof;/\' . round(_soo_image_convert_ratio($FNumber), 1);\n\n	if ( isset($FocalLength) )\n		$exif[\'FocalLength\'] = _soo_image_convert_ratio($FocalLength) . \'mm\';\n\n	if ( isset($FocalLengthIn35mmFilm) ) {\n		$fl35 = _soo_image_convert_ratio($FocalLengthIn35mmFilm);\n		if ( isset($FocalLength) )\n			$exif[\'CropFactor\'] = round($fl35 / _soo_image_convert_ratio($FocalLength), 1);\n		$exif[\'FocalLengthIn35mmFilm\'] = $fl35 . \'mm\';\n		// 43.27 is the diagonal length (mm) of a full-frame sensor\n		$exif[\'FOV\'] = round(rad2deg(2 * atan(43.27 / ( 2 * $fl35 ))), 1) . \'&deg;\';\n	}\n\n	if ( isset($DateTimeOriginal) )\n		$exif[\'DateTimeOriginal\'] =\n			safe_strftime($dateformat, strtotime($DateTimeOriginal));\n\n	if ( strtolower($fields[0]) == \'dump\' )\n		$fields = array_keys($exif);\n\n	foreach ( $fields as $k ) {\n		if ( isset($exif[$k]) ) {\n			$v = $exif[$k];\n			$soo_exif_field = isset($display[$k]) ? $display[$k] : $k;\n			$soo_exif_value = is_array($v) ? implode(\' \', $v) : trim($v);\n			$r_format = str_replace(\'{field}\', $soo_exif_field, $format);\n			$r_format = str_replace(\'{value}\', $soo_exif_value, $r_format);\n			$out[] = $thing ? parse($thing) : $r_format;\n		}\n	}\n	return isset($out) ? doWrap($out, $wraptag, $break) : \'\';\n}\n\nfunction soo_exif_field($atts) {\n\n	global $soo_exif_field;\n	extract(lAtts(array(\n		\'wraptag\'	=>	\'\',\n	), $atts));\n\n	if ( $soo_exif_field )\n		return doWrap(array($soo_exif_field), $wraptag, \'\');\n}\n\nfunction soo_exif_value($atts) {\n\n	global $soo_exif_value;\n	extract(lAtts(array(\n		\'wraptag\'	=>	\'\',\n	), $atts));\n\n	if ( $soo_exif_value ) {\n		if ( ! is_array($soo_exif_value) )\n			$soo_exif_value = array($soo_exif_value);\n		return doWrap($soo_exif_value, $wraptag, \'\');\n	}\n}\n\n  //---------------------------------------------------------------------//\n //							Support Functions							//\n//---------------------------------------------------------------------//\n\nfunction _soo_image_path() {\n\n	global $img_dir;\n\n	$img_dir_path = $_SERVER[\'DOCUMENT_ROOT\'] . \"/$img_dir/\";\n	$image = _soo_image_by_context();\n\n	if ( $image instanceof soo_txp_img )\n		$file_path = $img_dir_path . $image->id . $image->ext;\n\n	elseif ( $image instanceof soo_html_img ) {\n		$pattern = \'&^(\' . str_replace(\'.\', \'\\.\', hu) . \"|\\/)($img_dir.+)&\";\n		if ( preg_match($pattern, $image->src, $match) )\n			$file_path = $match[2];\n	}\n\n	if ( empty($file_path) or ! file_exists($file_path) )\n		return null;\n\n	return $file_path;\n}\n\nfunction _soo_article_image_list() {\n	global $thisarticle;\n	return empty($thisarticle[\'article_image\']) ?\n		array() : _soo_range_to_list($thisarticle[\'article_image\']);\n}\n\nfunction _soo_range_to_list( $csv ) {\n\n	$items = do_list($csv);\n	foreach ( $items as $item )\n		if ( preg_match(\'/(\\d+)(:|-)(\\d+)/\', $item, $match) ) {\n			if ( $match[3] > $match[1] )\n				for ( $i = $match[1]; $i <= $match[3]; $i++ )\n					$out[] = $i;\n			else\n				for ( $i = $match[1]; $i >= $match[3]; $i-- )\n					$out[] = $i;\n		}\n		else\n			$out[] = $item;\n	return $out;\n}\n\nfunction _soo_valid_ext( $in ) {\n	$ok = array(\'.jpg\', \'.jpeg\', \'.gif\', \'.png\', \'.bmp\');\n	$exts = is_array($in) ? $in : do_list($in);\n	$out = array();\n	foreach ($exts as $ext) {\n		$ext = \'.\' . ltrim($ext, \'.\');\n		if ( in_array($ext, $ok) )\n			$out[] = $ext;\n	}\n	return $out;\n}\n\nfunction _soo_image_convert_ratio( $r ) {\n	if ( is_numeric($r) )\n		return $r;\n	if ( ! preg_match(\'/(.+)(:|\\/)(.+)/\', $r, $match) )\n		return null;\n	if ( is_numeric($match[1]) and is_numeric($match[3]) and $match[3] != 0 )\n		return $match[1] / $match[3];\n	else\n		return null;\n}\n\nfunction _soo_image_aspect_ratio( $query, $aspect_ratio ) {\n\n	$fudge = 0.01;\n	$pattern = \'/^(\\+|-)?([^-]+)(-)?(.+)?$/\';\n	if ( ! preg_match($pattern, $aspect_ratio, $match) )\n		return;\n\n	if ( $match[1] == \'+\' ) {\n		$min_ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($min_ar) )\n			return;\n	}\n	elseif ( $match[1] == \'-\' ) {\n		$max_ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($max_ar) )\n			return;\n	}\n\n	elseif ( isset($match[3]) ) {\n		$min_ar = _soo_image_convert_ratio($match[2]);\n		$max_ar = _soo_image_convert_ratio($match[4]);\n		if ( is_null($min_ar) or is_null($max_ar) )\n			return;\n		if ( $max_ar < $min_ar )\n			list($max_ar, $min_ar) = array($min_ar, $max_ar);\n	}\n	else {\n		$ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($ar) )\n			return;\n		$query->where(\'w/h\', $ar + $fudge, \'<\')\n			->where(\'w/h\', $ar - $fudge, \'>\');\n		return;\n	}\n	if ( isset($min_ar) )\n		$query->where(\'w/h\', $min_ar - $fudge, \'>=\');\n	if ( isset($max_ar) )\n		$query->where(\'w/h\', $max_ar + $fudge, \'<=\');\n\n}\n\nfunction _soo_image_by_context( ) {\n// Return a single image object (Txp or Html) for the current image context.\n	// Image passed by parent soo_image_select tag, or;\n	// first article image, or;\n	// first selection from previous soo_image_select tag (subject to prefs).\n// Return false if image context is empty\n\n	global $soo_image;\n\n	$_soo_article_image_list = _soo_article_image_list();\n	$article_image = array_shift($_soo_article_image_list);\n\n	if ( isset($soo_image[\'data_obj\']) )\n		$img = $soo_image[\'data_obj\'];\n\n	elseif ( isset($soo_image[\'url\']) )\n		$img = $soo_image[\'url\'];\n\n	elseif ( $article_image )\n		$img = $article_image;\n\n	elseif ( ! empty($soo_image[\'first_selection\']) )\n		$img = $soo_image[\'first_selection\'];\n\n	if ( empty($img) ) return false;\n	if ( is_object($img) ) return $img;\n	if ( is_numeric($img) ) return new soo_txp_img($img);\n	return new soo_html_img(array(\'src\' => $img));\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_image_select\')\n		->register(\'soo_image\')\n		->register(\'soo_image_alt\')\n		->register(\'soo_image_author\')\n		->register(\'soo_image_caption\')\n		->register(\'soo_image_category\')\n		->register(\'soo_image_date\')\n		->register(\'soo_image_height\')\n		->register(\'soo_image_id\')\n		->register(\'soo_image_name\')\n		->register(\'soo_image_url\')\n		->register(\'soo_image_width\')\n		->register(\'soo_image_thumbnail_height\')\n		->register(\'soo_image_thumbnail_url\')\n		->register(\'soo_image_thumbnail_width\')\n		->register(\'soo_if_txp_image\')\n		->register(\'soo_if_image_author\')\n		->register(\'soo_if_image_category\')\n		->register(\'soo_if_image_thumbnail\')\n		->register(\'soo_if_image_count\')\n		->register(\'soo_image_next\')\n		->register(\'soo_image_prev\')\n		->register(\'soo_image_page_count\')\n		->register(\'soo_exif\')\n		->register(\'soo_exif_field\')\n		->register(\'soo_exif_value\')\n		;\n}\n\nrequire_plugin(\'soo_txp_obj\');\n@require_plugin(\'soo_plugin_pref\');		// optional\n\n// Plugin init not needed on admin side\nif ( @txpinterface == \'public\' )\n{\n	global $soo_image;\n	$soo_image = function_exists(\'soo_plugin_pref_vals\') ?\n		array_merge(soo_image_defaults(true), soo_plugin_pref_vals(\'soo_image\'))\n		: soo_image_defaults(true);\n	$soo_image[\'page_param\'] = \'soo_img_pg\';\n	$soo_image[\'jump_param\'] = \'soo_img_jp\';\n}\nelseif ( @txpinterface == \'admin\' )\n{\n	add_privs(\'plugin_prefs.soo_image\',\'1,2\');\n	add_privs(\'plugin_lifecycle.soo_image\',\'1,2\');\n	register_callback(\'soo_image_prefs\', \'plugin_prefs.soo_image\');\n	register_callback(\'soo_image_prefs\', \'plugin_lifecycle.soo_image\');\n}\n\nfunction soo_image_prefs( $event, $step ) {\n	if ( function_exists(\'soo_plugin_pref\') )\n		return soo_plugin_pref($event, $step, soo_image_defaults());\n	if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n		$plugin = substr($event, 12);\n		$message = \'<p><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n			gTxt(\'edit_preferences\') . \':</strong><br />\' . gTxt(\'install_plugin\')\n			. \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></p>\';\n		pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n	}\n}\n\nfunction soo_image_defaults( $vals_only = false ) {\n	$defaults = array(\n		\'default_form\'			=>	array(\n			\'val\'	=>	\'soo_image\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Default image form\',\n		),\n		\'default_listform\'		=>	array(\n			\'val\'	=>	\'\',\n			\'html\'	=>	\'text_input\',\n			\'text\'	=>	\'Default image list form\',\n		),\n		\'default_dimensions\'	=>	array(\n			\'val\'	=>	1,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Add height and width in pixels to img tag by default?\',\n		),\n		\'persistent_context\'	=>	array(\n			\'val\'	=>	1,\n			\'html\'	=>	\'yesnoradio\',\n			\'text\'	=>	\'Use persistent context for tags outside an image form?\',\n		),\n	);\n	if ( $vals_only )\n		foreach ( $defaults as $name => $arr )\n			$defaults[$name] = $arr[\'val\'];\n	return $defaults;\n}\n\n  //---------------------------------------------------------------------//\n //									Tags								//\n//---------------------------------------------------------------------//\n\nfunction soo_image_select( $atts, $thing = null ) {\n\n// Controller for the soo_image plugin.\n//\n// Determine what images the user is requesting, fetch image data objects\n// (or raw URLs, as appropriate) for those images, and pass this along\n// to an image form (i.e., a form called by this tag, containing one or\n// more of soo_image\'s output tags) for display.\n\n	global $soo_image, $thisarticle, $context, $c;\n\n	extract(lAtts(array(\n		\'aspect_ratio\'	=>	\'\',\n		\'author\'		=>	\'\',\n		\'break\'			=>	\'\',\n		\'category\'		=>	\'\',\n		\'class\'			=>	\'\',\n		\'ext\'			=>	\'\',\n		\'form\'			=>	$soo_image[\'default_form\'],		// required\n		\'html_id\'		=>	\'\',\n		\'id\'			=>	\'\',\n		\'limit\'			=>	0,\n		\'listform\'		=>	$soo_image[\'default_listform\'],\n		\'name\'			=>	\'\',\n		\'pagination\'	=>	true,\n		\'persistent_context\'	=> $soo_image[\'persistent_context\'],\n		\'sort\'			=>	\'name asc\',		// accepts lists, e.g. \"a asc,b,c desc\"\n		\'wraptag\'		=>	\'\',\n	), $atts));\n\n	$page = intval(gps($soo_image[\'page_param\']));\n	if ( $page < 1 ) $page = 1;\n	$offset = $pagination ? ($page - 1) * $limit : 0;\n\n	$jump_to = intval(gps($soo_image[\'jump_param\']));\n\n	$query = new soo_txp_select(\'txp_image\');\n\n// Selection priority: name; id; (category|author|ext|aspect_ratio); article image.\n// Comma-separated lists allowed for all criteria; preserve list order for name|id.\n\n	if ( $name or $id ) {\n		$sort = \'\';\n		if ( $name )\n			$query->in(\'name\', $name)->order_by_field(\'name\', $name);\n		else\n			$ids = _soo_range_to_list($id);\n	}\n\n	elseif ( $category or $author or $ext or $aspect_ratio ) {\n		if ( $category ) $query->in(\'category\', $category);\n		if ( $author ) $query->in(\'author\', $author);\n		if ( $ext ) {\n			if ( $ext = _soo_valid_ext($ext) )\n				$query->in(\'ext\', $ext);\n			else\n				return;\n		}\n		if ( $aspect_ratio ) _soo_image_aspect_ratio($query, trim($aspect_ratio));\n	}\n\n	elseif ( ! empty($thisarticle) ) {\n		$image_array = _soo_article_image_list();\n		if ( ! $image_array )\n			return false;\n		foreach ( $image_array as $img ) {\n			if ( is_numeric($img) )\n				$ids[] = $img;\n			else\n				$urls[] = $img;\n		}\n	}\n\n	elseif ( $context == gTxt(\'image_context\') and $c ) {\n		$query->in(\'category\', $c);\n	}\n\n	else return;\n\n	if ( isset($ids) ) {\n		$sort = \'\';\n		$query->in(\'id\', $ids)->order_by_field(\'id\', $ids);\n	}\n\n	$full_count = $query->count();\n	if ( $pagination && $full_count <= $offset ) {		// in case of direct URI manipulation and invalid #\n		$page = $limit ? ceil($full_count / $limit) : 1;	// go to last page\n		$offset = ($page - 1) * $limit;\n	}\n\n	$soo_image[\'this_page\'] = $page;\n	if ( $pagination ) $soo_image[\'total_pages\'] = $limit ? ceil($full_count / $limit) : 1;\n\n	if ( $jump_to and is_numeric($jump_to) ) {\n		$uri = new soo_uri;\n		$uri->set_query_param($soo_image[\'jump_param\']);\n		$all_ids = new soo_txp_rowset($query->order_by($sort));\n		$all_ids = array_flip($all_ids->field_vals(\'id\'));\n		if ( isset($all_ids[$jump_to]) ) {\n			$jump_index = $all_ids[$jump_to];\n			if ( $limit ) {\n				$page = ceil(($jump_index + 1) / $limit);\n				$offset = ($page - 1) * $limit;\n				$_POST[$soo_image[\'page_param\']] = $page;\n			}\n		}\n	}\n\n	$query->order_by($sort)->limit($limit)->offset($offset);\n	$rs = new soo_txp_rowset($query);\n	unset($query);\n\n	if ( isset($urls) )\n		foreach ( $image_array as $i => $img ) {\n			if ( isset($rs->rows[$img]) )\n				$image_array[$i] = $rs->rows[$img];\n			elseif ( ! in_array($img, $urls) )\n				unset($image_array[$i]);\n		}\n	else\n		$image_array = $rs->rows;\n\n	if ( empty($image_array) )\n		return false;\n\n	$soo_image[\'count\'] = count($image_array);\n\n	if ( $pagination && $limit && ( $offset + $limit < $full_count ) )\n		$soo_image[\'next\'] = $page + 1;\n\n	// $image_array now contains data objects and/or presumed URLs\n\n	if ( ( $soo_image[\'count\'] > 1 or $page > 1) and $listform )\n		$form = $listform;\n\n	$soo_image[\'first_selection\'] = isset($jump_index) ? new soo_txp_img($jump_to) : current($image_array);\n\n	foreach ( $image_array as $img ) {\n		if ( $img instanceof soo_txp_img )\n			$soo_image[\'data_obj\'] = $img;\n		else\n			$soo_image[\'url\'] = $img;\n		$parsed = is_null($thing) ? parse_form($form) : parse($thing);\n		if ( $parsed ) $out[] = $parsed;\n		unset($soo_image[\'data_obj\']);\n		unset($soo_image[\'url\']);\n	}\n\n	if ( ! $persistent_context )\n		unset($soo_image[\'first_selection\']);\n\n	unset($soo_image[\'count\']);\n\n	return isset($out) ? doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id) : false;\n\n}\n////////////////////// end of soo_image_select /////////////////////\n\nfunction soo_image( $atts ) {\n// Display an image. Selection by, in descending order of priority:\n	// the tag\'s \'name\' attribute;\n	// the tag\'s \'id\' attribute;\n	// the image context:\n		// id or url sent by $soo_image to an image form\n		// article image\n		// first selection from prior soo_image_select (subject to pref setting)\n\n	extract(lAtts(array(\n		\'id\'			=>	\'\',\n		\'name\'			=>	\'\',\n		\'thumbnail\'		=>	false,\n		\'height\'		=>	\'\',\n		\'width\'			=>	\'\',\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n		\'title\'			=>	\'{caption}\',\n		\'link\'			=>	false,\n		\'link_class\'	=>	\'\',\n		\'link_id\'		=>	\'\',\n		\'link_rel\'		=>	\'\',\n		\'link_to\'		=>	\'\',\n		\'escape\'		=>	true,\n		\'onclick\'		=>	\'\',		// only for linked thumbnails\n	), $atts));\n\n	global $soo_image;\n\n	if ( $link_class or $link_id or $link_rel or $onclick or $link_to )\n		$link = true;\n\n	if ( $link )\n		$thumbnail = true;\n\n	if ( $name or $id )\n		$image = new soo_txp_img($name ? $name : $id);\n	else\n		$image = _soo_image_by_context();\n			// false if image context is empty\n			// otherwise a soo_txp_img or soo_html_img object\n			// if soo_html_img it will be URL only, no other properties\n\n	if ( $image instanceof soo_html_img and $thumbnail )\n		return false;\n\n	if ( $image instanceof soo_txp_img ) {\n		if ( $thumbnail and !$image->thumbnail )\n			return false;\n		$image_data = $image;\n		$image = new soo_html_img($image_data, $thumbnail, $escape);\n		if ( $title )\n		{\n			$tokens = array(\'{alt}\', \'{caption}\');\n			$replace = array($image->alt, $image->title);\n			if ( strpos($title, \'{author}\') !== false )\n			{\n				$tokens[] = \'{author}\';\n				$replace[] = get_author_name($image_data->author);\n			}\n			$title = str_replace($tokens, $replace, $title);\n		}\n	}\n\n	if ( ! ( $image instanceof soo_html_img ) )\n		return false;\n\n// Standard height and width attributes (i.e., actual size in pixels)\n// will be added to the img tag if the default_dimensions preference is true\n// and has not been overriden (by specifying height or width other than 1),\n// or if either attribute has been set to 1. Otherwise, height and width\n// attributes will be passed through as is (in which case a value of 0\n// or empty will result in no attribute in the img tag).\n\n	if ( $width === \'0\' or $height === \'0\' or $width > 1 or $height > 1 )\n		$soo_image[\'default_dimensions\'] = false;\n\n	if ( ! $soo_image[\'default_dimensions\'] and $height != 1 and $width != 1 )\n		$image->height($height)->width($width);\n\n	$image->class($class)\n		->id($html_id)\n		->title($title);\n\n	// Shadowbox-specific block to create galleries\n 	if ( strtolower($link_rel) == \"shadowbox\"\n 		and isset($soo_image[\'first_selection\'])\n 		and $soo_image[\'first_selection\'] instanceof soo_txp_img ) {\n 			$img_obj = $soo_image[\'first_selection\'];\n 			$link_rel .= \'[\' . $img_obj->id . \']\';\n	}\n\n	if ( $thumbnail and $link ) {\n		if ( $link_to )\n			$url = hu . $link_to . \'?\' . $soo_image[\'jump_param\']\n				. \'=\' . $image_data->id;\n		else\n			$url = $image_data->full_url;\n		$anchor = new soo_html_anchor($url, $image);\n		$anchor->title($title)\n			->onclick($onclick)\n			->rel($link_rel)\n			->class($link_class)\n			->id($link_id);\n	}\n\n	return isset($anchor) ? $anchor->tag() : $image->tag();\n}\n///////////////////////// end of soo_image //////////////////////////\n\nfunction soo_image_alt($atts) { return _soo_image_data(\'alt\', $atts); }\nfunction soo_image_author($atts) { return _soo_image_data(\'author\', $atts); }\nfunction soo_image_caption($atts) {	return _soo_image_data(\'caption\', $atts); }\nfunction soo_image_category($atts) { return _soo_image_data(\'category\', $atts); }\nfunction soo_image_date($atts) { return _soo_image_data(\'date\', $atts); }\nfunction soo_image_height($atts) { return _soo_image_data(\'h\', $atts); }\nfunction soo_image_id($atts) { return _soo_image_data(\'id\', $atts); }\nfunction soo_image_name($atts) { return _soo_image_data(\'name\', $atts); }\nfunction soo_image_url($atts) { return _soo_image_data(\'full_url\', $atts); }\nfunction soo_image_width($atts) { return _soo_image_data(\'w\', $atts); }\nfunction soo_thumbnail_height($atts) { return _soo_image_data(\'thumb_h\', $atts); }\nfunction soo_thumbnail_url($atts) { return _soo_image_data(\'thumb_url\', $atts); }\nfunction soo_thumbnail_width($atts) { return _soo_image_data(\'thumb_w\', $atts); }\n\nfunction _soo_image_data($field, $atts = array()) {\n	extract(lAtts(array(\n		\'format\'	=>	\'\',\n		\'escape\'	=>	1,\n		\'no_widow\'	=>	1,\n		\'wraptag\'	=> \'\',\n		\'class\'		=> \'\',\n		\'html_id\'	=> \'\',\n	), $atts));\n	$image = _soo_image_by_context();\n	if ( ! $image instanceof soo_txp_img )\n		$out = $field == \'url\' ? $image : \'\';\n	else\n		switch ( $field ) {\n			case \'alt\':\n				$out = $escape ? htmlspecialchars($image->alt) : $image->alt;\n				break;\n			case \'author\':\n				$out = get_author_name($image->author);\n				break;\n			case \'caption\':\n				$out = $escape ? htmlspecialchars($image->caption) : $image->caption;\n				$out = $no_widow ? preg_replace(\'/\\s+(\\S+)$/\', \'&nbsp;\\1\', $out) : $out;\n				break;\n			case \'date\':\n				global $dateformat;\n				$f = $format ? $format : $dateformat;\n				$utime = safe_strtotime($image->date);\n				$out = $image->date ? safe_strftime($f, $utime) : \'\';\n				break;\n			default:\n				$out = $image->$field;\n		}\n	return doWrap(array($out), $wraptag, \'\', $class, \'\', \'\', \'\', $html_id);\n}\n\nfunction soo_if_txp_image($atts, $thing) {\n\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img ));\n}\n\nfunction soo_if_image_author($atts, $thing) {\n\n	extract(lAtts(array(\'name\' => \'\'), $atts));\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and in_list($image->author, $name) ));\n}\n\nfunction soo_if_image_category($atts, $thing) {\n\n	extract(lAtts(array(\'name\' => \'\'), $atts));\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and in_list($image->category, $name) ));\n}\n\nfunction soo_if_image_thumbnail($atts, $thing) {\n\n	$image = _soo_image_by_context();\n	return parse(EvalElse($thing, $image instanceof soo_txp_img and $image->thumbnail));\n}\n\nfunction soo_if_image_count($atts, $thing) {\n// Expected context:\n//   - Inside a soo_image_select tag/form, else\n//   - Inside an article/article_custom tag/form, else\n//   - On an image category page\n// Otherwise the tag returns empty.\n	extract(lAtts(array(\n		\'exact\'		=> null,\n		\'min\'		=> null,\n		\'max\'		=> null,\n	), $atts));\n	global $soo_image, $thisarticle, $context, $c;\n\n	if ( isset($soo_image[\'count\']) )	// inside a soo_image_select\n		$count = $soo_image[\'count\'];\n	elseif ( ! empty($thisarticle) )\n		$count = $thisarticle[\'article_image\'] ? count(do_list($thisarticle[\'article_image\'])) : 0;\n	elseif ( $context == gTxt(\'image_context\') and ! empty($c) )\n		$count = safe_count(\'txp_image\', \'category = \"\' . $c . \'\"\');\n	else return;\n\n	if ( ! is_null($exact) )\n		$condition = $count == $exact;\n	elseif ( ! is_null($min) )\n		$condition = $count >= $min;\n	elseif ( ! is_null($max) )\n		$condition = $count <= $max;\n	else return;\n\n	return parse(EvalElse($thing, $condition));\n}\n\nfunction soo_image_next($atts, $thing = null) {\n\n	extract(lAtts(array(\n		\'link_text\'		=> \'&rarr;\',\n		\'class\'			=> \'\',\n		\'html_id\'		=> \'\',\n	), $atts));\n	global $soo_image;\n\n	if ( ! isset($soo_image[\'next\']) )\n		return soo_util::secondpass(__FUNCTION__, $atts, $thing);\n	$uri = new soo_uri;\n	$uri->set_query_param($soo_image[\'jump_param\']);\n	$uri->set_query_param($soo_image[\'page_param\'], $soo_image[\'next\']);\n	$out = new soo_html_anchor($uri->full, $thing ? $thing : $link_text);\n	return $out->class($class)->id($html_id)->tag();\n}\n\nfunction soo_image_prev($atts, $thing = null) {\n\n	extract(lAtts(array(\n		\'link_text\'		=> \'&larr;\',\n		\'class\'			=> \'\',\n		\'html_id\'		=> \'\',\n	), $atts));\n	global $soo_image;\n\n	$prev = intval(gps($soo_image[\'page_param\'])) - 1;\n	if ( $prev < 1 )\n		return soo_util::secondpass(__FUNCTION__, $atts, $thing);\n	$uri = new soo_uri;\n	$uri->set_query_param($soo_image[\'jump_param\']);\n	$uri->set_query_param($soo_image[\'page_param\'], $prev > 1 ? $prev : null);\n	$out = new soo_html_anchor($uri->full, $thing ? $thing : $link_text);\n	return $out->class($class)->id($html_id)->tag();\n}\n\nfunction soo_image_page_count($atts) {\n\n	extract(lAtts(array(\n		\'format\' 		=>	\'Page {current} of {total}\',\n		\'showalways\'	=>	false,\n	), $atts));\n	global $soo_image;\n\n	if ( ! isset($soo_image[\'this_page\']) )\n		return soo_util::secondpass(__FUNCTION__, $atts);\n\n	if ( ! $showalways and $soo_image[\'total_pages\'] <= 1 ) return;\n\n	return str_replace(array(\'{current}\', \'{total}\'),\n		array($soo_image[\'this_page\'], $soo_image[\'total_pages\']), $format);\n}\n\nfunction soo_exif($atts, $thing) {\n\n	global $dateformat, $soo_exif_field, $soo_exif_value;\n	extract(lAtts(array(\n		\'field\'		=>	\'\',\n		\'format\'	=>	\'{field}: {value}\',\n		\'wraptag\'	=>	\'\',\n		\'break\'		=>	\'\',\n	), $atts));\n\n	$display = array(\n		\'Model\'					=>	\'Camera\',\n		\'ExposureTime\'			=>	\'Shutter speed\',\n		\'FNumber\'				=>	\'F-stop\',\n		\'ISOSpeedRatings\'		=>	\'ISO speed\',\n		\'DateTimeOriginal\'		=>	\'Exposure date\',\n		\'FocalLength\'			=>	\'Focal length\',\n		\'CropFactor\'			=>	\'Crop factor\',\n		\'FOV\'					=>	\'Computed FOV\',\n		\'ImageHistory\'			=>	\'History\',\n		);\n\n	$shortcuts = array(\n		\'model\'					=>	\'Model\',\n		\'exp-time\'				=>	\'ExposureTime\',\n		\'f-number\'				=>	\'FNumber\',\n		\'iso-speed\'				=>	\'ISOSpeedRatings\',\n		\'date-taken\'			=>	\'DateTimeOriginal\',\n		\'focal-len\'				=>	\'FocalLength\',\n		\'crop\'					=>	\'CropFactor\',\n		\'history\'				=>	\'ImageHistory\',\n	);\n\n	if ( empty($field) )\n		$fields = array_keys($display);\n\n	else {\n		$fields = do_list($field);\n		foreach ( $fields as $i => $f )\n			if ( array_key_exists(strtolower($f), $shortcuts) )\n				$fields[$i] = $shortcuts[$f];\n	}\n\n	$exif = exif_read_data(_soo_image_path());\n\n	extract($exif);\n\n	if ( isset($FNumber) )\n		$exif[\'FNumber\'] = \'&fnof;/\' . round(_soo_image_convert_ratio($FNumber), 1);\n\n	if ( isset($FocalLength) )\n		$exif[\'FocalLength\'] = _soo_image_convert_ratio($FocalLength) . \'mm\';\n\n	if ( isset($FocalLengthIn35mmFilm) ) {\n		$fl35 = _soo_image_convert_ratio($FocalLengthIn35mmFilm);\n		if ( isset($FocalLength) )\n			$exif[\'CropFactor\'] = round($fl35 / _soo_image_convert_ratio($FocalLength), 1);\n		$exif[\'FocalLengthIn35mmFilm\'] = $fl35 . \'mm\';\n		// 43.27 is the diagonal length (mm) of a full-frame sensor\n		$exif[\'FOV\'] = round(rad2deg(2 * atan(43.27 / ( 2 * $fl35 ))), 1) . \'&deg;\';\n	}\n\n	if ( isset($DateTimeOriginal) )\n		$exif[\'DateTimeOriginal\'] =\n			safe_strftime($dateformat, strtotime($DateTimeOriginal));\n\n	if ( strtolower($fields[0]) == \'dump\' )\n		$fields = array_keys($exif);\n\n	foreach ( $fields as $k ) {\n		if ( isset($exif[$k]) ) {\n			$v = $exif[$k];\n			$soo_exif_field = isset($display[$k]) ? $display[$k] : $k;\n			$soo_exif_value = is_array($v) ? implode(\' \', $v) : trim($v);\n			$r_format = str_replace(\'{field}\', $soo_exif_field, $format);\n			$r_format = str_replace(\'{value}\', $soo_exif_value, $r_format);\n			$out[] = $thing ? parse($thing) : $r_format;\n		}\n	}\n	return isset($out) ? doWrap($out, $wraptag, $break) : \'\';\n}\n\nfunction soo_exif_field($atts) {\n\n	global $soo_exif_field;\n	extract(lAtts(array(\n		\'wraptag\'	=>	\'\',\n	), $atts));\n\n	if ( $soo_exif_field )\n		return doWrap(array($soo_exif_field), $wraptag, \'\');\n}\n\nfunction soo_exif_value($atts) {\n\n	global $soo_exif_value;\n	extract(lAtts(array(\n		\'wraptag\'	=>	\'\',\n	), $atts));\n\n	if ( $soo_exif_value ) {\n		if ( ! is_array($soo_exif_value) )\n			$soo_exif_value = array($soo_exif_value);\n		return doWrap($soo_exif_value, $wraptag, \'\');\n	}\n}\n\n  //---------------------------------------------------------------------//\n //							Support Functions							//\n//---------------------------------------------------------------------//\n\nfunction _soo_image_path() {\n\n	global $img_dir;\n\n	$img_dir_path = $_SERVER[\'DOCUMENT_ROOT\'] . \"/$img_dir/\";\n	$image = _soo_image_by_context();\n\n	if ( $image instanceof soo_txp_img )\n		$file_path = $img_dir_path . $image->id . $image->ext;\n\n	elseif ( $image instanceof soo_html_img ) {\n		$pattern = \'&^(\' . str_replace(\'.\', \'\\.\', hu) . \"|\\/)($img_dir.+)&\";\n		if ( preg_match($pattern, $image->src, $match) )\n			$file_path = $match[2];\n	}\n\n	if ( empty($file_path) or ! file_exists($file_path) )\n		return null;\n\n	return $file_path;\n}\n\nfunction _soo_article_image_list() {\n	global $thisarticle;\n	return empty($thisarticle[\'article_image\']) ?\n		array() : _soo_range_to_list($thisarticle[\'article_image\']);\n}\n\nfunction _soo_range_to_list( $csv ) {\n\n	$items = do_list($csv);\n	foreach ( $items as $item )\n		if ( preg_match(\'/(\\d+)(:|-)(\\d+)/\', $item, $match) ) {\n			if ( $match[3] > $match[1] )\n				for ( $i = $match[1]; $i <= $match[3]; $i++ )\n					$out[] = $i;\n			else\n				for ( $i = $match[1]; $i >= $match[3]; $i-- )\n					$out[] = $i;\n		}\n		else\n			$out[] = $item;\n	return $out;\n}\n\nfunction _soo_valid_ext( $in ) {\n	$ok = array(\'.jpg\', \'.jpeg\', \'.gif\', \'.png\', \'.bmp\');\n	$exts = is_array($in) ? $in : do_list($in);\n	$out = array();\n	foreach ($exts as $ext) {\n		$ext = \'.\' . ltrim($ext, \'.\');\n		if ( in_array($ext, $ok) )\n			$out[] = $ext;\n	}\n	return $out;\n}\n\nfunction _soo_image_convert_ratio( $r ) {\n	if ( is_numeric($r) )\n		return $r;\n	if ( ! preg_match(\'/(.+)(:|\\/)(.+)/\', $r, $match) )\n		return null;\n	if ( is_numeric($match[1]) and is_numeric($match[3]) and $match[3] != 0 )\n		return $match[1] / $match[3];\n	else\n		return null;\n}\n\nfunction _soo_image_aspect_ratio( $query, $aspect_ratio ) {\n\n	$fudge = 0.01;\n	$pattern = \'/^(\\+|-)?([^-]+)(-)?(.+)?$/\';\n	if ( ! preg_match($pattern, $aspect_ratio, $match) )\n		return;\n\n	if ( $match[1] == \'+\' ) {\n		$min_ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($min_ar) )\n			return;\n	}\n	elseif ( $match[1] == \'-\' ) {\n		$max_ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($max_ar) )\n			return;\n	}\n\n	elseif ( isset($match[3]) ) {\n		$min_ar = _soo_image_convert_ratio($match[2]);\n		$max_ar = _soo_image_convert_ratio($match[4]);\n		if ( is_null($min_ar) or is_null($max_ar) )\n			return;\n		if ( $max_ar < $min_ar )\n			list($max_ar, $min_ar) = array($min_ar, $max_ar);\n	}\n	else {\n		$ar = _soo_image_convert_ratio($match[2]);\n		if ( is_null($ar) )\n			return;\n		$query->where(\'w/h\', $ar + $fudge, \'<\')\n			->where(\'w/h\', $ar - $fudge, \'>\');\n		return;\n	}\n	if ( isset($min_ar) )\n		$query->where(\'w/h\', $min_ar - $fudge, \'>=\');\n	if ( isset($max_ar) )\n		$query->where(\'w/h\', $max_ar + $fudge, \'<=\');\n\n}\n\nfunction _soo_image_by_context( ) {\n// Return a single image object (Txp or Html) for the current image context.\n	// Image passed by parent soo_image_select tag, or;\n	// first article image, or;\n	// first selection from previous soo_image_select tag (subject to prefs).\n// Return false if image context is empty\n\n	global $soo_image;\n\n	$_soo_article_image_list = _soo_article_image_list();\n	$article_image = array_shift($_soo_article_image_list);\n\n	if ( isset($soo_image[\'data_obj\']) )\n		$img = $soo_image[\'data_obj\'];\n\n	elseif ( isset($soo_image[\'url\']) )\n		$img = $soo_image[\'url\'];\n\n	elseif ( $article_image )\n		$img = $article_image;\n\n	elseif ( ! empty($soo_image[\'first_selection\']) )\n		$img = $soo_image[\'first_selection\'];\n\n	if ( empty($img) ) return false;\n	if ( is_object($img) ) return $img;\n	if ( is_numeric($img) ) return new soo_txp_img($img);\n	return new soo_html_img(array(\'src\' => $img));\n}\n', '27454fd6d9a8fa08cdc36dd5a5eafc0e', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_modified', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.1.2', 'Tags for article modification dates', '<h1>soo_modified</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>.</p>\n\n<ul>\n	<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=35196\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/126/soo_modified\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_modified\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#soo_modified_author\">soo_modified_author</a></li>\n	<li><a href=\"#soo_if_modified\">soo_if_modified</a></li>\n	<li><a href=\"#soo_if_modified_author\">soo_if_modified_author</a></li>\n	<li><a href=\"#notes\">Notes</a></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h1>soo_modified</h1>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>Inspired by <a href=\"https://forum.textpattern.io/viewtopic.php?id=3710\">ob1_modified</a>, <strong>soo_modified</strong> includes tags for dealing with article modification information. These supplement the basic function offered by the core <a href=\"https://docs.textpattern.io/tags/modified\">modified</a> tag.</p>\n\n<h2 id=\"soo_modified_author\">soo_modified_author</h2>\n\n<p>Output the name of the author who most recently modified the current article.</p>\n\n<pre>&lt;txp:soo_modified_author /&gt;</pre>\n\n<h3>Attributes</h3>\n\n<ul>\n	<li><code>fullname</code> <em>(boolean)</em> Whether to output the author&#8217;s full name or id. <span class=\"default\">Default</span> 1, output the author&#8217;s full name.</li>\n</ul>\n\n<h2 id=\"soo_if_modified\">soo_if_modified</h2>\n\n<p>Conditional tag that compares the current article&#8217;s modification date to its posted date.</p>\n\n<pre>&lt;txp:soo_if_modified&gt;\n...\n&lt;txp:else /&gt;\n...\n&lt;/txp:soo_if_modified&gt;</pre>\n\n<h3>Attributes</h3>\n\n<ul>\n	<li><code>format</code> _(<a href=\"http://us3.php.net/manual/en/function.strftime.php%29_\">strftime() format string</a> Date format to use for comparing the dates. <span class=\"default\">Defaults</span> to site preference for date format in the current context.</li>\n</ul>\n\n<p>By using a date format that doesn&#8217;t include the time of day, this tag will evaluate to true only if the last-modification stamp is on a different day than the posted stamp.</p>\n\n<h2 id=\"soo_if_modified_author\">soo_if_modified_author</h2>\n\n<p>Conditional tag that compares the author who most recently modified the current article to the article&#8217;s author, or to the specified author.</p>\n\n<pre>&lt;txp:soo_if_modified_author&gt;\n...\n&lt;txp:else /&gt;\n...\n&lt;/txp:soo_if_modified_author&gt;</pre>\n\n<h3>Attributes</h3>\n\n<p>If neither attribute is used, the tag will compare the author who last modified the article to the author who posted it.</p>\n\n<ul>\n	<li><code>id</code> <em>(Txp author id)</em> Txp ID of author to compare. <span class=\"default\">Default</span> empty.</li>\n	<li><code>name</code> <em>(Txp author id)</em> Full name of author to compare. (This must exactly match a full name from the txp_users table.) <span class=\"default\">Default</span> empty.</li>\n</ul>\n\n<h2 id=\"notes\">Notes</h2>\n\n<p>Two of the tags, <code>soo_modified_author</code> and <code>soo_if_modified_author</code>, can generate one or more database queries. This is unlikely to have a noticeable effect on performance, except possibly for very large article lists. (The plugin uses static variables to minimize the number of calls, but each distinct article on the page still generates at least one call.)</p>\n\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>0.1.2 (2017/2/15)</h3>\n\n<ul>\n	<li>Textpattern 4.6 compatibility update</li>\n</ul>\n\n<h3>0.1.1 (2011/1/1)</h3>\n\n<ul>\n	<li>Performance improvements (fewer database queries) for certain situations:\n	<ul>\n		<li>When both <code>soo_if_modified_author</code> and <code>soo_modified_author</code> are called</li>\n		<li>When using <code>soo_if_modified_author</code> or <code>soo_modified_author</code> in an article list</li>\n	</ul></li>\n	</ul>\n\n<h3>0.1.0 (2010/12/31)</h3>\n\n<ul>\n	<li>Initial release</li>\n	<li><code>soo_modified_author</code></li>\n	<li><code>soo_if_modified</code></li>\n	<li><code>soo_if_modified_author</code></li>\n</ul>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_if_modified\')\n        ->register(\'soo_if_modified_author\')\n        ->register(\'soo_modified_author\')\n        ;\n}\n\nfunction soo_if_modified ($atts, $thing)\n{\n    assert_article();\n\n    extract(lAtts(array(\n        \'format\'    =>  \'\',\n    ), $atts));\n\n    if (! $format) {\n        global $id, $c, $pg, $dateformat, $archive_dateformat;\n        $format = ($id or $c or $pg) ? $archive_dateformat : $dateformat;\n    }\n\n    global $thisarticle;\n\n    $modified = safe_strftime($format, $thisarticle[\'modified\']);\n    $posted = safe_strftime($format, $thisarticle[\'posted\']);\n\n    return parse($thing, $modified != $posted);\n}\n\nfunction soo_if_modified_author ($atts, $thing)\n{\n    assert_article();\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'id\' => \'\',\n        \'name\' => \'\',\n    ), $atts));\n\n    static $users = array();\n\n    if ($name and (! $authorid = array_search($name, $users))) {\n        $user_id = fetch(\'name\',\'txp_users\', \'RealName\', doSlash($name));\n        if ( ! $user_id ) {\n            trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\' \'\n                .gTxt(\'unknown_author\', array(\'name\' => $name)));\n            $user_id = \'unknown\';\n        }\n        $authorid = $users[$user_id] = $name;\n    } elseif ($id and (! $authorid = array_search($id, $users))) {\n        if (! safe_count(\'txp_users\', \'name = \"\'.doSlash($id).\'\"\')) {\n            trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\' \'\n                .gTxt(\'unknown_author\', array(\'name\' => $id)));\n        }\n        $authorid = $users[$id] = $id;\n    } else {\n        $authorid = $thisarticle[\'authorid\'];\n    }\n\n    $mod_id = _soo_modified_author_id($thisarticle[\'thisid\']);\n\n    return parse($thing, $mod_id == $authorid);\n}\n\nfunction soo_modified_author ($atts)\n{\n    assert_article();\n    global $thisarticle;\n    extract(lAtts(array(\n        \'fullname\' => 1,\n    ), $atts));\n\n    $author_id = _soo_modified_author_id($thisarticle[\'thisid\']);\n\n    return $fullname ? get_author_name($author_id) : $author_id;\n}\n\nfunction _soo_modified_author_id ($article_id)\n{\n    static $mod_ids = array();\n    if (empty($mod_ids[$article_id])) {\n        $mod_ids[$article_id] = fetch(\'LastModID\',\'textpattern\',\'ID\', $article_id);\n    }\n    return $mod_ids[$article_id];\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_if_modified\')\n        ->register(\'soo_if_modified_author\')\n        ->register(\'soo_modified_author\')\n        ;\n}\n\nfunction soo_if_modified ($atts, $thing)\n{\n    assert_article();\n\n    extract(lAtts(array(\n        \'format\'    =>  \'\',\n    ), $atts));\n\n    if (! $format) {\n        global $id, $c, $pg, $dateformat, $archive_dateformat;\n        $format = ($id or $c or $pg) ? $archive_dateformat : $dateformat;\n    }\n\n    global $thisarticle;\n\n    $modified = safe_strftime($format, $thisarticle[\'modified\']);\n    $posted = safe_strftime($format, $thisarticle[\'posted\']);\n\n    return parse($thing, $modified != $posted);\n}\n\nfunction soo_if_modified_author ($atts, $thing)\n{\n    assert_article();\n    global $thisarticle;\n\n    extract(lAtts(array(\n        \'id\' => \'\',\n        \'name\' => \'\',\n    ), $atts));\n\n    static $users = array();\n\n    if ($name and (! $authorid = array_search($name, $users))) {\n        $user_id = fetch(\'name\',\'txp_users\', \'RealName\', doSlash($name));\n        if ( ! $user_id ) {\n            trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\' \'\n                .gTxt(\'unknown_author\', array(\'name\' => $name)));\n            $user_id = \'unknown\';\n        }\n        $authorid = $users[$user_id] = $name;\n    } elseif ($id and (! $authorid = array_search($id, $users))) {\n        if (! safe_count(\'txp_users\', \'name = \"\'.doSlash($id).\'\"\')) {\n            trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\' \'\n                .gTxt(\'unknown_author\', array(\'name\' => $id)));\n        }\n        $authorid = $users[$id] = $id;\n    } else {\n        $authorid = $thisarticle[\'authorid\'];\n    }\n\n    $mod_id = _soo_modified_author_id($thisarticle[\'thisid\']);\n\n    return parse($thing, $mod_id == $authorid);\n}\n\nfunction soo_modified_author ($atts)\n{\n    assert_article();\n    global $thisarticle;\n    extract(lAtts(array(\n        \'fullname\' => 1,\n    ), $atts));\n\n    $author_id = _soo_modified_author_id($thisarticle[\'thisid\']);\n\n    return $fullname ? get_author_name($author_id) : $author_id;\n}\n\nfunction _soo_modified_author_id ($article_id)\n{\n    static $mod_ids = array();\n    if (empty($mod_ids[$article_id])) {\n        $mod_ids[$article_id] = fetch(\'LastModID\',\'textpattern\',\'ID\', $article_id);\n    }\n    return $mod_ids[$article_id];\n}\n', '0b615997794a7d39253097c224e391fe', '', '', 0, 5, 0),
('soo_required_files', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.2.8', 'Load JavaScript and CSS files per article', '<h1>soo_required_files</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>.</p>\n\n<ul>\n	<li><a href=\"https://forum.textpattern.io/viewtopic.php?id=30720\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/74/soo_required_files\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_required_files\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#requirements\">Requirements</a></li>\n	<li><a href=\"#installation\">Installation</a>\n	<ul>\n		<li><a href=\"#upgrading\">Upgrading</a></li>\n	</ul></li>\n	<li><a href=\"#usage\">Usage</a>\n	<ul>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#per-page\">Per-page loading</a></li>\n		<li><a href=\"#per-section\">Per-section loading</a></li>\n		<li><a href=\"#per-article\">Per-article loading</a></li>\n		<li><a href=\"#tag-contents\">Loading tag contents</a></li>\n		<li><a href=\"#load-order\">Load order</a></li>\n	</ul></li>\n	<li><a href=\"#defaults\">Preferences &amp; defaults</a></li>\n	<li><a href=\"#examples\">Examples</a></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>Automatic loading of <span class=\"caps\">CSS</span> and JavaScript files in the page <code>&lt;head&gt;</code> (or anywhere you like). For background, see <a href=\"http://ipsedixit.net/txp/73/article-specific-css-javascript-in-textpattern\">Article-specific <span class=\"caps\">CSS</span> &amp; JavaScript in Textpattern</a>.</p>\n\n<p><strong>soo_required_files</strong> has four options for loading files and forms:</p>\n\n<ul>\n	<li>all contexts (by tag contents)</li>\n	<li>per page (for files with matching names)</li>\n	<li>per section (for files with matching names)</li>\n	<li>per article (by custom field)</li>\n</ul>\n\n<p>These may be used in any combination.</p>\n\n<h2 id=\"requirements\">Requirements</h2>\n\n<p>No special requirements, except that per-article use <span class=\"required\">requires</span> a custom field (named <strong>Requires</strong> by default).</p>\n\n<p>All my plugins are generally developed in the latest Txp public release and may not have been tested in earlier versions.</p>\n\n<p><strong>soo_required_files</strong> is compatible with <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a>, which <span class=\"required\">requires</span> Txp version 4.2.0 or greater.</p>\n\n<h2 id=\"installation\">Installation</h2>\n\n<p>For per-article file and form loading, <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Advanced_Preferences#Custom_Fields\">name one of your custom fields</a> &#8220;Requires&#8221;. (If you wish to name it something else, change the custom field setting in plugin preferences (see <a href=\"#defaults\">Preferences &amp; defaults</a>, below.)</p>\n\n<p>Install the optional <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a> if you want to change default settings (Txp version 4.2.0 or greater <span class=\"required\">required</span>).</p>\n\n<h3 id=\"upgrading\">Upgrading from 0.1.1</h3>\n\n<p>If you are upgrading from version 0.1.1 with <strong>soo_plugin_pref</strong>, check preferences after installation. Two attribute names have been changed in this version, meaning any custom settings for those attributes (default js and css directories) will be overwritten on upgrade.</p>\n\n<h3>Upgrading to 0.2.6</h3>\n\n<p>Version 0.2.6 adds a new preference, <code>html_version</code>. If you are running <strong>soo_plugin_pref</strong>, after installing the new version of <strong>soo_required_files</strong> you must disable and re-enable it to install the preference. (The new preference will function at its default value even if you don&#8217;t do this.)</p>\n\n<h3>Upgrading to 0.2.7</h3>\n\n<p>Oops, I really didn&#8217;t need to add that preference to 0.2.6. Version 0.2.7 removes it, in favor of the global doctype preference. So if you previously installed or upgraded to 0.2.6 and are running <strong>soo_plugin_pref</strong>, to drop the now-useless preference from the database, after upgrading to 0.2.7, disable and re-enable the plugin. Sorry about that.</p>\n\n<h2 id=\"usage\">Usage</h2>\n\n<p>Place the <code>soo_required_files</code> tag in the page <code>&lt;head&gt;</code>. It works as either a single or container tag:</p>\n\n<pre>&lt;txp:soo_required_files /&gt;</pre>\n\n<p>or</p>\n\n<pre>&lt;txp:soo_required_files&gt;\n	&lt;!-- comma-separated list of file and form names --&gt;\n&lt;/txp:soo_required_files&gt;</pre>\n\n<h3 id=\"attributes\">Attributes</h3>\n\n<p>You can override any of the preferences with its corresponding attribute; the following are the ones you might actually want to set this way:</p>\n\n<ul>\n	<li><code>per_page</code> <em>(boolean)</em> Whether to enable per-page loading</li>\n	<li><code>per_section</code> <em>(boolean)</em> Whether to enable per-section loading</li>\n</ul>\n\n<h3 id=\"per-page\">Per-page loading</h3>\n\n<p>When this option is enabled, either through <a href=\"#defaults\">preferences</a> or by the <code>per_page</code> attribute, the plugin will look for a css and a js file with the same name as the current page. For example, if the current section uses a page called &#8220;article&#8221;, the plugin will look for files named &#8220;article.css&#8221; and &#8220;article.js&#8221; in their respective directories.</p>\n\n<h3 id=\"per-section\">Per-section loading</h3>\n\n<p>Works the same as per-page loading, except based on the current section name (and the <code>per_section</code> attribute/preference).</p>\n\n<h3 id=\"per-article\">Per-article loading</h3>\n\n<p>The <strong>Requires</strong> field takes a comma-separated list. List items can include:</p>\n\n<ul>\n	<li>JavaScript file names (e.g., &#8220;script.js&#8221;)</li>\n	<li><span class=\"caps\">CSS</span> file names (e.g, &#8220;style.css&#8221;)</li>\n	<li>Txp form names</li>\n</ul>\n\n<p>Any item ending in &#8220;.js&#8221; is assumed to be a JavaScript file. It will result in a <code>&lt;script&gt;</code> element loading the specified file from the default JavaScript directory (see <a href=\"#defaults\">Defaults</a>, below).</p>\n\n<p>Any item ending in &#8220;.css&#8221; is assumed to be a <span class=\"caps\">CSS</span> file. It will result in a <code>&lt;link&gt;</code> element loading the specified file in the default <span class=\"caps\">CSS</span> directory (see <a href=\"#defaults\">Defaults</a>, below).</p>\n\n<p>Anything else is assumed to be the name of a Txp form. The plugin will attempt to output the named form (first adding the default prefix; see <a href=\"#defaults\">Defaults</a>, below).</p>\n\n<h3 id=\"tag-contents\">Loading tag contents</h3>\n\n<p>If you use <code>soo_required_files</code> as a container, the tag contents are treated in the same way as the <strong>Requires</strong> field contents, above. That is, the tag contents should be a comma-separated list and can be any combination of css files, js files, and Txp form names.</p>\n\n<p>As of version 0.2.2 you can include Txp tags in the tag contents, allowing e.g. Txp conditional tags for further automation options.</p>\n\n<h3 id=\"load-order\">Load order</h3>\n\n<p>Files and forms are loaded in the following order:</p>\n\n<ol>\n	<li>Tag contents</li>\n	<li>Per-page files</li>\n	<li>Per-section files</li>\n	<li><strong>Requires</strong> field contents</li>\n</ol>\n\n<h2 id=\"defaults\">Preferences &amp; defaults</h2>\n\n<p>You can change the default values for various settings and attributes by installing the <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a> plugin. Once installed you can access the preferences <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Plugins#Panel_layout_.26_controls\">by clicking the <strong>Options</strong> link for <strong>soo_required_files</strong> in the plugin list</a>.</p>\n\n<p>The initial defaults are:</p>\n\n<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Description</th>\n			<th>Default</th>\n		</tr>\n		<tr>\n			<td><code>custom_field</code></td>\n			<th>Custom field name</th>\n			<td><kbd>Requires</kbd></td>\n		</tr>\n		<tr>\n			<td><code>css_dir</code></td>\n			<th><span class=\"caps\">CSS</span> directory</th>\n			<td><kbd>css/</kbd></td>\n		</tr>\n		<tr>\n			<td><code>js_dir</code></td>\n			<th>JS directory</th>\n			<td><kbd>js/</kbd></td>\n		</tr>\n		<tr>\n			<td><code>form_prefix</code></td>\n			<th>Form-name prefix</th>\n			<td><kbd>require_</kbd></td>\n		</tr>\n		<tr>\n			<td><code>per_page</code></td>\n			<th>Per-page loading</th>\n			<td>No</td>\n		</tr>\n		<tr>\n			<td><code>per_section</code></td>\n			<th>Per-section loading</th>\n			<td>No</td>\n		</tr>\n</table>\n\n<h2 id=\"examples\">Examples</h2>\n\n<h3>Using the Requires field</h3>\n\n<p>With defaults as above, entering <kbd>script1.js, style1.css, foo </kbd> in the <strong>Requires</strong> field will get <code>&lt;txp:soo_required_files /&gt;</code> to output something like:</p>\n\n<pre>&lt;script type=&quot;text/javascript&quot; src=&quot;http://example.com/js/script1.js&quot;&gt;&lt;/script&gt;\n&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;http://example.com/css/style1.css&quot; /&gt;</pre>\n\n<p>followed by whatever is in the form named &#8220;require_foo&#8221;.</p>\n\n<p>Forms are especially useful for preset combinations of files. For example, some scripts (e.g. <a href=\"http://www.shadowbox-js.com/\">Shadowbox</a>) require a mix of JavaScript and <span class=\"caps\">CSS</span> files. Putting the appropriate <code>&lt;script&gt;</code> and <code>&lt;link&gt;</code> elements in a form allows you to call this up with a single entry in the <strong>Requires</strong> field. So to load my &#8220;require_Shadowbox&#8221; form (which loads the scripts and <span class=\"caps\">CSS</span> required for Shadowbox), I enter <kbd>Shadowbox</kbd> in the <strong>Requires</strong> field.</p>\n\n<h3>Combining per-section/page, per-article, and tag content file loading</h3>\n\n<p>For my <a href=\"http://ipsedixit.net/\">personal website</a> I have <span class=\"caps\">CSS</span> files named after each section (including the default section), plus a file called &#8220;base.css&#8221; for common styles. Some articles have specific js/css requirements, so I also have a <strong>Requires</strong> field, used as above. I have a form called <code>page_top</code> that creates the <code>&lt;head&gt;</code> element for every section. It doesn&#8217;t have any <code>&lt;script&gt;</code> or <code>&lt;link&gt;</code> tags, just this:</p>\n\n<pre>&lt;txp:soo_required_files&gt;base.css&lt;/txp:soo_required_files&gt;</pre>\n\n<p>Because I have enabled per-section loading in preferences, every <span class=\"caps\">HTML</span> page automatically gets both the base stylesheet and the section-specific stylesheet, and individual article pages will also load anything listed in <strong>Requires</strong>.</p>\n\n<h2 id=\"history\">History</h2>\n\n<h3>Version 0.2.8 (2020/3/15)</h3>\n\n<ul>\n	<li>Fixed error notice when running plugin under Txp 4.8+ on a page with no required files</li>\n</ul>\n\n<h3>Version 0.2.7 (2017/3/14)</h3>\n\n<ul>\n	<li>Removes the preference added in 0.2.6 in favor of the global doctype preference. (NB: see Upgrade notes.)</li>\n</ul>\n\n<h3>Version 0.2.6 (2017/2/23)</h3>\n\n<ul>\n	<li>Added <span class=\"caps\">HTML</span> version preference, which controls whether or not the <code>type</code> attribute is added to <code>link</code> and <code>script</code> tags. (NB: see Upgrade notes.)</li>\n</ul>\n\n<h3>Version 0.2.5 (2017/2/13)</h3>\n\n<ul>\n	<li>Txp 4.6 compatibility update</li>\n</ul>\n\n<h3>Version 0.2.3 (2010/12/20), 0.2.4 (2010/12/27)</h3>\n\n<ul>\n	<li>Code cleaning only; no functional changes</li>\n</ul>\n\n<h3>Version 0.2.2 (2010/07/10)</h3>\n\n<ul>\n	<li>Tag contents can now include other Txp tags, allowing e.g. Txp conditional tags for further automation options.</li>\n</ul>\n\n<h3>Version 0.2.1 (2009/10/03)</h3>\n\n<ul>\n	<li>Fixed to work with sub-directory installations</li>\n</ul>\n\n<h3>Version 0.2 (2009/09/25)</h3>\n\n<ul>\n	<li>New features:\n	<ul>\n		<li>Per-section and/or per-page file loading</li>\n		<li>Container tag mode for additional files/forms</li>\n		<li>All preferences can now be overriden by tag attributes</li>\n	</ul></li>\n	<li>Note: new attribute names for default js and css directories. Users upgrading from version 0.1.1 should check preferences and update these values if needed.</li>\n</ul>\n\n<h3>Version 0.1.1 (2009/09/18)</h3>\n\n<ul>\n	<li>Added compatibility with <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a> for preference management</li>\n</ul>\n\n<h3>Version 0.1 (2009/05/15)</h3>\n\n<ul>\n	<li>Initial release. Per-article loading of js/css files, Txp forms.</li>\n</ul>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_required_files\')\n        ;\n}\n\n@require_plugin(\'soo_plugin_pref\');     // optional\n\nif (@txpinterface == \'admin\') {\n    add_privs(\'plugin_prefs.soo_required_files\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_required_files\',\'1,2\');\n    register_callback(\'soo_required_files_manage_prefs\', \'plugin_prefs.soo_required_files\');\n    register_callback(\'soo_required_files_manage_prefs\', \'plugin_lifecycle.soo_required_files\');\n}\n\nfunction soo_required_files_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\'))\n        return soo_plugin_pref($event, $step, soo_required_files_pref_spec());\n\n        // message to install soo_plugin_pref\n    if (substr($event, 0, 12) == \'plugin_prefs\') {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\'.gTxt(\'edit\').\" $plugin \".\n            gTxt(\'edit_preferences\').\':</strong><br />\'.gTxt(\'install_plugin\').\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">\'.\n            \'soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\').\" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_required_files_pref_spec()\n{\n    return array(\n        \'custom_field\' => array(\n            \'val\'   => \'Requires\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Custom field name\',\n        ),\n        \'css_dir\' => array(\n            \'val\'   => \'css/\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Default css dir (relative to base URL, with closing slash)\',\n        ),\n        \'js_dir\' => array(\n            \'val\'   => \'js/\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Default js dir (relative to base URL, with closing slash)\',\n        ),\n        \'form_prefix\' => array(\n            \'val\'   => \'require_\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Optional prefix for form names\',\n        ),\n        \'per_page\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'yesnoradio\',\n            \'text\'  => \'Load {page}.css and {page}.js?\',\n        ),\n        \'per_section\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'yesnoradio\',\n            \'text\'  => \'Load {section}.css and {section}.js?\',\n        ),\n    );\n}\n\nfunction soo_required_files_prefs()\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_required_files_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_required_files\'));\n        }\n    }\n    return $prefs;\n}\n\nfunction soo_required_files($atts, $thing = \'\')\n{\n    global $page, $s, $id, $doctype;\n    $prefs = soo_required_files_prefs();\n    extract($prefs);\n    $required = $thing ? do_list(parse($thing)) : [];\n\n    // tag atts override defaults/prefs\n    foreach ($atts as $k => $v)\n        if (array_key_exists($k, $prefs))\n            $$k = $v;\n\n    if ($per_page)\n        $required = array_merge($required, _soo_required_files_add($page));\n\n    if ($per_section)\n        $required = array_merge($required, _soo_required_files_add($s));\n\n    // if individual article, get custom field contents\n    if ($id and $custom_field)\n        $required = array_merge($required, do_list(custom_field(array(\n            \'name\'      =>  $custom_field,\n            \'escape\'    =>  \'html\',\n            \'default\'   =>  \'\',\n        ))));\n\n    $required = array_unique($required);\n\n    $cssAtts = $jsAtts = array();\n    $cssAtts[\'rel\'] = \'stylesheet\';\n    if ($doctype != \'html5\') {\n        $cssAtts[\'type\'] = \'text/css\';\n        $jsAtts[\'type\'] = \'text/javascript\';\n    }\n\n    foreach ($required as $req) {\n        if (substr(strtolower($req), -4) === \'.css\') {\n            $cssAtts[\'href\'] = hu.$css_dir.$req;\n            $out[] = tag_void(\'link\', $cssAtts);\n        } elseif (substr(strtolower($req), -3) === \'.js\') {\n            $jsAtts[\'src\'] = hu.$js_dir.$req;\n            $out[] = tag(null, \'script\', $jsAtts);\n        }\n        elseif ($req)\n            $out[] = parse_form($form_prefix.$req);\n    }\n\n    return isset($out) ? implode(\"\\n\", $out) : \'\';\n}\n\nfunction _soo_required_files_add($name)\n{\n    extract(soo_required_files_prefs());\n    $path_root = preg_replace(\'/index.php/\', \'\', $_SERVER[\'SCRIPT_FILENAME\']);\n    if (file_exists($path_root.$css_dir.$name.\'.css\'))\n        $out[] = $name.\'.css\';\n    if (file_exists($path_root.$js_dir.$name.\'.js\'))\n        $out[] = $name.\'.js\';\n    return isset($out) ? $out : array();\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_required_files\')\n        ;\n}\n\n@require_plugin(\'soo_plugin_pref\');     // optional\n\nif (@txpinterface == \'admin\') {\n    add_privs(\'plugin_prefs.soo_required_files\',\'1,2\');\n    add_privs(\'plugin_lifecycle.soo_required_files\',\'1,2\');\n    register_callback(\'soo_required_files_manage_prefs\', \'plugin_prefs.soo_required_files\');\n    register_callback(\'soo_required_files_manage_prefs\', \'plugin_lifecycle.soo_required_files\');\n}\n\nfunction soo_required_files_manage_prefs($event, $step)\n{\n    if (function_exists(\'soo_plugin_pref\'))\n        return soo_plugin_pref($event, $step, soo_required_files_pref_spec());\n\n        // message to install soo_plugin_pref\n    if (substr($event, 0, 12) == \'plugin_prefs\') {\n        $plugin = substr($event, 13);\n        $message = \'<p><br /><strong>\'.gTxt(\'edit\').\" $plugin \".\n            gTxt(\'edit_preferences\').\':</strong><br />\'.gTxt(\'install_plugin\').\n            \' <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">\'.\n            \'soo_plugin_pref</a></p>\';\n        pagetop(gTxt(\'edit_preferences\').\" &#8250; $plugin\", $message);\n    }\n}\n\nfunction soo_required_files_pref_spec()\n{\n    return array(\n        \'custom_field\' => array(\n            \'val\'   => \'Requires\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Custom field name\',\n        ),\n        \'css_dir\' => array(\n            \'val\'   => \'css/\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Default css dir (relative to base URL, with closing slash)\',\n        ),\n        \'js_dir\' => array(\n            \'val\'   => \'js/\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Default js dir (relative to base URL, with closing slash)\',\n        ),\n        \'form_prefix\' => array(\n            \'val\'   => \'require_\',\n            \'html\'  => \'text_input\',\n            \'text\'  => \'Optional prefix for form names\',\n        ),\n        \'per_page\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'yesnoradio\',\n            \'text\'  => \'Load {page}.css and {page}.js?\',\n        ),\n        \'per_section\' => array(\n            \'val\'   => 0,\n            \'html\'  => \'yesnoradio\',\n            \'text\'  => \'Load {section}.css and {section}.js?\',\n        ),\n    );\n}\n\nfunction soo_required_files_prefs()\n{\n    static $prefs;\n    if (! $prefs) {\n        foreach (soo_required_files_pref_spec() as $name => $spec) {\n            $prefs[$name] = $spec[\'val\'];\n        }\n        if (function_exists(\'soo_plugin_pref_vals\')) {\n            $prefs = array_merge($prefs, soo_plugin_pref_vals(\'soo_required_files\'));\n        }\n    }\n    return $prefs;\n}\n\nfunction soo_required_files($atts, $thing = \'\')\n{\n    global $page, $s, $id, $doctype;\n    $prefs = soo_required_files_prefs();\n    extract($prefs);\n    $required = $thing ? do_list(parse($thing)) : [];\n\n    // tag atts override defaults/prefs\n    foreach ($atts as $k => $v)\n        if (array_key_exists($k, $prefs))\n            $$k = $v;\n\n    if ($per_page)\n        $required = array_merge($required, _soo_required_files_add($page));\n\n    if ($per_section)\n        $required = array_merge($required, _soo_required_files_add($s));\n\n    // if individual article, get custom field contents\n    if ($id and $custom_field)\n        $required = array_merge($required, do_list(custom_field(array(\n            \'name\'      =>  $custom_field,\n            \'escape\'    =>  \'html\',\n            \'default\'   =>  \'\',\n        ))));\n\n    $required = array_unique($required);\n\n    $cssAtts = $jsAtts = array();\n    $cssAtts[\'rel\'] = \'stylesheet\';\n    if ($doctype != \'html5\') {\n        $cssAtts[\'type\'] = \'text/css\';\n        $jsAtts[\'type\'] = \'text/javascript\';\n    }\n\n    foreach ($required as $req) {\n        if (substr(strtolower($req), -4) === \'.css\') {\n            $cssAtts[\'href\'] = hu.$css_dir.$req;\n            $out[] = tag_void(\'link\', $cssAtts);\n        } elseif (substr(strtolower($req), -3) === \'.js\') {\n            $jsAtts[\'src\'] = hu.$js_dir.$req;\n            $out[] = tag(null, \'script\', $jsAtts);\n        }\n        elseif ($req)\n            $out[] = parse_form($form_prefix.$req);\n    }\n\n    return isset($out) ? implode(\"\\n\", $out) : \'\';\n}\n\nfunction _soo_required_files_add($name)\n{\n    extract(soo_required_files_prefs());\n    $path_root = preg_replace(\'/index.php/\', \'\', $_SERVER[\'SCRIPT_FILENAME\']);\n    if (file_exists($path_root.$css_dir.$name.\'.css\'))\n        $out[] = $name.\'.css\';\n    if (file_exists($path_root.$js_dir.$name.\'.js\'))\n        $out[] = $name.\'.js\';\n    return isset($out) ? $out : array();\n}\n', 'c976c8b8c5175e306e68f90d090696e2', '', '', 1, 5, 3),
('soo_thumb_atts', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.2.1', 'Update thumbnail h/w attributes', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help h6 {font-size: 1em;margin-left:2em;font-style:oblique;}\ndiv#sed_help li {list-style-type: disc;}\ndiv#sed_help li li {list-style-type: circle;}\ndiv#sed_help li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\n</style>\n <div id=\"sed_help\">\n\n	<h1>soo_thumb_atts</h1>\n\n	<h2 id=\"overview\">Overview</h2>\n\n	<p>Thumbnail height and width attributes were introduced in Textpattern version 4.2.0. However, these attributes are <strong>not</strong> automatically added to thumbnails already in the database when the upgrade takes place.</p>\n\n	<p>This plugin finds images with a thumbnail but no thumbnail height or width in the database, and allows you to update these with the correct values.</p>\n\n	<p>It is intended as a one-use plugin, but there might be future use for it as a means of correcting data for images that have been batch-replaced without going through the Textpattern admin panel. (It could easily be modified to examine all images, not just thumbnails.)</p>\n\n	<p>The plugin <span class=\"required\">requires</span> Textpattern version 4.2.0 or later.</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>Install and activate the plugin, then find its control panel under the Extensions tab. Click &#8220;Update&#8221; and you&#8217;re done; you will probably want to uninstall the plugin.</p>\n\n	<h2 id=\"history\">Version History</h2>\n\n	<h3>0.2.1 (9/3/2009)</h3>\n\n	<p>Code cleaning &#8212; thanks again to Ruud for the suggestions.</p>\n\n	<h3>0.2 (9/3/2009)</h3>\n\n	<p>Thanks to Ruud for the suggestions. This version only operates on thumbnails with h and w set to 0 in the database, and does not attempt to list more than 50 images (although it will still attempt to update all the images at once).</p>\n\n	<h3>0.1 (9/3/2009)</h3>\n\n	<p>Initial release:\n	<ul>\n		<li>Update thumbnail height and width values in the database</li>\n	</ul></p>\n\n </div>', '\nif ( @txpinterface == \'admin\' ) {\n	add_privs(\'soo_thumb_atts\',\'1,2\');\n\n	register_tab(\'extensions\', \'soo_thumb_atts\',\n		soo_thumb_atts_gTxt(\'soo_thumb_atts\'));\n	register_callback(\'soo_thumb_atts\', \'soo_thumb_atts\');\n}\n\nfunction soo_thumb_atts( $event, $step, $message = \'\' ) {\n	if( $step and in_array($step, array(\n			\'update\',\n		)) ) {\n		$func = \'soo_thumb_atts_\' . $step;\n		$message = $func();\n	}\n	soo_thumb_atts_ui($message);\n}\n\nfunction soo_thumb_atts_ui( $message ) {\n\n	global $prefs;\n	$version_ok = $prefs[\'version\'] >= 4.2;\n	$version_color = $version_ok ? \'#093\' : \'#f30\';\n	$version_text = \"<strong style=\\\"color:$version_color;\\\">\" .\n		$prefs[\'version\'] . \'</strong>\';\n	$cols = 3;\n	$limit = 50;\n	$tda = \' style=\"text-align:center;\"\';\n\n	pagetop(soo_thumb_atts_gTxt(\'soo_thumb_atts\'), $message);\n\n	echo n .\n	startTable(\'list\') .\n		tr( n .\n			tdcs(hed(soo_thumb_atts_gTxt(\'header\'), 1), $cols)\n		) .\n		tr( n .\n			tdcs(soo_thumb_atts_gTxt(\'version_notice\') . br .\n			soo_thumb_atts_gTxt(\'version_check\') . $version_text . \'.\'\n			, $cols)\n		)\n	;\n\n	if ( ! $version_ok ) {\n		echo endTable();\n		return;\n	}\n\n	$broken_thumbs = soo_thumb_atts_query(\'select\', $limit);\n	$count = count($broken_thumbs);\n\n	if ( $count == 0 ) {\n		echo\n		tr(\n			tdcs(strong(soo_thumb_atts_gTxt(\'up_to_date_begin\')) .\n				soo_thumb_atts_gTxt(\'up_to_date_end\'), $cols)\n		) .\n		endTable()\n		;\n		return;\n	}\n\n	echo\n	tr(\n		tdcs( ( $count == $limit ? soo_thumb_atts_gTxt(\'limit_begin\') : \'\' )\n			 . $count . soo_thumb_atts_gTxt(\'count_end\') .\n			 ( $count == $limit ? soo_thumb_atts_gTxt(\'limit_end\')\n			 	. $count . \'.\' : \'\' )\n			, $cols\n		)\n	) .\n	tr(\n		tdcs(\'<form method=\"post\" name=\"soo_thumb_atts_form\">\' . n .\n			sInput(\'update\') . n .\n			eInput(\'soo_thumb_atts\') . n .\n			fInput(\'submit\', \'soo_thumb_atts_update\',\n				soo_thumb_atts_gTxt(\'update\'), \'publish\')\n		, $cols)\n	) .\n	tr(\n		hCell(\'File\', \'\', $tda) . hCell(\'Actual w\', \'\', $tda) . hCell(\'Actual h\', \'\', $tda)\n	);\n\n	foreach ( $broken_thumbs as $t ) {\n		extract($t);\n		echo tr( n .\n			tda(\"${id}t$ext\", $tda) .\n			tda($w, $tda) .\n			tda($h, $tda)\n		);\n	}\n	echo endTable();\n}\n\nfunction soo_thumb_atts_update( ) {\n	return soo_thumb_atts_query(\'update\');\n}\n\nfunction soo_thumb_atts_query( $action = \'select\', $limit = null ) {\n	global $img_dir, $prefs;\n	$path = $prefs[\'path_to_site\'] . \"/$img_dir\";\n	$where = \'thumbnail = 1 and thumb_h = 0 and thumb_w = 0\' .\n		( $limit ? \" limit $limit\" : \'\' );\n	$rs = safe_rows_start(\'id, ext\', \'txp_image\', $where);\n	$count = 0;\n\n	while ( $a = nextRow($rs) ) {\n		extract($a);\n		$file = \"$path/${id}t$ext\";\n		if ( file_exists($file) ) {\n			list($w, $h) = getimagesize($file);\n			switch ( $action ) {\n				case \'select\':\n					$out[$id] = $a;\n					$out[$id][\'w\'] = $w;\n					$out[$id][\'h\'] = $h;\n					break;\n				case \'update\':\n					$ok = safe_update(\n						\'txp_image\',\n						\"thumb_w = $w, thumb_h = $h\",\n						\"id = $id\"\n					);\n					if ( $ok ) $count++;\n			}\n		}\n	}\n	if ( $action == \'select\' and isset($out) )\n		return $out;\n	if ( $action == \'update\' )\n		return $count . soo_thumb_atts_gTxt(\'images_updated\');\n}\n\nfunction soo_thumb_atts_gTxt ( $what, $atts = array() ) {\n	$lang = array(\n\'soo_thumb_atts\'	=> \'Thumbnail Atts\',\n\'header\'			=> \'Update Thumbnail H/W Attributes\',\n\'version_check\'		=> \'You appear to be running Txp version \',\n\'version_notice\'	=> \'This plugin requires Txp version 4.2.0 or greater.\',\n\'limit_begin\'		=> \'There are at least \',\n\'count_end\'			=> \' thumbnails to update.\',\n\'limit_end\'			=> \' Showing the first \',\n\'up_to_date_begin\'	=> \'Nothing to update: \',\n\'up_to_date_end\'	=> \'all thumbnails have correct h/w attributes.\',\n\'update\'			=> \'Update\',\n\'images_updated\'	=> \' thumbnails updated successfully.\',\n	);\n	return strtr($lang[$what], $atts);\n\n}\n', '\nif ( @txpinterface == \'admin\' ) {\n	add_privs(\'soo_thumb_atts\',\'1,2\');\n\n	register_tab(\'extensions\', \'soo_thumb_atts\',\n		soo_thumb_atts_gTxt(\'soo_thumb_atts\'));\n	register_callback(\'soo_thumb_atts\', \'soo_thumb_atts\');\n}\n\nfunction soo_thumb_atts( $event, $step, $message = \'\' ) {\n	if( $step and in_array($step, array(\n			\'update\',\n		)) ) {\n		$func = \'soo_thumb_atts_\' . $step;\n		$message = $func();\n	}\n	soo_thumb_atts_ui($message);\n}\n\nfunction soo_thumb_atts_ui( $message ) {\n\n	global $prefs;\n	$version_ok = $prefs[\'version\'] >= 4.2;\n	$version_color = $version_ok ? \'#093\' : \'#f30\';\n	$version_text = \"<strong style=\\\"color:$version_color;\\\">\" .\n		$prefs[\'version\'] . \'</strong>\';\n	$cols = 3;\n	$limit = 50;\n	$tda = \' style=\"text-align:center;\"\';\n\n	pagetop(soo_thumb_atts_gTxt(\'soo_thumb_atts\'), $message);\n\n	echo n .\n	startTable(\'list\') .\n		tr( n .\n			tdcs(hed(soo_thumb_atts_gTxt(\'header\'), 1), $cols)\n		) .\n		tr( n .\n			tdcs(soo_thumb_atts_gTxt(\'version_notice\') . br .\n			soo_thumb_atts_gTxt(\'version_check\') . $version_text . \'.\'\n			, $cols)\n		)\n	;\n\n	if ( ! $version_ok ) {\n		echo endTable();\n		return;\n	}\n\n	$broken_thumbs = soo_thumb_atts_query(\'select\', $limit);\n	$count = count($broken_thumbs);\n\n	if ( $count == 0 ) {\n		echo\n		tr(\n			tdcs(strong(soo_thumb_atts_gTxt(\'up_to_date_begin\')) .\n				soo_thumb_atts_gTxt(\'up_to_date_end\'), $cols)\n		) .\n		endTable()\n		;\n		return;\n	}\n\n	echo\n	tr(\n		tdcs( ( $count == $limit ? soo_thumb_atts_gTxt(\'limit_begin\') : \'\' )\n			 . $count . soo_thumb_atts_gTxt(\'count_end\') .\n			 ( $count == $limit ? soo_thumb_atts_gTxt(\'limit_end\')\n			 	. $count . \'.\' : \'\' )\n			, $cols\n		)\n	) .\n	tr(\n		tdcs(\'<form method=\"post\" name=\"soo_thumb_atts_form\">\' . n .\n			sInput(\'update\') . n .\n			eInput(\'soo_thumb_atts\') . n .\n			fInput(\'submit\', \'soo_thumb_atts_update\',\n				soo_thumb_atts_gTxt(\'update\'), \'publish\')\n		, $cols)\n	) .\n	tr(\n		hCell(\'File\', \'\', $tda) . hCell(\'Actual w\', \'\', $tda) . hCell(\'Actual h\', \'\', $tda)\n	);\n\n	foreach ( $broken_thumbs as $t ) {\n		extract($t);\n		echo tr( n .\n			tda(\"${id}t$ext\", $tda) .\n			tda($w, $tda) .\n			tda($h, $tda)\n		);\n	}\n	echo endTable();\n}\n\nfunction soo_thumb_atts_update( ) {\n	return soo_thumb_atts_query(\'update\');\n}\n\nfunction soo_thumb_atts_query( $action = \'select\', $limit = null ) {\n	global $img_dir, $prefs;\n	$path = $prefs[\'path_to_site\'] . \"/$img_dir\";\n	$where = \'thumbnail = 1 and thumb_h = 0 and thumb_w = 0\' .\n		( $limit ? \" limit $limit\" : \'\' );\n	$rs = safe_rows_start(\'id, ext\', \'txp_image\', $where);\n	$count = 0;\n\n	while ( $a = nextRow($rs) ) {\n		extract($a);\n		$file = \"$path/${id}t$ext\";\n		if ( file_exists($file) ) {\n			list($w, $h) = getimagesize($file);\n			switch ( $action ) {\n				case \'select\':\n					$out[$id] = $a;\n					$out[$id][\'w\'] = $w;\n					$out[$id][\'h\'] = $h;\n					break;\n				case \'update\':\n					$ok = safe_update(\n						\'txp_image\',\n						\"thumb_w = $w, thumb_h = $h\",\n						\"id = $id\"\n					);\n					if ( $ok ) $count++;\n			}\n		}\n	}\n	if ( $action == \'select\' and isset($out) )\n		return $out;\n	if ( $action == \'update\' )\n		return $count . soo_thumb_atts_gTxt(\'images_updated\');\n}\n\nfunction soo_thumb_atts_gTxt ( $what, $atts = array() ) {\n	$lang = array(\n\'soo_thumb_atts\'	=> \'Thumbnail Atts\',\n\'header\'			=> \'Update Thumbnail H/W Attributes\',\n\'version_check\'		=> \'You appear to be running Txp version \',\n\'version_notice\'	=> \'This plugin requires Txp version 4.2.0 or greater.\',\n\'limit_begin\'		=> \'There are at least \',\n\'count_end\'			=> \' thumbnails to update.\',\n\'limit_end\'			=> \' Showing the first \',\n\'up_to_date_begin\'	=> \'Nothing to update: \',\n\'up_to_date_end\'	=> \'all thumbnails have correct h/w attributes.\',\n\'update\'			=> \'Update\',\n\'images_updated\'	=> \' thumbnails updated successfully.\',\n	);\n	return strtr($lang[$what], $atts);\n\n}\n', '7103a5810d1b110d5d56450aeb1d69b8', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_page_break', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.1.2', 'Pagination within an article', '<h1>soo_page_break</h1>\n<p>This is a plugin for <a href=\"https://github.com/textpattern/textpattern\">Textpattern</a>. It provides a suite of tags for handling articles with body text marked up according to a couple of very simple rules: indicate page breaks with &quot;[&#8203;break]&quot; (or another delimiter of your choice), and somewhere after the break put a heading element (<code>h1</code>, <code>h2</code>, etc.) containing the title for that page.</p>\n<p>See <a href=\"http://ipsedixit.net/txp/157/soo_page_break\">http://ipsedixit.net/txp/157/soo_page_break</a> for further description to and  download the latest version, or grab the the compiled code from the <a href=\"https://github.com/jsoo/soo_page_break/releases\">GitHub repo</a>. Either way, copy the full contents of the compiled file and paste into the <a href=\"https://docs.textpattern.io/administration/plugins-panel\">Textpattern plugin installer</a>, and follow the instructions from there.</p>\n<h2>Contents</h2>\n<ul>\n<li><a href=\"#requirements\">Requirements</a></li>\n<li><a href=\"#usage\">Usage</a></li>\n<li><a href=\"#tags\">Tags</a>\n<ul>\n<li><a href=\"#soo_article_page\">soo_article_page</a></li>\n<li><a href=\"#soo_article_page_title\">soo_article_page_title</a></li>\n<li><a href=\"#soo_article_page_number\">soo_article_page_number</a></li>\n<li><a href=\"#soo_article_page_link\">soo_article_page_link</a></li>\n<li><a href=\"#soo_article_page_nav\">soo_article_page_nav</a></li>\n<li><a href=\"#soo_if_article_page\">soo_if_article_page</a></li>\n<li><a href=\"#soo_article_page_search_url\">soo_article_page_search_url</a></li>\n</ul></li>\n<li><a href=\"#examples\">Examples</a></li>\n<li><a href=\"#history\">History</a></li>\n</ul>\n<h2 id=\"requirements\">Requirements</h2>\n<p>This plugin was developed on Textpattern 4.6 and has not been tested on earlier versions. It should run on any system capable of running Txp 4.6.2 or later. It does not install anything other than itself in the database (no extra tables, nor even prefs, although prefs might come in a later version) and is public-side only, not changing the admin side in any way. It does not require custom fields.</p>\n<p>All of these tags require article context.</p>\n<h2 id=\"usage\">Usage</h2>\n<p>Add page breaks to an article by inserting &quot;[&#8203;break]&quot; (the default delimiter) or other delimiter of your choice. For Textile-enabled articles be sure to surround the break delimiter with blank lines, otherwise the break may occur in the middle of an HTML element, producing invalid HTML output. Do not use HTML special characters in your break delimiter, unless you are certain it will never undergo Textile transformation.</p>\n<p>For example:</p>\n<pre><code>\"No,\" said Mr. Prendergast.\n\n[&#8203;break]\n\nh2. Chapter Eight</code></pre>\n<p><code>soo_article_page</code> is a drop-in replacement for <code>body</code>. If the article has page breaks, it will output the page indicated by the URL. <strong>soo_page_break</strong> uses the standard Txp pagination context (i.e., the <code>pg</code> parameter in the URL\'s query string). Txp doesn\'t apply pagination to individual articles, so <strong>soo_page_break</strong> does not conflict with standard Txp behavior.</p>\n<p>The rest of the tags depend on having a <code>soo_article_page</code> tag earlier in the article tag or form. You can run <code>soo_article_page</code> in quiet mode if necessary. It caches the pages it finds, so there should be no real performance hit for running it twice, even for a very long article.</p>\n<p><code>soo_article_page_title</code> returns the contents of the first HTML heading element (i.e., <code>h1</code>, <code>h2</code>, etc.) in a page. Rather than using the tag directly, it is generally more useful to access it through the text-replacement feature of <code>soo_article_page_link</code> or <code>soo_article_page_nav</code>, as in the following example,</p>\n<pre><code>&lt;txp:soo_article_page_link text=\"{title}\" /&gt;</code></pre>\n<p>which displays a link to the next page, using the page title as the link text.</p>\n<p><code>soo_article_page_number</code> displays page info, e.g. &quot;Page 1 of 4&quot;.\n<code>soo_article_page_link</code> outputs a link to the first, previous, next, or last page, or to a particular page number.\n<code>soo_article_page_nav</code> outputs a nav widget for all pages in the article. By default it uses page numbers as link text, but can be configured to show page titles and/or other text.</p>\n<p><code>soo_if_article_page</code> is a conditional with various possible tests pertaining to article pages.</p>\n<p><code>soo_article_page_search_url</code> can be used in a search results form to link to the first article page matching the search term.</p>\n<h2 id=\"tags\">Tags</h2>\n<h3 id=\"soo_article_page\">soo_article_page</h3>\n<pre><code>&lt;txp:soo_article_page /&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li>\n<p><code>delimiter</code> <em>(text)</em> default <code>[&#8203;break]</code></p>\n<p>Text to insert into an article body to indicate a page break</p>\n</li>\n<li>\n<p><code>quiet</code> <em>(boolean)</em> default <code>0</code></p>\n<p>Whether or not to run in quiet mode, i.e., calculate page numbers without showing any output</p>\n</li>\n</ul>\n<p>If using a delimiter other than the default <code>[&#8203;break]</code>, and using more than one <code>soo_article_page</code> tag in an article form, set the custom <code>delimiter</code> attribute in the first of the <code>soo_article_page</code> tags.</p>\n<h3 id=\"soo_article_page_title\">soo_article_page_title</h3>\n<pre><code>&lt;txp:soo_article_page_title /&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li>\n<p><code>page_number</code> <em>(int)</em> default current page</p>\n<p>Page number of the title to display</p>\n</li>\n<li>\n<p><code>class</code> <em>(text)</em> default empty</p>\n<p>HTML class attribute for the link</p>\n</li>\n<li>\n<p><code>wraptag</code> <em>(text)</em> default empty</p>\n<p>HTML tag name (no brackets) to wrap the output</p>\n</li>\n</ul>\n<h3 id=\"soo_article_page_number\">soo_article_page_number</h3>\n<pre><code>&lt;txp:soo_article_page_number /&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li>\n<p><code>text</code> <em>(text)</em> default <code>{page} {pg} {of} {total}</code></p>\n<p>Output format. <code>{pg}</code> and <code>{total}</code> will be replaced by numbers. <code>{page}</code> and <code>{of}</code> will be replaced by <code>gTxt()</code> values, per Txp internationalisation settings.</p>\n</li>\n<li>\n<p><code>class</code> <em>(text)</em> default empty</p>\n<p>HTML class attribute for the link</p>\n</li>\n<li>\n<p><code>wraptag</code> <em>(text)</em> default empty</p>\n<p>HTML tag name (no brackets) to wrap the output</p>\n</li>\n</ul>\n<h3 id=\"soo_article_page_link\">soo_article_page_link</h3>\n<pre><code>&lt;txp:soo_article_page_link /&gt;</code></pre>\n<h4>Attributes</h4>\n<p>Required: by default this produces a link to the next page, per the <code>rel</code> attribute. If you change <code>rel</code> to something other than <code>first</code>, <code>last</code>, or <code>prev</code>, you must set <code>page_number</code> to a valid article page number to get the tag to produce a link.</p>\n<ul>\n<li>\n<p><code>page_number</code> <em>(int)</em> default 0</p>\n<p>Page number to link to, if not indicated by <code>rel</code></p>\n</li>\n<li>\n<p><code>text</code> <em>(text)</em> default <code>{next}</code></p>\n<p>Link text. <code>{pg}</code> will be replaced by the page number the link points to, <code>{page}</code>, <code>{prev}</code>, and <code>{next}</code> will be replaced by <code>gTxt()</code> values, per Txp internationalisation settings, and <code>{title}</code> will be replaced by the <code>soo_article_page_title</code> of the linked page, if available.</p>\n</li>\n<li>\n<p><code>rel</code> <em>(text)</em> default <code>next</code></p>\n<p>Link relationship. If set to <code>first</code>, <code>last</code>, <code>prev</code>, or <code>next</code> the link URL will point to that page. Otherwise, the link page number will be determined by <code>page_number</code> (see above). If you set <code>rel=\"\"</code> the HTML <code>rel</code> attribute will be determined automatically.</p>\n</li>\n<li>\n<p><code>rev</code> <em>(text)</em> default empty</p>\n<p>Reversed link relationship. Leave unset to let the tag add an appropriate value.</p>\n</li>\n<li>\n<p><code>showalways</code> <em>(boolean)</em> default <code>0</code></p>\n<p>Whether or not to output <code>text</code> if the link is invalid (e.g., <code>rel=\"next\"</code> when you are on the last page).</p>\n</li>\n<li>\n<p><code>class</code> <em>(text)</em> default empty</p>\n<p>HTML class attribute for the link</p>\n</li>\n<li>\n<p><code>title</code> <em>(text)</em> default empty</p>\n<p>HTML title attribute for the link. Uses the same text replacement formula as <code>text</code>, above. If you include <code>{title}</code> here and use Textile in your articles, you should also unset <code>escape</code>.</p>\n</li>\n<li>\n<p><code>escape</code> <em>(&quot;html&quot; or unset)</em> default html</p>\n<p>Escape HTML entities such as &lt;, &gt; and &amp; in the <code>title</code> attribute</p>\n</li>\n</ul>\n<h3 id=\"soo_article_page_nav\">soo_article_page_nav</h3>\n<pre><code>&lt;txp:soo_article_page_nav /&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li>\n<p><code>text</code> <em>(text)</em> default <code>{pg}</code> (page number)</p>\n<p>Link text. Uses the same text replacement formula as <code>soo_article_page_link</code>\'s <code>text</code> and <code>title</code> attributes.</p>\n</li>\n<li>\n<p><code>class</code> <em>(text)</em> default soo_article_page_nav</p>\n<p>HTML class attribute for the wraptag</p>\n</li>\n<li>\n<p><code>active_class</code> <em>(text)</em> default here</p>\n<p>HTML class attribute for the <code>span</code> tag containing the current page number</p>\n</li>\n<li>\n<p><code>wraptag</code> <em>(text)</em> default empty</p>\n<p>HTML tag name (no brackets) to wrap the output</p>\n</li>\n<li>\n<p><code>break</code> <em>(text)</em> default br</p>\n<p>HTML tag name (no brackets) or text to separate items</p>\n</li>\n</ul>\n<h3 id=\"soo_if_article_page\">soo_if_article_page</h3>\n<pre><code>&lt;txp:soo_if_article_page&gt;\n   Output if true\n&lt;txp:else /&gt;\n   Output if false\n&lt;/txp:soo_if_article_page&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li><code>first</code> <em>(boolean or unset)</em> default unset</li>\n<li><code>last</code> <em>(boolean or unset)</em> default unset</li>\n</ul>\n<h4>Possible conditional tests:</h4>\n<dl>\n<dt>No attributes:</dt>\n<dd>Does the article have multiple pages?</dd>\n<dt><code>first=\"1\"</code></dt>\n<dd>Is this the first page of a multi-page article?</dd>\n<dt><code>last=\"1\"</code></dt>\n<dd>Is this the last page of a multi-page article?</dd>\n<dt><code>first=\"0\"</code></dt>\n<dd>Is this a page other than the first page?</dd>\n<dt><code>last=\"0\"</code></dt>\n<dd>Is this a page other than the last page?</dd>\n<dt><code>first=\"0\" last=\"0\"</code></dt>\n<dd>Is this a page other than the first or last page?</dd>\n</dl>\n<h3 id=\"soo_article_page_search_url\">soo_article_page_search_url</h3>\n<pre><code>&lt;txp:soo_article_page_search_url /&gt; &lt;!-- Link text is the URL --&gt;\n\n&lt;txp:soo_article_page_search_url&gt;Link text&lt;/txp:soo_article_page_search_url&gt;</code></pre>\n<h4>Attributes</h4>\n<ul>\n<li>\n<p><code>class</code> <em>(text)</em> default empty</p>\n<p>HTML class attribute for the link</p>\n</li>\n<li>\n<p><code>title</code> <em>(text)</em> default empty</p>\n<p>HTML title attribute for the link</p>\n</li>\n<li>\n<p><code>escape</code> <em>(&quot;html&quot; or unset)</em> default html</p>\n<p>Escape HTML entities such as &lt;, &gt; and &amp; in the <code>title</code> attribute</p>\n</li>\n</ul>\n<h2 id=\"examples\">Examples</h2>\n<h3>In an article form</h3>\n<pre><code>&lt;txp:soo_article_page quiet=\"1\" delimiter=\"[page break]\" /&gt;\n&lt;header&gt;\n    &lt;h2&gt;&lt;txp:title /&gt;&lt;/h2&gt;\n    &lt;txp:soo_article_page_number wraptag=\"p\" /&gt;\n&lt;/header&gt;\n&lt;article&gt;\n    &lt;txp:soo_article_page /&gt;\n    &lt;txp:soo_article_page_nav wraptag=\"ul\" break=\"li\" /&gt;\n&lt;/article&gt;</code></pre>\n<p>Running <code>soo_article_page</code> in quiet mode is necessary here because the <code>soo_article_page_number</code> tag comes before the article body (i.e., the second <code>soo_article_page</code> tag). Note that a custom <code>delimiter</code> must be set in the first of those tags.</p>\n<p>This form would work fine for normal (i.e., single-page) articles too, where <code>soo_article_page_number</code> and <code>soo_article_page_nav</code> will output nothing, and <code>soo_article_page</code> will output the entire article body.</p>\n<h3>Different article forms for single-page and multi-page articles</h3>\n<pre><code>&lt;txp:if_individual_article&gt;\n   &lt;txp:soo_article_page quiet=\"1\" /&gt;\n   &lt;txp:soo_if_article_page&gt;\n       &lt;txp:article form=\"paginated\" /&gt;\n   &lt;txp:else /&gt;\n       &lt;txp:article /&gt;\n   &lt;/txp:soo_if_article_page&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n<h3>Conditional text for formatting page links</h3>\n<pre><code>&lt;txp:soo_article_page /&gt;\n&lt;txp:soo_article_page_link rel=\"prev\" text=\"{prev} {page}\" /&gt;\n&lt;txp:soo_if_article_page first=\"0\" last=\"0\"&gt; — &lt;/txp:soo_if_article_page&gt;\n&lt;txp:soo_article_page_link rel=\"next\" text=\"{next} {page}\" /&gt;</code></pre>\n<p>Because the <code>prev</code> and <code>next</code> links are not set to <code>showalways</code>, we only want the separator text on a middle page, when both links are visible.</p>\n<h3>In a search-results article form</h3>\n<pre><code>&lt;txp:soo_article_page quiet=\"1\" /&gt;\n&lt;h3&gt;&lt;txp:soo_article_page_search_url&gt;&lt;txp:title /&gt;&lt;/txp:soo_article_page_search_url&gt;&lt;/h3&gt;\n&lt;p&gt;&lt;txp:search_result_excerpt limit=\"2\" /&gt;\n    &lt;br /&gt;\n    &lt;small&gt;&lt;txp:soo_article_page_search_url /&gt; : &lt;txp:search_result_date /&gt;&lt;/small&gt;\n&lt;/p&gt;</code></pre>\n<h3><code>soo_article_page_nav</code> as a numbered widget, à la Google</h3>\n<pre><code>&lt;txp:soo_article_page_nav /&gt;</code></pre>\n<h3><code>soo_article_page_nav</code> as a table of contents</h3>\n<pre><code>&lt;txp:soo_article_page_nav wraptag=\"ul\" break=\"li\" text=\"{pg}. {title}\" /&gt;</code></pre>\n<p>or</p>\n<pre><code>&lt;txp:soo_article_page_nav wraptag=\"ol\" break=\"li\" text=\"{title}\" /&gt;</code></pre>\n<h2 id=\"history\">History</h2>\n<h3>0.1.2 (2017-03-11)</h3>\n<ul>\n<li>Documentation update &amp; reformatting</li>\n</ul>\n<h3>0.1.1 (2017-03-09)</h3>\n<ul>\n<li>Removed <code>soo_page_break</code> tag</li>\n<li>Added <code>soo_article_page_title</code> tag</li>\n<li>New options for <code>soo_article_page_link</code> and <code>soo_article_page_nav</code></li>\n</ul>\n<h3>0.1.0 (2017-03-08)</h3>\n<p>Initial release.</p>', '\n// Register public tags.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_article_page\')\n        ->register(\'soo_article_page_title\')\n        ->register(\'soo_article_page_number\')\n        ->register(\'soo_article_page_link\')\n        ->register(\'soo_article_page_nav\')\n        ->register(\'soo_if_article_page\')\n        ->register(\'soo_article_page_search_url\')\n        ;\n}\n\nfunction soo_article_page($atts)\n{\n    global $thisarticle, $is_article_body, $pg, $soo_article_pages;\n\n    static $soo_article_page_id;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'delimiter\' => \'[break]\',\n        \'quiet\'     => 0,\n    ), $atts));\n\n    if ($soo_article_page_id != $thisarticle[\'thisid\']) {\n        $soo_article_page_id = $thisarticle[\'thisid\'];\n        $soo_article_pages = array();\n\n        $delimiter = preg_quote($delimiter, \'%\');\n        $delimiter = \'%(<p>)?\'.$delimiter.\'\\s*(</p>)?%\';\n        $pages = preg_split($delimiter, $thisarticle[\'body\']);\n        $pages = array_map(\'trim\', $pages);\n\n        $matches = array();\n        foreach ($pages as $page) {\n            if (preg_match(\'/<(h\\d).*?>(.+)?<\\/\\1>/\', $page, $matches)) {\n                $header = $matches[2];\n            } else {\n                $header = \'\';\n            }\n            $soo_article_pages[] = array(\'header\' => $header, \'body\' => $page);\n        }\n\n        $soo_article_pages = array_combine(range(1, count($pages)), $soo_article_pages);\n    }\n\n    $page_number = $pg ? $pg : 1;\n    if ($page_number > count($soo_article_pages)) {\n        return \'\';\n    }\n    if ($quiet) {\n        return \'\';\n    }\n\n    $was_article_body = $is_article_body;\n    $is_article_body = 1;\n    $out = parse($soo_article_pages[$page_number][\'body\']);\n    $is_article_body = $was_article_body;\n\n    return $out;\n}\n\nfunction soo_article_page_title($atts)\n{\n    global $pg, $soo_article_pages;\n\n    extract(lAtts(array(\n        \'page_number\' => $pg ? $pg : 1,\n        \'class\'       => \'\',\n        \'wraptag\'     => \'\',\n    ), $atts));\n\n    assert_article();\n\n    if (! is_int($page_number) || $page_number < 1) return \'\';\n    $num_pages = count($soo_article_pages);\n    if ($num_pages == 1 || $num_pages < $page_number) return \'\';\n\n    if ($title = $soo_article_pages[$page_number][\'header\']) {\n        return tag($title, $wraptag, array(\'class\'=>$class));\n    }\n\n}\n\nfunction soo_article_page_number($atts)\n{\n    global $pg, $soo_article_pages;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'text\'    => \'{page} {pg} {of} {total}\',\n        \'class\'   => \'\',\n        \'wraptag\' => \'\',\n    ), $atts));\n\n    if (count($soo_article_pages) < 2) {\n        return \'\';\n    }\n\n    $text = _soo_article_page_replace($text, array(\n        \'{total}\' => count($soo_article_pages),\n        \'{pg}\'    => $pg ? $pg : 1,\n    ));\n    return tag($text, $wraptag, array(\'class\'=>$class));\n}\n\nfunction soo_article_page_link($atts)\n{\n    global $thisarticle, $pg, $soo_article_pages;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'page_number\' => 0,\n        \'text\'        => \'{next}\',\n        \'rel\'         => \'next\',\n        \'rev\'         => \'\',\n        \'showalways\'  => 0,\n        \'title\'       => \'\',\n        \'class\'       => \'\',\n        \'escape\'      => \'html\',\n    ), $atts));\n\n    $thispage = $pg ? (int) $pg : 1;\n\n    $total = count($soo_article_pages);\n    if (($page_number > $total) || ($total == 1)) {\n        return $showalways ? $text : \'\';\n    }\n\n    $goto = array(\n        \'prev\'  => $thispage > 1 ? $thispage - 1 : null,\n        \'next\'  => $thispage < $total ? $thispage + 1 : null,\n        \'first\' => 1,\n        \'last\'  => $total,\n    );\n\n    if (! array_key_exists($rel, $goto)) {\n        if ($page_number > 0 && $page_number <= $total) {\n            if (! $rel) {\n                if (in_array($page_number, $goto)) {\n                    $rel = array_search($page_number, $goto);\n                } else {\n                    $rel = \'page \'.$page_number;\n                }\n            }\n            $goto[$rel] = $page_number;\n        } else {\n            trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $page_number)), E_USER_NOTICE);\n            return \'\';\n        }\n    }\n\n    $dict = array(\n        \'{pg}\'    => $goto[$rel],\n        \'{title}\' => soo_article_page_title(array(\'page_number\' => $goto[$rel])),\n    );\n    $text = _soo_article_page_replace($text, $dict);\n\n    if ($goto[$rel] === null) {\n        return $showalways ? $text : \'\';\n    }\n\n    if ($title) {\n        $title = _soo_article_page_replace($title, $dict);\n    }\n\n    $url = permlinkurl($thisarticle);\n\n    if ($url) {\n        if (preg_match(\'/\\?\\w+=/\', $url)) {\n            $url .= \'&amp;pg=\';\n        } else {\n            $url .= \'?pg=\';\n        }\n        $url .= $goto[$rel];\n\n        if ($escape == \'html\') {\n            $title = escape_title($title);\n        }\n\n        $revler = array(\'next\' => \'prev\', \'prev\' => \'next\');\n        if (isset($revler[$rel])) {\n            $rev = $revler[$rel];\n        } elseif (! $rev) {\n            if ($thispage == 1) {\n                $rev = \'first\';\n            } elseif ($thispage == $total) {\n                $rev = \'last\';\n            } else {\n                $rev = \'page \'.$thispage;\n            }\n        }\n\n        return tag($text, \'a\', array(\n            \'rel\'   => $rel,\n            \'rev\'   => $rev,\n            \'href\'  => $url,\n            \'title\' => $title,\n            \'class\' => $class\n            )\n        );\n    }\n}\n\nfunction soo_article_page_nav($atts)\n{\n    global $pg, $soo_article_pages;\n\n    $total = count($soo_article_pages);\n    if ($total < 2) {\n        return \'\';\n    }\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'text\'         => \'{pg}\',\n        \'class\'        => __FUNCTION__,\n        \'active_class\' => \'here\',\n        \'wraptag\'      => \'\',\n        \'break\'        => \'br\',\n    ), $atts));\n\n    $out = array();\n    $link_atts = array(\n        \'class\' => $class,\n        \'text\'  => $text,\n        \'rel\'   => \'\',\n    );\n    $active_class = $active_class ? \"class=\\\"$active_class\\\"\" : \'\';\n    $page_number = $pg ? (int) $pg : 1;\n\n    for ($n = 1; $n <= $total; $n++) {\n        if ($n == $page_number) {\n            $text = _soo_article_page_replace($text, array(\n                \'{title}\' => soo_article_page_title(array(\'page_number\' => $n)),\n                \'{pg}\'    => $n,\n            ));\n\n            $out[] = tag($text, \'span\', $active_class);\n        } else {\n            $link_atts[\'page_number\'] = $n;\n            $link_atts[\'title\'] = gTxt(\'page\').\' \'.$n;\n            $out[] = soo_article_page_link($link_atts);\n        }\n    }\n\n    return $out ? doWrap($out, $wraptag, $break, $class) : \'\';\n}\n\nfunction soo_if_article_page($atts, $thing)\n{\n    global $pg, $soo_article_pages;\n\n    assert_article();\n\n    if (empty($soo_article_pages)) {\n        trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\': soo_article_page \'.gTxt(\'required\'), E_USER_NOTICE);\n        return parse($thing);\n    }\n    extract(lAtts(array(\n        \'first\' => null,\n        \'last\'  => null,\n    ), $atts));\n\n    $page = $pg ? $pg : 1;\n\n    if (count($soo_article_pages) < 2) {\n        $x = false;\n    } elseif (! empty($last)) {\n        $x = $page == count($soo_article_pages);\n    } elseif (! empty($first)) {\n        $x = $page == 1;\n    } elseif (($first === \"0\") && ($last === \"0\")) {\n        $x = ($page != 1) && ($page != count($soo_article_pages));\n    } elseif ($last === \"0\") {\n        $x = $page != count($soo_article_pages);\n    } elseif ($first === \"0\") {\n        $x = $page != 1;\n    } else {\n        $x = true;\n    }\n\n    return $thing ? parse($thing, $x) : $x;\n}\n\nfunction soo_article_page_search_url($atts, $thing)\n{\n    extract(lAtts(array(\n        \'class\'  => \'\',\n        \'title\'  => \'\',\n        \'escape\' => \'html\',\n    ), $atts));\n\n    global $thisarticle, $q, $soo_article_pages;\n\n    assert_article();\n\n    if (empty($soo_article_pages)) {\n        trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\': soo_article_page \'.gTxt(\'required\'), E_USER_NOTICE);\n        return parse($thing);\n    }\n\n    $found = false;\n    foreach ($soo_article_pages as $n => $page) {\n        if (stripos($page[\'body\'], $q)) {\n            $found = true;\n            break;\n        }\n    }\n    if (! $found) {\n        if (stripos($thisarticle[\'title\'], $q) !== false) {\n            $found = $n = 1;\n        }\n    }\n\n    if (! $found) return \'\';\n\n    $url = permlinkurl($thisarticle);\n\n    if (count($soo_article_pages) > 1) {\n        if (preg_match(\'/\\?\\w+=/\', $url)) {\n            $url .= \'&amp;pg=\';\n        } else {\n            $url .= \'?pg=\';\n        }\n        $url .= $n;\n    }\n\n    if ($thing === null) {\n        $thing = $url;\n    }\n\n    if ($escape == \'html\') {\n        $title = escape_title($title);\n    }\n\n    return tag(parse($thing), \'a\', array(\n        \'href\' => $url,\n        \'title\' => $title,\n        \'class\' => $class\n    ));\n}\n\nfunction _soo_article_page_replace($text, $extra = array())\n{\n    $dict = array(\n    \'{page}\' => gTxt(\'page\'),\n    \'{of}\'   => gTxt(\'of\'),\n    \'{next}\' => gTxt(\'next\'),\n    \'{prev}\' => gTxt(\'prev\'),\n    ) + $extra;\n\n    return str_replace(array_keys($dict), $dict, $text);\n}\n', '\n// Register public tags.\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_article_page\')\n        ->register(\'soo_article_page_title\')\n        ->register(\'soo_article_page_number\')\n        ->register(\'soo_article_page_link\')\n        ->register(\'soo_article_page_nav\')\n        ->register(\'soo_if_article_page\')\n        ->register(\'soo_article_page_search_url\')\n        ;\n}\n\nfunction soo_article_page($atts)\n{\n    global $thisarticle, $is_article_body, $pg, $soo_article_pages;\n\n    static $soo_article_page_id;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'delimiter\' => \'[break]\',\n        \'quiet\'     => 0,\n    ), $atts));\n\n    if ($soo_article_page_id != $thisarticle[\'thisid\']) {\n        $soo_article_page_id = $thisarticle[\'thisid\'];\n        $soo_article_pages = array();\n\n        $delimiter = preg_quote($delimiter, \'%\');\n        $delimiter = \'%(<p>)?\'.$delimiter.\'\\s*(</p>)?%\';\n        $pages = preg_split($delimiter, $thisarticle[\'body\']);\n        $pages = array_map(\'trim\', $pages);\n\n        $matches = array();\n        foreach ($pages as $page) {\n            if (preg_match(\'/<(h\\d).*?>(.+)?<\\/\\1>/\', $page, $matches)) {\n                $header = $matches[2];\n            } else {\n                $header = \'\';\n            }\n            $soo_article_pages[] = array(\'header\' => $header, \'body\' => $page);\n        }\n\n        $soo_article_pages = array_combine(range(1, count($pages)), $soo_article_pages);\n    }\n\n    $page_number = $pg ? $pg : 1;\n    if ($page_number > count($soo_article_pages)) {\n        return \'\';\n    }\n    if ($quiet) {\n        return \'\';\n    }\n\n    $was_article_body = $is_article_body;\n    $is_article_body = 1;\n    $out = parse($soo_article_pages[$page_number][\'body\']);\n    $is_article_body = $was_article_body;\n\n    return $out;\n}\n\nfunction soo_article_page_title($atts)\n{\n    global $pg, $soo_article_pages;\n\n    extract(lAtts(array(\n        \'page_number\' => $pg ? $pg : 1,\n        \'class\'       => \'\',\n        \'wraptag\'     => \'\',\n    ), $atts));\n\n    assert_article();\n\n    if (! is_int($page_number) || $page_number < 1) return \'\';\n    $num_pages = count($soo_article_pages);\n    if ($num_pages == 1 || $num_pages < $page_number) return \'\';\n\n    if ($title = $soo_article_pages[$page_number][\'header\']) {\n        return tag($title, $wraptag, array(\'class\'=>$class));\n    }\n\n}\n\nfunction soo_article_page_number($atts)\n{\n    global $pg, $soo_article_pages;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'text\'    => \'{page} {pg} {of} {total}\',\n        \'class\'   => \'\',\n        \'wraptag\' => \'\',\n    ), $atts));\n\n    if (count($soo_article_pages) < 2) {\n        return \'\';\n    }\n\n    $text = _soo_article_page_replace($text, array(\n        \'{total}\' => count($soo_article_pages),\n        \'{pg}\'    => $pg ? $pg : 1,\n    ));\n    return tag($text, $wraptag, array(\'class\'=>$class));\n}\n\nfunction soo_article_page_link($atts)\n{\n    global $thisarticle, $pg, $soo_article_pages;\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'page_number\' => 0,\n        \'text\'        => \'{next}\',\n        \'rel\'         => \'next\',\n        \'rev\'         => \'\',\n        \'showalways\'  => 0,\n        \'title\'       => \'\',\n        \'class\'       => \'\',\n        \'escape\'      => \'html\',\n    ), $atts));\n\n    $thispage = $pg ? (int) $pg : 1;\n\n    $total = count($soo_article_pages);\n    if (($page_number > $total) || ($total == 1)) {\n        return $showalways ? $text : \'\';\n    }\n\n    $goto = array(\n        \'prev\'  => $thispage > 1 ? $thispage - 1 : null,\n        \'next\'  => $thispage < $total ? $thispage + 1 : null,\n        \'first\' => 1,\n        \'last\'  => $total,\n    );\n\n    if (! array_key_exists($rel, $goto)) {\n        if ($page_number > 0 && $page_number <= $total) {\n            if (! $rel) {\n                if (in_array($page_number, $goto)) {\n                    $rel = array_search($page_number, $goto);\n                } else {\n                    $rel = \'page \'.$page_number;\n                }\n            }\n            $goto[$rel] = $page_number;\n        } else {\n            trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => $page_number)), E_USER_NOTICE);\n            return \'\';\n        }\n    }\n\n    $dict = array(\n        \'{pg}\'    => $goto[$rel],\n        \'{title}\' => soo_article_page_title(array(\'page_number\' => $goto[$rel])),\n    );\n    $text = _soo_article_page_replace($text, $dict);\n\n    if ($goto[$rel] === null) {\n        return $showalways ? $text : \'\';\n    }\n\n    if ($title) {\n        $title = _soo_article_page_replace($title, $dict);\n    }\n\n    $url = permlinkurl($thisarticle);\n\n    if ($url) {\n        if (preg_match(\'/\\?\\w+=/\', $url)) {\n            $url .= \'&amp;pg=\';\n        } else {\n            $url .= \'?pg=\';\n        }\n        $url .= $goto[$rel];\n\n        if ($escape == \'html\') {\n            $title = escape_title($title);\n        }\n\n        $revler = array(\'next\' => \'prev\', \'prev\' => \'next\');\n        if (isset($revler[$rel])) {\n            $rev = $revler[$rel];\n        } elseif (! $rev) {\n            if ($thispage == 1) {\n                $rev = \'first\';\n            } elseif ($thispage == $total) {\n                $rev = \'last\';\n            } else {\n                $rev = \'page \'.$thispage;\n            }\n        }\n\n        return tag($text, \'a\', array(\n            \'rel\'   => $rel,\n            \'rev\'   => $rev,\n            \'href\'  => $url,\n            \'title\' => $title,\n            \'class\' => $class\n            )\n        );\n    }\n}\n\nfunction soo_article_page_nav($atts)\n{\n    global $pg, $soo_article_pages;\n\n    $total = count($soo_article_pages);\n    if ($total < 2) {\n        return \'\';\n    }\n\n    assert_article();\n\n    extract(lAtts(array(\n        \'text\'         => \'{pg}\',\n        \'class\'        => __FUNCTION__,\n        \'active_class\' => \'here\',\n        \'wraptag\'      => \'\',\n        \'break\'        => \'br\',\n    ), $atts));\n\n    $out = array();\n    $link_atts = array(\n        \'class\' => $class,\n        \'text\'  => $text,\n        \'rel\'   => \'\',\n    );\n    $active_class = $active_class ? \"class=\\\"$active_class\\\"\" : \'\';\n    $page_number = $pg ? (int) $pg : 1;\n\n    for ($n = 1; $n <= $total; $n++) {\n        if ($n == $page_number) {\n            $text = _soo_article_page_replace($text, array(\n                \'{title}\' => soo_article_page_title(array(\'page_number\' => $n)),\n                \'{pg}\'    => $n,\n            ));\n\n            $out[] = tag($text, \'span\', $active_class);\n        } else {\n            $link_atts[\'page_number\'] = $n;\n            $link_atts[\'title\'] = gTxt(\'page\').\' \'.$n;\n            $out[] = soo_article_page_link($link_atts);\n        }\n    }\n\n    return $out ? doWrap($out, $wraptag, $break, $class) : \'\';\n}\n\nfunction soo_if_article_page($atts, $thing)\n{\n    global $pg, $soo_article_pages;\n\n    assert_article();\n\n    if (empty($soo_article_pages)) {\n        trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\': soo_article_page \'.gTxt(\'required\'), E_USER_NOTICE);\n        return parse($thing);\n    }\n    extract(lAtts(array(\n        \'first\' => null,\n        \'last\'  => null,\n    ), $atts));\n\n    $page = $pg ? $pg : 1;\n\n    if (count($soo_article_pages) < 2) {\n        $x = false;\n    } elseif (! empty($last)) {\n        $x = $page == count($soo_article_pages);\n    } elseif (! empty($first)) {\n        $x = $page == 1;\n    } elseif (($first === \"0\") && ($last === \"0\")) {\n        $x = ($page != 1) && ($page != count($soo_article_pages));\n    } elseif ($last === \"0\") {\n        $x = $page != count($soo_article_pages);\n    } elseif ($first === \"0\") {\n        $x = $page != 1;\n    } else {\n        $x = true;\n    }\n\n    return $thing ? parse($thing, $x) : $x;\n}\n\nfunction soo_article_page_search_url($atts, $thing)\n{\n    extract(lAtts(array(\n        \'class\'  => \'\',\n        \'title\'  => \'\',\n        \'escape\' => \'html\',\n    ), $atts));\n\n    global $thisarticle, $q, $soo_article_pages;\n\n    assert_article();\n\n    if (empty($soo_article_pages)) {\n        trigger_error(gTxt(\'tag_error\').\' \'.__FUNCTION__.\': soo_article_page \'.gTxt(\'required\'), E_USER_NOTICE);\n        return parse($thing);\n    }\n\n    $found = false;\n    foreach ($soo_article_pages as $n => $page) {\n        if (stripos($page[\'body\'], $q)) {\n            $found = true;\n            break;\n        }\n    }\n    if (! $found) {\n        if (stripos($thisarticle[\'title\'], $q) !== false) {\n            $found = $n = 1;\n        }\n    }\n\n    if (! $found) return \'\';\n\n    $url = permlinkurl($thisarticle);\n\n    if (count($soo_article_pages) > 1) {\n        if (preg_match(\'/\\?\\w+=/\', $url)) {\n            $url .= \'&amp;pg=\';\n        } else {\n            $url .= \'?pg=\';\n        }\n        $url .= $n;\n    }\n\n    if ($thing === null) {\n        $thing = $url;\n    }\n\n    if ($escape == \'html\') {\n        $title = escape_title($title);\n    }\n\n    return tag(parse($thing), \'a\', array(\n        \'href\' => $url,\n        \'title\' => $title,\n        \'class\' => $class\n    ));\n}\n\nfunction _soo_article_page_replace($text, $extra = array())\n{\n    $dict = array(\n    \'{page}\' => gTxt(\'page\'),\n    \'{of}\'   => gTxt(\'of\'),\n    \'{next}\' => gTxt(\'next\'),\n    \'{prev}\' => gTxt(\'prev\'),\n    ) + $extra;\n\n    return str_replace(array_keys($dict), $dict, $text);\n}\n', '98c6568d22f7985b136d48921af8a676', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_toc', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.1.5', 'Auto-generate table of contents for an article', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help h6 {font-size: 1em;margin-left:2em;font-style:oblique;}\ndiv#sed_help li {list-style-type: disc;}\ndiv#sed_help li li {list-style-type: circle;}\ndiv#sed_help li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\n</style>\n	<p><div id=\"sed_help\"></p>\n\n	<h1>soo_toc (table of contents generator)</h1>\n\n <div id=\"toc\">\n\n	<h2>Contents</h2>\n\n	<ul>\n		<li><a href=\"#overview\">Overview</a></li>\n		<li><a href=\"#syntax\">Syntax</a></li>\n		<li><a href=\"#attributes\">Attributes</a></li>\n		<li><a href=\"#usage\">Usage</a>\n	<ul>\n		<li><a href=\"#general\">General</a></li>\n		<li><a href=\"#headings\">The article headings</a></li>\n		<li><a href=\"#level\">Level</a></li>\n		<li><a href=\"#urls\">Relative vs. absolute <span class=\"caps\">URL</span>s</a></li>\n		<li><a href=\"#nested\">Nested output</a></li>\n	</ul></li>\n		<li><a href=\"#notes\">Notes</a>\n	<ul>\n		<li><a href=\"#performance\">Performance</a></li>\n		<li><a href=\"#history\">History</a></li>\n	</ul></li>\n	</ul>\n\n </div>\n\n	<h2 id=\"overview\">Overview</h2>\n\n	<p>This is a <a href=\"http://textpattern.com\">Textpattern</a> plugin. It contains one tag, <code>&#60;txp:soo_toc /&#62;</code>. This tag can be placed just about anywhere and will automatically generate a table of contents for any article it finds, so long as that article contains <span class=\"caps\">HTML</span> headings (h1, h2, etc.) with id attributes. Of course it requires an article context, either list or individual.</p>\n\n	<p>It&#8217;s much like the <a href=\"http://textpattern.org/plugins/658/cbs_article_index\"><code>cbs_article_index</code> plugin</a>. However, <code>&#60;txp:soo_toc /&#62;</code> offers nested list output, heading-level filtering, and a choice of relative or absolute <span class=\"caps\">URL</span>s. If you don&#8217;t need those features, you might prefer <a href=\"http://textpattern.org/plugins/658/cbs_article_index\"><code>cbs_article_index</code></a> for its lighter footprint.</p>\n\n	<h2 id=\"syntax\">Syntax</h2>\n\n<pre>&#60;txp:soo_toc /&#62;\n</pre>\n\n	<h2 id=\"attributes\">Attributes</h2>\n\n	<p>The first five are standard Textpattern attributes; only the last two (<code>level</code> and <code>full_url</code>) are plugin-specific.</p>\n\n	<ul>\n		<li><code>label=&#34;text&#34;</code><br />\nLabel to precede the contents list. Optional; default is unset.</li>\n		<li><code>labeltag=&#34;tag name&#34;</code><br />\n(X)HTML element (without brackets) for the label. Optional; default is unset.</li>\n		<li><code>break=&#34;text&#34;</code><br />\n(X)HTML element (without brackets) or text for separating list items. Optional; default is <code>li</code>. If you change this, you should also change the <code>wraptag</code>.</li>\n		<li><code>wraptag=&#34;tag name&#34;</code><br />\n(X)HTML element for enclosing the list. Optional; default is <code>ul</code>. If you change this to something other than <code>ol</code>, you should also change the <code>break</code>.</li>\n		<li><code>class=&#34;class name&#34;</code><br />\n(X)HTML class attribute to be applied to <code>wraptag</code>. Optional; default is <code>toc</code>.</li>\n		<li><code>level=&#34;value&#34;</code><br />\nNumber in the range 1&#8211;6, indicating the highest heading level to include in the table of contents (see <a href=\"#level\">Level, below</a>). <span class=\"required\">Required</span>; default is <code>6</code> (include all headings).</li>\n		<li><code>full_url=&#34;boolean&#34;</code><br />\nIf <code>true</code>, use absolute instead of relative <span class=\"caps\">URL</span>s. Optional (but see <a href=\"#urls\">Relative vs. Absolute <span class=\"caps\">URL</span>s</a>, below); default is unset (false).</li>\n	</ul>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<h3 id=\"general\">General</h3>\n\n	<p>Can be used in an article body, in a page, or in a form. If used with relative <span class=\"caps\">URL</span>s (the default setting) it <span class=\"required\">requires</span> an individual article context. If used in an article list, or on a page in which the <code>base</code> element indicates anything other than current page, the <code>full_url</code> attribute must be set to <code>true</code> (or anything other than <code>0</code>). (See <a href=\"#urls\">Relative vs. Absolute <span class=\"caps\">URL</span>s</a>, below.)</p>\n\n	<h3 id=\"headings\">The article headings</h3>\n\n	<p>Only headings that have an <code>id</code> attribute will be included. This is to give the links in the table of contents something to point to. In Textile, you can apply the <code>id</code> attribute like this:</p>\n\n<pre>h3(#example). Example\n</pre>\n\n	<p>to produce:</p>\n\n<pre>&#60;h3 id=&#34;example&#34;&#62;Example&#60;/h3&#62;\n</pre>\n\n	<h3 id=\"level\">Level</h3>\n\n	<p>The <code>level</code> attribute allows you to restrict which heading levels to include in the table of contents. For example, <code>level=&#34;3&#34;</code> will restrict output to <code>h1</code>, <code>h2</code>, and <code>h3</code> headings. The default value is <code>6</code>, show all headings.</p>\n\n	<p>You might use this to have both a full and a concise table of contents, one in the sidebar and the other in the article body. Or combine with a bit of javascript to allow the user to expand or collapse the table of contents.</p>\n\n	<h3 id=\"urls\">Relative vs. absolute <span class=\"caps\">URL</span>s</h3>\n\n	<p>The default output uses same-page relative <span class=\"caps\">URL</span>s (e.g. <code>href=&#34;#id_name&#34;</code>), which will only work correctly in an individual article context. To get absolute (full) <span class=\"caps\">URL</span>s instead, set the <code>full_url</code> attribute to <code>true</code> (or any value other than <code>0</code>).</p>\n\n	<p>If you use a <code>base</code> tag to point to the site root (or anything other than the current page), you will also need to use full <span class=\"caps\">URL</span>s. (N. B., such usage of <code>base</code> is, strictly speaking, incorrect.)</p>\n\n	<h3 id=\"nested\">Nested output</h3>\n\n	<p>If the <code>wraptag</code> is <code>ul</code> (the default value) or <code>ol</code>, and if the article has different levels of headings, the table of contents will be a nested list. For example, given the following article headings (shown in Textile):</p>\n\n<pre>h2(#foo). Foo\n...\nh3(#bar). Bar\n</pre>\n\n	<p>the plugin will produce, using default attributes:</p>\n\n<pre>&#60;ul class=&#34;toc&#34;&#62;\n	&#60;li&#62;&#60;a href=&#34;#foo&#34;&#62;Foo&#60;/a&#62;\n	&#60;ul&#62;\n		&#60;li&#62;&#60;a href=&#34;#bar&#34;&#62;Bar&#60;/a&#62;&#60;/li&#62;\n	&#60;/ul&#62;&#60;/li&#62;\n&#60;/ul&#62;\n</pre>\n\n	<p>For this nested output to make sense, the article&#8217;s heading structure must also make sense. <span class=\"warning\">In particular:</span></p>\n\n	<ul>\n		<li>do not jump upwards more than one heading level at a time (e.g., go directly from <code>h2</code> to <code>h4</code>)</li>\n		<li>the first heading should be at the lowest heading level (e.g., if the first heading in the article body is <code>h2</code>, there should not be any <code>h1</code>s in the article body)</li>\n	</ul>\n\n	<p>Because the plugin ignores headings with no id attribute, you could get unexpected results (and possibly <span class=\"caps\">XHTML</span> validation errors) if you are inconsistent about assigning id to headings.</p>\n\n	<h2 id=\"notes\">Notes</h2>\n\n	<h3 id=\"performance\">Performance</h3>\n\n	<p>The plugin runs a <code>preg_match_all</code> search on the article&#8217;s <span class=\"caps\">HTML</span> body, and another <code>preg_match</code> on each heading. Theoretically this could be a performance concern in some situations, particularly in an article list context.</p>\n\n	<h3 id=\"history\">History</h3>\n\n	<p>This plugin is very similar to the <a href=\"http://textpattern.org/plugins/658/cbs_article_index\"><code>cbs_article_index</code> plugin</a> (didn&#8217;t realize this when I wrote it), but with more options for output and control.</p>\n\n	<h4 id=\"version_0_1_5\">Version 0.1.5, 2017-02-15</h4>\n\n	<ul>\n		<li>Textpattern 4.6 compatibility update</li>\n	</ul>\n\n	<h4 id=\"version_0_1_4\">Version 0.1.4, 2009.4.26</h4>\n\n	<ul>\n		<li>Minor code cleaning</li>\n	</ul>\n\n	<h4 id=\"version_0_1_3\">Version 0.1.3, 2008.2.03</h4>\n\n	<ul>\n		<li>Code cleaning, plus valid <span class=\"caps\">XHTML</span> output even for articles with unsemantic heading structure</li>\n	</ul>\n\n	<h4 id=\"version_0_1_2\">Version 0.1.2, 2008.2.03</h4>\n\n	<ul>\n		<li>Fixed bug (design flaw, actually) re nested list output: thanks to Dejan for spotting this</li>\n	</ul>\n\n	<h4 id=\"version_0_1_1\">Version 0.1.1, 2008.1.29</h4>\n\n	<ul>\n		<li>Fixed bug re headings containing <span class=\"caps\">HTML</span> tags</li>\n	</ul>\n\n	<h4 id=\"version_0_1\">Version 0.1, 2008.1.26</h4>\n\n	<ul>\n		<li>Automatically generate a linked table of contents for an article</li>\n		<li>Nested output for (X)HTML lists</li>\n		<li>Option to filter results with the <code>level</code> attribute.</li>\n		<li>Option to use full <span class=\"caps\">URL</span>s</li>\n	</ul>\n\n	<p></div></p>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_toc\');\n}\n\nfunction soo_toc( $atts ) {\n\n	global $thisarticle;\n	assert_article();\n\n	extract(lAtts(array(\n		\'label\'			=> \'\',\n		\'labeltag\'		=> \'\',\n		\'break\'			=> \'li\',\n		\'wraptag\'		=> \'ul\',\n		\'class\'			=> \'toc\',\n		\'level\'			=> 6,\n		\'full_url\'		=> \'\',\n	), $atts));\n\n	$full_url = $full_url ? permlinkurl($thisarticle) : \'\';\n\n	$pattern = \'/<h(\\d)[^>]+?id\\s*=\\s*\"([\\w-]+)\"[^>]*>(.+?)<\\\\/h\\\\1/\';\n	preg_match_all($pattern, $thisarticle[\'body\'], $headings, PREG_SET_ORDER);\n\n	$items = array();\n\n	foreach ( $headings as $h )\n		if ( $h[1] <= $level ) {\n 			$filtered[] = $h;\n			$items[] = \'<a href=\"\' . $full_url . \'#\' . $h[2] . \'\">\' . $h[3] . \'</a>\';\n		}\n\n	if ( !$items )\n		return;\n\n	if ( $wraptag != \'ul\' && $wraptag != \'ol\' )\n		return doLabel($label, $labeltag) . doWrap($items, $wraptag, $break, $class);\n\n	$nest_level = 1;\n	$lines[] = soo_toc_prepend(\'<li>\' . $items[0], t, $nest_level);\n\n	for ($i = 1; $i < count($items); $i++) {\n		$level_change = $filtered[$i][1] - $filtered[$i-1][1];\n		if ($level_change > 0)\n			for ($j = 1; $j <= $level_change; $j++) {\n				$lines[] = soo_toc_prepend(\"<$wraptag>\", t, $nest_level).\n					($level_change > $j ? \'<li>\' : \'\');\n				$nest_level ++;\n			}\n		elseif ($level_change < 0)\n			for ($j = 1; $j <= -$level_change; $j++) {\n				$nest_level --;\n				$lines[] = soo_toc_prepend(\"</$wraptag></li>\", t, $nest_level);\n			}\n		$lines[] = soo_toc_prepend(\'<li>\' . $items[$i], t, $nest_level);\n	}\n\n	while ($nest_level > 1) {\n		$nest_level --;\n		$lines[] = soo_toc_prepend(\"</$wraptag></li>\", t, $nest_level);\n	}\n\n	for ($i = 0; $i < count($lines)-1; $i++)\n		if ( preg_match(\'/<li>/\', $lines[$i])\n			&& !preg_match(\"/<$wraptag>/\", $lines[$i+1]) )\n				$lines[$i] .= \'</li>\';\n\n	if ( !preg_match(\"/<\\/$wraptag>/\", $lines[count($lines)-1]) )\n		$lines[count($lines)-1] .= \'</li>\';\n\n	$out = \'<\' . $wraptag . ( $class ? \' class=\"\' . $class . \'\">\' : \'>\' )\n		. n . join(n, $lines) . n . \"</$wraptag>\" . n;\n\n	return doLabel($label, $labeltag) . n . $out;\n}\n\nfunction soo_toc_prepend($base, $add, $times) {\n	for ( $i = 1; $i <= $times; $i++ )\n		$base = $add . $base;\n	return $base;\n}', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_toc\');\n}\n\nfunction soo_toc( $atts ) {\n\n	global $thisarticle;\n	assert_article();\n\n	extract(lAtts(array(\n		\'label\'			=> \'\',\n		\'labeltag\'		=> \'\',\n		\'break\'			=> \'li\',\n		\'wraptag\'		=> \'ul\',\n		\'class\'			=> \'toc\',\n		\'level\'			=> 6,\n		\'full_url\'		=> \'\',\n	), $atts));\n\n	$full_url = $full_url ? permlinkurl($thisarticle) : \'\';\n\n	$pattern = \'/<h(\\d)[^>]+?id\\s*=\\s*\"([\\w-]+)\"[^>]*>(.+?)<\\\\/h\\\\1/\';\n	preg_match_all($pattern, $thisarticle[\'body\'], $headings, PREG_SET_ORDER);\n\n	$items = array();\n\n	foreach ( $headings as $h )\n		if ( $h[1] <= $level ) {\n 			$filtered[] = $h;\n			$items[] = \'<a href=\"\' . $full_url . \'#\' . $h[2] . \'\">\' . $h[3] . \'</a>\';\n		}\n\n	if ( !$items )\n		return;\n\n	if ( $wraptag != \'ul\' && $wraptag != \'ol\' )\n		return doLabel($label, $labeltag) . doWrap($items, $wraptag, $break, $class);\n\n	$nest_level = 1;\n	$lines[] = soo_toc_prepend(\'<li>\' . $items[0], t, $nest_level);\n\n	for ($i = 1; $i < count($items); $i++) {\n		$level_change = $filtered[$i][1] - $filtered[$i-1][1];\n		if ($level_change > 0)\n			for ($j = 1; $j <= $level_change; $j++) {\n				$lines[] = soo_toc_prepend(\"<$wraptag>\", t, $nest_level).\n					($level_change > $j ? \'<li>\' : \'\');\n				$nest_level ++;\n			}\n		elseif ($level_change < 0)\n			for ($j = 1; $j <= -$level_change; $j++) {\n				$nest_level --;\n				$lines[] = soo_toc_prepend(\"</$wraptag></li>\", t, $nest_level);\n			}\n		$lines[] = soo_toc_prepend(\'<li>\' . $items[$i], t, $nest_level);\n	}\n\n	while ($nest_level > 1) {\n		$nest_level --;\n		$lines[] = soo_toc_prepend(\"</$wraptag></li>\", t, $nest_level);\n	}\n\n	for ($i = 0; $i < count($lines)-1; $i++)\n		if ( preg_match(\'/<li>/\', $lines[$i])\n			&& !preg_match(\"/<$wraptag>/\", $lines[$i+1]) )\n				$lines[$i] .= \'</li>\';\n\n	if ( !preg_match(\"/<\\/$wraptag>/\", $lines[count($lines)-1]) )\n		$lines[count($lines)-1] .= \'</li>\';\n\n	$out = \'<\' . $wraptag . ( $class ? \' class=\"\' . $class . \'\">\' : \'>\' )\n		. n . join(n, $lines) . n . \"</$wraptag>\" . n;\n\n	return doLabel($label, $labeltag) . n . $out;\n}\n\nfunction soo_toc_prepend($base, $add, $times) {\n	for ( $i = 1; $i <= $times; $i++ )\n		$base = $add . $base;\n	return $base;\n}', '1b8ef1ed3ec0b55710dfbc89f3c6ec3d', '', '', 0, 5, 0),
('soo_wordfest', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.3', 'Word game for article comments', '<style type=\"text/css\">\ndiv#sed_help pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3, div#sed_help h3 code {font-family: sans-serif; font-weight: bold;}\ndiv#sed_help h1, div#sed_help h2, div#sed_help h3 {margin-left: -1em;}\ndiv#sed_help h2, div#sed_help h3 {margin-top: 2em;}\ndiv#sed_help h1 {font-size: 2.4em;}\ndiv#sed_help h2 {font-size: 1.8em;}\ndiv#sed_help h3 {font-size: 1.4em;}\ndiv#sed_help h4 {font-size: 1.2em;}\ndiv#sed_help h5 {font-size: 1em;margin-left:1em;font-style:oblique;}\ndiv#sed_help h6 {font-size: 1em;margin-left:2em;font-style:oblique;}\ndiv#sed_help li {list-style-type: disc;}\ndiv#sed_help li li {list-style-type: circle;}\ndiv#sed_help li li li {list-style-type: square;}\ndiv#sed_help li a code {font-weight: normal;}\ndiv#sed_help li code:first-child {background: #ddd;padding:0 .3em;margin-left:-.3em;}\ndiv#sed_help li li code:first-child {background:none;padding:0;margin-left:0;}\ndiv#sed_help dfn {font-weight:bold;font-style:oblique;}\ndiv#sed_help .required, div#sed_help .warning {color:red;}\ndiv#sed_help .default {color:green;}\n</style>\n <div id=\"sed_help\">\n\n	<h1>soo_wordfest</h1>\n\n <div id=\"toc\">\n\n	<h2>Contents</h2>\n\n	<ul>\n		<li><a href=\"#overview\">Overview</a></li>\n		<li><a href=\"#usage\">Usage</a></li>\n		<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#soo_wordfest\">soo_wordfest</a></li>\n		<li><a href=\"#soo_wordfest_rules\">soo_wordfest_rules</a></li>\n		<li><a href=\"#soo_if_wordfest\">soo_if_wordfest</a></li>\n		<li><a href=\"#soo_if_wordfestive\">soo_if_wordfestive</a></li>\n	</ul></li>\n		<li><a href=\"#examples\">Examples</a></li>\n		<li><a href=\"#notes\">Notes</a></li>\n		<li><a href=\"#support\">Support</a></li>\n		<li><a href=\"#history\">History</a></li>\n	</ul>\n\n </div>\n\n	<h2 id=\"overview\">Overview</h2>\n\n	<p>Subvert the dominant paradigm and add festivity to your comments with this Textpattern version of Allen Chang&#8217;s delightfully clever <a href=\"http://chaoticpattern.com/about/#on-wordfest\">Wordfest game</a> for <a href=\"http://symphony-cms.com/\">Symphony <span class=\"caps\">CMS</span></a> commenters. <a href=\"http://forum.textpattern.com/viewtopic.php?id=30069\"><span class=\"caps\">MLP</span> Pack</a> compatible (but for non-English usage please see <a href=\"#notes\">Notes</a>).</p>\n\n	<p>The object of <strong>Wordfest</strong> is to meet a set of constraints on writing a comment message. There is a conditional tag that allows you to flag successful comments however you choose.</p>\n\n	<h2 id=\"usage\">Usage</h2>\n\n	<p>At a minimum, you must stick a <code>soo_wordfest</code> tag somewhere above the article comments, then use one or more <strong>soo_wordfest</strong> output tags in your article and/or comment forms.</p>\n\n	<h2 id=\"tags\">Tags</h2>\n\n	<h3 id=\"soo_wordfest\">soo_wordfest</h3>\n\n	<p>A single tag you use to set the rules of the game. You could probably put it anywhere, so long as it comes before any other <strong>soo_wordfest</strong> tags, but the intent is to use it directly in an article body. This allows you to enable and set rules for the game on a per-article basis; it wouldn&#8217;t be particularly festive to have the same rules across articles. The tag itself doesn&#8217;t output anything; it just sets global variables used by the other tags.</p>\n\n	<p><span class=\"warning\">Note:</span> while there is some mild error checking, it is really up to you to pick a sensible set of rules.</p>\n\n<pre>&#60;txp:soo_wordfest /&#62;\n</pre>\n\n	<h4>Attributes</h4>\n\n	<p>All attributes <span class=\"default\">default</span> to empty, so you <span class=\"required\">must</span> set at least one attribute to enable <strong>Wordfest</strong>. Each attribute you set creates an additional rule for the game.</p>\n\n	<p>All of the text attributes are case-insensitive. (For non-English websites please see <a href=\"#notes\">Notes</a>.)</p>\n\n	<ul>\n		<li><code>must_contain</code> <em>(text)</em><br />\nText that must appear somewhere in the comment.</li>\n		<li><code>must_begin</code> <em>(text)</em><br />\nText that must appear at the start of the comment.</li>\n		<li><code>must_end</code> <em>(text)</em><br />\nText that must appear at the end of the comment.</li>\n		<li><code>anagram</code> <em>(text)</em><br />\nText for which an anagram must appear somewhere in the comment.</li>\n		<li><code>word_count</code> <em>(integer)</em><br />\nExact number of words allowed.</li>\n		<li><code>max_word_count</code> <em>(integer)</em><br />\nMaximum number of words allowed.</li>\n		<li><code>min_word_length</code> <em>(integer)</em><br />\nMinimum word length.</li>\n		<li><code>max_word_length</code> <em>(integer)</em><br />\nMaximum word length.</li>\n		<li><code>not_word_length</code> <em>(integer)</em><br />\nWord length that must be avoided.</li>\n	</ul>\n\n	<h3 id=\"soo_wordfest_rules\">soo_wordfest_rules</h3>\n\n	<p>A single tag that displays the rules in effect. It isn&#8217;t context sensitive, so you can use it anywhere following the <a href=\"#soo_wordfest\"><code>soo_wordfest</code></a> tag, but presumably you will want to use it in an article or comment form.</p>\n\n	<p>Uses the strings set in the plugin&#8217;s <a href=\"http://forum.textpattern.com/viewtopic.php?id=30069\"><span class=\"caps\">MLP</span> Pack</a> definitions, so you can translate or edit these as desired.</p>\n\n<pre>&#60;txp:soo_wordfest_rules /&#62;\n</pre>\n\n	<h4>Attributes</h4>\n\n	<p>Standard Txp attributes; none  <span class=\"required\">required</span>.</p>\n\n	<ul>\n		<li><code>html_id</code></li>\n		<li><code>class</code></li>\n		<li><code>wraptag</code></li>\n		<li><code>break</code></li>\n	</ul>\n\n	<h3 id=\"soo_if_wordfest\">soo_if_wordfest</h3>\n\n	<p>A standard Txp conditional tag that simply checks whether or not any <strong>Wordfest</strong> rules have been set.</p>\n\n<pre>&#60;txp:soo_if_wordfest&#62;\n... output if Wordfest rules are in effect ...\n&#60;txp:else /&#62;\n... otherwise this ...\n&#60;/txp:soo_if_wordfest&#62;\n</pre>\n\n	<h4>Attributes</h4>\n\n	<p>None.</p>\n\n	<h3 id=\"soo_if_wordfestive\">soo_if_wordfestive</h3>\n\n	<p>A standard Txp conditional tag that checks whether or not the current comment meets the current <strong>Wordfest</strong> rules. <span class=\"required\">Use only</span> in a comment display form.</p>\n\n<pre>&#60;txp:soo_if_wordfestive&#62;\n... output if comment is properly Wordfestive ...\n&#60;txp:else /&#62;\n... otherwise this ...\n&#60;/txp:soo_if_wordfestive&#62;\n</pre>\n\n	<h4>Attributes</h4>\n\n	<p>None.</p>\n\n	<h2 id=\"examples\">Examples</h2>\n\n	<p>See <a href=\"http://ipsedixit.net/txp/88/wordfest\">http://ipsedixit.net/txp/88/wordfest</a> for working examples.</p>\n\n	<h2 id=\"notes\">Notes</h2>\n\n	<h3>Internationalization</h3>\n\n	<p>Despite having added the <a href=\"http://forum.textpattern.com/viewtopic.php?id=30069\"><span class=\"caps\">MLP</span> Pack</a> functions, I really have no experience in dealing with internationalization. I&#8217;m quite sure the anagram function currently only works within the <span class=\"caps\">ASCII</span> set. I fear the other text-based functions are similary limited, using <span class=\"caps\">PHP</span> functions such as <code>substr()</code> that only work with single-byte text. Any hints or suggestions appreciated.</p>\n\n	<h2 id=\"support\">Support</h2>\n\n	<ul>\n		<li><a href=\"http://forum.textpattern.com/viewtopic.php?id=31430\">Textpattern support forum thread</a></li>\n		<li><a href=\"http://ipsedixit.net/txp/88/wordfest\">Online documentation</a></li>\n		<li><a href=\"http://ipsedixit.net/info/2/contact\">Contact the author</a></li>\n	</ul>\n\n	<h2 id=\"history\">Version History</h2>\n\n	<h3>0.3 (2017-02-15)</h3>\n\n	<p>Textpattern 4.6 compatibility update</p>\n\n	<h3>0.2 (August 2, 2009)</h3>\n\n	<p>Added <code>anagram</code> attribute to <code>soo_wordfest</code>.</p>\n\n	<h3>0.1 (August 1, 2009)</h3>\n\n	<p>Hot off the grill.</p>\n\n </div>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_wordfest\')\n		->register(\'soo_wordfest_rules\')\n		->register(\'soo_if_wordfest\')\n		->register(\'soo_if_wordfestive\')\n		;\n}\n\nfunction soo_wordfest( $atts ) {\n	global $soo_wordfest;\n	$integer_atts = array(\n		\'word_count\'		=> \'\',\n		\'max_word_count\'	=> \'\',\n		\'min_word_length\'	=> \'\',\n		\'max_word_length\'	=> \'\',\n		\'not_word_length\'	=> \'\',\n	);\n	$atts = lAtts(array(\n		\'must_contain\'		=> \'\',\n		\'must_begin\'		=> \'\',\n		\'must_end\'			=> \'\',\n		\'anagram\'			=> \'\',\n	) + $integer_atts, $atts);\n	foreach ( $atts as $name => $value ) {\n		if ( array_key_exists($name, $integer_atts) )\n			$value = abs(intval($value));\n		if ( is_string($value) )\n			$value = trim($value);\n		if ( $name == \'anagram\' )\n			$value = preg_replace(\'/[^a-z\\s\\\']/i\', \'\', $value);\n		if ( $value )\n			$soo_wordfest[$name] = $value;\n	}\n}\n\nfunction soo_wordfest_rules( $atts ) {\n	global $soo_wordfest;\n	extract(lAtts(array(\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'wraptag\'		=>	\'\',\n 		\'break\'			=>	\'\',\n	), $atts));\n\n	foreach ( $soo_wordfest as $rule => $value ) {\n		$display = soo_wordfest_gTxt($rule);\n		if ( is_string($value) ) {\n			$is_plural = str_word_count($value) > 1;\n			$pattern = \'/\\{([^}]+)\\|([^}]+)\\}/\';\n			preg_match($pattern, $display, $match);\n			if ( count($match) ) {\n				$replacement = $is_plural ? $match[2] : $match[1];\n				$display = preg_replace($pattern, $replacement, $display);\n			}\n			$value = soo_wordfest_gTxt(\'lquo\') . $value .\n				soo_wordfest_gTxt(\'rquo\');\n		}\n		$display = str_replace(\'{x}\', $value, $display);\n		if ( $value )\n			$out[] = $display;\n	}\n	return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n}\n\nfunction soo_if_wordfest( $atts, $thing ) {\n	global $soo_wordfest;\n	return parse(EvalElse($thing, is_array($soo_wordfest)));\n}\n\nfunction soo_if_wordfestive( $atts, $thing ) {\n\n	global $soo_wordfest, $thiscomment;\n\n	if ( is_array($soo_wordfest) )\n		extract($soo_wordfest);\n	else\n		return;\n\n	assert_comment();\n	$comment = trim(strtolower(strip_tags($thiscomment[\'message\'])));\n\n	$pass = true;\n\n	if ( ! empty($must_contain) )\n		if ( $comment == str_ireplace($must_contain, \'\', $comment) )\n			$pass = false;\n\n	if ( $pass and ! empty($must_begin) )\n		if ( strtolower($must_begin) != substr($comment, 0, strlen($must_begin)) )\n			$pass = false;\n\n	if ( $pass and ! empty($must_end) )\n		if ( strtolower($must_end) != substr($comment, - strlen($must_end)) )\n			$pass = false;\n\n	if ( $pass and ! empty($word_count) )\n		if ( $word_count != str_word_count($comment) )\n			$pass = false;\n\n	if ( $pass and ! empty($max_word_count) )\n		if ( $max_word_count < str_word_count($comment) )\n			$pass = false;\n\n	if ( $pass and ( ! empty($min_word_length) or ! empty($max_word_length)\n		or ! empty($not_word_length) or ! empty($anagram) ) ) {\n\n		$words = preg_split(\'/[\\W]+/\', $comment);\n		foreach ( $words as $word )\n			$word_lengths[] = strlen($word);\n		$s_word_lengths = array_unique($word_lengths);\n		sort($s_word_lengths);\n		if ( $s_word_lengths[0] == 0 )\n			array_shift($s_word_lengths);\n\n		if ( ! empty($min_word_length) )\n			if ( $min_word_length > $s_word_lengths[0] )\n				$pass = false;\n\n		if ( $pass and ! empty($max_word_length) )\n			if ( $max_word_length < $s_word_lengths[count($s_word_lengths) - 1] )\n				$pass = false;\n\n		if ( $pass and ! empty($not_word_length) )\n			if ( in_array($not_word_length, $s_word_lengths) )\n				$pass = false;\n\n		if ( $pass and ! empty($anagram) ) {\n			$anagram = strtolower(preg_replace(\'/[^a-z]/i\', \'\', $anagram));\n			$letters = str_split($anagram);\n			sort($letters);\n			$count = count($letters);\n			$words = preg_replace(\'/[^a-z]/i\', \'\', $words);\n			$match = false;\n			while ( ! $match and count($words) ) {\n				$test_string = $words[0];\n				$c_match = strlen($test_string) == $count;\n				$i = 1;\n				while ( ! $c_match and $i < count($words) ) {\n					$test_string .= $words[$i];\n					$c_match = strlen($test_string) == $count;\n					if ( strlen($test_string) > $count )\n						$i = count($words);\n					$i++;\n				}\n				if ( $c_match ) {\n					$test_string = strtolower($test_string);\n					$test_array = str_split($test_string);\n					sort($test_array);\n					$match = ( $letters == $test_array and $anagram != $test_string );\n				}\n				array_shift($words);\n			}\n			$pass = $match;\n		}\n	}\n\n	return parse(EvalElse($thing, $pass));\n}\n\n  //---------------------------------------------------------------------//\n //							MLP Pack definitions						//\n//---------------------------------------------------------------------//\n\ndefine(\'SOO_WORDFEST_PREFIX\', \'soo_fest\');\nglobal $soo_wordfest_strings;\n$soo_wordfest_strings = array(\n	\'must_contain\'		=> \'contain the {word|phrase} {x}\',\n	\'must_begin\'		=> \'begin with the {word|phrase} {x}\',\n	\'must_end\'			=> \'end with the {word|phrase} {x}\',\n	\'word_count\'		=> \'be exactly {x} words long\',\n	\'max_word_count\'	=> \'not exceed {x} words\',\n	\'min_word_length\'	=> \'only use words that are at least {x} letters long\',\n	\'max_word_length\'	=> \'only use words that are not more than {x} letters long\',\n	\'not_word_length\'	=> \'avoid using {x}-letter words\',\n	\'anagram\'			=> \'contain an anagram of {x}\',\n	\'lquo\'				=> \'&ldquo;\',\n	\'rquo\'				=> \'&rdquo;\',\n);\n\nregister_callback(\'soo_wordfest_enumerate_strings\', \'l10n.enumerate_strings\');\n\nfunction soo_wordfest_enumerate_strings( $event, $step = \'\', $pre = 0 ) {\n	global $soo_wordfest_strings;\n	$r = array(\n		\'owner\'		=> \'soo_wordfest\',\n		\'prefix\'	=> SOO_WORDFEST_PREFIX,\n		\'lang\'		=> \'en-us\',\n		\'event\'		=> \'public\',\n		\'strings\'	=> $soo_wordfest_strings,\n				);\n	return $r;\n}\n\nfunction soo_wordfest_gTxt( $what , $args = array() ) {\n	global $textarray;\n	global $soo_wordfest_strings;\n\n	$key = SOO_WORDFEST_PREFIX . \'-\' . $what;\n	$key = strtolower($key);\n\n	if(isset($textarray[$key]))\n		$str = $textarray[$key];\n\n	else {\n		$key = strtolower($what);\n\n		if( isset( $soo_wordfest_strings[$key] ) )\n			$str = $soo_wordfest_strings[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'soo_wordfest\')\n		->register(\'soo_wordfest_rules\')\n		->register(\'soo_if_wordfest\')\n		->register(\'soo_if_wordfestive\')\n		;\n}\n\nfunction soo_wordfest( $atts ) {\n	global $soo_wordfest;\n	$integer_atts = array(\n		\'word_count\'		=> \'\',\n		\'max_word_count\'	=> \'\',\n		\'min_word_length\'	=> \'\',\n		\'max_word_length\'	=> \'\',\n		\'not_word_length\'	=> \'\',\n	);\n	$atts = lAtts(array(\n		\'must_contain\'		=> \'\',\n		\'must_begin\'		=> \'\',\n		\'must_end\'			=> \'\',\n		\'anagram\'			=> \'\',\n	) + $integer_atts, $atts);\n	foreach ( $atts as $name => $value ) {\n		if ( array_key_exists($name, $integer_atts) )\n			$value = abs(intval($value));\n		if ( is_string($value) )\n			$value = trim($value);\n		if ( $name == \'anagram\' )\n			$value = preg_replace(\'/[^a-z\\s\\\']/i\', \'\', $value);\n		if ( $value )\n			$soo_wordfest[$name] = $value;\n	}\n}\n\nfunction soo_wordfest_rules( $atts ) {\n	global $soo_wordfest;\n	extract(lAtts(array(\n		\'html_id\'		=>	\'\',\n		\'class\'			=>	\'\',\n 		\'wraptag\'		=>	\'\',\n 		\'break\'			=>	\'\',\n	), $atts));\n\n	foreach ( $soo_wordfest as $rule => $value ) {\n		$display = soo_wordfest_gTxt($rule);\n		if ( is_string($value) ) {\n			$is_plural = str_word_count($value) > 1;\n			$pattern = \'/\\{([^}]+)\\|([^}]+)\\}/\';\n			preg_match($pattern, $display, $match);\n			if ( count($match) ) {\n				$replacement = $is_plural ? $match[2] : $match[1];\n				$display = preg_replace($pattern, $replacement, $display);\n			}\n			$value = soo_wordfest_gTxt(\'lquo\') . $value .\n				soo_wordfest_gTxt(\'rquo\');\n		}\n		$display = str_replace(\'{x}\', $value, $display);\n		if ( $value )\n			$out[] = $display;\n	}\n	return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n}\n\nfunction soo_if_wordfest( $atts, $thing ) {\n	global $soo_wordfest;\n	return parse(EvalElse($thing, is_array($soo_wordfest)));\n}\n\nfunction soo_if_wordfestive( $atts, $thing ) {\n\n	global $soo_wordfest, $thiscomment;\n\n	if ( is_array($soo_wordfest) )\n		extract($soo_wordfest);\n	else\n		return;\n\n	assert_comment();\n	$comment = trim(strtolower(strip_tags($thiscomment[\'message\'])));\n\n	$pass = true;\n\n	if ( ! empty($must_contain) )\n		if ( $comment == str_ireplace($must_contain, \'\', $comment) )\n			$pass = false;\n\n	if ( $pass and ! empty($must_begin) )\n		if ( strtolower($must_begin) != substr($comment, 0, strlen($must_begin)) )\n			$pass = false;\n\n	if ( $pass and ! empty($must_end) )\n		if ( strtolower($must_end) != substr($comment, - strlen($must_end)) )\n			$pass = false;\n\n	if ( $pass and ! empty($word_count) )\n		if ( $word_count != str_word_count($comment) )\n			$pass = false;\n\n	if ( $pass and ! empty($max_word_count) )\n		if ( $max_word_count < str_word_count($comment) )\n			$pass = false;\n\n	if ( $pass and ( ! empty($min_word_length) or ! empty($max_word_length)\n		or ! empty($not_word_length) or ! empty($anagram) ) ) {\n\n		$words = preg_split(\'/[\\W]+/\', $comment);\n		foreach ( $words as $word )\n			$word_lengths[] = strlen($word);\n		$s_word_lengths = array_unique($word_lengths);\n		sort($s_word_lengths);\n		if ( $s_word_lengths[0] == 0 )\n			array_shift($s_word_lengths);\n\n		if ( ! empty($min_word_length) )\n			if ( $min_word_length > $s_word_lengths[0] )\n				$pass = false;\n\n		if ( $pass and ! empty($max_word_length) )\n			if ( $max_word_length < $s_word_lengths[count($s_word_lengths) - 1] )\n				$pass = false;\n\n		if ( $pass and ! empty($not_word_length) )\n			if ( in_array($not_word_length, $s_word_lengths) )\n				$pass = false;\n\n		if ( $pass and ! empty($anagram) ) {\n			$anagram = strtolower(preg_replace(\'/[^a-z]/i\', \'\', $anagram));\n			$letters = str_split($anagram);\n			sort($letters);\n			$count = count($letters);\n			$words = preg_replace(\'/[^a-z]/i\', \'\', $words);\n			$match = false;\n			while ( ! $match and count($words) ) {\n				$test_string = $words[0];\n				$c_match = strlen($test_string) == $count;\n				$i = 1;\n				while ( ! $c_match and $i < count($words) ) {\n					$test_string .= $words[$i];\n					$c_match = strlen($test_string) == $count;\n					if ( strlen($test_string) > $count )\n						$i = count($words);\n					$i++;\n				}\n				if ( $c_match ) {\n					$test_string = strtolower($test_string);\n					$test_array = str_split($test_string);\n					sort($test_array);\n					$match = ( $letters == $test_array and $anagram != $test_string );\n				}\n				array_shift($words);\n			}\n			$pass = $match;\n		}\n	}\n\n	return parse(EvalElse($thing, $pass));\n}\n\n  //---------------------------------------------------------------------//\n //							MLP Pack definitions						//\n//---------------------------------------------------------------------//\n\ndefine(\'SOO_WORDFEST_PREFIX\', \'soo_fest\');\nglobal $soo_wordfest_strings;\n$soo_wordfest_strings = array(\n	\'must_contain\'		=> \'contain the {word|phrase} {x}\',\n	\'must_begin\'		=> \'begin with the {word|phrase} {x}\',\n	\'must_end\'			=> \'end with the {word|phrase} {x}\',\n	\'word_count\'		=> \'be exactly {x} words long\',\n	\'max_word_count\'	=> \'not exceed {x} words\',\n	\'min_word_length\'	=> \'only use words that are at least {x} letters long\',\n	\'max_word_length\'	=> \'only use words that are not more than {x} letters long\',\n	\'not_word_length\'	=> \'avoid using {x}-letter words\',\n	\'anagram\'			=> \'contain an anagram of {x}\',\n	\'lquo\'				=> \'&ldquo;\',\n	\'rquo\'				=> \'&rdquo;\',\n);\n\nregister_callback(\'soo_wordfest_enumerate_strings\', \'l10n.enumerate_strings\');\n\nfunction soo_wordfest_enumerate_strings( $event, $step = \'\', $pre = 0 ) {\n	global $soo_wordfest_strings;\n	$r = array(\n		\'owner\'		=> \'soo_wordfest\',\n		\'prefix\'	=> SOO_WORDFEST_PREFIX,\n		\'lang\'		=> \'en-us\',\n		\'event\'		=> \'public\',\n		\'strings\'	=> $soo_wordfest_strings,\n				);\n	return $r;\n}\n\nfunction soo_wordfest_gTxt( $what , $args = array() ) {\n	global $textarray;\n	global $soo_wordfest_strings;\n\n	$key = SOO_WORDFEST_PREFIX . \'-\' . $what;\n	$key = strtolower($key);\n\n	if(isset($textarray[$key]))\n		$str = $textarray[$key];\n\n	else {\n		$key = strtolower($what);\n\n		if( isset( $soo_wordfest_strings[$key] ) )\n			$str = $soo_wordfest_strings[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n', 'e66210df452970d591de636b049514da', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('soo_page_numbers', 0, 'Jeff Soo', 'http://ipsedixit.net/txp/', '0.4.2', 'Article list nav and page count widgets', '<h1>soo_page_numbers</h1>\n\n<p>This is a plugin for <a href=\"http://textpattern.com\">Textpattern</a>. It displays customisable navigation widgets for article list pages.</p>\n\n<ul>\n	<li><a href=\"https://forum.textpattern.io/viewtopic.php?id=31217\">Support forum topic</a></li>\n	<li><a href=\"http://ipsedixit.net/txp/86/soo_page_numbers\">Author site</a></li>\n	<li><a href=\"https://github.com/jsoo/soo_page_numbers\">GitHub</a></li>\n</ul>\n\n<div id=\"toc\">\n\n<h2>Contents</h2>\n\n<ul>\n	<li><a href=\"#overview\">Overview</a></li>\n	<li><a href=\"#tags\">Tags</a>\n	<ul>\n		<li><a href=\"#soo_page_links\">soo_page_links</a></li>\n		<li><a href=\"#soo_page_count\">soo_page_count</a></li>\n		<li><a href=\"#soo_prev_page\">soo_prev_page</a></li>\n	</ul></li>\n	<li><a href=\"#history\">History</a></li>\n</ul>\n\n</div>\n\n<h2 id=\"overview\">Overview</h2>\n\n<p>Display page navigation widgets and information for article list pages. (Inspired by <code>rsx_page_number</code>, an old plugin from Txp&#8217;s early days.)</p>\n\n<p>Version 0.3.0 includes a Textpack to localize pre-formatted text output such as &#8220;Page {current} of {total}&#8221;. Currently includes 21 languages.</p>\n\n<p>NB: Version 0.4.0 did away with the requirement for an external library plugin, but by moving to native Txp <span class=\"caps\">URI</span> handling this changed behavior on sites with custom <span class=\"caps\">URI</span> schemes. 0.4.1 restores that behavior. Note that the <code>p</code> (image ID) query parameter is not preserved; this is intentional.</p>\n\n<p><span class=\"warning\">Note:</span> If you have more than one pagination-capable tag on the page (<code>article</code> if not <code>status=&quot;sticky&quot;</code>, or any of <code>images</code>, <code>file_download_list</code>, or <code>linklist</code> if both <code>limit</code> and <code>pageby</code> are set) <strong>soo_page_numbers</strong> will take its values from the first such tag. This is true no matter where you put any <strong>soo_page_numbers</strong> tags.</p>\n\n<h2 id=\"tags\">Tags</h2>\n\n<h3 id=\"soo_page_links\">soo_page_links</h3>\n\n<p>Display a &#8220;Google-style&#8221; page navigation widget, i.e., a group of numbered links representing pages.</p>\n\n<pre>&lt;txp:soo_page_links /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>placeholder</code> <em>(text)</em> <span class=\"default\">default</span> <code>&amp;hellip;</code><br />\nText to place on either end of the central page range (when there are many pages)</li>\n	<li><code>window_size</code> <em>(integer)</em> <span class=\"default\">default</span> <code>5</code><br />\nSize of central page range</li>\n	<li><code>showalways</code> <em>(boolean)</em> <span class=\"default\">default</span> <code>0</code><br />\nWhether or not to show anything when the list is a single page</li>\n	<li><code>active_class</code> <em>(<span class=\"caps\">HTML</span> class)</em> <span class=\"default\">default</span> <code>here</code><br />\nClass for the current page number&#8217;s tag (the <code>break</code> tag, if any, otherwise <code>span</code>)</li>\n	<li><code>wraptag</code> <em>(text)</em> <span class=\"default\">default</span> empty<br />\n<span class=\"caps\">HTML</span> tag name (no brackets) to wrap the output</li>\n	<li><code>class</code> <em>(text)</em> <span class=\"default\">default</span> empty<br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-class\" title=\"s\"><span class=\"caps\">HTML</span> class name</a> for the <code>wraptag</code>.</li>\n	<li><code>html_id</code> <em>(text)</em> <span class=\"default\">default</span> empty<br />\n<a href=\"http://www.w3.org/TR/html401/struct/global.html#adef-id\"><span class=\"caps\">HTML</span> id</a> for the <code>wraptag</code>.</li>\n	<li><code>break</code> <em>(mixed)</em> <span class=\"default\">default</span> empty<br />\n<span class=\"caps\">HTML</span> tag name (no brackets) to wrap or text to place between adjacent page numbers. If <code>wraptag</code> and <code>break</code> are set, <code>break</code> is assumed to be a tag name. Otherwise it is treated as text (so don&#8217;t use <code>break=&quot;br&quot;</code>).</li>\n</ul>\n\n<h3 id=\"soo_page_count\">soo_page_count</h3>\n\n<pre>&lt;txp:soo_page_count /&gt;</pre>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>format</code> <em>(format string)</em> <span class=\"default\">default</span> <code> &quot;{prev} Page {current} of {total} {next}&quot; </code><br />\nTag will output this string (in your language, if among the 21 included) after replacing <code>{prev}</code>, <code>{next}</code>, <code>{first}</code>, and <code>{last}</code> with links, and <code>{current}</code> and <code>{total}</code> with page numbers</li>\n	<li><code>prev</code> <em>(text)</em> <span class=\"default\">default</span> <code>&amp;laquo;</code> (&laquo;)<br />\nLink text for the <code>{prev}</code> link</li>\n	<li><code>next</code> <em>(text)</em> <span class=\"default\">default</span> <code>&amp;raquo;</code> (&raquo;)<br />\nLink text for the <code>{next}</code> link</li>\n	<li><code>first</code> <em>(text)</em> <span class=\"default\">default</span> <code>|&amp;laquo;</code> (|&laquo;)<br />\nLink text for the <code>{first}</code> link</li>\n	<li><code>last</code> <em>(text)</em> <span class=\"default\">default</span> <code>&amp;raquo;|</code> (&raquo;|)<br />\nLink text for the <code>{last}</code> link</li>\n	<li><code>showalways</code> <em>(boolean)</em> <span class=\"default\">default</span> <code>0</code><br />\nWhether or not to show <code>{prev}</code> and <code>{next}</code> on the first and last pages, respectively, or anything at all when the list is a single page</li>\n	<li><code>wraptag</code> <em>(<span class=\"caps\">XHTML</span> tag name, no brackets)</em> optional tag to wrap the output</li>\n</ul>\n\n<h3 id=\"soo_prev_page\">soo_prev_page, soo_next_page</h3>\n\n<p>Shortcuts for <code>soo_page_count</code> when all you want is a single link. For example, <code>soo_prev_page</code> is a shortcut for <code>&lt;txp:soo_page_count format=&quot;{prev}&quot; /&gt;</code>.</p>\n\n<h4>Attributes</h4>\n\n<p>In addition to <code>soo_page_count</code> attributes, each of these tags also accepts a <code>text</code> attribute for setting the link text. The following tags are equivalent:</p>\n\n<pre>&lt;txp:soo_next_page text=&quot;Next&quot; /&gt;\n&lt;txp:soo_page_count format=&quot;{next}&quot; next=&quot;Next&quot; /&gt;</pre>\n\n<h2 id=\"history\">Version History</h2>\n\n<h3>0.4.2 (2020-03-09)</h3>\n\n<ul>\n	<li>Fixed longstanding bug that used to be harmless but turned lethal in Txp 4.8.0</li>\n</ul>\n\n<h3>0.4.1 (2017-03-10)</h3>\n\n<ul>\n	<li>New <span class=\"caps\">URI</span> handling algorithm, reverting to version 0.3 behavior, but as a freestanding plugin</li>\n</ul>\n\n<h3>0.4.0 (2017-03-04)</h3>\n\n<ul>\n	<li>Refactored to eliminate the requirement for an external library plugin. (NB: this means, inter alia, that the <code>p</code> (image ID) query parameter is no longer preserved in page links.)</li>\n</ul>\n\n<h3>0.3.2 (2017-02-15)</h3>\n\n<ul>\n	<li>Textpattern 4.6 compatibility update</li>\n</ul>\n\n<h3>0.3.1 (2012-01-27)</h3>\n\n<ul>\n	<li><code>soo_page_count</code> (hence also <code>soo_prev_page</code> and <code>soo_next_page</code>) now preserve non-Txp query params for &#8220;next&#8221; and &#8220;prev&#8221; links.</li>\n</ul>\n\n<h3>0.3.0 (2011-01-18)</h3>\n\n<ul>\n	<li>Plugin now includes a Textpack to localize pre-formatted text output such as &#8220;Page {current} of {total}&#8221;. Currently includes 21 languages.</li>\n</ul>\n\n<h3>0.2.7 (2010-02-11)</h3>\n\n<ul>\n	<li>Fixed Textpattern notice about non-existent attribute when using <code>soo_prev_page</code> or <code>soo_next_page</code> (functionality not affected)</li>\n</ul>\n\n<h3>0.2.6 (2009-11-23)</h3>\n\n<ul>\n	<li><code>soo_page_links</code> now restores the <code>&#39;pg&#39;</code> query parameter to its initial state, to avoid conflicts with other context-dependent plugins</li>\n</ul>\n\n<h3>0.2.5 (2009-10-21)</h3>\n\n<ul>\n	<li>New attributes and shortcut tags for <code>soo_page_count</code></li>\n</ul>\n\n<h3>0.2.4 (2009-07-16)</h3>\n\n<ul>\n	<li>Fixed <code>showalways</code> bug when an article list returns 0 pages (e.g. empty category)</li>\n</ul>\n\n<h3>0.2.3 (2009-07-09)</h3>\n\n<ul>\n	<li>Improved context check to prevent raw tag output</li>\n</ul>\n\n<h3>0.2.2 (2009-07-09)</h3>\n\n<ul>\n	<li>When both <code>wraptag</code> and <code>break</code> are set, non-linked text items (i.e., current page number or placeholder text) are no longer wrapped in <code>span</code> tags, and <code>active_class</code> is applied to the <code>break</code> element containing the current page number.</li>\n	<li>Both tags now do a context check and show nothing if the page is not an article list</li>\n</ul>\n\n<h3>0.2.1 (2009-07-07)</h3>\n\n<ul>\n	<li>Changed file name and one tag name</li>\n	<li>The <code>showalways</code> attribute of <code>soo_page_count</code> now also affects output when the article list is only one page</li>\n	<li><code>soo_page_links</code> has also been given the <code>showalways</code> attribute</li>\n	<li>Scrapped <code>soo_article_count</code>, which was inherently buggy (as is the <code>rsx_to_of</code> it was based on)</li>\n</ul>\n\n<h3>0.2 (2009-05-22)</h3>\n\n<p>Not publicly released. Code overhaul, fixed to work with any query string</p>\n\n<h3>0.1 (ages ago)</h3>\n\n<p>Not publicly released, not very good either, just a sorry hack of what was probably a quick one-off plugin to begin with (it was early days for Txp).</p>', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_page_links\')\n        ->register(\'soo_page_count\')\n        ->register(\'soo_prev_page\')\n        ->register(\'soo_next_page\')\n        ;\n}\n\nif ( @txpinterface == \'admin\' ) {\n    add_privs(\'plugin_lifecycle.soo_page_numbers\',\'1,2\');\n    register_callback(\'soo_page_numbers_lifecycle\', \'plugin_lifecycle.soo_page_numbers\');\n}\n\n// delete the Textpack\n// Note: callback_event() does not display message from lifecycle callbacks\nfunction soo_page_numbers_lifecycle ($event, $step)\n{\n    if (substr($event, strlen(\'plugin_lifecycle.\')) === \'soo_page_numbers\' && $step === \'deleted\') {\n        safe_delete(\'txp_lang\', \"event = \'soo_page_numbers\'\");\n    }\n}\n\n// load the Textpack\nif (@txpinterface == \'admin\') {\n    if ($soo_page_numbers_textpack = load_lang_event(\'soo_page_numbers\')) {\n        global $textarray;\n        $textarray += $soo_page_numbers_textpack;\n        unset($soo_page_numbers_textpack);\n    }\n}\n\nfunction soo_page_links ($atts)\n{\n    extract(lAtts(array(\n        \'placeholder\'   =>  \'&hellip;\',\n        \'window_size\'   =>  5,\n        \'active_class\'  =>  \'here\',\n        \'wraptag\'       =>  \'\',\n        \'class\'         =>  \'\',\n        \'html_id\'       =>  \'\',\n        \'break\'         =>  \'\',\n        \'showalways\'    =>  false,\n    ), $atts));\n\n    global $thispage;\n\n    if (! is_array($thispage)) {\n        return _soo_page_numbers_secondpass(__FUNCTION__, $atts);\n    }\n\n    if (! $showalways and $thispage[\'numPages\'] <= 1) return;\n\n    $numPages = $thispage[\'numPages\'];\n    $pg = $thispage[\'pg\'];\n\n    $w_start = max(1,\n        min($pg - floor($window_size / 2), $numPages - $window_size + 1));\n    $w_end = min($w_start + $window_size - 1, $numPages);\n\n    $pgs = array_unique(array_merge(\n        array(1), range($w_start, $w_end), array($numPages)\n    ));\n\n    $break_text = $wraptag ? \'\' : $break;\n    $text_tag = ( $break and $wraptag ) ? \'\' : \'span\';\n    $active_class = $active_class ? \" class=\\\"$active_class\\\"\" : \'\';\n\n    while ($pgs) {\n        $n = array_shift($pgs);\n        $fill = $pgs ? ( $pgs[0] > $n + 1 ? $placeholder : $break_text ) : \'\';\n        if ( $n == $pg ) {\n            $items[] = $text_tag ? tag($n, $text_tag, $active_class) : $n;\n        } else {\n            $uri = _soo_page_numbers_uri($n);\n            $items[] = href($n, $uri, \' title=\"\'.gTxt(\'page\').sp.$n.\'\"\');\n        }\n        if ($n < $numPages and $fill) {\n            $items[] = $text_tag ? tag($fill, $text_tag) : $fill;\n        }\n    }\n    if (isset($items)) {\n        return $wraptag ? str_replace(\"<$break>$pg<\",\n            \"<$break$active_class>$pg<\",\n            doWrap($items, $wraptag, $break, $class, \'\', \'\', \'\', $html_id))\n            : implode($break_text ? \'\' : n, $items);\n    }\n}\n\nfunction soo_page_count ($atts)\n{\n    extract(lAtts(array(\n        \'format\'        =>  gTxt(\'soo_page_count\'),\n        \'prev\'          =>  \'&laquo;\',\n        \'next\'          =>  \'&raquo;\',\n        \'first\'         =>  \'|&laquo;\',\n        \'last\'          =>  \'&raquo;|\',\n        \'showalways\'    =>  false,\n        \'wraptag\'       =>  \'\',\n    ), $atts));\n\n    global $thispage;\n\n    if (! is_array($thispage)) {\n        return _soo_page_numbers_secondpass(__FUNCTION__, $atts);\n    }\n\n    if (! $showalways && $thispage[\'numPages\'] <= 1) return;\n\n    $numPages = $thispage[\'numPages\'];\n    $pg = $thispage[\'pg\'];\n\n    if ($pg > 1) {\n        $first = href($first, _soo_page_numbers_uri(1), \' title=\"\'.gTxt(\'page\').\' 1\"\');\n        $prev = href($prev, _soo_page_numbers_uri($pg - 1), \' title=\"\'.gTxt(\'prev\').\'\"\' );\n    } elseif (! $showalways) $first = $prev = \'\';\n\n    if ( $pg < $numPages ) {\n        $last = href($last, _soo_page_numbers_uri($numPages), \' title=\"\'.gTxt(\'page\').sp.$numPages.\'\"\');\n        $next = href($next, _soo_page_numbers_uri($pg + 1), \' title=\"\'.gTxt(\'next\').\'\"\' );\n    }\n    elseif (! $showalways) $last = $next = \'\';\n\n    $out = str_replace(\n        array(\'{prev}\', \'{next}\', \'{first}\', \'{last}\', \'{current}\', \'{total}\'),\n        array($prev, $next, $first, $last, $pg, $numPages), $format);\n    return $wraptag ? tag($out, $wraptag) : $out;\n}\n\nfunction soo_prev_page ($atts)\n{\n    if (isset($atts[\'text\'])) {\n        $atts[\'prev\'] = $atts[\'text\'];\n        unset($atts[\'text\']);\n    }\n    $atts[\'format\'] = \'{prev}\';\n    return soo_page_count($atts);\n}\n\nfunction soo_next_page ($atts)\n{\n    if (isset($atts[\'text\'])) {\n        $atts[\'next\'] = $atts[\'text\'];\n        unset($atts[\'text\']);\n    }\n    $atts[\'format\'] = \'{next}\';\n    return soo_page_count($atts);\n}\n\nfunction _soo_page_numbers_secondpass ($func, $atts)\n{\n// in case $func\'s associated tag comes before an article tag,\n// this runs the tag again during textpattern()\'s second parse() pass\n    global $pretext;\n    if ($pretext[\'secondpass\']) return; // you only live twice\n    foreach ($atts as $k => $v)\n        $a[] = $k . \'=\"\' . $v . \'\" \';\n    return \"<txp:$func \".(isset($a) ? implode(\'\', $a) : \'\' ).\'/>\';\n}\n\nfunction _soo_page_numbers_uri($pg)\n{\n    static $baseUri, $qParams;\n\n    if (empty($baseUri)) {\n        $baseUri = preg_replace (\'%(.+)\\?.+%\', \'$1\', $_SERVER[\'REQUEST_URI\']);\n        $qParams = array();\n        parse_str($_SERVER[\'QUERY_STRING\'], $qParams);\n        unset($qParams[\'p\']);\n    }\n\n    if ($pg > 1) {\n        $qParams[\'pg\'] = $pg;\n    } else {\n        unset($qParams[\'pg\']);\n    }\n    if (empty($qParams)) {\n        return $baseUri;\n    }\n    return $baseUri.\'?\'.http_build_query($qParams, \'\', \'&amp;\');\n}\n', '\nif(class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'soo_page_links\')\n        ->register(\'soo_page_count\')\n        ->register(\'soo_prev_page\')\n        ->register(\'soo_next_page\')\n        ;\n}\n\nif ( @txpinterface == \'admin\' ) {\n    add_privs(\'plugin_lifecycle.soo_page_numbers\',\'1,2\');\n    register_callback(\'soo_page_numbers_lifecycle\', \'plugin_lifecycle.soo_page_numbers\');\n}\n\n// delete the Textpack\n// Note: callback_event() does not display message from lifecycle callbacks\nfunction soo_page_numbers_lifecycle ($event, $step)\n{\n    if (substr($event, strlen(\'plugin_lifecycle.\')) === \'soo_page_numbers\' && $step === \'deleted\') {\n        safe_delete(\'txp_lang\', \"event = \'soo_page_numbers\'\");\n    }\n}\n\n// load the Textpack\nif (@txpinterface == \'admin\') {\n    if ($soo_page_numbers_textpack = load_lang_event(\'soo_page_numbers\')) {\n        global $textarray;\n        $textarray += $soo_page_numbers_textpack;\n        unset($soo_page_numbers_textpack);\n    }\n}\n\nfunction soo_page_links ($atts)\n{\n    extract(lAtts(array(\n        \'placeholder\'   =>  \'&hellip;\',\n        \'window_size\'   =>  5,\n        \'active_class\'  =>  \'here\',\n        \'wraptag\'       =>  \'\',\n        \'class\'         =>  \'\',\n        \'html_id\'       =>  \'\',\n        \'break\'         =>  \'\',\n        \'showalways\'    =>  false,\n    ), $atts));\n\n    global $thispage;\n\n    if (! is_array($thispage)) {\n        return _soo_page_numbers_secondpass(__FUNCTION__, $atts);\n    }\n\n    if (! $showalways and $thispage[\'numPages\'] <= 1) return;\n\n    $numPages = $thispage[\'numPages\'];\n    $pg = $thispage[\'pg\'];\n\n    $w_start = max(1,\n        min($pg - floor($window_size / 2), $numPages - $window_size + 1));\n    $w_end = min($w_start + $window_size - 1, $numPages);\n\n    $pgs = array_unique(array_merge(\n        array(1), range($w_start, $w_end), array($numPages)\n    ));\n\n    $break_text = $wraptag ? \'\' : $break;\n    $text_tag = ( $break and $wraptag ) ? \'\' : \'span\';\n    $active_class = $active_class ? \" class=\\\"$active_class\\\"\" : \'\';\n\n    while ($pgs) {\n        $n = array_shift($pgs);\n        $fill = $pgs ? ( $pgs[0] > $n + 1 ? $placeholder : $break_text ) : \'\';\n        if ( $n == $pg ) {\n            $items[] = $text_tag ? tag($n, $text_tag, $active_class) : $n;\n        } else {\n            $uri = _soo_page_numbers_uri($n);\n            $items[] = href($n, $uri, \' title=\"\'.gTxt(\'page\').sp.$n.\'\"\');\n        }\n        if ($n < $numPages and $fill) {\n            $items[] = $text_tag ? tag($fill, $text_tag) : $fill;\n        }\n    }\n    if (isset($items)) {\n        return $wraptag ? str_replace(\"<$break>$pg<\",\n            \"<$break$active_class>$pg<\",\n            doWrap($items, $wraptag, $break, $class, \'\', \'\', \'\', $html_id))\n            : implode($break_text ? \'\' : n, $items);\n    }\n}\n\nfunction soo_page_count ($atts)\n{\n    extract(lAtts(array(\n        \'format\'        =>  gTxt(\'soo_page_count\'),\n        \'prev\'          =>  \'&laquo;\',\n        \'next\'          =>  \'&raquo;\',\n        \'first\'         =>  \'|&laquo;\',\n        \'last\'          =>  \'&raquo;|\',\n        \'showalways\'    =>  false,\n        \'wraptag\'       =>  \'\',\n    ), $atts));\n\n    global $thispage;\n\n    if (! is_array($thispage)) {\n        return _soo_page_numbers_secondpass(__FUNCTION__, $atts);\n    }\n\n    if (! $showalways && $thispage[\'numPages\'] <= 1) return;\n\n    $numPages = $thispage[\'numPages\'];\n    $pg = $thispage[\'pg\'];\n\n    if ($pg > 1) {\n        $first = href($first, _soo_page_numbers_uri(1), \' title=\"\'.gTxt(\'page\').\' 1\"\');\n        $prev = href($prev, _soo_page_numbers_uri($pg - 1), \' title=\"\'.gTxt(\'prev\').\'\"\' );\n    } elseif (! $showalways) $first = $prev = \'\';\n\n    if ( $pg < $numPages ) {\n        $last = href($last, _soo_page_numbers_uri($numPages), \' title=\"\'.gTxt(\'page\').sp.$numPages.\'\"\');\n        $next = href($next, _soo_page_numbers_uri($pg + 1), \' title=\"\'.gTxt(\'next\').\'\"\' );\n    }\n    elseif (! $showalways) $last = $next = \'\';\n\n    $out = str_replace(\n        array(\'{prev}\', \'{next}\', \'{first}\', \'{last}\', \'{current}\', \'{total}\'),\n        array($prev, $next, $first, $last, $pg, $numPages), $format);\n    return $wraptag ? tag($out, $wraptag) : $out;\n}\n\nfunction soo_prev_page ($atts)\n{\n    if (isset($atts[\'text\'])) {\n        $atts[\'prev\'] = $atts[\'text\'];\n        unset($atts[\'text\']);\n    }\n    $atts[\'format\'] = \'{prev}\';\n    return soo_page_count($atts);\n}\n\nfunction soo_next_page ($atts)\n{\n    if (isset($atts[\'text\'])) {\n        $atts[\'next\'] = $atts[\'text\'];\n        unset($atts[\'text\']);\n    }\n    $atts[\'format\'] = \'{next}\';\n    return soo_page_count($atts);\n}\n\nfunction _soo_page_numbers_secondpass ($func, $atts)\n{\n// in case $func\'s associated tag comes before an article tag,\n// this runs the tag again during textpattern()\'s second parse() pass\n    global $pretext;\n    if ($pretext[\'secondpass\']) return; // you only live twice\n    foreach ($atts as $k => $v)\n        $a[] = $k . \'=\"\' . $v . \'\" \';\n    return \"<txp:$func \".(isset($a) ? implode(\'\', $a) : \'\' ).\'/>\';\n}\n\nfunction _soo_page_numbers_uri($pg)\n{\n    static $baseUri, $qParams;\n\n    if (empty($baseUri)) {\n        $baseUri = preg_replace (\'%(.+)\\?.+%\', \'$1\', $_SERVER[\'REQUEST_URI\']);\n        $qParams = array();\n        parse_str($_SERVER[\'QUERY_STRING\'], $qParams);\n        unset($qParams[\'p\']);\n    }\n\n    if ($pg > 1) {\n        $qParams[\'pg\'] = $pg;\n    } else {\n        unset($qParams[\'pg\']);\n    }\n    if (empty($qParams)) {\n        return $baseUri;\n    }\n    return $baseUri.\'?\'.http_build_query($qParams, \'\', \'&amp;\');\n}\n', '5ff3257aab2b9771e107c00e12be0a7d', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('dzd_counter_view', 0, 'Messaoudi Rabah (Dragondz)', 'http://info.ix-dz.com/', '0.4', 'Count article pageviews using a custom field', '<h1>dzd_counter_view</h1>\n\n<p>This plugin can be used to count article pageviews using a custom field.</p>\n\n<p>You can count pageviews for:</p>\n\n<ul>\n	<li>individual articles you explicitly specify</li>\n	<li>all articles</li>\n	<li>articles in specific sections or categories.</li>\n</ul>\n\n<h3>Setting up</h3>\n\n<ol>\n	<li>Install the plugin and activate it.</li>\n	<li>Set up a custom field to use as your counter. The default custom field name is <code>countxx</code> but you may use any custom field name you like.</li>\n	<li>Add the tag <code>dzd_counter_view</code> in your individual article form, or in the <a href=\"https://docs.textpattern.io/tags/if_individual_article\" rel=\"nofollow\">if_individual_article</a> section of a form or txp:article(_custom) container. Use the <code>custom_field</code> attribute to specify your own custom field name and the <code>section</code> and/or <code>category</code> attributes to count only articles in specific sections or categories (see the tag reference)</li>\n	<li>If you wish to add a counter to specific articles, edit the respective articles in the and enter value <code>0</code> in the corresponding custom field. The counter will only advance on those articles. If you wish to add the counter to all articles, use the attribute <code>force=&quot;1&quot;</code> (see the tag reference).</li>\n</ol>\n\n<p>The counter will advance by 1 every time the url for the page is accessed. You can follow the counter by visiting the article edit page. If you want to display the counter value on your homepage, use <code>&lt;txp:custom_field name=&quot;countxx&quot; /&gt;</code> (or your own custom field name).</p>\n\n<h2>Tag reference</h2>\n\n<h3>Tag: <code>&lt;txp:dzd_counter_view&gt;</code></h3>\n\n<p>Logs each pageview to a custom field.</p>\n\n<dl>\n	<dt><strong>custom_field</strong></dt>\n	<dd>Name of the custom field to use for the counter.</dd>\n	<dd>Example: <code>custom_field=&quot;pageviews&quot;</code> uses the custom field “pageviews”</dd>\n	<dd>Default: <code>countxx</code></dd>\n	<dt><strong>category</strong></dt>\n	<dd>Restrict counter to articles in the specified category/categories.</dd>\n	<dd>Example: <code>category=&quot;apples,pears&quot;</code></dd>\n	<dd>Default: <em>empty</em> (=no restriction)</dd>\n	<dt><strong>section</strong></dt>\n	<dd>Restrict counter to articles in the specified section(s).</dd>\n	<dd>Example: <code>section=&quot;fruit,vegetables&quot;</code></dd>\n	<dd>Default: <em>empty</em> (=no restriction)</dd>\n	<dt><strong>force</strong></dt>\n	<dd>Force all articles to use a counter. Set this if you want your counter on all articles. You no longer need to manually set the custom field to zero in each article. The section/category filters still apply.</dd>\n	<dd>Example: <code>force=&quot;1&quot;</code></dd>\n	<dd>Default: <code>0</code></dd>\n	<dt><strong>reset</strong></dt>\n	<dd>Reset the article counter to a specified number. Only whole numbers are permitted.</dd>\n	<dd>Example: <code>reset=&quot;0&quot;</code> resets the counter to zero, <code>reset=&quot;10&quot;</code> resets the counter to ten.</dd>\n	<dd>Default: <em>empty</em></dd>\n	<dt><strong>operator</strong></dt>\n	<dd>Optionally subtract instead of advance the counter.</dd>\n	<dd>Example: <code>operator=&quot;-&quot;</code> or <code>operator=&quot;subtract&quot;</code> sets the counter to subtract one.</dd>\n	<dd>Default: <code>add</code></dd>\n</dl>\n\n\n<h2>Examples</h2>\n\n<p><em><strong>Reminder:</strong> Before you start, set up a custom field you wish to use for the counter. Then set the value of that custom field to <code>0</code> for all the articles that should be counted.</em></p>\n\n\n<h3>Example: Adding dzd_counter_view to Textpattern&#8217;s <code>default</code> article form.</h3>\n\n<p>Add the <code>dzd_counter_view</code> tag to the <code>if_individual_article</code> section of the form. The following is part of the top of the <code>default</code> form:</p>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n    &lt;h1 itemprop=&quot;headline&quot;&gt;\n        &lt;txp:title /&gt;\n    &lt;/h1&gt;\n    &lt;txp:dzd_counter_view /&gt;\n&lt;txp:else /&gt;\n    &lt;h1 itemprop=&quot;headline&quot;&gt;\n        &lt;a href=&quot;&lt;txp:permlink /&gt;&quot; itemprop=&quot;url&quot;&gt;\n            &lt;txp:title /&gt;\n        &lt;/a&gt;\n    &lt;/h1&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>This example assumes you are using a custom field called <code>countxx</code>.</p>\n\n\n<h3>Example: Using your own custom field name</h3>\n\n<p>The default custom field name is <code>countxx</code>. If you wish to use another custom field name, for example “pageviews”, use:</p>\n\n<pre><code>&lt;txp:dzd_counter_view custom_field=&quot;pageviews&quot; /&gt;</code></pre>\n\n\n<h3>Example: Displaying the counter</h3>\n\n<pre><code>&lt;txp:if_custom_field name=&quot;countxx&quot; value=&quot;&quot;&gt;&lt;txp:else /&gt;\n    &lt;p class=&quot;counter&quot;&gt;This article has been viewed &lt;txp:custom_field name=&quot;countxx&quot; /&gt; times.&lt;/p&gt;\n&lt;/txp:if_custom_field&gt;</code></pre>\n\n<p>If using a different custom field name, replace <code>countxx</code> with your own custom field name.</p>\n\n\n<h3>Example: Count all articles in the “tips” section</h3>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n    &lt;h1&gt;&lt;txp:title /&gt;&lt;/h1&gt;\n    &lt;txp:dzd_counter_view section=&quot;tips&quot; force=&quot;1&quot; /&gt;\n&lt;/txp:if_individual_article&gt;</code></pre>\n\n<p>Using the attribute <code>force=&quot;1&quot;</code> means you do not need to manually add “0” to your custom field to start the counter.</p>\n\n<p>If you wish to count just the textpattern tips articles (in section “tips” and category “textpattern”), you could use:</p>\n\n<pre><code>&lt;txp:dzd_counter_view section=&quot;tips&quot; category=&quot;textpattern&quot; force=&quot;1&quot; /&gt;</code></pre>\n\n\n<h3>Example: Get the top ten accessed articles in the “tips” section</h3>\n\n<pre><code>&lt;txp:article_custom section=&quot;tips&quot; sort=&quot;custom_2+0 desc&quot; limit=&quot;10&quot; wraptag=&quot;ul&quot; break=&quot;li&quot; label=&quot;Top ten tips&quot; labeltag=&quot;h3&quot;&gt;\n    &lt;txp:title /&gt;: &lt;txp:custom_field name=&quot;countxx&quot; /&gt; pageviews\n&lt;/txp:article_custom&gt;</code></pre>\n\n<p>Note that for the <code>sort</code> attribute, you need to use <strong>custom_X</strong> instead of the custom field name, where <strong>X</strong> is the custom field number. The addition of <code>+0</code> makes txp:article_custom sort numerically (i.e. 1-9 comes before 10) and using <code>desc</code> sorts from largest to smallest.</p>\n\n<p>If you have less than 10 counted articles, you will need to stop empty article counters showing, e.g.:</p>\n\n<pre><code>&lt;txp:article_custom section=&quot;tips&quot; sort=&quot;custom_2+0 desc&quot; limit=&quot;10&quot; wraptag=&quot;ul&quot; break=&quot;li&quot; label=&quot;Top ten tips&quot; labeltag=&quot;h3&quot;&gt;\n    &lt;txp:if_custom_field name=&quot;countxx&quot; value=&quot;&quot;&gt;&lt;txp:else /&gt;\n        &lt;txp:title /&gt;: &lt;txp:custom_field name=&quot;countxx&quot; /&gt; pageviews\n    &lt;/txp:if_custom_field&gt;\n&lt;/txp:article_custom&gt;</code></pre>\n\n\n<h3>Example: Show the first ten visitors of the week a freebie</h3>\n\n<p>This is an example of using the <code>reset</code> and <code>operator</code> attributes.</p>\n\n<pre><code>&lt;!-- Put day of the week in a variable: 0 = sunday … 6 = saturday --&gt;\n&lt;txp:variable name=&quot;weekday&quot;&gt;&lt;txp:php&gt;echo date(\'w\');&lt;/txp:php&gt;&lt;/txp:variable&gt;\n&lt;!-- It\'s Monday (1): reset counter to 10 if countdown is in \'starter state\' --&gt;\n&lt;txp:if_custom_field name=&quot;countxx&quot; value=&quot;99&quot;&gt;\n    &lt;txp:if_variable name=&quot;weekday&quot; value=&quot;1&quot;&gt;\n        &lt;txp:dzd_counter_view reset=&quot;10&quot; /&gt;\n    &lt;/txp:if_variable&gt;\n&lt;/txp:if_custom_field&gt;\n&lt;!-- It\'s end of the week (Sunday: 0), set countdown to \'starter state\' (99) --&gt;\n&lt;txp:if_variable name=&quot;weekday&quot; value=&quot;0&quot;&gt;\n    &lt;txp:dzd_counter_view reset=&quot;99&quot; /&gt;\n&lt;/txp:if_variable&gt;\n&lt;!-- Show the freebie message if the counter (custom field \'countxx\') is 1-10 --&gt;\n&lt;txp:if_custom_field name=&quot;countxx&quot; match=&quot;pattern&quot; value=&quot;^([1-9]|10)$&quot;&gt;\n    &lt;txp:dzd_counter_view operator=&quot;subtract&quot; /&gt;\n    &lt;p&gt;Hello, Early Bird! Here\'s your freebie.&lt;/p&gt;\n&lt;/txp:if_custom_field&gt;</code></pre>\n\n<p>In this example the counter is reset to ‘10’ on Monday and the message is displayed only for counter values of 1-10 (regex pattern: <code>^([1-9]|10)$</code>). Each time the message is displayed, the counter decreases by 1 (<code>operator=&quot;subtract&quot;</code>) until 0 is reached and the message no longer displays.</p>\n\n<p>To ensure the counter resets to 10 only once on a Monday, the reset can only happen when the counter is ‘primed to start’. For this we use a counter value that is well out of range (e.g. 99). At the end of the week, (e.g. on Sunday: weekday=0), the starter state is reset to ‘99’ in readiness for Monday.</p>\n\n<p>You could use the same code with <code>echo date(\'j\');</code> to make an announcement on a particular day of each month, or with <code>echo date(\'G\');</code> for a Happy Hour offer. See the php <a href=\"http://localhost/site1/php.net/manual/function.date.php\" rel=\"nofollow\">date()</a> function for more details.</p>\n\n\n<h2>Help &amp; Changelog</h2>\n\n<p>Help is always available from the friendly people on the <a href=\"https://forum.textpattern.io/viewtopic.php?pid=126594\" rel=\"nofollow\">textpattern forum</a>. French instructions are available on <a href=\"http://info.ix-dz.com/plugin/8/dzdcounterview-02\" rel=\"nofollow\">my homepage</a>.</p>\n\n<h3>Changelog</h3>\n\n<h4>v0.4</h4>\n\n<ul>\n	<li>Tag registered with the txp tag registry.</li>\n	<li>Added optional <code>reset</code> attribute.</li>\n	<li>Added optional <code>operator</code> attribute to optionally subtract per pageview.</li>\n	<li>More informative help.</li>\n</ul>\n\n<h4>v0.3</h4>\n\n<ul>\n	<li>Added optional <code>custom_field</code> attribute to specif own custom fields.</li>\n</ul>\n\n<h4>v0.2</h4>\n\n<ul>\n	<li>Added optional <code>section</code> and <code>category</code> attributes to restrict counting to certain sections or categories.</li>\n	<li>Added optional <code>force</code> attribute to apply counter to all articles (not just those with the counter custom field set to 0)</li>\n</ul>\n\n<h4>v0.1</h4>\n\n<ul>\n	<li>Initial version</li>\n</ul>', '\n// This is a PLUGIN TEMPLATE for Textpattern CMS.\n\n// Copy this file to a new name like abc_myplugin.php.  Edit the code, then\n// run this file at the command line to produce a plugin for distribution:\n// $ php abc_myplugin.php > abc_myplugin-0.1.txt\n\n// Plugin name is optional.  If unset, it will be extracted from the current\n// file name. Plugin names should start with a three letter prefix which is\n// unique and reserved for each plugin author (\'abc\' is just an example).\n// Uncomment and edit this line to override:\n$plugin[\'name\'] = \'dzd_counter_view\';\n\n// Allow raw HTML help, as opposed to Textile.\n// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\n// 1 = Plugin help is in raw HTML.  Not recommended.\n# $plugin[\'allow_html_help\'] = 0;\n\n$plugin[\'version\'] = \'0.4\';\n$plugin[\'author\'] = \'Messaoudi Rabah (Dragondz)\';\n$plugin[\'author_uri\'] = \'http://info.ix-dz.com/\';\n$plugin[\'description\'] = \'Count article pageviews using a custom field\';\n\n// Plugin load order:\n// The default value of 5 would fit most plugins, while for instance comment\n// spam evaluators or URL redirectors would probably want to run earlier\n// (1...4) to prepare the environment for everything else that follows.\n// Values 6...9 should be considered for plugins which would work late.\n// This order is user-overrideable.\n$plugin[\'order\'] = \'5\';\n\n// Plugin \'type\' defines where the plugin is loaded\n// 0 = public              : only on the public side of the website (default)\n// 1 = public+admin        : on both the public and admin side\n// 2 = library             : only when include_plugin() or require_plugin() is called\n// 3 = admin               : only on the admin side (no AJAX)\n// 4 = admin+ajax          : only on the admin side (AJAX supported)\n// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\n$plugin[\'type\'] = \'0\';\n\n// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\n// Use an appropriately OR-ed combination of these flags.\n// The four high-order bits 0xf000 are available for this plugin\'s private use\nif (!defined(\'PLUGIN_HAS_PREFS\')) {\n    define(\'PLUGIN_HAS_PREFS\', 0x0001);\n} // This plugin wants to receive \"plugin_prefs.{$plugin[\'name\']}\" events\nif (!defined(\'PLUGIN_LIFECYCLE_NOTIFY\')) {\n    define(\'PLUGIN_LIFECYCLE_NOTIFY\', 0x0002);\n} // This plugin wants to receive \"plugin_lifecycle.{$plugin[\'name\']}\" events\n\n$plugin[\'flags\'] = \'0\';\n\nif (!defined(\'txpinterface\')) {\n    @include_once(\'zem_tpl.php\');\n}\n\nif (0) {\n    ?>\n# --- BEGIN PLUGIN HELP ---\n\nh1. dzd_counter_view\n\nThis plugin can be used to count article pageviews using a custom field.\n\nYou can count pageviews for:\n\n* individual articles you explicitly specify\n* all articles\n* articles in specific sections or categories.\n\nh3. Setting up\n\n# Install the plugin and activate it.\n# Set up a custom field to use as your counter. The default custom field name is @countxx@ but you may use any custom field name you like.\n# Add the tag @dzd_counter_view@ in your individual article form, or in the \"if_individual_article\":https://docs.textpattern.io/tags/if_individual_article section of a form or txp:article(_custom) container. Use the @custom_field@ attribute to specify your own custom field name and the @section@ and/or @category@ attributes to count only articles in specific sections or categories (see the tag reference)\n# If you wish to add a counter to specific articles, edit the respective articles in the and enter value @0@ in the corresponding custom field. The counter will only advance on those articles. If you wish to add the counter to all articles, use the attribute @force=\"1\"@ (see the tag reference).\n\nThe counter will advance by 1 every time the url for the page is accessed. You can follow the counter by visiting the article edit page. If you want to display the counter value on your homepage, use @<txp:custom_field name=\"countxx\" />@ (or your own custom field name).\n\nh2. Tag reference\n\nh3. Tag: @<txp:dzd_counter_view>@\n\nLogs each pageview to a custom field.\n\n; *custom_field*\n: Name of the custom field to use for the counter.\n: Example: @custom_field=\"pageviews\"@ uses the custom field \"pageviews\"\n: Default: @countxx@\n; *category*\n: Restrict counter to articles in the specified category/categories.\n: Example: @category=\"apples,pears\"@\n: Default: _empty_ (=no restriction)\n; *section*\n: Restrict counter to articles in the specified section(s).\n: Example: @section=\"fruit,vegetables\"@\n: Default: _empty_ (=no restriction)\n; *force*\n: Force all articles to use a counter. Set this if you want your counter on all articles. You no longer need to manually set the custom field to zero in each article. The section/category filters still apply.\n: Example: @force=\"1\"@\n: Default: @0@\n; *reset*\n: Reset the article counter to a specified number. Only whole numbers are permitted.\n: Example: @reset=\"0\"@ resets the counter to zero, @reset=\"10\"@ resets the counter to ten.\n: Default: _empty_\n; *operator*\n: Optionally subtract instead of advance the counter.\n: Example: @operator=\"-\"@ or @operator=\"subtract\"@ sets the counter to subtract one.\n: Default: @add@\n\n\nh2. Examples\n\n_*Reminder:* Before you start, set up a custom field you wish to use for the counter. Then set the value of that custom field to @0@ for all the articles that should be counted._\n\n\nh3. Example: Adding dzd_counter_view to Textpattern\'s @default@ article form.\n\nAdd the @dzd_counter_view@ tag to the @if_individual_article@ section of the form. The following is part of the top of the @default@ form:\n\nbc.. <txp:if_individual_article>\n    <h1 itemprop=\"headline\">\n        <txp:title />\n    </h1>\n    <txp:dzd_counter_view />\n<txp:else />\n    <h1 itemprop=\"headline\">\n        <a href=\"<txp:permlink />\" itemprop=\"url\">\n            <txp:title />\n        </a>\n    </h1>\n</txp:if_individual_article>\n\np. This example assumes you are using a custom field called @countxx@.\n\n\nh3. Example: Using your own custom field name\n\nThe default custom field name is @countxx@. If you wish to use another custom field name, for example \"pageviews\", use:\n\nbc. <txp:dzd_counter_view custom_field=\"pageviews\" />\n\n\nh3. Example: Displaying the counter\n\nbc.. <txp:if_custom_field name=\"countxx\" value=\"\"><txp:else />\n    <p class=\"counter\">This article has been viewed <txp:custom_field name=\"countxx\" /> times.</p>\n</txp:if_custom_field>\n\np. If using a different custom field name, replace @countxx@ with your own custom field name.\n\n\nh3. Example: Count all articles in the \"tips\" section\n\nbc.. <txp:if_individual_article>\n    <h1><txp:title /></h1>\n    <txp:dzd_counter_view section=\"tips\" force=\"1\" />\n</txp:if_individual_article>\n\np. Using the attribute @force=\"1\"@ means you do not need to manually add \"0\" to your custom field to start the counter.\n\nIf you wish to count just the textpattern tips articles (in section \"tips\" and category \"textpattern\"), you could use:\n\nbc. <txp:dzd_counter_view section=\"tips\" category=\"textpattern\" force=\"1\" />\n\n\nh3. Example: Get the top ten accessed articles in the \"tips\" section\n\nbc.. <txp:article_custom section=\"tips\" sort=\"custom_2+0 desc\" limit=\"10\" wraptag=\"ul\" break=\"li\" label=\"Top ten tips\" labeltag=\"h3\">\n    <txp:title />: <txp:custom_field name=\"countxx\" /> pageviews\n</txp:article_custom>\n\np. Note that for the @sort@ attribute, you need to use *custom_X* instead of the custom field name, where *X* is the custom field number. The addition of @+0@ makes txp:article_custom sort numerically (i.e. 1-9 comes before 10) and using @desc@ sorts from largest to smallest.\n\nIf you have less than 10 counted articles, you will need to stop empty article counters showing, e.g.:\n\nbc.. <txp:article_custom section=\"tips\" sort=\"custom_2+0 desc\" limit=\"10\" wraptag=\"ul\" break=\"li\" label=\"Top ten tips\" labeltag=\"h3\">\n    <txp:if_custom_field name=\"countxx\" value=\"\"><txp:else />\n        <txp:title />: <txp:custom_field name=\"countxx\" /> pageviews\n    </txp:if_custom_field>\n</txp:article_custom>\n\n\nh3. Example: Show the first ten visitors of the week a freebie\n\nThis is an example of using the @reset@ and @operator@ attributes.\n\nbc.. <!-- Put day of the week in a variable: 0 = sunday … 6 = saturday -->\n<txp:variable name=\"weekday\"><txp:php>echo date(\'w\');</txp:php></txp:variable>\n<!-- It\'s Monday (1): reset counter to 10 if countdown is in \'starter state\' -->\n<txp:if_custom_field name=\"countxx\" value=\"99\">\n    <txp:if_variable name=\"weekday\" value=\"1\">\n        <txp:dzd_counter_view reset=\"10\" />\n    </txp:if_variable>\n</txp:if_custom_field>\n<!-- It\'s end of the week (Sunday: 0), set countdown to \'starter state\' (99) -->\n<txp:if_variable name=\"weekday\" value=\"0\">\n    <txp:dzd_counter_view reset=\"99\" />\n</txp:if_variable>\n<!-- Show the freebie message if the counter (custom field \'countxx\') is 1-10 -->\n<txp:if_custom_field name=\"countxx\" match=\"pattern\" value=\"^([1-9]|10)$\">\n    <txp:dzd_counter_view operator=\"subtract\" />\n    <p>Hello, Early Bird! Here\'s your freebie.</p>\n</txp:if_custom_field>\n\np. In this example the counter is reset to \'10\' on Monday and the message is displayed only for counter values of 1-10 (regex pattern: @^([1-9]|10)$@). Each time the message is displayed, the counter decreases by 1 (@operator=\"subtract\"@) until 0 is reached and the message no longer displays.\n\nTo ensure the counter resets to 10 only once on a Monday, the reset can only happen when the counter is \'primed to start\'. For this we use a counter value that is well out of range (e.g. 99). At the end of the week, (e.g. on Sunday: weekday=0), the starter state is reset to \'99\' in readiness for Monday.\n\nYou could use the same code with @echo date(\'j\');@ to make an announcement on a particular day of each month, or with @echo date(\'G\');@ for a Happy Hour offer. See the php \"date()\":php.net/manual/function.date.php function for more details.\n\n\nh2. Help & Changelog\n\nHelp is always available from the friendly people on the \"textpattern forum\":https://forum.textpattern.io/viewtopic.php?pid=126594. French instructions are available on \"my homepage\":http://info.ix-dz.com/plugin/8/dzdcounterview-02.\n\nh3. Changelog\n\nh4. v0.4\n\n* Tag registered with the txp tag registry.\n* Added optional @reset@ attribute.\n* Added optional @operator@ attribute to optionally subtract per pageview.\n* More informative help.\n\nh4. v0.3\n\n* Added optional @custom_field@ attribute to specif own custom fields.\n\nh4. v0.2\n\n* Added optional @section@ and @category@ attributes to restrict counting to certain sections or categories.\n* Added optional @force@ attribute to apply counter to all articles (not just those with the counter custom field set to 0)\n\nh4. v0.1\n\n* Initial version\n\n# --- END PLUGIN HELP ---\n<?php\n}\n\n// Register public tag with the tag registry\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'dzd_counter_view\');\n}\n\n\nfunction dzd_counter_view($atts)\n{\n    global $prefs, $id, $thisarticle;\n\n    extract(lAtts(array(\n            \'custom_field\' => \'countxx\',\n            \'category\' => \'\',\n            \'section\' => \'\',\n            \'force\' => 0,\n            \'reset\' => \'\',\n            \'operator\' => \'add\'\n        ), $atts));\n\n    // Get custom field\n    $key = array_search($custom_field, $prefs);\n\n    // Only count articles in a specified category\n    if ($category<>\'\') {\n        $cat1 = (in_list($thisarticle[\'category1\'], $category) ? 1 : 0) || (in_list($thisarticle[\'category2\'], $category) ? 1 : 0);\n    } else {\n        $cat1 = 1;\n    }\n\n    // Only count articles in a specified section\n    if ($section<>\'\') {\n        $sec1 = in_list($thisarticle[\'section\'], $section) ? 1 : 0;\n    } else {\n        $sec1 = 1;\n    }\n\n    // If custom field exists (and in category/section if specified)\n    if (($key<>\'\') && $cat1 && $sec1) {\n        $key = str_replace(\"_set\", \"\", $key);\n\n        if (is_numeric($thisarticle[$custom_field])) {\n            if ($reset<>\'\' && is_numeric($reset)) {\n                // Reset counter to an integer\n                $cal = $reset;\n            } else {\n                if (in_array($operator, array(\'-\',\'subtract\'))) {\n                    // Subtract counter by 1\n                    $cal = $thisarticle[$custom_field]-1;\n                } else {\n                    // Advance counter by 1\n                    $cal = $thisarticle[$custom_field]+1;\n                }\n            }\n        } elseif ($force) {\n            // Populate empty custom_field with 0\n            $cal = \'0\';\n        }\n\n        // Update custom field value\n        $updated = safe_update(\"textpattern\", $key.\"=\'\".doSlash($cal).\"\'\", \"ID=\'\".$id.\"\'\");\n    }\n}\n', '\n// This is a PLUGIN TEMPLATE for Textpattern CMS.\n\n// Copy this file to a new name like abc_myplugin.php.  Edit the code, then\n// run this file at the command line to produce a plugin for distribution:\n// $ php abc_myplugin.php > abc_myplugin-0.1.txt\n\n// Plugin name is optional.  If unset, it will be extracted from the current\n// file name. Plugin names should start with a three letter prefix which is\n// unique and reserved for each plugin author (\'abc\' is just an example).\n// Uncomment and edit this line to override:\n$plugin[\'name\'] = \'dzd_counter_view\';\n\n// Allow raw HTML help, as opposed to Textile.\n// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\n// 1 = Plugin help is in raw HTML.  Not recommended.\n# $plugin[\'allow_html_help\'] = 0;\n\n$plugin[\'version\'] = \'0.4\';\n$plugin[\'author\'] = \'Messaoudi Rabah (Dragondz)\';\n$plugin[\'author_uri\'] = \'http://info.ix-dz.com/\';\n$plugin[\'description\'] = \'Count article pageviews using a custom field\';\n\n// Plugin load order:\n// The default value of 5 would fit most plugins, while for instance comment\n// spam evaluators or URL redirectors would probably want to run earlier\n// (1...4) to prepare the environment for everything else that follows.\n// Values 6...9 should be considered for plugins which would work late.\n// This order is user-overrideable.\n$plugin[\'order\'] = \'5\';\n\n// Plugin \'type\' defines where the plugin is loaded\n// 0 = public              : only on the public side of the website (default)\n// 1 = public+admin        : on both the public and admin side\n// 2 = library             : only when include_plugin() or require_plugin() is called\n// 3 = admin               : only on the admin side (no AJAX)\n// 4 = admin+ajax          : only on the admin side (AJAX supported)\n// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\n$plugin[\'type\'] = \'0\';\n\n// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\n// Use an appropriately OR-ed combination of these flags.\n// The four high-order bits 0xf000 are available for this plugin\'s private use\nif (!defined(\'PLUGIN_HAS_PREFS\')) {\n    define(\'PLUGIN_HAS_PREFS\', 0x0001);\n} // This plugin wants to receive \"plugin_prefs.{$plugin[\'name\']}\" events\nif (!defined(\'PLUGIN_LIFECYCLE_NOTIFY\')) {\n    define(\'PLUGIN_LIFECYCLE_NOTIFY\', 0x0002);\n} // This plugin wants to receive \"plugin_lifecycle.{$plugin[\'name\']}\" events\n\n$plugin[\'flags\'] = \'0\';\n\nif (!defined(\'txpinterface\')) {\n    @include_once(\'zem_tpl.php\');\n}\n\nif (0) {\n    ?>\n# --- BEGIN PLUGIN HELP ---\n\nh1. dzd_counter_view\n\nThis plugin can be used to count article pageviews using a custom field.\n\nYou can count pageviews for:\n\n* individual articles you explicitly specify\n* all articles\n* articles in specific sections or categories.\n\nh3. Setting up\n\n# Install the plugin and activate it.\n# Set up a custom field to use as your counter. The default custom field name is @countxx@ but you may use any custom field name you like.\n# Add the tag @dzd_counter_view@ in your individual article form, or in the \"if_individual_article\":https://docs.textpattern.io/tags/if_individual_article section of a form or txp:article(_custom) container. Use the @custom_field@ attribute to specify your own custom field name and the @section@ and/or @category@ attributes to count only articles in specific sections or categories (see the tag reference)\n# If you wish to add a counter to specific articles, edit the respective articles in the and enter value @0@ in the corresponding custom field. The counter will only advance on those articles. If you wish to add the counter to all articles, use the attribute @force=\"1\"@ (see the tag reference).\n\nThe counter will advance by 1 every time the url for the page is accessed. You can follow the counter by visiting the article edit page. If you want to display the counter value on your homepage, use @<txp:custom_field name=\"countxx\" />@ (or your own custom field name).\n\nh2. Tag reference\n\nh3. Tag: @<txp:dzd_counter_view>@\n\nLogs each pageview to a custom field.\n\n; *custom_field*\n: Name of the custom field to use for the counter.\n: Example: @custom_field=\"pageviews\"@ uses the custom field \"pageviews\"\n: Default: @countxx@\n; *category*\n: Restrict counter to articles in the specified category/categories.\n: Example: @category=\"apples,pears\"@\n: Default: _empty_ (=no restriction)\n; *section*\n: Restrict counter to articles in the specified section(s).\n: Example: @section=\"fruit,vegetables\"@\n: Default: _empty_ (=no restriction)\n; *force*\n: Force all articles to use a counter. Set this if you want your counter on all articles. You no longer need to manually set the custom field to zero in each article. The section/category filters still apply.\n: Example: @force=\"1\"@\n: Default: @0@\n; *reset*\n: Reset the article counter to a specified number. Only whole numbers are permitted.\n: Example: @reset=\"0\"@ resets the counter to zero, @reset=\"10\"@ resets the counter to ten.\n: Default: _empty_\n; *operator*\n: Optionally subtract instead of advance the counter.\n: Example: @operator=\"-\"@ or @operator=\"subtract\"@ sets the counter to subtract one.\n: Default: @add@\n\n\nh2. Examples\n\n_*Reminder:* Before you start, set up a custom field you wish to use for the counter. Then set the value of that custom field to @0@ for all the articles that should be counted._\n\n\nh3. Example: Adding dzd_counter_view to Textpattern\'s @default@ article form.\n\nAdd the @dzd_counter_view@ tag to the @if_individual_article@ section of the form. The following is part of the top of the @default@ form:\n\nbc.. <txp:if_individual_article>\n    <h1 itemprop=\"headline\">\n        <txp:title />\n    </h1>\n    <txp:dzd_counter_view />\n<txp:else />\n    <h1 itemprop=\"headline\">\n        <a href=\"<txp:permlink />\" itemprop=\"url\">\n            <txp:title />\n        </a>\n    </h1>\n</txp:if_individual_article>\n\np. This example assumes you are using a custom field called @countxx@.\n\n\nh3. Example: Using your own custom field name\n\nThe default custom field name is @countxx@. If you wish to use another custom field name, for example \"pageviews\", use:\n\nbc. <txp:dzd_counter_view custom_field=\"pageviews\" />\n\n\nh3. Example: Displaying the counter\n\nbc.. <txp:if_custom_field name=\"countxx\" value=\"\"><txp:else />\n    <p class=\"counter\">This article has been viewed <txp:custom_field name=\"countxx\" /> times.</p>\n</txp:if_custom_field>\n\np. If using a different custom field name, replace @countxx@ with your own custom field name.\n\n\nh3. Example: Count all articles in the \"tips\" section\n\nbc.. <txp:if_individual_article>\n    <h1><txp:title /></h1>\n    <txp:dzd_counter_view section=\"tips\" force=\"1\" />\n</txp:if_individual_article>\n\np. Using the attribute @force=\"1\"@ means you do not need to manually add \"0\" to your custom field to start the counter.\n\nIf you wish to count just the textpattern tips articles (in section \"tips\" and category \"textpattern\"), you could use:\n\nbc. <txp:dzd_counter_view section=\"tips\" category=\"textpattern\" force=\"1\" />\n\n\nh3. Example: Get the top ten accessed articles in the \"tips\" section\n\nbc.. <txp:article_custom section=\"tips\" sort=\"custom_2+0 desc\" limit=\"10\" wraptag=\"ul\" break=\"li\" label=\"Top ten tips\" labeltag=\"h3\">\n    <txp:title />: <txp:custom_field name=\"countxx\" /> pageviews\n</txp:article_custom>\n\np. Note that for the @sort@ attribute, you need to use *custom_X* instead of the custom field name, where *X* is the custom field number. The addition of @+0@ makes txp:article_custom sort numerically (i.e. 1-9 comes before 10) and using @desc@ sorts from largest to smallest.\n\nIf you have less than 10 counted articles, you will need to stop empty article counters showing, e.g.:\n\nbc.. <txp:article_custom section=\"tips\" sort=\"custom_2+0 desc\" limit=\"10\" wraptag=\"ul\" break=\"li\" label=\"Top ten tips\" labeltag=\"h3\">\n    <txp:if_custom_field name=\"countxx\" value=\"\"><txp:else />\n        <txp:title />: <txp:custom_field name=\"countxx\" /> pageviews\n    </txp:if_custom_field>\n</txp:article_custom>\n\n\nh3. Example: Show the first ten visitors of the week a freebie\n\nThis is an example of using the @reset@ and @operator@ attributes.\n\nbc.. <!-- Put day of the week in a variable: 0 = sunday … 6 = saturday -->\n<txp:variable name=\"weekday\"><txp:php>echo date(\'w\');</txp:php></txp:variable>\n<!-- It\'s Monday (1): reset counter to 10 if countdown is in \'starter state\' -->\n<txp:if_custom_field name=\"countxx\" value=\"99\">\n    <txp:if_variable name=\"weekday\" value=\"1\">\n        <txp:dzd_counter_view reset=\"10\" />\n    </txp:if_variable>\n</txp:if_custom_field>\n<!-- It\'s end of the week (Sunday: 0), set countdown to \'starter state\' (99) -->\n<txp:if_variable name=\"weekday\" value=\"0\">\n    <txp:dzd_counter_view reset=\"99\" />\n</txp:if_variable>\n<!-- Show the freebie message if the counter (custom field \'countxx\') is 1-10 -->\n<txp:if_custom_field name=\"countxx\" match=\"pattern\" value=\"^([1-9]|10)$\">\n    <txp:dzd_counter_view operator=\"subtract\" />\n    <p>Hello, Early Bird! Here\'s your freebie.</p>\n</txp:if_custom_field>\n\np. In this example the counter is reset to \'10\' on Monday and the message is displayed only for counter values of 1-10 (regex pattern: @^([1-9]|10)$@). Each time the message is displayed, the counter decreases by 1 (@operator=\"subtract\"@) until 0 is reached and the message no longer displays.\n\nTo ensure the counter resets to 10 only once on a Monday, the reset can only happen when the counter is \'primed to start\'. For this we use a counter value that is well out of range (e.g. 99). At the end of the week, (e.g. on Sunday: weekday=0), the starter state is reset to \'99\' in readiness for Monday.\n\nYou could use the same code with @echo date(\'j\');@ to make an announcement on a particular day of each month, or with @echo date(\'G\');@ for a Happy Hour offer. See the php \"date()\":php.net/manual/function.date.php function for more details.\n\n\nh2. Help & Changelog\n\nHelp is always available from the friendly people on the \"textpattern forum\":https://forum.textpattern.io/viewtopic.php?pid=126594. French instructions are available on \"my homepage\":http://info.ix-dz.com/plugin/8/dzdcounterview-02.\n\nh3. Changelog\n\nh4. v0.4\n\n* Tag registered with the txp tag registry.\n* Added optional @reset@ attribute.\n* Added optional @operator@ attribute to optionally subtract per pageview.\n* More informative help.\n\nh4. v0.3\n\n* Added optional @custom_field@ attribute to specif own custom fields.\n\nh4. v0.2\n\n* Added optional @section@ and @category@ attributes to restrict counting to certain sections or categories.\n* Added optional @force@ attribute to apply counter to all articles (not just those with the counter custom field set to 0)\n\nh4. v0.1\n\n* Initial version\n\n# --- END PLUGIN HELP ---\n<?php\n}\n\n// Register public tag with the tag registry\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'dzd_counter_view\');\n}\n\n\nfunction dzd_counter_view($atts)\n{\n    global $prefs, $id, $thisarticle;\n\n    extract(lAtts(array(\n            \'custom_field\' => \'countxx\',\n            \'category\' => \'\',\n            \'section\' => \'\',\n            \'force\' => 0,\n            \'reset\' => \'\',\n            \'operator\' => \'add\'\n        ), $atts));\n\n    // Get custom field\n    $key = array_search($custom_field, $prefs);\n\n    // Only count articles in a specified category\n    if ($category<>\'\') {\n        $cat1 = (in_list($thisarticle[\'category1\'], $category) ? 1 : 0) || (in_list($thisarticle[\'category2\'], $category) ? 1 : 0);\n    } else {\n        $cat1 = 1;\n    }\n\n    // Only count articles in a specified section\n    if ($section<>\'\') {\n        $sec1 = in_list($thisarticle[\'section\'], $section) ? 1 : 0;\n    } else {\n        $sec1 = 1;\n    }\n\n    // If custom field exists (and in category/section if specified)\n    if (($key<>\'\') && $cat1 && $sec1) {\n        $key = str_replace(\"_set\", \"\", $key);\n\n        if (is_numeric($thisarticle[$custom_field])) {\n            if ($reset<>\'\' && is_numeric($reset)) {\n                // Reset counter to an integer\n                $cal = $reset;\n            } else {\n                if (in_array($operator, array(\'-\',\'subtract\'))) {\n                    // Subtract counter by 1\n                    $cal = $thisarticle[$custom_field]-1;\n                } else {\n                    // Advance counter by 1\n                    $cal = $thisarticle[$custom_field]+1;\n                }\n            }\n        } elseif ($force) {\n            // Populate empty custom_field with 0\n            $cal = \'0\';\n        }\n\n        // Update custom field value\n        $updated = safe_update(\"textpattern\", $key.\"=\'\".doSlash($cal).\"\'\", \"ID=\'\".$id.\"\'\");\n    }\n}\n', '2b0d68e300dafe5d05402b4612bbc17e', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_wp_import', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Import WordPress-generated RSS (XML) file contents to Textpattern.', '<p class=\"alert-block information\"><strong><a href=\"?event=kuo_wp_import\" style=\"display:block\">Import</a></strong></p>\n<h2>What does this plugin do?</h2>\n<p>It transforms WordPress <strong>categories</strong> to Textpattern categories, then it transforms WordPress <strong>posts</strong> and <strong>pages</strong> to Textpattern articles. The table below shows what bits of information are used from each page and post.</p>\n<table>\n<thead>\n<tr>\n<th>Before (in WordPress)</th>\n<th>After (in Textpattern)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Title</td>\n<td>Title</td>\n</tr>\n<tr>\n<td>Date when published</td>\n<td>Publish: date and time</td>\n</tr>\n<tr>\n<td>Commenting status</td>\n<td>Comment: on/off</td>\n</tr>\n<tr>\n<td>Post/Page status</td>\n<td>Status: Hidden, Pending or Live</td>\n</tr>\n<tr>\n<td>Categories and Terms</td>\n<td>Meta keywords</td>\n</tr>\n<tr>\n<td>Author username</td>\n<td>Posted by username</td>\n</tr>\n<tr>\n<td>Excerpt</td>\n<td>Excerpt</td>\n</tr>\n<tr>\n<td>Content</td>\n<td>Body</td>\n</tr>\n<tr>\n<td>URL slug</td>\n<td>URL-only title</td>\n</tr>\n</tbody>\n</table>\n<p>This tool does not import comments or media files (like images).</p>\n<p><code>kuo_wp_import</code> has been tested with Textpattern 4.6.2 and 4.7.0-dev, and XML files generated by WordPress 4.9.4.</p>\n<p>Please <strong><a href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=FY5GCBL2Q4G98\" rel=\"nofollow external\" target=\"_blank\">consider donating</a></strong> to make this tool better.</p>', 'if (@txpinterface === \'admin\') {\r\n\r\nadd_privs(\'kuo_wp_import\',1);\r\n\r\nregister_tab(\'extensions\',\'kuo_wp_import\',\'WP Import\');\r\n\r\nregister_callback(\'kuo_wp_import_tab\',\'kuo_wp_import\');\r\n\r\nfunction kuo_upload_form() {\r\n\r\n    return \'\r\n        <form method=\"post\" enctype=\"multipart/form-data\">\r\n            <fieldset>\r\n                <legend>Import content from WordPress to Textpattern</legend>\r\n                <p><strong>Choose a WordPress-generated <code>.xml</code> file that contains\r\n                for example categories, posts and pages. After choosing and uploading that file its\r\n                contents are <em>immediately</em> imported to this Textpattern.</strong></p>\r\n                <p><label for=\"kuo_file\">\'.gTxt(\'upload_file\').\':&nbsp;</label><input\r\n                type=\"file\"\r\n                id=\"kuo_file\"\r\n                name=\"kuo_file\"\r\n                size=\"55\"\r\n                accept=\".xml,text/xml\"\r\n                required=\"required\"\r\n                />&nbsp;<button type=\"submit\">\'.gTxt(\'upload\').\'</button></p>\r\n                <p>Max. file size: \'.kuo_max_file_upload_size().\' bytes.</p>\r\n            </fieldset>\r\n        </form>\r\n    \';\r\n}\r\n\r\nfunction kuo_max_file_upload_size() {\r\n\r\n    $max_file_size = safe_row(\'val\',\'txp_prefs\',\'`name` = \"file_max_upload_size\"\');\r\n    $max_file_size = $max_file_size[\'val\'];\r\n\r\n    settype($max_file_size,\'int\');\r\n\r\n    return $max_file_size;\r\n}\r\n\r\nfunction kuo_is_valid($data) {\r\n\r\n    if (kuo_max_file_upload_size() == 0) {\r\n        $max_file_size = 1048576;\r\n    }\r\n    else {\r\n        $max_file_size = kuo_max_file_upload_size();\r\n    }\r\n\r\n    if (\r\n        (is_array($data))\r\n        &&\r\n        (isset($data[\'kuo_file\'][\'type\'],$data[\'kuo_file\'][\'error\'],$data[\'kuo_file\'][\'size\']))\r\n        &&\r\n        ($data[\'kuo_file\'][\'type\'] === \'text/xml\')\r\n        &&\r\n        ($data[\'kuo_file\'][\'error\'] == 0)\r\n        &&\r\n        ($data[\'kuo_file\'][\'size\'] > 0)\r\n        &&\r\n        ($data[\'kuo_file\'][\'size\'] <= $max_file_size)\r\n    ) {\r\n        return true;\r\n    }\r\n    else {\r\n        return false;\r\n    }\r\n}\r\n\r\nfunction kuo_article_status($status) {\r\n\r\n    if ((isset($status)) && (!empty($status))) {\r\n\r\n        $status = strtolower($status);\r\n\r\n        if ($status === \'publish\') {\r\n            return 4;\r\n        }\r\n        elseif ($status === \'pending\') {\r\n            return 3;\r\n        }\r\n        else {\r\n            return 1;\r\n        }\r\n    }\r\n    else {\r\n        return 1;\r\n    }\r\n}\r\n\r\nfunction kuo_categories_to_keywords($categories) {\r\n    if ((isset($categories)) && (is_array($categories)) && (!empty($categories))) {\r\n\r\n        $keywords = NULL;\r\n\r\n        foreach ($categories as $categories) {\r\n            $keywords .= doslash($categories).\',\';\r\n        }\r\n\r\n        $keywords = rtrim($keywords,\',\');\r\n\r\n        return $keywords;\r\n\r\n    }\r\n    else {\r\n        return $categories;\r\n    }\r\n}\r\n\r\nfunction kuo_comment_status($status) {\r\n    if (isset($status)) {\r\n\r\n        $status = strtolower($status);\r\n\r\n        if ($status === \'open\') {\r\n            return 1;\r\n        }\r\n        else {\r\n            return 0;\r\n        }\r\n\r\n    }\r\n    else {\r\n        return 0;\r\n    }\r\n}\r\n\r\nfunction kuo_handle_xml($data) {\r\n\r\n    $data = file_get_contents($data);\r\n\r\n    # THIS ISN\'T A BEAUTY CONTEST:\r\n    $data = str_replace(\r\n        array(\r\n            \'<wp:\',\r\n            \'</wp:\',\r\n            \'<content:encoded>\',\r\n            \'</content:encoded>\',\r\n            \'<excerpt:encoded>\',\r\n            \'</excerpt:encoded>\',\r\n            \'<dc:creator>\',\r\n            \'</dc:creator>\'\r\n        ),\r\n        array(\r\n            \'<wp\',\r\n            \'</wp\',\r\n            \'<content>\',\r\n            \'</content>\',\r\n            \'<excerpt>\',\r\n            \'</excerpt>\',\r\n            \'<creator>\',\r\n            \'</creator>\'\r\n        ),\r\n        $data\r\n    );\r\n\r\n    $data = simplexml_load_string($data,\'SimpleXMLElement\',LIBXML_NOCDATA);\r\n\r\n    # OBJECTS TO ARRAYS\r\n    $data = json_encode($data);\r\n    $data = json_decode($data,true);\r\n\r\n    if (isset($data[\'channel\'])) {\r\n\r\n        $data = $data[\'channel\'];\r\n\r\n        # CONTAINERS\r\n        $category_sql = array();\r\n        $article_sql = array();\r\n\r\n        # BEGINS: CATEGORIES\r\n        if ((isset($data[\'wpcategory\'])) && (!empty($data[\'wpcategory\']))) {\r\n\r\n            # BEGINS: FOREACH\r\n            foreach ($data[\'wpcategory\'] as $data[\'wpcategory\']) {\r\n\r\n                # BEGINS: IS VALID CATEGORY\r\n                if (isset($data[\'wpcategory\'][\'wpcategory_nicename\'],$data[\'wpcategory\'][\'wpcat_name\'])) {\r\n\r\n                    $data[\'wpcategory\'][\'wpcategory_nicename\'] =\r\n                        substr($data[\'wpcategory\'][\'wpcategory_nicename\'],0,64);\r\n\r\n                    $category_sql[] =\r\n                    \'(\r\n                        \"\'.doslash($data[\'wpcategory\'][\'wpcategory_nicename\']).\'\",\r\n                        \"article\",\r\n                        \"root\",\r\n                        \"\'.doslash($data[\'wpcategory\'][\'wpcat_name\']).\'\"\r\n                    )\';\r\n                }\r\n                # ENDS: IS VALID CATEGORY\r\n            }\r\n            # ENDS: FOREACH\r\n        }\r\n        # ENDS: CATEGORIES\r\n\r\n        # BEGINS: ARTICLES\r\n        if ((isset($data[\'item\'])) && (!empty($data[\'item\']))) {\r\n\r\n            # BEGINS: FOREACH\r\n            foreach ($data[\'item\'] as $data[\'item\']) {\r\n\r\n                # BEGINS: IS VALID ARTICLE\r\n                if (\r\n                    (isset(\r\n                        $data[\'item\'][\'description\'],\r\n                        $data[\'item\'][\'title\'],\r\n                        $data[\'item\'][\'wppost_name\'],\r\n                        $data[\'item\'][\'wppost_date\'],\r\n                        $data[\'item\'][\'wpcomment_status\'],\r\n                        $data[\'item\'][\'wpstatus\'],\r\n                        $data[\'item\'][\'content\'],\r\n                        $data[\'item\'][\'excerpt\'],\r\n                        $data[\'item\'][\'creator\'],\r\n                        $data[\'item\'][\'wppost_type\'])\r\n                    )\r\n                    &&\r\n                    (!empty($data[\'item\'][\'title\']))\r\n                    &&\r\n                    (!empty($data[\'item\'][\'wppost_name\']))\r\n                    &&\r\n                    (in_array($data[\'item\'][\'wppost_type\'],array(\'post\',\'page\')))\r\n                ) {\r\n\r\n                    if (!is_array($data[\'item\'][\'content\'])) {\r\n                        $content = doslash($data[\'item\'][\'content\']);\r\n                    }\r\n                    else {\r\n                        $content = \'\';\r\n                    }\r\n\r\n                    if (!is_array($data[\'item\'][\'excerpt\'])) {\r\n                        $excerpt = doslash($data[\'item\'][\'excerpt\']);\r\n                    }\r\n                    else {\r\n                        $excerpt = \'\';\r\n                    }\r\n\r\n                    # BEGINS: ARTICLE-ONLY CATEGORIES\r\n                    if (isset($data[\'item\'][\'category\'])) {\r\n                        $categories = kuo_categories_to_keywords($data[\'item\'][\'category\']);\r\n                    }\r\n                    else {\r\n                        $categories = \'\';\r\n                    }\r\n                    # ENDS: ARTICLE-ONLY CATEGORIES\r\n\r\n                    $data[\'item\'][\'creator\'] = substr($data[\'item\'][\'creator\'],0,64);\r\n\r\n                    $article_sql[] =\r\n                    \'(\r\n                        \"\'.date(\'Y-m-d H:i:s\',strtotime($data[\'item\'][\'wppost_date\'])).\'\",\r\n                        NULL,\r\n                        \"\",\r\n                        \"\'.date(\'Y-m-d H:i:s\',strtotime($data[\'item\'][\'wppost_date\'])).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'creator\']).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'title\']).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'title\']).\'\",\r\n                        \"\'.$content.\'\",\r\n                        \"\'.$content.\'\",\r\n                        \"\'.$excerpt.\'\",\r\n                        \"\'.$excerpt.\'\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \'.kuo_comment_status($data[\'item\'][\'wpcomment_status\']).\',\r\n                        \"\",\r\n                        0,\r\n                        \'.kuo_article_status($data[\'item\'][\'wpstatus\']).\',\r\n                        1,\r\n                        1,\r\n                        \"articles\",\r\n                        \"\",\r\n                        \"\'.$categories.\'\",\r\n                        \"\",\r\n                        \"\'.doslash($data[\'item\'][\'wppost_name\']).\'\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\'.md5(uniqid(rand(),true)).\'\",\r\n                        \"\'.date(\'Y-m-d\').\'\"\r\n                    )\';\r\n\r\n                }\r\n                # ENDS: IS VALID ARTICLE\r\n            }\r\n            # ENDS: FOREACH\r\n        }\r\n        # ENDS: ARTICLES\r\n\r\n        if (!empty($category_sql)) {\r\n\r\n            $category_status = safe_query(\'\r\n                INSERT IGNORE INTO\r\n                    \'.safe_pfx(\'txp_category\').\'\r\n                (\r\n                    `name`,\r\n                    `type`,\r\n                    `parent`,\r\n                    `title`\r\n                )\r\n                VALUES\r\n                    \'.implode(\',\',$category_sql)\r\n            );\r\n\r\nrebuild_tree_full(\'article\');\r\n        }\r\n        else {\r\n            $category_status = false;\r\n        }\r\n\r\n        if (!empty($article_sql)) {\r\n\r\n            $article_status = safe_query(\'\r\n                INSERT IGNORE INTO\r\n                    \'.safe_pfx(\'textpattern\').\'\r\n                (\r\n                    `Posted`,\r\n                    `Expires`,\r\n                    `AuthorID`,\r\n                    `LastMod`,\r\n                    `LastModID`,\r\n                    `Title`,\r\n                    `Title_html`,\r\n                    `Body`,\r\n                    `Body_html`,\r\n                    `Excerpt`,\r\n                    `Excerpt_html`,\r\n                    `Image`,\r\n                    `Category1`,\r\n                    `Category2`,\r\n                    `Annotate`,\r\n                    `AnnotateInvite`,\r\n                    `comments_count`,\r\n                    `Status`,\r\n                    `textile_body`,\r\n                    `textile_excerpt`,\r\n                    `Section`,\r\n                    `override_form`,\r\n                    `Keywords`,\r\n                    `description`,\r\n                    `url_title`,\r\n                    `custom_1`,\r\n                    `custom_2`,\r\n                    `custom_3`,\r\n                    `custom_4`,\r\n                    `custom_5`,\r\n                    `custom_6`,\r\n                    `custom_7`,\r\n                    `custom_8`,\r\n                    `custom_9`,\r\n                    `custom_10`,\r\n                    `uid`,\r\n                    `feed_time`\r\n                )\r\n            VALUES\r\n                \'.implode(\',\',$article_sql)\r\n            );\r\n        }\r\n        else {\r\n            $article_status = false;\r\n        }\r\n\r\n        return array($category_status,$article_status);\r\n    }\r\n    else {\r\n        return false;\r\n    }\r\n}\r\n\r\n\r\nfunction kuo_wp_import_tab() {\r\n\r\npagetop(\'WP Import\');\r\n\r\nif ((isset($_FILES)) && (!empty($_FILES))) {\r\n\r\n    $kuo_import_html = NULL;\r\n\r\n    if (kuo_is_valid($_FILES) == true) {\r\n\r\n        $kuo_import_results = kuo_handle_xml($_FILES[\'kuo_file\'][\'tmp_name\']);\r\n\r\n        if (($kuo_import_results[0] == true) && ($kuo_import_results[1] == true)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Categories and articles were imported.</p>\';\r\n        }\r\n        elseif (($kuo_import_results[0] == true) && ($kuo_import_results[1] == false)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Categories were imported. Articles were not.</p>\';\r\n        }\r\n        elseif (($kuo_import_results[0] == false) && ($kuo_import_results[1] == true)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Articles were imported. Categories were not.</p>\';\r\n        }\r\n        else {\r\n            $kuo_import_html = \'<p class=\"alert-block error\">\'.gTxt(\'general_error\').\':\r\n            no categories or articles were imported.</p>\';\r\n        }\r\n\r\n        unset($kuo_import_results);\r\n\r\n    }\r\n    else {\r\n        $kuo_import_html = \'<p class=\"alert-block error\">\'.gTxt(\'general_error\').\': the file is not valid.</p>\';\r\n    }\r\n\r\n    echo $kuo_import_html;\r\n\r\n    unset(\r\n        $kuo_import_html,\r\n        $_FILES\r\n    );\r\n}\r\n\r\necho kuo_upload_form();\r\n\r\n}\r\n\r\n}', 'if (@txpinterface === \'admin\') {\r\n\r\nadd_privs(\'kuo_wp_import\',1);\r\n\r\nregister_tab(\'extensions\',\'kuo_wp_import\',\'WP Import\');\r\n\r\nregister_callback(\'kuo_wp_import_tab\',\'kuo_wp_import\');\r\n\r\nfunction kuo_upload_form() {\r\n\r\n    return \'\r\n        <form method=\"post\" enctype=\"multipart/form-data\">\r\n            <fieldset>\r\n                <legend>Import content from WordPress to Textpattern</legend>\r\n                <p><strong>Choose a WordPress-generated <code>.xml</code> file that contains\r\n                for example categories, posts and pages. After choosing and uploading that file its\r\n                contents are <em>immediately</em> imported to this Textpattern.</strong></p>\r\n                <p><label for=\"kuo_file\">\'.gTxt(\'upload_file\').\':&nbsp;</label><input\r\n                type=\"file\"\r\n                id=\"kuo_file\"\r\n                name=\"kuo_file\"\r\n                size=\"55\"\r\n                accept=\".xml,text/xml\"\r\n                required=\"required\"\r\n                />&nbsp;<button type=\"submit\">\'.gTxt(\'upload\').\'</button></p>\r\n                <p>Max. file size: \'.kuo_max_file_upload_size().\' bytes.</p>\r\n            </fieldset>\r\n        </form>\r\n    \';\r\n}\r\n\r\nfunction kuo_max_file_upload_size() {\r\n\r\n    $max_file_size = safe_row(\'val\',\'txp_prefs\',\'`name` = \"file_max_upload_size\"\');\r\n    $max_file_size = $max_file_size[\'val\'];\r\n\r\n    settype($max_file_size,\'int\');\r\n\r\n    return $max_file_size;\r\n}\r\n\r\nfunction kuo_is_valid($data) {\r\n\r\n    if (kuo_max_file_upload_size() == 0) {\r\n        $max_file_size = 1048576;\r\n    }\r\n    else {\r\n        $max_file_size = kuo_max_file_upload_size();\r\n    }\r\n\r\n    if (\r\n        (is_array($data))\r\n        &&\r\n        (isset($data[\'kuo_file\'][\'type\'],$data[\'kuo_file\'][\'error\'],$data[\'kuo_file\'][\'size\']))\r\n        &&\r\n        ($data[\'kuo_file\'][\'type\'] === \'text/xml\')\r\n        &&\r\n        ($data[\'kuo_file\'][\'error\'] == 0)\r\n        &&\r\n        ($data[\'kuo_file\'][\'size\'] > 0)\r\n        &&\r\n        ($data[\'kuo_file\'][\'size\'] <= $max_file_size)\r\n    ) {\r\n        return true;\r\n    }\r\n    else {\r\n        return false;\r\n    }\r\n}\r\n\r\nfunction kuo_article_status($status) {\r\n\r\n    if ((isset($status)) && (!empty($status))) {\r\n\r\n        $status = strtolower($status);\r\n\r\n        if ($status === \'publish\') {\r\n            return 4;\r\n        }\r\n        elseif ($status === \'pending\') {\r\n            return 3;\r\n        }\r\n        else {\r\n            return 1;\r\n        }\r\n    }\r\n    else {\r\n        return 1;\r\n    }\r\n}\r\n\r\nfunction kuo_categories_to_keywords($categories) {\r\n    if ((isset($categories)) && (is_array($categories)) && (!empty($categories))) {\r\n\r\n        $keywords = NULL;\r\n\r\n        foreach ($categories as $categories) {\r\n            $keywords .= doslash($categories).\',\';\r\n        }\r\n\r\n        $keywords = rtrim($keywords,\',\');\r\n\r\n        return $keywords;\r\n\r\n    }\r\n    else {\r\n        return $categories;\r\n    }\r\n}\r\n\r\nfunction kuo_comment_status($status) {\r\n    if (isset($status)) {\r\n\r\n        $status = strtolower($status);\r\n\r\n        if ($status === \'open\') {\r\n            return 1;\r\n        }\r\n        else {\r\n            return 0;\r\n        }\r\n\r\n    }\r\n    else {\r\n        return 0;\r\n    }\r\n}\r\n\r\nfunction kuo_handle_xml($data) {\r\n\r\n    $data = file_get_contents($data);\r\n\r\n    # THIS ISN\'T A BEAUTY CONTEST:\r\n    $data = str_replace(\r\n        array(\r\n            \'<wp:\',\r\n            \'</wp:\',\r\n            \'<content:encoded>\',\r\n            \'</content:encoded>\',\r\n            \'<excerpt:encoded>\',\r\n            \'</excerpt:encoded>\',\r\n            \'<dc:creator>\',\r\n            \'</dc:creator>\'\r\n        ),\r\n        array(\r\n            \'<wp\',\r\n            \'</wp\',\r\n            \'<content>\',\r\n            \'</content>\',\r\n            \'<excerpt>\',\r\n            \'</excerpt>\',\r\n            \'<creator>\',\r\n            \'</creator>\'\r\n        ),\r\n        $data\r\n    );\r\n\r\n    $data = simplexml_load_string($data,\'SimpleXMLElement\',LIBXML_NOCDATA);\r\n\r\n    # OBJECTS TO ARRAYS\r\n    $data = json_encode($data);\r\n    $data = json_decode($data,true);\r\n\r\n    if (isset($data[\'channel\'])) {\r\n\r\n        $data = $data[\'channel\'];\r\n\r\n        # CONTAINERS\r\n        $category_sql = array();\r\n        $article_sql = array();\r\n\r\n        # BEGINS: CATEGORIES\r\n        if ((isset($data[\'wpcategory\'])) && (!empty($data[\'wpcategory\']))) {\r\n\r\n            # BEGINS: FOREACH\r\n            foreach ($data[\'wpcategory\'] as $data[\'wpcategory\']) {\r\n\r\n                # BEGINS: IS VALID CATEGORY\r\n                if (isset($data[\'wpcategory\'][\'wpcategory_nicename\'],$data[\'wpcategory\'][\'wpcat_name\'])) {\r\n\r\n                    $data[\'wpcategory\'][\'wpcategory_nicename\'] =\r\n                        substr($data[\'wpcategory\'][\'wpcategory_nicename\'],0,64);\r\n\r\n                    $category_sql[] =\r\n                    \'(\r\n                        \"\'.doslash($data[\'wpcategory\'][\'wpcategory_nicename\']).\'\",\r\n                        \"article\",\r\n                        \"root\",\r\n                        \"\'.doslash($data[\'wpcategory\'][\'wpcat_name\']).\'\"\r\n                    )\';\r\n                }\r\n                # ENDS: IS VALID CATEGORY\r\n            }\r\n            # ENDS: FOREACH\r\n        }\r\n        # ENDS: CATEGORIES\r\n\r\n        # BEGINS: ARTICLES\r\n        if ((isset($data[\'item\'])) && (!empty($data[\'item\']))) {\r\n\r\n            # BEGINS: FOREACH\r\n            foreach ($data[\'item\'] as $data[\'item\']) {\r\n\r\n                # BEGINS: IS VALID ARTICLE\r\n                if (\r\n                    (isset(\r\n                        $data[\'item\'][\'description\'],\r\n                        $data[\'item\'][\'title\'],\r\n                        $data[\'item\'][\'wppost_name\'],\r\n                        $data[\'item\'][\'wppost_date\'],\r\n                        $data[\'item\'][\'wpcomment_status\'],\r\n                        $data[\'item\'][\'wpstatus\'],\r\n                        $data[\'item\'][\'content\'],\r\n                        $data[\'item\'][\'excerpt\'],\r\n                        $data[\'item\'][\'creator\'],\r\n                        $data[\'item\'][\'wppost_type\'])\r\n                    )\r\n                    &&\r\n                    (!empty($data[\'item\'][\'title\']))\r\n                    &&\r\n                    (!empty($data[\'item\'][\'wppost_name\']))\r\n                    &&\r\n                    (in_array($data[\'item\'][\'wppost_type\'],array(\'post\',\'page\')))\r\n                ) {\r\n\r\n                    if (!is_array($data[\'item\'][\'content\'])) {\r\n                        $content = doslash($data[\'item\'][\'content\']);\r\n                    }\r\n                    else {\r\n                        $content = \'\';\r\n                    }\r\n\r\n                    if (!is_array($data[\'item\'][\'excerpt\'])) {\r\n                        $excerpt = doslash($data[\'item\'][\'excerpt\']);\r\n                    }\r\n                    else {\r\n                        $excerpt = \'\';\r\n                    }\r\n\r\n                    # BEGINS: ARTICLE-ONLY CATEGORIES\r\n                    if (isset($data[\'item\'][\'category\'])) {\r\n                        $categories = kuo_categories_to_keywords($data[\'item\'][\'category\']);\r\n                    }\r\n                    else {\r\n                        $categories = \'\';\r\n                    }\r\n                    # ENDS: ARTICLE-ONLY CATEGORIES\r\n\r\n                    $data[\'item\'][\'creator\'] = substr($data[\'item\'][\'creator\'],0,64);\r\n\r\n                    $article_sql[] =\r\n                    \'(\r\n                        \"\'.date(\'Y-m-d H:i:s\',strtotime($data[\'item\'][\'wppost_date\'])).\'\",\r\n                        NULL,\r\n                        \"\",\r\n                        \"\'.date(\'Y-m-d H:i:s\',strtotime($data[\'item\'][\'wppost_date\'])).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'creator\']).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'title\']).\'\",\r\n                        \"\'.doslash($data[\'item\'][\'title\']).\'\",\r\n                        \"\'.$content.\'\",\r\n                        \"\'.$content.\'\",\r\n                        \"\'.$excerpt.\'\",\r\n                        \"\'.$excerpt.\'\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \'.kuo_comment_status($data[\'item\'][\'wpcomment_status\']).\',\r\n                        \"\",\r\n                        0,\r\n                        \'.kuo_article_status($data[\'item\'][\'wpstatus\']).\',\r\n                        1,\r\n                        1,\r\n                        \"articles\",\r\n                        \"\",\r\n                        \"\'.$categories.\'\",\r\n                        \"\",\r\n                        \"\'.doslash($data[\'item\'][\'wppost_name\']).\'\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\",\r\n                        \"\'.md5(uniqid(rand(),true)).\'\",\r\n                        \"\'.date(\'Y-m-d\').\'\"\r\n                    )\';\r\n\r\n                }\r\n                # ENDS: IS VALID ARTICLE\r\n            }\r\n            # ENDS: FOREACH\r\n        }\r\n        # ENDS: ARTICLES\r\n\r\n        if (!empty($category_sql)) {\r\n\r\n            $category_status = safe_query(\'\r\n                INSERT IGNORE INTO\r\n                    \'.safe_pfx(\'txp_category\').\'\r\n                (\r\n                    `name`,\r\n                    `type`,\r\n                    `parent`,\r\n                    `title`\r\n                )\r\n                VALUES\r\n                    \'.implode(\',\',$category_sql)\r\n            );\r\n\r\nrebuild_tree_full(\'article\');\r\n        }\r\n        else {\r\n            $category_status = false;\r\n        }\r\n\r\n        if (!empty($article_sql)) {\r\n\r\n            $article_status = safe_query(\'\r\n                INSERT IGNORE INTO\r\n                    \'.safe_pfx(\'textpattern\').\'\r\n                (\r\n                    `Posted`,\r\n                    `Expires`,\r\n                    `AuthorID`,\r\n                    `LastMod`,\r\n                    `LastModID`,\r\n                    `Title`,\r\n                    `Title_html`,\r\n                    `Body`,\r\n                    `Body_html`,\r\n                    `Excerpt`,\r\n                    `Excerpt_html`,\r\n                    `Image`,\r\n                    `Category1`,\r\n                    `Category2`,\r\n                    `Annotate`,\r\n                    `AnnotateInvite`,\r\n                    `comments_count`,\r\n                    `Status`,\r\n                    `textile_body`,\r\n                    `textile_excerpt`,\r\n                    `Section`,\r\n                    `override_form`,\r\n                    `Keywords`,\r\n                    `description`,\r\n                    `url_title`,\r\n                    `custom_1`,\r\n                    `custom_2`,\r\n                    `custom_3`,\r\n                    `custom_4`,\r\n                    `custom_5`,\r\n                    `custom_6`,\r\n                    `custom_7`,\r\n                    `custom_8`,\r\n                    `custom_9`,\r\n                    `custom_10`,\r\n                    `uid`,\r\n                    `feed_time`\r\n                )\r\n            VALUES\r\n                \'.implode(\',\',$article_sql)\r\n            );\r\n        }\r\n        else {\r\n            $article_status = false;\r\n        }\r\n\r\n        return array($category_status,$article_status);\r\n    }\r\n    else {\r\n        return false;\r\n    }\r\n}\r\n\r\n\r\nfunction kuo_wp_import_tab() {\r\n\r\npagetop(\'WP Import\');\r\n\r\nif ((isset($_FILES)) && (!empty($_FILES))) {\r\n\r\n    $kuo_import_html = NULL;\r\n\r\n    if (kuo_is_valid($_FILES) == true) {\r\n\r\n        $kuo_import_results = kuo_handle_xml($_FILES[\'kuo_file\'][\'tmp_name\']);\r\n\r\n        if (($kuo_import_results[0] == true) && ($kuo_import_results[1] == true)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Categories and articles were imported.</p>\';\r\n        }\r\n        elseif (($kuo_import_results[0] == true) && ($kuo_import_results[1] == false)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Categories were imported. Articles were not.</p>\';\r\n        }\r\n        elseif (($kuo_import_results[0] == false) && ($kuo_import_results[1] == true)) {\r\n            $kuo_import_html = \'<p class=\"alert-block success\">Articles were imported. Categories were not.</p>\';\r\n        }\r\n        else {\r\n            $kuo_import_html = \'<p class=\"alert-block error\">\'.gTxt(\'general_error\').\':\r\n            no categories or articles were imported.</p>\';\r\n        }\r\n\r\n        unset($kuo_import_results);\r\n\r\n    }\r\n    else {\r\n        $kuo_import_html = \'<p class=\"alert-block error\">\'.gTxt(\'general_error\').\': the file is not valid.</p>\';\r\n    }\r\n\r\n    echo $kuo_import_html;\r\n\r\n    unset(\r\n        $kuo_import_html,\r\n        $_FILES\r\n    );\r\n}\r\n\r\necho kuo_upload_form();\r\n\r\n}\r\n\r\n}', '0e10d1c8b04945ef11978536b5ca3d53', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_analytics', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'View your Google Analytics statistics.', '<ul id=\"links\">\n<li id=\"kuo_analytics\"><a href=\"#kuo_analytics\">kuo_analytics</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>Fetches Google Analytics statistics from a specific Web property.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_analytics</code>.</li>\n<li>Upload the folder <code>kuo_analytics</code>, which is in your website\'s root.</li>\n<li>Go to Google Developers Console: create a new project.</li>\n<li>In Google Developers Console: activate Analytics API for the project just created.</li>\n<li>In Google Developers Console: go to Credentials and click &quot;Create New Client ID&quot;<sup>*</sup>.</li>\n<li>In Google Developers Console: click &quot;Download JSON&quot;, then save it to your computer and rename it<sup>**</sup></li>\n<li>Upload the renamed JSON file inside folder <code>kuo_analytics</code>, which is in your website\'s root.</li>\n<li>Finally go to Textpattern\'s Extensions tab and click &quot;Analytics&quot;.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_analytics</code>.</li>\n<li><span>[optional]</span> Delete the folder <code>kuo_analytics</code> from your website\'s root.</li>\n<li><span>[optional]</span> Delete the project for this plugin from Google Developers Console.</li>\n</ol>\n\n<hr />\n\n<p>*) <strong>Detailed instructions how to &quot;Create New Client ID&quot;:</strong> In the opening form select &quot;Web application&quot; as Application type. Then leave empty the field below title &quot;Authorized JavaScript origins&quot;. Then under title &quot;Authorized redirect URIs&quot; type something like this: <code>http://replacethiswithyourwebsite.com/kuo_analytics/kuo_analytics_overview.php</code>, then click &quot;Create Client ID&quot;. (<a href=\"http://kuopassa.net/images/478.png\" rel=\"nofollow external\" target=\"_new\">Screenshot</a>)</p>\n\n<p>**) <strong>Detailed instructions how to rename JSON file, and to what:</strong> You can find the unique file name for your JSON file under this plugin\'s &quot;How to install?&quot; section, which is under Extensions tab. The JSON file name should be something like this: <code>5wfs4funss455.json</code>. So only the file name is unique, but the extension remains <code>.json</code>.</p>\n\n<hr />\n\n<p>If you have difficulties in getting this plugin to work, or you want to notify of a bug, or suggest a new feature, please share your thoughts at the <a href=\"http://forum.textpattern.com/viewtopic.php?id=46156\" rel=\"nofollow external\" targ=\"_new\">Textpattern Support Forum</a>.</p>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface === \'admin\') {\n\nregister_callback(\'kuo_uninstall_analytics\',\'plugin_lifecycle.kuo_analytics\',\'disabled\');\nregister_callback(\'kuo_install_analytics\',\'plugin_lifecycle.kuo_analytics\',\'enabled\');\nregister_callback(\'kuo_deleted_analytics\',\'plugin_lifecycle.kuo_analytics\',\'deleted\');\n\nregister_tab(\'extensions\',\'kuo_analytics\',\'Analytics\');\nregister_callback(\'kuo_analytics_tab\',\'kuo_analytics\');\nadd_privs(\'kuo_analytics\',\'1,2,3,4,5,6\');\n\n# UNINSTALLING PLUGIN: REMOVING DATABASE TABLE\nfunction kuo_uninstall_analytics() {\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_analytics\').\"\");\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_analytics_reports\').\"\");\n}\n\n# DELETING PLUGIN: LAUNCHING UNINSTALL FUNCTION\nfunction kuo_deleted_analytics() {\nkuo_uninstall_analytics();\n}\n\n# INSTALLING PLUGIN: CREATING DATABASE TABLE\nfunction kuo_install_analytics() {\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_analytics\').\" (`id` INT NOT NULL AUTO_INCREMENT, `view_id` INT DEFAULT NULL, `modified` DATETIME NOT NULL DEFAULT 0, `language` VARCHAR(2) NOT NULL DEFAULT \'en\', `auth_file` VARCHAR(30) DEFAULT NULL, `detailed_toggle` INT(1) NOT NULL DEFAULT \'1\', `datetime_format` INT(1) DEFAULT NULL, `show_detailed` TEXT DEFAULT NULL, `show_saved` TEXT DEFAULT NULL, PRIMARY KEY(`id`))\");\n\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_analytics_reports\').\" (`id` INT NOT NULL AUTO_INCREMENT, `view_id` INT DEFAULT NULL, `created` DATETIME NOT NULL DEFAULT 0, `modified` DATETIME NOT NULL DEFAULT 0, `property_name` VARCHAR(255) DEFAULT NULL, `report` BLOB DEFAULT NULL, `start_date` DATETIME NOT NULL DEFAULT 0, `end_date` DATETIME NOT NULL DEFAULT 0, `type` INT(1) NOT NULL DEFAULT \'1\', `starred` INT(1) NOT NULL DEFAULT \'0\', `comment` TEXT DEFAULT NULL, PRIMARY KEY(`id`))\");\n\n# CREATING A NICE LITTLE NAME FOR JSON AUTH FILE\n$analytics_file_name_candidate = substr(strrev(md5(uniqid($GLOBALS[\'prefs\'][\'siteurl\'].date(\'H:i:s\'),true))),rand(1,3),30);\n$analytics_basic_rights = serialize(array(\'publishers\'));\n\nsafe_insert(\'kuo_analytics\',\"`view_id` = NULL, `modified` = \'\".date(\'Y-m-d H:i:s\').\"\', `language` = \'en\', `auth_file` = \'\".$analytics_file_name_candidate.\"\', `show_detailed` = \'\".$analytics_basic_rights.\"\', `show_saved` = \'\".$analytics_basic_rights.\"\', `detailed_toggle` = \'1\', `datetime_format` = \'1\'\");\n\nunset($analytics_basic_rights);\nunset($analytics_file_name_candidate);\n\n}\n\n# REDIRECTING TO THE RIGHT PLACE\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_analytics\') {\nheader(\'location: ./index.php?event=kuo_analytics\');\ndie;\n}\n\n# ACTUAL PLUGIN THING\nfunction kuo_analytics_tab() {\n\n/*\nglobal $event;\nif ($event !== \'kuo_analytics\') {\nreturn;\n}\n*/\n\n# BEGINNING: CHECKING WHAT PRIVILEGES CURRENT USER HAS AND WHAT PRIVILEGES ARE ALLOWED\n$analytics_user_groups = safe_row(\'show_detailed,show_saved\',\'kuo_analytics\',\'id=1\');\n\n$analytics_current_user = is_logged_in();\n$analytics_current_user = $analytics_current_user[\'privs\'];\n\nif ($analytics_current_user == 1) {\n$analytics_current_user = \'publishers\';\n}\nelseif ($analytics_current_user == 2) {\n$analytics_current_user = \'managing-editors\';\n}\nelseif ($analytics_current_user == 3) {\n$analytics_current_user = \'copy-editors\';\n}\nelseif ($analytics_current_user == 4) {\n$analytics_current_user = \'staff-writers\';\n}\nelseif ($analytics_current_user == 5) {\n$analytics_current_user = \'freelancers\';\n}\nelseif ($analytics_current_user == 6) {\n$analytics_current_user = \'designers\';\n}\nelse {\n$analytics_current_user = NULL;\n}\n\nif ((!empty($analytics_user_groups[\'show_detailed\']))) {\n$analytics_user_groups_show_detailed = unserialize($analytics_user_groups[\'show_detailed\']);\n\n# IF MATCH IS FOUND: USER IS ALLOWED ACCESS\nif ((is_array($analytics_user_groups_show_detailed)) && (!empty($analytics_user_groups_show_detailed)) && (in_array($analytics_current_user,$analytics_user_groups_show_detailed,true))) {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',1);\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',0);\n}\n\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',0);\n}\n\n# DETAILED REPORT\'S PRIVILEGES USER GROUP CHECKER IS DONE. NEXT IN LINE: SAVED REPORT CHECKER\n\nif ((!empty($analytics_user_groups[\'show_saved\']))) {\n$analytics_user_groups_show_saved = unserialize($analytics_user_groups[\'show_saved\']);\n\n# IF MATCH IS FOUND: USER IS ALLOWED ACCESS SAVED REPORTS\nif ((is_array($analytics_user_groups_show_saved)) && (!empty($analytics_user_groups_show_saved)) && (in_array($analytics_current_user,$analytics_user_groups_show_saved,true))) {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',1);\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',0);\n}\n\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',0);\n}\n\n\n# UNSETTING SOME THINGS TO PREVENT ODDITIES LATER\nunset($analytics_current_user);\nunset($analytics_user_groups);\nunset($analytics_user_groups_show_detailed);\nunset($analytics_user_groups_show_saved);\nunset($analytics_user_groups_show_saved);\n\n# ENDING: CHECKING WHAT PRIVILEGES CURRENT USER HAS AND WHAT PRIVILEGES ARE ALLOWED\n\ndefine(\'KUO_ANALYTICS_ACTIVE_ID\',\' id=\"analytics_active_tab\"\');\ndefine(\'KUO_ANALYTICS_SELECTED_HTML\',\' selected=\"selected\"\');\ndefine(\'KUO_ANALYTICS_CHECKED_HTML\',\' checked=\"checked\"\');\ndefine(\'KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION\',\'<div class=\"analytics_box analytics_only_admin\"><p>This section is visible only to Publishers.</p></div>\');\n$analytics_message = NULL;\n\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\npagetop(\'Google Analytics for Textpattern\');\n\n# BEGINNING: IF SAVING SETTINGS\nif ((isset($_POST[\'submit\'])) && (empty($_POST[\'submit\'])) && (isset($_POST[\'analytics_language\'])) && (!empty($_POST[\'analytics_language\'])) && (strlen($_POST[\'analytics_language\']) == 2) && (isset($_POST[\'analytics_view_id\'])) && (isset($_POST[\'analytics_datetime\']))) {\n\n# SHOULD BE NUMERIC, BUT IN CASE SOMETHING CHANGES IN THE FUTURE\nif (is_numeric($_POST[\'analytics_view_id\'])) {\n$analytics_view_id = filter_var($_POST[\'analytics_view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_view_id = filter_var($_POST[\'analytics_view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif ((empty($analytics_view_id)) or (strlen($analytics_view_id) > 11)) {\n$analytics_view_id = NULL;\n}\n\n$analytics_language = strtolower(filter_var($_POST[\'analytics_language\'],FILTER_SANITIZE_STRING));\n\nif (!in_array($analytics_language,array(\'en\',\'fi\'))) {\n$analytics_language = \'en\';\n}\n\n$analytics_datetime = filter_var($_POST[\'analytics_datetime\'],FILTER_SANITIZE_NUMBER_INT);\n\nif ((empty($analytics_datetime)) or (!intval($analytics_datetime))) {\n$analytics_datetime = NULL;\n}\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW DETAILED REPORTS\nif ((isset($_POST[\'analytics_show_detailed\'])) && (is_array($_POST[\'analytics_show_detailed\']))) {\n\n# FOREACH START\nforeach ($_POST[\'analytics_show_detailed\'] as $analytics_show_detailed_groups) {\n\nif ($analytics_show_detailed_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',1);\n}\n\n}\n# FOREACH END\n\n$analytics_show_detailed = $_POST[\'analytics_show_detailed\'];\narray_push($analytics_show_detailed,\'publishers\');\n$analytics_show_detailed = serialize($analytics_show_detailed);\n}\nelse {\n$analytics_show_detailed = serialize(array(\'publishers\'));\n}\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW DETAILED REPORTS\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW SAVED REPORTS\nif ((isset($_POST[\'analytics_show_saved\'])) && (is_array($_POST[\'analytics_show_saved\']))) {\n\n# FOREACH START\nforeach ($_POST[\'analytics_show_saved\'] as $analytics_show_detailed_saved) {\n\nif ($analytics_show_detailed_saved === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'designers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',1);\n}\n\n}\n# FOREACH END\n$analytics_show_saved = $_POST[\'analytics_show_saved\'];\narray_push($analytics_show_saved,\'publishers\');\n$analytics_show_saved = serialize($analytics_show_saved);\n}\nelse {\n$analytics_show_saved = serialize(array(\'publishers\'));\n}\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW SAVED REPORTS\n\n# ACTUALLY SAVING DATA\n# CAN BE IN THE FUTURE ADJUSTED TO INCLUDE SEVERAL VIEW ID\'S,\n# BUT THAT FEATURE IS COMING LATER THAN SOONER\nif (safe_count(\'kuo_analytics\',\"id=1\") == 0) {\nsafe_insert(\'kuo_analytics\',\"`view_id` = \'\".$analytics_view_id.\"\', `modified` = \'\".date(\'Y-m-d H:i:s\').\"\', `language` = \'\".$analytics_language.\"\', `datetime_format` = \'\".$analytics_datetime.\"\', `show_detailed` = \'\".$analytics_show_detailed.\"\', `show_saved` = \'\".$analytics_show_saved.\"\'\");\n$analytics_message = \'<p id=\"analytics_message\">Great! View ID was saved. <a href=\"./index.php?event=kuo_analytics\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\nelse {\nsafe_update(\'kuo_analytics\',\"view_id = \'\".$analytics_view_id.\"\', modified = \'\".date(\'Y-m-d H:i:s\').\"\', language = \'\".$analytics_language.\"\', datetime_format= \'\".$analytics_datetime.\"\', `show_detailed` = \'\".$analytics_show_detailed.\"\', `show_saved` = \'\".$analytics_show_saved.\"\'\",\"id =1\");\n$analytics_message = \'<p id=\"analytics_message\">Great! Changes were saved. <a href=\"./index.php?event=kuo_analytics\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\n\n}\n# ENDING: IF SAVING SETTINGS\n# BEGINNING: SETTINGS FROM DATABASE\nelse {\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\nif (!empty($current_analytics_settings)) {\n\n$analytics_view_id = filter_var($current_analytics_settings[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (is_numeric($analytics_view_id)) {\n$analytics_view_id = filter_var($analytics_view_id,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_view_id = filter_var($analytics_view_id,FILTER_SANITIZE_STRING);\n}\n\n$analytics_language = filter_var($current_analytics_settings[\'language\'],FILTER_SANITIZE_STRING);\n\nif (empty($analytics_view_id)) {\n$analytics_view_id = NULL;\n}\n\nif (empty($analytics_language)) {\n$analytics_language = NULL;\n}\n\n$analytics_datetime = filter_var($current_analytics_settings[\'datetime_format\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (empty($analytics_datetime)) {\n$analytics_datetime = 1;\n}\n\n# BEGINNING: SHOW DETAILED REPORTS VIEW STATUS FROM DB\n$analytics_show_detailed = unserialize($current_analytics_settings[\'show_detailed\']);\n\nif (!empty($analytics_show_detailed)) {\n\nforeach ($analytics_show_detailed as $analytics_show_detailed_groups) {\n\nif ($analytics_show_detailed_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',1);\n}\n\n}\n}\n# ENDING: SHOW DETAILED REPORTS VIEW STATUS FROM DB\n\n# BEGINNING: SHOW ***SAVED*** REPORTS VIEW STATUS FROM DB\n$analytics_show_saved = unserialize($current_analytics_settings[\'show_saved\']);\nif (!empty($analytics_show_saved)) {\nforeach ($analytics_show_saved as $analytics_show_saved_groups) {\n\nif ($analytics_show_saved_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_saved_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_saved_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_saved_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_saved_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',1);\n}\n\n}\n}\n# ENDING: SHOW ***SAVED*** REPORTS VIEW STATUS FROM DB\n\n}\n# IF NO DATA FROM DATABASE\nelse {\n$analytics_view_id = NULL;\n$analytics_language = \'en\';\n}\n\n}\n\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\')) {\npagetop(\'Install | Google Analytics for Textpattern\');\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\npagetop(\'Status | Google Analytics for Textpattern\');\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\npagetop(\'Statistics | Google Analytics for Textpattern\');\n\n# BEGINNING: IF USER WANTS TO SAVE COMMENT TO SAVED REPORT\nif ((isset($_GET[\'saved-reports\'])) && (empty($_GET[\'saved-reports\'])) && (isset($_GET[\'view\'])) && (!empty($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n\nif ((isset($_POST[\'analytics_comment_source\'])) && (!empty($_POST[\'analytics_comment_source\'])) && (is_numeric($_POST[\'analytics_comment_source\'])) && (isset($_POST[\'analytics_comments\']))) {\n\n$analytics_posted_comment = $_POST[\'analytics_comments\'];\n$analytics_posted_comment_id = filter_var($_POST[\'analytics_comment_source\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (is_numeric($analytics_posted_comment)) {\n$analytics_posted_comment = filter_var($analytics_posted_comment,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_posted_comment = filter_var($analytics_posted_comment,FILTER_SANITIZE_STRING);\n}\n\n# SAVING\nif ($analytics_posted_comment_id == $_GET[\'view\']) {\nsafe_update(\'kuo_analytics_reports\',\"`comment` = \'\".$analytics_posted_comment.\"\', `modified` = \'\".date(\'Y-m-d H:i:s\').\"\'\",\"id=\'$analytics_posted_comment_id\'\",1);\n}\n\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&view=\'.$analytics_posted_comment_id.\'&status=\'.base64_encode(json_encode(array(\'success-comment-saved\',$analytics_posted_comment_id))).\'#analytics_message\');\nunset($analytics_posted_comment_id);\nunset($analytics_posted_comment);\ndie;\n\n}\n\n\n\n}\n# ENDING: IF USER WANTS TO SAVE COMMENT TO SAVED REPORT\n\n# BEGINNING: IF USER WANTS TO DELETE A REPORT\nif ((isset($_GET[\'delete\'])) && (!empty($_GET[\'delete\'])) && (is_numeric($_GET[\'delete\'])) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\n$analytics_delete_report_id = filter_var($_GET[\'delete\'],FILTER_SANITIZE_NUMBER_INT);\n# IF REPORT TO BE DELETED EXISTS\nif ((!empty($analytics_delete_report_id)) && (safe_count(\'kuo_analytics_reports\',\"id=$analytics_delete_report_id\") == 1)) {\n$analytics_message = \'<p id=\"analytics_message\">Do you really want to delete this report? <strong><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;delete=\'.$analytics_delete_report_id.\'&amp;confirm=\'.base64_encode(json_encode(array(\'confirm\',$analytics_delete_report_id))).\'\" rel=\"nofollow\">Yes</a> or <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"nofollow\">no</a></strong>?</p>\';\n\n# ACTUALLY DELETING THE REPORT IF CONFIRM MESSAGE OK\nif (isset($_GET[\'confirm\'])) {\n$analytics_report_delete_confirm_message = json_decode(base64_decode($_GET[\'confirm\']));\n\nif ((!empty($analytics_report_delete_confirm_message)) && (filter_var($analytics_report_delete_confirm_message[0],FILTER_SANITIZE_STRING) === \'confirm\') && (is_numeric(filter_var($analytics_report_delete_confirm_message[1],FILTER_SANITIZE_NUMBER_INT)))) {\n$report_delete_id = filter_var($analytics_report_delete_confirm_message[1],FILTER_SANITIZE_NUMBER_INT);\n\nif (safe_delete(\'kuo_analytics_reports\',\"id=\'$report_delete_id\'\")) {\n$analytics_status_message = \'success-deleted\';\n}\nelse {\n$analytics_status_message = \'fail-deleted\';\n}\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&status=\'.base64_encode(json_encode(array($analytics_status_message,$report_delete_id))));\ndie;\n\n}\n\n}\n\n}\n# IF THIS ARTICLE ID DOES NOT EXIST\nelse {\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&status=\'.base64_encode(json_encode(array(\'fail-missing\',$analytics_delete_report_id))));\nunset($analytics_delete_report_id);\ndie;\n}\n\n}\n# ENDING: IF USER WANTS TO DELETE A REPORT\n\n# CHECKING IF A REPORT HAS BEEN SAVED / FAILED TO SAVE TO DB\nif ((isset($_GET[\'status\'])) && (!empty($_GET[\'status\'])) && (base64_decode($_GET[\'status\'],true))) {\n\nif ((!isset($_GET[\'overview\'])) or (empty($_GET[\'overview\']))) {\n$analytics_return_tab = NULL;\n}\nelseif ($_GET[\'overview\'] === \'30-days\') {\n$analytics_return_tab = \'&amp;overview=30-days\';\n}\nelseif ($_GET[\'overview\'] === \'14-days\') {\n$analytics_return_tab = \'&amp;overview=14-days\';\n}\nelseif ($_GET[\'overview\'] === \'7-days\') {\n$analytics_return_tab = \'&amp;overview=7-days\';\n}\nelseif ($_GET[\'overview\'] === \'yesterday\') {\n$analytics_return_tab = \'&amp;overview=yesterday\';\n}\nelseif ($_GET[\'overview\'] === \'today\') {\n$analytics_return_tab = \'&amp;overview=today\';\n}\nelse {\n$analytics_return_tab = NULL;\n}\n\n$analytics_report_status = json_decode(base64_decode($_GET[\'status\']));\n\nif (!empty($analytics_report_status)) {\n$analytics_report_status_message = filter_var($analytics_report_status[0],FILTER_SANITIZE_STRING);\n$analytics_report_status_id = filter_var($analytics_report_status[1],FILTER_SANITIZE_NUMBER_INT);\n}\n\nif (!isset($_GET[\'saved-reports\'])) {\n$analytics_report_status_return_tab = NULL;\n}\nelse {\n$analytics_report_status_return_tab = \'&amp;saved-reports\';\n}\n\nif ((isset($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n$analytics_report_status_return_tab_saved_report_id = \'&amp;view=\'.intval($_GET[\'view\']).\'#analytics_tab\';\n}\nelse {\n$analytics_report_status_return_tab_saved_report_id = NULL;\n}\n\nif (in_array($analytics_report_status_message,array(\'success-updated\',\'success-created\',\'success-deleted\',\'success-comment-saved\'))) {\n\nif (!empty($analytics_report_status_id)) {\n$analytics_report_status_id = \'#\'.$analytics_report_status_id;\n}\n\nif ($analytics_report_status_message === \'success-created\') {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>saved to</strong> database.\';\n}\nelseif ($analytics_report_status_message === \'success-deleted\') {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>deleted from</strong> database.\';\n}\nelseif ($analytics_report_status_message === \'success-comment-saved\') {\n$analytics_report_status_message_type = \'The comment for report \'.$analytics_report_status_id.\' <strong>was saved</strong>.\';\n}\nelse {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>updated to</strong> database.\';\n}\n\n$analytics_message = \'<p id=\"analytics_message\">Success! \'.$analytics_report_status_message_type.\' <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_return_tab.$analytics_report_status_return_tab.$analytics_report_status_return_tab_saved_report_id.\'\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\nelse {\n$analytics_message = \'<p id=\"analytics_message\">Error. \'.$analytics_report_status_id.\' was not processed. <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_report_status_return_tab.$analytics_report_status_return_tab_saved_report_id.\'\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\n\nunset($analytics_report_status);\nunset($analytics_report_status_id);\n\n}\n\n}\nelse {\npagetop(\'Error | Google Analytics for Textpattern\');\n}\n\necho \'<style>\n#kuo_analytics_container { color: #333; position: relative; }\n#kuo_analytics_container h2, #kuo_analytics_container h3, #kuo_analytics_container h4, #kuo_analytics_container h5, #kuo_analytics_container h6 { font-family: Arial, Helvetica, sans-serif; font-size: xx-large; color: #333; padding-bottom: 0; margin-bottom: 0; }\n#analytics_navigation, #analytics_area ul { list-style: none; padding: 1% 0; margin: 0; font-size: 1.2em; line-height: 240%; }\n#analytics_area ul { font-size: 1em; }\n#analytics_area ul:nth-child(2) { font-size: 0.8em; }\n#analytics_area #analytics_detailed_navigation { padding: 1%; margin-bottom: 2%; background: #fcfcfc; border: 1px solid #f0f0f0; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_area #analytics_detailed_navigation dt { font-size: 1.2em; }\n#analytics_area #analytics_detailed_navigation li { line-height: 400%; }\n#kuo_analytics_container #analytics_navigation li, #kuo_analytics_container #analytics_area li { padding-top: 0; display: inline-block; float: none; clear: none; text-align: left; vertical-align: top; }\n#analytics_navigation a, #analytics_area ul a { text-decoration: none; background: #71BBCD; color: #3E3A3A; padding: 10px 20px; margin-right: 10px; border-bottom: 2px solid #3EA3B2; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_area ul a { background: #E5E5E5; color: #666; border-bottom-color: #999; }\n#analytics_navigation li:last-child { border-left: 1px solid #EFEBEB; margin-left: 10px; padding-left: 18px; }\n#analytics_navigation li:last-child a { margin-right: 0; background: #F99329; color: #fff; border-bottom-color: #D36A00; }\n#analytics_navigation a:hover, #analytics_navigation a:focus, #analytics_navigation a:active, #analytics_active_tab a, #analytics_area ul a:hover, #analytics_area ul a:focus, #analytics_area ul a:active { text-decoration: none; background: #5EC7D6; color: #202020; }\n#analytics_area ul a:hover, #analytics_area ul a:focus, #analytics_area ul a:active, #analytics_area ul .active_tab a { background: #ccc; color: #555; }\n#analytics_navigation li:last-child a:hover, #analytics_navigation li:last-child a:active, #analytics_navigation li:last-child a:focus, #analytics_navigation li:last-child#analytics_active_tab a { background: #EF8315; color: #ffc; }\n#kuo_analytics_container, #kuo_analytics_container legend { font-size: 1.2em; }\n#kuo_analytics_container form ul { list-style: none; padding: 2%; }\n#kuo_analytics_container li { padding-top: 20px; display: block; clear: both; }\n#kuo_analytics_container li span { display: block; border: 1px solid #f0f0f0; border-left-width: 5px; padding: 1%; margin-top: 10px; color: #666; margin-bottom: 3%; }\n#kuo_analytics_container button { padding: 10px 20px; font-size: 1.2em; margin-left: 210px; }\n#kuo_analytics_container label { padding-right: 10px; width: 210px; }\n#kuo_analytics_container label, #kuo_analytics_container select, #kuo_analytics_container button, #kuo_analytics_container input[type=checkbox] { cursor: pointer; }\n#kuo_analytics_container form li:last-child { text-align: right; }\n#analytics_by, #analytics_donate { text-align: center; color: #666; text-align: right; font-size: 0.85em; }\n#analytics_by a, #analytics_donate a { color: #666; text-decoration: underline; }\n#analytics_by a:hover, #analytics_by a:focus, #analytics_by a:active, #analytics_donate a:hover, #analytics_donate a:focus, #analytics_donate a:active { color: #444; }\n#analytics_health { list-style: none; padding: 0 0 3% 0; margin: 0; }\n#analytics_health li { padding-top: 5px; }\n.analytics_icon { font-weight: normal; font-size: 1.2em; margin-right: 5px; }\n.analytics_good { color: green; }\n.analytics_bad { color: red; }\n#analytics_message, .analytics_box, #analytics_reports { border: 1px solid #fcc; border-left-width: 5px; background: #fcfcfc; padding: 1%; margin-bottom: 1%; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_message a { float: right; color: #FF9191; }\n#analytics_message strong a { float: none; color: #c00; }\n.analytics_box, #analytics_reports { border-color: #ccc; margin-top: 1%; }\n#kuo_analytics_container .analytics_box p { padding-top: 0; margin-top: 0; }\n#kuo_analytics_container .analytics_box ul, #kuo_analytics_container .analytics_box ol { padding: 0 0 0 5%; margin: 0; }\n.analytics_box dd { margin-top: 2%; }\n#kuo_analytics_container .analytics_box ol li { padding: 0; margin: 0; }\n.analytics_highlight strong, #analytics_installation a { background: #A0CE9A; font-weight: normal; padding: 5px 8px; color: #152812; -moz-border-radius: 3px; border-radius: 3px; }\n.analytics_highlight strong a, #analytics_installation a { color: #152812; text-decoration: underline; }\n#analytics_installation a { text-align: center; background: #D1DFE5; color: #225066; display: block; padding-top: 10px; padding-bottom: 10px; }\n#kuo_analytics_header dl { background: #EF8315; padding: 5px; margin: 1% 0; -moz-border-radius: 3px; border-radius: 3px; }\n#kuo_analytics_header dt span { text-transform: uppercase; color: #ff0; float: right; }\n#kuo_analytics_header dt, #kuo_analytics_header dd { display: inline; color: #fff; margin: 0; padding: 0; }\n#kuo_analytics_summary dl { border: 1px solid #f0f0f0; border-left-width: 5px; background: #fcfcfc; padding: 2%; display: inline-block; width: 20%; margin: 1% 2% 0 0; -moz-border-radius: 3px; border-radius: 3px; }\n#kuo_analytics_summary dt { font-weight: normal; }\n#kuo_analytics_summary dd { font: 1.6em \"Georgia\", Sans; }\n#analytics_status_message { text-align: center; padding: 3% 0; font-size: 2em; }\n#kuo_analytics_container #analytics_reports { width: 100%; padding: 0; margin: 1% 0; border-collapse: collapse; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_reports th, #analytics_reports td { padding: 5px; vertical-align: top; }\n#analytics_no_reports td { padding: 4% 0; text-align: center; }\n#kuo_analytics_container .analytics_counter { border-right: 1px solid #ccc; text-align: center; }\n#analytics_reports th { border-bottom: 1px solid #ccc; }\n#analytics_reports th.analytics_empty_cell { border-bottom: 0; }\n#kuo_analytics_container .analytics_detailed_open { color: #999; }\n#kuo_analytics_container .analytics_detailed_open dd { display: none; }\n#analytics_detailed_navigation dt a { float: right; }\n#analytics_date_interval { margin-top: 0; padding-top: 0; }\n#kuo_analytics_container .analytics_error { padding-left: 2%; }\n#kuo_analytics_container .analytics_error dd { padding: 1% 0 0 0; margin: 0; }\n#kuo_analytics_container form li ul { padding: 0; margin: 0; display: inline; text-align: left; }\n#kuo_analytics_container form li ul li:last-child { text-align: left; }\n#kuo_analytics_container form li ul li { text-align: left; display: block; padding: 0 0 0 25%; }\n#kuo_analytics_container form li ul label, #kuo_analytics_container form li ul input { float: none; width: auto; vertical-align: middle; text-align: left; padding: 0 0 0 10px; display: inline; margin: 0; }\n#kuo_analytics_container textarea { max-width: 800px; width: 60%; min-width: 250px; }\n#kuo_analytics_container #analytics_tab form li { display: inline-block; }\n#kuo_analytics_container #analytics_tab form li:first-child { width: 85%; }\n#kuo_analytics_container #analytics_tab form li button { margin: 0; }\n#kuo_analytics_container .analytics_only_admin { padding: 0; position: absolute; right: 5px; top: -5px; border: 0; background: none; }\n#kuo_analytics_container .analytics_only_admin p { padding: 5px 10px; margin: 0; background: #F1F3F3; -moz-border-radius: 3px; border-radius: 3px; border-bottom: 2px solid #CFD7D7; color: #555; }\n</style>\n<div id=\"kuo_analytics_container\">\n\'.$analytics_message.\'\n<h2>Google Analytics for Textpattern</h2><ul id=\"analytics_navigation\"><li\';\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics\" rel=\"chapter nofollow\">Settings</a></li><li\';\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=status\" rel=\"chapter nofollow\">Status</a></li><li\';\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\" rel=\"chapter nofollow\">Statistics</a></li></ul>\n<div id=\"kuo_analytics_middle\">\';\n\n# BEGINNING: CERTAIN SECTIONS ARE SHOWN ONLY TO PUBLISHERS\n$analytics_current_user = is_logged_in();\n\nif ($analytics_current_user[\'privs\'] != 1) {\n\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\')) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelse {\n$analytics_is_not_allowed_to_view = 1;\n}\n\nif (empty($analytics_is_not_allowed_to_view)) {\necho \'<div class=\"analytics_box analytics_error\"><dl><dt>Error</dt><dd>Only users who have Publisher privileges can view this section.</dd></dl></div>\';\n}\n\n}\nelse {\n$analytics_is_not_allowed_to_view = 1;\n}\n# ENDING: CERTAIN SECTION ARE SHOWN ONLY TO PUBLISHERS\n\n# BEGINNING: MAIN TAB WHERE VIEW ID IS SET, ETC.\nif ((!isset($_GET[\'analytics_tab\'])) && (!empty($analytics_is_not_allowed_to_view))) {\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',0);\n}\n\n# OTHER SET:\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',0);\n}\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<form action=\"./index.php?event=kuo_analytics\" method=\"post\">\n<fieldset>\n<legend>Settings</legend>\n<ul>\n<li><label for=\"analytics_view_id\">Analytics View ID:</label><input type=\"text\" name=\"analytics_view_id\" id=\"analytics_view_id\" size=\"15\" maxlength=\"11\" value=\"\'.$analytics_view_id.\'\" title=\"Analytics View ID\" /><span>Should be a numerical value. You can find the View ID by signing in to your Analytics account, selecting Admin tab and then choosing the desired Analytics account from &quot;Account&quot; column, after which select the desired property from &quot;Property&quot; column, and finally clicking under &quot;Property&quot; option called &quot;Tracking Info&quot;. (<a href=\"https://support.google.com/analytics/answer/1032385?hl=en\" rel=\"nofollow external\" target=\"_new\">Help</a>)</span></li>\n<li><label for=\"analytics_language\">Preferred language:</label><select name=\"analytics_language\" id=\"analytics_language\" title=\"Plugin language\"><option\';\nif ((isset($analytics_language)) && ($analytics_language === \'en\')) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \' value=\"en\">English</option></select><span>Currently only English is available. Other language options are coming later.</span></li>\n\n<li><label for=\"analytics_datetime\">Date &amp; time format:</label><select name=\"analytics_datetime\" id=\"analytics_datetime\">\n<option value=\"1\"\';\nif ((isset($analytics_datetime)) && (empty($analytics_datetime)) or ($analytics_datetime == 1)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'Y-m-d H:i\').\'</option>\n<option value=\"2\"\';\nif ((isset($analytics_datetime)) && ($analytics_datetime == 2)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'n/j/Y H:i\').\'</option>\n<option value=\"3\"\';\nif ((isset($analytics_datetime)) && ($analytics_datetime == 3)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'j.n.Y H.i\').\'</option>\n</select><span>Date and time format, which is shown for example in the Saved reports secton.</span></li>\n\n<li><label>Show <strong>detailed reports</strong> to Publishers and:</label><ul>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_1\" value=\"managing-editors\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_1\">Managing Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_2\" value=\"copy-editors\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_2\">Copy Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_3\" value=\"staff-writers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_3\">Staff Writers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_4\" value=\"freelancers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_4\">Freelancers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_5\" value=\"designers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_5\">Designers</label></li>\n</ul><span>This functionality can be limited to certain user groups.</span></li>\n\n<li><label>Show <strong>saved reports</strong> to Publishers and:</label><ul>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_1\" value=\"managing-editors\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_1\">Managing Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_2\" value=\"copy-editors\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_2\">Copy Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_3\" value=\"staff-writers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_3\">Staff Writers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_4\" value=\"freelancers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_4\">Freelancers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_5\" value=\"designers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_5\">Designers</label></li>\n</ul><span>This functionality can be limited to certain user groups.</span></li>\n\n<li><button type=\"submit\" name=\"submit\">Save</button></li>\n</ul>\n</fieldset>\n</form>\';\n\n}\n# ENDING: MAIN TAB WHERE VIEW ID IS SET, ETC.\n# BEGINNING: STATUS TAB WHERE HEALTH CHECK IS\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\') && (!empty($analytics_is_not_allowed_to_view))) {\n\n$analytics_health_good = \'<strong class=\"analytics_icon analytics_good\">&#9745;</strong> \';\n$analytics_health_bad = \'<strong class=\"analytics_icon analytics_bad\">&#9744;</strong> \';\n\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<h3>Status</h3>\n<div class=\"analytics_box\">\n<p>Checking on the surface if certain required bits and pieces are in place. Something is likely wrong if you don\\\'t see green checkboxes.</p>\n<ul id=\"analytics_health\">\';\n\nif (is_dir(\'../kuo_analytics/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/src/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/src/Google/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/Google/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/</code> does not exist</li>\';\n}\n\nif (file_exists(\'../kuo_analytics/google-api-php-client/src/Google/autoload.php\')) {\n\nif (filesize(\'../kuo_analytics/google-api-php-client/src/Google/autoload.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> does not exist</li>\';\n}\n\n# BEGINNING: CHECKING IF INDEX.PHP EXISTS\nif (file_exists(\'../kuo_analytics/index.php\')) {\n\nif (filesize(\'../kuo_analytics/index.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/index.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/index.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/index.php</code> does not exist</li>\';\n}\n# ENDS: CHECKING IF INDEX.PHP EXISTS\n\n# BEGINNING: CHECKING IF KUO_ANALYTICS_OVERVIEW.PHP EXISTS\nif (file_exists(\'../kuo_analytics/kuo_analytics_overview.php\')) {\n\nif (filesize(\'../kuo_analytics/kuo_analytics_overview.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> does not exist</li>\';\n}\n# ENDS: CHECKING IF KUO_ANALYTICS_OVERVIEW.PHP EXISTS\n\nif ($current_analytics_settings) {\n\nif ($current_analytics_settings[\'view_id\']) {\necho \'<li>\'.$analytics_health_good.\' Analytics View ID exists in database</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' Analytics View ID does not exist in database</li>\';\n}\n\nif (file_exists(\'../kuo_analytics/\'.$current_analytics_settings[\'auth_file\'].\'.json\')) {\n\nif (filesize(\'../kuo_analytics/\'.$current_analytics_settings[\'auth_file\'].\'.json\') == 0) {\necho \'<li>\'.$analytics_health_bad.\' JSON authorization file exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\' JSON authorization file exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' JSON authorization file does not exist</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' Analytics View ID does not exist in database</li>\';\n}\n\necho \'</ul></div>\';\n\n}\n# ENDING: STATUS TAB WHERE HEALTH CHECK IS\n# BEGINNING: HELPING TO INSTALL THIS PLUGIN\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\') && (!empty($analytics_is_not_allowed_to_view))) {\n\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<h3>Install</h3>\n<div class=\"analytics_box\">\n<dl>\n<dt>JSON Auth File</dt>\n<dd>So that this plugin could fetch data from your Google Analytics account, a special JSON authorization file should be placed inside folder <code>../kuo_analytics/</code>. Please make sure that folder has the JSON authorization file called:</dd>\';\n\nif (empty($current_analytics_settings[\'auth_file\'])) {\n$current_analytics_auth_file = md5(strrev(date(\'H:i:s\')));\n\nsafe_update(\'kuo_analytics\',\"auth_file=$current_analytics_auth_file\",\"id=1\",1);\n\n}\nelse {\n$current_analytics_auth_file = $current_analytics_settings[\'auth_file\'];\n}\n\necho \'<dd><code>\'.$current_analytics_settings[\'auth_file\'].\'.json</code></dd></dl>\';\n\nif (!file_exists(\'../kuo_analytics/\'.filter_var($current_analytics_settings[\'auth_file\'],FILTER_SANITIZE_STRING).\'.json\')) {\necho \'<dl class=\"analytics_error\"><dt>JSON Auth File Status</dt><dd>&cross; Please fix this! Looks like the JSON file <strong>is not there</strong>. Data from Google Analytics can not be fetched.</dd></dl>\';\n}\nelse {\necho \'<dl class=\"analytics_ok\"><dt>JSON Auth File Status</dt><dd>&check; Great! Looks like the JSON file is in place. Data from Google Analytics should be possible to fetch.</dd></dl>\';\n}\n\necho \'</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>Requirements and installation in general</dt>\n<dd>To install this plugin, you should upload a folder called <code>kuo_analytics</code> with all the files that come with it inside your website\\s root (somewhere like: <code>\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/kuo_analytics/</code>) and upload inside that folder the above mentioned JSON file. Besides these files, you should log in to <a href=\"https://console.developers.google.com/\" rel=\"nofollow external\" target=\"_new\">Google Developers Console</a> and create a new project, which has Analytics API activated. After activating that API for your newly created project, it\\\'s time to make new credentials for the project.</dd>\n<dd class=\"analytics_highlight\"><strong>See these screenshots to get a better understanding of what to do in Google Developers Console: <a href=\"http://kuopassa.net/images/475.png\" rel=\"nofollow external\" target=\"_new\">screenshot 1</a>, <a href=\"http://kuopassa.net/images/474.png\" rel=\"nofollow external\" target=\"_new\">screenshot 2</a>.</strong></dd>\n</dl>\n</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>What you need</dt>\n<dd><ol>\n<li><strong>[easy to do]</strong> Textpattern account with Publisher, Managing Editor or Copy Editor privileges.</li>\n<li><strong>[easy to do]</strong> Google Analytics account with privileges to view Google Analytics data.</li>\n<li><strong>[easy to do]</strong> Plugin <code>kuo_analytics</code> installed and activated.</li>\n<li><strong>[easy to do]</strong> Folder <code>kuo_analytics</code>, including files that come with it, uploaded to your website\\\'s root.</li>\n<li><strong>[could be confusing]</strong> In Google Console a new project with Analytics API activated.</li>\n<li><strong>[could be confusing]</strong> JSON authorization file (from the previously mentioned Google Developers Console project) uploaded inside folder <code>kuo_analytics</code>.</li>\n</ol></dd>\n</dl>\n</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>X happened, now what?</dt>\n<dd>\n<ul>\n<li><strong>Google asks to allow &quot;offline use&quot;. Why?</strong> It\\\'s about logging in to your Google account in a way that you can view your Google Analytics statistics.</li>\n<li><strong>There is an error &quot;Error: redirect_uri_mismatch&quot;.</strong> In that case the URL, which you\\\'ve saved to your Google Developers Console project, is not correct. The URL should be something like <code>http://www.yoursite.com/kuo_analytics/kuo_analytics_overview.php</code>.</li>\n<li><strong>I don\\\'t see any statistics.</strong> Do you have privileges to view Google Analytics statistics for the View ID you\\\'ve saved to this plugin\\\'s settings? You can\\\'t open stats from any Google Analytics account: only from those which are either yours, or from such accounts where the account owner has allowed your Google account to view them.</li>\n<li><strong>What does property name &quot;All Website Data&quot; mean?</strong> That\\\'s just the default English name for the View ID. You can change that by logging in to Google Analytics at <a href=\"http://www.google.com/analytics/\" rel=\"nofollow external\" target=\"_new\">www.google.com/analytics</a> and selecting the property which you want to edit. Under &quot;View Settings&quot;, where the View ID is located as well, should be a text field to which you can type something more suitable.</li>\n</ul>\n</dd>\n</dl>\n</dvi>\';\n\n}\n# ENDING: HELPING TO INSTALL THIS PLUGIN\n# BEGINNING: STATISTICS SECTION\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\n\n$active_tab = \' class=\"active_tab\"\';\n\n# WHAT SUBPAGE USER IS TO GET PROPER URL FOR AJAX QUERY\nif ((!isset($_GET[\'overview\'])) or (empty($_GET[\'overview\']))) {\n$analytics_query_period = \'last-month\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'14-days\')) {\n$analytics_query_period = \'last-two-weeks\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'7-days\')) {\n$analytics_query_period = \'last-week\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'yesterday\')) {\n$analytics_query_period = \'yesterday\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'today\')) {\n$analytics_query_period = \'today\';\n}\nelse {\n$analytics_query_period = \'last-month\';\n}\n\n# COUNTING AMOUNT OF SAVED REPORTS\nif (safe_count(\'kuo_analytics_reports\',\"id\") != 0) {\n$analytics_amount_of_saved_reports = \' (\'.safe_count(\'kuo_analytics_reports\',\"id\").\')\';\n}\nelse {\n$analytics_amount_of_saved_reports = NULL;\n}\n\necho \'<h3>Statistics</h3>\n<div id=\"analytics_area\">\n<ul>\n<li\';\nif ((!isset($_GET[\'overview\'])) && (!isset($_GET[\'saved-reports\']))) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\" rel=\"chapter nofollow\">Past 30 days</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'14-days\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=14-days\" rel=\"chapter nofollow\">Past two weeks</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'7-days\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=7-days\" rel=\"chapter nofollow\">Past week</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'yesterday\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=yesterday\" rel=\"chapter nofollow\">Yesterday</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'today\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=today\" rel=\"chapter nofollow\">Today</a></li>\';\nif (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1) {\necho \'<li\';\nif (isset($_GET[\'saved-reports\'])) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"chapter nofollow\">Saved reports\'.$analytics_amount_of_saved_reports.\'</a></li>\';\n}\necho \'</ul>\';\n\nif ((isset($_GET[\'overview\'])) && (!empty($_GET[\'overview\']))) {\n$analytics_detailed_tab = \'&amp;overview=\'.filter_var($_GET[\'overview\'],FILTER_SANITIZE_STRING);\n}\nelse {\n$analytics_detailed_tab = NULL;\n}\n\nif (!isset($_GET[\'saved-reports\'])) {\n\nif ((isset($_SERVER[\"QUERY_STRING\"])) && (!empty($_SERVER[\"QUERY_STRING\"])) && (isset($_GET[\'analytics_tab\'])) && (!empty($_GET[\'analytics_tab\']))) {\n\n# CHECKING IF DETAILED REPORT BOX IS SAVED OPEN OR CLOSED\nif (safe_count(\'kuo_analytics\',\"detailed_toggle=\'1\'\",0) == 1) {\n$analytics_show_hide_text = \'Close\';\n}\nelse {\n$analytics_show_hide_text = \'Open\';\n}\n\n$analytics_show_hide_detailed = \' <a href=\"./index.php?\'.$_SERVER[\"QUERY_STRING\"].\'&amp;toggle\" rel=\"nofollow\">\'.$analytics_show_hide_text.\'</a>\';\n\n# AND IF TOGGLE IS CLICKED, LETS SAVE IT\nif ((isset($_GET[\'toggle\'])) && (empty($_GET[\'toggle\']))) {\n\nif (safe_count(\'kuo_analytics\',\"detailed_toggle=\'1\'\",0) == 1) {\nsafe_update(\'kuo_analytics\',\"detailed_toggle=0\",\"id=1\",0);\n}\nelse {\nsafe_update(\'kuo_analytics\',\"detailed_toggle=1\",\"id=1\",0);\n}\n\nheader(\'location:./index.php?\'.str_replace(\'&toggle\',\'\',$_SERVER[\"QUERY_STRING\"]));\ndie;\n\n}\n\n}\nelse {\n$analytics_show_hide_detailed = NULL;\n}\n\n$analytics_detailed_link = \' class=\"active_tab\"\';\n\n# BEGINNING: IF ALLOWED TO VIEW DETAILED REPORTS\nif (KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS == 1) {\n\necho \'<dl id=\"analytics_detailed_navigation\" class=\"analytics_detailed_\'.strtolower($analytics_show_hide_text).\'\">\n<dt>Detailed reports\'.$analytics_show_hide_detailed.\'</dt>\n<dd><ul>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'sessions-by-country\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=sessions-by-country\" rel=\"nofollow bookmark\">Sessions by country</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'users-and-pageviews\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=users-and-pageviews\" rel=\"nofollow bookmark\">Users and pageviews</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'mobile-traffic\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=mobile-traffic\" rel=\"nofollow bookmark\">Mobile traffic</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'revenue-generating-campaigns\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=revenue-generating-campaigns\" rel=\"nofollow bookmark\">Revenue generating campaigns</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'new-vs-returning-sessions\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=new-vs-returning-sessions\" rel=\"nofollow bookmark\">New vs. returning sessions</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'adsense\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=adsense\" rel=\"nofollow bookmark\">AdSense</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'browser-and-operating-system\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=browser-and-operating-system\" rel=\"nofollow bookmark\">Browser and OS</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'time-on-site\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=time-on-site\" rel=\"nofollow bookmark\">Time on site</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'all-traffic-sources-usage\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=all-traffic-sources-usage\" rel=\"nofollow bookmark\">All traffic: usage</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'referring-sites\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=referring-sites\" rel=\"nofollow bookmark\">Referring sites</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines\" rel=\"nofollow bookmark\">Search engines</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines-organic\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines-organic\" rel=\"nofollow bookmark\">Search engines: organic</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines-paid\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines-paid\" rel=\"nofollow bookmark\">Search engines: paid</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'keywords\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=keywords\" rel=\"nofollow bookmark\">Keywords</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-content\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-content\" rel=\"nofollow bookmark\">Top content</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-landing-pages\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-landing-pages\" rel=\"nofollow bookmark\">Top landing pages</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-exit-pages\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-exit-pages\" rel=\"nofollow bookmark\">Top exit pages</a></li>\n</ul></dd>\n</dl>\';\n}\n\n}\n# ENDING: IF ALLOWED TO VIEW DETAILED REPORTS\n\n# BEGINNING: IF SAVED REPORTS\nif ((!isset($_GET[\'overview\'])) && (isset($_GET[\'saved-reports\'])) && (empty($_GET[\'saved-reports\'])) && (!isset($_GET[\'detailed\'])) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\n# BEGINNING: CHECKING IF REPORT IS TO BE STARRED / UNSTARRED\nif ((isset($_GET[\'star\'])) && (!empty($_GET[\'star\'])) && (base64_decode($_GET[\'star\'],true))) {\n\n$star_saver_id = json_decode(base64_decode($_GET[\'star\']));\n\nif ($star_saver_id[0] == 1) {\nsafe_update(\'kuo_analytics_reports\',\"starred=1\",\"id=\'$star_saver_id[1]\'\",0);\n}\nelse {\nsafe_update(\'kuo_analytics_reports\',\"starred=0\",\"id=\'$star_saver_id[1]\'\",0);\n}\n\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports\');\ndie;\n\n}\n# ENDING: CHECKING IF REPORT IS TO BE STARRED / UNSTARRED\n\nif ((isset($_GET[\'order\'])) && (!empty($_GET[\'order\'])) && ($_GET[\'order\'] === \'uid\') && (isset($_GET[\'reverse\'])) && (empty($_GET[\'reverse\']))) {\n$analytics_order_link = NULL;\n$analytics_order_symbol = \'&duarr;\';\n$analytics_order_query = \'`id` ASC\';\n}\nelse {\n$analytics_order_link = \'&amp;order=uid&amp;reverse\';\n$analytics_order_symbol = \'&udarr;\';\n$analytics_order_query = \'`id` DESC\';\n}\n\necho \'<table id=\"analytics_reports\"><thead><tr><th class=\"analytics_empty_cell\">&nbsp;</th><th>&starf;</th><th>UID <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\'.$analytics_order_link.\'\" rel=\"nofollow\">\'.$analytics_order_symbol.\'</a></th><th>View ID</th><th>Property name</th><th>Report name</th><th>Saved</th><th title=\"Starting approximately\">Starting ~</th><th title=\"Ending approximately\">Ending ~</th><th>&nbsp;</th></tr></thead><tbody>\';\n\n# COUNTING IF REPORTS ARE SAVED\nif (safe_count(\'kuo_analytics_reports\',\"id\") != 0) {\n\nif (safe_count(\'kuo_analytics_reports\',\"id\") == 1) {\n$report_heading = \'One saved report\';\n}\nelse {\n$report_heading = safe_count(\'kuo_analytics_reports\',\"id\").\' saved reports\';\n}\n\n# CHECKING WHAT DATETIME (AND LATER MAYBE OTHER SETTINGS, LIKE LANGUAGE) USER WANTS\n$analytics_preferences = safe_row(\'datetime_format\',\'kuo_analytics\',\"id=\'1\'\",0);\n\n# BEGINNING: FORMATTING DATETIME\nif (($analytics_preferences[\'datetime_format\'] == 1) or (empty($analytics_preferences[\'datetime_format\']))) {\n$analytics_datetime_format_full = \'Y-m-d H:i\';\n$analytics_datetime_format_short = \'Y-m-d\';\n}\nelseif ($analytics_preferences[\'datetime_format\'] == 2) {\n$analytics_datetime_format_full = \'n/j/Y H:i\';\n$analytics_datetime_format_short = \'n/j/Y\';\n}\nelseif ($analytics_preferences[\'datetime_format\'] == 3) {\n$analytics_datetime_format_full = \'j.n.Y H.i\';\n$analytics_datetime_format_short = \'j.n.Y\';\n}\nelse {\n$analytics_datetime_format_full = \'j.n.Y H.i\';\n$analytics_datetime_format_short = \'Y-m-d\';\n}\n# ENDING: FORMATTING DATETIME\n\necho \'<h3>\'.$report_heading.\'</h3>\';\n\n$analytics_report_list = safe_rows(\'id,starred,view_id,created,property_name,report,start_date,end_date\',\'kuo_analytics_reports\',\'`id` IS NOT NULL AND `view_id` IS NOT NULL AND `created` IS NOT NULL AND `property_name` IS NOT NULL AND `report` IS NOT NULL AND `start_date` IS NOT NULL AND `end_date` IS NOT NULL ORDER BY \'.$analytics_order_query,0);\n\nif ($analytics_report_list) {\n\n$analytics_list_counter = 1;\n\nforeach ($analytics_report_list as $report_item) {\n\nif (is_numeric($report_item[\'id\'])) {\n$report_item_row_id = filter_var($report_item[\'id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_row_id = filter_var($report_item[\'id\'],FILTER_SANITIZE_STRING);\n}\n\nif (is_numeric($report_item[\'view_id\'])) {\n$report_item_view_id = filter_var($report_item[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_view_id = filter_var($report_item[\'view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif ((is_numeric($report_item[\'starred\'])) && (!empty($report_item[\'starred\']))) {\n$report_item_row_starred = base64_encode(json_encode(array(0,$report_item_row_id)));\n$report_item_row_starred_icon = \'&starf;\';\n}\nelse {\n$report_item_row_starred = base64_encode(json_encode(array(1,$report_item_row_id)));\n$report_item_row_starred_icon = \'&star;\';\n}\n\nif (is_numeric($report_item[\'property_name\'])) {\n$report_item_property_name = filter_var($report_item[\'property_name\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_property_name = filter_var($report_item[\'property_name\'],FILTER_SANITIZE_STRING);\n}\n\n$report_item_saved = date($analytics_datetime_format_full,strtotime(filter_var($report_item[\'created\'],FILTER_SANITIZE_STRING)));\n$report_item_start_date = date($analytics_datetime_format_short,strtotime(filter_var($report_item[\'start_date\'],FILTER_SANITIZE_STRING)));\n$report_item_end_date = date($analytics_datetime_format_short,strtotime(filter_var($report_item[\'end_date\'],FILTER_SANITIZE_STRING)));\n\n$report_item_actual_report = unserialize($report_item[\'report\']);\n\nif (is_array($report_item_actual_report)) {\n$report_item_report_name = filter_var($report_item_actual_report[\'Title\'],FILTER_SANITIZE_STRING);\n\nif (strlen($report_item_report_name) >= 20) {\n$report_item_report_name = substr($report_item_report_name,0,19).\'&hellip;\';\n}\n\n}\nelse {\n$report_item_report_name = \'Unknown\';\n}\n\necho \'<tr><td class=\"analytics_counter\">\'.$analytics_list_counter++.\'</td><td><a href=\"./index.php?event=kuo_analytics&analytics_tab=statistics&amp;saved-reports&amp;star=\'.$report_item_row_starred.\'\" rel=\"nofollow\">\'.$report_item_row_starred_icon.\'</a></td><td>\'.$report_item_row_id.\'</td><td>\'.$report_item_view_id.\'</td><td>\'.$report_item_property_name.\'</td><td><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;view=\'.$report_item_row_id.\'#analytics_tab\" rel=\"nofollow bookmark\">\'.$report_item_report_name.\'</a></td><td>\'.$report_item_saved.\'</td><td>\'.$report_item_start_date.\'</td><td>\'.$report_item_end_date.\'</td><td><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;delete=\'.$report_item_row_id.\'\" rel=\"nofollow\">Delete</a></td></tr>\';\n}\n}\n\n}\nelse {\necho \'<tr id=\"analytics_no_reports\"><td class=\"analytics_counter\">&nbsp;</td><td colspan=\"9\">No reports.</td></tr>\';\n}\n\nunset($analytics_list_counter);\nunset($report_item_view_id);\nunset($report_item_property_name);\nunset($report_item_report_name);\nunset($report_item_saved);\nunset($report_item_start_date);\nunset($report_item_end_date);\n\necho \'</tbody></table>\';\n\n# BEGINNING: IF VIEWING A SINGLE SAVED REPORT\nif ((isset($_GET[\'view\'])) && (!empty($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n\n$analytics_view_report_id = filter_var($_GET[\'view\'],FILTER_SANITIZE_NUMBER_INT);\n$analytics_report_single = safe_row(\'*\',\'kuo_analytics_reports\',\"id=\'$analytics_view_report_id\'\",0);\n\n# SOME CODE REPEATING, BUT SINGLE REPORT NEEDS THIS\n$report_item_view_starred = filter_var($analytics_report_single[\'starred\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (empty($report_item_view_starred)) {\n$report_item_view_starred = \'&star; \';\n}\nelse {\n$report_item_view_starred = \'&starf; \';\n}\n\nif (is_numeric($analytics_report_single[\'view_id\'])) {\n$report_item_view_id = filter_var($analytics_report_single[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_view_id = filter_var($analytics_report_single[\'view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif (is_numeric($analytics_report_single[\'property_name\'])) {\n$report_item_property_name = filter_var($analytics_report_single[\'property_name\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_property_name = filter_var($analytics_report_single[\'property_name\'],FILTER_SANITIZE_STRING);\n}\n\n$report_item_actual_report = unserialize($analytics_report_single[\'report\']);\n\nif (is_array($report_item_actual_report)) {\n$report_item_report_name = filter_var($report_item_actual_report[\'Title\'],FILTER_SANITIZE_STRING);\n}\nelse {\n$report_item_report_name = \'Unknown\';\n}\n\n$report_item_repot_comment = $analytics_report_single[\'comment\'];\n\nif (is_numeric($report_item_repot_comment)) {\n$report_item_repot_comment = filter_var($report_item_repot_comment,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_repot_comment = filter_var($report_item_repot_comment,FILTER_SANITIZE_STRING);\n}\n\n$report_item_start_date = date($analytics_datetime_format_short,strtotime(filter_var($analytics_report_single[\'start_date\'],FILTER_SANITIZE_STRING)));\n$report_item_end_date = date($analytics_datetime_format_short,strtotime(filter_var($analytics_report_single[\'end_date\'],FILTER_SANITIZE_STRING)));\n$report_item_modified = date($analytics_datetime_format_full,strtotime(filter_var($analytics_report_single[\'modified\'],FILTER_SANITIZE_STRING)));\n\necho \'<div id=\"analytics_tab\">\n<div id=\"kuo_analytics_widget\">\n<div id=\"kuo_analytics_header\">\n<dl>\n<dt><span>Saved report #\'.$analytics_view_report_id.\'</span> Property name: &quot;\'.$report_item_property_name.\'&quot;.</dt>\n<dd>View ID: ga:\'.$report_item_view_id.\'</dd>\n</dl>\n<p><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"nofollow\">Close this report</a></p>\n</div>\n<div id=\"kuo_analytics_summary\">\n<h3>\'.$report_item_view_starred.$report_item_report_name.\'</h3>\';\n\n# WRITE A COMMENT TO THIS SAVED REPORT\necho \'<form action=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;view=\'.$analytics_view_report_id.\'\" method=\"post\">\n<input type=\"hidden\" name=\"analytics_comment_source\" value=\"\'.$analytics_view_report_id.\'\" />\n<fieldset>\n<legend>Comments for this saved report</legend>\n<ul>\n<li><label for=\"analytics_comments\">Comments (plain text only):</label><textarea id=\"analytics_comments\" name=\"analytics_comments\" rows=\"4\" cols=\"60\" placeholder=\"Write a comment here.\">\'.$report_item_repot_comment.\'</textarea></li>\n<li><button type=\"submit\" name=\"submit\">Save comments</button></li>\n</ul>\n</fieldset>\n</form>\';\n\necho \'<p id=\"analytics_date_interval\"><em>From \'.$report_item_start_date.\' to \'.$report_item_end_date.\'. Modified \'.$report_item_modified.\'.</em></p>\';\n\n# IF REPORT STATUS = 1, IT\'S BASIC\n# IF REPORT STATUS = 2, IT\'S DETAILED\n\n# BEGINNING: BASIC REPORT\nif ((empty($analytics_report_single[\'type\'])) or ($analytics_report_single[\'type\'] == 1) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\nforeach (array_slice($report_item_actual_report,1) as $analytics_report_heading => $analytics_report_value) {\n\n$analytics_report_heading = filter_var($analytics_report_heading,FILTER_SANITIZE_STRING);\n$analytics_report_value = filter_var($analytics_report_value,FILTER_SANITIZE_STRING);\n\n# ADDING SOME VERBAL INSTRUCTIONS TO SOME FIELDS\nif ($analytics_report_heading === \'Avg. time on site\') {\n$analytics_report_value = $analytics_report_value.\' minutes\';\n}\nelseif ($analytics_report_heading === \'Bounce rate\') {\n$analytics_report_value = $analytics_report_value.\' %\';\n}\nelseif (($analytics_report_heading === \'Avg. page load time\') or ($analytics_report_heading === \'Avg. server response time\')) {\n$analytics_report_value = $analytics_report_value.\' seconds\';\n}\n\necho \'<dl><dt>\'.$analytics_report_heading.\'</dt><dd>\'.$analytics_report_value.\'</dd></dl>\';\n}\n\n}\nelse {\n# ENDING: BASIC REPORT\n# BEGINNING: DETAILED REPORT\n\necho \'<table id=\"analytics_reports\"><thead>\';\nforeach (array_splice($report_item_actual_report,1) as $table_data) {                                     \n\n$table_header_array = $table_data[0];\n\nforeach ($table_header_array as $table_header) {\necho \'<th>\'.filter_var($table_header,FILTER_SANITIZE_STRING).\'</th>\';\n}\n\nunset($table_header);\n\n}\necho \'</thead><tbody>\';\n\n# SINGLE DATA CELLS INSIDE <TR></TR> PAIRS\n# WELCOME TO WORLD OF HURT\n# THREE FOREACH...\n\nforeach (array_splice($table_data,1) as $table_data) {\n\nforeach ($table_data as $table_data2) {\necho \'<tr>\';\n\nforeach ($table_data2 as $table_data3) {\necho \'<td>\'.$table_data3.\'</td>\';\n}\n\necho \'</tr>\';\n}\n\n}\n\necho \'</tbody></table>\';\n}\n# ENDING: DETAILED REPORT\n\necho \'</div></div>\';\n\n}\n# ENDING: IF VIEWING A SINGLE SAVED REPORT\n\n}\n# ENDING: IF SAVED REPORTS\n\n\n# BEGINNING: IF AJAX STATISTICS\nelse {\n\n# BEGINNING: IF DETAILED REPORTS\nif ((isset($_GET[\'detailed\'])) && (!empty($_GET[\'detailed\'])) && (KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS == 1)) {\n\n$analytics_detailed = \'&detailed=\';\n\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'sessions-by-country\')) {\n$analytics_detailed .= 1;\n}\nelseif ($_GET[\'detailed\'] == \'users-and-pageviews\') {\n$analytics_detailed .= 2;\n}\nelseif ($_GET[\'detailed\'] === \'mobile-traffic\') {\n$analytics_detailed .= 3;\n}\nelseif ($_GET[\'detailed\'] === \'revenue-generating-campaigns\') {\n$analytics_detailed .= 4;\n}\nelseif ($_GET[\'detailed\'] === \'new-vs-returning-sessions\') {\n$analytics_detailed .= 5;\n}\nelseif ($_GET[\'detailed\'] === \'adsense\') {\n$analytics_detailed .= 6;\n}\nelseif ($_GET[\'detailed\'] === \'browser-and-operating-system\') {\n$analytics_detailed .= 7;\n}\nelseif ($_GET[\'detailed\'] === \'time-on-site\') {\n$analytics_detailed .= 8;\n}\nelseif ($_GET[\'detailed\'] === \'all-traffic-sources-usage\') {\n$analytics_detailed .= 9;\n}\nelseif ($_GET[\'detailed\'] === \'referring-sites\') {\n$analytics_detailed .= 10;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines\') {\n$analytics_detailed .= 11;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines-organic\') {\n$analytics_detailed .= 12;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines-paid\') {\n$analytics_detailed .= 13;\n}\nelseif ($_GET[\'detailed\'] === \'keywords\') {\n$analytics_detailed .= 14;\n}\nelseif ($_GET[\'detailed\'] === \'top-content\') {\n$analytics_detailed .= 15;\n}\nelseif ($_GET[\'detailed\'] === \'top-landing-pages\') {\n$analytics_detailed .= 16;\n}\nelseif ($_GET[\'detailed\'] === \'top-exit-pages\') {\n$analytics_detailed .= 17;\n}\nelse {\n$analytics_detailed = 1;\n}\n\n}\nelse {\n$analytics_detailed = NULL;\n}\n# ENDING: IF DETAILED REPORTS\n\necho \'<script type=\"text/javascript\">\n$(function(){\n\n$(\"#analytics_tab\").load(\"../kuo_analytics/index.php?duration=\'.$analytics_query_period.$analytics_detailed.\' #kuo_analytics_widget\", \n\nfunction (responseText,analytics_status,XMLHttpRequest) {\n\nif (analytics_status == \"error\") {\n$(\"#analytics_tab\").prepend(\"<p id=\\\"analytics_status_message\\\"><a href=\\\"../kuo_analytics/?login\\\" rel=\\\"nofollow\\\">Please log in to Google Analytics</a></p>\");\n}\nelse if (responseText == \"\") {\n$(\"#analytics_tab\").prepend(\"<p id=\\\"analytics_status_message\\\">No data available.</p>\");\n}\nelse {\n$(\"#analytics_tab #kuo_analytics_summary\").prepend(\"<p id=\\\"analytics_save\\\"><a href=\\\"../kuo_analytics/index.php?duration=\'.$analytics_query_period.$analytics_detailed.\'&amp;save\\\" rel=\\\"nofollow\\\">Save this report</a></p>\");\n}\n});\n\n});\n</script><div id=\"analytics_tab\"></div>\';\n}\n# ENDING: IF AJAX STATISTICS\necho \'</div>\';\n\n}\n# ENDING: STATISTICS SECTION\n\necho \'</div>\';\n\n# BEGINNING: LINK TO INSTALLATION GUIDE\nif ((!isset($_GET[\'analytics_tab\'])) or ($_GET[\'analytics_tab\'] !== \'install\')) {\necho \'<p id=\"analytics_installation\"><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=install\" rel=\"chapter nofollow\">How to install?</a></p>\';\n}\n# ENDING: LINK TO INSTALLATION GUIDE\n\necho \'<p id=\"analytics_by\">Plugin by <a href=\"http://kuopassa.net/txp/\" rel=\"external\" target=\"_new\">Pete</a> with the help of <a href=\"https://developers.google.com/api-client-library/php/\" rel=\"nofollow external\">API Client Library for PHP</a>.</p>\n<p id=\"analytics_donate\"><a href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=MXL5FSBXQBL3N\" rel=\"nofollow external\">Please donate to keep the development going</a></p>\n</div>\';\n}\n\n}', 'if (@txpinterface === \'admin\') {\n\nregister_callback(\'kuo_uninstall_analytics\',\'plugin_lifecycle.kuo_analytics\',\'disabled\');\nregister_callback(\'kuo_install_analytics\',\'plugin_lifecycle.kuo_analytics\',\'enabled\');\nregister_callback(\'kuo_deleted_analytics\',\'plugin_lifecycle.kuo_analytics\',\'deleted\');\n\nregister_tab(\'extensions\',\'kuo_analytics\',\'Analytics\');\nregister_callback(\'kuo_analytics_tab\',\'kuo_analytics\');\nadd_privs(\'kuo_analytics\',\'1,2,3,4,5,6\');\n\n# UNINSTALLING PLUGIN: REMOVING DATABASE TABLE\nfunction kuo_uninstall_analytics() {\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_analytics\').\"\");\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_analytics_reports\').\"\");\n}\n\n# DELETING PLUGIN: LAUNCHING UNINSTALL FUNCTION\nfunction kuo_deleted_analytics() {\nkuo_uninstall_analytics();\n}\n\n# INSTALLING PLUGIN: CREATING DATABASE TABLE\nfunction kuo_install_analytics() {\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_analytics\').\" (`id` INT NOT NULL AUTO_INCREMENT, `view_id` INT DEFAULT NULL, `modified` DATETIME NOT NULL DEFAULT 0, `language` VARCHAR(2) NOT NULL DEFAULT \'en\', `auth_file` VARCHAR(30) DEFAULT NULL, `detailed_toggle` INT(1) NOT NULL DEFAULT \'1\', `datetime_format` INT(1) DEFAULT NULL, `show_detailed` TEXT DEFAULT NULL, `show_saved` TEXT DEFAULT NULL, PRIMARY KEY(`id`))\");\n\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_analytics_reports\').\" (`id` INT NOT NULL AUTO_INCREMENT, `view_id` INT DEFAULT NULL, `created` DATETIME NOT NULL DEFAULT 0, `modified` DATETIME NOT NULL DEFAULT 0, `property_name` VARCHAR(255) DEFAULT NULL, `report` BLOB DEFAULT NULL, `start_date` DATETIME NOT NULL DEFAULT 0, `end_date` DATETIME NOT NULL DEFAULT 0, `type` INT(1) NOT NULL DEFAULT \'1\', `starred` INT(1) NOT NULL DEFAULT \'0\', `comment` TEXT DEFAULT NULL, PRIMARY KEY(`id`))\");\n\n# CREATING A NICE LITTLE NAME FOR JSON AUTH FILE\n$analytics_file_name_candidate = substr(strrev(md5(uniqid($GLOBALS[\'prefs\'][\'siteurl\'].date(\'H:i:s\'),true))),rand(1,3),30);\n$analytics_basic_rights = serialize(array(\'publishers\'));\n\nsafe_insert(\'kuo_analytics\',\"`view_id` = NULL, `modified` = \'\".date(\'Y-m-d H:i:s\').\"\', `language` = \'en\', `auth_file` = \'\".$analytics_file_name_candidate.\"\', `show_detailed` = \'\".$analytics_basic_rights.\"\', `show_saved` = \'\".$analytics_basic_rights.\"\', `detailed_toggle` = \'1\', `datetime_format` = \'1\'\");\n\nunset($analytics_basic_rights);\nunset($analytics_file_name_candidate);\n\n}\n\n# REDIRECTING TO THE RIGHT PLACE\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_analytics\') {\nheader(\'location: ./index.php?event=kuo_analytics\');\ndie;\n}\n\n# ACTUAL PLUGIN THING\nfunction kuo_analytics_tab() {\n\n/*\nglobal $event;\nif ($event !== \'kuo_analytics\') {\nreturn;\n}\n*/\n\n# BEGINNING: CHECKING WHAT PRIVILEGES CURRENT USER HAS AND WHAT PRIVILEGES ARE ALLOWED\n$analytics_user_groups = safe_row(\'show_detailed,show_saved\',\'kuo_analytics\',\'id=1\');\n\n$analytics_current_user = is_logged_in();\n$analytics_current_user = $analytics_current_user[\'privs\'];\n\nif ($analytics_current_user == 1) {\n$analytics_current_user = \'publishers\';\n}\nelseif ($analytics_current_user == 2) {\n$analytics_current_user = \'managing-editors\';\n}\nelseif ($analytics_current_user == 3) {\n$analytics_current_user = \'copy-editors\';\n}\nelseif ($analytics_current_user == 4) {\n$analytics_current_user = \'staff-writers\';\n}\nelseif ($analytics_current_user == 5) {\n$analytics_current_user = \'freelancers\';\n}\nelseif ($analytics_current_user == 6) {\n$analytics_current_user = \'designers\';\n}\nelse {\n$analytics_current_user = NULL;\n}\n\nif ((!empty($analytics_user_groups[\'show_detailed\']))) {\n$analytics_user_groups_show_detailed = unserialize($analytics_user_groups[\'show_detailed\']);\n\n# IF MATCH IS FOUND: USER IS ALLOWED ACCESS\nif ((is_array($analytics_user_groups_show_detailed)) && (!empty($analytics_user_groups_show_detailed)) && (in_array($analytics_current_user,$analytics_user_groups_show_detailed,true))) {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',1);\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',0);\n}\n\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS\',0);\n}\n\n# DETAILED REPORT\'S PRIVILEGES USER GROUP CHECKER IS DONE. NEXT IN LINE: SAVED REPORT CHECKER\n\nif ((!empty($analytics_user_groups[\'show_saved\']))) {\n$analytics_user_groups_show_saved = unserialize($analytics_user_groups[\'show_saved\']);\n\n# IF MATCH IS FOUND: USER IS ALLOWED ACCESS SAVED REPORTS\nif ((is_array($analytics_user_groups_show_saved)) && (!empty($analytics_user_groups_show_saved)) && (in_array($analytics_current_user,$analytics_user_groups_show_saved,true))) {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',1);\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',0);\n}\n\n}\nelse {\ndefine(\'KUO_ALLOW_ACCESS_TO_SAVED_REPORTS\',0);\n}\n\n\n# UNSETTING SOME THINGS TO PREVENT ODDITIES LATER\nunset($analytics_current_user);\nunset($analytics_user_groups);\nunset($analytics_user_groups_show_detailed);\nunset($analytics_user_groups_show_saved);\nunset($analytics_user_groups_show_saved);\n\n# ENDING: CHECKING WHAT PRIVILEGES CURRENT USER HAS AND WHAT PRIVILEGES ARE ALLOWED\n\ndefine(\'KUO_ANALYTICS_ACTIVE_ID\',\' id=\"analytics_active_tab\"\');\ndefine(\'KUO_ANALYTICS_SELECTED_HTML\',\' selected=\"selected\"\');\ndefine(\'KUO_ANALYTICS_CHECKED_HTML\',\' checked=\"checked\"\');\ndefine(\'KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION\',\'<div class=\"analytics_box analytics_only_admin\"><p>This section is visible only to Publishers.</p></div>\');\n$analytics_message = NULL;\n\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\npagetop(\'Google Analytics for Textpattern\');\n\n# BEGINNING: IF SAVING SETTINGS\nif ((isset($_POST[\'submit\'])) && (empty($_POST[\'submit\'])) && (isset($_POST[\'analytics_language\'])) && (!empty($_POST[\'analytics_language\'])) && (strlen($_POST[\'analytics_language\']) == 2) && (isset($_POST[\'analytics_view_id\'])) && (isset($_POST[\'analytics_datetime\']))) {\n\n# SHOULD BE NUMERIC, BUT IN CASE SOMETHING CHANGES IN THE FUTURE\nif (is_numeric($_POST[\'analytics_view_id\'])) {\n$analytics_view_id = filter_var($_POST[\'analytics_view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_view_id = filter_var($_POST[\'analytics_view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif ((empty($analytics_view_id)) or (strlen($analytics_view_id) > 11)) {\n$analytics_view_id = NULL;\n}\n\n$analytics_language = strtolower(filter_var($_POST[\'analytics_language\'],FILTER_SANITIZE_STRING));\n\nif (!in_array($analytics_language,array(\'en\',\'fi\'))) {\n$analytics_language = \'en\';\n}\n\n$analytics_datetime = filter_var($_POST[\'analytics_datetime\'],FILTER_SANITIZE_NUMBER_INT);\n\nif ((empty($analytics_datetime)) or (!intval($analytics_datetime))) {\n$analytics_datetime = NULL;\n}\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW DETAILED REPORTS\nif ((isset($_POST[\'analytics_show_detailed\'])) && (is_array($_POST[\'analytics_show_detailed\']))) {\n\n# FOREACH START\nforeach ($_POST[\'analytics_show_detailed\'] as $analytics_show_detailed_groups) {\n\nif ($analytics_show_detailed_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',1);\n}\n\n}\n# FOREACH END\n\n$analytics_show_detailed = $_POST[\'analytics_show_detailed\'];\narray_push($analytics_show_detailed,\'publishers\');\n$analytics_show_detailed = serialize($analytics_show_detailed);\n}\nelse {\n$analytics_show_detailed = serialize(array(\'publishers\'));\n}\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW DETAILED REPORTS\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW SAVED REPORTS\nif ((isset($_POST[\'analytics_show_saved\'])) && (is_array($_POST[\'analytics_show_saved\']))) {\n\n# FOREACH START\nforeach ($_POST[\'analytics_show_saved\'] as $analytics_show_detailed_saved) {\n\nif ($analytics_show_detailed_saved === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_saved === \'designers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',1);\n}\n\n}\n# FOREACH END\n$analytics_show_saved = $_POST[\'analytics_show_saved\'];\narray_push($analytics_show_saved,\'publishers\');\n$analytics_show_saved = serialize($analytics_show_saved);\n}\nelse {\n$analytics_show_saved = serialize(array(\'publishers\'));\n}\n\n# BEGINNING: SAVING WHAT USER GROUPS CAN VIEW SAVED REPORTS\n\n# ACTUALLY SAVING DATA\n# CAN BE IN THE FUTURE ADJUSTED TO INCLUDE SEVERAL VIEW ID\'S,\n# BUT THAT FEATURE IS COMING LATER THAN SOONER\nif (safe_count(\'kuo_analytics\',\"id=1\") == 0) {\nsafe_insert(\'kuo_analytics\',\"`view_id` = \'\".$analytics_view_id.\"\', `modified` = \'\".date(\'Y-m-d H:i:s\').\"\', `language` = \'\".$analytics_language.\"\', `datetime_format` = \'\".$analytics_datetime.\"\', `show_detailed` = \'\".$analytics_show_detailed.\"\', `show_saved` = \'\".$analytics_show_saved.\"\'\");\n$analytics_message = \'<p id=\"analytics_message\">Great! View ID was saved. <a href=\"./index.php?event=kuo_analytics\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\nelse {\nsafe_update(\'kuo_analytics\',\"view_id = \'\".$analytics_view_id.\"\', modified = \'\".date(\'Y-m-d H:i:s\').\"\', language = \'\".$analytics_language.\"\', datetime_format= \'\".$analytics_datetime.\"\', `show_detailed` = \'\".$analytics_show_detailed.\"\', `show_saved` = \'\".$analytics_show_saved.\"\'\",\"id =1\");\n$analytics_message = \'<p id=\"analytics_message\">Great! Changes were saved. <a href=\"./index.php?event=kuo_analytics\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\n\n}\n# ENDING: IF SAVING SETTINGS\n# BEGINNING: SETTINGS FROM DATABASE\nelse {\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\nif (!empty($current_analytics_settings)) {\n\n$analytics_view_id = filter_var($current_analytics_settings[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (is_numeric($analytics_view_id)) {\n$analytics_view_id = filter_var($analytics_view_id,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_view_id = filter_var($analytics_view_id,FILTER_SANITIZE_STRING);\n}\n\n$analytics_language = filter_var($current_analytics_settings[\'language\'],FILTER_SANITIZE_STRING);\n\nif (empty($analytics_view_id)) {\n$analytics_view_id = NULL;\n}\n\nif (empty($analytics_language)) {\n$analytics_language = NULL;\n}\n\n$analytics_datetime = filter_var($current_analytics_settings[\'datetime_format\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (empty($analytics_datetime)) {\n$analytics_datetime = 1;\n}\n\n# BEGINNING: SHOW DETAILED REPORTS VIEW STATUS FROM DB\n$analytics_show_detailed = unserialize($current_analytics_settings[\'show_detailed\']);\n\nif (!empty($analytics_show_detailed)) {\n\nforeach ($analytics_show_detailed as $analytics_show_detailed_groups) {\n\nif ($analytics_show_detailed_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_detailed_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',1);\n}\n\n}\n}\n# ENDING: SHOW DETAILED REPORTS VIEW STATUS FROM DB\n\n# BEGINNING: SHOW ***SAVED*** REPORTS VIEW STATUS FROM DB\n$analytics_show_saved = unserialize($current_analytics_settings[\'show_saved\']);\nif (!empty($analytics_show_saved)) {\nforeach ($analytics_show_saved as $analytics_show_saved_groups) {\n\nif ($analytics_show_saved_groups === \'managing-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',1);\n}\nelseif ($analytics_show_saved_groups === \'copy-editors\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',1);\n}\nelseif ($analytics_show_saved_groups === \'staff-writers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',1);\n}\nelseif ($analytics_show_saved_groups === \'freelancers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',1);\n}\nelseif ($analytics_show_saved_groups === \'designers\') {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',1);\n}\n\n}\n}\n# ENDING: SHOW ***SAVED*** REPORTS VIEW STATUS FROM DB\n\n}\n# IF NO DATA FROM DATABASE\nelse {\n$analytics_view_id = NULL;\n$analytics_language = \'en\';\n}\n\n}\n\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\')) {\npagetop(\'Install | Google Analytics for Textpattern\');\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\npagetop(\'Status | Google Analytics for Textpattern\');\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\npagetop(\'Statistics | Google Analytics for Textpattern\');\n\n# BEGINNING: IF USER WANTS TO SAVE COMMENT TO SAVED REPORT\nif ((isset($_GET[\'saved-reports\'])) && (empty($_GET[\'saved-reports\'])) && (isset($_GET[\'view\'])) && (!empty($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n\nif ((isset($_POST[\'analytics_comment_source\'])) && (!empty($_POST[\'analytics_comment_source\'])) && (is_numeric($_POST[\'analytics_comment_source\'])) && (isset($_POST[\'analytics_comments\']))) {\n\n$analytics_posted_comment = $_POST[\'analytics_comments\'];\n$analytics_posted_comment_id = filter_var($_POST[\'analytics_comment_source\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (is_numeric($analytics_posted_comment)) {\n$analytics_posted_comment = filter_var($analytics_posted_comment,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$analytics_posted_comment = filter_var($analytics_posted_comment,FILTER_SANITIZE_STRING);\n}\n\n# SAVING\nif ($analytics_posted_comment_id == $_GET[\'view\']) {\nsafe_update(\'kuo_analytics_reports\',\"`comment` = \'\".$analytics_posted_comment.\"\', `modified` = \'\".date(\'Y-m-d H:i:s\').\"\'\",\"id=\'$analytics_posted_comment_id\'\",1);\n}\n\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&view=\'.$analytics_posted_comment_id.\'&status=\'.base64_encode(json_encode(array(\'success-comment-saved\',$analytics_posted_comment_id))).\'#analytics_message\');\nunset($analytics_posted_comment_id);\nunset($analytics_posted_comment);\ndie;\n\n}\n\n\n\n}\n# ENDING: IF USER WANTS TO SAVE COMMENT TO SAVED REPORT\n\n# BEGINNING: IF USER WANTS TO DELETE A REPORT\nif ((isset($_GET[\'delete\'])) && (!empty($_GET[\'delete\'])) && (is_numeric($_GET[\'delete\'])) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\n$analytics_delete_report_id = filter_var($_GET[\'delete\'],FILTER_SANITIZE_NUMBER_INT);\n# IF REPORT TO BE DELETED EXISTS\nif ((!empty($analytics_delete_report_id)) && (safe_count(\'kuo_analytics_reports\',\"id=$analytics_delete_report_id\") == 1)) {\n$analytics_message = \'<p id=\"analytics_message\">Do you really want to delete this report? <strong><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;delete=\'.$analytics_delete_report_id.\'&amp;confirm=\'.base64_encode(json_encode(array(\'confirm\',$analytics_delete_report_id))).\'\" rel=\"nofollow\">Yes</a> or <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"nofollow\">no</a></strong>?</p>\';\n\n# ACTUALLY DELETING THE REPORT IF CONFIRM MESSAGE OK\nif (isset($_GET[\'confirm\'])) {\n$analytics_report_delete_confirm_message = json_decode(base64_decode($_GET[\'confirm\']));\n\nif ((!empty($analytics_report_delete_confirm_message)) && (filter_var($analytics_report_delete_confirm_message[0],FILTER_SANITIZE_STRING) === \'confirm\') && (is_numeric(filter_var($analytics_report_delete_confirm_message[1],FILTER_SANITIZE_NUMBER_INT)))) {\n$report_delete_id = filter_var($analytics_report_delete_confirm_message[1],FILTER_SANITIZE_NUMBER_INT);\n\nif (safe_delete(\'kuo_analytics_reports\',\"id=\'$report_delete_id\'\")) {\n$analytics_status_message = \'success-deleted\';\n}\nelse {\n$analytics_status_message = \'fail-deleted\';\n}\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&status=\'.base64_encode(json_encode(array($analytics_status_message,$report_delete_id))));\ndie;\n\n}\n\n}\n\n}\n# IF THIS ARTICLE ID DOES NOT EXIST\nelse {\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports&status=\'.base64_encode(json_encode(array(\'fail-missing\',$analytics_delete_report_id))));\nunset($analytics_delete_report_id);\ndie;\n}\n\n}\n# ENDING: IF USER WANTS TO DELETE A REPORT\n\n# CHECKING IF A REPORT HAS BEEN SAVED / FAILED TO SAVE TO DB\nif ((isset($_GET[\'status\'])) && (!empty($_GET[\'status\'])) && (base64_decode($_GET[\'status\'],true))) {\n\nif ((!isset($_GET[\'overview\'])) or (empty($_GET[\'overview\']))) {\n$analytics_return_tab = NULL;\n}\nelseif ($_GET[\'overview\'] === \'30-days\') {\n$analytics_return_tab = \'&amp;overview=30-days\';\n}\nelseif ($_GET[\'overview\'] === \'14-days\') {\n$analytics_return_tab = \'&amp;overview=14-days\';\n}\nelseif ($_GET[\'overview\'] === \'7-days\') {\n$analytics_return_tab = \'&amp;overview=7-days\';\n}\nelseif ($_GET[\'overview\'] === \'yesterday\') {\n$analytics_return_tab = \'&amp;overview=yesterday\';\n}\nelseif ($_GET[\'overview\'] === \'today\') {\n$analytics_return_tab = \'&amp;overview=today\';\n}\nelse {\n$analytics_return_tab = NULL;\n}\n\n$analytics_report_status = json_decode(base64_decode($_GET[\'status\']));\n\nif (!empty($analytics_report_status)) {\n$analytics_report_status_message = filter_var($analytics_report_status[0],FILTER_SANITIZE_STRING);\n$analytics_report_status_id = filter_var($analytics_report_status[1],FILTER_SANITIZE_NUMBER_INT);\n}\n\nif (!isset($_GET[\'saved-reports\'])) {\n$analytics_report_status_return_tab = NULL;\n}\nelse {\n$analytics_report_status_return_tab = \'&amp;saved-reports\';\n}\n\nif ((isset($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n$analytics_report_status_return_tab_saved_report_id = \'&amp;view=\'.intval($_GET[\'view\']).\'#analytics_tab\';\n}\nelse {\n$analytics_report_status_return_tab_saved_report_id = NULL;\n}\n\nif (in_array($analytics_report_status_message,array(\'success-updated\',\'success-created\',\'success-deleted\',\'success-comment-saved\'))) {\n\nif (!empty($analytics_report_status_id)) {\n$analytics_report_status_id = \'#\'.$analytics_report_status_id;\n}\n\nif ($analytics_report_status_message === \'success-created\') {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>saved to</strong> database.\';\n}\nelseif ($analytics_report_status_message === \'success-deleted\') {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>deleted from</strong> database.\';\n}\nelseif ($analytics_report_status_message === \'success-comment-saved\') {\n$analytics_report_status_message_type = \'The comment for report \'.$analytics_report_status_id.\' <strong>was saved</strong>.\';\n}\nelse {\n$analytics_report_status_message_type = \'The report \'.$analytics_report_status_id.\' <strong>updated to</strong> database.\';\n}\n\n$analytics_message = \'<p id=\"analytics_message\">Success! \'.$analytics_report_status_message_type.\' <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_return_tab.$analytics_report_status_return_tab.$analytics_report_status_return_tab_saved_report_id.\'\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\nelse {\n$analytics_message = \'<p id=\"analytics_message\">Error. \'.$analytics_report_status_id.\' was not processed. <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_report_status_return_tab.$analytics_report_status_return_tab_saved_report_id.\'\" rel=\"nofollow\">(Hide this)</a></p>\';\n}\n\nunset($analytics_report_status);\nunset($analytics_report_status_id);\n\n}\n\n}\nelse {\npagetop(\'Error | Google Analytics for Textpattern\');\n}\n\necho \'<style>\n#kuo_analytics_container { color: #333; position: relative; }\n#kuo_analytics_container h2, #kuo_analytics_container h3, #kuo_analytics_container h4, #kuo_analytics_container h5, #kuo_analytics_container h6 { font-family: Arial, Helvetica, sans-serif; font-size: xx-large; color: #333; padding-bottom: 0; margin-bottom: 0; }\n#analytics_navigation, #analytics_area ul { list-style: none; padding: 1% 0; margin: 0; font-size: 1.2em; line-height: 240%; }\n#analytics_area ul { font-size: 1em; }\n#analytics_area ul:nth-child(2) { font-size: 0.8em; }\n#analytics_area #analytics_detailed_navigation { padding: 1%; margin-bottom: 2%; background: #fcfcfc; border: 1px solid #f0f0f0; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_area #analytics_detailed_navigation dt { font-size: 1.2em; }\n#analytics_area #analytics_detailed_navigation li { line-height: 400%; }\n#kuo_analytics_container #analytics_navigation li, #kuo_analytics_container #analytics_area li { padding-top: 0; display: inline-block; float: none; clear: none; text-align: left; vertical-align: top; }\n#analytics_navigation a, #analytics_area ul a { text-decoration: none; background: #71BBCD; color: #3E3A3A; padding: 10px 20px; margin-right: 10px; border-bottom: 2px solid #3EA3B2; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_area ul a { background: #E5E5E5; color: #666; border-bottom-color: #999; }\n#analytics_navigation li:last-child { border-left: 1px solid #EFEBEB; margin-left: 10px; padding-left: 18px; }\n#analytics_navigation li:last-child a { margin-right: 0; background: #F99329; color: #fff; border-bottom-color: #D36A00; }\n#analytics_navigation a:hover, #analytics_navigation a:focus, #analytics_navigation a:active, #analytics_active_tab a, #analytics_area ul a:hover, #analytics_area ul a:focus, #analytics_area ul a:active { text-decoration: none; background: #5EC7D6; color: #202020; }\n#analytics_area ul a:hover, #analytics_area ul a:focus, #analytics_area ul a:active, #analytics_area ul .active_tab a { background: #ccc; color: #555; }\n#analytics_navigation li:last-child a:hover, #analytics_navigation li:last-child a:active, #analytics_navigation li:last-child a:focus, #analytics_navigation li:last-child#analytics_active_tab a { background: #EF8315; color: #ffc; }\n#kuo_analytics_container, #kuo_analytics_container legend { font-size: 1.2em; }\n#kuo_analytics_container form ul { list-style: none; padding: 2%; }\n#kuo_analytics_container li { padding-top: 20px; display: block; clear: both; }\n#kuo_analytics_container li span { display: block; border: 1px solid #f0f0f0; border-left-width: 5px; padding: 1%; margin-top: 10px; color: #666; margin-bottom: 3%; }\n#kuo_analytics_container button { padding: 10px 20px; font-size: 1.2em; margin-left: 210px; }\n#kuo_analytics_container label { padding-right: 10px; width: 210px; }\n#kuo_analytics_container label, #kuo_analytics_container select, #kuo_analytics_container button, #kuo_analytics_container input[type=checkbox] { cursor: pointer; }\n#kuo_analytics_container form li:last-child { text-align: right; }\n#analytics_by, #analytics_donate { text-align: center; color: #666; text-align: right; font-size: 0.85em; }\n#analytics_by a, #analytics_donate a { color: #666; text-decoration: underline; }\n#analytics_by a:hover, #analytics_by a:focus, #analytics_by a:active, #analytics_donate a:hover, #analytics_donate a:focus, #analytics_donate a:active { color: #444; }\n#analytics_health { list-style: none; padding: 0 0 3% 0; margin: 0; }\n#analytics_health li { padding-top: 5px; }\n.analytics_icon { font-weight: normal; font-size: 1.2em; margin-right: 5px; }\n.analytics_good { color: green; }\n.analytics_bad { color: red; }\n#analytics_message, .analytics_box, #analytics_reports { border: 1px solid #fcc; border-left-width: 5px; background: #fcfcfc; padding: 1%; margin-bottom: 1%; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_message a { float: right; color: #FF9191; }\n#analytics_message strong a { float: none; color: #c00; }\n.analytics_box, #analytics_reports { border-color: #ccc; margin-top: 1%; }\n#kuo_analytics_container .analytics_box p { padding-top: 0; margin-top: 0; }\n#kuo_analytics_container .analytics_box ul, #kuo_analytics_container .analytics_box ol { padding: 0 0 0 5%; margin: 0; }\n.analytics_box dd { margin-top: 2%; }\n#kuo_analytics_container .analytics_box ol li { padding: 0; margin: 0; }\n.analytics_highlight strong, #analytics_installation a { background: #A0CE9A; font-weight: normal; padding: 5px 8px; color: #152812; -moz-border-radius: 3px; border-radius: 3px; }\n.analytics_highlight strong a, #analytics_installation a { color: #152812; text-decoration: underline; }\n#analytics_installation a { text-align: center; background: #D1DFE5; color: #225066; display: block; padding-top: 10px; padding-bottom: 10px; }\n#kuo_analytics_header dl { background: #EF8315; padding: 5px; margin: 1% 0; -moz-border-radius: 3px; border-radius: 3px; }\n#kuo_analytics_header dt span { text-transform: uppercase; color: #ff0; float: right; }\n#kuo_analytics_header dt, #kuo_analytics_header dd { display: inline; color: #fff; margin: 0; padding: 0; }\n#kuo_analytics_summary dl { border: 1px solid #f0f0f0; border-left-width: 5px; background: #fcfcfc; padding: 2%; display: inline-block; width: 20%; margin: 1% 2% 0 0; -moz-border-radius: 3px; border-radius: 3px; }\n#kuo_analytics_summary dt { font-weight: normal; }\n#kuo_analytics_summary dd { font: 1.6em \"Georgia\", Sans; }\n#analytics_status_message { text-align: center; padding: 3% 0; font-size: 2em; }\n#kuo_analytics_container #analytics_reports { width: 100%; padding: 0; margin: 1% 0; border-collapse: collapse; -moz-border-radius: 3px; border-radius: 3px; }\n#analytics_reports th, #analytics_reports td { padding: 5px; vertical-align: top; }\n#analytics_no_reports td { padding: 4% 0; text-align: center; }\n#kuo_analytics_container .analytics_counter { border-right: 1px solid #ccc; text-align: center; }\n#analytics_reports th { border-bottom: 1px solid #ccc; }\n#analytics_reports th.analytics_empty_cell { border-bottom: 0; }\n#kuo_analytics_container .analytics_detailed_open { color: #999; }\n#kuo_analytics_container .analytics_detailed_open dd { display: none; }\n#analytics_detailed_navigation dt a { float: right; }\n#analytics_date_interval { margin-top: 0; padding-top: 0; }\n#kuo_analytics_container .analytics_error { padding-left: 2%; }\n#kuo_analytics_container .analytics_error dd { padding: 1% 0 0 0; margin: 0; }\n#kuo_analytics_container form li ul { padding: 0; margin: 0; display: inline; text-align: left; }\n#kuo_analytics_container form li ul li:last-child { text-align: left; }\n#kuo_analytics_container form li ul li { text-align: left; display: block; padding: 0 0 0 25%; }\n#kuo_analytics_container form li ul label, #kuo_analytics_container form li ul input { float: none; width: auto; vertical-align: middle; text-align: left; padding: 0 0 0 10px; display: inline; margin: 0; }\n#kuo_analytics_container textarea { max-width: 800px; width: 60%; min-width: 250px; }\n#kuo_analytics_container #analytics_tab form li { display: inline-block; }\n#kuo_analytics_container #analytics_tab form li:first-child { width: 85%; }\n#kuo_analytics_container #analytics_tab form li button { margin: 0; }\n#kuo_analytics_container .analytics_only_admin { padding: 0; position: absolute; right: 5px; top: -5px; border: 0; background: none; }\n#kuo_analytics_container .analytics_only_admin p { padding: 5px 10px; margin: 0; background: #F1F3F3; -moz-border-radius: 3px; border-radius: 3px; border-bottom: 2px solid #CFD7D7; color: #555; }\n</style>\n<div id=\"kuo_analytics_container\">\n\'.$analytics_message.\'\n<h2>Google Analytics for Textpattern</h2><ul id=\"analytics_navigation\"><li\';\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics\" rel=\"chapter nofollow\">Settings</a></li><li\';\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=status\" rel=\"chapter nofollow\">Status</a></li><li\';\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\necho KUO_ANALYTICS_ACTIVE_ID;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\" rel=\"chapter nofollow\">Statistics</a></li></ul>\n<div id=\"kuo_analytics_middle\">\';\n\n# BEGINNING: CERTAIN SECTIONS ARE SHOWN ONLY TO PUBLISHERS\n$analytics_current_user = is_logged_in();\n\nif ($analytics_current_user[\'privs\'] != 1) {\n\nif ((!isset($_GET[\'analytics_tab\'])) or (empty($_GET[\'analytics_tab\']))) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\')) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelseif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\')) {\n$analytics_is_not_allowed_to_view = 0;\n}\nelse {\n$analytics_is_not_allowed_to_view = 1;\n}\n\nif (empty($analytics_is_not_allowed_to_view)) {\necho \'<div class=\"analytics_box analytics_error\"><dl><dt>Error</dt><dd>Only users who have Publisher privileges can view this section.</dd></dl></div>\';\n}\n\n}\nelse {\n$analytics_is_not_allowed_to_view = 1;\n}\n# ENDING: CERTAIN SECTION ARE SHOWN ONLY TO PUBLISHERS\n\n# BEGINNING: MAIN TAB WHERE VIEW ID IS SET, ETC.\nif ((!isset($_GET[\'analytics_tab\'])) && (!empty($analytics_is_not_allowed_to_view))) {\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\')) {\ndefine(\'ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS\',0);\n}\n\n# OTHER SET:\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS\',0);\n}\n\nif (!defined(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\')) {\ndefine(\'ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS\',0);\n}\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<form action=\"./index.php?event=kuo_analytics\" method=\"post\">\n<fieldset>\n<legend>Settings</legend>\n<ul>\n<li><label for=\"analytics_view_id\">Analytics View ID:</label><input type=\"text\" name=\"analytics_view_id\" id=\"analytics_view_id\" size=\"15\" maxlength=\"11\" value=\"\'.$analytics_view_id.\'\" title=\"Analytics View ID\" /><span>Should be a numerical value. You can find the View ID by signing in to your Analytics account, selecting Admin tab and then choosing the desired Analytics account from &quot;Account&quot; column, after which select the desired property from &quot;Property&quot; column, and finally clicking under &quot;Property&quot; option called &quot;Tracking Info&quot;. (<a href=\"https://support.google.com/analytics/answer/1032385?hl=en\" rel=\"nofollow external\" target=\"_new\">Help</a>)</span></li>\n<li><label for=\"analytics_language\">Preferred language:</label><select name=\"analytics_language\" id=\"analytics_language\" title=\"Plugin language\"><option\';\nif ((isset($analytics_language)) && ($analytics_language === \'en\')) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \' value=\"en\">English</option></select><span>Currently only English is available. Other language options are coming later.</span></li>\n\n<li><label for=\"analytics_datetime\">Date &amp; time format:</label><select name=\"analytics_datetime\" id=\"analytics_datetime\">\n<option value=\"1\"\';\nif ((isset($analytics_datetime)) && (empty($analytics_datetime)) or ($analytics_datetime == 1)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'Y-m-d H:i\').\'</option>\n<option value=\"2\"\';\nif ((isset($analytics_datetime)) && ($analytics_datetime == 2)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'n/j/Y H:i\').\'</option>\n<option value=\"3\"\';\nif ((isset($analytics_datetime)) && ($analytics_datetime == 3)) {\necho KUO_ANALYTICS_SELECTED_HTML;\n}\necho \'>\'.date(\'j.n.Y H.i\').\'</option>\n</select><span>Date and time format, which is shown for example in the Saved reports secton.</span></li>\n\n<li><label>Show <strong>detailed reports</strong> to Publishers and:</label><ul>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_1\" value=\"managing-editors\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_MANAGING_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_1\">Managing Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_2\" value=\"copy-editors\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_COPY_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_2\">Copy Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_3\" value=\"staff-writers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_STAFF_WRITERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_3\">Staff Writers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_4\" value=\"freelancers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_FREELANCERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_4\">Freelancers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_detailed[]\" id=\"analytics_show_detailed_5\" value=\"designers\" \';\nif (ANALYTICS_SHOW_DETAILED_GROUPS_DESIGNERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_detailed_5\">Designers</label></li>\n</ul><span>This functionality can be limited to certain user groups.</span></li>\n\n<li><label>Show <strong>saved reports</strong> to Publishers and:</label><ul>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_1\" value=\"managing-editors\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_MANAGING_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_1\">Managing Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_2\" value=\"copy-editors\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_COPY_EDITORS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_2\">Copy Editors</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_3\" value=\"staff-writers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_STAFF_WRITERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_3\">Staff Writers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_4\" value=\"freelancers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_FREELANCERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_4\">Freelancers</label></li>\n<li><input type=\"checkbox\" name=\"analytics_show_saved[]\" id=\"analytics_show_saved_5\" value=\"designers\" \';\nif (ANALYTICS_SHOW_SAVED_GROUPS_DESIGNERS == 1) {\necho KUO_ANALYTICS_CHECKED_HTML;\n}\necho \'/><label for=\"analytics_show_saved_5\">Designers</label></li>\n</ul><span>This functionality can be limited to certain user groups.</span></li>\n\n<li><button type=\"submit\" name=\"submit\">Save</button></li>\n</ul>\n</fieldset>\n</form>\';\n\n}\n# ENDING: MAIN TAB WHERE VIEW ID IS SET, ETC.\n# BEGINNING: STATUS TAB WHERE HEALTH CHECK IS\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'status\') && (!empty($analytics_is_not_allowed_to_view))) {\n\n$analytics_health_good = \'<strong class=\"analytics_icon analytics_good\">&#9745;</strong> \';\n$analytics_health_bad = \'<strong class=\"analytics_icon analytics_bad\">&#9744;</strong> \';\n\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<h3>Status</h3>\n<div class=\"analytics_box\">\n<p>Checking on the surface if certain required bits and pieces are in place. Something is likely wrong if you don\\\'t see green checkboxes.</p>\n<ul id=\"analytics_health\">\';\n\nif (is_dir(\'../kuo_analytics/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/src/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/</code> does not exist</li>\';\n}\n\nif (is_dir(\'../kuo_analytics/google-api-php-client/src/Google/\')) {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/Google/</code> exists</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/</code> does not exist</li>\';\n}\n\nif (file_exists(\'../kuo_analytics/google-api-php-client/src/Google/autoload.php\')) {\n\nif (filesize(\'../kuo_analytics/google-api-php-client/src/Google/autoload.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/google-api-php-client/src/Google/autoload.php</code> does not exist</li>\';\n}\n\n# BEGINNING: CHECKING IF INDEX.PHP EXISTS\nif (file_exists(\'../kuo_analytics/index.php\')) {\n\nif (filesize(\'../kuo_analytics/index.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/index.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/index.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/index.php</code> does not exist</li>\';\n}\n# ENDS: CHECKING IF INDEX.PHP EXISTS\n\n# BEGINNING: CHECKING IF KUO_ANALYTICS_OVERVIEW.PHP EXISTS\nif (file_exists(\'../kuo_analytics/kuo_analytics_overview.php\')) {\n\nif (filesize(\'../kuo_analytics/kuo_analytics_overview.php\') == 0) {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\'<code>../kuo_analytics/kuo_analytics_overview.php</code> does not exist</li>\';\n}\n# ENDS: CHECKING IF KUO_ANALYTICS_OVERVIEW.PHP EXISTS\n\nif ($current_analytics_settings) {\n\nif ($current_analytics_settings[\'view_id\']) {\necho \'<li>\'.$analytics_health_good.\' Analytics View ID exists in database</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' Analytics View ID does not exist in database</li>\';\n}\n\nif (file_exists(\'../kuo_analytics/\'.$current_analytics_settings[\'auth_file\'].\'.json\')) {\n\nif (filesize(\'../kuo_analytics/\'.$current_analytics_settings[\'auth_file\'].\'.json\') == 0) {\necho \'<li>\'.$analytics_health_bad.\' JSON authorization file exists, but is empty</li>\';\n}\nelse {\necho \'<li>\'.$analytics_health_good.\' JSON authorization file exists</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' JSON authorization file does not exist</li>\';\n}\n\n}\nelse {\necho \'<li>\'.$analytics_health_bad.\' Analytics View ID does not exist in database</li>\';\n}\n\necho \'</ul></div>\';\n\n}\n# ENDING: STATUS TAB WHERE HEALTH CHECK IS\n# BEGINNING: HELPING TO INSTALL THIS PLUGIN\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'install\') && (!empty($analytics_is_not_allowed_to_view))) {\n\n$current_analytics_settings = safe_row(\'*\',\'kuo_analytics\',\'id=1\');\n\necho KUO_ANALYTICS_ONLY_PUBLISHER_NOTIFICATION.\'<h3>Install</h3>\n<div class=\"analytics_box\">\n<dl>\n<dt>JSON Auth File</dt>\n<dd>So that this plugin could fetch data from your Google Analytics account, a special JSON authorization file should be placed inside folder <code>../kuo_analytics/</code>. Please make sure that folder has the JSON authorization file called:</dd>\';\n\nif (empty($current_analytics_settings[\'auth_file\'])) {\n$current_analytics_auth_file = md5(strrev(date(\'H:i:s\')));\n\nsafe_update(\'kuo_analytics\',\"auth_file=$current_analytics_auth_file\",\"id=1\",1);\n\n}\nelse {\n$current_analytics_auth_file = $current_analytics_settings[\'auth_file\'];\n}\n\necho \'<dd><code>\'.$current_analytics_settings[\'auth_file\'].\'.json</code></dd></dl>\';\n\nif (!file_exists(\'../kuo_analytics/\'.filter_var($current_analytics_settings[\'auth_file\'],FILTER_SANITIZE_STRING).\'.json\')) {\necho \'<dl class=\"analytics_error\"><dt>JSON Auth File Status</dt><dd>&cross; Please fix this! Looks like the JSON file <strong>is not there</strong>. Data from Google Analytics can not be fetched.</dd></dl>\';\n}\nelse {\necho \'<dl class=\"analytics_ok\"><dt>JSON Auth File Status</dt><dd>&check; Great! Looks like the JSON file is in place. Data from Google Analytics should be possible to fetch.</dd></dl>\';\n}\n\necho \'</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>Requirements and installation in general</dt>\n<dd>To install this plugin, you should upload a folder called <code>kuo_analytics</code> with all the files that come with it inside your website\\s root (somewhere like: <code>\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/kuo_analytics/</code>) and upload inside that folder the above mentioned JSON file. Besides these files, you should log in to <a href=\"https://console.developers.google.com/\" rel=\"nofollow external\" target=\"_new\">Google Developers Console</a> and create a new project, which has Analytics API activated. After activating that API for your newly created project, it\\\'s time to make new credentials for the project.</dd>\n<dd class=\"analytics_highlight\"><strong>See these screenshots to get a better understanding of what to do in Google Developers Console: <a href=\"http://kuopassa.net/images/475.png\" rel=\"nofollow external\" target=\"_new\">screenshot 1</a>, <a href=\"http://kuopassa.net/images/474.png\" rel=\"nofollow external\" target=\"_new\">screenshot 2</a>.</strong></dd>\n</dl>\n</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>What you need</dt>\n<dd><ol>\n<li><strong>[easy to do]</strong> Textpattern account with Publisher, Managing Editor or Copy Editor privileges.</li>\n<li><strong>[easy to do]</strong> Google Analytics account with privileges to view Google Analytics data.</li>\n<li><strong>[easy to do]</strong> Plugin <code>kuo_analytics</code> installed and activated.</li>\n<li><strong>[easy to do]</strong> Folder <code>kuo_analytics</code>, including files that come with it, uploaded to your website\\\'s root.</li>\n<li><strong>[could be confusing]</strong> In Google Console a new project with Analytics API activated.</li>\n<li><strong>[could be confusing]</strong> JSON authorization file (from the previously mentioned Google Developers Console project) uploaded inside folder <code>kuo_analytics</code>.</li>\n</ol></dd>\n</dl>\n</div>\n<div class=\"analytics_box\">\n<dl>\n<dt>X happened, now what?</dt>\n<dd>\n<ul>\n<li><strong>Google asks to allow &quot;offline use&quot;. Why?</strong> It\\\'s about logging in to your Google account in a way that you can view your Google Analytics statistics.</li>\n<li><strong>There is an error &quot;Error: redirect_uri_mismatch&quot;.</strong> In that case the URL, which you\\\'ve saved to your Google Developers Console project, is not correct. The URL should be something like <code>http://www.yoursite.com/kuo_analytics/kuo_analytics_overview.php</code>.</li>\n<li><strong>I don\\\'t see any statistics.</strong> Do you have privileges to view Google Analytics statistics for the View ID you\\\'ve saved to this plugin\\\'s settings? You can\\\'t open stats from any Google Analytics account: only from those which are either yours, or from such accounts where the account owner has allowed your Google account to view them.</li>\n<li><strong>What does property name &quot;All Website Data&quot; mean?</strong> That\\\'s just the default English name for the View ID. You can change that by logging in to Google Analytics at <a href=\"http://www.google.com/analytics/\" rel=\"nofollow external\" target=\"_new\">www.google.com/analytics</a> and selecting the property which you want to edit. Under &quot;View Settings&quot;, where the View ID is located as well, should be a text field to which you can type something more suitable.</li>\n</ul>\n</dd>\n</dl>\n</dvi>\';\n\n}\n# ENDING: HELPING TO INSTALL THIS PLUGIN\n# BEGINNING: STATISTICS SECTION\nif ((isset($_GET[\'analytics_tab\'])) && ($_GET[\'analytics_tab\'] === \'statistics\')) {\n\n$active_tab = \' class=\"active_tab\"\';\n\n# WHAT SUBPAGE USER IS TO GET PROPER URL FOR AJAX QUERY\nif ((!isset($_GET[\'overview\'])) or (empty($_GET[\'overview\']))) {\n$analytics_query_period = \'last-month\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'14-days\')) {\n$analytics_query_period = \'last-two-weeks\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'7-days\')) {\n$analytics_query_period = \'last-week\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'yesterday\')) {\n$analytics_query_period = \'yesterday\';\n}\nelseif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'today\')) {\n$analytics_query_period = \'today\';\n}\nelse {\n$analytics_query_period = \'last-month\';\n}\n\n# COUNTING AMOUNT OF SAVED REPORTS\nif (safe_count(\'kuo_analytics_reports\',\"id\") != 0) {\n$analytics_amount_of_saved_reports = \' (\'.safe_count(\'kuo_analytics_reports\',\"id\").\')\';\n}\nelse {\n$analytics_amount_of_saved_reports = NULL;\n}\n\necho \'<h3>Statistics</h3>\n<div id=\"analytics_area\">\n<ul>\n<li\';\nif ((!isset($_GET[\'overview\'])) && (!isset($_GET[\'saved-reports\']))) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\" rel=\"chapter nofollow\">Past 30 days</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'14-days\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=14-days\" rel=\"chapter nofollow\">Past two weeks</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'7-days\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=7-days\" rel=\"chapter nofollow\">Past week</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'yesterday\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=yesterday\" rel=\"chapter nofollow\">Yesterday</a></li>\n<li\';\nif ((isset($_GET[\'overview\'])) && ($_GET[\'overview\'] === \'today\')) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;overview=today\" rel=\"chapter nofollow\">Today</a></li>\';\nif (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1) {\necho \'<li\';\nif (isset($_GET[\'saved-reports\'])) {\necho $active_tab;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"chapter nofollow\">Saved reports\'.$analytics_amount_of_saved_reports.\'</a></li>\';\n}\necho \'</ul>\';\n\nif ((isset($_GET[\'overview\'])) && (!empty($_GET[\'overview\']))) {\n$analytics_detailed_tab = \'&amp;overview=\'.filter_var($_GET[\'overview\'],FILTER_SANITIZE_STRING);\n}\nelse {\n$analytics_detailed_tab = NULL;\n}\n\nif (!isset($_GET[\'saved-reports\'])) {\n\nif ((isset($_SERVER[\"QUERY_STRING\"])) && (!empty($_SERVER[\"QUERY_STRING\"])) && (isset($_GET[\'analytics_tab\'])) && (!empty($_GET[\'analytics_tab\']))) {\n\n# CHECKING IF DETAILED REPORT BOX IS SAVED OPEN OR CLOSED\nif (safe_count(\'kuo_analytics\',\"detailed_toggle=\'1\'\",0) == 1) {\n$analytics_show_hide_text = \'Close\';\n}\nelse {\n$analytics_show_hide_text = \'Open\';\n}\n\n$analytics_show_hide_detailed = \' <a href=\"./index.php?\'.$_SERVER[\"QUERY_STRING\"].\'&amp;toggle\" rel=\"nofollow\">\'.$analytics_show_hide_text.\'</a>\';\n\n# AND IF TOGGLE IS CLICKED, LETS SAVE IT\nif ((isset($_GET[\'toggle\'])) && (empty($_GET[\'toggle\']))) {\n\nif (safe_count(\'kuo_analytics\',\"detailed_toggle=\'1\'\",0) == 1) {\nsafe_update(\'kuo_analytics\',\"detailed_toggle=0\",\"id=1\",0);\n}\nelse {\nsafe_update(\'kuo_analytics\',\"detailed_toggle=1\",\"id=1\",0);\n}\n\nheader(\'location:./index.php?\'.str_replace(\'&toggle\',\'\',$_SERVER[\"QUERY_STRING\"]));\ndie;\n\n}\n\n}\nelse {\n$analytics_show_hide_detailed = NULL;\n}\n\n$analytics_detailed_link = \' class=\"active_tab\"\';\n\n# BEGINNING: IF ALLOWED TO VIEW DETAILED REPORTS\nif (KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS == 1) {\n\necho \'<dl id=\"analytics_detailed_navigation\" class=\"analytics_detailed_\'.strtolower($analytics_show_hide_text).\'\">\n<dt>Detailed reports\'.$analytics_show_hide_detailed.\'</dt>\n<dd><ul>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'sessions-by-country\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=sessions-by-country\" rel=\"nofollow bookmark\">Sessions by country</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'users-and-pageviews\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=users-and-pageviews\" rel=\"nofollow bookmark\">Users and pageviews</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'mobile-traffic\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=mobile-traffic\" rel=\"nofollow bookmark\">Mobile traffic</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'revenue-generating-campaigns\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=revenue-generating-campaigns\" rel=\"nofollow bookmark\">Revenue generating campaigns</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'new-vs-returning-sessions\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=new-vs-returning-sessions\" rel=\"nofollow bookmark\">New vs. returning sessions</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'adsense\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=adsense\" rel=\"nofollow bookmark\">AdSense</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'browser-and-operating-system\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=browser-and-operating-system\" rel=\"nofollow bookmark\">Browser and OS</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'time-on-site\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=time-on-site\" rel=\"nofollow bookmark\">Time on site</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'all-traffic-sources-usage\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=all-traffic-sources-usage\" rel=\"nofollow bookmark\">All traffic: usage</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'referring-sites\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=referring-sites\" rel=\"nofollow bookmark\">Referring sites</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines\" rel=\"nofollow bookmark\">Search engines</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines-organic\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines-organic\" rel=\"nofollow bookmark\">Search engines: organic</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'search-engines-paid\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=search-engines-paid\" rel=\"nofollow bookmark\">Search engines: paid</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'keywords\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=keywords\" rel=\"nofollow bookmark\">Keywords</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-content\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-content\" rel=\"nofollow bookmark\">Top content</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-landing-pages\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-landing-pages\" rel=\"nofollow bookmark\">Top landing pages</a></li>\n<li\';\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'top-exit-pages\')) {\necho $analytics_detailed_link;\n}\necho \'><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics\'.$analytics_detailed_tab.\'&amp;detailed=top-exit-pages\" rel=\"nofollow bookmark\">Top exit pages</a></li>\n</ul></dd>\n</dl>\';\n}\n\n}\n# ENDING: IF ALLOWED TO VIEW DETAILED REPORTS\n\n# BEGINNING: IF SAVED REPORTS\nif ((!isset($_GET[\'overview\'])) && (isset($_GET[\'saved-reports\'])) && (empty($_GET[\'saved-reports\'])) && (!isset($_GET[\'detailed\'])) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\n# BEGINNING: CHECKING IF REPORT IS TO BE STARRED / UNSTARRED\nif ((isset($_GET[\'star\'])) && (!empty($_GET[\'star\'])) && (base64_decode($_GET[\'star\'],true))) {\n\n$star_saver_id = json_decode(base64_decode($_GET[\'star\']));\n\nif ($star_saver_id[0] == 1) {\nsafe_update(\'kuo_analytics_reports\',\"starred=1\",\"id=\'$star_saver_id[1]\'\",0);\n}\nelse {\nsafe_update(\'kuo_analytics_reports\',\"starred=0\",\"id=\'$star_saver_id[1]\'\",0);\n}\n\nheader(\'location: ./index.php?event=kuo_analytics&analytics_tab=statistics&saved-reports\');\ndie;\n\n}\n# ENDING: CHECKING IF REPORT IS TO BE STARRED / UNSTARRED\n\nif ((isset($_GET[\'order\'])) && (!empty($_GET[\'order\'])) && ($_GET[\'order\'] === \'uid\') && (isset($_GET[\'reverse\'])) && (empty($_GET[\'reverse\']))) {\n$analytics_order_link = NULL;\n$analytics_order_symbol = \'&duarr;\';\n$analytics_order_query = \'`id` ASC\';\n}\nelse {\n$analytics_order_link = \'&amp;order=uid&amp;reverse\';\n$analytics_order_symbol = \'&udarr;\';\n$analytics_order_query = \'`id` DESC\';\n}\n\necho \'<table id=\"analytics_reports\"><thead><tr><th class=\"analytics_empty_cell\">&nbsp;</th><th>&starf;</th><th>UID <a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\'.$analytics_order_link.\'\" rel=\"nofollow\">\'.$analytics_order_symbol.\'</a></th><th>View ID</th><th>Property name</th><th>Report name</th><th>Saved</th><th title=\"Starting approximately\">Starting ~</th><th title=\"Ending approximately\">Ending ~</th><th>&nbsp;</th></tr></thead><tbody>\';\n\n# COUNTING IF REPORTS ARE SAVED\nif (safe_count(\'kuo_analytics_reports\',\"id\") != 0) {\n\nif (safe_count(\'kuo_analytics_reports\',\"id\") == 1) {\n$report_heading = \'One saved report\';\n}\nelse {\n$report_heading = safe_count(\'kuo_analytics_reports\',\"id\").\' saved reports\';\n}\n\n# CHECKING WHAT DATETIME (AND LATER MAYBE OTHER SETTINGS, LIKE LANGUAGE) USER WANTS\n$analytics_preferences = safe_row(\'datetime_format\',\'kuo_analytics\',\"id=\'1\'\",0);\n\n# BEGINNING: FORMATTING DATETIME\nif (($analytics_preferences[\'datetime_format\'] == 1) or (empty($analytics_preferences[\'datetime_format\']))) {\n$analytics_datetime_format_full = \'Y-m-d H:i\';\n$analytics_datetime_format_short = \'Y-m-d\';\n}\nelseif ($analytics_preferences[\'datetime_format\'] == 2) {\n$analytics_datetime_format_full = \'n/j/Y H:i\';\n$analytics_datetime_format_short = \'n/j/Y\';\n}\nelseif ($analytics_preferences[\'datetime_format\'] == 3) {\n$analytics_datetime_format_full = \'j.n.Y H.i\';\n$analytics_datetime_format_short = \'j.n.Y\';\n}\nelse {\n$analytics_datetime_format_full = \'j.n.Y H.i\';\n$analytics_datetime_format_short = \'Y-m-d\';\n}\n# ENDING: FORMATTING DATETIME\n\necho \'<h3>\'.$report_heading.\'</h3>\';\n\n$analytics_report_list = safe_rows(\'id,starred,view_id,created,property_name,report,start_date,end_date\',\'kuo_analytics_reports\',\'`id` IS NOT NULL AND `view_id` IS NOT NULL AND `created` IS NOT NULL AND `property_name` IS NOT NULL AND `report` IS NOT NULL AND `start_date` IS NOT NULL AND `end_date` IS NOT NULL ORDER BY \'.$analytics_order_query,0);\n\nif ($analytics_report_list) {\n\n$analytics_list_counter = 1;\n\nforeach ($analytics_report_list as $report_item) {\n\nif (is_numeric($report_item[\'id\'])) {\n$report_item_row_id = filter_var($report_item[\'id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_row_id = filter_var($report_item[\'id\'],FILTER_SANITIZE_STRING);\n}\n\nif (is_numeric($report_item[\'view_id\'])) {\n$report_item_view_id = filter_var($report_item[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_view_id = filter_var($report_item[\'view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif ((is_numeric($report_item[\'starred\'])) && (!empty($report_item[\'starred\']))) {\n$report_item_row_starred = base64_encode(json_encode(array(0,$report_item_row_id)));\n$report_item_row_starred_icon = \'&starf;\';\n}\nelse {\n$report_item_row_starred = base64_encode(json_encode(array(1,$report_item_row_id)));\n$report_item_row_starred_icon = \'&star;\';\n}\n\nif (is_numeric($report_item[\'property_name\'])) {\n$report_item_property_name = filter_var($report_item[\'property_name\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_property_name = filter_var($report_item[\'property_name\'],FILTER_SANITIZE_STRING);\n}\n\n$report_item_saved = date($analytics_datetime_format_full,strtotime(filter_var($report_item[\'created\'],FILTER_SANITIZE_STRING)));\n$report_item_start_date = date($analytics_datetime_format_short,strtotime(filter_var($report_item[\'start_date\'],FILTER_SANITIZE_STRING)));\n$report_item_end_date = date($analytics_datetime_format_short,strtotime(filter_var($report_item[\'end_date\'],FILTER_SANITIZE_STRING)));\n\n$report_item_actual_report = unserialize($report_item[\'report\']);\n\nif (is_array($report_item_actual_report)) {\n$report_item_report_name = filter_var($report_item_actual_report[\'Title\'],FILTER_SANITIZE_STRING);\n\nif (strlen($report_item_report_name) >= 20) {\n$report_item_report_name = substr($report_item_report_name,0,19).\'&hellip;\';\n}\n\n}\nelse {\n$report_item_report_name = \'Unknown\';\n}\n\necho \'<tr><td class=\"analytics_counter\">\'.$analytics_list_counter++.\'</td><td><a href=\"./index.php?event=kuo_analytics&analytics_tab=statistics&amp;saved-reports&amp;star=\'.$report_item_row_starred.\'\" rel=\"nofollow\">\'.$report_item_row_starred_icon.\'</a></td><td>\'.$report_item_row_id.\'</td><td>\'.$report_item_view_id.\'</td><td>\'.$report_item_property_name.\'</td><td><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;view=\'.$report_item_row_id.\'#analytics_tab\" rel=\"nofollow bookmark\">\'.$report_item_report_name.\'</a></td><td>\'.$report_item_saved.\'</td><td>\'.$report_item_start_date.\'</td><td>\'.$report_item_end_date.\'</td><td><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;delete=\'.$report_item_row_id.\'\" rel=\"nofollow\">Delete</a></td></tr>\';\n}\n}\n\n}\nelse {\necho \'<tr id=\"analytics_no_reports\"><td class=\"analytics_counter\">&nbsp;</td><td colspan=\"9\">No reports.</td></tr>\';\n}\n\nunset($analytics_list_counter);\nunset($report_item_view_id);\nunset($report_item_property_name);\nunset($report_item_report_name);\nunset($report_item_saved);\nunset($report_item_start_date);\nunset($report_item_end_date);\n\necho \'</tbody></table>\';\n\n# BEGINNING: IF VIEWING A SINGLE SAVED REPORT\nif ((isset($_GET[\'view\'])) && (!empty($_GET[\'view\'])) && (is_numeric($_GET[\'view\']))) {\n\n$analytics_view_report_id = filter_var($_GET[\'view\'],FILTER_SANITIZE_NUMBER_INT);\n$analytics_report_single = safe_row(\'*\',\'kuo_analytics_reports\',\"id=\'$analytics_view_report_id\'\",0);\n\n# SOME CODE REPEATING, BUT SINGLE REPORT NEEDS THIS\n$report_item_view_starred = filter_var($analytics_report_single[\'starred\'],FILTER_SANITIZE_NUMBER_INT);\n\nif (empty($report_item_view_starred)) {\n$report_item_view_starred = \'&star; \';\n}\nelse {\n$report_item_view_starred = \'&starf; \';\n}\n\nif (is_numeric($analytics_report_single[\'view_id\'])) {\n$report_item_view_id = filter_var($analytics_report_single[\'view_id\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_view_id = filter_var($analytics_report_single[\'view_id\'],FILTER_SANITIZE_STRING);\n}\n\nif (is_numeric($analytics_report_single[\'property_name\'])) {\n$report_item_property_name = filter_var($analytics_report_single[\'property_name\'],FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_property_name = filter_var($analytics_report_single[\'property_name\'],FILTER_SANITIZE_STRING);\n}\n\n$report_item_actual_report = unserialize($analytics_report_single[\'report\']);\n\nif (is_array($report_item_actual_report)) {\n$report_item_report_name = filter_var($report_item_actual_report[\'Title\'],FILTER_SANITIZE_STRING);\n}\nelse {\n$report_item_report_name = \'Unknown\';\n}\n\n$report_item_repot_comment = $analytics_report_single[\'comment\'];\n\nif (is_numeric($report_item_repot_comment)) {\n$report_item_repot_comment = filter_var($report_item_repot_comment,FILTER_SANITIZE_NUMBER_INT);\n}\nelse {\n$report_item_repot_comment = filter_var($report_item_repot_comment,FILTER_SANITIZE_STRING);\n}\n\n$report_item_start_date = date($analytics_datetime_format_short,strtotime(filter_var($analytics_report_single[\'start_date\'],FILTER_SANITIZE_STRING)));\n$report_item_end_date = date($analytics_datetime_format_short,strtotime(filter_var($analytics_report_single[\'end_date\'],FILTER_SANITIZE_STRING)));\n$report_item_modified = date($analytics_datetime_format_full,strtotime(filter_var($analytics_report_single[\'modified\'],FILTER_SANITIZE_STRING)));\n\necho \'<div id=\"analytics_tab\">\n<div id=\"kuo_analytics_widget\">\n<div id=\"kuo_analytics_header\">\n<dl>\n<dt><span>Saved report #\'.$analytics_view_report_id.\'</span> Property name: &quot;\'.$report_item_property_name.\'&quot;.</dt>\n<dd>View ID: ga:\'.$report_item_view_id.\'</dd>\n</dl>\n<p><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports\" rel=\"nofollow\">Close this report</a></p>\n</div>\n<div id=\"kuo_analytics_summary\">\n<h3>\'.$report_item_view_starred.$report_item_report_name.\'</h3>\';\n\n# WRITE A COMMENT TO THIS SAVED REPORT\necho \'<form action=\"./index.php?event=kuo_analytics&amp;analytics_tab=statistics&amp;saved-reports&amp;view=\'.$analytics_view_report_id.\'\" method=\"post\">\n<input type=\"hidden\" name=\"analytics_comment_source\" value=\"\'.$analytics_view_report_id.\'\" />\n<fieldset>\n<legend>Comments for this saved report</legend>\n<ul>\n<li><label for=\"analytics_comments\">Comments (plain text only):</label><textarea id=\"analytics_comments\" name=\"analytics_comments\" rows=\"4\" cols=\"60\" placeholder=\"Write a comment here.\">\'.$report_item_repot_comment.\'</textarea></li>\n<li><button type=\"submit\" name=\"submit\">Save comments</button></li>\n</ul>\n</fieldset>\n</form>\';\n\necho \'<p id=\"analytics_date_interval\"><em>From \'.$report_item_start_date.\' to \'.$report_item_end_date.\'. Modified \'.$report_item_modified.\'.</em></p>\';\n\n# IF REPORT STATUS = 1, IT\'S BASIC\n# IF REPORT STATUS = 2, IT\'S DETAILED\n\n# BEGINNING: BASIC REPORT\nif ((empty($analytics_report_single[\'type\'])) or ($analytics_report_single[\'type\'] == 1) && (KUO_ALLOW_ACCESS_TO_SAVED_REPORTS == 1)) {\n\nforeach (array_slice($report_item_actual_report,1) as $analytics_report_heading => $analytics_report_value) {\n\n$analytics_report_heading = filter_var($analytics_report_heading,FILTER_SANITIZE_STRING);\n$analytics_report_value = filter_var($analytics_report_value,FILTER_SANITIZE_STRING);\n\n# ADDING SOME VERBAL INSTRUCTIONS TO SOME FIELDS\nif ($analytics_report_heading === \'Avg. time on site\') {\n$analytics_report_value = $analytics_report_value.\' minutes\';\n}\nelseif ($analytics_report_heading === \'Bounce rate\') {\n$analytics_report_value = $analytics_report_value.\' %\';\n}\nelseif (($analytics_report_heading === \'Avg. page load time\') or ($analytics_report_heading === \'Avg. server response time\')) {\n$analytics_report_value = $analytics_report_value.\' seconds\';\n}\n\necho \'<dl><dt>\'.$analytics_report_heading.\'</dt><dd>\'.$analytics_report_value.\'</dd></dl>\';\n}\n\n}\nelse {\n# ENDING: BASIC REPORT\n# BEGINNING: DETAILED REPORT\n\necho \'<table id=\"analytics_reports\"><thead>\';\nforeach (array_splice($report_item_actual_report,1) as $table_data) {                                     \n\n$table_header_array = $table_data[0];\n\nforeach ($table_header_array as $table_header) {\necho \'<th>\'.filter_var($table_header,FILTER_SANITIZE_STRING).\'</th>\';\n}\n\nunset($table_header);\n\n}\necho \'</thead><tbody>\';\n\n# SINGLE DATA CELLS INSIDE <TR></TR> PAIRS\n# WELCOME TO WORLD OF HURT\n# THREE FOREACH...\n\nforeach (array_splice($table_data,1) as $table_data) {\n\nforeach ($table_data as $table_data2) {\necho \'<tr>\';\n\nforeach ($table_data2 as $table_data3) {\necho \'<td>\'.$table_data3.\'</td>\';\n}\n\necho \'</tr>\';\n}\n\n}\n\necho \'</tbody></table>\';\n}\n# ENDING: DETAILED REPORT\n\necho \'</div></div>\';\n\n}\n# ENDING: IF VIEWING A SINGLE SAVED REPORT\n\n}\n# ENDING: IF SAVED REPORTS\n\n\n# BEGINNING: IF AJAX STATISTICS\nelse {\n\n# BEGINNING: IF DETAILED REPORTS\nif ((isset($_GET[\'detailed\'])) && (!empty($_GET[\'detailed\'])) && (KUO_ALLOW_ACCESS_TO_DETAILED_REPORTS == 1)) {\n\n$analytics_detailed = \'&detailed=\';\n\nif ((isset($_GET[\'detailed\'])) && ($_GET[\'detailed\'] === \'sessions-by-country\')) {\n$analytics_detailed .= 1;\n}\nelseif ($_GET[\'detailed\'] == \'users-and-pageviews\') {\n$analytics_detailed .= 2;\n}\nelseif ($_GET[\'detailed\'] === \'mobile-traffic\') {\n$analytics_detailed .= 3;\n}\nelseif ($_GET[\'detailed\'] === \'revenue-generating-campaigns\') {\n$analytics_detailed .= 4;\n}\nelseif ($_GET[\'detailed\'] === \'new-vs-returning-sessions\') {\n$analytics_detailed .= 5;\n}\nelseif ($_GET[\'detailed\'] === \'adsense\') {\n$analytics_detailed .= 6;\n}\nelseif ($_GET[\'detailed\'] === \'browser-and-operating-system\') {\n$analytics_detailed .= 7;\n}\nelseif ($_GET[\'detailed\'] === \'time-on-site\') {\n$analytics_detailed .= 8;\n}\nelseif ($_GET[\'detailed\'] === \'all-traffic-sources-usage\') {\n$analytics_detailed .= 9;\n}\nelseif ($_GET[\'detailed\'] === \'referring-sites\') {\n$analytics_detailed .= 10;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines\') {\n$analytics_detailed .= 11;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines-organic\') {\n$analytics_detailed .= 12;\n}\nelseif ($_GET[\'detailed\'] === \'search-engines-paid\') {\n$analytics_detailed .= 13;\n}\nelseif ($_GET[\'detailed\'] === \'keywords\') {\n$analytics_detailed .= 14;\n}\nelseif ($_GET[\'detailed\'] === \'top-content\') {\n$analytics_detailed .= 15;\n}\nelseif ($_GET[\'detailed\'] === \'top-landing-pages\') {\n$analytics_detailed .= 16;\n}\nelseif ($_GET[\'detailed\'] === \'top-exit-pages\') {\n$analytics_detailed .= 17;\n}\nelse {\n$analytics_detailed = 1;\n}\n\n}\nelse {\n$analytics_detailed = NULL;\n}\n# ENDING: IF DETAILED REPORTS\n\necho \'<script type=\"text/javascript\">\n$(function(){\n\n$(\"#analytics_tab\").load(\"../kuo_analytics/index.php?duration=\'.$analytics_query_period.$analytics_detailed.\' #kuo_analytics_widget\", \n\nfunction (responseText,analytics_status,XMLHttpRequest) {\n\nif (analytics_status == \"error\") {\n$(\"#analytics_tab\").prepend(\"<p id=\\\"analytics_status_message\\\"><a href=\\\"../kuo_analytics/?login\\\" rel=\\\"nofollow\\\">Please log in to Google Analytics</a></p>\");\n}\nelse if (responseText == \"\") {\n$(\"#analytics_tab\").prepend(\"<p id=\\\"analytics_status_message\\\">No data available.</p>\");\n}\nelse {\n$(\"#analytics_tab #kuo_analytics_summary\").prepend(\"<p id=\\\"analytics_save\\\"><a href=\\\"../kuo_analytics/index.php?duration=\'.$analytics_query_period.$analytics_detailed.\'&amp;save\\\" rel=\\\"nofollow\\\">Save this report</a></p>\");\n}\n});\n\n});\n</script><div id=\"analytics_tab\"></div>\';\n}\n# ENDING: IF AJAX STATISTICS\necho \'</div>\';\n\n}\n# ENDING: STATISTICS SECTION\n\necho \'</div>\';\n\n# BEGINNING: LINK TO INSTALLATION GUIDE\nif ((!isset($_GET[\'analytics_tab\'])) or ($_GET[\'analytics_tab\'] !== \'install\')) {\necho \'<p id=\"analytics_installation\"><a href=\"./index.php?event=kuo_analytics&amp;analytics_tab=install\" rel=\"chapter nofollow\">How to install?</a></p>\';\n}\n# ENDING: LINK TO INSTALLATION GUIDE\n\necho \'<p id=\"analytics_by\">Plugin by <a href=\"http://kuopassa.net/txp/\" rel=\"external\" target=\"_new\">Pete</a> with the help of <a href=\"https://developers.google.com/api-client-library/php/\" rel=\"nofollow external\">API Client Library for PHP</a>.</p>\n<p id=\"analytics_donate\"><a href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=MXL5FSBXQBL3N\" rel=\"nofollow external\">Please donate to keep the development going</a></p>\n</div>\';\n}\n\n}', '9a2129a634e501f7bd9b313a89f9c27f', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_ckeditor_cdn', 0, 'Petri Ikonen', 'http://kuopassa.net/', '0.2', 'Fetching CKEditor from CDN and activating it in the Write section.', '<ul id=\"links\">\n<li id=\"kuo_ckeditor_cdn\"><a href=\"#kuo_ckeditor_cdn\">kuo_ckeditor_cdn</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>Fetching CKEditor Standard Package 4.4.3 from a content delivery network (<code>cdn.ckeditor.com/4.4.3/standard/ckeditor.js</code>), inserting it to both of the <code>textarea</code>s (body and excerpt) at the Write tab.</p>\n<p id=\"notice\">This plugin is suitable for sites where ALL articles are to be handled with CKEditor.</p>\n<p>If the plugin is activated, it\'s activated in all of the articles&hellip; No toggling on and off per article, at least not yet. Also:</p>\n<ul>\n<li>Please leave text untouched for articles (Textile off).</li>\n<li>CKEditor is loaded in English.</li>\n<li>Use at your own risk.</li>\n</ul>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_ckeditor_cdn</code>.</li>\n<li>Upload file <code>ckeditor-adapter-jquery.js</code> to <code>js</code> folder at the root.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_ckeditor_cdn</code>.</li>\n<li><span>[Optional]</span> Delete file <code>ckeditor-adapter-jquery.js</code> from <code>js</code> folder.</li>\n</ol>\n<hr />\n<h4>What does the file <code>ckeditor-adapter-jquery.js</code> do?</h4>\n<p>It helps CKEditor to understand when to save articles.</p>\n<hr />\n<h4>Have you installed <code>ckeditor-adapter-jquery.js</code>?</h4>\n<p>If you have, please click the link below. If you find error 404 on that URL, the file is not installed properly.</p>\n<ul>\n<li><a href=\"../js/ckeditor-adapter-jquery.js\" rel=\"nofollow\"><code>../js/ckeditor-adapter-jquery.js</code></a><br />&rarr; if JavaScript code here, all is good.</li>\n</ul>\n<hr />\n<h5>Special thanks to</h5>\n<ul>\n<li>&quot;photonomad&quot; at the Textpattern CMS Support forum.</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style>#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_ckeditor_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_ckeditor_head() {\r\n\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\necho \'<script type=\"text/javascript\" src=\"//cdn.ckeditor.com/4.4.3/standard/ckeditor.js\"></script>\r\n<script type=\"text/javascript\" src=\"../js/ckeditor-adapter-jquery.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(document).ready(function(){\r\n\r\n$(\"textarea[name=Body],textarea[name=Excerpt]\").ckeditor();\r\n\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n});\r\n</script>\';\r\n}\r\n\r\n}', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_ckeditor_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_ckeditor_head() {\r\n\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\necho \'<script type=\"text/javascript\" src=\"//cdn.ckeditor.com/4.4.3/standard/ckeditor.js\"></script>\r\n<script type=\"text/javascript\" src=\"../js/ckeditor-adapter-jquery.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(document).ready(function(){\r\n\r\n$(\"textarea[name=Body],textarea[name=Excerpt]\").ckeditor();\r\n\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n});\r\n</script>\';\r\n}\r\n\r\n}', '61679ef5ffc54b7a1a4defabac9de773', '', '', 4, 5, 0),
('kuo_cleditor', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Activating CLEditor in the Write tab.', '<ul id=\"links\">\n<li id=\"kuo_cleditor\"><a href=\"#kuo_cleditor\">kuo_cleditor</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>This plugin activates CLEditor WYSIWYG Editor (1.4.5) for jQuery (read more at http://premiumsoftware.net/cleditor) and inserts it to both of the <code>textarea</code>s (body and excerpt) at the Write tab. This build of CLEditor includes XHTML plugin (1.0.1) and Table plugin (1.0.4) for the editor.</p>\n<p id=\"notice\">This plugin is suitable for sites where ALL articles are to be handled with CLEditor.</p>\n<p>If the plugin is activated, it\'s activated in all of the articles&hellip; No toggling on and off per article, at least not yet. Also:</p>\n<ul>\n<li>Please leave text untouched for articles (Textile off).</li>\n<li>CLEditor is loaded in English.</li>\n<li>Use at your own risk.</li>\n</ul>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_cleditor</code>.</li>\n<li>Upload folder called <code>cleditor</code> inside your Textpattern site\'s <code>textpattern</code> folder.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_cleditor</code>.</li>\n<li>Delete folder called <code>cleditor</code> from inside your Textpattern site\'s <code>textpattern</code> folder.</li>\n</ol>\n<hr />\n<h4>What does the <code>cleditor</code> folder do?</h4>\n<p>It includes all the necessary files; images, styles and JavaScript codes, which make CLEditor work.</p>\n<hr />\n<h4>Have you installed the folder?</h4>\n<p>If you have, please click these links. If you find error 404 on any of those URL\'s, the plugin is not installed properly.</p>\n<ul>\n<li><a href=\"../textpattern/cleditor/jquery.cleditor.css\" rel=\"nofollow external\"><code>../textpattern/cleditor/jquery.cleditor.css</code></a>\n<br /> &rarr; if CSS markup here, all is good</li>\n<li><a href=\"../textpattern/cleditor/jquery.cleditor.min.js\" rel=\"nofollow external\"><code>../textpattern/cleditor/jquery.cleditor.min.js</code></a>\n<br /> &rarr; if JavaScript code here, all is good</li>\n<li><a href=\"../textpattern/cleditor/jquery.cleditor.table.min.js\" rel=\"nofollow external\"><code>../textpattern/cleditor/jquery.cleditor.table.min.js</code></a>\n<br /> &rarr; if JavaScript code here, all is good</li>\n<li><a href=\"../textpattern/cleditor/jquery.cleditor.xhtml.min.js\" rel=\"nofollow external\"><code>../textpattern/cleditor/jquery.cleditor.xhtml.min.js</code></a>\n<br /> &rarr; if JavaScript code here, all is good</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_cleditor_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_cleditor_head() {\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\n\r\necho \'<link rel=\"stylesheet\" type=\"text/css\" href=\"./cleditor/jquery.cleditor.css\" media=\"all\" />\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.min.js\"></script>\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.xhtml.min.js\"></script>\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.table.min.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(document).ready(function () {\r\n$(\"textarea#body\").cleditor({height:350,width:\\\'94%\\\'});\r\n$(\"textarea#excerpt\").cleditor({height:150,width:\\\'94%\\\'});\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n});\r\n</script>\';\r\n\r\n}\r\n}', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_cleditor_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_cleditor_head() {\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\n\r\necho \'<link rel=\"stylesheet\" type=\"text/css\" href=\"./cleditor/jquery.cleditor.css\" media=\"all\" />\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.min.js\"></script>\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.xhtml.min.js\"></script>\r\n<script type=\"text/javascript\" src=\"./cleditor/jquery.cleditor.table.min.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(document).ready(function () {\r\n$(\"textarea#body\").cleditor({height:350,width:\\\'94%\\\'});\r\n$(\"textarea#excerpt\").cleditor({height:150,width:\\\'94%\\\'});\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n});\r\n</script>\';\r\n\r\n}\r\n}', 'b01c206f998e6b7b012d2e754025c48d', '', '', 3, 5, 0),
('kuo_code_search', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Admin-side search for Pages, Forms and Styles.', '<h2>The search</h2>\n<p>This plugin searches Forms, Pages and Styles.</p>\n\n<h3><code>c:</code> is for content</h3>\n<p>Search only content by using prefix <code>c:</code> in the input field before search term.</p>\n<p><code>c:&lt;txp:</code> searches content with search term <code>&lt;txp:</code>.</p>\n\n<h3><code>n:</code> is for name</h3>\n<p>Search can be limited to only names with prefix <code>n:</code> in the input field before search term.</p>\n<p><code>n:search_result</code> searches names with search term <code>search_result</code>.</p>\n\n<h3>No prefix in search term</h3>\n<p>Example: <code>&lt;!DOCTYPE html&gt;</code> is a search term, which doesn\'t have a prefix <code>n:</code> or <code>c:</code>, so it attempts to find results from content, and from names.</p>\n\n<h4>Feedback</h4>\n<p>Please visit Textpattern forum, where <strong><a href=\"https://forum.textpattern.io/viewtopic.php?pid=308377\" rel=\"nofollow external\" target=\"_new\">a dedicated thread</a></strong> exists for this plugin.</p>', '\r\n# BEGINS: ADMIN SIDE\r\nif (txpinterface === \'admin\') {\r\n\r\n    define(\'KUO_CODE_SEARCH_NAME\',\'kuo_code_search\');\r\n    define(\'KUO_CODE_SEARCH_CLOSE\',\'&nbsp;<a href=\"\'.$_SERVER[\'REQUEST_URI\'].\'\">\'.gTxt(\'close\').\'</a>\');\r\n    define(\'KUO_CODE_SEARCH_SQL_LIMIT\',5);\r\n    define(\'KUO_CODE_SEARCH_ACCESSKEY\',\'s\');\r\n\r\n    #add_privs(\'kuo_code_search_form\',0);\r\n\r\n    register_callback(\'kuo_code_search_form\',\'admin_side\',\'main_content\');\r\n\r\n    # BEGINS: FUNCTION: CREATE SEARCH FORM\r\n    function kuo_code_search_form($event,$step) {\r\n\r\n        # CONTAINER FOR HTML\r\n        $kuo_html_output = NULL;\r\n\r\n        # BEGINS: SEARCH PHRASE/TERM IS GIVEN\r\n        if (\r\n            (isset($_POST[KUO_CODE_SEARCH_NAME]))\r\n            &&\r\n            (strlen($_POST[KUO_CODE_SEARCH_NAME]) >= 3)\r\n            &&\r\n            (strlen($_POST[KUO_CODE_SEARCH_NAME]) <= 50)\r\n        ) {\r\n\r\n            $kuo_search_term = $_POST[KUO_CODE_SEARCH_NAME];\r\n\r\n            # BEGINS: REMOVE TARGET FROM SEARCH TERM\r\n            if (in_array(substr($kuo_search_term,0,2),array(\'n:\',\'c:\'))) {\r\n                $kuo_search_term_sql = substr($kuo_search_term,2);\r\n            }\r\n            # ENDS: REMOVE TARGET FROM SEARCH TERM\r\n            else {\r\n                $kuo_search_term_sql = $kuo_search_term;\r\n            }\r\n\r\n            # BEGINS: TARGET SEARCH TO NAME\r\n            if (substr($kuo_search_term,0,2) === \'n:\') {\r\n                $kuo_search_where_form = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_page = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_css = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n            }\r\n            # ENDS: TARGET SEARCH TO NAME\r\n            # BEGINS: TARGET SEARCH TO CONTENT\r\n            elseif (substr($kuo_search_term,0,2) === \'c:\') {\r\n                $kuo_search_where_form = \"`Form` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_page = \"`user_html` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_css = \"`css` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n            }\r\n            # ENDS: TARGET SEARCH TO CONTENT\r\n            # BEGINS: DON\'T TARGET SEARCH\r\n            else {\r\n                $kuo_search_where_form = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `Form` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n                $kuo_search_where_page = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `user_html` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n                $kuo_search_where_css = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `css` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n            }\r\n            # ENDS: DON\'T TARGET SEARCH\r\n\r\n            $kuo_search_results = safe_query(\"\r\n                (\r\n                    SELECT\r\n                        \'form\' AS `table`,\r\n                        `name`,\r\n                        `Form` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_form\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_form.\"\r\n                )\r\n                UNION ALL\r\n                (\r\n                    SELECT\r\n                        \'page\' AS `table`,\r\n                        `name`,\r\n                        `user_html` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_page\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_page.\"\r\n                )\r\n                UNION ALL\r\n                (\r\n                    SELECT\r\n                        \'css\' AS `table`,\r\n                        `name`,\r\n                        `css` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_css\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_css.\"\r\n                )\r\n                LIMIT\r\n                    0, \".KUO_CODE_SEARCH_SQL_LIMIT\r\n            );\r\n\r\n            unset(\r\n                $kuo_search_where_form,\r\n                $kuo_search_where_page,\r\n                $kuo_search_where_css\r\n            );\r\n\r\n            # BEGINS: FOUND RESULTS\r\n            if (($kuo_search_results) && (numRows($kuo_search_results) != 0)) {\r\n\r\n                $kuo_result_counter = 0;\r\n\r\n                # BEGINS: WHILE\r\n                while ($kuo_result = mysqli_fetch_assoc($kuo_search_results)) {\r\n\r\n                    $kuo_result_counter++;\r\n\r\n                    # SUMMARY IS CREATED OF CONTENT\r\n                    $summary = $kuo_result[\'content\'];\r\n                    $summary =\r\n                        gTxt(\'kuo_lines\').\': \'.count(preg_split(\'/\\n|\\r/\',$summary)).\'<br />\'.\r\n                        gTxt(\'kuo_characters\').\': \'.strlen($summary).\'<br />\'.\r\n                        gTxt(\'kuo_words\').\': \'.str_word_count($summary).\' \'.gTxt(\'kuo_approx\')\r\n                        ;\r\n\r\n                    # AN EXCERPT OF CONTENT\r\n                    $kuo_result[\'content\'] = trim($kuo_result[\'content\']);\r\n                    $kuo_result[\'content\'] = str_replace(array(PHP_EOL,\"\\r\\n\"),\'\',$kuo_result[\'content\']);\r\n\r\n                    $kuo_result_cut_start = strpos($kuo_result[\'content\'],$kuo_search_term);\r\n                    $kuo_result_cut_end = $kuo_result_cut_start+75;\r\n\r\n                    $kuo_result[\'content\'] = substr($kuo_result[\'content\'],$kuo_result_cut_start,$kuo_result_cut_end);\r\n                    $kuo_result[\'content\'] = trim($kuo_result[\'content\']);\r\n                    $kuo_result[\'content\'] = wordwrap($kuo_result[\'content\'],75,PHP_EOL);\r\n\r\n                    $kuo_html_output .= \'\r\n                        <tr>\r\n                            <td>\'.$kuo_result_counter.\'/\'.numRows($kuo_search_results).\'</td>\r\n                            <td>\r\n                                <a href=\"index.php?event=\'.$kuo_result[\'table\'].\'&amp;name=\'.$kuo_result[\'name\'].\'\">\r\n                                    <kbd>\'.htmlentities($kuo_result[\'name\']).\'</kbd>\r\n                                </a>\r\n                            </td>\r\n                            <td>\r\n                                <a href=\"index.php?event=\'.$kuo_result[\'table\'].\'\">\r\n                                    <kbd>\'.$kuo_result[\'table\'].\'</kbd>\r\n                                </a>\r\n                            </td>\r\n                            <td><pre>\'.htmlentities($kuo_result[\'content\']).\'</pre></td>\r\n                            <td>\'.$summary.\'</td>\r\n                        </tr>\r\n                    \';\r\n\r\n                }\r\n                # ENDS: WHILE\r\n\r\n                mysqli_free_result($kuo_search_results);\r\n\r\n                unset(\r\n                    $kuo_search_results,\r\n                    $kuo_result_counter\r\n                );\r\n\r\n                $kuo_html_output = \'\r\n                    <p>\'.KUO_CODE_SEARCH_CLOSE.\'</p>\r\n                    <table class=\"txp-list\">\r\n                        <thead>\r\n                            <tr>\r\n                                <th>#</th>\r\n                                <th>\'.gTxt(\'name\').\'</th>\r\n                                <th>\'.gTxt(\'type\').\'</th>\r\n                                <th>\'.gTxt(\'excerpt\').\'</th>\r\n                                <th>\'.gTxt(\'misc\').\'</th>\r\n                            </tr>\r\n                        </thead>\r\n                        <tbody>\r\n                            \'.$kuo_html_output.\'\r\n                        </tbody>\r\n                    </table>\r\n                    <p>\'.KUO_CODE_SEARCH_CLOSE.\'</p>\r\n                \';\r\n\r\n            }\r\n            # ENDS: FOUND RESULTS\r\n            # BEGINS: NOTHING FOUND\r\n            else {\r\n                $kuo_html_output = \'\r\n                    <p>\r\n                        \'.gTxt(\'no_search_matches\').\':\r\n                        <kbd>\'.htmlentities($kuo_search_term).\'</kbd>.\'.KUO_CODE_SEARCH_CLOSE.\'\r\n                    </p>\r\n                \';\r\n            }\r\n            # ENDS: NOTHING FOUND\r\n\r\n        }\r\n        # ENDS: SEARCH PHRASE/TERM IS GIVEN\r\n        # BEGINS: SEARCH HASN\'T BEEN MADE\r\n        else {\r\n            $kuo_search_term = NULL;\r\n        }\r\n        # ENDS: SEARCH HASN\'T BEEN MADE\r\n\r\n        echo \'\r\n            <form method=\"post\" title=\"\'.gTxt(\'kuo_accesskey\').\': \'.KUO_CODE_SEARCH_ACCESSKEY.\'\">\r\n                <p><label for=\"\'.KUO_CODE_SEARCH_NAME.\'\">\'.gTxt(\'kuo_search_for_code_tag_markup\').\':&nbsp;</label><input\r\n                type=\"search\"\r\n                name=\"\'.KUO_CODE_SEARCH_NAME.\'\"\r\n                id=\"\'.KUO_CODE_SEARCH_NAME.\'\"\r\n                value=\"\'.$kuo_search_term.\'\"\r\n                required=\"required\"\r\n                minlength=\"3\"\r\n                maxlength=\"50\"\r\n                autocomplete=\"on\"\r\n                size=\"20\"\r\n                accesskey=\"\'.KUO_CODE_SEARCH_ACCESSKEY.\'\"\r\n                />&nbsp;<button type=\"submit\">\'.gTxt(\'search\').\'</button></p>\r\n            </form>\r\n            \',$kuo_html_output\r\n        ;\r\n\r\n        unset($kuo_html_output);\r\n\r\n    }\r\n    # ENDS: FUNCTION: CREATE SEARCH FORM\r\n}\r\n# ENDS:  ADMIN SIDE\r\n', '\r\n# BEGINS: ADMIN SIDE\r\nif (txpinterface === \'admin\') {\r\n\r\n    define(\'KUO_CODE_SEARCH_NAME\',\'kuo_code_search\');\r\n    define(\'KUO_CODE_SEARCH_CLOSE\',\'&nbsp;<a href=\"\'.$_SERVER[\'REQUEST_URI\'].\'\">\'.gTxt(\'close\').\'</a>\');\r\n    define(\'KUO_CODE_SEARCH_SQL_LIMIT\',5);\r\n    define(\'KUO_CODE_SEARCH_ACCESSKEY\',\'s\');\r\n\r\n    #add_privs(\'kuo_code_search_form\',0);\r\n\r\n    register_callback(\'kuo_code_search_form\',\'admin_side\',\'main_content\');\r\n\r\n    # BEGINS: FUNCTION: CREATE SEARCH FORM\r\n    function kuo_code_search_form($event,$step) {\r\n\r\n        # CONTAINER FOR HTML\r\n        $kuo_html_output = NULL;\r\n\r\n        # BEGINS: SEARCH PHRASE/TERM IS GIVEN\r\n        if (\r\n            (isset($_POST[KUO_CODE_SEARCH_NAME]))\r\n            &&\r\n            (strlen($_POST[KUO_CODE_SEARCH_NAME]) >= 3)\r\n            &&\r\n            (strlen($_POST[KUO_CODE_SEARCH_NAME]) <= 50)\r\n        ) {\r\n\r\n            $kuo_search_term = $_POST[KUO_CODE_SEARCH_NAME];\r\n\r\n            # BEGINS: REMOVE TARGET FROM SEARCH TERM\r\n            if (in_array(substr($kuo_search_term,0,2),array(\'n:\',\'c:\'))) {\r\n                $kuo_search_term_sql = substr($kuo_search_term,2);\r\n            }\r\n            # ENDS: REMOVE TARGET FROM SEARCH TERM\r\n            else {\r\n                $kuo_search_term_sql = $kuo_search_term;\r\n            }\r\n\r\n            # BEGINS: TARGET SEARCH TO NAME\r\n            if (substr($kuo_search_term,0,2) === \'n:\') {\r\n                $kuo_search_where_form = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_page = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_css = \"`name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n            }\r\n            # ENDS: TARGET SEARCH TO NAME\r\n            # BEGINS: TARGET SEARCH TO CONTENT\r\n            elseif (substr($kuo_search_term,0,2) === \'c:\') {\r\n                $kuo_search_where_form = \"`Form` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_page = \"`user_html` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n                $kuo_search_where_css = \"`css` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\";\r\n            }\r\n            # ENDS: TARGET SEARCH TO CONTENT\r\n            # BEGINS: DON\'T TARGET SEARCH\r\n            else {\r\n                $kuo_search_where_form = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `Form` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n                $kuo_search_where_page = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `user_html` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n                $kuo_search_where_css = \"\r\n                    `name` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                    OR\r\n                    `css` LIKE \'%\".safe_escape($kuo_search_term_sql).\"%\'\r\n                \";\r\n            }\r\n            # ENDS: DON\'T TARGET SEARCH\r\n\r\n            $kuo_search_results = safe_query(\"\r\n                (\r\n                    SELECT\r\n                        \'form\' AS `table`,\r\n                        `name`,\r\n                        `Form` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_form\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_form.\"\r\n                )\r\n                UNION ALL\r\n                (\r\n                    SELECT\r\n                        \'page\' AS `table`,\r\n                        `name`,\r\n                        `user_html` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_page\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_page.\"\r\n                )\r\n                UNION ALL\r\n                (\r\n                    SELECT\r\n                        \'css\' AS `table`,\r\n                        `name`,\r\n                        `css` AS `content`\r\n                    FROM\r\n                        \".safe_pfx(\'txp_css\').\"\r\n                    WHERE\r\n                        \".$kuo_search_where_css.\"\r\n                )\r\n                LIMIT\r\n                    0, \".KUO_CODE_SEARCH_SQL_LIMIT\r\n            );\r\n\r\n            unset(\r\n                $kuo_search_where_form,\r\n                $kuo_search_where_page,\r\n                $kuo_search_where_css\r\n            );\r\n\r\n            # BEGINS: FOUND RESULTS\r\n            if (($kuo_search_results) && (numRows($kuo_search_results) != 0)) {\r\n\r\n                $kuo_result_counter = 0;\r\n\r\n                # BEGINS: WHILE\r\n                while ($kuo_result = mysqli_fetch_assoc($kuo_search_results)) {\r\n\r\n                    $kuo_result_counter++;\r\n\r\n                    # SUMMARY IS CREATED OF CONTENT\r\n                    $summary = $kuo_result[\'content\'];\r\n                    $summary =\r\n                        gTxt(\'kuo_lines\').\': \'.count(preg_split(\'/\\n|\\r/\',$summary)).\'<br />\'.\r\n                        gTxt(\'kuo_characters\').\': \'.strlen($summary).\'<br />\'.\r\n                        gTxt(\'kuo_words\').\': \'.str_word_count($summary).\' \'.gTxt(\'kuo_approx\')\r\n                        ;\r\n\r\n                    # AN EXCERPT OF CONTENT\r\n                    $kuo_result[\'content\'] = trim($kuo_result[\'content\']);\r\n                    $kuo_result[\'content\'] = str_replace(array(PHP_EOL,\"\\r\\n\"),\'\',$kuo_result[\'content\']);\r\n\r\n                    $kuo_result_cut_start = strpos($kuo_result[\'content\'],$kuo_search_term);\r\n                    $kuo_result_cut_end = $kuo_result_cut_start+75;\r\n\r\n                    $kuo_result[\'content\'] = substr($kuo_result[\'content\'],$kuo_result_cut_start,$kuo_result_cut_end);\r\n                    $kuo_result[\'content\'] = trim($kuo_result[\'content\']);\r\n                    $kuo_result[\'content\'] = wordwrap($kuo_result[\'content\'],75,PHP_EOL);\r\n\r\n                    $kuo_html_output .= \'\r\n                        <tr>\r\n                            <td>\'.$kuo_result_counter.\'/\'.numRows($kuo_search_results).\'</td>\r\n                            <td>\r\n                                <a href=\"index.php?event=\'.$kuo_result[\'table\'].\'&amp;name=\'.$kuo_result[\'name\'].\'\">\r\n                                    <kbd>\'.htmlentities($kuo_result[\'name\']).\'</kbd>\r\n                                </a>\r\n                            </td>\r\n                            <td>\r\n                                <a href=\"index.php?event=\'.$kuo_result[\'table\'].\'\">\r\n                                    <kbd>\'.$kuo_result[\'table\'].\'</kbd>\r\n                                </a>\r\n                            </td>\r\n                            <td><pre>\'.htmlentities($kuo_result[\'content\']).\'</pre></td>\r\n                            <td>\'.$summary.\'</td>\r\n                        </tr>\r\n                    \';\r\n\r\n                }\r\n                # ENDS: WHILE\r\n\r\n                mysqli_free_result($kuo_search_results);\r\n\r\n                unset(\r\n                    $kuo_search_results,\r\n                    $kuo_result_counter\r\n                );\r\n\r\n                $kuo_html_output = \'\r\n                    <p>\'.KUO_CODE_SEARCH_CLOSE.\'</p>\r\n                    <table class=\"txp-list\">\r\n                        <thead>\r\n                            <tr>\r\n                                <th>#</th>\r\n                                <th>\'.gTxt(\'name\').\'</th>\r\n                                <th>\'.gTxt(\'type\').\'</th>\r\n                                <th>\'.gTxt(\'excerpt\').\'</th>\r\n                                <th>\'.gTxt(\'misc\').\'</th>\r\n                            </tr>\r\n                        </thead>\r\n                        <tbody>\r\n                            \'.$kuo_html_output.\'\r\n                        </tbody>\r\n                    </table>\r\n                    <p>\'.KUO_CODE_SEARCH_CLOSE.\'</p>\r\n                \';\r\n\r\n            }\r\n            # ENDS: FOUND RESULTS\r\n            # BEGINS: NOTHING FOUND\r\n            else {\r\n                $kuo_html_output = \'\r\n                    <p>\r\n                        \'.gTxt(\'no_search_matches\').\':\r\n                        <kbd>\'.htmlentities($kuo_search_term).\'</kbd>.\'.KUO_CODE_SEARCH_CLOSE.\'\r\n                    </p>\r\n                \';\r\n            }\r\n            # ENDS: NOTHING FOUND\r\n\r\n        }\r\n        # ENDS: SEARCH PHRASE/TERM IS GIVEN\r\n        # BEGINS: SEARCH HASN\'T BEEN MADE\r\n        else {\r\n            $kuo_search_term = NULL;\r\n        }\r\n        # ENDS: SEARCH HASN\'T BEEN MADE\r\n\r\n        echo \'\r\n            <form method=\"post\" title=\"\'.gTxt(\'kuo_accesskey\').\': \'.KUO_CODE_SEARCH_ACCESSKEY.\'\">\r\n                <p><label for=\"\'.KUO_CODE_SEARCH_NAME.\'\">\'.gTxt(\'kuo_search_for_code_tag_markup\').\':&nbsp;</label><input\r\n                type=\"search\"\r\n                name=\"\'.KUO_CODE_SEARCH_NAME.\'\"\r\n                id=\"\'.KUO_CODE_SEARCH_NAME.\'\"\r\n                value=\"\'.$kuo_search_term.\'\"\r\n                required=\"required\"\r\n                minlength=\"3\"\r\n                maxlength=\"50\"\r\n                autocomplete=\"on\"\r\n                size=\"20\"\r\n                accesskey=\"\'.KUO_CODE_SEARCH_ACCESSKEY.\'\"\r\n                />&nbsp;<button type=\"submit\">\'.gTxt(\'search\').\'</button></p>\r\n            </form>\r\n            \',$kuo_html_output\r\n        ;\r\n\r\n        unset($kuo_html_output);\r\n\r\n    }\r\n    # ENDS: FUNCTION: CREATE SEARCH FORM\r\n}\r\n# ENDS:  ADMIN SIDE\r\n', '0b1090381272100b59ed9e4ba59b9d55', '#@kuo\nkuo_accesskey => Accesskey\nkuo_approx => approx.\nkuo_characters => Characters\nkuo_lines => Lines\nkuo_search_for_code_tag_markup => Search for code, tag, markup\nkuo_words => Words\n#@kuo\n#@language en-us\nkuo_accesskey => Accesskey\nkuo_approx => approx.\nkuo_characters => Characters\nkuo_lines => Lines\nkuo_search_for_code_tag_markup => Search for code, tag, markup\nkuo_words => Words\n#@kuo\n#@language fi-fi\nkuo_accesskey => Pikanäppäin\nkuo_approx => arviolta\nkuo_characters => Merkkiä\nkuo_lines => Riviä\nkuo_search_for_code_tag_markup => Etsi koodia, tagia, merkkausta\nkuo_words => Sanaa\n#@kuo\n#@language fr-fr\nkuo_accesskey => Accesskey\nkuo_approx => approx.\nkuo_characters => Caractères\nkuo_lines => Lignes\nkuo_search_for_code_tag_markup => Recherche de code, de balises, de balisages\nkuo_words => Mots\n#@kuo\n#@language sv-se\nkuo_accesskey => Åtkomstnyckel\nkuo_approx => cirka.\nkuo_characters => Tecken\nkuo_lines => Rader\nkuo_search_for_code_tag_markup => Sök efter kod, tagg, markup\nkuo_words => Ord', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_com_db', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Saves contents from any com_connect form to database.', '<h2>What is kuo_com_db</h2>\n<p>An experimental Textpattern plugin. Saves contents from any com_connect form to a database.</p>\n<h3>Do you use ext_file_attach?</h3>\n<p>If Textpattern plugin ext_file_attach is enabled, kuo_com_db will skip it\'s contents.</p>\n<h4>Want to leave feedback, present ideas?</h4>\n<p><a href=\"https://forum.textpattern.io/viewtopic.php?id=49790\" rel=\"external\">See Textpattern forum</a></p>', 'const\r\n	KUO_COM_DB_TABLE = \'kuo_com_db\',\r\n	KUO_COM_DB_PREFS_TABLE = \'kuo_com_db_prefs\',\r\n	KUO_COM_DB_DEBUG = false\r\n	;\r\n\r\nif (strcmp(txpinterface,\'admin\') == 0) {\r\n	\r\n    add_privs(\r\n		\'kuo_com_extensions\',\r\n		1\r\n	);\r\n	\r\n    register_tab(\r\n		\'extensions\',\r\n		\'kuo_com_extensions\',\r\n		\'kuo_com_db\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_com_extensions\',\r\n		\'kuo_com_extensions\'\r\n	);\r\n}\r\n\r\nregister_callback(\r\n	\'kuo_com_db_enabled\',\r\n	\'plugin_lifecycle.kuo_com_db\',\r\n	\'enabled\'\r\n);\r\n\r\nregister_callback(\r\n	\'kuo_com_db_deleted\',\r\n	\'plugin_lifecycle.kuo_com_db\',\r\n	\'deleted\'\r\n);\r\n\r\nregister_callback(\r\n	\'kuo_com_db\',\r\n	\'comconnect.deliver\'\r\n);\r\n\r\nfunction kuo_com_db_enabled() {\r\n	\r\n	safe_query(\r\n		\'\r\n		CREATE TABLE IF NOT EXISTS `\'.safe_pfx(KUO_COM_DB_TABLE).\'` (\r\n			`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,\r\n			`uid` char(32) NOT NULL,\r\n			`created` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\r\n			`comconnect` text NOT NULL,\r\n			`referrer` varchar(255) NULL DEFAULT NULL,\r\n			`is_spam` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`is_important` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			PRIMARY KEY (`id`),\r\n			UNIQUE KEY `uid` (`uid`)\r\n		)\r\n		\',\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	safe_query(\r\n		\'\r\n		CREATE TABLE IF NOT EXISTS `\'.safe_pfx(KUO_COM_DB_PREFS_TABLE).\'` (\r\n			`user_id` int(11) UNSIGNED NOT NULL,\r\n			`order_column` varchar(20) NOT NULL DEFAULT \\\'created\\\',\r\n			`order_direction` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`show_spam` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`messages_per_page` tinyint(3) UNSIGNED NOT NULL DEFAULT 25,\r\n			PRIMARY KEY `user_id` (`user_id`)\r\n		)\r\n		\',\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		safe_insert(\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`user_id` = \".$user_id.\",\r\n				`order_column` = \'created\',\r\n				`order_direction` = 0,\r\n				`show_spam` = 0,\r\n				`messages_per_page` = 25\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n	}\r\n}\r\n\r\nfunction kuo_com_db_deleted() {\r\n	\r\n	safe_drop(\r\n		KUO_COM_DB_TABLE,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	safe_drop(\r\n		KUO_COM_DB_PREFS_TABLE,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_db() {\r\n	\r\n	global $com_connect_form;\r\n	\r\n	$now = date(\'Y-m-d H:i:s\');\r\n	\r\n	if (isset($com_connect_form[\'com_connect_file\'])) {\r\n		unset($com_connect_form[\'com_connect_file\']);\r\n	}\r\n	\r\n	$comconnect = serialize($com_connect_form);\r\n	\r\n	if (isset($_SERVER[\'HTTP_REFERER\'])) {\r\n		\r\n		$referrer = filter_var(\r\n			$_SERVER[\'HTTP_REFERER\'],\r\n			FILTER_SANITIZE_URL\r\n		);\r\n	}\r\n	else {\r\n		$referrer = null;\r\n	}\r\n	\r\n	safe_insert(\r\n		KUO_COM_DB_TABLE,\r\n		\"\r\n			`uid` = \'\".md5(substr($now,0,-1).$comconnect.$referrer).\"\',\r\n			`created` = \'\".$now.\"\',\r\n			`modified` = \'\".$now.\"\',\r\n			`comconnect` = \'\".$comconnect.\"\',\r\n			`referrer` = \'\".$referrer.\"\',\r\n			`is_spam` = 0,\r\n			`is_important` = 0\r\n		\",\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_count($what = \'spam\') {\r\n	\r\n	if (strcmp($what,\'spam\') == 0) {\r\n		$what = \'`is_spam` = 1\';\r\n	}\r\n	elseif (strcmp($what,\'important\') == 0) {\r\n		$what = \'`is_important` = 1\';\r\n	}\r\n	else {\r\n		$what = \'1 = 1\';\r\n	}\r\n	\r\n	return safe_count(\r\n		KUO_COM_DB_TABLE,\r\n		$what,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_count_messages() {\r\n	\r\n	return safe_count(\r\n		KUO_COM_DB_TABLE,\r\n		\"1 = 1\",\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_extensions() {\r\n	\r\n	$base_url = \'index.php?event=kuo_com_extensions\';\r\n	\r\n	$title = href(\'Saved messages from com_connect\',$base_url,\'\');\r\n	\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	$search_term = null;\r\n	\r\n	# BEGINS: $_POST\r\n	if ((isset($_POST)) && (!empty($_POST))) {\r\n		\r\n		if (\r\n			(isset($_POST[\'show_spam\']))\r\n			||\r\n			(isset($_POST[\'messages_per_page\']))\r\n		) {\r\n			kuo_com_save_settings(\r\n				array(\r\n					\'show_spam\'=>$_POST[\'show_spam\'],\r\n					\'messages_per_page\'=>$_POST[\'messages_per_page\'],\r\n					\'order_column\'=>$settings[\'order_column\'],\r\n					\'order_direction\'=>$settings[\'order_direction\']\r\n				)\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_settings\';\r\n			\r\n			header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n			die;\r\n			\r\n		}\r\n		elseif ((isset($_POST[\'uid\'])) && (strlen($_POST[\'uid\']) == 32)) {\r\n			\r\n			if (isset($_POST[\'is_spam\'])) {\r\n				$_POST[\'is_spam\'] = 1;\r\n			}\r\n			else {\r\n				$_POST[\'is_spam\'] = 0;\r\n			}\r\n			\r\n			if (isset($_POST[\'is_important\'])) {\r\n				$_POST[\'is_important\'] = 1;\r\n			}\r\n			else {\r\n				$_POST[\'is_important\'] = 0;\r\n			}\r\n			\r\n			$status = safe_update(\r\n				KUO_COM_DB_TABLE,\r\n				\"\r\n					`is_spam` = \".$_POST[\'is_spam\'].\",\r\n					`is_important` = \".$_POST[\'is_important\'].\"\r\n				\",\r\n				\"\r\n					`uid` = \'\".$_POST[\'uid\'].\"\'\r\n				\",\r\n				KUO_COM_DB_DEBUG\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_message_\'.$_POST[\'uid\'];\r\n			\r\n			if (\r\n				(isset($_SERVER[\'HTTP_REFERER\']))\r\n				&&\r\n				(strpos($_SERVER[\'HTTP_REFERER\'],$base_url) == true)\r\n			) {\r\n				$base_url = $_SERVER[\'HTTP_REFERER\'];\r\n			}\r\n\r\n			header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n			die;\r\n		}\r\n		elseif (isset($_POST[\'kuo_com_search\'])) {\r\n			\r\n			if (\r\n				(strlen($_POST[\'kuo_com_search\']) >= 2)\r\n				&&\r\n				(strlen($_POST[\'kuo_com_search\']) <= 50)\r\n			) {\r\n				$search_term = $_POST[\'kuo_com_search\'];\r\n			}\r\n			else {\r\n				$hash_tag = \'kuo_com_search\';\r\n				\r\n				header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n				die;\r\n			}\r\n		}\r\n		elseif (\r\n			(isset(\r\n				$_POST[\'kuo_com_delete\'],\r\n				$_POST[\'kuo_com_delete_type\'])\r\n			)\r\n			&&\r\n			(strcmp($_POST[\'kuo_com_delete\'],1) == 0)\r\n			&&\r\n			(in_array($_POST[\'kuo_com_delete_type\'],array(0,1,2)))\r\n		) {\r\n			\r\n			echo \'KEHITETÄÄN\';\r\n			\r\n			if (strcmp($_POST[\'kuo_com_delete_type\'],0) == 0) {\r\n				$_POST[\'kuo_com_delete_type\'] = \'`is_spam` = 1\';\r\n			}\r\n			elseif (strcmp($_POST[\'kuo_com_delete_type\'],1) == 0) {\r\n				$_POST[\'kuo_com_delete_type\'] = \'`is_important` = 1\';\r\n			}\r\n			else {\r\n				$_POST[\'kuo_com_delete_type\'] = \'1 = 1\';\r\n			}\r\n			\r\n			safe_delete(\r\n				KUO_COM_DB_TABLE,\r\n				$_POST[\'kuo_com_delete_type\'],\r\n				KUO_COM_DB_DEBUG\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_delete\';\r\n			\r\n			header(\'location: \'.$base_url.\'&kuo_com_status=1#\'.$hash_tag);\r\n			die;\r\n		}\r\n	}\r\n	# ENDS: $_POST\r\n	\r\n	if (\r\n		(isset($_GET[\'order_column\']))\r\n		&&\r\n		(in_array($_GET[\'order_column\'],array(\'created\',\'modified\',\'referrer\')))\r\n	) {\r\n		$order_column = $_GET[\'order_column\'];\r\n	}\r\n	else {\r\n		$order_column = $settings[\'order_column\'];\r\n	}\r\n	\r\n	if (!isset($_GET[\'order_direction\'])) {\r\n		$order_direction = $settings[\'order_direction\'];\r\n	}\r\n	elseif ($_GET[\'order_direction\'] == 1) {\r\n		$order_direction = 0;\r\n	}\r\n	else {\r\n		$order_direction = 1;\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'order_column\'],$_GET[\'order_direction\']))\r\n		&&\r\n		(\r\n			(!isset($_POST))\r\n			||\r\n			(empty($_POST))\r\n		)\r\n	) {\r\n		\r\n		kuo_com_save_settings(\r\n			array(\r\n				\'order_column\'=>$_GET[\'order_column\'],\r\n				\'order_direction\'=>$order_direction,\r\n				\'show_spam\'=>$settings[\'show_spam\'],\r\n				\'messages_per_page\'=>$settings[\'messages_per_page\'],\r\n			)\r\n		);\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'messages_page\']))\r\n		&&\r\n		(is_numeric($_GET[\'messages_page\']))\r\n	) {\r\n		$messages_page = $_GET[\'messages_page\'];\r\n		\r\n		$sql_offset = $settings[\'messages_per_page\']*($messages_page-1);\r\n	}\r\n	else {\r\n		$messages_page = 1;\r\n		$sql_offset = 0;\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'kuo_com_status\']))\r\n		&&\r\n		(in_array($_GET[\'kuo_com_status\'],array(0,1)))\r\n	) {\r\n		$status_message = $_GET[\'kuo_com_status\'];\r\n	}\r\n	else {\r\n		$status_message = 2;\r\n	}\r\n	\r\n	if ($settings[\'show_spam\'] == 0) {\r\n		$sql_where = \'= 0\';\r\n	}\r\n	else {\r\n		$sql_where = \'IN (0,1)\';\r\n	}\r\n	\r\n	if (!is_null($search_term)) {\r\n		\r\n		$messages = safe_query(\r\n			\'\r\n			SELECT\r\n				`uid`,\r\n				`created`,\r\n				`modified`,\r\n				`comconnect`,\r\n				`referrer`,\r\n				`is_spam`,\r\n				`is_important`\r\n			FROM\r\n				`\'.safe_pfx(KUO_COM_DB_TABLE).\'`\r\n			WHERE\r\n				`uid` = \"\'.$search_term.\'\"\r\n				OR\r\n					(\r\n						`referrer` = \"\'.$search_term.\'\"\r\n					OR\r\n						`referrer` LIKE \"%\'.$search_term.\'%\"\r\n					)\r\n				OR\r\n					`comconnect` LIKE \"%\'.$search_term.\'%\"\r\n			ORDER BY\r\n				`\'.$order_column.\'` \'.kuo_com_db_order($order_direction).\'\r\n			LIMIT\r\n				0, \'.$settings[\'messages_per_page\'],\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		$messages = kuo_com_tbody(\r\n			$messages,\r\n			true\r\n		);\r\n	}\r\n	else {\r\n		\r\n		$messages = safe_query(\r\n			\'\r\n			SELECT\r\n				`uid`,\r\n				`created`,\r\n				`modified`,\r\n				`comconnect`,\r\n				`referrer`,\r\n				`is_spam`,\r\n				`is_important`\r\n			FROM\r\n				`\'.safe_pfx(KUO_COM_DB_TABLE).\'`\r\n			WHERE\r\n				`is_spam` \'.$sql_where.\'\r\n			ORDER BY\r\n				`\'.$order_column.\'` \'.kuo_com_db_order($order_direction).\'\r\n			LIMIT\r\n				\'.$sql_offset.\', \'.$settings[\'messages_per_page\'],\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		$messages = kuo_com_tbody(\r\n			$messages,\r\n			false\r\n		);\r\n	}\r\n	\r\n	$settings = \'\r\n		<form method=\"post\" action=\"#kuo_com_settings\">\r\n			<fieldset id=\"kuo_com_settings\">\r\n				<legend>Settings</legend>\r\n				<ul>\r\n					<li>\'.checkbox(\r\n						\'show_spam\',\r\n						1,\r\n						boolval($settings[\'show_spam\']),\r\n						null,\r\n						\'show_spam\'\r\n					).\'<label for=\"show_spam\">&nbsp;Show \r\n						\'.strong(\'also\',\'\').\' messages marked as spam \r\n						(\'.kuo_com_count(\'spam\').\')</label></li>\r\n					<li><label for=\"messages_per_page\">Show&nbsp;</label>\'.fInput(\r\n						\'text\',\r\n						\'messages_per_page\',\r\n						$settings[\'messages_per_page\'],\r\n						\'\',\r\n						\'\',\r\n						\'\',\r\n						3,\r\n						0,\r\n						\'messages_per_page\',\r\n						false,\r\n						true,\r\n						25\r\n					).\'<label for=\"messages_per_page\">&nbsp;messages \r\n						per page</label></li>\r\n					<li><button type=\"submit\">\'.gTxt(\'save\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	$table = \r\n		startTable(\r\n			\'kuo_com_table\',\r\n			\'\',\r\n			\'\',\r\n			0,\r\n			0\r\n		).\'\r\n			<thead>\r\n				\'.tr(\'\r\n					\'.hCell(\'#\',\'\',\'\').\'\r\n					\'.hCell(href(\'Created\',kuo_com_sort_url(\'created\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(href(\'Modified\',kuo_com_sort_url(\'modified\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(href(\'Referring URL\',kuo_com_sort_url(\'referrer\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(\'&nbsp;\',\'\',\'\').\'\r\n				\',\'\').\'\r\n			</thead>\r\n			<tbody>\r\n				\'.$messages.\'\r\n			</tbody>\r\n	\'.endTable();\r\n	\r\n	# BEGINS: MAIN CONTENT AREA\r\n	if (kuo_com_count_messages() > 0) {\r\n		\r\n		if (!is_null($search_term)) {\r\n			\r\n			$content = \r\n				kuo_com_search($search_term).\r\n				\'<h2>Search results for <samp>\'.$search_term.\'</samp></h2>\'.\r\n				$table.\r\n				\'<p>\'.href(\'Close results\',$base_url,\'\').\'</p>\'\r\n			;\r\n		}\r\n		else {\r\n			\r\n			$content = 	\r\n				kuo_com_search($search_term).\r\n				kuo_com_pagination($messages_page).\r\n				$table.\r\n				kuo_com_pagination($messages_page).\r\n				$settings.\r\n				kuo_com_delete($status_message)\r\n			;\r\n		}\r\n	}\r\n	else {\r\n		$content = \r\n			\'<p><mark>No messages.</mark></p>\'.\r\n			$settings\r\n		;\r\n	}\r\n	# ENDS: MAIN CONTENT AREA\r\n	\r\n    echo \r\n		pagetop(strip_tags($title)),\r\n		kuo_com_css(),\r\n		\'<h1>\',$title,\'</h1>\',\r\n		$content\r\n	;\r\n}\r\n\r\nfunction kuo_com_pagination($current_page = 0) {\r\n	\r\n	$messages = kuo_com_count_messages();\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	if ($settings[\'show_spam\'] != 1) {\r\n		$messages = $messages-kuo_com_count(\'spam\');\r\n	}\r\n	\r\n	$pagination = null;\r\n	\r\n	if ($messages > 0) {\r\n		\r\n		$pages = ceil($messages/$settings[\'messages_per_page\']);\r\n		\r\n		$href = \'index.php?event=kuo_com_extensions#kuo_com_settings\';\r\n	\r\n		for ($i = 1;$i <= $pages;$i++) {\r\n			\r\n			if ($i == $current_page) {\r\n				$selected = \' class=\"selected\"\';\r\n			}\r\n			else {\r\n				$selected = null;\r\n			}\r\n			\r\n			if ($i > 1) {\r\n				$href = str_replace(\r\n					\'#kuo_com_settings\',\r\n					\'&amp;messages_page=\'.$i.\'#kuo_com_settings\',\r\n					$href\r\n				);\r\n			}\r\n			\r\n			$pagination .= \r\n				\'<li\'.$selected.\'>\'.href($i,$href,\'\').\'</li>\';\r\n		}\r\n		\r\n		$pagination = \'\r\n			<nav class=\"kuo_com_nav\">\r\n				<ul>\r\n					\'.$pagination.\'\r\n				</ul>\r\n			</nav>\r\n		\';\r\n	}\r\n	\r\n	return $pagination;\r\n}\r\n\r\nfunction kuo_com_sort_url($column,$direction = 1) {\r\n	\r\n	$slug = \'?event=kuo_com_extensions&amp;order_column=\';\r\n	$slug .= $column;\r\n	$slug .= \'&amp;order_direction=\';\r\n	$slug .= $direction;\r\n	$slug .= \'#kuo_com_table\';\r\n	\r\n	return $slug;\r\n}\r\n\r\nfunction kuo_com_tbody($messages = null,$is_search_result = false) {\r\n	\r\n	if (\r\n		(!is_null($messages))\r\n		&&\r\n		(count($messages) > 0)\r\n	) {\r\n		\r\n		$tbody = null;\r\n		$counter = 0;\r\n		$date_format = get_pref(\'archive_dateformat\');\r\n		\r\n		foreach ($messages as $messages) {\r\n			\r\n			if (isset(\r\n				$messages[\'uid\'],\r\n				$messages[\'created\'],\r\n				$messages[\'modified\'],\r\n				$messages[\'comconnect\'],\r\n				$messages[\'referrer\'],\r\n				$messages[\'is_spam\'],\r\n				$messages[\'is_important\'])\r\n			) {\r\n			\r\n				$counter++;\r\n				\r\n				$messages[\'created\'] = strtotime($messages[\'created\']);\r\n				$messages[\'created\'] = strftime($date_format,$messages[\'created\']);\r\n				\r\n				$messages[\'modified\'] = strtotime($messages[\'modified\']);\r\n				$messages[\'modified\'] = strftime($date_format,$messages[\'modified\']);\r\n				\r\n				if (\r\n					($messages[\'is_important\'] == 1)\r\n					&&\r\n					($messages[\'is_spam\'] == 0)\r\n				) {\r\n					$row_css = \' class=\"important\"\';\r\n				}\r\n				elseif (\r\n					($messages[\'is_important\'] == 0)\r\n					&&\r\n					($messages[\'is_spam\'] == 1)\r\n				) {\r\n					$row_css = \' class=\"spam\"\';\r\n				}\r\n				elseif (\r\n					($messages[\'is_important\'] == 1)\r\n					&&\r\n					($messages[\'is_spam\'] == 1)\r\n				) {\r\n					$row_css = \' class=\"important spam\"\';\r\n				}\r\n				else {\r\n					$row_css = null;\r\n				}\r\n				\r\n				$messages[\'is_spam\'] = checkbox(\r\n					\'is_spam\',\r\n					1,\r\n					boolval($messages[\'is_spam\']),\r\n					0,\r\n					\'is_spam_\'.$messages[\'uid\']\r\n				);\r\n				\r\n				$messages[\'is_spam\'] .= \r\n					\'<label for=\"is_spam_\'.$messages[\'uid\'].\'\">&nbsp;\'.gTxt(\'spam\').\'</label>\';\r\n				\r\n				$messages[\'is_important\'] = checkbox(\r\n					\'is_important\',\r\n					1,\r\n					boolval($messages[\'is_important\']),\r\n					0,\r\n					\'is_important_\'.$messages[\'uid\']\r\n				);\r\n				\r\n				$messages[\'is_important\'] .= \r\n					\'<label for=\"is_important_\'.$messages[\'uid\'].\'\">&nbsp;Important</label>\';\r\n				\r\n				$messages[\'referrer\'] = htmlentities($messages[\'referrer\']);\r\n				\r\n				$messages[\'comconnect\'] = unserialize($messages[\'comconnect\']);\r\n				$messages[\'comconnect\'] = kuo_com_make_list($messages[\'comconnect\']);\r\n				\r\n				if (!$is_search_result) {\r\n					\r\n					$tools = \'\r\n						<form method=\"post\" id=\"kuo_com_message_\'.$messages[\'uid\'].\'\">\r\n							<input type=\"hidden\" name=\"uid\" value=\"\'.\r\n							$messages[\'uid\'].\'\" />\r\n							\'.$messages[\'is_spam\'].\' \'.\r\n							$messages[\'is_important\'].\' \r\n							<button type=\"submit\">\'.gTxt(\'save\').\'</button>\r\n						</form>\r\n					\';\r\n				}\r\n				else {\r\n					$tools = \'&nbsp;\';\r\n				}\r\n				\r\n				$tbody .= \'\r\n					<tr\'.$row_css.\' id=\"kuo_com_message_\'.$messages[\'uid\'].\'\">\r\n						<td rowspan=\"3\">\'.$counter.\'.</td>\r\n						<td>\'.$messages[\'created\'].\'</td>\r\n						<td>\'.$messages[\'modified\'].\'</td>\r\n						<td>\'.$messages[\'referrer\'].\'</td>\r\n						<td>\'.$tools.\'</td>\r\n					</tr>\r\n					<tr\'.$row_css.\'>\r\n						<td colspan=\"4\">\r\n							<ul>\r\n								\'.$messages[\'comconnect\'].\'\r\n							</ul>\r\n						</td>\r\n					</tr>\r\n					<tr\'.$row_css.\'>\r\n						<td colspan=\"4\">Message UID: \r\n						<code>\'.$messages[\'uid\'].\'</code></td>\r\n					</tr>\r\n				\';\r\n			}\r\n			else {\r\n				continue;\r\n			}\r\n		}\r\n		\r\n		return $tbody;\r\n	}\r\n	else {\r\n		return tr(\'<td colspan=\"5\">No messages.</td>\',\'\');\r\n	}\r\n}\r\n\r\nfunction kuo_com_make_list($comconnect = array()) {\r\n	\r\n	if (!empty($comconnect)) {\r\n		\r\n		$list = null;\r\n			\r\n		foreach ($comconnect as $key => $value) {\r\n\r\n			if (\r\n				(\r\n					(is_string($key))\r\n					||\r\n					(is_numeric($key))\r\n				)\r\n				&&\r\n				(\r\n					(is_string($value))\r\n					||\r\n					(is_numeric($value))\r\n				)\r\n			) {\r\n			\r\n				$key = htmlentities($key);\r\n				$value = htmlentities($value);\r\n\r\n				$list .= \'<li>\'.strong($key.\':\',\'\').\' \'.$value.\'</li>\';\r\n			}\r\n		}\r\n		\r\n		return $list;\r\n	}\r\n	else {\r\n		return \'<li>No content.</li>\';\r\n	}\r\n}\r\n\r\nfunction kuo_com_table_settings() {\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		$settings = safe_row(\r\n			\"\r\n				`order_column`,\r\n				`order_direction`,\r\n				`show_spam`,\r\n				`messages_per_page`\r\n			\",\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`user_id` = \".$user_id.\"\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		return $settings;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_db_order($number = 0) {\r\n	\r\n	if ($number == 0) {\r\n		return \'DESC\';\r\n	}\r\n	else {\r\n		return \'ASC\';\r\n	}\r\n}\r\n\r\nfunction kuo_com_user_id() {\r\n	\r\n	$user = is_logged_in();\r\n	\r\n	if (isset($user[\'name\'])) {\r\n	\r\n		$user = safe_row(\r\n			\'user_id\',\r\n			\'txp_users\',\r\n			\"`name` = \'\".$user[\'name\'].\"\'\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		if ((isset($user[\'user_id\'])) && (is_numeric($user[\'user_id\']))) {\r\n			\r\n			settype($user[\'user_id\'],\'int\');\r\n			\r\n			return $user[\'user_id\'];\r\n		}\r\n		else {\r\n			return false;\r\n		}\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_save_settings($data = array()) {\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	if (\r\n		(!isset($data[\'order_column\']))\r\n		||\r\n		(!in_array($data[\'order_column\'],array(\'created\',\'modified\',\'referrer\')))\r\n	) {\r\n		$data[\'order_column\'] = $settings[\'order_column\'];\r\n	}\r\n	\r\n	if (\r\n		(!isset($data[\'order_direction\']))\r\n		||\r\n		(!in_array($data[\'order_direction\'],array(0,1)))\r\n	) {\r\n		$data[\'order_direction\'] = $settings[\'order_direction\'];\r\n	}\r\n	\r\n	if (isset($data[\'show_spam\'])) {\r\n		$data[\'show_spam\'] = 1;\r\n	}\r\n	else {\r\n		$data[\'show_spam\'] = 0;\r\n	}\r\n	\r\n	if (\r\n		(!isset($data[\'messages_per_page\']))\r\n		||\r\n		(!is_numeric($data[\'messages_per_page\']))\r\n		||\r\n		($data[\'messages_per_page\'] < 1)\r\n		||\r\n		($data[\'messages_per_page\'] > 100)\r\n	) {\r\n		$data[\'messages_per_page\'] = $settings[\'messages_per_page\'];\r\n	}\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		$save = safe_update(\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`order_column` = \'\".$data[\'order_column\'].\"\',\r\n				`order_direction` = \".$data[\'order_direction\'].\",\r\n				`show_spam` = \".$data[\'show_spam\'].\",\r\n				`messages_per_page` = \".$data[\'messages_per_page\'].\"\r\n			\",\r\n			\"\r\n				`user_id` = \".$user_id.\"\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		return $save;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_css() {\r\n	\r\n	return \'\r\n		<style type=\"text/css\" media=\"all\">\r\n			#kuo_com_settings ul,\r\n			#kuo_com_search ul,\r\n			#kuo_com_delete ul,\r\n			.kuo_com_nav ul {\r\n				list-style: none;\r\n				margin-left: 0;\r\n				padding-left: 0;\r\n			}\r\n			.kuo_com_nav li,\r\n			.kuo_com_nav a,\r\n			#kuo_com_settings li,\r\n			#kuo_com_search li,\r\n			#kuo_com_delete li {\r\n				display: inline-block;\r\n			}\r\n			#kuo_com_settings li:not(:last-of-type) {\r\n				padding-right: 1.5em;\r\n				margin-right: 1.5em;\r\n				line-height: 200%;\r\n			}\r\n			.kuo_com_nav a {\r\n				padding: 0.5em 1em;\r\n				background: #eee;\r\n				border: 1px solid #ccc;\r\n				border-right-width: 0;\r\n			}\r\n			.kuo_com_nav li:last-of-type a {\r\n				border-right-width: 1px;\r\n			}\r\n			.kuo_com_nav .selected a {\r\n				font-weight: bold;\r\n				background: green;\r\n				color: #fff;\r\n				border-color: darkgreen;\r\n			}\r\n			tr.important td {\r\n				background: gold;\r\n			}\r\n			tr.spam td {\r\n				background: #eee;\r\n			}\r\n			tr.spam ul {\r\n				color: #999;\r\n				text-decoration: line-through;\r\n			}\r\n			tr.spam.important td {\r\n				background: #ffc;\r\n			}\r\n		</style>\r\n	\';\r\n}\r\n\r\nfunction kuo_com_search($term = null) {\r\n	\r\n	$form = \'\r\n		<form method=\"post\" action=\"#kuo_com_search\" id=\"kuo_com_search\">\r\n			<fieldset>\r\n				<legend>Search messages</legend>\r\n				<ul>\r\n					<li><label for=\"kuo_term\">Search \r\n					message content, message UID, \r\n					referring URL:&nbsp;</label>\'.fInput(\r\n						\'search\',\r\n						\'kuo_com_search\',\r\n						$term,\r\n						\'\',\r\n						\'\',\r\n						\'\',\r\n						40,\r\n						0,\r\n						\'kuo_term\',\r\n						false,\r\n						true,\r\n						\'\'\r\n					).\'</li>\r\n					<li><button type=\"submit\">\'.gTxt(\'search\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	return $form;\r\n}\r\n\r\n\r\nfunction kuo_com_delete($status = 2) {\r\n	\r\n	if ($status == 0) {\r\n		$status = \'<p><mark>Unable to delete.</mark></p>\';\r\n	}\r\n	elseif ($status == 1) {\r\n		$status = \'<p><mark>Messages were deleted.</mark></p>\';\r\n	}\r\n	else {\r\n		$status = null;\r\n	}\r\n	\r\n	$form = \'\r\n		<form method=\"post\" action=\"#kuo_com_delete\" id=\"kuo_com_delete\">\r\n			<fieldset>\r\n				<legend>Delete messages (\'.kuo_com_count(\'spam\').\')</legend>\r\n				\'.$status.\'\r\n				<ul>\r\n					<li>\'.checkbox2(\r\n						\'kuo_com_delete\',\r\n						0,\r\n						0,\r\n						\'kuo_delete\'\r\n					).\'<label for=\"kuo_delete\">&nbsp;Yes, delete</label></li>\r\n					<li>\'.selectInput(\r\n						\'kuo_com_delete_type\',\r\n						array(\r\n							0=>\'messages marked as spam (\'.kuo_com_count(\'spam\').\')\',\r\n							1=>\'messages marked as important (\'.kuo_com_count(\'important\').\')\',\r\n							2=>\'all messages (\'.kuo_com_count(\'all\').\')\'\r\n						),\r\n						\'0\',\r\n						true,\r\n						\'\',\r\n						\'\',\r\n						false,\r\n						false\r\n					).\'</li>\r\n					<li><button type=\"submit\">\'.gTxt(\'submit\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	return $form;\r\n}', 'const\r\n	KUO_COM_DB_TABLE = \'kuo_com_db\',\r\n	KUO_COM_DB_PREFS_TABLE = \'kuo_com_db_prefs\',\r\n	KUO_COM_DB_DEBUG = false\r\n	;\r\n\r\nif (strcmp(txpinterface,\'admin\') == 0) {\r\n	\r\n    add_privs(\r\n		\'kuo_com_extensions\',\r\n		1\r\n	);\r\n	\r\n    register_tab(\r\n		\'extensions\',\r\n		\'kuo_com_extensions\',\r\n		\'kuo_com_db\'\r\n	);\r\n	\r\n	register_callback(\r\n		\'kuo_com_extensions\',\r\n		\'kuo_com_extensions\'\r\n	);\r\n}\r\n\r\nregister_callback(\r\n	\'kuo_com_db_enabled\',\r\n	\'plugin_lifecycle.kuo_com_db\',\r\n	\'enabled\'\r\n);\r\n\r\nregister_callback(\r\n	\'kuo_com_db_deleted\',\r\n	\'plugin_lifecycle.kuo_com_db\',\r\n	\'deleted\'\r\n);\r\n\r\nregister_callback(\r\n	\'kuo_com_db\',\r\n	\'comconnect.deliver\'\r\n);\r\n\r\nfunction kuo_com_db_enabled() {\r\n	\r\n	safe_query(\r\n		\'\r\n		CREATE TABLE IF NOT EXISTS `\'.safe_pfx(KUO_COM_DB_TABLE).\'` (\r\n			`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,\r\n			`uid` char(32) NOT NULL,\r\n			`created` timestamp NOT NULL DEFAULT current_timestamp(),\r\n			`modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\r\n			`comconnect` text NOT NULL,\r\n			`referrer` varchar(255) NULL DEFAULT NULL,\r\n			`is_spam` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`is_important` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			PRIMARY KEY (`id`),\r\n			UNIQUE KEY `uid` (`uid`)\r\n		)\r\n		\',\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	safe_query(\r\n		\'\r\n		CREATE TABLE IF NOT EXISTS `\'.safe_pfx(KUO_COM_DB_PREFS_TABLE).\'` (\r\n			`user_id` int(11) UNSIGNED NOT NULL,\r\n			`order_column` varchar(20) NOT NULL DEFAULT \\\'created\\\',\r\n			`order_direction` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`show_spam` tinyint(1) UNSIGNED NOT NULL DEFAULT 0,\r\n			`messages_per_page` tinyint(3) UNSIGNED NOT NULL DEFAULT 25,\r\n			PRIMARY KEY `user_id` (`user_id`)\r\n		)\r\n		\',\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		safe_insert(\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`user_id` = \".$user_id.\",\r\n				`order_column` = \'created\',\r\n				`order_direction` = 0,\r\n				`show_spam` = 0,\r\n				`messages_per_page` = 25\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n	}\r\n}\r\n\r\nfunction kuo_com_db_deleted() {\r\n	\r\n	safe_drop(\r\n		KUO_COM_DB_TABLE,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n	\r\n	safe_drop(\r\n		KUO_COM_DB_PREFS_TABLE,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_db() {\r\n	\r\n	global $com_connect_form;\r\n	\r\n	$now = date(\'Y-m-d H:i:s\');\r\n	\r\n	if (isset($com_connect_form[\'com_connect_file\'])) {\r\n		unset($com_connect_form[\'com_connect_file\']);\r\n	}\r\n	\r\n	$comconnect = serialize($com_connect_form);\r\n	\r\n	if (isset($_SERVER[\'HTTP_REFERER\'])) {\r\n		\r\n		$referrer = filter_var(\r\n			$_SERVER[\'HTTP_REFERER\'],\r\n			FILTER_SANITIZE_URL\r\n		);\r\n	}\r\n	else {\r\n		$referrer = null;\r\n	}\r\n	\r\n	safe_insert(\r\n		KUO_COM_DB_TABLE,\r\n		\"\r\n			`uid` = \'\".md5(substr($now,0,-1).$comconnect.$referrer).\"\',\r\n			`created` = \'\".$now.\"\',\r\n			`modified` = \'\".$now.\"\',\r\n			`comconnect` = \'\".$comconnect.\"\',\r\n			`referrer` = \'\".$referrer.\"\',\r\n			`is_spam` = 0,\r\n			`is_important` = 0\r\n		\",\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_count($what = \'spam\') {\r\n	\r\n	if (strcmp($what,\'spam\') == 0) {\r\n		$what = \'`is_spam` = 1\';\r\n	}\r\n	elseif (strcmp($what,\'important\') == 0) {\r\n		$what = \'`is_important` = 1\';\r\n	}\r\n	else {\r\n		$what = \'1 = 1\';\r\n	}\r\n	\r\n	return safe_count(\r\n		KUO_COM_DB_TABLE,\r\n		$what,\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_count_messages() {\r\n	\r\n	return safe_count(\r\n		KUO_COM_DB_TABLE,\r\n		\"1 = 1\",\r\n		KUO_COM_DB_DEBUG\r\n	);\r\n}\r\n\r\nfunction kuo_com_extensions() {\r\n	\r\n	$base_url = \'index.php?event=kuo_com_extensions\';\r\n	\r\n	$title = href(\'Saved messages from com_connect\',$base_url,\'\');\r\n	\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	$search_term = null;\r\n	\r\n	# BEGINS: $_POST\r\n	if ((isset($_POST)) && (!empty($_POST))) {\r\n		\r\n		if (\r\n			(isset($_POST[\'show_spam\']))\r\n			||\r\n			(isset($_POST[\'messages_per_page\']))\r\n		) {\r\n			kuo_com_save_settings(\r\n				array(\r\n					\'show_spam\'=>$_POST[\'show_spam\'],\r\n					\'messages_per_page\'=>$_POST[\'messages_per_page\'],\r\n					\'order_column\'=>$settings[\'order_column\'],\r\n					\'order_direction\'=>$settings[\'order_direction\']\r\n				)\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_settings\';\r\n			\r\n			header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n			die;\r\n			\r\n		}\r\n		elseif ((isset($_POST[\'uid\'])) && (strlen($_POST[\'uid\']) == 32)) {\r\n			\r\n			if (isset($_POST[\'is_spam\'])) {\r\n				$_POST[\'is_spam\'] = 1;\r\n			}\r\n			else {\r\n				$_POST[\'is_spam\'] = 0;\r\n			}\r\n			\r\n			if (isset($_POST[\'is_important\'])) {\r\n				$_POST[\'is_important\'] = 1;\r\n			}\r\n			else {\r\n				$_POST[\'is_important\'] = 0;\r\n			}\r\n			\r\n			$status = safe_update(\r\n				KUO_COM_DB_TABLE,\r\n				\"\r\n					`is_spam` = \".$_POST[\'is_spam\'].\",\r\n					`is_important` = \".$_POST[\'is_important\'].\"\r\n				\",\r\n				\"\r\n					`uid` = \'\".$_POST[\'uid\'].\"\'\r\n				\",\r\n				KUO_COM_DB_DEBUG\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_message_\'.$_POST[\'uid\'];\r\n			\r\n			if (\r\n				(isset($_SERVER[\'HTTP_REFERER\']))\r\n				&&\r\n				(strpos($_SERVER[\'HTTP_REFERER\'],$base_url) == true)\r\n			) {\r\n				$base_url = $_SERVER[\'HTTP_REFERER\'];\r\n			}\r\n\r\n			header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n			die;\r\n		}\r\n		elseif (isset($_POST[\'kuo_com_search\'])) {\r\n			\r\n			if (\r\n				(strlen($_POST[\'kuo_com_search\']) >= 2)\r\n				&&\r\n				(strlen($_POST[\'kuo_com_search\']) <= 50)\r\n			) {\r\n				$search_term = $_POST[\'kuo_com_search\'];\r\n			}\r\n			else {\r\n				$hash_tag = \'kuo_com_search\';\r\n				\r\n				header(\'location: \'.$base_url.\'#\'.$hash_tag);\r\n				die;\r\n			}\r\n		}\r\n		elseif (\r\n			(isset(\r\n				$_POST[\'kuo_com_delete\'],\r\n				$_POST[\'kuo_com_delete_type\'])\r\n			)\r\n			&&\r\n			(strcmp($_POST[\'kuo_com_delete\'],1) == 0)\r\n			&&\r\n			(in_array($_POST[\'kuo_com_delete_type\'],array(0,1,2)))\r\n		) {\r\n			\r\n			echo \'KEHITETÄÄN\';\r\n			\r\n			if (strcmp($_POST[\'kuo_com_delete_type\'],0) == 0) {\r\n				$_POST[\'kuo_com_delete_type\'] = \'`is_spam` = 1\';\r\n			}\r\n			elseif (strcmp($_POST[\'kuo_com_delete_type\'],1) == 0) {\r\n				$_POST[\'kuo_com_delete_type\'] = \'`is_important` = 1\';\r\n			}\r\n			else {\r\n				$_POST[\'kuo_com_delete_type\'] = \'1 = 1\';\r\n			}\r\n			\r\n			safe_delete(\r\n				KUO_COM_DB_TABLE,\r\n				$_POST[\'kuo_com_delete_type\'],\r\n				KUO_COM_DB_DEBUG\r\n			);\r\n			\r\n			$hash_tag = \'kuo_com_delete\';\r\n			\r\n			header(\'location: \'.$base_url.\'&kuo_com_status=1#\'.$hash_tag);\r\n			die;\r\n		}\r\n	}\r\n	# ENDS: $_POST\r\n	\r\n	if (\r\n		(isset($_GET[\'order_column\']))\r\n		&&\r\n		(in_array($_GET[\'order_column\'],array(\'created\',\'modified\',\'referrer\')))\r\n	) {\r\n		$order_column = $_GET[\'order_column\'];\r\n	}\r\n	else {\r\n		$order_column = $settings[\'order_column\'];\r\n	}\r\n	\r\n	if (!isset($_GET[\'order_direction\'])) {\r\n		$order_direction = $settings[\'order_direction\'];\r\n	}\r\n	elseif ($_GET[\'order_direction\'] == 1) {\r\n		$order_direction = 0;\r\n	}\r\n	else {\r\n		$order_direction = 1;\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'order_column\'],$_GET[\'order_direction\']))\r\n		&&\r\n		(\r\n			(!isset($_POST))\r\n			||\r\n			(empty($_POST))\r\n		)\r\n	) {\r\n		\r\n		kuo_com_save_settings(\r\n			array(\r\n				\'order_column\'=>$_GET[\'order_column\'],\r\n				\'order_direction\'=>$order_direction,\r\n				\'show_spam\'=>$settings[\'show_spam\'],\r\n				\'messages_per_page\'=>$settings[\'messages_per_page\'],\r\n			)\r\n		);\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'messages_page\']))\r\n		&&\r\n		(is_numeric($_GET[\'messages_page\']))\r\n	) {\r\n		$messages_page = $_GET[\'messages_page\'];\r\n		\r\n		$sql_offset = $settings[\'messages_per_page\']*($messages_page-1);\r\n	}\r\n	else {\r\n		$messages_page = 1;\r\n		$sql_offset = 0;\r\n	}\r\n	\r\n	if (\r\n		(isset($_GET[\'kuo_com_status\']))\r\n		&&\r\n		(in_array($_GET[\'kuo_com_status\'],array(0,1)))\r\n	) {\r\n		$status_message = $_GET[\'kuo_com_status\'];\r\n	}\r\n	else {\r\n		$status_message = 2;\r\n	}\r\n	\r\n	if ($settings[\'show_spam\'] == 0) {\r\n		$sql_where = \'= 0\';\r\n	}\r\n	else {\r\n		$sql_where = \'IN (0,1)\';\r\n	}\r\n	\r\n	if (!is_null($search_term)) {\r\n		\r\n		$messages = safe_query(\r\n			\'\r\n			SELECT\r\n				`uid`,\r\n				`created`,\r\n				`modified`,\r\n				`comconnect`,\r\n				`referrer`,\r\n				`is_spam`,\r\n				`is_important`\r\n			FROM\r\n				`\'.safe_pfx(KUO_COM_DB_TABLE).\'`\r\n			WHERE\r\n				`uid` = \"\'.$search_term.\'\"\r\n				OR\r\n					(\r\n						`referrer` = \"\'.$search_term.\'\"\r\n					OR\r\n						`referrer` LIKE \"%\'.$search_term.\'%\"\r\n					)\r\n				OR\r\n					`comconnect` LIKE \"%\'.$search_term.\'%\"\r\n			ORDER BY\r\n				`\'.$order_column.\'` \'.kuo_com_db_order($order_direction).\'\r\n			LIMIT\r\n				0, \'.$settings[\'messages_per_page\'],\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		$messages = kuo_com_tbody(\r\n			$messages,\r\n			true\r\n		);\r\n	}\r\n	else {\r\n		\r\n		$messages = safe_query(\r\n			\'\r\n			SELECT\r\n				`uid`,\r\n				`created`,\r\n				`modified`,\r\n				`comconnect`,\r\n				`referrer`,\r\n				`is_spam`,\r\n				`is_important`\r\n			FROM\r\n				`\'.safe_pfx(KUO_COM_DB_TABLE).\'`\r\n			WHERE\r\n				`is_spam` \'.$sql_where.\'\r\n			ORDER BY\r\n				`\'.$order_column.\'` \'.kuo_com_db_order($order_direction).\'\r\n			LIMIT\r\n				\'.$sql_offset.\', \'.$settings[\'messages_per_page\'],\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		$messages = kuo_com_tbody(\r\n			$messages,\r\n			false\r\n		);\r\n	}\r\n	\r\n	$settings = \'\r\n		<form method=\"post\" action=\"#kuo_com_settings\">\r\n			<fieldset id=\"kuo_com_settings\">\r\n				<legend>Settings</legend>\r\n				<ul>\r\n					<li>\'.checkbox(\r\n						\'show_spam\',\r\n						1,\r\n						boolval($settings[\'show_spam\']),\r\n						null,\r\n						\'show_spam\'\r\n					).\'<label for=\"show_spam\">&nbsp;Show \r\n						\'.strong(\'also\',\'\').\' messages marked as spam \r\n						(\'.kuo_com_count(\'spam\').\')</label></li>\r\n					<li><label for=\"messages_per_page\">Show&nbsp;</label>\'.fInput(\r\n						\'text\',\r\n						\'messages_per_page\',\r\n						$settings[\'messages_per_page\'],\r\n						\'\',\r\n						\'\',\r\n						\'\',\r\n						3,\r\n						0,\r\n						\'messages_per_page\',\r\n						false,\r\n						true,\r\n						25\r\n					).\'<label for=\"messages_per_page\">&nbsp;messages \r\n						per page</label></li>\r\n					<li><button type=\"submit\">\'.gTxt(\'save\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	$table = \r\n		startTable(\r\n			\'kuo_com_table\',\r\n			\'\',\r\n			\'\',\r\n			0,\r\n			0\r\n		).\'\r\n			<thead>\r\n				\'.tr(\'\r\n					\'.hCell(\'#\',\'\',\'\').\'\r\n					\'.hCell(href(\'Created\',kuo_com_sort_url(\'created\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(href(\'Modified\',kuo_com_sort_url(\'modified\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(href(\'Referring URL\',kuo_com_sort_url(\'referrer\',$order_direction),\'\'),\'\',\'\').\'\r\n					\'.hCell(\'&nbsp;\',\'\',\'\').\'\r\n				\',\'\').\'\r\n			</thead>\r\n			<tbody>\r\n				\'.$messages.\'\r\n			</tbody>\r\n	\'.endTable();\r\n	\r\n	# BEGINS: MAIN CONTENT AREA\r\n	if (kuo_com_count_messages() > 0) {\r\n		\r\n		if (!is_null($search_term)) {\r\n			\r\n			$content = \r\n				kuo_com_search($search_term).\r\n				\'<h2>Search results for <samp>\'.$search_term.\'</samp></h2>\'.\r\n				$table.\r\n				\'<p>\'.href(\'Close results\',$base_url,\'\').\'</p>\'\r\n			;\r\n		}\r\n		else {\r\n			\r\n			$content = 	\r\n				kuo_com_search($search_term).\r\n				kuo_com_pagination($messages_page).\r\n				$table.\r\n				kuo_com_pagination($messages_page).\r\n				$settings.\r\n				kuo_com_delete($status_message)\r\n			;\r\n		}\r\n	}\r\n	else {\r\n		$content = \r\n			\'<p><mark>No messages.</mark></p>\'.\r\n			$settings\r\n		;\r\n	}\r\n	# ENDS: MAIN CONTENT AREA\r\n	\r\n    echo \r\n		pagetop(strip_tags($title)),\r\n		kuo_com_css(),\r\n		\'<h1>\',$title,\'</h1>\',\r\n		$content\r\n	;\r\n}\r\n\r\nfunction kuo_com_pagination($current_page = 0) {\r\n	\r\n	$messages = kuo_com_count_messages();\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	if ($settings[\'show_spam\'] != 1) {\r\n		$messages = $messages-kuo_com_count(\'spam\');\r\n	}\r\n	\r\n	$pagination = null;\r\n	\r\n	if ($messages > 0) {\r\n		\r\n		$pages = ceil($messages/$settings[\'messages_per_page\']);\r\n		\r\n		$href = \'index.php?event=kuo_com_extensions#kuo_com_settings\';\r\n	\r\n		for ($i = 1;$i <= $pages;$i++) {\r\n			\r\n			if ($i == $current_page) {\r\n				$selected = \' class=\"selected\"\';\r\n			}\r\n			else {\r\n				$selected = null;\r\n			}\r\n			\r\n			if ($i > 1) {\r\n				$href = str_replace(\r\n					\'#kuo_com_settings\',\r\n					\'&amp;messages_page=\'.$i.\'#kuo_com_settings\',\r\n					$href\r\n				);\r\n			}\r\n			\r\n			$pagination .= \r\n				\'<li\'.$selected.\'>\'.href($i,$href,\'\').\'</li>\';\r\n		}\r\n		\r\n		$pagination = \'\r\n			<nav class=\"kuo_com_nav\">\r\n				<ul>\r\n					\'.$pagination.\'\r\n				</ul>\r\n			</nav>\r\n		\';\r\n	}\r\n	\r\n	return $pagination;\r\n}\r\n\r\nfunction kuo_com_sort_url($column,$direction = 1) {\r\n	\r\n	$slug = \'?event=kuo_com_extensions&amp;order_column=\';\r\n	$slug .= $column;\r\n	$slug .= \'&amp;order_direction=\';\r\n	$slug .= $direction;\r\n	$slug .= \'#kuo_com_table\';\r\n	\r\n	return $slug;\r\n}\r\n\r\nfunction kuo_com_tbody($messages = null,$is_search_result = false) {\r\n	\r\n	if (\r\n		(!is_null($messages))\r\n		&&\r\n		(count($messages) > 0)\r\n	) {\r\n		\r\n		$tbody = null;\r\n		$counter = 0;\r\n		$date_format = get_pref(\'archive_dateformat\');\r\n		\r\n		foreach ($messages as $messages) {\r\n			\r\n			if (isset(\r\n				$messages[\'uid\'],\r\n				$messages[\'created\'],\r\n				$messages[\'modified\'],\r\n				$messages[\'comconnect\'],\r\n				$messages[\'referrer\'],\r\n				$messages[\'is_spam\'],\r\n				$messages[\'is_important\'])\r\n			) {\r\n			\r\n				$counter++;\r\n				\r\n				$messages[\'created\'] = strtotime($messages[\'created\']);\r\n				$messages[\'created\'] = strftime($date_format,$messages[\'created\']);\r\n				\r\n				$messages[\'modified\'] = strtotime($messages[\'modified\']);\r\n				$messages[\'modified\'] = strftime($date_format,$messages[\'modified\']);\r\n				\r\n				if (\r\n					($messages[\'is_important\'] == 1)\r\n					&&\r\n					($messages[\'is_spam\'] == 0)\r\n				) {\r\n					$row_css = \' class=\"important\"\';\r\n				}\r\n				elseif (\r\n					($messages[\'is_important\'] == 0)\r\n					&&\r\n					($messages[\'is_spam\'] == 1)\r\n				) {\r\n					$row_css = \' class=\"spam\"\';\r\n				}\r\n				elseif (\r\n					($messages[\'is_important\'] == 1)\r\n					&&\r\n					($messages[\'is_spam\'] == 1)\r\n				) {\r\n					$row_css = \' class=\"important spam\"\';\r\n				}\r\n				else {\r\n					$row_css = null;\r\n				}\r\n				\r\n				$messages[\'is_spam\'] = checkbox(\r\n					\'is_spam\',\r\n					1,\r\n					boolval($messages[\'is_spam\']),\r\n					0,\r\n					\'is_spam_\'.$messages[\'uid\']\r\n				);\r\n				\r\n				$messages[\'is_spam\'] .= \r\n					\'<label for=\"is_spam_\'.$messages[\'uid\'].\'\">&nbsp;\'.gTxt(\'spam\').\'</label>\';\r\n				\r\n				$messages[\'is_important\'] = checkbox(\r\n					\'is_important\',\r\n					1,\r\n					boolval($messages[\'is_important\']),\r\n					0,\r\n					\'is_important_\'.$messages[\'uid\']\r\n				);\r\n				\r\n				$messages[\'is_important\'] .= \r\n					\'<label for=\"is_important_\'.$messages[\'uid\'].\'\">&nbsp;Important</label>\';\r\n				\r\n				$messages[\'referrer\'] = htmlentities($messages[\'referrer\']);\r\n				\r\n				$messages[\'comconnect\'] = unserialize($messages[\'comconnect\']);\r\n				$messages[\'comconnect\'] = kuo_com_make_list($messages[\'comconnect\']);\r\n				\r\n				if (!$is_search_result) {\r\n					\r\n					$tools = \'\r\n						<form method=\"post\" id=\"kuo_com_message_\'.$messages[\'uid\'].\'\">\r\n							<input type=\"hidden\" name=\"uid\" value=\"\'.\r\n							$messages[\'uid\'].\'\" />\r\n							\'.$messages[\'is_spam\'].\' \'.\r\n							$messages[\'is_important\'].\' \r\n							<button type=\"submit\">\'.gTxt(\'save\').\'</button>\r\n						</form>\r\n					\';\r\n				}\r\n				else {\r\n					$tools = \'&nbsp;\';\r\n				}\r\n				\r\n				$tbody .= \'\r\n					<tr\'.$row_css.\' id=\"kuo_com_message_\'.$messages[\'uid\'].\'\">\r\n						<td rowspan=\"3\">\'.$counter.\'.</td>\r\n						<td>\'.$messages[\'created\'].\'</td>\r\n						<td>\'.$messages[\'modified\'].\'</td>\r\n						<td>\'.$messages[\'referrer\'].\'</td>\r\n						<td>\'.$tools.\'</td>\r\n					</tr>\r\n					<tr\'.$row_css.\'>\r\n						<td colspan=\"4\">\r\n							<ul>\r\n								\'.$messages[\'comconnect\'].\'\r\n							</ul>\r\n						</td>\r\n					</tr>\r\n					<tr\'.$row_css.\'>\r\n						<td colspan=\"4\">Message UID: \r\n						<code>\'.$messages[\'uid\'].\'</code></td>\r\n					</tr>\r\n				\';\r\n			}\r\n			else {\r\n				continue;\r\n			}\r\n		}\r\n		\r\n		return $tbody;\r\n	}\r\n	else {\r\n		return tr(\'<td colspan=\"5\">No messages.</td>\',\'\');\r\n	}\r\n}\r\n\r\nfunction kuo_com_make_list($comconnect = array()) {\r\n	\r\n	if (!empty($comconnect)) {\r\n		\r\n		$list = null;\r\n			\r\n		foreach ($comconnect as $key => $value) {\r\n\r\n			if (\r\n				(\r\n					(is_string($key))\r\n					||\r\n					(is_numeric($key))\r\n				)\r\n				&&\r\n				(\r\n					(is_string($value))\r\n					||\r\n					(is_numeric($value))\r\n				)\r\n			) {\r\n			\r\n				$key = htmlentities($key);\r\n				$value = htmlentities($value);\r\n\r\n				$list .= \'<li>\'.strong($key.\':\',\'\').\' \'.$value.\'</li>\';\r\n			}\r\n		}\r\n		\r\n		return $list;\r\n	}\r\n	else {\r\n		return \'<li>No content.</li>\';\r\n	}\r\n}\r\n\r\nfunction kuo_com_table_settings() {\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		$settings = safe_row(\r\n			\"\r\n				`order_column`,\r\n				`order_direction`,\r\n				`show_spam`,\r\n				`messages_per_page`\r\n			\",\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`user_id` = \".$user_id.\"\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		return $settings;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_db_order($number = 0) {\r\n	\r\n	if ($number == 0) {\r\n		return \'DESC\';\r\n	}\r\n	else {\r\n		return \'ASC\';\r\n	}\r\n}\r\n\r\nfunction kuo_com_user_id() {\r\n	\r\n	$user = is_logged_in();\r\n	\r\n	if (isset($user[\'name\'])) {\r\n	\r\n		$user = safe_row(\r\n			\'user_id\',\r\n			\'txp_users\',\r\n			\"`name` = \'\".$user[\'name\'].\"\'\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		if ((isset($user[\'user_id\'])) && (is_numeric($user[\'user_id\']))) {\r\n			\r\n			settype($user[\'user_id\'],\'int\');\r\n			\r\n			return $user[\'user_id\'];\r\n		}\r\n		else {\r\n			return false;\r\n		}\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_save_settings($data = array()) {\r\n	\r\n	$user_id = kuo_com_user_id();\r\n	$settings = kuo_com_table_settings();\r\n	\r\n	if (\r\n		(!isset($data[\'order_column\']))\r\n		||\r\n		(!in_array($data[\'order_column\'],array(\'created\',\'modified\',\'referrer\')))\r\n	) {\r\n		$data[\'order_column\'] = $settings[\'order_column\'];\r\n	}\r\n	\r\n	if (\r\n		(!isset($data[\'order_direction\']))\r\n		||\r\n		(!in_array($data[\'order_direction\'],array(0,1)))\r\n	) {\r\n		$data[\'order_direction\'] = $settings[\'order_direction\'];\r\n	}\r\n	\r\n	if (isset($data[\'show_spam\'])) {\r\n		$data[\'show_spam\'] = 1;\r\n	}\r\n	else {\r\n		$data[\'show_spam\'] = 0;\r\n	}\r\n	\r\n	if (\r\n		(!isset($data[\'messages_per_page\']))\r\n		||\r\n		(!is_numeric($data[\'messages_per_page\']))\r\n		||\r\n		($data[\'messages_per_page\'] < 1)\r\n		||\r\n		($data[\'messages_per_page\'] > 100)\r\n	) {\r\n		$data[\'messages_per_page\'] = $settings[\'messages_per_page\'];\r\n	}\r\n	\r\n	if (is_int($user_id)) {\r\n	\r\n		$save = safe_update(\r\n			KUO_COM_DB_PREFS_TABLE,\r\n			\"\r\n				`order_column` = \'\".$data[\'order_column\'].\"\',\r\n				`order_direction` = \".$data[\'order_direction\'].\",\r\n				`show_spam` = \".$data[\'show_spam\'].\",\r\n				`messages_per_page` = \".$data[\'messages_per_page\'].\"\r\n			\",\r\n			\"\r\n				`user_id` = \".$user_id.\"\r\n			\",\r\n			KUO_COM_DB_DEBUG\r\n		);\r\n		\r\n		return $save;\r\n	}\r\n	else {\r\n		return false;\r\n	}\r\n}\r\n\r\nfunction kuo_com_css() {\r\n	\r\n	return \'\r\n		<style type=\"text/css\" media=\"all\">\r\n			#kuo_com_settings ul,\r\n			#kuo_com_search ul,\r\n			#kuo_com_delete ul,\r\n			.kuo_com_nav ul {\r\n				list-style: none;\r\n				margin-left: 0;\r\n				padding-left: 0;\r\n			}\r\n			.kuo_com_nav li,\r\n			.kuo_com_nav a,\r\n			#kuo_com_settings li,\r\n			#kuo_com_search li,\r\n			#kuo_com_delete li {\r\n				display: inline-block;\r\n			}\r\n			#kuo_com_settings li:not(:last-of-type) {\r\n				padding-right: 1.5em;\r\n				margin-right: 1.5em;\r\n				line-height: 200%;\r\n			}\r\n			.kuo_com_nav a {\r\n				padding: 0.5em 1em;\r\n				background: #eee;\r\n				border: 1px solid #ccc;\r\n				border-right-width: 0;\r\n			}\r\n			.kuo_com_nav li:last-of-type a {\r\n				border-right-width: 1px;\r\n			}\r\n			.kuo_com_nav .selected a {\r\n				font-weight: bold;\r\n				background: green;\r\n				color: #fff;\r\n				border-color: darkgreen;\r\n			}\r\n			tr.important td {\r\n				background: gold;\r\n			}\r\n			tr.spam td {\r\n				background: #eee;\r\n			}\r\n			tr.spam ul {\r\n				color: #999;\r\n				text-decoration: line-through;\r\n			}\r\n			tr.spam.important td {\r\n				background: #ffc;\r\n			}\r\n		</style>\r\n	\';\r\n}\r\n\r\nfunction kuo_com_search($term = null) {\r\n	\r\n	$form = \'\r\n		<form method=\"post\" action=\"#kuo_com_search\" id=\"kuo_com_search\">\r\n			<fieldset>\r\n				<legend>Search messages</legend>\r\n				<ul>\r\n					<li><label for=\"kuo_term\">Search \r\n					message content, message UID, \r\n					referring URL:&nbsp;</label>\'.fInput(\r\n						\'search\',\r\n						\'kuo_com_search\',\r\n						$term,\r\n						\'\',\r\n						\'\',\r\n						\'\',\r\n						40,\r\n						0,\r\n						\'kuo_term\',\r\n						false,\r\n						true,\r\n						\'\'\r\n					).\'</li>\r\n					<li><button type=\"submit\">\'.gTxt(\'search\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	return $form;\r\n}\r\n\r\n\r\nfunction kuo_com_delete($status = 2) {\r\n	\r\n	if ($status == 0) {\r\n		$status = \'<p><mark>Unable to delete.</mark></p>\';\r\n	}\r\n	elseif ($status == 1) {\r\n		$status = \'<p><mark>Messages were deleted.</mark></p>\';\r\n	}\r\n	else {\r\n		$status = null;\r\n	}\r\n	\r\n	$form = \'\r\n		<form method=\"post\" action=\"#kuo_com_delete\" id=\"kuo_com_delete\">\r\n			<fieldset>\r\n				<legend>Delete messages (\'.kuo_com_count(\'spam\').\')</legend>\r\n				\'.$status.\'\r\n				<ul>\r\n					<li>\'.checkbox2(\r\n						\'kuo_com_delete\',\r\n						0,\r\n						0,\r\n						\'kuo_delete\'\r\n					).\'<label for=\"kuo_delete\">&nbsp;Yes, delete</label></li>\r\n					<li>\'.selectInput(\r\n						\'kuo_com_delete_type\',\r\n						array(\r\n							0=>\'messages marked as spam (\'.kuo_com_count(\'spam\').\')\',\r\n							1=>\'messages marked as important (\'.kuo_com_count(\'important\').\')\',\r\n							2=>\'all messages (\'.kuo_com_count(\'all\').\')\'\r\n						),\r\n						\'0\',\r\n						true,\r\n						\'\',\r\n						\'\',\r\n						false,\r\n						false\r\n					).\'</li>\r\n					<li><button type=\"submit\">\'.gTxt(\'submit\').\'</button></li>\r\n				</ul>\r\n			</fieldset>\r\n		</form>\r\n	\';\r\n	\r\n	return $form;\r\n}', 'bb3c070c2cc176cfdf7c979a1260da74', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_contact', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'A basic HTML5 contact form.', '<article>\n<h2>About this plugin</h2>\n<p><code>kuo_contact</code> creates a basic contact form in HTML5. It sends messages using PHP\'s <code>mail()</code> function.</p>\n<p>To configure the plugin, please open the <strong>Plugins</strong> section and from there open the <code>kuo_contact</code>\'s source code. After saving configuration, remember to check that plugin is activated. Then you can place the actual contact form in any <strong>Form</strong> or <strong>Page</strong> using this simple Textpattern tag:</p>\n<p><code>&lt;txp:kuo_contact /&gt;</code></p>\n<h3>Notes</h3>\n<p>This plugin only creates HTML5 markup and sends emails &mdash; it doesn\'t include any CSS for styling the form or status messages.</p>\n<p>Contact form has the following elements:</p>\n<ol>\n<li>Legend (the heading of form)</li>\n<li>Message (<code>textarea</code> element)</li>\n<li>Name (<code>input</code> element)</li>\n<li>Email (<code>input</code> element)</li>\n<li>Send button (<code>button</code> element)</li>\n</ol>\n</article>', '\r\n\r\n# BEGINS: SETTINGS\r\nconst\r\n    KUO_RECIPIENT_EMAIL = \'your@email.address\',\r\n    KUO_SPAMMER_FRUSTRATOR = \'296lolecc082cd710.e0fc0c69a66\',\r\n\r\n    KUO_CSS_CLASS_SUCCESS = \'success\',\r\n    KUO_CSS_CLASS_ERROR = \'error\',\r\n\r\n    KUO_HTML_DIR_TAG = \'ltr\',\r\n    KUO_HTML_LANG_TAG = \'en\',\r\n\r\n    KUO_MESSAGE_MIN_LENGTH = 5,\r\n    KUO_MESSAGE_MAX_LENGTH = 5000,\r\n    KUO_NAME_MIN_LENGTH = 1,\r\n    KUO_NAME_MAX_LENGTH = 50,\r\n    KUO_EMAIL_MIN_LENGTH = 4,\r\n    KUO_EMAIL_MAX_LENGTH = 50,\r\n\r\n    KUO_LANG_LEGEND = \'Send a message to the webmaster\',\r\n    KUO_LANG_MESSAGE = \'Message\',\r\n    KUO_LANG_NAME = \'Your name\',\r\n    KUO_LANG_EMAIL = \'Your email\',\r\n    KUO_LANG_SEND = \'Send\',\r\n    KUO_LANG_MESSAGE_HINT = \'Please write a message\',\r\n    KUO_LANG_NAME_HINT = \'Please write your name or nick\',\r\n    KUO_LANG_EMAIL_HINT = \'Please write your email address\',\r\n    KUO_LAN_AN_ERROR_BEGINS = \'Please check that\',\r\n    KUO_LAN_AN_ERROR_ENDS = \'has valid data\',\r\n    KUO_LAN_FORM_HAS_EXPIRED = \'The form has expired.\',\r\n    KUO_LAN_MESSAGE_WAS_SENT = \'Thank you! Your message was sent.\',\r\n    KUO_LAN_MESSAGE_NOT_SENT = \'Oh goodness, there\\\'s a problem. Unable to send email.\',\r\n    KUO_LAN_MESSAGE_TITLE = \'A message from contact form | Yourwebsite.com\'\r\n    ;\r\n# ENDS: SETTINGS\r\n\r\n# BEGINS: PUBLIC\r\nif (@txpinterface === \'public\') {\r\n\r\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'kuo_contact\');\r\n    }\r\n\r\n    # BEGINS: FUNCTION: CREATE A USER-SPECIFIC HTML NAME VALUE\r\n    function kuo_secure_html_name($name) {\r\n        if ((isset($name)) && (!empty($name))) {\r\n\r\n            if ((isset($_SERVER[\'REMOTE_ADDR\'])) && (!empty($_SERVER[\'REMOTE_ADDR\']))) {\r\n                $name = filter_var($_SERVER[\'REMOTE_ADDR\'],FILTER_SANITIZE_STRING).KUO_SPAMMER_FRUSTRATOR.$name;\r\n            }\r\n            else {\r\n                $name = KUO_SPAMMER_FRUSTRATOR.$name;\r\n            }\r\n\r\n            $name = date(\'m\').$name;\r\n\r\n            if (function_exists(\'sha1\')) {\r\n                return sha1($name);\r\n            }\r\n            elseif (function_exists(\'md5\')) {\r\n                return md5($name);\r\n            }\r\n            else {\r\n                return false;\r\n            }\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE A USER-SPECIFIC HTML NAME VALUE\r\n\r\n    # BEGINS: FUNCTION: CREATE THE HTML5 OF CONTACT FORM\r\n    function kuo_contact() {\r\n\r\n        # CONTAINERS\r\n        $kuo_mail_status = 0;\r\n\r\n        # BEGINS: PROMISING DATA WAS SENT WITH $_POST\r\n        if ((isset($_POST)) && (!empty($_POST)) && (is_array($_POST)) && (count($_POST) == 5)) {\r\n            $kuo_mail_status = kuo_process($_POST);\r\n\r\n            # BEGINS: ERRORS\r\n            if (!in_array($kuo_mail_status,array(0,1))) {\r\n                return \'<ul class=\"\'.KUO_CSS_CLASS_ERROR.\'\">\'.$kuo_mail_status.\'</ul>\';\r\n            }\r\n            # ENDS: ERRORS\r\n            # BEGINS: MAIL() WORKED\r\n            elseif ($kuo_mail_status == 1) {\r\n                return \'<p class=\"\'.KUO_CSS_CLASS_SUCCESS.\'\"><strong>\'.KUO_LAN_MESSAGE_WAS_SENT.\'\r\n                </strong></p>\';\r\n            }\r\n            # ENDS: MAIL() WORKED\r\n            # BEGINS: UNABLE TO SEND EMAIL\r\n            else {\r\n                return \'<p class=\"\'.KUO_CSS_CLASS_ERROR.\'\"><strong>\'.KUO_LAN_MESSAGE_NOT_SENT.\'\r\n                </strong></p>\';\r\n            }\r\n            # ENDS: UNABLE TO SEND EMAIL\r\n        }\r\n        # ENDS: PROMISING DATA WAS SENT WITH $_POST\r\n\r\n        # BEGINS: FORM HASN\'T BEEN SENT\r\n        if ($kuo_mail_status != 1) {\r\n            return \'\r\n            <form method=\"post\" autocomplete=\"off\" dir=\"\'.KUO_HTML_DIR_TAG.\'\" lang=\"\'.KUO_HTML_LANG_TAG.\'\">\r\n                <input type=\"hidden\" name=\"\'.kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR).\'\"\r\n                value=\"\'.base64_encode(time()).\'\" />\r\n                <fieldset>\r\n                    <legend>\'.KUO_LANG_LEGEND.\'</legend>\r\n                    <ul>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE.\'-id\').\'\"\r\n                        >\'.KUO_LANG_MESSAGE.\':</label>\r\n                        <textarea id=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE).\'\"\r\n                        cols=\"35\"\r\n                        rows=\"6\"\r\n                        minlength=\"\'.KUO_MESSAGE_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_MESSAGE_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_MESSAGE_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_MESSAGE_HINT.\'\"\r\n                        ></textarea></li>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_NAME.\'-id\').\'\">\'.KUO_LANG_NAME.\':</label>\r\n                        <input\r\n                        type=\"text\"\r\n                        id=\"\'.kuo_secure_html_name(KUO_LANG_NAME.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_NAME).\'\"\r\n                        size=\"25\"\r\n                        minlength=\"\'.KUO_NAME_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_NAME_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        value=\"\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_NAME_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_NAME_HINT.\'\"\r\n                        /></li>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL.\'-id\').\'\">\'.KUO_LANG_EMAIL.\':</label>\r\n                        <input\r\n                        type=\"email\"\r\n                        id=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL).\'\"\r\n                        size=\"25\"\r\n                        minlength=\"\'.KUO_EMAIL_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_EMAIL_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        value=\"\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_EMAIL_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_EMAIL_HINT.\'\"\r\n                        /></li>\r\n                        <li><button type=\"submit\" name=\"\'.kuo_secure_html_name(KUO_LANG_SEND).\'\"\r\n                        >\'.KUO_LANG_SEND.\'</button></li>\r\n                    </ul>\r\n                </fieldset>\r\n            </form>\r\n            \';\r\n        }\r\n        # ENDS: FORM HASN\'T BEEN SENT\r\n    }\r\n    # ENDS: FUNCTION: CREATE THE HTML5 OF CONTACT FORM\r\n\r\n    # BEGINS: FUNCTION: PROCESS FORM SUBMISSION\r\n    function kuo_process($post) {\r\n\r\n        # BEGINS: POST SEEMS TO CONTAIN ALL NECESSARY PARTS\r\n        if ((isset(\r\n            $post[kuo_secure_html_name(KUO_LANG_MESSAGE)],\r\n            $post[kuo_secure_html_name(KUO_LANG_NAME)],\r\n            $post[kuo_secure_html_name(KUO_LANG_EMAIL)],\r\n            $post[kuo_secure_html_name(KUO_LANG_SEND)],\r\n            $post[kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR)]\r\n        ))\r\n        && (filter_var($post[kuo_secure_html_name(KUO_LANG_EMAIL)],FILTER_VALIDATE_EMAIL))\r\n        && (empty($post[kuo_secure_html_name(KUO_LANG_SEND)]))\r\n        ) {\r\n\r\n            # BEGINS: CONTAINERS\r\n            $kuo_messages_array = array();\r\n            $kuo_messages_html = NULL;\r\n            $kuo_sleep_time = 0;\r\n\r\n            $kuo_message = filter_var($post[kuo_secure_html_name(KUO_LANG_MESSAGE)],FILTER_SANITIZE_STRING);\r\n            $kuo_name = filter_var($post[kuo_secure_html_name(KUO_LANG_NAME)],FILTER_SANITIZE_STRING);\r\n            $kuo_email = filter_var($post[kuo_secure_html_name(KUO_LANG_EMAIL)],FILTER_SANITIZE_EMAIL);\r\n            $kuo_timestamp = base64_decode($post[kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR)]);\r\n\r\n            $kuo_email_headers = \"From: \".$kuo_name.\" <\".$kuo_email.\">\\r\\n\";\r\n            $kuo_email_headers .= \"Reply-To: \".$kuo_name.\" <\".$kuo_email.\">\\r\\n\";\r\n            $kuo_email_headers .= \"Content-Type: text/plain;charset=UTF-8\\r\\n\";\r\n            $kuo_email_headers .= \"X-Mailer: PHP/\".phpversion();\r\n            # ENDS: CONTAINERS\r\n\r\n            # BEGINS: MESSAGE LENGTH IS NOT OK\r\n            if ((strlen($kuo_message) < KUO_MESSAGE_MIN_LENGTH)\r\n            or (strlen($kuo_message) > KUO_MESSAGE_MAX_LENGTH)) {\r\n                array_push($kuo_messages_array,KKUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.UO_LANG_MESSAGE.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: MESSAGE LENGTH IS NOT OK\r\n\r\n            # BEGINS: NAME LENGTH IS NOT OK\r\n            if ((strlen($kuo_name) < KUO_NAME_MIN_LENGTH)\r\n            or (strlen($kuo_name) > KUO_NAME_MAX_LENGTH)) {\r\n                array_push($kuo_messages_array,KUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.KUO_LANG_NAME.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: NAME LENGTH IS NOT OK\r\n\r\n            # BEGINS: EMAIL LENGTH OR FORMAT IS NOT OK\r\n            if ((strlen($kuo_email) < KUO_EMAIL_MIN_LENGTH)\r\n            or (strlen($kuo_email) > KUO_EMAIL_MAX_LENGTH)\r\n            or (!filter_var($kuo_email,FILTER_VALIDATE_EMAIL))) {\r\n                array_push($kuo_messages_array,KUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.KUO_LANG_EMAIL.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: EMAIL LENGTH OR FORMAT IS NOT OK\r\n\r\n            # BEGINS: TIMESTAMP OF FORM CREATION IS NOT OK\r\n            if ((!is_numeric($kuo_timestamp)) or ($kuo_timestamp < strtotime(\'20 minutes ago\'))) {\r\n                array_push($kuo_messages_array,KUO_LAN_FORM_HAS_EXPIRED);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: TIMESTAMP OF FORM CREATION IS NOT OK\r\n\r\n            unset($_POST);\r\n\r\n            # BEGINS: THERE ARE ERRORS\r\n            if (count($kuo_messages_array) != 0) {\r\n                # BEGINS: FOREACH\r\n                foreach ($kuo_messages_array as $message) {\r\n                    $kuo_messages_html .= \'<li>\'.$message.\'</li>\';\r\n                }\r\n                # ENDS: FOREACH\r\n\r\n                sleep($kuo_sleep_time);\r\n\r\n                return $kuo_messages_html;\r\n            }\r\n            # ENDS: THERE ARE ERRORS\r\n            # BEGINS: THIS MESSAGE CAN BE SENT\r\n            else {\r\n\r\n                # BEGINS: REQUIRED FUNCTION EXISTS\r\n                if (function_exists(\'mail\')) {\r\n\r\n                    $kuo_mail_status = mail(\r\n                        KUO_RECIPIENT_EMAIL,\r\n                        KUO_LAN_MESSAGE_TITLE,\r\n                        wordwrap($kuo_message),\r\n                        $kuo_email_headers\r\n                    );\r\n\r\n                    unset(\r\n                        $kuo_message,\r\n                        $kuo_name,\r\n                        $kuo_email,\r\n                        $kuo_timestamp,\r\n                        $kuo_email_headers,\r\n                        $kuo_messages_html,\r\n                        $kuo_messages_array,\r\n                        $kuo_sleep_time\r\n                    );\r\n\r\n                    # BEGINS: MAIL() WORKED\r\n                    if ($kuo_mail_status) {\r\n                        return 1;\r\n                    }\r\n                    # ENDS: MAIL() WORKED\r\n                    # BEGINS: UNABLE TO SEND EMAIL\r\n                    else {\r\n                        return 0;\r\n                    }\r\n                    # ENDS: UNABLE TO SEND EMAIL\r\n                }\r\n                # ENDS: REQUIRED FUNCTION EXISTS\r\n                # BEGINS: CAN NOT SEND: MAIL() IS MISSING\r\n                else {\r\n                    return 0;\r\n                }\r\n                # ENDS: CAN NOT SEND; MAIL() IS MISSING\r\n            }\r\n            # ENDS: THIS MESSAGE CAN BE SENT\r\n        }\r\n        # ENDS: POST SEEMS TO CONTAIN ALL NECESSARY PARTS\r\n        # BEGINS: REQUIRED DATA FROM $_POST IS MISSING\r\n        else {\r\n            return 0;\r\n        }\r\n        # ENDS: REQUIRED DATA FROM $_POST IS MISSING\r\n    }\r\n    # ENDS: FUNCTION: PROCESS FORM SUBMISSION\r\n}\r\n# ENDS: PUBLIC\r\n', '\r\n\r\n# BEGINS: SETTINGS\r\nconst\r\n    KUO_RECIPIENT_EMAIL = \'your@email.address\',\r\n    KUO_SPAMMER_FRUSTRATOR = \'296lolecc082cd710.e0fc0c69a66\',\r\n\r\n    KUO_CSS_CLASS_SUCCESS = \'success\',\r\n    KUO_CSS_CLASS_ERROR = \'error\',\r\n\r\n    KUO_HTML_DIR_TAG = \'ltr\',\r\n    KUO_HTML_LANG_TAG = \'en\',\r\n\r\n    KUO_MESSAGE_MIN_LENGTH = 5,\r\n    KUO_MESSAGE_MAX_LENGTH = 5000,\r\n    KUO_NAME_MIN_LENGTH = 1,\r\n    KUO_NAME_MAX_LENGTH = 50,\r\n    KUO_EMAIL_MIN_LENGTH = 4,\r\n    KUO_EMAIL_MAX_LENGTH = 50,\r\n\r\n    KUO_LANG_LEGEND = \'Send a message to the webmaster\',\r\n    KUO_LANG_MESSAGE = \'Message\',\r\n    KUO_LANG_NAME = \'Your name\',\r\n    KUO_LANG_EMAIL = \'Your email\',\r\n    KUO_LANG_SEND = \'Send\',\r\n    KUO_LANG_MESSAGE_HINT = \'Please write a message\',\r\n    KUO_LANG_NAME_HINT = \'Please write your name or nick\',\r\n    KUO_LANG_EMAIL_HINT = \'Please write your email address\',\r\n    KUO_LAN_AN_ERROR_BEGINS = \'Please check that\',\r\n    KUO_LAN_AN_ERROR_ENDS = \'has valid data\',\r\n    KUO_LAN_FORM_HAS_EXPIRED = \'The form has expired.\',\r\n    KUO_LAN_MESSAGE_WAS_SENT = \'Thank you! Your message was sent.\',\r\n    KUO_LAN_MESSAGE_NOT_SENT = \'Oh goodness, there\\\'s a problem. Unable to send email.\',\r\n    KUO_LAN_MESSAGE_TITLE = \'A message from contact form | Yourwebsite.com\'\r\n    ;\r\n# ENDS: SETTINGS\r\n\r\n# BEGINS: PUBLIC\r\nif (@txpinterface === \'public\') {\r\n\r\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'kuo_contact\');\r\n    }\r\n\r\n    # BEGINS: FUNCTION: CREATE A USER-SPECIFIC HTML NAME VALUE\r\n    function kuo_secure_html_name($name) {\r\n        if ((isset($name)) && (!empty($name))) {\r\n\r\n            if ((isset($_SERVER[\'REMOTE_ADDR\'])) && (!empty($_SERVER[\'REMOTE_ADDR\']))) {\r\n                $name = filter_var($_SERVER[\'REMOTE_ADDR\'],FILTER_SANITIZE_STRING).KUO_SPAMMER_FRUSTRATOR.$name;\r\n            }\r\n            else {\r\n                $name = KUO_SPAMMER_FRUSTRATOR.$name;\r\n            }\r\n\r\n            $name = date(\'m\').$name;\r\n\r\n            if (function_exists(\'sha1\')) {\r\n                return sha1($name);\r\n            }\r\n            elseif (function_exists(\'md5\')) {\r\n                return md5($name);\r\n            }\r\n            else {\r\n                return false;\r\n            }\r\n        }\r\n        else {\r\n            return false;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE A USER-SPECIFIC HTML NAME VALUE\r\n\r\n    # BEGINS: FUNCTION: CREATE THE HTML5 OF CONTACT FORM\r\n    function kuo_contact() {\r\n\r\n        # CONTAINERS\r\n        $kuo_mail_status = 0;\r\n\r\n        # BEGINS: PROMISING DATA WAS SENT WITH $_POST\r\n        if ((isset($_POST)) && (!empty($_POST)) && (is_array($_POST)) && (count($_POST) == 5)) {\r\n            $kuo_mail_status = kuo_process($_POST);\r\n\r\n            # BEGINS: ERRORS\r\n            if (!in_array($kuo_mail_status,array(0,1))) {\r\n                return \'<ul class=\"\'.KUO_CSS_CLASS_ERROR.\'\">\'.$kuo_mail_status.\'</ul>\';\r\n            }\r\n            # ENDS: ERRORS\r\n            # BEGINS: MAIL() WORKED\r\n            elseif ($kuo_mail_status == 1) {\r\n                return \'<p class=\"\'.KUO_CSS_CLASS_SUCCESS.\'\"><strong>\'.KUO_LAN_MESSAGE_WAS_SENT.\'\r\n                </strong></p>\';\r\n            }\r\n            # ENDS: MAIL() WORKED\r\n            # BEGINS: UNABLE TO SEND EMAIL\r\n            else {\r\n                return \'<p class=\"\'.KUO_CSS_CLASS_ERROR.\'\"><strong>\'.KUO_LAN_MESSAGE_NOT_SENT.\'\r\n                </strong></p>\';\r\n            }\r\n            # ENDS: UNABLE TO SEND EMAIL\r\n        }\r\n        # ENDS: PROMISING DATA WAS SENT WITH $_POST\r\n\r\n        # BEGINS: FORM HASN\'T BEEN SENT\r\n        if ($kuo_mail_status != 1) {\r\n            return \'\r\n            <form method=\"post\" autocomplete=\"off\" dir=\"\'.KUO_HTML_DIR_TAG.\'\" lang=\"\'.KUO_HTML_LANG_TAG.\'\">\r\n                <input type=\"hidden\" name=\"\'.kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR).\'\"\r\n                value=\"\'.base64_encode(time()).\'\" />\r\n                <fieldset>\r\n                    <legend>\'.KUO_LANG_LEGEND.\'</legend>\r\n                    <ul>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE.\'-id\').\'\"\r\n                        >\'.KUO_LANG_MESSAGE.\':</label>\r\n                        <textarea id=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_MESSAGE).\'\"\r\n                        cols=\"35\"\r\n                        rows=\"6\"\r\n                        minlength=\"\'.KUO_MESSAGE_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_MESSAGE_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_MESSAGE_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_MESSAGE_HINT.\'\"\r\n                        ></textarea></li>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_NAME.\'-id\').\'\">\'.KUO_LANG_NAME.\':</label>\r\n                        <input\r\n                        type=\"text\"\r\n                        id=\"\'.kuo_secure_html_name(KUO_LANG_NAME.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_NAME).\'\"\r\n                        size=\"25\"\r\n                        minlength=\"\'.KUO_NAME_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_NAME_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        value=\"\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_NAME_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_NAME_HINT.\'\"\r\n                        /></li>\r\n                        <li><label for=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL.\'-id\').\'\">\'.KUO_LANG_EMAIL.\':</label>\r\n                        <input\r\n                        type=\"email\"\r\n                        id=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL.\'-id\').\'\"\r\n                        name=\"\'.kuo_secure_html_name(KUO_LANG_EMAIL).\'\"\r\n                        size=\"25\"\r\n                        minlength=\"\'.KUO_EMAIL_MIN_LENGTH.\'\"\r\n                        maxlength=\"\'.KUO_EMAIL_MAX_LENGTH.\'\"\r\n                        required=\"required\"\r\n                        value=\"\"\r\n                        x-moz-errormessage=\"\'.KUO_LANG_EMAIL_HINT.\'\"\r\n                        title=\"\'.KUO_LANG_EMAIL_HINT.\'\"\r\n                        /></li>\r\n                        <li><button type=\"submit\" name=\"\'.kuo_secure_html_name(KUO_LANG_SEND).\'\"\r\n                        >\'.KUO_LANG_SEND.\'</button></li>\r\n                    </ul>\r\n                </fieldset>\r\n            </form>\r\n            \';\r\n        }\r\n        # ENDS: FORM HASN\'T BEEN SENT\r\n    }\r\n    # ENDS: FUNCTION: CREATE THE HTML5 OF CONTACT FORM\r\n\r\n    # BEGINS: FUNCTION: PROCESS FORM SUBMISSION\r\n    function kuo_process($post) {\r\n\r\n        # BEGINS: POST SEEMS TO CONTAIN ALL NECESSARY PARTS\r\n        if ((isset(\r\n            $post[kuo_secure_html_name(KUO_LANG_MESSAGE)],\r\n            $post[kuo_secure_html_name(KUO_LANG_NAME)],\r\n            $post[kuo_secure_html_name(KUO_LANG_EMAIL)],\r\n            $post[kuo_secure_html_name(KUO_LANG_SEND)],\r\n            $post[kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR)]\r\n        ))\r\n        && (filter_var($post[kuo_secure_html_name(KUO_LANG_EMAIL)],FILTER_VALIDATE_EMAIL))\r\n        && (empty($post[kuo_secure_html_name(KUO_LANG_SEND)]))\r\n        ) {\r\n\r\n            # BEGINS: CONTAINERS\r\n            $kuo_messages_array = array();\r\n            $kuo_messages_html = NULL;\r\n            $kuo_sleep_time = 0;\r\n\r\n            $kuo_message = filter_var($post[kuo_secure_html_name(KUO_LANG_MESSAGE)],FILTER_SANITIZE_STRING);\r\n            $kuo_name = filter_var($post[kuo_secure_html_name(KUO_LANG_NAME)],FILTER_SANITIZE_STRING);\r\n            $kuo_email = filter_var($post[kuo_secure_html_name(KUO_LANG_EMAIL)],FILTER_SANITIZE_EMAIL);\r\n            $kuo_timestamp = base64_decode($post[kuo_secure_html_name(KUO_SPAMMER_FRUSTRATOR)]);\r\n\r\n            $kuo_email_headers = \"From: \".$kuo_name.\" <\".$kuo_email.\">\\r\\n\";\r\n            $kuo_email_headers .= \"Reply-To: \".$kuo_name.\" <\".$kuo_email.\">\\r\\n\";\r\n            $kuo_email_headers .= \"Content-Type: text/plain;charset=UTF-8\\r\\n\";\r\n            $kuo_email_headers .= \"X-Mailer: PHP/\".phpversion();\r\n            # ENDS: CONTAINERS\r\n\r\n            # BEGINS: MESSAGE LENGTH IS NOT OK\r\n            if ((strlen($kuo_message) < KUO_MESSAGE_MIN_LENGTH)\r\n            or (strlen($kuo_message) > KUO_MESSAGE_MAX_LENGTH)) {\r\n                array_push($kuo_messages_array,KKUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.UO_LANG_MESSAGE.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: MESSAGE LENGTH IS NOT OK\r\n\r\n            # BEGINS: NAME LENGTH IS NOT OK\r\n            if ((strlen($kuo_name) < KUO_NAME_MIN_LENGTH)\r\n            or (strlen($kuo_name) > KUO_NAME_MAX_LENGTH)) {\r\n                array_push($kuo_messages_array,KUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.KUO_LANG_NAME.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: NAME LENGTH IS NOT OK\r\n\r\n            # BEGINS: EMAIL LENGTH OR FORMAT IS NOT OK\r\n            if ((strlen($kuo_email) < KUO_EMAIL_MIN_LENGTH)\r\n            or (strlen($kuo_email) > KUO_EMAIL_MAX_LENGTH)\r\n            or (!filter_var($kuo_email,FILTER_VALIDATE_EMAIL))) {\r\n                array_push($kuo_messages_array,KUO_LAN_AN_ERROR_BEGINS.\' <strong>\'.KUO_LANG_EMAIL.\'</strong>\r\n                \'.KUO_LAN_AN_ERROR_ENDS);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: EMAIL LENGTH OR FORMAT IS NOT OK\r\n\r\n            # BEGINS: TIMESTAMP OF FORM CREATION IS NOT OK\r\n            if ((!is_numeric($kuo_timestamp)) or ($kuo_timestamp < strtotime(\'20 minutes ago\'))) {\r\n                array_push($kuo_messages_array,KUO_LAN_FORM_HAS_EXPIRED);\r\n                $kuo_sleep_time++;\r\n            }\r\n            # ENDS: TIMESTAMP OF FORM CREATION IS NOT OK\r\n\r\n            unset($_POST);\r\n\r\n            # BEGINS: THERE ARE ERRORS\r\n            if (count($kuo_messages_array) != 0) {\r\n                # BEGINS: FOREACH\r\n                foreach ($kuo_messages_array as $message) {\r\n                    $kuo_messages_html .= \'<li>\'.$message.\'</li>\';\r\n                }\r\n                # ENDS: FOREACH\r\n\r\n                sleep($kuo_sleep_time);\r\n\r\n                return $kuo_messages_html;\r\n            }\r\n            # ENDS: THERE ARE ERRORS\r\n            # BEGINS: THIS MESSAGE CAN BE SENT\r\n            else {\r\n\r\n                # BEGINS: REQUIRED FUNCTION EXISTS\r\n                if (function_exists(\'mail\')) {\r\n\r\n                    $kuo_mail_status = mail(\r\n                        KUO_RECIPIENT_EMAIL,\r\n                        KUO_LAN_MESSAGE_TITLE,\r\n                        wordwrap($kuo_message),\r\n                        $kuo_email_headers\r\n                    );\r\n\r\n                    unset(\r\n                        $kuo_message,\r\n                        $kuo_name,\r\n                        $kuo_email,\r\n                        $kuo_timestamp,\r\n                        $kuo_email_headers,\r\n                        $kuo_messages_html,\r\n                        $kuo_messages_array,\r\n                        $kuo_sleep_time\r\n                    );\r\n\r\n                    # BEGINS: MAIL() WORKED\r\n                    if ($kuo_mail_status) {\r\n                        return 1;\r\n                    }\r\n                    # ENDS: MAIL() WORKED\r\n                    # BEGINS: UNABLE TO SEND EMAIL\r\n                    else {\r\n                        return 0;\r\n                    }\r\n                    # ENDS: UNABLE TO SEND EMAIL\r\n                }\r\n                # ENDS: REQUIRED FUNCTION EXISTS\r\n                # BEGINS: CAN NOT SEND: MAIL() IS MISSING\r\n                else {\r\n                    return 0;\r\n                }\r\n                # ENDS: CAN NOT SEND; MAIL() IS MISSING\r\n            }\r\n            # ENDS: THIS MESSAGE CAN BE SENT\r\n        }\r\n        # ENDS: POST SEEMS TO CONTAIN ALL NECESSARY PARTS\r\n        # BEGINS: REQUIRED DATA FROM $_POST IS MISSING\r\n        else {\r\n            return 0;\r\n        }\r\n        # ENDS: REQUIRED DATA FROM $_POST IS MISSING\r\n    }\r\n    # ENDS: FUNCTION: PROCESS FORM SUBMISSION\r\n}\r\n# ENDS: PUBLIC\r\n', '5e530e1e11b28afdb1408a11744d73c7', '', '', 0, 5, 0),
('kuo_disable_search', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Prevent users from making search queries with Textpattern\'s internal front-side search engine.', '', 'if (isset($_GET[\'q\'])) {\r\nhttp_response_code(403);\r\ndie;\r\n}', 'if (isset($_GET[\'q\'])) {\r\nhttp_response_code(403);\r\ndie;\r\n}', '1dc3fe688c59ff36fc7a3b9f896e861e', '', '', 0, 1, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_file_stats', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Collecting data about Textpattern\'s Files to understand when they are accessed and by whom.', '<ul id=\"links\">\n<li id=\"kuo_file_stats\"><a href=\"#kuo_file_stats\">kuo_file_stats</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>Each time someone opens a file that\'s saved to Textpattern\'s Files\' section, some information about the user is saved to the database. That information can then be accessed from Files. The plugin saves date and time when file was accessed, the IP address, as well Textpattern username and privilege (if the user in question was logged in to Textpattern when accessing a file), and other details.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin <code>kuo_file_stats</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin <code>kuo_file_stats</code>.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '\r\n# BEGINS: ADMIN-SIDE FUNCTIONS\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_file_stats_uninstall\',\'plugin_lifecycle.kuo_file_stats\',\'disabled\');\r\n    register_callback(\'kuo_file_stats_deleted\',\'plugin_lifecycle.kuo_file_stats\',\'deleted\');\r\n    register_callback(\'kuo_file_stats_install\',\'plugin_lifecycle.kuo_file_stats\',\'installed\');\r\n    register_callback(\'kuo_file_stats_install\',\'plugin_lifecycle.kuo_file_stats\',\'enabled\');\r\n    register_callback(\'kuo_file_stats_information\',\'file_ui\',\'extend_detail_form\');\r\n\r\n    # THIS REMOVES JUST THE SETTINGS FOR THIS PLUGIN\r\n    function kuo_file_stats_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(\'kuo_file_stats\').\"`\",0);\r\n    }\r\n\r\n    # IF PLUGIN IS SIMPLY DELETED THEN THIS IS TRIGGERED\r\n    function kuo_file_stats_deleted() {\r\n        kuo_file_stats_uninstall();\r\n    }\r\n\r\n    # THIS IN TURN CREATES A PLACE WHERE FILE DOWNLOAD DATA IS SAVED\r\n    function kuo_file_stats_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_file_stats\').\" (\r\n            `id` INT(11) NOT NULL AUTO_INCREMENT,\r\n            `file_id` SMALLINT UNSIGNED NOT NULL,\r\n            `file_name` VARCHAR(255) NOT NULL,\r\n            `ip` VARCHAR(45) NOT NULL,\r\n            `username` VARCHAR(255) NULL DEFAULT NULL COMMENT \'If user is logged in\',\r\n            `privs` TINYINT(1) UNSIGNED NULL DEFAULT NULL COMMENT \'If user is logged in\',\r\n            `when` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n            `misc` TEXT NOT NULL COMMENT \'Contains serialized data\',\r\n            PRIMARY KEY(`id`)\r\n        )\",0);\r\n    }\r\n    \r\n    # THIS IS THE FUNCTION THAT IS LAUNCHED WHEN ADMIN IS VIEWING A SPECIFIC (SINGLE) FILE\r\n    function kuo_file_stats_information() {\r\n        if ((isset($_GET[\'id\'])) && (!empty($_GET[\'id\'])) && (is_numeric($_GET[\'id\'])) && (safe_count(\'kuo_file_stats\',\'`file_id` = \"\'.doSlash($_GET[\'id\']).\'\"\',0) != 0)) {\r\n            \r\n            $kuo_file_stats = safe_rows(\"`id`, `ip`, `username`, `privs`, `when`, `misc`\",\"kuo_file_stats\",\"`file_id` = \'\".doSlash($_GET[\'id\']).\"\' ORDER BY `id` DESC LIMIT 10\",0);\r\n            \r\n            $kuo_file_stats_html = NULL;\r\n            define(\'KUO_FILE_STATS_NBSP\',\'&nbsp;\');\r\n            \r\n            # LETS\'S SEE IF DATABASE HAS MATCHING DATA\r\n            if (($kuo_file_stats) && (!empty($kuo_file_stats))) {\r\n                $kuo_stats_counter = 1;\r\n                foreach ($kuo_file_stats as $kuo_stats) {\r\n\r\n                    # IF IP ADDRESS OF SOME SORT IS AVAILABLE\r\n                    $kuo_file_stats_ip = filter_var($kuo_stats[\'ip\'],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_file_stats_ip)) {\r\n                        $kuo_file_stats_ip = KUO_FILE_STATS_NBSP;\r\n                    }\r\n\r\n                    # IF USERNAME IS AVAILABLE\r\n                    $kuo_file_stats_username = filter_var($kuo_stats[\'username\'],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_file_stats_username)) {\r\n                        $kuo_file_stats_username = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    \r\n                    # IF PRIVILEGES IS AVAILABLE\r\n                    $kuo_file_stats_privs = filter_var($kuo_stats[\'privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    if ((empty($kuo_file_stats_privs)) or ($kuo_file_stats_privs === \'0\')) {\r\n                        $kuo_file_stats_privs = gTxt(\'privs_none\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 1) {\r\n                        $kuo_file_stats_privs = gTxt(\'publisher\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 2) {\r\n                        $kuo_file_stats_privs = gTxt(\'managing_editor\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 3) {\r\n                        $kuo_file_stats_privs = gTxt(\'copy_editor\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 4) {\r\n                        $kuo_file_stats_privs = gTxt(\'staff_writer\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 5) {\r\n                        $kuo_file_stats_privs = gTxt(\'freelancer\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 6) {\r\n                        $kuo_file_stats_privs = gTxt(\'designer\');\r\n                    }\r\n\r\n                    # WHEN FILE WAS ACCESSED\r\n                    $kuo_file_stats_when = filter_var($kuo_stats[\'when\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    if (empty($kuo_file_stats_when)) {\r\n                        $kuo_file_stats_when = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    else {\r\n                        \r\n                        # CHECK HOW DATE AND TIME SHOULD BE FORMATTED\r\n                        # AT SOME POINT SHOULD CHECK HOW US/UK DATE FORMAT IS\r\n                        if ((isset($GLOBALS[\'prefs\'][\'language\'])) && (!empty($GLOBALS[\'prefs\'][\'language\']))) {\r\n                            if (in_array($GLOBALS[\'prefs\'][\'language\'],array(\'en-gb\',\'en-us\'))) {\r\n                                $kuo_file_stats_when_format = \'m/d/Y H:i\';\r\n                            }\r\n                            else {\r\n                                $kuo_file_stats_when_format = \'j.n.Y H.i\';\r\n                            }\r\n                        }\r\n                        else {\r\n                            $kuo_file_stats_when_format = \'m/d/Y H:i\';\r\n                        }\r\n                        \r\n                        $kuo_file_stats_when = date($kuo_file_stats_when_format,strtotime($kuo_file_stats_when));\r\n                    }\r\n                    \r\n                    # MISC DATA ABOUT USER WHO ACCESSED FILE\r\n                    $kuo_file_stats_misc = filter_var_array(unserialize($kuo_stats[\'misc\']),FILTER_SANITIZE_STRING);\r\n                    if ((!empty($kuo_file_stats_misc)) && (is_array($kuo_file_stats_misc))) {\r\n                        # BEGINS: IF VIEWING DETAILS\r\n                        if ((isset($_GET[\'kuo_file_stats_details\'])) && (!empty($_GET[\'kuo_file_stats_details\'])) && (is_numeric($_GET[\'kuo_file_stats_details\'])) && \r\n                        ($_GET[\'kuo_file_stats_details\'] == filter_var($kuo_stats[\'id\'],FILTER_SANITIZE_NUMBER_INT))) {\r\n                            $kuo_file_stats_misc = \'<ul>\r\n                                <li><strong>User agent:</strong> \'.filter_var($kuo_file_stats_misc[\'user_agent\'],FILTER_SANITIZE_STRING).\'</li>\r\n                                <li><strong>HTTP referrer:</strong> \'.filter_var($kuo_file_stats_misc[\'http_referrer\'],FILTER_SANITIZE_STRING).\'</li>\r\n                                <li><strong><a href=\"?event=file&amp;step=file_edit&amp;id=\'.filter_var($_GET[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'\">\'.gTxt(\'close\').\'</a></strong></li>\r\n                            </ul>\';\r\n                        }\r\n                        else {\r\n                            $kuo_file_stats_misc = \'<a href=\"?event=file&amp;step=file_edit&amp;id=\'.filter_var($_GET[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'&amp;kuo_file_stats_details=\'.filter_var($kuo_stats[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'\">\'.\r\n                            filter_var(substr($kuo_file_stats_misc[\'user_agent\'],0,20),FILTER_SANITIZE_STRING).\'&hellip;</a>\';\r\n                        }\r\n                        # ENDS: IF VIEWING DETAILS\r\n                    }\r\n                    else {\r\n                        $kuo_file_stats_misc = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    \r\n                    $kuo_file_stats_html .= \'<tr>\r\n                            <td>\'.$kuo_stats_counter++.\'.</td>\r\n                            <td>\'.$kuo_file_stats_ip.\'</td>\r\n                            <td>\'.$kuo_file_stats_username.\'</td>\r\n                            <td>\'.$kuo_file_stats_privs.\'</td>\r\n                            <td>\'.$kuo_file_stats_when.\'</td>\r\n                            <td>\'.$kuo_file_stats_misc.\'</td>\r\n                        </tr>\';\r\n                }\r\n                # FOREACH ENDS\r\n            }\r\n        }\r\n        else {\r\n            $kuo_file_stats_html = \'<tr><td colspan=\"6\">\'.gTxt(\'none\').\'</td></tr>\';\r\n        }\r\n        echo \'<style type=\"text/css\">\r\n            #kuo_file_stats {\r\n                border: 10px solid #f0f0f0;\r\n                padding: 3%;\r\n                margin: 1% auto;\r\n                background: #fff;\r\n                }\r\n            #kuo_file_stats table {\r\n                width: 100%;\r\n                border: 0;\r\n                border-collapse: collapse;\r\n                }\r\n            #kuo_file_stats th,\r\n            #kuo_file_stats td {\r\n                padding: 5px;\r\n                border-bottom: 1px solid #f0f0f0;\r\n                }\r\n            #kuo_file_stats tr:nth-child(odd) td {\r\n                background: #ffc;\r\n                }\r\n            </style>\r\n        <div id=\"kuo_file_stats\">\r\n            <h3>Recent file access logs</h3>\r\n            <table>\r\n                <thead>\r\n                    <tr>\r\n                        <th>#</th>\r\n                        <th>IP</th>\r\n                        <th>Username</th>\r\n                        <th>Privs</th>\r\n                        <th>When</th>\r\n                        <th>Details</th>\r\n                    </tr>\r\n                </thead>\r\n                <tbody>\r\n                    \'.$kuo_file_stats_html.\'\r\n                </tbody>\r\n            </table>\r\n        </div>\';\r\n        unset($kuo_file_stats);\r\n        unset($kuo_file_stats_html);\r\n    }\r\n    # FUNCTION ENDS\r\n\r\n}\r\n# ENDS: ADMIN-SIDE FUNCTIONS\r\n\r\n# BEGINS: PUBLIC-SIDE FUNCTIONS\r\nif (@txpinterface === \'public\') {\r\n\r\n    register_callback(\'kuo_file_stats_functions\',\'file_download\');\r\n\r\n    function kuo_file_stats_functions() {\r\n        # GETTING FILE ID\r\n        $kuo_file_id = explode(\'file_download/\',parse_url($_SERVER[\'REQUEST_URI\'],PHP_URL_PATH));\r\n        if ((!empty($kuo_file_id)) && (is_array($kuo_file_id))) {\r\n            define(\'KUO_FILE_STATS_REQUEST_ID\',filter_var(substr($kuo_file_id[1],0,strpos($kuo_file_id[1],\'/\'))),FILTER_SANITIZE_NUMBER_INT);\r\n            unset($kuo_file_id);\r\n        }\r\n        \r\n        # GETTING FILE NAME\r\n        define(\'KUO_FILE_STATS_REQUEST_FILE_NAME\',basename(parse_url($_SERVER[\'REQUEST_URI\'],PHP_URL_PATH)));\r\n        \r\n        # GETTING SOME IP\r\n        define(\'KUO_FILE_STATS_IP\',filter_var($_SERVER[\'REMOTE_ADDR\'],FILTER_SANITIZE_STRING));\r\n        \r\n        # GETTING MISC DATA\r\n        define(\'KUO_FILE_STATS_MISC\',serialize(filter_var_array(array(\r\n            \'user_agent\'=>$_SERVER[\'HTTP_USER_AGENT\'],\r\n            \'http_referrer\'=>$_SERVER[\'HTTP_REFERER\'],\r\n            \'script_filename\'=>$_SERVER[\'SCRIPT_FILENAME\'],\r\n            \'request_time\'=>$_SERVER[\'REQUEST_TIME\']\r\n        ),FILTER_SANITIZE_STRING)));\r\n        \r\n        # GETTING USER DATA IF AVAILABLE\r\n        if (is_logged_in()) {\r\n            define(\'KUO_FILE_STATS_USERNAME\',filter_var(is_logged_in()[\'name\'],FILTER_SANITIZE_STRING));\r\n            define(\'KUO_FILE_STATS_PRIVS\',filter_var(is_logged_in()[\'privs\'],FILTER_SANITIZE_NUMBER_INT));\r\n        }\r\n        else {\r\n            define(\'KUO_FILE_STATS_USERNAME\',NULL);\r\n            define(\'KUO_FILE_STATS_PRIVS\',NULL);\r\n        }\r\n        \r\n        # SAVING DATA\r\n        if ((defined(\'KUO_FILE_STATS_REQUEST_ID\')) && (KUO_FILE_STATS_REQUEST_ID != NULL) && (defined(\'KUO_FILE_STATS_REQUEST_FILE_NAME\')) && (KUO_FILE_STATS_REQUEST_FILE_NAME != NULL)\r\n        && (defined(\'KUO_FILE_STATS_IP\')) && (KUO_FILE_STATS_IP != NULL) && (defined(\'KUO_FILE_STATS_MISC\')) && (KUO_FILE_STATS_MISC != NULL)) {\r\n            safe_query(\'INSERT INTO `\'.safe_pfx(\'kuo_file_stats\').\'` (file_id, file_name, ip, username, privs, misc) VALUES (\"\'.doSlash(KUO_FILE_STATS_REQUEST_ID).\'\", \"\'.doSlash(KUO_FILE_STATS_REQUEST_FILE_NAME).\'\", \r\n            \"\'.doSlash(KUO_FILE_STATS_IP).\'\", \"\'.doSlash(KUO_FILE_STATS_USERNAME).\'\", \"\'.doSlash(KUO_FILE_STATS_PRIVS).\'\", \"\'.doSlash(KUO_FILE_STATS_MISC).\'\")\',1);\r\n        }   \r\n    }\r\n    # FUNCTIONS ENDS\r\n}\r\n# ENDS: PUBLIC-SIDE FUNCTIONS', '\r\n# BEGINS: ADMIN-SIDE FUNCTIONS\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_file_stats_uninstall\',\'plugin_lifecycle.kuo_file_stats\',\'disabled\');\r\n    register_callback(\'kuo_file_stats_deleted\',\'plugin_lifecycle.kuo_file_stats\',\'deleted\');\r\n    register_callback(\'kuo_file_stats_install\',\'plugin_lifecycle.kuo_file_stats\',\'installed\');\r\n    register_callback(\'kuo_file_stats_install\',\'plugin_lifecycle.kuo_file_stats\',\'enabled\');\r\n    register_callback(\'kuo_file_stats_information\',\'file_ui\',\'extend_detail_form\');\r\n\r\n    # THIS REMOVES JUST THE SETTINGS FOR THIS PLUGIN\r\n    function kuo_file_stats_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(\'kuo_file_stats\').\"`\",0);\r\n    }\r\n\r\n    # IF PLUGIN IS SIMPLY DELETED THEN THIS IS TRIGGERED\r\n    function kuo_file_stats_deleted() {\r\n        kuo_file_stats_uninstall();\r\n    }\r\n\r\n    # THIS IN TURN CREATES A PLACE WHERE FILE DOWNLOAD DATA IS SAVED\r\n    function kuo_file_stats_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_file_stats\').\" (\r\n            `id` INT(11) NOT NULL AUTO_INCREMENT,\r\n            `file_id` SMALLINT UNSIGNED NOT NULL,\r\n            `file_name` VARCHAR(255) NOT NULL,\r\n            `ip` VARCHAR(45) NOT NULL,\r\n            `username` VARCHAR(255) NULL DEFAULT NULL COMMENT \'If user is logged in\',\r\n            `privs` TINYINT(1) UNSIGNED NULL DEFAULT NULL COMMENT \'If user is logged in\',\r\n            `when` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n            `misc` TEXT NOT NULL COMMENT \'Contains serialized data\',\r\n            PRIMARY KEY(`id`)\r\n        )\",0);\r\n    }\r\n    \r\n    # THIS IS THE FUNCTION THAT IS LAUNCHED WHEN ADMIN IS VIEWING A SPECIFIC (SINGLE) FILE\r\n    function kuo_file_stats_information() {\r\n        if ((isset($_GET[\'id\'])) && (!empty($_GET[\'id\'])) && (is_numeric($_GET[\'id\'])) && (safe_count(\'kuo_file_stats\',\'`file_id` = \"\'.doSlash($_GET[\'id\']).\'\"\',0) != 0)) {\r\n            \r\n            $kuo_file_stats = safe_rows(\"`id`, `ip`, `username`, `privs`, `when`, `misc`\",\"kuo_file_stats\",\"`file_id` = \'\".doSlash($_GET[\'id\']).\"\' ORDER BY `id` DESC LIMIT 10\",0);\r\n            \r\n            $kuo_file_stats_html = NULL;\r\n            define(\'KUO_FILE_STATS_NBSP\',\'&nbsp;\');\r\n            \r\n            # LETS\'S SEE IF DATABASE HAS MATCHING DATA\r\n            if (($kuo_file_stats) && (!empty($kuo_file_stats))) {\r\n                $kuo_stats_counter = 1;\r\n                foreach ($kuo_file_stats as $kuo_stats) {\r\n\r\n                    # IF IP ADDRESS OF SOME SORT IS AVAILABLE\r\n                    $kuo_file_stats_ip = filter_var($kuo_stats[\'ip\'],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_file_stats_ip)) {\r\n                        $kuo_file_stats_ip = KUO_FILE_STATS_NBSP;\r\n                    }\r\n\r\n                    # IF USERNAME IS AVAILABLE\r\n                    $kuo_file_stats_username = filter_var($kuo_stats[\'username\'],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_file_stats_username)) {\r\n                        $kuo_file_stats_username = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    \r\n                    # IF PRIVILEGES IS AVAILABLE\r\n                    $kuo_file_stats_privs = filter_var($kuo_stats[\'privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    if ((empty($kuo_file_stats_privs)) or ($kuo_file_stats_privs === \'0\')) {\r\n                        $kuo_file_stats_privs = gTxt(\'privs_none\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 1) {\r\n                        $kuo_file_stats_privs = gTxt(\'publisher\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 2) {\r\n                        $kuo_file_stats_privs = gTxt(\'managing_editor\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 3) {\r\n                        $kuo_file_stats_privs = gTxt(\'copy_editor\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 4) {\r\n                        $kuo_file_stats_privs = gTxt(\'staff_writer\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 5) {\r\n                        $kuo_file_stats_privs = gTxt(\'freelancer\');\r\n                    }\r\n                    elseif ($kuo_file_stats_privs == 6) {\r\n                        $kuo_file_stats_privs = gTxt(\'designer\');\r\n                    }\r\n\r\n                    # WHEN FILE WAS ACCESSED\r\n                    $kuo_file_stats_when = filter_var($kuo_stats[\'when\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    if (empty($kuo_file_stats_when)) {\r\n                        $kuo_file_stats_when = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    else {\r\n                        \r\n                        # CHECK HOW DATE AND TIME SHOULD BE FORMATTED\r\n                        # AT SOME POINT SHOULD CHECK HOW US/UK DATE FORMAT IS\r\n                        if ((isset($GLOBALS[\'prefs\'][\'language\'])) && (!empty($GLOBALS[\'prefs\'][\'language\']))) {\r\n                            if (in_array($GLOBALS[\'prefs\'][\'language\'],array(\'en-gb\',\'en-us\'))) {\r\n                                $kuo_file_stats_when_format = \'m/d/Y H:i\';\r\n                            }\r\n                            else {\r\n                                $kuo_file_stats_when_format = \'j.n.Y H.i\';\r\n                            }\r\n                        }\r\n                        else {\r\n                            $kuo_file_stats_when_format = \'m/d/Y H:i\';\r\n                        }\r\n                        \r\n                        $kuo_file_stats_when = date($kuo_file_stats_when_format,strtotime($kuo_file_stats_when));\r\n                    }\r\n                    \r\n                    # MISC DATA ABOUT USER WHO ACCESSED FILE\r\n                    $kuo_file_stats_misc = filter_var_array(unserialize($kuo_stats[\'misc\']),FILTER_SANITIZE_STRING);\r\n                    if ((!empty($kuo_file_stats_misc)) && (is_array($kuo_file_stats_misc))) {\r\n                        # BEGINS: IF VIEWING DETAILS\r\n                        if ((isset($_GET[\'kuo_file_stats_details\'])) && (!empty($_GET[\'kuo_file_stats_details\'])) && (is_numeric($_GET[\'kuo_file_stats_details\'])) && \r\n                        ($_GET[\'kuo_file_stats_details\'] == filter_var($kuo_stats[\'id\'],FILTER_SANITIZE_NUMBER_INT))) {\r\n                            $kuo_file_stats_misc = \'<ul>\r\n                                <li><strong>User agent:</strong> \'.filter_var($kuo_file_stats_misc[\'user_agent\'],FILTER_SANITIZE_STRING).\'</li>\r\n                                <li><strong>HTTP referrer:</strong> \'.filter_var($kuo_file_stats_misc[\'http_referrer\'],FILTER_SANITIZE_STRING).\'</li>\r\n                                <li><strong><a href=\"?event=file&amp;step=file_edit&amp;id=\'.filter_var($_GET[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'\">\'.gTxt(\'close\').\'</a></strong></li>\r\n                            </ul>\';\r\n                        }\r\n                        else {\r\n                            $kuo_file_stats_misc = \'<a href=\"?event=file&amp;step=file_edit&amp;id=\'.filter_var($_GET[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'&amp;kuo_file_stats_details=\'.filter_var($kuo_stats[\'id\'],FILTER_SANITIZE_NUMBER_INT).\'\">\'.\r\n                            filter_var(substr($kuo_file_stats_misc[\'user_agent\'],0,20),FILTER_SANITIZE_STRING).\'&hellip;</a>\';\r\n                        }\r\n                        # ENDS: IF VIEWING DETAILS\r\n                    }\r\n                    else {\r\n                        $kuo_file_stats_misc = KUO_FILE_STATS_NBSP;\r\n                    }\r\n                    \r\n                    $kuo_file_stats_html .= \'<tr>\r\n                            <td>\'.$kuo_stats_counter++.\'.</td>\r\n                            <td>\'.$kuo_file_stats_ip.\'</td>\r\n                            <td>\'.$kuo_file_stats_username.\'</td>\r\n                            <td>\'.$kuo_file_stats_privs.\'</td>\r\n                            <td>\'.$kuo_file_stats_when.\'</td>\r\n                            <td>\'.$kuo_file_stats_misc.\'</td>\r\n                        </tr>\';\r\n                }\r\n                # FOREACH ENDS\r\n            }\r\n        }\r\n        else {\r\n            $kuo_file_stats_html = \'<tr><td colspan=\"6\">\'.gTxt(\'none\').\'</td></tr>\';\r\n        }\r\n        echo \'<style type=\"text/css\">\r\n            #kuo_file_stats {\r\n                border: 10px solid #f0f0f0;\r\n                padding: 3%;\r\n                margin: 1% auto;\r\n                background: #fff;\r\n                }\r\n            #kuo_file_stats table {\r\n                width: 100%;\r\n                border: 0;\r\n                border-collapse: collapse;\r\n                }\r\n            #kuo_file_stats th,\r\n            #kuo_file_stats td {\r\n                padding: 5px;\r\n                border-bottom: 1px solid #f0f0f0;\r\n                }\r\n            #kuo_file_stats tr:nth-child(odd) td {\r\n                background: #ffc;\r\n                }\r\n            </style>\r\n        <div id=\"kuo_file_stats\">\r\n            <h3>Recent file access logs</h3>\r\n            <table>\r\n                <thead>\r\n                    <tr>\r\n                        <th>#</th>\r\n                        <th>IP</th>\r\n                        <th>Username</th>\r\n                        <th>Privs</th>\r\n                        <th>When</th>\r\n                        <th>Details</th>\r\n                    </tr>\r\n                </thead>\r\n                <tbody>\r\n                    \'.$kuo_file_stats_html.\'\r\n                </tbody>\r\n            </table>\r\n        </div>\';\r\n        unset($kuo_file_stats);\r\n        unset($kuo_file_stats_html);\r\n    }\r\n    # FUNCTION ENDS\r\n\r\n}\r\n# ENDS: ADMIN-SIDE FUNCTIONS\r\n\r\n# BEGINS: PUBLIC-SIDE FUNCTIONS\r\nif (@txpinterface === \'public\') {\r\n\r\n    register_callback(\'kuo_file_stats_functions\',\'file_download\');\r\n\r\n    function kuo_file_stats_functions() {\r\n        # GETTING FILE ID\r\n        $kuo_file_id = explode(\'file_download/\',parse_url($_SERVER[\'REQUEST_URI\'],PHP_URL_PATH));\r\n        if ((!empty($kuo_file_id)) && (is_array($kuo_file_id))) {\r\n            define(\'KUO_FILE_STATS_REQUEST_ID\',filter_var(substr($kuo_file_id[1],0,strpos($kuo_file_id[1],\'/\'))),FILTER_SANITIZE_NUMBER_INT);\r\n            unset($kuo_file_id);\r\n        }\r\n        \r\n        # GETTING FILE NAME\r\n        define(\'KUO_FILE_STATS_REQUEST_FILE_NAME\',basename(parse_url($_SERVER[\'REQUEST_URI\'],PHP_URL_PATH)));\r\n        \r\n        # GETTING SOME IP\r\n        define(\'KUO_FILE_STATS_IP\',filter_var($_SERVER[\'REMOTE_ADDR\'],FILTER_SANITIZE_STRING));\r\n        \r\n        # GETTING MISC DATA\r\n        define(\'KUO_FILE_STATS_MISC\',serialize(filter_var_array(array(\r\n            \'user_agent\'=>$_SERVER[\'HTTP_USER_AGENT\'],\r\n            \'http_referrer\'=>$_SERVER[\'HTTP_REFERER\'],\r\n            \'script_filename\'=>$_SERVER[\'SCRIPT_FILENAME\'],\r\n            \'request_time\'=>$_SERVER[\'REQUEST_TIME\']\r\n        ),FILTER_SANITIZE_STRING)));\r\n        \r\n        # GETTING USER DATA IF AVAILABLE\r\n        if (is_logged_in()) {\r\n            define(\'KUO_FILE_STATS_USERNAME\',filter_var(is_logged_in()[\'name\'],FILTER_SANITIZE_STRING));\r\n            define(\'KUO_FILE_STATS_PRIVS\',filter_var(is_logged_in()[\'privs\'],FILTER_SANITIZE_NUMBER_INT));\r\n        }\r\n        else {\r\n            define(\'KUO_FILE_STATS_USERNAME\',NULL);\r\n            define(\'KUO_FILE_STATS_PRIVS\',NULL);\r\n        }\r\n        \r\n        # SAVING DATA\r\n        if ((defined(\'KUO_FILE_STATS_REQUEST_ID\')) && (KUO_FILE_STATS_REQUEST_ID != NULL) && (defined(\'KUO_FILE_STATS_REQUEST_FILE_NAME\')) && (KUO_FILE_STATS_REQUEST_FILE_NAME != NULL)\r\n        && (defined(\'KUO_FILE_STATS_IP\')) && (KUO_FILE_STATS_IP != NULL) && (defined(\'KUO_FILE_STATS_MISC\')) && (KUO_FILE_STATS_MISC != NULL)) {\r\n            safe_query(\'INSERT INTO `\'.safe_pfx(\'kuo_file_stats\').\'` (file_id, file_name, ip, username, privs, misc) VALUES (\"\'.doSlash(KUO_FILE_STATS_REQUEST_ID).\'\", \"\'.doSlash(KUO_FILE_STATS_REQUEST_FILE_NAME).\'\", \r\n            \"\'.doSlash(KUO_FILE_STATS_IP).\'\", \"\'.doSlash(KUO_FILE_STATS_USERNAME).\'\", \"\'.doSlash(KUO_FILE_STATS_PRIVS).\'\", \"\'.doSlash(KUO_FILE_STATS_MISC).\'\")\',1);\r\n        }   \r\n    }\r\n    # FUNCTIONS ENDS\r\n}\r\n# ENDS: PUBLIC-SIDE FUNCTIONS', '396a6b20a4515ea32dd1229318edf0cb', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_github_viewer', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.3', 'Find Textpattern plugins and themes from the MST-Textpattern collection in Github.', '<ul id=\"links\">\n<li id=\"kuo_github_viewer\"><a href=\"#kuo_github_viewer\">kuo_github_viewer</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>Find Textpattern themes, plugins and other Textpattern-related content from Github\'s <a href=\"https://github.com/MST-Textpattern\" rel=\"external\">MST-Textpattern</a> collection.</p>\n<p id=\"notice\">This plugin requires cURL. Plugin has been tested with Textpattern 4.5.5 and 4.5.7.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_github_viewer</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_github_viewer</code>.</li>\n<li><span>[optional]</span> Delete the folder called <code>kuo_github_viewer</code> from inside your website\'s root.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '\r\n#\r\n# PLEASE NOTE THAT WHEN THIS PLUGIN FETCHES PLUGINS INFORMATION FROM GITHUB,\r\n# IT HANDLES AT LEAST ONE MEGABYTE OF DATA. AFTER HANDLING IT, THE RESULTING\r\n# PLUGINS ARE SAVED TO A .json FILE WHICH IS SOMETHING LIKE 30-40 KB IN SIZE.\r\n#\r\n\r\n# IF SET TO \'1\', THIS IS IN PLUGIN MODE\r\ndefine(\'TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE\',1);\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ((isset($GLOBALS[\'event\'])) && ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_github_viewer\')) {\r\nheader(\'location: ./index.php?event=kuo_github_viewer\');\r\ndie;\r\n}\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\nregister_tab(\'extensions\',\'kuo_github_viewer\',\'Plugin &amp; Theme Viewer\');\r\nregister_callback(\'kuo_github_viewer_tab\',\'kuo_github_viewer\');\r\nadd_privs(\'kuo_github_viewer\',\'1\');\r\n}\r\n\r\n# BEGINNING: IF ADMIN INTERFACE\r\nif ((@txpinterface === \'admin\') && ((isset($_GET[\'event\'])) && ($_GET[\'event\'] === \'kuo_github_viewer\')) or (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE != 1)) {\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\n    define(\'TXP_GITHUB_FOLDER\',\'kuo_github_viewer/\');\r\n}\r\nelse {\r\n    define(\'TXP_GITHUB_FOLDER\',\'../kuo_github_viewer/\');\r\n}\r\n\r\ndefine(\'TXP_GITHUB_CACHE_FILE\',\'txp_github_cache.json\');\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES\',\'txp_plugin_prefixes.json\');\r\n\r\n# IN CASE SOME PLUGINS ARE NOT WANTED HERE\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED\',serialize(array(\'txp\')));\r\n\r\n# IN CASE SOME PREFIXES ARE NOT FOUND FROM WIKI\r\n# \"fw_\" IS LIKE THAT BECAUSE THE PREFIX STANDARD IS THREE CHARACTERS\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES_APPENDED\',serialize(array(\'mlp\',\'abc\',\'fw_\',\'tom\',\'jbx\')));\r\n\r\n# SOME PREFIXES CAN MEAN PLUGINS, THEMES AND SOMETHING ELSE. LET\'S SET STUFF MANUALLY IN RIGHT PLACES\r\n# THIS IS JUST BACKUP IN CASE SCRIPT DOESNT RECOGNIZE WHAT THE ITEM IS\r\ndefine(\'TXP_GITHUB_NOT_A_PLUGIN\',serialize(array(\'wet_hive\',\'abc_theme\',\'mrdale-txp-plugins-from-Bitbucket\',\'mem_plugins\')));\r\n\r\n# DATE AND TIME CAN BE LOCALIZED HERE\r\ndefine(\'TXP_GITHUB_DATE_FORMAT\',\'d/m/Y\');\r\n\r\ndefine(\'TXP_GITHUB_PAGE_URL\',\'./index.php?event=kuo_github_viewer\');\r\ndefine(\'TXP_GITHUB_CLOSE_LINK\',\'<p><a href=\"index.php?event=kuo_github_viewer\" rel=\"prev\">(Close)</a></p>\');\r\n\r\n# ACCESS TOKEN TO INCREASE QUERY LIMIT. IF NO TOKEN, IT\'S 60 QUERIES PER HOUR PER IP\r\n# NOT IMPLEMENTED YET\r\ndefine(\'TXP_GITHUB_ACCESS_TOKEN\',\'123456\');\r\n\r\n# IF YOU WANT TO OPEN PLUGIN LINKS IN SAME TAB, REPLACE NUMBER \"1\" WITH SOMETHING ELSE\r\ndefine(\'TXP_GITHUB_OPEN_LINK_IN_NEW_TAB\',1);\r\n\r\n# IF YOU WANT TO SKIP TABLESORTER.JS CHECK, REPLACE NUMBER \"1\" WITH SOMETHING ELSE\r\ndefine(\'TXP_GITHUB_CHECK_IF_TABLESORTER_INSTALLED\',1);\r\n\r\nfunction get_textpattern_plugins($txp_page = 1,$txp_limit = 100,$txp_mode = NULL,$txp_name = NULL) {\r\n# PROBS NOT THE BEST WAY TO GET THE TABLESORTER FILE,\r\n# BUT SOMETHING THAT WORKS IS BETTER THAN NOTHING\r\nif ($txp_mode === \'install-tablesorter\') {\r\n$txp_mode = \'https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-prefixes\') {\r\n$txp_mode = \'http://www.textpattern.net/wiki/index.php?title=Registered_Textpattern_developer_prefixes\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-readme\') {\r\n$txp_mode = \'https://api.github.com/repos/MST-Textpattern/\'.filter_var($txp_name,FILTER_SANITIZE_STRING).\'/readme\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-downloads\') {\r\n$txp_mode = \'https://api.github.com/repos/MST-Textpattern/\'.filter_var($txp_name,FILTER_SANITIZE_STRING).\'/contents/\';\r\n}\r\nelseif ($txp_mode === \'download-txt\') {\r\n$txp_mode = filter_var($txp_name,FILTER_SANITIZE_URL);\r\n}\r\nelseif ($txp_mode === \'github-rate\') {\r\n$txp_mode = \'https://api.github.com/rate_limit\';\r\n}\r\nelse {\r\n$txp_mode = \'https://api.github.com/orgs/MST-Textpattern/repos?page=\'.filter_var($txp_page,FILTER_SANITIZE_NUMBER_INT).\'&per_page=\'.filter_var($txp_limit,FILTER_SANITIZE_NUMBER_INT);\r\n}\r\n$txp_ch = curl_init();\r\ncurl_setopt($txp_ch,CURLOPT_URL,$txp_mode);\r\ncurl_setopt($txp_ch,CURLOPT_RETURNTRANSFER,1);\r\ncurl_setopt($txp_ch,CURLOPT_USERAGENT,\'Textpattern Plugin Seeker (by Kuopassa.net)\');\r\ncurl_setopt($txp_ch,CURLOPT_FOLLOWLOCATION,1);\r\ncurl_setopt($txp_ch,CURLOPT_SSL_VERIFYPEER,false);\r\ncurl_setopt($txp_ch,CURLOPT_SSL_VERIFYHOST,0);\r\nif ((TXP_GITHUB_ACCESS_TOKEN !== NULL) && (strlen(TXP_GITHUB_ACCESS_TOKEN) != 0)) {\r\n# APPARENTLY CURL CANT SEND AND RECEIVE AT THE SAME TIME, SO THIS NEEDS FIXING AT SOME POINT\r\n#curl_setopt($txp_ch,CURLOPT_POSTFIELDS,array(\'access_token\'=>TXP_GITHUB_ACCESS_TOKEN));\r\n#curl_setopt($txp_ch,CURLOPT_HTTPHEADER,array(\'Content-length: 0\',\'Content-type: application/json\',\'Authorization: OAuth: \'.TXP_GITHUB_ACCESS_TOKEN));\r\n#curl_setopt($txp_ch,CURLOPT_POST,true);\r\n}\r\n$txp_result = curl_exec($txp_ch);\r\ncurl_close($txp_ch);\r\nreturn $txp_result;\r\n}\r\n\r\n# BEGINNING: ACTUAL CODE\r\nfunction kuo_github_viewer_tab() {\r\n\r\n# BEGINNING: IF CURL EXISTS\r\nif (!function_exists(\'curl_version\')) {\r\necho \'<p style=\"text-align:center;padding:2% 0;font-size:1.2em;\"><strong>Looks like cURL is not available. This plugin can not function properly without it. (cURL is used to fetch plugins information from Github.)</strong></p>\';\r\n}\r\n# ENDS: IF CURL EXISTS\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\npagetop(\'Plugin and Theme Viewer\');\r\n}\r\n\r\n# IF PLUGIN FOLDER IS NOT THERE\r\nif (!is_dir(TXP_GITHUB_FOLDER)) {\r\nmkdir(TXP_GITHUB_FOLDER,0750);\r\n}\r\n\r\n# IF TABLE SORTER IS NOT INSIDE PLUGIN FOLDER\r\nif ((!file_exists(TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\')) && (TXP_GITHUB_CHECK_IF_TABLESORTER_INSTALLED == 1)) {\r\n$txp_tablesorter = get_textpattern_plugins(NULL,NULL,\'install-tablesorter\');\r\n    \r\nif ((!empty($txp_tablesorter)) && (strpos($txp_tablesorter,\'(function($){$.extend({tablesorter:new\') == true)) {\r\nfile_put_contents(TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\',$txp_tablesorter);\r\n}\r\nunset($txp_tablesorter);\r\n}\r\n\r\n# GETTING PLUGIN PREFIXES FROM WIKI\r\nif (!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES)) {\r\n$txp_plugin_prefixes = get_textpattern_plugins(NULL,NULL,\'get-plugin-prefixes\');\r\nif (strpos($txp_plugin_prefixes,\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\') == true) {\r\n    $txp_plugin_prefixes = substr($txp_plugin_prefixes,strpos($txp_plugin_prefixes,\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\')+strlen(\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\'));\r\n    $txp_plugin_prefixes = preg_replace(\'/(\\r\\n|\\r|\\n|\\t)+/\',\'\',$txp_plugin_prefixes);\r\n    preg_match_all(\'/<dt>(.*?)<\\/dt>/\',$txp_plugin_prefixes,$txp_plugin_prefixes);\r\n    array_splice($txp_plugin_prefixes,1);\r\n    \r\n    $txp_replace_from = array(\'<dt>\',\'</dt>\',\' \',\'&#160;\');\r\n    $txp_replace_with = array(\'\',\'\',\'\',\'\');\r\n\r\n    foreach ($txp_plugin_prefixes as $txp_plugin_prefixes) {\r\n        foreach ($txp_plugin_prefixes as $txp_plugin_prefixes) {\r\n            $txp_final_prefix_array[] = filter_var(str_replace($txp_replace_from,$txp_replace_with,$txp_plugin_prefixes),FILTER_SANITIZE_STRING);\r\n        }\r\n    }\r\n    \r\n    unset($txp_replace_from);\r\n    unset($txp_replace_with);\r\n    unset($txp_plugin_prefixes);\r\n    \r\n    if ((!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES)) && (!empty($txp_final_prefix_array))) {\r\n        file_put_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES,json_encode($txp_final_prefix_array));\r\n    }\r\n}\r\n}\r\nelse {\r\n    $txp_final_prefix_array = json_decode(file_get_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES));\r\n}\r\n\r\n# IF CACHE FILE NOT THERE OR ITS OLDER THAN SOMETHING\r\nif ((!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE)) or (time()-filemtime(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE) > strtotime(\'-1 day\'))) {\r\n    \r\n# GITHUB HAS PAGE LIMIT 100 SO THIS MUST BE DONE IN SEVERAL PIECES\r\n$get_plugins_part_1 = get_textpattern_plugins(1,100);\r\n$get_plugins_part_2 = get_textpattern_plugins(2,100);\r\n$get_plugins_part_3 = get_textpattern_plugins(3,100);\r\n    \r\n$txp_plugins = json_encode(array_merge(json_decode($get_plugins_part_1,true),json_decode($get_plugins_part_2,true),json_decode($get_plugins_part_3,true)));\r\n$txp_plugins = json_decode($txp_plugins,true);\r\n    \r\nif (!empty($txp_plugins)) {\r\n\r\n$being_a_bit_careless = array(\' -- \',\' - \',\' & \',\'UNSUPPORTED\',\' &ndash; \');\r\n$being_a_bit_autistic = array(\' &mdash; \',\' &ndash; \',\' &amp; \',\'<span class=\"important\">UNSUPPORTED</span>\',\' &mdash; \');\r\n\r\nforeach ($txp_plugins as $txp_plugin) {\r\n            \r\nif (!empty($txp_plugin[\'name\'])) {\r\n$txp_plugin_name = filter_var($txp_plugin[\'name\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$txp_plugin_name = \'Unknown\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'html_url\'])) {            \r\n$txp_plugin_url = filter_var($txp_plugin[\'html_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$txp_plugin_url = NULL;\r\n}\r\n            \r\nif (!empty($txp_plugin[\'description\'])) {            \r\n$txp_plugin_description = str_replace($being_a_bit_careless,$being_a_bit_autistic,filter_var($txp_plugin[\'description\'],FILTER_SANITIZE_STRING));\r\n}\r\nelse {\r\n$txp_plugin_description = \'(No description.)\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'homepage\'])) {            \r\n$txp_plugin_homepage_url = filter_var($txp_plugin[\'homepage\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$txp_plugin_homepage_url = NULL;\r\n}\r\n            \r\nif (!empty($txp_plugin[\'updated_at\'])) {            \r\n$txp_plugin_updated = date(TXP_GITHUB_DATE_FORMAT,strtotime(filter_var($txp_plugin[\'updated_at\'],FILTER_SANITIZE_STRING)));\r\n}\r\nelse {\r\n$txp_plugin_updated = \'&mdash;\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'stargazers_count\'])) {            \r\n$txp_plugin_stars = filter_var($txp_plugin[\'stargazers_count\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$txp_plugin_stars = 0;\r\n}\r\n\r\nif (!empty($txp_plugin[\'watchers_count\'])) {\r\n$txp_plugin_followers = filter_var($txp_plugin[\'watchers_count\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$txp_plugin_followers = 0;\r\n}\r\n            \r\n$txp_plugin_final[] = array($txp_plugin_url,$txp_plugin_name,$txp_plugin_description,$txp_plugin_updated,$txp_plugin_stars,$txp_plugin_followers);\r\n}\r\n}\r\n\r\nunset($txp_plugins);\r\n    \r\nif (!empty($txp_plugin_final)) {\r\nfile_put_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE,json_encode($txp_plugin_final));\r\n}\r\n    \r\nunset($txp_plugin_final);\r\nunset($get_plugins_part_1);\r\nunset($get_plugins_part_2);\r\nunset($get_plugins_part_3);\r\nunset($being_a_bit_careless);\r\nunset($being_a_bit_autistic);\r\n\r\necho \'<p style=\"text-align:center;padding:2% 0;font-size:1.2em;\"><a href=\"\'.TXP_GITHUB_PAGE_URL.\'\" rel=\"nofollow\">(Cache was refreshed. Please reload this page.)</a></p>\';\r\n}\r\n# OTHERWISE DATA IS FETCHED FROM CACHE\r\nelse {\r\n\r\n$get_from_cache = file_get_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE);\r\n\r\nif (!empty($get_from_cache)) {\r\n        \r\n$get_from_cache = json_decode($get_from_cache,true);\r\n        \r\n$plugin_counter = 1;\r\n        \r\nif (TXP_GITHUB_OPEN_LINK_IN_NEW_TAB == 1) {\r\n$open_in_new_tab = \' target=\"_new\"\';\r\n}\r\nelse {\r\n$open_in_new_tab = NULL;\r\n}\r\n\r\n# BEGINNING: GETTING CURRENT PLUGIN LIST\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\n$txp_current_plugins = safe_rows(\'`name`\',\'txp_plugin\',\'`status` = 1 OR `status` = 0\',0);\r\nforeach ($txp_current_plugins as $txp_plugin_name) {\r\n$txp_plugins_array[] = $txp_plugin_name[\'name\'];\r\n}\r\n}\r\nelse {\r\n$txp_plugins_array = array();\r\n}\r\n# ENDING: GETTING CURRENT PLUGIN LIST\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\necho \'<script type=\"text/javascript\" src=\"https://code.jquery.com/jquery-2.1.4.min.js\"></script>\';\r\n}\r\n\r\necho \'<script type=\"text/javascript\" src=\"\'.TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(function(){\r\n$(\"#kuo_github_plugins table\").tablesorter({sortList:[[0,0]]});\r\n});\r\n</script>\r\n<style type=\"text/css\">\r\n.txp_plugin_description {\r\nmax-width: 30%;\r\n}\r\n#kuo_github_plugins tbody tr:hover {\r\nbackground: #ffc;\r\n}\r\n#kuo_github_plugins th:before {\r\ncontent: \"\\2212\\a0\";\r\n}\r\n#kuo_github_plugins th:nth-child(8):before {\r\ncontent: \"\";\r\n}\r\n#kuo_github_plugins th {\r\ntext-align: left;\r\npadding: 5px;\r\nbackground-color: #fcc;\r\nborder-right: 1px solid #fff;\r\ncursor: pointer;\r\n}\r\n#kuo_github_plugins th:nth-child(8) {\r\ncursor: default;\r\n}\r\n#kuo_github_plugins th:last-child {\r\nborder-right: 0;\r\n}\r\n#kuo_github_plugins .txp_plugin_updated {\r\nmin-width: 150px;\r\n}\r\n#kuo_github_plugins td {\r\npadding: 5px;\r\nborder-bottom: 1px solid #f0f0f0 !important;\r\n}\r\n#kuo_github_plugins tr:nth-child(even) td {\r\nbackground: #f0f0f0;\r\n}\r\n#kuo_github_plugins .headerSortUp:before {\r\ncontent: \"\\25b2\\a0\";\r\n}\r\n#kuo_github_plugins .headerSortDown:before {\r\ncontent: \"\\25bc\\a0\";\r\n}\r\n#kuo_github_plugins .important {\r\ncolor: #a00;\r\n}\r\n#kuo_readme,\r\n#kuo_downloads,\r\n#txp_search_results,\r\n#txp_saved_files {\r\nborder: 1px solid #ccc;\r\npadding: 1%;\r\nmargin-bottom: 1%;\r\n}\r\n#kuo_readme {\r\nmax-height: 350px;\r\noverflow: auto;\r\n}\r\n#kuo_downloads {\r\nborder-color: #fcc;\r\n}\r\n#txp_search_results ol {\r\npadding-left: 20px;\r\n}\r\n#txp_search_results {\r\nbackground: #ffc;\r\n}\r\n#txp_seach label {\r\npadding-right: 5px;\r\n}\r\n#txp_seach {\r\nfont-size: 1.3em;\r\npadding: 1% 0;\r\n}\r\n#txp_seach button {\r\nmargin-left: 5px;\r\n}\r\n#kuo_github_plugins button,\r\n#kuo_github_plugins label {\r\ncursor: pointer;\r\n}\r\n</style>\r\n<div id=\"kuo_github_plugins\">\r\n<h2>Find Textpattern plugins and themes from Github</h2>\';\r\n\r\n# BEGINNING: AFTER USER CLICKS \"SHOW RATE\" OR SIMILAR LINK\r\nif ((isset($_GET[\'quota\'])) && (empty($_GET[\'quota\']))) {\r\n    $txp_show_quota = get_textpattern_plugins(NULL,NULL,\'github-rate\');\r\n    if (!empty($txp_show_quota)) {\r\n        $txp_show_quota = json_decode($txp_show_quota);\r\n        if (!empty($txp_show_quota)) {\r\n            echo TXP_GITHUB_CLOSE_LINK.\'<h3>About query quota</h3><p>Github limits how many queries you can make to the API. The query quota is per IP address. Your quota is <strong>\'.filter_var($txp_show_quota->rate->limit,FILTER_SANITIZE_NUMBER_INT).\'</strong> per hour\r\n            and you have <strong>\'.filter_var($txp_show_quota->rate->remaining,FILTER_SANITIZE_NUMBER_INT).\'</strong> queries left. \r\n            Quota is fully renewed after \'.date(TXP_GITHUB_DATE_FORMAT.\' (H:i:s)\',filter_var($txp_show_quota->rate->reset,FILTER_SANITIZE_NUMBER_INT)).\'. Opening these statistics are not reduced from the remaining quota, but each time when\r\n            &quot;Install&quot; link is clicked, a couple of queries are made to Github API (to fetch plugin\\\'s help file and actual downloadable files).</p>\';\r\n        }\r\n    }\r\n    unset($txp_show_quota);\r\n}\r\n# ENDING: AFTER USER CLICKS \"SHOW RATE\" OR SIMILAR LINK\r\n\r\n# BEGINNING: AFTER USER CLICKS \"INSTALL\" LINK NEXT TO PLUGIN\r\nif ((isset($_GET[\'install\'])) && (!empty($_GET[\'install\'])) && (!is_numeric($_GET[\'install\'])) && (!is_array($_GET[\'install\']))) {\r\n        \r\n    # FIRST IS THE README FILE IF SUCH THING EXISTS\r\n    $txp_show_readme = get_textpattern_plugins(NULL,NULL,\'get-plugin-readme\',filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING));\r\n    if (!empty($txp_show_readme)) {\r\n        $txp_show_readme = json_decode($txp_show_readme);\r\n        \r\n        if (!empty($txp_show_readme)) {\r\n            $txp_show_readme = base64_decode($txp_show_readme->content);\r\n\r\n            echo \'<div id=\"kuo_readme\">\'.TXP_GITHUB_CLOSE_LINK.\'<h3>About this plugin</h3>\';\r\n            if (!empty($txp_show_readme)) {\r\n                \r\n                if ((TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) && (file_exists(\'lib/classTextile.php\'))) {\r\n                    include(\'lib/classTextile.php\');\r\n                }\r\n                elseif ((TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE != 1) && (file_exists(\'../lib/classTextile.php\'))) {\r\n                    include(\'../lib/classTextile.php\');\r\n                }\r\n                \r\n                $txp_textile = new Textile();\r\n                echo $txp_textile->TextileThis($txp_show_readme);\r\n            }\r\n            else {\r\n                echo \'<p>No description for this plugin.</p>\';\r\n            }\r\n            echo \'</div>\';\r\n        \r\n            unset($txp_show_readme);\r\n\r\n        }\r\n        else {\r\n            $txp_show_readme = NULL;\r\n            echo \'<p><strong>Unable to get data from Github. Most likely your hourly quota for queries has been reached.</strong></p>\';\r\n        }\r\n        \r\n    }\r\n\r\n    # THEN THE TRICKY PART: LOCATE PLUGIN FILE OR FILES\r\n    $txp_show_downloads = get_textpattern_plugins(NULL,NULL,\'get-plugin-downloads\',filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING));\r\n    if (!empty($txp_show_downloads)) {\r\n        $txp_show_downloads = json_decode($txp_show_downloads,true);\r\n        \r\n        # SEARCHING FILES WHICH END WITH .TXT\r\n        foreach ($txp_show_downloads as $txp_show_downloads) {            \r\n            if (substr($txp_show_downloads[\'download_url\'],-4,4) == \'.txt\') {                \r\n                $txp_promising_files[] = filter_var($txp_show_downloads[\'download_url\'],FILTER_SANITIZE_URL);\r\n            }\r\n        }\r\n        \r\n        echo \'<div id=\"kuo_downloads\"><h3>Downloads</h3>\';\r\n        if (!empty($txp_promising_files)) {\r\n            echo \'<ul>\';\r\n            foreach ($txp_promising_files as $txp_promising_files) {\r\n                echo \'<li><a href=\"\'.filter_var($txp_promising_files,FILTER_SANITIZE_URL).\'\" rel=\"nofollow external\"\'.$open_in_new_tab.\'>\'.basename(filter_var($txp_promising_files,FILTER_SANITIZE_URL)).\'</a> \r\n                (<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING).\'&amp;download=\'.base64_encode(filter_var($txp_promising_files,FILTER_SANITIZE_URL)).\'\" rel=\"nofollow external\">Save to disk</a>)</li>\';                \r\n            }\r\n            echo \'</ul>\';\r\n        }\r\n        else {\r\n            echo \'<p>No downloadable plugin files.</p>\';\r\n        }\r\n        echo \'</div>\';\r\n        \r\n        unset($txp_promising_files);\r\n        unset($txp_show_downloads);\r\n    }\r\n\r\n    # BEGINNING: THIS IS A SEPARATE TOOL. WHEN USER WANTS TO \"SAVE TO DISK\"\r\n    if ((isset($_GET[\'download\'])) && (!empty($_GET[\'download\'])) && (!is_numeric($_GET[\'download\'])) && (!is_array($_GET[\'download\']))) {\r\n        \r\n        $txp_download_plugin = filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL);\r\n        \r\n        if (!empty($txp_download_plugin)) {\r\n            # DOWNLOADING ACTUAL PLUGIN FILE AND SAVING IT TO DISK\r\n            $txp_download_plugin = get_textpattern_plugins(NULL,NULL,\'download-txt\',filter_var($txp_download_plugin,FILTER_SANITIZE_URL));\r\n            if ((!empty($txp_download_plugin)) && (substr(base64_decode($_GET[\'download\']),-4,4) === \'.txt\')) {\r\n                file_put_contents(TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)),$txp_download_plugin);\r\n            }\r\n            else {\r\n                echo \'<p>File doesn\\\'t seem to be valid.</p>\';\r\n            }\r\n            \r\n            if (file_exists(TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)))) {\r\n                echo \'<p>File <a href=\"\'.TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)).\'\" rel=\"nofollow\"\'.$open_in_new_tab.\'>\'.basename(filter_var(base64_decode($_GET[\'download\'],FILTER_SANITIZE_URL))).\'</a> was saved to disk.</p>\';\r\n            }\r\n        }\r\n        else {\r\n            echo \'<p>Unable to save to disk.</p>\';\r\n        }\r\n        \r\n        unset($txp_download_plugin);        \r\n    }\r\n    # BEGINNING: THIS IS A SEPARATE TOOL. WHEN USER WANTS TO \"SAVE TO DISK\"\r\n}\r\n# ENDING: AFTER USER CLICKS \"INSTALL\" LINK NEXT TO PLUGIN\r\n\r\n# BEGINNING: SEARCH\r\nif (isset($_POST[\'search\'])) {\r\n    $txp_search = strtolower(filter_var($_POST[\'search\'],FILTER_SANITIZE_STRING));\r\n    \r\n    #var_dump($get_from_cache);\r\n    \r\n    if (!empty($txp_search)) {\r\n        $txp_search_result_counter = 0;\r\n        foreach ($get_from_cache as $txp_found_plugin) {\r\n            # SEARCHING FROM PLUGIN NAME AND DESCRIPTION\r\n            if ((preg_match(\'/\'.$txp_search.\'/\',strtolower($txp_found_plugin[1]),$txp_search_result)) or (preg_match(\'/\'.$txp_search.\'/\',strtolower($txp_found_plugin[2]),$txp_search_result))) {\r\n                    \r\n                if ($txp_search_result_counter++ == 10) {\r\n                    break;\r\n                }\r\n                \r\n                if (strlen(filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING)) > 50) {\r\n                    $txp_search_result_description = substr(filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING),0,48).\'&hellip;\';\r\n                }\r\n                else {\r\n                    $txp_search_result_description = filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING);\r\n                }\r\n                \r\n                if (((in_array(substr($txp_found_plugin[1],0,3),$txp_final_prefix_array,1)) or \r\n                (in_array(substr($txp_found_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_APPENDED),1))) && ((!in_array($txp_found_plugin[1],$txp_plugins_array,1)) && \r\n                (!in_array(substr($txp_found_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED),1)) &&\r\n                (!in_array($txp_found_plugin[1],unserialize(TXP_GITHUB_NOT_A_PLUGIN),1)))) {\r\n                    $txp_search_result_install = \'<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($txp_found_plugin[1],FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\">Install</a>\';\r\n                }\r\n                else {\r\n                    $txp_search_result_install = NULL;\r\n                }\r\n                \r\n                $txp_search_results[] = \'<li><a href=\"\'.filter_var($txp_found_plugin[0],FILTER_SANITIZE_URL).\'\" rel=\"external\"\'.$open_in_new_tab.\'><strong>\'.filter_var($txp_found_plugin[1],FILTER_SANITIZE_STRING).\':</strong> <q>\'.$txp_search_result_description.\'</q></a> | \'.$txp_search_result_install.\'</li>\';\r\n            }\r\n        }\r\n        if (empty($txp_search_results)) {\r\n            $txp_search_results = NULL;\r\n        }\r\n        unset($txp_search_result_counter);\r\n    }\r\n\r\n}\r\nelse {\r\n    $txp_search = NULL;\r\n}\r\n# ENDING: SEARCH\r\n\r\necho \'<form action=\"#txp_seach\" method=\"post\" id=\"txp_seach\"><label for=\"txp_search\">Search:</label><input name=\"search\" type=\"text\" value=\"\'.$txp_search.\'\" size=\"25\" maxlength=\"50\" /><button type=\"submit\">OK</button></form>\';\r\n\r\nif ((isset($txp_search_results)) && ($txp_search_results !== NULL)) {\r\n    echo \'<div id=\"txp_search_results\">\'.TXP_GITHUB_CLOSE_LINK.\'<h4>Search results for &quot;\'.$txp_search.\'&quot;</h4><ol>\';\r\n    foreach ($txp_search_results as $txp_search_results) {\r\n        echo $txp_search_results;\r\n    }\r\n    echo \'</ol></div>\';\r\n}\r\n\r\n# BEGINNING: AT THE POINT WE COULD GET PLUGIN FILES SAVED TO DISK\r\nif ((file_exists(TXP_GITHUB_FOLDER)) && (is_readable(TXP_GITHUB_FOLDER)) && (count(glob(TXP_GITHUB_FOLDER.\'*.txt\')) != 0) && (!isset($_GET[\'disk\']))) {\r\n    echo \'<p><a href=\"index.php?event=kuo_github_viewer&amp;disk\" rel=\"nofollow\">View files saved to disk</a></p>\';\r\n}\r\n\r\nif ((file_exists(TXP_GITHUB_FOLDER)) && (is_readable(TXP_GITHUB_FOLDER)) && (count(glob(TXP_GITHUB_FOLDER.\'*.txt\')) != 0) && (isset($_GET[\'disk\'])) && (empty($_GET[\'disk\']))) {\r\n    # WHEN USER CLICKS \"DELETE FROM DISK\"\r\n    if ((isset($_GET[\'delete\'])) && (!empty($_GET[\'delete\'])) && (!is_numeric($_GET[\'delete\'])) && (!is_array($_GET[\'delete\']))) {\r\n        $txp_file_to_be_deleted = filter_var(base64_decode($_GET[\'delete\']),FILTER_SANITIZE_STRING);\r\n\r\n        if ((!empty($txp_file_to_be_deleted)) && (file_exists($txp_file_to_be_deleted)) && (substr($txp_file_to_be_deleted,-4,4) === \'.txt\')) {\r\n            unlink($txp_file_to_be_deleted);\r\n            echo \'<p>File was deleted.</p>\';\r\n        }\r\n        else {\r\n            echo \'<p>Unable to delete.</p>\';\r\n        }\r\n        \r\n        unset($txp_file_to_be_deleted);\r\n    }\r\n    \r\n    # CREATING A LIST OF FILES\r\n    echo \'<div id=\"txp_saved_files\">\'.TXP_GITHUB_CLOSE_LINK.\'<h3>Files saved to disk</h3><ol>\';\r\n    foreach (array_reverse(glob(TXP_GITHUB_FOLDER.\'*.txt\',GLOB_NOSORT),true) as $txp_file) {\r\n        \r\n        # TRYING TO ANALYZE THE CONTENT TO SE IF IT\'S A PLUGIN OR NOT\r\n        $txp_is_this_a_plugin = file_get_contents(filter_var($txp_file,FILTER_SANITIZE_STRING),0,NULL,50,300);\r\n        if (strpos($txp_is_this_a_plugin,\'This is a plugin for Textpattern\') == true) {\r\n            $txp_is_this_a_plugin = \' [plugin]\';\r\n        }\r\n        else {\r\n            $txp_is_this_a_plugin = NULL;\r\n        }\r\n        \r\n        echo \'<li>\'.date(TXP_GITHUB_DATE_FORMAT.\' (H:i:s)\',filemtime(filter_var($txp_file,FILTER_SANITIZE_STRING))).\':\'.$txp_is_this_a_plugin.\'\r\n        <a href=\"\'.filter_var($txp_file,FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\"\'.$open_in_new_tab.\'>\'.basename(filter_var($txp_file,FILTER_SANITIZE_STRING)).\'</a>  | <a href=\"index.php?event=kuo_github_viewer&amp;disk&amp;delete=\'.base64_encode(filter_var($txp_file,FILTER_SANITIZE_STRING)).\'\" rel=\"nofollow\">Delete from disk</a></li>\';\r\n    }\r\n    unset($txp_is_this_a_plugin);\r\n    unset($txp_file);\r\n    echo \'</ol></div>\';\r\n}\r\n# ENDING: AT THE POINT WE COULD GET PLUGIN FILES SAVED TO DISK\r\n\r\necho \'<table><thead><tr><th>#</th><th class=\"txp_plugin_name\">Name</th><th class=\"txp_plugin_description\">Description</th><th class=\"txp_plugin_updated\">Updated</th><th>Stars</th><th>Followers</th><th>Installed</th><th class=\"sorter-false\">&nbsp;</th></tr></thead><tbody>\';\r\n\r\nforeach ($get_from_cache as $txp_plugin) {\r\necho \'<tr>\r\n<td>\'.$plugin_counter++.\'.</td>\r\n<td><a href=\"\'.filter_var($txp_plugin[0],FILTER_SANITIZE_URL).\'\" rel=\"external\"\'.$open_in_new_tab.\'>\'.filter_var($txp_plugin[1],FILTER_SANITIZE_STRING).\'</a></td>\r\n<td>\'.$txp_plugin[2].\'</td><td>\'.filter_var($txp_plugin[3],FILTER_SANITIZE_STRING).\'</td><td>\'.filter_var($txp_plugin[4],FILTER_SANITIZE_NUMBER_INT).\'</td>\r\n<td>\'.filter_var($txp_plugin[5],FILTER_SANITIZE_NUMBER_INT).\'</td>\r\n<td>\';\r\n\r\n# THIS MARKS PLUGINS WHICH ARE INSTALLED (DOESN\'T MATTER IF ACTIVE OR NOT)\r\nif (in_array($txp_plugin[1],$txp_plugins_array)) {\r\necho \'Yes\';\r\n}\r\nelse {\r\necho \'No\';\r\n}\r\n\r\necho \'</td><td>\';\r\n\r\n# THIS WILL LIST PLUGINS WHICH CAN BE INSTALLED\r\nif (((in_array(substr($txp_plugin[1],0,3),$txp_final_prefix_array,1)) or \r\n(in_array(substr($txp_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_APPENDED),1))) && ((!in_array($txp_plugin[1],$txp_plugins_array,1)) && \r\n(!in_array(substr($txp_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED),1)) &&\r\n(!in_array($txp_plugin[1],unserialize(TXP_GITHUB_NOT_A_PLUGIN),1)))) {\r\n    echo \'<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($txp_plugin[1],FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\">Install</a>\';\r\n}\r\nelse {\r\n    echo \'&nbsp;\';\r\n}\r\n\r\necho \'</td></tr>\';\r\n}\r\n\r\necho \'</tbody></table><p>Source: <a href=\"https://github.com/MST-Textpattern\" rel=\"external\"\'.$open_in_new_tab.\'>MST-Textpattern / Github</a>. <a href=\"index.php?event=kuo_github_viewer&amp;quota\" rel=\"nofollow\">What is query quota?</a></p></div>\';\r\n\r\nunset($plugin_counter);\r\nunset($get_from_cache);\r\n\r\n}\r\n}\r\n\r\n}\r\n# ENDING: ACTUAL CODE\r\n\r\n}\r\n# ENDING: IF ADMIN INTERFACE', '\r\n#\r\n# PLEASE NOTE THAT WHEN THIS PLUGIN FETCHES PLUGINS INFORMATION FROM GITHUB,\r\n# IT HANDLES AT LEAST ONE MEGABYTE OF DATA. AFTER HANDLING IT, THE RESULTING\r\n# PLUGINS ARE SAVED TO A .json FILE WHICH IS SOMETHING LIKE 30-40 KB IN SIZE.\r\n#\r\n\r\n# IF SET TO \'1\', THIS IS IN PLUGIN MODE\r\ndefine(\'TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE\',1);\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ((isset($GLOBALS[\'event\'])) && ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_github_viewer\')) {\r\nheader(\'location: ./index.php?event=kuo_github_viewer\');\r\ndie;\r\n}\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\nregister_tab(\'extensions\',\'kuo_github_viewer\',\'Plugin &amp; Theme Viewer\');\r\nregister_callback(\'kuo_github_viewer_tab\',\'kuo_github_viewer\');\r\nadd_privs(\'kuo_github_viewer\',\'1\');\r\n}\r\n\r\n# BEGINNING: IF ADMIN INTERFACE\r\nif ((@txpinterface === \'admin\') && ((isset($_GET[\'event\'])) && ($_GET[\'event\'] === \'kuo_github_viewer\')) or (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE != 1)) {\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\n    define(\'TXP_GITHUB_FOLDER\',\'kuo_github_viewer/\');\r\n}\r\nelse {\r\n    define(\'TXP_GITHUB_FOLDER\',\'../kuo_github_viewer/\');\r\n}\r\n\r\ndefine(\'TXP_GITHUB_CACHE_FILE\',\'txp_github_cache.json\');\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES\',\'txp_plugin_prefixes.json\');\r\n\r\n# IN CASE SOME PLUGINS ARE NOT WANTED HERE\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED\',serialize(array(\'txp\')));\r\n\r\n# IN CASE SOME PREFIXES ARE NOT FOUND FROM WIKI\r\n# \"fw_\" IS LIKE THAT BECAUSE THE PREFIX STANDARD IS THREE CHARACTERS\r\ndefine(\'TXP_GITHUB_PLUGIN_PREFIXES_APPENDED\',serialize(array(\'mlp\',\'abc\',\'fw_\',\'tom\',\'jbx\')));\r\n\r\n# SOME PREFIXES CAN MEAN PLUGINS, THEMES AND SOMETHING ELSE. LET\'S SET STUFF MANUALLY IN RIGHT PLACES\r\n# THIS IS JUST BACKUP IN CASE SCRIPT DOESNT RECOGNIZE WHAT THE ITEM IS\r\ndefine(\'TXP_GITHUB_NOT_A_PLUGIN\',serialize(array(\'wet_hive\',\'abc_theme\',\'mrdale-txp-plugins-from-Bitbucket\',\'mem_plugins\')));\r\n\r\n# DATE AND TIME CAN BE LOCALIZED HERE\r\ndefine(\'TXP_GITHUB_DATE_FORMAT\',\'d/m/Y\');\r\n\r\ndefine(\'TXP_GITHUB_PAGE_URL\',\'./index.php?event=kuo_github_viewer\');\r\ndefine(\'TXP_GITHUB_CLOSE_LINK\',\'<p><a href=\"index.php?event=kuo_github_viewer\" rel=\"prev\">(Close)</a></p>\');\r\n\r\n# ACCESS TOKEN TO INCREASE QUERY LIMIT. IF NO TOKEN, IT\'S 60 QUERIES PER HOUR PER IP\r\n# NOT IMPLEMENTED YET\r\ndefine(\'TXP_GITHUB_ACCESS_TOKEN\',\'123456\');\r\n\r\n# IF YOU WANT TO OPEN PLUGIN LINKS IN SAME TAB, REPLACE NUMBER \"1\" WITH SOMETHING ELSE\r\ndefine(\'TXP_GITHUB_OPEN_LINK_IN_NEW_TAB\',1);\r\n\r\n# IF YOU WANT TO SKIP TABLESORTER.JS CHECK, REPLACE NUMBER \"1\" WITH SOMETHING ELSE\r\ndefine(\'TXP_GITHUB_CHECK_IF_TABLESORTER_INSTALLED\',1);\r\n\r\nfunction get_textpattern_plugins($txp_page = 1,$txp_limit = 100,$txp_mode = NULL,$txp_name = NULL) {\r\n# PROBS NOT THE BEST WAY TO GET THE TABLESORTER FILE,\r\n# BUT SOMETHING THAT WORKS IS BETTER THAN NOTHING\r\nif ($txp_mode === \'install-tablesorter\') {\r\n$txp_mode = \'https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-prefixes\') {\r\n$txp_mode = \'http://www.textpattern.net/wiki/index.php?title=Registered_Textpattern_developer_prefixes\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-readme\') {\r\n$txp_mode = \'https://api.github.com/repos/MST-Textpattern/\'.filter_var($txp_name,FILTER_SANITIZE_STRING).\'/readme\';\r\n}\r\nelseif ($txp_mode === \'get-plugin-downloads\') {\r\n$txp_mode = \'https://api.github.com/repos/MST-Textpattern/\'.filter_var($txp_name,FILTER_SANITIZE_STRING).\'/contents/\';\r\n}\r\nelseif ($txp_mode === \'download-txt\') {\r\n$txp_mode = filter_var($txp_name,FILTER_SANITIZE_URL);\r\n}\r\nelseif ($txp_mode === \'github-rate\') {\r\n$txp_mode = \'https://api.github.com/rate_limit\';\r\n}\r\nelse {\r\n$txp_mode = \'https://api.github.com/orgs/MST-Textpattern/repos?page=\'.filter_var($txp_page,FILTER_SANITIZE_NUMBER_INT).\'&per_page=\'.filter_var($txp_limit,FILTER_SANITIZE_NUMBER_INT);\r\n}\r\n$txp_ch = curl_init();\r\ncurl_setopt($txp_ch,CURLOPT_URL,$txp_mode);\r\ncurl_setopt($txp_ch,CURLOPT_RETURNTRANSFER,1);\r\ncurl_setopt($txp_ch,CURLOPT_USERAGENT,\'Textpattern Plugin Seeker (by Kuopassa.net)\');\r\ncurl_setopt($txp_ch,CURLOPT_FOLLOWLOCATION,1);\r\ncurl_setopt($txp_ch,CURLOPT_SSL_VERIFYPEER,false);\r\ncurl_setopt($txp_ch,CURLOPT_SSL_VERIFYHOST,0);\r\nif ((TXP_GITHUB_ACCESS_TOKEN !== NULL) && (strlen(TXP_GITHUB_ACCESS_TOKEN) != 0)) {\r\n# APPARENTLY CURL CANT SEND AND RECEIVE AT THE SAME TIME, SO THIS NEEDS FIXING AT SOME POINT\r\n#curl_setopt($txp_ch,CURLOPT_POSTFIELDS,array(\'access_token\'=>TXP_GITHUB_ACCESS_TOKEN));\r\n#curl_setopt($txp_ch,CURLOPT_HTTPHEADER,array(\'Content-length: 0\',\'Content-type: application/json\',\'Authorization: OAuth: \'.TXP_GITHUB_ACCESS_TOKEN));\r\n#curl_setopt($txp_ch,CURLOPT_POST,true);\r\n}\r\n$txp_result = curl_exec($txp_ch);\r\ncurl_close($txp_ch);\r\nreturn $txp_result;\r\n}\r\n\r\n# BEGINNING: ACTUAL CODE\r\nfunction kuo_github_viewer_tab() {\r\n\r\n# BEGINNING: IF CURL EXISTS\r\nif (!function_exists(\'curl_version\')) {\r\necho \'<p style=\"text-align:center;padding:2% 0;font-size:1.2em;\"><strong>Looks like cURL is not available. This plugin can not function properly without it. (cURL is used to fetch plugins information from Github.)</strong></p>\';\r\n}\r\n# ENDS: IF CURL EXISTS\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\npagetop(\'Plugin and Theme Viewer\');\r\n}\r\n\r\n# IF PLUGIN FOLDER IS NOT THERE\r\nif (!is_dir(TXP_GITHUB_FOLDER)) {\r\nmkdir(TXP_GITHUB_FOLDER,0750);\r\n}\r\n\r\n# IF TABLE SORTER IS NOT INSIDE PLUGIN FOLDER\r\nif ((!file_exists(TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\')) && (TXP_GITHUB_CHECK_IF_TABLESORTER_INSTALLED == 1)) {\r\n$txp_tablesorter = get_textpattern_plugins(NULL,NULL,\'install-tablesorter\');\r\n    \r\nif ((!empty($txp_tablesorter)) && (strpos($txp_tablesorter,\'(function($){$.extend({tablesorter:new\') == true)) {\r\nfile_put_contents(TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\',$txp_tablesorter);\r\n}\r\nunset($txp_tablesorter);\r\n}\r\n\r\n# GETTING PLUGIN PREFIXES FROM WIKI\r\nif (!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES)) {\r\n$txp_plugin_prefixes = get_textpattern_plugins(NULL,NULL,\'get-plugin-prefixes\');\r\nif (strpos($txp_plugin_prefixes,\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\') == true) {\r\n    $txp_plugin_prefixes = substr($txp_plugin_prefixes,strpos($txp_plugin_prefixes,\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\')+strlen(\'<h2> <span class=\"mw-headline\" id=\"A\">A</span></h2>\'));\r\n    $txp_plugin_prefixes = preg_replace(\'/(\\r\\n|\\r|\\n|\\t)+/\',\'\',$txp_plugin_prefixes);\r\n    preg_match_all(\'/<dt>(.*?)<\\/dt>/\',$txp_plugin_prefixes,$txp_plugin_prefixes);\r\n    array_splice($txp_plugin_prefixes,1);\r\n    \r\n    $txp_replace_from = array(\'<dt>\',\'</dt>\',\' \',\'&#160;\');\r\n    $txp_replace_with = array(\'\',\'\',\'\',\'\');\r\n\r\n    foreach ($txp_plugin_prefixes as $txp_plugin_prefixes) {\r\n        foreach ($txp_plugin_prefixes as $txp_plugin_prefixes) {\r\n            $txp_final_prefix_array[] = filter_var(str_replace($txp_replace_from,$txp_replace_with,$txp_plugin_prefixes),FILTER_SANITIZE_STRING);\r\n        }\r\n    }\r\n    \r\n    unset($txp_replace_from);\r\n    unset($txp_replace_with);\r\n    unset($txp_plugin_prefixes);\r\n    \r\n    if ((!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES)) && (!empty($txp_final_prefix_array))) {\r\n        file_put_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES,json_encode($txp_final_prefix_array));\r\n    }\r\n}\r\n}\r\nelse {\r\n    $txp_final_prefix_array = json_decode(file_get_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_PLUGIN_PREFIXES));\r\n}\r\n\r\n# IF CACHE FILE NOT THERE OR ITS OLDER THAN SOMETHING\r\nif ((!file_exists(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE)) or (time()-filemtime(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE) > strtotime(\'-1 day\'))) {\r\n    \r\n# GITHUB HAS PAGE LIMIT 100 SO THIS MUST BE DONE IN SEVERAL PIECES\r\n$get_plugins_part_1 = get_textpattern_plugins(1,100);\r\n$get_plugins_part_2 = get_textpattern_plugins(2,100);\r\n$get_plugins_part_3 = get_textpattern_plugins(3,100);\r\n    \r\n$txp_plugins = json_encode(array_merge(json_decode($get_plugins_part_1,true),json_decode($get_plugins_part_2,true),json_decode($get_plugins_part_3,true)));\r\n$txp_plugins = json_decode($txp_plugins,true);\r\n    \r\nif (!empty($txp_plugins)) {\r\n\r\n$being_a_bit_careless = array(\' -- \',\' - \',\' & \',\'UNSUPPORTED\',\' &ndash; \');\r\n$being_a_bit_autistic = array(\' &mdash; \',\' &ndash; \',\' &amp; \',\'<span class=\"important\">UNSUPPORTED</span>\',\' &mdash; \');\r\n\r\nforeach ($txp_plugins as $txp_plugin) {\r\n            \r\nif (!empty($txp_plugin[\'name\'])) {\r\n$txp_plugin_name = filter_var($txp_plugin[\'name\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$txp_plugin_name = \'Unknown\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'html_url\'])) {            \r\n$txp_plugin_url = filter_var($txp_plugin[\'html_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$txp_plugin_url = NULL;\r\n}\r\n            \r\nif (!empty($txp_plugin[\'description\'])) {            \r\n$txp_plugin_description = str_replace($being_a_bit_careless,$being_a_bit_autistic,filter_var($txp_plugin[\'description\'],FILTER_SANITIZE_STRING));\r\n}\r\nelse {\r\n$txp_plugin_description = \'(No description.)\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'homepage\'])) {            \r\n$txp_plugin_homepage_url = filter_var($txp_plugin[\'homepage\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$txp_plugin_homepage_url = NULL;\r\n}\r\n            \r\nif (!empty($txp_plugin[\'updated_at\'])) {            \r\n$txp_plugin_updated = date(TXP_GITHUB_DATE_FORMAT,strtotime(filter_var($txp_plugin[\'updated_at\'],FILTER_SANITIZE_STRING)));\r\n}\r\nelse {\r\n$txp_plugin_updated = \'&mdash;\';\r\n}\r\n            \r\nif (!empty($txp_plugin[\'stargazers_count\'])) {            \r\n$txp_plugin_stars = filter_var($txp_plugin[\'stargazers_count\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$txp_plugin_stars = 0;\r\n}\r\n\r\nif (!empty($txp_plugin[\'watchers_count\'])) {\r\n$txp_plugin_followers = filter_var($txp_plugin[\'watchers_count\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$txp_plugin_followers = 0;\r\n}\r\n            \r\n$txp_plugin_final[] = array($txp_plugin_url,$txp_plugin_name,$txp_plugin_description,$txp_plugin_updated,$txp_plugin_stars,$txp_plugin_followers);\r\n}\r\n}\r\n\r\nunset($txp_plugins);\r\n    \r\nif (!empty($txp_plugin_final)) {\r\nfile_put_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE,json_encode($txp_plugin_final));\r\n}\r\n    \r\nunset($txp_plugin_final);\r\nunset($get_plugins_part_1);\r\nunset($get_plugins_part_2);\r\nunset($get_plugins_part_3);\r\nunset($being_a_bit_careless);\r\nunset($being_a_bit_autistic);\r\n\r\necho \'<p style=\"text-align:center;padding:2% 0;font-size:1.2em;\"><a href=\"\'.TXP_GITHUB_PAGE_URL.\'\" rel=\"nofollow\">(Cache was refreshed. Please reload this page.)</a></p>\';\r\n}\r\n# OTHERWISE DATA IS FETCHED FROM CACHE\r\nelse {\r\n\r\n$get_from_cache = file_get_contents(TXP_GITHUB_FOLDER.TXP_GITHUB_CACHE_FILE);\r\n\r\nif (!empty($get_from_cache)) {\r\n        \r\n$get_from_cache = json_decode($get_from_cache,true);\r\n        \r\n$plugin_counter = 1;\r\n        \r\nif (TXP_GITHUB_OPEN_LINK_IN_NEW_TAB == 1) {\r\n$open_in_new_tab = \' target=\"_new\"\';\r\n}\r\nelse {\r\n$open_in_new_tab = NULL;\r\n}\r\n\r\n# BEGINNING: GETTING CURRENT PLUGIN LIST\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\n$txp_current_plugins = safe_rows(\'`name`\',\'txp_plugin\',\'`status` = 1 OR `status` = 0\',0);\r\nforeach ($txp_current_plugins as $txp_plugin_name) {\r\n$txp_plugins_array[] = $txp_plugin_name[\'name\'];\r\n}\r\n}\r\nelse {\r\n$txp_plugins_array = array();\r\n}\r\n# ENDING: GETTING CURRENT PLUGIN LIST\r\n\r\nif (TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) {\r\necho \'<script type=\"text/javascript\" src=\"https://code.jquery.com/jquery-2.1.4.min.js\"></script>\';\r\n}\r\n\r\necho \'<script type=\"text/javascript\" src=\"\'.TXP_GITHUB_FOLDER.\'jquery.tablesorter.min.js\"></script>\r\n<script type=\"text/javascript\">\r\n$(function(){\r\n$(\"#kuo_github_plugins table\").tablesorter({sortList:[[0,0]]});\r\n});\r\n</script>\r\n<style type=\"text/css\">\r\n.txp_plugin_description {\r\nmax-width: 30%;\r\n}\r\n#kuo_github_plugins tbody tr:hover {\r\nbackground: #ffc;\r\n}\r\n#kuo_github_plugins th:before {\r\ncontent: \"\\2212\\a0\";\r\n}\r\n#kuo_github_plugins th:nth-child(8):before {\r\ncontent: \"\";\r\n}\r\n#kuo_github_plugins th {\r\ntext-align: left;\r\npadding: 5px;\r\nbackground-color: #fcc;\r\nborder-right: 1px solid #fff;\r\ncursor: pointer;\r\n}\r\n#kuo_github_plugins th:nth-child(8) {\r\ncursor: default;\r\n}\r\n#kuo_github_plugins th:last-child {\r\nborder-right: 0;\r\n}\r\n#kuo_github_plugins .txp_plugin_updated {\r\nmin-width: 150px;\r\n}\r\n#kuo_github_plugins td {\r\npadding: 5px;\r\nborder-bottom: 1px solid #f0f0f0 !important;\r\n}\r\n#kuo_github_plugins tr:nth-child(even) td {\r\nbackground: #f0f0f0;\r\n}\r\n#kuo_github_plugins .headerSortUp:before {\r\ncontent: \"\\25b2\\a0\";\r\n}\r\n#kuo_github_plugins .headerSortDown:before {\r\ncontent: \"\\25bc\\a0\";\r\n}\r\n#kuo_github_plugins .important {\r\ncolor: #a00;\r\n}\r\n#kuo_readme,\r\n#kuo_downloads,\r\n#txp_search_results,\r\n#txp_saved_files {\r\nborder: 1px solid #ccc;\r\npadding: 1%;\r\nmargin-bottom: 1%;\r\n}\r\n#kuo_readme {\r\nmax-height: 350px;\r\noverflow: auto;\r\n}\r\n#kuo_downloads {\r\nborder-color: #fcc;\r\n}\r\n#txp_search_results ol {\r\npadding-left: 20px;\r\n}\r\n#txp_search_results {\r\nbackground: #ffc;\r\n}\r\n#txp_seach label {\r\npadding-right: 5px;\r\n}\r\n#txp_seach {\r\nfont-size: 1.3em;\r\npadding: 1% 0;\r\n}\r\n#txp_seach button {\r\nmargin-left: 5px;\r\n}\r\n#kuo_github_plugins button,\r\n#kuo_github_plugins label {\r\ncursor: pointer;\r\n}\r\n</style>\r\n<div id=\"kuo_github_plugins\">\r\n<h2>Find Textpattern plugins and themes from Github</h2>\';\r\n\r\n# BEGINNING: AFTER USER CLICKS \"SHOW RATE\" OR SIMILAR LINK\r\nif ((isset($_GET[\'quota\'])) && (empty($_GET[\'quota\']))) {\r\n    $txp_show_quota = get_textpattern_plugins(NULL,NULL,\'github-rate\');\r\n    if (!empty($txp_show_quota)) {\r\n        $txp_show_quota = json_decode($txp_show_quota);\r\n        if (!empty($txp_show_quota)) {\r\n            echo TXP_GITHUB_CLOSE_LINK.\'<h3>About query quota</h3><p>Github limits how many queries you can make to the API. The query quota is per IP address. Your quota is <strong>\'.filter_var($txp_show_quota->rate->limit,FILTER_SANITIZE_NUMBER_INT).\'</strong> per hour\r\n            and you have <strong>\'.filter_var($txp_show_quota->rate->remaining,FILTER_SANITIZE_NUMBER_INT).\'</strong> queries left. \r\n            Quota is fully renewed after \'.date(TXP_GITHUB_DATE_FORMAT.\' (H:i:s)\',filter_var($txp_show_quota->rate->reset,FILTER_SANITIZE_NUMBER_INT)).\'. Opening these statistics are not reduced from the remaining quota, but each time when\r\n            &quot;Install&quot; link is clicked, a couple of queries are made to Github API (to fetch plugin\\\'s help file and actual downloadable files).</p>\';\r\n        }\r\n    }\r\n    unset($txp_show_quota);\r\n}\r\n# ENDING: AFTER USER CLICKS \"SHOW RATE\" OR SIMILAR LINK\r\n\r\n# BEGINNING: AFTER USER CLICKS \"INSTALL\" LINK NEXT TO PLUGIN\r\nif ((isset($_GET[\'install\'])) && (!empty($_GET[\'install\'])) && (!is_numeric($_GET[\'install\'])) && (!is_array($_GET[\'install\']))) {\r\n        \r\n    # FIRST IS THE README FILE IF SUCH THING EXISTS\r\n    $txp_show_readme = get_textpattern_plugins(NULL,NULL,\'get-plugin-readme\',filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING));\r\n    if (!empty($txp_show_readme)) {\r\n        $txp_show_readme = json_decode($txp_show_readme);\r\n        \r\n        if (!empty($txp_show_readme)) {\r\n            $txp_show_readme = base64_decode($txp_show_readme->content);\r\n\r\n            echo \'<div id=\"kuo_readme\">\'.TXP_GITHUB_CLOSE_LINK.\'<h3>About this plugin</h3>\';\r\n            if (!empty($txp_show_readme)) {\r\n                \r\n                if ((TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE == 1) && (file_exists(\'lib/classTextile.php\'))) {\r\n                    include(\'lib/classTextile.php\');\r\n                }\r\n                elseif ((TXP_GITHUB_PLUGIN_OR_STANDALONE_MODE != 1) && (file_exists(\'../lib/classTextile.php\'))) {\r\n                    include(\'../lib/classTextile.php\');\r\n                }\r\n                \r\n                $txp_textile = new Textile();\r\n                echo $txp_textile->TextileThis($txp_show_readme);\r\n            }\r\n            else {\r\n                echo \'<p>No description for this plugin.</p>\';\r\n            }\r\n            echo \'</div>\';\r\n        \r\n            unset($txp_show_readme);\r\n\r\n        }\r\n        else {\r\n            $txp_show_readme = NULL;\r\n            echo \'<p><strong>Unable to get data from Github. Most likely your hourly quota for queries has been reached.</strong></p>\';\r\n        }\r\n        \r\n    }\r\n\r\n    # THEN THE TRICKY PART: LOCATE PLUGIN FILE OR FILES\r\n    $txp_show_downloads = get_textpattern_plugins(NULL,NULL,\'get-plugin-downloads\',filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING));\r\n    if (!empty($txp_show_downloads)) {\r\n        $txp_show_downloads = json_decode($txp_show_downloads,true);\r\n        \r\n        # SEARCHING FILES WHICH END WITH .TXT\r\n        foreach ($txp_show_downloads as $txp_show_downloads) {            \r\n            if (substr($txp_show_downloads[\'download_url\'],-4,4) == \'.txt\') {                \r\n                $txp_promising_files[] = filter_var($txp_show_downloads[\'download_url\'],FILTER_SANITIZE_URL);\r\n            }\r\n        }\r\n        \r\n        echo \'<div id=\"kuo_downloads\"><h3>Downloads</h3>\';\r\n        if (!empty($txp_promising_files)) {\r\n            echo \'<ul>\';\r\n            foreach ($txp_promising_files as $txp_promising_files) {\r\n                echo \'<li><a href=\"\'.filter_var($txp_promising_files,FILTER_SANITIZE_URL).\'\" rel=\"nofollow external\"\'.$open_in_new_tab.\'>\'.basename(filter_var($txp_promising_files,FILTER_SANITIZE_URL)).\'</a> \r\n                (<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($_GET[\'install\'],FILTER_SANITIZE_STRING).\'&amp;download=\'.base64_encode(filter_var($txp_promising_files,FILTER_SANITIZE_URL)).\'\" rel=\"nofollow external\">Save to disk</a>)</li>\';                \r\n            }\r\n            echo \'</ul>\';\r\n        }\r\n        else {\r\n            echo \'<p>No downloadable plugin files.</p>\';\r\n        }\r\n        echo \'</div>\';\r\n        \r\n        unset($txp_promising_files);\r\n        unset($txp_show_downloads);\r\n    }\r\n\r\n    # BEGINNING: THIS IS A SEPARATE TOOL. WHEN USER WANTS TO \"SAVE TO DISK\"\r\n    if ((isset($_GET[\'download\'])) && (!empty($_GET[\'download\'])) && (!is_numeric($_GET[\'download\'])) && (!is_array($_GET[\'download\']))) {\r\n        \r\n        $txp_download_plugin = filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL);\r\n        \r\n        if (!empty($txp_download_plugin)) {\r\n            # DOWNLOADING ACTUAL PLUGIN FILE AND SAVING IT TO DISK\r\n            $txp_download_plugin = get_textpattern_plugins(NULL,NULL,\'download-txt\',filter_var($txp_download_plugin,FILTER_SANITIZE_URL));\r\n            if ((!empty($txp_download_plugin)) && (substr(base64_decode($_GET[\'download\']),-4,4) === \'.txt\')) {\r\n                file_put_contents(TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)),$txp_download_plugin);\r\n            }\r\n            else {\r\n                echo \'<p>File doesn\\\'t seem to be valid.</p>\';\r\n            }\r\n            \r\n            if (file_exists(TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)))) {\r\n                echo \'<p>File <a href=\"\'.TXP_GITHUB_FOLDER.basename(filter_var(base64_decode($_GET[\'download\']),FILTER_SANITIZE_URL)).\'\" rel=\"nofollow\"\'.$open_in_new_tab.\'>\'.basename(filter_var(base64_decode($_GET[\'download\'],FILTER_SANITIZE_URL))).\'</a> was saved to disk.</p>\';\r\n            }\r\n        }\r\n        else {\r\n            echo \'<p>Unable to save to disk.</p>\';\r\n        }\r\n        \r\n        unset($txp_download_plugin);        \r\n    }\r\n    # BEGINNING: THIS IS A SEPARATE TOOL. WHEN USER WANTS TO \"SAVE TO DISK\"\r\n}\r\n# ENDING: AFTER USER CLICKS \"INSTALL\" LINK NEXT TO PLUGIN\r\n\r\n# BEGINNING: SEARCH\r\nif (isset($_POST[\'search\'])) {\r\n    $txp_search = strtolower(filter_var($_POST[\'search\'],FILTER_SANITIZE_STRING));\r\n    \r\n    #var_dump($get_from_cache);\r\n    \r\n    if (!empty($txp_search)) {\r\n        $txp_search_result_counter = 0;\r\n        foreach ($get_from_cache as $txp_found_plugin) {\r\n            # SEARCHING FROM PLUGIN NAME AND DESCRIPTION\r\n            if ((preg_match(\'/\'.$txp_search.\'/\',strtolower($txp_found_plugin[1]),$txp_search_result)) or (preg_match(\'/\'.$txp_search.\'/\',strtolower($txp_found_plugin[2]),$txp_search_result))) {\r\n                    \r\n                if ($txp_search_result_counter++ == 10) {\r\n                    break;\r\n                }\r\n                \r\n                if (strlen(filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING)) > 50) {\r\n                    $txp_search_result_description = substr(filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING),0,48).\'&hellip;\';\r\n                }\r\n                else {\r\n                    $txp_search_result_description = filter_var($txp_found_plugin[2],FILTER_SANITIZE_STRING);\r\n                }\r\n                \r\n                if (((in_array(substr($txp_found_plugin[1],0,3),$txp_final_prefix_array,1)) or \r\n                (in_array(substr($txp_found_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_APPENDED),1))) && ((!in_array($txp_found_plugin[1],$txp_plugins_array,1)) && \r\n                (!in_array(substr($txp_found_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED),1)) &&\r\n                (!in_array($txp_found_plugin[1],unserialize(TXP_GITHUB_NOT_A_PLUGIN),1)))) {\r\n                    $txp_search_result_install = \'<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($txp_found_plugin[1],FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\">Install</a>\';\r\n                }\r\n                else {\r\n                    $txp_search_result_install = NULL;\r\n                }\r\n                \r\n                $txp_search_results[] = \'<li><a href=\"\'.filter_var($txp_found_plugin[0],FILTER_SANITIZE_URL).\'\" rel=\"external\"\'.$open_in_new_tab.\'><strong>\'.filter_var($txp_found_plugin[1],FILTER_SANITIZE_STRING).\':</strong> <q>\'.$txp_search_result_description.\'</q></a> | \'.$txp_search_result_install.\'</li>\';\r\n            }\r\n        }\r\n        if (empty($txp_search_results)) {\r\n            $txp_search_results = NULL;\r\n        }\r\n        unset($txp_search_result_counter);\r\n    }\r\n\r\n}\r\nelse {\r\n    $txp_search = NULL;\r\n}\r\n# ENDING: SEARCH\r\n\r\necho \'<form action=\"#txp_seach\" method=\"post\" id=\"txp_seach\"><label for=\"txp_search\">Search:</label><input name=\"search\" type=\"text\" value=\"\'.$txp_search.\'\" size=\"25\" maxlength=\"50\" /><button type=\"submit\">OK</button></form>\';\r\n\r\nif ((isset($txp_search_results)) && ($txp_search_results !== NULL)) {\r\n    echo \'<div id=\"txp_search_results\">\'.TXP_GITHUB_CLOSE_LINK.\'<h4>Search results for &quot;\'.$txp_search.\'&quot;</h4><ol>\';\r\n    foreach ($txp_search_results as $txp_search_results) {\r\n        echo $txp_search_results;\r\n    }\r\n    echo \'</ol></div>\';\r\n}\r\n\r\n# BEGINNING: AT THE POINT WE COULD GET PLUGIN FILES SAVED TO DISK\r\nif ((file_exists(TXP_GITHUB_FOLDER)) && (is_readable(TXP_GITHUB_FOLDER)) && (count(glob(TXP_GITHUB_FOLDER.\'*.txt\')) != 0) && (!isset($_GET[\'disk\']))) {\r\n    echo \'<p><a href=\"index.php?event=kuo_github_viewer&amp;disk\" rel=\"nofollow\">View files saved to disk</a></p>\';\r\n}\r\n\r\nif ((file_exists(TXP_GITHUB_FOLDER)) && (is_readable(TXP_GITHUB_FOLDER)) && (count(glob(TXP_GITHUB_FOLDER.\'*.txt\')) != 0) && (isset($_GET[\'disk\'])) && (empty($_GET[\'disk\']))) {\r\n    # WHEN USER CLICKS \"DELETE FROM DISK\"\r\n    if ((isset($_GET[\'delete\'])) && (!empty($_GET[\'delete\'])) && (!is_numeric($_GET[\'delete\'])) && (!is_array($_GET[\'delete\']))) {\r\n        $txp_file_to_be_deleted = filter_var(base64_decode($_GET[\'delete\']),FILTER_SANITIZE_STRING);\r\n\r\n        if ((!empty($txp_file_to_be_deleted)) && (file_exists($txp_file_to_be_deleted)) && (substr($txp_file_to_be_deleted,-4,4) === \'.txt\')) {\r\n            unlink($txp_file_to_be_deleted);\r\n            echo \'<p>File was deleted.</p>\';\r\n        }\r\n        else {\r\n            echo \'<p>Unable to delete.</p>\';\r\n        }\r\n        \r\n        unset($txp_file_to_be_deleted);\r\n    }\r\n    \r\n    # CREATING A LIST OF FILES\r\n    echo \'<div id=\"txp_saved_files\">\'.TXP_GITHUB_CLOSE_LINK.\'<h3>Files saved to disk</h3><ol>\';\r\n    foreach (array_reverse(glob(TXP_GITHUB_FOLDER.\'*.txt\',GLOB_NOSORT),true) as $txp_file) {\r\n        \r\n        # TRYING TO ANALYZE THE CONTENT TO SE IF IT\'S A PLUGIN OR NOT\r\n        $txp_is_this_a_plugin = file_get_contents(filter_var($txp_file,FILTER_SANITIZE_STRING),0,NULL,50,300);\r\n        if (strpos($txp_is_this_a_plugin,\'This is a plugin for Textpattern\') == true) {\r\n            $txp_is_this_a_plugin = \' [plugin]\';\r\n        }\r\n        else {\r\n            $txp_is_this_a_plugin = NULL;\r\n        }\r\n        \r\n        echo \'<li>\'.date(TXP_GITHUB_DATE_FORMAT.\' (H:i:s)\',filemtime(filter_var($txp_file,FILTER_SANITIZE_STRING))).\':\'.$txp_is_this_a_plugin.\'\r\n        <a href=\"\'.filter_var($txp_file,FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\"\'.$open_in_new_tab.\'>\'.basename(filter_var($txp_file,FILTER_SANITIZE_STRING)).\'</a>  | <a href=\"index.php?event=kuo_github_viewer&amp;disk&amp;delete=\'.base64_encode(filter_var($txp_file,FILTER_SANITIZE_STRING)).\'\" rel=\"nofollow\">Delete from disk</a></li>\';\r\n    }\r\n    unset($txp_is_this_a_plugin);\r\n    unset($txp_file);\r\n    echo \'</ol></div>\';\r\n}\r\n# ENDING: AT THE POINT WE COULD GET PLUGIN FILES SAVED TO DISK\r\n\r\necho \'<table><thead><tr><th>#</th><th class=\"txp_plugin_name\">Name</th><th class=\"txp_plugin_description\">Description</th><th class=\"txp_plugin_updated\">Updated</th><th>Stars</th><th>Followers</th><th>Installed</th><th class=\"sorter-false\">&nbsp;</th></tr></thead><tbody>\';\r\n\r\nforeach ($get_from_cache as $txp_plugin) {\r\necho \'<tr>\r\n<td>\'.$plugin_counter++.\'.</td>\r\n<td><a href=\"\'.filter_var($txp_plugin[0],FILTER_SANITIZE_URL).\'\" rel=\"external\"\'.$open_in_new_tab.\'>\'.filter_var($txp_plugin[1],FILTER_SANITIZE_STRING).\'</a></td>\r\n<td>\'.$txp_plugin[2].\'</td><td>\'.filter_var($txp_plugin[3],FILTER_SANITIZE_STRING).\'</td><td>\'.filter_var($txp_plugin[4],FILTER_SANITIZE_NUMBER_INT).\'</td>\r\n<td>\'.filter_var($txp_plugin[5],FILTER_SANITIZE_NUMBER_INT).\'</td>\r\n<td>\';\r\n\r\n# THIS MARKS PLUGINS WHICH ARE INSTALLED (DOESN\'T MATTER IF ACTIVE OR NOT)\r\nif (in_array($txp_plugin[1],$txp_plugins_array)) {\r\necho \'Yes\';\r\n}\r\nelse {\r\necho \'No\';\r\n}\r\n\r\necho \'</td><td>\';\r\n\r\n# THIS WILL LIST PLUGINS WHICH CAN BE INSTALLED\r\nif (((in_array(substr($txp_plugin[1],0,3),$txp_final_prefix_array,1)) or \r\n(in_array(substr($txp_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_APPENDED),1))) && ((!in_array($txp_plugin[1],$txp_plugins_array,1)) && \r\n(!in_array(substr($txp_plugin[1],0,3),unserialize(TXP_GITHUB_PLUGIN_PREFIXES_EXCLUDED),1)) &&\r\n(!in_array($txp_plugin[1],unserialize(TXP_GITHUB_NOT_A_PLUGIN),1)))) {\r\n    echo \'<a href=\"index.php?event=kuo_github_viewer&amp;install=\'.filter_var($txp_plugin[1],FILTER_SANITIZE_STRING).\'\" rel=\"nofollow\">Install</a>\';\r\n}\r\nelse {\r\n    echo \'&nbsp;\';\r\n}\r\n\r\necho \'</td></tr>\';\r\n}\r\n\r\necho \'</tbody></table><p>Source: <a href=\"https://github.com/MST-Textpattern\" rel=\"external\"\'.$open_in_new_tab.\'>MST-Textpattern / Github</a>. <a href=\"index.php?event=kuo_github_viewer&amp;quota\" rel=\"nofollow\">What is query quota?</a></p></div>\';\r\n\r\nunset($plugin_counter);\r\nunset($get_from_cache);\r\n\r\n}\r\n}\r\n\r\n}\r\n# ENDING: ACTUAL CODE\r\n\r\n}\r\n# ENDING: IF ADMIN INTERFACE', 'bdad57f5fc64106279df5d5524073416', '', '', 3, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_glogin', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Log in to Textpattern\'s public side with Google.', '<ul id=\"links\">\n<li id=\"kuo_glogin\"><a href=\"#kuo_glogin\">kuo_glogin</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>This plugin makes it possible for people who have a Google account to easily log in to your website´s public side. User´s are registered like &quot;real&quot; Textpattern users, but they simply log in using their Google account. Users who register with their Google account do not have the option to choose their login name to Textpattern. They can log in with Google account only to the public side of website.</p>\n<p id=\"notice\">If this plugin is correctly installed, anyone can log in to your Textpattern site.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_glogin</code>.</li>\n<li>Upload folder called <code>kuo_glogin</code> to your website´s root.</li>\n<li>Log in to Google API, create a project and copy that project´s Client ID and Client Secret. Then paste them to this plugins´s preferences page.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_glogin</code>.</li>\n<li><span>[Optional]</span> Delete folder called <code>kuo_glogin</code> from your website´s root.</li>\n<li><span>[Optional]</span> Remove your project from Google API.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; } #briefly img { display: block; text-align: center; margin: 0 auto 10px auto; width: 90%; height: auto; max-width: 1000px; border: 5px solid #f0f0f0; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\n\r\nglobal $event;\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_glogin\') {\r\nheader(\'location: ./index.php?event=kuo_glogin\');\r\ndie;\r\n}\r\n\r\n# PLUGIN NAME\r\nif (!defined(\'KUO_NAME\')) {\r\ndefine(\'KUO_NAME\',\'Google login for Textpattern\');\r\n}\r\n\r\n# VERSION NUMBER\r\nif (!defined(\'KUO_VERSION\')) {\r\ndefine(\'KUO_VERSION\',\'0.1\');\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_glogin\',KUO_NAME);\r\nregister_callback(\'kuo_glogin_tab\',\'kuo_glogin\');\r\nadd_privs(\'kuo_glogin\',\'1\');\r\n\r\n# IF PLUGIN IS ENABLED\r\nregister_callback(\'kuo_glogin_enabled\',\'plugin_lifecycle.kuo_glogin\',\'enabled\');\r\nfunction kuo_glogin_enabled($event,$step) {\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_glogin_prefs\').\" (\r\n`id` INT NOT NULL AUTO_INCREMENT,\r\n`login_url` VARCHAR(255),\r\n`user_privs` INT(1) NOT NULL DEFAULT \'0\',\r\n`logged_in_url` VARCHAR(255),\r\n`logged_out_url` VARCHAR(255) ,\r\n`textpattern_folder` VARCHAR(255) NOT NULL DEFAULT \'textpattern\',\r\n`site_path` VARCHAR(255),\r\n`client_id` VARCHAR(255),\r\n`client_secret` VARCHAR(255),\r\nPRIMARY KEY (`id`)\r\n)\");\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_glogin_banned\').\" (\r\n`id` INT NOT NULL AUTO_INCREMENT,\r\n`gplus_id` VARCHAR(255),\r\n`when` DATETIME,\r\n`why` VARCHAR(255) DEFAULT \'Not specified\',\r\nPRIMARY KEY (`id`)\r\n)\");\r\nsafe_query(\"INSERT INTO \".safe_pfx(\'kuo_glogin_prefs\').\" SET `login_url` = NULL, `user_privs` = \'0\', `logged_in_url` = NULL, `logged_out_url` = NULL, `textpattern_folder` = \'textpattern\', `site_path` = NULL, `client_id` = NULL, `client_secret` = NULL\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_avatar` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_url` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_id` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_login_times` INT NOT NULL DEFAULT \'1\'\");\r\n}\r\n\r\n# IF PLUGIN IS DISABLED\r\nregister_callback(\'kuo_glogin_disabled\',\'plugin_lifecycle.kuo_glogin\',\'disabled\');\r\nfunction kuo_glogin_disabled($event,$step) {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_prefs\'));\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_banned\'));\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_avatar`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_url`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_id`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_login_times`\");\r\n}\r\n\r\n# IF PLUGIN IS DELETED\r\nregister_callback(\'kuo_glogin_deleted\',\'plugin_lifecycle.kuo_glogin\',\'deleted\');\r\nfunction kuo_glogin_deleted($event,$step) {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_prefs\'));\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_banned\'));\r\n}\r\n\r\nif ($GLOBALS[\'event\'] === \'kuo_glogin\') {\r\n# THIS SHOULD BE THE ADMIN-SIDE\r\n# PAGE UNDER EXTENSIONS TAB\r\n\r\nfunction kuo_glogin_tab($event,$step) {\r\npagetop(KUO_NAME.\' \'.KUO_VERSION);\r\n\r\n# CREATING A FILE NAME FOR EXPORT FILE\r\n$export_file_name = safe_row(\'login_url\',\'kuo_glogin_prefs\',\'id=1\');\r\nif ((isset($export_file_name[\'login_url\'])) && (!empty($export_file_name[\'login_url\'])) && (filter_var($export_file_name[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$export_file_name = md5(strrev($export_file_name[\'login_url\']));\r\n}\r\nelse {\r\n$export_file_name = md5(strrev(KUO_NAME));\r\n}\r\n\r\n$active_id_html = \' id=\"kuo_active\"\';\r\n$kuo_selected_html = \' selected=\"selected\"\';\r\n$good_html = \'<strong class=\"good\">:-)</strong>\';\r\n$broken_html = \'<strong class=\"broken\">:-(</strong>\';\r\n\r\necho \'<style type=\"text/css\">\r\n#kuo_tools, form ul, dd ul { list-style: none; padding: 0; margin: 0; }\r\n#kuo_tools { border-bottom: 5px solid #f0f0f0; display: table; width: 100%; height: 100%; clear: both; }\r\n#kuo_tools li { display: inline; float: left; }\r\n#kuo_tools a { float: left; padding: 5px 10px; background: #f9f9f9; border-right: 1px solid #ccc; border-top: 1px solid #ccc; color: #555; margin-right: 5px; }\r\n#kuo_tools a:hover, #kuo_tools a:hover, #kuo_tools a:active, #kuo_tools #kuo_active a { background: #f0f0f0; text-decoration: none; }\r\n#kuo_area h2, #kuo_area  h3, #kuo_area  h4, #kuo_area  h5, #kuo_area  h6, #kuo_area  dt { font-family: Arial, Helvetica, sans-serif; }\r\n#kuo_area h2 { font-size: 2em; margin-bottom: 0; }\r\n#kuo_area h3 { font-size: 1.5em; }\r\n#kuo_area dt { font-size: 1.2em; }\r\n#kuo_area { min-height: 30em; background: #fcfcfc; border: 1px solid #f0f0f0; border-bottom-width: 5px; border-top: 0; padding: 2%; display: block; clear: both; }\r\n#kuo_area fieldset { padding: 10px 10px 0 10px; }\r\n#kuo_area form li { display: block; clear: both; padding-bottom: 10px; }\r\n#kuo_area label { float: left; width: 250px; padding: 2px 5px 0 0; }\r\n#kuo_area input[type=radio],\r\n#kuo_area input[type=checkbox],\r\n#kuo_area label,\r\n#kuo_area select,\r\n#kuo_area input[type=submit],\r\n#kuo_area input[type=button]\r\n#kuo_area button { cursor: pointer; }\r\n#kuo_area ul li span, #kuo_area .checkbox { display: block; clear: both; margin-left: 255px; }\r\n#kuo_area ul li span:before { content: \"\\25b2 \"; }\r\n#kuo_area input[type=text] { min-width: 400px; max-width: 700px; }\r\n#kuo_area .good { background: #690; color: #fff; padding: 2px 5px; }\r\n#kuo_area .broken { background: #333; color: #fff; padding: 2px 5px; }\r\n#kuo_area dd { margin: 1% 0; padding: 0; }\r\n#kuo_area dd ul li { padding: 0.5% 1%; border-top: 1px solid #f0f0f0; border-bottom: 1px solid #ccc; position: relative; width: 21%; float: left; clear: none; margin-right: 2%; margin-bottom: 0.5%; background: #fff; min-width: 150px; }\r\n#kuo_area dd ul li strong { position: absolute; right: 10px; top: 3px; }\r\n#kuo_area table { border-collapse: collapse; width: 100%; }\r\n#kuo_area th, #kuo_area td { padding: 5px; text-align: left; vertical-align: top; }\r\n#kuo_area th { font-weight: bold; border-bottom: 2px solid #f0f0f0; }\r\n#kuo_area td { border-bottom: 1px solid #f0f0f0; }\r\n#kuo_banner { margin-top: 1%; }\r\n#kuo_area li button, #kuo_area li input[type=submit], #kuo_area li input[type=button] { margin-left: 255px; }\r\n#kuo_area fieldset { border-top: 2px solid #ccc; }\r\n#kuo_area .kuo_box { float: right; width: 10%; min-width: 180px; padding: 1% 1% 0 1%; background: #eee; border: 1px solid #ccc; border-right-width: 2px; border-bottom-width: 2px; margin: 0.5% 0 0.5% 0.5%; list-style: none; }\r\n#kuo_area .kuo_box li { padding-bottom: 10px; }\r\n#kuo_area .blue, #kuo_area .message { padding: 1%; background: #3F81ED; color: #fff; }\r\n#kuo_area .blue code { background: #0157E4; color: #ffc; border-color: #0157E4; }\r\n#kuo_area hr { border: 1px dashed #ccc; }\r\n#kuo_donate { float: right; padding-top: 10px; }\r\n#kuo_note { text-align: center; color: #999; }\r\n#kuo_area td a { background: #3F81ED; color: #fff; padding: 0 4px; text-decoration: none; }\r\n#kuo_area td a:hover, #kuo_area td a:focus, #kuo_area td a:active { background: #0157E4; }\r\n#kuo_area dt code { font-weight: normal; }\r\n#kuo_area.taglist dd { padding-left: 5%; max-width: 600px; }\r\n#kuo_area.taglist dl { padding-bottom: 2%; }\r\n#kuo_area.taglist dl:last-child { padding-bottom: 1%; }\r\n#kuo_area .checkbox label, #kuo_area .checkbox input { float: none; display: inline; width: auto; vertical-align: baseline; padding: 0; }\r\n#kuo_area .big.and.bold { font: 1.1em Arial, Helvetica, sans-serif; padding: 10px; background: #eee; border: 1px solid #f0f0f0; }\r\n</style>\r\n\r\n<ul id=\"kuo_tools\">\r\n<li\';\r\nif (empty($GLOBALS[\'step\'])) { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin\" rel=\"nofollow chapter\">Overview</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'settings\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=settings\" rel=\"nofollow chapter\">Settings</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'banlist\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=banlist\" rel=\"nofollow chapter\">Banlist</a></li>\r\n<li\';\r\nif (($GLOBALS[\'step\'] === \'userlist\') or ($GLOBALS[\'step\'] === \'export\')) { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=userlist\" rel=\"nofollow chapter\">Userlist</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'tags\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=tags\" rel=\"nofollow chapter\">Tags</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'description\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=description\" rel=\"nofollow chapter\">Description</a></li>\r\n</ul><div id=\"kuo_area\"\';\r\nif ($GLOBALS[\'step\'] === \'tags\') { echo \' class=\"taglist\"\'; }\r\necho \'><h2>\'.KUO_NAME.\', version \'.KUO_VERSION.\'</h2>\';\r\nif (empty($GLOBALS[\'step\'])) {\r\n\r\n$are_prefs = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\n\r\nif ((isset($are_prefs[\'id\'])) && (!empty($are_prefs[\'id\'])) && (is_numeric($are_prefs[\'id\'])) && ($are_prefs[\'id\'] === \'1\')) {\r\n$kuo_status_id = $good_html;\r\n}\r\nelse {\r\n$kuo_status_id = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'login_url\'])) && (!empty($are_prefs[\'login_url\'])) && (filter_var($are_prefs[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_login_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_login_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'user_privs\'])) && (!empty($are_prefs[\'user_privs\'])) or ($are_prefs[\'user_privs\'] === \'0\') or (($are_prefs[\'user_privs\'] <= 6) && ($are_prefs[\'user_privs\'] >= 4))) {\r\n$kuo_status_user_privs = $good_html;\r\n}\r\nelse {\r\n$kuo_status_user_privs = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'logged_in_url\'])) && (!empty($are_prefs[\'logged_in_url\'])) && (filter_var($are_prefs[\'logged_in_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_logged_in_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_logged_in_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'logged_out_url\'])) && (!empty($are_prefs[\'logged_out_url\'])) && (filter_var($are_prefs[\'logged_out_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_logged_out_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_logged_out_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'textpattern_folder\'])) && (!empty($are_prefs[\'textpattern_folder\'])) && (file_exists(\'../\'.$are_prefs[\'textpattern_folder\'].\'/\'))) {\r\n$kuo_status_textpattern_folder = $good_html;\r\n}\r\nelse {\r\n$kuo_status_textpattern_folder = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'site_path\'])) && (!empty($are_prefs[\'site_path\'])) && (strpos($are_prefs[\'site_path\'],\'/\'))) {\r\n$kuo_status_site_path = $good_html;\r\n}\r\nelse {\r\n$kuo_status_site_path = $broken_html;\r\n}\r\n\r\n# IS SETTINGS TABLE THERE?\r\n$status_table1 = safe_query(\"SELECT * FROM \".safe_pfx(\'kuo_glogin_prefs\'));\r\nif ($status_table1) {\r\n$kuo_status_prefs_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_prefs_table = $broken_html;\r\n}\r\n\r\n# IS TXP_USERS UPDATED TO INCLUDE NEW DATA?\r\n$status_table2 = safe_query(\"SELECT `gplus_avatar`, `gplus_url`, `gplus_id` FROM \".safe_pfx(\'txp_users\'));\r\nif ($status_table2) {\r\n$kuo_status_users_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_users_table = $broken_html;\r\n}\r\n\r\n# IS BANLIST TABLE THERE?\r\n$status_table3 = safe_query(\"SELECT * FROM \".safe_pfx(\'kuo_glogin_banned\'));\r\nif ($status_table3) {\r\n$kuo_status_banned_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_banned_table = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'client_id\'])) && (!empty($are_prefs[\'client_id\'])) && (strpos($are_prefs[\'client_id\'],\'google\'))) {\r\n$kuo_client_id = $good_html;\r\n}\r\nelse {\r\n$kuo_client_id = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'client_secret\'])) && (!empty($are_prefs[\'client_secret\'])) && (strlen($are_prefs[\'client_secret\']) <= 255) && (strlen($are_prefs[\'client_secret\']) >= 5)) {\r\n$kuo_client_secret = $good_html;\r\n}\r\nelse {\r\n$kuo_client_secret = $broken_html;\r\n}\r\n\r\necho \'<h3>Overview</h3>\r\n<dl>\r\n<dt>System status</dt>\r\n<dd><ul>\r\n<li>API &#9658; Client ID: \'.$kuo_client_id.\'</li>\r\n<li>API &#9658; Client Secret: \'.$kuo_client_secret.\'</li>\r\n<li>DB &#9658; kuo_glogin_settings \'.$kuo_status_prefs_table.\'</li>\r\n<li>DB &#9658; txp_users \'.$kuo_status_users_table.\'</li>\r\n<li>DB &#9658; kuo_glogin_banned \'.$kuo_status_banned_table.\'</li>\r\n<li>Preferences &#9658; ID row \'.$kuo_status_id.\'</li>\r\n<li>Login URL \'.$kuo_status_login_url.\'</li>\r\n<li>New user privileges \'.$kuo_status_user_privs.\'</li>\r\n<li>Logged in URL \'.$kuo_status_logged_in_url.\'</li>\r\n<li>Logged out URL \'.$kuo_status_logged_out_url.\'</li>\r\n<li>Textpattern folder \'.$kuo_status_textpattern_folder.\'</li>\r\n<li>Site path \'.$kuo_status_site_path.\'</li>\r\n</ul></dd>\r\n</dl>\r\n\r\n\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'settings\') {\r\n\r\n# SAVE BUTTON CLICKED\r\nif ((isset($_POST[\'save\'])) && (empty($_POST[\'save\']))) {\r\n\r\n# CHECKING CLIENT ID\r\nif ((isset($_POST[\'kuo_api_client_id\'])) && (!empty($_POST[\'kuo_api_client_id\'])) && (strlen($_POST[\'kuo_api_client_id\']) <= 255)) {\r\n$kuo_api_client_id = filter_var($_POST[\'kuo_api_client_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_api_client_id = NULL;\r\n}\r\n\r\n# CHECKING CLIENT SECRET\r\nif ((isset($_POST[\'kuo_api_client_secret\'])) && (!empty($_POST[\'kuo_api_client_secret\'])) && (strlen($_POST[\'kuo_api_client_secret\']) <= 255)) {\r\n$kuo_api_client_secret = filter_var($_POST[\'kuo_api_client_secret\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_api_client_secret = NULL;\r\n}\r\n\r\n# CHECKING LOGIN URL\r\nif ((isset($_POST[\'kuo_login_url\'])) && (!empty($_POST[\'kuo_login_url\'])) && (strlen($_POST[\'kuo_login_url\']) <= 255)) {\r\n$kuo_login_url = filter_var($_POST[\'kuo_login_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n}\r\n\r\n# CHECKING DEFAULT USER PRIVS\r\nif (((isset($_POST[\'kuo_user_privs\'])) && (!empty($_POST[\'kuo_user_privs\']))) or ($_POST[\'kuo_user_privs\'] === \'0\') && (strlen($_POST[\'kuo_user_privs\']) == 1) && (in_array($_POST[\'kuo_user_privs\'],array(\'0\',\'6\',\'5\',\'4\')))) {\r\n$kuo_user_privs = filter_var($_POST[\'kuo_user_privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_privs = NULL;\r\n}\r\n\r\n# CHECKING LOGGED IN URL\r\nif ((isset($_POST[\'kuo_logged_in_url\'])) && (!empty($_POST[\'kuo_logged_in_url\'])) && (strlen($_POST[\'kuo_logged_in_url\']) <= 255)) {\r\n$kuo_logged_in_url = filter_var($_POST[\'kuo_logged_in_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_in_url = NULL;\r\n}\r\n\r\n# CHECKING LOGGED OUT URL\r\nif ((isset($_POST[\'kuo_logged_out_url\'])) && (!empty($_POST[\'kuo_logged_out_url\'])) && (strlen($_POST[\'kuo_logged_out_url\']) <= 255)) {\r\n$kuo_logged_out_url = filter_var($_POST[\'kuo_logged_out_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_out_url = NULL;\r\n}\r\n\r\n# CHECKING TEXTPATTERN FOLDER\r\nif ((isset($_POST[\'kuo_textpattern_folder\'])) && (!empty($_POST[\'kuo_textpattern_folder\'])) && (strlen($_POST[\'kuo_textpattern_folder\']) <= 255)) {\r\n$kuo_textpattern_folder = filter_var($_POST[\'kuo_textpattern_folder\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_textpattern_folder = NULL;\r\n}\r\n\r\n# CHECKING SITE PATH\r\nif ((isset($_POST[\'kuo_site_path\'])) && (!empty($_POST[\'kuo_site_path\'])) && (strlen($_POST[\'kuo_site_path\']) <= 255)) {\r\n$kuo_site_path = filter_var($_POST[\'kuo_site_path\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_site_path = NULL;\r\n}\r\n\r\n# AND FINALLY SAVING DATA\r\nif ($kuo_user_privs != NULL) {\r\n\r\n# SOME SALT TO CLIENT ID AND CLIENT SECRECT\r\nif ($kuo_api_client_id != NULL) {\r\n$kuo_api_client_id_db = substr(hash(\'sha1\',time()),-6).$kuo_api_client_id;\r\n}\r\nelse {\r\n$kuo_api_client_id_db = NULL;\r\n}\r\nif ($kuo_api_client_secret != NULL) {\r\n$kuo_api_client_secret_db = substr(hash(\'sha1\',time()),-5).$kuo_api_client_secret;\r\n}\r\nelse {\r\n$kuo_api_client_secret_db = NULL;\r\n}\r\n\r\n$saving_prefs = safe_query(\"UPDATE \".safe_pfx(\'kuo_glogin_prefs\').\" SET `login_url` = \'\".$kuo_login_url.\"\', `user_privs` = \'\".$kuo_user_privs.\"\', `logged_in_url` = \'\".$kuo_logged_in_url.\"\', `logged_out_url` = \'\".$kuo_logged_out_url.\"\', `textpattern_folder` = \'\".$kuo_textpattern_folder.\"\', `site_path` = \'\".$kuo_site_path.\"\', `client_id` = \'\".$kuo_api_client_id_db.\"\', `client_secret` = \'\".$kuo_api_client_secret_db.\"\' WHERE id=\'1\'\");\r\n\r\nif ($saving_prefs) {\r\n$message = \'<p class=\"message\">Preferences were saved.</p>\';\r\n}\r\nelse {\r\n$message = \'<p class=\"message\">An error occurred. Preferences were not saved.</p>\';\r\n}\r\nunset($saving_prefs);\r\n\r\n}\r\n\r\n\r\n}\r\nelse {\r\n\r\n# FETCHING VALUES FROM DATABASE\r\n$settings = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\n\r\nif ((isset($settings[\'login_url\'])) && (!empty($settings[\'login_url\']))) {\r\n$kuo_login_url = filter_var($settings[\'login_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'user_privs\'])) && (!empty($settings[\'user_privs\'])) or ($settings[\'user_privs\'] === \'0\')) {\r\n$kuo_user_privs = filter_var($settings[\'user_privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_privs = NULL;\r\n}\r\n\r\nif ((isset($settings[\'logged_in_url\'])) && (!empty($settings[\'logged_in_url\']))) {\r\n$kuo_logged_in_url = filter_var($settings[\'logged_in_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_in_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'logged_out_url\'])) && (!empty($settings[\'logged_out_url\']))) {\r\n$kuo_logged_out_url = filter_var($settings[\'logged_out_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_out_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'textpattern_folder\'])) && (!empty($settings[\'textpattern_folder\']))) {\r\n$kuo_textpattern_folder = filter_var($settings[\'textpattern_folder\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_textpattern_folder = NULL;\r\n}\r\n\r\nif ((isset($settings[\'site_path\'])) && (!empty($settings[\'site_path\']))) {\r\n$kuo_site_path = filter_var($settings[\'site_path\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_site_path = NULL;\r\n}\r\n\r\nif ((isset($settings[\'client_id\'])) && (!empty($settings[\'client_id\']))) {\r\n$kuo_api_client_id = substr(filter_var($settings[\'client_id\'],FILTER_SANITIZE_STRING),6);\r\n}\r\nelse {\r\n$kuo_api_client_id = NULL;\r\n}\r\n\r\nif ((isset($settings[\'client_secret\'])) && (!empty($settings[\'client_secret\']))) {\r\n$kuo_api_client_secret = substr(filter_var($settings[\'client_secret\'],FILTER_SANITIZE_STRING),5);\r\n}\r\nelse {\r\n$kuo_api_client_secret = NULL;\r\n}\r\n\r\n$message = NULL;\r\n\r\n}\r\n\r\necho \'<h3>Settings</h3>\r\n\'.$message.\'\r\n<p>Please make sure these settings are correct.</p>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=settings\" method=\"post\">\r\n<fieldset>\r\n<legend>General settings</legend>\r\n<ul>\r\n<li><label for=\"kuo_api_client_id\">Google API Client ID:</label><input type=\"text\" id=\"kuo_api_client_id\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_api_client_id.\'\" name=\"kuo_api_client_id\" /><span>This is the Client ID, a code, from Google API.</span></li>\r\n<li><label for=\"kuo_api_secret\">Google API Client Secret:</label><input type=\"text\" id=\"kuo_api_client_secret\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_api_client_secret.\'\" name=\"kuo_api_client_secret\" /><span>This is the Client Secret, another code, from Google API.</span></li>\r\n<li><label for=\"kuo_login_url\">URL for <code>kuo_glogin</code>:</label><input type=\"text\" id=\"kuo_login_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_login_url.\'\" name=\"kuo_login_url\" /><span>Full URL address for the plugin´s <code>kuo_glogin</code> folder, including the <code>index.php</code> file name. For example: <code>http://www.mysite.com/kuo_glogin/index.php</code></span></li>\r\n<li><label for=\"kuo_user_privs\">Privileges for new users who log in with Google:</label><select name=\"kuo_user_privs\" id=\"kuo_user_privs\">\r\n<optgroup label=\"Default\">\r\n<option value=\"0\"\';\r\nif ($kuo_user_privs === \'0\') { echo $kuo_selected_html; }\r\necho \'>0 = User</option>\r\n</optgroup>\r\n<optgroup label=\"Other\">\r\n<option value=\"6\"\';\r\nif ($kuo_user_privs === \'6\') { echo $kuo_selected_html; }\r\necho \'>6 = Designer</option>\r\n<option value=\"5\"\';\r\nif ($kuo_user_privs === \'5\') { echo $kuo_selected_html; }\r\necho \'>5 = Freelancer</option>\r\n<option value=\"4\"\';\r\nif ($kuo_user_privs === \'4\') { echo $kuo_selected_html; }\r\necho \'>4 = Staff Writer</option>\r\n</optgroup>\r\n</select><span>Default is <code>0</code>, which means user is registered, but not allowed to publish or edit anything, or to even see any tabs here at the Textpattern´s admin panel.</span></li>\r\n<li><label for=\"kuo_logged_in_url\">URL where user is taken after logging <strong>in</strong>:</label><input type=\"text\" id=\"kuo_logged_in_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_logged_in_url.\'\" name=\"kuo_logged_in_url\" /><span>Full URL address, please. This is the page user is relocated after logging in with Google account. For example: <code>http://www.mysite.com/welcome/</code>.</span></li>\r\n<li><label for=\"kuo_logged_out_url\">URL where user is taken after logging <strong>out</strong>:</label><input type=\"text\" id=\"kuo_logged_out_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_logged_out_url.\'\" name=\"kuo_logged_out_url\" /><span>Full URL address, please. This is the page user is relocated after logging out from the site (inc. Textpattern). For example: <code>http://www.mysite.com/logged-out/</code></span></li>\r\n<li><label for=\"kuo_textpattern_folder\">The name of your website´s Textpattern folder:</label><input type=\"text\" id=\"kuo_textpattern_Folder\" maxlength=\"255\" size=\"15\" value=\"\'.$kuo_textpattern_folder.\'\" name=\"kuo_textpattern_folder\" /><span>Usually it´s <code>textpattern</code>. Please no trailing slashes to this field.</span></li>\r\n<li><label for=\"kuo_site_path\">The path where your website is located:</label><input type=\"text\" id=\"kuo_site_path\" maxlength=\"255\" size=\"25\" value=\"\'.$kuo_site_path.\'\" name=\"kuo_site_path\" /><span>If your website is located at the root of your domain, then put just trailing slash, like this: <code>/</code>. But if your Textpattern site is located in some subfolder, then put the name of the folder, and a trailing slash at the end. Like this: <code>newsite/</code></li>\r\n<li><button type=\"submit\" name=\"save\">Save</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'banlist\') {\r\n\r\n# IF USER WANTS TO LIFT BAN\r\nif ((isset($_POST[\'delete\'])) && (!empty($_POST[\'delete\'])) && (is_numeric($_POST[\'delete\']))) {\r\n$ban_to_delete = filter_var($_POST[\'delete\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n# OK THIS FAR, LETS SEE IF USER IS BANNED\r\nif ((isset($ban_to_delete)) && (!empty($ban_to_delete)) && (is_numeric($ban_to_delete))) {\r\n$is_user_banned = safe_row(\'*\',\'kuo_glogin_banned\',\'id=\'.$ban_to_delete);\r\n\r\nif ($is_user_banned == TRUE) {\r\nif (safe_delete(\'kuo_glogin_banned\',\'id = \'.doSlash($ban_to_delete))) {\r\n$ban_message = \'<p class=\"message\">Ban has been lifted.</p>\';\r\nunset($ban_to_delete);\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">Unable to remove ban.</p>\';\r\n}\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">User is not banned.</p>\';\r\n}\r\n\r\n}\r\n\r\n}\r\nelse {\r\n$ban_message = NULL;\r\n}\r\n\r\n# IF USER WANTS TO BAN SOMEONE\r\nif ((isset($_POST[\'ban\'])) && (empty($_POST[\'ban\'])) && (isset($_POST[\'kuo_user_gplus_id\'])) && (!empty($_POST[\'kuo_user_gplus_id\'])) && (isset($_POST[\'kuo_user_gplus_why\']))) {\r\n\r\nif ((strlen($_POST[\'kuo_user_gplus_id\']) <= 255) && (strlen($_POST[\'kuo_user_gplus_id\']) >= 5)) {\r\n$kuo_user_gplus_id = filter_var($_POST[\'kuo_user_gplus_id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_gplus_id = NULL;\r\n}\r\n\r\nif (!empty($_POST[\'kuo_user_gplus_why\'])) {\r\n\r\nif (strlen($_POST[\'kuo_user_gplus_why\']) <= 255) {\r\n$kuo_user_gplus_why = filter_var($_POST[\'kuo_user_gplus_why\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\n# ALRIGHT, DATA SEEMS VALID TO BE SAVED? IF SO LETS ADD A NEW BANNED USER\r\nif ($kuo_user_gplus_id != NULL) {\r\n\r\n# BUT FIRST SHOULD SEE IF SAME G+ ALREADY EXISTS\r\n$is_already_banned = safe_row(\'*\',\'kuo_glogin_banned\',\'gplus_id = \'.$kuo_user_gplus_id);\r\nif (!$is_already_banned) {\r\n\r\n$ban_added = safe_query(\"INSERT INTO \".safe_pfx(\'kuo_glogin_banned\').\" (`gplus_id`,`why`,`when`) VALUES (\'\".doSlash($kuo_user_gplus_id).\"\',\'\".doSlash($kuo_user_gplus_why).\"\',NOW())\");\r\n\r\nif ($ban_added) {\r\n$ban_message = \'<p class=\"message\">User was banned.</p>\';\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">An error occurred. User was not banned.</p>\';\r\n}\r\n\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">User is already banned.</p>\';\r\n}\r\n\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_user_gplus_id = NULL;\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\necho \'<h3>Banlist</h3>\r\n\'.$ban_message.\'\r\n<form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th>Number</th>\r\n<th>Google+ ID</th>\r\n<th>When banned</th>\r\n<th>Why banned</th>\r\n<th>&nbsp;</th>\r\n</tr>\r\n</thead>\r\n<tbody>\';\r\n\r\n$banned_users = safe_rows(\"*\",\"kuo_glogin_banned\",\"1 ORDER BY id DESC\");\r\nforeach ($banned_users as $r) {\r\n\r\nif ((isset($r[\'id\'])) && (!empty($r[\'id\']))) {\r\n$kuo_id = filter_var($r[\'id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_id = filter_var($r[\'gplus_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_gplus_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'why\'])) && (!empty($r[\'why\']))) {\r\n$kuo_why = filter_var($r[\'why\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_why = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'when\'])) && (!empty($r[\'when\']))) {\r\n$kuo_when = filter_var($r[\'when\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_when = \'&nbsp;\';\r\n}\r\n\r\necho \'<tr>\r\n<td>\'.$kuo_id.\'</td>\r\n<td>\'.$kuo_gplus_id.\'</td>\r\n<td>\'.$kuo_when.\'</td>\r\n<td>\'.$kuo_why.\'</td>\r\n<td><form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\"><button type=\"submit\" name=\"delete\" value=\"\'.$r[\'id\'].\'\">Remove ban</button></form></td>\r\n</tr>\';\r\n\r\n}\r\n\r\nif (!$banned_users) {\r\necho \'<tr><td colspan=\"5\">No one has been banned.</td></tr>\';\r\n}\r\nelse {\r\necho \'</tbody>\r\n<tfoot>\r\n<tr>\r\n<td colspan=\"5\">Above is the list of currently banned users who have logged in with Google account. Users are banned by their Google+ ID.</td>\r\n</tr>\r\n</tfoot>\';\r\n}\r\n\r\necho \'</table>\r\n</form>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\" id=\"kuo_banner\">\r\n<fieldset>\r\n<legend>Ban a user</legend>\r\n<ul>\r\n<li><label for=\"kuo_user_gplus_id\">Google+ ID number:</label><input type=\"text\" size=\"20\" maxlength=\"255\" name=\"kuo_user_gplus_id\" id=\"kuo_user_gplus_id\" value=\"\" /><span>Enter a Google+ ID and click the button below. After that banned user cannot log in to this website with that Google+ ID.</span></li>\r\n<li><label for=\"kuo_user_gplus_why\">[Optional] Why user is banned:</label><input type=\"text\" size=\"20\" maxlength=\"255\" name=\"kuo_user_gplus_why\" id=\"kuo_user_gplus_why\" value=\"\" /><span>Optional description why this user is banned.</span></li>\r\n<li><button type=\"submit\" name=\"ban\">Ban user</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n\r\nunset($kuo_id);\r\nunset($kuo_gplus_id);\r\nunset($kuo_when);\r\nunset($kuo_why);\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'userlist\') {\r\necho \'<h3>Userlist</h3>\r\n<p><a href=\"./index.php?event=kuo_glogin&amp;step=export\" rel=\"nofollow chapter\">Export</a></p>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=userlist\" method=\"post\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th>Textpattern ID</th>\r\n<th>G+ ID</th>\r\n<th>Login name</th>\r\n<th>Real name</th>\r\n<th>Email</th>\r\n<th>Privileges</th>\r\n<th>Last login</th>\r\n<th>Avatar</th>\r\n<th>G+ URL</th>\r\n<th>G+ login times</th>\r\n</tr>\r\n</thead>\r\n<tbody>\';\r\n# CONSTRUCTING A LIST OF LATEST REGISTERED USERS\r\n$count_users = safe_query(\"SELECT COUNT(1) FROM \".safe_pfx(\'txp_users\'));\r\n$users_in_total = mysql_result($count_users,0,0);\r\n\r\necho \'<tr><td colspan=\"10\">You have \'.$users_in_total.\' users in total.</td></tr>\';\r\n\r\nunset($count_users);\r\nunset($users_in_total);\r\n\r\n$registered_users = safe_rows(\"*\",\"txp_users\",\"1 ORDER BY user_id DESC LIMIT 0, 100\");\r\nforeach ($registered_users as $r) {\r\n\r\nif ((isset($r[\'user_id\'])) && (!empty($r[\'user_id\']))) {\r\n$kuo_id = filter_var($r[\'user_id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_id = filter_var($r[\'gplus_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_gplus_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'name\'])) && (!empty($r[\'name\']))) {\r\n$kuo_name = filter_var($r[\'name\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_name = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'RealName\'])) && (!empty($r[\'RealName\']))) {\r\n$kuo_realname = filter_var($r[\'RealName\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_realname = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'email\'])) && (!empty($r[\'email\']))) {\r\n$kuo_email = str_replace(\'@\',\'&#064;\',filter_var($r[\'email\'],FILTER_SANITIZE_EMAIL));\r\n}\r\nelse {\r\n$kuo_email = \'&nbsp;\';\r\n}\r\n\r\nif (((isset($r[\'privs\'])) && (!empty($r[\'privs\']))) or ($r[\'privs\'] === \'0\')) {\r\n$kuo_privs = filter_var($r[\'privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_privs = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_url\'])) && (!empty($r[\'gplus_url\']))) {\r\n$kuo_gplus_url = filter_var($r[\'gplus_url\'],FILTER_SANITIZE_URL);\r\n\r\nif (!empty($kuo_gplus_url)) {\r\n$kuo_gplus_url = \'<a href=\"\'.$kuo_gplus_url.\'\" rel=\"nofollow external\">&#9658;</a>\';\r\n}\r\nelse {\r\n$kuo_gplus_url = \'&nbsp;\';\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_gplus_url = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'last_access\'])) && (!empty($r[\'last_access\']))) {\r\n$kuo_last_access = filter_var($r[\'last_access\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_last_access = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_avatar\'])) && (!empty($r[\'gplus_avatar\']))) {\r\n$kuo_avatar = filter_var($r[\'gplus_avatar\'],FILTER_SANITIZE_URL);\r\nif (!empty($kuo_avatar)) {\r\n$kuo_avatar = \'<img src=\"\'.str_replace(\'=50\',\'=25\',$kuo_avatar).\'\" alt=\"\" height=\"25\" width=\"25\" />\';\r\n}\r\nelse {\r\n$kuo_avatar = \'&nbsp;\';\r\n}\r\n}\r\nelse {\r\n$kuo_avatar = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_login_times\'])) && (!empty($r[\'gplus_login_times\'])) && (isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_login_times = filter_var($r[\'gplus_login_times\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_gplus_login_times = \'&nbsp;\';\r\n}\r\n\r\necho \'<tr>\r\n<td>\'.$kuo_id.\'</td>\r\n<td>\'.$kuo_gplus_id.\'</td>\r\n<td>\'.$kuo_name.\'</td>\r\n<td>\'.$kuo_realname.\'</td>\r\n<td>\'.$kuo_email.\'</td>\r\n<td><code>\'.$kuo_privs.\'</code></td>\r\n<td>\'.$kuo_last_access.\'</td>\r\n<td>\'.$kuo_avatar.\'</td>\r\n<td>\'.$kuo_gplus_url.\'</td>\r\n<td>\'.$kuo_gplus_login_times.\'</td>\r\n</tr>\';\r\n\r\n}\r\necho \'</tbody><tfoot><tr><td colspan=\"10\">Users are listed with their Textpattern ID so that newest is at the top, oldest at the bottom. The list shows 100 latest users.</td></tr></tfoot></table></form>\';\r\n\r\nunset($kuo_gplus_url);\r\nunset($kuo_avatar);\r\nunset($kuo_privs);\r\nunset($kuo_email);\r\nunset($kuo_realname);\r\nunset($kuo_name);\r\nunset($kuo_gplus_id);\r\nunset($kuo_id);\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'export\') {\r\n\r\n# IF USER HAS CLICKED THE EXPORT BUTTON\r\nif ((isset($_POST[\'export\'])) && (empty($_POST[\'export\']))) {\r\n\r\n$kuo_export_formats = array(\'csv\');\r\n\r\nif ((isset($_POST[\'kuo_export_format\'])) && (!empty($_POST[\'kuo_export_format\'])) && (in_array($_POST[\'kuo_export_format\'],$kuo_export_formats))) {\r\n\r\nif ($_POST[\'kuo_export_format\'] === \'csv\') {\r\n$kuo_export_format = \'csv\';\r\n}\r\nelse {\r\n$kuo_export_format = NULL;\r\n}\r\n\r\nif (isset($_POST[\'kuo_export_name\'])) {\r\n$kuo_export_name = 1;\r\n}\r\nelse {\r\n$kuo_export_name = 0;\r\n}\r\n\r\nif (isset($_POST[\'kuo_export_email\'])) {\r\n$kuo_export_email = 1;\r\n}\r\nelse {\r\n$kuo_export_email = 0;\r\n}\r\n\r\n# STARTING TO EXPORT CSV\r\nif ($kuo_export_format === \'csv\') {\r\n#$exported_users = safe_rows(\"DISTINCT *\",\"txp_users\",\"`name` IS NOT NULL AND `email` IS NOT NULL ORDER BY user_id DESC\",true);\r\n$exported_users = safe_rows(\"DISTINCT *\",\"txp_users\",\"1 ORDER BY user_id DESC\");\r\n\r\nif ($exported_users) {\r\n$fp = fopen($export_file_name.\'.csv\',\'w\');\r\nforeach ($exported_users as $row) {\r\n\r\nif ((isset($row[\'name\'])) && (!empty($row[\'name\'])) && (strlen($row[\'name\']) > 1)) {\r\n$export_name = $row[\'name\'];\r\n}\r\nelse {\r\n$export_name = NULL;\r\n}\r\n\r\nif ((isset($row[\'email\'])) && (!empty($row[\'email\'])) && (strlen($row[\'email\']) > 1)) {\r\n$export_email = $row[\'email\'];\r\n}\r\nelse {\r\n$export_email = NULL;\r\n}\r\n\r\nif (($kuo_export_email == 1) && ($kuo_export_name == 0)) {\r\nfputcsv($fp,array_filter(array($export_email)));\r\n}\r\nelseif (($kuo_export_email == 0) && ($kuo_export_name == 1)) {\r\nfputcsv($fp,array_filter(array($export_name)));\r\n}\r\nelse {\r\nfputcsv($fp,array($export_name,$export_email));\r\n}\r\n\r\n}\r\nfclose($fp);\r\nheader(\'location: \'.$export_file_name.\'.csv\');\r\n}\r\n\r\n}\r\n}\r\n}\r\n\r\necho \'<h3>Export user information</h3>\r\n<form action=\"./index.php?event=kuo_glogin&amp;step=export\" method=\"post\">\r\n<fieldset>\r\n<legend>Options</legend>\r\n<ul>\r\n<li><label for=\"kuo_export_format\">Export format:</label><select name=\"kuo_export_format\" id=\"kuo_export_format\"><option value=\"csv\" selected=\"selected\">CSV</option></select></li>\r\n<li class=\"checkbox\"><input type=\"checkbox\" name=\"kuo_export_name\" id=\"kuo_export_name\" checked=\"checked\" value=\"1\" /><label for=\"kuo_export_name\">Include name</label></li>\r\n<li class=\"checkbox\"><input type=\"checkbox\" name=\"kuo_export_email\" id=\"kuo_export_email\" checked=\"checked\" value=\"1\" /><label for=\"kuo_export_email\">Include email</label></li>\r\n<li><button type=\"submit\" name=\"export\">Export</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'description\') {\r\necho \'<h3>Description</h3>\r\n<p class=\"bold and big\">With this plugin users can register an account to Textpattern using their Google account. After registering user is logged in to the <strong>public side of the website</strong>. Currently (v. \'.KUO_VERSION.\') user is unable to enter the admin side straight with Google login.</p>\r\n<dl>\r\n<dt>Installing</dt>\r\n<dd><ol>\r\n<li>Install the Textpattern plugin called <code>kuo_glogin</code></li>\r\n<li>Upload folder called <code>kuo_glogin</code> to the root of your Textpattern website, like: <code>http://www.mysite.com/kuo_glogin/</code></li>\r\n<li>Start grinding through the steps listed below</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>Google Developers Console</dt>\r\n<dd><p>To use this plugin you should now login with your Google account to Google Developers Console, and then do the following:</p>\r\n<ol>\r\n<li>Click <strong>Projects</strong></li>\r\n<li>Click <strong>APIs</strong></li>\r\n<li>Click the <strong>Create Project</strong> button</li>\r\n<li>Give your project an informative name</li>\r\n<li>Let <strong>Project ID</strong> be whatever it is, or create something you like</li>\r\n<li>Click the <strong>Create</strong> button</li>\r\n<li>Wait for the project to load</li>\r\n<li>Check that <strong>Google+ API</strong> is set to <strong>On</strong></li>\r\n<li>When your project is processed, click it´s name</li>\r\n<li>Click then <strong>Credentials</strong> from the left side</li>\r\n<li>Click the <strong>Create new Client ID</strong> button under OAuth</li>\r\n<li>Select <strong>Web application</strong></li>\r\n<li>Either leave <strong>Authorized JavaScript Origins</strong> field empty or put your website´s URL there</li>\r\n<li><em>Important:</em> Put to <strong>Authorized Redirect URI</strong> your website´s URL, and include <code>kuo_glogin/index.php</code> at the end, like this: <code>http://www.mysite.com/kuo_glogin/index.php</code></li>\r\n<li>Click the <strong>Create Client ID</strong> button</li>\r\n<li>Copy your project´s <strong>Client ID</strong> and <strong>Client Secret</strong> and paste them to this plugin´s Settings page</li>\r\n<li>Done!</li>\r\n</ol>\r\n<p class=\"blue\">If you have created a project with Google API, installed and activated this plugin and uploaded the folder <code>kuo_glogin</code> to your website´s root, then you should have everything ready. Remember that phase 14 is most likely the one that´s causing problems. It should have exactly the correct URL. And if the plugin still doesn´t work, then go back to phase 13 and just leave the field empty at Google API.</p>\r\n</dd>\r\n</dl>\r\n<dl>\r\n<dt>General notes</dt>\r\n<dd><p>It´s important to remember that if this plugin is correctly installed, it means that anyone from anywhere can log in to your website with his Google account. That´s why by default this plugin grants level <code>0</code> for users. That´s the minimum level, which means user has been logged in and registered to Textpattern´s database &mdash; but he can´t basically do anything. User´s information is saved inside database. Immediately after logging in user can access the public and admin side of Textpattern website just like any regular Textpattern user.</p>\r\n<ol class=\"kuo_box\">\r\n<li>This plugin was tested with Textpattern 4.5.5.</li>\r\n<li>This control panel is currently available only in English.</li>\r\n<li>Use <code>kuo_glogin</code> with your own risk.</li>\r\n<li>To use this plugin website should have a Client ID and Client Secret from Google API.</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>When user logs in</dt>\r\n<dd>Logging in means user´s information is saved to the Textpattern database, to the <code>txp_users</code> table. The script fetches information from user´s Google account and places that inside <code>txp_users</code>. If user is a returning Google account user, some previously created information from the database will be used. The script tries to prevent duplicate accounts from being created.</dd>\r\n</dl>\r\n<dl>\r\n<dt>Original Textpattern users</dt>\r\n<dd>User accounts which are created with Textpattern will not be updated with Google account´s information if they should log in to the site with their Google account. That´s because inside <code>txp_users</code> those original Textpattern users do not have a field called <code>gplus_id</code>, which is used as an identifier for those users who log in with their Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt>Modifying &quot;Google account&quot; user´s privileges</dt>\r\n<dd>What about if someone who logs in with Google account should have wider privileges? In that case someone with Publisher privileges could modify that user´s privileges by going to Admin &rarr; Users.</dd>\r\n</dl>\r\n<dl>\r\n<dt>What information is fetched from Google account?</dt>\r\n<dd><ol>\r\n<li>Google+ ID number</li>\r\n<li>First name and last name</li>\r\n<li>Email address</li>\r\n<li>Avatar URL</li>\r\n<li>Google+ profile URL</li>\r\n<li>Google+ public name, &quot;nick&quot;</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>What´s the login URL?</dt>\r\n<dd>It´s something like this: <code>http://www.mysite.com/kuo_glogin/</code></dd>\r\n</dl>\r\n<dl>\r\n<dt>What´s the logout URL?</dt>\r\n<dd>It´s something like this: <code>http://www.mysite.com/kuo_glogin/?logout=1</code></dd>\r\n</dl>\r\n<dl>\r\n<dt>Can I change those two URL`s?</dt>\r\n<dd>Yes by modifying your Textpattern website´s root <code>.htaccess</code> file. Inside that file there should be a line of code which says: <code>RewriteEngine On</code>. Put right to the next line these codes:<br /><br />\r\n<code>RewriteRule ^login/$ ./kuo_glogin/index.php</code><br />\r\n<code>RewriteRule ^logout/$ ./kuo_glogin/?logout=1</code><br /><br />\r\nAfter that you can log in with <code>http://www.mysite.com/login/</code> and log out with <code>http://www.mysite.com/logout/</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt>What happens if I change the default user privilege?</dt>\r\n<dd>Default privilege is not updated to existing users, only to those new users who log in with their Google account.</p>\r\n</dl>\r\n<dl>\r\n<dt>Not working!</dt>\r\n<dd>Check your website´s <code>kuo_glogin</code> folder and see if there´s a file called <code>error_log</code>. If you find that file, open it and examine it´s contents. If you find error with code <code>(403) Access Not Configured.</code>, it means you haven´t enabled Google+ API from the Google Developers Console.</dd>\r\n</dl>\r\n<hr />\r\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_top\" id=\"kuo_donate\">\r\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />\r\n<input type=\"hidden\" name=\"hosted_button_id\" value=\"7YNFHD5HLUJT2\" />\r\n<input type=\"image\" src=\"https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif\" border=\"0\" name=\"submit\" alt=\"Donate with PayPal\" />\r\n</form>\r\n<p>\'.KUO_NAME.\' by <a href=\"http://kuopassa.net/txp/\" rel=\"nofollow external\">Petri Ikonen</a> with special thanks to <a href=\"https://github.com/google/google-api-php-client\" rel=\"nofollow external\">Google APIs Client Library for PHP</a> and everyone at the Textpattern CMS Support Forum.</p>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'tags\') {\r\necho \'<h3>Tags</h3>\r\n<p>These are public side tags. They can be placed inside forms and pages.</p>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_is_logged&gt;You are logged in.&lt;txp:else /&gt;You are not logged in.&lt;/txp:kuo_glogin_is_logged&gt;</code></dt>\r\n<dd>A conditional tag. Allows content to be targeted for users who´ve logged in and to those who are not.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_link /&gt;</code></dt>\r\n<dd>This tag creates a HTML tag, a normal link. Link´s <code>href</code> for logged in users is the logout URL, and for users who have not been logged in <code>href</code> is login URL. Link´s anchor text is localized automatically so that it´s fetched from Textpattern´s language preferences. So if your website is in English, the link says either <code>Login</code> or <code>Log out</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_link text=\"Log in by clicking this|Log out by clicking this\" /&gt;</code></dt>\r\n<dd>The tag can also have your own link text. It´s created so that pipe mark <code>|</code> separates the values. Left side value is for cases when user hasn´t logged in, right side is for user´s who are logged in.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_name /&gt;</code></dt>\r\n<dd>Outputs user´s login name. If user has been registered to the site via Google, login name might have a number at the end of it. That´s because each login name must be unique, so this plugin creates a unique incremental number and puts it at the end of the login name.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_real_name /&gt;</code></dt>\r\n<dd>This tag shows user´s full name. If it´s fetched from Google, it should be something like <code>Firstname Lastname</code>. If user logs in with Google account, his name is updated automatically to be the same what he´s using in his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_email /&gt;</code></dt>\r\n<dd>Outputs user´s currently used email address. If user logs in with Google account, his email address is updated to be the same what he´s using in his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_privs /&gt;</code></dt>\r\n<dd>This one simply outputs a numerical value. The number indicates what privileges user has. <code>0</code> = least privileges, <code>1</code> = Publisher.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_last_access /&gt;</code></dt>\r\n<dd>Date and time when user logged in last time. Not formatted in any way, so the style looks like: <code>yyyy-mm-dd hh:mm:ss</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_avatar /&gt;</code></dt>\r\n<dd>Creates an <code>img</code> HTML tag and shows by default 50&times;50 pixel size avatar from user´s Google account. If user hasn´t got an avatar, nothing is shown.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_avatar size=&quot;25&quot; /&gt;</code></dt>\r\n<dd>Same tag as above, but this one resizes the avatar´s height and width to the value set in <code>size</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_url /&gt;</code></dt>\r\n<dd>Shows only URL for user´s Google+ account. This URL is updated each time from Google if user logs in with his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_id /&gt;</code></dt>\r\n<dd>Shows only Google+ ID for user´s Google+ account. It should be a numerical value. It´s found also from the Google+ profile´s URL address.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_login_times /&gt;</code></dt>\r\n<dd>This long tag shows how many times user has logged in with his Google account. It <strong>does not</strong> count the times user has logged in the normal way.</dd>\r\n</dl>\';\r\n}\r\necho \'</div><p id=\"kuo_note\">(This section is visible only to those users who have Publisher privileges.)</p>\';\r\n\r\n# SHOULD DELETE THE EXPORT FILE AFTER ONE MINUTE\r\nif (file_exists($export_file_name.\'.csv\')) {\r\nif (filemtime($export_file_name.\'.csv\') <= strtotime(\'-1 minute\')) {\r\nunlink($export_file_name.\'.csv\');\r\n}\r\n}\r\n\r\n}\r\n\r\n}\r\n}\r\n\r\nif (@txpinterface == \'public\') {\r\n\r\n# TAG KUO_GLOGIN_LINK\r\nfunction kuo_glogin_link($atts,$thing=NULL) {\r\n# FOR TESTING\r\n#var_dump(is_logged_in());\r\n\r\nextract(lAtts(array(\'text\'=>\'Login|Logout\',),$atts));\r\n\r\nif ((isset($text)) && (!empty($text))) {\r\n$text = explode(\'|\',$text);\r\n$kuo_login_text = $text[0];\r\n$kuo_logout_text = $text[1];\r\n}\r\nelse {\r\n$kuo_login_text = gTxt(\'login\');\r\n$kuo_logout_text = gTxt(\'logout\');\r\n}\r\n\r\n$kuo_prefs = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\nif ($kuo_prefs) {\r\n\r\nif ((isset($kuo_prefs[\'login_url\'])) && (!empty($kuo_prefs[\'login_url\'])) && (filter_var($kuo_prefs[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_login_url = $kuo_prefs[\'login_url\'];\r\n$kuo_logout_url = $kuo_login_url.\'?logout=1\';\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n$kuo_logout_url = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n$kuo_logout_url = NULL;\r\n}\r\n\r\nif ((is_logged_in() === false) && ($kuo_login_url != NULL) && ($kuo_logout_url != NULL)) {\r\nreturn \'<a href=\"\'.$kuo_login_url.\'\" rel=\"chapter\">\'.$kuo_login_text.\'</a>\';\r\n}\r\nelse {\r\nreturn \'<a href=\"\'.$kuo_logout_url.\'\" rel=\"nofollow\">\'.$kuo_logout_text.\'</a>\';\r\n}\r\n\r\n}\r\n\r\n# TAG KUO_GLOGIN_NAME\r\nfunction kuo_glogin_name($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_name = is_logged_in();\r\nreturn htmlspecialchars($kuo_name[\'name\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_REALNAME\r\nfunction kuo_glogin_real_name($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'RealName\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_realname = is_logged_in();\r\nreturn htmlspecialchars($kuo_realname[\'RealName\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_EMAIL\r\nfunction kuo_glogin_email($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'email\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_email = is_logged_in();\r\nreturn htmlspecialchars($kuo_email[\'email\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_PRIVS\r\nfunction kuo_glogin_privs($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'privs\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_privs = is_logged_in();\r\nreturn htmlspecialchars($kuo_privs[\'privs\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_LASTACCESS\r\nfunction kuo_glogin_last_access($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `last_access` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_AVATAR\r\nfunction kuo_glogin_avatar($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\',\'size\'=>\'50\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif ((!isset($size)) or (empty($size)) or (!is_numeric($size))) {\r\n$size = 50;\r\n}\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_avatar` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn \'<img src=\"\'.htmlspecialchars(str_replace(\'sz=50\',\'sz=\'.$size,$kuo_result)).\'\" alt=\"\" height=\"\'.$size.\'\" width=\"\'.$size.\'\" />\';\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_GPLUS_URL\r\nfunction kuo_glogin_gplus_url($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_url` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_GPLUS_ID\r\nfunction kuo_glogin_gplus_id($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_id` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_IS_LOGGED\r\nfunction kuo_glogin_is_logged($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'privs\'),$atts));\r\n$kuo_privs = is_logged_in();\r\nreturn parse(EvalElse($thing,$kuo_privs));\r\n}\r\n\r\n# TAG KUO_GLOGIN_LOGIN_TIMES\r\nfunction kuo_glogin_gplus_login_times($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_login_Times` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n\r\n}', 'if (@txpinterface == \'admin\') {\r\n\r\nglobal $event;\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_glogin\') {\r\nheader(\'location: ./index.php?event=kuo_glogin\');\r\ndie;\r\n}\r\n\r\n# PLUGIN NAME\r\nif (!defined(\'KUO_NAME\')) {\r\ndefine(\'KUO_NAME\',\'Google login for Textpattern\');\r\n}\r\n\r\n# VERSION NUMBER\r\nif (!defined(\'KUO_VERSION\')) {\r\ndefine(\'KUO_VERSION\',\'0.1\');\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_glogin\',KUO_NAME);\r\nregister_callback(\'kuo_glogin_tab\',\'kuo_glogin\');\r\nadd_privs(\'kuo_glogin\',\'1\');\r\n\r\n# IF PLUGIN IS ENABLED\r\nregister_callback(\'kuo_glogin_enabled\',\'plugin_lifecycle.kuo_glogin\',\'enabled\');\r\nfunction kuo_glogin_enabled($event,$step) {\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_glogin_prefs\').\" (\r\n`id` INT NOT NULL AUTO_INCREMENT,\r\n`login_url` VARCHAR(255),\r\n`user_privs` INT(1) NOT NULL DEFAULT \'0\',\r\n`logged_in_url` VARCHAR(255),\r\n`logged_out_url` VARCHAR(255) ,\r\n`textpattern_folder` VARCHAR(255) NOT NULL DEFAULT \'textpattern\',\r\n`site_path` VARCHAR(255),\r\n`client_id` VARCHAR(255),\r\n`client_secret` VARCHAR(255),\r\nPRIMARY KEY (`id`)\r\n)\");\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_glogin_banned\').\" (\r\n`id` INT NOT NULL AUTO_INCREMENT,\r\n`gplus_id` VARCHAR(255),\r\n`when` DATETIME,\r\n`why` VARCHAR(255) DEFAULT \'Not specified\',\r\nPRIMARY KEY (`id`)\r\n)\");\r\nsafe_query(\"INSERT INTO \".safe_pfx(\'kuo_glogin_prefs\').\" SET `login_url` = NULL, `user_privs` = \'0\', `logged_in_url` = NULL, `logged_out_url` = NULL, `textpattern_folder` = \'textpattern\', `site_path` = NULL, `client_id` = NULL, `client_secret` = NULL\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_avatar` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_url` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_id` VARCHAR(255)\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" ADD `gplus_login_times` INT NOT NULL DEFAULT \'1\'\");\r\n}\r\n\r\n# IF PLUGIN IS DISABLED\r\nregister_callback(\'kuo_glogin_disabled\',\'plugin_lifecycle.kuo_glogin\',\'disabled\');\r\nfunction kuo_glogin_disabled($event,$step) {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_prefs\'));\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_banned\'));\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_avatar`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_url`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_id`\");\r\nsafe_query(\"ALTER TABLE \".safe_pfx(\'txp_users\').\" DROP COLUMN `gplus_login_times`\");\r\n}\r\n\r\n# IF PLUGIN IS DELETED\r\nregister_callback(\'kuo_glogin_deleted\',\'plugin_lifecycle.kuo_glogin\',\'deleted\');\r\nfunction kuo_glogin_deleted($event,$step) {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_prefs\'));\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_glogin_banned\'));\r\n}\r\n\r\nif ($GLOBALS[\'event\'] === \'kuo_glogin\') {\r\n# THIS SHOULD BE THE ADMIN-SIDE\r\n# PAGE UNDER EXTENSIONS TAB\r\n\r\nfunction kuo_glogin_tab($event,$step) {\r\npagetop(KUO_NAME.\' \'.KUO_VERSION);\r\n\r\n# CREATING A FILE NAME FOR EXPORT FILE\r\n$export_file_name = safe_row(\'login_url\',\'kuo_glogin_prefs\',\'id=1\');\r\nif ((isset($export_file_name[\'login_url\'])) && (!empty($export_file_name[\'login_url\'])) && (filter_var($export_file_name[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$export_file_name = md5(strrev($export_file_name[\'login_url\']));\r\n}\r\nelse {\r\n$export_file_name = md5(strrev(KUO_NAME));\r\n}\r\n\r\n$active_id_html = \' id=\"kuo_active\"\';\r\n$kuo_selected_html = \' selected=\"selected\"\';\r\n$good_html = \'<strong class=\"good\">:-)</strong>\';\r\n$broken_html = \'<strong class=\"broken\">:-(</strong>\';\r\n\r\necho \'<style type=\"text/css\">\r\n#kuo_tools, form ul, dd ul { list-style: none; padding: 0; margin: 0; }\r\n#kuo_tools { border-bottom: 5px solid #f0f0f0; display: table; width: 100%; height: 100%; clear: both; }\r\n#kuo_tools li { display: inline; float: left; }\r\n#kuo_tools a { float: left; padding: 5px 10px; background: #f9f9f9; border-right: 1px solid #ccc; border-top: 1px solid #ccc; color: #555; margin-right: 5px; }\r\n#kuo_tools a:hover, #kuo_tools a:hover, #kuo_tools a:active, #kuo_tools #kuo_active a { background: #f0f0f0; text-decoration: none; }\r\n#kuo_area h2, #kuo_area  h3, #kuo_area  h4, #kuo_area  h5, #kuo_area  h6, #kuo_area  dt { font-family: Arial, Helvetica, sans-serif; }\r\n#kuo_area h2 { font-size: 2em; margin-bottom: 0; }\r\n#kuo_area h3 { font-size: 1.5em; }\r\n#kuo_area dt { font-size: 1.2em; }\r\n#kuo_area { min-height: 30em; background: #fcfcfc; border: 1px solid #f0f0f0; border-bottom-width: 5px; border-top: 0; padding: 2%; display: block; clear: both; }\r\n#kuo_area fieldset { padding: 10px 10px 0 10px; }\r\n#kuo_area form li { display: block; clear: both; padding-bottom: 10px; }\r\n#kuo_area label { float: left; width: 250px; padding: 2px 5px 0 0; }\r\n#kuo_area input[type=radio],\r\n#kuo_area input[type=checkbox],\r\n#kuo_area label,\r\n#kuo_area select,\r\n#kuo_area input[type=submit],\r\n#kuo_area input[type=button]\r\n#kuo_area button { cursor: pointer; }\r\n#kuo_area ul li span, #kuo_area .checkbox { display: block; clear: both; margin-left: 255px; }\r\n#kuo_area ul li span:before { content: \"\\25b2 \"; }\r\n#kuo_area input[type=text] { min-width: 400px; max-width: 700px; }\r\n#kuo_area .good { background: #690; color: #fff; padding: 2px 5px; }\r\n#kuo_area .broken { background: #333; color: #fff; padding: 2px 5px; }\r\n#kuo_area dd { margin: 1% 0; padding: 0; }\r\n#kuo_area dd ul li { padding: 0.5% 1%; border-top: 1px solid #f0f0f0; border-bottom: 1px solid #ccc; position: relative; width: 21%; float: left; clear: none; margin-right: 2%; margin-bottom: 0.5%; background: #fff; min-width: 150px; }\r\n#kuo_area dd ul li strong { position: absolute; right: 10px; top: 3px; }\r\n#kuo_area table { border-collapse: collapse; width: 100%; }\r\n#kuo_area th, #kuo_area td { padding: 5px; text-align: left; vertical-align: top; }\r\n#kuo_area th { font-weight: bold; border-bottom: 2px solid #f0f0f0; }\r\n#kuo_area td { border-bottom: 1px solid #f0f0f0; }\r\n#kuo_banner { margin-top: 1%; }\r\n#kuo_area li button, #kuo_area li input[type=submit], #kuo_area li input[type=button] { margin-left: 255px; }\r\n#kuo_area fieldset { border-top: 2px solid #ccc; }\r\n#kuo_area .kuo_box { float: right; width: 10%; min-width: 180px; padding: 1% 1% 0 1%; background: #eee; border: 1px solid #ccc; border-right-width: 2px; border-bottom-width: 2px; margin: 0.5% 0 0.5% 0.5%; list-style: none; }\r\n#kuo_area .kuo_box li { padding-bottom: 10px; }\r\n#kuo_area .blue, #kuo_area .message { padding: 1%; background: #3F81ED; color: #fff; }\r\n#kuo_area .blue code { background: #0157E4; color: #ffc; border-color: #0157E4; }\r\n#kuo_area hr { border: 1px dashed #ccc; }\r\n#kuo_donate { float: right; padding-top: 10px; }\r\n#kuo_note { text-align: center; color: #999; }\r\n#kuo_area td a { background: #3F81ED; color: #fff; padding: 0 4px; text-decoration: none; }\r\n#kuo_area td a:hover, #kuo_area td a:focus, #kuo_area td a:active { background: #0157E4; }\r\n#kuo_area dt code { font-weight: normal; }\r\n#kuo_area.taglist dd { padding-left: 5%; max-width: 600px; }\r\n#kuo_area.taglist dl { padding-bottom: 2%; }\r\n#kuo_area.taglist dl:last-child { padding-bottom: 1%; }\r\n#kuo_area .checkbox label, #kuo_area .checkbox input { float: none; display: inline; width: auto; vertical-align: baseline; padding: 0; }\r\n#kuo_area .big.and.bold { font: 1.1em Arial, Helvetica, sans-serif; padding: 10px; background: #eee; border: 1px solid #f0f0f0; }\r\n</style>\r\n\r\n<ul id=\"kuo_tools\">\r\n<li\';\r\nif (empty($GLOBALS[\'step\'])) { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin\" rel=\"nofollow chapter\">Overview</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'settings\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=settings\" rel=\"nofollow chapter\">Settings</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'banlist\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=banlist\" rel=\"nofollow chapter\">Banlist</a></li>\r\n<li\';\r\nif (($GLOBALS[\'step\'] === \'userlist\') or ($GLOBALS[\'step\'] === \'export\')) { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=userlist\" rel=\"nofollow chapter\">Userlist</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'tags\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=tags\" rel=\"nofollow chapter\">Tags</a></li>\r\n<li\';\r\nif ($GLOBALS[\'step\'] === \'description\') { echo $active_id_html; }\r\necho \'><a href=\"./index.php?event=kuo_glogin&amp;step=description\" rel=\"nofollow chapter\">Description</a></li>\r\n</ul><div id=\"kuo_area\"\';\r\nif ($GLOBALS[\'step\'] === \'tags\') { echo \' class=\"taglist\"\'; }\r\necho \'><h2>\'.KUO_NAME.\', version \'.KUO_VERSION.\'</h2>\';\r\nif (empty($GLOBALS[\'step\'])) {\r\n\r\n$are_prefs = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\n\r\nif ((isset($are_prefs[\'id\'])) && (!empty($are_prefs[\'id\'])) && (is_numeric($are_prefs[\'id\'])) && ($are_prefs[\'id\'] === \'1\')) {\r\n$kuo_status_id = $good_html;\r\n}\r\nelse {\r\n$kuo_status_id = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'login_url\'])) && (!empty($are_prefs[\'login_url\'])) && (filter_var($are_prefs[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_login_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_login_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'user_privs\'])) && (!empty($are_prefs[\'user_privs\'])) or ($are_prefs[\'user_privs\'] === \'0\') or (($are_prefs[\'user_privs\'] <= 6) && ($are_prefs[\'user_privs\'] >= 4))) {\r\n$kuo_status_user_privs = $good_html;\r\n}\r\nelse {\r\n$kuo_status_user_privs = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'logged_in_url\'])) && (!empty($are_prefs[\'logged_in_url\'])) && (filter_var($are_prefs[\'logged_in_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_logged_in_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_logged_in_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'logged_out_url\'])) && (!empty($are_prefs[\'logged_out_url\'])) && (filter_var($are_prefs[\'logged_out_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_status_logged_out_url = $good_html;\r\n}\r\nelse {\r\n$kuo_status_logged_out_url = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'textpattern_folder\'])) && (!empty($are_prefs[\'textpattern_folder\'])) && (file_exists(\'../\'.$are_prefs[\'textpattern_folder\'].\'/\'))) {\r\n$kuo_status_textpattern_folder = $good_html;\r\n}\r\nelse {\r\n$kuo_status_textpattern_folder = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'site_path\'])) && (!empty($are_prefs[\'site_path\'])) && (strpos($are_prefs[\'site_path\'],\'/\'))) {\r\n$kuo_status_site_path = $good_html;\r\n}\r\nelse {\r\n$kuo_status_site_path = $broken_html;\r\n}\r\n\r\n# IS SETTINGS TABLE THERE?\r\n$status_table1 = safe_query(\"SELECT * FROM \".safe_pfx(\'kuo_glogin_prefs\'));\r\nif ($status_table1) {\r\n$kuo_status_prefs_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_prefs_table = $broken_html;\r\n}\r\n\r\n# IS TXP_USERS UPDATED TO INCLUDE NEW DATA?\r\n$status_table2 = safe_query(\"SELECT `gplus_avatar`, `gplus_url`, `gplus_id` FROM \".safe_pfx(\'txp_users\'));\r\nif ($status_table2) {\r\n$kuo_status_users_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_users_table = $broken_html;\r\n}\r\n\r\n# IS BANLIST TABLE THERE?\r\n$status_table3 = safe_query(\"SELECT * FROM \".safe_pfx(\'kuo_glogin_banned\'));\r\nif ($status_table3) {\r\n$kuo_status_banned_table = $good_html;\r\n}\r\nelse {\r\n$kuo_status_banned_table = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'client_id\'])) && (!empty($are_prefs[\'client_id\'])) && (strpos($are_prefs[\'client_id\'],\'google\'))) {\r\n$kuo_client_id = $good_html;\r\n}\r\nelse {\r\n$kuo_client_id = $broken_html;\r\n}\r\n\r\nif ((isset($are_prefs[\'client_secret\'])) && (!empty($are_prefs[\'client_secret\'])) && (strlen($are_prefs[\'client_secret\']) <= 255) && (strlen($are_prefs[\'client_secret\']) >= 5)) {\r\n$kuo_client_secret = $good_html;\r\n}\r\nelse {\r\n$kuo_client_secret = $broken_html;\r\n}\r\n\r\necho \'<h3>Overview</h3>\r\n<dl>\r\n<dt>System status</dt>\r\n<dd><ul>\r\n<li>API &#9658; Client ID: \'.$kuo_client_id.\'</li>\r\n<li>API &#9658; Client Secret: \'.$kuo_client_secret.\'</li>\r\n<li>DB &#9658; kuo_glogin_settings \'.$kuo_status_prefs_table.\'</li>\r\n<li>DB &#9658; txp_users \'.$kuo_status_users_table.\'</li>\r\n<li>DB &#9658; kuo_glogin_banned \'.$kuo_status_banned_table.\'</li>\r\n<li>Preferences &#9658; ID row \'.$kuo_status_id.\'</li>\r\n<li>Login URL \'.$kuo_status_login_url.\'</li>\r\n<li>New user privileges \'.$kuo_status_user_privs.\'</li>\r\n<li>Logged in URL \'.$kuo_status_logged_in_url.\'</li>\r\n<li>Logged out URL \'.$kuo_status_logged_out_url.\'</li>\r\n<li>Textpattern folder \'.$kuo_status_textpattern_folder.\'</li>\r\n<li>Site path \'.$kuo_status_site_path.\'</li>\r\n</ul></dd>\r\n</dl>\r\n\r\n\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'settings\') {\r\n\r\n# SAVE BUTTON CLICKED\r\nif ((isset($_POST[\'save\'])) && (empty($_POST[\'save\']))) {\r\n\r\n# CHECKING CLIENT ID\r\nif ((isset($_POST[\'kuo_api_client_id\'])) && (!empty($_POST[\'kuo_api_client_id\'])) && (strlen($_POST[\'kuo_api_client_id\']) <= 255)) {\r\n$kuo_api_client_id = filter_var($_POST[\'kuo_api_client_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_api_client_id = NULL;\r\n}\r\n\r\n# CHECKING CLIENT SECRET\r\nif ((isset($_POST[\'kuo_api_client_secret\'])) && (!empty($_POST[\'kuo_api_client_secret\'])) && (strlen($_POST[\'kuo_api_client_secret\']) <= 255)) {\r\n$kuo_api_client_secret = filter_var($_POST[\'kuo_api_client_secret\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_api_client_secret = NULL;\r\n}\r\n\r\n# CHECKING LOGIN URL\r\nif ((isset($_POST[\'kuo_login_url\'])) && (!empty($_POST[\'kuo_login_url\'])) && (strlen($_POST[\'kuo_login_url\']) <= 255)) {\r\n$kuo_login_url = filter_var($_POST[\'kuo_login_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n}\r\n\r\n# CHECKING DEFAULT USER PRIVS\r\nif (((isset($_POST[\'kuo_user_privs\'])) && (!empty($_POST[\'kuo_user_privs\']))) or ($_POST[\'kuo_user_privs\'] === \'0\') && (strlen($_POST[\'kuo_user_privs\']) == 1) && (in_array($_POST[\'kuo_user_privs\'],array(\'0\',\'6\',\'5\',\'4\')))) {\r\n$kuo_user_privs = filter_var($_POST[\'kuo_user_privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_privs = NULL;\r\n}\r\n\r\n# CHECKING LOGGED IN URL\r\nif ((isset($_POST[\'kuo_logged_in_url\'])) && (!empty($_POST[\'kuo_logged_in_url\'])) && (strlen($_POST[\'kuo_logged_in_url\']) <= 255)) {\r\n$kuo_logged_in_url = filter_var($_POST[\'kuo_logged_in_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_in_url = NULL;\r\n}\r\n\r\n# CHECKING LOGGED OUT URL\r\nif ((isset($_POST[\'kuo_logged_out_url\'])) && (!empty($_POST[\'kuo_logged_out_url\'])) && (strlen($_POST[\'kuo_logged_out_url\']) <= 255)) {\r\n$kuo_logged_out_url = filter_var($_POST[\'kuo_logged_out_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_out_url = NULL;\r\n}\r\n\r\n# CHECKING TEXTPATTERN FOLDER\r\nif ((isset($_POST[\'kuo_textpattern_folder\'])) && (!empty($_POST[\'kuo_textpattern_folder\'])) && (strlen($_POST[\'kuo_textpattern_folder\']) <= 255)) {\r\n$kuo_textpattern_folder = filter_var($_POST[\'kuo_textpattern_folder\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_textpattern_folder = NULL;\r\n}\r\n\r\n# CHECKING SITE PATH\r\nif ((isset($_POST[\'kuo_site_path\'])) && (!empty($_POST[\'kuo_site_path\'])) && (strlen($_POST[\'kuo_site_path\']) <= 255)) {\r\n$kuo_site_path = filter_var($_POST[\'kuo_site_path\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_site_path = NULL;\r\n}\r\n\r\n# AND FINALLY SAVING DATA\r\nif ($kuo_user_privs != NULL) {\r\n\r\n# SOME SALT TO CLIENT ID AND CLIENT SECRECT\r\nif ($kuo_api_client_id != NULL) {\r\n$kuo_api_client_id_db = substr(hash(\'sha1\',time()),-6).$kuo_api_client_id;\r\n}\r\nelse {\r\n$kuo_api_client_id_db = NULL;\r\n}\r\nif ($kuo_api_client_secret != NULL) {\r\n$kuo_api_client_secret_db = substr(hash(\'sha1\',time()),-5).$kuo_api_client_secret;\r\n}\r\nelse {\r\n$kuo_api_client_secret_db = NULL;\r\n}\r\n\r\n$saving_prefs = safe_query(\"UPDATE \".safe_pfx(\'kuo_glogin_prefs\').\" SET `login_url` = \'\".$kuo_login_url.\"\', `user_privs` = \'\".$kuo_user_privs.\"\', `logged_in_url` = \'\".$kuo_logged_in_url.\"\', `logged_out_url` = \'\".$kuo_logged_out_url.\"\', `textpattern_folder` = \'\".$kuo_textpattern_folder.\"\', `site_path` = \'\".$kuo_site_path.\"\', `client_id` = \'\".$kuo_api_client_id_db.\"\', `client_secret` = \'\".$kuo_api_client_secret_db.\"\' WHERE id=\'1\'\");\r\n\r\nif ($saving_prefs) {\r\n$message = \'<p class=\"message\">Preferences were saved.</p>\';\r\n}\r\nelse {\r\n$message = \'<p class=\"message\">An error occurred. Preferences were not saved.</p>\';\r\n}\r\nunset($saving_prefs);\r\n\r\n}\r\n\r\n\r\n}\r\nelse {\r\n\r\n# FETCHING VALUES FROM DATABASE\r\n$settings = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\n\r\nif ((isset($settings[\'login_url\'])) && (!empty($settings[\'login_url\']))) {\r\n$kuo_login_url = filter_var($settings[\'login_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'user_privs\'])) && (!empty($settings[\'user_privs\'])) or ($settings[\'user_privs\'] === \'0\')) {\r\n$kuo_user_privs = filter_var($settings[\'user_privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_privs = NULL;\r\n}\r\n\r\nif ((isset($settings[\'logged_in_url\'])) && (!empty($settings[\'logged_in_url\']))) {\r\n$kuo_logged_in_url = filter_var($settings[\'logged_in_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_in_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'logged_out_url\'])) && (!empty($settings[\'logged_out_url\']))) {\r\n$kuo_logged_out_url = filter_var($settings[\'logged_out_url\'],FILTER_SANITIZE_URL);\r\n}\r\nelse {\r\n$kuo_logged_out_url = NULL;\r\n}\r\n\r\nif ((isset($settings[\'textpattern_folder\'])) && (!empty($settings[\'textpattern_folder\']))) {\r\n$kuo_textpattern_folder = filter_var($settings[\'textpattern_folder\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_textpattern_folder = NULL;\r\n}\r\n\r\nif ((isset($settings[\'site_path\'])) && (!empty($settings[\'site_path\']))) {\r\n$kuo_site_path = filter_var($settings[\'site_path\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_site_path = NULL;\r\n}\r\n\r\nif ((isset($settings[\'client_id\'])) && (!empty($settings[\'client_id\']))) {\r\n$kuo_api_client_id = substr(filter_var($settings[\'client_id\'],FILTER_SANITIZE_STRING),6);\r\n}\r\nelse {\r\n$kuo_api_client_id = NULL;\r\n}\r\n\r\nif ((isset($settings[\'client_secret\'])) && (!empty($settings[\'client_secret\']))) {\r\n$kuo_api_client_secret = substr(filter_var($settings[\'client_secret\'],FILTER_SANITIZE_STRING),5);\r\n}\r\nelse {\r\n$kuo_api_client_secret = NULL;\r\n}\r\n\r\n$message = NULL;\r\n\r\n}\r\n\r\necho \'<h3>Settings</h3>\r\n\'.$message.\'\r\n<p>Please make sure these settings are correct.</p>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=settings\" method=\"post\">\r\n<fieldset>\r\n<legend>General settings</legend>\r\n<ul>\r\n<li><label for=\"kuo_api_client_id\">Google API Client ID:</label><input type=\"text\" id=\"kuo_api_client_id\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_api_client_id.\'\" name=\"kuo_api_client_id\" /><span>This is the Client ID, a code, from Google API.</span></li>\r\n<li><label for=\"kuo_api_secret\">Google API Client Secret:</label><input type=\"text\" id=\"kuo_api_client_secret\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_api_client_secret.\'\" name=\"kuo_api_client_secret\" /><span>This is the Client Secret, another code, from Google API.</span></li>\r\n<li><label for=\"kuo_login_url\">URL for <code>kuo_glogin</code>:</label><input type=\"text\" id=\"kuo_login_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_login_url.\'\" name=\"kuo_login_url\" /><span>Full URL address for the plugin´s <code>kuo_glogin</code> folder, including the <code>index.php</code> file name. For example: <code>http://www.mysite.com/kuo_glogin/index.php</code></span></li>\r\n<li><label for=\"kuo_user_privs\">Privileges for new users who log in with Google:</label><select name=\"kuo_user_privs\" id=\"kuo_user_privs\">\r\n<optgroup label=\"Default\">\r\n<option value=\"0\"\';\r\nif ($kuo_user_privs === \'0\') { echo $kuo_selected_html; }\r\necho \'>0 = User</option>\r\n</optgroup>\r\n<optgroup label=\"Other\">\r\n<option value=\"6\"\';\r\nif ($kuo_user_privs === \'6\') { echo $kuo_selected_html; }\r\necho \'>6 = Designer</option>\r\n<option value=\"5\"\';\r\nif ($kuo_user_privs === \'5\') { echo $kuo_selected_html; }\r\necho \'>5 = Freelancer</option>\r\n<option value=\"4\"\';\r\nif ($kuo_user_privs === \'4\') { echo $kuo_selected_html; }\r\necho \'>4 = Staff Writer</option>\r\n</optgroup>\r\n</select><span>Default is <code>0</code>, which means user is registered, but not allowed to publish or edit anything, or to even see any tabs here at the Textpattern´s admin panel.</span></li>\r\n<li><label for=\"kuo_logged_in_url\">URL where user is taken after logging <strong>in</strong>:</label><input type=\"text\" id=\"kuo_logged_in_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_logged_in_url.\'\" name=\"kuo_logged_in_url\" /><span>Full URL address, please. This is the page user is relocated after logging in with Google account. For example: <code>http://www.mysite.com/welcome/</code>.</span></li>\r\n<li><label for=\"kuo_logged_out_url\">URL where user is taken after logging <strong>out</strong>:</label><input type=\"text\" id=\"kuo_logged_out_url\" maxlength=\"255\" size=\"20\" value=\"\'.$kuo_logged_out_url.\'\" name=\"kuo_logged_out_url\" /><span>Full URL address, please. This is the page user is relocated after logging out from the site (inc. Textpattern). For example: <code>http://www.mysite.com/logged-out/</code></span></li>\r\n<li><label for=\"kuo_textpattern_folder\">The name of your website´s Textpattern folder:</label><input type=\"text\" id=\"kuo_textpattern_Folder\" maxlength=\"255\" size=\"15\" value=\"\'.$kuo_textpattern_folder.\'\" name=\"kuo_textpattern_folder\" /><span>Usually it´s <code>textpattern</code>. Please no trailing slashes to this field.</span></li>\r\n<li><label for=\"kuo_site_path\">The path where your website is located:</label><input type=\"text\" id=\"kuo_site_path\" maxlength=\"255\" size=\"25\" value=\"\'.$kuo_site_path.\'\" name=\"kuo_site_path\" /><span>If your website is located at the root of your domain, then put just trailing slash, like this: <code>/</code>. But if your Textpattern site is located in some subfolder, then put the name of the folder, and a trailing slash at the end. Like this: <code>newsite/</code></li>\r\n<li><button type=\"submit\" name=\"save\">Save</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'banlist\') {\r\n\r\n# IF USER WANTS TO LIFT BAN\r\nif ((isset($_POST[\'delete\'])) && (!empty($_POST[\'delete\'])) && (is_numeric($_POST[\'delete\']))) {\r\n$ban_to_delete = filter_var($_POST[\'delete\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n# OK THIS FAR, LETS SEE IF USER IS BANNED\r\nif ((isset($ban_to_delete)) && (!empty($ban_to_delete)) && (is_numeric($ban_to_delete))) {\r\n$is_user_banned = safe_row(\'*\',\'kuo_glogin_banned\',\'id=\'.$ban_to_delete);\r\n\r\nif ($is_user_banned == TRUE) {\r\nif (safe_delete(\'kuo_glogin_banned\',\'id = \'.doSlash($ban_to_delete))) {\r\n$ban_message = \'<p class=\"message\">Ban has been lifted.</p>\';\r\nunset($ban_to_delete);\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">Unable to remove ban.</p>\';\r\n}\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">User is not banned.</p>\';\r\n}\r\n\r\n}\r\n\r\n}\r\nelse {\r\n$ban_message = NULL;\r\n}\r\n\r\n# IF USER WANTS TO BAN SOMEONE\r\nif ((isset($_POST[\'ban\'])) && (empty($_POST[\'ban\'])) && (isset($_POST[\'kuo_user_gplus_id\'])) && (!empty($_POST[\'kuo_user_gplus_id\'])) && (isset($_POST[\'kuo_user_gplus_why\']))) {\r\n\r\nif ((strlen($_POST[\'kuo_user_gplus_id\']) <= 255) && (strlen($_POST[\'kuo_user_gplus_id\']) >= 5)) {\r\n$kuo_user_gplus_id = filter_var($_POST[\'kuo_user_gplus_id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_user_gplus_id = NULL;\r\n}\r\n\r\nif (!empty($_POST[\'kuo_user_gplus_why\'])) {\r\n\r\nif (strlen($_POST[\'kuo_user_gplus_why\']) <= 255) {\r\n$kuo_user_gplus_why = filter_var($_POST[\'kuo_user_gplus_why\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\n# ALRIGHT, DATA SEEMS VALID TO BE SAVED? IF SO LETS ADD A NEW BANNED USER\r\nif ($kuo_user_gplus_id != NULL) {\r\n\r\n# BUT FIRST SHOULD SEE IF SAME G+ ALREADY EXISTS\r\n$is_already_banned = safe_row(\'*\',\'kuo_glogin_banned\',\'gplus_id = \'.$kuo_user_gplus_id);\r\nif (!$is_already_banned) {\r\n\r\n$ban_added = safe_query(\"INSERT INTO \".safe_pfx(\'kuo_glogin_banned\').\" (`gplus_id`,`why`,`when`) VALUES (\'\".doSlash($kuo_user_gplus_id).\"\',\'\".doSlash($kuo_user_gplus_why).\"\',NOW())\");\r\n\r\nif ($ban_added) {\r\n$ban_message = \'<p class=\"message\">User was banned.</p>\';\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">An error occurred. User was not banned.</p>\';\r\n}\r\n\r\n}\r\nelse {\r\n$ban_message = \'<p class=\"message\">User is already banned.</p>\';\r\n}\r\n\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_user_gplus_id = NULL;\r\n$kuo_user_gplus_why = NULL;\r\n}\r\n\r\necho \'<h3>Banlist</h3>\r\n\'.$ban_message.\'\r\n<form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th>Number</th>\r\n<th>Google+ ID</th>\r\n<th>When banned</th>\r\n<th>Why banned</th>\r\n<th>&nbsp;</th>\r\n</tr>\r\n</thead>\r\n<tbody>\';\r\n\r\n$banned_users = safe_rows(\"*\",\"kuo_glogin_banned\",\"1 ORDER BY id DESC\");\r\nforeach ($banned_users as $r) {\r\n\r\nif ((isset($r[\'id\'])) && (!empty($r[\'id\']))) {\r\n$kuo_id = filter_var($r[\'id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_id = filter_var($r[\'gplus_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_gplus_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'why\'])) && (!empty($r[\'why\']))) {\r\n$kuo_why = filter_var($r[\'why\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_why = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'when\'])) && (!empty($r[\'when\']))) {\r\n$kuo_when = filter_var($r[\'when\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_when = \'&nbsp;\';\r\n}\r\n\r\necho \'<tr>\r\n<td>\'.$kuo_id.\'</td>\r\n<td>\'.$kuo_gplus_id.\'</td>\r\n<td>\'.$kuo_when.\'</td>\r\n<td>\'.$kuo_why.\'</td>\r\n<td><form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\"><button type=\"submit\" name=\"delete\" value=\"\'.$r[\'id\'].\'\">Remove ban</button></form></td>\r\n</tr>\';\r\n\r\n}\r\n\r\nif (!$banned_users) {\r\necho \'<tr><td colspan=\"5\">No one has been banned.</td></tr>\';\r\n}\r\nelse {\r\necho \'</tbody>\r\n<tfoot>\r\n<tr>\r\n<td colspan=\"5\">Above is the list of currently banned users who have logged in with Google account. Users are banned by their Google+ ID.</td>\r\n</tr>\r\n</tfoot>\';\r\n}\r\n\r\necho \'</table>\r\n</form>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=banlist\" method=\"post\" id=\"kuo_banner\">\r\n<fieldset>\r\n<legend>Ban a user</legend>\r\n<ul>\r\n<li><label for=\"kuo_user_gplus_id\">Google+ ID number:</label><input type=\"text\" size=\"20\" maxlength=\"255\" name=\"kuo_user_gplus_id\" id=\"kuo_user_gplus_id\" value=\"\" /><span>Enter a Google+ ID and click the button below. After that banned user cannot log in to this website with that Google+ ID.</span></li>\r\n<li><label for=\"kuo_user_gplus_why\">[Optional] Why user is banned:</label><input type=\"text\" size=\"20\" maxlength=\"255\" name=\"kuo_user_gplus_why\" id=\"kuo_user_gplus_why\" value=\"\" /><span>Optional description why this user is banned.</span></li>\r\n<li><button type=\"submit\" name=\"ban\">Ban user</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n\r\nunset($kuo_id);\r\nunset($kuo_gplus_id);\r\nunset($kuo_when);\r\nunset($kuo_why);\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'userlist\') {\r\necho \'<h3>Userlist</h3>\r\n<p><a href=\"./index.php?event=kuo_glogin&amp;step=export\" rel=\"nofollow chapter\">Export</a></p>\r\n<form action=\"index.php?event=kuo_glogin&amp;step=userlist\" method=\"post\">\r\n<table>\r\n<thead>\r\n<tr>\r\n<th>Textpattern ID</th>\r\n<th>G+ ID</th>\r\n<th>Login name</th>\r\n<th>Real name</th>\r\n<th>Email</th>\r\n<th>Privileges</th>\r\n<th>Last login</th>\r\n<th>Avatar</th>\r\n<th>G+ URL</th>\r\n<th>G+ login times</th>\r\n</tr>\r\n</thead>\r\n<tbody>\';\r\n# CONSTRUCTING A LIST OF LATEST REGISTERED USERS\r\n$count_users = safe_query(\"SELECT COUNT(1) FROM \".safe_pfx(\'txp_users\'));\r\n$users_in_total = mysql_result($count_users,0,0);\r\n\r\necho \'<tr><td colspan=\"10\">You have \'.$users_in_total.\' users in total.</td></tr>\';\r\n\r\nunset($count_users);\r\nunset($users_in_total);\r\n\r\n$registered_users = safe_rows(\"*\",\"txp_users\",\"1 ORDER BY user_id DESC LIMIT 0, 100\");\r\nforeach ($registered_users as $r) {\r\n\r\nif ((isset($r[\'user_id\'])) && (!empty($r[\'user_id\']))) {\r\n$kuo_id = filter_var($r[\'user_id\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_id = filter_var($r[\'gplus_id\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_gplus_id = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'name\'])) && (!empty($r[\'name\']))) {\r\n$kuo_name = filter_var($r[\'name\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_name = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'RealName\'])) && (!empty($r[\'RealName\']))) {\r\n$kuo_realname = filter_var($r[\'RealName\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_realname = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'email\'])) && (!empty($r[\'email\']))) {\r\n$kuo_email = str_replace(\'@\',\'&#064;\',filter_var($r[\'email\'],FILTER_SANITIZE_EMAIL));\r\n}\r\nelse {\r\n$kuo_email = \'&nbsp;\';\r\n}\r\n\r\nif (((isset($r[\'privs\'])) && (!empty($r[\'privs\']))) or ($r[\'privs\'] === \'0\')) {\r\n$kuo_privs = filter_var($r[\'privs\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_privs = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_url\'])) && (!empty($r[\'gplus_url\']))) {\r\n$kuo_gplus_url = filter_var($r[\'gplus_url\'],FILTER_SANITIZE_URL);\r\n\r\nif (!empty($kuo_gplus_url)) {\r\n$kuo_gplus_url = \'<a href=\"\'.$kuo_gplus_url.\'\" rel=\"nofollow external\">&#9658;</a>\';\r\n}\r\nelse {\r\n$kuo_gplus_url = \'&nbsp;\';\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_gplus_url = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'last_access\'])) && (!empty($r[\'last_access\']))) {\r\n$kuo_last_access = filter_var($r[\'last_access\'],FILTER_SANITIZE_STRING);\r\n}\r\nelse {\r\n$kuo_last_access = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_avatar\'])) && (!empty($r[\'gplus_avatar\']))) {\r\n$kuo_avatar = filter_var($r[\'gplus_avatar\'],FILTER_SANITIZE_URL);\r\nif (!empty($kuo_avatar)) {\r\n$kuo_avatar = \'<img src=\"\'.str_replace(\'=50\',\'=25\',$kuo_avatar).\'\" alt=\"\" height=\"25\" width=\"25\" />\';\r\n}\r\nelse {\r\n$kuo_avatar = \'&nbsp;\';\r\n}\r\n}\r\nelse {\r\n$kuo_avatar = \'&nbsp;\';\r\n}\r\n\r\nif ((isset($r[\'gplus_login_times\'])) && (!empty($r[\'gplus_login_times\'])) && (isset($r[\'gplus_id\'])) && (!empty($r[\'gplus_id\']))) {\r\n$kuo_gplus_login_times = filter_var($r[\'gplus_login_times\'],FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$kuo_gplus_login_times = \'&nbsp;\';\r\n}\r\n\r\necho \'<tr>\r\n<td>\'.$kuo_id.\'</td>\r\n<td>\'.$kuo_gplus_id.\'</td>\r\n<td>\'.$kuo_name.\'</td>\r\n<td>\'.$kuo_realname.\'</td>\r\n<td>\'.$kuo_email.\'</td>\r\n<td><code>\'.$kuo_privs.\'</code></td>\r\n<td>\'.$kuo_last_access.\'</td>\r\n<td>\'.$kuo_avatar.\'</td>\r\n<td>\'.$kuo_gplus_url.\'</td>\r\n<td>\'.$kuo_gplus_login_times.\'</td>\r\n</tr>\';\r\n\r\n}\r\necho \'</tbody><tfoot><tr><td colspan=\"10\">Users are listed with their Textpattern ID so that newest is at the top, oldest at the bottom. The list shows 100 latest users.</td></tr></tfoot></table></form>\';\r\n\r\nunset($kuo_gplus_url);\r\nunset($kuo_avatar);\r\nunset($kuo_privs);\r\nunset($kuo_email);\r\nunset($kuo_realname);\r\nunset($kuo_name);\r\nunset($kuo_gplus_id);\r\nunset($kuo_id);\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'export\') {\r\n\r\n# IF USER HAS CLICKED THE EXPORT BUTTON\r\nif ((isset($_POST[\'export\'])) && (empty($_POST[\'export\']))) {\r\n\r\n$kuo_export_formats = array(\'csv\');\r\n\r\nif ((isset($_POST[\'kuo_export_format\'])) && (!empty($_POST[\'kuo_export_format\'])) && (in_array($_POST[\'kuo_export_format\'],$kuo_export_formats))) {\r\n\r\nif ($_POST[\'kuo_export_format\'] === \'csv\') {\r\n$kuo_export_format = \'csv\';\r\n}\r\nelse {\r\n$kuo_export_format = NULL;\r\n}\r\n\r\nif (isset($_POST[\'kuo_export_name\'])) {\r\n$kuo_export_name = 1;\r\n}\r\nelse {\r\n$kuo_export_name = 0;\r\n}\r\n\r\nif (isset($_POST[\'kuo_export_email\'])) {\r\n$kuo_export_email = 1;\r\n}\r\nelse {\r\n$kuo_export_email = 0;\r\n}\r\n\r\n# STARTING TO EXPORT CSV\r\nif ($kuo_export_format === \'csv\') {\r\n#$exported_users = safe_rows(\"DISTINCT *\",\"txp_users\",\"`name` IS NOT NULL AND `email` IS NOT NULL ORDER BY user_id DESC\",true);\r\n$exported_users = safe_rows(\"DISTINCT *\",\"txp_users\",\"1 ORDER BY user_id DESC\");\r\n\r\nif ($exported_users) {\r\n$fp = fopen($export_file_name.\'.csv\',\'w\');\r\nforeach ($exported_users as $row) {\r\n\r\nif ((isset($row[\'name\'])) && (!empty($row[\'name\'])) && (strlen($row[\'name\']) > 1)) {\r\n$export_name = $row[\'name\'];\r\n}\r\nelse {\r\n$export_name = NULL;\r\n}\r\n\r\nif ((isset($row[\'email\'])) && (!empty($row[\'email\'])) && (strlen($row[\'email\']) > 1)) {\r\n$export_email = $row[\'email\'];\r\n}\r\nelse {\r\n$export_email = NULL;\r\n}\r\n\r\nif (($kuo_export_email == 1) && ($kuo_export_name == 0)) {\r\nfputcsv($fp,array_filter(array($export_email)));\r\n}\r\nelseif (($kuo_export_email == 0) && ($kuo_export_name == 1)) {\r\nfputcsv($fp,array_filter(array($export_name)));\r\n}\r\nelse {\r\nfputcsv($fp,array($export_name,$export_email));\r\n}\r\n\r\n}\r\nfclose($fp);\r\nheader(\'location: \'.$export_file_name.\'.csv\');\r\n}\r\n\r\n}\r\n}\r\n}\r\n\r\necho \'<h3>Export user information</h3>\r\n<form action=\"./index.php?event=kuo_glogin&amp;step=export\" method=\"post\">\r\n<fieldset>\r\n<legend>Options</legend>\r\n<ul>\r\n<li><label for=\"kuo_export_format\">Export format:</label><select name=\"kuo_export_format\" id=\"kuo_export_format\"><option value=\"csv\" selected=\"selected\">CSV</option></select></li>\r\n<li class=\"checkbox\"><input type=\"checkbox\" name=\"kuo_export_name\" id=\"kuo_export_name\" checked=\"checked\" value=\"1\" /><label for=\"kuo_export_name\">Include name</label></li>\r\n<li class=\"checkbox\"><input type=\"checkbox\" name=\"kuo_export_email\" id=\"kuo_export_email\" checked=\"checked\" value=\"1\" /><label for=\"kuo_export_email\">Include email</label></li>\r\n<li><button type=\"submit\" name=\"export\">Export</button></li>\r\n</ul>\r\n</fieldset>\r\n</form>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'description\') {\r\necho \'<h3>Description</h3>\r\n<p class=\"bold and big\">With this plugin users can register an account to Textpattern using their Google account. After registering user is logged in to the <strong>public side of the website</strong>. Currently (v. \'.KUO_VERSION.\') user is unable to enter the admin side straight with Google login.</p>\r\n<dl>\r\n<dt>Installing</dt>\r\n<dd><ol>\r\n<li>Install the Textpattern plugin called <code>kuo_glogin</code></li>\r\n<li>Upload folder called <code>kuo_glogin</code> to the root of your Textpattern website, like: <code>http://www.mysite.com/kuo_glogin/</code></li>\r\n<li>Start grinding through the steps listed below</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>Google Developers Console</dt>\r\n<dd><p>To use this plugin you should now login with your Google account to Google Developers Console, and then do the following:</p>\r\n<ol>\r\n<li>Click <strong>Projects</strong></li>\r\n<li>Click <strong>APIs</strong></li>\r\n<li>Click the <strong>Create Project</strong> button</li>\r\n<li>Give your project an informative name</li>\r\n<li>Let <strong>Project ID</strong> be whatever it is, or create something you like</li>\r\n<li>Click the <strong>Create</strong> button</li>\r\n<li>Wait for the project to load</li>\r\n<li>Check that <strong>Google+ API</strong> is set to <strong>On</strong></li>\r\n<li>When your project is processed, click it´s name</li>\r\n<li>Click then <strong>Credentials</strong> from the left side</li>\r\n<li>Click the <strong>Create new Client ID</strong> button under OAuth</li>\r\n<li>Select <strong>Web application</strong></li>\r\n<li>Either leave <strong>Authorized JavaScript Origins</strong> field empty or put your website´s URL there</li>\r\n<li><em>Important:</em> Put to <strong>Authorized Redirect URI</strong> your website´s URL, and include <code>kuo_glogin/index.php</code> at the end, like this: <code>http://www.mysite.com/kuo_glogin/index.php</code></li>\r\n<li>Click the <strong>Create Client ID</strong> button</li>\r\n<li>Copy your project´s <strong>Client ID</strong> and <strong>Client Secret</strong> and paste them to this plugin´s Settings page</li>\r\n<li>Done!</li>\r\n</ol>\r\n<p class=\"blue\">If you have created a project with Google API, installed and activated this plugin and uploaded the folder <code>kuo_glogin</code> to your website´s root, then you should have everything ready. Remember that phase 14 is most likely the one that´s causing problems. It should have exactly the correct URL. And if the plugin still doesn´t work, then go back to phase 13 and just leave the field empty at Google API.</p>\r\n</dd>\r\n</dl>\r\n<dl>\r\n<dt>General notes</dt>\r\n<dd><p>It´s important to remember that if this plugin is correctly installed, it means that anyone from anywhere can log in to your website with his Google account. That´s why by default this plugin grants level <code>0</code> for users. That´s the minimum level, which means user has been logged in and registered to Textpattern´s database &mdash; but he can´t basically do anything. User´s information is saved inside database. Immediately after logging in user can access the public and admin side of Textpattern website just like any regular Textpattern user.</p>\r\n<ol class=\"kuo_box\">\r\n<li>This plugin was tested with Textpattern 4.5.5.</li>\r\n<li>This control panel is currently available only in English.</li>\r\n<li>Use <code>kuo_glogin</code> with your own risk.</li>\r\n<li>To use this plugin website should have a Client ID and Client Secret from Google API.</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>When user logs in</dt>\r\n<dd>Logging in means user´s information is saved to the Textpattern database, to the <code>txp_users</code> table. The script fetches information from user´s Google account and places that inside <code>txp_users</code>. If user is a returning Google account user, some previously created information from the database will be used. The script tries to prevent duplicate accounts from being created.</dd>\r\n</dl>\r\n<dl>\r\n<dt>Original Textpattern users</dt>\r\n<dd>User accounts which are created with Textpattern will not be updated with Google account´s information if they should log in to the site with their Google account. That´s because inside <code>txp_users</code> those original Textpattern users do not have a field called <code>gplus_id</code>, which is used as an identifier for those users who log in with their Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt>Modifying &quot;Google account&quot; user´s privileges</dt>\r\n<dd>What about if someone who logs in with Google account should have wider privileges? In that case someone with Publisher privileges could modify that user´s privileges by going to Admin &rarr; Users.</dd>\r\n</dl>\r\n<dl>\r\n<dt>What information is fetched from Google account?</dt>\r\n<dd><ol>\r\n<li>Google+ ID number</li>\r\n<li>First name and last name</li>\r\n<li>Email address</li>\r\n<li>Avatar URL</li>\r\n<li>Google+ profile URL</li>\r\n<li>Google+ public name, &quot;nick&quot;</li>\r\n</ol></dd>\r\n</dl>\r\n<dl>\r\n<dt>What´s the login URL?</dt>\r\n<dd>It´s something like this: <code>http://www.mysite.com/kuo_glogin/</code></dd>\r\n</dl>\r\n<dl>\r\n<dt>What´s the logout URL?</dt>\r\n<dd>It´s something like this: <code>http://www.mysite.com/kuo_glogin/?logout=1</code></dd>\r\n</dl>\r\n<dl>\r\n<dt>Can I change those two URL`s?</dt>\r\n<dd>Yes by modifying your Textpattern website´s root <code>.htaccess</code> file. Inside that file there should be a line of code which says: <code>RewriteEngine On</code>. Put right to the next line these codes:<br /><br />\r\n<code>RewriteRule ^login/$ ./kuo_glogin/index.php</code><br />\r\n<code>RewriteRule ^logout/$ ./kuo_glogin/?logout=1</code><br /><br />\r\nAfter that you can log in with <code>http://www.mysite.com/login/</code> and log out with <code>http://www.mysite.com/logout/</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt>What happens if I change the default user privilege?</dt>\r\n<dd>Default privilege is not updated to existing users, only to those new users who log in with their Google account.</p>\r\n</dl>\r\n<dl>\r\n<dt>Not working!</dt>\r\n<dd>Check your website´s <code>kuo_glogin</code> folder and see if there´s a file called <code>error_log</code>. If you find that file, open it and examine it´s contents. If you find error with code <code>(403) Access Not Configured.</code>, it means you haven´t enabled Google+ API from the Google Developers Console.</dd>\r\n</dl>\r\n<hr />\r\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_top\" id=\"kuo_donate\">\r\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />\r\n<input type=\"hidden\" name=\"hosted_button_id\" value=\"7YNFHD5HLUJT2\" />\r\n<input type=\"image\" src=\"https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif\" border=\"0\" name=\"submit\" alt=\"Donate with PayPal\" />\r\n</form>\r\n<p>\'.KUO_NAME.\' by <a href=\"http://kuopassa.net/txp/\" rel=\"nofollow external\">Petri Ikonen</a> with special thanks to <a href=\"https://github.com/google/google-api-php-client\" rel=\"nofollow external\">Google APIs Client Library for PHP</a> and everyone at the Textpattern CMS Support Forum.</p>\';\r\n}\r\nelseif ($GLOBALS[\'step\'] === \'tags\') {\r\necho \'<h3>Tags</h3>\r\n<p>These are public side tags. They can be placed inside forms and pages.</p>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_is_logged&gt;You are logged in.&lt;txp:else /&gt;You are not logged in.&lt;/txp:kuo_glogin_is_logged&gt;</code></dt>\r\n<dd>A conditional tag. Allows content to be targeted for users who´ve logged in and to those who are not.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_link /&gt;</code></dt>\r\n<dd>This tag creates a HTML tag, a normal link. Link´s <code>href</code> for logged in users is the logout URL, and for users who have not been logged in <code>href</code> is login URL. Link´s anchor text is localized automatically so that it´s fetched from Textpattern´s language preferences. So if your website is in English, the link says either <code>Login</code> or <code>Log out</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_link text=\"Log in by clicking this|Log out by clicking this\" /&gt;</code></dt>\r\n<dd>The tag can also have your own link text. It´s created so that pipe mark <code>|</code> separates the values. Left side value is for cases when user hasn´t logged in, right side is for user´s who are logged in.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_name /&gt;</code></dt>\r\n<dd>Outputs user´s login name. If user has been registered to the site via Google, login name might have a number at the end of it. That´s because each login name must be unique, so this plugin creates a unique incremental number and puts it at the end of the login name.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_real_name /&gt;</code></dt>\r\n<dd>This tag shows user´s full name. If it´s fetched from Google, it should be something like <code>Firstname Lastname</code>. If user logs in with Google account, his name is updated automatically to be the same what he´s using in his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_email /&gt;</code></dt>\r\n<dd>Outputs user´s currently used email address. If user logs in with Google account, his email address is updated to be the same what he´s using in his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_privs /&gt;</code></dt>\r\n<dd>This one simply outputs a numerical value. The number indicates what privileges user has. <code>0</code> = least privileges, <code>1</code> = Publisher.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_last_access /&gt;</code></dt>\r\n<dd>Date and time when user logged in last time. Not formatted in any way, so the style looks like: <code>yyyy-mm-dd hh:mm:ss</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_avatar /&gt;</code></dt>\r\n<dd>Creates an <code>img</code> HTML tag and shows by default 50&times;50 pixel size avatar from user´s Google account. If user hasn´t got an avatar, nothing is shown.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_avatar size=&quot;25&quot; /&gt;</code></dt>\r\n<dd>Same tag as above, but this one resizes the avatar´s height and width to the value set in <code>size</code>.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_url /&gt;</code></dt>\r\n<dd>Shows only URL for user´s Google+ account. This URL is updated each time from Google if user logs in with his Google account.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_id /&gt;</code></dt>\r\n<dd>Shows only Google+ ID for user´s Google+ account. It should be a numerical value. It´s found also from the Google+ profile´s URL address.</dd>\r\n</dl>\r\n<dl>\r\n<dt><code>&lt;txp:kuo_glogin_gplus_login_times /&gt;</code></dt>\r\n<dd>This long tag shows how many times user has logged in with his Google account. It <strong>does not</strong> count the times user has logged in the normal way.</dd>\r\n</dl>\';\r\n}\r\necho \'</div><p id=\"kuo_note\">(This section is visible only to those users who have Publisher privileges.)</p>\';\r\n\r\n# SHOULD DELETE THE EXPORT FILE AFTER ONE MINUTE\r\nif (file_exists($export_file_name.\'.csv\')) {\r\nif (filemtime($export_file_name.\'.csv\') <= strtotime(\'-1 minute\')) {\r\nunlink($export_file_name.\'.csv\');\r\n}\r\n}\r\n\r\n}\r\n\r\n}\r\n}\r\n\r\nif (@txpinterface == \'public\') {\r\n\r\n# TAG KUO_GLOGIN_LINK\r\nfunction kuo_glogin_link($atts,$thing=NULL) {\r\n# FOR TESTING\r\n#var_dump(is_logged_in());\r\n\r\nextract(lAtts(array(\'text\'=>\'Login|Logout\',),$atts));\r\n\r\nif ((isset($text)) && (!empty($text))) {\r\n$text = explode(\'|\',$text);\r\n$kuo_login_text = $text[0];\r\n$kuo_logout_text = $text[1];\r\n}\r\nelse {\r\n$kuo_login_text = gTxt(\'login\');\r\n$kuo_logout_text = gTxt(\'logout\');\r\n}\r\n\r\n$kuo_prefs = safe_row(\'*\',\'kuo_glogin_prefs\',\'id=1\');\r\nif ($kuo_prefs) {\r\n\r\nif ((isset($kuo_prefs[\'login_url\'])) && (!empty($kuo_prefs[\'login_url\'])) && (filter_var($kuo_prefs[\'login_url\'],FILTER_VALIDATE_URL))) {\r\n$kuo_login_url = $kuo_prefs[\'login_url\'];\r\n$kuo_logout_url = $kuo_login_url.\'?logout=1\';\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n$kuo_logout_url = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$kuo_login_url = NULL;\r\n$kuo_logout_url = NULL;\r\n}\r\n\r\nif ((is_logged_in() === false) && ($kuo_login_url != NULL) && ($kuo_logout_url != NULL)) {\r\nreturn \'<a href=\"\'.$kuo_login_url.\'\" rel=\"chapter\">\'.$kuo_login_text.\'</a>\';\r\n}\r\nelse {\r\nreturn \'<a href=\"\'.$kuo_logout_url.\'\" rel=\"nofollow\">\'.$kuo_logout_text.\'</a>\';\r\n}\r\n\r\n}\r\n\r\n# TAG KUO_GLOGIN_NAME\r\nfunction kuo_glogin_name($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_name = is_logged_in();\r\nreturn htmlspecialchars($kuo_name[\'name\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_REALNAME\r\nfunction kuo_glogin_real_name($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'RealName\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_realname = is_logged_in();\r\nreturn htmlspecialchars($kuo_realname[\'RealName\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_EMAIL\r\nfunction kuo_glogin_email($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'email\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_email = is_logged_in();\r\nreturn htmlspecialchars($kuo_email[\'email\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_PRIVS\r\nfunction kuo_glogin_privs($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'privs\'),$atts));\r\nif (is_logged_in() !== false) {\r\n$kuo_privs = is_logged_in();\r\nreturn htmlspecialchars($kuo_privs[\'privs\']);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_LASTACCESS\r\nfunction kuo_glogin_last_access($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `last_access` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_AVATAR\r\nfunction kuo_glogin_avatar($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\',\'size\'=>\'50\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif ((!isset($size)) or (empty($size)) or (!is_numeric($size))) {\r\n$size = 50;\r\n}\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_avatar` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn \'<img src=\"\'.htmlspecialchars(str_replace(\'sz=50\',\'sz=\'.$size,$kuo_result)).\'\" alt=\"\" height=\"\'.$size.\'\" width=\"\'.$size.\'\" />\';\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_GPLUS_URL\r\nfunction kuo_glogin_gplus_url($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_url` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_GPLUS_ID\r\nfunction kuo_glogin_gplus_id($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_id` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n# TAG KUO_GLOGIN_IS_LOGGED\r\nfunction kuo_glogin_is_logged($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'privs\'),$atts));\r\n$kuo_privs = is_logged_in();\r\nreturn parse(EvalElse($thing,$kuo_privs));\r\n}\r\n\r\n# TAG KUO_GLOGIN_LOGIN_TIMES\r\nfunction kuo_glogin_gplus_login_times($atts,$thing=NULL) {\r\nextract(lAtts(array(\'type\'=>\'name\'),$atts));\r\n$kuo_name = is_logged_in();\r\n$kuo_name = $kuo_name[\'name\'];\r\nif (is_logged_in() !== false) {\r\n$kuo_result = safe_query(\"SELECT DISTINCT `gplus_login_Times` FROM \".safe_pfx(\'txp_users\').\" WHERE `name` = \'\".doSlash($kuo_name).\"\' LIMIT 0, 1\");\r\n$kuo_result = mysql_result($kuo_result,0,0);\r\nreturn htmlspecialchars($kuo_result);\r\n}\r\n}\r\n\r\n\r\n}', 'fe341831d34eab43ab0db02d34145177', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_image_name', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Replace automatically image ID numbers with file names in public side. Only for full size images.', '<ul id=\"links\">\n<li id=\"kuo_image_name\"><a href=\"#kuo_image_name\">kuo_image_name</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>After installing this plugin numerical image files at the website\'s public side can be accessed using their file names. This system works only with full size images and not with thumbnails. So thumbnails are still something like <code>1t.jpg</code> but full size image will have file name <code>thisisgreat_1.jpg</code>, for example. To make this plugin work properly, couple lines of code should be added to a .htaccess file at the root of your website. They belong just below the <code>RewriteEngine On</code> line.</p>\n<h3>Rewrite rules that should be put inside .htaccess file</h3>\n<p><code>RewriteRule ^images/([0-9]+)\\.(jpe?g|png|gif|ico|bmp)$ ./index.php?kuo_image_id=$1</code></p>\n<p><code>RewriteRule ^images/([\\+\\-a-zA-Z0-9]+)_([0-9]+)\\.(jpe?g|png|gif|ico|bmp)$ ./index.php?kuo_image_id=$2&amp;kuo_image_name=$1</code></p>\n<h3>How is image name formed?</h3>\n<p>It\'s a combination of original file name and incremental number which Textpattern assigns to each image file when image is uploaded. The format of image name is:</p>\n<p><code>this-is-a-text_50.jpg</code></p>\n<p>In that example original file name is <code>this-is-a-test.jpg</code> and the number for image is <code>50</code>. File name and number are separated with an underscore <code>_</code>. It\'s good to note that <strong>this plugin doesn\'t make changes to image\'s file name</strong>, so user should make sure file name doesn\'t contain &quot;illegal&quot; characters like currency signs or spaces. File name can have only letters (<code>a-z</code> and <code>A-Z</code>), numbers (<code>0-9</code>), hyphens (<code>-</code>) and pluses (<code>+</code>), but by modifying the rewrite code in .htaccess it might be possible to allow other characters as well.</p>\n<h3>Notes</h3>\n<ul>\n<li>Plugin works with numerical images that are inside <code>/images/</code> folder.</li>\n<li>Images are served through PHP, but with caching.</li>\n<li>If Web server has <code>mod_xsendfile</code> active, it will be used instead of PHP\'s <code>readfile</code>.</li>\n</ul>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin <code>kuo_image_name</code>.</li>\n<li>Insert to the website\'s root .htaccess file the above mentioned rewrite rules.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin <code>kuo_image_name</code>.</li>\n<li>Remove from the website\'s root .htaccess file rewrite rules for this plugin.\n</ol>\n<p id=\"notice\">The plugin has been tested with Textpattern 4.6-dev, 4.5.7 and 4.5.5.</p>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }.underline { text-decoration: underline; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '# BEGINS: IF PUBLIC\r\nif (@txpinterface === \'public\') {\r\n    register_callback(\'kuo_image_renamer_early\',\'textpattern\');\r\n    register_callback(\'kuo_image_renamer_late\',\'textpattern_end\');\r\n\r\n    # BEGINS: FUNCTION TO COLLECT PAGE HTML\r\n    function kuo_image_renamer_early() {\r\n        ob_start();\r\n    }\r\n    # ENDS: FUNCTION TO COLLECT PAGE HTML\r\n\r\n    # BEGINS: FUNCTION TO OUTPUT COLLECTED PAGE HTML\r\n    function kuo_image_renamer_late() {\r\n        $kuo_output = ob_get_clean();\r\n\r\n        # TRY TO LOCATE IMAGE ID\'S IN HTML\r\n        if (strpos($kuo_output,\'/images/\') == true) {\r\n\r\n            preg_match_all(\'/([0-9]+\\.(jpg|jpeg|png|ico|gif|bmp))/i\',$kuo_output,$kuo_image_ids);\r\n\r\n            if (!empty($kuo_image_ids)) {\r\n\r\n                # THIS IS FOR THE ID\'S IN DATABASE QUERY\r\n                $kuo_image_id_array = array();\r\n\r\n                # THIS IS FOR THE HTML MARKERS IN STR_REPLACE\r\n                $kuo_image_id_array_html_marker = array();\r\n\r\n                foreach ($kuo_image_ids[0] as $kuo_image_id) {\r\n                    $kuo_image_id_array[] .= filter_var($kuo_image_id,FILTER_SANITIZE_NUMBER_INT);\r\n                    $kuo_image_id_array_html_marker[] .= \'/\'.$kuo_image_id;\r\n                }\r\n                # FOREACH ENDS\r\n\r\n                unset($kuo_image_ids);\r\n\r\n                # REMOVING DUPLICATES\r\n                $kuo_image_id_array = array_unique($kuo_image_id_array);\r\n                $kuo_image_id_array_html_marker = array_unique($kuo_image_id_array_html_marker);\r\n\r\n                # FETCHING IMAGE NAMES FROM DB\r\n                if (!empty($kuo_image_id_array)) {\r\n                    if (count($kuo_image_id_array) == 1) {\r\n                        $kuo_image_names = safe_row(\"`name`, `id`, `ext`\",\"txp_image\",\"`id` = \'\".$kuo_image_id_array[0].\"\'\",0);\r\n                        $kuo_file_names = \'/\'.substr($kuo_image_names[\'name\'],0,strrpos($kuo_image_names[\'name\'],\'.\')).\'_\'.$kuo_image_names[\'id\'].$kuo_image_names[\'ext\'];\r\n                    }\r\n                    else {\r\n                        $kuo_image_names = safe_query(\"SELECT DISTINCT `name`, `id`, `ext` FROM `\".safe_pfx(\'txp_image\').\"` WHERE `id` IN (\".implode(\',\',$kuo_image_id_array).\")\",0);\r\n                        $kuo_file_names = array();\r\n                        foreach ($kuo_image_names as $kuo_name) {\r\n                            $kuo_file_names[] .= \'/\'.substr($kuo_name[\'name\'],0,strrpos($kuo_name[\'name\'],\'.\')).\'_\'.$kuo_name[\'id\'].$kuo_name[\'ext\'];\r\n                        }\r\n                        # FOREACH ENDS\r\n                    }\r\n\r\n                    # BEGINS: HTML IS MODIFIED WITH SEARCH AND REPLACE\r\n                    $kuo_output = str_replace($kuo_image_id_array_html_marker,$kuo_file_names,$kuo_output);\r\n                    # ENDS: HTML IS MODIFIED WITH SEARCH AND REPLACE\r\n\r\n                    unset($kuo_image_ids);\r\n                    unset($kuo_file_names);\r\n                    unset($kuo_image_id_array);\r\n                    unset($kuo_image_id_array_html_marker);\r\n                }\r\n                # IF ID ARRAY NOT EMPTY ENDS\r\n            }\r\n            # IF ENDS\r\n        }\r\n        # ELSE ENDS\r\n\r\n        echo $kuo_output;\r\n        die;\r\n    }\r\n    # ENDS: FUNCTION TO OUTPUT COLLECTED PAGE HTML\r\n\r\n    # BEGINS: SETTINGS ARE SET IF IMAGE ID IS AVAILABLE\r\n    if ((isset($_GET[\'kuo_image_id\'])) && (!empty($_GET[\'kuo_image_id\']))) {\r\n        define(\'KUO_IMAGE_ID\',filter_var($_GET[\'kuo_image_id\'],FILTER_SANITIZE_NUMBER_INT));\r\n\r\n        $kuo_image_info = safe_row(\"`name`\",\"txp_image\",\"`id` = \'\".KUO_IMAGE_ID.\"\' LIMIT 0, 1\",0);\r\n\r\n        define(\'KUO_IMAGE_EXTENSION\',pathinfo($kuo_image_info[\'name\'],PATHINFO_EXTENSION));\r\n        define(\'KUO_IMAGE_NAME\',substr($kuo_image_info[\'name\'],0,strrpos($kuo_image_info[\'name\'],\'.\')));\r\n        define(\'KUO_IMAGE_PATH\',\'./images/\'.KUO_IMAGE_ID.\'.\'.KUO_IMAGE_EXTENSION);\r\n\r\n        unset($kuo_image_info);\r\n    }\r\n    # ENDS: SETTINGS ARE SET IF IMAGE ID IS AVAILABLE\r\n\r\n    # BEGINS: FETCHING IMAGE FILE FROM DISK\r\n    if ((defined(\'KUO_IMAGE_ID\')) && (isset($_GET[\'kuo_image_name\']))) {\r\n        if ((file_exists(KUO_IMAGE_PATH)) && (is_readable(KUO_IMAGE_PATH))) {\r\n\r\n            header(\'Content-Length: \'.filesize(KUO_IMAGE_PATH));\r\n            header(\'Last-Modified: \'.date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)));\r\n            header(\'ETag: \"\'.md5(date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)).KUO_IMAGE_NAME).\'\"\');\r\n            header(\'Cache-Control: public\');\r\n            header(\'Content-Disposition: filename=\'.KUO_IMAGE_NAME);\r\n            header(\'Content-Type: \'.mime_content_type(KUO_IMAGE_PATH));\r\n\r\n            if (((isset($_SERVER[\'HTTP_IF_MODIFIED_SINCE\'])) && (date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)) === $_SERVER[\'HTTP_IF_MODIFIED_SINCE\']))\r\n            or ((isset($_SERVER[\'HTTP_IF_NONE_MATCH\'])) && trim($_SERVER[\'HTTP_IF_NONE_MATCH\']) === md5(date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)).KUO_IMAGE_NAME))) {\r\n                header(\'HTTP/1.1 304 Not Modified\');\r\n            }\r\n            # IF MODIFIED ENDS\r\n\r\n            # WILL ATTEMPT TO USE X-SENDFILE FIRST\r\n            if ((function_exists(\'apache_get_modules\')) && (in_array(\'mod_xsendfile\',apache_get_modules()))) {\r\n\r\n                # X-SENDFILE REQUIRES ABSOLUTE PATH\r\n                $kuo_path = safe_row(\"`val`\",\"txp_prefs\",\"`name` = \'path_to_site\'\",0);\r\n                $kuo_path = filter_var($kuo_path[\'val\'],FILTER_SANITIZE_STRING);\r\n\r\n                header(\'X-Sendfile: \'.$kuo_path.str_replace(\'./\',\'/\',KUO_IMAGE_PATH));\r\n            }\r\n            # IF X-SENDFILE NOT AVAILABLE, USE READFILE INSTEAD\r\n            else {\r\n                readfile(KUO_IMAGE_PATH);\r\n            }\r\n        }\r\n        # IF FILE NOT THERE\r\n        else {\r\n            header(\'HTTP/1.0 404 Not Found\');\r\n        }\r\n        die;\r\n    }\r\n    # ENDS: FETCHING IMAGE FILE FROM DISK\r\n    # BEGINS: ROUTING IMAGE FILE THAT HAS ONLY ID TO A KEYWORDED URL\r\n    elseif ((defined(\'KUO_IMAGE_ID\')) && (!isset($_GET[\'kuo_image_name\']))) {\r\n        if (!empty(KUO_IMAGE_NAME)) {\r\n            header(\'location: \'.KUO_IMAGE_NAME.\'_\'.KUO_IMAGE_ID.\'.\'.KUO_IMAGE_EXTENSION);\r\n        }\r\n        else {\r\n            header(\'HTTP/1.0 404 Not Found\');\r\n        }\r\n        die;\r\n    }\r\n    # ENDS: ROUTING IMAGE FILE THAT HAS ONLY ID TO A KEYWORDED URL\r\n}\r\n# ENDS: IF PUBLIC\r\n', '# BEGINS: IF PUBLIC\r\nif (@txpinterface === \'public\') {\r\n    register_callback(\'kuo_image_renamer_early\',\'textpattern\');\r\n    register_callback(\'kuo_image_renamer_late\',\'textpattern_end\');\r\n\r\n    # BEGINS: FUNCTION TO COLLECT PAGE HTML\r\n    function kuo_image_renamer_early() {\r\n        ob_start();\r\n    }\r\n    # ENDS: FUNCTION TO COLLECT PAGE HTML\r\n\r\n    # BEGINS: FUNCTION TO OUTPUT COLLECTED PAGE HTML\r\n    function kuo_image_renamer_late() {\r\n        $kuo_output = ob_get_clean();\r\n\r\n        # TRY TO LOCATE IMAGE ID\'S IN HTML\r\n        if (strpos($kuo_output,\'/images/\') == true) {\r\n\r\n            preg_match_all(\'/([0-9]+\\.(jpg|jpeg|png|ico|gif|bmp))/i\',$kuo_output,$kuo_image_ids);\r\n\r\n            if (!empty($kuo_image_ids)) {\r\n\r\n                # THIS IS FOR THE ID\'S IN DATABASE QUERY\r\n                $kuo_image_id_array = array();\r\n\r\n                # THIS IS FOR THE HTML MARKERS IN STR_REPLACE\r\n                $kuo_image_id_array_html_marker = array();\r\n\r\n                foreach ($kuo_image_ids[0] as $kuo_image_id) {\r\n                    $kuo_image_id_array[] .= filter_var($kuo_image_id,FILTER_SANITIZE_NUMBER_INT);\r\n                    $kuo_image_id_array_html_marker[] .= \'/\'.$kuo_image_id;\r\n                }\r\n                # FOREACH ENDS\r\n\r\n                unset($kuo_image_ids);\r\n\r\n                # REMOVING DUPLICATES\r\n                $kuo_image_id_array = array_unique($kuo_image_id_array);\r\n                $kuo_image_id_array_html_marker = array_unique($kuo_image_id_array_html_marker);\r\n\r\n                # FETCHING IMAGE NAMES FROM DB\r\n                if (!empty($kuo_image_id_array)) {\r\n                    if (count($kuo_image_id_array) == 1) {\r\n                        $kuo_image_names = safe_row(\"`name`, `id`, `ext`\",\"txp_image\",\"`id` = \'\".$kuo_image_id_array[0].\"\'\",0);\r\n                        $kuo_file_names = \'/\'.substr($kuo_image_names[\'name\'],0,strrpos($kuo_image_names[\'name\'],\'.\')).\'_\'.$kuo_image_names[\'id\'].$kuo_image_names[\'ext\'];\r\n                    }\r\n                    else {\r\n                        $kuo_image_names = safe_query(\"SELECT DISTINCT `name`, `id`, `ext` FROM `\".safe_pfx(\'txp_image\').\"` WHERE `id` IN (\".implode(\',\',$kuo_image_id_array).\")\",0);\r\n                        $kuo_file_names = array();\r\n                        foreach ($kuo_image_names as $kuo_name) {\r\n                            $kuo_file_names[] .= \'/\'.substr($kuo_name[\'name\'],0,strrpos($kuo_name[\'name\'],\'.\')).\'_\'.$kuo_name[\'id\'].$kuo_name[\'ext\'];\r\n                        }\r\n                        # FOREACH ENDS\r\n                    }\r\n\r\n                    # BEGINS: HTML IS MODIFIED WITH SEARCH AND REPLACE\r\n                    $kuo_output = str_replace($kuo_image_id_array_html_marker,$kuo_file_names,$kuo_output);\r\n                    # ENDS: HTML IS MODIFIED WITH SEARCH AND REPLACE\r\n\r\n                    unset($kuo_image_ids);\r\n                    unset($kuo_file_names);\r\n                    unset($kuo_image_id_array);\r\n                    unset($kuo_image_id_array_html_marker);\r\n                }\r\n                # IF ID ARRAY NOT EMPTY ENDS\r\n            }\r\n            # IF ENDS\r\n        }\r\n        # ELSE ENDS\r\n\r\n        echo $kuo_output;\r\n        die;\r\n    }\r\n    # ENDS: FUNCTION TO OUTPUT COLLECTED PAGE HTML\r\n\r\n    # BEGINS: SETTINGS ARE SET IF IMAGE ID IS AVAILABLE\r\n    if ((isset($_GET[\'kuo_image_id\'])) && (!empty($_GET[\'kuo_image_id\']))) {\r\n        define(\'KUO_IMAGE_ID\',filter_var($_GET[\'kuo_image_id\'],FILTER_SANITIZE_NUMBER_INT));\r\n\r\n        $kuo_image_info = safe_row(\"`name`\",\"txp_image\",\"`id` = \'\".KUO_IMAGE_ID.\"\' LIMIT 0, 1\",0);\r\n\r\n        define(\'KUO_IMAGE_EXTENSION\',pathinfo($kuo_image_info[\'name\'],PATHINFO_EXTENSION));\r\n        define(\'KUO_IMAGE_NAME\',substr($kuo_image_info[\'name\'],0,strrpos($kuo_image_info[\'name\'],\'.\')));\r\n        define(\'KUO_IMAGE_PATH\',\'./images/\'.KUO_IMAGE_ID.\'.\'.KUO_IMAGE_EXTENSION);\r\n\r\n        unset($kuo_image_info);\r\n    }\r\n    # ENDS: SETTINGS ARE SET IF IMAGE ID IS AVAILABLE\r\n\r\n    # BEGINS: FETCHING IMAGE FILE FROM DISK\r\n    if ((defined(\'KUO_IMAGE_ID\')) && (isset($_GET[\'kuo_image_name\']))) {\r\n        if ((file_exists(KUO_IMAGE_PATH)) && (is_readable(KUO_IMAGE_PATH))) {\r\n\r\n            header(\'Content-Length: \'.filesize(KUO_IMAGE_PATH));\r\n            header(\'Last-Modified: \'.date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)));\r\n            header(\'ETag: \"\'.md5(date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)).KUO_IMAGE_NAME).\'\"\');\r\n            header(\'Cache-Control: public\');\r\n            header(\'Content-Disposition: filename=\'.KUO_IMAGE_NAME);\r\n            header(\'Content-Type: \'.mime_content_type(KUO_IMAGE_PATH));\r\n\r\n            if (((isset($_SERVER[\'HTTP_IF_MODIFIED_SINCE\'])) && (date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)) === $_SERVER[\'HTTP_IF_MODIFIED_SINCE\']))\r\n            or ((isset($_SERVER[\'HTTP_IF_NONE_MATCH\'])) && trim($_SERVER[\'HTTP_IF_NONE_MATCH\']) === md5(date(DATE_RFC2822,filemtime(KUO_IMAGE_PATH)).KUO_IMAGE_NAME))) {\r\n                header(\'HTTP/1.1 304 Not Modified\');\r\n            }\r\n            # IF MODIFIED ENDS\r\n\r\n            # WILL ATTEMPT TO USE X-SENDFILE FIRST\r\n            if ((function_exists(\'apache_get_modules\')) && (in_array(\'mod_xsendfile\',apache_get_modules()))) {\r\n\r\n                # X-SENDFILE REQUIRES ABSOLUTE PATH\r\n                $kuo_path = safe_row(\"`val`\",\"txp_prefs\",\"`name` = \'path_to_site\'\",0);\r\n                $kuo_path = filter_var($kuo_path[\'val\'],FILTER_SANITIZE_STRING);\r\n\r\n                header(\'X-Sendfile: \'.$kuo_path.str_replace(\'./\',\'/\',KUO_IMAGE_PATH));\r\n            }\r\n            # IF X-SENDFILE NOT AVAILABLE, USE READFILE INSTEAD\r\n            else {\r\n                readfile(KUO_IMAGE_PATH);\r\n            }\r\n        }\r\n        # IF FILE NOT THERE\r\n        else {\r\n            header(\'HTTP/1.0 404 Not Found\');\r\n        }\r\n        die;\r\n    }\r\n    # ENDS: FETCHING IMAGE FILE FROM DISK\r\n    # BEGINS: ROUTING IMAGE FILE THAT HAS ONLY ID TO A KEYWORDED URL\r\n    elseif ((defined(\'KUO_IMAGE_ID\')) && (!isset($_GET[\'kuo_image_name\']))) {\r\n        if (!empty(KUO_IMAGE_NAME)) {\r\n            header(\'location: \'.KUO_IMAGE_NAME.\'_\'.KUO_IMAGE_ID.\'.\'.KUO_IMAGE_EXTENSION);\r\n        }\r\n        else {\r\n            header(\'HTTP/1.0 404 Not Found\');\r\n        }\r\n        die;\r\n    }\r\n    # ENDS: ROUTING IMAGE FILE THAT HAS ONLY ID TO A KEYWORDED URL\r\n}\r\n# ENDS: IF PUBLIC\r\n', 'fee6cc08ca7d3967c8ce7709f5067630', '', '', 1, 6, 0),
('kuo_jhtmlarea_cdn', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.3', 'Fetching jHtmlArea from CDN (jsDelivr) and activating it in the Write section.', '<ul id=\"links\">\n<li id=\"kuo_jhtmlarea_cdn\"><a href=\"#kuo_jhtmlarea_cdn\">kuo_jhtmlarea_cdn</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>This plugin activates jHtmlArea WYSIWYG HTML Editor (0.8.0) for jQuery (read more at http://jhtmlarea.codeplex.com) and inserts it to both of the <code>textarea</code>s (body and excerpt) at the Write tab.</p>\n<p id=\"notice\">This plugin is suitable for sites where ALL articles are to be handled with jHtmlArea.</p>\n<p>If the plugin is activated, it\'s activated in all of the articles&hellip; No toggling on and off per article, at least not yet. Also:</p>\n<ul>\n<li>Please leave text untouched for articles (Textile off).</li>\n<li>jHtmlArea is loaded in English.</li>\n<li>Use at your own risk.</li>\n<li>Plugin is loaded from the content distribution network jsDelivr.</li>\n<li>The plugin is not fully compatible with <code>kuo_sortable</code>. Problem is fixed when <code>kuo_sortable</code> is reset or <code>kuo_sortable</code> is not used.</li>\n</ul>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_jhtmlarea_cdn</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_jhtmlarea_cdn</code>.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style>#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 1% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_jhtmlarea_head\',\'admin_side\',\'head_end\');\r\n\r\n    function kuo_jhtmlarea_head() {\r\n        if ($GLOBALS[\'event\'] === \'article\') {\r\n            echo \'<script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/jhtmlarea/0.8.0/scripts/jHtmlArea-0.8.min.js\"></script>\r\n\r\n            <style type=\"text/css\" media=\"all\">\r\n            .ToolBar {\r\n                max-width: 100%;\r\n            }\r\n            </style>\r\n\r\n            <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/jhtmlarea/0.8.0/style/jHtmlArea.css\" />\r\n\r\n            <script type=\"text/javascript\">\r\n            $(document).ready(function() {\r\n                var editor_ok = \"1px solid #060\";\r\n                var editor_problem = \"1px solid #c00\";\r\n\r\n                $(\"textarea#body,textarea#excerpt\").htmlarea();\r\n\r\n                $(\"select#markup-body option[value=\\\'0\\\']\").attr(\"selected\",\"selected\");\r\n                $(\"select#markup-excerpt option[value=\\\'0\\\']\").attr(\"selected\",\"selected\");\r\n                $(\"select#markup-body\").css(\"border\",editor_ok);\r\n                $(\"select#markup-excerpt\").css(\"border\",editor_ok);\r\n\r\n                $(\"select#markup-body,select#markup-excerpt\").change(function() {\r\n                    if ($(this).val() == 0) {\r\n                        $(this).css(\"border\",editor_ok);\r\n                    }\r\n                    else {\r\n                        $(this).css(\"border\",editor_problem);\r\n                    }\r\n                });\r\n            });\r\n            </script>\';\r\n        }\r\n        # IF ARTICLE PAGE ENDS\r\n    }\r\n    # FUNCTION ENDS\r\n}\r\n# IF ADMIN AREA ENDS\r\n', 'if (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_jhtmlarea_head\',\'admin_side\',\'head_end\');\r\n\r\n    function kuo_jhtmlarea_head() {\r\n        if ($GLOBALS[\'event\'] === \'article\') {\r\n            echo \'<script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/jhtmlarea/0.8.0/scripts/jHtmlArea-0.8.min.js\"></script>\r\n\r\n            <style type=\"text/css\" media=\"all\">\r\n            .ToolBar {\r\n                max-width: 100%;\r\n            }\r\n            </style>\r\n\r\n            <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/jhtmlarea/0.8.0/style/jHtmlArea.css\" />\r\n\r\n            <script type=\"text/javascript\">\r\n            $(document).ready(function() {\r\n                var editor_ok = \"1px solid #060\";\r\n                var editor_problem = \"1px solid #c00\";\r\n\r\n                $(\"textarea#body,textarea#excerpt\").htmlarea();\r\n\r\n                $(\"select#markup-body option[value=\\\'0\\\']\").attr(\"selected\",\"selected\");\r\n                $(\"select#markup-excerpt option[value=\\\'0\\\']\").attr(\"selected\",\"selected\");\r\n                $(\"select#markup-body\").css(\"border\",editor_ok);\r\n                $(\"select#markup-excerpt\").css(\"border\",editor_ok);\r\n\r\n                $(\"select#markup-body,select#markup-excerpt\").change(function() {\r\n                    if ($(this).val() == 0) {\r\n                        $(this).css(\"border\",editor_ok);\r\n                    }\r\n                    else {\r\n                        $(this).css(\"border\",editor_problem);\r\n                    }\r\n                });\r\n            });\r\n            </script>\';\r\n        }\r\n        # IF ARTICLE PAGE ENDS\r\n    }\r\n    # FUNCTION ENDS\r\n}\r\n# IF ADMIN AREA ENDS\r\n', '58778daebfeba31d24a1d3d180210665', '', '', 4, 5, 0),
('kuo_json_feed', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Create JSON Feeds of articles.', '<h2>Overview</h2>\n\n<p>Generate a <a href=\"https://jsonfeed.org/\" rel=\"external\" target=\"_new\">JSON Feed</a> of website\'s articles. By default this plugin will create a feed that is located at: <a href=\"../?kuo_json_feed\" target=\"_new\">?kuo_json_feed</a>.</p>\n\n<p>Individual sections\' articles can be accessed from: <code>?kuo_json_feed=mysection</code>, but only if that section in question actually exists and its <strong>Syndicate feed</strong> setting allows feed creation.</p>\n\n<hr />\n\n<h3>Clean URL\'s</h3>\n\n<p>To create clean URL addresses of the above mentioned &quot;messy&quot; URL\'s, please put inside your website\'s <code>.htaccess</code> file the following two lines (below this: <code>RewriteEngine On</code>):</p>\n\n<p><code>RewriteRule ^feed.json$ ./index.php?kuo_json_feed</code></p>\n<p><code>RewriteRule ^([\\-a-zA-Z0-9]+)-feed.json$ ./index.php?kuo_json_feed=$1</code></p>\n\n<p>Those two rules make it so that feed can be opened from <code>www.yoursite.com/feed.json</code> and individual sections\' feed from: <code>www.yoursite.com/mysection-feed.json</code>.</p>\n\n<hr />\n\n<h6>Tested with</h6>\n\n<p>This plugin has so far been tested with Textpattern 4.6.2.</p>', '\r\n# BEGINS: PUBLIC\r\nif ((@txpinterface === \'public\') && (isset($_GET[\'kuo_json_feed\']))) {\r\n\r\n    register_callback(\'kuo_json_feed\',\'pretext\');\r\n\r\n    function kuo_json_feed() {\r\n\r\n        # MESSY URL BY DEFAULT, CHANGE THIS IF YOU CREATE A CLEANER ONE (LIKE: \"feed.json\") WITH HTACCESS\r\n        # IF YOU WANT TO PUT CLEAN URLS, PLACE INSIDE ROOT\'S .htaccess FILE FOR EXAMPLE THESE:\r\n        # RewriteRule ^feed.json$ ./index.php?kuo_json_feed\r\n        # RewriteRule ^([\\-a-zA-Z0-9]+)-feed.json$ ./index.php?kuo_json_feed=$1\r\n        # WITH THE ABOVE RULES FEED CAN BE ACCESSED FROM:\r\n        # www.mysite.com/feed.json\r\n        # www.mysite.com/mysection-feed.json\r\n        # CLEAN URL\'S:\r\n        #define(\'KUO_JSON_FEED_URL_SLUG\',$_GET[\'kuo_json_feed\'].\'-feed.json\');\r\n        # MESSY URL\'S:\r\n        if (!empty($_GET[\'kuo_json_feed\'])) {\r\n            define(\'KUO_JSON_FEED_URL_SLUG\',\'?kuo_json_feed=\'.$_GET[\'kuo_json_feed\']);\r\n        }\r\n        else {\r\n            define(\'KUO_JSON_FEED_URL_SLUG\',\'?kuo_json_feed\');\r\n        }\r\n\r\n        define(\'KUO_JSON_FEED_USER_COMMENT\',\'JSON\');\r\n        define(\'KUO_JSON_FEED_FAVICON_URL\',site_url().\'favicon.ico\');\r\n\r\n        # ICON SHOULD BE SQUARE AND LARGE (IE. 512x512)\r\n        # BY DEFAULT THIS OPTION IS NOT USED\r\n        #define(\'KUO_JSON_FEED_ICON_URL\',site_url().\'large-image-file.png\');\r\n\r\n        # HAS THIS WEBSITE EXPIRED, THAT IS: IT\'S NO LONGER UPDATED?\r\n        # YES = true\r\n        # NO = false\r\n        define(\'KUO_JSON_FEED_STATUS\',false);\r\n\r\n        # NUMBER OF JSON ITEMS IS BY DEFAULT SAME AS NUMBER OF RSS/ATOM ITEMS\r\n        define(\'KUO_JSON_FEED_ITEM_LIMIT\',intval(get_pref(\'rss_how_many\',5)));\r\n\r\n        # SHOULD JSON FEED SHOW EXCERPT OR BODY? BY DEFAULT SAME SETTING AS WITH RSS/ATOM FEEDS\r\n        define(\'KUO_JSON_FEED_SYNDICATE_CONTENT\',get_pref(\'syndicate_body_or_excerpt\',1));\r\n\r\n        # BEGINS: MODIFY SQL QUERY TO GET WHAT IS NEEDED\r\n        if (KUO_JSON_FEED_SYNDICATE_CONTENT == 1) {\r\n            $article_content_sql = \', \'.safe_pfx(\'textpattern\').\'.Body_html AS `content_html`\';\r\n        }\r\n        else {\r\n            $article_content_sql = \', \'.safe_pfx(\'textpattern\').\'.Excerpt_html AS `content_html`\';\r\n        }\r\n        # ENDS: MODIFY SQL QUERY TO GET WHAT IS NEEDED\r\n\r\n        # LIST ALLOWED SECTIONS (ONLY THOSE THAT HAVE \"SYNDICATE CONTENT\" SET AS \"YES\")\r\n        $sections = safe_rows(\'name\',\'txp_section\',\'in_rss = 1\',0);\r\n\r\n        # CONTAINER\r\n        $section_flattened_array = array();\r\n\r\n        # BEGINS: FLATTENS THE RESULT ARRAY\r\n        if ((is_array($sections)) && (!empty($sections))) {\r\n            foreach ($sections as $section) {\r\n                $section_flattened_array[] = $section[\'name\'];\r\n            }\r\n        }\r\n        # ENDS: FLATTENS THE RESULT ARRAY\r\n\r\n        # IF USER IS VISITNG A CERTAIN SECTION, THE SQL QUERY SHOULD LIMIT\r\n        # THE SEARCH FOR CONTENT TO JUST THAT SECTION\r\n        if (in_array($_GET[\'kuo_json_feed\'],$section_flattened_array)) {\r\n            $article_limit_sql = \' AND \'.safe_pfx(\'txp_section\').\'.name = \"\'.$_GET[\'kuo_json_feed\'].\'\" \';\r\n        }\r\n        elseif (empty($_GET[\'kuo_json_feed\'])) {\r\n            $article_limit_sql = NULL;\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        $article_list = safe_query(\"\r\n        SELECT\r\n            \".safe_pfx(\'textpattern\').\".ID AS `ID`,\r\n            \".safe_pfx(\'textpattern\').\".Posted AS `Posted`,\r\n            \".safe_pfx(\'textpattern\').\".AuthorID AS `AuthorID`,\r\n            \".safe_pfx(\'textpattern\').\".LastMod AS `LastMod`,\r\n            \".safe_pfx(\'textpattern\').\".Title AS `Title`,\r\n            \".safe_pfx(\'textpattern\').\".Keywords AS `Keywords`,\r\n            \".safe_pfx(\'textpattern\').\".description AS `description`\r\n            \".$article_content_sql.\"\r\n        FROM\r\n            \".safe_pfx(\'textpattern\').\"\r\n        INNER JOIN\r\n            \".safe_pfx(\'txp_section\').\"\r\n        ON\r\n            \".safe_pfx(\'txp_section\').\".name = \".safe_pfx(\'textpattern\').\".Section\r\n        WHERE\r\n            \".safe_pfx(\'txp_section\').\".in_rss = 1\r\n            \".$article_limit_sql.\"\r\n        GROUP BY\r\n            \".safe_pfx(\'textpattern\').\".ID\r\n        ORDER BY\r\n            \".safe_pfx(\'textpattern\').\".ID DESC\r\n        LIMIT\r\n            0, \".KUO_JSON_FEED_ITEM_LIMIT\r\n        );\r\n\r\n        # BEGINS: CONTENT WAS FOUND\r\n        if (count($article_list) > 0) {\r\n\r\n            $articles_array = array();\r\n\r\n            # BEGINS: ARTICLES ARRAY\r\n            while ($article = mysqli_fetch_assoc($article_list)) {\r\n                # BEGINS: ENSURING THAT THERE IS REQUIRED CONTENT FOR THIS FEED ITEM\r\n                if ((isset($article[\'content_html\'])) && (!empty($article[\'content_html\']))) {\r\n                    $articles_array[] = array(\r\n                        \'title\'=>$article[\'Title\'],\r\n                        \'date_published\'=>date(\'c\',strtotime($article[\'Posted\'])),\r\n                        \'date_modified\'=>date(\'c\',strtotime($article[\'LastMod\'])),\r\n                        \'id\'=>permlinkurl_id($article[\'ID\']),\r\n                        \'url\'=>permlinkurl_id($article[\'ID\']),\r\n                        \'author\'=>array(\'name\'=>get_author_name($article[\'AuthorID\'])),\r\n                        \'content_html\'=>$article[\'content_html\'],\r\n                        \'summary\'=>$article[\'description\'],\r\n                        \'tags\'=>$article[\'Keywords\']\r\n                    );\r\n                }\r\n                # ENDS: ENSURING THAT THERE IS REQUIRED CONTENT FOR THIS FEED ITEM\r\n            }\r\n            # ENDS: ARTICLES ARRAY\r\n\r\n            # REMOVE EMPTY ARRAY ITEMS\r\n            $articles_array = array_map(\'array_filter\',$articles_array);\r\n\r\n            $kuo_json_feed = array(\r\n                \'version\'=>\'https://jsonfeed.org/version/1\',\r\n                \'title\'=>site_name(),\r\n                \'description\'=>site_slogan(),\r\n                \'user_comment\'=>KUO_JSON_FEED_USER_COMMENT,\r\n                \'home_page_url\'=>site_url(),\r\n                \'feed_url\'=>site_url().KUO_JSON_FEED_URL_SLUG,\r\n                \'favicon\'=>KUO_JSON_FEED_FAVICON_URL,\r\n                \'expired\'=>KUO_JSON_FEED_STATUS\r\n            );\r\n\r\n            # BEGINS: IS THERE A FEED ICON AVAILABLE?\r\n            if ((defined(\'KUO_JSON_FEED_ICON_URL\')) && (!empty(KUO_JSON_FEED_ICON_URL))) {\r\n                $kuo_json_feed[\'icon\'] = KUO_JSON_FEED_ICON_URL;\r\n            }\r\n            # ENDS: IS THERE A FEED ICON AVAILABLE?\r\n\r\n            # BEGINS: ADD FEED ITEMS TO THE END OF ARRAY\r\n            if ((is_array($articles_array)) && (count($articles_array) != 0)) {\r\n                $kuo_json_feed[\'items\'] = $articles_array;\r\n            }\r\n            # ENDS: ADD FEED ITEMS TO THE END OF ARRAY\r\n\r\n            unset(\r\n                $article_content_sql,\r\n                $article_list,\r\n                $articles_array\r\n            );\r\n\r\n            # OUTPUTTING JSON\r\n            send_json_response($kuo_json_feed);\r\n        }\r\n        # ENDS: CONTENT WAS FOUND\r\n        # # BEGINS: NO CONTENT AT ALL = NO FEED\r\n        else {\r\n            return;\r\n        }\r\n        # ENDS: NO CONTENT AT ALL = NO FEED\r\n    }\r\n\r\n    echo kuo_json_feed();\r\n    die;\r\n}\r\n# ENDS: PUBLIC\r\n', '\r\n# BEGINS: PUBLIC\r\nif ((@txpinterface === \'public\') && (isset($_GET[\'kuo_json_feed\']))) {\r\n\r\n    register_callback(\'kuo_json_feed\',\'pretext\');\r\n\r\n    function kuo_json_feed() {\r\n\r\n        # MESSY URL BY DEFAULT, CHANGE THIS IF YOU CREATE A CLEANER ONE (LIKE: \"feed.json\") WITH HTACCESS\r\n        # IF YOU WANT TO PUT CLEAN URLS, PLACE INSIDE ROOT\'S .htaccess FILE FOR EXAMPLE THESE:\r\n        # RewriteRule ^feed.json$ ./index.php?kuo_json_feed\r\n        # RewriteRule ^([\\-a-zA-Z0-9]+)-feed.json$ ./index.php?kuo_json_feed=$1\r\n        # WITH THE ABOVE RULES FEED CAN BE ACCESSED FROM:\r\n        # www.mysite.com/feed.json\r\n        # www.mysite.com/mysection-feed.json\r\n        # CLEAN URL\'S:\r\n        #define(\'KUO_JSON_FEED_URL_SLUG\',$_GET[\'kuo_json_feed\'].\'-feed.json\');\r\n        # MESSY URL\'S:\r\n        if (!empty($_GET[\'kuo_json_feed\'])) {\r\n            define(\'KUO_JSON_FEED_URL_SLUG\',\'?kuo_json_feed=\'.$_GET[\'kuo_json_feed\']);\r\n        }\r\n        else {\r\n            define(\'KUO_JSON_FEED_URL_SLUG\',\'?kuo_json_feed\');\r\n        }\r\n\r\n        define(\'KUO_JSON_FEED_USER_COMMENT\',\'JSON\');\r\n        define(\'KUO_JSON_FEED_FAVICON_URL\',site_url().\'favicon.ico\');\r\n\r\n        # ICON SHOULD BE SQUARE AND LARGE (IE. 512x512)\r\n        # BY DEFAULT THIS OPTION IS NOT USED\r\n        #define(\'KUO_JSON_FEED_ICON_URL\',site_url().\'large-image-file.png\');\r\n\r\n        # HAS THIS WEBSITE EXPIRED, THAT IS: IT\'S NO LONGER UPDATED?\r\n        # YES = true\r\n        # NO = false\r\n        define(\'KUO_JSON_FEED_STATUS\',false);\r\n\r\n        # NUMBER OF JSON ITEMS IS BY DEFAULT SAME AS NUMBER OF RSS/ATOM ITEMS\r\n        define(\'KUO_JSON_FEED_ITEM_LIMIT\',intval(get_pref(\'rss_how_many\',5)));\r\n\r\n        # SHOULD JSON FEED SHOW EXCERPT OR BODY? BY DEFAULT SAME SETTING AS WITH RSS/ATOM FEEDS\r\n        define(\'KUO_JSON_FEED_SYNDICATE_CONTENT\',get_pref(\'syndicate_body_or_excerpt\',1));\r\n\r\n        # BEGINS: MODIFY SQL QUERY TO GET WHAT IS NEEDED\r\n        if (KUO_JSON_FEED_SYNDICATE_CONTENT == 1) {\r\n            $article_content_sql = \', \'.safe_pfx(\'textpattern\').\'.Body_html AS `content_html`\';\r\n        }\r\n        else {\r\n            $article_content_sql = \', \'.safe_pfx(\'textpattern\').\'.Excerpt_html AS `content_html`\';\r\n        }\r\n        # ENDS: MODIFY SQL QUERY TO GET WHAT IS NEEDED\r\n\r\n        # LIST ALLOWED SECTIONS (ONLY THOSE THAT HAVE \"SYNDICATE CONTENT\" SET AS \"YES\")\r\n        $sections = safe_rows(\'name\',\'txp_section\',\'in_rss = 1\',0);\r\n\r\n        # CONTAINER\r\n        $section_flattened_array = array();\r\n\r\n        # BEGINS: FLATTENS THE RESULT ARRAY\r\n        if ((is_array($sections)) && (!empty($sections))) {\r\n            foreach ($sections as $section) {\r\n                $section_flattened_array[] = $section[\'name\'];\r\n            }\r\n        }\r\n        # ENDS: FLATTENS THE RESULT ARRAY\r\n\r\n        # IF USER IS VISITNG A CERTAIN SECTION, THE SQL QUERY SHOULD LIMIT\r\n        # THE SEARCH FOR CONTENT TO JUST THAT SECTION\r\n        if (in_array($_GET[\'kuo_json_feed\'],$section_flattened_array)) {\r\n            $article_limit_sql = \' AND \'.safe_pfx(\'txp_section\').\'.name = \"\'.$_GET[\'kuo_json_feed\'].\'\" \';\r\n        }\r\n        elseif (empty($_GET[\'kuo_json_feed\'])) {\r\n            $article_limit_sql = NULL;\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n\r\n        $article_list = safe_query(\"\r\n        SELECT\r\n            \".safe_pfx(\'textpattern\').\".ID AS `ID`,\r\n            \".safe_pfx(\'textpattern\').\".Posted AS `Posted`,\r\n            \".safe_pfx(\'textpattern\').\".AuthorID AS `AuthorID`,\r\n            \".safe_pfx(\'textpattern\').\".LastMod AS `LastMod`,\r\n            \".safe_pfx(\'textpattern\').\".Title AS `Title`,\r\n            \".safe_pfx(\'textpattern\').\".Keywords AS `Keywords`,\r\n            \".safe_pfx(\'textpattern\').\".description AS `description`\r\n            \".$article_content_sql.\"\r\n        FROM\r\n            \".safe_pfx(\'textpattern\').\"\r\n        INNER JOIN\r\n            \".safe_pfx(\'txp_section\').\"\r\n        ON\r\n            \".safe_pfx(\'txp_section\').\".name = \".safe_pfx(\'textpattern\').\".Section\r\n        WHERE\r\n            \".safe_pfx(\'txp_section\').\".in_rss = 1\r\n            \".$article_limit_sql.\"\r\n        GROUP BY\r\n            \".safe_pfx(\'textpattern\').\".ID\r\n        ORDER BY\r\n            \".safe_pfx(\'textpattern\').\".ID DESC\r\n        LIMIT\r\n            0, \".KUO_JSON_FEED_ITEM_LIMIT\r\n        );\r\n\r\n        # BEGINS: CONTENT WAS FOUND\r\n        if (count($article_list) > 0) {\r\n\r\n            $articles_array = array();\r\n\r\n            # BEGINS: ARTICLES ARRAY\r\n            while ($article = mysqli_fetch_assoc($article_list)) {\r\n                # BEGINS: ENSURING THAT THERE IS REQUIRED CONTENT FOR THIS FEED ITEM\r\n                if ((isset($article[\'content_html\'])) && (!empty($article[\'content_html\']))) {\r\n                    $articles_array[] = array(\r\n                        \'title\'=>$article[\'Title\'],\r\n                        \'date_published\'=>date(\'c\',strtotime($article[\'Posted\'])),\r\n                        \'date_modified\'=>date(\'c\',strtotime($article[\'LastMod\'])),\r\n                        \'id\'=>permlinkurl_id($article[\'ID\']),\r\n                        \'url\'=>permlinkurl_id($article[\'ID\']),\r\n                        \'author\'=>array(\'name\'=>get_author_name($article[\'AuthorID\'])),\r\n                        \'content_html\'=>$article[\'content_html\'],\r\n                        \'summary\'=>$article[\'description\'],\r\n                        \'tags\'=>$article[\'Keywords\']\r\n                    );\r\n                }\r\n                # ENDS: ENSURING THAT THERE IS REQUIRED CONTENT FOR THIS FEED ITEM\r\n            }\r\n            # ENDS: ARTICLES ARRAY\r\n\r\n            # REMOVE EMPTY ARRAY ITEMS\r\n            $articles_array = array_map(\'array_filter\',$articles_array);\r\n\r\n            $kuo_json_feed = array(\r\n                \'version\'=>\'https://jsonfeed.org/version/1\',\r\n                \'title\'=>site_name(),\r\n                \'description\'=>site_slogan(),\r\n                \'user_comment\'=>KUO_JSON_FEED_USER_COMMENT,\r\n                \'home_page_url\'=>site_url(),\r\n                \'feed_url\'=>site_url().KUO_JSON_FEED_URL_SLUG,\r\n                \'favicon\'=>KUO_JSON_FEED_FAVICON_URL,\r\n                \'expired\'=>KUO_JSON_FEED_STATUS\r\n            );\r\n\r\n            # BEGINS: IS THERE A FEED ICON AVAILABLE?\r\n            if ((defined(\'KUO_JSON_FEED_ICON_URL\')) && (!empty(KUO_JSON_FEED_ICON_URL))) {\r\n                $kuo_json_feed[\'icon\'] = KUO_JSON_FEED_ICON_URL;\r\n            }\r\n            # ENDS: IS THERE A FEED ICON AVAILABLE?\r\n\r\n            # BEGINS: ADD FEED ITEMS TO THE END OF ARRAY\r\n            if ((is_array($articles_array)) && (count($articles_array) != 0)) {\r\n                $kuo_json_feed[\'items\'] = $articles_array;\r\n            }\r\n            # ENDS: ADD FEED ITEMS TO THE END OF ARRAY\r\n\r\n            unset(\r\n                $article_content_sql,\r\n                $article_list,\r\n                $articles_array\r\n            );\r\n\r\n            # OUTPUTTING JSON\r\n            send_json_response($kuo_json_feed);\r\n        }\r\n        # ENDS: CONTENT WAS FOUND\r\n        # # BEGINS: NO CONTENT AT ALL = NO FEED\r\n        else {\r\n            return;\r\n        }\r\n        # ENDS: NO CONTENT AT ALL = NO FEED\r\n    }\r\n\r\n    echo kuo_json_feed();\r\n    die;\r\n}\r\n# ENDS: PUBLIC\r\n', '7c16eeb64c4b7f694490357d081e97ae', '', '', 0, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_pubbar', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'If user is logged in, plugin includes an admin bar to the public side of website.', '<ul id=\"links\">\n<li id=\"kuo_pubbar\"><a href=\"#kuo_pubbar\">kuo_pubbar</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<img alt=\"\" src=\"data:image/png;base64, \" height=\"91\" width=\"975\" />\n<h2>Briefly about this plugin</h2>\n<p>This plugin generates a horizontal control bar to the public side of website. It\'s visible to certain user groups. The control bar\'s height is about 40 pixels. It includes links to most commonly used sections of Textpattern\'s admin side. Control bar uses the language which is set in Textpattern\'s preferences.</p>\n<p id=\"notice\">If the plugin is activated, it then shows the public admin bar for each logged in with Publisher, Managing editor and Copy editor privileges.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_pubbar</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_pubbar</code>.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; } #briefly img { display: block; text-align: center; margin: 0 auto 10px auto; width: 90%; height: auto; max-width: 1000px; border: 5px solid #f0f0f0; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'public\') {\r\nregister_callback(\'kuo_pubbar_bottom\',\'textpattern_end\');\r\n\r\nfunction kuo_pubbar_bottom() {\r\n\r\n# THANKS rvm_privileged FOR INTRODUCING US THIS FUNCTION\r\n$is_user_etc = is_logged_in();\r\n\r\n# GENERAL NOTES IN CASE YOU,\r\n# AND YES, I MEAN YOU,\r\n# WANT TO MODIFY THIS PLUGIN\r\n# 1 = Publisher (admin)\r\n# 2 = Managing editor\r\n# 3 = Copy editor\r\n# 4 = Staff writer\r\n# 5 = Freelancer\r\n# 6 = Designer\r\n# 0 = User with no privileges\r\n# TOP 3 USER GROUPS CAN SEE THIS:\r\n$allowed_privs = array(1,2,3);\r\n\r\n# \"PUBBAR\" IS SHOWN ONLY TO LOGGED IN USERS\r\n# WHO HAVE CERTAIN PRIVILEGES\r\nif ((isset($_COOKIE[\'txp_login_public\'])) && (!empty($_COOKIE[\'txp_login_public\'])) && (isset($GLOBALS[\'prefs\'][\'siteurl\'])) && (in_array($is_user_etc[\'privs\'],$allowed_privs))) {\r\n\r\n# TRYING TO FIGURE OUT WHAT URL TO USE\r\nif ((!empty($_SERVER[\'HTTPS\'])) && ($_SERVER[\'HTTPS\'] !== \'off\') or ($_SERVER[\'SERVER_PORT\'] == 443)) {\r\n$site_url = \'https://\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/textpattern/\';\r\n}\r\nelse {\r\n$site_url = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/textpattern/\';\r\n}\r\n\r\n# IS THIS ARTICLE?\r\nif ((isset($GLOBALS[\'pretext\'][\'id\'])) && (!empty($GLOBALS[\'pretext\'][\'id\'])) && (is_numeric($GLOBALS[\'pretext\'][\'id\']))) {\r\n$edit_article_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?event=article&amp;step=edit&amp;ID=\'.$GLOBALS[\'pretext\'][\'id\'].\'\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'article\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_article_link = NULL;\r\n}\r\n\r\n# IS THIS CATEGORY?\r\nif ((isset($GLOBALS[\'pretext\'][\'c\'])) && (!empty($GLOBALS[\'pretext\'][\'c\']))) {\r\n$edit_category_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?event=category\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'categories\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_category_link = NULL;\r\n}\r\n\r\n# DOES THIS ARTICLE HAVE COMMENTS?\r\nif ((isset($GLOBALS[\'thisarticle\'][\'comments_count\'])) && (!empty($GLOBALS[\'thisarticle\'][\'comments_count\'])) && (is_numeric($GLOBALS[\'thisarticle\'][\'comments_count\']))) {\r\n$edit_comments_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?search_method=parent&amp;crit=\'.$GLOBALS[\'thisarticle\'][\'comments_count\'].\'&amp;event=discuss&amp;step=list\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'comments\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_comments_link = NULL;\r\n}\r\n\r\n# GENERATING THE ACTUAL BAR\r\necho \'<style type=\"text/css\">\r\nbody { padding-top: 30px !important; }\r\n#kuo_pubbar { position: absolute; left: 0; top: 0; z-index: 999; height: 36px; width: 99%; padding-left: 1%; display: block; overflow: hidden; font: 12px Arial, Helvetica, sans-serif; border-top: 2px solid #FBC431; }\r\n#kuo_pubbar ul { list-style: none; padding: 0; margin: 0; font-size: 12px; display: table; width: 100%; height: 100%; clear: both; white-space: nowrap; line-height: 14px; }\r\n#kuo_pubbar li { display: inline; float: left; }\r\n#kuo_pubbar a { text-decoration: none; display: inline-block; float: left; padding: 7px 10px; color: #333; border-right: 1px solid #E1A61A; border-bottom: 1px solid #E1A61A; background: #FBC431; white-space: nowrap; margin-left: 1px; }\r\n#kuo_pubbar #kuo_logout a { font-weight: bold; color: #a00; }\r\n#kuo_pubbar li:first-child a { border-radius: 0 0 0 5px; border-left: 1px solid #E1A61A; }\r\n#kuo_pubbar li:last-child a { border-radius: 0 0 5px 0; }\r\n@media all and (min-width: 960px) {\r\n#kuo_pubbar a { padding: 7px 20%; }\r\n}\r\n#kuo_pubbar .special a { font-weight: bold; }\r\n#kuo_pubbar a:hover,#kuo_pubbar a:focus,#kuo_pubbar a:active { background: #FBC431; color: #333; text-decoration: none; border-top: 5px solid #FBC431; color: #101010; border-radius: 0 0 5px 5px; }\r\n#kuo_pubbar li:last-child a:hover, #kuo_pubbar li:last-child a:focus, #kuo_pubbar li:last-child a:active, #kuo_pubbar li:first-child a:hover, #kuo_pubbar li:first-child a:focus,#kuo_pubbar li:first-child a:Active { border-radius: 0 0 5px 5px; }\r\n</style>\r\n\r\n<div id=\"kuo_pubbar\"><ul>\r\n<li><a href=\"\'.$site_url.\'index.php?event=article\" rel=\"nofollow chapter\">\'.gTxt(\'tab_write\').\'</a></li>\r\n\'.$edit_article_link.$edit_category_link.$edit_comments_link.\'\r\n<li><a href=\"\'.$site_url.\'index.php?event=list\" rel=\"nofollow chapter\">\'.gTxt(\'tab_list\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=image\" rel=\"nofollow chapter\">\'.gTxt(\'tab_image\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=file\" rel=\"nofollow chapter\">\'.gTxt(\'tab_file\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=link\" rel=\"nofollow chapter\">\'.gTxt(\'tab_link\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=discuss\" rel=\"nofollow chapter\">\'.gTxt(\'tab_comments\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=page\" rel=\"nofollow chapter\">\'.gTxt(\'tab_pages\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=form\" rel=\"nofollow chapter\">\'.gTxt(\'tab_forms\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=css\" rel=\"nofollow chapter\">\'.gTxt(\'tab_style\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=prefs\" rel=\"nofollow chapter\">\'.gTxt(\'tab_preferences\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=admin\" rel=\"nofollow chapter\">\'.gTxt(\'tab_site_admin\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=log\" rel=\"nofollow chapter\">\'.gTxt(\'tab_logs\').\'</a></li>\r\n<li id=\"kuo_logout\"><a href=\"\'.$site_url.\'index.php?logout=1\" rel=\"nofollow\">\'.gTxt(\'logout\').\' (\'.$is_user_etc[\'name\'].\')</a></li>\r\n</ul></div>\';\r\n}\r\n\r\n}\r\n}', 'if (@txpinterface == \'public\') {\r\nregister_callback(\'kuo_pubbar_bottom\',\'textpattern_end\');\r\n\r\nfunction kuo_pubbar_bottom() {\r\n\r\n# THANKS rvm_privileged FOR INTRODUCING US THIS FUNCTION\r\n$is_user_etc = is_logged_in();\r\n\r\n# GENERAL NOTES IN CASE YOU,\r\n# AND YES, I MEAN YOU,\r\n# WANT TO MODIFY THIS PLUGIN\r\n# 1 = Publisher (admin)\r\n# 2 = Managing editor\r\n# 3 = Copy editor\r\n# 4 = Staff writer\r\n# 5 = Freelancer\r\n# 6 = Designer\r\n# 0 = User with no privileges\r\n# TOP 3 USER GROUPS CAN SEE THIS:\r\n$allowed_privs = array(1,2,3);\r\n\r\n# \"PUBBAR\" IS SHOWN ONLY TO LOGGED IN USERS\r\n# WHO HAVE CERTAIN PRIVILEGES\r\nif ((isset($_COOKIE[\'txp_login_public\'])) && (!empty($_COOKIE[\'txp_login_public\'])) && (isset($GLOBALS[\'prefs\'][\'siteurl\'])) && (in_array($is_user_etc[\'privs\'],$allowed_privs))) {\r\n\r\n# TRYING TO FIGURE OUT WHAT URL TO USE\r\nif ((!empty($_SERVER[\'HTTPS\'])) && ($_SERVER[\'HTTPS\'] !== \'off\') or ($_SERVER[\'SERVER_PORT\'] == 443)) {\r\n$site_url = \'https://\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/textpattern/\';\r\n}\r\nelse {\r\n$site_url = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].\'/textpattern/\';\r\n}\r\n\r\n# IS THIS ARTICLE?\r\nif ((isset($GLOBALS[\'pretext\'][\'id\'])) && (!empty($GLOBALS[\'pretext\'][\'id\'])) && (is_numeric($GLOBALS[\'pretext\'][\'id\']))) {\r\n$edit_article_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?event=article&amp;step=edit&amp;ID=\'.$GLOBALS[\'pretext\'][\'id\'].\'\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'article\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_article_link = NULL;\r\n}\r\n\r\n# IS THIS CATEGORY?\r\nif ((isset($GLOBALS[\'pretext\'][\'c\'])) && (!empty($GLOBALS[\'pretext\'][\'c\']))) {\r\n$edit_category_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?event=category\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'categories\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_category_link = NULL;\r\n}\r\n\r\n# DOES THIS ARTICLE HAVE COMMENTS?\r\nif ((isset($GLOBALS[\'thisarticle\'][\'comments_count\'])) && (!empty($GLOBALS[\'thisarticle\'][\'comments_count\'])) && (is_numeric($GLOBALS[\'thisarticle\'][\'comments_count\']))) {\r\n$edit_comments_link = \'<li class=\"special\"><a href=\"\'.$site_url.\'index.php?search_method=parent&amp;crit=\'.$GLOBALS[\'thisarticle\'][\'comments_count\'].\'&amp;event=discuss&amp;step=list\" rel=\"nofollow chapter\">\'.ucfirst(gTxt(\'edit\')).\' \'.strtolower(gTxt(\'comments\')).\'</a></li>\';\r\n}\r\nelse {\r\n$edit_comments_link = NULL;\r\n}\r\n\r\n# GENERATING THE ACTUAL BAR\r\necho \'<style type=\"text/css\">\r\nbody { padding-top: 30px !important; }\r\n#kuo_pubbar { position: absolute; left: 0; top: 0; z-index: 999; height: 36px; width: 99%; padding-left: 1%; display: block; overflow: hidden; font: 12px Arial, Helvetica, sans-serif; border-top: 2px solid #FBC431; }\r\n#kuo_pubbar ul { list-style: none; padding: 0; margin: 0; font-size: 12px; display: table; width: 100%; height: 100%; clear: both; white-space: nowrap; line-height: 14px; }\r\n#kuo_pubbar li { display: inline; float: left; }\r\n#kuo_pubbar a { text-decoration: none; display: inline-block; float: left; padding: 7px 10px; color: #333; border-right: 1px solid #E1A61A; border-bottom: 1px solid #E1A61A; background: #FBC431; white-space: nowrap; margin-left: 1px; }\r\n#kuo_pubbar #kuo_logout a { font-weight: bold; color: #a00; }\r\n#kuo_pubbar li:first-child a { border-radius: 0 0 0 5px; border-left: 1px solid #E1A61A; }\r\n#kuo_pubbar li:last-child a { border-radius: 0 0 5px 0; }\r\n@media all and (min-width: 960px) {\r\n#kuo_pubbar a { padding: 7px 20%; }\r\n}\r\n#kuo_pubbar .special a { font-weight: bold; }\r\n#kuo_pubbar a:hover,#kuo_pubbar a:focus,#kuo_pubbar a:active { background: #FBC431; color: #333; text-decoration: none; border-top: 5px solid #FBC431; color: #101010; border-radius: 0 0 5px 5px; }\r\n#kuo_pubbar li:last-child a:hover, #kuo_pubbar li:last-child a:focus, #kuo_pubbar li:last-child a:active, #kuo_pubbar li:first-child a:hover, #kuo_pubbar li:first-child a:focus,#kuo_pubbar li:first-child a:Active { border-radius: 0 0 5px 5px; }\r\n</style>\r\n\r\n<div id=\"kuo_pubbar\"><ul>\r\n<li><a href=\"\'.$site_url.\'index.php?event=article\" rel=\"nofollow chapter\">\'.gTxt(\'tab_write\').\'</a></li>\r\n\'.$edit_article_link.$edit_category_link.$edit_comments_link.\'\r\n<li><a href=\"\'.$site_url.\'index.php?event=list\" rel=\"nofollow chapter\">\'.gTxt(\'tab_list\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=image\" rel=\"nofollow chapter\">\'.gTxt(\'tab_image\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=file\" rel=\"nofollow chapter\">\'.gTxt(\'tab_file\').\'</a></li>\r\n<li><a href=\"\'.$site_url.\'index.php?event=link\" rel=\"nofollow chapter\">\'.gTxt(\'tab_link\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=discuss\" rel=\"nofollow chapter\">\'.gTxt(\'tab_comments\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=page\" rel=\"nofollow chapter\">\'.gTxt(\'tab_pages\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=form\" rel=\"nofollow chapter\">\'.gTxt(\'tab_forms\').\'</a></li>\r\n<li class=\"kuo_presentation\"><a href=\"\'.$site_url.\'index.php?event=css\" rel=\"nofollow chapter\">\'.gTxt(\'tab_style\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=prefs\" rel=\"nofollow chapter\">\'.gTxt(\'tab_preferences\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=admin\" rel=\"nofollow chapter\">\'.gTxt(\'tab_site_admin\').\'</a></li>\r\n<li class=\"kuo_admin\"><a href=\"\'.$site_url.\'index.php?event=log\" rel=\"nofollow chapter\">\'.gTxt(\'tab_logs\').\'</a></li>\r\n<li id=\"kuo_logout\"><a href=\"\'.$site_url.\'index.php?logout=1\" rel=\"nofollow\">\'.gTxt(\'logout\').\' (\'.$is_user_etc[\'name\'].\')</a></li>\r\n</ul></div>\';\r\n}\r\n\r\n}\r\n}', '0896052c6e1e9eefc83fd8ab85ed0b5b', '', '', 0, 5, 0),
('kuo_robots_editor', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Edit robots.txt at the website\'s root.', '<ul id=\"links\">\n<li id=\"kuo_robots_editor\"><a href=\"#kuo_robots_editor\">kuo_robots_editor</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>Edit your website\'s root <code>robots.txt</code> with this plugin.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_robots_editor</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_robots_editor</code>.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface === \'admin\') {\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_robots_editor\') {\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_robots_editor\',\'Robots.txt Editor\');\r\nregister_callback(\'kuo_robots_editor_tab\',\'kuo_robots_editor\');\r\nadd_privs(\'kuo_robots_editor\',\'1\');\r\n\r\n# ACTUAL CODE BEGINS\r\nfunction kuo_robots_editor_tab() {\r\n\r\nif ((isset($_GET[\'create\'])) && (empty($_GET[\'create\']))) {\r\nfopen(\'../robots.txt\',\'w\');\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'save\'])) && (empty($_GET[\'save\']))) {\r\nif ((isset($_POST[\'submit\'])) && (empty($_POST[\'submit\'])) && (isset($_POST[\'robots_txt\']))) {\r\nfile_put_contents(\'../robots.txt\',filter_var($_POST[\'robots_txt\'],FILTER_SANITIZE_STRING),LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'disallow\'])) && (!empty($_GET[\'disallow\']))) {\r\nif (is_dir(\'../\'.$_GET[\'disallow\'].\'/\')) {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'Disallow: /\'.filter_var($_GET[\'disallow\'],FILTER_SANITIZE_STRING).\'/\',LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'allow\'])) && (!empty($_GET[\'allow\'])) && (in_array($_GET[\'allow\'],array(\'all\',\'none\')))) {\r\nif ($_GET[\'allow\'] === \'all\') {\r\nfile_put_contents(\'../robots.txt\',\'User-agent: *\'.PHP_EOL.\'Disallow: \'.PHP_EOL,LOCK_EX);\r\n}\r\nelse {\r\nfile_put_contents(\'../robots.txt\',\'User-agent: *\'.PHP_EOL.\'Disallow: /\'.PHP_EOL,LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'target\'])) && (!empty($_GET[\'target\'])) && (in_array($_GET[\'target\'],array(\'all\',\'google\',\'mediapartners-google\')))) {\r\nif ($_GET[\'target\'] === \'all\') {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: *\',LOCK_EX);\r\n}\r\nelseif ($_GET[\'target\'] === \'google\') {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: Googlebot\',LOCK_EX);\r\n}\r\nelse {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: Mediapartners-Google\',LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\npagetop(\'Robots.txt Editor\');\r\necho \'\r\n<style>\r\nh3, .note:last-of-type { font-size: 1.6em; }\r\nh3, .align-center, .note:last-of-type { text-align: center; }\r\n.note:last-of-type { padding: 3% 0; }\r\nform ul { list-style: none; padding: 10px 0 5px 0; margin: 0; }\r\nform li { padding-bottom: 5px; }\r\nlegend { font-size: 1.3em; }\r\n</style>\r\n\r\n<h2>Robots.txt Editor</h2>\';\r\n\r\nif (file_exists(\'../robots.txt\')) {\r\n\r\n$robots_txt = file_get_contents(\'../robots.txt\',0,NULL,0,1000);\r\necho \'<p>Modified: \'.date(\'Y-m-d H:i\',filemtime(\'../robots.txt\')).\'</p>\r\n<form action=\"index.php?event=kuo_robots_editor&amp;save\" method=\"post\">\r\n<fieldset>\r\n<legend>Edit the contents</legend>\r\n<ul>\r\n<li><label for=\"robots_txt\">Contents:</label><textarea name=\"robots_txt\" id=\"robots_txt\" cols=\"30\" rows=\"10\">\'.filter_var($robots_txt,FILTER_SANITIZE_STRING).\'</textarea></li>\r\n<li><button type=\"submit\" name=\"submit\">Save</button></li>\r\n</ul>\r\n</fieldset>\r\n\r\n<h3>&mdash; OR &mdash;</h3>\r\n\r\n<fieldset>\r\n<legend>Use quick tool</legend>\r\n\r\n<ul>\r\n<li><a href=\"index.php?event=kuo_robots_editor&amp;allow=all\" rel=\"nofollow\">Allow indexing of everything</a></li>\r\n<li><a href=\"index.php?event=kuo_robots_editor&amp;allow=none\" rel=\"nofollow\">Allow indexing of nothing</a></li>\r\n</ul>\r\n\r\n<ul>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=all\" rel=\"nofollow\"><code>User-agent: *</code></a></li>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=google\" rel=\"nofollow\"><code>User-agent: Googlebot</code></a></li>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=mediapartners-google\" rel=\"nofollow\"><code>User-agent: Mediapartners-Google</code></a></li>\r\n</ul>\r\n\r\n<ul>\';\r\n\r\nforeach(glob(\'../*\', GLOB_ONLYDIR) as $dir) {\r\necho \'<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;disallow=\'.basename($dir).\'\" rel=\"nofollow\"><code>Disallow: ./\'.basename($dir).\'/</code></a></li>\';\r\n}\r\n\r\necho \'</ul></fieldset>\r\n<fieldset>\r\n\r\n</form>\r\n\r\n<p class=\"notification note\">Changes are saved directly to <code>robots.txt</code> after clicking &quot;Save&quot; button or &quot;Disallow&quot; link.</p>\r\n\r\n<p class=\"notification note center align-center\"><a href=\"../robots.txt\">Open robots.txt</a></p>\';\r\n\r\n}\r\nelse {\r\necho \'<p class=\"error\">Looks like <a href=\"../robots.txt\"><code>robots.txt</code></a> doesn\\\'t exist in <code>../robots.txt</code>. Would you like to <strong><a href=\"index.php?event=kuo_robots_editor&amp;create\" rel=\"nofollow\">create it now</a></strong>?</p>\';\r\n}\r\n\r\n\r\n}\r\n\r\n}', 'if (@txpinterface === \'admin\') {\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_robots_editor\') {\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_robots_editor\',\'Robots.txt Editor\');\r\nregister_callback(\'kuo_robots_editor_tab\',\'kuo_robots_editor\');\r\nadd_privs(\'kuo_robots_editor\',\'1\');\r\n\r\n# ACTUAL CODE BEGINS\r\nfunction kuo_robots_editor_tab() {\r\n\r\nif ((isset($_GET[\'create\'])) && (empty($_GET[\'create\']))) {\r\nfopen(\'../robots.txt\',\'w\');\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'save\'])) && (empty($_GET[\'save\']))) {\r\nif ((isset($_POST[\'submit\'])) && (empty($_POST[\'submit\'])) && (isset($_POST[\'robots_txt\']))) {\r\nfile_put_contents(\'../robots.txt\',filter_var($_POST[\'robots_txt\'],FILTER_SANITIZE_STRING),LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'disallow\'])) && (!empty($_GET[\'disallow\']))) {\r\nif (is_dir(\'../\'.$_GET[\'disallow\'].\'/\')) {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'Disallow: /\'.filter_var($_GET[\'disallow\'],FILTER_SANITIZE_STRING).\'/\',LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'allow\'])) && (!empty($_GET[\'allow\'])) && (in_array($_GET[\'allow\'],array(\'all\',\'none\')))) {\r\nif ($_GET[\'allow\'] === \'all\') {\r\nfile_put_contents(\'../robots.txt\',\'User-agent: *\'.PHP_EOL.\'Disallow: \'.PHP_EOL,LOCK_EX);\r\n}\r\nelse {\r\nfile_put_contents(\'../robots.txt\',\'User-agent: *\'.PHP_EOL.\'Disallow: /\'.PHP_EOL,LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\nif ((isset($_GET[\'target\'])) && (!empty($_GET[\'target\'])) && (in_array($_GET[\'target\'],array(\'all\',\'google\',\'mediapartners-google\')))) {\r\nif ($_GET[\'target\'] === \'all\') {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: *\',LOCK_EX);\r\n}\r\nelseif ($_GET[\'target\'] === \'google\') {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: Googlebot\',LOCK_EX);\r\n}\r\nelse {\r\nfile_put_contents(\'../robots.txt\',file_get_contents(\'../robots.txt\').PHP_EOL.\'User-agent: Mediapartners-Google\',LOCK_EX);\r\n}\r\nheader(\'location: ./index.php?event=kuo_robots_editor\');\r\ndie;\r\n}\r\n\r\npagetop(\'Robots.txt Editor\');\r\necho \'\r\n<style>\r\nh3, .note:last-of-type { font-size: 1.6em; }\r\nh3, .align-center, .note:last-of-type { text-align: center; }\r\n.note:last-of-type { padding: 3% 0; }\r\nform ul { list-style: none; padding: 10px 0 5px 0; margin: 0; }\r\nform li { padding-bottom: 5px; }\r\nlegend { font-size: 1.3em; }\r\n</style>\r\n\r\n<h2>Robots.txt Editor</h2>\';\r\n\r\nif (file_exists(\'../robots.txt\')) {\r\n\r\n$robots_txt = file_get_contents(\'../robots.txt\',0,NULL,0,1000);\r\necho \'<p>Modified: \'.date(\'Y-m-d H:i\',filemtime(\'../robots.txt\')).\'</p>\r\n<form action=\"index.php?event=kuo_robots_editor&amp;save\" method=\"post\">\r\n<fieldset>\r\n<legend>Edit the contents</legend>\r\n<ul>\r\n<li><label for=\"robots_txt\">Contents:</label><textarea name=\"robots_txt\" id=\"robots_txt\" cols=\"30\" rows=\"10\">\'.filter_var($robots_txt,FILTER_SANITIZE_STRING).\'</textarea></li>\r\n<li><button type=\"submit\" name=\"submit\">Save</button></li>\r\n</ul>\r\n</fieldset>\r\n\r\n<h3>&mdash; OR &mdash;</h3>\r\n\r\n<fieldset>\r\n<legend>Use quick tool</legend>\r\n\r\n<ul>\r\n<li><a href=\"index.php?event=kuo_robots_editor&amp;allow=all\" rel=\"nofollow\">Allow indexing of everything</a></li>\r\n<li><a href=\"index.php?event=kuo_robots_editor&amp;allow=none\" rel=\"nofollow\">Allow indexing of nothing</a></li>\r\n</ul>\r\n\r\n<ul>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=all\" rel=\"nofollow\"><code>User-agent: *</code></a></li>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=google\" rel=\"nofollow\"><code>User-agent: Googlebot</code></a></li>\r\n<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;target=mediapartners-google\" rel=\"nofollow\"><code>User-agent: Mediapartners-Google</code></a></li>\r\n</ul>\r\n\r\n<ul>\';\r\n\r\nforeach(glob(\'../*\', GLOB_ONLYDIR) as $dir) {\r\necho \'<li>Append: <a href=\"index.php?event=kuo_robots_editor&amp;disallow=\'.basename($dir).\'\" rel=\"nofollow\"><code>Disallow: ./\'.basename($dir).\'/</code></a></li>\';\r\n}\r\n\r\necho \'</ul></fieldset>\r\n<fieldset>\r\n\r\n</form>\r\n\r\n<p class=\"notification note\">Changes are saved directly to <code>robots.txt</code> after clicking &quot;Save&quot; button or &quot;Disallow&quot; link.</p>\r\n\r\n<p class=\"notification note center align-center\"><a href=\"../robots.txt\">Open robots.txt</a></p>\';\r\n\r\n}\r\nelse {\r\necho \'<p class=\"error\">Looks like <a href=\"../robots.txt\"><code>robots.txt</code></a> doesn\\\'t exist in <code>../robots.txt</code>. Would you like to <strong><a href=\"index.php?event=kuo_robots_editor&amp;create\" rel=\"nofollow\">create it now</a></strong>?</p>\';\r\n}\r\n\r\n\r\n}\r\n\r\n}', '5ef9a615bfe062aa11d89bd1396c904e', '', '', 3, 5, 1),
('kuo_search_saver', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Saves search phrases to database.', '<ul id=\"links\">\n<li id=\"kuo_search_saver\"><a href=\"#kuo_search_saver\">kuo_search_saver</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>This plugin will try to detect if your website is using Textpattern\'s inbuilt search engine. If a search is made with it, this plugin will try to save the search phrase to a database table. After deactivating or deleting this plugin the database table, which holds the saved search phrases, is deleted.</p>\n<p>Every time a search is made, the following information is saved to database table: 1) search phrase inside field called <code>phrase</code> with field type <code>VARCHAR</code>, 2) date and time inside field called <code>searched</code> with field type <code>DATETIME</code>.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_search_saver</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_search_saver</code>.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '# START OF: INSTALLATION AND DELETION PROCESSES\r\nif (@txpinterface === \'admin\') {\r\nregister_callback(\'kuo_uninstall\',\'plugin_lifecycle.kuo_search_saver\',\'disabled\');\r\nregister_callback(\'kuo_install\',\'plugin_lifecycle.kuo_search_saver\',\'enabled\');\r\nregister_callback(\'kuo_deleted\',\'plugin_lifecycle.kuo_search_saver\',\'deleted\');\r\n\r\n# IF DELETING AND/OR DISABLING\r\nfunction kuo_uninstall() {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_search_saver\').\"\");\r\n}\r\nfunction kuo_deleted() {\r\nkuo_uninstall();\r\n}\r\n\r\n# IF INSTALLING\r\nfunction kuo_install() {\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_search_saver\').\" (`id` INT NOT NULL AUTO_INCREMENT,`phrase` VARCHAR(255) CHARACTER SET `utf8` COLLATE `utf8_unicode_ci` NOT NULL,`searched` DATETIME NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\");\r\n}\r\n\r\n# END OF: INSTALLATION AND DELETION PROCESSES\r\n}\r\n\r\n# START OF: PUBLIC SIDE\r\nif ((@txpinterface === \'public\') && (isset($_GET[\'q\']))) {\r\n\r\nif ((!empty($_GET[\'q\'])) && (strlen($_GET[\'q\']) <= 255)) {\r\n\r\n$phrase = strip_tags($_GET[\'q\']);\r\n\r\nif (is_numeric($phrase)) {\r\n$phrase = filter_var($phrase,FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$phrase = filter_var($phrase,FILTER_SANITIZE_STRING);\r\n}\r\n\r\nif (empty($phrase)) {\r\n$phrase = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$phrase = NULL;\r\n}\r\n\r\n# SAVING TO DATABASE\r\n$now = date(\'Y-m-d H:i:s\');\r\nif (($phrase != NULL) && (safe_count(\'kuo_search_saver\',\"`searched`=\'$now\'\") == 0)) {\r\nsafe_insert(\'kuo_search_saver\',\"`phrase` = \'$phrase\', `searched` = \'$now\'\");\r\nunset($now);\r\nunset($phrase);\r\n}\r\n\r\n}\r\n# END OF: PUBLIC SIDE', '# START OF: INSTALLATION AND DELETION PROCESSES\r\nif (@txpinterface === \'admin\') {\r\nregister_callback(\'kuo_uninstall\',\'plugin_lifecycle.kuo_search_saver\',\'disabled\');\r\nregister_callback(\'kuo_install\',\'plugin_lifecycle.kuo_search_saver\',\'enabled\');\r\nregister_callback(\'kuo_deleted\',\'plugin_lifecycle.kuo_search_saver\',\'deleted\');\r\n\r\n# IF DELETING AND/OR DISABLING\r\nfunction kuo_uninstall() {\r\nsafe_query(\"DROP TABLE IF EXISTS \".safe_pfx(\'kuo_search_saver\').\"\");\r\n}\r\nfunction kuo_deleted() {\r\nkuo_uninstall();\r\n}\r\n\r\n# IF INSTALLING\r\nfunction kuo_install() {\r\nsafe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_search_saver\').\" (`id` INT NOT NULL AUTO_INCREMENT,`phrase` VARCHAR(255) CHARACTER SET `utf8` COLLATE `utf8_unicode_ci` NOT NULL,`searched` DATETIME NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\");\r\n}\r\n\r\n# END OF: INSTALLATION AND DELETION PROCESSES\r\n}\r\n\r\n# START OF: PUBLIC SIDE\r\nif ((@txpinterface === \'public\') && (isset($_GET[\'q\']))) {\r\n\r\nif ((!empty($_GET[\'q\'])) && (strlen($_GET[\'q\']) <= 255)) {\r\n\r\n$phrase = strip_tags($_GET[\'q\']);\r\n\r\nif (is_numeric($phrase)) {\r\n$phrase = filter_var($phrase,FILTER_SANITIZE_NUMBER_INT);\r\n}\r\nelse {\r\n$phrase = filter_var($phrase,FILTER_SANITIZE_STRING);\r\n}\r\n\r\nif (empty($phrase)) {\r\n$phrase = NULL;\r\n}\r\n\r\n}\r\nelse {\r\n$phrase = NULL;\r\n}\r\n\r\n# SAVING TO DATABASE\r\n$now = date(\'Y-m-d H:i:s\');\r\nif (($phrase != NULL) && (safe_count(\'kuo_search_saver\',\"`searched`=\'$now\'\") == 0)) {\r\nsafe_insert(\'kuo_search_saver\',\"`phrase` = \'$phrase\', `searched` = \'$now\'\");\r\nunset($now);\r\nunset($phrase);\r\n}\r\n\r\n}\r\n# END OF: PUBLIC SIDE', 'a7ff3a13af32992a8a42c6fe147b3b7a', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_sortable', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Sort elements in Write section to your liking.', '<ul id=\"links\">\n<li id=\"kuo_sortable\"><a href=\"#kuo_sortable\">kuo_sortable</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>Sort elements to your liking in Write page. This plugin will save your modifications to the database so they are remembered. Settings are saved per user. If you want to hide an element, drag it to the box with text <code>Drop here to hide / <span class=\"underline\">click this to reset</span></code> in it.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin <code>kuo_sortable</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin <code>kuo_sortable</code>.</li>\n</ol>\n<p id=\"notice\">The plugin should work with Textpattern 4.6-dev and 4.5.7.</p>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }.underline { text-decoration: underline; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '\r\n# BEGINS: ADMIN-SIDE FUNCTIONS\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_sortable_uninstall\',\'plugin_lifecycle.kuo_sortable\',\'disabled\');\r\n    register_callback(\'kuo_sortable_delete\',\'plugin_lifecycle.kuo_sortable\',\'deleted\');\r\n    register_callback(\'kuo_sortable_install\',\'plugin_lifecycle.kuo_sortable\',\'installed\');\r\n    register_callback(\'kuo_sortable_enable\',\'plugin_lifecycle.kuo_sortable\',\'enabled\');\r\n    register_callback(\'kuo_sortable_late_function\',\'admin_side\',\'head_end\');\r\n    register_callback(\'kuo_sortable_early_function\',\'admin_side\');\r\n    \r\n    define(\'KUO_SORTABLE_USERNAME\',is_logged_in()[\'name\']);\r\n\r\n    # THIS REMOVES JUST THE SETTINGS FOR THIS PLUGIN\r\n    function kuo_sortable_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(\'kuo_sortable\').\"`\",0);\r\n    }\r\n\r\n    # IF PLUGIN IS SIMPLY DELETED THEN THIS IS TRIGGERED\r\n    function kuo_sortable_delete() {\r\n        kuo_sortable_uninstall();\r\n    }\r\n\r\n    # THIS IN TURN CREATES A PLACE WHERE FILE DOWNLOAD DATA IS SAVED\r\n    function kuo_sortable_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_sortable\').\" (\r\n            `id` INT(11) NOT NULL AUTO_INCREMENT,\r\n            `place` VARCHAR(45) NOT NULL,\r\n            `order` TEXT NOT NULL,\r\n            `updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n            `username` VARCHAR(255) NOT NULL,\r\n            PRIMARY KEY (`id`),\r\n            KEY `username` (`username`)\r\n        )\",0);\r\n    }\r\n    \r\n    # IF PLUGIN IS ENABLED WILL LAUNCH INSTALL FUNCTION JUST IN CASE\r\n    function kuo_sortable_enable() {\r\n        kuo_sortable_install();\r\n    }\r\n    \r\n    # THIS WILL LAUNCH VERY EARLY TO HANDLE AJAX\r\n    function kuo_sortable_early_function($kuo_get_sortable) {\r\n        if ((isset($_SERVER[\'HTTP_X_REQUESTED_WITH\'])) && (!empty($_SERVER[\'HTTP_X_REQUESTED_WITH\'])) && (strtolower($_SERVER[\'HTTP_X_REQUESTED_WITH\']) === \'xmlhttprequest\') && \r\n        ((isset($_GET[\'event\'])) && (!empty($_GET[\'event\'])) && ($_GET[\'event\'] === \'article\') or (!isset($_GET[\'event\']))) && (is_logged_in())) {\r\n            \r\n            if ((isset($_GET[\'kuo_save_sortable\'])) && (empty($_GET[\'kuo_save_sortable\']))) {            \r\n                if (isset($_POST)) {\r\n                    $kuo_save_sortable = array_filter(filter_var_array($_POST,FILTER_SANITIZE_STRING));\r\n                    \r\n                    # LET\'S IDENTIFY TO WHAT DATABASE ROW WE SHOULD UPDATE\r\n                    if ((array_key_exists(\'kuo_sortable_preview\',$kuo_save_sortable)) && (!array_key_exists(\'kuo_sortable_left\',$kuo_save_sortable)) && (!array_key_exists(\'kuo_sortable_right\',$kuo_save_sortable))\r\n                    && (!array_key_exists(\'kuo_sortable_middle\',$kuo_save_sortable))) {\r\n                        \r\n                        var_dump($kuo_save_sortable);\r\n \r\n                         # THIS PHASE WILL FLATTEN ARRAY\r\n                        if (!empty($kuo_save_sortable)) {\r\n                            $kuo_save_sortable = call_user_func_array(\'array_merge\',$kuo_save_sortable);\r\n                        }\r\n \r\n                        # BEGINS: WRITE_PREVIEW TO DATABASE\r\n                        if (!empty($kuo_save_sortable)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_save_sortable)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_preview\', \'\".doSlash(serialize($kuo_save_sortable)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_PREVIEW TO DATABASE\r\n                        \r\n                        unset($kuo_save_sortable);\r\n                        \r\n                        die;\r\n                    }\r\n                    elseif ((!array_key_exists(\'kuo_sortable_preview\',$kuo_save_sortable)) && (array_key_exists(\'kuo_sortable_left\',$kuo_save_sortable)) or (array_key_exists(\'kuo_sortable_right\',$kuo_save_sortable))\r\n                    or (array_key_exists(\'kuo_sortable_middle\',$kuo_save_sortable))) {\r\n\r\n                        $kuo_left_array = array();\r\n                        $kuo_right_array = array();\r\n                        $kuo_middle_array = array();\r\n\r\n                        # BEGINS: FOREACH\r\n                        foreach ($kuo_save_sortable as $kuo_key => $kuo_value) {\r\n                            if (!empty($kuo_value)) {\r\n                                if ($kuo_key === \'kuo_sortable_left\') {\r\n                                    $kuo_left_array[] = $kuo_value;\r\n                                }\r\n                                elseif ($kuo_key === \'kuo_sortable_right\') {\r\n                                    $kuo_right_array[] = $kuo_value;\r\n                                }\r\n                                else {\r\n                                    $kuo_middle_array[] = $kuo_value;\r\n                                }\r\n                            }\r\n                            # ENDS: IF EMPTY\r\n                        }\r\n                        # ENDS: FOREACH\r\n                        \r\n                        # THIS PHASE WILL FLATTEN ARRAY\r\n                        if (!empty($kuo_left_array)) {\r\n                            $kuo_left_array = call_user_func_array(\'array_merge\',$kuo_left_array);\r\n                        }\r\n\r\n                        if (!empty($kuo_right_array)) {\r\n                            $kuo_right_array = call_user_func_array(\'array_merge\',$kuo_right_array);\r\n                        }\r\n\r\n                        if (!empty($kuo_middle_array)) {\r\n                            $kuo_middle_array = call_user_func_array(\'array_merge\',$kuo_middle_array);\r\n                        }\r\n                        \r\n                        # BEGINS: WRITE_LEFT TO DATABASE\r\n                        if (!empty($kuo_left_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_left_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_left\', \'\".doSlash(serialize($kuo_left_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_LEFT TO DATABASE\r\n                        \r\n                        unset($kuo_left_array);\r\n\r\n                        # BEGINS: WRITE_RIGHT TO DATABASE\r\n                        if (!empty($kuo_right_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_right_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_right\', \'\".doSlash(serialize($kuo_right_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_RIGHT TO DATABASE\r\n                        \r\n                        unset($kuo_right_array);\r\n\r\n                        # BEGINS: WRITE_MIDDLE TO DATABASE\r\n                        if (!empty($kuo_middle_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_middle_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_middle\', \'\".doSlash(serialize($kuo_middle_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_MIDDLE TO DATABASE\r\n                        \r\n                        unset($kuo_middle_array);\r\n                        \r\n                        die;\r\n\r\n                    }\r\n                    # IF AND ELSE ENDS\r\n                    die;\r\n                    # DIE AFTER QUERY\r\n                }\r\n                # IF POST ENDS\r\n                else {\r\n                    die;\r\n                }\r\n                # ELSE DIE IN CASE NO POST IS SENT\r\n            }\r\n            # BEGINS: SAVING ELEMENT THAT\'S TO BE HIDDEN\r\n            elseif ((isset($_GET[\'kuo_save_droppable\'])) && (empty($_GET[\'kuo_save_droppable\']))) {\r\n                $kuo_hidden_element = filter_var($_POST[\'kuo_hidden\'],FILTER_SANITIZE_STRING);\r\n                if ((!empty($kuo_hidden_element)) && (strpos($kuo_hidden_element,\'_\') !== false)) {\r\n                    #$kuo_hidden_element = substr($kuo_hidden_element,0,strrpos($kuo_hidden_element,\'_\'));\r\n\r\n                    if ((safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0) == 1) && (KUO_SORTABLE_USERNAME != NULL)) {      \r\n                        # SAVING CHANGES TO EXISTING ROW\r\n                        $kuo_is_saved = safe_row(\"`order`\",\"kuo_sortable\",\"`username` = \'\".doSlash(KUO_SORTABLE_USERNAME).\"\' AND `place` = \'write_hidden\'\",0);\r\n                        if (!empty($kuo_is_saved[\'order\'])) {\r\n                            $kuo_is_saved = unserialize($kuo_is_saved[\'order\']);\r\n                            if (!empty($kuo_is_saved)) {\r\n                                \r\n                                # ADDING THIS CURRENT ELEMENT TO EXISTING ARRAY\r\n                                array_push($kuo_is_saved,$kuo_hidden_element);\r\n                                \r\n                                # REMOVING DUPLICATE ELEMENTS\r\n                                $kuo_is_saved = array_unique($kuo_is_saved);\r\n                                \r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_is_saved)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0);\r\n                                \r\n                                unset($kuo_is_saved);\r\n                            }\r\n                        }\r\n                        die;\r\n                    }\r\n                    else {\r\n                        # SAVING A NEW ROW TO DATABASE\r\n                        safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_hidden\', \'\".doSlash(serialize(array($kuo_hidden_element))).\"\', \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                    }\r\n                    unset($kuo_is_saved);\r\n                    die;\r\n                }\r\n                die;\r\n            }\r\n            # ENDS: SAVING ELEMENT THAT\'S TO BE HIDDEN\r\n            # BEGINS: IF REQUESTING CURRENT POSITIONS FROM DATABASE\r\n            elseif ((isset($_GET[\'kuo_get_sortable\'])) && (!empty($_GET[\'kuo_get_sortable\'])) && (in_array($_GET[\'kuo_get_sortable\'],array(\'preview\',\'hidden\',\'left\',\'right\',\'middle\')))) {\r\n                if ($_GET[\'kuo_get_sortable\'] === \'left\') {\r\n                    $kuo_get_position = \'write_left\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'right\') {\r\n                    $kuo_get_position = \'write_right\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'middle\') {\r\n                    $kuo_get_position = \'write_middle\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'hidden\') {\r\n                    $kuo_get_position = \'write_hidden\';\r\n                }\r\n                else {\r\n                    $kuo_get_position = \'write_preview\';\r\n                }\r\n                \r\n                $kuo_get_sortable = safe_row(\"`order`\",\"kuo_sortable\",\"`username` = \'\".doSlash(KUO_SORTABLE_USERNAME).\"\' AND `place` = \'\".doSlash($kuo_get_position).\"\'\",0);\r\n                if (($kuo_get_sortable) && (!empty($kuo_get_sortable)) && (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"\'.doSlash($kuo_get_position).\'\"\',0) == 1)) {\r\n                    $kuo_get_sortable = unserialize($kuo_get_sortable[\'order\']);\r\n                    if (!empty($kuo_get_sortable)) {\r\n                        echo json_encode($kuo_get_sortable);\r\n                        die;\r\n                    }\r\n                }\r\n                die;\r\n            }\r\n            # ENDS: IF REQUESTING CURRENT POSITIONS FROM DATABASE\r\n            elseif ((isset($_GET[\'kuo_reset_droppable\'])) && (empty($_GET[\'kuo_reset_droppable\']))) {\r\n                if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\"\',0) != 0) {\r\n                    safe_delete(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\"\',0);\r\n                }\r\n                else {\r\n                    die;\r\n                }\r\n            }\r\n            # BEGINS: RESET FOR HIDDEN ELEMENTS\r\n            # ENDS: RESET FOR HIDDEN ELEMENTS\r\n            else {\r\n                die;\r\n            }\r\n        }\r\n        # IF AJAX, IF GET ETC.\r\n    }\r\n    # EARLY FUNCTION ENDS\r\n    \r\n    # THIS IS THE FUNCTION THAT IS LAUNCHED WHEN ADMIN IS VIEWING A SPECIFIC (SINGLE) FILE\r\n    function kuo_sortable_late_function() {\r\n        if (((isset($_GET[\'event\'])) && (!empty($_GET[\'event\'])) && ($_GET[\'event\'] === \'article\') or (!isset($_GET[\'event\'])) && (is_logged_in()))) {\r\n\r\n            # TO MAKE AJAX QUERIES\r\n            define(\'KUO_SORTABLE_PROCESSOR\',\'index.php?event=article\');\r\n            \r\n            if ((isset($GLOBALS[\'prefs\'][\'version\'])) && (!empty($GLOBALS[\'prefs\'][\'version\'])) && (filter_var($GLOBALS[\'prefs\'][\'version\'],FILTER_SANITIZE_NUMBER_INT) <= \'457\')) {\r\n                echo \'<script type=\"text/javascript\" src=\"https://code.jquery.com/ui/1.11.4/jquery-ui.min.js\"></script>\';\r\n            }\r\n            \r\n            echo \'<script type=\"text/javascript\">\r\n                $(function() {\r\n        \r\n                    $(\".title\").attr(\"id\",\"kuo_title\");\r\n                    $(\".body\").attr(\"id\",\"kuo_body\");\r\n                    $(\".excerpt\").attr(\"id\",\"kuo_excerpt\");\r\n\r\n                    if ($(\"p.author\").length > 0) {\r\n                        $(\".author\").attr(\"id\",\"kuo_author\");\r\n                    }\r\n\r\n                    if ($(\".nav-tertiary\").length > 0) {\r\n                        $(\".nav-tertiary\").attr(\"id\",\"kuo_nav_tertiary\");\r\n                    }\r\n\r\n                    if ($(\".action-create\").length > 0) {\r\n                        $(\".action-create\").attr(\"id\",\"kuo_action_create\");\r\n                    }\r\n\r\n                    /* DIV FOR DEACTIVATED ELEMENTS */\r\n                    $(\"div#supporting_content\").append(\"<div id=\\\"kuo_deactivated\\\" title=\\\"Drop elements here to hide them, or click the link to reset layout settings\\\"><p>Drop here to hide / <a href=\\\"#\\\">click this to reset</a></p></div>\");\r\n    \r\n                    /* WILL CREATE UNIQUE NUMERICAL VALUES FOR ID SELECTORS */\r\n                    $(\"div#main_content div.text div,div#view_modes li,div#supporting_content section,#kuo_author,#kuo_nav_tertiary,#kuo_action_create\").attr(\"id\",function(i) {\r\n                        $(this).attr(\"id\",$(this).attr(\"id\")+\"_\"+(i+1));\r\n                    });\r\n\r\n                    /* BEGINS: SAVE SORTED DATA TO DATABASE */\r\n                    function kuo_current_sortable(kuo_sortable_position) {\r\n                        $.get(\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_get_sortable=\"+kuo_sortable_position,function(kuo_get_sortable) {\r\n        \r\n                        kuo_get_sortable = JSON.parse(kuo_get_sortable);\r\n                        var kuo_get_sortable;\r\n\r\n                        /* BEGINS: IF IS EMPTY */\r\n                        if ((kuo_get_sortable != null) && (kuo_get_sortable != undefined)) {\r\n\r\n                            $.each(kuo_get_sortable,function(name_start,name_end) {\r\n                                /* CUSTOM FIELDS, META, ETC. */\r\n                                if (kuo_sortable_position === \"left\") {                        \r\n                                    /* ITS TXP 4.5.7 OR OLDER */\r\n                                    if ($(\"div#configuration_content\").length > 0) {\r\n                                        $(\"#\"+name_end).appendTo(\"div#configuration_content\");\r\n                                    }\r\n                                    /* THIS IS 4.6+ */\r\n                                    else {\r\n                                        $(\"#\"+name_end).appendTo(\"div#main_content div.text\");\r\n                                    }\r\n                                }\r\n                                /* TITLE, EXCERPT, BODY */\r\n                                else if (kuo_sortable_position === \"middle\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#main_content div.text\");\r\n                                }\r\n                                /* STATUS, COMMENTS, PUBLISH/SAVE, ETC */\r\n                                else if (kuo_sortable_position === \"right\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#supporting_content\");\r\n                                }\r\n                                /* VIEW MODES (PREVIEW) */\r\n                                else if (kuo_sortable_position === \"preview\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#view_modes ul\");\r\n                                }\r\n                            });\r\n                        }\r\n                        /* ENDS: IF IS EMPTY */\r\n                    });\r\n                }\r\n\r\n        function kuo_update_sortable() {\r\n            $(\"div#view_modes ul\").sortable({\r\n                distance: 5,\r\n                cursorAt: {left: 5},\r\n                delay: 200,\r\n                revert: true,\r\n                scroll: true,\r\n                opacity: 0.8,\r\n                cursor: \"move\",\r\n                containment: \"document\",\r\n                placeholder: \"kuo_sortable_placeholder\",\r\n                axis: \"x,y\",\r\n                tolerance: \"pointer\",\r\n                helper: \"clone\",\r\n                forceHelperSize: true,\r\n                forcePlaceholderSize: true,\r\n                zIndex: 9999,\r\n                opacity: 0.8,\r\n                update: function(event,ui) {\r\n                    var kuo_block_1 = $(this).sortable(\"toArray\");\r\n                    var kuo_final_data = {\"kuo_sortable_preview\":kuo_block_1};\r\n                    $.ajax({data:kuo_final_data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_sortable\"});\r\n                }\r\n            });\r\n            \r\n            var kuo_sortable_mixture = $(\"div#configuration_content,div#main_content div.text,div#supporting_content\");\r\n            \r\n            $(kuo_sortable_mixture).sortable({\r\n                connectWith: kuo_sortable_mixture,\r\n                distance: 5,\r\n                cursorAt: {left: 5},\r\n                delay: 200,\r\n                revert: true,\r\n                scroll: true,\r\n                opacity: 0.8,\r\n                cursor: \"move\",\r\n                containment: \"document\",\r\n                placeholder: \"kuo_sortable_placeholder\",\r\n                axis: \"x,y\",\r\n                tolerance: \"pointer\",\r\n                helper: \"clone\",\r\n                forceHelperSize: true,\r\n                forcePlaceholderSize: true,\r\n                zIndex: 9999,\r\n                opacity: 0.8,\r\n                update: function(event,ui) {\r\n                    \r\n                    var kuo_block_1 = $(\"div#main_content div.text\").sortable(\"toArray\");\r\n                    var kuo_block_2 = $(\"div#supporting_content\").sortable(\"toArray\");\r\n                    \r\n                    /* IF TXP 4.5.7 OR OLDER */\r\n                    if ($(\"div#configuration_content\").length > 0) {\r\n                        var kuo_block_3 = $(\"div#configuration_content\").sortable(\"toArray\");\r\n                        var kuo_final_data = {\"kuo_sortable_left\":kuo_block_3,\"kuo_sortable_right\":kuo_block_2,\"kuo_sortable_middle\":kuo_block_1};\r\n                    }\r\n                    /* IF 4.6 OR NEWER */\r\n                    else {\r\n                        var kuo_final_data = {\"kuo_sortable_left\":kuo_block_1,\"kuo_sortable_right\":kuo_block_2};\r\n                    }\r\n\r\n                    $.ajax({data:kuo_final_data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_sortable\"});\r\n                }\r\n            });\r\n\r\n            $(\"div[id^=\\\'kuo_deactivated\\\']\").droppable({\r\n                accept: \".ui-sortable-handle\",\r\n                hoverClass: \"kuo_droppable_hover\",\r\n                tolerance: \"touch\",\r\n                greedy: true,\r\n                helper: \"clone\",\r\n                drop: function(event,ui) {\r\n                    var data = {kuo_hidden:ui.draggable[0].id};\r\n                    ui.draggable.remove();\r\n                    $.ajax({data:data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_droppable\",success:kuo_saved});\r\n                }\r\n            });\r\n        \r\n            function kuo_saved() {\r\n                $(\"div[id^=\\\'kuo_deactivated\\\']\").effect(\"bounce\",\"slow\").stop(0);\r\n            }\r\n        }\r\n        /* ENDS: SAVE SORTED DATA TO DATABASE */\r\n\r\n        /* LAUNCH ABOVE FUNCTION */\r\n        kuo_update_sortable();\r\n\r\n        /* BEGINS: FETCHING HIDDEN ELEMENTS FROM DATABASE */\r\n        function kuo_hidden_elements() {\r\n            $.get(\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_get_sortable=hidden\",function(kuo_get_sortable) {\r\n                kuo_get_sortable = JSON.parse(kuo_get_sortable);\r\n                var kuo_get_sortable;\r\n\r\n                /* BEGINS: IF IS EMPTY */\r\n                if ((kuo_get_sortable != null) && (kuo_get_sortable != undefined)) {\r\n                    $.each(kuo_get_sortable,function(name_index,name_place) {\r\n                        $(\"#\"+name_place).hide();\r\n                    });\r\n                }\r\n                /* ENDS: IF IS EMPTY */\r\n            });\r\n        }\r\n        /* ENDS: FETCHING HIDDEN ELEMENTS FROM DATABASE */\r\n   \r\n        /* BEGINS: IF USER WANTS TO RESET LAYOUT */\r\n        $(\"div[id^=\\\'kuo_deactivated\\\'] p a\").click(function() {\r\n            var kuo_confirm = confirm(\"Do you want to reset layout settings? This page will be reloaded.\");\r\n            if (kuo_confirm) {\r\n                $.ajax({url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_reset_droppable\",success:function(result) {\r\n                    location.reload();\r\n                }});\r\n            }\r\n        });\r\n        /* ENDS: IF USER WANTS TO RESET LAYOUT */\r\n        \';\r\n    \r\n        # REDUCING CALLS TO FUNCTIONS WITH THIS\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"left\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"right\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"middle\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"preview\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0) == 1) {\r\n            echo \'kuo_hidden_elements();\';\r\n        }\r\n\r\n        echo \'\r\n            });\r\n        </script>\r\n        <style type=\"text/css\" media=\"all\">\r\n            .kuo_sortable_placeholder {\r\n                outline: 1px dashed #ccc;\r\n                background: #ffc;\r\n                padding: 10px;\r\n                -moz-border-radius: 3px;\r\n                border-radius: 3px;\r\n                min-height: 30px;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] {\r\n                border: 2px dashed #ccc;\r\n                background: #f0f0f0;\r\n                }\r\n            td#article-col-1 div[id^=\"kuo_deactivated\"] {\r\n                margin: 1em auto;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] p {\r\n                -moz-user-select: none;\r\n                -khtml-user-select: none;\r\n                -webkit-user-select: none;\r\n                user-select: none;\r\n                text-align: center;\r\n                vertical-align: middle;\r\n                padding: 25px 5px;\r\n                margin: 0 auto;\r\n                color: #999;\r\n                cursor: default;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] a {\r\n                text-decoration: underline;\r\n                color: #999;\r\n                cursor: pointer;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] a:hover,\r\n            div[id^=\"kuo_deactivated\"] a:focus,\r\n            div[id^=\"kuo_deactivated\"] a:active {\r\n                color: #555;\r\n                cursor: pointer;\r\n                }\r\n            .kuo_droppable_hover {\r\n                border-color: #555 !important;\r\n                }\r\n            /* MUST HAVE SOME HEIGHT, OTHERWISE CANNOT DROP IF THIS BLOCK IS EMPTY */\r\n            div#supporting_content,\r\n            div#main_content div.text,\r\n            div#configuration_content {\r\n                min-height: 300px;\r\n                }\r\n            </style>\';\r\n        }\r\n        # ENDS: IF EVENT IS ARTICLE OR JUST SAVED ARTICLE\r\n    }\r\n    # ENDS: LATE FUNCTION\r\n}\r\n# ENDS: ADMIN-SIDE FUNCTIONS', '\r\n# BEGINS: ADMIN-SIDE FUNCTIONS\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_sortable_uninstall\',\'plugin_lifecycle.kuo_sortable\',\'disabled\');\r\n    register_callback(\'kuo_sortable_delete\',\'plugin_lifecycle.kuo_sortable\',\'deleted\');\r\n    register_callback(\'kuo_sortable_install\',\'plugin_lifecycle.kuo_sortable\',\'installed\');\r\n    register_callback(\'kuo_sortable_enable\',\'plugin_lifecycle.kuo_sortable\',\'enabled\');\r\n    register_callback(\'kuo_sortable_late_function\',\'admin_side\',\'head_end\');\r\n    register_callback(\'kuo_sortable_early_function\',\'admin_side\');\r\n    \r\n    define(\'KUO_SORTABLE_USERNAME\',is_logged_in()[\'name\']);\r\n\r\n    # THIS REMOVES JUST THE SETTINGS FOR THIS PLUGIN\r\n    function kuo_sortable_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(\'kuo_sortable\').\"`\",0);\r\n    }\r\n\r\n    # IF PLUGIN IS SIMPLY DELETED THEN THIS IS TRIGGERED\r\n    function kuo_sortable_delete() {\r\n        kuo_sortable_uninstall();\r\n    }\r\n\r\n    # THIS IN TURN CREATES A PLACE WHERE FILE DOWNLOAD DATA IS SAVED\r\n    function kuo_sortable_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(\'kuo_sortable\').\" (\r\n            `id` INT(11) NOT NULL AUTO_INCREMENT,\r\n            `place` VARCHAR(45) NOT NULL,\r\n            `order` TEXT NOT NULL,\r\n            `updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n            `username` VARCHAR(255) NOT NULL,\r\n            PRIMARY KEY (`id`),\r\n            KEY `username` (`username`)\r\n        )\",0);\r\n    }\r\n    \r\n    # IF PLUGIN IS ENABLED WILL LAUNCH INSTALL FUNCTION JUST IN CASE\r\n    function kuo_sortable_enable() {\r\n        kuo_sortable_install();\r\n    }\r\n    \r\n    # THIS WILL LAUNCH VERY EARLY TO HANDLE AJAX\r\n    function kuo_sortable_early_function($kuo_get_sortable) {\r\n        if ((isset($_SERVER[\'HTTP_X_REQUESTED_WITH\'])) && (!empty($_SERVER[\'HTTP_X_REQUESTED_WITH\'])) && (strtolower($_SERVER[\'HTTP_X_REQUESTED_WITH\']) === \'xmlhttprequest\') && \r\n        ((isset($_GET[\'event\'])) && (!empty($_GET[\'event\'])) && ($_GET[\'event\'] === \'article\') or (!isset($_GET[\'event\']))) && (is_logged_in())) {\r\n            \r\n            if ((isset($_GET[\'kuo_save_sortable\'])) && (empty($_GET[\'kuo_save_sortable\']))) {            \r\n                if (isset($_POST)) {\r\n                    $kuo_save_sortable = array_filter(filter_var_array($_POST,FILTER_SANITIZE_STRING));\r\n                    \r\n                    # LET\'S IDENTIFY TO WHAT DATABASE ROW WE SHOULD UPDATE\r\n                    if ((array_key_exists(\'kuo_sortable_preview\',$kuo_save_sortable)) && (!array_key_exists(\'kuo_sortable_left\',$kuo_save_sortable)) && (!array_key_exists(\'kuo_sortable_right\',$kuo_save_sortable))\r\n                    && (!array_key_exists(\'kuo_sortable_middle\',$kuo_save_sortable))) {\r\n                        \r\n                        var_dump($kuo_save_sortable);\r\n \r\n                         # THIS PHASE WILL FLATTEN ARRAY\r\n                        if (!empty($kuo_save_sortable)) {\r\n                            $kuo_save_sortable = call_user_func_array(\'array_merge\',$kuo_save_sortable);\r\n                        }\r\n \r\n                        # BEGINS: WRITE_PREVIEW TO DATABASE\r\n                        if (!empty($kuo_save_sortable)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_save_sortable)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_preview\', \'\".doSlash(serialize($kuo_save_sortable)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_PREVIEW TO DATABASE\r\n                        \r\n                        unset($kuo_save_sortable);\r\n                        \r\n                        die;\r\n                    }\r\n                    elseif ((!array_key_exists(\'kuo_sortable_preview\',$kuo_save_sortable)) && (array_key_exists(\'kuo_sortable_left\',$kuo_save_sortable)) or (array_key_exists(\'kuo_sortable_right\',$kuo_save_sortable))\r\n                    or (array_key_exists(\'kuo_sortable_middle\',$kuo_save_sortable))) {\r\n\r\n                        $kuo_left_array = array();\r\n                        $kuo_right_array = array();\r\n                        $kuo_middle_array = array();\r\n\r\n                        # BEGINS: FOREACH\r\n                        foreach ($kuo_save_sortable as $kuo_key => $kuo_value) {\r\n                            if (!empty($kuo_value)) {\r\n                                if ($kuo_key === \'kuo_sortable_left\') {\r\n                                    $kuo_left_array[] = $kuo_value;\r\n                                }\r\n                                elseif ($kuo_key === \'kuo_sortable_right\') {\r\n                                    $kuo_right_array[] = $kuo_value;\r\n                                }\r\n                                else {\r\n                                    $kuo_middle_array[] = $kuo_value;\r\n                                }\r\n                            }\r\n                            # ENDS: IF EMPTY\r\n                        }\r\n                        # ENDS: FOREACH\r\n                        \r\n                        # THIS PHASE WILL FLATTEN ARRAY\r\n                        if (!empty($kuo_left_array)) {\r\n                            $kuo_left_array = call_user_func_array(\'array_merge\',$kuo_left_array);\r\n                        }\r\n\r\n                        if (!empty($kuo_right_array)) {\r\n                            $kuo_right_array = call_user_func_array(\'array_merge\',$kuo_right_array);\r\n                        }\r\n\r\n                        if (!empty($kuo_middle_array)) {\r\n                            $kuo_middle_array = call_user_func_array(\'array_merge\',$kuo_middle_array);\r\n                        }\r\n                        \r\n                        # BEGINS: WRITE_LEFT TO DATABASE\r\n                        if (!empty($kuo_left_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_left_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_left\', \'\".doSlash(serialize($kuo_left_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_LEFT TO DATABASE\r\n                        \r\n                        unset($kuo_left_array);\r\n\r\n                        # BEGINS: WRITE_RIGHT TO DATABASE\r\n                        if (!empty($kuo_right_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_right_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_right\', \'\".doSlash(serialize($kuo_right_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_RIGHT TO DATABASE\r\n                        \r\n                        unset($kuo_right_array);\r\n\r\n                        # BEGINS: WRITE_MIDDLE TO DATABASE\r\n                        if (!empty($kuo_middle_array)) {\r\n                            if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0) == 1) {\r\n                                # SAVING CHANGES TO EXISTING ROW\r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_middle_array)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0);\r\n                            }\r\n                            else {\r\n                                # SAVING A NEW ROW TO DATABASE\r\n                                safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_middle\', \'\".doSlash(serialize($kuo_middle_array)).\"\', \r\n                                \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                            }\r\n                        }\r\n                        # ENDS: WRITE_MIDDLE TO DATABASE\r\n                        \r\n                        unset($kuo_middle_array);\r\n                        \r\n                        die;\r\n\r\n                    }\r\n                    # IF AND ELSE ENDS\r\n                    die;\r\n                    # DIE AFTER QUERY\r\n                }\r\n                # IF POST ENDS\r\n                else {\r\n                    die;\r\n                }\r\n                # ELSE DIE IN CASE NO POST IS SENT\r\n            }\r\n            # BEGINS: SAVING ELEMENT THAT\'S TO BE HIDDEN\r\n            elseif ((isset($_GET[\'kuo_save_droppable\'])) && (empty($_GET[\'kuo_save_droppable\']))) {\r\n                $kuo_hidden_element = filter_var($_POST[\'kuo_hidden\'],FILTER_SANITIZE_STRING);\r\n                if ((!empty($kuo_hidden_element)) && (strpos($kuo_hidden_element,\'_\') !== false)) {\r\n                    #$kuo_hidden_element = substr($kuo_hidden_element,0,strrpos($kuo_hidden_element,\'_\'));\r\n\r\n                    if ((safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0) == 1) && (KUO_SORTABLE_USERNAME != NULL)) {      \r\n                        # SAVING CHANGES TO EXISTING ROW\r\n                        $kuo_is_saved = safe_row(\"`order`\",\"kuo_sortable\",\"`username` = \'\".doSlash(KUO_SORTABLE_USERNAME).\"\' AND `place` = \'write_hidden\'\",0);\r\n                        if (!empty($kuo_is_saved[\'order\'])) {\r\n                            $kuo_is_saved = unserialize($kuo_is_saved[\'order\']);\r\n                            if (!empty($kuo_is_saved)) {\r\n                                \r\n                                # ADDING THIS CURRENT ELEMENT TO EXISTING ARRAY\r\n                                array_push($kuo_is_saved,$kuo_hidden_element);\r\n                                \r\n                                # REMOVING DUPLICATE ELEMENTS\r\n                                $kuo_is_saved = array_unique($kuo_is_saved);\r\n                                \r\n                                safe_query(\'UPDATE `\'.safe_pfx(\'kuo_sortable\').\'` SET `order` = \"\'.doSlash(serialize($kuo_is_saved)).\'\", `updated` = \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\"\r\n                                WHERE `username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0);\r\n                                \r\n                                unset($kuo_is_saved);\r\n                            }\r\n                        }\r\n                        die;\r\n                    }\r\n                    else {\r\n                        # SAVING A NEW ROW TO DATABASE\r\n                        safe_query(\"INSERT INTO `\".safe_pfx(\'kuo_sortable\').\"` (`place`, `order`, `username`) VALUE (\'write_hidden\', \'\".doSlash(serialize(array($kuo_hidden_element))).\"\', \'\".doSlash(KUO_SORTABLE_USERNAME).\"\')\",0);\r\n                    }\r\n                    unset($kuo_is_saved);\r\n                    die;\r\n                }\r\n                die;\r\n            }\r\n            # ENDS: SAVING ELEMENT THAT\'S TO BE HIDDEN\r\n            # BEGINS: IF REQUESTING CURRENT POSITIONS FROM DATABASE\r\n            elseif ((isset($_GET[\'kuo_get_sortable\'])) && (!empty($_GET[\'kuo_get_sortable\'])) && (in_array($_GET[\'kuo_get_sortable\'],array(\'preview\',\'hidden\',\'left\',\'right\',\'middle\')))) {\r\n                if ($_GET[\'kuo_get_sortable\'] === \'left\') {\r\n                    $kuo_get_position = \'write_left\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'right\') {\r\n                    $kuo_get_position = \'write_right\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'middle\') {\r\n                    $kuo_get_position = \'write_middle\';\r\n                }\r\n                elseif ($_GET[\'kuo_get_sortable\'] === \'hidden\') {\r\n                    $kuo_get_position = \'write_hidden\';\r\n                }\r\n                else {\r\n                    $kuo_get_position = \'write_preview\';\r\n                }\r\n                \r\n                $kuo_get_sortable = safe_row(\"`order`\",\"kuo_sortable\",\"`username` = \'\".doSlash(KUO_SORTABLE_USERNAME).\"\' AND `place` = \'\".doSlash($kuo_get_position).\"\'\",0);\r\n                if (($kuo_get_sortable) && (!empty($kuo_get_sortable)) && (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"\'.doSlash($kuo_get_position).\'\"\',0) == 1)) {\r\n                    $kuo_get_sortable = unserialize($kuo_get_sortable[\'order\']);\r\n                    if (!empty($kuo_get_sortable)) {\r\n                        echo json_encode($kuo_get_sortable);\r\n                        die;\r\n                    }\r\n                }\r\n                die;\r\n            }\r\n            # ENDS: IF REQUESTING CURRENT POSITIONS FROM DATABASE\r\n            elseif ((isset($_GET[\'kuo_reset_droppable\'])) && (empty($_GET[\'kuo_reset_droppable\']))) {\r\n                if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\"\',0) != 0) {\r\n                    safe_delete(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\"\',0);\r\n                }\r\n                else {\r\n                    die;\r\n                }\r\n            }\r\n            # BEGINS: RESET FOR HIDDEN ELEMENTS\r\n            # ENDS: RESET FOR HIDDEN ELEMENTS\r\n            else {\r\n                die;\r\n            }\r\n        }\r\n        # IF AJAX, IF GET ETC.\r\n    }\r\n    # EARLY FUNCTION ENDS\r\n    \r\n    # THIS IS THE FUNCTION THAT IS LAUNCHED WHEN ADMIN IS VIEWING A SPECIFIC (SINGLE) FILE\r\n    function kuo_sortable_late_function() {\r\n        if (((isset($_GET[\'event\'])) && (!empty($_GET[\'event\'])) && ($_GET[\'event\'] === \'article\') or (!isset($_GET[\'event\'])) && (is_logged_in()))) {\r\n\r\n            # TO MAKE AJAX QUERIES\r\n            define(\'KUO_SORTABLE_PROCESSOR\',\'index.php?event=article\');\r\n            \r\n            if ((isset($GLOBALS[\'prefs\'][\'version\'])) && (!empty($GLOBALS[\'prefs\'][\'version\'])) && (filter_var($GLOBALS[\'prefs\'][\'version\'],FILTER_SANITIZE_NUMBER_INT) <= \'457\')) {\r\n                echo \'<script type=\"text/javascript\" src=\"https://code.jquery.com/ui/1.11.4/jquery-ui.min.js\"></script>\';\r\n            }\r\n            \r\n            echo \'<script type=\"text/javascript\">\r\n                $(function() {\r\n        \r\n                    $(\".title\").attr(\"id\",\"kuo_title\");\r\n                    $(\".body\").attr(\"id\",\"kuo_body\");\r\n                    $(\".excerpt\").attr(\"id\",\"kuo_excerpt\");\r\n\r\n                    if ($(\"p.author\").length > 0) {\r\n                        $(\".author\").attr(\"id\",\"kuo_author\");\r\n                    }\r\n\r\n                    if ($(\".nav-tertiary\").length > 0) {\r\n                        $(\".nav-tertiary\").attr(\"id\",\"kuo_nav_tertiary\");\r\n                    }\r\n\r\n                    if ($(\".action-create\").length > 0) {\r\n                        $(\".action-create\").attr(\"id\",\"kuo_action_create\");\r\n                    }\r\n\r\n                    /* DIV FOR DEACTIVATED ELEMENTS */\r\n                    $(\"div#supporting_content\").append(\"<div id=\\\"kuo_deactivated\\\" title=\\\"Drop elements here to hide them, or click the link to reset layout settings\\\"><p>Drop here to hide / <a href=\\\"#\\\">click this to reset</a></p></div>\");\r\n    \r\n                    /* WILL CREATE UNIQUE NUMERICAL VALUES FOR ID SELECTORS */\r\n                    $(\"div#main_content div.text div,div#view_modes li,div#supporting_content section,#kuo_author,#kuo_nav_tertiary,#kuo_action_create\").attr(\"id\",function(i) {\r\n                        $(this).attr(\"id\",$(this).attr(\"id\")+\"_\"+(i+1));\r\n                    });\r\n\r\n                    /* BEGINS: SAVE SORTED DATA TO DATABASE */\r\n                    function kuo_current_sortable(kuo_sortable_position) {\r\n                        $.get(\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_get_sortable=\"+kuo_sortable_position,function(kuo_get_sortable) {\r\n        \r\n                        kuo_get_sortable = JSON.parse(kuo_get_sortable);\r\n                        var kuo_get_sortable;\r\n\r\n                        /* BEGINS: IF IS EMPTY */\r\n                        if ((kuo_get_sortable != null) && (kuo_get_sortable != undefined)) {\r\n\r\n                            $.each(kuo_get_sortable,function(name_start,name_end) {\r\n                                /* CUSTOM FIELDS, META, ETC. */\r\n                                if (kuo_sortable_position === \"left\") {                        \r\n                                    /* ITS TXP 4.5.7 OR OLDER */\r\n                                    if ($(\"div#configuration_content\").length > 0) {\r\n                                        $(\"#\"+name_end).appendTo(\"div#configuration_content\");\r\n                                    }\r\n                                    /* THIS IS 4.6+ */\r\n                                    else {\r\n                                        $(\"#\"+name_end).appendTo(\"div#main_content div.text\");\r\n                                    }\r\n                                }\r\n                                /* TITLE, EXCERPT, BODY */\r\n                                else if (kuo_sortable_position === \"middle\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#main_content div.text\");\r\n                                }\r\n                                /* STATUS, COMMENTS, PUBLISH/SAVE, ETC */\r\n                                else if (kuo_sortable_position === \"right\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#supporting_content\");\r\n                                }\r\n                                /* VIEW MODES (PREVIEW) */\r\n                                else if (kuo_sortable_position === \"preview\") {\r\n                                    $(\"#\"+name_end).appendTo(\"div#view_modes ul\");\r\n                                }\r\n                            });\r\n                        }\r\n                        /* ENDS: IF IS EMPTY */\r\n                    });\r\n                }\r\n\r\n        function kuo_update_sortable() {\r\n            $(\"div#view_modes ul\").sortable({\r\n                distance: 5,\r\n                cursorAt: {left: 5},\r\n                delay: 200,\r\n                revert: true,\r\n                scroll: true,\r\n                opacity: 0.8,\r\n                cursor: \"move\",\r\n                containment: \"document\",\r\n                placeholder: \"kuo_sortable_placeholder\",\r\n                axis: \"x,y\",\r\n                tolerance: \"pointer\",\r\n                helper: \"clone\",\r\n                forceHelperSize: true,\r\n                forcePlaceholderSize: true,\r\n                zIndex: 9999,\r\n                opacity: 0.8,\r\n                update: function(event,ui) {\r\n                    var kuo_block_1 = $(this).sortable(\"toArray\");\r\n                    var kuo_final_data = {\"kuo_sortable_preview\":kuo_block_1};\r\n                    $.ajax({data:kuo_final_data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_sortable\"});\r\n                }\r\n            });\r\n            \r\n            var kuo_sortable_mixture = $(\"div#configuration_content,div#main_content div.text,div#supporting_content\");\r\n            \r\n            $(kuo_sortable_mixture).sortable({\r\n                connectWith: kuo_sortable_mixture,\r\n                distance: 5,\r\n                cursorAt: {left: 5},\r\n                delay: 200,\r\n                revert: true,\r\n                scroll: true,\r\n                opacity: 0.8,\r\n                cursor: \"move\",\r\n                containment: \"document\",\r\n                placeholder: \"kuo_sortable_placeholder\",\r\n                axis: \"x,y\",\r\n                tolerance: \"pointer\",\r\n                helper: \"clone\",\r\n                forceHelperSize: true,\r\n                forcePlaceholderSize: true,\r\n                zIndex: 9999,\r\n                opacity: 0.8,\r\n                update: function(event,ui) {\r\n                    \r\n                    var kuo_block_1 = $(\"div#main_content div.text\").sortable(\"toArray\");\r\n                    var kuo_block_2 = $(\"div#supporting_content\").sortable(\"toArray\");\r\n                    \r\n                    /* IF TXP 4.5.7 OR OLDER */\r\n                    if ($(\"div#configuration_content\").length > 0) {\r\n                        var kuo_block_3 = $(\"div#configuration_content\").sortable(\"toArray\");\r\n                        var kuo_final_data = {\"kuo_sortable_left\":kuo_block_3,\"kuo_sortable_right\":kuo_block_2,\"kuo_sortable_middle\":kuo_block_1};\r\n                    }\r\n                    /* IF 4.6 OR NEWER */\r\n                    else {\r\n                        var kuo_final_data = {\"kuo_sortable_left\":kuo_block_1,\"kuo_sortable_right\":kuo_block_2};\r\n                    }\r\n\r\n                    $.ajax({data:kuo_final_data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_sortable\"});\r\n                }\r\n            });\r\n\r\n            $(\"div[id^=\\\'kuo_deactivated\\\']\").droppable({\r\n                accept: \".ui-sortable-handle\",\r\n                hoverClass: \"kuo_droppable_hover\",\r\n                tolerance: \"touch\",\r\n                greedy: true,\r\n                helper: \"clone\",\r\n                drop: function(event,ui) {\r\n                    var data = {kuo_hidden:ui.draggable[0].id};\r\n                    ui.draggable.remove();\r\n                    $.ajax({data:data,type:\"POST\",url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_save_droppable\",success:kuo_saved});\r\n                }\r\n            });\r\n        \r\n            function kuo_saved() {\r\n                $(\"div[id^=\\\'kuo_deactivated\\\']\").effect(\"bounce\",\"slow\").stop(0);\r\n            }\r\n        }\r\n        /* ENDS: SAVE SORTED DATA TO DATABASE */\r\n\r\n        /* LAUNCH ABOVE FUNCTION */\r\n        kuo_update_sortable();\r\n\r\n        /* BEGINS: FETCHING HIDDEN ELEMENTS FROM DATABASE */\r\n        function kuo_hidden_elements() {\r\n            $.get(\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_get_sortable=hidden\",function(kuo_get_sortable) {\r\n                kuo_get_sortable = JSON.parse(kuo_get_sortable);\r\n                var kuo_get_sortable;\r\n\r\n                /* BEGINS: IF IS EMPTY */\r\n                if ((kuo_get_sortable != null) && (kuo_get_sortable != undefined)) {\r\n                    $.each(kuo_get_sortable,function(name_index,name_place) {\r\n                        $(\"#\"+name_place).hide();\r\n                    });\r\n                }\r\n                /* ENDS: IF IS EMPTY */\r\n            });\r\n        }\r\n        /* ENDS: FETCHING HIDDEN ELEMENTS FROM DATABASE */\r\n   \r\n        /* BEGINS: IF USER WANTS TO RESET LAYOUT */\r\n        $(\"div[id^=\\\'kuo_deactivated\\\'] p a\").click(function() {\r\n            var kuo_confirm = confirm(\"Do you want to reset layout settings? This page will be reloaded.\");\r\n            if (kuo_confirm) {\r\n                $.ajax({url:\"\'.KUO_SORTABLE_PROCESSOR.\'&kuo_reset_droppable\",success:function(result) {\r\n                    location.reload();\r\n                }});\r\n            }\r\n        });\r\n        /* ENDS: IF USER WANTS TO RESET LAYOUT */\r\n        \';\r\n    \r\n        # REDUCING CALLS TO FUNCTIONS WITH THIS\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_left\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"left\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_right\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"right\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_middle\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"middle\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_preview\"\',0) == 1) {\r\n            echo \'kuo_current_sortable(\"preview\");\';\r\n        }\r\n\r\n        if (safe_count(\'kuo_sortable\',\'`username` = \"\'.doSlash(KUO_SORTABLE_USERNAME).\'\" AND `place` = \"write_hidden\"\',0) == 1) {\r\n            echo \'kuo_hidden_elements();\';\r\n        }\r\n\r\n        echo \'\r\n            });\r\n        </script>\r\n        <style type=\"text/css\" media=\"all\">\r\n            .kuo_sortable_placeholder {\r\n                outline: 1px dashed #ccc;\r\n                background: #ffc;\r\n                padding: 10px;\r\n                -moz-border-radius: 3px;\r\n                border-radius: 3px;\r\n                min-height: 30px;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] {\r\n                border: 2px dashed #ccc;\r\n                background: #f0f0f0;\r\n                }\r\n            td#article-col-1 div[id^=\"kuo_deactivated\"] {\r\n                margin: 1em auto;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] p {\r\n                -moz-user-select: none;\r\n                -khtml-user-select: none;\r\n                -webkit-user-select: none;\r\n                user-select: none;\r\n                text-align: center;\r\n                vertical-align: middle;\r\n                padding: 25px 5px;\r\n                margin: 0 auto;\r\n                color: #999;\r\n                cursor: default;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] a {\r\n                text-decoration: underline;\r\n                color: #999;\r\n                cursor: pointer;\r\n                }\r\n            div[id^=\"kuo_deactivated\"] a:hover,\r\n            div[id^=\"kuo_deactivated\"] a:focus,\r\n            div[id^=\"kuo_deactivated\"] a:active {\r\n                color: #555;\r\n                cursor: pointer;\r\n                }\r\n            .kuo_droppable_hover {\r\n                border-color: #555 !important;\r\n                }\r\n            /* MUST HAVE SOME HEIGHT, OTHERWISE CANNOT DROP IF THIS BLOCK IS EMPTY */\r\n            div#supporting_content,\r\n            div#main_content div.text,\r\n            div#configuration_content {\r\n                min-height: 300px;\r\n                }\r\n            </style>\';\r\n        }\r\n        # ENDS: IF EVENT IS ARTICLE OR JUST SAVED ARTICLE\r\n    }\r\n    # ENDS: LATE FUNCTION\r\n}\r\n# ENDS: ADMIN-SIDE FUNCTIONS', '516c65a260500396f883feb11cd56ca6', '', '', 3, 6, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_star_rating', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'A basic star rating plugin. Requires HTML5, jQuery and Textpattern 4.6.', '<h2>Overview</h2>\n\n<p>Create star ratings for articles or other things with <strong>kuo_star_rating</strong>.</p>\n<ul>\n<li>The plugin requires jQuery.</li>\n<li>All ratings have fixed scale between 1&ndash;5 stars.</li>\n<li>Minimum rating is 1 star. Maximum is 5 stars.</li>\n<li>Votes are per IP address.</li>\n<li>An IP address can vote once.</li>\n<li>After vote is submitted, it\'s saved to database. Voter\'s IP address is saved in <code>ip2long</code> format.</li>\n</ul>\n\n<h3>Example</h3>\n\n<p><img src=\"data:image/png;base64, \" alt=\"\" height=\"150\" width=\"470\" /></p>\n\n<hr />\n\n<h3>How to install?</h3>\n\n<ol>\n<li>Install and activate <strong>kuo_star_rating</strong> plugin code like any other Textpattern plugin.</li>\n<li>Modify contents inside <code>kuo_star_rating.js</code>. Check that language strings are as you want, and find the constant named <code>KUO_ROOT_URL</code>. Replace it\'s value with your website\'s full URL address.</li>\n<li>Upload files <code>kuo_star_rating.php</code> and <code>kuo_star_rating.js</code> to your website\'s root.</li>\n<li>Put somewhere in website\'s layout JavaScript libraries: <code>&lt;script type=\"text/javascript\" src=\"&lt;txp:site_url /&gt;textpattern/vendors/jquery/jquery/jquery.js\"&gt;&lt;/script&gt;</code> and <code>&lt;script type=\"text/javascript\" src=\"&lt;site_url /&gt;kuo_star_rating.js\"&gt;&lt;/script&gt;</code> and test that they are loaded.</li>\n<li>Finally place the tag <code>&lt;txp:kuo_star_rating id=\'&lt;txp:article_id /&gt;\' /&gt;</code> to a Textpattern <strong>form</strong> or <strong>page</strong> template.</li>\n<li><em>Optional:</em> to show a separate rating summary with Schema.org markup, place the tag <code>&lt;txp:kuo_star_rating_snippet id=\'&lt;txp:article_id /&gt;\' /&gt;</code> to a Textpattern <strong>form</strong> or <strong>page</strong> template.</li>\n</ol>\n\n<hr />\n\n<h3>How to uninstall?</h3>\n\n<ul>\n<li>Remove the tag <code>&lt;txp:kuo_star_rating id=\'&lt;txp:article_id /&gt;\' /&gt;</code> and the optional tag <code>&lt;txp:kuo_star_rating_snippet id=\'&lt;txp:article_id /&gt;\' /&gt;</code> from Textpattern <strong>forms</strong> and <strong>pages</strong> templates.</li>\n<li>Delete files <code>kuo_star_rating.php</code> and <code>kuo_star_rating.js</code>.</li>\n<li>Remove the two JavaScript libraries from website\'s layout.</li>\n<li>Deactivate and/or delete <strong>kuo_star_rating</strong> from Textpattern\'s Plugins section.</li>\n</ul>\n\n<hr />\n\n<h4>Available tags</h4>\n\n<p>Rating and summary: <code>&lt;txp:kuo_star_rating id=\"???\" /&gt;</code></p>\n<p>Optional rating summary: <code>&lt;txp:kuo_star_rating_snippet id=\"???\" /&gt;</code></p>\n\n<hr />\n\n<h4>About optional rating summary</h4>\n\n<p>It has several attributes that can be used to localize it\'s output. (By default output is in English.) This is the tag with all attributes in use:</p>\n\n<p><code>&lt;txp:kuo_star_rating_snippet id=\"???\" wraptag=\"p\" wrapclass=\"kuo-star-rating-snippet\" decimal_point=\".\" schema_name=\"CreativeWork\" lan_is_rated=\"is rated\" lan_out_of=\"/\" lan_based_on=\"based on\" lan_vote=\"vote\" lan_votes=\"votes\" /&gt;</code></p>\n\n<hr />\n\n<h5>Database structure</h5>\n\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Type</th>\n<th>Extras</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>row_id</code></td>\n<td><code>INT(10)</code></td>\n<td><code>NOT NULL AUTO_INCREMENT</code>, <code>PRIMARY KEY</code></td>\n<td>&mdash;</td>\n</tr>\n\n<tr>\n<td><code>article_id</code></td>\n<td><code>INT(10)</code></td>\n<td>&mdash;</td>\n<td>Can be Textpattern article ID, not necessarily.</td>\n</tr>\n\n<tr>\n<td><code>stars_val</code></td>\n<td><code>TINYINT(1)</code></td>\n<td><code>NOT NULL DEFAULT \'1\'</code></td>\n<td>Stars between 1&ndash;5.</td>\n</tr>\n\n<tr>\n<td><code>voter_id</code></td>\n<td><code>VARBINARY(16)</code></td>\n<td>&mdash;</td>\n<td>Saved as <code>ip2long</code>.</td>\n</tr>\n\n<tr>\n<td><code>timestamp</code></td>\n<td><code>TIMESTAMP</code></td>\n<td>&mdash;</td>\n<td>Not currently used by <strong>kuo_star_rating</strong>.</td>\n</tr>\n\n</tbody>\n</table>\n\n<hr />\n\n<h6>Tested with</h6>\n\n<p>This plugin has so far been tested with Textpattern 4.6.2 and jQuery versions 1.12.4, 2.2.4 and 3.2.1.</p>', '# BEGINS: ADMIN\r\nif (@txpinterface === \'admin\') {\r\n\r\n    register_callback(\'kuo_star_rating_enabled\',\'plugin_lifecycle.kuo_star_rating\',\'enabled\');\r\n    register_callback(\'kuo_star_rating_deleted\',\'plugin_lifecycle.kuo_star_rating\',\'deleted\');\r\n\r\n    # BEGINS: FUNCTION: ENABLING\r\n    function kuo_star_rating_enabled() {\r\n        safe_create(\'kuo_star_rating\',\"\r\n            `row_id` INT(10) NOT NULL AUTO_INCREMENT,\r\n            `article_id` INT(10),\r\n            `stars_val` TINYINT(1) NOT NULL DEFAULT \'1\',\r\n            `voter_id` VARBINARY(16),\r\n            `timestamp` TIMESTAMP,\r\n            PRIMARY KEY (row_id)\r\n        \");\r\n    }\r\n    # ENDS: FUNCTION: ENABLING\r\n\r\n    # BEGINS: FUNCTION: DELETING\r\n    function kuo_star_rating_deleted() {\r\n        safe_drop(\'kuo_star_rating\');\r\n    }\r\n    # ENDS: FUNCTION: DELETING\r\n\r\n}\r\n# ENDS: ADMIN\r\n# BEGINS: PUBLIC\r\nelseif (@txpinterface === \'public\') {\r\n\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'kuo_star_rating\')->register(\'kuo_star_rating_snippet\');\r\n\r\n    # BEGINS: FUNCTION: CREATE STAR RATING TAG (HTML)\r\n    function kuo_star_rating($attributes) {\r\n        extract(lAtts(array(\'id\'=>NULL),$attributes));\r\n        if (is_numeric($id)) {\r\n            return \'<ins class=\"kuo-star-rating\" id=\"kuo-star-rating-\'.intval($id).\'\"></ins>\';\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE STAR RATING TAG (HTML)\r\n\r\n    # BEGINS: FUNCTION: CREATE RICH SNIPPET (HTML)\r\n    function kuo_star_rating_snippet($attributes) {\r\n        extract(lAtts(filter_var_array(array(\r\n            \'id\'=>NULL,\r\n            \'wraptag\'=>\'p\',\r\n            \'wrapclass\'=>\'kuo-star-rating-snippet\',\r\n            \'decimal_point\'=>\'.\',\r\n            \'schema_name\'=>\'CreativeWork\',\r\n            \'lan_is_rated\'=>\'is rated\',\r\n            \'lan_out_of\'=>\'/\',\r\n            \'lan_based_on\'=>\'based on\',\r\n            \'lan_vote\'=>\'vote\',\r\n            \'lan_votes\'=>\'votes\'\r\n        ),FILTER_SANITIZE_STRING),$attributes));\r\n\r\n        if (is_numeric($id)) {\r\n\r\n            $kuo_rating_snippet = safe_query(\'\r\n            SELECT\r\n                AVG(\'.safe_pfx(\'kuo_star_rating\').\'.stars_val) AS `rating`,\r\n                COUNT(\'.safe_pfx(\'kuo_star_rating\').\'.row_id) AS `voters`,\r\n                \'.safe_pfx(\'textpattern\').\'.Title AS `title`\r\n            FROM\r\n                `\'.safe_pfx(\'kuo_star_rating\').\'`\r\n            LEFT JOIN\r\n                `\'.safe_pfx(\'textpattern\').\'`\r\n            ON\r\n                \'.safe_pfx(\'kuo_star_rating\').\'.article_id = \'.safe_pfx(\'textpattern\').\'.ID\r\n            WHERE\r\n                \'.safe_pfx(\'kuo_star_rating\').\'.article_id = \'.intval($id).\'\r\n            \',0);\r\n\r\n            # BEGINS: SOME RESULTS FOUND\r\n            if (($kuo_rating_snippet) && (numRows($kuo_rating_snippet) == 1)) {\r\n\r\n                $kuo_rating_snippet = nextRow($kuo_rating_snippet);\r\n\r\n                # BEGINS: NEEDS AT LEAST ONE VOTE\r\n                if (!empty($kuo_rating_snippet[\'voters\'])) {\r\n\r\n                    # MORE THAN ONE VOTE\r\n                    if ($kuo_rating_snippet[\'voters\'] > 1) {\r\n                        $kuo_lan_vote_or_votes = $lan_votes;\r\n                    }\r\n                    # NO VOTES OR ONE VOTE\r\n                    else {\r\n                        $kuo_lan_vote_or_votes = $lan_vote;\r\n                    }\r\n\r\n                    if ((isset($kuo_rating_snippet[\'title\'])) && (!empty($kuo_rating_snippet[\'title\']))) {\r\n                        $kuo_title = \'&quot;<span itemprop=\"name\">\'.htmlentities($kuo_rating_snippet[\'title\']).\'</span>&quot; \'.$lan_is_rated;\r\n                    }\r\n                    else {\r\n                        $kuo_title = NULL;\r\n                    }\r\n\r\n                    return \'<\'.$wraptag.\' class=\"\'.$wrapclass.\'\" itemscope itemtype=\"http://schema.org/\'.$schema_name.\'\">\r\n                    <span itemprop=\"aggregateRating\" itemscope itemtype=\"http://schema.org/AggregateRating\">\r\n                    <meta itemprop=\"worstRating\" content=\"1\" />\'.$kuo_title.\' <span itemprop=\"ratingValue\">\'.\r\n                    str_replace(\'.\',$decimal_point,round($kuo_rating_snippet[\'rating\'],2)).\r\n                    \'</span> \'.$lan_out_of.\' 5, \'.$lan_based_on.\' <span itemprop=\"reviewCount\">\'.$kuo_rating_snippet[\'voters\'].\'</span> \'.$kuo_lan_vote_or_votes.\r\n                    \'.</span></\'.$wraptag.\'>\';\r\n                }\r\n                # ENDS: NEEDS AT LEAST ONE VOTE\r\n            }\r\n            # ENDS: SOME RESULTS FOUND\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE RICH SNIPPET (HTML)\r\n\r\n}\r\n# ENDS: PUBLIC\r\n', '# BEGINS: ADMIN\r\nif (@txpinterface === \'admin\') {\r\n\r\n    register_callback(\'kuo_star_rating_enabled\',\'plugin_lifecycle.kuo_star_rating\',\'enabled\');\r\n    register_callback(\'kuo_star_rating_deleted\',\'plugin_lifecycle.kuo_star_rating\',\'deleted\');\r\n\r\n    # BEGINS: FUNCTION: ENABLING\r\n    function kuo_star_rating_enabled() {\r\n        safe_create(\'kuo_star_rating\',\"\r\n            `row_id` INT(10) NOT NULL AUTO_INCREMENT,\r\n            `article_id` INT(10),\r\n            `stars_val` TINYINT(1) NOT NULL DEFAULT \'1\',\r\n            `voter_id` VARBINARY(16),\r\n            `timestamp` TIMESTAMP,\r\n            PRIMARY KEY (row_id)\r\n        \");\r\n    }\r\n    # ENDS: FUNCTION: ENABLING\r\n\r\n    # BEGINS: FUNCTION: DELETING\r\n    function kuo_star_rating_deleted() {\r\n        safe_drop(\'kuo_star_rating\');\r\n    }\r\n    # ENDS: FUNCTION: DELETING\r\n\r\n}\r\n# ENDS: ADMIN\r\n# BEGINS: PUBLIC\r\nelseif (@txpinterface === \'public\') {\r\n\r\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'kuo_star_rating\')->register(\'kuo_star_rating_snippet\');\r\n\r\n    # BEGINS: FUNCTION: CREATE STAR RATING TAG (HTML)\r\n    function kuo_star_rating($attributes) {\r\n        extract(lAtts(array(\'id\'=>NULL),$attributes));\r\n        if (is_numeric($id)) {\r\n            return \'<ins class=\"kuo-star-rating\" id=\"kuo-star-rating-\'.intval($id).\'\"></ins>\';\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE STAR RATING TAG (HTML)\r\n\r\n    # BEGINS: FUNCTION: CREATE RICH SNIPPET (HTML)\r\n    function kuo_star_rating_snippet($attributes) {\r\n        extract(lAtts(filter_var_array(array(\r\n            \'id\'=>NULL,\r\n            \'wraptag\'=>\'p\',\r\n            \'wrapclass\'=>\'kuo-star-rating-snippet\',\r\n            \'decimal_point\'=>\'.\',\r\n            \'schema_name\'=>\'CreativeWork\',\r\n            \'lan_is_rated\'=>\'is rated\',\r\n            \'lan_out_of\'=>\'/\',\r\n            \'lan_based_on\'=>\'based on\',\r\n            \'lan_vote\'=>\'vote\',\r\n            \'lan_votes\'=>\'votes\'\r\n        ),FILTER_SANITIZE_STRING),$attributes));\r\n\r\n        if (is_numeric($id)) {\r\n\r\n            $kuo_rating_snippet = safe_query(\'\r\n            SELECT\r\n                AVG(\'.safe_pfx(\'kuo_star_rating\').\'.stars_val) AS `rating`,\r\n                COUNT(\'.safe_pfx(\'kuo_star_rating\').\'.row_id) AS `voters`,\r\n                \'.safe_pfx(\'textpattern\').\'.Title AS `title`\r\n            FROM\r\n                `\'.safe_pfx(\'kuo_star_rating\').\'`\r\n            LEFT JOIN\r\n                `\'.safe_pfx(\'textpattern\').\'`\r\n            ON\r\n                \'.safe_pfx(\'kuo_star_rating\').\'.article_id = \'.safe_pfx(\'textpattern\').\'.ID\r\n            WHERE\r\n                \'.safe_pfx(\'kuo_star_rating\').\'.article_id = \'.intval($id).\'\r\n            \',0);\r\n\r\n            # BEGINS: SOME RESULTS FOUND\r\n            if (($kuo_rating_snippet) && (numRows($kuo_rating_snippet) == 1)) {\r\n\r\n                $kuo_rating_snippet = nextRow($kuo_rating_snippet);\r\n\r\n                # BEGINS: NEEDS AT LEAST ONE VOTE\r\n                if (!empty($kuo_rating_snippet[\'voters\'])) {\r\n\r\n                    # MORE THAN ONE VOTE\r\n                    if ($kuo_rating_snippet[\'voters\'] > 1) {\r\n                        $kuo_lan_vote_or_votes = $lan_votes;\r\n                    }\r\n                    # NO VOTES OR ONE VOTE\r\n                    else {\r\n                        $kuo_lan_vote_or_votes = $lan_vote;\r\n                    }\r\n\r\n                    if ((isset($kuo_rating_snippet[\'title\'])) && (!empty($kuo_rating_snippet[\'title\']))) {\r\n                        $kuo_title = \'&quot;<span itemprop=\"name\">\'.htmlentities($kuo_rating_snippet[\'title\']).\'</span>&quot; \'.$lan_is_rated;\r\n                    }\r\n                    else {\r\n                        $kuo_title = NULL;\r\n                    }\r\n\r\n                    return \'<\'.$wraptag.\' class=\"\'.$wrapclass.\'\" itemscope itemtype=\"http://schema.org/\'.$schema_name.\'\">\r\n                    <span itemprop=\"aggregateRating\" itemscope itemtype=\"http://schema.org/AggregateRating\">\r\n                    <meta itemprop=\"worstRating\" content=\"1\" />\'.$kuo_title.\' <span itemprop=\"ratingValue\">\'.\r\n                    str_replace(\'.\',$decimal_point,round($kuo_rating_snippet[\'rating\'],2)).\r\n                    \'</span> \'.$lan_out_of.\' 5, \'.$lan_based_on.\' <span itemprop=\"reviewCount\">\'.$kuo_rating_snippet[\'voters\'].\'</span> \'.$kuo_lan_vote_or_votes.\r\n                    \'.</span></\'.$wraptag.\'>\';\r\n                }\r\n                # ENDS: NEEDS AT LEAST ONE VOTE\r\n            }\r\n            # ENDS: SOME RESULTS FOUND\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION: CREATE RICH SNIPPET (HTML)\r\n\r\n}\r\n# ENDS: PUBLIC\r\n', '930b88aad59ca9da8d80927c1200c908', '', '', 0, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_theme', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.2', 'Easily install and uninstall certain front-side themes.', '<ul id=\"links\">\n<li id=\"kuo_theme\"><a href=\"#kuo_theme\">kuo_theme</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>With this plugin a new Extensions tab is added to your Textpattern\'s admin panel. It\'s visible to users who have Admin privileges. Such users can easily install and uninstall certain front-end themes.</p>\n<h3>Installing this plugin</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_theme</code>.</li>\n</ol>\n<h3>Uninstalling this plugin</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_theme</code>.</li>\n<li><span>[optional]</span> Delete <code>themes</code> folder from your website\'s root.</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '\r\nif (@txpinterface === \'admin\') {\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_theme\') {\r\n    header(\'location: ./index.php?event=kuo_theme\');\r\n    die;\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_theme\',\'kuo_theme\');\r\nregister_callback(\'kuo_theme_tab\',\'kuo_theme\');\r\nadd_privs(\'kuo_theme\',\'1\');\r\n\r\n##########################\r\n\r\n# BEGINNING: ACTUAL THEME FUNCTIONS TAB\r\nfunction kuo_theme_tab() {\r\n\r\n# SOME BASIC SETTINGS\r\ndefine(\'KUO_THEME_TAB\',\'../textpattern/index.php?event=kuo_theme\');\r\ndefine(\'KUO_THEME_FOLDER\',\'../themes/\');\r\ndefine(\'KUO_ACTIVE_CSS_CLASS\',\' class=\"kuo_theme_open\"\');\r\ndefine(\'KUO_DEFAULT_THEME_FEED\',\'http://kuopassa.net/kuo_themes_list.txt\');\r\ndefine(\'KUO_NO_SCREENSHOT_IMG\',\'<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAADICAYAAAGxEod0AAAAB3RJTUUH3woPEgM3sV2NfwAAAAlw\r\nSFlzAAAchAAAHIQBhemxiwAAAARnQU1BAACxjwv8YQUAAAZGSURBVHja7d3RihtHFATQ1aKn/emx\r\nvnxCh2CcTVyxKh5x3T4HBCHYXC7tmtIK0Xt7e3v7ePvGeZ5f//t2u/30/3/VjPe3TVhkmvu3/86W\r\nK3LxihnbnMg2i9xf8Wj0+H3CNot4ak2zzSKeWtNss4in1jTbLOKpNc02i3hqTbPNIutsvn5AN+UJ\r\n1MzY5kQsMs194qO0mbHNiWyzyH3iG0CP3x14ak2zzSKeWtNss4in1jTbLOKpNc02i3hqTbPNIn/7\r\ngO6zz0f89S99eqq0f96Mf9rmX9YuHMgw9+9FZ/lZkTbjx/+8hAzjQIa57/LuZJcZEjKMAxnGu6xh\r\nMyRkGAcyjHdZw2ZIyDAOZBgHMoy3vcNmSMgwDmQYb3uHzZCQYRzIMN5lDZshIcM4kGG++0W53d/N\r\nTJ0hIcM4kGEcyDAOZBgHMowDGeY+8afV33mGhAzjQIZxIMM4kGEcyDD3XT6U22WGhAzjQIZxIMM4\r\nkGEcyDA+yxo2Q0KGcSDDOJBhHMgwDmQYn2UNmyEhwziQYRzIMA5kGAcyjAMZxoeLw2ZIyDAOZBgH\r\nMowDGcaBDOPDxWEzJGQYBzKMAxnGgQzjQIbxWdawGRIyjAMZxoEM40CGcSDDxF+9+j3p3cO/Dgmf\r\n6ZhhxuQZnlgQCAgEAgKBgEAgIBDcn/2pfnn20wMzzPhVZ2gQCAQEAgGBQEAgEBAI7rt8Z8YMM66Y\r\noUEgEBAIBAQCAYFAQCAQEAh8WdEMMwINAoGAQCAgEAgIBAICgS8rmmFGoEEgEBAIBAQCAYFAQCDw\r\nXSwzzAg0CAQCAoGAQCAgEAgIBL6LZYYZgQaBQEAgEBAIBAQCAYFAQCDwZUUzzAg0CAQCAoGAQCAg\r\nEAgI/IePZ17HcXyc5/nUywwzftUZGgQCAYFAQCAQEAgEBAIBgUBAIBAQCAQEAgGBQEAgEBAIbn99\r\n6evH/8ImN+aZYcaP0CAQCAgEAgKBgEAgIBAICAQCAoGAQCAgEAgIBAICwfv6fsozr+M4/vwOzDMv\r\nM8z4VWdoEAgEBAIBgUBAIBAQCAQEAgGBQEAgEBAIBAQCAYFAQCBwcZwZZgQaBAIBgUBAIBAQCAQE\r\nAgGBQEAgEBAIBAQCAYFAQCBwcZwZZrg4DjoCAoGAQCAgEAgIBAICgYBAICAQCAgEAgKBgEAgIBC4\r\nOM4MMwINAoGAQCAgEAgIBAICgYBAICAQCAgEAgKBgEAgIBC4OM4MM1wcBx0BgUBAIBAQCAQEAgGB\r\nQEAgEBAIBAQCAYFAQCAQEAhcHGeGGYEGgUBAIBAQCAQEAgGBQEAgEBAIBAQCAYFAQCAQEAjWl1Oe\r\n+i5WY13Y9eXLl2sXKb5nYw972MMe9uh5hwVARYEAUFEgAFQUCAAVBQJARYEAUFEgAFQUCAAVBQJA\r\nRYEAUFEgAFRux3FcfhfW4/G4fJF174s97GEPe9jjdXs8/YttqyEvuDjs2V9yag972MMe9vh/fIQF\r\nQEWBAFBRIABUFAgAFQUCQEWBAFBRIABUFAgAFQUCQEWBAFBRIABU3td9KVe/1sVh6+6XK1/2sIc9\r\n7GGP1+7hJxAAKgoEgIoCAaCiQACoKBAAKgoEgIoCAaCiQACoKBAAKgoEgIoCAaByO47j4+ohj8fj\r\n8kXWvS/2sIc97GGP1+1xO8/z8gJZF3tdbV3sZQ972MMe9njdHj7CAqCiQACoKBAAKgoEgIoCAaCi\r\nQACoKBAAKgoEgIoCAaCiQACoKBAAKu/rvpSrX+visHX3y5Uve9jDHvawx2v38BMIABUFAkBFgQBQ\r\nUSAAVBQIABUFAkBFgQBQUSAAVBQIABUFAkBFgQBQuR3H8XH1kMfjcfki694Xe9jDHvawx+v2uJ3n\r\neXmBrIu9rrYu9rKHPexhD3u8bg8fYQFQUSAAVBQIABUFAkBFgQBQUSAAVBQIABUFAkBFgQBQUSAA\r\nVBQIAJX3dV/K1a91cdi6++XKlz3sYQ972OO1e/gJBICKAgGgokAAqCgQACoKBICKAgGgokAAqCgQ\r\nACoKBICKAgGgokAAqNyO4/i4esjj8bh8kXXviz3sYQ972ON1e9zO87y8QNbFXldbF3vZwx72sIc9\r\nXreHj7AAqCgQACoKBICKAgGgokAAqCgQACoKBICKAgGgokAAqCgQACoKBIDKH+YGlwN0DPgrAAAA\r\nAElFTkSuQmCC\" alt=\"No image\" height=\"200\" width=\"400\" />\');\r\n\r\n# SOME BASIC FUNCTIONS\r\nif ((isset($_GET[\'fix\'])) && (!empty($_GET[\'fix\'])) && (in_array($_GET[\'fix\'],array(\'folder\')))) {\r\n    # CREATING THE FOLDER FOR THEMES\r\n    if (!is_dir(KUO_THEME_FOLDER)) {\r\n        mkdir(KUO_THEME_FOLDER,0755,true);\r\n    }\r\n    header(\'location:\'.KUO_THEME_TAB);\r\n    die;\r\n}\r\nelseif ((isset($_GET[\'fix\'])) && (empty($_GET[\'fix\']))) {\r\n    header(\'location:\'.KUO_THEME_TAB);\r\n    die;\r\n}\r\n\r\n# INCLUDING HTML FOR THE TAB\r\npagetop(\'Themes\');\r\n\r\n# STARTS: IF INSTALLING A THEME\r\nif ((isset($_GET[\'install\'])) && (!empty($_GET[\'install\'])) && (base64_encode(base64_decode($_GET[\'install\'],true)) === $_GET[\'install\']) && (!file_exists(base64_decode($_GET[\'install\']).\'/installed.txp\'))) {\r\n    # FETCHING THEME DATA FROM \"THEMES\" FOLDER\r\n    if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (file_exists(base64_decode($_GET[\'install\']))) && (file_exists(base64_decode($_GET[\'install\']).\'/textpattern.txp\')) && (is_readable(base64_decode($_GET[\'install\']).\'/textpattern.txp\'))) {\r\n        $theme_folder_name = explode(\'/\',str_replace(KUO_THEME_FOLDER,\'\',base64_decode($_GET[\'install\'])));\r\n        $theme_folder_name = $theme_folder_name[0];\r\n\r\n        # LOOPING THROUGH THEME FOLDERS AND FILES INSIDE THEM\r\n        \r\n        # FIRST IN LINE IS \"PAGES\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/pages/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/pages/*.txp\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    $template_name = $theme_folder_name.\'-\'.str_replace(\'.txp\',\'\',basename($theme_file));\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_page\').\'` (name, user_html) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n        }\r\n        \r\n        # SECOND IS \"FORMS\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/forms/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/forms/*.txp\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    # FORMS HAVE TYPES, SO LETS FIGURE OUT IF THIS TEMPLATE NAME HAS THAT INCLUDED\r\n                    $template_type = explode(\'.\',str_replace(\'.txp\',\'\',basename($theme_file)));\r\n                    \r\n                    # THIS TIME TEMPLATE NAME IS FROM ABOVE EXPLOSION\r\n                    $template_name = $theme_folder_name.\'-\'.$template_type[1];\r\n                    \r\n                    # AND TEMPLATE TYPE IS THE EL NUMERO 0\r\n                    $template_type = $template_type[0];\r\n\r\n                    # MUST CHECK THAT THIS FORM TYPE IS OK\r\n                    # IF NOT OK THEN FORM IS MISCELLANEOUS\r\n                    if (!in_array($template_type,array(\'article\',\'misc\',\'comment\',\'category\',\'file\',\'link\',\'section\'))) {\r\n                        $template_type = \'misc\';\r\n                    }\r\n\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_form\',\'`name` = \"\'.$template_name.\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_form\').\'` (name, type, Form) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_type).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                        unset($template_type);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n            unset($template_name);\r\n        }\r\n\r\n        # THIRD AND FINAL IS \"STYLES\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/styles/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/styles/*.css\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    $template_name = $theme_folder_name.\'-\'.str_replace(\'.css\',\'\',basename($theme_file));\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_css\').\'` (name, css) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n            unset($template_name);\r\n        }\r\n\r\n        # IF THEME IS INSTALLED, IT\'S MARKED WITH FILE \"installed.txp\" WHICH HAS THE DATE\r\n        file_put_contents(base64_decode($_GET[\'install\']).\'/installed.txp\',date(\'Y-m-d H:i:s\'));\r\n\r\n        # THEN USER IS REDIRECTED TO MAIN PAGE WITH SUCCESS MESSAGE\r\n        header(\'location: index.php?event=kuo_theme&message=3\');\r\n        die;\r\n    }\r\n    else {\r\n        # SOME KIND OF ERROR\r\n        header(\'location: index.php?event=kuo_theme&message=11\');\r\n        die;\r\n    }\r\n}\r\n# ENDS: IF INSTALLING A THEME\r\n\r\n# STARTS: UNINSTALLING A THEME\r\nif ((isset($_GET[\'uninstall\'])) && (!empty($_GET[\'uninstall\'])) && (base64_encode(base64_decode($_GET[\'uninstall\'],true)) === $_GET[\'uninstall\']) && (file_exists(base64_decode($_GET[\'uninstall\']).\'/installed.txp\'))) {\r\n    $theme_folder_name = explode(\'/\',str_replace(KUO_THEME_FOLDER,\'\',base64_decode($_GET[\'uninstall\'])));\r\n    $theme_folder_name = $theme_folder_name[0];\r\n\r\n    # LOOPING THROUGH THEME FOLDERS AND FILES INSIDE THEM\r\n        \r\n    # FIRST IN LINE IS \"PAGES\" FOLDER\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/pages/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/pages/*.txp\') as $theme_file) {\r\n            $template_name = $theme_folder_name.\'-\'.str_replace(\'.txp\',\'\',basename($theme_file));\r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {\r\n                safe_delete(\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # SECOND IS \"FORMS\" FOLDER\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/forms/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/forms/*.txp\') as $theme_file) {\r\n            $template_type = explode(\'.\',str_replace(\'.txp\',\'\',basename($theme_file)));\r\n            $template_name = $theme_folder_name.\'-\'.$template_type[1];\r\n            \r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_form\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {                \r\n                # AND TEMPLATE TYPE IS THE EL NUMERO 0\r\n                $template_type = $template_type[0];\r\n\r\n                # MUST CHECK THAT THIS FORM TYPE IS OK\r\n                # IF NOT OK THEN FORM IS MISCELLANEOUS\r\n                if (!in_array($template_type,array(\'article\',\'misc\',\'comment\',\'category\',\'file\',\'link\',\'section\'))) {\r\n                    $template_type = \'misc\';\r\n                }\r\n\r\n                safe_delete(\'txp_form\',\'`name` = \"\'.doSlash($template_name).\'\" AND `type` = \"\'.doSlash($template_type).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # THIRD AND FINAL IS \"STYLES\"\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/styles/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/styles/*.css\') as $theme_file) {\r\n            $template_name = $theme_folder_name.\'-\'.str_replace(\'.css\',\'\',basename($theme_file));\r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {\r\n                safe_delete(\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # AFTER REMOVING PAGES, FORMS AND STYLES FROM DATABASE\r\n    # IT\'S TIME TO REMOVE \"install.txp\" SO THEME CAN BE REINSTALLED\r\n    unlink(base64_decode($_GET[\'uninstall\']).\'/installed.txp\');\r\n\r\n    # THEN USER IS REDIRECTED TO MAIN PAGE\r\n    header(\'location: index.php?event=kuo_theme&message=2\');\r\n    die;\r\n}\r\n# ENDS: UNINSTALLING A THEME\r\n\r\n# STARTS: UPLOADING A ZIPPED THEME\r\nif ((isset($_FILES[\'upload\'])) && (!empty($_FILES[\'upload\'])) && (isset($_POST[\'submit\'])) && (empty($_POST[\'submit\']))) {\r\n\r\n    # CHECKING IF ERRORS\r\n    if ((!isset($_FILES[\'upload\'][\'error\'])) or (is_array($_FILES[\'upload\'][\'error\']))) {\r\n        header(\'location: index.php?event=kuo_theme&message=5\');\r\n        die;\r\n    }\r\n\r\n    # PRELIMINARY TYPE CHECK\r\n    if (!in_array(str_replace(\'\"\',\'\',$_FILES[\'upload\'][\'type\']),array(\'application/zip\',\'application/octet-stream\',\'application/x-zip\',\'application/x-zip-compressed\'))) {\r\n        header(\'location: index.php?event=kuo_theme&message=6\');\r\n        die;\r\n    }\r\n\r\n\r\n    $kuo_zip_name = basename($_FILES[\'upload\'][\'name\']);\r\n    $kuo_zip_theme_name = substr($kuo_zip_name,0,-4);\r\n    $kuo_zip_size = basename($_FILES[\'upload\'][\'size\']);\r\n\r\n    # FIRST WE MUST CHECK IF A THEME WITH SAME NAME EXISTS OR NOT\r\n    if (file_exists(KUO_THEME_FOLDER.$kuo_zip_theme_name)) {\r\n        header(\'location: index.php?event=kuo_theme&message=1\');\r\n        die;\r\n    }\r\n\r\n    # IF THE THEME IS EITHER TOO LARGE OR SUSPICIOUSLY SMALL\r\n    # 1. IT MUST NOT BE EMPTY\r\n    # 2. IT MUST NOT BE 100: TOO SMALL TO BE A VALID THEME\r\n    # 3. IT MUST NOT BE OVER 10 MEGABYTES\r\n    if ((empty($kuo_zip_size)) or ($kuo_zip_size <= 100) or ($kuo_zip_size >= 10485760)) {\r\n        header(\'location: index.php?event=kuo_theme&message=4\');\r\n        die;\r\n    }\r\n\r\n    # CONTINUING TO CHECK UPLOADED FILE\r\n    $kuo_zip_resource = zip_open($_FILES[\'upload\'][\'tmp_name\']);\r\n    if (is_resource($kuo_zip_resource)) {\r\n        # CHECKING THAT ZIP HAS \"textpattern.txp\" ETC.\r\n        $kuo_count_textpattern_txt = 0;\r\n        while ($zip_entry = zip_read($kuo_zip_resource)) {\r\n            if (basename(zip_entry_name($zip_entry)) === \'textpattern.txp\') {\r\n                $kuo_count_textpattern_txt++;\r\n            }\r\n        }\r\n\r\n        # COUNTER IS ZERO SO THIS IS NOT A VALID THEME\r\n        if (empty($kuo_count_textpattern_txt)) {\r\n            header(\'location: index.php?event=kuo_theme&message=10\');\r\n            die;\r\n        }\r\n        unset($kuo_count_textpattern_txt);\r\n        zip_close($kuo_zip_resource);\r\n    }\r\n    else {\r\n        header(\'location: index.php?event=kuo_theme&message=7\');\r\n        die;\r\n    }\r\n\r\n    # CREATING FOLDER FOR THEME\r\n    if (!is_dir(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'/\')) {\r\n        mkdir(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'/\',0755);\r\n    }\r\n\r\n    # PROCESSING ZIP ARCHIVE CONTENTS TO ABOVE FOLDER\r\n    if (move_uploaded_file($_FILES[\'upload\'][\'tmp_name\'],KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\')) {\r\n        $kuo_actual_zip = new ZipArchive();\r\n        $kuo_actual_zip_check = $kuo_actual_zip->open(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\');\r\n        if ($kuo_actual_zip_check === true) {\r\n            $kuo_actual_zip->extractTo(KUO_THEME_FOLDER);\r\n            $kuo_actual_zip->close();\r\n            unset($kuo_actual_zip);\r\n            unset($kuo_actual_zip_check);\r\n\r\n            # DELETING TEMP CONTENT\r\n            if (file_exists(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\')) {\r\n                unlink(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\');\r\n            }\r\n        }\r\n\r\n        # IF EXTRACTING THEME WAS OK = SUCCESS\r\n        header(\'location: index.php?event=kuo_theme&message=8\');\r\n        die;\r\n    }\r\n    else {\r\n        # IF AN ERROR DURING EXTRACTION\r\n        header(\'location: index.php?event=kuo_theme&message=9\');\r\n        die;\r\n    }\r\n}\r\n# ENDS: UPLOADING A ZIPPED THEME\r\n\r\necho \'<style type=\"text/css\" media=\"all\">\r\n#kuo_themes_area { font-family: Arial, Helvetica, sans-serif; color: #333; font-size: 1em; }\r\n#kuo_themes_area h2, #kuo_themes_area h3,#kuo_themes_area h4,#kuo_themes_area h5, #kuo_themes_area h6 { font: 3em Arial, Helvetica, sans-serif; }\r\n#kuo_themes_area p, #kuo_themes_area dl { font-size: 1.3em; margin: 0; padding: 0 0 10px 0; }\r\n#kuo_themes_area .kuo_theme_sort .kuo_theme_open { font-weight: bold; }\r\n#kuo_themes_area #kuo_themes_sidebar,\r\n#kuo_themes_area #kuo_themes_main { display: inline; float: left; }\r\n#kuo_themes_area #kuo_themes_sidebar { width: 23%; }\r\n#kuo_themes_area #kuo_themes_main { width: 75%; margin-left: 2%; }\r\n#kuo_themes_area .kuo_themes_clear { display: block; clear: both; }\r\n#kuo_themes_area #kuo_themes_sidebar dl {\r\nborder: 1px solid #eee;\r\npadding: 10px;\r\nmargin-bottom: 10px;\r\n}\r\n#kuo_themes_sidebar h2 {\r\npadding: 0;\r\nmargin: 0 0 10px 0;\r\n}\r\n#kuo_themes_area dl.kuo_theme {\r\ndisplay: inline-block;\r\nwidth: 48.5%;\r\nbackground: -webkit-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: -o-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: -moz-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: linear-gradient(#fcfcfc,#f0f0f0);\r\npadding: 0;\r\nmargin: 0 1% 1% 0;\r\ntext-align: center;\r\nborder: 1px solid #ccc;\r\n}\r\n#kuo_themes_area dl.kuo_theme dt {\r\nfont-size: 1.3em;\r\nfont-weight: normal;\r\npadding: 2% 0 10px 0;\r\nmargin-bottom: 10px;\r\nbackground: -webkit-linear-gradient(#ccc,#fcfcfc);\r\nbackground: -o-linear-gradient(#ccc,#fcfcfc);\r\nbackground: -moz-linear-gradient(#ccc,#fcfcfc);\r\nbackground: linear-gradient(#eee,#fcfcfc);\r\ncolor: #333;\r\n}\r\n#kuo_themes_area dl.kuo_theme dd {\r\npadding: 0;\r\nmargin: 0;\r\n}\r\n#kuo_themes_area dl.kuo_theme img {\r\nwidth: 100%;\r\nheight: auto;\r\nmax-width: 380px;\r\nmax-height: 180px;\r\nmargin: 10px 0;\r\nborder: 5px solid #EBEBEB;\r\npadding: 5px;\r\nbackground: #fcfcfc;\r\n}\r\n#kuo_themes_area dl.kuo_theme img:hover {\r\nborder: 0;\r\npadding: 0;\r\nmax-width: 400px;\r\nmax-height: 200px;\r\n}\r\n#kuo_themes_area dl.kuo_theme dd.kuo_links {\r\npadding: 17px 0;\r\nbackground: #fff;\r\nborder-top: 1px solid #ccc;\r\n}\r\n#kuo_themes_area dd.kuo_links span {\r\ndisplay: none;\r\n}\r\n#kuo_themes_area dd.kuo_links a {\r\ntext-decoration: none;\r\npadding: 5px 15px;\r\ncolor: #ffc;\r\nbackground: #1A95D0;\r\nbackground: -webkit-linear-gradient(#1A95D0,#0070A2);\r\nbackground: -o-linear-gradient(#1A95D0,#0070A2);\r\nbackground: -moz-linear-gradient(#1A95D0,#0070A2);\r\nbackground: linear-gradient(#1A95D0,#0070A2);\r\nfont-weight: bold;\r\n}\r\n#kuo_themes_area dd.kuo_links a:last-of-type {\r\nbackground: #E69714;\r\nbackground: -webkit-linear-gradient(#E69714,#C2430E);\r\nbackground: -o-linear-gradient(#E69714,#C2430E);\r\nbackground: -moz-linear-gradient(#E69714,#C2430E);\r\nbackground: linear-gradient(#E69714,#C2430E);\r\nmargin-left: 1px;\r\n}\r\n#kuo_themes_area dd.kuo_links a.kuo_uninstall {\r\nbackground: #999;\r\nbackground: -webkit-linear-gradient(#999,#777);\r\nbackground: -o-linear-gradient(#999,#777);\r\nbackground: -moz-linear-gradient(#999,#777);\r\nbackground: linear-gradient(#999,#777);\r\n}\r\n#kuo_themes_area dd.kuo_links a:hover,\r\n#kuo_themes_area dd.kuo_links a:focus,\r\n#kuo_themes_area dd.kuo_links a:active {\r\ncolor: #ff0;\r\ntext-decoration: underline;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme {\r\nwidth: auto;\r\ndisplay: block;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme dd.kuo_links {\r\nmargin-top: 15px;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme dt {\r\nmargin-bottom: 0;\r\n}\r\n#kuo_themes_area .message {\r\nfont-size: 1.3em;\r\npadding: 1% 0;\r\nmargin-left: 25%;\r\n}\r\n#kuo_themes_area #kuo_themes_sidebar .message {\r\nmargin: 0 0 20px 0;\r\n}\r\n#kuo_themes_area .message strong {\r\nbackground: #ff0;\r\nborder: 1px dashed #a00;\r\npadding: 3px 10px;\r\n}\r\n#kuo_themes_area form,\r\n#kuo_themes_area ul,\r\n#kuo_themes_area ol,\r\n#kuo_themes_area form p,\r\n#kuo_themes_area form ul,\r\n#kuo_themes_area form ol,\r\n#kuo_themes_area table {\r\nfont: 1.2em Arial, Helvetica, sans-serif;\r\n}\r\n#kuo_themes_area label,\r\n#kuo_themes_area button,\r\n#kuo_themes_area input[type=file] {\r\ncursor: pointer;\r\n}\r\n#kuo_themes_area legend {\r\npadding: 0;\r\nmargin: 0;\r\nfont-size: 1.2em;\r\nfont-weight: bold;\r\n}\r\n#kuo_themes_area form p {\r\npadding: 15px 0;\r\nmargin: 0;\r\n}\r\n#kuo_themes_area label {\r\ndisplay: block;\r\npadding: 5px 5px 15px 5px;\r\nwidth: auto;\r\nfloat: none;\r\nclear: both;\r\n}\r\n#kuo_themes_area label code {\r\nborder: 0;\r\npadding: 0;\r\nbackground: 0;\r\ncolor: inherit;\r\nfont: 1em Arial, Helvetica, sans-serif;\r\n}\r\n#kuo_themes_area input[type=file] {\r\nbackground: #ffc;\r\nborder: 1px solid #ccc;\r\npadding: 10px;\r\nmargin: 0 5px 15px 5px;\r\ndisplay: block;\r\n}\r\n#kuo_themes_area button {\r\ndisplay: block;\r\ntext-align: center;\r\npadding: 5px 10px;\r\n}\r\n</style>\r\n<div id=\"kuo_themes_area\">\';\r\n\r\n# STARTS: IF THEME IS INSTALLED OR UNINSTALLED\r\nif ((isset($_GET[\'message\'])) && (!empty($_GET[\'message\'])) && (is_numeric($_GET[\'message\']))) {\r\n    if ($_GET[\'message\'] == 3) {\r\n        $kuo_message = \'Theme was installed.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 2) {\r\n        $kuo_message = \'Theme was uninstalled.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 1) {\r\n        $kuo_message = \'There is already a theme with that name.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 4) {\r\n        $kuo_message = \'Theme file size can not be over 10 Mb.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 5) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 6) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 7) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 8) {\r\n        $kuo_message = \'Theme was uploaded.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 9) {\r\n        $kuo_message = \'Unable to upload theme.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 10) {\r\n        $kuo_message = \'Not a valid theme.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 11) {\r\n        $kuo_message = \'Unable to install that theme.\';\r\n    }\r\n    else {\r\n        header(\'location: index.php?event=kuo_theme\');\r\n        die;\r\n    }\r\n    echo \'<p class=\"message\"><strong>\'.$kuo_message.\'</strong></p>\';\r\n    unset($kuo_message);\r\n}\r\n# ENDS: IF THEME IS INSTALLED OR UNINSTALLED\r\n\r\necho \'<div id=\"kuo_themes_sidebar\"><h2>Themes</h2><dl><dt>Help:</dt><dd><ul>\r\n<li><a href=\"\'.KUO_THEME_TAB.\'&amp;help=get-themes\" rel=\"nofollow\">Get themes</a></li>\r\n<li><a href=\"\'.KUO_THEME_TAB.\'&amp;help=theme-structure\" rel=\"nofollow\">Theme structure</a></li>\r\n</ul></dd></dl>\';\r\n\r\n# IF THEMES FOLDER EXISTS AND IT HAS FOLDERS (THEMES) INSIDE IT\r\nif ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) !== 0)) {\r\n    \r\n        if (intval(count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR))) == 1) {\r\n            #$there_are_or_is_x_themes = \'is one valid theme\';\r\n        }\r\n        else {\r\n            if ((isset($_GET[\'sort\'])) && (!empty($_GET[\'sort\'])) && (in_array($_GET[\'sort\'],array(\'abc\',\'cba\',\'newest-first\',\'oldest-first\')))) {\r\n                $kuo_theme_active = filter_var($_GET[\'sort\'],FILTER_SANITIZE_STRING);\r\n            }\r\n            else {\r\n                $kuo_theme_active = NULL;\r\n            }\r\n\r\n            # AT THE SAME POINT SOME OTHER FUNCTIONS\r\n            echo \'<dl class=\"kuo_theme_sort\"><dt>Sort by name:</dt><dd><ul><li\';\r\n            if ((empty($kuo_theme_active)) or ($kuo_theme_active === \'abc\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=abc\" rel=\"nofollow\">ABC</a></li><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'cba\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=cba\" rel=\"nofollow\">CBA</a></li></ul></dd></dl><dl class=\"kuo_theme_sort\"><dt>Sort by freshness:</dt><dd><ul><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'newest-first\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=newest-first\" rel=\"nofollow\">Newest first</a></li><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'oldest-first\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=oldest-first\" rel=\"nofollow\">Oldest first</a></li></ul></dd></dl>\';\r\n\r\n            # THIS IS NOT RELIABLE BECAUSE IT COUNTS ONLY FOLDERS AND NOT CHECK THEIR CONTENTS\r\n            #$there_are_or_is_x_themes = \'are \'.intval(count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR))).\' valid themes\';\r\n        }\r\n        # ELSE ENDS\r\n        \r\n        # ECHOING HTML FORM FOR ZIP UPLOADING\r\n        kuo_upload_theme();\r\n\r\n        # STARTS: IF VIEWING \"ABOUT\" IF SINGLE THEME\r\n        if ((isset($_GET[\'about\'])) && (!empty($_GET[\'about\'])) && (base64_encode(base64_decode($_GET[\'about\'],true)) === $_GET[\'about\']) && (!isset($_GET[\'help\']))) {\r\n            # FETCHING THEME DATA FROM \"THEMES\" FOLDER\r\n            if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (file_exists(base64_decode($_GET[\'about\']))) && (file_exists(base64_decode($_GET[\'about\']).\'/textpattern.txp\')) && (is_readable(base64_decode($_GET[\'about\']).\'/textpattern.txp\'))) {\r\n                $kuo_about_this_theme = explode(PHP_EOL,file_get_contents(base64_decode($_GET[\'about\']).\'/textpattern.txp\',0,NULL,0,900));\r\n            }\r\n            else {\r\n                $kuo_about_this_theme = NULL;\r\n                echo \'<p class=\"message\"><strong>Error. Unable to fetch theme information.</strong></p>\';\r\n            }\r\n        \r\n            # STARTS: FETCHING THEME NAME\r\n            $kuo_about_this_theme_name = $kuo_about_this_theme[0];\r\n            if (!empty($kuo_about_this_theme_name)) {\r\n                $kuo_about_this_theme_name = str_replace(\'Theme name: \',\'\',$kuo_about_this_theme_name);\r\n                if (is_numeric($kuo_about_this_theme_name)) {\r\n                    $kuo_about_this_theme_name = filter_var($kuo_about_this_theme_name,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_name = filter_var($kuo_about_this_theme_name,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_name = \'Unknown\';\r\n            }\r\n            # ENDS: FETCHING THEME NAME\r\n\r\n            # STARTS: FETCHING THEME AUTHOR NAME\r\n            $kuo_about_this_theme_author = $kuo_about_this_theme[1];\r\n            if (!empty($kuo_about_this_theme_author)) {\r\n                $kuo_about_this_theme_author = str_replace(\'Theme author: \',\'\',$kuo_about_this_theme_author);\r\n                if (is_numeric($kuo_about_this_theme_author)) {\r\n                    $kuo_about_this_theme_author = filter_var($kuo_about_this_theme_author,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_author = filter_var($kuo_about_this_theme_author,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_author = \'Unknown\';\r\n            }\r\n            # ENDS: FETCHING THEME AUTHOR NAME\r\n\r\n            # STARTS: FETCHING THEME URL\r\n            $kuo_about_this_theme_url = $kuo_about_this_theme[2];\r\n            if (!empty($kuo_about_this_theme_url)) {\r\n                $kuo_about_this_theme_url = str_replace(\'Theme URL: \',\'\',$kuo_about_this_theme_url);\r\n                $kuo_about_this_theme_url = filter_var($kuo_about_this_theme_url,FILTER_SANITIZE_URL);\r\n                if (!empty($kuo_about_this_theme_url)) {\r\n                    $kuo_about_this_theme_url = \', <a href=\"\'.$kuo_about_this_theme_url.\'\" rel=\"nofollow external\" target=\"_new\">\'.$kuo_about_this_theme_url.\'</a>\';\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_url = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME URL\r\n\r\n            # STARTS: FETCHING THEME VERSION NUMBER\r\n            $kuo_about_this_theme_version = $kuo_about_this_theme[3];\r\n            if (!empty($kuo_about_this_theme_version)) {\r\n                $kuo_about_this_theme_version = str_replace(\'Theme version: \',\'\',$kuo_about_this_theme_version);\r\n                if (is_numeric($kuo_about_this_theme_version)) {\r\n                    $kuo_about_this_theme_version = filter_var($kuo_about_this_theme_version,FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_version = filter_var($kuo_about_this_theme_version,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_version = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME VERSION NUMBER\r\n\r\n            # STARTS: FETCHING THEME DESCRIPTION\r\n            $kuo_about_this_theme_description = $kuo_about_this_theme[4];\r\n            if (!empty($kuo_about_this_theme_description)) {\r\n                $kuo_about_this_theme_description = str_replace(\'Theme description: \',\'\',$kuo_about_this_theme_description);\r\n                if (is_numeric($kuo_about_this_theme_description)) {\r\n                    $kuo_about_this_theme_description = filter_var($kuo_about_this_theme_description,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_description = filter_var($kuo_about_this_theme_description,FILTER_SANITIZE_STRING);\r\n                }\r\n                if (!empty($kuo_about_this_theme_description)) {\r\n                    $kuo_about_this_theme_description = \'<dd>\'.$kuo_about_this_theme_description.\'</dd>\';\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_description = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_description = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME DESCRIPTION\r\n\r\n            # STARTS: LOOKING FOR THEME SCREENSHOT\r\n            if ((file_exists(base64_decode($_GET[\'about\']).\'/screenshot.png\')) && (is_readable(base64_decode($_GET[\'about\']).\'/screenshot.png\'))) {\r\n                $kuo_about_this_theme_screenshot = \'<img src=\"\'.base64_decode($_GET[\'about\']).\'/screenshot.png\" alt=\"\" height=\"200\" width=\"400\" />\';\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_screenshot = KUO_NO_SCREENSHOT_IMG;\r\n            }\r\n            # ENDS: LOOKING FOR THEME SCREENSHOT\r\n\r\n            # STARTS: LOOKING FOR THEME \"LOGIC\"\r\n            if (isset($kuo_about_this_theme[5])) {\r\n                $kuo_about_this_theme_logic = $kuo_about_this_theme[5];\r\n                if (!empty($kuo_about_this_theme_logic)) {\r\n                    $kuo_about_this_theme_logic = str_replace(\'Theme logic: \',\'\',$kuo_about_this_theme_logic);\r\n                    $kuo_about_this_theme_logic = explode(\';\',$kuo_about_this_theme_logic);\r\n\r\n                    foreach ($kuo_about_this_theme_logic as $kuo_theme_logic) {\r\n                        # IF THEME HAS A SPECIFIC DEFAULT SECTION\r\n                        if (strpos($kuo_theme_logic,\'default_page=\') !== false) {\r\n                            $kuo_about_this_theme_default_page = \'<li>Default page: <code>\'.str_replace(\'default_page=\',basename(base64_decode($_GET[\'about\'])).\'-\',$kuo_theme_logic).\'</code></li>\';\r\n                        }\r\n\r\n                        # IF THEME REQUIRES SOME PLUGINS IN ORDER TO WORK PROPERLY\r\n                        if (strpos($kuo_theme_logic,\'required_plugins=\') !== false) {\r\n                            $kuo_about_this_theme_required_plugins = \'<li>Required plugins: <code>\'.str_replace(array(\'required_plugins=\',\',\'),array(\'\',\', \'),$kuo_theme_logic).\'</code></li>\';\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_logic = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_logic = NULL;\r\n            }\r\n            # ENDS: LOOKING FOR THEME \"LOGIC\"\r\n\r\n            echo \'</div><div id=\"kuo_themes_main\" class=\"kuo_single_theme\"><h2><a href=\"\'.KUO_THEME_TAB.\'\" rel=\"prev\">Available themes:</a> \'.$kuo_about_this_theme_name.\' \'.$kuo_about_this_theme_version.\'</h2>\';\r\n\r\n            # WHEN IT WAS INSTALLED?\r\n            # (ONLY IF INSTALL FILE IS FOUND)\r\n            if ((file_exists(base64_decode($_GET[\'about\']).\'/installed.txp\')) && (is_readable(base64_decode($_GET[\'about\']).\'/installed.txp\'))) {\r\n                $kuo_when_installed = file_get_contents(base64_decode($_GET[\'about\']).\'/installed.txp\',NULL,NULL,0,19);\r\n                echo \'<p>Installed: \'.filter_var($kuo_when_installed).\'</p>\';\r\n                unset($kuo_when_installed);\r\n            }\r\n\r\n            echo \'<dl class=\"kuo_theme\"><dt>By \'.utf8_encode($kuo_about_this_theme_author.$kuo_about_this_theme_url).\'</dt>\'.utf8_encode($kuo_about_this_theme_screenshot.$kuo_about_this_theme_description);\r\n\r\n            # THIS MEANS THEME IS INSTALLED\r\n            if (file_exists(base64_decode($_GET[\'about\']).\'/installed.txp\')) {\r\n                $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;uninstall=\'.$_GET[\'about\'].\'\" rel=\"nofollow\" class=\"kuo_uninstall\" onclick=\"return confirm(\\\'Do you really want to uninstall this theme?\\\')\">Uninstall</a>\';\r\n            }\r\n            # OTHERWISE THEME IS NOT INSTALLED\r\n            else {\r\n                $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;install=\'.$_GET[\'about\'].\'\" rel=\"nofollow\">Install</a>\';\r\n            }\r\n\r\n            echo \'<dd class=\"kuo_links\">\'.$kuo_action_link.\'</dd></dl>\';\r\n\r\n            # AT THIS POINT THEME\'S \"LOGIC\" THING CAN BE USED\r\n            # TO INFORM USER ABOUT HOW THIS THEME IS INTENDED TO BE USED\r\n            # AND WHAT PLUGINS, IF ANY, IT USES OR REQUIRES\r\n            if ((isset($kuo_about_this_theme_logic)) && (!empty($kuo_about_this_theme_logic))) {\r\n                echo \'<dl><dt>Theme instructions</dt><dd><ul>\'.$kuo_about_this_theme_default_page.$kuo_about_this_theme_required_plugins.\'</ul></dd></dl>\';\r\n            }\r\n\r\n            echo \'</div><br class=\"kuo_themes_clear\" /></div>\';\r\n        }\r\n        # ENDS: IF VIEWING \"ABOUT\" IF SINGLE THEME\r\n        # STARTS: IF VIEWING \"HELP\" PAGE\r\n        elseif ((isset($_GET[\'help\'])) && (!empty($_GET[\'help\'])) && (!isset($_GET[\'about\']))) {\r\n            # SHOWING WHAT THE CURRENT THEME STRUCTURE LOOKS LIKE\r\n            if ($_GET[\'help\'] === \'theme-structure\') {\r\n                $kuo_page_heading = \'Theme structure\';\r\n                $kuo_page_content = \'<pre>website.com/themes/\r\n    website.com/themes/mytheme/\r\n        website.com/themes/mytheme/pages/\r\n            website.com/themes/mytheme/pages/pagename.txp\r\n            website.com/themes/mytheme/pages/another-page.txp\r\n        website.com/themes/mytheme/forms/\r\n            website.com/themes/mytheme/forms/article.default.txp\r\n            website.com/themes/mytheme/forms/article.second-form.txp\r\n            website.com/themes/mytheme/forms/misc.tracking-code.txp\r\n            website.com/themes/mytheme/forms/misc.another_test.txp\r\n        website.com/themes/mytheme/styles/\r\n            website.com/themes/mytheme/styles/layout.css\r\n            website.com/themes/mytheme/styles/another.css\r\n        website.com/themes/mytheme/textpattern.txp\r\n        website.com/themes/mytheme/screenshot.png</pre>\r\n        <h3>What is inside <code>textpattern.txp</code>?</h3>\r\n        <pre>Theme name: Theme name\r\nTheme author: Author name\r\nTheme URL: http://www.website.com/\r\nTheme version: version.number\r\nTheme description: A short description.\r\nTheme logic: default_page=default;required_plugins=none;</pre>\r\n<p>The &quot;Theme logic&quot; part can be left out. It can be used to help the user to get theme working properly if it requires some Textpattern plugins. If plugins are required, use instead of <code>none</code> a comma-separated list like this:\r\n<code>dzd_counter_view,aks_header,rah_metas</code>.</p>\';\r\n            }\r\n            # HELPING USER TO LOCATE SUITABLE THEMES\r\n            elseif ($_GET[\'help\'] === \'get-themes\') {\r\n                $kuo_page_heading = \'Get themes\';\r\n                \r\n                # CHECKING IF FILE EXISTS IN EXTERNAL SOURCE\r\n                if (@get_headers(KUO_DEFAULT_THEME_FEED)[0] !== \'HTTP/1.1 404 Not Found\') {\r\n                    \r\n                    # IF CACHED FILE IS NOT THERE OR IT\'S TOO OLD\r\n                    if ((!file_exists(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) or (!is_readable(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) or (filectime(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) <= time()-strtotime(\'-1 hour\')) {\r\n                        $kuo_page_content = file_get_contents(KUO_DEFAULT_THEME_FEED,0);\r\n                        if (!empty($kuo_page_content)) {\r\n                            file_put_contents(KUO_THEME_FOLDER.\'kuo_themes_list.txt\',$kuo_page_content);\r\n                        }\r\n                        else {\r\n                            $kuo_page_content = NULL;\r\n                        }\r\n                    }\r\n                    else {\r\n                        $kuo_page_content = file_get_contents(KUO_THEME_FOLDER.\'kuo_themes_list.txt\',0);\r\n                    }\r\n                    \r\n                    # PROCESSING DATA IF AVAILABLE\r\n                    if ((isset($kuo_page_content)) && (!empty($kuo_page_content))) {\r\n                        $kuo_page_content = explode(\'|\',$kuo_page_content);\r\n                        foreach ($kuo_page_content as $kuo_page_single_theme) {\r\n                            $kuo_page_themes[] = explode(\';;\',$kuo_page_single_theme);\r\n                        }\r\n                        \r\n                        # HANDLING SINGLE THEME DATA\r\n                        if ((is_array($kuo_page_themes)) && (!empty($kuo_page_themes))) {\r\n                            $kuo_page_content = NULL;\r\n                            foreach ($kuo_page_themes as $kuo_page_single_theme) {\r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME NAME                                \r\n                                if ((isset($kuo_page_single_theme[0])) && (!empty($kuo_page_single_theme[0]))) {\r\n                                    $kuo_page_single_theme_name = filter_var($kuo_page_single_theme[0],FILTER_SANITIZE_STRING);\r\n                                    if (empty($kuo_page_single_theme_name)) {\r\n                                        $kuo_page_single_theme_name = \'(Empty)\';\r\n                                    }\r\n                                    else {\r\n                                        if (strlen($kuo_page_single_theme_name) >= 40) {\r\n                                            $kuo_page_single_theme_name = substr($kuo_page_single_theme_name,0,39).\'&hellip;\';\r\n                                        }\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_name = \'(Empty)\';\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME NAME    \r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME DESCRIPTION\r\n                                if ((isset($kuo_page_single_theme[5])) && (!empty($kuo_page_single_theme[5]))) {\r\n                                    $kuo_page_single_theme_description = filter_var($kuo_page_single_theme[5],FILTER_SANITIZE_STRING);\r\n                                    if (empty($kuo_page_single_theme_description)) {\r\n                                        $kuo_page_single_theme_description = NULL;\r\n                                    }\r\n                                    else {\r\n                                        \r\n                                        if (strlen($kuo_page_single_theme_description) >= 50) {\r\n                                            $kuo_page_single_theme_description = substr($kuo_page_single_theme_description,0,49).\'&hellip;\';\r\n                                        }\r\n                                        \r\n                                        $kuo_page_single_theme_description = \'<dd class=\"kuo_theme_description\">\'.utf8_encode($kuo_page_single_theme_description).\'</dd>\';\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_description = NULL;\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME DESCRIPTION\r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME SCREENSHOT\r\n                                if ((isset($kuo_page_single_theme[1])) && (!empty($kuo_page_single_theme[1])) && (filter_var($kuo_page_single_theme[1],FILTER_VALIDATE_URL) == true) \r\n                                && (in_array(pathinfo($kuo_page_single_theme[1],PATHINFO_EXTENSION),array(\'png\',\'jpg\',\'gif\',\'jpeg\')))) {\r\n                                    \r\n                                    # TO SPEED THIS PART OF PAGE LOADING IMAGES MUST BE CACHED\r\n                                    if (!is_dir(KUO_THEME_FOLDER.\'kuo_cache\')) {\r\n                                        mkdir(KUO_THEME_FOLDER.\'kuo_cache\',0755);\r\n                                    }\r\n                                    \r\n                                    # DOWNLOADING THE THEME SCREENSHOT FROM EXTERNAL SERVER\r\n                                    # IF THEME FEED IS FROM KUOPASSA.NET, SCREENSHOT IS A 470x250 THUMBNAIL\r\n                                    if ((!file_exists(KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]))) && (@get_headers($kuo_page_single_theme[1])[0] !== \'HTTP/1.1 404 Not Found\')) {                                    \r\n                                        file_put_contents(KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]),file_get_contents(filter_var($kuo_page_single_theme[1],FILTER_SANITIZE_URL)));\r\n                                    }                                    \r\n                                    \r\n                                    $kuo_page_single_theme_screenshot = \'<dd><img src=\"\'.KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]).\'\" alt=\"A screenshot of &quot;\'.utf8_encode($kuo_page_single_theme_name).\'&quot;.\" height=\"200\" width=\"400\" /></dd>\';\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_screenshot = \'<dd>\'.KUO_NO_SCREENSHOT_IMG.\'</dd>\';\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME SCREENSHOT\r\n                                \r\n                                # STARTS: CHECKING EXTERNAL THEME DOWNLOAD LINK\r\n                                if ((isset($kuo_page_single_theme[2])) && (!empty($kuo_page_single_theme[2])) && (filter_var($kuo_page_single_theme[2],FILTER_VALIDATE_URL) == true)) {\r\n                                    $kuo_page_single_theme_download = filter_var($kuo_page_single_theme[2],FILTER_SANITIZE_URL);\r\n                                    if (!empty($kuo_page_single_theme_download)) {\r\n                                        $kuo_page_single_theme_download = \'<dd class=\"kuo_links\"><a href=\"\'.$kuo_page_single_theme_download.\'\" rel=\"nofollow external\">Download</a></dd>\';\r\n                                    }\r\n                                    else {\r\n                                        $kuo_page_single_theme_download = NULL;\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_download = NULL;\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME DOWNLOAD LINK                                \r\n\r\n                                # OUTPUTTING SINGLE THEME\r\n                                $kuo_page_content .= \'<dl class=\"kuo_theme\"><dt class=\"kuo_theme_name\">\'.utf8_encode(htmlspecialchars($kuo_page_single_theme_name)).\'</dt>\'.$kuo_page_single_theme_description.$kuo_page_single_theme_screenshot.$kuo_page_single_theme_download.\'</dl>\';\r\n                            }\r\n                        }\r\n                        else {\r\n                            $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                        }\r\n\r\n                    }\r\n                    else {\r\n                        $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                    }\r\n\r\n                }\r\n                else {\r\n                    $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                }\r\n            }\r\n            else {\r\n                $kuo_page_heading = \'404\';\r\n                $kuo_page_content = \'Page was not found.\';\r\n            }\r\n            echo \'</div><div id=\"kuo_themes_main\"><h2><a href=\"\'.KUO_THEME_TAB.\'\" rel=\"prev\">Available themes:</a> \'.$kuo_page_heading.\'</h2>\'.$kuo_page_content.\'</div><br class=\"kuo_themes_clear\" /></div>\';\r\n        }\r\n        # ENDS: IF VIEWING \"HELP\" PAGE\r\n        # STARTS: IF MAIN PAGE OF THIS PLUGIN\r\n        else {\r\n            kuo_theme_list();\r\n        }\r\n        # ENDS: IF MAIN PAGE OF THIS PLUGIN\r\n    }\r\n    # IF THEMES FOLDER EXISTS BUT NO THEMES EXIST\r\n    elseif ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) == 0)) {\r\n        echo \'<p class=\"message\"><strong>Themes were not found.</strong></p>\';\r\n        kuo_upload_theme();\r\n    }\r\n    # IF THEMES FOLDER DOES NOT EXIST\r\n    else {\r\n        echo \'<p class=\"message\"><strong>Theme folder does not exist. <a href=\"\'.KUO_THEME_TAB.\'&amp;fix=folder\" rel=\"nofollow\">Create it now?</a></strong></p>\';\r\n    }\r\n    echo \'<br class=\"kuo_themes_clear\" /></div>\';\r\n}\r\n# ENDING: ACTUAL THEME FUNCTIONS TAB\r\n\r\n##########################\r\n\r\n# BEGINNING: BLOCK FOR FILE UPLOAD FORM\r\nfunction kuo_upload_theme() {\r\n    echo \'<form action=\"\'.KUO_THEME_TAB.\'&amp;upload\" method=\"post\" enctype=\"multipart/form-data\"><fieldset><legend>Upload new themes</legend>\r\n    <p><label for=\"upload-zip\">Choose a <code>.zip</code> file with maximum 10 Mb size:</label><input id=\"upload-zip\" \r\n    type=\"file\" name=\"upload\" accept=\".zip\" required=\"required\" /><button name=\"submit\" type=\"submit\">Upload</button></p></fieldset></form>\';\r\n}\r\n# ENDING: BLOCK FOR FILE UPLOAD FORM\r\n\r\n##########################\r\n\r\n# BEGINNING: LISTING CONTENTS OF THEME FOLDER\r\nfunction kuo_theme_list() {\r\n    # GOING THROUGH THE THEME LIST\r\n    if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) !== 0)) {\r\n        $kuo_theme_settings_meta = array();\r\n\r\n        if ((isset($_GET[\'sort\'])) && (!empty($_GET[\'sort\'])) && (in_array($_GET[\'sort\'],array(\'abc\',\'cba\',\'newest-first\',\'oldest-first\')))) {\r\n            $kuo_theme_sort_type = filter_var($_GET[\'sort\'],FILTER_SANITIZE_STRING);\r\n\r\n            if ($kuo_theme_sort_type === \'abc\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR|GLOB_NOSORT);\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'cba\') {\r\n                $kuo_theme_sorter = array_reverse(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR));\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'newest-first\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n                array_multisort(array_map(\'filectime\',$kuo_theme_sorter),SORT_NUMERIC,SORT_DESC,$kuo_theme_sorter);\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'oldest-first\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n                array_multisort(array_map(\'filectime\',$kuo_theme_sorter),SORT_NUMERIC,SORT_ASC,$kuo_theme_sorter);\r\n            }\r\n            else {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n        }\r\n\r\n        echo \'</div><div id=\"kuo_themes_main\">\';\r\n\r\nforeach ($kuo_theme_sorter as $kuo_theme) {\r\n# CHECKING IF THEME SETTINGS FILE EXISTS\r\nif ((file_exists($kuo_theme.\'/textpattern.txp\')) && (is_readable($kuo_theme.\'/textpattern.txp\'))) {\r\n    $kuo_theme_meta  = explode(PHP_EOL,file_get_contents($kuo_theme.\'/textpattern.txp\',0,NULL,0,300));\r\n    $kuo_theme_meta_counter = 0;\r\n\r\n    foreach ($kuo_theme_meta as $kuo_theme_name => $kuo_theme_value) {\r\n        $kuo_theme_meta_counter++;\r\n        $kuo_single_theme_dl = NULL;\r\n\r\n        if ($kuo_theme_meta_counter == 1) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme name: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 2) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme author: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 3) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme URL: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 4) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme version: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 5) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme description: \',\'\',$kuo_theme_value);\r\n        }\r\n        else {\r\n            $kuo_theme_meta[$kuo_theme_name] = explode(\':\',$kuo_theme_value);\r\n        }\r\n    }\r\n    # FOREACH ENDS\r\n\r\n    # OUTPUTTING THEME DATA\r\n    if ((isset($kuo_theme_meta[0])) && (!empty($kuo_theme_meta[0]))) {\r\n        if (is_numeric($kuo_theme_meta[0])) {\r\n            $kuo_theme_name = filter_var($kuo_theme_meta[0],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_theme_name = filter_var($kuo_theme_meta[0],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        if (strlen($kuo_theme_name) >= 50) {\r\n            $kuo_theme_name = substr($kuo_theme_name,0,49).\'&hellip;\';\r\n        }\r\n\r\n        if (empty($kuo_theme_name)) {\r\n            $kuo_theme_name = \'(Empty)\';\r\n        }\r\n\r\n        # IF THEME HAS VERSION NUMBER\r\n        if ((isset($kuo_theme_meta[3])) && (!empty($kuo_theme_meta[3]))) {\r\n            # NO STANDARD WAY OF VERSIONING, SO...\r\n            if (is_numeric($kuo_theme_meta[3])) {\r\n                $kuo_theme_version = filter_var($kuo_theme_meta[3],FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);\r\n            }\r\n            else {\r\n                $kuo_theme_version = filter_var($kuo_theme_meta[3],FILTER_SANITIZE_STRING);\r\n            }\r\n\r\n            if (!empty($kuo_theme_version)) {\r\n                $kuo_theme_version = \' (\'.$kuo_theme_version.\')\';\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_version = NULL;\r\n        }\r\n\r\n        # IF THEME HAS DESCRIPTION\r\n        if ((isset($kuo_theme_meta[4])) && (!empty($kuo_theme_meta[4]))) {\r\n            $kuo_theme_description = filter_var($kuo_theme_meta[4],FILTER_SANITIZE_STRING);\r\n            if (strlen($kuo_theme_description) >= 61) {\r\n                $kuo_theme_description = substr($kuo_theme_description,0,60).\'&hellip;\';\r\n            }\r\n\r\n            if (empty($kuo_theme_description)) {\r\n                $kuo_theme_description = NULL;\r\n            }\r\n            else {\r\n                $kuo_theme_description = \'<dd class=\"kuo_theme_description\">\'.$kuo_theme_description.\'</dd>\';\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_description = NULL;\r\n        }\r\n        \r\n        $kuo_single_theme_dl .= \'<dl class=\"kuo_theme\"><dt class=\"kuo_theme_name\">\'.utf8_encode(htmlspecialchars($kuo_theme_name.$kuo_theme_version)).\'</dt>\'.utf8_encode($kuo_theme_description);\r\n    }\r\n    # ENDING IF THEME HAS META\r\n\r\n    # IF THEME HAS SCREENSHOT AND IT\'S OK\r\n    if ((file_exists($kuo_theme.\'/screenshot.png\')) && (is_readable($kuo_theme.\'/screenshot.png\')) && (filesize($kuo_theme.\'/screenshot.png\') != 0)) {\r\n        $kuo_single_theme_screenshot = \'<dd><img src=\"\'.$kuo_theme.\'/screenshot.png\'.\'\" alt=\"A screenshot of &quot;\'.utf8_encode($kuo_theme_name).\'&quot;.\" height=\"200\" width=\"400\" /></dd>\';\r\n    }\r\n    else {\r\n        $kuo_single_theme_screenshot = \'<dd>\'.KUO_NO_SCREENSHOT_IMG.\'</dd>\';\r\n    }\r\n\r\n    # THIS MEANS THEME IS INSTALLED\r\n    if (file_exists($kuo_theme.\'/installed.txp\')) {\r\n        $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;uninstall=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\" class=\"kuo_uninstall\" onclick=\"return confirm(\\\'Do you really want to uninstall this theme?\\\')\">Uninstall</a>\';\r\n    }\r\n    # OTHERWISE THEME IS NOT INSTALLED\r\n    else {\r\n        $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;install=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\">Install</a>\';\r\n    }\r\n\r\n    # LINKS: INFO AND INSTALL/UNINSTALL\r\n    $kuo_single_theme_dl .= $kuo_single_theme_screenshot.\'<dd class=\"kuo_links\"><a href=\"\'.KUO_THEME_TAB.\'&amp;about=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\">About</a><span> &bull; </span>\'.$kuo_action_link.\'</dd></dl>\';\r\n    \r\n    if (empty($kuo_theme_name)) {\r\n        $kuo_single_theme_dl = NULL;\r\n    }\r\n}\r\nelse {\r\n    $kuo_single_theme_dl = NULL;\r\n}\r\n# ENDING IF FILE EXIST\r\n\r\n    if ((isset($kuo_single_theme_dl)) && ($kuo_single_theme_dl !== NULL)) {\r\n        echo $kuo_single_theme_dl;\r\n    }\r\n}\r\n# ENDING FOREACH\r\nunset($kuo_single_theme_dl);\r\necho \'</div>\';\r\n}\r\nelse {\r\n    return;\r\n}\r\n\r\n}\r\n# ENDING LISTING CONTENTS OF THEME FOLDER\r\n\r\n}\r\n# ENDING IF ADMIN', '\r\nif (@txpinterface === \'admin\') {\r\n\r\n# REDIRECTING TO THE RIGHT PLACE\r\nif ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_theme\') {\r\n    header(\'location: ./index.php?event=kuo_theme\');\r\n    die;\r\n}\r\n\r\nregister_tab(\'extensions\',\'kuo_theme\',\'kuo_theme\');\r\nregister_callback(\'kuo_theme_tab\',\'kuo_theme\');\r\nadd_privs(\'kuo_theme\',\'1\');\r\n\r\n##########################\r\n\r\n# BEGINNING: ACTUAL THEME FUNCTIONS TAB\r\nfunction kuo_theme_tab() {\r\n\r\n# SOME BASIC SETTINGS\r\ndefine(\'KUO_THEME_TAB\',\'../textpattern/index.php?event=kuo_theme\');\r\ndefine(\'KUO_THEME_FOLDER\',\'../themes/\');\r\ndefine(\'KUO_ACTIVE_CSS_CLASS\',\' class=\"kuo_theme_open\"\');\r\ndefine(\'KUO_DEFAULT_THEME_FEED\',\'http://kuopassa.net/kuo_themes_list.txt\');\r\ndefine(\'KUO_NO_SCREENSHOT_IMG\',\'<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAADICAYAAAGxEod0AAAAB3RJTUUH3woPEgM3sV2NfwAAAAlw\r\nSFlzAAAchAAAHIQBhemxiwAAAARnQU1BAACxjwv8YQUAAAZGSURBVHja7d3RihtHFATQ1aKn/emx\r\nvnxCh2CcTVyxKh5x3T4HBCHYXC7tmtIK0Xt7e3v7ePvGeZ5f//t2u/30/3/VjPe3TVhkmvu3/86W\r\nK3LxihnbnMg2i9xf8Wj0+H3CNot4ak2zzSKeWtNss4in1jTbLOKpNc02i3hqTbPNIutsvn5AN+UJ\r\n1MzY5kQsMs194qO0mbHNiWyzyH3iG0CP3x14ak2zzSKeWtNss4in1jTbLOKpNc02i3hqTbPNIn/7\r\ngO6zz0f89S99eqq0f96Mf9rmX9YuHMgw9+9FZ/lZkTbjx/+8hAzjQIa57/LuZJcZEjKMAxnGu6xh\r\nMyRkGAcyjHdZw2ZIyDAOZBgHMoy3vcNmSMgwDmQYb3uHzZCQYRzIMN5lDZshIcM4kGG++0W53d/N\r\nTJ0hIcM4kGEcyDAOZBgHMowDGeY+8afV33mGhAzjQIZxIMM4kGEcyDD3XT6U22WGhAzjQIZxIMM4\r\nkGEcyDA+yxo2Q0KGcSDDOJBhHMgwDmQYn2UNmyEhwziQYRzIMA5kGAcyjAMZxoeLw2ZIyDAOZBgH\r\nMowDGcaBDOPDxWEzJGQYBzKMAxnGgQzjQIbxWdawGRIyjAMZxoEM40CGcSDDxF+9+j3p3cO/Dgmf\r\n6ZhhxuQZnlgQCAgEAgKBgEAgIBDcn/2pfnn20wMzzPhVZ2gQCAQEAgGBQEAgEBAI7rt8Z8YMM66Y\r\noUEgEBAIBAQCAYFAQCAQEAh8WdEMMwINAoGAQCAgEAgIBAICgS8rmmFGoEEgEBAIBAQCAYFAQCDw\r\nXSwzzAg0CAQCAoGAQCAgEAgIBL6LZYYZgQaBQEAgEBAIBAQCAYFAQCDwZUUzzAg0CAQCAoGAQCAg\r\nEAgI/IePZ17HcXyc5/nUywwzftUZGgQCAYFAQCAQEAgEBAIBgUBAIBAQCAQEAgGBQEAgEBAIbn99\r\n6evH/8ImN+aZYcaP0CAQCAgEAgKBgEAgIBAICAQCAoGAQCAgEAgIBAICwfv6fsozr+M4/vwOzDMv\r\nM8z4VWdoEAgEBAIBgUBAIBAQCAQEAgGBQEAgEBAIBAQCAYFAQCBwcZwZZgQaBAIBgUBAIBAQCAQE\r\nAgGBQEAgEBAIBAQCAYFAQCBwcZwZZrg4DjoCAoGAQCAgEAgIBAICgYBAICAQCAgEAgKBgEAgIBC4\r\nOM4MMwINAoGAQCAgEAgIBAICgYBAICAQCAgEAgKBgEAgIBC4OM4MM1wcBx0BgUBAIBAQCAQEAgGB\r\nQEAgEBAIBAQCAYFAQCAQEAhcHGeGGYEGgUBAIBAQCAQEAgGBQEAgEBAIBAQCAYFAQCAQEAjWl1Oe\r\n+i5WY13Y9eXLl2sXKb5nYw972MMe9uh5hwVARYEAUFEgAFQUCAAVBQJARYEAUFEgAFQUCAAVBQJA\r\nRYEAUFEgAFRux3FcfhfW4/G4fJF174s97GEPe9jjdXs8/YttqyEvuDjs2V9yag972MMe9vh/fIQF\r\nQEWBAFBRIABUFAgAFQUCQEWBAFBRIABUFAgAFQUCQEWBAFBRIABU3td9KVe/1sVh6+6XK1/2sIc9\r\n7GGP1+7hJxAAKgoEgIoCAaCiQACoKBAAKgoEgIoCAaCiQACoKBAAKgoEgIoCAaByO47j4+ohj8fj\r\n8kXWvS/2sIc97GGP1+1xO8/z8gJZF3tdbV3sZQ972MMe9njdHj7CAqCiQACoKBAAKgoEgIoCAaCi\r\nQACoKBAAKgoEgIoCAaCiQACoKBAAKu/rvpSrX+visHX3y5Uve9jDHvawx2v38BMIABUFAkBFgQBQ\r\nUSAAVBQIABUFAkBFgQBQUSAAVBQIABUFAkBFgQBQuR3H8XH1kMfjcfki694Xe9jDHvawx+v2uJ3n\r\neXmBrIu9rrYu9rKHPexhD3u8bg8fYQFQUSAAVBQIABUFAkBFgQBQUSAAVBQIABUFAkBFgQBQUSAA\r\nVBQIAJX3dV/K1a91cdi6++XKlz3sYQ972OO1e/gJBICKAgGgokAAqCgQACoKBICKAgGgokAAqCgQ\r\nACoKBICKAgGgokAAqNyO4/i4esjj8bh8kXXviz3sYQ972ON1e9zO87y8QNbFXldbF3vZwx72sIc9\r\nXreHj7AAqCgQACoKBICKAgGgokAAqCgQACoKBICKAgGgokAAqCgQACoKBIDKH+YGlwN0DPgrAAAA\r\nAElFTkSuQmCC\" alt=\"No image\" height=\"200\" width=\"400\" />\');\r\n\r\n# SOME BASIC FUNCTIONS\r\nif ((isset($_GET[\'fix\'])) && (!empty($_GET[\'fix\'])) && (in_array($_GET[\'fix\'],array(\'folder\')))) {\r\n    # CREATING THE FOLDER FOR THEMES\r\n    if (!is_dir(KUO_THEME_FOLDER)) {\r\n        mkdir(KUO_THEME_FOLDER,0755,true);\r\n    }\r\n    header(\'location:\'.KUO_THEME_TAB);\r\n    die;\r\n}\r\nelseif ((isset($_GET[\'fix\'])) && (empty($_GET[\'fix\']))) {\r\n    header(\'location:\'.KUO_THEME_TAB);\r\n    die;\r\n}\r\n\r\n# INCLUDING HTML FOR THE TAB\r\npagetop(\'Themes\');\r\n\r\n# STARTS: IF INSTALLING A THEME\r\nif ((isset($_GET[\'install\'])) && (!empty($_GET[\'install\'])) && (base64_encode(base64_decode($_GET[\'install\'],true)) === $_GET[\'install\']) && (!file_exists(base64_decode($_GET[\'install\']).\'/installed.txp\'))) {\r\n    # FETCHING THEME DATA FROM \"THEMES\" FOLDER\r\n    if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (file_exists(base64_decode($_GET[\'install\']))) && (file_exists(base64_decode($_GET[\'install\']).\'/textpattern.txp\')) && (is_readable(base64_decode($_GET[\'install\']).\'/textpattern.txp\'))) {\r\n        $theme_folder_name = explode(\'/\',str_replace(KUO_THEME_FOLDER,\'\',base64_decode($_GET[\'install\'])));\r\n        $theme_folder_name = $theme_folder_name[0];\r\n\r\n        # LOOPING THROUGH THEME FOLDERS AND FILES INSIDE THEM\r\n        \r\n        # FIRST IN LINE IS \"PAGES\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/pages/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/pages/*.txp\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    $template_name = $theme_folder_name.\'-\'.str_replace(\'.txp\',\'\',basename($theme_file));\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_page\').\'` (name, user_html) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n        }\r\n        \r\n        # SECOND IS \"FORMS\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/forms/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/forms/*.txp\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    # FORMS HAVE TYPES, SO LETS FIGURE OUT IF THIS TEMPLATE NAME HAS THAT INCLUDED\r\n                    $template_type = explode(\'.\',str_replace(\'.txp\',\'\',basename($theme_file)));\r\n                    \r\n                    # THIS TIME TEMPLATE NAME IS FROM ABOVE EXPLOSION\r\n                    $template_name = $theme_folder_name.\'-\'.$template_type[1];\r\n                    \r\n                    # AND TEMPLATE TYPE IS THE EL NUMERO 0\r\n                    $template_type = $template_type[0];\r\n\r\n                    # MUST CHECK THAT THIS FORM TYPE IS OK\r\n                    # IF NOT OK THEN FORM IS MISCELLANEOUS\r\n                    if (!in_array($template_type,array(\'article\',\'misc\',\'comment\',\'category\',\'file\',\'link\',\'section\'))) {\r\n                        $template_type = \'misc\';\r\n                    }\r\n\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_form\',\'`name` = \"\'.$template_name.\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_form\').\'` (name, type, Form) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_type).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                        unset($template_type);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n            unset($template_name);\r\n        }\r\n\r\n        # THIRD AND FINAL IS \"STYLES\" FOLDER\r\n        if (is_dir(base64_decode($_GET[\'install\']).\'/styles/\')) {\r\n            foreach (glob(base64_decode($_GET[\'install\']).\'/styles/*.css\') as $theme_file) {\r\n                if (is_readable($theme_file)) {\r\n                    $template_name = $theme_folder_name.\'-\'.str_replace(\'.css\',\'\',basename($theme_file));\r\n                    # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n                    $template_search = safe_rows(\'name\',\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                    # NO TEMPLATE EXISTS: OK TO INSTALL\r\n                    if (empty($template_search)) {\r\n                        $template_contents = file_get_contents($theme_file);\r\n                        safe_query(\'INSERT IGNORE INTO `\'.safe_pfx(\'txp_css\').\'` (name, css) VALUES (\"\'.doSlash($template_name).\'\", \"\'.doSlash($template_contents).\'\")\',0);\r\n                        unset($template_contents);\r\n                    }\r\n                    unset($template_search);\r\n                }\r\n            }\r\n            unset($template_name);\r\n        }\r\n\r\n        # IF THEME IS INSTALLED, IT\'S MARKED WITH FILE \"installed.txp\" WHICH HAS THE DATE\r\n        file_put_contents(base64_decode($_GET[\'install\']).\'/installed.txp\',date(\'Y-m-d H:i:s\'));\r\n\r\n        # THEN USER IS REDIRECTED TO MAIN PAGE WITH SUCCESS MESSAGE\r\n        header(\'location: index.php?event=kuo_theme&message=3\');\r\n        die;\r\n    }\r\n    else {\r\n        # SOME KIND OF ERROR\r\n        header(\'location: index.php?event=kuo_theme&message=11\');\r\n        die;\r\n    }\r\n}\r\n# ENDS: IF INSTALLING A THEME\r\n\r\n# STARTS: UNINSTALLING A THEME\r\nif ((isset($_GET[\'uninstall\'])) && (!empty($_GET[\'uninstall\'])) && (base64_encode(base64_decode($_GET[\'uninstall\'],true)) === $_GET[\'uninstall\']) && (file_exists(base64_decode($_GET[\'uninstall\']).\'/installed.txp\'))) {\r\n    $theme_folder_name = explode(\'/\',str_replace(KUO_THEME_FOLDER,\'\',base64_decode($_GET[\'uninstall\'])));\r\n    $theme_folder_name = $theme_folder_name[0];\r\n\r\n    # LOOPING THROUGH THEME FOLDERS AND FILES INSIDE THEM\r\n        \r\n    # FIRST IN LINE IS \"PAGES\" FOLDER\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/pages/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/pages/*.txp\') as $theme_file) {\r\n            $template_name = $theme_folder_name.\'-\'.str_replace(\'.txp\',\'\',basename($theme_file));\r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {\r\n                safe_delete(\'txp_page\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # SECOND IS \"FORMS\" FOLDER\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/forms/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/forms/*.txp\') as $theme_file) {\r\n            $template_type = explode(\'.\',str_replace(\'.txp\',\'\',basename($theme_file)));\r\n            $template_name = $theme_folder_name.\'-\'.$template_type[1];\r\n            \r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_form\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {                \r\n                # AND TEMPLATE TYPE IS THE EL NUMERO 0\r\n                $template_type = $template_type[0];\r\n\r\n                # MUST CHECK THAT THIS FORM TYPE IS OK\r\n                # IF NOT OK THEN FORM IS MISCELLANEOUS\r\n                if (!in_array($template_type,array(\'article\',\'misc\',\'comment\',\'category\',\'file\',\'link\',\'section\'))) {\r\n                    $template_type = \'misc\';\r\n                }\r\n\r\n                safe_delete(\'txp_form\',\'`name` = \"\'.doSlash($template_name).\'\" AND `type` = \"\'.doSlash($template_type).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # THIRD AND FINAL IS \"STYLES\"\r\n    if (is_dir(base64_decode($_GET[\'uninstall\']).\'/styles/\')) {\r\n        foreach (glob(base64_decode($_GET[\'uninstall\']).\'/styles/*.css\') as $theme_file) {\r\n            $template_name = $theme_folder_name.\'-\'.str_replace(\'.css\',\'\',basename($theme_file));\r\n            # SEARCHING FROM DATABASE IF THERE IS ALREADY \"PAGE\" WITH SAME NAME\r\n            $template_search = safe_rows(\'name\',\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n            # NO TEMPLATE EXISTS: DELETING\r\n            if (!empty($template_search)) {\r\n                safe_delete(\'txp_css\',\'`name` = \"\'.doSlash($template_name).\'\"\',0);\r\n                unset($template_contents);\r\n            }\r\n            unset($template_search);\r\n        }\r\n        unset($template_name);\r\n    }\r\n\r\n    # AFTER REMOVING PAGES, FORMS AND STYLES FROM DATABASE\r\n    # IT\'S TIME TO REMOVE \"install.txp\" SO THEME CAN BE REINSTALLED\r\n    unlink(base64_decode($_GET[\'uninstall\']).\'/installed.txp\');\r\n\r\n    # THEN USER IS REDIRECTED TO MAIN PAGE\r\n    header(\'location: index.php?event=kuo_theme&message=2\');\r\n    die;\r\n}\r\n# ENDS: UNINSTALLING A THEME\r\n\r\n# STARTS: UPLOADING A ZIPPED THEME\r\nif ((isset($_FILES[\'upload\'])) && (!empty($_FILES[\'upload\'])) && (isset($_POST[\'submit\'])) && (empty($_POST[\'submit\']))) {\r\n\r\n    # CHECKING IF ERRORS\r\n    if ((!isset($_FILES[\'upload\'][\'error\'])) or (is_array($_FILES[\'upload\'][\'error\']))) {\r\n        header(\'location: index.php?event=kuo_theme&message=5\');\r\n        die;\r\n    }\r\n\r\n    # PRELIMINARY TYPE CHECK\r\n    if (!in_array(str_replace(\'\"\',\'\',$_FILES[\'upload\'][\'type\']),array(\'application/zip\',\'application/octet-stream\',\'application/x-zip\',\'application/x-zip-compressed\'))) {\r\n        header(\'location: index.php?event=kuo_theme&message=6\');\r\n        die;\r\n    }\r\n\r\n\r\n    $kuo_zip_name = basename($_FILES[\'upload\'][\'name\']);\r\n    $kuo_zip_theme_name = substr($kuo_zip_name,0,-4);\r\n    $kuo_zip_size = basename($_FILES[\'upload\'][\'size\']);\r\n\r\n    # FIRST WE MUST CHECK IF A THEME WITH SAME NAME EXISTS OR NOT\r\n    if (file_exists(KUO_THEME_FOLDER.$kuo_zip_theme_name)) {\r\n        header(\'location: index.php?event=kuo_theme&message=1\');\r\n        die;\r\n    }\r\n\r\n    # IF THE THEME IS EITHER TOO LARGE OR SUSPICIOUSLY SMALL\r\n    # 1. IT MUST NOT BE EMPTY\r\n    # 2. IT MUST NOT BE 100: TOO SMALL TO BE A VALID THEME\r\n    # 3. IT MUST NOT BE OVER 10 MEGABYTES\r\n    if ((empty($kuo_zip_size)) or ($kuo_zip_size <= 100) or ($kuo_zip_size >= 10485760)) {\r\n        header(\'location: index.php?event=kuo_theme&message=4\');\r\n        die;\r\n    }\r\n\r\n    # CONTINUING TO CHECK UPLOADED FILE\r\n    $kuo_zip_resource = zip_open($_FILES[\'upload\'][\'tmp_name\']);\r\n    if (is_resource($kuo_zip_resource)) {\r\n        # CHECKING THAT ZIP HAS \"textpattern.txp\" ETC.\r\n        $kuo_count_textpattern_txt = 0;\r\n        while ($zip_entry = zip_read($kuo_zip_resource)) {\r\n            if (basename(zip_entry_name($zip_entry)) === \'textpattern.txp\') {\r\n                $kuo_count_textpattern_txt++;\r\n            }\r\n        }\r\n\r\n        # COUNTER IS ZERO SO THIS IS NOT A VALID THEME\r\n        if (empty($kuo_count_textpattern_txt)) {\r\n            header(\'location: index.php?event=kuo_theme&message=10\');\r\n            die;\r\n        }\r\n        unset($kuo_count_textpattern_txt);\r\n        zip_close($kuo_zip_resource);\r\n    }\r\n    else {\r\n        header(\'location: index.php?event=kuo_theme&message=7\');\r\n        die;\r\n    }\r\n\r\n    # CREATING FOLDER FOR THEME\r\n    if (!is_dir(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'/\')) {\r\n        mkdir(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'/\',0755);\r\n    }\r\n\r\n    # PROCESSING ZIP ARCHIVE CONTENTS TO ABOVE FOLDER\r\n    if (move_uploaded_file($_FILES[\'upload\'][\'tmp_name\'],KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\')) {\r\n        $kuo_actual_zip = new ZipArchive();\r\n        $kuo_actual_zip_check = $kuo_actual_zip->open(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\');\r\n        if ($kuo_actual_zip_check === true) {\r\n            $kuo_actual_zip->extractTo(KUO_THEME_FOLDER);\r\n            $kuo_actual_zip->close();\r\n            unset($kuo_actual_zip);\r\n            unset($kuo_actual_zip_check);\r\n\r\n            # DELETING TEMP CONTENT\r\n            if (file_exists(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\')) {\r\n                unlink(KUO_THEME_FOLDER.$kuo_zip_theme_name.\'_\'.md5($_FILES[\'upload\'][\'tmp_name\']).\'_tmp\');\r\n            }\r\n        }\r\n\r\n        # IF EXTRACTING THEME WAS OK = SUCCESS\r\n        header(\'location: index.php?event=kuo_theme&message=8\');\r\n        die;\r\n    }\r\n    else {\r\n        # IF AN ERROR DURING EXTRACTION\r\n        header(\'location: index.php?event=kuo_theme&message=9\');\r\n        die;\r\n    }\r\n}\r\n# ENDS: UPLOADING A ZIPPED THEME\r\n\r\necho \'<style type=\"text/css\" media=\"all\">\r\n#kuo_themes_area { font-family: Arial, Helvetica, sans-serif; color: #333; font-size: 1em; }\r\n#kuo_themes_area h2, #kuo_themes_area h3,#kuo_themes_area h4,#kuo_themes_area h5, #kuo_themes_area h6 { font: 3em Arial, Helvetica, sans-serif; }\r\n#kuo_themes_area p, #kuo_themes_area dl { font-size: 1.3em; margin: 0; padding: 0 0 10px 0; }\r\n#kuo_themes_area .kuo_theme_sort .kuo_theme_open { font-weight: bold; }\r\n#kuo_themes_area #kuo_themes_sidebar,\r\n#kuo_themes_area #kuo_themes_main { display: inline; float: left; }\r\n#kuo_themes_area #kuo_themes_sidebar { width: 23%; }\r\n#kuo_themes_area #kuo_themes_main { width: 75%; margin-left: 2%; }\r\n#kuo_themes_area .kuo_themes_clear { display: block; clear: both; }\r\n#kuo_themes_area #kuo_themes_sidebar dl {\r\nborder: 1px solid #eee;\r\npadding: 10px;\r\nmargin-bottom: 10px;\r\n}\r\n#kuo_themes_sidebar h2 {\r\npadding: 0;\r\nmargin: 0 0 10px 0;\r\n}\r\n#kuo_themes_area dl.kuo_theme {\r\ndisplay: inline-block;\r\nwidth: 48.5%;\r\nbackground: -webkit-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: -o-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: -moz-linear-gradient(#fcfcfc,#f0f0f0);\r\nbackground: linear-gradient(#fcfcfc,#f0f0f0);\r\npadding: 0;\r\nmargin: 0 1% 1% 0;\r\ntext-align: center;\r\nborder: 1px solid #ccc;\r\n}\r\n#kuo_themes_area dl.kuo_theme dt {\r\nfont-size: 1.3em;\r\nfont-weight: normal;\r\npadding: 2% 0 10px 0;\r\nmargin-bottom: 10px;\r\nbackground: -webkit-linear-gradient(#ccc,#fcfcfc);\r\nbackground: -o-linear-gradient(#ccc,#fcfcfc);\r\nbackground: -moz-linear-gradient(#ccc,#fcfcfc);\r\nbackground: linear-gradient(#eee,#fcfcfc);\r\ncolor: #333;\r\n}\r\n#kuo_themes_area dl.kuo_theme dd {\r\npadding: 0;\r\nmargin: 0;\r\n}\r\n#kuo_themes_area dl.kuo_theme img {\r\nwidth: 100%;\r\nheight: auto;\r\nmax-width: 380px;\r\nmax-height: 180px;\r\nmargin: 10px 0;\r\nborder: 5px solid #EBEBEB;\r\npadding: 5px;\r\nbackground: #fcfcfc;\r\n}\r\n#kuo_themes_area dl.kuo_theme img:hover {\r\nborder: 0;\r\npadding: 0;\r\nmax-width: 400px;\r\nmax-height: 200px;\r\n}\r\n#kuo_themes_area dl.kuo_theme dd.kuo_links {\r\npadding: 17px 0;\r\nbackground: #fff;\r\nborder-top: 1px solid #ccc;\r\n}\r\n#kuo_themes_area dd.kuo_links span {\r\ndisplay: none;\r\n}\r\n#kuo_themes_area dd.kuo_links a {\r\ntext-decoration: none;\r\npadding: 5px 15px;\r\ncolor: #ffc;\r\nbackground: #1A95D0;\r\nbackground: -webkit-linear-gradient(#1A95D0,#0070A2);\r\nbackground: -o-linear-gradient(#1A95D0,#0070A2);\r\nbackground: -moz-linear-gradient(#1A95D0,#0070A2);\r\nbackground: linear-gradient(#1A95D0,#0070A2);\r\nfont-weight: bold;\r\n}\r\n#kuo_themes_area dd.kuo_links a:last-of-type {\r\nbackground: #E69714;\r\nbackground: -webkit-linear-gradient(#E69714,#C2430E);\r\nbackground: -o-linear-gradient(#E69714,#C2430E);\r\nbackground: -moz-linear-gradient(#E69714,#C2430E);\r\nbackground: linear-gradient(#E69714,#C2430E);\r\nmargin-left: 1px;\r\n}\r\n#kuo_themes_area dd.kuo_links a.kuo_uninstall {\r\nbackground: #999;\r\nbackground: -webkit-linear-gradient(#999,#777);\r\nbackground: -o-linear-gradient(#999,#777);\r\nbackground: -moz-linear-gradient(#999,#777);\r\nbackground: linear-gradient(#999,#777);\r\n}\r\n#kuo_themes_area dd.kuo_links a:hover,\r\n#kuo_themes_area dd.kuo_links a:focus,\r\n#kuo_themes_area dd.kuo_links a:active {\r\ncolor: #ff0;\r\ntext-decoration: underline;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme {\r\nwidth: auto;\r\ndisplay: block;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme dd.kuo_links {\r\nmargin-top: 15px;\r\n}\r\n#kuo_themes_area .kuo_single_theme dl.kuo_theme dt {\r\nmargin-bottom: 0;\r\n}\r\n#kuo_themes_area .message {\r\nfont-size: 1.3em;\r\npadding: 1% 0;\r\nmargin-left: 25%;\r\n}\r\n#kuo_themes_area #kuo_themes_sidebar .message {\r\nmargin: 0 0 20px 0;\r\n}\r\n#kuo_themes_area .message strong {\r\nbackground: #ff0;\r\nborder: 1px dashed #a00;\r\npadding: 3px 10px;\r\n}\r\n#kuo_themes_area form,\r\n#kuo_themes_area ul,\r\n#kuo_themes_area ol,\r\n#kuo_themes_area form p,\r\n#kuo_themes_area form ul,\r\n#kuo_themes_area form ol,\r\n#kuo_themes_area table {\r\nfont: 1.2em Arial, Helvetica, sans-serif;\r\n}\r\n#kuo_themes_area label,\r\n#kuo_themes_area button,\r\n#kuo_themes_area input[type=file] {\r\ncursor: pointer;\r\n}\r\n#kuo_themes_area legend {\r\npadding: 0;\r\nmargin: 0;\r\nfont-size: 1.2em;\r\nfont-weight: bold;\r\n}\r\n#kuo_themes_area form p {\r\npadding: 15px 0;\r\nmargin: 0;\r\n}\r\n#kuo_themes_area label {\r\ndisplay: block;\r\npadding: 5px 5px 15px 5px;\r\nwidth: auto;\r\nfloat: none;\r\nclear: both;\r\n}\r\n#kuo_themes_area label code {\r\nborder: 0;\r\npadding: 0;\r\nbackground: 0;\r\ncolor: inherit;\r\nfont: 1em Arial, Helvetica, sans-serif;\r\n}\r\n#kuo_themes_area input[type=file] {\r\nbackground: #ffc;\r\nborder: 1px solid #ccc;\r\npadding: 10px;\r\nmargin: 0 5px 15px 5px;\r\ndisplay: block;\r\n}\r\n#kuo_themes_area button {\r\ndisplay: block;\r\ntext-align: center;\r\npadding: 5px 10px;\r\n}\r\n</style>\r\n<div id=\"kuo_themes_area\">\';\r\n\r\n# STARTS: IF THEME IS INSTALLED OR UNINSTALLED\r\nif ((isset($_GET[\'message\'])) && (!empty($_GET[\'message\'])) && (is_numeric($_GET[\'message\']))) {\r\n    if ($_GET[\'message\'] == 3) {\r\n        $kuo_message = \'Theme was installed.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 2) {\r\n        $kuo_message = \'Theme was uninstalled.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 1) {\r\n        $kuo_message = \'There is already a theme with that name.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 4) {\r\n        $kuo_message = \'Theme file size can not be over 10 Mb.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 5) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 6) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 7) {\r\n        $kuo_message = \'Not a valid .zip file.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 8) {\r\n        $kuo_message = \'Theme was uploaded.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 9) {\r\n        $kuo_message = \'Unable to upload theme.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 10) {\r\n        $kuo_message = \'Not a valid theme.\';\r\n    }\r\n    elseif ($_GET[\'message\'] == 11) {\r\n        $kuo_message = \'Unable to install that theme.\';\r\n    }\r\n    else {\r\n        header(\'location: index.php?event=kuo_theme\');\r\n        die;\r\n    }\r\n    echo \'<p class=\"message\"><strong>\'.$kuo_message.\'</strong></p>\';\r\n    unset($kuo_message);\r\n}\r\n# ENDS: IF THEME IS INSTALLED OR UNINSTALLED\r\n\r\necho \'<div id=\"kuo_themes_sidebar\"><h2>Themes</h2><dl><dt>Help:</dt><dd><ul>\r\n<li><a href=\"\'.KUO_THEME_TAB.\'&amp;help=get-themes\" rel=\"nofollow\">Get themes</a></li>\r\n<li><a href=\"\'.KUO_THEME_TAB.\'&amp;help=theme-structure\" rel=\"nofollow\">Theme structure</a></li>\r\n</ul></dd></dl>\';\r\n\r\n# IF THEMES FOLDER EXISTS AND IT HAS FOLDERS (THEMES) INSIDE IT\r\nif ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) !== 0)) {\r\n    \r\n        if (intval(count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR))) == 1) {\r\n            #$there_are_or_is_x_themes = \'is one valid theme\';\r\n        }\r\n        else {\r\n            if ((isset($_GET[\'sort\'])) && (!empty($_GET[\'sort\'])) && (in_array($_GET[\'sort\'],array(\'abc\',\'cba\',\'newest-first\',\'oldest-first\')))) {\r\n                $kuo_theme_active = filter_var($_GET[\'sort\'],FILTER_SANITIZE_STRING);\r\n            }\r\n            else {\r\n                $kuo_theme_active = NULL;\r\n            }\r\n\r\n            # AT THE SAME POINT SOME OTHER FUNCTIONS\r\n            echo \'<dl class=\"kuo_theme_sort\"><dt>Sort by name:</dt><dd><ul><li\';\r\n            if ((empty($kuo_theme_active)) or ($kuo_theme_active === \'abc\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=abc\" rel=\"nofollow\">ABC</a></li><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'cba\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=cba\" rel=\"nofollow\">CBA</a></li></ul></dd></dl><dl class=\"kuo_theme_sort\"><dt>Sort by freshness:</dt><dd><ul><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'newest-first\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=newest-first\" rel=\"nofollow\">Newest first</a></li><li\';\r\n            if ((!empty($kuo_theme_active)) && ($kuo_theme_active === \'oldest-first\')) {\r\n                echo KUO_ACTIVE_CSS_CLASS;\r\n            }\r\n            echo \'><a href=\"\'.KUO_THEME_TAB.\'&amp;sort=oldest-first\" rel=\"nofollow\">Oldest first</a></li></ul></dd></dl>\';\r\n\r\n            # THIS IS NOT RELIABLE BECAUSE IT COUNTS ONLY FOLDERS AND NOT CHECK THEIR CONTENTS\r\n            #$there_are_or_is_x_themes = \'are \'.intval(count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR))).\' valid themes\';\r\n        }\r\n        # ELSE ENDS\r\n        \r\n        # ECHOING HTML FORM FOR ZIP UPLOADING\r\n        kuo_upload_theme();\r\n\r\n        # STARTS: IF VIEWING \"ABOUT\" IF SINGLE THEME\r\n        if ((isset($_GET[\'about\'])) && (!empty($_GET[\'about\'])) && (base64_encode(base64_decode($_GET[\'about\'],true)) === $_GET[\'about\']) && (!isset($_GET[\'help\']))) {\r\n            # FETCHING THEME DATA FROM \"THEMES\" FOLDER\r\n            if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (file_exists(base64_decode($_GET[\'about\']))) && (file_exists(base64_decode($_GET[\'about\']).\'/textpattern.txp\')) && (is_readable(base64_decode($_GET[\'about\']).\'/textpattern.txp\'))) {\r\n                $kuo_about_this_theme = explode(PHP_EOL,file_get_contents(base64_decode($_GET[\'about\']).\'/textpattern.txp\',0,NULL,0,900));\r\n            }\r\n            else {\r\n                $kuo_about_this_theme = NULL;\r\n                echo \'<p class=\"message\"><strong>Error. Unable to fetch theme information.</strong></p>\';\r\n            }\r\n        \r\n            # STARTS: FETCHING THEME NAME\r\n            $kuo_about_this_theme_name = $kuo_about_this_theme[0];\r\n            if (!empty($kuo_about_this_theme_name)) {\r\n                $kuo_about_this_theme_name = str_replace(\'Theme name: \',\'\',$kuo_about_this_theme_name);\r\n                if (is_numeric($kuo_about_this_theme_name)) {\r\n                    $kuo_about_this_theme_name = filter_var($kuo_about_this_theme_name,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_name = filter_var($kuo_about_this_theme_name,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_name = \'Unknown\';\r\n            }\r\n            # ENDS: FETCHING THEME NAME\r\n\r\n            # STARTS: FETCHING THEME AUTHOR NAME\r\n            $kuo_about_this_theme_author = $kuo_about_this_theme[1];\r\n            if (!empty($kuo_about_this_theme_author)) {\r\n                $kuo_about_this_theme_author = str_replace(\'Theme author: \',\'\',$kuo_about_this_theme_author);\r\n                if (is_numeric($kuo_about_this_theme_author)) {\r\n                    $kuo_about_this_theme_author = filter_var($kuo_about_this_theme_author,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_author = filter_var($kuo_about_this_theme_author,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_author = \'Unknown\';\r\n            }\r\n            # ENDS: FETCHING THEME AUTHOR NAME\r\n\r\n            # STARTS: FETCHING THEME URL\r\n            $kuo_about_this_theme_url = $kuo_about_this_theme[2];\r\n            if (!empty($kuo_about_this_theme_url)) {\r\n                $kuo_about_this_theme_url = str_replace(\'Theme URL: \',\'\',$kuo_about_this_theme_url);\r\n                $kuo_about_this_theme_url = filter_var($kuo_about_this_theme_url,FILTER_SANITIZE_URL);\r\n                if (!empty($kuo_about_this_theme_url)) {\r\n                    $kuo_about_this_theme_url = \', <a href=\"\'.$kuo_about_this_theme_url.\'\" rel=\"nofollow external\" target=\"_new\">\'.$kuo_about_this_theme_url.\'</a>\';\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_url = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME URL\r\n\r\n            # STARTS: FETCHING THEME VERSION NUMBER\r\n            $kuo_about_this_theme_version = $kuo_about_this_theme[3];\r\n            if (!empty($kuo_about_this_theme_version)) {\r\n                $kuo_about_this_theme_version = str_replace(\'Theme version: \',\'\',$kuo_about_this_theme_version);\r\n                if (is_numeric($kuo_about_this_theme_version)) {\r\n                    $kuo_about_this_theme_version = filter_var($kuo_about_this_theme_version,FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_version = filter_var($kuo_about_this_theme_version,FILTER_SANITIZE_STRING);\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_version = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME VERSION NUMBER\r\n\r\n            # STARTS: FETCHING THEME DESCRIPTION\r\n            $kuo_about_this_theme_description = $kuo_about_this_theme[4];\r\n            if (!empty($kuo_about_this_theme_description)) {\r\n                $kuo_about_this_theme_description = str_replace(\'Theme description: \',\'\',$kuo_about_this_theme_description);\r\n                if (is_numeric($kuo_about_this_theme_description)) {\r\n                    $kuo_about_this_theme_description = filter_var($kuo_about_this_theme_description,FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_description = filter_var($kuo_about_this_theme_description,FILTER_SANITIZE_STRING);\r\n                }\r\n                if (!empty($kuo_about_this_theme_description)) {\r\n                    $kuo_about_this_theme_description = \'<dd>\'.$kuo_about_this_theme_description.\'</dd>\';\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_description = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_description = NULL;\r\n            }\r\n            # ENDS: FETCHING THEME DESCRIPTION\r\n\r\n            # STARTS: LOOKING FOR THEME SCREENSHOT\r\n            if ((file_exists(base64_decode($_GET[\'about\']).\'/screenshot.png\')) && (is_readable(base64_decode($_GET[\'about\']).\'/screenshot.png\'))) {\r\n                $kuo_about_this_theme_screenshot = \'<img src=\"\'.base64_decode($_GET[\'about\']).\'/screenshot.png\" alt=\"\" height=\"200\" width=\"400\" />\';\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_screenshot = KUO_NO_SCREENSHOT_IMG;\r\n            }\r\n            # ENDS: LOOKING FOR THEME SCREENSHOT\r\n\r\n            # STARTS: LOOKING FOR THEME \"LOGIC\"\r\n            if (isset($kuo_about_this_theme[5])) {\r\n                $kuo_about_this_theme_logic = $kuo_about_this_theme[5];\r\n                if (!empty($kuo_about_this_theme_logic)) {\r\n                    $kuo_about_this_theme_logic = str_replace(\'Theme logic: \',\'\',$kuo_about_this_theme_logic);\r\n                    $kuo_about_this_theme_logic = explode(\';\',$kuo_about_this_theme_logic);\r\n\r\n                    foreach ($kuo_about_this_theme_logic as $kuo_theme_logic) {\r\n                        # IF THEME HAS A SPECIFIC DEFAULT SECTION\r\n                        if (strpos($kuo_theme_logic,\'default_page=\') !== false) {\r\n                            $kuo_about_this_theme_default_page = \'<li>Default page: <code>\'.str_replace(\'default_page=\',basename(base64_decode($_GET[\'about\'])).\'-\',$kuo_theme_logic).\'</code></li>\';\r\n                        }\r\n\r\n                        # IF THEME REQUIRES SOME PLUGINS IN ORDER TO WORK PROPERLY\r\n                        if (strpos($kuo_theme_logic,\'required_plugins=\') !== false) {\r\n                            $kuo_about_this_theme_required_plugins = \'<li>Required plugins: <code>\'.str_replace(array(\'required_plugins=\',\',\'),array(\'\',\', \'),$kuo_theme_logic).\'</code></li>\';\r\n                        }\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_about_this_theme_logic = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_about_this_theme_logic = NULL;\r\n            }\r\n            # ENDS: LOOKING FOR THEME \"LOGIC\"\r\n\r\n            echo \'</div><div id=\"kuo_themes_main\" class=\"kuo_single_theme\"><h2><a href=\"\'.KUO_THEME_TAB.\'\" rel=\"prev\">Available themes:</a> \'.$kuo_about_this_theme_name.\' \'.$kuo_about_this_theme_version.\'</h2>\';\r\n\r\n            # WHEN IT WAS INSTALLED?\r\n            # (ONLY IF INSTALL FILE IS FOUND)\r\n            if ((file_exists(base64_decode($_GET[\'about\']).\'/installed.txp\')) && (is_readable(base64_decode($_GET[\'about\']).\'/installed.txp\'))) {\r\n                $kuo_when_installed = file_get_contents(base64_decode($_GET[\'about\']).\'/installed.txp\',NULL,NULL,0,19);\r\n                echo \'<p>Installed: \'.filter_var($kuo_when_installed).\'</p>\';\r\n                unset($kuo_when_installed);\r\n            }\r\n\r\n            echo \'<dl class=\"kuo_theme\"><dt>By \'.utf8_encode($kuo_about_this_theme_author.$kuo_about_this_theme_url).\'</dt>\'.utf8_encode($kuo_about_this_theme_screenshot.$kuo_about_this_theme_description);\r\n\r\n            # THIS MEANS THEME IS INSTALLED\r\n            if (file_exists(base64_decode($_GET[\'about\']).\'/installed.txp\')) {\r\n                $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;uninstall=\'.$_GET[\'about\'].\'\" rel=\"nofollow\" class=\"kuo_uninstall\" onclick=\"return confirm(\\\'Do you really want to uninstall this theme?\\\')\">Uninstall</a>\';\r\n            }\r\n            # OTHERWISE THEME IS NOT INSTALLED\r\n            else {\r\n                $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;install=\'.$_GET[\'about\'].\'\" rel=\"nofollow\">Install</a>\';\r\n            }\r\n\r\n            echo \'<dd class=\"kuo_links\">\'.$kuo_action_link.\'</dd></dl>\';\r\n\r\n            # AT THIS POINT THEME\'S \"LOGIC\" THING CAN BE USED\r\n            # TO INFORM USER ABOUT HOW THIS THEME IS INTENDED TO BE USED\r\n            # AND WHAT PLUGINS, IF ANY, IT USES OR REQUIRES\r\n            if ((isset($kuo_about_this_theme_logic)) && (!empty($kuo_about_this_theme_logic))) {\r\n                echo \'<dl><dt>Theme instructions</dt><dd><ul>\'.$kuo_about_this_theme_default_page.$kuo_about_this_theme_required_plugins.\'</ul></dd></dl>\';\r\n            }\r\n\r\n            echo \'</div><br class=\"kuo_themes_clear\" /></div>\';\r\n        }\r\n        # ENDS: IF VIEWING \"ABOUT\" IF SINGLE THEME\r\n        # STARTS: IF VIEWING \"HELP\" PAGE\r\n        elseif ((isset($_GET[\'help\'])) && (!empty($_GET[\'help\'])) && (!isset($_GET[\'about\']))) {\r\n            # SHOWING WHAT THE CURRENT THEME STRUCTURE LOOKS LIKE\r\n            if ($_GET[\'help\'] === \'theme-structure\') {\r\n                $kuo_page_heading = \'Theme structure\';\r\n                $kuo_page_content = \'<pre>website.com/themes/\r\n    website.com/themes/mytheme/\r\n        website.com/themes/mytheme/pages/\r\n            website.com/themes/mytheme/pages/pagename.txp\r\n            website.com/themes/mytheme/pages/another-page.txp\r\n        website.com/themes/mytheme/forms/\r\n            website.com/themes/mytheme/forms/article.default.txp\r\n            website.com/themes/mytheme/forms/article.second-form.txp\r\n            website.com/themes/mytheme/forms/misc.tracking-code.txp\r\n            website.com/themes/mytheme/forms/misc.another_test.txp\r\n        website.com/themes/mytheme/styles/\r\n            website.com/themes/mytheme/styles/layout.css\r\n            website.com/themes/mytheme/styles/another.css\r\n        website.com/themes/mytheme/textpattern.txp\r\n        website.com/themes/mytheme/screenshot.png</pre>\r\n        <h3>What is inside <code>textpattern.txp</code>?</h3>\r\n        <pre>Theme name: Theme name\r\nTheme author: Author name\r\nTheme URL: http://www.website.com/\r\nTheme version: version.number\r\nTheme description: A short description.\r\nTheme logic: default_page=default;required_plugins=none;</pre>\r\n<p>The &quot;Theme logic&quot; part can be left out. It can be used to help the user to get theme working properly if it requires some Textpattern plugins. If plugins are required, use instead of <code>none</code> a comma-separated list like this:\r\n<code>dzd_counter_view,aks_header,rah_metas</code>.</p>\';\r\n            }\r\n            # HELPING USER TO LOCATE SUITABLE THEMES\r\n            elseif ($_GET[\'help\'] === \'get-themes\') {\r\n                $kuo_page_heading = \'Get themes\';\r\n                \r\n                # CHECKING IF FILE EXISTS IN EXTERNAL SOURCE\r\n                if (@get_headers(KUO_DEFAULT_THEME_FEED)[0] !== \'HTTP/1.1 404 Not Found\') {\r\n                    \r\n                    # IF CACHED FILE IS NOT THERE OR IT\'S TOO OLD\r\n                    if ((!file_exists(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) or (!is_readable(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) or (filectime(KUO_THEME_FOLDER.\'kuo_themes_list.txt\')) <= time()-strtotime(\'-1 hour\')) {\r\n                        $kuo_page_content = file_get_contents(KUO_DEFAULT_THEME_FEED,0);\r\n                        if (!empty($kuo_page_content)) {\r\n                            file_put_contents(KUO_THEME_FOLDER.\'kuo_themes_list.txt\',$kuo_page_content);\r\n                        }\r\n                        else {\r\n                            $kuo_page_content = NULL;\r\n                        }\r\n                    }\r\n                    else {\r\n                        $kuo_page_content = file_get_contents(KUO_THEME_FOLDER.\'kuo_themes_list.txt\',0);\r\n                    }\r\n                    \r\n                    # PROCESSING DATA IF AVAILABLE\r\n                    if ((isset($kuo_page_content)) && (!empty($kuo_page_content))) {\r\n                        $kuo_page_content = explode(\'|\',$kuo_page_content);\r\n                        foreach ($kuo_page_content as $kuo_page_single_theme) {\r\n                            $kuo_page_themes[] = explode(\';;\',$kuo_page_single_theme);\r\n                        }\r\n                        \r\n                        # HANDLING SINGLE THEME DATA\r\n                        if ((is_array($kuo_page_themes)) && (!empty($kuo_page_themes))) {\r\n                            $kuo_page_content = NULL;\r\n                            foreach ($kuo_page_themes as $kuo_page_single_theme) {\r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME NAME                                \r\n                                if ((isset($kuo_page_single_theme[0])) && (!empty($kuo_page_single_theme[0]))) {\r\n                                    $kuo_page_single_theme_name = filter_var($kuo_page_single_theme[0],FILTER_SANITIZE_STRING);\r\n                                    if (empty($kuo_page_single_theme_name)) {\r\n                                        $kuo_page_single_theme_name = \'(Empty)\';\r\n                                    }\r\n                                    else {\r\n                                        if (strlen($kuo_page_single_theme_name) >= 40) {\r\n                                            $kuo_page_single_theme_name = substr($kuo_page_single_theme_name,0,39).\'&hellip;\';\r\n                                        }\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_name = \'(Empty)\';\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME NAME    \r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME DESCRIPTION\r\n                                if ((isset($kuo_page_single_theme[5])) && (!empty($kuo_page_single_theme[5]))) {\r\n                                    $kuo_page_single_theme_description = filter_var($kuo_page_single_theme[5],FILTER_SANITIZE_STRING);\r\n                                    if (empty($kuo_page_single_theme_description)) {\r\n                                        $kuo_page_single_theme_description = NULL;\r\n                                    }\r\n                                    else {\r\n                                        \r\n                                        if (strlen($kuo_page_single_theme_description) >= 50) {\r\n                                            $kuo_page_single_theme_description = substr($kuo_page_single_theme_description,0,49).\'&hellip;\';\r\n                                        }\r\n                                        \r\n                                        $kuo_page_single_theme_description = \'<dd class=\"kuo_theme_description\">\'.utf8_encode($kuo_page_single_theme_description).\'</dd>\';\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_description = NULL;\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME DESCRIPTION\r\n                                \r\n                                # BEGINS: CHECKING EXTERNAL THEME SCREENSHOT\r\n                                if ((isset($kuo_page_single_theme[1])) && (!empty($kuo_page_single_theme[1])) && (filter_var($kuo_page_single_theme[1],FILTER_VALIDATE_URL) == true) \r\n                                && (in_array(pathinfo($kuo_page_single_theme[1],PATHINFO_EXTENSION),array(\'png\',\'jpg\',\'gif\',\'jpeg\')))) {\r\n                                    \r\n                                    # TO SPEED THIS PART OF PAGE LOADING IMAGES MUST BE CACHED\r\n                                    if (!is_dir(KUO_THEME_FOLDER.\'kuo_cache\')) {\r\n                                        mkdir(KUO_THEME_FOLDER.\'kuo_cache\',0755);\r\n                                    }\r\n                                    \r\n                                    # DOWNLOADING THE THEME SCREENSHOT FROM EXTERNAL SERVER\r\n                                    # IF THEME FEED IS FROM KUOPASSA.NET, SCREENSHOT IS A 470x250 THUMBNAIL\r\n                                    if ((!file_exists(KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]))) && (@get_headers($kuo_page_single_theme[1])[0] !== \'HTTP/1.1 404 Not Found\')) {                                    \r\n                                        file_put_contents(KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]),file_get_contents(filter_var($kuo_page_single_theme[1],FILTER_SANITIZE_URL)));\r\n                                    }                                    \r\n                                    \r\n                                    $kuo_page_single_theme_screenshot = \'<dd><img src=\"\'.KUO_THEME_FOLDER.\'kuo_cache/\'.md5($kuo_page_single_theme_name).\'-\'.basename($kuo_page_single_theme[1]).\'\" alt=\"A screenshot of &quot;\'.utf8_encode($kuo_page_single_theme_name).\'&quot;.\" height=\"200\" width=\"400\" /></dd>\';\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_screenshot = \'<dd>\'.KUO_NO_SCREENSHOT_IMG.\'</dd>\';\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME SCREENSHOT\r\n                                \r\n                                # STARTS: CHECKING EXTERNAL THEME DOWNLOAD LINK\r\n                                if ((isset($kuo_page_single_theme[2])) && (!empty($kuo_page_single_theme[2])) && (filter_var($kuo_page_single_theme[2],FILTER_VALIDATE_URL) == true)) {\r\n                                    $kuo_page_single_theme_download = filter_var($kuo_page_single_theme[2],FILTER_SANITIZE_URL);\r\n                                    if (!empty($kuo_page_single_theme_download)) {\r\n                                        $kuo_page_single_theme_download = \'<dd class=\"kuo_links\"><a href=\"\'.$kuo_page_single_theme_download.\'\" rel=\"nofollow external\">Download</a></dd>\';\r\n                                    }\r\n                                    else {\r\n                                        $kuo_page_single_theme_download = NULL;\r\n                                    }\r\n                                }\r\n                                else {\r\n                                    $kuo_page_single_theme_download = NULL;\r\n                                }\r\n                                # ENDS: CHECKING EXTERNAL THEME DOWNLOAD LINK                                \r\n\r\n                                # OUTPUTTING SINGLE THEME\r\n                                $kuo_page_content .= \'<dl class=\"kuo_theme\"><dt class=\"kuo_theme_name\">\'.utf8_encode(htmlspecialchars($kuo_page_single_theme_name)).\'</dt>\'.$kuo_page_single_theme_description.$kuo_page_single_theme_screenshot.$kuo_page_single_theme_download.\'</dl>\';\r\n                            }\r\n                        }\r\n                        else {\r\n                            $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                        }\r\n\r\n                    }\r\n                    else {\r\n                        $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                    }\r\n\r\n                }\r\n                else {\r\n                    $kuo_page_content = \'<p>Unable to fetch theme feed.</p>\';\r\n                }\r\n            }\r\n            else {\r\n                $kuo_page_heading = \'404\';\r\n                $kuo_page_content = \'Page was not found.\';\r\n            }\r\n            echo \'</div><div id=\"kuo_themes_main\"><h2><a href=\"\'.KUO_THEME_TAB.\'\" rel=\"prev\">Available themes:</a> \'.$kuo_page_heading.\'</h2>\'.$kuo_page_content.\'</div><br class=\"kuo_themes_clear\" /></div>\';\r\n        }\r\n        # ENDS: IF VIEWING \"HELP\" PAGE\r\n        # STARTS: IF MAIN PAGE OF THIS PLUGIN\r\n        else {\r\n            kuo_theme_list();\r\n        }\r\n        # ENDS: IF MAIN PAGE OF THIS PLUGIN\r\n    }\r\n    # IF THEMES FOLDER EXISTS BUT NO THEMES EXIST\r\n    elseif ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) == 0)) {\r\n        echo \'<p class=\"message\"><strong>Themes were not found.</strong></p>\';\r\n        kuo_upload_theme();\r\n    }\r\n    # IF THEMES FOLDER DOES NOT EXIST\r\n    else {\r\n        echo \'<p class=\"message\"><strong>Theme folder does not exist. <a href=\"\'.KUO_THEME_TAB.\'&amp;fix=folder\" rel=\"nofollow\">Create it now?</a></strong></p>\';\r\n    }\r\n    echo \'<br class=\"kuo_themes_clear\" /></div>\';\r\n}\r\n# ENDING: ACTUAL THEME FUNCTIONS TAB\r\n\r\n##########################\r\n\r\n# BEGINNING: BLOCK FOR FILE UPLOAD FORM\r\nfunction kuo_upload_theme() {\r\n    echo \'<form action=\"\'.KUO_THEME_TAB.\'&amp;upload\" method=\"post\" enctype=\"multipart/form-data\"><fieldset><legend>Upload new themes</legend>\r\n    <p><label for=\"upload-zip\">Choose a <code>.zip</code> file with maximum 10 Mb size:</label><input id=\"upload-zip\" \r\n    type=\"file\" name=\"upload\" accept=\".zip\" required=\"required\" /><button name=\"submit\" type=\"submit\">Upload</button></p></fieldset></form>\';\r\n}\r\n# ENDING: BLOCK FOR FILE UPLOAD FORM\r\n\r\n##########################\r\n\r\n# BEGINNING: LISTING CONTENTS OF THEME FOLDER\r\nfunction kuo_theme_list() {\r\n    # GOING THROUGH THE THEME LIST\r\n    if ((is_dir(KUO_THEME_FOLDER)) && (is_readable(KUO_THEME_FOLDER)) && (count(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR)) !== 0)) {\r\n        $kuo_theme_settings_meta = array();\r\n\r\n        if ((isset($_GET[\'sort\'])) && (!empty($_GET[\'sort\'])) && (in_array($_GET[\'sort\'],array(\'abc\',\'cba\',\'newest-first\',\'oldest-first\')))) {\r\n            $kuo_theme_sort_type = filter_var($_GET[\'sort\'],FILTER_SANITIZE_STRING);\r\n\r\n            if ($kuo_theme_sort_type === \'abc\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR|GLOB_NOSORT);\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'cba\') {\r\n                $kuo_theme_sorter = array_reverse(glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR));\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'newest-first\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n                array_multisort(array_map(\'filectime\',$kuo_theme_sorter),SORT_NUMERIC,SORT_DESC,$kuo_theme_sorter);\r\n            }\r\n            elseif ($kuo_theme_sort_type === \'oldest-first\') {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n                array_multisort(array_map(\'filectime\',$kuo_theme_sorter),SORT_NUMERIC,SORT_ASC,$kuo_theme_sorter);\r\n            }\r\n            else {\r\n                $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_sorter = glob(KUO_THEME_FOLDER.\'*\',GLOB_ONLYDIR);\r\n        }\r\n\r\n        echo \'</div><div id=\"kuo_themes_main\">\';\r\n\r\nforeach ($kuo_theme_sorter as $kuo_theme) {\r\n# CHECKING IF THEME SETTINGS FILE EXISTS\r\nif ((file_exists($kuo_theme.\'/textpattern.txp\')) && (is_readable($kuo_theme.\'/textpattern.txp\'))) {\r\n    $kuo_theme_meta  = explode(PHP_EOL,file_get_contents($kuo_theme.\'/textpattern.txp\',0,NULL,0,300));\r\n    $kuo_theme_meta_counter = 0;\r\n\r\n    foreach ($kuo_theme_meta as $kuo_theme_name => $kuo_theme_value) {\r\n        $kuo_theme_meta_counter++;\r\n        $kuo_single_theme_dl = NULL;\r\n\r\n        if ($kuo_theme_meta_counter == 1) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme name: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 2) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme author: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 3) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme URL: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 4) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme version: \',\'\',$kuo_theme_value);\r\n        }\r\n        elseif ($kuo_theme_meta_counter == 5) {\r\n            $kuo_theme_meta[$kuo_theme_name] = str_replace(\'Theme description: \',\'\',$kuo_theme_value);\r\n        }\r\n        else {\r\n            $kuo_theme_meta[$kuo_theme_name] = explode(\':\',$kuo_theme_value);\r\n        }\r\n    }\r\n    # FOREACH ENDS\r\n\r\n    # OUTPUTTING THEME DATA\r\n    if ((isset($kuo_theme_meta[0])) && (!empty($kuo_theme_meta[0]))) {\r\n        if (is_numeric($kuo_theme_meta[0])) {\r\n            $kuo_theme_name = filter_var($kuo_theme_meta[0],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_theme_name = filter_var($kuo_theme_meta[0],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        if (strlen($kuo_theme_name) >= 50) {\r\n            $kuo_theme_name = substr($kuo_theme_name,0,49).\'&hellip;\';\r\n        }\r\n\r\n        if (empty($kuo_theme_name)) {\r\n            $kuo_theme_name = \'(Empty)\';\r\n        }\r\n\r\n        # IF THEME HAS VERSION NUMBER\r\n        if ((isset($kuo_theme_meta[3])) && (!empty($kuo_theme_meta[3]))) {\r\n            # NO STANDARD WAY OF VERSIONING, SO...\r\n            if (is_numeric($kuo_theme_meta[3])) {\r\n                $kuo_theme_version = filter_var($kuo_theme_meta[3],FILTER_SANITIZE_NUMBER_FLOAT,FILTER_FLAG_ALLOW_FRACTION);\r\n            }\r\n            else {\r\n                $kuo_theme_version = filter_var($kuo_theme_meta[3],FILTER_SANITIZE_STRING);\r\n            }\r\n\r\n            if (!empty($kuo_theme_version)) {\r\n                $kuo_theme_version = \' (\'.$kuo_theme_version.\')\';\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_version = NULL;\r\n        }\r\n\r\n        # IF THEME HAS DESCRIPTION\r\n        if ((isset($kuo_theme_meta[4])) && (!empty($kuo_theme_meta[4]))) {\r\n            $kuo_theme_description = filter_var($kuo_theme_meta[4],FILTER_SANITIZE_STRING);\r\n            if (strlen($kuo_theme_description) >= 61) {\r\n                $kuo_theme_description = substr($kuo_theme_description,0,60).\'&hellip;\';\r\n            }\r\n\r\n            if (empty($kuo_theme_description)) {\r\n                $kuo_theme_description = NULL;\r\n            }\r\n            else {\r\n                $kuo_theme_description = \'<dd class=\"kuo_theme_description\">\'.$kuo_theme_description.\'</dd>\';\r\n            }\r\n        }\r\n        else {\r\n            $kuo_theme_description = NULL;\r\n        }\r\n        \r\n        $kuo_single_theme_dl .= \'<dl class=\"kuo_theme\"><dt class=\"kuo_theme_name\">\'.utf8_encode(htmlspecialchars($kuo_theme_name.$kuo_theme_version)).\'</dt>\'.utf8_encode($kuo_theme_description);\r\n    }\r\n    # ENDING IF THEME HAS META\r\n\r\n    # IF THEME HAS SCREENSHOT AND IT\'S OK\r\n    if ((file_exists($kuo_theme.\'/screenshot.png\')) && (is_readable($kuo_theme.\'/screenshot.png\')) && (filesize($kuo_theme.\'/screenshot.png\') != 0)) {\r\n        $kuo_single_theme_screenshot = \'<dd><img src=\"\'.$kuo_theme.\'/screenshot.png\'.\'\" alt=\"A screenshot of &quot;\'.utf8_encode($kuo_theme_name).\'&quot;.\" height=\"200\" width=\"400\" /></dd>\';\r\n    }\r\n    else {\r\n        $kuo_single_theme_screenshot = \'<dd>\'.KUO_NO_SCREENSHOT_IMG.\'</dd>\';\r\n    }\r\n\r\n    # THIS MEANS THEME IS INSTALLED\r\n    if (file_exists($kuo_theme.\'/installed.txp\')) {\r\n        $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;uninstall=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\" class=\"kuo_uninstall\" onclick=\"return confirm(\\\'Do you really want to uninstall this theme?\\\')\">Uninstall</a>\';\r\n    }\r\n    # OTHERWISE THEME IS NOT INSTALLED\r\n    else {\r\n        $kuo_action_link = \'<a href=\"\'.KUO_THEME_TAB.\'&amp;install=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\">Install</a>\';\r\n    }\r\n\r\n    # LINKS: INFO AND INSTALL/UNINSTALL\r\n    $kuo_single_theme_dl .= $kuo_single_theme_screenshot.\'<dd class=\"kuo_links\"><a href=\"\'.KUO_THEME_TAB.\'&amp;about=\'.base64_encode($kuo_theme).\'\" rel=\"nofollow\">About</a><span> &bull; </span>\'.$kuo_action_link.\'</dd></dl>\';\r\n    \r\n    if (empty($kuo_theme_name)) {\r\n        $kuo_single_theme_dl = NULL;\r\n    }\r\n}\r\nelse {\r\n    $kuo_single_theme_dl = NULL;\r\n}\r\n# ENDING IF FILE EXIST\r\n\r\n    if ((isset($kuo_single_theme_dl)) && ($kuo_single_theme_dl !== NULL)) {\r\n        echo $kuo_single_theme_dl;\r\n    }\r\n}\r\n# ENDING FOREACH\r\nunset($kuo_single_theme_dl);\r\necho \'</div>\';\r\n}\r\nelse {\r\n    return;\r\n}\r\n\r\n}\r\n# ENDING LISTING CONTENTS OF THEME FOLDER\r\n\r\n}\r\n# ENDING IF ADMIN', 'e06b9241c30fe832c020d88cc2b48224', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_tinymce_cdn', 0, 'Petri Ikonen', 'http://kuopassa.net/', '0.2', 'Fetching TinyMCE from CDN and activating it in the Write section.', '<ul id=\"links\">\n<li id=\"kuo_tinymce_cdn\"><a href=\"#kuo_tinymce_cdn\">kuo_tinymce_cdn</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>Briefly about this plugin</h2>\n<p>Fetching TinyMCE 4.1 from a content delivery network (<code>tinymce.cachefly.net/4.1/tinymce.min.js</code>), inserting it to both of the <code>textarea</code>s (body and excerpt) at the Write tab.</p>\n<p id=\"notice\">This plugin is suitable for sites where ALL articles are to be handled with TinyMCE.</p>\nIf the plugin is activated, it\'s activated in all of the articles&hellip; No toggling on and off per article, at least not yet. Also:</p>\n<ul>\n<li>Please leave text untouched for articles (Textile off).</li>\n<li>TinyMCE is loaded in English.</li>\n<li>Use at your own risk.</li>\n</ul>\n<hr />\n<h4>Special thanks to</h4>\n<ul>\n<li>photonomad for <code>tinyMCE.triggerSave();</code></li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style>#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 1% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_tinymce_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_tinymce_head() {\r\n\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\necho \'<script type=\"text/javascript\" src=\"//tinymce.cachefly.net/4.1/tinymce.min.js\"></script>\r\n<script type=\"text/javascript\">\r\ntinymce.init({entity_encoding:\"raw\",selector:\"textarea#body,textarea#excerpt\",removed_menuitems:\"newdocument\",plugins:[\"code\"]});\r\n$(document).ready(function(){\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n\r\nif ($(\"form.async\")) {\r\n   $(\"form.async\").on(\"click\", \"input[type=submit]\", function (evt) {\r\n        tinyMCE.triggerSave();\r\n   });\r\n}\r\n\r\n});\r\n</script>\';\r\n}\r\n\r\n}', 'if (@txpinterface == \'admin\') {\r\nregister_callback(\'kuo_tinymce_head\',\'admin_side\',\'head_end\');\r\n}\r\n\r\nfunction kuo_tinymce_head() {\r\n\r\nif ($GLOBALS[\'event\'] === \'article\') {\r\necho \'<script type=\"text/javascript\" src=\"//tinymce.cachefly.net/4.1/tinymce.min.js\"></script>\r\n<script type=\"text/javascript\">\r\ntinymce.init({entity_encoding:\"raw\",selector:\"textarea#body,textarea#excerpt\",removed_menuitems:\"newdocument\",plugins:[\"code\"]});\r\n$(document).ready(function(){\r\n$(\"select#markup-body option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-excerpt option[value=0]\").attr(\"selected\",\"selected\");\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n$($(\"select#markup-body\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-body\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n$($(\"select#markup-excerpt\")).change(function(){\r\nif ($(this).val() == 0) {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #690\");\r\n}\r\nelse {\r\n$(\"select#markup-excerpt\").css(\"border\",\"1px solid #c00\");\r\n}\r\n});\r\n\r\nif ($(\"form.async\")) {\r\n   $(\"form.async\").on(\"click\", \"input[type=submit]\", function (evt) {\r\n        tinyMCE.triggerSave();\r\n   });\r\n}\r\n\r\n});\r\n</script>\';\r\n}\r\n\r\n}', '07a731684997c90ceec434a363c58ee6', '', '', 5, 5, 0),
('kuo_trigger_save', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Trigger \"Save\" button click with a keyboard key combination.', 'A tiny plugin that attempts to save articles by using keyboard combination <kbd>Ctrl</kbd>&plus;<kbd>Enter</kbd>.', '# BEGINS: ADMIN SIDE\r\nif (txpinterface === \'admin\') {\r\n\r\n    register_callback(\'kuo_trigger_save\',\'admin_side\',\'head_end\');\r\n\r\n    # BEGINS: FUNCTION: kuo_trigger_save()\r\n    function kuo_trigger_save($event,$step) {\r\n\r\n        echo \'\r\n        <script>\r\n            $(window).keypress(function(e) {\r\n                if (e.which == 13 && e.ctrlKey) {\r\n                    $(\"input[name$=\\\'ave\\\'],input[name$=\\\'ublish\\\'],input#ied_editSave\").click();\r\n                }\r\n            });\r\n        </script>\r\n        \';\r\n    }\r\n    # ENDS: FUNCTION: kuo_trigger_save()\r\n}\r\n# ENDS: ADMIN SIDE\r\n', '# BEGINS: ADMIN SIDE\r\nif (txpinterface === \'admin\') {\r\n\r\n    register_callback(\'kuo_trigger_save\',\'admin_side\',\'head_end\');\r\n\r\n    # BEGINS: FUNCTION: kuo_trigger_save()\r\n    function kuo_trigger_save($event,$step) {\r\n\r\n        echo \'\r\n        <script>\r\n            $(window).keypress(function(e) {\r\n                if (e.which == 13 && e.ctrlKey) {\r\n                    $(\"input[name$=\\\'ave\\\'],input[name$=\\\'ublish\\\'],input#ied_editSave\").click();\r\n                }\r\n            });\r\n        </script>\r\n        \';\r\n    }\r\n    # ENDS: FUNCTION: kuo_trigger_save()\r\n}\r\n# ENDS: ADMIN SIDE\r\n', 'eab883cdb0544b19c66d1fef24370b96', '', '', 3, 5, 0),
('kuo_upload_image', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Upload an image to Images section with this and zem_contact_reborn.', '<ul id=\"links\">\n<li id=\"kuo_upload_image\"><a href=\"#kuo_upload_image\">kuo_upload_image</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>With this plugin a form that\'s using <code>zem_contact_reborn</code> can be used to upload image files to Textpattern\'s Images section. This plugin was tested with Textpattern versions 4.5.5 and 4.5.7 and with <code>zem_contact_reborn</code> version 4.0.3.20.</p>\n<h3>An example how to use</h3>\n<p><code>&lt;txp:zem_contact to=\"email@address.com\" label=\"Upload an image\"&gt;<br />\n&lt;txp:zem_contact_secret value=\"A new image was uploaded to Textpattern.\" /&gt;<br />\n&lt;txp:kuo_upload_image label=\"Choose a file\" wraptag=\"p\" category=\"pending\" caption_label=\"Image caption\" uploader_username=\"test\" upload_max_size=\"2097152\" /&gt;<br />\n&lt;p&gt;&lt;txp:zem_contact_submit&gt;Upload&lt;/txp:zem_contact_submit&gt;&lt;/p&gt;<br />\n&lt;/txp:zem_contact&gt;</code></p>\n<p id=\"notice\">You can build any kind of form with <code>zem_contact_reborn</code> and include this plugin\'s functionality simply with the <code>&lt;txp:kuo_upload_image /&gt;</code> tag. In any case an email message is sent when the form is successfully submitted. The reason why the code example above uses <code>zem_contact_secret</code> is that the form needs something to be sent, and not just a submit button with the upload field.</p>\n<h3>Tag attributes</h3>\n<ul>\n<li><code>label</code> is shown next to file upload field.</li>\n<li><code>wraptag</code> will surround the file upload field, and image caption field as well if caption field is used.</li>\n<li><code>category</code> is an Image category name in lowercase, in \"URL slug\" format.</li>\n<li><code>caption_label</code> is shown under the file upload field. This is a text field. The text typed in this field will be saved as the image\'s caption text.</li>\n<li><code>uploader_username</code> is the author name to whom image is assigned. In \"URL slug\" format.</li>\n<li><code>upload_max_size</code> is the maximum file size for image. Default is 2 megabytes. If you want 4 megabytes, use: <code>4194304</code> (because <code>2097152 &times; 2 = 4194304</code>)</li>\n</ul>\n<h3>The error message</h3>\n<p>If user is for some reason not allowed to upload the image, an error message will be shown. The error message is from <code>zem_contact_lang</code> the one that in English is the following: <q>We do not accept spam, thank you!</q> It\'s advisable to modify this error message to something more generic &mdash; something that for example instructs the user not to submit too large image file.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_upload_image</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_upload_image</code>.</li>\n</ol>\n<h3>Notes</h3>\n<p><strong>It\'s good to notice</strong> that this plugin will automatically do a small modification to the source code of <code>zem_contact_reborn</code>. The modification basically adds a HTML attribute <code>enctype=\"multipart/form-data\"</code> to <code>zem_contact_reborn</code>\'s HTML markup.</p>\n<p>This plugin will not create a thumbnail for the uploaded image.</p>\n<p>The upload folder for images is presumed to be <code>images</code> from the website root.</p>\n<p><strong>Downloads:</strong> <a href=\"https://vanmelick.com/txp/zem_contact_reborn.php\" rel=\"nofollow external\" target=\"_new\">zem_contact_reborn</a>, <a href=\"https://vanmelick.com/txp/zem_contact_lang.php\" rel=\"nofollow external\" target=\"_new\">zem_contact_lang</a></p>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '    # BEGINS: IMAGE UPLOAD SYSTEM\r\n    function kuo_upload_image($atts,$thing = NULL) {\r\n        extract(lAtts(array(\'wraptag\'=>NULL,\'label\'=>NULL,\'debug\'=>NULL,\'category\'=>NULL,\'caption_label\'=>NULL,\'uploader_username\'=>NULL,\'upload_max_size\'=>NULL),$atts));\r\n        $field_id = uniqid(\'kuo\',1);\r\n\r\n        # WRAPTAG IS USED FOR BOTH HTML ELEMENTS:\r\n        # 1) FILE UPLOAD FIELD HAS ITS OWN WRAPTAG\r\n        # 2) CAPTION TEXT FIELD HAS ITS OWN WRAPTAG\r\n        $wraptag_options = array(\'p\',\'ul\',\'ol\',\'dl\',\'span\',\'div\',\'li\',\'dd\',\'dt\');\r\n\r\n        if ((!isset($wraptag)) or (empty($wraptag))) {\r\n            $wraptag_start = NULL;\r\n            $wraptag_end = NULL;\r\n        }\r\n        elseif ((isset($wraptag)) && (!empty($wraptag)) && (in_array($wraptag,$wraptag_options))) {\r\n            $wraptag_start = \'<\'.$wraptag.\'>\';\r\n            $wraptag_end = \'</\'.$wraptag.\'>\';\r\n        }\r\n\r\n        # LABEL THAT IS SHOWN NEXT TO FILE UPLOAD FIELD\r\n        if ((!isset($label)) or (empty($label))) {\r\n            $label = NULL;\r\n        }\r\n        else {\r\n            $label = \'<label for=\"kuo_upload\'.$field_id.\'\">\'.filter_var($label,FILTER_SANITIZE_STRING).\'</label>\';\r\n        }\r\n\r\n        # CAPTION TEXT LABEL\r\n        # THIS IS DELIVERED TO DATABASE\r\n        if ((!isset($caption_label)) or (empty($caption_label))) {\r\n            $caption_label = NULL;\r\n        }\r\n        else {\r\n            $caption_label = $wraptag_start.\'<label for=\"kuo_upload_caption_\'.$field_id.\'\">\'.filter_var($caption_label,FILTER_SANITIZE_STRING).\'</label>\'.\r\n            finput(\'text\',\'kuo_upload_caption\',\'\',\'kuo_upload_image\',\'\',\'\',\'\',\'\',\'kuo_upload_caption_\'.$field_id,\'\',$required).$wraptag_end;\r\n        }\r\n\r\n        # DEBUG MODE ON OR OF\r\n        if ((!isset($debug)) or (empty($debug))) {\r\n            $debug = NULL;\r\n        }\r\n        else {\r\n            $debug = finput(\'hidden\',\'kuo_upload_debug\',1);\r\n        }\r\n\r\n        # THE CATEGORY FOR UPLOADED IMAGES\r\n        # MUST BE LOWERCASE LIKE IN A \"URL SLUG\" STYLE\r\n        if ((!isset($category)) or (empty($category))) {\r\n            $category = NULL;\r\n        }\r\n        else {\r\n            $category = strtolower(filter_var($category,FILTER_SANITIZE_STRING));\r\n        }\r\n\r\n        # THE USERNAME THAT IS USED FOR THE UPLOADED IMAGES\r\n        # SOMETHING SHOULD BE, I SUPPOSE, SO WE USE \"kuopassa\" AS DEFAULT\r\n        if ((!isset($uploader_username)) or (empty($uploader_username))) {\r\n            $uploader_username = \'kuopassa\';\r\n        }\r\n        else {\r\n            $uploader_username = filter_var($uploader_username,FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        # WHAT IS THE MAXIMUM SIZE OF UPLOADED FILE\r\n        # IN BYTES\r\n        # DEFAULT VALUE: 2 MEGABYTES (2097152)\r\n        if ((!isset($upload_max_size)) or (empty($upload_max_size))) {\r\n            $upload_max_size = 2097152;\r\n        }\r\n        else {\r\n            $upload_max_size = filter_var($upload_max_size,FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n\r\n        # OUTPUTTING THE TAGS\r\n        return $wraptag_start.$label.finput(\'file\',\'kuo_upload_image\',\'\',\'kuo_upload_image\',\'\',\'\',\'\',\'\',\'kuo_upload_\'.$field_id,\'\',$required).$wraptag_end.$caption_label.\r\n        finput(\'hidden\',\'kuo_upload_settings\',base64_encode(json_encode(array($category,$uploader_username,$upload_max_size)))).$debug;\r\n    }\r\n\r\n    #  VALIDATING UPLOAD ETC.:\r\n    register_callback(\'kuo_image_upload_submit\',\'zemcontact.submit\');\r\n    \r\n    function kuo_image_upload_submit() {\r\n        $kuo_file = $_FILES[\'kuo_upload_image\'][\'name\'];\r\n        $kuo_debug = trim(ps(\'kuo_upload_debug\'));\r\n        $kuo_image_settings = json_decode(base64_decode(trim(ps(\'kuo_upload_settings\'))));\r\n        $kuo_image_caption = trim(ps(\'kuo_upload_caption\'));\r\n\r\n        if ((isset($kuo_file))) {\r\n            # IF THIS IS DEBUGGING MODE\r\n            if ($kuo_debug === \'1\') {\r\n                var_dump($_POST);\r\n            }\r\n            \r\n            $evaluation =& get_zemcontact_evaluator();\r\n\r\n            if ((isset($kuo_file)) && (empty($kuo_file))) {\r\n                $evaluation -> add_zemcontact_status(1);\r\n            }\r\n            else {\r\n                \r\n                # JUST THE EXTENSION NAME WITHOUT DOT AT THE BEGINNING\r\n                define(\'KUO_UPLOADED_EXTENSION\',pathinfo($kuo_file,PATHINFO_EXTENSION));\r\n\r\n                # MUST GET IMAGE CATEGORY AND UPLOADER USERNAME FROM AN ARRAY\r\n                if ((isset($kuo_image_settings)) && (!empty($kuo_image_settings)) && (is_array($kuo_image_settings))) {\r\n                    $kuo_image_category = filter_var($kuo_image_settings[0],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_image_category)) {\r\n                        $kuo_image_category = NULL;\r\n                    }\r\n                \r\n                    $kuo_image_author = filter_var($kuo_image_settings[1],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_image_author)) {\r\n                        $kuo_image_author = \'kuopassa\';\r\n                    }\r\n                    \r\n                    $kuo_image_max_size = filter_var($kuo_image_settings[2],FILTER_SANITIZE_NUMBER_INT);\r\n                    if ((empty($kuo_image_max_size)) or (!is_numeric($kuo_image_max_size))) {\r\n                        $kuo_image_max_size = 2097152;\r\n                    }\r\n                }\r\n                \r\n                # IF EXTENSION IS NOT ONE OF THE IN ARRAY\r\n                if (!in_array(KUO_UPLOADED_EXTENSION,array(\'jpg\',\'jpeg\',\'gif\',\'png\'))) {\r\n                    $not_good_extension = TRUE;\r\n                    $evaluation -> add_zemcontact_status(1);\r\n                    return;\r\n                }\r\n            \r\n                # STARTS: IF EXTENSION OK AND FILE SIZE\r\n                if (($not_good_extension === NULL) && ($_FILES[\'kuo_upload_image\'][\'size\'] <= $kuo_image_max_size)) {\r\n\r\n                    # IN CASE FILE NAME IS VERY LONG\r\n                    if (strlen($kuo_file) >= 50) {\r\n                        $kuo_file = substr($kuo_file,50-strlen($kuo_file)).\'.\'.KUO_UPLOADED_EXTENSION;\r\n                    }\r\n                    \r\n                    $file_name = preg_replace(\'/[^-_a-z0-9\\.]/\',\'\',strtolower($kuo_file));\r\n                    \r\n                    # PREPARING UPLOAD LOCATION\r\n                    define(\'KUO_UPLOAD_IMAGE_FOLDER\',\'./images/\');\r\n                    if (!is_dir(KUO_UPLOAD_IMAGE_FOLDER)) {\r\n                        mkdir(KUO_UPLOAD_IMAGE_FOLDER,644);\r\n                    }\r\n                    \r\n                    # FETCHING CURRENT IMAGE INCREMENTAL NUMBER AND THEN...\r\n                    # ADDING ONE TO ITS VALUE\r\n                    $image_id = safe_query(\"SELECT `id` FROM \".safe_pfx(\'txp_image\').\" ORDER BY `id` DESC LIMIT 1\");\r\n                    $image_id = mysql_fetch_assoc($image_id);\r\n                    $image_id = intval($image_id[\'id\'])+1;\r\n\r\n                    # COPY, OVER AND OUT\r\n                    if (move_uploaded_file($_FILES[\'kuo_upload_image\'][\'tmp_name\'],KUO_UPLOAD_IMAGE_FOLDER.$image_id.\'.\'.KUO_UPLOADED_EXTENSION)) {\r\n                        # SUCCESS\r\n                        $kuo_status = 1;\r\n                        \r\n                        safe_query(\'INSERT INTO `\'.safe_pfx(\'txp_image\').\'` (\r\n                        name,\r\n                        category,\r\n                        ext,\r\n                        alt,\r\n                        caption,\r\n                        date,\r\n                        author,\r\n                        thumbnail\r\n                        )\r\n                        VALUES (\r\n                        \"\'.doSlash($file_name).\'\",\r\n                        \"\'.doSlash($kuo_image_category).\'\",\r\n                        \"\'.doSlash(\'.\'.KUO_UPLOADED_EXTENSION).\'\",\r\n                        \"\",\r\n                        \"\'.doSlash($kuo_image_caption).\'\",\r\n                        \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\",\r\n                        \"\'.doSlash($kuo_image_author).\'\",\r\n                        \"0\"\r\n                        )\',$kuo_debug);\r\n                    }\r\n                    else {\r\n                        # ERROR\r\n                        $kuo_status = 0;\r\n                    }\r\n\r\n                    # BEGINS: DEBUG MODE\r\n                    if ($kuo_debug === \'1\') {\r\n                        echo \'<pre><ul><li>File name: \'.$file_name.\'</li><li>Status: \'.$kuo_status.\'</li><li>Location: \'.KUO_UPLOAD_IMAGE_FOLDER.\'</li></ul></pre>\';\r\n                    }\r\n                    # ENDS: DEBUG MODE\r\n                }\r\n                # ENDS: IF EXTENSION OK AND FILE SIZE\r\n                else {\r\n                    $evaluation -> add_zemcontact_status(1);\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n    }\r\n    # ENDS: IMAGE UPLOAD SYSTEM\r\n    \r\n    # BEGINS: ZEM_CONTACT_REBORN CONNECTOR\r\n    register_callback(\'kuo_zreborn_connect\',\'textpattern\');\r\n    function kuo_zreborn_connect() {\r\n        if (function_exists(\'zem_contact\')) {\r\n            $kuo_zreborn_connect = safe_row(\'*\',\'txp_plugin\',\"name=\'zem_contact_reborn\'\",0);\r\n            if ($kuo_zreborn_connect) {\r\n                define(\'KUO_ZREBORN_CONNECT\',\'if(function_exists(\\\'kuo_zreborn_connector\\\')){define(\\\'KUO_CONNECTOR_ZREBORN\\\',kuo_zreborn_connector());}else{define(\\\'KUO_CONNECTOR_ZREBORN\\\',\\\'\\\');}return \\\'<form \\\'.KUO_CONNECTOR_ZREBORN.\\\'\');\r\n                define(\'KUO_SEARCH_ZREBORN\',\'return \\\'<form\');\r\n                \r\n                # WILL TRY TO CHECK IF CONNECTION HAS BEEN MADE\r\n                # IF NOT, A SMALL CHANGE TO THE PLUGIN WILL BE SAVED\r\n                if ((!strpos($kuo_zreborn_connect[\'code\'],\'kuo_zreborn_connect\')) && (!strpos($kuo_zreborn_connect[\'code\'],KUO_ZREBORN_CONNECT))) {\r\n                    if (strpos($kuo_zreborn_connect[\'code\'],KUO_SEARCH_ZREBORN)) {\r\n                        $kuo_updated_code = str_replace(KUO_SEARCH_ZREBORN,KUO_ZREBORN_CONNECT,$kuo_zreborn_connect[\'code\']);\r\n                        \r\n                        # SAVING THE CHANGE TO ZEM_CONTACT_REBORN\r\n                        safe_query(\"UPDATE \".safe_pfx(\'txp_plugin\').\" SET `code` = \'\".doSlash($kuo_updated_code).\"\' WHERE `name` = \'zem_contact_reborn\'\",0);\r\n                    }\r\n                }\r\n\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    register_callback(\'kuo_zreborn_connector\',\'textpattern\');\r\n    # THIS PART PRODUCES THE ACTUAL ADDITIONAL CODE FOR ZEM CONTACT REBORN\r\n    # IT WILL LAUNCH THIS ONLY IF THIS PLUGIN IS AVAILABLE\r\n    function kuo_zreborn_connector() {\r\n        if ((@txpinterface === \'public\') && (function_exists(\'kuo_upload_image\'))) {\r\n            return \'enctype=\"multipart/form-data\"\';\r\n        }\r\n    }\r\n    # EMDS: ZEM_CONTACT_REBORN CONNECTOR', '    # BEGINS: IMAGE UPLOAD SYSTEM\r\n    function kuo_upload_image($atts,$thing = NULL) {\r\n        extract(lAtts(array(\'wraptag\'=>NULL,\'label\'=>NULL,\'debug\'=>NULL,\'category\'=>NULL,\'caption_label\'=>NULL,\'uploader_username\'=>NULL,\'upload_max_size\'=>NULL),$atts));\r\n        $field_id = uniqid(\'kuo\',1);\r\n\r\n        # WRAPTAG IS USED FOR BOTH HTML ELEMENTS:\r\n        # 1) FILE UPLOAD FIELD HAS ITS OWN WRAPTAG\r\n        # 2) CAPTION TEXT FIELD HAS ITS OWN WRAPTAG\r\n        $wraptag_options = array(\'p\',\'ul\',\'ol\',\'dl\',\'span\',\'div\',\'li\',\'dd\',\'dt\');\r\n\r\n        if ((!isset($wraptag)) or (empty($wraptag))) {\r\n            $wraptag_start = NULL;\r\n            $wraptag_end = NULL;\r\n        }\r\n        elseif ((isset($wraptag)) && (!empty($wraptag)) && (in_array($wraptag,$wraptag_options))) {\r\n            $wraptag_start = \'<\'.$wraptag.\'>\';\r\n            $wraptag_end = \'</\'.$wraptag.\'>\';\r\n        }\r\n\r\n        # LABEL THAT IS SHOWN NEXT TO FILE UPLOAD FIELD\r\n        if ((!isset($label)) or (empty($label))) {\r\n            $label = NULL;\r\n        }\r\n        else {\r\n            $label = \'<label for=\"kuo_upload\'.$field_id.\'\">\'.filter_var($label,FILTER_SANITIZE_STRING).\'</label>\';\r\n        }\r\n\r\n        # CAPTION TEXT LABEL\r\n        # THIS IS DELIVERED TO DATABASE\r\n        if ((!isset($caption_label)) or (empty($caption_label))) {\r\n            $caption_label = NULL;\r\n        }\r\n        else {\r\n            $caption_label = $wraptag_start.\'<label for=\"kuo_upload_caption_\'.$field_id.\'\">\'.filter_var($caption_label,FILTER_SANITIZE_STRING).\'</label>\'.\r\n            finput(\'text\',\'kuo_upload_caption\',\'\',\'kuo_upload_image\',\'\',\'\',\'\',\'\',\'kuo_upload_caption_\'.$field_id,\'\',$required).$wraptag_end;\r\n        }\r\n\r\n        # DEBUG MODE ON OR OF\r\n        if ((!isset($debug)) or (empty($debug))) {\r\n            $debug = NULL;\r\n        }\r\n        else {\r\n            $debug = finput(\'hidden\',\'kuo_upload_debug\',1);\r\n        }\r\n\r\n        # THE CATEGORY FOR UPLOADED IMAGES\r\n        # MUST BE LOWERCASE LIKE IN A \"URL SLUG\" STYLE\r\n        if ((!isset($category)) or (empty($category))) {\r\n            $category = NULL;\r\n        }\r\n        else {\r\n            $category = strtolower(filter_var($category,FILTER_SANITIZE_STRING));\r\n        }\r\n\r\n        # THE USERNAME THAT IS USED FOR THE UPLOADED IMAGES\r\n        # SOMETHING SHOULD BE, I SUPPOSE, SO WE USE \"kuopassa\" AS DEFAULT\r\n        if ((!isset($uploader_username)) or (empty($uploader_username))) {\r\n            $uploader_username = \'kuopassa\';\r\n        }\r\n        else {\r\n            $uploader_username = filter_var($uploader_username,FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        # WHAT IS THE MAXIMUM SIZE OF UPLOADED FILE\r\n        # IN BYTES\r\n        # DEFAULT VALUE: 2 MEGABYTES (2097152)\r\n        if ((!isset($upload_max_size)) or (empty($upload_max_size))) {\r\n            $upload_max_size = 2097152;\r\n        }\r\n        else {\r\n            $upload_max_size = filter_var($upload_max_size,FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n\r\n        # OUTPUTTING THE TAGS\r\n        return $wraptag_start.$label.finput(\'file\',\'kuo_upload_image\',\'\',\'kuo_upload_image\',\'\',\'\',\'\',\'\',\'kuo_upload_\'.$field_id,\'\',$required).$wraptag_end.$caption_label.\r\n        finput(\'hidden\',\'kuo_upload_settings\',base64_encode(json_encode(array($category,$uploader_username,$upload_max_size)))).$debug;\r\n    }\r\n\r\n    #  VALIDATING UPLOAD ETC.:\r\n    register_callback(\'kuo_image_upload_submit\',\'zemcontact.submit\');\r\n    \r\n    function kuo_image_upload_submit() {\r\n        $kuo_file = $_FILES[\'kuo_upload_image\'][\'name\'];\r\n        $kuo_debug = trim(ps(\'kuo_upload_debug\'));\r\n        $kuo_image_settings = json_decode(base64_decode(trim(ps(\'kuo_upload_settings\'))));\r\n        $kuo_image_caption = trim(ps(\'kuo_upload_caption\'));\r\n\r\n        if ((isset($kuo_file))) {\r\n            # IF THIS IS DEBUGGING MODE\r\n            if ($kuo_debug === \'1\') {\r\n                var_dump($_POST);\r\n            }\r\n            \r\n            $evaluation =& get_zemcontact_evaluator();\r\n\r\n            if ((isset($kuo_file)) && (empty($kuo_file))) {\r\n                $evaluation -> add_zemcontact_status(1);\r\n            }\r\n            else {\r\n                \r\n                # JUST THE EXTENSION NAME WITHOUT DOT AT THE BEGINNING\r\n                define(\'KUO_UPLOADED_EXTENSION\',pathinfo($kuo_file,PATHINFO_EXTENSION));\r\n\r\n                # MUST GET IMAGE CATEGORY AND UPLOADER USERNAME FROM AN ARRAY\r\n                if ((isset($kuo_image_settings)) && (!empty($kuo_image_settings)) && (is_array($kuo_image_settings))) {\r\n                    $kuo_image_category = filter_var($kuo_image_settings[0],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_image_category)) {\r\n                        $kuo_image_category = NULL;\r\n                    }\r\n                \r\n                    $kuo_image_author = filter_var($kuo_image_settings[1],FILTER_SANITIZE_STRING);\r\n                    if (empty($kuo_image_author)) {\r\n                        $kuo_image_author = \'kuopassa\';\r\n                    }\r\n                    \r\n                    $kuo_image_max_size = filter_var($kuo_image_settings[2],FILTER_SANITIZE_NUMBER_INT);\r\n                    if ((empty($kuo_image_max_size)) or (!is_numeric($kuo_image_max_size))) {\r\n                        $kuo_image_max_size = 2097152;\r\n                    }\r\n                }\r\n                \r\n                # IF EXTENSION IS NOT ONE OF THE IN ARRAY\r\n                if (!in_array(KUO_UPLOADED_EXTENSION,array(\'jpg\',\'jpeg\',\'gif\',\'png\'))) {\r\n                    $not_good_extension = TRUE;\r\n                    $evaluation -> add_zemcontact_status(1);\r\n                    return;\r\n                }\r\n            \r\n                # STARTS: IF EXTENSION OK AND FILE SIZE\r\n                if (($not_good_extension === NULL) && ($_FILES[\'kuo_upload_image\'][\'size\'] <= $kuo_image_max_size)) {\r\n\r\n                    # IN CASE FILE NAME IS VERY LONG\r\n                    if (strlen($kuo_file) >= 50) {\r\n                        $kuo_file = substr($kuo_file,50-strlen($kuo_file)).\'.\'.KUO_UPLOADED_EXTENSION;\r\n                    }\r\n                    \r\n                    $file_name = preg_replace(\'/[^-_a-z0-9\\.]/\',\'\',strtolower($kuo_file));\r\n                    \r\n                    # PREPARING UPLOAD LOCATION\r\n                    define(\'KUO_UPLOAD_IMAGE_FOLDER\',\'./images/\');\r\n                    if (!is_dir(KUO_UPLOAD_IMAGE_FOLDER)) {\r\n                        mkdir(KUO_UPLOAD_IMAGE_FOLDER,644);\r\n                    }\r\n                    \r\n                    # FETCHING CURRENT IMAGE INCREMENTAL NUMBER AND THEN...\r\n                    # ADDING ONE TO ITS VALUE\r\n                    $image_id = safe_query(\"SELECT `id` FROM \".safe_pfx(\'txp_image\').\" ORDER BY `id` DESC LIMIT 1\");\r\n                    $image_id = mysql_fetch_assoc($image_id);\r\n                    $image_id = intval($image_id[\'id\'])+1;\r\n\r\n                    # COPY, OVER AND OUT\r\n                    if (move_uploaded_file($_FILES[\'kuo_upload_image\'][\'tmp_name\'],KUO_UPLOAD_IMAGE_FOLDER.$image_id.\'.\'.KUO_UPLOADED_EXTENSION)) {\r\n                        # SUCCESS\r\n                        $kuo_status = 1;\r\n                        \r\n                        safe_query(\'INSERT INTO `\'.safe_pfx(\'txp_image\').\'` (\r\n                        name,\r\n                        category,\r\n                        ext,\r\n                        alt,\r\n                        caption,\r\n                        date,\r\n                        author,\r\n                        thumbnail\r\n                        )\r\n                        VALUES (\r\n                        \"\'.doSlash($file_name).\'\",\r\n                        \"\'.doSlash($kuo_image_category).\'\",\r\n                        \"\'.doSlash(\'.\'.KUO_UPLOADED_EXTENSION).\'\",\r\n                        \"\",\r\n                        \"\'.doSlash($kuo_image_caption).\'\",\r\n                        \"\'.doSlash(date(\'Y-m-d H:i:s\')).\'\",\r\n                        \"\'.doSlash($kuo_image_author).\'\",\r\n                        \"0\"\r\n                        )\',$kuo_debug);\r\n                    }\r\n                    else {\r\n                        # ERROR\r\n                        $kuo_status = 0;\r\n                    }\r\n\r\n                    # BEGINS: DEBUG MODE\r\n                    if ($kuo_debug === \'1\') {\r\n                        echo \'<pre><ul><li>File name: \'.$file_name.\'</li><li>Status: \'.$kuo_status.\'</li><li>Location: \'.KUO_UPLOAD_IMAGE_FOLDER.\'</li></ul></pre>\';\r\n                    }\r\n                    # ENDS: DEBUG MODE\r\n                }\r\n                # ENDS: IF EXTENSION OK AND FILE SIZE\r\n                else {\r\n                    $evaluation -> add_zemcontact_status(1);\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n    }\r\n    # ENDS: IMAGE UPLOAD SYSTEM\r\n    \r\n    # BEGINS: ZEM_CONTACT_REBORN CONNECTOR\r\n    register_callback(\'kuo_zreborn_connect\',\'textpattern\');\r\n    function kuo_zreborn_connect() {\r\n        if (function_exists(\'zem_contact\')) {\r\n            $kuo_zreborn_connect = safe_row(\'*\',\'txp_plugin\',\"name=\'zem_contact_reborn\'\",0);\r\n            if ($kuo_zreborn_connect) {\r\n                define(\'KUO_ZREBORN_CONNECT\',\'if(function_exists(\\\'kuo_zreborn_connector\\\')){define(\\\'KUO_CONNECTOR_ZREBORN\\\',kuo_zreborn_connector());}else{define(\\\'KUO_CONNECTOR_ZREBORN\\\',\\\'\\\');}return \\\'<form \\\'.KUO_CONNECTOR_ZREBORN.\\\'\');\r\n                define(\'KUO_SEARCH_ZREBORN\',\'return \\\'<form\');\r\n                \r\n                # WILL TRY TO CHECK IF CONNECTION HAS BEEN MADE\r\n                # IF NOT, A SMALL CHANGE TO THE PLUGIN WILL BE SAVED\r\n                if ((!strpos($kuo_zreborn_connect[\'code\'],\'kuo_zreborn_connect\')) && (!strpos($kuo_zreborn_connect[\'code\'],KUO_ZREBORN_CONNECT))) {\r\n                    if (strpos($kuo_zreborn_connect[\'code\'],KUO_SEARCH_ZREBORN)) {\r\n                        $kuo_updated_code = str_replace(KUO_SEARCH_ZREBORN,KUO_ZREBORN_CONNECT,$kuo_zreborn_connect[\'code\']);\r\n                        \r\n                        # SAVING THE CHANGE TO ZEM_CONTACT_REBORN\r\n                        safe_query(\"UPDATE \".safe_pfx(\'txp_plugin\').\" SET `code` = \'\".doSlash($kuo_updated_code).\"\' WHERE `name` = \'zem_contact_reborn\'\",0);\r\n                    }\r\n                }\r\n\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    register_callback(\'kuo_zreborn_connector\',\'textpattern\');\r\n    # THIS PART PRODUCES THE ACTUAL ADDITIONAL CODE FOR ZEM CONTACT REBORN\r\n    # IT WILL LAUNCH THIS ONLY IF THIS PLUGIN IS AVAILABLE\r\n    function kuo_zreborn_connector() {\r\n        if ((@txpinterface === \'public\') && (function_exists(\'kuo_upload_image\'))) {\r\n            return \'enctype=\"multipart/form-data\"\';\r\n        }\r\n    }\r\n    # EMDS: ZEM_CONTACT_REBORN CONNECTOR', '693f8698b9bec495323c1a1ed808d466', '', '', 1, 5, 0),
('kuo_user_last_action', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Collect information about what page a logged in Textpattern user last time accessed and when.', '<ul id=\"links\">\n<li id=\"kuo_user_last_action\"><a href=\"#kuo_file_stats\">kuo_user_last_action</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>If a Textpattern user is logged in, this plugin will try to save to database when that user opens any page. Database of user last activity has the following fields: <code>username</code>, <code>timestamp</code> and <code>action</code>. The last one includes information about the last accessed page. The plugin will not save data when user <strong>logs in or out, or if user visits Textpattern\'s Plugins section</strong>.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin <code>kuo_user_last_action</code>.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin <code>kuo_user_last_action</code>.</li>\n</ol>\n<h3>Database structure</h3>\n<table>\n<thead>\n<tr>\n<th>Name</th>\n<th>Type</th>\n<th>Can be null</th>\n<th>Default value</th>\n<th>Defails</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>id</td>\n<td>int(11)</td>\n<td>no</td>\n<td>&nbsp;</td>\n<td>primary, auto increment</td>\n</tr>\n<tr>\n<td>username</td>\n<td>varchar(255)</td>\n<td>no</td>\n<td>&nbsp;</td>\n<td>unique</td>\n</tr>\n<tr>\n<td>timestamp</td>\n<td>timestamp</td>\n<td>no</td>\n<td>current timestamp</td>\n<td>Y-m-d H:i:s</td>\n</tr>\n<tr>\n<td>action</td>\n<td>varchar(255)</td>\n<td>yes</td>\n<td>null</td>\n<td>&nbsp;</td>\n</tr>\n</tbody>\n</table>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }#briefly table { border-collapse: collapse; width: 100%; padding: 1%; background: #eee; }#briefly td,#briefly th { padding: 10px; border-bottom: 2px solid #fff; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'define(\'KUO_ULA_DB_TABLE_NAME\',\'kuo_user_last_action\');\r\ndefine(\'KUO_ULA_DEBUG\',0);\r\n\r\n# BEGINS: INSTALLING, ENABLING, DISABLING, UNINSTALLING\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_ula_uninstall\',\'plugin_lifecycle.kuo_user_last_action\',\'disabled\');\r\n    register_callback(\'kuo_ula_deleted\',\'plugin_lifecycle.kuo_user_last_action\',\'deleted\');\r\n    register_callback(\'kuo_ula_install\',\'plugin_lifecycle.kuo_user_last_action\',\'installed\');\r\n    register_callback(\'kuo_ula_install\',\'plugin_lifecycle.kuo_user_last_action\',\'enabled\');\r\n\r\n    # BEGINS: FUNCTION THAT UNINSTALLS THE PLUGIN\r\n    function kuo_ula_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(KUO_ULA_DB_TABLE_NAME).\"`\",KUO_ULA_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT UNINSTALLS THE PLUGIN\r\n\r\n    # BEGINS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n    function kuo_ula_deleted() {\r\n        kuo_ula_uninstall();\r\n    }\r\n    # ENDS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n\r\n    # BEGINS: CREATING A DATABASE TABLE FOR THIS PLUGIN\r\n    function kuo_ula_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(KUO_ULA_DB_TABLE_NAME).\" (\r\n        `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\r\n        `username` VARCHAR(255) NOT NULL UNIQUE,\r\n        `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n        `action` VARCHAR(255) DEFAULT NULL\r\n        )\",KUO_ULA_DEBUG);\r\n    }\r\n    # ENDS: CREATING A DATABASE TABLE FOR THIS PLUGIN\r\n}\r\n# ENDS: INSTALLING, ENABLING, DISABLING, UNINSTALLING\r\n\r\n# BEGINS: LOG USER ACTION TO DATABASE IF USER IS LOGGED IN\r\nif ((is_logged_in()) && (is_logged_in()[\'name\']) && (isset($_SERVER[\'REQUEST_URI\']))) {\r\n    safe_query(\"INSERT INTO \".safe_pfx(KUO_ULA_DB_TABLE_NAME).\" (username, action) VALUES (\'\".is_logged_in()[\'name\'].\"\', \'\".filter_var($_SERVER[\'REQUEST_URI\'],FILTER_SANITIZE_STRING).\"\')\r\n    ON DUPLICATE KEY UPDATE `action` = \'\".filter_var($_SERVER[\'REQUEST_URI\'],FILTER_SANITIZE_STRING).\"\', `timestamp` = \'\".date(\'Y-m-d H:i:s\').\"\'\",KUO_ULA_DEBUG);\r\n}\r\n# ENDS: LOG USER ACTION TO DATABASE IF USER IS LOGGED IN\r\n', 'define(\'KUO_ULA_DB_TABLE_NAME\',\'kuo_user_last_action\');\r\ndefine(\'KUO_ULA_DEBUG\',0);\r\n\r\n# BEGINS: INSTALLING, ENABLING, DISABLING, UNINSTALLING\r\nif (@txpinterface === \'admin\') {\r\n    register_callback(\'kuo_ula_uninstall\',\'plugin_lifecycle.kuo_user_last_action\',\'disabled\');\r\n    register_callback(\'kuo_ula_deleted\',\'plugin_lifecycle.kuo_user_last_action\',\'deleted\');\r\n    register_callback(\'kuo_ula_install\',\'plugin_lifecycle.kuo_user_last_action\',\'installed\');\r\n    register_callback(\'kuo_ula_install\',\'plugin_lifecycle.kuo_user_last_action\',\'enabled\');\r\n\r\n    # BEGINS: FUNCTION THAT UNINSTALLS THE PLUGIN\r\n    function kuo_ula_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(KUO_ULA_DB_TABLE_NAME).\"`\",KUO_ULA_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT UNINSTALLS THE PLUGIN\r\n\r\n    # BEGINS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n    function kuo_ula_deleted() {\r\n        kuo_ula_uninstall();\r\n    }\r\n    # ENDS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n\r\n    # BEGINS: CREATING A DATABASE TABLE FOR THIS PLUGIN\r\n    function kuo_ula_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(KUO_ULA_DB_TABLE_NAME).\" (\r\n        `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\r\n        `username` VARCHAR(255) NOT NULL UNIQUE,\r\n        `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\r\n        `action` VARCHAR(255) DEFAULT NULL\r\n        )\",KUO_ULA_DEBUG);\r\n    }\r\n    # ENDS: CREATING A DATABASE TABLE FOR THIS PLUGIN\r\n}\r\n# ENDS: INSTALLING, ENABLING, DISABLING, UNINSTALLING\r\n\r\n# BEGINS: LOG USER ACTION TO DATABASE IF USER IS LOGGED IN\r\nif ((is_logged_in()) && (is_logged_in()[\'name\']) && (isset($_SERVER[\'REQUEST_URI\']))) {\r\n    safe_query(\"INSERT INTO \".safe_pfx(KUO_ULA_DB_TABLE_NAME).\" (username, action) VALUES (\'\".is_logged_in()[\'name\'].\"\', \'\".filter_var($_SERVER[\'REQUEST_URI\'],FILTER_SANITIZE_STRING).\"\')\r\n    ON DUPLICATE KEY UPDATE `action` = \'\".filter_var($_SERVER[\'REQUEST_URI\'],FILTER_SANITIZE_STRING).\"\', `timestamp` = \'\".date(\'Y-m-d H:i:s\').\"\'\",KUO_ULA_DEBUG);\r\n}\r\n# ENDS: LOG USER ACTION TO DATABASE IF USER IS LOGGED IN\r\n', '7ba00a354994ba2679b546e4871bc072', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('kuo_zcr_smtp', 0, 'Petri Ikonen', 'http://kuopassa.net/txp/', '0.1', 'Use PHPMailer\'s SMTP function in Zem Contact Reborn.', '<ul id=\"links\">\n<li id=\"kuo_ace\"><a href=\"#kuo_zcr_smtp\">kuo_zcr_smtp</a></li>\n<li id=\"external\"><a href=\"http://kuopassa.net/txp/\" rel=\"external\">kuopassa.net/txp</a></li>\n</ul>\n<div id=\"briefly\">\n<h2>About this plugin</h2>\n<p>Use SMTP with Zem Contact Reborn. You can send HTML or plain text with this, choose message priority if you want to and to what email addresses messages should be sent. Use at your own risk.</p>\n<h3>Using with Gmail</h3>\n<p>If you\'re using Gmail with <code>kuo_zcr_smtp</code> and have 2-way verification active with your Google account, you should create an <a href=\"https://security.google.com/settings/security/apppasswords\">App password</a> for this plugin.</p>\n<p>The &quot;from&quot; field in email message header might mention your Gmail address. To change that please log in to Gmail, go to <strong>Settings &rarr; Accounts and Import &rarr; Send mail as &rarr; Add another email address that you own</strong>. <a href=\"https://support.google.com/mail/answer/22370?hl=en\">More help is available here.</a></p>\n<p id=\"notice\">This plugin was tested with Textpattern 4.5.7 and 4.6.0-beta with <code>zem_contact_reborn</code> version 4.5.0.0.</p>\n<h3>Installing</h3>\n<ol>\n<li>Activate the Textpattern plugin called <code>kuo_zcr_smtp</code>.</li>\n<li>Go to Extensions tab and see if PHPMailer classes (two PHP files) are installed.</li>\n</ol>\n<h3>Uninstalling</h3>\n<ol>\n<li>Deactivate the Textpattern plugin called <code>kuo_zcr_smtp</code>.</li>\n<li><span>[Optional]</span> Delete PHPMailer classes (two PHP files).</li>\n</ol>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">#briefly { padding: 3%; font-size: 1.2em; -moz-box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; box-shadow: 0 0 20px #ccc; }#notice { padding: 1%; margin: 0.5% 0; background: #ffc; border: 1px dashed #fcc; } li span { color: #666; } .large code { font-size: 1.3em; padding: 1%; border: 1px solid #690; } hr { border: 3px solid #ccc; font: 1em Arial, Helvetica, sans-serif;}#links { display: table; height: 100%; width: 100%; clear: both; padding: 0; margin: 0 0 5px 0; list-style: none; }#links li { float: left; display: inline; } #links a { float: left; margin-right: 5px; background: #8AC6ED; color: #333; padding: 5px 10px; text-decoration: none; } #links li:last-child a { background: #9DBB78; }#links li:last-child a:hover,#links li:last-child a:focus,#links li:last-child a:active {background: #E2826B; }</style>\n<!-- *** END PLUGIN CSS *** -->\n', '# BEGINS: PUBLIC OR ADMIN\r\nif (in_array(@txpinterface,array(\'admin\',\'public\'))) {\r\n    # CHANGE THE VALUE OF THIS CONSTANT TO SOMETHING OF YOUR LIKING IF YOU\'RE NOT SATISFIED WITH IT\r\n    define(\'KUO_ZCR_SECRET_KEY\',md5(str_replace(\'/\',\'\',(strrev($GLOBALS[\'prefs\'][\'path_from_root\']).md5(dirname(__FILE__))))));\r\n\r\n    define(\'KUO_ZCR_DB_TABLE_NAME\',\'kuo_zcr_smtp\');\r\n    define(\'KUO_ZCR_CLASS_PHPMAILER_MAIN\',dirname(__FILE__).\'/kuo_zcr_smtp/class.phpmailer.php\');\r\n    define(\'KUO_ZCR_CLASS_PHPMAILER_SMTP\',dirname(__FILE__).\'/kuo_zcr_smtp/class.smtp.php\');\r\n\r\n    # DEBUGGING MODE FOR EXTENSION TAB\r\n    # 1 = ON\r\n    # 0 = OFF\r\n    define(\'KUO_ZCR_DEBUG\',0);\r\n\r\n    # BEGINS: FUNCTION THAT FETCHES SMTP INFO FROM DB\r\n    function kuo_zcr_info() {\r\n        $kuo_zcr_settings = safe_row(\"`debugging`, `email_host`, AES_DECRYPT(`email_username`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_username`, AES_DECRYPT(`email_password`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_password`, `use_auth`,\r\n        `ssl_tls`, `port`, `use_html`, AES_DECRYPT(`email_to`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_to`, AES_DECRYPT(`email_cc`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_cc`, AES_DECRYPT(`email_bcc`,\'\".KUO_ZCR_SECRET_KEY.\"\')\r\n        AS `email_bcc`, `subject`, `xpriority`, `charset`, `xmailer`\r\n        \",\r\n        KUO_ZCR_DB_TABLE_NAME,\"`id` = \'1\' LIMIT 0, 1\",KUO_ZCR_DEBUG);\r\n        return $kuo_zcr_settings;\r\n    }\r\n    # ENDS: FUNCTION THAT FETCHES SMTP INFO FROM DB\r\n}\r\n# ENDS: PUBLIC OR ADMIN\r\n\r\n# BEGINS: ADMIN SIDE\r\nif (@txpinterface === \'admin\') {\r\n\r\n    add_privs(\'kuo_zcr_smtp\',\'1\');\r\n    register_tab(\'extensions\',\'kuo_zcr_smtp\',\'SMTP for ZCR\');\r\n    register_callback(\'kuo_zcr_smtp_settings\',\'kuo_zcr_smtp\');\r\n    register_callback(\'kuo_zcr_smtp_uninstall\',\'plugin_lifecycle.kuo_zcr_smtp\',\'disabled\');\r\n    register_callback(\'kuo_zcr_smtp_deleted\',\'plugin_lifecycle.kuo_zcr_smtp\',\'deleted\');\r\n    register_callback(\'kuo_zcr_smtp_install\',\'plugin_lifecycle.kuo_zcr_smtp\',\'installed\');\r\n    register_callback(\'kuo_zcr_smtp_install\',\'plugin_lifecycle.kuo_zcr_smtp\',\'enabled\');\r\n\r\n    define(\'KUO_ZCR_LOCATION\',\'./index.php?event=kuo_zcr_smtp\');\r\n    define(\'KUO_ZCR_CSS_ERROR\',\'class=\"kuo_error\"\');\r\n    define(\'KUO_ZCR_CSS_OK\',\'class=\"kuo_ok\"\');\r\n    define(\'KUO_ZRC_HTML_ERROR\',\'<span \'.KUO_ZCR_CSS_ERROR.\'>Missing</span>\');\r\n    define(\'KUO_ZRC_HTML_OK\',\'<span \'.KUO_ZCR_CSS_OK.\'>OK</span>\');\r\n    define(\'KUO_ZCR_CSS_ERROR\',\'class=\"kuo_error\"\');\r\n    define(\'KUO_ZCR_GITHUB_URL\',\'https://raw.githubusercontent.com/PHPMailer/PHPMailer/master/\');\r\n    define(\'KUO_ZCR_SELECTED_SELECTED\',\' selected=\"selected\"\');\r\n\r\n    if ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_zcr_smtp\') {\r\n        header(\'location: \'.KUO_ZCR_LOCATION);\r\n        die;\r\n    }\r\n\r\n    # BEGINS: FUNCTION THAT UNINSTALL THE PLUGIN\r\n    function kuo_zcr_smtp_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(KUO_ZCR_DB_TABLE_NAME).\"`\",KUO_ZCR_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT UNINSTALL THE PLUGIN\r\n\r\n    # BEGINS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n    function kuo_zcr_smtp_deleted() {\r\n        kuo_zcr_smtp_uninstall();\r\n    }\r\n    # ENDS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n\r\n    # BEGINS: FUNCTION THAT INSTALL DB TABLE FOR SETTINGS\r\n    function kuo_zcr_smtp_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(KUO_ZCR_DB_TABLE_NAME).\" (\r\n        `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\r\n        `debugging` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',\r\n        `email_host` VARCHAR(255) DEFAULT NULL,\r\n        `email_username` BLOB DEFAULT NULL,\r\n        `email_password` BLOB DEFAULT NULL,\r\n        `use_auth` TINYINT(1) UNSIGNED DEFAULT \'0\',\r\n        `ssl_tls` TINYINT(1) UNSIGNED DEFAULT \'0\',\r\n        `port` SMALLINT(5) UNSIGNED DEFAULT NULL,\r\n        `use_html` TINYINT(1) UNSIGNED DEFAULT \'1\',\r\n        `email_to` BLOB DEFAULT NULL,\r\n        `email_cc` BLOB DEFAULT NULL,\r\n        `email_bcc` BLOB DEFAULT NULL,\r\n        `subject` VARCHAR(255) DEFAULT NULL,\r\n        `xpriority` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',\r\n        `charset` VARCHAR(15) NOT NULL DEFAULT \'UTF-8\',\r\n        `xmailer` VARCHAR(100) DEFAULT NULL\r\n        )\",KUO_ZCR_DEBUG);\r\n\r\n        safe_insert(KUO_ZCR_DB_TABLE_NAME,\"\r\n        `debugging` = \'0\',\r\n        `email_host` = NULL,\r\n        `email_username` = NULL,\r\n        `email_password` = NULL,\r\n        `use_auth` = \'0\',\r\n        `ssl_tls` = \'1\',\r\n        `port` = NULL,\r\n        `use_html` = \'1\',\r\n        `email_to` = NULL,\r\n        `email_cc` = NULL,\r\n        `email_bcc` = NULL,\r\n        `subject` = NULL,\r\n        `xpriority` = \'0\',\r\n        `charset` = \'UTF-8\',\r\n        `xmailer` = NULL\r\n        \",KUO_ZCR_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT INSTALL DB TABLE FOR SETTINGS\r\n\r\n    # BEGINS: FUNCTION THAT CHECKS IF PHP CLASS SEEMS FINE\r\n    function kuo_class_surface_check($kuo_class = NULL) {\r\n        # FIRST SET OF CONDITIONS: MUST EXIST, MUST BE READABLE, MUST BE OVER 20 KB\r\n        if ((file_exists($kuo_class)) && (is_readable($kuo_class)) && (filesize($kuo_class) >= 20480)) {\r\n            $kuo_class = file_get_contents($kuo_class,NULL,NULL,0,80);\r\n            # SECOND SET OF CONDITIONS: MUST NOT BE EMPTY, MUST HAVE \"PHPMAILER\" AND MUST BEGIN WITH \"<?php\"\r\n            if ((!empty($kuo_class)) && (strpos($kuo_class,\'PHPMailer\') == true) && (substr($kuo_class,0,5) === \'<?php\')) {\r\n                return \'OK\';\r\n            }\r\n            else {\r\n                return;\r\n            }\r\n            unset($kuo_class);\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION THAT CHECKS IF PHP CLASS SEEMS FINE\r\n\r\n    # BEGINS: FUNCTION THAT ATTEMPTS TO DOWNLOAD AND INSTALL REQUIRED CLASSES\r\n    function kuo_class_installer($kuo_class = NULL) {\r\n        if ($kuo_class === \'class.phpmailer\') {\r\n            $kuo_existing_file_to_check = KUO_ZCR_CLASS_PHPMAILER_MAIN;\r\n        }\r\n        else {\r\n            $kuo_existing_file_to_check = KUO_ZCR_CLASS_PHPMAILER_SMTP;\r\n        }\r\n\r\n        if ((isset($kuo_class)) && (!empty($kuo_class)) && (in_array($kuo_class,array(\'class.phpmailer\',\'class.smtp\'))) && (kuo_class_surface_check($kuo_existing_file_to_check) !== \'OK\')) {\r\n            $kuo_class = $kuo_class.\'.php\';\r\n            $kuo_class_header = @get_headers(KUO_ZCR_GITHUB_URL.$kuo_class);\r\n\r\n            if ((strpos($kuo_class_header[0],\'200\') == true)) {\r\n\r\n                # IF FOLDER IS NOT THERE\r\n                if (!is_dir(dirname(__FILE__).\'/kuo_zcr_smtp/\')) {\r\n                    mkdir(dirname(__FILE__).\'/kuo_zcr_smtp/\',0750);\r\n                }\r\n\r\n                # FETCH CLASS CONTENTS FROM GITHUB\r\n                $kuo_class_contents = file_get_contents(KUO_ZCR_GITHUB_URL.$kuo_class);\r\n\r\n                # SAVE CONTENTS AS A LOCAL FILE\r\n                if (!empty($kuo_class_contents)) {\r\n                    file_put_contents(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class,$kuo_class_contents);\r\n\r\n                    if (file_exists(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class)) {\r\n                        chmod(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class,0444);\r\n                    }\r\n\r\n                    header(\'location:\'.KUO_ZCR_LOCATION.\'#status\');\r\n                    die;\r\n                }\r\n                else {\r\n                    return;\r\n                }\r\n                unset($kuo_class);\r\n                unset($kuo_class_contents);\r\n            }\r\n            else {\r\n                return;\r\n            }\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION THAT ATTEMPTS TO DOWNLOAD AND INSTALL REQUIRED CLASSES\r\n\r\n    # BEGINS: EXTENSION TAB\r\n    function kuo_zcr_smtp_settings() {\r\n        if ($GLOBALS[\'event\'] === \'kuo_zcr_smtp\') {\r\n\r\n            # BEGINS: IF ATTEMPTING TO INSTALL\r\n            if ((isset($_GET[\'kuo_install\'])) && (in_array($_GET[\'kuo_install\'],array(\'class.phpmailer\',\'class.smtp\')))) {\r\n                kuo_class_installer($_GET[\'kuo_install\']);\r\n            }\r\n            # ENDS: IF ATTEMPTING TO INSTALL\r\n\r\n            # SETTINGS PAGE BEGINS\r\n            pagetop(\'SMTP for ZCR\');\r\n\r\n            echo \'<style type=\"text/css\" media=\"all\">\r\n            form fieldset,\r\n            #status{\r\n                padding: 3%;\r\n            }\r\n            form ul,\r\n            #status ul {\r\n                list-style: none;\r\n                padding: 0;\r\n                margin: 0;\r\n            }\r\n            form li,\r\n            #status li {\r\n                display: block;\r\n                clear: both;\r\n                border-bottom: 1px dotted #eee;\r\n                padding: 10px 0;\r\n            }\r\n            label,\r\n            select,\r\n            button {\r\n                cursor: pointer;\r\n            }\r\n            li label {\r\n                padding-right: 5px;\r\n                float: left;\r\n                width: 30%;\r\n                min-width: 160px;\r\n                max-width: 360px;\r\n            }\r\n            li input {\r\n                min-width: 100px;\r\n            }\r\n            li button {\r\n                padding: 5px 20px;\r\n                height: auto;\r\n                font-size: 1.3em;\r\n            }\r\n            span.kuo_error,\r\n            span.kuo_ok {\r\n                padding: 2px 4px;\r\n                margin-left: 10px;\r\n                color: #fff;\r\n                text-transform: uppercase;\r\n                font-weight: bold;\r\n            }\r\n            span.kuo_error {\r\n                background: #c00;\r\n            }\r\n            span.kuo_ok {\r\n                background: #390;\r\n            }\r\n            li.kuo_error code {\r\n                border-color: #c00;\r\n            }\r\n            li.kuo_ok code {\r\n                border-color: #390;\r\n            }\r\n            a.kuo_install {\r\n                margin-left: 10px;\r\n                font-weight: bold;\r\n            }\r\n            #status {\r\n                border: 1px solid #eee;\r\n                margin-top: 1%;\r\n            }\r\n            li span {\r\n                margin-left: 10px;\r\n                border: 1px solid #eee;\r\n                padding: 2px 4px;\r\n            }\r\n            #kuo_saved_message {\r\n                text-align: center;\r\n                background: #390;\r\n                color: #ffc;\r\n                padding: 5px;\r\n                margin: 1% auto;\r\n            }\r\n            #kuo_saved_message.kuo_error {\r\n                background: #c00;\r\n            }\r\n            </style>\';\r\n\r\n            # BEGINS: SAVING SETTINGS TO DB / FETCHING SETTINGS FROM DB\r\n            if ((isset($_POST[\'kuo_zcr_save\'])) && (empty($_POST[\'kuo_zcr_save\']))) {\r\n                # DEBUGGING MODE\r\n                if ((isset($_POST[\'kuo_debug\'])) && ($_POST[\'kuo_debug\'] == 1)) {\r\n                    $kuo_zcr_debug = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_debug = 0;\r\n                }\r\n\r\n                # EMAIL HOST\r\n                if (isset($_POST[\'kuo_host\'])) {\r\n                    $kuo_zcr_host = filter_var($_POST[\'kuo_host\'],FILTER_SANITIZE_STRING);\r\n                }\r\n                else {\r\n                    $kuo_zcr_host = NULL;\r\n                }\r\n\r\n                # EMAIL USERNAME\r\n                if (isset($_POST[\'kuo_username\'])) {\r\n                    if (is_numeric($_POST[\'kuo_username\'])) {\r\n                        $kuo_zcr_username = filter_var($_POST[\'kuo_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_username = filter_var($_POST[\'kuo_username\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_username = NULL;\r\n                }\r\n\r\n                # EMAIL PASSWORD\r\n                if (isset($_POST[\'kuo_password\'])) {\r\n                    if (is_numeric($_POST[\'kuo_password\'])) {\r\n                        $kuo_zcr_password = filter_var($_POST[\'kuo_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_password = filter_var($_POST[\'kuo_password\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_password = NULL;\r\n                }\r\n\r\n                # USE SMTP AUTH\r\n                if ((isset($_POST[\'kuo_auth\'])) && ($_POST[\'kuo_auth\'] == 1)) {\r\n                    $kuo_zcr_auth = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_auth = 0;\r\n                }\r\n\r\n                # SSLT OR TLS\r\n                if ((isset($_POST[\'kuo_use\'])) && ($_POST[\'kuo_use\'] == 1)) {\r\n                    $kuo_zcr_use = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_use = 0;\r\n                }\r\n\r\n                # PORT NUMBER\r\n                if (isset($_POST[\'kuo_port\'])) {\r\n                    if (is_numeric($_POST[\'kuo_port\'])) {\r\n                        $kuo_zcr_port = filter_var($_POST[\'kuo_port\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_port = filter_var($_POST[\'kuo_port\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_port = NULL;\r\n                }\r\n\r\n                # USE HTML IN EMAIL\r\n                if ((isset($_POST[\'kuo_html\'])) && ($_POST[\'kuo_html\'] == 1)) {\r\n                    $kuo_zcr_html = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_html = 0;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (TO)\r\n                if ((isset($_POST[\'kuo_receiver_email\'])) && (filter_var($_POST[\'kuo_receiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_receiver_email = filter_var($_POST[\'kuo_receiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_receiver_email = NULL;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (CC)\r\n                if ((isset($_POST[\'kuo_ccreceiver_email\'])) && (filter_var($_POST[\'kuo_ccreceiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_ccreceiver_email = filter_var($_POST[\'kuo_ccreceiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_ccreceiver_email = NULL;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (BCC)\r\n                if ((isset($_POST[\'kuo_bccreceiver_email\'])) && (filter_var($_POST[\'kuo_bccreceiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_bccreceiver_email = filter_var($_POST[\'kuo_bccreceiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_bccreceiver_email = NULL;\r\n                }\r\n\r\n                # SUBJECT\r\n                if (isset($_POST[\'kuo_subject\'])) {\r\n                    if (is_numeric($_POST[\'kuo_subject\'])) {\r\n                        $kuo_zcr_subject = filter_var($_POST[\'kuo_subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_subject = filter_var($_POST[\'kuo_subject\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    # MUST ENSURE THAT A SUBJECT EXISTS TO INCREASE CHANCES OF GETTING MESSAGE TO RECEIVER\r\n                    $kuo_zcr_subject = gTxt(\'new_email\');\r\n                }\r\n\r\n                # X-PRIORITY HEADER\r\n                if ((isset($_POST[\'kuo_xpriority\'])) && (in_array($_POST[\'kuo_xpriority\'],array(1,3,5)))) {\r\n                    $kuo_zcr_xpriority = filter_var($_POST[\'kuo_xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_xpriority = 0;\r\n                }\r\n\r\n                # CHARSET\r\n                if (isset($_POST[\'kuo_charset\'])) {\r\n                    if (is_numeric($_POST[\'kuo_subject\'])) {\r\n                        $kuo_zcr_charset = filter_var($_POST[\'kuo_charset\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_charset = filter_var($_POST[\'kuo_charset\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_charset = \'UTF-8\';\r\n                }\r\n\r\n                # X-MAILER HEADER\r\n                if (isset($_POST[\'kuo_charset\'])) {\r\n                    if (is_numeric($_POST[\'kuo_xmailer\'])) {\r\n                        $kuo_zcr_xmailer = filter_var($_POST[\'kuo_xmailer\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_xmailer = filter_var($_POST[\'kuo_xmailer\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_xmailer = NULL;\r\n                }\r\n\r\n                # SAVING DATA TO DB\r\n                $kuo_zcr_save = safe_update(KUO_ZCR_DB_TABLE_NAME,\r\n                \"\r\n                `debugging` = \'\".$kuo_zcr_debug.\"\',\r\n                `email_host` = \'\".$kuo_zcr_host.\"\',\r\n                `email_username` = AES_ENCRYPT(\'\".$kuo_zcr_username.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_password` = AES_ENCRYPT(\'\".$kuo_zcr_password.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `use_auth` = \'\".$kuo_zcr_auth.\"\',\r\n                `ssl_tls` = \'\".$kuo_zcr_use.\"\',\r\n                `port` = \'\".$kuo_zcr_port.\"\',\r\n                `use_html` = \'\".$kuo_zcr_html.\"\',\r\n                `email_to` = AES_ENCRYPT(\'\".$kuo_zcr_receiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_cc` = AES_ENCRYPT(\'\".$kuo_zcr_ccreceiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_bcc` = AES_ENCRYPT(\'\".$kuo_zcr_bccreceiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `subject` = \'\".$kuo_zcr_subject.\"\',\r\n                `xpriority` = \'\".$kuo_zcr_xpriority.\"\',\r\n                `charset` = \'\".$kuo_zcr_charset.\"\',\r\n                `xmailer` = \'\".$kuo_zcr_xmailer.\"\'\r\n                \"\r\n                ,\r\n                \"`id` = \'1\'\",\r\n                KUO_ZCR_DEBUG);\r\n\r\n                # SAVED OR NOT, SHOW SUITABLE MESSAGE FOR A FEW SECONDS\r\n                if ($kuo_zcr_save) {\r\n                    echo \'<p id=\"kuo_saved_message\">\'.gTxt(\'preferences_saved\').\'</p>\';\r\n                }\r\n                else {\r\n                    echo \'<p id=\"kuo_saved_message\" class=\"kuo_error\">\'.ucfirst(strip_tags(gTxt(\'not_saved\'))).\'</p>\';\r\n                }\r\n\r\n                echo \'<script type=\"text/javascript\">$(\\\'#kuo_saved_message\\\').delay(5000).fadeOut(300);</script>\';\r\n            }\r\n            # THIS ELSE MEANS WE ARE NOT SAVING DATA BUT FETCHING IT FROM DB\r\n            else {\r\n                $kuo_zcr_settings = kuo_zcr_info();\r\n\r\n                $kuo_zcr_debug = filter_var($kuo_zcr_settings[\'debugging\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_host = filter_var($kuo_zcr_settings[\'email_host\'],FILTER_SANITIZE_STRING);\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'email_username\'])) {\r\n                    $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'email_password\'])) {\r\n                    $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                $kuo_zcr_auth = filter_var($kuo_zcr_settings[\'use_auth\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_use = filter_var($kuo_zcr_settings[\'ssl_tls\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_port = filter_var($kuo_zcr_settings[\'port\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_html = filter_var($kuo_zcr_settings[\'use_html\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_receiver_email = filter_var($kuo_zcr_settings[\'email_to\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_ccreceiver_email = filter_var($kuo_zcr_settings[\'email_cc\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_bccreceiver_email = filter_var($kuo_zcr_settings[\'email_bcc\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_charset = filter_var($kuo_zcr_settings[\'charset\'],FILTER_SANITIZE_STRING);\r\n                $kuo_zcr_xmailer = filter_var($kuo_zcr_settings[\'xmailer\'],FILTER_SANITIZE_STRING);\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'subject\'])) {\r\n                    $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                # MUST ENSURE THAT A SUBJECT EXISTS TO INCREASE CHANCES OF GETTING MESSAGE TO RECEIVER\r\n                if (empty($kuo_zcr_subject)) {\r\n                    $kuo_zcr_subject = gTxt(\'new_email\');\r\n                }\r\n\r\n                $kuo_zcr_xpriority = filter_var($kuo_zcr_settings[\'xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n            }\r\n            # ENDS: SAVING SETTINGS TO DB / FETCHING SETTINGS FROM DB\r\n\r\n            # MUST CHECK THAT PHP CLASSES ARE THERE\r\n            if ((file_exists(KUO_ZCR_CLASS_PHPMAILER_MAIN)) && (is_readable(KUO_ZCR_CLASS_PHPMAILER_MAIN)) && (kuo_class_surface_check(KUO_ZCR_CLASS_PHPMAILER_MAIN) === \'OK\')) {\r\n                $kuo_status_phpmailer_main_html = KUO_ZRC_HTML_OK;\r\n                $kuo_status_phpmailer_main_css = KUO_ZCR_CSS_OK;\r\n            }\r\n            else {\r\n                $kuo_status_phpmailer_main_html = KUO_ZRC_HTML_ERROR.\' <a href=\"\'.KUO_ZCR_LOCATION.\'&amp;kuo_install=\'.basename(KUO_ZCR_CLASS_PHPMAILER_MAIN,\'.php\').\'\" rel=\"nofollow\" class=\"kuo_install\">Try to\r\n                install from Github?</a>\';\r\n                $kuo_status_phpmailer_main_css = KUO_ZCR_CSS_ERROR;\r\n            }\r\n\r\n            if ((file_exists(KUO_ZCR_CLASS_PHPMAILER_SMTP)) && (is_readable(KUO_ZCR_CLASS_PHPMAILER_SMTP)) && (kuo_class_surface_check(KUO_ZCR_CLASS_PHPMAILER_SMTP) === \'OK\')) {\r\n                $kuo_status_phpmailer_smtp_html = KUO_ZRC_HTML_OK;\r\n                $kuo_status_phpmailer_smtp_css = KUO_ZCR_CSS_OK;\r\n            }\r\n            else {\r\n                $kuo_status_phpmailer_smtp_html = KUO_ZRC_HTML_ERROR.\' <a href=\"\'.KUO_ZCR_LOCATION.\'&amp;kuo_install=\'.basename(KUO_ZCR_CLASS_PHPMAILER_SMTP,\'.php\').\'\" rel=\"nofollow\" class=\"kuo_install\">Try to\r\n                install from Github?</a>\';\r\n                $kuo_status_phpmailer_smtp_css = KUO_ZCR_CSS_ERROR;\r\n            }\r\n\r\n            echo \'<form action=\"\'.KUO_ZCR_LOCATION.\'\" method=\"post\">\r\n                <fieldset>\r\n                    <legend>SMTP settings</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_debug\">Debugging mode:</label><select name=\"kuo_debug\" id=\"kuo_debug\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_debug)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_debug)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Yes</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_host\">Outgoing mail server (SMTP):</label><input name=\"kuo_host\" id=\"kuo_host\" type=\"text\" size=\"25\" minlength=\"3\" maxlength=\"255\" value=\"\'.$kuo_zcr_host.\'\"\r\n                        placeholder=\"Example: smtp.host.com\" /></li>\r\n                        <li><label for=\"kuo_username\">Email account username:</label><input name=\"kuo_username\" id=\"kuo_username\" type=\"email\" size=\"25\" minlength=\"3\" maxlength=\"255\" value=\"\'.$kuo_zcr_username.\'\" /></li>\r\n                        <li><label for=\"kuo_password\">Email account password:</label><input name=\"kuo_password\" id=\"kuo_password\" type=\"password\" size=\"25\" minlength=\"1\" maxlength=\"255\" value=\"\'.$kuo_zcr_password.\'\" /></li>\r\n                        <li><label for=\"kuo_auth\">Use SMTP Auth:</label><select name=\"kuo_auth\" id=\"kuo_auth\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_auth)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_auth)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Yes</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_use\">SSL or TLS:</label><select name=\"kuo_use\" id=\"kuo_use\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_use)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>TLS</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_use)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>SSL</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_port\">Port number:</label><input name=\"kuo_port\" id=\"kuo_port\" type=\"text\" size=\"3\" minlength=\"1\" maxlength=\"4\" value=\"\'.$kuo_zcr_port.\'\"\r\n                        placeholder=\"Example: 465\" /></li>\r\n                        <li><label for=\"kuo_xpriority\">Message priority (X-Priority):</label><select name=\"kuo_xpriority\" id=\"kuo_xpriority\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_xpriority)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Don\\\'t set</option>\r\n                            <option value=\"5\"\';\r\n                            if ($kuo_zcr_xpriority == 5) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Low (5)</option>\r\n                            <option value=\"3\"\';\r\n                            if ($kuo_zcr_xpriority == 3) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Normal (3)</option>\r\n                            <option value=\"1\"\';\r\n                            if ($kuo_zcr_xpriority == 1) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>High (1)</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_charset\">Message charset:</label><input name=\"kuo_charset\" id=\"kuo_charset\" type=\"text\" size=\"5\" minlength=\"3\" maxlength=\"15\" value=\"\'.$kuo_zcr_charset.\'\"\r\n                        placholder=\"Example: UTF-8\" /></li>\r\n                        <li><label for=\"kuo_xmailer\">X-Mailer in message header:</label><input name=\"kuo_xmailer\" id=\"kuo_xmailer\" type=\"text\" size=\"25\" minlength=\"1\" maxlength=\"100\" value=\"\'.$kuo_zcr_xmailer.\'\"\r\n                        placeholder=\"Leave blank for default\" /><span>Default X-Mailer header is something like <code>PHPMailer 5.2.16 (https://github.com/PHPMailer/PHPMailer/)</code> including that URL.</span></li>\r\n                    </ul>\r\n                    </fieldset>\r\n                    <fieldset>\r\n                    <legend>Email addresses</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_receiver_email\">Messages are sent to (<code>to</code> email):</label><input name=\"kuo_receiver_email\" id=\"kuo_receiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_receiver_email.\'\" placeholder=\"Messages to\" /><span>This <strong>overrides</strong> receiver email address that might be saved inside <code>zem_contact_reborn</code>\r\n                        tag.</span></li>\r\n                        <li><label for=\"kuo_ccreceiver_email\">Messages are sent also to (<code>cc</code> email):</label><input name=\"kuo_ccreceiver_email\" id=\"kuo_ccreceiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_ccreceiver_email.\'\" placeholder=\"A copy to\" /></li>\r\n                        <li><label for=\"kuo_bccreceiver_email\">Messages are sent also to (<code>bcc</code> email):</label><input name=\"kuo_bccreceiver_email\" id=\"kuo_bccreceiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_bccreceiver_email.\'\" placeholder=\"A hidden copy to\" /></li>\r\n                    </ul>\r\n                </fieldset>\r\n                <fieldset>\r\n                    <legend>Message</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_subject\">Message subject:</label><input name=\"kuo_subject\" id=\"kuo_subject\" type=\"text\" size=\"25\" minlength=\"1\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_subject.\'\" placeholder=\"Subject field\" required=\"required\" /></li>\r\n                        <li><label for=\"kuo_html\">Use HTML in messages:</label><select name=\"kuo_html\" id=\"kuo_html\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_html)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_html)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Yes</option>\r\n                        </select></li>\r\n                    </ul>\r\n                </fieldset>\r\n                <fieldset>\r\n                    <ul>\r\n                        <li><button name=\"kuo_zcr_save\">\'.gTxt(\'save\').\'</button></li>\r\n                    </ul>\r\n                </fieldset>\r\n            </form>\r\n            <div id=\"status\">\r\n                <h3>Status of required PHPMailer classes</h3>\r\n                <ul>\r\n                    <li \'.$kuo_status_phpmailer_main_css.\'><code>\'.str_replace(\'/\',\'\\\\\',KUO_ZCR_CLASS_PHPMAILER_MAIN).\'</code>\'.$kuo_status_phpmailer_main_html.\'</li>\r\n                    <li \'.$kuo_status_phpmailer_smtp_css.\'><code>\'.str_replace(\'/\',\'\\\\\',KUO_ZCR_CLASS_PHPMAILER_SMTP).\'</code>\'.$kuo_status_phpmailer_smtp_html.\'</li>\r\n                </ul>\r\n            </div>\';\r\n        }\r\n    }\r\n    # ENDS: EXTENSION TAB\r\n}\r\n# ENDS: ADMIN SIDE\r\n# BEGINS: PUBLIC SIDE\r\n\r\nif (@txpinterface === \'public\') {\r\n    register_callback(\'kuo_zcr_smtp\',\'zemcontact.deliver\');\r\n\r\n    # FUNCTION BEGINS: KUO_ZCR_SMTP\r\n    function kuo_zcr_smtp() {\r\n\r\n        global $sitename, $zem_contact_form, $zem_contact_from;\r\n\r\n        require_once(KUO_ZCR_CLASS_PHPMAILER_MAIN);\r\n        require_once(KUO_ZCR_CLASS_PHPMAILER_SMTP);\r\n\r\n        # CLEANING SITE NAME\r\n        if ((isset($sitename)) && (!empty($sitename))) {\r\n            if (is_numeric($sitename)) {\r\n                $kuo_zcr_sitename = filter_var($sitename,FILTER_SANITIZE_NUMBER_INT);\r\n            }\r\n            else {\r\n                $kuo_zcr_sitename = filter_var($sitename,FILTER_SANITIZE_STRING);\r\n            }\r\n        }\r\n        else {\r\n            $kuo_zcr_sitename = NULL;\r\n        }\r\n\r\n        # CLEANING OTHER EMAIL DATA\r\n        if ((isset($zem_contact_form)) && (!empty($zem_contact_form))) {\r\n            $kuo_zcr_content = filter_var_array($zem_contact_form,FILTER_SANITIZE_STRING);\r\n            if (!empty($kuo_zcr_content)) {\r\n                if (is_array($kuo_zcr_content)) {\r\n                    # THIS WILL CREATE TWO SETS OF CONTENT TO BE SENT: HTML AND TEXT\r\n                    $kuo_content_html = NULL;\r\n                    $kuo_content_plain = NULL;\r\n                    foreach ($kuo_zcr_content as $kuo_content_key => $kuo_content_value) {\r\n                        $kuo_content_html .= \'<li><strong>\'.$kuo_content_key.\':</strong> \'.$kuo_content_value.\'</li>\';\r\n                        $kuo_content_plain .= $kuo_content_key.\': \'.$kuo_content_value.PHP_EOL.\'***\'.PHP_EOL;\r\n                    }\r\n                    $kuo_content_html = \'<ul>\'.$kuo_content_html.\'</ul>\';\r\n                    $kuo_content_plain = PHP_EOL.$kuo_content_plain.PHP_EOL;\r\n                }\r\n                else {\r\n                    $kuo_content_html = NULL;\r\n                    $kuo_content_plain = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_zcr_content = NULL;\r\n            }\r\n        }\r\n        else {\r\n            $kuo_zcr_content = NULL;\r\n        }\r\n\r\n        # CLEANING FROM EMAIL\r\n        if ((isset($zem_contact_from)) && (!empty($zem_contact_from))) {\r\n            $kuo_zcr_from = filter_var($zem_contact_from,FILTER_SANITIZE_EMAIL);\r\n        }\r\n        else {\r\n            $kuo_zcr_from = NULL;\r\n        }\r\n\r\n        $kuo_zcr_settings = kuo_zcr_info();\r\n\r\n        $kuo_zcr_debug = filter_var($kuo_zcr_settings[\'debugging\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_host = filter_var($kuo_zcr_settings[\'email_host\'],FILTER_SANITIZE_STRING);\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'email_username\'])) {\r\n            $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'email_password\'])) {\r\n            $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        $kuo_zcr_auth = filter_var($kuo_zcr_settings[\'use_auth\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_use = filter_var($kuo_zcr_settings[\'ssl_tls\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_port = filter_var($kuo_zcr_settings[\'port\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_html = filter_var($kuo_zcr_settings[\'use_html\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_receiver_email = filter_var($kuo_zcr_settings[\'email_to\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_ccreceiver_email = filter_var($kuo_zcr_settings[\'email_cc\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_bccreceiver_email = filter_var($kuo_zcr_settings[\'email_bcc\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_charset = filter_var($kuo_zcr_settings[\'charset\'],FILTER_SANITIZE_STRING);\r\n        $kuo_zcr_xmailer = filter_var($kuo_zcr_settings[\'xmailer\'],FILTER_SANITIZE_STRING);\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'subject\'])) {\r\n            $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        $kuo_zcr_xpriority = filter_var($kuo_zcr_settings[\'xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n        # THE ACTUAL SMTP BIT STARTS HERE\r\n        if ((!empty($kuo_zcr_content)) && (!empty($kuo_zcr_from)) && (!empty($kuo_zcr_settings)) && (!empty($kuo_zcr_host)) && (!empty($kuo_zcr_username)) && (!empty($kuo_zcr_password)) && (!empty($kuo_zcr_receiver_email))) {\r\n\r\n            $kuo_smtp = new PHPMailer;\r\n\r\n            # 0 = DEBUGGING OFF\r\n            # 3 = DEBUGGING ON (MOST DETAILED)\r\n            if (($kuo_zcr_debug == 0) or (KUO_ZCR_DEBUG == 0)) {\r\n                $kuo_zcr_debug = 0;\r\n            }\r\n            else {\r\n                $kuo_zcr_debug = 3;\r\n                #var_dump($kuo_zcr_sitename,$kuo_zcr_recipient,$kuo_zcr_content,$kuo_zcr_from);\r\n            }\r\n            $kuo_smtp->SMTPDebug = $kuo_zcr_debug;\r\n\r\n            $kuo_smtp->isSMTP();\r\n\r\n            # HOST NAME\r\n            $kuo_smtp->Host = $kuo_zcr_host;\r\n\r\n            # SMTP AUTH ON OR OFF\r\n            if ($kuo_zcr_auth == 1) {\r\n                $kuo_zcr_auth = true;\r\n            }\r\n            else {\r\n                $kuo_zcr_auth = false;\r\n            }\r\n            $kuo_smtp->SMTPAuth = $kuo_zcr_auth;\r\n\r\n            # SMTP USERNAME\r\n            $kuo_smtp->Username = $kuo_zcr_username;\r\n\r\n            # SMTP PASSWORD\r\n            $kuo_smtp->Password = $kuo_zcr_password;\r\n\r\n            # TLS OR SSL\r\n            if ($kuo_zcr_use == 0) {\r\n                $kuo_zcr_use = \'tls\';\r\n            }\r\n            else {\r\n                $kuo_zcr_use = \'ssl\';\r\n            }\r\n            $kuo_smtp->SMTPSecure = $kuo_zcr_use;\r\n\r\n            # PORT NUMBER\r\n            $kuo_smtp->Port = $kuo_zcr_port;\r\n\r\n            # SENDER EMAIL AND NAME\r\n            $kuo_smtp->setFrom($kuo_zcr_receiver_email,$kuo_zcr_receiver_email);\r\n\r\n            # RECEIVER EMAIL AND NAME\r\n            $kuo_smtp->addAddress($kuo_zcr_receiver_email,$kuo_zcr_receiver_email);\r\n\r\n            # REPLY-TO EMAIL AND NAME\r\n            $kuo_smtp->addReplyTo($kuo_zcr_from);\r\n\r\n            # SEND A COPY TO\r\n            if (!empty($kuo_zcr_ccreceiver_email)) {\r\n                $kuo_smtp->addCC($kuo_zcr_ccreceiver_email);\r\n            }\r\n\r\n            # SEND A BCC (HIDDEN) COPY TO\r\n            if (!empty($kuo_zcr_bccreceiver_email)) {\r\n                $kuo_smtp->addBCC($kuo_zcr_bccreceiver_email);\r\n            }\r\n\r\n            # IS EMAIL HTML (TRUE) OR PLAIN TEXT (FALSE)\r\n            if ($kuo_zcr_html == 1) {\r\n                $kuo_zcr_html = true;\r\n            }\r\n            else {\r\n                $kuo_zcr_html = false;\r\n            }\r\n            $kuo_smtp->isHTML($kuo_zcr_html);\r\n\r\n            # CHARSET\r\n            if (empty($kuo_zcr_charset)) {\r\n                $kuo_zcr_charset = \'UTF-8\';\r\n            }\r\n            $kuo_smtp->CharSet = $kuo_zcr_charset;\r\n\r\n            # SUBJECT FIELD\r\n            if (empty($kuo_zcr_subject)) {\r\n                $kuo_zcr_subject = $kuo_zcr_sitename;\r\n            }\r\n            $kuo_smtp->Subject = $kuo_zcr_subject;\r\n\r\n            # CONTENT FIELD\r\n            $kuo_smtp->Body = $kuo_content_html;\r\n\r\n            # CONTENT FIELD THAT IS SHOWN IN CASE HTML NOT ALLOWED\r\n            $kuo_smtp->AltBody = $kuo_content_plain;\r\n\r\n            # X-PRIORITY (IS NOT SET IF ZERO)\r\n            if (in_array($kuo_zcr_xpriority,array(1,3,5))) {\r\n                $kuo_smtp->addCustomHeader(\'X-Priority: \'.$kuo_zcr_xpriority);\r\n            }\r\n\r\n            # X-MAILER (IF EMPTY = USING DEFAULT)\r\n            if (!empty($kuo_zcr_xmailer)) {\r\n                $kuo_smtp->XMailer = $kuo_zcr_xmailer;\r\n            }\r\n\r\n            # ATTEMPTING TO SEND\r\n            if (!$kuo_smtp->send()) {\r\n                sleep(1);\r\n                return \'zemcontact.fail\';\r\n            }\r\n            else {\r\n                return \'zemcontact.skip\';\r\n            }\r\n        }\r\n        else {\r\n            sleep(1);\r\n            return \'zemcontact.fail\';\r\n        }\r\n    }\r\n    # FUNCTION ENDS: KUO_ZCR_SMTP\r\n}\r\n# ENDS: PUBLIC SIDE\r\n', '# BEGINS: PUBLIC OR ADMIN\r\nif (in_array(@txpinterface,array(\'admin\',\'public\'))) {\r\n    # CHANGE THE VALUE OF THIS CONSTANT TO SOMETHING OF YOUR LIKING IF YOU\'RE NOT SATISFIED WITH IT\r\n    define(\'KUO_ZCR_SECRET_KEY\',md5(str_replace(\'/\',\'\',(strrev($GLOBALS[\'prefs\'][\'path_from_root\']).md5(dirname(__FILE__))))));\r\n\r\n    define(\'KUO_ZCR_DB_TABLE_NAME\',\'kuo_zcr_smtp\');\r\n    define(\'KUO_ZCR_CLASS_PHPMAILER_MAIN\',dirname(__FILE__).\'/kuo_zcr_smtp/class.phpmailer.php\');\r\n    define(\'KUO_ZCR_CLASS_PHPMAILER_SMTP\',dirname(__FILE__).\'/kuo_zcr_smtp/class.smtp.php\');\r\n\r\n    # DEBUGGING MODE FOR EXTENSION TAB\r\n    # 1 = ON\r\n    # 0 = OFF\r\n    define(\'KUO_ZCR_DEBUG\',0);\r\n\r\n    # BEGINS: FUNCTION THAT FETCHES SMTP INFO FROM DB\r\n    function kuo_zcr_info() {\r\n        $kuo_zcr_settings = safe_row(\"`debugging`, `email_host`, AES_DECRYPT(`email_username`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_username`, AES_DECRYPT(`email_password`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_password`, `use_auth`,\r\n        `ssl_tls`, `port`, `use_html`, AES_DECRYPT(`email_to`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_to`, AES_DECRYPT(`email_cc`,\'\".KUO_ZCR_SECRET_KEY.\"\') AS `email_cc`, AES_DECRYPT(`email_bcc`,\'\".KUO_ZCR_SECRET_KEY.\"\')\r\n        AS `email_bcc`, `subject`, `xpriority`, `charset`, `xmailer`\r\n        \",\r\n        KUO_ZCR_DB_TABLE_NAME,\"`id` = \'1\' LIMIT 0, 1\",KUO_ZCR_DEBUG);\r\n        return $kuo_zcr_settings;\r\n    }\r\n    # ENDS: FUNCTION THAT FETCHES SMTP INFO FROM DB\r\n}\r\n# ENDS: PUBLIC OR ADMIN\r\n\r\n# BEGINS: ADMIN SIDE\r\nif (@txpinterface === \'admin\') {\r\n\r\n    add_privs(\'kuo_zcr_smtp\',\'1\');\r\n    register_tab(\'extensions\',\'kuo_zcr_smtp\',\'SMTP for ZCR\');\r\n    register_callback(\'kuo_zcr_smtp_settings\',\'kuo_zcr_smtp\');\r\n    register_callback(\'kuo_zcr_smtp_uninstall\',\'plugin_lifecycle.kuo_zcr_smtp\',\'disabled\');\r\n    register_callback(\'kuo_zcr_smtp_deleted\',\'plugin_lifecycle.kuo_zcr_smtp\',\'deleted\');\r\n    register_callback(\'kuo_zcr_smtp_install\',\'plugin_lifecycle.kuo_zcr_smtp\',\'installed\');\r\n    register_callback(\'kuo_zcr_smtp_install\',\'plugin_lifecycle.kuo_zcr_smtp\',\'enabled\');\r\n\r\n    define(\'KUO_ZCR_LOCATION\',\'./index.php?event=kuo_zcr_smtp\');\r\n    define(\'KUO_ZCR_CSS_ERROR\',\'class=\"kuo_error\"\');\r\n    define(\'KUO_ZCR_CSS_OK\',\'class=\"kuo_ok\"\');\r\n    define(\'KUO_ZRC_HTML_ERROR\',\'<span \'.KUO_ZCR_CSS_ERROR.\'>Missing</span>\');\r\n    define(\'KUO_ZRC_HTML_OK\',\'<span \'.KUO_ZCR_CSS_OK.\'>OK</span>\');\r\n    define(\'KUO_ZCR_CSS_ERROR\',\'class=\"kuo_error\"\');\r\n    define(\'KUO_ZCR_GITHUB_URL\',\'https://raw.githubusercontent.com/PHPMailer/PHPMailer/master/\');\r\n    define(\'KUO_ZCR_SELECTED_SELECTED\',\' selected=\"selected\"\');\r\n\r\n    if ($GLOBALS[\'event\'] === \'plugin_prefs.kuo_zcr_smtp\') {\r\n        header(\'location: \'.KUO_ZCR_LOCATION);\r\n        die;\r\n    }\r\n\r\n    # BEGINS: FUNCTION THAT UNINSTALL THE PLUGIN\r\n    function kuo_zcr_smtp_uninstall() {\r\n        safe_query(\"DROP TABLE IF EXISTS `\".safe_pfx(KUO_ZCR_DB_TABLE_NAME).\"`\",KUO_ZCR_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT UNINSTALL THE PLUGIN\r\n\r\n    # BEGINS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n    function kuo_zcr_smtp_deleted() {\r\n        kuo_zcr_smtp_uninstall();\r\n    }\r\n    # ENDS: FUNCTION THAT IS TRIGGERED WHEN PLUGIN IS DELETED\r\n\r\n    # BEGINS: FUNCTION THAT INSTALL DB TABLE FOR SETTINGS\r\n    function kuo_zcr_smtp_install() {\r\n        safe_query(\"CREATE TABLE IF NOT EXISTS \".safe_pfx(KUO_ZCR_DB_TABLE_NAME).\" (\r\n        `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\r\n        `debugging` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',\r\n        `email_host` VARCHAR(255) DEFAULT NULL,\r\n        `email_username` BLOB DEFAULT NULL,\r\n        `email_password` BLOB DEFAULT NULL,\r\n        `use_auth` TINYINT(1) UNSIGNED DEFAULT \'0\',\r\n        `ssl_tls` TINYINT(1) UNSIGNED DEFAULT \'0\',\r\n        `port` SMALLINT(5) UNSIGNED DEFAULT NULL,\r\n        `use_html` TINYINT(1) UNSIGNED DEFAULT \'1\',\r\n        `email_to` BLOB DEFAULT NULL,\r\n        `email_cc` BLOB DEFAULT NULL,\r\n        `email_bcc` BLOB DEFAULT NULL,\r\n        `subject` VARCHAR(255) DEFAULT NULL,\r\n        `xpriority` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',\r\n        `charset` VARCHAR(15) NOT NULL DEFAULT \'UTF-8\',\r\n        `xmailer` VARCHAR(100) DEFAULT NULL\r\n        )\",KUO_ZCR_DEBUG);\r\n\r\n        safe_insert(KUO_ZCR_DB_TABLE_NAME,\"\r\n        `debugging` = \'0\',\r\n        `email_host` = NULL,\r\n        `email_username` = NULL,\r\n        `email_password` = NULL,\r\n        `use_auth` = \'0\',\r\n        `ssl_tls` = \'1\',\r\n        `port` = NULL,\r\n        `use_html` = \'1\',\r\n        `email_to` = NULL,\r\n        `email_cc` = NULL,\r\n        `email_bcc` = NULL,\r\n        `subject` = NULL,\r\n        `xpriority` = \'0\',\r\n        `charset` = \'UTF-8\',\r\n        `xmailer` = NULL\r\n        \",KUO_ZCR_DEBUG);\r\n    }\r\n    # ENDS: FUNCTION THAT INSTALL DB TABLE FOR SETTINGS\r\n\r\n    # BEGINS: FUNCTION THAT CHECKS IF PHP CLASS SEEMS FINE\r\n    function kuo_class_surface_check($kuo_class = NULL) {\r\n        # FIRST SET OF CONDITIONS: MUST EXIST, MUST BE READABLE, MUST BE OVER 20 KB\r\n        if ((file_exists($kuo_class)) && (is_readable($kuo_class)) && (filesize($kuo_class) >= 20480)) {\r\n            $kuo_class = file_get_contents($kuo_class,NULL,NULL,0,80);\r\n            # SECOND SET OF CONDITIONS: MUST NOT BE EMPTY, MUST HAVE \"PHPMAILER\" AND MUST BEGIN WITH \"<?php\"\r\n            if ((!empty($kuo_class)) && (strpos($kuo_class,\'PHPMailer\') == true) && (substr($kuo_class,0,5) === \'<?php\')) {\r\n                return \'OK\';\r\n            }\r\n            else {\r\n                return;\r\n            }\r\n            unset($kuo_class);\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION THAT CHECKS IF PHP CLASS SEEMS FINE\r\n\r\n    # BEGINS: FUNCTION THAT ATTEMPTS TO DOWNLOAD AND INSTALL REQUIRED CLASSES\r\n    function kuo_class_installer($kuo_class = NULL) {\r\n        if ($kuo_class === \'class.phpmailer\') {\r\n            $kuo_existing_file_to_check = KUO_ZCR_CLASS_PHPMAILER_MAIN;\r\n        }\r\n        else {\r\n            $kuo_existing_file_to_check = KUO_ZCR_CLASS_PHPMAILER_SMTP;\r\n        }\r\n\r\n        if ((isset($kuo_class)) && (!empty($kuo_class)) && (in_array($kuo_class,array(\'class.phpmailer\',\'class.smtp\'))) && (kuo_class_surface_check($kuo_existing_file_to_check) !== \'OK\')) {\r\n            $kuo_class = $kuo_class.\'.php\';\r\n            $kuo_class_header = @get_headers(KUO_ZCR_GITHUB_URL.$kuo_class);\r\n\r\n            if ((strpos($kuo_class_header[0],\'200\') == true)) {\r\n\r\n                # IF FOLDER IS NOT THERE\r\n                if (!is_dir(dirname(__FILE__).\'/kuo_zcr_smtp/\')) {\r\n                    mkdir(dirname(__FILE__).\'/kuo_zcr_smtp/\',0750);\r\n                }\r\n\r\n                # FETCH CLASS CONTENTS FROM GITHUB\r\n                $kuo_class_contents = file_get_contents(KUO_ZCR_GITHUB_URL.$kuo_class);\r\n\r\n                # SAVE CONTENTS AS A LOCAL FILE\r\n                if (!empty($kuo_class_contents)) {\r\n                    file_put_contents(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class,$kuo_class_contents);\r\n\r\n                    if (file_exists(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class)) {\r\n                        chmod(dirname(__FILE__).\'/kuo_zcr_smtp/\'.$kuo_class,0444);\r\n                    }\r\n\r\n                    header(\'location:\'.KUO_ZCR_LOCATION.\'#status\');\r\n                    die;\r\n                }\r\n                else {\r\n                    return;\r\n                }\r\n                unset($kuo_class);\r\n                unset($kuo_class_contents);\r\n            }\r\n            else {\r\n                return;\r\n            }\r\n        }\r\n        else {\r\n            return;\r\n        }\r\n    }\r\n    # ENDS: FUNCTION THAT ATTEMPTS TO DOWNLOAD AND INSTALL REQUIRED CLASSES\r\n\r\n    # BEGINS: EXTENSION TAB\r\n    function kuo_zcr_smtp_settings() {\r\n        if ($GLOBALS[\'event\'] === \'kuo_zcr_smtp\') {\r\n\r\n            # BEGINS: IF ATTEMPTING TO INSTALL\r\n            if ((isset($_GET[\'kuo_install\'])) && (in_array($_GET[\'kuo_install\'],array(\'class.phpmailer\',\'class.smtp\')))) {\r\n                kuo_class_installer($_GET[\'kuo_install\']);\r\n            }\r\n            # ENDS: IF ATTEMPTING TO INSTALL\r\n\r\n            # SETTINGS PAGE BEGINS\r\n            pagetop(\'SMTP for ZCR\');\r\n\r\n            echo \'<style type=\"text/css\" media=\"all\">\r\n            form fieldset,\r\n            #status{\r\n                padding: 3%;\r\n            }\r\n            form ul,\r\n            #status ul {\r\n                list-style: none;\r\n                padding: 0;\r\n                margin: 0;\r\n            }\r\n            form li,\r\n            #status li {\r\n                display: block;\r\n                clear: both;\r\n                border-bottom: 1px dotted #eee;\r\n                padding: 10px 0;\r\n            }\r\n            label,\r\n            select,\r\n            button {\r\n                cursor: pointer;\r\n            }\r\n            li label {\r\n                padding-right: 5px;\r\n                float: left;\r\n                width: 30%;\r\n                min-width: 160px;\r\n                max-width: 360px;\r\n            }\r\n            li input {\r\n                min-width: 100px;\r\n            }\r\n            li button {\r\n                padding: 5px 20px;\r\n                height: auto;\r\n                font-size: 1.3em;\r\n            }\r\n            span.kuo_error,\r\n            span.kuo_ok {\r\n                padding: 2px 4px;\r\n                margin-left: 10px;\r\n                color: #fff;\r\n                text-transform: uppercase;\r\n                font-weight: bold;\r\n            }\r\n            span.kuo_error {\r\n                background: #c00;\r\n            }\r\n            span.kuo_ok {\r\n                background: #390;\r\n            }\r\n            li.kuo_error code {\r\n                border-color: #c00;\r\n            }\r\n            li.kuo_ok code {\r\n                border-color: #390;\r\n            }\r\n            a.kuo_install {\r\n                margin-left: 10px;\r\n                font-weight: bold;\r\n            }\r\n            #status {\r\n                border: 1px solid #eee;\r\n                margin-top: 1%;\r\n            }\r\n            li span {\r\n                margin-left: 10px;\r\n                border: 1px solid #eee;\r\n                padding: 2px 4px;\r\n            }\r\n            #kuo_saved_message {\r\n                text-align: center;\r\n                background: #390;\r\n                color: #ffc;\r\n                padding: 5px;\r\n                margin: 1% auto;\r\n            }\r\n            #kuo_saved_message.kuo_error {\r\n                background: #c00;\r\n            }\r\n            </style>\';\r\n\r\n            # BEGINS: SAVING SETTINGS TO DB / FETCHING SETTINGS FROM DB\r\n            if ((isset($_POST[\'kuo_zcr_save\'])) && (empty($_POST[\'kuo_zcr_save\']))) {\r\n                # DEBUGGING MODE\r\n                if ((isset($_POST[\'kuo_debug\'])) && ($_POST[\'kuo_debug\'] == 1)) {\r\n                    $kuo_zcr_debug = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_debug = 0;\r\n                }\r\n\r\n                # EMAIL HOST\r\n                if (isset($_POST[\'kuo_host\'])) {\r\n                    $kuo_zcr_host = filter_var($_POST[\'kuo_host\'],FILTER_SANITIZE_STRING);\r\n                }\r\n                else {\r\n                    $kuo_zcr_host = NULL;\r\n                }\r\n\r\n                # EMAIL USERNAME\r\n                if (isset($_POST[\'kuo_username\'])) {\r\n                    if (is_numeric($_POST[\'kuo_username\'])) {\r\n                        $kuo_zcr_username = filter_var($_POST[\'kuo_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_username = filter_var($_POST[\'kuo_username\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_username = NULL;\r\n                }\r\n\r\n                # EMAIL PASSWORD\r\n                if (isset($_POST[\'kuo_password\'])) {\r\n                    if (is_numeric($_POST[\'kuo_password\'])) {\r\n                        $kuo_zcr_password = filter_var($_POST[\'kuo_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_password = filter_var($_POST[\'kuo_password\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_password = NULL;\r\n                }\r\n\r\n                # USE SMTP AUTH\r\n                if ((isset($_POST[\'kuo_auth\'])) && ($_POST[\'kuo_auth\'] == 1)) {\r\n                    $kuo_zcr_auth = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_auth = 0;\r\n                }\r\n\r\n                # SSLT OR TLS\r\n                if ((isset($_POST[\'kuo_use\'])) && ($_POST[\'kuo_use\'] == 1)) {\r\n                    $kuo_zcr_use = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_use = 0;\r\n                }\r\n\r\n                # PORT NUMBER\r\n                if (isset($_POST[\'kuo_port\'])) {\r\n                    if (is_numeric($_POST[\'kuo_port\'])) {\r\n                        $kuo_zcr_port = filter_var($_POST[\'kuo_port\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_port = filter_var($_POST[\'kuo_port\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_port = NULL;\r\n                }\r\n\r\n                # USE HTML IN EMAIL\r\n                if ((isset($_POST[\'kuo_html\'])) && ($_POST[\'kuo_html\'] == 1)) {\r\n                    $kuo_zcr_html = 1;\r\n                }\r\n                else {\r\n                    $kuo_zcr_html = 0;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (TO)\r\n                if ((isset($_POST[\'kuo_receiver_email\'])) && (filter_var($_POST[\'kuo_receiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_receiver_email = filter_var($_POST[\'kuo_receiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_receiver_email = NULL;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (CC)\r\n                if ((isset($_POST[\'kuo_ccreceiver_email\'])) && (filter_var($_POST[\'kuo_ccreceiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_ccreceiver_email = filter_var($_POST[\'kuo_ccreceiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_ccreceiver_email = NULL;\r\n                }\r\n\r\n                # EMAIL RECIPIENT (BCC)\r\n                if ((isset($_POST[\'kuo_bccreceiver_email\'])) && (filter_var($_POST[\'kuo_bccreceiver_email\'],FILTER_VALIDATE_EMAIL))) {\r\n                    $kuo_zcr_bccreceiver_email = filter_var($_POST[\'kuo_bccreceiver_email\'],FILTER_SANITIZE_EMAIL);\r\n                }\r\n                else {\r\n                    $kuo_zcr_bccreceiver_email = NULL;\r\n                }\r\n\r\n                # SUBJECT\r\n                if (isset($_POST[\'kuo_subject\'])) {\r\n                    if (is_numeric($_POST[\'kuo_subject\'])) {\r\n                        $kuo_zcr_subject = filter_var($_POST[\'kuo_subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_subject = filter_var($_POST[\'kuo_subject\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    # MUST ENSURE THAT A SUBJECT EXISTS TO INCREASE CHANCES OF GETTING MESSAGE TO RECEIVER\r\n                    $kuo_zcr_subject = gTxt(\'new_email\');\r\n                }\r\n\r\n                # X-PRIORITY HEADER\r\n                if ((isset($_POST[\'kuo_xpriority\'])) && (in_array($_POST[\'kuo_xpriority\'],array(1,3,5)))) {\r\n                    $kuo_zcr_xpriority = filter_var($_POST[\'kuo_xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_xpriority = 0;\r\n                }\r\n\r\n                # CHARSET\r\n                if (isset($_POST[\'kuo_charset\'])) {\r\n                    if (is_numeric($_POST[\'kuo_subject\'])) {\r\n                        $kuo_zcr_charset = filter_var($_POST[\'kuo_charset\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_charset = filter_var($_POST[\'kuo_charset\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_charset = \'UTF-8\';\r\n                }\r\n\r\n                # X-MAILER HEADER\r\n                if (isset($_POST[\'kuo_charset\'])) {\r\n                    if (is_numeric($_POST[\'kuo_xmailer\'])) {\r\n                        $kuo_zcr_xmailer = filter_var($_POST[\'kuo_xmailer\'],FILTER_SANITIZE_NUMBER_INT);\r\n                    }\r\n                    else {\r\n                        $kuo_zcr_xmailer = filter_var($_POST[\'kuo_xmailer\'],FILTER_SANITIZE_STRING);\r\n                    }\r\n                }\r\n                else {\r\n                    $kuo_zcr_xmailer = NULL;\r\n                }\r\n\r\n                # SAVING DATA TO DB\r\n                $kuo_zcr_save = safe_update(KUO_ZCR_DB_TABLE_NAME,\r\n                \"\r\n                `debugging` = \'\".$kuo_zcr_debug.\"\',\r\n                `email_host` = \'\".$kuo_zcr_host.\"\',\r\n                `email_username` = AES_ENCRYPT(\'\".$kuo_zcr_username.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_password` = AES_ENCRYPT(\'\".$kuo_zcr_password.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `use_auth` = \'\".$kuo_zcr_auth.\"\',\r\n                `ssl_tls` = \'\".$kuo_zcr_use.\"\',\r\n                `port` = \'\".$kuo_zcr_port.\"\',\r\n                `use_html` = \'\".$kuo_zcr_html.\"\',\r\n                `email_to` = AES_ENCRYPT(\'\".$kuo_zcr_receiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_cc` = AES_ENCRYPT(\'\".$kuo_zcr_ccreceiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `email_bcc` = AES_ENCRYPT(\'\".$kuo_zcr_bccreceiver_email.\"\',\'\".KUO_ZCR_SECRET_KEY.\"\'),\r\n                `subject` = \'\".$kuo_zcr_subject.\"\',\r\n                `xpriority` = \'\".$kuo_zcr_xpriority.\"\',\r\n                `charset` = \'\".$kuo_zcr_charset.\"\',\r\n                `xmailer` = \'\".$kuo_zcr_xmailer.\"\'\r\n                \"\r\n                ,\r\n                \"`id` = \'1\'\",\r\n                KUO_ZCR_DEBUG);\r\n\r\n                # SAVED OR NOT, SHOW SUITABLE MESSAGE FOR A FEW SECONDS\r\n                if ($kuo_zcr_save) {\r\n                    echo \'<p id=\"kuo_saved_message\">\'.gTxt(\'preferences_saved\').\'</p>\';\r\n                }\r\n                else {\r\n                    echo \'<p id=\"kuo_saved_message\" class=\"kuo_error\">\'.ucfirst(strip_tags(gTxt(\'not_saved\'))).\'</p>\';\r\n                }\r\n\r\n                echo \'<script type=\"text/javascript\">$(\\\'#kuo_saved_message\\\').delay(5000).fadeOut(300);</script>\';\r\n            }\r\n            # THIS ELSE MEANS WE ARE NOT SAVING DATA BUT FETCHING IT FROM DB\r\n            else {\r\n                $kuo_zcr_settings = kuo_zcr_info();\r\n\r\n                $kuo_zcr_debug = filter_var($kuo_zcr_settings[\'debugging\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_host = filter_var($kuo_zcr_settings[\'email_host\'],FILTER_SANITIZE_STRING);\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'email_username\'])) {\r\n                    $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'email_password\'])) {\r\n                    $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                $kuo_zcr_auth = filter_var($kuo_zcr_settings[\'use_auth\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_use = filter_var($kuo_zcr_settings[\'ssl_tls\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_port = filter_var($kuo_zcr_settings[\'port\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_html = filter_var($kuo_zcr_settings[\'use_html\'],FILTER_SANITIZE_NUMBER_INT);\r\n                $kuo_zcr_receiver_email = filter_var($kuo_zcr_settings[\'email_to\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_ccreceiver_email = filter_var($kuo_zcr_settings[\'email_cc\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_bccreceiver_email = filter_var($kuo_zcr_settings[\'email_bcc\'],FILTER_SANITIZE_EMAIL);\r\n                $kuo_zcr_charset = filter_var($kuo_zcr_settings[\'charset\'],FILTER_SANITIZE_STRING);\r\n                $kuo_zcr_xmailer = filter_var($kuo_zcr_settings[\'xmailer\'],FILTER_SANITIZE_STRING);\r\n\r\n                if (is_numeric($kuo_zcr_settings[\'subject\'])) {\r\n                    $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n                }\r\n                else {\r\n                    $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_STRING);\r\n                }\r\n\r\n                # MUST ENSURE THAT A SUBJECT EXISTS TO INCREASE CHANCES OF GETTING MESSAGE TO RECEIVER\r\n                if (empty($kuo_zcr_subject)) {\r\n                    $kuo_zcr_subject = gTxt(\'new_email\');\r\n                }\r\n\r\n                $kuo_zcr_xpriority = filter_var($kuo_zcr_settings[\'xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n            }\r\n            # ENDS: SAVING SETTINGS TO DB / FETCHING SETTINGS FROM DB\r\n\r\n            # MUST CHECK THAT PHP CLASSES ARE THERE\r\n            if ((file_exists(KUO_ZCR_CLASS_PHPMAILER_MAIN)) && (is_readable(KUO_ZCR_CLASS_PHPMAILER_MAIN)) && (kuo_class_surface_check(KUO_ZCR_CLASS_PHPMAILER_MAIN) === \'OK\')) {\r\n                $kuo_status_phpmailer_main_html = KUO_ZRC_HTML_OK;\r\n                $kuo_status_phpmailer_main_css = KUO_ZCR_CSS_OK;\r\n            }\r\n            else {\r\n                $kuo_status_phpmailer_main_html = KUO_ZRC_HTML_ERROR.\' <a href=\"\'.KUO_ZCR_LOCATION.\'&amp;kuo_install=\'.basename(KUO_ZCR_CLASS_PHPMAILER_MAIN,\'.php\').\'\" rel=\"nofollow\" class=\"kuo_install\">Try to\r\n                install from Github?</a>\';\r\n                $kuo_status_phpmailer_main_css = KUO_ZCR_CSS_ERROR;\r\n            }\r\n\r\n            if ((file_exists(KUO_ZCR_CLASS_PHPMAILER_SMTP)) && (is_readable(KUO_ZCR_CLASS_PHPMAILER_SMTP)) && (kuo_class_surface_check(KUO_ZCR_CLASS_PHPMAILER_SMTP) === \'OK\')) {\r\n                $kuo_status_phpmailer_smtp_html = KUO_ZRC_HTML_OK;\r\n                $kuo_status_phpmailer_smtp_css = KUO_ZCR_CSS_OK;\r\n            }\r\n            else {\r\n                $kuo_status_phpmailer_smtp_html = KUO_ZRC_HTML_ERROR.\' <a href=\"\'.KUO_ZCR_LOCATION.\'&amp;kuo_install=\'.basename(KUO_ZCR_CLASS_PHPMAILER_SMTP,\'.php\').\'\" rel=\"nofollow\" class=\"kuo_install\">Try to\r\n                install from Github?</a>\';\r\n                $kuo_status_phpmailer_smtp_css = KUO_ZCR_CSS_ERROR;\r\n            }\r\n\r\n            echo \'<form action=\"\'.KUO_ZCR_LOCATION.\'\" method=\"post\">\r\n                <fieldset>\r\n                    <legend>SMTP settings</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_debug\">Debugging mode:</label><select name=\"kuo_debug\" id=\"kuo_debug\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_debug)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_debug)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Yes</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_host\">Outgoing mail server (SMTP):</label><input name=\"kuo_host\" id=\"kuo_host\" type=\"text\" size=\"25\" minlength=\"3\" maxlength=\"255\" value=\"\'.$kuo_zcr_host.\'\"\r\n                        placeholder=\"Example: smtp.host.com\" /></li>\r\n                        <li><label for=\"kuo_username\">Email account username:</label><input name=\"kuo_username\" id=\"kuo_username\" type=\"email\" size=\"25\" minlength=\"3\" maxlength=\"255\" value=\"\'.$kuo_zcr_username.\'\" /></li>\r\n                        <li><label for=\"kuo_password\">Email account password:</label><input name=\"kuo_password\" id=\"kuo_password\" type=\"password\" size=\"25\" minlength=\"1\" maxlength=\"255\" value=\"\'.$kuo_zcr_password.\'\" /></li>\r\n                        <li><label for=\"kuo_auth\">Use SMTP Auth:</label><select name=\"kuo_auth\" id=\"kuo_auth\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_auth)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_auth)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Yes</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_use\">SSL or TLS:</label><select name=\"kuo_use\" id=\"kuo_use\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_use)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>TLS</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_use)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>SSL</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_port\">Port number:</label><input name=\"kuo_port\" id=\"kuo_port\" type=\"text\" size=\"3\" minlength=\"1\" maxlength=\"4\" value=\"\'.$kuo_zcr_port.\'\"\r\n                        placeholder=\"Example: 465\" /></li>\r\n                        <li><label for=\"kuo_xpriority\">Message priority (X-Priority):</label><select name=\"kuo_xpriority\" id=\"kuo_xpriority\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_xpriority)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Don\\\'t set</option>\r\n                            <option value=\"5\"\';\r\n                            if ($kuo_zcr_xpriority == 5) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>Low (5)</option>\r\n                            <option value=\"3\"\';\r\n                            if ($kuo_zcr_xpriority == 3) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Normal (3)</option>\r\n                            <option value=\"1\"\';\r\n                            if ($kuo_zcr_xpriority == 1) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>High (1)</option>\r\n                        </select></li>\r\n                        <li><label for=\"kuo_charset\">Message charset:</label><input name=\"kuo_charset\" id=\"kuo_charset\" type=\"text\" size=\"5\" minlength=\"3\" maxlength=\"15\" value=\"\'.$kuo_zcr_charset.\'\"\r\n                        placholder=\"Example: UTF-8\" /></li>\r\n                        <li><label for=\"kuo_xmailer\">X-Mailer in message header:</label><input name=\"kuo_xmailer\" id=\"kuo_xmailer\" type=\"text\" size=\"25\" minlength=\"1\" maxlength=\"100\" value=\"\'.$kuo_zcr_xmailer.\'\"\r\n                        placeholder=\"Leave blank for default\" /><span>Default X-Mailer header is something like <code>PHPMailer 5.2.16 (https://github.com/PHPMailer/PHPMailer/)</code> including that URL.</span></li>\r\n                    </ul>\r\n                    </fieldset>\r\n                    <fieldset>\r\n                    <legend>Email addresses</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_receiver_email\">Messages are sent to (<code>to</code> email):</label><input name=\"kuo_receiver_email\" id=\"kuo_receiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_receiver_email.\'\" placeholder=\"Messages to\" /><span>This <strong>overrides</strong> receiver email address that might be saved inside <code>zem_contact_reborn</code>\r\n                        tag.</span></li>\r\n                        <li><label for=\"kuo_ccreceiver_email\">Messages are sent also to (<code>cc</code> email):</label><input name=\"kuo_ccreceiver_email\" id=\"kuo_ccreceiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_ccreceiver_email.\'\" placeholder=\"A copy to\" /></li>\r\n                        <li><label for=\"kuo_bccreceiver_email\">Messages are sent also to (<code>bcc</code> email):</label><input name=\"kuo_bccreceiver_email\" id=\"kuo_bccreceiver_email\" type=\"email\" size=\"25\" minlength=\"3\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_bccreceiver_email.\'\" placeholder=\"A hidden copy to\" /></li>\r\n                    </ul>\r\n                </fieldset>\r\n                <fieldset>\r\n                    <legend>Message</legend>\r\n                    <ul>\r\n                        <li><label for=\"kuo_subject\">Message subject:</label><input name=\"kuo_subject\" id=\"kuo_subject\" type=\"text\" size=\"25\" minlength=\"1\"\r\n                        maxlength=\"255\" value=\"\'.$kuo_zcr_subject.\'\" placeholder=\"Subject field\" required=\"required\" /></li>\r\n                        <li><label for=\"kuo_html\">Use HTML in messages:</label><select name=\"kuo_html\" id=\"kuo_html\" required=\"required\">\r\n                            <option value=\"0\"\';\r\n                            if (empty($kuo_zcr_html)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                            echo \'>No</option>\r\n                            <option value=\"1\"\';\r\n                            if (!empty($kuo_zcr_html)) {\r\n                                echo KUO_ZCR_SELECTED_SELECTED;\r\n                            }\r\n                        echo \'>Yes</option>\r\n                        </select></li>\r\n                    </ul>\r\n                </fieldset>\r\n                <fieldset>\r\n                    <ul>\r\n                        <li><button name=\"kuo_zcr_save\">\'.gTxt(\'save\').\'</button></li>\r\n                    </ul>\r\n                </fieldset>\r\n            </form>\r\n            <div id=\"status\">\r\n                <h3>Status of required PHPMailer classes</h3>\r\n                <ul>\r\n                    <li \'.$kuo_status_phpmailer_main_css.\'><code>\'.str_replace(\'/\',\'\\\\\',KUO_ZCR_CLASS_PHPMAILER_MAIN).\'</code>\'.$kuo_status_phpmailer_main_html.\'</li>\r\n                    <li \'.$kuo_status_phpmailer_smtp_css.\'><code>\'.str_replace(\'/\',\'\\\\\',KUO_ZCR_CLASS_PHPMAILER_SMTP).\'</code>\'.$kuo_status_phpmailer_smtp_html.\'</li>\r\n                </ul>\r\n            </div>\';\r\n        }\r\n    }\r\n    # ENDS: EXTENSION TAB\r\n}\r\n# ENDS: ADMIN SIDE\r\n# BEGINS: PUBLIC SIDE\r\n\r\nif (@txpinterface === \'public\') {\r\n    register_callback(\'kuo_zcr_smtp\',\'zemcontact.deliver\');\r\n\r\n    # FUNCTION BEGINS: KUO_ZCR_SMTP\r\n    function kuo_zcr_smtp() {\r\n\r\n        global $sitename, $zem_contact_form, $zem_contact_from;\r\n\r\n        require_once(KUO_ZCR_CLASS_PHPMAILER_MAIN);\r\n        require_once(KUO_ZCR_CLASS_PHPMAILER_SMTP);\r\n\r\n        # CLEANING SITE NAME\r\n        if ((isset($sitename)) && (!empty($sitename))) {\r\n            if (is_numeric($sitename)) {\r\n                $kuo_zcr_sitename = filter_var($sitename,FILTER_SANITIZE_NUMBER_INT);\r\n            }\r\n            else {\r\n                $kuo_zcr_sitename = filter_var($sitename,FILTER_SANITIZE_STRING);\r\n            }\r\n        }\r\n        else {\r\n            $kuo_zcr_sitename = NULL;\r\n        }\r\n\r\n        # CLEANING OTHER EMAIL DATA\r\n        if ((isset($zem_contact_form)) && (!empty($zem_contact_form))) {\r\n            $kuo_zcr_content = filter_var_array($zem_contact_form,FILTER_SANITIZE_STRING);\r\n            if (!empty($kuo_zcr_content)) {\r\n                if (is_array($kuo_zcr_content)) {\r\n                    # THIS WILL CREATE TWO SETS OF CONTENT TO BE SENT: HTML AND TEXT\r\n                    $kuo_content_html = NULL;\r\n                    $kuo_content_plain = NULL;\r\n                    foreach ($kuo_zcr_content as $kuo_content_key => $kuo_content_value) {\r\n                        $kuo_content_html .= \'<li><strong>\'.$kuo_content_key.\':</strong> \'.$kuo_content_value.\'</li>\';\r\n                        $kuo_content_plain .= $kuo_content_key.\': \'.$kuo_content_value.PHP_EOL.\'***\'.PHP_EOL;\r\n                    }\r\n                    $kuo_content_html = \'<ul>\'.$kuo_content_html.\'</ul>\';\r\n                    $kuo_content_plain = PHP_EOL.$kuo_content_plain.PHP_EOL;\r\n                }\r\n                else {\r\n                    $kuo_content_html = NULL;\r\n                    $kuo_content_plain = NULL;\r\n                }\r\n            }\r\n            else {\r\n                $kuo_zcr_content = NULL;\r\n            }\r\n        }\r\n        else {\r\n            $kuo_zcr_content = NULL;\r\n        }\r\n\r\n        # CLEANING FROM EMAIL\r\n        if ((isset($zem_contact_from)) && (!empty($zem_contact_from))) {\r\n            $kuo_zcr_from = filter_var($zem_contact_from,FILTER_SANITIZE_EMAIL);\r\n        }\r\n        else {\r\n            $kuo_zcr_from = NULL;\r\n        }\r\n\r\n        $kuo_zcr_settings = kuo_zcr_info();\r\n\r\n        $kuo_zcr_debug = filter_var($kuo_zcr_settings[\'debugging\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_host = filter_var($kuo_zcr_settings[\'email_host\'],FILTER_SANITIZE_STRING);\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'email_username\'])) {\r\n            $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_username = filter_var($kuo_zcr_settings[\'email_username\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'email_password\'])) {\r\n            $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_password = filter_var($kuo_zcr_settings[\'email_password\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        $kuo_zcr_auth = filter_var($kuo_zcr_settings[\'use_auth\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_use = filter_var($kuo_zcr_settings[\'ssl_tls\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_port = filter_var($kuo_zcr_settings[\'port\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_html = filter_var($kuo_zcr_settings[\'use_html\'],FILTER_SANITIZE_NUMBER_INT);\r\n        $kuo_zcr_receiver_email = filter_var($kuo_zcr_settings[\'email_to\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_ccreceiver_email = filter_var($kuo_zcr_settings[\'email_cc\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_bccreceiver_email = filter_var($kuo_zcr_settings[\'email_bcc\'],FILTER_SANITIZE_EMAIL);\r\n        $kuo_zcr_charset = filter_var($kuo_zcr_settings[\'charset\'],FILTER_SANITIZE_STRING);\r\n        $kuo_zcr_xmailer = filter_var($kuo_zcr_settings[\'xmailer\'],FILTER_SANITIZE_STRING);\r\n\r\n        if (is_numeric($kuo_zcr_settings[\'subject\'])) {\r\n            $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_NUMBER_INT);\r\n        }\r\n        else {\r\n            $kuo_zcr_subject = filter_var($kuo_zcr_settings[\'subject\'],FILTER_SANITIZE_STRING);\r\n        }\r\n\r\n        $kuo_zcr_xpriority = filter_var($kuo_zcr_settings[\'xpriority\'],FILTER_SANITIZE_NUMBER_INT);\r\n\r\n        # THE ACTUAL SMTP BIT STARTS HERE\r\n        if ((!empty($kuo_zcr_content)) && (!empty($kuo_zcr_from)) && (!empty($kuo_zcr_settings)) && (!empty($kuo_zcr_host)) && (!empty($kuo_zcr_username)) && (!empty($kuo_zcr_password)) && (!empty($kuo_zcr_receiver_email))) {\r\n\r\n            $kuo_smtp = new PHPMailer;\r\n\r\n            # 0 = DEBUGGING OFF\r\n            # 3 = DEBUGGING ON (MOST DETAILED)\r\n            if (($kuo_zcr_debug == 0) or (KUO_ZCR_DEBUG == 0)) {\r\n                $kuo_zcr_debug = 0;\r\n            }\r\n            else {\r\n                $kuo_zcr_debug = 3;\r\n                #var_dump($kuo_zcr_sitename,$kuo_zcr_recipient,$kuo_zcr_content,$kuo_zcr_from);\r\n            }\r\n            $kuo_smtp->SMTPDebug = $kuo_zcr_debug;\r\n\r\n            $kuo_smtp->isSMTP();\r\n\r\n            # HOST NAME\r\n            $kuo_smtp->Host = $kuo_zcr_host;\r\n\r\n            # SMTP AUTH ON OR OFF\r\n            if ($kuo_zcr_auth == 1) {\r\n                $kuo_zcr_auth = true;\r\n            }\r\n            else {\r\n                $kuo_zcr_auth = false;\r\n            }\r\n            $kuo_smtp->SMTPAuth = $kuo_zcr_auth;\r\n\r\n            # SMTP USERNAME\r\n            $kuo_smtp->Username = $kuo_zcr_username;\r\n\r\n            # SMTP PASSWORD\r\n            $kuo_smtp->Password = $kuo_zcr_password;\r\n\r\n            # TLS OR SSL\r\n            if ($kuo_zcr_use == 0) {\r\n                $kuo_zcr_use = \'tls\';\r\n            }\r\n            else {\r\n                $kuo_zcr_use = \'ssl\';\r\n            }\r\n            $kuo_smtp->SMTPSecure = $kuo_zcr_use;\r\n\r\n            # PORT NUMBER\r\n            $kuo_smtp->Port = $kuo_zcr_port;\r\n\r\n            # SENDER EMAIL AND NAME\r\n            $kuo_smtp->setFrom($kuo_zcr_receiver_email,$kuo_zcr_receiver_email);\r\n\r\n            # RECEIVER EMAIL AND NAME\r\n            $kuo_smtp->addAddress($kuo_zcr_receiver_email,$kuo_zcr_receiver_email);\r\n\r\n            # REPLY-TO EMAIL AND NAME\r\n            $kuo_smtp->addReplyTo($kuo_zcr_from);\r\n\r\n            # SEND A COPY TO\r\n            if (!empty($kuo_zcr_ccreceiver_email)) {\r\n                $kuo_smtp->addCC($kuo_zcr_ccreceiver_email);\r\n            }\r\n\r\n            # SEND A BCC (HIDDEN) COPY TO\r\n            if (!empty($kuo_zcr_bccreceiver_email)) {\r\n                $kuo_smtp->addBCC($kuo_zcr_bccreceiver_email);\r\n            }\r\n\r\n            # IS EMAIL HTML (TRUE) OR PLAIN TEXT (FALSE)\r\n            if ($kuo_zcr_html == 1) {\r\n                $kuo_zcr_html = true;\r\n            }\r\n            else {\r\n                $kuo_zcr_html = false;\r\n            }\r\n            $kuo_smtp->isHTML($kuo_zcr_html);\r\n\r\n            # CHARSET\r\n            if (empty($kuo_zcr_charset)) {\r\n                $kuo_zcr_charset = \'UTF-8\';\r\n            }\r\n            $kuo_smtp->CharSet = $kuo_zcr_charset;\r\n\r\n            # SUBJECT FIELD\r\n            if (empty($kuo_zcr_subject)) {\r\n                $kuo_zcr_subject = $kuo_zcr_sitename;\r\n            }\r\n            $kuo_smtp->Subject = $kuo_zcr_subject;\r\n\r\n            # CONTENT FIELD\r\n            $kuo_smtp->Body = $kuo_content_html;\r\n\r\n            # CONTENT FIELD THAT IS SHOWN IN CASE HTML NOT ALLOWED\r\n            $kuo_smtp->AltBody = $kuo_content_plain;\r\n\r\n            # X-PRIORITY (IS NOT SET IF ZERO)\r\n            if (in_array($kuo_zcr_xpriority,array(1,3,5))) {\r\n                $kuo_smtp->addCustomHeader(\'X-Priority: \'.$kuo_zcr_xpriority);\r\n            }\r\n\r\n            # X-MAILER (IF EMPTY = USING DEFAULT)\r\n            if (!empty($kuo_zcr_xmailer)) {\r\n                $kuo_smtp->XMailer = $kuo_zcr_xmailer;\r\n            }\r\n\r\n            # ATTEMPTING TO SEND\r\n            if (!$kuo_smtp->send()) {\r\n                sleep(1);\r\n                return \'zemcontact.fail\';\r\n            }\r\n            else {\r\n                return \'zemcontact.skip\';\r\n            }\r\n        }\r\n        else {\r\n            sleep(1);\r\n            return \'zemcontact.fail\';\r\n        }\r\n    }\r\n    # FUNCTION ENDS: KUO_ZCR_SMTP\r\n}\r\n# ENDS: PUBLIC SIDE\r\n', '2b3b73fa62097e9c323fa1d471617fa7', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('ebl_image_edit', 0, 'Eric Limegrover + mrdale + MarcoK', 'http://www.syserror.net/', '2.3', 'Advanced Image Editing Plugin for Textpattern', '<h1><span class=\"caps\">EBL</span> Image Edit</h1>\n\n<h2>Advanced Image Editing Plugin for Textpattern</h2>\n\n<p><span>T</span> his plugin introduced advanced image editing functionality to Textpattern 4.07 and has now been updated to work nicely with the Hive theme of Textpattern 4.6.\n Users may now custom crop, resize, rotate, and create custom thumbnails directly within Textpattern.</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section01\" rel=\"nofollow\">List of features</a></li>\n	<li><a href=\"#help-section02\" rel=\"nofollow\">Plugin requirements</a></li>\n	<li><a href=\"#help-section03\" rel=\"nofollow\">Installation</a></li>\n	<li><a href=\"#help-section04\" rel=\"nofollow\">Usage Instructions</a></li>\n</ol>\n\n<h2>List of features</h2>\n\n<p>p.This plugin allows for functions previously only available in image-editing programins. It allows for users, at any terminal which they&#8217;re logged in to to do the following:</p>\n\n<ul>\n	<li><strong>Cropping</strong>: The original uploaded image can be edited to cut out parts that are not desired and keep only those important facets of the original image.</li>\n	<li><strong>Thumbnail Crop</strong>: Make a thumbnail show not the entire image, but just enough to pique interest.</li>\n	<li><strong>Rotate</strong>: For those pictures that are sideways or upside down. Users may rotate the image clockwise90&amp;deg;, counterclockwise90&amp;deg; or rotate a full 18090&amp;deg;.</li>\n	<li><strong>Resize</strong>: Many digital images are just too large to be shown on the web. This option allows for excessively sized images to be scaled down. Built into the resizing function is the ability to maintain the original height to width ratio, so images will never seem squished one way or the other.</li>\n	<li><strong>Image Backups</strong>: New for <em>2.0</em> is the feature to save an original copy of the the image to a temp folder so that you may restore it at any time. The image must first be saved by clicking on backup, and then may be restored at any time.</li>\n</ul>\n\n<h2>Plugin requirements</h2>\n\n<p>ebl_image_edit 2.3 minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.5</li>\n	<li>Jquery &gt; 1.7.2</li>\n	<li>JCrop http://deepliquid.com/content/Jcrop_Download.html</li>\n</ul>\n\n<h2>Installation</h2>\n\n<p>The general behavior stands: paste plugin code to the plugin installer textarea and run the automatic setup. Then just activate the plugin and you are ready to use new tags that plugin includes like others.</p>\n\n<p>Upload Jcrop folder to the Textpattern directory (e.g., <code>/textpattern/</code> )</p>\n\n<p>p.Once the plugin has been activated, navigate to <em>Plugins -&gt; <span class=\"caps\">EBL</span> Image Edit -&gt; Options</em>. The temp folder and db field will be created. The plugin will be ready for use at this point.</p>\n\n<h2>Usage Instructions</h2>\n\n<p>Further help can be found online in the textpattern forum topic: <a href=\"http://forum.textpattern.com/viewtopic.php?id=29547\" rel=\"nofollow\">Ebl Image Edit</a></p>\n\n<ul>\n	<li><strong>Crop: This tab permits the cropping of images. Click and drag across the image to select the crop area. While selecting the crop region, the size of the crop area willbe displayed in as Width X Height. This is measured in pixels. You&#8217;ll note that the area outside of the crop region is dark, whereas the area inside is lighter. This is the intended effect. Everything within the crop area will be selected and will replace the original image once the *Crop Original</strong> button is clicked.\n	<ul>\n		<li><ins>Predefined size</ins>: This drop-down box allows the user to select pre-defined crop area selections. They may be moved around anywhere on the image. If needed, these areas can be sized differently, but the aspect ratio of the original will remain. <em>(Found in Thumbnail Crop as well.)</em></li>\n		<li><ins>Lock Aspect Ratio</ins>: Unchecking this box will allow for a different aspect ratio than what was selected in the drop down box. <em>(Found in Thumbnail Crop as well.)</em></li>\n	</ul></li>\n	<li><strong>Thumbnail Crop</strong>: Nearly identical in function to the main image crop, the thumbnail crop allows for a selected region to be highlighted to act as the primary image thumbnail. To create the cropped thumbnail, select the crop-area. If you want the image resized to a specific dimension, check <em>Resize Thumbnail</em> and edit the size accordingly. Once you are satisified with the crop area and the options, click on the Create Thumbnail button.</li>\n	<li><strong>Rotate</strong>: This tab allows three seperate methods of rotating. 1. Rotate Clockwise 90&amp;deg; (Turn Right), 2. Counter-Clockwise 90&amp;deg; (Left Turn) or 3. 180&amp;deg; (Flip). Clicking any of these buttons will rotate the original button. <em>Do not repeatedly rotate the images as this will gradually degrade image quality</em>.&lt;/li&gt;</li>\n	<li><strong>Resize</strong>: This tab allows the for a longest-side resize of an original image. Simply input the desired dimensions and the image will be sized down to fit. Note, if the image is smaller in any one dimension that which was entered, it will not be sized up to fit, but rather, sized down in proportion with the longest side. If the image is entirely smaller than the dimensions entered, it will not be resized.</li>\n	<li><strong>Watermark</strong>: This tab allows the for insert an watermark image onto the original image. Select the <em><span class=\"caps\">PNG</span></em> watermark from <code>www.yoursite.com/images/</code> then insert the relative position of watermark. The value of image and position are stored on txp_prefs table.</li>\n	<li><strong>Backup</strong>: This tab allows for the backing-up of an image into a backup folder located within your image directory. When active, uno button are displayed. The Backup will immediately copy the associated image to the backup directory and overwrite any identically named file. If Backup image exist Restore button will show. Clicking Restore will copy that image back to the main image directory but will still maintain the copied file in this location.</li>\n</ul>', 'if (@txpinterface == \'admin\')\n	{\n		//add_privs(\'eblimgeditext\', \'1\');\n		//register_tab(\"extensions\", \"eblimgeditext\", \"Image Edit\");\n		//register_callback(\"ebl_image_edit_ext\", \"eblimgeditext\");\n		add_privs(\'plugin_prefs.ebl_image_edit\', \'1\');\n		register_callback(\"ebl_image_edit_ext\", \"plugin_prefs.ebl_image_edit\");\n\n	switch (gps(\'event\'))\n	{\n		case \'eblrotateleft\':\n			ebl_img_edit(gps(\'imgid\'), \'left\', \'rotate\'); exit();\n			break;\n		case \'eblrotateright\':\n			ebl_img_edit(gps(\'imgid\'), \'right\', \'rotate\'); exit();\n			break;\n		case \'eblrotateup\':\n			ebl_img_edit(gps(\'imgid\'), \'up\', \'rotate\'); exit();\n			break;\n		case \'eblimgcrop\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'crop\'); exit();\n			break;\n		case \'eblimgcrop\':\n			ebl_processUpload(gps(\'uploadType\'));\n			break;\n		case \'ebltmbcrop\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'thumbnail\'); exit();\n			break;\n		case \'eblrszimg\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'rsz\'); exit();\n			break;\n		case \'eblbackupimg\':\n			eblbackupimg(gps(\'imgid\'));\n			break;\n		case \'eblrestoreimg\':\n			eblrestoreimg(gps(\'imgid\'));\n			break;\n		case \'eblwmcreate\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'watermark\'); exit();\n			break;\n	}\n}\n\nfunction ebl_image_edit_ext()\n{\n\n	$step = gps(\'step\');\n\n\n	$message = (is_callable($step)) ? $step() : \'\';\n\n	echo pagetop(\"EBL Image Edit Preferences\", $message);\n\n	echo 	\'<div style=\"margin: 0 auto; width: 400px;\">\'.n.\n			ebl_list_cropStyles().\n			\'</div>\';\n}\n\nfunction ebl_list_cropStyles()\n{\n	global $txp_user, $path_to_site, $img_dir;\n	$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		if(!file_exists($backupdir)) {\n\n		$createEBLtable = safe_query(\"CREATE TABLE `\".PFX.\"ebl_crop` (\".\n									\"`name` varchar(64) NOT NULL,\".\n									\"`width` varchar(16) NOT NULL,\".\n									\"`height` varchar(16) NOT NULL,\".\n									\"`thumb` varchar(1) NOT NULL default \'0\',\".\n									\"UNIQUE KEY `name` (`name`)\".\n									\");\");\n\n		$rs = safe_insert(\'ebl_crop\', \"`name` = \'Image Crop\', `width` = \'500\',`height` = \'500\', `thumb` = \'0\'\");\n		$rs = safe_insert(\'ebl_crop\', \"`name` = \'Thumbnail Square\', `width` = \'100\',`height` = \'100\', `thumb` = \'1\'\");\n\n			$mkdir = (@mkdir($backupdir)) ? TRUE : FALSE;\n			if($mkdir) {\n				echo \"<p  style=\\\"text-align: center; \\\">$backupdir did not exist previously. Now created.</p>\";\n			} else {\n				echo \"<p style=\\\"text-align: center; font-weight: bold; \\\">Unable to create $backupdir. Please check your folder permissions.</p>\";\n			}\n		}\n\n\n\n	echo n.n.hed(gTxt(\'Crop Styles\'), 1, \' style=\"text-align: center; margin-top:2em; font-weight: bold;\"\').\n		n.n.startTable(\'list\',\'\',\'txp-list\').\n		n.\'<thead>\'.tr(\n			n.hCell(gTxt(\'name\')).\n			n.hCell(gTxt(\'width\')).\n			n.hCell().\n			n.hCell(gTxt(\'height\')).\n			n.hCell(gTxt(\'thumbnail\')).\n			n.hCell(gTxt(\'delete\'))\n		).\'</thead>\';\n\n	$rs = safe_rows_start(\'*\', \'ebl_crop\', \'1=1 ORDER BY `name`\');\n\n	$out = \'\';\n\n	if ($rs)\n	{\n		while ($a = nextRow($rs))\n		{\n			$isChecked = ($a[\'thumb\'] == 1) ? \'yes\' : \'no\';\n			$out .= n.tr(\n					td( htmlspecialchars($a[\'name\'])).\n					td( htmlspecialchars($a[\'width\'])).\n					td( htmlspecialchars(\' X \')).\n					td( htmlspecialchars($a[\'height\'])).\n					td( $isChecked).\n					td(\n						dLink(\'plugin_prefs.ebl_image_edit\', \'ebl_cropdelete\', \'stylename\', $a[\'name\'])\n					)\n				);\n		}\n	}\n\n	echo form($out);\n\n	echo n.tr(\n		form(\n			td( fInput(\'text\', \'name\', \'\', \'edit\',\'\',\'\',10) ).\n			td( fInput(\'text\', \'width\', \'\', \'edit\',\'\',\'\',5) ).\n			td().\n			td( fInput(\'text\', \'height\', \'\', \'edit\',\'\',\'\',5) ).\n			td( \'<input name=\"eblistmb\" class=\"checkbox\" type=\"checkbox\">\').\n			td( fInput(\'submit\', \'add\', gTxt(\'add\'), \'smallerbox\') ).\n			n.eInput(\'plugin_prefs.ebl_image_edit\').\n			n.sInput(\'ebl_cropsavenew\')\n		)\n	);\n\n	echo n.endTable();\n}\n\nfunction selectStyles($thumb = \'0\') {\n\n	$rs = safe_rows_start(\'*\', \'ebl_crop\', \'thumb=\'.$thumb.\' ORDER BY `name`\');\n\n	$out = \"<select name=\\\"customsize\\\" id=\\\"customsize$thumb\\\">\' + \".n.\n		   \"\'<option value=\\\"Custom\\\">Custom</option>\' + \".n;\n\n	if ($rs)\n	{\n		while ($a = nextRow($rs))\n		{\n			$width	= $a[\'width\'];\n			$height	= $a[\'height\'];\n			$name 	= $a[\'name\'];\n\n			$out .= \"\'<option value=\".$width.\"x\".$height.\"\\>\".htmlspecialchars($name).\" : $width x $height </option>\' + \".n;\n		}\n	}\n	$out .= \"\'</select>\";\n\n	return $out;\n}\nfunction ebl_cropsavenew()\n{\n\n	extract(doSlash(psa(array(\'name\', \'width\',\'height\',\'eblistmb\'))));\n\n	if ($name && is_numeric($width) && is_numeric($height))\n	{\n		$eblistmb = ($eblistmb == \"on\") ? 1 : 0;\n\n		$rs = @safe_insert(\'ebl_crop\', \"\n			`name`	 = \'$name\',\n			`width`	 = \'$width\',\n			`height` = \'$height\',\n			`thumb`  = \'$eblistmb\'\");\n\n		return ($rs) ?\n			\"New crop style created\" :\n			\"<b>Error:</b> Duplicate Name\";\n\n	} else {\n\n		return \"<b>Width</b> and <b>Height</b> must be numeric values\";\n	}\n\n	return FALSE;\n}\n\nfunction ebl_cropdelete()\n{\n	$name = ps(\'stylename\');\n	return (safe_delete(\'ebl_crop\', \"`name` = \'\".doSlash($name).\"\'\")) ?\n		\"<b>Deleted</b> $name\" :\n		\"<b>Error</b> Unable to delete $name\";\n}\n\n\nif(gps(\'step\') == \'image_edit\' || gps(\'step\') == \'thumbnail_insert\' || gps(\'step\') == \'image_replace\' || gps(\'step\') == \'thumbnail_create\' || gps(\'step\') == \'thumbnail_delete\') {\n	register_callback(\'ebl_imgcrop\', \'admin_side\', \'head_end\');\n}\n\nfunction ebl_imgcrop() {\n\n	global $path_to_site,$img_dir,$prefs;\n\n	$id=gps(\'id\');\n	$imagedir = hu . $img_dir . \'/\';\n\n	$rs = @safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename;\n		$tmbsrc = $imagedir.$id.\'t\'.$ext;\n		$backup 	= $path_to_site . \'/\' . $img_dir . \'/backup/\'.$filename;\n	}\n\n	$restore=(!file_exists($backup))?\'$(\"#eblrestore\").hide();\':\'\';\n\n\n	$customThumbSize = selectStyles(1);\n	$customImgSize = selectStyles(0);\n\n	$eblwmprefs=get_pref(\'eblwatermark\');\n	$eblwmval=(!empty($eblwmprefs))?$eblwmval=unserialize($eblwmprefs):array(\'wm\'=>\'watermark\',\'top\'=>\'100\',\'left\'=>\'100\');\n\n	echo <<< EOF\n<script src=\"jcrop/jquery.Jcrop.pack.js\"></script>\n<link href=\"jcrop/jquery.Jcrop.css\" rel=\"stylesheet\" type=\"text/css\" />\n<style type=\"text/css\">\n#eblimgprocess {\n	margin: 5px;\n	font-weight: bold;\n	font-size: 14pt;\n	color: #550000;\n	padding: 0 0 0 20px;\n	background: url(\'jcrop/processing.gif\') left no-repeat;\n}\na { padding: 0 0 3px;}\na:visited {} a:hover {} a:active { border: 0; }\na:focus { outline: 0; text-decoration: none;}\n.jcrop-holder{margin:20px auto 40px}\n</style>\n<script type=\"text/javascript\">\n$(document).ready(function () { // init everything\n\n    $(\"#image_container .fullsize-image img\").attr(\'id\', \'mainImg\'); // Give our image an ID\n\n    $(\'.thumbnail-edit img\').attr(\'id\', \'thumb\'); // Give our thumbnail image an ID\n    $(\'.thumbnail-edit\').attr(\'id\', \'thumbTD\'); // name the TD that contains the image so we can swap it out.\n\n    if (!$(\'#thumbTD\').length) {\n        $(\".thumbnail-upload\").attr(\'id\', \'thumbTD\');\n    }\n\n    //prepare Button Container\n    $(\'#image_container\').prepend(\'<section id=\"eblcropui\" class=\"txp-edit\"></section>\');\n\n    $(\'#eblcropui\').append(\'<section role=\"region\" id=\"eblcontainer_group\" class=\"txp-details\" aria-labelledby=\"eblcontainer_group-label\"><h3 id=\"eblcontainer_group-label\">Edit Image</h3><div role=\"group\" id=\"eblcontainer\"></div></section>\');\n\n    //insert Button\n    $(\'#eblcontainer\').append(\n        \'<p class=\"nav-tertiary\">\' +\n        \'<a href=\"#\" id=\"eblcroplnk\" class=\"navlink\">Crop</a>\' +\n        \'<a href=\"#\" id=\"ebltmblnk\" class=\"navlink\">Thumbnail</a>\' +\n        \'<a href=\"#\" id=\"eblrotatelnk\" class=\"navlink\">Rotate</a>\' +\n        \'<a href=\"#\" id=\"eblresizelnk\" class=\"navlink\">Resize</a>\' +\n        \'<a href=\"#\" id=\"eblwmlnk\" class=\"navlink\">Watermark</a>\' +\n        \'<a href=\"#\" id=\"eblbackuplnk\" class=\"navlink\">Backup</a>\' +\n        \'</p>\'\n    );\n\n    //insert Div for function\n    $(\'#eblcontainer\').append(\'<div id=\"eblwmctrl\"></div><div id=\"eblcropdata\"></div><div id=\"eblcropctrl\"></div><div id=\"ebltmbctrl\"></div><div id=\"eblrotatectrl\"></div><div id=\"eblresize\"></div><div id=\"eblbackupimg\"></div><div id=\"eblhiddendata\"></div><div id=\"eblimgprocess\"> Processing...</div>\').children(\'div\').hide();;\n\n\n\n    //------------------------------------------\n    // CROP Control\n    $(\'#eblcropctrl\').append(\n        \'<input type=\"hidden\" id=\"eblcropHh\" name=\"eblcropH\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropWh\" name=\"eblcropW\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropXh\" name=\"eblcropXh\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropYh\" name=\"eblcropYh\" value=\"\" />\'\n    );\n    $(\'#eblcropctrl\').append(\n        \'<p>Use predefined size: $customImgSize | <input name=\"aspectratio\" type=\"checkbox\" id=\"imgaspectratio\" value=\"\" /> Lock Aspect Ratio </p>\' +\n        \'<p><input type=\"submit\" name=\"$id\" id=\"eblimgcrop\" value=\"Crop Original\" class=\"smallerbox\" /></p>\'\n    );\n    $(\'#eblcropdata\').append(\n        \'<p>Current Crop Area (W: <span style=\"font-weight: bold;\" id=\"eblcropW\">0</span>) x (H: <span style=\"font-weight: bold;\" id=\"eblcropH\">0</span>)</p>\'\n    );\n\n    // set our defaults\n    window.crop = false;\n    window.tmbcrop = false;\n\n    $(\'#mainImg\').wrap(\'<div id=\"imgDiv\"></div\');\n    $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n\n\n\n    function removeCrop() {\n        // Removes jCrop if found.\n        if ($(\'.jcrop-holder\').length) {\n            var x = $(\'#imgDiv\');\n\n            // generate random number to append to img url to avoid cache issues\n            var rand = Math.random();\n\n            // hide Width/Height data\n            $(\'#eblcropdata\').hide();\n\n            // replace removed content with just the image.\n            x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n            window.myCrop = \'\';\n        }\n\n        window.cropOn = false;\n        window.crop = false;\n        window.tmbcrop = false;\n    }\n\n    function addCrop() {\n        // Bind jCrop to #mainImg\n        var aspectratio = $(\'#aspectratio\').attr(\'checked\') ? 1 : 0; // Thanks Manfre\n\n        // Display H X W\n        $(\'#eblcropdata\').show();\n\n        // check if the jcrop holder is already active. Load if not active.\n        if (!$(\'.jcrop-holder\').length) {\n            window.myCrop = $.Jcrop(\'#mainImg\', {\n                onSelect: showCoords,\n                onChange: showCoords,\n                onDblClick: function () {\n                    if ($(\'#eblcropctrl\').is(\':visible\')) {\n                        $(\'#eblimgcrop\').click();\n                    } else if ($(\'#ebltmbctrl\').is(\':visible\')) {\n                        $(\'#ebltmbcrop\').click();\n                    }\n                },\n                aspectRatio: aspectratio\n            });\n            myCrop.setSelect([0, 0, 100, 150]);\n        }\n        window.cropOn = true;\n    }\n\n\n    $(\'#eblcroplnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblcropctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblcropctrl\').hide();\n\n\n        if (window.crop && window.cropOn) { // user closed main crop function\n            window.crop = false;\n            removeCrop(); // remove the crop binding\n        } else if (!window.crop && window.cropOn) { // user switched from thumb crop\n            window.tmbcrop = false;\n            window.crop = true;\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            $(\'#customsize0\').val(\'Custom\');\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n            myCrop.animateTo([0, 0, 100, 150]);\n        } else { // user opened main crop function\n            addCrop(); // add crop binding\n            window.crop = true;\n        }\n    });\n\n    $(\'#eblimgcrop\').click(function () {\n        var id = $(\'#eblimgcrop\').attr(\'name\');\n        var H = $(\'#eblcropHh\').attr(\'value\');\n        var W = $(\'#eblcropWh\').attr(\'value\');\n        var X = $(\'#eblcropXh\').attr(\'value\');\n        var Y = $(\'#eblcropYh\').attr(\'value\');\n        $(\'#eblimgprocess\').show();\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblimgcrop&imgid=\" + id,\n            data: \"&eblcropXh=\" + X + \"&eblcropYh=\" + Y + \"&eblcropW=\" + W + \"&eblcropH=\" + H,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    removeCrop();\n                    $(\'#eblcropctrl\').toggle();\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n    $(\'#customsize0,#customsize1\').change(function () {\n        var size = $(this).val();\n        if (!size.match(\"Custom\")) {\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', false).attr(\'checked\', true);\n            var x = size.split(\'x\')[0];\n            var y = size.split(\'x\')[1];\n\n            myCrop.animateTo([0, 0, x, y]);\n\n            var aspectratio = x / y;\n\n            var opt = {\n                aspectRatio: aspectratio\n            }\n\n            myCrop.setOptions(opt);\n        } else {\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n        }\n\n    });\n\n    $(\'#imgaspectratio,#tmbaspectratio\').change(function () {\n        if ($(this).attr(\'checked\')) {\n            if (this.id === \'imgaspectratio\') {\n                var size = $(\'#customsize0\').val();\n            } else {\n                var size = $(\'#customsize1\').val();\n            }\n\n            var x = size.split(\'x\')[0];\n            var y = size.split(\'x\')[1];\n\n            var aspectratio = x / y;\n            myCrop.animateTo([0, 0, x, y]);\n\n        } else {\n            var aspectratio = 0;\n\n        }\n\n        var opt = {\n            aspectRatio: aspectratio\n        }\n        myCrop.setOptions(opt);\n    });\n\n\n\n    //------------------------------------------\n    // THUMB Control\n    $(\'#ebltmbctrl\').append(\n        \'</p><p>Use predefined size: $customThumbSize | \' +\n        \'<input name=\"aspectratio\" type=\"checkbox\" id=\"tmbaspectratio\" value=\"\" /> Lock Aspect Ratio </p></p><p> Thumbnail Dimensions: \' +\n        \'( W: <input name=\"ebltmbW\" type=\"text\" id=\"ebltmbW\" size=\"5\" maxlength=\"4\" value=\"100\" /> ) x \' +\n        \'( H: <input name=\"ebltmbH\" type=\"text\" id=\"ebltmbH\" size=\"5\" maxlength=\"4\" value=\"100\" /> )\' +\n        \'| <input name=\"cropthumb\" type=\"checkbox\" id=\"cropthumb\" checked=\"true\"/> Resize Thumbnail \' +\n        \'</p><p>\' +\n        \'<input type=\"button\" name=\"$id\" id=\"ebltmbcrop\" value=\"Create Thumbnail\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#ebltmblnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#ebltmbctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#ebltmbctrl\').hide();\n\n        if (window.tmbcrop && window.cropOn) { // user closed thumb crop\n            window.tmbcrop = false;\n            removeCrop();\n        } else if (!window.tmbcrop && window.cropOn) { // user switched from main crop\n            window.tmbcrop = true;\n            window.crop = false;\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            $(\'#customsize1\').val(\'Custom\');\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n            myCrop.animateTo([0, 0, 100, 150]);\n        } else { // user opens thumb crop\n            addCrop();\n            window.tmbcrop = true;\n        }\n\n    });\n\n    $(\'#ebltmbcrop\').click(function () {\n        var id = $(\'#eblimgcrop\').attr(\'name\');\n        var H = $(\'#eblcropHh\').attr(\'value\');\n        var W = $(\'#eblcropWh\').attr(\'value\');\n        var X = $(\'#eblcropXh\').attr(\'value\');\n        var Y = $(\'#eblcropYh\').attr(\'value\');\n\n        var tH = $(\'#ebltmbH\').attr(\'value\');\n        var tW = $(\'#ebltmbW\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n        var rszTmb = $(\'#cropthumb\').attr(\'checked\') ? \'rsz\' : \'no\';\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=ebltmbcrop&imgid=\" + id,\n            data: \"&rszTmb=\" + rszTmb + \"&eblcropXh=\" + X + \"&eblcropYh=\" + Y + \"&eblcropW=\" + W + \"&eblcropH=\" + H + \"&ebltmbH=\" + tH + \"&ebltmbW=\" + tW,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    removeCrop();\n                    $(\'#thumbTD #thumb\').remove();\n                    $(\'#thumbTD\').prepend(\'<img src=\"$tmbsrc?\' + rand + \'\" id=\"thumb\" class=\"content-image\" />\');\n                    $(\'#ebltmbctrl\').toggle();\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#cropthumb\').change(function () {\n        var cropthumb = $(this).attr(\'checked\') ? false : true;\n\n        if (cropthumb) {\n            $(\'#ebltmbW, #ebltmbH\').attr({\n                disabled: \"disabled\",\n                value: \"###\"\n            });\n        } else {\n            $(\'#ebltmbW, #ebltmbH\').removeAttr(\"disabled\");\n            $(\'#ebltmbW, #ebltmbH\').attr(\"value\", \"100\");\n        }\n    });\n\n\n\n    //------------------------------------------\n    // Rotate Control\n    $(\'#eblrotatectrl\').append(\n        \'<p>Rotate <input type=\"button\" name=\"$id\" id=\"rotateright\" value=\"Clockwise 90&deg;\" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"rotateleft\" value=\"Counterclockwise 90&deg; \" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"rotateup\" value=\"180 &deg; \" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#eblrotatelnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblrotatectrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblrotatectrl\').hide();\n\n        removeCrop();\n        window.myCrop = \' \';\n        window.crop = false;\n    });\n\n    $(\'#rotateleft\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateleft&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#rotateright\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateright&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#rotateup\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateup&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // BACKUP Control\n    $(\'#eblbackupimg\').append(\n        \'<p><input type=\"button\" name=\"$id\" id=\"eblbackup\" value=\"Backup\" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"eblrestore\" value=\"Restore\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n    $restore\n\n    $(\'#eblbackuplnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblbackupimg\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblbackupimg\').hide();\n\n        removeCrop();\n        window.myCrop = \' \';\n        window.crop = false;\n    });\n\n    $(\'#eblbackup\').click(function () {\n        var id = $(this).attr(\'name\');\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblbackupimg&imgid=\" + id,\n            success: function (html) {\n                $(\'#eblrestore\').show();\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"image backed up\");\n                } else {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"Error : \" + id + \"  |   \" + html);\n                }\n            }\n        });\n    });\n\n    $(\'#eblrestore\').click(function () {\n        var id = $(this).attr(\'name\');\n        $(\'#eblimgprocess\').show();\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrestoreimg&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    alert(\"image restored\");\n                } else {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"Error : \" + html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // RESIZE Control\n    $(\'#eblresize\').append(\n        \'<p>Current Image Size : W[ <span id=\"origW\"></span> ] x H[ <span id=\"origH\"></span> ]</p>\' +\n        \'<p><label for=\"eblrszW\">Width: </label><input type=\"text\" name=\"eblrszW\" id=\"eblrszW\" /> x \' +\n        \'<label for=\"eblrszH\">Height: </label><input type=\"text\" name=\"eblrszH\" id=\"eblrszH\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"eblrszimg\" value=\"Resize\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#eblresizelnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblresize\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblresize\').hide();\n\n        var imgW = $(\'#mainImg\').width();\n        var imgH = $(\'#mainImg\').height();\n\n        $(\'#origW\').text(imgW);\n        $(\'#origH\').text(imgH);\n\n        removeCrop();\n    });\n\n    $(\'#eblrszimg\').click(function () {\n        var id = $(\'#eblrszimg\').attr(\'name\');\n        var H = $(\'#eblrszH\').attr(\'value\');\n        var W = $(\'#eblrszW\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrszimg&imgid=\" + id,\n            data: \"&eblrszH=\" + H + \"&eblrszW=\" + W,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    //\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // WATERMARK Control\n    $(\'#eblwmctrl\').append(\n        \'<p><label for=\"eblmwimg\">Immagine: $imagedir</label><input type=\"text\" name=\"eblwmimg\" id=\"eblwmimg\" value=\"$eblwmval[wm]\"/></p>\' +\n        \'<p><label for=\"eblwmT\">Top: </label><input type=\"text\" name=\"eblwmT\" id=\"eblwmT\" value=\"$eblwmval[top]\"/> px \' +\n        \'<label for=\"eblwmL\">Left: </label><input type=\"text\" name=\"eblwmL\" id=\"eblwmL\" value=\"$eblwmval[left]\" /> px </p>\' +\n        \'<p><input type=\"button\" name=\"$id\" id=\"eblwatermark\" value=\"Watermark\" class=\"smallerbox\" /></p>\'\n    );\n\n\n    $(\'#eblwmlnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblwmctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblwmctrl\').hide();\n        removeCrop();\n    });\n\n    $(\'#eblwatermark\').click(function () {\n        var id = $(this).attr(\'name\');\n        var wm = $(\'#eblwmimg\').attr(\'value\');\n        var T = $(\'#eblwmT\').attr(\'value\');\n        var L = $(\'#eblwmL\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblwmcreate&imgid=\" + id,\n            data: \"&eblwmT=\" + T + \"&eblwmL=\" + L + \"&eblwmimg=\" + wm,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    //\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n});\n\nfunction showCoords(c) {\n    $(\'#eblcropX\').text(c.x);\n    $(\'#eblcropXh\').val(c.x);\n    $(\'#eblcropY\').text(c.y);\n    $(\'#eblcropYh\').val(c.y);\n    $(\'#eblcropX2\').text(c.x2);\n    $(\'#eblcropX2h\').val(c.x2);\n    $(\'#eblcropY2\').text(c.y2);\n    $(\'#eblcropY2h\').val(c.y2);\n\n    var imgW = zeroPad(c.w, 4);\n    var imgH = zeroPad(c.h, 4);\n\n    $(\'#eblcropW\').text(imgW);\n    $(\'#eblcropWh\').val(c.w);\n    $(\'#eblcropH\').text(imgH);\n    $(\'#eblcropHh\').val(c.h);\n};\n\nfunction zeroPad(num, count) {\n    var numZeropad = num + \'\';\n    while (numZeropad.length < count) {\n        numZeropad = \"0\" + numZeropad;\n    }\n    return numZeropad;\n}\n\n</script>\nEOF;\n\n}\n\nfunction eblrestoreimg($id)\n{\n\n	global $path_to_site,$img_dir;\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		$imagedir	= $path_to_site . \'/\' . $img_dir . \'/\';\n\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename;\n\n		if(copy($backupdir.$filename, $imagedir.$filename))\n		{\n			list($width, $height) = getimagesize($backupdir.$filename);\n			if(safe_update(\'txp_image\', \"w = \'\".$width.\"\', h = \'\".$height.\"\'\", \"id = $id\"))\n			{\n				echo \"success\"; exit();\n			} else {\n				echo \"File Copied but db not updated\";\n			}\n		}\n	} else {\n		echo \"Database Error\";\n	}\n\n	exit();\n}\n\nfunction eblbackupimg($id)\n{\n	global $path_to_site,$img_dir;\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		$imagedir	= $path_to_site . \'/\' . $img_dir . \'/\';\n\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename; echo $imagedir.$filename;\n\n		$cr = copy($imagedir.$filename,$backupdir.$filename);\n		echo \"success\";\n	} else {\n		echo \"Database Error\";\n	}\n\n	exit();\n}\n\nfunction ebl_img_edit ($id, $direction, $action)\n{\n	global $path_to_site,$img_dir;\n\n	$imagedir = $path_to_site . \'/\' . $img_dir . \'/\';\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		extract($rs);\n		$filename = $id.$ext;\n\n		switch (strtolower($ext)) {\n			case \'.jpg\':\n				$srcimage = imagecreatefromjpeg($imagedir . $filename);\n				break;\n			case \'.gif\':\n				$srcimage = imagecreatefromgif($imagedir . $filename);\n				break;\n			case \'.png\':\n				$srcimage = imagecreatefrompng($imagedir . $filename);\n				break;\n		}\n\n		if($action==\'watermark\'){ //top and left was based on full image.\n			$eblwm[\'top\']=gps(\'eblwmT\');\n			$eblwm[\'left\']=gps(\'eblwmL\');\n			$eblwm[\'wm\']=gps(\'eblwmimg\');\n\n			set_pref(\'eblwatermark\', serialize($eblwm), \'admin\',2);\n			if (file_exists($imagedir.$eblwm[\'wm\'])) {	//first step WM must be a PNG!\n				$wmimg=@imagecreatefrompng($imagedir.$eblwm[\'wm\']);\n				$wmimg_w = imagesx($wmimg);\n				$wmimg_h = imagesy($wmimg);\n				imagecopy($srcimage, $wmimg,$eblwm[\'left\'],$eblwm[\'top\'], 0, 0, $wmimg_w, $wmimg_h);\n				$newimg=$srcimage;\n			}\n		}\n\n		if($action == \'rotate\') { // rotate image\n\n			/** Ternary equivalent of  if / elseif / else : Done for simple shorthand. Assumes anything not right / left = 180 **/\n			$degrees = ($direction == \'right\') ? -90 : (($direction == \'left\') ? 90 : 180);\n			$newimg  = imagerotate($srcimage, $degrees, 0);\n		} elseif ($action == \'crop\') { // primary image cropping\n\n			//gather crop variables from hidden fields.\n			$cropX = gps(\'eblcropXh\');\n			$cropY = gps(\'eblcropYh\');\n			$targW = $cropW = gps(\'eblcropW\');\n			$targH = $cropH = gps(\'eblcropH\');\n\n			// prevents division by zero & invalid crop widths.\n			$value = (int)$targH + (int)$targW;\n			if($value < 30) {\n				echo \"invalid crop values detected\";\n				imagedestroy($srcimage);\n				exit();\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0, $cropX, $cropY, $targW, $targH, $cropW, $cropH);\n\n		} elseif ($action == \'thumbnail\') { // create thumbnail\n\n			//gather crop variables from hidden fields.\n			$cropX = gps(\'eblcropXh\');\n			$cropY = gps(\'eblcropYh\');\n			$cropH = gps(\'eblcropH\');\n			$cropW = gps(\'eblcropW\');\n\n			// prevents division by zero & invalid crop widths.\n			$value = (int)$cropH + (int)$cropW;\n			if($value < 4) {\n				echo \"invalid crop values detected\";\n				imagedestroy($srcimage);\n				exit();\n			}\n\n			if(gps(\'rszTmb\') == \"rsz\") { // are we resizing the the thumbnail?\n				$targH = gps(\'ebltmbH\');\n				$targW = gps(\'ebltmbW\');\n\n			// did someone forget to input a height or width? If so, default to crop width\n			if($targH < 1) { $targH = $cropH; }\n			if($targW < 1) { $targW = $cropW; }\n\n				// determine the correct scale value\n				$scale = min($targW / $cropW,$targH / $cropH);\n				// are the sides equal? If not, scale to fit.\n				if ($scale < 1) {\n					$targW = ceil($scale * $cropW);\n					$targH = ceil($scale * $cropH);\n				}\n			} elseif (gps(\'rszTmb\') != \'rsz\') { // if not, we\'ll just use the crop-area size to create our thumbnail\n				$targH = $cropH;\n				$targW = $cropW;\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0, $cropX, $cropY, $targW, $targH, $cropW, $cropH);\n\n		} elseif ($action == \'rsz\') { // general resize\n			$targH = gps(\'eblrszH\');\n			$targW = gps(\'eblrszW\');\n\n			if(($targH + $targW) < 1) {\n				echo \" ERROR : Dimensions must be entered.\";\n				return false;\n			}\n			// did someone forget to input a height or width? If so, we\'ll default to the original size for the missing value.\n			if($targH < 1) { $targH = $h; }\n			if($targW < 1) { $targW = $w; }\n			// checks to see if the targeted values are larger than the original, if so, reset to original value as the longest side.\n			if($targH > $h) { $targH = $h; }\n			if($targW > $w) { $targW = $w; }\n\n			$scale = min($targW / $w, $targH / $h);\n			// are the sides equal? If not, scale to fit.\n			if ($scale < 1) {\n				$targW = ceil($scale * $w);\n				$targH = ceil($scale * $h);\n			} else { // image is smaller than requested scale. Abort.\n				imagedestroy($srcimage);\n				echo \"Image smaller than parameters entered\";\n				return;\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0,0,0, $targW, $targH, $w, $h);\n		}\n\n		$t = ($action == \'thumbnail\') ? \'t\' : \'\';\n\n		$filename = $id.$t.$ext;\n\n		switch (strtolower($ext)) {\n			case ($ext == \'.jpg\' || $ext == \'.jpeg\'):\n				$fileresult = imagejpeg($newimg,$imagedir . $filename,\'100\');\n				break;\n			case \".gif\":\n				$fileresult = imagegif($newimg,$imagedir . $filename);\n				break;\n			case \".png\":\n				$fileresult = imagepng($newimg,$imagedir . $filename);\n				break;\n		}\n\n		imagedestroy($srcimage);\n	}\n\n	if($action != \'thumbnail\')\n	{\n		list ($width,$height) = getimagesize($imagedir . $filename);\n		$rs = safe_update(\'txp_image\', \"h = \'$height\', w = \'$width\'\", \"id = $id\");\n	} else {\n		list ($width,$height) = getimagesize($imagedir . $filename);\n		$rs = safe_update(\'txp_image\', \"thumb_h = \'$height\', thumb_w = \'$width\', thumbnail = \'1\'\", \"id = $id\");\n	}\n\n	echo ($fileresult && $rs) ? \"success\" : \'ERROR \';\n}\n', 'if (@txpinterface == \'admin\')\n	{\n		//add_privs(\'eblimgeditext\', \'1\');\n		//register_tab(\"extensions\", \"eblimgeditext\", \"Image Edit\");\n		//register_callback(\"ebl_image_edit_ext\", \"eblimgeditext\");\n		add_privs(\'plugin_prefs.ebl_image_edit\', \'1\');\n		register_callback(\"ebl_image_edit_ext\", \"plugin_prefs.ebl_image_edit\");\n\n	switch (gps(\'event\'))\n	{\n		case \'eblrotateleft\':\n			ebl_img_edit(gps(\'imgid\'), \'left\', \'rotate\'); exit();\n			break;\n		case \'eblrotateright\':\n			ebl_img_edit(gps(\'imgid\'), \'right\', \'rotate\'); exit();\n			break;\n		case \'eblrotateup\':\n			ebl_img_edit(gps(\'imgid\'), \'up\', \'rotate\'); exit();\n			break;\n		case \'eblimgcrop\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'crop\'); exit();\n			break;\n		case \'eblimgcrop\':\n			ebl_processUpload(gps(\'uploadType\'));\n			break;\n		case \'ebltmbcrop\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'thumbnail\'); exit();\n			break;\n		case \'eblrszimg\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'rsz\'); exit();\n			break;\n		case \'eblbackupimg\':\n			eblbackupimg(gps(\'imgid\'));\n			break;\n		case \'eblrestoreimg\':\n			eblrestoreimg(gps(\'imgid\'));\n			break;\n		case \'eblwmcreate\':\n			ebl_img_edit(gps(\'imgid\'), \'\', \'watermark\'); exit();\n			break;\n	}\n}\n\nfunction ebl_image_edit_ext()\n{\n\n	$step = gps(\'step\');\n\n\n	$message = (is_callable($step)) ? $step() : \'\';\n\n	echo pagetop(\"EBL Image Edit Preferences\", $message);\n\n	echo 	\'<div style=\"margin: 0 auto; width: 400px;\">\'.n.\n			ebl_list_cropStyles().\n			\'</div>\';\n}\n\nfunction ebl_list_cropStyles()\n{\n	global $txp_user, $path_to_site, $img_dir;\n	$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		if(!file_exists($backupdir)) {\n\n		$createEBLtable = safe_query(\"CREATE TABLE `\".PFX.\"ebl_crop` (\".\n									\"`name` varchar(64) NOT NULL,\".\n									\"`width` varchar(16) NOT NULL,\".\n									\"`height` varchar(16) NOT NULL,\".\n									\"`thumb` varchar(1) NOT NULL default \'0\',\".\n									\"UNIQUE KEY `name` (`name`)\".\n									\");\");\n\n		$rs = safe_insert(\'ebl_crop\', \"`name` = \'Image Crop\', `width` = \'500\',`height` = \'500\', `thumb` = \'0\'\");\n		$rs = safe_insert(\'ebl_crop\', \"`name` = \'Thumbnail Square\', `width` = \'100\',`height` = \'100\', `thumb` = \'1\'\");\n\n			$mkdir = (@mkdir($backupdir)) ? TRUE : FALSE;\n			if($mkdir) {\n				echo \"<p  style=\\\"text-align: center; \\\">$backupdir did not exist previously. Now created.</p>\";\n			} else {\n				echo \"<p style=\\\"text-align: center; font-weight: bold; \\\">Unable to create $backupdir. Please check your folder permissions.</p>\";\n			}\n		}\n\n\n\n	echo n.n.hed(gTxt(\'Crop Styles\'), 1, \' style=\"text-align: center; margin-top:2em; font-weight: bold;\"\').\n		n.n.startTable(\'list\',\'\',\'txp-list\').\n		n.\'<thead>\'.tr(\n			n.hCell(gTxt(\'name\')).\n			n.hCell(gTxt(\'width\')).\n			n.hCell().\n			n.hCell(gTxt(\'height\')).\n			n.hCell(gTxt(\'thumbnail\')).\n			n.hCell(gTxt(\'delete\'))\n		).\'</thead>\';\n\n	$rs = safe_rows_start(\'*\', \'ebl_crop\', \'1=1 ORDER BY `name`\');\n\n	$out = \'\';\n\n	if ($rs)\n	{\n		while ($a = nextRow($rs))\n		{\n			$isChecked = ($a[\'thumb\'] == 1) ? \'yes\' : \'no\';\n			$out .= n.tr(\n					td( htmlspecialchars($a[\'name\'])).\n					td( htmlspecialchars($a[\'width\'])).\n					td( htmlspecialchars(\' X \')).\n					td( htmlspecialchars($a[\'height\'])).\n					td( $isChecked).\n					td(\n						dLink(\'plugin_prefs.ebl_image_edit\', \'ebl_cropdelete\', \'stylename\', $a[\'name\'])\n					)\n				);\n		}\n	}\n\n	echo form($out);\n\n	echo n.tr(\n		form(\n			td( fInput(\'text\', \'name\', \'\', \'edit\',\'\',\'\',10) ).\n			td( fInput(\'text\', \'width\', \'\', \'edit\',\'\',\'\',5) ).\n			td().\n			td( fInput(\'text\', \'height\', \'\', \'edit\',\'\',\'\',5) ).\n			td( \'<input name=\"eblistmb\" class=\"checkbox\" type=\"checkbox\">\').\n			td( fInput(\'submit\', \'add\', gTxt(\'add\'), \'smallerbox\') ).\n			n.eInput(\'plugin_prefs.ebl_image_edit\').\n			n.sInput(\'ebl_cropsavenew\')\n		)\n	);\n\n	echo n.endTable();\n}\n\nfunction selectStyles($thumb = \'0\') {\n\n	$rs = safe_rows_start(\'*\', \'ebl_crop\', \'thumb=\'.$thumb.\' ORDER BY `name`\');\n\n	$out = \"<select name=\\\"customsize\\\" id=\\\"customsize$thumb\\\">\' + \".n.\n		   \"\'<option value=\\\"Custom\\\">Custom</option>\' + \".n;\n\n	if ($rs)\n	{\n		while ($a = nextRow($rs))\n		{\n			$width	= $a[\'width\'];\n			$height	= $a[\'height\'];\n			$name 	= $a[\'name\'];\n\n			$out .= \"\'<option value=\".$width.\"x\".$height.\"\\>\".htmlspecialchars($name).\" : $width x $height </option>\' + \".n;\n		}\n	}\n	$out .= \"\'</select>\";\n\n	return $out;\n}\nfunction ebl_cropsavenew()\n{\n\n	extract(doSlash(psa(array(\'name\', \'width\',\'height\',\'eblistmb\'))));\n\n	if ($name && is_numeric($width) && is_numeric($height))\n	{\n		$eblistmb = ($eblistmb == \"on\") ? 1 : 0;\n\n		$rs = @safe_insert(\'ebl_crop\', \"\n			`name`	 = \'$name\',\n			`width`	 = \'$width\',\n			`height` = \'$height\',\n			`thumb`  = \'$eblistmb\'\");\n\n		return ($rs) ?\n			\"New crop style created\" :\n			\"<b>Error:</b> Duplicate Name\";\n\n	} else {\n\n		return \"<b>Width</b> and <b>Height</b> must be numeric values\";\n	}\n\n	return FALSE;\n}\n\nfunction ebl_cropdelete()\n{\n	$name = ps(\'stylename\');\n	return (safe_delete(\'ebl_crop\', \"`name` = \'\".doSlash($name).\"\'\")) ?\n		\"<b>Deleted</b> $name\" :\n		\"<b>Error</b> Unable to delete $name\";\n}\n\n\nif(gps(\'step\') == \'image_edit\' || gps(\'step\') == \'thumbnail_insert\' || gps(\'step\') == \'image_replace\' || gps(\'step\') == \'thumbnail_create\' || gps(\'step\') == \'thumbnail_delete\') {\n	register_callback(\'ebl_imgcrop\', \'admin_side\', \'head_end\');\n}\n\nfunction ebl_imgcrop() {\n\n	global $path_to_site,$img_dir,$prefs;\n\n	$id=gps(\'id\');\n	$imagedir = hu . $img_dir . \'/\';\n\n	$rs = @safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename;\n		$tmbsrc = $imagedir.$id.\'t\'.$ext;\n		$backup 	= $path_to_site . \'/\' . $img_dir . \'/backup/\'.$filename;\n	}\n\n	$restore=(!file_exists($backup))?\'$(\"#eblrestore\").hide();\':\'\';\n\n\n	$customThumbSize = selectStyles(1);\n	$customImgSize = selectStyles(0);\n\n	$eblwmprefs=get_pref(\'eblwatermark\');\n	$eblwmval=(!empty($eblwmprefs))?$eblwmval=unserialize($eblwmprefs):array(\'wm\'=>\'watermark\',\'top\'=>\'100\',\'left\'=>\'100\');\n\n	echo <<< EOF\n<script src=\"jcrop/jquery.Jcrop.pack.js\"></script>\n<link href=\"jcrop/jquery.Jcrop.css\" rel=\"stylesheet\" type=\"text/css\" />\n<style type=\"text/css\">\n#eblimgprocess {\n	margin: 5px;\n	font-weight: bold;\n	font-size: 14pt;\n	color: #550000;\n	padding: 0 0 0 20px;\n	background: url(\'jcrop/processing.gif\') left no-repeat;\n}\na { padding: 0 0 3px;}\na:visited {} a:hover {} a:active { border: 0; }\na:focus { outline: 0; text-decoration: none;}\n.jcrop-holder{margin:20px auto 40px}\n</style>\n<script type=\"text/javascript\">\n$(document).ready(function () { // init everything\n\n    $(\"#image_container .fullsize-image img\").attr(\'id\', \'mainImg\'); // Give our image an ID\n\n    $(\'.thumbnail-edit img\').attr(\'id\', \'thumb\'); // Give our thumbnail image an ID\n    $(\'.thumbnail-edit\').attr(\'id\', \'thumbTD\'); // name the TD that contains the image so we can swap it out.\n\n    if (!$(\'#thumbTD\').length) {\n        $(\".thumbnail-upload\").attr(\'id\', \'thumbTD\');\n    }\n\n    //prepare Button Container\n    $(\'#image_container\').prepend(\'<section id=\"eblcropui\" class=\"txp-edit\"></section>\');\n\n    $(\'#eblcropui\').append(\'<section role=\"region\" id=\"eblcontainer_group\" class=\"txp-details\" aria-labelledby=\"eblcontainer_group-label\"><h3 id=\"eblcontainer_group-label\">Edit Image</h3><div role=\"group\" id=\"eblcontainer\"></div></section>\');\n\n    //insert Button\n    $(\'#eblcontainer\').append(\n        \'<p class=\"nav-tertiary\">\' +\n        \'<a href=\"#\" id=\"eblcroplnk\" class=\"navlink\">Crop</a>\' +\n        \'<a href=\"#\" id=\"ebltmblnk\" class=\"navlink\">Thumbnail</a>\' +\n        \'<a href=\"#\" id=\"eblrotatelnk\" class=\"navlink\">Rotate</a>\' +\n        \'<a href=\"#\" id=\"eblresizelnk\" class=\"navlink\">Resize</a>\' +\n        \'<a href=\"#\" id=\"eblwmlnk\" class=\"navlink\">Watermark</a>\' +\n        \'<a href=\"#\" id=\"eblbackuplnk\" class=\"navlink\">Backup</a>\' +\n        \'</p>\'\n    );\n\n    //insert Div for function\n    $(\'#eblcontainer\').append(\'<div id=\"eblwmctrl\"></div><div id=\"eblcropdata\"></div><div id=\"eblcropctrl\"></div><div id=\"ebltmbctrl\"></div><div id=\"eblrotatectrl\"></div><div id=\"eblresize\"></div><div id=\"eblbackupimg\"></div><div id=\"eblhiddendata\"></div><div id=\"eblimgprocess\"> Processing...</div>\').children(\'div\').hide();;\n\n\n\n    //------------------------------------------\n    // CROP Control\n    $(\'#eblcropctrl\').append(\n        \'<input type=\"hidden\" id=\"eblcropHh\" name=\"eblcropH\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropWh\" name=\"eblcropW\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropXh\" name=\"eblcropXh\" value=\"\" />\' +\n        \'<input type=\"hidden\" id=\"eblcropYh\" name=\"eblcropYh\" value=\"\" />\'\n    );\n    $(\'#eblcropctrl\').append(\n        \'<p>Use predefined size: $customImgSize | <input name=\"aspectratio\" type=\"checkbox\" id=\"imgaspectratio\" value=\"\" /> Lock Aspect Ratio </p>\' +\n        \'<p><input type=\"submit\" name=\"$id\" id=\"eblimgcrop\" value=\"Crop Original\" class=\"smallerbox\" /></p>\'\n    );\n    $(\'#eblcropdata\').append(\n        \'<p>Current Crop Area (W: <span style=\"font-weight: bold;\" id=\"eblcropW\">0</span>) x (H: <span style=\"font-weight: bold;\" id=\"eblcropH\">0</span>)</p>\'\n    );\n\n    // set our defaults\n    window.crop = false;\n    window.tmbcrop = false;\n\n    $(\'#mainImg\').wrap(\'<div id=\"imgDiv\"></div\');\n    $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n\n\n\n    function removeCrop() {\n        // Removes jCrop if found.\n        if ($(\'.jcrop-holder\').length) {\n            var x = $(\'#imgDiv\');\n\n            // generate random number to append to img url to avoid cache issues\n            var rand = Math.random();\n\n            // hide Width/Height data\n            $(\'#eblcropdata\').hide();\n\n            // replace removed content with just the image.\n            x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n            window.myCrop = \'\';\n        }\n\n        window.cropOn = false;\n        window.crop = false;\n        window.tmbcrop = false;\n    }\n\n    function addCrop() {\n        // Bind jCrop to #mainImg\n        var aspectratio = $(\'#aspectratio\').attr(\'checked\') ? 1 : 0; // Thanks Manfre\n\n        // Display H X W\n        $(\'#eblcropdata\').show();\n\n        // check if the jcrop holder is already active. Load if not active.\n        if (!$(\'.jcrop-holder\').length) {\n            window.myCrop = $.Jcrop(\'#mainImg\', {\n                onSelect: showCoords,\n                onChange: showCoords,\n                onDblClick: function () {\n                    if ($(\'#eblcropctrl\').is(\':visible\')) {\n                        $(\'#eblimgcrop\').click();\n                    } else if ($(\'#ebltmbctrl\').is(\':visible\')) {\n                        $(\'#ebltmbcrop\').click();\n                    }\n                },\n                aspectRatio: aspectratio\n            });\n            myCrop.setSelect([0, 0, 100, 150]);\n        }\n        window.cropOn = true;\n    }\n\n\n    $(\'#eblcroplnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblcropctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblcropctrl\').hide();\n\n\n        if (window.crop && window.cropOn) { // user closed main crop function\n            window.crop = false;\n            removeCrop(); // remove the crop binding\n        } else if (!window.crop && window.cropOn) { // user switched from thumb crop\n            window.tmbcrop = false;\n            window.crop = true;\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            $(\'#customsize0\').val(\'Custom\');\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n            myCrop.animateTo([0, 0, 100, 150]);\n        } else { // user opened main crop function\n            addCrop(); // add crop binding\n            window.crop = true;\n        }\n    });\n\n    $(\'#eblimgcrop\').click(function () {\n        var id = $(\'#eblimgcrop\').attr(\'name\');\n        var H = $(\'#eblcropHh\').attr(\'value\');\n        var W = $(\'#eblcropWh\').attr(\'value\');\n        var X = $(\'#eblcropXh\').attr(\'value\');\n        var Y = $(\'#eblcropYh\').attr(\'value\');\n        $(\'#eblimgprocess\').show();\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblimgcrop&imgid=\" + id,\n            data: \"&eblcropXh=\" + X + \"&eblcropYh=\" + Y + \"&eblcropW=\" + W + \"&eblcropH=\" + H,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    removeCrop();\n                    $(\'#eblcropctrl\').toggle();\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n    $(\'#customsize0,#customsize1\').change(function () {\n        var size = $(this).val();\n        if (!size.match(\"Custom\")) {\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', false).attr(\'checked\', true);\n            var x = size.split(\'x\')[0];\n            var y = size.split(\'x\')[1];\n\n            myCrop.animateTo([0, 0, x, y]);\n\n            var aspectratio = x / y;\n\n            var opt = {\n                aspectRatio: aspectratio\n            }\n\n            myCrop.setOptions(opt);\n        } else {\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n        }\n\n    });\n\n    $(\'#imgaspectratio,#tmbaspectratio\').change(function () {\n        if ($(this).attr(\'checked\')) {\n            if (this.id === \'imgaspectratio\') {\n                var size = $(\'#customsize0\').val();\n            } else {\n                var size = $(\'#customsize1\').val();\n            }\n\n            var x = size.split(\'x\')[0];\n            var y = size.split(\'x\')[1];\n\n            var aspectratio = x / y;\n            myCrop.animateTo([0, 0, x, y]);\n\n        } else {\n            var aspectratio = 0;\n\n        }\n\n        var opt = {\n            aspectRatio: aspectratio\n        }\n        myCrop.setOptions(opt);\n    });\n\n\n\n    //------------------------------------------\n    // THUMB Control\n    $(\'#ebltmbctrl\').append(\n        \'</p><p>Use predefined size: $customThumbSize | \' +\n        \'<input name=\"aspectratio\" type=\"checkbox\" id=\"tmbaspectratio\" value=\"\" /> Lock Aspect Ratio </p></p><p> Thumbnail Dimensions: \' +\n        \'( W: <input name=\"ebltmbW\" type=\"text\" id=\"ebltmbW\" size=\"5\" maxlength=\"4\" value=\"100\" /> ) x \' +\n        \'( H: <input name=\"ebltmbH\" type=\"text\" id=\"ebltmbH\" size=\"5\" maxlength=\"4\" value=\"100\" /> )\' +\n        \'| <input name=\"cropthumb\" type=\"checkbox\" id=\"cropthumb\" checked=\"true\"/> Resize Thumbnail \' +\n        \'</p><p>\' +\n        \'<input type=\"button\" name=\"$id\" id=\"ebltmbcrop\" value=\"Create Thumbnail\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#ebltmblnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#ebltmbctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#ebltmbctrl\').hide();\n\n        if (window.tmbcrop && window.cropOn) { // user closed thumb crop\n            window.tmbcrop = false;\n            removeCrop();\n        } else if (!window.tmbcrop && window.cropOn) { // user switched from main crop\n            window.tmbcrop = true;\n            window.crop = false;\n            $(\'#imgaspectratio,#tmbaspectratio\').attr(\'disabled\', true).attr(\'checked\', false);\n            $(\'#customsize1\').val(\'Custom\');\n            myCrop.setOptions({\n                aspectRatio: 0\n            });\n            myCrop.animateTo([0, 0, 100, 150]);\n        } else { // user opens thumb crop\n            addCrop();\n            window.tmbcrop = true;\n        }\n\n    });\n\n    $(\'#ebltmbcrop\').click(function () {\n        var id = $(\'#eblimgcrop\').attr(\'name\');\n        var H = $(\'#eblcropHh\').attr(\'value\');\n        var W = $(\'#eblcropWh\').attr(\'value\');\n        var X = $(\'#eblcropXh\').attr(\'value\');\n        var Y = $(\'#eblcropYh\').attr(\'value\');\n\n        var tH = $(\'#ebltmbH\').attr(\'value\');\n        var tW = $(\'#ebltmbW\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n        var rszTmb = $(\'#cropthumb\').attr(\'checked\') ? \'rsz\' : \'no\';\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=ebltmbcrop&imgid=\" + id,\n            data: \"&rszTmb=\" + rszTmb + \"&eblcropXh=\" + X + \"&eblcropYh=\" + Y + \"&eblcropW=\" + W + \"&eblcropH=\" + H + \"&ebltmbH=\" + tH + \"&ebltmbW=\" + tW,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    removeCrop();\n                    $(\'#thumbTD #thumb\').remove();\n                    $(\'#thumbTD\').prepend(\'<img src=\"$tmbsrc?\' + rand + \'\" id=\"thumb\" class=\"content-image\" />\');\n                    $(\'#ebltmbctrl\').toggle();\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#cropthumb\').change(function () {\n        var cropthumb = $(this).attr(\'checked\') ? false : true;\n\n        if (cropthumb) {\n            $(\'#ebltmbW, #ebltmbH\').attr({\n                disabled: \"disabled\",\n                value: \"###\"\n            });\n        } else {\n            $(\'#ebltmbW, #ebltmbH\').removeAttr(\"disabled\");\n            $(\'#ebltmbW, #ebltmbH\').attr(\"value\", \"100\");\n        }\n    });\n\n\n\n    //------------------------------------------\n    // Rotate Control\n    $(\'#eblrotatectrl\').append(\n        \'<p>Rotate <input type=\"button\" name=\"$id\" id=\"rotateright\" value=\"Clockwise 90&deg;\" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"rotateleft\" value=\"Counterclockwise 90&deg; \" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"rotateup\" value=\"180 &deg; \" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#eblrotatelnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblrotatectrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblrotatectrl\').hide();\n\n        removeCrop();\n        window.myCrop = \' \';\n        window.crop = false;\n    });\n\n    $(\'#rotateleft\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateleft&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#rotateright\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateright&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n    $(\'#rotateup\').click(function () {\n        removeCrop();\n        var id = $(\'#rotateright\').attr(\'name\');\n        var rand = Math.random();\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrotateup&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // BACKUP Control\n    $(\'#eblbackupimg\').append(\n        \'<p><input type=\"button\" name=\"$id\" id=\"eblbackup\" value=\"Backup\" class=\"smallerbox\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"eblrestore\" value=\"Restore\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n    $restore\n\n    $(\'#eblbackuplnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblbackupimg\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblbackupimg\').hide();\n\n        removeCrop();\n        window.myCrop = \' \';\n        window.crop = false;\n    });\n\n    $(\'#eblbackup\').click(function () {\n        var id = $(this).attr(\'name\');\n        $(\'#eblimgprocess\').show();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblbackupimg&imgid=\" + id,\n            success: function (html) {\n                $(\'#eblrestore\').show();\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"image backed up\");\n                } else {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"Error : \" + id + \"  |   \" + html);\n                }\n            }\n        });\n    });\n\n    $(\'#eblrestore\').click(function () {\n        var id = $(this).attr(\'name\');\n        $(\'#eblimgprocess\').show();\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrestoreimg&imgid=\" + id,\n            success: function (html) {\n                if (html.match(/success/)) {\n                    $(\'#eblimgprocess\').hide();\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    alert(\"image restored\");\n                } else {\n                    $(\'#eblimgprocess\').hide();\n                    alert(\"Error : \" + html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // RESIZE Control\n    $(\'#eblresize\').append(\n        \'<p>Current Image Size : W[ <span id=\"origW\"></span> ] x H[ <span id=\"origH\"></span> ]</p>\' +\n        \'<p><label for=\"eblrszW\">Width: </label><input type=\"text\" name=\"eblrszW\" id=\"eblrszW\" /> x \' +\n        \'<label for=\"eblrszH\">Height: </label><input type=\"text\" name=\"eblrszH\" id=\"eblrszH\" /> \' +\n        \'<input type=\"button\" name=\"$id\" id=\"eblrszimg\" value=\"Resize\" class=\"smallerbox\" />\' +\n        \'</p>\'\n    );\n\n    $(\'#eblresizelnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblresize\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblresize\').hide();\n\n        var imgW = $(\'#mainImg\').width();\n        var imgH = $(\'#mainImg\').height();\n\n        $(\'#origW\').text(imgW);\n        $(\'#origH\').text(imgH);\n\n        removeCrop();\n    });\n\n    $(\'#eblrszimg\').click(function () {\n        var id = $(\'#eblrszimg\').attr(\'name\');\n        var H = $(\'#eblrszH\').attr(\'value\');\n        var W = $(\'#eblrszW\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblrszimg&imgid=\" + id,\n            data: \"&eblrszH=\" + H + \"&eblrszW=\" + W,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    //\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n\n    //------------------------------------------\n    // WATERMARK Control\n    $(\'#eblwmctrl\').append(\n        \'<p><label for=\"eblmwimg\">Immagine: $imagedir</label><input type=\"text\" name=\"eblwmimg\" id=\"eblwmimg\" value=\"$eblwmval[wm]\"/></p>\' +\n        \'<p><label for=\"eblwmT\">Top: </label><input type=\"text\" name=\"eblwmT\" id=\"eblwmT\" value=\"$eblwmval[top]\"/> px \' +\n        \'<label for=\"eblwmL\">Left: </label><input type=\"text\" name=\"eblwmL\" id=\"eblwmL\" value=\"$eblwmval[left]\" /> px </p>\' +\n        \'<p><input type=\"button\" name=\"$id\" id=\"eblwatermark\" value=\"Watermark\" class=\"smallerbox\" /></p>\'\n    );\n\n\n    $(\'#eblwmlnk\').click(function () {\n        $(this).siblings().css(\'font-weight\', \'normal\');\n        $(this).css(\'font-weight\', \'bold\');\n        $(\'#eblwmctrl\').toggle();\n        $(\'#eblcontainer div\').not(\'#eblwmctrl\').hide();\n        removeCrop();\n    });\n\n    $(\'#eblwatermark\').click(function () {\n        var id = $(this).attr(\'name\');\n        var wm = $(\'#eblwmimg\').attr(\'value\');\n        var T = $(\'#eblwmT\').attr(\'value\');\n        var L = $(\'#eblwmL\').attr(\'value\');\n\n        $(\'#eblimgprocess\').show();\n\n        var rand = Math.random();\n        $.ajax({\n            type: \"POST\",\n            url: \"index.php?event=eblwmcreate&imgid=\" + id,\n            data: \"&eblwmT=\" + T + \"&eblwmL=\" + L + \"&eblwmimg=\" + wm,\n            success: function (html) {\n                $(\'#eblimgprocess\').hide();\n                if (html.match(/success/)) {\n                    var x = $(\'#imgDiv\');\n                    x.empty().append(\'<img src=\"$imgsrc?\' + rand + \'\" id=\"mainImg\" />\');\n                    //\n                } else {\n                    alert(html);\n                }\n            }\n        });\n    });\n\n\n});\n\nfunction showCoords(c) {\n    $(\'#eblcropX\').text(c.x);\n    $(\'#eblcropXh\').val(c.x);\n    $(\'#eblcropY\').text(c.y);\n    $(\'#eblcropYh\').val(c.y);\n    $(\'#eblcropX2\').text(c.x2);\n    $(\'#eblcropX2h\').val(c.x2);\n    $(\'#eblcropY2\').text(c.y2);\n    $(\'#eblcropY2h\').val(c.y2);\n\n    var imgW = zeroPad(c.w, 4);\n    var imgH = zeroPad(c.h, 4);\n\n    $(\'#eblcropW\').text(imgW);\n    $(\'#eblcropWh\').val(c.w);\n    $(\'#eblcropH\').text(imgH);\n    $(\'#eblcropHh\').val(c.h);\n};\n\nfunction zeroPad(num, count) {\n    var numZeropad = num + \'\';\n    while (numZeropad.length < count) {\n        numZeropad = \"0\" + numZeropad;\n    }\n    return numZeropad;\n}\n\n</script>\nEOF;\n\n}\n\nfunction eblrestoreimg($id)\n{\n\n	global $path_to_site,$img_dir;\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		$imagedir	= $path_to_site . \'/\' . $img_dir . \'/\';\n\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename;\n\n		if(copy($backupdir.$filename, $imagedir.$filename))\n		{\n			list($width, $height) = getimagesize($backupdir.$filename);\n			if(safe_update(\'txp_image\', \"w = \'\".$width.\"\', h = \'\".$height.\"\'\", \"id = $id\"))\n			{\n				echo \"success\"; exit();\n			} else {\n				echo \"File Copied but db not updated\";\n			}\n		}\n	} else {\n		echo \"Database Error\";\n	}\n\n	exit();\n}\n\nfunction eblbackupimg($id)\n{\n	global $path_to_site,$img_dir;\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		$backupdir 	= $path_to_site . \'/\' . $img_dir . \'/backup/\';\n		$imagedir	= $path_to_site . \'/\' . $img_dir . \'/\';\n\n		extract($rs);\n		$filename = $id.$ext;\n		$imgsrc = $imagedir.$filename; echo $imagedir.$filename;\n\n		$cr = copy($imagedir.$filename,$backupdir.$filename);\n		echo \"success\";\n	} else {\n		echo \"Database Error\";\n	}\n\n	exit();\n}\n\nfunction ebl_img_edit ($id, $direction, $action)\n{\n	global $path_to_site,$img_dir;\n\n	$imagedir = $path_to_site . \'/\' . $img_dir . \'/\';\n\n	$rs = safe_row(\"*, unix_timestamp(date) as uDate\", \"txp_image\", \"id = $id\");\n\n	if ($rs) {\n		extract($rs);\n		$filename = $id.$ext;\n\n		switch (strtolower($ext)) {\n			case \'.jpg\':\n				$srcimage = imagecreatefromjpeg($imagedir . $filename);\n				break;\n			case \'.gif\':\n				$srcimage = imagecreatefromgif($imagedir . $filename);\n				break;\n			case \'.png\':\n				$srcimage = imagecreatefrompng($imagedir . $filename);\n				break;\n		}\n\n		if($action==\'watermark\'){ //top and left was based on full image.\n			$eblwm[\'top\']=gps(\'eblwmT\');\n			$eblwm[\'left\']=gps(\'eblwmL\');\n			$eblwm[\'wm\']=gps(\'eblwmimg\');\n\n			set_pref(\'eblwatermark\', serialize($eblwm), \'admin\',2);\n			if (file_exists($imagedir.$eblwm[\'wm\'])) {	//first step WM must be a PNG!\n				$wmimg=@imagecreatefrompng($imagedir.$eblwm[\'wm\']);\n				$wmimg_w = imagesx($wmimg);\n				$wmimg_h = imagesy($wmimg);\n				imagecopy($srcimage, $wmimg,$eblwm[\'left\'],$eblwm[\'top\'], 0, 0, $wmimg_w, $wmimg_h);\n				$newimg=$srcimage;\n			}\n		}\n\n		if($action == \'rotate\') { // rotate image\n\n			/** Ternary equivalent of  if / elseif / else : Done for simple shorthand. Assumes anything not right / left = 180 **/\n			$degrees = ($direction == \'right\') ? -90 : (($direction == \'left\') ? 90 : 180);\n			$newimg  = imagerotate($srcimage, $degrees, 0);\n		} elseif ($action == \'crop\') { // primary image cropping\n\n			//gather crop variables from hidden fields.\n			$cropX = gps(\'eblcropXh\');\n			$cropY = gps(\'eblcropYh\');\n			$targW = $cropW = gps(\'eblcropW\');\n			$targH = $cropH = gps(\'eblcropH\');\n\n			// prevents division by zero & invalid crop widths.\n			$value = (int)$targH + (int)$targW;\n			if($value < 30) {\n				echo \"invalid crop values detected\";\n				imagedestroy($srcimage);\n				exit();\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0, $cropX, $cropY, $targW, $targH, $cropW, $cropH);\n\n		} elseif ($action == \'thumbnail\') { // create thumbnail\n\n			//gather crop variables from hidden fields.\n			$cropX = gps(\'eblcropXh\');\n			$cropY = gps(\'eblcropYh\');\n			$cropH = gps(\'eblcropH\');\n			$cropW = gps(\'eblcropW\');\n\n			// prevents division by zero & invalid crop widths.\n			$value = (int)$cropH + (int)$cropW;\n			if($value < 4) {\n				echo \"invalid crop values detected\";\n				imagedestroy($srcimage);\n				exit();\n			}\n\n			if(gps(\'rszTmb\') == \"rsz\") { // are we resizing the the thumbnail?\n				$targH = gps(\'ebltmbH\');\n				$targW = gps(\'ebltmbW\');\n\n			// did someone forget to input a height or width? If so, default to crop width\n			if($targH < 1) { $targH = $cropH; }\n			if($targW < 1) { $targW = $cropW; }\n\n				// determine the correct scale value\n				$scale = min($targW / $cropW,$targH / $cropH);\n				// are the sides equal? If not, scale to fit.\n				if ($scale < 1) {\n					$targW = ceil($scale * $cropW);\n					$targH = ceil($scale * $cropH);\n				}\n			} elseif (gps(\'rszTmb\') != \'rsz\') { // if not, we\'ll just use the crop-area size to create our thumbnail\n				$targH = $cropH;\n				$targW = $cropW;\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0, $cropX, $cropY, $targW, $targH, $cropW, $cropH);\n\n		} elseif ($action == \'rsz\') { // general resize\n			$targH = gps(\'eblrszH\');\n			$targW = gps(\'eblrszW\');\n\n			if(($targH + $targW) < 1) {\n				echo \" ERROR : Dimensions must be entered.\";\n				return false;\n			}\n			// did someone forget to input a height or width? If so, we\'ll default to the original size for the missing value.\n			if($targH < 1) { $targH = $h; }\n			if($targW < 1) { $targW = $w; }\n			// checks to see if the targeted values are larger than the original, if so, reset to original value as the longest side.\n			if($targH > $h) { $targH = $h; }\n			if($targW > $w) { $targW = $w; }\n\n			$scale = min($targW / $w, $targH / $h);\n			// are the sides equal? If not, scale to fit.\n			if ($scale < 1) {\n				$targW = ceil($scale * $w);\n				$targH = ceil($scale * $h);\n			} else { // image is smaller than requested scale. Abort.\n				imagedestroy($srcimage);\n				echo \"Image smaller than parameters entered\";\n				return;\n			}\n\n			/** Create base canvas **/\n			$newimg = imagecreatetruecolor($targW,$targH);\n			/** Use the X/Y coords to plot the initial point, width/height take care of the rest **/\n			imagecopyresampled($newimg, $srcimage,0,0,0,0, $targW, $targH, $w, $h);\n		}\n\n		$t = ($action == \'thumbnail\') ? \'t\' : \'\';\n\n		$filename = $id.$t.$ext;\n\n		switch (strtolower($ext)) {\n			case ($ext == \'.jpg\' || $ext == \'.jpeg\'):\n				$fileresult = imagejpeg($newimg,$imagedir . $filename,\'100\');\n				break;\n			case \".gif\":\n				$fileresult = imagegif($newimg,$imagedir . $filename);\n				break;\n			case \".png\":\n				$fileresult = imagepng($newimg,$imagedir . $filename);\n				break;\n		}\n\n		imagedestroy($srcimage);\n	}\n\n	if($action != \'thumbnail\')\n	{\n		list ($width,$height) = getimagesize($imagedir . $filename);\n		$rs = safe_update(\'txp_image\', \"h = \'$height\', w = \'$width\'\", \"id = $id\");\n	} else {\n		list ($width,$height) = getimagesize($imagedir . $filename);\n		$rs = safe_update(\'txp_image\', \"thumb_h = \'$height\', thumb_w = \'$width\', thumbnail = \'1\'\", \"id = $id\");\n	}\n\n	echo ($fileresult && $rs) ? \"success\" : \'ERROR \';\n}\n', '60546bddcf7bc1facaa2d3c9782df0a1', '', '', 4, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('aks_dragdrop', 0, 'makss', 'http://makss.uaho.net/plugins/aks_dragdrop', '0.4.1', 'Drag & Drop TxP categories (article, link, image, file)', '<h1 style=\"margin: 100px auto 200px auto;\"><a href=\"?event=aks_dragdrop_tab\">Open Drag&amp;Drop categories</a></h1>\n\n\n\n\n<!--aks_dragdrop_css_\"text/css;charset=UTF-8\"\n.cf:after { visibility: hidden; display: block; font-size: 0; content: \" \"; clear: both; height: 0; }\n* html .cf { zoom: 1; }\n*:first-child+html .cf { zoom: 1; }\n\n#aks_tbl {\n	width: 100%;\n	margin: 0 auto;\n	max-width: 1280px;\n}\n#aks_tbl tr {\n	border: 0px;\n}\n#aks_tbl td {\n	width: 50%;\n	vertical-align: top;\n	padding: 0px 10px 20px 10px;\n}\n\n.aks_rr {\n	float: right !important;\n}\n.aks_hide {\n	display: none;\n}\n#aks_bulk {\n	background-color: #f5f5f5;\n	border: 1px solid #bbb;\n	box-shadow: 3px 3px 15px #ccc, 0 0 10px #ccc inset;\n	border-radius: 15px;\n	max-width: 600px;\n	padding: 10px;\n	margin-bottom: 30px;\n}\n#aks_bulk textarea {\n	background-color: #fff;\n	margin-bottom: 10px;\n	border-radius: 5px;\n	min-height: 200px;\n}\n#bulkinsert {\n	padding-bottom: 10px;\n}\n.aks_count a {\n	padding: 5px 10px;\n	float: right;\n	box-shadow: 0 0 20px #fff inset, 2px 2px 10px #777;\n	border-radius: 20px 0 0 0;\n}\n\n.aks_count a:hover {\n	box-shadow: 0 0 20px #ffd21b inset, 2px 2px 7px #777;\n}\n\na:hover { text-decoration: none; }\np { line-height: 1.5em; }\n.small { color: #666; font-size: 0.875em; }\n.large { font-size: 1.25em; }\n\n.dd {\n	position: relative; display: block; margin: 0; max-width: 600px; list-style: none; font-size: 13px; line-height: 20px;\n	box-shadow: 3px 3px 15px #ccc, 0 0 5px #ccc inset;\n	border-radius: 15px;\n	padding: 10px;\n	border: 1px solid #ccc;\n}\n.dd-list { display: block; position: relative; margin: 0; padding: 0; list-style: none; }\n.dd-list .dd-list { padding-left: 30px; }\n.dd-collapsed .dd-list { display: none; }\n.dd-item,\n.dd-empty,\n.dd-placeholder { display: block; position: relative; margin: 0; padding: 0; min-height: 20px; font-size: 13px; line-height: 20px; }\n.dd-handle { display: block; height: 30px; margin: 5px 0; padding: 5px 10px; color: #333; text-decoration: none; font-weight: bold; border: 1px solid #ccc;\n	background: #fafafa;\n	background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);\n	background:    -moz-linear-gradient(top, #fafafa 0%, #eee 100%);\n	background:         linear-gradient(top, #fafafa 0%, #eee 100%);\n	-webkit-border-radius: 3px;\n	border-radius: 3px;\n	box-sizing: border-box; -moz-box-sizing: border-box;\n}\n.dd-handle:hover { color: #2ea8e5; background: #fff; }\n.dd-item > button { display: block; position: relative; cursor: pointer; float: left; width: 25px; height: 20px; margin: 5px 0; padding: 0; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 0; background: transparent; font-size: 12px; line-height: 1; text-align: center; font-weight: bold; }\n.dd-item > button:before { content: \'+\'; display: block; position: absolute; width: 100%; text-align: center; text-indent: 0; }\n.dd-item > button[data-action=\"collapse\"]:before { content: \'-\'; }\n\n.dd-placeholder,\n.dd-empty { margin: 5px 0; padding: 0; min-height: 30px; background: #f2fbff; border: 1px dashed #b6bcbf; box-sizing: border-box; -moz-box-sizing: border-box; }\n.dd-empty { border: 1px dashed #bbb; min-height: 100px; background-color: #e5e5e5;\n	background-image: -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),\n	-webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);\n	background-image:    -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),\n	-moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);\n	background-image:         linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),\n	linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);\n	background-size: 60px 60px;\n	background-position: 0 0, 30px 30px;\n	border-radius: 5px;\n}\n\n.dd-dragel { position: absolute; pointer-events: none; z-index: 9999; }\n.dd-dragel > .dd-item .dd-handle { margin-top: 0; }\n.dd-dragel .dd-handle {\n	-webkit-box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);\n	box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);\n}\n\n/*\n.nestable-lists { display: block; clear: both; padding: 10px 0; width: 100%; border: 0; border-top: 2px solid #ddd; border-bottom: 2px solid #ddd; }\n*/\n\n#nestable-menu { padding: 0; margin: 20px 0; }\n#nestable-output, #nestable2-output, #nestable3-output {	display: hide; }\n\n#nestable2 .dd-handle {\n	color: #fff;\n	border: 1px solid #999;\n	background: #bbb;\n	background: -webkit-linear-gradient(top, #bbb 0%, #999 100%);\n	background:    -moz-linear-gradient(top, #bbb 0%, #999 100%);\n	background:         linear-gradient(top, #bbb 0%, #999 100%);\n}\n#nestable2 .dd-handle:hover { background: #bbb; }\n#nestable2 .dd-item > button:before { color: #fff; }\n#nestable2 .dd-handle a {\n	color: #fff;\n}\n#nestable3 { margin-top: 20px; }\n#nestable3 .dd-handle {\n	color: #fff;\n	border: 1px solid #999;\n	background: red;\n}\n#nestable3 .dd-handle:hover { background: #FC6A6A; }\n#nestable3 .dd-item > button:before { color: #fff; }\n\n\n\n@media only screen and (min-width: 700px) {\n/*	.dd { float: left; width: 45%; } */\n/*	.dd + .dd { margin-left: 2%; } */\n}\n\n.dd-hover > .dd-handle { background: #2ea8e5 !important; }\naks_dragdrop_css-->\n\n\n\n\n\n\n\n\n\n\n<!--aks_dragdrop_js_\"text/javascript; charset=UTF-8\"\n/*!\n * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/\n * Dual-licensed under the BSD or MIT licenses\n */\n;(function($, window, document, undefined)\n{\n    var hasTouch = \'ontouchstart\' in document;\n\n    /**\n     * Detect CSS pointer-events property\n     * events are normally disabled on the dragging element to avoid conflicts\n     * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js\n     */\n    var hasPointerEvents = (function()\n    {\n        var el    = document.createElement(\'div\'),\n            docEl = document.documentElement;\n        if (!(\'pointerEvents\' in el.style)) {\n            return false;\n        }\n        el.style.pointerEvents = \'auto\';\n        el.style.pointerEvents = \'x\';\n        docEl.appendChild(el);\n        var supports = window.getComputedStyle && window.getComputedStyle(el, \'\').pointerEvents === \'auto\';\n        docEl.removeChild(el);\n        return !!supports;\n    })();\n\n    var defaults = {\n            listNodeName    : \'ol\',\n            itemNodeName    : \'li\',\n            rootClass       : \'dd\',\n            listClass       : \'dd-list\',\n            itemClass       : \'dd-item\',\n            dragClass       : \'dd-dragel\',\n            handleClass     : \'dd-handle\',\n            collapsedClass  : \'dd-collapsed\',\n            placeClass      : \'dd-placeholder\',\n            noDragClass     : \'dd-nodrag\',\n            emptyClass      : \'dd-empty\',\n            expandBtnHTML   : \'<button data-action=\"expand\" type=\"button\">Expand</button>\',\n            collapseBtnHTML : \'<button data-action=\"collapse\" type=\"button\">Collapse</button>\',\n            group           : 0,\n            maxDepth        : 5,\n            threshold       : 20\n        };\n\n    function Plugin(element, options)\n    {\n        this.w  = $(document);\n        this.el = $(element);\n        this.options = $.extend({}, defaults, options);\n        this.init();\n    }\n\n    Plugin.prototype = {\n\n        init: function()\n        {\n            var list = this;\n\n            list.reset();\n\n            list.el.data(\'nestable-group\', this.options.group);\n\n            list.placeEl = $(\'<div class=\"\' + list.options.placeClass + \'\"/>\');\n\n            $.each(this.el.find(list.options.itemNodeName), function(k, el) {\n                list.setParent($(el));\n            });\n\n            list.el.on(\'click\', \'button\', function(e) {\n                if (list.dragEl) {\n                    return;\n                }\n                var target = $(e.currentTarget),\n                    action = target.data(\'action\'),\n                    item   = target.parent(list.options.itemNodeName);\n                if (action === \'collapse\') {\n                    list.collapseItem(item);\n                }\n                if (action === \'expand\') {\n                    list.expandItem(item);\n                }\n            });\n\n            var onStartEvent = function(e)\n            {\n                var handle = $(e.target);\n                if (!handle.hasClass(list.options.handleClass)) {\n                    if (handle.closest(\'.\' + list.options.noDragClass).length) {\n                        return;\n                    }\n                    handle = handle.closest(\'.\' + list.options.handleClass);\n                }\n                if (!handle.length || list.dragEl) {\n                    return;\n                }\n\n                list.isTouch = /^touch/.test(e.type);\n                if (list.isTouch && e.touches.length !== 1) {\n                    return;\n                }\n\n                e.preventDefault();\n                list.dragStart(e.touches ? e.touches[0] : e);\n            };\n\n            var onMoveEvent = function(e)\n            {\n                if (list.dragEl) {\n                    e.preventDefault();\n                    list.dragMove(e.touches ? e.touches[0] : e);\n                }\n            };\n\n            var onEndEvent = function(e)\n            {\n                if (list.dragEl) {\n                    e.preventDefault();\n                    list.dragStop(e.touches ? e.touches[0] : e);\n                }\n            };\n\n            if (hasTouch) {\n                list.el[0].addEventListener(\'touchstart\', onStartEvent, false);\n                window.addEventListener(\'touchmove\', onMoveEvent, false);\n                window.addEventListener(\'touchend\', onEndEvent, false);\n                window.addEventListener(\'touchcancel\', onEndEvent, false);\n            }\n\n            list.el.on(\'mousedown\', onStartEvent);\n            list.w.on(\'mousemove\', onMoveEvent);\n            list.w.on(\'mouseup\', onEndEvent);\n\n        },\n\n        serialize: function()\n        {\n            var data,\n                depth = 0,\n                list  = this;\n                step  = function(level, depth)\n                {\n                    var array = [ ],\n                        items = level.children(list.options.itemNodeName);\n                    items.each(function()\n                    {\n                        var li   = $(this),\n                            item = $.extend({}, li.data()),\n                            sub  = li.children(list.options.listNodeName);\n                        if (sub.length) {\n                            item.children = step(sub, depth + 1);\n                        }\n                        array.push(item);\n                    });\n                    return array;\n                };\n            data = step(list.el.find(list.options.listNodeName).first(), depth);\n            return data;\n        },\n\n        serialise: function()\n        {\n            return this.serialize();\n        },\n\n        reset: function()\n        {\n            this.mouse = {\n                offsetX   : 0,\n                offsetY   : 0,\n                startX    : 0,\n                startY    : 0,\n                lastX     : 0,\n                lastY     : 0,\n                nowX      : 0,\n                nowY      : 0,\n                distX     : 0,\n                distY     : 0,\n                dirAx     : 0,\n                dirX      : 0,\n                dirY      : 0,\n                lastDirX  : 0,\n                lastDirY  : 0,\n                distAxX   : 0,\n                distAxY   : 0\n            };\n            this.isTouch    = false;\n            this.moving     = false;\n            this.dragEl     = null;\n            this.dragRootEl = null;\n            this.dragDepth  = 0;\n            this.hasNewRoot = false;\n            this.pointEl    = null;\n        },\n\n        expandItem: function(li)\n        {\n            li.removeClass(this.options.collapsedClass);\n            li.children(\'[data-action=\"expand\"]\').hide();\n            li.children(\'[data-action=\"collapse\"]\').show();\n            li.children(this.options.listNodeName).show();\n        },\n\n        collapseItem: function(li)\n        {\n            var lists = li.children(this.options.listNodeName);\n            if (lists.length) {\n                li.addClass(this.options.collapsedClass);\n                li.children(\'[data-action=\"collapse\"]\').hide();\n                li.children(\'[data-action=\"expand\"]\').show();\n                li.children(this.options.listNodeName).hide();\n            }\n        },\n\n        expandAll: function()\n        {\n            var list = this;\n            list.el.find(list.options.itemNodeName).each(function() {\n                list.expandItem($(this));\n            });\n        },\n\n        collapseAll: function()\n        {\n            var list = this;\n            list.el.find(list.options.itemNodeName).each(function() {\n                list.collapseItem($(this));\n            });\n        },\n\n        setParent: function(li)\n        {\n            if (li.children(this.options.listNodeName).length) {\n                li.prepend($(this.options.expandBtnHTML));\n                li.prepend($(this.options.collapseBtnHTML));\n            }\n            li.children(\'[data-action=\"expand\"]\').hide();\n        },\n\n        unsetParent: function(li)\n        {\n            li.removeClass(this.options.collapsedClass);\n            li.children(\'[data-action]\').remove();\n            li.children(this.options.listNodeName).remove();\n        },\n\n        dragStart: function(e)\n        {\n            var mouse    = this.mouse,\n                target   = $(e.target),\n                dragItem = target.closest(this.options.itemNodeName);\n\n            this.placeEl.css(\'height\', dragItem.height());\n\n            mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;\n            mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;\n            mouse.startX = mouse.lastX = e.pageX;\n            mouse.startY = mouse.lastY = e.pageY;\n\n            this.dragRootEl = this.el;\n\n            this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + \' \' + this.options.dragClass);\n            this.dragEl.css(\'width\', dragItem.width());\n\n            dragItem.after(this.placeEl);\n            dragItem[0].parentNode.removeChild(dragItem[0]);\n            dragItem.appendTo(this.dragEl);\n\n            $(document.body).append(this.dragEl);\n            this.dragEl.css({\n                \'left\' : e.pageX - mouse.offsetX,\n                \'top\'  : e.pageY - mouse.offsetY\n            });\n            // total depth of dragging item\n            var i, depth,\n                items = this.dragEl.find(this.options.itemNodeName);\n            for (i = 0; i < items.length; i++) {\n                depth = $(items[i]).parents(this.options.listNodeName).length;\n                if (depth > this.dragDepth) {\n                    this.dragDepth = depth;\n                }\n            }\n        },\n\n        dragStop: function(e)\n        {\n            var el = this.dragEl.children(this.options.itemNodeName).first();\n            el[0].parentNode.removeChild(el[0]);\n            this.placeEl.replaceWith(el);\n\n            this.dragEl.remove();\n            this.el.trigger(\'change\');\n            if (this.hasNewRoot) {\n                this.dragRootEl.trigger(\'change\');\n            }\n            this.reset();\n        },\n\n        dragMove: function(e)\n        {\n            var list, parent, prev, next, depth,\n                opt   = this.options,\n                mouse = this.mouse;\n\n            this.dragEl.css({\n                \'left\' : e.pageX - mouse.offsetX,\n                \'top\'  : e.pageY - mouse.offsetY\n            });\n\n            // mouse position last events\n            mouse.lastX = mouse.nowX;\n            mouse.lastY = mouse.nowY;\n            // mouse position this events\n            mouse.nowX  = e.pageX;\n            mouse.nowY  = e.pageY;\n            // distance mouse moved between events\n            mouse.distX = mouse.nowX - mouse.lastX;\n            mouse.distY = mouse.nowY - mouse.lastY;\n            // direction mouse was moving\n            mouse.lastDirX = mouse.dirX;\n            mouse.lastDirY = mouse.dirY;\n            // direction mouse is now moving (on both axis)\n            mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;\n            mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;\n            // axis mouse is now moving on\n            var newAx   = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;\n\n            // do nothing on first move\n            if (!mouse.moving) {\n                mouse.dirAx  = newAx;\n                mouse.moving = true;\n                return;\n            }\n\n            // calc distance moved on this axis (and direction)\n            if (mouse.dirAx !== newAx) {\n                mouse.distAxX = 0;\n                mouse.distAxY = 0;\n            } else {\n                mouse.distAxX += Math.abs(mouse.distX);\n                if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {\n                    mouse.distAxX = 0;\n                }\n                mouse.distAxY += Math.abs(mouse.distY);\n                if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {\n                    mouse.distAxY = 0;\n                }\n            }\n            mouse.dirAx = newAx;\n\n            /**\n             * move horizontal\n             */\n            if (mouse.dirAx && mouse.distAxX >= opt.threshold) {\n                // reset move distance on x-axis for new phase\n                mouse.distAxX = 0;\n                prev = this.placeEl.prev(opt.itemNodeName);\n                // increase horizontal level if previous sibling exists and is not collapsed\n                if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {\n                    // cannot increase level when item above is collapsed\n                    list = prev.find(opt.listNodeName).last();\n                    // check if depth limit has reached\n                    depth = this.placeEl.parents(opt.listNodeName).length;\n                    if (depth + this.dragDepth <= opt.maxDepth) {\n                        // create new sub-level if one doesn\'t exist\n                        if (!list.length) {\n                            list = $(\'<\' + opt.listNodeName + \'/>\').addClass(opt.listClass);\n                            list.append(this.placeEl);\n                            prev.append(list);\n                            this.setParent(prev);\n                        } else {\n                            // else append to next level up\n                            list = prev.children(opt.listNodeName).last();\n                            list.append(this.placeEl);\n                        }\n                    }\n                }\n                // decrease horizontal level\n                if (mouse.distX < 0) {\n                    // we can\'t decrease a level if an item preceeds the current one\n                    next = this.placeEl.next(opt.itemNodeName);\n                    if (!next.length) {\n                        parent = this.placeEl.parent();\n                        this.placeEl.closest(opt.itemNodeName).after(this.placeEl);\n                        if (!parent.children().length) {\n                            this.unsetParent(parent.parent());\n                        }\n                    }\n                }\n            }\n\n            var isEmpty = false;\n\n            // find list item under cursor\n            if (!hasPointerEvents) {\n                this.dragEl[0].style.visibility = \'hidden\';\n            }\n            this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));\n            if (!hasPointerEvents) {\n                this.dragEl[0].style.visibility = \'visible\';\n            }\n            if (this.pointEl.hasClass(opt.handleClass)) {\n                this.pointEl = this.pointEl.parent(opt.itemNodeName);\n            }\n            if (this.pointEl.hasClass(opt.emptyClass)) {\n                isEmpty = true;\n            }\n            else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {\n                return;\n            }\n\n            // find parent list of item under cursor\n            var pointElRoot = this.pointEl.closest(\'.\' + opt.rootClass),\n                isNewRoot   = this.dragRootEl.data(\'nestable-id\') !== pointElRoot.data(\'nestable-id\');\n\n            /**\n             * move vertical\n             */\n            if (!mouse.dirAx || isNewRoot || isEmpty) {\n                // check if groups match if dragging over new root\n                if (isNewRoot && opt.group !== pointElRoot.data(\'nestable-group\')) {\n                    return;\n                }\n                // check depth limit\n                depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;\n                if (depth > opt.maxDepth) {\n                    return;\n                }\n                var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);\n                    parent = this.placeEl.parent();\n                // if empty create new list to replace empty placeholder\n                if (isEmpty) {\n                    list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);\n                    list.append(this.placeEl);\n                    this.pointEl.replaceWith(list);\n                }\n                else if (before) {\n                    this.pointEl.before(this.placeEl);\n                }\n                else {\n                    this.pointEl.after(this.placeEl);\n                }\n                if (!parent.children().length) {\n                    this.unsetParent(parent.parent());\n                }\n                if (!this.dragRootEl.find(opt.itemNodeName).length) {\n                    this.dragRootEl.append(\'<div class=\"\' + opt.emptyClass + \'\"/>\');\n                }\n                // parent root list has changed\n                if (isNewRoot) {\n                    this.dragRootEl = pointElRoot;\n                    this.hasNewRoot = this.el[0] !== this.dragRootEl[0];\n                }\n            }\n        }\n\n    };\n\n    $.fn.nestable = function(params)\n    {\n        var lists  = this,\n            retval = this;\n\n        lists.each(function()\n        {\n            var plugin = $(this).data(\"nestable\");\n\n            if (!plugin) {\n                $(this).data(\"nestable\", new Plugin(this, params));\n                $(this).data(\"nestable-id\", new Date().getTime());\n            } else {\n                if (typeof params === \'string\' && typeof plugin[params] === \'function\') {\n                    retval = plugin[params]();\n                }\n            }\n        });\n\n        return retval || lists;\n    };\n\n})(window.jQuery || window.Zepto, window, document);\naks_dragdrop_js-->', 'if(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_dragdrop_tab\', \'1,2\');\r\n	register_tab(\"content\", \"aks_dragdrop_tab\", gTxt(\'tab_organise\').\" +\");\r\n	register_callback(\"aks_dragdrop_tab\", \"aks_dragdrop_tab\");\r\n	add_privs(\'aks_dragdrop_file\', \'1,2\');\r\n	register_callback(\"aks_dragdrop_file\", \"aks_dragdrop_file\");\r\n\r\n}\r\n\r\nfunction aks_dragdrop_tab($event, $step){\r\n	global $aks_msgerr, $aks_count, $aks_link, $aks_link_edit;\r\n	$categories = array(\"article\", \"link\", \"image\", \"file\");\r\n	if( empty($step) ){ $step = \"article\"; }\r\n	if( !in_array($step, $categories, TRUE) ){ return; }\r\n	$type = $step;\r\n	$tt = doSlash($type);\r\n	$tbl = \'txp_category\';		// FIXIT: hardcode txp_category\r\n\r\n	$aks_msgerr = \"\";\r\n	$message = \"\";\r\n	if( ps(\"save\") ){\r\n		$tree = @json_decode( ps(\'json1\'), TRUE);\r\n		if( !empty($tree) ){\r\n			$root_id = safe_field(\'id\', $tbl, \"name =\'root\' and type=\'{$tt}\'\");\r\n			aks_dragdrop__upd( $tree, $root_id, $type, $tbl);\r\n		}\r\n		$tree = @json_decode( ps(\'json2\'), TRUE);\r\n		if( !empty($tree) ){\r\n			aks_dragdrop__upd2( $tree, $type, $tbl);\r\n		}\r\n		$tree = @json_decode( ps(\'json3\'), TRUE);\r\n		if( !empty($tree) ){\r\n			aks_dragdrop__delete( $tree, $type, $tbl);\r\n		}\r\n\r\n		safe_update($tbl, \'lft=0, rgt=0\', \"type=\'{$tt}\'\");\r\n		rebuild_tree_full( $type );\r\n		$message = gTxt(\"{$type}_head\").\' \'.gTxt(\'saved\');\r\n	}\r\n\r\n	if( ps(\"savebulk\") ){\r\n		if( $bulk = preg_split(\'/[\\r\\n]+/s\', trim(ps(\'bulk\'))) ){\r\n			$added = array();\r\n			$parent = doSlash(ps(\'parent\'));\r\n			if( empty($parent) ){ $parent = \"root\"; }\r\n			foreach( $bulk as $title){\r\n				$title = trim($title);\r\n				$name = doSlash(strtolower(sanitizeForUrl($title))); if( empty($name) ){ continue; }\r\n				if( safe_column(\'id\', $tbl, \"type=\'{$tt}\' and name=\'{$name}\'\") ){\r\n					$aks_msgerr .= str_replace(\'{name}\', $title, gTxt(\"{$type}_category_already_exists\") ).\"\\n\";\r\n				}else{\r\n					$added[] = $title;\r\n					$title = doSlash($title);\r\n					safe_insert($tbl, \"name=\'{$name}\', type=\'{$tt}\', parent=\'$parent\', title=\'{$title}\' \");\r\n				}\r\n			}\r\n			if( !empty($added) ){\r\n				$message .= str_replace(\'{name}\', join(\', \', $added), gTxt(\"{$type}_category_created\") );\r\n				safe_update($tbl, \'lft=0, rgt=0\', \"type=\'{$tt}\'\");\r\n				rebuild_tree_full( $type );\r\n			}\r\n		}\r\n	}\r\n	if( $aks_msgerr ){ $aks_msgerr = \"<pre>$aks_msgerr</pre>\"; }\r\n	pagetop(gTxt(\'tab_organise\').\" +\", $message);\r\n\r\n	if( $type == \'article\' ){\r\n		$aks_link = \"?event=list&search_method=categories&crit=\";\r\n		$rs = getRows(\'select category, count(*) as num from (\'.\r\n			\'select ID, Category1 as category from \'.safe_pfx(\'textpattern\').\r\n			\' union \'.\r\n			\'select ID, Category2 as category from \'.safe_pfx(\'textpattern\').\r\n			\') as t where category != \"\" group by category\');\r\n	}else{\r\n		$aks_link = \"?event={$type}&search_method=category&crit=\";\r\n		$rs = safe_rows(\'category, count(*) as num\', \"txp_{$type}\", \"1 group by category\");\r\n\r\n	}\r\n	$aks_link_edit = \"?event=category&step=cat_{$type}_edit&id=\";\r\n	$aks_count = array();\r\n	foreach( $rs as $r ){\r\n		$aks_count[$r[\'category\']] = $r[\'num\'];\r\n	}\r\n\r\n	$list1 = aks_dragdrop_list(\'root\', $type, \'1=1\', $tbl);\r\n	if( $rs = safe_rows(\'id,name,title\', $tbl, \"type=\'{$tt}\' and lft=0 and rgt=0\") ){\r\n		$list2 = \"<ol class=\'dd-list\'>\";\r\n		foreach( $rs as $r ){ extract($r);\r\n			if( !empty($aks_count[$name]) ){\r\n				$count = \"<div class=\'aks_count\'><a href=\'{$aks_link}{$name}\'>{$aks_count[$name]}</a></div>\";\r\n			}else{	$count = \"\"; }\r\n			$list2 .= \"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div></li>\";\r\n		}	$list2 .= \"</ol>\";\r\n	}else{\r\n		$list2 = \"<div class=\'dd-empty\'></div>\";\r\n	}\r\n\r\n	$cats = \"\";\r\n	foreach( $categories as $cat ){\r\n		$cats.= \"<option value=\'{$cat}\'\".(($type==$cat)? \" selected=\'selected\'\":\"\").\">\".gTxt(\"{$cat}_head\").\"</option>\";\r\n	}\r\n	$html = \'<textarea id=\"nestable-output\" name=\"json1\" style=\"display:none;\"></textarea><textarea id=\"nestable2-output\" name=\"json2\" style=\"display:none;\"></textarea><textarea id=\"nestable3-output\" name=\"json3\" style=\"display:none;\"></textarea>\';\r\n	$form = form(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_dragdrop_tab\").sInput($type).$html);\r\n\r\n	$title = gTxt(\"{$type}_head\");\r\n	$title2 = $title.\" (\".gTxt(\"hidden\").\")\";\r\n	$title3 = gTxt(\'delete\');\r\n	$tcount = gTxt($type);\r\n\r\n	$rs = getTree(\'root\', $type);\r\n	array_unshift($rs, array(\'name\'=>\'aks_dragdrop-hide\', \'title\'=>$title2, \'level\'=>0 ));\r\n	$parent = gTxt(\'parent\').treeSelectInput(\'parent\', $rs, \"aks_dragdrop-hide\", \'category_parent\');\r\n	$htmlbulk = \"<textarea name=\'bulk\'></textarea>{$parent}\";\r\n	$formbulk = form($htmlbulk.fInput(\"submit\",\"savebulk\",gTxt(\"save_button\"),\"publish aks_rr\").eInput(\"aks_dragdrop_tab\").sInput($type));\r\n\r\n\r\necho <<<FORM\r\n	<link type=\"text/css\" href=\"?event=aks_dragdrop_file&step=css\" rel=\"stylesheet\" />\r\n	<script type=\"text/javascript\" src=\"?event=aks_dragdrop_file&step=js\"></script>\r\n	{$aks_msgerr}\r\n\r\n<table id=\"aks_tbl\"><tr><td>\r\n	<menu id=\"nestable-menu\" style=\"padding: 0 10px 10px 10px;\">\r\n		<div style=\"float:right; margin-left:10px;\">{$form}</div>\r\n		<div class=\"aks_rr\">\r\n			<button type=\"button\" data-action=\"expand-all\">Expand All</button>\r\n			<button type=\"button\" data-action=\"collapse-all\">Collapse All</button>\r\n		</div>\r\n		<form method=\"get\" action=\"index.php\"><input type=\"hidden\" value=\"aks_dragdrop_tab\" name=\"event\" />\r\n			<select name=\"step\" onchange=\"submit(this.form);\" style=\"font-size: 18px;\">{$cats}</select>\r\n		</form>\r\n	</menu>\r\n	<div class=\"dd\" id=\"nestable\"><div>{$title}<span class=\"aks_rr\">{$tcount}</span></div>{$list1}</div>\r\n</td><td>\r\n	<div id=\"aks_bulk\"><h3><a class=\"on_off\" rel=\"bulkinsert\" href=\"#\">Bulk Insert categories</a></h3>\r\n		<div class=\"aks_hide\" id=\"bulkinsert\"><h3>{$title} <span style=\'color: #996633;\' title=\"Add new categories, one per line\">[?]</span></h3>\r\n			{$formbulk}\r\n		</div>\r\n	</div>\r\n	<div class=\"dd\" id=\"nestable2\"><div>{$title2}<span class=\"aks_rr\">{$tcount}</span></div>{$list2}</div>\r\n	<div class=\"dd\" id=\"nestable3\"><div>{$title3}<span class=\"aks_rr\">{$tcount}</span></div><div class=\'dd-empty\'></div></div>\r\n</td></tr></table>\r\n\r\n\r\n<script type=\"text/javascript\">\r\n$(document).ready(function() {\r\n	var updateOutput = function(e){\r\n		var list   = e.length ? e : $(e.target),\r\n			output = list.data(\'output\');\r\n		if (window.JSON) {\r\n			output.val(window.JSON.stringify(list.nestable(\'serialize\')));//, null, 2));\r\n		} else {\r\n			output.val(\'JSON browser support required for this demo.\');\r\n		}\r\n	};\r\n\r\n	$(\'#nestable\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable\').data(\'output\', $(\'#nestable-output\')));\r\n\r\n	$(\'#nestable2\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable2\').data(\'output\', $(\'#nestable2-output\')));\r\n\r\n	$(\'#nestable3\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable3\').data(\'output\', $(\'#nestable3-output\')));\r\n\r\n	$(\'#nestable-menu\').on(\'click\', function(e){\r\n		var target = $(e.target),\r\n			action = target.data(\'action\');\r\n		if (action === \'expand-all\') {\r\n			$(\'.dd\').nestable(\'expandAll\');\r\n		}\r\n		if (action === \'collapse-all\') {\r\n			$(\'.dd\').nestable(\'collapseAll\');\r\n		}\r\n	});\r\n\r\n	$(\'.on_off\').click(function(){\r\n		var child = $(\'#\'+ $(this).attr(\"rel\"));\r\n		child.toggleClass(\"aks_hide\");\r\n		if( child.hasClass(\"aks_hide\") ){\r\n			$(this).removeClass(\'on\').addClass(\'off\');\r\n		}else{\r\n			$(this).removeClass(\'off\').addClass(\'on\');\r\n		}\r\n		return false;\r\n	});\r\n\r\n});\r\n</script>\r\nFORM;\r\n}\r\n\r\n\r\nfunction aks_dragdrop__upd( $tree, $root_id, $type, $tbl){\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$rid	= doSlash($root_id);\r\n		$tt	= doSlash($type);\r\n		$q = \"update \".safe_pfx($tbl).\" c1, \".safe_pfx($tbl).\" c2 set c1.parent=c2.name where c1.type=\'{$tt}\' and c1.id=\'{$id}\' and c2.id=\'{$rid}\'\";\r\n		safe_query($q);\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__upd( $a[\'children\'], $a[\'id\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop__upd2( $tree, $type, $tbl){\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$tt	= doSlash($type);\r\n		safe_update($tbl, \"parent=\'aks_dragdrop-hide\'\", \"type=\'{$tt}\' and id=\'{$id}\'\");\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__upd2( $a[\'children\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop__delete( $tree, $type, $tbl){\r\n	global $aks_msgerr;\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$tt	= doSlash($type);\r\n		if( $rs = safe_row(\'name,title\', $tbl, \"id =\'{$id}\'\") ){\r\n			$name	= doSlash($rs[\'name\']);\r\n			if( $type == \'article\' ){\r\n				$exist = safe_field(\"id\", \"textpattern\", \"category1 =\'{$name}\' or category2 =\'{$name}\'\");\r\n				$link = \"?event=list&search_method=categories&crit={$name}\";\r\n			}else{\r\n				$exist = safe_field(\"id\", \"txp_{$tt}\", \"category =\'{$name}\'\");\r\n				$link = \"?event={$type}&search_method=category&crit={$name}\";\r\n			}\r\n			if( $exist ){\r\n				$title	= txpspecialchars($rs[\'title\']);\r\n				$aks_msgerr .= \"Category `{$title}` is not empty, see <a href=\'{$link}\'>here</a>\\n\";\r\n			}else{\r\n				safe_delete($tbl, \"id=\'{$id}\'\");\r\n			}\r\n		}\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__delete( $a[\'children\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop_list($root, $type, $where=\'1=1\', $tbl=\'txp_category\'){\r\n	global $aks_count, $aks_link, $aks_link_edit;\r\n	$rs = getTree($root, $type, $where, $tbl);\r\n	if( $rs ){\r\n		$out = \"\"; $lev = 0;\r\n		foreach( $rs as $a ){ extract($a);\r\n			while( $lev>$level ){ $out.=\"</ol></li>\"; $lev--; }\r\n			$lev = $level;\r\n			if( !empty($aks_count[$name]) ){\r\n				$count = \"<div class=\'aks_count\'><a href=\'{$aks_link}{$name}\'>{$aks_count[$name]}</a></div>\";\r\n			}else{	$count = \"\"; }\r\n\r\n			if( empty($children) ){\r\n				$out.=\"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div></li>\";\r\n			}else{\r\n				$out.=\"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div><ol class=\'dd-list\'>\";\r\n			}\r\n		}\r\n		while( $lev>0 ){ $out.=\"</ol></li>\"; $lev--; }\r\n		return \"<ol class=\'dd-list\'>{$out}</ol>\";\r\n	}\r\n	return \"<div class=\'dd-empty\'></div>\";\r\n}\r\n\r\nfunction aks_dragdrop_file($event, $step){\r\n	$help = safe_field(\'help\', \'txp_plugin\', \"name =\'aks_dragdrop\'\");\r\n	if( in_array($step, array(\'css\', \'js\'), TRUE) ){\r\n		if( preg_match(\'%<!--aks_dragdrop_\'.$step.\'(_\"(.*?)\")?(.*?)aks_dragdrop_\'.$step.\'-->%is\', $help, $mm) ){\r\n			if( !empty($mm[2]) ){ header(\"Content-Type: {$mm[2]}\"); }\r\n//			header(\'Expires: \'.gmdate(\'D, d M Y H:i:s \\G\\M\\T\', time() + 604800));\r\n			echo $mm[3];\r\n		}\r\n	}\r\n	exit;\r\n}\r\n', 'if(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_dragdrop_tab\', \'1,2\');\r\n	register_tab(\"content\", \"aks_dragdrop_tab\", gTxt(\'tab_organise\').\" +\");\r\n	register_callback(\"aks_dragdrop_tab\", \"aks_dragdrop_tab\");\r\n	add_privs(\'aks_dragdrop_file\', \'1,2\');\r\n	register_callback(\"aks_dragdrop_file\", \"aks_dragdrop_file\");\r\n\r\n}\r\n\r\nfunction aks_dragdrop_tab($event, $step){\r\n	global $aks_msgerr, $aks_count, $aks_link, $aks_link_edit;\r\n	$categories = array(\"article\", \"link\", \"image\", \"file\");\r\n	if( empty($step) ){ $step = \"article\"; }\r\n	if( !in_array($step, $categories, TRUE) ){ return; }\r\n	$type = $step;\r\n	$tt = doSlash($type);\r\n	$tbl = \'txp_category\';		// FIXIT: hardcode txp_category\r\n\r\n	$aks_msgerr = \"\";\r\n	$message = \"\";\r\n	if( ps(\"save\") ){\r\n		$tree = @json_decode( ps(\'json1\'), TRUE);\r\n		if( !empty($tree) ){\r\n			$root_id = safe_field(\'id\', $tbl, \"name =\'root\' and type=\'{$tt}\'\");\r\n			aks_dragdrop__upd( $tree, $root_id, $type, $tbl);\r\n		}\r\n		$tree = @json_decode( ps(\'json2\'), TRUE);\r\n		if( !empty($tree) ){\r\n			aks_dragdrop__upd2( $tree, $type, $tbl);\r\n		}\r\n		$tree = @json_decode( ps(\'json3\'), TRUE);\r\n		if( !empty($tree) ){\r\n			aks_dragdrop__delete( $tree, $type, $tbl);\r\n		}\r\n\r\n		safe_update($tbl, \'lft=0, rgt=0\', \"type=\'{$tt}\'\");\r\n		rebuild_tree_full( $type );\r\n		$message = gTxt(\"{$type}_head\").\' \'.gTxt(\'saved\');\r\n	}\r\n\r\n	if( ps(\"savebulk\") ){\r\n		if( $bulk = preg_split(\'/[\\r\\n]+/s\', trim(ps(\'bulk\'))) ){\r\n			$added = array();\r\n			$parent = doSlash(ps(\'parent\'));\r\n			if( empty($parent) ){ $parent = \"root\"; }\r\n			foreach( $bulk as $title){\r\n				$title = trim($title);\r\n				$name = doSlash(strtolower(sanitizeForUrl($title))); if( empty($name) ){ continue; }\r\n				if( safe_column(\'id\', $tbl, \"type=\'{$tt}\' and name=\'{$name}\'\") ){\r\n					$aks_msgerr .= str_replace(\'{name}\', $title, gTxt(\"{$type}_category_already_exists\") ).\"\\n\";\r\n				}else{\r\n					$added[] = $title;\r\n					$title = doSlash($title);\r\n					safe_insert($tbl, \"name=\'{$name}\', type=\'{$tt}\', parent=\'$parent\', title=\'{$title}\' \");\r\n				}\r\n			}\r\n			if( !empty($added) ){\r\n				$message .= str_replace(\'{name}\', join(\', \', $added), gTxt(\"{$type}_category_created\") );\r\n				safe_update($tbl, \'lft=0, rgt=0\', \"type=\'{$tt}\'\");\r\n				rebuild_tree_full( $type );\r\n			}\r\n		}\r\n	}\r\n	if( $aks_msgerr ){ $aks_msgerr = \"<pre>$aks_msgerr</pre>\"; }\r\n	pagetop(gTxt(\'tab_organise\').\" +\", $message);\r\n\r\n	if( $type == \'article\' ){\r\n		$aks_link = \"?event=list&search_method=categories&crit=\";\r\n		$rs = getRows(\'select category, count(*) as num from (\'.\r\n			\'select ID, Category1 as category from \'.safe_pfx(\'textpattern\').\r\n			\' union \'.\r\n			\'select ID, Category2 as category from \'.safe_pfx(\'textpattern\').\r\n			\') as t where category != \"\" group by category\');\r\n	}else{\r\n		$aks_link = \"?event={$type}&search_method=category&crit=\";\r\n		$rs = safe_rows(\'category, count(*) as num\', \"txp_{$type}\", \"1 group by category\");\r\n\r\n	}\r\n	$aks_link_edit = \"?event=category&step=cat_{$type}_edit&id=\";\r\n	$aks_count = array();\r\n	foreach( $rs as $r ){\r\n		$aks_count[$r[\'category\']] = $r[\'num\'];\r\n	}\r\n\r\n	$list1 = aks_dragdrop_list(\'root\', $type, \'1=1\', $tbl);\r\n	if( $rs = safe_rows(\'id,name,title\', $tbl, \"type=\'{$tt}\' and lft=0 and rgt=0\") ){\r\n		$list2 = \"<ol class=\'dd-list\'>\";\r\n		foreach( $rs as $r ){ extract($r);\r\n			if( !empty($aks_count[$name]) ){\r\n				$count = \"<div class=\'aks_count\'><a href=\'{$aks_link}{$name}\'>{$aks_count[$name]}</a></div>\";\r\n			}else{	$count = \"\"; }\r\n			$list2 .= \"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div></li>\";\r\n		}	$list2 .= \"</ol>\";\r\n	}else{\r\n		$list2 = \"<div class=\'dd-empty\'></div>\";\r\n	}\r\n\r\n	$cats = \"\";\r\n	foreach( $categories as $cat ){\r\n		$cats.= \"<option value=\'{$cat}\'\".(($type==$cat)? \" selected=\'selected\'\":\"\").\">\".gTxt(\"{$cat}_head\").\"</option>\";\r\n	}\r\n	$html = \'<textarea id=\"nestable-output\" name=\"json1\" style=\"display:none;\"></textarea><textarea id=\"nestable2-output\" name=\"json2\" style=\"display:none;\"></textarea><textarea id=\"nestable3-output\" name=\"json3\" style=\"display:none;\"></textarea>\';\r\n	$form = form(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_dragdrop_tab\").sInput($type).$html);\r\n\r\n	$title = gTxt(\"{$type}_head\");\r\n	$title2 = $title.\" (\".gTxt(\"hidden\").\")\";\r\n	$title3 = gTxt(\'delete\');\r\n	$tcount = gTxt($type);\r\n\r\n	$rs = getTree(\'root\', $type);\r\n	array_unshift($rs, array(\'name\'=>\'aks_dragdrop-hide\', \'title\'=>$title2, \'level\'=>0 ));\r\n	$parent = gTxt(\'parent\').treeSelectInput(\'parent\', $rs, \"aks_dragdrop-hide\", \'category_parent\');\r\n	$htmlbulk = \"<textarea name=\'bulk\'></textarea>{$parent}\";\r\n	$formbulk = form($htmlbulk.fInput(\"submit\",\"savebulk\",gTxt(\"save_button\"),\"publish aks_rr\").eInput(\"aks_dragdrop_tab\").sInput($type));\r\n\r\n\r\necho <<<FORM\r\n	<link type=\"text/css\" href=\"?event=aks_dragdrop_file&step=css\" rel=\"stylesheet\" />\r\n	<script type=\"text/javascript\" src=\"?event=aks_dragdrop_file&step=js\"></script>\r\n	{$aks_msgerr}\r\n\r\n<table id=\"aks_tbl\"><tr><td>\r\n	<menu id=\"nestable-menu\" style=\"padding: 0 10px 10px 10px;\">\r\n		<div style=\"float:right; margin-left:10px;\">{$form}</div>\r\n		<div class=\"aks_rr\">\r\n			<button type=\"button\" data-action=\"expand-all\">Expand All</button>\r\n			<button type=\"button\" data-action=\"collapse-all\">Collapse All</button>\r\n		</div>\r\n		<form method=\"get\" action=\"index.php\"><input type=\"hidden\" value=\"aks_dragdrop_tab\" name=\"event\" />\r\n			<select name=\"step\" onchange=\"submit(this.form);\" style=\"font-size: 18px;\">{$cats}</select>\r\n		</form>\r\n	</menu>\r\n	<div class=\"dd\" id=\"nestable\"><div>{$title}<span class=\"aks_rr\">{$tcount}</span></div>{$list1}</div>\r\n</td><td>\r\n	<div id=\"aks_bulk\"><h3><a class=\"on_off\" rel=\"bulkinsert\" href=\"#\">Bulk Insert categories</a></h3>\r\n		<div class=\"aks_hide\" id=\"bulkinsert\"><h3>{$title} <span style=\'color: #996633;\' title=\"Add new categories, one per line\">[?]</span></h3>\r\n			{$formbulk}\r\n		</div>\r\n	</div>\r\n	<div class=\"dd\" id=\"nestable2\"><div>{$title2}<span class=\"aks_rr\">{$tcount}</span></div>{$list2}</div>\r\n	<div class=\"dd\" id=\"nestable3\"><div>{$title3}<span class=\"aks_rr\">{$tcount}</span></div><div class=\'dd-empty\'></div></div>\r\n</td></tr></table>\r\n\r\n\r\n<script type=\"text/javascript\">\r\n$(document).ready(function() {\r\n	var updateOutput = function(e){\r\n		var list   = e.length ? e : $(e.target),\r\n			output = list.data(\'output\');\r\n		if (window.JSON) {\r\n			output.val(window.JSON.stringify(list.nestable(\'serialize\')));//, null, 2));\r\n		} else {\r\n			output.val(\'JSON browser support required for this demo.\');\r\n		}\r\n	};\r\n\r\n	$(\'#nestable\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable\').data(\'output\', $(\'#nestable-output\')));\r\n\r\n	$(\'#nestable2\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable2\').data(\'output\', $(\'#nestable2-output\')));\r\n\r\n	$(\'#nestable3\').nestable({ group: 1 })	.on(\'change\', updateOutput);\r\n	updateOutput($(\'#nestable3\').data(\'output\', $(\'#nestable3-output\')));\r\n\r\n	$(\'#nestable-menu\').on(\'click\', function(e){\r\n		var target = $(e.target),\r\n			action = target.data(\'action\');\r\n		if (action === \'expand-all\') {\r\n			$(\'.dd\').nestable(\'expandAll\');\r\n		}\r\n		if (action === \'collapse-all\') {\r\n			$(\'.dd\').nestable(\'collapseAll\');\r\n		}\r\n	});\r\n\r\n	$(\'.on_off\').click(function(){\r\n		var child = $(\'#\'+ $(this).attr(\"rel\"));\r\n		child.toggleClass(\"aks_hide\");\r\n		if( child.hasClass(\"aks_hide\") ){\r\n			$(this).removeClass(\'on\').addClass(\'off\');\r\n		}else{\r\n			$(this).removeClass(\'off\').addClass(\'on\');\r\n		}\r\n		return false;\r\n	});\r\n\r\n});\r\n</script>\r\nFORM;\r\n}\r\n\r\n\r\nfunction aks_dragdrop__upd( $tree, $root_id, $type, $tbl){\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$rid	= doSlash($root_id);\r\n		$tt	= doSlash($type);\r\n		$q = \"update \".safe_pfx($tbl).\" c1, \".safe_pfx($tbl).\" c2 set c1.parent=c2.name where c1.type=\'{$tt}\' and c1.id=\'{$id}\' and c2.id=\'{$rid}\'\";\r\n		safe_query($q);\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__upd( $a[\'children\'], $a[\'id\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop__upd2( $tree, $type, $tbl){\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$tt	= doSlash($type);\r\n		safe_update($tbl, \"parent=\'aks_dragdrop-hide\'\", \"type=\'{$tt}\' and id=\'{$id}\'\");\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__upd2( $a[\'children\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop__delete( $tree, $type, $tbl){\r\n	global $aks_msgerr;\r\n	foreach( $tree as $a){\r\n		$id	= doSlash($a[\'id\']);\r\n		$tt	= doSlash($type);\r\n		if( $rs = safe_row(\'name,title\', $tbl, \"id =\'{$id}\'\") ){\r\n			$name	= doSlash($rs[\'name\']);\r\n			if( $type == \'article\' ){\r\n				$exist = safe_field(\"id\", \"textpattern\", \"category1 =\'{$name}\' or category2 =\'{$name}\'\");\r\n				$link = \"?event=list&search_method=categories&crit={$name}\";\r\n			}else{\r\n				$exist = safe_field(\"id\", \"txp_{$tt}\", \"category =\'{$name}\'\");\r\n				$link = \"?event={$type}&search_method=category&crit={$name}\";\r\n			}\r\n			if( $exist ){\r\n				$title	= txpspecialchars($rs[\'title\']);\r\n				$aks_msgerr .= \"Category `{$title}` is not empty, see <a href=\'{$link}\'>here</a>\\n\";\r\n			}else{\r\n				safe_delete($tbl, \"id=\'{$id}\'\");\r\n			}\r\n		}\r\n		if( !empty($a[\'children\']) ){\r\n			aks_dragdrop__delete( $a[\'children\'], $type, $tbl);\r\n		}\r\n	}\r\n}\r\n\r\nfunction aks_dragdrop_list($root, $type, $where=\'1=1\', $tbl=\'txp_category\'){\r\n	global $aks_count, $aks_link, $aks_link_edit;\r\n	$rs = getTree($root, $type, $where, $tbl);\r\n	if( $rs ){\r\n		$out = \"\"; $lev = 0;\r\n		foreach( $rs as $a ){ extract($a);\r\n			while( $lev>$level ){ $out.=\"</ol></li>\"; $lev--; }\r\n			$lev = $level;\r\n			if( !empty($aks_count[$name]) ){\r\n				$count = \"<div class=\'aks_count\'><a href=\'{$aks_link}{$name}\'>{$aks_count[$name]}</a></div>\";\r\n			}else{	$count = \"\"; }\r\n\r\n			if( empty($children) ){\r\n				$out.=\"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div></li>\";\r\n			}else{\r\n				$out.=\"<li class=\'dd-item\' data-id=\'{$id}\'>{$count}<div class=\'dd-handle\'><a class=\'dd-nodrag\' href=\'{$aks_link_edit}{$id}\'>{$title}</a></div><ol class=\'dd-list\'>\";\r\n			}\r\n		}\r\n		while( $lev>0 ){ $out.=\"</ol></li>\"; $lev--; }\r\n		return \"<ol class=\'dd-list\'>{$out}</ol>\";\r\n	}\r\n	return \"<div class=\'dd-empty\'></div>\";\r\n}\r\n\r\nfunction aks_dragdrop_file($event, $step){\r\n	$help = safe_field(\'help\', \'txp_plugin\', \"name =\'aks_dragdrop\'\");\r\n	if( in_array($step, array(\'css\', \'js\'), TRUE) ){\r\n		if( preg_match(\'%<!--aks_dragdrop_\'.$step.\'(_\"(.*?)\")?(.*?)aks_dragdrop_\'.$step.\'-->%is\', $help, $mm) ){\r\n			if( !empty($mm[2]) ){ header(\"Content-Type: {$mm[2]}\"); }\r\n//			header(\'Expires: \'.gmdate(\'D, d M Y H:i:s \\G\\M\\T\', time() + 604800));\r\n			echo $mm[3];\r\n		}\r\n	}\r\n	exit;\r\n}\r\n', '622d3db449900b53f4c5d8a6ecec6a7d', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_slimpattern', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wetslimpattern-integrate-slimstat-with-textpattern', '0.4.1', 'Integrates SlimStat or Mint logging into Textpattern.', '<h3>SlimStat and Mint to Textpattern integration.</h3>\n\n<h4>Usage</h4>\n\n<p>Install. Enable. Enjoy.</p>\n\n<h4>Customizing (Kind of&#8230;)</h4>\n\n<p>By default, this plugin ignores hits to all feed <span class=\"caps\">URL</span>s (<code>/rss</code>, <code>/atom</code>, and the like). You can change this behaviour as well as add custom <span class=\"caps\">URL</span>s to be ignored by modifying the array named <code>$ignore</code> inside the plugin&#8217;s code.</p>\n\n<p>This plugin assumes that the SlimStat logging code <code>inc.stats.php</code> is installed in a folder named <code>/slimstat</code> right under the website&#8217;s root, while Mint logging code <code>auto.php</code> is expected in <code>/mint/config</code>. Change that paths accordingly if your SlimStat or Mint installation path differs by directly editing the plugin and adapting the <code>$statslogger</code> string according to your website&#8217;s situation.</p>\n\n<p>Visit <a href=\"http://wettone.com/code/slimstat\" rel=\"nofollow\">http://wettone.com/code/slimstat</a> for SlimStat documentation, support and downloadable files. Mint is available at <a href=\"http://haveamint.com\" rel=\"nofollow\">HaveAMint.com</a>.</p>\n\n<h4>Licence</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\n// RegExp patterns of URLs/queries to ignore. Just add or remove.\n$ignore = \'/\\/&|^\\/rss\\/|^\\/rss$|^\\/atom\\/|^\\/atom$|\\?rss=1|\\?atom=1/\';\n\n# Actual logging code resides here\n$statslogger = \"/slimstat/inc.stats.php\";\n# remove the # prepended to the next line to enable Mint integration.\n# $statslogger = \"/mint/config/auto.php\");\n\nif(txpinterface == \'public\' && !preg_grep ($ignore, array($_SERVER[\'REQUEST_URI\'], $_SERVER[\'QUERY_STRING\']))) {\n 	@include_once ($_SERVER[\"DOCUMENT_ROOT\"].$statslogger);\n}\n', '\n// RegExp patterns of URLs/queries to ignore. Just add or remove.\n$ignore = \'/\\/&|^\\/rss\\/|^\\/rss$|^\\/atom\\/|^\\/atom$|\\?rss=1|\\?atom=1/\';\n\n# Actual logging code resides here\n$statslogger = \"/slimstat/inc.stats.php\";\n# remove the # prepended to the next line to enable Mint integration.\n# $statslogger = \"/mint/config/auto.php\");\n\nif(txpinterface == \'public\' && !preg_grep ($ignore, array($_SERVER[\'REQUEST_URI\'], $_SERVER[\'QUERY_STRING\']))) {\n 	@include_once ($_SERVER[\"DOCUMENT_ROOT\"].$statslogger);\n}\n', '651152c4cd2144c4ee05cfb817a068c8', '', '', 0, 5, 0),
('zem_contact_lang-de', 0, 'TXP Community', 'http://forum.textpattern.com/viewtopic.php?id=19823', '4.0.3.6', 'Language plug-in for Zem Contact Reborn - Deutsch', '<h1>Deutsches Sprachplugin fuer zem_contact</h1>', 'function zem_contact_gTxt($what, $var1 = \'\', $var2 = \'\')\n{\n	$lang = array(\n		\'checkbox\'		=> \'Auswahl\',\n		\'contact\'		=> \'Kontakt\',\n		\'email\'			=> \'E-Mail\',\n		\'email_subject\'	=> \"$var1 > Anfrage\",\n		\'email_thanks\'	=> \'Vielen Dank. Ihre Nachricht wurde versandt.\',\n		\'field_missing\'	=> \"Eingabe erforderlich, &#8220;<strong>$var1</strong>&#8221; fehlt.\",\n		\'form_expired\'	=> \'Es tut uns leid, aber Sie haben zu lange gewartet. Bitte wiederholen Sie Ihre Eingabe.\',\n		\'form_used\'		=> \'Dieses Formular wurde bereits verwendet. Bitte laden Sie die Seite neu und f&#252;llen Sie ein neues Formular aus.\',\n		\'general_inquiry\'=> \'Allgemeine Anfrage\',\n		\'invalid_email\'	=> \"&#8220;<strong>$var1</strong>&#8221; ist keine g&#252;ltige E-Mail-Adresse.\",\n		\'invalid_host\'	=> \"&#8220;<strong>$var1</strong>&#8221; ist kein g&#252;ltiger E-Mail-Server.\",\n		\'invalid_utf8\'	=> \"&#8220;<strong>$var1</strong>&#8221; enth&#228;lt ung&#252;ltige UTF-8-Zeichen.\",\n		\'invalid_value\'	=> \"Ung&#252;ltige Eingabe &#8220;<strong>$var1</strong>&#8221;, &#8220;<strong>$var2</strong>&#8221; ist keine erlaubte Eingabe.\",\n		\'mail_sorry\'	=> \'Es tut uns leid, aber wegen technischer Schwierigkeiten konnte die Nachricht nicht versandt werden.\',\n		\'message\'		=> \'Nachricht\',\n		\'min_warning\'	=> \"&#8220;<strong>$var1</strong>&#8221; muss mindestens $var2 Zeichen lang sein.\",\n		\'max_warning\'	=> \"&#8220;<strong>$var1</strong>&#8221; darf nicht l&#228;nger als $var2 Zeichen sein.\",\n		\'name\'			=> \'Name\',\n		\'option\'		=> \'Option\',\n		\'radio\'			=> \'Auswahl\',\n		\'receiver\'		=> \'Empf&#228;nger\',\n		\'recipient\' 	=> \'Empf&#228;nger\',\n 		\'refresh\'		=> \'Klicken Sie auf diesen Link, wenn die Seite nicht automatisch neu geladen wird.\',\n		\'secret\'		=> \'Geheimnis\',\n		\'send\'			=> \'Senden\',\n		\'send_article\'	=> \'Artikel senden\',\n		\'spam\'			=> \'Spam-Mail wird nicht akzeptiert.\',\n		\'text\'			=> \'Text\',\n		\'to_missing\'	=> \'Die E-Mail-Adresse des &#8220;<strong>Empf&#228;ngers</strong>&#8221; fehlt.\',\n 		\'version\'		=> \'4.0.3.6\'\n	);\n\n	return $lang[$what];\n}', 'function zem_contact_gTxt($what, $var1 = \'\', $var2 = \'\')\n{\n	$lang = array(\n		\'checkbox\'		=> \'Auswahl\',\n		\'contact\'		=> \'Kontakt\',\n		\'email\'			=> \'E-Mail\',\n		\'email_subject\'	=> \"$var1 > Anfrage\",\n		\'email_thanks\'	=> \'Vielen Dank. Ihre Nachricht wurde versandt.\',\n		\'field_missing\'	=> \"Eingabe erforderlich, &#8220;<strong>$var1</strong>&#8221; fehlt.\",\n		\'form_expired\'	=> \'Es tut uns leid, aber Sie haben zu lange gewartet. Bitte wiederholen Sie Ihre Eingabe.\',\n		\'form_used\'		=> \'Dieses Formular wurde bereits verwendet. Bitte laden Sie die Seite neu und f&#252;llen Sie ein neues Formular aus.\',\n		\'general_inquiry\'=> \'Allgemeine Anfrage\',\n		\'invalid_email\'	=> \"&#8220;<strong>$var1</strong>&#8221; ist keine g&#252;ltige E-Mail-Adresse.\",\n		\'invalid_host\'	=> \"&#8220;<strong>$var1</strong>&#8221; ist kein g&#252;ltiger E-Mail-Server.\",\n		\'invalid_utf8\'	=> \"&#8220;<strong>$var1</strong>&#8221; enth&#228;lt ung&#252;ltige UTF-8-Zeichen.\",\n		\'invalid_value\'	=> \"Ung&#252;ltige Eingabe &#8220;<strong>$var1</strong>&#8221;, &#8220;<strong>$var2</strong>&#8221; ist keine erlaubte Eingabe.\",\n		\'mail_sorry\'	=> \'Es tut uns leid, aber wegen technischer Schwierigkeiten konnte die Nachricht nicht versandt werden.\',\n		\'message\'		=> \'Nachricht\',\n		\'min_warning\'	=> \"&#8220;<strong>$var1</strong>&#8221; muss mindestens $var2 Zeichen lang sein.\",\n		\'max_warning\'	=> \"&#8220;<strong>$var1</strong>&#8221; darf nicht l&#228;nger als $var2 Zeichen sein.\",\n		\'name\'			=> \'Name\',\n		\'option\'		=> \'Option\',\n		\'radio\'			=> \'Auswahl\',\n		\'receiver\'		=> \'Empf&#228;nger\',\n		\'recipient\' 	=> \'Empf&#228;nger\',\n 		\'refresh\'		=> \'Klicken Sie auf diesen Link, wenn die Seite nicht automatisch neu geladen wird.\',\n		\'secret\'		=> \'Geheimnis\',\n		\'send\'			=> \'Senden\',\n		\'send_article\'	=> \'Artikel senden\',\n		\'spam\'			=> \'Spam-Mail wird nicht akzeptiert.\',\n		\'text\'			=> \'Text\',\n		\'to_missing\'	=> \'Die E-Mail-Adresse des &#8220;<strong>Empf&#228;ngers</strong>&#8221; fehlt.\',\n 		\'version\'		=> \'4.0.3.6\'\n	);\n\n	return $lang[$what];\n}', '09977748815e19014e777f01043577fe', '', '', 0, 5, 0),
('yab_download', 0, 'Tommy Schmucker', 'http://www.yablo.de/', '0.7', 'Allows you to offer a download of multiple files with or without a confirmation button and an option to zip all files on the fly.', '<h1>yab_download</h1>\n\n<p>This plugin allows you to offer a download of multiple files (per file id and/or category) with or without a confirmation button. Additional you can offer an option to download all the files as zip archive on the fly. The names of the files are read from the files description. If no description is given the filenames are displayed.</p>\n\n<h2>Usage</h2>\n\n<p>This plugin has only one tag: <code>&lt;txp:yab_download /&gt;</code>. You have to place it in an individual article.<br />\nThe following attributes are available:</p>\n\n<ul>\n	<li><strong>files</strong>: The IDs of the files, which are for download (seperated by comma). If no files assigned with an ID an error message will displayed in the option field.</li>\n	<li><strong>label1</strong>: Label for select</li>\n	<li><strong>label2</strong>: Label for confirmation checkbox</li>\n	<li><strong>label3</strong>: Label for the submit input</li>\n	<li><strong>label4</strong>: Label for zip download checkbox</li>\n	<li><strong>notice</strong>: Error notice, if confirmation is not set but required</li>\n	<li><strong>size</strong>: Display filesize additionally (values are B, KB,MB, GB,PB).</li>\n	<li><strong>confirm</strong> : Form used with a confirmation checkbox (values: &#8216;1&#8217; or some other string)</li>\n	<li><strong>formid</strong>: html id of the element form, used for anchor, if you use more than one <txp:yab_download /> in a single site, make different entries here; (default <code>yab_cd_download</code>)</li>\n	<li><strong>cats</strong>: File categories you will add to download (seperated by comma); will additional displayed with files from <code>files</code> attribute above</li>\n	<li><strong>exclude</strong>: IDs of the files you will exclude; Does only work with <code>cats</code> attribute.</li>\n	<li><strong>sort</strong>: Sort the files from <code>cats</code>; (defaul <code>description asc</code>)</li>\n	<li><strong>zip</strong>: Offfers an checkbox with an option to download all offered files as zip instead (values: &#8216;1&#8217; for zip creation; &#8216;2&#8217; to force zip creation in memory)</li>\n	<li><strong>zipname</strong>: the name of the offerd zip (default all.zip)</li>\n	<li><strong>hidelist</strong>: hides the select list of the files, usefull if you offer only one file or offer many files zipped without a seletion (values: &#8216;1&#8217;)</li>\n	<li><strong>hidezip</strong>: hides the zip checkbox (download as zip is forced), useful if you offer only one file as zip or many files as zip wihtout a file selection; can be combined with &#8216;hidelist&#8217;, so only a download button will be displayed (values: &#8216;1&#8217;)</li>\n</ul>\n\n<h2>Example</h2>\n\n<p><strong>simple</strong></p>\n\n<p><code>&lt;txp:yab_download files=&quot;1,2,3,4&quot; /&gt;</code></p>\n\n<p><strong>full</strong></p>\n\n<p><code>&lt;txp:yab_download files=&quot;1,2,3,4&quot; label1=&quot;Choose your download&quot; label2=&quot;Accept Download at your own risk&quot; label3=&quot;Klick here for download&quot; label4=&quot;Yeah give me all as zip&quot; notice=&quot;You have to accept the condition below&quot; size=&quot;MB&quot; formid=&quot;form-no-1&quot; confirm=&quot;1&quot; cats=&quot;category1, category2&quot; exclude=&quot;5,7&quot; sort=&quot;filename desc&quot; zip=&quot;1&quot; zipname=&quot;archive.zip&quot; /&gt;</code></p>\n\n<p>The full example above will show you a form with two checkboxes. First one offers an option to download all the files zipped. The second checkbox is a confirmation checkbox.<br />\nThe offered files will be the files with the file id #1, #2, #3, #4 and all files of the categories category1 and category2. If in category1 or category2 a file with the file id #5 and #7 so these will be excluded from download. The zipped file will be named as archive.zip.</p>\n\n<h2>Note on zip creation</h2>\n\n<p>If you are running a php version 5.2.0 or above so the attribute <code>zip=&quot;1&quot;</code> will try to create the zip on harddisk instead in memory. If you have php 5.2.0 or above and you have problems with some restrictions like safe_mode, open_basedir etc., so try the attribute <code>zip=&quot;2&quot;</code>. This will force the zip creation in memory.</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n	h1, h2, h3\n	h1 code, h2 code, h3 code {\n		margin-bottom: 0.6em;\n		font-weight: bold\n	}\n	h1 {\n		font-size: 1.4em\n	}\n	h2 {\n		font-size: 1.25em\n	}\n	h3 {\n		margin-bottom: 0;\n		font-size: 1.1em\n	}\n	table {\n		margin-bottom: 1em\n	}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_download\');\n}\n\n/**\n *\n * Displays the download form\n */\nfunction yab_download($atts)\n{\n	extract(lAtts\n		(array(\n			\'label1\'		=> \'Files\',\n			\'label2\'		=> \'Please accept this condition!\',\n			\'label3\'		=> \'Download\',\n			\'label4\'		=> \'Download all as ZIP instead\',\n			\'files\'	 		=> \'\',\n			\'size\'			=> \'\',\n			\'notice\'		=> \'You have to accept the condition below for download.\',\n			\'formid\'		=> \'yab_cd_download\',\n			\'confirm\' 	=> \'\',\n			\'cats\'			=> \'\',\n			\'exclude\' 	=> \'\',\n			\'sort\'			=> \'description desc\',\n			\'zip\'				=> \'\',\n			\'zipname\'		=> \'all.zip\',\n			\'hidelist\'	=> \'\',\n			\'hidezip\'		=> \'\'\n			),$atts\n		)\n	);\n\n	$select = \'\';\n	$out = \'\';\n	$notice_out = \'\';\n	$filesizes = \'\';\n\n	global $thisarticle;\n\n	$url_title = $thisarticle[\'url_title\'];\n	$section = $thisarticle[\'section\'];\n	$posted = $thisarticle[\'posted\'];\n	$id = $thisarticle[\'thisid\'];\n\n	$purl = permlinkurl(array(\n		\'title\'		 => \'\',\n		\'url_title\' => $url_title,\n		\'section\'	 => $section,\n		\'posted\'		=> $posted,\n		\'thisid\'		=> $id\n		));\n\n	// create empty array for zip\n	if ($zip != \'\')\n	{\n		$z_filenames_array = array();\n	}\n\n	// look for given files in attribute @files@ and create option fields\n	if ($files !== \'\')\n	{\n		$files_array = explode(\',\',$files);\n		foreach ($files_array as $file)\n		{\n			$file = trim($file);\n			$thisfile = fileDownloadFetchInfo(\'id = \'.intval($file));\n			if ($size != \'\')\n			{\n				$filesizes = \' [\'.yab_cd_size($thisfile[\'size\'], $size).\']\';\n			}\n			$select .= \'<option value=\"\'.$file.\'\">\'.yab_cd_description($file).$filesizes.\'</option>\'.n;\n\n			// fill array with filnames (file) for zip\n			$z_filenames_array[] = $thisfile[\'filename\'];\n		}\n	}\n\n	// if attribute @cats@ is given create additional option fields with files from @cats@\n	if ($cats != \'\')\n	{\n		$catsfiles = yab_cd_catfiles($cats, $sort, $size, $exclude);\n		foreach ($catsfiles as $catsfile => $fileinfo)\n		{\n			if ($size != \'\')\n			{\n				$filesizes = \' [\'.$fileinfo[\'size\'].\']\';\n			}\n			$select .= \'<option value=\"\'.$catsfile.\'\">\'.yab_cd_description($catsfile).$filesizes.\'</option>\'.n;\n\n			// fill array with filnames (cat) for zip\n			$thisfile = fileDownloadFetchInfo(\'id = \'.intval($catsfile));\n			$z_filenames_array[] = $thisfile[\'filename\'];\n		}\n	}\n\n	// is form is submitted and confirmation checkbox needed\n	if (ps(\'yab_cd_submit\') != \'\' and $confirm != \'\')\n	{\n		// if conf checkbox and no zip download\n		if (ps(\'check\') and ps(\'check2\') == \'\')\n		{\n			$did = yab_cd_sanitize($_POST[\'files\']);\n			$durl = file_download_link(array(\'id\' => $did),\'\');\n\n			$out .= header(\'Content-Type: application/octet-stream\');\n			$out .= header(\'Content-Transfer-Encoding: binary\');\n			$out .= header(\'Location: \'.$durl);\n		}\n		// if conf checkbox and zip download\n		elseif (ps(\'check\') and ps(\'check2\') != \'\')\n		{\n			yab_zip_files($z_filenames_array, $zipname, $zip);\n		}\n		// no confirmation checkbox throw messge\n		else\n		{\n			$notice_out .= graf($notice, \' class=\"notice\"\').n;\n		}\n	}\n	// is form submitted and no confirmation checkbox needed\n	elseif (ps(\'yab_cd_submit\') != \'\' and $confirm == \'\')\n	{\n		// zip download\n		if (ps(\'check2\') != \'\')\n		{\n			yab_zip_files($z_filenames_array, $zipname, $zip);\n		}\n		// single file download\n		else\n		{\n			$did = yab_cd_sanitize(ps(\'files\'));\n			$durl = file_download_link(array(\'id\' => $did));\n\n			$out .= header(\'Content-Type: application/octet-stream\');\n			$out .= header(\'Content-Transfer-Encoding: binary\');\n			$out .= header(\'Location: \'.$durl);\n		}\n	}\n\n	// create confirmation checkbox\n	$c_checkbox = \'\';\n	if ($confirm != \'\')\n	{\n		$c_checkbox .= \'<p class=\"yab_cd_checkbox\"><input type=\"checkbox\" value=\"1\" name=\"check\" id=\"\'.$formid.\'-check\" /><label for=\"\'.$formid.\'-check\">\'.$label2.\'</label></p>\'.n;\n	}\n\n	// create zip checkbox\n	$zipcats_checkbox = \'\';\n	if ($zip != \'\')\n	{\n		// hide or show zip checkbox\n		if ($hidezip == \'\')\n		{\n			$zipcats_checkbox .= \'<p class=\"yab_cd_zipcats\"><input type=\"checkbox\" value=\"1\" name=\"check2\" id=\"\'.$formid.\'-check2\" /><label for=\"\'.$formid.\'-check2\">\'.$label4.\'</label></p>\'.n;\n		}\n		else\n		{\n			$zipcats_checkbox .= \'<input type=\"hidden\" value=\"1\" name=\"check2\" id=\"\'.$formid.\'-check2\" />\'.n;\n		}\n	}\n\n	// hide or show files select list\n	if ($hidelist == \'\')\n	{\n		$option_list = \'<p class=\"yab_cd_select\">\'.n;\n		$option_list .= \'<label for=\"\'.$formid.\'-files\">\'.$label1.\'</label><select name=\"files\" id=\"\'.$formid.\'-files\">\'.n;\n		$option_list .= $select;\n		$option_list .= \'</select>\'.n;\n		$option_list .= \'</p>\'.n;\n	}\n	else\n	{\n		if  (!isset($file))\n		{\n			$file = \'\';\n		}\n		$option_list = \'<input type=\"hidden\" name=\"files\" id=\"\'.$formid.\'-files\" value=\"\'.$file.\'\" />\'.n;\n	}\n\n	// create download form\n	$out .= \'<form method=\"post\" action=\"\'.$purl.\'#\'.$formid.\'\" id=\"\'.$formid.\'\">\'.n;\n	$out .= \'<div>\'.n;\n	$out .= $notice_out;\n	$out .= $option_list;\n	$out .= $zipcats_checkbox;\n	$out .= $c_checkbox;\n	$out .= \'<input type=\"submit\" value=\"\'.$label3.\'\" name=\"yab_cd_submit\" class=\"yab_cd_submit\" />\'.n;\n	$out .= \'</div>\'.n;\n	$out .= \'</form>\'.n;\n\n	return $out;\n}\n\n/**\n *\n * Creates a zipfile vom given filenames array and force to download it.\n *\n * @param array $z_filenames_array Array of filenames with file id as $val\n * @param string $zipname Name of the created zipfile, given from attribute @zipname@\n * @param string $zip zip-modus (\'2\' to force creation in memory)\n * @return Force download of the zipfile with header modifications\n */\nfunction yab_zip_files($z_filenames_array, $zipname, $zip)\n{\n	global $prefs;\n	$path = $prefs[\'file_base_path\'];\n	$tmpdir = $prefs[\'tempdir\'];\n\n	// test for zlib (php5.2); create zip on disk\n	if (version_compare(phpversion(), \'5.2.0\', \'>=\') and class_exists(\'ZipArchive\') and $zip != \'2\')\n	{\n		// start new zipfile instance\n		$tmpfile = tempnam($tmpdir,\'zip\');\n		$zipfile = new ZipArchive();\n\n		if ($zipfile->open($tmpfile, constant(\'ZipArchive::OVERWRITE\')) === true)\n		{\n			// add every file from array to zipfile\n			foreach ($z_filenames_array as $key => $val)\n			{\n				$z_filename = $path.\'/\'.$val;\n				$zipfile->addFile($z_filename,$val);\n			}\n			$zipfile->close();\n			@ob_end_clean();\n			// create headers to force download\n			header(\'Content-Type: application/zip\');\n			header(\'Content-Length: \'.filesize($tmpfile));\n			header(\'Content-Disposition: attachment; filename=\"\'.$zipname.\'\"\');\n			// delete zipfile\n			readfile($tmpfile);\n			@unlink($tmpfile);\n			exit;\n		}\n	}\n	// create zip in memory\n	else\n	{\n		// start new zipfile instance\n		$zipfile = new zipfile();\n\n		// add every file from array to zipfile\n		foreach ($z_filenames_array as $key => $val)\n		{\n			$z_filename = $path.\'/\'.$val;\n			$handle = fopen($z_filename, \"r\");\n			$content = fread($handle, filesize($z_filename));\n			fclose ($handle);\n\n			$zipfile->addFile($content, $val);\n		}\n		$dump_buffer = $zipfile->file();\n		// create headers to force download\n		header(\'Content-Type: application/zip\');\n		header(\'Content-Length: \'.strlen($dump_buffer));\n		header(\'Content-Disposition: attachment; filename=\"\'.$zipname.\'\"\');\n		echo $dump_buffer;\n		exit;\n	}\n}\n\n/**\n *\n * Creates an array of fileinfo from files assigned with specific categories\n *\n * @param string $category Categories from attribute @cats@\n * @param string $sort Sort from attribute @sort@\n * @param string $format Unit of the filesize, attribute @size@\n * @param string $exclude Files to exclude from attribute @exclude@\n * @return array\n */\nfunction yab_cd_catfiles($category = \'\', $sort = \'\', $format = \'\', $exclude = \'\')\n{\n	// create SQL-query\n	$where = array(\'1=1\');\n	$where[] = \"category IN (\'\".join(\"\',\'\", doSlash(do_list($category))).\"\')\";\n	if ($exclude != \'\')\n	{\n		$where[] = \"id NOT IN (\'\".join(\"\',\'\", doSlash(do_list($exclude))).\"\')\";\n	}\n\n	$qparts = array(\'order by \'.doSlash($sort));\n\n	// and query\n	$rs = safe_rows_start(\'*\', \'txp_file\', join(\' and \', $where).\' \'.join(\' \', $qparts));\n\n	if ($rs)\n	{\n		$out = array();\n\n		// every row in db\n		while ($a = nextRow($rs))\n		{\n			$id = $a[\'id\'];\n			$filesize = $a[\'size\'];\n\n			$out[$id][\'link\'] = file_download_link(array(\'id\' => $id),\'\');\n			$out[$id][\'size\'] = yab_cd_size($filesize, $format);\n			$out[$id][\'description\'] = yab_cd_description($id);\n		}\n	}\n\n	return $out;\n}\n\n\n/**\n *\n * Calculated the filesize depending on given unit\n *\n * @param string $filesize Given filesize in Byte\n * @param string $format Unit of the calculated filesize\n * @return string Ready for output\n */\nfunction yab_cd_size($filesize, $format = \'\')\n{\n	$decimals = 2;\n	$size = $filesize;\n\n	if (!in_array($format, array(\'B\',\'KB\',\'MB\',\'GB\',\'PB\')))\n	{\n		$divs = 0;\n		while ($size >= 1024)\n		{\n			$size /= 1024;\n			$divs++;\n		}\n\n		switch ($divs)\n		{\n			case 1:\n				$format = \'KB\';\n				break;\n			case 2:\n				$format = \'MB\';\n				break;\n			case 3:\n				$format = \'GB\';\n				break;\n			case 4:\n				$format = \'PB\';\n				break;\n			case 0:\n				default:\n				$format = \'B\';\n				break;\n		}\n	}\n\n	switch ($format)\n	{\n		case \'KB\':\n			$size /= 1024;\n			break;\n		case \'MB\':\n			$size /= (1024*1024);\n			break;\n		case \'GB\':\n			$size /= (1024*1024*1024);\n			break;\n		case \'PB\':\n			$size /= (1024*1024*1024*1024);\n			break;\n		case \'B\':\n			default:\n			break;\n	}\n\n	return number_format($size, $decimals).$format;\n}\n\n/**\n *\n * Outputs filename or given filedescription if exists.\n *\n * @param string $fileid ID of the file\n * @return string Ready for output\n */\nfunction yab_cd_description($fileid)\n{\n	$thisfile = fileDownloadFetchInfo(\'id = \'.intval($fileid));\n\n	if ($thisfile)\n	{\n		if ($thisfile[\'description\'])\n		{\n			$out = htmlspecialchars($thisfile[\'description\']);\n		}\n		else\n		{\n			$out = htmlspecialchars($thisfile[\'filename\']);\n		}\n	}\n	else\n	{\n		$out = \'No file with an ID #\'.$fileid.\' available\';\n	}\n	return $out;\n}\n\n/**\n *\n * Small sanitization. Strips all non-numerics.\n *\n * @param string $in POSTed data\n * @return string\n */\nfunction yab_cd_sanitize($in)\n{\n	$out = preg_replace(\'/[^0-9]/\', \'\', $in);\n\n	return $out;\n}\n\n/**\n *\n * @version $Id: zip.lib.php 10240 2007-04-01 11:02:46Z cybot_tm $\n *\n *\n * Zip file creation class.\n * Makes zip files.\n *\n * Based on :\n *\n *	http://www.zend.com/codex.php?id=535&single=1\n *	By Eric Mueller <eric@themepark.com>\n *\n *	http://www.zend.com/codex.php?id=470&single=1\n *	by Denis125 <webmaster@atlant.ru>\n *\n *	a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified\n *	date and time of the compressed file\n *\n * Official ZIP file format: http://www.pkware.com/appnote.txt\n *\n * @access	public\n */\nclass zipfile\n{\n		/**\n		 * Array to store compressed data\n		 *\n		 * @var	array		$datasec\n		 */\n		var $datasec			= array();\n\n		/**\n		 * Central directory\n		 *\n		 * @var	array		$ctrl_dir\n		 */\n		var $ctrl_dir		 = array();\n\n		/**\n		 * End of central directory record\n		 *\n		 * @var	string	 $eof_ctrl_dir\n		 */\n		var $eof_ctrl_dir = \"\\x50\\x4b\\x05\\x06\\x00\\x00\\x00\\x00\";\n\n		/**\n		 * Last offset position\n		 *\n		 * @var	integer	$old_offset\n		 */\n		var $old_offset	 = 0;\n\n\n		/**\n		 * Converts an Unix timestamp to a four byte DOS date and time format (date\n		 * in high two bytes, time in low two bytes allowing magnitude comparison).\n		 *\n		 * @param	integer	the current Unix timestamp\n		 *\n		 * @return integer	the current date in a four byte DOS format\n		 *\n		 * @access private\n		 */\n		function unix2DosTime($unixtime = 0) {\n				$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);\n\n				if ($timearray[\'year\'] < 1980) {\n						$timearray[\'year\']		= 1980;\n						$timearray[\'mon\']		 = 1;\n						$timearray[\'mday\']		= 1;\n						$timearray[\'hours\']	 = 0;\n						$timearray[\'minutes\'] = 0;\n						$timearray[\'seconds\'] = 0;\n				} // end if\n\n				return (($timearray[\'year\'] - 1980) << 25) | ($timearray[\'mon\'] << 21) | ($timearray[\'mday\'] << 16) |\n								($timearray[\'hours\'] << 11) | ($timearray[\'minutes\'] << 5) | ($timearray[\'seconds\'] >> 1);\n		} // end of the \'unix2DosTime()\' method\n\n\n		/**\n		 * Adds \"file\" to archive\n		 *\n		 * @param	string	 file contents\n		 * @param	string	 name of the file in the archive (may contains the path)\n		 * @param	integer	the current timestamp\n		 *\n		 * @access public\n		 */\n		function addFile($data, $name, $time = 0)\n		{\n				$name		 = str_replace(\'\\\\\', \'/\', $name);\n\n				$dtime		= dechex($this->unix2DosTime($time));\n				$hexdtime = \'\\x\' . $dtime[6] . $dtime[7]\n									. \'\\x\' . $dtime[4] . $dtime[5]\n									. \'\\x\' . $dtime[2] . $dtime[3]\n									. \'\\x\' . $dtime[0] . $dtime[1];\n				eval(\'$hexdtime = \"\' . $hexdtime . \'\";\');\n\n				$fr	 = \"\\x50\\x4b\\x03\\x04\";\n				$fr	 .= \"\\x14\\x00\";						// ver needed to extract\n				$fr	 .= \"\\x00\\x00\";						// gen purpose bit flag\n				$fr	 .= \"\\x08\\x00\";						// compression method\n				$fr	 .= $hexdtime;						 // last mod time and date\n\n				// \"local file header\" segment\n				$unc_len = strlen($data);\n				$crc		 = crc32($data);\n				$zdata	 = gzcompress($data);\n				$zdata	 = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug\n				$c_len	 = strlen($zdata);\n				$fr			.= pack(\'V\', $crc);						 // crc32\n				$fr			.= pack(\'V\', $c_len);					 // compressed filesize\n				$fr			.= pack(\'V\', $unc_len);				 // uncompressed filesize\n				$fr			.= pack(\'v\', strlen($name));		// length of filename\n				$fr			.= pack(\'v\', 0);								// extra field length\n				$fr			.= $name;\n\n				// \"file data\" segment\n				$fr .= $zdata;\n\n				// \"data descriptor\" segment (optional but necessary if archive is not\n				// served as file)\n				// nijel(2004-10-19): this seems not to be needed at all and causes\n				// problems in some cases (bug #1037737)\n				//$fr .= pack(\'V\', $crc);								 // crc32\n				//$fr .= pack(\'V\', $c_len);							 // compressed filesize\n				//$fr .= pack(\'V\', $unc_len);						 // uncompressed filesize\n\n				// add this entry to array\n				$this -> datasec[] = $fr;\n\n				// now add to central directory record\n				$cdrec = \"\\x50\\x4b\\x01\\x02\";\n				$cdrec .= \"\\x00\\x00\";								// version made by\n				$cdrec .= \"\\x14\\x00\";								// version needed to extract\n				$cdrec .= \"\\x00\\x00\";								// gen purpose bit flag\n				$cdrec .= \"\\x08\\x00\";								// compression method\n				$cdrec .= $hexdtime;								 // last mod time & date\n				$cdrec .= pack(\'V\', $crc);					 // crc32\n				$cdrec .= pack(\'V\', $c_len);				 // compressed filesize\n				$cdrec .= pack(\'V\', $unc_len);			 // uncompressed filesize\n				$cdrec .= pack(\'v\', strlen($name)); // length of filename\n				$cdrec .= pack(\'v\', 0);						 // extra field length\n				$cdrec .= pack(\'v\', 0);						 // file comment length\n				$cdrec .= pack(\'v\', 0);						 // disk number start\n				$cdrec .= pack(\'v\', 0);						 // internal file attributes\n				$cdrec .= pack(\'V\', 32);						// external file attributes - \'archive\' bit set\n\n				$cdrec .= pack(\'V\', $this -> old_offset); // relative offset of local header\n				$this -> old_offset += strlen($fr);\n\n				$cdrec .= $name;\n\n				// optional extra field, file comment goes here\n				// save to central directory\n				$this -> ctrl_dir[] = $cdrec;\n		} // end of the \'addFile()\' method\n\n\n		/**\n		 * Dumps out file\n		 *\n		 * @return	string	the zipped file\n		 *\n		 * @access public\n		 */\n		function file()\n		{\n				$data		= implode(\'\', $this -> datasec);\n				$ctrldir = implode(\'\', $this -> ctrl_dir);\n\n				return\n						$data .\n						$ctrldir .\n						$this -> eof_ctrl_dir .\n						pack(\'v\', sizeof($this -> ctrl_dir)) .	// total # of entries \"on this disk\"\n						pack(\'v\', sizeof($this -> ctrl_dir)) .	// total # of entries overall\n						pack(\'V\', strlen($ctrldir)) .					 // size of central dir\n						pack(\'V\', strlen($data)) .							// offset to start of central dir\n						\"\\x00\\x00\";														 // .zip file comment length\n		} // end of the \'file()\' method\n\n} // end of the \'zipfile\' class', '/**\n *\n * This plugin is released under the GNU General Public License Version 2 and above\n * Version 2: http://www.gnu.org/licenses/gpl-2.0.html\n * Version 3: http://www.gnu.org/licenses/gpl-3.0.html\n */\n\nif (class_exists(\'\\Textpattern\\Tag\\Registry\'))\n{\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'yab_download\');\n}\n\n/**\n *\n * Displays the download form\n */\nfunction yab_download($atts)\n{\n	extract(lAtts\n		(array(\n			\'label1\'		=> \'Files\',\n			\'label2\'		=> \'Please accept this condition!\',\n			\'label3\'		=> \'Download\',\n			\'label4\'		=> \'Download all as ZIP instead\',\n			\'files\'	 		=> \'\',\n			\'size\'			=> \'\',\n			\'notice\'		=> \'You have to accept the condition below for download.\',\n			\'formid\'		=> \'yab_cd_download\',\n			\'confirm\' 	=> \'\',\n			\'cats\'			=> \'\',\n			\'exclude\' 	=> \'\',\n			\'sort\'			=> \'description desc\',\n			\'zip\'				=> \'\',\n			\'zipname\'		=> \'all.zip\',\n			\'hidelist\'	=> \'\',\n			\'hidezip\'		=> \'\'\n			),$atts\n		)\n	);\n\n	$select = \'\';\n	$out = \'\';\n	$notice_out = \'\';\n	$filesizes = \'\';\n\n	global $thisarticle;\n\n	$url_title = $thisarticle[\'url_title\'];\n	$section = $thisarticle[\'section\'];\n	$posted = $thisarticle[\'posted\'];\n	$id = $thisarticle[\'thisid\'];\n\n	$purl = permlinkurl(array(\n		\'title\'		 => \'\',\n		\'url_title\' => $url_title,\n		\'section\'	 => $section,\n		\'posted\'		=> $posted,\n		\'thisid\'		=> $id\n		));\n\n	// create empty array for zip\n	if ($zip != \'\')\n	{\n		$z_filenames_array = array();\n	}\n\n	// look for given files in attribute @files@ and create option fields\n	if ($files !== \'\')\n	{\n		$files_array = explode(\',\',$files);\n		foreach ($files_array as $file)\n		{\n			$file = trim($file);\n			$thisfile = fileDownloadFetchInfo(\'id = \'.intval($file));\n			if ($size != \'\')\n			{\n				$filesizes = \' [\'.yab_cd_size($thisfile[\'size\'], $size).\']\';\n			}\n			$select .= \'<option value=\"\'.$file.\'\">\'.yab_cd_description($file).$filesizes.\'</option>\'.n;\n\n			// fill array with filnames (file) for zip\n			$z_filenames_array[] = $thisfile[\'filename\'];\n		}\n	}\n\n	// if attribute @cats@ is given create additional option fields with files from @cats@\n	if ($cats != \'\')\n	{\n		$catsfiles = yab_cd_catfiles($cats, $sort, $size, $exclude);\n		foreach ($catsfiles as $catsfile => $fileinfo)\n		{\n			if ($size != \'\')\n			{\n				$filesizes = \' [\'.$fileinfo[\'size\'].\']\';\n			}\n			$select .= \'<option value=\"\'.$catsfile.\'\">\'.yab_cd_description($catsfile).$filesizes.\'</option>\'.n;\n\n			// fill array with filnames (cat) for zip\n			$thisfile = fileDownloadFetchInfo(\'id = \'.intval($catsfile));\n			$z_filenames_array[] = $thisfile[\'filename\'];\n		}\n	}\n\n	// is form is submitted and confirmation checkbox needed\n	if (ps(\'yab_cd_submit\') != \'\' and $confirm != \'\')\n	{\n		// if conf checkbox and no zip download\n		if (ps(\'check\') and ps(\'check2\') == \'\')\n		{\n			$did = yab_cd_sanitize($_POST[\'files\']);\n			$durl = file_download_link(array(\'id\' => $did),\'\');\n\n			$out .= header(\'Content-Type: application/octet-stream\');\n			$out .= header(\'Content-Transfer-Encoding: binary\');\n			$out .= header(\'Location: \'.$durl);\n		}\n		// if conf checkbox and zip download\n		elseif (ps(\'check\') and ps(\'check2\') != \'\')\n		{\n			yab_zip_files($z_filenames_array, $zipname, $zip);\n		}\n		// no confirmation checkbox throw messge\n		else\n		{\n			$notice_out .= graf($notice, \' class=\"notice\"\').n;\n		}\n	}\n	// is form submitted and no confirmation checkbox needed\n	elseif (ps(\'yab_cd_submit\') != \'\' and $confirm == \'\')\n	{\n		// zip download\n		if (ps(\'check2\') != \'\')\n		{\n			yab_zip_files($z_filenames_array, $zipname, $zip);\n		}\n		// single file download\n		else\n		{\n			$did = yab_cd_sanitize(ps(\'files\'));\n			$durl = file_download_link(array(\'id\' => $did));\n\n			$out .= header(\'Content-Type: application/octet-stream\');\n			$out .= header(\'Content-Transfer-Encoding: binary\');\n			$out .= header(\'Location: \'.$durl);\n		}\n	}\n\n	// create confirmation checkbox\n	$c_checkbox = \'\';\n	if ($confirm != \'\')\n	{\n		$c_checkbox .= \'<p class=\"yab_cd_checkbox\"><input type=\"checkbox\" value=\"1\" name=\"check\" id=\"\'.$formid.\'-check\" /><label for=\"\'.$formid.\'-check\">\'.$label2.\'</label></p>\'.n;\n	}\n\n	// create zip checkbox\n	$zipcats_checkbox = \'\';\n	if ($zip != \'\')\n	{\n		// hide or show zip checkbox\n		if ($hidezip == \'\')\n		{\n			$zipcats_checkbox .= \'<p class=\"yab_cd_zipcats\"><input type=\"checkbox\" value=\"1\" name=\"check2\" id=\"\'.$formid.\'-check2\" /><label for=\"\'.$formid.\'-check2\">\'.$label4.\'</label></p>\'.n;\n		}\n		else\n		{\n			$zipcats_checkbox .= \'<input type=\"hidden\" value=\"1\" name=\"check2\" id=\"\'.$formid.\'-check2\" />\'.n;\n		}\n	}\n\n	// hide or show files select list\n	if ($hidelist == \'\')\n	{\n		$option_list = \'<p class=\"yab_cd_select\">\'.n;\n		$option_list .= \'<label for=\"\'.$formid.\'-files\">\'.$label1.\'</label><select name=\"files\" id=\"\'.$formid.\'-files\">\'.n;\n		$option_list .= $select;\n		$option_list .= \'</select>\'.n;\n		$option_list .= \'</p>\'.n;\n	}\n	else\n	{\n		if  (!isset($file))\n		{\n			$file = \'\';\n		}\n		$option_list = \'<input type=\"hidden\" name=\"files\" id=\"\'.$formid.\'-files\" value=\"\'.$file.\'\" />\'.n;\n	}\n\n	// create download form\n	$out .= \'<form method=\"post\" action=\"\'.$purl.\'#\'.$formid.\'\" id=\"\'.$formid.\'\">\'.n;\n	$out .= \'<div>\'.n;\n	$out .= $notice_out;\n	$out .= $option_list;\n	$out .= $zipcats_checkbox;\n	$out .= $c_checkbox;\n	$out .= \'<input type=\"submit\" value=\"\'.$label3.\'\" name=\"yab_cd_submit\" class=\"yab_cd_submit\" />\'.n;\n	$out .= \'</div>\'.n;\n	$out .= \'</form>\'.n;\n\n	return $out;\n}\n\n/**\n *\n * Creates a zipfile vom given filenames array and force to download it.\n *\n * @param array $z_filenames_array Array of filenames with file id as $val\n * @param string $zipname Name of the created zipfile, given from attribute @zipname@\n * @param string $zip zip-modus (\'2\' to force creation in memory)\n * @return Force download of the zipfile with header modifications\n */\nfunction yab_zip_files($z_filenames_array, $zipname, $zip)\n{\n	global $prefs;\n	$path = $prefs[\'file_base_path\'];\n	$tmpdir = $prefs[\'tempdir\'];\n\n	// test for zlib (php5.2); create zip on disk\n	if (version_compare(phpversion(), \'5.2.0\', \'>=\') and class_exists(\'ZipArchive\') and $zip != \'2\')\n	{\n		// start new zipfile instance\n		$tmpfile = tempnam($tmpdir,\'zip\');\n		$zipfile = new ZipArchive();\n\n		if ($zipfile->open($tmpfile, constant(\'ZipArchive::OVERWRITE\')) === true)\n		{\n			// add every file from array to zipfile\n			foreach ($z_filenames_array as $key => $val)\n			{\n				$z_filename = $path.\'/\'.$val;\n				$zipfile->addFile($z_filename,$val);\n			}\n			$zipfile->close();\n			@ob_end_clean();\n			// create headers to force download\n			header(\'Content-Type: application/zip\');\n			header(\'Content-Length: \'.filesize($tmpfile));\n			header(\'Content-Disposition: attachment; filename=\"\'.$zipname.\'\"\');\n			// delete zipfile\n			readfile($tmpfile);\n			@unlink($tmpfile);\n			exit;\n		}\n	}\n	// create zip in memory\n	else\n	{\n		// start new zipfile instance\n		$zipfile = new zipfile();\n\n		// add every file from array to zipfile\n		foreach ($z_filenames_array as $key => $val)\n		{\n			$z_filename = $path.\'/\'.$val;\n			$handle = fopen($z_filename, \"r\");\n			$content = fread($handle, filesize($z_filename));\n			fclose ($handle);\n\n			$zipfile->addFile($content, $val);\n		}\n		$dump_buffer = $zipfile->file();\n		// create headers to force download\n		header(\'Content-Type: application/zip\');\n		header(\'Content-Length: \'.strlen($dump_buffer));\n		header(\'Content-Disposition: attachment; filename=\"\'.$zipname.\'\"\');\n		echo $dump_buffer;\n		exit;\n	}\n}\n\n/**\n *\n * Creates an array of fileinfo from files assigned with specific categories\n *\n * @param string $category Categories from attribute @cats@\n * @param string $sort Sort from attribute @sort@\n * @param string $format Unit of the filesize, attribute @size@\n * @param string $exclude Files to exclude from attribute @exclude@\n * @return array\n */\nfunction yab_cd_catfiles($category = \'\', $sort = \'\', $format = \'\', $exclude = \'\')\n{\n	// create SQL-query\n	$where = array(\'1=1\');\n	$where[] = \"category IN (\'\".join(\"\',\'\", doSlash(do_list($category))).\"\')\";\n	if ($exclude != \'\')\n	{\n		$where[] = \"id NOT IN (\'\".join(\"\',\'\", doSlash(do_list($exclude))).\"\')\";\n	}\n\n	$qparts = array(\'order by \'.doSlash($sort));\n\n	// and query\n	$rs = safe_rows_start(\'*\', \'txp_file\', join(\' and \', $where).\' \'.join(\' \', $qparts));\n\n	if ($rs)\n	{\n		$out = array();\n\n		// every row in db\n		while ($a = nextRow($rs))\n		{\n			$id = $a[\'id\'];\n			$filesize = $a[\'size\'];\n\n			$out[$id][\'link\'] = file_download_link(array(\'id\' => $id),\'\');\n			$out[$id][\'size\'] = yab_cd_size($filesize, $format);\n			$out[$id][\'description\'] = yab_cd_description($id);\n		}\n	}\n\n	return $out;\n}\n\n\n/**\n *\n * Calculated the filesize depending on given unit\n *\n * @param string $filesize Given filesize in Byte\n * @param string $format Unit of the calculated filesize\n * @return string Ready for output\n */\nfunction yab_cd_size($filesize, $format = \'\')\n{\n	$decimals = 2;\n	$size = $filesize;\n\n	if (!in_array($format, array(\'B\',\'KB\',\'MB\',\'GB\',\'PB\')))\n	{\n		$divs = 0;\n		while ($size >= 1024)\n		{\n			$size /= 1024;\n			$divs++;\n		}\n\n		switch ($divs)\n		{\n			case 1:\n				$format = \'KB\';\n				break;\n			case 2:\n				$format = \'MB\';\n				break;\n			case 3:\n				$format = \'GB\';\n				break;\n			case 4:\n				$format = \'PB\';\n				break;\n			case 0:\n				default:\n				$format = \'B\';\n				break;\n		}\n	}\n\n	switch ($format)\n	{\n		case \'KB\':\n			$size /= 1024;\n			break;\n		case \'MB\':\n			$size /= (1024*1024);\n			break;\n		case \'GB\':\n			$size /= (1024*1024*1024);\n			break;\n		case \'PB\':\n			$size /= (1024*1024*1024*1024);\n			break;\n		case \'B\':\n			default:\n			break;\n	}\n\n	return number_format($size, $decimals).$format;\n}\n\n/**\n *\n * Outputs filename or given filedescription if exists.\n *\n * @param string $fileid ID of the file\n * @return string Ready for output\n */\nfunction yab_cd_description($fileid)\n{\n	$thisfile = fileDownloadFetchInfo(\'id = \'.intval($fileid));\n\n	if ($thisfile)\n	{\n		if ($thisfile[\'description\'])\n		{\n			$out = htmlspecialchars($thisfile[\'description\']);\n		}\n		else\n		{\n			$out = htmlspecialchars($thisfile[\'filename\']);\n		}\n	}\n	else\n	{\n		$out = \'No file with an ID #\'.$fileid.\' available\';\n	}\n	return $out;\n}\n\n/**\n *\n * Small sanitization. Strips all non-numerics.\n *\n * @param string $in POSTed data\n * @return string\n */\nfunction yab_cd_sanitize($in)\n{\n	$out = preg_replace(\'/[^0-9]/\', \'\', $in);\n\n	return $out;\n}\n\n/**\n *\n * @version $Id: zip.lib.php 10240 2007-04-01 11:02:46Z cybot_tm $\n *\n *\n * Zip file creation class.\n * Makes zip files.\n *\n * Based on :\n *\n *	http://www.zend.com/codex.php?id=535&single=1\n *	By Eric Mueller <eric@themepark.com>\n *\n *	http://www.zend.com/codex.php?id=470&single=1\n *	by Denis125 <webmaster@atlant.ru>\n *\n *	a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified\n *	date and time of the compressed file\n *\n * Official ZIP file format: http://www.pkware.com/appnote.txt\n *\n * @access	public\n */\nclass zipfile\n{\n		/**\n		 * Array to store compressed data\n		 *\n		 * @var	array		$datasec\n		 */\n		var $datasec			= array();\n\n		/**\n		 * Central directory\n		 *\n		 * @var	array		$ctrl_dir\n		 */\n		var $ctrl_dir		 = array();\n\n		/**\n		 * End of central directory record\n		 *\n		 * @var	string	 $eof_ctrl_dir\n		 */\n		var $eof_ctrl_dir = \"\\x50\\x4b\\x05\\x06\\x00\\x00\\x00\\x00\";\n\n		/**\n		 * Last offset position\n		 *\n		 * @var	integer	$old_offset\n		 */\n		var $old_offset	 = 0;\n\n\n		/**\n		 * Converts an Unix timestamp to a four byte DOS date and time format (date\n		 * in high two bytes, time in low two bytes allowing magnitude comparison).\n		 *\n		 * @param	integer	the current Unix timestamp\n		 *\n		 * @return integer	the current date in a four byte DOS format\n		 *\n		 * @access private\n		 */\n		function unix2DosTime($unixtime = 0) {\n				$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);\n\n				if ($timearray[\'year\'] < 1980) {\n						$timearray[\'year\']		= 1980;\n						$timearray[\'mon\']		 = 1;\n						$timearray[\'mday\']		= 1;\n						$timearray[\'hours\']	 = 0;\n						$timearray[\'minutes\'] = 0;\n						$timearray[\'seconds\'] = 0;\n				} // end if\n\n				return (($timearray[\'year\'] - 1980) << 25) | ($timearray[\'mon\'] << 21) | ($timearray[\'mday\'] << 16) |\n								($timearray[\'hours\'] << 11) | ($timearray[\'minutes\'] << 5) | ($timearray[\'seconds\'] >> 1);\n		} // end of the \'unix2DosTime()\' method\n\n\n		/**\n		 * Adds \"file\" to archive\n		 *\n		 * @param	string	 file contents\n		 * @param	string	 name of the file in the archive (may contains the path)\n		 * @param	integer	the current timestamp\n		 *\n		 * @access public\n		 */\n		function addFile($data, $name, $time = 0)\n		{\n				$name		 = str_replace(\'\\\\\', \'/\', $name);\n\n				$dtime		= dechex($this->unix2DosTime($time));\n				$hexdtime = \'\\x\' . $dtime[6] . $dtime[7]\n									. \'\\x\' . $dtime[4] . $dtime[5]\n									. \'\\x\' . $dtime[2] . $dtime[3]\n									. \'\\x\' . $dtime[0] . $dtime[1];\n				eval(\'$hexdtime = \"\' . $hexdtime . \'\";\');\n\n				$fr	 = \"\\x50\\x4b\\x03\\x04\";\n				$fr	 .= \"\\x14\\x00\";						// ver needed to extract\n				$fr	 .= \"\\x00\\x00\";						// gen purpose bit flag\n				$fr	 .= \"\\x08\\x00\";						// compression method\n				$fr	 .= $hexdtime;						 // last mod time and date\n\n				// \"local file header\" segment\n				$unc_len = strlen($data);\n				$crc		 = crc32($data);\n				$zdata	 = gzcompress($data);\n				$zdata	 = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug\n				$c_len	 = strlen($zdata);\n				$fr			.= pack(\'V\', $crc);						 // crc32\n				$fr			.= pack(\'V\', $c_len);					 // compressed filesize\n				$fr			.= pack(\'V\', $unc_len);				 // uncompressed filesize\n				$fr			.= pack(\'v\', strlen($name));		// length of filename\n				$fr			.= pack(\'v\', 0);								// extra field length\n				$fr			.= $name;\n\n				// \"file data\" segment\n				$fr .= $zdata;\n\n				// \"data descriptor\" segment (optional but necessary if archive is not\n				// served as file)\n				// nijel(2004-10-19): this seems not to be needed at all and causes\n				// problems in some cases (bug #1037737)\n				//$fr .= pack(\'V\', $crc);								 // crc32\n				//$fr .= pack(\'V\', $c_len);							 // compressed filesize\n				//$fr .= pack(\'V\', $unc_len);						 // uncompressed filesize\n\n				// add this entry to array\n				$this -> datasec[] = $fr;\n\n				// now add to central directory record\n				$cdrec = \"\\x50\\x4b\\x01\\x02\";\n				$cdrec .= \"\\x00\\x00\";								// version made by\n				$cdrec .= \"\\x14\\x00\";								// version needed to extract\n				$cdrec .= \"\\x00\\x00\";								// gen purpose bit flag\n				$cdrec .= \"\\x08\\x00\";								// compression method\n				$cdrec .= $hexdtime;								 // last mod time & date\n				$cdrec .= pack(\'V\', $crc);					 // crc32\n				$cdrec .= pack(\'V\', $c_len);				 // compressed filesize\n				$cdrec .= pack(\'V\', $unc_len);			 // uncompressed filesize\n				$cdrec .= pack(\'v\', strlen($name)); // length of filename\n				$cdrec .= pack(\'v\', 0);						 // extra field length\n				$cdrec .= pack(\'v\', 0);						 // file comment length\n				$cdrec .= pack(\'v\', 0);						 // disk number start\n				$cdrec .= pack(\'v\', 0);						 // internal file attributes\n				$cdrec .= pack(\'V\', 32);						// external file attributes - \'archive\' bit set\n\n				$cdrec .= pack(\'V\', $this -> old_offset); // relative offset of local header\n				$this -> old_offset += strlen($fr);\n\n				$cdrec .= $name;\n\n				// optional extra field, file comment goes here\n				// save to central directory\n				$this -> ctrl_dir[] = $cdrec;\n		} // end of the \'addFile()\' method\n\n\n		/**\n		 * Dumps out file\n		 *\n		 * @return	string	the zipped file\n		 *\n		 * @access public\n		 */\n		function file()\n		{\n				$data		= implode(\'\', $this -> datasec);\n				$ctrldir = implode(\'\', $this -> ctrl_dir);\n\n				return\n						$data .\n						$ctrldir .\n						$this -> eof_ctrl_dir .\n						pack(\'v\', sizeof($this -> ctrl_dir)) .	// total # of entries \"on this disk\"\n						pack(\'v\', sizeof($this -> ctrl_dir)) .	// total # of entries overall\n						pack(\'V\', strlen($ctrldir)) .					 // size of central dir\n						pack(\'V\', strlen($data)) .							// offset to start of central dir\n						\"\\x00\\x00\";														 // .zip file comment length\n		} // end of the \'file()\' method\n\n} // end of the \'zipfile\' class', '1d197f24c8cbcd11da2f1b7d4a5ed58f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('dzd_mailtodb', 0, 'Messaoudi Rabah (Dragondz)', 'http://info.ix-dz.com', '0.1', 'Store mail content on DB table', '', '/**\r\n	Registers the callback. dzd_mailverif_function() is\r\n	now loaded on \'zemcontact.submit\' event. You can find\r\n	the callback spot from ZRC\'s source and what it can offer.\r\n*/\r\n\r\nregister_callback(\'dzd_mailtodb\',\'zemcontact.submit\');\r\n\r\n/**\r\n	The function that does the work on\r\n	the submit event\r\n*/\r\n\r\nfunction dzd_mailtodb() {\r\n\r\n\r\n	$evaluation =& get_zemcontact_evaluator();\r\n\r\n	/*\r\n		It\'s spam, end here\r\n	*/\r\n\r\n	if($evaluation->get_zemcontact_status() != 0)\r\n		return;\r\n\r\n	/*\r\n		Saving the data goes here etc..\r\n		$zem_contact_values global etc. can be\r\n		used to get the data and so on.\r\n	*/\r\n	\r\n	global $zem_contact_values;\r\n	$set = \'timestamp=NOW()\';\r\n    $uid = md5(uniqid(rand(),true));\r\n    $if_ins = $zem_contact_values[\'dzd_mailtodb\'] ? 1 : 0;\r\n	\r\n	if ($if_ins)\r\n	{\r\n		$values = doSlash($zem_contact_values);\r\n		$expvar = var_export($values, true);\r\n		$myid = safe_insert(\r\n				   \"adhere\",\r\n				   \"raison          = \'$values[raison]\',\r\n					adresse         = \'$values[adresse]\',\r\n					ville           = \'$values[ville]\',\r\n					wilaya          = \'$values[wilaya]\',\r\n					telephone       = \'$values[telephone]\',\r\n					mobile          = \'$values[mobile]\',\r\n					fax             = \'$values[fax]\',\r\n					Email           = \'$values[Email]\',\r\n					nature          = \'$values[nature]\',\r\n					secteur         = \'$values[secteur]\',\r\n					branche         = \'$values[branche]\',\r\n					rc              = \'$values[rc]\',\r\n					nif             = \'$values[nif]\',\r\n					ai              = \'$values[ai]\',\r\n					nis             = \'$values[nis]\',\r\n					nom             = \'$values[nom]\',\r\n					representant    = \'$values[representant]\',\r\n					conseil         = \'$values[conseil]\',\r\n					candidat        = \'$values[candidat]\',\r\n					candidatregion  = \'$values[candidatregion]\'\"\r\n				);\r\n		\r\n		if ($myid<>\'\'){\r\n            $zem_contact_values[\'insertion\'] = \'Votre formulaire est enregistré\';\r\n         } else {\r\n			$zem_contact_values[\'insertion\'] = \'En Attente : Erreur insertion DB\'.$expvar;\r\n		 }\r\n	}\r\n}', '/**\r\n	Registers the callback. dzd_mailverif_function() is\r\n	now loaded on \'zemcontact.submit\' event. You can find\r\n	the callback spot from ZRC\'s source and what it can offer.\r\n*/\r\n\r\nregister_callback(\'dzd_mailtodb\',\'zemcontact.submit\');\r\n\r\n/**\r\n	The function that does the work on\r\n	the submit event\r\n*/\r\n\r\nfunction dzd_mailtodb() {\r\n\r\n\r\n	$evaluation =& get_zemcontact_evaluator();\r\n\r\n	/*\r\n		It\'s spam, end here\r\n	*/\r\n\r\n	if($evaluation->get_zemcontact_status() != 0)\r\n		return;\r\n\r\n	/*\r\n		Saving the data goes here etc..\r\n		$zem_contact_values global etc. can be\r\n		used to get the data and so on.\r\n	*/\r\n	\r\n	global $zem_contact_values;\r\n	$set = \'timestamp=NOW()\';\r\n    $uid = md5(uniqid(rand(),true));\r\n    $if_ins = $zem_contact_values[\'dzd_mailtodb\'] ? 1 : 0;\r\n	\r\n	if ($if_ins)\r\n	{\r\n		$values = doSlash($zem_contact_values);\r\n		$expvar = var_export($values, true);\r\n		$myid = safe_insert(\r\n				   \"adhere\",\r\n				   \"raison          = \'$values[raison]\',\r\n					adresse         = \'$values[adresse]\',\r\n					ville           = \'$values[ville]\',\r\n					wilaya          = \'$values[wilaya]\',\r\n					telephone       = \'$values[telephone]\',\r\n					mobile          = \'$values[mobile]\',\r\n					fax             = \'$values[fax]\',\r\n					Email           = \'$values[Email]\',\r\n					nature          = \'$values[nature]\',\r\n					secteur         = \'$values[secteur]\',\r\n					branche         = \'$values[branche]\',\r\n					rc              = \'$values[rc]\',\r\n					nif             = \'$values[nif]\',\r\n					ai              = \'$values[ai]\',\r\n					nis             = \'$values[nis]\',\r\n					nom             = \'$values[nom]\',\r\n					representant    = \'$values[representant]\',\r\n					conseil         = \'$values[conseil]\',\r\n					candidat        = \'$values[candidat]\',\r\n					candidatregion  = \'$values[candidatregion]\'\"\r\n				);\r\n		\r\n		if ($myid<>\'\'){\r\n            $zem_contact_values[\'insertion\'] = \'Votre formulaire est enregistré\';\r\n         } else {\r\n			$zem_contact_values[\'insertion\'] = \'En Attente : Erreur insertion DB\'.$expvar;\r\n		 }\r\n	}\r\n}', '99c3fba89599863a6c9a22ca9866b863', '', '', 0, 6, 0),
('dzd_mailverif', 0, 'Messaoudi Rabah (Dragondz)', 'http://info.ix-dz.com', '0.1', 'Change mail content and store data on table', '', '/**\r\n	Registers the callback. dzd_mailverif_function() is\r\n	now loaded on \'zemcontact.submit\' event. You can find\r\n	the callback spot from ZRC\'s source and what it can offer.\r\n*/\r\n\r\nregister_callback(\'dzd_mailverif\',\'zemcontact.submit\');\r\n\r\n/**\r\n	The function that does the work on\r\n	the submit event\r\n*/\r\n\r\nfunction dzd_mailverif() {\r\n\r\n\r\n	$evaluation =& get_zemcontact_evaluator();\r\n\r\n	/*\r\n		It\'s spam, end here\r\n	*/\r\n\r\n	if($evaluation->get_zemcontact_status() != 0)\r\n		return;\r\n\r\n	/*\r\n		Saving the data goes here etc..\r\n		$zem_contact_values global etc. can be\r\n		used to get the data and so on.\r\n	*/\r\n	\r\n	global $zem_contact_values;\r\n	$set = \'timestamp=NOW()\';\r\n        $uid = md5(uniqid(rand(),true));\r\n        $mytitle = stripSpace($zem_contact_values[nom], 1);\r\n        $vowels = array(\" \", \",\", \".\");\r\n        $onlynumbers = str_replace($vowels, \"\", $zem_contact_values[montant]);\r\n	\r\n	$myid = safe_insert(\r\n			   \"textpattern\",\r\n			   \"Title           = \'$zem_contact_values[nom]\',\r\n				Body            = \'$zem_contact_values[organisme]\',\r\n				Body_html       = \'$zem_contact_values[organisme]\',\r\n				Status          =  \'2\',\r\n				Posted          =  now(),\r\n				AuthorID        = \'dons\',\r\n				LastMod         =  now(),\r\n				LastModID       = \'dons\',\r\n				Section         = \'dons\',\r\n				url_title       = \'$mytitle\',\r\n				custom_1        = \'$zem_contact_values[Email]\',\r\n				custom_2        = \'$onlynumbers\',\r\n				custom_3        = \'$uid\',\r\n				custom_4        = \'$zem_contact_values[visible]\',\r\n				custom_5        = \'$zem_contact_values[adresse]\',\r\n				custom_6        = \'$zem_contact_values[tel]\',\r\n				custom_7        = \'$zem_contact_values[newsletter]\',\r\n				uid             = \'$uid\',\r\n				feed_time       = now()\"\r\n			);\r\n         if ($myid<>\'\'){\r\n            $zem_contact_values[\'lien\'] = \'Cliquez sur ce lien pour valider votre don : http://darnadz.org/verif?mid=\'.$myid.\'&myverif=\'.$uid.\'\';\r\n            $zem_contact_values[montant] = $onlynumbers;\r\n         }\r\n\r\n}', '/**\r\n	Registers the callback. dzd_mailverif_function() is\r\n	now loaded on \'zemcontact.submit\' event. You can find\r\n	the callback spot from ZRC\'s source and what it can offer.\r\n*/\r\n\r\nregister_callback(\'dzd_mailverif\',\'zemcontact.submit\');\r\n\r\n/**\r\n	The function that does the work on\r\n	the submit event\r\n*/\r\n\r\nfunction dzd_mailverif() {\r\n\r\n\r\n	$evaluation =& get_zemcontact_evaluator();\r\n\r\n	/*\r\n		It\'s spam, end here\r\n	*/\r\n\r\n	if($evaluation->get_zemcontact_status() != 0)\r\n		return;\r\n\r\n	/*\r\n		Saving the data goes here etc..\r\n		$zem_contact_values global etc. can be\r\n		used to get the data and so on.\r\n	*/\r\n	\r\n	global $zem_contact_values;\r\n	$set = \'timestamp=NOW()\';\r\n        $uid = md5(uniqid(rand(),true));\r\n        $mytitle = stripSpace($zem_contact_values[nom], 1);\r\n        $vowels = array(\" \", \",\", \".\");\r\n        $onlynumbers = str_replace($vowels, \"\", $zem_contact_values[montant]);\r\n	\r\n	$myid = safe_insert(\r\n			   \"textpattern\",\r\n			   \"Title           = \'$zem_contact_values[nom]\',\r\n				Body            = \'$zem_contact_values[organisme]\',\r\n				Body_html       = \'$zem_contact_values[organisme]\',\r\n				Status          =  \'2\',\r\n				Posted          =  now(),\r\n				AuthorID        = \'dons\',\r\n				LastMod         =  now(),\r\n				LastModID       = \'dons\',\r\n				Section         = \'dons\',\r\n				url_title       = \'$mytitle\',\r\n				custom_1        = \'$zem_contact_values[Email]\',\r\n				custom_2        = \'$onlynumbers\',\r\n				custom_3        = \'$uid\',\r\n				custom_4        = \'$zem_contact_values[visible]\',\r\n				custom_5        = \'$zem_contact_values[adresse]\',\r\n				custom_6        = \'$zem_contact_values[tel]\',\r\n				custom_7        = \'$zem_contact_values[newsletter]\',\r\n				uid             = \'$uid\',\r\n				feed_time       = now()\"\r\n			);\r\n         if ($myid<>\'\'){\r\n            $zem_contact_values[\'lien\'] = \'Cliquez sur ce lien pour valider votre don : http://darnadz.org/verif?mid=\'.$myid.\'&myverif=\'.$uid.\'\';\r\n            $zem_contact_values[montant] = $onlynumbers;\r\n         }\r\n\r\n}', '1785506915d4dc6a0d2d1384244cd204', '', '', 0, 6, 0),
('dzd_multicat_creator', 0, 'Messaoudi Rabah (Dragondz)', 'http://info.ix-dz.com', '0.2', 'Add multiples categories at once', '<h1>dzd_multicat_creator</h1>\n\n	<p>This plugin help creating multiple categories at once.</p>\n\n	<h2>Installation</h2>\n\n	<p>Require textpattern 4.3.0 or above</p>\n\n	<p>Activate the plugin</p>\n\n	<p>it adds title &#8220;Multiple category creator&#8221; at the bottom of category tab.</p>\n\n	<h2>How it works</h2>\n\n	<p>Click on &#8220;Multiple category creator&#8221;, that will show an area, copy/past the structure from spreadsheet (OpenOffice calc or Excel or other), and click &#8220;Send&#8221;.</p>\n\n	<p>The structure is then interpreted and shown as a tabular data, that let you check if all is correct.</p>\n\n	<p>Then click on &#8220;Create categories&#8221; to create the categories.</p>\n\n	<p>NB. To be able to see the new categories you must reload the categoy tab.</p>\n\n	<h2>Data structure</h2>\n\n	<p>The data to make on spreadsheet must be:</p>\n\n	<p>name, title, parent, type(article, image, link, file)</p>\n\n	<p>Ex:</p>\n\n	<table style=\"border-collapse: collapse;\" class=\"dzdtable\">\n		<tr>\n			<td>mycat</td>\n			<td>MyTitle</td>\n			<td>myparent</td>\n			<td>article</td>\n		</tr>\n		<tr>\n			<td>cate</td>\n			<td>Cétacé</td>\n			<td>mycat</td>\n			<td>article</td>\n		</tr>\n	</table>\n\n	<p>NB. You dont need a header in your structure</p>\n\n	<h2>History</h2>\n\n	<p>0.1 Initial release</p>\n\n	<h1>dzd_multicat_creator (Français)</h1>\n\n	<p>Ce plugin permet de créer plusieurs catégories en une seule fois.</p>\n\n	<h2>Installation</h2>\n\n	<p>nécessite Textpattern 4.3.0 or plus.</p>\n\n	<p>Installer le plugin et l&#8217;activer.</p>\n\n	<p>Il ajoute un titre &#8220;Multiple category creator&#8221; en bas de la page &#8220;Contenu/Organiser&#8221;</p>\n\n	<h2>Comment ça fonctionne</h2>\n\n	<p>Cliquez sur &#8220;Multiple category creator&#8221;, ce qui déroule un champs textarea, copier/coller la structure de catégorie à créer depuis un tableur (OpenOfiice Calc ou Excel ou tout autre tableur), et cliquez sur &#8220;Send&#8221;.</p>\n\n	<p>La structure est interprété par le plugin et affiché en dessous du textarea, ce qui vous permet de vérifier que tout est correctement interprété.</p>\n\n	<p>Puis cliquez sur &#8220;Create categories&#8221; pour créer la structure dans Textpattern.</p>\n\n	<p>NB. Pour voir apparaitre la structure il est nécessaire de recharger la page &#8220;Organiser&#8221; de Textpattern.</p>\n\n	<h2>Comment créer la structure de catégories</h2>\n\n	<p>Dans un tableur la structure doit avoir la forme suivante :</p>\n\n	<p>name, title, parent, type(article, image, link, file)</p>\n\n	<p>Ex:</p>\n\n	<table style=\"border-collapse: collapse;\" class=\"dzdtable\">\n		<tr>\n			<td>mycat</td>\n			<td>MyTitle</td>\n			<td>myparent</td>\n			<td>article</td>\n		</tr>\n		<tr>\n			<td>cate</td>\n			<td>Cétacé</td>\n			<td>mycat</td>\n			<td>article</td>\n		</tr>\n	</table>\n\n	<p>NB. Vous n&#8217;avez pas besoins de mettre des entêtes pour la structure.</p>\n\n	<h2>Historique</h2>\n\n	<p>0.1 Première mise en ligne</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n.dzdtable td\n{\nborder: 1px solid #666 ! important;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '//<?php\r\n\r\n\r\nregister_callback(\'dzd_multicat_creator\', \'category\', \'\');\r\n\r\nfunction dzd_multicat_creator($event, $step) {\r\n   global $sitename, $prefs, $thisarticle, $txp_user, $txpcfg;\r\n\r\n  $myarea = \'\';\r\n	$confirm = \'\';\r\n\r\n    if (ps(\'dzdsend\'))\r\n    {\r\n			$myarea = ps(\'cat_content\');\r\n			$confirm = \'<input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" />\';\r\n			$up_form = <<<uform1\r\n<script>\r\n$(document).ready(function() {\r\n$(\'div.txp-layout\').after(\'<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"text-align:center;\" name=\"dzd_form\" ><input type=\"hidden\" value=\"category\" name=\"event\" /><textarea name=\"cat_content\" rows=\"10\" cols=\"100\">$myarea</textarea><br /><input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" /></form>\');\r\n$(\'div.txp-layout\').after(\'<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\" id=\"dzdlink\">Multiple category creator</a></h3>\');\r\n});\r\n</script>\r\n<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\">Multiple category creator</a></h3>\r\n<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"text-align:center;\" name=\"dzd_form\" >\r\n<input type=\"hidden\" value=\"category\" name=\"event\" />\r\n<textarea name=\"cat_content\" rows=\"10\" cols=\"100\">$myarea</textarea><br />\r\n<input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" />\r\n</form>\r\nuform1;\r\n\r\n    } else {\r\n\r\n    $up_form = <<<uform1\r\n<script>\r\n$(document).ready(function() {\r\n$(\'div.txp-layout\').after(\'<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"display:none;text-align:center;\" name=\"dzd_form\" ><input type=\"hidden\" value=\"category\" name=\"event\" /><textarea name=\"cat_content\" rows=\"10\" cols=\"100\"></textarea><br /><input type=\"submit\" name=\"dzdsend\" value=\"Send\" /></form>\');\r\n$(\'div.txp-layout\').after(\'<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\" id=\"dzdlink\">Multiple category creator</a></h3>\');\r\n$(\'#dzdlink\').click(function() {\r\n$(\'form#dzd_form\').toggle();\r\n});\r\n});\r\n</script>\r\nuform1;\r\n	}\r\n	\r\n	if (ps(\'dzdconfirm\'))\r\n    {\r\n		$myarea = ps(\'cat_content\');\r\n	}\r\n	\r\n\r\n  if ($myarea <>\'\')\r\n  {\r\n		$up_form .=\'<table style=\"text-align: center;width:50%;\" ><tr><th>Name</th><th>Title</th><th>Parent</th><th>Type</th></tr>\';\r\n			$data_array = explode(\"\\n\", $myarea);\r\n			foreach($data_array as $key=>$value)\r\n			{\r\n				 //  becareful to check the value for  empty line \r\n				 $value=trim($value);\r\n			 \r\n			 $up_form .=\'<tr>\';\r\n				 if  (!empty($value))\r\n				 {\r\n							$array1 = explode(\"\\t\", $data_array[$key]);\r\n							foreach($array1 as $key1=>$value1)\r\n							{\r\n									$value1=trim($value1);\r\n									$myarray[$key][$key1] = $value1;\r\n					$up_form .= \'<td>\'.$value1.\'</td>\';\r\n							}\r\n				if (ps(\'dzdconfirm\'))\r\n				{\r\n					$mes = my_category_create($myarray[$key][3], $myarray[$key][0], $myarray[$key][2], $myarray[$key][1]);\r\n					$up_form .=\'<td>\'.$mes.\'</td>\';\r\n				}\r\n				 }\r\n			 $up_form .=\'</tr>\';\r\n			}\r\n		$up_form .= \'</table>\';\r\n\r\n	}\r\n	\r\n	echo $up_form;\r\n\r\n}\r\n\r\nfunction my_category_create($event,$title1,$papa=\'root\',$ti)\r\n	{\r\n		global $txpcfg;\r\n\r\n		$name = sanitizeForUrl($title1);\r\n		\r\n		if (!$name)\r\n		{\r\n			$message = gTxt($event.\'_category_invalid\', array(\'{name}\' => $name));\r\n\r\n			return $message;\r\n		}\r\n\r\n		$exists = safe_field(\'name\', \'txp_category\', \"name = \'\".doSlash($name).\"\' and type = \'\".doSlash($event).\"\'\");\r\n\r\n		if ($exists)\r\n		{\r\n			$message = gTxt($event.\'_category_already_exists\', array(\'{name}\' => $name));\r\n\r\n			return $message;\r\n		}\r\n		\r\n\r\n		$q = safe_insert(\'txp_category\', \"name = \'\".doSlash($name).\"\', title = \'\".doSlash($ti).\"\', type = \'\".doSlash($event).\"\', parent =\'\" .doSlash($papa).\"\'\");\r\n\r\n		if ($q)\r\n		{\r\n		rebuild_tree(\'root\', 1, $event);\r\n\r\n		$message = \'OK\';\r\n		return $message;\r\n		}\r\n	} ', '//<?php\r\n\r\n\r\nregister_callback(\'dzd_multicat_creator\', \'category\', \'\');\r\n\r\nfunction dzd_multicat_creator($event, $step) {\r\n   global $sitename, $prefs, $thisarticle, $txp_user, $txpcfg;\r\n\r\n  $myarea = \'\';\r\n	$confirm = \'\';\r\n\r\n    if (ps(\'dzdsend\'))\r\n    {\r\n			$myarea = ps(\'cat_content\');\r\n			$confirm = \'<input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" />\';\r\n			$up_form = <<<uform1\r\n<script>\r\n$(document).ready(function() {\r\n$(\'div.txp-layout\').after(\'<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"text-align:center;\" name=\"dzd_form\" ><input type=\"hidden\" value=\"category\" name=\"event\" /><textarea name=\"cat_content\" rows=\"10\" cols=\"100\">$myarea</textarea><br /><input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" /></form>\');\r\n$(\'div.txp-layout\').after(\'<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\" id=\"dzdlink\">Multiple category creator</a></h3>\');\r\n});\r\n</script>\r\n<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\">Multiple category creator</a></h3>\r\n<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"text-align:center;\" name=\"dzd_form\" >\r\n<input type=\"hidden\" value=\"category\" name=\"event\" />\r\n<textarea name=\"cat_content\" rows=\"10\" cols=\"100\">$myarea</textarea><br />\r\n<input type=\"submit\" name=\"dzdconfirm\" value=\"Create categories\" />\r\n</form>\r\nuform1;\r\n\r\n    } else {\r\n\r\n    $up_form = <<<uform1\r\n<script>\r\n$(document).ready(function() {\r\n$(\'div.txp-layout\').after(\'<form method=\"post\" action=\"index.php?event=category\" id=\"dzd_form\" class=\"toggle\" style=\"display:none;text-align:center;\" name=\"dzd_form\" ><input type=\"hidden\" value=\"category\" name=\"event\" /><textarea name=\"cat_content\" rows=\"10\" cols=\"100\"></textarea><br /><input type=\"submit\" name=\"dzdsend\" value=\"Send\" /></form>\');\r\n$(\'div.txp-layout\').after(\'<h3 class=\"plain lever\" style=\"padding-top: 10px;text-align:center;\"><a href=\"#dzd_form\" id=\"dzdlink\">Multiple category creator</a></h3>\');\r\n$(\'#dzdlink\').click(function() {\r\n$(\'form#dzd_form\').toggle();\r\n});\r\n});\r\n</script>\r\nuform1;\r\n	}\r\n	\r\n	if (ps(\'dzdconfirm\'))\r\n    {\r\n		$myarea = ps(\'cat_content\');\r\n	}\r\n	\r\n\r\n  if ($myarea <>\'\')\r\n  {\r\n		$up_form .=\'<table style=\"text-align: center;width:50%;\" ><tr><th>Name</th><th>Title</th><th>Parent</th><th>Type</th></tr>\';\r\n			$data_array = explode(\"\\n\", $myarea);\r\n			foreach($data_array as $key=>$value)\r\n			{\r\n				 //  becareful to check the value for  empty line \r\n				 $value=trim($value);\r\n			 \r\n			 $up_form .=\'<tr>\';\r\n				 if  (!empty($value))\r\n				 {\r\n							$array1 = explode(\"\\t\", $data_array[$key]);\r\n							foreach($array1 as $key1=>$value1)\r\n							{\r\n									$value1=trim($value1);\r\n									$myarray[$key][$key1] = $value1;\r\n					$up_form .= \'<td>\'.$value1.\'</td>\';\r\n							}\r\n				if (ps(\'dzdconfirm\'))\r\n				{\r\n					$mes = my_category_create($myarray[$key][3], $myarray[$key][0], $myarray[$key][2], $myarray[$key][1]);\r\n					$up_form .=\'<td>\'.$mes.\'</td>\';\r\n				}\r\n				 }\r\n			 $up_form .=\'</tr>\';\r\n			}\r\n		$up_form .= \'</table>\';\r\n\r\n	}\r\n	\r\n	echo $up_form;\r\n\r\n}\r\n\r\nfunction my_category_create($event,$title1,$papa=\'root\',$ti)\r\n	{\r\n		global $txpcfg;\r\n\r\n		$name = sanitizeForUrl($title1);\r\n		\r\n		if (!$name)\r\n		{\r\n			$message = gTxt($event.\'_category_invalid\', array(\'{name}\' => $name));\r\n\r\n			return $message;\r\n		}\r\n\r\n		$exists = safe_field(\'name\', \'txp_category\', \"name = \'\".doSlash($name).\"\' and type = \'\".doSlash($event).\"\'\");\r\n\r\n		if ($exists)\r\n		{\r\n			$message = gTxt($event.\'_category_already_exists\', array(\'{name}\' => $name));\r\n\r\n			return $message;\r\n		}\r\n		\r\n\r\n		$q = safe_insert(\'txp_category\', \"name = \'\".doSlash($name).\"\', title = \'\".doSlash($ti).\"\', type = \'\".doSlash($event).\"\', parent =\'\" .doSlash($papa).\"\'\");\r\n\r\n		if ($q)\r\n		{\r\n		rebuild_tree(\'root\', 1, $event);\r\n\r\n		$message = \'OK\';\r\n		return $message;\r\n		}\r\n	} ', '555eeb10d894593b9a05c35c1fc6888a', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('hcg_themes', 0, 'Bert Garcia', 'http://txptag.com', '0.5', 'Import/Export Textpattern Themes', '<h1>Import/Export Themes as Files</h1>\n\n<p>This plugin creates a new <strong>Themes</strong> tab under <strong>Extensions</strong>, enabling the trivial export of <strong>Pages</strong>, <strong>Forms</strong>, and <strong>Style</strong> rules to a specified folder for convenient editing, and the subsequent import of new and updated files.</p>\n\n<h2>Requirements</h2>\n\n<p>This plugin requires Textpattern <strong>4.3.0</strong> and above.</p>\n\n<p>Regardless of where it&#8217;s been tested, this plugin messes around with your database.</p>\n\n<p><em>Do not use it without backing up your database</em>.</p>\n\n<h2>Setup</h2>\n\n<p>By default, the plugin looks for a directory named <strong>themes</strong> in your Textpattern root directory. If the directory doesn&#8217;t exist, the plugin will attempt to create it the first time you export your themes. This creation will almost certainly fail, since the Textpattern root directory usually isn&#8217;t writable. In that case, you&#8217;ll need to create this directory, and ensure that the web server has write access.</p>\n\n<p>If your Textpattern root is located at <strong>/users/home/myuser/web/public/</strong>, the following commands could be used:</p>\n\n<pre><code>cd /users/home/myuser/web/public/\nmkdir themes\nchmod 777 themes</code></pre>\n\n<h2>Usage</h2>\n\n<p>Import &#8211; select a theme to import from the dropdown on the <strong>Themes</strong> tab and press <em>Go</em>. Before importing, the plugin will do an export of your installed themes to a folder called <strong>preimport-data</strong>.</p>\n\n<p>Export &#8211; type in an export name and press <em>Go</em>. Keep in mind that naming an export the same as an exisiting folder will overwrite the contents.</p>\n\n<p>Themes sub folders and file naming convention:</p>\n\n<ul>\n	<li>themes\n	<ul>\n		<li>forms\n		<ul>\n			<li>comments.comment.txp</li>\n			<li>default.article.txp</li>\n			<li>plainlinks.links.txp</li>\n		</ul></li>\n		<li>pages\n		<ul>\n			<li>default.txp</li>\n			<li>error_default.txp</li>\n		</ul></li>\n		<li>styles\n		<ul>\n			<li>default.css</li>\n		</ul></li>\n		</ul></li>\n		</ul>\n\n<h2>Credits</h2>\n\n<p>Plugin code based on mcw_templates by <a href=\"http://mikewest.org/\">Mike West</a>.</p>', '/*\r\n    PUBLIC PLUGIN CONFIG\r\n    -------------------------------------------------------------------------\r\n*/\r\n    $hcg_themes = array(\r\n        \"base_dir\"      =>  \"themes\",\r\n\r\n        \"subdir_pages\"  =>  \"pages\",\r\n        \"subdir_forms\"  =>  \"forms\",\r\n        \"subdir_styles\" =>  \"styles\",\r\n\r\n        \"ext_pages\"     =>  \".txp\",\r\n        \"ext_forms\"     =>  \".txp\",\r\n        \"ext_styles\"	=>  \".css\"\r\n    );\r\n\r\n/*\r\n    PLUGIN CODE (no editing below this line, please)\r\n    -------------------------------------------------------------------------\r\n*/\r\n    define(\'_HCG_THEMES_IMPORT\', 1);\r\n    define(\'_HCG_THEMES_EXPORT\', 2);\r\n    $GLOBALS[\'_HCG_THEMES\'] = $hcg_themes;\r\n\r\n\r\n\r\n    /*\r\n        PLUGIN CODE::INSTANTIATION\r\n        -------------------------------------------------------------\r\n    */\r\n        if (@txpinterface == \'admin\') {\r\n        	$import         = \'hcg_themes\';\r\n        	$import_tab     = \'Themes\';\r\n\r\n        	add_privs($import, \'1,2\');\r\n        	register_tab(\"extensions\", $import, $import_tab);\r\n        	register_callback(\"hcg_themes\", $import);\r\n        }\r\n\r\n    /*\r\n        PLUGIN CODE::MAIN CALLBACK\r\n        -------------------------------------------------------------\r\n    */\r\n        function hcg_themes($event, $step) {\r\n			$GLOBALS[\'prefs\'] = get_prefs();\r\n            $theme = new hcg_theme();\r\n\r\n    		pagetop(\"Process Themes\", \"\");\r\n		    print \"\r\n	        <style type=\'text/css\'>\r\n	            .success { color: #009900; }\r\n	            .failure { color: #FF0000; }\r\n	        </style>\r\n\r\n	        <table cellpadding=\'0\' cellspacing=\'0\' border=\'0\' id=\'list\' align=\'center\'>\r\n	            <tr>\r\n	                <td>\r\n	        \";\r\n\r\n	        switch ($step) {\r\n	            case \"import\":\r\n	                $theme->import(ps(\'import_dir\'));\r\n	                break;\r\n\r\n	            case \"export\":\r\n	                $dir = ps(\'export_dir\');\r\n\r\n	                $dir =  str_replace(\r\n	                            array(\" \"),\r\n	                            array(\"-\"),\r\n	                            $dir\r\n                            );\r\n	                $theme->export($dir);\r\n	                break;\r\n\r\n	            default:\r\n	                $importlist = $theme->getThemeList();\r\n\r\n                    print \"\r\n                        <h1>Import Themes</h1>\r\n                    \".form(\r\n                          graf(\'Which theme set would you like to import?\'.\r\n                            selectInput(\'import_dir\', $importlist, \'\', 1).\r\n                            fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n                            eInput(\'hcg_themes\').sInput(\'import\')\r\n                        )\r\n        	        );\r\n\r\n        	        print \"\r\n                        <h1>Export Themes</h1>\r\n                    \".form(\r\n                          graf(\'Name this export:\'.\r\n                            fInput(\'text\', \'export_dir\', \'\').\r\n                            fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n                            eInput(\'hcg_themes\').sInput(\'export\')\r\n                        )\r\n        	        );\r\n\r\n	                break;\r\n	        }\r\n	        print \"\r\n	                </td>\r\n	            </tr>\r\n	        </table>\r\n	        \";\r\n        }\r\n\r\n    class hcg_theme {\r\n        function hcg_theme() {\r\n			global $prefs;\r\n            global $_HCG_THEMES;\r\n\r\n            $this->_config = $_HCG_THEMES;\r\n\r\n        /*\r\n            PRIVATE CONFIG\r\n            ------------------------------------------------------\r\n        */\r\n            $this->_config[\'root_path\']         =   $prefs[\'path_to_site\'];\r\n            $this->_config[\'full_base_path\']    =   sprintf(\r\n                                                        \"%s/%s\",\r\n                                                        $this->_config[\'root_path\'],\r\n                                                        $this->_config[\'base_dir\']\r\n                                                    );\r\n\r\n            $this->_config[\'error_theme\']    =   \"\r\n                <h1 class=\'failure\'>%s</h1>\r\n                <p>%s</p>\r\n            \";\r\n\r\n            $missing_dir_head   = \"Theme Directory Missing\";\r\n            $missing_dir_text   = \"The theme directory `<strong>%1\\$s</strong>` does not exist, and could not be automatically created.  Would you mind creating it yourself by running something like</p><pre><code>    mkdir %1\\$s\\n    chmod 777 %1\\$s</code></pre><p>That should fix the issue.  You could also adjust the plugin\'s directory by modifying <code>\\$hcg_themes[\'base_dir\']</code> in the plugin\'s code.\";\r\n            $cant_write_head    = \"Theme Directory Not Writable\";\r\n            $cant_write_text    = \"I can\'t seem to write to the theme directory `<strong>%1\\$s</strong>`.  Would you mind running something like</p><pre><code>    chmod 777 %1\\$s</code></pre><p>to fix the problem?\";\r\n            $cant_read_head     = \"Theme Directory Not Readable\";\r\n            $cant_read_text     = \"I can\'t seem to read from the theme directory `<strong>%1\\$s</strong>`.  Would you mind running something like</p><pre><code>    chmod 777 %%1\\$s</code></pre><p>to fix the problem?\";\r\n\r\n\r\n            $this->_config[\'error_missing_dir\'] =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $missing_dir_head,\r\n                                                        $missing_dir_text\r\n                                                    );\r\n            $this->_config[\'error_cant_write\']  =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $cant_write_head,\r\n                                                        $cant_write_text\r\n                                                    );\r\n            $this->_config[\'error_cant_read\']   =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $cant_read_head,\r\n                                                        $cant_read_text\r\n                                                    );\r\n\r\n            $this->exportTypes = array(\r\n                \"pages\" =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_pages\'],\r\n                                \"data\"      =>  \"user_html\",\r\n                                \"fields\"    =>  \"name, user_html\",\r\n                                \"nice_name\" =>  \"Page Files\",\r\n                                \"regex\"     =>  \"/(.+)\".$this->_config[\'ext_pages\'].\"/\",\r\n                                \"sql\"       =>  \"`user_html` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_pages\'],\r\n                                \"table\"     =>  \"txp_page\"\r\n                            ),\r\n                \"forms\" =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_forms\'],\r\n                                \"data\"      =>  \"Form\",\r\n                                \"fields\"    =>  \"name, type, Form\",\r\n                                \"nice_name\" =>  \"Form Files\",\r\n                                \"regex\"     =>  \"/(.+)\\.(.+)\".$this->_config[\'ext_forms\'].\"/\",\r\n                                \"sql\"       =>  \"`Form` = \'%s\', `type` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_forms\'],\r\n                                \"table\"     =>  \"txp_form\"\r\n                            ),\r\n                \"css\"   =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_styles\'],\r\n                                \"data\"      =>  \"css\",\r\n                                \"fields\"    =>  \"name, css\",\r\n                                \"nice_name\" =>  \"CSS Rules\",\r\n                                \"regex\"     =>  \"/(.+)\".$this->_config[\'ext_styles\'].\"/\",\r\n                                \"sql\"       =>  \"`css` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_styles\'],\r\n                                \"table\"     =>  \"txp_css\"\r\n                            )\r\n            );\r\n        }\r\n\r\n        function checkdir($dir = \'\', $type = _HCG_THEMES_EXPORT) {\r\n            /*\r\n                If $type == _EXPORT, then:\r\n                    1.  Check to see that /base/path/$dir exists, and is\r\n                        writable.  If not, create it.\r\n                    2.  Check to see that /base/path/$dir/subdir_* exist,\r\n                        and are writable.  If not, create them.\r\n\r\n                If $type == _IMPORT, then:\r\n                    1.  Check to see that /base/path/$dir exists, and is readable.\r\n                    2.  Check to see that /base/path/$dir/subdir_* exist, and are readable.\r\n            */\r\n            $dir =  sprintf(\r\n                        \"%s/%s\",\r\n                        $this->_config[\'full_base_path\'],\r\n                        $dir\r\n                    );\r\n\r\n            $tocheck =  array(\r\n                            $dir,\r\n                            $dir.\'/\'.$this->_config[\'subdir_pages\'],\r\n                            $dir.\'/\'.$this->_config[\'subdir_styles\'],\r\n                            $dir.\'/\'.$this->_config[\'subdir_forms\']\r\n                        );\r\n            foreach ($tocheck as $curDir) {\r\n                switch ($type) {\r\n                    case _HCG_THEMES_IMPORT:\r\n                        if (!is_dir($curDir)) {\r\n                            echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        if (!is_readable($curDir)) {\r\n                            echo sprintf($this->_config[\'error_cant_read\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        break;\r\n\r\n                    case _HCG_THEMES_EXPORT:\r\n                        if (!is_dir($curDir)) {\r\n                            if (!@mkdir($curDir, 0777)) {\r\n                                echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n                                return false;\r\n                            }\r\n                        }\r\n                        if (!is_writable($curDir)) {\r\n                            echo sprintf($this->_config[\'error_cant_write\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        break;\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n\r\n        /*\r\n            EXPORT FUNCTIONS\r\n            ----------------------------------------------------------\r\n        */\r\n        function export($dir = \'\') {\r\n            if (!$this->checkdir($dir, _HCG_THEMES_EXPORT)) {\r\n                return;\r\n            }\r\n\r\n            foreach ($this->exportTypes as $type => $config) {\r\n                print \"\r\n                    <h1>Exporting \".$config[\'nice_name\'].\"</h1>\r\n                    <ul class=\'results\'>\r\n                \";\r\n\r\n                $rows = safe_rows($config[\'fields\'], $config[\'table\'], \'1=1\');\r\n\r\n                foreach ($rows as $row) {\r\n                    $filename       =   sprintf(\r\n                                            \"%s/%s/%s/%s%s\",\r\n                                            $this->_config[\'full_base_path\'],\r\n                                            $dir,\r\n                                            $config[\'subdir\'],\r\n                                            (isset($row[\'type\'])? $row[\'type\'].\".\":\"\") . $row[\'name\'],\r\n                                            $config[\'ext\']\r\n                                        );\r\n                    $nicefilename =     sprintf(\r\n                                            \".../%s/%s/%s%s\",\r\n                                            $dir,\r\n                                            $config[\'subdir\'],\r\n                                            (isset($row[\'type\'])? $row[\'type\'].\".\":\"\") . $row[\'name\'],\r\n                                            $config[\'ext\']\r\n                                        );\r\n\r\n                    $f = @fopen($filename, \"w+\");\r\n                    if ($f) {\r\n                        fwrite($f,$row[$config[\'data\']]);\r\n                        fclose($f);\r\n                        print \"\r\n                        <li><span class=\'success\'>Successfully exported</span> \".$config[\'nice_name\'].\" \'\".$row[\'name\'].\"\' to \'\".$nicefilename.\"\'</li>\r\n                        \";\r\n                    } else {\r\n                        print \"\r\n                        <li><span class=\'failure\'>Failure exporting</span> \".$config[\'nice_name\'].\" \'\".$row[\'name\'].\"\' to \'\".$nicefilename.\"\'</li>\r\n                        \";\r\n                    }\r\n                }\r\n                print \"\r\n                    </ul>\r\n                \";\r\n            }\r\n        }\r\n\r\n        /*\r\n            IMPORT FUNCTIONS\r\n            ----------------------------------------------------------\r\n        */\r\n        function getThemeList() {\r\n            $dir = opendir($this->_config[\'full_base_path\']);\r\n\r\n            $list = array();\r\n\r\n            while(false !== ($filename = readdir($dir))) {\r\n                if (\r\n                    is_dir(\r\n                        sprintf(\r\n                            \"%s/%s\",\r\n                            $this->_config[\'full_base_path\'],\r\n                            $filename\r\n                        )\r\n                    ) && $filename != \'.\' && $filename != \'..\'\r\n                ) {\r\n                    $list[$filename] = $filename;\r\n                }\r\n            }\r\n            return $list;\r\n        }\r\n\r\n        function import($dir) {\r\n            if (!$this->checkdir($dir, _HCG_THEMES_IMPORT)) {\r\n                return;\r\n            }\r\n\r\n            /*\r\n                Auto export into `preimport-data`\r\n            */\r\n            print \"\r\n                <h1>Backing up current theme data</h1>\r\n                <p>Your current theme data will be available for re-import as `preimport-data`.</p>\r\n            \";\r\n\r\n            $this->export(\'preimport-data\');\r\n\r\n            $basedir =  sprintf(\r\n                            \"%s/%s\",\r\n                            $this->_config[\'full_base_path\'],\r\n                            $dir\r\n                        );\r\n            foreach ($this->exportTypes as $type => $config) {\r\n                print \"\r\n                    <h1>Importing \".$config[\'nice_name\'].\"</h1>\r\n                    <ul class=\'results\'>\r\n                \";\r\n\r\n                $exportdir =    sprintf(\r\n                                    \"%s/%s\",\r\n                                    $basedir,\r\n                                    $config[\'subdir\']\r\n                                );\r\n\r\n                $dir = opendir($exportdir);\r\n                while (false !== ($filename = readdir($dir))) {\r\n                    if (preg_match($config[\'regex\'], $filename, $filedata)) {\r\n                        if (isset($filedata[2])) {\r\n                                $themeType = $filedata[1]; \r\n                                $themeName = addslashes($filedata[2]);\r\n                        } else {                       \r\n                                $themeType = \"\"; \r\n                                $themeName = addslashes($filedata[1]);\r\n                        }\r\n\r\n                        $f =    sprintf(\r\n                                    \"%s/%s\",\r\n                                    $exportdir,\r\n                                    $filename\r\n                                );\r\n\r\n		                if ($data = file($f)) {\r\n    		                    $data = addslashes(implode(\'\', $data));\r\n                            if (safe_field(\'name\', $config[\'table\'], \"name=\'\".$themeName.\"\'\")) {\r\n                                $result = safe_update($config[\'table\'], sprintf($config[\'sql\'], $data, $themeType), \"`name` = \'\".$themeName.\"\'\");\r\n                                $success = ($result)?1:0;\r\n                            } else {\r\n                                $result = safe_insert($config[\'table\'], sprintf($config[\'sql\'], $data, $themeType).\", `name` = \'\".$themeName.\"\'\");\r\n                                $success = ($result)?1:0;\r\n                            }\r\n    		            }\r\n\r\n    		            $success = true;\r\n                        if ($success) {\r\n                            print \"<li><span class=\'success\'>Successfully imported</span> file \'\".$filename.\"\'</li>\";\r\n                        } else {\r\n                            print \"<li><span class=\'failure\'>Failed importing</span> file \'\".$filename.\"\'</li>\";\r\n                        }\r\n                    }\r\n                }\r\n\r\n                print \"\r\n                    </ul>\r\n                \";\r\n            }\r\n        }\r\n    }\r\n', '/*\r\n    PUBLIC PLUGIN CONFIG\r\n    -------------------------------------------------------------------------\r\n*/\r\n    $hcg_themes = array(\r\n        \"base_dir\"      =>  \"themes\",\r\n\r\n        \"subdir_pages\"  =>  \"pages\",\r\n        \"subdir_forms\"  =>  \"forms\",\r\n        \"subdir_styles\" =>  \"styles\",\r\n\r\n        \"ext_pages\"     =>  \".txp\",\r\n        \"ext_forms\"     =>  \".txp\",\r\n        \"ext_styles\"	=>  \".css\"\r\n    );\r\n\r\n/*\r\n    PLUGIN CODE (no editing below this line, please)\r\n    -------------------------------------------------------------------------\r\n*/\r\n    define(\'_HCG_THEMES_IMPORT\', 1);\r\n    define(\'_HCG_THEMES_EXPORT\', 2);\r\n    $GLOBALS[\'_HCG_THEMES\'] = $hcg_themes;\r\n\r\n\r\n\r\n    /*\r\n        PLUGIN CODE::INSTANTIATION\r\n        -------------------------------------------------------------\r\n    */\r\n        if (@txpinterface == \'admin\') {\r\n        	$import         = \'hcg_themes\';\r\n        	$import_tab     = \'Themes\';\r\n\r\n        	add_privs($import, \'1,2\');\r\n        	register_tab(\"extensions\", $import, $import_tab);\r\n        	register_callback(\"hcg_themes\", $import);\r\n        }\r\n\r\n    /*\r\n        PLUGIN CODE::MAIN CALLBACK\r\n        -------------------------------------------------------------\r\n    */\r\n        function hcg_themes($event, $step) {\r\n			$GLOBALS[\'prefs\'] = get_prefs();\r\n            $theme = new hcg_theme();\r\n\r\n    		pagetop(\"Process Themes\", \"\");\r\n		    print \"\r\n	        <style type=\'text/css\'>\r\n	            .success { color: #009900; }\r\n	            .failure { color: #FF0000; }\r\n	        </style>\r\n\r\n	        <table cellpadding=\'0\' cellspacing=\'0\' border=\'0\' id=\'list\' align=\'center\'>\r\n	            <tr>\r\n	                <td>\r\n	        \";\r\n\r\n	        switch ($step) {\r\n	            case \"import\":\r\n	                $theme->import(ps(\'import_dir\'));\r\n	                break;\r\n\r\n	            case \"export\":\r\n	                $dir = ps(\'export_dir\');\r\n\r\n	                $dir =  str_replace(\r\n	                            array(\" \"),\r\n	                            array(\"-\"),\r\n	                            $dir\r\n                            );\r\n	                $theme->export($dir);\r\n	                break;\r\n\r\n	            default:\r\n	                $importlist = $theme->getThemeList();\r\n\r\n                    print \"\r\n                        <h1>Import Themes</h1>\r\n                    \".form(\r\n                          graf(\'Which theme set would you like to import?\'.\r\n                            selectInput(\'import_dir\', $importlist, \'\', 1).\r\n                            fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n                            eInput(\'hcg_themes\').sInput(\'import\')\r\n                        )\r\n        	        );\r\n\r\n        	        print \"\r\n                        <h1>Export Themes</h1>\r\n                    \".form(\r\n                          graf(\'Name this export:\'.\r\n                            fInput(\'text\', \'export_dir\', \'\').\r\n                            fInput(\'submit\', \'go\', \'Go\', \'smallerbox\').\r\n                            eInput(\'hcg_themes\').sInput(\'export\')\r\n                        )\r\n        	        );\r\n\r\n	                break;\r\n	        }\r\n	        print \"\r\n	                </td>\r\n	            </tr>\r\n	        </table>\r\n	        \";\r\n        }\r\n\r\n    class hcg_theme {\r\n        function hcg_theme() {\r\n			global $prefs;\r\n            global $_HCG_THEMES;\r\n\r\n            $this->_config = $_HCG_THEMES;\r\n\r\n        /*\r\n            PRIVATE CONFIG\r\n            ------------------------------------------------------\r\n        */\r\n            $this->_config[\'root_path\']         =   $prefs[\'path_to_site\'];\r\n            $this->_config[\'full_base_path\']    =   sprintf(\r\n                                                        \"%s/%s\",\r\n                                                        $this->_config[\'root_path\'],\r\n                                                        $this->_config[\'base_dir\']\r\n                                                    );\r\n\r\n            $this->_config[\'error_theme\']    =   \"\r\n                <h1 class=\'failure\'>%s</h1>\r\n                <p>%s</p>\r\n            \";\r\n\r\n            $missing_dir_head   = \"Theme Directory Missing\";\r\n            $missing_dir_text   = \"The theme directory `<strong>%1\\$s</strong>` does not exist, and could not be automatically created.  Would you mind creating it yourself by running something like</p><pre><code>    mkdir %1\\$s\\n    chmod 777 %1\\$s</code></pre><p>That should fix the issue.  You could also adjust the plugin\'s directory by modifying <code>\\$hcg_themes[\'base_dir\']</code> in the plugin\'s code.\";\r\n            $cant_write_head    = \"Theme Directory Not Writable\";\r\n            $cant_write_text    = \"I can\'t seem to write to the theme directory `<strong>%1\\$s</strong>`.  Would you mind running something like</p><pre><code>    chmod 777 %1\\$s</code></pre><p>to fix the problem?\";\r\n            $cant_read_head     = \"Theme Directory Not Readable\";\r\n            $cant_read_text     = \"I can\'t seem to read from the theme directory `<strong>%1\\$s</strong>`.  Would you mind running something like</p><pre><code>    chmod 777 %%1\\$s</code></pre><p>to fix the problem?\";\r\n\r\n\r\n            $this->_config[\'error_missing_dir\'] =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $missing_dir_head,\r\n                                                        $missing_dir_text\r\n                                                    );\r\n            $this->_config[\'error_cant_write\']  =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $cant_write_head,\r\n                                                        $cant_write_text\r\n                                                    );\r\n            $this->_config[\'error_cant_read\']   =   sprintf(\r\n                                                        $this->_config[\'error_theme\'],\r\n                                                        $cant_read_head,\r\n                                                        $cant_read_text\r\n                                                    );\r\n\r\n            $this->exportTypes = array(\r\n                \"pages\" =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_pages\'],\r\n                                \"data\"      =>  \"user_html\",\r\n                                \"fields\"    =>  \"name, user_html\",\r\n                                \"nice_name\" =>  \"Page Files\",\r\n                                \"regex\"     =>  \"/(.+)\".$this->_config[\'ext_pages\'].\"/\",\r\n                                \"sql\"       =>  \"`user_html` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_pages\'],\r\n                                \"table\"     =>  \"txp_page\"\r\n                            ),\r\n                \"forms\" =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_forms\'],\r\n                                \"data\"      =>  \"Form\",\r\n                                \"fields\"    =>  \"name, type, Form\",\r\n                                \"nice_name\" =>  \"Form Files\",\r\n                                \"regex\"     =>  \"/(.+)\\.(.+)\".$this->_config[\'ext_forms\'].\"/\",\r\n                                \"sql\"       =>  \"`Form` = \'%s\', `type` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_forms\'],\r\n                                \"table\"     =>  \"txp_form\"\r\n                            ),\r\n                \"css\"   =>  array(\r\n                                \"ext\"       =>  $this->_config[\'ext_styles\'],\r\n                                \"data\"      =>  \"css\",\r\n                                \"fields\"    =>  \"name, css\",\r\n                                \"nice_name\" =>  \"CSS Rules\",\r\n                                \"regex\"     =>  \"/(.+)\".$this->_config[\'ext_styles\'].\"/\",\r\n                                \"sql\"       =>  \"`css` = \'%s\'\",\r\n                                \"subdir\"    =>  $this->_config[\'subdir_styles\'],\r\n                                \"table\"     =>  \"txp_css\"\r\n                            )\r\n            );\r\n        }\r\n\r\n        function checkdir($dir = \'\', $type = _HCG_THEMES_EXPORT) {\r\n            /*\r\n                If $type == _EXPORT, then:\r\n                    1.  Check to see that /base/path/$dir exists, and is\r\n                        writable.  If not, create it.\r\n                    2.  Check to see that /base/path/$dir/subdir_* exist,\r\n                        and are writable.  If not, create them.\r\n\r\n                If $type == _IMPORT, then:\r\n                    1.  Check to see that /base/path/$dir exists, and is readable.\r\n                    2.  Check to see that /base/path/$dir/subdir_* exist, and are readable.\r\n            */\r\n            $dir =  sprintf(\r\n                        \"%s/%s\",\r\n                        $this->_config[\'full_base_path\'],\r\n                        $dir\r\n                    );\r\n\r\n            $tocheck =  array(\r\n                            $dir,\r\n                            $dir.\'/\'.$this->_config[\'subdir_pages\'],\r\n                            $dir.\'/\'.$this->_config[\'subdir_styles\'],\r\n                            $dir.\'/\'.$this->_config[\'subdir_forms\']\r\n                        );\r\n            foreach ($tocheck as $curDir) {\r\n                switch ($type) {\r\n                    case _HCG_THEMES_IMPORT:\r\n                        if (!is_dir($curDir)) {\r\n                            echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        if (!is_readable($curDir)) {\r\n                            echo sprintf($this->_config[\'error_cant_read\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        break;\r\n\r\n                    case _HCG_THEMES_EXPORT:\r\n                        if (!is_dir($curDir)) {\r\n                            if (!@mkdir($curDir, 0777)) {\r\n                                echo sprintf($this->_config[\'error_missing_dir\'], $curDir);\r\n                                return false;\r\n                            }\r\n                        }\r\n                        if (!is_writable($curDir)) {\r\n                            echo sprintf($this->_config[\'error_cant_write\'], $curDir);\r\n                            return false;\r\n                        }\r\n                        break;\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n\r\n        /*\r\n            EXPORT FUNCTIONS\r\n            ----------------------------------------------------------\r\n        */\r\n        function export($dir = \'\') {\r\n            if (!$this->checkdir($dir, _HCG_THEMES_EXPORT)) {\r\n                return;\r\n            }\r\n\r\n            foreach ($this->exportTypes as $type => $config) {\r\n                print \"\r\n                    <h1>Exporting \".$config[\'nice_name\'].\"</h1>\r\n                    <ul class=\'results\'>\r\n                \";\r\n\r\n                $rows = safe_rows($config[\'fields\'], $config[\'table\'], \'1=1\');\r\n\r\n                foreach ($rows as $row) {\r\n                    $filename       =   sprintf(\r\n                                            \"%s/%s/%s/%s%s\",\r\n                                            $this->_config[\'full_base_path\'],\r\n                                            $dir,\r\n                                            $config[\'subdir\'],\r\n                                            (isset($row[\'type\'])? $row[\'type\'].\".\":\"\") . $row[\'name\'],\r\n                                            $config[\'ext\']\r\n                                        );\r\n                    $nicefilename =     sprintf(\r\n                                            \".../%s/%s/%s%s\",\r\n                                            $dir,\r\n                                            $config[\'subdir\'],\r\n                                            (isset($row[\'type\'])? $row[\'type\'].\".\":\"\") . $row[\'name\'],\r\n                                            $config[\'ext\']\r\n                                        );\r\n\r\n                    $f = @fopen($filename, \"w+\");\r\n                    if ($f) {\r\n                        fwrite($f,$row[$config[\'data\']]);\r\n                        fclose($f);\r\n                        print \"\r\n                        <li><span class=\'success\'>Successfully exported</span> \".$config[\'nice_name\'].\" \'\".$row[\'name\'].\"\' to \'\".$nicefilename.\"\'</li>\r\n                        \";\r\n                    } else {\r\n                        print \"\r\n                        <li><span class=\'failure\'>Failure exporting</span> \".$config[\'nice_name\'].\" \'\".$row[\'name\'].\"\' to \'\".$nicefilename.\"\'</li>\r\n                        \";\r\n                    }\r\n                }\r\n                print \"\r\n                    </ul>\r\n                \";\r\n            }\r\n        }\r\n\r\n        /*\r\n            IMPORT FUNCTIONS\r\n            ----------------------------------------------------------\r\n        */\r\n        function getThemeList() {\r\n            $dir = opendir($this->_config[\'full_base_path\']);\r\n\r\n            $list = array();\r\n\r\n            while(false !== ($filename = readdir($dir))) {\r\n                if (\r\n                    is_dir(\r\n                        sprintf(\r\n                            \"%s/%s\",\r\n                            $this->_config[\'full_base_path\'],\r\n                            $filename\r\n                        )\r\n                    ) && $filename != \'.\' && $filename != \'..\'\r\n                ) {\r\n                    $list[$filename] = $filename;\r\n                }\r\n            }\r\n            return $list;\r\n        }\r\n\r\n        function import($dir) {\r\n            if (!$this->checkdir($dir, _HCG_THEMES_IMPORT)) {\r\n                return;\r\n            }\r\n\r\n            /*\r\n                Auto export into `preimport-data`\r\n            */\r\n            print \"\r\n                <h1>Backing up current theme data</h1>\r\n                <p>Your current theme data will be available for re-import as `preimport-data`.</p>\r\n            \";\r\n\r\n            $this->export(\'preimport-data\');\r\n\r\n            $basedir =  sprintf(\r\n                            \"%s/%s\",\r\n                            $this->_config[\'full_base_path\'],\r\n                            $dir\r\n                        );\r\n            foreach ($this->exportTypes as $type => $config) {\r\n                print \"\r\n                    <h1>Importing \".$config[\'nice_name\'].\"</h1>\r\n                    <ul class=\'results\'>\r\n                \";\r\n\r\n                $exportdir =    sprintf(\r\n                                    \"%s/%s\",\r\n                                    $basedir,\r\n                                    $config[\'subdir\']\r\n                                );\r\n\r\n                $dir = opendir($exportdir);\r\n                while (false !== ($filename = readdir($dir))) {\r\n                    if (preg_match($config[\'regex\'], $filename, $filedata)) {\r\n                        if (isset($filedata[2])) {\r\n                                $themeType = $filedata[1]; \r\n                                $themeName = addslashes($filedata[2]);\r\n                        } else {                       \r\n                                $themeType = \"\"; \r\n                                $themeName = addslashes($filedata[1]);\r\n                        }\r\n\r\n                        $f =    sprintf(\r\n                                    \"%s/%s\",\r\n                                    $exportdir,\r\n                                    $filename\r\n                                );\r\n\r\n		                if ($data = file($f)) {\r\n    		                    $data = addslashes(implode(\'\', $data));\r\n                            if (safe_field(\'name\', $config[\'table\'], \"name=\'\".$themeName.\"\'\")) {\r\n                                $result = safe_update($config[\'table\'], sprintf($config[\'sql\'], $data, $themeType), \"`name` = \'\".$themeName.\"\'\");\r\n                                $success = ($result)?1:0;\r\n                            } else {\r\n                                $result = safe_insert($config[\'table\'], sprintf($config[\'sql\'], $data, $themeType).\", `name` = \'\".$themeName.\"\'\");\r\n                                $success = ($result)?1:0;\r\n                            }\r\n    		            }\r\n\r\n    		            $success = true;\r\n                        if ($success) {\r\n                            print \"<li><span class=\'success\'>Successfully imported</span> file \'\".$filename.\"\'</li>\";\r\n                        } else {\r\n                            print \"<li><span class=\'failure\'>Failed importing</span> file \'\".$filename.\"\'</li>\";\r\n                        }\r\n                    }\r\n                }\r\n\r\n                print \"\r\n                    </ul>\r\n                \";\r\n            }\r\n        }\r\n    }\r\n', '2ba1608f20e9df7b9b03437052f66cd6', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_link', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_link-textpattern-plugin', '0.8', 'Create all kinds of links', '<p><code>&lt;txp:wet_link&gt;</code> creates links. It works as a replacement for txp:permlink, and it can also be used to create direct links to articles, pages, images, article lists, and external web sites.</p>\n\n<p>Some examples:</p>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link&gt;foo&lt;/txp:wet_link&gt;</code><br />\nIn an article form, works the same as txp:permlink</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link title=&quot;permanent link to \'%s\'&quot;&gt;foo&lt;/txp:wet_link&gt;</code><br />\nSame as txp:permlink, except with a title attribute.  The optional ‘%s’ will be replaced with the article title.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link href=&quot;25&quot;&gt;foo&lt;/txp:wet_link&gt;</code><br />\nDirect link to article #25. If a ‘title’ attribute is supplied, it works the same as for a permanent link, with ‘%s’ replaced with the article title.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link href=&quot;foo/&quot;&amp;gt;foo&amp;lt;/txp:wet_link&amp;gt;</code><br />\nCreates a link relative to the textpattern root.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link linkid=&quot;15&quot;&gt;foo&lt;/txp:wet_link&gt;</code><br />\nDirect link to link #15.  If no ‘title’ attribute is supplied, the link description field is used.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link image=&quot;15&quot;&gt;foo&lt;/txp:wet_link&gt;</code><br />\nDirect link to image #15.  If no ‘title’ attribute is supplied, the image caption field is used.</p>\n</blockquote>\n\n<p>As of version 0.3, it is possible to use wet_link as a self-closing tag in certain circumstances:</p>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link href=&quot;25&quot; /&gt;</code><br />\nDirect link to article #25.  The article title will be used as the link text.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link linkid=&quot;15&quot; /&gt;</code><br />\nDirect link to link #15.  The link name field will be used as the link text.</p>\n</blockquote>\n\n<p>As of version 0.4, it is possible to use wet_link as a link to article lists:</p>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link section=&quot;about&quot;&gt;About Us&lt;/txp:wet_link&gt;</code><br />\nLink to the section named “about”. Differing from <code>&lt;txp:section /&gt;</code>, this tag will include the enclosed text as a link.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link category=&quot;apples&quot;&gt;Apples galore&lt;/txp:wet_link&gt;</code><br />\nLink to the category named “apples”.</p>\n</blockquote>\n\n<blockquote>\n	<p><code>&lt;txp:wet_link author=&quot;eve&quot;&gt;All articles by Eve&lt;/txp:wet_link&gt;</code><br />\nLink to a list of Eve&#8217;s articles.</p>\n</blockquote>\n\n<p>All of the above forms support the following attributes:</p>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n		</tr>\n		<tr>\n			<td><strong>href</strong></td>\n			<td>the <span class=\"caps\">URL</span> or article ID, as described above.</td>\n		</tr>\n		<tr>\n			<td><strong>linkid</strong></td>\n			<td>the link ID, as described above.</td>\n		</tr>\n		<tr>\n			<td><strong>section</strong></td>\n			<td>a section&#8217;s name to link to.</td>\n		</tr>\n		<tr>\n			<td><strong>category</strong></td>\n			<td>a category&#8217;s name to link to.</td>\n		</tr>\n		<tr>\n			<td><strong>author</strong></td>\n			<td>an author&#8217;s name to link to.</td>\n		</tr>\n		<tr>\n			<td><strong>image</strong></td>\n			<td>an image&#8217;s id to link to.</td>\n		</tr>\n		<tr>\n			<td><strong>class</strong></td>\n			<td><span class=\"caps\">CSS</span> class name.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>target</strong></td>\n			<td>link target.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>title</strong></td>\n			<td>link title.  Optional.  ‘%s’ may be used in links to article and link IDs, to include the article title or link description.</td>\n		</tr>\n		<tr>\n			<td><strong>rel</strong></td>\n			<td>link attribute “rel”.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>id</strong></td>\n			<td>link atribute “id”.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>name</strong></td>\n			<td>link attribute “name”.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>accesskey</strong></td>\n			<td>link attribute “accesskey”.  Optional.</td>\n		</tr>\n		<tr>\n			<td><strong>no_widow</strong></td>\n			<td> Suppress widows in titles. Optional.</td>\n		</tr>\n</table>\n\n\n<p>Either <strong>href</strong>, <strong>image</strong>, <strong>linkid</strong> or <strong>section</strong>, <strong>author</strong>, and <strong>category</strong> are mutually exclusive.</p>\n\n<p>Largely based upon <a href=\"http://thresholdstate.com/software/3702/zem_link\" rel=\"nofollow\">zem_link</a>. Thanks, Alex!</p>', 'function wet_link($atts, $anchortext=\'\') {\n	global $thisarticle, $s, $img_dir, $prefs;\n\n 	extract(lAtts(array(\n		\'href\' => \'\',\n		\'linkid\' => \'\',\n		\'title\' => \'%s\',\n		\'section\' => \'\',\n		\'author\' => \'\',\n		\'category\' => \'\',\n		\'image\' => \'\',\n		\'class\' => \'\',\n		\'target\' => \'\',\n		\'rel\' => \'\',\n		\'id\' => \'\',\n		\'name\' => \'\',\n		\'accesskey\' => \'\',\n 		\'no_widow\' => @$prefs[\'title_no_widow\']\n	),$atts));\n\n	if (!empty($class)) $class = \' class=\"\'.$class.\'\"\';\n	if (!empty($target)) $target = \' target=\"\'.$target.\'\"\';\n	if (!empty($rel)) $rel = \' rel=\"\'.$rel.\'\"\';\n	if (!empty($id)) $id = \' id=\"\'.$id.\'\"\';\n	if (!empty($name)) $name = \' name=\"\'.$name.\'\"\';\n	if (!empty($accesskey)) $accesskey = \' accesskey=\"\'.$accesskey.\'\"\';\n\n	if ($linkid) {\n		$rs = safe_row(\'*\', \'txp_link\', \'id=\\\'\'.$linkid.\'\\\'\');\n		if ($rs) {\n			$anchortext = (empty($anchortext) ? $rs[\'linkname\'] : $anchortext);\n			$title = sprintf($title, $rs[\'description\']);\n			$url = $rs[\'url\'];\n		}\n	}\n	elseif ($section.$author.$category != \'\') {\n		# list mode context\n		$url = pagelinkurl(array(\'s\'=>$section, \'c\'=>$category, \'author\'=>$author));\n		$title = sprintf($title, \'\');\n	}\n	elseif (!$href && !$image) {\n		# Permlink context\n		if (function_exists(\'assert_article\')) assert_article();\n		$title = sprintf($title, $thisarticle[\'title\']);\n		if (empty($anchortext)) $anchortext = $thisarticle[\'title\'];\n		$url = permlinkurl($thisarticle);\n	}\n	elseif (is_numeric($href)) {\n		# Link to an article by ID\n		$rs = safe_row(\'Title\',\'textpattern\',\'ID=\\\'\'.$href.\"\' and Status in (\'4\',\'5\') limit 1\");\n		if ($rs) {\n			$title = sprintf($title, $rs[\'Title\']);\n			$anchortext = (empty($anchortext) ? $rs[\'Title\'] : $anchortext);\n			$url = permlinkurl_id($href);\n		}\n	}\n	elseif (substr($href, 0, 5) == \'http:\' || substr($href, 0, 6) == \'https:\' || substr($href, 0, 4) == \'ftp:\') {\n		# External or absolute link\n		$url = $href;\n		$title = sprintf($title);\n	}\n	elseif ($image) {\n		$image = (int) $image;\n		$rs = safe_row(\'*\', \'txp_image\', \"id = $image limit 1\");\n		$title = sprintf($title, $rs[\'caption\']);\n		$anchortext = (empty($anchortext) ? $rs[\'name\'] : $anchortext);\n		$url = hu.$img_dir.\'/\'.$image.$rs[\'ext\'];\n	}\n	elseif ($href{0} == \'/\' || $href{0} == \'#\') {\n		# Internal link absolute or fragment identifier\n		$url = $href;\n		$title = sprintf($title);\n	}\n	else {\n		# Internal link relative to Textpattern root\n		$url = hu.$href;\n	}\n\n	$title = function_exists(\'escape_title\') ? escape_title($title) : htmlentities($title);\n	$title = ($title ? \' title=\"\'.$title.\'\"\' : \'\');\n\n	# error reporting\n	if (!isset($url)) {\n		$url = \'#\';\n		$anchortext = \'***\'.$anchortext.\'***\';\n		$title = \' title=\"wet_link cannot find item for atts=\'.htmlentities(var_export ($atts, TRUE)).\'\"\';\n	}\n\n	if (!empty($anchortext)) $anchortext = parse($anchortext);\n	if ($no_widow) $anchortext = noWidow($anchortext);\n	return \"<a href=\\\"$url\\\"{$title}{$class}{$target}{$rel}{$id}{$name}{$accesskey}>$anchortext</a>\";\n}\n\n\n// compatibility shell\nfunction zem_link($atts, $thing=\'\') {\n	return wet_link($atts, $thing);\n}\n', 'function wet_link($atts, $anchortext=\'\') {\n	global $thisarticle, $s, $img_dir, $prefs;\n\n 	extract(lAtts(array(\n		\'href\' => \'\',\n		\'linkid\' => \'\',\n		\'title\' => \'%s\',\n		\'section\' => \'\',\n		\'author\' => \'\',\n		\'category\' => \'\',\n		\'image\' => \'\',\n		\'class\' => \'\',\n		\'target\' => \'\',\n		\'rel\' => \'\',\n		\'id\' => \'\',\n		\'name\' => \'\',\n		\'accesskey\' => \'\',\n 		\'no_widow\' => @$prefs[\'title_no_widow\']\n	),$atts));\n\n	if (!empty($class)) $class = \' class=\"\'.$class.\'\"\';\n	if (!empty($target)) $target = \' target=\"\'.$target.\'\"\';\n	if (!empty($rel)) $rel = \' rel=\"\'.$rel.\'\"\';\n	if (!empty($id)) $id = \' id=\"\'.$id.\'\"\';\n	if (!empty($name)) $name = \' name=\"\'.$name.\'\"\';\n	if (!empty($accesskey)) $accesskey = \' accesskey=\"\'.$accesskey.\'\"\';\n\n	if ($linkid) {\n		$rs = safe_row(\'*\', \'txp_link\', \'id=\\\'\'.$linkid.\'\\\'\');\n		if ($rs) {\n			$anchortext = (empty($anchortext) ? $rs[\'linkname\'] : $anchortext);\n			$title = sprintf($title, $rs[\'description\']);\n			$url = $rs[\'url\'];\n		}\n	}\n	elseif ($section.$author.$category != \'\') {\n		# list mode context\n		$url = pagelinkurl(array(\'s\'=>$section, \'c\'=>$category, \'author\'=>$author));\n		$title = sprintf($title, \'\');\n	}\n	elseif (!$href && !$image) {\n		# Permlink context\n		if (function_exists(\'assert_article\')) assert_article();\n		$title = sprintf($title, $thisarticle[\'title\']);\n		if (empty($anchortext)) $anchortext = $thisarticle[\'title\'];\n		$url = permlinkurl($thisarticle);\n	}\n	elseif (is_numeric($href)) {\n		# Link to an article by ID\n		$rs = safe_row(\'Title\',\'textpattern\',\'ID=\\\'\'.$href.\"\' and Status in (\'4\',\'5\') limit 1\");\n		if ($rs) {\n			$title = sprintf($title, $rs[\'Title\']);\n			$anchortext = (empty($anchortext) ? $rs[\'Title\'] : $anchortext);\n			$url = permlinkurl_id($href);\n		}\n	}\n	elseif (substr($href, 0, 5) == \'http:\' || substr($href, 0, 6) == \'https:\' || substr($href, 0, 4) == \'ftp:\') {\n		# External or absolute link\n		$url = $href;\n		$title = sprintf($title);\n	}\n	elseif ($image) {\n		$image = (int) $image;\n		$rs = safe_row(\'*\', \'txp_image\', \"id = $image limit 1\");\n		$title = sprintf($title, $rs[\'caption\']);\n		$anchortext = (empty($anchortext) ? $rs[\'name\'] : $anchortext);\n		$url = hu.$img_dir.\'/\'.$image.$rs[\'ext\'];\n	}\n	elseif ($href{0} == \'/\' || $href{0} == \'#\') {\n		# Internal link absolute or fragment identifier\n		$url = $href;\n		$title = sprintf($title);\n	}\n	else {\n		# Internal link relative to Textpattern root\n		$url = hu.$href;\n	}\n\n	$title = function_exists(\'escape_title\') ? escape_title($title) : htmlentities($title);\n	$title = ($title ? \' title=\"\'.$title.\'\"\' : \'\');\n\n	# error reporting\n	if (!isset($url)) {\n		$url = \'#\';\n		$anchortext = \'***\'.$anchortext.\'***\';\n		$title = \' title=\"wet_link cannot find item for atts=\'.htmlentities(var_export ($atts, TRUE)).\'\"\';\n	}\n\n	if (!empty($anchortext)) $anchortext = parse($anchortext);\n	if ($no_widow) $anchortext = noWidow($anchortext);\n	return \"<a href=\\\"$url\\\"{$title}{$class}{$target}{$rel}{$id}{$name}{$accesskey}>$anchortext</a>\";\n}\n\n\n// compatibility shell\nfunction zem_link($atts, $thing=\'\') {\n	return wet_link($atts, $thing);\n}\n', 'd41d50f001b97de463ce2ea4d84d4452', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_instagram', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0', 'Display Instagram user infos and recent images.', '<style type=\"text/css\">\n.txp-layout-textbox { max-width: 100%; }\n</style>\n<script>\n  $( function() {\n    $( \"#tabs\" ).tabs().removeClass(\'ui-widget ui-widget-content ui-corner-all\').addClass( \"ui-tabs-vertical\" );\n    $(\'.switcher-list\').removeClass(\'ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all\');\n    $(\'.switcher-list li\').removeClass(\'ui-state-default ui-corner-top\');\n    $(\'.options_group\').removeClass(\'ui-widget-content ui-corner-bottom\');\n  } );\n</script>\n<div id=\"tabs\" class=\"txp-layout\">\n<div class=\"txp-layout-1col\">\n\n<h1 class=\"txp-heading\">oui_instagram</h1>\n\n</div>\n<div class=\"txp-layout-4col-alt\" id=\"oui_player_switcher\" role=\"region\">\n<section class=\"txp-details\" id=\"all_options\" aria-labelledby=\"all_options-label\">\n\n<h3 id=\"all_options-label\">Table of contents</h3>\n\n<div role=\"group\">\n\n<ul class=\"switcher-list\">\n	<li><a href=\"#basics\">Basics</a></li>\n	<li><a href=\"#prefs\">Preferences</a></li>\n	<li><a href=\"#oui_insta_author\">oui_insta_author</a></li>\n	<li><a href=\"#oui_insta_author_info\">oui_insta_author_info</a></li>\n	<li><a href=\"#oui_insta_images\">oui_insta_images</a></li>\n	<li><a href=\"#oui_insta_image\">oui_insta_image</a></li>\n	<li><a href=\"#oui_insta_image_info\">oui_insta_image_info</a></li>\n	<li><a href=\"#oui_insta_image_url\">oui_insta_image_url</a></li>\n	<li><a href=\"#oui_insta_image_date\">oui_insta_image_date</a></li>\n	<li><a href=\"#oui_insta_image_author\">oui_insta_image_author</a></li>\n	<li><a href=\"#examples\">Examples</a></li>\n	<li><a href=\"#credits\">Credits</a></li>\n</ul>\n\n</div>\n</section>\n\n<p><a class=\"navlink\" href=\"https://github.com/NicolasGraph/oui_insta\">Contribute</a> <a class=\"navlink\" href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=PREHX3MZMXEUN\">Donate</a> <a class=\"navlink\" href=\"https://twitter.com/intent/tweet?text=%23oui_insta%20plays%20nicely%20with%20%23Textpattern%20%23CMS%20to%20%23embed%20%23Instagram%20galleries%20on%20my%20website!%20%40NicolasGraph%20https://github.com/NicolasGraph/oui_insta\">Tweet</a></p>\n\n\n</div>\n<div class=\"txp-layout-4col-2span\">\n<section id=\"basics\" class=\"options_group\">\n\n<h2>Basics</h2>\n\n<p>Display Instagram user infos and recent images.</p>\n\n<h2>Plugin requirements</h2>\n\n<p>oui_instagram’s minimum requirements:</p>\n\n<ul>\n	<li>Textpattern 4.6+</li>\n</ul>\n\n<h2>Installation</h2>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_instagram/releases\">Download</a> the compiled plugin file.</li>\n	<li>Paste the content of the plugin file under the <strong>Admin &gt; Plugins</strong> and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click <em>Options</em> or visit your <strong>Admin&gt;Preferences</strong> tab to fill the plugin prefs.</li>\n</ol>\n\n<h2>Update</h2>\n\n<p>Unless contrary instructions, proceed as follow:</p>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n	<li>Disable and re-enable the plugin to update its preferences while keeping existing values untouched.</li>\n</ol>\n\n<h2>Uninstall</h2>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <strong>Admin &gt; Plugins</strong>.</li>\n	<li>open the select list at the bottom of the plugins tables and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n\n</section>\n<section id=\"prefs\" class=\"options_group\">\n\n<h2>Preferences/options</h2>\n\n<ul>\n	<li><strong>Access token</strong> &#8211; <em>Default: unset</em> &#8211; A valid Instagram access token.</li>\n</ul>\n\n<p class=\"alert-block information\"><span class=\"ui-icon ui-icon-info\">.</span> you can easily get an access token from Instagram or by using a webservice like <a href=\"http://instagram.pixelunion.net/\">Pixel Union</a>.</p>\n\n</section>\n<section id=\"oui_insta_author\" class=\"options_group\">\n\n<h2>oui_insta_author</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_author</code> tag is now deprecated.</p>\n\n<p>Displays an Instagram user related info as a single tag, or several ones as a container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_author /&gt;</code></pre>\n\n<p>or</p>\n\n<pre><code>&lt;txp:oui_insta_author&gt;\n[…]\n&lt;/txp:oui_insta_author&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>access_token</code></dt>\n	<dd><p>Instagram access token.<br />\n<strong>Default:</strong> the access token provided via the plugin prefs.<br />\n<strong>Valid:</strong> a different Instagram access token than the one provided via the plugin prefs</p></dd>\n	<dt><code>break</code></dt>\n	<dd><p>he <span class=\"caps\">HTML</span> tag used around each generated image<br />\n<strong>Default:</strong> <code>br</code><br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> tag without brakets</p></dd>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>form</code></dt>\n	<dd><p>A Textpattern form name if used to contain other plugin tags.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>label</code></dt>\n	<dd><p>The label used to entitled the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>labeltag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag used around the value assigned to <code>label</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n	<dt><code>link</code></dt>\n	<dd><p>To apply a link around each generated image to the standard_resolution image.<br />\n<strong>Default:</strong> <code>auto</code><br />\n<strong>Valid:</strong> <code>auto</code> (linked to the Instagram page), <code>1</code> (linked to the image url), <code>0</code></p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The author info to display.<br />\n<strong>Default:</strong> <code>username</code><br />\n<strong>Valid:</strong> <code>username</code>, <code>full_name</code>, <code>profile_picture</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> <code>span</code><br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_author_info\" class=\"options_group\">\n\n<h2>oui_insta_author_info</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_author_info</code> tag is now deprecated.</p>\n\n<p>Displays each author info in a <code>oui_insta_author</code> container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_author_info /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>link</code></dt>\n	<dd><p>To link the author info to its Instagram page or to his website.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <code>instagram</code>, <code>website</code></p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The information type to display.<br />\n<strong>Default:</strong> <code>caption</code><br />\n<strong>Valid:</strong> <code>id</code>, <code>username</code>, <code>full_name</code>, <code>avatar</code>, <code>bio</code>, <code>followers_count</code>, <code>following_count</code>, <code>medias_count</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_images\" class=\"options_group\">\n\n<h2>oui_insta_images</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_images</code> tag is now deprecated.</p>\n\n<p>Displays a recent images list.<br />\nCan be used as a single or a container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_images /&gt;</code></pre>\n\n<p>or</p>\n\n<pre><code>&lt;txp:oui_insta_images&gt;\n[…]\n&lt;/txp:oui_insta_images&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>user_id</code> and <code>username</code> attributes has been removed from v1.0.0, you can now use the <code>access_token</code> attribute as an alternative to generate Instagram galleries from different accounts.</p>\n\n<dl>\n	<dt><code>access_token</code></dt>\n	<dd><p>Instagram access token.<br />\n<strong>Default:</strong> the access token provided via the plugin prefs.<br />\n<strong>Valid:</strong> a different Instagram access token than the one provided via the plugin prefs</p></dd>\n	<dt><code>break</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag used around each generated image<br />\n<strong>Default:</strong> <code>li</code><br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> tag without brakets</p></dd>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>form</code></dt>\n	<dd><p>A Textpattern form name if used to contain other plugin tags.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>label</code></dt>\n	<dd><p>The label used to entitled the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>labeltag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag used around the value assigned to <code>label</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n	<dt><code>limit</code></dt>\n	<dd><p>The number of images to display.<br />\nIf the <code>limit</code> value is greater than <em>20</em>, several requests will be thrown to pull your images<br />\n<strong>Default:</strong> <code>10</code><br />\n<strong>Valid:</strong> integer</p></dd>\n	<dt><code>link</code></dt>\n	<dd><p>To apply a link around each generated image to the standard_resolution image.<br />\n<strong>Default:</strong> <code>auto</code><br />\n<strong>Valid:</strong> <code>auto</code> (linked to the Instagram page), <code>1</code> (linked to the image url), <code>0</code></p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The image type to display.<br />\n<strong>Default:</strong> <code>thumbnail</code><br />\n<strong>Valid:</strong> <code>thumbnail</code>, <code>low_resolution</code>, <code>standard_resolution</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> <code>ul</code><br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_image\" class=\"options_group\">\n\n<h2>oui_insta_image</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_image</code> tag is now deprecated.</p>\n\n<p>Displays each image in a <code>oui_insta_images</code> container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_image /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <code>img</code> <span class=\"caps\">HTML</span> tag or to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code><br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The image type to display.<br />\n<strong>Default:</strong> value of the <code>type</code> attribute of the container tag.<br />\n<strong>Valid:</strong> <code>thumbnail</code>, <code>low_resolution</code>, <code>standard_resolution</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_image_info\" class=\"options_group\">\n\n<h2>oui_insta_image_info</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_image_info</code> tag is now deprecated.</p>\n\n<p>Displays each image info in a <code>oui_insta_images</code> container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_image_info /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>break</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag used around each generated info.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> self-closing tag without brakets</p></dd>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The information type to display.<br />\n<strong>Default:</strong> <code>caption</code><br />\n<strong>Valid:</strong> <code>caption</code>, <code>comments_count</code> (<code>comments</code> is now deprecated), <code>likes_count</code> (<code>likes</code> is now deprecated), <code>w</code>, <code>h</code>, <code>low_w</code>, <code>low_h</code>, <code>thumb_w</code>, <code>thumb_h</code>, <code>location</code>, <code>address</code>, <code>latitude</code>, <code>longitude</code>, <code>id, </code>type@, <code>date</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_image_url\" class=\"options_group\">\n\n<h2>oui_insta_image_url</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_image_url</code> tag is now deprecated.</p>\n\n<pre><code>&lt;txp:oui_insta_image_url /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <code>a</code> <span class=\"caps\">HTML</span> tag if link is defined or to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>link</code></dt>\n	<dd><p>To apply a link to the standard_resolution image.<br />\n<strong>Default:</strong> <code>auto</code><br />\n<strong>Valid:</strong> <code>auto</code> (link container tag only), <code>1</code>, <code>0</code></p></dd>\n	<dt><code>type</code></dt>\n	<dd><p>The url type to use.<br />\n<strong>Default:</strong> <code>instagram</code><br />\n<strong>Valid:</strong> <code>thumbnail</code>, <code>low_resolution</code>, <code>standard_resolution</code>, <code>instagram</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_image_date\" class=\"options_group\">\n\n<h2>oui_insta_image_date</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_images_date</code> tag is now deprecated.</p>\n\n<p>Displays each image date in a <code>oui_insta_images</code> container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_image_date /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>format</code></dt>\n	<dd><p>To adjust the display of the date to taste.<br />\n<strong>Default:</strong> the Archive date format set in the preferences<br />\n<strong>Valid:</strong> strftime() string values</p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"oui_insta_image_author\" class=\"options_group\">\n\n<h2>oui_insta_image_author</h2>\n\n<p class=\"alert-block warning\"><span class=\"ui-icon ui-icon-alert\">.</span> <code>oui_instagram_image_author</code> tag is now deprecated.</p>\n\n<p>Displays each image author in a <code>oui_insta_images</code> container tag.</p>\n\n<pre><code>&lt;txp:oui_insta_image_author /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<dl>\n	<dt><code>class</code></dt>\n	<dd><p>The css class to apply to the <code>a</code> <span class=\"caps\">HTML</span> tag assigned by <code>link=&quot;1&quot;</code> or to the <span class=\"caps\">HTML</span> tag assigned to <code>wraptag</code>.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> string</p></dd>\n	<dt><code>link</code></dt>\n	<dd><p>To apply a link around the generated content.<br />\n<strong>Default:</strong> <code>0</code><br />\n<strong>Valid:</strong> <code>0</code> or <code>1</code></p></dd>\n	<dt><code>title</code></dt>\n	<dd><p>To show the full name (1) or the username (0).<br />\n<strong>Default:</strong> <code>1</code><br />\n<strong>Valid:</strong> <code>0</code> or <code>1</code></p></dd>\n	<dt><code>wraptag</code></dt>\n	<dd><p>The <span class=\"caps\">HTML</span> tag to use around the generated content.<br />\n<strong>Default:</strong> none<br />\n<strong>Valid:</strong> <span class=\"caps\">HTML</span> container tag without brakets</p></dd>\n</dl>\n\n</section>\n<section id=\"examples\" class=\"options_group\">\n\n<h2>Examples</h2>\n\n<h3 id=\"single_tag\">Example 1: single tag use</h3>\n\n<pre class=\"language-markup\"><code>// Instagram username as a link to the related account.\n&lt;txp:oui_insta_user /&gt;\n\n// List of recent Instagram images linked to their related Instagram pages.\n&lt;txp:oui_insta_images /&gt;</code></pre>\n\n<h3 id=\"container_tag\">Example 2: container tag use</h3>\n\n<pre class=\"language-markup\"><code>// Instagram avatar as a link to the related account.\n&lt;txp:oui_insta_user&gt;\n    &lt;txp:oui_insta_user_info type=&quot;avatar&quot; link=&quot;Instagram&quot; /&gt;\n&lt;txp:oui_insta_user&gt;\n\n// List of recent Instagram images linked to their related Instagram pages.\n// Each image is folowed by its caption, its author and its post date.\n&lt;txp:oui_insta_images&gt;\n    &lt;txp:oui_insta_image_url&gt;&lt;txp:oui_insta_image /&gt;&lt;/txp:oui_insta_image_url&gt;\n    &lt;txp:oui_insta_image_info /&gt;\n    &lt;txp:oui_insta_image_author /&gt;, &lt;txp:oui_insta_image_date /&gt;\n&lt;/txp:oui_insta_images&gt;</code></pre>\n\n</section>\n<section id=\"credits\" class=\"options_group\">\n\n<h2>Author</h2>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h2>Licence</h2>\n\n<p>This plugin is distributed under <a href=\"http://www.gnu.org/licenses/gpl-2.0.fr.html\">GPLv2</a>.</p>\n\n<h2>Changelog</h2>\n\n<ul>\n	<li><strong>v1.0.0</strong> (2017-01-30) :\n	<ul>\n		<li>deprecates <code>oui_instagram_</code> tags in favour of shorter <code>oui_insta_</code> tags;</li>\n		<li>adds missing spaces in <code>img</code> tags;</li>\n		<li>adds <code>&lt;txp:oui_insta_author /&gt;</code> and <code>&lt;txp:oui_insta_author_info /&gt;</code> tags;</li>\n		<li>removes cache (an external cache system such <a href=\"http://forum.textpattern.com/viewtopic.php?id=33460\">aks_cache</a> should be used);</li>\n		<li>restores the <code>access_token</code> attribute and removes <code>username</code> and <code>user_id</code> which needs restricted Instagram registration;</li>\n		<li>introduces an attribute values inheritance in a container tag context;</li>\n		<li>changes <code>&lt;txp:oui_insta_image_author /&gt;</code> <code>title</code> attribute to <code>type</code>;</li>\n		<li>uses <span class=\"caps\">CURL</span> to get the Instagram feed when enable;</li>\n		<li>uses <span class=\"caps\">OOP</span>;</li>\n		<li>styles Help file.</li>\n	</ul></li>\n	<li><strong>v0.6.8</strong> (2016-05-23) :\n	<ul>\n		<li>removes Username and user id preferences: access token related account used by default via self/media;</li>\n		<li>adds pop helps (Txp4.6 only);</li>\n		<li>Improves multiple requests with Instagram pagination.</li>\n	</ul></li>\n	<li><strong>v0.6.7</strong> (2016-05-13) :\n	<ul>\n		<li>fixes multiple galleries support;</li>\n		<li>improves user id request;</li>\n		<li>automatically fills user id in plugin prefs.</li>\n	</ul></li>\n	<li><strong>v0.6.6</strong> (2016-05-12) :\n	<ul>\n		<li>changes Cache time from seconds to minutes (5 by default);</li>\n		<li>hides the hash key pref (not really useful to show it and may be confusing);</li>\n		<li>fixes prefs order;</li>\n		<li>improves cache conditioning;</li>\n		<li>adds <code>update_lastmod()</code> after each Instagram query;</li>\n		<li>improves code comments.</li>\n	</ul></li>\n	<li><strong>v0.6.5</strong> (2016-04-30) :\n	<ul>\n		<li>adds Instagram feed pagination support.</li>\n	</ul></li>\n	<li><strong>v0.6.4:</strong> (2016-04-27) :\n	<ul>\n		<li>adds username, user_id and cache_time default values via plugin prefs;</li>\n		<li>replaces <code>hash_key</code> by a random plugin pref value;</li>\n		<li>updates the help file.</li>\n	</ul></li>\n	<li><strong>v0.6.3:</strong> (2016-04-20) :\n	<ul>\n		<li>code rework;</li>\n		<li>manages Instagram access token via plugin prefs;</li>\n		<li>adds a <code>user_id</code> attribute (faster than username);</li>\n		<li>improves error messages;</li>\n		<li>fixes bugs.</li>\n	</ul></li>\n	<li><strong>v0.5.9</strong> (2016-03-22) :\n	<ul>\n		<li>adds <code>access_token</code> attribute;</li>\n		<li>uses a shorter cache file key with <code>hash_key</code> attribute;</li>\n		<li>updates the help file.</li>\n	</ul></li>\n	<li><strong>Initial release</strong> (2016-03-11) :</li>\n</ul>\n\n</section>\n</div>\n</div>', '\n\n/*\n * oui_insta - Easily display recent images from an Instagram account.\n *\n * https://github.com/NicolasGraph/oui_insta\n *\n * Copyright (C) 2016 Nicolas Morand\n *\n * This file is part of oui_insta.\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n\nnamespace Oui\\Instagram {\n\n    class Admin\n    {\n\n        public function __construct()\n        {\n            /**\n             * Register callbacks.\n             */\n            if (txpinterface === \'admin\') {\n                add_privs(\'prefs.oui_instagram\', \'1\');\n                add_privs(\'plugin_prefs.oui_instagram\', \'1\');\n\n                register_callback(array($this, \'lifeCycle\'), \'plugin_lifecycle.oui_instagram\');\n                register_callback(array($this, \'setPref\'), \'prefs\', null, 1);\n                register_callback(array($this, \'options\'), \'plugin_prefs.oui_instagram\', null, 1);\n            } else {\n                /**\n                 * Register tags.\n                 */\n                \\Txp::get(\'\\Textpattern\\Tag\\Registry\')\n                    ->register(\'oui_insta_author\')\n                    ->register(\'oui_insta_author_info\')\n                    ->register(\'oui_insta_images\')\n                    ->register(\'oui_insta_image\')\n                    ->register(\'oui_insta_image_info\')\n                    ->register(\'oui_insta_image_url\')\n                    ->register(\'oui_insta_image_date\')\n                    ->register(\'oui_insta_image_author\');\n            }\n        }\n\n        /**\n         * Handler for plugin lifecycle events.\n         *\n         * @param string $evt Textpattern action event\n         * @param string $stp Textpattern action step\n         */\n        public function lifeCycle($evt, $stp)\n        {\n            switch ($stp) {\n                case \'enabled\':\n                    $this->setPref();\n                    break;\n                case \'deleted\':\n                    remove_pref(null, \'oui_instagram\');\n                    safe_delete(\'txp_lang\', \"owner LIKE \'oui\\_instagram\'\");\n                    break;\n            }\n        }\n\n        /**\n         * Jump to the prefs panel.\n         */\n        public function options()\n        {\n            $url = \'?event=prefs#prefs_group_oui_instagram\';\n            header(\'Location: \' . $url);\n        }\n\n        /**\n         * Set the access token pref.\n         */\n        public function setPref()\n        {\n            $pref = \'oui_instagram_access_token\';\n            if (get_pref($pref, null) === null) {\n                set_pref($pref, \'\', \'oui_instagram\', PREF_PLUGIN, \'text_input\');\n            }\n        }\n    }\n\n    new Admin;\n\n}\n\n\n\nnamespace Oui\\Instagram {\n\n    class Main\n    {\n        public $access_token;\n        public $limit = 10;\n\n        private $api = \'https://api.instagram.com/v1/users/self/\';\n\n        public function __construct()\n        {\n            $this->access_token = get_pref(\'oui_instagram_access_token\');\n        }\n        /**\n         * Get the Instagram recent feed\n         */\n        public function getProfile()\n        {\n            if ($this->access_token) {\n                $query = $this->api . \'?access_token=\' . $this->access_token;\n\n                if (function_exists(\'curl_version\')) {\n                    $ch = curl_init($query);\n                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n                    $result = json_decode(curl_exec($ch));\n                } else {\n                    $result = json_decode(file_get_contents($query));\n                }\n\n                if ($result->meta->code==\'200\') {\n                    return $result->data;\n                } else {\n                    trigger_error(\'oui_insta was not able to get your profile.\');\n                    return;\n                }\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public function getAuthor($type = \'username\', $link = \'instagram\')\n        {\n            $profile = $this->getProfile();\n            return self::getAuthorInfo($profile, $type, $link);\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public static function getAuthorInfo($profile, $type = \'username\', $link = \'instagram\')\n        {\n            $infos = array(\n                \'id\'              => $profile->{\'id\'},\n                \'username\'        => $profile->{\'username\'},\n                \'avatar\'          => $profile->{\'profile_picture\'},\n                \'full_name\'       => $profile->{\'full_name\'},\n                \'bio\'             => $profile->{\'bio\'},\n                \'website\'         => $profile->{\'website\'},\n                \'followers_count\' => $profile->{\'counts\'}->{\'followed_by\'},\n                \'following_count\' => $profile->{\'counts\'}->{\'follows\'},\n                \'medias_count\'    => $profile->{\'counts\'}->{\'media\'},\n            );\n\n            if (array_key_exists($type, $infos)) {\n                $info = $infos[$type];\n            } else {\n                trigger_error(\'Unknown author info.\');\n            }\n\n            if ($type === \'avatar\') {\n                $info = \'<img src=\"\' . $info . \'\" width=\"150\" height=\"150\" alt=\"\' . $profile->{\'username\'} . \'\" />\';\n            }\n\n            if ($link === \'instagram\') {\n                $username = $profile->{\'username\'};\n                $url = \'https://www.instagram.com/\' . $username;\n                $title = $username . gtxt(\'on_instagram\');\n            } elseif ($link === \'website\') {\n                $username = $profile->{\'username\'};\n                $url = $profile->{\'website\'};\n                $title = $username . gtxt(\'s_website\');\n            }\n\n            return $link ? href($info, $url, $title) : $img;\n        }\n\n        /**\n         * Get the Instagram recent feed\n         */\n        public function getFeed()\n        {\n            if ($this->access_token) {\n                $pages = ceil($this->limit / 20);\n                $shots = array();\n\n                for ($page = 1; $page <= $pages; $page++) {\n                    $count = ($page == $pages) ? ($this->limit % 20) : \'20\';\n                    $from = isset($next) ? $next : \'\';\n                    $query = $this->api . \'media/recent?access_token=\' . $this->access_token . \'&count=\' . $count . $from;\n\n                    if (function_exists(\'curl_version\')) {\n                        $ch = curl_init($query);\n                        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n                        $results = json_decode(curl_exec($ch));\n                    } else {\n                        $results = json_decode(file_get_contents($query));\n                    }\n\n                    if ($page != $pages) {\n                        $next = \'&max_id=\' . $results->{\'pagination\'}->{\'next_max_id\'};\n                    }\n\n                    if ($results->meta->code==\'200\') {\n                        $shots = array_merge($shots, $results->data);\n                    } else {\n                        trigger_error(\n                            \'oui_insta was not able to get your \' . $this->limit . \' last shots. \'\n                            . $results->meta->error_message\n                        );\n                    }\n                }\n                return $shots;\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get a gallery from the recent shots.\n         *\n         * @param string $type Image resolution\n         * @param string $link Images are linked to…\n         */\n        public function getImages($type = \'thumbnail\', $link = \'auto\')\n        {\n            if ($this->access_token) {\n                $shots = $this->getfeed();\n                if ($shots) {\n                    foreach ($shots as $shot) {\n                        $out[] = self::getImage($shot, $type, $link);\n                    }\n                } else {\n                    trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                    return;\n                }\n                return $out;\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type Image resolution\n         * @param string $link Images are linked to…\n         */\n        public static function getImage($shot, $type = \'thumbnail\', $link = \'auto\')\n        {\n            $src = \'src=\"\' . $shot->{\'images\'}->{$type}->{\'url\'}.\'\" \';\n            $width = \'width=\"\' . $shot->{\'images\'}->{$type}->{\'width\'}.\'\" \';\n            $height = \'height=\"\' . $shot->{\'images\'}->{$type}->{\'height\'}.\'\" \';\n\n            if (isset($shot->{\'caption\'}->{\'text\'})) {\n                $alt = \'title=\"\' . $shot->{\'caption\'}->{\'text\'}.\'\" \';\n            } else {\n                $alt = \' \';\n            }\n\n            $title = $alt;\n            $url = ($link === \'auto\') ? $shot->{\'link\'} : $shot->{\'images\'}->{$type}->{\'url\'};\n            $img = \'<img \' . $src . $alt . $width . $height . \'/>\';\n\n            return $link ? href($img, $url, $title) : $img;\n        }\n\n        /**\n         * Get a defined shot url.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type Image resolution\n         */\n        public static function getImageUrl($shot, $type = \'thumbnail\')\n        {\n            $validTypes = array(\'instagram\', \'thumbnail\', \'low_resolution\', \'standard_resolution\');\n\n            if (in_array($type, $validTypes)) {\n                $out = ($type == \'instagram\') ? $shot->{\'link\'} : $shot->{\'images\'}->{$type}->{\'url\'};\n                return $out;\n            } else {\n                trigger_error(\n                    \"unknown attribute value;\n                    oui_insta_image_url type attribute accepts the following values:\n                    instagram, thumbnail, low_resolution, standard_resolution\"\n                );\n                return;\n            }\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public static function getImageInfo($shot, $type = \'caption\')\n        {\n            $infos = array(\n                \'id\'             => $shot->{\'id\'},\n                \'type\'           => $shot->{\'type\'},\n                \'date\'           => $shot->{\'created_time\'},\n                \'w\'              => $shot->{\'images\'}->{\'standard_resolution\'}->{\'width\'},\n                \'h\'              => $shot->{\'images\'}->{\'standard_resolution\'}->{\'height\'},\n                \'thumb_w\'        => $shot->{\'images\'}->{\'thumbnail\'}->{\'width\'},\n                \'thumb_h\'        => $shot->{\'images\'}->{\'low_resolution\'}->{\'height\'},\n                \'low_w\'          => $shot->{\'images\'}->{\'low_resolution\'}->{\'width\'},\n                \'low_h\'          => $shot->{\'images\'}->{\'thumbnail\'}->{\'height\'},\n                \'comments\'       => $shot->{\'comments\'}->{\'count\'},\n                \'comments_count\' => $shot->{\'comments\'}->{\'count\'},\n                \'likes\'          => $shot->{\'likes\'}->{\'count\'},\n                \'likes_count\'    => $shot->{\'likes\'}->{\'count\'},\n                \'caption\'        => isset($shot->{\'caption\'}->{\'text\'}) ? $shot->{\'caption\'}->{\'text\'} : \'\',\n                \'location\'       => isset($shot->{\'location\'}->{\'name\'}) ? $shot->{\'location\'}->{\'name\'} : \'\',\n                \'address\'        => isset($shot->{\'location\'}->{\'street_address\'}) ? $shot->{\'location\'}->{\'street_address\'} : \'\',\n                \'latitude\'       => isset($shot->{\'location\'}->{\'latitude\'}) ? $shot->{\'location\'}->{\'latitude\'} : \'\',\n                \'longitude\'      => isset($shot->{\'location\'}->{\'longitude\'}) ? $shot->{\'location\'}->{\'longitude\'} : \'\',\n            );\n\n            if (array_key_exists($type, $infos)) {\n                return $infos[$type];\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n        }\n\n        /**\n         * Get the post date of a defined shot.\n         *\n         * @param object $shot   shot related feed portion\n         * @param string $format Date format\n         */\n        public static function getImageDate($shot, $format = \'\')\n        {\n            return fileDownloadFormatTime(array(\n                \'ftime\'  => $shot->{\'created_time\'},\n                \'format\' => $format,\n            ));\n        }\n\n        /**\n         * Get defined shot author.\n         *\n         * @param object $shot  shot related feed portion\n         * @param bool   $title Image resolution\n         * @param bool   $link  Link to…\n         */\n        public static function getImageAuthor($shot, $type = \'username\', $link = 0)\n        {\n            $infos = array(\n                \'username\' => $shot->{\'user\'}->{\'username\'},\n                \'id\'       => $shot->{\'user\'}->{\'id\'},\n                \'avatar\'   => $shot->{\'user\'}->{\'profile_picture\'},\n            );\n\n            if (array_key_exists($type, $infos)) {\n                $author = $infos[$type];\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n\n            if ($type === \'avatar\') {\n                $alt = $shot->{\'user\'}->{\'username\'};\n                $author = \'<img src=\"\' . $author . \'\" width=\"150\" height=\"150\" alt=\" alt=\"\' . $alt . \'\" />\';\n            }\n\n            if ($author) {\n                return $link ? href($author, \'http://instagram.com/\'.$username) : $author;\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n        }\n    }\n}\n\n\n\nnamespace {\n\n    /**\n     * Main plugin function.\n     *\n     * Pull images if needed;\n     * parse and cache the gallery;\n     * display the content.\n     */\n    function oui_insta_images($atts, $thing = null)\n    {\n        global $oui_insta_shot, $oui_insta_type;\n\n        extract(lAtts(array(\n            \'access_token\' => get_pref(\'oui_instagram_access_token\'),\n            \'limit\'        => \'10\',\n            \'form\'         => \'\',\n            \'type\'         => \'thumbnail\',\n            \'link\'         => \'auto\',\n            \'wraptag\'      => \'ul\',\n            \'class\'        => \'\',\n            \'break\'        => \'li\',\n            \'label\'        => \'\',\n            \'labeltag\'     => \'\',\n        ), $atts));\n\n        $insta = \'Oui\\Instagram\\Main\';\n        $obj = new $insta;\n        $obj->access_token = $access_token;\n        $obj->limit = $limit;\n\n        // Check the query result.\n        if ($thing || $form) {\n            $shots = $obj->getFeed();\n            if ($shots) {\n                foreach ($shots as $shot) {\n                    $oui_insta_shot = $shot;\n                    $oui_insta_type = $type;\n                    $out[] = $thing ? parse($thing) : parse_form($form);\n                }\n                unset(\n                    $GLOBALS[\'oui_insta_shot\'],\n                    $GLOBALS[\'oui_insta_type\']\n                );\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                return;\n            }\n        } else {\n            $images = $obj->getImages($type, $link);\n            if ($images) {\n                $out = $images;\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram images.\');\n                return;\n            }\n        }\n\n        $out = doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n        update_lastmod();\n\n        return $out;\n    }\n\n    // Deprecated\n    function oui_instagram_images($atts, $thing = null)\n    {\n        return oui_insta_images($atts, $thing);\n    }\n\n    /**\n     * Display each image in a oui_insta_images context.\n     */\n    function oui_insta_image($atts)\n    {\n        global $oui_insta_shot, $oui_insta_type;\n\n        extract(lAtts(array(\n            \'link\'    => \'auto\',\n            \'type\'    => $oui_insta_type,\n            \'class\'   => \'\',\n            \'wraptag\' => \'\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImage($oui_insta_shot, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image($atts)\n    {\n        return oui_insta_image($atts);\n    }\n\n    /**\n     * Display each image url in a oui_insta_images context.\n     */\n    function oui_insta_image_url($atts, $thing = null)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'type\'    => \'instagram\',\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'link\'    => \'auto\',\n        ), $atts));\n\n        $url = Oui\\Instagram\\Main::getImageUrl($oui_insta_shot, $type, $link);\n\n        $validLinks = array(\'auto\', \'1\', \'0\');\n\n        if (in_array($link, $validLinks)) {\n            $link = ($link == \'auto\') ? (($thing) ? 1 : 0) : $link;\n            $out = ($thing) ? parse($thing) : $url;\n            $out = ($link) ? href($out, $url, ($wraptag) ? \'\' : \' class=\"\'.$class.\'\"\') : $out;\n        } else {\n            trigger_error(\n                \"unknown attribute value;\n                oui_insta_image_url link attribute accepts the following values:\n                auto, 1, 0\"\n            );\n        }\n\n        return $out ? doTag($out, $wraptag, $class) : \'\';\n    }\n\n    // Deprecated\n    function oui_instagram_image_url($atts, $thing = null)\n    {\n        return oui_insta_image_url($atts, $thing);\n    }\n\n    /**\n     * Display each image information in a oui_insta_images context.\n     */\n    function oui_insta_image_info($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'type\'    => \'caption\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageInfo($oui_insta_shot, $type);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_info($atts)\n    {\n        return oui_insta_image_info($atts);\n    }\n\n    /**\n     * Display each image date in a oui_insta_images context.\n     */\n    function oui_insta_image_date($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'format\'  => \'\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageDate($oui_insta_shot, $format);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_date($atts)\n    {\n        return oui_insta_image_date($atts);\n    }\n\n    /**\n     * Display each image author in a oui_insta_images context.\n     */\n    function oui_insta_image_author($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'link\'    => 0,\n            \'type\'   => \'username\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageAuthor($oui_insta_shot, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_author($atts)\n    {\n        return oui_insta_image_author($atts);\n    }\n\n    /**\n     * Main plugin function.\n     *\n     * Pull images if needed;\n     * parse and cache the gallery;\n     * display the content.\n     */\n    function oui_insta_author($atts, $thing = null)\n    {\n        global $oui_insta_profile;\n\n        extract(lAtts(array(\n            \'access_token\' => \'\',\n            \'type\'         => \'username\',\n            \'link\'         => \'instagram\',\n            \'wraptag\'      => \'p\',\n            \'form\'         => \'\',\n            \'class\'        => \'\',\n            \'break\'        => \'br\',\n            \'label\'        => \'\',\n            \'labeltag\'     => \'\',\n        ), $atts));\n\n        $access_token ?: $access_token = get_pref(\'oui_instagram_access_token\');\n\n        $insta = \'Oui\\Instagram\\Main\';\n        $obj = new $insta;\n        $obj->access_token = $access_token;\n\n        // Check the query result.\n        if ($thing || $form) {\n            $profile = $obj->getProfile();\n            if ($profile) {\n                $oui_insta_profile = $profile;\n                $out[] = $thing ? parse($thing) : $parse_form($form);\n                unset($GLOBALS[\'oui_insta_profile\']);\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                return;\n            }\n        } else {\n            $info = $obj->getAuthor($type, $link);\n            if ($info) {\n                $out[] = $info;\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram images.\');\n                return;\n            }\n        }\n\n        $out = doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n        update_lastmod();\n\n        return $out;\n    }\n\n    // Deprecated\n    function oui_instagram_author($atts, $thing = null)\n    {\n        return oui_insta_author($atts, $thing);\n    }\n\n    /**\n     * Display each image information in a oui_insta_images context.\n     */\n    function oui_insta_author_info($atts)\n    {\n        global $oui_insta_profile;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'type\'    => \'username\',\n            \'link\'    => \'instagram\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getAuthorInfo($oui_insta_profile, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_author_info($atts, $thing = null)\n    {\n        return oui_insta_author_info($atts, $thing);\n    }\n}\n', '\n\n/*\n * oui_insta - Easily display recent images from an Instagram account.\n *\n * https://github.com/NicolasGraph/oui_insta\n *\n * Copyright (C) 2016 Nicolas Morand\n *\n * This file is part of oui_insta.\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n */\n\nnamespace Oui\\Instagram {\n\n    class Admin\n    {\n\n        public function __construct()\n        {\n            /**\n             * Register callbacks.\n             */\n            if (txpinterface === \'admin\') {\n                add_privs(\'prefs.oui_instagram\', \'1\');\n                add_privs(\'plugin_prefs.oui_instagram\', \'1\');\n\n                register_callback(array($this, \'lifeCycle\'), \'plugin_lifecycle.oui_instagram\');\n                register_callback(array($this, \'setPref\'), \'prefs\', null, 1);\n                register_callback(array($this, \'options\'), \'plugin_prefs.oui_instagram\', null, 1);\n            } else {\n                /**\n                 * Register tags.\n                 */\n                \\Txp::get(\'\\Textpattern\\Tag\\Registry\')\n                    ->register(\'oui_insta_author\')\n                    ->register(\'oui_insta_author_info\')\n                    ->register(\'oui_insta_images\')\n                    ->register(\'oui_insta_image\')\n                    ->register(\'oui_insta_image_info\')\n                    ->register(\'oui_insta_image_url\')\n                    ->register(\'oui_insta_image_date\')\n                    ->register(\'oui_insta_image_author\');\n            }\n        }\n\n        /**\n         * Handler for plugin lifecycle events.\n         *\n         * @param string $evt Textpattern action event\n         * @param string $stp Textpattern action step\n         */\n        public function lifeCycle($evt, $stp)\n        {\n            switch ($stp) {\n                case \'enabled\':\n                    $this->setPref();\n                    break;\n                case \'deleted\':\n                    remove_pref(null, \'oui_instagram\');\n                    safe_delete(\'txp_lang\', \"owner LIKE \'oui\\_instagram\'\");\n                    break;\n            }\n        }\n\n        /**\n         * Jump to the prefs panel.\n         */\n        public function options()\n        {\n            $url = \'?event=prefs#prefs_group_oui_instagram\';\n            header(\'Location: \' . $url);\n        }\n\n        /**\n         * Set the access token pref.\n         */\n        public function setPref()\n        {\n            $pref = \'oui_instagram_access_token\';\n            if (get_pref($pref, null) === null) {\n                set_pref($pref, \'\', \'oui_instagram\', PREF_PLUGIN, \'text_input\');\n            }\n        }\n    }\n\n    new Admin;\n\n}\n\n\n\nnamespace Oui\\Instagram {\n\n    class Main\n    {\n        public $access_token;\n        public $limit = 10;\n\n        private $api = \'https://api.instagram.com/v1/users/self/\';\n\n        public function __construct()\n        {\n            $this->access_token = get_pref(\'oui_instagram_access_token\');\n        }\n        /**\n         * Get the Instagram recent feed\n         */\n        public function getProfile()\n        {\n            if ($this->access_token) {\n                $query = $this->api . \'?access_token=\' . $this->access_token;\n\n                if (function_exists(\'curl_version\')) {\n                    $ch = curl_init($query);\n                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n                    $result = json_decode(curl_exec($ch));\n                } else {\n                    $result = json_decode(file_get_contents($query));\n                }\n\n                if ($result->meta->code==\'200\') {\n                    return $result->data;\n                } else {\n                    trigger_error(\'oui_insta was not able to get your profile.\');\n                    return;\n                }\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public function getAuthor($type = \'username\', $link = \'instagram\')\n        {\n            $profile = $this->getProfile();\n            return self::getAuthorInfo($profile, $type, $link);\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public static function getAuthorInfo($profile, $type = \'username\', $link = \'instagram\')\n        {\n            $infos = array(\n                \'id\'              => $profile->{\'id\'},\n                \'username\'        => $profile->{\'username\'},\n                \'avatar\'          => $profile->{\'profile_picture\'},\n                \'full_name\'       => $profile->{\'full_name\'},\n                \'bio\'             => $profile->{\'bio\'},\n                \'website\'         => $profile->{\'website\'},\n                \'followers_count\' => $profile->{\'counts\'}->{\'followed_by\'},\n                \'following_count\' => $profile->{\'counts\'}->{\'follows\'},\n                \'medias_count\'    => $profile->{\'counts\'}->{\'media\'},\n            );\n\n            if (array_key_exists($type, $infos)) {\n                $info = $infos[$type];\n            } else {\n                trigger_error(\'Unknown author info.\');\n            }\n\n            if ($type === \'avatar\') {\n                $info = \'<img src=\"\' . $info . \'\" width=\"150\" height=\"150\" alt=\"\' . $profile->{\'username\'} . \'\" />\';\n            }\n\n            if ($link === \'instagram\') {\n                $username = $profile->{\'username\'};\n                $url = \'https://www.instagram.com/\' . $username;\n                $title = $username . gtxt(\'on_instagram\');\n            } elseif ($link === \'website\') {\n                $username = $profile->{\'username\'};\n                $url = $profile->{\'website\'};\n                $title = $username . gtxt(\'s_website\');\n            }\n\n            return $link ? href($info, $url, $title) : $img;\n        }\n\n        /**\n         * Get the Instagram recent feed\n         */\n        public function getFeed()\n        {\n            if ($this->access_token) {\n                $pages = ceil($this->limit / 20);\n                $shots = array();\n\n                for ($page = 1; $page <= $pages; $page++) {\n                    $count = ($page == $pages) ? ($this->limit % 20) : \'20\';\n                    $from = isset($next) ? $next : \'\';\n                    $query = $this->api . \'media/recent?access_token=\' . $this->access_token . \'&count=\' . $count . $from;\n\n                    if (function_exists(\'curl_version\')) {\n                        $ch = curl_init($query);\n                        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n                        $results = json_decode(curl_exec($ch));\n                    } else {\n                        $results = json_decode(file_get_contents($query));\n                    }\n\n                    if ($page != $pages) {\n                        $next = \'&max_id=\' . $results->{\'pagination\'}->{\'next_max_id\'};\n                    }\n\n                    if ($results->meta->code==\'200\') {\n                        $shots = array_merge($shots, $results->data);\n                    } else {\n                        trigger_error(\n                            \'oui_insta was not able to get your \' . $this->limit . \' last shots. \'\n                            . $results->meta->error_message\n                        );\n                    }\n                }\n                return $shots;\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get a gallery from the recent shots.\n         *\n         * @param string $type Image resolution\n         * @param string $link Images are linked to…\n         */\n        public function getImages($type = \'thumbnail\', $link = \'auto\')\n        {\n            if ($this->access_token) {\n                $shots = $this->getfeed();\n                if ($shots) {\n                    foreach ($shots as $shot) {\n                        $out[] = self::getImage($shot, $type, $link);\n                    }\n                } else {\n                    trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                    return;\n                }\n                return $out;\n            } else {\n                trigger_error(\'oui_insta is missing an Instagram access token.\');\n                return;\n            }\n        }\n\n        /**\n         * Get a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type Image resolution\n         * @param string $link Images are linked to…\n         */\n        public static function getImage($shot, $type = \'thumbnail\', $link = \'auto\')\n        {\n            $src = \'src=\"\' . $shot->{\'images\'}->{$type}->{\'url\'}.\'\" \';\n            $width = \'width=\"\' . $shot->{\'images\'}->{$type}->{\'width\'}.\'\" \';\n            $height = \'height=\"\' . $shot->{\'images\'}->{$type}->{\'height\'}.\'\" \';\n\n            if (isset($shot->{\'caption\'}->{\'text\'})) {\n                $alt = \'title=\"\' . $shot->{\'caption\'}->{\'text\'}.\'\" \';\n            } else {\n                $alt = \' \';\n            }\n\n            $title = $alt;\n            $url = ($link === \'auto\') ? $shot->{\'link\'} : $shot->{\'images\'}->{$type}->{\'url\'};\n            $img = \'<img \' . $src . $alt . $width . $height . \'/>\';\n\n            return $link ? href($img, $url, $title) : $img;\n        }\n\n        /**\n         * Get a defined shot url.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type Image resolution\n         */\n        public static function getImageUrl($shot, $type = \'thumbnail\')\n        {\n            $validTypes = array(\'instagram\', \'thumbnail\', \'low_resolution\', \'standard_resolution\');\n\n            if (in_array($type, $validTypes)) {\n                $out = ($type == \'instagram\') ? $shot->{\'link\'} : $shot->{\'images\'}->{$type}->{\'url\'};\n                return $out;\n            } else {\n                trigger_error(\n                    \"unknown attribute value;\n                    oui_insta_image_url type attribute accepts the following values:\n                    instagram, thumbnail, low_resolution, standard_resolution\"\n                );\n                return;\n            }\n        }\n\n        /**\n         * Get infos about a defined shot.\n         *\n         * @param object $shot shot related feed portion\n         * @param string $type \'caption\', \'likes\' or \'comments\'\n         */\n        public static function getImageInfo($shot, $type = \'caption\')\n        {\n            $infos = array(\n                \'id\'             => $shot->{\'id\'},\n                \'type\'           => $shot->{\'type\'},\n                \'date\'           => $shot->{\'created_time\'},\n                \'w\'              => $shot->{\'images\'}->{\'standard_resolution\'}->{\'width\'},\n                \'h\'              => $shot->{\'images\'}->{\'standard_resolution\'}->{\'height\'},\n                \'thumb_w\'        => $shot->{\'images\'}->{\'thumbnail\'}->{\'width\'},\n                \'thumb_h\'        => $shot->{\'images\'}->{\'low_resolution\'}->{\'height\'},\n                \'low_w\'          => $shot->{\'images\'}->{\'low_resolution\'}->{\'width\'},\n                \'low_h\'          => $shot->{\'images\'}->{\'thumbnail\'}->{\'height\'},\n                \'comments\'       => $shot->{\'comments\'}->{\'count\'},\n                \'comments_count\' => $shot->{\'comments\'}->{\'count\'},\n                \'likes\'          => $shot->{\'likes\'}->{\'count\'},\n                \'likes_count\'    => $shot->{\'likes\'}->{\'count\'},\n                \'caption\'        => isset($shot->{\'caption\'}->{\'text\'}) ? $shot->{\'caption\'}->{\'text\'} : \'\',\n                \'location\'       => isset($shot->{\'location\'}->{\'name\'}) ? $shot->{\'location\'}->{\'name\'} : \'\',\n                \'address\'        => isset($shot->{\'location\'}->{\'street_address\'}) ? $shot->{\'location\'}->{\'street_address\'} : \'\',\n                \'latitude\'       => isset($shot->{\'location\'}->{\'latitude\'}) ? $shot->{\'location\'}->{\'latitude\'} : \'\',\n                \'longitude\'      => isset($shot->{\'location\'}->{\'longitude\'}) ? $shot->{\'location\'}->{\'longitude\'} : \'\',\n            );\n\n            if (array_key_exists($type, $infos)) {\n                return $infos[$type];\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n        }\n\n        /**\n         * Get the post date of a defined shot.\n         *\n         * @param object $shot   shot related feed portion\n         * @param string $format Date format\n         */\n        public static function getImageDate($shot, $format = \'\')\n        {\n            return fileDownloadFormatTime(array(\n                \'ftime\'  => $shot->{\'created_time\'},\n                \'format\' => $format,\n            ));\n        }\n\n        /**\n         * Get defined shot author.\n         *\n         * @param object $shot  shot related feed portion\n         * @param bool   $title Image resolution\n         * @param bool   $link  Link to…\n         */\n        public static function getImageAuthor($shot, $type = \'username\', $link = 0)\n        {\n            $infos = array(\n                \'username\' => $shot->{\'user\'}->{\'username\'},\n                \'id\'       => $shot->{\'user\'}->{\'id\'},\n                \'avatar\'   => $shot->{\'user\'}->{\'profile_picture\'},\n            );\n\n            if (array_key_exists($type, $infos)) {\n                $author = $infos[$type];\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n\n            if ($type === \'avatar\') {\n                $alt = $shot->{\'user\'}->{\'username\'};\n                $author = \'<img src=\"\' . $author . \'\" width=\"150\" height=\"150\" alt=\" alt=\"\' . $alt . \'\" />\';\n            }\n\n            if ($author) {\n                return $link ? href($author, \'http://instagram.com/\'.$username) : $author;\n            } else {\n                trigger_error(\'Unknown image info.\');\n            }\n        }\n    }\n}\n\n\n\nnamespace {\n\n    /**\n     * Main plugin function.\n     *\n     * Pull images if needed;\n     * parse and cache the gallery;\n     * display the content.\n     */\n    function oui_insta_images($atts, $thing = null)\n    {\n        global $oui_insta_shot, $oui_insta_type;\n\n        extract(lAtts(array(\n            \'access_token\' => get_pref(\'oui_instagram_access_token\'),\n            \'limit\'        => \'10\',\n            \'form\'         => \'\',\n            \'type\'         => \'thumbnail\',\n            \'link\'         => \'auto\',\n            \'wraptag\'      => \'ul\',\n            \'class\'        => \'\',\n            \'break\'        => \'li\',\n            \'label\'        => \'\',\n            \'labeltag\'     => \'\',\n        ), $atts));\n\n        $insta = \'Oui\\Instagram\\Main\';\n        $obj = new $insta;\n        $obj->access_token = $access_token;\n        $obj->limit = $limit;\n\n        // Check the query result.\n        if ($thing || $form) {\n            $shots = $obj->getFeed();\n            if ($shots) {\n                foreach ($shots as $shot) {\n                    $oui_insta_shot = $shot;\n                    $oui_insta_type = $type;\n                    $out[] = $thing ? parse($thing) : parse_form($form);\n                }\n                unset(\n                    $GLOBALS[\'oui_insta_shot\'],\n                    $GLOBALS[\'oui_insta_type\']\n                );\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                return;\n            }\n        } else {\n            $images = $obj->getImages($type, $link);\n            if ($images) {\n                $out = $images;\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram images.\');\n                return;\n            }\n        }\n\n        $out = doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n        update_lastmod();\n\n        return $out;\n    }\n\n    // Deprecated\n    function oui_instagram_images($atts, $thing = null)\n    {\n        return oui_insta_images($atts, $thing);\n    }\n\n    /**\n     * Display each image in a oui_insta_images context.\n     */\n    function oui_insta_image($atts)\n    {\n        global $oui_insta_shot, $oui_insta_type;\n\n        extract(lAtts(array(\n            \'link\'    => \'auto\',\n            \'type\'    => $oui_insta_type,\n            \'class\'   => \'\',\n            \'wraptag\' => \'\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImage($oui_insta_shot, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image($atts)\n    {\n        return oui_insta_image($atts);\n    }\n\n    /**\n     * Display each image url in a oui_insta_images context.\n     */\n    function oui_insta_image_url($atts, $thing = null)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'type\'    => \'instagram\',\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'link\'    => \'auto\',\n        ), $atts));\n\n        $url = Oui\\Instagram\\Main::getImageUrl($oui_insta_shot, $type, $link);\n\n        $validLinks = array(\'auto\', \'1\', \'0\');\n\n        if (in_array($link, $validLinks)) {\n            $link = ($link == \'auto\') ? (($thing) ? 1 : 0) : $link;\n            $out = ($thing) ? parse($thing) : $url;\n            $out = ($link) ? href($out, $url, ($wraptag) ? \'\' : \' class=\"\'.$class.\'\"\') : $out;\n        } else {\n            trigger_error(\n                \"unknown attribute value;\n                oui_insta_image_url link attribute accepts the following values:\n                auto, 1, 0\"\n            );\n        }\n\n        return $out ? doTag($out, $wraptag, $class) : \'\';\n    }\n\n    // Deprecated\n    function oui_instagram_image_url($atts, $thing = null)\n    {\n        return oui_insta_image_url($atts, $thing);\n    }\n\n    /**\n     * Display each image information in a oui_insta_images context.\n     */\n    function oui_insta_image_info($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'type\'    => \'caption\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageInfo($oui_insta_shot, $type);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_info($atts)\n    {\n        return oui_insta_image_info($atts);\n    }\n\n    /**\n     * Display each image date in a oui_insta_images context.\n     */\n    function oui_insta_image_date($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'format\'  => \'\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageDate($oui_insta_shot, $format);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_date($atts)\n    {\n        return oui_insta_image_date($atts);\n    }\n\n    /**\n     * Display each image author in a oui_insta_images context.\n     */\n    function oui_insta_image_author($atts)\n    {\n        global $oui_insta_shot;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'link\'    => 0,\n            \'type\'   => \'username\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getImageAuthor($oui_insta_shot, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_image_author($atts)\n    {\n        return oui_insta_image_author($atts);\n    }\n\n    /**\n     * Main plugin function.\n     *\n     * Pull images if needed;\n     * parse and cache the gallery;\n     * display the content.\n     */\n    function oui_insta_author($atts, $thing = null)\n    {\n        global $oui_insta_profile;\n\n        extract(lAtts(array(\n            \'access_token\' => \'\',\n            \'type\'         => \'username\',\n            \'link\'         => \'instagram\',\n            \'wraptag\'      => \'p\',\n            \'form\'         => \'\',\n            \'class\'        => \'\',\n            \'break\'        => \'br\',\n            \'label\'        => \'\',\n            \'labeltag\'     => \'\',\n        ), $atts));\n\n        $access_token ?: $access_token = get_pref(\'oui_instagram_access_token\');\n\n        $insta = \'Oui\\Instagram\\Main\';\n        $obj = new $insta;\n        $obj->access_token = $access_token;\n\n        // Check the query result.\n        if ($thing || $form) {\n            $profile = $obj->getProfile();\n            if ($profile) {\n                $oui_insta_profile = $profile;\n                $out[] = $thing ? parse($thing) : $parse_form($form);\n                unset($GLOBALS[\'oui_insta_profile\']);\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram feed.\');\n                return;\n            }\n        } else {\n            $info = $obj->getAuthor($type, $link);\n            if ($info) {\n                $out[] = $info;\n            } else {\n                trigger_error(\'oui_insta was not able to get your Instagram images.\');\n                return;\n            }\n        }\n\n        $out = doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n\n        update_lastmod();\n\n        return $out;\n    }\n\n    // Deprecated\n    function oui_instagram_author($atts, $thing = null)\n    {\n        return oui_insta_author($atts, $thing);\n    }\n\n    /**\n     * Display each image information in a oui_insta_images context.\n     */\n    function oui_insta_author_info($atts)\n    {\n        global $oui_insta_profile;\n\n        extract(lAtts(array(\n            \'wraptag\' => \'\',\n            \'class\'   => \'\',\n            \'type\'    => \'username\',\n            \'link\'    => \'instagram\',\n        ), $atts));\n\n        $out = Oui\\Instagram\\Main::getAuthorInfo($oui_insta_profile, $type, $link);\n\n        return ($wraptag) ? doTag($out, $wraptag, $class) : $out;\n    }\n\n    // Deprecated\n    function oui_instagram_author_info($atts, $thing = null)\n    {\n        return oui_insta_author_info($atts, $thing);\n    }\n}\n', '47d4cef891589d29ab5d49dc9043f5cd', '#@admin\n#Default language, en-us, en-gb\noui_instagram => Instagram (oui_instagram)\noui_instagram_access_token => Access token\n\n#@admin\n#@language fr-fr\noui_instagram => Instagram (oui_instagram)\noui_instagram_access_token => Access token\n', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('tok_osm_leaflet', 0, 'Torsten Krüger', 'http://www.kryger.de/', '0.4', 'Displays a small OpenStreetMap (with a marker in it if requested) using the leaflet library', '<h1>tok_osm_leaflet</h1>\n\n<p>… displays a map based on OpenStreetMap data using the leaflet javascript library. Width and height of map div are configurable and of course the portion to be shown. A marker may be set on the landscape for adding a textual comment.</p>\n\n<p>Required resources will be loaded from external sources, this applies obviously to the map tiles, but also to javascript, css and image files e.g. for the markers.</p>\n\n\n<h2>Attributes of this plugin</h2>\n\n\n<h3>Map dimension</h3>\n\n<ul>\n	<li>width</li>\n	<li>height</li>\n</ul>\n\n<p>Are any valid <span class=\"caps\">CSS</span> dimensions to set width and height of the map div. If you omit the unit, “px” is assumed.</p>\n\n<p>If you do not provide width and height, more or less senseful default values will be applied.</p>\n\n<h4>Set up the map</h4>\n\n<p>The visible portion of the map is determined by three attributes:</p>\n\n<ul>\n	<li>clon</li>\n	<li>clat</li>\n	<li>zoom</li>\n</ul>\n\n<p>Where <em>clon</em> is the longitude and <em>clat</em> is the latitude of the maps centeras <span class=\"caps\">GPS</span> values. Both <em>clon</em> and <em>clat</em> are mandatory.</p>\n\n<p>The scale of the map is set by the <em>zoom</em> attribute.</p>\n\n<h3>Adding a marker</h3>\n\n<p>The attributes for setting a marker are:</p>\n\n<ul>\n	<li>mlon</li>\n	<li>mlat</li>\n	<li>mcomment</li>\n</ul>\n\n<p><em>mlon</em> and <em>mlat</em> are the <span class=\"caps\">GPS</span> coordinates of the markers position, exactly as <em>clon</em> and <em>clat</em>. A marker will be placed on the map at that position. If <em>mlon</em> and <em>mlat</em> are provided, <em>clon</em> and <em>clat</em> may be omitted; the map will be centered slightly avobe the marker in that case.</p>\n\n<p>If <em>mcomment</em> has a value, the marker will be clickable to show the given content.</p>\n\n\n<h2>Preference of this plugin</h2>\n\n<h3>Local leaflet installation</h3>\n\n<p>By default <code>tok_osm_leaflet</code> loads code and style (including images) from the leaflet content delivering network. In case you have downloaded the packaged files from the leaflet site, saved them on your server (and maybe have adjusted them to your needs) you may advise the plugin to use that files. Just go to the <code>Options</code>-page of this plugin located at <code>Admin</code> → <code>Plugins</code> and enter the path of the base directory of your leaflet stuff.</p>\n\n<p>Assuming you unpacked the leaflet zip file in a directory called “leaflet” in your textpattern base directory, the following values would be valid:</p>\n\n<ul>\n	<li><code>http://www.yourdomain.tld/leaflet</code></li>\n	<li><code>$site_url/leaflet</code></li>\n	<li><code>/leaflet</code></li>\n</ul>\n\n<p>If you forgot to remove a trailing slash in path declaration, the plugin will do it for you.</p>\n\n<p>An empty value will cause the files to be loaded from the leaflet content delivery network.</p>\n\n\n<h2>Examples</h2>\n\n<h3>The simplest way of showing a map</h3>\n\n<p>The Brandenburg Gate in Berlin</p>\n\n<pre><code>&lt;txp:tok_osm_leaflet clat=&quot;52.5163&quot; clon=&quot;13.3778&quot; /&gt;</code></pre>\n\n\n<h3>Showing the exact portion</h3>\n\n<p>The city area of Berlin.</p>\n\n<pre><code>&lt;txp:tok_osm_leaflet width=&quot;500&quot; height=&quot;400&quot;\n     clat=&quot;52.5012&quot; clon=&quot;13.4314&quot; zoom=&quot;11&quot; /&gt;</code></pre>\n\n\n<h3>A simple marker</h3>\n\n<p>… on the Eiffel Tower in Paris</p>\n\n<pre><code>&lt;txp:tok_osm_leaflet width=&quot;600&quot; height=&quot;450&quot; zoom=&quot;12&quot;\n     clat=&quot;48.8586&quot; clon=&quot;2.3439&quot; mlat=&quot;48.8583&quot; mlon=&quot;2.2944&quot; /&gt;</code></pre>\n\n\n<h3>An automatically centered marker</h3>\n\n<p>The Eiffel Tower again; just omit <em>clon</em> and <em>clat</em></p>\n\n<pre><code>&lt;txp:tok_osm_leaflet width=&quot;600&quot; height=&quot;450&quot; zoom=&quot;12&quot; mlat=&quot;48.8583&quot; mlon=&quot;2.2944&quot; /&gt;</code></pre>\n\n\n<h3>A marker with comment</h3>\n\n<p>A Jazz Standard</p>\n\n<pre><code>&lt;txp:tok_osm_leaflet mlat=&quot;40.76291&quot; mlon=&quot;-73.98285&quot; mcomment=&quot;Birdland&quot; /&gt;</code></pre>', '\nglobal $tok_osm_leaflet_leafletpath, $tok_osm_leaflet_mapcounter;\nif ($tok_osm_leaflet_mapcounter == NULL) {\n  $tok_osm_leaflet_mapcounter = 0; }\n\n// register admin stuff of plugin\nif (@txpinterface === \'admin\') {\n  add_privs(\'tok_osm_leaflet_prefs\', 1);\n  add_privs(\'plugin_prefs.tok_osm_leaflet\', \'1,2\');\n  register_callback(\'tok_osm_leaflet_lifecycle\', \'plugin_lifecycle.tok_osm_leaflet\');\n  register_callback(\'tok_osm_leaflet_prefs\', \'plugin_prefs.tok_osm_leaflet\');\n  register_callback(\'tok_osm_leaflet_prefs\', \'tok_osm_leaflet_prefs\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'tok_osm_leaflet\');\n    }\n}\n\n\n// lifecycle: install and remove plugin\nfunction tok_osm_leaflet_lifecycle($event = \'\', $step = \'\') {\n\n  global $prefs;\n\n  if($step == \'installed\') {\n    if (! isset($prefs[\'tok_osm_leaflet_leafletpath\'])) {\n      safe_insert(\'txp_prefs\',\n		   \"name = \'tok_osm_leaflet_leafletpath\',\n                    val = \'\',\n                    html = \'text_input\',\n                    prefs_id = 1,\n                    type = 2,\n                    event = \'publish\',\n                    position = 0\n                    \");\n      return;\n    }\n  }\n\n  if($step == \'deleted\') {\n    safe_delete(\'txp_prefs\',\n		 \"name like \'tok_osm_leaflet_%\'\"\n		);\n    return;\n  }\n}\n\n\n// preferences\nfunction tok_osm_leaflet_prefs($event, $step){\n\n  global $tok_osm_leaflet_leafletpath;\n  include_once txpath . \'/include/txp_prefs.php\';\n\n  if (ps(\"save\")) {\n    prefs_save();\n    header(\"Location: index.php?event=tok_osm_leaflet_prefs\");\n  }\n\n  pagetop(\'tok_osm_leaflet Preferences\');\n  echo \'<h1>tok_osm_leaflet Preferences</h1>\';\n  echo form(startTable(\'\', \'\', \'txp-list\').\n	     tr(tdcs(hed(\'Where to load the leaflet code and style from\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(\'Path to leaflet directory\').\n		 td(text_input(\"tok_osm_leaflet_leafletpath\",\n				$tok_osm_leaflet_leafletpath,\n				\'20\'))).\n	     endTable().\n	     graf(fInput(\"submit\",\"save\", \"save\" , \"save\").\n		   eInput(\"tok_osm_leaflet_prefs\").\n		   sInput(\'saveprefs\')));\n}\n\n\n// main part of plugin (output on page)\nfunction tok_osm_leaflet($atts) {\n\n  global $tok_osm_leaflet_mapcounter, $tok_osm_leaflet_leafletpath;\n  $tok_osm_leaflet_mapcounter++;\n\n  // setup path for leaflet stuff\n  if (empty ($tok_osm_leaflet_leafletpath)) {\n    $tok_osm_leaflet_leafletpath = \'http://cdn.leafletjs.com/leaflet-0.7.3\';}\n  $leafletpath = rtrim($tok_osm_leaflet_leafletpath, \'/\');\n  /* $leafletpath = str_replace(\'$site_url\', rtrim(site_url(), \'/\'), $leafletpath); */\n  $leafletpath = str_replace(\'$site_url\', rtrim(site_url(array()), \'/\'), $leafletpath);\n\n  // Get Attributes\n  extract(lAtts(array(\n			\'width\'    => \'600px\',\n			\'height\'   => \'400px\',\n			\'zoom\'     => \'16\',\n			\'clat\'     => \'\',\n			\'clon\'     => \'\',\n			\'mlat\'     => \'\',\n			\'mlon\'     => \'\',\n			\'mcomment\' => \'\',\n                        \'tokcounter\' => 0\n			), $atts));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check neccessary atttributes\n  if (empty ($clat)) {\n    if (!empty($mlat)) {\n      $clat = number_format($mlat + 0.0005, 5, \'.\', \'\');\n    }\n    else {\n      return(sprintf($err_format, \'Center latitude is missing!\'));\n    }\n  }\n  if (empty ($clon)) {\n    if (!empty($mlon)) {\n      $clon = $mlon;\n    }\n    else {\n      return(sprintf($err_format, \'Center longitude is missing!\'));\n    }\n  }\n\n  // add \"px\" to width and height, if no unit was given\n  if (is_numeric($width)) {$width = $width . \'px\';}\n  if (is_numeric($height)) {$height = $height . \'px\';}\n\n  // build map ID\n  $map_id = \'tok_osm_leaflet_map_\' . $tok_osm_leaflet_mapcounter;\n\n  // assemble html part\n  $map_part = \'\';\n\n  // load leaflet stuff only once\n  if ($tok_osm_leaflet_mapcounter == 1) {\n    $map_part .= \'<script src=\"\' . $leafletpath . \'/leaflet.js\"></script>\' . \"\\n\" .\n    \'<link rel=\"stylesheet\" href=\"\' . $leafletpath . \'/leaflet.css\" />\' . \"\\n\"; }\n\n  $map_part .=  \'<div id=\"\' . $map_id . \'\" style=\"width:\' . $width .\n    \';height:\' . $height . \'; border: 1px solid #8a7364;\"></div>\' . \"\\n\" .\n    \'<script type=\"text/javascript\">\' . \"\\n\" .\n    \'window.addEventListener(\"load\", build_\' . $map_id . \', false);\' . \"\\n\" .\n    \'function build_\' . $map_id . \'() {\'. \"\\n\" .\n    \'var \' . $map_id . \' = L.map(\"\' . $map_id . \'\").setView([\' . $clat . \',\' . $clon . \'],\' . $zoom . \');\' .\n    \"\\n\" . \'L.tileLayer(\"http://{s}.tile.osm.org/{z}/{x}/{y}.png\", {\' . \"\\n\" .\n    \'attribution: \\\'&copy; <a href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors\\\'}).addTo(\' . $map_id . \');\';\n\n  // add marker if one was requestes\n  if ((!empty($mlon)) && (!empty($mlat))) {\n    $map_part .= \'L.marker([\' . $mlat . \',\' . $mlon . \']).addTo(\' . $map_id . \')\';\n    if (!empty($mcomment)) {\n      $map_part .= \'.bindPopup(\"\'.$mcomment.\'\")\';}\n  }\n\n  // finish\n  $map_part .= \'}</script>\';\n\n  return ($map_part);\n}\n', '\nglobal $tok_osm_leaflet_leafletpath, $tok_osm_leaflet_mapcounter;\nif ($tok_osm_leaflet_mapcounter == NULL) {\n  $tok_osm_leaflet_mapcounter = 0; }\n\n// register admin stuff of plugin\nif (@txpinterface === \'admin\') {\n  add_privs(\'tok_osm_leaflet_prefs\', 1);\n  add_privs(\'plugin_prefs.tok_osm_leaflet\', \'1,2\');\n  register_callback(\'tok_osm_leaflet_lifecycle\', \'plugin_lifecycle.tok_osm_leaflet\');\n  register_callback(\'tok_osm_leaflet_prefs\', \'plugin_prefs.tok_osm_leaflet\');\n  register_callback(\'tok_osm_leaflet_prefs\', \'tok_osm_leaflet_prefs\');\n} elseif (txpinterface === \'public\') {\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'tok_osm_leaflet\');\n    }\n}\n\n\n// lifecycle: install and remove plugin\nfunction tok_osm_leaflet_lifecycle($event = \'\', $step = \'\') {\n\n  global $prefs;\n\n  if($step == \'installed\') {\n    if (! isset($prefs[\'tok_osm_leaflet_leafletpath\'])) {\n      safe_insert(\'txp_prefs\',\n		   \"name = \'tok_osm_leaflet_leafletpath\',\n                    val = \'\',\n                    html = \'text_input\',\n                    prefs_id = 1,\n                    type = 2,\n                    event = \'publish\',\n                    position = 0\n                    \");\n      return;\n    }\n  }\n\n  if($step == \'deleted\') {\n    safe_delete(\'txp_prefs\',\n		 \"name like \'tok_osm_leaflet_%\'\"\n		);\n    return;\n  }\n}\n\n\n// preferences\nfunction tok_osm_leaflet_prefs($event, $step){\n\n  global $tok_osm_leaflet_leafletpath;\n  include_once txpath . \'/include/txp_prefs.php\';\n\n  if (ps(\"save\")) {\n    prefs_save();\n    header(\"Location: index.php?event=tok_osm_leaflet_prefs\");\n  }\n\n  pagetop(\'tok_osm_leaflet Preferences\');\n  echo \'<h1>tok_osm_leaflet Preferences</h1>\';\n  echo form(startTable(\'\', \'\', \'txp-list\').\n	     tr(tdcs(hed(\'Where to load the leaflet code and style from\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(\'Path to leaflet directory\').\n		 td(text_input(\"tok_osm_leaflet_leafletpath\",\n				$tok_osm_leaflet_leafletpath,\n				\'20\'))).\n	     endTable().\n	     graf(fInput(\"submit\",\"save\", \"save\" , \"save\").\n		   eInput(\"tok_osm_leaflet_prefs\").\n		   sInput(\'saveprefs\')));\n}\n\n\n// main part of plugin (output on page)\nfunction tok_osm_leaflet($atts) {\n\n  global $tok_osm_leaflet_mapcounter, $tok_osm_leaflet_leafletpath;\n  $tok_osm_leaflet_mapcounter++;\n\n  // setup path for leaflet stuff\n  if (empty ($tok_osm_leaflet_leafletpath)) {\n    $tok_osm_leaflet_leafletpath = \'http://cdn.leafletjs.com/leaflet-0.7.3\';}\n  $leafletpath = rtrim($tok_osm_leaflet_leafletpath, \'/\');\n  /* $leafletpath = str_replace(\'$site_url\', rtrim(site_url(), \'/\'), $leafletpath); */\n  $leafletpath = str_replace(\'$site_url\', rtrim(site_url(array()), \'/\'), $leafletpath);\n\n  // Get Attributes\n  extract(lAtts(array(\n			\'width\'    => \'600px\',\n			\'height\'   => \'400px\',\n			\'zoom\'     => \'16\',\n			\'clat\'     => \'\',\n			\'clon\'     => \'\',\n			\'mlat\'     => \'\',\n			\'mlon\'     => \'\',\n			\'mcomment\' => \'\',\n                        \'tokcounter\' => 0\n			), $atts));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check neccessary atttributes\n  if (empty ($clat)) {\n    if (!empty($mlat)) {\n      $clat = number_format($mlat + 0.0005, 5, \'.\', \'\');\n    }\n    else {\n      return(sprintf($err_format, \'Center latitude is missing!\'));\n    }\n  }\n  if (empty ($clon)) {\n    if (!empty($mlon)) {\n      $clon = $mlon;\n    }\n    else {\n      return(sprintf($err_format, \'Center longitude is missing!\'));\n    }\n  }\n\n  // add \"px\" to width and height, if no unit was given\n  if (is_numeric($width)) {$width = $width . \'px\';}\n  if (is_numeric($height)) {$height = $height . \'px\';}\n\n  // build map ID\n  $map_id = \'tok_osm_leaflet_map_\' . $tok_osm_leaflet_mapcounter;\n\n  // assemble html part\n  $map_part = \'\';\n\n  // load leaflet stuff only once\n  if ($tok_osm_leaflet_mapcounter == 1) {\n    $map_part .= \'<script src=\"\' . $leafletpath . \'/leaflet.js\"></script>\' . \"\\n\" .\n    \'<link rel=\"stylesheet\" href=\"\' . $leafletpath . \'/leaflet.css\" />\' . \"\\n\"; }\n\n  $map_part .=  \'<div id=\"\' . $map_id . \'\" style=\"width:\' . $width .\n    \';height:\' . $height . \'; border: 1px solid #8a7364;\"></div>\' . \"\\n\" .\n    \'<script type=\"text/javascript\">\' . \"\\n\" .\n    \'window.addEventListener(\"load\", build_\' . $map_id . \', false);\' . \"\\n\" .\n    \'function build_\' . $map_id . \'() {\'. \"\\n\" .\n    \'var \' . $map_id . \' = L.map(\"\' . $map_id . \'\").setView([\' . $clat . \',\' . $clon . \'],\' . $zoom . \');\' .\n    \"\\n\" . \'L.tileLayer(\"http://{s}.tile.osm.org/{z}/{x}/{y}.png\", {\' . \"\\n\" .\n    \'attribution: \\\'&copy; <a href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors\\\'}).addTo(\' . $map_id . \');\';\n\n  // add marker if one was requestes\n  if ((!empty($mlon)) && (!empty($mlat))) {\n    $map_part .= \'L.marker([\' . $mlat . \',\' . $mlon . \']).addTo(\' . $map_id . \')\';\n    if (!empty($mcomment)) {\n      $map_part .= \'.bindPopup(\"\'.$mcomment.\'\")\';}\n  }\n\n  // finish\n  $map_part .= \'}</script>\';\n\n  return ($map_part);\n}\n', 'c1ffb7a546771f06a76253c9426abb79', '', '', 1, 5, 3),
('tok_next_weekly', 0, 'Torsten Krüger', 'http://kryger.de/', '0.3', 'Shows the next date of a weekly recurring event.', '<h1>tok_next_weekly</h1>\n\n<p>… calculates the upcoming occurence of a weekly recurring date. The output is widely configurable through strftime compatibility.</p>\n\n\n<h2>Attributes of the tag</h2>\n\n<dl>\n	<dt>dow</dt>\n	<dd>Number of day of week in <span class=\"caps\">ISO</span> 8601 format (from 1 = monday through 7 = sunday)</dd>\n	<dt>week</dt>\n	<dd>In which weeks does the date repeat? Allowed values are:<br />\n  <code>every</code> to a weekly repeating event (which is the default)<br />\n  <code>odd</code> for events that repeat every odd week<br />\n  <code>even</code> for events that repeat every even week</dd>\n	<dt>skip_at</dt>\n	<dd>Set the time at which a todays date changes from today to the next occurence.<br />\nPlease provide the time in 24-hour format, immediately (without any separating character) followed by the two digits of the minute. So <code>09:34 PM</code> becomes <code>2134</code>. Defaults to “1200” for events in the morning.</dd>\n	<dt>format</dt>\n	<dd>the date output in strftime format. Default is <code>%x</code> – the “preferred date representation based on locale”.</dd>\n	<dt>todays_label</dt>\n	<dd>Append a special label to the output if the date is today. Defaults to an exclamation mark in parentheses</dd>\n	<dt>alt_locale</dt>\n	<dd>As names in calendars are language dependent, a locale setting may be forced with this attributed (default: not set). Please note that this function depends massively on which locales are available on the server system</dd>\n</dl>\n\n\n<h2>Examples</h2>\n\n\n<h3>The most simple one</h3>\n\n<p>The upcoming wednesday</p>\n\n<pre><code>&lt;txp:tok_next_weekly dow=&quot;3&quot; /&gt;</code></pre>\n\n\n<h3>A bit broader about todays occurence</h3>\n\n<p>You will see this only, if you set the appropriate day of week. Or just wait …</p>\n\n<pre><code>&lt;txp:tok_next_weekly dow=&quot;1&quot; todays_label=&quot; *Which is today. So hurry up!*&quot; /&gt;</code></pre>\n\n\n<h3>Nicer date format</h3>\n\n<p>A typical german date mark with full day and month names, leading zeros stripped from day number</p>\n\n<pre><code>&lt;txp:tok_next_weekly dow=&quot;6&quot; format=&quot;%A, %-d. %B&quot; /&gt;</code></pre>\n\n\n<h3>With manipulated locale</h3>\n\n<p>This makes sense only when running under an exotic locale</p>\n\n<pre><code>&lt;txp:tok_next_weekly format=&quot;%x, %A&quot; dow=&quot;4&quot; alt_locale=&quot;C&quot; /&gt;</code></pre>', '\nfunction tok_next_weekly( $atts ) {\n\n  // Get Attributes\n  extract( lAtts( array(\n			\'dow\'          => \'\',\n			\'week\'         => \'every\',\n			\'skip_at\'      => \'1200\',\n			\'format\'       => \'%x\',\n			\'alt_locale\'   => \'\',\n			\'todays_label\' => \'(!)\'\n			), $atts ));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check mandatory attribute\n  if ( empty ( $dow )) {\n    return( sprintf( $err_format, \'No day of week given\' )); }\n\n  $ret_val = sprintf( $err_format, \'date unknown\' );\n\n  if ($alt_locale) {\n    $regular_locale = setlocale( LC_TIME, 0 );\n    setlocale( LC_TIME, $alt_locale ); }\n\n  // get day and time of now\n  $today = safe_strftime( \"%u:%V:%H%M\" );\n  list( $dow_today, $week_today_no, $time_now ) = explode(\':\', $today);\n  $week_today = ( $week_today_no & 1 ? \"odd\" : \"even\" );\n\n  // calculate the day we are looking for\n  $date_offset = 0;\n\n  // let\'s see, if we\'re looking for today ...\n  if (( $week_today == $week || $week == \'every\' ) &&\n      $dow_today == $dow &&\n      0 + $time_now < 0 + $skip_at ) {\n    $ret_val = ( strftime( $format, strtotime( \'today\' )) . \"$todays_label\" ); }\n\n  else { // so, it\'s not today, ...\n\n    // ... but maybe a forthcoming day in the current week ...\n    if (( $week_today == $week || $week == \'every\' ) &&\n	$dow_today < $dow ) {\n      $date_offset = $dow - $dow_today; }\n\n    // ... if not, it\'s in a future week:\n    else {\n      // either next one  ...\n      if ( $week_today != $week || $week == \'every\' ) {\n	$date_offset = 7 + $dow - $dow_today; }\n      // ... or after next week\n      else {\n	$date_offset = 14 + $dow - $dow_today; }\n    }\n\n    // put value into variable\n    $ret_val = ( strftime( $format, strtotime( \'+\' . $date_offset . \' day\' )) );\n  }\n\n  // reset locale\n  if ($alt_locale) {\n    setlocale( LC_TIME, $regular_locale );\n  }\n\n  // finish\n  return( $ret_val );\n}\n', '\nfunction tok_next_weekly( $atts ) {\n\n  // Get Attributes\n  extract( lAtts( array(\n			\'dow\'          => \'\',\n			\'week\'         => \'every\',\n			\'skip_at\'      => \'1200\',\n			\'format\'       => \'%x\',\n			\'alt_locale\'   => \'\',\n			\'todays_label\' => \'(!)\'\n			), $atts ));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check mandatory attribute\n  if ( empty ( $dow )) {\n    return( sprintf( $err_format, \'No day of week given\' )); }\n\n  $ret_val = sprintf( $err_format, \'date unknown\' );\n\n  if ($alt_locale) {\n    $regular_locale = setlocale( LC_TIME, 0 );\n    setlocale( LC_TIME, $alt_locale ); }\n\n  // get day and time of now\n  $today = safe_strftime( \"%u:%V:%H%M\" );\n  list( $dow_today, $week_today_no, $time_now ) = explode(\':\', $today);\n  $week_today = ( $week_today_no & 1 ? \"odd\" : \"even\" );\n\n  // calculate the day we are looking for\n  $date_offset = 0;\n\n  // let\'s see, if we\'re looking for today ...\n  if (( $week_today == $week || $week == \'every\' ) &&\n      $dow_today == $dow &&\n      0 + $time_now < 0 + $skip_at ) {\n    $ret_val = ( strftime( $format, strtotime( \'today\' )) . \"$todays_label\" ); }\n\n  else { // so, it\'s not today, ...\n\n    // ... but maybe a forthcoming day in the current week ...\n    if (( $week_today == $week || $week == \'every\' ) &&\n	$dow_today < $dow ) {\n      $date_offset = $dow - $dow_today; }\n\n    // ... if not, it\'s in a future week:\n    else {\n      // either next one  ...\n      if ( $week_today != $week || $week == \'every\' ) {\n	$date_offset = 7 + $dow - $dow_today; }\n      // ... or after next week\n      else {\n	$date_offset = 14 + $dow - $dow_today; }\n    }\n\n    // put value into variable\n    $ret_val = ( strftime( $format, strtotime( \'+\' . $date_offset . \' day\' )) );\n  }\n\n  // reset locale\n  if ($alt_locale) {\n    setlocale( LC_TIME, $regular_locale );\n  }\n\n  // finish\n  return( $ret_val );\n}\n', 'd1e64eee0e97b43eff99460675204216', '', '', 0, 5, 0),
('tok_owm_current', 0, 'Torsten Krüger', 'http://kryger.de/', '0.2', 'Shows data from OpenWeatherMap widely configurable', '<h1>tok_owm_current</h1>\n\n<p>… displays current weather conditions using the <a href=\"http://openweathermap.org/api\" rel=\"nofollow\">OpenWeatherMap <span class=\"caps\">API</span></a></p>\n\n<p>The output may be widely configured by setting query parameters and using variables via the plugins attributes.</p>\n\n\n<h2>Attributes of this plugin</h2>\n\n<ul>\n	<li>apikey</li>\n</ul>\n\n<p>Necessary attribute. Please provide your <a href=\"http://openweathermap.org/appid\" rel=\"nofollow\">OpenWeatherMap <span class=\"caps\">APPID</span></a> here.</p>\n\n<ul>\n	<li>cityids</li>\n</ul>\n\n<p>Another necessary attribute: The id number (or numbers) of the desired city (or cites) as defined in the OpenWeatherMap database. Use the <a href=\"http://openweathermap.org/find\" rel=\"nofollow\">search form</a> or try the <a href=\"http://openweathermap.org/maps\" rel=\"nofollow\">maps</a> to find your city. You&#8217;ll find the appropriate number in the address bar of your browser. If you want to request data of more than one city, please seperate the id numbers by a comma sign “,”.</p>\n\n<ul>\n	<li>lang</li>\n</ul>\n\n<p>This attribute is your interface to the <a href=\"http://openweathermap.org/current#multi\" rel=\"nofollow\">OpenWeaterMap multilingual support</a>. Defaults to “en”.</p>\n\n<ul>\n	<li>na</li>\n</ul>\n\n<p>What to show, if a requested value is not available. This may depend on language or personal preferences; so you may set it here. Default is a question mark “?”.</p>\n\n<ul>\n	<li>cache_secs</li>\n</ul>\n\n<p>Time in seconds to cache a requests result. The same request within the given time will be answered from the cached data without sending another request to OpenWeatherMap. Please note, that the output is cached &#8212; so if you are fiddeling around with the display attribute, reduce this value for the time of testing. Default is 600 (10 minutes).</p>\n\n<ul>\n	<li>cache_mark</li>\n</ul>\n\n<p>Text to be attached to the output, if cached data was used.</p>\n\n<ul>\n	<li>units</li>\n</ul>\n\n<p>OpenWeatherMap supports two metric systems: “metric” or “imperial” – choose one! Defaults to “metric”</p>\n\n<ul>\n	<li>display</li>\n</ul>\n\n<p>Define the look of the plugin. Provide a simple html sequence here. Defaults to show the name of city, an icon showing current weather condition and the temperature. If you use this <code>tok_owm_current</code> as a container tag, the display attribute is ignored and the tags content is taken instead. Special variables are used for the weather data. Please see the next section for details:</p>\n\n\n<h3>Display variables</h3>\n\n<p>You may use the following variables in the display attribute (the double square brackets belong to the variable name, please see the <em>example</em> section). The variable names follow the <a href=\"http://openweathermap.org/weather-data#current\" rel=\"nofollow\">OpenWeatherMap parameters</a></p>\n\n<ul>\n	<li><code>[[cityname]]</code></li>\n</ul>\n\n<p>Name of city as provided by OpenWeatherMap</p>\n\n<ul>\n	<li><code>[[city_owm_url]]</code></li>\n</ul>\n\n<p><span class=\"caps\">URL</span> of the selected city´s page at OpenWeatherMap</p>\n\n<ul>\n	<li><code>[[lat]]</code></li>\n	<li><code>[[lon]]</code></li>\n</ul>\n\n<p>Coordinates of the city as provided by OpenWeatherMap</p>\n\n<ul>\n	<li><code>[[temp]]</code></li>\n</ul>\n\n<p>Current temperature with (at this moment) two decimal places</p>\n\n<ul>\n	<li><code>[[temp_int]]</code></li>\n</ul>\n\n<p>Current temperature rounded to an integer</p>\n\n<ul>\n	<li><code>[[temp_min]]</code></li>\n</ul>\n\n<p>Minimum temperature at the moment</p>\n\n<ul>\n	<li><code>[[temp_max]]</code></li>\n</ul>\n\n<p>Maximum temperature at the moment</p>\n\n<ul>\n	<li><code>[[humidity]]</code></li>\n</ul>\n\n<p>Humidity</p>\n\n<ul>\n	<li><code>[[pressure]]</code></li>\n</ul>\n\n<p>Atmospheric pressure</p>\n\n<ul>\n	<li><code>[[speed]]</code></li>\n</ul>\n\n<p>Wind speed</p>\n\n<ul>\n	<li><code>[[deg]]</code></li>\n</ul>\n\n<p>Wind direction</p>\n\n<ul>\n	<li><code>[[clouds]]</code></li>\n</ul>\n\n<p>Cloudiness</p>\n\n<ul>\n	<li><code>[[condition]]</code></li>\n</ul>\n\n<p>Weather <a href=\"http://openweathermap.org/weather-conditions\" rel=\"nofollow\">condition code</a> as used by OpenWeatherMap</p>\n\n<ul>\n	<li><code>[[main]]</code></li>\n</ul>\n\n<p>Group of weather parameters</p>\n\n<ul>\n	<li><code>[[description]]</code></li>\n</ul>\n\n<p>Description of current weather condition</p>\n\n<ul>\n	<li><code>[[icon_url]]</code></li>\n</ul>\n\n<p><span class=\"caps\">URL</span> of weather icon</p>\n\n<ul>\n	<li><code>[[rain1]]</code></li>\n</ul>\n\n<p>Precipitation volume for last 1 hour</p>\n\n<ul>\n	<li><code>[[rain3]]</code></li>\n</ul>\n\n<p>Precipitation volume for last 3 hours</p>\n\n\n<h2>Examples</h2>\n\n<p>Please note, that you have to provide a valid <span class=\"caps\">APPID</span> to get the examples to work.</p>\n\n<h3>A short textual weather report</h3>\n\n<pre><code>&lt;txp:tok_owm_current\n  cityids = &quot;2147714&quot;\n  apikey = &quot;0123456789abcdef0123456789abcdef&quot;\n  display = &quot;&lt;a href=\'[[city_owm_url]]\' target=\'_blank\'&gt;[[cityname]]&lt;/a&gt;: [[temp_int]]°C, [[description]]&quot; /&gt;&lt;/li&gt;</code></pre>\n\n\n<h3>Nicer output, some eyecandy too, used as container tag</h3>\n\n<p>This example looks scaring, mostly because of the built-in <span class=\"caps\">CSS</span> code. But in that way I am allowed to provide an example which is ready to use by copy and paste. In your textpattern environment the <span class=\"caps\">CSS</span> code should be better integrated.</p>\n\n<p>Some very special technology is used here (<span class=\"caps\">CSS</span> 3, Unicode characters), so please use an up-to-date browser for this one.</p>\n\n<pre><code>&lt;txp:tok_owm_current cityids = &quot;1796236,3369157,3435910&quot; apikey = &quot;0123456789abcdef0123456789abcdef&quot;&gt;\n&lt;div style=\'border:1px solid #ccc;border-radius:5px;display:inline-block;\n            font-size:0.8em;margin-right:1em;padding:6px;text-align:center;\'&gt;\n  &lt;strong style=\'font-size:1.2em;\'&gt;[[cityname]]&lt;/strong&gt;&lt;br /&gt;\n  &lt;img src=\'[[icon_url]]\' alt=\'[[description]]\' title=\'[[description]]\' /&gt;&lt;br /&gt;\n  &lt;span style=\'background-color: #777;border-radius:3px;color:#fff;display:inline-block;\n               font-weight:bold;margin-bottom:1ex;padding:3px;vertical-align: baseline;\'\n               title=\'Current temperature\'&gt;[[temp]]°C&lt;/span&gt;&lt;br /&gt;\n  &lt;span title=\'Cloudiness\'&gt;☁  [[clouds]] %&lt;/span&gt;&lt;br /&gt;\n  &lt;span title=\'Precipitation volume for last hour\'&gt;☔ [[rain1]] mm&lt;/span&gt;&lt;/p&gt;\n  &lt;p title=\'Wind direction and speed\'&gt;\n    %{display:inline-block;margin-top:1ex;transform:rotate([[deg]]deg);}↓%&lt;br /&gt;\n    [[speed]] m/s&lt;/p&gt;\n  &lt;p title=\'Humidity and pressure\'&gt;[[humidity]] %&lt;br /&gt;\n    [[pressure]] hpa&lt;/p&gt;\n&lt;/div&gt;&lt;/txp:tok_owm_current&gt;</code></pre>\n\n\n<h2>Development</h2>\n\n<p>If you have suggestions for additional features of this plugin, please <a href=\"https://github.com/torsk/txp_plugins/issues\" rel=\"nofollow\">let me know</a>.</p>', '\n  // {{{ lifecycle callbacks and functions\n\n    // {{{ Lifecycle callback\nif (@txpinterface == \'admin\') {\n  register_callback(\'tok_owm_current_installation_routine\',\n		    \'plugin_lifecycle.tok_owm_current\', \'installed\');\n  register_callback(\'tok_owm_current_deletion_routine\',\n		    \'plugin_lifecycle.tok_owm_current\', \'deleted\');\n}\n// }}}\n\n    // {{{ installation routine\nfunction tok_owm_current_installation_routine() {\n\n  $create_table =\"CREATE TABLE IF NOT EXISTS `\".PFX.\"tok_owm_current_requests_cache` (\";\n  $create_table .= <<<EOD\n    `id` int(15) NOT NULL AUTO_INCREMENT,\n    `request` varchar(32) NOT NULL,\n    `timestamp` int(11) NOT NULL,\n    `data` mediumtext NOT NULL DEFAULT \'\',\n    PRIMARY KEY (`id`),\n    KEY `request` (`request`)\n    ) DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;\nEOD;\n\n  safe_query($create_table);\n}\n// }}}\n\n    // {{{ deletion routine\nfunction tok_owm_current_deletion_routine() {\n  $drop_table = \"DROP TABLE IF EXISTS `\".PFX.\"tok_owm_current_requests_cache`;\";\n  safe_query($drop_table);\n}\n// }}}\n\n// }}}\n\n  // {{{ main function\nfunction tok_owm_current($atts, $thing = null ) {\n\n  // {{{ get and check Attributes\n  extract( lAtts( array(\n			\'apikey\'  => \'\',\n			\'lang\'  => \'en\',\n			\'na\'  => \'?\',\n			\'cache_secs\'  => \'600\',\n			\'cache_mark\'  => \'\',\n			\'cityids\'  => \'\',\n			\'units\'   => \'metric\',\n			\'display\' => \'<p><strong>[[cityname]]</strong><br /><img src=\"[[icon_url]]\" \'.\n			\'alt=\"[[description]]\" /><br />[[temp_int]]°C</p>\'\n			), $atts ));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check\n  $error = \'\';\n  if ( empty ( $cityids )) {\n    $error .= sprintf( $err_format, \'City ID is missing!\' );\n  }\n  if ( empty ( $apikey )) {\n    $error .= sprintf( $err_format, \'No API key given!\' );\n  }\n  if ( $units != \'metric\' and $units != \'imperial\' ) {\n    $error .= sprintf( $err_format, \'Invalid units system! (\'.\n  		       \'»metric« and »imperial« are allowed, but »\'.\n  		       $units.\'« was given\');\n  }\n  if ( $error ) {\n    return( $error );\n  }\n  // }}}\n\n\n  if ($thing !== null) {\n    $display = $thing;\n  }\n\n  // {{{ return cached data, if there is some\n  $now = time();\n  $index = md5( $cityids.\'_\'.$lang.\'_\'.$units );\n  $cached_data = safe_row(\"timestamp, data\",\n			  \"tok_owm_current_requests_cache\",\n			  \"request=\'$index\'\");\n\n  if( $cached_data ){\n    extract( $cached_data );\n    if ( $now < $timestamp + $cache_secs ) {\n      return( $data.$cache_mark );\n    }\n  };\n  // }}}\n\n  // {{{ else request data from OpenWeatherMap\n\n  // request depends on number of requested cities\n  $request_find = ( strpos( $cityids, \',\') ) ? \"group\" : \"weather\";\n\n  // send request\n  $response_json = file_get_contents\n    (sprintf\n     ( \"http://api.openweathermap.org/data/2.5/%s?id=%s&units=%s&appid=%s&lang=%s\",\n       $request_find,\n       $cityids,\n       $units,\n       $apikey,\n       $lang\n       )\n     );\n  $owm_response = json_decode( $response_json, true );\n  // }}}\n\n  // {{{ assemble plugins output, save to database and return to txp\n  $out = \"\";\n\n  // single city\n  if ( $request_find === \"weather\" ) {\n    $weather = tok_owm_current_import_data( $owm_response, $na );\n    $out = strtr( $display, $weather );\n  }\n  // group of cities\n  else {\n    foreach ( $owm_response[ \"list\" ] as $current_city ) {\n      $weather = tok_owm_current_import_data( $current_city, $na );\n      $out .= strtr( $display, $weather );\n    }\n  }\n\n  if ($thing !== null) {\n    $out = parse( $out );\n  }\n\n  // save output to database\n  if( $cached_data ){\n    safe_update( \"tok_owm_current_requests_cache\",\n		 \"timestamp=\'\".$now.\"\',\".\n		 \"data=\'\".mysql_real_escape_string( $out ).\"\'\",\n		 \"request=\'$index\';\");\n  }\n  else {\n    safe_insert( \"tok_owm_current_requests_cache\",\n		 \"timestamp=\'\".$now.\"\',\".\n		 \"data=\'\".mysql_real_escape_string( $out ).\"\',\".\n		 \"request=\'\".$index.\"\';\");\n  }\n\n  return ( $out );\n  // }}}\n}\n\n\n/* write data */\n/* safe_update(\"aks_cache\", \"ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\", \"hid=\'$hash\'\");  */\n/* safe_insert(\"aks_cache\", \"hid=\'$hash\', ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\");  */\n\n\n\n\n// }}}\n\n  // {{{ function to import data\n// convert response array to plugins variables\nfunction tok_owm_current_import_data( $owm_data, $na ) {\n\n  // basic data\n  $variable_array = array(\"[[id]]\" => $owm_data[\'id\'],\n			  \"[[rx_timestamp_epoch]]\" => $owm_data[\"dt\"],\n			  \"[[cityname]]\" => $owm_data[\"name\"],\n			  \"[[lat]]\" => $owm_data[\"coord\"][\"lat\"],\n			  \"[[lon]]\" => $owm_data[\"coord\"][\"lon\"],\n			  \"[[country]]\" => $owm_data[\"sys\"][\"country\"],\n			  \"[[sunrise]]\" => $owm_data[\"sys\"][\"sunrise\"],\n			  \"[[sunset]]\" => $owm_data[\"sys\"][\"sunset\"],\n			  \"[[temp]]\" => $owm_data[\"main\"][\"temp\"],\n			  \"[[humidity]]\" => $owm_data[\"main\"][\"humidity\"],\n			  \"[[temp_min]]\" => $owm_data[\"main\"][\"temp_min\"],\n			  \"[[temp_max]]\" => $owm_data[\"main\"][\"temp_max\"],\n			  \"[[pressure]]\" => $owm_data[\"main\"][\"pressure\"],\n			  \"[[speed]]\" => $owm_data[\"wind\"][\"speed\"],\n			  \"[[deg]]\" => $owm_data[\"wind\"][\"deg\"],\n			  \"[[clouds]]\" => $owm_data[\"clouds\"][\"all\"],\n			  \"[[condition_id]]\" => $owm_data[\"weather\"][0][\"id\"],\n			  \"[[main]]\" => $owm_data[\"weather\"][0][\"main\"],\n			  \"[[description]]\" => $owm_data[\"weather\"][0][\"description\"],\n			  \"[[icon]]\" => $owm_data[\"weather\"][0][\"icon\"],\n			  // calculated values\n			  \"[[temp_int]]\" => round( $owm_data[\"main\"][\"temp\"] ),\n			  \"[[icon_url]]\" => \'http://openweathermap.org/img/w/\'.$owm_data[\"weather\"][0][\"icon\"].\'.png\',\n			  \"[[city_owm_url]]\" => \'http://www.openweathermap.org/city/\'.$owm_data[\'id\']\n			  );\n\n  // volatile data\n  $variable_array[ \"[[rain3]]\" ] = ( isset( $owm_data[\"rain\"][\"3h\"] ))\n    ? $owm_data[\"rain\"][\"3h\"] : \"0\";\n  $variable_array[ \"[[rain1]]\" ] = ( isset( $owm_data[\"rain\"][\"1h\"] ))\n    ? $owm_data[\"rain\"][\"1h\"] : \"0\";\n  $variable_array[ \"[[snow3]]\" ] = ( isset( $owm_data[\"snow\"][\"3h\"] ))\n    ? $owm_data[\"snow\"][\"3h\"] : \"0\";\n  $variable_array[ \"[[snow1]]\" ] = ( isset( $owm_data[\"snow\"][\"1h\"] ))\n    ? $owm_data[\"snow\"][\"1h\"] : \"0\";\n  $variable_array[ \"[[gust]]\" ] = ( isset( $owm_data[\"wind\"][\"gust\"] ))\n    ? $owm_data[\"wind\"][\"gust\"] : $na;\n  $variable_array[ \"[[pressure_sea_level]]\" ] = ( isset( $owm_data[\"main\"][\"sea_level\"] ))\n    ? $owm_data[\"main\"][\"sea_level\"] : $na;\n  $variable_array[ \"pressure_grnd_level\" ] = ( isset( $owm_data[\"main\"][\"grnd_level\"] ))\n    ? $owm_data[\"main\"][\"grnd_level\"] : $na;\n\n  return ( $variable_array);\n}\n// }}}\n', '\n  // {{{ lifecycle callbacks and functions\n\n    // {{{ Lifecycle callback\nif (@txpinterface == \'admin\') {\n  register_callback(\'tok_owm_current_installation_routine\',\n		    \'plugin_lifecycle.tok_owm_current\', \'installed\');\n  register_callback(\'tok_owm_current_deletion_routine\',\n		    \'plugin_lifecycle.tok_owm_current\', \'deleted\');\n}\n// }}}\n\n    // {{{ installation routine\nfunction tok_owm_current_installation_routine() {\n\n  $create_table =\"CREATE TABLE IF NOT EXISTS `\".PFX.\"tok_owm_current_requests_cache` (\";\n  $create_table .= <<<EOD\n    `id` int(15) NOT NULL AUTO_INCREMENT,\n    `request` varchar(32) NOT NULL,\n    `timestamp` int(11) NOT NULL,\n    `data` mediumtext NOT NULL DEFAULT \'\',\n    PRIMARY KEY (`id`),\n    KEY `request` (`request`)\n    ) DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;\nEOD;\n\n  safe_query($create_table);\n}\n// }}}\n\n    // {{{ deletion routine\nfunction tok_owm_current_deletion_routine() {\n  $drop_table = \"DROP TABLE IF EXISTS `\".PFX.\"tok_owm_current_requests_cache`;\";\n  safe_query($drop_table);\n}\n// }}}\n\n// }}}\n\n  // {{{ main function\nfunction tok_owm_current($atts, $thing = null ) {\n\n  // {{{ get and check Attributes\n  extract( lAtts( array(\n			\'apikey\'  => \'\',\n			\'lang\'  => \'en\',\n			\'na\'  => \'?\',\n			\'cache_secs\'  => \'600\',\n			\'cache_mark\'  => \'\',\n			\'cityids\'  => \'\',\n			\'units\'   => \'metric\',\n			\'display\' => \'<p><strong>[[cityname]]</strong><br /><img src=\"[[icon_url]]\" \'.\n			\'alt=\"[[description]]\" /><br />[[temp_int]]°C</p>\'\n			), $atts ));\n\n  $err_format = \'<span style=\"color:#d12;\" title=\"%s\">█</span>\';\n\n  // check\n  $error = \'\';\n  if ( empty ( $cityids )) {\n    $error .= sprintf( $err_format, \'City ID is missing!\' );\n  }\n  if ( empty ( $apikey )) {\n    $error .= sprintf( $err_format, \'No API key given!\' );\n  }\n  if ( $units != \'metric\' and $units != \'imperial\' ) {\n    $error .= sprintf( $err_format, \'Invalid units system! (\'.\n  		       \'»metric« and »imperial« are allowed, but »\'.\n  		       $units.\'« was given\');\n  }\n  if ( $error ) {\n    return( $error );\n  }\n  // }}}\n\n\n  if ($thing !== null) {\n    $display = $thing;\n  }\n\n  // {{{ return cached data, if there is some\n  $now = time();\n  $index = md5( $cityids.\'_\'.$lang.\'_\'.$units );\n  $cached_data = safe_row(\"timestamp, data\",\n			  \"tok_owm_current_requests_cache\",\n			  \"request=\'$index\'\");\n\n  if( $cached_data ){\n    extract( $cached_data );\n    if ( $now < $timestamp + $cache_secs ) {\n      return( $data.$cache_mark );\n    }\n  };\n  // }}}\n\n  // {{{ else request data from OpenWeatherMap\n\n  // request depends on number of requested cities\n  $request_find = ( strpos( $cityids, \',\') ) ? \"group\" : \"weather\";\n\n  // send request\n  $response_json = file_get_contents\n    (sprintf\n     ( \"http://api.openweathermap.org/data/2.5/%s?id=%s&units=%s&appid=%s&lang=%s\",\n       $request_find,\n       $cityids,\n       $units,\n       $apikey,\n       $lang\n       )\n     );\n  $owm_response = json_decode( $response_json, true );\n  // }}}\n\n  // {{{ assemble plugins output, save to database and return to txp\n  $out = \"\";\n\n  // single city\n  if ( $request_find === \"weather\" ) {\n    $weather = tok_owm_current_import_data( $owm_response, $na );\n    $out = strtr( $display, $weather );\n  }\n  // group of cities\n  else {\n    foreach ( $owm_response[ \"list\" ] as $current_city ) {\n      $weather = tok_owm_current_import_data( $current_city, $na );\n      $out .= strtr( $display, $weather );\n    }\n  }\n\n  if ($thing !== null) {\n    $out = parse( $out );\n  }\n\n  // save output to database\n  if( $cached_data ){\n    safe_update( \"tok_owm_current_requests_cache\",\n		 \"timestamp=\'\".$now.\"\',\".\n		 \"data=\'\".mysql_real_escape_string( $out ).\"\'\",\n		 \"request=\'$index\';\");\n  }\n  else {\n    safe_insert( \"tok_owm_current_requests_cache\",\n		 \"timestamp=\'\".$now.\"\',\".\n		 \"data=\'\".mysql_real_escape_string( $out ).\"\',\".\n		 \"request=\'\".$index.\"\';\");\n  }\n\n  return ( $out );\n  // }}}\n}\n\n\n/* write data */\n/* safe_update(\"aks_cache\", \"ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\", \"hid=\'$hash\'\");  */\n/* safe_insert(\"aks_cache\", \"hid=\'$hash\', ttl=$ttl2, data=\'$data2\', infos=\'$id2|$diff|\".strlen($data).\"\'\");  */\n\n\n\n\n// }}}\n\n  // {{{ function to import data\n// convert response array to plugins variables\nfunction tok_owm_current_import_data( $owm_data, $na ) {\n\n  // basic data\n  $variable_array = array(\"[[id]]\" => $owm_data[\'id\'],\n			  \"[[rx_timestamp_epoch]]\" => $owm_data[\"dt\"],\n			  \"[[cityname]]\" => $owm_data[\"name\"],\n			  \"[[lat]]\" => $owm_data[\"coord\"][\"lat\"],\n			  \"[[lon]]\" => $owm_data[\"coord\"][\"lon\"],\n			  \"[[country]]\" => $owm_data[\"sys\"][\"country\"],\n			  \"[[sunrise]]\" => $owm_data[\"sys\"][\"sunrise\"],\n			  \"[[sunset]]\" => $owm_data[\"sys\"][\"sunset\"],\n			  \"[[temp]]\" => $owm_data[\"main\"][\"temp\"],\n			  \"[[humidity]]\" => $owm_data[\"main\"][\"humidity\"],\n			  \"[[temp_min]]\" => $owm_data[\"main\"][\"temp_min\"],\n			  \"[[temp_max]]\" => $owm_data[\"main\"][\"temp_max\"],\n			  \"[[pressure]]\" => $owm_data[\"main\"][\"pressure\"],\n			  \"[[speed]]\" => $owm_data[\"wind\"][\"speed\"],\n			  \"[[deg]]\" => $owm_data[\"wind\"][\"deg\"],\n			  \"[[clouds]]\" => $owm_data[\"clouds\"][\"all\"],\n			  \"[[condition_id]]\" => $owm_data[\"weather\"][0][\"id\"],\n			  \"[[main]]\" => $owm_data[\"weather\"][0][\"main\"],\n			  \"[[description]]\" => $owm_data[\"weather\"][0][\"description\"],\n			  \"[[icon]]\" => $owm_data[\"weather\"][0][\"icon\"],\n			  // calculated values\n			  \"[[temp_int]]\" => round( $owm_data[\"main\"][\"temp\"] ),\n			  \"[[icon_url]]\" => \'http://openweathermap.org/img/w/\'.$owm_data[\"weather\"][0][\"icon\"].\'.png\',\n			  \"[[city_owm_url]]\" => \'http://www.openweathermap.org/city/\'.$owm_data[\'id\']\n			  );\n\n  // volatile data\n  $variable_array[ \"[[rain3]]\" ] = ( isset( $owm_data[\"rain\"][\"3h\"] ))\n    ? $owm_data[\"rain\"][\"3h\"] : \"0\";\n  $variable_array[ \"[[rain1]]\" ] = ( isset( $owm_data[\"rain\"][\"1h\"] ))\n    ? $owm_data[\"rain\"][\"1h\"] : \"0\";\n  $variable_array[ \"[[snow3]]\" ] = ( isset( $owm_data[\"snow\"][\"3h\"] ))\n    ? $owm_data[\"snow\"][\"3h\"] : \"0\";\n  $variable_array[ \"[[snow1]]\" ] = ( isset( $owm_data[\"snow\"][\"1h\"] ))\n    ? $owm_data[\"snow\"][\"1h\"] : \"0\";\n  $variable_array[ \"[[gust]]\" ] = ( isset( $owm_data[\"wind\"][\"gust\"] ))\n    ? $owm_data[\"wind\"][\"gust\"] : $na;\n  $variable_array[ \"[[pressure_sea_level]]\" ] = ( isset( $owm_data[\"main\"][\"sea_level\"] ))\n    ? $owm_data[\"main\"][\"sea_level\"] : $na;\n  $variable_array[ \"pressure_grnd_level\" ] = ( isset( $owm_data[\"main\"][\"grnd_level\"] ))\n    ? $owm_data[\"main\"][\"grnd_level\"] : $na;\n\n  return ( $variable_array);\n}\n// }}}\n', '236a1dbac286df83d439ad7f0b966978', '', '', 0, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('tok_mailalert', 0, 'Torsten Krueger', 'http://www.kryger.de/', '0.2', 'Automatically send a mail on new posting', '<h1>tok_mailalert</h1>\n\n<p>This plugin adds a checkbox to the Write tab. If the checkmark is set,\n  an email will be sent to a preconfigured list of email addresses\n  when the article is saved the next time.</p>\n\n<p>The result of the mail sending attempt will be show in the same place.</p>\n\n<p>Recipients list and mail body as well as the appearance of the checkbox\n  are configurable on the extensions tab.</p>\n\n<p>Version: 0.1</p>\n\n<h2>Table of contents</h2>\n\n<ol>\n	<li><a href=\"#help-section02\" rel=\"nofollow\">Plugin requirements</a></li>\n	<li><a href=\"#help-section03\" rel=\"nofollow\">Installation</a></li>\n	<li><a href=\"#help-section04\" rel=\"nofollow\">Configuration</a></li>\n	<li><a href=\"#help-section05\" rel=\"nofollow\">Uninstallation</a></li>\n</ol>\n\n\n<h2>Plugin requirements</h2>\n\n<p>This plugin has only been tested with Textpattern 4.5.7 but may run\n    on other/previous versions.</p>\n\n<h2>Installation</h2>\n\n<p>Right after installation the plugin will not work;\n   the list of email recipients must first be configured on the extensions tab.</p>\n\n<p>Other settings have default values and may be changed according to your needs.</p>\n\n<p>These values are stored in textpatterns prefs table.</p>\n\n<h2>Configuration</h2>\n\n<p>Things on the extension tab should not be to hard to understand.\n     But you may use variables in the mail body. They are:</p>\n\n<table>\n		<tr>\n			<th>variable    </th>\n			<th>result                </th>\n		</tr>\n		<tr>\n			<td> &lt;%siteurl%&gt;   </td>\n			<td> Site <span class=\"caps\">URL</span>                </td>\n		</tr>\n		<tr>\n			<td> &lt;%userName%&gt;  </td>\n			<td> name of logged in user  </td>\n		</tr>\n</table>\n\n<h2>Uninstallation</h2>\n\n<p>The plugins deinstallation routine will remove all previously inserted\n     records of the table “txp_prefs”.</p>', '\n/*\n * Setup\n * -----------------------------------------------------------------------------\n */\nif (@txpinterface == \'admin\') {\n\n  // Variables used\n    global $rs,$tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  // plugin registration\n  add_privs(\'tok_mailalert\', \'1\'); // Publishers only\n  register_callback(\'tok_mailalert\', \'article_ui\', \'status\');\n\n  // plugins configuration\n  add_privs(\'tok_mailalert_configuration\', \'1\');\n  add_privs(\'plugin_prefs.tok_mailalert\', \'1,2\');\n  register_tab(\'extensions\', \'tok_mailalert_configuration\', \'tok_mailalert\');\n  register_callback(\'tok_mailalert_configuration\', \'tok_mailalert_configuration\');\n  register_callback(\'tok_mailalert_configuration\', \'plugin_prefs.tok_mailalert\');\n\n  // Lifecycle stuff\n  register_callback(\'tok_mailalert_installation_routine\',\n		    \'plugin_lifecycle.tok_mailalert\', \'installed\');\n  register_callback(\'tok_mailalert_deletion_routine\',\n		    \'plugin_lifecycle.tok_mailalert\', \'deleted\');\n}\n\n/*\n * helper functions\n * -----------------------------------------------------------------------------\n */\n\n// allow variables in mail body\nfunction tok_mail_replace_vars($text, $rs) {\n  // setup vars\n  $userId = $rs[\'AuthorID\'];\n  $articleId = $rs[\'ID\'];\n  $userName = safe_field(\'RealName\', \'txp_users\', \"name = \'\".doSlash($userId).\"\'\");\n  $article = safe_row (\"ID, Status, Title, Body_html, AuthorID\", \"textpattern\", \"ID=$articleId\");\n  $postingTitle = $rs[\'Title\'];\n  $postingExcerpt = $rs[\'Excerpt\'];\n  $postingSection = $rs[\'Section\'];\n  require_once txpath.\'/lib/txplib_publish.php\';\n  $postingBody = parse($article[\'Body_html\']);\n\n  // replace\n  $text = str_replace(\'<%userId%>\', $userId, $text);\n  $text = str_replace(\'<%userName%>\', $userName, $text);\n  $text = str_replace(\'<%siteurl%>\', $GLOBALS[\'prefs\'][\'siteurl\'] , $text);\n  $text = str_replace(\'<%postingTitle%>\', $postingTitle, $text);\n  $text = str_replace(\'<%postingExcerpt%>\', $postingExcerpt, $text);\n  $text = str_replace(\'<%postingBody%>\', $postingBody, $text);\n  $text = str_replace(\'<%postingSection%>\', $postingSection, $text);\n  return($text); }\n\n\n/*\n * main function\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert($event, $step, $data, $rs) {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint,\n    $app_mode;\n\n  // in async mode remove tok_mailalert box from page\n  if ($app_mode == \'async\') {\n    send_script_response(\'$(\"#tok_mailalert_box\").remove();\'); }\n\n  $arraydata = print_r($rs , true);\n\n\n  // check if plugin was setup already\n  if ((! isset($tok_mailalert_recipientlist)) or\n      (! isset($tok_mailalert_subject)) or\n      (! isset($tok_mailalert_mailfrom)) or\n      (! isset($tok_mailalert_mailbody)) or\n      (! isset($tok_mailalert_element_txt_label)) or\n      (! isset($tok_mailalert_element_txt_fail)) or\n      (! isset($tok_mailalert_element_txt_ok)) or\n      (! $tok_mailalert_recipientlist)) {\n    return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	    n.\'<legend>\'.gTxt(\"tok_mailalert_boxtitle_plugin_error\").\'</legend>\'.\n	    n.\'<p class=\"alert-block error\"><strong class=\"error\">\'.\n	    gTxt(\"tok_mailalert_boxtxt_not_yet_setup\").\'</strong></p>\'.\n	    n.\'</fieldset>\'); }\n\n  /*\n   * if checkbox is checked, send mail\n   */\n  if (isset($_POST[\"tok_mailalert_send_action\"])) {\n    // get configured list of recipients\n    $recipients = explode(\",\", $tok_mailalert_recipientlist);\n    // setup parts of mail\n    $mail_subject = tok_mail_replace_vars($tok_mailalert_subject, $rs);\n    $mail_body = tok_mail_replace_vars($tok_mailalert_mailbody, $rs);\n    // if sending mail was requested\n    $error_num = 0;\n    foreach($recipients as $recipient) {\n      $success = mail($recipient,\n  		       $mail_subject,\n  		       chunk_split(base64_encode($mail_body)),\n  		       \"From: \" . $tok_mailalert_mailfrom . \"\\r\\n\" .\n  		       \"Content-Type: text/plain; charset=utf-8\" . \"\\r\\n\" .\n  		       \"Content-Transfer-Encoding: base64\" . \"\\r\\n\");\n      if (! $success) {$error_num += 1;}\n    } // recipient list loop ends\n    if ($error_num > 0) { // inform about mail errors …\n      return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	      n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	      n.\'<p class=\"alert-block error\"><strong>\'.\n	      sprintf($tok_mailalert_element_txt_fail,  $error_num).\n	      \'</strong></p>\'.n.\'</fieldset>\');\n    }\n    else {\n      return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	      n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	      n.\'<p class=\"alert-block success\" style=\"margin:0;\">\'.$tok_mailalert_element_txt_ok.\n	      \'</p>\'.n.\'</fieldset>\');\n    }\n  }\n  return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	  n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	  n.graf(checkbox(\'tok_mailalert_send_action\', \'1\',\n			  false, \'\', \'tok_mailalert_send_check\').\n		 \'<label for=\"tok_mailalert_send_check\">\'.gTxt($tok_mailalert_element_txt_label).\n		 \'</label>\', \' title=\"\' . $tok_mailalert_element_txt_hint . \'\"\').\n	  n.\'</fieldset>\');\n}\n\n\n/*\n * Configuration\n * -----------------------------------------------------------------------------\n */\nfunction tok_mailalert_configuration($event, $step) {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  require_once txpath.\'/include/txp_prefs.php\';\n\n  if (ps(\"save\")) {\n    prefs_save();\n    header(\"Location: index.php?event=tok_mailalert_configuration\"); }\n\n  pagetop(\"Mail Alert Preferences\");\n\n  echo \'<h1>Mail Alert Preferences</h1>\';\n\n  echo form(startTable(\'\', \'\', \'txp-list\').\n	     tr(tdcs(hed(\'List of email recipients\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"Comma separated list<br />of e mail addresses\")).\n		 td(\'<textarea id=\"tok_mailalert_recipientlist\" name=\"tok_mailalert_recipientlist\" \' .\n		     \'rows=\"12\" cols=\"60\">\'.htmlspecialchars($tok_mailalert_recipientlist).\'</textarea>\')).\n	     tr(tdcs(hed(\'Mail Appearance\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"mail from header\")).\n		 td(text_input(\"tok_mailalert_mailfrom\", $tok_mailalert_mailfrom, 60))).\n	     tr(tda(gTxt(\"mail subject\")).\n		 td(text_input(\"tok_mailalert_subject\", $tok_mailalert_subject, 60))).\n	     tr(tda(gTxt(\"mail body<br />\" .\n			    \"(You may use these variables:<br />\" .\n			    \"&lt;%siteurl%&gt: Site URL<br />\" .\n			    \'&lt;%userName%&gt: name of logged in user)\')).\n		 td(\'<textarea id=\"tok_mailalert_mailbody\" name=\"tok_mailalert_mailbody\" \' .\n		     \'rows=\"12\" cols=\"60\">\'.htmlspecialchars($tok_mailalert_mailbody).\'</textarea>\')).\n	     tr(tdcs(hed(\'Element Texts\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"element title\")).\n		 td(text_input(\"tok_mailalert_element_txt_title\", $tok_mailalert_element_txt_title, 60))).\n	     tr(tda(gTxt(\"label text\")).\n		 td(text_input(\"tok_mailalert_element_txt_label\", $tok_mailalert_element_txt_label, 60))).\n	     tr(tda(gTxt(\"label hint\")).\n		 td(text_input(\"tok_mailalert_element_txt_hint\", $tok_mailalert_element_txt_hint, 60))).\n	     tr(tda(gTxt(\"message mailing ok\")).\n		 td(text_input(\"tok_mailalert_element_txt_ok\", $tok_mailalert_element_txt_ok, 60))).\n	     tr(tda(gTxt(\"message mailing failed\")).\n		 td(text_input(\"tok_mailalert_element_txt_fail\", $tok_mailalert_element_txt_fail, 60))).\n	     tr(tda(fInput(\"submit\", \"save\", gTxt(\"save\"), \"publish\").\n		      eInput(\"tok_mailalert_configuration\").\n		      sInput(\'saveprefs\'), \" colspan=\\\"2\\\" class=\\\"noline\\\"\")).\n	     endTable()); }\n\n\n\n\n/*\n * Plugin Installation\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert_installation_routine() {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  // mail variables\n  if (! isset($tok_mailalert_recipientlist)) {\n    $tok_mailalert_recipientlist = \"\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_recipientlist\', val=\'$tok_mailalert_recipientlist\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_mailfrom)) {\n    $tok_mailalert_mailfrom = \"\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_mailfrom\', val=\'$tok_mailalert_mailfrom\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_subject)) {\n    $tok_mailalert_subject = gTxt(\"tok_mailalert_conf_val_mailsubject\");\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_subject\', val=\'$tok_mailalert_subject\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_mailbody)) {\n    $tok_mailalert_mailbody = gTxt(\"tok_mailalert_conf_val_mailbody\");\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_mailbody\', val=\'$tok_mailalert_mailbody\', prefs_id=\'1\'\"); }\n\n  // element appearance\n  if (! isset($tok_mailalert_element_txt_title)) {\n    $tok_mailalert_element_txt_title = \"Mail Alert\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_title\', val=\'$tok_mailalert_element_txt_title\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_label)) {\n    $tok_mailalert_element_txt_label = \"Send Infomail\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_label\', val=\'$tok_mailalert_element_txt_label\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_hint)) {\n    $tok_mailalert_element_txt_hint = \"To have friends of the site informed about a new posts via mail check here\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_hint\', val=\'$tok_mailalert_element_txt_hint\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_ok)) {\n    $tok_mailalert_element_txt_ok = \"Mails have successfully been sent\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_ok\', val=\'$tok_mailalert_element_txt_ok\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_fail)) {\n    $tok_mailalert_element_txt_fail = \"Sending of %s mails failed\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_fail\', val=\'$tok_mailalert_element_txt_fail\', prefs_id=\'1\'\"); }\n}\n\n/*\n * Plugin Deletion\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert_deletion_routine() {\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_recipientlist\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_subject\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_mailfrom\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_mailbody\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_title\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_label\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_label\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_fail\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_ok\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_hint\'\");\n}\n\n', '\n/*\n * Setup\n * -----------------------------------------------------------------------------\n */\nif (@txpinterface == \'admin\') {\n\n  // Variables used\n    global $rs,$tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  // plugin registration\n  add_privs(\'tok_mailalert\', \'1\'); // Publishers only\n  register_callback(\'tok_mailalert\', \'article_ui\', \'status\');\n\n  // plugins configuration\n  add_privs(\'tok_mailalert_configuration\', \'1\');\n  add_privs(\'plugin_prefs.tok_mailalert\', \'1,2\');\n  register_tab(\'extensions\', \'tok_mailalert_configuration\', \'tok_mailalert\');\n  register_callback(\'tok_mailalert_configuration\', \'tok_mailalert_configuration\');\n  register_callback(\'tok_mailalert_configuration\', \'plugin_prefs.tok_mailalert\');\n\n  // Lifecycle stuff\n  register_callback(\'tok_mailalert_installation_routine\',\n		    \'plugin_lifecycle.tok_mailalert\', \'installed\');\n  register_callback(\'tok_mailalert_deletion_routine\',\n		    \'plugin_lifecycle.tok_mailalert\', \'deleted\');\n}\n\n/*\n * helper functions\n * -----------------------------------------------------------------------------\n */\n\n// allow variables in mail body\nfunction tok_mail_replace_vars($text, $rs) {\n  // setup vars\n  $userId = $rs[\'AuthorID\'];\n  $articleId = $rs[\'ID\'];\n  $userName = safe_field(\'RealName\', \'txp_users\', \"name = \'\".doSlash($userId).\"\'\");\n  $article = safe_row (\"ID, Status, Title, Body_html, AuthorID\", \"textpattern\", \"ID=$articleId\");\n  $postingTitle = $rs[\'Title\'];\n  $postingExcerpt = $rs[\'Excerpt\'];\n  $postingSection = $rs[\'Section\'];\n  require_once txpath.\'/lib/txplib_publish.php\';\n  $postingBody = parse($article[\'Body_html\']);\n\n  // replace\n  $text = str_replace(\'<%userId%>\', $userId, $text);\n  $text = str_replace(\'<%userName%>\', $userName, $text);\n  $text = str_replace(\'<%siteurl%>\', $GLOBALS[\'prefs\'][\'siteurl\'] , $text);\n  $text = str_replace(\'<%postingTitle%>\', $postingTitle, $text);\n  $text = str_replace(\'<%postingExcerpt%>\', $postingExcerpt, $text);\n  $text = str_replace(\'<%postingBody%>\', $postingBody, $text);\n  $text = str_replace(\'<%postingSection%>\', $postingSection, $text);\n  return($text); }\n\n\n/*\n * main function\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert($event, $step, $data, $rs) {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint,\n    $app_mode;\n\n  // in async mode remove tok_mailalert box from page\n  if ($app_mode == \'async\') {\n    send_script_response(\'$(\"#tok_mailalert_box\").remove();\'); }\n\n  $arraydata = print_r($rs , true);\n\n\n  // check if plugin was setup already\n  if ((! isset($tok_mailalert_recipientlist)) or\n      (! isset($tok_mailalert_subject)) or\n      (! isset($tok_mailalert_mailfrom)) or\n      (! isset($tok_mailalert_mailbody)) or\n      (! isset($tok_mailalert_element_txt_label)) or\n      (! isset($tok_mailalert_element_txt_fail)) or\n      (! isset($tok_mailalert_element_txt_ok)) or\n      (! $tok_mailalert_recipientlist)) {\n    return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	    n.\'<legend>\'.gTxt(\"tok_mailalert_boxtitle_plugin_error\").\'</legend>\'.\n	    n.\'<p class=\"alert-block error\"><strong class=\"error\">\'.\n	    gTxt(\"tok_mailalert_boxtxt_not_yet_setup\").\'</strong></p>\'.\n	    n.\'</fieldset>\'); }\n\n  /*\n   * if checkbox is checked, send mail\n   */\n  if (isset($_POST[\"tok_mailalert_send_action\"])) {\n    // get configured list of recipients\n    $recipients = explode(\",\", $tok_mailalert_recipientlist);\n    // setup parts of mail\n    $mail_subject = tok_mail_replace_vars($tok_mailalert_subject, $rs);\n    $mail_body = tok_mail_replace_vars($tok_mailalert_mailbody, $rs);\n    // if sending mail was requested\n    $error_num = 0;\n    foreach($recipients as $recipient) {\n      $success = mail($recipient,\n  		       $mail_subject,\n  		       chunk_split(base64_encode($mail_body)),\n  		       \"From: \" . $tok_mailalert_mailfrom . \"\\r\\n\" .\n  		       \"Content-Type: text/plain; charset=utf-8\" . \"\\r\\n\" .\n  		       \"Content-Transfer-Encoding: base64\" . \"\\r\\n\");\n      if (! $success) {$error_num += 1;}\n    } // recipient list loop ends\n    if ($error_num > 0) { // inform about mail errors …\n      return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	      n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	      n.\'<p class=\"alert-block error\"><strong>\'.\n	      sprintf($tok_mailalert_element_txt_fail,  $error_num).\n	      \'</strong></p>\'.n.\'</fieldset>\');\n    }\n    else {\n      return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	      n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	      n.\'<p class=\"alert-block success\" style=\"margin:0;\">\'.$tok_mailalert_element_txt_ok.\n	      \'</p>\'.n.\'</fieldset>\');\n    }\n  }\n  return($data.n.\'<fieldset id=\"tok_mailalert_box\">\'.\n	  n.\'<legend>\'.gTxt($tok_mailalert_element_txt_title).\'</legend>\'.\n	  n.graf(checkbox(\'tok_mailalert_send_action\', \'1\',\n			  false, \'\', \'tok_mailalert_send_check\').\n		 \'<label for=\"tok_mailalert_send_check\">\'.gTxt($tok_mailalert_element_txt_label).\n		 \'</label>\', \' title=\"\' . $tok_mailalert_element_txt_hint . \'\"\').\n	  n.\'</fieldset>\');\n}\n\n\n/*\n * Configuration\n * -----------------------------------------------------------------------------\n */\nfunction tok_mailalert_configuration($event, $step) {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  require_once txpath.\'/include/txp_prefs.php\';\n\n  if (ps(\"save\")) {\n    prefs_save();\n    header(\"Location: index.php?event=tok_mailalert_configuration\"); }\n\n  pagetop(\"Mail Alert Preferences\");\n\n  echo \'<h1>Mail Alert Preferences</h1>\';\n\n  echo form(startTable(\'\', \'\', \'txp-list\').\n	     tr(tdcs(hed(\'List of email recipients\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"Comma separated list<br />of e mail addresses\")).\n		 td(\'<textarea id=\"tok_mailalert_recipientlist\" name=\"tok_mailalert_recipientlist\" \' .\n		     \'rows=\"12\" cols=\"60\">\'.htmlspecialchars($tok_mailalert_recipientlist).\'</textarea>\')).\n	     tr(tdcs(hed(\'Mail Appearance\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"mail from header\")).\n		 td(text_input(\"tok_mailalert_mailfrom\", $tok_mailalert_mailfrom, 60))).\n	     tr(tda(gTxt(\"mail subject\")).\n		 td(text_input(\"tok_mailalert_subject\", $tok_mailalert_subject, 60))).\n	     tr(tda(gTxt(\"mail body<br />\" .\n			    \"(You may use these variables:<br />\" .\n			    \"&lt;%siteurl%&gt: Site URL<br />\" .\n			    \'&lt;%userName%&gt: name of logged in user)\')).\n		 td(\'<textarea id=\"tok_mailalert_mailbody\" name=\"tok_mailalert_mailbody\" \' .\n		     \'rows=\"12\" cols=\"60\">\'.htmlspecialchars($tok_mailalert_mailbody).\'</textarea>\')).\n	     tr(tdcs(hed(\'Element Texts\', 3), 2), \' class=\"pref-heading\"\').\n	     tr(tda(gTxt(\"element title\")).\n		 td(text_input(\"tok_mailalert_element_txt_title\", $tok_mailalert_element_txt_title, 60))).\n	     tr(tda(gTxt(\"label text\")).\n		 td(text_input(\"tok_mailalert_element_txt_label\", $tok_mailalert_element_txt_label, 60))).\n	     tr(tda(gTxt(\"label hint\")).\n		 td(text_input(\"tok_mailalert_element_txt_hint\", $tok_mailalert_element_txt_hint, 60))).\n	     tr(tda(gTxt(\"message mailing ok\")).\n		 td(text_input(\"tok_mailalert_element_txt_ok\", $tok_mailalert_element_txt_ok, 60))).\n	     tr(tda(gTxt(\"message mailing failed\")).\n		 td(text_input(\"tok_mailalert_element_txt_fail\", $tok_mailalert_element_txt_fail, 60))).\n	     tr(tda(fInput(\"submit\", \"save\", gTxt(\"save\"), \"publish\").\n		      eInput(\"tok_mailalert_configuration\").\n		      sInput(\'saveprefs\'), \" colspan=\\\"2\\\" class=\\\"noline\\\"\")).\n	     endTable()); }\n\n\n\n\n/*\n * Plugin Installation\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert_installation_routine() {\n\n  global $tok_mailalert_recipientlist,\n    $tok_mailalert_subject, $tok_mailalert_mailfrom, $tok_mailalert_mailbody,\n    $tok_mailalert_element_txt_title,$tok_mailalert_element_txt_label,\n    $tok_mailalert_element_txt_fail, $tok_mailalert_element_txt_ok,\n    $tok_mailalert_element_txt_hint;\n\n  // mail variables\n  if (! isset($tok_mailalert_recipientlist)) {\n    $tok_mailalert_recipientlist = \"\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_recipientlist\', val=\'$tok_mailalert_recipientlist\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_mailfrom)) {\n    $tok_mailalert_mailfrom = \"\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_mailfrom\', val=\'$tok_mailalert_mailfrom\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_subject)) {\n    $tok_mailalert_subject = gTxt(\"tok_mailalert_conf_val_mailsubject\");\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_subject\', val=\'$tok_mailalert_subject\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_mailbody)) {\n    $tok_mailalert_mailbody = gTxt(\"tok_mailalert_conf_val_mailbody\");\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_mailbody\', val=\'$tok_mailalert_mailbody\', prefs_id=\'1\'\"); }\n\n  // element appearance\n  if (! isset($tok_mailalert_element_txt_title)) {\n    $tok_mailalert_element_txt_title = \"Mail Alert\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_title\', val=\'$tok_mailalert_element_txt_title\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_label)) {\n    $tok_mailalert_element_txt_label = \"Send Infomail\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_label\', val=\'$tok_mailalert_element_txt_label\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_hint)) {\n    $tok_mailalert_element_txt_hint = \"To have friends of the site informed about a new posts via mail check here\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_hint\', val=\'$tok_mailalert_element_txt_hint\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_ok)) {\n    $tok_mailalert_element_txt_ok = \"Mails have successfully been sent\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_ok\', val=\'$tok_mailalert_element_txt_ok\', prefs_id=\'1\'\"); }\n\n  if (! isset($tok_mailalert_element_txt_fail)) {\n    $tok_mailalert_element_txt_fail = \"Sending of %s mails failed\";\n    safe_insert(\'txp_prefs\',\n		\"name=\'tok_mailalert_element_txt_fail\', val=\'$tok_mailalert_element_txt_fail\', prefs_id=\'1\'\"); }\n}\n\n/*\n * Plugin Deletion\n * -----------------------------------------------------------------------------\n */\n\nfunction tok_mailalert_deletion_routine() {\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_recipientlist\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_subject\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_mailfrom\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_mailbody\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_title\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_label\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_label\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_fail\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_ok\'\");\n  safe_delete(\"txp_prefs\",\"name=\'tok_mailalert_element_txt_hint\'\");\n}\n\n', 'c09fbdf41c246ae2071666fa4ce56d1f', '#@admin\n#@language en-gb\ntok_mailalert_boxtitle_plugin_error => »tok_mailalert« plugin error\ntok_mailalert_boxtxt_not_yet_setup => The plugin »tok_mailalert« is not yet setup\ntok_mailalert_conf_val_mailsubject => A new posting was published\ntok_mailalert_conf_val_mailbody => Hi!\r\rPlease visit <%siteurl%> as soon as possible\r\rSincerely, <%userName%>\n#@language de\ntok_mailalert_boxtitle_plugin_error => »tok_mailalert« Plugin Fehler\ntok_mailalert_boxtxt_not_yet_setup => Das Plugin »tok_mailalert« ist noch nicht konfiguriert\ntok_mailalert_conf_val_mailsubject => Ein neues Posting wurde veröffentlicht\ntok_mailalert_conf_val_mailbody => Hallo!\r\rBitte besuche alsbald\r\r<%siteurl%>\r\rViele Grüße\r<%userName%>', '', 4, 5, 3),
('gdv_groupdocs_comparison', 0, ' GroupDocs Marketplace Team', 'groupdocs.com', '0.1', 'GroupDocs is a next generation Document Management solution that makes it easier for businesses to collaborate, share and work with documents online. So, organise, view, annotate, compare, assemble and share all your documents with Textpattern', '', '/**\r\n * GroupDocs Signature Plugin v0.1.0\r\n * Author: Pavel Teplitskiy\r\n * Date: 16.01.2013\r\n *\r\n * By default: Textpattern doesn\'t have WYSIWYG Editor (ex.: TinyMCE), means that you will be seeing\r\n * only <iframe> tag while editing page. See results in \"Article preview\" or your site.\r\n */\r\n\r\n// admin user only\r\nif (@txpinterface == \'admin\') {\r\n    add_privs(\'article\', \'1\'); // Publishers only\r\n    register_callback(\'gdv_append_button_comparison\', \'article_ui\', \'title\');\r\n}\r\n\r\n// add GroupDocs button\r\nfunction gdv_append_button_comparison($event, $step, $data, $rs) {\r\n\r\n    $js= gdv_javascript_signature();\r\n    $button = \'<input type=\"button\" value=\"Insert GroupDocs Comparison\" onclick=\"gdv_insert_comparison()\">\';\r\n\r\n    $output_result = isset($rs[\'url_title\']) ? \'<br/>\' . $js.$button : \'\';\r\n    return $data.$output_result;\r\n}\r\n\r\n// add javascript\r\nfunction gdv_javascript_signature(){\r\n    // jquery is working here\r\n    $r= \'<script>\';\r\n    $r.= \'function gdv_insert_comparison(){\r\n\r\n            // Enter GroupDocs Data\r\n            var area = document.getElementById(\"main_content\");\r\n            var form = document.createElement(\"div\");\r\n            form.setAttribute(\"id\", \"groupdocs\")\r\n            form.innerHTML = \\\'<form action=\"#\" name=\"form\">\\\'+\r\n                                \\\'<label for=\"embedKey\">Embed Key</label><br />\\\'+\r\n                                \\\'<input type=\"text\", name=\"embedKey\" value=\"\" /><br />\\\'+\r\n                                \\\'<label for=\"fileId\">File ID</label><br />\\\'+\r\n                                \\\'<input type=\"text\" name=\"fileid\" value=\"\"><br />\\\'+\r\n                                \\\'<input type=\"button\" name=\"doRequest\" value=\"Make request\" onClick=\"insertIframe();\">\\\'+\r\n                                \\\'<input type=\"button\" name=\"cancel\" value=\"Cancel\" onClick=\"delForm()\">\\\'+\r\n                            \\\'</form>\\\'+\r\n                            \\\'<br />\\\'+\r\n                            \\\'<a target=\"blank\" href=\"http://groupdocs.com/docs/display/gendoc/FAQs\">See our FAQ</a> to learn how to use Comparison.\\\';\r\n            area.appendChild(form);\r\n            }\r\n\r\n            function insertIframe()\r\n            {\r\n                form = document.forms.form;\r\n                embedId = form.embedKey.value;\r\n                fileId = form.fileid.value;\r\n                if (fileId != \"\" && embedId != \"\") {\r\n                    var cmsName = \"Textpattern\"\r\n                    var pluginVersion = \"1.0\"\r\n                    var iframe = \\\'<iframe src=\"https://apps.groupdocs.com/document-comparison/embed/\\\' + embedId + \\\'/\\\' + fileId + \\\'?&referer=Textpattern-Comparison/1.0\" frameborder=\"0\" width=\"500\" height=\"650\">If you can see this text, your browser does not support iframes. Please enable iframe support in your browser or use the latest version of any popular web browser such as Mozilla Firefox or Google Chrome. For more help, please check our documentation Wiki: http://groupdocs.com/docs/display/comparison/GroupDocs+Comparison+Integration+with+3rd+Party+Platforms</iframe>\\\';\r\n                    // insert in the end of <textarea id=\"body\">\r\n                    var gdv_body_comparison = $(\"textarea#body\").html()+ iframe;\r\n                    $(\"textarea#body\").html(gdv_body_comparison);\r\n                    }\r\n           }\r\n           function delForm(){\r\n                del = document.getElementById(\"main_content\");\r\n                del.removeChild(\"groupdocs\");\r\n           }\';\r\n    $r.= \'</script>\';\r\n    return $r;\r\n}', '/**\r\n * GroupDocs Signature Plugin v0.1.0\r\n * Author: Pavel Teplitskiy\r\n * Date: 16.01.2013\r\n *\r\n * By default: Textpattern doesn\'t have WYSIWYG Editor (ex.: TinyMCE), means that you will be seeing\r\n * only <iframe> tag while editing page. See results in \"Article preview\" or your site.\r\n */\r\n\r\n// admin user only\r\nif (@txpinterface == \'admin\') {\r\n    add_privs(\'article\', \'1\'); // Publishers only\r\n    register_callback(\'gdv_append_button_comparison\', \'article_ui\', \'title\');\r\n}\r\n\r\n// add GroupDocs button\r\nfunction gdv_append_button_comparison($event, $step, $data, $rs) {\r\n\r\n    $js= gdv_javascript_signature();\r\n    $button = \'<input type=\"button\" value=\"Insert GroupDocs Comparison\" onclick=\"gdv_insert_comparison()\">\';\r\n\r\n    $output_result = isset($rs[\'url_title\']) ? \'<br/>\' . $js.$button : \'\';\r\n    return $data.$output_result;\r\n}\r\n\r\n// add javascript\r\nfunction gdv_javascript_signature(){\r\n    // jquery is working here\r\n    $r= \'<script>\';\r\n    $r.= \'function gdv_insert_comparison(){\r\n\r\n            // Enter GroupDocs Data\r\n            var area = document.getElementById(\"main_content\");\r\n            var form = document.createElement(\"div\");\r\n            form.setAttribute(\"id\", \"groupdocs\")\r\n            form.innerHTML = \\\'<form action=\"#\" name=\"form\">\\\'+\r\n                                \\\'<label for=\"embedKey\">Embed Key</label><br />\\\'+\r\n                                \\\'<input type=\"text\", name=\"embedKey\" value=\"\" /><br />\\\'+\r\n                                \\\'<label for=\"fileId\">File ID</label><br />\\\'+\r\n                                \\\'<input type=\"text\" name=\"fileid\" value=\"\"><br />\\\'+\r\n                                \\\'<input type=\"button\" name=\"doRequest\" value=\"Make request\" onClick=\"insertIframe();\">\\\'+\r\n                                \\\'<input type=\"button\" name=\"cancel\" value=\"Cancel\" onClick=\"delForm()\">\\\'+\r\n                            \\\'</form>\\\'+\r\n                            \\\'<br />\\\'+\r\n                            \\\'<a target=\"blank\" href=\"http://groupdocs.com/docs/display/gendoc/FAQs\">See our FAQ</a> to learn how to use Comparison.\\\';\r\n            area.appendChild(form);\r\n            }\r\n\r\n            function insertIframe()\r\n            {\r\n                form = document.forms.form;\r\n                embedId = form.embedKey.value;\r\n                fileId = form.fileid.value;\r\n                if (fileId != \"\" && embedId != \"\") {\r\n                    var cmsName = \"Textpattern\"\r\n                    var pluginVersion = \"1.0\"\r\n                    var iframe = \\\'<iframe src=\"https://apps.groupdocs.com/document-comparison/embed/\\\' + embedId + \\\'/\\\' + fileId + \\\'?&referer=Textpattern-Comparison/1.0\" frameborder=\"0\" width=\"500\" height=\"650\">If you can see this text, your browser does not support iframes. Please enable iframe support in your browser or use the latest version of any popular web browser such as Mozilla Firefox or Google Chrome. For more help, please check our documentation Wiki: http://groupdocs.com/docs/display/comparison/GroupDocs+Comparison+Integration+with+3rd+Party+Platforms</iframe>\\\';\r\n                    // insert in the end of <textarea id=\"body\">\r\n                    var gdv_body_comparison = $(\"textarea#body\").html()+ iframe;\r\n                    $(\"textarea#body\").html(gdv_body_comparison);\r\n                    }\r\n           }\r\n           function delForm(){\r\n                del = document.getElementById(\"main_content\");\r\n                del.removeChild(\"groupdocs\");\r\n           }\';\r\n    $r.= \'</script>\';\r\n    return $r;\r\n}', 'd8a539a51bef379ded9892921747c60f', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('goe_sass', 1, 'Georg Oechsler', 'http://txp.oechsler.de/', '0.1', 'Allow use of SASS for CSS output.', '\n<h1>Sassify Textpattern!</h1>\n\n<p>This plugin allows the use of <a href=\"http://sass-lang.com/\">SASS</a> for CSS preprocessing\nwithin textpattern.</p>\n\n<p>It will compile the SASS code you define within the regular CSS style form to\nstatic CSS files, serving it through a tag which is quite similar to the well\nknown &lt;txp:css /&gt; tag.</p>\n\n<p>This plugin relies on <a href=\"http://phpsass.com/\">phpSASS</a> and borrows code from the\n<a href=\"https://drupal.org/project/sassy\">sassy</a> module for Drupal.</p>\n\n<p>It started off as a hack of <a href=\"http://textpattern.org/plugins/934/rvm_css\">rvm_css</a>,\nwhich can do similar things with LESS. Under the hood you will find that it is still\nheavily based on the code of the great <a href=\"http://vanmelick.com/\">Ruud van Melick</a>.</p>\n\n<h2>Installation</h2>\n\n<ol>\n<li>Grab a copy of <a href=\"http://phpsass.com/\">PHPSass</a> and put it somewhere your\nwebserver can find it. The default location is &#8216;textpattern/vendor/phpsass&#8217;.</li>\n<li>Create a directory for the static CSS files on your webserver. You should make\nsure that PHP is able to write to that directory. The default path to this\ndirectory is /css.</li>\n<li>Install and enable the plugin like any other plugin out there.</li>\n<li>Check and adjust the configuration under the extensions tab. It ships with\nsane default. See section &#8220;Configuration&#8221; for details.</li>\n<li>If you want to play safe: Resave a CSS style definition form and see if there\nis a file written to the output directory you have configured in step 2. You&#8217;ll\nneed to resave all your style sheets anyway to create the static files.</li>\n<li>Once your set you may replace all occurrences of &lt;txp:css /&gt; with\n&lt;txp:goe_sass /&gt;.</li>\n</ol>\n\n<p>The &lt;txp:goe_sass /&gt; tag supplied by this plugin has the exact same\nattributes as the built-in &lt;txp:css /&gt; tag and can be used as a drop-in\nreplacement.</p>\n\n<p>Note: Because not all characters are allowed in filenames, avoid using\nnon-alphanumeric characters in style sheet names.</p>\n\n<h2>Configuration</h2>\n\n<p>There are four configuration items for this plugin:</p>\n\n<dl>\n<dt>Path to PHPSass</dt>\n<dd>The filesystem path to PHPSass. This is where the file SassParser.php sits in.</dd>\n<dd>default value: [path to textpattern directory] + vendor/phpsass</dd>\n</dl>\n\n<dl>\n<dt>CSS Output Directory</dt>\n<dd>The location of the css output directory relative to document root.</dd>\n<dd>default value: /css</dd>\n</dl>\n\n<dl>\n<dt>Output Style</dt>\n<dd>There are several output styles to choose from. Choice is yours.</dd>\n<dd>nested: Each property+selector takes up 1 line selector indentation reflects nesting depth.</dd>\n<dd>expanded: Each property+selector takes up 1 line no selector indentation.</dd>\n<dd>compact: Each selector takes up 1 line with properties on the same line. No indentation.</dd>\n<dd>compressed: Compressed: Almost no whitespace designed to be as space-efficient as possible.</dd>\n<dd>default value: nested</dd>\n</dl>\n\n<dl>\n<dt>Language Syntax</dt>\n<dd>PHPSass supports two syntax options.</dd>\n<dd>scss: The regular &#8220;Sassy CSS&#8221; style which is a superset of CSS3&#8217;s syntax.</dd>\n<dd>sass: The older syntax known as the indented syntax.</dd>\n<dd>default value: scss</dd>\n</dl>\n</div>\n\n', 'if (txpinterface == \'admin\') {\n  register_callback(\'goe_sass_css_save\', \'css\', \'css_save\');\n  register_callback(\'goe_sass_css_save\', \'css\', \'css_save_posted\');\n  register_callback(\'goe_sass_css_save\', \'css\', \'del_dec\');\n  register_callback(\'goe_sass_css_delete\', \'css\', \'css_delete\');\n  register_callback(\'goe_sass_cleanup\', \'plugin_lifecycle.goe_sass\', \'deleted\');\n  register_callback(\'goe_sass_default_prefs\', \'plugin_lifecycle.goe_sass\', \'enabled\');\n  register_callback(\'goe_sass_prefs\', \'plugin_prefs.goe_sass\');\n\n  // Register event sassconfig with own tab under extensions and respective callback.\n  add_privs(\'goe_sass_config\');\n  register_tab(\'extensions\', \'goe_sass_config\', gTxt(\'SASS Configuration\'));\n  register_callback(\'goe_sass_config\', \'goe_sass_config\');\n}\n\n\n/**\n * Code for the <txp:goe_sass /> tag.\n *\n * Behaves pretty much like <txp:css /> tag, but links our compiled SASS output\n * as static files.\n */\nfunction goe_sass($atts) {\n  global $txp_error_code, $s, $path_to_site, $version;\n\n  $goe_sass_css_dir = get_pref(\'goe_sass_css_dir\');\n\n  extract(lAtts(array(\n    \'format\' => \'url\',\n    \'media\'  => \'screen\',\n    \'n\'      => \'\',\n    \'name\'   => \'\',\n    \'rel\'    => \'stylesheet\',\n    \'title\'  => \'\',\n  ), $atts));\n\n  if (!$n and !$name) {\n    if ($s) {\n      $name = safe_field(\'css\', \'txp_section\', \"name=\'\".doSlash($s).\"\'\");\n    }\n    else {\n      $name = \'default\';\n    }\n  }\n  elseif (!$name) {\n    $name = $n;\n  }\n\n  // Dispose leading slash on $goe_sass_css_dir, if any, to prevent double\n  // slashes in output.\n  $goe_sass_css_dir = preg_replace(\'#^/#\', \'\', $goe_sass_css_dir);\n  $file = $goe_sass_css_dir . \'/\' . strtolower(sanitizeForUrl($name)).\'.css\';\n\n  if (empty($goe_sass_css_dir) or !is_readable($path_to_site.\'/\'.$file)) {\n    if (version_compare($version, \'4.3.0\', \'>=\')) {\n      unset($atts[\'n\']);\n      $atts[\'name\'] = $name;\n    } else {\n      unset($atts[\'name\']);\n      $atts[\'n\'] = $name;\n    }\n    return css($atts);\n  }\n\n  if ($format == \'link\') {\n    return \'<link rel=\"\' . txpspecialchars($rel) . \'\" type=\"text/css\"\'.\n      ($media ? \' media=\"\' . txpspecialchars($media) . \'\"\' : \'\').\n      ($title ? \' title=\"\' . txpspecialchars($title) . \'\"\' : \'\').\n      \' href=\"\' . hu . txpspecialchars($file) . \'\" />\';\n  }\n\n  return hu . txpspecialchars($file);\n}\n\n/**\n * Callback for the \"css_save\" event.\n *\n * Compile SASS code from database and save it to CSS files.\n */\nfunction goe_sass_css_save() {\n  global $path_to_site;\n\n  $vars = goe_sass_vars();\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n  }\n\n  $name = (ps(\'copy\') or ps(\'savenew\')) ? ps(\'newname\') : ps(\'name\');\n  $filename = strtolower(sanitizeForUrl($name));\n\n  if (empty($goe_sass_css_dir) or !$filename) {\n    return;\n  }\n\n  $css = safe_field(\'css\', \'txp_css\', \"name=\'\".doSlash($name).\"\'\");\n\n  if ($css) {\n    if (preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\', $css)) {\n      $css = base64_decode($css);\n    }\n\n    $library = $goe_sass_path . \'/SassParser.php\';\n    $template_dir = $path_to_site . $goe_sass_css_dir . \'/sass-templates\';\n\n    if (file_exists($library)) {\n      try {\n        require_once ($library);\n\n        $options = array(\n          \'debug\' => TRUE,\n          \'syntax\' => $goe_sass_style,\n          \'style\' => $goe_sass_style,\n          \'load_paths\' => array($template_dir),\n        );\n\n        $parser = new SassParser($options);\n        $css = $parser->toCss($css, false);\n      }\n      catch (Exception $e) {\n        print \"<script>alert(\'SASS Error: \" . preg_replace(\'/\\n/\', \' \', $e->getMessage()) . \"\');</script>\";\n        return;\n      }\n    }\n    $file = $path_to_site . \'/\' . $goe_sass_css_dir . \'/\' . $filename . \'.css\';\n    $handle = fopen($file, \'wb\');\n    fwrite($handle, $css);\n    fclose($handle);\n    chmod($file, 0644);\n\n  }\n}\n\n\n/**\n * Callback for the \"css\" event.\n * Triggered when css is deleted.\n *\n * Unlink compiled css files.\n */\nfunction goe_sass_css_delete()\n{\n  global $path_to_site;\n\n  $goe_sass_css_dir = get_pref(\'goe_sass_css_dir\');\n\n  if (safe_field(\'css\', \'txp_css\', \"name=\'\".doSlash(ps(\'name\')).\"\'\")) {\n    return;\n  }\n\n  $name = strtolower(sanitizeForUrl(ps(\'name\')));\n  $file = $path_to_site . $goe_sass_css_dir . \'/\' . $name;\n\n  if (!empty($goe_sass_css_dir) and $name) {\n    unlink($file.\'.css\');\n  }\n}\n\n/**\n * Helper returning configuration variable data.\n */\nfunction goe_sass_vars() {\n  global $path_to_site;\n\n  return array(\n    \'goe_sass_path\' => array(\n      \'title\' => \'Path to PHPSass\',\n      \'default\' => txpath . \'/vendor/phpsass\',\n    ),\n    \'goe_sass_css_dir\' => array(\n      \'title\' => \'CSS Output Directory\',\n      \'default\' => \'/css\',\n    ),\n    \'goe_sass_style\' => array(\n      \'title\' => \'Output Style\',\n      \'default\' => \'nested\',\n    ),\n    \'goe_sass_syntax\' => array(\n      \'title\' => \'Language Syntax\',\n      \'default\' => \'scss\',\n    ),\n  );\n}\n\n/**\n * Callback for the \"plugin_lifecycle\" event.\n * Triggered when the plugin is deleted.\n *\n * Dispose config variables.\n */\nfunction goe_sass_cleanup($event, $step) {\n  $vars = goe_sass_vars();\n\n  //Delete config variables.\n  foreach (array_keys($vars) as $var) {\n    safe_delete(\'txp_prefs\', \"name=\'$var\'\");\n  }\n}\n\n/**\n * Callback for the \"goe_sass_config\" event.\n *\n * Show and process configuration form.\n */\nfunction goe_sass_config($event, $step) {\n\n  include(txpath . \'/include/txp_prefs.php\');\n\n  $vars = goe_sass_vars();\n\n  // Read value from database or use defaults\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n\n    if (! $$var) {\n      set_pref($var, $data[\'default\']);\n      $$var = $data[\'default\'];\n    }\n  }\n\n  // Save values if form was submitted\n  if (gps(\"submit\")) {\n    foreach (array_keys($vars) as $var) {\n      set_pref($var, addslashes(ps($var)));\n    }\n    header(\"Location: index.php?event=goe_sass_config\");\n  }\n\n  // Output form\n  $sassConfigTxt = gTxt(\"SASS Configuration\");\n  pagetop($sassConfigTxt);\n\n  $tdaStyle = \' style=\"text-align:right;vertical-align:middle\"\';\n  $form = startTable(\"list\") . tr(tdcs(hed($sassConfigTxt,1),2));\n\n  foreach ($vars as $var => $data) {\n    $form .= tr(tda(gTxt($data[\'title\']), $tdaStyle) . tda(text_input($var, $$var, \'40\'), \'\'));\n  }\n\n  $form .= tr(tda(graf(fInput(\"submit\",\"submit\", gTxt(\"Submit\"), \"publish\")), \' colspan=\"2\"\'));\n  $form .= eInput(\"goe_sass_config\");\n  $form .= endTable();\n\n  echo form($form);\n}\n\n/**\n * Callback for the \"plugin_lifecycle\" event.\n * Triggered when the plugin is enabled.\n *\n * Setup default config variables.\n */\nfunction goe_sass_default_prefs($event, $step) {\n\n  $vars = goe_sass_vars();\n\n  // If variables are not set or empty write default values.\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n\n    if (! $$var) {\n      set_pref($var, $data[\'default\']);\n    }\n  }\n}\n', 'if (txpinterface == \'admin\') {\n  register_callback(\'goe_sass_css_save\', \'css\', \'css_save\');\n  register_callback(\'goe_sass_css_save\', \'css\', \'css_save_posted\');\n  register_callback(\'goe_sass_css_save\', \'css\', \'del_dec\');\n  register_callback(\'goe_sass_css_delete\', \'css\', \'css_delete\');\n  register_callback(\'goe_sass_cleanup\', \'plugin_lifecycle.goe_sass\', \'deleted\');\n  register_callback(\'goe_sass_default_prefs\', \'plugin_lifecycle.goe_sass\', \'enabled\');\n  register_callback(\'goe_sass_prefs\', \'plugin_prefs.goe_sass\');\n\n  // Register event sassconfig with own tab under extensions and respective callback.\n  add_privs(\'goe_sass_config\');\n  register_tab(\'extensions\', \'goe_sass_config\', gTxt(\'SASS Configuration\'));\n  register_callback(\'goe_sass_config\', \'goe_sass_config\');\n}\n\n\n/**\n * Code for the <txp:goe_sass /> tag.\n *\n * Behaves pretty much like <txp:css /> tag, but links our compiled SASS output\n * as static files.\n */\nfunction goe_sass($atts) {\n  global $txp_error_code, $s, $path_to_site, $version;\n\n  $goe_sass_css_dir = get_pref(\'goe_sass_css_dir\');\n\n  extract(lAtts(array(\n    \'format\' => \'url\',\n    \'media\'  => \'screen\',\n    \'n\'      => \'\',\n    \'name\'   => \'\',\n    \'rel\'    => \'stylesheet\',\n    \'title\'  => \'\',\n  ), $atts));\n\n  if (!$n and !$name) {\n    if ($s) {\n      $name = safe_field(\'css\', \'txp_section\', \"name=\'\".doSlash($s).\"\'\");\n    }\n    else {\n      $name = \'default\';\n    }\n  }\n  elseif (!$name) {\n    $name = $n;\n  }\n\n  // Dispose leading slash on $goe_sass_css_dir, if any, to prevent double\n  // slashes in output.\n  $goe_sass_css_dir = preg_replace(\'#^/#\', \'\', $goe_sass_css_dir);\n  $file = $goe_sass_css_dir . \'/\' . strtolower(sanitizeForUrl($name)).\'.css\';\n\n  if (empty($goe_sass_css_dir) or !is_readable($path_to_site.\'/\'.$file)) {\n    if (version_compare($version, \'4.3.0\', \'>=\')) {\n      unset($atts[\'n\']);\n      $atts[\'name\'] = $name;\n    } else {\n      unset($atts[\'name\']);\n      $atts[\'n\'] = $name;\n    }\n    return css($atts);\n  }\n\n  if ($format == \'link\') {\n    return \'<link rel=\"\' . txpspecialchars($rel) . \'\" type=\"text/css\"\'.\n      ($media ? \' media=\"\' . txpspecialchars($media) . \'\"\' : \'\').\n      ($title ? \' title=\"\' . txpspecialchars($title) . \'\"\' : \'\').\n      \' href=\"\' . hu . txpspecialchars($file) . \'\" />\';\n  }\n\n  return hu . txpspecialchars($file);\n}\n\n/**\n * Callback for the \"css_save\" event.\n *\n * Compile SASS code from database and save it to CSS files.\n */\nfunction goe_sass_css_save() {\n  global $path_to_site;\n\n  $vars = goe_sass_vars();\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n  }\n\n  $name = (ps(\'copy\') or ps(\'savenew\')) ? ps(\'newname\') : ps(\'name\');\n  $filename = strtolower(sanitizeForUrl($name));\n\n  if (empty($goe_sass_css_dir) or !$filename) {\n    return;\n  }\n\n  $css = safe_field(\'css\', \'txp_css\', \"name=\'\".doSlash($name).\"\'\");\n\n  if ($css) {\n    if (preg_match(\'!^[a-zA-Z0-9/+]*={0,2}$!\', $css)) {\n      $css = base64_decode($css);\n    }\n\n    $library = $goe_sass_path . \'/SassParser.php\';\n    $template_dir = $path_to_site . $goe_sass_css_dir . \'/sass-templates\';\n\n    if (file_exists($library)) {\n      try {\n        require_once ($library);\n\n        $options = array(\n          \'debug\' => TRUE,\n          \'syntax\' => $goe_sass_style,\n          \'style\' => $goe_sass_style,\n          \'load_paths\' => array($template_dir),\n        );\n\n        $parser = new SassParser($options);\n        $css = $parser->toCss($css, false);\n      }\n      catch (Exception $e) {\n        print \"<script>alert(\'SASS Error: \" . preg_replace(\'/\\n/\', \' \', $e->getMessage()) . \"\');</script>\";\n        return;\n      }\n    }\n    $file = $path_to_site . \'/\' . $goe_sass_css_dir . \'/\' . $filename . \'.css\';\n    $handle = fopen($file, \'wb\');\n    fwrite($handle, $css);\n    fclose($handle);\n    chmod($file, 0644);\n\n  }\n}\n\n\n/**\n * Callback for the \"css\" event.\n * Triggered when css is deleted.\n *\n * Unlink compiled css files.\n */\nfunction goe_sass_css_delete()\n{\n  global $path_to_site;\n\n  $goe_sass_css_dir = get_pref(\'goe_sass_css_dir\');\n\n  if (safe_field(\'css\', \'txp_css\', \"name=\'\".doSlash(ps(\'name\')).\"\'\")) {\n    return;\n  }\n\n  $name = strtolower(sanitizeForUrl(ps(\'name\')));\n  $file = $path_to_site . $goe_sass_css_dir . \'/\' . $name;\n\n  if (!empty($goe_sass_css_dir) and $name) {\n    unlink($file.\'.css\');\n  }\n}\n\n/**\n * Helper returning configuration variable data.\n */\nfunction goe_sass_vars() {\n  global $path_to_site;\n\n  return array(\n    \'goe_sass_path\' => array(\n      \'title\' => \'Path to PHPSass\',\n      \'default\' => txpath . \'/vendor/phpsass\',\n    ),\n    \'goe_sass_css_dir\' => array(\n      \'title\' => \'CSS Output Directory\',\n      \'default\' => \'/css\',\n    ),\n    \'goe_sass_style\' => array(\n      \'title\' => \'Output Style\',\n      \'default\' => \'nested\',\n    ),\n    \'goe_sass_syntax\' => array(\n      \'title\' => \'Language Syntax\',\n      \'default\' => \'scss\',\n    ),\n  );\n}\n\n/**\n * Callback for the \"plugin_lifecycle\" event.\n * Triggered when the plugin is deleted.\n *\n * Dispose config variables.\n */\nfunction goe_sass_cleanup($event, $step) {\n  $vars = goe_sass_vars();\n\n  //Delete config variables.\n  foreach (array_keys($vars) as $var) {\n    safe_delete(\'txp_prefs\', \"name=\'$var\'\");\n  }\n}\n\n/**\n * Callback for the \"goe_sass_config\" event.\n *\n * Show and process configuration form.\n */\nfunction goe_sass_config($event, $step) {\n\n  include(txpath . \'/include/txp_prefs.php\');\n\n  $vars = goe_sass_vars();\n\n  // Read value from database or use defaults\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n\n    if (! $$var) {\n      set_pref($var, $data[\'default\']);\n      $$var = $data[\'default\'];\n    }\n  }\n\n  // Save values if form was submitted\n  if (gps(\"submit\")) {\n    foreach (array_keys($vars) as $var) {\n      set_pref($var, addslashes(ps($var)));\n    }\n    header(\"Location: index.php?event=goe_sass_config\");\n  }\n\n  // Output form\n  $sassConfigTxt = gTxt(\"SASS Configuration\");\n  pagetop($sassConfigTxt);\n\n  $tdaStyle = \' style=\"text-align:right;vertical-align:middle\"\';\n  $form = startTable(\"list\") . tr(tdcs(hed($sassConfigTxt,1),2));\n\n  foreach ($vars as $var => $data) {\n    $form .= tr(tda(gTxt($data[\'title\']), $tdaStyle) . tda(text_input($var, $$var, \'40\'), \'\'));\n  }\n\n  $form .= tr(tda(graf(fInput(\"submit\",\"submit\", gTxt(\"Submit\"), \"publish\")), \' colspan=\"2\"\'));\n  $form .= eInput(\"goe_sass_config\");\n  $form .= endTable();\n\n  echo form($form);\n}\n\n/**\n * Callback for the \"plugin_lifecycle\" event.\n * Triggered when the plugin is enabled.\n *\n * Setup default config variables.\n */\nfunction goe_sass_default_prefs($event, $step) {\n\n  $vars = goe_sass_vars();\n\n  // If variables are not set or empty write default values.\n  foreach ($vars as $var => $data) {\n    $$var = get_pref($var);\n\n    if (! $$var) {\n      set_pref($var, $data[\'default\']);\n    }\n  }\n}\n', '70e58dba0b1fe0ca438331abf0e63090', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('esq_sectionsort', 0, '', 'http://textpattern.org', '2.02', 'Custom sorting of sections and categories', 'For help or more information on <strong>esq_sectionsort</strong>, please see the <a href=\"http://forum.textpattern.com/viewtopic.php?id=34637\">forum thread</a>.', '//<?php\nif(@txpinterface == \'admin\') {\nregister_callback(\'esq_sectionsort_section_js\', \'section_ui\', \'multi_edit_options\');\nregister_callback(\'esq_sectionsort_category_js\', \'category_ui\', \'multi_edit_options\');\nregister_callback(\'esq_sectionsort_prefs\', \'plugin_prefs.esq_sectionsort\');\nregister_callback(\'esq_sectionsort_setup\', \'plugin_lifecycle.esq_sectionsort\');\nadd_privs(\'plugin_prefs.esq_sectionsort\',\'1,2,3,4,5,6\');\n}\nfunction esq_sectionsort_section_js() {\necho <<<EOB\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sort_elements\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sortable\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=section\"></script>\n<style>\n#section_form tbody tr td:first-child {\ncursor: move;\n}\n</style>\nEOB;\n}\nfunction esq_sectionsort_category_js() {\nif (defined(\'esq_sectionsort_section_js_run\')) {\nreturn;\n}\ndefine(\'esq_sectionsort_section_js_run\', 1);\necho <<<EOB\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sort_elements\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sortable\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=category\"></script>\n<style>\ntd.categories p:first-child {\nborder-top: 0;\n}\n#category_container .category-tree > .categorysort:first-child {\nborder-top: 1px solid #ddd;\n}\n#category_container .category-tree p .handle {\ncursor: move;\n}\n</style>\nEOB;\n}\nfunction esq_sectionsort_prefs($event=\'\', $step=\'\', $message=\'\') {\nif ($step == \'revertDB\') {\nglobal $txp_user;\nif (safe_field(\'privs\', \'txp_users\', \'name=\\\'\'.doSlash($txp_user).\'\\\'\') != \'1\') {\nexit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.gTxt(\'restricted_area\').\'</p>\');\n}\n}\nswitch($step) {\ncase \'get_js\':\nesq_sectionsort_js(gps(\'js\'));\nbreak;\ncase \'put\':\nesq_sectionsort_js(gps(\'type\'));\nbreak;\ndefault:\n$step = \'default\';\ncase \'revertDB\':\n$step = \'esq_sectionsort_\'.$step;\n$step_result = $step();\npagetop(\'esq_sectionsort Options\', $step_result);\nbreak;\n}\necho \'<div style=\"text-align: center;\"><div style=\"text-align: left; margin: 0 auto; width: 900px;\">\';\nif (esq_sectionsort_checkDB() == true) {\necho form(\n\'<input type=\"hidden\" name=\"event\" value=\"plugin_prefs.esq_sectionsort\" />\'.\n\'<input type=\"hidden\" name=\"step\" value=\"revertDB\" />\'\n.fInput(\'submit\', \'submit\', \'Revert DB & Disable\', \'publish\').\n\'<p>This will undo changes made by <strong>esq_sectionsort</strong> to your Textpattern database, and disable the plugin.</p>\'\n);\necho \'<p>This should only be done before uninstalling <strong>esq_sectionsort</strong>, as it removes all sort preferences. This will cause errors in your Pages and Forms where you have used <code>sort=\"sectionsort\"</code> or <code>sort=\"categorysort\"</code>.</p>\';\necho \'<p>Reverting the database is not necessary before uninstalling the plugin - it has just been included for those who are meticulous about their DB structure, and don\\\'t want superfluous columns lying around.</p>\';\necho \'<p>If you accidentally revert the database, you can re-enable the plugin to set the database up again for use with <strong>esq_sectionsort</strong>, but your sort preferences will have been lost.</p>\';\necho \'<p>Note: Reverting the database effectively runs the MySQL command<br><code>ALTER TABLE `txp_section` DROP `sectionsort`; ALTER TABLE `txp_category` DROP `categorysort`;</code>, which removes the columns \\\'sectionsort\\\' and \\\'categorysort\\\' from the tables \\\'txp_section\\\' and \\\'txp_category\\\' (respectively) in your Textpattern database.\';\necho \'This plugin was designed for Textpattern 4.5.0 and tested up to 4.5.4; although unlikely, future versions may actually make use of columns with this name. As such, if you are uninstalling this plugin because it has been made redundant by a feature in a new Textpattern release, please be careful.</p>\';\n} else {\nif (($step == \'esq_sectionsort_revertDB\') && (!is_array($step_result))) {\necho \'<p>Your Textpattern database has been reverted, and <strong>esq_sectionsort</strong> has (hopefully) been disabled. You may now delete the plugin.</p>\';\necho \'<p>If you have done this by accident, enabling <strong>esq_sectionsort</strong> will restore the required column in your Textpattern database. Your sort preferences have been lost however.</p>\';\n} else {\necho \'<p>Looks like something went wrong. This is probably because you hit the refresh button in your browser - please avoid doing so.</p>\';\necho \'<p>Alternatively your Textpattern database may not have been set up properly during the <strong>esq_sectionsort</strong> installation process. Try checking MySQL user permissions, then disable and re-enable the plugin.</p>\';\n}\n}\necho \'</div></div>\';\n}\nfunction esq_sectionsort_setup($event, $step) {\nswitch ($step) {\ncase \'installed\':\ncase \'enabled\':\nif (esq_sectionsort_checkDB()) {\nif (!esq_sectionsort_checkDB_update()) {\nreturn esq_sectionsort_setupDB_update();\n}\n} else {\nreturn esq_sectionsort_setupDB(ucwords($step));\n}\nbreak;\n}\nreturn \'\';\n}\nfunction esq_sectionsort_default() {\nreturn \'\';\n}\nfunction esq_sectionsort_checkDB() {\n$columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx(\'txp_section\'));\nforeach($columns as $column => $columnData) {\n$columns[$columnData[\'Field\']] = \'\';\nunset($columns[$column]);\n}\nreturn isset($columns[\'sectionsort\']);\n}\nfunction esq_sectionsort_checkDB_update() {\n$columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx(\'txp_category\'));\nforeach($columns as $column => $columnData) {\n$columns[$columnData[\'Field\']] = $columnData[\'Type\'];\nunset($columns[$column]);\n}\nreturn isset($columns[\'categorysort\']) && preg_match(\'/^tinyint/\', $columns[\'categorysort\']);\n}\nfunction esq_sectionsort_setupDB($lifecycle) {\nif (\nsafe_alter(\'txp_section\', \'ADD `sectionsort` TINYINT NULL\') &&\nsafe_alter(\'txp_category\', \'ADD `categorysort` TINYINT NULL\')\n) {\nreturn $lifecycle.\' <strong>esq_sectionsort</strong> and DB setup OK.\';\n} else {\nreturn array(\n$lifecycle.\' <strong>esq_sectionsort</strong>. DB setup failed.\',\nE_ERROR\n);\n}\n}\nfunction sectionsort_convert($a, $b) {\n$a = $a[\'sectionsort\'];\n$b = $b[\'sectionsort\'];\nif ($a === $b) {\nreturn 0;\n}\nreturn $a < $b ? -1 : 1;\n}\nfunction esq_sectionsort_setupDB_update() {\n$current_sectionsort = safe_rows(\'name, sectionsort\', \'txp_section\', \'1=1\');\nusort($current_sectionsort, \'sectionsort_convert\');\nforeach ($current_sectionsort as $sort => $section_data) {\nsafe_update(\'txp_section\', \'sectionsort = \\\'\' . $sort . \'\\\'\', \'name = \\\'\' . $section_data[\'name\'] . \'\\\'\');\n}\nif (\nsafe_alter(\'txp_section\', \'MODIFY `sectionsort` TINYINT NULL\') &&\nsafe_alter(\'txp_category\', \'ADD `categorysort` TINYINT NULL\')\n) {\nreturn \'<strong>esq_sectionsort</strong> and DB updated OK.\';\n} else {\nreturn array(\n\'<strong>esq_sectionsort</strong> updated, but DB update failed. Try adjusting a section\\\'s order, then disable and enable the plugin again.\',\nE_ERROR\n);\n}\n}\nfunction esq_sectionsort_revertDB() {\nif (\nsafe_alter(\'txp_section\', \'DROP `sectionsort`\') &&\nsafe_alter(\'txp_category\', \'DROP `categorysort`\')\n) {\nif (safe_update(\'txp_plugin\', \'status = 0\', \'name = \\\'esq_sectionsort\\\'\')) {\nreturn \'DB reverted and <strong>esq_sectionsort</strong> disabled OK.\';\n} else {\nreturn array(\n\'DB reverted OK. Failed to disabled <strong>esq_sectionsort</strong>.\',\nE_ERROR\n);\n}\n} else {\nreturn array(\n\'DB revert failed. No status change.\',\nE_ERROR\n);\n}\n}\nfunction esq_sectionsort_js($js) {\nheader(\'Content-Type: text/javascript\');\nswitch ($js) {\ncase \'section\':\nif ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\nheader(\'Content-Type: application/json\');\n$success = true;\nforeach ($_POST as $section => $sort) {\nif (!safe_update(\'txp_section\', \'sectionsort=\\\'\'.doSlash($sort).\'\\\'\', \'name=\\\'\'.doSlash($section).\'\\\'\')) {\n$success = false;\n}\n}\necho json_encode(array(\'success\' => $success));\n} else {\n$sectionsort = array();\nforeach(safe_rows(\'name, sectionsort\', \'txp_section\', \'1=1\') as $row) {\n$section = $row[\'name\'];\n$sort = $row[\'sectionsort\'];\nif (!strlen($sort)) {\n$sort = 0;\n}\n$sectionsort[\'txp_section_\' . $section] = ctype_digit($sort) ? (int)$sort : $sort;\n}\necho \'var sectionsort = \', json_encode($sectionsort), \';\'.\"\\n\";\necho <<<EOB\n$(function() {\n$(\'#section_container thead tr\').prepend(\'<th style=\"width: 30px;\">Sort</th>\').find(\'th\').each(function() {\nvar th = $(this);\nth.html(th.text());\n});\n$(\'#section_container table\').css(\'table-layout\', \'fixed\').find(\'tbody tr\').prepend(\'<td></td>\').not(\'#txp_section_default\').appendTo(\'#section_container tbody\').sortElements(function(a, b) {\nvar a_sort = sectionsort[$(a).attr(\'id\')];\nvar b_sort = sectionsort[$(b).attr(\'id\')];\nif (a_sort == b_sort) {\nreturn 0;\n}\nreturn a_sort > b_sort ? 1 : -1;\n}).parent().sortable({\nitems: \'tr:not(:first-child)\',\nhandle: \'td:first-child\',\nstart: function(event, ui) {\nif (!$(\'#cb_toggle_section_detail\').is(\':checked\')) {\nui.placeholder.find(\':nth-child(n+7)\').hide();\n}\n},\nstop: function() {\nvar sectionsort = {};\n$(this).find(\'tr\').each(function() {\nvar tr = $(this);\nsectionsort[tr.attr(\'id\').replace(\'txp_section_\', \'\')] = tr.index();\n});\nvar set_message = function(message, type) {\n$(\'#messagepane\').html(\'<span id=\"message\" class=\"\' + type + \'\">\' + message + \' <a href=\"#close\" class=\"close\">&times;</a></span>\');\n$(\'#message\').fadeOut(\'fast\').fadeIn(\'fast\');\n}\n$.ajax(\n\'index.php?event=plugin_prefs.esq_sectionsort&step=put&type=section\',\n{\ntype: \'POST\',\ndata: sectionsort,\ndataType: \'json\',\nsuccess: function(response) {\nif (response.success) {\nset_message(\'Section order saved OK\', \'success\')\n} else {\nthis.error();\n}\n},\nerror: function() {\nset_message(\'Section order save failed\', \'error\');\n}\n}\n);\n}\n}).find(\'tr\').not(\':first-child\').find(\'td:first-child\').html(\'&uarr;&darr;\');\n});\nEOB;\n}\nbreak;\ncase \'category\':\nif ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\nheader(\'Content-Type: application/json\');\n$success = true;\nforeach ($_POST as $category_id => $sort) {\nif (!safe_update(\'txp_category\', \'categorysort=\\\'\'.doSlash($sort).\'\\\'\', \'id=\\\'\'.doSlash($category_id).\'\\\'\')) {\n$success = false;\n}\n}\necho json_encode(array(\'success\' => $success));\n} else {\n$categorysort = array();\nforeach(safe_rows(\'id, categorysort\', \'txp_category\', \'1=1\') as $row) {\n$category_id = $row[\'id\'];\n$sort = $row[\'categorysort\'];\nif (!strlen($sort)) {\n$sort = 0;\n}\n$categorysort[$category_id] = ctype_digit($sort) ? (int)$sort : $sort;\n}\necho \'var categorysort = \', json_encode($categorysort), \';\'.\"\\n\";\necho <<<EOB\n$(function() {\nvar sort_groups = [];\n$(\'#category_container .category-tree\').each(function() {\n$(this).find(\'p\').wrapAll(\'<div class=\"categorysort\"/>\').each(function() {\nvar current = $(this);\nvar next = current.next(\'p\');\nvar group = current;\nvar level = Number(current.attr(\'class\').replace(\'level-\', \'\'));\nvar category_id = Number(current.find(\'input\').val());\ncurrent.data({\ncategory_id: category_id,\ncategorysort: categorysort[category_id]\n});\ncurrent.prepend(\'<span class=\"handle\">&uarr;&darr;</span>\');\nif (current.data(\'grouped\')) {\nreturn;\n}\nwhile (next.length) {\nvar next_level = Number(next.attr(\'class\').replace(\'level-\', \'\'));\nif (next_level <= level) {\nbreak;\n}\ngroup = group.add(next);\nif (next_level == level) {\nnext.data(\'grouped\', true);\n}\nnext = next.next(\'p\');\n}\nsort_groups.push(group);\n});\n});\n$.each(sort_groups, function() {\nvar group = $(this);\nvar group_top = group.eq(0);\ngroup.wrapAll(\'<div class=\"categorysort\" data-category_id=\"\' + group_top.data(\'category_id\') + \'\" data-categorysort=\"\' + group_top.data(\'categorysort\') + \'\" />\');\n});\n$(\'.categorysort > .categorysort\').parent().each(function() {\n$(this).sortable({\nitems: \'> .categorysort\',\nhandle: \'.handle\',\nstop: function() {\nvar categorysort = {};\nvar group = $(this).find(\'> .categorysort\');\ngroup.each(function() {\nvar el = $(this);\ncategorysort[el.data(\'category_id\')] = el.closest(\'.category-tree\').find(\'.categorysort\').index(el);\n});\nvar set_message = function(message, type) {\n$(\'#messagepane\').html(\'<span id=\"message\" class=\"\' + type + \'\">\' + message + \' <a href=\"#close\" class=\"close\">&times;</a></span>\');\n$(\'#message\').fadeOut(\'fast\').fadeIn(\'fast\');\n}\n$.ajax(\n\'index.php?event=plugin_prefs.esq_sectionsort&step=put&type=category\',\n{\ntype: \'POST\',\ndata: categorysort,\ndataType: \'json\',\nsuccess: function(response) {\nif (response.success) {\nset_message(\'Section order saved OK\', \'success\')\n} else {\nthis.error();\n}\n},\nerror: function() {\nset_message(\'Section order save failed\', \'error\');\n}\n}\n);\n}\n}).find(\'> .categorysort\').sortElements(function(a, b) {\nvar a_sort = $(a).data(\'categorysort\');\nvar b_sort = $(b).data(\'categorysort\');\nif (a_sort == b_sort) {\nreturn 0;\n}\nreturn a_sort > b_sort ? 1 : -1;\n});\n});\n});\nEOB;\n}\nbreak;\ncase \'sort_elements\':\necho <<<EOB\n/**\n* jQuery.fn.sortElements\n* --------------\n* @author James Padolsey (http://james.padolsey.com)\n* @version 0.11\n* @updated 18-MAR-2010\n* --------------\n* @param Function comparator:\n*   Exactly the same behaviour as [1,2,3].sort(comparator)\n*   \n* @param Function getSortable\n*   A function that should return the element that is\n*   to be sorted. The comparator will run on the\n*   current collection, but you may want the actual\n*   resulting sort to occur on a parent or another\n*   associated element.\n*   \n*   E.g. $(\'td\').sortElements(comparator, function(){\n*      return this.parentNode; \n*   })\n*   \n*   The <td>\'s parent (<tr>) will be sorted instead\n*   of the <td> itself.\n*/\njQuery.fn.sortElements = (function(){\nvar sort = [].sort;\nreturn function(comparator, getSortable) {\ngetSortable = getSortable || function(){return this;};\nvar placements = this.map(function(){\nvar sortElement = getSortable.call(this),\nparentNode = sortElement.parentNode,\n// Since the element itself will change position, we have\n// to have some way of storing it\'s original position in\n// the DOM. The easiest way is to have a \'flag\' node:\nnextSibling = parentNode.insertBefore(\ndocument.createTextNode(\'\'),\nsortElement.nextSibling\n);\nreturn function() {\nif (parentNode === this) {\nthrow new Error(\n\"You can\'t sort elements if any one is a descendant of another.\"\n);\n}\n// Insert before flag:\nparentNode.insertBefore(this, nextSibling);\n// Remove flag:\nparentNode.removeChild(nextSibling);\n};\n});\nreturn sort.call(this, comparator).each(function(i){\nplacements[i].call(getSortable.call(this));\n});\n};\n})();\nEOB;\nbreak;\ncase \'sortable\':\necho <<<EOB\n/*! jQuery UI - v1.10.3 - 2013-09-24\n* http://jqueryui.com\n* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js\n* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */\n(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return\"area\"===r?(s=t.parentNode,a=s.name,t.href&&a&&\"map\"===s.nodeName.toLowerCase()?(o=e(\"img[usemap=#\"+a+\"]\")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:\"a\"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return\"hidden\"===e.css(this,\"visibility\")}).length}var s=0,a=/^ui-id-\\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:\"1.10.3\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return\"number\"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css(\"zIndex\",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css(\"position\"),(\"absolute\"===n||\"relative\"===n||\"fixed\"===n)&&(s=parseInt(a.css(\"zIndex\"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr(\"id\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e(\"<a>\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,\"padding\"+this))||0,n&&(i-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(i-=parseFloat(e.css(t,\"margin\"+this))||0)}),i}var a=\"Width\"===n?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+n]=function(i){return i===t?r[\"inner\"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+\"px\")})},e.fn[\"outer\"+n]=function(t,i){return\"number\"!=typeof t?r[\"outer\"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+\"px\")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e(\"<a>\").data(\"a-b\",\"a\").removeData(\"a-b\").data(\"a-b\")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart=\"onselectstart\"in document.createElement(\"div\"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if(\"hidden\"===e(t).css(\"overflow\"))return!1;var n=i&&\"left\"===i?\"scrollLeft\":\"scrollTop\",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler(\"remove\")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(\".\")[0];i=i.split(\".\")[1],o=c+\"-\"+i,n||(n=s,s=t.Widget),t.expr[\":\"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+\".\"+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r=\"string\"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&\"_\"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error(\"no such method \'\"+a+\"\' for \"+i+\" widget instance\"):t.error(\"cannot call methods on \"+i+\" prior to initialization; \"+\"attempted to call method \'\"+a+\"\'\")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:\"widget\",widgetEventPrefix:\"\",defaultElement:\"<div>\",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if(\"string\"==typeof i)if(r={},n=i.split(\".\"),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),s===e)return o[i]===e?null:o[i];o[i]=s}else{if(s===e)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,\"disabled\"===t&&(this.widget().toggleClass(this.widgetFullName+\"-disabled ui-state-disabled\",!!e).attr(\"aria-disabled\",e),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")),this},enable:function(){return this._setOption(\"disabled\",!1)},disable:function(){return this._setOption(\"disabled\",!0)},_on:function(i,s,n){var o,a=this;\"boolean\"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass(\"ui-state-disabled\")?(\"string\"==typeof r?a[r]:r).apply(a,arguments):e}\"string\"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\\w+)\\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||\"\").split(\" \").join(this.eventNamespace+\" \")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return(\"string\"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass(\"ui-state-hover\")},mouseleave:function(e){t(e.currentTarget).removeClass(\"ui-state-hover\")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass(\"ui-state-focus\")},focusout:function(e){t(e.currentTarget).removeClass(\"ui-state-focus\")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:\"fadeIn\",hide:\"fadeOut\"},function(e,i){t.Widget.prototype[\"_\"+e]=function(s,n,o){\"string\"==typeof n&&(n={effect:n});var a,r=n?n===!0||\"number\"==typeof n?i:n.effect||i:e;n=n||{},\"number\"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget(\"ui.mouse\",{version:\"1.10.3\",options:{cancel:\"input,textarea,button,select,option\",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind(\"mousedown.\"+this.widgetName,function(t){return e._mouseDown(t)}).bind(\"click.\"+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+\".preventClickEvent\")?(t.removeData(i.target,e.widgetName+\".preventClickEvent\"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind(\".\"+this.widgetName),this._mouseMoveDelegate&&t(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a=\"string\"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+\".preventClickEvent\")&&t.removeData(i.target,this.widgetName+\".preventClickEvent\"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).bind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css(\"float\"))||/inline|table-cell/.test(t.css(\"display\"))}t.widget(\"ui.sortable\",t.ui.mouse,{version:\"1.10.3\",widgetEventPrefix:\"sort\",ready:!1,options:{appendTo:\"parent\",axis:!1,connectWith:!1,containment:!1,cursor:\"auto\",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:\"original\",items:\"> *\",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:\"default\",tolerance:\"intersect\",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass(\"ui-sortable\"),this.refresh(),this.floating=this.items.length?\"x\"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass(\"ui-sortable ui-sortable-disabled\"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+\"-item\");return this},_setOption:function(e,i){\"disabled\"===e?(this.options[e]=i,this.widget().toggleClass(\"ui-sortable-disabled\",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||\"static\"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+\"-item\")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+\"-item\")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find(\"*\").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css(\"position\",\"absolute\"),this.cssPosition=this.helper.css(\"position\"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&\"auto\"!==a.cursor&&(o=this.document.find(\"body\"),this.storedCursor=o.css(\"cursor\"),o.css(\"cursor\",a.cursor),this.storedStylesheet=t(\"<style>*{ cursor: \"+a.cursor+\" !important; }</style>\").appendTo(o)),a.opacity&&(this.helper.css(\"opacity\")&&(this._storedOpacity=this.helper.css(\"opacity\")),this.helper.css(\"opacity\",a.opacity)),a.zIndex&&(this.helper.css(\"zIndex\")&&(this._storedZIndex=this.helper.css(\"zIndex\")),this.helper.css(\"zIndex\",a.zIndex)),this.scrollParent[0]!==document&&\"HTML\"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger(\"start\",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger(\"activate\",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass(\"ui-sortable-helper\"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo(\"absolute\"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&\"HTML\"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-t(document).scrollTop()<a.scrollSensitivity?r=t(document).scrollTop(t(document).scrollTop()-a.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<a.scrollSensitivity&&(r=t(document).scrollTop(t(document).scrollTop()+a.scrollSpeed)),e.pageX-t(document).scrollLeft()<a.scrollSensitivity?r=t(document).scrollLeft(t(document).scrollLeft()-a.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<a.scrollSensitivity&&(r=t(document).scrollLeft(t(document).scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo(\"absolute\"),this.options.axis&&\"y\"===this.options.axis||(this.helper[0].style.left=this.position.left+\"px\"),this.options.axis&&\"x\"===this.options.axis||(this.helper[0].style.top=this.position.top+\"px\"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?\"next\":\"prev\"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&(\"semi-dynamic\"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?\"down\":\"up\",\"pointer\"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger(\"change\",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger(\"sort\",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&\"x\"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&\"y\"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),\"original\"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass(\"ui-sortable-helper\"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger(\"deactivate\",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger(\"out\",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),\"original\"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||\"id\")||\"\").match(e.expression||/(.+)[\\-=_](.+)/);i&&s.push((e.key||i[1]+\"[]\")+\"=\"+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+\"=\"),s.join(\"&\")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||\"id\")||\"\")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u=\"x\"===this.options.axis||s+l>r&&h>s+l,d=\"y\"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return\"pointer\"===this.options.tolerance||this.options.forcePointerForContainers||\"pointer\"!==this.options.tolerance&&this.helperProportions[this.floating?\"width\":\"height\"]>t[this.floating?\"width\":\"height\"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i=\"x\"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s=\"y\"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&\"right\"===a||\"down\"===o?2:1:o&&(\"down\"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?\"right\"===o&&s||\"left\"===o&&!s:n&&(\"down\"===n&&i||\"up\"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?\"down\":\"up\")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?\"right\":\"left\")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){var i,s,n,o,a=[],r=[],h=this._connectWith();if(h&&e)for(i=h.length-1;i>=0;i--)for(n=t(h[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&r.push([t.isFunction(o.options.items)?o.options.items.call(o.element):t(o.options.items,o.element).not(\".ui-sortable-helper\").not(\".ui-sortable-placeholder\"),o]);for(r.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(\".ui-sortable-helper\").not(\".ui-sortable-placeholder\"),this]),i=r.length-1;i>=0;i--)r[i][0].each(function(){a.push(this)});return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(\":data(\"+this.widgetName+\"-item)\");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+\"-item\",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t(\"<\"+s+\">\",e.document[0]).addClass(i||e.currentItem[0].className+\" ui-sortable-placeholder\").removeClass(\"ui-sortable-helper\");return\"tr\"===s?e.currentItem.children().each(function(){t(\"<td>&#160;</td>\",e.document[0]).attr(\"colspan\",t(this).attr(\"colspan\")||1).appendTo(n)}):\"img\"===s&&n.attr(\"src\",e.currentItem.attr(\"src\")),i||n.css(\"visibility\",\"hidden\"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css(\"paddingTop\")||0,10)-parseInt(e.currentItem.css(\"paddingBottom\")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css(\"paddingLeft\")||0,10)-parseInt(e.currentItem.css(\"paddingRight\")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger(\"out\",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger(\"over\",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?\"left\":\"top\",l=p?\"width\":\"height\",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?\"up\":\"down\"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger(\"change\",s,this._uiHash()),this.containers[g]._trigger(\"change\",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger(\"over\",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):\"clone\"===i.helper?this.currentItem.clone():this.currentItem;return s.parents(\"body\").length||t(\"parent\"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css(\"position\"),top:this.currentItem.css(\"top\"),left:this.currentItem.css(\"left\")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){\"string\"==typeof e&&(e=e.split(\" \")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),\"left\"in e&&(this.offset.click.left=e.left+this.margins.left),\"right\"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),\"top\"in e&&(this.offset.click.top=e.top+this.margins.top),\"bottom\"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return\"absolute\"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&\"html\"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css(\"borderTopWidth\"),10)||0),left:e.left+(parseInt(this.offsetParent.css(\"borderLeftWidth\"),10)||0)}},_getRelativeOffset:function(){if(\"relative\"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css(\"top\"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css(\"left\"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css(\"marginLeft\"),10)||0,top:parseInt(this.currentItem.css(\"marginTop\"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;\"parent\"===n.containment&&(n.containment=this.helper[0].parentNode),(\"document\"===n.containment||\"window\"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t(\"document\"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t(\"document\"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s=\"hidden\"!==t(e).css(\"overflow\"),this.containment=[i.left+(parseInt(t(e).css(\"borderLeftWidth\"),10)||0)+(parseInt(t(e).css(\"paddingLeft\"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css(\"borderTopWidth\"),10)||0)+(parseInt(t(e).css(\"paddingTop\"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css(\"borderLeftWidth\"),10)||0)-(parseInt(t(e).css(\"paddingRight\"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css(\"borderTopWidth\"),10)||0)-(parseInt(t(e).css(\"paddingBottom\"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s=\"absolute\"===e?1:-1,n=\"absolute\"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-(\"fixed\"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-(\"fixed\"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r=\"absolute\"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return\"relative\"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(\"fixed\"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(\"fixed\"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],\"down\"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)(\"auto\"===this._storedCSS[i]||\"static\"===this._storedCSS[i])&&(this._storedCSS[i]=\"\");this.currentItem.css(this._storedCSS).removeClass(\"ui-sortable-helper\")}else this.currentItem.show();for(this.fromOutside&&!e&&s.push(function(t){this._trigger(\"receive\",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(\".ui-sortable-helper\")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||s.push(function(t){this._trigger(\"update\",t,this._uiHash())}),this!==this.currentContainer&&(e||(s.push(function(t){this._trigger(\"remove\",t,this._uiHash())}),s.push(function(t){return function(e){t._trigger(\"receive\",e,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(t){return function(e){t._trigger(\"update\",e,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)e||s.push(function(t){return function(e){t._trigger(\"deactivate\",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(function(t){return function(e){t._trigger(\"out\",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find(\"body\").css(\"cursor\",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css(\"opacity\",this._storedOpacity),this._storedZIndex&&this.helper.css(\"zIndex\",\"auto\"===this._storedZIndex?\"\":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger(\"beforeStop\",t,this._uiHash()),i=0;s.length>i;i++)s[i].call(this,t);this._trigger(\"stop\",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger(\"beforeStop\",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(i=0;s.length>i;i++)s[i].call(this,t);this._trigger(\"stop\",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);\nEOB;\nbreak;\n}\nexit();\n}', '//<?php\nif(@txpinterface == \'admin\') {\nregister_callback(\'esq_sectionsort_section_js\', \'section_ui\', \'multi_edit_options\');\nregister_callback(\'esq_sectionsort_category_js\', \'category_ui\', \'multi_edit_options\');\nregister_callback(\'esq_sectionsort_prefs\', \'plugin_prefs.esq_sectionsort\');\nregister_callback(\'esq_sectionsort_setup\', \'plugin_lifecycle.esq_sectionsort\');\nadd_privs(\'plugin_prefs.esq_sectionsort\',\'1,2,3,4,5,6\');\n}\nfunction esq_sectionsort_section_js() {\necho <<<EOB\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sort_elements\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sortable\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=section\"></script>\n<style>\n#section_form tbody tr td:first-child {\ncursor: move;\n}\n</style>\nEOB;\n}\nfunction esq_sectionsort_category_js() {\nif (defined(\'esq_sectionsort_section_js_run\')) {\nreturn;\n}\ndefine(\'esq_sectionsort_section_js_run\', 1);\necho <<<EOB\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sort_elements\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=sortable\"></script>\n<script src=\"index.php?event=plugin_prefs.esq_sectionsort&step=get_js&js=category\"></script>\n<style>\ntd.categories p:first-child {\nborder-top: 0;\n}\n#category_container .category-tree > .categorysort:first-child {\nborder-top: 1px solid #ddd;\n}\n#category_container .category-tree p .handle {\ncursor: move;\n}\n</style>\nEOB;\n}\nfunction esq_sectionsort_prefs($event=\'\', $step=\'\', $message=\'\') {\nif ($step == \'revertDB\') {\nglobal $txp_user;\nif (safe_field(\'privs\', \'txp_users\', \'name=\\\'\'.doSlash($txp_user).\'\\\'\') != \'1\') {\nexit(pageTop(\'Restricted\').\'<p style=\"margin-top:3em;text-align:center\">\'.gTxt(\'restricted_area\').\'</p>\');\n}\n}\nswitch($step) {\ncase \'get_js\':\nesq_sectionsort_js(gps(\'js\'));\nbreak;\ncase \'put\':\nesq_sectionsort_js(gps(\'type\'));\nbreak;\ndefault:\n$step = \'default\';\ncase \'revertDB\':\n$step = \'esq_sectionsort_\'.$step;\n$step_result = $step();\npagetop(\'esq_sectionsort Options\', $step_result);\nbreak;\n}\necho \'<div style=\"text-align: center;\"><div style=\"text-align: left; margin: 0 auto; width: 900px;\">\';\nif (esq_sectionsort_checkDB() == true) {\necho form(\n\'<input type=\"hidden\" name=\"event\" value=\"plugin_prefs.esq_sectionsort\" />\'.\n\'<input type=\"hidden\" name=\"step\" value=\"revertDB\" />\'\n.fInput(\'submit\', \'submit\', \'Revert DB & Disable\', \'publish\').\n\'<p>This will undo changes made by <strong>esq_sectionsort</strong> to your Textpattern database, and disable the plugin.</p>\'\n);\necho \'<p>This should only be done before uninstalling <strong>esq_sectionsort</strong>, as it removes all sort preferences. This will cause errors in your Pages and Forms where you have used <code>sort=\"sectionsort\"</code> or <code>sort=\"categorysort\"</code>.</p>\';\necho \'<p>Reverting the database is not necessary before uninstalling the plugin - it has just been included for those who are meticulous about their DB structure, and don\\\'t want superfluous columns lying around.</p>\';\necho \'<p>If you accidentally revert the database, you can re-enable the plugin to set the database up again for use with <strong>esq_sectionsort</strong>, but your sort preferences will have been lost.</p>\';\necho \'<p>Note: Reverting the database effectively runs the MySQL command<br><code>ALTER TABLE `txp_section` DROP `sectionsort`; ALTER TABLE `txp_category` DROP `categorysort`;</code>, which removes the columns \\\'sectionsort\\\' and \\\'categorysort\\\' from the tables \\\'txp_section\\\' and \\\'txp_category\\\' (respectively) in your Textpattern database.\';\necho \'This plugin was designed for Textpattern 4.5.0 and tested up to 4.5.4; although unlikely, future versions may actually make use of columns with this name. As such, if you are uninstalling this plugin because it has been made redundant by a feature in a new Textpattern release, please be careful.</p>\';\n} else {\nif (($step == \'esq_sectionsort_revertDB\') && (!is_array($step_result))) {\necho \'<p>Your Textpattern database has been reverted, and <strong>esq_sectionsort</strong> has (hopefully) been disabled. You may now delete the plugin.</p>\';\necho \'<p>If you have done this by accident, enabling <strong>esq_sectionsort</strong> will restore the required column in your Textpattern database. Your sort preferences have been lost however.</p>\';\n} else {\necho \'<p>Looks like something went wrong. This is probably because you hit the refresh button in your browser - please avoid doing so.</p>\';\necho \'<p>Alternatively your Textpattern database may not have been set up properly during the <strong>esq_sectionsort</strong> installation process. Try checking MySQL user permissions, then disable and re-enable the plugin.</p>\';\n}\n}\necho \'</div></div>\';\n}\nfunction esq_sectionsort_setup($event, $step) {\nswitch ($step) {\ncase \'installed\':\ncase \'enabled\':\nif (esq_sectionsort_checkDB()) {\nif (!esq_sectionsort_checkDB_update()) {\nreturn esq_sectionsort_setupDB_update();\n}\n} else {\nreturn esq_sectionsort_setupDB(ucwords($step));\n}\nbreak;\n}\nreturn \'\';\n}\nfunction esq_sectionsort_default() {\nreturn \'\';\n}\nfunction esq_sectionsort_checkDB() {\n$columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx(\'txp_section\'));\nforeach($columns as $column => $columnData) {\n$columns[$columnData[\'Field\']] = \'\';\nunset($columns[$column]);\n}\nreturn isset($columns[\'sectionsort\']);\n}\nfunction esq_sectionsort_checkDB_update() {\n$columns = getRows(\'SHOW COLUMNS FROM \'.safe_pfx(\'txp_category\'));\nforeach($columns as $column => $columnData) {\n$columns[$columnData[\'Field\']] = $columnData[\'Type\'];\nunset($columns[$column]);\n}\nreturn isset($columns[\'categorysort\']) && preg_match(\'/^tinyint/\', $columns[\'categorysort\']);\n}\nfunction esq_sectionsort_setupDB($lifecycle) {\nif (\nsafe_alter(\'txp_section\', \'ADD `sectionsort` TINYINT NULL\') &&\nsafe_alter(\'txp_category\', \'ADD `categorysort` TINYINT NULL\')\n) {\nreturn $lifecycle.\' <strong>esq_sectionsort</strong> and DB setup OK.\';\n} else {\nreturn array(\n$lifecycle.\' <strong>esq_sectionsort</strong>. DB setup failed.\',\nE_ERROR\n);\n}\n}\nfunction sectionsort_convert($a, $b) {\n$a = $a[\'sectionsort\'];\n$b = $b[\'sectionsort\'];\nif ($a === $b) {\nreturn 0;\n}\nreturn $a < $b ? -1 : 1;\n}\nfunction esq_sectionsort_setupDB_update() {\n$current_sectionsort = safe_rows(\'name, sectionsort\', \'txp_section\', \'1=1\');\nusort($current_sectionsort, \'sectionsort_convert\');\nforeach ($current_sectionsort as $sort => $section_data) {\nsafe_update(\'txp_section\', \'sectionsort = \\\'\' . $sort . \'\\\'\', \'name = \\\'\' . $section_data[\'name\'] . \'\\\'\');\n}\nif (\nsafe_alter(\'txp_section\', \'MODIFY `sectionsort` TINYINT NULL\') &&\nsafe_alter(\'txp_category\', \'ADD `categorysort` TINYINT NULL\')\n) {\nreturn \'<strong>esq_sectionsort</strong> and DB updated OK.\';\n} else {\nreturn array(\n\'<strong>esq_sectionsort</strong> updated, but DB update failed. Try adjusting a section\\\'s order, then disable and enable the plugin again.\',\nE_ERROR\n);\n}\n}\nfunction esq_sectionsort_revertDB() {\nif (\nsafe_alter(\'txp_section\', \'DROP `sectionsort`\') &&\nsafe_alter(\'txp_category\', \'DROP `categorysort`\')\n) {\nif (safe_update(\'txp_plugin\', \'status = 0\', \'name = \\\'esq_sectionsort\\\'\')) {\nreturn \'DB reverted and <strong>esq_sectionsort</strong> disabled OK.\';\n} else {\nreturn array(\n\'DB reverted OK. Failed to disabled <strong>esq_sectionsort</strong>.\',\nE_ERROR\n);\n}\n} else {\nreturn array(\n\'DB revert failed. No status change.\',\nE_ERROR\n);\n}\n}\nfunction esq_sectionsort_js($js) {\nheader(\'Content-Type: text/javascript\');\nswitch ($js) {\ncase \'section\':\nif ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\nheader(\'Content-Type: application/json\');\n$success = true;\nforeach ($_POST as $section => $sort) {\nif (!safe_update(\'txp_section\', \'sectionsort=\\\'\'.doSlash($sort).\'\\\'\', \'name=\\\'\'.doSlash($section).\'\\\'\')) {\n$success = false;\n}\n}\necho json_encode(array(\'success\' => $success));\n} else {\n$sectionsort = array();\nforeach(safe_rows(\'name, sectionsort\', \'txp_section\', \'1=1\') as $row) {\n$section = $row[\'name\'];\n$sort = $row[\'sectionsort\'];\nif (!strlen($sort)) {\n$sort = 0;\n}\n$sectionsort[\'txp_section_\' . $section] = ctype_digit($sort) ? (int)$sort : $sort;\n}\necho \'var sectionsort = \', json_encode($sectionsort), \';\'.\"\\n\";\necho <<<EOB\n$(function() {\n$(\'#section_container thead tr\').prepend(\'<th style=\"width: 30px;\">Sort</th>\').find(\'th\').each(function() {\nvar th = $(this);\nth.html(th.text());\n});\n$(\'#section_container table\').css(\'table-layout\', \'fixed\').find(\'tbody tr\').prepend(\'<td></td>\').not(\'#txp_section_default\').appendTo(\'#section_container tbody\').sortElements(function(a, b) {\nvar a_sort = sectionsort[$(a).attr(\'id\')];\nvar b_sort = sectionsort[$(b).attr(\'id\')];\nif (a_sort == b_sort) {\nreturn 0;\n}\nreturn a_sort > b_sort ? 1 : -1;\n}).parent().sortable({\nitems: \'tr:not(:first-child)\',\nhandle: \'td:first-child\',\nstart: function(event, ui) {\nif (!$(\'#cb_toggle_section_detail\').is(\':checked\')) {\nui.placeholder.find(\':nth-child(n+7)\').hide();\n}\n},\nstop: function() {\nvar sectionsort = {};\n$(this).find(\'tr\').each(function() {\nvar tr = $(this);\nsectionsort[tr.attr(\'id\').replace(\'txp_section_\', \'\')] = tr.index();\n});\nvar set_message = function(message, type) {\n$(\'#messagepane\').html(\'<span id=\"message\" class=\"\' + type + \'\">\' + message + \' <a href=\"#close\" class=\"close\">&times;</a></span>\');\n$(\'#message\').fadeOut(\'fast\').fadeIn(\'fast\');\n}\n$.ajax(\n\'index.php?event=plugin_prefs.esq_sectionsort&step=put&type=section\',\n{\ntype: \'POST\',\ndata: sectionsort,\ndataType: \'json\',\nsuccess: function(response) {\nif (response.success) {\nset_message(\'Section order saved OK\', \'success\')\n} else {\nthis.error();\n}\n},\nerror: function() {\nset_message(\'Section order save failed\', \'error\');\n}\n}\n);\n}\n}).find(\'tr\').not(\':first-child\').find(\'td:first-child\').html(\'&uarr;&darr;\');\n});\nEOB;\n}\nbreak;\ncase \'category\':\nif ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\nheader(\'Content-Type: application/json\');\n$success = true;\nforeach ($_POST as $category_id => $sort) {\nif (!safe_update(\'txp_category\', \'categorysort=\\\'\'.doSlash($sort).\'\\\'\', \'id=\\\'\'.doSlash($category_id).\'\\\'\')) {\n$success = false;\n}\n}\necho json_encode(array(\'success\' => $success));\n} else {\n$categorysort = array();\nforeach(safe_rows(\'id, categorysort\', \'txp_category\', \'1=1\') as $row) {\n$category_id = $row[\'id\'];\n$sort = $row[\'categorysort\'];\nif (!strlen($sort)) {\n$sort = 0;\n}\n$categorysort[$category_id] = ctype_digit($sort) ? (int)$sort : $sort;\n}\necho \'var categorysort = \', json_encode($categorysort), \';\'.\"\\n\";\necho <<<EOB\n$(function() {\nvar sort_groups = [];\n$(\'#category_container .category-tree\').each(function() {\n$(this).find(\'p\').wrapAll(\'<div class=\"categorysort\"/>\').each(function() {\nvar current = $(this);\nvar next = current.next(\'p\');\nvar group = current;\nvar level = Number(current.attr(\'class\').replace(\'level-\', \'\'));\nvar category_id = Number(current.find(\'input\').val());\ncurrent.data({\ncategory_id: category_id,\ncategorysort: categorysort[category_id]\n});\ncurrent.prepend(\'<span class=\"handle\">&uarr;&darr;</span>\');\nif (current.data(\'grouped\')) {\nreturn;\n}\nwhile (next.length) {\nvar next_level = Number(next.attr(\'class\').replace(\'level-\', \'\'));\nif (next_level <= level) {\nbreak;\n}\ngroup = group.add(next);\nif (next_level == level) {\nnext.data(\'grouped\', true);\n}\nnext = next.next(\'p\');\n}\nsort_groups.push(group);\n});\n});\n$.each(sort_groups, function() {\nvar group = $(this);\nvar group_top = group.eq(0);\ngroup.wrapAll(\'<div class=\"categorysort\" data-category_id=\"\' + group_top.data(\'category_id\') + \'\" data-categorysort=\"\' + group_top.data(\'categorysort\') + \'\" />\');\n});\n$(\'.categorysort > .categorysort\').parent().each(function() {\n$(this).sortable({\nitems: \'> .categorysort\',\nhandle: \'.handle\',\nstop: function() {\nvar categorysort = {};\nvar group = $(this).find(\'> .categorysort\');\ngroup.each(function() {\nvar el = $(this);\ncategorysort[el.data(\'category_id\')] = el.closest(\'.category-tree\').find(\'.categorysort\').index(el);\n});\nvar set_message = function(message, type) {\n$(\'#messagepane\').html(\'<span id=\"message\" class=\"\' + type + \'\">\' + message + \' <a href=\"#close\" class=\"close\">&times;</a></span>\');\n$(\'#message\').fadeOut(\'fast\').fadeIn(\'fast\');\n}\n$.ajax(\n\'index.php?event=plugin_prefs.esq_sectionsort&step=put&type=category\',\n{\ntype: \'POST\',\ndata: categorysort,\ndataType: \'json\',\nsuccess: function(response) {\nif (response.success) {\nset_message(\'Section order saved OK\', \'success\')\n} else {\nthis.error();\n}\n},\nerror: function() {\nset_message(\'Section order save failed\', \'error\');\n}\n}\n);\n}\n}).find(\'> .categorysort\').sortElements(function(a, b) {\nvar a_sort = $(a).data(\'categorysort\');\nvar b_sort = $(b).data(\'categorysort\');\nif (a_sort == b_sort) {\nreturn 0;\n}\nreturn a_sort > b_sort ? 1 : -1;\n});\n});\n});\nEOB;\n}\nbreak;\ncase \'sort_elements\':\necho <<<EOB\n/**\n* jQuery.fn.sortElements\n* --------------\n* @author James Padolsey (http://james.padolsey.com)\n* @version 0.11\n* @updated 18-MAR-2010\n* --------------\n* @param Function comparator:\n*   Exactly the same behaviour as [1,2,3].sort(comparator)\n*   \n* @param Function getSortable\n*   A function that should return the element that is\n*   to be sorted. The comparator will run on the\n*   current collection, but you may want the actual\n*   resulting sort to occur on a parent or another\n*   associated element.\n*   \n*   E.g. $(\'td\').sortElements(comparator, function(){\n*      return this.parentNode; \n*   })\n*   \n*   The <td>\'s parent (<tr>) will be sorted instead\n*   of the <td> itself.\n*/\njQuery.fn.sortElements = (function(){\nvar sort = [].sort;\nreturn function(comparator, getSortable) {\ngetSortable = getSortable || function(){return this;};\nvar placements = this.map(function(){\nvar sortElement = getSortable.call(this),\nparentNode = sortElement.parentNode,\n// Since the element itself will change position, we have\n// to have some way of storing it\'s original position in\n// the DOM. The easiest way is to have a \'flag\' node:\nnextSibling = parentNode.insertBefore(\ndocument.createTextNode(\'\'),\nsortElement.nextSibling\n);\nreturn function() {\nif (parentNode === this) {\nthrow new Error(\n\"You can\'t sort elements if any one is a descendant of another.\"\n);\n}\n// Insert before flag:\nparentNode.insertBefore(this, nextSibling);\n// Remove flag:\nparentNode.removeChild(nextSibling);\n};\n});\nreturn sort.call(this, comparator).each(function(i){\nplacements[i].call(getSortable.call(this));\n});\n};\n})();\nEOB;\nbreak;\ncase \'sortable\':\necho <<<EOB\n/*! jQuery UI - v1.10.3 - 2013-09-24\n* http://jqueryui.com\n* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js\n* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */\n(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return\"area\"===r?(s=t.parentNode,a=s.name,t.href&&a&&\"map\"===s.nodeName.toLowerCase()?(o=e(\"img[usemap=#\"+a+\"]\")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:\"a\"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return\"hidden\"===e.css(this,\"visibility\")}).length}var s=0,a=/^ui-id-\\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:\"1.10.3\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return\"number\"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css(\"zIndex\",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css(\"position\"),(\"absolute\"===n||\"relative\"===n||\"fixed\"===n)&&(s=parseInt(a.css(\"zIndex\"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr(\"id\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e(\"<a>\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,\"padding\"+this))||0,n&&(i-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(i-=parseFloat(e.css(t,\"margin\"+this))||0)}),i}var a=\"Width\"===n?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+n]=function(i){return i===t?r[\"inner\"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+\"px\")})},e.fn[\"outer\"+n]=function(t,i){return\"number\"!=typeof t?r[\"outer\"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+\"px\")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e(\"<a>\").data(\"a-b\",\"a\").removeData(\"a-b\").data(\"a-b\")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart=\"onselectstart\"in document.createElement(\"div\"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if(\"hidden\"===e(t).css(\"overflow\"))return!1;var n=i&&\"left\"===i?\"scrollLeft\":\"scrollTop\",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler(\"remove\")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(\".\")[0];i=i.split(\".\")[1],o=c+\"-\"+i,n||(n=s,s=t.Widget),t.expr[\":\"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+\".\"+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r=\"string\"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&\"_\"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error(\"no such method \'\"+a+\"\' for \"+i+\" widget instance\"):t.error(\"cannot call methods on \"+i+\" prior to initialization; \"+\"attempted to call method \'\"+a+\"\'\")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:\"widget\",widgetEventPrefix:\"\",defaultElement:\"<div>\",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if(\"string\"==typeof i)if(r={},n=i.split(\".\"),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),s===e)return o[i]===e?null:o[i];o[i]=s}else{if(s===e)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,\"disabled\"===t&&(this.widget().toggleClass(this.widgetFullName+\"-disabled ui-state-disabled\",!!e).attr(\"aria-disabled\",e),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")),this},enable:function(){return this._setOption(\"disabled\",!1)},disable:function(){return this._setOption(\"disabled\",!0)},_on:function(i,s,n){var o,a=this;\"boolean\"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass(\"ui-state-disabled\")?(\"string\"==typeof r?a[r]:r).apply(a,arguments):e}\"string\"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\\w+)\\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||\"\").split(\" \").join(this.eventNamespace+\" \")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return(\"string\"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass(\"ui-state-hover\")},mouseleave:function(e){t(e.currentTarget).removeClass(\"ui-state-hover\")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass(\"ui-state-focus\")},focusout:function(e){t(e.currentTarget).removeClass(\"ui-state-focus\")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:\"fadeIn\",hide:\"fadeOut\"},function(e,i){t.Widget.prototype[\"_\"+e]=function(s,n,o){\"string\"==typeof n&&(n={effect:n});var a,r=n?n===!0||\"number\"==typeof n?i:n.effect||i:e;n=n||{},\"number\"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget(\"ui.mouse\",{version:\"1.10.3\",options:{cancel:\"input,textarea,button,select,option\",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind(\"mousedown.\"+this.widgetName,function(t){return e._mouseDown(t)}).bind(\"click.\"+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+\".preventClickEvent\")?(t.removeData(i.target,e.widgetName+\".preventClickEvent\"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind(\".\"+this.widgetName),this._mouseMoveDelegate&&t(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a=\"string\"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+\".preventClickEvent\")&&t.removeData(i.target,this.widgetName+\".preventClickEvent\"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).bind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css(\"float\"))||/inline|table-cell/.test(t.css(\"display\"))}t.widget(\"ui.sortable\",t.ui.mouse,{version:\"1.10.3\",widgetEventPrefix:\"sort\",ready:!1,options:{appendTo:\"parent\",axis:!1,connectWith:!1,containment:!1,cursor:\"auto\",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:\"original\",items:\"> *\",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:\"default\",tolerance:\"intersect\",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass(\"ui-sortable\"),this.refresh(),this.floating=this.items.length?\"x\"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass(\"ui-sortable ui-sortable-disabled\"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+\"-item\");return this},_setOption:function(e,i){\"disabled\"===e?(this.options[e]=i,this.widget().toggleClass(\"ui-sortable-disabled\",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||\"static\"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+\"-item\")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+\"-item\")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find(\"*\").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css(\"position\",\"absolute\"),this.cssPosition=this.helper.css(\"position\"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&\"auto\"!==a.cursor&&(o=this.document.find(\"body\"),this.storedCursor=o.css(\"cursor\"),o.css(\"cursor\",a.cursor),this.storedStylesheet=t(\"<style>*{ cursor: \"+a.cursor+\" !important; }</style>\").appendTo(o)),a.opacity&&(this.helper.css(\"opacity\")&&(this._storedOpacity=this.helper.css(\"opacity\")),this.helper.css(\"opacity\",a.opacity)),a.zIndex&&(this.helper.css(\"zIndex\")&&(this._storedZIndex=this.helper.css(\"zIndex\")),this.helper.css(\"zIndex\",a.zIndex)),this.scrollParent[0]!==document&&\"HTML\"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger(\"start\",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger(\"activate\",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass(\"ui-sortable-helper\"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo(\"absolute\"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&\"HTML\"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-t(document).scrollTop()<a.scrollSensitivity?r=t(document).scrollTop(t(document).scrollTop()-a.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<a.scrollSensitivity&&(r=t(document).scrollTop(t(document).scrollTop()+a.scrollSpeed)),e.pageX-t(document).scrollLeft()<a.scrollSensitivity?r=t(document).scrollLeft(t(document).scrollLeft()-a.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<a.scrollSensitivity&&(r=t(document).scrollLeft(t(document).scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo(\"absolute\"),this.options.axis&&\"y\"===this.options.axis||(this.helper[0].style.left=this.position.left+\"px\"),this.options.axis&&\"x\"===this.options.axis||(this.helper[0].style.top=this.position.top+\"px\"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?\"next\":\"prev\"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&(\"semi-dynamic\"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?\"down\":\"up\",\"pointer\"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(e,s),this._trigger(\"change\",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger(\"sort\",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&\"x\"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&\"y\"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),\"original\"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass(\"ui-sortable-helper\"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger(\"deactivate\",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger(\"out\",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),\"original\"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||\"id\")||\"\").match(e.expression||/(.+)[\\-=_](.+)/);i&&s.push((e.key||i[1]+\"[]\")+\"=\"+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+\"=\"),s.join(\"&\")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||\"id\")||\"\")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u=\"x\"===this.options.axis||s+l>r&&h>s+l,d=\"y\"===this.options.axis||e+c>o&&a>e+c,p=u&&d;return\"pointer\"===this.options.tolerance||this.options.forcePointerForContainers||\"pointer\"!==this.options.tolerance&&this.helperProportions[this.floating?\"width\":\"height\"]>t[this.floating?\"width\":\"height\"]?p:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i=\"x\"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s=\"y\"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&\"right\"===a||\"down\"===o?2:1:o&&(\"down\"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?\"right\"===o&&s||\"left\"===o&&!s:n&&(\"down\"===n&&i||\"up\"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?\"down\":\"up\")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?\"right\":\"left\")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){var i,s,n,o,a=[],r=[],h=this._connectWith();if(h&&e)for(i=h.length-1;i>=0;i--)for(n=t(h[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&r.push([t.isFunction(o.options.items)?o.options.items.call(o.element):t(o.options.items,o.element).not(\".ui-sortable-helper\").not(\".ui-sortable-placeholder\"),o]);for(r.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(\".ui-sortable-helper\").not(\".ui-sortable-placeholder\"),this]),i=r.length-1;i>=0;i--)r[i][0].each(function(){a.push(this)});return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(\":data(\"+this.widgetName+\"-item)\");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+\"-item\",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t(\"<\"+s+\">\",e.document[0]).addClass(i||e.currentItem[0].className+\" ui-sortable-placeholder\").removeClass(\"ui-sortable-helper\");return\"tr\"===s?e.currentItem.children().each(function(){t(\"<td>&#160;</td>\",e.document[0]).attr(\"colspan\",t(this).attr(\"colspan\")||1).appendTo(n)}):\"img\"===s&&n.attr(\"src\",e.currentItem.attr(\"src\")),i||n.css(\"visibility\",\"hidden\"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css(\"paddingTop\")||0,10)-parseInt(e.currentItem.css(\"paddingBottom\")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css(\"paddingLeft\")||0,10)-parseInt(e.currentItem.css(\"paddingRight\")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger(\"out\",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger(\"over\",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?\"left\":\"top\",l=p?\"width\":\"height\",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?\"up\":\"down\"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger(\"change\",s,this._uiHash()),this.containers[g]._trigger(\"change\",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger(\"over\",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):\"clone\"===i.helper?this.currentItem.clone():this.currentItem;return s.parents(\"body\").length||t(\"parent\"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css(\"position\"),top:this.currentItem.css(\"top\"),left:this.currentItem.css(\"left\")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){\"string\"==typeof e&&(e=e.split(\" \")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),\"left\"in e&&(this.offset.click.left=e.left+this.margins.left),\"right\"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),\"top\"in e&&(this.offset.click.top=e.top+this.margins.top),\"bottom\"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return\"absolute\"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&\"html\"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css(\"borderTopWidth\"),10)||0),left:e.left+(parseInt(this.offsetParent.css(\"borderLeftWidth\"),10)||0)}},_getRelativeOffset:function(){if(\"relative\"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css(\"top\"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css(\"left\"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css(\"marginLeft\"),10)||0,top:parseInt(this.currentItem.css(\"marginTop\"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;\"parent\"===n.containment&&(n.containment=this.helper[0].parentNode),(\"document\"===n.containment||\"window\"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t(\"document\"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t(\"document\"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s=\"hidden\"!==t(e).css(\"overflow\"),this.containment=[i.left+(parseInt(t(e).css(\"borderLeftWidth\"),10)||0)+(parseInt(t(e).css(\"paddingLeft\"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css(\"borderTopWidth\"),10)||0)+(parseInt(t(e).css(\"paddingTop\"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css(\"borderLeftWidth\"),10)||0)-(parseInt(t(e).css(\"paddingRight\"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css(\"borderTopWidth\"),10)||0)-(parseInt(t(e).css(\"paddingBottom\"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s=\"absolute\"===e?1:-1,n=\"absolute\"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-(\"fixed\"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-(\"fixed\"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r=\"absolute\"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return\"relative\"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(\"fixed\"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(\"fixed\"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],\"down\"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)(\"auto\"===this._storedCSS[i]||\"static\"===this._storedCSS[i])&&(this._storedCSS[i]=\"\");this.currentItem.css(this._storedCSS).removeClass(\"ui-sortable-helper\")}else this.currentItem.show();for(this.fromOutside&&!e&&s.push(function(t){this._trigger(\"receive\",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(\".ui-sortable-helper\")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||s.push(function(t){this._trigger(\"update\",t,this._uiHash())}),this!==this.currentContainer&&(e||(s.push(function(t){this._trigger(\"remove\",t,this._uiHash())}),s.push(function(t){return function(e){t._trigger(\"receive\",e,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(t){return function(e){t._trigger(\"update\",e,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)e||s.push(function(t){return function(e){t._trigger(\"deactivate\",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(function(t){return function(e){t._trigger(\"out\",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find(\"body\").css(\"cursor\",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css(\"opacity\",this._storedOpacity),this._storedZIndex&&this.helper.css(\"zIndex\",\"auto\"===this._storedZIndex?\"\":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger(\"beforeStop\",t,this._uiHash()),i=0;s.length>i;i++)s[i].call(this,t);this._trigger(\"stop\",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger(\"beforeStop\",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(i=0;s.length>i;i++)s[i].call(this,t);this._trigger(\"stop\",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})})(jQuery);\nEOB;\nbreak;\n}\nexit();\n}', '9220bbbeff577a113155584646ba7dd9', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('smd_ebook', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.10', 'Create e-books (e.g. Kindle) from Textpattern content', '<div id=\"smd_help\">\n\n	<h1>smd_ebook</h1>\n\n	<p>There are a few ways to create E-books suitable for e-readers like Kindle / Kobo / Nook / etc:</p>\n\n	<ul>\n		<li><a href=\"http://calibre-ebook.com/\">Install Calibre</a> and use the software to guide you towards creating your book</li>\n		<li>Install a plugin for Adobe InDesign and let it help you create the book from your <span class=\"caps\">DTP</span> files</li>\n		<li>Download the <a href=\"http://www.amazon.com/gp/feature.html?ie=UTF8&amp;docId=1000234621\">command-line Kindlegen</a> tool, create all the input files manually and hope</li>\n		<li>Upload the Kindlegen program to your Textpattern web host and use this plugin to convert one or more articles into an E-book</li>\n	</ul>\n\n	<p>The last one is of course the focus of this plugin! Features:</p>\n\n	<ul>\n		<li>Choose articles to be converted &#8212; order of articles in final book is alphabetical by <span class=\"caps\">URL</span> title</li>\n		<li>Standard Textile formatting governs the (multi-level) table of contents and document entry points (plugin will automatically create ToC entry points if you choose)</li>\n		<li>Set cover art as article image</li>\n		<li>Enter Description, Publisher, Genre, Author notes and Price in plugin or use article fields (useful for publisher sites to allow authors to publish their own content)</li>\n		<li>Tweak files if necessary before final E-book generation</li>\n		<li>Download files for distribution via third party sites, or send them to Txp&#8217;s Files tab ready for direct download by others</li>\n	</ul>\n\n	<h2>Installation / uninstallation</h2>\n\n	<p class=\"important\">Requires Textpattern 4.5.0+ and PHP5+</p>\n\n	<p>Download the plugin from either <a href=\"http://textpattern.org/plugins/NNNN/smd_ebook\">textpattern.org</a>, or the <a href=\"http://stefdawson.com/sw\">software page</a>, paste the code into the Txp <em>Admin-&gt;Plugins</em> pane, install and enable the plugin. Visit the <a href=\"http://forum.textpattern.com/viewtopic.php?id=YYYYY\">forum thread</a> for more info or to report on the success or otherwise of the plugin.</p>\n\n	<p>To remove the plugin, simply delete it from the <em>Admin-&gt;Plugins</em> pane.</p>\n\n	<h2>Setting up</h2>\n\n	<ol>\n		<li>Obtain the <a href=\"http://www.amazon.com/gp/feature.html?ie=UTF8&amp;docId=1000234621\">kindlegen program</a> that is compatible with your web host &#8212; most likely the Linux version. While you&#8217;re there you might as well grab the (huge!) Kindle Previewer too as it&#8217;s very handy to test files made with this plugin.</li>\n		<li>Upload kindlegen via your <span class=\"caps\">FTP</span> program <strong>as binary</strong> to a location of your choosing on your web host; preferably outside document root so it can&#8217;t be run by other people. Double check it is uploaded as binary &#8212; some <span class=\"caps\">FTP</span> software (e.g. FileZilla) is set to auto-negotiate the file type and often gets it wrong. If the plugin doesn&#8217;t work, this is the most likely source of failure.</li>\n		<li>Visit the <em>Content-&gt;E-books</em> tab with a Publisher level account and hit the <em>Settings</em> button. Configure the <em>Path to kindlegen executable</em> to reflect the location of your uploaded kindlegen file. Set up any other relevant settings while you are here and save them.</li>\n		<li><strong>After saving the settings</strong> you can click the <em>Test kindlegen program</em> link to check that the program is uploaded correctly and the plugin can find it. If everything&#8217;s OK, you will be told so in a text box that appears below the link. If the kindlegen file produces errors or cannot be found, the error messages will be shown instead.</li>\n	</ol>\n\n	<h2>Writing content suitable for E-readers</h2>\n\n	<p>While the technology and tools are improving, there are some guidelines and things to be aware of when creating content in Textpattern that will translate well into a good e-reader experience:</p>\n\n	<ul>\n		<li>Use headings to create chapters or logical breaks in your prose. You can create many articles if you wish &#8212; perhaps one article per chapter &#8212; and create a single file from them, or create it all in one article.</li>\n		<li>Supply cover art. This must be a <span class=\"caps\">GIF</span> or <span class=\"caps\">JPG</span> image of dimensions 500 (w) x 600 (h) pixels. Assign the ID of the cover image uploaded to Textpattern in the <em>Article Image</em> field of the first chapter.</li>\n		<li>Create a stylesheet to lay out your table of contents or alter facets of your book. Formatting is often hit and miss because the e-readers use their own internal styles, but some things can be influenced with a stylesheet. Tinker with it to see what effects you can create.</li>\n		<li><a href=\"#smd_ebook_images\">Inline images</a> cannot flow around text &#8212; they always appear block style.</li>\n		<li>Add author notes to a field in your first article &#8212; such notes appear after the cover image and before the ToC. Copyright info and acknowledgements are useful here. See the setting <em>Get author notes from field</em>.</li>\n	</ul>\n\n	<h3>Formatting for Table of Contents (ToC)</h3>\n\n	<p>The concept of a ToC maps nicely in Textile / <span class=\"caps\">HTML</span> to the <code>&lt;h1&gt;</code> &#8211; <code>&lt;h6&gt;</code> tags, although you do not have to stick to that convention. Any anchor with an <span class=\"caps\">HTML</span> ID will be converted into a ToC entry point by the plugin.</p>\n\n	<p>There are two primary methods for creating a table of contents:</p>\n\n	<ol>\n		<li>Use <code>h1.</code> to <code>h6.</code> Textile tags and set the plugin to automatically create ToC entries from headings</li>\n		<li>Manually add <code>id</code> atributes to some/all headings (in Textile: <code>h2(#some-id). My Great Heading</code>) or other anchors</li>\n	</ol>\n\n	<p>Any anchors you miss off will be automatically assigned by the plugin if you have elected to permit this behaviour.</p>\n\n	<p>Use hyphens in the ID to create nested menu structures: each hyphen creates one &#8216;level&#8217;, e.g.:</p>\n\n<pre class=\"block\"><code class=\"block\">h2(#l1). Level 1 heading\nh2(#some_heading). Another level 1 heading\nh2(#l1-subbie). A sub-heading beneath the previous heading\nh2(#some-name). Another subheading beneath the previous heading\n</code></pre>\n\n	<p>Note that the names don&#8217;t have to have the same prefix or follow any pattern (though it makes sense to do so for sanity&#8217;s sake!), nor does the heading level bear any influence. The number of hyphens overrides the heading level to govern the nesting levels. If you don&#8217;t like this feature, use underscores to separate words in your IDs, or leave the plugin to create ToC entry points for you.</p>\n\n	<h3 id=\"smd_ebook_pbr\">Page breaks</h3>\n\n	<p>Page breaks normally occur in E-books before chapter headings. To insert page breaks into the document you have three options:</p>\n\n	<ol>\n		<li>Add the special tag <code>~~~~~</code> before each chapter heading (you will most likely need a blank line above and below it: the plugin will eat the extra paragraph marks that Textile inserts).</li>\n		<li>Modify the heading style to include a class name in your nominated kindle stylesheet that has the rule: <code>page-break-before: always</code>.</li>\n		<li>Add <code>style=&quot;page-break-before: always&quot;</code> directly inline in your heading tags, or wherever you want a page break to occur.</li>\n	</ol>\n\n	<h3 id=\"smd_ebook_images\">Inline images</h3>\n\n	<p>It may seem tempting to use Textpattern&#8217;s image tags in articles to insert inline images. While this works and looks good in the plugin&#8217;s Preview window, the images will not be rendered in the final downloadable book. If you study the Report closely you&#8217;ll see that kindlegen struggles with such images, stating it cannot find them.</p>\n\n	<p>The reason for this is that kindlegen expects images and embedded content to be presented as <em>files</em> not <em><span class=\"caps\">URL</span>s</em>. Textpattern&#8217;s image tags (and those in image plugins) all output <span class=\"caps\">URL</span>s of the form <code>http://site.com/images/NN.ext</code> by default. In order to use images in the final e-book you need to specify images as <code>/path/to/site/images/NN.ext</code>. You can do this fairly easily with the <code>&lt;txp:images&gt;</code> tag and <code>&lt;txp:image_info /&gt;</code>, like this:</p>\n\n<pre class=\"block\"><code class=\"block\">&lt;txp:images name=&quot;my-pic.jpg&quot;&gt;\n   &lt;img\n     src=&quot;/path/to/site/images/&lt;txp:image_info type=&quot;id, ext&quot; /&gt;&quot;\n     alt=&quot;&lt;txp:image_info type=&quot;alt&quot; /&gt;&quot;\n     title=&quot;&lt;txp:image_info /&gt;&quot; /&gt;\n&lt;/txp:images&gt;\n</code></pre>\n\n	<p>For convenience you could set this content up as an smd_macro.</p>\n\n	<p>The plugin&#8217;s [Preview] window will detect the fact your images are using paths and attempt to swap them for <span class=\"caps\">URL</span>s on the fly so you can see them in the preview. It may not work on Windows hosts or if you have your images in a separate domain.</p>\n\n	<h2>Plugin settings</h2>\n\n	<h3>Configuration</h3>\n\n	<dl>\n		<dt><span class=\"atnm\">Unique ID</span>:</dt>\n		<dd>An optional unique reference ID for all projects created from the plugin. Useful for publishers to &#8216;tag&#8217; all their own books with a code.</dd>\n		<dt><span class=\"atnm\">Page break character sequence</span></dt>\n		<dd>Use this string in your article text to denote where a page break should occur. You may <a href=\"#smd_ebook_pbr\">use <span class=\"caps\">CSS</span></a> if you prefer.</dd>\n		<dd>Default: <code>~~~~~</code> (five tilde characters)</dd>\n		<dt><span class=\"atnm\">Render ToC as</span></dt>\n		<dd>Choose between creating the Table of Contents markup as:\n	<dl>\n		<dd><strong>Standard</strong> (<code>&lt;ul&gt;</code>)</dd>\n		<dd><strong>Numeric</strong> (<code>&lt;ol&gt;</code>)</dd>\n	</dl></dd>\n		<dd>Default: Standard</dd>\n		<dt><span class=\"atnm\">ToC <span class=\"caps\">CSS</span> class name</span></dt>\n		<dd>The <span class=\"caps\">CSS</span> class to apply to the table of contents.</dd>\n		<dd>Default: <code>smd_ebook_toc</code></dd>\n		<dt><span class=\"atnm\">List articles from section</span></dt>\n		<dd>Limit the articles in the select list to the ones in the nominated section. Otherwise, all sections are considered.</dd>\n		<dt><span class=\"atnm\">Stylesheet to include with the book</span></dt>\n		<dd>Choose one Textpattern stylesheet that will be inserted into each article in the book to govern formatting.</dd>\n		<dt><span class=\"atnm\">Apply Textile to</span></dt>\n		<dd>Choose whether to pass the checked content through Textile. If the field you are choosing to represent the content has already been Textiled, it&#8217;s probably not a good idea to do it a second time.</dd>\n		<dt><span class=\"atnm\">Character set of document</span></dt>\n		<dd>The character set to use in the final document. Useful values are usually <code>utf-8</code> or <code>iso-8859-1</code>.</dd>\n		<dd>Default: <code>utf-8</code></dd>\n		<dt><span class=\"atnm\">Path to kindlegen executable</span></dt>\n		<dd>The full system file path to the kindlegen program that you uploaded to your web host (<strong>as a binary file</strong>!)</dd>\n		<dd>It is preferable to make this a non-web-accessible location.</dd>\n	</dl>\n\n	<h3>Publishing</h3>\n\n	<dl>\n		<dt><span class=\"atnm\">Automatically create ToC anchors on headings</span></dt>\n		<dd>Set to <code>Yes</code> to automatically create anchors from all heading tags in your article(s). If you have already put some in the dcument, the plugin will only auto-generate ones you have missed.</dd>\n		<dd>Default: Yes</dd>\n		<dt><span class=\"atnm\">Get book title from field</span></dt>\n		<dd>Nominate an article field to hold the book&#8217;s title. Leave this item empty to force the title to be entered at book compilation time, or choose <em>Static text</em> and enter a title that will be applied to all created books.</dd>\n		<dd>Default: article&#8217;s <code>Title</code> field</dd>\n		<dt><span class=\"atnm\">Get chapter titles from field</span></dt>\n		<dd>Nominate an article field to hold the chapter titles. Leave this item empty to set chapters manually in the body text, or choose <em>Static text</em> and enter a title that will be applied to every chapter.</dd>\n		<dd>Any chosen item will be wrapped with <span class=\"caps\">HTML</span> heading tags of the level given in the <em>Chapter heading level</em> setting.</dd>\n		<dt><span class=\"atnm\">Get author from field</span></dt>\n		<dd>Nominate an article field to hold the author of the work. Leave this item empty to allow the author to be entered at book compilation time, or choose <em>Static text</em> and enter an author that will be applied to all created books.</dd>\n		<dd>The <code>Author</code> entry in the list will read the Author from the first article in the book.</dd>\n		<dd>Default: <code>Author</code></dd>\n		<dt><span class=\"atnm\">Get description from field</span></dt>\n		<dd>Nominate an article field to hold the book&#8217;s description. Leave this item empty to allow the description to be entered at book compilation time, or choose <em>Static text</em> and enter a description that will be applied to all created books.</dd>\n		<dd>This field will be Textiled if you have checked it in the <em>Apply Textile to</em> setting.</dd>\n		<dt><span class=\"atnm\">Get author notes from field</span></dt>\n		<dd>Nominate an article field to hold the author notes (acknowledgements, copyright, etc). Leave this item empty to allow the notes to be entered at book compilation time, or choose <em>Static text</em> and enter content that will be applied to all created books.</dd>\n		<dd>This field will be Textiled if you have checked it in the <em>Apply Textile to</em> setting.</dd>\n		<dt><span class=\"atnm\">Get subject (genre) from field</span></dt>\n		<dd>Nominate an article field to determine the subject or genre of the article. Leave this item empty to allow the genre to be entered at book compilation time, or choose <em>Static text</em> and enter content that will be applied to all created books.</dd>\n		<dt><span class=\"atnm\">Get publisher from field</span></dt>\n		<dd>Nominate an article field to set the publisher of the article. Leave this item empty to allow the info to be entered at book compilation time, or choose <em>Static text</em> and enter a publisher name that will be applied to all created books.</dd>\n		<dt><span class=\"atnm\">Get <span class=\"caps\">SRP</span> (price) from field</span></dt>\n		<dd>Nominate an article field to set the download price of the article. Leave this item empty to allow the info to be entered at book compilation time, or choose <em>Static text</em> ten enter a price and optional three-letter currency code (e.g. <span class=\"caps\">USD</span>, <span class=\"caps\">GBP</span>, <span class=\"caps\">EUR</span>), separated by a pipe symbol, that will be applied to all created books. If the currency is not supplied it will be taken from the setting <em>Default three-letter currency code</em>.</dd>\n		<dt><span class=\"atnm\">Default three-letter currency code</span></dt>\n		<dd>The three-letter <a href=\"http://www.xe.com/iso4217.php\">currency code</a> of the default currency to use for the book&#8217;s price.</dd>\n		<dd>Default: <code>EUR</code></dd>\n		<dt><span class=\"atnm\">Chapter heading level</span></dt>\n		<dd>If your chapter headings are being read from an article field, they will automatically be wrapped with <span class=\"caps\">HTML</span> <code>&lt;hN&gt;...&lt;/hN&gt;</code> tags, where <code>N</code> is the value in this setting.</dd>\n		<dd>Default: 2</dd>\n		<dt><span class=\"atnm\">Store files in category</span></dt>\n		<dd>If you elect to store your completed E-books in Textpattern&#8217;s Files tab, this is the category to which they&#8217;ll be assigned.</dd>\n	</dl>\n\n	<h3>Rights</h3>\n\n	<dl>\n		<dt><span class=\"atnm\">Groups that can publish</span></dt>\n		<dd>Select the user groups that are permitted to publish e-books. Users in these groups will see a <em>Content-&gt;E-books</em> tab but will not be permitted to alter the Settings.</dd>\n		<dt><span class=\"atnm\">Groups that can edit .opf</span></dt>\n		<dd>The .opf is the master file that governs e-book creation. Publisher account holders can always edit this file but if you have preset some of the content using the <em>Static text</em> publishing options, being able to alter the .opf would allow someone to change the presets. For this reason you can use this setting to govern which user groups you trust to edit the .opf and potentially override the settings.</dd>\n	</dl>\n\n	<h2>Creating a book</h2>\n\n	<p>The creation process takes place in two stages, although the plugin will have a stab at creating everything in one step if it can.</p>\n\n	<p>To kick things off, visit the <em>Content-&gt;E-books</em> tab. Choose one or more articles from the first select list to create into a book. When choosing multiple files, the order they will appear in the book is the same order as they are in this list. It is goverened by <span class=\"caps\">URL</span> title so if you want things to appear in a different order, alter the url-only title of your articles, e.g.:</p>\n\n	<ul>\n		<li>chapter01-the-bell-tolls</li>\n		<li>chapter02-trousers-on-fire</li>\n		<li>chapter03-false-alarm<br />\n&#8230;</li>\n	</ul>\n\n	<p>You may optionally fill in any of the remaining content fields that are presented. Only Title is mandatory. Some may be prefilled by content from the indicated fields in your selected article(s). Note that such information is <strong>only taken from the first article in which that field has data</strong>. Thus when compiling multiple articles into a single book, it&#8217;s a good idea to put all such meta data &#8212; including article image (which is used for the cover artwork) &#8212; in the first chapter. Once a piece of info is set, it is not altered in subsequent files that make up the same book, even if data is present in the nominated fields.</p>\n\n	<p>A note about the Price field: you specify up to two pieces of info in this field, separated by a pipe symbol (<code>|</code>). First the price itself and then the three-letter currency code (e.g. <span class=\"caps\">GBP</span>, <span class=\"caps\">EUR</span>, <span class=\"caps\">USD</span>, <span class=\"caps\">AUD</span>, etc). If the currency code is not supplied, the setting <em>Default three-letter currency code</em> will be used.</p>\n\n	<p>After clicking <em>Create</em>, the plugin will collate all the selected articles and meta data and try to produce a complete e-book (.mobi file). The success or otherwise of the process is shown in the <em>Build report</em> box. Scroll through this info to find any errors. You may need to go back to your source documents to fix them. Alternatively you may be able to fix the problems by manually editing the various files that make up the project.</p>\n\n	<p>All files are listed on the left hand side of the screen. Although each project is different, the various components that may be present are:</p>\n\n	<ul>\n		<li>One .html file for each article you selected.</li>\n		<li>An <span class=\"caps\">HTML</span> Table of Contents with the filename of the first article in the project plus <code>_toc.html</code>.</li>\n		<li>An author notes page (filename of first article plus <code>_notes.html</code>).</li>\n		<li>A .ncx file which is a special (<span class=\"caps\">XML</span>) waypoint and navigation aid that allows e-readers to show chapter points in a timeline and permit jumping to various parts of the document (cover, author notes, chapters, etc) from the context menu key on the reader.</li>\n		<li>A .opf file (which may not be displayed depending on the rights assigned to your user account) which is the master (<span class=\"caps\">XML</span>) record that ties all the other files together and is fed to the kindlegen program to create the final .mobi file.</li>\n	</ul>\n\n	<p>You can click any file name to open it for editing in the adjacent box, make changes to the markup and <em>Save</em> the alterations. Do this as often as you like and, when completed, hit the <em class=\"Re\">generate</em> button to tell the plugin to try to create the .mobi file again. If you wish to preview any of the <span class=\"caps\">HTML</span> files (for example, to check your stylesheet is applying appropriate rules) click the <code>[Preview]</code> link after the file&#8217;s title. Click the &#8216;X&#8217; in the top-right corner of the preview window, or hit the <span class=\"caps\">ESC</span>ape key to dismiss the preview.</p>\n\n	<p>If you wish to create an article with a specific language string you can do one of two things:</p>\n\n	<ol>\n		<li>Manually alter the .ncx file&#8217;s <code>xml:lang</code> attribute and the .opf file&#8217;s <code>&lt;dc:language&gt;</code> markup.</li>\n		<li>Change Textpattern&#8217;s admin side language from the <em>Admin-&gt;Preferences</em> tab, then regenerate your book.</li>\n	</ol>\n\n	<p>Upon successful completion of the process you can choose whether to:</p>\n\n	<ul>\n		<li>Click the <em>Store file</em> button to copy the complete E-book file to Textpattern&#8217;s Files tab. If the file does not exist, it will be created. If it exists, it will be updated with the new details (title and description as you entered them in the input boxes when the book was created, and the category as set in the plugin settings).</li>\n		<li>Click the <em>Download</em> button to download a copy of the complete E-book to your computer, whereby it&#8217;s a good idea to test it in Kindle Previewer or transfer it to your real e-reader and check the navigation and formatting are to your satisfaction.</li>\n	</ul>\n\n	<h2>Tidying up</h2>\n\n	<p>The plugin uses Textpattern&#8217;s <code>tmp</code> directory to store its files as it creates them. Since the editorial process may involve editing or tweaking them and so forth, the files are left in situ even after the .mobil file has been created.</p>\n\n	<p>It is up to the site admin to keep things tidy and, to this end, there&#8217;s a helpful extra panel under the e-books tab called &#8216;Tidy up&#8217;. Click that button to be shown a list of possible e-book-ish files in the tmp directory. In addition to the files that are editable after creation of an e-book, one other special file &#8212; a .smd file &#8212; is something the plugin uses to keep track of which files are in each project. Once a .mobil file has been created, this file is also no longer of any use.</p>\n\n	<p>Select the files you want to delete and hit the <em>Delete</em> button. No warning is given: they are deleted immediately.</p>\n\n	<h2>Author and credits</h2>\n\n	<p>Written by <a href=\"http://stefdawson.com/contact\">Stef Dawson</a>. For other software by me, or to make a donation, see the <a href=\"http://stefdawson.com/sw\">software page</a>.</p>\n\n	<p>While the code to glue all the various parts together is mine, the various websites, blogs and forums I had to trawl to gather the info are many. Thank you to anybody who has posted Kindle / ePub / .mobi / e-reader tricks, tips and guides. Without you I could not have completed this plugin because official documentation on the kindlegen program is surprisingly lacking. Thanks also to Amazon techs for writing the kindlegen program.</p>\n\n	<h2>Changelog</h2>\n\n	<ul>\n		<li>19 Mar 2012 | 0.10 | Beta release</li>\n	</ul>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#smd_help { line-height:1.5 ;}\n#smd_help code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #f0e68c; color:#333; }\n#smd_help code.block { font-weight:normal; border:1px dotted #999; display:block; margin:10px 10px 20px; padding:10px; }\n#smd_help h1 { font: 22px Georgia, serif; margin: 0; text-align: center; }\n#smd_help h2 { border-bottom: 1px solid black; padding:10px 0 0; font: 18px Georgia, serif; }\n#smd_help h3 { font: bold 13px Georgia, serif; letter-spacing: 1px; margin: 20px 0 0; text-decoration:underline; }\n#smd_help h4 { font: bold 12px Georgia, serif; letter-spacing: 1px; margin: 10px 0 0; text-transform: uppercase; }\n#smd_help .atnm { font-weight:bold; }\n#smd_help .mand { background:#eee; border:1px dotted #999; }\n#smd_help table { width:90%; text-align:center; padding-bottom:1em; border-collapse:collapse; }\n#smd_help td, #smd_help th { border:1px solid #999; padding:.5em; }\n#smd_help ul { list-style-type:square; }\n#smd_help .important { color:red; }\n#smd_help li { margin:5px 20px 5px 30px; }\n#smd_help .break { margin-top:5px; }\n#smd_help dl dd { margin:2px 15px; }\n#smd_help dl dd:before { content: \"\\2022\\00a0\"; }\n#smd_help dl dd dl { padding: 0 15px; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * smd_ebook\r\n *\r\n * A Textpattern CMS plugin for creating e-books (Kindle) from Txp content\r\n *  -> Content can be in one article or across many\r\n *  -> Article image of one of the articles is used as cover art\r\n *  -> Automatic TOC generation and page breaks from Textiled markup\r\n *  -> Support for book description\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\nglobal $smd_ebook_prefs;\r\nsmd_ebook_get_prefs();\r\n\r\nif(@txpinterface == \'admin\') {\r\n	global $smd_ebook_event, $smd_ebook_styles;\r\n	$smd_ebook_event = \'smd_ebook\';\r\n\r\n	$smd_ebook_styles = array(\r\n		\'cpanel\' =>\r\n         \'.smd_hidden { display:none; }\r\n         .smd_active { font-weight:bold; }\r\n         .smd_clear { clear:both; }\r\n         .smd_preselected { opacity:0.6; font-style:italic; }\r\n         .smd_selected { border-top:solid #444; border-bottom:solid #444; }\r\n         .smd_important { color:red; }\r\n         .smd_inline { display:inline; }\r\n         .txp-container, .txp-control-panel { text-align:center; }\r\n         #smd_ebook_preview { display:none; position:absolute; top:1em; left:1em; margin:0 auto; text-align:left; border:2px ridge #999; background:#ececec; max-width:640px; min-width:300px; box-shadow: 8px 8px 15px #b9b9b9; }\r\n         #smd_ebook_preview_close { float:right; cursor:pointer; margin-left:1em;}\r\n         #smd_ebook_preview_content { padding:1em; }\r\n         #smd_ebook_preview_titlebar { padding:5px; border-bottom:1px solid black; font-size:120%; background:#ccc;;}\r\n         #smd_ebook_form { margin:0 auto; width:80%; }\r\n         #smd_ebook_form label, #smd_ebook_create { display:block; }\r\n         #smd_ebook_prefs input[type=\"text\"] { width:250px }\r\n         .smd_ebook_manager { position:relative; margin:0 auto; width:80% }\r\n         .smd_ebook_buttons { display:inline-block; margin:0 2em; }\r\n         .smd_ebook_report textarea { width:80%; }\r\n         #smd_ebook_editor { display:block; width:75%}\r\n         .smd_ebook_files { float:left; width:25% }\r\n         .smd_ebook_mobi_options { text-align:right; }\r\n         .smd_ebook_entity { float:left; margin:1em; }\r\n         #smd_ebook_form label, .smd_ebook_report, .smd_ebook_manager { margin-top:1.5em; }\r\n         .smd_ebook_file { display:block; line-height:1.5; }\',\r\n	);\r\n\r\n	$pub_prv = get_pref(\'smd_ebook_privs\', $smd_ebook_prefs[\'smd_ebook_privs\'][\'default\']);\r\n	add_privs($smd_ebook_event, \'1\'. (($pub_prv) ? \',\'.$pub_prv: \'\') );\r\n	add_privs(\'plugin_prefs.\'.$smd_ebook_event, \'1\');\r\n\r\n	register_tab(\'content\', $smd_ebook_event, smd_ebook_gTxt(\'smd_ebook_tab_name\'));\r\n	register_callback(\'smd_ebook_dispatcher\', $smd_ebook_event);\r\n	register_callback(\'smd_ebook_dispatcher\', \'plugin_prefs.\'.$smd_ebook_event);\r\n	register_callback(\'smd_ebook_welcome\', \'plugin_lifecycle.\'.$smd_ebook_event);\r\n}\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n// Plugin jump off point\r\nfunction smd_ebook_dispatcher($evt, $stp) {\r\n	global $smd_ebook_event;\r\n\r\n	$available_steps = array(\r\n		\'smd_ebook\'               => false,\r\n		\'smd_ebook_prefs\'         => true,\r\n		\'smd_ebook_create\'        => true,\r\n		\'smd_ebook_generate\'      => true,\r\n		\'smd_ebook_loadfile\'      => true,\r\n		\'smd_ebook_savefile\'      => true,\r\n		\'smd_ebook_viewfile\'      => true,\r\n		\'smd_ebook_test\'          => true,\r\n		\'smd_ebook_tidy\'          => true,\r\n		\'save_pane_state\'         => true,\r\n	);\r\n\r\n	if ($stp == \'save_pane_state\') {\r\n		smd_ebook_save_pane_state();\r\n	} else if (!$stp or !bouncer($stp, $available_steps)) {\r\n		$stp = $smd_ebook_event;\r\n	}\r\n	$stp();\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			$msg = \'Go publish!\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_ebook_prefs_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\n// Stub with correct signature for being called via Txp\r\nfunction smd_ebook($evt=\'\', $stp=\'\') {\r\n	smd_ebook_ui();\r\n}\r\n\r\n// ------------------------\r\n// Interface for compiling the book\r\nfunction smd_ebook_ui($msg=\'\', $listfile=\'\', $report = \'\', $retval=\'\') {\r\n	global $smd_ebook_event, $smd_ebook_prefs, $smd_ebook_styles, $prefs;\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'mgr\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	// Inject styles\r\n	echo n.\'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n	echo n.\'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	if (!$listfile) {\r\n		// Stage 1: Gather the info and create the content\r\n\r\n		// Figure out if the various fields are coming from CFs or from user-supplied text areas\r\n		$fields = array(\r\n			\'description\' => array(\r\n				\'html\' => \'textarea\',\r\n			),\r\n			\'authornote\' => array(\r\n				\'html\' => \'textarea\',\r\n			),\r\n			\'title\' => array(\r\n				\'html\'     => \'text_input\',\r\n				\'required\' => true,\r\n			),\r\n			\'chaptitle\' => array(\r\n				\'html\'       => \'text_input\',\r\n				\'hide_empty\' => true,\r\n			),\r\n			\'author\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'publisher\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'subject\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'srp\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n		);\r\n\r\n		$cfs = getCustomFields();\r\n\r\n		foreach ($fields as $field => $data) {\r\n			$data[\'value\'] = get_pref(\'smd_ebook_fld_\'.$field, $smd_ebook_prefs[\'smd_ebook_fld_\'.$field][\'default\']);\r\n			$data[\'column\'] = is_numeric($data[\'value\']) ? \'custom_\'.$data[\'value\'] : $data[\'value\'];\r\n			$data[\'name\'] = (is_numeric($data[\'value\']) && isset($cfs[$data[\'value\']])) ? $cfs[$data[\'value\']] : $data[\'value\'];\r\n			$data[\'content\'] = ($data[\'value\'] == \'SMD_FIXED\') ? get_pref(\'smd_ebook_fld_\'.$field.\'_fixed\', \'\') : \'\';\r\n			$data[\'required\'] = isset($data[\'required\']) ? $data[\'required\'] : false;\r\n			$data[\'hide_empty\'] = isset($data[\'hide_empty\']) ? $data[\'hide_empty\'] : false;\r\n			${\'ip_\'.$field} = \'<div class=\"smd_ebook_entity\">\' . ( ($data[\'content\'] || ($data[\'name\']==\'\' && $data[\'hide_empty\']))\r\n				? hInput(\'smd_ebook_fld_\'.$field, htmlspecialchars($data[\'content\']))\r\n				: \'<label for=\"smd_ebook_fld_\'.$field.\'\">\' . smd_ebook_gTxt(\'smd_ebook_lbl_\'.$field) . \'</label>\'\r\n					. ( ($data[\'column\'])\r\n						? hInput(\'smd_ebook_fld_\'.$field, \'SMD_FLD_\'.$data[\'column\'])\r\n							. \'<span class=\"smd_preselected\">\' . smd_ebook_gTxt(\'smd_ebook_from\').\' \'.str_replace(\'SMD_FLD_\', \'\', $data[\'name\']) . \'</span>\'\r\n						: ( ($data[\'html\'] == \'textarea\')\r\n							? text_area(\'smd_ebook_fld_\'.$field, \'150\', \'250\', \'\', \'smd_ebook_fld_\'.$field)\r\n							: fInput(\'text\', \'smd_ebook_fld_\'.$field, \'\', \'\', \'\', \'\', \'\', \'\', \'smd_ebook_fld_\'.$field, \'\', $data[\'required\'])\r\n						)\r\n					)\r\n			) . \'</div>\';\r\n		}\r\n\r\n		$where = array(\'1=1\');\r\n		$sec = get_pref(\'smd_ebook_section\', $smd_ebook_prefs[\'smd_ebook_section\'][\'default\']);\r\n		if ($sec) {\r\n			$where[] = \"Section=\'\" . doSlash($sec) . \"\'\";\r\n		}\r\n\r\n		$articles = safe_rows(\'*\', \'textpattern\', join(\' AND \', $where). \' ORDER BY url_title\');\r\n\r\n		// Build dropdown list of articles: not using selectInput() because it doesn\'t support multiples\r\n		$alist = array();\r\n		$alist[] = \'<select name=\"smd_ebook_articles[]\" id=\"smd_ebook_articles\" class=\"list multiple\" multiple=\"multiple\" size=\"12\" required=\"\">\';\r\n		foreach ($articles as $row) {\r\n			$alist[] = \'<option value=\"smd_ebook_article_\'.htmlspecialchars($row[\'ID\']).\'\">\' . htmlspecialchars($row[\'Title\']) . \'</option>\';\r\n		}\r\n		$alist[] = \'</select>\';\r\n\r\n		echo n.\'<div id=\"\'.$smd_ebook_event.\'_container\" class=\"txp-container\">\';\r\n		echo n.\'<form id=\"smd_ebook_form\" action=\"index.php\" method=\"post\">\';\r\n		echo n.\'<div class=\"smd_ebook_entity\"><label for=\"smd_ebook_articles\">\' . smd_ebook_gTxt(\'smd_ebook_lbl_articles\') . \'</label>\';\r\n		echo join(n, $alist);\r\n		echo n.\'</div>\';\r\n		echo n. $ip_description .n. $ip_authornote .n. $ip_title .n. $ip_chaptitle .n. $ip_author .n. $ip_publisher .n.$ip_subject .n. $ip_srp;\r\n		echo n.\'<div class=\"smd_clear\"></div>\';\r\n		echo n.fInput(\'submit\', \'smd_ebook_create\', smd_ebook_gTxt(\'smd_ebook_lbl_create\'), \'publish\', \'\', \'\', \'\', \'\', \'smd_ebook_create\');\r\n		echo n.eInput($smd_ebook_event);\r\n		echo n.sInput(\'smd_ebook_create\');\r\n		echo n.tInput();\r\n		echo \'</form>\';\r\n		echo \'</div>\';\r\n\r\n	} else {\r\n		// Stage 2: Edit the content and generate the kindle file\r\n		$titlePrefix = smd_ebook_gTxt(\'smd_ebook_preview_prefix\');\r\n\r\n		$qs = array(\r\n			\"event\" => $smd_ebook_event,\r\n		);\r\n		$qsVars = \"index.php\".join_qs($qs);\r\n\r\n		echo <<<EOJS\r\n<script type=\"text/javascript\">\r\nvar smd_ebook_currfile;\r\n\r\njQuery(function() {\r\n	// Load a file into the editor\r\n	jQuery(\'.smd_ebook_files .smd_ebook_file\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var me = jQuery(this);\r\n		var name = me.text();\r\n		smd_ebook_currfile = name;\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_loadfile\',\r\n				name: name,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				jQuery(\'.smd_ebook_files .smd_ebook_file\').removeClass(\'smd_selected\');\r\n				me.toggleClass(\'smd_selected\');\r\n				jQuery(\'#smd_ebook_editor\').val(jQuery(data).find(\'smd_ebook_filedata\').attr(\'value\'));\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	// Save the current file back to the file system\r\n	jQuery(\'.smd_ebook_filesave\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var content = jQuery(\'#smd_ebook_editor\').val();\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_savefile\',\r\n				name: smd_ebook_currfile,\r\n				data: content,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				jQuery(\'.smd_ebook_files .smd_ebook_file\').removeClass(\'smd_selected\');\r\n				jQuery(\'#smd_ebook_editor\').val(\'\');\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	// Preview an html file in its own popup window\r\n	var smd_ebook_previewing = 0;\r\n	jQuery(\'.smd_ebook_files .smd_ebook_view\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var me = jQuery(this).prev();\r\n		var name = me.text();\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_viewfile\',\r\n				name: name,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				// Grab body text and inject it into the preview container\r\n				jQuery(\'#smd_ebook_preview_content\').empty().append(jQuery(data).find(\'smd_ebook_filedata\').attr(\'value\'));\r\n				jQuery(\'#smd_ebook_preview_title\').text(\'{$titlePrefix} \' + name);\r\n				if ((jQuery(ev.target).hasClass(\'smd_ebook_view\')) && !smd_ebook_previewing) {\r\n					smd_ebook_prevu();\r\n				}\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	function smd_ebook_prevu() {\r\n		jQuery(\'#smd_ebook_preview\').toggle(\'fast\');\r\n		smd_ebook_previewing = !smd_ebook_previewing;\r\n	}\r\n\r\n	function smd_ebook_prevu_bind() {\r\n		jQuery(\'#smd_ebook_preview_close\').click(function(){\r\n			smd_ebook_prevu();\r\n		});\r\n		jQuery(document).keypress(function(e){\r\n			if(e.keyCode==27 && smd_ebook_previewing) {\r\n				smd_ebook_prevu();\r\n			}\r\n		});\r\n	}\r\n	smd_ebook_prevu_bind();\r\n});\r\n</script>\r\nEOJS;\r\n		echo n.\'<div id=\"\'.$smd_ebook_event.\'_container\" class=\"txp-container\">\';\r\n		echo n.\'<form id=\"smd_ebook_form\" action=\"index.php\" method=\"post\">\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_report\">\'\r\n			. hed(smd_ebook_gTxt(\'smd_ebook_lbl_report\'), 2)\r\n			. \'<textarea id=\"smd_ebook_report\" cols=80 rows=\"6\">\'.$report.\'</textarea>\'\r\n			. \'</div>\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_manager\">\';\r\n\r\n		// \'Generate book\' and \'download\' buttons\r\n		echo \'<div class=\"smd_ebook_mobi_options\">\';\r\n		echo n.fInput(\'submit\', \'smd_ebook_generate\', smd_ebook_gTxt(\'smd_ebook_lbl_generate\'), \'publish smd_ebook_mobi\');\r\n		if ($retval <= 1) {\r\n			$info = explode (\'.\',$listfile);\r\n			$basepart = array_slice($info, 0, count($info)-1);\r\n			$mobifile = join(\'\', $basepart) . \'.mobi\';\r\n			echo n.hInput(\'smd_ebook_mobifile\', $mobifile);\r\n			echo n.fInput(\'submit\', \'smd_ebook_to_files\', smd_ebook_gTxt(\'smd_ebook_lbl_to_files\'), \'publish smd_ebook_mobi\');;\r\n			echo n.fInput(\'submit\', \'smd_ebook_download\', smd_ebook_gTxt(\'smd_ebook_lbl_download\'), \'publish smd_ebook_mobi\');;\r\n		}\r\n		echo \'</div>\';\r\n\r\n		echo n.hed(smd_ebook_gTxt(\'smd_ebook_lbl_files\'), 2);\r\n		echo n.\'<div class=\"smd_ebook_files\">\';\r\n\r\n		$opf_edit = get_pref(\'smd_ebook_opf_edit\', $smd_ebook_prefs[\'smd_ebook_opf_edit\'][\'default\']);\r\n		$opf_allowed = do_list($opf_edit);\r\n		$opf_allowed[] = \'1\'; // Publishers can always edit .opf\r\n		$can_opf = in_array($GLOBALS[\'privs\'], $opf_allowed);\r\n\r\n		$files = file($prefs[\'tempdir\'] . DS . $listfile);\r\n\r\n		foreach ($files as $file) {\r\n			$info = explode (\'.\',$file);\r\n			$lastpart = count($info)-1;\r\n			$ext = trim($info[$lastpart]);\r\n			if ($ext == \'opf\') {\r\n				echo n.hInput(\'smd_ebook_opf_file\', $file);\r\n			}\r\n			if ($ext != \'opf\' || ($ext == \'opf\' && $can_opf)) {\r\n				echo n.\'<a href=\"#\" class=\"smd_ebook_file\">\'.trim($file).\'</a>\';\r\n				if ($ext == \'html\') {\r\n					echo n.\'<a href=\"#\" class=\"smd_ebook_view\">\'.smd_ebook_gTxt(\'smd_ebook_lbl_view\').\'</a>\';\r\n				}\r\n			}\r\n		}\r\n\r\n		echo n.\'<button class=\"smd_ebook_filesave smallerbox\">\'.gTxt(\'save\').\'</button>\';\r\n		echo n.\'</div>\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_editor\">\';\r\n		echo n.\'<textarea id=\"smd_ebook_editor\" cols=\"60\" rows=\"25\"></textarea>\';\r\n		echo n.\'</div>\';\r\n		echo n.\'<div id=\"smd_ebook_preview\">\r\n			<div id=\"smd_ebook_preview_titlebar\">\r\n				<span id=\"smd_ebook_preview_title\"></span>\r\n				<a id=\"smd_ebook_preview_close\">X</a>\r\n			</div>\r\n			<div id=\"smd_ebook_preview_content\"></div>\r\n			</div>\';\r\n\r\n		echo n.hInput(\'smd_ebook_listfile\', $listfile);\r\n		echo n.eInput($smd_ebook_event);\r\n		echo n.sInput(\'smd_ebook_generate\');\r\n		echo n.tInput();\r\n		echo n.\'</div>\';\r\n		echo n.\'</form>\';\r\n		echo n.\'<div class=\"smd_clear\"></div>\';\r\n		echo n.\'</div>\';\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_loadfile() {\r\n	global $prefs;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$file = file($prefs[\'tempdir\'] . DS . $name);\r\n	if ($file) {\r\n		send_xml_response(array(\'smd_ebook_filedata\' => str_replace(array(\"\'\"), array(\'&#039;\'), join(\'\', $file)) ));\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_savefile() {\r\n	global $prefs;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$content = ps(\'data\');\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $name, \"wb\");\r\n	fwrite($fp, trim($content));\r\n	fclose($fp);\r\n	if ($fp) {\r\n		send_xml_response();\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\n// Extract a subset of the HTML file for display\r\nfunction smd_ebook_viewfile() {\r\n	global $prefs, $path_to_site;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$file = file_get_contents($prefs[\'tempdir\'] . DS . $name);\r\n\r\n	if ($file) {\r\n		$doc = new DOMDocument();\r\n		$doc->loadHTML($file);\r\n		$domxpath = new DOMXpath($doc);\r\n		$newDoc = new DOMDocument(\'1.0\',\'UTF-8\');\r\n\r\n		$nodeStyle = $domxpath->query(\'//style\');\r\n		$nodeList = $domxpath->query(\'//body\');\r\n\r\n		// Create a new document and import the document subsets\r\n		$newDoc->appendChild($newDoc->importNode($nodeStyle->item(0), true));\r\n		$newDoc->appendChild($newDoc->importNode($nodeList->item(0), true));\r\n		$out = $newDoc->saveHTML();\r\n\r\n		send_xml_response(array(\'smd_ebook_filedata\' => str_replace(array(\"\'\", $path_to_site.DS), array(\'&#039;\', ihu), $out) ));\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_templates() {\r\n	// .opf file template\r\n	$template[\'opf\'] = <<<EOOPF\r\n<?xml version=\"1.0\" encoding=\"{smd_ebook_encoding}\"?>\r\n<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"2.0\" {smd_ebook_uid_ref}>\r\n	<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:opf=\"http://www.idpf.org/2007/opf\">\r\n		{smd_ebook_md_uid}\r\n		{smd_ebook_md_title}\r\n		{smd_ebook_md_lang}\r\n		{smd_ebook_md_creator}\r\n		{smd_ebook_md_date}\r\n		{smd_ebook_md_description}\r\n		{smd_ebook_md_subject}\r\n		{smd_ebook_md_publisher}\r\n		{smd_ebook_md_cover}\r\n		<x-metadata>\r\n			<output encoding=\"{smd_ebook_encoding}\" content-type=\"text/x-oeb1-document\"></output>\r\n			{smd_ebook_md_srp}\r\n		</x-metadata>\r\n	</metadata>\r\n\r\n	<manifest>\r\n		{smd_ebook_manifest_ncx}\r\n		{smd_ebook_manifest_cover}\r\n		{smd_ebook_manifest_authornote}\r\n		{smd_ebook_manifest_toc}\r\n		{smd_ebook_manifest_items}\r\n	</manifest>\r\n\r\n	<spine {smd_ebook_spine_ncx_ref}>\r\n		{smd_ebook_spine_ncx}\r\n		{smd_ebook_spine_authornote}\r\n		{smd_ebook_spine_toc}\r\n		{smd_ebook_spine_items}\r\n	</spine>\r\n\r\n	<guide>\r\n		{smd_ebook_guide_toc}\r\n		{smd_ebook_guide_start}\r\n	</guide>\r\n</package>\r\nEOOPF;\r\n\r\n	// .ncx file template\r\n	$template[\'ncx\'] = <<<EONCX\r\n<?xml version=\"1.0\" encoding=\"{smd_ebook_encoding}\"?>\r\n<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"\r\n\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\r\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\" xml:lang=\"{smd_ebook_lang}\">\r\n	<head>\r\n		{smd_ebook_dtb_uid}\r\n	</head>\r\n\r\n	<docTitle><text>{smd_ebook_title}</text></docTitle>\r\n	<docAuthor><text>{smd_ebook_creator}</text></docAuthor>\r\n	<navMap>\r\n		{smd_ebook_ncx_map}\r\n	</navMap>\r\n</ncx>\r\nEONCX;\r\n\r\n	// navPoint template (a portion of the .ncx file)\r\n	$template[\'nav\'] = <<<EONAV\r\n		<navPoint class=\"titlepage\" id=\"{smd_ebook_nav_hash}\" playOrder=\"{smd_ebook_nav_idx}\">\r\n			<navLabel><text>{smd_ebook_nav_label}</text></navLabel>\r\n			<content src=\"{smd_ebook_file_name}#{smd_ebook_nav_hash}\" />\r\n		</navPoint>\r\nEONAV;\r\n\r\n	// TOC template\r\n	$template[\'toc\'] = <<<EOTOC\r\n<html>\r\n<head>\r\n	<title>Table of Contents</title>\r\n	{smd_ebook_stylesheet}\r\n</head>\r\n<body>\r\n	{smd_ebook_toc_list}\r\n</body>\r\n</html>\r\nEOTOC;\r\n\r\n	// HTML template\r\n	$template[\'doc\'] = <<<EOTOC\r\n<html>\r\n<head>\r\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset={smd_ebook_encoding}\" />\r\n	<title>{smd_ebook_title}</title>\r\n	{smd_ebook_stylesheet}\r\n</head>\r\n<body>\r\n	{smd_ebook_chaptitle}\r\n	{smd_ebook_contents}\r\n</body>\r\n</html>\r\nEOTOC;\r\n\r\n	return $template;\r\n}\r\n\r\n// ------------------------\r\n// Stage 1: create the files necessary for generation of the book.\r\n// The actual generation via kindlegen is a separate step.\r\nfunction smd_ebook_create() {\r\n	global $smd_ebook_prefs, $prefs, $img_dir;\r\n\r\n	@include_once txpath.\'/lib/classTextile.php\';\r\n	@include_once txpath.\'/publish.php\'; // for parse()\r\n	$textile = new Textile();\r\n\r\n	$template = smd_ebook_templates();\r\n	$msg = \'\';\r\n	$report = $toc = $ncx = $reps = array();\r\n\r\n	// Get Textile and encoding options\r\n	$encoding = get_pref(\'smd_ebook_encoding\', $smd_ebook_prefs[\'smd_ebook_encoding\'][\'default\']);\r\n	$which = get_pref(\'smd_ebook_textile\', $smd_ebook_prefs[\'smd_ebook_textile\'][\'default\']);\r\n	$txt_description = in_list(\'description\', $which);\r\n	$txt_authornote = in_list(\'authornote\', $which);\r\n\r\n	// Build up a giant replacement table which is then substituted into\r\n	// the various templates before passing to kindlegen\r\n\r\n	// Populate the unique ID entries direcly into the .opf template as they\'re only used once each\r\n	$uid = get_pref(\'smd_ebook_uid\', $smd_ebook_prefs[\'smd_ebook_uid\'][\'default\']);\r\n\r\n	$template[\'opf\'] = str_replace(\'{smd_ebook_uid_ref}\', (($uid) ? \'unique-identifier=\"uid\"\' : \'\'), $template[\'opf\']);\r\n	$template[\'opf\'] = str_replace(\'{smd_ebook_md_uid}\', (($uid) ? \'<dc:identifier id=\"uid\">\' . $uid . \'</dc:identifier>\' : \'\'), $template[\'opf\']);\r\n	$template[\'ncx\'] = str_replace(\'{smd_ebook_dtb_uid}\', (($uid) ? \'<meta name=\"dtb:uid\" content=\"uid\"/>\' : \'\'), $template[\'ncx\']);\r\n\r\n	// Set up the TOC wrappers\r\n	$toc_wrap = get_pref(\'smd_ebook_toc_wraptag\', $smd_ebook_prefs[\'smd_ebook_toc_wraptag\'][\'default\']);\r\n	$toc_class = get_pref(\'smd_ebook_toc_class\', $smd_ebook_prefs[\'smd_ebook_toc_class\'][\'default\']);\r\n	$wrapit = ($toc_wrap == \'ol\') ? \'#\' : \'*\';\r\n\r\n	$article_cnt = $ncx_cnt = $elem_cnt = 0;\r\n	$article_refs = $article_spines = array();\r\n\r\n	// Page break, stylesheet and heading references\r\n	$pbr = get_pref(\'smd_ebook_page_break\', $smd_ebook_prefs[\'smd_ebook_page_break\'][\'default\']);\r\n	$css = get_pref(\'smd_ebook_stylesheet\', $smd_ebook_prefs[\'smd_ebook_stylesheet\'][\'default\']);\r\n	$hdg = get_pref(\'smd_ebook_heading_level\', $smd_ebook_prefs[\'smd_ebook_heading_level\'][\'default\']);\r\n\r\n	if ($css) {\r\n		$sheet = safe_field(\'css\', \'txp_css\', \"name=\'\" . doSlash($css) . \"\'\");\r\n	}\r\n	$sheet = ($sheet) ? \'<style type=\"text/css\">\' . $sheet . \'</style>\' : \'\';\r\n\r\n\r\n	// Loop for each article in the collection\r\n	foreach (ps(\'smd_ebook_articles\') as $artid) {\r\n		$article_cnt++;\r\n\r\n		$id = str_replace(\'smd_ebook_article_\', \'\', $artid);\r\n		$row = safe_row(\'*\', \'textpattern\', \"ID = \'\" . doSlash($id) . \"\'\");\r\n\r\n		if ($row) {\r\n			// Initialize a few things\r\n			$note_content = \'\';\r\n			$reps[\'{smd_ebook_file_name}\'] = $row[\'url_title\'] . \'.html\';\r\n			$reps[\'{smd_ebook_encoding}\'] = $encoding;\r\n			$cur_file = $row[\'url_title\'] . \'.html\';\r\n\r\n			// Each of the items starting !isset() are only ever loaded _once_ from the\r\n			// first article in which they are found.\r\n			// Begin by setting up the file names\r\n			if (!isset($firstfile)) {\r\n				$firstfile = $row[\'url_title\'] . \'.html\';\r\n				$listfile = $row[\'url_title\'] . \'.smd\';\r\n				$notefile = $row[\'url_title\'] . \'_notes.html\';\r\n				$toc_file = $row[\'url_title\'] . \'_toc.html\';\r\n				$ncx_file = $row[\'url_title\'] . \'.ncx\';\r\n				$opf_file = $row[\'url_title\'] . \'.opf\';\r\n			}\r\n			// Language\r\n			if (!isset($reps[\'{smd_ebook_md_lang}\'])) {\r\n				$reps[\'{smd_ebook_md_lang}\'] = \'<dc:language>\'.$prefs[\'language\'].\'</dc:language>\';\r\n				$reps[\'{smd_ebook_lang}\'] = $prefs[\'language\'];\r\n			}\r\n			// Author can come from:\r\n			//  1) an article field\r\n			//  2) the current logged in user\r\n			//  3) user-supplied at book creation time\r\n			//  4) hard-coded in plugin settings\r\n			if (!isset($reps[\'{smd_ebook_md_creator}\'])) {\r\n				$val = ps(\'smd_ebook_fld_author\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? ( ($valfld == \'AuthorID\') ? get_author_name($row[$valfld]) : $row[$valfld] ) : \'\';\r\n				}\r\n				if ($val) {\r\n					$reps[\'{smd_ebook_md_creator}\'] = \'<dc:creator>\'.$val.\'</dc:creator>\';\r\n					$reps[\'{smd_ebook_creator}\'] = $val;\r\n				}\r\n			}\r\n			// Publication date\r\n			// TODO: reformat?\r\n			if (!isset($reps[\'{smd_ebook_md_date}\'])) {\r\n				$reps[\'{smd_ebook_md_date}\'] = \'<dc:date>\'.($row[\'Posted\']).\'</dc:date>\';\r\n			}\r\n\r\n			// Cover image\r\n			if (!isset($reps[\'{smd_ebook_md_cover}\'])) {\r\n				if (isset($row[\'Image\'])) {\r\n					$img = safe_row(\'*\', \'txp_image\', \"id=\'\" . intval($row[\'Image\']) . \"\'\");\r\n\r\n					// Only GIFs and JPGs need apply\r\n					$mime_type = (($img[\'ext\'] == \'.jpg\') ? \'image/jpeg\' : (($img[\'ext\'] == \'.gif\') ? \'image/gif\' : \'\'));\r\n					if ($mime_type) {\r\n						$reps[\'{smd_ebook_md_cover}\'] = \'<meta name=\"cover\" content=\"cover-image\" />\';\r\n						$reps[\'{smd_ebook_manifest_cover}\'] = \'<item id=\"cover-image\" media-type=\"\'.$mime_type.\'\" href=\"\' . $prefs[\'path_to_site\'] . DS . $img_dir . DS . $img[\'id\'] . $img[\'ext\'] . \'\" />\';\r\n					}\r\n				}\r\n			}\r\n\r\n			// The following values can either come from the given field or be used verbatim\r\n			// Firstly the title, description, subject, publisher and chapter title\r\n			$setMany = array(\'chaptitle\');\r\n			foreach (array(\'title\', \'description\', \'subject\', \'publisher\', \'chaptitle\') as $thingy) {\r\n				if (in_array($thingy, $setMany) || !isset($reps[\'{smd_ebook_md_\'.$thingy.\'}\'])) {\r\n					$val = ps(\'smd_ebook_fld_\'.$thingy);\r\n					if (strpos($val, \'SMD_FLD_\') !== false) {\r\n						$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n						$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n					}\r\n					if ($val) {\r\n						// Textile the content?\r\n						$content = (isset($txt_{$thingy}) && $txt_{$thingy}) ? trim($textile->TextileThis($val)) : trim($val);\r\n						if (!in_array($thingy, $setMany)) {\r\n							$reps[\'{smd_ebook_md_\'.$thingy.\'}\'] = \'<dc:\'.$thingy.\'>\' . $content . \'</dc:\'.$thingy.\'>\';\r\n						}\r\n\r\n						// There are two titles: one for the metadata and one raw so if the title\r\n						// has just been found, populate the raw title too\r\n						if ($thingy == \'title\') {\r\n							$reps[\'{smd_ebook_title}\'] = $content;\r\n						} else if ($thingy == \'chaptitle\') {\r\n							// Chapter title has an associated heading level\r\n							$reps[\'{smd_ebook_chaptitle}\'] = \'<h\'.$hdg.\'>\'.$content.\'</h\'.$hdg.\'>\';\r\n						}\r\n					}\r\n				}\r\n			}\r\n\r\n			// Price (SRP) can also come from a field but it needs some special jiggery pokery\r\n			if (!isset($reps[\'{smd_ebook_md_srp}\'])) {\r\n				$val = ps(\'smd_ebook_fld_srp\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n				}\r\n				if ($val) {\r\n					$parts = do_list($val, \'|\');\r\n					$parts[0] = $parts[0] ? $parts[0] : \'0.00\';\r\n					$parts[1] = (isset($parts[1]) && $parts[1]) ? $parts[1] : get_pref(\'smd_ebook_currency\', $smd_ebook_prefs[\'smd_ebook_currency\'][\'default\']);\r\n					$reps[\'{smd_ebook_md_srp}\'] = \'<SRP Currency=\"\'.$parts[1].\'\">\'.$parts[0].\'</SRP>\';\r\n				}\r\n			}\r\n\r\n			// Authornote is slightly different because it needs storing as a file,\r\n			// and needs adding to the .ncx (but not to the TOC)\r\n			if (!isset($reps[\'{smd_ebook_manifest_authornote}\'])) {\r\n				$val = ps(\'smd_ebook_fld_authornote\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n				}\r\n				if ($val) {\r\n					$reps[\'{smd_ebook_manifest_authornote}\'] = \'<item id=\"smd_ebook_notes\" media-type=\"text/html\" href=\"\'.$notefile.\'\" />\';\r\n					$reps[\'{smd_ebook_spine_authornote}\'] = \'<itemref idref=\"smd_ebook_notes\" />\';\r\n					$note_content = \'<span id=\"smd_ebook_notes\"></span>\' . (($txt_authornote) ? $textile->TextileThis($val) : $val);\r\n\r\n					// While it\'s 99% likely the actual title used for the eventual book has been found,\r\n					// there\'s a slim chance it hasn\'t. In that case, the current row\'s title is used as a fallback\r\n					$note_title = isset($reps[\'{smd_ebook_title}\']) ? $reps[\'{smd_ebook_title}\'] : $row[\'Title\'];\r\n					$note_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $note_title, \'\', $sheet, $note_content), $template[\'doc\']);\r\n\r\n					$fp = fopen($prefs[\'tempdir\'].DS.$notefile, \"wb\");\r\n					fwrite($fp, trim($note_content));\r\n					fclose($fp);\r\n					$lfout[] = $notefile;\r\n\r\n					// Add it to the .ncx\r\n					$ncx_cnt++;\r\n					$from = array(\'{smd_ebook_file_name}\', \'{smd_ebook_nav_label}\', \'{smd_ebook_nav_hash}\', \'{smd_ebook_nav_idx}\');\r\n					$to = array($notefile, smd_ebook_gTxt(\'smd_ebook_lbl_authornote\'), \'smd_ebook_notes\', $ncx_cnt);\r\n					$ncx[] = str_replace($from, $to, $template[\'nav\']);\r\n				}\r\n			}\r\n\r\n			// Note:\r\n			//  1) a full (well-formed, hopefully) HTML file (from <html>...</html>) is generated\r\n			//     here so the loadHTML() method is happy. The body will need reinjecting into\r\n			//     the template after the ToC has been generated.\r\n			//  2) The current HTML file\'s title is used instead of the overall book title.\r\n			//  3) parse() is called twice to simulate secondpass.\r\n			$chap_title = isset($reps[\'{smd_ebook_chaptitle}\']) ? $reps[\'{smd_ebook_chaptitle}\'] : \'\';\r\n			article_format_info($row); // Load article context\r\n			$html_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $row[\'Title\'], $chap_title, $sheet, parse(parse($row[\'Body_html\']))), $template[\'doc\']);\r\n\r\n			// Trawl through the HTML content, either:\r\n			//  a) pulling out the ToC entries.\r\n			//  b) creating ToC entries if the pref allows\r\n			$autotoc = get_pref(\'smd_ebook_auto_toc\', $smd_ebook_prefs[\'smd_ebook_auto_toc\'][\'default\']);\r\n			$doc = new DOMDocument();\r\n			$dom_ok = $doc->loadHTML($html_content);\r\n			if ($dom_ok) {\r\n				$items = $doc->getElementsByTagName(\'*\');\r\n				$offset = $toc_cnt = 0;\r\n				foreach ($items as $item) {\r\n					if ($autotoc && !$item->hasAttribute(\'id\') && preg_match(\'/h([1-6])/i\', $item->nodeName, $matches)) {\r\n						// It\'s a heading. Make the anchor chain based on the heading level\r\n						$anchor_parts = array_fill(0, $matches[1], \'sub\');\r\n						$anchor = join(\'-\', $anchor_parts). ++$elem_cnt;\r\n						$item->setAttribute(\'id\', $anchor);\r\n					}\r\n\r\n					if ($item->hasAttribute(\'id\')) {\r\n						$ncx_cnt++;\r\n						$toc_cnt++;\r\n						$hashval = $item->getAttribute(\'id\');\r\n						if ( (!isset($reps[\'{smd_ebook_guide_start}\'])) && ($toc_cnt == 1) ) {\r\n							$reps[\'{smd_ebook_guide_start}\'] = \'<reference type=\"text\" title=\"\'.smd_ebook_gTxt(\'smd_ebook_lbl_start\').\'\" href=\"\'.$firstfile.\'#\'.$hashval.\'\"></reference>\';\r\n						}\r\n\r\n						// mb_convert_encoding() seems to bypass the odd behaviour where apostrophes\r\n						// would appear in the TOC as â€™. This may actually be a band-aid to circumvent\r\n						// problems with the encoding in DOMDocument: perhaps if appropriate encoding is\r\n						// used there, this hack won\'t be necessary\r\n						$node = mb_convert_encoding($item->nodeValue, \'HTML-ENTITIES\', \'utf-8\');\r\n						$from = array(\'{smd_ebook_file_name}\', \'{smd_ebook_nav_label}\', \'{smd_ebook_nav_hash}\', \'{smd_ebook_nav_idx}\');\r\n						$to = array($cur_file, $node, $hashval, $ncx_cnt);\r\n						$ncx[] = str_replace($from, $to, $template[\'nav\']);\r\n\r\n						// Now it\'s the turn of the HTML TOC. Utilise Textile here to\r\n						// create the toc list from ul or ol syntax\r\n						$hashBits = do_list($hashval, \'-\');\r\n						$indent = count($hashBits);\r\n\r\n						if ( ($toc_cnt == 1) && ($indent > 1) ) {\r\n							// Doesn\'t start with h1 (begins h2, maybe) so scale back the indent.\r\n							// Without this, Textile produces invalid markup\r\n							$offset = $indent - 1;\r\n						}\r\n\r\n						$toc_cls = (($toc_cnt == 1) && $toc_class) ? \'(\'.$toc_class.\')\' : \'\';\r\n						$toc[] = str_pad(\'\', max(1, $indent-$offset), $wrapit) . $toc_cls.\' \' . href($node, $cur_file.\'#\'.$hashval);\r\n					}\r\n				}\r\n\r\n				// Grab any changes just made to the DOM tree in case anchors have been added.\r\n				// Note _only_ the <body> is extracted since the XML headers that come with a full\r\n				// saveXML() get in the way. Also note that saveHTML() is not being used because its\r\n				// \'node\' parameter wasn\'t added until PHP 5.3.6 which would affect the plugin\'s\r\n				// base requirements.\r\n				// Hackish: remove the body tag wrapper with substr() so when the html_content\r\n				// is shoved back into the template (which has a body tag already) there\'s no\r\n				// tag duplication\r\n				$html_content = substr($doc->saveXML($doc->getElementsByTagName(\'body\')->item(0)), 6, -7);\r\n\r\n				// Swap out any line break placeholders. Note that the line break is replaced twice:\r\n				// once to get rid of any surrounding <p> tags that Textile may have introduced around\r\n				// the marker, and again in case a few of them didn\'t get paragraph tags.\r\n				$html_content = str_replace(\'<p>\'.$pbr.\'</p>\', \'<mbp:pagebreak />\', $html_content);\r\n				$html_content = str_replace($pbr, \'<mbp:pagebreak />\', $html_content);\r\n\r\n				// Pass the extracted <body> tree into the doc template again so it regenerates\r\n				// the full <html>...</html> document.\r\n				$html_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $row[\'Title\'], \'\', $sheet, $html_content), $template[\'doc\']);\r\n			} else {\r\n				trigger_error(smd_ebook_gTxt(\'smd_ebook_malformed\'), E_WARNING);\r\n			}\r\n\r\n			// Write the final HTML document to the file system\r\n			$fp = fopen($prefs[\'tempdir\'].DS.$cur_file, \"wb\");\r\n			fwrite($fp, trim($html_content));\r\n			fclose($fp);\r\n\r\n			$lfout[] = $cur_file;\r\n			$article_refs[] = \'<item id=\"smd_ebook_item_\'.$article_cnt.\'\" media-type=\"text/html\" href=\"\'.$row[\'url_title\'].\'.html\" />\';\r\n			$article_spines[] = \'<itemref idref=\"smd_ebook_item_\' . $article_cnt . \'\" />\';\r\n\r\n		}\r\n	}\r\n\r\n	// Ensure any NULL replacements are cleared or throw errors\r\n	$reps[\'{smd_ebook_chaptitle}\'] = (!isset($reps[\'{smd_ebook_chaptitle}\'])) ? \'\' : $reps[\'{smd_ebook_chaptitle}\'];\r\n	$reps[\'{smd_ebook_creator}\'] = (!isset($reps[\'{smd_ebook_creator}\'])) ? \'\' : $reps[\'{smd_ebook_creator}\'];\r\n	$reps[\'{smd_ebook_md_creator}\'] = (!isset($reps[\'{smd_ebook_md_creator}\'])) ? \'\' : $reps[\'{smd_ebook_md_creator}\'];\r\n	$reps[\'{smd_ebook_md_description}\'] = (!isset($reps[\'{smd_ebook_md_description}\'])) ? \'\' : $reps[\'{smd_ebook_md_description}\'];\r\n	$reps[\'{smd_ebook_md_subject}\'] = (!isset($reps[\'{smd_ebook_md_subject}\'])) ? \'\' : $reps[\'{smd_ebook_md_subject}\'];\r\n	$reps[\'{smd_ebook_md_publisher}\'] = (!isset($reps[\'{smd_ebook_md_publisher}\'])) ? \'\' : $reps[\'{smd_ebook_md_publisher}\'];\r\n	$reps[\'{smd_ebook_md_srp}\'] = (!isset($reps[\'{smd_ebook_md_srp}\'])) ? \'\' : $reps[\'{smd_ebook_md_srp}\'];\r\n	$reps[\'{smd_ebook_guide_start}\'] = (!isset($reps[\'{smd_ebook_guide_start}\'])) ? \'\' : $reps[\'{smd_ebook_guide_start}\'];\r\n	if (!isset($reps[\'{smd_ebook_md_cover}\'])) {\r\n		$reps[\'{smd_ebook_md_cover}\'] = \'\';\r\n		$reps[\'{smd_ebook_manifest_cover}\'] = \'\';\r\n	}\r\n	if (!isset($reps[\'{smd_ebook_manifest_authornote}\'])) {\r\n		$reps[\'{smd_ebook_manifest_authornote}\'] =  \'\';\r\n		$reps[\'{smd_ebook_spine_authornote}\'] =  \'\';\r\n	}\r\n\r\n	// All the replacements are set up so prepare for kindlegen\r\n	// First, create the TOC and write it to the filesystem\r\n	if ($toc_cnt > 0) {\r\n		$reps[\'{smd_ebook_manifest_toc}\'] = \'<item id=\"toc\" media-type=\"text/html\" href=\"\'.$toc_file.\'\" />\';\r\n		$reps[\'{smd_ebook_spine_toc}\'] = \'<itemref idref=\"toc\" />\';\r\n		$reps[\'{smd_ebook_guide_toc}\'] = \'<reference type=\"toc\" title=\"\' . smd_ebook_gTxt(\'smd_ebook_toc\') . \'\" href=\"\'.$toc_file.\'\"></reference>\';\r\n		$html_toc = $textile->TextileThis(join(n, $toc));\r\n		$html_toc = str_replace(array(\'{smd_ebook_toc_list}\', \'{smd_ebook_stylesheet}\'), array($html_toc, $sheet), $template[\'toc\']);\r\n		$fp = fopen($prefs[\'tempdir\'] . DS . $toc_file, \"wb\");\r\n		fwrite($fp, trim($html_toc));\r\n		fclose($fp);\r\n		$lfout[] = $toc_file;\r\n	} else {\r\n		$reps[\'{smd_ebook_manifest_toc}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_toc}\'] = \'\';\r\n		$reps[\'{smd_ebook_guide_toc}\'] = \'\';\r\n	}\r\n\r\n	// Add the ncx waypoints to the reps array and generate the .ncx file\r\n	if ($ncx_cnt > 0) {\r\n		$reps[\'{smd_ebook_ncx_map}\'] = join(n, $ncx);\r\n		$reps[\'{smd_ebook_manifest_ncx}\'] = \'<item id=\"ncx\" media-type=\"application/x-dtbncx+xml\" href=\"\'.$ncx_file.\'\" />\';\r\n		$reps[\'{smd_ebook_spine_ncx}\'] = \'<itemref idref=\"ncx\" />\';\r\n		$reps[\'{smd_ebook_spine_ncx_ref}\'] = \'toc=\"ncx\"\';\r\n		$ncx_file_content = strtr($template[\'ncx\'], $reps);\r\n		$fp = fopen($prefs[\'tempdir\'] . DS . $ncx_file, \"wb\");\r\n		fwrite($fp, trim($ncx_file_content));\r\n		fclose($fp);\r\n		$lfout[] = $ncx_file;\r\n	} else {\r\n		$reps[\'{smd_ebook_manifest_ncx}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_ncx}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_ncx_ref}\'] = \'\';\r\n	}\r\n\r\n	// Build the remaining manifest replacements and generate the OPF\r\n	$reps[\'{smd_ebook_manifest_items}\'] = join(n, $article_refs);\r\n	$reps[\'{smd_ebook_spine_items}\'] = join(n, $article_spines);\r\n\r\n	$opf_file_content = strtr($template[\'opf\'], $reps);\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $opf_file, \"wb\");\r\n	fwrite($fp, trim($opf_file_content));\r\n	fclose($fp);\r\n	$lfout[] = $opf_file;\r\n\r\n	// Write the listfile, which contains a list of all the files used in this stage\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $listfile, \"wb\");\r\n	fwrite($fp, join(n, $lfout));\r\n	fclose($fp);\r\n\r\n	// Hand off to Stage 2 to do the deed\r\n	smd_ebook_generate($listfile, $opf_file);\r\n}\r\n\r\n// ------------------------\r\n// Stage 2 only: Pre-requisites are that the necessary files (toc, .html, ncx + opf)\r\n// have already been generated by the previous stage. If called directly via the\r\n// GUI, the hidden form value containing the OPF file is read.\r\nfunction smd_ebook_generate($listfile=\'\', $opf_file=\'\') {\r\n	global $prefs, $smd_ebook_prefs;\r\n\r\n	$report = array();\r\n	$retval = NULL;\r\n\r\n	// Use passed in values in lieu of the one in the form\r\n	$opf_file = ($opf_file) ? $opf_file : ps(\'smd_ebook_opf_file\');\r\n	$listfile = ($listfile) ? $listfile : ps(\'smd_ebook_listfile\');\r\n\r\n	// .mobifile credentials\r\n	$mobifile = ps(\'smd_ebook_mobifile\');\r\n	$fullpath = $prefs[\'tempdir\'] . DS . $mobifile;\r\n	$filesize = filesize($fullpath);\r\n\r\n	$downloadit = ps(\'smd_ebook_download\');\r\n	$fileit = ps(\'smd_ebook_to_files\');\r\n\r\n	if ($downloadit) {\r\n		ob_clean();\r\n		header(\'Content-Description: File Download\');\r\n		header(\'Content-Type: application/octet-stream\');\r\n		header(\'Content-Disposition: attachment; filename=\"\'.$mobifile.\'\"; size = \"\'.$filesize.\'\"\');\r\n		header(\"Content-Transfer-Encoding: binary\");\r\n		header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\r\n		header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\r\n		header(\'Cache-Control: private\');\r\n		@set_time_limit(0);\r\n		if ($file = fopen($fullpath, \'rb\')) {\r\n			while(!feof($file) and (connection_status()==0)) {\r\n				echo fread($file, 1024*64);\r\n				ob_flush();\r\n				flush();\r\n			}\r\n			fclose($file);\r\n		}\r\n		exit;\r\n\r\n	} else if ($fileit) {\r\n\r\n		@include_once txpath.\'/include/txp_file.php\';\r\n\r\n		// Copy the file to the files area\r\n		$destpath = $prefs[\'file_base_path\'] . DS . $mobifile;\r\n		copy($fullpath, $destpath);\r\n\r\n		// Get the file category\r\n		$filecat = get_pref(\'smd_ebook_file_cat\', $smd_ebook_prefs[\'smd_ebook_file_cat\'][\'default\']);\r\n\r\n		// Read description and title from .opf\r\n		$doc = new DOMDocument();\r\n		$content = file_get_contents($prefs[\'tempdir\'] . DS . $opf_file);\r\n		$dom_ok = $doc->loadXML($content);\r\n\r\n		$description = $title = \'\';\r\n		if ($dom_ok) {\r\n			$items = $doc->getElementsByTagName(\'*\');\r\n			foreach ($items as $item) {\r\n				if ($item->nodeName == \'dc:title\') {\r\n					$title = $item->nodeValue;\r\n				}\r\n				if ($item->nodeName == \'dc:description\') {\r\n					$description = $item->nodeValue;\r\n				}\r\n			}\r\n		}\r\n\r\n		$curid = safe_field(\'id\', \'txp_file\', \"filename=\'\".doSlash($mobifile).\"\'\");\r\n\r\n		if ($curid) {\r\n			// Update existing database entry\r\n			$ret = safe_update(\'txp_file\',\r\n				\"\r\n					title=\'\" . doSlash($title) . \"\',\r\n					category=\'\" . doSlash($filecat) . \"\',\r\n					description=\'\" . doSlash($description) . \"\',\r\n					size=\'\" . doSlash($filesize) . \"\',\r\n					modified= now()\r\n				\",\r\n				\"id=\'\".doSlash($curid).\"\'\"\r\n			);\r\n			if ($ret) {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_updated\', array(\'{id}\' => $curid));\r\n			} else {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_not_filed\');\r\n			}\r\n\r\n		} else {\r\n			// Make a new entry in the database for it\r\n			$newid = file_db_add(doSlash($mobifile), doSlash($filecat), \'\', doSlash($description), doSlash($filesize), doSlash($title));\r\n			if ($newid) {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_filed\', array(\'{id}\' => $newid));\r\n			} else {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_not_filed\');\r\n			}\r\n		}\r\n\r\n	} else {\r\n\r\n		// (Re)generate the book\r\n		list($report, $retval) = smd_ebook_kindlegen($opf_file);\r\n\r\n		if ($retval > 1) {\r\n			$msg = smd_ebook_gTxt(\'smd_ebook_generate_failed\', array(\'{code}\' => $retval));\r\n		} else {\r\n			$msg = smd_ebook_gTxt(\'smd_ebook_generate_ok\');\r\n		}\r\n	}\r\n	smd_ebook_ui($msg, $listfile, join(n, $report), $retval);\r\n}\r\n\r\n// ------------------------\r\n// Interface with kindlegen to generate the .mobi file.\r\nfunction smd_ebook_kindlegen($opf) {\r\n	global $prefs, $smd_ebook_prefs;	\r\n\r\n	$kgen = get_pref(\'smd_ebook_kindlegen_path\', $smd_ebook_prefs[\'smd_ebook_kindlegen_path\'][\'default\']);\r\n	$command = $kgen . \' \' . $prefs[\'tempdir\'] . DS . $opf;\r\n	exec($command, $output, $result);\r\n\r\n	return array($output, $result);\r\n}\r\n\r\n// ------------------------\r\n// Common buttons for the interface\r\nfunction smd_ebook_buttons($curr=\'mgr\') {\r\n	global $smd_ebook_event;\r\n\r\n	$ret = array (\r\n		\'btnMgr\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.\'\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_mgr\'), \'smallerbox\'.($curr==\'mgr\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnPrf\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_prefs\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_prf\'), \'smallerbox\'.($curr==\'prf\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnCln\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_tidy\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_cln\'), \'smallerbox\'.($curr==\'cln\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnTst\' => \'<a href=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_test\'.a.\'_txp_token=\'.form_token().\'\" class=\"smd_inline\">\'.smd_ebook_gTxt(\'smd_ebook_lbl_tst\').\'</a>\',\r\n	);\r\n	return $ret;\r\n}\r\n\r\n// ------------------------\r\n// Tidy up the temp dir\r\nfunction smd_ebook_tidy($msg=\'\') {\r\n	global $prefs, $smd_ebook_event, $smd_ebook_styles;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	if (ps(\'smd_ebook_cleanup\')) {\r\n		$to_delete = ps(\'smd_ebook_files\');\r\n		foreach($to_delete as $del) {\r\n			$path = realpath($prefs[\'tempdir\'] . DS . $del);\r\n			unlink($path);\r\n		}\r\n		$msg = smd_ebook_gTxt(\'smd_ebook_deleted\');\r\n	}\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'cln\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	$filelist = array();\r\n	$valid = array(\'mobi\', \'html\', \'ncx\', \'opf\', \'smd\');\r\n	$tmp = $prefs[\'tempdir\'] . DS;\r\n\r\n	// Grab all files then remove unnecessary ones: faster than multiple globs\r\n	// for each file type and more robust than relying on GLOB_BRACE support\r\n	$allfiles = glob($tmp.\'*.*\');\r\n\r\n	foreach ($allfiles as $file) {\r\n		$info = explode (\'.\',$file);\r\n		$lastpart = count($info)-1;\r\n		$ext = trim($info[$lastpart]);\r\n		if (in_array($ext, $valid)) {\r\n			$filelist[] = $file;\r\n		}\r\n	}\r\n\r\n	// Inject styles\r\n	echo \'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n\r\n	echo \'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	$filesel = \'\';\r\n	if ($filelist) {\r\n		$filez = array();\r\n		foreach($filelist as $val) {\r\n			$val = basename($val);\r\n			$key = sanitizeForFile($val);\r\n			$filez[$key] = $val;\r\n	   }\r\n		$selout[] = \'<select id=\"smd_ebook_files\" name=\"smd_ebook_files[]\" class=\"list\" size=\"20\" multiple=\"multiple\">\';\r\n		foreach ($filez as $key => $leaf) {\r\n			$selout[] = t.\'<option value=\"\'.$key.\'\">\'.htmlspecialchars($leaf).\'</option>\'.n;\r\n		}\r\n		$selout[] = \'</select>\';\r\n		$filesel = join(n, $selout);\r\n	}\r\n\r\n	echo \'<div class=\"txp-list\">\';\r\n	echo startTable(\'list\');\r\n	echo \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.\'\">\';\r\n	echo tr(tda(strong(smd_ebook_gTxt(\'smd_ebook_tidy\'))));\r\n	echo ($filesel) ? tr(tda($filesel)) : tr(tda(smd_ebook_gTxt(\'smd_ebook_no_files\')));\r\n	echo tr(tda(fInput(\'submit\', \'smd_ebook_cleanup\', gTxt(\'delete\'), \'publish\'), \' class=\"noline\"\'));\r\n	echo sInput(\'smd_ebook_tidy\');\r\n	echo tInput();\r\n	echo \'</form>\';\r\n	echo endTable();\r\n	echo \'</div>\';\r\n}\r\n\r\n// ------------------------\r\n// List of current stylesheets\r\nfunction smd_ebook_style_list($name, $val=\'\') {\r\n	$styles = safe_column(\'name\', \'txp_css\', \'1=1\');\r\n	return selectInput($name, $styles, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of current file categories\r\nfunction smd_ebook_file_cat_list($name, $val=\'\') {\r\n	$rs = getTree(\'root\', \'file\');\r\n	if ($rs) {\r\n		return treeSelectInput($name, $rs, $val, $name);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// List of numbers for heading levels\r\nfunction smd_ebook_number($name, $val=\'\') {\r\n	// Can\'t use range() since it creates indices starting at 0\r\n	$nums = array();\r\n	for ($idx = 1; $idx <= 6; $idx++) {\r\n		$nums[$idx] = $idx;\r\n	}\r\n	return selectInput($name, $nums, $val, false);\r\n}\r\n\r\n// ------------------------\r\n// List of current sections\r\n// TODO: multiple select?\r\nfunction smd_ebook_section_list($name, $val=\'\') {\r\n	$secs = safe_column(\'name\', \'txp_section\', \'1=1\');\r\n	return selectInput($name, $secs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields\r\nfunction smd_ebook_fld_list($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields with a few extras\r\nfunction smd_ebook_fld_list_plus($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'Category1\'] = gTxt(\'category1\');\r\n	$cfs[\'Category2\'] = gTxt(\'category2\');\r\n	$cfs[\'Section\'] = gTxt(\'section\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields\r\nfunction smd_ebook_fld_list_author($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'AuthorID\'] = gTxt(\'author\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// Multi-select list of privilege levels\r\nfunction smd_ebook_priv_list($name, $val=\'\') {\r\n	$grps = get_groups();\r\n	unset($grps[\'0\']); // Remove \'none\'\r\n	unset($grps[\'1\']); // Remove publishers -- they get access to everything already\r\n\r\n	$sels = do_list($val);\r\n\r\n	$ulist = array();\r\n	$ulist[] = \'<select name=\"\'.$name.\'[]\" id=\"\'.$name.\'\" class=\"list multiple\" multiple=\"multiple\" size=\"6\">\';\r\n	foreach ($grps as $lvl => $grp) {\r\n		$selected = in_array($lvl, $sels) ? \' selected=\"selected\"\' : \'\';\r\n		$ulist[] = \'<option value=\"\'.$lvl.\'\"\'.$selected.\'>\' . htmlspecialchars($grp) . \'</option>\';\r\n	}\r\n	$ulist[] = \'</select>\';\r\n\r\n	return join(n, $ulist);\r\n}\r\n\r\n// ------------------------\r\n// Mini diagnostics to see if the kindlegen program can be run on this host.\r\nfunction smd_ebook_test() {\r\n	global $smd_ebook_event, $smd_ebook_prefs;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	$out = \'\';\r\n	$kgen = get_pref(\'smd_ebook_kindlegen_path\', $smd_ebook_prefs[\'smd_ebook_kindlegen_path\'][\'default\']);\r\n	exec($kgen, $output, $retval);\r\n\r\n	if ($retval != 0) {\r\n		switch ($retval) {\r\n			case 126:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_permissions_issue\');\r\n			break;\r\n			case 127:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_not_found\');\r\n			break;\r\n			default:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_error_code\', array(\'{code}\' => $retval));\r\n			break;\r\n		}\r\n		$out = print_r($output, true);\r\n	} else {\r\n		$out = smd_ebook_gTxt(\'smd_ebook_ok\');\r\n	}\r\n	$_POST[\'smd_ebook_test_output\'] = $out;\r\n	$msg = smd_ebook_gTxt(\'smd_ebook_test_complete\');\r\n	smd_ebook_prefs($msg);\r\n}\r\n\r\n// ------------------------\r\n// Handle the prefs panel\r\nfunction smd_ebook_prefs($msg=\'\') {\r\n	global $smd_ebook_event, $smd_ebook_prefs, $smd_ebook_styles, $step;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	if (ps(\'smd_ebook_pref_save\')) {\r\n		foreach ($smd_ebook_prefs as $idx => $prefobj) {\r\n			$val = ps($idx);\r\n			$val = (is_array($val)) ? join(\', \', $val) : $val;\r\n			set_pref($idx, doSlash($val), \'smd_ebook\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n		}\r\n\r\n		$msg = gTxt(\'preferences_saved\');\r\n	}\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'prf\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	// Inject styles\r\n	echo \'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n\r\n	echo <<<EOJS\r\n<script type=\"text/javascript\">\r\njQuery(function() {\r\n	jQuery(\"select[name=\'smd_ebook_fld_title\'], select[name=\'smd_ebook_fld_chaptitle\'], select[name=\'smd_ebook_fld_author\'], select[name=\'smd_ebook_fld_description\'], select[name=\'smd_ebook_fld_authornote\'], select[name=\'smd_ebook_fld_subject\'], select[name=\'smd_ebook_fld_publisher\'], select[name=\'smd_ebook_fld_srp\']\").change(function() {\r\n		var xtra = jQuery(this).attr(\'name\') + \'_fixed\';\r\n		if (jQuery(\'option:selected\', this).val() == \'SMD_FIXED\') {\r\n			jQuery(\"input[name=\'\"+xtra+\"\']\").parent().parent().show(\'normal\');\r\n		} else {\r\n			jQuery(\"input[name=\'\"+xtra+\"\']\").parent().parent().hide(\'fast\');\r\n		}\r\n	}).change();\r\n});\r\n</script>\r\nEOJS;\r\n	echo \'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	$out = array();\r\n	$out[] = n.\'<div class=\"txp-list\">\';\r\n	$out[] = \'<form name=\"smd_ebook_prefs\" id=\"smd_ebook_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_ebook_event).sInput(\'smd_ebook_prefs\');\r\n	$out[] = startTable(\'list\');\r\n	$last_grp = \'\';\r\n	foreach ($smd_ebook_prefs as $idx => $prefobj) {\r\n		if ($last_grp != $prefobj[\'group\']) {\r\n			$out[] = tr(tdcs(strong(smd_ebook_gTxt($prefobj[\'group\'])), 2));\r\n		}\r\n		$last_grp = $prefobj[\'group\'];\r\n		$subout = array();\r\n		$subout[] = tda(\'<label for=\"\'.$idx.\'\">\'.smd_ebook_gTxt($idx).\'</label>\', \' class=\"noline\" style=\"text-align: right; vertical-align: middle;\"\');\r\n		$val = get_pref($idx, $prefobj[\'default\'], 1);\r\n		$vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'smd_hidden\' : \'\';\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = tda(fInput(\'text\', $idx, $val, \'\', \'\', \'\', \'\', \'\', $idx),\' class=\"noline\"\');\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = tda(yesnoRadio($idx, $val),\' class=\"noline\"\');\r\n			break;\r\n			case \'radioset\':\r\n				$subout[] = tda(radioSet($prefobj[\'content\'], $idx, $val),\' class=\"noline\"\');\r\n			break;\r\n			case \'checkboxset\':\r\n				$vals = do_list($val);\r\n				$lclout = array();\r\n				foreach ($prefobj[\'content\'] as $cb => $val) {\r\n					$checked = in_array($cb, $vals);\r\n					$lclout[] = checkbox($idx.\'[]\', $cb, $checked). smd_ebook_gTxt($val);\r\n				}\r\n				$subout[] = tda(join(n, $lclout),\' class=\"noline\"\');\r\n			break;\r\n			case \'selectlist\':\r\n				$subout[] = tda(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]),\' class=\"noline\"\');\r\n			break;\r\n			default:\r\n				if ( strpos($prefobj[\'html\'], \'smd_ebook_\') !== false && is_callable($prefobj[\'html\']) ) {\r\n					$subout[] = tda($prefobj[\'html\']($idx, $val),\' class=\"noline\"\');\r\n				}\r\n			break;\r\n		}\r\n		$out[] = tr(join(n ,$subout), \' class=\"\'.$vis.\'\"\');\r\n	}\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda($btnTst, \' class=\"noline\"\'));\r\n\r\n	if ($step == \'smd_ebook_test\') {\r\n		$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(text_area(\'smd_ebook_test_results\', 150, 200, ps(\'smd_ebook_test_output\')), \' class=\"noline\"\'));\r\n	}\r\n\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(fInput(\'submit\', \'smd_ebook_pref_save\', gTxt(\'save\'), \'publish\'), \' class=\"noline\"\'));\r\n	$out[] = endTable().tInput();\r\n	$out[] = \'</form></div>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// ------------------------\r\n// Delete plugin prefs\r\nfunction smd_ebook_prefs_remove($showpane=1) {\r\n	$message = \'\';\r\n\r\n	safe_delete(\'txp_prefs\', \"name like \'smd_ebook_%\'\");\r\n\r\n	if ($showpane) {\r\n		$message = smd_ebook_gTxt(\'smd_ebook_prefs_deleted\');\r\n		smd_ebook($message);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Set up the global prefs for the plugin\r\nfunction smd_ebook_get_prefs() {\r\n	global $smd_ebook_prefs, $prefs;\r\n\r\n	$smd_ebook_prefs = array(\r\n		\'smd_ebook_uid\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_page_break\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'~~~~~\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_toc_wraptag\' => array(\r\n			\'html\'     => \'radioset\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'content\'  => array(\'ul\' => smd_ebook_gTxt(\'smd_ebook_lbl_ul\'), \'ol\' => smd_ebook_gTxt(\'smd_ebook_lbl_ol\')),\r\n			\'default\'  => \'ul\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_toc_class\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'smd_ebook_toc\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_section\' => array(\r\n			\'html\'     => \'smd_ebook_section_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 70,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_stylesheet\' => array(\r\n			\'html\'     => \'smd_ebook_style_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 80,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_textile\' => array(\r\n			\'html\'     => \'checkboxset\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 90,\r\n			\'content\'  => array(\'description\' => \'smd_ebook_lbl_description\', \'authornote\' => \'smd_ebook_lbl_authornote\'),\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_encoding\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 100,\r\n			\'default\'  => \'utf-8\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_kindlegen_path\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 200,\r\n			\'default\'  => $prefs[\'path_to_site\'].DS.\'kindle\'.DS.\'kindlegen\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_auto_toc\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'1\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_title\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'Title\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_title_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 25,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_chaptitle\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_chaptitle_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 35,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_author\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_author\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'default\'  => \'AuthorID\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_author_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 45,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_description\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 50,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_description_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 55,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_authornote\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_authornote_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 65,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_subject\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_plus\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 70,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_subject_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 75,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_publisher\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_plus\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 80,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_publisher_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 85,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_srp\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 90,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_srp_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 95,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_currency\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 100,\r\n			\'default\'  => \'EUR\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_heading_level\' => array(\r\n			\'html\'     => \'smd_ebook_number\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 110,\r\n			\'default\'  => \'2\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_file_cat\' => array(\r\n			\'html\'     => \'smd_ebook_file_cat_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 120,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_privs\' => array(\r\n			\'html\'     => \'smd_ebook_priv_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_usrset\',\r\n		),\r\n		\'smd_ebook_opf_edit\' => array(\r\n			\'html\'     => \'smd_ebook_priv_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_usrset\',\r\n		),\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'en-gb\' => array(\r\n			\'smd_ebook_auto_toc\'              => \'Automatically create ToC anchors on headings\',\r\n			\'smd_ebook_currency\'              => \'Default three-letter currency code\',\r\n			\'smd_ebook_deleted\'               => \'Temporary files deleted\',\r\n			\'smd_ebook_encoding\'              => \'Character set of document\',\r\n			\'smd_ebook_error_code\'            => \'Status code {code} returned. Ensure the program was uploaded as Binary\',\r\n			\'smd_ebook_file_cat\'              => \'Store files in category\',\r\n			\'smd_ebook_filed\'                 => \'E-book filed as ID {id}\',\r\n			\'smd_ebook_fixed\'                 => \'Static text\',\r\n			\'smd_ebook_fld_author\'            => \'Get author from field\',\r\n			\'smd_ebook_fld_author_fixed\'      => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_authornote\'        => \'Get author notes from field\',\r\n			\'smd_ebook_fld_authornote_fixed\'  => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_chaptitle\'         => \'Get chapter titles from field\',\r\n			\'smd_ebook_fld_chaptitle_fixed\'   => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_description\'       => \'Get description from field\',\r\n			\'smd_ebook_fld_description_fixed\' => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_publisher\'         => \'Get publisher from field\',\r\n			\'smd_ebook_fld_publisher_fixed\'   => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_srp\'               => \'Get SRP (price) from field\',\r\n			\'smd_ebook_fld_srp_fixed\'         => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_subject\'           => \'Get subject (genre) from field\',\r\n			\'smd_ebook_fld_subject_fixed\'     => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_title\'             => \'Get book title from field\',\r\n			\'smd_ebook_fld_title_fixed\'       => \'&lfloor;_ Text\',\r\n			\'smd_ebook_from\'                  => \'From\',\r\n			\'smd_ebook_heading_level\'         => \'Chapter heading level\',\r\n			\'smd_ebook_generate_failed\'       => \'E-book generation failed (exit status {code})\',\r\n			\'smd_ebook_generate_ok\'           => \'E-book generation successful\',\r\n			\'smd_ebook_kindlegen_path\'        => \'Path to kindlegen executable\',\r\n			\'smd_ebook_lbl_articles\'          => \'Choose book article(s)\',\r\n			\'smd_ebook_lbl_author\'            => \'Author\',\r\n			\'smd_ebook_lbl_authornote\'        => \'Author notes\',\r\n			\'smd_ebook_lbl_chaptitle\'         => \'Chapter titles\',\r\n			\'smd_ebook_lbl_cln\'               => \'Tidy up\',\r\n			\'smd_ebook_lbl_create\'            => \'Create\',\r\n			\'smd_ebook_lbl_description\'       => \'Book description\',\r\n			\'smd_ebook_lbl_download\'          => \'Download\',\r\n			\'smd_ebook_lbl_files\'             => \'File manager\',\r\n			\'smd_ebook_lbl_generate\'          => \'(Re)generate\',\r\n			\'smd_ebook_lbl_mgr\'               => \'E-book\',\r\n			\'smd_ebook_lbl_ol\'                => \'Numeric list\',\r\n			\'smd_ebook_lbl_prf\'               => \'Settings\',\r\n			\'smd_ebook_lbl_publisher\'         => \'Publisher\',\r\n			\'smd_ebook_lbl_report\'            => \'Build report\',\r\n			\'smd_ebook_lbl_srp\'               => \'Price|Currency\',\r\n			\'smd_ebook_lbl_start\'             => \'Welcome\',\r\n			\'smd_ebook_lbl_subject\'           => \'Subject (genre)\',\r\n			\'smd_ebook_lbl_title\'             => \'Book title\',\r\n			\'smd_ebook_lbl_to_files\'          => \'Store file\',\r\n			\'smd_ebook_lbl_tst\'               => \'Test kindlegen program\',\r\n			\'smd_ebook_lbl_ul\'                => \'Standard list\',\r\n			\'smd_ebook_lbl_view\'              => \'[Preview]\',\r\n			\'smd_ebook_malformed\'             => \'Could not process HTML from {file}. Malformed?\',\r\n			\'smd_ebook_no_files\'              => \'No e-book files found\',\r\n			\'smd_ebook_not_filed\'             => \'E-book NOT filed\',\r\n			\'smd_ebook_not_found\'             => \'File not found. Check path?\',\r\n			\'smd_ebook_ok\'                    => \'Everything looks OK\',\r\n			\'smd_ebook_opf_edit\'              => \'Groups that can edit .opf\',\r\n			\'smd_ebook_page_break\'            => \'Page break character sequence\',\r\n			\'smd_ebook_permissions_issue\'     => \'Permissions problem. Is the file executable?\',\r\n			\'smd_ebook_prefs_deleted\'         => \'Settings deleted\',\r\n			\'smd_ebook_preview_prefix\'        => \'Preview of\',\r\n			\'smd_ebook_privs\'                 => \'Groups that can publish\',\r\n			\'smd_ebook_pubset\'                => \'Publishing\',\r\n			\'smd_ebook_stylesheet\'            => \'Stylesheet to include with the book\',\r\n			\'smd_ebook_section\'               => \'List articles from section\',\r\n			\'smd_ebook_settings\'              => \'Plugin configuration\',\r\n			\'smd_ebook_tab_name\'              => \'E-books\',\r\n			\'smd_ebook_test_complete\'         => \'Test complete\',\r\n			\'smd_ebook_textile\'               => \'Apply Textile to\',\r\n			\'smd_ebook_tidy\'                  => \'Tidy up temporary e-book files\',\r\n			\'smd_ebook_toc\'                   => \'Table of Contents\',\r\n			\'smd_ebook_toc_class\'             => \'ToC CSS class name\',\r\n			\'smd_ebook_toc_wraptag\'           => \'Render ToC as\',\r\n			\'smd_ebook_uid\'                   => \'Unique ID\',\r\n			\'smd_ebook_updated\'               => \'E-book info for file ID {id} updated\',\r\n			\'smd_ebook_usrset\'                => \'Rights\',\r\n		),\r\n	);\r\n\r\n	$thislang = get_pref(\'language\', \'en-gb\');\r\n	$exists = (isset($lang[$thislang][$what]));\r\n	$thislang = $exists ? $thislang : \'en-gb\';\r\n	return ($exists) ? strtr($lang[$thislang][$what], $atts) : $what;\r\n}', '/**\r\n * smd_ebook\r\n *\r\n * A Textpattern CMS plugin for creating e-books (Kindle) from Txp content\r\n *  -> Content can be in one article or across many\r\n *  -> Article image of one of the articles is used as cover art\r\n *  -> Automatic TOC generation and page breaks from Textiled markup\r\n *  -> Support for book description\r\n *\r\n * @author Stef Dawson\r\n * @link   http://stefdawson.com/\r\n */\r\n\r\nglobal $smd_ebook_prefs;\r\nsmd_ebook_get_prefs();\r\n\r\nif(@txpinterface == \'admin\') {\r\n	global $smd_ebook_event, $smd_ebook_styles;\r\n	$smd_ebook_event = \'smd_ebook\';\r\n\r\n	$smd_ebook_styles = array(\r\n		\'cpanel\' =>\r\n         \'.smd_hidden { display:none; }\r\n         .smd_active { font-weight:bold; }\r\n         .smd_clear { clear:both; }\r\n         .smd_preselected { opacity:0.6; font-style:italic; }\r\n         .smd_selected { border-top:solid #444; border-bottom:solid #444; }\r\n         .smd_important { color:red; }\r\n         .smd_inline { display:inline; }\r\n         .txp-container, .txp-control-panel { text-align:center; }\r\n         #smd_ebook_preview { display:none; position:absolute; top:1em; left:1em; margin:0 auto; text-align:left; border:2px ridge #999; background:#ececec; max-width:640px; min-width:300px; box-shadow: 8px 8px 15px #b9b9b9; }\r\n         #smd_ebook_preview_close { float:right; cursor:pointer; margin-left:1em;}\r\n         #smd_ebook_preview_content { padding:1em; }\r\n         #smd_ebook_preview_titlebar { padding:5px; border-bottom:1px solid black; font-size:120%; background:#ccc;;}\r\n         #smd_ebook_form { margin:0 auto; width:80%; }\r\n         #smd_ebook_form label, #smd_ebook_create { display:block; }\r\n         #smd_ebook_prefs input[type=\"text\"] { width:250px }\r\n         .smd_ebook_manager { position:relative; margin:0 auto; width:80% }\r\n         .smd_ebook_buttons { display:inline-block; margin:0 2em; }\r\n         .smd_ebook_report textarea { width:80%; }\r\n         #smd_ebook_editor { display:block; width:75%}\r\n         .smd_ebook_files { float:left; width:25% }\r\n         .smd_ebook_mobi_options { text-align:right; }\r\n         .smd_ebook_entity { float:left; margin:1em; }\r\n         #smd_ebook_form label, .smd_ebook_report, .smd_ebook_manager { margin-top:1.5em; }\r\n         .smd_ebook_file { display:block; line-height:1.5; }\',\r\n	);\r\n\r\n	$pub_prv = get_pref(\'smd_ebook_privs\', $smd_ebook_prefs[\'smd_ebook_privs\'][\'default\']);\r\n	add_privs($smd_ebook_event, \'1\'. (($pub_prv) ? \',\'.$pub_prv: \'\') );\r\n	add_privs(\'plugin_prefs.\'.$smd_ebook_event, \'1\');\r\n\r\n	register_tab(\'content\', $smd_ebook_event, smd_ebook_gTxt(\'smd_ebook_tab_name\'));\r\n	register_callback(\'smd_ebook_dispatcher\', $smd_ebook_event);\r\n	register_callback(\'smd_ebook_dispatcher\', \'plugin_prefs.\'.$smd_ebook_event);\r\n	register_callback(\'smd_ebook_welcome\', \'plugin_lifecycle.\'.$smd_ebook_event);\r\n}\r\n\r\n// ********************\r\n// ADMIN SIDE INTERFACE\r\n// ********************\r\n// Plugin jump off point\r\nfunction smd_ebook_dispatcher($evt, $stp) {\r\n	global $smd_ebook_event;\r\n\r\n	$available_steps = array(\r\n		\'smd_ebook\'               => false,\r\n		\'smd_ebook_prefs\'         => true,\r\n		\'smd_ebook_create\'        => true,\r\n		\'smd_ebook_generate\'      => true,\r\n		\'smd_ebook_loadfile\'      => true,\r\n		\'smd_ebook_savefile\'      => true,\r\n		\'smd_ebook_viewfile\'      => true,\r\n		\'smd_ebook_test\'          => true,\r\n		\'smd_ebook_tidy\'          => true,\r\n		\'save_pane_state\'         => true,\r\n	);\r\n\r\n	if ($stp == \'save_pane_state\') {\r\n		smd_ebook_save_pane_state();\r\n	} else if (!$stp or !bouncer($stp, $available_steps)) {\r\n		$stp = $smd_ebook_event;\r\n	}\r\n	$stp();\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_welcome($evt, $stp) {\r\n	$msg = \'\';\r\n	switch ($stp) {\r\n		case \'installed\':\r\n			$msg = \'Go publish!\';\r\n			break;\r\n		case \'deleted\':\r\n			smd_ebook_prefs_remove(0);\r\n			break;\r\n	}\r\n	return $msg;\r\n}\r\n\r\n// ------------------------\r\n// Stub with correct signature for being called via Txp\r\nfunction smd_ebook($evt=\'\', $stp=\'\') {\r\n	smd_ebook_ui();\r\n}\r\n\r\n// ------------------------\r\n// Interface for compiling the book\r\nfunction smd_ebook_ui($msg=\'\', $listfile=\'\', $report = \'\', $retval=\'\') {\r\n	global $smd_ebook_event, $smd_ebook_prefs, $smd_ebook_styles, $prefs;\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'mgr\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	// Inject styles\r\n	echo n.\'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n	echo n.\'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	if (!$listfile) {\r\n		// Stage 1: Gather the info and create the content\r\n\r\n		// Figure out if the various fields are coming from CFs or from user-supplied text areas\r\n		$fields = array(\r\n			\'description\' => array(\r\n				\'html\' => \'textarea\',\r\n			),\r\n			\'authornote\' => array(\r\n				\'html\' => \'textarea\',\r\n			),\r\n			\'title\' => array(\r\n				\'html\'     => \'text_input\',\r\n				\'required\' => true,\r\n			),\r\n			\'chaptitle\' => array(\r\n				\'html\'       => \'text_input\',\r\n				\'hide_empty\' => true,\r\n			),\r\n			\'author\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'publisher\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'subject\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n			\'srp\' => array(\r\n				\'html\' => \'text_input\',\r\n			),\r\n		);\r\n\r\n		$cfs = getCustomFields();\r\n\r\n		foreach ($fields as $field => $data) {\r\n			$data[\'value\'] = get_pref(\'smd_ebook_fld_\'.$field, $smd_ebook_prefs[\'smd_ebook_fld_\'.$field][\'default\']);\r\n			$data[\'column\'] = is_numeric($data[\'value\']) ? \'custom_\'.$data[\'value\'] : $data[\'value\'];\r\n			$data[\'name\'] = (is_numeric($data[\'value\']) && isset($cfs[$data[\'value\']])) ? $cfs[$data[\'value\']] : $data[\'value\'];\r\n			$data[\'content\'] = ($data[\'value\'] == \'SMD_FIXED\') ? get_pref(\'smd_ebook_fld_\'.$field.\'_fixed\', \'\') : \'\';\r\n			$data[\'required\'] = isset($data[\'required\']) ? $data[\'required\'] : false;\r\n			$data[\'hide_empty\'] = isset($data[\'hide_empty\']) ? $data[\'hide_empty\'] : false;\r\n			${\'ip_\'.$field} = \'<div class=\"smd_ebook_entity\">\' . ( ($data[\'content\'] || ($data[\'name\']==\'\' && $data[\'hide_empty\']))\r\n				? hInput(\'smd_ebook_fld_\'.$field, htmlspecialchars($data[\'content\']))\r\n				: \'<label for=\"smd_ebook_fld_\'.$field.\'\">\' . smd_ebook_gTxt(\'smd_ebook_lbl_\'.$field) . \'</label>\'\r\n					. ( ($data[\'column\'])\r\n						? hInput(\'smd_ebook_fld_\'.$field, \'SMD_FLD_\'.$data[\'column\'])\r\n							. \'<span class=\"smd_preselected\">\' . smd_ebook_gTxt(\'smd_ebook_from\').\' \'.str_replace(\'SMD_FLD_\', \'\', $data[\'name\']) . \'</span>\'\r\n						: ( ($data[\'html\'] == \'textarea\')\r\n							? text_area(\'smd_ebook_fld_\'.$field, \'150\', \'250\', \'\', \'smd_ebook_fld_\'.$field)\r\n							: fInput(\'text\', \'smd_ebook_fld_\'.$field, \'\', \'\', \'\', \'\', \'\', \'\', \'smd_ebook_fld_\'.$field, \'\', $data[\'required\'])\r\n						)\r\n					)\r\n			) . \'</div>\';\r\n		}\r\n\r\n		$where = array(\'1=1\');\r\n		$sec = get_pref(\'smd_ebook_section\', $smd_ebook_prefs[\'smd_ebook_section\'][\'default\']);\r\n		if ($sec) {\r\n			$where[] = \"Section=\'\" . doSlash($sec) . \"\'\";\r\n		}\r\n\r\n		$articles = safe_rows(\'*\', \'textpattern\', join(\' AND \', $where). \' ORDER BY url_title\');\r\n\r\n		// Build dropdown list of articles: not using selectInput() because it doesn\'t support multiples\r\n		$alist = array();\r\n		$alist[] = \'<select name=\"smd_ebook_articles[]\" id=\"smd_ebook_articles\" class=\"list multiple\" multiple=\"multiple\" size=\"12\" required=\"\">\';\r\n		foreach ($articles as $row) {\r\n			$alist[] = \'<option value=\"smd_ebook_article_\'.htmlspecialchars($row[\'ID\']).\'\">\' . htmlspecialchars($row[\'Title\']) . \'</option>\';\r\n		}\r\n		$alist[] = \'</select>\';\r\n\r\n		echo n.\'<div id=\"\'.$smd_ebook_event.\'_container\" class=\"txp-container\">\';\r\n		echo n.\'<form id=\"smd_ebook_form\" action=\"index.php\" method=\"post\">\';\r\n		echo n.\'<div class=\"smd_ebook_entity\"><label for=\"smd_ebook_articles\">\' . smd_ebook_gTxt(\'smd_ebook_lbl_articles\') . \'</label>\';\r\n		echo join(n, $alist);\r\n		echo n.\'</div>\';\r\n		echo n. $ip_description .n. $ip_authornote .n. $ip_title .n. $ip_chaptitle .n. $ip_author .n. $ip_publisher .n.$ip_subject .n. $ip_srp;\r\n		echo n.\'<div class=\"smd_clear\"></div>\';\r\n		echo n.fInput(\'submit\', \'smd_ebook_create\', smd_ebook_gTxt(\'smd_ebook_lbl_create\'), \'publish\', \'\', \'\', \'\', \'\', \'smd_ebook_create\');\r\n		echo n.eInput($smd_ebook_event);\r\n		echo n.sInput(\'smd_ebook_create\');\r\n		echo n.tInput();\r\n		echo \'</form>\';\r\n		echo \'</div>\';\r\n\r\n	} else {\r\n		// Stage 2: Edit the content and generate the kindle file\r\n		$titlePrefix = smd_ebook_gTxt(\'smd_ebook_preview_prefix\');\r\n\r\n		$qs = array(\r\n			\"event\" => $smd_ebook_event,\r\n		);\r\n		$qsVars = \"index.php\".join_qs($qs);\r\n\r\n		echo <<<EOJS\r\n<script type=\"text/javascript\">\r\nvar smd_ebook_currfile;\r\n\r\njQuery(function() {\r\n	// Load a file into the editor\r\n	jQuery(\'.smd_ebook_files .smd_ebook_file\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var me = jQuery(this);\r\n		var name = me.text();\r\n		smd_ebook_currfile = name;\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_loadfile\',\r\n				name: name,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				jQuery(\'.smd_ebook_files .smd_ebook_file\').removeClass(\'smd_selected\');\r\n				me.toggleClass(\'smd_selected\');\r\n				jQuery(\'#smd_ebook_editor\').val(jQuery(data).find(\'smd_ebook_filedata\').attr(\'value\'));\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	// Save the current file back to the file system\r\n	jQuery(\'.smd_ebook_filesave\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var content = jQuery(\'#smd_ebook_editor\').val();\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_savefile\',\r\n				name: smd_ebook_currfile,\r\n				data: content,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				jQuery(\'.smd_ebook_files .smd_ebook_file\').removeClass(\'smd_selected\');\r\n				jQuery(\'#smd_ebook_editor\').val(\'\');\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	// Preview an html file in its own popup window\r\n	var smd_ebook_previewing = 0;\r\n	jQuery(\'.smd_ebook_files .smd_ebook_view\').click(function(ev) {\r\n		ev.preventDefault();\r\n\r\n		// Spinner and user feedback\r\n		var form = jQuery(this).closest(\'form\');\r\n		form.addClass(\'busy\');\r\n		s = jQuery(ev.currentTarget);\r\n		s.after(\'<span class=\"spinner\"></span>\')\r\n\r\n		var me = jQuery(this).prev();\r\n		var name = me.text();\r\n\r\n		jQuery.post(\'{$qsVars}\',\r\n			{\r\n				step: \'smd_ebook_viewfile\',\r\n				name: name,\r\n				_txp_token: textpattern._txp_token\r\n			},\r\n			function(data) {\r\n				// Grab body text and inject it into the preview container\r\n				jQuery(\'#smd_ebook_preview_content\').empty().append(jQuery(data).find(\'smd_ebook_filedata\').attr(\'value\'));\r\n				jQuery(\'#smd_ebook_preview_title\').text(\'{$titlePrefix} \' + name);\r\n				if ((jQuery(ev.target).hasClass(\'smd_ebook_view\')) && !smd_ebook_previewing) {\r\n					smd_ebook_prevu();\r\n				}\r\n				form.removeClass(\'busy\');\r\n				jQuery(\'span.spinner\').remove();\r\n			}\r\n		);\r\n	});\r\n\r\n	function smd_ebook_prevu() {\r\n		jQuery(\'#smd_ebook_preview\').toggle(\'fast\');\r\n		smd_ebook_previewing = !smd_ebook_previewing;\r\n	}\r\n\r\n	function smd_ebook_prevu_bind() {\r\n		jQuery(\'#smd_ebook_preview_close\').click(function(){\r\n			smd_ebook_prevu();\r\n		});\r\n		jQuery(document).keypress(function(e){\r\n			if(e.keyCode==27 && smd_ebook_previewing) {\r\n				smd_ebook_prevu();\r\n			}\r\n		});\r\n	}\r\n	smd_ebook_prevu_bind();\r\n});\r\n</script>\r\nEOJS;\r\n		echo n.\'<div id=\"\'.$smd_ebook_event.\'_container\" class=\"txp-container\">\';\r\n		echo n.\'<form id=\"smd_ebook_form\" action=\"index.php\" method=\"post\">\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_report\">\'\r\n			. hed(smd_ebook_gTxt(\'smd_ebook_lbl_report\'), 2)\r\n			. \'<textarea id=\"smd_ebook_report\" cols=80 rows=\"6\">\'.$report.\'</textarea>\'\r\n			. \'</div>\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_manager\">\';\r\n\r\n		// \'Generate book\' and \'download\' buttons\r\n		echo \'<div class=\"smd_ebook_mobi_options\">\';\r\n		echo n.fInput(\'submit\', \'smd_ebook_generate\', smd_ebook_gTxt(\'smd_ebook_lbl_generate\'), \'publish smd_ebook_mobi\');\r\n		if ($retval <= 1) {\r\n			$info = explode (\'.\',$listfile);\r\n			$basepart = array_slice($info, 0, count($info)-1);\r\n			$mobifile = join(\'\', $basepart) . \'.mobi\';\r\n			echo n.hInput(\'smd_ebook_mobifile\', $mobifile);\r\n			echo n.fInput(\'submit\', \'smd_ebook_to_files\', smd_ebook_gTxt(\'smd_ebook_lbl_to_files\'), \'publish smd_ebook_mobi\');;\r\n			echo n.fInput(\'submit\', \'smd_ebook_download\', smd_ebook_gTxt(\'smd_ebook_lbl_download\'), \'publish smd_ebook_mobi\');;\r\n		}\r\n		echo \'</div>\';\r\n\r\n		echo n.hed(smd_ebook_gTxt(\'smd_ebook_lbl_files\'), 2);\r\n		echo n.\'<div class=\"smd_ebook_files\">\';\r\n\r\n		$opf_edit = get_pref(\'smd_ebook_opf_edit\', $smd_ebook_prefs[\'smd_ebook_opf_edit\'][\'default\']);\r\n		$opf_allowed = do_list($opf_edit);\r\n		$opf_allowed[] = \'1\'; // Publishers can always edit .opf\r\n		$can_opf = in_array($GLOBALS[\'privs\'], $opf_allowed);\r\n\r\n		$files = file($prefs[\'tempdir\'] . DS . $listfile);\r\n\r\n		foreach ($files as $file) {\r\n			$info = explode (\'.\',$file);\r\n			$lastpart = count($info)-1;\r\n			$ext = trim($info[$lastpart]);\r\n			if ($ext == \'opf\') {\r\n				echo n.hInput(\'smd_ebook_opf_file\', $file);\r\n			}\r\n			if ($ext != \'opf\' || ($ext == \'opf\' && $can_opf)) {\r\n				echo n.\'<a href=\"#\" class=\"smd_ebook_file\">\'.trim($file).\'</a>\';\r\n				if ($ext == \'html\') {\r\n					echo n.\'<a href=\"#\" class=\"smd_ebook_view\">\'.smd_ebook_gTxt(\'smd_ebook_lbl_view\').\'</a>\';\r\n				}\r\n			}\r\n		}\r\n\r\n		echo n.\'<button class=\"smd_ebook_filesave smallerbox\">\'.gTxt(\'save\').\'</button>\';\r\n		echo n.\'</div>\';\r\n\r\n		echo n.\'<div class=\"smd_ebook_editor\">\';\r\n		echo n.\'<textarea id=\"smd_ebook_editor\" cols=\"60\" rows=\"25\"></textarea>\';\r\n		echo n.\'</div>\';\r\n		echo n.\'<div id=\"smd_ebook_preview\">\r\n			<div id=\"smd_ebook_preview_titlebar\">\r\n				<span id=\"smd_ebook_preview_title\"></span>\r\n				<a id=\"smd_ebook_preview_close\">X</a>\r\n			</div>\r\n			<div id=\"smd_ebook_preview_content\"></div>\r\n			</div>\';\r\n\r\n		echo n.hInput(\'smd_ebook_listfile\', $listfile);\r\n		echo n.eInput($smd_ebook_event);\r\n		echo n.sInput(\'smd_ebook_generate\');\r\n		echo n.tInput();\r\n		echo n.\'</div>\';\r\n		echo n.\'</form>\';\r\n		echo n.\'<div class=\"smd_clear\"></div>\';\r\n		echo n.\'</div>\';\r\n	}\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_loadfile() {\r\n	global $prefs;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$file = file($prefs[\'tempdir\'] . DS . $name);\r\n	if ($file) {\r\n		send_xml_response(array(\'smd_ebook_filedata\' => str_replace(array(\"\'\"), array(\'&#039;\'), join(\'\', $file)) ));\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_savefile() {\r\n	global $prefs;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$content = ps(\'data\');\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $name, \"wb\");\r\n	fwrite($fp, trim($content));\r\n	fclose($fp);\r\n	if ($fp) {\r\n		send_xml_response();\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\n// Extract a subset of the HTML file for display\r\nfunction smd_ebook_viewfile() {\r\n	global $prefs, $path_to_site;\r\n\r\n	$name = sanitizeForFile(ps(\'name\'));\r\n	$file = file_get_contents($prefs[\'tempdir\'] . DS . $name);\r\n\r\n	if ($file) {\r\n		$doc = new DOMDocument();\r\n		$doc->loadHTML($file);\r\n		$domxpath = new DOMXpath($doc);\r\n		$newDoc = new DOMDocument(\'1.0\',\'UTF-8\');\r\n\r\n		$nodeStyle = $domxpath->query(\'//style\');\r\n		$nodeList = $domxpath->query(\'//body\');\r\n\r\n		// Create a new document and import the document subsets\r\n		$newDoc->appendChild($newDoc->importNode($nodeStyle->item(0), true));\r\n		$newDoc->appendChild($newDoc->importNode($nodeList->item(0), true));\r\n		$out = $newDoc->saveHTML();\r\n\r\n		send_xml_response(array(\'smd_ebook_filedata\' => str_replace(array(\"\'\", $path_to_site.DS), array(\'&#039;\', ihu), $out) ));\r\n	} else {\r\n		send_xml_response(array(\'http-status\' => \'400 Bad Request\'));\r\n	}\r\n	exit; // Don\'t display page_end\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_templates() {\r\n	// .opf file template\r\n	$template[\'opf\'] = <<<EOOPF\r\n<?xml version=\"1.0\" encoding=\"{smd_ebook_encoding}\"?>\r\n<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"2.0\" {smd_ebook_uid_ref}>\r\n	<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:opf=\"http://www.idpf.org/2007/opf\">\r\n		{smd_ebook_md_uid}\r\n		{smd_ebook_md_title}\r\n		{smd_ebook_md_lang}\r\n		{smd_ebook_md_creator}\r\n		{smd_ebook_md_date}\r\n		{smd_ebook_md_description}\r\n		{smd_ebook_md_subject}\r\n		{smd_ebook_md_publisher}\r\n		{smd_ebook_md_cover}\r\n		<x-metadata>\r\n			<output encoding=\"{smd_ebook_encoding}\" content-type=\"text/x-oeb1-document\"></output>\r\n			{smd_ebook_md_srp}\r\n		</x-metadata>\r\n	</metadata>\r\n\r\n	<manifest>\r\n		{smd_ebook_manifest_ncx}\r\n		{smd_ebook_manifest_cover}\r\n		{smd_ebook_manifest_authornote}\r\n		{smd_ebook_manifest_toc}\r\n		{smd_ebook_manifest_items}\r\n	</manifest>\r\n\r\n	<spine {smd_ebook_spine_ncx_ref}>\r\n		{smd_ebook_spine_ncx}\r\n		{smd_ebook_spine_authornote}\r\n		{smd_ebook_spine_toc}\r\n		{smd_ebook_spine_items}\r\n	</spine>\r\n\r\n	<guide>\r\n		{smd_ebook_guide_toc}\r\n		{smd_ebook_guide_start}\r\n	</guide>\r\n</package>\r\nEOOPF;\r\n\r\n	// .ncx file template\r\n	$template[\'ncx\'] = <<<EONCX\r\n<?xml version=\"1.0\" encoding=\"{smd_ebook_encoding}\"?>\r\n<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"\r\n\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\r\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\" xml:lang=\"{smd_ebook_lang}\">\r\n	<head>\r\n		{smd_ebook_dtb_uid}\r\n	</head>\r\n\r\n	<docTitle><text>{smd_ebook_title}</text></docTitle>\r\n	<docAuthor><text>{smd_ebook_creator}</text></docAuthor>\r\n	<navMap>\r\n		{smd_ebook_ncx_map}\r\n	</navMap>\r\n</ncx>\r\nEONCX;\r\n\r\n	// navPoint template (a portion of the .ncx file)\r\n	$template[\'nav\'] = <<<EONAV\r\n		<navPoint class=\"titlepage\" id=\"{smd_ebook_nav_hash}\" playOrder=\"{smd_ebook_nav_idx}\">\r\n			<navLabel><text>{smd_ebook_nav_label}</text></navLabel>\r\n			<content src=\"{smd_ebook_file_name}#{smd_ebook_nav_hash}\" />\r\n		</navPoint>\r\nEONAV;\r\n\r\n	// TOC template\r\n	$template[\'toc\'] = <<<EOTOC\r\n<html>\r\n<head>\r\n	<title>Table of Contents</title>\r\n	{smd_ebook_stylesheet}\r\n</head>\r\n<body>\r\n	{smd_ebook_toc_list}\r\n</body>\r\n</html>\r\nEOTOC;\r\n\r\n	// HTML template\r\n	$template[\'doc\'] = <<<EOTOC\r\n<html>\r\n<head>\r\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset={smd_ebook_encoding}\" />\r\n	<title>{smd_ebook_title}</title>\r\n	{smd_ebook_stylesheet}\r\n</head>\r\n<body>\r\n	{smd_ebook_chaptitle}\r\n	{smd_ebook_contents}\r\n</body>\r\n</html>\r\nEOTOC;\r\n\r\n	return $template;\r\n}\r\n\r\n// ------------------------\r\n// Stage 1: create the files necessary for generation of the book.\r\n// The actual generation via kindlegen is a separate step.\r\nfunction smd_ebook_create() {\r\n	global $smd_ebook_prefs, $prefs, $img_dir;\r\n\r\n	@include_once txpath.\'/lib/classTextile.php\';\r\n	@include_once txpath.\'/publish.php\'; // for parse()\r\n	$textile = new Textile();\r\n\r\n	$template = smd_ebook_templates();\r\n	$msg = \'\';\r\n	$report = $toc = $ncx = $reps = array();\r\n\r\n	// Get Textile and encoding options\r\n	$encoding = get_pref(\'smd_ebook_encoding\', $smd_ebook_prefs[\'smd_ebook_encoding\'][\'default\']);\r\n	$which = get_pref(\'smd_ebook_textile\', $smd_ebook_prefs[\'smd_ebook_textile\'][\'default\']);\r\n	$txt_description = in_list(\'description\', $which);\r\n	$txt_authornote = in_list(\'authornote\', $which);\r\n\r\n	// Build up a giant replacement table which is then substituted into\r\n	// the various templates before passing to kindlegen\r\n\r\n	// Populate the unique ID entries direcly into the .opf template as they\'re only used once each\r\n	$uid = get_pref(\'smd_ebook_uid\', $smd_ebook_prefs[\'smd_ebook_uid\'][\'default\']);\r\n\r\n	$template[\'opf\'] = str_replace(\'{smd_ebook_uid_ref}\', (($uid) ? \'unique-identifier=\"uid\"\' : \'\'), $template[\'opf\']);\r\n	$template[\'opf\'] = str_replace(\'{smd_ebook_md_uid}\', (($uid) ? \'<dc:identifier id=\"uid\">\' . $uid . \'</dc:identifier>\' : \'\'), $template[\'opf\']);\r\n	$template[\'ncx\'] = str_replace(\'{smd_ebook_dtb_uid}\', (($uid) ? \'<meta name=\"dtb:uid\" content=\"uid\"/>\' : \'\'), $template[\'ncx\']);\r\n\r\n	// Set up the TOC wrappers\r\n	$toc_wrap = get_pref(\'smd_ebook_toc_wraptag\', $smd_ebook_prefs[\'smd_ebook_toc_wraptag\'][\'default\']);\r\n	$toc_class = get_pref(\'smd_ebook_toc_class\', $smd_ebook_prefs[\'smd_ebook_toc_class\'][\'default\']);\r\n	$wrapit = ($toc_wrap == \'ol\') ? \'#\' : \'*\';\r\n\r\n	$article_cnt = $ncx_cnt = $elem_cnt = 0;\r\n	$article_refs = $article_spines = array();\r\n\r\n	// Page break, stylesheet and heading references\r\n	$pbr = get_pref(\'smd_ebook_page_break\', $smd_ebook_prefs[\'smd_ebook_page_break\'][\'default\']);\r\n	$css = get_pref(\'smd_ebook_stylesheet\', $smd_ebook_prefs[\'smd_ebook_stylesheet\'][\'default\']);\r\n	$hdg = get_pref(\'smd_ebook_heading_level\', $smd_ebook_prefs[\'smd_ebook_heading_level\'][\'default\']);\r\n\r\n	if ($css) {\r\n		$sheet = safe_field(\'css\', \'txp_css\', \"name=\'\" . doSlash($css) . \"\'\");\r\n	}\r\n	$sheet = ($sheet) ? \'<style type=\"text/css\">\' . $sheet . \'</style>\' : \'\';\r\n\r\n\r\n	// Loop for each article in the collection\r\n	foreach (ps(\'smd_ebook_articles\') as $artid) {\r\n		$article_cnt++;\r\n\r\n		$id = str_replace(\'smd_ebook_article_\', \'\', $artid);\r\n		$row = safe_row(\'*\', \'textpattern\', \"ID = \'\" . doSlash($id) . \"\'\");\r\n\r\n		if ($row) {\r\n			// Initialize a few things\r\n			$note_content = \'\';\r\n			$reps[\'{smd_ebook_file_name}\'] = $row[\'url_title\'] . \'.html\';\r\n			$reps[\'{smd_ebook_encoding}\'] = $encoding;\r\n			$cur_file = $row[\'url_title\'] . \'.html\';\r\n\r\n			// Each of the items starting !isset() are only ever loaded _once_ from the\r\n			// first article in which they are found.\r\n			// Begin by setting up the file names\r\n			if (!isset($firstfile)) {\r\n				$firstfile = $row[\'url_title\'] . \'.html\';\r\n				$listfile = $row[\'url_title\'] . \'.smd\';\r\n				$notefile = $row[\'url_title\'] . \'_notes.html\';\r\n				$toc_file = $row[\'url_title\'] . \'_toc.html\';\r\n				$ncx_file = $row[\'url_title\'] . \'.ncx\';\r\n				$opf_file = $row[\'url_title\'] . \'.opf\';\r\n			}\r\n			// Language\r\n			if (!isset($reps[\'{smd_ebook_md_lang}\'])) {\r\n				$reps[\'{smd_ebook_md_lang}\'] = \'<dc:language>\'.$prefs[\'language\'].\'</dc:language>\';\r\n				$reps[\'{smd_ebook_lang}\'] = $prefs[\'language\'];\r\n			}\r\n			// Author can come from:\r\n			//  1) an article field\r\n			//  2) the current logged in user\r\n			//  3) user-supplied at book creation time\r\n			//  4) hard-coded in plugin settings\r\n			if (!isset($reps[\'{smd_ebook_md_creator}\'])) {\r\n				$val = ps(\'smd_ebook_fld_author\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? ( ($valfld == \'AuthorID\') ? get_author_name($row[$valfld]) : $row[$valfld] ) : \'\';\r\n				}\r\n				if ($val) {\r\n					$reps[\'{smd_ebook_md_creator}\'] = \'<dc:creator>\'.$val.\'</dc:creator>\';\r\n					$reps[\'{smd_ebook_creator}\'] = $val;\r\n				}\r\n			}\r\n			// Publication date\r\n			// TODO: reformat?\r\n			if (!isset($reps[\'{smd_ebook_md_date}\'])) {\r\n				$reps[\'{smd_ebook_md_date}\'] = \'<dc:date>\'.($row[\'Posted\']).\'</dc:date>\';\r\n			}\r\n\r\n			// Cover image\r\n			if (!isset($reps[\'{smd_ebook_md_cover}\'])) {\r\n				if (isset($row[\'Image\'])) {\r\n					$img = safe_row(\'*\', \'txp_image\', \"id=\'\" . intval($row[\'Image\']) . \"\'\");\r\n\r\n					// Only GIFs and JPGs need apply\r\n					$mime_type = (($img[\'ext\'] == \'.jpg\') ? \'image/jpeg\' : (($img[\'ext\'] == \'.gif\') ? \'image/gif\' : \'\'));\r\n					if ($mime_type) {\r\n						$reps[\'{smd_ebook_md_cover}\'] = \'<meta name=\"cover\" content=\"cover-image\" />\';\r\n						$reps[\'{smd_ebook_manifest_cover}\'] = \'<item id=\"cover-image\" media-type=\"\'.$mime_type.\'\" href=\"\' . $prefs[\'path_to_site\'] . DS . $img_dir . DS . $img[\'id\'] . $img[\'ext\'] . \'\" />\';\r\n					}\r\n				}\r\n			}\r\n\r\n			// The following values can either come from the given field or be used verbatim\r\n			// Firstly the title, description, subject, publisher and chapter title\r\n			$setMany = array(\'chaptitle\');\r\n			foreach (array(\'title\', \'description\', \'subject\', \'publisher\', \'chaptitle\') as $thingy) {\r\n				if (in_array($thingy, $setMany) || !isset($reps[\'{smd_ebook_md_\'.$thingy.\'}\'])) {\r\n					$val = ps(\'smd_ebook_fld_\'.$thingy);\r\n					if (strpos($val, \'SMD_FLD_\') !== false) {\r\n						$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n						$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n					}\r\n					if ($val) {\r\n						// Textile the content?\r\n						$content = (isset($txt_{$thingy}) && $txt_{$thingy}) ? trim($textile->TextileThis($val)) : trim($val);\r\n						if (!in_array($thingy, $setMany)) {\r\n							$reps[\'{smd_ebook_md_\'.$thingy.\'}\'] = \'<dc:\'.$thingy.\'>\' . $content . \'</dc:\'.$thingy.\'>\';\r\n						}\r\n\r\n						// There are two titles: one for the metadata and one raw so if the title\r\n						// has just been found, populate the raw title too\r\n						if ($thingy == \'title\') {\r\n							$reps[\'{smd_ebook_title}\'] = $content;\r\n						} else if ($thingy == \'chaptitle\') {\r\n							// Chapter title has an associated heading level\r\n							$reps[\'{smd_ebook_chaptitle}\'] = \'<h\'.$hdg.\'>\'.$content.\'</h\'.$hdg.\'>\';\r\n						}\r\n					}\r\n				}\r\n			}\r\n\r\n			// Price (SRP) can also come from a field but it needs some special jiggery pokery\r\n			if (!isset($reps[\'{smd_ebook_md_srp}\'])) {\r\n				$val = ps(\'smd_ebook_fld_srp\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n				}\r\n				if ($val) {\r\n					$parts = do_list($val, \'|\');\r\n					$parts[0] = $parts[0] ? $parts[0] : \'0.00\';\r\n					$parts[1] = (isset($parts[1]) && $parts[1]) ? $parts[1] : get_pref(\'smd_ebook_currency\', $smd_ebook_prefs[\'smd_ebook_currency\'][\'default\']);\r\n					$reps[\'{smd_ebook_md_srp}\'] = \'<SRP Currency=\"\'.$parts[1].\'\">\'.$parts[0].\'</SRP>\';\r\n				}\r\n			}\r\n\r\n			// Authornote is slightly different because it needs storing as a file,\r\n			// and needs adding to the .ncx (but not to the TOC)\r\n			if (!isset($reps[\'{smd_ebook_manifest_authornote}\'])) {\r\n				$val = ps(\'smd_ebook_fld_authornote\');\r\n				if (strpos($val, \'SMD_FLD_\') !== false) {\r\n					$valfld = str_replace(\'SMD_FLD_\', \'\', $val);\r\n					$val = isset($row[$valfld]) ? $row[$valfld] : \'\';\r\n				}\r\n				if ($val) {\r\n					$reps[\'{smd_ebook_manifest_authornote}\'] = \'<item id=\"smd_ebook_notes\" media-type=\"text/html\" href=\"\'.$notefile.\'\" />\';\r\n					$reps[\'{smd_ebook_spine_authornote}\'] = \'<itemref idref=\"smd_ebook_notes\" />\';\r\n					$note_content = \'<span id=\"smd_ebook_notes\"></span>\' . (($txt_authornote) ? $textile->TextileThis($val) : $val);\r\n\r\n					// While it\'s 99% likely the actual title used for the eventual book has been found,\r\n					// there\'s a slim chance it hasn\'t. In that case, the current row\'s title is used as a fallback\r\n					$note_title = isset($reps[\'{smd_ebook_title}\']) ? $reps[\'{smd_ebook_title}\'] : $row[\'Title\'];\r\n					$note_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $note_title, \'\', $sheet, $note_content), $template[\'doc\']);\r\n\r\n					$fp = fopen($prefs[\'tempdir\'].DS.$notefile, \"wb\");\r\n					fwrite($fp, trim($note_content));\r\n					fclose($fp);\r\n					$lfout[] = $notefile;\r\n\r\n					// Add it to the .ncx\r\n					$ncx_cnt++;\r\n					$from = array(\'{smd_ebook_file_name}\', \'{smd_ebook_nav_label}\', \'{smd_ebook_nav_hash}\', \'{smd_ebook_nav_idx}\');\r\n					$to = array($notefile, smd_ebook_gTxt(\'smd_ebook_lbl_authornote\'), \'smd_ebook_notes\', $ncx_cnt);\r\n					$ncx[] = str_replace($from, $to, $template[\'nav\']);\r\n				}\r\n			}\r\n\r\n			// Note:\r\n			//  1) a full (well-formed, hopefully) HTML file (from <html>...</html>) is generated\r\n			//     here so the loadHTML() method is happy. The body will need reinjecting into\r\n			//     the template after the ToC has been generated.\r\n			//  2) The current HTML file\'s title is used instead of the overall book title.\r\n			//  3) parse() is called twice to simulate secondpass.\r\n			$chap_title = isset($reps[\'{smd_ebook_chaptitle}\']) ? $reps[\'{smd_ebook_chaptitle}\'] : \'\';\r\n			article_format_info($row); // Load article context\r\n			$html_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $row[\'Title\'], $chap_title, $sheet, parse(parse($row[\'Body_html\']))), $template[\'doc\']);\r\n\r\n			// Trawl through the HTML content, either:\r\n			//  a) pulling out the ToC entries.\r\n			//  b) creating ToC entries if the pref allows\r\n			$autotoc = get_pref(\'smd_ebook_auto_toc\', $smd_ebook_prefs[\'smd_ebook_auto_toc\'][\'default\']);\r\n			$doc = new DOMDocument();\r\n			$dom_ok = $doc->loadHTML($html_content);\r\n			if ($dom_ok) {\r\n				$items = $doc->getElementsByTagName(\'*\');\r\n				$offset = $toc_cnt = 0;\r\n				foreach ($items as $item) {\r\n					if ($autotoc && !$item->hasAttribute(\'id\') && preg_match(\'/h([1-6])/i\', $item->nodeName, $matches)) {\r\n						// It\'s a heading. Make the anchor chain based on the heading level\r\n						$anchor_parts = array_fill(0, $matches[1], \'sub\');\r\n						$anchor = join(\'-\', $anchor_parts). ++$elem_cnt;\r\n						$item->setAttribute(\'id\', $anchor);\r\n					}\r\n\r\n					if ($item->hasAttribute(\'id\')) {\r\n						$ncx_cnt++;\r\n						$toc_cnt++;\r\n						$hashval = $item->getAttribute(\'id\');\r\n						if ( (!isset($reps[\'{smd_ebook_guide_start}\'])) && ($toc_cnt == 1) ) {\r\n							$reps[\'{smd_ebook_guide_start}\'] = \'<reference type=\"text\" title=\"\'.smd_ebook_gTxt(\'smd_ebook_lbl_start\').\'\" href=\"\'.$firstfile.\'#\'.$hashval.\'\"></reference>\';\r\n						}\r\n\r\n						// mb_convert_encoding() seems to bypass the odd behaviour where apostrophes\r\n						// would appear in the TOC as â€™. This may actually be a band-aid to circumvent\r\n						// problems with the encoding in DOMDocument: perhaps if appropriate encoding is\r\n						// used there, this hack won\'t be necessary\r\n						$node = mb_convert_encoding($item->nodeValue, \'HTML-ENTITIES\', \'utf-8\');\r\n						$from = array(\'{smd_ebook_file_name}\', \'{smd_ebook_nav_label}\', \'{smd_ebook_nav_hash}\', \'{smd_ebook_nav_idx}\');\r\n						$to = array($cur_file, $node, $hashval, $ncx_cnt);\r\n						$ncx[] = str_replace($from, $to, $template[\'nav\']);\r\n\r\n						// Now it\'s the turn of the HTML TOC. Utilise Textile here to\r\n						// create the toc list from ul or ol syntax\r\n						$hashBits = do_list($hashval, \'-\');\r\n						$indent = count($hashBits);\r\n\r\n						if ( ($toc_cnt == 1) && ($indent > 1) ) {\r\n							// Doesn\'t start with h1 (begins h2, maybe) so scale back the indent.\r\n							// Without this, Textile produces invalid markup\r\n							$offset = $indent - 1;\r\n						}\r\n\r\n						$toc_cls = (($toc_cnt == 1) && $toc_class) ? \'(\'.$toc_class.\')\' : \'\';\r\n						$toc[] = str_pad(\'\', max(1, $indent-$offset), $wrapit) . $toc_cls.\' \' . href($node, $cur_file.\'#\'.$hashval);\r\n					}\r\n				}\r\n\r\n				// Grab any changes just made to the DOM tree in case anchors have been added.\r\n				// Note _only_ the <body> is extracted since the XML headers that come with a full\r\n				// saveXML() get in the way. Also note that saveHTML() is not being used because its\r\n				// \'node\' parameter wasn\'t added until PHP 5.3.6 which would affect the plugin\'s\r\n				// base requirements.\r\n				// Hackish: remove the body tag wrapper with substr() so when the html_content\r\n				// is shoved back into the template (which has a body tag already) there\'s no\r\n				// tag duplication\r\n				$html_content = substr($doc->saveXML($doc->getElementsByTagName(\'body\')->item(0)), 6, -7);\r\n\r\n				// Swap out any line break placeholders. Note that the line break is replaced twice:\r\n				// once to get rid of any surrounding <p> tags that Textile may have introduced around\r\n				// the marker, and again in case a few of them didn\'t get paragraph tags.\r\n				$html_content = str_replace(\'<p>\'.$pbr.\'</p>\', \'<mbp:pagebreak />\', $html_content);\r\n				$html_content = str_replace($pbr, \'<mbp:pagebreak />\', $html_content);\r\n\r\n				// Pass the extracted <body> tree into the doc template again so it regenerates\r\n				// the full <html>...</html> document.\r\n				$html_content = str_replace(array(\'{smd_ebook_encoding}\', \'{smd_ebook_title}\', \'{smd_ebook_chaptitle}\', \'{smd_ebook_stylesheet}\', \'{smd_ebook_contents}\'), array($encoding, $row[\'Title\'], \'\', $sheet, $html_content), $template[\'doc\']);\r\n			} else {\r\n				trigger_error(smd_ebook_gTxt(\'smd_ebook_malformed\'), E_WARNING);\r\n			}\r\n\r\n			// Write the final HTML document to the file system\r\n			$fp = fopen($prefs[\'tempdir\'].DS.$cur_file, \"wb\");\r\n			fwrite($fp, trim($html_content));\r\n			fclose($fp);\r\n\r\n			$lfout[] = $cur_file;\r\n			$article_refs[] = \'<item id=\"smd_ebook_item_\'.$article_cnt.\'\" media-type=\"text/html\" href=\"\'.$row[\'url_title\'].\'.html\" />\';\r\n			$article_spines[] = \'<itemref idref=\"smd_ebook_item_\' . $article_cnt . \'\" />\';\r\n\r\n		}\r\n	}\r\n\r\n	// Ensure any NULL replacements are cleared or throw errors\r\n	$reps[\'{smd_ebook_chaptitle}\'] = (!isset($reps[\'{smd_ebook_chaptitle}\'])) ? \'\' : $reps[\'{smd_ebook_chaptitle}\'];\r\n	$reps[\'{smd_ebook_creator}\'] = (!isset($reps[\'{smd_ebook_creator}\'])) ? \'\' : $reps[\'{smd_ebook_creator}\'];\r\n	$reps[\'{smd_ebook_md_creator}\'] = (!isset($reps[\'{smd_ebook_md_creator}\'])) ? \'\' : $reps[\'{smd_ebook_md_creator}\'];\r\n	$reps[\'{smd_ebook_md_description}\'] = (!isset($reps[\'{smd_ebook_md_description}\'])) ? \'\' : $reps[\'{smd_ebook_md_description}\'];\r\n	$reps[\'{smd_ebook_md_subject}\'] = (!isset($reps[\'{smd_ebook_md_subject}\'])) ? \'\' : $reps[\'{smd_ebook_md_subject}\'];\r\n	$reps[\'{smd_ebook_md_publisher}\'] = (!isset($reps[\'{smd_ebook_md_publisher}\'])) ? \'\' : $reps[\'{smd_ebook_md_publisher}\'];\r\n	$reps[\'{smd_ebook_md_srp}\'] = (!isset($reps[\'{smd_ebook_md_srp}\'])) ? \'\' : $reps[\'{smd_ebook_md_srp}\'];\r\n	$reps[\'{smd_ebook_guide_start}\'] = (!isset($reps[\'{smd_ebook_guide_start}\'])) ? \'\' : $reps[\'{smd_ebook_guide_start}\'];\r\n	if (!isset($reps[\'{smd_ebook_md_cover}\'])) {\r\n		$reps[\'{smd_ebook_md_cover}\'] = \'\';\r\n		$reps[\'{smd_ebook_manifest_cover}\'] = \'\';\r\n	}\r\n	if (!isset($reps[\'{smd_ebook_manifest_authornote}\'])) {\r\n		$reps[\'{smd_ebook_manifest_authornote}\'] =  \'\';\r\n		$reps[\'{smd_ebook_spine_authornote}\'] =  \'\';\r\n	}\r\n\r\n	// All the replacements are set up so prepare for kindlegen\r\n	// First, create the TOC and write it to the filesystem\r\n	if ($toc_cnt > 0) {\r\n		$reps[\'{smd_ebook_manifest_toc}\'] = \'<item id=\"toc\" media-type=\"text/html\" href=\"\'.$toc_file.\'\" />\';\r\n		$reps[\'{smd_ebook_spine_toc}\'] = \'<itemref idref=\"toc\" />\';\r\n		$reps[\'{smd_ebook_guide_toc}\'] = \'<reference type=\"toc\" title=\"\' . smd_ebook_gTxt(\'smd_ebook_toc\') . \'\" href=\"\'.$toc_file.\'\"></reference>\';\r\n		$html_toc = $textile->TextileThis(join(n, $toc));\r\n		$html_toc = str_replace(array(\'{smd_ebook_toc_list}\', \'{smd_ebook_stylesheet}\'), array($html_toc, $sheet), $template[\'toc\']);\r\n		$fp = fopen($prefs[\'tempdir\'] . DS . $toc_file, \"wb\");\r\n		fwrite($fp, trim($html_toc));\r\n		fclose($fp);\r\n		$lfout[] = $toc_file;\r\n	} else {\r\n		$reps[\'{smd_ebook_manifest_toc}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_toc}\'] = \'\';\r\n		$reps[\'{smd_ebook_guide_toc}\'] = \'\';\r\n	}\r\n\r\n	// Add the ncx waypoints to the reps array and generate the .ncx file\r\n	if ($ncx_cnt > 0) {\r\n		$reps[\'{smd_ebook_ncx_map}\'] = join(n, $ncx);\r\n		$reps[\'{smd_ebook_manifest_ncx}\'] = \'<item id=\"ncx\" media-type=\"application/x-dtbncx+xml\" href=\"\'.$ncx_file.\'\" />\';\r\n		$reps[\'{smd_ebook_spine_ncx}\'] = \'<itemref idref=\"ncx\" />\';\r\n		$reps[\'{smd_ebook_spine_ncx_ref}\'] = \'toc=\"ncx\"\';\r\n		$ncx_file_content = strtr($template[\'ncx\'], $reps);\r\n		$fp = fopen($prefs[\'tempdir\'] . DS . $ncx_file, \"wb\");\r\n		fwrite($fp, trim($ncx_file_content));\r\n		fclose($fp);\r\n		$lfout[] = $ncx_file;\r\n	} else {\r\n		$reps[\'{smd_ebook_manifest_ncx}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_ncx}\'] = \'\';\r\n		$reps[\'{smd_ebook_spine_ncx_ref}\'] = \'\';\r\n	}\r\n\r\n	// Build the remaining manifest replacements and generate the OPF\r\n	$reps[\'{smd_ebook_manifest_items}\'] = join(n, $article_refs);\r\n	$reps[\'{smd_ebook_spine_items}\'] = join(n, $article_spines);\r\n\r\n	$opf_file_content = strtr($template[\'opf\'], $reps);\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $opf_file, \"wb\");\r\n	fwrite($fp, trim($opf_file_content));\r\n	fclose($fp);\r\n	$lfout[] = $opf_file;\r\n\r\n	// Write the listfile, which contains a list of all the files used in this stage\r\n	$fp = fopen($prefs[\'tempdir\'] . DS . $listfile, \"wb\");\r\n	fwrite($fp, join(n, $lfout));\r\n	fclose($fp);\r\n\r\n	// Hand off to Stage 2 to do the deed\r\n	smd_ebook_generate($listfile, $opf_file);\r\n}\r\n\r\n// ------------------------\r\n// Stage 2 only: Pre-requisites are that the necessary files (toc, .html, ncx + opf)\r\n// have already been generated by the previous stage. If called directly via the\r\n// GUI, the hidden form value containing the OPF file is read.\r\nfunction smd_ebook_generate($listfile=\'\', $opf_file=\'\') {\r\n	global $prefs, $smd_ebook_prefs;\r\n\r\n	$report = array();\r\n	$retval = NULL;\r\n\r\n	// Use passed in values in lieu of the one in the form\r\n	$opf_file = ($opf_file) ? $opf_file : ps(\'smd_ebook_opf_file\');\r\n	$listfile = ($listfile) ? $listfile : ps(\'smd_ebook_listfile\');\r\n\r\n	// .mobifile credentials\r\n	$mobifile = ps(\'smd_ebook_mobifile\');\r\n	$fullpath = $prefs[\'tempdir\'] . DS . $mobifile;\r\n	$filesize = filesize($fullpath);\r\n\r\n	$downloadit = ps(\'smd_ebook_download\');\r\n	$fileit = ps(\'smd_ebook_to_files\');\r\n\r\n	if ($downloadit) {\r\n		ob_clean();\r\n		header(\'Content-Description: File Download\');\r\n		header(\'Content-Type: application/octet-stream\');\r\n		header(\'Content-Disposition: attachment; filename=\"\'.$mobifile.\'\"; size = \"\'.$filesize.\'\"\');\r\n		header(\"Content-Transfer-Encoding: binary\");\r\n		header(\"Cache-Control: no-cache, must-revalidate, max-age=60\");\r\n		header(\"Expires: Sat, 01 Jan 2000 12:00:00 GMT\");\r\n		header(\'Cache-Control: private\');\r\n		@set_time_limit(0);\r\n		if ($file = fopen($fullpath, \'rb\')) {\r\n			while(!feof($file) and (connection_status()==0)) {\r\n				echo fread($file, 1024*64);\r\n				ob_flush();\r\n				flush();\r\n			}\r\n			fclose($file);\r\n		}\r\n		exit;\r\n\r\n	} else if ($fileit) {\r\n\r\n		@include_once txpath.\'/include/txp_file.php\';\r\n\r\n		// Copy the file to the files area\r\n		$destpath = $prefs[\'file_base_path\'] . DS . $mobifile;\r\n		copy($fullpath, $destpath);\r\n\r\n		// Get the file category\r\n		$filecat = get_pref(\'smd_ebook_file_cat\', $smd_ebook_prefs[\'smd_ebook_file_cat\'][\'default\']);\r\n\r\n		// Read description and title from .opf\r\n		$doc = new DOMDocument();\r\n		$content = file_get_contents($prefs[\'tempdir\'] . DS . $opf_file);\r\n		$dom_ok = $doc->loadXML($content);\r\n\r\n		$description = $title = \'\';\r\n		if ($dom_ok) {\r\n			$items = $doc->getElementsByTagName(\'*\');\r\n			foreach ($items as $item) {\r\n				if ($item->nodeName == \'dc:title\') {\r\n					$title = $item->nodeValue;\r\n				}\r\n				if ($item->nodeName == \'dc:description\') {\r\n					$description = $item->nodeValue;\r\n				}\r\n			}\r\n		}\r\n\r\n		$curid = safe_field(\'id\', \'txp_file\', \"filename=\'\".doSlash($mobifile).\"\'\");\r\n\r\n		if ($curid) {\r\n			// Update existing database entry\r\n			$ret = safe_update(\'txp_file\',\r\n				\"\r\n					title=\'\" . doSlash($title) . \"\',\r\n					category=\'\" . doSlash($filecat) . \"\',\r\n					description=\'\" . doSlash($description) . \"\',\r\n					size=\'\" . doSlash($filesize) . \"\',\r\n					modified= now()\r\n				\",\r\n				\"id=\'\".doSlash($curid).\"\'\"\r\n			);\r\n			if ($ret) {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_updated\', array(\'{id}\' => $curid));\r\n			} else {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_not_filed\');\r\n			}\r\n\r\n		} else {\r\n			// Make a new entry in the database for it\r\n			$newid = file_db_add(doSlash($mobifile), doSlash($filecat), \'\', doSlash($description), doSlash($filesize), doSlash($title));\r\n			if ($newid) {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_filed\', array(\'{id}\' => $newid));\r\n			} else {\r\n				$msg = smd_ebook_gTxt(\'smd_ebook_not_filed\');\r\n			}\r\n		}\r\n\r\n	} else {\r\n\r\n		// (Re)generate the book\r\n		list($report, $retval) = smd_ebook_kindlegen($opf_file);\r\n\r\n		if ($retval > 1) {\r\n			$msg = smd_ebook_gTxt(\'smd_ebook_generate_failed\', array(\'{code}\' => $retval));\r\n		} else {\r\n			$msg = smd_ebook_gTxt(\'smd_ebook_generate_ok\');\r\n		}\r\n	}\r\n	smd_ebook_ui($msg, $listfile, join(n, $report), $retval);\r\n}\r\n\r\n// ------------------------\r\n// Interface with kindlegen to generate the .mobi file.\r\nfunction smd_ebook_kindlegen($opf) {\r\n	global $prefs, $smd_ebook_prefs;	\r\n\r\n	$kgen = get_pref(\'smd_ebook_kindlegen_path\', $smd_ebook_prefs[\'smd_ebook_kindlegen_path\'][\'default\']);\r\n	$command = $kgen . \' \' . $prefs[\'tempdir\'] . DS . $opf;\r\n	exec($command, $output, $result);\r\n\r\n	return array($output, $result);\r\n}\r\n\r\n// ------------------------\r\n// Common buttons for the interface\r\nfunction smd_ebook_buttons($curr=\'mgr\') {\r\n	global $smd_ebook_event;\r\n\r\n	$ret = array (\r\n		\'btnMgr\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.\'\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_mgr\'), \'smallerbox\'.($curr==\'mgr\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnPrf\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_prefs\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_prf\'), \'smallerbox\'.($curr==\'prf\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnCln\' => \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_tidy\" class=\"smd_inline\">\'.fInput(\'submit\', \'submit\', smd_ebook_gTxt(\'smd_ebook_lbl_cln\'), \'smallerbox\'.($curr==\'cln\'?\' smd_active\':\'\')).tInput().\'</form>\',\r\n		\'btnTst\' => \'<a href=\"?event=\'.$smd_ebook_event.a.\'step=smd_ebook_test\'.a.\'_txp_token=\'.form_token().\'\" class=\"smd_inline\">\'.smd_ebook_gTxt(\'smd_ebook_lbl_tst\').\'</a>\',\r\n	);\r\n	return $ret;\r\n}\r\n\r\n// ------------------------\r\n// Tidy up the temp dir\r\nfunction smd_ebook_tidy($msg=\'\') {\r\n	global $prefs, $smd_ebook_event, $smd_ebook_styles;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	if (ps(\'smd_ebook_cleanup\')) {\r\n		$to_delete = ps(\'smd_ebook_files\');\r\n		foreach($to_delete as $del) {\r\n			$path = realpath($prefs[\'tempdir\'] . DS . $del);\r\n			unlink($path);\r\n		}\r\n		$msg = smd_ebook_gTxt(\'smd_ebook_deleted\');\r\n	}\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'cln\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	$filelist = array();\r\n	$valid = array(\'mobi\', \'html\', \'ncx\', \'opf\', \'smd\');\r\n	$tmp = $prefs[\'tempdir\'] . DS;\r\n\r\n	// Grab all files then remove unnecessary ones: faster than multiple globs\r\n	// for each file type and more robust than relying on GLOB_BRACE support\r\n	$allfiles = glob($tmp.\'*.*\');\r\n\r\n	foreach ($allfiles as $file) {\r\n		$info = explode (\'.\',$file);\r\n		$lastpart = count($info)-1;\r\n		$ext = trim($info[$lastpart]);\r\n		if (in_array($ext, $valid)) {\r\n			$filelist[] = $file;\r\n		}\r\n	}\r\n\r\n	// Inject styles\r\n	echo \'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n\r\n	echo \'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	$filesel = \'\';\r\n	if ($filelist) {\r\n		$filez = array();\r\n		foreach($filelist as $val) {\r\n			$val = basename($val);\r\n			$key = sanitizeForFile($val);\r\n			$filez[$key] = $val;\r\n	   }\r\n		$selout[] = \'<select id=\"smd_ebook_files\" name=\"smd_ebook_files[]\" class=\"list\" size=\"20\" multiple=\"multiple\">\';\r\n		foreach ($filez as $key => $leaf) {\r\n			$selout[] = t.\'<option value=\"\'.$key.\'\">\'.htmlspecialchars($leaf).\'</option>\'.n;\r\n		}\r\n		$selout[] = \'</select>\';\r\n		$filesel = join(n, $selout);\r\n	}\r\n\r\n	echo \'<div class=\"txp-list\">\';\r\n	echo startTable(\'list\');\r\n	echo \'<form method=\"post\" action=\"?event=\'.$smd_ebook_event.\'\">\';\r\n	echo tr(tda(strong(smd_ebook_gTxt(\'smd_ebook_tidy\'))));\r\n	echo ($filesel) ? tr(tda($filesel)) : tr(tda(smd_ebook_gTxt(\'smd_ebook_no_files\')));\r\n	echo tr(tda(fInput(\'submit\', \'smd_ebook_cleanup\', gTxt(\'delete\'), \'publish\'), \' class=\"noline\"\'));\r\n	echo sInput(\'smd_ebook_tidy\');\r\n	echo tInput();\r\n	echo \'</form>\';\r\n	echo endTable();\r\n	echo \'</div>\';\r\n}\r\n\r\n// ------------------------\r\n// List of current stylesheets\r\nfunction smd_ebook_style_list($name, $val=\'\') {\r\n	$styles = safe_column(\'name\', \'txp_css\', \'1=1\');\r\n	return selectInput($name, $styles, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of current file categories\r\nfunction smd_ebook_file_cat_list($name, $val=\'\') {\r\n	$rs = getTree(\'root\', \'file\');\r\n	if ($rs) {\r\n		return treeSelectInput($name, $rs, $val, $name);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// List of numbers for heading levels\r\nfunction smd_ebook_number($name, $val=\'\') {\r\n	// Can\'t use range() since it creates indices starting at 0\r\n	$nums = array();\r\n	for ($idx = 1; $idx <= 6; $idx++) {\r\n		$nums[$idx] = $idx;\r\n	}\r\n	return selectInput($name, $nums, $val, false);\r\n}\r\n\r\n// ------------------------\r\n// List of current sections\r\n// TODO: multiple select?\r\nfunction smd_ebook_section_list($name, $val=\'\') {\r\n	$secs = safe_column(\'name\', \'txp_section\', \'1=1\');\r\n	return selectInput($name, $secs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields\r\nfunction smd_ebook_fld_list($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields with a few extras\r\nfunction smd_ebook_fld_list_plus($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'Category1\'] = gTxt(\'category1\');\r\n	$cfs[\'Category2\'] = gTxt(\'category2\');\r\n	$cfs[\'Section\'] = gTxt(\'section\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// List of custom fields\r\nfunction smd_ebook_fld_list_author($name, $val=\'\') {\r\n	$cfs = getCustomFields();\r\n	$cfs[\'Title\'] = gTxt(\'title\');\r\n	$cfs[\'Excerpt_html\'] = gTxt(\'excerpt\');\r\n	$cfs[\'AuthorID\'] = gTxt(\'author\');\r\n	$cfs[\'SMD_FIXED\'] = smd_ebook_gTxt(\'smd_ebook_fixed\');\r\n	return selectInput($name, $cfs, $val, true);\r\n}\r\n\r\n// ------------------------\r\n// Multi-select list of privilege levels\r\nfunction smd_ebook_priv_list($name, $val=\'\') {\r\n	$grps = get_groups();\r\n	unset($grps[\'0\']); // Remove \'none\'\r\n	unset($grps[\'1\']); // Remove publishers -- they get access to everything already\r\n\r\n	$sels = do_list($val);\r\n\r\n	$ulist = array();\r\n	$ulist[] = \'<select name=\"\'.$name.\'[]\" id=\"\'.$name.\'\" class=\"list multiple\" multiple=\"multiple\" size=\"6\">\';\r\n	foreach ($grps as $lvl => $grp) {\r\n		$selected = in_array($lvl, $sels) ? \' selected=\"selected\"\' : \'\';\r\n		$ulist[] = \'<option value=\"\'.$lvl.\'\"\'.$selected.\'>\' . htmlspecialchars($grp) . \'</option>\';\r\n	}\r\n	$ulist[] = \'</select>\';\r\n\r\n	return join(n, $ulist);\r\n}\r\n\r\n// ------------------------\r\n// Mini diagnostics to see if the kindlegen program can be run on this host.\r\nfunction smd_ebook_test() {\r\n	global $smd_ebook_event, $smd_ebook_prefs;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	$out = \'\';\r\n	$kgen = get_pref(\'smd_ebook_kindlegen_path\', $smd_ebook_prefs[\'smd_ebook_kindlegen_path\'][\'default\']);\r\n	exec($kgen, $output, $retval);\r\n\r\n	if ($retval != 0) {\r\n		switch ($retval) {\r\n			case 126:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_permissions_issue\');\r\n			break;\r\n			case 127:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_not_found\');\r\n			break;\r\n			default:\r\n				$out = smd_ebook_gTxt(\'smd_ebook_error_code\', array(\'{code}\' => $retval));\r\n			break;\r\n		}\r\n		$out = print_r($output, true);\r\n	} else {\r\n		$out = smd_ebook_gTxt(\'smd_ebook_ok\');\r\n	}\r\n	$_POST[\'smd_ebook_test_output\'] = $out;\r\n	$msg = smd_ebook_gTxt(\'smd_ebook_test_complete\');\r\n	smd_ebook_prefs($msg);\r\n}\r\n\r\n// ------------------------\r\n// Handle the prefs panel\r\nfunction smd_ebook_prefs($msg=\'\') {\r\n	global $smd_ebook_event, $smd_ebook_prefs, $smd_ebook_styles, $step;\r\n\r\n	require_privs(\'plugin_prefs.\'.$smd_ebook_event);\r\n\r\n	if (ps(\'smd_ebook_pref_save\')) {\r\n		foreach ($smd_ebook_prefs as $idx => $prefobj) {\r\n			$val = ps($idx);\r\n			$val = (is_array($val)) ? join(\', \', $val) : $val;\r\n			set_pref($idx, doSlash($val), \'smd_ebook\', $prefobj[\'type\'], $prefobj[\'html\'], $prefobj[\'position\']);\r\n		}\r\n\r\n		$msg = gTxt(\'preferences_saved\');\r\n	}\r\n\r\n	pagetop(smd_ebook_gTxt(\'smd_ebook_tab_name\'), $msg);\r\n	extract(smd_ebook_buttons(\'prf\'));\r\n\r\n	$btnbar = (has_privs(\'plugin_prefs.\'.$smd_ebook_event))? \'<span class=\"smd_ebook_buttons\">\'.$btnMgr.$btnPrf.$btnCln.\'</span>\' : \'\';\r\n\r\n	// Inject styles\r\n	echo \'<style type=\"text/css\">\' . $smd_ebook_styles[\'cpanel\'] . \'</style>\';\r\n\r\n	echo <<<EOJS\r\n<script type=\"text/javascript\">\r\njQuery(function() {\r\n	jQuery(\"select[name=\'smd_ebook_fld_title\'], select[name=\'smd_ebook_fld_chaptitle\'], select[name=\'smd_ebook_fld_author\'], select[name=\'smd_ebook_fld_description\'], select[name=\'smd_ebook_fld_authornote\'], select[name=\'smd_ebook_fld_subject\'], select[name=\'smd_ebook_fld_publisher\'], select[name=\'smd_ebook_fld_srp\']\").change(function() {\r\n		var xtra = jQuery(this).attr(\'name\') + \'_fixed\';\r\n		if (jQuery(\'option:selected\', this).val() == \'SMD_FIXED\') {\r\n			jQuery(\"input[name=\'\"+xtra+\"\']\").parent().parent().show(\'normal\');\r\n		} else {\r\n			jQuery(\"input[name=\'\"+xtra+\"\']\").parent().parent().hide(\'fast\');\r\n		}\r\n	}).change();\r\n});\r\n</script>\r\nEOJS;\r\n	echo \'<div id=\"\'.$smd_ebook_event.\'_control\" class=\"txp-control-panel\">\' . $btnbar . \'</div>\';\r\n\r\n	$out = array();\r\n	$out[] = n.\'<div class=\"txp-list\">\';\r\n	$out[] = \'<form name=\"smd_ebook_prefs\" id=\"smd_ebook_prefs\" action=\"index.php\" method=\"post\">\';\r\n	$out[] = eInput($smd_ebook_event).sInput(\'smd_ebook_prefs\');\r\n	$out[] = startTable(\'list\');\r\n	$last_grp = \'\';\r\n	foreach ($smd_ebook_prefs as $idx => $prefobj) {\r\n		if ($last_grp != $prefobj[\'group\']) {\r\n			$out[] = tr(tdcs(strong(smd_ebook_gTxt($prefobj[\'group\'])), 2));\r\n		}\r\n		$last_grp = $prefobj[\'group\'];\r\n		$subout = array();\r\n		$subout[] = tda(\'<label for=\"\'.$idx.\'\">\'.smd_ebook_gTxt($idx).\'</label>\', \' class=\"noline\" style=\"text-align: right; vertical-align: middle;\"\');\r\n		$val = get_pref($idx, $prefobj[\'default\'], 1);\r\n		$vis = (isset($prefobj[\'visible\']) && !$prefobj[\'visible\']) ? \'smd_hidden\' : \'\';\r\n		switch ($prefobj[\'html\']) {\r\n			case \'text_input\':\r\n				$subout[] = tda(fInput(\'text\', $idx, $val, \'\', \'\', \'\', \'\', \'\', $idx),\' class=\"noline\"\');\r\n			break;\r\n			case \'yesnoradio\':\r\n				$subout[] = tda(yesnoRadio($idx, $val),\' class=\"noline\"\');\r\n			break;\r\n			case \'radioset\':\r\n				$subout[] = tda(radioSet($prefobj[\'content\'], $idx, $val),\' class=\"noline\"\');\r\n			break;\r\n			case \'checkboxset\':\r\n				$vals = do_list($val);\r\n				$lclout = array();\r\n				foreach ($prefobj[\'content\'] as $cb => $val) {\r\n					$checked = in_array($cb, $vals);\r\n					$lclout[] = checkbox($idx.\'[]\', $cb, $checked). smd_ebook_gTxt($val);\r\n				}\r\n				$subout[] = tda(join(n, $lclout),\' class=\"noline\"\');\r\n			break;\r\n			case \'selectlist\':\r\n				$subout[] = tda(selectInput($idx, $prefobj[\'content\'][0], $val, $prefobj[\'content\'][1]),\' class=\"noline\"\');\r\n			break;\r\n			default:\r\n				if ( strpos($prefobj[\'html\'], \'smd_ebook_\') !== false && is_callable($prefobj[\'html\']) ) {\r\n					$subout[] = tda($prefobj[\'html\']($idx, $val),\' class=\"noline\"\');\r\n				}\r\n			break;\r\n		}\r\n		$out[] = tr(join(n ,$subout), \' class=\"\'.$vis.\'\"\');\r\n	}\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda($btnTst, \' class=\"noline\"\'));\r\n\r\n	if ($step == \'smd_ebook_test\') {\r\n		$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(text_area(\'smd_ebook_test_results\', 150, 200, ps(\'smd_ebook_test_output\')), \' class=\"noline\"\'));\r\n	}\r\n\r\n	$out[] = tr(tda(\'&nbsp;\', \' class=\"noline\"\') . tda(fInput(\'submit\', \'smd_ebook_pref_save\', gTxt(\'save\'), \'publish\'), \' class=\"noline\"\'));\r\n	$out[] = endTable().tInput();\r\n	$out[] = \'</form></div>\';\r\n\r\n	echo join(n, $out);\r\n}\r\n\r\n// ------------------------\r\n// Delete plugin prefs\r\nfunction smd_ebook_prefs_remove($showpane=1) {\r\n	$message = \'\';\r\n\r\n	safe_delete(\'txp_prefs\', \"name like \'smd_ebook_%\'\");\r\n\r\n	if ($showpane) {\r\n		$message = smd_ebook_gTxt(\'smd_ebook_prefs_deleted\');\r\n		smd_ebook($message);\r\n	}\r\n}\r\n\r\n// ------------------------\r\n// Set up the global prefs for the plugin\r\nfunction smd_ebook_get_prefs() {\r\n	global $smd_ebook_prefs, $prefs;\r\n\r\n	$smd_ebook_prefs = array(\r\n		\'smd_ebook_uid\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_page_break\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'~~~~~\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_toc_wraptag\' => array(\r\n			\'html\'     => \'radioset\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'content\'  => array(\'ul\' => smd_ebook_gTxt(\'smd_ebook_lbl_ul\'), \'ol\' => smd_ebook_gTxt(\'smd_ebook_lbl_ol\')),\r\n			\'default\'  => \'ul\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_toc_class\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'smd_ebook_toc\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_section\' => array(\r\n			\'html\'     => \'smd_ebook_section_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 70,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_stylesheet\' => array(\r\n			\'html\'     => \'smd_ebook_style_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 80,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_textile\' => array(\r\n			\'html\'     => \'checkboxset\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 90,\r\n			\'content\'  => array(\'description\' => \'smd_ebook_lbl_description\', \'authornote\' => \'smd_ebook_lbl_authornote\'),\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_encoding\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 100,\r\n			\'default\'  => \'utf-8\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_kindlegen_path\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 200,\r\n			\'default\'  => $prefs[\'path_to_site\'].DS.\'kindle\'.DS.\'kindlegen\',\r\n			\'group\'    => \'smd_ebook_settings\',\r\n		),\r\n		\'smd_ebook_auto_toc\' => array(\r\n			\'html\'     => \'yesnoradio\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'1\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_title\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'Title\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_title_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 25,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_chaptitle\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 30,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_chaptitle_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 35,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_author\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_author\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 40,\r\n			\'default\'  => \'AuthorID\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_author_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 45,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_description\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 50,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_description_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 55,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_authornote\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 60,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_authornote_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 65,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_subject\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_plus\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 70,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_subject_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 75,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_publisher\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list_plus\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 80,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_publisher_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 85,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_fld_srp\' => array(\r\n			\'html\'     => \'smd_ebook_fld_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 90,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_fld_srp_fixed\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 95,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n			\'visible\'  => false,\r\n		),\r\n		\'smd_ebook_currency\' => array(\r\n			\'html\'     => \'text_input\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 100,\r\n			\'default\'  => \'EUR\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_heading_level\' => array(\r\n			\'html\'     => \'smd_ebook_number\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 110,\r\n			\'default\'  => \'2\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_file_cat\' => array(\r\n			\'html\'     => \'smd_ebook_file_cat_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 120,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_pubset\',\r\n		),\r\n		\'smd_ebook_privs\' => array(\r\n			\'html\'     => \'smd_ebook_priv_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 10,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_usrset\',\r\n		),\r\n		\'smd_ebook_opf_edit\' => array(\r\n			\'html\'     => \'smd_ebook_priv_list\',\r\n			\'type\'     => PREF_HIDDEN,\r\n			\'position\' => 20,\r\n			\'default\'  => \'\',\r\n			\'group\'    => \'smd_ebook_usrset\',\r\n		),\r\n	);\r\n}\r\n\r\n// ------------------------\r\nfunction smd_ebook_gTxt($what, $atts = array()) {\r\n	$lang = array(\r\n		\'en-gb\' => array(\r\n			\'smd_ebook_auto_toc\'              => \'Automatically create ToC anchors on headings\',\r\n			\'smd_ebook_currency\'              => \'Default three-letter currency code\',\r\n			\'smd_ebook_deleted\'               => \'Temporary files deleted\',\r\n			\'smd_ebook_encoding\'              => \'Character set of document\',\r\n			\'smd_ebook_error_code\'            => \'Status code {code} returned. Ensure the program was uploaded as Binary\',\r\n			\'smd_ebook_file_cat\'              => \'Store files in category\',\r\n			\'smd_ebook_filed\'                 => \'E-book filed as ID {id}\',\r\n			\'smd_ebook_fixed\'                 => \'Static text\',\r\n			\'smd_ebook_fld_author\'            => \'Get author from field\',\r\n			\'smd_ebook_fld_author_fixed\'      => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_authornote\'        => \'Get author notes from field\',\r\n			\'smd_ebook_fld_authornote_fixed\'  => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_chaptitle\'         => \'Get chapter titles from field\',\r\n			\'smd_ebook_fld_chaptitle_fixed\'   => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_description\'       => \'Get description from field\',\r\n			\'smd_ebook_fld_description_fixed\' => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_publisher\'         => \'Get publisher from field\',\r\n			\'smd_ebook_fld_publisher_fixed\'   => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_srp\'               => \'Get SRP (price) from field\',\r\n			\'smd_ebook_fld_srp_fixed\'         => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_subject\'           => \'Get subject (genre) from field\',\r\n			\'smd_ebook_fld_subject_fixed\'     => \'&lfloor;_ Text\',\r\n			\'smd_ebook_fld_title\'             => \'Get book title from field\',\r\n			\'smd_ebook_fld_title_fixed\'       => \'&lfloor;_ Text\',\r\n			\'smd_ebook_from\'                  => \'From\',\r\n			\'smd_ebook_heading_level\'         => \'Chapter heading level\',\r\n			\'smd_ebook_generate_failed\'       => \'E-book generation failed (exit status {code})\',\r\n			\'smd_ebook_generate_ok\'           => \'E-book generation successful\',\r\n			\'smd_ebook_kindlegen_path\'        => \'Path to kindlegen executable\',\r\n			\'smd_ebook_lbl_articles\'          => \'Choose book article(s)\',\r\n			\'smd_ebook_lbl_author\'            => \'Author\',\r\n			\'smd_ebook_lbl_authornote\'        => \'Author notes\',\r\n			\'smd_ebook_lbl_chaptitle\'         => \'Chapter titles\',\r\n			\'smd_ebook_lbl_cln\'               => \'Tidy up\',\r\n			\'smd_ebook_lbl_create\'            => \'Create\',\r\n			\'smd_ebook_lbl_description\'       => \'Book description\',\r\n			\'smd_ebook_lbl_download\'          => \'Download\',\r\n			\'smd_ebook_lbl_files\'             => \'File manager\',\r\n			\'smd_ebook_lbl_generate\'          => \'(Re)generate\',\r\n			\'smd_ebook_lbl_mgr\'               => \'E-book\',\r\n			\'smd_ebook_lbl_ol\'                => \'Numeric list\',\r\n			\'smd_ebook_lbl_prf\'               => \'Settings\',\r\n			\'smd_ebook_lbl_publisher\'         => \'Publisher\',\r\n			\'smd_ebook_lbl_report\'            => \'Build report\',\r\n			\'smd_ebook_lbl_srp\'               => \'Price|Currency\',\r\n			\'smd_ebook_lbl_start\'             => \'Welcome\',\r\n			\'smd_ebook_lbl_subject\'           => \'Subject (genre)\',\r\n			\'smd_ebook_lbl_title\'             => \'Book title\',\r\n			\'smd_ebook_lbl_to_files\'          => \'Store file\',\r\n			\'smd_ebook_lbl_tst\'               => \'Test kindlegen program\',\r\n			\'smd_ebook_lbl_ul\'                => \'Standard list\',\r\n			\'smd_ebook_lbl_view\'              => \'[Preview]\',\r\n			\'smd_ebook_malformed\'             => \'Could not process HTML from {file}. Malformed?\',\r\n			\'smd_ebook_no_files\'              => \'No e-book files found\',\r\n			\'smd_ebook_not_filed\'             => \'E-book NOT filed\',\r\n			\'smd_ebook_not_found\'             => \'File not found. Check path?\',\r\n			\'smd_ebook_ok\'                    => \'Everything looks OK\',\r\n			\'smd_ebook_opf_edit\'              => \'Groups that can edit .opf\',\r\n			\'smd_ebook_page_break\'            => \'Page break character sequence\',\r\n			\'smd_ebook_permissions_issue\'     => \'Permissions problem. Is the file executable?\',\r\n			\'smd_ebook_prefs_deleted\'         => \'Settings deleted\',\r\n			\'smd_ebook_preview_prefix\'        => \'Preview of\',\r\n			\'smd_ebook_privs\'                 => \'Groups that can publish\',\r\n			\'smd_ebook_pubset\'                => \'Publishing\',\r\n			\'smd_ebook_stylesheet\'            => \'Stylesheet to include with the book\',\r\n			\'smd_ebook_section\'               => \'List articles from section\',\r\n			\'smd_ebook_settings\'              => \'Plugin configuration\',\r\n			\'smd_ebook_tab_name\'              => \'E-books\',\r\n			\'smd_ebook_test_complete\'         => \'Test complete\',\r\n			\'smd_ebook_textile\'               => \'Apply Textile to\',\r\n			\'smd_ebook_tidy\'                  => \'Tidy up temporary e-book files\',\r\n			\'smd_ebook_toc\'                   => \'Table of Contents\',\r\n			\'smd_ebook_toc_class\'             => \'ToC CSS class name\',\r\n			\'smd_ebook_toc_wraptag\'           => \'Render ToC as\',\r\n			\'smd_ebook_uid\'                   => \'Unique ID\',\r\n			\'smd_ebook_updated\'               => \'E-book info for file ID {id} updated\',\r\n			\'smd_ebook_usrset\'                => \'Rights\',\r\n		),\r\n	);\r\n\r\n	$thislang = get_pref(\'language\', \'en-gb\');\r\n	$exists = (isset($lang[$thislang][$what]));\r\n	$thislang = $exists ? $thislang : \'en-gb\';\r\n	return ($exists) ? strtr($lang[$thislang][$what], $atts) : $what;\r\n}', 'd9d6daaf116860ce81aa826f7e31dfb2', '', '', 4, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('sed_default_article_status', 0, 'Stef Dawson, Destry Wion & Netcarver', 'http://forum.textpattern.com/viewtopic.php?pid=249855#p249855', '0.4.1.12', 'Makes the Draft article status default.', '	<style type=\"text/css\">\n	div#sed_default_article_status td { vertical-align:top; }\n	div#sed_default_article_status code { font-weight:bold; font: 105%/130% \"Courier New\", courier, monospace; background-color: #FFFFCC;}\n	div#sed_default_article_status code.sed_code_tag { font-weight:normal; border:1px dotted #999; background-color: #f0e68c; display:block; margin:10px 10px 20px; padding:10px; }\n	div#sed_default_article_status a:link, div#sed_default_article_status a:visited { color: blue; text-decoration: none; border-bottom: 1px solid blue; padding-bottom:1px;}\n	div#sed_default_article_status a:hover, div#sed_default_article_status a:active { color: blue; text-decoration: none; border-bottom: 2px solid blue; padding-bottom:1px;}\n	div#sed_default_article_status h1 { color: #369; font: 20px Georgia, sans-serif; margin: 0; text-align: center; }\n	div#sed_default_article_status h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #369; font: 17px Georgia, sans-serif; }\n	div#sed_default_article_status h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n	div#sed_default_article_status ul ul { font-size:85%; }\n	div#sed_default_article_status h3 { color: #693; font: bold 12px Arial, sans-serif; letter-spacing: 1px; margin: 10px 0 0;text-transform: uppercase;}\n	</style>\n	<p>\n<div id=\"sed_default_article_status\"></p>\n	<p>Once you have enabled the plugin just go to <code>Admin &gt; Prefs &gt; Advanced &gt; Publish</code> and choose the default status for articles.<br />\nSave the preferences and from then on, your articles will have that status by default.</p>\n	<p></div></p>\n\n\n ', '\nif (@txpinterface==\'admin\')\n{\n	defined(\'sed_das_prefix\') || define( \'sed_das_prefix\' , \'sed_das\' );\n\n	global $textarray;\n	$textarray[sed_das_prefix.\'_default_status\'] = \'Default article status\';\n\n	register_callback(\'sed_das_article\', \'article_ui\', \'status\');\n	register_callback(\'sed_das_install\', \'plugin_lifecycle.sed_default_article_status\', \'installed\' );\n	register_callback(\'sed_das_enable\',  \'plugin_lifecycle.sed_default_article_status\', \'enabled\' );\n	register_callback(\'sed_das_disable\', \'plugin_lifecycle.sed_default_article_status\', \'disabled\' );\n	register_callback(\'sed_das_delete\',  \'plugin_lifecycle.sed_default_article_status\', \'deleted\'   );\n\n	function sed_das_status_list($name, $val)\n	{\n		$vals = array(\n			\'1\' => gTxt(\'draft\'),\n			\'2\' => gTxt(\'hidden\'),\n			\'3\' => gTxt(\'pending\'),\n			\'4\' => gTxt(\'live\'),\n			\'5\' => gTxt(\'sticky\'),\n			);\n\n		return selectInput($name, $vals, $val, \'\', \'\', $name);\n	}\n\n\n	function sed_das_install($evt, $stp=\'\')\n	{\n		$status = get_pref( sed_das_prefix.\'_default_status\', \'4\' );\n		set_pref( sed_das_prefix.\'_default_status\' , $status , \'publish\' , 10 , \'sed_das_status_list\' );\n	}\n\n\n	function sed_das_enable ($evt, $stp=\'\')\n	{\n		$name = sed_das_prefix.\'_default_status\';\n		safe_update( \'txp_prefs\', \"`type`=\'1\'\", \"`name`=\'$name\'\", 0 );\n	}\n\n\n	function sed_das_disable ($evt, $stp=\'\')\n	{\n		$name = sed_das_prefix.\'_default_status\';\n		safe_update( \'txp_prefs\', \"`type`=\'10\'\", \"`name`=\'$name\'\", 0 );\n	}\n\n\n	function sed_das_delete ($evt, $stp=\'\')\n	{\n		safe_delete( \'txp_prefs\' , \"`name`=\'\".sed_das_prefix.\"_default_status\'\" );\n	}\n\n	function sed_das_article($evt, $stp, $data, $rs)\n	{\n		global $step;\n\n		$js = \'\';\n\n		if (in_array($step, array(\'\', \'create\')))\n		{\n			$level = get_pref( sed_das_prefix.\'_default_status\', \'4\' );\n			$js = <<<EOJS\n<script type=\"text/javascript\">\njQuery(function() {\n	jQuery(\'input[id=\"status-$level\"]\').prop(\'checked\', true);\n});\n</script>\nEOJS;\n		}\n\n		return $data.$js;\n	}\n}\n', '\nif (@txpinterface==\'admin\')\n{\n	defined(\'sed_das_prefix\') || define( \'sed_das_prefix\' , \'sed_das\' );\n\n	global $textarray;\n	$textarray[sed_das_prefix.\'_default_status\'] = \'Default article status\';\n\n	register_callback(\'sed_das_article\', \'article_ui\', \'status\');\n	register_callback(\'sed_das_install\', \'plugin_lifecycle.sed_default_article_status\', \'installed\' );\n	register_callback(\'sed_das_enable\',  \'plugin_lifecycle.sed_default_article_status\', \'enabled\' );\n	register_callback(\'sed_das_disable\', \'plugin_lifecycle.sed_default_article_status\', \'disabled\' );\n	register_callback(\'sed_das_delete\',  \'plugin_lifecycle.sed_default_article_status\', \'deleted\'   );\n\n	function sed_das_status_list($name, $val)\n	{\n		$vals = array(\n			\'1\' => gTxt(\'draft\'),\n			\'2\' => gTxt(\'hidden\'),\n			\'3\' => gTxt(\'pending\'),\n			\'4\' => gTxt(\'live\'),\n			\'5\' => gTxt(\'sticky\'),\n			);\n\n		return selectInput($name, $vals, $val, \'\', \'\', $name);\n	}\n\n\n	function sed_das_install($evt, $stp=\'\')\n	{\n		$status = get_pref( sed_das_prefix.\'_default_status\', \'4\' );\n		set_pref( sed_das_prefix.\'_default_status\' , $status , \'publish\' , 10 , \'sed_das_status_list\' );\n	}\n\n\n	function sed_das_enable ($evt, $stp=\'\')\n	{\n		$name = sed_das_prefix.\'_default_status\';\n		safe_update( \'txp_prefs\', \"`type`=\'1\'\", \"`name`=\'$name\'\", 0 );\n	}\n\n\n	function sed_das_disable ($evt, $stp=\'\')\n	{\n		$name = sed_das_prefix.\'_default_status\';\n		safe_update( \'txp_prefs\', \"`type`=\'10\'\", \"`name`=\'$name\'\", 0 );\n	}\n\n\n	function sed_das_delete ($evt, $stp=\'\')\n	{\n		safe_delete( \'txp_prefs\' , \"`name`=\'\".sed_das_prefix.\"_default_status\'\" );\n	}\n\n	function sed_das_article($evt, $stp, $data, $rs)\n	{\n		global $step;\n\n		$js = \'\';\n\n		if (in_array($step, array(\'\', \'create\')))\n		{\n			$level = get_pref( sed_das_prefix.\'_default_status\', \'4\' );\n			$js = <<<EOJS\n<script type=\"text/javascript\">\njQuery(function() {\n	jQuery(\'input[id=\"status-$level\"]\').prop(\'checked\', true);\n});\n</script>\nEOJS;\n		}\n\n		return $data.$js;\n	}\n}\n', '14ad97bd98b29d262b338f011e5b2154', '', '', 3, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('aks_rss', 1, 'makss', 'http://textpattern.org.ua/plugins/aks_rss', '0.1.7', 'RSS parser plugin that fetches RSS feeds and shows the latest feed content inside TxP articles.', '<div id=\"aks\"><h1><span class=\"caps\">RSS</span> parser and aggregator</h1>\n\n	<p><a href=\"http://textpattern.org.ua/files/aks_rss.zip\">download link</a> / <a href=\"http://textpattern.org.ua/plugins/aks_rss\">homepage</a> / <a href=\"http://textpattern.org.ua/demo/demo-aks_rss\">demo</a></p>\n\n	<h2>Summary</h2>\n\n	<p>aks_rss is an <span class=\"caps\">RSS</span> parser plugin that fetches <span class=\"caps\">RSS</span> feeds and shows the latest feed content inside TxP articles. It is possible to fetch multiple feeds and aggregate the content in one output. aks_rss uses <a href=\"http://simplepie.org/\">SimplePie</a> 1.1.3 with <span class=\"caps\">IDN</span> support, see installation notes.</p>\n\n	<p>This plugin based on <a href=\"http://forum.textpattern.com/viewtopic.php?id=16794\">bit_rss</a> 0.4.1 by Bit Santos.</p>\n\n	<p>aks_rss plugin compatibility with <a href=\"http://textpattern.org/plugins/621/simplepie-plugin-for-textpattern\">SimplePie Plugin for Textpattern</a></p>\n\n	<h2>Installation</h2>\n\n	<ol>\n		<li>Install this plugin.</li>\n		<li>Copy <strong>simplepie.inc</strong> to <strong>textpattern/lib/</strong> directory.\n	<ul>\n		<li>If your need <span class=\"caps\">IDN</span> support, just copy files from <strong>idn</strong> directory to <strong>textpattern/lib/</strong> (<span class=\"caps\">IDN</span> &#8211; Internationalized Domain Names)</li>\n\n	</ul></li>\n		<li>Make sure that the TxP <strong>tmp</strong> directory is writeable. This is where Simplepie will put its cache files by default.</li>\n	</ol>\n\n	<h2>Changelog</h2>\n\n	<p>v0.1.1</p>\n\n	<ul>\n		<li>Rewrite some code for compatibility with last version Textpattern</li>\n		<li>Rewrite code for compatibility with last version SimplePie 1.1.3</li>\n		<li>Delete attributes &#8216;strip_ads&#8217; and &#8216;replace_headers&#8217; (SimplePie does not support them more)</li>\n\n		<li>Add attribute <code>&#39;target&#39;, &#39;limit_per_feed&#39;,&#39;offset_per_feed&#39;</code></li>\n		<li>Add tags <code>&lt;txp:author /&gt;</code> and <code>&lt;txp:excerpt /&gt;</code></li>\n		<li>Delete tag <code>&lt;txp:bit_author /&gt;</code>, please use <code>&lt;txp:author /&gt;</code></li>\n\n		<li><strong>BugFix:</strong> plugin bit_rss 0.4.1 destroy global variable $GLOBALS[&#8216;thisarticle&#8217;]</li>\n	</ul>\n\n	<p>v0.1.2</p>\n\n	<ul>\n		<li>Add support inline form.   <code>&lt;txp:aks_rss feed=&quot;http://abc.com/rss.xml&quot;&gt;Output form here&lt;/txp:aks_rss&gt;</code></li>\n\n	</ul>\n\n	<p>v0.1.3</p>\n\n	<ul>\n		<li><strong>Compatibility with SimplePie Plugin for Textpattern.</strong>\n	<ul>\n		<li>Tag <code>&lt;txp:feed&gt;http://abc.com/rss.xml,http://def.com/rss2.xml&lt;/txp:feed&gt;</code></li>\n\n	</ul></li>\n		<li>Add css class for each feed <code>&lt;txp:aks_rssclass /&gt;</code></li>\n		<li>Add attributes <code>wraptag, id, class</code></li>\n	</ul>\n\n	<p>v0.1.4</p>\n\n	<ul>\n		<li>Added admin interface in Extension tab</li>\n	</ul>\n\n	<p>v0.1.5\n	<ul>\n		<li>Add attribute <code>shortdesc</code> &#8211; short item description to <strong>N</strong> words</li>\n\n		<li>Fix bad naming:\n	<ul>\n		<li><code>&lt;txp:aks_rsstitle /&gt;</code> renamed to <code>&lt;txp:aks_rss_title /&gt;</code></li>\n		<li><code>&lt;txp:aks_rsslink /&gt;</code> renamed to <code>&lt;txp:aks_rss_link /&gt;</code></li>\n		<li><code>&lt;txp:aks_rssclass /&gt;</code> &#8211; please, use other tag &#8211; <code>&lt;txp:aks_rss_source /&gt;</code></li>\n\n	</ul></li>\n	</ul></p>\n\n	<p>v0.1.6\n	<ul>\n		<li>Add/fix attributes <code>wraptag</code>, <code>break</code></li>\n		<li>Add attributes <code>showtitle</code>, <code>alttitle</code>, <code>h</code></li>\n\n		<li>Add predefined form &#8211; attribute <code>simpleform</code></li>\n	</ul></p>\n\n	<h2>2do</h2>\n\n	<ul>\n		<li><del>Compatibility with SimplePie Plugin for Textpattern</del> possible <span class=\"caps\">DONE</span></li>\n\n	</ul>\n\n	<h2>Tags</h2>\n\n	<p><code>&lt;txp:aks_rss /&gt;</code></p>\n\n	<table>\n		<tr>\n			<th>attributes</th>\n\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>feed</td>\n			<td>&#8216;&#8217;</td>\n\n			<td><code>&#39;http://feed1.us/rss&#39;</code></td>\n			<td>one or more feeds</td>\n		</tr>\n		<tr>\n			<td>simplepie</td>\n			<td><code>txpath.&#39;/lib/simplepie.inc&#39;</code></td>\n\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>caching</td>\n			<td>&#8216;1&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n\n		</tr>\n		<tr>\n			<td>cache_dir</td>\n			<td><code>txpath.&#39;/tmp&#39;</code></td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n\n		<tr>\n			<td>cache_time</td>\n			<td>60</td>\n			<td>&#8216;&#8217;</td>\n			<td>in minutes</td>\n		</tr>\n		<tr>\n\n			<td>encode_html</td>\n			<td>&#8216;1&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>strip_html</td>\n\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>strip_atts</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>form</td>\n			<td>&#8216;rss&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>output form</td>\n\n		</tr>\n		<tr>\n			<td>simpleform</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>&#39;title&#39;</code></td>\n			<td>predefined form</td>\n		</tr>\n\n		<tr>\n			<td>shortdesc</td>\n			<td>all</td>\n			<td>&#8216;&#8217;</td>\n			<td>short descriptions to <strong>N</strong> words</td>\n		</tr>\n\n		<tr>\n			<td>limit</td>\n			<td>all items</td>\n			<td>&#8216;&#8217;</td>\n			<td>limit output items</td>\n		</tr>\n		<tr>\n\n			<td>limit_per_feed</td>\n			<td>all items</td>\n			<td>&#8216;&#8217;</td>\n			<td>limit items per each feed</td>\n		</tr>\n		<tr>\n			<td>offset</td>\n\n			<td>&#8216;0&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>number of entries to skip in output. <strong><span class=\"caps\"><span class=\"caps\">NOTE</span></span>:</strong> Don&#8217;t use <strong>offset</strong> with <strong>limit_per_feed</strong></td>\n\n		</tr>\n		<tr>\n			<td>offset_per_feed</td>\n			<td>&#8216;0&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>number of entries to skip per each feed</td>\n		</tr>\n\n		<tr>\n			<td>order</td>\n			<td>desc</td>\n			<td>asc</td>\n			<td>sort order</td>\n		</tr>\n		<tr>\n\n			<td>target</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;_blank&#8217;</td>\n			<td>href attribute</td>\n		</tr>\n		<tr>\n			<td>emptymsg</td>\n\n			<td>&#8216;No feed items could be found.&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>wraptag</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>ul</code></td>\n\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n			<td>break</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>li</code></td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n\n		<tr>\n			<td>id</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS id</td>\n		</tr>\n		<tr>\n			<td>class</td>\n\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS class</td>\n		</tr>\n		<tr>\n			<td>breakclass</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n\n			<td>(X)HTML/CSS class</td>\n		</tr>\n		<tr>\n			<td>showtitle</td>\n			<td>0</td>\n			<td>1</td>\n			<td>display feed title</td>\n\n		</tr>\n		<tr>\n			<td>alttitle</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>&#39;Some title&#39;</code></td>\n			<td>feed title</td>\n		</tr>\n\n		<tr>\n			<td>h</td>\n			<td><code>h3</code></td>\n			<td><code>h4</code></td>\n			<td>tag for feed title</td>\n		</tr>\n	</table>\n\n	<p>Example.  ( <code>==</code> &#8211; disable Textile)</p>\n\n<pre>==&lt;txp:aks_rss simpleform=&quot;title&quot; showtitle=&quot;1&quot;  limit=&quot;2&quot; feed=&quot;http://forum.textpattern.com/extern.php?type=RSS&amp;action=active&amp;fid=79&quot; /&gt;==\n</pre>\n\n	<p>or</p>\n\n<pre>==&lt;txp:aks_rss  wraptag=&quot;ul&quot; break=&quot;li&quot; showtitle=&quot;1&quot; limit=&quot;2&quot; feed=&quot;http://forum.textpattern.com/extern.php?type=RSS&amp;action=active&amp;fid=79&quot;&gt;\n&lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;&lt;/txp:aks_rss&gt;==\n</pre>\n\n	<p>Generate code:</p>\n\n<pre>&lt;h3&gt;&lt;a href=&quot;http://forum.textpattern.com/&quot; target=&#39;_blank&#39;&gt;Textpattern CMS Support Forum&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;\n	&lt;li&gt;&lt;a href=&quot;http://forum.textpattern.com/viewtopic.php?id=30263&amp;amp;action=new&quot; target=&quot;_blank&quot;&gt;aks_rss : RSS parser and&amp;#160;aggregator&lt;/a&gt;&lt;/li&gt;\n	&lt;li&gt;&lt;a href=&quot;http://forum.textpattern.com/viewtopic.php?id=32447&amp;amp;action=new&quot; target=&quot;_blank&quot;&gt;arc_twitter&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n</pre>\n\n	<p><strong>Support inline form:</strong><br />\n<code>&lt;txp:aks_rss&gt;</code> Output form here <code>&lt;/txp:aks_rss&gt;</code></p>\n\n	<p>Example.</p>\n\n<pre>==&lt;txp:aks_rss feed=&quot;http://rss.news.yahoo.com/rss/topstories&quot; wraptag=&quot;ul&quot;&gt;\n&lt;li class=&quot;&lt;txp:aks_rss_source /&gt;&quot;&gt;&lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;&lt;br /&gt;&lt;txp:body /&gt;&lt;hr/&gt;\n&lt;sub&gt;(&lt;txp:posted format=&quot;rfc822&quot; /&gt;) &lt;b&gt;&lt;txp:aks_rss_title /&gt;&lt;/b&gt;&lt;/sub&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;\n&lt;/txp:aks_rss&gt;==\n</pre>\n\n	<h3>Attributes:</h3>\n\n	<p><strong>feed</strong></p>\n\n	<p>Comma-delimited list of <span class=\"caps\">URL</span>s of the feeds to be used<br />\n\nExample: <code>feed=&#39;http://domain1.net/blog/feed&#39;</code><br />\nAggregate example: <code>feed=&#39;http://domain1.net/blog/feed,http://domain2.net/blog/atom,http://domain3.net/rss.xml&#39;</code></p>\n\n	<p><strong>target</strong> (optional)<br />\nhref attribute target<br />\nDefault: false<br />\n\nExample: <code>target=&#39;_blank&#39;</code></p>\n\n	<p><strong>simplepie</strong> (optional)<br />\nAbsolute path to the SimplePie class file (simplepie.inc).<br />\nDefault: <code>txpath.&#39;/lib/simplepie.inc&#39;</code><br />\nExample: <code>simplepie=&#39;/var/www/httpdocs/textpattern/lib/simplepie.inc&#39;</code></p>\n\n	<p><strong>caching</strong> (optional)<br />\nWhether caching is enabled or not.<br />\nCaching is disabled if it is set to false, no, or 0.<br />\nDefault: true</p>\n\n	<p><strong>cache_dir</strong> (optional)<br />\n\nAbsolute path to the SimplePie cache directory. This directory must be writeable.<br />\nDefault: <code>txpath.&#39;/tmp&#39;</code><br />\nExample: <code>simplepie=&#39;/var/www/httpdocs/textpattern/tmp&#39;</code></p>\n\n	<p><strong>cache_time</strong> (optional)<br />\nThe length of time (in minutes) that the cache files are stored before being refreshed.<br />\n\nDefault: 60</p>\n\n	<p><strong>strip_html</strong> (optional)<br />\nSimplePie strips potentially dangerous/annoying tags by default.<br />\nThe list of tags to be stripped can be changed by setting this attribute to a comma-delimited list of <span class=\"caps\">HTML</span> tags.<br />\nDefault: <code>&#39;base&#39;, &#39;blink&#39;, &#39;body&#39;, &#39;doctype&#39;, &#39;embed&#39;, &#39;font&#39;, &#39;form&#39;, &#39;frame&#39;, &#39;frameset&#39;, &#39;html&#39;, &#39;iframe&#39;, &#39;input&#39;, &#39;marquee&#39;, &#39;meta&#39;, &#39;noscript&#39;, &#39;object&#39;, &#39;param&#39;, &#39;script&#39;, &#39;style&#39;</code></p>\n\n	<p><strong>strip_atts</strong> (optional)<br />\nSimplePie strips potentially dangerous/annoying attributes by default.<br />\nThe list of attributes to be stripped can be changed by setting this attribute to a comma-delimited list of <span class=\"caps\">HTML</span> attributes.<br />\nDefault: <code>&#39;bgsound&#39;, &#39;class&#39;, &#39;expr&#39;, &#39;id&#39;, &#39;style&#39;, &#39;onclick&#39;, &#39;onerror&#39;, &#39;onfinish&#39;, &#39;onmouseover&#39;, &#39;onmouseout&#39;, &#39;onfocus&#39;, &#39;onblur&#39;, &#39;lowsrc&#39;, &#39;dynsrc&#39;</code></p>\n\n	<p><strong>encode_html</strong> (optional)<br />\nSets whether or not to encode the <span class=\"caps\">HTML</span> tags instead of stripping them<br />\n<span class=\"caps\">HTML</span> tags will be encoded when this is set to &#8216;1&#8217;, &#8216;true&#8217;, or &#8216;yes&#8217;.<br />\n\nDefault: &#8216;0&#8217;</p>\n\n	<p><strong>form</strong> (optional)<br />\nForm to be used to display each item in the feed.<br />\nThe article tags, <code>&lt;txp:title /&gt;, &lt;txp:posted /&gt;, &lt;txp:body /&gt;, &lt;txp:author /&gt;, &lt;txp:permlink /&gt;</code> will work normally in the form used. <code>&lt;txp:category1 /&gt;, &lt;txp:category2 /&gt;, &lt;txp:excerpt /&gt;</code> will also function normally with appropriate feeds.<br />\n\nDefault: &#8216;rss&#8217;</p>\n\n	<p><strong>simpleform</strong> (optional)<br />\nPredefined form.<br />\nNow support only two predefined form: <code>title</code>, <code>body</code></p>\n\n	<p><strong>limit</strong> (optional)<br />\nMaximum number of items to fetch.<br />\nDefault: number of items in the feed</p>\n\n	<p><strong>limit_per_feed</strong> (optional)<br />\nMaximum number of items to fetch per feed.<br />\n\nDefault: all items</p>\n\n	<p><strong>offset</strong> (optional)<br />\nNumber of entries to skip.<br />\nDefault: 0</p>\n\n	<p><strong>order</strong> (optional)<br />\n\nOrder of items based on date. asc (ascending – oldest first) or desc (descending – newest first).<br />\nDefault: desc</p>\n\n	<p><strong>emptymsg</strong> (optional)<br />\nThe text to output if no feed items were retrieved.<br />\nDefault: No feed items could be found.</p>\n\n	<h2>Form Tags</h2>\n\n	<p><code>&lt;txp:aks_rss_title /&gt;</code><br />\nArticle form tag. This tag outputs the title of the parent feed of the item.<br />\nExample: <code>&lt;txp:aks_rss_title /&gt;</code></p>\n\n	<p><code>&lt;txp:aks_rss_link /&gt;</code><br />\nArticle form tag. This tag outputs the link of the parent feed of the item. It can be used as a container tag or as a single tag. Its attributes are exactly like the attributes of permlink except for the title attribute – the title attribute of the resulting link will be the feed title by default.<br />\nExample 1: <code>&lt;txp:aks_rss_link /&gt;</code><br />\n\nExample 2: <code>&lt;txp:aks_rss_link&gt;&lt;txp:aks_rss_title /&gt;&lt;/txp:aks_rss_link&gt;</code></p>\n\n	<p><code>&lt;txp:aks_rss_source /&gt;</code><br />\nArticle form tag. This tag outputs the source of the parent feed. Useful for class or other<br />\nExample: <code>class=&quot;&lt;txp:aks_rss_source /&gt;&quot;</code></p>\n\n	<p><del>&lt;txp:aks_rssclass /&gt;</del><br />\n\n<del>Article form tag. This tag outputs css class for each feed</del><br />\n<del>Example: &lt;txp:aks_rssclass /&gt;</del></p>\n\n	<h2>Tags</h2>\n\n	<p><code>&lt;txp:feed&gt;</code> &#8211; compatibility with SimplePie Plugin for Textpattern</p>\n\n	<table>\n		<tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n\n		<tr>\n			<td>feed</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>&#39;http://feed1.us/rss,http://feed2.de/feed&#39;</code></td>\n			<td>or use <code>&lt;txp:feed&gt;http://feed1.us/rss&lt;/txp:feed&gt;</code></td>\n		</tr>\n\n		<tr>\n			<td>items</td>\n			<td>all items</td>\n			<td>&#8216;&#8217;</td>\n			<td>limit output items</td>\n		</tr>\n		<tr>\n\n			<td>showdesc</td>\n			<td>&#8216;1&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>show descriptions</td>\n		</tr>\n		<tr>\n			<td>shortdesc</td>\n\n			<td>all</td>\n			<td>&#8216;&#8217;</td>\n			<td>short descriptions to <strong>N</strong> words</td>\n		</tr>\n		<tr>\n			<td>showdate</td>\n\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;1&#8217;</td>\n			<td>logical value</td>\n		</tr>\n		<tr>\n			<td>target</td>\n			<td>&#8216;_blank&#8217;</td>\n\n			<td>&#8216;_blank&#8217;</td>\n			<td>href attribute</td>\n		</tr>\n		<tr>\n			<td>txt1</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;prefix&#8217;</td>\n\n			<td>text before item title</td>\n		</tr>\n		<tr>\n			<td>txt2</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;suffix&#8217;</td>\n			<td>text affter item title</td>\n\n		</tr>\n		<tr>\n			<td>error</td>\n			<td>&#8216;No feed items could be found.&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n		</tr>\n		<tr>\n\n			<td>listtype</td>\n			<td><code>ul</code></td>\n			<td><code>ol</code></td>\n			<td>It&#8217;s wraptag(for compatibility )</td>\n		</tr>\n		<tr>\n\n			<td>break</td>\n			<td><code>li</code></td>\n			<td>&#8216;&#8217;</td>\n			<td>break tag</td>\n		</tr>\n		<tr>\n			<td>id</td>\n\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS id</td>\n		</tr>\n		<tr>\n			<td>class</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n\n			<td>(X)HTML/CSS class</td>\n		</tr>\n		<tr>\n			<td>breakclass</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS class</td>\n\n		</tr>\n		<tr>\n			<td>showtitle</td>\n			<td>1</td>\n			<td>0</td>\n			<td> Determines whether the built-in feed title is displayed or not</td>\n\n		</tr>\n		<tr>\n			<td>alttitle</td>\n			<td>&#8216;&#8217;</td>\n			<td><code>some title</code></td>\n			<td> Displays a custom title in place of the feed&#8217;s built-in title</td>\n\n		</tr>\n		<tr>\n			<td>h</td>\n			<td>h3</td>\n			<td>h4</td>\n			<td>Define the html header used for the title</td>\n		</tr>\n\n	</table>\n\n	<p><del>Unsupported attributes from SimplePie Plugin for Textpattern:  shortdesc, showtitle, alttitle, h</del></p>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n	background-color: #E3E3DB;\n	border:1px solid;\n	border-color:#ddd #999 #888 #ddd;\n	padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #000000;\nmargin: 1.5em 0;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'aks_rss_install();\r\n\r\nif (@txpinterface == \'admin\') {\r\n    add_privs(\'aks_rss_admin_tab\', \'1,2\');\r\n    register_tab(\"extensions\", \"aks_rss_admin_tab\", \"aks_rss\");\r\n    register_callback(\"aks_rss_admin_tab\", \"aks_rss_admin_tab\");\r\n}\r\n\r\nfunction aks_rss_install($atts=array()){\r\n	global $prefs,$txpcfg;\r\n	if (!isset($prefs[\'aks_rss_simplepie\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_simplepie\', val=\'\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_cache_dir\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_cache_dir\', val=\'\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_cache_time\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_cache_time\', val=\'60\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_target\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_target\', val=\'_blank\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_emptymsg\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_emptymsg\', val=\'No feed items.\', prefs_id=\'1\'\"); }\r\n\r\n	if(! $prefs[\'aks_rss_simplepie\']){$prefs[\'aks_rss_simplepie\']=txpath.\'/lib/simplepie.inc\';}\r\n	if(! $prefs[\'aks_rss_cache_dir\']){$prefs[\'aks_rss_cache_dir\']=txpath.\'/tmp\';}\r\n\r\n}\r\n\r\n\r\nif (! function_exists(\'feed\')) {\r\nfunction feed($atts, $thing=\'\') {      // Compatibility with SimplePie Plugin for Textpattern\r\n  global $prefs;\r\n  extract(lAtts(array( \'feed\'=>\'\', \'listtype\'=>\'ul\', \'break\'=>\'li\', \'id\'=>\'\', \'class\'=>\'\', \'breakclass\'=>\'\', \'h\'=>\'h3\', \'items\'=>\'\', \'showtitle\'=>1, \'alttitle\'=>\'\', \'showdesc\'=>1, \'shortdesc\'=>\'\', \'showdate\'=>\'\', \'target\'=>$prefs[\'aks_rss_target\'], \'txt1\'=>\'\', \'txt2\'=>\'\', \'error\'=>$prefs[\'aks_rss_emptymsg\']),$atts));\r\n\r\n  if(!$feed){$feed=$thing;} if(!$feed){return $error; }\r\n  $showdesc=aks_checkBool($showdesc);\r\n  $showtitle=aks_checkBool($showtitle);\r\n\r\n  $form=\"<txp:permlink>$txt1<txp:title />$txt2</txp:permlink>\";\r\n  if($showdate){$form.=\" <span class=\'date\'><txp:posted format=\'rfc822\' /></span>\";}\r\n  if($showdesc){$form.=\" <txp:body />\";}\r\n\r\n  return aks_rss(array(\'feed\'=>$feed,\'limit\'=>$items,  \'target\'=>$target, \'wraptag\'=>$listtype, \'break\'=>$break, \'id\'=>$id, \'class\'=>$class, \'breakclass\'=>$breakclass, \'emptymsg\'=>$error, \'h\'=>$h, \'shortdesc\'=>$shortdesc, \'showtitle\'=>$showtitle, \'alttitle\'=>$alttitle), $form);\r\n}\r\n\r\n}\r\n\r\n\r\nfunction aks_rss($atts,$thing=\'\') {\r\n    global $pretext,$prefs,$txpcfg;\r\n    extract($pretext);\r\n    extract(lAtts(array(\r\n        \'feed\'       => \'\',\r\n        \'simplepie\'  => $prefs[\'aks_rss_simplepie\'],\r\n        \'caching\'    => \'1\',\r\n        \'cache_dir\'  => $prefs[\'aks_rss_cache_dir\'],\r\n        \'cache_time\' => $prefs[\'aks_rss_cache_time\'],\r\n        \'encode_html\' => \'1\',\r\n        \'strip_html\' => \'\',\r\n        \'strip_atts\' => \'\',\r\n        \'remove_div\' => \'1\',\r\n        \'form\'       => \'\',\r\n        \'simpleform\'       => \'\',\r\n\r\n        \'shortdesc\'      => \'\',\r\n        \'limit\'      => \'\',\r\n        \'limit_per_feed\' => \'0\',\r\n        \'offset\'     => \'0\',\r\n        \'offset_per_feed\' => \'0\',\r\n        \'order\'      => \'desc\',\r\n\r\n        \'wraptag\'  => \'\',\r\n        \'break\'  => \'\',\r\n\r\n        \'id\'  => \'\',\r\n        \'class\'  => \'\',\r\n        \'breakclass\'  => \'\',\r\n\r\n        \'target\'      => $prefs[\'aks_rss_target\'],\r\n        \'emptymsg\'   => $prefs[\'aks_rss_emptymsg\'],\r\n\r\n	\'showtitle\' => 0,		// attributes for compatibility with SimplePie Plugin for Textpattern...\r\n	\'alttitle\'  => \'\',\r\n	\'h\'  => \'h3\'\r\n\r\n    ),$atts));\r\n\r\n    $caching = aks_checkBool($caching);\r\n    $encode_html = aks_checkBool($encode_html);\r\n    $remove_div = aks_checkBool($remove_div);\r\n    if($target){$target=\" target=\'$target\'\";}\r\n\r\n    if(!$thing && !$form){$form=\'rss\';}\r\n    if($form){ $Form = safe_field(\'Form\',\'txp_form\',\"name=\'$form\'\"); }else{ $Form=$thing; }\r\n\r\n    if($simpleform){\r\n		if($simpleform==\'title\'){ $Form=\"<txp:permlink><txp:title /></txp:permlink>\"; }\r\n		if($simpleform==\'body\'){ $Form=\"<txp:body />\"; }\r\n		if(!$wraptag){$wraptag=\"ul\";}\r\n		if(!$break){$break=\"li\";}\r\n    }\r\n\r\n    if(!$Form) {\r\n        return \"The form for aks_rss could not be found.\";\r\n    }else{\r\n        require_once($simplepie);\r\n	$Form=preg_replace(\"/<txp:permlink/i\", \"<txp:aks_itemlink\".$target, $Form);\r\n	$Form=preg_replace(\"/<\\/txp:permlink/i\", \"</txp:aks_itemlink\", $Form);\r\n	$Form=preg_replace(\"/<txp:author/i\", \"<txp:aks_author\", $Form);\r\n\r\n        $feeds = preg_split(\"/[ \\r\\n\\t,]+/is\", $feed);\r\n        $feedcount = count($feeds);\r\n        $rssArray = array();\r\n        $titlerecord = array();\r\n        $linkrecord = array();\r\n\r\n        $feedcount = count($feeds);\r\n\r\n        foreach($feeds as $f) {\r\n            $feed = new SimplePie();\r\n            $feed->set_feed_url($f);\r\n\r\n            $feed->enable_cache($caching);\r\n            if($caching) {\r\n                $feed->set_cache_location($cache_dir);\r\n                $feed->set_autodiscovery_cache_duration($cache_time*60);\r\n            }\r\n\r\n            $feed->remove_div($remove_div);\r\n            $feed->encode_instead_of_strip($encode_html);\r\n\r\n            if(!empty($strip_html) && preg_match(\"/^[a-zA-Z0-9, \\r\\n\\t]+$/is\", $strip_html)) {\r\n                $tags = preg_split(\"/[ \\r\\n\\t,]+/is\", $strip_html);\r\n                $feed->strip_htmltags($tags);\r\n            }else {\r\n                $feed->strip_htmltags();\r\n            }\r\n            if(!empty($strip_atts) && preg_match(\"/^[a-zA-Z, \\r\\n\\t]+$/is\", $strip_atts)) {\r\n		  $atts = preg_split(\"/[ \\r\\n\\t,]+/is\", $strip_atts);\r\n                $feed->strip_attributes($atts);\r\n            }else {\r\n                $feed->strip_attributes();\r\n            }\r\n\r\n            $success = $feed->init();\r\n            if($success) {\r\n                $feeditems = $feed->get_items($offset_per_feed,$limit_per_feed);\r\n\r\n                // Give each item information about its parent feed\r\n                foreach($feeditems as $feeditem) {\r\n                    $titlerecord[$feeditem->get_id()] = $feed->get_title();\r\n                    $linkrecord[$feeditem->get_id()] = $feed->get_link();\r\n                }\r\n		if(! $alttitle){ $alttitle=$feed->get_title(); }\r\n		if($showtitle && $alttitle){ $showtitle=doTag(\"<a href=\\\"\".$feed->get_link().\"\\\"$target>\".$alttitle.\"</a>\",$h); }else{$showtitle=\'\';}\r\n\r\n                $rssArray[] = $feeditems;\r\n            }else {\r\n                dmp($feed->error);\r\n            }\r\n        }\r\n\r\n        // Check that the feeding was successful\r\n        if(count($rssArray)) {\r\n            // Skip quicksort if there is only one feed\r\n            $finalItems = ($feedcount > 1)\r\n                ? aks_parseItems($rssArray)\r\n                : $rssArray[0];\r\n\r\n            $limit   = (!empty($limit) && $limit <= count($finalItems)) ? $limit : count($finalItems);\r\n//            $offset  = (!empty($offset) && $offset < $limit) ? $offset : 0;\r\n//            $offset  = (!empty($offset) ) ? $offset : 0;\r\n\r\n            $items   = $finalItems;\r\n//echo $limit.\"=\".$offset;\r\n//            $order   = (!empty($order) && preg_match(\"/asc/i\", $order) ) ? strToLower($order) : \"desc\";\r\n\r\n            if(preg_match(\"/asc/i\", $order) ) { $items = array_reverse($items); }\r\n\r\n            $items = array_slice($items, $offset, $limit); $temp_thisarticle=$GLOBALS[\'thisarticle\'];\r\n            foreach($items as $item) {\r\n                // Assign the parent feed title\r\n                if(!empty($titlerecord[$item->get_id()])) { $out[\'custom_1\'] = $titlerecord[$item->get_id()]; }\r\n                // Assign the parent feed link\r\n                if(!empty($linkrecord[$item->get_id()])) {  $out[\'custom_2\'] = $linkrecord[$item->get_id()]; }\r\n                // Check and retrieve date\r\n                if($item->get_date()) { $out[\"posted\"] = $item->get_date(\'U\'); }\r\n\r\n                // Check and retrieve authors\r\n                if($item->get_authors()) {\r\n                    $authors = $item->get_authors();\r\n                    $authArray = array();\r\n                    foreach($authors as $author) { $authArray[] = $author->get_name(); }\r\n                    $out[\'author\'] = join(\", \", $authArray);\r\n                }\r\n\r\n                // Assign the first two categories found to category1 and category2\r\n                if($item->get_categories()) {\r\n                    $cats = $item->get_categories(0,1);\r\n                    $out[\'category1\'] = $cats[0]->get_label();\r\n                    if(!empty($cats[1])) { $out[\'category2\'] = $cats[1]->get_label(); }\r\n                }\r\n		$out[\'title\'] = $item->get_title();\r\n		$out[\'permlink\'] = $item->get_permalink();\r\n		$out[\'body\'] = aks_rss_short($item->get_content(), $shortdesc);\r\n		$out[\'excerpt\'] = aks_rss_short($item->get_description(), $shortdesc);\r\n		$GLOBALS[\'thisarticle\'] = $out;\r\n		$articles[] = parse($Form);\r\n            }\r\n            $GLOBALS[\'thisarticle\']=$temp_thisarticle;\r\n            if(!$articles){return \"\";}\r\n            return $showtitle . doWrap($articles, $wraptag, $break, $class, $breakclass, \'\', \'\', $id);\r\n\r\n        } else {\r\n            return $showtitle . $emptymsg;\r\n        }\r\n    }\r\n}\r\n//========================================\r\nfunction aks_author($atts) { global $thisarticle; $names = $thisarticle[\'author\']; return $names; }\r\nfunction aks_itemlink($atts, $thing = NULL) {\r\n    global $thisarticle;\r\n    extract(lAtts(array( \'target\' => \'\', \'class\' => \'\', \'style\' => \'\', \'title\' => \'\'), $atts));\r\n    $url=$GLOBALS[\'thisarticle\'][\'permlink\'];\r\n    if ($url) {   if ($thing === NULL) { return $url;  }\r\n        return tag(parse($thing), \'a\', \' href=\"\'.$url.\'\"\'.\r\n            ($title ? \' title=\"\'.$title.\'\"\' : \'\').\r\n            ($style ? \' style=\"\'.$style.\'\"\' : \'\').\r\n            ($target ? \' target=\"\'.$target.\'\"\' : \'\').\r\n            ($class ? \' class=\"\'.$class.\'\"\' : \'\')\r\n        );\r\n    }\r\n}\r\n\r\nfunction aks_rss_short($txt=\"\", $shortdesc=0, $short=\"...\") {\r\n	$words = explode(\" \", $txt); if($shortdesc && count($words) >$shortdesc){$txt = join(\" \",array_slice($words, 0, $shortdesc)).$short; }\r\n	return $txt;\r\n}\r\n\r\n\r\n\r\n// Output the title of the parent feed of the item ---------------------\r\nfunction aks_rss_title($atts) {\r\n    global $thisarticle;\r\n    return $thisarticle[\'custom_1\'];\r\n}\r\n\r\n\r\nfunction aks_rss_source($atts) {\r\n    global $thisarticle;\r\n    preg_match(\"#(https?://)(www\\.)?(.*?)(/|$)#i\",$thisarticle[\'custom_2\'],$mm);\r\n    $source=preg_replace(\'/\\./\',\'-\',$mm[3]);\r\n    return $source;\r\n}\r\n\r\n\r\n\r\n\r\n// Output the link of the parent feed of the item\r\nfunction aks_rss_link($atts, $enclosed = null) {\r\n    global $thisarticle;\r\n    extract(lAtts(array( \'class\' => \'\', \'id\'    => \'\', \'style\' => \'\', \'title\' => $thisarticle[\'custom_1\']),$atts));\r\n\r\n    $href  = \" href=\'\".$thisarticle[\'custom_2\'].\"\'\";\r\n    $class = empty($class) ? \"\" : \" class=\'\".$class.\"\'\";\r\n    $id    = empty($id) ? \"\" : \" id=\'\".$id.\"\'\";\r\n    $title = empty($title) ? \"\" : \" title=\'\".$title.\"\'\";\r\n\r\n    return empty($enclosed)\r\n        ? $thisarticle[\'custom_2\']\r\n        : tag(parse($enclosed), \'a\', $href.$class.$id);\r\n}\r\n\r\n\r\nfunction aks_checkBool($value) { return ($value == \"1\" || $value == \"true\" || $value == \"yes\"); }\r\nfunction aks_parseItems($itemArray) {\r\n    $allItems = array();\r\n    for($i = 0; $i < count($itemArray); $i++) {\r\n        $allItems = array_merge($allItems, $itemArray[$i]);\r\n    }\r\n    return aks_quickSort($allItems);\r\n}\r\n\r\nfunction aks_quickSort($items) {\r\n    if(count($items) > 1) {\r\n        $pivot = array_pop($items);\r\n        $less = array();\r\n        $more = array();\r\n        for($i = 0; $i < count($items); $i++) {\r\n            $cItem = $items[$i];\r\n            if($cItem->get_date(\'U\') <= $pivot->get_date(\'U\')) {\r\n                $more[] = $cItem;\r\n            }else {\r\n                $less[] = $cItem;\r\n            }\r\n        }\r\n        return array_merge(aks_quickSort($less), array($pivot), aks_quickSort($more));\r\n    }else {\r\n        return $items;\r\n    }\r\n}\r\n\r\nfunction aks_gismeteo($atts) {           // for internal use only, no support. possibly it will be deleted in future version\r\n	global $txpcfg;\r\n	$argv  = $atts;\r\n	if(! isset($argv[\'gisid\']) || ! $argv[\'gisid\']){ return; }\r\n	if (!class_exists(\'SimplePie\')) require_once(txpath.\'/lib/simplepie.inc\');\r\n	$feed = new SimplePie();\r\n	$feed->set_feed_url(\"http://informer.gismeteo.ua/rss/\".$argv[\'gisid\'].\".xml\");\r\n	$feed->set_cache_location(txpath . \'/tmp\');\r\n	$feed->set_autodiscovery_cache_duration(36027);\r\n	$success = $feed->init();\r\n	if ($success && $feed->data) {\r\n		$output = \'\';\r\n		$max = $feed->get_item_quantity();\r\n		for($x=0; $x<$max; $x++) {\r\n			$item = $feed->get_item($x);\r\n			$title = SEntities($item->get_title());\r\n			$title=preg_replace(\'/\\,.*$/i\',\'\',$title);\r\n			$tt=preg_replace(\'/:.*$/i\',\'\',$title);\r\n			if ($x == 0){$output .= \"<div id=\'pogoda\'><h3>$tt</h3><br /><ul>\";}\r\n			$title=preg_replace(\'/^.*: ?/i\',\'\',$title);\r\n			$desc = SEntities($item->get_description());\r\n			$desc=preg_replace(\'/давлен.*?, ?/i\',\'<br />\',$desc);\r\n			$output .= \"<li><b>$title</b><br />$desc</li>\";\r\n		}\r\n		$output .= \'</ul></div>\';\r\n	}else{	$output= \'\';\r\n	}\r\n	return $output;\r\n}\r\n\r\nfunction SEntities($s = \'\') {\r\n $inputs = array(\'&#8211;\', \'&#8212;\', \'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8230;\', \'&#91;\', \'&#93;\');\r\n $outputs = array(\'-\', \'--\', \"\'\", \"\'\", \'\"\', \'\"\', \'...\', \'[\', \']\');\r\n $s = str_replace($inputs, $outputs, $s);\r\n return $s;\r\n}\r\n\r\n//-------------------------------------------------------------\r\n\r\nfunction aks_rss_admin_tab($event, $step) {\r\n	global $prefs,$txpcfg;\r\n\r\n        if (ps(\"save\")) {\r\n            pagetop(\"aks_rss Prefs\", \"Preferences Saved\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_simplepie\')).\"\'\",\"name = \'aks_rss_simplepie\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_cache_dir\')).\"\'\",\"name = \'aks_rss_cache_dir\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_cache_time\')).\"\'\",\"name = \'aks_rss_cache_time\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_target\')).\"\'\",\"name = \'aks_rss_target\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_emptymsg\')).\"\'\",\"name = \'aks_rss_emptymsg\' and prefs_id =\'1\'\");\r\n            header(\"Location: index.php?event=aks_rss_admin_tab\");\r\n        } else {\r\n		pagetop(\"aks_rss Prefs\");\r\n        }\r\n	$GLOBALS[\'prefs\'] = get_prefs();\r\n\r\n        echo startTable(\'list\').\r\n        form(\r\n        tr(tdcs(hed(\"aks_rss Preferences\",1),3)).\r\n        tr(\r\ntda(gTxt(\'Absolute path to simplepie.inc:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_simplepie\",$prefs[\'aks_rss_simplepie\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"blank for default\")\r\n\r\n        ).\r\n        tr(\r\ntda(gTxt(\'Absolute path to cache_dir:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_cache_dir\",$prefs[\'aks_rss_cache_dir\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"blank for default\")\r\n        ).\r\n        tr(\r\ntda(gTxt(\'Cache feed time in minutes:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_cache_time\",$prefs[\'aks_rss_cache_time\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"\")\r\n        ).\r\n\r\n        tr(\r\ntda(gTxt(\'\"target\" attribute for feed items &lt;a href=...:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_target\",$prefs[\'aks_rss_target\'],\'edit\',\'\',\'\',\'40\'), \' \') .tda(\"Sample: <b>_blank</b>\")\r\n        ).\r\n        tr(\r\n            tda(gTxt(\'No items error msg:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_emptymsg\",$prefs[\'aks_rss_emptymsg\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"\")\r\n        ).\r\n\r\n        tr(\r\n            tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_rss_admin_tab\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\")\r\n        )). endTable();\r\n}\r\n\r\n\r\n\r\n//--------------------------------------------------- BAD naming. Only for compatibility.\r\nfunction aks_rsstitle($atts){ return aks_rss_title($atts); }\r\nfunction aks_rsslink($atts, $enclosed = null){ return aks_rss_link($atts, $enclosed); }\r\n\r\nfunction aks_rssclass($atts) {\r\n    global $thisarticle;\r\n    preg_match(\"#(https?://)(www\\.)?(.*?)(/|$)#i\",$thisarticle[\'custom_2\'],$mm);\r\n    $class=preg_replace(\'/\\./\',\'-\',$mm[3]);\r\n    return \"class=\'$class\'\";\r\n}\r\n', 'aks_rss_install();\r\n\r\nif (@txpinterface == \'admin\') {\r\n    add_privs(\'aks_rss_admin_tab\', \'1,2\');\r\n    register_tab(\"extensions\", \"aks_rss_admin_tab\", \"aks_rss\");\r\n    register_callback(\"aks_rss_admin_tab\", \"aks_rss_admin_tab\");\r\n}\r\n\r\nfunction aks_rss_install($atts=array()){\r\n	global $prefs,$txpcfg;\r\n	if (!isset($prefs[\'aks_rss_simplepie\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_simplepie\', val=\'\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_cache_dir\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_cache_dir\', val=\'\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_cache_time\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_cache_time\', val=\'60\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_target\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_target\', val=\'_blank\', prefs_id=\'1\'\"); }\r\n	if (!isset($prefs[\'aks_rss_emptymsg\'])){ $rs = safe_insert(\'txp_prefs\', \"name=\'aks_rss_emptymsg\', val=\'No feed items.\', prefs_id=\'1\'\"); }\r\n\r\n	if(! $prefs[\'aks_rss_simplepie\']){$prefs[\'aks_rss_simplepie\']=txpath.\'/lib/simplepie.inc\';}\r\n	if(! $prefs[\'aks_rss_cache_dir\']){$prefs[\'aks_rss_cache_dir\']=txpath.\'/tmp\';}\r\n\r\n}\r\n\r\n\r\nif (! function_exists(\'feed\')) {\r\nfunction feed($atts, $thing=\'\') {      // Compatibility with SimplePie Plugin for Textpattern\r\n  global $prefs;\r\n  extract(lAtts(array( \'feed\'=>\'\', \'listtype\'=>\'ul\', \'break\'=>\'li\', \'id\'=>\'\', \'class\'=>\'\', \'breakclass\'=>\'\', \'h\'=>\'h3\', \'items\'=>\'\', \'showtitle\'=>1, \'alttitle\'=>\'\', \'showdesc\'=>1, \'shortdesc\'=>\'\', \'showdate\'=>\'\', \'target\'=>$prefs[\'aks_rss_target\'], \'txt1\'=>\'\', \'txt2\'=>\'\', \'error\'=>$prefs[\'aks_rss_emptymsg\']),$atts));\r\n\r\n  if(!$feed){$feed=$thing;} if(!$feed){return $error; }\r\n  $showdesc=aks_checkBool($showdesc);\r\n  $showtitle=aks_checkBool($showtitle);\r\n\r\n  $form=\"<txp:permlink>$txt1<txp:title />$txt2</txp:permlink>\";\r\n  if($showdate){$form.=\" <span class=\'date\'><txp:posted format=\'rfc822\' /></span>\";}\r\n  if($showdesc){$form.=\" <txp:body />\";}\r\n\r\n  return aks_rss(array(\'feed\'=>$feed,\'limit\'=>$items,  \'target\'=>$target, \'wraptag\'=>$listtype, \'break\'=>$break, \'id\'=>$id, \'class\'=>$class, \'breakclass\'=>$breakclass, \'emptymsg\'=>$error, \'h\'=>$h, \'shortdesc\'=>$shortdesc, \'showtitle\'=>$showtitle, \'alttitle\'=>$alttitle), $form);\r\n}\r\n\r\n}\r\n\r\n\r\nfunction aks_rss($atts,$thing=\'\') {\r\n    global $pretext,$prefs,$txpcfg;\r\n    extract($pretext);\r\n    extract(lAtts(array(\r\n        \'feed\'       => \'\',\r\n        \'simplepie\'  => $prefs[\'aks_rss_simplepie\'],\r\n        \'caching\'    => \'1\',\r\n        \'cache_dir\'  => $prefs[\'aks_rss_cache_dir\'],\r\n        \'cache_time\' => $prefs[\'aks_rss_cache_time\'],\r\n        \'encode_html\' => \'1\',\r\n        \'strip_html\' => \'\',\r\n        \'strip_atts\' => \'\',\r\n        \'remove_div\' => \'1\',\r\n        \'form\'       => \'\',\r\n        \'simpleform\'       => \'\',\r\n\r\n        \'shortdesc\'      => \'\',\r\n        \'limit\'      => \'\',\r\n        \'limit_per_feed\' => \'0\',\r\n        \'offset\'     => \'0\',\r\n        \'offset_per_feed\' => \'0\',\r\n        \'order\'      => \'desc\',\r\n\r\n        \'wraptag\'  => \'\',\r\n        \'break\'  => \'\',\r\n\r\n        \'id\'  => \'\',\r\n        \'class\'  => \'\',\r\n        \'breakclass\'  => \'\',\r\n\r\n        \'target\'      => $prefs[\'aks_rss_target\'],\r\n        \'emptymsg\'   => $prefs[\'aks_rss_emptymsg\'],\r\n\r\n	\'showtitle\' => 0,		// attributes for compatibility with SimplePie Plugin for Textpattern...\r\n	\'alttitle\'  => \'\',\r\n	\'h\'  => \'h3\'\r\n\r\n    ),$atts));\r\n\r\n    $caching = aks_checkBool($caching);\r\n    $encode_html = aks_checkBool($encode_html);\r\n    $remove_div = aks_checkBool($remove_div);\r\n    if($target){$target=\" target=\'$target\'\";}\r\n\r\n    if(!$thing && !$form){$form=\'rss\';}\r\n    if($form){ $Form = safe_field(\'Form\',\'txp_form\',\"name=\'$form\'\"); }else{ $Form=$thing; }\r\n\r\n    if($simpleform){\r\n		if($simpleform==\'title\'){ $Form=\"<txp:permlink><txp:title /></txp:permlink>\"; }\r\n		if($simpleform==\'body\'){ $Form=\"<txp:body />\"; }\r\n		if(!$wraptag){$wraptag=\"ul\";}\r\n		if(!$break){$break=\"li\";}\r\n    }\r\n\r\n    if(!$Form) {\r\n        return \"The form for aks_rss could not be found.\";\r\n    }else{\r\n        require_once($simplepie);\r\n	$Form=preg_replace(\"/<txp:permlink/i\", \"<txp:aks_itemlink\".$target, $Form);\r\n	$Form=preg_replace(\"/<\\/txp:permlink/i\", \"</txp:aks_itemlink\", $Form);\r\n	$Form=preg_replace(\"/<txp:author/i\", \"<txp:aks_author\", $Form);\r\n\r\n        $feeds = preg_split(\"/[ \\r\\n\\t,]+/is\", $feed);\r\n        $feedcount = count($feeds);\r\n        $rssArray = array();\r\n        $titlerecord = array();\r\n        $linkrecord = array();\r\n\r\n        $feedcount = count($feeds);\r\n\r\n        foreach($feeds as $f) {\r\n            $feed = new SimplePie();\r\n            $feed->set_feed_url($f);\r\n\r\n            $feed->enable_cache($caching);\r\n            if($caching) {\r\n                $feed->set_cache_location($cache_dir);\r\n                $feed->set_autodiscovery_cache_duration($cache_time*60);\r\n            }\r\n\r\n            $feed->remove_div($remove_div);\r\n            $feed->encode_instead_of_strip($encode_html);\r\n\r\n            if(!empty($strip_html) && preg_match(\"/^[a-zA-Z0-9, \\r\\n\\t]+$/is\", $strip_html)) {\r\n                $tags = preg_split(\"/[ \\r\\n\\t,]+/is\", $strip_html);\r\n                $feed->strip_htmltags($tags);\r\n            }else {\r\n                $feed->strip_htmltags();\r\n            }\r\n            if(!empty($strip_atts) && preg_match(\"/^[a-zA-Z, \\r\\n\\t]+$/is\", $strip_atts)) {\r\n		  $atts = preg_split(\"/[ \\r\\n\\t,]+/is\", $strip_atts);\r\n                $feed->strip_attributes($atts);\r\n            }else {\r\n                $feed->strip_attributes();\r\n            }\r\n\r\n            $success = $feed->init();\r\n            if($success) {\r\n                $feeditems = $feed->get_items($offset_per_feed,$limit_per_feed);\r\n\r\n                // Give each item information about its parent feed\r\n                foreach($feeditems as $feeditem) {\r\n                    $titlerecord[$feeditem->get_id()] = $feed->get_title();\r\n                    $linkrecord[$feeditem->get_id()] = $feed->get_link();\r\n                }\r\n		if(! $alttitle){ $alttitle=$feed->get_title(); }\r\n		if($showtitle && $alttitle){ $showtitle=doTag(\"<a href=\\\"\".$feed->get_link().\"\\\"$target>\".$alttitle.\"</a>\",$h); }else{$showtitle=\'\';}\r\n\r\n                $rssArray[] = $feeditems;\r\n            }else {\r\n                dmp($feed->error);\r\n            }\r\n        }\r\n\r\n        // Check that the feeding was successful\r\n        if(count($rssArray)) {\r\n            // Skip quicksort if there is only one feed\r\n            $finalItems = ($feedcount > 1)\r\n                ? aks_parseItems($rssArray)\r\n                : $rssArray[0];\r\n\r\n            $limit   = (!empty($limit) && $limit <= count($finalItems)) ? $limit : count($finalItems);\r\n//            $offset  = (!empty($offset) && $offset < $limit) ? $offset : 0;\r\n//            $offset  = (!empty($offset) ) ? $offset : 0;\r\n\r\n            $items   = $finalItems;\r\n//echo $limit.\"=\".$offset;\r\n//            $order   = (!empty($order) && preg_match(\"/asc/i\", $order) ) ? strToLower($order) : \"desc\";\r\n\r\n            if(preg_match(\"/asc/i\", $order) ) { $items = array_reverse($items); }\r\n\r\n            $items = array_slice($items, $offset, $limit); $temp_thisarticle=$GLOBALS[\'thisarticle\'];\r\n            foreach($items as $item) {\r\n                // Assign the parent feed title\r\n                if(!empty($titlerecord[$item->get_id()])) { $out[\'custom_1\'] = $titlerecord[$item->get_id()]; }\r\n                // Assign the parent feed link\r\n                if(!empty($linkrecord[$item->get_id()])) {  $out[\'custom_2\'] = $linkrecord[$item->get_id()]; }\r\n                // Check and retrieve date\r\n                if($item->get_date()) { $out[\"posted\"] = $item->get_date(\'U\'); }\r\n\r\n                // Check and retrieve authors\r\n                if($item->get_authors()) {\r\n                    $authors = $item->get_authors();\r\n                    $authArray = array();\r\n                    foreach($authors as $author) { $authArray[] = $author->get_name(); }\r\n                    $out[\'author\'] = join(\", \", $authArray);\r\n                }\r\n\r\n                // Assign the first two categories found to category1 and category2\r\n                if($item->get_categories()) {\r\n                    $cats = $item->get_categories(0,1);\r\n                    $out[\'category1\'] = $cats[0]->get_label();\r\n                    if(!empty($cats[1])) { $out[\'category2\'] = $cats[1]->get_label(); }\r\n                }\r\n		$out[\'title\'] = $item->get_title();\r\n		$out[\'permlink\'] = $item->get_permalink();\r\n		$out[\'body\'] = aks_rss_short($item->get_content(), $shortdesc);\r\n		$out[\'excerpt\'] = aks_rss_short($item->get_description(), $shortdesc);\r\n		$GLOBALS[\'thisarticle\'] = $out;\r\n		$articles[] = parse($Form);\r\n            }\r\n            $GLOBALS[\'thisarticle\']=$temp_thisarticle;\r\n            if(!$articles){return \"\";}\r\n            return $showtitle . doWrap($articles, $wraptag, $break, $class, $breakclass, \'\', \'\', $id);\r\n\r\n        } else {\r\n            return $showtitle . $emptymsg;\r\n        }\r\n    }\r\n}\r\n//========================================\r\nfunction aks_author($atts) { global $thisarticle; $names = $thisarticle[\'author\']; return $names; }\r\nfunction aks_itemlink($atts, $thing = NULL) {\r\n    global $thisarticle;\r\n    extract(lAtts(array( \'target\' => \'\', \'class\' => \'\', \'style\' => \'\', \'title\' => \'\'), $atts));\r\n    $url=$GLOBALS[\'thisarticle\'][\'permlink\'];\r\n    if ($url) {   if ($thing === NULL) { return $url;  }\r\n        return tag(parse($thing), \'a\', \' href=\"\'.$url.\'\"\'.\r\n            ($title ? \' title=\"\'.$title.\'\"\' : \'\').\r\n            ($style ? \' style=\"\'.$style.\'\"\' : \'\').\r\n            ($target ? \' target=\"\'.$target.\'\"\' : \'\').\r\n            ($class ? \' class=\"\'.$class.\'\"\' : \'\')\r\n        );\r\n    }\r\n}\r\n\r\nfunction aks_rss_short($txt=\"\", $shortdesc=0, $short=\"...\") {\r\n	$words = explode(\" \", $txt); if($shortdesc && count($words) >$shortdesc){$txt = join(\" \",array_slice($words, 0, $shortdesc)).$short; }\r\n	return $txt;\r\n}\r\n\r\n\r\n\r\n// Output the title of the parent feed of the item ---------------------\r\nfunction aks_rss_title($atts) {\r\n    global $thisarticle;\r\n    return $thisarticle[\'custom_1\'];\r\n}\r\n\r\n\r\nfunction aks_rss_source($atts) {\r\n    global $thisarticle;\r\n    preg_match(\"#(https?://)(www\\.)?(.*?)(/|$)#i\",$thisarticle[\'custom_2\'],$mm);\r\n    $source=preg_replace(\'/\\./\',\'-\',$mm[3]);\r\n    return $source;\r\n}\r\n\r\n\r\n\r\n\r\n// Output the link of the parent feed of the item\r\nfunction aks_rss_link($atts, $enclosed = null) {\r\n    global $thisarticle;\r\n    extract(lAtts(array( \'class\' => \'\', \'id\'    => \'\', \'style\' => \'\', \'title\' => $thisarticle[\'custom_1\']),$atts));\r\n\r\n    $href  = \" href=\'\".$thisarticle[\'custom_2\'].\"\'\";\r\n    $class = empty($class) ? \"\" : \" class=\'\".$class.\"\'\";\r\n    $id    = empty($id) ? \"\" : \" id=\'\".$id.\"\'\";\r\n    $title = empty($title) ? \"\" : \" title=\'\".$title.\"\'\";\r\n\r\n    return empty($enclosed)\r\n        ? $thisarticle[\'custom_2\']\r\n        : tag(parse($enclosed), \'a\', $href.$class.$id);\r\n}\r\n\r\n\r\nfunction aks_checkBool($value) { return ($value == \"1\" || $value == \"true\" || $value == \"yes\"); }\r\nfunction aks_parseItems($itemArray) {\r\n    $allItems = array();\r\n    for($i = 0; $i < count($itemArray); $i++) {\r\n        $allItems = array_merge($allItems, $itemArray[$i]);\r\n    }\r\n    return aks_quickSort($allItems);\r\n}\r\n\r\nfunction aks_quickSort($items) {\r\n    if(count($items) > 1) {\r\n        $pivot = array_pop($items);\r\n        $less = array();\r\n        $more = array();\r\n        for($i = 0; $i < count($items); $i++) {\r\n            $cItem = $items[$i];\r\n            if($cItem->get_date(\'U\') <= $pivot->get_date(\'U\')) {\r\n                $more[] = $cItem;\r\n            }else {\r\n                $less[] = $cItem;\r\n            }\r\n        }\r\n        return array_merge(aks_quickSort($less), array($pivot), aks_quickSort($more));\r\n    }else {\r\n        return $items;\r\n    }\r\n}\r\n\r\nfunction aks_gismeteo($atts) {           // for internal use only, no support. possibly it will be deleted in future version\r\n	global $txpcfg;\r\n	$argv  = $atts;\r\n	if(! isset($argv[\'gisid\']) || ! $argv[\'gisid\']){ return; }\r\n	if (!class_exists(\'SimplePie\')) require_once(txpath.\'/lib/simplepie.inc\');\r\n	$feed = new SimplePie();\r\n	$feed->set_feed_url(\"http://informer.gismeteo.ua/rss/\".$argv[\'gisid\'].\".xml\");\r\n	$feed->set_cache_location(txpath . \'/tmp\');\r\n	$feed->set_autodiscovery_cache_duration(36027);\r\n	$success = $feed->init();\r\n	if ($success && $feed->data) {\r\n		$output = \'\';\r\n		$max = $feed->get_item_quantity();\r\n		for($x=0; $x<$max; $x++) {\r\n			$item = $feed->get_item($x);\r\n			$title = SEntities($item->get_title());\r\n			$title=preg_replace(\'/\\,.*$/i\',\'\',$title);\r\n			$tt=preg_replace(\'/:.*$/i\',\'\',$title);\r\n			if ($x == 0){$output .= \"<div id=\'pogoda\'><h3>$tt</h3><br /><ul>\";}\r\n			$title=preg_replace(\'/^.*: ?/i\',\'\',$title);\r\n			$desc = SEntities($item->get_description());\r\n			$desc=preg_replace(\'/давлен.*?, ?/i\',\'<br />\',$desc);\r\n			$output .= \"<li><b>$title</b><br />$desc</li>\";\r\n		}\r\n		$output .= \'</ul></div>\';\r\n	}else{	$output= \'\';\r\n	}\r\n	return $output;\r\n}\r\n\r\nfunction SEntities($s = \'\') {\r\n $inputs = array(\'&#8211;\', \'&#8212;\', \'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8230;\', \'&#91;\', \'&#93;\');\r\n $outputs = array(\'-\', \'--\', \"\'\", \"\'\", \'\"\', \'\"\', \'...\', \'[\', \']\');\r\n $s = str_replace($inputs, $outputs, $s);\r\n return $s;\r\n}\r\n\r\n//-------------------------------------------------------------\r\n\r\nfunction aks_rss_admin_tab($event, $step) {\r\n	global $prefs,$txpcfg;\r\n\r\n        if (ps(\"save\")) {\r\n            pagetop(\"aks_rss Prefs\", \"Preferences Saved\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_simplepie\')).\"\'\",\"name = \'aks_rss_simplepie\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_cache_dir\')).\"\'\",\"name = \'aks_rss_cache_dir\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_cache_time\')).\"\'\",\"name = \'aks_rss_cache_time\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_target\')).\"\'\",\"name = \'aks_rss_target\' and prefs_id =\'1\'\");\r\n            safe_update(\"txp_prefs\", \"val = \'\".addslashes(ps(\'aks_rss_emptymsg\')).\"\'\",\"name = \'aks_rss_emptymsg\' and prefs_id =\'1\'\");\r\n            header(\"Location: index.php?event=aks_rss_admin_tab\");\r\n        } else {\r\n		pagetop(\"aks_rss Prefs\");\r\n        }\r\n	$GLOBALS[\'prefs\'] = get_prefs();\r\n\r\n        echo startTable(\'list\').\r\n        form(\r\n        tr(tdcs(hed(\"aks_rss Preferences\",1),3)).\r\n        tr(\r\ntda(gTxt(\'Absolute path to simplepie.inc:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_simplepie\",$prefs[\'aks_rss_simplepie\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"blank for default\")\r\n\r\n        ).\r\n        tr(\r\ntda(gTxt(\'Absolute path to cache_dir:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_cache_dir\",$prefs[\'aks_rss_cache_dir\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"blank for default\")\r\n        ).\r\n        tr(\r\ntda(gTxt(\'Cache feed time in minutes:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_cache_time\",$prefs[\'aks_rss_cache_time\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"\")\r\n        ).\r\n\r\n        tr(\r\ntda(gTxt(\'\"target\" attribute for feed items &lt;a href=...:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_target\",$prefs[\'aks_rss_target\'],\'edit\',\'\',\'\',\'40\'), \' \') .tda(\"Sample: <b>_blank</b>\")\r\n        ).\r\n        tr(\r\n            tda(gTxt(\'No items error msg:\'), \' style=\"text-align:right;vertical-align:middle\"\').tda(fInput(\"text\",\"aks_rss_emptymsg\",$prefs[\'aks_rss_emptymsg\'],\'edit\',\'\',\'\',\'70\'), \' \') .tda(\"\")\r\n        ).\r\n\r\n        tr(\r\n            tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_rss_admin_tab\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\")\r\n        )). endTable();\r\n}\r\n\r\n\r\n\r\n//--------------------------------------------------- BAD naming. Only for compatibility.\r\nfunction aks_rsstitle($atts){ return aks_rss_title($atts); }\r\nfunction aks_rsslink($atts, $enclosed = null){ return aks_rss_link($atts, $enclosed); }\r\n\r\nfunction aks_rssclass($atts) {\r\n    global $thisarticle;\r\n    preg_match(\"#(https?://)(www\\.)?(.*?)(/|$)#i\",$thisarticle[\'custom_2\'],$mm);\r\n    $class=preg_replace(\'/\\./\',\'-\',$mm[3]);\r\n    return \"class=\'$class\'\";\r\n}\r\n', 'bbcc8a9a179d00b72136ea7e0a3405db', '', '', 1, 7, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('aks_article', 0, 'makss', 'http://makss.uaho.net/plugins/aks_article', '0.3.2', 'Extended article_custom tag', '<div id=\'aks\'>\n<h1>aks_article – Extended <code>article_custom</code> tag</h1>\n\n	<p><a href=\"http://makss.uaho.net/plugins/aks_article\">homepage</a></p>\n\n	<h2>Summary</h2>\n\n	<p>The plugin is based on a rewritten core functions <code>article_custom(), parseArticles(), doArticles()</code></p>\n\n	<h2>Change log</h2>\n\n	<p>v0.1 plugin was writen for testing <a href=\"https://forum.textpattern.io/viewtopic.php?id=38081\">Feature Idea</a> <code>&lt;txp:else /&gt;</code><br>\nv0.2+ plugin is completely rewritten</p>\n\n	<p>2012-05-22 Changes v0.2.4:\n	</p><ul>\n		<li>optimize code, <span class=\"caps\">SQL</span>, memory usage.</li>\n		<li>added support <code>&lt;txp:else /&gt;</code></li>\n		<li>added attributes <code>category1</code>, <code>category2</code>, <code>where</code></li>\n		<li>extend attribute <code>status</code> to status list</li>\n	</ul><p></p>\n\n	<p>2012-08-31 Changes v0.2.5:\n	</p><ul>\n		<li>Inside form:\n	<ul>\n		<li>zebra-lines feature, useful for zebra-coloring lines</li>\n		<li>active-line feature, useful for set <strong>class=“active”</strong></li>\n		<li>get current line number</li>\n	</ul></li>\n		<li>Return <code>txp:variables</code>:\n	<ul>\n		<li>displayed articles</li>\n		<li>rest articles (limit – displayed), useful for second call to <code>aks_article</code></li>\n		<li>zebra_next count for second zebra call</li>\n	</ul></li>\n	</ul><p></p>\n\n	<p>2012-09-04 Changes v0.2.6:\n	</p><ul>\n		<li>Prev/Next carousel feature – show nearest articles. Attribute <code>carousel</code></li>\n		<li>Multi-column feature, attributes: <code>column</code>, <code>columntag</code>, <code>columnclass</code></li>\n		<li>Other attributes: <code>hideself</code>, <code>wrapid</code>, <code>breakclass</code></li>\n	</ul><p></p>\n\n	<p>2013-10-22 Changes v0.2.7\n	</p><ul>\n		<li>Attribute <code>time</code> added new value <code>\'all\'</code> equivalent as <code>time=\"any\"\' and expired=\"1\"</code> (this remove time check condition from sql query)</li>\n		<li>New experimental attribute <code>list</code> allow to generate a list of articles with minimal impact on mysql. This support <span class=\"caps\">ONLY</span> <code>&lt;txp:title /&gt;</code> and <code>&lt;txp:permlink /&gt;</code> tags inside form.\n	<ul>\n		<li>New experimental attribute <code>listfield</code> used with <code>list</code> attribute.</li>\n	</ul></li>\n	</ul><p></p>\n\n	<p>2013-10-29 Changes v0.2.8\n	</p><ul>\n		<li>Shorthand for <code>list</code> attribute</li>\n	</ul><p></p>\n\n	<p>2014-03-13 Changes v0.2.9\n	</p><ul>\n		<li>Added <code>search</code> feature (based on MySQL <span class=\"caps\">FULLTEXT</span> search) – useful for get related articles</li>\n	</ul><p></p>\n\n	<p>2016-07-02 Changes v0.3\n	</p><ul>\n		<li>Txp 4.6 fix</li>\n	</ul><p></p>\n\n	<p>2017-04-17 Changes v0.3.1\n	</p><ul>\n		<li>Txp 4.6 fix and Using <span class=\"caps\">SQL</span> caching capabilities. Txp 4.6+</li>\n		<li>Added <code>exclude</code>  attribute</li>\n		<li>Added <code>aks_article_ids</code>  Txp variable – This is the ID of the articles that were displayed.</li>\n	</ul><p></p>\n\n	<h2>Tags</h2>\n\n	<p><code>&lt;txp:aks_article /&gt;</code> is a single or a container tag</p>\n\n <table><tbody><tr><th>attributes</th><th>default</th><th>sample</th><th>description</th></tr><tr><td>form</td><td>default</td><td>some_form</td><td>same as article_custom</td></tr><tr><td>category</td><td></td><td>cat1,cat2</td><td>for category1 or category2</td></tr><tr><td><strong>category1</strong></td><td></td><td>cat1,cat2</td><td><code>category1 IN(\'cat1\',\'cat2\')</code></td></tr><tr><td><strong>category2</strong></td><td></td><td>cat1,cat2</td><td><code>category2 IN(\'cat1\',\'cat2\')</code></td></tr><tr><td><strong>where</strong></td><td></td><td><code>category1&lt;&gt;\'\' or title like \'%sport%\'</code></td><td><span class=\"caps\">SQL</span> where. <strong><span class=\"caps\">WARNING</span> not slashed</strong>, safety use it!</td></tr><tr><td>section</td><td></td><td>sec1,sec2</td><td>…</td></tr><tr><td>excerpted</td><td></td><td>y</td><td>…</td></tr><tr><td>author</td><td></td><td>author1,author2</td><td>…</td></tr><tr><td>sort</td><td></td><td>title asc</td><td>…</td></tr><tr><td>month</td><td></td><td></td><td>…</td></tr><tr><td>keywords</td><td></td><td></td><td>…</td></tr><tr><td>expired</td><td></td><td></td><td>…</td></tr><tr><td>id</td><td></td><td>1,7,33,12</td><td>…</td></tr><tr><td>exclude</td><td></td><td>2,17,71</td><td>…</td></tr><tr><td>time</td><td>past</td><td></td><td>One of: past, any, future, <strong>all</strong></td></tr><tr><td><strong>status</strong></td><td>4</td><td>hidden,5,live</td><td>Allow any status list(text or numeric, draft, hidden…) safety use it!</td></tr><tr><td>allowoverride</td><td>0</td><td>1</td><td>…</td></tr><tr><td>hideself</td><td>0</td><td>1</td><td>…</td></tr><tr><td>offset</td><td>0</td><td></td><td>…</td></tr><tr><td>limit</td><td>10</td><td>999</td><td>…</td></tr><tr><td><strong>search</strong></td><td></td><td>word1 word2</td><td>Find this and sort articles by MySQL search scope</td></tr><tr><td><strong>searchfield</strong></td><td>Title,Body</td><td></td><td>For advanced, see below.</td></tr><tr><td>zebra</td><td></td><td>3</td><td>zebra-coloring, used with <code>&lt;txp:aks_article_zebra /&gt;</code></td></tr><tr><td>zebra_next</td><td></td><td></td><td>for second call aks_article, see examples</td></tr><tr><td>carousel</td><td>0</td><td>1</td><td>prev-1/prev/current/next/next+1 (limited by <code>limit</code> attribute)</td></tr><tr><td>list</td><td></td><td>1</td><td><strong>1</strong> – turn on;  <strong>permlink</strong> or <strong>permlink2</strong> – turn on and set a fixed form. See below.</td></tr><tr><td>listfield</td><td></td><td></td><td>For advanced, see below. <strong><span class=\"caps\">WARNING</span> not slashed</strong>, safety use it!</td></tr><tr><td>column</td><td>1</td><td>3</td><td>multi-column output</td></tr><tr><td>columntag</td><td>div</td><td>td</td><td>…</td></tr><tr><td>columnclass</td><td></td><td></td><td>…</td></tr><tr><td>wraptag</td><td></td><td></td><td>…</td></tr><tr><td>wrapid</td><td></td><td></td><td>…</td></tr><tr><td>class</td><td></td><td></td><td>wrap class</td></tr><tr><td>break</td><td></td><td></td><td>…</td></tr><tr><td>breakclass</td><td></td><td></td><td>…</td></tr><tr><td>label</td><td></td><td></td><td>…</td></tr><tr><td>labeltag</td><td></td><td></td><td>…</td></tr></tbody></table>\n\n	<p>This tag returns <code>&lt;txp:variables /&gt;</code>\n	</p><ul>\n		<li><code>&lt;txp:variable name=\"aks_article_count\" /&gt;</code> – Displayed articles</li>\n		<li><code>&lt;txp:variable name=\"aks_article_rest\" /&gt;</code> – Rest articles (limit – displayed), useful for second call to <code>aks_article</code></li>\n		<li><code>&lt;txp:variable name=\"aks_article_zebra_next\" /&gt;</code> – Next count for second zebra call</li>\n		<li><code>&lt;txp:variable name=\"aks_article_ids\" /&gt;</code> – The ID of the articles that were displayed.</li>\n	</ul><p></p>\n\n	<h3>Attribute `list`</h3>\n\n	<p>This attribute is used to increase performance when building simple lists of articles. I also recommend setting the attribute <code>time=\"all\"</code></p>\n\n <table><tbody><tr><th>Attribyte</th><th>Set `fixed form`</th><th>Set other attribute</th></tr><tr><td><code>list=\"1\"</code></td><td>Use inline form or form in `form` attribute</td><td>none</td></tr><tr><td><code>list=\"permlink\"</code></td><td> <code>&lt;a href=\"&lt;txp:permlink /&gt;\"&gt;&lt;txp:title /&gt;&lt;/a&gt;</code></td><td>none</td></tr><tr><td><code>list=\"permlink_nf\"</code></td><td> <code>&lt;a href=\"&lt;txp:permlink /&gt;\" rel=\"nofollow\"&gt;&lt;txp:title /&gt;&lt;/a&gt;</code></td><td>none</td></tr><tr><td><code>list=\"permlink2\"</code></td><td> <code>&lt;txp:permlink&gt;&lt;txp:title /&gt;&lt;/txp:permlink&gt;</code></td><td>none</td></tr><tr><td><code>list=\"all_permlink\"</code></td><td> <code>&lt;a href=\"&lt;txp:permlink /&gt;\"&gt;&lt;txp:title /&gt;&lt;/a&gt;</code></td><td> <code>time=\"all\"</code></td></tr><tr><td><code>list=\"ul_permlink\"</code></td><td> <code>&lt;a href=\"&lt;txp:permlink /&gt;\"&gt;&lt;txp:title /&gt;&lt;/a&gt;</code></td><td> <code>wraptag=\"ul\" break=\"li\"</code></td></tr><tr><td><code>list=\"all_ul_permlink\"</code></td><td> <code>&lt;a href=\"&lt;txp:permlink /&gt;\"&gt;&lt;txp:title /&gt;&lt;/a&gt;</code></td><td> <code>time=\"all\" wraptag=\"ul\" break=\"li\"</code></td></tr></tbody></table>\n\n	<p>Example:</p>\n\n<pre>&lt;txp:aks_article category2=\"abc\" limit=\"40\" sort=\"title\" wraptag=\"ul\" break=\"li\" time=\"all\" list=\"permlink_nf\" /&gt;\nequivalent shorthand:\n&lt;txp:aks_article category2=\"abc\" limit=\"40\" sort=\"title\" list=\"all_ul_permlink_nf\" /&gt;\n</pre>\n\n	<h3>Attribute `listfield`</h3>\n\n	<p>This attribute is used only in combination with the attribute `list`.<br>\nYou can add additional fields from the table textpattern. By default, the query returns the following fields: <code>ID,Title,url_title,Section,Category1,Category2</code><br>\nWarning, the name fields are case sensitive.</p>\n\n	<p>Example:</p>\n\n<pre>&lt;txp:aks_article form=\"someform\" time=\"all\" list=\"1\" listfield=\"Image,qwerty\" /&gt;\n</pre>\n\n	<p>where:<br>\nImage – field from table textpattern<br>\nqwerty – Named custom field `custom_2`</p>\n\n	<p>This query returns the following fields: <code>ID,Title,url_title,Section,Category1,Category2,Image,custom_2</code></p>\n\n	<h3>Attribute `search`</h3>\n\n	<p>Find related articles by search in <code>Title, Body</code> fields.</p>\n\n<pre>&lt;txp:aks_article hideself=\"1\" search=\'&lt;txp:keywords /&gt;\' wraptag=\"ul\" break=\"li\" limit=\"20\"&gt;\n	&lt;txp:variable name=\"aks_article_scope\"/&gt;	&lt;txp:title /&gt;\n&lt;/txp:aks_article&gt;\n</pre>\n\n	<p>Remark: <code>&lt;txp:variable name=\"aks_article_scope\"/&gt;</code> used only for debug.</p>\n\n	<h3>Attribute `searchfield`</h3>\n\n	<p>For advanced <span class=\"caps\">ONLY</span>! – need change `textpattern` table.</p>\n\n	<p>Find related articles by search in <code>Keywords</code> field.<br>\n<strong>Importantly:</strong> create <span class=\"caps\">FULLTEXT</span> index for `textpattern` table. <span class=\"caps\">SQL</span> command:</p>\n\n<pre>ALTER TABLE `textpattern` ADD FULLTEXT (`Keywords`);\n</pre>\n\n	<p>Then you can use the code:</p>\n\n<pre>&lt;txp:aks_article hideself=\"1\" search=\'&lt;txp:keywords /&gt;\' searchfield=\"Keywords\" wraptag=\"ul\" break=\"li\" limit=\"20\"&gt;\n	&lt;txp:variable name=\"aks_article_scope\"/&gt;	&lt;txp:title /&gt;\n&lt;/txp:aks_article&gt;\n</pre>\n\n	<h2>Form tags</h2>\n\n	<p><code>&lt;txp:aks_article_zebra /&gt;</code> is a single tag  – <strong>Used <span class=\"caps\">ONLY</span> inside form</strong>\n </p><table><tbody><tr><th>attributes</th><th>default</th><th>sample</th><th>description</th></tr><tr><td>class</td><td></td><td>some</td><td>simple wrap for lazy: <code>class=\"some{$zebra}\"</code></td></tr></tbody></table><br>\nit not attributes then return <code>$zebra</code> value<p></p>\n\n	<p><code>&lt;txp:if_aks_article_active /&gt;</code> is a single or a container tag  – <strong>Used <span class=\"caps\">ONLY</span> inside form</strong></p>\n\n <table><tbody><tr><th>attributes</th><th>default</th><th>sample</th><th>description</th></tr><tr><td>value</td><td></td><td><code>class=\'active\'</code></td><td>only single tag attribute, return <code>value</code></td></tr></tbody></table>\n\n	<p>as container tag support <code>&lt;txp:else /&gt;</code></p>\n\n	<p><code>&lt;txp:aks_article_count /&gt;</code> is a single tag  – <strong>Used <span class=\"caps\">ONLY</span> inside form</strong><br>\nReturn current line number, no attribute available</p>\n\n	<h2>Example:</h2>\n\n<pre>&lt;txp:aks_article section=\"articles\" wraptag=\"ul\" break=\"li\"&gt;\n	&lt;txp:title /&gt;\n&lt;txp:else /&gt;\n	Articles not found\n&lt;/txp:aks_article&gt;\n</pre>\n\n	<p>Uses with <code>form</code></p>\n\n<pre>&lt;txp:aks_article section=\"articles\" form=\"single\"&gt;&lt;txp:else /&gt;\n	Articles not found\n&lt;/txp:aks_article&gt;\n</pre>\n\n	<p>Display first `Sticky` article in section if exist, else display some default_welcome_form</p>\n\n<pre>&lt;txp:aks_article section=\'&lt;txp:section /&gt;\' status=\"sticky\" limit=\"1\"&gt;\n	&lt;txp:body /&gt;\n&lt;txp:else /&gt;\n	&lt;txp:output_form form=\"default_welcome_form\" /&gt;\n&lt;/txp:aks_article&gt;\n</pre>\n\n	<h2>Example zebra/active/count features</h2>\n\n<pre>&lt;txp:aks_article category2=\"mycat\" limit=\"10\" sort=\"title\" wraptag=\"ul\" zebra=\"3\"&gt;\n	&lt;li class=\"zebra&lt;txp:aks_article_zebra /&gt;&lt;txp:if_aks_article_active value=\" active\" /&gt;\"&gt;\n		&lt;txp:aks_article_count  /&gt; &lt;txp:title /&gt;\n	&lt;/li&gt;\n&lt;/txp:aks_article&gt;\nDisplayed: &lt;txp:variable name=\"aks_article_count\" /&gt; articles\nLimit - Displayed = &lt;txp:variable name=\"aks_article_rest\" /&gt; articles\n</pre>\n\n	<p><span class=\"caps\">HTML</span> output:</p>\n\n<pre>&lt;ul&gt;\n&lt;li class=\"zebra0\"&gt; 1 King&lt;/li&gt;\n&lt;li class=\"zebra1\"&gt; 2 La Bonne Maison&lt;/li&gt;\n&lt;li class=\"zebra2\"&gt; 3 Marmaris&lt;/li&gt;\n&lt;li class=\"zebra0 active\"&gt; 4 Paradise&lt;/li&gt;\n&lt;li class=\"zebra1\"&gt; 5 Relax&lt;/li&gt;\n&lt;li class=\"zebra2\"&gt; 6 Sunrise&lt;/li&gt;\n&lt;/ul&gt;\nDisplayed: 6 articles\nLimit - Displayed = 4 articles\n</pre>\n\n	<p>and additional call for rest 4 articles with other conditions</p>\n\n<pre>&lt;txp:if_variable name=\"aks_article_rest\" /&gt;\n	&lt;txp:aks_article category2=\"othercat\" zebra=\"3\"\n		limit=\'&lt;txp:variable name=\"aks_article_rest\" /&gt;\'\n		zebra_next=\'&lt;txp:variable name=\"aks_article_zebra_next\" /&gt;\'\n	 /&gt;\n&lt;/txp:if_variable&gt;\n</pre>\n\n	<p><del>know bug – not stored <code>last zebra color</code> for second call <code>aks_article</code></del> – <strong><span class=\"caps\">FIXED</span></strong>, <code>zebra_next</code> attribute</p>\n\n	<h2>Example carousel feature</h2>\n\n	<p>Show nearest articles.</p>\n\n<pre>&lt;txp:aks_article limit=\"7\" carousel=\"1\" sort=\"title\" /&gt;\n</pre>\n\n	<p>output:</p>\n\n	<ul>\n		<li>prev_article-2</li>\n		<li>prev_article-1</li>\n		<li>prev_article</li>\n		<li><strong>current_article</strong></li>\n		<li>next_article</li>\n		<li>next_article+1</li>\n		<li>next_article+2</li>\n	</ul>\n\n	<h2>Example multi-column feature</h2>\n\n<pre>&lt;txp:aks_article limit=\"9\" column=\"3\" break=\"br\"&gt;&lt;txp:aks_article_count /&gt; article&lt;/txp:aks_article&gt;\n</pre>\n\n	<p><span class=\"caps\">HTML</span> output:</p>\n\n<pre>&lt;div&gt;\n	1 article&lt;br /&gt;\n	2 article&lt;br /&gt;\n	3 article\n&lt;/div&gt;\n&lt;div&gt;\n	4 article&lt;br /&gt;\n	5 article&lt;br /&gt;\n	6 article\n&lt;/div&gt;\n&lt;div&gt;\n	7 article&lt;br /&gt;\n	8 article&lt;br /&gt;\n	9 article\n&lt;/div&gt;\n</pre>\n\n	<p>or using <code>&lt;table&gt;</code></p>\n\n<pre>&lt;table&gt;&lt;tr&gt;\n	&lt;txp:aks_article limit=\"9\" column=\"3\" columntag=\"td\" break=\"br\"&gt;&lt;txp:aks_article_count /&gt; article&lt;/txp:aks_article&gt;\n&lt;/tr&gt;&lt;/table&gt;\n</pre>\n\n	<p><span class=\"caps\">HTML</span> output:</p>\n\n<pre>&lt;table&gt;&lt;tr&gt;\n&lt;td&gt;\n	1 article&lt;br /&gt;\n	2 article&lt;br /&gt;\n	3 article\n&lt;/td&gt;\n&lt;td&gt;\n	4 article&lt;br /&gt;\n	5 article&lt;br /&gt;\n	6 article\n&lt;/td&gt;\n&lt;td&gt;\n	7 article&lt;br /&gt;\n	8 article&lt;br /&gt;\n	9 article\n&lt;/td&gt;\n&lt;/tr&gt;&lt;/table&gt;\n</pre>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n	background-color: #E3E3DB;\n	border:1px solid;\n	border-color:#ddd #999 #888 #ddd;\n	padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #000000;\nmargin: 1.5em 0;\n}\n#aks p { margin: 15px 0px 10px 0px; }\n#aks code { background-color: #FFFFCC; }\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'aks_article\')\r\n		->register(\'aks_article_count\')\r\n		->register(\'aks_article_zebra\')\r\n		->register(\'if_aks_article_active\')\r\n	;\r\n}\r\n\r\n//---	Only-form-tags\r\nfunction aks_article_zebra($atts){\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'class\'		=> \'\'		// lazy-wrap return:  class=\"$class$zebra\"\r\n	),$atts));\r\n	return ( $class ) ? \" class=\\\"{$class}{$thisarticle[\'aks_article_zebra\']}\\\"\" : $thisarticle[\'aks_article_zebra\'];\r\n}\r\n\r\nfunction aks_article_count($atts){\r\n	global $thisarticle;\r\n	return $thisarticle[\'aks_article_count\'];\r\n}\r\n\r\nfunction if_aks_article_active($atts, $thing = NULL){\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'value\'		=> \'\'		// single tag attribute, return:  $value\r\n	),$atts));\r\n	if( !$value ){ return EvalElse($thing, $thisarticle[\'aks_article_active\']); }\r\n	return ( $thisarticle[\'aks_article_active\'] ) ? $value : \'\';\r\n}\r\n\r\n\r\n//---	Main-tags\r\nfunction aks_article($atts, $thing = NULL){\r\n	global $is_article_list;	$old_ial = $is_article_list;	$is_article_list = true;\r\n	article_push();\r\n\r\n	$out = @aks_article_doArticles($atts, EvalElse($thing, true));\r\n\r\n	article_pop();		$is_article_list = $old_ial;\r\n	if (!$out) $out=parse(EvalElse($thing, false));\r\n	return $out;\r\n}\r\n\r\n// ------------------------------------------------------------- internal subs\r\nfunction aks_article_doArticles($atts, $thing = NULL){\r\n	global $prefs, $thisarticle, $variable;\r\n	$customFields = getCustomFields();\r\n	$customlAtts = array_null(array_flip($customFields));\r\n	$active_id = @intval($thisarticle[\'thisid\']);\r\n\r\n	$theAtts = lAtts(array(\r\n		\'form\'          => \'default\',\r\n		\'category\'      => \'\',\r\n		\'category1\'	=> \'\',\r\n		\'category2\'	=> \'\',\r\n		\'where\'		=> \'\',		// WARNING $where not slashed!!! safety use it.\r\n\r\n		\'section\'       => \'\',\r\n		\'excerpted\'     => \'\',\r\n		\'author\'        => \'\',\r\n		\'sort\'          => \'\',\r\n		\'month\'         => \'\',\r\n		\'keywords\'      => \'\',\r\n		\'expired\'       => $prefs[\'publish_expired_articles\'],\r\n\r\n		\'id\'            => \'\',\r\n		\'exclude\'	=> \'\',\r\n\r\n		\'time\'          => \'past\',\r\n		\'status\'        => \'4\',\r\n		\'allowoverride\' => 0,\r\n		\'offset\'        => 0,\r\n		\'limit\'         => 10,\r\n\r\n\'search\'	=> \'\',\r\n\'searchfield\'	=> ( empty($prefs[\'searchable_article_fields\']) ? \'Title, Body\' : $prefs[\'searchable_article_fields\']),\r\n\r\n\'zebra\'		=> 0,\r\n\'zebra_next\'	=> 0,				//set initial count for zebra, used for second call aks_article\r\n\'carousel\'	=> 0,\r\n\'hideself\'	=> 0,\r\n\r\n\'column\'	=> 1,\r\n\'columntag\'	=> \'div\',\r\n\'columnclass\'	=> \'\',\r\n\r\n\'list\'		=> \'\',\r\n\'listfield\'	=> \'\',				// WARNING $listfield not slashed!!! safety use it.\r\n\r\n		\'wraptag\'       => \'\',\r\n		\'wrapid\'        => \'\',\r\n		\'class\'         => \'\',\r\n\r\n		\'break\'         => \'\',\r\n		\'breakclass\'    => \'\',\r\n\r\n		\'label\'         => \'\',\r\n		\'labeltag\'      => \'\'\r\n	)+$customlAtts,$atts);\r\n	extract($theAtts);\r\n\r\n	$zebra_next = intval($zebra_next);\r\n	$limit = assert_int($limit);\r\n	$hideself = ( $hideself ) ? 1 : 0;\r\n	$column = (assert_int($column)>1) ? assert_int($column) : 1;\r\n\r\n	$category  = join(\"\',\'\", doSlash(aks_article_ulist($category)));\r\n	$category  = (!$category)  ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\r\n\r\n	$category1 = join(\"\',\'\", doSlash(aks_article_ulist($category1)));	if($category1){ $category.=\" and Category1 IN (\'\".$category1.\"\')\"; }\r\n	$category2 = join(\"\',\'\", doSlash(aks_article_ulist($category2)));	if($category2){ $category.=\" and Category2 IN (\'\".$category2.\"\')\"; }\r\n\r\n	$where = (!$where) ? \'\' : \" and ($where)\";\r\n\r\n	$section   = (!$section)   ? \'\' : \" and Section IN (\'\".join(\"\',\'\", doSlash(aks_article_ulist($section))).\"\')\";\r\n	$excerpted = ($excerpted==\'y\')  ? \" and Excerpt !=\'\'\" : \'\';\r\n	$author    = (!$author)    ? \'\' : \" and AuthorID IN (\'\".join(\"\',\'\", doSlash(aks_article_ulist($author))).\"\')\";\r\n	$month     = (!$month)     ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\r\n\r\n	$ids = array_map(\'intval\', aks_article_ulist($id));\r\n	$id  = (empty($id))  ? \'\' : \" AND ID IN (\".join(\',\', $ids).\")\";\r\n\r\n	$excludes = array_map(\'intval\', aks_article_ulist($exclude));\r\n	$exclude  = (empty($exclude)) ? \'\' : \" AND ID NOT IN (\".join(\',\', $excludes).\")\";\r\n\r\n	if( $list ){	// list shorthand\r\n		if( preg_match(\'/^(all_)(.*)/i\', $list, $mm) ){	$list = @$mm[2];\r\n			$time = \"all\";\r\n		}\r\n		if( preg_match(\'/^(ul_)(.*)/i\', $list, $mm) ){	$list = @$mm[2];\r\n			$wraptag = \"ul\";\r\n			$break = \"li\";\r\n		}\r\n	}\r\n\r\n	// Using SQL caching capabilities. Txp 4.6+\r\n	if (function_exists(\'now\')) {\r\n		$where_posted = now(\'posted\');\r\n		$where_expired = \" AND (\".now(\'expires\').\" <= Expires OR Expires IS NULL)\";\r\n	} else {\r\n		$where_posted = \"now()\";\r\n		$where_expired = \" AND (now() <= Expires OR Expires = \".NULLDATETIME.\")\";\r\n	}\r\n\r\n	switch ($time) {\r\n		case \'any\':	$time = \"\"; break;\r\n		case \'all\':	$time = \"\"; $expired = 1; break;\r\n		case \'future\':	$time = \" AND Posted > \".$where_posted; break;\r\n		default:	$time = \" AND Posted <= \".$where_posted;\r\n	}\r\n	if (!$expired) {\r\n		$time .= $where_expired;\r\n	}\r\n\r\n	$custom = \'\';\r\n	if ($customFields) {\r\n		foreach($customFields as $cField) {\r\n			if (isset($atts[$cField]))\r\n				$customPairs[$cField] = $atts[$cField];\r\n		}\r\n		if(!empty($customPairs)) {\r\n			$custom = buildCustomSql($customFields,$customPairs);\r\n		}\r\n	}\r\n\r\n	if ($keywords) {\r\n		$keys = doSlash(aks_article_ulist($keywords));\r\n		foreach ($keys as $key) {\r\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\r\n		}\r\n		$keywords = \" and (\" . join(\' or \',$keyparts) . \")\";\r\n	}\r\n\r\n	$statuss = array_map(\'aks_article_getStatusNum\', aks_article_ulist($status));\r\n	$statusq = (!$status)        ? \'Status=4\' : \"Status IN (\".join(\',\', array_unique($statuss)).\")\";	//fix later\r\n\r\n	$where = $statusq. $time. $where. $id. $category. $section. $exclude. $excerpted. $month. $author. $keywords. $custom;\r\n\r\n	if (!empty($atts[\'id\']) && empty($atts[\'sort\'])){\r\n		$safe_sort = \'field(id, \'.join(\',\', $ids).\')\';\r\n	}else{\r\n		$safe_sort = ($sort)	? doSlash($sort) : \'Posted desc\';\r\n	}\r\n\r\n\r\n	if( $carousel && $active_id){\r\n		$rs2=aks_article_safe_column_num(\"id\", \"textpattern\", \"$where order by $safe_sort\");	if( empty($rs2) ){ return; }\r\n		$nm = array_keys($rs2, $active_id);\r\n		$max=count($rs2)-$hideself;\r\n		if( empty($nm[0]) || ( intval($nm[0])-floor(($limit+$hideself)/2) )<=0 ){\r\n			$offset = 0;\r\n		}else{\r\n			$offset = intval($nm[0])-floor(($limit+$hideself)/2);\r\n			if( $offset+$limit > $max ){ $offset = $max-$limit; }\r\n			if( $offset<0 ){ $offset=0; }\r\n		}\r\n	}\r\n	if( $hideself && $active_id){ $where .= \" AND id<>$active_id \"; }\r\n\r\n	$sf = \"\";\r\n	if( !empty($search) ){\r\n		$safe_sort = \'aks_article_scope desc\';\r\n		$ff = \'`\'.join(\'`, `\', do_list($searchfield)).\'`\';\r\n		$sf = \", match($ff) AGAINST (\'\".doSlash($search).\"\') as aks_article_scope\";\r\n//		if you have problem with `having` then comment `having` line and uncomment next line\r\n		$where .= \" having aks_article_scope>0 \";\r\n//		$where .= \" and match($ff) AGAINST (\'\".doSlash($search).\"\')>0 \";\r\n	}\r\n\r\n\r\n	if( $list ){\r\n		switch ($list) {\r\n			case \'permlink\':	$thing = \'<a href=\"<txp:permlink />\"><txp:title /></a>\'; break;\r\n			case \'permlink_nf\':	$thing = \'<a href=\"<txp:permlink />\" rel=\"nofollow\"><txp:title /></a>\'; break;\r\n			case \'permlink2\':	$thing = \'<txp:permlink><txp:title /></txp:permlink>\'; break;\r\n		}\r\n\r\n		$ff = array(\"ID\", \"Title\", \"url_title\", \"Section\", \"Category1\", \"Category2\");\r\n		if( $listfield ){\r\n			$listfield = aks_article_ulist($listfield);\r\n			foreach( $listfield as $a){\r\n				if( in_array($a, $customFields) ){\r\n					$nm = array_keys($customFields, $a);\r\n					$ff[] = \"custom_{$nm[0]}\";\r\n				}elseif( $a ){\r\n					$ff[] = $a;\r\n				}\r\n			}\r\n		}\r\n		$fields = join(\',\', array_unique($ff) );\r\n		$rs = safe_rows_start($fields . $sf, \"textpattern\", \"$where order by $safe_sort limit \".intval($offset).\", $limit\");\r\n	}else{\r\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\" .$sf, \"textpattern\", \"$where order by $safe_sort limit \".intval($offset).\", $limit\");\r\n	}\r\n\r\n	if ($rs) {\r\n		$count = 0;\r\n		$col=1;\r\n		$last = numRows($rs);\r\n		$percolumn = ceil($last/$column);\r\n\r\n		$articles = array();\r\n		$article_ids = array();\r\n		while($a = nextRow($rs)) {\r\n			unset($GLOBALS[\'thisarticle\']);\r\n			populateArticleData($a);\r\n			global $thisarticle;\r\n			$thisarticle[\'aks_article_zebra\'] = ($zebra > 1) ? ($count + $zebra_next) % $zebra : 0;\r\n			++$count;\r\n			if( $count > $percolumn*$col){ ++$col; }\r\n\r\n			$thisarticle[\'is_first\'] = ($count == 1);\r\n			$thisarticle[\'is_last\'] = ($count == $last);\r\n			$thisarticle[\'aks_article_active\'] = ($active_id == $thisarticle[\'thisid\'] );\r\n			$thisarticle[\'aks_article_count\'] = $count;\r\n			$article_ids[] = $thisarticle[\'thisid\'];\r\n\r\n			// this variable used for debug search feature inside article form\r\n			$variable[\'aks_article_scope\'] = @$a[\'aks_article_scope\'];\r\n			$variable[\'aks_article_count\'] = $count;\r\n\r\n			if( $allowoverride and $a[\'override_form\'] ){\r\n				$articles[$col][] = parse_form($a[\'override_form\']);\r\n			}else{\r\n				$articles[$col][] = ($thing) ? parse($thing) : parse_form($form);\r\n			}\r\n		}\r\n		$variable[\'aks_article_count\'] = $count;\r\n		$variable[\'aks_article_rest\']  = $limit - $count;\r\n		$variable[\'aks_article_ids\'] = join(\',\', $article_ids);\r\n		if ($zebra > 1) { $variable[\'aks_article_zebra_next\'] = ($count + $zebra_next) % $zebra; }\r\n\r\n		if( $col==1 ){ return doLabel($label, $labeltag).doWrap($articles[1], $wraptag, $break, $class, $breakclass, \'\', \'\', $wrapid); }\r\n\r\n\r\n		$out=array();\r\n		for ($i = 1; $i <= $col; $i++) {\r\n			if( is_array($articles[$i]) ){\r\n				$out[]=doWrap($articles[$i], $wraptag, $break, $class, $breakclass, \'\', \'\', $wrapid);\r\n			}\r\n		}\r\n		return doWrap($out, \'\', $columntag, \'\', $columnclass);\r\n	}\r\n}\r\n\r\n// ------------------------------------------------------------- getStatusNum($name)	- added numeric status support\r\nfunction aks_article_getStatusNum($name){\r\n	$labels = array(\'draft\' => 1, \'hidden\' => 2, \'pending\' => 3, \'live\' => 4, \'sticky\' => 5, \'1\'=>1, \'2\'=>2, \'3\'=>3, \'4\'=>4, \'5\'=>5);\r\n	$status = strtolower($name);\r\n	$num = empty($labels[$status]) ? 4 : $labels[$status];\r\n	return $num;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction aks_article_ulist($list, $delim = \',\'){\r\n	return array_unique(array_map(\'trim\', explode($delim, $list)));\r\n}\r\n\r\n// ------------------------------------------------------------- for compatibity with Txp < 4.5.0\r\nfunction aks_article_safe_column_num($thing, $table, $where, $debug=\'\'){\r\n	$q = \"select $thing from \".safe_pfx_j($table).\" where $where\";\r\n	$rs = getRows($q,$debug);\r\n	if ($rs) {\r\n		foreach($rs as $a) {\r\n			$v = array_shift($a);\r\n			$out[] = $v;\r\n		}\r\n		return $out;\r\n	};\r\n	return array();\r\n}\r\n', 'if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'aks_article\')\r\n		->register(\'aks_article_count\')\r\n		->register(\'aks_article_zebra\')\r\n		->register(\'if_aks_article_active\')\r\n	;\r\n}\r\n\r\n//---	Only-form-tags\r\nfunction aks_article_zebra($atts){\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'class\'		=> \'\'		// lazy-wrap return:  class=\"$class$zebra\"\r\n	),$atts));\r\n	return ( $class ) ? \" class=\\\"{$class}{$thisarticle[\'aks_article_zebra\']}\\\"\" : $thisarticle[\'aks_article_zebra\'];\r\n}\r\n\r\nfunction aks_article_count($atts){\r\n	global $thisarticle;\r\n	return $thisarticle[\'aks_article_count\'];\r\n}\r\n\r\nfunction if_aks_article_active($atts, $thing = NULL){\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'value\'		=> \'\'		// single tag attribute, return:  $value\r\n	),$atts));\r\n	if( !$value ){ return EvalElse($thing, $thisarticle[\'aks_article_active\']); }\r\n	return ( $thisarticle[\'aks_article_active\'] ) ? $value : \'\';\r\n}\r\n\r\n\r\n//---	Main-tags\r\nfunction aks_article($atts, $thing = NULL){\r\n	global $is_article_list;	$old_ial = $is_article_list;	$is_article_list = true;\r\n	article_push();\r\n\r\n	$out = @aks_article_doArticles($atts, EvalElse($thing, true));\r\n\r\n	article_pop();		$is_article_list = $old_ial;\r\n	if (!$out) $out=parse(EvalElse($thing, false));\r\n	return $out;\r\n}\r\n\r\n// ------------------------------------------------------------- internal subs\r\nfunction aks_article_doArticles($atts, $thing = NULL){\r\n	global $prefs, $thisarticle, $variable;\r\n	$customFields = getCustomFields();\r\n	$customlAtts = array_null(array_flip($customFields));\r\n	$active_id = @intval($thisarticle[\'thisid\']);\r\n\r\n	$theAtts = lAtts(array(\r\n		\'form\'          => \'default\',\r\n		\'category\'      => \'\',\r\n		\'category1\'	=> \'\',\r\n		\'category2\'	=> \'\',\r\n		\'where\'		=> \'\',		// WARNING $where not slashed!!! safety use it.\r\n\r\n		\'section\'       => \'\',\r\n		\'excerpted\'     => \'\',\r\n		\'author\'        => \'\',\r\n		\'sort\'          => \'\',\r\n		\'month\'         => \'\',\r\n		\'keywords\'      => \'\',\r\n		\'expired\'       => $prefs[\'publish_expired_articles\'],\r\n\r\n		\'id\'            => \'\',\r\n		\'exclude\'	=> \'\',\r\n\r\n		\'time\'          => \'past\',\r\n		\'status\'        => \'4\',\r\n		\'allowoverride\' => 0,\r\n		\'offset\'        => 0,\r\n		\'limit\'         => 10,\r\n\r\n\'search\'	=> \'\',\r\n\'searchfield\'	=> ( empty($prefs[\'searchable_article_fields\']) ? \'Title, Body\' : $prefs[\'searchable_article_fields\']),\r\n\r\n\'zebra\'		=> 0,\r\n\'zebra_next\'	=> 0,				//set initial count for zebra, used for second call aks_article\r\n\'carousel\'	=> 0,\r\n\'hideself\'	=> 0,\r\n\r\n\'column\'	=> 1,\r\n\'columntag\'	=> \'div\',\r\n\'columnclass\'	=> \'\',\r\n\r\n\'list\'		=> \'\',\r\n\'listfield\'	=> \'\',				// WARNING $listfield not slashed!!! safety use it.\r\n\r\n		\'wraptag\'       => \'\',\r\n		\'wrapid\'        => \'\',\r\n		\'class\'         => \'\',\r\n\r\n		\'break\'         => \'\',\r\n		\'breakclass\'    => \'\',\r\n\r\n		\'label\'         => \'\',\r\n		\'labeltag\'      => \'\'\r\n	)+$customlAtts,$atts);\r\n	extract($theAtts);\r\n\r\n	$zebra_next = intval($zebra_next);\r\n	$limit = assert_int($limit);\r\n	$hideself = ( $hideself ) ? 1 : 0;\r\n	$column = (assert_int($column)>1) ? assert_int($column) : 1;\r\n\r\n	$category  = join(\"\',\'\", doSlash(aks_article_ulist($category)));\r\n	$category  = (!$category)  ? \'\' : \" and (Category1 IN (\'\".$category.\"\') or Category2 IN (\'\".$category.\"\'))\";\r\n\r\n	$category1 = join(\"\',\'\", doSlash(aks_article_ulist($category1)));	if($category1){ $category.=\" and Category1 IN (\'\".$category1.\"\')\"; }\r\n	$category2 = join(\"\',\'\", doSlash(aks_article_ulist($category2)));	if($category2){ $category.=\" and Category2 IN (\'\".$category2.\"\')\"; }\r\n\r\n	$where = (!$where) ? \'\' : \" and ($where)\";\r\n\r\n	$section   = (!$section)   ? \'\' : \" and Section IN (\'\".join(\"\',\'\", doSlash(aks_article_ulist($section))).\"\')\";\r\n	$excerpted = ($excerpted==\'y\')  ? \" and Excerpt !=\'\'\" : \'\';\r\n	$author    = (!$author)    ? \'\' : \" and AuthorID IN (\'\".join(\"\',\'\", doSlash(aks_article_ulist($author))).\"\')\";\r\n	$month     = (!$month)     ? \'\' : \" and Posted like \'\".doSlash($month).\"%\'\";\r\n\r\n	$ids = array_map(\'intval\', aks_article_ulist($id));\r\n	$id  = (empty($id))  ? \'\' : \" AND ID IN (\".join(\',\', $ids).\")\";\r\n\r\n	$excludes = array_map(\'intval\', aks_article_ulist($exclude));\r\n	$exclude  = (empty($exclude)) ? \'\' : \" AND ID NOT IN (\".join(\',\', $excludes).\")\";\r\n\r\n	if( $list ){	// list shorthand\r\n		if( preg_match(\'/^(all_)(.*)/i\', $list, $mm) ){	$list = @$mm[2];\r\n			$time = \"all\";\r\n		}\r\n		if( preg_match(\'/^(ul_)(.*)/i\', $list, $mm) ){	$list = @$mm[2];\r\n			$wraptag = \"ul\";\r\n			$break = \"li\";\r\n		}\r\n	}\r\n\r\n	// Using SQL caching capabilities. Txp 4.6+\r\n	if (function_exists(\'now\')) {\r\n		$where_posted = now(\'posted\');\r\n		$where_expired = \" AND (\".now(\'expires\').\" <= Expires OR Expires IS NULL)\";\r\n	} else {\r\n		$where_posted = \"now()\";\r\n		$where_expired = \" AND (now() <= Expires OR Expires = \".NULLDATETIME.\")\";\r\n	}\r\n\r\n	switch ($time) {\r\n		case \'any\':	$time = \"\"; break;\r\n		case \'all\':	$time = \"\"; $expired = 1; break;\r\n		case \'future\':	$time = \" AND Posted > \".$where_posted; break;\r\n		default:	$time = \" AND Posted <= \".$where_posted;\r\n	}\r\n	if (!$expired) {\r\n		$time .= $where_expired;\r\n	}\r\n\r\n	$custom = \'\';\r\n	if ($customFields) {\r\n		foreach($customFields as $cField) {\r\n			if (isset($atts[$cField]))\r\n				$customPairs[$cField] = $atts[$cField];\r\n		}\r\n		if(!empty($customPairs)) {\r\n			$custom = buildCustomSql($customFields,$customPairs);\r\n		}\r\n	}\r\n\r\n	if ($keywords) {\r\n		$keys = doSlash(aks_article_ulist($keywords));\r\n		foreach ($keys as $key) {\r\n			$keyparts[] = \"FIND_IN_SET(\'\".$key.\"\',Keywords)\";\r\n		}\r\n		$keywords = \" and (\" . join(\' or \',$keyparts) . \")\";\r\n	}\r\n\r\n	$statuss = array_map(\'aks_article_getStatusNum\', aks_article_ulist($status));\r\n	$statusq = (!$status)        ? \'Status=4\' : \"Status IN (\".join(\',\', array_unique($statuss)).\")\";	//fix later\r\n\r\n	$where = $statusq. $time. $where. $id. $category. $section. $exclude. $excerpted. $month. $author. $keywords. $custom;\r\n\r\n	if (!empty($atts[\'id\']) && empty($atts[\'sort\'])){\r\n		$safe_sort = \'field(id, \'.join(\',\', $ids).\')\';\r\n	}else{\r\n		$safe_sort = ($sort)	? doSlash($sort) : \'Posted desc\';\r\n	}\r\n\r\n\r\n	if( $carousel && $active_id){\r\n		$rs2=aks_article_safe_column_num(\"id\", \"textpattern\", \"$where order by $safe_sort\");	if( empty($rs2) ){ return; }\r\n		$nm = array_keys($rs2, $active_id);\r\n		$max=count($rs2)-$hideself;\r\n		if( empty($nm[0]) || ( intval($nm[0])-floor(($limit+$hideself)/2) )<=0 ){\r\n			$offset = 0;\r\n		}else{\r\n			$offset = intval($nm[0])-floor(($limit+$hideself)/2);\r\n			if( $offset+$limit > $max ){ $offset = $max-$limit; }\r\n			if( $offset<0 ){ $offset=0; }\r\n		}\r\n	}\r\n	if( $hideself && $active_id){ $where .= \" AND id<>$active_id \"; }\r\n\r\n	$sf = \"\";\r\n	if( !empty($search) ){\r\n		$safe_sort = \'aks_article_scope desc\';\r\n		$ff = \'`\'.join(\'`, `\', do_list($searchfield)).\'`\';\r\n		$sf = \", match($ff) AGAINST (\'\".doSlash($search).\"\') as aks_article_scope\";\r\n//		if you have problem with `having` then comment `having` line and uncomment next line\r\n		$where .= \" having aks_article_scope>0 \";\r\n//		$where .= \" and match($ff) AGAINST (\'\".doSlash($search).\"\')>0 \";\r\n	}\r\n\r\n\r\n	if( $list ){\r\n		switch ($list) {\r\n			case \'permlink\':	$thing = \'<a href=\"<txp:permlink />\"><txp:title /></a>\'; break;\r\n			case \'permlink_nf\':	$thing = \'<a href=\"<txp:permlink />\" rel=\"nofollow\"><txp:title /></a>\'; break;\r\n			case \'permlink2\':	$thing = \'<txp:permlink><txp:title /></txp:permlink>\'; break;\r\n		}\r\n\r\n		$ff = array(\"ID\", \"Title\", \"url_title\", \"Section\", \"Category1\", \"Category2\");\r\n		if( $listfield ){\r\n			$listfield = aks_article_ulist($listfield);\r\n			foreach( $listfield as $a){\r\n				if( in_array($a, $customFields) ){\r\n					$nm = array_keys($customFields, $a);\r\n					$ff[] = \"custom_{$nm[0]}\";\r\n				}elseif( $a ){\r\n					$ff[] = $a;\r\n				}\r\n			}\r\n		}\r\n		$fields = join(\',\', array_unique($ff) );\r\n		$rs = safe_rows_start($fields . $sf, \"textpattern\", \"$where order by $safe_sort limit \".intval($offset).\", $limit\");\r\n	}else{\r\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\" .$sf, \"textpattern\", \"$where order by $safe_sort limit \".intval($offset).\", $limit\");\r\n	}\r\n\r\n	if ($rs) {\r\n		$count = 0;\r\n		$col=1;\r\n		$last = numRows($rs);\r\n		$percolumn = ceil($last/$column);\r\n\r\n		$articles = array();\r\n		$article_ids = array();\r\n		while($a = nextRow($rs)) {\r\n			unset($GLOBALS[\'thisarticle\']);\r\n			populateArticleData($a);\r\n			global $thisarticle;\r\n			$thisarticle[\'aks_article_zebra\'] = ($zebra > 1) ? ($count + $zebra_next) % $zebra : 0;\r\n			++$count;\r\n			if( $count > $percolumn*$col){ ++$col; }\r\n\r\n			$thisarticle[\'is_first\'] = ($count == 1);\r\n			$thisarticle[\'is_last\'] = ($count == $last);\r\n			$thisarticle[\'aks_article_active\'] = ($active_id == $thisarticle[\'thisid\'] );\r\n			$thisarticle[\'aks_article_count\'] = $count;\r\n			$article_ids[] = $thisarticle[\'thisid\'];\r\n\r\n			// this variable used for debug search feature inside article form\r\n			$variable[\'aks_article_scope\'] = @$a[\'aks_article_scope\'];\r\n			$variable[\'aks_article_count\'] = $count;\r\n\r\n			if( $allowoverride and $a[\'override_form\'] ){\r\n				$articles[$col][] = parse_form($a[\'override_form\']);\r\n			}else{\r\n				$articles[$col][] = ($thing) ? parse($thing) : parse_form($form);\r\n			}\r\n		}\r\n		$variable[\'aks_article_count\'] = $count;\r\n		$variable[\'aks_article_rest\']  = $limit - $count;\r\n		$variable[\'aks_article_ids\'] = join(\',\', $article_ids);\r\n		if ($zebra > 1) { $variable[\'aks_article_zebra_next\'] = ($count + $zebra_next) % $zebra; }\r\n\r\n		if( $col==1 ){ return doLabel($label, $labeltag).doWrap($articles[1], $wraptag, $break, $class, $breakclass, \'\', \'\', $wrapid); }\r\n\r\n\r\n		$out=array();\r\n		for ($i = 1; $i <= $col; $i++) {\r\n			if( is_array($articles[$i]) ){\r\n				$out[]=doWrap($articles[$i], $wraptag, $break, $class, $breakclass, \'\', \'\', $wrapid);\r\n			}\r\n		}\r\n		return doWrap($out, \'\', $columntag, \'\', $columnclass);\r\n	}\r\n}\r\n\r\n// ------------------------------------------------------------- getStatusNum($name)	- added numeric status support\r\nfunction aks_article_getStatusNum($name){\r\n	$labels = array(\'draft\' => 1, \'hidden\' => 2, \'pending\' => 3, \'live\' => 4, \'sticky\' => 5, \'1\'=>1, \'2\'=>2, \'3\'=>3, \'4\'=>4, \'5\'=>5);\r\n	$status = strtolower($name);\r\n	$num = empty($labels[$status]) ? 4 : $labels[$status];\r\n	return $num;\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction aks_article_ulist($list, $delim = \',\'){\r\n	return array_unique(array_map(\'trim\', explode($delim, $list)));\r\n}\r\n\r\n// ------------------------------------------------------------- for compatibity with Txp < 4.5.0\r\nfunction aks_article_safe_column_num($thing, $table, $where, $debug=\'\'){\r\n	$q = \"select $thing from \".safe_pfx_j($table).\" where $where\";\r\n	$rs = getRows($q,$debug);\r\n	if ($rs) {\r\n		foreach($rs as $a) {\r\n			$v = array_shift($a);\r\n			$out[] = $v;\r\n		}\r\n		return $out;\r\n	};\r\n	return array();\r\n}\r\n', 'f82dc1c70d1128b579262541018564b1', '', '', 0, 5, 0),
('aks_clean_url', 0, 'makss', 'http://makss.uaho.net/plugins/aks_clean_url', '0.1.2', 'Makes safe URLs. BugFix hardcode in preText().', '', 'if(@txpinterface == \'public\'){\r\n	register_callback(\'aks_clean_url_cb_public\', \'pretext\');\r\n}\r\nif(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_clean_url_tab\', \'1\');\r\n	add_privs(\'plugin_prefs.aks_clean_url\',\'1\');\r\n	register_tab(\"extensions\", \"aks_clean_url_tab\", \"Clean URL\");\r\n	register_callback(\"aks_clean_url_tab\", \"aks_clean_url_tab\");\r\n	register_callback(\'aks_clean_url_tab\', \'plugin_prefs.aks_clean_url\');\r\n}\r\n\r\n\r\nfunction aks_clean_url_cb_public(){\r\n	global $prefs;\r\n\r\n	$messy=do_list(@$prefs[\'aks_clean_url_messy\']);\r\n	foreach($messy as $m){\r\n		unset($_POST[$m], $_GET[$m]);\r\n	}\r\n\r\n	$hardlink=do_list(@$prefs[\'aks_clean_url_hardlink\']);\r\n	$hardlink=array_map(\'aks_clean_url_decode\', $hardlink );\r\n	$req=chopUrl(serverSet(\'REQUEST_URI\'));\r\n	if( in_array($req[\'u1\'], $hardlink) ){\r\n		txp_die(gTxt(\'404_not_found\'), \'404\');		// maybe 301 in future\r\n	}\r\n}\r\n\r\n//admin tab\r\nfunction aks_clean_url_tab(){\r\n	global $prefs;\r\n	if (ps(\"save\")) {\r\n		// for buggy set_pref() function..  :(\r\n		safe_update(\'txp_prefs\', \"type = 2\", \"name in(\'aks_clean_url_messy\', \'aks_clean_url_hardlink\')\");\r\n\r\n		$val=@join(\',\', ps(\'aks_clean_url_messy\'));	set_pref(\"aks_clean_url_messy\", $val, \'publish\', 2);\r\n		$val=@join(\',\', ps(\'aks_clean_url_hardlink\'));	set_pref(\"aks_clean_url_hardlink\", $val, \'publish\', 2);\r\n		pagetop(\"Clean URL Prefs\", \"Preferences Saved\");\r\n		$prefs=get_prefs();\r\n	} else {\r\n		pagetop(\"Clean URL Prefs\");\r\n	}\r\n\r\n	$messy=array(\r\n		\'id\'		=> \'Article ID\'\r\n		,\'s\'		=> \'Section\'\r\n		,\'c\'		=> \'Category\'\r\n		,\'context\'	=> \'Category content type\'\r\n		,\'q\'		=> \'Internal TxP search\'\r\n		,\'m\'		=> \'Search match type\'\r\n		,\'pg\'		=> \'Page number\'\r\n		,\'p\'		=> \'Image ID\'\r\n		,\'month\'	=> \'Date\'\r\n		,\'author\'	=> \'Author\'\r\n		,\'rss\'		=> \'RSS feed\'\r\n		,\'atom\'		=> \'Atom feed\'\r\n//		,\'parentid\'		=> \'Comments...\'\r\n	);\r\n\r\n	$hardlink=array(\r\n		\'rss\'				=> \'RSS feed\'\r\n		,\'atom\'				=> \'Atom feed\'\r\n		,\'section\'			=> \'Section\'\r\n		,\'category\'			=> \'Category\'\r\n		,\'author\'			=> \'Author\'\r\n		,\'file_download\'	=> \'File download\'\r\n	);\r\n\r\n	$html_messy=aks_clean_url_admin_table( $messy, \'aks_clean_url_messy\');\r\n	$html_hardlink=aks_clean_url_admin_table( $hardlink, \'aks_clean_url_hardlink\', 1);\r\n\r\n	//html code...\r\n	$button=fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_clean_url_tab\").sInput(\'saveprefs\');\r\n	echo <<<EOF\r\n<style type=\"text/css\">\r\n.aks_chk_table { width: 400px; margin: 10px; }\r\n.aks_chk_table th{ border-bottom: thin dotted;	border-color: #000000; }\r\n.aks_chk_table td{ border-bottom: thin dotted;	border-color: #cccccc; }\r\n.aks_chk_table .chk{ text-align: center; }\r\n</style>\r\n<center><form method=\"post\" action=\"index.php\">\r\n<table><tr valign=\"top\"><td>\r\n<h2>Ignore messy variables hardcoded in Textpattern</h2>\r\n<table class=\"aks_chk aks_chk_table\"><tr><th>Ignore</th><th>Variable</th><th>Description</th></tr>{$html_messy}</table>\r\n</td><td>\r\n<h2>Return 404 for hardcoded url</h2>\r\n<table class=\"aks_chk aks_chk_table\"><tr><th>404</th><th>Hardcode url</th><th>Description</th></tr>{$html_hardlink}</table>\r\n</td></tr></table><br /><br />\r\n<input type=\"button\" value=\"All\"  onclick=\"$(\'.aks_chk input:checkbox:enabled\').attr(\'checked\', \'checked\');\" />\r\n<input type=\"button\" value=\"None\" onclick=\"$(\'.aks_chk input:checkbox:enabled\').removeAttr(\'checked\');\" />\r\n&nbsp;&nbsp;&nbsp;$button\r\n</form></center>\r\nEOF;\r\n}\r\n\r\n\r\nfunction aks_clean_url_admin_table( $aa, $name, $link=0){\r\n	global $prefs;\r\n	$out=\"\";\r\n	$chks=do_list(@$prefs[$name]);\r\n	if(is_array($aa)){\r\n		foreach($aa as $key=>$d){\r\n			$kk  =($link) ? \"/{$key}\" : \"/?{$key}=\";\r\n			$chk = (in_array($key, $chks)) ? \"checked\" : \"\";\r\n			$out.=\"<tr><td class=\'chk\'><input type=\'checkbox\' name=\'{$name}[]\' value=\'$key\' class=\'checkbox\' $chk /></td><td>$kk</td><td>$d</td></tr>\";\r\n		}\r\n		return $out;\r\n	}\r\n}\r\n\r\n// from preText, I do not understand this.\r\nfunction aks_clean_url_decode($txt=\'\'){ return urldecode(strtolower(urlencode(gTxt($txt)))); }\r\n', 'if(@txpinterface == \'public\'){\r\n	register_callback(\'aks_clean_url_cb_public\', \'pretext\');\r\n}\r\nif(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_clean_url_tab\', \'1\');\r\n	add_privs(\'plugin_prefs.aks_clean_url\',\'1\');\r\n	register_tab(\"extensions\", \"aks_clean_url_tab\", \"Clean URL\");\r\n	register_callback(\"aks_clean_url_tab\", \"aks_clean_url_tab\");\r\n	register_callback(\'aks_clean_url_tab\', \'plugin_prefs.aks_clean_url\');\r\n}\r\n\r\n\r\nfunction aks_clean_url_cb_public(){\r\n	global $prefs;\r\n\r\n	$messy=do_list(@$prefs[\'aks_clean_url_messy\']);\r\n	foreach($messy as $m){\r\n		unset($_POST[$m], $_GET[$m]);\r\n	}\r\n\r\n	$hardlink=do_list(@$prefs[\'aks_clean_url_hardlink\']);\r\n	$hardlink=array_map(\'aks_clean_url_decode\', $hardlink );\r\n	$req=chopUrl(serverSet(\'REQUEST_URI\'));\r\n	if( in_array($req[\'u1\'], $hardlink) ){\r\n		txp_die(gTxt(\'404_not_found\'), \'404\');		// maybe 301 in future\r\n	}\r\n}\r\n\r\n//admin tab\r\nfunction aks_clean_url_tab(){\r\n	global $prefs;\r\n	if (ps(\"save\")) {\r\n		// for buggy set_pref() function..  :(\r\n		safe_update(\'txp_prefs\', \"type = 2\", \"name in(\'aks_clean_url_messy\', \'aks_clean_url_hardlink\')\");\r\n\r\n		$val=@join(\',\', ps(\'aks_clean_url_messy\'));	set_pref(\"aks_clean_url_messy\", $val, \'publish\', 2);\r\n		$val=@join(\',\', ps(\'aks_clean_url_hardlink\'));	set_pref(\"aks_clean_url_hardlink\", $val, \'publish\', 2);\r\n		pagetop(\"Clean URL Prefs\", \"Preferences Saved\");\r\n		$prefs=get_prefs();\r\n	} else {\r\n		pagetop(\"Clean URL Prefs\");\r\n	}\r\n\r\n	$messy=array(\r\n		\'id\'		=> \'Article ID\'\r\n		,\'s\'		=> \'Section\'\r\n		,\'c\'		=> \'Category\'\r\n		,\'context\'	=> \'Category content type\'\r\n		,\'q\'		=> \'Internal TxP search\'\r\n		,\'m\'		=> \'Search match type\'\r\n		,\'pg\'		=> \'Page number\'\r\n		,\'p\'		=> \'Image ID\'\r\n		,\'month\'	=> \'Date\'\r\n		,\'author\'	=> \'Author\'\r\n		,\'rss\'		=> \'RSS feed\'\r\n		,\'atom\'		=> \'Atom feed\'\r\n//		,\'parentid\'		=> \'Comments...\'\r\n	);\r\n\r\n	$hardlink=array(\r\n		\'rss\'				=> \'RSS feed\'\r\n		,\'atom\'				=> \'Atom feed\'\r\n		,\'section\'			=> \'Section\'\r\n		,\'category\'			=> \'Category\'\r\n		,\'author\'			=> \'Author\'\r\n		,\'file_download\'	=> \'File download\'\r\n	);\r\n\r\n	$html_messy=aks_clean_url_admin_table( $messy, \'aks_clean_url_messy\');\r\n	$html_hardlink=aks_clean_url_admin_table( $hardlink, \'aks_clean_url_hardlink\', 1);\r\n\r\n	//html code...\r\n	$button=fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"aks_clean_url_tab\").sInput(\'saveprefs\');\r\n	echo <<<EOF\r\n<style type=\"text/css\">\r\n.aks_chk_table { width: 400px; margin: 10px; }\r\n.aks_chk_table th{ border-bottom: thin dotted;	border-color: #000000; }\r\n.aks_chk_table td{ border-bottom: thin dotted;	border-color: #cccccc; }\r\n.aks_chk_table .chk{ text-align: center; }\r\n</style>\r\n<center><form method=\"post\" action=\"index.php\">\r\n<table><tr valign=\"top\"><td>\r\n<h2>Ignore messy variables hardcoded in Textpattern</h2>\r\n<table class=\"aks_chk aks_chk_table\"><tr><th>Ignore</th><th>Variable</th><th>Description</th></tr>{$html_messy}</table>\r\n</td><td>\r\n<h2>Return 404 for hardcoded url</h2>\r\n<table class=\"aks_chk aks_chk_table\"><tr><th>404</th><th>Hardcode url</th><th>Description</th></tr>{$html_hardlink}</table>\r\n</td></tr></table><br /><br />\r\n<input type=\"button\" value=\"All\"  onclick=\"$(\'.aks_chk input:checkbox:enabled\').attr(\'checked\', \'checked\');\" />\r\n<input type=\"button\" value=\"None\" onclick=\"$(\'.aks_chk input:checkbox:enabled\').removeAttr(\'checked\');\" />\r\n&nbsp;&nbsp;&nbsp;$button\r\n</form></center>\r\nEOF;\r\n}\r\n\r\n\r\nfunction aks_clean_url_admin_table( $aa, $name, $link=0){\r\n	global $prefs;\r\n	$out=\"\";\r\n	$chks=do_list(@$prefs[$name]);\r\n	if(is_array($aa)){\r\n		foreach($aa as $key=>$d){\r\n			$kk  =($link) ? \"/{$key}\" : \"/?{$key}=\";\r\n			$chk = (in_array($key, $chks)) ? \"checked\" : \"\";\r\n			$out.=\"<tr><td class=\'chk\'><input type=\'checkbox\' name=\'{$name}[]\' value=\'$key\' class=\'checkbox\' $chk /></td><td>$kk</td><td>$d</td></tr>\";\r\n		}\r\n		return $out;\r\n	}\r\n}\r\n\r\n// from preText, I do not understand this.\r\nfunction aks_clean_url_decode($txt=\'\'){ return urldecode(strtolower(urlencode(gTxt($txt)))); }\r\n', '006ec8403a78cd4cf9c44c9ddd8897c9', '', '', 1, 3, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('aks_cron', 0, 'makss', 'http://makss.uaho.net/plugins/aks_cron', '0.2.1', 'Cron inside Textpattern', 'Sorry, no help available.<br /><br />\n<a href=\"http://makss.uaho.net/plugins/aks_cron\" target=\"_blank\" >Homepage aks_cron</a>\n\n<!--\n<pre>\n2do:\n	prefs\n		log\n		auto unhold\n\n	get templates from homepage\n\n\nchanges:\n\n0.2c		default template\n		run job now\n\n0.2d		bugfix in akk_log\n0.2e		has prefs\n		field php_cron -> mediumtext\n0.2.1		Txp 4.7-dev\n\n</pre>\n-->', 'if(@txpinterface==\'public\' && (@$prefs[\'aks_cron_lastrun\']+@$prefs[\'aks_cron_runevery\']*60<time()) ){\r\n	aks_cron_run();\r\n}\r\n\r\nif(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_cron_tab\', \'1\');\r\n	register_tab(\"extensions\", \"aks_cron_tab\", \"aks_cron\");\r\n	register_callback(\"aks_cron_tab\", \"aks_cron_tab\");\r\n	add_privs(\'plugin_prefs.aks_cron\', \'1\');\r\n	register_callback(\'aks_cron_tab\', \'plugin_prefs.aks_cron\');\r\n}\r\n\r\n\r\nfunction aks_cron_run($job=\'\'){\r\n	global $prefs;\r\n	set_pref(\'aks_cron_lastrun\', time(), \'aks_cron\', 1);\r\n	if($job){\r\n		$rs = @safe_row(\"*\", \"aks_cron\",\"cron_id=\'$job\'\");\r\n	}else{\r\n		$rs = @safe_row(\"*\", \"aks_cron\",\"a1=1 and hold=0 and next_run<UNIX_TIMESTAMP() order by next_run limit 1\");\r\n	}\r\n	if($rs){\r\n		extract($rs);\r\n		$tt = explode(\'|\', $date_txt);\r\n		$diff = $tt[0]*86400 + $tt[1]*3600 + $tt[2]*60;\r\n		safe_update(\'aks_cron\', \"last_result=\'Running\', hold=1, last_run=UNIX_TIMESTAMP()\", \"cron_id=\'$cron_id\'\");\r\n		$res = @eval($php_cron);\r\n		$tt = explode(\'|\', $res);\r\n		$res1 = doSlash($tt[0]);\r\n		safe_update(\'aks_cron\', \"last_result=\'$res1\', hold=0, next_run=UNIX_TIMESTAMP()+$diff\", \"cron_id=\'$cron_id\'\");\r\n		if(function_exists(\'akk_log\')){\r\n			akk_log(\"{$res1}\\t\\t{$title}\\t\".@$tt[1] );\r\n		}\r\n	}\r\n\r\n}\r\n\r\n\r\nfunction aks_cron_init(){\r\n	global $prefs;\r\n	$tcheck = getRows(\"SHOW TABLES LIKE \'\".PFX.\"aks_cron\'\");\r\n	if(!$tcheck) {\r\n		$qc=\"CREATE TABLE `\".PFX.\"aks_cron` (\";\r\n		$qc.= <<<EOF\r\n		`cron_id` int(11) NOT NULL auto_increment,\r\n		`date_txt` varchar(255) NOT NULL default \'\',\r\n		`next_run` int(10) unsigned NOT NULL default \'0\',\r\n		`last_run` int(10) unsigned NOT NULL default \'0\',\r\n		`last_result` varchar(255) NOT NULL default \'\',\r\n		`title` varchar(255) NOT NULL default \'\',\r\n		`php_cron` mediumtext NOT NULL,\r\n		`php_back` mediumtext NOT NULL,\r\n		`check` tinyint(4) NOT NULL default \'0\',\r\n		`a1` tinyint(4) NOT NULL default \'0\',\r\n		`hold` tinyint(4) NOT NULL default \'0\',\r\n		PRIMARY KEY  (`cron_id`),\r\n		KEY `ind_nextrun` (`a1`,`next_run`)\r\n		) ENGINE=MyISAM DEFAULT CHARSET=utf8;\r\nEOF;\r\n		safe_query($qc);\r\n	}\r\n	if(!isset($prefs[\'aks_cron_lastrun\'])){\r\n		set_pref(\'aks_cron_lastrun\', 0, \'aks_cron\', 1);\r\n		$prefs[\'aks_cron_lastrun\'] = \"0\";\r\n	}\r\n	if(!isset($prefs[\'aks_cron_runevery\'])){\r\n		set_pref(\'aks_cron_runevery\', 10, \'aks_cron\', 1);\r\n		$prefs[\'aks_cron_runevery\'] = \"10\";\r\n	}\r\n}\r\n\r\n\r\nfunction aks_cron_tab($event, $step){\r\n	global $prefs;\r\n	aks_cron_init();\r\n\r\n	if($step==\'reset_lastrun\'){\r\n		set_pref(\'aks_cron_lastrun\', 0, \'aks_cron\', 1);\r\n	}\r\n\r\n	if($step==\'save_job\'){\r\n		$cron_id = doSlash(ps(\'cron_id\'));\r\n		$title = doSlash(ps(\'title\'));\r\n		$php_cron = doSlash(ps(\'php_cron\'));\r\n		$date_txt = doSlash(intval(ps(\'t0\')).\'|\'.intval(ps(\'t1\')).\'|\'.intval(ps(\'t2\')));\r\n		$hold = (int)ps(\'hold\');\r\n		$a1 = (int)ps(\'active\');\r\n\r\n		if(ps(\'asnew\')==\'NEW\' || $cron_id==\'NEW\'){\r\n			safe_insert(\'aks_cron\', \"title=\'$title\', php_cron=\'$php_cron\', date_txt=\'$date_txt\', hold=$hold, a1=$a1\");\r\n		}else{\r\n			safe_update(\'aks_cron\', \"title=\'$title\', php_cron=\'$php_cron\', date_txt=\'$date_txt\', hold=$hold, a1=$a1\", \"cron_id = $cron_id\");\r\n		}\r\n	}\r\n\r\n	if($step==\'run\' && gps(\'id\')){\r\n		aks_cron_run(gps(\'id\'));\r\n	}\r\n\r\n\r\n	if($step==\'edit\'){\r\n		$id = \"NEW\"; $date_txt = \"1|0|0\"; $title = \"New job\"; $a1 = \"\"; $hold = \"\";\r\n		$php_cron=\"// \".date(\'Y-m-d\\TH:i:sO\').\"\\t\\tremark\\n\\n\\n// if(\\$rs){ return \\\"OK\\\"; }else{return \\\"FALLED|some wrong ... your message\\\";}\\n\";\r\n\r\n		if(gps(\'id\')){\r\n			$rs=safe_row(\"*\", \"aks_cron\", \"cron_id=\'\".doSlash(gps(\'id\')).\"\'\");\r\n			if($rs){\r\n				extract($rs);\r\n				$id = gps(\'id\');\r\n				if($a1){ $a1=\"checked\"; }\r\n				if($hold){ $hold=\"checked\"; }\r\n			}\r\n		}\r\n		$tt = explode(\'|\', $date_txt);\r\n		$t0 = $tt[0]; $t1 = @$tt[1]; $t2 = @$tt[2];\r\n		pagetop(\"aks_cron Prefs\");\r\n		echo <<<EOF\r\n<form name=\"longform\" method=\"post\" action=\"?event=aks_cron_tab\">\r\n<input type=\"hidden\" name=\"cron_id\" value=\"$id\" /><input type=\"hidden\" name=\"step\" value=\"save_job\" />\r\n<table width=\"90%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\"><tr><td style=\"text-align:right;\">\r\nTitle<br /><br />\r\nphp_cron<br />\r\n</td><td>\r\n<input type=\"text\" name=\"title\" value=\"$title\" size=\"120\" /><br /><br />\r\n<textarea name=\"php_cron\" rows=\"30\" cols=\"120\">$php_cron</textarea><br />\r\n</td><td style=\"text-align:right;\">\r\ncron_id<br /><br />\r\nday<br />\r\nhour<br />\r\nmin<br />\r\n</td><td>\r\n<b>$id</b><br /><br />\r\n<input type=\"text\" name=\"t0\" value=\"$t0\" size=\"7\" /><br />\r\n<input type=\"text\" name=\"t1\" value=\"$t1\" size=\"7\" /><br />\r\n<input type=\"text\" name=\"t2\" value=\"$t2\" size=\"7\" /><br /><br />\r\n<input type=\'checkbox\' name=\'active\' value=\'1\' $a1 /> Active<br />\r\n<input type=\'checkbox\' name=\'hold\' value=\'1\' $hold /> Hold<br /><br />\r\n<input type=\'checkbox\' name=\'asnew\' value=\'NEW\' /> Save as NEW job<br />\r\n<br /><input type=\"submit\" value=\"Save\" class=\"publish\" />\r\n</td></tr></table>\r\n</form>\r\nEOF;\r\n		return;\r\n	}	//-- end edit\r\n\r\n	if(ps(\'v\')){\r\n		$v = implode(\',\', ps(\'v\'));\r\n		if(ps(\'mode\')==\'active\'  ){ safe_update(\"aks_cron\", \"a1 = 1\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'unactive\'){ safe_update(\"aks_cron\", \"a1 = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'unhold\'){   safe_update(\"aks_cron\", \"hold = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'nextrun\'){  safe_update(\"aks_cron\", \"next_run = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'delete\'){   safe_delete(\"aks_cron\", \"cron_id in($v)\"); }\r\n	}\r\n\r\n	pagetop(\"aks_cron Prefs\");\r\n	$GLOBALS[\'prefs\'] = get_prefs();\r\n	$rs = safe_rows(\"cron_id, date_txt, FROM_UNIXTIME(next_run) next_run, FROM_UNIXTIME(last_run) last_run, last_result, title, hold, a1\", \"aks_cron\", \' 1=1 order by a1 desc, title\');\r\n\r\n	echo \'<form name=\"longform\" method=\"post\" action=\"?event=aks_cron_tab\" onsubmit=\"return confirm(\\\'Вы уверены?\\\')\">\';\r\n	echo \'<table width=\"70%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\"><tr style=\"background-color: #eeeeee;\"><th>id</th><th style=\"text-align:right;\">Run Every<br />Day_Hour:Min</th><th>Job Title</th><th>Next Run</th><th>Last Run</th><th>Result</th><th>Hold</th><th>Active</th><th> </th><th> </th></tr>\';\r\n\r\n	if($rs){\r\n		foreach($rs as $a){\r\n			extract($a);\r\n			$tt = explode(\'|\', $date_txt);\r\n			$t = $tt[0].\"_\".$tt[1].\":\".$tt[2];\r\n			if(!$hold){$hold=\"\";} if(!$a1){$a1=\"\";}\r\n			if(!$a1 || $hold){$bb=\'style=\"background-color: #ffffcc;\"\'; }else{$bb=\"\";}\r\necho \"<tr $bb><td>$cron_id</td><td style=\'text-align:right;\'><b>$t</b></td><td><a href=\'?event=aks_cron_tab&step=edit&id=$cron_id\' title=\'Edit Job\'>$title</a></td><td>$next_run</td><td><a href=\'?event=aks_cron_tab&step=run&id=$cron_id\' title=\'Run Job NOW\'>$last_run</a></td><td>$last_result</td><td>$hold</td><td>$a1</td><td><input type=\'checkbox\' name=\'v[]\' value=\'$cron_id\' /></td><td> </td></tr>\";\r\n		}\r\n	}\r\n	$lr = date(\'Y-m-d H:i:s\', $prefs[\'aks_cron_lastrun\']);\r\n	$lr0 = date(\'Y-m-d H:i:s\');\r\n	echo <<<EOF\r\n<tr style=\"background-color: #eeeeee;\"><td colspan=\"3\"><a href=\"?event=aks_cron_tab&step=edit\">Create New Job</a></td>\r\n<td colspan=\"7\" style=\'text-align:right;\'>\r\n	<select name=\"mode\">\r\n		<option value=\"\"></option>\r\n		<option value=\"active\">Active</option>\r\n		<option value=\"unactive\">unActive</option>\r\n		<option value=\"unhold\">unHold</option>\r\n		<option value=\"nextrun\">Reset NextRun</option>\r\n		<option value=\"delete\">Delete</option>\r\n		<!-- <option value=\"backup\">Backup Jobs</option> -->\r\n	</select>\r\n	<input type=\"submit\" value=\"Submit\" />\r\n</td></tr>\r\n<tr style=\"background-color: #eeeeee;\"><td colspan=\"3\" style=\"text-align:right;\">\r\n	<a href=\"?event=aks_cron_tab&step=reset_lastrun\">Reset =&gt;</a> Last Run:<br />Now:\r\n</td><td colspan=\"7\">\r\n	$lr<br />\r\n	$lr0\r\n</td></tr>\r\n</table>\r\n</form>\r\nEOF;\r\n}\r\n', 'if(@txpinterface==\'public\' && (@$prefs[\'aks_cron_lastrun\']+@$prefs[\'aks_cron_runevery\']*60<time()) ){\r\n	aks_cron_run();\r\n}\r\n\r\nif(@txpinterface == \'admin\'){\r\n	add_privs(\'aks_cron_tab\', \'1\');\r\n	register_tab(\"extensions\", \"aks_cron_tab\", \"aks_cron\");\r\n	register_callback(\"aks_cron_tab\", \"aks_cron_tab\");\r\n	add_privs(\'plugin_prefs.aks_cron\', \'1\');\r\n	register_callback(\'aks_cron_tab\', \'plugin_prefs.aks_cron\');\r\n}\r\n\r\n\r\nfunction aks_cron_run($job=\'\'){\r\n	global $prefs;\r\n	set_pref(\'aks_cron_lastrun\', time(), \'aks_cron\', 1);\r\n	if($job){\r\n		$rs = @safe_row(\"*\", \"aks_cron\",\"cron_id=\'$job\'\");\r\n	}else{\r\n		$rs = @safe_row(\"*\", \"aks_cron\",\"a1=1 and hold=0 and next_run<UNIX_TIMESTAMP() order by next_run limit 1\");\r\n	}\r\n	if($rs){\r\n		extract($rs);\r\n		$tt = explode(\'|\', $date_txt);\r\n		$diff = $tt[0]*86400 + $tt[1]*3600 + $tt[2]*60;\r\n		safe_update(\'aks_cron\', \"last_result=\'Running\', hold=1, last_run=UNIX_TIMESTAMP()\", \"cron_id=\'$cron_id\'\");\r\n		$res = @eval($php_cron);\r\n		$tt = explode(\'|\', $res);\r\n		$res1 = doSlash($tt[0]);\r\n		safe_update(\'aks_cron\', \"last_result=\'$res1\', hold=0, next_run=UNIX_TIMESTAMP()+$diff\", \"cron_id=\'$cron_id\'\");\r\n		if(function_exists(\'akk_log\')){\r\n			akk_log(\"{$res1}\\t\\t{$title}\\t\".@$tt[1] );\r\n		}\r\n	}\r\n\r\n}\r\n\r\n\r\nfunction aks_cron_init(){\r\n	global $prefs;\r\n	$tcheck = getRows(\"SHOW TABLES LIKE \'\".PFX.\"aks_cron\'\");\r\n	if(!$tcheck) {\r\n		$qc=\"CREATE TABLE `\".PFX.\"aks_cron` (\";\r\n		$qc.= <<<EOF\r\n		`cron_id` int(11) NOT NULL auto_increment,\r\n		`date_txt` varchar(255) NOT NULL default \'\',\r\n		`next_run` int(10) unsigned NOT NULL default \'0\',\r\n		`last_run` int(10) unsigned NOT NULL default \'0\',\r\n		`last_result` varchar(255) NOT NULL default \'\',\r\n		`title` varchar(255) NOT NULL default \'\',\r\n		`php_cron` mediumtext NOT NULL,\r\n		`php_back` mediumtext NOT NULL,\r\n		`check` tinyint(4) NOT NULL default \'0\',\r\n		`a1` tinyint(4) NOT NULL default \'0\',\r\n		`hold` tinyint(4) NOT NULL default \'0\',\r\n		PRIMARY KEY  (`cron_id`),\r\n		KEY `ind_nextrun` (`a1`,`next_run`)\r\n		) ENGINE=MyISAM DEFAULT CHARSET=utf8;\r\nEOF;\r\n		safe_query($qc);\r\n	}\r\n	if(!isset($prefs[\'aks_cron_lastrun\'])){\r\n		set_pref(\'aks_cron_lastrun\', 0, \'aks_cron\', 1);\r\n		$prefs[\'aks_cron_lastrun\'] = \"0\";\r\n	}\r\n	if(!isset($prefs[\'aks_cron_runevery\'])){\r\n		set_pref(\'aks_cron_runevery\', 10, \'aks_cron\', 1);\r\n		$prefs[\'aks_cron_runevery\'] = \"10\";\r\n	}\r\n}\r\n\r\n\r\nfunction aks_cron_tab($event, $step){\r\n	global $prefs;\r\n	aks_cron_init();\r\n\r\n	if($step==\'reset_lastrun\'){\r\n		set_pref(\'aks_cron_lastrun\', 0, \'aks_cron\', 1);\r\n	}\r\n\r\n	if($step==\'save_job\'){\r\n		$cron_id = doSlash(ps(\'cron_id\'));\r\n		$title = doSlash(ps(\'title\'));\r\n		$php_cron = doSlash(ps(\'php_cron\'));\r\n		$date_txt = doSlash(intval(ps(\'t0\')).\'|\'.intval(ps(\'t1\')).\'|\'.intval(ps(\'t2\')));\r\n		$hold = (int)ps(\'hold\');\r\n		$a1 = (int)ps(\'active\');\r\n\r\n		if(ps(\'asnew\')==\'NEW\' || $cron_id==\'NEW\'){\r\n			safe_insert(\'aks_cron\', \"title=\'$title\', php_cron=\'$php_cron\', date_txt=\'$date_txt\', hold=$hold, a1=$a1\");\r\n		}else{\r\n			safe_update(\'aks_cron\', \"title=\'$title\', php_cron=\'$php_cron\', date_txt=\'$date_txt\', hold=$hold, a1=$a1\", \"cron_id = $cron_id\");\r\n		}\r\n	}\r\n\r\n	if($step==\'run\' && gps(\'id\')){\r\n		aks_cron_run(gps(\'id\'));\r\n	}\r\n\r\n\r\n	if($step==\'edit\'){\r\n		$id = \"NEW\"; $date_txt = \"1|0|0\"; $title = \"New job\"; $a1 = \"\"; $hold = \"\";\r\n		$php_cron=\"// \".date(\'Y-m-d\\TH:i:sO\').\"\\t\\tremark\\n\\n\\n// if(\\$rs){ return \\\"OK\\\"; }else{return \\\"FALLED|some wrong ... your message\\\";}\\n\";\r\n\r\n		if(gps(\'id\')){\r\n			$rs=safe_row(\"*\", \"aks_cron\", \"cron_id=\'\".doSlash(gps(\'id\')).\"\'\");\r\n			if($rs){\r\n				extract($rs);\r\n				$id = gps(\'id\');\r\n				if($a1){ $a1=\"checked\"; }\r\n				if($hold){ $hold=\"checked\"; }\r\n			}\r\n		}\r\n		$tt = explode(\'|\', $date_txt);\r\n		$t0 = $tt[0]; $t1 = @$tt[1]; $t2 = @$tt[2];\r\n		pagetop(\"aks_cron Prefs\");\r\n		echo <<<EOF\r\n<form name=\"longform\" method=\"post\" action=\"?event=aks_cron_tab\">\r\n<input type=\"hidden\" name=\"cron_id\" value=\"$id\" /><input type=\"hidden\" name=\"step\" value=\"save_job\" />\r\n<table width=\"90%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\"><tr><td style=\"text-align:right;\">\r\nTitle<br /><br />\r\nphp_cron<br />\r\n</td><td>\r\n<input type=\"text\" name=\"title\" value=\"$title\" size=\"120\" /><br /><br />\r\n<textarea name=\"php_cron\" rows=\"30\" cols=\"120\">$php_cron</textarea><br />\r\n</td><td style=\"text-align:right;\">\r\ncron_id<br /><br />\r\nday<br />\r\nhour<br />\r\nmin<br />\r\n</td><td>\r\n<b>$id</b><br /><br />\r\n<input type=\"text\" name=\"t0\" value=\"$t0\" size=\"7\" /><br />\r\n<input type=\"text\" name=\"t1\" value=\"$t1\" size=\"7\" /><br />\r\n<input type=\"text\" name=\"t2\" value=\"$t2\" size=\"7\" /><br /><br />\r\n<input type=\'checkbox\' name=\'active\' value=\'1\' $a1 /> Active<br />\r\n<input type=\'checkbox\' name=\'hold\' value=\'1\' $hold /> Hold<br /><br />\r\n<input type=\'checkbox\' name=\'asnew\' value=\'NEW\' /> Save as NEW job<br />\r\n<br /><input type=\"submit\" value=\"Save\" class=\"publish\" />\r\n</td></tr></table>\r\n</form>\r\nEOF;\r\n		return;\r\n	}	//-- end edit\r\n\r\n	if(ps(\'v\')){\r\n		$v = implode(\',\', ps(\'v\'));\r\n		if(ps(\'mode\')==\'active\'  ){ safe_update(\"aks_cron\", \"a1 = 1\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'unactive\'){ safe_update(\"aks_cron\", \"a1 = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'unhold\'){   safe_update(\"aks_cron\", \"hold = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'nextrun\'){  safe_update(\"aks_cron\", \"next_run = 0\", \"cron_id in($v)\"); }\r\n		if(ps(\'mode\')==\'delete\'){   safe_delete(\"aks_cron\", \"cron_id in($v)\"); }\r\n	}\r\n\r\n	pagetop(\"aks_cron Prefs\");\r\n	$GLOBALS[\'prefs\'] = get_prefs();\r\n	$rs = safe_rows(\"cron_id, date_txt, FROM_UNIXTIME(next_run) next_run, FROM_UNIXTIME(last_run) last_run, last_result, title, hold, a1\", \"aks_cron\", \' 1=1 order by a1 desc, title\');\r\n\r\n	echo \'<form name=\"longform\" method=\"post\" action=\"?event=aks_cron_tab\" onsubmit=\"return confirm(\\\'Вы уверены?\\\')\">\';\r\n	echo \'<table width=\"70%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" id=\"list\" align=\"center\"><tr style=\"background-color: #eeeeee;\"><th>id</th><th style=\"text-align:right;\">Run Every<br />Day_Hour:Min</th><th>Job Title</th><th>Next Run</th><th>Last Run</th><th>Result</th><th>Hold</th><th>Active</th><th> </th><th> </th></tr>\';\r\n\r\n	if($rs){\r\n		foreach($rs as $a){\r\n			extract($a);\r\n			$tt = explode(\'|\', $date_txt);\r\n			$t = $tt[0].\"_\".$tt[1].\":\".$tt[2];\r\n			if(!$hold){$hold=\"\";} if(!$a1){$a1=\"\";}\r\n			if(!$a1 || $hold){$bb=\'style=\"background-color: #ffffcc;\"\'; }else{$bb=\"\";}\r\necho \"<tr $bb><td>$cron_id</td><td style=\'text-align:right;\'><b>$t</b></td><td><a href=\'?event=aks_cron_tab&step=edit&id=$cron_id\' title=\'Edit Job\'>$title</a></td><td>$next_run</td><td><a href=\'?event=aks_cron_tab&step=run&id=$cron_id\' title=\'Run Job NOW\'>$last_run</a></td><td>$last_result</td><td>$hold</td><td>$a1</td><td><input type=\'checkbox\' name=\'v[]\' value=\'$cron_id\' /></td><td> </td></tr>\";\r\n		}\r\n	}\r\n	$lr = date(\'Y-m-d H:i:s\', $prefs[\'aks_cron_lastrun\']);\r\n	$lr0 = date(\'Y-m-d H:i:s\');\r\n	echo <<<EOF\r\n<tr style=\"background-color: #eeeeee;\"><td colspan=\"3\"><a href=\"?event=aks_cron_tab&step=edit\">Create New Job</a></td>\r\n<td colspan=\"7\" style=\'text-align:right;\'>\r\n	<select name=\"mode\">\r\n		<option value=\"\"></option>\r\n		<option value=\"active\">Active</option>\r\n		<option value=\"unactive\">unActive</option>\r\n		<option value=\"unhold\">unHold</option>\r\n		<option value=\"nextrun\">Reset NextRun</option>\r\n		<option value=\"delete\">Delete</option>\r\n		<!-- <option value=\"backup\">Backup Jobs</option> -->\r\n	</select>\r\n	<input type=\"submit\" value=\"Submit\" />\r\n</td></tr>\r\n<tr style=\"background-color: #eeeeee;\"><td colspan=\"3\" style=\"text-align:right;\">\r\n	<a href=\"?event=aks_cron_tab&step=reset_lastrun\">Reset =&gt;</a> Last Run:<br />Now:\r\n</td><td colspan=\"7\">\r\n	$lr<br />\r\n	$lr0\r\n</td></tr>\r\n</table>\r\n</form>\r\nEOF;\r\n}\r\n', 'db9a7d43e6fa91399ab446abe2495e7d', '', '', 1, 9, 1),
('aks_header', 0, 'makss', 'http://makss.uaho.net/plugins/aks_header', '0.4.1', 'Strip white spaces and GZIP compress pages on the fly. Set any page headers. Simple 301 redirect with conditions. Etag/If-None-Match support', '<div id=\"aks\">\n<h1>aks_header</h1>\n\n<p><a href=\"http://makss.uaho.net/plugins/aks_header\">aks_header homepage</a></p>\n<p><i>This plugin based on <a href=\"http://textpattern.org/plugins/856/mg_setheader\">mg_setheader</a>  by Mike Gravel.</i></p>\n\n<h2>Features:</h2><ul><li>Set any page headers. It’s useful for create your custom feeds.</li><li>Strip white spaces on the fly.</li><li><span class=\"caps\">GZIP</span> compress on the fly.</li><li>301 redirect non exist pages or directories to other place.</li></ul><p>.</p><p><code>&lt;txp:aks_header /&gt;</code></p><table><tbody><tr><th>attributes</th><th>default</th><th>sample</th><th>description</th></tr><tr><td>name</td><td><code>\'Content-Type\'</code></td><td><code>\'Content-Type\'</code></td><td>header</td></tr><tr><td>value</td><td><code>\'\'</code></td><td><code>\'application/rss+xml\'</code></td><td>header</td></tr><tr><td>gzip</td><td><code>\'0\'</code></td><td><code>\'1\'</code></td><td><span class=\"caps\"><span class=\"caps\">GZIP</span></span> compressions</td></tr><tr><td>strip</td><td><code>\'0\'</code></td><td><code>\'1\'</code></td><td>Strip white spaces</td></tr><tr><td>nostrip</td><td><code>\'pre,code,textarea,script,style\'</code></td><td><code>\'pre\'</code></td><td>Not Strip tags</td></tr><tr><td>file</td><td><code>\'0\'</code></td><td><code>\'1\'</code></td><td>Auto set header per file extension</td></tr><tr><td>nodebug</td><td><code>\'0\'</code></td><td><code>\'1\'</code></td><td>Set production_status = ‘live’</td></tr><tr><td>nocache</td><td><code>\'0\'</code></td><td><code>\'1\'</code></td><td>Set <strong>no-cache</strong> header</td></tr></tbody></table><h2>Examples:</h2><p>Place on top your page template</p> <pre>&lt;txp:aks_header strip=\"1\" gzip=\"1\" /&gt;&lt;!DOCTYPE html  . . .\n\n</pre> <p>or place on top your xml page template</p> <pre>&lt;txp:aks_header name=\"Content-Type\" value=\"application/rss+xml\" strip=\"1\" gzip=\"1\" /&gt;&lt;?xml version=\"1.0\" . . .\n</pre> <h2>Tests</h2><table><tbody><tr><th>Page</th><th>Original size</th><th>Strip white spaces</th><th><span class=\"caps\"><span class=\"caps\">GZIP</span></span></th><th>Strip+GZIP</th></tr><tr><td>http://makss.uaho.net/plugins/aks_rss</td><td>16726 bytes</td><td>15385 bytes</td><td>4234 bytes</td><td>4039 bytes</td></tr><tr><td>Save space from original size</td><td>0 %</td><td>8 %</td><td>75 %</td><td>76 %</td></tr></tbody></table><h2>Set header per file extension. “File” attribute</h2><p><strong>Support file extensions:</strong></p> <pre>	\'txt\' =&gt; \'text/plain\',\n	\'css\' =&gt; \'text/css\',\n	\'js\' =&gt; \'application/x-javascript\',\n	\'xml\' =&gt; \'application/rss+xml\'\n\n</pre> <p>.</p><h1>aks_301 – Simple 301 redirect with conditions</h1><h2>Summary</h2><p>This tag useful for redirect non exist pages or directories to other place.<br> Default: to domain root.</p><h2>Tags</h2><p><strong style=\"text-align: left;\">txp:aks_301 /&gt;</strong> — place this tag <strong>!!!ONLY!!!</strong> to start your page.</p><p>Sample my <strong>error_default</strong> page:</p> <pre>&lt;txp:aks_301 start=\"/categories/|/article/\" /&gt;&lt;!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"&gt;\n\n&lt;html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"&gt;\n&lt;head&gt;\n[skip...]\n</pre> <h2>Attributes</h2><table><tbody><tr><th>Attribute</th><th>Default</th><th>Description</th></tr><tr><td><strong>start</strong></td><td><code>\'\'</code></td><td>one or more conditions. Delimiter: <code>|</code></td></tr><tr><td><strong>url</strong></td><td><code>\'http://yourdomain.com/\'</code></td><td>one or more urls. Delimiter: <code>|</code> Default: your domail root</td></tr>\n<tr>\n	<td><strong>ignore</strong></td>\n	<td><code>&#39;&#39;</code></td>\n	<td>one or more conditions. Delimiter: <code>|</code>. Does nothing.</td>\n</tr>\n<tr>\n	<td><strong>black</strong></td>\n	<td> <code>&#39;cltreq.asp|owssvr.dll&#39;</code> </td>\n	<td>one or more conditions. Delimiter: <code>|</code>. Return 404 header and exit.</td>\n</tr>\n</tbody></table><p>.</p><h2>Examples</h2><p><strong>error_default</strong> page:</p> <pre>&lt;txp:aks_301 start=\"/categories/\" url=\"http://mydomain.com/somepage.html\" /&gt;\n\nall non exist pages in /categories/* - 301 redirect to http://mydomain.com/somepage.html\n</pre> <pre>&lt;txp:aks_301 start=\"/categories/|/section1/\" /&gt;\nall non exist pages in /categories/* and /section1/* - 301 redirect to domain root\n</pre> <pre>&lt;txp:aks_301 start=\"/\" /&gt;\nALL non exist pages  - 301 redirect to domain root\n</pre> <p><strong>default</strong> page:</p> <pre>&lt;txp:aks_301 start=\"/categories/\" url=\"http://mydomain.com/somepage.html\" /&gt;\nall EXIST pages in /categories/* - 301 redirect to http://mydomain.com/somepage.html\n\n</pre> <pre>&lt;txp:aks_301 start=\"/\" url=\"http://otherdomain.com/\" /&gt;\nALL EXIST pages  - 301 redirect to http://otherdomain.com/\n</pre> <p>.</p>\n\n\n<pre>\n0.3.8.4    add header Vary: Accept-Encoding\n0.3.9	redesing code, values\n	added:	aks_header_etag\n			etag attribute\n			If-None-Match  -  304 Not Modified\n0.4		clear mark  aks_...	used in aks_meta, aks_backlink\n0.4.1		Txp 4.6 fixed\n\n</pre>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n	background-color: #E3E3DB;\n	border:1px solid;\n	border-color:#ddd #999 #888 #ddd;\n	padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #CCCCCC;\nmargin: 1.5em 0;\n}\n\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if(@txpinterface == \'public\'){\r\n	if(!in_array(\'aks_header_callback\', ob_list_handlers() )){ ob_start(\'aks_header_callback\'); }\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n			->register(\'aks_301\')\r\n			->register(\'aks_header\')\r\n			->register(\'aks_header_etag\')\r\n		;\r\n	}\r\n}\r\n\r\nfunction aks_header($atts) {\r\n	global	$thisarticle, $aks_headers\r\n		, $aks_header_strip\r\n		, $aks_header_nostrip\r\n		, $aks_header_gzip\r\n		, $aks_filter\r\n		, $aks_header_etag;\r\n	$ext=array(				// extension => header\r\n		\'txt\'	=> \'text/plain\',\r\n		\'css\'	=> \'text/css\',\r\n		\'js\'	=> \'application/x-javascript\',\r\n		\'xml\'	=> \'application/rss+xml\'\r\n	);\r\n	extract(lAtts(array(\r\n		\'file\'		=> \'0\',\r\n		\'nodebug\'	=> \'0\',\r\n		\'nocache\'	=> \'0\',\r\n		\'name\'		=> \'Content-Type\',\r\n		\'value\'		=> \'\',\r\n		\'strip\'		=> \'0\',\r\n		\'gzip\'		=> \'0\',\r\n		\'filter\'	=> \'\',\r\n		\'etag\'		=> \'\',\r\n		\'nostrip\'	=> \'pre,code,textarea,script,style\'\r\n	),$atts));\r\n	$aks_header_nostrip = trim($nostrip);\r\n	$aks_header_etag = ($etag) ? 1 : 0;\r\n\r\n	if($filter==\'none\'){\r\n		$aks_filter = array();\r\n	}elseif($filter){\r\n		$aks_filter = array_merge($aks_filter, preg_split(\'/\\,/\', $filter) );\r\n	}\r\n\r\n	if($strip){ $aks_header_strip = 1; }\r\n	if($gzip && !in_array(\'zlib output compression\', ob_list_handlers()) ){ $aks_header_gzip = 1; }\r\n\r\n	if($value){ $aks_headers[$name] = $value; }\r\n	if($nodebug || $file){ $GLOBALS[\'production_status\'] = \'live\'; }\r\n\r\n	if($file && preg_match(\'/^.*\\.(.*)$/\', $thisarticle[\'url_title\'], $mm) ){\r\n		if($ext[$mm[1]]){ $aks_headers[\'Content-Type\'] = $ext[$mm[1]];}\r\n		if($mm[1] !=\'xml\'){ $aks_headers[\'Last-Modified\'] = safe_strftime(\'rfc822\',$thisarticle[\'modified\']); }\r\n	}\r\n	if($nocache){\r\n		$aks_headers[\"Cache-Control\"] = \"no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\";\r\n		$aks_headers[\"Expires\"] = \"Sat, 9 Jun 1990 07:00:00 GMT\";\r\n		$aks_headers[\'Last-Modified\'] = safe_strftime(\'rfc822\');\r\n		$aks_headers[\"Pragma\"] = \"no-cache\";\r\n	}\r\n\r\n}\r\n\r\nfunction aks_header_callback_preg($mm) {\r\n	static $c=0; $c+=1;\r\n	global $aks_header_block;\r\n	$key = \"@@DONTOUCHMETAG{$c}@@\";\r\n	$aks_header_block[$key] = $mm[0];\r\n	return $key;\r\n}\r\n\r\nfunction aks_header_callback($html) {\r\n	global	$aks_headers\r\n		, $aks_header_strip\r\n		, $aks_header_nostrip\r\n		, $aks_header_gzip\r\n		, $aks_header_block\r\n		, $aks_filter\r\n		, $aks_header_etag\r\n		, $production_status;\r\n\r\n	if( isset($aks_headers) && is_array($aks_headers) ){\r\n		foreach ($aks_headers as $name => $value){ header(\"$name: $value\"); }\r\n	}\r\n\r\n	if( is_array($aks_filter) ){\r\n		ksort($aks_filter);\r\n		foreach( array_unique($aks_filter) as $filter){\r\n			if( function_exists($filter) ){ $html = $filter($html); }\r\n		}\r\n	}\r\n\r\n	if( $production_status == \'live\' ){					// clear marks aks_	<!--aks_...--><!--/aks_...-->\r\n		$html = preg_replace(\'/<!--\\/?aks_.*?-->/i\', \'\', $html);\r\n	}\r\n\r\n	if( $aks_header_strip ){\r\n		$aks_header_block = array();\r\n		$html = preg_replace (\"/\\015\\012|\\015|\\012/\", PHP_EOL, $html);\r\n		if( $aks_header_nostrip ){\r\n			$aa = preg_split(\'/[, ]+/\', $aks_header_nostrip);\r\n			foreach($aa as $a){\r\n				$html = preg_replace_callback(\"!<(\".$a.\")[^>]*>.*?</(\".$a.\")>!isx\", \'aks_header_callback_preg\', $html);\r\n			}\r\n		}\r\n//		$html = preg_replace(\'/<!--([^\\[]).*?-->/s\', \'\', $html);\r\n		$html = preg_replace(\'/((?<!\\?>)\'.PHP_EOL.\')[\\s]+/m\', \'\\1\', $html);\r\n		$html = str_replace(\">\".PHP_EOL.\"<\", \'><\', $html);\r\n		$html = str_replace(PHP_EOL, \' \', $html);\r\n		$html = preg_replace(\'/[ \\t]+/\', \' \', $html);\r\n		foreach( array_reverse($aks_header_block,true) as $key=>$block ){ $html = str_replace($key,  $block, $html); }\r\n	}\r\n\r\n	// Check for buggy versions of Internet Explorer\r\n	if( $aks_header_gzip && !strstr($_SERVER[\'HTTP_USER_AGENT\'], \'Opera\') &&\r\n	preg_match(\'/^Mozilla\\/4\\.0 \\(compatible; MSIE ([0-9]\\.[0-9])/i\', $_SERVER[\'HTTP_USER_AGENT\'], $matches) ){\r\n		$version = floatval($matches[1]);\r\n		if( $version < 6 || ($version == 6 && !strstr($_SERVER[\'HTTP_USER_AGENT\'], \'SV1\')) ){ $aks_header_gzip=0; }\r\n	}\r\n\r\n	if( $aks_header_etag && $production_status == \'live\' ){\r\n		$etag = ( $aks_header_etag == 1 ) ? \'\"\'.md5($html).\'\"\' : \'\"\'.$aks_header_etag.\'\"\';\r\n		header(\"ETag: $etag\");\r\n		if( $match = serverSet(\'HTTP_IF_NONE_MATCH\') ){		// check etag:  304 Not Modified\r\n			if( in_array($etag, do_list($match)) ){		// TODO:   If-None-Match \"*\"\r\n				txp_status_header(\'304 Not Modified\');\r\n				return;\r\n			}\r\n		}\r\n	}\r\n\r\n	if( $aks_header_gzip && isset($_SERVER[\'HTTP_ACCEPT_ENCODING\']) ){\r\n		$encoding = $_SERVER[\'HTTP_ACCEPT_ENCODING\'];\r\n		if( function_exists(\'gzencode\') && preg_match(\'/gzip/i\', $encoding) ){\r\n			header(\"Vary: Accept-Encoding\");\r\n			header(\'Content-Encoding: gzip\');\r\n			$html = gzencode($html);\r\n		}elseif( function_exists(\'gzdeflate\') && preg_match(\'/deflate/i\', $encoding) ){\r\n			header(\"Vary: Accept-Encoding\");\r\n			header(\'Content-Encoding: deflate\');\r\n			$html = gzdeflate($html);\r\n		}\r\n	}\r\n//	$size=strlen($html);	if($size){ header(\'Content-Length: \' . $size); }\r\n	return $html;\r\n}\r\n\r\nfunction aks_header_etag($atts, $thing=\'\'){\r\n	global $aks_header_etag;\r\n	extract(lAtts(array(\r\n		\'hash\'	=> \'\'\r\n	),$atts));\r\n	if( $thing ){\r\n		$aks_header_etag = md5( $hash . $aks_header_etag . $thing );\r\n		return parse($thing);\r\n	}\r\n}\r\n\r\n\r\n\r\n/*	// for external plugins\r\n	if( function_exists(\'aks_filter_add\') ){\r\n		aks_filter_add(\'aks_text_filter\', 30);\r\n	}else{\r\n		trigger_error(\"Unable to include required plugin \'aks_header\'\", E_USER_ERROR);	// E_USER_WARNING\r\n	}\r\n*/\r\nfunction aks_filter_add($name, $priority=50){\r\n	global $aks_filter;\r\n	$aks_filter[\"{$priority}_{$name}\"] = $name;\r\n}\r\n\r\n\r\n\r\n\r\nfunction aks_301($atts) {\r\n	extract(lAtts(array(\r\n		\'start\'=>\'\',\r\n		\'url\'=>\'http://\'.$_SERVER[\"SERVER_NAME\"].\'/\',\r\n		\'ignore\'=>\'\',\r\n		\'black\'=>\'cltreq\\.asp|owssvr\\.dll\'\r\n	),$atts));\r\n\r\n	if( $ignore ){\r\n		$ignor=explode(\'|\',$ignore);\r\n		for( $i = 0; $i < count($ignor); $i++ ){\r\n			if( preg_match(\"|\".$ignor[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){	return; }\r\n		}\r\n	}\r\n\r\n	if( $black ){\r\n		$bl=explode(\'|\',$black);\r\n		for( $i = 0; $i < count($bl); $i++ ){\r\n			if( preg_match(\"|\".$bl[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){ header(\"HTTP/1.0 404 Not Found\"); exit; }\r\n		}\r\n	}\r\n\r\n	if( $start ){\r\n		$st=explode(\'|\',$start); $ur=explode(\'|\',$url);\r\n		for( $i = 0; $i < count($st); $i++ ){\r\n			if( preg_match(\"|^\".$st[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){\r\n				if( !$ur[$i] ){ $ur[$i] = $ur[0]; }\r\n				if( preg_match(\'/\\(/\', $st[$i]) ){         // check if regexp\r\n					$ur[$i] = preg_replace(\"|^\".$st[$i].\"|i\", $ur[$i], $_SERVER[\"REQUEST_URI\"]);\r\n				}\r\n				header(\'HTTP/1.1 301 Moved Permanently\');\r\n				header(\"Location: \".$ur[$i]); exit; \r\n			}\r\n		}\r\n	}\r\n}\r\n', 'if(@txpinterface == \'public\'){\r\n	if(!in_array(\'aks_header_callback\', ob_list_handlers() )){ ob_start(\'aks_header_callback\'); }\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n			->register(\'aks_301\')\r\n			->register(\'aks_header\')\r\n			->register(\'aks_header_etag\')\r\n		;\r\n	}\r\n}\r\n\r\nfunction aks_header($atts) {\r\n	global	$thisarticle, $aks_headers\r\n		, $aks_header_strip\r\n		, $aks_header_nostrip\r\n		, $aks_header_gzip\r\n		, $aks_filter\r\n		, $aks_header_etag;\r\n	$ext=array(				// extension => header\r\n		\'txt\'	=> \'text/plain\',\r\n		\'css\'	=> \'text/css\',\r\n		\'js\'	=> \'application/x-javascript\',\r\n		\'xml\'	=> \'application/rss+xml\'\r\n	);\r\n	extract(lAtts(array(\r\n		\'file\'		=> \'0\',\r\n		\'nodebug\'	=> \'0\',\r\n		\'nocache\'	=> \'0\',\r\n		\'name\'		=> \'Content-Type\',\r\n		\'value\'		=> \'\',\r\n		\'strip\'		=> \'0\',\r\n		\'gzip\'		=> \'0\',\r\n		\'filter\'	=> \'\',\r\n		\'etag\'		=> \'\',\r\n		\'nostrip\'	=> \'pre,code,textarea,script,style\'\r\n	),$atts));\r\n	$aks_header_nostrip = trim($nostrip);\r\n	$aks_header_etag = ($etag) ? 1 : 0;\r\n\r\n	if($filter==\'none\'){\r\n		$aks_filter = array();\r\n	}elseif($filter){\r\n		$aks_filter = array_merge($aks_filter, preg_split(\'/\\,/\', $filter) );\r\n	}\r\n\r\n	if($strip){ $aks_header_strip = 1; }\r\n	if($gzip && !in_array(\'zlib output compression\', ob_list_handlers()) ){ $aks_header_gzip = 1; }\r\n\r\n	if($value){ $aks_headers[$name] = $value; }\r\n	if($nodebug || $file){ $GLOBALS[\'production_status\'] = \'live\'; }\r\n\r\n	if($file && preg_match(\'/^.*\\.(.*)$/\', $thisarticle[\'url_title\'], $mm) ){\r\n		if($ext[$mm[1]]){ $aks_headers[\'Content-Type\'] = $ext[$mm[1]];}\r\n		if($mm[1] !=\'xml\'){ $aks_headers[\'Last-Modified\'] = safe_strftime(\'rfc822\',$thisarticle[\'modified\']); }\r\n	}\r\n	if($nocache){\r\n		$aks_headers[\"Cache-Control\"] = \"no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\";\r\n		$aks_headers[\"Expires\"] = \"Sat, 9 Jun 1990 07:00:00 GMT\";\r\n		$aks_headers[\'Last-Modified\'] = safe_strftime(\'rfc822\');\r\n		$aks_headers[\"Pragma\"] = \"no-cache\";\r\n	}\r\n\r\n}\r\n\r\nfunction aks_header_callback_preg($mm) {\r\n	static $c=0; $c+=1;\r\n	global $aks_header_block;\r\n	$key = \"@@DONTOUCHMETAG{$c}@@\";\r\n	$aks_header_block[$key] = $mm[0];\r\n	return $key;\r\n}\r\n\r\nfunction aks_header_callback($html) {\r\n	global	$aks_headers\r\n		, $aks_header_strip\r\n		, $aks_header_nostrip\r\n		, $aks_header_gzip\r\n		, $aks_header_block\r\n		, $aks_filter\r\n		, $aks_header_etag\r\n		, $production_status;\r\n\r\n	if( isset($aks_headers) && is_array($aks_headers) ){\r\n		foreach ($aks_headers as $name => $value){ header(\"$name: $value\"); }\r\n	}\r\n\r\n	if( is_array($aks_filter) ){\r\n		ksort($aks_filter);\r\n		foreach( array_unique($aks_filter) as $filter){\r\n			if( function_exists($filter) ){ $html = $filter($html); }\r\n		}\r\n	}\r\n\r\n	if( $production_status == \'live\' ){					// clear marks aks_	<!--aks_...--><!--/aks_...-->\r\n		$html = preg_replace(\'/<!--\\/?aks_.*?-->/i\', \'\', $html);\r\n	}\r\n\r\n	if( $aks_header_strip ){\r\n		$aks_header_block = array();\r\n		$html = preg_replace (\"/\\015\\012|\\015|\\012/\", PHP_EOL, $html);\r\n		if( $aks_header_nostrip ){\r\n			$aa = preg_split(\'/[, ]+/\', $aks_header_nostrip);\r\n			foreach($aa as $a){\r\n				$html = preg_replace_callback(\"!<(\".$a.\")[^>]*>.*?</(\".$a.\")>!isx\", \'aks_header_callback_preg\', $html);\r\n			}\r\n		}\r\n//		$html = preg_replace(\'/<!--([^\\[]).*?-->/s\', \'\', $html);\r\n		$html = preg_replace(\'/((?<!\\?>)\'.PHP_EOL.\')[\\s]+/m\', \'\\1\', $html);\r\n		$html = str_replace(\">\".PHP_EOL.\"<\", \'><\', $html);\r\n		$html = str_replace(PHP_EOL, \' \', $html);\r\n		$html = preg_replace(\'/[ \\t]+/\', \' \', $html);\r\n		foreach( array_reverse($aks_header_block,true) as $key=>$block ){ $html = str_replace($key,  $block, $html); }\r\n	}\r\n\r\n	// Check for buggy versions of Internet Explorer\r\n	if( $aks_header_gzip && !strstr($_SERVER[\'HTTP_USER_AGENT\'], \'Opera\') &&\r\n	preg_match(\'/^Mozilla\\/4\\.0 \\(compatible; MSIE ([0-9]\\.[0-9])/i\', $_SERVER[\'HTTP_USER_AGENT\'], $matches) ){\r\n		$version = floatval($matches[1]);\r\n		if( $version < 6 || ($version == 6 && !strstr($_SERVER[\'HTTP_USER_AGENT\'], \'SV1\')) ){ $aks_header_gzip=0; }\r\n	}\r\n\r\n	if( $aks_header_etag && $production_status == \'live\' ){\r\n		$etag = ( $aks_header_etag == 1 ) ? \'\"\'.md5($html).\'\"\' : \'\"\'.$aks_header_etag.\'\"\';\r\n		header(\"ETag: $etag\");\r\n		if( $match = serverSet(\'HTTP_IF_NONE_MATCH\') ){		// check etag:  304 Not Modified\r\n			if( in_array($etag, do_list($match)) ){		// TODO:   If-None-Match \"*\"\r\n				txp_status_header(\'304 Not Modified\');\r\n				return;\r\n			}\r\n		}\r\n	}\r\n\r\n	if( $aks_header_gzip && isset($_SERVER[\'HTTP_ACCEPT_ENCODING\']) ){\r\n		$encoding = $_SERVER[\'HTTP_ACCEPT_ENCODING\'];\r\n		if( function_exists(\'gzencode\') && preg_match(\'/gzip/i\', $encoding) ){\r\n			header(\"Vary: Accept-Encoding\");\r\n			header(\'Content-Encoding: gzip\');\r\n			$html = gzencode($html);\r\n		}elseif( function_exists(\'gzdeflate\') && preg_match(\'/deflate/i\', $encoding) ){\r\n			header(\"Vary: Accept-Encoding\");\r\n			header(\'Content-Encoding: deflate\');\r\n			$html = gzdeflate($html);\r\n		}\r\n	}\r\n//	$size=strlen($html);	if($size){ header(\'Content-Length: \' . $size); }\r\n	return $html;\r\n}\r\n\r\nfunction aks_header_etag($atts, $thing=\'\'){\r\n	global $aks_header_etag;\r\n	extract(lAtts(array(\r\n		\'hash\'	=> \'\'\r\n	),$atts));\r\n	if( $thing ){\r\n		$aks_header_etag = md5( $hash . $aks_header_etag . $thing );\r\n		return parse($thing);\r\n	}\r\n}\r\n\r\n\r\n\r\n/*	// for external plugins\r\n	if( function_exists(\'aks_filter_add\') ){\r\n		aks_filter_add(\'aks_text_filter\', 30);\r\n	}else{\r\n		trigger_error(\"Unable to include required plugin \'aks_header\'\", E_USER_ERROR);	// E_USER_WARNING\r\n	}\r\n*/\r\nfunction aks_filter_add($name, $priority=50){\r\n	global $aks_filter;\r\n	$aks_filter[\"{$priority}_{$name}\"] = $name;\r\n}\r\n\r\n\r\n\r\n\r\nfunction aks_301($atts) {\r\n	extract(lAtts(array(\r\n		\'start\'=>\'\',\r\n		\'url\'=>\'http://\'.$_SERVER[\"SERVER_NAME\"].\'/\',\r\n		\'ignore\'=>\'\',\r\n		\'black\'=>\'cltreq\\.asp|owssvr\\.dll\'\r\n	),$atts));\r\n\r\n	if( $ignore ){\r\n		$ignor=explode(\'|\',$ignore);\r\n		for( $i = 0; $i < count($ignor); $i++ ){\r\n			if( preg_match(\"|\".$ignor[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){	return; }\r\n		}\r\n	}\r\n\r\n	if( $black ){\r\n		$bl=explode(\'|\',$black);\r\n		for( $i = 0; $i < count($bl); $i++ ){\r\n			if( preg_match(\"|\".$bl[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){ header(\"HTTP/1.0 404 Not Found\"); exit; }\r\n		}\r\n	}\r\n\r\n	if( $start ){\r\n		$st=explode(\'|\',$start); $ur=explode(\'|\',$url);\r\n		for( $i = 0; $i < count($st); $i++ ){\r\n			if( preg_match(\"|^\".$st[$i].\"|i\", $_SERVER[\"REQUEST_URI\"]) ){\r\n				if( !$ur[$i] ){ $ur[$i] = $ur[0]; }\r\n				if( preg_match(\'/\\(/\', $st[$i]) ){         // check if regexp\r\n					$ur[$i] = preg_replace(\"|^\".$st[$i].\"|i\", $ur[$i], $_SERVER[\"REQUEST_URI\"]);\r\n				}\r\n				header(\'HTTP/1.1 301 Moved Permanently\');\r\n				header(\"Location: \".$ur[$i]); exit; \r\n			}\r\n		}\r\n	}\r\n}\r\n', '8fb99bb05dafeec0d39783dd5fdb9254', '', '', 0, 1, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('aks_meta', 0, 'makss', 'http://makss.uaho.net/plugins/aks_meta', '0.2.4a', 'Change metas after page rendering. Added mark...', '<div id=\"aks\">\n<h1>aks_meta &#8211; Change metas after page rendering</h1>\n\n	<p><a href=\"http://makss.uaho.net/plugins/aks_meta\">homepage</a> </p>\n\n<h2>Summary</h2>\n\n	<p>Set or change html page <code>&lt;title&gt;..</code> , <code>&lt;meta name=\"keywords\"...</code> , <code>&lt;meta name=\"description\"...</code><br>\non runtime inside any tags or including forms.</p>\n\n	<p>This will avoid a large(<span class=\"caps\">MONSTER</span>) code in the </p>\n\n<pre>&lt;head&gt;\n&lt;txp:if_section...  &lt;title&gt;&lt;txp:title...   &lt;meta name=\"keywords\"...\n&lt;txp:if_category...    &lt;meta name=\"keywords\"...\n&lt;txp:if_individual_article...  &lt;title&gt;&lt;txp:title...\n&lt;txp:if_custom_field...    &lt;title&gt;&lt;txp:custom_field...\nother if...\n&lt;/head&gt;\n</pre>\n\n	<p><strong>Example:</strong></p>\n\n<pre>&lt;html&gt;&lt;head&gt;\n&lt;txp:aks_meta_title&gt;My default site title&lt;/txp:aks_meta_title&gt;\n&lt;txp:aks_meta_keywords&gt;key1,key2&lt;/txp:aks_meta_keywords&gt;\n&lt;/head&gt;&lt;body&gt;\n&lt;txp:if_....&gt;&lt;txp:output_form form=\"form1\" /&gt;\n...\n...\n&lt;txp:aks_meta_title add=\"last\" value=\"with love :)\" /&gt;\n&lt;/body&gt;&lt;/html&gt;\n</pre>\n\n<pre>&lt;!-- form1 --&gt;\n&lt;txp:aks_meta_title add=\"first\" value=\'&lt;txp:title /&gt;\' /&gt;\n&lt;txp:if_....&gt;&lt;txp:output_form form=\"render_data_from_external_store\" /&gt;\n</pre>\n\n<pre>&lt;!-- render_data_from_external_store --&gt;\nrendering data...\n&lt;txp:aks_meta_title add=\"first\"&gt;TitleFromExtStore&lt;/txp:aks_meta_title&gt;\n&lt;txp:aks_meta_keywords add=\"last\"&gt;KeysFromExtStore&lt;/txp:aks_meta_keywords&gt;\n&lt;txp:aks_meta_description&gt;DescriptionFromExtStore&lt;/txp:aks_meta_description&gt;\n</pre>\n\n	<p><strong>Result in browser:</strong></p>\n\n<pre> &lt;html&gt;&lt;head&gt;\n&lt;title&gt;TitleFromExtStore :: form1 title :: My default site title :: with love :)&lt;/title&gt;\n&lt;meta name=\"keywords\" content=\"key1,key2,KeysFromExtStore\" /&gt;\n&lt;meta name=\"description\" content=\"DescriptionFromExtStore\" /&gt;\n...skip...\n</pre>\n\n	<p><strong>Remark:</strong><br>\nPlugin store self data as ordinary txp:variable and you may use tags<br>\n<code>&lt;txp:if_variable name=\"aks_meta_title\"&gt;...</code> or <code>&lt;txp:variable /&gt;</code></p>\n\n	<h2>Changelog</h2>\n\n	<p>v0.2.2 (beta)</p>\n\n	<ul>\n		<li>Added tag <code>aks_meta_head</code></li>\n		<li>Added tag <code>aks_meta_rel</code></li>\n	</ul>\n\n	<h2>Tags</h2>\n\n	<p><code>&lt;txp:aks_meta_title /&gt;</code> is a single or a container tag</p>\n\n	<table>\n		<tbody><tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>add</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'first\'</code> or <code>\'last\'</code></td>\n			<td>Add or overwrite exist value</td>\n		</tr>\n		<tr>\n			<td>separator</td>\n			<td><code>\' :: \'</code></td>\n			<td><code>\' any_text \'</code></td>\n			<td>Uses with <code>add</code> attribute</td>\n		</tr>\n		<tr>\n			<td>value</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'my new value\'</code></td>\n			<td>Value or get value from container</td>\n		</tr>\n		<tr>\n			<td>strip</td>\n			<td><code>1</code></td>\n			<td><code>0</code></td>\n			<td>Strip html tags</td>\n		</tr>\n	</tbody></table>\n\n	<p>.</p>\n\n	<p><code>&lt;txp:aks_meta_keywords /&gt;</code> is a single or a container tag</p>\n\n	<table>\n		<tbody><tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>add</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'first\'</code> or <code>\'last\'</code></td>\n			<td>Add or overwrite exist value</td>\n		</tr>\n		<tr>\n			<td>separator</td>\n			<td><code>\',\'</code></td>\n			<td><code>\', \'</code></td>\n			<td>Uses with <code>add</code> attribute</td>\n		</tr>\n		<tr>\n			<td>value</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'my new value\'</code></td>\n			<td>Value or get value from container</td>\n		</tr>\n		<tr>\n			<td>strip</td>\n			<td><code>1</code></td>\n			<td><code>0</code></td>\n			<td>Strip html tags</td>\n		</tr>\n	</tbody></table>\n\n	<p>.</p>\n\n	<p><code>&lt;txp:aks_meta_description /&gt;</code> is a single or a container tag</p>\n\n	<table>\n		<tbody><tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>add</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'first\'</code> or <code>\'last\'</code></td>\n			<td>Add or overwrite exist value</td>\n		</tr>\n		<tr>\n			<td>separator</td>\n			<td><code>\' \'</code></td>\n			<td><code>\' any_text \'</code></td>\n			<td>Uses with <code>add</code> attribute</td>\n		</tr>\n		<tr>\n			<td>value</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'my new value\'</code></td>\n			<td>Value or get value from container</td>\n		</tr>\n		<tr>\n			<td>strip</td>\n			<td><code>1</code></td>\n			<td><code>0</code></td>\n			<td>Strip html tags</td>\n		</tr>\n	</tbody></table>\n\n	<p>.</p>\n\n	<p><code>&lt;txp:aks_meta_head /&gt;</code> is a single or a container tag – Allows you to insert any text/css/js  in html_head section. For sample:</p>\n\n<pre>&lt;meta name=“robots” content=“noindex,follow” /&gt;\n</pre>\n\n	<table>\n		<tbody><tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'some\'</code></td>\n			<td>Optional,  used only for overwrite previous definition</td>\n		</tr>\n		<tr>\n			<td>value</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'&lt;!-- my text in html_head section --&gt;\'</code></td>\n			<td>Text</td>\n		</tr>\n	</tbody></table>\n\n	<p>.</p>\n\n	<p><code>&lt;txp:aks_meta_rel /&gt;</code> is a single or a container tag – Allows you to insert <code>&lt;link rel=.../&gt;</code>  in html_head section. For sample:</p>\n\n<pre>&lt;link rel=\"prev\" title=\"my prev article\" href=\"http://...\" /&gt;\n&lt;link rel=\"next\" title=\"my next article\" href=\"http://...\" /&gt;\n&lt;link rel=\"canonical\" href=\"http://...\" /&gt;\n</pre>\n\n	<table>\n		<tbody><tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'next\'</code></td>\n			<td> <code>&lt;link rel=\'name\' ... /&gt;</code> </td>\n		</tr>\n		<tr>\n			<td>title</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'next article\'</code></td>\n			<td>Optional</td>\n		</tr>\n		<tr>\n			<td>url</td>\n			<td><code>\'\'</code></td>\n			<td><code>\'http://...\'</code></td>\n			<td><span class=\"caps\">URL</span></td>\n		</tr>\n	</tbody></table>\n\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n	background-color: #E3E3DB;\n	border:1px solid;\n	border-color:#ddd #999 #888 #ddd;\n	padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks code { background-color: #F2F2ED; font-weight: bold; }\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #CCCCCC;\nmargin: 1.5em 0;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'if(@txpinterface == \'public\'){\r\n	if( function_exists(\'aks_filter_add\') ){\r\n		aks_filter_add(\'aks_meta__filter\', 20);\r\n	}else{\r\n//		trigger_error(\"Unable to include required plugin \'aks_header\' v0.4+\", E_USER_ERROR);	// E_USER_WARNING\r\n		register_callback(\'aks_meta__callback\',\'textpattern\');	// standalone...\r\n	}\r\n\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n			->register(\'aks_meta_head\')\r\n			->register(\'aks_meta_rel\')\r\n			->register(\'aks_meta\')\r\n			->register(\'aks_meta_title\')\r\n			->register(\'aks_meta_keywords\')\r\n			->register(\'aks_meta_description\')\r\n			->register(\'aks_meta_words\')\r\n		;\r\n	}\r\n}\r\n\r\nfunction aks_meta_head($atts, $this = NULL){\r\n	global $variable;\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'value\'		=> \'\'\r\n	),$atts));\r\n	if(!$value && $this){ $value = parse($this); }\r\n	if( !$value ){ return; }\r\n	if( $name ){\r\n		$variable[\"aks_meta_head\"][$name] = $value;\r\n	}else{\r\n		$variable[\"aks_meta_head\"][] = $value;\r\n	}\r\n}\r\n\r\nfunction aks_meta_rel($atts, $this = NULL){\r\n	global $variable;\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'title\'		=> \'\',\r\n		\'url\'		=> \'\'\r\n	),$atts));\r\n	if(!$url && $this){ $url = parse($this); }\r\n	if( !$url || !$name ){ return; }\r\n	if($title){\r\n		$title = trim(preg_replace(\'%\\s+%su\', \' \', strip_tags($title) ));\r\n		$title = trim(str_replace( array(\'\"\',\'>\',\'<\'), array(\'&quot;\',\'&gt;\',\'&lt;\'), $title));\r\n		$title = \"title=\\\"{$title}\\\"\";\r\n	}else{\r\n		$title = \"\";\r\n	}\r\n	$txt = \"<link rel=\\\"{$name}\\\" {$title} href=\\\"\".trim($url).\"\\\" />\";\r\n	$variable[\"aks_meta_head\"][\"aks_rel_$name\"] = $txt;\r\n}\r\n\r\n\r\nfunction aks_meta($atts, $this = NULL){\r\n	if( $mark=@$atts[\'mark\'] ){ return \"<!--aks_mark_{$mark}-->\"; }\r\n	if( !($name=@$atts[\'name\']) ){ return; }\r\n	static $s;	if( is_null($s) ){ $s = array();	$s[\'title\']=\" :: \";	$s[\'keywords\']=\",\";	$s[\'description\']=\" \"; }\r\n\r\n	if( isset($atts[\'separator\']) ){ $s[$name]=$atts[\'separator\']; }\r\n	$def_strip=preg_match(\'/^(title|keywords|description)$/i\', $name);\r\n\r\n	extract(lAtts(array(\r\n		\'name\' => \'\',\r\n		\'add\'  => \'\',			// \'first\' or \'last\'\r\n		\'separator\' => @$s[$name],	// used with `add` attribute\r\n		\'value\'  => \'\',\r\n		\'strip\'  => $def_strip\r\n	),$atts));\r\n\r\n	global $variable;\r\n	if(!$value && $this){ $value=parse($this); }\r\n\r\n	if($strip){\r\n		$value=trim(preg_replace(\'%\\s+%su\', \' \', strip_tags($value) ));\r\n		$value=str_replace( array(\'\"\',\'>\',\'<\'), array(\'&quot;\',\'&gt;\',\'&lt;\'), $value);\r\n	}\r\n\r\n	if($add){\r\n		if($add==\'first\'  && $value){ @$variable[\"aks_meta_$name\"]=$value.$separator.@$variable[\"aks_meta_$name\"]; }\r\n		if($add==\'last\'   && $value){ @$variable[\"aks_meta_$name\"].=$separator.$value; }\r\n	}else{\r\n		$variable[\"aks_meta_$name\"]=$value;\r\n	}\r\n}\r\n\r\n// short tags\r\nfunction aks_meta_title($atts, $this = NULL){		$atts[\"name\"]=\"title\";		aks_meta($atts, $this); }\r\nfunction aks_meta_keywords($atts, $this = NULL){	$atts[\"name\"]=\"keywords\";	aks_meta($atts, $this); }\r\nfunction aks_meta_description($atts, $this = NULL){	$atts[\"name\"]=\"description\";	aks_meta($atts, $this); }\r\n\r\n\r\n\r\n\r\nfunction aks_meta__callback(){\r\n	if(!in_array(\'aks_meta__filter\', ob_list_handlers() )){ ob_start(\'aks_meta__filter\'); }\r\n}\r\n\r\n\r\nfunction aks_meta__filter($html){\r\n	global $variable;\r\n	$meta=\"\";\r\n	if(@$variable[\'aks_meta_title\']){\r\n		$html=preg_replace(\'%([ \\t]+)?<title[^>]*>.*?</title[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\"<title>{$variable[\'aks_meta_title\']}</title>\\n\";\r\n	}\r\n	if(@$variable[\'aks_meta_keywords\']){\r\n		$keys=preg_replace(\'%[ \\.,]?,[ \\.,]?%\', \',\', $variable[\'aks_meta_keywords\']);\r\n		$keys=aks_meta_words($keys, 30);\r\n		$html=preg_replace(\'%([ \\t]+)?<meta [^>]*name[ =\\\"\\\']*keywords[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\'<meta name=\"keywords\" content=\"\'.$keys.\'\" />\'.n;\r\n	}\r\n	if(@$variable[\'aks_meta_description\']){\r\n		$html=preg_replace(\'%([ \\t]+)?<meta [^>]*name[ =\\\"\\\']*description[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\'<meta name=\"description\" content=\"\'.$variable[\'aks_meta_description\'].\'\" />\'.n;\r\n	}\r\n	if(@is_array($variable[\'aks_meta_head\']) ){\r\n		$meta.=join(\"\", $variable[\'aks_meta_head\']);\r\n	}\r\n	if($meta){\r\n//		$html=preg_replace(\'%</head>%is\', \"$meta</head>\", $html);		// insert before </head>\r\n//		$html=preg_replace(\'%(<head[^>]*>)%is\', \"$1\\n$meta\", $html);		// insert after <head>\r\n		$html=str_replace(\"<head>\", \"<head>$meta\", $html);\r\n	}\r\n	$html=preg_replace_callback(\'/<!--aks_mark_(.*?)-->/xsi\', \"_aks_mark\", $html);\r\n	return $html;\r\n}\r\n\r\nfunction _aks_mark($mm){\r\n	global $variable;\r\n	if( isset( $variable[\"aks_meta_$mm[1]\"] ) ){ return $variable[\"aks_meta_$mm[1]\"]; }\r\n}\r\n\r\nfunction aks_meta_words($txt=\'\', $words=50) {\r\n	if($words){\r\n//		$word = split(\" \", $value);\r\n//		if(count($word) >$words){\r\n		$txt=implode(\",\", array_slice(preg_split(\"%[,]+%\", $txt), 0, $words));\r\n	}\r\n	return $txt;\r\n}\r\n', 'if(@txpinterface == \'public\'){\r\n	if( function_exists(\'aks_filter_add\') ){\r\n		aks_filter_add(\'aks_meta__filter\', 20);\r\n	}else{\r\n//		trigger_error(\"Unable to include required plugin \'aks_header\' v0.4+\", E_USER_ERROR);	// E_USER_WARNING\r\n		register_callback(\'aks_meta__callback\',\'textpattern\');	// standalone...\r\n	}\r\n\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n			->register(\'aks_meta_head\')\r\n			->register(\'aks_meta_rel\')\r\n			->register(\'aks_meta\')\r\n			->register(\'aks_meta_title\')\r\n			->register(\'aks_meta_keywords\')\r\n			->register(\'aks_meta_description\')\r\n			->register(\'aks_meta_words\')\r\n		;\r\n	}\r\n}\r\n\r\nfunction aks_meta_head($atts, $this = NULL){\r\n	global $variable;\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'value\'		=> \'\'\r\n	),$atts));\r\n	if(!$value && $this){ $value = parse($this); }\r\n	if( !$value ){ return; }\r\n	if( $name ){\r\n		$variable[\"aks_meta_head\"][$name] = $value;\r\n	}else{\r\n		$variable[\"aks_meta_head\"][] = $value;\r\n	}\r\n}\r\n\r\nfunction aks_meta_rel($atts, $this = NULL){\r\n	global $variable;\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'title\'		=> \'\',\r\n		\'url\'		=> \'\'\r\n	),$atts));\r\n	if(!$url && $this){ $url = parse($this); }\r\n	if( !$url || !$name ){ return; }\r\n	if($title){\r\n		$title = trim(preg_replace(\'%\\s+%su\', \' \', strip_tags($title) ));\r\n		$title = trim(str_replace( array(\'\"\',\'>\',\'<\'), array(\'&quot;\',\'&gt;\',\'&lt;\'), $title));\r\n		$title = \"title=\\\"{$title}\\\"\";\r\n	}else{\r\n		$title = \"\";\r\n	}\r\n	$txt = \"<link rel=\\\"{$name}\\\" {$title} href=\\\"\".trim($url).\"\\\" />\";\r\n	$variable[\"aks_meta_head\"][\"aks_rel_$name\"] = $txt;\r\n}\r\n\r\n\r\nfunction aks_meta($atts, $this = NULL){\r\n	if( $mark=@$atts[\'mark\'] ){ return \"<!--aks_mark_{$mark}-->\"; }\r\n	if( !($name=@$atts[\'name\']) ){ return; }\r\n	static $s;	if( is_null($s) ){ $s = array();	$s[\'title\']=\" :: \";	$s[\'keywords\']=\",\";	$s[\'description\']=\" \"; }\r\n\r\n	if( isset($atts[\'separator\']) ){ $s[$name]=$atts[\'separator\']; }\r\n	$def_strip=preg_match(\'/^(title|keywords|description)$/i\', $name);\r\n\r\n	extract(lAtts(array(\r\n		\'name\' => \'\',\r\n		\'add\'  => \'\',			// \'first\' or \'last\'\r\n		\'separator\' => @$s[$name],	// used with `add` attribute\r\n		\'value\'  => \'\',\r\n		\'strip\'  => $def_strip\r\n	),$atts));\r\n\r\n	global $variable;\r\n	if(!$value && $this){ $value=parse($this); }\r\n\r\n	if($strip){\r\n		$value=trim(preg_replace(\'%\\s+%su\', \' \', strip_tags($value) ));\r\n		$value=str_replace( array(\'\"\',\'>\',\'<\'), array(\'&quot;\',\'&gt;\',\'&lt;\'), $value);\r\n	}\r\n\r\n	if($add){\r\n		if($add==\'first\'  && $value){ @$variable[\"aks_meta_$name\"]=$value.$separator.@$variable[\"aks_meta_$name\"]; }\r\n		if($add==\'last\'   && $value){ @$variable[\"aks_meta_$name\"].=$separator.$value; }\r\n	}else{\r\n		$variable[\"aks_meta_$name\"]=$value;\r\n	}\r\n}\r\n\r\n// short tags\r\nfunction aks_meta_title($atts, $this = NULL){		$atts[\"name\"]=\"title\";		aks_meta($atts, $this); }\r\nfunction aks_meta_keywords($atts, $this = NULL){	$atts[\"name\"]=\"keywords\";	aks_meta($atts, $this); }\r\nfunction aks_meta_description($atts, $this = NULL){	$atts[\"name\"]=\"description\";	aks_meta($atts, $this); }\r\n\r\n\r\n\r\n\r\nfunction aks_meta__callback(){\r\n	if(!in_array(\'aks_meta__filter\', ob_list_handlers() )){ ob_start(\'aks_meta__filter\'); }\r\n}\r\n\r\n\r\nfunction aks_meta__filter($html){\r\n	global $variable;\r\n	$meta=\"\";\r\n	if(@$variable[\'aks_meta_title\']){\r\n		$html=preg_replace(\'%([ \\t]+)?<title[^>]*>.*?</title[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\"<title>{$variable[\'aks_meta_title\']}</title>\\n\";\r\n	}\r\n	if(@$variable[\'aks_meta_keywords\']){\r\n		$keys=preg_replace(\'%[ \\.,]?,[ \\.,]?%\', \',\', $variable[\'aks_meta_keywords\']);\r\n		$keys=aks_meta_words($keys, 30);\r\n		$html=preg_replace(\'%([ \\t]+)?<meta [^>]*name[ =\\\"\\\']*keywords[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\'<meta name=\"keywords\" content=\"\'.$keys.\'\" />\'.n;\r\n	}\r\n	if(@$variable[\'aks_meta_description\']){\r\n		$html=preg_replace(\'%([ \\t]+)?<meta [^>]*name[ =\\\"\\\']*description[^>]*>([\\r\\n]+)?%is\', \'\', $html);\r\n		$meta.=\'<meta name=\"description\" content=\"\'.$variable[\'aks_meta_description\'].\'\" />\'.n;\r\n	}\r\n	if(@is_array($variable[\'aks_meta_head\']) ){\r\n		$meta.=join(\"\", $variable[\'aks_meta_head\']);\r\n	}\r\n	if($meta){\r\n//		$html=preg_replace(\'%</head>%is\', \"$meta</head>\", $html);		// insert before </head>\r\n//		$html=preg_replace(\'%(<head[^>]*>)%is\', \"$1\\n$meta\", $html);		// insert after <head>\r\n		$html=str_replace(\"<head>\", \"<head>$meta\", $html);\r\n	}\r\n	$html=preg_replace_callback(\'/<!--aks_mark_(.*?)-->/xsi\', \"_aks_mark\", $html);\r\n	return $html;\r\n}\r\n\r\nfunction _aks_mark($mm){\r\n	global $variable;\r\n	if( isset( $variable[\"aks_meta_$mm[1]\"] ) ){ return $variable[\"aks_meta_$mm[1]\"]; }\r\n}\r\n\r\nfunction aks_meta_words($txt=\'\', $words=50) {\r\n	if($words){\r\n//		$word = split(\" \", $value);\r\n//		if(count($word) >$words){\r\n		$txt=implode(\",\", array_slice(preg_split(\"%[,]+%\", $txt), 0, $words));\r\n	}\r\n	return $txt;\r\n}\r\n', '274d9d7980d0480b0f2abd268788a9d7', '', '', 0, 5, 0),
('aks_table', 0, 'makss', 'http://makss.uaho.net/plugins/aks_table', '0.3', 'Insert a table in TxP article is very simple!', '<h1>Simple tables in TxP (Ctrl+C, Ctrl+V)</h1>\n\n	<p><a href=\"http://makss.uaho.net/file/aks_table.txt\">download link</a> / <a href=\"http://makss.uaho.net/plugins/aks_table\">homepage</a></p>\n\n	<h2>Summary</h2>\n\n	<p>Insert a table in TxP article is very simple! Select in Excel, IE, Mozilla, FireFox, Opera any spreadsheet, Ctrl + C (copy) and Ctrl + V (paste) in TxP Edit area. Add aks_table tags &#8211; that&#8217;s all.</p>\n\n	<p><strong>Note:</strong> plugin support only simple table. (rowspan and colspan &#8211; unsupported).</p>\n\n	<h2>Tags</h2>\n\n	<p><code>&lt;txp:aks_table&gt;...content...&lt;/txp:aks_table&gt;</code></p>\n\n	<table border=\"1\" width=\"90%\" cellspacing=\"0\" cellpadding=\"10\">\n		<tr>\n			<th>attributes</th>\n			<th>default</th>\n			<th>sample</th>\n			<th>description</th>\n\n		</tr>\n		<tr>\n			<td>h</td>\n			<td>&#8216;&#8217;</td>\n			<td>1</td>\n			<td>first line &#8211; table header</td>\n\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS id</td>\n		</tr>\n		<tr>\n\n			<td>class</td>\n			<td>&#8216;&#8217;</td>\n			<td>&#8216;&#8217;</td>\n			<td>(X)HTML/CSS class</td>\n		</tr>\n	</table>\n\n<br /><br /><h2><a href=\"http://makss.uaho.net/plugins/aks_table\">Example</a></h2>', 'if(@txpinterface == \'admin\'){\r\n	register_callback(\'aks_table_save\', \'article\', \'\', 1);\r\n}\r\n\r\nfunction aks_table_save($event, $step){\r\n  if(gps(\'save\') || gps(\'publish\')){\r\n	if($_POST[\'textile_body\']==1){ $_POST[\'Body\']=preg_replace(\'/(^|[\\r\\n]+)<txp:aks_table/is\',\"$1 <txp:aks_table\",$_POST[\'Body\']); }\r\n	if($_POST[\'textile_excerpt\']==1){ $_POST[\'Excerpt\']=preg_replace(\'/(^|[\\r\\n]+)<txp:aks_table/is\',\"$1 <txp:aks_table\",$_POST[\'Excerpt\']); }\r\n  }\r\n}\r\n\r\nfunction aks_table ($atts, $thing=\'\'){\r\n  extract(lAtts(array( \'h\'  => \'\', \'class\'=>\'\', \'id\'=>\'\', \'a\'=>\'\'),$atts));\r\n\r\n  if($h){ $h=1;}else{$h=0;}\r\n  $pattern=array(\'/( +)?\\t( +)?/i\',\'/^/i\',\'/$/i\');\r\n  $replace[0]=array(\'</td><td>\',\'<tr><td>\',\'</td></tr>\');\r\n  $replace[1]=array(\'</th><th>\',\'<tr><th>\',\'</th></tr>\');\r\n\r\n  $lines=explode(\"\\n\",$thing);\r\n  for ($i = 0; $i < count($lines); $i++) {\r\n    $lines[$i]=preg_replace(\'/<br.*?>/i\',\'\',$lines[$i]);\r\n    $lines[$i]=preg_replace(\'/[ ]+$/i\',\'\',$lines[$i]);\r\n    if($lines[$i]){ $lines[$i]=preg_replace($pattern,$replace[$h],$lines[$i]); $h=0; }\r\n  }\r\n  $out=join(\"\",$lines);\r\n  return doTag($out, \"table\", $class, \"\", $id);\r\n}', 'if(@txpinterface == \'admin\'){\r\n	register_callback(\'aks_table_save\', \'article\', \'\', 1);\r\n}\r\n\r\nfunction aks_table_save($event, $step){\r\n  if(gps(\'save\') || gps(\'publish\')){\r\n	if($_POST[\'textile_body\']==1){ $_POST[\'Body\']=preg_replace(\'/(^|[\\r\\n]+)<txp:aks_table/is\',\"$1 <txp:aks_table\",$_POST[\'Body\']); }\r\n	if($_POST[\'textile_excerpt\']==1){ $_POST[\'Excerpt\']=preg_replace(\'/(^|[\\r\\n]+)<txp:aks_table/is\',\"$1 <txp:aks_table\",$_POST[\'Excerpt\']); }\r\n  }\r\n}\r\n\r\nfunction aks_table ($atts, $thing=\'\'){\r\n  extract(lAtts(array( \'h\'  => \'\', \'class\'=>\'\', \'id\'=>\'\', \'a\'=>\'\'),$atts));\r\n\r\n  if($h){ $h=1;}else{$h=0;}\r\n  $pattern=array(\'/( +)?\\t( +)?/i\',\'/^/i\',\'/$/i\');\r\n  $replace[0]=array(\'</td><td>\',\'<tr><td>\',\'</td></tr>\');\r\n  $replace[1]=array(\'</th><th>\',\'<tr><th>\',\'</th></tr>\');\r\n\r\n  $lines=explode(\"\\n\",$thing);\r\n  for ($i = 0; $i < count($lines); $i++) {\r\n    $lines[$i]=preg_replace(\'/<br.*?>/i\',\'\',$lines[$i]);\r\n    $lines[$i]=preg_replace(\'/[ ]+$/i\',\'\',$lines[$i]);\r\n    if($lines[$i]){ $lines[$i]=preg_replace($pattern,$replace[$h],$lines[$i]); $h=0; }\r\n  }\r\n  $out=join(\"\",$lines);\r\n  return doTag($out, \"table\", $class, \"\", $id);\r\n}', '07df3a28fa2b0966ea5792b67fd09b31', '', '', 1, 5, 0),
('aks_var', 0, 'makss', 'http://makss.uaho.net/plugins/aks_var', '0.3a', 'Easy variables and calculations TxP', '<div id=\"aks\">\n<h1>aks_var – Easy variables and calculations TxP</h1>\n\n	<p><a href=\"http://makss.uaho.net/file/aks_var.txt\">download link</a> / <a href=\"http://makss.uaho.net/plugins/aks_var\">homepage</a></p>\n\n	<h2>Summary</h2>\n	<p>aks_var operate with Txp variables and Custom Fields.</p>\n\n	<h2>Tags</h2>\n	<p><strong>&#60;txp:aks_var /&#62;</strong>	or <strong>&#60;txp:aks_var&#62;some&#8230;&#60;/txp:aks_var&#62;</strong><br />\n<strong>&#60;txp:aks_evar /&#62;</strong>	same as &#60;txp:aks_var /&#62;, but it display result.  (evar &#8211; <strong>E</strong> cho <strong><span class=\"caps\">VAR</span></strong>)</p>\n\n	<h2>Attributes</h2>\n	<table>\n		<tr>\n			<th>Attribute</th>\n			<th>Sample</th>\n			<th>Description</th>\n\n		</tr>\n		<tr>\n			<td>// &#8212;&#8212; input $value</td>\n		</tr>\n		<tr>\n			<td><strong>name</strong></td>\n			<td><code>\'my_var\'</code></td>\n\n			<td><span class=\"caps\"><span class=\"caps\">TXP</span></span> variable</td>\n		</tr>\n		<tr>\n			<td><strong>customfield</strong></td>\n			<td><code>\'my_cust_field\'</code></td>\n			<td>Custom field</td>\n\n		</tr>\n		<tr>\n			<td><strong>value</strong></td>\n			<td><code>\'12345 qwerty\'</code></td>\n			<td>Init value, text or digits</td>\n		</tr>\n		<tr>\n\n			<td><strong>avar</strong></td>\n			<td><code>\'SERVER_NAME\'</code></td>\n			<td>apache variables, same as $_SERVER[$avar]</td>\n		</tr>\n		<tr>\n			<td><strong>tvar</strong></td>\n\n			<td><code>\'url_title\'</code></td>\n\n			<td>thisarticle variables, same as $<span class=\"caps\"><span class=\"caps\">GLOBALS</span></span>[&#8216;thisarticle&#8217;][$tvar]</td>\n		</tr>\n		<tr>\n			<td><strong>var</strong></td>\n\n			<td><code>\'$GLOBALS[\"prefs\"][\"spam_blacklists\"]\'</code></td>\n\n			<td><span class=\"caps\"><span class=\"caps\">ANY</span></span> variables</td>\n		</tr>\n		<tr>\n			<td>// &#8212;&#8212; some calc</td>\n\n		</tr>\n		<tr>\n\n			<td><strong>calc</strong></td>\n			<td><code>\'*5-(32-2)/4\'</code></td>\n			<td>Any arithmetic operations   + &#8211; * /  &#8230;</td>\n		</tr>\n\n		<tr>\n			<td><strong>substr</strong></td>\n\n			<td><code>\'2:7\'</code></td>\n			<td>Same as <span class=\"caps\"><span class=\"caps\">PHP</span></span> substr.  <strong><span class=\"caps\"><span class=\"caps\">WARNING</span></span></strong> &#8211; NO <span class=\"caps\"><span class=\"caps\">UTF</span></span>-8 support</td>\n\n		</tr>\n\n		<tr>\n			<td><strong>regexp</strong></td>\n			<td><code>\'/^(some text)/i\'</code></td>\n			<td>preg_replace($regexp, $to, <em>$value</em>)</td>\n		</tr>\n\n		<tr>\n			<td><strong>to</strong></td>\n			<td><code>\"It\'s $1\"</code></td>\n			<td>&nbsp;</td>\n		</tr>\n\n		<tr>\n			<td><strong>split</strong></td>\n\n			<td><code>\"2\"</code></td>\n			<td>Return part of spliting string</td>\n		</tr>\n		<tr>\n			<td><strong>delim</strong></td>\n			<td><code>\"##\"</code></td>\n			<td>delim for split</td>\n\n		</tr>\n\n\n\n		<tr>\n			<td>// &#8212;&#8212; output $value</td>\n		</tr>\n\n		<tr>\n\n			<td><strong>setname</strong></td>\n			<td><code>\'my_var2\'</code></td>\n			<td>result to this <span class=\"caps\"><span class=\"caps\">TXP</span></span> variable</td>\n		</tr>\n	</table>\n\n	<p>.</p>\n\n	<h2>Examples dump:  :)</h2>\n\n	<p>.<br />\n<strong>Variables</strong></p>\n\n	<table>\n\n		<tr>\n			<th>Example</th>\n			<th>Result</th>\n			<th>Comments</th>\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_var name=\"my_var\" value=\"123\" /&#62;</code></td>\n\n			<td>display none</td>\n			<td>set <span class=\"caps\"><span class=\"caps\">TXP</span></span> variable  <strong>my_var</strong> =123. It&#8217;s equal <strong>&#60;txp:variable name=&#8220;my_var&#8221; value=&#8220;123&#8221; /&#62;</strong> tag.</td>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" /&#62;</code></td>\n			<td>123</td>\n			<td>only display <strong>my_var</strong>. It&#8217;s equal as <strong>&#60;txp:variable name=&#8220;my_var&#8221; /&#62;</strong> tag</td>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" setname=\"other_var\" /&#62;</code></td>\n			<td>123</td>\n			<td><strong>other_var</strong> =my_var</td>\n		</tr>\n\n	</table>\n\n	<p>.<br />\n<strong>Custom Fields</strong></p>\n\n	<table>\n		<tr>\n			<th>Example</th>\n\n			<th>Comments</th>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar customfield=\"my_cust_field\" /&#62;</code></td>\n			<td>It&#8217;s equal as <strong>&#60;txp:custom_field name=&#8220;my_cust_field&#8221; /&#62;</strong></td>\n\n		</tr>\n\n		<tr>\n			<td><code>&#60;txp:aks_evar customfield=\"my_cust_field\" setname=\"my_var\" /&#62;</code></td>\n			<td><strong>my_var</strong> = my_cust_field    //get custom field to TxP variable</td>\n		</tr>\n		<tr>\n\n			<td><code>&#60;txp:aks_evar customfield=\"my_cust_field\" substr=\"0:5\" setname=\"my_var\" /&#62;</code></td>\n\n			<td><strong>my_var</strong> = substr(my_cust_field,0,5)</td>\n		</tr>\n	</table>\n\n	<p>.<br />\n\n<strong><span class=\"caps\">CALC</span></strong></p>\n\n	<table>\n\n		<tr>\n			<th>Example</th>\n			<th>Result</th>\n			<th>Comments</th>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" value=\"14\" calc=\"/2\" /&#62;</code></td>\n\n			<td>7</td>\n			<td><strong>my_var</strong> =14/2</td>\n		</tr>\n\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" calc=\"+1\" /&#62;</code></td>\n			<td>8</td>\n\n			<td><strong>my_var</strong> =my_var+1</td>\n		</tr>\n		<tr>\n\n			<td><code>&#60;txp:aks_evar name=\"my_var\" calc=\"*2+(24-4)/2\" setname=\"other_var\" /&#62;</code></td>\n			<td>26</td>\n			<td><strong>other_var</strong> =my_var*2+(24-4)/2   // my_var &#8211; no change</td>\n\n		</tr>\n	</table>\n\n	<p>.<br /><strong><span class=\"caps\">SUBSTR</span></strong></p>\n	<table>\n		<tr>\n			<th>Example</th>\n			<th>Result</th>\n			<th>Comments</th>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" value=\"123456789\" substr=\"3\" /&#62;</code></td>\n			<td>456789</td>\n			<td><strong>my_var</strong> =substr(&#8216;123456789&#8217;,3)     // <strong><span class=\"caps\"><span class=\"caps\">WARNING</span></span></strong> &#8211; NO <span class=\"caps\"><span class=\"caps\">UTF</span></span>-8 support</td>\n\n		</tr>\n		<tr>\n			<td><code>&#60;txp:aks_evar name=\"my_var\" substr=\"2:3\" setname=\"other_var\" /&#62;</code></td>\n			<td>678</td>\n			<td><strong>other_var</strong> =substr(my_var,2,3)</td>\n		</tr>\n\n	</table>\n	<p>.</p>\n\n<p>.<br /><strong><span class=\"caps\">SPLIT</span></strong></p>\n\n<table>\n	<tr>\n		<th>Example</th>\n\n		<th>Result</th>\n		<th>Comments</th>\n	</tr>\n<tr>\n<td><code>&#60;txp:aks_evar name=\"my_var\" value=\"123:qqq\" split=\"2\" delim=\":\" /&#62;</code></td>\n<td>qqq</td>\n<td>Return part of spliting string</td>\n</tr>\n</table>\n\n\n\n\n\n\n\n\n\n	<h2><span class=\"caps\">REGEXP</span></h2>\n\n	<p>Change \\r\\n to &#60;br /&#62;</p>\n\n<pre>&#60;txp:aks_evar regexp=\"/[\\r\\n]+/s\" to=\"&#60;br /&#62;\"&#62;\n\nSome text\nnext line\nnext\netc\n&#60;/txp:aks_evar&#62;\n</pre>\n\n	<p>Get subdomain name. Sample:   abc.domain.com =&#62; abc   and www.abc.domain.com =&#62; abc</p>\n\n<pre>&#60;txp:aks_evar avar=\'SERVER_NAME\' regexp=\"/^(www\\.)?(.*?)\\..*$/i\" to=\"$2\" /&#62;\n</pre>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\n#aks h1 { color: #000000; font: 20px sans-serif;}\n#aks h2 { border-bottom: 1px solid black; padding:10px 0 0; color: #000000; font: 15px sans-serif; }\n#aks table {width: 100%; border:1px solid; border-color:#ddd #000 #000 #ddd;}\n#aks th {\n	background-color: #E3E3DB;\n	border:1px solid;\n	border-color:#ddd #999 #888 #ddd;\n	padding: 10px 1px 10px 1px;\n}\n#aks td { background-color: #F2F2ED; padding: 5px 1px 5px 1px;}\n\n#aks pre { padding: 5px;\nline-height: 1.6em;\nfont-family: Verdana, Arial;\nfont-size: 100%;\nborder: 1px dashed #CCCCCC;\nmargin: 1.5em 0;\n}\n\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', 'function aks_var($atts, $thing=\'\') { aks_evar($atts,$thing); return \"\"; }\r\n\r\nfunction aks_evar($atts, $thing=\'\') {\r\n	extract(lAtts(array(\r\n		\'name\'			=> \'\',\r\n		\'customfield\'		=> \'\',\r\n		\'value\'			=> \'\',\r\n\r\n		\'var\'			=> \'\',		// any variables\r\n		\'avar\'			=> \'\',		// apache variables  $_SERVER[$avar]\r\n		\'tvar\'			=> \'\',		// thisarticle variables   $GLOBALS[\'thisarticle\'][$tvar]\r\n\r\n		\'substr\'		=> \'\',\r\n\r\n		\'regexp\'		=> \'\',\r\n		\'to\'		=> \'\',\r\n\'func\' =>\'\',						// testing\r\n		\'calc\'			=> \'\',\r\n		\'round\'			=> \'\',\r\n\r\n		\'split\'			=> \'\',\r\n		\'delim\'			=> \'##\',\r\n\r\n		\'words\'			=> \'\',		// return N words from string		//testing\r\n		\'nohtml\'		=> \'\',		// testing\r\n\r\n		\'setname\'     => \'\'\r\n	), $atts));\r\n//--------------------------- get $value\r\n  if(!$value){\r\n     if($name){$value = $GLOBALS[\'variable\'][$name];}\r\n     if($customfield){$customfield=strtolower($customfield); $value = $GLOBALS[\'thisarticle\'][$customfield];}\r\n  }else{\r\n     $value=parse($value);\r\n  }\r\n  if($var){ eval(\'$value=\'.$var.\';\'); }\r\n  if($avar){ eval(\'$value=$_SERVER[\"\'.$avar.\'\"];\'); }\r\n  if($tvar){ eval(\'$value=$GLOBALS[\"thisarticle\"][\"\'.$tvar.\'\"];\'); }\r\n\r\n  if($thing){ $value=parse($thing); }\r\n//  if($thing){ $value=$thing; }\r\n\r\n\r\n//---------------------------- some calc with $value\r\n  if($split) {   list($s1,$s2)=explode(\':\', $split,2); if($s2){$s2=1;}else{$s2=0;}$tt=explode($delim, $value, $s1+$s2); $value=$tt[$s1-1]; }\r\n  if($substr){ list($s1,$s2)=explode(\':\', $substr,2); if($s2){$s1.=\",$s2\";} eval(\"\\$value=substr(\'$value\',$s1);\"); }   // warning, no UTF-8 support!\r\n  if($regexp){ $value=preg_replace($regexp, $to, $value); }\r\n\r\n  if($calc){ eval(\'$value=\'.$value.parse($calc).\";\"); }\r\n  if($round || $round ==\"0\"){ $value=round($value,$round); }\r\n  if($func && function_exists($func)){ $value=$func($value); }\r\n  if($nohtml){\r\n	$value=preg_replace(\'/<.*?\\>/is\',\' \', $value);\r\n	$value=preg_replace(\'/&lt;.*?\\&gt;/is\',\' \', $value);\r\n	$value=preg_replace(\'/&amp;.*?;/is\',\' \', $value);\r\n	$value=preg_replace(\'/&nbsp;/i\',\' \',$value);\r\n	$value=preg_replace(\'/[ \\r\\n]+/is\',\' \', $value);\r\n  }\r\n  if($words){ $word = explode(\" \", $value); if(count($word) >$words){$value = join(\" \",array_slice($word, 0, $words)).\"...\"; } 	}\r\n\r\n//---------------------------- out $value\r\n  if($setname){\r\n     $GLOBALS[\'variable\'][$setname] = $value;\r\n  }else{\r\n     if($name){$GLOBALS[\'variable\'][$name] = $value;}\r\n  }\r\n\r\n  return $value;\r\n}\r\n', 'function aks_var($atts, $thing=\'\') { aks_evar($atts,$thing); return \"\"; }\r\n\r\nfunction aks_evar($atts, $thing=\'\') {\r\n	extract(lAtts(array(\r\n		\'name\'			=> \'\',\r\n		\'customfield\'		=> \'\',\r\n		\'value\'			=> \'\',\r\n\r\n		\'var\'			=> \'\',		// any variables\r\n		\'avar\'			=> \'\',		// apache variables  $_SERVER[$avar]\r\n		\'tvar\'			=> \'\',		// thisarticle variables   $GLOBALS[\'thisarticle\'][$tvar]\r\n\r\n		\'substr\'		=> \'\',\r\n\r\n		\'regexp\'		=> \'\',\r\n		\'to\'		=> \'\',\r\n\'func\' =>\'\',						// testing\r\n		\'calc\'			=> \'\',\r\n		\'round\'			=> \'\',\r\n\r\n		\'split\'			=> \'\',\r\n		\'delim\'			=> \'##\',\r\n\r\n		\'words\'			=> \'\',		// return N words from string		//testing\r\n		\'nohtml\'		=> \'\',		// testing\r\n\r\n		\'setname\'     => \'\'\r\n	), $atts));\r\n//--------------------------- get $value\r\n  if(!$value){\r\n     if($name){$value = $GLOBALS[\'variable\'][$name];}\r\n     if($customfield){$customfield=strtolower($customfield); $value = $GLOBALS[\'thisarticle\'][$customfield];}\r\n  }else{\r\n     $value=parse($value);\r\n  }\r\n  if($var){ eval(\'$value=\'.$var.\';\'); }\r\n  if($avar){ eval(\'$value=$_SERVER[\"\'.$avar.\'\"];\'); }\r\n  if($tvar){ eval(\'$value=$GLOBALS[\"thisarticle\"][\"\'.$tvar.\'\"];\'); }\r\n\r\n  if($thing){ $value=parse($thing); }\r\n//  if($thing){ $value=$thing; }\r\n\r\n\r\n//---------------------------- some calc with $value\r\n  if($split) {   list($s1,$s2)=explode(\':\', $split,2); if($s2){$s2=1;}else{$s2=0;}$tt=explode($delim, $value, $s1+$s2); $value=$tt[$s1-1]; }\r\n  if($substr){ list($s1,$s2)=explode(\':\', $substr,2); if($s2){$s1.=\",$s2\";} eval(\"\\$value=substr(\'$value\',$s1);\"); }   // warning, no UTF-8 support!\r\n  if($regexp){ $value=preg_replace($regexp, $to, $value); }\r\n\r\n  if($calc){ eval(\'$value=\'.$value.parse($calc).\";\"); }\r\n  if($round || $round ==\"0\"){ $value=round($value,$round); }\r\n  if($func && function_exists($func)){ $value=$func($value); }\r\n  if($nohtml){\r\n	$value=preg_replace(\'/<.*?\\>/is\',\' \', $value);\r\n	$value=preg_replace(\'/&lt;.*?\\&gt;/is\',\' \', $value);\r\n	$value=preg_replace(\'/&amp;.*?;/is\',\' \', $value);\r\n	$value=preg_replace(\'/&nbsp;/i\',\' \',$value);\r\n	$value=preg_replace(\'/[ \\r\\n]+/is\',\' \', $value);\r\n  }\r\n  if($words){ $word = explode(\" \", $value); if(count($word) >$words){$value = join(\" \",array_slice($word, 0, $words)).\"...\"; } 	}\r\n\r\n//---------------------------- out $value\r\n  if($setname){\r\n     $GLOBALS[\'variable\'][$setname] = $value;\r\n  }else{\r\n     if($name){$GLOBALS[\'variable\'][$name] = $value;}\r\n  }\r\n\r\n  return $value;\r\n}\r\n', 'c18f2e6bdcf32f04c21838f0236d4caf', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rss_unlimited_categories', 0, 'Rob Sable, makss', 'http://makss.uaho.net/plugins/rss_unlimited_categories', '0.7.6.6k', 'Enable unlimited categories (tags) per article. Require Txp 4.6-dev', '<style type=\"text/css\">\n.rss-tbl {\n	margin: 30px 0;\n	box-shadow: 1px 2px 10px #ccc;\n}\n.rss-tbl td, .rss-tbl th {\n	border: 1px dotted #ccc;\n	padding: 3px;\n}\n.rss-tbl th {\n	text-align: center;\n	vertical-align: middle;\n	white-space: nowrap;\n	background-color: #ccc;\n}\n.rss-tbl .c {\n	background-color: #eee;\n}\n\n</style>\n\n\n<div style=\'text-align:right;\'><a href=\'?event=rss_uc_admin_tab_pref\'>Plugin preferences</a><br />\n	<a href=\"http://makss.uaho.net/plugins/rss_unlimited_categories\">Check new version</a>\n</div>\n\n<div style=\"font-size: 1.2em;\">\n	<h1>Unlimited Article Categories in Textpattern</h1>\n\n	<p>The rss_unlimited_categories plugin enables you to assign an unlimited number of categories to an article so that you&#8217;re not limited to the default number allowed by Textpattern (2).</p>\n\n	<p>The plugin will add a multiple select list to your Txp > Content -> Write tab to enable the selection of unlimited categories.  It also includes several public side tags to display the categories and list articles by category and an admin tab to set plugin preferences.</p>\n\n<br/>\n\n	<h2>Installation and Setup</h2>\n\n	<ol>\n		<li>Simply install and enable the plugin.  The plugin will automatically install a new table that relates articles to categories.</li>\n		<li>Navigate to the Txp > Content -> Write tab and you&#8217;ll find a new multiple select list above the standard Textpattern category selection.</li>\n		<li>For existing sites, you can copy your current article/category assignments to the new category table structure using the following <span class=\"caps\">SQL</span> statements:</li>\n	</ol>\n\n<pre><code>INSERT INTO textpattern_category (article_id, category_id) SELECT t.id, c.id\nFROM textpattern t LEFT JOIN txp_category c on t.category1 = c.name\nWHERE c.type = &#39;article&#39; and t.category1 != &#39;&#39;;</code></pre>\n\n<pre><code>INSERT INTO textpattern_category (article_id, category_id) SELECT t.id, c.id\nFROM textpattern t LEFT JOIN txp_category c on t.category2 = c.name\nWHERE c.type = &#39;article&#39; and t.category2 != &#39;&#39;;</code></pre>\n<br/>\n\n	<table class=\"rss-tbl\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" id=\"Public-side-Tags\" colspan=\"2\">Public-side Tags</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">All of the tags use the new category table created by this plugin instead of the standard Textpattern category 1 and 2 fields.  The collection of public site tags include:</td>\n		</tr>\n		<tr>\n			<th>tag name</th>\n			<th>description</th>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_filedunder\"><strong>rss_uc_filedunder</strong></a></td>\n			<td>Lists all categories assigned to the current article.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_cloud\"><strong>rss_uc_cloud</strong></a></td>\n			<td>Displays a weighted list of all categories as a tag cloud.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_list\"><strong>rss_uc_list</strong></a></td>\n			<td>Displays a list of all categories with the number of posts in each.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_article_list\"><strong>rss_uc_article_list</strong></a></td>\n			<td>Lists articles by category.  This is a replacement for the standard Textpattern article list tags.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_related\"><strong>rss_uc_related</strong></a></td>\n			<td>Lists related articles by category.  This is a replacement for the standard Textpattern related article tag that uses the unlimited category tables instead of the standard Textpattern category 1 and 2 fields.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_sct_permlink\"><strong>rss_sct_permlink</strong></a></td>\n			<td>Builds a permlink in /section/category/title format.  This is used exactly as the standard Textpattern permlink tag.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_uc_count\"><strong>rss_uc_count</strong></a></td>\n			<td>Displays the number of articles assigned to a given category.</td>\n		</tr>\n		<tr>\n			<td><a href=\"#rss_if_article_uc\"><strong>rss_if_article_uc</strong></a></td>\n			<td>Conditional tag that tells if a particular category is assigned to an article.  This is a replacement for the standard Textpattern if article category tag.</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_filedunder\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_filedunder (rss_unlimited_categories_filedunder)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Lists all categories assigned to the current article.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>parent</td>\n			<td>Accepts a comma separated list of categories. Only categories with the specified category as a parent will be displayed.</td>\n			<td>&nbsp;</td>\n			<td>parent1,parent2</td>\n		</tr>\n		<tr>\n			<td>linktosection</td>\n			<td>The section used for category links.</td>\n			<td>Current section</td>\n			<td>section1</td>\n		</tr>\n		<tr>\n			<td>delim</td>\n			<td>Delimiter between linktosection and category</td>\n			<td>/</td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>suffix</td>\n			<td>The suffix used for category links.</td>\n			<td></td>\n			<td></td>\n		</tr>\n\n		<tr>\n			<td>linked</td>\n			<td>0 - Plain category titles,<br />1 - Linked category titles,<br />2 - Plain category names<br />3 - /name...</td>\n			<td>1</td>\n			<td>0</td>\n		</tr>\n		<tr>\n			<td>usemessy</td>\n			<td>Use messy (/section/?c=category) URLs instead of clean (/section/category) URLs for category links..</td>\n			<td>0</td>\n			<td>1</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The field the articles are sorted by: title, name</td>\n			<td>title asc</td>\n			<td>name asc</td>\n		</tr>\n		<tr class=\"c\">\n			<td>wraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the category list.</td>\n			<td></td>\n			<td>ul</td>\n		</tr>\n		<tr class=\"c\">\n			<td>class</td>\n			<td>The class attribute on the wraptag.</td>\n			<td></td>\n			<td>myclass</td>\n		</tr>\n		<tr class=\"c\">\n			<td>break</td>\n			<td>HTML/text that wraps/separates each element in the list.</td>\n			<td>,</td>\n			<td>li</td>\n		</tr>\n		<tr class=\"c\">\n			<td>breakclass</td>\n			<td>The maximum number of live search results returned.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>listwraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label and category list.</td>\n			<td></td>\n			<td>div</td>\n		</tr>\n		<tr class=\"c\">\n			<td>label</td>\n			<td>A label that appears before the list.</td>\n			<td></td>\n			<td>File under: </td>\n		</tr>\n		<tr class=\"c\">\n			<td>labeltag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label.</td>\n			<td></td>\n			<td>h4</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_cloud\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_cloud (rss_unlimited_categories_cloud)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Displays a weighted list of all categories as a &#8220;cloud&#8221;.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>section</td>\n			<td>Accepts a comma separated list of sections that posts are listed from. section=&#8221;!photos,writing,thoughts&#8221; (writing <span class=\"caps\">AND</span> thoughts <span class=\"caps\">AND</span> <span class=\"caps\">NOT</span> photos), or just section=&#8221;!photos&#8221; (all sections except photos).</td>\n			<td></td>\n			<td>section1,section2</td>\n		</tr>\n		<tr>\n			<td>time</td>\n			<td>Accepts one of 3 options &#8220;past&#8221;, &#8220;future&#8221; or &#8220;any&#8221;.  By default (past) will display articles with a posted date before the current date/time.\nSelecting <span class=\"caps\">FUTURE</span> will display articles with a posted date after the current date/time.  Selecting <span class=\"caps\">ALL</span> will display all articles regardless of posted date.</td>\n			<td>past</td>\n			<td>any</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>The maximum number of categories returned.</td>\n			<td>999</td>\n			<td>limit=&#8220;5&#8221;</td>\n		</tr>\n		<tr>\n			<td>weightmin</td>\n			<td>The minimum weight(count) for include in \"cloud\".</td>\n			<td>0</td>\n			<td>weightmin=&#8220;5&#8221;</td>\n		</tr>\n		<tr>\n			<td>parent</td>\n			<td>Only categories with the specified category as a parent will be displayed.</td>\n			<td>&nbsp;</td>\n			<td>parent=&#8220;mycategory&#8221;</td>\n		</tr>\n		<tr>\n			<td>linktosection</td>\n			<td>The section used for category links.</td>\n			<td>article</td>\n			<td>linktosection=&#8220;archive&#8221;</td>\n		</tr>\n		<tr>\n			<td>usemessy</td>\n			<td>Use messy (/section/?c=category) URLs instead of clean (/section/category) URLs for category links.</td>\n			<td>0</td>\n			<td>1</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The field the articles are sorted by: title, name, id</td>\n			<td>title asc</td>\n			<td>name asc</td>\n		</tr>\n		<tr class=\"c\">\n			<td>cloudwraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label and category list.</td>\n			<td></td>\n			<td>div</td>\n		</tr>\n		<tr class=\"c\">\n			<td>wraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the category list.</td>\n			<td>p</td>\n			<td>ul</td>\n		</tr>\n		<tr class=\"c\">\n			<td>class</td>\n			<td>The class attribute on the wraptag.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>break</td>\n			<td>HTML/text that wraps/separates each element in the list.</td>\n			<td>,</td>\n			<td>li</td>\n		</tr>\n		<tr class=\"c\">\n			<td>breakclass</td>\n			<td>The maximum number of live search results returned.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>label</td>\n			<td>A label that appears before the list.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>labeltag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label.</td>\n			<td></td>\n			<td>h4</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_list\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_list (rss_unlimited_categories_list)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Displays a list of all categories with the number of posts in each.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>section</td>\n			<td>Accepts a comma separated list of sections that posts are listed from. section=&#8221;!photos,writing,thoughts&#8221; (writing <span class=\"caps\">AND</span> thoughts <span class=\"caps\">AND</span> <span class=\"caps\">NOT</span> photos), or just section=&#8221;!photos&#8221; (all sections except photos).</td>\n			<td></td>\n			<td>section1,section2</td>\n		</tr>\n		<tr>\n			<td>time</td>\n			<td>Accepts one of 3 options &#8220;past&#8221;, &#8220;future&#8221; or &#8220;any&#8221;.  By default (past) will display articles with a posted date before the current date/time.\nSelecting <span class=\"caps\">FUTURE</span> will display articles with a posted date after the current date/time.  Selecting <span class=\"caps\">ALL</span> will display all articles regardless of posted date.</td>\n			<td>past</td>\n			<td>any</td>\n		</tr>\n		<tr>\n			<td>parent</td>\n			<td>Only categories with the specified category as a parent will be displayed.</td>\n			<td>&nbsp;</td>\n			<td>parent=&#8220;mycategory&#8221;</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>The maximum number of categories returned.</td>\n			<td>999</td>\n			<td>limit=&#8220;5&#8221;</td>\n		</tr>\n		<tr>\n			<td>offset</td>\n			<td>Excludes x number of categories from the beginning of the category listing.</td>\n			<td>0</td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>linktosection</td>\n			<td>The section used for category links.</td>\n			<td>article</td>\n			<td>section1</td>\n		</tr>\n		<tr>\n			<td>usemessy</td>\n			<td>Use messy (/section/?c=category) URLs instead of clean (/section/category) URLs for category links..</td>\n			<td>0</td>\n			<td>1</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The field the articles are sorted by: title, name, id</td>\n			<td>title asc</td>\n			<td></td>\n		</tr>\n\n\n		<tr>\n			<td>showcount</td>\n			<td>Displays the number of posts in each category.</td>\n			<td>1</td>\n			<td>showcount=&#8220;0&#8221;</td>\n		</tr>\n		<tr>\n			<td>countlinked</td>\n			<td>Determines whether the category count is shown inside the link.  By default (1) the count is inside the link.</td>\n			<td>1</td>\n			<td>countlinked=&#8220;0&#8221;</td>\n		</tr>\n		<tr>\n			<td>showallcount</td>\n			<td>Displays the number of posts in all categories at the end of the category listing.</td>\n			<td>0</td>\n			<td>showallcount=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>allcountlabel</td>\n			<td>The label used for the all categories count.</td>\n			<td>All</td>\n			<td>allcountlabel=&#8220;All Categories&#8221;</td>\n		</tr>\n		<tr class=\"c\">\n			<td>listwraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label and category list.</td>\n			<td></td>\n			<td>div</td>\n		</tr>\n		<tr class=\"c\">\n			<td>wraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the category list.</td>\n			<td>ul</td>\n			<td>p</td>\n		</tr>\n		<tr class=\"c\">\n			<td>class</td>\n			<td>The class attribute on the wraptag.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>break</td>\n			<td>HTML/text that wraps/separates each element in the list.</td>\n			<td>li</td>\n			<td>, </td>\n		</tr>\n		<tr class=\"c\">\n			<td>breakclass</td>\n			<td>The class attribute on the breaktag.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>label</td>\n			<td>A label that appears before the list.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr class=\"c\">\n			<td>labeltag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the label.</td>\n			<td>&nbsp;</td>\n			<td>labeltag=&#8220;h4&#8221;</td>\n		</tr>\n		<tr class=\"c\">\n			<td><b>activeclass</b></td>\n			<td>This allows you to apply special class to the currently-viewed category.</td>\n			<td></td>\n			<td>css-active-class</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_article_list\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_article_list (rss_unlimited_categories_article_list)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Lists articles by category.  This is a replacement for the standard Textpattern article list tags that uses the unlimited category tables instead of the standard Textpattern category 1 and 2 fields.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>section</td>\n			<td>Accepts a comma separated list of sections that posts are listed from. section=&#8221;!photos,writing,thoughts&#8221; (writing <span class=\"caps\">AND</span> thoughts <span class=\"caps\">AND</span> <span class=\"caps\">NOT</span> photos), or just section=&#8221;!photos&#8221; (all sections except photos).</td>\n			<td>article</td>\n			<td>section1,section2</td>\n		</tr>\n		<tr>\n			<td>category</td>\n			<td>Accepts a comma separated list of categories.  NO spaces after commas.  If currently browsing by category, the global category will be used as the default.</td>\n			<td><code>$c</code> (global category)</td>\n			<td>category=&#8220;5&#8221;</td>\n		</tr>\n		<tr>\n			<td>andcategory</td>\n			<td>Accepts a comma separated list of categories.  NO spaces after commas.  (category1 AND category2 AND ...)</td>\n			<td>\"\"</td>\n			<td>andcategory=&#8220;cat1,cat2&#8221;</td>\n		</tr>\n		<tr>\n			<td>categorylogic</td>\n			<td>Logic for \"category\" and \"andcategory\" attributes. Valid choices include \"AND\", \"OR\"<br /><br /><u>Sample:</u> category=\"cat1,cat2\" andcategory=\"cat4,cat5\" categorylogic=\"AND\"<br /><br />return articles<br />(cat1 OR cat2)<b>AND</b>(cat4 AND cat5)<br /><br /><u>Sample:</u> category=\"cat1,cat2\" andcategory=\"cat4,cat5\" categorylogic=\"OR\"<br /><br />return articles<br />(cat1 OR cat2)<b>OR</b>(cat4 AND cat5)</td>\n			<td>\"AND\"</td>\n			<td>categorylogic=\"OR\"</td>\n		</tr>\n\n		<tr>\n			<td>usechildren</td>\n			<td>If a single article category is specified and that category is a parent category, articles assigned to the parent&#8217;s child categories will be listed rather than articles assigned to the parent.</td>\n			<td>0</td>\n			<td>usechildren=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>form</td>\n			<td>The article form used in the article list.</td>\n			<td>default</td>\n			<td>form=&#8220;myform&#8221;</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>The maximum number of articles returned.</td>\n			<td>999</td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>offset</td>\n			<td>Excludes x number of articles from the beginning of the article listing.</td>\n			<td>0</td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>time</td>\n			<td>Accepts one of 3 options &#8220;past&#8221;, &#8220;future&#8221; or &#8220;any&#8221;.  By default (past) will display articles with a posted date before the current date/time.\nSelecting <span class=\"caps\">FUTURE</span> will display articles with a posted date after the current date/time.  Selecting <span class=\"caps\">ALL</span> will display all articles regardless of posted date.</td>\n			<td>past</td>\n			<td>any</td>\n		</tr>\n		<tr>\n			<td>status</td>\n			<td>Restrict by status. Available values: draft, hidden, pending, live, sticky.</td>\n			<td>live</td>\n			<td>status=\"hidden\"</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The field the articles are sorted by.</td>\n			<td>uPosted desc</td>\n			<td>title asc</td>\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>The ID of a single article to display.</td>\n			<td>&nbsp;</td>\n			<td>id=&#8220;100&#8221;</td>\n		</tr>\n		<tr>\n			<td>hideself</td>\n			<td>Hide current article from listing</td>\n			<td>1</td>\n			<td>hideself=&#8220;0&#8221;</td>\n		</tr>\n\n		<tr>\n			<td>filter</td>\n			<td>Indicates whether filtering is turned on.</td>\n			<td>0</td>\n			<td>filter=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>filterfield</td>\n			<td>The field the articles are filtered on.</td>\n			<td>&nbsp;</td>\n			<td>filterfield=&#8220;custom_4&#8221;</td>\n		</tr>\n		<tr>\n			<td>filtername</td>\n			<td>The <span class=\"caps\">URL</span> keyword that preceeds the filter.  For example, these attributes (filter=&#8220;1&#8221; filterfield=&#8220;custom_4&#8221; filtername=&#8220;byprice&#8221;) would filter this <span class=\"caps\">URL</span> (/article/byprice/lt/3) and display articles where the value in the custom_4 field was less than 3.  The valid <span class=\"caps\">URL</span> operators are <strong>eq</strong> (equals), <strong>lt</strong> (less than) and <strong>gt</strong> (greater than).  Filter URLs must be constructed manually at this point.</td>\n			<td>&nbsp;</td>\n			<td>filtername=&#8220;byprice&#8221;</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_related\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_related (rss_unlimited_categories_related)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Lists related articles by category.  This is a replacement for the standard Textpattern related article tag that uses the unlimited category tables instead of the standard Textpattern category 1 and 2 fields.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>section</td>\n			<td>Accepts a comma separated list of sections that posts are listed from. section=&#8221;!photos,writing,thoughts&#8221; (writing <span class=\"caps\">AND</span> thoughts <span class=\"caps\">AND</span> <span class=\"caps\">NOT</span> photos), or just section=&#8221;!photos&#8221; (all sections except photos).</td>\n			<td></td>\n			<td>section1,section2</td>\n		</tr>\n		<tr>\n			<td>form</td>\n			<td>The article form used in the article list.</td>\n			<td>default</td>\n			<td>form=&#8220;myform&#8221;</td>\n		</tr>\n		<tr>\n			<td>limit</td>\n			<td>The maximum number of articles returned.</td>\n			<td>999</td>\n			<td>limit=&#8220;5&#8221;</td>\n		</tr>\n		<tr>\n			<td>offset</td>\n			<td>Excludes x number of articles from the beginning of the article listing.</td>\n			<td>0</td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>time</td>\n			<td>Accepts one of 3 options &#8220;past&#8221;, &#8220;future&#8221; or &#8220;any&#8221;.  By default (past) will display articles with a posted date before the current date/time.\nSelecting <span class=\"caps\">FUTURE</span> will display articles with a posted date after the current date/time.  Selecting <span class=\"caps\">ALL</span> will display all articles regardless of posted date.</td>\n			<td>past</td>\n			<td>any</td>\n		</tr>\n		<tr>\n			<td>sort</td>\n			<td>The field the articles are sorted by.</td>\n			<td>uPosted desc</td>\n			<td>title asc</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_sct_permlink\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_sct_permlink</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Build article permlinks in /section/category/title format.  Use as a replacement for the <code>&#60;txp:permlink/&#62;</code> tag.<br/>For example: <code>&#60;txp:rss_sct_permlink&#62;&#60;txp:title/&#62;&#60;/txp:rss_sct_permlink&#62;</code></td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td>The category name to count.</td>\n			<td>&nbsp;</td>\n			<td>category=&#8220;mycategory&#8221;</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_uc_count\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_uc_count (rss_unlimited_category_count)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Displays the number of articles assigned to a given category.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td>The category name to count.</td>\n			<td></td>\n			<td>mycategory</td>\n		</tr>\n		<tr>\n			<td>id</td>\n			<td>ID category to count.</td>\n			<td></td>\n			<td></td>\n		</tr>\n		<tr>\n			<td>section</td>\n			<td>Accepts a comma separated list of sections that posts are listed from.\nsection=&#8221;!photos,writing,thoughts&#8221; (writing <span class=\"caps\">AND</span> thoughts <span class=\"caps\">AND</span> <span class=\"caps\">NOT</span> photos), or just section=&#8221;!photos&#8221; (all sections except photos).</td>\n			<td></td>\n			<td>section1,section2</td>\n		</tr>\n		<tr>\n			<td>time</td>\n			<td>Accepts one of 3 options &#8220;past&#8221;, &#8220;future&#8221; or &#8220;any&#8221;.  By default (past) will display articles with a posted date before the current date/time.\nSelecting <span class=\"caps\">FUTURE</span> will display articles with a posted date after the current date/time.  Selecting <span class=\"caps\">ALL</span> will display all articles regardless of posted date.</td>\n			<td>past</td>\n			<td>any</td>\n		</tr>\n	</table>\n\n	<table class=\"rss-tbl\" id=\"rss_if_article_uc\">\n		<tr>\n			<td style=\"vertical-align:middle;font:normal bold 1.4em georgia;text-align:left;color:#500;\" colspan=\"4\">rss_if_article_uc (rss_if_article_unlimited_category)</td>\n		</tr>\n		<tr>\n			<td style=\"vertical-align:middle;font:normal 1.1em verdana;text-align:left;color:#036;\" colspan=\"4\">Conditional tag that tells if a particular category is assigned to an article.  This is a replacement for the standard Textpattern if article category tag.</td>\n		</tr>\n		<tr>\n			<th>attribute</th>\n			<th>description</th>\n			<th>default value</th>\n			<th>example usage</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td>The category name to check.</td>\n			<td>&nbsp;</td>\n			<td>name=&#8220;mycategory&#8221;</td>\n		</tr>\n	</table>\n\n</div>', 'if (@txpinterface == \'admin\') {\r\n	if ( !(getRows(\"SHOW TABLES LIKE \'\".PFX.\"textpattern_category\'\")) ) {\r\n		safe_create(\"textpattern_category\", \"`article_id` int(11) NOT NULL default \'0\', `category_id` int(6) NOT NULL default \'0\', UNIQUE KEY  (`article_id`,`category_id`)\");\r\n	}\r\n\r\n	register_callback(\'rss_uc_admin_tab_article\', \'article\');\r\n	register_callback(\'rss_uc_admin_tab_category\', \'category\');\r\n\r\n	add_privs(\'rss_uc_admin_tab_pref\', \'1,2\');\r\n	register_tab(\"extensions\", \"rss_uc_admin_tab_pref\", \"Unlim Cats\");\r\n	register_callback(\"rss_uc_admin_tab_pref\", \"rss_uc_admin_tab_pref\");\r\n	add_privs(\'plugin_prefs.rss_unlimited_categories\',\'1,2\');\r\n	register_callback(\'rss_uc_admin_tab_pref\', \'plugin_prefs.rss_unlimited_categories\');\r\n\r\n	register_callback(\"rss_uc_admin_article_save\", \"article_posted\");\r\n	register_callback(\"rss_uc_admin_article_save\", \"article_saved\");\r\n	register_callback(\"rss_uc_admin_articles_deleted\", \"articles_deleted\");\r\n	register_callback(\"rss_uc_admin_categories_deleted\", \"categories_deleted\");\r\n}else{\r\n	register_callback(\'rss_multi_url\',\'pretext\');\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'rss_if_article_uc\')\r\n		->register(\'rss_uc_article_list\')\r\n		->register(\'rss_uc_related\')\r\n		->register(\'rss_uc_cloud\')\r\n		->register(\'rss_uc_filedunder\')\r\n		->register(\'rss_uc_list\')\r\n		->register(\'rss_uc_count\')\r\n		->register(\'rss_sct_permlink\')\r\n		// aliases\r\n		->register(\'rss_if_article_unlimited_category\')\r\n		->register(\'rss_unlimited_categories_article_list\')\r\n		->register(\'rss_unlimited_categories_related\')\r\n		->register(\'rss_unlimited_categories_cloud\')\r\n		->register(\'rss_unlimited_categories_filedunder\')\r\n		->register(\'rss_unlimited_categories_list\')\r\n		->register(\'rss_unlimited_category_count\')\r\n		;\r\n	}\r\n}\r\n\r\n\r\n\r\n\r\n// public\r\n\r\nfunction rss_multi_url() {\r\n  global $attach_titles_to_permalinks,$permlink_mode,$pretext;\r\n\r\n  // set messy variables\r\n  $out =  makeOut(\'id\',\'s\',\'c\',\'q\',\'pg\',\'p\',\'month\',\'author\');\r\n  if (!$out[\'id\'] && !$out[\'s\'] && !(txpinterface==\'css\')) {\r\n    $subpath = preg_quote(preg_replace(\"/http:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu),\"/\");\r\n    $req = preg_replace(\"/^$subpath/i\",\"/\",serverSet(\'REQUEST_URI\'));\r\n    extract(chopUrl($req));\r\n\r\n    if (!empty($u1)) {\r\n\r\n      switch($u1) {\r\n\r\n	case \'atom\':	include txpath.\'/publish/atom.php\'; exit(atom());\r\n	case \'rss\':		include txpath.\'/publish/rss.php\'; exit(rss());\r\n	default:\r\n		     if ($permlink_mode == \"section_id_title\" || $permlink_mode = \"section_title\") {\r\n			if (empty($u2)) {\r\n			    $out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    $is_404 = empty($out[\'s\']);\r\n			}else{\r\n			    if (isset($u2)) {\r\n				$rs = lookupByTitleSection($u2,$u1);\r\n				$out[\'id\'] = @$rs[\'ID\'];\r\n				$_GET[\'id\'] = @$rs[\'ID\'];\r\n				$out[\'s\'] = @$rs[\'Section\'];\r\n				$_GET[\'s\'] = @$rs[\'Section\'];\r\n				$is_404 = empty($out[\'s\']);\r\n				if(!$out[\'s\'] && empty($u3)) {\r\n				    $out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : NULL;\r\n				    $_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 :NULL;\r\n				    if (!empty($out[\'c\'])) {\r\n					$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : NULL;\r\n					$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : NULL;\r\n				    }\r\n				    $is_404 = empty($out[\'c\']);\r\n				}\r\n			    }\r\n			    if (!$out[\'id\'] && isset($u3)) {\r\n				$rs = lookupByTitleSection($u3,$u1);\r\n				if (empty($rs[\'ID\'])) {\r\n				    $out[\'pg\'] = $u3;\r\n				    $is_404 = (empty($out[\'s\']) or empty($out[\'id\']));\r\n				} else {\r\n				    $out[\'id\'] = @$rs[\'ID\'];\r\n				    $out[\'s\'] = @$rs[\'Section\'];\r\n				    $out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				    $_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				    $_GET[\'s\'] = @$rs[\'Section\'];\r\n				    $_GET[\'id\'] = @$rs[\'ID\'];\r\n				}\r\n			    }\r\n			    if (!empty($u4)) {\r\n				$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n				$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    }\r\n			    if ($out[\'id\'] or $out[\'c\']) {\r\n				$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n				$out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				$_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    } else {\r\n				$is_404 = (empty($out[\'s\']) or empty($out[\'id\']));\r\n			    }\r\n			}\r\n		    }\r\n		}\r\n	    }\r\n	}\r\n    }\r\n\r\n\r\n//------------------------------------------------------------- TAGS\r\nfunction rss_unlimited_categories_filedunder($atts){	return rss_uc_filedunder($atts); }\r\nfunction rss_uc_filedunder($atts) {\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'linktosection\'	=> $thisarticle[\'section\'],\r\n		\'delim\'		=> \'/\',\r\n		\'suffix\'	=> \'\',\r\n		\'linked\'	=> 1,\r\n		\'parent\'	=> \'\',\r\n		\'usemessy\'	=> 0,\r\n		\'sort\'		=> \'title asc\',\r\n\r\n		\'wraptag\'	=> \'\',\r\n		\'class\'		=> \'\',\r\n		\'break\'		=> \', \',\r\n		\'breakclass\'	=> \'\',\r\n		\'listwraptag\'	=> \'\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n	),$atts));\r\n\r\n	$parents = ($parent) ? \" AND c.parent IN(\'\".join(\"\',\'\", doSlash(do_list_unique($parent))).\"\')\" : \"\";\r\n\r\n	$cats = array();\r\n	$rsc = getRows(\"SELECT c.title, c.name FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE article_id=\".$thisarticle[\'thisid\'].$parents.\" ORDER BY $sort\");\r\n	if ($rsc) {\r\n		foreach ($rsc as $b) {\r\n			if($linked==1){\r\n				$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.$linktosection.$delim.strtolower($b[\'name\']).$suffix;\r\n				$cats[]=tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($b[\'title\']).\'\"\');\r\n			}elseif($linked==2){\r\n				$cats[]=strtolower($b[\'name\']);\r\n			}elseif($linked==3){\r\n				$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.strtolower($b[\'name\']).$suffix;\r\n				$cats[]=tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($b[\'title\']).\'\"\');\r\n			}else{\r\n				$cats[]=htmlspecialchars($b[\'title\']);\r\n			}\r\n		}\r\n		return doTag(doLabel($label, $labeltag).doWrap($cats, $wraptag, $break, $class, $breakclass), $listwraptag);\r\n	}\r\n	return \'\';\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_list($atts){	return rss_uc_list($atts); }\r\nfunction rss_uc_list($atts) {\r\n	global $c;	// Mod: activeclass attribute\r\n	extract(lAtts(array(\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n		\'parent\'	=> \'\',\r\n		\'showcount\'	=> 1,\r\n		\'showallcount\'	=> 0,\r\n		\'allcountlabel\'	=> \'All\',\r\n		\'countlinked\'	=> 1,\r\n		\'linktosection\'	=> \'article\',\r\n		\'usemessy\'	=> 0,\r\n		\'listwraptag\'	=> \'\',\r\n		\'break\'		=> \'li\',\r\n		\'wraptag\'	=> \'ul\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n\r\n		\'class\'		=> \'\',\r\n		\'breakclass\'	=> \'\',\r\n		\'sort\'		=> \'title asc\',\r\n		\'activeclass\'	=> \'\',\r\n	),$atts));\r\n\r\n	$sections = rssBuildSctSql($section);\r\n	$time = rssBuildTimeSql($time);\r\n\r\n	$prnt = ($parent) ? \" WHERE parent = \'$parent\' \" : \"\";\r\n	$rsc = getRows(\"SELECT distinct c.id, c.name, c.title FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id \".$prnt.\"ORDER BY {$sort} LIMIT {$offset},{$limit}\");\r\n\r\n      if ($rsc) {\r\n	$activeclass = ($activeclass) ? \" class=\\\"$activeclass\\\"\" :\"\";\r\n\r\n	$aq= \"SELECT c.category_id, count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as c ON t.ID = c.article_id WHERE \".$sections.\" t.Status = 4 \".$time.\" group by c.category_id\";\r\n	$uc_count = array();\r\n	if( $rsa = getRows($aq) ){\r\n		foreach( $rsa as $r ){\r\n			if( !empty($r[\'category_id\']) ){\r\n				$uc_count[$r[\'category_id\']] = $r[\'cc\'];\r\n			}\r\n		}\r\n	}\r\n\r\n	$row = array();\r\n	$total = 0;\r\n	foreach ($rsc as $b) {\r\n	    if ( !empty($uc_count[$b[\'id\']]) ) {\r\n	      $path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.$linktosection.\"/\".strtolower($b[\'name\']);\r\n	      $count = ($showcount) ? \" ({$uc_count[$b[\'id\']]})\" : \"\";\r\n	      $total += $uc_count[$b[\'id\']];\r\n	      $row[] = ($countlinked) ?\r\n		// Next two lines add activeclass to category link\r\n		tag(htmlspecialchars($b[\'title\'].$count),\'a\',\' href=\"\'.$path.\'\"\'.($c == strtolower($b[\'name\']) ? $activeclass : \'\').\' title=\"\'.htmlspecialchars($b[\'title\']).\'\"\') :\r\n		tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\' . $path . \'\"\'.($c == strtolower($b[\'name\']) ? $activeclass : \'\').\' title=\"\' . htmlspecialchars($b[\'title\']) . \'\"\') . $count;\r\n	    }\r\n	}\r\n	if ($showallcount) {\r\n	  $path = hu.$linktosection.\"/\".$parent;\r\n	  $count = ($showcount) ? \" (\".$total.\") \" : \"\";\r\n	  $row[]=($countlinked) ?\r\n	    tag(htmlspecialchars($allcountlabel.$count),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($allcountlabel).\'\"\') :\r\n	    tag(htmlspecialchars($allcountlabel),\'a\',\' href=\"\' . $path . \'\" title=\"\' . htmlspecialchars($allcountlabel) . \'\"\') . $count;\r\n	}\r\n	return doTag(doLabel($label, $labeltag).doWrap($row, $wraptag, $break, $class, $breakclass), $listwraptag).n;\r\n      }\r\n      return \'\';\r\n}\r\n\r\n\r\nfunction rss_unlimited_category_count($atts){	return rss_uc_count($atts); }\r\nfunction rss_uc_count($atts) {\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'id\'		=> \'\',\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n	),$atts));\r\n	if( $name && !$id ){\r\n		$id = safe_field(\'id\', \'txp_category\', \"name=\'\".doSlash($name).\"\'\");\r\n	}\r\n	if( $id ){\r\n		$sections=rssBuildSctSql($section);\r\n		$time = rssBuildTimeSql($time);\r\n		$cc = getThing(\"SELECT count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as c ON t.ID = c.article_id WHERE \".$sections.\" c.category_id = \'{$id}\' and t.Status = 4 \".$time);\r\n		return $cc;\r\n	}\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_cloud($atts){ return rss_uc_cloud($atts); }\r\nfunction rss_uc_cloud($atts) {\r\n	extract(lAtts(array(\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n		\'limit\'		=> 999,\r\n		\'weightmin\'	=> 0,\r\n		\'parent\'	=> \'\',			// ?\r\n		\'linktosection\'	=> \'article\',\r\n		\'usemessy\'	=> 0,\r\n		\'sort\'		=> \'title asc\',		// ?\r\n\r\n		\'cloudwraptag\'	=> \'div\',\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \', \',\r\n		\'class\'		=> \'\',\r\n		\'breakclass\'	=> \'\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n	),$atts));\r\n\r\n	$sections = rssBuildSctSql($section);\r\n	$time = rssBuildTimeSql($time);\r\n//	$parents = ($parent) ? \" WHERE c.parent IN(\'\".join(\"\',\'\", doSlash(do_list_unique($parent))).\"\')\" : \"\";\r\n\r\n	$q = \"SELECT tc.category_id, c.name, c.title, c.parent, count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id  LEFT JOIN \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE \".$sections.\" t.Status = 4 \".$time.\" and tc.category_id>0  group by tc.category_id having cc>{$weightmin} order by cc desc limit {$limit}\";\r\n\r\n	if( $rsa = getRows($q) ){\r\n		$max = $rsa[0][\'cc\'];\r\n		$min = $rsa[count($rsa)-1][\'cc\'];\r\n		$x = 200; $y = 100; // 200%, 100%\r\n		$stepvalue = ($max - $min != 0) ? ($max - $min) / ($x - $y) : 1;\r\n		$row = array();\r\n		foreach ($rsa as $r) {\r\n			$weight = $y + round(($r[\'cc\']-$min) / $stepvalue);\r\n			$style = ($weight > $y) ? \' style=\"font-size:\'. $weight . \'%;\"\' : \'\';\r\n			$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($r[\'name\']) : hu.$linktosection.\"/\".strtolower($r[\'name\']);\r\n			$row[$r[\'title\']] = tag(htmlspecialchars($r[\'title\']),\'a\',\' href=\"\'.$path.\'\"\'.$style.\' title=\"\'.htmlspecialchars($r[\'title\']).\'\"\');\r\n		}\r\n		ksort($row);\r\n		return doTag(doLabel($label, $labeltag).doWrap($row, $wraptag, $break, $class, $breakclass), $cloudwraptag).n;\r\n	}\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_article_list($atts) {	return rss_uc_article_list($atts); }\r\nfunction rss_uc_article_list($atts) {\r\n	global $s, $c, $pg, $prefs, $thisarticle, $id, $has_article_tag;\r\n\r\n	$thisarticle22=$thisarticle; $s22=$s; $c22=$c; $pg22=$pg; $prefs22=$prefs; $id22=$id;\r\n\r\n	$actual_id = $id;\r\n	extract(lAtts(array(\r\n		\'section\'	=> $s,\r\n		\'category\'	=> $c,\r\n		\'andcategory\'	=> \'\',\r\n		\'categorylogic\'	=> \'and\',\r\n		\'usechildren\'	=> 0,\r\n		\'form\'		=> \'default\',\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'time\'		=> \'past\',\r\n		\'status\'	=> \'live\',\r\n		\'sort\'		=> \'uPosted desc\',\r\n		\'id\'		=> \'\',\r\n		\'hideself\'	=> \'1\',\r\n		\'filter\'	=> 0,\r\n		\'filterfield\'	=> \'\',\r\n		\'filtername\'	=> \'\',\r\n	),$atts));\r\n\r\n	$parent = \"\";\r\n	if( $usechildren ){\r\n		$rs = safe_rows(\"name\", \"txp_category\", \"parent=\'$category\' and name != \'root\'\");\r\n		if ($rs) {\r\n			$parent = $category;\r\n			$category = \"\";\r\n			foreach ($rs as $t) {\r\n				$category.= $t[\'name\'].\",\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if($id){\r\n		$rs = safe_row(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\", \"textpattern\", \"ID=\'\".intval($id).\"\'\");\r\n		if ($rs) {\r\n			extract($rs);\r\n			populateArticleData($rs);\r\n			$thisarticle[\'parentcat\'] = $parent;\r\n			$thisarticle[\'thiscat\'] = $category;\r\n		}\r\n		$article = fetch_form($form);\r\n		$has_article_tag = true;\r\n		$thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n		return $article;\r\n	}\r\n\r\n	$filtersql = \"\";\r\n	if ($filter && $filterfield && $filtername) {\r\n	    $subpath = preg_quote(preg_replace(\"/http:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu),\"/\");\r\n	    $req = preg_replace(\"/^$subpath/i\",\"/\",serverSet(\'REQUEST_URI\'));\r\n	    extract(chopUrl($req));\r\n	    if ($u2 == $filtername && $u4) {\r\n		$qt = (is_numeric($u4)) ? \"\" : \"\'\";\r\n		switch ($u3) {\r\n		    case \'lt\':\r\n			$filterop = \"<\"; break;\r\n		    case \'gt\':\r\n			$filterop = \" > \"; break;\r\n		    default:\r\n			$filterop = \" = \";\r\n		}\r\n		$filtersql = \" AND $filterfield $filterop \".$qt.doSlash($u4).$qt.\" \";\r\n	    }\r\n	}\r\n\r\n	$time = rssBuildTimeSql($time);\r\n	$sections=rssBuildSctSql($section);\r\n	$status=getStatusNum($status);\r\n\r\n	$qa0=array(); $qa1=array(); $cc=0;\r\n	foreach (explode(\',\', $category) as $category){ if ($category) $catsql[] = \" cat.name = \'\" . urldecode($category) . \"\' \"; }\r\n	$categories= isset($catsql) ? \' AND (\' . join(\' OR \', $catsql) . \') \' : \"\";\r\n\r\n	foreach(explode(\',\', $andcategory) as $andcategory){\r\n		$cc++;\r\n		if( $andcategory ){\r\n			$andcatsql[] = \" cat.name = \'\" . urldecode($andcategory) . \"\' \";\r\n		}\r\n	}\r\n	$andcategories = isset($andcatsql) ? \' AND (\' . join(\' OR \', $andcatsql) . \') \' : \"\";\r\n\r\n	if($categories || (! $categories && ! $andcategories)){\r\n		$q0 = \"SELECT DISTINCT t.ID FROM \".PFX.\"textpattern as t  LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id\r\n		LEFT JOIN \".PFX.\"txp_category as cat ON cat.id = tc.category_id\r\n		WHERE \".$sections.\" Status=$status \".$categories.$filtersql.$time;\r\n		$rsc = getRows($q0);	if($rsc){ foreach ($rsc as $a){ $qa0[]=$a[\'ID\']; } }\r\n	}\r\n	if($andcategories){\r\n		$q1 = \"SELECT DISTINCT t.ID FROM \".PFX.\"textpattern as t  LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id\r\n		LEFT JOIN \".PFX.\"txp_category as cat ON cat.id = tc.category_id\r\n		WHERE \".$sections.\" Status=$status \".$andcategories.$filtersql.$time;\r\n		if($cc>1){ $q1.=\" GROUP BY t.ID HAVING count(*)=$cc\"; }\r\n		$rsc = getRows($q1);	if($rsc){ foreach ($rsc as $a){ $qa1[]=$a[\'ID\']; } }\r\n	}\r\n\r\n	if((strtolower($categorylogic) == \'and\')&& $qa0 && $qa1){\r\n		$qa=array_intersect($qa0,$qa1);\r\n	}else{\r\n		$qa=array_unique(array_merge($qa0,$qa1));\r\n	}\r\n\r\n	if ($qa) {\r\n		$articles=isset($qa) ? \" AND ID in(\".join(\',\',$qa).\")\" :\"\";\r\n		if($hideself){ $articles.=\" AND ID<>\'{$thisarticle[\'thisid\']}\'\"; }\r\n\r\n		$total = safe_count(\'textpattern\',\"1=1 \" . $articles) - $offset;\r\n		$numPages = ceil($total/$limit);\r\n		$pg = (!$pg) ? 1 : $pg;\r\n		$pgoffset = $offset + (($pg - 1) * $limit).\', \';\r\n		// send paging info to txp:newer and txp:older\r\n		$pageout[\'pg\']		= $pg;\r\n		$pageout[\'numPages\']	= $numPages;\r\n		$pageout[\'s\']		= $s;\r\n		$pageout[\'c\']		= $c;\r\n		$pageout[\'total\']	= $total;\r\n\r\n		$GLOBALS[\'thispage\'] = $pageout;\r\n		$q2 = \"1=1 $time$articles ORDER BY $sort limit \" . doSlash($pgoffset.$limit);\r\n\r\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\", \"textpattern\", $q2);\r\n		if ($rs) {\r\n		    $count = 0;\r\n		    $articles = array();\r\n		    while($a = nextRow($rs)) {\r\n			++$count;\r\n			$comparing = $a[\'ID\'];\r\n			populateArticleData($a);\r\n			global $thisarticle, $uPosted, $limit;\r\n			$thisarticle[\'parentcat\'] = $parent;\r\n			$thisarticle[\'thiscat\'] = $category;\r\n			$thisarticle[\'is_first\'] = ($count == 1);\r\n			$thisarticle[\'is_last\'] = ($count == numRows($rs));\r\n			// define the article form\r\n			$article = ($prefs[\'allow_form_override\'] && $a[\'override_form\']) ? fetch_form($a[\'override_form\']) : fetch_form($form);\r\n			if (!$hideself || $comparing != $actual_id) {$articles[] = parse($article);}\r\n\r\n			// sending these to paging_link(); Required?\r\n			$uPosted = $a[\'uPosted\'];\r\n			$limit = $limit;\r\n		    }\r\n		    $has_article_tag = true;\r\n		    $thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n		    return join(\'\',$articles);\r\n		}\r\n	}\r\n	$thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_related($atts){	return rss_uc_related($atts); }\r\nfunction rss_uc_related($atts){\r\n	global $id,$thisarticle,$s;\r\n	extract(lAtts(array(\r\n		\'section\'	=> $s,\r\n		\'form\'		=> \'default\',\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'time\'		=> \'past\',\r\n		\'sort\'		=> \'uPosted desc\',\r\n	),$atts));\r\n\r\n	$cats = array();\r\n	$rsc = getRows(\"SELECT c.title, c.name FROM \".PFX.\"textpattern_category as tc\r\n		LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id\r\n		WHERE article_id=\".$thisarticle[\'thisid\']);\r\n\r\n	if ($rsc) {\r\n		foreach ($rsc as $a) {\r\n			extract($a);		//FIXME\r\n			$cats[$name]=$name;\r\n		}\r\n	}\r\n\r\n	return rss_uc_article_list(\r\n	    array(\r\n		\'section\'	=> $section,\r\n		\'category\'	=> implode(\',\', $cats),\r\n		\'form\'		=> $form,\r\n		\'limit\'		=> $limit,\r\n		\'offset\'	=> $offset,\r\n		\'time\'		=> $time,\r\n		\'sort\'		=> $sort)\r\n	    );\r\n}\r\n\r\nfunction rss_if_article_unlimited_category($atts, $thing){	return rss_if_article_uc($atts, $thing); }\r\nfunction rss_if_article_uc($atts, $thing) {\r\n	global $thisarticle;\r\n	assert_article();\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n	),$atts));\r\n\r\n	if( $name ){\r\n		$name = getRows(\"SELECT name FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE article_id=\".$thisarticle[\'thisid\'].\" AND c.name IN(\'\".join(\"\',\'\", doSlash(do_list_unique($name))).\"\') limit 1\");\r\n	}\r\n	return parse($thing, $name);\r\n}\r\n\r\n\r\nfunction rss_sct_permlink($atts, $thing) {\r\n	global $thisarticle, $c;\r\n	extract(lAtts(array(\r\n		\'isparent\'	=> 0,\r\n		\'useparent\'	=> 0,\r\n		\'findparent\'	=> 0,\r\n		\'inparents\'	=> \'\',\r\n		\'category\'	=> $c,\r\n		\'id\'		=> \'\',\r\n	),$atts));\r\n\r\n	if ($useparent) {\r\n		$rs = fetch(\"parent\",\"txp_category\",\"name\",$category);\r\n		if ($rs && $rs != \'root\') {\r\n			$category = $rs;\r\n		}\r\n	}\r\n\r\n	$urltitle = $thisarticle[\'url_title\'];\r\n	$sct = $thisarticle[\'section\'];\r\n\r\n	if ($id) {\r\n	    $article = safe_row(\r\n		\"*,ID as thisid, unix_timestamp(Posted) as posted, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\",\r\n		\"textpattern\",\r\n		\'ID=\'.intval($id));\r\n		$urltitle = $article[\'url_title\'];\r\n		$sct = $article[\'Section\'];\r\n	}\r\n\r\n	$catlink = ($category) ? \"$category/\" : \"\";\r\n	$catlink = (isset($thisarticle[\'thiscat\']) && $thisarticle[\'thiscat\'] && $thisarticle[\'thiscat\'] != $c) ? $thisarticle[\'thiscat\'].\"/\" : $catlink;\r\n	$catlink = ($isparent) ? $thisarticle[\'parentcat\'].\"/\" : $catlink;\r\n\r\n	if ($findparent) {\r\n	    $rsc = safe_rows(\"category_id\", \"textpattern_category\", \"article_id=\".$thisarticle[\'thisid\']);\r\n\r\n	    if ($rsc) {\r\n		$thecats = array();\r\n		foreach ($rsc as $cat) {\r\n		    $thecats[] = $cat[\'category_id\'];\r\n		}\r\n		$rs = safe_rows(\"parent\", \"txp_category\", \"id in (\".implode(\",\",$thecats).\")\");\r\n		if ($rs) {\r\n		    foreach ($rs as $t) {\r\n			$linkcat = $t[\'parent\'];\r\n			if (in_array($linkcat, explode(\",\",$inparents))) {\r\n			   $catlink = $linkcat.\"/\";\r\n			}\r\n		    }\r\n		}\r\n	    }\r\n	}\r\n	$url = hu.$sct.\"/$catlink\".$urltitle;\r\n	return tag(parse($thing),\'a\',\' href=\"\'.$url.\'\" title=\"\'.gTxt(\'permanent_link\').\'\"\');\r\n}\r\n\r\n//=============================================================\r\nfunction rssBuildSctSql($section) {\r\n	if ($section) {\r\n	    foreach (do_list_unique($section) as $section) {\r\n		switch (substr(trim($section), 0, 1)) {\r\n		    case \'*\':\r\n			$sctsql[] = \" (section like \'%\') \";\r\n		    break;\r\n		    case \'!\':\r\n			$notsctsql[] = \" AND (section != \'\" . doSlash(str_replace(\'!\',\'\',$section)) . \"\') \";\r\n		    break;\r\n		    default:\r\n			$sctsql[] = \" (section = \'\" . doSlash($section) . \"\') \";\r\n		    break;\r\n		}\r\n	    }\r\n	    $sections = \' 1=1 AND (\' . join(\' OR \', $sctsql) . @join(\' AND \', $notsctsql) . \') AND \';\r\n	} else {\r\n	    $sections=\'\';\r\n	}\r\n	return $sections;\r\n}\r\n\r\nfunction rssBuildTimeSql($time) {\r\n	global $prefs;\r\n	switch ($time) {\r\n	    case \'any\':\r\n		$time = \"\"; break;\r\n	    case \'future\':\r\n		$time = \" and Posted > now()\"; break;\r\n	    default:\r\n		$time = \" and Posted <= now()\";\r\n	}\r\n	if(! $prefs[\'publish_expired_articles\']){ $time.=\" and (Expires IS NULL or Expires > now() or Expires = 0) \"; }\r\n	return $time;\r\n}\r\n\r\nfunction rss_multiTreeSelectInput($selectname=\"\", $array=\"\", $value=\"\") {\r\n	global $rss_unlim_sel_size;\r\n	$out = \'<select id=\"rss_uc_multiselect_id\" name=\"\'.$selectname.\'[]\" class=\"list\" size=\"\'.$rss_unlim_sel_size.\'\" multiple>\';\r\n	foreach ($array as $a) {\r\n		extract($a);\r\n		if( $name==\'root\' ){ continue; }\r\n		$selected = in_array($id, $value, true) ? \' selected=\"selected\"\' : \'\';\r\n		$name = htmlspecialchars($name);\r\n		$sp = ($level > 0) ? str_repeat(sp.sp.sp, $level) : \'\';\r\n		$out .= \'<option value=\"\'.$id.\'\"\'.$selected.\'>\'.$sp.addslashes($title).\'</option>\';\r\n	}\r\n	$out .= \'</select>\';\r\n	return $out;\r\n}\r\n\r\n\r\n// admin side _________________________________\r\nfunction rss_uc_admin_article_save( $event=\'\', $step=\'\', $rs ){\r\n	//	echo \"/*\"; print_r($rs); echo \"*/\"; exit;\r\n	if ( $ID=intval($rs[\'ID\']) ) {\r\n		safe_delete(\"textpattern_category\", \"article_id=$ID\");\r\n		$vals = ps(\'Cats\');\r\n		if( is_array($vals) ){\r\n			foreach( array_unique($vals) as $val ){\r\n				safe_insert(\"textpattern_category\", \"category_id = \".intval($val).\", article_id=\'$ID\'\");\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\nfunction rss_uc_admin_articles_deleted( $event=\'\', $step=\'\', $ids ){\r\n	$ids = array_map(\'intval\', $ids);\r\n	if( $ids ){\r\n		$id = join(\',\', $ids);\r\n		safe_delete(\"textpattern_category\",\"article_id in($id)\");\r\n	}\r\n}\r\n\r\nfunction rss_uc_admin_categories_deleted( $event=\'\', $step=\'\', $ids ){\r\n	$ids = array_map(\'intval\', $ids);\r\n	if( $ids ){\r\n		$id = join(\',\', $ids);\r\n		safe_delete(\"textpattern_category\",\"category_id in($id)\");\r\n	}\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_pref($event, $step) {\r\n	global $prefs, $rss_unlim_sel_size, $rss_unlim_sel_parent, $rss_unlim_sel_sections;\r\n\r\n	if (!isset($rss_unlim_sel_size)) {\r\n		set_pref(\'rss_unlim_sel_size\', 5, \'rss_uc\', 1);\r\n	}\r\n	if (!isset($rss_unlim_sel_parent)) {\r\n		set_pref(\'rss_unlim_sel_parent\', \'\', \'rss_uc\', 1);\r\n	}\r\n	if (!isset($rss_unlim_sel_sections)) {\r\n		set_pref(\'rss_unlim_sel_sections\', \'\', \'rss_uc\', 1);\r\n	}\r\n	if (!isset($prefs[\'rss_unlim\'])) {\r\n		set_pref(\'rss_unlim\', \'\', \'rss_uc\', 1);\r\n		$qq = \'\';\r\n	}else{\r\n		$qq = $prefs[\'rss_unlim\'];\r\n	}\r\n\r\n\r\n	if (!$rss_unlim_sel_size){ $rss_unlim_sel_size=\'5\'; }\r\n\r\n	if (ps(\"save\")) {\r\n		pagetop(\"Unlimited Categories Prefs\", \"Preferences Saved\");\r\n		set_pref(\'rss_unlim_sel_size\', (int)ps(\'rss_unlim_sel_size\'), \'rss_uc\', 1);\r\n		set_pref(\'rss_unlim_sel_parent\', ps(\'rss_unlim_sel_parent\'), \'rss_uc\', 1);\r\n		set_pref(\'rss_unlim_sel_sections\', ps(\'rss_unlim_sel_sections\'), \'rss_uc\', 1);\r\n		$rss_unlim = (isset($_POST[\'rss_unlim\'])) ? implode(\',\', $_POST[\'rss_unlim\']) : \'\';\r\n		set_pref(\'rss_unlim\', $rss_unlim, \'rss_uc\', 1);\r\n		header(\"Location: index.php?event=rss_uc_admin_tab_pref\");\r\n	} else {\r\n		pagetop(\"Unlim Cats Prefs\");\r\n	}\r\n	if (ps(\"fix\")) {\r\n		safe_query(\"delete tc FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE c.id IS NULL\");\r\n	}\r\n\r\n	$rs = getRows(\"SELECT count(*) cc FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE c.id IS NULL\");\r\n	$rsq=$rs[0][\'cc\'];\r\n	if($rsq){ $chk=\"<b>Detect $rsq non-existent links</b>&nbsp;&nbsp;\".fInput(\"submit\",\"fix\",\"FIX IT\",\"publish\"); }else{ $chk=\"<b>Status OK</b>\"; }\r\n\r\n	$rs = getTree(\'root\', \'article\');\r\n	echo \"<center>\".\r\n	form(\r\n	startTable(\'rss-list\').\r\n	tr(tdcs(hed(\"Unlimited Categories Preferences\",1, array(\'class\' => \'txp-heading\')),2)).\r\n	tr(\r\n	    tda(gTxt(\'Config write article tab\')).tda(\"<div style=\'text-align:right;\'>[<a href=\'?event=plugin&step=plugin_help&name=rss_unlimited_categories\'>Plugin help</a>]</div>\")\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Select List Size:\')).tda(fInput(\"text\", \"rss_unlim_sel_size\", $rss_unlim_sel_size,\'edit\',\'\',\'\', 10), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Parent for display:\')).tda(treeSelectInput(\'rss_unlim_sel_parent\', $rs, $rss_unlim_sel_parent), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Display only in sections:\')).tda(fInput(\"text\", \"rss_unlim_sel_sections\", $rss_unlim_sel_sections,\'edit\',\'\',\'\', 80).\"<br />Sample: section1,section2\", \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Hide `category1` and `category2`:\')).tda(checkbox(\'rss_unlim[]\',\'hide12\',((strpos($qq,\'hide12\')===false)? 0:1) ), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Check rss_uc:\')).tda($chk)\r\n	).\r\n	tr(\r\n	    tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"rss_uc_admin_tab_pref\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\").tda(\'\')\r\n	).\r\n	endTable()).\"</center>\";\r\necho <<<EOF\r\n<style type=\"text/css\">\r\n#rss-list td{\r\n	padding: 5px;\r\n	border-bottom: 1px dotted #ccc;\r\n}\r\n#rss-list .edit{\r\n	width: 100%;\r\n}\r\n\r\ntr td:first-child {\r\n	min-width: 108px;\r\n	text-align:right;\r\n	vertical-align:middle;\r\n}\r\n</style>\r\nEOF;\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_category($event=\"\", $step=\"\") {\r\n	$rs = safe_rows(\"category_id, count(*) cc\", \"textpattern_category\", \"1=1 group by category_id\");\r\n	if( empty($rs) ){\r\n		return;\r\n	}\r\n	$ss = \":\";\r\n	foreach ($rs as $t){\r\n		$ss .= \"{$t[\'category_id\']}={$t[\'cc\']}:\";\r\n	}\r\n$js = <<<EOF\r\n<!-- rss_uc -->\r\n<script language=\"javascript\" type=\"text/javascript\"><!--\r\nvar rss_ss = \"$ss\";\r\nvar rss_all, rss_pp, rss_tt;\r\nrss_all = document.evaluate(\"//input[@name=\\\"selected[]\\\"]\", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);\r\n\r\nfor (var i = 0; i < rss_all.snapshotLength; i++) {\r\n	rss_tt = rss_all.snapshotItem(i);\r\n	rss_pp = rss_tt.parentNode;\r\n\r\n	rr = new RegExp(\"^.*:\"+rss_tt.value+\"=\", \"g\");\r\n	se = rss_ss.replace(rr, \"\");\r\n	se = se.replace(/:.*$/g, \"\");\r\n\r\n	var spanTag = document.createElement(\"span\");\r\n	spanTag.className = \"rss_uc_num\";\r\n	spanTag.innerHTML = se;\r\n	rss_pp.insertBefore(spanTag, rss_tt);\r\n}\r\n-->\r\n</script>\r\n<style type=\"text/css\">\r\n.rss_uc_num {\r\n	width: 25px;\r\n	display: inline-block;\r\n	text-align: right;\r\n	margin-right: 5px;\r\n}\r\n</style>\r\n<!-- /rss_uc -->\r\nEOF;\r\necho n.$js;\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_article($event, $step) {\r\n	global $prefs, $rss_unlim_sel_parent, $rss_unlim_sel_sections;\r\n	extract(gpsa(array(\'view\', \'from_view\', \'step\')));\r\n	if( empty($view) ){\r\n		$view = \"text\";\r\n	}\r\n\r\n	// store selected categories in hidden field during preview/html\r\n	if ($view == \'preview\' or $view == \'html\')  {\r\n		// if nav is text-to-preview or -html, get cats from multi-select box\r\n		if ($from_view == \'text\') {\r\n			$cats_array = ps(\'Cats\');\r\n			$cats_list = (!empty($cats_array)) ? implode(\',\', $cats_array) : \'-1\';\r\n			$cats_store = hInput(\'cats_store\', $cats_list);\r\n			echo dom_attach(\'article_form\', $cats_store);\r\n		}\r\n		// if nav is preview-to-html or vice versa, get cats from hidden field\r\n		if ($from_view == \'preview\' or $from_view == \'html\') {\r\n			$cats_list = ps(\'cats_store\');\r\n			if (!empty($cats_list)) {\r\n				$cats_store = hInput(\'cats_store\', $cats_list);\r\n				echo dom_attach(\'article_form\', $cats_store);\r\n			}\r\n		}\r\n		return;\r\n	}\r\n\r\n	// use stored categories if available (and applicable)\r\n	$rsc = array();\r\n	if ($view == \'text\' and ($from_view == \'preview\' or $from_view == \'html\')) {\r\n		$cats_list = ps(\'cats_store\');\r\n		if (!empty($cats_list)) {\r\n			$rsc = explode(\',\', $cats_list);\r\n		}\r\n	}\r\n	if( empty($rsc) ){\r\n		$ID = empty($GLOBALS[\'ID\']) ? intval(gps(\'ID\')) : intval($GLOBALS[\'ID\']);\r\n		if ($ID){\r\n			$rsc = safe_column_num(\"category_id\", \"textpattern_category\", \"article_id=\".$ID);\r\n		}\r\n	}\r\n\r\n\r\n	if( empty($rss_unlim_sel_parent) ){ $rss_unlim_sel_parent=\'root\'; }\r\n	$rs = getTree($rss_unlim_sel_parent, \'article\');\r\n	$mtsi = \"\";\r\n	if ($rs) {\r\n		$mtsi = rss_multiTreeSelectInput(\'Cats\', $rs, $rsc);\r\n	}\r\n\r\n	$catsel = ($view == \'text\') ? (br.$mtsi.\r\n		\'<p>[\'.eLink(\'category\',\'\',\'\',\'\',gTxt(\'edit\')).\']&nbsp;&nbsp;&nbsp;&nbsp;[<a href=\"#\" onClick=\"rss_deselect()\">Deselect all</a>]</p>\'\r\n	) : \'\';\r\n\r\n	global $app_mode;\r\n	if( $app_mode == \'async\' ){\r\n		echo \"\\n// rss_uc async mode 2do..\\n\\n\"; return;\r\n	}\r\n\r\n// write tab customize\r\n	$rss_title = gTxt(\'categories\');\r\n	$js0 = \"\";\r\n	if(isset($prefs[\'rss_unlim\']) && strpos($prefs[\'rss_unlim\'],\'hide12\')!==false){\r\n		$js0 .= \'	$(\"#categories_group\").hide();\'.n;\r\n	}\r\n\r\nif($rss_unlim_sel_sections){\r\n$js0 .= <<<EOF0\r\n	$(\"#section\").change(function () { \r\n		rss_uc_sections=\",$rss_unlim_sel_sections,\";\r\n		if(rss_uc_sections.search(\",\"+document.getElementById(\'section\').value+\",\") == -1 ){\r\n			$(\"#rss-uc\").hide();\r\n		}else{\r\n			$(\"#rss-uc\").show();\r\n		}\r\n	}).change();\r\n\r\nEOF0;\r\n}\r\n\r\n$js = <<<EOF\r\n<!-- rss_uc -->\r\n<script language=\"javascript\" type=\"text/javascript\"><!--\r\n$(document).ready(function() {\r\n	rss_js = \'<section class=\"txp-details\" id=\"rss-uc\" aria-labelledby=\"rss-uc-label\"><h3 id=\"rss-uc-label\">{$rss_title}</h3><div id=\"rss-uc-content\" class=\"toggle\" role=\"group\" aria-expanded=\"true\">\'+\'$catsel\'+\'</div></section>\';\r\n	$( \"#txp-write-sort-group\" ).after( rss_js );\r\n$js0\r\n});\r\nfunction rss_deselect(){\r\n	document.getElementById(\'rss_uc_multiselect_id\').selectedIndex=-1;\r\n	return false;\r\n}\r\n\r\n-->\r\n</script>\r\n<!-- /rss_uc -->\r\nEOF;\r\necho n.$js;\r\n}\r\n', 'if (@txpinterface == \'admin\') {\r\n	if ( !(getRows(\"SHOW TABLES LIKE \'\".PFX.\"textpattern_category\'\")) ) {\r\n		safe_create(\"textpattern_category\", \"`article_id` int(11) NOT NULL default \'0\', `category_id` int(6) NOT NULL default \'0\', UNIQUE KEY  (`article_id`,`category_id`)\");\r\n	}\r\n\r\n	register_callback(\'rss_uc_admin_tab_article\', \'article\');\r\n	register_callback(\'rss_uc_admin_tab_category\', \'category\');\r\n\r\n	add_privs(\'rss_uc_admin_tab_pref\', \'1,2\');\r\n	register_tab(\"extensions\", \"rss_uc_admin_tab_pref\", \"Unlim Cats\");\r\n	register_callback(\"rss_uc_admin_tab_pref\", \"rss_uc_admin_tab_pref\");\r\n	add_privs(\'plugin_prefs.rss_unlimited_categories\',\'1,2\');\r\n	register_callback(\'rss_uc_admin_tab_pref\', \'plugin_prefs.rss_unlimited_categories\');\r\n\r\n	register_callback(\"rss_uc_admin_article_save\", \"article_posted\");\r\n	register_callback(\"rss_uc_admin_article_save\", \"article_saved\");\r\n	register_callback(\"rss_uc_admin_articles_deleted\", \"articles_deleted\");\r\n	register_callback(\"rss_uc_admin_categories_deleted\", \"categories_deleted\");\r\n}else{\r\n	register_callback(\'rss_multi_url\',\'pretext\');\r\n	if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'rss_if_article_uc\')\r\n		->register(\'rss_uc_article_list\')\r\n		->register(\'rss_uc_related\')\r\n		->register(\'rss_uc_cloud\')\r\n		->register(\'rss_uc_filedunder\')\r\n		->register(\'rss_uc_list\')\r\n		->register(\'rss_uc_count\')\r\n		->register(\'rss_sct_permlink\')\r\n		// aliases\r\n		->register(\'rss_if_article_unlimited_category\')\r\n		->register(\'rss_unlimited_categories_article_list\')\r\n		->register(\'rss_unlimited_categories_related\')\r\n		->register(\'rss_unlimited_categories_cloud\')\r\n		->register(\'rss_unlimited_categories_filedunder\')\r\n		->register(\'rss_unlimited_categories_list\')\r\n		->register(\'rss_unlimited_category_count\')\r\n		;\r\n	}\r\n}\r\n\r\n\r\n\r\n\r\n// public\r\n\r\nfunction rss_multi_url() {\r\n  global $attach_titles_to_permalinks,$permlink_mode,$pretext;\r\n\r\n  // set messy variables\r\n  $out =  makeOut(\'id\',\'s\',\'c\',\'q\',\'pg\',\'p\',\'month\',\'author\');\r\n  if (!$out[\'id\'] && !$out[\'s\'] && !(txpinterface==\'css\')) {\r\n    $subpath = preg_quote(preg_replace(\"/http:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu),\"/\");\r\n    $req = preg_replace(\"/^$subpath/i\",\"/\",serverSet(\'REQUEST_URI\'));\r\n    extract(chopUrl($req));\r\n\r\n    if (!empty($u1)) {\r\n\r\n      switch($u1) {\r\n\r\n	case \'atom\':	include txpath.\'/publish/atom.php\'; exit(atom());\r\n	case \'rss\':		include txpath.\'/publish/rss.php\'; exit(rss());\r\n	default:\r\n		     if ($permlink_mode == \"section_id_title\" || $permlink_mode = \"section_title\") {\r\n			if (empty($u2)) {\r\n			    $out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    $is_404 = empty($out[\'s\']);\r\n			}else{\r\n			    if (isset($u2)) {\r\n				$rs = lookupByTitleSection($u2,$u1);\r\n				$out[\'id\'] = @$rs[\'ID\'];\r\n				$_GET[\'id\'] = @$rs[\'ID\'];\r\n				$out[\'s\'] = @$rs[\'Section\'];\r\n				$_GET[\'s\'] = @$rs[\'Section\'];\r\n				$is_404 = empty($out[\'s\']);\r\n				if(!$out[\'s\'] && empty($u3)) {\r\n				    $out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : NULL;\r\n				    $_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 :NULL;\r\n				    if (!empty($out[\'c\'])) {\r\n					$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : NULL;\r\n					$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : NULL;\r\n				    }\r\n				    $is_404 = empty($out[\'c\']);\r\n				}\r\n			    }\r\n			    if (!$out[\'id\'] && isset($u3)) {\r\n				$rs = lookupByTitleSection($u3,$u1);\r\n				if (empty($rs[\'ID\'])) {\r\n				    $out[\'pg\'] = $u3;\r\n				    $is_404 = (empty($out[\'s\']) or empty($out[\'id\']));\r\n				} else {\r\n				    $out[\'id\'] = @$rs[\'ID\'];\r\n				    $out[\'s\'] = @$rs[\'Section\'];\r\n				    $out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				    $_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				    $_GET[\'s\'] = @$rs[\'Section\'];\r\n				    $_GET[\'id\'] = @$rs[\'ID\'];\r\n				}\r\n			    }\r\n			    if (!empty($u4)) {\r\n				$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n				$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    }\r\n			    if ($out[\'id\'] or $out[\'c\']) {\r\n				$out[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n				$out[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				$_GET[\'c\'] = (ckEx(\'category\',$u2)) ? $u2 : \'\';\r\n				$_GET[\'s\'] = (ckEx(\'section\',$u1)) ? $u1 : \'\';\r\n			    } else {\r\n				$is_404 = (empty($out[\'s\']) or empty($out[\'id\']));\r\n			    }\r\n			}\r\n		    }\r\n		}\r\n	    }\r\n	}\r\n    }\r\n\r\n\r\n//------------------------------------------------------------- TAGS\r\nfunction rss_unlimited_categories_filedunder($atts){	return rss_uc_filedunder($atts); }\r\nfunction rss_uc_filedunder($atts) {\r\n	global $thisarticle;\r\n	extract(lAtts(array(\r\n		\'linktosection\'	=> $thisarticle[\'section\'],\r\n		\'delim\'		=> \'/\',\r\n		\'suffix\'	=> \'\',\r\n		\'linked\'	=> 1,\r\n		\'parent\'	=> \'\',\r\n		\'usemessy\'	=> 0,\r\n		\'sort\'		=> \'title asc\',\r\n\r\n		\'wraptag\'	=> \'\',\r\n		\'class\'		=> \'\',\r\n		\'break\'		=> \', \',\r\n		\'breakclass\'	=> \'\',\r\n		\'listwraptag\'	=> \'\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n	),$atts));\r\n\r\n	$parents = ($parent) ? \" AND c.parent IN(\'\".join(\"\',\'\", doSlash(do_list_unique($parent))).\"\')\" : \"\";\r\n\r\n	$cats = array();\r\n	$rsc = getRows(\"SELECT c.title, c.name FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE article_id=\".$thisarticle[\'thisid\'].$parents.\" ORDER BY $sort\");\r\n	if ($rsc) {\r\n		foreach ($rsc as $b) {\r\n			if($linked==1){\r\n				$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.$linktosection.$delim.strtolower($b[\'name\']).$suffix;\r\n				$cats[]=tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($b[\'title\']).\'\"\');\r\n			}elseif($linked==2){\r\n				$cats[]=strtolower($b[\'name\']);\r\n			}elseif($linked==3){\r\n				$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.strtolower($b[\'name\']).$suffix;\r\n				$cats[]=tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($b[\'title\']).\'\"\');\r\n			}else{\r\n				$cats[]=htmlspecialchars($b[\'title\']);\r\n			}\r\n		}\r\n		return doTag(doLabel($label, $labeltag).doWrap($cats, $wraptag, $break, $class, $breakclass), $listwraptag);\r\n	}\r\n	return \'\';\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_list($atts){	return rss_uc_list($atts); }\r\nfunction rss_uc_list($atts) {\r\n	global $c;	// Mod: activeclass attribute\r\n	extract(lAtts(array(\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n		\'parent\'	=> \'\',\r\n		\'showcount\'	=> 1,\r\n		\'showallcount\'	=> 0,\r\n		\'allcountlabel\'	=> \'All\',\r\n		\'countlinked\'	=> 1,\r\n		\'linktosection\'	=> \'article\',\r\n		\'usemessy\'	=> 0,\r\n		\'listwraptag\'	=> \'\',\r\n		\'break\'		=> \'li\',\r\n		\'wraptag\'	=> \'ul\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n\r\n		\'class\'		=> \'\',\r\n		\'breakclass\'	=> \'\',\r\n		\'sort\'		=> \'title asc\',\r\n		\'activeclass\'	=> \'\',\r\n	),$atts));\r\n\r\n	$sections = rssBuildSctSql($section);\r\n	$time = rssBuildTimeSql($time);\r\n\r\n	$prnt = ($parent) ? \" WHERE parent = \'$parent\' \" : \"\";\r\n	$rsc = getRows(\"SELECT distinct c.id, c.name, c.title FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id \".$prnt.\"ORDER BY {$sort} LIMIT {$offset},{$limit}\");\r\n\r\n      if ($rsc) {\r\n	$activeclass = ($activeclass) ? \" class=\\\"$activeclass\\\"\" :\"\";\r\n\r\n	$aq= \"SELECT c.category_id, count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as c ON t.ID = c.article_id WHERE \".$sections.\" t.Status = 4 \".$time.\" group by c.category_id\";\r\n	$uc_count = array();\r\n	if( $rsa = getRows($aq) ){\r\n		foreach( $rsa as $r ){\r\n			if( !empty($r[\'category_id\']) ){\r\n				$uc_count[$r[\'category_id\']] = $r[\'cc\'];\r\n			}\r\n		}\r\n	}\r\n\r\n	$row = array();\r\n	$total = 0;\r\n	foreach ($rsc as $b) {\r\n	    if ( !empty($uc_count[$b[\'id\']]) ) {\r\n	      $path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($b[\'name\']) : hu.$linktosection.\"/\".strtolower($b[\'name\']);\r\n	      $count = ($showcount) ? \" ({$uc_count[$b[\'id\']]})\" : \"\";\r\n	      $total += $uc_count[$b[\'id\']];\r\n	      $row[] = ($countlinked) ?\r\n		// Next two lines add activeclass to category link\r\n		tag(htmlspecialchars($b[\'title\'].$count),\'a\',\' href=\"\'.$path.\'\"\'.($c == strtolower($b[\'name\']) ? $activeclass : \'\').\' title=\"\'.htmlspecialchars($b[\'title\']).\'\"\') :\r\n		tag(htmlspecialchars($b[\'title\']),\'a\',\' href=\"\' . $path . \'\"\'.($c == strtolower($b[\'name\']) ? $activeclass : \'\').\' title=\"\' . htmlspecialchars($b[\'title\']) . \'\"\') . $count;\r\n	    }\r\n	}\r\n	if ($showallcount) {\r\n	  $path = hu.$linktosection.\"/\".$parent;\r\n	  $count = ($showcount) ? \" (\".$total.\") \" : \"\";\r\n	  $row[]=($countlinked) ?\r\n	    tag(htmlspecialchars($allcountlabel.$count),\'a\',\' href=\"\'.$path.\'\" title=\"\'.htmlspecialchars($allcountlabel).\'\"\') :\r\n	    tag(htmlspecialchars($allcountlabel),\'a\',\' href=\"\' . $path . \'\" title=\"\' . htmlspecialchars($allcountlabel) . \'\"\') . $count;\r\n	}\r\n	return doTag(doLabel($label, $labeltag).doWrap($row, $wraptag, $break, $class, $breakclass), $listwraptag).n;\r\n      }\r\n      return \'\';\r\n}\r\n\r\n\r\nfunction rss_unlimited_category_count($atts){	return rss_uc_count($atts); }\r\nfunction rss_uc_count($atts) {\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'id\'		=> \'\',\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n	),$atts));\r\n	if( $name && !$id ){\r\n		$id = safe_field(\'id\', \'txp_category\', \"name=\'\".doSlash($name).\"\'\");\r\n	}\r\n	if( $id ){\r\n		$sections=rssBuildSctSql($section);\r\n		$time = rssBuildTimeSql($time);\r\n		$cc = getThing(\"SELECT count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as c ON t.ID = c.article_id WHERE \".$sections.\" c.category_id = \'{$id}\' and t.Status = 4 \".$time);\r\n		return $cc;\r\n	}\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_cloud($atts){ return rss_uc_cloud($atts); }\r\nfunction rss_uc_cloud($atts) {\r\n	extract(lAtts(array(\r\n		\'section\'	=> \'\',\r\n		\'time\'		=> \'past\',\r\n		\'limit\'		=> 999,\r\n		\'weightmin\'	=> 0,\r\n		\'parent\'	=> \'\',			// ?\r\n		\'linktosection\'	=> \'article\',\r\n		\'usemessy\'	=> 0,\r\n		\'sort\'		=> \'title asc\',		// ?\r\n\r\n		\'cloudwraptag\'	=> \'div\',\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \', \',\r\n		\'class\'		=> \'\',\r\n		\'breakclass\'	=> \'\',\r\n		\'label\'		=> \'\',\r\n		\'labeltag\'	=> \'\',\r\n	),$atts));\r\n\r\n	$sections = rssBuildSctSql($section);\r\n	$time = rssBuildTimeSql($time);\r\n//	$parents = ($parent) ? \" WHERE c.parent IN(\'\".join(\"\',\'\", doSlash(do_list_unique($parent))).\"\')\" : \"\";\r\n\r\n	$q = \"SELECT tc.category_id, c.name, c.title, c.parent, count(*) cc FROM \".PFX.\"textpattern as t LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id  LEFT JOIN \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE \".$sections.\" t.Status = 4 \".$time.\" and tc.category_id>0  group by tc.category_id having cc>{$weightmin} order by cc desc limit {$limit}\";\r\n\r\n	if( $rsa = getRows($q) ){\r\n		$max = $rsa[0][\'cc\'];\r\n		$min = $rsa[count($rsa)-1][\'cc\'];\r\n		$x = 200; $y = 100; // 200%, 100%\r\n		$stepvalue = ($max - $min != 0) ? ($max - $min) / ($x - $y) : 1;\r\n		$row = array();\r\n		foreach ($rsa as $r) {\r\n			$weight = $y + round(($r[\'cc\']-$min) / $stepvalue);\r\n			$style = ($weight > $y) ? \' style=\"font-size:\'. $weight . \'%;\"\' : \'\';\r\n			$path = ($usemessy) ? hu.\"?s=\".$linktosection.\"&amp;c=\".strtolower($r[\'name\']) : hu.$linktosection.\"/\".strtolower($r[\'name\']);\r\n			$row[$r[\'title\']] = tag(htmlspecialchars($r[\'title\']),\'a\',\' href=\"\'.$path.\'\"\'.$style.\' title=\"\'.htmlspecialchars($r[\'title\']).\'\"\');\r\n		}\r\n		ksort($row);\r\n		return doTag(doLabel($label, $labeltag).doWrap($row, $wraptag, $break, $class, $breakclass), $cloudwraptag).n;\r\n	}\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_article_list($atts) {	return rss_uc_article_list($atts); }\r\nfunction rss_uc_article_list($atts) {\r\n	global $s, $c, $pg, $prefs, $thisarticle, $id, $has_article_tag;\r\n\r\n	$thisarticle22=$thisarticle; $s22=$s; $c22=$c; $pg22=$pg; $prefs22=$prefs; $id22=$id;\r\n\r\n	$actual_id = $id;\r\n	extract(lAtts(array(\r\n		\'section\'	=> $s,\r\n		\'category\'	=> $c,\r\n		\'andcategory\'	=> \'\',\r\n		\'categorylogic\'	=> \'and\',\r\n		\'usechildren\'	=> 0,\r\n		\'form\'		=> \'default\',\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'time\'		=> \'past\',\r\n		\'status\'	=> \'live\',\r\n		\'sort\'		=> \'uPosted desc\',\r\n		\'id\'		=> \'\',\r\n		\'hideself\'	=> \'1\',\r\n		\'filter\'	=> 0,\r\n		\'filterfield\'	=> \'\',\r\n		\'filtername\'	=> \'\',\r\n	),$atts));\r\n\r\n	$parent = \"\";\r\n	if( $usechildren ){\r\n		$rs = safe_rows(\"name\", \"txp_category\", \"parent=\'$category\' and name != \'root\'\");\r\n		if ($rs) {\r\n			$parent = $category;\r\n			$category = \"\";\r\n			foreach ($rs as $t) {\r\n				$category.= $t[\'name\'].\",\";\r\n			}\r\n		}\r\n	}\r\n\r\n	if($id){\r\n		$rs = safe_row(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\", \"textpattern\", \"ID=\'\".intval($id).\"\'\");\r\n		if ($rs) {\r\n			extract($rs);\r\n			populateArticleData($rs);\r\n			$thisarticle[\'parentcat\'] = $parent;\r\n			$thisarticle[\'thiscat\'] = $category;\r\n		}\r\n		$article = fetch_form($form);\r\n		$has_article_tag = true;\r\n		$thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n		return $article;\r\n	}\r\n\r\n	$filtersql = \"\";\r\n	if ($filter && $filterfield && $filtername) {\r\n	    $subpath = preg_quote(preg_replace(\"/http:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu),\"/\");\r\n	    $req = preg_replace(\"/^$subpath/i\",\"/\",serverSet(\'REQUEST_URI\'));\r\n	    extract(chopUrl($req));\r\n	    if ($u2 == $filtername && $u4) {\r\n		$qt = (is_numeric($u4)) ? \"\" : \"\'\";\r\n		switch ($u3) {\r\n		    case \'lt\':\r\n			$filterop = \"<\"; break;\r\n		    case \'gt\':\r\n			$filterop = \" > \"; break;\r\n		    default:\r\n			$filterop = \" = \";\r\n		}\r\n		$filtersql = \" AND $filterfield $filterop \".$qt.doSlash($u4).$qt.\" \";\r\n	    }\r\n	}\r\n\r\n	$time = rssBuildTimeSql($time);\r\n	$sections=rssBuildSctSql($section);\r\n	$status=getStatusNum($status);\r\n\r\n	$qa0=array(); $qa1=array(); $cc=0;\r\n	foreach (explode(\',\', $category) as $category){ if ($category) $catsql[] = \" cat.name = \'\" . urldecode($category) . \"\' \"; }\r\n	$categories= isset($catsql) ? \' AND (\' . join(\' OR \', $catsql) . \') \' : \"\";\r\n\r\n	foreach(explode(\',\', $andcategory) as $andcategory){\r\n		$cc++;\r\n		if( $andcategory ){\r\n			$andcatsql[] = \" cat.name = \'\" . urldecode($andcategory) . \"\' \";\r\n		}\r\n	}\r\n	$andcategories = isset($andcatsql) ? \' AND (\' . join(\' OR \', $andcatsql) . \') \' : \"\";\r\n\r\n	if($categories || (! $categories && ! $andcategories)){\r\n		$q0 = \"SELECT DISTINCT t.ID FROM \".PFX.\"textpattern as t  LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id\r\n		LEFT JOIN \".PFX.\"txp_category as cat ON cat.id = tc.category_id\r\n		WHERE \".$sections.\" Status=$status \".$categories.$filtersql.$time;\r\n		$rsc = getRows($q0);	if($rsc){ foreach ($rsc as $a){ $qa0[]=$a[\'ID\']; } }\r\n	}\r\n	if($andcategories){\r\n		$q1 = \"SELECT DISTINCT t.ID FROM \".PFX.\"textpattern as t  LEFT JOIN \".PFX.\"textpattern_category as tc ON t.ID = tc.article_id\r\n		LEFT JOIN \".PFX.\"txp_category as cat ON cat.id = tc.category_id\r\n		WHERE \".$sections.\" Status=$status \".$andcategories.$filtersql.$time;\r\n		if($cc>1){ $q1.=\" GROUP BY t.ID HAVING count(*)=$cc\"; }\r\n		$rsc = getRows($q1);	if($rsc){ foreach ($rsc as $a){ $qa1[]=$a[\'ID\']; } }\r\n	}\r\n\r\n	if((strtolower($categorylogic) == \'and\')&& $qa0 && $qa1){\r\n		$qa=array_intersect($qa0,$qa1);\r\n	}else{\r\n		$qa=array_unique(array_merge($qa0,$qa1));\r\n	}\r\n\r\n	if ($qa) {\r\n		$articles=isset($qa) ? \" AND ID in(\".join(\',\',$qa).\")\" :\"\";\r\n		if($hideself){ $articles.=\" AND ID<>\'{$thisarticle[\'thisid\']}\'\"; }\r\n\r\n		$total = safe_count(\'textpattern\',\"1=1 \" . $articles) - $offset;\r\n		$numPages = ceil($total/$limit);\r\n		$pg = (!$pg) ? 1 : $pg;\r\n		$pgoffset = $offset + (($pg - 1) * $limit).\', \';\r\n		// send paging info to txp:newer and txp:older\r\n		$pageout[\'pg\']		= $pg;\r\n		$pageout[\'numPages\']	= $numPages;\r\n		$pageout[\'s\']		= $s;\r\n		$pageout[\'c\']		= $c;\r\n		$pageout[\'total\']	= $total;\r\n\r\n		$GLOBALS[\'thispage\'] = $pageout;\r\n		$q2 = \"1=1 $time$articles ORDER BY $sort limit \" . doSlash($pgoffset.$limit);\r\n\r\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\", \"textpattern\", $q2);\r\n		if ($rs) {\r\n		    $count = 0;\r\n		    $articles = array();\r\n		    while($a = nextRow($rs)) {\r\n			++$count;\r\n			$comparing = $a[\'ID\'];\r\n			populateArticleData($a);\r\n			global $thisarticle, $uPosted, $limit;\r\n			$thisarticle[\'parentcat\'] = $parent;\r\n			$thisarticle[\'thiscat\'] = $category;\r\n			$thisarticle[\'is_first\'] = ($count == 1);\r\n			$thisarticle[\'is_last\'] = ($count == numRows($rs));\r\n			// define the article form\r\n			$article = ($prefs[\'allow_form_override\'] && $a[\'override_form\']) ? fetch_form($a[\'override_form\']) : fetch_form($form);\r\n			if (!$hideself || $comparing != $actual_id) {$articles[] = parse($article);}\r\n\r\n			// sending these to paging_link(); Required?\r\n			$uPosted = $a[\'uPosted\'];\r\n			$limit = $limit;\r\n		    }\r\n		    $has_article_tag = true;\r\n		    $thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n		    return join(\'\',$articles);\r\n		}\r\n	}\r\n	$thisarticle=$thisarticle22; $s=$s22; $c=$c22; $pg=$pg22; $prefs=$prefs22; $id=$id22;\r\n}\r\n\r\n\r\nfunction rss_unlimited_categories_related($atts){	return rss_uc_related($atts); }\r\nfunction rss_uc_related($atts){\r\n	global $id,$thisarticle,$s;\r\n	extract(lAtts(array(\r\n		\'section\'	=> $s,\r\n		\'form\'		=> \'default\',\r\n		\'limit\'		=> 999,\r\n		\'offset\'	=> 0,\r\n		\'time\'		=> \'past\',\r\n		\'sort\'		=> \'uPosted desc\',\r\n	),$atts));\r\n\r\n	$cats = array();\r\n	$rsc = getRows(\"SELECT c.title, c.name FROM \".PFX.\"textpattern_category as tc\r\n		LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id\r\n		WHERE article_id=\".$thisarticle[\'thisid\']);\r\n\r\n	if ($rsc) {\r\n		foreach ($rsc as $a) {\r\n			extract($a);		//FIXME\r\n			$cats[$name]=$name;\r\n		}\r\n	}\r\n\r\n	return rss_uc_article_list(\r\n	    array(\r\n		\'section\'	=> $section,\r\n		\'category\'	=> implode(\',\', $cats),\r\n		\'form\'		=> $form,\r\n		\'limit\'		=> $limit,\r\n		\'offset\'	=> $offset,\r\n		\'time\'		=> $time,\r\n		\'sort\'		=> $sort)\r\n	    );\r\n}\r\n\r\nfunction rss_if_article_unlimited_category($atts, $thing){	return rss_if_article_uc($atts, $thing); }\r\nfunction rss_if_article_uc($atts, $thing) {\r\n	global $thisarticle;\r\n	assert_article();\r\n	extract(lAtts(array(\r\n		\'name\'		=> \'\',\r\n	),$atts));\r\n\r\n	if( $name ){\r\n		$name = getRows(\"SELECT name FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE article_id=\".$thisarticle[\'thisid\'].\" AND c.name IN(\'\".join(\"\',\'\", doSlash(do_list_unique($name))).\"\') limit 1\");\r\n	}\r\n	return parse($thing, $name);\r\n}\r\n\r\n\r\nfunction rss_sct_permlink($atts, $thing) {\r\n	global $thisarticle, $c;\r\n	extract(lAtts(array(\r\n		\'isparent\'	=> 0,\r\n		\'useparent\'	=> 0,\r\n		\'findparent\'	=> 0,\r\n		\'inparents\'	=> \'\',\r\n		\'category\'	=> $c,\r\n		\'id\'		=> \'\',\r\n	),$atts));\r\n\r\n	if ($useparent) {\r\n		$rs = fetch(\"parent\",\"txp_category\",\"name\",$category);\r\n		if ($rs && $rs != \'root\') {\r\n			$category = $rs;\r\n		}\r\n	}\r\n\r\n	$urltitle = $thisarticle[\'url_title\'];\r\n	$sct = $thisarticle[\'section\'];\r\n\r\n	if ($id) {\r\n	    $article = safe_row(\r\n		\"*,ID as thisid, unix_timestamp(Posted) as posted, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\",\r\n		\"textpattern\",\r\n		\'ID=\'.intval($id));\r\n		$urltitle = $article[\'url_title\'];\r\n		$sct = $article[\'Section\'];\r\n	}\r\n\r\n	$catlink = ($category) ? \"$category/\" : \"\";\r\n	$catlink = (isset($thisarticle[\'thiscat\']) && $thisarticle[\'thiscat\'] && $thisarticle[\'thiscat\'] != $c) ? $thisarticle[\'thiscat\'].\"/\" : $catlink;\r\n	$catlink = ($isparent) ? $thisarticle[\'parentcat\'].\"/\" : $catlink;\r\n\r\n	if ($findparent) {\r\n	    $rsc = safe_rows(\"category_id\", \"textpattern_category\", \"article_id=\".$thisarticle[\'thisid\']);\r\n\r\n	    if ($rsc) {\r\n		$thecats = array();\r\n		foreach ($rsc as $cat) {\r\n		    $thecats[] = $cat[\'category_id\'];\r\n		}\r\n		$rs = safe_rows(\"parent\", \"txp_category\", \"id in (\".implode(\",\",$thecats).\")\");\r\n		if ($rs) {\r\n		    foreach ($rs as $t) {\r\n			$linkcat = $t[\'parent\'];\r\n			if (in_array($linkcat, explode(\",\",$inparents))) {\r\n			   $catlink = $linkcat.\"/\";\r\n			}\r\n		    }\r\n		}\r\n	    }\r\n	}\r\n	$url = hu.$sct.\"/$catlink\".$urltitle;\r\n	return tag(parse($thing),\'a\',\' href=\"\'.$url.\'\" title=\"\'.gTxt(\'permanent_link\').\'\"\');\r\n}\r\n\r\n//=============================================================\r\nfunction rssBuildSctSql($section) {\r\n	if ($section) {\r\n	    foreach (do_list_unique($section) as $section) {\r\n		switch (substr(trim($section), 0, 1)) {\r\n		    case \'*\':\r\n			$sctsql[] = \" (section like \'%\') \";\r\n		    break;\r\n		    case \'!\':\r\n			$notsctsql[] = \" AND (section != \'\" . doSlash(str_replace(\'!\',\'\',$section)) . \"\') \";\r\n		    break;\r\n		    default:\r\n			$sctsql[] = \" (section = \'\" . doSlash($section) . \"\') \";\r\n		    break;\r\n		}\r\n	    }\r\n	    $sections = \' 1=1 AND (\' . join(\' OR \', $sctsql) . @join(\' AND \', $notsctsql) . \') AND \';\r\n	} else {\r\n	    $sections=\'\';\r\n	}\r\n	return $sections;\r\n}\r\n\r\nfunction rssBuildTimeSql($time) {\r\n	global $prefs;\r\n	switch ($time) {\r\n	    case \'any\':\r\n		$time = \"\"; break;\r\n	    case \'future\':\r\n		$time = \" and Posted > now()\"; break;\r\n	    default:\r\n		$time = \" and Posted <= now()\";\r\n	}\r\n	if(! $prefs[\'publish_expired_articles\']){ $time.=\" and (Expires IS NULL or Expires > now() or Expires = 0) \"; }\r\n	return $time;\r\n}\r\n\r\nfunction rss_multiTreeSelectInput($selectname=\"\", $array=\"\", $value=\"\") {\r\n	global $rss_unlim_sel_size;\r\n	$out = \'<select id=\"rss_uc_multiselect_id\" name=\"\'.$selectname.\'[]\" class=\"list\" size=\"\'.$rss_unlim_sel_size.\'\" multiple>\';\r\n	foreach ($array as $a) {\r\n		extract($a);\r\n		if( $name==\'root\' ){ continue; }\r\n		$selected = in_array($id, $value, true) ? \' selected=\"selected\"\' : \'\';\r\n		$name = htmlspecialchars($name);\r\n		$sp = ($level > 0) ? str_repeat(sp.sp.sp, $level) : \'\';\r\n		$out .= \'<option value=\"\'.$id.\'\"\'.$selected.\'>\'.$sp.addslashes($title).\'</option>\';\r\n	}\r\n	$out .= \'</select>\';\r\n	return $out;\r\n}\r\n\r\n\r\n// admin side _________________________________\r\nfunction rss_uc_admin_article_save( $event=\'\', $step=\'\', $rs ){\r\n	//	echo \"/*\"; print_r($rs); echo \"*/\"; exit;\r\n	if ( $ID=intval($rs[\'ID\']) ) {\r\n		safe_delete(\"textpattern_category\", \"article_id=$ID\");\r\n		$vals = ps(\'Cats\');\r\n		if( is_array($vals) ){\r\n			foreach( array_unique($vals) as $val ){\r\n				safe_insert(\"textpattern_category\", \"category_id = \".intval($val).\", article_id=\'$ID\'\");\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\nfunction rss_uc_admin_articles_deleted( $event=\'\', $step=\'\', $ids ){\r\n	$ids = array_map(\'intval\', $ids);\r\n	if( $ids ){\r\n		$id = join(\',\', $ids);\r\n		safe_delete(\"textpattern_category\",\"article_id in($id)\");\r\n	}\r\n}\r\n\r\nfunction rss_uc_admin_categories_deleted( $event=\'\', $step=\'\', $ids ){\r\n	$ids = array_map(\'intval\', $ids);\r\n	if( $ids ){\r\n		$id = join(\',\', $ids);\r\n		safe_delete(\"textpattern_category\",\"category_id in($id)\");\r\n	}\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_pref($event, $step) {\r\n	global $prefs, $rss_unlim_sel_size, $rss_unlim_sel_parent, $rss_unlim_sel_sections;\r\n\r\n	if (!isset($rss_unlim_sel_size)) {\r\n		set_pref(\'rss_unlim_sel_size\', 5, \'rss_uc\', 1);\r\n	}\r\n	if (!isset($rss_unlim_sel_parent)) {\r\n		set_pref(\'rss_unlim_sel_parent\', \'\', \'rss_uc\', 1);\r\n	}\r\n	if (!isset($rss_unlim_sel_sections)) {\r\n		set_pref(\'rss_unlim_sel_sections\', \'\', \'rss_uc\', 1);\r\n	}\r\n	if (!isset($prefs[\'rss_unlim\'])) {\r\n		set_pref(\'rss_unlim\', \'\', \'rss_uc\', 1);\r\n		$qq = \'\';\r\n	}else{\r\n		$qq = $prefs[\'rss_unlim\'];\r\n	}\r\n\r\n\r\n	if (!$rss_unlim_sel_size){ $rss_unlim_sel_size=\'5\'; }\r\n\r\n	if (ps(\"save\")) {\r\n		pagetop(\"Unlimited Categories Prefs\", \"Preferences Saved\");\r\n		set_pref(\'rss_unlim_sel_size\', (int)ps(\'rss_unlim_sel_size\'), \'rss_uc\', 1);\r\n		set_pref(\'rss_unlim_sel_parent\', ps(\'rss_unlim_sel_parent\'), \'rss_uc\', 1);\r\n		set_pref(\'rss_unlim_sel_sections\', ps(\'rss_unlim_sel_sections\'), \'rss_uc\', 1);\r\n		$rss_unlim = (isset($_POST[\'rss_unlim\'])) ? implode(\',\', $_POST[\'rss_unlim\']) : \'\';\r\n		set_pref(\'rss_unlim\', $rss_unlim, \'rss_uc\', 1);\r\n		header(\"Location: index.php?event=rss_uc_admin_tab_pref\");\r\n	} else {\r\n		pagetop(\"Unlim Cats Prefs\");\r\n	}\r\n	if (ps(\"fix\")) {\r\n		safe_query(\"delete tc FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE c.id IS NULL\");\r\n	}\r\n\r\n	$rs = getRows(\"SELECT count(*) cc FROM \".PFX.\"textpattern_category as tc LEFT JOIN  \".PFX.\"txp_category as c ON tc.category_id = c.id WHERE c.id IS NULL\");\r\n	$rsq=$rs[0][\'cc\'];\r\n	if($rsq){ $chk=\"<b>Detect $rsq non-existent links</b>&nbsp;&nbsp;\".fInput(\"submit\",\"fix\",\"FIX IT\",\"publish\"); }else{ $chk=\"<b>Status OK</b>\"; }\r\n\r\n	$rs = getTree(\'root\', \'article\');\r\n	echo \"<center>\".\r\n	form(\r\n	startTable(\'rss-list\').\r\n	tr(tdcs(hed(\"Unlimited Categories Preferences\",1, array(\'class\' => \'txp-heading\')),2)).\r\n	tr(\r\n	    tda(gTxt(\'Config write article tab\')).tda(\"<div style=\'text-align:right;\'>[<a href=\'?event=plugin&step=plugin_help&name=rss_unlimited_categories\'>Plugin help</a>]</div>\")\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Select List Size:\')).tda(fInput(\"text\", \"rss_unlim_sel_size\", $rss_unlim_sel_size,\'edit\',\'\',\'\', 10), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Parent for display:\')).tda(treeSelectInput(\'rss_unlim_sel_parent\', $rs, $rss_unlim_sel_parent), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Display only in sections:\')).tda(fInput(\"text\", \"rss_unlim_sel_sections\", $rss_unlim_sel_sections,\'edit\',\'\',\'\', 80).\"<br />Sample: section1,section2\", \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Hide `category1` and `category2`:\')).tda(checkbox(\'rss_unlim[]\',\'hide12\',((strpos($qq,\'hide12\')===false)? 0:1) ), \' \')\r\n	).\r\n	tr(\r\n	    tda(gTxt(\'Check rss_uc:\')).tda($chk)\r\n	).\r\n	tr(\r\n	    tda(fInput(\"submit\",\"save\",gTxt(\"save_button\"),\"publish\").eInput(\"rss_uc_admin_tab_pref\").sInput(\'saveprefs\'), \" class=\\\"noline\\\"\").tda(\'\')\r\n	).\r\n	endTable()).\"</center>\";\r\necho <<<EOF\r\n<style type=\"text/css\">\r\n#rss-list td{\r\n	padding: 5px;\r\n	border-bottom: 1px dotted #ccc;\r\n}\r\n#rss-list .edit{\r\n	width: 100%;\r\n}\r\n\r\ntr td:first-child {\r\n	min-width: 108px;\r\n	text-align:right;\r\n	vertical-align:middle;\r\n}\r\n</style>\r\nEOF;\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_category($event=\"\", $step=\"\") {\r\n	$rs = safe_rows(\"category_id, count(*) cc\", \"textpattern_category\", \"1=1 group by category_id\");\r\n	if( empty($rs) ){\r\n		return;\r\n	}\r\n	$ss = \":\";\r\n	foreach ($rs as $t){\r\n		$ss .= \"{$t[\'category_id\']}={$t[\'cc\']}:\";\r\n	}\r\n$js = <<<EOF\r\n<!-- rss_uc -->\r\n<script language=\"javascript\" type=\"text/javascript\"><!--\r\nvar rss_ss = \"$ss\";\r\nvar rss_all, rss_pp, rss_tt;\r\nrss_all = document.evaluate(\"//input[@name=\\\"selected[]\\\"]\", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);\r\n\r\nfor (var i = 0; i < rss_all.snapshotLength; i++) {\r\n	rss_tt = rss_all.snapshotItem(i);\r\n	rss_pp = rss_tt.parentNode;\r\n\r\n	rr = new RegExp(\"^.*:\"+rss_tt.value+\"=\", \"g\");\r\n	se = rss_ss.replace(rr, \"\");\r\n	se = se.replace(/:.*$/g, \"\");\r\n\r\n	var spanTag = document.createElement(\"span\");\r\n	spanTag.className = \"rss_uc_num\";\r\n	spanTag.innerHTML = se;\r\n	rss_pp.insertBefore(spanTag, rss_tt);\r\n}\r\n-->\r\n</script>\r\n<style type=\"text/css\">\r\n.rss_uc_num {\r\n	width: 25px;\r\n	display: inline-block;\r\n	text-align: right;\r\n	margin-right: 5px;\r\n}\r\n</style>\r\n<!-- /rss_uc -->\r\nEOF;\r\necho n.$js;\r\n}\r\n\r\n\r\nfunction rss_uc_admin_tab_article($event, $step) {\r\n	global $prefs, $rss_unlim_sel_parent, $rss_unlim_sel_sections;\r\n	extract(gpsa(array(\'view\', \'from_view\', \'step\')));\r\n	if( empty($view) ){\r\n		$view = \"text\";\r\n	}\r\n\r\n	// store selected categories in hidden field during preview/html\r\n	if ($view == \'preview\' or $view == \'html\')  {\r\n		// if nav is text-to-preview or -html, get cats from multi-select box\r\n		if ($from_view == \'text\') {\r\n			$cats_array = ps(\'Cats\');\r\n			$cats_list = (!empty($cats_array)) ? implode(\',\', $cats_array) : \'-1\';\r\n			$cats_store = hInput(\'cats_store\', $cats_list);\r\n			echo dom_attach(\'article_form\', $cats_store);\r\n		}\r\n		// if nav is preview-to-html or vice versa, get cats from hidden field\r\n		if ($from_view == \'preview\' or $from_view == \'html\') {\r\n			$cats_list = ps(\'cats_store\');\r\n			if (!empty($cats_list)) {\r\n				$cats_store = hInput(\'cats_store\', $cats_list);\r\n				echo dom_attach(\'article_form\', $cats_store);\r\n			}\r\n		}\r\n		return;\r\n	}\r\n\r\n	// use stored categories if available (and applicable)\r\n	$rsc = array();\r\n	if ($view == \'text\' and ($from_view == \'preview\' or $from_view == \'html\')) {\r\n		$cats_list = ps(\'cats_store\');\r\n		if (!empty($cats_list)) {\r\n			$rsc = explode(\',\', $cats_list);\r\n		}\r\n	}\r\n	if( empty($rsc) ){\r\n		$ID = empty($GLOBALS[\'ID\']) ? intval(gps(\'ID\')) : intval($GLOBALS[\'ID\']);\r\n		if ($ID){\r\n			$rsc = safe_column_num(\"category_id\", \"textpattern_category\", \"article_id=\".$ID);\r\n		}\r\n	}\r\n\r\n\r\n	if( empty($rss_unlim_sel_parent) ){ $rss_unlim_sel_parent=\'root\'; }\r\n	$rs = getTree($rss_unlim_sel_parent, \'article\');\r\n	$mtsi = \"\";\r\n	if ($rs) {\r\n		$mtsi = rss_multiTreeSelectInput(\'Cats\', $rs, $rsc);\r\n	}\r\n\r\n	$catsel = ($view == \'text\') ? (br.$mtsi.\r\n		\'<p>[\'.eLink(\'category\',\'\',\'\',\'\',gTxt(\'edit\')).\']&nbsp;&nbsp;&nbsp;&nbsp;[<a href=\"#\" onClick=\"rss_deselect()\">Deselect all</a>]</p>\'\r\n	) : \'\';\r\n\r\n	global $app_mode;\r\n	if( $app_mode == \'async\' ){\r\n		echo \"\\n// rss_uc async mode 2do..\\n\\n\"; return;\r\n	}\r\n\r\n// write tab customize\r\n	$rss_title = gTxt(\'categories\');\r\n	$js0 = \"\";\r\n	if(isset($prefs[\'rss_unlim\']) && strpos($prefs[\'rss_unlim\'],\'hide12\')!==false){\r\n		$js0 .= \'	$(\"#categories_group\").hide();\'.n;\r\n	}\r\n\r\nif($rss_unlim_sel_sections){\r\n$js0 .= <<<EOF0\r\n	$(\"#section\").change(function () { \r\n		rss_uc_sections=\",$rss_unlim_sel_sections,\";\r\n		if(rss_uc_sections.search(\",\"+document.getElementById(\'section\').value+\",\") == -1 ){\r\n			$(\"#rss-uc\").hide();\r\n		}else{\r\n			$(\"#rss-uc\").show();\r\n		}\r\n	}).change();\r\n\r\nEOF0;\r\n}\r\n\r\n$js = <<<EOF\r\n<!-- rss_uc -->\r\n<script language=\"javascript\" type=\"text/javascript\"><!--\r\n$(document).ready(function() {\r\n	rss_js = \'<section class=\"txp-details\" id=\"rss-uc\" aria-labelledby=\"rss-uc-label\"><h3 id=\"rss-uc-label\">{$rss_title}</h3><div id=\"rss-uc-content\" class=\"toggle\" role=\"group\" aria-expanded=\"true\">\'+\'$catsel\'+\'</div></section>\';\r\n	$( \"#txp-write-sort-group\" ).after( rss_js );\r\n$js0\r\n});\r\nfunction rss_deselect(){\r\n	document.getElementById(\'rss_uc_multiselect_id\').selectedIndex=-1;\r\n	return false;\r\n}\r\n\r\n-->\r\n</script>\r\n<!-- /rss_uc -->\r\nEOF;\r\necho n.$js;\r\n}\r\n', '70bd2edef90c92842b264ced573b7b55', '', '', 5, 6, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('pat_amazon_isbn', 0, 'Patrick LEFEVRE', 'http://twitter.com/lowel', '0.1.1', 'Convert ISBN13 number & create a link to the corresponding Amazon\'s sell page.', '<div id=\"pat-amazon-isbn\">\n<h1>Plugin Help</h1>\n<h2>Purpose</h2>\n<p>This Textpattern CMS plugin converts ISBN13 format numbers to ISBN10 ones in order to access the corresponding book page on Amazon stores (support for internationalization).</p>\n<h2>Usage</h2>\n<p><code class=\"code\">&lt;txp:pat_amazon_isbn number=\"\" url=\"\" tooltip=\"\" title=\"\" /&gt;</code></p>\n<h2>Attributes</h2>\n<ul>\n<li><b>number</b>: Your <code>ISBN13</code> number <sup>1</sup>. Default: <em>none</em> (empty) .</li>\n<li><b>url</b>: TLD extension for the Amazon domain choosen. Default: <em>com</em> (for the http://www.amazon.com URL american store).</li>\n<li><b>tooltip</b>: Text for the tooltip link. Default: <em>Access to Amazon store</em>.</li>\n<li><b>title</b>: Title of the link. Default: <em>Buy on Amazon</em>.</li>\n</ul>\n<h2>Example</h2>\n<p><code class=\"code\">&lt;txp:pat_amazon_isbn number=\"978-1118008188\" url=\"com\" tooltip=\" On the Amazon Store \" title=\"Buy on Amazon\" /&gt;</code></p>\n<p>The code above will produce an HTML link to the Amazon.com corresponding sell page for the book with the ISBN13 number \"<a href=\"http://www.amazon.com/HTML-CSS-Design-Build-Websites/dp/1118008189/ref=sr_1_1?s=books&amp;ie=UTF8&amp;qid=1384414611&amp;sr=1-1&amp;keywords=HTML+and+CSS%3A+Design+and+Build+Websites\" title=\" See this book on Amazon \" target=\"_blank\">978-1118008188</a>\".</p>\n<p>Result: <br><a href=\"http://www.amazon.com/dp/1118008189\" title=\" On the Amazon Store \" target=\"_blank\">Buy on Amazon</a></p>\n<p>Source code: <br><code class=\"code\">&lt;a href=\"http://www.amazon.com/dp/1118008189\" title=\" On the Amazon Store \" target=\"_blank\"&gt;Buy on Amazon&lt;/a&gt;</code></p>\n<h2>Example with a custom field</h2>\n<p><code class=\"code\">&lt;txp:if_custom_field name=\"ISBN\"&gt;&lt;txp:pat_amazon_isbn number=\'&lt;txp:custom_field name=\"ISBN\" /&gt;\' url=\"co.uk\" tooltip=\" On the Amazon Store \" title=\"Buy on Amazon\" /&gt;&lt;/txp:if_custom_field&gt;</code></p>\n<h2>Notes</h2>\n<p>This plugin checks and validates all attributes. <br><sup>1</sup> It don\'t care about how you set the ISBN13 number. (i.e. Both are accepted: <code>978-1118008188</code> <em>or</em> <code>978-1-118008-18-8</code>) <em>even</em> without any dashes.</p>\n<h2>Change log</h2>\n<ul>\n<li>20 nov 2013: v0.1.1, code review based on Jukka Svahn advices.</li>\n<li>13 nov 2013: v0.1, first release.</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style>#pat-amazon-isbn{text-align:justify}#pat-amazon-isbn .code{float:left;clear:both;width:100%;margin:0 0 2em;word-break:break-all;text-align:left}#pat-amazon-isbn sup{border-bottom:1px solid #aaa}</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * @plugin:  pat_amazon_isbn for Textpattern CMS\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com\r\n * @link:    http://pat-amazon-isbn.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.1.1\r\n * @license: GPLv2\r\n*/\r\n\r\n/**\r\n * Main plugin function\r\n *\r\n * @param:	$atts\r\n * @return:	tag link\r\n */\r\n\r\nfunction pat_amazon_isbn ($atts) {\r\n\r\n	extract(lAtts(array(\r\n		\'number\'	=> NULL,\r\n		\'url\'		=> \'com\',\r\n		\'tooltip\'	=> \' Access to Amazon store \',\r\n		\'title\'		=> \'Buy on Amazon\',\r\n	), $atts));\r\n\r\n	if ( pat_amazon_validate_isbn($number) ) {\r\n\r\n		$number = strtoupper($number);\r\n		$isbn = implode( \'\', explode(\'-\', $number) );\r\n\r\n		if (preg_match(\'/^\\d{3}(\\d{9})\\d$/\', $isbn, $m)) {\r\n\r\n			$sequence = $m[1];\r\n			$sum = 0;\r\n			$mul = 10;\r\n			for ($i = 0; $i < 9; $i++) {\r\n				$sum = $sum + ($mul * (int) $sequence{$i});\r\n				$mul--;\r\n			}\r\n\r\n			$mod = 11 - ($sum%11);\r\n\r\n			if ($mod == 10) {\r\n				$mod = \"X\";\r\n			}\r\n			else if ($mod == 11) {\r\n				$mod = 0;\r\n			}\r\n\r\n			$isbn = $sequence.$mod;\r\n		}\r\n\r\n		$out = href( trim($title), pat_amazon_validate_url($url).$isbn, \' rel=\"nofollow\"\' . ($tooltip ? \' title=\"\' . strip_tags($tooltip) . \'\"\' : \'\') . \' target=\"_blank\"\' );\r\n\r\n	} else {\r\n\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'isbn\')), E_USER_WARNING);\r\n	}\r\n\r\n		return $out;\r\n\r\n}\r\n\r\n/**\r\n * Validates and builds an URL.\r\n *\r\n * Validates the TLD extension from the given string, and use it\r\n * to create a link to a specific international Amazon store.\r\n *\r\n * @param  string $host The TLD extension\r\n * @return string URL to the Amazon store based on the given $host extension\r\n */\r\n\r\nfunction pat_amazon_validate_url ($host) {\r\n\r\n	preg_match(\'/^([a-z]{2}\\.)?[a-z]{2,4}$/i\', $host, $m, PREG_OFFSET_CAPTURE);\r\n\r\n	$link = isset($m[0][0]) ? \'http://www.amazon.\'.$m[0][0].\'/dp/\' : trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'url\')), E_USER_WARNING);\r\n\r\n	return $link;\r\n	\r\n}\r\n\r\n/**\r\n * Part of ISBN PHP library.\r\n *\r\n * @param  string validates an ISBN13 number format\r\n * @return ISBN10 number format without any dashes\r\n */\r\n\r\nfunction pat_amazon_validate_isbn ($isbn) {\r\n\r\n	$isbn = str_replace(\'-\', \'\', $isbn);\r\n\r\n	if (strlen($isbn) != 13)\r\n		return false;\r\n\r\n	$check = 0;\r\n\r\n	for ($i = 0; $i < 13; $i+=2)\r\n		$check += substr($isbn, $i, 1);\r\n	for ($i = 1; $i < 12; $i+=2)\r\n		$check += 3 * substr($isbn, $i, 1);\r\n\r\n	return $check % 10 == 0;\r\n\r\n}', '/**\r\n * @plugin:  pat_amazon_isbn for Textpattern CMS\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com\r\n * @link:    http://pat-amazon-isbn.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.1.1\r\n * @license: GPLv2\r\n*/\r\n\r\n/**\r\n * Main plugin function\r\n *\r\n * @param:	$atts\r\n * @return:	tag link\r\n */\r\n\r\nfunction pat_amazon_isbn ($atts) {\r\n\r\n	extract(lAtts(array(\r\n		\'number\'	=> NULL,\r\n		\'url\'		=> \'com\',\r\n		\'tooltip\'	=> \' Access to Amazon store \',\r\n		\'title\'		=> \'Buy on Amazon\',\r\n	), $atts));\r\n\r\n	if ( pat_amazon_validate_isbn($number) ) {\r\n\r\n		$number = strtoupper($number);\r\n		$isbn = implode( \'\', explode(\'-\', $number) );\r\n\r\n		if (preg_match(\'/^\\d{3}(\\d{9})\\d$/\', $isbn, $m)) {\r\n\r\n			$sequence = $m[1];\r\n			$sum = 0;\r\n			$mul = 10;\r\n			for ($i = 0; $i < 9; $i++) {\r\n				$sum = $sum + ($mul * (int) $sequence{$i});\r\n				$mul--;\r\n			}\r\n\r\n			$mod = 11 - ($sum%11);\r\n\r\n			if ($mod == 10) {\r\n				$mod = \"X\";\r\n			}\r\n			else if ($mod == 11) {\r\n				$mod = 0;\r\n			}\r\n\r\n			$isbn = $sequence.$mod;\r\n		}\r\n\r\n		$out = href( trim($title), pat_amazon_validate_url($url).$isbn, \' rel=\"nofollow\"\' . ($tooltip ? \' title=\"\' . strip_tags($tooltip) . \'\"\' : \'\') . \' target=\"_blank\"\' );\r\n\r\n	} else {\r\n\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'isbn\')), E_USER_WARNING);\r\n	}\r\n\r\n		return $out;\r\n\r\n}\r\n\r\n/**\r\n * Validates and builds an URL.\r\n *\r\n * Validates the TLD extension from the given string, and use it\r\n * to create a link to a specific international Amazon store.\r\n *\r\n * @param  string $host The TLD extension\r\n * @return string URL to the Amazon store based on the given $host extension\r\n */\r\n\r\nfunction pat_amazon_validate_url ($host) {\r\n\r\n	preg_match(\'/^([a-z]{2}\\.)?[a-z]{2,4}$/i\', $host, $m, PREG_OFFSET_CAPTURE);\r\n\r\n	$link = isset($m[0][0]) ? \'http://www.amazon.\'.$m[0][0].\'/dp/\' : trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'url\')), E_USER_WARNING);\r\n\r\n	return $link;\r\n	\r\n}\r\n\r\n/**\r\n * Part of ISBN PHP library.\r\n *\r\n * @param  string validates an ISBN13 number format\r\n * @return ISBN10 number format without any dashes\r\n */\r\n\r\nfunction pat_amazon_validate_isbn ($isbn) {\r\n\r\n	$isbn = str_replace(\'-\', \'\', $isbn);\r\n\r\n	if (strlen($isbn) != 13)\r\n		return false;\r\n\r\n	$check = 0;\r\n\r\n	for ($i = 0; $i < 13; $i+=2)\r\n		$check += substr($isbn, $i, 1);\r\n	for ($i = 1; $i < 12; $i+=2)\r\n		$check += 3 * substr($isbn, $i, 1);\r\n\r\n	return $check % 10 == 0;\r\n\r\n}', '3843392af68c3ee3eafce738b87295c0', '', '', 0, 5, 0),
('pat_reading_time', 0, 'Patrick LEFEVRE', 'http://twitter.com/lowel', '0.2', 'Display a words counter & estimated reading time for articles.', '<div class=\"pat-reading-time\">\n<h1>pat_reading_time</h1>\n<p>This (very simple) Textpattern CMS plugin offers 2 tags to display a word counter and/or an estimated time reading for articles.</p>\n<h2>Usages</h2>\n<h3>TXP tag</h3>\n<p><code>&lt;txp:pat_reading_time text=\"\" title=\"\" before=\"\" charlist=\"\" wraptag=\"\" break=\"\" class=\"\" /&gt;</code></p>\n<h3>Attributes</h3>\n<ul>\n<li><code>text</code> (string): set which content to apply for the result, among <b>body</b> or <b>excerpt</b>. Default: <em>body</em>.</li>\n<li><code>title</code> (string): set the text before the result. Default: \"<em>Words: </em>\".</li>\n<li><code>plural</code> (string): plural character for \"word\". Default: \"<em>s</em>\".</li>\n<li><code>before</code> (boolean): position with the count for the content of the \"title\" attribute (among <em>before</em>: <b>1</b> or <em>after</em>: <b>0</b>). Default: \"<em>1</em>\".</li>\n<li><code>charlist</code> (string): list of additional characters - <b>without any commas</b> - to include into the count (e.g. \"<b><code>!\\\"\'</code></b>\"). Default:&nbsp;<em>empty</em>.</li>\n<li><code>wraptag</code> (letter): HTML container tag. Default: <em>p</em>.</li>\n<li><code>break</code> (string): HTML break tag. Default: <em>br</em>.</li>\n<li><code>class</code> (string): HTML class attribute. Default: \"<em>word-count</em>\".</li>\n</ul>\n<h3>TXP tag</h3>\n<p><code>&lt;txp:pat_estimate_time_reading text=\"\" title=\"\" short=\"\" minute=\"\" second=\"\" plural=\"\" wraptag=\"\" break=\"\" class=\"\" /&gt;</code></p>\n<h3>Attributes</h3>\n<ul>\n<li><code>text</code> (string): set which content to apply for the result, among <b>body</b> or <b>excerpt</b>. Default: <em>body</em>.</li>\n<li><code>title</code> (string): set the text before the result. Default: \"<em>Time to read:  </em>\".</li>\n<li><code>short</code> (boolean): display only minutes time. Default: \"<em>0</em>\" (display full estimation time with seconds).</li>\n<li><code>wraptag</code> (string): HTML container tag. Default: <em>p</em>.</li>\n<li><code>break</code> (string): HTML breaking tag. Default: <em>br</em>.</li>\n<li><code>minute</code> (integer): word for the \"minute\" time (i18n support). Default: \"<em>minute</em>\".</li>\n<li><code>second</code> (integer): word for the \"second\" time (i18n support). Default: \"<em>second</em>\".</li>\n<li><code>plural</code> (string): plural character for \"minute\" & \"second\". Default: \"<em>s</em>\".</li>\n<li><code>class</code> (string): HTML  class attribute. Default: \"<em>time-reading</em>\".</li>\n<li><code>frequence</code> (integer): the number of words read by minutes (average). Default: <em>200</em>.</li>\n</ul>\n<h2>Changelog</h2>\n<ul>\n<li>6th August 2014. v 0.2. Add \"short\" attribute in order to show only minutes time.</li>\n<li>12 May 2014. v 0.1. First release.</li>\n</ul>\n</div>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\" media=\"screen\">.pat-word-counter code{display: inline-block;max-width: 95%;word-break: break-word}</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/**\r\n * pat_words_count\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-reading-time.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.2\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * Return word count\r\n *\r\n * @param  atts\r\n * @return integer The number of words\r\n */\r\nfunction pat_reading_time($atts) {\r\n\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'text\'		=> \'body\',\r\n		\'before\'	=> 1,\r\n		\'title\'		=> \'Word\',\r\n		\'plural\'	=> \'s\',\r\n		\'charlist\'	=> NULL,\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \'br\',\r\n		\'class\'		=> \'word-count\',\r\n	), $atts));\r\n\r\n	$content = htmlspecialchars_decode(strip_tags($thisarticle[$text]));\r\n\r\n	if ($content) {\r\n\r\n		$result = _pat_words_count($content, $charlist);\r\n		$result = ( $before == true ? _pat_plural($result, $title, $plural).\' \'.$result : $result.\' \'._pat_plural($result, $title, $plural) ).\'.\';\r\n		if ($break == \'br\' or $break == \'hr\')\r\n			$break = \"<$break />\".n;\r\n		else\r\n			$break = \'\';\r\n		$out = ($wraptag) ? doTag($result, $wraptag, $class).$break : $result;\r\n	} else {\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'text\')), E_USER_WARNING);\r\n	}\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return estimated time reading\r\n *\r\n * @param  atts\r\n * @return string Reading time in plain text\r\n */\r\nfunction pat_estimate_time_reading($atts) {\r\n\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'text\'		=> \'body\',\r\n		\'title\'		=> \'Time to read: \',\r\n		\'short\'		=> false,\r\n		\'minute\'	=> \'minute\',\r\n		\'second\'	=> \'second\',\r\n		\'plural\'	=> \'s\',\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \'br\',\r\n		\'class\'		=> \'time-reading\',\r\n		\'frequence\'	=> 200,\r\n	), $atts));\r\n\r\n	$content = $thisarticle[$text];\r\n\r\n	if($content) {\r\n\r\n		$word = _pat_words_count($content);\r\n		$m = floor($word / $frequence);\r\n		$s = floor($word % $frequence / ($frequence / 60));\r\n\r\n		$est = $title . $m . \' \' . _pat_plural($m, $minute, $plural) .( $short ? \'\' : \', \' . $s . \' \' . _pat_plural($s, $second, $plural) ).\'.\';\r\n		if ($break == \'br\' or $break == \'hr\')\r\n			$break = \"<$break />\".n;\r\n		else\r\n			$break = \'\';\r\n\r\n		$out = ($wraptag) ? doTag($est, $wraptag, $class).$break : $est;\r\n	} else {\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'text\')), E_USER_WARNING);\r\n	}\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return lenght of string\r\n *\r\n * @param  $words, $extra	string, signs to include in count\r\n * @return integer\r\n */\r\nfunction _pat_words_count($words, $extra = NULL)\r\n{\r\n	$counter = str_word_count( strip_tags($words), 0, $charlist );\r\n\r\n	return $counter;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return plural word\r\n *\r\n * @param  $text, $title, $sign		string, word, plural sign\r\n * @return string\r\n */\r\nfunction _pat_plural($count, $title, $sign)\r\n{\r\n	return ($count <= 1 ? $title : $title.$sign);\r\n}', '/**\r\n * pat_words_count\r\n * @author:  © Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-reading-time.cara-tm.com\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.2\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * Return word count\r\n *\r\n * @param  atts\r\n * @return integer The number of words\r\n */\r\nfunction pat_reading_time($atts) {\r\n\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'text\'		=> \'body\',\r\n		\'before\'	=> 1,\r\n		\'title\'		=> \'Word\',\r\n		\'plural\'	=> \'s\',\r\n		\'charlist\'	=> NULL,\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \'br\',\r\n		\'class\'		=> \'word-count\',\r\n	), $atts));\r\n\r\n	$content = htmlspecialchars_decode(strip_tags($thisarticle[$text]));\r\n\r\n	if ($content) {\r\n\r\n		$result = _pat_words_count($content, $charlist);\r\n		$result = ( $before == true ? _pat_plural($result, $title, $plural).\' \'.$result : $result.\' \'._pat_plural($result, $title, $plural) ).\'.\';\r\n		if ($break == \'br\' or $break == \'hr\')\r\n			$break = \"<$break />\".n;\r\n		else\r\n			$break = \'\';\r\n		$out = ($wraptag) ? doTag($result, $wraptag, $class).$break : $result;\r\n	} else {\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'text\')), E_USER_WARNING);\r\n	}\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return estimated time reading\r\n *\r\n * @param  atts\r\n * @return string Reading time in plain text\r\n */\r\nfunction pat_estimate_time_reading($atts) {\r\n\r\n	global $thisarticle;\r\n\r\n	assert_article();\r\n\r\n	extract(lAtts(array(\r\n		\'text\'		=> \'body\',\r\n		\'title\'		=> \'Time to read: \',\r\n		\'short\'		=> false,\r\n		\'minute\'	=> \'minute\',\r\n		\'second\'	=> \'second\',\r\n		\'plural\'	=> \'s\',\r\n		\'wraptag\'	=> \'p\',\r\n		\'break\'		=> \'br\',\r\n		\'class\'		=> \'time-reading\',\r\n		\'frequence\'	=> 200,\r\n	), $atts));\r\n\r\n	$content = $thisarticle[$text];\r\n\r\n	if($content) {\r\n\r\n		$word = _pat_words_count($content);\r\n		$m = floor($word / $frequence);\r\n		$s = floor($word % $frequence / ($frequence / 60));\r\n\r\n		$est = $title . $m . \' \' . _pat_plural($m, $minute, $plural) .( $short ? \'\' : \', \' . $s . \' \' . _pat_plural($s, $second, $plural) ).\'.\';\r\n		if ($break == \'br\' or $break == \'hr\')\r\n			$break = \"<$break />\".n;\r\n		else\r\n			$break = \'\';\r\n\r\n		$out = ($wraptag) ? doTag($est, $wraptag, $class).$break : $est;\r\n	} else {\r\n		$out = trigger_error(gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'text\')), E_USER_WARNING);\r\n	}\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return lenght of string\r\n *\r\n * @param  $words, $extra	string, signs to include in count\r\n * @return integer\r\n */\r\nfunction _pat_words_count($words, $extra = NULL)\r\n{\r\n	$counter = str_word_count( strip_tags($words), 0, $charlist );\r\n\r\n	return $counter;\r\n\r\n}\r\n\r\n\r\n/**\r\n * Return plural word\r\n *\r\n * @param  $text, $title, $sign		string, word, plural sign\r\n * @return string\r\n */\r\nfunction _pat_plural($count, $title, $sign)\r\n{\r\n	return ($count <= 1 ? $title : $title.$sign);\r\n}', '50164ba003df2a7b1ea24feed9ebe6a0', '', '', 0, 5, 0),
('jnm_feed_custom_item', 0, 'Juanjo Navarro', 'http://www.juanjonavarro.com', '0.1', 'Customize the format of your feed item', '<p>This plugin let you customize the format of your feeds items.</p>\n\n<p>The \"body\" of the item is parsed from the \"feed_item_body\" form. <b>¡You need to create a form with this name!</b></p>\n\n<h2>Some examples and Ideas</h2>\n\n<p>You can use this plugin to:</p>\n\n<h3>Include the article image</h3>\n\n<p>Use this feed_item_body:</p>\n\n<textarea><txp:article_image />\n<txp:body/></textarea>\n\n<h3>Include a custom field after the body:</h3>\n\n<textarea><txp:body/>\n<p>Color: <txp:custom_field name=\"color\"/></p></textarea>\n\n<h3>Include the article original link (anti splogs)</h3>\n\n<textarea><txp:body/>\n<p>Original article: <txp:permlink><txp:title /></txp:permlink></p></textarea>\n<!-- --- BEGIN PLUGIN CSS --- -->\n<style type=\"text/css\">\nh2 {font-size: 150%}\n\nh3 {font-size: 100%; font-weight: bold}\n\ntextarea {width: 80%; height: 5em}\n\n</style>\n<!-- --- END PLUGIN CSS --- -->\n', 'register_callback(\'jnm_feed_custom_item\', \'atom_entry\');\r\nregister_callback(\'jnm_feed_custom_item\', \'rss_entry\');\r\n\r\nfunction jnm_feed_custom_item($event, $step) {\r\n   $form_name=\"feed_item_body\";\r\n\r\n   global $thisarticle; \r\n   global $jnm_feed_image_form;\r\n\r\n   if (!isset($jnm_feed_image_form)) {\r\n     $jnm_feed_image_form=fetch_form($form_name);\r\n   } \r\n\r\n   $body=parse($jnm_feed_image_form);\r\n   $thisarticle[\'body\']=$body;\r\n}', 'register_callback(\'jnm_feed_custom_item\', \'atom_entry\');\r\nregister_callback(\'jnm_feed_custom_item\', \'rss_entry\');\r\n\r\nfunction jnm_feed_custom_item($event, $step) {\r\n   $form_name=\"feed_item_body\";\r\n\r\n   global $thisarticle; \r\n   global $jnm_feed_image_form;\r\n\r\n   if (!isset($jnm_feed_image_form)) {\r\n     $jnm_feed_image_form=fetch_form($form_name);\r\n   } \r\n\r\n   $body=parse($jnm_feed_image_form);\r\n   $thisarticle[\'body\']=$body;\r\n}', 'ebf47c483134509eca7c22398fbbed58', '', '', 0, 5, 0),
('jnm_categories', 0, 'Juanjo Navarro', 'http://www.juanjonavarro.com/', '0.1', 'Display article\'s categories, separated by \'separator\'', '	<p>\nSimple plugin to display article&#8217;s categories, separated by &#8216;separator&#8217;.</p>\n\n	<p>Attributes:</p>\n\n	<p>You can use the same attributes as txp:category1 and txp:category2. You can also<br />\nset the &#8216;separator&#8217; attribute (default &#8216;, &#8216;)</p>', '\nfunction jnm_categories($attrs) {\n   extract(lAtts(array(\n    \'title\'        => 0,\n    \'link\'         => 0,\n    \'section\'      => \'\',\n    \'this_section\' => 0,\n    \'class\'        => \'\',\n    \'wraptag\'      => \'\',\n    \'separator\'    => \', \'\n  ),$attrs));\n\n  $cat1=category1(array(\'title\'        => $title,\n                        \'link\'         => $link,\n                        \'section\'      => $section,\n                        \'this_section\' => $this_section,\n                        \'class\'        => $class,\n                        \'wraptag\'      => $wraptag));\n\n  $cat2=category2(array(\'title\'        => $title,\n                        \'link\'         => $link,\n                        \'section\'      => $section,\n                        \'this_section\' => $this_section,\n                        \'class\'        => $class,\n                        \'wraptag\'      => $wraptag));\n\n  return ($cat2==\'\') ? $cat1 : $cat1.$separator.$cat2;\n}\n', '\nfunction jnm_categories($attrs) {\n   extract(lAtts(array(\n    \'title\'        => 0,\n    \'link\'         => 0,\n    \'section\'      => \'\',\n    \'this_section\' => 0,\n    \'class\'        => \'\',\n    \'wraptag\'      => \'\',\n    \'separator\'    => \', \'\n  ),$attrs));\n\n  $cat1=category1(array(\'title\'        => $title,\n                        \'link\'         => $link,\n                        \'section\'      => $section,\n                        \'this_section\' => $this_section,\n                        \'class\'        => $class,\n                        \'wraptag\'      => $wraptag));\n\n  $cat2=category2(array(\'title\'        => $title,\n                        \'link\'         => $link,\n                        \'section\'      => $section,\n                        \'this_section\' => $this_section,\n                        \'class\'        => $class,\n                        \'wraptag\'      => $wraptag));\n\n  return ($cat2==\'\') ? $cat1 : $cat1.$separator.$cat2;\n}\n', '4353418535842276fd31f4bc3f828850', '', '', 0, 5, 0),
('jnm_audio', 0, 'Juanjo Navarro', 'http://www.juanjonavarro.com/', '0.3', 'Insert an mp3 audio player (flash) in your pages and articles', '	<p>\n<strong>Options:</strong></p>\n	<ul>\n		<li>fileid = id of the uploaded textpattern file (using txp file tab) to be used as the track for this player</li>\n		<li>filename = name of the uploaded textpattern file (using txp file tab) to be used as the track for this player</li>\n		<li>audio = name of the file to be used as the the track for this player. Textpattern will look for this file inside a folder called &#8220;audio&#8221; in your blog directory (you need to use ftp or other method to upload the file)</li>\n		<li>url = url of an mp3 file to be used as the track for this player.</li>\n	</ul>\n	<p>You must only specify one source for the track (use only one of the previous attributes).</p>\n	<p>If you specify &#8217;.&#8217; as the value for any of the previous attributes, the tag will parse his body to obtain the actual value.</p>\n	<p>Example:</p>\n<code>&lt;txp:jnm_audio url=\".\"&gt;&lt;txp:custom_field name=\"custom1\" /&gt;&lt;/txp:jnm_audio&gt;</code>\n	<p>(will use the text in &#8216;custom1&#8217; as the url for this player)</p>\n	<ul>\n		<li>loop = set to &#8216;yes&#8217; to loop the track indefinitely</li>\n		<li>autostart = set to &#8216;yes&#8217; to start to play the track automatically</li>\n	</ul>\n	<ul>\n		<li>bgcolor = Background color</li>\n		<li>textcolor = Text color</li>\n		<li>leftbgcolor = Left background color</li>\n		<li>lefticoncolor = Left icon color</li>\n		<li>rightbgcolor = Right background color</li>\n		<li>rightbghovercolor = Right background hover color</li>\n		<li>righticoncolor = Right icon color</li>\n		<li>righticonhovercolor = Right icon hover color</li>\n		<li>slidercolor = Progress slider color</li>\n		<li>trackcolor = Progress track color</li>\n		<li>loadercolor = Loader bar color</li>\n		<li>bordercolor = Border color</li>\n	</ul>\n\n\n ', '\nfunction jnm_audio($atts, $thing=\'\') {\n   $plugindir=\'plugins\'; // Directory where plugin files reside\n   $audiodir=\'audio\';    // Directory where audio files reside\n   extract(lAtts(array(\n      \'bgcolor\' => \'0xf8f8f8\',            // Background color\n      \'textcolor\' => \'0x666666\',          // Text color\n      \'leftbgcolor\' => \'0xeeeeee\',        // Left background color\n      \'lefticoncolor\' => \'0x666666\',      // Left icon color\n      \'rightbgcolor\'=> \'0xcccccc\',        // Right background color\n      \'rightbghovercolor\' => \'0x999999\',  // Right background hover color\n      \'righticoncolor\' => \'0x666666\',     // Right icon color\n      \'righticonhovercolor\' => \'0xffffff\',// Right icon hover color\n      \'slidercolor\' => \'0x666666\',        // Progress slider color\n      \'trackcolor\' => \'0xFFFFFF\',         // Progress track color\n      \'loadercolor\' => \'0x9FFFB8\',        // Loader bar color\n      \'bordercolor\' => \'0x666666\',        // Border color\n      \'loop\' => \'no\',\n      \'autostart\' => \'no\',\n      \'audio\' => \'\',\n      \'fileid\' => 0,\n      \'filename\' => \'\',\n      \'url\' => \'\',\n   ),$atts));\n\n   // Nothing to touch below this line\n\n   global $permlink_mode;\n\n   static $playerid=1;\n\n   $plugindir=hu.$plugindir;\n\n	 if (!empty($fileid) || !empty($filename)) {\n	    if ($fileid==\'.\')\n	        $fileid=parse($thing);\n	    if ($filename==\'.\')\n          $filename=parse($thing);\n      $where = (!empty($fileid) && $fileid != 0)? \"id=\'$fileid\'\" : ((!empty($filename))? \"filename=\'$filename\'\" : \'\');\n			$thisfile = fileDownloadFetchInfo($where);\n			$url= ($permlink_mode == \'messy\') ?\n					hu.\'index.php?s=file_download&amp;id=\'.$thisfile[\'id\']:\n					hu.gTxt(\'file_download\').\'/\'.$thisfile[\'id\'];\n	 }\n	 else if (!empty($audio) && $audio!=\'\') {\n	    if ($audio==\'.\')\n          $audio=parse($thing);\n      $url=hu.$audiodir.\'/\'.$audio;\n   }\n   else if ($url==\'.\') {\n       $url=parse($thing);\n   }\n\n   $output=\'\';\n\n   if ($playerid==1) {\n     $output=\"<script language=\\\"JavaScript\\\" type=\\\"text/javascript\\\" src=\\\"$plugindir/audio-player.js\\\"></script>\\n\";\n   }\n\n   $object= <<< END\n<object type=\"application/x-shockwave-flash\" data=\"$plugindir/player.swf\" id=\"audioplayer$playerid\" height=\"24\" width=\"290\">\n<param name=\"movie\" value=\"$plugindir/player.swf\"/>\n<param name=\"FlashVars\" value=\"playerID=$playerid&amp;bg=$bgcolor&amp;leftbg=$leftbgcolor&amp;lefticon=$lefticoncolor&amp;rightbg=$rightbgcolor&amp;rightbghover=$rightbghovercolor&amp;righticon=$righticoncolor&amp;righticonhover=$righticonhovercolor&amp;text=$textcolor&amp;slider=$slidercolor&amp;track=$trackcolor&amp;border=$bordercolor&amp;loader=$loadercolor&amp;loop=$loop&amp;autostart=$autostart&amp;soundFile=$url\"/>\n<param name=\"quality\" value=\"high\"/>\n<param name=\"menu\" value=\"false\"/>\n<param name=\"wmode\" value=\"transparent\"/></object>\nEND;\n\n   $output = $output.$object;\n   $playerid++;\n\n   return $output;\n}\n', '\nfunction jnm_audio($atts, $thing=\'\') {\n   $plugindir=\'plugins\'; // Directory where plugin files reside\n   $audiodir=\'audio\';    // Directory where audio files reside\n   extract(lAtts(array(\n      \'bgcolor\' => \'0xf8f8f8\',            // Background color\n      \'textcolor\' => \'0x666666\',          // Text color\n      \'leftbgcolor\' => \'0xeeeeee\',        // Left background color\n      \'lefticoncolor\' => \'0x666666\',      // Left icon color\n      \'rightbgcolor\'=> \'0xcccccc\',        // Right background color\n      \'rightbghovercolor\' => \'0x999999\',  // Right background hover color\n      \'righticoncolor\' => \'0x666666\',     // Right icon color\n      \'righticonhovercolor\' => \'0xffffff\',// Right icon hover color\n      \'slidercolor\' => \'0x666666\',        // Progress slider color\n      \'trackcolor\' => \'0xFFFFFF\',         // Progress track color\n      \'loadercolor\' => \'0x9FFFB8\',        // Loader bar color\n      \'bordercolor\' => \'0x666666\',        // Border color\n      \'loop\' => \'no\',\n      \'autostart\' => \'no\',\n      \'audio\' => \'\',\n      \'fileid\' => 0,\n      \'filename\' => \'\',\n      \'url\' => \'\',\n   ),$atts));\n\n   // Nothing to touch below this line\n\n   global $permlink_mode;\n\n   static $playerid=1;\n\n   $plugindir=hu.$plugindir;\n\n	 if (!empty($fileid) || !empty($filename)) {\n	    if ($fileid==\'.\')\n	        $fileid=parse($thing);\n	    if ($filename==\'.\')\n          $filename=parse($thing);\n      $where = (!empty($fileid) && $fileid != 0)? \"id=\'$fileid\'\" : ((!empty($filename))? \"filename=\'$filename\'\" : \'\');\n			$thisfile = fileDownloadFetchInfo($where);\n			$url= ($permlink_mode == \'messy\') ?\n					hu.\'index.php?s=file_download&amp;id=\'.$thisfile[\'id\']:\n					hu.gTxt(\'file_download\').\'/\'.$thisfile[\'id\'];\n	 }\n	 else if (!empty($audio) && $audio!=\'\') {\n	    if ($audio==\'.\')\n          $audio=parse($thing);\n      $url=hu.$audiodir.\'/\'.$audio;\n   }\n   else if ($url==\'.\') {\n       $url=parse($thing);\n   }\n\n   $output=\'\';\n\n   if ($playerid==1) {\n     $output=\"<script language=\\\"JavaScript\\\" type=\\\"text/javascript\\\" src=\\\"$plugindir/audio-player.js\\\"></script>\\n\";\n   }\n\n   $object= <<< END\n<object type=\"application/x-shockwave-flash\" data=\"$plugindir/player.swf\" id=\"audioplayer$playerid\" height=\"24\" width=\"290\">\n<param name=\"movie\" value=\"$plugindir/player.swf\"/>\n<param name=\"FlashVars\" value=\"playerID=$playerid&amp;bg=$bgcolor&amp;leftbg=$leftbgcolor&amp;lefticon=$lefticoncolor&amp;rightbg=$rightbgcolor&amp;rightbghover=$rightbghovercolor&amp;righticon=$righticoncolor&amp;righticonhover=$righticonhovercolor&amp;text=$textcolor&amp;slider=$slidercolor&amp;track=$trackcolor&amp;border=$bordercolor&amp;loader=$loadercolor&amp;loop=$loop&amp;autostart=$autostart&amp;soundFile=$url\"/>\n<param name=\"quality\" value=\"high\"/>\n<param name=\"menu\" value=\"false\"/>\n<param name=\"wmode\" value=\"transparent\"/></object>\nEND;\n\n   $output = $output.$object;\n   $playerid++;\n\n   return $output;\n}\n', '1c292a3cb4b5a3720c0434625ad46508', '', '', 0, 5, 0),
('egg_anonymizer', 0, 'Eric Eggert', 'http://yatil.de/', '0.1', 'Anonymizes commenters ip adresses', '<h1>Anonymizer <small>by Eric Eggert, published at <a href=\"http://yatil.de\">yatil.de</a></small></h1>\r\n\r\n<p>After a court decision in Germany website owners are not allowed to store personal information not necessary for processing information (like IPs). That?s especially important for comments, so site owners should anonymize comment IP adresses after checking them.</p>\r\n<p>More information at <a href=\"http://www.advisign.de/datenschutz/2007-10/die-vorratsdatenspeicherung-fuer-den-hausgebrauch-oder-darf-man-ip-adressen-der-websitebesucher-speichern\">advisign.de</a> (in German).</p>            \r\n\r\n<h2>Usage Instructions</h2>      \r\n\r\n<p>This plugin adds links for anonymizing commenters IP adresses to the comments tab. (JavaScript required, and TXP 4.0.5 for jQuery)</p>\r\n<p>It adds a Anonymizer tab under Extensions, where all comments can be anonymized with one click.</p>\r\n\r\n<h1>Anonymizer <small>von Eric Eggert, veröffentlicht auf <a href=\"http://yatil.de\">yatil.de</a></small></h1>\r\n   \r\n<p>Nach einer Gerichtsentscheidung in Deutschland ist es Betreibern von Webseiten nicht mehr erlaubt persönliche Informationen (wie IPs) dauerhaft zu speichern. Das gilt vor allem für Kommentare in Textpattern, deren IP-Adressen sollten nachdem sie überprüft wurden gelöscht werden.</p>\r\n<p>Weitere Informationen unter <a href=\"http://www.advisign.de/datenschutz/2007-10/die-vorratsdatenspeicherung-fuer-den-hausgebrauch-oder-darf-man-ip-adressen-der-websitebesucher-speichern\">advisign.de</a>.</p>                     \r\n\r\n\r\n<h2>Verwendung</h2>\r\n\r\n<p>Dieses Plugin fügt einen Link im Kommentar-Tab ein, welcher es erlaubt einzelne Kommentare zu anonymisieren. (JavaScript und TXP 4.0.5 wegen jQuery benötigt)</p>\r\n<p>Zudem gibt es einen Anonymizer-Tab, in dem alle Kommentare auf einmal anonymisiert werden können.</p>', '# <?php // Tricking TextMate\r\n\r\nif (@txpinterface == \'admin\') {\r\n	add_privs(\'egg_anonymizer\',\'1,2,3,4,5\');\r\n	register_tab(\'extensions\', \'egg_anonymizer\', \"Anonymizer\");\r\n	register_callback(\'egg_anon_javascript\', \'discuss\');\r\n	register_callback(\'egg_anonymizer\', \'egg_anonymizer\');\r\n}\r\n\r\nfunction egg_anonymizer ($event, $step) {\r\n	if(!$step or !in_array($step, \r\n	            array(\'egg_anon_doit\', \'egg_anon_them_all\'\r\n	                  ))){\r\n		egg_anon_init();\r\n	} \r\n	else $step();\r\n}\r\nfunction egg_anon_init($message=\'\') {	\r\n    pagetop(\"Anonymizer\",$message); \r\n	echo(\'<p style=\"text-align:center\"><a href=\"?event=egg_anonymizer&step=egg_anon_them_all\">Anonymize all Comments!</a></p>\');\r\n}\r\n\r\nfunction egg_anon_javascript() {\r\n	echo <<<js\r\n<script type=\"text/javascript\">\r\n	var tablerows = $(\'table#list tr\'); \r\n	var thtext = document.createTextNode(\'Anonymize\');\r\n	var thtd = document.createElement(\'th\');\r\n	thtd.appendChild(thtext);\r\n	tablerows[0].appendChild(thtd);\r\n\r\n	for (var i = 1; i < (tablerows.length-1); i++) {\r\n		var thatid = tablerows[i].getElementsByTagName(\'a\')[0].innerHTML;\r\n		var link = document.createElement(\'a\');\r\n		link.setAttribute(\'href\',(\'?event=egg_anonymizer&step=egg_anon_doit&id=\'+thatid));\r\n		var linktext = document.createTextNode(\'Anonymize it!\');\r\n		link.appendChild(linktext);\r\n		var tabledata = document.createElement(\'td\');\r\n		tabledata.appendChild(link);\r\n		tablerows[i].appendChild(tabledata);\r\n	};\r\n</script>\r\njs;\r\n}\r\n\r\nfunction egg_anon_doit(){\r\n	pagetop(\'Anonymizer\');\r\n	$id=gps(\'id\');\r\n	safe_update(\"txp_discuss\", \"ip=\'127.0.0.1\'\", \"discussid=\'\".$id.\"\'\"); \r\n	echo(\'<p style=\"text-align:center\"><a href=\"\'.$_SERVER[\"HTTP_REFERER\"].\'\">Zurück zu den Kommentaren?</a></p>\');\r\n}\r\n\r\nfunction egg_anon_them_all(){\r\n	$done = safe_update(\"txp_discuss\", \"ip=\'127.0.0.1\'\", \"1=1\"); \r\n	if ($done) {\r\n		return egg_anon_init(\'Alle IP-Adressen erfolgreich gelöscht.\');\r\n	} else {\r\n		return egg_anon_init(\'MySQL-Fehler beim löschen der IP-Daten.\');\r\n	}\r\n}\r\n', '# <?php // Tricking TextMate\r\n\r\nif (@txpinterface == \'admin\') {\r\n	add_privs(\'egg_anonymizer\',\'1,2,3,4,5\');\r\n	register_tab(\'extensions\', \'egg_anonymizer\', \"Anonymizer\");\r\n	register_callback(\'egg_anon_javascript\', \'discuss\');\r\n	register_callback(\'egg_anonymizer\', \'egg_anonymizer\');\r\n}\r\n\r\nfunction egg_anonymizer ($event, $step) {\r\n	if(!$step or !in_array($step, \r\n	            array(\'egg_anon_doit\', \'egg_anon_them_all\'\r\n	                  ))){\r\n		egg_anon_init();\r\n	} \r\n	else $step();\r\n}\r\nfunction egg_anon_init($message=\'\') {	\r\n    pagetop(\"Anonymizer\",$message); \r\n	echo(\'<p style=\"text-align:center\"><a href=\"?event=egg_anonymizer&step=egg_anon_them_all\">Anonymize all Comments!</a></p>\');\r\n}\r\n\r\nfunction egg_anon_javascript() {\r\n	echo <<<js\r\n<script type=\"text/javascript\">\r\n	var tablerows = $(\'table#list tr\'); \r\n	var thtext = document.createTextNode(\'Anonymize\');\r\n	var thtd = document.createElement(\'th\');\r\n	thtd.appendChild(thtext);\r\n	tablerows[0].appendChild(thtd);\r\n\r\n	for (var i = 1; i < (tablerows.length-1); i++) {\r\n		var thatid = tablerows[i].getElementsByTagName(\'a\')[0].innerHTML;\r\n		var link = document.createElement(\'a\');\r\n		link.setAttribute(\'href\',(\'?event=egg_anonymizer&step=egg_anon_doit&id=\'+thatid));\r\n		var linktext = document.createTextNode(\'Anonymize it!\');\r\n		link.appendChild(linktext);\r\n		var tabledata = document.createElement(\'td\');\r\n		tabledata.appendChild(link);\r\n		tablerows[i].appendChild(tabledata);\r\n	};\r\n</script>\r\njs;\r\n}\r\n\r\nfunction egg_anon_doit(){\r\n	pagetop(\'Anonymizer\');\r\n	$id=gps(\'id\');\r\n	safe_update(\"txp_discuss\", \"ip=\'127.0.0.1\'\", \"discussid=\'\".$id.\"\'\"); \r\n	echo(\'<p style=\"text-align:center\"><a href=\"\'.$_SERVER[\"HTTP_REFERER\"].\'\">Zurück zu den Kommentaren?</a></p>\');\r\n}\r\n\r\nfunction egg_anon_them_all(){\r\n	$done = safe_update(\"txp_discuss\", \"ip=\'127.0.0.1\'\", \"1=1\"); \r\n	if ($done) {\r\n		return egg_anon_init(\'Alle IP-Adressen erfolgreich gelöscht.\');\r\n	} else {\r\n		return egg_anon_init(\'MySQL-Fehler beim löschen der IP-Daten.\');\r\n	}\r\n}\r\n', '78478209258961c6ceed892d48f03612', '', '', 1, 5, 0),
('egg_shorturl', 0, 'Eric Eggert', 'http://yatil.de', '0.3', 'Helpers for short URLs', '<h1>egg_shorturl</h1>\r\n\r\n	<p>This plugin needs to be used with following lines in your <kbd>.htaccess</kbd> file:</p>\r\n<pre><code>RewriteEngine On\r\nRewriteRule ^(\\d+)$ http://example.com/s/$1 [R=301,L]</code></pre>\r\n	<p>(with example.com = your domain)</p>\r\n	<p>and a section <code>s</code> with the following template:</p>\r\n<pre><code>&lt;txp:egg_shorturl_handler /&gt;</code></pre>\r\n	<p><strong>This plugin works only with the <code>/section/title</code> URL schema.</strong></p>\r\n\r\n\r\n	<h2>Helper functions</h2>\r\n	<h3>&lt;txp:egg_shorturl_link /></h3>\r\n	<p>Outputs a link element for the header area of your <span class=\"caps\">HTML</span> document. (Empty on article list documents.)</p>\r\n	<h3>&lt;txp:egg_shorturl_a /></h3>\r\n	<p>Outputs a normal a href element to use in article context (eg. in your default form).</p>\r\n	<h3>&lt;txp:egg_shorturl_tweet /></h3>\r\n	<p>Outputs a link to twitter filling the textbox with the title and a link to your article. Parameters:</p>\r\n<ul>\r\n	<li><code>class</code> = <span class=\"caps\">CSS</span> class, defaults to <code>tweetme</code></li>\r\n		<li><code>tweetpre</code> = Text preceding your tweet, defaults to <code>Lese gerade:</code> (en: <code>Now reading:</code>)</li>\r\n		<li><code>linktext</code> = Text in your a element, defaults to <code>Diesen Artikel twittern</code> (en: <code>Tweet this article</code>)</li>\r\n	</ul>', '# <?php // Tricking Textmate into displaying syntax highlighting\r\n\r\nfunction egg_shorturl_link() {\r\n	if (isset($GLOBALS[\'thisarticle\'][\'thisid\'])) {\r\n		return \'<link rel=\"shorturl\" href=\"http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'\" />\';\r\n	}\r\n}\r\n\r\nfunction egg_shorturl_a() {\r\n	return \'<a href=\"http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'\" rel=\"shorturl\">\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'</a>\';\r\n}\r\n\r\nfunction egg_shorturl_tweet($atts) {\r\n\r\n	if (is_array($atts)) extract($atts);\r\n\r\n	$linktext = isset($linktext) ? $linktext : \'Diesen Artikel twittern\';\r\n	$tweetpre = isset($tweetpre) ? $tweetpre : \'Lese gerade:\'; \r\n	$class = isset($class) ? $class : \'tweetme\'; \r\n	$href = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'];\r\n\r\n	$title = $GLOBALS[\'thisarticle\'][\'title\'];\r\n\r\n	$tweetspace = 140 - (strlen($tweetpre) + strlen($href) + 2);\r\n\r\n	/* This part trims the title */\r\n	/* By @toscho - see http://toscho.de/2009/php-funktion-end_on_word/ */\r\n\r\n	if(strlen($title) > $tweetspace){\r\n		$title = substr($title, 0, $tweetspace);\r\n		$arr   = explode(\' \', trim($title) );\r\n		array_pop($arr);\r\n		$title = rtrim( implode(\' \', $arr), \',;\');\r\n		$title = $title.\"?\";\r\n	}\r\n\r\n	$message = $tweetpre.\" \".$title.\" \".$href;\r\n\r\n	return \'<a href=\"http://twitter.com/home?status=\'.urlencode($message).\'\" class=\"\'.$class.\'\">\'.$linktext.\'</a>\';\r\n\r\n}\r\n\r\nfunction egg_shorturl_handler() {\r\n\r\n	$subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu);\r\n	$regsafesubpath = preg_quote($subpath, \'/\');\r\n	$req = preg_replace(\"/^$regsafesubpath/i\",\'/\',$_SERVER[\'REQUEST_URI\']);\r\n\r\n	$qs = strpos($req, \'?\');\r\n	$qatts = ($qs ? \'&\'.substr($req, $qs + 1) : \'\');\r\n	if ($qs) $req = substr($req, 0, $qs);\r\n\r\n	$parts = array_values(array_filter(split(\'/\', $req)));\r\n	$rs = safe_row(\"Section, url_title\",\"textpattern\", \"id=\".$parts[1]);\r\n	\r\n	extract($rs);\r\n	\r\n	$url = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$Section.\'/\'.$url_title;\r\n	\r\n	header(\'Location: \' . $url);\r\n	txp_die(\'\', \'301\');\r\n}', '# <?php // Tricking Textmate into displaying syntax highlighting\r\n\r\nfunction egg_shorturl_link() {\r\n	if (isset($GLOBALS[\'thisarticle\'][\'thisid\'])) {\r\n		return \'<link rel=\"shorturl\" href=\"http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'\" />\';\r\n	}\r\n}\r\n\r\nfunction egg_shorturl_a() {\r\n	return \'<a href=\"http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'\" rel=\"shorturl\">\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'].\'</a>\';\r\n}\r\n\r\nfunction egg_shorturl_tweet($atts) {\r\n\r\n	if (is_array($atts)) extract($atts);\r\n\r\n	$linktext = isset($linktext) ? $linktext : \'Diesen Artikel twittern\';\r\n	$tweetpre = isset($tweetpre) ? $tweetpre : \'Lese gerade:\'; \r\n	$class = isset($class) ? $class : \'tweetme\'; \r\n	$href = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$GLOBALS[\'thisarticle\'][\'thisid\'];\r\n\r\n	$title = $GLOBALS[\'thisarticle\'][\'title\'];\r\n\r\n	$tweetspace = 140 - (strlen($tweetpre) + strlen($href) + 2);\r\n\r\n	/* This part trims the title */\r\n	/* By @toscho - see http://toscho.de/2009/php-funktion-end_on_word/ */\r\n\r\n	if(strlen($title) > $tweetspace){\r\n		$title = substr($title, 0, $tweetspace);\r\n		$arr   = explode(\' \', trim($title) );\r\n		array_pop($arr);\r\n		$title = rtrim( implode(\' \', $arr), \',;\');\r\n		$title = $title.\"?\";\r\n	}\r\n\r\n	$message = $tweetpre.\" \".$title.\" \".$href;\r\n\r\n	return \'<a href=\"http://twitter.com/home?status=\'.urlencode($message).\'\" class=\"\'.$class.\'\">\'.$linktext.\'</a>\';\r\n\r\n}\r\n\r\nfunction egg_shorturl_handler() {\r\n\r\n	$subpath = preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\",\"$1\",hu);\r\n	$regsafesubpath = preg_quote($subpath, \'/\');\r\n	$req = preg_replace(\"/^$regsafesubpath/i\",\'/\',$_SERVER[\'REQUEST_URI\']);\r\n\r\n	$qs = strpos($req, \'?\');\r\n	$qatts = ($qs ? \'&\'.substr($req, $qs + 1) : \'\');\r\n	if ($qs) $req = substr($req, 0, $qs);\r\n\r\n	$parts = array_values(array_filter(split(\'/\', $req)));\r\n	$rs = safe_row(\"Section, url_title\",\"textpattern\", \"id=\".$parts[1]);\r\n	\r\n	extract($rs);\r\n	\r\n	$url = \'http://\'.$GLOBALS[\'prefs\'][\'siteurl\'].$GLOBALS[\'prefs\'][\'path_from_root\'].$Section.\'/\'.$url_title;\r\n	\r\n	header(\'Location: \' . $url);\r\n	txp_die(\'\', \'301\');\r\n}', '1851597fb195f66a0eb7ad46ff7c3cfd', '', '', 0, 5, 0),
('mg_setheader', 0, 'Mike Gravel', 'http://www.dev.michaelgravel.com', '0.1', 'Sets proper header for xml pages', '<p><code>&lt;txp:mg_setheader /&gt;</code> is used on a page template and sets the content type header for that page.  Two attributes:</p>\n\n<p><strong>name</strong>: Defaults to ‘Content-Type’</p>\n\n<p><strong>value</strong>: defaults to ‘text/xml’</p>', '\nfunction mg_setheader($atts) {\n		global $abc_headers;\n		extract(lAtts(array(\n			\'name\'  => \'Content-Type\',\n			\'value\'  => \'text/xml\',\n		),$atts));\n		$abc_headers[$name] = $value;\n		if (!in_array(\'abc_headercallback\', ob_list_handlers() ))\n			ob_start(\'abc_headercallback\');\n}\n\nfunction abc_headercallback($buffer) {\n		global $abc_headers;\n		if (isset($abc_headers) && is_array($abc_headers))\n			foreach ($abc_headers as $name => $value)\n				header(\"$name: $value\");\n		return $buffer;\n}\n', '\nfunction mg_setheader($atts) {\n		global $abc_headers;\n		extract(lAtts(array(\n			\'name\'  => \'Content-Type\',\n			\'value\'  => \'text/xml\',\n		),$atts));\n		$abc_headers[$name] = $value;\n		if (!in_array(\'abc_headercallback\', ob_list_handlers() ))\n			ob_start(\'abc_headercallback\');\n}\n\nfunction abc_headercallback($buffer) {\n		global $abc_headers;\n		if (isset($abc_headers) && is_array($abc_headers))\n			foreach ($abc_headers as $name => $value)\n				header(\"$name: $value\");\n		return $buffer;\n}\n', 'ec483fbbc92f30f222351ff12485a0bc', '', '', 0, 5, 0),
('mg_comments_form', 0, 'Mike Gravel', 'http://www.dev.michaelgravel.com', '0.2', 'Replaces class on the comment posted paragraph', '<p><code>&lt;txp:mg_comments_form /&gt;</code> allows replacement of the class on the “comment posted” message.</p>\n\n<p><strong>Example:</strong></p>\n\n<p><code>&lt;txp:mg_comments_form pclass=&quot;message&quot; /&gt;</code> places the class “message” on the paragraph that appears after a comment is successfully posted.</p>', '\n// -------------------------------------------------------------\n\n	function mg_comments_form($atts)\n	{\n		global $thisarticle, $has_comments_preview, $pretext;\n\n		extract(lAtts(array(\n			\'class\'        => __FUNCTION__,\n			\'form\'         => \'comment_form\',\n			\'id\'           => @$pretext[\'id\'],\n			\'isize\'        => \'25\',\n			\'msgcols\'      => \'25\',\n			\'msgrows\'      => \'5\',\n			\'msgstyle\'     => \'\',\n			\'show_preview\' => empty($has_comments_preview),\n			\'wraptag\'      => \'\',\n			\'pclass\'       => \'action-message\',\n		), $atts));\n\n		assert_article();\n\n		if (is_array($thisarticle)) extract($thisarticle);\n\n		if (@$thisid) $id = $thisid;\n\n		$out = \'\';\n		if ($id) {\n			$ip = serverset(\'REMOTE_ADDR\');\n\n			$blacklisted = is_blacklisted($ip);\n\n			if (!checkCommentsAllowed($id)) {\n				$out = graf(gTxt(\"comments_closed\"), \' id=\"comments_closed\"\');\n			} elseif (!checkBan($ip)) {\n				$out = graf(gTxt(\'you_have_been_banned\'), \' id=\"comments_banned\"\');\n			} elseif ($blacklisted) {\n				$out = graf(gTxt(\'your_ip_is_blacklisted_by\'.\' \'.$blacklisted), \' id=\"comments_blacklisted\"\');\n			} elseif (gps(\'commented\')!==\'\') {\n				$out = gTxt(\"comment_posted\");\n				if (gps(\'commented\')===\'0\')\n					$out .= \" \". gTxt(\"comment_moderated\");\n				$out = graf($out, \' class=\"\'.$pclass.\'\"\');\n			} else {\n				# display a comment preview if required\n				if (ps(\'preview\') and $show_preview)\n					$out = comments_preview(array());\n				$out .= commentForm($id,$atts);\n			}\n\n			return (!$wraptag ? $out : doTag($out,$wraptag,$class) );\n		}\n	}\n', '\n// -------------------------------------------------------------\n\n	function mg_comments_form($atts)\n	{\n		global $thisarticle, $has_comments_preview, $pretext;\n\n		extract(lAtts(array(\n			\'class\'        => __FUNCTION__,\n			\'form\'         => \'comment_form\',\n			\'id\'           => @$pretext[\'id\'],\n			\'isize\'        => \'25\',\n			\'msgcols\'      => \'25\',\n			\'msgrows\'      => \'5\',\n			\'msgstyle\'     => \'\',\n			\'show_preview\' => empty($has_comments_preview),\n			\'wraptag\'      => \'\',\n			\'pclass\'       => \'action-message\',\n		), $atts));\n\n		assert_article();\n\n		if (is_array($thisarticle)) extract($thisarticle);\n\n		if (@$thisid) $id = $thisid;\n\n		$out = \'\';\n		if ($id) {\n			$ip = serverset(\'REMOTE_ADDR\');\n\n			$blacklisted = is_blacklisted($ip);\n\n			if (!checkCommentsAllowed($id)) {\n				$out = graf(gTxt(\"comments_closed\"), \' id=\"comments_closed\"\');\n			} elseif (!checkBan($ip)) {\n				$out = graf(gTxt(\'you_have_been_banned\'), \' id=\"comments_banned\"\');\n			} elseif ($blacklisted) {\n				$out = graf(gTxt(\'your_ip_is_blacklisted_by\'.\' \'.$blacklisted), \' id=\"comments_blacklisted\"\');\n			} elseif (gps(\'commented\')!==\'\') {\n				$out = gTxt(\"comment_posted\");\n				if (gps(\'commented\')===\'0\')\n					$out .= \" \". gTxt(\"comment_moderated\");\n				$out = graf($out, \' class=\"\'.$pclass.\'\"\');\n			} else {\n				# display a comment preview if required\n				if (ps(\'preview\') and $show_preview)\n					$out = comments_preview(array());\n				$out .= commentForm($id,$atts);\n			}\n\n			return (!$wraptag ? $out : doTag($out,$wraptag,$class) );\n		}\n	}\n', '859f4798a0c152ce16f911cd13186e4f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('esq_adminsplash', 0, '', '', '1.01', 'Admin splash page for displaying help to clients etc.', '<p>Edit this plugin to configure it to your needs, then set \'Default admin tab\' under Advanced in the Preferences tab to \'Admin Help\' (or whatever you named your tab in the config).</p><p>See the <a href=\"http://txptips.com/documentation-page-for-clients\">TXP Tip</a> or <a href=\"http://forum.textpattern.com/viewtopic.php?id=34713\">forum thread</a> for more information on <strong>esq_adminsplash</strong>.</p>', '//<?php\n/*\n* CONFIG - edit values in the array below.\n*/\nfunction esq_adminsplash_config() {\n$config = array(\n\'tab_name\' => \'Admin Help\',\n\'page_title\' => \'Textpattern Admin Help\',\n\'content_width\' => \'900px\',\n\'content_textiled\' => true,\n\'content\' => <<<EOF\nTextpattern admin help goes here.\nYou can use \"*Textile*\":http://textile.thresholdstate.com/ to style your content.\nEOF\n);\nreturn $config;\n}\n/*\n* END CONFIG - do not modify anything below unless you know what you are doing.\n*/\nif (@txpinterface == \'admin\') {\n$config = esq_adminsplash_config();\nadd_privs(\'esq_adminsplash\', \'1,2,3,4,5,6\');\nregister_tab(\'extensions\', \'esq_adminsplash\', $config[\'tab_name\']);\nregister_callback(\'esq_adminsplash\', \'esq_adminsplash\');\n}\nfunction esq_adminsplash() {\nglobal $prefs;\n$config = esq_adminsplash_config();\npagetop($config[\'page_title\']);\necho \'<div id=\"esq_adminsplashWrapper\" style=\"text-align: center;\"><div id=\"esq_adminsplashContent\" style=\"text-align: left; margin: 0 auto; width: \'.$config[\'content_width\'].\'\">\';\nif ($config[\'content_textiled\'] == true) {\n@include_once(txpath.\'/lib/classTextile.php\');\nif (class_exists(\'Textile\')) {\n$textile = new Textile();\necho $textile->TextileThis($config[\'content\']);\necho \'</div></div>\';\nreturn;\n} elseif ($prefs[\'production_status\'] != \'live\') {\necho \'<p><strong>Error: Textile not found on server.</strong></p>\';\n}\n}\necho $config[\'content\'];\necho \'</div></div>\';\n}', '//<?php\n/*\n* CONFIG - edit values in the array below.\n*/\nfunction esq_adminsplash_config() {\n$config = array(\n\'tab_name\' => \'Admin Help\',\n\'page_title\' => \'Textpattern Admin Help\',\n\'content_width\' => \'900px\',\n\'content_textiled\' => true,\n\'content\' => <<<EOF\nTextpattern admin help goes here.\nYou can use \"*Textile*\":http://textile.thresholdstate.com/ to style your content.\nEOF\n);\nreturn $config;\n}\n/*\n* END CONFIG - do not modify anything below unless you know what you are doing.\n*/\nif (@txpinterface == \'admin\') {\n$config = esq_adminsplash_config();\nadd_privs(\'esq_adminsplash\', \'1,2,3,4,5,6\');\nregister_tab(\'extensions\', \'esq_adminsplash\', $config[\'tab_name\']);\nregister_callback(\'esq_adminsplash\', \'esq_adminsplash\');\n}\nfunction esq_adminsplash() {\nglobal $prefs;\n$config = esq_adminsplash_config();\npagetop($config[\'page_title\']);\necho \'<div id=\"esq_adminsplashWrapper\" style=\"text-align: center;\"><div id=\"esq_adminsplashContent\" style=\"text-align: left; margin: 0 auto; width: \'.$config[\'content_width\'].\'\">\';\nif ($config[\'content_textiled\'] == true) {\n@include_once(txpath.\'/lib/classTextile.php\');\nif (class_exists(\'Textile\')) {\n$textile = new Textile();\necho $textile->TextileThis($config[\'content\']);\necho \'</div></div>\';\nreturn;\n} elseif ($prefs[\'production_status\'] != \'live\') {\necho \'<p><strong>Error: Textile not found on server.</strong></p>\';\n}\n}\necho $config[\'content\'];\necho \'</div></div>\';\n}', 'a7c4c3fb8e92c55545a69ac642a9ee29', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('gbp_admin_library', 0, 'Graeme Porteous', 'http://rgbp.co.uk/projects/textpattern/gbp_admin_library/', '0.4.669', 'GBP\'s Admin-Side Library', '<p>&lt;div id=“adminlib_help”&gt;</p>\n\n<h1>Graeme Porteous’ Admin Library.</h1>\n\n<p>Provides basic classes for building the admin side of your own, derived, plugins.</p>\n\n<p>&lt;/div&gt;</p>', '\n// Constants\ndefine(\'gbp_tab\', \'tab\');\ndefine(\'gbp_id\', \'id\');\n\nclass GBPPlugin {\n	// Internal variables\n	var $plugin_name;\n	var $title;\n	var $event;\n	var $message = \'\';\n	var $tabs = array();\n	var $active_tab = 0;\n	var $use_tabs = false;\n	var $gp = array();\n	var $preferences = array();\n	var $permissions = \'1,2,3,4,5,6\';\n	var $wizard_key;\n	var $wizard_installed = false;\n\n	// Constructor\n	function GBPPlugin ($title = \'\', $event = \'\', $parent_tab = \'\') {\n\n		global $txp_current_plugin;\n\n		// Store a reference to this class so we can get PHP 4 to work\n		if (version_compare(phpversion(), \'5.0.0\', \'<\'))\n			global $gbp_admin_lib_refs; $gbp_admin_lib_refs[$txp_current_plugin] = &$this;\n\n		// Get the plugin_name from the global txp_current_plugin variable\n		$this->plugin_name = $txp_current_plugin;\n\n		// When making a GBPAdminView there must be event attributes\n		$this->event = $event;\n\n		// Add privs for this event\n		global $txp_permissions;\n		$perms = @$txp_permissions[$this->permissions];\n		add_privs($this->event, ($perms ? $perms : $this->permissions));\n\n		if (@txpinterface == \'admin\') {\n			// We are admin-side.\n\n			// There must be title and event attributes\n			$this->title = $title;\n\n			// The parent_tab can only be one of four things, make sure it is\n			if ($event AND $title AND $parent_tab AND array_search($parent_tab, array(\'content\', \'presentation\', \'admin\', \'extensions\')) === false)\n				$parent_tab = \'extensions\';\n\n			// Set up the get-post array\n			$this->gp = array_merge(array(\'event\', gbp_tab), $this->gp);\n\n			// Check if our event is active, if so call preload()\n			if (gps(\'event\') == $event) {\n\n				$this->load_preferences();\n\n				$this->preload();\n\n				// Tabs should be loaded by now\n				if ($parent_tab && $this->use_tabs) {\n\n					foreach (array_keys($this->tabs) as $key) {\n						$tab = &$this->tabs[$key];\n						$tab->php_4_fix();\n						if (is_a($tab, \'GBPWizardTabView\')) {\n							$this->wizard_key = $key;\n							$this->wizard_installed = $tab->installed();\n						}\n					}\n\n					if (!$this->wizard_installed && $this->wizard_key)\n						$this->active_tab = $this->wizard_key;\n\n					// Let the active_tab know it\'s active and call it\'s preload()\n					$tab = &$this->tabs[$this->active_tab];\n					$tab->is_active = 1;\n					$tab->preload();\n				}\n			}\n\n			// Call txp functions to register this plugin\n			if ($parent_tab) {\n				register_tab($parent_tab, $event, $title);\n				register_callback(array(&$this, \'render\'), $event, null, 0);\n			}\n		}\n		if (@txpinterface == \'public\')\n			$this->load_preferences();\n	}\n\n	function load_preferences () {\n		/*\n		Grab and store all preferences with event matching this plugin, combine gbp_partial\n		rows and decode the value if it\'s of custom type.\n		*/\n		global $prefs;\n\n		// Override the default values if the prefs have been stored in the preferences table.\n		$preferences = safe_rows(\"name, html as type\",\n		\'txp_prefs\', \"event = \'{$this->event}\' AND html <> \'gbp_partial\'\");\n\n		// Add the default preferences which aren\'t saved in the db but defined in the plugin\'s source.\n		foreach ($this->preferences as $key => $pref) {\n			$db_pref = array(\'name\' => $this->plugin_name.\'_\'.$key, \'type\' => $pref[\'type\']);\n			if (array_search($db_pref, $preferences) === false)\n				$preferences[] = $db_pref + array(\'default_value\' => $pref[\'value\']);\n		}\n\n		foreach ($preferences as $name => $pref) {\n			// Extract the name and type.\n			extract($pref);\n\n			// The base name which gbp_partial preferences could share.\n			$base_name = $name;\n\n			// Combine the extended preferences, which go over two rows into one preference.\n			$i = 0; $value = \'\';\n			while (array_key_exists($name, $prefs)) {\n				$value .= $prefs[$name];\n				unset($prefs[$name]);\n				// Update name for the next array_key_exists check.\n				$name = $base_name.\'_\'.++$i;\n			}\n\n			// If there is no value then revert to the default value if it exists.\n			if ((!$value || (@!$value[0] && count($value) <= 1)) && isset($default_value))\n				$value = $default_value;\n\n			// Else if this a custom type (E.g. gbp_serialized OR gbp_array_text)\n			// call it\'s db_get method to decode it\'s value.\n			else if (is_callable(array(&$this, $type)))\n				$value = call_user_func(array(&$this, $type), \'db_get\', $value);\n\n			// Re-set the combined and decoded value to the global prefs array.\n			$prefs[$base_name] = $value;\n\n			// If the preference exists in our preference array set the new value and correct type.\n			$base_name = substr($base_name, strlen($this->plugin_name.\'_\'));\n			if (array_key_exists($base_name, $this->preferences))\n				$this->preferences[$base_name] = array(\'value\' => $value, \'type\' => $type);\n		}\n	}\n\n	function set_preference ($key, $value, $type = \'\') {\n		global $prefs, $txp_current_plugin;\n\n		// If the plugin_name or event isn\'t set is it safe to assume\n		// $txp_current_plugin and gps(\'event\') are correct?\n		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;\n		$event = ($this->event) ? $this->event : gps(\'event\');\n\n		// Set some standard db fields\n		$base_name = $plugin.\'_\'.$key;\n		$name = $base_name;\n\n		// If a type hasn\'t been specified then look the key up in our preferences.\n		// Else assume it\'s type is \'text_input\'.\n		if (empty($type) && array_key_exists($key, $this->preferences))\n			$type = $this->preferences[$key][\'type\'];\n		else if (empty($type))\n			$type = \'text_input\';\n\n		// Set the new value to the global prefs array and if the preference exists\n		// to our own preference array.\n		$prefs[$name] = $value;\n		if (array_key_exists($key, $this->preferences))\n			$this->preferences[$key] = array(\'value\' => $value, \'type\' => $type);\n\n		// If this preference has a custom type (E.g. gbp_serialized OR gbp_array_text)\n		// call it\'s db_set method to encode the value.\n		if (is_callable(array(&$this, $type)))\n			$value = call_user_func(array(&$this, $type), \'db_set\', $value);\n\n		// It is possible to leave old \'gbp_partial\' perferences when reducing the\n		// lenght of a preference. Remove them all.\n		$this->remove_preference($name);\n\n		// Make sure preferences which equal NULL are saved\n		if (empty($value))\n			set_pref($name, \'\', $event, 2, $type);\n\n		$i = 0; $value = doSlash($value);\n		// Limit preference to approximatly 4Kb of data. I hope this will be enough\n		while (strlen($value) && $i < 16) {\n			// Grab the first 255 chars from the value and strip any backward slashes which\n			// cause the SQL to break.\n			$value_segment = rtrim(substr($value, 0, 255), \'\\\\\');\n\n			// Set the preference and update name for the next array_key_exists check.\n			set_pref($name, $value_segment, $event, 2, ($i ? \'gbp_partial\' : $type));\n			$name = $base_name.\'_\'.++$i;\n\n			// Remove the segment of the value which has been saved.\n			$value = substr_replace($value, \'\', 0, strlen($value_segment));\n		}\n	}\n\n	function remove_preference ($key) {\n		$event = $this->event;\n		safe_delete(\'txp_prefs\', \"event = \'$event\' AND ((name LIKE \'$key\') OR (name LIKE \'{$key}_%\' AND html = \'gbp_partial\'))\");\n	}\n\n	function gbp_serialized ($step, $value, $item = \'\') {\n		switch (strtolower($step)) {\n			default:\n			case \'ui_in\':\n				if (!is_array($value)) $value = array($value);\n				return text_input($item, implode(\',\', $value), 50);\n			break;\n			case \'ui_out\':\n				return explode(\',\', $value);\n			break;\n			case \'db_set\':\n				return serialize($value);\n			break;\n			case \'db_get\':\n				return unserialize($value);\n			break;\n		}\n		return \'\';\n	}\n\n	function gbp_array_text ($step, $value, $item = \'\') {\n		switch (strtolower($step)) {\n			default:\n			case \'ui_in\':\n				if (!is_array($value)) $value = array($value);\n				return text_input($item, implode(\',\', $value), 50);\n			break;\n			case \'ui_out\':\n				return explode(\',\', $value);\n			break;\n			case \'db_set\':\n				return implode(\',\', $value);\n			break;\n			case \'db_get\':\n				return explode(\',\', $value);\n			break;\n		}\n		return \'\';\n	}\n\n	function &add_tab ($tab, $is_default = NULL) {\n\n		// Check to see if the tab is active\n		if (($is_default && !gps(gbp_tab)) || (gps(gbp_tab) == $tab->event))\n			$this->active_tab = count($this->tabs);\n\n		if (is_a($tab, \'GBPWizardTabView\')) {\n			$tab->parent = &$this;\n\n			// Wizard routines\n			$step = gps(\'step\');\n			if (in_array($step, array(\'setup\', \'cleanup\'))) {\n				$installation_steps = ($step == \'setup\')\n					? array_keys($tab->installation_steps)\n					: array_reverse(array_keys($tab->installation_steps));\n				foreach ($installation_steps as $key) {\n					$function = array(&$tab, $step.\'_\'.$key);\n					if (is_callable($function)) {\n						$optional = @$tab->installation_steps[$key][\'optional\'];\n						if (($optional && gps(\'optional_\'.$key)) || !$optional)\n							call_user_func($function);\n						else\n							$tab->add_report_item($tab->installation_steps[$key][$step], \'skipped\');\n					}\n				}\n			}\n		}\n\n		// Store the tab\n		$this->tabs[] = $tab;\n\n		// We\'ve got a tab, lets assume we want to use it\n		$this->use_tabs = true;\n\n		return $this;\n	}\n\n	function preload () {\n		// Override this function if you require sub tabs.\n	}\n\n	function render () {\n\n		// render() gets called because it is specified in txp\'s register_callback()\n\n		// After a callback we lose track of the current plugin in PHP 4\n		global $txp_current_plugin;\n		$txp_current_plugin = $this->plugin_name;\n\n		$this->render_header();\n		$this->main();\n\n		if ($this->use_tabs) {\n\n			$this->render_tabs();\n			$this->render_tab_main();\n		}\n\n		$this->render_footer();\n		$this->end();\n	}\n\n	function render_header () {\n\n		// Render the pagetop, a txp function\n		pagetop($this->title, $this->message);\n\n		// Once a message has been used we discard it\n		$this->message = \'\';\n	}\n\n	function render_tabs () {\n		// This table, which contains the tags, will have to be changed if any improvements\n		// happen to the admin interface\n		$out[] = \'<table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"margin-top:-2em;margin-bottom:2em;\">\';\n		$out[] = \'<tr><td align=\"center\" class=\"tabs\">\';\n		$out[] = \'<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\"><tr>\';\n\n		$style = \'style=\"padding: 0 30px;\"\';\n		// Force the wizard to be the only tab if the plugin isn\'t installed\n		if ($this->wizard_installed || !$this->wizard_key)\n			foreach (array_keys($this->tabs) as $key) {\n				// Render each tab but keep a reference to the tab so any changes made are stored\n				$tab = &$this->tabs[$key];\n				$out[] = $tab->render_tab();\n				$fn = array(&$tab , \'get_canvas_style\');\n				if (is_callable($fn)) {\n					$res = call_user_func($fn);\n					$style = (false!==$res) ? $res : $style ;\n				}\n			}\n		else {\n			$tab = &$this->tabs[$this->wizard_key];\n			$out[] = $tab->render_tab();\n			$fn = array(&$tab , \'get_canvas_style\');\n			if (is_callable($fn)) {\n				$res = call_user_func($fn);\n				$style = (false!==$res) ? $res : $style ;\n			}\n		}\n\n		$out[] = \'</tr></table>\';\n		$out[] = \'</td></tr>\';\n		$out[] = \'</table><div \'.$style.\'>\';\n\n		echo join(\'\', $out);\n	}\n\n	function main () {\n		// Override this function\n	}\n\n	function render_tab_main () {\n\n		// Call main() for the active_tab\n		$tab = &$this->tabs[$this->active_tab];\n		if (($this->wizard_installed || !$this->wizard_key || $tab->event == \'wizard\') && has_privs($this->event.\'.\'.$tab->event))\n			$tab->main();\n		else\n			echo \'<p style=\"margin-top:3em;text-align:center\">\'.gTxt(\'restricted_area\').\'</p>\';\n	}\n\n	function render_footer () {\n\n		// A simple footer\n		global $plugins_ver;\n		$out[] = \'</div>\';\n		$out[] = \'<div style=\"padding-top: 3em; text-align: center; clear: both;\">\';\n		$out[] = $this->plugin_name;\n		if (@$plugins_ver[$this->plugin_name])\n		 	$out[] = \' &#183; \' . $plugins_ver[$this->plugin_name];\n		$out[] = \'</div>\';\n\n		echo join(\'\', $out);\n	}\n\n	function end () {\n		// Override this function\n	}\n\n	function form_inputs () {\n\n		$out[] = eInput($this->event);\n\n		if ($this->use_tabs) {\n\n			$tab = $this->tabs[$this->active_tab];\n			$out[] = hInput(gbp_tab, $tab->event);\n		}\n\n		return join(\'\', $out);\n	}\n\n	function url ($vars = array(), $gp = false) {\n		/*\n		Expands $vars into a get style url and redirects to that location. These can be\n		overriden with the current get, post, session variables defined in $this->gp\n		by setting $gp = true\n		NOTE: If $vars is not an array or is empty then we assume $gp = true.\n		*/\n\n		if (!is_array($vars))\n			$vars = gpsa($this->gp);\n		else if ($gp || !count($vars))\n			$vars = array_merge(gpsa($this->gp), $vars);\n\n		foreach ($vars as $key => $value) {\n			if (!empty($value))\n				$out[] = $key.\'=\'.$value;\n		}\n\n		$script = hu.basename(txpath).\'/index.php\';\n		return $script . (isset($out)\n			? \'?\'.join(\'&\', $out)\n			: \'\');\n	}\n\n	function redirect ($url = \'\', $status = 303) {\n		/*\n		If $vars is an array, use url() to expand as an GET style url and redirect to\n		that location using the HTTP status code definition defined by $status.\n		*/\n\n		static $status_definitions = array (\n			301 => \"Moved Permanently\",\n			302 => \"Found\",\n			303 => \"See Other\",\n			307 => \"Temporary Redirect\"\n		);\n\n		if (!in_array($status, array_keys($status_definitions)))\n			$status = 303;\n\n		if (is_array($url))\n			$url = $this->url($url);\n		else {\n			$url_details = parse_url($url);\n			if (!@$url_details[\'scheme\'])\n				$url = \'http://\'.$url;\n		}\n\n		if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\'])) {\n			header(\'HTTP/1.1 \'.$status.\' \'.$status_definitions[$status]);\n			header(\'Status: \'.$status);\n			header(\'Location: \'.$url);\n			header(\'Connection: close\');\n			header(\'Content-Length: 0\');\n			exit(0);\n			} else {\n			global $sitename;\n			$url = htmlspecialchars($url);\n			echo <<<END\n			<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n			<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n			<head>\n				<title>$sitename</title>\n				<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n				<meta http-equiv=\"refresh\" content=\"0;url=$url\" />\n			</head>\n			<body>\n			<a href=\"$url\">{$status_definitions[$status]}</a>\n			</body>\n			</html>\nEND;\n		}\n	}\n\n	function pref ($key) {\n		global $prefs, $txp_current_plugin;\n\n		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;\n		$key = $plugin.\'_\'.$key;\n\n		if (@$this->preferences[$key])\n			return $this->preferences[$key][\'value\'];\n		if (@$prefs[$key])\n			return $prefs[$key];\n		return NULL;\n	}\n}\n\nclass GBPAdminTabView {\n	//	Internal variables\n	var $title;\n	var $event;\n	var $is_active;\n	var $parent;\n	var $permissions = \'1,2,3,4,5,6\';\n\n	//	Constructor\n	function GBPAdminTabView ($title, $event, &$parent, $is_default = NULL) {\n\n		$this->title = (function_exists(\'mb_convert_case\'))\n			? mb_convert_case($title, MB_CASE_TITLE, \"UTF-8\")\n			: ucwords($title);\n\n		$this->event = $event;\n\n		// Note: $this->parent only gets set correctly for PHP 5\n		$this->parent =& $parent->add_tab($this, $is_default);\n\n		// Add privs for this tab\n		global $txp_permissions;\n		$perms = @$txp_permissions[$this->permissions];\n		add_privs($this->parent->event.\'.\'.$this->event, ($perms ? $perms : $this->permissions));\n	}\n\n	function php_4_fix () {\n\n		// Fix references in PHP 4 so sub tabs can access their parent tab\n		if (version_compare(phpversion(), \'5.0.0\', \'<\')) {\n			global $txp_current_plugin, $gbp_admin_lib_refs;\n			$this->parent =& $gbp_admin_lib_refs[$txp_current_plugin];\n		}\n	}\n\n	function preload () {\n		// Override this function\n	}\n\n	function render_tab () {\n\n		// Grab the url to this tab\n		$url = $this->parent->url(array(gbp_tab => $this->event), true);\n\n		// Will need updating if any improvements happen to the admin interface\n		$out[] = \'<td class=\"\' . ($this->is_active ? \'tabup\' : \'tabdown2\');\n		$out[] = \'\" onclick=\"window.location.href=\\\'\' .$url. \'\\\'\">\';\n		$out[] = \'<a href=\"\' .$url. \'\" class=\"plain\">\' .$this->title. \'</a></td>\';\n\n		return join(\'\', $out);\n	}\n\n	function main () {\n		// Override this function\n	}\n\n	function pref ($key) {\n		return @$this->parent->pref($key);\n	}\n\n	function redirect ($vars = \'\') {\n		$this->parent->redirect($vars);\n	}\n\n	function set_preference ($key, $value, $type = \'\') {\n		return $this->parent->set_preference($key, $value, $type);\n	}\n\n	function remove_preference ($key) {\n		return $this->parent->remove_preference($key);\n	}\n\n	function url ($vars, $gp = false) {\n		return $this->parent->url($vars, $gp);\n	}\n\n	function form_inputs () {\n		return $this->parent->form_inputs();\n	}\n}\n\nclass GBPPreferenceTabView extends GBPAdminTabView {\n\n	var $permissions = \'prefs\';\n\n	function GBPPreferenceTabView (&$parent, $is_default = NULL) {\n		// Call the parent constructor\n		GBPAdminTabView::GBPAdminTabView(gTxt(\'tab_preferences\'), \'preference\', $parent, $is_default);\n	}\n\n	function preload () {\n		if (ps(\'step\') == \'prefs_save\') {\n			foreach ($this->parent->preferences as $key => $pref) {\n				extract($pref);\n				$value = ps($key);\n				if (is_callable(array(&$this->parent, $type)))\n					$value = call_user_func(array(&$this->parent, $type), \'ui_out\', $value);\n				$this->parent->set_preference($key, $value);\n			}\n		}\n	}\n\n	function main () {\n		// Make txp_prefs.php happy :)\n		global $event;\n		$event = $this->parent->event;\n\n		include_once txpath.\'/include/txp_prefs.php\';\n\n		echo\n			\'<form action=\"index.php\" method=\"post\">\',\n			startTable(\'list\');\n\n		foreach ($this->parent->preferences as $key => $pref) {\n			extract($pref);\n\n			$out = tda(gTxt($key), \' style=\"text-align:right;vertical-align:middle\"\');\n\n			switch ($type) {\n				case \'text_input\':\n					$out .= td(pref_func(\'text_input\', $key, $value, 20));\n				break;\n				default:\n					if (is_callable(array(&$this->parent, $type)))\n						$out .= td(call_user_func(array(&$this->parent, $type), \'ui_in\', $value, $key));\n					else\n						$out .= td(pref_func($type, $key, $value, 50));\n				break;\n			}\n\n			$out .= tda($this->popHelp($key), \' style=\"vertical-align:middle\"\');\n			echo tr($out);\n		}\n\n		echo\n			tr(tda(fInput(\'submit\', \'Submit\', gTxt(\'save_button\'), \'publish\'), \' colspan=\"3\" class=\"noline\"\')),\n			endTable(),\n			$this->parent->form_inputs(),\n			sInput(\'prefs_save\'),\n			\'</form>\';\n	}\n\n	function popHelp ($helpvar) {\n		$script = hu.basename(txpath).\'/index.php\';\n		return \'<a href=\"\'.$script.\'?event=plugin&step=plugin_help&name=\'.$this->parent->plugin_name.\'#\'.$helpvar.\'\" class=\"pophelp\">?</a>\';\n	}\n}\n\nclass GBPWizardTabView extends GBPAdminTabView {\n\n	var $installation_steps = array();\n	var $wiz_report = array();\n	var $permissions = \'admin.edit\';\n\n	function GBPWizardTabView (&$parent, $is_default = NULL, $title = \'Wizards\') {\n		global $textarray;\n\n		#\n		#	Get the strings and merge into the textarray before we get the steps...\n		#\n		$strings = $this->get_strings();\n		$textarray = array_merge($strings , $textarray);\n\n		#\n		#	Now get the steps...\n		#\n		$this->installation_steps = $this->get_steps();\n\n		// Call the parent constructor\n		GBPAdminTabView::GBPAdminTabView($title, \'wizard\', $parent, $is_default);\n	}\n\n	function get_steps () {\n		#\n		#	Override this method in derived classes to return the appropriate setup/cleanup steps.\n		#\n		$steps = array(\n			\'basic\' 		=> array(\'setup\' => \'Basic setup step\', \'cleanup\' => \'Basic cleanup step\'),\n			\'optional\'		=> array(\'setup\' => \'Optional setup step\', \'cleanup\' => \'Optional cleanup step\', \'optional\' => true , \'checked\' => 0),\n			\'has_options\'	=> array(\'setup\' => \'Setup step with a option\', \'cleanup\' => \'Cleanup step with a option\', \'has_options\' => true),\n		);\n		return $steps;\n	}\n\n	function get_strings ($language = \'\') {\n		#\n		#	Override this function in derived classes to define/change the set of strings to\n		# inject into $textarray to localise the wizard.\n		#\n		$strings = array(\n			\'gbp_adlib_wiz-version_errors\'		=> \'Version Errors\',\n			\'gbp_adlib_wiz-version_reason\'		=> \'This plugin cannot operate in this installation because&#8230;\',\n			\'gbp_adlib_wiz-version_item\'		=> \'It requires <strong class=\"failure\">{name} {min}</strong> or above, current install is {current}.\',\n			\'gbp_adlib_wiz-setup\' 				=> \'Setup\',\n			\'gbp_adlib_wiz-setup_steps\' 		=> \'The following setup steps will be taken&#8230;\',\n			\'gbp_adlib_wiz-setup_report\'		=> \'Setup Report&#8230;\',\n			\'gbp_adlib_wiz-cleanup\' 			=> \'Cleanup\',\n			\'gbp_adlib_wiz-cleanup_steps\' 		=> \'The following cleanup steps will be taken&#8230;\',\n			\'gbp_adlib_wiz-cleanup_report\'		=> \'Cleanup Report&#8230;\',\n			\'gbp_adlib_wiz-cleanup_next\' 		=> \'The plugin can now be disabled and/or uninstalled.\',\n			\'gbp_adlib_wiz-done\' 				=> \'Done\',\n			\'gbp_adlib_wiz-skipped\'				=> \'Skipped\',\n			\'gbp_adlib_wiz-failed\'				=> \'Failure\',\n			\'gbp_adlib_wiz-step_basic\'			=> \'Basic Step\',\n			\'gbp_adlib_wiz-step_optional\'		=> \'Optional step\',\n			\'gbp_adlib_wiz-step_complex\'		=> \'Step with option(s)\',\n			\'gbp_adlib_wiz-step_complex_txt\'	=> \'This {step} step has an option/options.\',\n		);\n		return $strings;\n	}\n\n	function versions_ok () {\n		$msg = \'\';\n\n		#\n		#	Check the plugin can run in this environment.\n		#\n		#	Return: TRUE -> Yes.\n		#	HTML formatted string -> No, and explain why.\n		#\n		$tests = $this->get_required_versions();\n		if (count($tests))\n			foreach ($tests as $name => $versions) {\n				if( array_key_exists( \'custom_handler\' , $versions ) && is_callable( $versions[\'custom_handler\'] ) ) {\n					#\n					# Allow derived classes to define their own checking routines...\n					#\n					$fn = $versions[\'custom_handler\'];\n					$res = call_user_func( $fn , $name , $versions );\n					if( $res )\n						$msg[] = tag($res , \'li\', \' style=\"text-align: left; padding-top: 0.75em;\"\');\n				}\n				else {\n					if (version_compare($versions[\'current\'], $versions[\'min\'] , \'<\')) {\n						$res = gTxt(\'gbp_adlib_wiz-version_item\' , array(\'{name}\' => $name , \'{min}\' => $versions[\'min\'] , \'{current}\' => $versions[\'current\']));\n						$msg[] = tag($res , \'li\', \' style=\"text-align: left; padding-top: 0.75em;\"\');\n					}\n				}\n			}\n\n		if (!empty($msg))\n			return tag(join(\'\' , $msg) , \'ol\');\n\n		return true;\n	}\n\n	function get_required_versions () {\n		global $prefs;\n\n		#\n		#	Override this function to return an array of tests to be carried out.\n		#\n		$tests = array(\'TxP\' => array(\n			\'current\'	=> $prefs[\'version\'],\n			\'min\'		=> \'4.0.3\',\n		));\n		return $tests;\n	}\n\n	function main () {\n		$out[] = \'<style type=\"text/css\"> .success { color: #009900; } .failure { color: #FF0000; } .skipped { color: #0000FF; } </style>\';\n		$out[] = \'<div style=\"border: 1px solid gray; width: 50em; text-align: center; margin: 1em auto; padding: 1em; clear: both;\">\';\n\n		$feaildset_style = \' style=\"text-align: left; padding: 1em 0\"\';\n\n		$step = gps(\'step\');\n		if (empty($step)) {\n			$result = $this->versions_ok();\n			if (is_string($result))\n				$step = \'version_error\';\n			else\n				$step = ($this->installed()) ? \'cleanup-verify\' : \'setup-verify\';\n\n			$_POST[\'step\'] = $step;\n		}\n\n		switch ($step) {\n			case \'version_error\':\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-version_errors\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-version_reason\'));\n				$out[] = $result;\n			break;\n\n			case \'setup-verify\':\n			// Render the setup wizard initial step...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-setup\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-setup_steps\'));\n				$out[] = tag(tag($this->wizard_steps(\'setup\') , \'ol\') , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\', gTxt(\'gbp_adlib_wiz-setup\'), \'\');\n				$out[] = $this->form_inputs();\n				$out[] = sInput(\'setup\');\n			break;\n\n			case \'setup\':\n			// Render the post-setup screen...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-setup_report\') , 1);\n				$out[] = tag($this->wizard_report() , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\' , gTxt(\'next\') , \'\');\n				$out[] = eInput($this->parent->event);\n				$out[] = hInput(gbp_tab, \'preference\');\n			break;\n\n			case \'cleanup-verify\':\n			// Render the cleanup wizard initial step...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-cleanup\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-cleanup_steps\'));\n				$out[] = tag(tag($this->wizard_steps(\'cleanup\') , \'ol\') , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\', gTxt(\'gbp_adlib_wiz-cleanup\'), \'\');\n				$out[] = $this->form_inputs();\n				$out[] = sInput(\'cleanup\');\n			break;\n\n			case \'cleanup\':\n			// Render the post-cleanup screen...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-cleanup_report\') , 1);\n				$out[] = tag($this->wizard_report() , \'fieldset\', $feaildset_style);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-cleanup_next\'));\n				$out[] = fInput(\'submit\', \'\' , gTxt(\'next\') , \'\');\n				$out[] = eInput(\'plugin\');\n			break;\n		}\n\n		$out[] = \'</div>\';\n\n		$verify = (in_array($step, array(\'setup-verify\', \'cleanup-verify\')))\n			? \"verify(\'\".doSlash(gTxt(\'are_you_sure\')).\"\')\"\n			: \'\';\n\n		echo form(join(n, $out), \'\', $verify);\n	}\n\n	function installed () {\n		return false;\n	}\n\n	function wizard_steps ($step) {\n		$step_details = \'\';\n\n		foreach ($this->installation_steps as $key => $detail) {\n			if (@$detail[$step]) {\n				$options = \'\';\n				if (@$detail[\'has_options\']) {\n					$function = array(&$this, \'option_\'.$key);\n					if (is_callable($function))\n						$options = n.tag(call_user_func($function, $step), \'span\', \' id=\"wizard_\'.$key.\'\" style=\"display: block; margin-right: 1em; padding: 0.5em; background-color: #eee;\"\');\n				}\n\n				$checkbox = \'\';\n				if (@$detail[\'optional\']) {\n					$checked = (isset($detail[\'checked\'])) ? $detail[\'checked\'] : 0 ;\n					$checkbox = checkbox2(\'optional_\'.$key, $checked, ($options ? \'\" onclick=\"toggleDisplay(\\\'wizard_\'.$key.\'\\\');\' : \'\'));\n				}\n\n				$step_details .= n.tag(graf(\n					tag($detail[$step].$checkbox, \'label\').$options\n				), \'li\');\n			}\n		}\n\n		return $step_details.n;\n	}\n\n	function wizard_report () {\n		// Render the wizard report as an ordered list. There maybe\n		// \'sub\' reports which we need to also render as ordered lists\n		$out = array();\n		foreach ($this->wiz_report as $report) {\n			$out_sub = array();\n\n			// Skip the first element as it is in fact the parent report\n			next($report);\n\n			// Lets generate a sub report - if there are more elements\n			while (list($key, $report_sub) = each($report))\n				$out_sub[] = tag($report_sub , \'li\');\n\n			// Check to see if we actually have a sub report - tag it as necessary\n			$out_sub = (count($out_sub) > 0)\n				? tag(join(n , $out_sub), \'ol\')\n				: \'\';\n\n			$out[] = tag($report[0] . $out_sub , \'li\');\n		}\n		return tag(join(n , $out) , \'ol\');\n	}\n\n	function add_report_item ($string , $ok = NULL, $sub = false) {\n		if (isset($ok)) {\n			switch ($ok) {\n				case \'1\' :\n					$class = \'success\';\n					$okfail = gTxt(\'gbp_adlib_wiz-done\');\n				break;\n\n				default :\n				case \'0\' :\n					$class = \'failure\';\n					$okfail = gTxt(\'gbp_adlib_wiz-failed\');\n				break;\n\n				case \'skipped\' :\n					$class = \'skipped\';\n					$okfail = gTxt(\'gbp_adlib_wiz-skipped\');\n				break;\n			}\n			$okfail = \' : <span class=\"\'.$class.\'\">\'.tag($okfail, \'strong\').\'</span>\';\n		}\n\n		$line = graf($string . (isset($okfail) ? $okfail : \'\'));\n\n		if ($sub && count($this->wiz_report) > 0)\n			$this->wiz_report[count($this->wiz_report) - 1][] = $line;\n		else\n			$this->wiz_report[] = array($line);\n	}\n\n	function setup_basic () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_basic\'), true);\n	}\n\n	function cleanup_basic () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_basic\'), false);\n	}\n\n	function setup_optional () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_optional\'), true);\n	}\n\n	function cleanup_optional () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_optional\'), false);\n	}\n\n	function setup_has_options () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_complex\'), true);\n	}\n\n	function cleanup_has_options () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_complex\'), false);\n	}\n\n	function option_has_options ($step) {\n		return graf(gTxt(\'gbp_adlib_wiz-step_complex_txt\' , array(\'{step}\' => $step))).yesnoRadio(\'wizard_has_options_test\', 1);\n	}\n}\n', '\n// Constants\ndefine(\'gbp_tab\', \'tab\');\ndefine(\'gbp_id\', \'id\');\n\nclass GBPPlugin {\n	// Internal variables\n	var $plugin_name;\n	var $title;\n	var $event;\n	var $message = \'\';\n	var $tabs = array();\n	var $active_tab = 0;\n	var $use_tabs = false;\n	var $gp = array();\n	var $preferences = array();\n	var $permissions = \'1,2,3,4,5,6\';\n	var $wizard_key;\n	var $wizard_installed = false;\n\n	// Constructor\n	function GBPPlugin ($title = \'\', $event = \'\', $parent_tab = \'\') {\n\n		global $txp_current_plugin;\n\n		// Store a reference to this class so we can get PHP 4 to work\n		if (version_compare(phpversion(), \'5.0.0\', \'<\'))\n			global $gbp_admin_lib_refs; $gbp_admin_lib_refs[$txp_current_plugin] = &$this;\n\n		// Get the plugin_name from the global txp_current_plugin variable\n		$this->plugin_name = $txp_current_plugin;\n\n		// When making a GBPAdminView there must be event attributes\n		$this->event = $event;\n\n		// Add privs for this event\n		global $txp_permissions;\n		$perms = @$txp_permissions[$this->permissions];\n		add_privs($this->event, ($perms ? $perms : $this->permissions));\n\n		if (@txpinterface == \'admin\') {\n			// We are admin-side.\n\n			// There must be title and event attributes\n			$this->title = $title;\n\n			// The parent_tab can only be one of four things, make sure it is\n			if ($event AND $title AND $parent_tab AND array_search($parent_tab, array(\'content\', \'presentation\', \'admin\', \'extensions\')) === false)\n				$parent_tab = \'extensions\';\n\n			// Set up the get-post array\n			$this->gp = array_merge(array(\'event\', gbp_tab), $this->gp);\n\n			// Check if our event is active, if so call preload()\n			if (gps(\'event\') == $event) {\n\n				$this->load_preferences();\n\n				$this->preload();\n\n				// Tabs should be loaded by now\n				if ($parent_tab && $this->use_tabs) {\n\n					foreach (array_keys($this->tabs) as $key) {\n						$tab = &$this->tabs[$key];\n						$tab->php_4_fix();\n						if (is_a($tab, \'GBPWizardTabView\')) {\n							$this->wizard_key = $key;\n							$this->wizard_installed = $tab->installed();\n						}\n					}\n\n					if (!$this->wizard_installed && $this->wizard_key)\n						$this->active_tab = $this->wizard_key;\n\n					// Let the active_tab know it\'s active and call it\'s preload()\n					$tab = &$this->tabs[$this->active_tab];\n					$tab->is_active = 1;\n					$tab->preload();\n				}\n			}\n\n			// Call txp functions to register this plugin\n			if ($parent_tab) {\n				register_tab($parent_tab, $event, $title);\n				register_callback(array(&$this, \'render\'), $event, null, 0);\n			}\n		}\n		if (@txpinterface == \'public\')\n			$this->load_preferences();\n	}\n\n	function load_preferences () {\n		/*\n		Grab and store all preferences with event matching this plugin, combine gbp_partial\n		rows and decode the value if it\'s of custom type.\n		*/\n		global $prefs;\n\n		// Override the default values if the prefs have been stored in the preferences table.\n		$preferences = safe_rows(\"name, html as type\",\n		\'txp_prefs\', \"event = \'{$this->event}\' AND html <> \'gbp_partial\'\");\n\n		// Add the default preferences which aren\'t saved in the db but defined in the plugin\'s source.\n		foreach ($this->preferences as $key => $pref) {\n			$db_pref = array(\'name\' => $this->plugin_name.\'_\'.$key, \'type\' => $pref[\'type\']);\n			if (array_search($db_pref, $preferences) === false)\n				$preferences[] = $db_pref + array(\'default_value\' => $pref[\'value\']);\n		}\n\n		foreach ($preferences as $name => $pref) {\n			// Extract the name and type.\n			extract($pref);\n\n			// The base name which gbp_partial preferences could share.\n			$base_name = $name;\n\n			// Combine the extended preferences, which go over two rows into one preference.\n			$i = 0; $value = \'\';\n			while (array_key_exists($name, $prefs)) {\n				$value .= $prefs[$name];\n				unset($prefs[$name]);\n				// Update name for the next array_key_exists check.\n				$name = $base_name.\'_\'.++$i;\n			}\n\n			// If there is no value then revert to the default value if it exists.\n			if ((!$value || (@!$value[0] && count($value) <= 1)) && isset($default_value))\n				$value = $default_value;\n\n			// Else if this a custom type (E.g. gbp_serialized OR gbp_array_text)\n			// call it\'s db_get method to decode it\'s value.\n			else if (is_callable(array(&$this, $type)))\n				$value = call_user_func(array(&$this, $type), \'db_get\', $value);\n\n			// Re-set the combined and decoded value to the global prefs array.\n			$prefs[$base_name] = $value;\n\n			// If the preference exists in our preference array set the new value and correct type.\n			$base_name = substr($base_name, strlen($this->plugin_name.\'_\'));\n			if (array_key_exists($base_name, $this->preferences))\n				$this->preferences[$base_name] = array(\'value\' => $value, \'type\' => $type);\n		}\n	}\n\n	function set_preference ($key, $value, $type = \'\') {\n		global $prefs, $txp_current_plugin;\n\n		// If the plugin_name or event isn\'t set is it safe to assume\n		// $txp_current_plugin and gps(\'event\') are correct?\n		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;\n		$event = ($this->event) ? $this->event : gps(\'event\');\n\n		// Set some standard db fields\n		$base_name = $plugin.\'_\'.$key;\n		$name = $base_name;\n\n		// If a type hasn\'t been specified then look the key up in our preferences.\n		// Else assume it\'s type is \'text_input\'.\n		if (empty($type) && array_key_exists($key, $this->preferences))\n			$type = $this->preferences[$key][\'type\'];\n		else if (empty($type))\n			$type = \'text_input\';\n\n		// Set the new value to the global prefs array and if the preference exists\n		// to our own preference array.\n		$prefs[$name] = $value;\n		if (array_key_exists($key, $this->preferences))\n			$this->preferences[$key] = array(\'value\' => $value, \'type\' => $type);\n\n		// If this preference has a custom type (E.g. gbp_serialized OR gbp_array_text)\n		// call it\'s db_set method to encode the value.\n		if (is_callable(array(&$this, $type)))\n			$value = call_user_func(array(&$this, $type), \'db_set\', $value);\n\n		// It is possible to leave old \'gbp_partial\' perferences when reducing the\n		// lenght of a preference. Remove them all.\n		$this->remove_preference($name);\n\n		// Make sure preferences which equal NULL are saved\n		if (empty($value))\n			set_pref($name, \'\', $event, 2, $type);\n\n		$i = 0; $value = doSlash($value);\n		// Limit preference to approximatly 4Kb of data. I hope this will be enough\n		while (strlen($value) && $i < 16) {\n			// Grab the first 255 chars from the value and strip any backward slashes which\n			// cause the SQL to break.\n			$value_segment = rtrim(substr($value, 0, 255), \'\\\\\');\n\n			// Set the preference and update name for the next array_key_exists check.\n			set_pref($name, $value_segment, $event, 2, ($i ? \'gbp_partial\' : $type));\n			$name = $base_name.\'_\'.++$i;\n\n			// Remove the segment of the value which has been saved.\n			$value = substr_replace($value, \'\', 0, strlen($value_segment));\n		}\n	}\n\n	function remove_preference ($key) {\n		$event = $this->event;\n		safe_delete(\'txp_prefs\', \"event = \'$event\' AND ((name LIKE \'$key\') OR (name LIKE \'{$key}_%\' AND html = \'gbp_partial\'))\");\n	}\n\n	function gbp_serialized ($step, $value, $item = \'\') {\n		switch (strtolower($step)) {\n			default:\n			case \'ui_in\':\n				if (!is_array($value)) $value = array($value);\n				return text_input($item, implode(\',\', $value), 50);\n			break;\n			case \'ui_out\':\n				return explode(\',\', $value);\n			break;\n			case \'db_set\':\n				return serialize($value);\n			break;\n			case \'db_get\':\n				return unserialize($value);\n			break;\n		}\n		return \'\';\n	}\n\n	function gbp_array_text ($step, $value, $item = \'\') {\n		switch (strtolower($step)) {\n			default:\n			case \'ui_in\':\n				if (!is_array($value)) $value = array($value);\n				return text_input($item, implode(\',\', $value), 50);\n			break;\n			case \'ui_out\':\n				return explode(\',\', $value);\n			break;\n			case \'db_set\':\n				return implode(\',\', $value);\n			break;\n			case \'db_get\':\n				return explode(\',\', $value);\n			break;\n		}\n		return \'\';\n	}\n\n	function &add_tab ($tab, $is_default = NULL) {\n\n		// Check to see if the tab is active\n		if (($is_default && !gps(gbp_tab)) || (gps(gbp_tab) == $tab->event))\n			$this->active_tab = count($this->tabs);\n\n		if (is_a($tab, \'GBPWizardTabView\')) {\n			$tab->parent = &$this;\n\n			// Wizard routines\n			$step = gps(\'step\');\n			if (in_array($step, array(\'setup\', \'cleanup\'))) {\n				$installation_steps = ($step == \'setup\')\n					? array_keys($tab->installation_steps)\n					: array_reverse(array_keys($tab->installation_steps));\n				foreach ($installation_steps as $key) {\n					$function = array(&$tab, $step.\'_\'.$key);\n					if (is_callable($function)) {\n						$optional = @$tab->installation_steps[$key][\'optional\'];\n						if (($optional && gps(\'optional_\'.$key)) || !$optional)\n							call_user_func($function);\n						else\n							$tab->add_report_item($tab->installation_steps[$key][$step], \'skipped\');\n					}\n				}\n			}\n		}\n\n		// Store the tab\n		$this->tabs[] = $tab;\n\n		// We\'ve got a tab, lets assume we want to use it\n		$this->use_tabs = true;\n\n		return $this;\n	}\n\n	function preload () {\n		// Override this function if you require sub tabs.\n	}\n\n	function render () {\n\n		// render() gets called because it is specified in txp\'s register_callback()\n\n		// After a callback we lose track of the current plugin in PHP 4\n		global $txp_current_plugin;\n		$txp_current_plugin = $this->plugin_name;\n\n		$this->render_header();\n		$this->main();\n\n		if ($this->use_tabs) {\n\n			$this->render_tabs();\n			$this->render_tab_main();\n		}\n\n		$this->render_footer();\n		$this->end();\n	}\n\n	function render_header () {\n\n		// Render the pagetop, a txp function\n		pagetop($this->title, $this->message);\n\n		// Once a message has been used we discard it\n		$this->message = \'\';\n	}\n\n	function render_tabs () {\n		// This table, which contains the tags, will have to be changed if any improvements\n		// happen to the admin interface\n		$out[] = \'<table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"margin-top:-2em;margin-bottom:2em;\">\';\n		$out[] = \'<tr><td align=\"center\" class=\"tabs\">\';\n		$out[] = \'<table cellpadding=\"0\" cellspacing=\"0\" align=\"center\"><tr>\';\n\n		$style = \'style=\"padding: 0 30px;\"\';\n		// Force the wizard to be the only tab if the plugin isn\'t installed\n		if ($this->wizard_installed || !$this->wizard_key)\n			foreach (array_keys($this->tabs) as $key) {\n				// Render each tab but keep a reference to the tab so any changes made are stored\n				$tab = &$this->tabs[$key];\n				$out[] = $tab->render_tab();\n				$fn = array(&$tab , \'get_canvas_style\');\n				if (is_callable($fn)) {\n					$res = call_user_func($fn);\n					$style = (false!==$res) ? $res : $style ;\n				}\n			}\n		else {\n			$tab = &$this->tabs[$this->wizard_key];\n			$out[] = $tab->render_tab();\n			$fn = array(&$tab , \'get_canvas_style\');\n			if (is_callable($fn)) {\n				$res = call_user_func($fn);\n				$style = (false!==$res) ? $res : $style ;\n			}\n		}\n\n		$out[] = \'</tr></table>\';\n		$out[] = \'</td></tr>\';\n		$out[] = \'</table><div \'.$style.\'>\';\n\n		echo join(\'\', $out);\n	}\n\n	function main () {\n		// Override this function\n	}\n\n	function render_tab_main () {\n\n		// Call main() for the active_tab\n		$tab = &$this->tabs[$this->active_tab];\n		if (($this->wizard_installed || !$this->wizard_key || $tab->event == \'wizard\') && has_privs($this->event.\'.\'.$tab->event))\n			$tab->main();\n		else\n			echo \'<p style=\"margin-top:3em;text-align:center\">\'.gTxt(\'restricted_area\').\'</p>\';\n	}\n\n	function render_footer () {\n\n		// A simple footer\n		global $plugins_ver;\n		$out[] = \'</div>\';\n		$out[] = \'<div style=\"padding-top: 3em; text-align: center; clear: both;\">\';\n		$out[] = $this->plugin_name;\n		if (@$plugins_ver[$this->plugin_name])\n		 	$out[] = \' &#183; \' . $plugins_ver[$this->plugin_name];\n		$out[] = \'</div>\';\n\n		echo join(\'\', $out);\n	}\n\n	function end () {\n		// Override this function\n	}\n\n	function form_inputs () {\n\n		$out[] = eInput($this->event);\n\n		if ($this->use_tabs) {\n\n			$tab = $this->tabs[$this->active_tab];\n			$out[] = hInput(gbp_tab, $tab->event);\n		}\n\n		return join(\'\', $out);\n	}\n\n	function url ($vars = array(), $gp = false) {\n		/*\n		Expands $vars into a get style url and redirects to that location. These can be\n		overriden with the current get, post, session variables defined in $this->gp\n		by setting $gp = true\n		NOTE: If $vars is not an array or is empty then we assume $gp = true.\n		*/\n\n		if (!is_array($vars))\n			$vars = gpsa($this->gp);\n		else if ($gp || !count($vars))\n			$vars = array_merge(gpsa($this->gp), $vars);\n\n		foreach ($vars as $key => $value) {\n			if (!empty($value))\n				$out[] = $key.\'=\'.$value;\n		}\n\n		$script = hu.basename(txpath).\'/index.php\';\n		return $script . (isset($out)\n			? \'?\'.join(\'&\', $out)\n			: \'\');\n	}\n\n	function redirect ($url = \'\', $status = 303) {\n		/*\n		If $vars is an array, use url() to expand as an GET style url and redirect to\n		that location using the HTTP status code definition defined by $status.\n		*/\n\n		static $status_definitions = array (\n			301 => \"Moved Permanently\",\n			302 => \"Found\",\n			303 => \"See Other\",\n			307 => \"Temporary Redirect\"\n		);\n\n		if (!in_array($status, array_keys($status_definitions)))\n			$status = 303;\n\n		if (is_array($url))\n			$url = $this->url($url);\n		else {\n			$url_details = parse_url($url);\n			if (!@$url_details[\'scheme\'])\n				$url = \'http://\'.$url;\n		}\n\n		if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\'])) {\n			header(\'HTTP/1.1 \'.$status.\' \'.$status_definitions[$status]);\n			header(\'Status: \'.$status);\n			header(\'Location: \'.$url);\n			header(\'Connection: close\');\n			header(\'Content-Length: 0\');\n			exit(0);\n			} else {\n			global $sitename;\n			$url = htmlspecialchars($url);\n			echo <<<END\n			<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n			<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n			<head>\n				<title>$sitename</title>\n				<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n				<meta http-equiv=\"refresh\" content=\"0;url=$url\" />\n			</head>\n			<body>\n			<a href=\"$url\">{$status_definitions[$status]}</a>\n			</body>\n			</html>\nEND;\n		}\n	}\n\n	function pref ($key) {\n		global $prefs, $txp_current_plugin;\n\n		$plugin = ($this->plugin_name) ? $this->plugin_name : $txp_current_plugin;\n		$key = $plugin.\'_\'.$key;\n\n		if (@$this->preferences[$key])\n			return $this->preferences[$key][\'value\'];\n		if (@$prefs[$key])\n			return $prefs[$key];\n		return NULL;\n	}\n}\n\nclass GBPAdminTabView {\n	//	Internal variables\n	var $title;\n	var $event;\n	var $is_active;\n	var $parent;\n	var $permissions = \'1,2,3,4,5,6\';\n\n	//	Constructor\n	function GBPAdminTabView ($title, $event, &$parent, $is_default = NULL) {\n\n		$this->title = (function_exists(\'mb_convert_case\'))\n			? mb_convert_case($title, MB_CASE_TITLE, \"UTF-8\")\n			: ucwords($title);\n\n		$this->event = $event;\n\n		// Note: $this->parent only gets set correctly for PHP 5\n		$this->parent =& $parent->add_tab($this, $is_default);\n\n		// Add privs for this tab\n		global $txp_permissions;\n		$perms = @$txp_permissions[$this->permissions];\n		add_privs($this->parent->event.\'.\'.$this->event, ($perms ? $perms : $this->permissions));\n	}\n\n	function php_4_fix () {\n\n		// Fix references in PHP 4 so sub tabs can access their parent tab\n		if (version_compare(phpversion(), \'5.0.0\', \'<\')) {\n			global $txp_current_plugin, $gbp_admin_lib_refs;\n			$this->parent =& $gbp_admin_lib_refs[$txp_current_plugin];\n		}\n	}\n\n	function preload () {\n		// Override this function\n	}\n\n	function render_tab () {\n\n		// Grab the url to this tab\n		$url = $this->parent->url(array(gbp_tab => $this->event), true);\n\n		// Will need updating if any improvements happen to the admin interface\n		$out[] = \'<td class=\"\' . ($this->is_active ? \'tabup\' : \'tabdown2\');\n		$out[] = \'\" onclick=\"window.location.href=\\\'\' .$url. \'\\\'\">\';\n		$out[] = \'<a href=\"\' .$url. \'\" class=\"plain\">\' .$this->title. \'</a></td>\';\n\n		return join(\'\', $out);\n	}\n\n	function main () {\n		// Override this function\n	}\n\n	function pref ($key) {\n		return @$this->parent->pref($key);\n	}\n\n	function redirect ($vars = \'\') {\n		$this->parent->redirect($vars);\n	}\n\n	function set_preference ($key, $value, $type = \'\') {\n		return $this->parent->set_preference($key, $value, $type);\n	}\n\n	function remove_preference ($key) {\n		return $this->parent->remove_preference($key);\n	}\n\n	function url ($vars, $gp = false) {\n		return $this->parent->url($vars, $gp);\n	}\n\n	function form_inputs () {\n		return $this->parent->form_inputs();\n	}\n}\n\nclass GBPPreferenceTabView extends GBPAdminTabView {\n\n	var $permissions = \'prefs\';\n\n	function GBPPreferenceTabView (&$parent, $is_default = NULL) {\n		// Call the parent constructor\n		GBPAdminTabView::GBPAdminTabView(gTxt(\'tab_preferences\'), \'preference\', $parent, $is_default);\n	}\n\n	function preload () {\n		if (ps(\'step\') == \'prefs_save\') {\n			foreach ($this->parent->preferences as $key => $pref) {\n				extract($pref);\n				$value = ps($key);\n				if (is_callable(array(&$this->parent, $type)))\n					$value = call_user_func(array(&$this->parent, $type), \'ui_out\', $value);\n				$this->parent->set_preference($key, $value);\n			}\n		}\n	}\n\n	function main () {\n		// Make txp_prefs.php happy :)\n		global $event;\n		$event = $this->parent->event;\n\n		include_once txpath.\'/include/txp_prefs.php\';\n\n		echo\n			\'<form action=\"index.php\" method=\"post\">\',\n			startTable(\'list\');\n\n		foreach ($this->parent->preferences as $key => $pref) {\n			extract($pref);\n\n			$out = tda(gTxt($key), \' style=\"text-align:right;vertical-align:middle\"\');\n\n			switch ($type) {\n				case \'text_input\':\n					$out .= td(pref_func(\'text_input\', $key, $value, 20));\n				break;\n				default:\n					if (is_callable(array(&$this->parent, $type)))\n						$out .= td(call_user_func(array(&$this->parent, $type), \'ui_in\', $value, $key));\n					else\n						$out .= td(pref_func($type, $key, $value, 50));\n				break;\n			}\n\n			$out .= tda($this->popHelp($key), \' style=\"vertical-align:middle\"\');\n			echo tr($out);\n		}\n\n		echo\n			tr(tda(fInput(\'submit\', \'Submit\', gTxt(\'save_button\'), \'publish\'), \' colspan=\"3\" class=\"noline\"\')),\n			endTable(),\n			$this->parent->form_inputs(),\n			sInput(\'prefs_save\'),\n			\'</form>\';\n	}\n\n	function popHelp ($helpvar) {\n		$script = hu.basename(txpath).\'/index.php\';\n		return \'<a href=\"\'.$script.\'?event=plugin&step=plugin_help&name=\'.$this->parent->plugin_name.\'#\'.$helpvar.\'\" class=\"pophelp\">?</a>\';\n	}\n}\n\nclass GBPWizardTabView extends GBPAdminTabView {\n\n	var $installation_steps = array();\n	var $wiz_report = array();\n	var $permissions = \'admin.edit\';\n\n	function GBPWizardTabView (&$parent, $is_default = NULL, $title = \'Wizards\') {\n		global $textarray;\n\n		#\n		#	Get the strings and merge into the textarray before we get the steps...\n		#\n		$strings = $this->get_strings();\n		$textarray = array_merge($strings , $textarray);\n\n		#\n		#	Now get the steps...\n		#\n		$this->installation_steps = $this->get_steps();\n\n		// Call the parent constructor\n		GBPAdminTabView::GBPAdminTabView($title, \'wizard\', $parent, $is_default);\n	}\n\n	function get_steps () {\n		#\n		#	Override this method in derived classes to return the appropriate setup/cleanup steps.\n		#\n		$steps = array(\n			\'basic\' 		=> array(\'setup\' => \'Basic setup step\', \'cleanup\' => \'Basic cleanup step\'),\n			\'optional\'		=> array(\'setup\' => \'Optional setup step\', \'cleanup\' => \'Optional cleanup step\', \'optional\' => true , \'checked\' => 0),\n			\'has_options\'	=> array(\'setup\' => \'Setup step with a option\', \'cleanup\' => \'Cleanup step with a option\', \'has_options\' => true),\n		);\n		return $steps;\n	}\n\n	function get_strings ($language = \'\') {\n		#\n		#	Override this function in derived classes to define/change the set of strings to\n		# inject into $textarray to localise the wizard.\n		#\n		$strings = array(\n			\'gbp_adlib_wiz-version_errors\'		=> \'Version Errors\',\n			\'gbp_adlib_wiz-version_reason\'		=> \'This plugin cannot operate in this installation because&#8230;\',\n			\'gbp_adlib_wiz-version_item\'		=> \'It requires <strong class=\"failure\">{name} {min}</strong> or above, current install is {current}.\',\n			\'gbp_adlib_wiz-setup\' 				=> \'Setup\',\n			\'gbp_adlib_wiz-setup_steps\' 		=> \'The following setup steps will be taken&#8230;\',\n			\'gbp_adlib_wiz-setup_report\'		=> \'Setup Report&#8230;\',\n			\'gbp_adlib_wiz-cleanup\' 			=> \'Cleanup\',\n			\'gbp_adlib_wiz-cleanup_steps\' 		=> \'The following cleanup steps will be taken&#8230;\',\n			\'gbp_adlib_wiz-cleanup_report\'		=> \'Cleanup Report&#8230;\',\n			\'gbp_adlib_wiz-cleanup_next\' 		=> \'The plugin can now be disabled and/or uninstalled.\',\n			\'gbp_adlib_wiz-done\' 				=> \'Done\',\n			\'gbp_adlib_wiz-skipped\'				=> \'Skipped\',\n			\'gbp_adlib_wiz-failed\'				=> \'Failure\',\n			\'gbp_adlib_wiz-step_basic\'			=> \'Basic Step\',\n			\'gbp_adlib_wiz-step_optional\'		=> \'Optional step\',\n			\'gbp_adlib_wiz-step_complex\'		=> \'Step with option(s)\',\n			\'gbp_adlib_wiz-step_complex_txt\'	=> \'This {step} step has an option/options.\',\n		);\n		return $strings;\n	}\n\n	function versions_ok () {\n		$msg = \'\';\n\n		#\n		#	Check the plugin can run in this environment.\n		#\n		#	Return: TRUE -> Yes.\n		#	HTML formatted string -> No, and explain why.\n		#\n		$tests = $this->get_required_versions();\n		if (count($tests))\n			foreach ($tests as $name => $versions) {\n				if( array_key_exists( \'custom_handler\' , $versions ) && is_callable( $versions[\'custom_handler\'] ) ) {\n					#\n					# Allow derived classes to define their own checking routines...\n					#\n					$fn = $versions[\'custom_handler\'];\n					$res = call_user_func( $fn , $name , $versions );\n					if( $res )\n						$msg[] = tag($res , \'li\', \' style=\"text-align: left; padding-top: 0.75em;\"\');\n				}\n				else {\n					if (version_compare($versions[\'current\'], $versions[\'min\'] , \'<\')) {\n						$res = gTxt(\'gbp_adlib_wiz-version_item\' , array(\'{name}\' => $name , \'{min}\' => $versions[\'min\'] , \'{current}\' => $versions[\'current\']));\n						$msg[] = tag($res , \'li\', \' style=\"text-align: left; padding-top: 0.75em;\"\');\n					}\n				}\n			}\n\n		if (!empty($msg))\n			return tag(join(\'\' , $msg) , \'ol\');\n\n		return true;\n	}\n\n	function get_required_versions () {\n		global $prefs;\n\n		#\n		#	Override this function to return an array of tests to be carried out.\n		#\n		$tests = array(\'TxP\' => array(\n			\'current\'	=> $prefs[\'version\'],\n			\'min\'		=> \'4.0.3\',\n		));\n		return $tests;\n	}\n\n	function main () {\n		$out[] = \'<style type=\"text/css\"> .success { color: #009900; } .failure { color: #FF0000; } .skipped { color: #0000FF; } </style>\';\n		$out[] = \'<div style=\"border: 1px solid gray; width: 50em; text-align: center; margin: 1em auto; padding: 1em; clear: both;\">\';\n\n		$feaildset_style = \' style=\"text-align: left; padding: 1em 0\"\';\n\n		$step = gps(\'step\');\n		if (empty($step)) {\n			$result = $this->versions_ok();\n			if (is_string($result))\n				$step = \'version_error\';\n			else\n				$step = ($this->installed()) ? \'cleanup-verify\' : \'setup-verify\';\n\n			$_POST[\'step\'] = $step;\n		}\n\n		switch ($step) {\n			case \'version_error\':\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-version_errors\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-version_reason\'));\n				$out[] = $result;\n			break;\n\n			case \'setup-verify\':\n			// Render the setup wizard initial step...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-setup\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-setup_steps\'));\n				$out[] = tag(tag($this->wizard_steps(\'setup\') , \'ol\') , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\', gTxt(\'gbp_adlib_wiz-setup\'), \'\');\n				$out[] = $this->form_inputs();\n				$out[] = sInput(\'setup\');\n			break;\n\n			case \'setup\':\n			// Render the post-setup screen...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-setup_report\') , 1);\n				$out[] = tag($this->wizard_report() , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\' , gTxt(\'next\') , \'\');\n				$out[] = eInput($this->parent->event);\n				$out[] = hInput(gbp_tab, \'preference\');\n			break;\n\n			case \'cleanup-verify\':\n			// Render the cleanup wizard initial step...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-cleanup\') , 1);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-cleanup_steps\'));\n				$out[] = tag(tag($this->wizard_steps(\'cleanup\') , \'ol\') , \'fieldset\', $feaildset_style);\n				$out[] = fInput(\'submit\', \'\', gTxt(\'gbp_adlib_wiz-cleanup\'), \'\');\n				$out[] = $this->form_inputs();\n				$out[] = sInput(\'cleanup\');\n			break;\n\n			case \'cleanup\':\n			// Render the post-cleanup screen...\n				$out[] = hed(gTxt(\'gbp_adlib_wiz-cleanup_report\') , 1);\n				$out[] = tag($this->wizard_report() , \'fieldset\', $feaildset_style);\n				$out[] = graf(gTxt(\'gbp_adlib_wiz-cleanup_next\'));\n				$out[] = fInput(\'submit\', \'\' , gTxt(\'next\') , \'\');\n				$out[] = eInput(\'plugin\');\n			break;\n		}\n\n		$out[] = \'</div>\';\n\n		$verify = (in_array($step, array(\'setup-verify\', \'cleanup-verify\')))\n			? \"verify(\'\".doSlash(gTxt(\'are_you_sure\')).\"\')\"\n			: \'\';\n\n		echo form(join(n, $out), \'\', $verify);\n	}\n\n	function installed () {\n		return false;\n	}\n\n	function wizard_steps ($step) {\n		$step_details = \'\';\n\n		foreach ($this->installation_steps as $key => $detail) {\n			if (@$detail[$step]) {\n				$options = \'\';\n				if (@$detail[\'has_options\']) {\n					$function = array(&$this, \'option_\'.$key);\n					if (is_callable($function))\n						$options = n.tag(call_user_func($function, $step), \'span\', \' id=\"wizard_\'.$key.\'\" style=\"display: block; margin-right: 1em; padding: 0.5em; background-color: #eee;\"\');\n				}\n\n				$checkbox = \'\';\n				if (@$detail[\'optional\']) {\n					$checked = (isset($detail[\'checked\'])) ? $detail[\'checked\'] : 0 ;\n					$checkbox = checkbox2(\'optional_\'.$key, $checked, ($options ? \'\" onclick=\"toggleDisplay(\\\'wizard_\'.$key.\'\\\');\' : \'\'));\n				}\n\n				$step_details .= n.tag(graf(\n					tag($detail[$step].$checkbox, \'label\').$options\n				), \'li\');\n			}\n		}\n\n		return $step_details.n;\n	}\n\n	function wizard_report () {\n		// Render the wizard report as an ordered list. There maybe\n		// \'sub\' reports which we need to also render as ordered lists\n		$out = array();\n		foreach ($this->wiz_report as $report) {\n			$out_sub = array();\n\n			// Skip the first element as it is in fact the parent report\n			next($report);\n\n			// Lets generate a sub report - if there are more elements\n			while (list($key, $report_sub) = each($report))\n				$out_sub[] = tag($report_sub , \'li\');\n\n			// Check to see if we actually have a sub report - tag it as necessary\n			$out_sub = (count($out_sub) > 0)\n				? tag(join(n , $out_sub), \'ol\')\n				: \'\';\n\n			$out[] = tag($report[0] . $out_sub , \'li\');\n		}\n		return tag(join(n , $out) , \'ol\');\n	}\n\n	function add_report_item ($string , $ok = NULL, $sub = false) {\n		if (isset($ok)) {\n			switch ($ok) {\n				case \'1\' :\n					$class = \'success\';\n					$okfail = gTxt(\'gbp_adlib_wiz-done\');\n				break;\n\n				default :\n				case \'0\' :\n					$class = \'failure\';\n					$okfail = gTxt(\'gbp_adlib_wiz-failed\');\n				break;\n\n				case \'skipped\' :\n					$class = \'skipped\';\n					$okfail = gTxt(\'gbp_adlib_wiz-skipped\');\n				break;\n			}\n			$okfail = \' : <span class=\"\'.$class.\'\">\'.tag($okfail, \'strong\').\'</span>\';\n		}\n\n		$line = graf($string . (isset($okfail) ? $okfail : \'\'));\n\n		if ($sub && count($this->wiz_report) > 0)\n			$this->wiz_report[count($this->wiz_report) - 1][] = $line;\n		else\n			$this->wiz_report[] = array($line);\n	}\n\n	function setup_basic () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_basic\'), true);\n	}\n\n	function cleanup_basic () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_basic\'), false);\n	}\n\n	function setup_optional () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_optional\'), true);\n	}\n\n	function cleanup_optional () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_optional\'), false);\n	}\n\n	function setup_has_options () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_complex\'), true);\n	}\n\n	function cleanup_has_options () {\n		$this->add_report_item(gTxt(\'gbp_adlib_wiz-step_complex\'), false);\n	}\n\n	function option_has_options ($step) {\n		return graf(gTxt(\'gbp_adlib_wiz-step_complex_txt\' , array(\'{step}\' => $step))).yesnoRadio(\'wizard_has_options_test\', 1);\n	}\n}\n', '0aff37fb1d6e93d8405ef97d7b8825bd', '', '', 2, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('gbp_permanent_links', 0, 'Graeme Porteous', 'http://rgbp.co.uk/projects/textpattern/gbp_permanent_links/', '0.14.7', 'Custom permanent links rules', '<h1>gbp_permanent_links.</h1>\n\n<p>There is no plugin documentation. For help please use the <a href=\"http://forum.textpattern.com/viewtopic.php?id=18918\" rel=\"nofollow\">forum thread</a>.</p>', '// Constants\n@define(\'gbp_save\', \'save\');\n@define(\'gbp_post\', \'post\');\n@define(\'gbp_separator\', \'&~&~&\');\n\n// require_plugin() will reset the $txp_current_plugin global\nglobal $txp_current_plugin;\n$gbp_current_plugin = $txp_current_plugin;\nrequire_plugin(\'gbp_admin_library\');\n$txp_current_plugin = $gbp_current_plugin;\n\nclass PermanentLinks extends GBPPlugin\n{\n	var $preferences = array(\n		\'show_prefix\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n		\'show_suffix\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n		\'omit_trailing_slash\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'redirect_mt_style_links\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'clean_page_archive_links\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'join_pretext_to_pagelinks\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'check_pretext_category_context\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'check_pretext_section_context\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'force_lowercase_urls\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'automatically_append_title\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'use_cleaver_partial_matches\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'permlink_redirect_http_status\' => array(\'value\' => \'301\' , \'type\' => \'text_input\'),\n		\'url_redirect_http_status\' => array(\'value\' => \'302\' , \'type\' => \'text_input\'),\n		\'text_and_regex_segment_scores\' => array(\'value\' => \'0\' , \'type\' => \'text_input\'),\n		\'debug\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n	);\n	var $matched_permlink = array();\n	var $partial_matches = array();\n	var $cleaver_partial_match;\n	var $buffer_debug = array();\n\n	function preload () {\n		new PermanentLinksListTabView(\'list\', \'list\', $this);\n		new PermanentLinksBuildTabView(\'build\', \'build\', $this);\n		new GBPPreferenceTabView($this);\n	}\n\n	function main () {\n		require_privs($this->event);\n	}\n\n	function get_all_permlinks ($sort = 0, $exclude = array()) {\n		static $rs;\n		if (!isset($rs))\n			$rs = safe_column(\n				\"REPLACE(name, \'{$this->plugin_name}_\', \'\') AS id\", \'txp_prefs\',\n				\"`event` = \'{$this->event}\' AND `name` REGEXP \'^{$this->plugin_name}_.{13}$\'\"\n			);\n\n		if (@txpinterface == \'public\')\n			$this->debug(\'Loading permlinks from db\');\n		$i = 0;\n\n		$permlinks = array();\n		foreach ($rs as $id) {\n			$pl = $this->get_permlink($id);\n\n			// Don\'t try and load permalink rules from the new version\n			if (!isset($pl[\'components\']))\n				continue;\n\n			if (count($exclude) > 0)\n				foreach ($pl[\'components\'] as $pl_c) {\n					if (is_array($exclude) && in_array($pl_c[\'type\'], $exclude))\n						continue 2;\n					if (is_string($exclude) && $pl_c[\'type\'] === $exclude)\n						continue 2;\n				}\n\n			$permlinks[$id] = $pl;\n\n			if ($sort)\n				$precedence[$id] = $permlinks[$id][\'settings\'][\'pl_precedence\'];\n		}\n\n		// If more than one permanent link, sort by their precedence value.\n		if ($sort && count($permlinks) > 1)\n			array_multisort($precedence, SORT_DESC, $permlinks);\n\n		if (@txpinterface == \'public\') {\n			foreach ($permlinks as $pl)\n				$this->debug($pl[\'settings\'][\'pl_precedence\'].\': \'.$pl[\'settings\'][\'pl_name\'].\' (\'.$pl[\'settings\'][\'pl_preview\'].\')\');\n		}\n\n		return $permlinks;\n	}\n\n	function get_permlink ($id) {\n		$permlink = $this->pref($id);\n		return is_array($permlink) ? $permlink : array();\n	}\n\n	function remove_permlink ($id) {\n		$permlink = $this->get_permlink($id);\n		safe_delete(\'txp_prefs\', \"`event` = \'{$this->event}\' AND `name` LIKE \'{$this->plugin_name}_{$id}%\'\");\n		return $permlink[\'settings\'][\'pl_name\'];\n	}\n\n	function _feed_entry () {\n		static $set;\n		if (!isset($set)) {\n			// We\'re in a feed force debugging off.\n			$this->preferences[\'debug\'][\'value\'] = $GLOBALS[\'prefs\'][$this->plugin_name.\'_debug\'] = 0;\n\n			$this->set_permlink_mode(true);\n			$set = true;\n		}\n	}\n\n	function _textpattern () {\n		global $plugins_ver, $pretext, $prefs, $plugin_callback;\n\n		$this->debug(\'Plugin: \'.$this->plugin_name.\' - \'.$plugins_ver[$this->plugin_name]);\n		$this->debug(\'Function: \'.__FUNCTION__.\'()\');\n\n		// URI\n		$req = $pretext[\'req\'];\n		$req = preg_replace(\'%\\?[^\\/]+$%\', \'\', $req);\n		$this->debug(\'Request URI: \'.$req);\n		$uri = explode(\'/\', trim($req, \'/\'));\n\n		// The number of components comes in useful when determining the best partial match.\n		$uri_component_count = count($uri);\n\n		// Permanent links\n		$permlinks = $this->get_all_permlinks(1);\n\n		// Force Textpattern and tags to use messy URLs - these are easier to\n		// find in regex\n		$this->set_permlink_mode();\n\n		if (count($permlinks)) {\n\n			// We also want to match the front page of the site (for page numbers / feeds etc..).\n			// Add a permlinks rule which will do that.\n			$permlinks[\'default\'] = array(\n				\'components\' => array(),\n				\'settings\' => array(\n					\'pl_name\' => \'gbp_permanent_links_default\', \'pl_precedence\' => \'\', \'pl_preview\' => \'/\',\n					\'con_section\' => \'\', \'con_category\' => \'\', \'des_section\' => \'\', \'des_category\' => \'\',\n					\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\', \'des_page\' => \'\'\n			));\n\n			// Extend the pretext_replacement scope outside the foreach permlink loop\n			$pretext_replacement = NULL;\n\n			foreach($permlinks as $id => $pl) {\n				// Extract the permlink settings\n				$pl_settings = $pl[\'settings\'];\n				extract($pl_settings);\n\n				$this->debug(\'Permlink name: \'.$pl_name);\n				$this->debug(\'Permlink id: \'.$id);\n				$this->debug(\'Preview: \'.$pl_preview);\n\n				$pl_components = $pl[\'components\'];\n\n				// URI components\n				$uri_components = $uri;\n\n				$this->debug(\'PL component count: \'.count($pl_components));\n				$this->debug(\'URL component count: \'.count($uri_components));\n\n				$date = false; $title_page_feed = false;\n				foreach($pl_components as $pl_c)\n					// Are we expecting a date component? If so the number of pl and uri components won\'t match\n					if ($pl_c[\'type\'] == \'date\')\n						$date = true;\n					// Do we have either a title, page or a feed component?\n					else if (in_array($pl_c[\'type\'], array(\'title\', \'page\', \'feed\')))\n						$title_page_feed = true;\n\n				if (!$title_page_feed)\n					// If there isn\'t a title, page or feed component then append a special type for cleaver partial matching\n					$pl_components[] = array(\'type\' => \'title_page_feed\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\');\n\n				// Exit early if there are more URL components than PL components,\n				// taking into account whether there is a data component\n				if (!$uri_components[0] || count($uri_components) > count($pl_components) + ($date ? 2 : 0)) {\n					$this->debug(\'More URL components than PL components\');\n					continue;\n				}\n\n				// Reset pretext_replacement as we are about to start another comparison\n				$pretext_replacement = array(\'permlink_id\' => $id);\n\n				// Reset the article context string\n				$context = array();\n				unset($context_str);\n				if (!empty($des_section))\n					$context[] = \"`Section` = \'$des_section\'\";\n				if (!empty($des_category))\n					$context[] = \"(`Category1` = \'$des_category\' OR `Category2` = \'$des_category\')\";\n				$context_str = (count($context) > 0 ? \'and \'.join(\' and \', $context) : \'\');\n\n				// Assume there is no match\n				$partial_match = false;\n				$cleaver_partial_match = false;\n\n				// Loop through the permlink components\n				foreach ($pl_components as $pl_c_index => $pl_c) {\n					// Assume there is no match\n					$match = false;\n\n					// Check to see if there are still URI components to be checked.\n					if (count($uri_components))\n						// Get the next component.\n						$uri_c = array_shift($uri_components);\n\n					else if (!$title_page_feed && count($pl_components) - 1 == $uri_component_count) {\n						// If we appended a title_page_feed component earlier and permlink and URI components\n						// counts are equal, we must of finished checking this permlink, and it matches so break.\n						$match = true;\n						break;\n\n					} else {\n						// If there are no more URI components then we have a partial match.\n						// Store the partial match data unless there has been a preceding permlink with the\n						// same number of components, as permlink have already been sorted by precedence.\n						if (!array_key_exists($uri_component_count, $this->partial_matches))\n							$this->partial_matches[$uri_component_count] = $pretext_replacement;\n\n						// Unset pretext_replacement as changes could of been made in a preceding component\n						unset($pretext_replacement);\n\n						// Break early form the foreach permlink components loop.\n						$partial_match = true;\n						break;\n					}\n\n					// Extract the permlink components.\n					extract($pl_c);\n\n					// If it\'s a date, grab and combine the next two URI components.\n					if ($type == \'date\')\n						$uri_c .= \'/\'.array_shift($uri_components).\'/\'.array_shift($uri_components);\n\n					// Decode the URL\n					$uri_c = urldecode($uri_c);\n\n					// Always check the type unless the prefix or suffix aren\'t there\n					$check_type = true;\n\n					// Check prefix\n					if ($prefix && $this->pref(\'show_prefix\')) {\n						$sanitized_prefix = urldecode($this->encode_url($prefix));\n						if (($pos = strpos($uri_c, $sanitized_prefix)) === false || $pos != 0) {\n							$check_type = false;\n							$this->debug(\'Can\\\'t find prefix: \'.$prefix);\n						} else\n							// Check passed, remove prefix ready for the next check\n							$uri_c = substr_replace($uri_c, \'\', 0, strlen($sanitized_prefix));\n					}\n\n					// Check suffix\n					if ($check_type && $suffix && $this->pref(\'show_suffix\')) {\n						$sanitized_suffix = urldecode($this->encode_url($suffix));\n						if (($pos = strrpos($uri_c, $sanitized_suffix)) === false) {\n							$check_type = false;\n							$this->debug(\'Can\\\'t find suffix: \'.$suffix);\n						} else\n							// Check passed, remove suffix ready for the next check\n							$uri_c = substr_replace($uri_c, \'\', $pos, strlen($sanitized_suffix));\n					}\n\n					// Both the prefix and suffix settings have passed\n					if ($check_type) {\n						$this->debug(\'Checking if \"\'.$uri_c.\'\" is of type \"\'.$type.\'\"\');\n						$uri_c = doSlash($uri_c);\n\n						//\n						if ($prefs[\'permalink_title_format\']) {\n							$mt_search = array(\'/_/\', \'/\\.html$/\');\n							$mt_replace = array(\'-\', \'\');\n						} else {\n							$mt_search = array(\'/(?:^|_)(.)/e\', \'/\\.html$/\');\n							$mt_replace = array(\"strtoupper(\'\\\\1\')\", \'\');\n						}\n						$mt_uri_c = $this->pref(\'redirect_mt_style_links\')\n							? preg_replace($mt_search, $mt_replace, $uri_c)\n							: \'\';\n\n						// Compare based on type\n						switch ($type) {\n							case \'section\':\n								if ($rs = safe_row(\'name\', \'txp_section\', \"(`name` like \'$uri_c\' or `name` like \'$mt_uri_c\') limit 1\")) {\n									$this->debug(\'Section name: \'.$rs[\'name\']);\n									$pretext_replacement[\'s\'] = $rs[\'name\'];\n									$context[] = \"`Section` = \'{$rs[\'name\']}\'\";\n									$match = true;\n								}\n							break;\n							case \'category\':\n								if ($rs = safe_row(\'name\', \'txp_category\', \"(`name` like \'$uri_c\' or `name` like \'$mt_uri_c\') and `type` = \'article\' limit 1\")) {\n									$this->debug(\'Category name: \'.$rs[\'name\']);\n									$pretext_replacement[\'c\'] = $rs[\'name\'];\n									$context[] = \"(`Category1` = \'{$rs[\'name\']}\' OR `Category2` = \'$uri_c\')\";\n									$match = true;\n								}\n							break;\n							case \'title\':\n								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\")) {\n									$this->debug(\'URL Title: \'.$rs[\'url_title\']);\n									$mt_redirect = ($uri_c != $mt_uri_c);\n									$pretext_replacement[\'url_title\'] = $rs[\'url_title\'];\n									$match = true;\n								}\n							break;\n							case \'id\':\n								if ($rs = safe_row(\'ID, Posted\', \'textpattern\', \"`ID` = \'$uri_c\' $context_str and `Status` >= 4 limit 1\")) {\n									$pretext_replacement[\'id\'] = $rs[\'ID\'];\n									$pretext_replacement[\'Posted\'] = $rs[\'Posted\'];\n									$pretext[\'numPages\'] = 1;\n									$pretext[\'is_article_list\'] = false;\n									$match = true;\n								}\n							break;\n							case \'author\':\n								if ($author = safe_field(\'name\', \'txp_users\', \"RealName like \'$uri_c\' limit 1\")) {\n									$pretext_replacement[\'author\'] = $author;\n									$context[] = \"`AuthorID` = \'$author\'\";\n									$match = true;\n								}\n							break;\n							case \'login\':\n								if ($author = safe_field(\'name\', \'txp_users\', \"name like \'$uri_c\' limit 1\")) {\n									$pretext_replacement[\'author\'] = $author;\n									$context[] = \"`AuthorID` = \'$author\'\";\n									$match = true;\n								}\n							break;\n							case \'custom\':\n								$custom_options = array_values(array_map(array($this, \"encode_url\"), safe_column(\"custom_$custom\", \'textpattern\', \"custom_$custom != \'\'\")));\n								if ($this->pref(\'force_lowercase_urls\'))\n									$custom_options = array_map(\"strtolower\", $custom_options);\n								if (in_array($uri_c, $custom_options)) {\n									$match = true;\n								}\n							break;\n							case \'date\':\n								if (preg_match(\'/^\\d{4}\\/\\d{2}\\/\\d{2}$/\', $uri_c)) {\n									$pretext_replacement[\'date\'] = str_replace(\'/\', \'-\', $uri_c);\n									$match = true;\n								}\n							break;\n							case \'year\':\n								if (preg_match(\'/^\\d{4}$/\', $uri_c)) {\n									$pretext_replacement[\'year\'] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'month\':\n							case \'day\':\n								if (preg_match(\'/^\\d{2}$/\', $uri_c)) {\n									$pretext_replacement[$type] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'page\':\n								if (is_numeric($uri_c)) {\n									$pretext_replacement[\'pg\'] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'feed\':\n								if (in_array($uri_c, array(\'rss\', \'atom\'))) {\n									$pretext_replacement[$uri_c] = 1;\n									$match = true;\n								}\n							break;\n							case \'search\':\n									$pretext_replacement[\'q\'] = $uri_c;\n									$match = true;\n							break;\n							case \'text\':\n								if ($this->encode_url($text) == $uri_c) {\n									$match = true;\n									$pretext_replacement[\"permlink_text_{$name}\"] = $uri_c;\n								}\n							break;\n							case \'regex\':\n								// Check to see if regex is valid without outputting error messages.\n								ob_start();\n								preg_match($regex, $uri_c, $regex_matches);\n								$is_valid_regex = !(ob_get_clean());\n								if ($is_valid_regex && @$regex_matches[0]) {\n									$match = true;\n									$pretext_replacement[\"permlink_regex_{$name}\"] = $regex_matches[0];\n								}\n							break;\n						} // switch type end\n\n						// Update the article context string\n						$context_str = (count($context) > 0 ? \'and \'.join(\' and \', $context) : \'\');\n\n						$this->debug(($match == true) ? \'YES\' : \'NO\');\n\n						if (!$match && !$cleaver_partial_match && $this->pref(\'use_cleaver_partial_matches\')) {\n							// There hasn\'t been a match or a complete cleaver partial match. Lets try to be cleaver and\n							// check to see if this component is either a title, page or a feed. This makes it more probable\n							// a successful match for a given permlink rule occurs.\n							$this->debug(\'Checking if \"\'.$uri_c.\'\" is of type \"title_page_feed\"\');\n\n							if ($type != \'title\' && $url_title = safe_field(\'url_title\', \'textpattern\', \"`url_title` like \'$uri_c\' $context_str and `Status` >= 4 limit 1\")) {\n								$pretext_replacement[\'url_title\'] = $url_title;\n								$pretext[\'numPages\'] = 1;\n								$pretext[\'is_article_list\'] = false;\n								$cleaver_partial_match = true;\n							} else if ($this->pref(\'clean_page_archive_links\') && $type != \'page\' && is_numeric($uri_c)) {\n								$pretext_replacement[\'pg\'] = $uri_c;\n								$cleaver_partial_match = true;\n							} else if ($type != \'feed\' && in_array($uri_c, array(\'rss\', \'atom\'))) {\n								$pretext_replacement[$uri_c] = 1;\n								$cleaver_partial_match = true;\n							}\n\n							$this->debug(($cleaver_partial_match == true) ? \'YES\' : \'NO\');\n\n							if ($cleaver_partial_match) {\n								$this->cleaver_partial_match = $pretext_replacement;\n\n								// Unset pretext_replacement as changes could of been made in a preceding component\n								unset($pretext_replacement);\n\n								$cleaver_partial_match = true;\n								continue 2;\n							}\n						}\n					} // check type end\n\n					// Break early if the component doesn\'t match, as there is no point continuing\n					if ($match == false) {\n						// Unset pretext_replacement as changes could of been made in a preceding component\n						unset($pretext_replacement);\n						break;\n					}\n				} // foreach permlink component end\n\n				if (!isset($pretext_replacement[\'id\'])) {\n					if (isset($pretext_replacement[\'url_title\'])) {\n						if (isset($pretext_replacement[\'date\'])) {\n							$date_val = $pretext_replacement[\'date\'];\n						} else if (isset($pretext_replacement[\'year\'])) {\n							$date_val = $pretext_replacement[\'year\'];\n							if (isset($pretext_replacement[\'month\'])) {\n								$date_val .= \'-\' . $pretext_replacement[\'month\'];\n								if (isset($pretext_replacement[\'day\'])) {\n									$date_val .= \'-\' . $pretext_replacement[\'day\'];\n								}\n							}\n						}\n						if (isset($date_val))\n							$context_str .= \" and `Posted` like \'$date_val%\'\";\n						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\")) {\n							if (isset($date_val))\n								$this->debug(\'Found date and title-based match.\');\n							else\n								$this->debug(\'Found title-based match.\');\n							$pretext_replacement[\'id\'] = $rs[\'ID\'];\n							$pretext_replacement[\'Posted\'] = $rs[\'Posted\'];\n							$pretext[\'numPages\'] = 1;\n							$pretext[\'is_article_list\'] = false;\n						} else {\n							$match = false;\n							unset($pretext_replacement);\n						}\n					}\n				}\n\n				if ($match || $partial_match || $cleaver_partial_match) {\n					// Extract the settings for this permlink\n					@extract($permlinks[$pretext_replacement[\'permlink_id\']][\'settings\']);\n\n					// Check the permlink section and category conditions\n					if ((!empty($con_section) && $con_section != @$pretext_replacement[\'s\']) ||\n					(!empty($con_category) && $con_category != @$pretext_replacement[\'c\'])) {\n						$this->debug(\'Permlink conditions failed\');\n						if (@$con_section)  $this->debug(\'con_section = \'. $con_section);\n						if (@$con_category) $this->debug(\'con_category = \'. $con_category);\n\n						unset($pretext_replacement);\n					}\n					else if ($match && isset($pretext_replacement))\n						$this->debug(\'We have a match!\');\n\n					else if ($partial_match && count($this->partial_matches))\n						$this->debug(\'We have a \\\'partial match\\\'\');\n\n					else if ($cleaver_partial_match && isset($cleaver_partial_match))\n						$this->debug(\'We have a \\\'cleaver partial match\\\'\');\n\n					else {\n						$this->debug(\'Error: Can\\\'t determine the correct type match\');\n						// This permlink has failed, continue execution of the foreach permlinks loop\n						unset($pretext_replacement);\n					}\n				}\n\n				// We have a match\n				if (@$pretext_replacement)\n					break;\n\n			} // foreach permlinks end\n\n			// If there is no match restore the most likely partial match. Sorted by number of components and then precedence\n			if (!@$pretext_replacement && count($this->partial_matches)) {\n				$pt_slice = array_slice($this->partial_matches, -1);\n				$pretext_replacement = array_shift($pt_slice);\n			}\n			unset($this->partial_matches);\n\n			// Restore the cleaver_partial_match if there is no other matches\n			if (!@$pretext_replacement && $this->cleaver_partial_match)\n				$pretext_replacement = $this->cleaver_partial_match;\n			unset($this->cleaver_partial_match);\n\n			// Extract the settings for this permlink\n			@extract($permlinks[$pretext_replacement[\'permlink_id\']][\'settings\']);\n\n			// If pretext_replacement is still set here then we have a match\n			if (@$pretext_replacement) {\n				$this->debug(\'Pretext Replacement \'.print_r($pretext_replacement, 1));\n\n				if (!empty($des_section))\n					$pretext_replacement[\'s\'] = $des_section;\n				if (!empty($des_category))\n					$pretext_replacement[\'c\'] = $des_category;\n				if (!empty($des_feed))\n					$pretext_replacement[$des_feed] = 1;\n\n				if (@$pretext_replacement[\'id\'] && @$pretext_replacement[\'Posted\']) {\n					if ($np = getNextPrev($pretext_replacement[\'id\'], $pretext_replacement[\'Posted\'], @$pretext_replacement[\'s\']))\n						$pretext_replacement = array_merge($pretext_replacement, $np);\n				}\n				unset($pretext_replacement[\'Posted\']);\n\n				// If there is a match then we most set the http status correctly as txp\'s pretext might set it to 404\n				$pretext_replacement[\'status\'] = \'200\';\n\n				// Store the orginial HTTP status code\n				// We might need to log the page hit if it equals 404\n				$orginial_status = $pretext[\'status\'];\n\n				// Txp only looks at the month, but due to how we phase the month we can manipulate the sql to our needs\n				if (array_key_exists(\'date\', $pretext_replacement)) {\n					$pretext_replacement[\'month\'] = $pretext_replacement[\'date\'];\n					unset($pretext_replacement[\'date\']);\n				} else if (array_key_exists(\'year\', $pretext_replacement) ||\n				array_key_exists(\'month\', $pretext_replacement) ||\n				array_key_exists(\'day\', $pretext_replacement)) {\n					$month = \'\';\n					$month .= (array_key_exists(\'year\', $pretext_replacement))\n						? $pretext_replacement[\'year\'].\'-\' : \'____-\';\n					$month .= (array_key_exists(\'month\', $pretext_replacement))\n						? $pretext_replacement[\'month\'].\'-\' : \'__-\';\n					$month .= (array_key_exists(\'day\', $pretext_replacement))\n						? $pretext_replacement[\'day\'].\' \' : \'__ \';\n\n					$pretext_replacement[\'month\'] = $month;\n					unset($pretext_replacement[\'year\']);\n					unset($pretext_replacement[\'day\']);\n				}\n\n				// Section needs to be defined so we can always get a page template.\n				if (!array_key_exists(\'s\', $pretext_replacement))\n				{\n					if (!@$pretext_replacement[\'id\'])\n						$pretext_replacement[\'s\'] = \'default\';\n					else\n						$pretext_replacement[\'s\'] = safe_field(\'Section\', \'textpattern\', \'ID = \'.$pretext_replacement[\'id\']);\n				}\n\n				// Set the css and page template, otherwise we get an unknown section\n				$section_settings = safe_row(\'css, page\', \'txp_section\', \"name = \'{$pretext_replacement[\'s\']}\' limit 1\");\n				$pretext_replacement[\'page\'] = (@$des_page) ? $des_page : $section_settings[\'page\'];\n				$pretext_replacement[\'css\']  = $section_settings[\'css\'];\n\n				$this->matched_permlink = $pretext_replacement;\n\n				global $permlink_mode;\n\n				if (in_array($prefs[\'permlink_mode\'], array(\'id_title\', \'section_id_title\')) && @$pretext_replacement[\'pg\'] && !@$pretext_replacement[\'id\']) {\n					$pretext_replacement[\'id\'] = \'\';\n					$pretext_replacement[\'is_article_list\'] = true;\n				}\n\n				// Merge pretext_replacement with pretext\n				$pretext = array_merge($pretext, $pretext_replacement);\n\n				if (is_numeric(@$pretext[\'id\'])) {\n					$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\');\n					populateArticleData($a);\n				}\n\n				// Export required values to the global namespace\n				foreach (array(\'id\', \'s\', \'c\', \'pg\', \'is_article_list\', \'prev_id\', \'prev_title\', \'next_id\', \'next_title\', \'css\') as $key) {\n					if (array_key_exists($key, $pretext))\n						$GLOBALS[$key] = $pretext[$key];\n				}\n\n				if (count($this->matched_permlink) || @$mt_redirect) {\n					$pl_index = $pretext[\'permlink_id\'];\n					if (!@$mt_redirect || !$this->pref(\'redirect_mt_style_links\')) {\n						$pl = $this->get_permlink($pretext[\'permlink_id\']);\n						$pl_index = @$pl[\'settings\'][\'des_permlink\'];\n					}\n\n					if (@$pretext[\'id\'] && $pl_index) {\n						if (count($this->get_permlink($pl_index)) > 0) {\n							ob_clean();\n							global $siteurl;\n							$rs = safe_row(\'*, ID as thisid, unix_timestamp(Posted) as posted\', \'textpattern\', \"ID = \'{$pretext[\'id\']}\'\");\n							$host = rtrim(str_replace(rtrim(doStrip($pretext[\'subpath\']), \'/\'), \'\', hu), \'/\');\n							$this->redirect($host.$this->_permlinkurl($rs, PERMLINKURL, $pl_index), $this->pref(\'permlink_redirect_http_status\'));\n						}\n					} else if ($url = @$pl[\'settings\'][\'des_location\']) {\n						ob_clean();\n						$this->redirect($url, $this->pref(\'url_redirect_http_status\'));\n					}\n				}\n\n				if (@$pretext[\'rss\']) {\n					if (@$pretext[\'s\'])\n						$_POST[\'section\'] = $pretext[\'s\'];\n					if (@$pretext[\'c\'])\n						$_POST[\'category\'] = $pretext[\'c\'];\n					ob_clean();\n					include txpath.\'/publish/rss.php\';\n					exit(rss());\n				}\n\n				if (@$pretext[\'atom\']) {\n					if (@$pretext[\'s\'])\n						$_POST[\'section\'] = $pretext[\'s\'];\n					if (@$pretext[\'c\'])\n						$_POST[\'category\'] = $pretext[\'c\'];\n					ob_clean();\n					include txpath.\'/publish/atom.php\';\n					exit(atom());\n				}\n\n				$this->debug(\'Pretext \'.print_r($pretext, 1));\n			} else {\n				$this->debug(\'NO CHANGES MADE\');\n			}\n\n			// Log this page hit\n			if (@$orginial_status == 404)\n				log_hit($pretext[\'status\']);\n\n			// Start output buffering and pseudo callback to textpattern_end\n			ob_start(array(&$this, \'_textpattern_end_callback\'));\n\n			// TxP 4.0.5 (r2436) introduced the textpattern_end callback making the following redundant\n			$version = array_sum(array_map(\n				create_function(\'$line\', \'if (preg_match(\\\'/^\\$\'.\'LastChangedRevision: (\\w+) \\$/\\\', $line, $match)) return $match[1];\'),\n				@file(txpath . \'/publish.php\')\n			));\n			if ($version >= \'2436\') return;\n\n			// Remove the plugin callbacks which have already been called\n			function filter_callbacks($c) {\n				if ($c[\'event\']!=\'textpattern\') return true;\n				if (@$c[\'function\'][0]->plugin_name == \'gbp_permanent_links\' &&\n						@$c[\'function\'][1] == \'_textpattern\')\n				{\n					$GLOBALS[\'gbp_found_self\'] = true;\n					return false;\n				}\n				return @$GLOBALS[\'gbp_found_self\'];\n			}\n			$plugin_callback = array_filter($plugin_callback, \'filter_callbacks\');\n			unset($GLOBALS[\'gbp_found_self\']);\n\n			// Re-call textpattern\n			textpattern();\n\n			// Call custom textpattern_end callback\n			$this->_textpattern_end();\n\n			// textpattern() has run, kill the connection\n			die();\n		}\n\n	} // function _textpattern end\n\n	function _textpattern_end () {\n		// Redirect to a 404 if the page number is greater than the max number of pages\n		// Has to be after textpattern() as $thispage is set during <txp:article />\n		global $thispage, $pretext;\n		if ((@$pretext[\'pg\'] && isset($thispage)) &&\n		($thispage[\'numPages\'] < $pretext[\'pg\'])) {\n			ob_end_clean();\n			txp_die(gTxt(\'404_not_found\'), \'404\');\n		}\n\n		// Stop output buffering, this sends the buffer to _textpattern_end_callback()\n		while (@ob_end_flush());\n\n	} // function _textpattern_end end\n\n	function _textpattern_end_callback ($html, $override = \'\') {\n		global $pretext, $production_status;\n\n		if ($override) $pretext[\'permlink_override\'] = $override;\n		$html = preg_replace_callback(\n			\'%href=\"(\'.hu.\'|\\?)([^\"]*)\"%\',\n			array(&$this, \'_pagelinkurl\'),\n			$html\n		);\n		unset($pretext[\'permlink_override\']);\n\n		if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\'))) {\n			$debug = join(n, $this->buffer_debug);\n			$this->buffer_debug = array();\n			if ($debug)\n				$html = comment(n.$debug.n) . $html;\n		}\n\n		return $html;\n	} // function _textpattern_end_callback end\n\n	function check_permlink_conditions ($pl, $article_array) {\n		if (empty($article_array[\'section\'])) $article_array[\'section\'] = @$article_array[\'Section\'];\n		if (empty($article_array[\'category1\'])) $article_array[\'category1\'] = @$article_array[\'Category1\'];\n		if (empty($article_array[\'category2\'])) $article_array[\'category2\'] = @$article_array[\'Category2\'];\n\n		if (@$pl[\'settings\'][\'con_category\'] && ($pl[\'settings\'][\'con_category\'] != $article_array[\'category1\'] || $pl[\'settings\'][\'con_category\'] != $article_array[\'category2\']))\n			return false;\n		if (@$pl[\'settings\'][\'con_section\'] && $pl[\'settings\'][\'con_section\'] != $article_array[\'section\'])\n			return false;\n\n		return true;\n	}\n\n	function _permlinkurl ($article_array, $type = PERMLINKURL, $pl_index = NULL) {\n		global $pretext, $prefs, $production_status;\n\n		if ($type == PAGELINKURL)\n			return $this->toggle_custom_url_func(\'pagelinkurl\', $article_array);\n\n		if (empty($article_array)) return;\n\n		if ($pl_index)\n			$pl = $this->get_permlink($pl_index);\n		else {\n			// Get the matched pretext replacement array.\n			$matched = (count($this->matched_permlink))\n			? $this->matched_permlink\n			: @array_shift(array_slice($this->partial_matches, -1));\n\n			if (!isset($pl) && $matched && array_key_exists(\'id\', $matched)) {\n				// The permlink id is stored in the pretext replacement array, so we can find the permlink.\n				$pl = $this->get_permlink($matched[\'permlink_id\']);\n				foreach ($pl[\'components\'] as $pl_c)\n					if (in_array($pl_c[\'type\'], array(\'feed\', \'page\')) || !$this->check_permlink_conditions($pl, $article_array)) {\n						unset($pl);\n						break;\n					}\n			}\n\n			if (!isset($pl)) {\n				// We have no permlink id so grab the permlink with the highest precedence.\n				$permlinks = $this->get_all_permlinks(1, array(\'feed\', \'page\'));\n				foreach ($permlinks as $key => $pl)\n					if (!$this->check_permlink_conditions($pl, $article_array))\n						unset($permlinks[$key]);\n				$pl = array_shift($permlinks);\n			}\n		}\n\n		$uri = \'\';\n\n		if (is_array($pl) && array_key_exists(\'components\', $pl)) {\n			extract($article_array);\n\n			if (!isset($title)) $title = $Title;\n			if (empty($url_title)) $url_title = stripSpace($title);\n			if (empty($section)) $section = $Section;\n			if (empty($posted)) $posted = $Posted;\n			if (empty($authorid)) $authorid = @$AuthorID;\n			if (empty($category1)) $category1 = @$Category1;\n			if (empty($category2)) $category2 = @$Category2;\n			if (empty($thisid)) $thisid = $ID;\n\n			$pl_components = $pl[\'components\'];\n\n			// Check to see if there is a title component.\n			$title = false;\n			foreach($pl_components as $pl_c)\n				if ($pl_c[\'type\'] == \'title\' || $pl_c[\'type\'] == \'id\')\n					$title = true;\n\n			// If there isn\'t a title component then we need to append one to the end of the URI\n			if (!$title && $this->pref(\'automatically_append_title\'))\n				$pl_components[] = array(\'type\' => \'title\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\');\n\n			$uri = rtrim(doStrip(@$pretext[\'subpath\']), \'/\');\n			foreach ($pl_components as $pl_c) {\n				$uri .= \'/\';\n\n				$type = $pl_c[\'type\'];\n				switch ($type) {\n					case \'category\':\n						if (!@$pl_c[\'category\']) $pl_c[\'category\'] = 1;\n						$primary = \'category\'. $pl_c[\'category\'];\n						$secondary = \'category\'. (3-(int)$pl_c[\'category\']);\n						$check_context = ($this->pref(\'join_pretext_to_pagelinks\') && $this->pref(\'check_pretext_category_context\'));\n						if (!$check_context || $$primary == $pretext[\'c\'])\n							$uri_c = $$primary;\n						else if (!$check_context || $$secondary == $pretext[\'c\'])\n							$uri_c = $$secondary;\n						else if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n							$uri_c = \'--INVALID_CATEGORY--\';\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'section\':\n						$check_context = ($this->pref(\'join_pretext_to_pagelinks\') && $this->pref(\'check_pretext_section_context\'));\n						if (!$check_context || $section == $pretext[\'s\'])\n							$uri_c = $section;\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'title\': $uri_c = $url_title; break;\n					case \'id\': $uri_c = $thisid; break;\n					case \'author\': $uri_c = safe_field(\'RealName\', \'txp_users\', \"name like \'{$authorid}\'\"); break;\n					case \'login\': $uri_c = $authorid; break;\n					case \'date\': $uri_c = explode(\'/\', date(\'Y/m/d\', $posted)); break;\n					case \'year\': $uri_c = date(\'Y\', $posted); break;\n					case \'month\': $uri_c = date(\'m\', $posted); break;\n					case \'day\': $uri_c = date(\'d\', $posted); break;\n					case \'custom\':\n						if ($uri_c = @$article_array[$prefs[\"custom_{$pl_c[\'custom\']}_set\"]]);\n						else if ($uri_c = @$article_array[\"custom_{$pl_c[\'custom\']}\"]);\n						else if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n							$uri_c = \'--UNSET_CUSTOM_FIELD--\';\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'text\': $uri_c = $pl_c[\'text\']; break;\n					case \'regex\':\n						// Check to see if regex is valid without outputting error messages.\n						ob_start();\n						preg_match($pl_c[\'regex\'], $pl_c[\'regex\'], $regex_matches);\n						$is_valid_regex = !(ob_get_clean());\n						if ($is_valid_regex) {\n							$key = \"permlink_regex_{$pl_c[\'name\']}\";\n							$uri_c = (array_key_exists($key, $pretext)) ? $pretext[$key] : $regex_matches[0];\n						} else if ($this->pref(\'debug\'))\n							$uri_c = \'--INVALID_REGEX--\';\n					break;\n				}\n\n				if (empty($uri_c))\n					if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n						$uri_c = \'--PERMLINK_FORMAT_ERROR--\';\n					else {\n						unset($uri);\n						break;\n					}\n\n				if (@$pl_c[\'prefix\'])\n					$uri .= $this->encode_url($pl_c[\'prefix\']);\n\n				if (is_array($uri_c)) {\n					foreach ($uri_c as $uri_c2)\n						$uri .= $this->encode_url($uri_c2) . \'/\';\n					$uri = rtrim($uri, \'/\');\n				} else\n					$uri .= $this->encode_url($uri_c);\n\n				if (@$pl_c[\'suffix\'])\n					$uri .= $this->encode_url($pl_c[\'suffix\']);\n\n				unset($uri_c);\n			}\n\n			if (isset($uri))\n				$uri .= \'/\';\n		}\n\n		if ($uri_empty = empty($uri)) {\n			// It is possible the uri is still empty if there is no match or if we\'re using\n			// strict matching if so try the default permlink mode.\n			$uri = $this->toggle_permlink_mode(\'permlinkurl\', $article_array);\n		}\n\n		if ($this->pref(\'omit_trailing_slash\'))\n			$uri = rtrim($uri, \'/\');\n\n		if (!$uri_empty && in_array(txpath.\'/publish/rss.php\', get_included_files()) || in_array(txpath.\'/publish/atom.php\', get_included_files()) || txpinterface == \'admin\') {\n			$host = rtrim(str_replace(rtrim(doStrip(@$pretext[\'subpath\']), \'/\'), \'\', hu), \'/\');\n			$uri = $host . $uri;\n		}\n\n		return ($this->pref(\'force_lowercase_urls\')) ? strtolower($uri) : $uri;\n	}\n\n	function _pagelinkurl ($parts) {\n		extract(lAtts(array(\n			\'path\'		=> \'index.php\',\n			\'query\'		=> \'\',\n			\'fragment\'	=> \'\',\n		), parse_url(html_entity_decode(str_replace(\'&#38;\', \'&\', $parts[2])))));\n\n		// Tidy up links back to the site homepage\n		if ($path == \'index.php\' && empty($query))\n			return \'href=\"\' .hu. \'\"\';\n\n		// Fix matches like href=\"?s=foo\"\n		else if ($path && empty($query) && $parts[1] == \'?\') {\n			$query = $path;\n			$path = \'index.php\';\n		}\n\n		// Check to see if there is query to work with.\n		else if (empty($query) || $path != \'index.php\' || strpos($query, \'/\') === true)\n			return $parts[0];\n\n		// \'&amp;\' will break parse_str() if they are found in a query string\n		$query = str_replace(\'&amp;\', \'&\', $query);\n\n		if ($fragment)\n			$fragment = \'#\'.$fragment;\n\n		global $pretext;\n		parse_str($query, $query_part);\n		if (!array_key_exists(\'pg\', $query_part))\n			$query_part[\'pg\'] = 0;\n		if (!array_key_exists(\'id\', $query_part))\n			$query_part[\'id\'] = 0;\n		if (!array_key_exists(\'rss\', $query_part))\n			$query_part[\'rss\'] = 0;\n		if (!array_key_exists(\'atom\', $query_part))\n			$query_part[\'atom\'] = 0;\n		if ($this->pref(\'join_pretext_to_pagelinks\'))\n			extract(array_merge($pretext, $query_part));\n		else\n			extract($query_part);\n\n		// We have a id, pass to permlinkurl()\n		if ($id) {\n			if (@$s == \'file_download\') {\n				$title = (version_compare($dbversion, \'4.2\', \'>=\')) ? NULL : safe_field(\'filename\', \'txp_file\', \"id = \'{$id}\'\");\n				$url = $this->toggle_permlink_mode(\'filedownloadurl\', array($id, $title), true);\n			} else {\n				$rs = safe_row(\'*, ID as thisid, unix_timestamp(Posted) as posted\', \'textpattern\', \"ID = \'{$id}\'\");\n				$url = $this->_permlinkurl($rs, PERMLINKURL) . $fragment;\n			}\n			return \'href=\"\'. $url .\'\"\';\n		}\n\n		if (@$s == \'default\')\n			unset($s);\n\n		// Some TxP tags, e.g. <txp:feed_link /> use \'section\' or \'category\' inconsistent\n		// with most other tags. Process these now so we only have to check $s and $c.\n		if (@$section)  $s = $section;\n		if (@$category) $c = $category;\n\n		// Debugging for buffers\n		$this->buffer_debug[] = \'url: \'.str_replace(\'&amp;\', \'&\', $parts[1].$parts[2]);\n		$this->buffer_debug[] = \'path: \'.$path;\n		$this->buffer_debug[] = \'query: \'.$query;\n		if ($fragment) $this->buffer_debug[] = \'fragment: \'.$fragment;\n\n		if (@$id) $this->buffer_debug[] = \'id: \'.$id;\n		if (@$s) $this->buffer_debug[] = \'s: \'.$s;\n		if (@$c) $this->buffer_debug[] = \'c: \'.$c;\n		if (@$rss) $this->buffer_debug[] = \'rss: \'.$rss;\n		if (@$atom) $this->buffer_debug[] = \'atom: \'.$atom;\n		if (@$pg) $this->buffer_debug[] = \'pg: \'.$pg;\n		if (@$q) $this->buffer_debug[] = \'q: \'.$q;\n\n		if (@$pretext[\'permlink_override\']) {\n			$override_ids = explode(\',\', $pretext[\'permlink_override\']);\n			foreach ($override_ids as $override_id) {\n				$pl = $this->get_permlink($override_id);\n				if (count($pl) > 0) $permlinks[] = $pl;\n			}\n		}\n\n		if (empty($permlinks)) {\n			$permlinks = $this->get_all_permlinks(1);\n\n			$permlinks[\'gbp_permanent_links_default\'] = array(\n				\'components\' => array(\n					array(\'type\' => \'text\', \'text\' => strtolower(urlencode(gTxt(\'category\')))),\n					array(\'type\' => \'category\'),\n				),\n				\'settings\' => array(\n					\'pl_name\' => \'gbp_permanent_links_default\', \'pl_precedence\' => \'\', \'pl_preview\' => \'\',\n					\'con_section\' => \'\', \'con_category\' => \'\', \'des_section\' => \'\', \'des_category\' => \'\',\n					\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\',\n			));\n		}\n\n		$current_segments = explode(\'/\', ltrim($pretext[\'request_uri\'], \'/\'));\n\n		$highest_match_count = null;\n		foreach ($permlinks as $key => $pl) {\n			$this->buffer_debug[] = \'Testing permlink: \'. $pl[\'settings\'][\'pl_name\'] .\' - \'. $key;\n			$this->buffer_debug[] = \'Preview: \'. $pl[\'settings\'][\'pl_preview\'];\n			$out = array(); $keys = array(); $match_count = 0;\n			foreach ($pl[\'components\'] as $i => $pl_c) {\n				switch ($pl_c[\'type\']) {\n					case \'text\':\n						$out[] = $pl_c[\'text\'];\n					break;\n					case \'regex\':\n						$out[] = $pretext[\'permlink_regex_\'.$pl_c[\'name\']];\n					break;\n					case \'section\':\n						if (@$s) $out[] = $s;\n						else break 2;\n					break;\n					case \'category\':\n						if (@$c) $out[] = $c;\n						else break 2;\n					break;\n					case \'feed\':\n						if (@$rss) $keys[] = $out[] = \'rss\';\n						else if (@$atom) $keys[] = $out[] = \'atom\';\n						else break 2;\n					break;\n					case \'search\':\n						if (@$q) $out[] = $q;\n						else break 2;\n					break;\n					case \'page\':\n						if (@$pg) {\n							$out[] = $pg;\n							$keys[] = \'pg\';\n						}\n						else break 2;\n					break;\n					default: break 2;\n				}\n				if (in_array($pl_c[\'type\'], array(\'text\', \'regex\'))) {\n					if ($current_segments[$i] == end($out) && strlen(end($out)) > 0)\n						$match_count += $this->pref(\'text_and_regex_segment_scores\');\n				}\n				elseif (!in_array($pl_c[\'type\'], array(\'title\', \'id\')))\n					$match_count++;\n				else break;\n			}\n\n			$this->buffer_debug[] = \'Match count: \'. $match_count;\n\n			// Todo: Store according to the precedence value\n			if (count($out) > 0 && ($match_count > $highest_match_count || !isset($highest_match_count)) &&\n			!($key == \'gbp_permanent_links_default\' && !$match_count)) {\n				extract($pl[\'settings\']);\n				if ((empty($s) && empty($c)) ||\n				(empty($con_section) || @$s == $con_section) ||\n				(empty($con_category) || @$c == $con_category)) {\n					$this->buffer_debug[] = \'New highest match! \'. implode(\'/\', $out);\n					$highest_match_count = $match_count;\n					$match = $out;\n					$match_keys = $keys;\n				}\n			}\n		}\n\n		if (empty($match) && (!(@$pg && $this->pref(\'clean_page_archive_links\')) || (@$pg && @$q))) {\n			global $prefs, $pretext, $permlink_mode;\n			$this->buffer_debug[] = \'No match\';\n			$this->buffer_debug[] = \'----\';\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n			$url = pagelinkurl($query_part);\n			$pretext[\'permlink_mode\'] = $permlink_mode = \'messy\';\n			return \'href=\"\'. $url .\'\"\';\n		}\n\n		$this->buffer_debug[] = \'match: \'.      serialize($match);\n		$this->buffer_debug[] = \'match_keys: \'. serialize($match_keys);\n\n		$url = \'/\'.join(\'/\', $match);\n		$url = rtrim(hu, \'/\').rtrim($url, \'/\').\'/\';\n\n		if ($rss && !in_array(\'rss\', $match_keys))\n			$url .= \'rss\';\n		else if ($atom && !in_array(\'atom\', $match_keys))\n			$url .= \'atom\';\n		else if ($pg && !in_array(\'pg\', $match_keys)) {\n			if ($this->pref(\'clean_page_archive_links\'))\n				$url .= $pg;\n			else {\n				$url .= \'?pg=\'. $pg;\n				$omit_trailing_slash = true;\n			}\n		}\n\n		$url = rtrim($url, \'/\') . \'/\';\n\n		if (@$omit_trailing_slash || $this->pref(\'omit_trailing_slash\'))\n			$url = rtrim($url, \'/\');\n\n		$this->buffer_debug[] = $url;\n		$this->buffer_debug[] = \'----\';\n\n		if ($path == \'index.php\' && $url != hu)\n			return \'href=\"\'. $url . $fragment .\'\"\';\n\n		/*\n		1 = index, textpattern/css, NULL (=index)\n		2 = id, s, section, c, category, rss, atom, pg, q, (n, p, month, author)\n		*/\n\n		return ($this->pref(\'force_lowercase_urls\')) ? strtolower($parts[0]) : $parts[0];\n	}\n\n	function set_permlink_mode ($reset_function = false) {\n		global $prefs, $pretext, $permlink_mode;\n		$prefs[\'custom_url_func\'] = array(&$this, \'_permlinkurl\');\n\n		if (!$reset_function)\n			$pretext[\'permlink_mode\'] = $permlink_mode = \'messy\';\n		else\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n	}\n\n	function reset_permlink_mode () {\n		global $prefs, $pretext, $permlink_mode;\n		unset($prefs[\'custom_url_func\']);\n		$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n	}\n\n	function toggle_custom_url_func ($func, $atts = NULL, $toogle_permlink_mode = false, $expand_arguments = false) {\n		global $prefs, $pretext;\n\n		if ($toogle_permlink_mode) {\n			global $permlink_mode;\n			$_permlink_mode = $permlink_mode;\n		}\n\n		$_call_user_func = @$prefs[\'custom_url_func\'];\n\n		unset($prefs[\'custom_url_func\']);\n		if ($toogle_permlink_mode)\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n\n		if (is_callable($func)) {\n			if (is_array($atts) and $expand_arguments)\n				$rs = call_user_func_array($func, $atts);\n			else\n				$rs = call_user_func($func, $atts);\n		}\n\n		$prefs[\'custom_url_func\'] = $_call_user_func;\n\n		if ($toogle_permlink_mode)\n			$pretext[\'permlink_mode\'] = $permlink_mode = $_permlink_mode;\n\n		return $rs;\n	}\n\n	function toggle_permlink_mode ($func, $atts = NULL, $expand_arguments = false) {\n		return $this->toggle_custom_url_func($func, $atts, true, $expand_arguments);\n	}\n\n	function encode_url ($text) {\n		return urlencode(trim(dumbDown(urldecode($text))));\n	}\n\n	function debug () {\n		if ($this->pref(\'debug\')) {\n			global $production_status;\n			$a = func_get_args();\n\n			if (@txpinterface == \'admin\')\n				foreach ($a as $thing)\n					dmp($thing);\n\n			if (@txpinterface == \'public\' && in_array($production_status, array(\'debug\', \'testing\')))\n				foreach ($a as $thing)\n					echo comment(is_scalar($thing) ? strval($thing) : var_export($thing, true)),n;\n		}\n	}\n}\n\nclass PermanentLinksBuildTabView extends GBPAdminTabView\n{\n	function preload () {\n		register_callback(array(&$this, \'post_save_permlink\'), $this->parent->event, gbp_save, 1);\n		register_callback(array(&$this, \'post_save_permlink\'), $this->parent->event, gbp_post, 1);\n	}\n\n	function main () {\n		global $prefs;\n		extract(gpsa(array(\'step\', gbp_id)));\n\n		// With have an ID, either the permlink has just been saved or the user wants to edit it\n		if ($id) {\n			// Newly saved or beening edited, either way we\'re editing a permlink\n			$step = gbp_save;\n\n			// Use the ID to grab the permlink data (components & settings)\n			$permlink = $this->parent->get_permlink($id);\n			$components = $this->phpArrayToJsArray(\'components\', $permlink[\'components\']);\n			$settings = $permlink[\'settings\'];\n		} else {\n			// Creating a new ID and permlink.\n			$step = gbp_post;\n			$id = uniqid(\'\');\n\n			// Set the default set of components depending on whether there is parent permlink\n			$components = $this->phpArrayToJsArray(\'components\', array(\n				array(\'type\' => \'section\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\'),\n				array(\'type\' => \'category\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\', \'category\' => \'1\'),\n				array(\'type\' => \'title\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\'),\n			));\n\n			$settings = array(\n				\'pl_name\' => \'Untitled\', \'pl_precedence\' => \'0\',\n				\'con_section\' => \'\', \'con_category\' => \'\',\n				\'des_section\' => \'\', \'des_category\' => \'\', \'des_page\' => \'\',\n				\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\',\n			);\n		}\n\n		// Extract settings - this will be useful when creating the user interface\n		extract($settings);\n\n		// PHP & Javascript constants;\n		$separator = gbp_separator;\n		$components_div = \'permlink_components_ui\';\n		$components_form = \'permlink_components\';\n		$settings_form = \'permlink_settings\';\n		$show_prefix = $this->pref(\'show_prefix\');\n		$show_suffix = $this->pref(\'show_suffix\');\n\n		// A little credit here and there doesn\'t hurt\n		$out[] = \"<!-- {$this->parent->plugin_name} by Graeme Porteous -->\";\n\n		// The Javascript\n$out[] = <<<HTML\n	<script type=\"text/javascript\" language=\"javascript\" charset=\"utf-8\">\n	// <![CDATA[\n\n	// Global variables\nvar {$components}// components array for all the data\n\n	var _current = 0; // Index of the components array, of the currently selected component\n	var c_vals = new Array(\'type\', \'custom\', \'category\', \'name\', \'prefix\', \'suffix\', \'regex\', \'text\');\n\n	window.onload = function() {\n		component_refresh_all();\n		component_switch(component(_current));\n	}\n\n	function component_add () {\n		// Create data set\n		var data = new Array();\n		for (key in c_vals) {\n			data[c_vals[key]] = \'\';\n		}\n\n		// Add data\n		components.push(data);\n\n		// Reset component type list\n		form(\'{$components_form}\').type.value = \'\';\n\n		// Switch to the new component\n		_current = components.length - 1;\n\n		// Refresh UI\n		component_refresh_all();\n		component_update();\n	}\n\n	function component_refresh (element) {\n		var c = components[element.id];\n\n		// CSS\n		if (_current == element.id)\n			element.style[\'backgroundColor\'] = \'rgb(249, 206, 73)\';\n		else\n			element.style[\'backgroundColor\'] = \'rgb(255, 254, 211)\';\n		element.style[\'color\'] = \'rgb(0, 0, 0)\';\n		element.style[\'fontFamily\'] = \'Arial\';\n		element.style[\'fontWeight\'] = \'bold\';\n		element.style[\'verticalAlign\'] = \'middle\';\n		element.style[\'textAlign\'] = \'center\';\n		element.style[\'lineHeight\'] = \'1.5em\';\n		element.style[\'height\'] = \'1.5em\';\n		element.style[\'padding\'] = \'0 5px\';\n		element.style[\'marginRight\'] = \'5px\';\n		element.style[\'cssFloat\'] = \'left\';\n		element.style[\'display\'] = \'inline\';\n\n		// Remove all child nodes\n		while (element.hasChildNodes()) { element.removeChild(element.firstChild); }\n\n		// Create the visible string representing this component\n		switch (c[\'type\']) {\n			case \'\' :\n				string = \'/\';\n				break;\n			case \'regex\' :\n			case \'text\' :\n				string = c[c[\'type\']] + \' /\';\n				break;\n			case \'date\' :\n				string = c[\'type\'] + \' /\';\n				break;\n			case \'custom\' :\n				string = c[\'prefix\'] + \'custom_\' + c[\'custom\'] + c[\'suffix\'] + \' /\';\n				break;\n			default :\n				string = c[\'prefix\'] + c[\'type\'] + c[\'suffix\'] + \' /\';\n			break;\n		}\n\n		// Set the visible string\n		element.appendChild(document.createTextNode(string));\n\n		return element;\n	}\n\n	function component_refresh_all () {\n		// Remove all child nodes\n		while (permlink_div().hasChildNodes()) { permlink_div().removeChild(permlink_div().firstChild); }\n\n		for (var i = 0; i < components.length; i++) {\n			var c = components[i];\n\n			// Create the new component\n			var new_component = document.createElement(\'div\');\n\n			// Set the id interger for this component\n			new_component.id = i;\n\n			// Javascript, onmouseup setting\n			new_component.setAttribute(\'onmousedown\', \'component_switch(this);\');\n			new_component.onmousedown = function() { component_switch(this); };\n\n			// Refresh the look of the component\n			new_component = component_refresh(new_component);\n\n			// And add the new component to the ui\n			permlink_div().appendChild(new_component);\n		}\n	}\n\n	function component_remove () {\n		if (components.length > 1) {\n			components.splice(_current, 1);\n\n			if (_current >= components.length)\n				_current = components.length - 1;\n\n			component_refresh_all();\n		}\n	}\n\n	function component_switch (element) {\n		// Update current index\n		_current = element.id;\n\n		// Refresh UI\n		component_refresh_all();\n\n		// Set form input values\n		var c = components[_current];\n		for (key in c_vals) {\n			var k = c_vals[key];\n			var e = form(\'{$components_form}\').elements.namedItem(k);\n\n			if (c[k]) e.value = c[k];\n			else e.value = \'\';\n		}\n\n		// Hide unneeded form inputs\n		component_update();\n	}\n\n	function component_update (element) {\n		// Store the data in form inputs, and hide all form inputs\n		var c = new Array()\n		for (key in c_vals) {\n			var k = c_vals[key];\n			var e = form(\'{$components_form}\').elements.namedItem(k);\n\n			c[k] = e.value;\n\n			e.parentNode.style[\'display\'] = \'none\';\n		}\n\n		// Reshow type option list\n		form(\'{$components_form}\').type.parentNode.style[\'display\'] = \'\';\n\n		// Set other form inputs to the correct visibility state, dependent on type\n		switch (c[\'type\']) {\n			case \'\' :\n			case \'date\' : break;\n			case \'regex\' :\n				form(\'{$components_form}\').name.parentNode.style[\'display\'] = \'\';\n				form(\'{$components_form}\').regex.parentNode.style[\'display\'] = \'\';\n			break;\n			case \'text\' :\n				form(\'{$components_form}\').name.parentNode.style[\'display\'] = \'\';\n				form(\'{$components_form}\').text.parentNode.style[\'display\'] = \'\';\n			break;\n			case \'custom\' :\n				form(\'{$components_form}\').custom.parentNode.style[\'display\'] = \'\';\n				display_fixes();\n			break;\n			case \'category\' :\n				form(\'{$components_form}\').category.parentNode.style[\'display\'] = \'\';\n				display_fixes();\n			break;\n			default :\n				display_fixes();\n			break;\n		}\n\n		// Save data\n		components[_current] = c;\n\n		// Refresh component to reflect new data\n		component_refresh(component(_current));\n\n		// Re-focus the active form input\n		if (element)\n			element.focus();\n	}\n\n	function display_fixes () {\n		if (\'{$show_prefix}\')\n			form(\'{$components_form}\').prefix.parentNode.style[\'display\'] = \'\';\n		if (\'{$show_suffix}\')\n			form(\'{$components_form}\').suffix.parentNode.style[\'display\'] = \'\';\n	}\n\n	function component_left () {\n		if (components.length > 1 && _current > 0) {\n			// Store current component\n			var c = components[_current];\n\n			// Remove current component\n			components.splice(_current, 1);\n\n			// Update current index\n			_current--;\n\n			// Re-add current component\n			components.splice(_current, 0, c);\n\n			// Refresh UI\n			component_refresh_all();\n		}\n	}\n\n	function component_right () {\n		if (_current < components.length - 1) {\n			// Store current component\n			var c = components[_current];\n\n			// Remove current component\n			components.splice(_current, 1);\n\n			// Update current index\n			_current++;\n\n			// Re-add current component\n			components.splice(_current, 0, c);\n\n			// Refresh UI\n			component_refresh_all();\n		}\n	}\n\n	function save (form) {\n		var c = \'\'; var is_permlink = false; var has_page_or_search = false;\n		for (var i = 0; i < components.length; i++) {\n			if (components[i][\'type\'] == \'title\' || components[i][\'type\'] == \'id\')\n				is_permlink = true;\n			if (components[i][\'type\'] == \'page\' || components[i][\'type\'] == \'feed\' || components[i][\'type\'] == \'search\')\n				has_page_feed_search = true;\n			c = c + jsArrayToPhpArray(components[i]) + \'{$separator}\';\n		}\n\n		if (is_permlink && has_page_or_search)\n			alert(\"Your permanent link can\'t contain either a \'page\', \'feed\' or a \'search\' component with \'title\' or \'id\' components.\");\n\n		else if (is_permlink && (form.pl_name.value == \'\' || form.pl_name.value == \'Untitled\')) {\n			document.getElementById(\'settings\').style[\'display\'] = \'\';\n			form.pl_name.style[\'border\'] = \'3px solid rgb(221, 0, 0)\';\n			form.pl_precedence.style[\'border\'] = \'\';\n			alert(\'Please enter a name for this permanent link rule.\');\n		} else if (form.pl_precedence.value == \'\') {\n			document.getElementById(\'settings\').style[\'display\'] = \'\';\n			form.pl_precedence.style[\'border\'] = \'3px solid rgb(221, 0, 0)\';\n			form.pl_name.style[\'border\'] = \'\';\n			alert(\'Please enter a precedence value for this permanent link rule.\');\n		} else {\n			form.components.value = c;\n			if (permlink_div().textContent)\n				form.pl_preview.value = permlink_div().textContent;\n			else if (permlink_div().innerText)\n				form.pl_preview.value = permlink_div().innerText;\n			return true;\n		}\n\n		return false;\n	}\n\n	function jsArrayToPhpArray (array) {\n		// http://farm.tucows.com/blog/_archives/2005/5/30/895901.html\n		var array_php = \"\";\n		var total = 0;\n		for (var key in array) {\n			++ total;\n			array_php = array_php + \"s:\" +\n				String(key).length + \":\\\"\" + String(key) + \"\\\";s:\" +\n				String(array[key]).length + \":\\\"\" + String(array[key]) + \"\\\";\";\n		}\n		array_php = \"a:\" + total + \":{\" + array_php + \"}\";\n		return array_php;\n	}\n\n	function permlink_div () {\n		// Return the permlink rule element\n		return document.getElementById(\'{$components_div}\');\n	}\n\n	function form (name) {\n		if (!name)\n			name = \'{$components_form}\'\n		// Return the form element with name\n		return document.forms.namedItem(name);\n	}\n\n	function component (index) {\n		// Return component with index\n		return permlink_div().childNodes[index];\n	}\n\n	// ]]>\n	</script>\nHTML;\n\n		// --- Rule --- //\n\n		$out[] = hed(\'Permanent link rule\', 2);\n		$out[] = \'<div id=\"\'.$components_div.\'\" style=\"background-color: rgb(230, 230, 230); width: auto; height: 1.5em; margin: 10px 0; padding: 5px;\"></div>\';\n		$out[] = graf\n			(\n			$this->fInput(\'button\', \'component_add\', \'Add component\', array(\'click\' => \'component_add();\')).n.\n			$this->fInput(\'button\', \'component_remove\', \'Remove component\', array(\'click\' => \'component_remove();\')).n.\n			$this->fInput(\'button\', \'component_left\', \'Move left\', array(\'click\' => \'component_left();\')).n.\n			$this->fInput(\'button\', \'component_right\', \'Move right\', array(\'click\' => \'component_right();\'))\n			);\n\n		// --- Component form --- //\n\n		$out[] = \'<form action=\"index.php\" name=\"\'.$components_form.\'\" onsubmit=\"return false;\">\';\n\n		// --- Component type --- //\n\n		$component_types = array (\n			\'section\' => \'Section\', \'category\' => \'Category\',\n			\'title\' => \'Title\', \'id\' => \'ID\',\n			\'date\' => \'Date (yyyy/mm/dd)\', \'year\' => \'Year\',\n			\'month\' => \'Month\', \'day\' => \'Day\',\n			\'author\' => \'Author (Real name)\', \'login\' => \'Author (Login)\',\n			\'custom\' => \'Custom Field\', \'page\' => \'Page Number\',\n			\'feed\' => \'Feed\', \'search\' => \'Search request\',\n			\'text\' => \'Plain Text\', \'regex\' => \'Regular Expression\'\n		);\n		$out[] = graf($this->fSelect(\'type\', $component_types, \'\', 1, \'Component type\', \' onchange=\"component_update();\"\'));\n\n		// --- Component data --- //\n\n		// Grab the custom field titles\n		$custom_fields = array();\n		for ($i = 1; $i <= 10; $i++) {\n			if ($v = $prefs[\"custom_{$i}_set\"])\n				$custom_fields[$i] = $v;\n		}\n\n		$out[] = graf (\n			$this->fSelect(\'custom\', $custom_fields, \'\', 0, \'Custom\', \' onchange=\"component_update(this);\"\').n.\n			$this->fSelect(\'category\', array(\'1\' => \'Category 1\', \'2\' => \'Category 2\'), \'\', 0, \'Primary Category\', \' onchange=\"component_update(this);\"\').n.\n			$this->fInput(\'text\', \'name\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Name\').n.\n			$this->fInput(\'text\', \'prefix\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Prefix\').n.\n			$this->fInput(\'text\', \'regex\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Regular Expression\').n.\n			$this->fInput(\'text\', \'suffix\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Suffix\').n.\n			$this->fInput(\'text\', \'text\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Text\')\n		);\n		$hr = \'<hr style=\"border: 0; height: 1px; background-color: rgb(200, 200, 200); color: rgb(200, 200, 200); margin-bottom: 10px;\" />\';\n		$out[] = $hr;\n		$out[] = \'</form>\';\n\n		// --- Settings form --- //\n\n		$out[] = \'<form action=\"index.php\" method=\"post\" name=\"\'.$settings_form.\'\" onsubmit=\"return save(this);\">\';\n\n		// --- Settings --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'settings\\\'); return false;\">Settings</a>\', 2);\n		$out[] = \'<div id=\"settings\">\';\n		$out[] = graf($this->fInput(\'text\', \'pl_name\', $pl_name, NULL, \'Name\'));\n		$out[] = graf($this->fInput(\'text\', \'pl_precedence\', $pl_precedence, NULL, \'Precedence\'));\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// --- Conditions --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'conditions\\\'); return false;\">Conditions</a>\', 2);\n		$out[] = \'<div id=\"conditions\" style=\"display:none\">\';\n		$out[] = graf(strong(\'Only use this permanent link if the following conditions apply...\'));\n\n		// Generate a sections array (name=>title)\n		$sections = array();\n		$rs = safe_rows(\'name, title\', \'txp_section\', \"name != \'default\' order by name\");\n		foreach ($rs as $sec) {\n			$sections[$sec[\'name\']] = $sec[\'title\'];\n		}\n\n		// Generate a categories array (name=>title)\n		$categories = array();\n		$rs = safe_rows(\'name, title\', \'txp_category\', \"type = \'article\' and name != \'root\' order by name\");\n		foreach ($rs as $sec) {\n			$categories[$sec[\'name\']] = $sec[\'title\'];\n		}\n\n		$out[] = graf (\n			$this->fSelect(\'con_section\', $sections, $con_section, 1, \'Within section\').n.\n			$this->fSelect(\'con_category\', $categories, $con_category, 1, \'Within category\')\n		);\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// --- Destination --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'destination\\\'); return false;\">Destination</a>\', 2);\n		$out[] = \'<div id=\"destination\" style=\"display:none\">\';\n		$out[] = graf(strong(\'Forward this permanent link to...\'));\n		$out[] = graf (\n			$this->fSelect(\'des_section\', $sections, $des_section, 1, \'Section\').n.\n			$this->fSelect(\'des_category\', $categories, $des_category, 1, \'Category\')\n		);\n		$out[] = graf($this->fSelect(\'des_page\', safe_column(\'name\', \'txp_page\', \"1\"), @$des_page, 1, \'Page\'));\n		$out[] = graf($this->fBoxes(\'des_feed\', array(\'rss\', \'atom\', \'\'), $des_feed, NULL, array(\'RSS feed\', \'Atom feed\', \'Neither\')));\n		$out[] = graf(strong(\'Redirect this permanent link to...\'));\n		// Generate a permlinks array\n		$permlinks = $this->parent->get_all_permlinks(1);\n		foreach ($permlinks as $key => $pl) {\n			$permlinks[$key] = $pl[\'settings\'][\'pl_name\'];\n		}\n		unset($permlinks[$id]);\n		$out[] = graf($this->fSelect(\'des_permlink\', $permlinks, @$des_permlink, 1, \'Permanent link\'));\n		$out[] = graf($this->fInput(\'text\', \'des_location\', $des_location, NULL, \'HTTP location\'));\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// Save button\n		$out[] = fInput(\'submit\', \'\', \'Save permanent link\');\n\n		// Extra form inputs which get filled on submit\n		$out[] = hInput(\'components\', \'\');\n		$out[] = hInput(\'pl_preview\', \'\');\n		// Event and tab form inputs\n		$out[] = $this->form_inputs();\n		// Step and ID form inputs\n		$out[] = sInput($step);\n		$out[] = hInput(gbp_id, $id);\n\n		$out[] = \'</form>\';\n\n		// Lets echo everything out. Yah!\n		echo join(n, $out);\n	}\n\n	function fLabel ($label, $contents = \'\', $label_right = false) {\n		// <label> the contents with the name $lable\n		$contents = ($label_right)\n		? $contents.$label\n		: $label.($contents ? \': \'.$contents : \'\');\n		return tag($contents, \'label\');\n	}\n\n	function fBoxes ($name = \'\', $value = \'\', $checked_value = \'\', $on = array(), $label = \'\') {\n		$out = array();\n		if (is_array($value)) {\n			$i = 0;\n			foreach ($value as $val) {\n				$o = \'<input type=\"radio\" name=\"\'.$name.\'\" value=\"\'.$val.\'\"\';\n				$o .= ($checked_value == $val) ? \' checked=\"checked\"\' : \'\';\n				if (is_array($on)) foreach($on as $k => $v)\n					$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n				$o .= \' />\';\n				$out[] = $this->fLabel($label[$i++], $o, true);\n			}\n		} else {\n			$o = \'<input type=\"checkbox\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\';\n			$o .= ($checked_value == $value) ? \' checked=\"checked\"\' : \'\';\n			if (is_array($on)) foreach($on as $k => $v)\n				$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n			$o .= \' />\';\n			$out[] = $this->fLabel($label, $o, true);\n		}\n\n		return join(\'\', $out);\n	}\n\n	function fInput ($type, $name = \'\', $value = \'\', $on = array(), $label = \'\') {\n		if ($type == \'radio\' || $type == \'checkbox\')\n			return $this->fBoxes($name, $value, $on, $label);\n\n		$o = \'<input type=\"\'.$type.\'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\';\n		if (is_array($on)) foreach($on as $k => $v)\n			$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n		$o .= \' />\';\n		return ($label) ? $this->fLabel($label, $o) : $o;\n	}\n\n	function fSelect ($name = \'\', $array = \'\', $value = \'\', $blank_first = \'\', $label = \'\', $on_submit = \'\') {\n		$o = selectInput($name, $array, $value, $blank_first, $on_submit);\n		return ($label ? $this->fLabel($label, $o) : $o);\n	}\n\n	function post_save_permlink () {\n		// The function posts or saves a permanent link to txp_prefs\n\n		extract(gpsa(array(\'step\', gbp_id)));\n\n		// Grab the user defined settings from the form POST data\n		$settings = gpsa(array(\n			\'pl_name\', \'pl_precedence\', \'pl_preview\',\n			\'con_section\', \'con_category\',\n			\'des_section\', \'des_category\', \'des_page\',\n			\'des_permlink\', \'des_feed\', \'des_location\',\n		));\n\n		// Remove spaces from the permanent link preview\n		$settings[\'pl_preview\'] = preg_replace(\'%\\s+/\\s*%\', \'/\', $settings[\'pl_preview\']);\n\n		// Explode the separated string of serialize components - this was made by JavaScript.\n		$serialize_components = explode(gbp_separator, rtrim(gps(\'components\'), gbp_separator));\n\n		// Unserialize the components\n		$components = array();\n		foreach ($serialize_components as $c)\n			$components[] = unserialize(stripslashes($c));\n\n		// Complete the permanent link array - this is exactly what needs to be stored in the db\n		$permlink = array(\'settings\' => $settings, \'components\' => $components);\n\n		// Save it\n		$this->set_preference($id, $permlink, \'gbp_serialized\');\n\n		$this->parent->message = messenger(\'\', $settings[\'pl_name\'], \'saved\');\n	}\n\n	function phpArrayToJsArray ($name, $array) {\n		// From PHP.net\n		if (is_array($array)) {\n			$result = $name.\' = new Array();\'.n;\n			foreach ($array as $key => $value)\n				$result .= $this->phpArrayToJsArray($name.\'[\\\'\'.$key.\'\\\']\',$value,\'\').n;\n		} else {\n			$result = $name.\' = \\\'\'.$array.\'\\\';\';\n		}\n		return $result;\n	}\n}\n\nclass PermanentLinksListTabView extends GBPAdminTabView\n{\n	function preload () {\n		register_callback(array(&$this, $this->parent->event.\'_multi_edit\'), $this->parent->event, $this->parent->event.\'_multi_edit\', 1);\n		register_callback(array(&$this, $this->parent->event.\'_change_pageby\'), $this->parent->event, $this->parent->event.\'_change_pageby\', 1);\n	}\n\n	function main () {\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$event = $this->parent->event;\n\n		$permlinks = $this->parent->get_all_permlinks();\n		$total = count($permlinks);\n\n		if ($total < 1) {\n			echo graf(\'You haven\\\'t created any custom permanent links rules yet.\', \' style=\"text-align: center;\"\').\n				 graf(\'<a href=\"\'.$this->url(array(gbp_tab => \'build\'), true).\'\">Click here</a> to add one.\', \' style=\"text-align: center;\"\');\n			return;\n		}\n\n		$limit = max($this->pref(\'list_pageby\'), 15);\n\n		list($page, $offset, $numPages) = $this->pager($total, $limit, $page);\n\n		if (empty($sort))\n			$sort = \'pl_precedence\';\n\n		if (empty($dir))\n			$dir = \'desc\';\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		// Sort the permlinks via the selected column and then their names.\n		foreach ($permlinks as $id => $permlink) {\n			$sort_keys[$id] = $permlink[\'settings\'][$sort];\n			$name[$id] = $permlink[\'settings\'][\'pl_name\'];\n		}\n		array_multisort($sort_keys, (($dir == \'desc\') ? SORT_DESC : SORT_ASC), $name, SORT_ASC, $permlinks);\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$permlinks = array_slice($permlinks, $offset, $limit);\n\n		if (count($permlinks)) {\n			echo n.n.\'<form name=\"longform\" method=\"post\" action=\"index.php\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\n\n				n.startTable(\'list\').\n				n.tr(\n					n.column_head(\'name\', \'pl_name\', $event, true, $switch_dir, $crit, $search_method).\n					hCell().\n					column_head(\'preview\', \'pl_preview\', $event, true, $switch_dir, $crit, $search_method).\n					column_head(\'precedence\', \'pl_precedence\', $event, true, $switch_dir, $crit, $search_method).\n					hCell()\n				);\n\n			include_once txpath.\'/publish/taghandlers.php\';\n\n			foreach ($permlinks as $id => $permlink) {\n				extract($permlink[\'settings\']);\n\n				$manage = n.\'<ul\'.(version_compare($GLOBALS[\'thisversion\'], \'4.0.3\', \'<=\') ? \' style=\"margin:0;padding:0;list-style-type:none;\">\' : \'>\').\n						n.t.\'<li>\'.href(gTxt(\'edit\'), $this->url(array(gbp_tab => \'build\', gbp_id => $id), true)).\'</li>\'.\n						n.\'</ul>\';\n\n				echo n.n.tr(\n\n					td(\n						href($pl_name, $this->url(array(gbp_tab => \'build\', gbp_id => $id), true))\n					, 75).\n\n					td($manage, 35).\n\n					td($pl_preview, 175).\n					td($pl_precedence.\'&nbsp;\', 50).\n\n					td(\n						fInput(\'checkbox\', \'selected[]\', $id)\n					)\n				);\n			}\n\n			echo n.n.tr(\n				tda(\n					select_buttons().\n					$this->permlinks_multiedit_form($page, $sort, $dir, $crit, $search_method)\n				,\' colspan=\"4\" style=\"text-align: right; border: none;\"\')\n			).\n\n			n.endTable().\n			n.\'</form>\'.\n\n			n.$this->nav_form($event, $page, $numPages, $sort, $dir, $crit, $search_method).\n\n			n.pageby_form($event, $this->pref(\'list_pageby\'));\n		}\n	}\n\n	function pager ($total, $limit, $page) {\n		if (function_exists(\'pager\'))\n			return pager($total, $limit, $page);\n\n		// This is taken from txplib_misc.php r1588 it is required for 4.0.3 compatibitly\n		$num_pages = ceil($total / $limit);\n		$page = $page ? (int) $page : 1;\n		$page = min(max($page, 1), $num_pages);\n		$offset = max(($page - 1) * $limit, 0);\n		return array($page, $offset, $num_pages);\n	}\n\n	function nav_form ($event, $page, $numPages, $sort, $dir, $crit, $method) {\n		if (function_exists(\'nav_form\'))\n			return nav_form($event, $page, $numPages, $sort, $dir, $crit, $method);\n\n		// This is basically stolen from the 4.0.3 version of includes/txp_list.php\n		// - list_nav_form() for 4.0.3 compatibitly\n		$nav[] = ($page > 1)\n			? PrevNextLink($event, $page-1, gTxt(\'prev\'), \'prev\', $sort, $dir) : \'\';\n		$nav[] = sp.small($page. \'/\'.$numPages).sp;\n		$nav[] = ($page != $numPages)\n			? PrevNextLink($event, $page+1, gTxt(\'next\'), \'next\', $sort, $dir) : \'\';\n		return ($nav)\n			? graf(join(\'\', $nav), \' align=\"center\"\') : \'\';\n	}\n\n	function permlinks_multiedit_form ($page, $sort, $dir, $crit, $search_method) {\n		$methods = array(\n			\'delete\' => gTxt(\'delete\'),\n		);\n\n		return event_multiedit_form($this->parent->event, $methods, $page, $sort, $dir, $crit, $search_method);\n	}\n\n	function permlinks_change_pageby () {\n		$this->set_preference(\'list_pageby\', gps(\'qty\'));\n	}\n\n	function permlinks_multi_edit () {\n		$method = gps(\'edit_method\')\n			? gps(\'edit_method\') // From Txp 4.0.4 and greater\n			: gps(\'method\'); // Up to Txp 4.0.3\n\n		switch ($method) {\n			case \'delete\':\n				foreach (gps(\'selected\') as $id) {\n							$deleted[] = $this->parent->remove_permlink($id);\n				}\n			break;\n		}\n\n		$this->parent->message = (isset($deleted) && is_array($deleted) && count($deleted))\n			? messenger(\'\', join(\', \', $deleted) ,\'deleted\')\n			: messenger(\'an error occurred\', \'\', \'\');\n	}\n}\n\nglobal $gbp_pl;\n$gbp_pl = new PermanentLinks(\'Permanent Links\', \'permlinks\', \'admin\');\nif (@txpinterface == \'public\') {\n	register_callback(array(&$gbp_pl, \'_feed_entry\'), \'rss_entry\');\n	register_callback(array(&$gbp_pl, \'_feed_entry\'), \'atom_entry\');\n	register_callback(array(&$gbp_pl, \'_textpattern\'), \'textpattern\');\n	register_callback(array(&$gbp_pl, \'_textpattern_end\'), \'textpattern_end\');\n\n	function gbp_if_regex ($atts, $thing) {\n		global $pretext;\n		extract(lAtts(array(\n			\'name\' => \'\',\n			\'val\'  => \'\',\n		),$atts));\n		$match = (@$pretext[\"permlink_regex_{$name}\"] == $val);\n		return parse(EvalElse($thing, $match));\n	}\n\n	function gbp_if_text ($atts, $thing) {\n		global $pretext;\n		extract(lAtts(array(\n			\'name\' => \'\',\n			\'val\'  => \'\',\n		),$atts));\n\n		$match = false;\n		if (!empty($name)) {\n			if (empty($val))\n				$match = (isset($pretext[\"permlink_text_{$name}\"]));\n			else\n				$match = (@$pretext[\"permlink_text_{$name}\"] == $val);\n		}\n		return parse(EvalElse($thing, $match));\n	}\n\n	function gbp_use_pagelink ($atts, $thing = \'\') {\n		global $gbp_pl;\n		extract(lAtts(array(\n			\'rule\' => \'\',\n		),$atts));\n		return $gbp_pl->_textpattern_end_callback(parse($thing), $rule);\n	}\n\n	function gbp_disable_permlinks ($atts, $thing = \'\') {\n		global $gbp_pl;\n		return $gbp_pl->toggle_permlink_mode(\'parse\', $thing);\n	}\n}', '// Constants\n@define(\'gbp_save\', \'save\');\n@define(\'gbp_post\', \'post\');\n@define(\'gbp_separator\', \'&~&~&\');\n\n// require_plugin() will reset the $txp_current_plugin global\nglobal $txp_current_plugin;\n$gbp_current_plugin = $txp_current_plugin;\nrequire_plugin(\'gbp_admin_library\');\n$txp_current_plugin = $gbp_current_plugin;\n\nclass PermanentLinks extends GBPPlugin\n{\n	var $preferences = array(\n		\'show_prefix\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n		\'show_suffix\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n		\'omit_trailing_slash\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'redirect_mt_style_links\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'clean_page_archive_links\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'join_pretext_to_pagelinks\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'check_pretext_category_context\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'check_pretext_section_context\' => array(\'value\' => 0 , \'type\' => \'yesnoradio\'),\n		\'force_lowercase_urls\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'automatically_append_title\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'use_cleaver_partial_matches\' => array(\'value\' => 1 , \'type\' => \'yesnoradio\'),\n		\'permlink_redirect_http_status\' => array(\'value\' => \'301\' , \'type\' => \'text_input\'),\n		\'url_redirect_http_status\' => array(\'value\' => \'302\' , \'type\' => \'text_input\'),\n		\'text_and_regex_segment_scores\' => array(\'value\' => \'0\' , \'type\' => \'text_input\'),\n		\'debug\' => array(\'value\' => 0, \'type\' => \'yesnoradio\'),\n	);\n	var $matched_permlink = array();\n	var $partial_matches = array();\n	var $cleaver_partial_match;\n	var $buffer_debug = array();\n\n	function preload () {\n		new PermanentLinksListTabView(\'list\', \'list\', $this);\n		new PermanentLinksBuildTabView(\'build\', \'build\', $this);\n		new GBPPreferenceTabView($this);\n	}\n\n	function main () {\n		require_privs($this->event);\n	}\n\n	function get_all_permlinks ($sort = 0, $exclude = array()) {\n		static $rs;\n		if (!isset($rs))\n			$rs = safe_column(\n				\"REPLACE(name, \'{$this->plugin_name}_\', \'\') AS id\", \'txp_prefs\',\n				\"`event` = \'{$this->event}\' AND `name` REGEXP \'^{$this->plugin_name}_.{13}$\'\"\n			);\n\n		if (@txpinterface == \'public\')\n			$this->debug(\'Loading permlinks from db\');\n		$i = 0;\n\n		$permlinks = array();\n		foreach ($rs as $id) {\n			$pl = $this->get_permlink($id);\n\n			// Don\'t try and load permalink rules from the new version\n			if (!isset($pl[\'components\']))\n				continue;\n\n			if (count($exclude) > 0)\n				foreach ($pl[\'components\'] as $pl_c) {\n					if (is_array($exclude) && in_array($pl_c[\'type\'], $exclude))\n						continue 2;\n					if (is_string($exclude) && $pl_c[\'type\'] === $exclude)\n						continue 2;\n				}\n\n			$permlinks[$id] = $pl;\n\n			if ($sort)\n				$precedence[$id] = $permlinks[$id][\'settings\'][\'pl_precedence\'];\n		}\n\n		// If more than one permanent link, sort by their precedence value.\n		if ($sort && count($permlinks) > 1)\n			array_multisort($precedence, SORT_DESC, $permlinks);\n\n		if (@txpinterface == \'public\') {\n			foreach ($permlinks as $pl)\n				$this->debug($pl[\'settings\'][\'pl_precedence\'].\': \'.$pl[\'settings\'][\'pl_name\'].\' (\'.$pl[\'settings\'][\'pl_preview\'].\')\');\n		}\n\n		return $permlinks;\n	}\n\n	function get_permlink ($id) {\n		$permlink = $this->pref($id);\n		return is_array($permlink) ? $permlink : array();\n	}\n\n	function remove_permlink ($id) {\n		$permlink = $this->get_permlink($id);\n		safe_delete(\'txp_prefs\', \"`event` = \'{$this->event}\' AND `name` LIKE \'{$this->plugin_name}_{$id}%\'\");\n		return $permlink[\'settings\'][\'pl_name\'];\n	}\n\n	function _feed_entry () {\n		static $set;\n		if (!isset($set)) {\n			// We\'re in a feed force debugging off.\n			$this->preferences[\'debug\'][\'value\'] = $GLOBALS[\'prefs\'][$this->plugin_name.\'_debug\'] = 0;\n\n			$this->set_permlink_mode(true);\n			$set = true;\n		}\n	}\n\n	function _textpattern () {\n		global $plugins_ver, $pretext, $prefs, $plugin_callback;\n\n		$this->debug(\'Plugin: \'.$this->plugin_name.\' - \'.$plugins_ver[$this->plugin_name]);\n		$this->debug(\'Function: \'.__FUNCTION__.\'()\');\n\n		// URI\n		$req = $pretext[\'req\'];\n		$req = preg_replace(\'%\\?[^\\/]+$%\', \'\', $req);\n		$this->debug(\'Request URI: \'.$req);\n		$uri = explode(\'/\', trim($req, \'/\'));\n\n		// The number of components comes in useful when determining the best partial match.\n		$uri_component_count = count($uri);\n\n		// Permanent links\n		$permlinks = $this->get_all_permlinks(1);\n\n		// Force Textpattern and tags to use messy URLs - these are easier to\n		// find in regex\n		$this->set_permlink_mode();\n\n		if (count($permlinks)) {\n\n			// We also want to match the front page of the site (for page numbers / feeds etc..).\n			// Add a permlinks rule which will do that.\n			$permlinks[\'default\'] = array(\n				\'components\' => array(),\n				\'settings\' => array(\n					\'pl_name\' => \'gbp_permanent_links_default\', \'pl_precedence\' => \'\', \'pl_preview\' => \'/\',\n					\'con_section\' => \'\', \'con_category\' => \'\', \'des_section\' => \'\', \'des_category\' => \'\',\n					\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\', \'des_page\' => \'\'\n			));\n\n			// Extend the pretext_replacement scope outside the foreach permlink loop\n			$pretext_replacement = NULL;\n\n			foreach($permlinks as $id => $pl) {\n				// Extract the permlink settings\n				$pl_settings = $pl[\'settings\'];\n				extract($pl_settings);\n\n				$this->debug(\'Permlink name: \'.$pl_name);\n				$this->debug(\'Permlink id: \'.$id);\n				$this->debug(\'Preview: \'.$pl_preview);\n\n				$pl_components = $pl[\'components\'];\n\n				// URI components\n				$uri_components = $uri;\n\n				$this->debug(\'PL component count: \'.count($pl_components));\n				$this->debug(\'URL component count: \'.count($uri_components));\n\n				$date = false; $title_page_feed = false;\n				foreach($pl_components as $pl_c)\n					// Are we expecting a date component? If so the number of pl and uri components won\'t match\n					if ($pl_c[\'type\'] == \'date\')\n						$date = true;\n					// Do we have either a title, page or a feed component?\n					else if (in_array($pl_c[\'type\'], array(\'title\', \'page\', \'feed\')))\n						$title_page_feed = true;\n\n				if (!$title_page_feed)\n					// If there isn\'t a title, page or feed component then append a special type for cleaver partial matching\n					$pl_components[] = array(\'type\' => \'title_page_feed\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\');\n\n				// Exit early if there are more URL components than PL components,\n				// taking into account whether there is a data component\n				if (!$uri_components[0] || count($uri_components) > count($pl_components) + ($date ? 2 : 0)) {\n					$this->debug(\'More URL components than PL components\');\n					continue;\n				}\n\n				// Reset pretext_replacement as we are about to start another comparison\n				$pretext_replacement = array(\'permlink_id\' => $id);\n\n				// Reset the article context string\n				$context = array();\n				unset($context_str);\n				if (!empty($des_section))\n					$context[] = \"`Section` = \'$des_section\'\";\n				if (!empty($des_category))\n					$context[] = \"(`Category1` = \'$des_category\' OR `Category2` = \'$des_category\')\";\n				$context_str = (count($context) > 0 ? \'and \'.join(\' and \', $context) : \'\');\n\n				// Assume there is no match\n				$partial_match = false;\n				$cleaver_partial_match = false;\n\n				// Loop through the permlink components\n				foreach ($pl_components as $pl_c_index => $pl_c) {\n					// Assume there is no match\n					$match = false;\n\n					// Check to see if there are still URI components to be checked.\n					if (count($uri_components))\n						// Get the next component.\n						$uri_c = array_shift($uri_components);\n\n					else if (!$title_page_feed && count($pl_components) - 1 == $uri_component_count) {\n						// If we appended a title_page_feed component earlier and permlink and URI components\n						// counts are equal, we must of finished checking this permlink, and it matches so break.\n						$match = true;\n						break;\n\n					} else {\n						// If there are no more URI components then we have a partial match.\n						// Store the partial match data unless there has been a preceding permlink with the\n						// same number of components, as permlink have already been sorted by precedence.\n						if (!array_key_exists($uri_component_count, $this->partial_matches))\n							$this->partial_matches[$uri_component_count] = $pretext_replacement;\n\n						// Unset pretext_replacement as changes could of been made in a preceding component\n						unset($pretext_replacement);\n\n						// Break early form the foreach permlink components loop.\n						$partial_match = true;\n						break;\n					}\n\n					// Extract the permlink components.\n					extract($pl_c);\n\n					// If it\'s a date, grab and combine the next two URI components.\n					if ($type == \'date\')\n						$uri_c .= \'/\'.array_shift($uri_components).\'/\'.array_shift($uri_components);\n\n					// Decode the URL\n					$uri_c = urldecode($uri_c);\n\n					// Always check the type unless the prefix or suffix aren\'t there\n					$check_type = true;\n\n					// Check prefix\n					if ($prefix && $this->pref(\'show_prefix\')) {\n						$sanitized_prefix = urldecode($this->encode_url($prefix));\n						if (($pos = strpos($uri_c, $sanitized_prefix)) === false || $pos != 0) {\n							$check_type = false;\n							$this->debug(\'Can\\\'t find prefix: \'.$prefix);\n						} else\n							// Check passed, remove prefix ready for the next check\n							$uri_c = substr_replace($uri_c, \'\', 0, strlen($sanitized_prefix));\n					}\n\n					// Check suffix\n					if ($check_type && $suffix && $this->pref(\'show_suffix\')) {\n						$sanitized_suffix = urldecode($this->encode_url($suffix));\n						if (($pos = strrpos($uri_c, $sanitized_suffix)) === false) {\n							$check_type = false;\n							$this->debug(\'Can\\\'t find suffix: \'.$suffix);\n						} else\n							// Check passed, remove suffix ready for the next check\n							$uri_c = substr_replace($uri_c, \'\', $pos, strlen($sanitized_suffix));\n					}\n\n					// Both the prefix and suffix settings have passed\n					if ($check_type) {\n						$this->debug(\'Checking if \"\'.$uri_c.\'\" is of type \"\'.$type.\'\"\');\n						$uri_c = doSlash($uri_c);\n\n						//\n						if ($prefs[\'permalink_title_format\']) {\n							$mt_search = array(\'/_/\', \'/\\.html$/\');\n							$mt_replace = array(\'-\', \'\');\n						} else {\n							$mt_search = array(\'/(?:^|_)(.)/e\', \'/\\.html$/\');\n							$mt_replace = array(\"strtoupper(\'\\\\1\')\", \'\');\n						}\n						$mt_uri_c = $this->pref(\'redirect_mt_style_links\')\n							? preg_replace($mt_search, $mt_replace, $uri_c)\n							: \'\';\n\n						// Compare based on type\n						switch ($type) {\n							case \'section\':\n								if ($rs = safe_row(\'name\', \'txp_section\', \"(`name` like \'$uri_c\' or `name` like \'$mt_uri_c\') limit 1\")) {\n									$this->debug(\'Section name: \'.$rs[\'name\']);\n									$pretext_replacement[\'s\'] = $rs[\'name\'];\n									$context[] = \"`Section` = \'{$rs[\'name\']}\'\";\n									$match = true;\n								}\n							break;\n							case \'category\':\n								if ($rs = safe_row(\'name\', \'txp_category\', \"(`name` like \'$uri_c\' or `name` like \'$mt_uri_c\') and `type` = \'article\' limit 1\")) {\n									$this->debug(\'Category name: \'.$rs[\'name\']);\n									$pretext_replacement[\'c\'] = $rs[\'name\'];\n									$context[] = \"(`Category1` = \'{$rs[\'name\']}\' OR `Category2` = \'$uri_c\')\";\n									$match = true;\n								}\n							break;\n							case \'title\':\n								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\")) {\n									$this->debug(\'URL Title: \'.$rs[\'url_title\']);\n									$mt_redirect = ($uri_c != $mt_uri_c);\n									$pretext_replacement[\'url_title\'] = $rs[\'url_title\'];\n									$match = true;\n								}\n							break;\n							case \'id\':\n								if ($rs = safe_row(\'ID, Posted\', \'textpattern\', \"`ID` = \'$uri_c\' $context_str and `Status` >= 4 limit 1\")) {\n									$pretext_replacement[\'id\'] = $rs[\'ID\'];\n									$pretext_replacement[\'Posted\'] = $rs[\'Posted\'];\n									$pretext[\'numPages\'] = 1;\n									$pretext[\'is_article_list\'] = false;\n									$match = true;\n								}\n							break;\n							case \'author\':\n								if ($author = safe_field(\'name\', \'txp_users\', \"RealName like \'$uri_c\' limit 1\")) {\n									$pretext_replacement[\'author\'] = $author;\n									$context[] = \"`AuthorID` = \'$author\'\";\n									$match = true;\n								}\n							break;\n							case \'login\':\n								if ($author = safe_field(\'name\', \'txp_users\', \"name like \'$uri_c\' limit 1\")) {\n									$pretext_replacement[\'author\'] = $author;\n									$context[] = \"`AuthorID` = \'$author\'\";\n									$match = true;\n								}\n							break;\n							case \'custom\':\n								$custom_options = array_values(array_map(array($this, \"encode_url\"), safe_column(\"custom_$custom\", \'textpattern\', \"custom_$custom != \'\'\")));\n								if ($this->pref(\'force_lowercase_urls\'))\n									$custom_options = array_map(\"strtolower\", $custom_options);\n								if (in_array($uri_c, $custom_options)) {\n									$match = true;\n								}\n							break;\n							case \'date\':\n								if (preg_match(\'/^\\d{4}\\/\\d{2}\\/\\d{2}$/\', $uri_c)) {\n									$pretext_replacement[\'date\'] = str_replace(\'/\', \'-\', $uri_c);\n									$match = true;\n								}\n							break;\n							case \'year\':\n								if (preg_match(\'/^\\d{4}$/\', $uri_c)) {\n									$pretext_replacement[\'year\'] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'month\':\n							case \'day\':\n								if (preg_match(\'/^\\d{2}$/\', $uri_c)) {\n									$pretext_replacement[$type] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'page\':\n								if (is_numeric($uri_c)) {\n									$pretext_replacement[\'pg\'] = $uri_c;\n									$match = true;\n								}\n							break;\n							case \'feed\':\n								if (in_array($uri_c, array(\'rss\', \'atom\'))) {\n									$pretext_replacement[$uri_c] = 1;\n									$match = true;\n								}\n							break;\n							case \'search\':\n									$pretext_replacement[\'q\'] = $uri_c;\n									$match = true;\n							break;\n							case \'text\':\n								if ($this->encode_url($text) == $uri_c) {\n									$match = true;\n									$pretext_replacement[\"permlink_text_{$name}\"] = $uri_c;\n								}\n							break;\n							case \'regex\':\n								// Check to see if regex is valid without outputting error messages.\n								ob_start();\n								preg_match($regex, $uri_c, $regex_matches);\n								$is_valid_regex = !(ob_get_clean());\n								if ($is_valid_regex && @$regex_matches[0]) {\n									$match = true;\n									$pretext_replacement[\"permlink_regex_{$name}\"] = $regex_matches[0];\n								}\n							break;\n						} // switch type end\n\n						// Update the article context string\n						$context_str = (count($context) > 0 ? \'and \'.join(\' and \', $context) : \'\');\n\n						$this->debug(($match == true) ? \'YES\' : \'NO\');\n\n						if (!$match && !$cleaver_partial_match && $this->pref(\'use_cleaver_partial_matches\')) {\n							// There hasn\'t been a match or a complete cleaver partial match. Lets try to be cleaver and\n							// check to see if this component is either a title, page or a feed. This makes it more probable\n							// a successful match for a given permlink rule occurs.\n							$this->debug(\'Checking if \"\'.$uri_c.\'\" is of type \"title_page_feed\"\');\n\n							if ($type != \'title\' && $url_title = safe_field(\'url_title\', \'textpattern\', \"`url_title` like \'$uri_c\' $context_str and `Status` >= 4 limit 1\")) {\n								$pretext_replacement[\'url_title\'] = $url_title;\n								$pretext[\'numPages\'] = 1;\n								$pretext[\'is_article_list\'] = false;\n								$cleaver_partial_match = true;\n							} else if ($this->pref(\'clean_page_archive_links\') && $type != \'page\' && is_numeric($uri_c)) {\n								$pretext_replacement[\'pg\'] = $uri_c;\n								$cleaver_partial_match = true;\n							} else if ($type != \'feed\' && in_array($uri_c, array(\'rss\', \'atom\'))) {\n								$pretext_replacement[$uri_c] = 1;\n								$cleaver_partial_match = true;\n							}\n\n							$this->debug(($cleaver_partial_match == true) ? \'YES\' : \'NO\');\n\n							if ($cleaver_partial_match) {\n								$this->cleaver_partial_match = $pretext_replacement;\n\n								// Unset pretext_replacement as changes could of been made in a preceding component\n								unset($pretext_replacement);\n\n								$cleaver_partial_match = true;\n								continue 2;\n							}\n						}\n					} // check type end\n\n					// Break early if the component doesn\'t match, as there is no point continuing\n					if ($match == false) {\n						// Unset pretext_replacement as changes could of been made in a preceding component\n						unset($pretext_replacement);\n						break;\n					}\n				} // foreach permlink component end\n\n				if (!isset($pretext_replacement[\'id\'])) {\n					if (isset($pretext_replacement[\'url_title\'])) {\n						if (isset($pretext_replacement[\'date\'])) {\n							$date_val = $pretext_replacement[\'date\'];\n						} else if (isset($pretext_replacement[\'year\'])) {\n							$date_val = $pretext_replacement[\'year\'];\n							if (isset($pretext_replacement[\'month\'])) {\n								$date_val .= \'-\' . $pretext_replacement[\'month\'];\n								if (isset($pretext_replacement[\'day\'])) {\n									$date_val .= \'-\' . $pretext_replacement[\'day\'];\n								}\n							}\n						}\n						if (isset($date_val))\n							$context_str .= \" and `Posted` like \'$date_val%\'\";\n						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\")) {\n							if (isset($date_val))\n								$this->debug(\'Found date and title-based match.\');\n							else\n								$this->debug(\'Found title-based match.\');\n							$pretext_replacement[\'id\'] = $rs[\'ID\'];\n							$pretext_replacement[\'Posted\'] = $rs[\'Posted\'];\n							$pretext[\'numPages\'] = 1;\n							$pretext[\'is_article_list\'] = false;\n						} else {\n							$match = false;\n							unset($pretext_replacement);\n						}\n					}\n				}\n\n				if ($match || $partial_match || $cleaver_partial_match) {\n					// Extract the settings for this permlink\n					@extract($permlinks[$pretext_replacement[\'permlink_id\']][\'settings\']);\n\n					// Check the permlink section and category conditions\n					if ((!empty($con_section) && $con_section != @$pretext_replacement[\'s\']) ||\n					(!empty($con_category) && $con_category != @$pretext_replacement[\'c\'])) {\n						$this->debug(\'Permlink conditions failed\');\n						if (@$con_section)  $this->debug(\'con_section = \'. $con_section);\n						if (@$con_category) $this->debug(\'con_category = \'. $con_category);\n\n						unset($pretext_replacement);\n					}\n					else if ($match && isset($pretext_replacement))\n						$this->debug(\'We have a match!\');\n\n					else if ($partial_match && count($this->partial_matches))\n						$this->debug(\'We have a \\\'partial match\\\'\');\n\n					else if ($cleaver_partial_match && isset($cleaver_partial_match))\n						$this->debug(\'We have a \\\'cleaver partial match\\\'\');\n\n					else {\n						$this->debug(\'Error: Can\\\'t determine the correct type match\');\n						// This permlink has failed, continue execution of the foreach permlinks loop\n						unset($pretext_replacement);\n					}\n				}\n\n				// We have a match\n				if (@$pretext_replacement)\n					break;\n\n			} // foreach permlinks end\n\n			// If there is no match restore the most likely partial match. Sorted by number of components and then precedence\n			if (!@$pretext_replacement && count($this->partial_matches)) {\n				$pt_slice = array_slice($this->partial_matches, -1);\n				$pretext_replacement = array_shift($pt_slice);\n			}\n			unset($this->partial_matches);\n\n			// Restore the cleaver_partial_match if there is no other matches\n			if (!@$pretext_replacement && $this->cleaver_partial_match)\n				$pretext_replacement = $this->cleaver_partial_match;\n			unset($this->cleaver_partial_match);\n\n			// Extract the settings for this permlink\n			@extract($permlinks[$pretext_replacement[\'permlink_id\']][\'settings\']);\n\n			// If pretext_replacement is still set here then we have a match\n			if (@$pretext_replacement) {\n				$this->debug(\'Pretext Replacement \'.print_r($pretext_replacement, 1));\n\n				if (!empty($des_section))\n					$pretext_replacement[\'s\'] = $des_section;\n				if (!empty($des_category))\n					$pretext_replacement[\'c\'] = $des_category;\n				if (!empty($des_feed))\n					$pretext_replacement[$des_feed] = 1;\n\n				if (@$pretext_replacement[\'id\'] && @$pretext_replacement[\'Posted\']) {\n					if ($np = getNextPrev($pretext_replacement[\'id\'], $pretext_replacement[\'Posted\'], @$pretext_replacement[\'s\']))\n						$pretext_replacement = array_merge($pretext_replacement, $np);\n				}\n				unset($pretext_replacement[\'Posted\']);\n\n				// If there is a match then we most set the http status correctly as txp\'s pretext might set it to 404\n				$pretext_replacement[\'status\'] = \'200\';\n\n				// Store the orginial HTTP status code\n				// We might need to log the page hit if it equals 404\n				$orginial_status = $pretext[\'status\'];\n\n				// Txp only looks at the month, but due to how we phase the month we can manipulate the sql to our needs\n				if (array_key_exists(\'date\', $pretext_replacement)) {\n					$pretext_replacement[\'month\'] = $pretext_replacement[\'date\'];\n					unset($pretext_replacement[\'date\']);\n				} else if (array_key_exists(\'year\', $pretext_replacement) ||\n				array_key_exists(\'month\', $pretext_replacement) ||\n				array_key_exists(\'day\', $pretext_replacement)) {\n					$month = \'\';\n					$month .= (array_key_exists(\'year\', $pretext_replacement))\n						? $pretext_replacement[\'year\'].\'-\' : \'____-\';\n					$month .= (array_key_exists(\'month\', $pretext_replacement))\n						? $pretext_replacement[\'month\'].\'-\' : \'__-\';\n					$month .= (array_key_exists(\'day\', $pretext_replacement))\n						? $pretext_replacement[\'day\'].\' \' : \'__ \';\n\n					$pretext_replacement[\'month\'] = $month;\n					unset($pretext_replacement[\'year\']);\n					unset($pretext_replacement[\'day\']);\n				}\n\n				// Section needs to be defined so we can always get a page template.\n				if (!array_key_exists(\'s\', $pretext_replacement))\n				{\n					if (!@$pretext_replacement[\'id\'])\n						$pretext_replacement[\'s\'] = \'default\';\n					else\n						$pretext_replacement[\'s\'] = safe_field(\'Section\', \'textpattern\', \'ID = \'.$pretext_replacement[\'id\']);\n				}\n\n				// Set the css and page template, otherwise we get an unknown section\n				$section_settings = safe_row(\'css, page\', \'txp_section\', \"name = \'{$pretext_replacement[\'s\']}\' limit 1\");\n				$pretext_replacement[\'page\'] = (@$des_page) ? $des_page : $section_settings[\'page\'];\n				$pretext_replacement[\'css\']  = $section_settings[\'css\'];\n\n				$this->matched_permlink = $pretext_replacement;\n\n				global $permlink_mode;\n\n				if (in_array($prefs[\'permlink_mode\'], array(\'id_title\', \'section_id_title\')) && @$pretext_replacement[\'pg\'] && !@$pretext_replacement[\'id\']) {\n					$pretext_replacement[\'id\'] = \'\';\n					$pretext_replacement[\'is_article_list\'] = true;\n				}\n\n				// Merge pretext_replacement with pretext\n				$pretext = array_merge($pretext, $pretext_replacement);\n\n				if (is_numeric(@$pretext[\'id\'])) {\n					$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\');\n					populateArticleData($a);\n				}\n\n				// Export required values to the global namespace\n				foreach (array(\'id\', \'s\', \'c\', \'pg\', \'is_article_list\', \'prev_id\', \'prev_title\', \'next_id\', \'next_title\', \'css\') as $key) {\n					if (array_key_exists($key, $pretext))\n						$GLOBALS[$key] = $pretext[$key];\n				}\n\n				if (count($this->matched_permlink) || @$mt_redirect) {\n					$pl_index = $pretext[\'permlink_id\'];\n					if (!@$mt_redirect || !$this->pref(\'redirect_mt_style_links\')) {\n						$pl = $this->get_permlink($pretext[\'permlink_id\']);\n						$pl_index = @$pl[\'settings\'][\'des_permlink\'];\n					}\n\n					if (@$pretext[\'id\'] && $pl_index) {\n						if (count($this->get_permlink($pl_index)) > 0) {\n							ob_clean();\n							global $siteurl;\n							$rs = safe_row(\'*, ID as thisid, unix_timestamp(Posted) as posted\', \'textpattern\', \"ID = \'{$pretext[\'id\']}\'\");\n							$host = rtrim(str_replace(rtrim(doStrip($pretext[\'subpath\']), \'/\'), \'\', hu), \'/\');\n							$this->redirect($host.$this->_permlinkurl($rs, PERMLINKURL, $pl_index), $this->pref(\'permlink_redirect_http_status\'));\n						}\n					} else if ($url = @$pl[\'settings\'][\'des_location\']) {\n						ob_clean();\n						$this->redirect($url, $this->pref(\'url_redirect_http_status\'));\n					}\n				}\n\n				if (@$pretext[\'rss\']) {\n					if (@$pretext[\'s\'])\n						$_POST[\'section\'] = $pretext[\'s\'];\n					if (@$pretext[\'c\'])\n						$_POST[\'category\'] = $pretext[\'c\'];\n					ob_clean();\n					include txpath.\'/publish/rss.php\';\n					exit(rss());\n				}\n\n				if (@$pretext[\'atom\']) {\n					if (@$pretext[\'s\'])\n						$_POST[\'section\'] = $pretext[\'s\'];\n					if (@$pretext[\'c\'])\n						$_POST[\'category\'] = $pretext[\'c\'];\n					ob_clean();\n					include txpath.\'/publish/atom.php\';\n					exit(atom());\n				}\n\n				$this->debug(\'Pretext \'.print_r($pretext, 1));\n			} else {\n				$this->debug(\'NO CHANGES MADE\');\n			}\n\n			// Log this page hit\n			if (@$orginial_status == 404)\n				log_hit($pretext[\'status\']);\n\n			// Start output buffering and pseudo callback to textpattern_end\n			ob_start(array(&$this, \'_textpattern_end_callback\'));\n\n			// TxP 4.0.5 (r2436) introduced the textpattern_end callback making the following redundant\n			$version = array_sum(array_map(\n				create_function(\'$line\', \'if (preg_match(\\\'/^\\$\'.\'LastChangedRevision: (\\w+) \\$/\\\', $line, $match)) return $match[1];\'),\n				@file(txpath . \'/publish.php\')\n			));\n			if ($version >= \'2436\') return;\n\n			// Remove the plugin callbacks which have already been called\n			function filter_callbacks($c) {\n				if ($c[\'event\']!=\'textpattern\') return true;\n				if (@$c[\'function\'][0]->plugin_name == \'gbp_permanent_links\' &&\n						@$c[\'function\'][1] == \'_textpattern\')\n				{\n					$GLOBALS[\'gbp_found_self\'] = true;\n					return false;\n				}\n				return @$GLOBALS[\'gbp_found_self\'];\n			}\n			$plugin_callback = array_filter($plugin_callback, \'filter_callbacks\');\n			unset($GLOBALS[\'gbp_found_self\']);\n\n			// Re-call textpattern\n			textpattern();\n\n			// Call custom textpattern_end callback\n			$this->_textpattern_end();\n\n			// textpattern() has run, kill the connection\n			die();\n		}\n\n	} // function _textpattern end\n\n	function _textpattern_end () {\n		// Redirect to a 404 if the page number is greater than the max number of pages\n		// Has to be after textpattern() as $thispage is set during <txp:article />\n		global $thispage, $pretext;\n		if ((@$pretext[\'pg\'] && isset($thispage)) &&\n		($thispage[\'numPages\'] < $pretext[\'pg\'])) {\n			ob_end_clean();\n			txp_die(gTxt(\'404_not_found\'), \'404\');\n		}\n\n		// Stop output buffering, this sends the buffer to _textpattern_end_callback()\n		while (@ob_end_flush());\n\n	} // function _textpattern_end end\n\n	function _textpattern_end_callback ($html, $override = \'\') {\n		global $pretext, $production_status;\n\n		if ($override) $pretext[\'permlink_override\'] = $override;\n		$html = preg_replace_callback(\n			\'%href=\"(\'.hu.\'|\\?)([^\"]*)\"%\',\n			array(&$this, \'_pagelinkurl\'),\n			$html\n		);\n		unset($pretext[\'permlink_override\']);\n\n		if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\'))) {\n			$debug = join(n, $this->buffer_debug);\n			$this->buffer_debug = array();\n			if ($debug)\n				$html = comment(n.$debug.n) . $html;\n		}\n\n		return $html;\n	} // function _textpattern_end_callback end\n\n	function check_permlink_conditions ($pl, $article_array) {\n		if (empty($article_array[\'section\'])) $article_array[\'section\'] = @$article_array[\'Section\'];\n		if (empty($article_array[\'category1\'])) $article_array[\'category1\'] = @$article_array[\'Category1\'];\n		if (empty($article_array[\'category2\'])) $article_array[\'category2\'] = @$article_array[\'Category2\'];\n\n		if (@$pl[\'settings\'][\'con_category\'] && ($pl[\'settings\'][\'con_category\'] != $article_array[\'category1\'] || $pl[\'settings\'][\'con_category\'] != $article_array[\'category2\']))\n			return false;\n		if (@$pl[\'settings\'][\'con_section\'] && $pl[\'settings\'][\'con_section\'] != $article_array[\'section\'])\n			return false;\n\n		return true;\n	}\n\n	function _permlinkurl ($article_array, $type = PERMLINKURL, $pl_index = NULL) {\n		global $pretext, $prefs, $production_status;\n\n		if ($type == PAGELINKURL)\n			return $this->toggle_custom_url_func(\'pagelinkurl\', $article_array);\n\n		if (empty($article_array)) return;\n\n		if ($pl_index)\n			$pl = $this->get_permlink($pl_index);\n		else {\n			// Get the matched pretext replacement array.\n			$matched = (count($this->matched_permlink))\n			? $this->matched_permlink\n			: @array_shift(array_slice($this->partial_matches, -1));\n\n			if (!isset($pl) && $matched && array_key_exists(\'id\', $matched)) {\n				// The permlink id is stored in the pretext replacement array, so we can find the permlink.\n				$pl = $this->get_permlink($matched[\'permlink_id\']);\n				foreach ($pl[\'components\'] as $pl_c)\n					if (in_array($pl_c[\'type\'], array(\'feed\', \'page\')) || !$this->check_permlink_conditions($pl, $article_array)) {\n						unset($pl);\n						break;\n					}\n			}\n\n			if (!isset($pl)) {\n				// We have no permlink id so grab the permlink with the highest precedence.\n				$permlinks = $this->get_all_permlinks(1, array(\'feed\', \'page\'));\n				foreach ($permlinks as $key => $pl)\n					if (!$this->check_permlink_conditions($pl, $article_array))\n						unset($permlinks[$key]);\n				$pl = array_shift($permlinks);\n			}\n		}\n\n		$uri = \'\';\n\n		if (is_array($pl) && array_key_exists(\'components\', $pl)) {\n			extract($article_array);\n\n			if (!isset($title)) $title = $Title;\n			if (empty($url_title)) $url_title = stripSpace($title);\n			if (empty($section)) $section = $Section;\n			if (empty($posted)) $posted = $Posted;\n			if (empty($authorid)) $authorid = @$AuthorID;\n			if (empty($category1)) $category1 = @$Category1;\n			if (empty($category2)) $category2 = @$Category2;\n			if (empty($thisid)) $thisid = $ID;\n\n			$pl_components = $pl[\'components\'];\n\n			// Check to see if there is a title component.\n			$title = false;\n			foreach($pl_components as $pl_c)\n				if ($pl_c[\'type\'] == \'title\' || $pl_c[\'type\'] == \'id\')\n					$title = true;\n\n			// If there isn\'t a title component then we need to append one to the end of the URI\n			if (!$title && $this->pref(\'automatically_append_title\'))\n				$pl_components[] = array(\'type\' => \'title\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\');\n\n			$uri = rtrim(doStrip(@$pretext[\'subpath\']), \'/\');\n			foreach ($pl_components as $pl_c) {\n				$uri .= \'/\';\n\n				$type = $pl_c[\'type\'];\n				switch ($type) {\n					case \'category\':\n						if (!@$pl_c[\'category\']) $pl_c[\'category\'] = 1;\n						$primary = \'category\'. $pl_c[\'category\'];\n						$secondary = \'category\'. (3-(int)$pl_c[\'category\']);\n						$check_context = ($this->pref(\'join_pretext_to_pagelinks\') && $this->pref(\'check_pretext_category_context\'));\n						if (!$check_context || $$primary == $pretext[\'c\'])\n							$uri_c = $$primary;\n						else if (!$check_context || $$secondary == $pretext[\'c\'])\n							$uri_c = $$secondary;\n						else if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n							$uri_c = \'--INVALID_CATEGORY--\';\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'section\':\n						$check_context = ($this->pref(\'join_pretext_to_pagelinks\') && $this->pref(\'check_pretext_section_context\'));\n						if (!$check_context || $section == $pretext[\'s\'])\n							$uri_c = $section;\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'title\': $uri_c = $url_title; break;\n					case \'id\': $uri_c = $thisid; break;\n					case \'author\': $uri_c = safe_field(\'RealName\', \'txp_users\', \"name like \'{$authorid}\'\"); break;\n					case \'login\': $uri_c = $authorid; break;\n					case \'date\': $uri_c = explode(\'/\', date(\'Y/m/d\', $posted)); break;\n					case \'year\': $uri_c = date(\'Y\', $posted); break;\n					case \'month\': $uri_c = date(\'m\', $posted); break;\n					case \'day\': $uri_c = date(\'d\', $posted); break;\n					case \'custom\':\n						if ($uri_c = @$article_array[$prefs[\"custom_{$pl_c[\'custom\']}_set\"]]);\n						else if ($uri_c = @$article_array[\"custom_{$pl_c[\'custom\']}\"]);\n						else if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n							$uri_c = \'--UNSET_CUSTOM_FIELD--\';\n						else {\n							unset($uri);\n							break 2;\n						}\n					break;\n					case \'text\': $uri_c = $pl_c[\'text\']; break;\n					case \'regex\':\n						// Check to see if regex is valid without outputting error messages.\n						ob_start();\n						preg_match($pl_c[\'regex\'], $pl_c[\'regex\'], $regex_matches);\n						$is_valid_regex = !(ob_get_clean());\n						if ($is_valid_regex) {\n							$key = \"permlink_regex_{$pl_c[\'name\']}\";\n							$uri_c = (array_key_exists($key, $pretext)) ? $pretext[$key] : $regex_matches[0];\n						} else if ($this->pref(\'debug\'))\n							$uri_c = \'--INVALID_REGEX--\';\n					break;\n				}\n\n				if (empty($uri_c))\n					if ($this->pref(\'debug\') && in_array($production_status, array(\'debug\', \'testing\')))\n						$uri_c = \'--PERMLINK_FORMAT_ERROR--\';\n					else {\n						unset($uri);\n						break;\n					}\n\n				if (@$pl_c[\'prefix\'])\n					$uri .= $this->encode_url($pl_c[\'prefix\']);\n\n				if (is_array($uri_c)) {\n					foreach ($uri_c as $uri_c2)\n						$uri .= $this->encode_url($uri_c2) . \'/\';\n					$uri = rtrim($uri, \'/\');\n				} else\n					$uri .= $this->encode_url($uri_c);\n\n				if (@$pl_c[\'suffix\'])\n					$uri .= $this->encode_url($pl_c[\'suffix\']);\n\n				unset($uri_c);\n			}\n\n			if (isset($uri))\n				$uri .= \'/\';\n		}\n\n		if ($uri_empty = empty($uri)) {\n			// It is possible the uri is still empty if there is no match or if we\'re using\n			// strict matching if so try the default permlink mode.\n			$uri = $this->toggle_permlink_mode(\'permlinkurl\', $article_array);\n		}\n\n		if ($this->pref(\'omit_trailing_slash\'))\n			$uri = rtrim($uri, \'/\');\n\n		if (!$uri_empty && in_array(txpath.\'/publish/rss.php\', get_included_files()) || in_array(txpath.\'/publish/atom.php\', get_included_files()) || txpinterface == \'admin\') {\n			$host = rtrim(str_replace(rtrim(doStrip(@$pretext[\'subpath\']), \'/\'), \'\', hu), \'/\');\n			$uri = $host . $uri;\n		}\n\n		return ($this->pref(\'force_lowercase_urls\')) ? strtolower($uri) : $uri;\n	}\n\n	function _pagelinkurl ($parts) {\n		extract(lAtts(array(\n			\'path\'		=> \'index.php\',\n			\'query\'		=> \'\',\n			\'fragment\'	=> \'\',\n		), parse_url(html_entity_decode(str_replace(\'&#38;\', \'&\', $parts[2])))));\n\n		// Tidy up links back to the site homepage\n		if ($path == \'index.php\' && empty($query))\n			return \'href=\"\' .hu. \'\"\';\n\n		// Fix matches like href=\"?s=foo\"\n		else if ($path && empty($query) && $parts[1] == \'?\') {\n			$query = $path;\n			$path = \'index.php\';\n		}\n\n		// Check to see if there is query to work with.\n		else if (empty($query) || $path != \'index.php\' || strpos($query, \'/\') === true)\n			return $parts[0];\n\n		// \'&amp;\' will break parse_str() if they are found in a query string\n		$query = str_replace(\'&amp;\', \'&\', $query);\n\n		if ($fragment)\n			$fragment = \'#\'.$fragment;\n\n		global $pretext;\n		parse_str($query, $query_part);\n		if (!array_key_exists(\'pg\', $query_part))\n			$query_part[\'pg\'] = 0;\n		if (!array_key_exists(\'id\', $query_part))\n			$query_part[\'id\'] = 0;\n		if (!array_key_exists(\'rss\', $query_part))\n			$query_part[\'rss\'] = 0;\n		if (!array_key_exists(\'atom\', $query_part))\n			$query_part[\'atom\'] = 0;\n		if ($this->pref(\'join_pretext_to_pagelinks\'))\n			extract(array_merge($pretext, $query_part));\n		else\n			extract($query_part);\n\n		// We have a id, pass to permlinkurl()\n		if ($id) {\n			if (@$s == \'file_download\') {\n				$title = (version_compare($dbversion, \'4.2\', \'>=\')) ? NULL : safe_field(\'filename\', \'txp_file\', \"id = \'{$id}\'\");\n				$url = $this->toggle_permlink_mode(\'filedownloadurl\', array($id, $title), true);\n			} else {\n				$rs = safe_row(\'*, ID as thisid, unix_timestamp(Posted) as posted\', \'textpattern\', \"ID = \'{$id}\'\");\n				$url = $this->_permlinkurl($rs, PERMLINKURL) . $fragment;\n			}\n			return \'href=\"\'. $url .\'\"\';\n		}\n\n		if (@$s == \'default\')\n			unset($s);\n\n		// Some TxP tags, e.g. <txp:feed_link /> use \'section\' or \'category\' inconsistent\n		// with most other tags. Process these now so we only have to check $s and $c.\n		if (@$section)  $s = $section;\n		if (@$category) $c = $category;\n\n		// Debugging for buffers\n		$this->buffer_debug[] = \'url: \'.str_replace(\'&amp;\', \'&\', $parts[1].$parts[2]);\n		$this->buffer_debug[] = \'path: \'.$path;\n		$this->buffer_debug[] = \'query: \'.$query;\n		if ($fragment) $this->buffer_debug[] = \'fragment: \'.$fragment;\n\n		if (@$id) $this->buffer_debug[] = \'id: \'.$id;\n		if (@$s) $this->buffer_debug[] = \'s: \'.$s;\n		if (@$c) $this->buffer_debug[] = \'c: \'.$c;\n		if (@$rss) $this->buffer_debug[] = \'rss: \'.$rss;\n		if (@$atom) $this->buffer_debug[] = \'atom: \'.$atom;\n		if (@$pg) $this->buffer_debug[] = \'pg: \'.$pg;\n		if (@$q) $this->buffer_debug[] = \'q: \'.$q;\n\n		if (@$pretext[\'permlink_override\']) {\n			$override_ids = explode(\',\', $pretext[\'permlink_override\']);\n			foreach ($override_ids as $override_id) {\n				$pl = $this->get_permlink($override_id);\n				if (count($pl) > 0) $permlinks[] = $pl;\n			}\n		}\n\n		if (empty($permlinks)) {\n			$permlinks = $this->get_all_permlinks(1);\n\n			$permlinks[\'gbp_permanent_links_default\'] = array(\n				\'components\' => array(\n					array(\'type\' => \'text\', \'text\' => strtolower(urlencode(gTxt(\'category\')))),\n					array(\'type\' => \'category\'),\n				),\n				\'settings\' => array(\n					\'pl_name\' => \'gbp_permanent_links_default\', \'pl_precedence\' => \'\', \'pl_preview\' => \'\',\n					\'con_section\' => \'\', \'con_category\' => \'\', \'des_section\' => \'\', \'des_category\' => \'\',\n					\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\',\n			));\n		}\n\n		$current_segments = explode(\'/\', ltrim($pretext[\'request_uri\'], \'/\'));\n\n		$highest_match_count = null;\n		foreach ($permlinks as $key => $pl) {\n			$this->buffer_debug[] = \'Testing permlink: \'. $pl[\'settings\'][\'pl_name\'] .\' - \'. $key;\n			$this->buffer_debug[] = \'Preview: \'. $pl[\'settings\'][\'pl_preview\'];\n			$out = array(); $keys = array(); $match_count = 0;\n			foreach ($pl[\'components\'] as $i => $pl_c) {\n				switch ($pl_c[\'type\']) {\n					case \'text\':\n						$out[] = $pl_c[\'text\'];\n					break;\n					case \'regex\':\n						$out[] = $pretext[\'permlink_regex_\'.$pl_c[\'name\']];\n					break;\n					case \'section\':\n						if (@$s) $out[] = $s;\n						else break 2;\n					break;\n					case \'category\':\n						if (@$c) $out[] = $c;\n						else break 2;\n					break;\n					case \'feed\':\n						if (@$rss) $keys[] = $out[] = \'rss\';\n						else if (@$atom) $keys[] = $out[] = \'atom\';\n						else break 2;\n					break;\n					case \'search\':\n						if (@$q) $out[] = $q;\n						else break 2;\n					break;\n					case \'page\':\n						if (@$pg) {\n							$out[] = $pg;\n							$keys[] = \'pg\';\n						}\n						else break 2;\n					break;\n					default: break 2;\n				}\n				if (in_array($pl_c[\'type\'], array(\'text\', \'regex\'))) {\n					if ($current_segments[$i] == end($out) && strlen(end($out)) > 0)\n						$match_count += $this->pref(\'text_and_regex_segment_scores\');\n				}\n				elseif (!in_array($pl_c[\'type\'], array(\'title\', \'id\')))\n					$match_count++;\n				else break;\n			}\n\n			$this->buffer_debug[] = \'Match count: \'. $match_count;\n\n			// Todo: Store according to the precedence value\n			if (count($out) > 0 && ($match_count > $highest_match_count || !isset($highest_match_count)) &&\n			!($key == \'gbp_permanent_links_default\' && !$match_count)) {\n				extract($pl[\'settings\']);\n				if ((empty($s) && empty($c)) ||\n				(empty($con_section) || @$s == $con_section) ||\n				(empty($con_category) || @$c == $con_category)) {\n					$this->buffer_debug[] = \'New highest match! \'. implode(\'/\', $out);\n					$highest_match_count = $match_count;\n					$match = $out;\n					$match_keys = $keys;\n				}\n			}\n		}\n\n		if (empty($match) && (!(@$pg && $this->pref(\'clean_page_archive_links\')) || (@$pg && @$q))) {\n			global $prefs, $pretext, $permlink_mode;\n			$this->buffer_debug[] = \'No match\';\n			$this->buffer_debug[] = \'----\';\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n			$url = pagelinkurl($query_part);\n			$pretext[\'permlink_mode\'] = $permlink_mode = \'messy\';\n			return \'href=\"\'. $url .\'\"\';\n		}\n\n		$this->buffer_debug[] = \'match: \'.      serialize($match);\n		$this->buffer_debug[] = \'match_keys: \'. serialize($match_keys);\n\n		$url = \'/\'.join(\'/\', $match);\n		$url = rtrim(hu, \'/\').rtrim($url, \'/\').\'/\';\n\n		if ($rss && !in_array(\'rss\', $match_keys))\n			$url .= \'rss\';\n		else if ($atom && !in_array(\'atom\', $match_keys))\n			$url .= \'atom\';\n		else if ($pg && !in_array(\'pg\', $match_keys)) {\n			if ($this->pref(\'clean_page_archive_links\'))\n				$url .= $pg;\n			else {\n				$url .= \'?pg=\'. $pg;\n				$omit_trailing_slash = true;\n			}\n		}\n\n		$url = rtrim($url, \'/\') . \'/\';\n\n		if (@$omit_trailing_slash || $this->pref(\'omit_trailing_slash\'))\n			$url = rtrim($url, \'/\');\n\n		$this->buffer_debug[] = $url;\n		$this->buffer_debug[] = \'----\';\n\n		if ($path == \'index.php\' && $url != hu)\n			return \'href=\"\'. $url . $fragment .\'\"\';\n\n		/*\n		1 = index, textpattern/css, NULL (=index)\n		2 = id, s, section, c, category, rss, atom, pg, q, (n, p, month, author)\n		*/\n\n		return ($this->pref(\'force_lowercase_urls\')) ? strtolower($parts[0]) : $parts[0];\n	}\n\n	function set_permlink_mode ($reset_function = false) {\n		global $prefs, $pretext, $permlink_mode;\n		$prefs[\'custom_url_func\'] = array(&$this, \'_permlinkurl\');\n\n		if (!$reset_function)\n			$pretext[\'permlink_mode\'] = $permlink_mode = \'messy\';\n		else\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n	}\n\n	function reset_permlink_mode () {\n		global $prefs, $pretext, $permlink_mode;\n		unset($prefs[\'custom_url_func\']);\n		$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n	}\n\n	function toggle_custom_url_func ($func, $atts = NULL, $toogle_permlink_mode = false, $expand_arguments = false) {\n		global $prefs, $pretext;\n\n		if ($toogle_permlink_mode) {\n			global $permlink_mode;\n			$_permlink_mode = $permlink_mode;\n		}\n\n		$_call_user_func = @$prefs[\'custom_url_func\'];\n\n		unset($prefs[\'custom_url_func\']);\n		if ($toogle_permlink_mode)\n			$pretext[\'permlink_mode\'] = $permlink_mode = $prefs[\'permlink_mode\'];\n\n		if (is_callable($func)) {\n			if (is_array($atts) and $expand_arguments)\n				$rs = call_user_func_array($func, $atts);\n			else\n				$rs = call_user_func($func, $atts);\n		}\n\n		$prefs[\'custom_url_func\'] = $_call_user_func;\n\n		if ($toogle_permlink_mode)\n			$pretext[\'permlink_mode\'] = $permlink_mode = $_permlink_mode;\n\n		return $rs;\n	}\n\n	function toggle_permlink_mode ($func, $atts = NULL, $expand_arguments = false) {\n		return $this->toggle_custom_url_func($func, $atts, true, $expand_arguments);\n	}\n\n	function encode_url ($text) {\n		return urlencode(trim(dumbDown(urldecode($text))));\n	}\n\n	function debug () {\n		if ($this->pref(\'debug\')) {\n			global $production_status;\n			$a = func_get_args();\n\n			if (@txpinterface == \'admin\')\n				foreach ($a as $thing)\n					dmp($thing);\n\n			if (@txpinterface == \'public\' && in_array($production_status, array(\'debug\', \'testing\')))\n				foreach ($a as $thing)\n					echo comment(is_scalar($thing) ? strval($thing) : var_export($thing, true)),n;\n		}\n	}\n}\n\nclass PermanentLinksBuildTabView extends GBPAdminTabView\n{\n	function preload () {\n		register_callback(array(&$this, \'post_save_permlink\'), $this->parent->event, gbp_save, 1);\n		register_callback(array(&$this, \'post_save_permlink\'), $this->parent->event, gbp_post, 1);\n	}\n\n	function main () {\n		global $prefs;\n		extract(gpsa(array(\'step\', gbp_id)));\n\n		// With have an ID, either the permlink has just been saved or the user wants to edit it\n		if ($id) {\n			// Newly saved or beening edited, either way we\'re editing a permlink\n			$step = gbp_save;\n\n			// Use the ID to grab the permlink data (components & settings)\n			$permlink = $this->parent->get_permlink($id);\n			$components = $this->phpArrayToJsArray(\'components\', $permlink[\'components\']);\n			$settings = $permlink[\'settings\'];\n		} else {\n			// Creating a new ID and permlink.\n			$step = gbp_post;\n			$id = uniqid(\'\');\n\n			// Set the default set of components depending on whether there is parent permlink\n			$components = $this->phpArrayToJsArray(\'components\', array(\n				array(\'type\' => \'section\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\'),\n				array(\'type\' => \'category\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\', \'category\' => \'1\'),\n				array(\'type\' => \'title\', \'prefix\' => \'\', \'suffix\' => \'\', \'regex\' => \'\', \'text\' => \'\'),\n			));\n\n			$settings = array(\n				\'pl_name\' => \'Untitled\', \'pl_precedence\' => \'0\',\n				\'con_section\' => \'\', \'con_category\' => \'\',\n				\'des_section\' => \'\', \'des_category\' => \'\', \'des_page\' => \'\',\n				\'des_permlink\' => \'\', \'des_feed\' => \'\', \'des_location\' => \'\',\n			);\n		}\n\n		// Extract settings - this will be useful when creating the user interface\n		extract($settings);\n\n		// PHP & Javascript constants;\n		$separator = gbp_separator;\n		$components_div = \'permlink_components_ui\';\n		$components_form = \'permlink_components\';\n		$settings_form = \'permlink_settings\';\n		$show_prefix = $this->pref(\'show_prefix\');\n		$show_suffix = $this->pref(\'show_suffix\');\n\n		// A little credit here and there doesn\'t hurt\n		$out[] = \"<!-- {$this->parent->plugin_name} by Graeme Porteous -->\";\n\n		// The Javascript\n$out[] = <<<HTML\n	<script type=\"text/javascript\" language=\"javascript\" charset=\"utf-8\">\n	// <![CDATA[\n\n	// Global variables\nvar {$components}// components array for all the data\n\n	var _current = 0; // Index of the components array, of the currently selected component\n	var c_vals = new Array(\'type\', \'custom\', \'category\', \'name\', \'prefix\', \'suffix\', \'regex\', \'text\');\n\n	window.onload = function() {\n		component_refresh_all();\n		component_switch(component(_current));\n	}\n\n	function component_add () {\n		// Create data set\n		var data = new Array();\n		for (key in c_vals) {\n			data[c_vals[key]] = \'\';\n		}\n\n		// Add data\n		components.push(data);\n\n		// Reset component type list\n		form(\'{$components_form}\').type.value = \'\';\n\n		// Switch to the new component\n		_current = components.length - 1;\n\n		// Refresh UI\n		component_refresh_all();\n		component_update();\n	}\n\n	function component_refresh (element) {\n		var c = components[element.id];\n\n		// CSS\n		if (_current == element.id)\n			element.style[\'backgroundColor\'] = \'rgb(249, 206, 73)\';\n		else\n			element.style[\'backgroundColor\'] = \'rgb(255, 254, 211)\';\n		element.style[\'color\'] = \'rgb(0, 0, 0)\';\n		element.style[\'fontFamily\'] = \'Arial\';\n		element.style[\'fontWeight\'] = \'bold\';\n		element.style[\'verticalAlign\'] = \'middle\';\n		element.style[\'textAlign\'] = \'center\';\n		element.style[\'lineHeight\'] = \'1.5em\';\n		element.style[\'height\'] = \'1.5em\';\n		element.style[\'padding\'] = \'0 5px\';\n		element.style[\'marginRight\'] = \'5px\';\n		element.style[\'cssFloat\'] = \'left\';\n		element.style[\'display\'] = \'inline\';\n\n		// Remove all child nodes\n		while (element.hasChildNodes()) { element.removeChild(element.firstChild); }\n\n		// Create the visible string representing this component\n		switch (c[\'type\']) {\n			case \'\' :\n				string = \'/\';\n				break;\n			case \'regex\' :\n			case \'text\' :\n				string = c[c[\'type\']] + \' /\';\n				break;\n			case \'date\' :\n				string = c[\'type\'] + \' /\';\n				break;\n			case \'custom\' :\n				string = c[\'prefix\'] + \'custom_\' + c[\'custom\'] + c[\'suffix\'] + \' /\';\n				break;\n			default :\n				string = c[\'prefix\'] + c[\'type\'] + c[\'suffix\'] + \' /\';\n			break;\n		}\n\n		// Set the visible string\n		element.appendChild(document.createTextNode(string));\n\n		return element;\n	}\n\n	function component_refresh_all () {\n		// Remove all child nodes\n		while (permlink_div().hasChildNodes()) { permlink_div().removeChild(permlink_div().firstChild); }\n\n		for (var i = 0; i < components.length; i++) {\n			var c = components[i];\n\n			// Create the new component\n			var new_component = document.createElement(\'div\');\n\n			// Set the id interger for this component\n			new_component.id = i;\n\n			// Javascript, onmouseup setting\n			new_component.setAttribute(\'onmousedown\', \'component_switch(this);\');\n			new_component.onmousedown = function() { component_switch(this); };\n\n			// Refresh the look of the component\n			new_component = component_refresh(new_component);\n\n			// And add the new component to the ui\n			permlink_div().appendChild(new_component);\n		}\n	}\n\n	function component_remove () {\n		if (components.length > 1) {\n			components.splice(_current, 1);\n\n			if (_current >= components.length)\n				_current = components.length - 1;\n\n			component_refresh_all();\n		}\n	}\n\n	function component_switch (element) {\n		// Update current index\n		_current = element.id;\n\n		// Refresh UI\n		component_refresh_all();\n\n		// Set form input values\n		var c = components[_current];\n		for (key in c_vals) {\n			var k = c_vals[key];\n			var e = form(\'{$components_form}\').elements.namedItem(k);\n\n			if (c[k]) e.value = c[k];\n			else e.value = \'\';\n		}\n\n		// Hide unneeded form inputs\n		component_update();\n	}\n\n	function component_update (element) {\n		// Store the data in form inputs, and hide all form inputs\n		var c = new Array()\n		for (key in c_vals) {\n			var k = c_vals[key];\n			var e = form(\'{$components_form}\').elements.namedItem(k);\n\n			c[k] = e.value;\n\n			e.parentNode.style[\'display\'] = \'none\';\n		}\n\n		// Reshow type option list\n		form(\'{$components_form}\').type.parentNode.style[\'display\'] = \'\';\n\n		// Set other form inputs to the correct visibility state, dependent on type\n		switch (c[\'type\']) {\n			case \'\' :\n			case \'date\' : break;\n			case \'regex\' :\n				form(\'{$components_form}\').name.parentNode.style[\'display\'] = \'\';\n				form(\'{$components_form}\').regex.parentNode.style[\'display\'] = \'\';\n			break;\n			case \'text\' :\n				form(\'{$components_form}\').name.parentNode.style[\'display\'] = \'\';\n				form(\'{$components_form}\').text.parentNode.style[\'display\'] = \'\';\n			break;\n			case \'custom\' :\n				form(\'{$components_form}\').custom.parentNode.style[\'display\'] = \'\';\n				display_fixes();\n			break;\n			case \'category\' :\n				form(\'{$components_form}\').category.parentNode.style[\'display\'] = \'\';\n				display_fixes();\n			break;\n			default :\n				display_fixes();\n			break;\n		}\n\n		// Save data\n		components[_current] = c;\n\n		// Refresh component to reflect new data\n		component_refresh(component(_current));\n\n		// Re-focus the active form input\n		if (element)\n			element.focus();\n	}\n\n	function display_fixes () {\n		if (\'{$show_prefix}\')\n			form(\'{$components_form}\').prefix.parentNode.style[\'display\'] = \'\';\n		if (\'{$show_suffix}\')\n			form(\'{$components_form}\').suffix.parentNode.style[\'display\'] = \'\';\n	}\n\n	function component_left () {\n		if (components.length > 1 && _current > 0) {\n			// Store current component\n			var c = components[_current];\n\n			// Remove current component\n			components.splice(_current, 1);\n\n			// Update current index\n			_current--;\n\n			// Re-add current component\n			components.splice(_current, 0, c);\n\n			// Refresh UI\n			component_refresh_all();\n		}\n	}\n\n	function component_right () {\n		if (_current < components.length - 1) {\n			// Store current component\n			var c = components[_current];\n\n			// Remove current component\n			components.splice(_current, 1);\n\n			// Update current index\n			_current++;\n\n			// Re-add current component\n			components.splice(_current, 0, c);\n\n			// Refresh UI\n			component_refresh_all();\n		}\n	}\n\n	function save (form) {\n		var c = \'\'; var is_permlink = false; var has_page_or_search = false;\n		for (var i = 0; i < components.length; i++) {\n			if (components[i][\'type\'] == \'title\' || components[i][\'type\'] == \'id\')\n				is_permlink = true;\n			if (components[i][\'type\'] == \'page\' || components[i][\'type\'] == \'feed\' || components[i][\'type\'] == \'search\')\n				has_page_feed_search = true;\n			c = c + jsArrayToPhpArray(components[i]) + \'{$separator}\';\n		}\n\n		if (is_permlink && has_page_or_search)\n			alert(\"Your permanent link can\'t contain either a \'page\', \'feed\' or a \'search\' component with \'title\' or \'id\' components.\");\n\n		else if (is_permlink && (form.pl_name.value == \'\' || form.pl_name.value == \'Untitled\')) {\n			document.getElementById(\'settings\').style[\'display\'] = \'\';\n			form.pl_name.style[\'border\'] = \'3px solid rgb(221, 0, 0)\';\n			form.pl_precedence.style[\'border\'] = \'\';\n			alert(\'Please enter a name for this permanent link rule.\');\n		} else if (form.pl_precedence.value == \'\') {\n			document.getElementById(\'settings\').style[\'display\'] = \'\';\n			form.pl_precedence.style[\'border\'] = \'3px solid rgb(221, 0, 0)\';\n			form.pl_name.style[\'border\'] = \'\';\n			alert(\'Please enter a precedence value for this permanent link rule.\');\n		} else {\n			form.components.value = c;\n			if (permlink_div().textContent)\n				form.pl_preview.value = permlink_div().textContent;\n			else if (permlink_div().innerText)\n				form.pl_preview.value = permlink_div().innerText;\n			return true;\n		}\n\n		return false;\n	}\n\n	function jsArrayToPhpArray (array) {\n		// http://farm.tucows.com/blog/_archives/2005/5/30/895901.html\n		var array_php = \"\";\n		var total = 0;\n		for (var key in array) {\n			++ total;\n			array_php = array_php + \"s:\" +\n				String(key).length + \":\\\"\" + String(key) + \"\\\";s:\" +\n				String(array[key]).length + \":\\\"\" + String(array[key]) + \"\\\";\";\n		}\n		array_php = \"a:\" + total + \":{\" + array_php + \"}\";\n		return array_php;\n	}\n\n	function permlink_div () {\n		// Return the permlink rule element\n		return document.getElementById(\'{$components_div}\');\n	}\n\n	function form (name) {\n		if (!name)\n			name = \'{$components_form}\'\n		// Return the form element with name\n		return document.forms.namedItem(name);\n	}\n\n	function component (index) {\n		// Return component with index\n		return permlink_div().childNodes[index];\n	}\n\n	// ]]>\n	</script>\nHTML;\n\n		// --- Rule --- //\n\n		$out[] = hed(\'Permanent link rule\', 2);\n		$out[] = \'<div id=\"\'.$components_div.\'\" style=\"background-color: rgb(230, 230, 230); width: auto; height: 1.5em; margin: 10px 0; padding: 5px;\"></div>\';\n		$out[] = graf\n			(\n			$this->fInput(\'button\', \'component_add\', \'Add component\', array(\'click\' => \'component_add();\')).n.\n			$this->fInput(\'button\', \'component_remove\', \'Remove component\', array(\'click\' => \'component_remove();\')).n.\n			$this->fInput(\'button\', \'component_left\', \'Move left\', array(\'click\' => \'component_left();\')).n.\n			$this->fInput(\'button\', \'component_right\', \'Move right\', array(\'click\' => \'component_right();\'))\n			);\n\n		// --- Component form --- //\n\n		$out[] = \'<form action=\"index.php\" name=\"\'.$components_form.\'\" onsubmit=\"return false;\">\';\n\n		// --- Component type --- //\n\n		$component_types = array (\n			\'section\' => \'Section\', \'category\' => \'Category\',\n			\'title\' => \'Title\', \'id\' => \'ID\',\n			\'date\' => \'Date (yyyy/mm/dd)\', \'year\' => \'Year\',\n			\'month\' => \'Month\', \'day\' => \'Day\',\n			\'author\' => \'Author (Real name)\', \'login\' => \'Author (Login)\',\n			\'custom\' => \'Custom Field\', \'page\' => \'Page Number\',\n			\'feed\' => \'Feed\', \'search\' => \'Search request\',\n			\'text\' => \'Plain Text\', \'regex\' => \'Regular Expression\'\n		);\n		$out[] = graf($this->fSelect(\'type\', $component_types, \'\', 1, \'Component type\', \' onchange=\"component_update();\"\'));\n\n		// --- Component data --- //\n\n		// Grab the custom field titles\n		$custom_fields = array();\n		for ($i = 1; $i <= 10; $i++) {\n			if ($v = $prefs[\"custom_{$i}_set\"])\n				$custom_fields[$i] = $v;\n		}\n\n		$out[] = graf (\n			$this->fSelect(\'custom\', $custom_fields, \'\', 0, \'Custom\', \' onchange=\"component_update(this);\"\').n.\n			$this->fSelect(\'category\', array(\'1\' => \'Category 1\', \'2\' => \'Category 2\'), \'\', 0, \'Primary Category\', \' onchange=\"component_update(this);\"\').n.\n			$this->fInput(\'text\', \'name\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Name\').n.\n			$this->fInput(\'text\', \'prefix\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Prefix\').n.\n			$this->fInput(\'text\', \'regex\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Regular Expression\').n.\n			$this->fInput(\'text\', \'suffix\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Suffix\').n.\n			$this->fInput(\'text\', \'text\', \'\', array(\'keyup\' => \'component_update(this);\'), \'Text\')\n		);\n		$hr = \'<hr style=\"border: 0; height: 1px; background-color: rgb(200, 200, 200); color: rgb(200, 200, 200); margin-bottom: 10px;\" />\';\n		$out[] = $hr;\n		$out[] = \'</form>\';\n\n		// --- Settings form --- //\n\n		$out[] = \'<form action=\"index.php\" method=\"post\" name=\"\'.$settings_form.\'\" onsubmit=\"return save(this);\">\';\n\n		// --- Settings --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'settings\\\'); return false;\">Settings</a>\', 2);\n		$out[] = \'<div id=\"settings\">\';\n		$out[] = graf($this->fInput(\'text\', \'pl_name\', $pl_name, NULL, \'Name\'));\n		$out[] = graf($this->fInput(\'text\', \'pl_precedence\', $pl_precedence, NULL, \'Precedence\'));\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// --- Conditions --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'conditions\\\'); return false;\">Conditions</a>\', 2);\n		$out[] = \'<div id=\"conditions\" style=\"display:none\">\';\n		$out[] = graf(strong(\'Only use this permanent link if the following conditions apply...\'));\n\n		// Generate a sections array (name=>title)\n		$sections = array();\n		$rs = safe_rows(\'name, title\', \'txp_section\', \"name != \'default\' order by name\");\n		foreach ($rs as $sec) {\n			$sections[$sec[\'name\']] = $sec[\'title\'];\n		}\n\n		// Generate a categories array (name=>title)\n		$categories = array();\n		$rs = safe_rows(\'name, title\', \'txp_category\', \"type = \'article\' and name != \'root\' order by name\");\n		foreach ($rs as $sec) {\n			$categories[$sec[\'name\']] = $sec[\'title\'];\n		}\n\n		$out[] = graf (\n			$this->fSelect(\'con_section\', $sections, $con_section, 1, \'Within section\').n.\n			$this->fSelect(\'con_category\', $categories, $con_category, 1, \'Within category\')\n		);\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// --- Destination --- //\n\n		$out[] = hed(\'<a href=\"#\" onclick=\"toggleDisplay(\\\'destination\\\'); return false;\">Destination</a>\', 2);\n		$out[] = \'<div id=\"destination\" style=\"display:none\">\';\n		$out[] = graf(strong(\'Forward this permanent link to...\'));\n		$out[] = graf (\n			$this->fSelect(\'des_section\', $sections, $des_section, 1, \'Section\').n.\n			$this->fSelect(\'des_category\', $categories, $des_category, 1, \'Category\')\n		);\n		$out[] = graf($this->fSelect(\'des_page\', safe_column(\'name\', \'txp_page\', \"1\"), @$des_page, 1, \'Page\'));\n		$out[] = graf($this->fBoxes(\'des_feed\', array(\'rss\', \'atom\', \'\'), $des_feed, NULL, array(\'RSS feed\', \'Atom feed\', \'Neither\')));\n		$out[] = graf(strong(\'Redirect this permanent link to...\'));\n		// Generate a permlinks array\n		$permlinks = $this->parent->get_all_permlinks(1);\n		foreach ($permlinks as $key => $pl) {\n			$permlinks[$key] = $pl[\'settings\'][\'pl_name\'];\n		}\n		unset($permlinks[$id]);\n		$out[] = graf($this->fSelect(\'des_permlink\', $permlinks, @$des_permlink, 1, \'Permanent link\'));\n		$out[] = graf($this->fInput(\'text\', \'des_location\', $des_location, NULL, \'HTTP location\'));\n		$out[] = \'</div>\';\n		$out[] = $hr;\n\n		// Save button\n		$out[] = fInput(\'submit\', \'\', \'Save permanent link\');\n\n		// Extra form inputs which get filled on submit\n		$out[] = hInput(\'components\', \'\');\n		$out[] = hInput(\'pl_preview\', \'\');\n		// Event and tab form inputs\n		$out[] = $this->form_inputs();\n		// Step and ID form inputs\n		$out[] = sInput($step);\n		$out[] = hInput(gbp_id, $id);\n\n		$out[] = \'</form>\';\n\n		// Lets echo everything out. Yah!\n		echo join(n, $out);\n	}\n\n	function fLabel ($label, $contents = \'\', $label_right = false) {\n		// <label> the contents with the name $lable\n		$contents = ($label_right)\n		? $contents.$label\n		: $label.($contents ? \': \'.$contents : \'\');\n		return tag($contents, \'label\');\n	}\n\n	function fBoxes ($name = \'\', $value = \'\', $checked_value = \'\', $on = array(), $label = \'\') {\n		$out = array();\n		if (is_array($value)) {\n			$i = 0;\n			foreach ($value as $val) {\n				$o = \'<input type=\"radio\" name=\"\'.$name.\'\" value=\"\'.$val.\'\"\';\n				$o .= ($checked_value == $val) ? \' checked=\"checked\"\' : \'\';\n				if (is_array($on)) foreach($on as $k => $v)\n					$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n				$o .= \' />\';\n				$out[] = $this->fLabel($label[$i++], $o, true);\n			}\n		} else {\n			$o = \'<input type=\"checkbox\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\';\n			$o .= ($checked_value == $value) ? \' checked=\"checked\"\' : \'\';\n			if (is_array($on)) foreach($on as $k => $v)\n				$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n			$o .= \' />\';\n			$out[] = $this->fLabel($label, $o, true);\n		}\n\n		return join(\'\', $out);\n	}\n\n	function fInput ($type, $name = \'\', $value = \'\', $on = array(), $label = \'\') {\n		if ($type == \'radio\' || $type == \'checkbox\')\n			return $this->fBoxes($name, $value, $on, $label);\n\n		$o = \'<input type=\"\'.$type.\'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\';\n		if (is_array($on)) foreach($on as $k => $v)\n			$o .= ($on) ? \' on\'.$k.\'=\"\'.$v.\'\"\' : \'\';\n		$o .= \' />\';\n		return ($label) ? $this->fLabel($label, $o) : $o;\n	}\n\n	function fSelect ($name = \'\', $array = \'\', $value = \'\', $blank_first = \'\', $label = \'\', $on_submit = \'\') {\n		$o = selectInput($name, $array, $value, $blank_first, $on_submit);\n		return ($label ? $this->fLabel($label, $o) : $o);\n	}\n\n	function post_save_permlink () {\n		// The function posts or saves a permanent link to txp_prefs\n\n		extract(gpsa(array(\'step\', gbp_id)));\n\n		// Grab the user defined settings from the form POST data\n		$settings = gpsa(array(\n			\'pl_name\', \'pl_precedence\', \'pl_preview\',\n			\'con_section\', \'con_category\',\n			\'des_section\', \'des_category\', \'des_page\',\n			\'des_permlink\', \'des_feed\', \'des_location\',\n		));\n\n		// Remove spaces from the permanent link preview\n		$settings[\'pl_preview\'] = preg_replace(\'%\\s+/\\s*%\', \'/\', $settings[\'pl_preview\']);\n\n		// Explode the separated string of serialize components - this was made by JavaScript.\n		$serialize_components = explode(gbp_separator, rtrim(gps(\'components\'), gbp_separator));\n\n		// Unserialize the components\n		$components = array();\n		foreach ($serialize_components as $c)\n			$components[] = unserialize(stripslashes($c));\n\n		// Complete the permanent link array - this is exactly what needs to be stored in the db\n		$permlink = array(\'settings\' => $settings, \'components\' => $components);\n\n		// Save it\n		$this->set_preference($id, $permlink, \'gbp_serialized\');\n\n		$this->parent->message = messenger(\'\', $settings[\'pl_name\'], \'saved\');\n	}\n\n	function phpArrayToJsArray ($name, $array) {\n		// From PHP.net\n		if (is_array($array)) {\n			$result = $name.\' = new Array();\'.n;\n			foreach ($array as $key => $value)\n				$result .= $this->phpArrayToJsArray($name.\'[\\\'\'.$key.\'\\\']\',$value,\'\').n;\n		} else {\n			$result = $name.\' = \\\'\'.$array.\'\\\';\';\n		}\n		return $result;\n	}\n}\n\nclass PermanentLinksListTabView extends GBPAdminTabView\n{\n	function preload () {\n		register_callback(array(&$this, $this->parent->event.\'_multi_edit\'), $this->parent->event, $this->parent->event.\'_multi_edit\', 1);\n		register_callback(array(&$this, $this->parent->event.\'_change_pageby\'), $this->parent->event, $this->parent->event.\'_change_pageby\', 1);\n	}\n\n	function main () {\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$event = $this->parent->event;\n\n		$permlinks = $this->parent->get_all_permlinks();\n		$total = count($permlinks);\n\n		if ($total < 1) {\n			echo graf(\'You haven\\\'t created any custom permanent links rules yet.\', \' style=\"text-align: center;\"\').\n				 graf(\'<a href=\"\'.$this->url(array(gbp_tab => \'build\'), true).\'\">Click here</a> to add one.\', \' style=\"text-align: center;\"\');\n			return;\n		}\n\n		$limit = max($this->pref(\'list_pageby\'), 15);\n\n		list($page, $offset, $numPages) = $this->pager($total, $limit, $page);\n\n		if (empty($sort))\n			$sort = \'pl_precedence\';\n\n		if (empty($dir))\n			$dir = \'desc\';\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		// Sort the permlinks via the selected column and then their names.\n		foreach ($permlinks as $id => $permlink) {\n			$sort_keys[$id] = $permlink[\'settings\'][$sort];\n			$name[$id] = $permlink[\'settings\'][\'pl_name\'];\n		}\n		array_multisort($sort_keys, (($dir == \'desc\') ? SORT_DESC : SORT_ASC), $name, SORT_ASC, $permlinks);\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$permlinks = array_slice($permlinks, $offset, $limit);\n\n		if (count($permlinks)) {\n			echo n.n.\'<form name=\"longform\" method=\"post\" action=\"index.php\" onsubmit=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\')\">\'.\n\n				n.startTable(\'list\').\n				n.tr(\n					n.column_head(\'name\', \'pl_name\', $event, true, $switch_dir, $crit, $search_method).\n					hCell().\n					column_head(\'preview\', \'pl_preview\', $event, true, $switch_dir, $crit, $search_method).\n					column_head(\'precedence\', \'pl_precedence\', $event, true, $switch_dir, $crit, $search_method).\n					hCell()\n				);\n\n			include_once txpath.\'/publish/taghandlers.php\';\n\n			foreach ($permlinks as $id => $permlink) {\n				extract($permlink[\'settings\']);\n\n				$manage = n.\'<ul\'.(version_compare($GLOBALS[\'thisversion\'], \'4.0.3\', \'<=\') ? \' style=\"margin:0;padding:0;list-style-type:none;\">\' : \'>\').\n						n.t.\'<li>\'.href(gTxt(\'edit\'), $this->url(array(gbp_tab => \'build\', gbp_id => $id), true)).\'</li>\'.\n						n.\'</ul>\';\n\n				echo n.n.tr(\n\n					td(\n						href($pl_name, $this->url(array(gbp_tab => \'build\', gbp_id => $id), true))\n					, 75).\n\n					td($manage, 35).\n\n					td($pl_preview, 175).\n					td($pl_precedence.\'&nbsp;\', 50).\n\n					td(\n						fInput(\'checkbox\', \'selected[]\', $id)\n					)\n				);\n			}\n\n			echo n.n.tr(\n				tda(\n					select_buttons().\n					$this->permlinks_multiedit_form($page, $sort, $dir, $crit, $search_method)\n				,\' colspan=\"4\" style=\"text-align: right; border: none;\"\')\n			).\n\n			n.endTable().\n			n.\'</form>\'.\n\n			n.$this->nav_form($event, $page, $numPages, $sort, $dir, $crit, $search_method).\n\n			n.pageby_form($event, $this->pref(\'list_pageby\'));\n		}\n	}\n\n	function pager ($total, $limit, $page) {\n		if (function_exists(\'pager\'))\n			return pager($total, $limit, $page);\n\n		// This is taken from txplib_misc.php r1588 it is required for 4.0.3 compatibitly\n		$num_pages = ceil($total / $limit);\n		$page = $page ? (int) $page : 1;\n		$page = min(max($page, 1), $num_pages);\n		$offset = max(($page - 1) * $limit, 0);\n		return array($page, $offset, $num_pages);\n	}\n\n	function nav_form ($event, $page, $numPages, $sort, $dir, $crit, $method) {\n		if (function_exists(\'nav_form\'))\n			return nav_form($event, $page, $numPages, $sort, $dir, $crit, $method);\n\n		// This is basically stolen from the 4.0.3 version of includes/txp_list.php\n		// - list_nav_form() for 4.0.3 compatibitly\n		$nav[] = ($page > 1)\n			? PrevNextLink($event, $page-1, gTxt(\'prev\'), \'prev\', $sort, $dir) : \'\';\n		$nav[] = sp.small($page. \'/\'.$numPages).sp;\n		$nav[] = ($page != $numPages)\n			? PrevNextLink($event, $page+1, gTxt(\'next\'), \'next\', $sort, $dir) : \'\';\n		return ($nav)\n			? graf(join(\'\', $nav), \' align=\"center\"\') : \'\';\n	}\n\n	function permlinks_multiedit_form ($page, $sort, $dir, $crit, $search_method) {\n		$methods = array(\n			\'delete\' => gTxt(\'delete\'),\n		);\n\n		return event_multiedit_form($this->parent->event, $methods, $page, $sort, $dir, $crit, $search_method);\n	}\n\n	function permlinks_change_pageby () {\n		$this->set_preference(\'list_pageby\', gps(\'qty\'));\n	}\n\n	function permlinks_multi_edit () {\n		$method = gps(\'edit_method\')\n			? gps(\'edit_method\') // From Txp 4.0.4 and greater\n			: gps(\'method\'); // Up to Txp 4.0.3\n\n		switch ($method) {\n			case \'delete\':\n				foreach (gps(\'selected\') as $id) {\n							$deleted[] = $this->parent->remove_permlink($id);\n				}\n			break;\n		}\n\n		$this->parent->message = (isset($deleted) && is_array($deleted) && count($deleted))\n			? messenger(\'\', join(\', \', $deleted) ,\'deleted\')\n			: messenger(\'an error occurred\', \'\', \'\');\n	}\n}\n\nglobal $gbp_pl;\n$gbp_pl = new PermanentLinks(\'Permanent Links\', \'permlinks\', \'admin\');\nif (@txpinterface == \'public\') {\n	register_callback(array(&$gbp_pl, \'_feed_entry\'), \'rss_entry\');\n	register_callback(array(&$gbp_pl, \'_feed_entry\'), \'atom_entry\');\n	register_callback(array(&$gbp_pl, \'_textpattern\'), \'textpattern\');\n	register_callback(array(&$gbp_pl, \'_textpattern_end\'), \'textpattern_end\');\n\n	function gbp_if_regex ($atts, $thing) {\n		global $pretext;\n		extract(lAtts(array(\n			\'name\' => \'\',\n			\'val\'  => \'\',\n		),$atts));\n		$match = (@$pretext[\"permlink_regex_{$name}\"] == $val);\n		return parse(EvalElse($thing, $match));\n	}\n\n	function gbp_if_text ($atts, $thing) {\n		global $pretext;\n		extract(lAtts(array(\n			\'name\' => \'\',\n			\'val\'  => \'\',\n		),$atts));\n\n		$match = false;\n		if (!empty($name)) {\n			if (empty($val))\n				$match = (isset($pretext[\"permlink_text_{$name}\"]));\n			else\n				$match = (@$pretext[\"permlink_text_{$name}\"] == $val);\n		}\n		return parse(EvalElse($thing, $match));\n	}\n\n	function gbp_use_pagelink ($atts, $thing = \'\') {\n		global $gbp_pl;\n		extract(lAtts(array(\n			\'rule\' => \'\',\n		),$atts));\n		return $gbp_pl->_textpattern_end_callback(parse($thing), $rule);\n	}\n\n	function gbp_disable_permlinks ($atts, $thing = \'\') {\n		global $gbp_pl;\n		return $gbp_pl->toggle_permlink_mode(\'parse\', $thing);\n	}\n}', '0105461ff0893f89bacbd4e89e0fb222', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_tree', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.1.4', 'All sort of trees', '<h4>Description</h4>\n\n<p>This plugin will enable a drag\'n\'drop interface for easy tree creation. You can use it to create and output all kind of menus, lists and other hierarchical structures.</p>\n\n<h4>Usage</h4>\n\n<p>Go to <code>Extensions/etc_tree</code> and add some items to the tree. The items can be of arbitrary nature: links, images, txp tags... To add a new tree, check <code>List</code> case. Use <code>&equiv;</code> handle to drag and drop the items. Hover it to see (after save) the items <code>id</code>. Click the handle to fold/unfold the tree. Double-click it to put the node in the trash. You can (with a decent browser) reedit items directly on the tree. Double-click the text to turn the items raw HTML mode on/off.</p>\n\n<p>On the public side, call <code>&lt;txp:etc_tree id=\"some_id\" /&gt;</code> to display a menu. Can be used as container too, see below.</p>\n\n<h4>Attributes</h4>\n\n<h5>etc_tree</h5>\n\n<ul>\n<li><strong>id</strong>: tree item id that will be used as root.</li>\n<li><strong>self</strong>: whether the root itself must be included, or only its children (default).</li>\n<li><strong>include</strong>: a comma-separated list of tree <code>id</code>s or <code>.</code> (current node). If set, only the paths to <strong>include</strong> will be output. If an item is followed by <code>+</code>, its children will be included as well.</li>\n<li><strong>exclude</strong>: ids of item that must be excluded, together with their children.</li>\n<li><strong>depth</strong>: maximum tree depth.</li>\n<li><strong>circular</strong>: see below.</li>\n<li><strong>html_id, class, wraptag, break, breakclass</strong>: as usual. Note that every subtree will be wrapped in <strong>wraptag</strong> with <strong>class</strong> attribute. The <strong>breakclass</strong> attribute can be comma-separated <code>inactive, active</code> list.</li>\n</ul>\n\n<h5>etc_tree_title</h5>\n\n<p>Will output the current tree title in the container mode.</p>\n\n<h4>Advanced</h4>\n\n<p>Since txp tags can be used as tree nodes, recursive structures are possible. For example, if we create a node <code>Hi &lt;txp:etc_tree id=\"1\" /&gt;</code> with <code>id=1</code>, and call <code>&lt;txp:etc_tree id=\"1\" /&gt;</code>, it will enter an infinite loop. To prevent it, <code>&lt;etc_tree /&gt;</code> accepts <strong>circular</strong> attribute (0 by default), that indicates how many times the node can be reused in recursive calls. So a node <code>Hi &lt;txp:etc_tree id=\"1\" circular=\"3\" /&gt;</code> would output \"Hi\" four (1+3) times.</p>\n\n<p>A special case is <code>&lt;txp:etc_tree /&gt;</code> without attributes inside another <code>etc_tree</code> used as container: it will be replaced by the container itself. So, if we call</p>\n<pre><code>&lt;txp:etc_tree circular=\"3\" wraptag=\"\" break=\"\"&gt;\n	&lt;txp:category_list wraptag=\"ul\" break=\"li\" parent=\'&lt;txp:category /&gt;\' exclude=\'&lt;txp:category /&gt;\' children=\"0\"&gt;\n		&lt;txp:category title=\"1\" /&gt;\n		&lt;txp:etc_tree /&gt;\n	&lt;/txp:category_list&gt;\n&lt;/txp:etc_tree&gt;\n</code></pre>\n\n<p>it will output three levels of the category tree.</p>', 'if(@txpinterface == \'admin\') {\n	add_privs(\'etc_tree\', \'1,2\');\n	register_tab(\"extensions\", \"etc_tree\", \"etc_tree\");\n	register_callback(\"etc_tree_menu\", \"etc_tree\");\n	add_privs(\'prefs.etc_tree\',\'1,2\');\n//	register_callback(\'etc_tree_menu\', \'plugin_prefs.etc_tree\');\n	register_callback(\'etc_tree_install\', \'plugin_lifecycle.etc_tree\');\n\n\nfunction etc_tree_install($event=\'\', $step=\'\')\n{\n	global $prefs;\n	if($step == \'deleted\') {\n		safe_delete(\'txp_prefs\', \"name LIKE \'etc\\_tree\\_%\'\");\n		safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'etc_tree\'));\n	}\n	elseif($step == \'enabled\') {\n		if(!get_pref(\'etc_tree_css\')) set_pref(\'etc_tree_css\', $pref[\'etc_tree_css\'] = \'#etc-tree-form *[contenteditable]{display:inline-block}\n#etc-tree-form select[disabled],.etc-close>ul{display:none}\n.etc-close:after{content:\"\\022EE\";float:right}\n.etc-handle{cursor:pointer;padding-right:1em;}\n.etc-sortable{padding:1em;margin-bottom:0.5em;background:#fff;border:1pt solid #ccc;list-style:none}\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n		if(!get_pref(\'etc_tree_js\')) set_pref(\'etc_tree_js\', $pref[\'etc_tree_js\'] = \'$(\"#etc-tree-form\").on(\"dblclick\", \"*[data-escape]\", function(){\n		var $this = $(this), $escape = $this.attr(\"data-escape\");\n		if($escape == \"0\") $this.attr(\"data-escape\", \"1\").text($this.html());\n		else if($this.text().indexOf(\"<txp:\") < 0) $this.html($this.attr(\"data-escape\", \"0\").text());\n	}).on(\"click\", \".etc-handle\", function(){\n		var $thisli = $(this).parent(\"li\");\n		if($thisli.children(\"ul\").length) $thisli.toggleClass(\"etc-close\");\n	}).on(\"dblclick\", \".etc-handle\", function(){\n		$(this).parent(\"li\").appendTo($(\"#etc-link-trash\"));\n	});\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n		if(!get_pref(\'etc_tree_link\')) set_pref(\'etc_tree_link\', $pref[\'etc_tree_link\'] = \'<a href=\"{href}\" rel=\"bookmark\" data-id=\"{id}\" data-type=\"{type}\">{text}</a>\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n\n		$qc = \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'etc_tree\');\n		$qc .= <<<EOF\n		(\n		`id` int(6) NOT NULL AUTO_INCREMENT,\n		`parent` int(6) NOT NULL,\n		`menu` tinyint(1) NOT NULL,\n		`escape` tinyint(1) NOT NULL,\n		`sort` int(6) NOT NULL,\n		`lft` int(11) NOT NULL DEFAULT 0,\n		`rgt` int(11) NOT NULL DEFAULT 0,\n		`type` varchar(64) NOT NULL,\n		`title` text NOT NULL,\n		PRIMARY KEY (`id`)\n		) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;\nEOF;\n		safe_query($qc);\n	}\n\n	return;\n}\n\nfunction etc_tree_save($menu, $parent = 0, $delete = false) {\n	if(empty($menu[\'children\'])) return;\n	foreach($menu[\'children\'] as $key => $child) {\n		$id = $newid = intval($child[\'id\']);\n		if(!$delete) $newid = safe_upsert(\'etc_tree\',\n			\"parent=$parent,\n			type=\'\".doSlash($child[\'type\']).\"\',\n			title=\'\".doSlash($child[\'value\']).\"\',\n			menu=\'\".(isset($child[\'menu\']) ? \'1\' : \'0\').\"\',\n			escape=\'\".intval($child[\'escape\']).\"\',\n			sort=\'\".intval($key).\"\'\",\n			\"id=$id\");\n		elseif($id) safe_delete(\'etc_tree\', \"id=$id\");\n\n		if(isset($child[\'menu\'])) etc_tree_save($child[\'menu\'], $newid === true ? $id : $newid, $delete);\n	}\n}\n\nfunction etc_tree_menu($event, $step) {//dmp($_POST);\n	register_callback(\'etc_tree_css\', \'admin_side\', \'head_end\');\n	register_callback(\'etc_tree_js\', \'admin_side\', \'footer\');\n\n	if($step === \'save\' && bouncer($step, array(\'save\'=>true))) {\n		if(isset($_POST[\'etc_save\'])) etc_tree_save(ps(\'etc_save\'));\n		if(isset($_POST[\'etc_delete\'])) etc_tree_save(ps(\'etc_delete\'), 0, true);\n		etc_rebuild_tree();\n//		safe_delete(\'etc_tree\', \"lft = 0 and rgt = 0\");\n	}\n\n	pagetop(\"etc_tree\", \"<strong>etc_tree</strong> preferences\");\n	$out = \'<form id=\"etc-tree-form\" method=\"post\" action=\"?event=etc_tree\" class=\"txp-edit\" onsubmit=\"return $.fn.etc_link_submit()\">\'.n;\n	$out .= \'<h2>Edit tree (<a href=\"index.php?event=prefs#prefs_group_etc_tree\">options</a>)</h2>\'.n;\n\n	$out .= etc_tree(array(\'class\'=>\"etc-sortable\", \'html_id\'=>\"etc-link-list\", \'id\'=>0)).n;\n	$out .= \'<h3>New</h3>\'.n;\n\n	$out .= etc_type_select().n;\n	$out .= \'<h3>Trash</h3>\'.n.\'<ul class=\"etc-sortable\" id=\"etc-link-trash\"></ul>\'.n;\n	$out .= \'<p>\'.sInput(\'save\').tInput().fInput(\'submit\', \'save\', \'Save\', \'publish\').\'</p>\'.n.\'</form>\';\n\n	echo $out;\n}\n\n// -------------------------------------------------------------\n	function etc_rebuild_tree($parent=0, $left=0, $tbl=\'etc_tree\')\n	{\n		$left  = intval($left);\n		$right = $left+1;\n\n		$parent = intval($parent);\n\n		$result = safe_column_num(\"id\", $tbl,\n			\"parent=$parent order by sort\");\n\n		foreach($result as $row) {\n			$right = etc_rebuild_tree($row, $right, $tbl);\n		}\n\n		safe_update(\n			$tbl,\n			\"lft=$left, rgt=$right\",\n			\"id=$parent\"\n		);\n		return $right+1;\n	}\n\nfunction etc_type_select() {\n\n	include_once txpath.\'/publish/taghandlers.php\';\n	include_once txpath.\'/lib/txplib_publish.php\';\n\n	if($links = get_pref(\'etc_tree_link\')) $links = do_list($links, \"\\r\\n\\r\\n\");\n	else $links = array(\'<a href=\"{href}\" rel=\"bookmark\" data-id=\"{id}\" data-type=\"{type}\">{text}</a>\');\n\n	$out = \'<p><select id=\"etc-tree-format\" style=\"width:100%\">\'.n;\n	foreach($links as $link) if($link) {\n		list($val, $title) = explode(\':==:\', $link) + array($link, $link);\n		$val = str_replace(\"\'\", \"&#39;\", $val);\n		$out .= \"<option value=\'$val\' title=\'$val\'>\".htmlspecialchars($title).\"</option>\".n;\n	}\n	$out .= \'</select></p>\'.n;\n	$out .= \'<textarea id=\"etc-tree-value\"></textarea>\'.n;\n	$out .= \'<p class=\"edit-link-url\">\';\n	$out .= \'<select id=\"etc-tree-type\">\'.n;\n	foreach(array(\'misc\', \'article\', \'category\', \'section\', \'link\', \'images\', \'file\', \'author\') as $type) $out .= \"<option value=\'$type\'>\".gTxt($type).\"</option>\".n;\n	$out .= \'</select>\'.n;\n	$out .= \'<span class=\"txp-label\"><input type=\"checkbox\" id=\"etc-link-menu\" /><label for=\"etc-link-menu\">\'.gTxt(\'list\').\'</label></span>\';\n	$out .= \'<span class=\"txp-label\"><input type=\"checkbox\" id=\"etc-link-code\" /><label for=\"etc-link-code\">\'.gTxt(\'txprawxhtml\').\'</label></span>\';\n	$out .= \'<span class=\"txp-value\"><input style=\"margin-left:1em\" type=\"reset\" value=\"\'.gTxt(\'add\').\'\" onclick=\"return $.fn.etc_link_update()\"></span></p>\'.n;\n	$out .= \'<div id=\"etc-link-pane\">\'.n;\n\n	$out .= \'<p class=\"edit-link-url\">\'.n;\n\n	if($rows = safe_rows(\'ID, Title, Section, url_title, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as lastmod, unix_timestamp(Expires) as expires\', \'textpattern\', \'Status>=4 ORDER BY Title\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-article\" disabled=\"disabled\">\'.n;\n		foreach($rows as $row) $out .= \'<option value=\"\'.permlinkurl($row).\'\" data-id=\"\'.$row[\'ID\'].\'\">\'.escape_title($row[\'Title\']).\'</option>\'.n;\n		$out .= \'</select></span>\'.n;\n	}\n\n	if($rows = safe_rows(\'*\', \'txp_category\', \'name!=\"root\" ORDER BY type, title\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-category\" disabled=\"disabled\">\'.n;\n		$type = \'article\';\n		foreach($rows as $row) {\n			if($type !== $row[\'type\']) {$type = $row[\'type\']; $out .= \'<optgroup style=\"text-align:center\" label=\"\'.gTxt($row[\'type\']).\'\"></optgroup>\'.n;}\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'c\'=>$row[\'name\'], \'context\'=>$row[\'type\'])).\'\" data-id=\"\'.$row[\'type\'].\'-\'.$row[\'name\'].\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\n		}\n		$out .= \'</select></span>\'.n;\n	}\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-section\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:section_list break=\"\"><option value=\"<txp:section url=\"1\" />\" data-id=\"<txp:section />\"><txp:section title=\"1\" /></option></txp:section_list>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-link\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:linklist break=\"\"><option value=\"<txp:link_url />\" data-id=\"<txp:link_id />\"><txp:link_name /></option></txp:linklist>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-images\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:images break=\"\"><option value=\"<txp:image_url />\" data-id=\"<txp:image_info type=\"id\" />\"><txp:image_info type=\"name\" /></option></txp:images>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-file\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:file_download_list break=\"\"><option value=\"<txp:file_download_link />\" data-id=\"<txp:file_download_id />\"><txp:file_download_name /></option></txp:file_download_list>\').n\n		.\'</select></span>\';\n\n	if($rows = safe_rows(\'name, RealName\', \'txp_users\', \'1 ORDER BY RealName\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-author\" disabled=\"disabled\">\'.n;\n		foreach($rows as $row) {\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'author\'=>$row[\'RealName\'])).\'\" data-id=\"\'.$row[\'name\'].\'\">\'.htmlspecialchars($row[\'RealName\']).\'</option>\'.n;\n		}\n		$out .= \'</select></span>\'.n;\n	}\n	$out .= \'</p></div>\'.n;\n	return $out;\n}\n\nfunction etc_tree_css($event, $step) {\n	echo \'<style>\'.n.get_pref(\'etc_tree_css\').n.\'</style>\'.n;\n}\n\nfunction etc_tree_js($event, $step, $pre, $rs=\'\') {\n	global $prefs;\n	$out = \'\';\n	if($prefs[\'version\'] < \'4.6\') $out .= \'<script src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js\"></script>\'.n;\n\n	$out .= script_js(\'$(function () {\n\n	var etcTreeCurrent = {link:$(\"#etc-tree-format\").val(), type:$(\"#etc-tree-type\").val()};\n\n	$(\"#etc-tree-format\").change(function() {\n		etcTreeCurrent.link = $(this).val();\n		$.fn.etc_link_refresh();\n	});\n\n	$(\"#etc-link-pane select\").change(function() {\n		var $selected = $(this).children(\"option:selected\").first();\n		$.extend(etcTreeCurrent, {id:$selected.attr(\"data-id\"), text:$selected.text(), href:$(this).val()});\n/*		etcTreeCurrent.id = $selected.attr(\"data-id\");\n		etcTreeCurrent.text = $selected.text();\n		etcTreeCurrent.href = $(this).val();*/\n		$.fn.etc_link_refresh();\n	});\n\n	$(\"#etc-tree-type\").change(function() {\n		etcTreeCurrent.id = null;\n		etcTreeCurrent.type = $(this).val();\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\n		var $select = $(\"#etc-link-\"+etcTreeCurrent.type);\n		if($select.val()) $select.attr(\"disabled\", null).change();\n		else $(\"#etc-tree-value\").val(\"\");\n	});\n\n	$.fn.etc_link_refresh = function()\n	{\n		if(etcTreeCurrent.id) $(\"#etc-tree-value\").val(etcTreeCurrent.link.replace(\"{href}\", etcTreeCurrent.href).replace(\"{id}\", etcTreeCurrent.id).replace(\"{type}\", etcTreeCurrent.type).replace(\"{text}\", etcTreeCurrent.text));\n		else $(\"#etc-tree-value\").val(\"\");\n//		$(\"#etc-link-current\").html($text);\n	}\n\n	$.fn.etc_link_post = function($this)\n	{\n		var $post = {menu:1};\n		$post.children = [];\n		$this.children(\"li\").each(function() {\n			var $thisli = $(this);\n			var $child = {};\n			$thisli.children(\"ul.etc-sortable\").each(function() {\n				$child.menu = $.fn.etc_link_post($(this));\n			});\n			$child.id = $thisli.attr(\"data-id\");\n			$child.type = $thisli.attr(\"data-type\");\n			$child.escape = $thisli.children(\"*[contenteditable]\").first().attr(\"data-escape\");\n			$child.value = $child[\"escape\"] == \"0\" ? $thisli.children(\"*[contenteditable]\").html() : $thisli.children(\"*[contenteditable]\").text();\n			$post.children.push($child);\n		});\n\n		return $post;\n	}\n\n	$.fn.etc_link_submit = function()\n	{\n		var $formdata = $(\"#etc-tree-form\").serializeArray();\n		var $params = {\"etc_save\":$.fn.etc_link_post($(\"#etc-link-list\")), \"etc_delete\":$.fn.etc_link_post($(\"#etc-link-trash\"))};\n		$.each($formdata, function(i, field){var $data = $.parseJSON(\"{\\\"\"+field.name+\"\\\":\\\"\"+field.value+\"\\\"}\"); $.extend($params, $data);});\n\n		$( \"#etc-link-list\" ).load( \"index.php?event=etc_tree #etc-link-list>*\", $params, function() {$.fn.etc_link_reset();});\n		return false;\n	}\n\n	$.fn.etc_link_update = function(item)\n	{\n		var $type = etcTreeCurrent.type+(etcTreeCurrent.id ? \"-\"+etcTreeCurrent.id : \"\"), $value = $(\"#etc-tree-value\").val();\n		if($value == \"\" && !confirm(\"\'.gTxt(\'link_empty\').\' \'.gTxt(\'continue\').\'?\")) return false;\n		var escape = $(\"#etc-link-code\").is(\":checked\") || $value.indexOf(\"<txp:\") >= 0;\n		var $span = $(\"<div contenteditable=\\\"true\\\"></div>\");\n		if(escape) $span.attr(\"data-escape\", \"1\").text($value); else $span.attr(\"data-escape\", \"0\").html($value);\n		var $item = $(\"<li data-id=\\\"0\\\" data-type=\\\"\"+$type+\"\\\"><span class=\\\"etc-handle\\\">&equiv;</span></li>\").append($span);\n		if($(\"#etc-link-menu\").is(\":checked\")) $item.append(\"<ul class=\\\"etc-sortable\\\" />\");\n		$(\"#etc-link-list\").append($item);\n		return false;\n	}\n\n	$.fn.etc_link_reset = function()\n	{\n		$(\"#etc-link-trash\").empty();\n		$(\".etc-sortable\").sortable({\n			helper: \"clone\",\n			connectWith: \".etc-sortable\",\n			cancel: \"[contenteditable]\",\n		/*	update: function( event, ui ) {\n				var $parent = ui.item.parent(\"ul\");\n			}*/\n		});\n		return true;\n	}\'.n.get_pref(\'etc_tree_js\').n.\'\n	$.fn.etc_link_reset();\n});\');\n	return $rs.n.$out;\n}\n\n\n}//admin\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'etc_tree\')\n		->register(\'etc_tree_title\')\n	;\n}\n\nfunction etc_tree_title($atts) {\n	global $etc_tree;\n	extract(lAtts(array(\n		\"type\"=>\'title\'\n	), $atts));\n	return(isset($etc_tree[$type])) ? ($type === \'title\' ? parse($etc_tree[$type]) : $etc_tree[$type]) : \'\';\n}\n\nfunction etc_tree($atts, $thing = \'\') {\n\n	static $trees = array(), $stack = array(), $pageid = null;\n	$admin = txpinterface == \'admin\' && gps(\'event\') == \'etc_tree\';\n	global $pretext, $etc_tree, $etc_tree_atts, $etc_tree_thing;\n	if(isset($etc_tree_atts)) $safe_atts= $etc_tree_atts;\n	if(isset($etc_tree_thing)) $safe_thing = $etc_tree_thing;\n	if(isset($etc_tree)) $safe_tree = $etc_tree;\n\n	if(!isset($atts[\'id\'])) {\n		if(!empty($etc_tree_atts)) $atts += $etc_tree_atts;\n		if(!$thing && !empty($etc_tree_thing)) $thing = $etc_tree_thing;//dmp($etc_tree_atts);dmp($etc_tree_thing);\n	}\n\n	$etc_tree_atts= $atts;\n	$etc_tree_thing = $thing;\n\n	$defaults = array(\n		\"debug\"=>\'\',\n		\"id\"=>null,\n		\"self\"=>\"\",\n		\"depth\"=>null,\n		\"target\"=>null,//depreciated\n		\"include\"=>null,\n		\"exclude\"=>null,\n		\"wraptag\"=>\'ul\',\n		\"break\"=>\'li\',\n		\"separator\"=>\',\',\n		\"class\"=>\'\',\n		\"breakclass\"=>\'\',\n		\"html_id\"=>\'\',\n		\"circular\"=>0,//get_pref(\'etc_tree_depth\')\n	);\n	extract(lAtts($defaults , $atts));\n\n	$treeid = isset($id) ? intval($id) : \'.\';\n\n	if(!isset($stack[$treeid])) $stack[$treeid] = 0;\n\n	if(!isset($trees[$treeid])) {\n		if($debug) dmp(\'Loading \'.$treeid);\n		if($treeid === \'.\') $trees[\'.\'] = array(array(\'id\'=>\'.\', \'menu\'=>0, \'escape\'=>1, \'lft\'=>0, \'rgt\'=>1, \'title\'=>\'\', \'type\'=>\'\'));\n		elseif ($treeid && !extract($rs = safe_row(\"lft as l, rgt as r\", \'etc_tree\', \"id=$treeid\"))) $trees[$treeid] = array();\n		else $trees[$treeid] = safe_rows(\'*\', \'etc_tree\', ($treeid ? \"lft >= $l and lft <= $r\" : \"1\").\" order by lft asc\");\n	}\n	$tree = $trees[$treeid];\n\n	$out = \'\';\n	$first = $treeid && $treeid !== \'.\' && !$self ? 1 : 0;\n	$last = count($tree) - 1;\n\n	if(!isset($pageid)) {\n		$pageid = array();\n		if(!empty($pretext[\'id\'])) $pageid = array(\'article-\'.$pretext[\'id\']);\n		else {\n			if(!empty($pretext[\'author\'])) $pageid[] = \'author-\'.$pretext[\'author\'];\n			if(!empty($pretext[\'c\'])) $pageid[] = \'category-\'.$pretext[\'context\'].\'-\'.$pretext[\'c\'];\n			if(!empty($pretext[\'s\']) && $pretext[\'s\'] !== \'default\') $pageid[] = \'section-\'.$pretext[\'s\'];\n		}\n	}\n\n	if($admin || !empty($tree) && $stack[$treeid] <= intval($circular) && $last >= $first) {\n\n		$stack[$treeid] += 1;\n		if(isset($target)) $include = $target;\n		$exclude = $exclude ? do_list($exclude) : array();\n		$include = $include ? do_list($include) : array();\n		$targets = array();\n		foreach($include as $target) {\n			$end = $last;\n			list($target, $children) = explode(\'+\', $target, 2) + array(0, null);\n			if(!in_array($target, $targets)) while($target) {\n				for($i = $end; $i >= $first; $i--) if($tree[$i][\'id\'] == $target || $target === \'.\' && in_array($tree[$i][\'type\'], $pageid)) {\n					$targets[] = $tree[$i][\'id\'];\n					if(isset($children)) {\n						unset($children);\n						for($j = $i+1; $j <= $last && $tree[$j][\'rgt\'] < $tree[$i][\'rgt\']; $j++) $targets[] = $tree[$j][\'id\'];\n					}\n					$target = $tree[$i][\'parent\'];\n					$end = $i - 1;\n					break;\n				}\n				if($i < $first) break;\n			}\n		}\n		if($include) {$targets = array_unique(array_diff($targets, $exclude)); $exclude = array();}\n\n		$class = $class ? \' class=\"\'.htmlspecialchars($class).\'\"\' : \'\';\n		$html_id = $html_id ? \' id=\"\'.htmlspecialchars($html_id).\'\"\' : \'\';\n		$wrw = $wraptag && preg_match(\'/^\\w+$/\', $wraptag);\n		if(!$wrw) list($wrap, $endwrap) = explode($separator, $wraptag, 2) + array($wraptag, $wraptag);\n		else {$wrap = \"<$wraptag$class>\"; $endwrap = \"</$wraptag>\";}\n		list($breakclass, $currentclass) = explode($separator, $breakclass, 2) + array($breakclass, $breakclass);\n		$br = $break == \'br\' || $break == \'hr\';\n		$brw = !$br && preg_match(\'/^\\w+$/\', $break);\n		$cbreak = $brw ? \"<$break\".($currentclass ? \' class=\"\'.htmlspecialchars($currentclass).\'\">\' : \'>\') : \'\';\n		$endbreak = $br ? \"<$break />\" : ($brw ? \"</$break>\" : $break);\n		$break = $brw ? \"<$break\".($breakclass ? \' class=\"\'.htmlspecialchars($breakclass).\'\">\' : \'>\') : \'\';\n\n		$right = array();\n		$depth = isset($depth) ? intval($depth) : $last;\n		$clevel = 0;\n\n		if(!$include || $targets) for($i = $first; $i <= $last; $i++) {\n			extract($tree[$i]);\n\n			if(isset($until) && $rgt <= $until) continue;\n			if($exclude && in_array($id, $exclude) || $include && !in_array($id, $targets)) {$until = $rgt; continue;}\n			else unset($until);\n\n			while ($right && $right[count($right)-1] < $rgt) {\n				array_pop($right);\n				$out .= $endwrap.($brw ? $endbreak : \'\');\n				$clevel--;\n			}\n			if($clevel > $depth) continue;\n			$etc_tree = $tree[$i];\n			if($admin) {\n				$escape |= strpos($title, \'<txp:\') !== false;\n				$out .= \"<li data-type=\'$type\' data-id=\'$id\'\".($menu && !$clevel ? \" class=\'etc-close\'>\" : \">\").\"<span class=\'etc-handle\' title=\'id=$id\'>&equiv;</span><div data-escape=\'$escape\' contenteditable=\'true\'>\".($escape ? htmlspecialchars($title) : $title).\"</div>\".n;\n			}\n			elseif(trim($chunck = parse($thing ? $thing : $title)) === \'\') continue;\n			else $out .= (in_array($type, $pageid) ? $cbreak : $break).$chunck;\n			if($menu) {\n				$out .= $wrap;\n				$right[] = $rgt; $clevel++;\n			}\n			elseif($brw || $i < $last && !($right && $right[count($right)-1] < $tree[$i+1][\'rgt\'])) $out .= $endbreak;\n		}\n		$out .= str_repeat($endwrap.($brw ? $endbreak : \'\'), $clevel);\n\n		$stack[$treeid] -= 1;\n	}\n\n	$etc_tree = isset($safe_tree) ? $safe_tree : null;\n	$etc_tree_atts = isset($safe_atts) ? $safe_atts: null;\n	$etc_tree_thing = isset($safe_thing) ? $safe_thing : null;\n\n	return trim($out) === \'\' && !$admin ? \'\' : ($wrw ? \"<$wraptag$class$html_id>\" : $wrap).$out.$endwrap;\n}', 'if(@txpinterface == \'admin\') {\n	add_privs(\'etc_tree\', \'1,2\');\n	register_tab(\"extensions\", \"etc_tree\", \"etc_tree\");\n	register_callback(\"etc_tree_menu\", \"etc_tree\");\n	add_privs(\'prefs.etc_tree\',\'1,2\');\n//	register_callback(\'etc_tree_menu\', \'plugin_prefs.etc_tree\');\n	register_callback(\'etc_tree_install\', \'plugin_lifecycle.etc_tree\');\n\n\nfunction etc_tree_install($event=\'\', $step=\'\')\n{\n	global $prefs;\n	if($step == \'deleted\') {\n		safe_delete(\'txp_prefs\', \"name LIKE \'etc\\_tree\\_%\'\");\n		safe_query(\'DROP TABLE IF EXISTS \'.safe_pfx(\'etc_tree\'));\n	}\n	elseif($step == \'enabled\') {\n		if(!get_pref(\'etc_tree_css\')) set_pref(\'etc_tree_css\', $pref[\'etc_tree_css\'] = \'#etc-tree-form *[contenteditable]{display:inline-block}\n#etc-tree-form select[disabled],.etc-close>ul{display:none}\n.etc-close:after{content:\"\\022EE\";float:right}\n.etc-handle{cursor:pointer;padding-right:1em;}\n.etc-sortable{padding:1em;margin-bottom:0.5em;background:#fff;border:1pt solid #ccc;list-style:none}\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n		if(!get_pref(\'etc_tree_js\')) set_pref(\'etc_tree_js\', $pref[\'etc_tree_js\'] = \'$(\"#etc-tree-form\").on(\"dblclick\", \"*[data-escape]\", function(){\n		var $this = $(this), $escape = $this.attr(\"data-escape\");\n		if($escape == \"0\") $this.attr(\"data-escape\", \"1\").text($this.html());\n		else if($this.text().indexOf(\"<txp:\") < 0) $this.html($this.attr(\"data-escape\", \"0\").text());\n	}).on(\"click\", \".etc-handle\", function(){\n		var $thisli = $(this).parent(\"li\");\n		if($thisli.children(\"ul\").length) $thisli.toggleClass(\"etc-close\");\n	}).on(\"dblclick\", \".etc-handle\", function(){\n		$(this).parent(\"li\").appendTo($(\"#etc-link-trash\"));\n	});\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n		if(!get_pref(\'etc_tree_link\')) set_pref(\'etc_tree_link\', $pref[\'etc_tree_link\'] = \'<a href=\"{href}\" rel=\"bookmark\" data-id=\"{id}\" data-type=\"{type}\">{text}</a>\', \'etc_tree\', PREF_PLUGIN, \'longtext_input\');\n\n		$qc = \'CREATE TABLE IF NOT EXISTS \'.safe_pfx(\'etc_tree\');\n		$qc .= <<<EOF\n		(\n		`id` int(6) NOT NULL AUTO_INCREMENT,\n		`parent` int(6) NOT NULL,\n		`menu` tinyint(1) NOT NULL,\n		`escape` tinyint(1) NOT NULL,\n		`sort` int(6) NOT NULL,\n		`lft` int(11) NOT NULL DEFAULT 0,\n		`rgt` int(11) NOT NULL DEFAULT 0,\n		`type` varchar(64) NOT NULL,\n		`title` text NOT NULL,\n		PRIMARY KEY (`id`)\n		) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;\nEOF;\n		safe_query($qc);\n	}\n\n	return;\n}\n\nfunction etc_tree_save($menu, $parent = 0, $delete = false) {\n	if(empty($menu[\'children\'])) return;\n	foreach($menu[\'children\'] as $key => $child) {\n		$id = $newid = intval($child[\'id\']);\n		if(!$delete) $newid = safe_upsert(\'etc_tree\',\n			\"parent=$parent,\n			type=\'\".doSlash($child[\'type\']).\"\',\n			title=\'\".doSlash($child[\'value\']).\"\',\n			menu=\'\".(isset($child[\'menu\']) ? \'1\' : \'0\').\"\',\n			escape=\'\".intval($child[\'escape\']).\"\',\n			sort=\'\".intval($key).\"\'\",\n			\"id=$id\");\n		elseif($id) safe_delete(\'etc_tree\', \"id=$id\");\n\n		if(isset($child[\'menu\'])) etc_tree_save($child[\'menu\'], $newid === true ? $id : $newid, $delete);\n	}\n}\n\nfunction etc_tree_menu($event, $step) {//dmp($_POST);\n	register_callback(\'etc_tree_css\', \'admin_side\', \'head_end\');\n	register_callback(\'etc_tree_js\', \'admin_side\', \'footer\');\n\n	if($step === \'save\' && bouncer($step, array(\'save\'=>true))) {\n		if(isset($_POST[\'etc_save\'])) etc_tree_save(ps(\'etc_save\'));\n		if(isset($_POST[\'etc_delete\'])) etc_tree_save(ps(\'etc_delete\'), 0, true);\n		etc_rebuild_tree();\n//		safe_delete(\'etc_tree\', \"lft = 0 and rgt = 0\");\n	}\n\n	pagetop(\"etc_tree\", \"<strong>etc_tree</strong> preferences\");\n	$out = \'<form id=\"etc-tree-form\" method=\"post\" action=\"?event=etc_tree\" class=\"txp-edit\" onsubmit=\"return $.fn.etc_link_submit()\">\'.n;\n	$out .= \'<h2>Edit tree (<a href=\"index.php?event=prefs#prefs_group_etc_tree\">options</a>)</h2>\'.n;\n\n	$out .= etc_tree(array(\'class\'=>\"etc-sortable\", \'html_id\'=>\"etc-link-list\", \'id\'=>0)).n;\n	$out .= \'<h3>New</h3>\'.n;\n\n	$out .= etc_type_select().n;\n	$out .= \'<h3>Trash</h3>\'.n.\'<ul class=\"etc-sortable\" id=\"etc-link-trash\"></ul>\'.n;\n	$out .= \'<p>\'.sInput(\'save\').tInput().fInput(\'submit\', \'save\', \'Save\', \'publish\').\'</p>\'.n.\'</form>\';\n\n	echo $out;\n}\n\n// -------------------------------------------------------------\n	function etc_rebuild_tree($parent=0, $left=0, $tbl=\'etc_tree\')\n	{\n		$left  = intval($left);\n		$right = $left+1;\n\n		$parent = intval($parent);\n\n		$result = safe_column_num(\"id\", $tbl,\n			\"parent=$parent order by sort\");\n\n		foreach($result as $row) {\n			$right = etc_rebuild_tree($row, $right, $tbl);\n		}\n\n		safe_update(\n			$tbl,\n			\"lft=$left, rgt=$right\",\n			\"id=$parent\"\n		);\n		return $right+1;\n	}\n\nfunction etc_type_select() {\n\n	include_once txpath.\'/publish/taghandlers.php\';\n	include_once txpath.\'/lib/txplib_publish.php\';\n\n	if($links = get_pref(\'etc_tree_link\')) $links = do_list($links, \"\\r\\n\\r\\n\");\n	else $links = array(\'<a href=\"{href}\" rel=\"bookmark\" data-id=\"{id}\" data-type=\"{type}\">{text}</a>\');\n\n	$out = \'<p><select id=\"etc-tree-format\" style=\"width:100%\">\'.n;\n	foreach($links as $link) if($link) {\n		list($val, $title) = explode(\':==:\', $link) + array($link, $link);\n		$val = str_replace(\"\'\", \"&#39;\", $val);\n		$out .= \"<option value=\'$val\' title=\'$val\'>\".htmlspecialchars($title).\"</option>\".n;\n	}\n	$out .= \'</select></p>\'.n;\n	$out .= \'<textarea id=\"etc-tree-value\"></textarea>\'.n;\n	$out .= \'<p class=\"edit-link-url\">\';\n	$out .= \'<select id=\"etc-tree-type\">\'.n;\n	foreach(array(\'misc\', \'article\', \'category\', \'section\', \'link\', \'images\', \'file\', \'author\') as $type) $out .= \"<option value=\'$type\'>\".gTxt($type).\"</option>\".n;\n	$out .= \'</select>\'.n;\n	$out .= \'<span class=\"txp-label\"><input type=\"checkbox\" id=\"etc-link-menu\" /><label for=\"etc-link-menu\">\'.gTxt(\'list\').\'</label></span>\';\n	$out .= \'<span class=\"txp-label\"><input type=\"checkbox\" id=\"etc-link-code\" /><label for=\"etc-link-code\">\'.gTxt(\'txprawxhtml\').\'</label></span>\';\n	$out .= \'<span class=\"txp-value\"><input style=\"margin-left:1em\" type=\"reset\" value=\"\'.gTxt(\'add\').\'\" onclick=\"return $.fn.etc_link_update()\"></span></p>\'.n;\n	$out .= \'<div id=\"etc-link-pane\">\'.n;\n\n	$out .= \'<p class=\"edit-link-url\">\'.n;\n\n	if($rows = safe_rows(\'ID, Title, Section, url_title, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as lastmod, unix_timestamp(Expires) as expires\', \'textpattern\', \'Status>=4 ORDER BY Title\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-article\" disabled=\"disabled\">\'.n;\n		foreach($rows as $row) $out .= \'<option value=\"\'.permlinkurl($row).\'\" data-id=\"\'.$row[\'ID\'].\'\">\'.escape_title($row[\'Title\']).\'</option>\'.n;\n		$out .= \'</select></span>\'.n;\n	}\n\n	if($rows = safe_rows(\'*\', \'txp_category\', \'name!=\"root\" ORDER BY type, title\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-category\" disabled=\"disabled\">\'.n;\n		$type = \'article\';\n		foreach($rows as $row) {\n			if($type !== $row[\'type\']) {$type = $row[\'type\']; $out .= \'<optgroup style=\"text-align:center\" label=\"\'.gTxt($row[\'type\']).\'\"></optgroup>\'.n;}\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'c\'=>$row[\'name\'], \'context\'=>$row[\'type\'])).\'\" data-id=\"\'.$row[\'type\'].\'-\'.$row[\'name\'].\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\n		}\n		$out .= \'</select></span>\'.n;\n	}\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-section\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:section_list break=\"\"><option value=\"<txp:section url=\"1\" />\" data-id=\"<txp:section />\"><txp:section title=\"1\" /></option></txp:section_list>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-link\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:linklist break=\"\"><option value=\"<txp:link_url />\" data-id=\"<txp:link_id />\"><txp:link_name /></option></txp:linklist>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-images\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:images break=\"\"><option value=\"<txp:image_url />\" data-id=\"<txp:image_info type=\"id\" />\"><txp:image_info type=\"name\" /></option></txp:images>\').n\n		.\'</select></span>\';\n\n	$out .= \'<span class=\"txp-value\"><select id=\"etc-link-file\" disabled=\"disabled\">\'.n\n		.parse(\'<txp:file_download_list break=\"\"><option value=\"<txp:file_download_link />\" data-id=\"<txp:file_download_id />\"><txp:file_download_name /></option></txp:file_download_list>\').n\n		.\'</select></span>\';\n\n	if($rows = safe_rows(\'name, RealName\', \'txp_users\', \'1 ORDER BY RealName\'))\n	{\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-author\" disabled=\"disabled\">\'.n;\n		foreach($rows as $row) {\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'author\'=>$row[\'RealName\'])).\'\" data-id=\"\'.$row[\'name\'].\'\">\'.htmlspecialchars($row[\'RealName\']).\'</option>\'.n;\n		}\n		$out .= \'</select></span>\'.n;\n	}\n	$out .= \'</p></div>\'.n;\n	return $out;\n}\n\nfunction etc_tree_css($event, $step) {\n	echo \'<style>\'.n.get_pref(\'etc_tree_css\').n.\'</style>\'.n;\n}\n\nfunction etc_tree_js($event, $step, $pre, $rs=\'\') {\n	global $prefs;\n	$out = \'\';\n	if($prefs[\'version\'] < \'4.6\') $out .= \'<script src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js\"></script>\'.n;\n\n	$out .= script_js(\'$(function () {\n\n	var etcTreeCurrent = {link:$(\"#etc-tree-format\").val(), type:$(\"#etc-tree-type\").val()};\n\n	$(\"#etc-tree-format\").change(function() {\n		etcTreeCurrent.link = $(this).val();\n		$.fn.etc_link_refresh();\n	});\n\n	$(\"#etc-link-pane select\").change(function() {\n		var $selected = $(this).children(\"option:selected\").first();\n		$.extend(etcTreeCurrent, {id:$selected.attr(\"data-id\"), text:$selected.text(), href:$(this).val()});\n/*		etcTreeCurrent.id = $selected.attr(\"data-id\");\n		etcTreeCurrent.text = $selected.text();\n		etcTreeCurrent.href = $(this).val();*/\n		$.fn.etc_link_refresh();\n	});\n\n	$(\"#etc-tree-type\").change(function() {\n		etcTreeCurrent.id = null;\n		etcTreeCurrent.type = $(this).val();\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\n		var $select = $(\"#etc-link-\"+etcTreeCurrent.type);\n		if($select.val()) $select.attr(\"disabled\", null).change();\n		else $(\"#etc-tree-value\").val(\"\");\n	});\n\n	$.fn.etc_link_refresh = function()\n	{\n		if(etcTreeCurrent.id) $(\"#etc-tree-value\").val(etcTreeCurrent.link.replace(\"{href}\", etcTreeCurrent.href).replace(\"{id}\", etcTreeCurrent.id).replace(\"{type}\", etcTreeCurrent.type).replace(\"{text}\", etcTreeCurrent.text));\n		else $(\"#etc-tree-value\").val(\"\");\n//		$(\"#etc-link-current\").html($text);\n	}\n\n	$.fn.etc_link_post = function($this)\n	{\n		var $post = {menu:1};\n		$post.children = [];\n		$this.children(\"li\").each(function() {\n			var $thisli = $(this);\n			var $child = {};\n			$thisli.children(\"ul.etc-sortable\").each(function() {\n				$child.menu = $.fn.etc_link_post($(this));\n			});\n			$child.id = $thisli.attr(\"data-id\");\n			$child.type = $thisli.attr(\"data-type\");\n			$child.escape = $thisli.children(\"*[contenteditable]\").first().attr(\"data-escape\");\n			$child.value = $child[\"escape\"] == \"0\" ? $thisli.children(\"*[contenteditable]\").html() : $thisli.children(\"*[contenteditable]\").text();\n			$post.children.push($child);\n		});\n\n		return $post;\n	}\n\n	$.fn.etc_link_submit = function()\n	{\n		var $formdata = $(\"#etc-tree-form\").serializeArray();\n		var $params = {\"etc_save\":$.fn.etc_link_post($(\"#etc-link-list\")), \"etc_delete\":$.fn.etc_link_post($(\"#etc-link-trash\"))};\n		$.each($formdata, function(i, field){var $data = $.parseJSON(\"{\\\"\"+field.name+\"\\\":\\\"\"+field.value+\"\\\"}\"); $.extend($params, $data);});\n\n		$( \"#etc-link-list\" ).load( \"index.php?event=etc_tree #etc-link-list>*\", $params, function() {$.fn.etc_link_reset();});\n		return false;\n	}\n\n	$.fn.etc_link_update = function(item)\n	{\n		var $type = etcTreeCurrent.type+(etcTreeCurrent.id ? \"-\"+etcTreeCurrent.id : \"\"), $value = $(\"#etc-tree-value\").val();\n		if($value == \"\" && !confirm(\"\'.gTxt(\'link_empty\').\' \'.gTxt(\'continue\').\'?\")) return false;\n		var escape = $(\"#etc-link-code\").is(\":checked\") || $value.indexOf(\"<txp:\") >= 0;\n		var $span = $(\"<div contenteditable=\\\"true\\\"></div>\");\n		if(escape) $span.attr(\"data-escape\", \"1\").text($value); else $span.attr(\"data-escape\", \"0\").html($value);\n		var $item = $(\"<li data-id=\\\"0\\\" data-type=\\\"\"+$type+\"\\\"><span class=\\\"etc-handle\\\">&equiv;</span></li>\").append($span);\n		if($(\"#etc-link-menu\").is(\":checked\")) $item.append(\"<ul class=\\\"etc-sortable\\\" />\");\n		$(\"#etc-link-list\").append($item);\n		return false;\n	}\n\n	$.fn.etc_link_reset = function()\n	{\n		$(\"#etc-link-trash\").empty();\n		$(\".etc-sortable\").sortable({\n			helper: \"clone\",\n			connectWith: \".etc-sortable\",\n			cancel: \"[contenteditable]\",\n		/*	update: function( event, ui ) {\n				var $parent = ui.item.parent(\"ul\");\n			}*/\n		});\n		return true;\n	}\'.n.get_pref(\'etc_tree_js\').n.\'\n	$.fn.etc_link_reset();\n});\');\n	return $rs.n.$out;\n}\n\n\n}//admin\n// TXP 4.6 tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\n		->register(\'etc_tree\')\n		->register(\'etc_tree_title\')\n	;\n}\n\nfunction etc_tree_title($atts) {\n	global $etc_tree;\n	extract(lAtts(array(\n		\"type\"=>\'title\'\n	), $atts));\n	return(isset($etc_tree[$type])) ? ($type === \'title\' ? parse($etc_tree[$type]) : $etc_tree[$type]) : \'\';\n}\n\nfunction etc_tree($atts, $thing = \'\') {\n\n	static $trees = array(), $stack = array(), $pageid = null;\n	$admin = txpinterface == \'admin\' && gps(\'event\') == \'etc_tree\';\n	global $pretext, $etc_tree, $etc_tree_atts, $etc_tree_thing;\n	if(isset($etc_tree_atts)) $safe_atts= $etc_tree_atts;\n	if(isset($etc_tree_thing)) $safe_thing = $etc_tree_thing;\n	if(isset($etc_tree)) $safe_tree = $etc_tree;\n\n	if(!isset($atts[\'id\'])) {\n		if(!empty($etc_tree_atts)) $atts += $etc_tree_atts;\n		if(!$thing && !empty($etc_tree_thing)) $thing = $etc_tree_thing;//dmp($etc_tree_atts);dmp($etc_tree_thing);\n	}\n\n	$etc_tree_atts= $atts;\n	$etc_tree_thing = $thing;\n\n	$defaults = array(\n		\"debug\"=>\'\',\n		\"id\"=>null,\n		\"self\"=>\"\",\n		\"depth\"=>null,\n		\"target\"=>null,//depreciated\n		\"include\"=>null,\n		\"exclude\"=>null,\n		\"wraptag\"=>\'ul\',\n		\"break\"=>\'li\',\n		\"separator\"=>\',\',\n		\"class\"=>\'\',\n		\"breakclass\"=>\'\',\n		\"html_id\"=>\'\',\n		\"circular\"=>0,//get_pref(\'etc_tree_depth\')\n	);\n	extract(lAtts($defaults , $atts));\n\n	$treeid = isset($id) ? intval($id) : \'.\';\n\n	if(!isset($stack[$treeid])) $stack[$treeid] = 0;\n\n	if(!isset($trees[$treeid])) {\n		if($debug) dmp(\'Loading \'.$treeid);\n		if($treeid === \'.\') $trees[\'.\'] = array(array(\'id\'=>\'.\', \'menu\'=>0, \'escape\'=>1, \'lft\'=>0, \'rgt\'=>1, \'title\'=>\'\', \'type\'=>\'\'));\n		elseif ($treeid && !extract($rs = safe_row(\"lft as l, rgt as r\", \'etc_tree\', \"id=$treeid\"))) $trees[$treeid] = array();\n		else $trees[$treeid] = safe_rows(\'*\', \'etc_tree\', ($treeid ? \"lft >= $l and lft <= $r\" : \"1\").\" order by lft asc\");\n	}\n	$tree = $trees[$treeid];\n\n	$out = \'\';\n	$first = $treeid && $treeid !== \'.\' && !$self ? 1 : 0;\n	$last = count($tree) - 1;\n\n	if(!isset($pageid)) {\n		$pageid = array();\n		if(!empty($pretext[\'id\'])) $pageid = array(\'article-\'.$pretext[\'id\']);\n		else {\n			if(!empty($pretext[\'author\'])) $pageid[] = \'author-\'.$pretext[\'author\'];\n			if(!empty($pretext[\'c\'])) $pageid[] = \'category-\'.$pretext[\'context\'].\'-\'.$pretext[\'c\'];\n			if(!empty($pretext[\'s\']) && $pretext[\'s\'] !== \'default\') $pageid[] = \'section-\'.$pretext[\'s\'];\n		}\n	}\n\n	if($admin || !empty($tree) && $stack[$treeid] <= intval($circular) && $last >= $first) {\n\n		$stack[$treeid] += 1;\n		if(isset($target)) $include = $target;\n		$exclude = $exclude ? do_list($exclude) : array();\n		$include = $include ? do_list($include) : array();\n		$targets = array();\n		foreach($include as $target) {\n			$end = $last;\n			list($target, $children) = explode(\'+\', $target, 2) + array(0, null);\n			if(!in_array($target, $targets)) while($target) {\n				for($i = $end; $i >= $first; $i--) if($tree[$i][\'id\'] == $target || $target === \'.\' && in_array($tree[$i][\'type\'], $pageid)) {\n					$targets[] = $tree[$i][\'id\'];\n					if(isset($children)) {\n						unset($children);\n						for($j = $i+1; $j <= $last && $tree[$j][\'rgt\'] < $tree[$i][\'rgt\']; $j++) $targets[] = $tree[$j][\'id\'];\n					}\n					$target = $tree[$i][\'parent\'];\n					$end = $i - 1;\n					break;\n				}\n				if($i < $first) break;\n			}\n		}\n		if($include) {$targets = array_unique(array_diff($targets, $exclude)); $exclude = array();}\n\n		$class = $class ? \' class=\"\'.htmlspecialchars($class).\'\"\' : \'\';\n		$html_id = $html_id ? \' id=\"\'.htmlspecialchars($html_id).\'\"\' : \'\';\n		$wrw = $wraptag && preg_match(\'/^\\w+$/\', $wraptag);\n		if(!$wrw) list($wrap, $endwrap) = explode($separator, $wraptag, 2) + array($wraptag, $wraptag);\n		else {$wrap = \"<$wraptag$class>\"; $endwrap = \"</$wraptag>\";}\n		list($breakclass, $currentclass) = explode($separator, $breakclass, 2) + array($breakclass, $breakclass);\n		$br = $break == \'br\' || $break == \'hr\';\n		$brw = !$br && preg_match(\'/^\\w+$/\', $break);\n		$cbreak = $brw ? \"<$break\".($currentclass ? \' class=\"\'.htmlspecialchars($currentclass).\'\">\' : \'>\') : \'\';\n		$endbreak = $br ? \"<$break />\" : ($brw ? \"</$break>\" : $break);\n		$break = $brw ? \"<$break\".($breakclass ? \' class=\"\'.htmlspecialchars($breakclass).\'\">\' : \'>\') : \'\';\n\n		$right = array();\n		$depth = isset($depth) ? intval($depth) : $last;\n		$clevel = 0;\n\n		if(!$include || $targets) for($i = $first; $i <= $last; $i++) {\n			extract($tree[$i]);\n\n			if(isset($until) && $rgt <= $until) continue;\n			if($exclude && in_array($id, $exclude) || $include && !in_array($id, $targets)) {$until = $rgt; continue;}\n			else unset($until);\n\n			while ($right && $right[count($right)-1] < $rgt) {\n				array_pop($right);\n				$out .= $endwrap.($brw ? $endbreak : \'\');\n				$clevel--;\n			}\n			if($clevel > $depth) continue;\n			$etc_tree = $tree[$i];\n			if($admin) {\n				$escape |= strpos($title, \'<txp:\') !== false;\n				$out .= \"<li data-type=\'$type\' data-id=\'$id\'\".($menu && !$clevel ? \" class=\'etc-close\'>\" : \">\").\"<span class=\'etc-handle\' title=\'id=$id\'>&equiv;</span><div data-escape=\'$escape\' contenteditable=\'true\'>\".($escape ? htmlspecialchars($title) : $title).\"</div>\".n;\n			}\n			elseif(trim($chunck = parse($thing ? $thing : $title)) === \'\') continue;\n			else $out .= (in_array($type, $pageid) ? $cbreak : $break).$chunck;\n			if($menu) {\n				$out .= $wrap;\n				$right[] = $rgt; $clevel++;\n			}\n			elseif($brw || $i < $last && !($right && $right[count($right)-1] < $tree[$i+1][\'rgt\'])) $out .= $endbreak;\n		}\n		$out .= str_repeat($endwrap.($brw ? $endbreak : \'\'), $clevel);\n\n		$stack[$treeid] -= 1;\n	}\n\n	$etc_tree = isset($safe_tree) ? $safe_tree : null;\n	$etc_tree_atts = isset($safe_atts) ? $safe_atts: null;\n	$etc_tree_thing = isset($safe_thing) ? $safe_thing : null;\n\n	return trim($out) === \'\' && !$admin ? \'\' : ($wrw ? \"<$wraptag$class$html_id>\" : $wrap).$out.$endwrap;\n}', '7fc634043d2387a57ddf81c3c9a14e1b', '', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_link_helper', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.2b', 'Enhaced Edit link tab', 'Please visit the plugins forum \"thread\":http://forum.textpattern.com/viewtopic.php?id=39895.', 'register_callback(\'etc_link_select\', \'link_ui\', \'extend_detail_form\');\r\n//register_callback(\'etc_link_test\', \'link_ui\', \'multi_edit_options\');\r\n\r\nfunction etc_link_select() {\r\n	register_callback(\'etc_link_js\', \'admin_side\', \'footer\');\r\n	include_once txpath.\'/publish/taghandlers.php\';\r\n	$out = \'<div id=\"etc-link-pane\">\'.n;\r\n	$out .= \'<p class=\"edit-link-url\"><span class=\"txp-label\">\'.gTxt(\'type\').\'</span>\';\r\n	$out .= \'<span class=\"txp-value\"><input type=\"reset\" value=\"\'.gTxt(\'reset\').\'\" onclick=\"return $.fn.etc_link_reset();\"></span></p>\'.n;\r\n\r\n	if($rows = safe_rows(\'ID, Title, Section, url_title, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as lastmod, unix_timestamp(Expires) as expires\', \'textpattern\', \'Status>=4 ORDER BY Title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-article\" name=\"etc-link-type\" value=\"article\"><label for=\"etc-link-type-article\">\'.gTxt(\'article\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-article\" disabled=\"disabled\">\'.n;\r\n		foreach($rows as $row) $out .= \'<option value=\"\'.permlinkurl($row).\'\">\'.escape_title($row[\'Title\']).\'</option>\'.n;\r\n		$out .= \'</select></span></p>\'.n;\r\n	}\r\n\r\n	if($rows = safe_rows(\'*\', \'txp_category\', \'name!=\"root\" ORDER BY type, title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-category\" name=\"etc-link-type\" value=\"category\"><label for=\"etc-link-type-category\">\'.gTxt(\'category\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-category\" disabled=\"disabled\">\'.n;\r\n		$type = \'article\';\r\n		foreach($rows as $row) {\r\n			if($type !== $row[\'type\']) {$type = $row[\'type\']; $out .= \'<optgroup style=\"text-align:center\" label=\"\'.gTxt($row[\'type\']).\'\"></optgroup>\'.n;}\r\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'c\'=>$row[\'name\'], \'context\'=>$row[\'type\'])).\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\r\n		}\r\n		$out .= \'</select></span></p>\';\r\n	}\r\n\r\n	if($rows = safe_rows(\'*\', \'txp_section\', \'name!=\"default\" ORDER BY title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-section\" name=\"etc-link-type\" value=\"section\"><label for=\"etc-link-type-section\">\'.gTxt(\'section\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-section\" disabled=\"disabled\">\'.n;\r\n		foreach($rows as $row) {\r\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'s\'=>$row[\'name\'])).\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\r\n		}\r\n		$out .= \'</select></span></p>\';\r\n	}\r\n	$out .= \'</div>\'.n;\r\n\r\n\r\n	$out .= \'<ul id=\"etc-link-list\">\'.n;\r\n	$out .= \'<ul class=\"etc-sortable\" data-cat=\"\">\'.n;\r\n	if(!($cid = gps(\'id\'))) $out .= \'<li id=\"etc-link-current\">\'.gTxt(\'add_new_link\').\'</li>\'.n;\r\n	if($rows = safe_rows(\'*\', \'txp_link\', \"category=\'\' ORDER BY linksort\"))\r\n	{\r\n		foreach($rows as $row) {\r\n			$out .= \'<li\'.($row[\'id\'] == $cid ? \' id=\"etc-link-current\"\' : \'\').\' title=\"\'.txpspecialchars($row[\'linksort\']).\'\">\'.txpspecialchars($row[\'linkname\']).\'</li>\'.n;\r\n		}\r\n	}\r\n	$out .= \'</ul>\'.n;\r\n\r\n	$clevel = 0;\r\n	foreach(getTree(\'root\', \'link\') as $linkcat) {\r\n		extract($linkcat);\r\n		if($clevel < $level) $out .= str_repeat(\'<ul>\', $level - $clevel);\r\n		elseif($clevel > $level) $out .= str_repeat(\'</ul>\', $clevel - $level).n;\r\n		$clevel = $level;\r\n		$out .= \'<li>\'.txpspecialchars($title).\'</li>\'.n;\r\n		$out .= \'<ul class=\"etc-sortable\" data-cat=\"\'.$name.\'\">\'.n;\r\n		if($rows = safe_rows(\'*\', \'txp_link\', \"category=\'$name\' ORDER BY linksort\"))\r\n		{\r\n			foreach($rows as $row) {\r\n				$out .= \'<li\'.($row[\'id\'] == $cid ? \' id=\"etc-link-current\"\' : \'\').\' title=\"\'.txpspecialchars($row[\'linksort\']).\'\">\'.txpspecialchars($row[\'linkname\']).\'</li>\'.n;\r\n			}\r\n		}\r\n		$out .= \'</ul>\'.n;\r\n	}\r\n	$out .= str_repeat(\'</ul>\', $clevel).n;\r\n	\r\n	$out .= \'</ul>\'.n;\r\n\r\n	return $out;//etc_link_js(null, null, null, $out);\r\n}\r\n\r\nfunction etc_link_js($event, $step, $pre, $rs=\'\') {\r\n	global $prefs;\r\n	$out = \'<style>#etc-link-current{font-weight:bold;cursor:pointer}.etc-sortable{padding:1ex;background:#fff;border:1pt solid #ccc;list-style:none}</style>\'.n;\r\n	if($prefs[\'version\'] < \'4.6\') $out .= \'<script src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js\"></script>\'.n;\r\n\r\n	$out .= script_js(\'$(function () {\r\n\r\n	var $name = $(\"#linkname\").val(), $url = $(\"#url\").val(), $sort = $(\"#linksort\").val();\r\n	var $etc_list = $(\"#etc-link-list\").html();\r\n\r\n	$(\"#etc-link-list\").insertAfter(\"p.edit-link-category\").first();\r\n\r\n	$(\"#linkname\").change(function() {\r\n		$(\"#etc-link-current\").html($(this).val());\r\n	});\r\n\r\n	$(\"#link_category\").change(function() {\r\n		var $link = $(\"#etc-link-current\");\r\n		$(\"#etc-link-list .etc-sortable[data-cat=\\\'\"+$(this).val()+\"\\\']\").append($link);\r\n		$.fn.etc_link_update($link);\r\n	});\r\n\r\n	$(\"#etc-link-pane select\").change(function() {\r\n		var $text = $(this).children(\"option:selected\").first().text();\r\n		//if($(\"#linksort\").val() == $(\"#linkname\").val()) $(\"#linksort\").val(\"\");\r\n		if($sort == \"\") $(\"#linksort\").val($text);\r\n		$(\"#linkname\").val($text);\r\n		$(\"#url\").val($(this).val());\r\n		$(\"#etc-link-current\").html($text);\r\n	});//.children(\"option[value=\\\'\"+$url+\"\\\']\").first().attr(\"selected\", \"selected\");\r\n\r\n	$(\"#etc-link-pane input[name=\\\'etc-link-type\\\']\").change(function() {\r\n		var $val = $(this).val();\r\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\r\n		if($val) $(\"#etc-link-\"+$val).attr(\"disabled\",null).trigger(\"change\");\r\n		else {$(\"#linkname\").val($name); $(\"#url\").val($url)};\r\n	});\r\n\r\n\r\n\r\n	$.fn.etc_link_update = function(item)\r\n	{\r\n		var $current = \"\", $prev = item.prev(\"li\").attr(\"title\"), $next = item.next(\"li\").attr(\"title\");\r\n		if($prev === undefined) $prev = \"\"; else $prev = $prev.toString();\r\n		if($next === undefined) $next = \"\"; else $next = $next.toString();\r\n		var $lPrev = $prev.length, $lNext = $next.length, $lMin = Math.min($lPrev, $lNext);\r\n		var i = 0;\r\n		while(i < $lMin && $prev.charCodeAt(i) == $next.charCodeAt(i)) i++;//alert(i);\r\n//		$current = $prev.substr(0, i);\r\n		if($prev == $next) $current = $prev;\r\n		else if(i == $lMin)\r\n			if($lPrev > i) $current = $prev.substr(0, i).concat(String.fromCharCode($prev.charCodeAt(i) + 1));\r\n			else $current = $next.substr(0, i).concat(String.fromCharCode($next.charCodeAt(i) - 1));\r\n		else if($prev.charCodeAt(i) + 1 < $next.charCodeAt(i)) $current = $prev.substr(0, i).concat(String.fromCharCode(parseInt(($prev.charCodeAt(i) + $next.charCodeAt(i))/2)));\r\n		else $current = ($lNext > i+1 ? $next.substr(0, i+1) : ($lPrev > i+1 ? $prev.substr(0, i+1).concat(String.fromCharCode($prev.charCodeAt(i+1) + 1)) : $prev.concat(\"1\")));\r\n\r\n		if($current == \"0\" && $next) $current = \"/\";//$current.concat(\"1\");\r\n		$(\"#linksort\").val($current);\r\n		$(\"#etc-link-current\").attr(\"title\", $current);\r\n	}\r\n\r\n	$.fn.etc_link_reset = function()\r\n	{\r\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\r\n		$(\"#etc-link-list\").html($etc_list);\r\n		$(\"#etc-link-list .etc-sortable\").disableSelection().sortable({\r\n			connectWith: \".etc-sortable\",\r\n			cancel: \"li:not(#etc-link-current)\",\r\n			update: function( event, ui ) {\r\n				var $cat = ui.item.parent(\"ul\").attr(\"data-cat\");\r\n				$.fn.etc_link_update(ui.item);\r\n				$(\"#link_category\").val($cat);\r\n			}\r\n		});\r\n		return true;\r\n	}\r\n\r\n	$.fn.etc_link_reset();\r\n});\');\r\n	return $rs.n.$out;\r\n}', 'register_callback(\'etc_link_select\', \'link_ui\', \'extend_detail_form\');\r\n//register_callback(\'etc_link_test\', \'link_ui\', \'multi_edit_options\');\r\n\r\nfunction etc_link_select() {\r\n	register_callback(\'etc_link_js\', \'admin_side\', \'footer\');\r\n	include_once txpath.\'/publish/taghandlers.php\';\r\n	$out = \'<div id=\"etc-link-pane\">\'.n;\r\n	$out .= \'<p class=\"edit-link-url\"><span class=\"txp-label\">\'.gTxt(\'type\').\'</span>\';\r\n	$out .= \'<span class=\"txp-value\"><input type=\"reset\" value=\"\'.gTxt(\'reset\').\'\" onclick=\"return $.fn.etc_link_reset();\"></span></p>\'.n;\r\n\r\n	if($rows = safe_rows(\'ID, Title, Section, url_title, unix_timestamp(Posted) as posted, unix_timestamp(LastMod) as lastmod, unix_timestamp(Expires) as expires\', \'textpattern\', \'Status>=4 ORDER BY Title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-article\" name=\"etc-link-type\" value=\"article\"><label for=\"etc-link-type-article\">\'.gTxt(\'article\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-article\" disabled=\"disabled\">\'.n;\r\n		foreach($rows as $row) $out .= \'<option value=\"\'.permlinkurl($row).\'\">\'.escape_title($row[\'Title\']).\'</option>\'.n;\r\n		$out .= \'</select></span></p>\'.n;\r\n	}\r\n\r\n	if($rows = safe_rows(\'*\', \'txp_category\', \'name!=\"root\" ORDER BY type, title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-category\" name=\"etc-link-type\" value=\"category\"><label for=\"etc-link-type-category\">\'.gTxt(\'category\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-category\" disabled=\"disabled\">\'.n;\r\n		$type = \'article\';\r\n		foreach($rows as $row) {\r\n			if($type !== $row[\'type\']) {$type = $row[\'type\']; $out .= \'<optgroup style=\"text-align:center\" label=\"\'.gTxt($row[\'type\']).\'\"></optgroup>\'.n;}\r\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'c\'=>$row[\'name\'], \'context\'=>$row[\'type\'])).\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\r\n		}\r\n		$out .= \'</select></span></p>\';\r\n	}\r\n\r\n	if($rows = safe_rows(\'*\', \'txp_section\', \'name!=\"default\" ORDER BY title\'))\r\n	{\r\n		$out .= \'<p class=\"edit-link-url\">\'.n.\'<span class=\"txp-label\"><input type=\"radio\" id=\"etc-link-type-section\" name=\"etc-link-type\" value=\"section\"><label for=\"etc-link-type-section\">\'.gTxt(\'section\').\'</label></span>\';\r\n		$out .= \'<span class=\"txp-value\"><select id=\"etc-link-section\" disabled=\"disabled\">\'.n;\r\n		foreach($rows as $row) {\r\n			$out .= \'<option value=\"\'.pagelinkurl(array(\'s\'=>$row[\'name\'])).\'\">\'.escape_title($row[\'title\']).\'</option>\'.n;\r\n		}\r\n		$out .= \'</select></span></p>\';\r\n	}\r\n	$out .= \'</div>\'.n;\r\n\r\n\r\n	$out .= \'<ul id=\"etc-link-list\">\'.n;\r\n	$out .= \'<ul class=\"etc-sortable\" data-cat=\"\">\'.n;\r\n	if(!($cid = gps(\'id\'))) $out .= \'<li id=\"etc-link-current\">\'.gTxt(\'add_new_link\').\'</li>\'.n;\r\n	if($rows = safe_rows(\'*\', \'txp_link\', \"category=\'\' ORDER BY linksort\"))\r\n	{\r\n		foreach($rows as $row) {\r\n			$out .= \'<li\'.($row[\'id\'] == $cid ? \' id=\"etc-link-current\"\' : \'\').\' title=\"\'.txpspecialchars($row[\'linksort\']).\'\">\'.txpspecialchars($row[\'linkname\']).\'</li>\'.n;\r\n		}\r\n	}\r\n	$out .= \'</ul>\'.n;\r\n\r\n	$clevel = 0;\r\n	foreach(getTree(\'root\', \'link\') as $linkcat) {\r\n		extract($linkcat);\r\n		if($clevel < $level) $out .= str_repeat(\'<ul>\', $level - $clevel);\r\n		elseif($clevel > $level) $out .= str_repeat(\'</ul>\', $clevel - $level).n;\r\n		$clevel = $level;\r\n		$out .= \'<li>\'.txpspecialchars($title).\'</li>\'.n;\r\n		$out .= \'<ul class=\"etc-sortable\" data-cat=\"\'.$name.\'\">\'.n;\r\n		if($rows = safe_rows(\'*\', \'txp_link\', \"category=\'$name\' ORDER BY linksort\"))\r\n		{\r\n			foreach($rows as $row) {\r\n				$out .= \'<li\'.($row[\'id\'] == $cid ? \' id=\"etc-link-current\"\' : \'\').\' title=\"\'.txpspecialchars($row[\'linksort\']).\'\">\'.txpspecialchars($row[\'linkname\']).\'</li>\'.n;\r\n			}\r\n		}\r\n		$out .= \'</ul>\'.n;\r\n	}\r\n	$out .= str_repeat(\'</ul>\', $clevel).n;\r\n	\r\n	$out .= \'</ul>\'.n;\r\n\r\n	return $out;//etc_link_js(null, null, null, $out);\r\n}\r\n\r\nfunction etc_link_js($event, $step, $pre, $rs=\'\') {\r\n	global $prefs;\r\n	$out = \'<style>#etc-link-current{font-weight:bold;cursor:pointer}.etc-sortable{padding:1ex;background:#fff;border:1pt solid #ccc;list-style:none}</style>\'.n;\r\n	if($prefs[\'version\'] < \'4.6\') $out .= \'<script src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js\"></script>\'.n;\r\n\r\n	$out .= script_js(\'$(function () {\r\n\r\n	var $name = $(\"#linkname\").val(), $url = $(\"#url\").val(), $sort = $(\"#linksort\").val();\r\n	var $etc_list = $(\"#etc-link-list\").html();\r\n\r\n	$(\"#etc-link-list\").insertAfter(\"p.edit-link-category\").first();\r\n\r\n	$(\"#linkname\").change(function() {\r\n		$(\"#etc-link-current\").html($(this).val());\r\n	});\r\n\r\n	$(\"#link_category\").change(function() {\r\n		var $link = $(\"#etc-link-current\");\r\n		$(\"#etc-link-list .etc-sortable[data-cat=\\\'\"+$(this).val()+\"\\\']\").append($link);\r\n		$.fn.etc_link_update($link);\r\n	});\r\n\r\n	$(\"#etc-link-pane select\").change(function() {\r\n		var $text = $(this).children(\"option:selected\").first().text();\r\n		//if($(\"#linksort\").val() == $(\"#linkname\").val()) $(\"#linksort\").val(\"\");\r\n		if($sort == \"\") $(\"#linksort\").val($text);\r\n		$(\"#linkname\").val($text);\r\n		$(\"#url\").val($(this).val());\r\n		$(\"#etc-link-current\").html($text);\r\n	});//.children(\"option[value=\\\'\"+$url+\"\\\']\").first().attr(\"selected\", \"selected\");\r\n\r\n	$(\"#etc-link-pane input[name=\\\'etc-link-type\\\']\").change(function() {\r\n		var $val = $(this).val();\r\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\r\n		if($val) $(\"#etc-link-\"+$val).attr(\"disabled\",null).trigger(\"change\");\r\n		else {$(\"#linkname\").val($name); $(\"#url\").val($url)};\r\n	});\r\n\r\n\r\n\r\n	$.fn.etc_link_update = function(item)\r\n	{\r\n		var $current = \"\", $prev = item.prev(\"li\").attr(\"title\"), $next = item.next(\"li\").attr(\"title\");\r\n		if($prev === undefined) $prev = \"\"; else $prev = $prev.toString();\r\n		if($next === undefined) $next = \"\"; else $next = $next.toString();\r\n		var $lPrev = $prev.length, $lNext = $next.length, $lMin = Math.min($lPrev, $lNext);\r\n		var i = 0;\r\n		while(i < $lMin && $prev.charCodeAt(i) == $next.charCodeAt(i)) i++;//alert(i);\r\n//		$current = $prev.substr(0, i);\r\n		if($prev == $next) $current = $prev;\r\n		else if(i == $lMin)\r\n			if($lPrev > i) $current = $prev.substr(0, i).concat(String.fromCharCode($prev.charCodeAt(i) + 1));\r\n			else $current = $next.substr(0, i).concat(String.fromCharCode($next.charCodeAt(i) - 1));\r\n		else if($prev.charCodeAt(i) + 1 < $next.charCodeAt(i)) $current = $prev.substr(0, i).concat(String.fromCharCode(parseInt(($prev.charCodeAt(i) + $next.charCodeAt(i))/2)));\r\n		else $current = ($lNext > i+1 ? $next.substr(0, i+1) : ($lPrev > i+1 ? $prev.substr(0, i+1).concat(String.fromCharCode($prev.charCodeAt(i+1) + 1)) : $prev.concat(\"1\")));\r\n\r\n		if($current == \"0\" && $next) $current = \"/\";//$current.concat(\"1\");\r\n		$(\"#linksort\").val($current);\r\n		$(\"#etc-link-current\").attr(\"title\", $current);\r\n	}\r\n\r\n	$.fn.etc_link_reset = function()\r\n	{\r\n		$(\"#etc-link-pane select\").attr(\"disabled\",\"disabled\");\r\n		$(\"#etc-link-list\").html($etc_list);\r\n		$(\"#etc-link-list .etc-sortable\").disableSelection().sortable({\r\n			connectWith: \".etc-sortable\",\r\n			cancel: \"li:not(#etc-link-current)\",\r\n			update: function( event, ui ) {\r\n				var $cat = ui.item.parent(\"ul\").attr(\"data-cat\");\r\n				$.fn.etc_link_update(ui.item);\r\n				$(\"#link_category\").val($cat);\r\n			}\r\n		});\r\n		return true;\r\n	}\r\n\r\n	$.fn.etc_link_reset();\r\n});\');\r\n	return $rs.n.$out;\r\n}', '0d4993977327172cd0bd4dcb49841ea3', '', '', 3, 5, 0),
('etc_output_file', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.1', 'Display files in capable browsers', 'Create some file category, say <code>inline-pdf</code>, and a file-type <code>file_download_header</code> form\n\n<pre>\n&lt;txp:evaluate query=\'&lt;txp:file_download_category escape=\"quote\" /&gt;=\"inline-pdf\"\'&gt;\n    &lt;txp:header name=\"Content-Disposition\" value=\"inline\" /&gt;\n    &lt;txp:header name=\"Content-Type\" value=\"application/pdf\" /&gt;\n&lt;/txp:evaluate&gt;\n</pre>\n\nAdjust as needed.', 'register_callback(\'etc_output_file\', \'file_download\');\r\n\r\nfunction etc_output_file($event) {\r\n    global $pretext;\r\n\r\n    if (empty($pretext[\'skin\']))\r\n        $pretext[\'skin\'] = safe_field(\"skin\", \"txp_section\", \"name = \'default\'\");\r\n\r\n    parse_form(\'file_download_header\');\r\n}', 'register_callback(\'etc_output_file\', \'file_download\');\r\n\r\nfunction etc_output_file($event) {\r\n    global $pretext;\r\n\r\n    if (empty($pretext[\'skin\']))\r\n        $pretext[\'skin\'] = safe_field(\"skin\", \"txp_section\", \"name = \'default\'\");\r\n\r\n    parse_form(\'file_download_header\');\r\n}', 'e60aa81686ce1770fe4876522c4d095b', '', '', 0, 5, 0),
('etc_preview', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.2.2', 'Live comments preview', '	<h4>Description</h4>\n\n	<p>This plugin will enable live comments preview.</p>\n\n	<h4>Requirements</h4>\n\n	<p>jQuery 1.7 or newer.</p>\n\n	<h4>Usage</h4>\n\n	<p>Include <code>&lt;txp:etc_live_preview /&gt;</code> somewhere near (preferably before) <code>&lt;txp:comments_form /&gt;</code> in your <code>comments_display</code> form, like this:</p>\n\n<pre><code>...\n&lt;txp:if_comments_allowed&gt;\n	&lt;txp:etc_live_preview /&gt;\n	&lt;txp:comments_form /&gt;\n&lt;txp:else /&gt;\n	&lt;p&gt;&lt;txp:text item=&quot;comments_closed&quot; /&gt;&lt;/p&gt;\n&lt;/txp:if_comments_allowed&gt;\n...\n</code></pre>\n\n	<p>It will play the role of <code>&lt;txp:comments_preview /&gt;</code> and wrapped in <code>&lt;div id=&quot;cpreview&quot;&gt;&lt;/div&gt;</code>, unless you set the <strong>preview</strong> attribute explicitly:</p>\n\n<pre><code>&lt;div id=&quot;my-preview&quot;&gt;\n	&lt;txp:etc_live_preview preview=&quot;#my-preview&quot; /&gt;\n&lt;/div&gt;\n</code></pre>\n\n	<p>Live preview updates will trigger <code>start</code> and <code>stop</code> jQuery events on the nodes matched by <strong>preview</strong> attribute, so you can enhance them with your favorite animations:</p>\n\n<pre><code>&lt;script&gt;\n$(function () {\n	$(&quot;#creview&quot;).bind(&quot;start&quot;, function() {\n		$(this).stop(true).fadeTo(&quot;fast&quot;, 0.1);\n	}).bind(&quot;stop&quot;, function() {\n		$(this).stop(true).fadeTo(&quot;fast&quot;, 1);\n	});\n});\n&lt;/script&gt;\n</code></pre>\n\n	<h4>Tags</h4>\n\n	<p><code>&lt;txp:etc_live_preview /&gt;</code> has the following attributes:</p>\n\n	<ul>\n		<li><strong>timeout</strong>: a timeout (in ms) of user inactivity before two live previews, default 600. Setting <strong>timeout</strong> to <code>0</code> will restrict live preview to &#8220;Preview&#8221; button clicks only.</li>\n		<li><strong>auto</strong>: whether the live preview should be automatically triggered after the user has filled all required fields. Default is <code>0</code> (no).</li>\n		<li><strong>preview</strong>: a jQuery selector to be used instead the default <code>#cpreview</code>.</li>\n		<li><strong>form</strong>, <strong>wraptag</strong>, <strong>class</strong>: same as <code>&lt;txp:comments_preview /&gt;</code>.</li>\n	</ul>', 'function etc_live_preview($atts)\r\n{\r\n\r\n	extract(lAtts(array(\r\n		\"timeout\" => 600,\r\n		\"form\" => \'comments\',\r\n		\"wraptag\" => \'\',\r\n		\"class\" => \'comments_preview\',\r\n		\"preview\" => null,\r\n		\"auto\" => 0\r\n	), $atts));\r\n\r\n	$out = comments_preview(array(\'form\'=>$form, \'wraptag\'=>$wraptag, \'class\'=>$class));\r\n	if(!isset($preview)) {\r\n		$preview = \'#cpreview\';\r\n		$out = \'<div id=\"cpreview\">\'.$out.\'</div>\';\r\n	}\r\n\r\n	$hash = md5(get_pref(\'blog_uid\').$form.$wraptag.$class);\r\n	$timeout = (int) $timeout;\r\n	$form = escape_js($form);\r\n	$wraptag = escape_js($wraptag);\r\n	$class = escape_js($class);\r\n	$preview = escape_js($preview);\r\n\r\n	$js = <<<EOF\r\n	var liveComments = {\r\n	liveTimeout: null,\r\n	liveRequest: null,\r\n	liveInit: true,\r\n\r\n	liveStart: function(event, timeout) {\r\n		if((timeout || !this.liveInit) && this.liveTimeout) window.clearTimeout(this.liveTimeout);\r\n		if(!timeout) liveComments.livePreview(event);\r\n		else this.liveTimeout = window.setTimeout(\"liveComments.livePreview(null)\", timeout);\r\n	},\r\n\r\n	livePreview : function (event) {\r\n		var form = jQuery(\"#txpCommentInputForm\");\r\n		var valid = true;\r\n		jQuery(\"#message, *[required]\", form).each(function() {\r\n			if(!jQuery(this).val()) return(valid = false);\r\n		});\r\n		if(!valid) return;\r\n		if(event) event.preventDefault();\r\n		var post = jQuery.merge([{name : \"hash\", value : \"{$hash}\"}, {name : \"form\", value : \"{$form}\"}, {name : \"wraptag\", value : \"{$wraptag}\"}, {name : \"class\", value : \"{$class}\"}, {name : \"preview\", value : \"Preview\"}], form.serializeArray());\r\n		jQuery(\"{$preview}\").trigger(\"start\");\r\n		if(event && liveComments.liveInit) {\r\n			form.load(form.attr(\"action\")+\" #txpCommentInputForm>*\", post, function() {\r\n				liveComments.liveInit = jQuery(\"#txpCommentSubmit\").is(\":disabled\"); jQuery(\"{$preview}\").trigger(\"stop\")\r\n			});\r\n		}\r\n		else jQuery(\"{$preview}\").load(\"?etc_preview=live\", post, function() {jQuery(this).trigger(\"stop\")});\r\n	}\r\n	};\r\n\r\n	jQuery(function () {\r\n\r\n	jQuery(document).ajaxSend(function( event, jqxhr, settings ) {\r\n		if (settings.url == \"?etc_preview=live\") {\r\n			if(liveComments.liveRequest) liveComments.liveRequest.abort();\r\n			liveComments.liveRequest = jqxhr;\r\n		}\r\n	});\r\n	jQuery(\"#txpCommentInputForm\").on(\"click\", \"#txpCommentPreview\", function(event) {\r\n		liveComments.liveStart(event, 0);\r\n	});\r\nEOF;\r\n	if($timeout) $js .= \'	document.getElementById(\"txpCommentInputForm\").addEventListener(\"input\", function(event) {\r\n		if(!liveComments.liveInit) liveComments.liveStart(event, \'.$timeout.\');\r\n	});\'.n;\r\n	if($auto) $js .= \'	jQuery(\"#message, #txpCommentInputForm :input[required]\").blur(function(){jQuery(\"#txpCommentPreview\").click();});\'.n;\r\n	$js .= \'});\';\r\n\r\n	return $out.n.n.script_js($js);\r\n}\r\n\r\nclass etc_LiveComment\r\n{\r\n	public function __construct()\r\n	{\r\n		register_callback(array($this, \'noLog\'), \'log_hit\');\r\n		if(gps(\'etc_preview\')) register_callback(array($this, \'livePreview\'), \'textpattern\');\r\n	}\r\n\r\n	/**\r\n	* Disable the logging of live requests\r\n	*/\r\n\r\n	public function noLog()\r\n	{\r\n		extract(getComment());\r\n		if ($nonce)\r\n		{\r\n			global $nolog;\r\n			$nolog = true;\r\n		}\r\n	}\r\n\r\n	/**\r\n	* Live preview\r\n	*/\r\n\r\n	public function livePreview()\r\n	{\r\n		global $thisarticle;\r\n		if(!isset($thisarticle)) $thisarticle = true;\r\n		ob_clean();\r\n		txp_status_header(\'200 OK\');\r\n		set_error_handler(\'tagErrorHandler\');\r\n		extract(psa(array(\'hash\', \'form\', \'wraptag\', \'class\')));\r\n		if($hash === md5(get_pref(\'blog_uid\').$form.$wraptag.$class))\r\n			echo comments_preview(array(\'form\'=>$form, \'wraptag\'=>$wraptag, \'class\'=>$class));\r\n		restore_error_handler();\r\n		exit;\r\n	}\r\n}\r\n\r\nif(ps(\'preview\')) new etc_LiveComment();', 'function etc_live_preview($atts)\r\n{\r\n\r\n	extract(lAtts(array(\r\n		\"timeout\" => 600,\r\n		\"form\" => \'comments\',\r\n		\"wraptag\" => \'\',\r\n		\"class\" => \'comments_preview\',\r\n		\"preview\" => null,\r\n		\"auto\" => 0\r\n	), $atts));\r\n\r\n	$out = comments_preview(array(\'form\'=>$form, \'wraptag\'=>$wraptag, \'class\'=>$class));\r\n	if(!isset($preview)) {\r\n		$preview = \'#cpreview\';\r\n		$out = \'<div id=\"cpreview\">\'.$out.\'</div>\';\r\n	}\r\n\r\n	$hash = md5(get_pref(\'blog_uid\').$form.$wraptag.$class);\r\n	$timeout = (int) $timeout;\r\n	$form = escape_js($form);\r\n	$wraptag = escape_js($wraptag);\r\n	$class = escape_js($class);\r\n	$preview = escape_js($preview);\r\n\r\n	$js = <<<EOF\r\n	var liveComments = {\r\n	liveTimeout: null,\r\n	liveRequest: null,\r\n	liveInit: true,\r\n\r\n	liveStart: function(event, timeout) {\r\n		if((timeout || !this.liveInit) && this.liveTimeout) window.clearTimeout(this.liveTimeout);\r\n		if(!timeout) liveComments.livePreview(event);\r\n		else this.liveTimeout = window.setTimeout(\"liveComments.livePreview(null)\", timeout);\r\n	},\r\n\r\n	livePreview : function (event) {\r\n		var form = jQuery(\"#txpCommentInputForm\");\r\n		var valid = true;\r\n		jQuery(\"#message, *[required]\", form).each(function() {\r\n			if(!jQuery(this).val()) return(valid = false);\r\n		});\r\n		if(!valid) return;\r\n		if(event) event.preventDefault();\r\n		var post = jQuery.merge([{name : \"hash\", value : \"{$hash}\"}, {name : \"form\", value : \"{$form}\"}, {name : \"wraptag\", value : \"{$wraptag}\"}, {name : \"class\", value : \"{$class}\"}, {name : \"preview\", value : \"Preview\"}], form.serializeArray());\r\n		jQuery(\"{$preview}\").trigger(\"start\");\r\n		if(event && liveComments.liveInit) {\r\n			form.load(form.attr(\"action\")+\" #txpCommentInputForm>*\", post, function() {\r\n				liveComments.liveInit = jQuery(\"#txpCommentSubmit\").is(\":disabled\"); jQuery(\"{$preview}\").trigger(\"stop\")\r\n			});\r\n		}\r\n		else jQuery(\"{$preview}\").load(\"?etc_preview=live\", post, function() {jQuery(this).trigger(\"stop\")});\r\n	}\r\n	};\r\n\r\n	jQuery(function () {\r\n\r\n	jQuery(document).ajaxSend(function( event, jqxhr, settings ) {\r\n		if (settings.url == \"?etc_preview=live\") {\r\n			if(liveComments.liveRequest) liveComments.liveRequest.abort();\r\n			liveComments.liveRequest = jqxhr;\r\n		}\r\n	});\r\n	jQuery(\"#txpCommentInputForm\").on(\"click\", \"#txpCommentPreview\", function(event) {\r\n		liveComments.liveStart(event, 0);\r\n	});\r\nEOF;\r\n	if($timeout) $js .= \'	document.getElementById(\"txpCommentInputForm\").addEventListener(\"input\", function(event) {\r\n		if(!liveComments.liveInit) liveComments.liveStart(event, \'.$timeout.\');\r\n	});\'.n;\r\n	if($auto) $js .= \'	jQuery(\"#message, #txpCommentInputForm :input[required]\").blur(function(){jQuery(\"#txpCommentPreview\").click();});\'.n;\r\n	$js .= \'});\';\r\n\r\n	return $out.n.n.script_js($js);\r\n}\r\n\r\nclass etc_LiveComment\r\n{\r\n	public function __construct()\r\n	{\r\n		register_callback(array($this, \'noLog\'), \'log_hit\');\r\n		if(gps(\'etc_preview\')) register_callback(array($this, \'livePreview\'), \'textpattern\');\r\n	}\r\n\r\n	/**\r\n	* Disable the logging of live requests\r\n	*/\r\n\r\n	public function noLog()\r\n	{\r\n		extract(getComment());\r\n		if ($nonce)\r\n		{\r\n			global $nolog;\r\n			$nolog = true;\r\n		}\r\n	}\r\n\r\n	/**\r\n	* Live preview\r\n	*/\r\n\r\n	public function livePreview()\r\n	{\r\n		global $thisarticle;\r\n		if(!isset($thisarticle)) $thisarticle = true;\r\n		ob_clean();\r\n		txp_status_header(\'200 OK\');\r\n		set_error_handler(\'tagErrorHandler\');\r\n		extract(psa(array(\'hash\', \'form\', \'wraptag\', \'class\')));\r\n		if($hash === md5(get_pref(\'blog_uid\').$form.$wraptag.$class))\r\n			echo comments_preview(array(\'form\'=>$form, \'wraptag\'=>$wraptag, \'class\'=>$class));\r\n		restore_error_handler();\r\n		exit;\r\n	}\r\n}\r\n\r\nif(ps(\'preview\')) new etc_LiveComment();', 'e2720ab7b792a23f89690487f0fd6603', '', '', 0, 5, 0),
('etc_url', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.4', 'Create/rewrite Textpattern URL', '<p>This very simple plugin will take an <strong>url</strong> and append (or rewrite) some <strong>query</strong> string to it. The resulting url will respect the chosen permalink mode, like this:</p>\n\n<pre><code>&lt;txp:etc_url url=&quot;http://my.web.site/section/tag/?q=etc&amp;pg=2&quot; query=&quot;pg=3&quot; /&gt;\n</code></pre>\n\n	<p>will produce <code>http://my.web.site/tag/?q=etc&amp;pg=3</code> in <code>/section/title</code> mode, and <code>http://my.web.site/index.php?s=tag&q=etc&amp;pg=3</code> in messy mode.</p>\n\n	<p>Semantic txp parameters (like section or category) will hence receive a special treatment, respecting the chosen permalink mode.</p>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><strong>url</strong>: URL to rewrite, default is the current page URL.</li>\n		<li><strong>context</strong>: article, file, image or link. To be used in an article (file, ...) form for custom schemes compatibility and/or faster processing. Empty by default.</li>\n		<li><strong>query</strong>: <code>&amp;</code>-separated list of tokens <code>key</code> or <code>key=value</code>, like this: <code>key1=val1&amp;key2&amp;key3=val3</code>. If <code>val</code> is not set, it will be removed from <code>url</code>.</li>\n		<li><strong>sanitize</strong>: comma-separated URL parameters that should be transformed following the same rules as for article titles.</li>\n		<li><strong>type</strong>: URL parameter to return, default none. If <code>type</code> starts with <code>&amp;</code>, it\'s value will be URL-encoded.</li>\n		<li><strong>mode</strong>: optional TXP <code>permlink mode</code> used to parse <code>url</code>.</li>\n	</ul>\n\n	<h4>History</h4>\n\n	<p><strong>Version 0.1</strong>: initial release.<br />\n<strong>Version 0.2</strong>: works in all url modes now. Added <strong>custom</strong> attribute.<br />\n<strong>Version 0.3</strong>: Added <strong>sanitize</strong> and <strong>type</strong> attributes, removed <strong>custom</strong> attribute.</p>', '// TXP 4.6 tag registration\r\nif (class_exists(\'Textpattern_Tag_Registry\')) {\r\n	Txp::get(\'Textpattern_Tag_Registry\')->register(\'etc_url\');\r\n}\r\n\r\nfunction etc_url($atts, $thing=null) {\r\n	global $pretext;\r\n	static $partscache = array(), $hu = null;\r\n\r\n	extract(lAtts(array(\r\n		\"debug\"=>\'\',\r\n		\"url\"=>null,\r\n		\"context\"=>\'\',\r\n		\"query\"=>\'\',\r\n		\"sanitize\"=>\'\',\r\n		\"mode\"=>$pretext[\'permlink_mode\'],\r\n		\"type\"=>null\r\n	),$atts));\r\n\r\n	$parts = $parse = $qs = array();\r\n	$sanitize = $sanitize ? array_fill_keys(do_list($sanitize), true) : array();\r\n	$custom = false;\r\n\r\n	if(!isset($hu)) $hu = parse_url(hu);\r\n\r\n	if($context) {global ${\'this\'.$context}; if(empty(${\'this\'.$context})) return;}\r\n	elseif(!isset($partscache[$cachekey = md5($mode.$url)]))\r\n	{\r\n		if($url === null)\r\n			foreach(array(\'id\',\'s\',\'c\',\'context\',\'q\',\'m\',\'pg\',\'p\',\'month\',\'author\') as $key) {if($pretext[$key] > \'\') $parts[$key] = $pretext[$key];}\r\n		elseif($url > \'\') {\r\n			$parse = parse_url(str_replace(array(\'&amp;\', \'&#38;\'), \'&\', $url)) + $hu;\r\n			$subpath = $pretext[\'subpath\'];\r\n			$custom = $parse[\'host\'] != $hu[\'host\'] || $parse[\'path\'][0] == \'/\' && strpos($parse[\'path\'], $hu[\'path\']) !== 0;\r\n			if(!$custom && $req = preg_replace(\"/^$subpath/i\", \"/\", $parse[\'path\'])) {\r\n				$parts = etc_parse_req($req[0] === \'/\' ? $req : \'/\'.$req, $mode);\r\n			}\r\n		}\r\n		if($url !== null) {\r\n			if(isset($parse[\'query\'])) parse_str($parse[\'query\'], $qs);\r\n			$parts = $qs + $parts;\r\n		}\r\n		else $parts = $_GET + $parts;\r\n		$partscache[$cachekey] = $parts;\r\n	}\r\n	else $parts = $partscache[$cachekey];\r\n\r\nif($debug) {echo $custom, $url; dmp($hu); dmp($parse); dmp($parts);}\r\n\r\n	$qs = array();\r\n	if($query) foreach(do_list($query, \'&\') as $part) {\r\n		if($part === \'*\') $parts = array();\r\n		elseif($part === \'#\') unset($parse[\'fragment\']);\r\n		elseif(strpos(rtrim($part, \'=\'), \'=\') === false) unset($parts[urldecode($part)]);\r\n 		else $qs[] = $part;\r\n	}\r\n\r\n	if($qs) parse_str(implode(\'&\', $qs), $qs);\r\n\r\n	foreach($qs as $q => $v) if(!isset($sanitize[$q])) $parts[$q] = $v;\r\n		elseif(is_array($v)) $parts[$q] = array_map(\'stripSpace\', $v, array_fill(0, count($v), 1));\r\n		else $parts[$q] = stripSpace($v, 1);\r\n\r\n	if(isset($type)) {\r\n		if($encode = ($type[0] === \'&\')) $type = substr($type, 1);\r\n		if(isset($parts[\'url_\'.$type])) $parts[$type] = $parts[\'url_\'.$type];\r\n		if(!isset($parts[$type])) return \'\';\r\n		if(is_array($parts[$type])) $parts[$type] = implode(\',\', $parts[$type]);\r\n		return ($encode ? urlencode($parts[$type]) : htmlspecialchars($parts[$type]));\r\n	}\r\n\r\n	unset($parts[\'url_id\'], $parts[\'url_s\']);\r\n\r\n	if($custom) if(is_callable(\'http_build_url\')) {\r\n			$parse[\'query\'] = ($parts ? ltrim(join_qs($parts), \'?\') : null);\r\n			$url = http_build_url($parse);\r\n		} else {\r\n			$url = $url && isset($parse[\'query\']) ? str_replace(\'?\'.$parse[\'query\'], join_qs($parts), $url) : (isset($parse[\'fragment\']) ? str_replace(\'#\'.$parse[\'fragment\'], join_qs($parts).\'#\'.$parse[\'fragment\'], $url) : $url.join_qs($parts));\r\n		}\r\n	else {\r\n		if($context || !empty($parts[\'id\'])) {\r\n			if($context === \'article\') {\r\n				$url = permlinkurl($thisarticle);\r\n			}\r\n			elseif($context === \'link\') {\r\n				$url = $thislink[\'url\'];\r\n			}\r\n			elseif($context === \'file\') {\r\n				$url = filedownloadurl($thisfile[\'id\'], $thisfile[\'filename\']);\r\n			}\r\n			elseif($context === \'image\') {\r\n				$url = imagesrcurl($thisimage[\'id\'], $thisimage[\'ext\']);\r\n			}\r\n			else $url = $parts[\'s\'] == \'file_download\' ? filedownloadurl($parts[\'id\'], isset($parts[\'filename\']) ? $parts[\'filename\'] : \'\') : permlinkurl_id($parts[\'id\']);\r\n			foreach(array(\'id\',\'s\',\'c\',\'context\',\'q\',\'m\',\'pg\',\'p\',\'month\',\'author\') as $key) unset($parts[$key]);\r\n			if($parts) $url .= strpos($url, \'?\') !== false ? \'&amp;\'.ltrim(join_qs($parts), \'?\') : join_qs($parts);\r\n		}\r\n		else $url = pagelinkurl($parts);\r\n		if(isset($parse[\'scheme\']) && $parse[\'scheme\'] != $hu[\'scheme\']) $url = preg_replace(\'/^\'.$hu[\'scheme\'].\'/\', $parse[\'scheme\'], $url);\r\n		if(isset($parse[\'fragment\'])) $url .= \'#\'.$parse[\'fragment\'];\r\n	}\r\n	return $thing === null ? $url : \"<a href=\'$url\'>\".parse($thing).\'</a>\';\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction etc_parse_req($req, $mode)\r\n{\r\n	global $prefs;\r\n	$out = array();\r\n	if ($req) {\r\n		extract(chopUrl($req));\r\n\r\n		//first we sniff out some of the preset url schemes\r\n		if (strlen($u1)) {\r\n			switch($u1) {\r\n				case \'atom\':\r\n					$out[\'feed\'] = \'atom\'; break;\r\n				case \'rss\':\r\n					$out[\'feed\'] = \'rss\'; break;\r\n\r\n				// urldecode(strtolower(urlencode())) looks ugly but is the only way to\r\n				// make it multibyte-safe without breaking backwards-compatibility\r\n				case urldecode(strtolower(urlencode(gTxt(\'section\')))):\r\n					$out[\'s\'] = $u2; break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'category\')))):\r\n					if ($u3) {\r\n						$out[\'c\'] = $u3;\r\n					} else {\r\n						$out[\'c\'] = $u2;\r\n					}\r\n					break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'author\')))):\r\n					if ($u3) {\r\n						$out[\'author\'] = $u3;\r\n					} else {\r\n						$out[\'author\'] = $u2;\r\n					}\r\n					break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'file_download\')))):\r\n					$out[\'s\'] = \'file_download\';\r\n					$out[\'id\'] = (!empty($u2)) ? $u2 : \'\';\r\n					$out[\'filename\'] = (!empty($u3)) ? $u3 : \'\';\r\n					break;\r\n				default:\r\n					// then see if the prefs-defined permlink scheme is usable\r\n					switch ($mode /*$prefs[\'permlink_mode\']*/) {\r\n						case \'section_id_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							else {\r\n								$rs = lookupByIDSection($u2, $u1);\r\n								$out[\'s\'] = @$rs[\'Section\'];\r\n								$out[\'id\'] = @$rs[\'ID\'];\r\n								$out[\'url_s\'] = $u1;\r\n								$out[\'url_id\'] = $u2;\r\n							}\r\n							break;\r\n						case \'year_month_day_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							elseif (empty($u4)){\r\n								$month = \"$u1-$u2\";\r\n								if (!empty($u3)) $month.= \"-$u3\";\r\n								if (preg_match(\'/\\d+-\\d+(?:-\\d+)?/\', $month)) {\r\n									$out[\'month\'] = $month;\r\n									$out[\'s\'] = \'default\';\r\n								}\r\n							}else{\r\n								$when = \"$u1-$u2-$u3\";\r\n								$rs = lookupByDateTitle($when,$u4);\r\n								$out[\'id\'] = (!empty($rs[\'ID\'])) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = (!empty($rs[\'Section\'])) ? $rs[\'Section\'] : \'\';\r\n							}\r\n							break;\r\n						case \'section_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							else {\r\n								$rs = lookupByTitleSection($u2,$u1);\r\n								$out[\'id\'] = isset($rs[\'ID\']) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = isset($rs[\'Section\']) ? $rs[\'Section\'] : \'\';\r\n							}\r\n							break;\r\n						case \'title_only\':\r\n							$rs = lookupByTitle($u1);\r\n							$out[\'id\'] = @$rs[\'ID\'];\r\n							$out[\'s\'] = (empty($rs[\'Section\']) ? $u1 : $rs[\'Section\']);\r\n							break;\r\n						case \'id_title\':\r\n							if (is_numeric($u1))\r\n							{\r\n								$rs = lookupByID($u1);\r\n								$out[\'url_id\'] = $u1;\r\n								$out[\'id\'] = (!empty($rs[\'ID\'])) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = (!empty($rs[\'Section\'])) ? $rs[\'Section\'] : \'\';\r\n							}else{\r\n								// We don\'t want to miss the /section/ pages\r\n								$out[\'s\']= $u1;\r\n							}\r\n							break;\r\n					}\r\n					break; // prefs-defined permlink scheme case\r\n			}\r\n		} else {\r\n			$out[\'s\'] = \'default\';\r\n		}\r\n	}\r\n\r\n	// Resolve AuthorID from Authorname\r\n	if (isset($out[\'author\']))\r\n	{\r\n		$name = safe_field(\'name\', \'txp_users\', \"RealName like \'\".doSlash($out[\'author\']).\"\'\");\r\n		$out[\'author\'] = $name ? $name : \'\';\r\n	}\r\n\r\n	$out[\'id\'] = !empty($out[\'id\']) ? intval($out[\'id\']) : \'\';\r\n	$out[\'s\'] = (empty($out[\'s\'])) ? \'default\' : $out[\'s\'];\r\n\r\n	return $out;\r\n}', '// TXP 4.6 tag registration\r\nif (class_exists(\'Textpattern_Tag_Registry\')) {\r\n	Txp::get(\'Textpattern_Tag_Registry\')->register(\'etc_url\');\r\n}\r\n\r\nfunction etc_url($atts, $thing=null) {\r\n	global $pretext;\r\n	static $partscache = array(), $hu = null;\r\n\r\n	extract(lAtts(array(\r\n		\"debug\"=>\'\',\r\n		\"url\"=>null,\r\n		\"context\"=>\'\',\r\n		\"query\"=>\'\',\r\n		\"sanitize\"=>\'\',\r\n		\"mode\"=>$pretext[\'permlink_mode\'],\r\n		\"type\"=>null\r\n	),$atts));\r\n\r\n	$parts = $parse = $qs = array();\r\n	$sanitize = $sanitize ? array_fill_keys(do_list($sanitize), true) : array();\r\n	$custom = false;\r\n\r\n	if(!isset($hu)) $hu = parse_url(hu);\r\n\r\n	if($context) {global ${\'this\'.$context}; if(empty(${\'this\'.$context})) return;}\r\n	elseif(!isset($partscache[$cachekey = md5($mode.$url)]))\r\n	{\r\n		if($url === null)\r\n			foreach(array(\'id\',\'s\',\'c\',\'context\',\'q\',\'m\',\'pg\',\'p\',\'month\',\'author\') as $key) {if($pretext[$key] > \'\') $parts[$key] = $pretext[$key];}\r\n		elseif($url > \'\') {\r\n			$parse = parse_url(str_replace(array(\'&amp;\', \'&#38;\'), \'&\', $url)) + $hu;\r\n			$subpath = $pretext[\'subpath\'];\r\n			$custom = $parse[\'host\'] != $hu[\'host\'] || $parse[\'path\'][0] == \'/\' && strpos($parse[\'path\'], $hu[\'path\']) !== 0;\r\n			if(!$custom && $req = preg_replace(\"/^$subpath/i\", \"/\", $parse[\'path\'])) {\r\n				$parts = etc_parse_req($req[0] === \'/\' ? $req : \'/\'.$req, $mode);\r\n			}\r\n		}\r\n		if($url !== null) {\r\n			if(isset($parse[\'query\'])) parse_str($parse[\'query\'], $qs);\r\n			$parts = $qs + $parts;\r\n		}\r\n		else $parts = $_GET + $parts;\r\n		$partscache[$cachekey] = $parts;\r\n	}\r\n	else $parts = $partscache[$cachekey];\r\n\r\nif($debug) {echo $custom, $url; dmp($hu); dmp($parse); dmp($parts);}\r\n\r\n	$qs = array();\r\n	if($query) foreach(do_list($query, \'&\') as $part) {\r\n		if($part === \'*\') $parts = array();\r\n		elseif($part === \'#\') unset($parse[\'fragment\']);\r\n		elseif(strpos(rtrim($part, \'=\'), \'=\') === false) unset($parts[urldecode($part)]);\r\n 		else $qs[] = $part;\r\n	}\r\n\r\n	if($qs) parse_str(implode(\'&\', $qs), $qs);\r\n\r\n	foreach($qs as $q => $v) if(!isset($sanitize[$q])) $parts[$q] = $v;\r\n		elseif(is_array($v)) $parts[$q] = array_map(\'stripSpace\', $v, array_fill(0, count($v), 1));\r\n		else $parts[$q] = stripSpace($v, 1);\r\n\r\n	if(isset($type)) {\r\n		if($encode = ($type[0] === \'&\')) $type = substr($type, 1);\r\n		if(isset($parts[\'url_\'.$type])) $parts[$type] = $parts[\'url_\'.$type];\r\n		if(!isset($parts[$type])) return \'\';\r\n		if(is_array($parts[$type])) $parts[$type] = implode(\',\', $parts[$type]);\r\n		return ($encode ? urlencode($parts[$type]) : htmlspecialchars($parts[$type]));\r\n	}\r\n\r\n	unset($parts[\'url_id\'], $parts[\'url_s\']);\r\n\r\n	if($custom) if(is_callable(\'http_build_url\')) {\r\n			$parse[\'query\'] = ($parts ? ltrim(join_qs($parts), \'?\') : null);\r\n			$url = http_build_url($parse);\r\n		} else {\r\n			$url = $url && isset($parse[\'query\']) ? str_replace(\'?\'.$parse[\'query\'], join_qs($parts), $url) : (isset($parse[\'fragment\']) ? str_replace(\'#\'.$parse[\'fragment\'], join_qs($parts).\'#\'.$parse[\'fragment\'], $url) : $url.join_qs($parts));\r\n		}\r\n	else {\r\n		if($context || !empty($parts[\'id\'])) {\r\n			if($context === \'article\') {\r\n				$url = permlinkurl($thisarticle);\r\n			}\r\n			elseif($context === \'link\') {\r\n				$url = $thislink[\'url\'];\r\n			}\r\n			elseif($context === \'file\') {\r\n				$url = filedownloadurl($thisfile[\'id\'], $thisfile[\'filename\']);\r\n			}\r\n			elseif($context === \'image\') {\r\n				$url = imagesrcurl($thisimage[\'id\'], $thisimage[\'ext\']);\r\n			}\r\n			else $url = $parts[\'s\'] == \'file_download\' ? filedownloadurl($parts[\'id\'], isset($parts[\'filename\']) ? $parts[\'filename\'] : \'\') : permlinkurl_id($parts[\'id\']);\r\n			foreach(array(\'id\',\'s\',\'c\',\'context\',\'q\',\'m\',\'pg\',\'p\',\'month\',\'author\') as $key) unset($parts[$key]);\r\n			if($parts) $url .= strpos($url, \'?\') !== false ? \'&amp;\'.ltrim(join_qs($parts), \'?\') : join_qs($parts);\r\n		}\r\n		else $url = pagelinkurl($parts);\r\n		if(isset($parse[\'scheme\']) && $parse[\'scheme\'] != $hu[\'scheme\']) $url = preg_replace(\'/^\'.$hu[\'scheme\'].\'/\', $parse[\'scheme\'], $url);\r\n		if(isset($parse[\'fragment\'])) $url .= \'#\'.$parse[\'fragment\'];\r\n	}\r\n	return $thing === null ? $url : \"<a href=\'$url\'>\".parse($thing).\'</a>\';\r\n}\r\n\r\n// -------------------------------------------------------------\r\nfunction etc_parse_req($req, $mode)\r\n{\r\n	global $prefs;\r\n	$out = array();\r\n	if ($req) {\r\n		extract(chopUrl($req));\r\n\r\n		//first we sniff out some of the preset url schemes\r\n		if (strlen($u1)) {\r\n			switch($u1) {\r\n				case \'atom\':\r\n					$out[\'feed\'] = \'atom\'; break;\r\n				case \'rss\':\r\n					$out[\'feed\'] = \'rss\'; break;\r\n\r\n				// urldecode(strtolower(urlencode())) looks ugly but is the only way to\r\n				// make it multibyte-safe without breaking backwards-compatibility\r\n				case urldecode(strtolower(urlencode(gTxt(\'section\')))):\r\n					$out[\'s\'] = $u2; break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'category\')))):\r\n					if ($u3) {\r\n						$out[\'c\'] = $u3;\r\n					} else {\r\n						$out[\'c\'] = $u2;\r\n					}\r\n					break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'author\')))):\r\n					if ($u3) {\r\n						$out[\'author\'] = $u3;\r\n					} else {\r\n						$out[\'author\'] = $u2;\r\n					}\r\n					break;\r\n				case urldecode(strtolower(urlencode(gTxt(\'file_download\')))):\r\n					$out[\'s\'] = \'file_download\';\r\n					$out[\'id\'] = (!empty($u2)) ? $u2 : \'\';\r\n					$out[\'filename\'] = (!empty($u3)) ? $u3 : \'\';\r\n					break;\r\n				default:\r\n					// then see if the prefs-defined permlink scheme is usable\r\n					switch ($mode /*$prefs[\'permlink_mode\']*/) {\r\n						case \'section_id_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							else {\r\n								$rs = lookupByIDSection($u2, $u1);\r\n								$out[\'s\'] = @$rs[\'Section\'];\r\n								$out[\'id\'] = @$rs[\'ID\'];\r\n								$out[\'url_s\'] = $u1;\r\n								$out[\'url_id\'] = $u2;\r\n							}\r\n							break;\r\n						case \'year_month_day_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							elseif (empty($u4)){\r\n								$month = \"$u1-$u2\";\r\n								if (!empty($u3)) $month.= \"-$u3\";\r\n								if (preg_match(\'/\\d+-\\d+(?:-\\d+)?/\', $month)) {\r\n									$out[\'month\'] = $month;\r\n									$out[\'s\'] = \'default\';\r\n								}\r\n							}else{\r\n								$when = \"$u1-$u2-$u3\";\r\n								$rs = lookupByDateTitle($when,$u4);\r\n								$out[\'id\'] = (!empty($rs[\'ID\'])) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = (!empty($rs[\'Section\'])) ? $rs[\'Section\'] : \'\';\r\n							}\r\n							break;\r\n						case \'section_title\':\r\n							if (empty($u2)) {\r\n								$out[\'s\'] = $u1;\r\n							}\r\n							else {\r\n								$rs = lookupByTitleSection($u2,$u1);\r\n								$out[\'id\'] = isset($rs[\'ID\']) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = isset($rs[\'Section\']) ? $rs[\'Section\'] : \'\';\r\n							}\r\n							break;\r\n						case \'title_only\':\r\n							$rs = lookupByTitle($u1);\r\n							$out[\'id\'] = @$rs[\'ID\'];\r\n							$out[\'s\'] = (empty($rs[\'Section\']) ? $u1 : $rs[\'Section\']);\r\n							break;\r\n						case \'id_title\':\r\n							if (is_numeric($u1))\r\n							{\r\n								$rs = lookupByID($u1);\r\n								$out[\'url_id\'] = $u1;\r\n								$out[\'id\'] = (!empty($rs[\'ID\'])) ? $rs[\'ID\'] : \'\';\r\n								$out[\'s\'] = (!empty($rs[\'Section\'])) ? $rs[\'Section\'] : \'\';\r\n							}else{\r\n								// We don\'t want to miss the /section/ pages\r\n								$out[\'s\']= $u1;\r\n							}\r\n							break;\r\n					}\r\n					break; // prefs-defined permlink scheme case\r\n			}\r\n		} else {\r\n			$out[\'s\'] = \'default\';\r\n		}\r\n	}\r\n\r\n	// Resolve AuthorID from Authorname\r\n	if (isset($out[\'author\']))\r\n	{\r\n		$name = safe_field(\'name\', \'txp_users\', \"RealName like \'\".doSlash($out[\'author\']).\"\'\");\r\n		$out[\'author\'] = $name ? $name : \'\';\r\n	}\r\n\r\n	$out[\'id\'] = !empty($out[\'id\']) ? intval($out[\'id\']) : \'\';\r\n	$out[\'s\'] = (empty($out[\'s\'])) ? \'default\' : $out[\'s\'];\r\n\r\n	return $out;\r\n}', '7547dfff568944fc7f0c0f8f65e8af44', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_date', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '0.4.6', 'Custom dates and calendars', '<h4>Description</h4>\n\n<p>This plugin displays dates in custom locales. It introduces three tags: <code>&lt;txp:etc_date /&gt;</code>, <code>&lt;txp:etc_if_date /&gt;</code> and <code>&lt;txp:etc_date_diff /&gt;</code>. The last one requires <span class=\"caps\">PHP</span> DateTime extension.</p>\n\n<h4>Attributes</h4>\n\n<h5>Common to <code>&lt;txp:etc_date /&gt;</code> and <code>&lt;txp:etc_if_date /&gt;</code></h5>\n\n<ul>\n	<li><strong>timestamp</strong>: timestamp, or</li>\n	<li><strong>date</strong>: <code>posted</code>, <code>modified</code>, <code>expires</code>, <code>lastmod</code>, some <a href=\"http://www.php.net/manual/en/datetime.formats.php\">date string</a> (current server date by default).</li>\n	<li><strong>format</strong>: <a href=\"http://www.php.net/manual/en/function.strftime.php\">strftime</a> output format (example: <code>%d %B %Y</code>).</li>\n	<li><strong>lang</strong>: name of desired language string or locale (example: <code>fr-fr</code> or <code>fr_FR</code>, preferred). You can define custom locale strings in <code>etc_date</code> admin tab.</li>\n	<li><strong>calendar</strong>: custom function for dates output (currently only <code>hijri</code> and <code>jalali</code> are available).</li>\n	<li><strong>custom</strong>: one of <code>2,1,0,-1,-2</code>, for fine-tuning.\n	<ul>\n		<li><code>2</code>: use only custom locales.</li>\n		<li><code>1</code>: if custom locale does not exist, search system locales.</li>\n		<li><code>0</code> (default): search for both custom and system locales.</li>\n		<li><code>-1</code>: if system locale does not exist, search custom locales.</li>\n		<li><code>-2</code>: use only system locales.</li>\n	</ul></li>\n	</ul>\n\n<h5><code>etc_if_date</code>-specific</h5>\n\n<ul>\n	<li><strong>compare</strong>: a date to process and compare the processed <strong>date</strong> against, or</li>\n	<li><strong>value</strong>: a value to literally compare the processed <strong>date</strong> against.</li>\n</ul>\n\n<p>Both can be one or more <code>|</code>-separated ranges of the form <code>start..end</code>. If a range starts with <code>!</code>, it will be inverted.</p>\n\n<h5><code>etc_date_diff</code>-specific</h5>\n\n<ul>\n	<li><strong>from</strong>: English <em>from</em> date.</li>\n	<li><strong>to</strong>: English <em>to</em> date.</li>\n	<li><strong>format</strong>: <a href=\"http://www.php.net/manual/en/function.strftime.php\">strftime</a> output format (default: <code>%a</code>).</li>\n</ul>\n\n<h4>Example</h4>\n\n<p><code>&lt;txp:etc_date date=&quot;posted&quot; /&gt;</code> (replaces <code>&lt;txp:posted /&gt;</code> in article form), or <code>&lt;txp:etc_date date=&quot;next Friday&quot; /&gt;</code>.</p>\n\n<pre><code>&lt;txp:etc_if_date date=&quot;expires&quot; compare=&quot;now..+3 days&quot; format=&quot;%s&quot;&gt;\n	This article expires in less than &lt;txp:etc_date_diff from=&quot;yesterday&quot; to=&quot;expires&quot; /&gt; days!\n&lt;/txp:etc_if_date&gt;</code></pre>\n\n<pre><code>&lt;txp:etc_if_date value=&quot;12-24..12-31 | 01-01..01-02&quot; format=&quot;%m-%d&quot;&gt;\n	Closed for winter holidays!\n&lt;/txp:etc_if_date&gt;</code></pre>\n\n<h4>Settings</h4>\n\n<p>The plugin will create <code>etc_date</code> tab under <code>Extensions</code>. If you need to define custom locales, you should set the (self-explanatory) defaults there.</p>', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_date\')\r\n		->register(\'etc_if_date\')\r\n		->register(\'etc_date_diff\')\r\n		->register(\'etc_timestamp\');\r\n}\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'etc_date\', \'1,2\');\r\n	register_tab(\"extensions\", \"etc_date\", \"etc_date\");\r\n	register_callback(\"etc_date_tab\", \"etc_date\");\r\n	add_privs(\'plugin_prefs.etc_date\',\'1,2\');\r\n	register_callback(\'etc_date_tab\', \'plugin_prefs.etc_date\');\r\n	register_callback(\'etc_date_install\', \'plugin_lifecycle.etc_date\');\r\n\r\n\r\nfunction etc_date_install($event=\'\', $step=\'\') {\r\n	if($step == \'installed\'); /*{\r\n		set_pref(\'etc_date_locale_esperanto\', serialize(array(\r\n			\'month\' => array(\'januaro\', \'februaro\', \'marto\', \'aprilo\', \'majo\', \'junio\', \'julio\', \'augusto\', \'septembro\', \'oktobro\', \'novembro\', \'decembro\'),\r\n			\'weekday\' => array(\'lundo\', \'mardo\', \'merkredo\', \'jaudo\', \'vendredo\', \'sabato\', \'dimancho\')\r\n		)), \'etc_date\', PREF_HIDDEN);\r\n	}*/\r\n	elseif($step == \'deleted\') \r\n		safe_delete(\'txp_prefs\', \"event = \'etc_date\'\");\r\n}\r\n\r\nfunction etc_date_tab($event, $step) {\r\n	global $prefs;\r\n	$etc_calendars = array(\'\',\'hijri\',\'jalali\');\r\n	extract(psa(array(\'loc\', \'old_loc\', \'def_loc\', \'def_cal\', \'def_form\')));\r\n\r\n	if($step && bouncer($step, array(\'save\'=>true, \'def\'=>true))) if($step == \'save\') switch(ps(\'save\')) {\r\n		case \'Save\' : if($loc != $old_loc) safe_delete(\'txp_prefs\', \"name=\'etc_date_locale_\".doSlash($old_loc).\"\'\");\r\n		set_pref(\'etc_date_locale_\'.$loc, serialize(array(\'month\'=>do_list(ps(\'month\')), \'weekday\'=>do_list(ps(\'weekday\')))), \'etc_date\', PREF_HIDDEN);\r\n		break;\r\n		case \'Delete\' : safe_delete(\'txp_prefs\', \"name=\'etc_date_locale_\".doSlash($loc).\"\'\");\r\n		break;\r\n	} elseif($step == \'def\') {\r\n			$prefs[\'etc_date_default_locale\'] = $def_loc;\r\n			$prefs[\'etc_date_default_calendar\'] = in_array($def_cal, $etc_calendars) ? $def_cal : \'\';\r\n			$prefs[\'etc_date_default_format\'] = $def_form;\r\n			if($prefs[\'etc_date_default_locale\']) set_pref(\'etc_date_default_locale\', $prefs[\'etc_date_default_locale\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_locale\'\");\r\n			if($prefs[\'etc_date_default_calendar\']) set_pref(\'etc_date_default_calendar\', $prefs[\'etc_date_default_calendar\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_calendar\'\");\r\n			if($prefs[\'etc_date_default_format\']) set_pref(\'etc_date_default_format\', $prefs[\'etc_date_default_format\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_format\'\");\r\n		}\r\n\r\n	$def_loc = get_pref(\'etc_date_default_locale\');\r\n	$def_cal = get_pref(\'etc_date_default_calendar\');\r\n	$def_form = get_pref(\'etc_date_default_format\');\r\n	$rs = safe_rows(\'name, val\', \'txp_prefs\', \"name like \'etc_date_locale_%\'\");\r\n	$calendars = \'\';\r\n	foreach($etc_calendars as $calendar) $calendars .= \'<option value=\"\'.$calendar.\'\"\'.($calendar == $def_cal ? \' selected=\"selected\"\' : \'\').\'>\'.$calendar.\'</option>\';\r\n\r\n	pagetop(\"etc_date\", \"<strong>etc_date</strong> preferences\");\r\n	echo \'<style>\',\r\n	\'	.etc-wide {width: 100%}\',\r\n	\'	#etc-default label, #etc-default input[type=\"text\"], #etc-default select {margin-right: 1em;}\',\r\n	\'</style>\';\r\n	echo \'<h1>etc_date setup</h1>\', n;\r\n\r\n	echo \'<h2>Defaults</h2>\', n;\r\n	echo \'<form id=\"etc-default\" action=\"?event=etc_date\" method=\"post\">\', n, \'<p><label>Locale</label>\', fInput(\'text\', \'def_loc\', $def_loc), \'<label>Calendar</label>\', \'<select name=\"def_cal\">\', $calendars, \'</select>\', \'<label>Format</label>\', fInput(\'text\', \'def_form\', $def_form), tInput(), sInput(\'def\'), fInput(\'submit\', \'save\', \'Save\', \'publish\'), n, \'</p></form>\', n;\r\n\r\n	echo \'<h2>Locale strings</h2>\', n;\r\n	foreach($rs as $row) {\r\n		$loc = str_replace(\'etc_date_locale_\', \'\', $row[\'name\']);\r\n		$data = $row[\'val\'] ? unserialize($row[\'val\']) : null;\r\n		$month = isset($data[\'month\']) ? implode(\',\', $data[\'month\']) : \'\';\r\n		$weekday = isset($data[\'weekday\']) ? implode(\',\', $data[\'weekday\']) : \'\';\r\n		echo etc_date_form(array(\'loc\'=>$loc, \'month\'=>$month, \'weekday\'=>$weekday));\r\n	}\r\n	echo etc_date_form(array(\'loc\'=>\'\', \'month\'=>\'\', \'weekday\'=>\'\'));\r\n\r\n	echo \'<script>$(function () {\r\n		$(\".lever a[href=\\\'#etc-form-\\\']\").trigger(\"click\");\r\n	});</script>\';\r\n\r\n}\r\n\r\nfunction etc_date_form($row) {\r\n	$loc = htmlspecialchars($row[\'loc\']);\r\n	echo \'<div><h3 class=\"lever\"><a href=\"#etc-form-\'.$loc.\'\">\', ($loc ? \'Locale \'.$loc : \'New locale\'), \'</a></h3>\', n;\r\n	echo \'<form id=\"etc-form-\'.$loc.\'\" class=\"toggle\" method=\"post\" action=\"?event=etc_date\">\', n;\r\n	echo \'<p><label>Name</label>&nbsp;\', fInput(\'text\', \'loc\', $row[\'loc\']), \'</p>\', n;\r\n	echo \'<p><label>Month names (comma-separated)</label><br />\', fInput(\'text\', \'month\', $row[\'month\'], \'etc-wide\'), \'</p>\', n;\r\n	echo \'<p><label>Weekday names (comma-separated) strating with Sunday</label><br />\', fInput(\'text\', \'weekday\', $row[\'weekday\'], \'etc-wide\'), \'</p>\', n;\r\n	echo \'<p>\', sInput(\'save\'), hInput(\'old_loc\', $row[\'loc\']), tInput(), fInput(\'submit\', \'save\', \'Save\', \'publish\'), ($loc ? fInput(\'submit\', \'save\', \'Delete\', \'publish\') : \'\'), \'</p>\', n;\r\n	echo \'</form></div>\';\r\n}\r\n\r\n}//admin\r\n\r\nfunction etc_date($atts, $thing = \'\') {\r\n\r\n	global $thisarticle, $prefs, $archive_dateformat, $locale, $etc_timestamp;\r\n	if(empty($locale)) $locale = setlocale(LC_TIME, 0);\r\n	$old_locale = $locale;\r\n	$old_timestamp = isset($etc_timestamp) ? $etc_timestamp : null;\r\n\r\n	extract(lAtts(array(\r\n		\"custom\"=>\"0\",\r\n		\"date\"=>null,\r\n		\"timestamp\"=>null,\r\n		\"calendar\"=>isset($prefs[\'etc_date_default_calendar\']) ? $prefs[\'etc_date_default_calendar\'] : \'\',\r\n		\"lang\"=>isset($prefs[\'etc_date_default_locale\']) ? $prefs[\'etc_date_default_locale\'] : \'\',\r\n		\"format\"=>isset($prefs[\'etc_date_default_format\']) ? $prefs[\'etc_date_default_format\'] : $archive_dateformat,\r\n		\"wraptag\"=>\'\',\r\n		\"break\"=>\'\'\r\n	),$atts));\r\n\r\n	$custom = intval($custom);\r\n	$etc_locale = $lang && $custom > -2 ? unserialize(get_pref(\'etc_date_locale_\'.$lang)) : null;\r\n	if($lang == $old_locale) $lang = null;\r\n	switch($custom) {\r\n		case 2 : $lang = null;\r\n		case 1 : if($etc_locale) $lang = null;\r\n		default : if($lang) {\r\n			if($new_locale = setlocale(LC_TIME, $lang)) $locale = $new_locale;\r\n			else $locale = getlocale($lang);//sets $locale\r\n			if($locale != $old_locale && ($custom < 0 || $custom == 0 && !$calendar)) $etc_locale = null;\r\n		}\r\n	}\r\n	if($calendar && is_callable(\'etc_date_\'.$calendar)) $calendar = \'etc_date_\'.$calendar;\r\n	else $calendar = $etc_locale ? \'etc_date_\' : null;\r\n	$out = array();\r\n\r\n	if(!isset($timestamp) && !isset($date)) $timestamp = time();\r\n	if(isset($timestamp)) $out[] = $thing \r\n		? parse(EvalElse($thing, $timestamp)) \r\n		: strftime($calendar ? $calendar($format, $timestamp, $etc_locale) : $format, $timestamp);\r\n	else foreach(do_list($date, \'|\') as $date) {\r\n		unset($timestamp);\r\n		list($date, $step) = explode(\'::\', $date, 2) + array(null, 86400);\r\n		if($date) switch(strtolower($date)) {\r\n			case \'posted\' : case \'modified\' : case \'expires\' : if(isset($thisarticle[$date])) $timestamp = $thisarticle[$date] + tz_offset($thisarticle[$date]); break;\r\n			case \'lastmod\' : $timestamp = strtotime($prefs[\'lastmod\']); break;\r\n			default : if(!empty($thisarticle)) $date = str_ireplace(array(\'posted\', \'modified\', \'expires\'), array(date(\'r\', $thisarticle[\'posted\']), date(\'r\', $thisarticle[\'modified\']), date(\'r\', $thisarticle[\'expires\'])), $date);\r\n		}\r\n		@list($from, $to) = explode(\'..\', $date, 2);\r\n		if(!isset($timestamp)) $timestamp = strtotime($from);\r\n		$to = isset($to) ? strtotime($to) : $timestamp;\r\n		if($timestamp === false || $to === false) continue;\r\n		for($t = $timestamp; $step > 0 ? $t <= $to : $t >= $to; $t += $step) {\r\n			if($calendar) $format = $calendar($format, $t, $etc_locale);\r\n			$etc_timestamp = $t;\r\n			$out[] = $thing ? parse(EvalElse($thing, true)) : strftime($format, $t);\r\n		}\r\n	}\r\n\r\n	if($old_locale != $locale) $locale = setlocale(LC_TIME, $old_locale);\r\n	$etc_timestamp = $old_timestamp;\r\n	return $out ? ($wraptag || $break ? doWrap($out, $wraptag, $break) : implode(\'\', $out)) : parse(EvalElse($thing, false));\r\n}\r\n\r\nfunction etc_if_date($atts, $thing=null) {\r\n\r\n	global $prefs, $archive_dateformat;\r\n\r\n	extract(lAtts(array(\r\n		\"value\"=>null,\r\n		\"compare\"=>null,\r\n		\"date\"=>null,\r\n		\"timestamp\"=>null,\r\n		\"calendar\"=>isset($prefs[\'etc_date_default_calendar\']) ? $prefs[\'etc_date_default_calendar\'] : \'\',\r\n		\"lang\"=>isset($prefs[\'etc_date_default_locale\']) ? $prefs[\'etc_date_default_locale\'] : \'\',\r\n		\"format\"=>isset($prefs[\'etc_date_default_format\']) ? $prefs[\'etc_date_default_format\'] : $archive_dateformat\r\n	),$atts));\r\n\r\n	unset($atts[\'value\'], $atts[\'compare\']);\r\n	if($value === null && $compare === null) return $thing ? parse(EvalElse($thing, true)) : etc_date($atts);\r\n	if($value === null) $value = $compare/*etc_date(array(\'date\' => $compare) + $atts)*/; else $compare = false;\r\n	$tdate = trim($date = etc_date($atts));\r\n	unset($atts[\'timestamp\']);\r\n	foreach(explode(\'|\', $value) as $range) if(($range = ltrim($range)) || ($range > \'\')) {\r\n		$not = $range[0] === \'!\';\r\n		if($not) $range = substr($range, 1);\r\n		@list($start, $end) = do_list($range, \'..\');\r\n		if($compare !== false) {\r\n			if($start > \'\') $start = etc_date(array(\'date\' => $start) + $atts);\r\n			if(isset($end) && $end > \'\') $end = etc_date(array(\'date\' => $end) + $atts);\r\n		}\r\n		$result = isset($end) ? ($start === \'\' || $start <= $tdate) && ($end === \'\' || $tdate <= $end) : $start == $tdate;\r\n		if($not) $result = !$result;\r\n		if($result) break;\r\n	}\r\n	return $thing ? parse(EvalElse($thing, $result)) : ($result ? $date : \'\');\r\n}\r\n\r\nfunction etc_date_diff($atts) {\r\n\r\n	if(!class_exists(\'DateTime\')) return \'\';\r\n\r\n	global $thisarticle, $prefs;\r\n	extract(lAtts(array(\r\n		\"from\"=>null,\r\n		\"to\"=>null,\r\n		\"format\"=>\'%a\'\r\n	),$atts));\r\n\r\n	if($from) $from = str_ireplace(\'lastmod\', $prefs[\'lastmod\'], $from);\r\n	if($to) $to = str_ireplace(\'lastmod\', $prefs[\'lastmod\'], $to);\r\n	if(!empty($thisarticle)) {\r\n//		$replacements = array(date(\'r\', $thisarticle[\'posted\']), date(\'r\', $thisarticle[\'modified\']), date(\'r\', $thisarticle[\'expires\']));\r\n		$replacements = array(\'@\'.$thisarticle[\'posted\'], \'@\'.$thisarticle[\'modified\'], \'@\'.$thisarticle[\'expires\']);\r\n		if($from) $from = str_ireplace(array(\'posted\', \'modified\', \'expires\'), $replacements, $from);\r\n		if($to) $to = str_ireplace(array(\'posted\', \'modified\', \'expires\'), $replacements, $to);\r\n	}\r\n	$interval = date_diff(date_create($from), date_create($to));\r\n	return $interval->format($format);\r\n}\r\n\r\nfunction etc_timestamp() {\r\n	global $etc_timestamp;\r\n	return isset($etc_timestamp) ? $etc_timestamp : time();\r\n}\r\n\r\n/*\r\n * Default calendar \r\n */\r\nfunction etc_date_($format, $timestamp=null, $locale=null) {\r\n\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	$from = $to = array();\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	if(isset($monthname[$mon-1])) {$from[] = \'%B\'; $to[] = $monthname[$mon-1]; $from[] = \'%b\'; $to[] = $monthname[$mon-1];}\r\n	return strtr($format, array_combine($from, $to));\r\n}\r\n\r\n/*\r\n *\r\n * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)\r\n * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)\r\n *\r\n * Licensed under The MIT License\r\n * Redistributions of files must retain the above copyright notice.\r\n *\r\n */\r\n\r\nfunction etc_date_hijri($format, $timestamp=null, $locale=null) {\r\n\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n//	$monthname = array(\'Muharram\',\'Safar\',\'Rabiulawal\',\'Rabiulakhir\',\'Jamadilawal\',\'Jamadilakhir\',\'Rejab\',\'Syaaban\',\'Ramadhan\',\'Syawal\',\'Zulkaedah\',\'Zulhijjah\');\r\n//	$dayname = array(\'Aḥad\',\'Ithnayn\',\'Thulaathaa\',\'Arba’aa\',\'Khamīs\',\'Jumu’ah\',\'Sabt\');//Sunday, Monday, ...\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	if (($year>1582)||(($year==1582)&&($mon>10))||(($year==1582)&&($mon==10)&&($mday>14))){\r\n		$jd=(int)((1461*($year+4800+(int)(($mon-14)/12)))/4)+(int)((367*($mon-2-12*((int)(($mon-14)/12))))/12)-(int)( (3* ((int)(  ($year+4900+    (int)( ($mon-14)/12))/100)))/4)+$mday-32075;\r\n	} else {\r\n		$jd = 367*$year-(int)((7*($year+5001+(int)(($mon-9)/7)))/4)+(int)((275*$mon)/9)+$mday+1729777;\r\n	}\r\n	$l=$jd-1948440+10632;\r\n	$n=(int)(($l-1)/10631);\r\n	$l=$l-10631*$n+354;\r\n	$j=((int)((10985-$l)/5316))*((int)((50*$l)/17719))+((int)($l/5670))*((int)((43*$l)/15238));\r\n	$l=$l-((int)((30-$j)/15))*((int)((17719*$j)/50))-((int)($j/16))*((int)((15238*$j)/43))+29;\r\n	$mon=(int)((24*$l)/709);\r\n	$mday=$l-(int)((709*$mon)/24);\r\n	$year=30*$n+$j-30;\r\n\r\n	$from = array(\'%d\', \'%e\', \'%m\', \'%Y\');\r\n	$to = array($mday, $mday, $mon, $year);\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	$mname = isset($monthname[$mon-1]) ? $monthname[$mon-1] : strftime(\'%B\', mktime(0, 0, 0, $mon));\r\n	if($mname) {$from[] = \'%B\'; $to[] = $mname; $from[] = \'%b\'; $to[] = $mname; $from[] = \'%h\'; $to[] = $mname;}\r\n//	if(isset($monthname[$mon-1])) {$from[] = \'%B\'; $to[] = $monthname[$mon-1]; $from[] = \'%b\'; $to[] = $monthname[$mon-1]; $from[] = \'%h\'; $to[] = $monthname[$mon-1];}\r\n	return strtr($format, array_combine($from, $to));\r\n}\r\n\r\nfunction etc_date_jalali ($format, $timestamp=null, $locale=null /*$g_y, $g_m, $g_d*/)\r\n{\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	$g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);\r\n	$j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29);\r\n\r\n	$gy = $year-1600;//$g_y\r\n	$gm = $mon-1;//$g_m\r\n	$gd = $mday-1;//$g_d\r\n\r\n	$g_day_no = 365*$gy+intval(($gy+3)/4)-intval(($gy+99)/100)+intval(($gy+399)/400);\r\n\r\n	for ($i=0; $i < $gm; ++$i)\r\n		$g_day_no += $g_days_in_month[$i];\r\n	if ($gm>1 && (($gy%4==0 && $gy%100!=0) || ($gy%400==0)))\r\n	/* leap and after Feb */\r\n		$g_day_no++;\r\n	$g_day_no += $gd;\r\n	$j_day_no = $g_day_no-79;\r\n\r\n	$j_np = intval($j_day_no/12053); /* 12053 = 365*33 + 32/4 */\r\n	$j_day_no = $j_day_no % 12053;\r\n\r\n	$jy = 979+33*$j_np+4*intval($j_day_no/1461); /* 1461 = 365*4 + 4/4 */\r\n\r\n	$j_day_no %= 1461;\r\n\r\n	if ($j_day_no >= 366) {\r\n		$jy += intval(($j_day_no-1)/365);\r\n		$j_day_no = ($j_day_no-1)%365;\r\n	}\r\n\r\n	for ($i = 0; $i < 11 && $j_day_no >= $j_days_in_month[$i]; ++$i)\r\n		$j_day_no -= $j_days_in_month[$i];\r\n	$year = $jy;\r\n	$mon = $i+1;\r\n	$mday = $j_day_no+1;\r\n\r\n	$from = array(\'%d\', \'%e\', \'%m\', \'%Y\');\r\n	$to = array($mday, $mday, $mon, $year);\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	$mname = isset($monthname[$mon-1]) ? $monthname[$mon-1] : strftime(\'%B\', mktime(0, 0, 0, $mon));\r\n	if($mname) {$from[] = \'%B\'; $to[] = $mname; $from[] = \'%b\'; $to[] = $mname; $from[] = \'%h\'; $to[] = $mname;}\r\n	return strtr($format, array_combine($from, $to));\r\n}', '// TXP 4.6 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_date\')\r\n		->register(\'etc_if_date\')\r\n		->register(\'etc_date_diff\')\r\n		->register(\'etc_timestamp\');\r\n}\r\n\r\nif(@txpinterface == \'admin\') {\r\n	add_privs(\'etc_date\', \'1,2\');\r\n	register_tab(\"extensions\", \"etc_date\", \"etc_date\");\r\n	register_callback(\"etc_date_tab\", \"etc_date\");\r\n	add_privs(\'plugin_prefs.etc_date\',\'1,2\');\r\n	register_callback(\'etc_date_tab\', \'plugin_prefs.etc_date\');\r\n	register_callback(\'etc_date_install\', \'plugin_lifecycle.etc_date\');\r\n\r\n\r\nfunction etc_date_install($event=\'\', $step=\'\') {\r\n	if($step == \'installed\'); /*{\r\n		set_pref(\'etc_date_locale_esperanto\', serialize(array(\r\n			\'month\' => array(\'januaro\', \'februaro\', \'marto\', \'aprilo\', \'majo\', \'junio\', \'julio\', \'augusto\', \'septembro\', \'oktobro\', \'novembro\', \'decembro\'),\r\n			\'weekday\' => array(\'lundo\', \'mardo\', \'merkredo\', \'jaudo\', \'vendredo\', \'sabato\', \'dimancho\')\r\n		)), \'etc_date\', PREF_HIDDEN);\r\n	}*/\r\n	elseif($step == \'deleted\') \r\n		safe_delete(\'txp_prefs\', \"event = \'etc_date\'\");\r\n}\r\n\r\nfunction etc_date_tab($event, $step) {\r\n	global $prefs;\r\n	$etc_calendars = array(\'\',\'hijri\',\'jalali\');\r\n	extract(psa(array(\'loc\', \'old_loc\', \'def_loc\', \'def_cal\', \'def_form\')));\r\n\r\n	if($step && bouncer($step, array(\'save\'=>true, \'def\'=>true))) if($step == \'save\') switch(ps(\'save\')) {\r\n		case \'Save\' : if($loc != $old_loc) safe_delete(\'txp_prefs\', \"name=\'etc_date_locale_\".doSlash($old_loc).\"\'\");\r\n		set_pref(\'etc_date_locale_\'.$loc, serialize(array(\'month\'=>do_list(ps(\'month\')), \'weekday\'=>do_list(ps(\'weekday\')))), \'etc_date\', PREF_HIDDEN);\r\n		break;\r\n		case \'Delete\' : safe_delete(\'txp_prefs\', \"name=\'etc_date_locale_\".doSlash($loc).\"\'\");\r\n		break;\r\n	} elseif($step == \'def\') {\r\n			$prefs[\'etc_date_default_locale\'] = $def_loc;\r\n			$prefs[\'etc_date_default_calendar\'] = in_array($def_cal, $etc_calendars) ? $def_cal : \'\';\r\n			$prefs[\'etc_date_default_format\'] = $def_form;\r\n			if($prefs[\'etc_date_default_locale\']) set_pref(\'etc_date_default_locale\', $prefs[\'etc_date_default_locale\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_locale\'\");\r\n			if($prefs[\'etc_date_default_calendar\']) set_pref(\'etc_date_default_calendar\', $prefs[\'etc_date_default_calendar\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_calendar\'\");\r\n			if($prefs[\'etc_date_default_format\']) set_pref(\'etc_date_default_format\', $prefs[\'etc_date_default_format\'], \'etc_date\', PREF_HIDDEN);\r\n			else safe_delete(\'txp_prefs\', \"name = \'etc_date_default_format\'\");\r\n		}\r\n\r\n	$def_loc = get_pref(\'etc_date_default_locale\');\r\n	$def_cal = get_pref(\'etc_date_default_calendar\');\r\n	$def_form = get_pref(\'etc_date_default_format\');\r\n	$rs = safe_rows(\'name, val\', \'txp_prefs\', \"name like \'etc_date_locale_%\'\");\r\n	$calendars = \'\';\r\n	foreach($etc_calendars as $calendar) $calendars .= \'<option value=\"\'.$calendar.\'\"\'.($calendar == $def_cal ? \' selected=\"selected\"\' : \'\').\'>\'.$calendar.\'</option>\';\r\n\r\n	pagetop(\"etc_date\", \"<strong>etc_date</strong> preferences\");\r\n	echo \'<style>\',\r\n	\'	.etc-wide {width: 100%}\',\r\n	\'	#etc-default label, #etc-default input[type=\"text\"], #etc-default select {margin-right: 1em;}\',\r\n	\'</style>\';\r\n	echo \'<h1>etc_date setup</h1>\', n;\r\n\r\n	echo \'<h2>Defaults</h2>\', n;\r\n	echo \'<form id=\"etc-default\" action=\"?event=etc_date\" method=\"post\">\', n, \'<p><label>Locale</label>\', fInput(\'text\', \'def_loc\', $def_loc), \'<label>Calendar</label>\', \'<select name=\"def_cal\">\', $calendars, \'</select>\', \'<label>Format</label>\', fInput(\'text\', \'def_form\', $def_form), tInput(), sInput(\'def\'), fInput(\'submit\', \'save\', \'Save\', \'publish\'), n, \'</p></form>\', n;\r\n\r\n	echo \'<h2>Locale strings</h2>\', n;\r\n	foreach($rs as $row) {\r\n		$loc = str_replace(\'etc_date_locale_\', \'\', $row[\'name\']);\r\n		$data = $row[\'val\'] ? unserialize($row[\'val\']) : null;\r\n		$month = isset($data[\'month\']) ? implode(\',\', $data[\'month\']) : \'\';\r\n		$weekday = isset($data[\'weekday\']) ? implode(\',\', $data[\'weekday\']) : \'\';\r\n		echo etc_date_form(array(\'loc\'=>$loc, \'month\'=>$month, \'weekday\'=>$weekday));\r\n	}\r\n	echo etc_date_form(array(\'loc\'=>\'\', \'month\'=>\'\', \'weekday\'=>\'\'));\r\n\r\n	echo \'<script>$(function () {\r\n		$(\".lever a[href=\\\'#etc-form-\\\']\").trigger(\"click\");\r\n	});</script>\';\r\n\r\n}\r\n\r\nfunction etc_date_form($row) {\r\n	$loc = htmlspecialchars($row[\'loc\']);\r\n	echo \'<div><h3 class=\"lever\"><a href=\"#etc-form-\'.$loc.\'\">\', ($loc ? \'Locale \'.$loc : \'New locale\'), \'</a></h3>\', n;\r\n	echo \'<form id=\"etc-form-\'.$loc.\'\" class=\"toggle\" method=\"post\" action=\"?event=etc_date\">\', n;\r\n	echo \'<p><label>Name</label>&nbsp;\', fInput(\'text\', \'loc\', $row[\'loc\']), \'</p>\', n;\r\n	echo \'<p><label>Month names (comma-separated)</label><br />\', fInput(\'text\', \'month\', $row[\'month\'], \'etc-wide\'), \'</p>\', n;\r\n	echo \'<p><label>Weekday names (comma-separated) strating with Sunday</label><br />\', fInput(\'text\', \'weekday\', $row[\'weekday\'], \'etc-wide\'), \'</p>\', n;\r\n	echo \'<p>\', sInput(\'save\'), hInput(\'old_loc\', $row[\'loc\']), tInput(), fInput(\'submit\', \'save\', \'Save\', \'publish\'), ($loc ? fInput(\'submit\', \'save\', \'Delete\', \'publish\') : \'\'), \'</p>\', n;\r\n	echo \'</form></div>\';\r\n}\r\n\r\n}//admin\r\n\r\nfunction etc_date($atts, $thing = \'\') {\r\n\r\n	global $thisarticle, $prefs, $archive_dateformat, $locale, $etc_timestamp;\r\n	if(empty($locale)) $locale = setlocale(LC_TIME, 0);\r\n	$old_locale = $locale;\r\n	$old_timestamp = isset($etc_timestamp) ? $etc_timestamp : null;\r\n\r\n	extract(lAtts(array(\r\n		\"custom\"=>\"0\",\r\n		\"date\"=>null,\r\n		\"timestamp\"=>null,\r\n		\"calendar\"=>isset($prefs[\'etc_date_default_calendar\']) ? $prefs[\'etc_date_default_calendar\'] : \'\',\r\n		\"lang\"=>isset($prefs[\'etc_date_default_locale\']) ? $prefs[\'etc_date_default_locale\'] : \'\',\r\n		\"format\"=>isset($prefs[\'etc_date_default_format\']) ? $prefs[\'etc_date_default_format\'] : $archive_dateformat,\r\n		\"wraptag\"=>\'\',\r\n		\"break\"=>\'\'\r\n	),$atts));\r\n\r\n	$custom = intval($custom);\r\n	$etc_locale = $lang && $custom > -2 ? unserialize(get_pref(\'etc_date_locale_\'.$lang)) : null;\r\n	if($lang == $old_locale) $lang = null;\r\n	switch($custom) {\r\n		case 2 : $lang = null;\r\n		case 1 : if($etc_locale) $lang = null;\r\n		default : if($lang) {\r\n			if($new_locale = setlocale(LC_TIME, $lang)) $locale = $new_locale;\r\n			else $locale = getlocale($lang);//sets $locale\r\n			if($locale != $old_locale && ($custom < 0 || $custom == 0 && !$calendar)) $etc_locale = null;\r\n		}\r\n	}\r\n	if($calendar && is_callable(\'etc_date_\'.$calendar)) $calendar = \'etc_date_\'.$calendar;\r\n	else $calendar = $etc_locale ? \'etc_date_\' : null;\r\n	$out = array();\r\n\r\n	if(!isset($timestamp) && !isset($date)) $timestamp = time();\r\n	if(isset($timestamp)) $out[] = $thing \r\n		? parse(EvalElse($thing, $timestamp)) \r\n		: strftime($calendar ? $calendar($format, $timestamp, $etc_locale) : $format, $timestamp);\r\n	else foreach(do_list($date, \'|\') as $date) {\r\n		unset($timestamp);\r\n		list($date, $step) = explode(\'::\', $date, 2) + array(null, 86400);\r\n		if($date) switch(strtolower($date)) {\r\n			case \'posted\' : case \'modified\' : case \'expires\' : if(isset($thisarticle[$date])) $timestamp = $thisarticle[$date] + tz_offset($thisarticle[$date]); break;\r\n			case \'lastmod\' : $timestamp = strtotime($prefs[\'lastmod\']); break;\r\n			default : if(!empty($thisarticle)) $date = str_ireplace(array(\'posted\', \'modified\', \'expires\'), array(date(\'r\', $thisarticle[\'posted\']), date(\'r\', $thisarticle[\'modified\']), date(\'r\', $thisarticle[\'expires\'])), $date);\r\n		}\r\n		@list($from, $to) = explode(\'..\', $date, 2);\r\n		if(!isset($timestamp)) $timestamp = strtotime($from);\r\n		$to = isset($to) ? strtotime($to) : $timestamp;\r\n		if($timestamp === false || $to === false) continue;\r\n		for($t = $timestamp; $step > 0 ? $t <= $to : $t >= $to; $t += $step) {\r\n			if($calendar) $format = $calendar($format, $t, $etc_locale);\r\n			$etc_timestamp = $t;\r\n			$out[] = $thing ? parse(EvalElse($thing, true)) : strftime($format, $t);\r\n		}\r\n	}\r\n\r\n	if($old_locale != $locale) $locale = setlocale(LC_TIME, $old_locale);\r\n	$etc_timestamp = $old_timestamp;\r\n	return $out ? ($wraptag || $break ? doWrap($out, $wraptag, $break) : implode(\'\', $out)) : parse(EvalElse($thing, false));\r\n}\r\n\r\nfunction etc_if_date($atts, $thing=null) {\r\n\r\n	global $prefs, $archive_dateformat;\r\n\r\n	extract(lAtts(array(\r\n		\"value\"=>null,\r\n		\"compare\"=>null,\r\n		\"date\"=>null,\r\n		\"timestamp\"=>null,\r\n		\"calendar\"=>isset($prefs[\'etc_date_default_calendar\']) ? $prefs[\'etc_date_default_calendar\'] : \'\',\r\n		\"lang\"=>isset($prefs[\'etc_date_default_locale\']) ? $prefs[\'etc_date_default_locale\'] : \'\',\r\n		\"format\"=>isset($prefs[\'etc_date_default_format\']) ? $prefs[\'etc_date_default_format\'] : $archive_dateformat\r\n	),$atts));\r\n\r\n	unset($atts[\'value\'], $atts[\'compare\']);\r\n	if($value === null && $compare === null) return $thing ? parse(EvalElse($thing, true)) : etc_date($atts);\r\n	if($value === null) $value = $compare/*etc_date(array(\'date\' => $compare) + $atts)*/; else $compare = false;\r\n	$tdate = trim($date = etc_date($atts));\r\n	unset($atts[\'timestamp\']);\r\n	foreach(explode(\'|\', $value) as $range) if(($range = ltrim($range)) || ($range > \'\')) {\r\n		$not = $range[0] === \'!\';\r\n		if($not) $range = substr($range, 1);\r\n		@list($start, $end) = do_list($range, \'..\');\r\n		if($compare !== false) {\r\n			if($start > \'\') $start = etc_date(array(\'date\' => $start) + $atts);\r\n			if(isset($end) && $end > \'\') $end = etc_date(array(\'date\' => $end) + $atts);\r\n		}\r\n		$result = isset($end) ? ($start === \'\' || $start <= $tdate) && ($end === \'\' || $tdate <= $end) : $start == $tdate;\r\n		if($not) $result = !$result;\r\n		if($result) break;\r\n	}\r\n	return $thing ? parse(EvalElse($thing, $result)) : ($result ? $date : \'\');\r\n}\r\n\r\nfunction etc_date_diff($atts) {\r\n\r\n	if(!class_exists(\'DateTime\')) return \'\';\r\n\r\n	global $thisarticle, $prefs;\r\n	extract(lAtts(array(\r\n		\"from\"=>null,\r\n		\"to\"=>null,\r\n		\"format\"=>\'%a\'\r\n	),$atts));\r\n\r\n	if($from) $from = str_ireplace(\'lastmod\', $prefs[\'lastmod\'], $from);\r\n	if($to) $to = str_ireplace(\'lastmod\', $prefs[\'lastmod\'], $to);\r\n	if(!empty($thisarticle)) {\r\n//		$replacements = array(date(\'r\', $thisarticle[\'posted\']), date(\'r\', $thisarticle[\'modified\']), date(\'r\', $thisarticle[\'expires\']));\r\n		$replacements = array(\'@\'.$thisarticle[\'posted\'], \'@\'.$thisarticle[\'modified\'], \'@\'.$thisarticle[\'expires\']);\r\n		if($from) $from = str_ireplace(array(\'posted\', \'modified\', \'expires\'), $replacements, $from);\r\n		if($to) $to = str_ireplace(array(\'posted\', \'modified\', \'expires\'), $replacements, $to);\r\n	}\r\n	$interval = date_diff(date_create($from), date_create($to));\r\n	return $interval->format($format);\r\n}\r\n\r\nfunction etc_timestamp() {\r\n	global $etc_timestamp;\r\n	return isset($etc_timestamp) ? $etc_timestamp : time();\r\n}\r\n\r\n/*\r\n * Default calendar \r\n */\r\nfunction etc_date_($format, $timestamp=null, $locale=null) {\r\n\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	$from = $to = array();\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	if(isset($monthname[$mon-1])) {$from[] = \'%B\'; $to[] = $monthname[$mon-1]; $from[] = \'%b\'; $to[] = $monthname[$mon-1];}\r\n	return strtr($format, array_combine($from, $to));\r\n}\r\n\r\n/*\r\n *\r\n * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)\r\n * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)\r\n *\r\n * Licensed under The MIT License\r\n * Redistributions of files must retain the above copyright notice.\r\n *\r\n */\r\n\r\nfunction etc_date_hijri($format, $timestamp=null, $locale=null) {\r\n\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n//	$monthname = array(\'Muharram\',\'Safar\',\'Rabiulawal\',\'Rabiulakhir\',\'Jamadilawal\',\'Jamadilakhir\',\'Rejab\',\'Syaaban\',\'Ramadhan\',\'Syawal\',\'Zulkaedah\',\'Zulhijjah\');\r\n//	$dayname = array(\'Aḥad\',\'Ithnayn\',\'Thulaathaa\',\'Arba’aa\',\'Khamīs\',\'Jumu’ah\',\'Sabt\');//Sunday, Monday, ...\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	if (($year>1582)||(($year==1582)&&($mon>10))||(($year==1582)&&($mon==10)&&($mday>14))){\r\n		$jd=(int)((1461*($year+4800+(int)(($mon-14)/12)))/4)+(int)((367*($mon-2-12*((int)(($mon-14)/12))))/12)-(int)( (3* ((int)(  ($year+4900+    (int)( ($mon-14)/12))/100)))/4)+$mday-32075;\r\n	} else {\r\n		$jd = 367*$year-(int)((7*($year+5001+(int)(($mon-9)/7)))/4)+(int)((275*$mon)/9)+$mday+1729777;\r\n	}\r\n	$l=$jd-1948440+10632;\r\n	$n=(int)(($l-1)/10631);\r\n	$l=$l-10631*$n+354;\r\n	$j=((int)((10985-$l)/5316))*((int)((50*$l)/17719))+((int)($l/5670))*((int)((43*$l)/15238));\r\n	$l=$l-((int)((30-$j)/15))*((int)((17719*$j)/50))-((int)($j/16))*((int)((15238*$j)/43))+29;\r\n	$mon=(int)((24*$l)/709);\r\n	$mday=$l-(int)((709*$mon)/24);\r\n	$year=30*$n+$j-30;\r\n\r\n	$from = array(\'%d\', \'%e\', \'%m\', \'%Y\');\r\n	$to = array($mday, $mday, $mon, $year);\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	$mname = isset($monthname[$mon-1]) ? $monthname[$mon-1] : strftime(\'%B\', mktime(0, 0, 0, $mon));\r\n	if($mname) {$from[] = \'%B\'; $to[] = $mname; $from[] = \'%b\'; $to[] = $mname; $from[] = \'%h\'; $to[] = $mname;}\r\n//	if(isset($monthname[$mon-1])) {$from[] = \'%B\'; $to[] = $monthname[$mon-1]; $from[] = \'%b\'; $to[] = $monthname[$mon-1]; $from[] = \'%h\'; $to[] = $monthname[$mon-1];}\r\n	return strtr($format, array_combine($from, $to));\r\n}\r\n\r\nfunction etc_date_jalali ($format, $timestamp=null, $locale=null /*$g_y, $g_m, $g_d*/)\r\n{\r\n	if(isset($locale[\'month\'])) $monthname = $locale[\'month\'];\r\n	if(isset($locale[\'weekday\'])) $dayname = $locale[\'weekday\'];\r\n\r\n	extract(getdate($timestamp));\r\n\r\n	$g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);\r\n	$j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29);\r\n\r\n	$gy = $year-1600;//$g_y\r\n	$gm = $mon-1;//$g_m\r\n	$gd = $mday-1;//$g_d\r\n\r\n	$g_day_no = 365*$gy+intval(($gy+3)/4)-intval(($gy+99)/100)+intval(($gy+399)/400);\r\n\r\n	for ($i=0; $i < $gm; ++$i)\r\n		$g_day_no += $g_days_in_month[$i];\r\n	if ($gm>1 && (($gy%4==0 && $gy%100!=0) || ($gy%400==0)))\r\n	/* leap and after Feb */\r\n		$g_day_no++;\r\n	$g_day_no += $gd;\r\n	$j_day_no = $g_day_no-79;\r\n\r\n	$j_np = intval($j_day_no/12053); /* 12053 = 365*33 + 32/4 */\r\n	$j_day_no = $j_day_no % 12053;\r\n\r\n	$jy = 979+33*$j_np+4*intval($j_day_no/1461); /* 1461 = 365*4 + 4/4 */\r\n\r\n	$j_day_no %= 1461;\r\n\r\n	if ($j_day_no >= 366) {\r\n		$jy += intval(($j_day_no-1)/365);\r\n		$j_day_no = ($j_day_no-1)%365;\r\n	}\r\n\r\n	for ($i = 0; $i < 11 && $j_day_no >= $j_days_in_month[$i]; ++$i)\r\n		$j_day_no -= $j_days_in_month[$i];\r\n	$year = $jy;\r\n	$mon = $i+1;\r\n	$mday = $j_day_no+1;\r\n\r\n	$from = array(\'%d\', \'%e\', \'%m\', \'%Y\');\r\n	$to = array($mday, $mday, $mon, $year);\r\n	if(isset($dayname[$wday])) {$from[] = \'%A\'; $to[] = $dayname[$wday]; $from[] = \'%a\'; $to[] = $dayname[$wday];}\r\n	$mname = isset($monthname[$mon-1]) ? $monthname[$mon-1] : strftime(\'%B\', mktime(0, 0, 0, $mon));\r\n	if($mname) {$from[] = \'%B\'; $to[] = $mname; $from[] = \'%b\'; $to[] = $mname; $from[] = \'%h\'; $to[] = $mname;}\r\n	return strtr($format, array_combine($from, $to));\r\n}', '415816bc84ca6385e9454933cbedd2cc', '', '', 1, 5, 1);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('peg_nested_comments', 0, 'Egypt Urnash', 'http://egypt.urnash.com/', '0.2.4', 'Enable comments replies', '<h1>peg_nested_comments</h1>\n\n<h2>Installation, upgrading and uninstallation</h2>\n\n<p>Download the latest version of the plugin, paste the code into the Textpattern Admin → Plugins panel, install and enable the plugin.</p>\n\n<p>To uninstall, delete from the Admin → Plugins panel.</p>\n\n<h2>Tags</h2>\n\n<h3>peg_comments tag</h3>\n\n<pre><code>&lt;txp:peg_comments /&gt;</code></pre>\n\n<p>In the <code>comments_display</code> form, replace your <code>&lt;txp:comments /&gt;</code> with <code>&lt;txp:peg_comments /&gt;</code>.</p>\n\n<h4>Attributes</h4>\n\n<p>Attributes are the same as <code>&lt;txp:comments /&gt;</code>.<br />\nSee <a href=\"http://www.textpattern.net/wiki/index.php?title=comments\" rel=\"nofollow\">here</a> for more informations.</p>\n\n<h3>peg_child_comments tag</h3>\n\n<pre><code>&lt;txp:peg_child_comments /&gt;</code></pre>\n\n<p>in your <code>comments</code> form, add <code>&lt;txp:peg_child_comments /&gt;</code> at the end. Duplicate whatever attributes you used for <code>&lt;txp:peg_comments /&gt;</code>.</p>\n\n<h3>peg_reply_link tag</h3>\n\n<pre><code>&lt;txp:peg_reply_link /&gt;</code></pre>\n\n<p>Also add <code>&lt;txp:peg_reply_link /&gt;</code> wherever you see fit; this generates the link to reply to the comment.</p>\n\n<h3>peg_reply_to tag</h3>\n\n<pre><code>&lt;txp:peg_reply_to /&gt;</code></pre>\n\n<p>finally, insert <code>&lt;txp:peg_reply_to /&gt;</code> somewhere in your <code>comment_form</code> form.</p>\n\n<h4>Attributes</h4>\n\n<ul>\n	<li><code>wraptag=&quot;span&quot;</code>  <em>(default: span)</em>\n	<ul>\n		<li>used to wrap the generated label, input and links;</li>\n	</ul></li>\n	<li><code>class=&quot;&quot;</code>\n	<ul>\n		<li>applied to the wraptag;</li>\n	</ul></li>\n	<li><code>label=&quot;Your label&quot;</code> <em>(default: Replying to)</em>\n	<ul>\n		<li>used as the value of the <code>&lt;label&gt;</code> <span class=\"caps\">HTML</span> tag;</li>\n	</ul></li>\n	<li><code>back=&quot;Go the initial comment&quot;</code> <em>(default: #)</em>\n	<ul>\n		<li>used as a link to the comment you are replying to;</li>\n	</ul></li>\n	<li><code>close=&quot;Unchain&quot;</code> <em>(default: ×)</em>\n	<ul>\n		<li>used as for link which allow to break the link to the comment you were replying to;</li>\n		<li>add the <code>.peg_replyto_inactive</code> to #peg_replyto_wrap.</li>\n	</ul></li>\n	</ul>\n\n<h4>Styling</h4>\n\n<p>Use <code>.peg_replyto_active</code> and <code>.peg_replyto_inactive</code> classes to to hide or alter the display of this tag content in your comment form. <code>.peg_replyto_active</code> is used on reply, <code>.peg_replyto_inactive</code> on simple comment; the second one should be enough to do the following.</p>\n\n<h5>Exemples</h5>\n\n<p>Blur the content on simple comment (not a reply)…</p>\n\n<pre><code>.peg_replyto_inactive { opacity: 0.6; }</code></pre>\n\n<p>…or hide it with…</p>\n\n<pre><code>.peg_replyto_inactive { display: none; }</code></pre>\n\n<p>…or…</p>\n\n<pre><code>.peg_replyto_inactive { visibility: hidden; }</code></pre>\n\n<h2>Authors/credits</h2>\n\n<p>Made Txp4.5 compatible by <a href=\"http://www.iut-fbleau.fr/projet/etc/\" rel=\"nofollow\">Oleg Loukianov</a> from a Margaret Trauth&#8217;s code.<br />\nMany thanks to <a href=\"https://github.com/bloatware/peg_nested_comments/graphs/contributors\" rel=\"nofollow\">all additional contributors</a>.</p>', '\nregister_callback(\'peg_saveCommentLinks\',\'comment.save\');\nregister_callback(\'peg_comments_install\', \'plugin_lifecycle.peg_nested_comments\');\n\nfunction peg_comments_install($event=\'\', $step=\'\') {\n	$rs = safe_query(\"show columns from txp_discuss like \'peg_children\'\");// or die(\"Error checking tables.\");\n	if($step == \'deleted\') {\n		if (numRows($rs) > 0) $rs = safe_alter (\'txp_discuss\',\"DROP peg_children\") or die(\"Error altering tables.\");\n		return;\n	}\n	if($step == \'installed\') {\n		if (numRows($rs) == 0) {\n			$rs = safe_alter (\'txp_discuss\',\"ADD peg_children text not null\") or die(\"Error altering tables.\");\n		}\n		return;\n	}\n}\n\n/*\nif (@txpinterface == \'public\') {\n	// public page stuff would go here\n} else {\n	// admin page stuff would go here\n}\n*/\n\n// cut and paste from txp\'s parse_form\n// with the anti-recursion bits removed\nfunction peg_parse_form_recursively($name) {\n	$f = fetch_form($name);\n	if ($f) {\n		// array_push($stack, $name);\n		$out = parse($f);\n		// array_pop($stack);\n		return $out;\n	}\n}\n\n// provides a nested list of comments\n// this started as a direct cut-and-paste of txp\'s \'comments\' function...\n\nfunction peg_comments($atts) {\n  global $thisarticle, $thiscomment, $prefs;\n\n  extract(lAtts(array(\n		\'form\'       => \'comments\',\n		\'wraptag\'    => ($prefs[\'comments_are_ol\'] ? \'ol\' : \'\'),\n		\'break\'      => ($prefs[\'comments_are_ol\'] ? \'li\' : \'div\'),\n		\'class\'      => \'comments\',\n		\'breakclass\' => \'\',\n		\'limit\'      => 0,\n		\'offset\'     => 0,\n		\'sort\'       => \'posted ASC\',\n	),$atts));\n\n	assert_article();\n//	extract($thisarticle);\n\n	if (!$thisarticle[\'comments_count\']) return \'\';\n	$thisid = intval($thisarticle[\'thisid\']);\n\n//	$txp_discuss = safe_pfx(\'txp_discuss\');\n//	$peg_discuss = safe_pfx(\'peg_discuss\');\n	if(!empty($thiscomment)) {\n		$peg_children = $thiscomment[\'peg_children\'];\n	} else {\n//		safe_query(\"CREATE TEMPORARY TABLE $peg_discuss (SELECT * FROM $txp_discuss WHERE parentid=$thisid AND visible=\".VISIBLE.\")\");\n		$rs = safe_rows(\'discussid, peg_children\', \'txp_discuss\', \"parentid=$thisid AND visible=\".VISIBLE);\n		$peg_children = $rows = array();\n		foreach($rs as $vars) {\n			$rows[] = $vars[\'discussid\'];\n			if($vars[\'peg_children\']) $peg_children = array_merge($peg_children, explode(\',\', $vars[\'peg_children\']));\n		}\n		$peg_children = implode(\',\', $peg_children ? array_diff($rows, $peg_children) : $rows);\n	}\n	if(empty($peg_children)) return \'\';\n\n	$qparts = \"discussid IN($peg_children) AND parentid=$thisid AND visible=\".VISIBLE.\n	($sort ? \" ORDER BY $sort\" : \'\').\n	($limit || $offset ? \' LIMIT \'.intval($offset).\', \'.intval($limit) : \'\');\n\n	$rs = safe_rows(\'*, unix_timestamp(posted) as time\', \'txp_discuss\', $qparts);\n	$out = \'\';\n\n	if ($rs) {\n\n		foreach ($rs as $vars) {\n			$GLOBALS[\'thiscomment\'] = $vars;\n			$comments[] = peg_parse_form_recursively($form).n;\n			unset($GLOBALS[\'thiscomment\']);\n		}\n\n		$out .= doWrap($comments,$wraptag,$break,$class,$breakclass);\n	}\n\n	return $out;\n}\n\nfunction peg_child_comments($atts) {//legacity stuff\n	return peg_comments($atts);\n}\n\nfunction peg_saveCommentLinks($event, $step=\'\') {\n	$reply_to = htmlspecialchars(ps(\'peg_replyto\'));\n	echo $reply_to;\n	if ($reply_to != \'Article\' && $reply_to != \'\') {\n		$nextID = \'lpad(\"\'.peg_getNextAutoID(safe_pfx(\'txp_discuss\')).\'\",6,\"0\")\';\n		$rs = safe_update(\'txp_discuss\',\'peg_children = if (peg_children = \"\", \'.$nextID.\',  concat_ws(\",\",peg_children,\'.$nextID.\'))\',\'discussid=\'.$reply_to);\n	}\n}\n\n// this one is straight out of cha_comment_reply\nfunction peg_getNextAutoID($tname) {\n	/*\n	 * Return the next id to be used on auto_increment in table $tname\n	 */\n\n	$status = getRow(\'show table status like \\\'\'.$tname.\'\\\'\');\n	if (is_array($status)) {\n		$ret = $status[\'Auto_increment\'];\n	} else {\n		$ret = False;\n	}\n\n	return $ret;\n}\n\n// adds an invisible value to the reply form\n// telling us what comment is being replied to\n// pretty much swiped from cha_comment_reply\n\nfunction peg_reply_to($atts) {\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'wraptag\'  => \'span\',\n		\'class\'    => \'\',\n		\'back\'     => \'#\',\n		\'close\'    => \'&times;\',\n		\'size\'     => 25\n	),$atts));\n\n	$reply_to = htmlspecialchars(ps(\'peg_replyto\'));\n	$reply_to_input = htmlspecialchars(ps(\'peg_replyto_input\'));\n\n	return\n	\'<\'.$wraptag.\' id=\"peg_replyto_wrap\" class=\"\'.$class. ($reply_to?\'peg_replyto_active\':\'peg_replyto_inactive\').\'\">\'.n.\n	\'<label for=\"peg_replyto_input\" id=\"peg_replyto_label\">\'.$label.\'</label><input type=\"text\" name=\"peg_replyto_input\" id=\"peg_replyto_input\" value=\"\'.$reply_to_input.\'\" readonly size=\"\'.$size.\'\" />\'.n.\n	\'<input type=\"hidden\" name=\"peg_replyto\" id=\"peg_replyto\" value=\"\'.$reply_to.\'\" />\'.n.\n  	($back ? \'<a id=\"peg_replyto_backlink\" href=\'.($reply_to?\"#c$reply_to\":\"#\").\'>\'.$back.\'</a>\' : \'\').n.\n  	($close ? \'<a id=\"peg_replyto_closelink\" href=\"#peg_replyto_input\" title=\"Empty\" onclick=\"return peg_selectComment(null,null,null,this);\">\'.$close.\'</a>\' : \'\').\'</\'.$wraptag.\'>\'.n.\n\n	\'<script type=\"text/javascript\">\n		function peg_selectComment(discussid,name,title,target) {\n			var pegSelect = document.getElementById(\"peg_replyto\");\n			var pegSelectWrap = document.getElementById(\"peg_replyto_wrap\");\n			var pegSelectInput = document.getElementById(\"peg_replyto_input\");\n			var pegSelectBack = document.getElementById(\"peg_replyto_backlink\");\n\n			if(pegSelect) pegSelect.value = discussid;\n			if(pegSelectWrap) {\n				pegSelectWrap.className = (discussid?\"peg_replyto_active\":\"peg_replyto_inactive\");\n			}\n			if(pegSelectInput) {\n				pegSelectInput.value = (name?name:\"\");\n				pegSelectInput.title = (title?title:\"\");\n			}\n			if(pegSelectBack) {\n				pegSelectBack.href = (discussid?\"#c\"+discussid:null);\n				pegSelectBack.title = (discussid?discussid:\"\");\n			}\n			return !!discussid;\n		}\n	</script>\';\n}\n\nfunction peg_reply_link($atts) {\n	// our current comment\n	global $thisarticle, $thiscomment, $comments_dateformat;\n    //	if (!checkCommentsAllowed($thisarticle[\'thisid\'])) return;\n	extract(lAtts(array(\n		\'label\'    => \'Reply\',\n		\'size\'     => 80\n	),$atts));\n\n	$href = \"#txpCommentInputForm\";\n\n	if($size) {\n		$title = preg_replace(\'/\\s+/s\', \' \', substr(trim(strip_tags($thiscomment[\'message\'])), 0, $size+16));\n		if(strlen($title) > $size+8) {\n			$title = substr($title, 0, $size);\n			$title = substr($title, 0, strrpos($title,\" \"));\n			$title = $title.\'...\';\n		}\n	} else $title=\'\';\n\n	$onclick = \"return peg_selectComment(\'\".$thiscomment[\'discussid\'].\"\',\'\".htmlspecialchars($thiscomment[\'name\']).\" &#183; \".safe_strftime($comments_dateformat, $thiscomment[\'time\']).\"\',\'$title\', this)\";\n\n	$out = \"<a class=\\\"peg_replyto\\\" href=\\\"$href\\\" onclick=\\\"$onclick\\\">$label</a>\";\n\n	return $out;\n}\n', '\nregister_callback(\'peg_saveCommentLinks\',\'comment.save\');\nregister_callback(\'peg_comments_install\', \'plugin_lifecycle.peg_nested_comments\');\n\nfunction peg_comments_install($event=\'\', $step=\'\') {\n	$rs = safe_query(\"show columns from txp_discuss like \'peg_children\'\");// or die(\"Error checking tables.\");\n	if($step == \'deleted\') {\n		if (numRows($rs) > 0) $rs = safe_alter (\'txp_discuss\',\"DROP peg_children\") or die(\"Error altering tables.\");\n		return;\n	}\n	if($step == \'installed\') {\n		if (numRows($rs) == 0) {\n			$rs = safe_alter (\'txp_discuss\',\"ADD peg_children text not null\") or die(\"Error altering tables.\");\n		}\n		return;\n	}\n}\n\n/*\nif (@txpinterface == \'public\') {\n	// public page stuff would go here\n} else {\n	// admin page stuff would go here\n}\n*/\n\n// cut and paste from txp\'s parse_form\n// with the anti-recursion bits removed\nfunction peg_parse_form_recursively($name) {\n	$f = fetch_form($name);\n	if ($f) {\n		// array_push($stack, $name);\n		$out = parse($f);\n		// array_pop($stack);\n		return $out;\n	}\n}\n\n// provides a nested list of comments\n// this started as a direct cut-and-paste of txp\'s \'comments\' function...\n\nfunction peg_comments($atts) {\n  global $thisarticle, $thiscomment, $prefs;\n\n  extract(lAtts(array(\n		\'form\'       => \'comments\',\n		\'wraptag\'    => ($prefs[\'comments_are_ol\'] ? \'ol\' : \'\'),\n		\'break\'      => ($prefs[\'comments_are_ol\'] ? \'li\' : \'div\'),\n		\'class\'      => \'comments\',\n		\'breakclass\' => \'\',\n		\'limit\'      => 0,\n		\'offset\'     => 0,\n		\'sort\'       => \'posted ASC\',\n	),$atts));\n\n	assert_article();\n//	extract($thisarticle);\n\n	if (!$thisarticle[\'comments_count\']) return \'\';\n	$thisid = intval($thisarticle[\'thisid\']);\n\n//	$txp_discuss = safe_pfx(\'txp_discuss\');\n//	$peg_discuss = safe_pfx(\'peg_discuss\');\n	if(!empty($thiscomment)) {\n		$peg_children = $thiscomment[\'peg_children\'];\n	} else {\n//		safe_query(\"CREATE TEMPORARY TABLE $peg_discuss (SELECT * FROM $txp_discuss WHERE parentid=$thisid AND visible=\".VISIBLE.\")\");\n		$rs = safe_rows(\'discussid, peg_children\', \'txp_discuss\', \"parentid=$thisid AND visible=\".VISIBLE);\n		$peg_children = $rows = array();\n		foreach($rs as $vars) {\n			$rows[] = $vars[\'discussid\'];\n			if($vars[\'peg_children\']) $peg_children = array_merge($peg_children, explode(\',\', $vars[\'peg_children\']));\n		}\n		$peg_children = implode(\',\', $peg_children ? array_diff($rows, $peg_children) : $rows);\n	}\n	if(empty($peg_children)) return \'\';\n\n	$qparts = \"discussid IN($peg_children) AND parentid=$thisid AND visible=\".VISIBLE.\n	($sort ? \" ORDER BY $sort\" : \'\').\n	($limit || $offset ? \' LIMIT \'.intval($offset).\', \'.intval($limit) : \'\');\n\n	$rs = safe_rows(\'*, unix_timestamp(posted) as time\', \'txp_discuss\', $qparts);\n	$out = \'\';\n\n	if ($rs) {\n\n		foreach ($rs as $vars) {\n			$GLOBALS[\'thiscomment\'] = $vars;\n			$comments[] = peg_parse_form_recursively($form).n;\n			unset($GLOBALS[\'thiscomment\']);\n		}\n\n		$out .= doWrap($comments,$wraptag,$break,$class,$breakclass);\n	}\n\n	return $out;\n}\n\nfunction peg_child_comments($atts) {//legacity stuff\n	return peg_comments($atts);\n}\n\nfunction peg_saveCommentLinks($event, $step=\'\') {\n	$reply_to = htmlspecialchars(ps(\'peg_replyto\'));\n	echo $reply_to;\n	if ($reply_to != \'Article\' && $reply_to != \'\') {\n		$nextID = \'lpad(\"\'.peg_getNextAutoID(safe_pfx(\'txp_discuss\')).\'\",6,\"0\")\';\n		$rs = safe_update(\'txp_discuss\',\'peg_children = if (peg_children = \"\", \'.$nextID.\',  concat_ws(\",\",peg_children,\'.$nextID.\'))\',\'discussid=\'.$reply_to);\n	}\n}\n\n// this one is straight out of cha_comment_reply\nfunction peg_getNextAutoID($tname) {\n	/*\n	 * Return the next id to be used on auto_increment in table $tname\n	 */\n\n	$status = getRow(\'show table status like \\\'\'.$tname.\'\\\'\');\n	if (is_array($status)) {\n		$ret = $status[\'Auto_increment\'];\n	} else {\n		$ret = False;\n	}\n\n	return $ret;\n}\n\n// adds an invisible value to the reply form\n// telling us what comment is being replied to\n// pretty much swiped from cha_comment_reply\n\nfunction peg_reply_to($atts) {\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'wraptag\'  => \'span\',\n		\'class\'    => \'\',\n		\'back\'     => \'#\',\n		\'close\'    => \'&times;\',\n		\'size\'     => 25\n	),$atts));\n\n	$reply_to = htmlspecialchars(ps(\'peg_replyto\'));\n	$reply_to_input = htmlspecialchars(ps(\'peg_replyto_input\'));\n\n	return\n	\'<\'.$wraptag.\' id=\"peg_replyto_wrap\" class=\"\'.$class. ($reply_to?\'peg_replyto_active\':\'peg_replyto_inactive\').\'\">\'.n.\n	\'<label for=\"peg_replyto_input\" id=\"peg_replyto_label\">\'.$label.\'</label><input type=\"text\" name=\"peg_replyto_input\" id=\"peg_replyto_input\" value=\"\'.$reply_to_input.\'\" readonly size=\"\'.$size.\'\" />\'.n.\n	\'<input type=\"hidden\" name=\"peg_replyto\" id=\"peg_replyto\" value=\"\'.$reply_to.\'\" />\'.n.\n  	($back ? \'<a id=\"peg_replyto_backlink\" href=\'.($reply_to?\"#c$reply_to\":\"#\").\'>\'.$back.\'</a>\' : \'\').n.\n  	($close ? \'<a id=\"peg_replyto_closelink\" href=\"#peg_replyto_input\" title=\"Empty\" onclick=\"return peg_selectComment(null,null,null,this);\">\'.$close.\'</a>\' : \'\').\'</\'.$wraptag.\'>\'.n.\n\n	\'<script type=\"text/javascript\">\n		function peg_selectComment(discussid,name,title,target) {\n			var pegSelect = document.getElementById(\"peg_replyto\");\n			var pegSelectWrap = document.getElementById(\"peg_replyto_wrap\");\n			var pegSelectInput = document.getElementById(\"peg_replyto_input\");\n			var pegSelectBack = document.getElementById(\"peg_replyto_backlink\");\n\n			if(pegSelect) pegSelect.value = discussid;\n			if(pegSelectWrap) {\n				pegSelectWrap.className = (discussid?\"peg_replyto_active\":\"peg_replyto_inactive\");\n			}\n			if(pegSelectInput) {\n				pegSelectInput.value = (name?name:\"\");\n				pegSelectInput.title = (title?title:\"\");\n			}\n			if(pegSelectBack) {\n				pegSelectBack.href = (discussid?\"#c\"+discussid:null);\n				pegSelectBack.title = (discussid?discussid:\"\");\n			}\n			return !!discussid;\n		}\n	</script>\';\n}\n\nfunction peg_reply_link($atts) {\n	// our current comment\n	global $thisarticle, $thiscomment, $comments_dateformat;\n    //	if (!checkCommentsAllowed($thisarticle[\'thisid\'])) return;\n	extract(lAtts(array(\n		\'label\'    => \'Reply\',\n		\'size\'     => 80\n	),$atts));\n\n	$href = \"#txpCommentInputForm\";\n\n	if($size) {\n		$title = preg_replace(\'/\\s+/s\', \' \', substr(trim(strip_tags($thiscomment[\'message\'])), 0, $size+16));\n		if(strlen($title) > $size+8) {\n			$title = substr($title, 0, $size);\n			$title = substr($title, 0, strrpos($title,\" \"));\n			$title = $title.\'...\';\n		}\n	} else $title=\'\';\n\n	$onclick = \"return peg_selectComment(\'\".$thiscomment[\'discussid\'].\"\',\'\".htmlspecialchars($thiscomment[\'name\']).\" &#183; \".safe_strftime($comments_dateformat, $thiscomment[\'time\']).\"\',\'$title\', this)\";\n\n	$out = \"<a class=\\\"peg_replyto\\\" href=\\\"$href\\\" onclick=\\\"$onclick\\\">$label</a>\";\n\n	return $out;\n}\n', 'ccb7a496e1790a3bff7d8ac6ac576075', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('etc_query', 0, 'Oleg Loukianov', 'http://www.iut-fbleau.fr/projet/etc/', '1.3.4', 'Query data with XPath', '<h4>Description</h4>\n\n	<p>This multipurpose plugin allows you to pick some data, transform it and display the result the way you like. It can query HTML/XML/JSON documents, databases, LDAP directories or global variables, and can be useful for</p>\n\n	<ul>\n		<li>importing data from external sites/feeds</li>\n		<li>querying databases</li>\n		<li>retrieving values of global variables</li>\n		<li>performing all kind of calculations</li>\n		<li>iterating over arrays/ranges</li>\n		<li>cleaning poor article markup</li>\n		<li>customizing the output of Textpattern tags and other plugins</li>\n		<li>creating navigation links, pagination, and much more.</li>\n	</ul>\n\n	<p>It is in many points inspired by <a href=\"http://textpattern.org/plugins/1138/smd_xml\">smd_xml</a> and <a href=\"http://textpattern.org/plugins/976/smd_query\">smd_query</a>, but uses XPath to access the desired nodes.</p>\n\n	<p>The sensible features (PHP functions, db query, external data import) are available only to site administrators.</p>\n\n	<h4>Requirements</h4>\n\n	<ul>\n		<li><span class=\"caps\">PHP</span> 5.3.10 with <span class=\"caps\">DOM</span> extension (enabled by default)</li>\n		<li>Basic knowledge of <a href=\"http://www.w3schools.com/xpath/\">XPath</a></li>\n	</ul>\n\n	<h4>Syntax</h4>\n\n	<p>The workflow is: import some <strong>data</strong>, <strong>query</strong> it for some nodes with XPath, do some <strong>replace</strong>ments and display the result. For example,</p>\n\n<pre><code>&lt;txp:etc_query data=&#39;&lt;txp:body /&gt;&#39; replace=&quot;//@style&quot; /&gt;\n</code></pre>\n\n	<p>in an article form will display the body of the article with all style attributes removed.</p>\n\n<pre><code>&lt;txp:etc_query url=&quot;http://textpattern.com&quot; query=&quot;//div[@id=&#39;accoladesBlock&#39;]//img&quot; replace=&quot;@width=128&quot;&gt;\n  {.} : {@alt?}\n&lt;txp:else /&gt;\n  &lt;p&gt;Nobody here.&lt;/p&gt;\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<p>will display all <code>img</code> elements (scaled to <code>width=128</code>) with their <code>alt</code> text found in the <code>div#accoladeBlock</code> on <a href=\"http://textpattern.com\">textpattern.com</a>.</p>\n\n<pre><code>&lt;txp:etc_query data=&quot;SELECT * FROM txp_users WHERE 1&quot; wraptag=&quot;ul&quot; break=&quot;li&quot;&gt;\n  {$strtoupper({RealName?})}\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<p>will display an uppercase list of all users of your site.</p>\n\n<pre><code>&lt;txp:etc_query globals=&quot;_GET&quot; functions=&quot;sqrt&quot; query=&quot;sqrt({?num|0|intval})&quot; /&gt;\n</code></pre>\n\n	<p>will merely calculate the square root of <span class=\"caps\">HTML</span> <span class=\"caps\">GET</span> <code>num</code> field, sanitized as integer.</p>\n\n	<h4>Attributes</h4>\n\n	<ul>\n		<li><strong>data</strong>: The data to parse. Can be some <span class=\"caps\">HTML</span>/XML code, array, database query or LDAP string. Defaults to <code>null</code>.</li>\n		<li><strong>url</strong>: The address of the web page to retrieve the data from. If not empty, supersedes the <strong>data</strong> attribute. This may be a <span class=\"caps\">LARGE</span> <span class=\"caps\">SECURITY</span> <span class=\"caps\">HOLE</span>, use it only with the sites you trust. In <code>db</code> mode, <strong>url</strong> is the name of a local database. Default is empty (txp own database).</li>\n		<li><strong>context</strong>: <code>options</code> (html headers and so on) to pass to php <a href=\"http://fr2.php.net/manual/en/function.stream-context-create.php\">stream-context-create</a> function when retrieving data from <strong>url</strong>. Defaults to <strong>null</strong>.</li>\n		<li><strong>markup</strong>: <code>html</code>, <code>xml</code>, <code>db</code>, <code>json</code>, <code>array</code>, <code>list</code>, <code>ldap</code>, <code>data</code> or <code>raw</code>. If followed by <code>:pfx</code>, the default-namespace nodes can be accessed with <code>pfx:nodename</code>. Defaults to empty (guess from <strong>data</strong>).</li>\n		<li><strong>query</strong>: XPath expression, e.g. the path of the reference nodes. Defaults to <code>.</code> (the <strong>data</strong> itself, wrapped in some root).</li>\n		<li><strong>encoding</strong>: the desired output charset, must be either <code>charset_to</code> or <code>charset_to:charset_from</code>. If <code>charset_from</code> is given, the data will be re-encoded even if it is identical to <code>charset_to</code>. Default is <code>UTF-8</code>.</li>\n		<li><strong>form</strong>: the <span class=\"caps\">TXP</span> Form with which to parse each node. You may use the plugin as a container instead if you prefer. Default is empty.</li>\n		<li><strong>populate</strong>: a <span class=\"caps\">TXP</span> entity (article, image, file or link) fields that will be populated by the data. Default is empty.</li>\n		<li><strong>replace</strong>: *separator*-separated list (empty by default) of tokens like <code>xpath=value</code>, see below.</li>\n		<li><strong>delim</strong>: symbols to be used instead of <code>=</code> in <strong>replace</strong> couples. The default <code>=</code> should suffice in most cases.</li>\n		<li><strong>separator</strong>: symbols to separate the <strong>replace</strong> tokens. Defaults to <code>;</code> (semi-colon).</li>\n		<li><strong>argsep</strong>: symbols to separate the arguments of function patterns, see below. Defaults to <code>|</code> (pipe).</li>\n		<li><strong>globals</strong> : comma-separated list of global variables to be used with the special replacement patterns, see below.</li>\n		<li><strong>sanitize</strong> : comma-separated list of <strong>globals</strong> that should be sanitized with <code>htmlspecialchars</code>. Default is <code>_COOKIE,_GET,_POST,_REQUEST</code>. You can use custom sanitizers as well: <code>sanitize=\"_GET:intval\"</code>.</li>\n		<li><strong>specials</strong> : comma-separated list of attributes (like <code>data</code>, <code>query</code>, <code>replace</code> or <code>content</code>) to proceed with the special replacement patterns. Default is <code>url,data,query</code>.</li>\n		<li><strong>functions</strong> : comma-separated list of php functions to be used in xpath expressions. Requires sufficient user privileges.</li>\n		<li><strong>parse</strong>: whether <span class=\"caps\">TXP</span> parser should parse the content <code>before</code> or <code>after</code> the replacements, or <code>both</code>, or not at all. If you don&#8217;t expect <code>&lt;txp:</code> tags inside the container, you can set <code>parse=\"\"</code>, this will speed up the processing of large data. Default is <code>auto</code>: empty if no <code>&lt;txp:</code> tags are detected, <code>after</code> otherwise.</li>\n		<li><strong>name</strong> : if set, the output will be assigned to the corresponding <code>&lt;txp:variable /&gt;</code>.</li>\n		<li><strong>save</strong> : if set, the data will be stored for further retrieving.</li>\n		<li><strong>limit</strong>, <strong>offset</strong>, <strong>wraptag</strong>, <strong>break</strong>, <strong>html_id</strong>, <strong>class</strong>, <strong>label</strong> and <strong>labeltag</strong> are the usual Textpattern attributes, all empty by default.</li>\n	</ul>\n\n	<h4>Special patterns</h4>\n\n	<p>Every token inside curly <code>{braces}</code> will have a special meaning for <code>etc_query</code>. There are four kinds of patterns.</p>\n\n	<h5>Variable patterns</h5>\n\n	<p>A pattern <code>{?var1,var2,...|default|function_pattern}</code> or <code>{!var1,var2,...|default|function_pattern}</code> will search for the field <code>var1</code> in global variables from the list <strong>globals</strong>, then for <code>var2</code> if <code>var1</code> is not found, and so on. If nothing is found, the <code>default</code> (if given) will be used. Otherwise, the <code>function_pattern</code> (if set) will be applied to the variable before processing, see below. For example, to assign the value of some url <span class=\"caps\">GET</span> variable <code>u</code> to a textpattern <code>&lt;variable name=&quot;v&quot; /&gt;</code> if <code>v</code> is not set, use</p>\n\n<pre><code>&lt;txp:etc_query globals=&quot;variable,_GET&quot; name=&quot;v&quot; data=&quot;{?v,u|0}&quot; /&gt;\n</code></pre>\n\n	<p>If some of <strong>globals</strong> is in <strong>sanitize</strong> list, and no <code>function_pattern</code> is given, the corresponding variables will be <code>htmlspecialchars</code>ed.</p>\n\n	<p>Default for <strong>globals</strong> is <code>variable, pretext, thisarticle, thisimage, thisfile, thislink</code>, but you can use any global variable, like <code>_SESSION</code> or even <code>_REQUEST</code> (at your own risk). To modify the default <strong>globals</strong> list, you can save it (manually for the moment) in <code>$prefs[&#39;etc_globals&#39;]</code>.</p>\n\n	<h5>XPath patterns</h5>\n\n	<p>These are of the form <code>{xpath[?|#]}</code>, and represent the nodes or expressions (relatively to <strong>query</strong>) you want to display/evaluate. For example, the xpath <code>{.}</code> matches the current node retrieved by <strong>query</strong>, <code>{count(./a)}</code> counts all <code>&lt;a&gt;</code> tags that are its children, and so on. If <code>xpath</code> is followed by <code>?</code>, the node value will be displayed, and <code>#</code> results in the node name. If <code>xpath</code> is an expression (like <code>count</code>), the result is its value. Otherwise, the complete node with its children will be displayed. The tags <code>{?}</code> and <code>{#}</code> represent the context node&#8217;s value and name respectively.  Please feel free to test the example above to see what it all means.</p>\n\n	<h5>Internal patterns</h5>\n\n	<p>There are also few internal patterns that can be used in the container and <strong>replace</strong>: <code>{#row}</code> and <code>{#rows}</code> will return the current row number starting from 1 and the total number of displayed rows. Their cousins  <code>{#ind}</code> and <code>{#total}</code> produce the absolute row number (ignoring <strong>limit</strong> and <strong>offset</strong> attributes) starting with 0, and the total number of matched nodes. Finally, <code>{##}</code> represents the internal node number.</p>\n\n	<h5>Function patterns</h5>\n\n	<p>A pattern of the form <code>{$func1(arg1|arg2|...).func2(...)}</code> in the container or <strong>replace</strong> will call the <span class=\"caps\">PHP</span> functions <code>func1(arg1, arg2, ...)</code>, and so on. If there are no args, then <code>func(node&#39;s value)</code> will be returned. The output of each function can be passed as <code>$</code> to the next one, unless the function name is prefixed by <code>@</code>. That\'s mainly used to call some function without changing the &quot;current&quot; value, or to pass it by reference. For example,</p>\n\n<pre><code>&lt;txp:etc_query data=&quot;I hate cats&quot;&gt;\n      {$json_decode({&quot;hate&quot;:&quot;love&quot;,&quot;cats&quot;:&quot;dogs&quot;}|1).strtr({?}|$).@shuffle.implode( |$)}\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<p>will output &#8220;I love dogs&#8221; or some of 6 possible permutations of these 3 words. Without <code>@</code>, <code>shuffle</code> would assign its return value (true or false) to <code>$</code>, and <code>implode</code> wouldn\'t work.</p>\n\n	<p>Few functions are predefined: <code>{$date(format)}</code> will transform the node&#8217;s value into a date formatted as <code>format</code>, <code>textile</code> will Textile it. The pattern <code>{$?(if|then|else}</code> will output <code>then</code> if <code>if</code> is not empty, or <code>else</code> otherwise. You can also use the comparison (<code>=,&lt;,&gt;</code>), concatenation (<code>~</code>) and arithmetic operations <code>+,-,*,/,%</code>, like <code>{$+1}</code> to add 1 to the current node value.</p>\n\n	<p>The default argument separator is <code>|</code> (pipe), but you can change it with <strong>argsep</strong> attribute.</p>\n\n	<h4>Replace attribute</h4>\n\n	<p>The purpose of <strong>replace</strong> attribute is to replace/insert/delete some nodes in the <strong>data</strong> <span class=\"caps\">DOM</span> tree. There are several types of <strong>replace</strong> tokens.</p>\n\n	<p>The first type is just <code>xpath</code>, which removes all the nodes matched by <code>xpath</code>.  For example, replace=&#8221;//script|//@*[starts-with(name(),&#8216;on&#8217;)]&#8221; will attempt to avoid javascript injection.</p>\n\n	<p>Next, a couple <code>xpath=value</code> gives to the nodes matched by <code>xpath</code> the <code>value</code> (possibly empty). The <code>value</code> will be parsed into <span class=\"caps\">XML</span> tree, unless <code>xpath</code> is followed by <code>?</code>. If <code>xpath</code> is followed by <code>+</code>, the <code>value</code> will be appended as child to each matched node. If <code>xpath</code> is followed by <code>^</code> (respectively <code>$</code>), the <code>value</code> will be inserted before (after) each matched node. A token <code>xpath&amp;=value</code> will replace the matched node by value.</p>\n\n	<p>The tokens <code>xpath~=xpath</code> and <code>xpath/=xpath</code> will move the left-hand node, inserting it before the right-hand one, or appending as its child respectively. For exemple, <code>//a[1]/=..</code> will move every first link to the end of its parent node.</p>\n\n	<p>A token <code>xpath@@attr1=val1@attr2=val2...</code> will set the attributes <code>attr1,attr2,...</code> of matched elements to the respective values. You can use the shortcuts <code>.</code>, <code>?</code>, <code>+</code>, <code>^</code>, <code>$</code> <code>~</code> and <code>/</code> as attributes too.</p>\n\n	<p>You can also do replacements inside the container and nest the special tokens. For example,</p>\n\n<pre><code>&lt;txp:etc_query data=&quot;{?body}&quot; query=&quot;//p&quot;&gt;\n{.+=&lt;span class=&quot;row&quot;&gt;{#row}&lt;/span&gt;}\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<p>will number and display all the paragraphs of the article.</p>\n\n	<p>Alternatively, <span class=\"caps\">XSL</span> transforms can be used in <strong>replace</strong> and <strong>content</strong> to format the output.</p>\n\n	<h4><span class=\"caps\">JSON</span> ranges</h4>\n\n	<p>A <strong>data</strong> like <code>[1..10]</code> with <code>markup=&quot;json&quot;</code> will be considered as range of integers <code>1&lt;=n&lt;=10</code>. You can optionally set its step and direction: <code>[10..1:2]</code> is interpreted as the array <code>[10,8,...,2]</code>, but <code>[10..1:+2]</code> is empty.</p>\n\n	<h4>Examples</h4>\n\n	<h5>Prefetching and <span class=\"caps\">TOC</span></h5>\n\n	<p>The article form</p>\n\n<pre><code>&lt;txp:if_individual_article&gt;\n  &lt;txp:etc_query data=&quot;{?body}&quot; query=&quot;//h2|//h3|//h4|//h5&quot; label=&quot;Table of Contents&quot; labeltag=&quot;h1&quot; /&gt;\n  &lt;txp:body /&gt;\n&lt;txp:else /&gt;\n  &lt;txp:excerpt /&gt;\n  &lt;txp:etc_query data=&quot;{?body}&quot; query=&quot;//img&quot;&gt;\n    &lt;link rel=&quot;prefetch&quot; href=&quot;{@src?}&quot; /&gt;\n  &lt;/txp:etc_query&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n	<p>will pre-load the images found in articles&#8217; bodies while viewing the excerpts, and produce a table of contents in the individual article view.</p>\n\n	<h5>Customizing hard-coded txp tags</h5>\n\n<pre><code>&lt;txp:etc_query data=&#39;&lt;txp:comments_form /&gt;&#39; query=&quot;//textarea[@id=&#39;message&#39;]&quot; replace=&quot;@@required=required@placeholder=Drop us a word&quot; /&gt;\n</code></pre>\n\n	<h5>Adding <code>_GET</code> data to some <span class=\"caps\">TXP</span> variable</h5>\n\n<pre><code>&lt;txp:etc_query globals=&quot;variable,_GET&quot; query=&quot;{?var}+{?getvar|0|intval}&quot; /&gt;\n</code></pre>\n\n	<h5>Multiplication table :-)</h5>\n\n<pre><code>&lt;txp:etc_query data=&quot;[1..9]&quot; break=&quot;tr&quot; wraptag=&quot;table&quot;&gt;\n&lt;txp:etc_query data=&quot;[1..9]&quot; break=&quot;td&quot;&gt;\n{{$*{?}}}\n&lt;/txp:etc_query&gt;\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<h5>Importing <span class=\"caps\">RSS</span> feeds</h5>\n\n<pre><code>&lt;txp:etc_query url=&quot;http://forum.textpattern.com/extern.php?type=RSS&amp;action=new&amp;fid=79&quot;\n  markup=&quot;xml&quot;\n  query=&quot;//channel/item[position()&lt;6]&quot;\n  wraptag=&quot;dl&quot;\n  label=&quot;News from Textpattern Forum&quot; labeltag=&quot;h4&quot;&gt;\n    &lt;dt&gt;&lt;a href=&quot;{link?}&quot;&gt;{title?}&lt;/a&gt;&lt;/dt&gt;\n    &lt;dd&gt;{description?}&lt;/dd&gt;\n&lt;/txp:etc_query&gt;\n</code></pre>\n\n	<p>More help and examples on <span class=\"caps\">TXP</span> Forum <a href=\"http://forum.textpattern.com/viewtopic.php?id=37932\">thread</a> or plugins <a href=\"http://www.iut-fbleau.fr/projet/etc/\">home</a>.</p>\n\n	<h4>History</h4>\n\n	<ul>\n		<li>Version 0.1: proof of concept, not publicly released.</li>\n		<li>Version 0.2: first release.</li>\n		<li>Version 0.3: fixed encoding.</li>\n		<li>Version 0.4: added <strong>remove</strong> and <strong>separator</strong> attributes.</li>\n		<li>Version 0.41: can also be used as single tag.</li>\n		<li>Version 0.5: fixed html markup, enhanced <strong>replace</strong> attribute.</li>\n		<li>Version 0.6: removed <strong>remove</strong> (merged with <strong>replace</strong>), introduced special patterns. The <strong>name is changed</strong> to <code>etc_query</code>.</li>\n		<li>Version 0.61: minor tweaks.</li>\n		<li>Version 0.7: added <strong>functions</strong>, <strong>limit</strong> and <strong>offset</strong>, enhanced <strong>replace</strong>.</li>\n		<li>Version 0.75: <span class=\"caps\">PHP</span> functions in <strong>replace</strong>.</li>\n		<li>Version 0.8: Now can also query databases and arrays/objects.</li>\n		<li>Version 0.82: introduced <strong>specials</strong> attribute.</li>\n		<li>Version 0.87: enhanced special patterns with default values.</li>\n		<li>Version 0.88: enhanced special patterns with sanitizer functions. Dropped <strong>preparse</strong> attribute.</li>\n		<li>Version 0.89: minor bugfixes.</li>\n		<li>Version 0.9: introduced <strong>parse</strong> and <strong>sanitize</strong> attributes.</li>\n		<li>Version 0.93: internal optimization.</li>\n		<li>Version 0.96: query enhancements.</li>\n		<li>Version 0.97: more robust parser.</li>\n		<li>Version 0.98: function patterns respect php scripting preferences / user levels.</li>\n		<li>Version 1.0: enhanced <strong>replace</strong> attribute, enabled by-reference passing in functions.</li>\n		<li>Version 1.1: even more enhanced <strong>replace</strong> attribute.</li>\n		<li>Version 1.2: if enabled on the server, <span class=\"caps\">XSL</span> transforms can be used in <strong>replace</strong> and <strong>content</strong> to format the output.</li>\n	</ul>', '// TXP 4.7 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_query\');\r\n	if(method_exists(\'\\Textpattern\\Tag\\Registry\', \'registerAttr\'))\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')->registerAttr(\'etc_replace\', \'etc-query, etc-replace\')->registerAttr(false, \'etc-break\');\r\n}\r\n\r\nfunction etc_replace($atts, $thing = \'\')\r\n{\r\n	$atts = lAtts(array(\r\n		\'etc-query\' => \'.\',\r\n		\'etc-replace\' => \'\',\r\n		\'etc-break\' => \'\',\r\n	), $atts, false);\r\n\r\n	$out = trim($thing) !== \'\' ? etc_query(array(\'data\' => $thing, \'query\' => $atts[\'etc-query\'], \'replace\' => $atts[\'etc-replace\'], \'break\' => $atts[\'etc-break\'], \'markup\' => \'html\', \'globals\' => \'\', \'specials\' => \'\', \'parse\' => \'\')) : $thing;\r\n	return $out;\r\n}\r\n\r\nfunction etc_query($atts, $thing=\'\') {\r\n	$php = php(null, \'echo 1;\') === \'1\';\r\n\r\n	global $etc_query, $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$safe_context = isset($etc_query_context) ? $etc_query_context : null;\r\n	$safe_globals = isset($etc_query_globals) ? $etc_query_globals : null;\r\n	$safe_sanitize = isset($etc_query_sanitize) ? $etc_query_sanitize : null;\r\n	$safe_specials = isset($etc_query_specials) ? $etc_query_specials : null;\r\n	static $safe_defaults = array(\r\n		\'url\' => \'\',\r\n		\'context\' => null,\r\n		\'globals\' => \'variable,pretext,thisarticle,thisimage,thisfile,thislink\',\r\n		\'sanitize\' => \'\',\r\n		\'functions\' => \'\'\r\n	),\r\n	$atts_defaults = array(\r\n		\'name\' => null,\r\n		\'save\' => null,\r\n		\'data\' => null,\r\n		\'url\' => \'\',\r\n		\'context\' => null,\r\n		\'markup\' => \'\',\r\n		\'encoding\' => \'UTF-8\',\r\n//		\'start\' => \'\',\r\n		\'query\' => null,\r\n		\'form\' => \'\',\r\n		\'populate\' => \'\',\r\n//		\'thorough\' => \'\',\r\n		\'replace\' => \'\',\r\n		\'separator\' => \';\',\r\n		\'argsep\' => \'|\',\r\n		\'globals\' => \'variable,pretext,thisarticle,thisimage,thisfile,thislink\',\r\n		\'specials\' => \'data,query\',\r\n		\'sanitize\' => \'\',\r\n		\'parse\' => \'auto\',\r\n		\'functions\' => \'\',\r\n		\'limit\' => null,\r\n		\'offset\' => \'0\',\r\n		\'label\' => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'wraptag\' => \'\',\r\n		\'break\' => \'\',\r\n		\'class\' => \'\',\r\n		\'html_id\' => \'\',\r\n		\'debug\' => \'\'\r\n	);\r\n\r\n	extract(lAtts($atts_defaults, $php ? $atts : $safe_defaults + $atts));\r\n\r\n	if($debug) {\r\n		$debug = do_list($debug);\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	// Grab the form or embedded $thing\r\n	$oldThing = $thing;\r\n	if($form && $form = fetch_form($form)) $thing = $content = $form;\r\n	else $content = isset($thing) ? EvalElse($thing, 1) : \'\';\r\n\r\n	$nodata = !$url && ($data === null);\r\n	@list($encoding, $enc_from) = explode(\':\', $encoding);\r\n\r\n	$etc_query_context = array(\'argsep\'=>$argsep, \'encoding\'=>$encoding, \'functions\'=>\'\', \'strip\'=>array(\'_COOKIE\', \'_GET\', \'_POST\', \'_REQUEST\'), \'php\'=>$php);\r\n\r\n	$etc_query_globals = $globals ? do_list($globals) : false;\r\n	$specialchars = is_callable(\'txpspecialchars\') ? \'txpspecialchars\' : \'htmlspecialchars\';\r\n	$etc_query_sanitize = array_fill_keys($etc_query_context[\'strip\'], $specialchars);\r\n	if($sanitize) foreach(do_list($sanitize) as $item) {\r\n		@list($key, $val) = explode(\':\', $item, 2);\r\n		if(!isset($val)) $etc_query_sanitize[$key] = $specialchars;\r\n		elseif(!$val || is_callable($val)) $etc_query_sanitize[$key] = $val;\r\n	}\r\n//	$etc_query_sanitize = $sanitize ? array_fill_keys(do_list($sanitize), true) : null;\r\n\r\n	if($specials) foreach(do_list($specials) as $special) if(!empty($$special)) {\r\n		$$special = preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $$special);\r\n	}\r\n\r\n	if($functions) {\r\n		$functions = do_list($functions);\r\n		$func = implode(\'|\', $functions);\r\n		if($query) $query = preg_replace(\'/\\b(\'.$func.\')\\s*\\(/\', \"php:function(\'$1\',\", $query);\r\n	}\r\n	$etc_query_context[\'functions\'] = $functions ? $func : \'\';\r\n\r\n	if($markup !== \'data\') $loaded = false;\r\n	elseif($data && isset($etc_query[$data])) {\r\n		$markup = $etc_query[$data]->markup;\r\n		if(isset($etc_query[$data]->dom)) $dom = &$etc_query[$data]->dom;\r\n		if(isset($etc_query[$data]->data)) $data = &$etc_query[$data]->data;\r\n		$loaded = true;\r\n	} else $data = \'\';\r\n\r\n	if($data === \'\' || $query === \'\') return etc_cleanup(parse($oldThing, false), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n\r\n	if(empty($markup)) if($data === null) $markup = $url ? \'\' : ($query !== null && $query[0] !== \'$\' ? \'xml\' : \'raw\');\r\n		else $markup = (strtolower(substr($data, 0, 7)) === \'select \' ? \'db\' : ($data[0] === \'[\' || $data[0] === \'{\' && ($data[1] === \'\"\' || $data[1] === \' \') ? \'json\' : ($url || $replace || strpos($data, \'<\') !== false ? \'html\' : \'raw\')));\r\n	else {\r\n		@list($markup, $pfx) = explode(\':\', $markup, 2);\r\n		if(substr($markup, -1) === \'*\') {$cdata = true; $markup = substr($markup, 0, -1);}\r\n		if($markup === \'db\' && strtolower(substr($data, 0, 7)) !== \'select \') {\r\n			switch($populate) {\r\n				case \'file\' : $fields = \'*\'; $filter = safe_pfx($populate).\" WHERE status >= 4 AND $data\"; break;\r\n				case \'image\' : case \'link\' : $fields = \'*\'; $filter = safe_pfx($populate).\' WHERE \'.$data; break;\r\n				default : $fields = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\';\r\n				if (is_callable(\'now\')) {$now_posted = now(\'posted\'); $now_expires = now(\'expires\');} else {$now_posted = $now_expires = \'NOW()\';}\r\n				$filter = safe_pfx(\'textpattern\').\" WHERE Status >= 4 AND Posted <= $now_posted AND (Expires IS NULL OR Expires >= $now_expires) AND $data\";\r\n				//if(empty($pfx)) $pfx = \'article\';\r\n			}\r\n			$data = \"SELECT $fields FROM $filter\";\r\n		}\r\n	}\r\n\r\n	if(!$php && $markup === \'db\') return etc_cleanup(parse($oldThing, false), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n\r\n	if(!empty($populate)) {\r\n		global ${\'this\'.$populate};\r\n		$thispopulate = isset(${\'this\'.$populate}) ? ${\'this\'.$populate} : null;\r\n		$format_info = $populate.($populate === \'file\' ? \'_download\' : \'\').\'_format_info\';\r\n		if($populate === \'article\') $column_map = article_column_map();\r\n	}\r\n\r\n	if($content !== \'\') {\r\n		if($parse === \'auto\') $parse = (strpos($content, \'<txp:\') !== false ? \'after\' : \'\');\r\n		elseif($parse === \'before\' || $parse === \'both\') $content = parse($content);\r\n		if(strpos($content, \'<xsl:\') !== false) {$xcontent = $content; $content = \'\';}\r\n	}\r\n	$parse = ($parse === \'after\' || $parse === \'both\');\r\n\r\n	$flat = ($markup !== \'html\' && $markup !== \'xml\' && !$replace && !isset($xcontent));//array mode\r\n	$query_array = null;//array();\r\n	if($flat) if($query !== null) \r\n	{\r\n		if($query[0] === \'$\') {\r\n			if($nodata) $data = $content;\r\n			$content = \'{\'.$query.\'}\';\r\n			$query = \'.\'; $query_array = array();\r\n		} else {\r\n			$query_array = etc_parse_xpath($query.\'?\', $flat);\r\n			if(isset($query_array[\'array\'])) $query_array = $query_array[\'array\'];\r\n		}\r\n	}\r\n	elseif($markup === \'db\')\r\n		if($content === \'\') {$query = \'*/*\'; $query_array = array(\'*\', \'*\');}\r\n\r\n	if($etc_query_globals && strpos($content.$replace, \'{?\') === false && strpos($content.$replace, \'{!\') === false) $etc_query_globals = false;\r\n	if($content !== \'\') $contents = etc_parse_string($content, $flat);\r\n	elseif($thing) $contents = array(\'text\' => \'\');\r\n	else $contents = array();\r\n\r\n	if($replace) if(strpos($replace, \'<xsl:\') !== false) {$xreplace = $replace; $replace = \'\';}\r\n	else {\r\n		$replace_array = array();\r\n		foreach(do_list($replace, $separator) as $pat) if($pat !== \'\') {\r\n			$replace_array[] = etc_parse_xpath($pat, $flat);\r\n		}\r\n		if($debug && in_array(\'r\', $debug)) {echo \'REPLACE\'; dmp($replace_array);}\r\n	}\r\n\r\n	if(!$loaded) {\r\n		if($url) if($markup === \'db\')\r\n			{\r\n				global $DB;\r\n				$mysqli = is_object($DB->link);\r\n				$result = $mysqli ? mysqli_select_db($DB->link, $url) : mysql_select_db($url);\r\n				$data = $result ? getRows($data) : null;\r\n				if($mysqli) mysqli_select_db($DB->link, $DB->db); else mysql_select_db($DB->db);\r\n			} elseif($markup === \'ldap\') {\r\n				$ldapconn = ldap_connect($url);\r\n//				ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);\r\n				if($ldapconn)\r\n				{\r\n					@list($ldaprdn, $ldapfilter, $ldaponly) = do_list($data, \';\');\r\n					if(!isset($ldapfilter)) $ldapfilter = \'objectClass=*\';\r\n					$ldaponly = (!empty($ldaponly) ? do_list($ldaponly) : array());\r\n					$data = ldap_search($ldapconn, $ldaprdn, $ldapfilter, $ldaponly);\r\n					$data = ldap_get_entries($ldapconn, $data);\r\n					ldap_close($ldapconn);\r\n				}\r\n				else $data = null;\r\n			} else {\r\n				$context = $context ? stream_context_create(json_decode($context, true)) : null;\r\n				$data = file_get_contents($url, false, $context);\r\n				if(!empty($http_response_header)) foreach($http_response_header as $header) if(preg_match(\'/^Content\\-Type\\:(.*)/i\', $header, $matches)) {\r\n					list($mime, $opt) = explode(\';\', $matches[1]);\r\n					list($type, $subtype) = explode(\'/\', strtolower(trim($mime)));\r\n					if(!$markup && in_array($subtype, array(\'json\', \'html\', \'xml\'))) $markup = $subtype;\r\n					if(preg_match(\'/\\bcharset=([\\w\\-#]+)/i\', $opt, $matches)) $enc = strtoupper($matches[1]);\r\n					break;\r\n				}\r\n				if($encoding && $data) {\r\n					$encoding = strtoupper($encoding);\r\n					if(isset($enc_from) && $enc_from !== \'*\') $enc = strtoupper($enc_from);\r\n					elseif(!isset($enc) && preg_match(\'/<meta\\b[^>]+\\bcharset=\"?([\\w\\-#]+)\"?[^>]*>/i\', $data, $matches)) $enc = strtoupper($matches[1]);\r\n					if(is_callable(\'mb_detect_encoding\')) {\r\n						if(!isset($enc)) $enc = strtoupper(mb_detect_encoding($data)); //else dmp($enc);\r\n						$mb_encodings = $enc ? array_map(\'strtoupper\', mb_list_encodings()) : array();\r\n						$mb_encodings = in_array($encoding, $mb_encodings) && in_array($enc, $mb_encodings);\r\n					}\r\n					else $mb_encodings = false;\r\n					if($enc && ($enc !== $encoding || isset($enc_from)))\r\n						if($mb_encodings) $data = mb_convert_encoding($data, $encoding, $enc);\r\n						elseif(is_callable(\'iconv\')) $data = iconv($enc, $encoding.\'//IGNORE\', $data);\r\n				}\r\n			}\r\n\r\n		if($markup === \'json\') if(preg_match(\'/^\\[(\\-?\\w+)\\.\\.(\\-?\\w+):?([\\+\\-]?)(\\d*)\\]$/\', $data, $matches)) switch($matches[3]) {\r\n				case \'+\' : if($matches[1] <= $matches[2]) $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1); else $data = null; break;\r\n				case \'-\' : if($matches[1] >= $matches[2]) $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1); else $data = null; break;\r\n				default : $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1);\r\n			} else $data = json_decode($data, true); //array\r\n		elseif($markup === \'array\') $data = unserialize($data);\r\n		elseif($markup === \'list\') {\r\n			$data = array_filter(do_list($data, isset($pfx) ? $pfx : \',\'), function($a) {return (string)$a !== \'\';});\r\n			if(isset($pfx)) unset($pfx);\r\n		} elseif($markup === \'db\' && !$url /*&& $data*/) {\r\n			$data = getRows($data);\r\n		} elseif(!$markup) $markup = \'html\';\r\n	}\r\n	elseif(!$flat && !isset($dom)) {$loaded = false;}\r\n\r\n	$is_html = $markup === \'html\';\r\n	$is_xml = $markup === \'xml\';\r\n	$flat = $flat && !$is_html && !$is_xml;\r\n\r\n	if($debug) {\r\n		$endtime = microtime(TRUE);\r\n		dmp(\"Preloaded in \" . ($endtime - $starttime) . \" seconds.\\n\");\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	if(!$nodata && empty($data)) $roots = null;\r\n	elseif($flat) {$roots = array($data ? $data : \'\'); $xpath = null;}\r\n	else {\r\n		if(!$loaded) {\r\n			/*** load the data into a new dom object ***/\r\n			$dom = new DOMDocument;\r\n			$dom->preserveWhiteSpace = false;\r\n			$dom->strictErrorChecking = false;\r\n			$dom->encoding = $encoding;\r\n			if(!$debug) libxml_use_internal_errors(true);\r\n\r\n			if(isset($cdata) && $data) $data = str_ireplace(array(\'<![CDATA[\', \']]>\'), \'<!---->\', $data);\r\n			if(isset($pfx) && $pfx === \'\') $data = str_replace(\' xmlns=\', \' etcns=\', $data);\r\n\r\n			if($is_html) {\r\n//				if($url) $dom->loadHTML(preg_replace(\'/<head.*>/Usi\', \'\\\\0<meta http-equiv=\"content-type\" content=\"text/html; charset=\' . $encoding . \'\">\', $data));\r\n				if($url) $dom->loadHTML(\'<meta http-equiv=\"content-type\" content=\"text/html; charset=\' . $encoding . \'\">\' . $data);\r\n				else $dom->loadHTML(\'<?xml encoding=\"\'.$encoding.\'\">\'.$data);\r\n			}\r\n			elseif($is_xml) {if(!$nodata) $dom->loadXML($data, LIBXML_COMPACT);}\r\n			elseif($data) if(!is_array($data) && !is_object($data)) $dom->appendChild($dom->createElement(\'root\', (string)$data)); else {\r\n				foreach ($data as $key => $val) $dom->appendChild(etc_create_element($key, $val, $dom));\r\n				$is_xml = true;\r\n			}\r\n		}\r\n\r\n		if((isset($xcontent) || isset($xreplace)) && class_exists(\'XSLTProcessor\')) {\r\n			$xslt = new DOMDocument;\r\n			$proc = new XSLTProcessor;\r\n			if($functions) $proc->registerPHPFunctions($functions);\r\n			if(isset($xreplace)) {\r\n				$xslt->loadXML($xreplace);\r\n				$proc->importStyleSheet($xslt);\r\n				$dom = $proc->transformToDoc($dom);\r\n			}\r\n			if(isset($xcontent)) {\r\n				$xslt->loadXML($xcontent);\r\n				$proc->importStyleSheet($xslt);\r\n				return etc_cleanup($proc->transformToXML($dom), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n			}\r\n		}\r\n\r\n		$xpath = new DOMXpath($dom);\r\n		if($functions) {\r\n			$xpath->registerNamespace(\'php\', \'http://php.net/xpath\');\r\n			$xpath->registerPHPFunctions($functions);\r\n		}\r\n		if(!empty($pfx)) {\r\n			$xpath->registerNamespace($pfx, $dom->lookupNamespaceUri($dom->namespaceURI));\r\n		}\r\n\r\n		if(empty($start)) $start = ($is_html ? \'/html/body\' : \'/\');\r\n		$roots = $xpath->query($start);\r\n\r\n		if($debug) {if(in_array(\'h\', $debug)) {echo $markup; dmp($is_xml? $dom->saveXML() : $dom->saveHTML());}}\r\n		else libxml_clear_errors();\r\n	}//dom\r\n\r\n	if(empty($contents)) {//$thing === null\r\n		$path = $flat || $query !== null || $is_xml ? \'.\' : \'node()\';\r\n		$content = $path !== \'.\';\r\n		$contents = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => $path, \'mode\' => $flat ? \'?\' : \'.\', \'value\' => array(), \'array\' => array())));\r\n	} else $content = true;\r\n	if($query === null) $query = ($is_xml && !$nodata ? \'node()\' : \'.\');\r\n	$out = array();\r\n	$etc_query_context[\'xpath\'] = &$xpath;\r\n	$etc_query_context[\'xml\'] = $is_xml;\r\n\r\n	if($debug) {\r\n		$endtime = microtime(TRUE);\r\n		dmp(\"Loaded in \" . ($endtime - $starttime) . \" seconds. $flat\\n\");\r\n		if(in_array(\'d\', $debug)) {echo \'DATA \', $markup; dmp($data);}\r\n		if(in_array(\'q\', $debug)) {echo \'QUERY\'; dmp($query); dmp($query_array);}\r\n		if(in_array(\'c\', $debug)) {echo \'CONTENTS\'; dmp($contents);}\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	if(!empty($roots)) {\r\n		foreach($roots as $root) /*if(!$thorough || $xpath->query($root->getNodePath())->length)*/ {\r\n			// Ugly hack to detect if the node is removed\r\n			$elements = $internals = array();\r\n\r\n			if(!$flat) {\r\n				$nodes = $xpath->evaluate($query, $root);\r\n				if (!($nodes instanceof DOMNodeList)) {\r\n					$internals[\'{?}\'] = $nodes;\r\n					if($nodes) {\r\n						$newout = !$content ? $nodes : (isset($contents[\'text\']) ? $contents[\'text\'] : etc_evaluate_string($contents, $root, $internals, $flat));\r\n						$out[] = $parse ? parse($newout) : $newout;\r\n					}\r\n					continue;\r\n				}\r\n				foreach($nodes as $element) $elements[] = $element;\r\n			}// or $root = $data\r\n			elseif($query === \'*\' || $query_array === null) $elements = is_array($root) ? $root : (array) $root;\r\n			elseif(empty($query_array)) $elements = array($root);//query = \".\" or \"/\"\r\n			else {\r\n				$query_array = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => \'.\', \'mode\' => \'?\', \'array\' => $query_array, \'value\' => array())));\r\n				$elements = etc_evaluate_string($query_array, $root, $internals, 2);\r\n			}\r\n\r\n			$nums = $num_rows = count($elements);\r\n			$offnum = ($offset < 0 ? $nums + $offset : $offset);\r\n			if($offset || $limit) {\r\n				$elements = array_slice($elements, $offset, $limit, true);\r\n				$num_rows = count($elements);\r\n			}\r\n\r\n			if(isset($etc_query_specials[\'{#rows}\'])) $internals[\'{#rows}\'] = $num_rows;\r\n			if(isset($etc_query_specials[\'{#total}\'])) $internals[\'{#total}\'] = $nums;\r\n			$num = 0;\r\n\r\n			if(!empty($elements)) foreach($elements as $row => &$element) {\r\n				if(!empty($format_info)) {\r\n					global ${\'this\'.$populate};//damn doArticles()!\r\n					if($flat) {\r\n						if($populate === \'article\') foreach ($column_map as $key => $column)\r\n							$thisarticle[$key] = isset($element[$column]) ? $element[$column] : \'\';//article_format_info($element);\r\n						else ${\'this\'.$populate} = $format_info($element);\r\n					} elseif($populate === \'article\') {\r\n						foreach ($column_map as $key => $column) {\r\n							$nodes = $xpath->query($is_xml ? $column : strtolower($column), $element);\r\n							$thisarticle[$key] = $nodes->length ? $nodes->item(0)->nodeValue : \'\';\r\n						}\r\n					}\r\n					${\'this\'.$populate}[\'is_first\'] = ($num == 0);\r\n					${\'this\'.$populate}[\'is_last\'] = ($num+1 == $num_rows);\r\n				}\r\n\r\n				if(isset($etc_query_specials[\'{#row}\'])) $internals[\'{#row}\'] = $num+1;\r\n				if(isset($etc_query_specials[\'{#ind}\'])) $internals[\'{#ind}\'] = $num+$offnum;\r\n				if(isset($etc_query_specials[\'{?}\'])) $internals[\'{?}\'] = ($flat ? $element : $element->nodeValue);\r\n				if(isset($etc_query_specials[\'{#}\'])) $internals[\'{#}\'] = ($flat ? $row : $element->nodeName);\r\n\r\n				// Replace/remove values\r\n				if($replace) foreach($replace_array as $val) {\r\n					$nodes = ($val[\'path\'] === \'.\' ? array($element) : $xpath->query($internals ? strtr($val[\'path\'], $internals) : $val[\'path\'], $element));\r\n					$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n					if($nodes) foreach($nodes as $node) /*if(!$thorough || $xpath->query($node->getNodePath())->length)*/ try\r\n					{\r\n						if($ind) $internals[\'{##}\'] = $ind++;\r\n						etc_process_node($val[\'value\'], $node, $internals, false, true);\r\n					} catch(DOMException $e) {if($debug) echo $e->getMessage();}\r\n				}\r\n\r\n				// Construct the replacement arrays\r\n				if(isset($etc_query_specials[\'{##}\'])) $internals[\'{##}\'] = $num+1;\r\n\r\n				$newout = !$content && !$sanitize\r\n					? ($flat ? $element : ($is_html ? $dom->saveHTML($element) : $dom->saveXML($element)))\r\n					: (isset($contents[\'text\']) ? $contents[\'text\'] : etc_evaluate_string($contents, $element, $internals, $flat));\r\n\r\n				$out[] = $parse ? parse($newout) : $newout;\r\n				$num++;\r\n			}\r\n			unset($element, $elements);\r\n		}\r\n	}\r\n\r\n	if(!empty($populate)) ${\'this\'.$populate} = $thispopulate;\r\n	if($save) {\r\n		if(!$flat) $etc_query[$save]->dom = &$dom; else $etc_query[$save]->data = &$data;\r\n		$etc_query[$save]->markup = ($is_xml ? \'xml\' : $markup);\r\n	}\r\n	if ($out) if(!$save || $name !== null) {\r\n		// Make up the final output\r\n		$finalout = ($label ? doLabel($label, $labeltag) : \'\').($wraptag || $break ? doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id) : implode($out));\r\n	} else $finalout = \'\';\r\n	else $finalout = parse($oldThing, false);//nothing found\r\n\r\n	etc_cleanup(null, $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n	$endtime = microtime(TRUE);\r\n	if($debug) dmp(\"Processed in \" . ($endtime - $starttime) . \" seconds.\\n\");\r\n	if($debug && in_array(\'o\', $debug)) dmp($finalout);\r\n	if($name) {global $variable; $variable[$name] = &$finalout;}\r\n	return $name ? \'\' : $finalout;\r\n}\r\n\r\n/************************** PARSE *************************/\r\n\r\nfunction etc_parse_string($content, &$flat) {\r\n	if(strpos($content, \'{\') === false) return array(\'text\' => $content);\r\n	global $etc_query_specials, $etc_query_context;\r\n//	$preg_string = \'/(\\{(?![\\s\"])(?:[^{}]|(?1))+\\})/U\';\r\n\r\n	$contents = array();\r\n	$contents_array = preg_split(\'/(\\{(?>[^{}]|(?1))+\\})/U\', $content, null, PREG_SPLIT_DELIM_CAPTURE);\r\n	$num = count($contents_array);//2n+1\r\n	$text = $contents_array[0];\r\n	for($i = 1; $i < $num; $i+=2) {\r\n		$string = substr($contents_array[$i], 1, -1);\r\n		if(strpos(\'{ \"\', $string[0]) !== false) {\r\n			$l = 0; while($string[$l] === \'{\') $l++;\r\n			$cstring = etc_parse_string($l ? substr($string, $l, -$l) : $string, $flat);\r\n			$l = max(1, $l);\r\n			if(isset($cstring[\'text\'])) $text .= str_repeat(\'{\', $l).$cstring[\'text\'].str_repeat(\'}\', $l);\r\n			else {$contents[] = array(\'type\' => \'string\', \'text\' => $text, \'value\' => $cstring, \'level\' => $l); $text = \'\';}\r\n		}\r\n		elseif($string[0] === \'?\' && strlen($string) > 1) $text .= etc_parse_variable(array($contents_array[$i]));\r\n		else {\r\n			switch($string[0]) {\r\n				case \'$\' : $value = etc_parse_function(substr($string, 1), $flat);\r\n				if($value && $value[0][\'func\'] === \'@\') {\r\n					$contents[] = array(\'type\' => \'define\', \'text\' => \'\', \'value\' => $value[0][\'args\'][0][0][\'value\']);\r\n					$value = array_slice($value, 1);\r\n				}\r\n				$contents[] = array(\'type\' => \'func\', \'text\' => $text, \'value\' => $value); $text = \'\'; break;\r\n				case \'#\' : case \'?\' : $contents[] = $etc_query_specials[$contents_array[$i]] = array(\'type\' => \'internal\', \'text\' => $text, \'value\' => $string); $text = \'\'; break;\r\n				case \'%\' : $text .= substr($string, 1); break;\r\n				case \'!\' : $parts = explode(\'|\', $string, 3);\r\n				if(count($parts) > 2) {$string = $parts[0].\'|\'.$parts[1]; $func = etc_parse_function($parts[2], $flat);}\r\n				else $func = null;\r\n				$contents[] = $func ? array(\'type\' => \'func\', \'text\' => $text, \'value\' => $func, \'var\' => array(\'{\'.$string.\'}\')) : array(\'type\' => \'var\', \'text\' => $text, \'value\' => array($contents_array[$i])) ; $text = \'\'; break;\r\n				default : $contents[] = array(\'type\' => \'xpath\', \'text\' => $text, \'value\' => etc_parse_xpath($string, $flat)); $text = \'\';\r\n			}\r\n		}\r\n		$text .= $contents_array[$i+1];\r\n	}\r\n	if($text > \'\') $contents[] = array(\'type\' => \'text\', \'text\' => $text);\r\n	if(count($contents) === 1 && $contents[0][\'type\'] === \'text\') $contents = array(\'text\' => $contents[0][\'text\']);\r\n	return $contents;\r\n}\r\n\r\nfunction etc_parse_variable($val) {\r\n	global $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$pat = $val[0];\r\n	if(isset($etc_query_specials[$pat])) return $etc_query_specials[$pat];\r\n	@list($vars, $def, $format) = explode(\'|\', substr($pat, 2, -1), 3);\r\n	if($vars === \'\' || !empty($etc_query_globals)) foreach(explode(\',\', $vars) as $var)\r\n		if($var !== \'\') {foreach($etc_query_globals as $global) if(isset($GLOBALS[$global][$var]) && $GLOBALS[$global][$var] !== \'\')\r\n			{$value = $GLOBALS[$global][$var]; break 2;}}\r\n		else {$value = (isset($def) ? $def : \'\'); $global = null;}\r\n	if(!isset($value)) $value = (isset($def) ? $def : \'\');\r\n	else {\r\n		if(MAGIC_QUOTES_GPC && $global && in_array($global, $etc_query_context[\'strip\'])) $value = doStrip($value);\r\n		if(!empty($format)) {\r\n			$flat = true; //$internals = null;\r\n			if ($format[0] === \'#\') {\r\n				$format = ltrim($format, \'#.\'); $value = isset($var) ? $var : $value;\r\n			}\r\n			$format = array(array(\'type\' => \'func\', \'text\' => \'\', \'value\' => etc_parse_function($format, $flat)));\r\n			$value = etc_evaluate_string($format, $value, null);\r\n		}\r\n		elseif(!empty($etc_query_sanitize[$global])) $value = $etc_query_sanitize[$global]($value);\r\n		elseif(!isset($etc_query_sanitize[$global]) && !empty($etc_query_sanitize[\'*\'])) $value = $etc_query_sanitize[\'*\']($value);\r\n	}\r\n	if($pat[1] === \'?\') $etc_query_specials[$pat] = $value;\r\n	return $value;\r\n}\r\n\r\nfunction etc_parse_function($format, &$flat) {\r\n	global $etc_query_context;\r\n\r\n	$pipe = $etc_query_context[\'argsep\'];\r\n	$res = $rep = array();\r\n	if(strpos($format, \'{\') !== false) {\r\n		$format = preg_split(\'/(\\{(?>[^{}]|(?1))*\\})/U\', $format, null, PREG_SPLIT_DELIM_CAPTURE);//2n+1\r\n		$count = count($format);\r\n		for($i=1; $i<$count; $i+=2) {$rep[\'{\'.$i.\'}\'] = $format[$i]; $format[$i] = \'{\'.$i.\'}\'; }\r\n		$format = implode($format);\r\n	}\r\n//\'/([^\\(\\)\\.]*(?:\\([^\\(\\)]*\\))?)(?:\\.|$)/U\'\r\n	if($format[0] === \'.\') $format = \'~\'.$format;\r\n	$format = preg_split(\'/([^\\(\\)\\.]*)(\\((?>[^\\(\\)]|(?2))*\\))?(?:\\.|$)/\', $format, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);\r\n\r\n	foreach($format as $i => $form) {\r\n		if($form[0] !== \'(\') {//func\r\n			if(isset($func)) $res[] = array(\'func\'=>$func, \'ref\'=>$ref, \'args\'=>$args);\r\n			$func = ltrim($form);\r\n			$ref = $func[0] === \'@\';\r\n			if($ref) $func = substr($func, 1);\r\n			$args = array(array(\'text\' => \'$\'));\r\n			if($func !== \'\') switch($func[0]) {\r\n				case \'+\' : case \'*\' : case \'-\' : case \'/\' : case \'%\' : case \'^\' :\r\n				if(strlen($func) > 1) $args[] = array(\'text\' => floatval(substr($func, 1))); $func = $func[0]; break;\r\n				case \'~\' : if(strlen($func) > 1) $args[] = array(\'text\' => substr($func, 1)); $func = $func[0]; break;\r\n				case \'=\' : case \'>\' : case \'<\' : case \'?\' : if(strlen($func) > 1) $args[] = array(\'text\' => ltrim(substr($func, 1))); $func = $func[0]; break;\r\n				case \'$\' : if($i) {$args[] = array(\'text\' => $func === \'$\' ? \'\' : $func); $func = \'$\';}\r\n				else {$args = array(array(\'text\' => \'\'), array(\'text\' => $func)); $func = \'~\';}\r\n				break;\r\n				default : $func = trim($func);\r\n				if(!$etc_query_context[\'php\'] && !in_array($func, array(\'date\', \'textile\'))) $func = \'\';\r\n				if($func === \'textile\') {\r\n					$func = \'Textile->TextileThis\';\r\n				}\r\n				if (strpos($func, \'::\')) $func = explode(\'::\', $func);\r\n				elseif(strpos($func, \'->\')) {\r\n					$func = explode(\'->\', $func);\r\n					if(strtolower($func[0]) === \'textile\') include_once(txpath.\'/lib/classTextile.php\');\r\n					if (class_exists($func[0])) $func[0] = new $func[0];\r\n				}\r\n				if(!is_callable($func)) unset($func, $ref);\r\n			} else unset($func, $ref);\r\n		} else {//args\r\n			$args_list = substr($form, 1, -1);\r\n			if(!isset($func)) {\r\n				if(!$i) {\r\n					$func = \'@\'; $ref = false;\r\n					$args = array(array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => etc_parse_xpath($args_list, $flat))));\r\n				}\r\n				continue;\r\n			}\r\n			if($func !== \'?\' && count($args) < 2) $args = array();\r\n			if($args_list !== \'\') foreach(explode($pipe, $args_list) as $arg) $args[] = $rep ? etc_parse_string(strtr($arg, $rep), $flat) : array(\'text\' => $arg);\r\n			if($func === \'?\') $args = array_slice($args, -3);\r\n		}\r\n	}\r\n	if(isset($func)) $res[] = array(\'func\'=>$func, \'ref\'=>$ref, \'args\'=>$args);\r\n	return $res;\r\n}\r\n\r\nfunction etc_parse_xpath($pattern, &$flat) {\r\n	global $etc_query_context, $etc_query_globals;\r\n\r\n	$func = $etc_query_context[\'functions\'];\r\n\r\n	@list($path, $val) = explode(\'@@\', $pattern, 2);\r\n\r\n	if(isset($val)) {\r\n		$pairs = preg_split(\'/((?>[^\\[\\](){}]|[\\[\\(\\{](?1)*[\\]\\)\\}])+)@/U\', $val, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);\r\n		$flat = false;\r\n		$val = array();\r\n		foreach($pairs as $pair) {\r\n			@list($name, $aval) = explode(\'=\', $pair, 2);\r\n			if($name > \'\') $val[$name] = $aval;\r\n		}\r\n	} else {\r\n		@list($path, $val) = preg_split(\'/^((?>[^\\[\\](){}]|[\\[\\(\\{](?1)*[\\]\\)\\}])+)=(.*)$/sDU\', $pattern, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);\r\n		if(!isset($val) && strlen($path) < strlen($pattern)) $val = \'\';\r\n	}\r\n\r\n	if($path !== \'\' && strpos(\':?#+^$~&/\', substr($path, -1)) !== false) {\r\n		$mode = substr($path, -1); $path = substr($path, 0, -1);\r\n	} else $mode = \'.\';\r\n	if($mode !== \'?\') $flat = false;\r\n	if($path === \'\') $path = \'.\';\r\n	elseif($etc_query_globals) $path = preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $path);\r\n	if(isset($val) && !is_array($val)) $val = array($mode => $val);\r\n\r\n	$attr_array = array();\r\n\r\n	if(!empty($val)) foreach($val as $attr=>$value) if($attr !== \'&\') {\r\n		$rep[\'attr\'] = $etc_query_globals ? preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $attr) : $attr;\r\n		$rep[\'value\'] = $value === null ? null : etc_parse_string($value, $flat);\r\n		$attr_array[] = $rep;\r\n//		unset($rep);\r\n	} elseif($value !== null) {$last[\'attr\'] = \'&\'; $last[\'value\'] = etc_parse_string($value, $flat);}\r\n	if(isset($last)) $attr_array[] = $last;\r\n	unset($pattern);\r\n	$pattern[\'path\'] = $func ? preg_replace(\'/\\b(\'.$func.\')\\s*\\(/\', \"php:function(\'$1\',\", $path) : $path;\r\n	$pattern[\'mode\'] = $mode;\r\n	$pattern[\'value\'] = $attr_array;\r\n\r\n	if($flat && $path !== \'/\') if(strpos($path, \'//\') !== false) $flat = false; else {\r\n		$matches = explode(\'/\', $path);\r\n		foreach($matches as $match) if($match !== \'*\' && $match !== \'.\' && preg_match(\'/[\\(\\)\\[\\]\\.:@]/\', $match)) {\r\n			$flat = false; break;\r\n		}\r\n		if($flat) $pattern[\'array\'] = array_diff($matches, array(\'.\'));\r\n	}\r\n	return $pattern;\r\n}\r\n\r\n/************************** EVALUATE *************************/\r\n\r\nfunction etc_evaluate_string(&$contents, &$element, $internals, $flat=true) {\r\n	if(isset($contents[\'text\'])) return $contents[\'text\'];\r\n	global $etc_query_context, $etc_query_sanitize, $etc_query_specials;\r\n\r\n	$wrap = $flat === 2;\r\n	$res = $wrap ? array() : \'\';\r\n	$defines = array();\r\n	if(!$flat) {\r\n		$is_xml = $etc_query_context[\'xml\'];\r\n		$dom = $element->ownerDocument;\r\n	}\r\n\r\n	foreach($contents as $content) {\r\n		if(!$wrap) $res .= $content[\'text\'];\r\n		switch($content[\'type\']) {\r\n			case \'text\' : break;\r\n\r\n			case \'internal\' : $res .= (isset($internals[\'{\'.$content[\'value\'].\'}\']) ? $internals[\'{\'.$content[\'value\'].\'}\'] : $content[\'value\']); break;\r\n\r\n			case \'var\' : $res .= etc_parse_variable($content[\'value\']); break;\r\n\r\n			case \'string\' : $l = !empty($content[\'level\']) ? $content[\'level\'] : 0;\r\n			$res .= str_repeat(\'{\', $l).etc_evaluate_string($content[\'value\'], $element, $internals, $flat).str_repeat(\'}\', $l); break;\r\n\r\n			case \'func\' : if(!empty($undefined)) {unset($undefined); break;}\r\n			$val = isset($content[\'var\']) ? etc_parse_variable($content[\'var\']) : (isset($define) ? $define : ($flat ? $element : $element->nodeValue));\r\n			unset($define);\r\n			if(empty($content[\'value\'])) {$res .= $val; break;}\r\n			foreach($content[\'value\'] as $form) {\r\n				$func = $form[\'func\'];\r\n				$ref = $form[\'ref\'];\r\n				$args = array();\r\n				if($has_args = count($form[\'args\'])) {\r\n					foreach($form[\'args\'] as $key => $arg) {\r\n						if(!$key || $func !== \'?\' || $key === ($args[0] ? 1 : 2))\r\n							$arg = isset($arg[\'text\']) ? $arg[\'text\'] : etc_evaluate_string($arg, $element, $internals, $flat);\r\n						else $arg = \'\';\r\n						if($arg === \'$\') $args[] =& $val;\r\n						elseif($arg && $arg[0] === \'$\' && isset($defines[$arg])) $args[] =& $defines[$arg];\r\n						else $args[] = $arg;\r\n					}\r\n				}\r\n				switch($func) {\r\n					case \'$\' : if($args[1] !== \'\') $defines[$args[1]] = $val; else $val = \'\'; break; //$args[0]=\'$\'\r\n					case \'~\' : $val = is_array($args[0]) ? implode($has_args > 1 ? $args[1] : \'\', $args[0]) : implode($args); break;\r\n					case \'+\' : $val = array_sum($args); break;\r\n					case \'*\' : $val = array_product($args); break;\r\n					case \'-\' : $val = 2*$args[0] - array_sum($args); break;\r\n					case \'/\' : $val = $args[0] ? $args[0]*$args[0] / array_product($args) : 0; break;\r\n					case \'%\' : $val = $args[0] % $args[1]; break;\r\n					case \'^\' : $val = pow($args[0], $args[1]); break;\r\n					case \'?\' : switch($has_args) {\r\n						case 0 : break;\r\n						case 1 : $val = ($args[0] ? \'1\' : \'0\'); break;\r\n						default : $val = !empty($args[0]) ? $args[1] : (isset($args[2]) ? $args[2] : \'\');\r\n					} break;\r\n					case \'=\' : switch($has_args) {\r\n						case 0 : case 1 : break;\r\n						case 2 : $val = ($args[0] == $args[1] ? \'1\' : \'0\'); break;\r\n						default : $val = count(array_unique($args))<=1 ? \'1\' : \'0\';\r\n					} break;\r\n					case \'<\' : case \'>\' : if($has_args > 1) {\r\n						$val = ($func === \'<\' ? ($args[0] < $args[1] ? \'1\' : \'0\') : ($args[0] > $args[1] ? \'1\' : \'0\'));\r\n					} break;\r\n					case \'date\' : if($has_args) $val = date($args[0], strtotime(isset($args[1]) ? $args[1] : $val)); break;\r\n//					case \'escape\' : $val = doSlash($val); break;\r\n					default : $newval = call_user_func_array($func, $args);\r\n					if(!$ref) $val = $newval;\r\n				}\r\n			}\r\n			$res .= $val; break;\r\n\r\n			default : if(!$flat) {\r\n				$value = \'\';\r\n				$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n				$nodes = $etc_query_context[\'xpath\']->evaluate($internals ? strtr($content[\'value\'][\'path\'], $internals) : $content[\'value\'][\'path\'], $element);\r\n				if (!($nodes instanceof DOMNodeList)) $value = $nodes;\r\n				else foreach($nodes as $node)\r\n				{\r\n					if($ind) $internals[\'{##}\'] = $ind++;\r\n					$out = empty($content[\'value\'][\'value\']) ? false : etc_process_node($content[\'value\'][\'value\'], $node, $internals, false);\r\n					$value .= $out ? $out : ($content[\'value\'][\'mode\'] === \'?\' ? $node->nodeValue : ($content[\'value\'][\'mode\'] === \'#\' ? $node->nodeName : (!$is_xml ? $dom->saveHTML($node) : $dom->saveXML($node))));\r\n				}\r\n				if($content[\'type\'] !== \'define\') $res .= $value; else $define = $value;\r\n			} else {//flat\r\n				$value = $element; $chunk=\'\';\r\n				if(!empty($content[\'value\'][\'array\'])) foreach($content[\'value\'][\'array\'] as $key => $chunk)\r\n				{\r\n					if($chunk !== \'*\')\r\n						if(isset($value[$chunk])) $value = $value[$chunk];\r\n						else {unset($value); break;}\r\n					else {\r\n						$query = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => \'.\', \'mode\' => \'?\', \'array\' => array_slice($content[\'value\'][\'array\'], $key + 1), \'value\' => $content[\'value\'][\'value\'])));\r\n						$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n						foreach($value as $k => $val) {\r\n							if(isset($etc_query_specials[\'{#}\'])) $internals[\'{#}\'] = $k;\r\n							if($ind) $internals[\'{##}\'] = $ind++;\r\n							$newres = etc_evaluate_string($query, $val, $internals, $flat);\r\n							if($wrap) foreach($newres as $row) $res[] = $row;\r\n							else $res .= $newres;\r\n						}\r\n						unset($value); break;\r\n					}\r\n				}\r\n				if(isset($value)) {\r\n					if(!empty($content[\'value\'][\'value\'])) etc_process_node($content[\'value\'][\'value\'], $value, $internals);\r\n					elseif(!$wrap)\r\n						if(isset($etc_query_sanitize[$chunk.\'?\'])) {if($etc_query_sanitize[$chunk.\'?\']) $value = $etc_query_sanitize[$chunk.\'?\']($value);}\r\n						elseif(!empty($etc_query_sanitize[\'?\'])) $value = $etc_query_sanitize[\'?\']($value);\r\n					if($content[\'type\'] !== \'define\') if($wrap) $res[] = $value; else $res .= $value;\r\n					else $define = $value;\r\n				} elseif($content[\'type\'] === \'define\') $undefined = true;\r\n			}\r\n		}\r\n	}\r\n	return $res;\r\n}\r\n\r\nfunction etc_process_node(&$attr, &$node, $internals=null, $flat=true, $remove=false) {\r\n	if($flat) {\r\n		if(empty($attr) && $remove) $node = null;\r\n		else foreach($attr as $val) if($val[\'attr\'] === \'.\' || $val[\'attr\'] === \'?\') {\r\n			if(isset($internals[\'{?}\'])) $internals[\'{?}\'] = $node;\r\n			$node = etc_evaluate_string($val[\'value\'], $node, $internals, $flat);\r\n		}\r\n		return;\r\n	}\r\n\r\n	global $etc_query_context;\r\n\r\n//	$self_path = array(\'.\', \'?\', \'#\', \'+\', \'^\', \'$\', \'~\', \'&\', \'/\');\r\n	$is_attr = ($node->nodeType === XML_ATTRIBUTE_NODE);\r\n	if(empty($attr) && $remove) {\r\n		if($is_attr) $node->ownerElement->removeAttributeNode($node);\r\n		else $node->parentNode->removeChild($node);\r\n	}\r\n	else foreach($attr as $val) {\r\n//		unset($name);\r\n		$self = strpos(\'.?#+^$~&/\', $val[\'attr\']) !== false;//in_array($val[\'attr\'], $self_path);\r\n		if(!isset($val[\'value\'])) {if(!$self && !$is_attr) $node->removeAttribute($val[\'attr\']);}\r\n		else {\r\n			if(isset($internals[\'{?}\'])) $internals[\'{?}\'] = $self || $is_attr ? $node->nodeValue : $node->getAttribute($val[\'attr\']);\r\n			if(isset($internals[\'{#}\'])) $internals[\'{#}\'] = $self || $is_attr ? $node->nodeName : $val[\'attr\'];\r\n			$value = isset($val[\'value\'][\'text\']) ? $val[\'value\'][\'text\'] : etc_evaluate_string($val[\'value\'], $node, $internals, $flat);\r\n			switch($val[\'attr\']) {\r\n				case \'&\' : if(!$remove) return $value;\r\n				case \'.\' : case \'+\' : case \'^\' : case \'$\' : if(!$is_attr) {\r\n					if(strpos($value, \'<\') === false)\r\n						if($val[\'attr\'] !== \'.\') $f = $node->ownerDocument->createTextNode($value);\r\n						else {$node->nodeValue = $value; break;}\r\n					else {\r\n						$encoding = $etc_query_context[\'encoding\'];\r\n						$is_xml = $etc_query_context[\'xml\'];\r\n						if(!$is_xml) $value = preg_replace(\'/<((?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\\b[^>]*)(?<!\\/)>/iU\', \'<$1 />\', $value);\r\n						if(!$is_xml && strpos($value, \'&\') !== false) {\r\n							if(!isset($etc_query_context[\'decode\'])) $etc_query_context[\'decode\'] = array_flip(array_diff(get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, $encoding), get_html_translation_table(HTML_SPECIALCHARS, ENT_COMPAT, $encoding)));\r\n							$value = strtr($value, $etc_query_context[\'decode\']);\r\n						}\r\n						$f = $node->ownerDocument->createDocumentFragment();\r\n						$f->appendXML($value);\r\n					}\r\n					if($val[\'attr\'] === \'.\') $node->nodeValue = null;\r\n					if($val[\'attr\'] === \'&\') $node->parentNode->replaceChild($f, $node);\r\n					elseif($val[\'attr\'] === \'^\') $node->parentNode->insertBefore($f, $node);\r\n					elseif($val[\'attr\'] === \'$\')\r\n						if($node->nextSibling) $node->parentNode->insertBefore($f, $node->nextSibling);\r\n						else $node->parentNode->appendChild($f);\r\n					else $node->appendChild($f); break;\r\n				}\r\n\r\n				case \'?\' : $value = htmlspecialchars($value, ENT_COMPAT, $etc_query_context[\'encoding\'], false);\r\n				$node->nodeValue = $val[\'attr\'] === \'+\' || $val[\'attr\'] === \'$\' ? $node->nodeValue.$value : ($val[\'attr\'] === \'^\' ? $value.$node->nodeValue : $value);\r\n				break;\r\n\r\n				case \'~\' : case \'/\' :\r\n				$nodes = $etc_query_context[\'xpath\']->evaluate($value, $node);\r\n				if ($nodes instanceof DOMNodeList) foreach($nodes as $element)\r\n					if(!$is_attr) {\r\n						if($val[\'attr\'] === \'~\') $element->parentNode->insertBefore($node, $element);\r\n						else $element->appendChild($node);\r\n					} else $element->setAttributeNode($node);\r\n				break;\r\n\r\n				case \'#\' : if($is_attr && $value != $node->name) {\r\n					$node->ownerElement->setAttribute($value, $node->value);\r\n					$node->ownerElement->removeAttributeNode($node);\r\n				} break;\r\n\r\n				default : if(!$is_attr) $node->setAttribute($val[\'attr\'], $value);\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\n/*************** MISC **********************/\r\n\r\nfunction etc_create_element($name, &$value, &$doc) {\r\n	if(empty($name) || is_numeric($name)) {$attr = $name; $name = \'row\';}\r\n	else unset($attr);\r\n	if(!is_array($value) && !is_object($value)) {\r\n		$value = (string)$value;\r\n		if(strpos($value, \'&\') !== false) $value = preg_replace(\'/&(#?\\w+\\b(?!;)|(?![#\\w]))/\', \'&amp;$1\', $value);\r\n		$element = $doc->createElement($name, $value);\r\n/*		$element = $doc->createElement($name);\r\n		$element->appendChild($doc->createTextNode((string)$value));\r\n*/	}\r\n	else {\r\n		$element = $doc->createElement($name);\r\n		foreach($value as $key=>$val) $element->appendChild(etc_create_element($key, $val, $doc));\r\n	}\r\n	if(isset($attr)) $element->setAttribute(\'index\', $attr);\r\n	return $element;\r\n}\r\n\r\nfunction etc_cleanup($out = null, $safe_context = null, $safe_globals = null, $safe_sanitize = null, $safe_specials = null) {\r\n	global $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$etc_query_context = $safe_context;\r\n	$etc_query_globals = $safe_globals;\r\n	$etc_query_sanitize = $safe_sanitize;\r\n	$etc_query_specials = $safe_specials;\r\n	return($out);\r\n}\r\n\r\nif(!defined(\'ETC_PREG_SPEC\')) define(\'ETC_PREG_SPEC\', \'/(?<!\\{)(\\{[?!](?>[^{}]|(?1))+\\})/U\');', '// TXP 4.7 tag registration\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')->register(\'etc_query\');\r\n	if(method_exists(\'\\Textpattern\\Tag\\Registry\', \'registerAttr\'))\r\n		Txp::get(\'\\Textpattern\\Tag\\Registry\')->registerAttr(\'etc_replace\', \'etc-query, etc-replace\')->registerAttr(false, \'etc-break\');\r\n}\r\n\r\nfunction etc_replace($atts, $thing = \'\')\r\n{\r\n	$atts = lAtts(array(\r\n		\'etc-query\' => \'.\',\r\n		\'etc-replace\' => \'\',\r\n		\'etc-break\' => \'\',\r\n	), $atts, false);\r\n\r\n	$out = trim($thing) !== \'\' ? etc_query(array(\'data\' => $thing, \'query\' => $atts[\'etc-query\'], \'replace\' => $atts[\'etc-replace\'], \'break\' => $atts[\'etc-break\'], \'markup\' => \'html\', \'globals\' => \'\', \'specials\' => \'\', \'parse\' => \'\')) : $thing;\r\n	return $out;\r\n}\r\n\r\nfunction etc_query($atts, $thing=\'\') {\r\n	$php = php(null, \'echo 1;\') === \'1\';\r\n\r\n	global $etc_query, $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$safe_context = isset($etc_query_context) ? $etc_query_context : null;\r\n	$safe_globals = isset($etc_query_globals) ? $etc_query_globals : null;\r\n	$safe_sanitize = isset($etc_query_sanitize) ? $etc_query_sanitize : null;\r\n	$safe_specials = isset($etc_query_specials) ? $etc_query_specials : null;\r\n	static $safe_defaults = array(\r\n		\'url\' => \'\',\r\n		\'context\' => null,\r\n		\'globals\' => \'variable,pretext,thisarticle,thisimage,thisfile,thislink\',\r\n		\'sanitize\' => \'\',\r\n		\'functions\' => \'\'\r\n	),\r\n	$atts_defaults = array(\r\n		\'name\' => null,\r\n		\'save\' => null,\r\n		\'data\' => null,\r\n		\'url\' => \'\',\r\n		\'context\' => null,\r\n		\'markup\' => \'\',\r\n		\'encoding\' => \'UTF-8\',\r\n//		\'start\' => \'\',\r\n		\'query\' => null,\r\n		\'form\' => \'\',\r\n		\'populate\' => \'\',\r\n//		\'thorough\' => \'\',\r\n		\'replace\' => \'\',\r\n		\'separator\' => \';\',\r\n		\'argsep\' => \'|\',\r\n		\'globals\' => \'variable,pretext,thisarticle,thisimage,thisfile,thislink\',\r\n		\'specials\' => \'data,query\',\r\n		\'sanitize\' => \'\',\r\n		\'parse\' => \'auto\',\r\n		\'functions\' => \'\',\r\n		\'limit\' => null,\r\n		\'offset\' => \'0\',\r\n		\'label\' => \'\',\r\n		\'labeltag\' => \'\',\r\n		\'wraptag\' => \'\',\r\n		\'break\' => \'\',\r\n		\'class\' => \'\',\r\n		\'html_id\' => \'\',\r\n		\'debug\' => \'\'\r\n	);\r\n\r\n	extract(lAtts($atts_defaults, $php ? $atts : $safe_defaults + $atts));\r\n\r\n	if($debug) {\r\n		$debug = do_list($debug);\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	// Grab the form or embedded $thing\r\n	$oldThing = $thing;\r\n	if($form && $form = fetch_form($form)) $thing = $content = $form;\r\n	else $content = isset($thing) ? EvalElse($thing, 1) : \'\';\r\n\r\n	$nodata = !$url && ($data === null);\r\n	@list($encoding, $enc_from) = explode(\':\', $encoding);\r\n\r\n	$etc_query_context = array(\'argsep\'=>$argsep, \'encoding\'=>$encoding, \'functions\'=>\'\', \'strip\'=>array(\'_COOKIE\', \'_GET\', \'_POST\', \'_REQUEST\'), \'php\'=>$php);\r\n\r\n	$etc_query_globals = $globals ? do_list($globals) : false;\r\n	$specialchars = is_callable(\'txpspecialchars\') ? \'txpspecialchars\' : \'htmlspecialchars\';\r\n	$etc_query_sanitize = array_fill_keys($etc_query_context[\'strip\'], $specialchars);\r\n	if($sanitize) foreach(do_list($sanitize) as $item) {\r\n		@list($key, $val) = explode(\':\', $item, 2);\r\n		if(!isset($val)) $etc_query_sanitize[$key] = $specialchars;\r\n		elseif(!$val || is_callable($val)) $etc_query_sanitize[$key] = $val;\r\n	}\r\n//	$etc_query_sanitize = $sanitize ? array_fill_keys(do_list($sanitize), true) : null;\r\n\r\n	if($specials) foreach(do_list($specials) as $special) if(!empty($$special)) {\r\n		$$special = preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $$special);\r\n	}\r\n\r\n	if($functions) {\r\n		$functions = do_list($functions);\r\n		$func = implode(\'|\', $functions);\r\n		if($query) $query = preg_replace(\'/\\b(\'.$func.\')\\s*\\(/\', \"php:function(\'$1\',\", $query);\r\n	}\r\n	$etc_query_context[\'functions\'] = $functions ? $func : \'\';\r\n\r\n	if($markup !== \'data\') $loaded = false;\r\n	elseif($data && isset($etc_query[$data])) {\r\n		$markup = $etc_query[$data]->markup;\r\n		if(isset($etc_query[$data]->dom)) $dom = &$etc_query[$data]->dom;\r\n		if(isset($etc_query[$data]->data)) $data = &$etc_query[$data]->data;\r\n		$loaded = true;\r\n	} else $data = \'\';\r\n\r\n	if($data === \'\' || $query === \'\') return etc_cleanup(parse($oldThing, false), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n\r\n	if(empty($markup)) if($data === null) $markup = $url ? \'\' : ($query !== null && $query[0] !== \'$\' ? \'xml\' : \'raw\');\r\n		else $markup = (strtolower(substr($data, 0, 7)) === \'select \' ? \'db\' : ($data[0] === \'[\' || $data[0] === \'{\' && ($data[1] === \'\"\' || $data[1] === \' \') ? \'json\' : ($url || $replace || strpos($data, \'<\') !== false ? \'html\' : \'raw\')));\r\n	else {\r\n		@list($markup, $pfx) = explode(\':\', $markup, 2);\r\n		if(substr($markup, -1) === \'*\') {$cdata = true; $markup = substr($markup, 0, -1);}\r\n		if($markup === \'db\' && strtolower(substr($data, 0, 7)) !== \'select \') {\r\n			switch($populate) {\r\n				case \'file\' : $fields = \'*\'; $filter = safe_pfx($populate).\" WHERE status >= 4 AND $data\"; break;\r\n				case \'image\' : case \'link\' : $fields = \'*\'; $filter = safe_pfx($populate).\' WHERE \'.$data; break;\r\n				default : $fields = \'*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod\';\r\n				if (is_callable(\'now\')) {$now_posted = now(\'posted\'); $now_expires = now(\'expires\');} else {$now_posted = $now_expires = \'NOW()\';}\r\n				$filter = safe_pfx(\'textpattern\').\" WHERE Status >= 4 AND Posted <= $now_posted AND (Expires IS NULL OR Expires >= $now_expires) AND $data\";\r\n				//if(empty($pfx)) $pfx = \'article\';\r\n			}\r\n			$data = \"SELECT $fields FROM $filter\";\r\n		}\r\n	}\r\n\r\n	if(!$php && $markup === \'db\') return etc_cleanup(parse($oldThing, false), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n\r\n	if(!empty($populate)) {\r\n		global ${\'this\'.$populate};\r\n		$thispopulate = isset(${\'this\'.$populate}) ? ${\'this\'.$populate} : null;\r\n		$format_info = $populate.($populate === \'file\' ? \'_download\' : \'\').\'_format_info\';\r\n		if($populate === \'article\') $column_map = article_column_map();\r\n	}\r\n\r\n	if($content !== \'\') {\r\n		if($parse === \'auto\') $parse = (strpos($content, \'<txp:\') !== false ? \'after\' : \'\');\r\n		elseif($parse === \'before\' || $parse === \'both\') $content = parse($content);\r\n		if(strpos($content, \'<xsl:\') !== false) {$xcontent = $content; $content = \'\';}\r\n	}\r\n	$parse = ($parse === \'after\' || $parse === \'both\');\r\n\r\n	$flat = ($markup !== \'html\' && $markup !== \'xml\' && !$replace && !isset($xcontent));//array mode\r\n	$query_array = null;//array();\r\n	if($flat) if($query !== null) \r\n	{\r\n		if($query[0] === \'$\') {\r\n			if($nodata) $data = $content;\r\n			$content = \'{\'.$query.\'}\';\r\n			$query = \'.\'; $query_array = array();\r\n		} else {\r\n			$query_array = etc_parse_xpath($query.\'?\', $flat);\r\n			if(isset($query_array[\'array\'])) $query_array = $query_array[\'array\'];\r\n		}\r\n	}\r\n	elseif($markup === \'db\')\r\n		if($content === \'\') {$query = \'*/*\'; $query_array = array(\'*\', \'*\');}\r\n\r\n	if($etc_query_globals && strpos($content.$replace, \'{?\') === false && strpos($content.$replace, \'{!\') === false) $etc_query_globals = false;\r\n	if($content !== \'\') $contents = etc_parse_string($content, $flat);\r\n	elseif($thing) $contents = array(\'text\' => \'\');\r\n	else $contents = array();\r\n\r\n	if($replace) if(strpos($replace, \'<xsl:\') !== false) {$xreplace = $replace; $replace = \'\';}\r\n	else {\r\n		$replace_array = array();\r\n		foreach(do_list($replace, $separator) as $pat) if($pat !== \'\') {\r\n			$replace_array[] = etc_parse_xpath($pat, $flat);\r\n		}\r\n		if($debug && in_array(\'r\', $debug)) {echo \'REPLACE\'; dmp($replace_array);}\r\n	}\r\n\r\n	if(!$loaded) {\r\n		if($url) if($markup === \'db\')\r\n			{\r\n				global $DB;\r\n				$mysqli = is_object($DB->link);\r\n				$result = $mysqli ? mysqli_select_db($DB->link, $url) : mysql_select_db($url);\r\n				$data = $result ? getRows($data) : null;\r\n				if($mysqli) mysqli_select_db($DB->link, $DB->db); else mysql_select_db($DB->db);\r\n			} elseif($markup === \'ldap\') {\r\n				$ldapconn = ldap_connect($url);\r\n//				ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);\r\n				if($ldapconn)\r\n				{\r\n					@list($ldaprdn, $ldapfilter, $ldaponly) = do_list($data, \';\');\r\n					if(!isset($ldapfilter)) $ldapfilter = \'objectClass=*\';\r\n					$ldaponly = (!empty($ldaponly) ? do_list($ldaponly) : array());\r\n					$data = ldap_search($ldapconn, $ldaprdn, $ldapfilter, $ldaponly);\r\n					$data = ldap_get_entries($ldapconn, $data);\r\n					ldap_close($ldapconn);\r\n				}\r\n				else $data = null;\r\n			} else {\r\n				$context = $context ? stream_context_create(json_decode($context, true)) : null;\r\n				$data = file_get_contents($url, false, $context);\r\n				if(!empty($http_response_header)) foreach($http_response_header as $header) if(preg_match(\'/^Content\\-Type\\:(.*)/i\', $header, $matches)) {\r\n					list($mime, $opt) = explode(\';\', $matches[1]);\r\n					list($type, $subtype) = explode(\'/\', strtolower(trim($mime)));\r\n					if(!$markup && in_array($subtype, array(\'json\', \'html\', \'xml\'))) $markup = $subtype;\r\n					if(preg_match(\'/\\bcharset=([\\w\\-#]+)/i\', $opt, $matches)) $enc = strtoupper($matches[1]);\r\n					break;\r\n				}\r\n				if($encoding && $data) {\r\n					$encoding = strtoupper($encoding);\r\n					if(isset($enc_from) && $enc_from !== \'*\') $enc = strtoupper($enc_from);\r\n					elseif(!isset($enc) && preg_match(\'/<meta\\b[^>]+\\bcharset=\"?([\\w\\-#]+)\"?[^>]*>/i\', $data, $matches)) $enc = strtoupper($matches[1]);\r\n					if(is_callable(\'mb_detect_encoding\')) {\r\n						if(!isset($enc)) $enc = strtoupper(mb_detect_encoding($data)); //else dmp($enc);\r\n						$mb_encodings = $enc ? array_map(\'strtoupper\', mb_list_encodings()) : array();\r\n						$mb_encodings = in_array($encoding, $mb_encodings) && in_array($enc, $mb_encodings);\r\n					}\r\n					else $mb_encodings = false;\r\n					if($enc && ($enc !== $encoding || isset($enc_from)))\r\n						if($mb_encodings) $data = mb_convert_encoding($data, $encoding, $enc);\r\n						elseif(is_callable(\'iconv\')) $data = iconv($enc, $encoding.\'//IGNORE\', $data);\r\n				}\r\n			}\r\n\r\n		if($markup === \'json\') if(preg_match(\'/^\\[(\\-?\\w+)\\.\\.(\\-?\\w+):?([\\+\\-]?)(\\d*)\\]$/\', $data, $matches)) switch($matches[3]) {\r\n				case \'+\' : if($matches[1] <= $matches[2]) $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1); else $data = null; break;\r\n				case \'-\' : if($matches[1] >= $matches[2]) $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1); else $data = null; break;\r\n				default : $data = range($matches[1], $matches[2], $matches[4] ? $matches[4] : 1);\r\n			} else $data = json_decode($data, true); //array\r\n		elseif($markup === \'array\') $data = unserialize($data);\r\n		elseif($markup === \'list\') {\r\n			$data = array_filter(do_list($data, isset($pfx) ? $pfx : \',\'), function($a) {return (string)$a !== \'\';});\r\n			if(isset($pfx)) unset($pfx);\r\n		} elseif($markup === \'db\' && !$url /*&& $data*/) {\r\n			$data = getRows($data);\r\n		} elseif(!$markup) $markup = \'html\';\r\n	}\r\n	elseif(!$flat && !isset($dom)) {$loaded = false;}\r\n\r\n	$is_html = $markup === \'html\';\r\n	$is_xml = $markup === \'xml\';\r\n	$flat = $flat && !$is_html && !$is_xml;\r\n\r\n	if($debug) {\r\n		$endtime = microtime(TRUE);\r\n		dmp(\"Preloaded in \" . ($endtime - $starttime) . \" seconds.\\n\");\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	if(!$nodata && empty($data)) $roots = null;\r\n	elseif($flat) {$roots = array($data ? $data : \'\'); $xpath = null;}\r\n	else {\r\n		if(!$loaded) {\r\n			/*** load the data into a new dom object ***/\r\n			$dom = new DOMDocument;\r\n			$dom->preserveWhiteSpace = false;\r\n			$dom->strictErrorChecking = false;\r\n			$dom->encoding = $encoding;\r\n			if(!$debug) libxml_use_internal_errors(true);\r\n\r\n			if(isset($cdata) && $data) $data = str_ireplace(array(\'<![CDATA[\', \']]>\'), \'<!---->\', $data);\r\n			if(isset($pfx) && $pfx === \'\') $data = str_replace(\' xmlns=\', \' etcns=\', $data);\r\n\r\n			if($is_html) {\r\n//				if($url) $dom->loadHTML(preg_replace(\'/<head.*>/Usi\', \'\\\\0<meta http-equiv=\"content-type\" content=\"text/html; charset=\' . $encoding . \'\">\', $data));\r\n				if($url) $dom->loadHTML(\'<meta http-equiv=\"content-type\" content=\"text/html; charset=\' . $encoding . \'\">\' . $data);\r\n				else $dom->loadHTML(\'<?xml encoding=\"\'.$encoding.\'\">\'.$data);\r\n			}\r\n			elseif($is_xml) {if(!$nodata) $dom->loadXML($data, LIBXML_COMPACT);}\r\n			elseif($data) if(!is_array($data) && !is_object($data)) $dom->appendChild($dom->createElement(\'root\', (string)$data)); else {\r\n				foreach ($data as $key => $val) $dom->appendChild(etc_create_element($key, $val, $dom));\r\n				$is_xml = true;\r\n			}\r\n		}\r\n\r\n		if((isset($xcontent) || isset($xreplace)) && class_exists(\'XSLTProcessor\')) {\r\n			$xslt = new DOMDocument;\r\n			$proc = new XSLTProcessor;\r\n			if($functions) $proc->registerPHPFunctions($functions);\r\n			if(isset($xreplace)) {\r\n				$xslt->loadXML($xreplace);\r\n				$proc->importStyleSheet($xslt);\r\n				$dom = $proc->transformToDoc($dom);\r\n			}\r\n			if(isset($xcontent)) {\r\n				$xslt->loadXML($xcontent);\r\n				$proc->importStyleSheet($xslt);\r\n				return etc_cleanup($proc->transformToXML($dom), $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n			}\r\n		}\r\n\r\n		$xpath = new DOMXpath($dom);\r\n		if($functions) {\r\n			$xpath->registerNamespace(\'php\', \'http://php.net/xpath\');\r\n			$xpath->registerPHPFunctions($functions);\r\n		}\r\n		if(!empty($pfx)) {\r\n			$xpath->registerNamespace($pfx, $dom->lookupNamespaceUri($dom->namespaceURI));\r\n		}\r\n\r\n		if(empty($start)) $start = ($is_html ? \'/html/body\' : \'/\');\r\n		$roots = $xpath->query($start);\r\n\r\n		if($debug) {if(in_array(\'h\', $debug)) {echo $markup; dmp($is_xml? $dom->saveXML() : $dom->saveHTML());}}\r\n		else libxml_clear_errors();\r\n	}//dom\r\n\r\n	if(empty($contents)) {//$thing === null\r\n		$path = $flat || $query !== null || $is_xml ? \'.\' : \'node()\';\r\n		$content = $path !== \'.\';\r\n		$contents = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => $path, \'mode\' => $flat ? \'?\' : \'.\', \'value\' => array(), \'array\' => array())));\r\n	} else $content = true;\r\n	if($query === null) $query = ($is_xml && !$nodata ? \'node()\' : \'.\');\r\n	$out = array();\r\n	$etc_query_context[\'xpath\'] = &$xpath;\r\n	$etc_query_context[\'xml\'] = $is_xml;\r\n\r\n	if($debug) {\r\n		$endtime = microtime(TRUE);\r\n		dmp(\"Loaded in \" . ($endtime - $starttime) . \" seconds. $flat\\n\");\r\n		if(in_array(\'d\', $debug)) {echo \'DATA \', $markup; dmp($data);}\r\n		if(in_array(\'q\', $debug)) {echo \'QUERY\'; dmp($query); dmp($query_array);}\r\n		if(in_array(\'c\', $debug)) {echo \'CONTENTS\'; dmp($contents);}\r\n		$starttime = microtime(TRUE);\r\n	}\r\n\r\n	if(!empty($roots)) {\r\n		foreach($roots as $root) /*if(!$thorough || $xpath->query($root->getNodePath())->length)*/ {\r\n			// Ugly hack to detect if the node is removed\r\n			$elements = $internals = array();\r\n\r\n			if(!$flat) {\r\n				$nodes = $xpath->evaluate($query, $root);\r\n				if (!($nodes instanceof DOMNodeList)) {\r\n					$internals[\'{?}\'] = $nodes;\r\n					if($nodes) {\r\n						$newout = !$content ? $nodes : (isset($contents[\'text\']) ? $contents[\'text\'] : etc_evaluate_string($contents, $root, $internals, $flat));\r\n						$out[] = $parse ? parse($newout) : $newout;\r\n					}\r\n					continue;\r\n				}\r\n				foreach($nodes as $element) $elements[] = $element;\r\n			}// or $root = $data\r\n			elseif($query === \'*\' || $query_array === null) $elements = is_array($root) ? $root : (array) $root;\r\n			elseif(empty($query_array)) $elements = array($root);//query = \".\" or \"/\"\r\n			else {\r\n				$query_array = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => \'.\', \'mode\' => \'?\', \'array\' => $query_array, \'value\' => array())));\r\n				$elements = etc_evaluate_string($query_array, $root, $internals, 2);\r\n			}\r\n\r\n			$nums = $num_rows = count($elements);\r\n			$offnum = ($offset < 0 ? $nums + $offset : $offset);\r\n			if($offset || $limit) {\r\n				$elements = array_slice($elements, $offset, $limit, true);\r\n				$num_rows = count($elements);\r\n			}\r\n\r\n			if(isset($etc_query_specials[\'{#rows}\'])) $internals[\'{#rows}\'] = $num_rows;\r\n			if(isset($etc_query_specials[\'{#total}\'])) $internals[\'{#total}\'] = $nums;\r\n			$num = 0;\r\n\r\n			if(!empty($elements)) foreach($elements as $row => &$element) {\r\n				if(!empty($format_info)) {\r\n					global ${\'this\'.$populate};//damn doArticles()!\r\n					if($flat) {\r\n						if($populate === \'article\') foreach ($column_map as $key => $column)\r\n							$thisarticle[$key] = isset($element[$column]) ? $element[$column] : \'\';//article_format_info($element);\r\n						else ${\'this\'.$populate} = $format_info($element);\r\n					} elseif($populate === \'article\') {\r\n						foreach ($column_map as $key => $column) {\r\n							$nodes = $xpath->query($is_xml ? $column : strtolower($column), $element);\r\n							$thisarticle[$key] = $nodes->length ? $nodes->item(0)->nodeValue : \'\';\r\n						}\r\n					}\r\n					${\'this\'.$populate}[\'is_first\'] = ($num == 0);\r\n					${\'this\'.$populate}[\'is_last\'] = ($num+1 == $num_rows);\r\n				}\r\n\r\n				if(isset($etc_query_specials[\'{#row}\'])) $internals[\'{#row}\'] = $num+1;\r\n				if(isset($etc_query_specials[\'{#ind}\'])) $internals[\'{#ind}\'] = $num+$offnum;\r\n				if(isset($etc_query_specials[\'{?}\'])) $internals[\'{?}\'] = ($flat ? $element : $element->nodeValue);\r\n				if(isset($etc_query_specials[\'{#}\'])) $internals[\'{#}\'] = ($flat ? $row : $element->nodeName);\r\n\r\n				// Replace/remove values\r\n				if($replace) foreach($replace_array as $val) {\r\n					$nodes = ($val[\'path\'] === \'.\' ? array($element) : $xpath->query($internals ? strtr($val[\'path\'], $internals) : $val[\'path\'], $element));\r\n					$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n					if($nodes) foreach($nodes as $node) /*if(!$thorough || $xpath->query($node->getNodePath())->length)*/ try\r\n					{\r\n						if($ind) $internals[\'{##}\'] = $ind++;\r\n						etc_process_node($val[\'value\'], $node, $internals, false, true);\r\n					} catch(DOMException $e) {if($debug) echo $e->getMessage();}\r\n				}\r\n\r\n				// Construct the replacement arrays\r\n				if(isset($etc_query_specials[\'{##}\'])) $internals[\'{##}\'] = $num+1;\r\n\r\n				$newout = !$content && !$sanitize\r\n					? ($flat ? $element : ($is_html ? $dom->saveHTML($element) : $dom->saveXML($element)))\r\n					: (isset($contents[\'text\']) ? $contents[\'text\'] : etc_evaluate_string($contents, $element, $internals, $flat));\r\n\r\n				$out[] = $parse ? parse($newout) : $newout;\r\n				$num++;\r\n			}\r\n			unset($element, $elements);\r\n		}\r\n	}\r\n\r\n	if(!empty($populate)) ${\'this\'.$populate} = $thispopulate;\r\n	if($save) {\r\n		if(!$flat) $etc_query[$save]->dom = &$dom; else $etc_query[$save]->data = &$data;\r\n		$etc_query[$save]->markup = ($is_xml ? \'xml\' : $markup);\r\n	}\r\n	if ($out) if(!$save || $name !== null) {\r\n		// Make up the final output\r\n		$finalout = ($label ? doLabel($label, $labeltag) : \'\').($wraptag || $break ? doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id) : implode($out));\r\n	} else $finalout = \'\';\r\n	else $finalout = parse($oldThing, false);//nothing found\r\n\r\n	etc_cleanup(null, $safe_context, $safe_globals, $safe_sanitize, $safe_specials);\r\n	$endtime = microtime(TRUE);\r\n	if($debug) dmp(\"Processed in \" . ($endtime - $starttime) . \" seconds.\\n\");\r\n	if($debug && in_array(\'o\', $debug)) dmp($finalout);\r\n	if($name) {global $variable; $variable[$name] = &$finalout;}\r\n	return $name ? \'\' : $finalout;\r\n}\r\n\r\n/************************** PARSE *************************/\r\n\r\nfunction etc_parse_string($content, &$flat) {\r\n	if(strpos($content, \'{\') === false) return array(\'text\' => $content);\r\n	global $etc_query_specials, $etc_query_context;\r\n//	$preg_string = \'/(\\{(?![\\s\"])(?:[^{}]|(?1))+\\})/U\';\r\n\r\n	$contents = array();\r\n	$contents_array = preg_split(\'/(\\{(?>[^{}]|(?1))+\\})/U\', $content, null, PREG_SPLIT_DELIM_CAPTURE);\r\n	$num = count($contents_array);//2n+1\r\n	$text = $contents_array[0];\r\n	for($i = 1; $i < $num; $i+=2) {\r\n		$string = substr($contents_array[$i], 1, -1);\r\n		if(strpos(\'{ \"\', $string[0]) !== false) {\r\n			$l = 0; while($string[$l] === \'{\') $l++;\r\n			$cstring = etc_parse_string($l ? substr($string, $l, -$l) : $string, $flat);\r\n			$l = max(1, $l);\r\n			if(isset($cstring[\'text\'])) $text .= str_repeat(\'{\', $l).$cstring[\'text\'].str_repeat(\'}\', $l);\r\n			else {$contents[] = array(\'type\' => \'string\', \'text\' => $text, \'value\' => $cstring, \'level\' => $l); $text = \'\';}\r\n		}\r\n		elseif($string[0] === \'?\' && strlen($string) > 1) $text .= etc_parse_variable(array($contents_array[$i]));\r\n		else {\r\n			switch($string[0]) {\r\n				case \'$\' : $value = etc_parse_function(substr($string, 1), $flat);\r\n				if($value && $value[0][\'func\'] === \'@\') {\r\n					$contents[] = array(\'type\' => \'define\', \'text\' => \'\', \'value\' => $value[0][\'args\'][0][0][\'value\']);\r\n					$value = array_slice($value, 1);\r\n				}\r\n				$contents[] = array(\'type\' => \'func\', \'text\' => $text, \'value\' => $value); $text = \'\'; break;\r\n				case \'#\' : case \'?\' : $contents[] = $etc_query_specials[$contents_array[$i]] = array(\'type\' => \'internal\', \'text\' => $text, \'value\' => $string); $text = \'\'; break;\r\n				case \'%\' : $text .= substr($string, 1); break;\r\n				case \'!\' : $parts = explode(\'|\', $string, 3);\r\n				if(count($parts) > 2) {$string = $parts[0].\'|\'.$parts[1]; $func = etc_parse_function($parts[2], $flat);}\r\n				else $func = null;\r\n				$contents[] = $func ? array(\'type\' => \'func\', \'text\' => $text, \'value\' => $func, \'var\' => array(\'{\'.$string.\'}\')) : array(\'type\' => \'var\', \'text\' => $text, \'value\' => array($contents_array[$i])) ; $text = \'\'; break;\r\n				default : $contents[] = array(\'type\' => \'xpath\', \'text\' => $text, \'value\' => etc_parse_xpath($string, $flat)); $text = \'\';\r\n			}\r\n		}\r\n		$text .= $contents_array[$i+1];\r\n	}\r\n	if($text > \'\') $contents[] = array(\'type\' => \'text\', \'text\' => $text);\r\n	if(count($contents) === 1 && $contents[0][\'type\'] === \'text\') $contents = array(\'text\' => $contents[0][\'text\']);\r\n	return $contents;\r\n}\r\n\r\nfunction etc_parse_variable($val) {\r\n	global $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$pat = $val[0];\r\n	if(isset($etc_query_specials[$pat])) return $etc_query_specials[$pat];\r\n	@list($vars, $def, $format) = explode(\'|\', substr($pat, 2, -1), 3);\r\n	if($vars === \'\' || !empty($etc_query_globals)) foreach(explode(\',\', $vars) as $var)\r\n		if($var !== \'\') {foreach($etc_query_globals as $global) if(isset($GLOBALS[$global][$var]) && $GLOBALS[$global][$var] !== \'\')\r\n			{$value = $GLOBALS[$global][$var]; break 2;}}\r\n		else {$value = (isset($def) ? $def : \'\'); $global = null;}\r\n	if(!isset($value)) $value = (isset($def) ? $def : \'\');\r\n	else {\r\n		if(MAGIC_QUOTES_GPC && $global && in_array($global, $etc_query_context[\'strip\'])) $value = doStrip($value);\r\n		if(!empty($format)) {\r\n			$flat = true; //$internals = null;\r\n			if ($format[0] === \'#\') {\r\n				$format = ltrim($format, \'#.\'); $value = isset($var) ? $var : $value;\r\n			}\r\n			$format = array(array(\'type\' => \'func\', \'text\' => \'\', \'value\' => etc_parse_function($format, $flat)));\r\n			$value = etc_evaluate_string($format, $value, null);\r\n		}\r\n		elseif(!empty($etc_query_sanitize[$global])) $value = $etc_query_sanitize[$global]($value);\r\n		elseif(!isset($etc_query_sanitize[$global]) && !empty($etc_query_sanitize[\'*\'])) $value = $etc_query_sanitize[\'*\']($value);\r\n	}\r\n	if($pat[1] === \'?\') $etc_query_specials[$pat] = $value;\r\n	return $value;\r\n}\r\n\r\nfunction etc_parse_function($format, &$flat) {\r\n	global $etc_query_context;\r\n\r\n	$pipe = $etc_query_context[\'argsep\'];\r\n	$res = $rep = array();\r\n	if(strpos($format, \'{\') !== false) {\r\n		$format = preg_split(\'/(\\{(?>[^{}]|(?1))*\\})/U\', $format, null, PREG_SPLIT_DELIM_CAPTURE);//2n+1\r\n		$count = count($format);\r\n		for($i=1; $i<$count; $i+=2) {$rep[\'{\'.$i.\'}\'] = $format[$i]; $format[$i] = \'{\'.$i.\'}\'; }\r\n		$format = implode($format);\r\n	}\r\n//\'/([^\\(\\)\\.]*(?:\\([^\\(\\)]*\\))?)(?:\\.|$)/U\'\r\n	if($format[0] === \'.\') $format = \'~\'.$format;\r\n	$format = preg_split(\'/([^\\(\\)\\.]*)(\\((?>[^\\(\\)]|(?2))*\\))?(?:\\.|$)/\', $format, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);\r\n\r\n	foreach($format as $i => $form) {\r\n		if($form[0] !== \'(\') {//func\r\n			if(isset($func)) $res[] = array(\'func\'=>$func, \'ref\'=>$ref, \'args\'=>$args);\r\n			$func = ltrim($form);\r\n			$ref = $func[0] === \'@\';\r\n			if($ref) $func = substr($func, 1);\r\n			$args = array(array(\'text\' => \'$\'));\r\n			if($func !== \'\') switch($func[0]) {\r\n				case \'+\' : case \'*\' : case \'-\' : case \'/\' : case \'%\' : case \'^\' :\r\n				if(strlen($func) > 1) $args[] = array(\'text\' => floatval(substr($func, 1))); $func = $func[0]; break;\r\n				case \'~\' : if(strlen($func) > 1) $args[] = array(\'text\' => substr($func, 1)); $func = $func[0]; break;\r\n				case \'=\' : case \'>\' : case \'<\' : case \'?\' : if(strlen($func) > 1) $args[] = array(\'text\' => ltrim(substr($func, 1))); $func = $func[0]; break;\r\n				case \'$\' : if($i) {$args[] = array(\'text\' => $func === \'$\' ? \'\' : $func); $func = \'$\';}\r\n				else {$args = array(array(\'text\' => \'\'), array(\'text\' => $func)); $func = \'~\';}\r\n				break;\r\n				default : $func = trim($func);\r\n				if(!$etc_query_context[\'php\'] && !in_array($func, array(\'date\', \'textile\'))) $func = \'\';\r\n				if($func === \'textile\') {\r\n					$func = \'Textile->TextileThis\';\r\n				}\r\n				if (strpos($func, \'::\')) $func = explode(\'::\', $func);\r\n				elseif(strpos($func, \'->\')) {\r\n					$func = explode(\'->\', $func);\r\n					if(strtolower($func[0]) === \'textile\') include_once(txpath.\'/lib/classTextile.php\');\r\n					if (class_exists($func[0])) $func[0] = new $func[0];\r\n				}\r\n				if(!is_callable($func)) unset($func, $ref);\r\n			} else unset($func, $ref);\r\n		} else {//args\r\n			$args_list = substr($form, 1, -1);\r\n			if(!isset($func)) {\r\n				if(!$i) {\r\n					$func = \'@\'; $ref = false;\r\n					$args = array(array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => etc_parse_xpath($args_list, $flat))));\r\n				}\r\n				continue;\r\n			}\r\n			if($func !== \'?\' && count($args) < 2) $args = array();\r\n			if($args_list !== \'\') foreach(explode($pipe, $args_list) as $arg) $args[] = $rep ? etc_parse_string(strtr($arg, $rep), $flat) : array(\'text\' => $arg);\r\n			if($func === \'?\') $args = array_slice($args, -3);\r\n		}\r\n	}\r\n	if(isset($func)) $res[] = array(\'func\'=>$func, \'ref\'=>$ref, \'args\'=>$args);\r\n	return $res;\r\n}\r\n\r\nfunction etc_parse_xpath($pattern, &$flat) {\r\n	global $etc_query_context, $etc_query_globals;\r\n\r\n	$func = $etc_query_context[\'functions\'];\r\n\r\n	@list($path, $val) = explode(\'@@\', $pattern, 2);\r\n\r\n	if(isset($val)) {\r\n		$pairs = preg_split(\'/((?>[^\\[\\](){}]|[\\[\\(\\{](?1)*[\\]\\)\\}])+)@/U\', $val, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);\r\n		$flat = false;\r\n		$val = array();\r\n		foreach($pairs as $pair) {\r\n			@list($name, $aval) = explode(\'=\', $pair, 2);\r\n			if($name > \'\') $val[$name] = $aval;\r\n		}\r\n	} else {\r\n		@list($path, $val) = preg_split(\'/^((?>[^\\[\\](){}]|[\\[\\(\\{](?1)*[\\]\\)\\}])+)=(.*)$/sDU\', $pattern, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);\r\n		if(!isset($val) && strlen($path) < strlen($pattern)) $val = \'\';\r\n	}\r\n\r\n	if($path !== \'\' && strpos(\':?#+^$~&/\', substr($path, -1)) !== false) {\r\n		$mode = substr($path, -1); $path = substr($path, 0, -1);\r\n	} else $mode = \'.\';\r\n	if($mode !== \'?\') $flat = false;\r\n	if($path === \'\') $path = \'.\';\r\n	elseif($etc_query_globals) $path = preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $path);\r\n	if(isset($val) && !is_array($val)) $val = array($mode => $val);\r\n\r\n	$attr_array = array();\r\n\r\n	if(!empty($val)) foreach($val as $attr=>$value) if($attr !== \'&\') {\r\n		$rep[\'attr\'] = $etc_query_globals ? preg_replace_callback(ETC_PREG_SPEC, \'etc_parse_variable\', $attr) : $attr;\r\n		$rep[\'value\'] = $value === null ? null : etc_parse_string($value, $flat);\r\n		$attr_array[] = $rep;\r\n//		unset($rep);\r\n	} elseif($value !== null) {$last[\'attr\'] = \'&\'; $last[\'value\'] = etc_parse_string($value, $flat);}\r\n	if(isset($last)) $attr_array[] = $last;\r\n	unset($pattern);\r\n	$pattern[\'path\'] = $func ? preg_replace(\'/\\b(\'.$func.\')\\s*\\(/\', \"php:function(\'$1\',\", $path) : $path;\r\n	$pattern[\'mode\'] = $mode;\r\n	$pattern[\'value\'] = $attr_array;\r\n\r\n	if($flat && $path !== \'/\') if(strpos($path, \'//\') !== false) $flat = false; else {\r\n		$matches = explode(\'/\', $path);\r\n		foreach($matches as $match) if($match !== \'*\' && $match !== \'.\' && preg_match(\'/[\\(\\)\\[\\]\\.:@]/\', $match)) {\r\n			$flat = false; break;\r\n		}\r\n		if($flat) $pattern[\'array\'] = array_diff($matches, array(\'.\'));\r\n	}\r\n	return $pattern;\r\n}\r\n\r\n/************************** EVALUATE *************************/\r\n\r\nfunction etc_evaluate_string(&$contents, &$element, $internals, $flat=true) {\r\n	if(isset($contents[\'text\'])) return $contents[\'text\'];\r\n	global $etc_query_context, $etc_query_sanitize, $etc_query_specials;\r\n\r\n	$wrap = $flat === 2;\r\n	$res = $wrap ? array() : \'\';\r\n	$defines = array();\r\n	if(!$flat) {\r\n		$is_xml = $etc_query_context[\'xml\'];\r\n		$dom = $element->ownerDocument;\r\n	}\r\n\r\n	foreach($contents as $content) {\r\n		if(!$wrap) $res .= $content[\'text\'];\r\n		switch($content[\'type\']) {\r\n			case \'text\' : break;\r\n\r\n			case \'internal\' : $res .= (isset($internals[\'{\'.$content[\'value\'].\'}\']) ? $internals[\'{\'.$content[\'value\'].\'}\'] : $content[\'value\']); break;\r\n\r\n			case \'var\' : $res .= etc_parse_variable($content[\'value\']); break;\r\n\r\n			case \'string\' : $l = !empty($content[\'level\']) ? $content[\'level\'] : 0;\r\n			$res .= str_repeat(\'{\', $l).etc_evaluate_string($content[\'value\'], $element, $internals, $flat).str_repeat(\'}\', $l); break;\r\n\r\n			case \'func\' : if(!empty($undefined)) {unset($undefined); break;}\r\n			$val = isset($content[\'var\']) ? etc_parse_variable($content[\'var\']) : (isset($define) ? $define : ($flat ? $element : $element->nodeValue));\r\n			unset($define);\r\n			if(empty($content[\'value\'])) {$res .= $val; break;}\r\n			foreach($content[\'value\'] as $form) {\r\n				$func = $form[\'func\'];\r\n				$ref = $form[\'ref\'];\r\n				$args = array();\r\n				if($has_args = count($form[\'args\'])) {\r\n					foreach($form[\'args\'] as $key => $arg) {\r\n						if(!$key || $func !== \'?\' || $key === ($args[0] ? 1 : 2))\r\n							$arg = isset($arg[\'text\']) ? $arg[\'text\'] : etc_evaluate_string($arg, $element, $internals, $flat);\r\n						else $arg = \'\';\r\n						if($arg === \'$\') $args[] =& $val;\r\n						elseif($arg && $arg[0] === \'$\' && isset($defines[$arg])) $args[] =& $defines[$arg];\r\n						else $args[] = $arg;\r\n					}\r\n				}\r\n				switch($func) {\r\n					case \'$\' : if($args[1] !== \'\') $defines[$args[1]] = $val; else $val = \'\'; break; //$args[0]=\'$\'\r\n					case \'~\' : $val = is_array($args[0]) ? implode($has_args > 1 ? $args[1] : \'\', $args[0]) : implode($args); break;\r\n					case \'+\' : $val = array_sum($args); break;\r\n					case \'*\' : $val = array_product($args); break;\r\n					case \'-\' : $val = 2*$args[0] - array_sum($args); break;\r\n					case \'/\' : $val = $args[0] ? $args[0]*$args[0] / array_product($args) : 0; break;\r\n					case \'%\' : $val = $args[0] % $args[1]; break;\r\n					case \'^\' : $val = pow($args[0], $args[1]); break;\r\n					case \'?\' : switch($has_args) {\r\n						case 0 : break;\r\n						case 1 : $val = ($args[0] ? \'1\' : \'0\'); break;\r\n						default : $val = !empty($args[0]) ? $args[1] : (isset($args[2]) ? $args[2] : \'\');\r\n					} break;\r\n					case \'=\' : switch($has_args) {\r\n						case 0 : case 1 : break;\r\n						case 2 : $val = ($args[0] == $args[1] ? \'1\' : \'0\'); break;\r\n						default : $val = count(array_unique($args))<=1 ? \'1\' : \'0\';\r\n					} break;\r\n					case \'<\' : case \'>\' : if($has_args > 1) {\r\n						$val = ($func === \'<\' ? ($args[0] < $args[1] ? \'1\' : \'0\') : ($args[0] > $args[1] ? \'1\' : \'0\'));\r\n					} break;\r\n					case \'date\' : if($has_args) $val = date($args[0], strtotime(isset($args[1]) ? $args[1] : $val)); break;\r\n//					case \'escape\' : $val = doSlash($val); break;\r\n					default : $newval = call_user_func_array($func, $args);\r\n					if(!$ref) $val = $newval;\r\n				}\r\n			}\r\n			$res .= $val; break;\r\n\r\n			default : if(!$flat) {\r\n				$value = \'\';\r\n				$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n				$nodes = $etc_query_context[\'xpath\']->evaluate($internals ? strtr($content[\'value\'][\'path\'], $internals) : $content[\'value\'][\'path\'], $element);\r\n				if (!($nodes instanceof DOMNodeList)) $value = $nodes;\r\n				else foreach($nodes as $node)\r\n				{\r\n					if($ind) $internals[\'{##}\'] = $ind++;\r\n					$out = empty($content[\'value\'][\'value\']) ? false : etc_process_node($content[\'value\'][\'value\'], $node, $internals, false);\r\n					$value .= $out ? $out : ($content[\'value\'][\'mode\'] === \'?\' ? $node->nodeValue : ($content[\'value\'][\'mode\'] === \'#\' ? $node->nodeName : (!$is_xml ? $dom->saveHTML($node) : $dom->saveXML($node))));\r\n				}\r\n				if($content[\'type\'] !== \'define\') $res .= $value; else $define = $value;\r\n			} else {//flat\r\n				$value = $element; $chunk=\'\';\r\n				if(!empty($content[\'value\'][\'array\'])) foreach($content[\'value\'][\'array\'] as $key => $chunk)\r\n				{\r\n					if($chunk !== \'*\')\r\n						if(isset($value[$chunk])) $value = $value[$chunk];\r\n						else {unset($value); break;}\r\n					else {\r\n						$query = array(array(\'type\' => \'xpath\', \'text\' => \'\', \'value\' => array(\'path\' => \'.\', \'mode\' => \'?\', \'array\' => array_slice($content[\'value\'][\'array\'], $key + 1), \'value\' => $content[\'value\'][\'value\'])));\r\n						$ind = isset($etc_query_specials[\'{##}\']) ? 1 : 0;\r\n						foreach($value as $k => $val) {\r\n							if(isset($etc_query_specials[\'{#}\'])) $internals[\'{#}\'] = $k;\r\n							if($ind) $internals[\'{##}\'] = $ind++;\r\n							$newres = etc_evaluate_string($query, $val, $internals, $flat);\r\n							if($wrap) foreach($newres as $row) $res[] = $row;\r\n							else $res .= $newres;\r\n						}\r\n						unset($value); break;\r\n					}\r\n				}\r\n				if(isset($value)) {\r\n					if(!empty($content[\'value\'][\'value\'])) etc_process_node($content[\'value\'][\'value\'], $value, $internals);\r\n					elseif(!$wrap)\r\n						if(isset($etc_query_sanitize[$chunk.\'?\'])) {if($etc_query_sanitize[$chunk.\'?\']) $value = $etc_query_sanitize[$chunk.\'?\']($value);}\r\n						elseif(!empty($etc_query_sanitize[\'?\'])) $value = $etc_query_sanitize[\'?\']($value);\r\n					if($content[\'type\'] !== \'define\') if($wrap) $res[] = $value; else $res .= $value;\r\n					else $define = $value;\r\n				} elseif($content[\'type\'] === \'define\') $undefined = true;\r\n			}\r\n		}\r\n	}\r\n	return $res;\r\n}\r\n\r\nfunction etc_process_node(&$attr, &$node, $internals=null, $flat=true, $remove=false) {\r\n	if($flat) {\r\n		if(empty($attr) && $remove) $node = null;\r\n		else foreach($attr as $val) if($val[\'attr\'] === \'.\' || $val[\'attr\'] === \'?\') {\r\n			if(isset($internals[\'{?}\'])) $internals[\'{?}\'] = $node;\r\n			$node = etc_evaluate_string($val[\'value\'], $node, $internals, $flat);\r\n		}\r\n		return;\r\n	}\r\n\r\n	global $etc_query_context;\r\n\r\n//	$self_path = array(\'.\', \'?\', \'#\', \'+\', \'^\', \'$\', \'~\', \'&\', \'/\');\r\n	$is_attr = ($node->nodeType === XML_ATTRIBUTE_NODE);\r\n	if(empty($attr) && $remove) {\r\n		if($is_attr) $node->ownerElement->removeAttributeNode($node);\r\n		else $node->parentNode->removeChild($node);\r\n	}\r\n	else foreach($attr as $val) {\r\n//		unset($name);\r\n		$self = strpos(\'.?#+^$~&/\', $val[\'attr\']) !== false;//in_array($val[\'attr\'], $self_path);\r\n		if(!isset($val[\'value\'])) {if(!$self && !$is_attr) $node->removeAttribute($val[\'attr\']);}\r\n		else {\r\n			if(isset($internals[\'{?}\'])) $internals[\'{?}\'] = $self || $is_attr ? $node->nodeValue : $node->getAttribute($val[\'attr\']);\r\n			if(isset($internals[\'{#}\'])) $internals[\'{#}\'] = $self || $is_attr ? $node->nodeName : $val[\'attr\'];\r\n			$value = isset($val[\'value\'][\'text\']) ? $val[\'value\'][\'text\'] : etc_evaluate_string($val[\'value\'], $node, $internals, $flat);\r\n			switch($val[\'attr\']) {\r\n				case \'&\' : if(!$remove) return $value;\r\n				case \'.\' : case \'+\' : case \'^\' : case \'$\' : if(!$is_attr) {\r\n					if(strpos($value, \'<\') === false)\r\n						if($val[\'attr\'] !== \'.\') $f = $node->ownerDocument->createTextNode($value);\r\n						else {$node->nodeValue = $value; break;}\r\n					else {\r\n						$encoding = $etc_query_context[\'encoding\'];\r\n						$is_xml = $etc_query_context[\'xml\'];\r\n						if(!$is_xml) $value = preg_replace(\'/<((?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\\b[^>]*)(?<!\\/)>/iU\', \'<$1 />\', $value);\r\n						if(!$is_xml && strpos($value, \'&\') !== false) {\r\n							if(!isset($etc_query_context[\'decode\'])) $etc_query_context[\'decode\'] = array_flip(array_diff(get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, $encoding), get_html_translation_table(HTML_SPECIALCHARS, ENT_COMPAT, $encoding)));\r\n							$value = strtr($value, $etc_query_context[\'decode\']);\r\n						}\r\n						$f = $node->ownerDocument->createDocumentFragment();\r\n						$f->appendXML($value);\r\n					}\r\n					if($val[\'attr\'] === \'.\') $node->nodeValue = null;\r\n					if($val[\'attr\'] === \'&\') $node->parentNode->replaceChild($f, $node);\r\n					elseif($val[\'attr\'] === \'^\') $node->parentNode->insertBefore($f, $node);\r\n					elseif($val[\'attr\'] === \'$\')\r\n						if($node->nextSibling) $node->parentNode->insertBefore($f, $node->nextSibling);\r\n						else $node->parentNode->appendChild($f);\r\n					else $node->appendChild($f); break;\r\n				}\r\n\r\n				case \'?\' : $value = htmlspecialchars($value, ENT_COMPAT, $etc_query_context[\'encoding\'], false);\r\n				$node->nodeValue = $val[\'attr\'] === \'+\' || $val[\'attr\'] === \'$\' ? $node->nodeValue.$value : ($val[\'attr\'] === \'^\' ? $value.$node->nodeValue : $value);\r\n				break;\r\n\r\n				case \'~\' : case \'/\' :\r\n				$nodes = $etc_query_context[\'xpath\']->evaluate($value, $node);\r\n				if ($nodes instanceof DOMNodeList) foreach($nodes as $element)\r\n					if(!$is_attr) {\r\n						if($val[\'attr\'] === \'~\') $element->parentNode->insertBefore($node, $element);\r\n						else $element->appendChild($node);\r\n					} else $element->setAttributeNode($node);\r\n				break;\r\n\r\n				case \'#\' : if($is_attr && $value != $node->name) {\r\n					$node->ownerElement->setAttribute($value, $node->value);\r\n					$node->ownerElement->removeAttributeNode($node);\r\n				} break;\r\n\r\n				default : if(!$is_attr) $node->setAttribute($val[\'attr\'], $value);\r\n			}\r\n		}\r\n	}\r\n}\r\n\r\n/*************** MISC **********************/\r\n\r\nfunction etc_create_element($name, &$value, &$doc) {\r\n	if(empty($name) || is_numeric($name)) {$attr = $name; $name = \'row\';}\r\n	else unset($attr);\r\n	if(!is_array($value) && !is_object($value)) {\r\n		$value = (string)$value;\r\n		if(strpos($value, \'&\') !== false) $value = preg_replace(\'/&(#?\\w+\\b(?!;)|(?![#\\w]))/\', \'&amp;$1\', $value);\r\n		$element = $doc->createElement($name, $value);\r\n/*		$element = $doc->createElement($name);\r\n		$element->appendChild($doc->createTextNode((string)$value));\r\n*/	}\r\n	else {\r\n		$element = $doc->createElement($name);\r\n		foreach($value as $key=>$val) $element->appendChild(etc_create_element($key, $val, $doc));\r\n	}\r\n	if(isset($attr)) $element->setAttribute(\'index\', $attr);\r\n	return $element;\r\n}\r\n\r\nfunction etc_cleanup($out = null, $safe_context = null, $safe_globals = null, $safe_sanitize = null, $safe_specials = null) {\r\n	global $etc_query_context, $etc_query_globals, $etc_query_sanitize, $etc_query_specials;\r\n	$etc_query_context = $safe_context;\r\n	$etc_query_globals = $safe_globals;\r\n	$etc_query_sanitize = $safe_sanitize;\r\n	$etc_query_specials = $safe_specials;\r\n	return($out);\r\n}\r\n\r\nif(!defined(\'ETC_PREG_SPEC\')) define(\'ETC_PREG_SPEC\', \'/(?<!\\{)(\\{[?!](?>[^{}]|(?1))+\\})/U\');', '4ac212127f92229a8545f7c312f1e99c', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_prefs', 0, 'NicolasGraph', '', '0.10', 'Set prefs', '', '/**\n * Main class for admin-side functionality.\n */\nclass oui_prefs\n{\n    /**\n     * Plugin event names and privs.\n     *\n     * Change this to match your intended prefs group names and list of priv group values.\n     *\n     * @var array\n     */\n    protected $plugEvent = array(\n        \'oui_prefs\' => \'1,2,3\',\n    );\n\n    /**\n     * Plugin pref list.\n     *\n     * Change this array to set up your prefs. One entry per pref in this format:\n     *  preference_name => array(\n     *      default_value,   [value that is inserted by default]\n     *      event,           [the prefs group to which this belongs. You can set its name in the Textpack above]\n     *      pref_visibility, [usually PREF_ADVANCED for 4.5.x or PREF_PLUGIN for 4.6+. Note: no quotes]\n     *      input_widget,    [text_input, yesnoradio, onoffradio]\n     *      position,        [order of prefs on-screen, lowest first]\n     *      is_private,      [true = preference for individual users, false = preference that affects everyone]\n     *  ),\n     *\n     * The preference names can be defined in the Textpack above.\n     *\n     * @var array\n     * @todo change PREF_ADVANCED to PREF_PLUGIN from 4.6\n     */\n    protected $prefList = array(\n        \'pref_name1\' => array(\n            \'value\'      => \'\',\n            \'event\'      => \'oui_prefs\',\n            \'visibility\' => PREF_ADVANCED,\n            \'widget\'     => \'text_input\',\n            \'position\'   => \'10\',\n            \'is_private\' => false,\n        ),\n        \'pref_name2\' => array(\n            \'value\'      => \'\',\n            \'event\'      => \'oui_prefs\',\n            \'visibility\' => PREF_ADVANCED,\n            \'widget\'     => \'text_input\',\n            \'position\'   => \'20\',\n            \'is_private\' => false,\n        ),\n    );\n\n    /**\n     * Register callbacks and kick things off.\n     */\n    public function __construct()\n    {\n        if (txpinterface === \'admin\') {\n            $firstEv = null;\n            foreach ($this->plugEvent as $evName => $privs) {\n                if ($firstEv === null) {\n                    $firstEv = $evName;\n                }\n\n                add_privs(\'prefs.\' . $evName, $privs);\n                add_privs(\'plugin_prefs.\' . $evName, $privs);\n            }\n\n            register_callback(array($this, \'oui_prefs_welcome\'), \'plugin_lifecycle.oui_prefs\');\n            register_callback(array($this, \'oui_prefs_install\'), \'prefs\', null, 1);\n            register_callback(array($this, \'oui_prefs_options\'), \'plugin_prefs.\' . $firstEv, null, 1);\n        } elseif (txpinterface === \'public\') {\n            register_callback(array($this, \'oui_prefs_inject_vars\'), \'pretext_end\');\n        }\n    }\n\n    /**\n     * Handler for plugin lifecycle events.\n     *\n     * @param string $evt Textpattern action event\n     * @param string $stp Textpattern action step\n     */\n    public function oui_prefs_welcome($evt, $stp)\n    {\n        switch ($stp) {\n            case \'enabled\':\n                $this->oui_prefs_install();\n                break;\n            case \'disabled\':\n                $this->oui_prefs_uninstall();\n            case \'deleted\':\n                $this->oui_prefs_uninstall();\n\n                foreach ($this->plugEvent as $evName => $privs) {\n                    $evName = doSlash(str_replace(\'_\', \'\\_\', $evName));\n                    safe_delete(\'txp_lang\', \"name LIKE \'\" . $evName . \"%\'\");\n                }\n\n                break;\n        }\n    }\n\n    /**\n     * Install the prefs defined in $plugEvent if necessary.\n     *\n     * This is a separate function so it can be used as a direct callback.\n     * When operating under a plugin cache environment, the install lifecycle\n     * event is never fired, so this is a fallback.\n     *\n     * The lifecycle callback remains for deletion purposes under a regular installation,\n     * since the plugin cannot detect this in a cache environment.\n     *\n     * @see oui_prefs_welcome()\n     */\n    public function oui_prefs_install()\n    {\n        foreach ($this->prefList as $pref => $options) {\n            if (get_pref($pref, null) === null) {\n                set_pref(\n                    $pref,\n                    $options[\'value\'],\n                    $options[\'event\'],\n                    $options[\'visibility\'],\n                    $options[\'widget\'],\n                    $options[\'position\'],\n                    $options[\'is_private\']\n                );\n            }\n        }\n    }\n\n    /**\n     * Remove the prefs defined in $plugEvent.\n     */\n    public function oui_prefs_uninstall()\n    {\n        if (function_exists(\'remove_pref\')) {\n            // 4.6 API\n            foreach ($this->plugEvent as $evName => $privs) {\n                remove_pref(null, $evName);\n            }\n        } else {\n            foreach ($this->plugEvent as $evName => $privs) {\n                safe_delete(\'txp_prefs\', \"event=\'\" . doSlash($evName) . \"\'\");\n            }\n        }\n    }\n\n    /**\n     * Fetch the admin-side prefs panel link.\n     *\n     * It\'s version dependent, as 4.6.0 doesn\'t have the notion of\n     * Advanced Prefs.\n     */\n    protected function oui_prefs_link()\n    {\n        global $dbversion;\n\n        if (version_compare($dbversion, \'4.6-dev\') < 0) {\n            $link = \'?event=prefs&step=advanced_prefs\';\n        } else {\n            foreach ($this->plugEvent as $evName => $privs) {\n                // Assign first pref event group as the one to jump to.\n                break;\n            }\n\n            $link = \'?event=prefs#prefs_group_\' . $evName;\n        }\n\n        return $link;\n    }\n\n    /**\n     * Jump to the prefs panel.\n     */\n    public function oui_prefs_options()\n    {\n        $link = $this->oui_prefs_link();\n\n        header(\'Location: \' . $link);\n    }\n\n    /**\n     * Public-side callback to read the prefs values and assign them to variables.\n     */\n    public function oui_prefs_inject_vars()\n    {\n        global $variable;\n\n        foreach ($this->plugEvent as $evName => $privs) {\n            $prefset = safe_rows(\'name, val\', \'txp_prefs\', \"event = \'\" . doSlash($evName) . \"\'\");\n\n            foreach ($prefset as $pref) {\n                $variable[$pref[\'name\']] = $pref[\'val\'];\n            }\n        }\n    }\n}\n\nnew oui_prefs();', '/**\n * Main class for admin-side functionality.\n */\nclass oui_prefs\n{\n    /**\n     * Plugin event names and privs.\n     *\n     * Change this to match your intended prefs group names and list of priv group values.\n     *\n     * @var array\n     */\n    protected $plugEvent = array(\n        \'oui_prefs\' => \'1,2,3\',\n    );\n\n    /**\n     * Plugin pref list.\n     *\n     * Change this array to set up your prefs. One entry per pref in this format:\n     *  preference_name => array(\n     *      default_value,   [value that is inserted by default]\n     *      event,           [the prefs group to which this belongs. You can set its name in the Textpack above]\n     *      pref_visibility, [usually PREF_ADVANCED for 4.5.x or PREF_PLUGIN for 4.6+. Note: no quotes]\n     *      input_widget,    [text_input, yesnoradio, onoffradio]\n     *      position,        [order of prefs on-screen, lowest first]\n     *      is_private,      [true = preference for individual users, false = preference that affects everyone]\n     *  ),\n     *\n     * The preference names can be defined in the Textpack above.\n     *\n     * @var array\n     * @todo change PREF_ADVANCED to PREF_PLUGIN from 4.6\n     */\n    protected $prefList = array(\n        \'pref_name1\' => array(\n            \'value\'      => \'\',\n            \'event\'      => \'oui_prefs\',\n            \'visibility\' => PREF_ADVANCED,\n            \'widget\'     => \'text_input\',\n            \'position\'   => \'10\',\n            \'is_private\' => false,\n        ),\n        \'pref_name2\' => array(\n            \'value\'      => \'\',\n            \'event\'      => \'oui_prefs\',\n            \'visibility\' => PREF_ADVANCED,\n            \'widget\'     => \'text_input\',\n            \'position\'   => \'20\',\n            \'is_private\' => false,\n        ),\n    );\n\n    /**\n     * Register callbacks and kick things off.\n     */\n    public function __construct()\n    {\n        if (txpinterface === \'admin\') {\n            $firstEv = null;\n            foreach ($this->plugEvent as $evName => $privs) {\n                if ($firstEv === null) {\n                    $firstEv = $evName;\n                }\n\n                add_privs(\'prefs.\' . $evName, $privs);\n                add_privs(\'plugin_prefs.\' . $evName, $privs);\n            }\n\n            register_callback(array($this, \'oui_prefs_welcome\'), \'plugin_lifecycle.oui_prefs\');\n            register_callback(array($this, \'oui_prefs_install\'), \'prefs\', null, 1);\n            register_callback(array($this, \'oui_prefs_options\'), \'plugin_prefs.\' . $firstEv, null, 1);\n        } elseif (txpinterface === \'public\') {\n            register_callback(array($this, \'oui_prefs_inject_vars\'), \'pretext_end\');\n        }\n    }\n\n    /**\n     * Handler for plugin lifecycle events.\n     *\n     * @param string $evt Textpattern action event\n     * @param string $stp Textpattern action step\n     */\n    public function oui_prefs_welcome($evt, $stp)\n    {\n        switch ($stp) {\n            case \'enabled\':\n                $this->oui_prefs_install();\n                break;\n            case \'disabled\':\n                $this->oui_prefs_uninstall();\n            case \'deleted\':\n                $this->oui_prefs_uninstall();\n\n                foreach ($this->plugEvent as $evName => $privs) {\n                    $evName = doSlash(str_replace(\'_\', \'\\_\', $evName));\n                    safe_delete(\'txp_lang\', \"name LIKE \'\" . $evName . \"%\'\");\n                }\n\n                break;\n        }\n    }\n\n    /**\n     * Install the prefs defined in $plugEvent if necessary.\n     *\n     * This is a separate function so it can be used as a direct callback.\n     * When operating under a plugin cache environment, the install lifecycle\n     * event is never fired, so this is a fallback.\n     *\n     * The lifecycle callback remains for deletion purposes under a regular installation,\n     * since the plugin cannot detect this in a cache environment.\n     *\n     * @see oui_prefs_welcome()\n     */\n    public function oui_prefs_install()\n    {\n        foreach ($this->prefList as $pref => $options) {\n            if (get_pref($pref, null) === null) {\n                set_pref(\n                    $pref,\n                    $options[\'value\'],\n                    $options[\'event\'],\n                    $options[\'visibility\'],\n                    $options[\'widget\'],\n                    $options[\'position\'],\n                    $options[\'is_private\']\n                );\n            }\n        }\n    }\n\n    /**\n     * Remove the prefs defined in $plugEvent.\n     */\n    public function oui_prefs_uninstall()\n    {\n        if (function_exists(\'remove_pref\')) {\n            // 4.6 API\n            foreach ($this->plugEvent as $evName => $privs) {\n                remove_pref(null, $evName);\n            }\n        } else {\n            foreach ($this->plugEvent as $evName => $privs) {\n                safe_delete(\'txp_prefs\', \"event=\'\" . doSlash($evName) . \"\'\");\n            }\n        }\n    }\n\n    /**\n     * Fetch the admin-side prefs panel link.\n     *\n     * It\'s version dependent, as 4.6.0 doesn\'t have the notion of\n     * Advanced Prefs.\n     */\n    protected function oui_prefs_link()\n    {\n        global $dbversion;\n\n        if (version_compare($dbversion, \'4.6-dev\') < 0) {\n            $link = \'?event=prefs&step=advanced_prefs\';\n        } else {\n            foreach ($this->plugEvent as $evName => $privs) {\n                // Assign first pref event group as the one to jump to.\n                break;\n            }\n\n            $link = \'?event=prefs#prefs_group_\' . $evName;\n        }\n\n        return $link;\n    }\n\n    /**\n     * Jump to the prefs panel.\n     */\n    public function oui_prefs_options()\n    {\n        $link = $this->oui_prefs_link();\n\n        header(\'Location: \' . $link);\n    }\n\n    /**\n     * Public-side callback to read the prefs values and assign them to variables.\n     */\n    public function oui_prefs_inject_vars()\n    {\n        global $variable;\n\n        foreach ($this->plugEvent as $evName => $privs) {\n            $prefset = safe_rows(\'name, val\', \'txp_prefs\', \"event = \'\" . doSlash($evName) . \"\'\");\n\n            foreach ($prefset as $pref) {\n                $variable[$pref[\'name\']] = $pref[\'val\'];\n            }\n        }\n    }\n}\n\nnew oui_prefs();', '0344391b2e020322497efac3134c2b46', '', '', 1, 5, 3),
('chh_if_data', 0, 'Coke Harrington', 'http://www.cokesque.com/', '0.10', 'Show a block of text only if enclosed Txp tags produce some output.', '<h2>chh_if_data</h2>\n\n	<p>This tag is used to determine whether some contained Txp tags output any data, allowing the conditional output of extra markup, like a header or a \"no articles found\" message.</p>\n\n<pre>\n&lt;txp:chh_if_data&gt;\n    &lt;h3&gt;Here\'s an article list&lt;/h3&gt;\n    &lt;txp:article_custom category=\"Nogo\" /&gt;\n&lt;txp:else /&gt;\n    &lt;p&gt;article_custom had nothing to say.&lt;/p&gt;\n&lt;/txp:chh_if_data&gt;</pre>\n<pre>\n&lt;txp:chh_if_data&gt;\n    Static text will NEVER be shown.\n    Drop a Txp tag in here!<br />\n&lt;/txp:chh_if_data&gt;<br />\n</pre>\n\n	<p>If the enclosed chunk of text contains several Txp tags, <code>chh_if_data</code> will evaluate to true when <em>any</em> of the tags returns data.</p>\n\n	<p>\"Data,\" in this case, mean any output at all, including whitespace.</p>', '\nfunction chh_if_data ($atts, $thing=\'\') {\n    $atts = lAtts(array(\'debug\' => 0), $atts);\n\n    $f = \'/<txp:(\\S+)\\b(.*)(?:(?<!br )(\\/))?\'.chr(62).\'(?(3)|(.+)<\\/txp:\\1>)/sU\';\n    $iftext = EvalElse($thing, true);\n    $thresh = 1 + strlen(preg_replace($f, \'\', $iftext));\n\n    $parsed = parse($iftext);\n    $parsed_len = strlen($parsed);\n\n    $empty = \'Data\';\n    if (strlen($parsed) < $thresh) { #or !preg_match(\'/\\S/\', $parsed)) {\n        $parsed = parse(EvalElse($thing, false));\n        $empty = \'No Data\';\n    }\n    return $atts[\'debug\']\n           ? \"<!-- $empty -- Threshhold: $thresh Length: $parsed_len -->\" . $parsed\n           : $parsed;\n}\n\n// Deprecated due to poor naming\nfunction chh_unless_empty ($atts, $thing=\'\') {\n    return chh_if_data($atts, $thing);\n}\nfunction chh_if_not_empty ($atts, $thing=\'\') {\n    return chh_if_data($atts, $thing);\n}\n', '\nfunction chh_if_data ($atts, $thing=\'\') {\n    $atts = lAtts(array(\'debug\' => 0), $atts);\n\n    $f = \'/<txp:(\\S+)\\b(.*)(?:(?<!br )(\\/))?\'.chr(62).\'(?(3)|(.+)<\\/txp:\\1>)/sU\';\n    $iftext = EvalElse($thing, true);\n    $thresh = 1 + strlen(preg_replace($f, \'\', $iftext));\n\n    $parsed = parse($iftext);\n    $parsed_len = strlen($parsed);\n\n    $empty = \'Data\';\n    if (strlen($parsed) < $thresh) { #or !preg_match(\'/\\S/\', $parsed)) {\n        $parsed = parse(EvalElse($thing, false));\n        $empty = \'No Data\';\n    }\n    return $atts[\'debug\']\n           ? \"<!-- $empty -- Threshhold: $thresh Length: $parsed_len -->\" . $parsed\n           : $parsed;\n}\n\n// Deprecated due to poor naming\nfunction chh_unless_empty ($atts, $thing=\'\') {\n    return chh_if_data($atts, $thing);\n}\nfunction chh_if_not_empty ($atts, $thing=\'\') {\n    return chh_if_data($atts, $thing);\n}\n', '', '', '', 0, 5, 0),
('rss_auto_excerpt', 0, 'Rob Sable', 'http://www.wilshireone.com/', '0.5', 'Automatic article excerpts', '<h1>Automatic article excerpts</h1>\n\n	<p>The <code>rss_auto_excerpt</code> plugin automatically displays article excerpts even if an excerpt has not been saved with an article.  If for some articles you have defined an excerpt, your own excerpt will be displayed rather than the auto-generated excerpt.  You can also override this feature so that an auto-generated excerpt is always displayed even if an excerpt has been defined.</p>\n\n	<p>There are 4 different options for auto-generating the excerpt from the article body:</p>\n\n	<ul>\n		<li><strong>By Length</strong> &#8211; specify the number of characters to display from the article body</li>\n		<li><strong>By Words</strong> &#8211; specify the number of words to display from the article body</li>\n		<li><strong>By Sentences</strong> &#8211; specify the number of sentences to display from the article body</li>\n	</ul>\n	<ul>\n		<li><strong>By Paragraphs</strong> &#8211; specify the number of paragraphs to display from the article body</li>\n	</ul>\n\n	<p>If auto-generating by length, the plugin will ensure that the excerpt does not cutoff in the middle of a word.  If based on the parameters passed into the plugin, the excerpt would not be shorter than the entire article body, the body will be shown instead.</p>\n\n	<h1>Reverse excerpts</h1>\n\n	<p>You can also use the plugin to generate a &#8220;reverse&#8221; excerpt by using the <em>skip</em> variants of the length attributes.  For example:<br />\n<pre><code>&#60;txp:if_individual_article&#62;\n  &#60;txp:rss_auto_excerpt skipparagraphs=&#34;1&#34; paragraphs=&#34;9999&#34; /&#62;\n&#60;txp:else /&#62;\n  &#60;txp:rss_auto_excerpt paragraphs=&#34;1&#34; /&#62;\n&#60;/txp:if_individual_article&#62;\n</code></pre></p>\n\n	<p>This code would display the first paragraph of your article on an article listing page, but on an individual article page, you would see all paragraphs except the first.</p>\n\n	<p>The skip tags could also be used to apply different styling to you first paragraph (i.e., larger font size) or character (i.e., drop caps).</p>\n\n	<p><strong>The plugin will also automatically close any open <span class=\"caps\">HTML</span> tags.</strong>  This will allow for proper handling of lists, bolded or italicized text and other situations.  You can also choose to strip out all <span class=\"caps\">HTML</span> tags from the excerpt.</p>\n\n	<h1>Plugin Attributes</h1>\n\n	<table style=\"color:#333;font:normal 1em verdana;width: 100%;padding: 0 0 20px;margin: 0 auto 20px;border-bottom:1px solid #666;\" id=\"list\">\n		<tr>\n			<th style=\"vertical-align:middle;white-space: nowrap;\">attribute name</th>\n			<th style=\"vertical-align:middle;white-space: nowrap;\">description</th>\n			<th style=\"vertical-align:middle;white-space: nowrap;\">default value</th>\n			<th style=\"vertical-align:middle;white-space: nowrap;\">example usage</th>\n		</tr>\n		<tr>\n			<td>length</td>\n			<td>The number of characters to display from the article body.</td>\n			<td> &nbsp;</td>\n			<td>length=&#8220;350&#8221;</td>\n		</tr>\n		<tr>\n			<td>words</td>\n			<td>The number of words to display from the article body.</td>\n			<td> &nbsp;</td>\n			<td>words=&#8220;50&#8221;</td>\n		</tr>\n		<tr>\n			<td>sentences</td>\n			<td>The number of sentences to display from the article body.</td>\n			<td> &nbsp;</td>\n			<td>sentences=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>paragraphs</td>\n			<td>The number of paragraphs to display from the article body.</td>\n			<td> &nbsp;</td>\n			<td>paragraphs=&#8220;2&#8221;</td>\n		</tr>\n		<tr>\n			<td>linktext</td>\n			<td>The link text for the permlink to the full article.</td>\n			<td>Keep Reading&#8230;</td>\n			<td>linktext=&#8220;Read More&#8221;</td>\n		</tr>\n		<tr>\n			<td>ending</td>\n			<td>The text that will be displayed at the end of the excerpt when generating the excerpt by length or words.  This is used to show that text is being truncated.</td>\n			<td>...</td>\n			<td>ending=&#8221;........&#8221;</td>\n		</tr>\n		<tr>\n			<td>linkwraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the permlink to the full article.</td>\n			<td>p</td>\n			<td>linkwraptag=&#8220;span&#8221;</td>\n		</tr>\n		<tr>\n			<td>linkclass</td>\n			<td>The class attribute on the linkwraptag.</td>\n			<td> &nbsp;</td>\n			<td>linkclass=&#8220;morelink&#8221;</td>\n		</tr>\n		<tr>\n			<td>linklabel</td>\n			<td>A label that appears before the permlink to the full article.  It will appear outside the link tag.</td>\n			<td> &nbsp;</td>\n			<td>linklabel=&#8221;- &#8220;</td>\n		</tr>\n		<tr>\n			<td>wrapreadmore</td>\n			<td>Determines whether the permlink to the article will be wrapped in the last closing tag of the excerpt or placed outside.</td>\n			<td>0</td>\n			<td>wrapreadmore=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>excerptwraptag</td>\n			<td>The <span class=\"caps\">HTML</span> tag that wraps the excerpt of the full article.</td>\n			<td>p</td>\n			<td>excerptwraptag=&#8220;span&#8221;</td>\n		</tr>\n		<tr>\n			<td>showlinkwithbody</td>\n			<td>Determines whether the permlink to the article will be shown when the article body is displayed because the generated excerpt is not shorter than the article body.</td>\n			<td>0</td>\n			<td>showlinkwithbody=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>showlinkwithexcerpt</td>\n			<td>Determines whether the permlink to the article will be shown when the automatic excerpt is displayed.</td>\n			<td>1</td>\n			<td>showlinkwithexcerpt=&#8220;0&#8221;</td>\n		</tr>\n		<tr>\n			<td>overrideexcerpt</td>\n			<td>Displays an automatic excerpt even if the article has an excerpt defined.</td>\n			<td>0</td>\n			<td>overrideexcerpt=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>striptags</td>\n			<td>Strips <span class=\"caps\">HTML</span> tags from the automatic excerpt that is displayed.</td>\n			<td>0</td>\n			<td>striptags=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>stripbreakstabs</td>\n			<td>Strips line breaks and tabs from the automatic excerpt that is displayed.</td>\n			<td>0</td>\n			<td>stripbreakstabs=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>skiplength</td>\n			<td>The number of characters to skip at the beginning of the display from the article body.</td>\n			<td>0</td>\n			<td>skiplength=&#8220;50&#8221;</td>\n		</tr>\n		<tr>\n			<td>skipwords</td>\n			<td>The number of words to skip at the beginning of the display from the article body.</td>\n			<td>0</td>\n			<td>skipwords=&#8220;2&#8221;</td>\n		</tr>\n		<tr>\n			<td>skipsentences</td>\n			<td>The number of sentences to skip at the beginning of the display from the article body.</td>\n			<td>0</td>\n			<td>skipsentences=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>skipparagraphs</td>\n			<td>The number of paragraphs to skip at the beginning of the display from the article body.</td>\n			<td>0</td>\n			<td>skipparagraphs=&#8220;1&#8221;</td>\n		</tr>\n		<tr>\n			<td>excerpt</td>\n			<td>The field name to use for the article excerpt.</td>\n			<td>excerpt</td>\n			<td>excerpt=&#8220;custom1&#8221;</td>\n		</tr>\n		<tr>\n			<td>body</td>\n			<td>The field name to use for the article body.</td>\n			<td>body</td>\n			<td>body=&#8220;custom2&#8221;</td>\n		</tr>\n	</table>', '\n	function rss_auto_excerpt($atts) {\n		global $thisarticle;\n\n		extract(lAtts(array(\n			\'linkwraptag\' => \'p\',\n			\'linkclass\' => \'\',\n			\'linklabel\' => \'\',\n			\'linktext\' => \'Keep Reading...\',\n			\'excerptwraptag\' => \'\',\n			\'length\' => 0,\n			\'words\' => 0,\n			\'sentences\' => 0,\n			\'paragraphs\' => 0,\n			\'skiplength\' => 0,\n			\'skipwords\' => 0,\n			\'skipsentences\' => 0,\n			\'skipparagraphs\' => 0,\n			\'ending\' => \'...\',\n			\'showlinkwithbody\' => 0,\n			\'showlinkwithexcerpt\' => 1,\n			\'overrideexcerpt\' => 0,\n			\'striptags\' => 0,\n			\'stripbreakstabs\' => 0,\n			\'wrapreadmore\' => 0,\n			\'excerpt\' => \'excerpt\',\n			\'body\' => \'body\',\n		),$atts));\n\n		$excerpt = trim($thisarticle[$excerpt]);\n		$body = trim($thisarticle[$body]);\n\n		// if the article has an excerpt, show it\n		if ($excerpt != \'\' && !$overrideexcerpt) {\n			$autoex = ($excerptwraptag) ? tag(parse($excerpt),$excerptwraptag).n : parse($excerpt).n;\n			$autoex.= ($showlinkwithexcerpt) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \"\";\n			return $autoex;\n		}\n\n		$body = ($striptags) ? strip_tags($body) : $body;\n\n		$body = ($stripbreakstabs) ? ereg_replace(\"\\n|\\r|\\r\\n|\\n\\r|\\t\", \" \", preg_replace(\'/\\s\\s+/\', \' \', trim($body))) : $body;\n		$charcount = strlen($body);\n		$wordcount = count(explode(\" \", $body));\n		$sencount = count(explode(\".\", $body));\n		$pghcount = count(explode(\"</p>\", $body));\n\n		if (!$length && !$words && !$sentences && !$paragraphs) $length = 150;\n\n		$doex = 0;\n		$doex = ($length) ? ($length < $charcount) ? 1 : 0 : $doex;\n		$doex = ($words) ? ($words < $wordcount) ? 1 : 0 : $doex;\n		$doex = ($sentences) ? ($sentences < $sencount) ? 1 : 0 : $doex;\n		$doex = ($paragraphs) ? ($paragraphs < $pghcount) ? 1 : 0 : $doex;\n\n		if ($doex) {\n			if ($length) {\n				$exa = explode(\" \", substr($body, $skiplength, $length));\n				if (count($exa) > 1) array_pop($exa);\n				$ex = implode(\" \",$exa);\n			} else if ($words) {\n				$allwords = explode(\" \", $body);\n				$awa = array_slice($allwords, $skipwords, $words);\n				$ex = implode(\" \",$awa);\n			} else if ($sentences) {\n				$allsens = explode(\".\", $body);\n				$asa = array_slice($allsens, $skipsentences, $sentences);\n				$ex = implode(\".\",$asa).\".\";\n				$ending = \"\";\n			} else if ($paragraphs) {\n				$allpghs = explode(\"</p>\", $body);\n				$awp = array_slice($allpghs, $skipparagraphs, $paragraphs);\n				$ex = implode(\"</p>\",$awp);\n				$ending = \"\";\n			}\n			$ex.= $ending.rssAutoCloseTags($ex);\n\n			// find the last closing tag\n			preg_match_all(\"/<\\/(([A-Z]|[a-z]).*)(( )|(>))/isU\",$ex,$closetags);\n			$lasttag = end($closetags[0]);\n\n			// add the read more text either inside the last closing tag or outside\n			if ($wrapreadmore && $lasttag) {\n				$exrm = ($showlinkwithexcerpt) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n				$ex = substr($ex, 0, -(strlen($lasttag))) . $exrm . $lasttag;\n			} else {\n				$ex.= ($showlinkwithexcerpt) ? n . doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n			}\n\n		} else {\n			$ex = parse($body);\n			$ex.= ($showlinkwithbody) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n		}\n\n		return $ex;\n	}\n\n// ----------------------------------------------------\n\n	function rssAutoCloseTags($string) {\n		// automatically close HTML-Tags\n		// (usefull e.g. if you want to extract part of a blog entry or news as preview/teaser)\n		// coded by Constantin Gross <connum at googlemail dot com> / 3rd of June, 2006\n		// feel free to leave comments or to improve this function!\n		// Updates by Rob Sable <www.wilshireone.com> / 20th of August, 2006\n\n		$donotclose=array(\'br\',\'img\',\'input\'); //Tags that are not to be closed\n\n		//prepare vars and arrays\n		$tagstoclose=\'\';\n		$tags=array();\n\n		//put all opened tags into an array\n		preg_match_all(\"/<(([A-Z]|[a-z]).*)(( )|(>))/isU\",$string,$result);\n		$openedtags=$result[1];\n		$openedtags=array_reverse($openedtags); //this is just done so that the order of the closed tags in the end will be better\n\n		//put all closed tags into an array\n		preg_match_all(\"/<\\/(([A-Z]|[a-z]).*)(( )|(>))/isU\",$string,$result2);\n		$closedtags=$result2[1];\n\n		//look up which tags still have to be closed and put them in an array\n		for ($i=0;$i<count($openedtags);$i++) {\n			 if (in_array($openedtags[$i],$closedtags)) { unset($closedtags[array_search($openedtags[$i],$closedtags)]); }\n					 else array_push($tags, $openedtags[$i]);\n		}\n		//$tags=array_reverse($tags); //now this reversion is done again for a better order of close-tags\n\n		//prepare the close-tags for output\n		for($x=0;$x<count($tags);$x++) {\n		$add=strtolower(trim($tags[$x]));\n		if(!in_array($add,$donotclose)) $tagstoclose.=\'</\'.$add.\'>\';\n		}\n\n		//and finally\n		return $tagstoclose;\n	}\n', '\n	function rss_auto_excerpt($atts) {\n		global $thisarticle;\n\n		extract(lAtts(array(\n			\'linkwraptag\' => \'p\',\n			\'linkclass\' => \'\',\n			\'linklabel\' => \'\',\n			\'linktext\' => \'Keep Reading...\',\n			\'excerptwraptag\' => \'\',\n			\'length\' => 0,\n			\'words\' => 0,\n			\'sentences\' => 0,\n			\'paragraphs\' => 0,\n			\'skiplength\' => 0,\n			\'skipwords\' => 0,\n			\'skipsentences\' => 0,\n			\'skipparagraphs\' => 0,\n			\'ending\' => \'...\',\n			\'showlinkwithbody\' => 0,\n			\'showlinkwithexcerpt\' => 1,\n			\'overrideexcerpt\' => 0,\n			\'striptags\' => 0,\n			\'stripbreakstabs\' => 0,\n			\'wrapreadmore\' => 0,\n			\'excerpt\' => \'excerpt\',\n			\'body\' => \'body\',\n		),$atts));\n\n		$excerpt = trim($thisarticle[$excerpt]);\n		$body = trim($thisarticle[$body]);\n\n		// if the article has an excerpt, show it\n		if ($excerpt != \'\' && !$overrideexcerpt) {\n			$autoex = ($excerptwraptag) ? tag(parse($excerpt),$excerptwraptag).n : parse($excerpt).n;\n			$autoex.= ($showlinkwithexcerpt) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \"\";\n			return $autoex;\n		}\n\n		$body = ($striptags) ? strip_tags($body) : $body;\n\n		$body = ($stripbreakstabs) ? ereg_replace(\"\\n|\\r|\\r\\n|\\n\\r|\\t\", \" \", preg_replace(\'/\\s\\s+/\', \' \', trim($body))) : $body;\n		$charcount = strlen($body);\n		$wordcount = count(explode(\" \", $body));\n		$sencount = count(explode(\".\", $body));\n		$pghcount = count(explode(\"</p>\", $body));\n\n		if (!$length && !$words && !$sentences && !$paragraphs) $length = 150;\n\n		$doex = 0;\n		$doex = ($length) ? ($length < $charcount) ? 1 : 0 : $doex;\n		$doex = ($words) ? ($words < $wordcount) ? 1 : 0 : $doex;\n		$doex = ($sentences) ? ($sentences < $sencount) ? 1 : 0 : $doex;\n		$doex = ($paragraphs) ? ($paragraphs < $pghcount) ? 1 : 0 : $doex;\n\n		if ($doex) {\n			if ($length) {\n				$exa = explode(\" \", substr($body, $skiplength, $length));\n				if (count($exa) > 1) array_pop($exa);\n				$ex = implode(\" \",$exa);\n			} else if ($words) {\n				$allwords = explode(\" \", $body);\n				$awa = array_slice($allwords, $skipwords, $words);\n				$ex = implode(\" \",$awa);\n			} else if ($sentences) {\n				$allsens = explode(\".\", $body);\n				$asa = array_slice($allsens, $skipsentences, $sentences);\n				$ex = implode(\".\",$asa).\".\";\n				$ending = \"\";\n			} else if ($paragraphs) {\n				$allpghs = explode(\"</p>\", $body);\n				$awp = array_slice($allpghs, $skipparagraphs, $paragraphs);\n				$ex = implode(\"</p>\",$awp);\n				$ending = \"\";\n			}\n			$ex.= $ending.rssAutoCloseTags($ex);\n\n			// find the last closing tag\n			preg_match_all(\"/<\\/(([A-Z]|[a-z]).*)(( )|(>))/isU\",$ex,$closetags);\n			$lasttag = end($closetags[0]);\n\n			// add the read more text either inside the last closing tag or outside\n			if ($wrapreadmore && $lasttag) {\n				$exrm = ($showlinkwithexcerpt) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n				$ex = substr($ex, 0, -(strlen($lasttag))) . $exrm . $lasttag;\n			} else {\n				$ex.= ($showlinkwithexcerpt) ? n . doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n			}\n\n		} else {\n			$ex = parse($body);\n			$ex.= ($showlinkwithbody) ? doTag($linklabel . permlink(array(), $linktext),$linkwraptag,$linkclass) : \'\';\n		}\n\n		return $ex;\n	}\n\n// ----------------------------------------------------\n\n	function rssAutoCloseTags($string) {\n		// automatically close HTML-Tags\n		// (usefull e.g. if you want to extract part of a blog entry or news as preview/teaser)\n		// coded by Constantin Gross <connum at googlemail dot com> / 3rd of June, 2006\n		// feel free to leave comments or to improve this function!\n		// Updates by Rob Sable <www.wilshireone.com> / 20th of August, 2006\n\n		$donotclose=array(\'br\',\'img\',\'input\'); //Tags that are not to be closed\n\n		//prepare vars and arrays\n		$tagstoclose=\'\';\n		$tags=array();\n\n		//put all opened tags into an array\n		preg_match_all(\"/<(([A-Z]|[a-z]).*)(( )|(>))/isU\",$string,$result);\n		$openedtags=$result[1];\n		$openedtags=array_reverse($openedtags); //this is just done so that the order of the closed tags in the end will be better\n\n		//put all closed tags into an array\n		preg_match_all(\"/<\\/(([A-Z]|[a-z]).*)(( )|(>))/isU\",$string,$result2);\n		$closedtags=$result2[1];\n\n		//look up which tags still have to be closed and put them in an array\n		for ($i=0;$i<count($openedtags);$i++) {\n			 if (in_array($openedtags[$i],$closedtags)) { unset($closedtags[array_search($openedtags[$i],$closedtags)]); }\n					 else array_push($tags, $openedtags[$i]);\n		}\n		//$tags=array_reverse($tags); //now this reversion is done again for a better order of close-tags\n\n		//prepare the close-tags for output\n		for($x=0;$x<count($tags);$x++) {\n		$add=strtolower(trim($tags[$x]));\n		if(!in_array($add,$donotclose)) $tagstoclose.=\'</\'.$add.\'>\';\n		}\n\n		//and finally\n		return $tagstoclose;\n	}\n', '', '', '', 0, 5, 0),
('act_if_mobile', 0, 'Andrew Ilin (and Andy Moore)', 'http://www.angrycat.info', '0.1.2', 'This plugin adds conditional tag to determine, if user is browsing from mobile device, or not', '<h1> &lt;txp:act_if_mobile /&gt; </h1>\n\n<p>This plugin adds conditional tag to determine, if user is browsing from mobile device, or not (using \"else\" clause). Browser detection function by <a href=\"http://www.andymoore.info/\">Andy Moore</a> - dotMobi Certified Mobile Web Developer. I\'ve just wraped it into TxP plugin.</p>\n\n<p>Small modification for php 5 compatibility</p>\n\n<pre><code>\n&lt;txp:act_if_mobile&gt;<br/>\n	Content for mobile browser<br/>\n&lt;txp:else/&gt;<br/>\n	Content for other browsers<br/>\n&lt;/txp:act_if_mobile&gt;\n</code></pre>', '// Условный тэг для определения зашел ли полбзователь с мобильного устройства или с большого брата\r\n// Функция определения от Andy Moore - dotMobi Certified Mobile Web Developer http://www.andymoore.info/\r\n// Я только завернул её в плагин\r\n\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n    ->register(\'act_if_mobile\');\r\n}\r\n\r\n\r\nfunction act_if_mobile($attr, $thing){\r\n	if ( detect_mobile_device() ) {\r\n		return parse( EvalElse( $thing, true ) );\r\n	}\r\n	return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction detect_mobile_device(){\r\n\r\n	// check if the user agent value claims to be windows but not windows mobile\r\n	if(stristr($_SERVER[\'HTTP_USER_AGENT\'],\'windows\')&&!stristr($_SERVER[\'HTTP_USER_AGENT\'],\'windows ce\')){\r\n		return false;\r\n	}\r\n	// check if the user agent gives away any tell tale signs it\'s a mobile browser\r\nif(preg_match(\'/(up.browser|up.link|windows ce|iemobile|mini|mmp|symbian|midp|wap|phone|pocket|mobile|pda|psp)/i\', $_SERVER[\'HTTP_USER_AGENT\']))\r\n{\r\n	return true;\r\n}\r\n	// check the http accept header to see if wap.wml or wap.xhtml support is claimed\r\n	if(stristr($_SERVER[\'HTTP_ACCEPT\'],\'text/vnd.wap.wml\')||stristr($_SERVER[\'HTTP_ACCEPT\'],\'application/vnd.wap.xhtml+xml\')){\r\n		return true;\r\n	}\r\n	// check if there are any tell tales signs it\'s a mobile device from the _server headers\r\n	if(isset($_SERVER[\'HTTP_X_WAP_PROFILE\'])||isset($_SERVER[\'HTTP_PROFILE\'])||isset($_SERVER[\'X-OperaMini-Features\'])||isset($_SERVER[\'UA-pixels\'])){\r\n		return true;\r\n	}\r\n	// build an array with the first four characters from the most common mobile user agents\r\n	$a = array(\r\n		\'acs-\'=>\'acs-\',\r\n		\'alav\'=>\'alav\',\r\n		\'alca\'=>\'alca\',\r\n		\'amoi\'=>\'amoi\',\r\n		\'audi\'=>\'audi\',\r\n		\'aste\'=>\'aste\',\r\n		\'avan\'=>\'avan\',\r\n		\'benq\'=>\'benq\',\r\n		\'bird\'=>\'bird\',\r\n		\'blac\'=>\'blac\',\r\n		\'blaz\'=>\'blaz\',\r\n		\'brew\'=>\'brew\',\r\n		\'cell\'=>\'cell\',\r\n		\'cldc\'=>\'cldc\',\r\n		\'cmd-\'=>\'cmd-\',\r\n		\'dang\'=>\'dang\',\r\n		\'doco\'=>\'doco\',\r\n		\'eric\'=>\'eric\',\r\n		\'hipt\'=>\'hipt\',\r\n		\'inno\'=>\'inno\',\r\n		\'ipaq\'=>\'ipaq\',\r\n		\'java\'=>\'java\',\r\n		\'jigs\'=>\'jigs\',\r\n		\'kddi\'=>\'kddi\',\r\n		\'keji\'=>\'keji\',\r\n		\'leno\'=>\'leno\',\r\n		\'lg-c\'=>\'lg-c\',\r\n		\'lg-d\'=>\'lg-d\',\r\n		\'lg-g\'=>\'lg-g\',\r\n		\'lge-\'=>\'lge-\',\r\n		\'maui\'=>\'maui\',\r\n		\'maxo\'=>\'maxo\',\r\n		\'midp\'=>\'midp\',\r\n		\'mits\'=>\'mits\',\r\n		\'mmef\'=>\'mmef\',\r\n		\'mobi\'=>\'mobi\',\r\n		\'mot-\'=>\'mot-\',\r\n		\'moto\'=>\'moto\',\r\n		\'mwbp\'=>\'mwbp\',\r\n		\'nec-\'=>\'nec-\',\r\n		\'newt\'=>\'newt\',\r\n		\'noki\'=>\'noki\',\r\n		\'opwv\'=>\'opwv\',\r\n		\'palm\'=>\'palm\',\r\n		\'pana\'=>\'pana\',\r\n		\'pant\'=>\'pant\',\r\n		\'pdxg\'=>\'pdxg\',\r\n		\'phil\'=>\'phil\',\r\n		\'play\'=>\'play\',\r\n		\'pluc\'=>\'pluc\',\r\n		\'port\'=>\'port\',\r\n		\'prox\'=>\'prox\',\r\n		\'qtek\'=>\'qtek\',\r\n		\'qwap\'=>\'qwap\',\r\n		\'sage\'=>\'sage\',\r\n		\'sams\'=>\'sams\',\r\n		\'sany\'=>\'sany\',\r\n		\'sch-\'=>\'sch-\',\r\n		\'sec-\'=>\'sec-\',\r\n		\'send\'=>\'send\',\r\n		\'seri\'=>\'seri\',\r\n		\'sgh-\'=>\'sgh-\',\r\n		\'shar\'=>\'shar\',\r\n		\'sie-\'=>\'sie-\',\r\n		\'siem\'=>\'siem\',\r\n		\'smal\'=>\'smal\',\r\n		\'smar\'=>\'smar\',\r\n		\'sony\'=>\'sony\',\r\n		\'sph-\'=>\'sph-\',\r\n		\'symb\'=>\'symb\',\r\n		\'t-mo\'=>\'t-mo\',\r\n		\'teli\'=>\'teli\',\r\n		\'tim-\'=>\'tim-\',\r\n		\'tosh\'=>\'tosh\',\r\n		\'treo\'=>\'treo\',\r\n		\'tsm-\'=>\'tsm-\',\r\n		\'upg1\'=>\'upg1\',\r\n		\'upsi\'=>\'upsi\',\r\n		\'vk-v\'=>\'vk-v\',\r\n		\'voda\'=>\'voda\',\r\n		\'wap-\'=>\'wap-\',\r\n		\'wapa\'=>\'wapa\',\r\n		\'wapi\'=>\'wapi\',\r\n		\'wapp\'=>\'wapp\',\r\n		\'wapr\'=>\'wapr\',\r\n		\'webc\'=>\'webc\',\r\n		\'winw\'=>\'winw\',\r\n		\'winw\'=>\'winw\',\r\n		\'xda-\'=>\'xda-\'\r\n	);\r\n	// check if the first four characters of the current user agent are set as a key in the array\r\n	if(isset($a[substr($_SERVER[\'HTTP_USER_AGENT\'],0,4)])){\r\n		return true;\r\n	}\r\n}', '// Условный тэг для определения зашел ли полбзователь с мобильного устройства или с большого брата\r\n// Функция определения от Andy Moore - dotMobi Certified Mobile Web Developer http://www.andymoore.info/\r\n// Я только завернул её в плагин\r\n\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n    ->register(\'act_if_mobile\');\r\n}\r\n\r\n\r\nfunction act_if_mobile($attr, $thing){\r\n	if ( detect_mobile_device() ) {\r\n		return parse( EvalElse( $thing, true ) );\r\n	}\r\n	return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction detect_mobile_device(){\r\n\r\n	// check if the user agent value claims to be windows but not windows mobile\r\n	if(stristr($_SERVER[\'HTTP_USER_AGENT\'],\'windows\')&&!stristr($_SERVER[\'HTTP_USER_AGENT\'],\'windows ce\')){\r\n		return false;\r\n	}\r\n	// check if the user agent gives away any tell tale signs it\'s a mobile browser\r\nif(preg_match(\'/(up.browser|up.link|windows ce|iemobile|mini|mmp|symbian|midp|wap|phone|pocket|mobile|pda|psp)/i\', $_SERVER[\'HTTP_USER_AGENT\']))\r\n{\r\n	return true;\r\n}\r\n	// check the http accept header to see if wap.wml or wap.xhtml support is claimed\r\n	if(stristr($_SERVER[\'HTTP_ACCEPT\'],\'text/vnd.wap.wml\')||stristr($_SERVER[\'HTTP_ACCEPT\'],\'application/vnd.wap.xhtml+xml\')){\r\n		return true;\r\n	}\r\n	// check if there are any tell tales signs it\'s a mobile device from the _server headers\r\n	if(isset($_SERVER[\'HTTP_X_WAP_PROFILE\'])||isset($_SERVER[\'HTTP_PROFILE\'])||isset($_SERVER[\'X-OperaMini-Features\'])||isset($_SERVER[\'UA-pixels\'])){\r\n		return true;\r\n	}\r\n	// build an array with the first four characters from the most common mobile user agents\r\n	$a = array(\r\n		\'acs-\'=>\'acs-\',\r\n		\'alav\'=>\'alav\',\r\n		\'alca\'=>\'alca\',\r\n		\'amoi\'=>\'amoi\',\r\n		\'audi\'=>\'audi\',\r\n		\'aste\'=>\'aste\',\r\n		\'avan\'=>\'avan\',\r\n		\'benq\'=>\'benq\',\r\n		\'bird\'=>\'bird\',\r\n		\'blac\'=>\'blac\',\r\n		\'blaz\'=>\'blaz\',\r\n		\'brew\'=>\'brew\',\r\n		\'cell\'=>\'cell\',\r\n		\'cldc\'=>\'cldc\',\r\n		\'cmd-\'=>\'cmd-\',\r\n		\'dang\'=>\'dang\',\r\n		\'doco\'=>\'doco\',\r\n		\'eric\'=>\'eric\',\r\n		\'hipt\'=>\'hipt\',\r\n		\'inno\'=>\'inno\',\r\n		\'ipaq\'=>\'ipaq\',\r\n		\'java\'=>\'java\',\r\n		\'jigs\'=>\'jigs\',\r\n		\'kddi\'=>\'kddi\',\r\n		\'keji\'=>\'keji\',\r\n		\'leno\'=>\'leno\',\r\n		\'lg-c\'=>\'lg-c\',\r\n		\'lg-d\'=>\'lg-d\',\r\n		\'lg-g\'=>\'lg-g\',\r\n		\'lge-\'=>\'lge-\',\r\n		\'maui\'=>\'maui\',\r\n		\'maxo\'=>\'maxo\',\r\n		\'midp\'=>\'midp\',\r\n		\'mits\'=>\'mits\',\r\n		\'mmef\'=>\'mmef\',\r\n		\'mobi\'=>\'mobi\',\r\n		\'mot-\'=>\'mot-\',\r\n		\'moto\'=>\'moto\',\r\n		\'mwbp\'=>\'mwbp\',\r\n		\'nec-\'=>\'nec-\',\r\n		\'newt\'=>\'newt\',\r\n		\'noki\'=>\'noki\',\r\n		\'opwv\'=>\'opwv\',\r\n		\'palm\'=>\'palm\',\r\n		\'pana\'=>\'pana\',\r\n		\'pant\'=>\'pant\',\r\n		\'pdxg\'=>\'pdxg\',\r\n		\'phil\'=>\'phil\',\r\n		\'play\'=>\'play\',\r\n		\'pluc\'=>\'pluc\',\r\n		\'port\'=>\'port\',\r\n		\'prox\'=>\'prox\',\r\n		\'qtek\'=>\'qtek\',\r\n		\'qwap\'=>\'qwap\',\r\n		\'sage\'=>\'sage\',\r\n		\'sams\'=>\'sams\',\r\n		\'sany\'=>\'sany\',\r\n		\'sch-\'=>\'sch-\',\r\n		\'sec-\'=>\'sec-\',\r\n		\'send\'=>\'send\',\r\n		\'seri\'=>\'seri\',\r\n		\'sgh-\'=>\'sgh-\',\r\n		\'shar\'=>\'shar\',\r\n		\'sie-\'=>\'sie-\',\r\n		\'siem\'=>\'siem\',\r\n		\'smal\'=>\'smal\',\r\n		\'smar\'=>\'smar\',\r\n		\'sony\'=>\'sony\',\r\n		\'sph-\'=>\'sph-\',\r\n		\'symb\'=>\'symb\',\r\n		\'t-mo\'=>\'t-mo\',\r\n		\'teli\'=>\'teli\',\r\n		\'tim-\'=>\'tim-\',\r\n		\'tosh\'=>\'tosh\',\r\n		\'treo\'=>\'treo\',\r\n		\'tsm-\'=>\'tsm-\',\r\n		\'upg1\'=>\'upg1\',\r\n		\'upsi\'=>\'upsi\',\r\n		\'vk-v\'=>\'vk-v\',\r\n		\'voda\'=>\'voda\',\r\n		\'wap-\'=>\'wap-\',\r\n		\'wapa\'=>\'wapa\',\r\n		\'wapi\'=>\'wapi\',\r\n		\'wapp\'=>\'wapp\',\r\n		\'wapr\'=>\'wapr\',\r\n		\'webc\'=>\'webc\',\r\n		\'winw\'=>\'winw\',\r\n		\'winw\'=>\'winw\',\r\n		\'xda-\'=>\'xda-\'\r\n	);\r\n	// check if the first four characters of the current user agent are set as a key in the array\r\n	if(isset($a[substr($_SERVER[\'HTTP_USER_AGENT\'],0,4)])){\r\n		return true;\r\n	}\r\n}', 'ee5696a19576ae07ea05e8790e48c78d', '', '', 0, 5, 0),
('etc_easter_eggs', 0, '', '', '0.2', '', '', 'register_callback(\'etc_prefs\', \'pretext_end\');\r\nregister_callback(\'etc_prefs\', \'skin\', \'\', 1);\r\n\r\nfunction etc_prefs($event, $step) {\r\n    global $prefs;\r\n\r\n    // PHP functions used in <txp:evaluate />: comma-separated list of \'name\' or \'alias:name\'\r\n    $prefs[\'txp_evaluate_functions\'] = \'md5, replace:preg_replace\';\r\n\r\n    // Max form nesting depth\r\n    $prefs[\'form_circular_depth\'] = 15;\r\n\r\n    // Parse css?\r\n    $prefs[\'parse_css\'] = false;\r\n\r\n    // Mimetypes\r\n    $prefs[\'assets_mimetypes\'] = \';css=\"text/css\",\r\n        txt=\"text/plain\",\r\n        csv=\"text/csv\",\r\n        htm=\"text/html\",\r\n        html=\"text/html\",\r\n        js=\"application/javascript\",\r\n        json=\"application/json\",\r\n        xml=\"application/xml\",\r\n        svg=\"image/svg+xml\"\';\r\n}', 'register_callback(\'etc_prefs\', \'pretext_end\');\r\nregister_callback(\'etc_prefs\', \'skin\', \'\', 1);\r\n\r\nfunction etc_prefs($event, $step) {\r\n    global $prefs;\r\n\r\n    // PHP functions used in <txp:evaluate />: comma-separated list of \'name\' or \'alias:name\'\r\n    $prefs[\'txp_evaluate_functions\'] = \'md5, replace:preg_replace\';\r\n\r\n    // Max form nesting depth\r\n    $prefs[\'form_circular_depth\'] = 15;\r\n\r\n    // Parse css?\r\n    $prefs[\'parse_css\'] = false;\r\n\r\n    // Mimetypes\r\n    $prefs[\'assets_mimetypes\'] = \';css=\"text/css\",\r\n        txt=\"text/plain\",\r\n        csv=\"text/csv\",\r\n        htm=\"text/html\",\r\n        html=\"text/html\",\r\n        js=\"application/javascript\",\r\n        json=\"application/json\",\r\n        xml=\"application/xml\",\r\n        svg=\"image/svg+xml\"\';\r\n}', '91e29b8b1b8259ad1400b3a0061a8b87', '', '', 5, 5, 0),
('ext_com_connect_verify', 0, 'Stef Dawson', 'http://stefdawson.com/', '0.1', 'Use patterns to prevent spam', '', 'register_callback(\'ext_com_connect_verify\', \'comconnect.submit\');\r\n\r\nfunction ext_com_connect_verify()\r\n{\r\n    $emailField = trim(ps(\'Email\'));\r\n    $banPatterns = explode(\"\\n\", fetch_form(\'comcon_spam_patterns\'));\r\n\r\n    $evaluation = &get_comconnect_evaluator();\r\n\r\n    // If the email address matches one of the given patterns, fail.\r\n    foreach ($banPatterns as $pattern) {\r\n        $pat = trim($pattern);\r\n\r\n        if ($pat && preg_match($pat, $emailField)) {\r\n            $evaluation -> add_comconnect_status(1);\r\n        }\r\n    }\r\n\r\n    return;\r\n}', 'register_callback(\'ext_com_connect_verify\', \'comconnect.submit\');\r\n\r\nfunction ext_com_connect_verify()\r\n{\r\n    $emailField = trim(ps(\'Email\'));\r\n    $banPatterns = explode(\"\\n\", fetch_form(\'comcon_spam_patterns\'));\r\n\r\n    $evaluation = &get_comconnect_evaluator();\r\n\r\n    // If the email address matches one of the given patterns, fail.\r\n    foreach ($banPatterns as $pattern) {\r\n        $pat = trim($pattern);\r\n\r\n        if ($pat && preg_match($pat, $emailField)) {\r\n            $evaluation -> add_comconnect_status(1);\r\n        }\r\n    }\r\n\r\n    return;\r\n}', '63bccae32c182d55d38b544f9e4a1779', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('fha_time_diff', 0, 'Filip Hajny', 'http://filip.hajny.net', '0.23', 'Returns time difference from the date set via arguments.', '<p>The plugin takes in individual parts of date and time, i. e. year, month, hour etc. and return the time elapsed/remaining since/until now. Both past and future dates are allowed and processed properly. If some units are not provided, no values for these units are returned either - provided that you \'trim\' the values list from the right, i. e. at least the year needs to be specified. Also, there shouldn\'t be any gaps in the sequence logic, i. e. if you want minutes calculated, you must specify hours as well (and provide a <code>hour=\"0\"</code> if the hour was zero).</p>\n	<p>Some examples:</p>\n\n    <dl>\n	    <dt><code>&lt;txp:fha_time_diff year=\"2004\" month=\"3\" day=\"23\" hour=\"2\" minute=\"40\" /&gt;</code></dt>\n		<dd>Returns a complete time information.</dd>\n        <dt><code>&lt;txp:fha_time_diff year=\"2000\" month=\"10\" /&gt;</code></dt>\n		<dd>Returns months and years since October 2000.</dd>\n        <dt><code>&lt;txp:fha_time_diff year=\"1980\" month=\"5\" day=\"1\" /&gt;</code></dt>\n		<dd>Returns years, months and days since May 1, 1980.</dd>\n        <dt><code>&lt;txp:fha_time_diff year=\"1975\" /&gt;</code></dt>\n		<dd>Only returns years since 1975.</dd>\n    </dl>\n\n	<p>Furthermore, there two special arguments, which change the way time is calculated.</p>\n	<dl>\n		<dt><code>posted=\"\"</code></dt>\n		<dd>Can be specified instead of the date/time values above. If set to any value (as in posted=\"yes\"), it takes in the current article\'s posted date (provided there is such a context). Currently it works in article forms, NOT in article bodies. (The original intention is to use this plugin instead of the <code>&lt;txp:posted /&gt;</code> tag for better date/time handling than the built-in \"since\" function.)</dd>\n		<dt><code>basedate=\"\"</code></dt>\n		<dd>Allows to set a date/time, which will be taken as a base to run calculations against (instead of the default <em>now</em>). The date/time format should adhere to the usual syntax (for examples see corresponding date and time section on this <a href=\"http://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#SEC100\">GNU manual page</a>).</dd>\n	</dl>\n\n    <p>Additionally, these arguments can be passed to the plugin. (They\'re not necessary for basic use with English language.)</p>\n\n    <dl>\n        <dt><code>lang=\"\"</code></dt>\n		<dd>Sets a language (two chars) for unit words; see plugin source for languages provided and hints on how to add others. If not set, the plugin tries the site wide language setting, otherwise &#8216;en&#8217; is assumed.</dd>\n        <dt><code>simpleplurals=\"\"</code></dt>\n		<dd>If set to a non-zero number, indicates which amount triggers the plural form (e. g. &#8216;years&#8217; instead of &#8216;year&#8217;). If set to zero, turns on special word forms handling that always follows the last digit (e. g. necessary for Czech dates). If not set, defaults to a language-specific value (2 for English, 0 for Czech).</dd>\n        <dt><code>delimiter=\"\"</code></dt>\n		<dd>Sets string that should separate individual values. If not set, a comma with a space are assumed.</dd>\n        <dt><code>last=\"\"</code></dt>\n		<dd>Sets string that should be used as a final delimiter, i. e. to go before the last used date/time item. If not specified (and no delimiter is set), defaults to language specific value, e. g. \" and \" in English.</dd>\n		<dt><code>limit=\"\"</code></dt>\n		<dd>Limits number of date/time entities returned to the first <strong>n</strong> of them. E. g. if the entire set of year/month/day/hour/minute values was to be outputted, setting this property to 2 would only output years and months (or years and days, if months are zero and so on).</dd>\n		<dt><code>since=\"\"</code></dt>\n		<dd>The word attached to the result in case the event date is in the past. There is no default value. If both <em>since</em> and <em>until</em> are specified, the plugin use one of them according the final result. Defaults to language specific values (provided the <code>attach</code> argument is set).</dd>\n		<dt><code>until=\"\"</code></dt>\n		<dd>The word attached to the result in case the event date is in the future. There is no default value. If both <em>since</em> and <em>until</em> are specified, the plugin use one of them according the final result. Defaults to language specific values (provided the <code>attach</code> argument is set).</dd>\n		<dt><code>attach=\"\"</code></dt>\n		<dd>A switch that turns on attaching of since/until words and also specifies whether they are to be attached before or after the result. Valid values are \"before\", \"after\" and \"auto\" (which will use the language specific position). There is no default value, so you have to provide this argument to use this feature.</dd>\n		<dt><code>wraptag=\"\"</code></dt>\n		<dd>Sets the tag to wrap the result with (e. g. \"span\"). Use only the tag name, not the brackets or anything.</dd>\n		<dt><code>class=\"\"</code></dt>\n		<dd>Sets the CSS class for the wraptag (e. g. \"articleDate\") for styling purposes.</dd>\n    </dl>\n\n    <p>The plugin is based upon a script written by <a href=\"http://www.kathmann.com/up/\">Mark H. Kathmann</a>.</p>', 'if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n    ->register(\'fha_time_diff\');\r\n}\r\n\r\n\r\nfunction fha_time_diff($atts) {\r\n\r\n	if (is_array($atts)) extract($atts);\r\n\r\n/*\r\n	// declare your language here (e. g. \'de\' for German etc.)\r\n	$l = \'en\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	// an integer for simple plurals - enter the first number that uses a plural form (usually 2)\r\n	// or zero to activate last-digit specific forms (e. g. for Czech language)\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	// replace numbers with appropriate word forms for these amounts (e. g. \'years\', \'year\' etc.)\r\n	// if using simpleplurals, only first two entries need to be entered (0 is plural form, 1 is singular form)\r\n	$words[$l][\'year\']   = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'month\']  = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'day\']    = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'hour\']   = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'minute\'] = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	// if NOT using simpleplurals, fill array with numbers, which ALWAYS get a plural form (ignoring the ending digit)\r\n	$words[$l][\'override\'] = array(11,12,13,14);\r\n	// words to attach to the final result in case of a past/future event date\r\n	$words[$l][\'since\'] = \'\';\r\n	$words[$l][\'until\'] = \'\';\r\n	// position where to attach the above words, valid values are \'before\' and \'after\'\r\n	$words[$l][\'attach\'] = \'\';\r\n	// word to use as a final delimiter (before the last used date/item item)\r\n	$words[$l][\'last\'] = \'\';\r\n*/\r\n	\r\n	$l = \'en\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'years\',\'year\');\r\n	$words[$l][\'month\']  = array(\'months\',\'month\');\r\n	$words[$l][\'day\']    = array(\'days\',\'day\');\r\n	$words[$l][\'hour\']   = array(\'hours\',\'hour\');\r\n	$words[$l][\'minute\'] = array(\'minutes\',\'minute\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'since\';\r\n	$words[$l][\'until\'] = \'until\';\r\n	$words[$l][\'attach\'] = \'after\';\r\n	$words[$l][\'last\'] = \' and \';\r\n\r\n	$l = \'de\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'Jahre\',\'Jahr\');\r\n	$words[$l][\'month\']  = array(\'Monate\',\'Monat\');\r\n	$words[$l][\'day\']    = array(\'Tage\',\'Tag\');\r\n	$words[$l][\'hour\']   = array(\'Stunden\',\'Stunde\');\r\n	$words[$l][\'minute\'] = array(\'Minuten\',\'Minute\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'schon\';\r\n	$words[$l][\'until\'] = \'noch\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \' und \';\r\n\r\n	$l = \'cz\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 0;\r\n	$words[$l][\'year\']   = array(\'let\',\'rok\',\'roky\',\'roky\',\'roky\',\'let\',\'let\',\'let\',\'let\',\'let\');\r\n	$words[$l][\'month\']  = array(\'m?síc?\',\'m?síc\',\'m?síce\',\'m?síce\',\'m?síce\',\'m?síc?\',\'m?síc?\',\'m?síc?\',\'m?síc?\',\'m?síc?\');\r\n	$words[$l][\'day\']    = array(\'dn?\',\'den\',\'dny\',\'dny\',\'dny\',\'dn?\',\'dn?\',\'dn?\',\'dn?\',\'dn?\');\r\n	$words[$l][\'hour\']   = array(\'hodin\',\'hodina\',\'hodiny\',\'hodiny\',\'hodiny\',\'hodin\',\'hodin\',\'hodin\',\'hodin\',\'hodin\');\r\n	$words[$l][\'minute\'] = array(\'minut\',\'minuta\',\'minuty\',\'minuty\',\'minuty\',\'minut\',\'minut\',\'minut\',\'minut\',\'minut\');\r\n	$words[$l][\'override\'] = array(11,12,13,14);\r\n	$words[$l][\'since\'] = \'ji?\';\r\n	$words[$l][\'until\'] = \'je?t?\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \' a \';\r\n	\r\n	$l = \'nl\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'jaar\',\'jaar\');\r\n	$words[$l][\'month\']  = array(\'maanden\',\'maand\');\r\n	$words[$l][\'day\']    = array(\'dagen\',\'dag\');\r\n	$words[$l][\'hour\']   = array(\'uur\',\'uur\');\r\n	$words[$l][\'minute\'] = array(\'minuten\',\'minuut\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'\';\r\n	$words[$l][\'until\'] = \'\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \'\';\r\n\r\n\r\n	if (empty($lang)) $lang = LANG;\r\n	if (!in_array(strtolower($lang), $words)) $lang = \'en\';\r\n		else $lang = strtolower($lang);\r\n	if (empty($simpleplurals)) $simpleplurals = $words[$lang][\'simpleplurals\'];\r\n	if (empty($last)) $last = (empty($delimiter)) ? $words[$lang][\'last\'] : $delimiter;\r\n	if (empty($delimiter)) $delimiter = \', \';\r\n	if (empty($class)) $classWord = \'\';\r\n		else $classWord = \" class=\\\"$class\\\"\";\r\n	if (empty($wraptag)) {\r\n		$wrapOpen = \'\';\r\n		$wrapClose = \'\';\r\n	}\r\n	else {\r\n		$wrapOpen = \"<$wraptag$classWord>\";\r\n		$wrapClose = \"</$wraptag>\";\r\n	}\r\n	if (empty($attach)) $attach = \'no\';\r\n	  elseif ($attach == \'auto\') $attach = $words[$lang][\'attach\'];\r\n	if (empty($since)) $since = $words[$lang][\'since\'];\r\n	if (empty($until)) $until = $words[$lang][\'until\'];\r\n	if (empty($limit) || $limit < 1) $limit = 5;\r\n\r\n	if (!empty($posted)) {\r\n		global $thisarticle;\r\n		$year = date(\'Y\', $thisarticle[\'posted\']);\r\n		$month = date(\'n\', $thisarticle[\'posted\']);\r\n		$day = date(\'j\', $thisarticle[\'posted\']);\r\n		$hour = date(\'G\', $thisarticle[\'posted\']);\r\n		$minute = date(\'i\', $thisarticle[\'posted\']);\r\n	}\r\n\r\n	$today = (!empty($from)) ? strtotime($from) : time();\r\n	$todayYear    = date(\'Y\', $today);\r\n	$todayMonth  = date(\'n\', $today);\r\n	$todayDay    = date(\'j\', $today);\r\n	$todayHour   = date(\'G\', $today);\r\n	$todayMinute = date(\'i\', $today);\r\n	$monthday    = date(\'md\', $today);\r\n\r\n	if (isset($month)) $month = str_pad($month,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($day)) $day = str_pad($day,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($hour)) $hour = str_pad($hour,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($minute)) $minute = str_pad($minute,2,\'0\',STR_PAD_LEFT);\r\n	$m = (isset($minute)?$minute:\'00\');\r\n	$hm = (isset($hour)?$hour:\'00\').$m;\r\n	$dhm = (isset($day)?$day:\'01\').$hm;\r\n	$mdhm = (isset($month)?$month:\'01\').$dhm;\r\n	$past = (strtotime((isset($year)?$year:\'00\').\'-\'.(isset($month)?$month:\'01\').\'-\'.(isset($day)?$day:\'01\').\' \'.(isset($hour)?$hour:\'00\').\':\'.(isset($minute)?$minute:\'00\')) < time()) ? (true) : (false);\r\n\r\n	if (isset($year) && $limit > 0) {\r\n\r\n		$years = abs($todayYear - $year);\r\n		if (($past && date(\'mdHi\', $today) < $mdhm) || (!$past && date(\'mdHi\', $today) > $mdhm)) $years--;\r\n\r\n		if (!$simpleplurals) {\r\n			$yearWord = ($years > 0) ? $words[$lang][\'year\'][substr($years,-1)] : \'\';\r\n			if (in_array(substr($years,-2),$words[$lang][\'override\']) !== false) $yearWord = $words[$lang][\'year\'][0];\r\n		}\r\n		else $yearWord = ($years < $simpleplurals) ? $words[$lang][\'year\'][1] : $words[$lang][\'year\'][0];\r\n\r\n		if ($years > 0) $limit--;\r\n\r\n\r\n		if (isset($month) && $limit > 0) {\r\n\r\n			if ($past) $months = ($todayMonth > $month) ? ($todayMonth - $month) : (12 - $month + $todayMonth);\r\n			else       $months = ($todayMonth < $month) ? ($month - $todayMonth) : (12 - $todayMonth + $month);\r\n\r\n			if (($past && date(\'dHi\', $today) < $dhm) || (!$past && date(\'dHi\', $today) > $dhm)) $months--;\r\n			if ($months == 12) $months = 0;\r\n\r\n			if (!$simpleplurals) {\r\n				$monthWord = ($months > 0) ? $words[$lang][\'month\'][substr($months,-1)] : \'\';\r\n				if (in_array(substr($months,-2),$words[$lang][\'override\']) !== false) $monthWord = $words[$lang][\'month\'][0];\r\n			}\r\n			else $monthWord = ($months < $simpleplurals) ? $words[$lang][\'month\'][1] : $words[$lang][\'month\'][0];\r\n\r\n			if ($months > 0) $limit--;\r\n\r\n\r\n			if (isset($day) && $limit > 0) {\r\n\r\n				if ($past) $days = ($todayDay > $day) ? ($todayDay - $day) : (date(\'t\', strtotime(\"$year/$month/$day\")) - $day + $todayDay);\r\n				else       $days = ($todayDay < $day) ? ($day - $todayDay) : (date(\'t\', strtotime(($month==1)?($year-1):($year).\'/\'.($month==1)?(12):($month-1).\'/\'.$day)) - $todayDay + $day);\r\n				if (($past && date(\'Hi\', $today) < $hm) || (!$past && date(\'Hi\', $today) > $hm)) $days--;\r\n				$compDate = ($past) ? date(\'t\', strtotime(\"$year/$month/$day\")) : date(\'t\', strtotime(($month==1)?($year-1):($year).\'/\'.($month==1)?(12):($month-1).\'/\'.$day));\r\n				if ($days == $compDate) $days = 0;\r\n\r\n				if (!$simpleplurals) {\r\n					$dayWord = ($days > 0) ? $words[$lang][\'day\'][substr($days,-1)] : \'\';\r\n					if (in_array(substr($days,-2),$words[$lang][\'override\']) !== false) $dayWord = $words[$lang][\'day\'][0];\r\n				}\r\n				else $dayWord = ($days < $simpleplurals) ? $words[$lang][\'day\'][1] : $words[$lang][\'day\'][0];\r\n\r\n				if ($days > 0) $limit--;\r\n\r\n\r\n				if (isset($hour) && $limit > 0) {\r\n\r\n					if ($past) $hours = ($todayHour > $hour) ? ($todayHour - $hour) : (24 - $hour + $todayHour);\r\n					else       $hours = ($todayHour < $hour) ? ($hour - $todayHour) : (24 - $todayHour + $hour);\r\n\r\n					if (($past && date(\'i\', $today) < $minute) || (!$past && date(\'i\', $today) > $minute)) $hours--;\r\n					if ($hours == 24) $hours = 0;\r\n\r\n					if (!$simpleplurals) {\r\n						$hourWord = ($hours > 0) ? $words[$lang][\'hour\'][substr($hours,-1)] : \'\';\r\n						if (in_array(substr($hours,-2),$words[$lang][\'override\']) !== false) $hourWord = $words[$lang][\'hour\'][0];\r\n					}\r\n					else $hourWord = ($hours < $simpleplurals) ? $words[$lang][\'hour\'][1] : $words[$lang][\'hour\'][0];\r\n\r\n					if ($hours > 0) $limit--;\r\n\r\n\r\n					if (isset($minute) && $limit > 0) {\r\n\r\n						if ($past) $minutes = ($todayMinute > $minute) ? ($todayMinute - $minute) : (60 - $minute + $todayMinute);\r\n						else       $minutes = ($todayMinute < $minute) ? ($minute - $todayMinute) : (60 - $todayMinute + $minute);\r\n						if ($minutes == 60) $minutes = 0;\r\n\r\n						if (!$simpleplurals) {\r\n							$minuteWord = ($minutes > 0) ? $words[$lang][\'minute\'][substr($minutes,-1)] : \'\';\r\n							if (in_array(substr($minutes,-2),$words[$lang][\'override\']) !== false) $minuteWord = $words[$lang][\'minute\'][0];\r\n						}\r\n						else $minuteWord = ($minutes < $simpleplurals) ? $words[$lang][\'minute\'][1] : $words[$lang][\'minute\'][0];\r\n\r\n					}\r\n				}\r\n			}\r\n		}\r\n	}\r\n\r\n	if (!empty($minutes)) $out = (empty($out)) ? (\"$last$minutes $minuteWord\") : (\"$delimiter$minutes $minuteWord$out\");\r\n	if (!empty($hours)) $out = (empty($out)) ? (\"$last$hours $hourWord\") : (\"$delimiter$hours $hourWord$out\");\r\n	if (!empty($days)) $out = (empty($out)) ? (\"$last$days $dayWord\") : (\"$delimiter$days $dayWord$out\");\r\n	if (!empty($months)) $out = (empty($out)) ? (\"$last$months $monthWord\") : (\"$delimiter$months $monthWord$out\");\r\n	if (!empty($years)) $out = (empty($out)) ? (\"$years $yearWord\") : (\"$years $yearWord$out\");\r\n\r\n	$testOut = strstr($out, $delimiter);\r\n	if (strlen($testOut) == strlen($out)) $out = substr($out, strlen($delimiter));\r\n	$testOut = strstr($out, $last);\r\n	if (strlen($testOut) == strlen($out)) $out = substr($out, strlen($last));\r\n\r\n	if ($attach == \'before\') $out = ($past) ? (\"$since $out\") : (\"$until $out\");\r\n	elseif ($attach == \'after\') $out = ($past) ? (\"$out $since\") : (\"$out $until\");\r\n\r\n	return $wrapOpen.trim($out).$wrapClose;\r\n}\r\n', 'if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n  Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n    ->register(\'fha_time_diff\');\r\n}\r\n\r\n\r\nfunction fha_time_diff($atts) {\r\n\r\n	if (is_array($atts)) extract($atts);\r\n\r\n/*\r\n	// declare your language here (e. g. \'de\' for German etc.)\r\n	$l = \'en\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	// an integer for simple plurals - enter the first number that uses a plural form (usually 2)\r\n	// or zero to activate last-digit specific forms (e. g. for Czech language)\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	// replace numbers with appropriate word forms for these amounts (e. g. \'years\', \'year\' etc.)\r\n	// if using simpleplurals, only first two entries need to be entered (0 is plural form, 1 is singular form)\r\n	$words[$l][\'year\']   = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'month\']  = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'day\']    = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'hour\']   = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	$words[$l][\'minute\'] = array(\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\');\r\n	// if NOT using simpleplurals, fill array with numbers, which ALWAYS get a plural form (ignoring the ending digit)\r\n	$words[$l][\'override\'] = array(11,12,13,14);\r\n	// words to attach to the final result in case of a past/future event date\r\n	$words[$l][\'since\'] = \'\';\r\n	$words[$l][\'until\'] = \'\';\r\n	// position where to attach the above words, valid values are \'before\' and \'after\'\r\n	$words[$l][\'attach\'] = \'\';\r\n	// word to use as a final delimiter (before the last used date/item item)\r\n	$words[$l][\'last\'] = \'\';\r\n*/\r\n	\r\n	$l = \'en\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'years\',\'year\');\r\n	$words[$l][\'month\']  = array(\'months\',\'month\');\r\n	$words[$l][\'day\']    = array(\'days\',\'day\');\r\n	$words[$l][\'hour\']   = array(\'hours\',\'hour\');\r\n	$words[$l][\'minute\'] = array(\'minutes\',\'minute\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'since\';\r\n	$words[$l][\'until\'] = \'until\';\r\n	$words[$l][\'attach\'] = \'after\';\r\n	$words[$l][\'last\'] = \' and \';\r\n\r\n	$l = \'de\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'Jahre\',\'Jahr\');\r\n	$words[$l][\'month\']  = array(\'Monate\',\'Monat\');\r\n	$words[$l][\'day\']    = array(\'Tage\',\'Tag\');\r\n	$words[$l][\'hour\']   = array(\'Stunden\',\'Stunde\');\r\n	$words[$l][\'minute\'] = array(\'Minuten\',\'Minute\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'schon\';\r\n	$words[$l][\'until\'] = \'noch\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \' und \';\r\n\r\n	$l = \'cz\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 0;\r\n	$words[$l][\'year\']   = array(\'let\',\'rok\',\'roky\',\'roky\',\'roky\',\'let\',\'let\',\'let\',\'let\',\'let\');\r\n	$words[$l][\'month\']  = array(\'m?síc?\',\'m?síc\',\'m?síce\',\'m?síce\',\'m?síce\',\'m?síc?\',\'m?síc?\',\'m?síc?\',\'m?síc?\',\'m?síc?\');\r\n	$words[$l][\'day\']    = array(\'dn?\',\'den\',\'dny\',\'dny\',\'dny\',\'dn?\',\'dn?\',\'dn?\',\'dn?\',\'dn?\');\r\n	$words[$l][\'hour\']   = array(\'hodin\',\'hodina\',\'hodiny\',\'hodiny\',\'hodiny\',\'hodin\',\'hodin\',\'hodin\',\'hodin\',\'hodin\');\r\n	$words[$l][\'minute\'] = array(\'minut\',\'minuta\',\'minuty\',\'minuty\',\'minuty\',\'minut\',\'minut\',\'minut\',\'minut\',\'minut\');\r\n	$words[$l][\'override\'] = array(11,12,13,14);\r\n	$words[$l][\'since\'] = \'ji?\';\r\n	$words[$l][\'until\'] = \'je?t?\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \' a \';\r\n	\r\n	$l = \'nl\';\r\n	$words[] = $l;\r\n	$words[$l] = array(\'simpleplurals\',\'year\',\'month\',\'day\',\'hour\',\'minute\',\'override\',\'since\',\'until\',\'attach\',\'last\');\r\n	$words[$l][\'simpleplurals\'] = 2;\r\n	$words[$l][\'year\']   = array(\'jaar\',\'jaar\');\r\n	$words[$l][\'month\']  = array(\'maanden\',\'maand\');\r\n	$words[$l][\'day\']    = array(\'dagen\',\'dag\');\r\n	$words[$l][\'hour\']   = array(\'uur\',\'uur\');\r\n	$words[$l][\'minute\'] = array(\'minuten\',\'minuut\');\r\n	$words[$l][\'override\'] = array(0);\r\n	$words[$l][\'since\'] = \'\';\r\n	$words[$l][\'until\'] = \'\';\r\n	$words[$l][\'attach\'] = \'before\';\r\n	$words[$l][\'last\'] = \'\';\r\n\r\n\r\n	if (empty($lang)) $lang = LANG;\r\n	if (!in_array(strtolower($lang), $words)) $lang = \'en\';\r\n		else $lang = strtolower($lang);\r\n	if (empty($simpleplurals)) $simpleplurals = $words[$lang][\'simpleplurals\'];\r\n	if (empty($last)) $last = (empty($delimiter)) ? $words[$lang][\'last\'] : $delimiter;\r\n	if (empty($delimiter)) $delimiter = \', \';\r\n	if (empty($class)) $classWord = \'\';\r\n		else $classWord = \" class=\\\"$class\\\"\";\r\n	if (empty($wraptag)) {\r\n		$wrapOpen = \'\';\r\n		$wrapClose = \'\';\r\n	}\r\n	else {\r\n		$wrapOpen = \"<$wraptag$classWord>\";\r\n		$wrapClose = \"</$wraptag>\";\r\n	}\r\n	if (empty($attach)) $attach = \'no\';\r\n	  elseif ($attach == \'auto\') $attach = $words[$lang][\'attach\'];\r\n	if (empty($since)) $since = $words[$lang][\'since\'];\r\n	if (empty($until)) $until = $words[$lang][\'until\'];\r\n	if (empty($limit) || $limit < 1) $limit = 5;\r\n\r\n	if (!empty($posted)) {\r\n		global $thisarticle;\r\n		$year = date(\'Y\', $thisarticle[\'posted\']);\r\n		$month = date(\'n\', $thisarticle[\'posted\']);\r\n		$day = date(\'j\', $thisarticle[\'posted\']);\r\n		$hour = date(\'G\', $thisarticle[\'posted\']);\r\n		$minute = date(\'i\', $thisarticle[\'posted\']);\r\n	}\r\n\r\n	$today = (!empty($from)) ? strtotime($from) : time();\r\n	$todayYear    = date(\'Y\', $today);\r\n	$todayMonth  = date(\'n\', $today);\r\n	$todayDay    = date(\'j\', $today);\r\n	$todayHour   = date(\'G\', $today);\r\n	$todayMinute = date(\'i\', $today);\r\n	$monthday    = date(\'md\', $today);\r\n\r\n	if (isset($month)) $month = str_pad($month,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($day)) $day = str_pad($day,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($hour)) $hour = str_pad($hour,2,\'0\',STR_PAD_LEFT);\r\n	if (isset($minute)) $minute = str_pad($minute,2,\'0\',STR_PAD_LEFT);\r\n	$m = (isset($minute)?$minute:\'00\');\r\n	$hm = (isset($hour)?$hour:\'00\').$m;\r\n	$dhm = (isset($day)?$day:\'01\').$hm;\r\n	$mdhm = (isset($month)?$month:\'01\').$dhm;\r\n	$past = (strtotime((isset($year)?$year:\'00\').\'-\'.(isset($month)?$month:\'01\').\'-\'.(isset($day)?$day:\'01\').\' \'.(isset($hour)?$hour:\'00\').\':\'.(isset($minute)?$minute:\'00\')) < time()) ? (true) : (false);\r\n\r\n	if (isset($year) && $limit > 0) {\r\n\r\n		$years = abs($todayYear - $year);\r\n		if (($past && date(\'mdHi\', $today) < $mdhm) || (!$past && date(\'mdHi\', $today) > $mdhm)) $years--;\r\n\r\n		if (!$simpleplurals) {\r\n			$yearWord = ($years > 0) ? $words[$lang][\'year\'][substr($years,-1)] : \'\';\r\n			if (in_array(substr($years,-2),$words[$lang][\'override\']) !== false) $yearWord = $words[$lang][\'year\'][0];\r\n		}\r\n		else $yearWord = ($years < $simpleplurals) ? $words[$lang][\'year\'][1] : $words[$lang][\'year\'][0];\r\n\r\n		if ($years > 0) $limit--;\r\n\r\n\r\n		if (isset($month) && $limit > 0) {\r\n\r\n			if ($past) $months = ($todayMonth > $month) ? ($todayMonth - $month) : (12 - $month + $todayMonth);\r\n			else       $months = ($todayMonth < $month) ? ($month - $todayMonth) : (12 - $todayMonth + $month);\r\n\r\n			if (($past && date(\'dHi\', $today) < $dhm) || (!$past && date(\'dHi\', $today) > $dhm)) $months--;\r\n			if ($months == 12) $months = 0;\r\n\r\n			if (!$simpleplurals) {\r\n				$monthWord = ($months > 0) ? $words[$lang][\'month\'][substr($months,-1)] : \'\';\r\n				if (in_array(substr($months,-2),$words[$lang][\'override\']) !== false) $monthWord = $words[$lang][\'month\'][0];\r\n			}\r\n			else $monthWord = ($months < $simpleplurals) ? $words[$lang][\'month\'][1] : $words[$lang][\'month\'][0];\r\n\r\n			if ($months > 0) $limit--;\r\n\r\n\r\n			if (isset($day) && $limit > 0) {\r\n\r\n				if ($past) $days = ($todayDay > $day) ? ($todayDay - $day) : (date(\'t\', strtotime(\"$year/$month/$day\")) - $day + $todayDay);\r\n				else       $days = ($todayDay < $day) ? ($day - $todayDay) : (date(\'t\', strtotime(($month==1)?($year-1):($year).\'/\'.($month==1)?(12):($month-1).\'/\'.$day)) - $todayDay + $day);\r\n				if (($past && date(\'Hi\', $today) < $hm) || (!$past && date(\'Hi\', $today) > $hm)) $days--;\r\n				$compDate = ($past) ? date(\'t\', strtotime(\"$year/$month/$day\")) : date(\'t\', strtotime(($month==1)?($year-1):($year).\'/\'.($month==1)?(12):($month-1).\'/\'.$day));\r\n				if ($days == $compDate) $days = 0;\r\n\r\n				if (!$simpleplurals) {\r\n					$dayWord = ($days > 0) ? $words[$lang][\'day\'][substr($days,-1)] : \'\';\r\n					if (in_array(substr($days,-2),$words[$lang][\'override\']) !== false) $dayWord = $words[$lang][\'day\'][0];\r\n				}\r\n				else $dayWord = ($days < $simpleplurals) ? $words[$lang][\'day\'][1] : $words[$lang][\'day\'][0];\r\n\r\n				if ($days > 0) $limit--;\r\n\r\n\r\n				if (isset($hour) && $limit > 0) {\r\n\r\n					if ($past) $hours = ($todayHour > $hour) ? ($todayHour - $hour) : (24 - $hour + $todayHour);\r\n					else       $hours = ($todayHour < $hour) ? ($hour - $todayHour) : (24 - $todayHour + $hour);\r\n\r\n					if (($past && date(\'i\', $today) < $minute) || (!$past && date(\'i\', $today) > $minute)) $hours--;\r\n					if ($hours == 24) $hours = 0;\r\n\r\n					if (!$simpleplurals) {\r\n						$hourWord = ($hours > 0) ? $words[$lang][\'hour\'][substr($hours,-1)] : \'\';\r\n						if (in_array(substr($hours,-2),$words[$lang][\'override\']) !== false) $hourWord = $words[$lang][\'hour\'][0];\r\n					}\r\n					else $hourWord = ($hours < $simpleplurals) ? $words[$lang][\'hour\'][1] : $words[$lang][\'hour\'][0];\r\n\r\n					if ($hours > 0) $limit--;\r\n\r\n\r\n					if (isset($minute) && $limit > 0) {\r\n\r\n						if ($past) $minutes = ($todayMinute > $minute) ? ($todayMinute - $minute) : (60 - $minute + $todayMinute);\r\n						else       $minutes = ($todayMinute < $minute) ? ($minute - $todayMinute) : (60 - $todayMinute + $minute);\r\n						if ($minutes == 60) $minutes = 0;\r\n\r\n						if (!$simpleplurals) {\r\n							$minuteWord = ($minutes > 0) ? $words[$lang][\'minute\'][substr($minutes,-1)] : \'\';\r\n							if (in_array(substr($minutes,-2),$words[$lang][\'override\']) !== false) $minuteWord = $words[$lang][\'minute\'][0];\r\n						}\r\n						else $minuteWord = ($minutes < $simpleplurals) ? $words[$lang][\'minute\'][1] : $words[$lang][\'minute\'][0];\r\n\r\n					}\r\n				}\r\n			}\r\n		}\r\n	}\r\n\r\n	if (!empty($minutes)) $out = (empty($out)) ? (\"$last$minutes $minuteWord\") : (\"$delimiter$minutes $minuteWord$out\");\r\n	if (!empty($hours)) $out = (empty($out)) ? (\"$last$hours $hourWord\") : (\"$delimiter$hours $hourWord$out\");\r\n	if (!empty($days)) $out = (empty($out)) ? (\"$last$days $dayWord\") : (\"$delimiter$days $dayWord$out\");\r\n	if (!empty($months)) $out = (empty($out)) ? (\"$last$months $monthWord\") : (\"$delimiter$months $monthWord$out\");\r\n	if (!empty($years)) $out = (empty($out)) ? (\"$years $yearWord\") : (\"$years $yearWord$out\");\r\n\r\n	$testOut = strstr($out, $delimiter);\r\n	if (strlen($testOut) == strlen($out)) $out = substr($out, strlen($delimiter));\r\n	$testOut = strstr($out, $last);\r\n	if (strlen($testOut) == strlen($out)) $out = substr($out, strlen($last));\r\n\r\n	if ($attach == \'before\') $out = ($past) ? (\"$since $out\") : (\"$until $out\");\r\n	elseif ($attach == \'after\') $out = ($past) ? (\"$out $since\") : (\"$out $until\");\r\n\r\n	return $wrapOpen.trim($out).$wrapClose;\r\n}\r\n', 'f7a649b9cbd4fcea070b3938e5645525', '', '', 0, 5, 0),
('zem_redirect', 0, 'Alex Shiels', 'http://thresholdstate.com/', '1.2.1', 'Redirect Pro: handle incorrect, outdated and duplicate URLs', '<p>This plug-in\'s help documents are available to paying subscribers only. <strong>New subscriptions are currently unavailable.</strong> Current subscribers can access the help documents by visiting: <a href=\"http://thresholdstate.com/subscribers/ \">http://thresholdstate.com/subscribers/ </a></p>', '/*\r\n\r\nCopyright 2005-2007 Alex Shiels http://thresholdstate.com/\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nversion 2 as published by the Free Software Foundation.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\nGNU General Public License for more details.\r\n\r\n*/\r\n\r\nfunction zem_redirect_fix_url($url, $debug=0) {\r\n	$parts = parse_url($url);\r\n	extract($parts);\r\n	$q = preg_replace(\'@&amp;@\', \'&\', @$query);\r\n	$q = $q ? \'?\'.$q : \'\';\r\n\r\n	$host = empty($scheme) ? serverSet(\'HTTP_HOST\') : $host;\r\n	$scheme = empty($scheme) ? \'http\' : $scheme;\r\n	$port = empty($port) ? \'\' : \':\'.$port;\r\n\r\n	return $scheme.\'://\'.$host.$port.\'/\'.ltrim($path, \'/\').$q;\r\n}\r\n\r\nfunction zem_redirect_send_headers($location, $type=\'\', $debug=0) {\r\n	global $prefs, $pretext;\r\n	extract($prefs);\r\n\r\n	if ($production_status == \'debug\' or $debug) {\r\n		echo \'<div style=\"color:#000;background-color:#fee;\">\'\r\n			.\'zem_redirect: from <a href=\"\'.$pretext[\'request_uri\'].\'\">\'.$pretext[\'request_uri\'].\'</a> to <a href=\"\'.$location.\'\">\'.$location.\'</a></div>\'.n;\r\n		return $location;\r\n	}\r\n	else {\r\n		if ($production_status == \'live\') {\r\n			if (!$type)\r\n				$type = \'301 Moved Permanently\';\r\n		}\r\n		else\r\n			$type = \'302 Found\';\r\n\r\n		txp_status_header($type);\r\n		header(\"Location: $location\");\r\n		ob_flush();\r\n		exit;\r\n	}\r\n\r\n}\r\n\r\n\r\n	function zem_redirect_url($test=0) {\r\n		global $pretext, $siteurl, $path_from_root, $prefs;\r\n\r\n		extract($prefs);\r\n\r\n 		if (empty($permlink_mode)\r\n 			or $permlink_mode == \'messy\'\r\n			or strcasecmp(serverSet(\'REQUEST_METHOD\'), \'POST\') == 0\r\n			or gps(\'txpreview\'))\r\n 			return;\r\n\r\n\r\n		$actual_host = serverSet(\"HTTP_HOST\");\r\n		$actual_path = ($test !== 0 ? $test : $pretext[\'request_uri\']);\r\n		$actual_url = \'http://\'.$actual_host.$actual_path;\r\n		$canonical_url = \'\';\r\n\r\n		# other unknown query parameters might be used by plugins\r\n		$get = gpsa(array_keys($_GET));\r\n		if (!$get) $get = array();\r\n		unset($get[\'id\']);\r\n		unset($get[\'s\']);\r\n		unset($get[\'c\']);\r\n\r\n		// remove bogus GET paramaters inserted by rss_unlimited_categories\r\n		global $plugins;\r\n		if (@in_array(\'rss_unlimited_categories\', $plugins)) {\r\n			unset($get[\'request_uri\']);\r\n			unset($get[\'qs\']);\r\n		}\r\n\r\n		if (gps(\'atom\') or gps(\'rss\')) {\r\n			$canonical_url = pagelinkurl($get);\r\n		}\r\n		elseif ($pretext[\'status\'] == 404) {\r\n			// url with subdir stripped\r\n			$url = @$pretext[\'req\'];\r\n\r\n			if (preg_match(\'@/(\\d{4})/(\\d{2})/(\\d{2})/([^/?]*)/?@\', $url, $m)) {\r\n				$when = $m[1].\'-\'.$m[2].\'-\'.$m[3];\r\n				$rs = lookupByDateTitle($when,$m[4]);\r\n				if (!$rs)\r\n					$rs = lookupByTitle($m[4]);\r\n			}\r\n			elseif (preg_match(\'@/([^/]+)/(\\d+)/([^/?]+)/?@\', $url, $m)) {\r\n				$rs = lookupByID($m[2]);\r\n			}\r\n			elseif (preg_match(\'@/([^/?]+)/([^/?]+)/?@\', $url, $m)) {\r\n				if (is_numeric($m[2]))\r\n					$rs = lookupByID($m[2]);\r\n				else {\r\n					$rs = lookupByTitleSection($m[2], $m[1]);\r\n					if (!$rs)\r\n						$rs = lookupByTitle($m[2]);\r\n				}\r\n			}\r\n			elseif (preg_match(\'@/([^/?]+)/?@\', $url, $m)) {\r\n				$rs = lookupByTitle($m[1]);\r\n			}\r\n\r\n			if ($rs) {\r\n				$canonical_url = permlinkurl_id($rs[\'ID\']) . join_qs($get);\r\n			}\r\n			else {\r\n				return;\r\n			}\r\n		}\r\n		elseif ($pretext[\'s\'] == \'file_download\') {\r\n			return;\r\n		}\r\n		elseif ($pretext[\'month\'] and $permlink_mode == \'year_month_day_title\') {\r\n			return;\r\n		}\r\n		elseif ($pretext[\"id\"]) {\r\n			# Article page\r\n			$parts = parse_url($pretext[\'req\']);\r\n			if (isset($parts[\'path\'])) {\r\n				$pathinfo = pathinfo($parts[\'path\']);\r\n				if (!empty($pathinfo[\'extension\']) and $pathinfo[\'basename\'] != \'index.php\') {\r\n					return;\r\n				}\r\n			}\r\n			$id = safe_field(\"ID\",\"textpattern\",\"ID=\'\".doSlash($pretext[\"id\"]).\"\' and Status IN (\'4\', \'5\') limit 1\");\r\n			if ($id) {\r\n				$canonical_url = permlinkurl_id($id);\r\n			}\r\n\r\n			# reattach messy URL parameters, if any\r\n			$canonical_url .= join_qs($get);\r\n		}\r\n		else {\r\n			# List page\r\n			$canonical_url = pagelinkurl($get, array(\'s\'=>$pretext[\'s\'], \'c\'=>$pretext[\'c\']));\r\n		}\r\n\r\n		if ($canonical_url) {\r\n\r\n			# fix up ampersands\r\n			$canonical_url = zem_redirect_fix_url($canonical_url);\r\n			if ($actual_url != $canonical_url) {\r\n				return zem_redirect_send_headers($canonical_url, \'301 Moved Permanently\', $test !== 0);\r\n			}\r\n		}\r\n\r\n	}\r\n\r\n\r\nfunction zem_redirect_handler($event, $step) {\r\n	if ($event == \'textpattern\')\r\n		zem_redirect_url();\r\n}\r\n\r\nregister_callback(\'zem_redirect_handler\', \'textpattern\');\r\n\r\nif (txpinterface == \'public\' and (gps(\'atom\') or gps(\'rss\'))) {\r\n	zem_redirect_url();\r\n}\r\n\r\n\r\nfunction zem_redirect($atts) {\r\n	global $pretext;\r\n\r\n	extract(lAtts(array(\r\n		\'to\' => \'\',\r\n		\'from\' => \'\',\r\n		\'debug\' => 0,\r\n		\'type\' => \'302\',\r\n	), $atts));\r\n\r\n	$to = trim($to);\r\n	$from = trim($from);\r\n\r\n\r\n	$dest = \'\';\r\n	if ($from and $to) {\r\n		$from = addcslashes($from, \'@\');\r\n		$to = addcslashes($to, \'@\');\r\n		if (preg_match(\'@\'.$from.\'@\', $pretext[\'request_uri\'])) {\r\n			$out = preg_replace(\'@\'.$from.\'@\', $to, $pretext[\'request_uri\']);\r\n			$dest = zem_redirect_fix_url($out, $debug);\r\n		}\r\n	}\r\n	elseif ($to) {\r\n		$dest = zem_redirect_fix_url($to, $debug);\r\n	}\r\n	else {\r\n		trigger_error(\'No destination specified\');\r\n	}\r\n\r\n	$actual_host = serverSet(\"HTTP_HOST\");\r\n	$actual_path = ($test !== 0 ? $test : $pretext[\'request_uri\']);\r\n	$actual_url = \'http://\'.$actual_host.$actual_path;\r\n\r\n	if ($dest and $dest != $actual_url) {\r\n		$status = ($type == \'301\' ? \'301 Moved Permanently\' : \'302 Found\');\r\n		return zem_redirect_send_headers($dest, $status, $debug);\r\n	}\r\n\r\n}\r\n', '/*\r\n\r\nCopyright 2005-2007 Alex Shiels http://thresholdstate.com/\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nversion 2 as published by the Free Software Foundation.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\nGNU General Public License for more details.\r\n\r\n*/\r\n\r\nfunction zem_redirect_fix_url($url, $debug=0) {\r\n	$parts = parse_url($url);\r\n	extract($parts);\r\n	$q = preg_replace(\'@&amp;@\', \'&\', @$query);\r\n	$q = $q ? \'?\'.$q : \'\';\r\n\r\n	$host = empty($scheme) ? serverSet(\'HTTP_HOST\') : $host;\r\n	$scheme = empty($scheme) ? \'http\' : $scheme;\r\n	$port = empty($port) ? \'\' : \':\'.$port;\r\n\r\n	return $scheme.\'://\'.$host.$port.\'/\'.ltrim($path, \'/\').$q;\r\n}\r\n\r\nfunction zem_redirect_send_headers($location, $type=\'\', $debug=0) {\r\n	global $prefs, $pretext;\r\n	extract($prefs);\r\n\r\n	if ($production_status == \'debug\' or $debug) {\r\n		echo \'<div style=\"color:#000;background-color:#fee;\">\'\r\n			.\'zem_redirect: from <a href=\"\'.$pretext[\'request_uri\'].\'\">\'.$pretext[\'request_uri\'].\'</a> to <a href=\"\'.$location.\'\">\'.$location.\'</a></div>\'.n;\r\n		return $location;\r\n	}\r\n	else {\r\n		if ($production_status == \'live\') {\r\n			if (!$type)\r\n				$type = \'301 Moved Permanently\';\r\n		}\r\n		else\r\n			$type = \'302 Found\';\r\n\r\n		txp_status_header($type);\r\n		header(\"Location: $location\");\r\n		ob_flush();\r\n		exit;\r\n	}\r\n\r\n}\r\n\r\n\r\n	function zem_redirect_url($test=0) {\r\n		global $pretext, $siteurl, $path_from_root, $prefs;\r\n\r\n		extract($prefs);\r\n\r\n 		if (empty($permlink_mode)\r\n 			or $permlink_mode == \'messy\'\r\n			or strcasecmp(serverSet(\'REQUEST_METHOD\'), \'POST\') == 0\r\n			or gps(\'txpreview\'))\r\n 			return;\r\n\r\n\r\n		$actual_host = serverSet(\"HTTP_HOST\");\r\n		$actual_path = ($test !== 0 ? $test : $pretext[\'request_uri\']);\r\n		$actual_url = \'http://\'.$actual_host.$actual_path;\r\n		$canonical_url = \'\';\r\n\r\n		# other unknown query parameters might be used by plugins\r\n		$get = gpsa(array_keys($_GET));\r\n		if (!$get) $get = array();\r\n		unset($get[\'id\']);\r\n		unset($get[\'s\']);\r\n		unset($get[\'c\']);\r\n\r\n		// remove bogus GET paramaters inserted by rss_unlimited_categories\r\n		global $plugins;\r\n		if (@in_array(\'rss_unlimited_categories\', $plugins)) {\r\n			unset($get[\'request_uri\']);\r\n			unset($get[\'qs\']);\r\n		}\r\n\r\n		if (gps(\'atom\') or gps(\'rss\')) {\r\n			$canonical_url = pagelinkurl($get);\r\n		}\r\n		elseif ($pretext[\'status\'] == 404) {\r\n			// url with subdir stripped\r\n			$url = @$pretext[\'req\'];\r\n\r\n			if (preg_match(\'@/(\\d{4})/(\\d{2})/(\\d{2})/([^/?]*)/?@\', $url, $m)) {\r\n				$when = $m[1].\'-\'.$m[2].\'-\'.$m[3];\r\n				$rs = lookupByDateTitle($when,$m[4]);\r\n				if (!$rs)\r\n					$rs = lookupByTitle($m[4]);\r\n			}\r\n			elseif (preg_match(\'@/([^/]+)/(\\d+)/([^/?]+)/?@\', $url, $m)) {\r\n				$rs = lookupByID($m[2]);\r\n			}\r\n			elseif (preg_match(\'@/([^/?]+)/([^/?]+)/?@\', $url, $m)) {\r\n				if (is_numeric($m[2]))\r\n					$rs = lookupByID($m[2]);\r\n				else {\r\n					$rs = lookupByTitleSection($m[2], $m[1]);\r\n					if (!$rs)\r\n						$rs = lookupByTitle($m[2]);\r\n				}\r\n			}\r\n			elseif (preg_match(\'@/([^/?]+)/?@\', $url, $m)) {\r\n				$rs = lookupByTitle($m[1]);\r\n			}\r\n\r\n			if ($rs) {\r\n				$canonical_url = permlinkurl_id($rs[\'ID\']) . join_qs($get);\r\n			}\r\n			else {\r\n				return;\r\n			}\r\n		}\r\n		elseif ($pretext[\'s\'] == \'file_download\') {\r\n			return;\r\n		}\r\n		elseif ($pretext[\'month\'] and $permlink_mode == \'year_month_day_title\') {\r\n			return;\r\n		}\r\n		elseif ($pretext[\"id\"]) {\r\n			# Article page\r\n			$parts = parse_url($pretext[\'req\']);\r\n			if (isset($parts[\'path\'])) {\r\n				$pathinfo = pathinfo($parts[\'path\']);\r\n				if (!empty($pathinfo[\'extension\']) and $pathinfo[\'basename\'] != \'index.php\') {\r\n					return;\r\n				}\r\n			}\r\n			$id = safe_field(\"ID\",\"textpattern\",\"ID=\'\".doSlash($pretext[\"id\"]).\"\' and Status IN (\'4\', \'5\') limit 1\");\r\n			if ($id) {\r\n				$canonical_url = permlinkurl_id($id);\r\n			}\r\n\r\n			# reattach messy URL parameters, if any\r\n			$canonical_url .= join_qs($get);\r\n		}\r\n		else {\r\n			# List page\r\n			$canonical_url = pagelinkurl($get, array(\'s\'=>$pretext[\'s\'], \'c\'=>$pretext[\'c\']));\r\n		}\r\n\r\n		if ($canonical_url) {\r\n\r\n			# fix up ampersands\r\n			$canonical_url = zem_redirect_fix_url($canonical_url);\r\n			if ($actual_url != $canonical_url) {\r\n				return zem_redirect_send_headers($canonical_url, \'301 Moved Permanently\', $test !== 0);\r\n			}\r\n		}\r\n\r\n	}\r\n\r\n\r\nfunction zem_redirect_handler($event, $step) {\r\n	if ($event == \'textpattern\')\r\n		zem_redirect_url();\r\n}\r\n\r\nregister_callback(\'zem_redirect_handler\', \'textpattern\');\r\n\r\nif (txpinterface == \'public\' and (gps(\'atom\') or gps(\'rss\'))) {\r\n	zem_redirect_url();\r\n}\r\n\r\n\r\nfunction zem_redirect($atts) {\r\n	global $pretext;\r\n\r\n	extract(lAtts(array(\r\n		\'to\' => \'\',\r\n		\'from\' => \'\',\r\n		\'debug\' => 0,\r\n		\'type\' => \'302\',\r\n	), $atts));\r\n\r\n	$to = trim($to);\r\n	$from = trim($from);\r\n\r\n\r\n	$dest = \'\';\r\n	if ($from and $to) {\r\n		$from = addcslashes($from, \'@\');\r\n		$to = addcslashes($to, \'@\');\r\n		if (preg_match(\'@\'.$from.\'@\', $pretext[\'request_uri\'])) {\r\n			$out = preg_replace(\'@\'.$from.\'@\', $to, $pretext[\'request_uri\']);\r\n			$dest = zem_redirect_fix_url($out, $debug);\r\n		}\r\n	}\r\n	elseif ($to) {\r\n		$dest = zem_redirect_fix_url($to, $debug);\r\n	}\r\n	else {\r\n		trigger_error(\'No destination specified\');\r\n	}\r\n\r\n	$actual_host = serverSet(\"HTTP_HOST\");\r\n	$actual_path = ($test !== 0 ? $test : $pretext[\'request_uri\']);\r\n	$actual_url = \'http://\'.$actual_host.$actual_path;\r\n\r\n	if ($dest and $dest != $actual_url) {\r\n		$status = ($type == \'301\' ? \'301 Moved Permanently\' : \'302 Found\');\r\n		return zem_redirect_send_headers($dest, $status, $debug);\r\n	}\r\n\r\n}\r\n', 'd3e364be665cd83c521eaf87b247432f', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('stm_javascript', 0, 'Stanislav Müller', 'http://dhonishow.de', '0.1', 'Stores JavaScript files within the database and handles them similar to CSS files.', 'In admin section, go to tab \"presentation > javascript\" and create one ore more javascript files that you want to embed within your page templates. This is similiar to the \"styles\" tab - the JavaScript files get stored in the database and you can embed them with a new Textpattern tag:\n\n<code>\n&lt;txp:js /&gt; (embeds default JavaScript file)\n</code>\n<code>\n<txp:js n=\"myDomScript\" /> (embeds JavaScript file named &quot;myDomScript&quot;)\n</code>\n\nOutput:\n<code>\n<script type=\"text/javascript\" src=\"http://mysite.com/?js=myDomScript\"></script>\n</code>', 'if (txpinterface == \'admin\') {	\r\n	add_privs(\'javascript\', \'1,2\');\r\n	add_privs(\'js\', \'1,2\');\r\n	register_tab(\"presentation\", \"js\", \"Javascript\");\r\n	register_callback(\"handle_backend_javascript\", \"js\");\r\n}else{\r\n	if(gps(\'js\')){\r\n		return_js(gps(\'js\'));	\r\n	}\r\n}\r\n\r\nfunction js($array=\'\'){\r\n	extract($array);	\r\n	if(!$n){\r\n		$n = \"default\";\r\n	}\r\n	return sprintf(\'<script type=\"text/javascript\" src=\"http://%s/?js=%s\"></script>\', $GLOBALS[\'prefs\'][\'siteurl\'], $n);\r\n}\r\n\r\nfunction return_js($name){\r\n	ob_start();\r\n	ob_end_clean();		\r\n	global $js_prefix;\r\n	\r\n	if(strlen($GLOBALS[\'txpcfg\'][\'table_prefix\']) != 0){\r\n		$js_prefix = $GLOBALS[\'txpcfg\'][\'table_prefix\'].\"js\";\r\n	}else{\r\n		$js_prefix = \"txp_js\";\r\n	}\r\n	\r\n	$js = fetch(\"js\",$GLOBALS[\'js_prefix\'],\'name\',$name);\r\n	\r\n	if ($js) {\r\n		$js_decoded =  base64_decode($js);\r\n	}else{\r\n		$js_decoded = \"alert(\'Javascript File could not be found!\')\";\r\n	}	\r\n	header(\'Content-type: application/x-javascript\');\r\n	header(sprintf(\'Content-Length: %d\', strlen($js_decoded)));\r\n	echo $js_decoded;\r\n	exit;\r\n}\r\n\r\nfunction handle_backend_javascript(){\r\n	global $js_prefix;\r\n	\r\n	if(strlen($GLOBALS[\'txpcfg\'][\'table_prefix\']) != 0){\r\n		$js_prefix = $GLOBALS[\'txpcfg\'][\'table_prefix\'].\"js\";\r\n	}else{\r\n		$js_prefix = \"txp_js\";\r\n	}\r\n	\r\n	safe_query(sprintf(\"CREATE TABLE IF NOT EXISTS %s ( \r\n						  name varchar(255) NOT NULL default \'\',\r\n						  js longtext NOT NULL,\r\n						  PRIMARY KEY  (name));\", $js_prefix));\r\n	if(safe_count($js_prefix, \"name=\'default\'\") == \"0\"){\r\n		safe_insert($js_prefix, \"name=\'default\', js=\'Ly8gUGxlYXNlIGluc2VydCB5b3VyIEphdmFTY3JpcHQgaGVyZTo=\'\");\r\n	}\r\n	\r\n	\r\n	\r\n	if (!defined(\'txpinterface\')) die(\'txpinterface is undefined.\');\r\n		if (gps(\'event\') == \'js\') {\r\n			$step = gps(\'step\');\r\n			require_privs(\'js\');\r\n			switch ($step) {\r\n				case \'\': js_edit(); 											break;\r\n				case \'js_edit_raw\': js_edit();           	break;\r\n				case \'js_edit_form\': js_edit();          	break;\r\n				case \'pour\': js_edit();	               		break;\r\n				case \'js_save\': js_save();              	break;\r\n				case \'js_save_as\': js_save_as();         	break;\r\n				case \'js_save_posted\': js_save_posted(); 	break;\r\n				case \'js_delete\': js_delete();           	break;\r\n				case \'js_edit\': js_edit();               	break;\r\n				case \'del_dec\': js_edit();               	break;\r\n				case \'add_dec\': js_edit();                break;\r\n				case \'add_sel\': js_edit();\r\n			}\r\n	}\r\n}\r\n\r\n// ------------------------------\r\n\r\n//-------------------------------------------------------------\r\n	function js_list($name){	\r\n		$out[] = startTable(\'list\',\'left\');\r\n		$rs = safe_rows_start(\"name as jsname\", $GLOBALS[\'js_prefix\'],\"1=1\");\r\n		if ($rs) {\r\n			while ($a = nextRow($rs)) {\r\n				extract($a);\r\n				$namelink = ($name!=$jsname)\r\n				?	eLink(\'js\',\'\',\'name\',$jsname,$jsname)\r\n				:	$jsname;\r\n				$deletelink = ($jsname!=\'default\') ? \r\n					dLink(\'js\',\'js_delete\',\'name\',$jsname) : \'\';\r\n				$out[] = tr(td($namelink).td($deletelink));\r\n			}\r\n			$out[] =  endTable();\r\n			return join(\'\',$out);\r\n		}\r\n	}\r\n\r\n//-------------------------------------------------------------\r\n	function js_edit($message=\'\'){\r\n		pagetop(gTxt(\"edit_js\"),$message);\r\n		global $step,$prefs;\r\n		if (!$step or $step == \'js_save\'){\r\n			js_edit_raw();\r\n		} else {\r\n			if ($step==\'js_edit_raw\' or $step==\'pour\' or $step==\'js_delete\') {\r\n				js_edit_raw();\r\n			}\r\n		}\r\n	}\r\n\r\n\r\n//-------------------------------------------------------------\r\n	function js_edit_raw(){\r\n		global $step;\r\n		$name = (!gps(\'name\') or $step==\'js_delete\') ? \'default\' : gps(\'name\');\r\n		if (gps(\'copy\') && trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))) )\r\n			$name = gps(\'newname\');\r\n\r\n		if ($step==\'pour\'){\r\n			$buttons = \r\n			gTxt(\'Name of JavaScript file\').\': \'\r\n			.fInput(\'text\',\'newname\',\'\',\'edit\',\'\',\'\',20).\r\n			hInput(\'savenew\',\'savenew\');\r\n			$thejs = \'\';\r\n		} else {\r\n			$buttons = \'\';\r\n			$thejs = base64_decode(fetch(\"js\",$GLOBALS[\'js_prefix\'],\'name\',$name));\r\n		}\r\n	\r\n		if ($step!=\'pour\') {\r\n			$left = graf(gTxt(\'You are editing:\').br.strong($name)).\r\n				graf(sLink(\'js\', \'pour\', gTxt(\'Create new JavaScript file\')));\r\n		} else {\r\n			$left = \'&nbsp;\';\r\n		}\r\n\r\n		$right = \r\n		hed(gTxt(\'All JavaScript files\'),2).\r\n		js_list($name);\r\n\r\n		echo \r\n		startTable(\'edit\').\r\n		tr(\r\n			tdtl(\r\n				$left\r\n			).\r\n			td(\r\n				form(\r\n					graf($buttons).\r\n					\'<textarea id=\"js\" class=\"code\" name=\"js\" cols=\"78\" rows=\"32\">\'.$thejs.\'</textarea>\'.br.\r\n					fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\').\r\n					eInput(\'js\').sInput(\'js_save\').\r\n					hInput(\'name\',$name)\r\n				)\r\n			).\r\n			tdtl(\r\n				$right\r\n			)\r\n		).\r\n		endTable();\r\n		\r\n	}\r\n\r\n// -------------------------------------------------------------\r\n\r\n	function js_save_posted(){\r\n		$name = gps(\'name\');\r\n		#$js  = parsePostedjs();\r\n		$js  = doSlash(base64_encode(js_format($js)));\r\n\r\n		safe_update($GLOBALS[\'js_prefix\'], \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\r\n\r\n		$message = gTxt(\'js_updated\', array(\'{name}\' => $name));\r\n\r\n		js_edit($message);\r\n	}\r\n\r\n//-------------------------------------------------------------\r\n\r\n	function js_save(){\r\n		extract(gpsa(array(\'name\',\'js\',\'savenew\',\'newname\',\'copy\')));\r\n		$js = doSlash(base64_encode($js));\r\n\r\n		if ($savenew or $copy)\r\n		{\r\n			$newname = doSlash(trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))));\r\n\r\n			if ($newname and safe_field(\'name\', $GLOBALS[\'js_prefix\'], \"name = \'$newname\'\"))\r\n			{\r\n				$message = gTxt(\'js_already_exists\', array(\'{name}\' => $newname));\r\n			}\r\n			elseif ($newname) \r\n			{\r\n				safe_insert($GLOBALS[\'js_prefix\'], \"name = \'\".$newname.\"\', js = \'$js\'\");\r\n\r\n				$message = gTxt(\'js_created\', array(\'{name}\' => $newname));\r\n			}\r\n			else\r\n			{\r\n				$message = gTxt(\'js_name_required\');\r\n			}\r\n\r\n			js_edit($message);\r\n		}\r\n\r\n		else\r\n		{\r\n			safe_update($GLOBALS[\'js_prefix\'], \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			$message = gTxt(\'js_updated\', array(\'{name}\' => $name));\r\n\r\n			js_edit($message);\r\n		}\r\n	}\r\n\r\n// -------------------------------------------------------------\r\n\r\n//-------------------------------------------------------------\r\n	function js_delete(){\r\n		$name = ps(\'name\');\r\n\r\n		if ($name != \'default\')\r\n		{\r\n			safe_delete($GLOBALS[\'js_prefix\'], \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			js_edit(\r\n				gTxt(\'js_deleted\', array(\'{name}\' => $name))\r\n			);\r\n		}\r\n\r\n		else\r\n		{\r\n			echo gTxt(\'cannot_delete_default_js\').\'.\';\r\n		}\r\n	}\r\n', 'if (txpinterface == \'admin\') {	\r\n	add_privs(\'javascript\', \'1,2\');\r\n	add_privs(\'js\', \'1,2\');\r\n	register_tab(\"presentation\", \"js\", \"Javascript\");\r\n	register_callback(\"handle_backend_javascript\", \"js\");\r\n}else{\r\n	if(gps(\'js\')){\r\n		return_js(gps(\'js\'));	\r\n	}\r\n}\r\n\r\nfunction js($array=\'\'){\r\n	extract($array);	\r\n	if(!$n){\r\n		$n = \"default\";\r\n	}\r\n	return sprintf(\'<script type=\"text/javascript\" src=\"http://%s/?js=%s\"></script>\', $GLOBALS[\'prefs\'][\'siteurl\'], $n);\r\n}\r\n\r\nfunction return_js($name){\r\n	ob_start();\r\n	ob_end_clean();		\r\n	global $js_prefix;\r\n	\r\n	if(strlen($GLOBALS[\'txpcfg\'][\'table_prefix\']) != 0){\r\n		$js_prefix = $GLOBALS[\'txpcfg\'][\'table_prefix\'].\"js\";\r\n	}else{\r\n		$js_prefix = \"txp_js\";\r\n	}\r\n	\r\n	$js = fetch(\"js\",$GLOBALS[\'js_prefix\'],\'name\',$name);\r\n	\r\n	if ($js) {\r\n		$js_decoded =  base64_decode($js);\r\n	}else{\r\n		$js_decoded = \"alert(\'Javascript File could not be found!\')\";\r\n	}	\r\n	header(\'Content-type: application/x-javascript\');\r\n	header(sprintf(\'Content-Length: %d\', strlen($js_decoded)));\r\n	echo $js_decoded;\r\n	exit;\r\n}\r\n\r\nfunction handle_backend_javascript(){\r\n	global $js_prefix;\r\n	\r\n	if(strlen($GLOBALS[\'txpcfg\'][\'table_prefix\']) != 0){\r\n		$js_prefix = $GLOBALS[\'txpcfg\'][\'table_prefix\'].\"js\";\r\n	}else{\r\n		$js_prefix = \"txp_js\";\r\n	}\r\n	\r\n	safe_query(sprintf(\"CREATE TABLE IF NOT EXISTS %s ( \r\n						  name varchar(255) NOT NULL default \'\',\r\n						  js longtext NOT NULL,\r\n						  PRIMARY KEY  (name));\", $js_prefix));\r\n	if(safe_count($js_prefix, \"name=\'default\'\") == \"0\"){\r\n		safe_insert($js_prefix, \"name=\'default\', js=\'Ly8gUGxlYXNlIGluc2VydCB5b3VyIEphdmFTY3JpcHQgaGVyZTo=\'\");\r\n	}\r\n	\r\n	\r\n	\r\n	if (!defined(\'txpinterface\')) die(\'txpinterface is undefined.\');\r\n		if (gps(\'event\') == \'js\') {\r\n			$step = gps(\'step\');\r\n			require_privs(\'js\');\r\n			switch ($step) {\r\n				case \'\': js_edit(); 											break;\r\n				case \'js_edit_raw\': js_edit();           	break;\r\n				case \'js_edit_form\': js_edit();          	break;\r\n				case \'pour\': js_edit();	               		break;\r\n				case \'js_save\': js_save();              	break;\r\n				case \'js_save_as\': js_save_as();         	break;\r\n				case \'js_save_posted\': js_save_posted(); 	break;\r\n				case \'js_delete\': js_delete();           	break;\r\n				case \'js_edit\': js_edit();               	break;\r\n				case \'del_dec\': js_edit();               	break;\r\n				case \'add_dec\': js_edit();                break;\r\n				case \'add_sel\': js_edit();\r\n			}\r\n	}\r\n}\r\n\r\n// ------------------------------\r\n\r\n//-------------------------------------------------------------\r\n	function js_list($name){	\r\n		$out[] = startTable(\'list\',\'left\');\r\n		$rs = safe_rows_start(\"name as jsname\", $GLOBALS[\'js_prefix\'],\"1=1\");\r\n		if ($rs) {\r\n			while ($a = nextRow($rs)) {\r\n				extract($a);\r\n				$namelink = ($name!=$jsname)\r\n				?	eLink(\'js\',\'\',\'name\',$jsname,$jsname)\r\n				:	$jsname;\r\n				$deletelink = ($jsname!=\'default\') ? \r\n					dLink(\'js\',\'js_delete\',\'name\',$jsname) : \'\';\r\n				$out[] = tr(td($namelink).td($deletelink));\r\n			}\r\n			$out[] =  endTable();\r\n			return join(\'\',$out);\r\n		}\r\n	}\r\n\r\n//-------------------------------------------------------------\r\n	function js_edit($message=\'\'){\r\n		pagetop(gTxt(\"edit_js\"),$message);\r\n		global $step,$prefs;\r\n		if (!$step or $step == \'js_save\'){\r\n			js_edit_raw();\r\n		} else {\r\n			if ($step==\'js_edit_raw\' or $step==\'pour\' or $step==\'js_delete\') {\r\n				js_edit_raw();\r\n			}\r\n		}\r\n	}\r\n\r\n\r\n//-------------------------------------------------------------\r\n	function js_edit_raw(){\r\n		global $step;\r\n		$name = (!gps(\'name\') or $step==\'js_delete\') ? \'default\' : gps(\'name\');\r\n		if (gps(\'copy\') && trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))) )\r\n			$name = gps(\'newname\');\r\n\r\n		if ($step==\'pour\'){\r\n			$buttons = \r\n			gTxt(\'Name of JavaScript file\').\': \'\r\n			.fInput(\'text\',\'newname\',\'\',\'edit\',\'\',\'\',20).\r\n			hInput(\'savenew\',\'savenew\');\r\n			$thejs = \'\';\r\n		} else {\r\n			$buttons = \'\';\r\n			$thejs = base64_decode(fetch(\"js\",$GLOBALS[\'js_prefix\'],\'name\',$name));\r\n		}\r\n	\r\n		if ($step!=\'pour\') {\r\n			$left = graf(gTxt(\'You are editing:\').br.strong($name)).\r\n				graf(sLink(\'js\', \'pour\', gTxt(\'Create new JavaScript file\')));\r\n		} else {\r\n			$left = \'&nbsp;\';\r\n		}\r\n\r\n		$right = \r\n		hed(gTxt(\'All JavaScript files\'),2).\r\n		js_list($name);\r\n\r\n		echo \r\n		startTable(\'edit\').\r\n		tr(\r\n			tdtl(\r\n				$left\r\n			).\r\n			td(\r\n				form(\r\n					graf($buttons).\r\n					\'<textarea id=\"js\" class=\"code\" name=\"js\" cols=\"78\" rows=\"32\">\'.$thejs.\'</textarea>\'.br.\r\n					fInput(\'submit\',\'\',gTxt(\'save\'),\'publish\').\r\n					eInput(\'js\').sInput(\'js_save\').\r\n					hInput(\'name\',$name)\r\n				)\r\n			).\r\n			tdtl(\r\n				$right\r\n			)\r\n		).\r\n		endTable();\r\n		\r\n	}\r\n\r\n// -------------------------------------------------------------\r\n\r\n	function js_save_posted(){\r\n		$name = gps(\'name\');\r\n		#$js  = parsePostedjs();\r\n		$js  = doSlash(base64_encode(js_format($js)));\r\n\r\n		safe_update($GLOBALS[\'js_prefix\'], \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\r\n\r\n		$message = gTxt(\'js_updated\', array(\'{name}\' => $name));\r\n\r\n		js_edit($message);\r\n	}\r\n\r\n//-------------------------------------------------------------\r\n\r\n	function js_save(){\r\n		extract(gpsa(array(\'name\',\'js\',\'savenew\',\'newname\',\'copy\')));\r\n		$js = doSlash(base64_encode($js));\r\n\r\n		if ($savenew or $copy)\r\n		{\r\n			$newname = doSlash(trim(preg_replace(\'/[<>&\"\\\']/\', \'\', gps(\'newname\'))));\r\n\r\n			if ($newname and safe_field(\'name\', $GLOBALS[\'js_prefix\'], \"name = \'$newname\'\"))\r\n			{\r\n				$message = gTxt(\'js_already_exists\', array(\'{name}\' => $newname));\r\n			}\r\n			elseif ($newname) \r\n			{\r\n				safe_insert($GLOBALS[\'js_prefix\'], \"name = \'\".$newname.\"\', js = \'$js\'\");\r\n\r\n				$message = gTxt(\'js_created\', array(\'{name}\' => $newname));\r\n			}\r\n			else\r\n			{\r\n				$message = gTxt(\'js_name_required\');\r\n			}\r\n\r\n			js_edit($message);\r\n		}\r\n\r\n		else\r\n		{\r\n			safe_update($GLOBALS[\'js_prefix\'], \"js = \'$js\'\", \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			$message = gTxt(\'js_updated\', array(\'{name}\' => $name));\r\n\r\n			js_edit($message);\r\n		}\r\n	}\r\n\r\n// -------------------------------------------------------------\r\n\r\n//-------------------------------------------------------------\r\n	function js_delete(){\r\n		$name = ps(\'name\');\r\n\r\n		if ($name != \'default\')\r\n		{\r\n			safe_delete($GLOBALS[\'js_prefix\'], \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			js_edit(\r\n				gTxt(\'js_deleted\', array(\'{name}\' => $name))\r\n			);\r\n		}\r\n\r\n		else\r\n		{\r\n			echo gTxt(\'cannot_delete_default_js\').\'.\';\r\n		}\r\n	}\r\n', '933032a841b3977a035aaf1030ba3f06', '', '', 1, 5, 0),
('pap_comconnect', 0, 'tranquillo and Stef', 'https://forum.textpattern.io/viewtopic.php?pid=312784#p312784', '0.1.2', 'Prevent Spam through com_connect', '<h1>Pap Contact Cleaner</h1>\n\n	<p>This plugin adds two invisible fields to your com_connect form. If the fields are filled, the contact form won&#8217;t get submitted. The plugin uses two invisible fields named <em>phone</em> and <em>mail</em> and your com_connect fields cannot have those names.</p>\n\n\n	<p>The plugin is based on slog_spam42form by Thomas Schrader.</p>\n\n	<h1>Usage</h1>\n\n	<p>1. Install pap_contact_cleaner from pap_contact_cleaner.txt.<br />\n2. Activate the plugin and watch the spam disappear.</p>', 'register_callback(\'pap_comconnect_form\',\'comconnect.form\');\r\nregister_callback(\'pap_comconnect_submit\',\'comconnect.submit\');\r\n\r\nfunction pap_comconnect_form() {\r\n\r\n	$field = \'<div style=\"visibility: hidden\">\'.finput(\'text\',\'phone\',ps(\'phone\'),\'\',\'\',\'\',\'\',\'\',\'phone\').\'<br />\'.finput(\'text\',\'mail\',ps(\'mail\'),\'\',\'\',\'\',\'\',\'\',\'mail\').\'</div>\';\r\n	return $field;\r\n\r\n}\r\n\r\nfunction pap_comconnect_submit() {\r\n\r\n	$checking_mail_field = trim(ps(\'mail\'));\r\n	$checking_phone_field = trim(ps(\'phone\'));\r\n\r\n	$evaluation =& get_comconnect_evaluator();\r\n\r\n	// If the hidden fields are filled out, the contact form won\'t be submitted!\r\n	if ($checking_mail_field != \'\' || $checking_phone_field != \'\'){\r\n		$evaluation -> add_comconnect_status(1);\r\n	}\r\n	return;\r\n\r\n}\r\n', 'register_callback(\'pap_comconnect_form\',\'comconnect.form\');\r\nregister_callback(\'pap_comconnect_submit\',\'comconnect.submit\');\r\n\r\nfunction pap_comconnect_form() {\r\n\r\n	$field = \'<div style=\"visibility: hidden\">\'.finput(\'text\',\'phone\',ps(\'phone\'),\'\',\'\',\'\',\'\',\'\',\'phone\').\'<br />\'.finput(\'text\',\'mail\',ps(\'mail\'),\'\',\'\',\'\',\'\',\'\',\'mail\').\'</div>\';\r\n	return $field;\r\n\r\n}\r\n\r\nfunction pap_comconnect_submit() {\r\n\r\n	$checking_mail_field = trim(ps(\'mail\'));\r\n	$checking_phone_field = trim(ps(\'phone\'));\r\n\r\n	$evaluation =& get_comconnect_evaluator();\r\n\r\n	// If the hidden fields are filled out, the contact form won\'t be submitted!\r\n	if ($checking_mail_field != \'\' || $checking_phone_field != \'\'){\r\n		$evaluation -> add_comconnect_status(1);\r\n	}\r\n	return;\r\n\r\n}\r\n', '1329774a2b28922ad0e48ca67694fafe', '', '', 0, 5, 0),
('rah_pathway', 0, 'Jukka Svahn', 'https://github.com/gocom/rah_pathway', '0.1.0', 'Custom article permlinks', '<h1>rah_pathway</h1>\n\n<p><a href=\"https://packagist.org/packages/rah/rah_pathway\" rel=\"nofollow\">Packagist</a> | <a href=\"https://github.com/gocom/rah_pathway/issues\" rel=\"nofollow\">Issues</a></p>\n\n<p>Custom free-form article permalinks for <a href=\"https://textpattern.com\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a>. These <span class=\"caps\">URL</span>s can contain false sub-sections, forward slashes &#8212; literally anything.</p>\n\n<h2>Install</h2>\n\n<p>Using <a href=\"https://getcomposer.org\" rel=\"nofollow\">Composer</a>:</p>\n\n<pre><code>$ composer require rah/rah_pathway</code></pre>\n\n<h2>Configure</h2>\n\n<p>In Preferences, under <strong>Site</strong> section, <strong>Free-form article <span class=\"caps\">URL</span> field</strong> option controls which field is going to be used for the free-form article <span class=\"caps\">URL</span>. The assigned field&#8217;s contents are going to be used as the article <span class=\"caps\">URL</span> as-is.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.1.0 &#8211; 2019/04/09</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/*\n * rah_pathway - Custom permlinks for Textpattern CMS\n * https://github.com/gocom/rah_pathway\n *\n * Copyright (C) 2019 Jukka Svahn\n *\n * This file is part of rah_pathway.\n *\n * rah_pathway is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_pathway is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with rah_pathway. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * The plugin class.\n */\nfinal class Rah_Pathway\n{\n    /**\n     * Used article field.\n     *\n     * @var string\n     */\n    private $field;\n\n    /**\n     * Requested URL.\n     *\n     * @var string\n     */\n    private $pageUrl;\n\n    /**\n     * Stores an array of the real request parameters.\n     *\n     * @var array\n     */\n    private $stash = [];\n\n    /**\n     * Permlink cache.\n     *\n     * @var array\n     */\n    private $permlinks = [];\n\n    /**\n     * Constructor.\n     */\n    public function __construct()\n    {\n        register_callback([$this, \'install\'], \'plugin_lifecycle.rah_pathway\', \'installed\');\n        register_callback([$this, \'uninstall\'], \'plugin_lifecycle.rah_pathway\', \'deleted\');\n\n        try {\n            $this->setField(get_pref(\'rah_pathway_field\'));\n        } catch (\\Exception $e) {\n            return;\n        }\n\n        $this->pageUrl = trim(serverSet(\'REQUEST_URI\'), \'/\');\n\n        register_callback([$this, \'route\'], \'pretext\');\n        register_callback([$this, \'setPermlink\'], \'pretext_end\');\n        register_callback([$this, \'restore\'], \'pretext_end\');\n        register_callback([$this, \'sanitizer\'], \'sanitize_for_url\');\n\n        if (txpinterface == \'admin\') {\n            global $event;\n            register_callback([$this, \'setPermlink\'], $event, \'\', 1);\n        }\n    }\n\n    /**\n     * Installer.\n     */\n    public function install()\n    {\n        create_pref(\'rah_pathway_field\', \'\', \'site\', PREF_PLUGIN, \'Rah_Pathway::renderFieldOption\', 181);\n    }\n\n    /**\n     * Uninstaller.\n     */\n    public function uninstall()\n    {\n        remove_pref(\'rah_pathway_field\');\n    }\n\n    /**\n     * Sets the used field.\n     *\n     * @param  string $column Column name\n     * @return $this\n     */\n    private function setField($column)\n    {\n        $field = preg_replace(\'/[^a-z0-9_]/i\', \'\', $column);\n\n        if (!$field) {\n            throw new \\InvalidArgumentException(\'Invalid field name.\');\n        }\n\n        $this->field = $field;\n\n        return $this;\n    }\n\n    /**\n     * Routes requests to the article.\n     */\n    public function route()\n    {\n        foreach ([\'id\'] as $name) {\n            if (isset($_POST[$name])) {\n                $this->stash[$name] = $_POST[$name];\n            } elseif (isset($_GET[$name])) {\n                $this->stash[$name] = $_GET[$name];\n            } else {\n                $this->stash[$name] = null;\n            }\n        }\n\n        if (!$this->pageUrl) {\n            return;\n        }\n\n        $id = safe_field(\'ID\', \'textpattern\', $this->field.\"=\'\".doSlash($this->pageUrl).\"\' limit 1\");\n\n        if (!$id) {\n            return;\n        }\n\n        $_POST[\'id\'] = $_GET[\'id\'] = $id;\n    }\n\n    /**\n     * Restores GET and POST parameters.\n     */\n    public function restore()\n    {\n        foreach ($this->stash as $name => $value) {\n            if ($value === null) {\n                unset($_GET[$name], $_POST[$name]);\n            } else {\n                $_POST[$name] = $_GET[$name] = $value;\n            }\n        }\n    }\n\n    /**\n     * Registers permlink handler.\n     *\n     * @return void\n     */\n    public function setPermlink()\n    {\n        global $prefs;\n        $prefs[\'custom_url_func\'] = [$this, \'permlink\'];\n    }\n\n    /**\n     * Sanitizer that allows freeform URLs.\n     *\n     * @param  string $ent  Event\n     * @param  string $step Step\n     * @param  string $url  URL\n     * @return string\n     */\n    public function sanitizer($ent, $step, $url)\n    {\n        global $event;\n\n        if ($this->field === \'url_title\' && $event === \'article\') {\n            return $url;\n        }\n\n        return \'\';\n    }\n\n    /**\n     * Gets a permlink from the given data.\n     *\n     * @param  array  $data The data\n     * @return string|bool\n     */\n    public function permlink($data)\n    {\n        if (empty($data[\'thisid\']) || empty($data[\'url_title\'])) {\n            return false;\n        }\n\n        $id = (int)$data[\'thisid\'];\n\n        if (isset($this->permlinks[$id])) {\n            return $this->permlinks[$id];\n        }\n\n        if (isset($data[$this->field])) {\n            $url = $data[$this->field];\n        } else {\n            $url = safe_field($this->field, \'textpattern\', \"ID=\'$id\' limit 1\");\n        }\n\n        if ($url) {\n            $url = hu . $url;\n        } else {\n            $url = false;\n        }\n\n        $this->permlinks[$id] = $url;\n\n        return $url;\n    }\n\n    /**\n     * Renders input.\n     *\n     * @param  string $name      Field name\n     * @param  string $selected  Current value\n     * @return string HTML select field\n     */\n    public static function renderFieldOption($name, $selected)\n    {\n        $options = [\n            \'url_title\' => gTxt(\'url_title\'),\n        ];\n\n        foreach (getCustomFields() as $number => $label) {\n            $options[\'custom_\' . intval($number)] = gTxt($label);\n        }\n\n        asort($options);\n\n        return selectInput($name, $options, $selected, true, \'\', $name);\n    }\n}\n\n\n\n/*\n * rah_pathway - Custom permlinks for Textpattern CMS\n * https://github.com/gocom/rah_pathway\n *\n * Copyright (C) 2019 Jukka Svahn\n *\n * This file is part of rah_pathway.\n *\n * rah_pathway is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_pathway is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with rah_pathway. If not, see <http://www.gnu.org/licenses/>.\n */\n\nnew Rah_Pathway();\n', '\n\n/*\n * rah_pathway - Custom permlinks for Textpattern CMS\n * https://github.com/gocom/rah_pathway\n *\n * Copyright (C) 2019 Jukka Svahn\n *\n * This file is part of rah_pathway.\n *\n * rah_pathway is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_pathway is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with rah_pathway. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * The plugin class.\n */\nfinal class Rah_Pathway\n{\n    /**\n     * Used article field.\n     *\n     * @var string\n     */\n    private $field;\n\n    /**\n     * Requested URL.\n     *\n     * @var string\n     */\n    private $pageUrl;\n\n    /**\n     * Stores an array of the real request parameters.\n     *\n     * @var array\n     */\n    private $stash = [];\n\n    /**\n     * Permlink cache.\n     *\n     * @var array\n     */\n    private $permlinks = [];\n\n    /**\n     * Constructor.\n     */\n    public function __construct()\n    {\n        register_callback([$this, \'install\'], \'plugin_lifecycle.rah_pathway\', \'installed\');\n        register_callback([$this, \'uninstall\'], \'plugin_lifecycle.rah_pathway\', \'deleted\');\n\n        try {\n            $this->setField(get_pref(\'rah_pathway_field\'));\n        } catch (\\Exception $e) {\n            return;\n        }\n\n        $this->pageUrl = trim(serverSet(\'REQUEST_URI\'), \'/\');\n\n        register_callback([$this, \'route\'], \'pretext\');\n        register_callback([$this, \'setPermlink\'], \'pretext_end\');\n        register_callback([$this, \'restore\'], \'pretext_end\');\n        register_callback([$this, \'sanitizer\'], \'sanitize_for_url\');\n\n        if (txpinterface == \'admin\') {\n            global $event;\n            register_callback([$this, \'setPermlink\'], $event, \'\', 1);\n        }\n    }\n\n    /**\n     * Installer.\n     */\n    public function install()\n    {\n        create_pref(\'rah_pathway_field\', \'\', \'site\', PREF_PLUGIN, \'Rah_Pathway::renderFieldOption\', 181);\n    }\n\n    /**\n     * Uninstaller.\n     */\n    public function uninstall()\n    {\n        remove_pref(\'rah_pathway_field\');\n    }\n\n    /**\n     * Sets the used field.\n     *\n     * @param  string $column Column name\n     * @return $this\n     */\n    private function setField($column)\n    {\n        $field = preg_replace(\'/[^a-z0-9_]/i\', \'\', $column);\n\n        if (!$field) {\n            throw new \\InvalidArgumentException(\'Invalid field name.\');\n        }\n\n        $this->field = $field;\n\n        return $this;\n    }\n\n    /**\n     * Routes requests to the article.\n     */\n    public function route()\n    {\n        foreach ([\'id\'] as $name) {\n            if (isset($_POST[$name])) {\n                $this->stash[$name] = $_POST[$name];\n            } elseif (isset($_GET[$name])) {\n                $this->stash[$name] = $_GET[$name];\n            } else {\n                $this->stash[$name] = null;\n            }\n        }\n\n        if (!$this->pageUrl) {\n            return;\n        }\n\n        $id = safe_field(\'ID\', \'textpattern\', $this->field.\"=\'\".doSlash($this->pageUrl).\"\' limit 1\");\n\n        if (!$id) {\n            return;\n        }\n\n        $_POST[\'id\'] = $_GET[\'id\'] = $id;\n    }\n\n    /**\n     * Restores GET and POST parameters.\n     */\n    public function restore()\n    {\n        foreach ($this->stash as $name => $value) {\n            if ($value === null) {\n                unset($_GET[$name], $_POST[$name]);\n            } else {\n                $_POST[$name] = $_GET[$name] = $value;\n            }\n        }\n    }\n\n    /**\n     * Registers permlink handler.\n     *\n     * @return void\n     */\n    public function setPermlink()\n    {\n        global $prefs;\n        $prefs[\'custom_url_func\'] = [$this, \'permlink\'];\n    }\n\n    /**\n     * Sanitizer that allows freeform URLs.\n     *\n     * @param  string $ent  Event\n     * @param  string $step Step\n     * @param  string $url  URL\n     * @return string\n     */\n    public function sanitizer($ent, $step, $url)\n    {\n        global $event;\n\n        if ($this->field === \'url_title\' && $event === \'article\') {\n            return $url;\n        }\n\n        return \'\';\n    }\n\n    /**\n     * Gets a permlink from the given data.\n     *\n     * @param  array  $data The data\n     * @return string|bool\n     */\n    public function permlink($data)\n    {\n        if (empty($data[\'thisid\']) || empty($data[\'url_title\'])) {\n            return false;\n        }\n\n        $id = (int)$data[\'thisid\'];\n\n        if (isset($this->permlinks[$id])) {\n            return $this->permlinks[$id];\n        }\n\n        if (isset($data[$this->field])) {\n            $url = $data[$this->field];\n        } else {\n            $url = safe_field($this->field, \'textpattern\', \"ID=\'$id\' limit 1\");\n        }\n\n        if ($url) {\n            $url = hu . $url;\n        } else {\n            $url = false;\n        }\n\n        $this->permlinks[$id] = $url;\n\n        return $url;\n    }\n\n    /**\n     * Renders input.\n     *\n     * @param  string $name      Field name\n     * @param  string $selected  Current value\n     * @return string HTML select field\n     */\n    public static function renderFieldOption($name, $selected)\n    {\n        $options = [\n            \'url_title\' => gTxt(\'url_title\'),\n        ];\n\n        foreach (getCustomFields() as $number => $label) {\n            $options[\'custom_\' . intval($number)] = gTxt($label);\n        }\n\n        asort($options);\n\n        return selectInput($name, $options, $selected, true, \'\', $name);\n    }\n}\n\n\n\n/*\n * rah_pathway - Custom permlinks for Textpattern CMS\n * https://github.com/gocom/rah_pathway\n *\n * Copyright (C) 2019 Jukka Svahn\n *\n * This file is part of rah_pathway.\n *\n * rah_pathway is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_pathway is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with rah_pathway. If not, see <http://www.gnu.org/licenses/>.\n */\n\nnew Rah_Pathway();\n', '947052243ad3a62023587cfe9525eb03', '#@admin\nrah_pathway_field => Free-form article URL field\n\n#@admin\n#@language fi-fi\nrah_pathway_field => Vapaamuotoinen artikkelin osoitekenttä\n', '', 5, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('rss_admin_db_manager', 1, 'Rob Sable', 'http://www.wilshireone.com/', '4.5', 'Database management system.', '<h1>Textpattern Database Manager</h1>\n\n<p>The rss_admin_db_manager plugin adds three menu items to your Textpattern admin interface. Each contains different functionality to help manage your <a href=\"http://www.mysql.com/\" rel=\"nofollow\">MySQL</a> database. You can think of this plugin as a lightweight replacement for <a href=\"http://www.phpmyadmin.net/home_page/\" rel=\"nofollow\">phpMyAdmin</a>.</p>\n\n<h2>Database Backup</h2>\n\n<p>The <strong>DB Backup panel</strong> allows you to backup, download and restore the MySQL database that is used for your Textpattern installation.</p>\n\n<p>The database backups and restores are run using MySQL&#8217;s <a href=\"http://dev.mysql.com/doc/mysql/en/mysqldump.html\" rel=\"nofollow\">mysqldump</a> command.</p>\n\n<p>On this panel you are able to:</p>\n\n<ul>\n	<li>Create a .sql backup file on windows with the additional option of creating a gzipped backup on *nix operating systems</li>\n	<li>View a list of previous backup files</li>\n	<li>Restore your database from one of the previous backups</li>\n	<li>Download a backup file</li>\n	<li>Delete old backups</li>\n</ul>\n\n<h2>Backup Preferences</h2>\n\n<p>You have the ability to set several preferences related to your database backups. You can set these options on the backup panel. The options include:</p>\n\n<ul>\n	<li><strong>Lock Tables</strong> &#8211; Your host may or may not support this option.For example, by default, Textdrive doesn&#8217;t allow table locking.If your backup fails, try setting this to “No”.</li>\n	<li><strong>Debug Mode</strong> &#8211; Turning debugging on will echo the command being run to the screen.</li>\n	<li><strong>Backup Path</strong> &#8211; Set the directory that your backups will be saved to.</li>\n	<li><strong>Mysqldump Path</strong> &#8211; It&#8217;s likely that the default will work for you.If not, enter the full path the the executable.</li>\n	<li><strong>Mysql Path</strong> &#8211; It&#8217;s likely that the default will work for you.If not, enter the full path the the executable.</li>\n</ul>\n\n<h2>Database Manager</h2>\n\n<p>The <strong>DB Manager panel</strong> displays information about your MySQL database and all of its tables. A detailed list includes the name of the table, number of rows and file space usage.</p>\n\n<p>You will also be alerted of any overhead or errors that need to be repaired.Tables can be repaired, dropped or backed up from this listing.</p>\n\n<ul>\n	<li>Clicking on the name of the table will run a <code>select * FROM [table name]</code> <span class=\"caps\">SQL</span> statement and take you to the <strong>Run <span class=\"caps\">SQL</span> panel</strong> to display the results.</li>\n	<li>Repair a single table in the listing by clicking the Repair link.</li>\n	<li>Repair all tables in the listing by clicking the Repair All link.</li>\n	<li>Backup a single table in the listing by clicking the Backup link.</li>\n	<li>Drop a single table in the listing by clicking the Drop link.</li>\n</ul>\n\n<h2>Run <span class=\"caps\">SQL</span> Window</h2>\n\n<p>The <strong>Run <span class=\"caps\">SQL</span> tab</strong> allows for free form entry and execution of <span class=\"caps\">SQL</span> statements. The <span class=\"caps\">SQL</span> window accepts</p>\n\n<p><span class=\"caps\">SELECT</span>, <span class=\"caps\">INSERT</span>, <span class=\"caps\">UPDATE</span>, <span class=\"caps\">CREATE</span>, <span class=\"caps\">REPLACE</span>, <span class=\"caps\">TRUNCATE</span>, and <span class=\"caps\">DELETE</span> statements. If a <span class=\"caps\">SELECT</span> statement is run, the results will be displayed below the <span class=\"caps\">SQL</span> window in a table.</p>\n\n<p>The table markup allows you to add your own styles for creating a <a href=\"http://www.agavegroup.com/?p=31\" rel=\"nofollow\"><span class=\"caps\">CSS</span> Scrollable Table</a>.</p>\n\n<h2>Major Ransom Contributors</h2>\n\n<ul>\n	<li>Jan Willem de Bruijn</li>\n	<li>Heikki Yl</li>\n</ul>', '// This is a PLUGIN TEMPLATE for Textpattern CMS.\n\n// Copy this file to a new name like abc_myplugin.php. Edit the code, then\n// run this file at the command line to produce a plugin for distribution:\n// $ php abc_myplugin.php > abc_myplugin-0.1.txt\n\n// Plugin name is optional. If unset, it will be extracted from the current\n// file name. Plugin names should start with a three letter prefix which is\n// unique and reserved for each plugin author (\"abc\" is just an example).\n// Uncomment and edit this line to override:\n$plugin[\'name\'] = \'rss_admin_db_manager\';\n\n// Allow raw HTML help, as opposed to Textile.\n// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\n// 1 = Plugin help is in raw HTML. Not recommended.\n# $plugin[\'allow_html_help\'] = 1;\n\n$plugin[\'version\'] = \'4.5\';\n$plugin[\'author\'] = \'Rob Sable\';\n$plugin[\'author_uri\'] = \'http://www.wilshireone.com/\';\n$plugin[\'description\'] = \'Database management system.\';\n\n// Plugin load order:\n// The default value of 5 would fit most plugins, while for instance comment\n// spam evaluators or URL redirectors would probably want to run earlier\n// (1...4) to prepare the environment for everything else that follows.\n// Values 6...9 should be considered for plugins which would work late.\n// This order is user-overrideable.\n$plugin[\'order\'] = \'5\';\n\n// Plugin \'type\' defines where the plugin is loaded\n// 0 = public              : only on the public side of the website (default)\n// 1 = public+admin        : on both the public and admin side\n// 2 = library             : only when include_plugin() or require_plugin() is called\n// 3 = admin               : only on the admin side (no AJAX)\n// 4 = admin+ajax          : only on the admin side (AJAX supported)\n// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\n$plugin[\'type\'] = \'3\';\n\n// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\n// Use an appropriately OR-ed combination of these flags.\n// The four high-order bits 0xf000 are available for this plugin\'s private use\nif (!defined(\'PLUGIN_HAS_PREFS\')) define(\'PLUGIN_HAS_PREFS\', 0x0001); // This plugin wants to receive \"plugin_prefs.{$plugin[\'name\']}\" events\nif (!defined(\'PLUGIN_LIFECYCLE_NOTIFY\')) define(\'PLUGIN_LIFECYCLE_NOTIFY\', 0x0002); // This plugin wants to receive \"plugin_lifecycle.{$plugin[\'name\']}\" events\n\n$plugin[\'flags\'] = \'0\';\n\n// Plugin \'textpack\' is optional. It provides i18n strings to be used in conjunction with gTxt().\n// Syntax:\n// ## arbitrary comment\n// #@event\n// #@language ISO-LANGUAGE-CODE\n// abc_string_name => Localized String\n\n/** Uncomment me, if you need a textpack\n$plugin[\'textpack\'] = <<< EOT\n#@admin\n#@language en-gb\nabc_sample_string => Sample String\nabc_one_more => One more\n#@language de-de\nabc_sample_string => Beispieltext\nabc_one_more => Noch einer\nEOT;\n**/\n// End of textpack\n\nif (!defined(\'txpinterface\')) @include_once (\'zem_tpl.php\');\n\n// --- BEGIN PLUGIN CODE ---\n\nif (txpinterface === \'admin\') {\n    add_privs(\'rss_db_man\', \'1\');\n    register_tab(\"extensions\", \"rss_db_man\", \"DB Manager\");\n    register_callback(\"rss_db_man\", \"rss_db_man\");\n    add_privs(\'rss_sql_run\', \'1\');\n    register_tab(\"extensions\", \"rss_sql_run\", \"Run SQL\");\n    register_callback(\"rss_sql_run\", \"rss_sql_run\");\n    add_privs(\'rss_db_bk\', \'1\');\n    register_tab(\"extensions\", \"rss_db_bk\", \"DB Backup\");\n    register_callback(\"rss_db_bk\", \"rss_db_bk\");\n}\n\nfunction rss_db_bk($event, $step)\n{\n    global $prefs, $rss_dbbk_path, $rss_dbbk_dump, $rss_dbbk_mysql, $rss_dbbk_lock, $rss_dbbk_txplog, $rss_dbbk_debug, $DB, $file_base_path;\n    if (!isset($rss_dbbk_lock)) {\n        $rss_dbbk_lock = \"1\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_lock\', val=\'$rss_dbbk_lock\'\");\n    }\n\n    if (!isset($rss_dbbk_txplog)) {\n        $rss_dbbk_txplog = \"1\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_txplog\', val=\'$rss_dbbk_txplog\'\");\n    }\n\n    if (!isset($rss_dbbk_debug)) {\n        $rss_dbbk_debug = \"0\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_debug\', val=\'$rss_dbbk_debug\'\");\n    }\n\n    if (!isset($rss_dbbk_path)) {\n        $rss_dbbk_path = $file_base_path;\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_path\', val=\'\" . addslashes($rss_dbbk_path) . \"\'\");\n    }\n\n    if (!isset($rss_dbbk_dump)) {\n        $rss_dbbk_dump = \"mysqldump\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_dump\', val=\'\" . addslashes($rss_dbbk_dump) . \"\'\");\n    }\n\n    if (!isset($rss_dbbk_mysql)) {\n        $rss_dbbk_mysql = \"mysql\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_mysql\', val=\'\" . addslashes($rss_dbbk_mysql) . \"\'\");\n    }\n\n    include (txpath . \'/include/txp_prefs.php\');\n\n    $bkpath = $rss_dbbk_path;\n    $iswin = preg_match(\'/Win/\', php_uname());\n    $mysql_hup = \' -h\' . $DB->host . \' -u\' . $DB->user . \' -p\' . escapeshellcmd($DB->pass);\n    $txplogps = ps(\'rss_dbbk_txplog\');\n\n    if (ps(\"save\")) {\n        pagetop(\"DB Manager\", \"Preferences Saved\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_path\')) . \"\'\", \"name = \'rss_dbbk_path\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_dump\')) . \"\'\", \"name = \'rss_dbbk_dump\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_mysql\')) . \"\'\", \"name = \'rss_dbbk_mysql\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_lock\') . \"\'\", \"name = \'rss_dbbk_lock\'\");\n        if (isset($txplogps)) safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_txplog\') . \"\'\", \"name = \'rss_dbbk_txplog\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_debug\') . \"\'\", \"name = \'rss_dbbk_debug\'\");\n        header(\"Location: index.php?event=rss_db_bk\");\n    } elseif (gps(\"bk\")) {\n        $bk_table = (gps(\"bk_table\")) ? \" --tables \" . gps(\"bk_table\") . \" \" : \"\";\n        $tabpath = (gps(\"bk_table\")) ? \"-\" . gps(\"bk_table\") : \"\";\n        $gzip = gps(\"gzip\");\n        $filename = time() . \'-\' . $DB->db . $tabpath;\n        $backup_path = $bkpath . \'/\' . $filename . \'.sql\';\n        $lock = ($rss_dbbk_lock) ? \"\" : \" --skip-lock-tables --skip-add-locks \";\n        echo $txplogps;\n        $nolog = ($rss_dbbk_txplog) ? \"\" : \" --ignore-table=\" . $DB->db . \".txp_log \";\n        $nolog = (isset($bk_table) && gps(\"bk_table\") == \"txp_log\") ? \"\" : $nolog;\n\n        if ($gzip) {\n            $backup_path.= \'.gz\';\n            $backup_cmd = $rss_dbbk_dump . $mysql_hup . \' -Q --add-drop-table \' . $lock . $nolog . $DB->db . $bk_table . \' | gzip > \' . $backup_path;\n        } else {\n            $backup_cmd = $rss_dbbk_dump . $mysql_hup . \' -Q --add-drop-table \' . $lock . $nolog . $DB->db . $bk_table . \' > \' . $backup_path;\n        }\n\n        $bkdebug = ($rss_dbbk_debug) ? $backup_cmd : \'\';\n        $error = \"\";\n\n        if (function_exists(\'passthru\')) {\n            passthru($backup_cmd, $error);\n        } else {\n            $dumpIt = popen($backup_cmd, \'r\');\n            pclose($dumpIt);\n        }\n\n        if (!is_writable($bkpath)) {\n            pagetop(\"DB Manager\", \"BACKUP FAILED: folder is not writable\");\n        } elseif ($error) {\n            unlink($backup_path);\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } elseif (!is_file($backup_path)) {\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } elseif (filesize($backup_path) == 0) {\n            unlink($backup_path);\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } else {\n            pagetop(\"DB Manager\", \"Backed Up: \" . $DB->db . \" to \" . $filename);\n        }\n    } elseif (gps(\"download\")) {\n        $fn = gps(\"download\");\n        $file_path = $bkpath . \'/\' . $fn;\n        header(\"Pragma: public\");\n        header(\"Expires: 0\");\n        header(\"Cache-Control: must-revalidate, post-check=0, pre-check=0\");\n        header(\"Content-Type: application/force-download\");\n        header(\"Content-Type: application/octet-stream\");\n        header(\"Content-Type: application/download\");\n        if (substr($fn, -2) == \"gz\") header(\"Content-Type: application/zip\");\n        header(\"Content-Disposition: attachment; filename=\" . basename($file_path) . \";\");\n        header(\"Content-Transfer-Encoding: binary\");\n        header(\"Content-Length: \" . filesize($file_path));\n        @readfile($file_path);\n    } elseif (gps(\"restore\")) {\n        if (stristr(gps(\"restore\") , \'.gz\')) {\n            $backup_cmd = \'gunzip < \' . $bkpath . \'/\' . gps(\"restore\") . \' | \' . $rss_dbbk_mysql . $mysql_hup . \' \' . $DB->db;\n        } else {\n            $backup_cmd = $rss_dbbk_mysql . $mysql_hup . \' \' . $DB->db . \' < \' . $bkpath . \'/\' . gps(\"restore\");\n        }\n\n        $bkdebug = ($rss_dbbk_debug) ? $backup_cmd : \'\';\n        $error = \"\";\n\n        if (function_exists(\'passthru\')) {\n            passthru($backup_cmd, $error);\n        } else {\n            $dumpIt = popen($backup_cmd, \'r\');\n            pclose($dumpIt);\n        }\n\n        if ($error) {\n            pagetop(\"DB Manager\", \"FAILED TO RESTORE: \" . $error);\n        } else {\n            pagetop(\"DB Manager\", \"Restored: \" . gps(\"restore\") . \" to \" . $DB->db);\n        }\n    } elseif (gps(\"delete\")) {\n        if (is_file($bkpath . \'/\' . gps(\"delete\"))) {\n            if (!unlink($bkpath . \'/\' . gps(\"delete\"))) {\n                pagetop(\"DB Manager\", \"Unable to Delete: \" . gps(\"delete\"));\n            } else {\n                pagetop(\"DB Manager\", \"Deleted: \" . gps(\"delete\"));\n            }\n        } else {\n            pagetop(\"DB Manager\", \"Unable to Delete: \" . gps(\"delete\"));\n        }\n    } else {\n        pagetop(\"DB Backup\");\n    }\n\n    $gzp = (!$iswin) ? \" | \" . href(\'gzipped file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db&amp;gzip=1\") : \"\";\n    $sqlversion = getRow(\"SELECT VERSION() AS version\");\n    $sqlv = explode(\"-\", $sqlversion[\'version\']);\n    $allownologs = ((float)$sqlv[0] >= (float)\"4.1.9\") ? tda(gTxt(\'Include txp_log:\') , \' style=\"text-align:right;vertical-align:middle\"\') . tda(yesnoRadio(\"rss_dbbk_txplog\", $rss_dbbk_txplog) , \' style=\"text-align:left;vertical-align:middle\"\') : \'\';\n\n    if (isset($bkdebug) && $bkdebug) {\n        echo \'<p align=\"center\">\' . $bkdebug . \'</p>\';\n    }\n\n    echo startTable(\'txp-list\') .\n        form(\n            tr(\n                tda(gTxt(\'Lock Tables:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(yesnoRadio(\"rss_dbbk_lock\", $rss_dbbk_lock), \' style=\"text-align:left;vertical-align:middle\"\') .\n                $allownologs .\n                tda(gTxt(\'Debug Mode:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(yesnoRadio(\"rss_dbbk_debug\", $rss_dbbk_debug), \' style=\"text-align:left;vertical-align:middle\"\') .\n                tda(fInput(\"submit\", \"save\", gTxt(\"save\") , \"publish\") .\n                eInput(\"rss_db_bk\") .\n                sInput(\'saveprefs\'), \" colspan=\\\"2\\\" class=\\\"noline\\\"\")\n            ) .\n            tr(\n                tda(gTxt(\'Backup Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_path\", $rss_dbbk_path, \'50\'), \' colspan=\"15\"\')\n            ) .\n            tr(\n                tda(gTxt(\'mysqldump Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_dump\", $rss_dbbk_dump, \'50\'), \' colspan=\"15\"\')\n            ) .\n            tr(\n                tda(gTxt(\'mysql Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_mysql\", $rss_dbbk_mysql, \'50\'), \' colspan=\"15\"\')\n            )\n        ) .\n        endTable() .\n        tag_start(\'div\', array(\'class\' => \'txp-listtables\')) .\n        startTable(\"txp-list\") .\n        tr(\n            tda(hed(\'Create a new backup of the \' . $DB->db . \' database\' .\n                br .\n                href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3), \' colspan=\"7\" style=\"text-align:center;\"\')\n        ) .\n        tr(\n            tdcs(hed(\"Previous Backup Files\", 1), 7)\n        ) .\n        tr(\n            hcell(\"No.\") .\n            hcell(\"Backup File Name\") .\n            hcell(\"Backup Date/Time\") .\n            hcell(\"Backup File Size\") .\n            hcell(\"\") .\n            hcell(\"\") .\n            hcell(\"\")\n        );\n\n    $totalsize = 0;\n    $no = 0;\n\n    if (!is_folder_empty($bkpath)) {\n        if (file_exists($bkpath)) {\n            $database_files = array();\n            $dir = new DirectoryIterator($bkpath);\n\n            foreach ($dir as $file) {\n                $extension = $file->getExtension();\n\n                if (!$file->isFile() || !$file->isReadable() || !in_array($extension, array(\'sql\', \'gz\'))) {\n                    continue;\n                }\n\n\n                $mtime = $file->getMTime();\n                $database_files[$mtime] = $file->getFilename();\n            }\n\n            krsort($database_files);\n\n            foreach ($database_files as $stamp => $filename) {\n                $no++;\n                $style = ($no % 2 == 0) ? \' style=\"background-color: #eee;\"\' : \'\';\n                $database_text = substr($filename, 0, 50);\n                $date_text = strftime(\"%A, %B %d, %Y [%H:%M:%S]\", $stamp);\n                $size_text = filesize($bkpath . \'/\' . $filename);\n                $totalsize+= $size_text;\n                echo tr(td($no) . td($database_text) . td($date_text) . td(prettyFileSize($size_text)) . \'<td><a href=\"index.php?event=rss_db_bk&amp;download=\' . $filename . \'\">Download</a></td>\' . \'<td><a href=\"index.php?event=rss_db_bk&amp;restore=\' . $filename . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Restore</a></td>\' . \'<td><a href=\"index.php?event=rss_db_bk&amp;delete=\' . $filename . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Delete</a></td>\', $style);\n            }\n\n            echo tr(tag($no . \" Backup File(s)\", \"th\", \' colspan=\"3\"\') . tag(prettyFileSize($totalsize) , \"th\", \' colspan=\"4\"\'));\n        } else {\n            echo tr(tda(hed(\'You have no database backups\' . br . \'Create a new backup of the \' . $DB->db . \' database\' . br . href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3) , \' colspan=\"7\" style=\"text-align:center;\"\'));\n        }\n    } else {\n        echo tr(tda(hed(\'You have no database backups\' . br . \'Create a new backup of the \' . $DB->db . \' database\' . br . href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3) , \' colspan=\"7\" style=\"text-align:center;\"\'));\n    }\n\n    echo endTable().tag_end(\'div\');\n}\n\nfunction rss_db_man($event, $step)\n{\n    global $DB;\n\n    if (gps(\"opt_table\")) {\n        $query = \"OPTIMIZE TABLE \" . gps(\"opt_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Optimzed: \" . gps(\"opt_table\"));\n    } elseif (gps(\"rep_table\")) {\n        $query = \"REPAIR TABLE \" . gps(\"rep_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Repaired: \" . gps(\"rep_table\"));\n    } elseif (gps(\"rep_all\")) {\n        $query = \"REPAIR TABLE \" . gps(\"rep_all\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Repaired All Tables\");\n    } elseif (gps(\"drop_table\")) {\n        $query = \"DROP TABLE \" . gps(\"drop_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Dropped: \" . gps(\"drop_table\"));\n    } else {\n        pagetop(\"Database Manager\");\n    }\n\n    $sqlversion = getRow(\"SELECT VERSION() AS version\");\n    $headatts = \' style=\"color:#0069D1;padding:0 10px 0 5px;\"\';\n\n    echo startTable(\'dbinfo\') .\n        tr(\n            hcell(\"Database Host:\") .\n            tda($DB->host, $headatts) .\n            hcell(\"Database Name:\") .\n            tda($DB->db, $headatts) .\n            hcell(\"Database User:\") .\n            tda($DB->user, $headatts) .\n            hcell(\"Database Version:\") .\n            tda(\"MySQL v\" . $sqlversion[\'version\'], $headatts)\n        ) .\n        endTable() .\n        br;\n\n    echo startTable(\'list\') .\n        tr(\n            hcell(\"No.\") .\n            hcell(\"Tables\") .\n            hcell(\"Records\") .\n            hcell(\"Data Usage\") .\n            hcell(\"Index Usage\") .\n            hcell(\"Total Usage\") .\n            hcell(\"Overhead\") .\n            hcell(\"ErrNo\") .\n            hcell(\"Repair\") .\n            hcell(\"Backup\") .\n            hcell(\"Drop\")\n        );\n\n    if (version_compare($sqlversion[\'version\'], \'3.23\', \'>=\')) {\n        $no = 0;\n        $row_usage = 0;\n        $data_usage = 0;\n        $index_usage = 0;\n        $overhead_usage = 0;\n        $alltabs = array();\n        $tablesstatus = getRows(\"SHOW TABLE STATUS\");\n\n        foreach ($tablesstatus as $tablestatus) {\n            extract($tablestatus);\n            $q = \"SHOW KEYS FROM `\" . $Name . \"`\";\n            safe_query($q);\n            $mysqlErrno = mysqli_errno($DB->link);\n            $alltabs[] = $Name;\n            $color = ($mysqlErrno != 0) ? \' style=\"color:#D10000;\"\' : \' style=\"color:#4B9F00;\"\';\n            $color2 = ($Data_free > 0) ? \' style=\"color:#D10000;\"\' : \' style=\"color:#4B9F00;\"\';\n            $style = ($no % 2 == 0) ? \' style=\"background-color: #eee;\"\' : \'\';\n            $no++;\n            $row_usage+= $Rows;\n            $data_usage+= $Data_length;\n            $index_usage+= $Index_length;\n            $overhead_usage+= $Data_free;\n            echo tr(td($no) . td(href($Name, \"index.php?event=rss_sql_run&amp;tn=\" . $Name)) . td(\" \" . $Rows) . td(prettyFileSize($Data_length)) . td(prettyFileSize($Index_length)) . td(prettyFileSize($Data_length + $Index_length)) . tda(prettyFileSize($Data_free) , $color2) . tda(\" \" . $mysqlErrno, $color) . td(href(\"Repair\", \"index.php?event=rss_db_man&amp;rep_table=\" . $Name)) . td(href(\"Backup\", \"index.php?event=rss_db_bk&amp;bk=1&amp;bk_table=\" . $Name) . \'<td><a href=\"index.php?event=rss_db_man&amp;drop_table=\' . $Name . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Drop</a></td>\') , $style);\n        }\n\n        echo tr(hcell(\"Total\") . hcell($no . \" Tables\") . hcell(number_format($row_usage)) . hcell(prettyFileSize($data_usage)) . hcell(prettyFileSize($index_usage)) . hcell(prettyFileSize($data_usage + $index_usage)) . hcell(prettyFileSize($overhead_usage)) . hcell() . tda(href(strong(\"Repair All\") , \"index.php?event=rss_db_man&amp;rep_all=\" . implode(\",\", $alltabs)) , \' style=\"text-align:center;\" colspan=\"3\"\') , $style);\n    } else {\n        echo tr(tda(\"Could Not Show Table Status Because Your MYSQL Version Is Lower Than 3.23.\", \' style=\"text-align:center;\" colspan=14\"\'));\n    }\n\n    echo tr(tda(href(\"Run SQL\", \"index.php?event=rss_sql_run\") , \' style=\"text-align:center;\" colspan=\"14\"\')) . endTable();\n}\n\nfunction rss_sql_run($event, $step)\n{\n    global $DB;\n\n    pagetop(\"Run SQL Query\");\n    $text = \"\";\n    $rsd[] = \"\";\n    $sql_query2 = \"\";\n\n    if (gps(\"tn\")) {\n        $tq = \"select * from \" . gps(\"tn\");\n    }\n\n    if (gps(\"sql_query\") || gps(\"tn\")) {\n        $sql_queries2 = (gps(\"sql_query\")) ? trim(gps(\"sql_query\")) : trim($tq);\n        $totalquerycount = 0;\n        $successquery = 0;\n\n        if ($sql_queries2) {\n            $sql_queries = array();\n            $sql_queries2 = explode(\"\\n\", $sql_queries2);\n\n            foreach ($sql_queries2 as $sql_query2) {\n                $sql_query2 = trim(stripslashes($sql_query2));\n                $sql_query2 = preg_replace(\"/[\\r\\n]+/\", \'\', $sql_query2);\n\n                if (!empty($sql_query2)) {\n                    $sql_queries[] = $sql_query2;\n                }\n            }\n\n            foreach ($sql_queries as $sql_query) {\n                if (preg_match(\"/^\\\\s*(insert|update|replace|delete|create|truncate) /i\", $sql_query)) {\n                    $run_query = safe_query($sql_query);\n\n                    if (!$run_query) {\n                        $text.= graf(mysqli_error($DB->link) , \' style=\"color:#D10000;\"\');\n                        $text.= graf($sql_query, \' style=\"color:#D10000;\"\');\n                    } else {\n                        $successquery++;\n                        $text.= graf($sql_query, \' style=\"color:#4B9F00;\"\');\n                    }\n\n                    $totalquerycount++;\n                } elseif (preg_match(\"/^\\\\s*(select) /i\", $sql_query)) {\n                    $run_query = safe_query($sql_query);\n\n                    if ($run_query) {\n                        $successquery++;\n                    }\n\n                    if ($run_query && mysqli_num_rows($run_query) > 0) {\n                        /* get column metadata */\n                        $i = 0;\n                        $headers = \"\";\n\n                        while ($i < mysqli_num_fields($run_query)) {\n                            $meta = mysqli_fetch_field($run_query);\n                            $headers.= hcell($meta->name);\n                            $i++;\n                        }\n\n                        $rsd[] = \'<div class=\"scrollWrapper\">\' . startTable(\'list\', \'\', \'scrollable\') . \'<thead>\' . tr($headers) . \'</thead><tbody>\';\n\n                        while ($a = mysqli_fetch_assoc($run_query)) {\n                            $out[] = $a;\n                        }\n\n                        mysqli_free_result($run_query);\n\n                        foreach ($out as $b) {\n                            $data = \"\";\n\n                            foreach ($b as $f) {\n                                $data.= td($f);\n                            }\n\n                            $rsd[] = tr($data);\n                        }\n\n                        $rsd[] = \'</tbody>\' . endTable() . \'</div>\' . br;\n                        $out = array();\n                    } else {\n                        $text.= graf(mysqli_error($DB->link) , \' style=\"color:#D10000;\"\');\n                    }\n\n                    $text.= graf($sql_query, \' style=\"color:#D10000;\"\');\n                    $totalquerycount++;\n                } elseif (preg_match(\"/^\\\\s*(drop|show|grant) /i\", $sql_query)) {\n                    $text.= graf($sql_query . \" - QUERY TYPE NOT SUPPORTED\", \' style=\"color:#D10000;\"\');\n                    $totalquerycount++;\n                }\n            }\n\n            $text.= graf($successquery . \"/\" . $totalquerycount . \" Query(s) Executed Successfully\", \' style=\"color:#0069D1;\"\');\n        }\n    }\n\n    echo startTable(\'edit\') .\n        tr(\n            td(\n                form(\n                    graf(\"Each query must be on a single line.You may run multiple queries at once by starting a new line.\" .\n                        br .\n                        \"Supported query types include SELECT, INSERT, UPDATE, CREATE, REPLACE, and DELETE.\") .\n                    graf(\"WARNING: All SQL run in this window will immediately and permanently change your database.\", \' style=\"font-weight:bold;\"\') .\n                    text_area(\'sql_query\', \'200\', \'550\', $sql_query2) .\n                    br .\n                    fInput(\'submit\', \'run\', gTxt(\'Run\'), \'publish\') .\n                    href(\"Go to Database Manager\", \"index.php?event=rss_db_man\") .\n                    eInput(\'rss_sql_run\'), \'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n                )\n            )\n        ) .\n        tr(\n            td(graf($text . br . implode(\'\', $rsd)))\n        ) .\n        endTable();\n}\n\nfunction prettyFileSize($bytes)\n{\n    if ($bytes < 1024) {\n        return \"$bytes bytes\";\n    } elseif (strlen($bytes) <= 9 && strlen($bytes) >= 7) {\n        return number_format($bytes / 1048576, 2) . \" MB\";\n    } elseif (strlen($bytes) >= 10) {\n        return number_format($bytes / 1073741824, 2) . \" GB\";\n    }\n\n    return number_format($bytes / 1024, 2) . \" KB\";\n}\n\nfunction is_folder_empty($dir)\n{\n    if (is_dir($dir)) {\n        $dl = opendir($dir);\n\n        if ($dl) {\n            while ($name = readdir($dl)) {\n                if (!is_dir(\"$dir/$name\")) {\n                    return false;\n                    break;\n                }\n            }\n\n            closedir($dl);\n        }\n\n        return true;\n    } else return true;\n}', '// This is a PLUGIN TEMPLATE for Textpattern CMS.\n\n// Copy this file to a new name like abc_myplugin.php. Edit the code, then\n// run this file at the command line to produce a plugin for distribution:\n// $ php abc_myplugin.php > abc_myplugin-0.1.txt\n\n// Plugin name is optional. If unset, it will be extracted from the current\n// file name. Plugin names should start with a three letter prefix which is\n// unique and reserved for each plugin author (\"abc\" is just an example).\n// Uncomment and edit this line to override:\n$plugin[\'name\'] = \'rss_admin_db_manager\';\n\n// Allow raw HTML help, as opposed to Textile.\n// 0 = Plugin help is in Textile format, no raw HTML allowed (default).\n// 1 = Plugin help is in raw HTML. Not recommended.\n# $plugin[\'allow_html_help\'] = 1;\n\n$plugin[\'version\'] = \'4.5\';\n$plugin[\'author\'] = \'Rob Sable\';\n$plugin[\'author_uri\'] = \'http://www.wilshireone.com/\';\n$plugin[\'description\'] = \'Database management system.\';\n\n// Plugin load order:\n// The default value of 5 would fit most plugins, while for instance comment\n// spam evaluators or URL redirectors would probably want to run earlier\n// (1...4) to prepare the environment for everything else that follows.\n// Values 6...9 should be considered for plugins which would work late.\n// This order is user-overrideable.\n$plugin[\'order\'] = \'5\';\n\n// Plugin \'type\' defines where the plugin is loaded\n// 0 = public              : only on the public side of the website (default)\n// 1 = public+admin        : on both the public and admin side\n// 2 = library             : only when include_plugin() or require_plugin() is called\n// 3 = admin               : only on the admin side (no AJAX)\n// 4 = admin+ajax          : only on the admin side (AJAX supported)\n// 5 = public+admin+ajax   : on both the public and admin side (AJAX supported)\n$plugin[\'type\'] = \'3\';\n\n// Plugin \"flags\" signal the presence of optional capabilities to the core plugin loader.\n// Use an appropriately OR-ed combination of these flags.\n// The four high-order bits 0xf000 are available for this plugin\'s private use\nif (!defined(\'PLUGIN_HAS_PREFS\')) define(\'PLUGIN_HAS_PREFS\', 0x0001); // This plugin wants to receive \"plugin_prefs.{$plugin[\'name\']}\" events\nif (!defined(\'PLUGIN_LIFECYCLE_NOTIFY\')) define(\'PLUGIN_LIFECYCLE_NOTIFY\', 0x0002); // This plugin wants to receive \"plugin_lifecycle.{$plugin[\'name\']}\" events\n\n$plugin[\'flags\'] = \'0\';\n\n// Plugin \'textpack\' is optional. It provides i18n strings to be used in conjunction with gTxt().\n// Syntax:\n// ## arbitrary comment\n// #@event\n// #@language ISO-LANGUAGE-CODE\n// abc_string_name => Localized String\n\n/** Uncomment me, if you need a textpack\n$plugin[\'textpack\'] = <<< EOT\n#@admin\n#@language en-gb\nabc_sample_string => Sample String\nabc_one_more => One more\n#@language de-de\nabc_sample_string => Beispieltext\nabc_one_more => Noch einer\nEOT;\n**/\n// End of textpack\n\nif (!defined(\'txpinterface\')) @include_once (\'zem_tpl.php\');\n\n// --- BEGIN PLUGIN CODE ---\n\nif (txpinterface === \'admin\') {\n    add_privs(\'rss_db_man\', \'1\');\n    register_tab(\"extensions\", \"rss_db_man\", \"DB Manager\");\n    register_callback(\"rss_db_man\", \"rss_db_man\");\n    add_privs(\'rss_sql_run\', \'1\');\n    register_tab(\"extensions\", \"rss_sql_run\", \"Run SQL\");\n    register_callback(\"rss_sql_run\", \"rss_sql_run\");\n    add_privs(\'rss_db_bk\', \'1\');\n    register_tab(\"extensions\", \"rss_db_bk\", \"DB Backup\");\n    register_callback(\"rss_db_bk\", \"rss_db_bk\");\n}\n\nfunction rss_db_bk($event, $step)\n{\n    global $prefs, $rss_dbbk_path, $rss_dbbk_dump, $rss_dbbk_mysql, $rss_dbbk_lock, $rss_dbbk_txplog, $rss_dbbk_debug, $DB, $file_base_path;\n    if (!isset($rss_dbbk_lock)) {\n        $rss_dbbk_lock = \"1\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_lock\', val=\'$rss_dbbk_lock\'\");\n    }\n\n    if (!isset($rss_dbbk_txplog)) {\n        $rss_dbbk_txplog = \"1\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_txplog\', val=\'$rss_dbbk_txplog\'\");\n    }\n\n    if (!isset($rss_dbbk_debug)) {\n        $rss_dbbk_debug = \"0\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_debug\', val=\'$rss_dbbk_debug\'\");\n    }\n\n    if (!isset($rss_dbbk_path)) {\n        $rss_dbbk_path = $file_base_path;\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_path\', val=\'\" . addslashes($rss_dbbk_path) . \"\'\");\n    }\n\n    if (!isset($rss_dbbk_dump)) {\n        $rss_dbbk_dump = \"mysqldump\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_dump\', val=\'\" . addslashes($rss_dbbk_dump) . \"\'\");\n    }\n\n    if (!isset($rss_dbbk_mysql)) {\n        $rss_dbbk_mysql = \"mysql\";\n        $rs = safe_insert(\'txp_prefs\', \"name=\'rss_dbbk_mysql\', val=\'\" . addslashes($rss_dbbk_mysql) . \"\'\");\n    }\n\n    include (txpath . \'/include/txp_prefs.php\');\n\n    $bkpath = $rss_dbbk_path;\n    $iswin = preg_match(\'/Win/\', php_uname());\n    $mysql_hup = \' -h\' . $DB->host . \' -u\' . $DB->user . \' -p\' . escapeshellcmd($DB->pass);\n    $txplogps = ps(\'rss_dbbk_txplog\');\n\n    if (ps(\"save\")) {\n        pagetop(\"DB Manager\", \"Preferences Saved\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_path\')) . \"\'\", \"name = \'rss_dbbk_path\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_dump\')) . \"\'\", \"name = \'rss_dbbk_dump\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . addslashes(ps(\'rss_dbbk_mysql\')) . \"\'\", \"name = \'rss_dbbk_mysql\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_lock\') . \"\'\", \"name = \'rss_dbbk_lock\'\");\n        if (isset($txplogps)) safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_txplog\') . \"\'\", \"name = \'rss_dbbk_txplog\'\");\n        safe_update(\"txp_prefs\", \"val = \'\" . ps(\'rss_dbbk_debug\') . \"\'\", \"name = \'rss_dbbk_debug\'\");\n        header(\"Location: index.php?event=rss_db_bk\");\n    } elseif (gps(\"bk\")) {\n        $bk_table = (gps(\"bk_table\")) ? \" --tables \" . gps(\"bk_table\") . \" \" : \"\";\n        $tabpath = (gps(\"bk_table\")) ? \"-\" . gps(\"bk_table\") : \"\";\n        $gzip = gps(\"gzip\");\n        $filename = time() . \'-\' . $DB->db . $tabpath;\n        $backup_path = $bkpath . \'/\' . $filename . \'.sql\';\n        $lock = ($rss_dbbk_lock) ? \"\" : \" --skip-lock-tables --skip-add-locks \";\n        echo $txplogps;\n        $nolog = ($rss_dbbk_txplog) ? \"\" : \" --ignore-table=\" . $DB->db . \".txp_log \";\n        $nolog = (isset($bk_table) && gps(\"bk_table\") == \"txp_log\") ? \"\" : $nolog;\n\n        if ($gzip) {\n            $backup_path.= \'.gz\';\n            $backup_cmd = $rss_dbbk_dump . $mysql_hup . \' -Q --add-drop-table \' . $lock . $nolog . $DB->db . $bk_table . \' | gzip > \' . $backup_path;\n        } else {\n            $backup_cmd = $rss_dbbk_dump . $mysql_hup . \' -Q --add-drop-table \' . $lock . $nolog . $DB->db . $bk_table . \' > \' . $backup_path;\n        }\n\n        $bkdebug = ($rss_dbbk_debug) ? $backup_cmd : \'\';\n        $error = \"\";\n\n        if (function_exists(\'passthru\')) {\n            passthru($backup_cmd, $error);\n        } else {\n            $dumpIt = popen($backup_cmd, \'r\');\n            pclose($dumpIt);\n        }\n\n        if (!is_writable($bkpath)) {\n            pagetop(\"DB Manager\", \"BACKUP FAILED: folder is not writable\");\n        } elseif ($error) {\n            unlink($backup_path);\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } elseif (!is_file($backup_path)) {\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } elseif (filesize($backup_path) == 0) {\n            unlink($backup_path);\n            pagetop(\"DB Manager\", \"BACKUP FAILED.  ERROR NO: \" . $error);\n        } else {\n            pagetop(\"DB Manager\", \"Backed Up: \" . $DB->db . \" to \" . $filename);\n        }\n    } elseif (gps(\"download\")) {\n        $fn = gps(\"download\");\n        $file_path = $bkpath . \'/\' . $fn;\n        header(\"Pragma: public\");\n        header(\"Expires: 0\");\n        header(\"Cache-Control: must-revalidate, post-check=0, pre-check=0\");\n        header(\"Content-Type: application/force-download\");\n        header(\"Content-Type: application/octet-stream\");\n        header(\"Content-Type: application/download\");\n        if (substr($fn, -2) == \"gz\") header(\"Content-Type: application/zip\");\n        header(\"Content-Disposition: attachment; filename=\" . basename($file_path) . \";\");\n        header(\"Content-Transfer-Encoding: binary\");\n        header(\"Content-Length: \" . filesize($file_path));\n        @readfile($file_path);\n    } elseif (gps(\"restore\")) {\n        if (stristr(gps(\"restore\") , \'.gz\')) {\n            $backup_cmd = \'gunzip < \' . $bkpath . \'/\' . gps(\"restore\") . \' | \' . $rss_dbbk_mysql . $mysql_hup . \' \' . $DB->db;\n        } else {\n            $backup_cmd = $rss_dbbk_mysql . $mysql_hup . \' \' . $DB->db . \' < \' . $bkpath . \'/\' . gps(\"restore\");\n        }\n\n        $bkdebug = ($rss_dbbk_debug) ? $backup_cmd : \'\';\n        $error = \"\";\n\n        if (function_exists(\'passthru\')) {\n            passthru($backup_cmd, $error);\n        } else {\n            $dumpIt = popen($backup_cmd, \'r\');\n            pclose($dumpIt);\n        }\n\n        if ($error) {\n            pagetop(\"DB Manager\", \"FAILED TO RESTORE: \" . $error);\n        } else {\n            pagetop(\"DB Manager\", \"Restored: \" . gps(\"restore\") . \" to \" . $DB->db);\n        }\n    } elseif (gps(\"delete\")) {\n        if (is_file($bkpath . \'/\' . gps(\"delete\"))) {\n            if (!unlink($bkpath . \'/\' . gps(\"delete\"))) {\n                pagetop(\"DB Manager\", \"Unable to Delete: \" . gps(\"delete\"));\n            } else {\n                pagetop(\"DB Manager\", \"Deleted: \" . gps(\"delete\"));\n            }\n        } else {\n            pagetop(\"DB Manager\", \"Unable to Delete: \" . gps(\"delete\"));\n        }\n    } else {\n        pagetop(\"DB Backup\");\n    }\n\n    $gzp = (!$iswin) ? \" | \" . href(\'gzipped file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db&amp;gzip=1\") : \"\";\n    $sqlversion = getRow(\"SELECT VERSION() AS version\");\n    $sqlv = explode(\"-\", $sqlversion[\'version\']);\n    $allownologs = ((float)$sqlv[0] >= (float)\"4.1.9\") ? tda(gTxt(\'Include txp_log:\') , \' style=\"text-align:right;vertical-align:middle\"\') . tda(yesnoRadio(\"rss_dbbk_txplog\", $rss_dbbk_txplog) , \' style=\"text-align:left;vertical-align:middle\"\') : \'\';\n\n    if (isset($bkdebug) && $bkdebug) {\n        echo \'<p align=\"center\">\' . $bkdebug . \'</p>\';\n    }\n\n    echo startTable(\'txp-list\') .\n        form(\n            tr(\n                tda(gTxt(\'Lock Tables:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(yesnoRadio(\"rss_dbbk_lock\", $rss_dbbk_lock), \' style=\"text-align:left;vertical-align:middle\"\') .\n                $allownologs .\n                tda(gTxt(\'Debug Mode:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(yesnoRadio(\"rss_dbbk_debug\", $rss_dbbk_debug), \' style=\"text-align:left;vertical-align:middle\"\') .\n                tda(fInput(\"submit\", \"save\", gTxt(\"save\") , \"publish\") .\n                eInput(\"rss_db_bk\") .\n                sInput(\'saveprefs\'), \" colspan=\\\"2\\\" class=\\\"noline\\\"\")\n            ) .\n            tr(\n                tda(gTxt(\'Backup Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_path\", $rss_dbbk_path, \'50\'), \' colspan=\"15\"\')\n            ) .\n            tr(\n                tda(gTxt(\'mysqldump Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_dump\", $rss_dbbk_dump, \'50\'), \' colspan=\"15\"\')\n            ) .\n            tr(\n                tda(gTxt(\'mysql Path:\'), \' style=\"text-align:right;vertical-align:middle\"\') .\n                tda(text_input(\"rss_dbbk_mysql\", $rss_dbbk_mysql, \'50\'), \' colspan=\"15\"\')\n            )\n        ) .\n        endTable() .\n        tag_start(\'div\', array(\'class\' => \'txp-listtables\')) .\n        startTable(\"txp-list\") .\n        tr(\n            tda(hed(\'Create a new backup of the \' . $DB->db . \' database\' .\n                br .\n                href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3), \' colspan=\"7\" style=\"text-align:center;\"\')\n        ) .\n        tr(\n            tdcs(hed(\"Previous Backup Files\", 1), 7)\n        ) .\n        tr(\n            hcell(\"No.\") .\n            hcell(\"Backup File Name\") .\n            hcell(\"Backup Date/Time\") .\n            hcell(\"Backup File Size\") .\n            hcell(\"\") .\n            hcell(\"\") .\n            hcell(\"\")\n        );\n\n    $totalsize = 0;\n    $no = 0;\n\n    if (!is_folder_empty($bkpath)) {\n        if (file_exists($bkpath)) {\n            $database_files = array();\n            $dir = new DirectoryIterator($bkpath);\n\n            foreach ($dir as $file) {\n                $extension = $file->getExtension();\n\n                if (!$file->isFile() || !$file->isReadable() || !in_array($extension, array(\'sql\', \'gz\'))) {\n                    continue;\n                }\n\n\n                $mtime = $file->getMTime();\n                $database_files[$mtime] = $file->getFilename();\n            }\n\n            krsort($database_files);\n\n            foreach ($database_files as $stamp => $filename) {\n                $no++;\n                $style = ($no % 2 == 0) ? \' style=\"background-color: #eee;\"\' : \'\';\n                $database_text = substr($filename, 0, 50);\n                $date_text = strftime(\"%A, %B %d, %Y [%H:%M:%S]\", $stamp);\n                $size_text = filesize($bkpath . \'/\' . $filename);\n                $totalsize+= $size_text;\n                echo tr(td($no) . td($database_text) . td($date_text) . td(prettyFileSize($size_text)) . \'<td><a href=\"index.php?event=rss_db_bk&amp;download=\' . $filename . \'\">Download</a></td>\' . \'<td><a href=\"index.php?event=rss_db_bk&amp;restore=\' . $filename . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Restore</a></td>\' . \'<td><a href=\"index.php?event=rss_db_bk&amp;delete=\' . $filename . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Delete</a></td>\', $style);\n            }\n\n            echo tr(tag($no . \" Backup File(s)\", \"th\", \' colspan=\"3\"\') . tag(prettyFileSize($totalsize) , \"th\", \' colspan=\"4\"\'));\n        } else {\n            echo tr(tda(hed(\'You have no database backups\' . br . \'Create a new backup of the \' . $DB->db . \' database\' . br . href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3) , \' colspan=\"7\" style=\"text-align:center;\"\'));\n        }\n    } else {\n        echo tr(tda(hed(\'You have no database backups\' . br . \'Create a new backup of the \' . $DB->db . \' database\' . br . href(\'.sql file\', \"index.php?event=rss_db_bk&amp;bk=$DB->db\") . $gzp, 3) , \' colspan=\"7\" style=\"text-align:center;\"\'));\n    }\n\n    echo endTable().tag_end(\'div\');\n}\n\nfunction rss_db_man($event, $step)\n{\n    global $DB;\n\n    if (gps(\"opt_table\")) {\n        $query = \"OPTIMIZE TABLE \" . gps(\"opt_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Optimzed: \" . gps(\"opt_table\"));\n    } elseif (gps(\"rep_table\")) {\n        $query = \"REPAIR TABLE \" . gps(\"rep_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Repaired: \" . gps(\"rep_table\"));\n    } elseif (gps(\"rep_all\")) {\n        $query = \"REPAIR TABLE \" . gps(\"rep_all\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Repaired All Tables\");\n    } elseif (gps(\"drop_table\")) {\n        $query = \"DROP TABLE \" . gps(\"drop_table\");\n        safe_query($query);\n        pagetop(\"DB Manager\", \"Dropped: \" . gps(\"drop_table\"));\n    } else {\n        pagetop(\"Database Manager\");\n    }\n\n    $sqlversion = getRow(\"SELECT VERSION() AS version\");\n    $headatts = \' style=\"color:#0069D1;padding:0 10px 0 5px;\"\';\n\n    echo startTable(\'dbinfo\') .\n        tr(\n            hcell(\"Database Host:\") .\n            tda($DB->host, $headatts) .\n            hcell(\"Database Name:\") .\n            tda($DB->db, $headatts) .\n            hcell(\"Database User:\") .\n            tda($DB->user, $headatts) .\n            hcell(\"Database Version:\") .\n            tda(\"MySQL v\" . $sqlversion[\'version\'], $headatts)\n        ) .\n        endTable() .\n        br;\n\n    echo startTable(\'list\') .\n        tr(\n            hcell(\"No.\") .\n            hcell(\"Tables\") .\n            hcell(\"Records\") .\n            hcell(\"Data Usage\") .\n            hcell(\"Index Usage\") .\n            hcell(\"Total Usage\") .\n            hcell(\"Overhead\") .\n            hcell(\"ErrNo\") .\n            hcell(\"Repair\") .\n            hcell(\"Backup\") .\n            hcell(\"Drop\")\n        );\n\n    if (version_compare($sqlversion[\'version\'], \'3.23\', \'>=\')) {\n        $no = 0;\n        $row_usage = 0;\n        $data_usage = 0;\n        $index_usage = 0;\n        $overhead_usage = 0;\n        $alltabs = array();\n        $tablesstatus = getRows(\"SHOW TABLE STATUS\");\n\n        foreach ($tablesstatus as $tablestatus) {\n            extract($tablestatus);\n            $q = \"SHOW KEYS FROM `\" . $Name . \"`\";\n            safe_query($q);\n            $mysqlErrno = mysqli_errno($DB->link);\n            $alltabs[] = $Name;\n            $color = ($mysqlErrno != 0) ? \' style=\"color:#D10000;\"\' : \' style=\"color:#4B9F00;\"\';\n            $color2 = ($Data_free > 0) ? \' style=\"color:#D10000;\"\' : \' style=\"color:#4B9F00;\"\';\n            $style = ($no % 2 == 0) ? \' style=\"background-color: #eee;\"\' : \'\';\n            $no++;\n            $row_usage+= $Rows;\n            $data_usage+= $Data_length;\n            $index_usage+= $Index_length;\n            $overhead_usage+= $Data_free;\n            echo tr(td($no) . td(href($Name, \"index.php?event=rss_sql_run&amp;tn=\" . $Name)) . td(\" \" . $Rows) . td(prettyFileSize($Data_length)) . td(prettyFileSize($Index_length)) . td(prettyFileSize($Data_length + $Index_length)) . tda(prettyFileSize($Data_free) , $color2) . tda(\" \" . $mysqlErrno, $color) . td(href(\"Repair\", \"index.php?event=rss_db_man&amp;rep_table=\" . $Name)) . td(href(\"Backup\", \"index.php?event=rss_db_bk&amp;bk=1&amp;bk_table=\" . $Name) . \'<td><a href=\"index.php?event=rss_db_man&amp;drop_table=\' . $Name . \'\"onclick=\"return verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\">Drop</a></td>\') , $style);\n        }\n\n        echo tr(hcell(\"Total\") . hcell($no . \" Tables\") . hcell(number_format($row_usage)) . hcell(prettyFileSize($data_usage)) . hcell(prettyFileSize($index_usage)) . hcell(prettyFileSize($data_usage + $index_usage)) . hcell(prettyFileSize($overhead_usage)) . hcell() . tda(href(strong(\"Repair All\") , \"index.php?event=rss_db_man&amp;rep_all=\" . implode(\",\", $alltabs)) , \' style=\"text-align:center;\" colspan=\"3\"\') , $style);\n    } else {\n        echo tr(tda(\"Could Not Show Table Status Because Your MYSQL Version Is Lower Than 3.23.\", \' style=\"text-align:center;\" colspan=14\"\'));\n    }\n\n    echo tr(tda(href(\"Run SQL\", \"index.php?event=rss_sql_run\") , \' style=\"text-align:center;\" colspan=\"14\"\')) . endTable();\n}\n\nfunction rss_sql_run($event, $step)\n{\n    global $DB;\n\n    pagetop(\"Run SQL Query\");\n    $text = \"\";\n    $rsd[] = \"\";\n    $sql_query2 = \"\";\n\n    if (gps(\"tn\")) {\n        $tq = \"select * from \" . gps(\"tn\");\n    }\n\n    if (gps(\"sql_query\") || gps(\"tn\")) {\n        $sql_queries2 = (gps(\"sql_query\")) ? trim(gps(\"sql_query\")) : trim($tq);\n        $totalquerycount = 0;\n        $successquery = 0;\n\n        if ($sql_queries2) {\n            $sql_queries = array();\n            $sql_queries2 = explode(\"\\n\", $sql_queries2);\n\n            foreach ($sql_queries2 as $sql_query2) {\n                $sql_query2 = trim(stripslashes($sql_query2));\n                $sql_query2 = preg_replace(\"/[\\r\\n]+/\", \'\', $sql_query2);\n\n                if (!empty($sql_query2)) {\n                    $sql_queries[] = $sql_query2;\n                }\n            }\n\n            foreach ($sql_queries as $sql_query) {\n                if (preg_match(\"/^\\\\s*(insert|update|replace|delete|create|truncate) /i\", $sql_query)) {\n                    $run_query = safe_query($sql_query);\n\n                    if (!$run_query) {\n                        $text.= graf(mysqli_error($DB->link) , \' style=\"color:#D10000;\"\');\n                        $text.= graf($sql_query, \' style=\"color:#D10000;\"\');\n                    } else {\n                        $successquery++;\n                        $text.= graf($sql_query, \' style=\"color:#4B9F00;\"\');\n                    }\n\n                    $totalquerycount++;\n                } elseif (preg_match(\"/^\\\\s*(select) /i\", $sql_query)) {\n                    $run_query = safe_query($sql_query);\n\n                    if ($run_query) {\n                        $successquery++;\n                    }\n\n                    if ($run_query && mysqli_num_rows($run_query) > 0) {\n                        /* get column metadata */\n                        $i = 0;\n                        $headers = \"\";\n\n                        while ($i < mysqli_num_fields($run_query)) {\n                            $meta = mysqli_fetch_field($run_query);\n                            $headers.= hcell($meta->name);\n                            $i++;\n                        }\n\n                        $rsd[] = \'<div class=\"scrollWrapper\">\' . startTable(\'list\', \'\', \'scrollable\') . \'<thead>\' . tr($headers) . \'</thead><tbody>\';\n\n                        while ($a = mysqli_fetch_assoc($run_query)) {\n                            $out[] = $a;\n                        }\n\n                        mysqli_free_result($run_query);\n\n                        foreach ($out as $b) {\n                            $data = \"\";\n\n                            foreach ($b as $f) {\n                                $data.= td($f);\n                            }\n\n                            $rsd[] = tr($data);\n                        }\n\n                        $rsd[] = \'</tbody>\' . endTable() . \'</div>\' . br;\n                        $out = array();\n                    } else {\n                        $text.= graf(mysqli_error($DB->link) , \' style=\"color:#D10000;\"\');\n                    }\n\n                    $text.= graf($sql_query, \' style=\"color:#D10000;\"\');\n                    $totalquerycount++;\n                } elseif (preg_match(\"/^\\\\s*(drop|show|grant) /i\", $sql_query)) {\n                    $text.= graf($sql_query . \" - QUERY TYPE NOT SUPPORTED\", \' style=\"color:#D10000;\"\');\n                    $totalquerycount++;\n                }\n            }\n\n            $text.= graf($successquery . \"/\" . $totalquerycount . \" Query(s) Executed Successfully\", \' style=\"color:#0069D1;\"\');\n        }\n    }\n\n    echo startTable(\'edit\') .\n        tr(\n            td(\n                form(\n                    graf(\"Each query must be on a single line.You may run multiple queries at once by starting a new line.\" .\n                        br .\n                        \"Supported query types include SELECT, INSERT, UPDATE, CREATE, REPLACE, and DELETE.\") .\n                    graf(\"WARNING: All SQL run in this window will immediately and permanently change your database.\", \' style=\"font-weight:bold;\"\') .\n                    text_area(\'sql_query\', \'200\', \'550\', $sql_query2) .\n                    br .\n                    fInput(\'submit\', \'run\', gTxt(\'Run\'), \'publish\') .\n                    href(\"Go to Database Manager\", \"index.php?event=rss_db_man\") .\n                    eInput(\'rss_sql_run\'), \'\', \' verify(\\\'\' . gTxt(\'are_you_sure\') . \'\\\')\"\'\n                )\n            )\n        ) .\n        tr(\n            td(graf($text . br . implode(\'\', $rsd)))\n        ) .\n        endTable();\n}\n\nfunction prettyFileSize($bytes)\n{\n    if ($bytes < 1024) {\n        return \"$bytes bytes\";\n    } elseif (strlen($bytes) <= 9 && strlen($bytes) >= 7) {\n        return number_format($bytes / 1048576, 2) . \" MB\";\n    } elseif (strlen($bytes) >= 10) {\n        return number_format($bytes / 1073741824, 2) . \" GB\";\n    }\n\n    return number_format($bytes / 1024, 2) . \" KB\";\n}\n\nfunction is_folder_empty($dir)\n{\n    if (is_dir($dir)) {\n        $dl = opendir($dir);\n\n        if ($dl) {\n            while ($name = readdir($dl)) {\n                if (!is_dir(\"$dir/$name\")) {\n                    return false;\n                    break;\n                }\n            }\n\n            closedir($dl);\n        }\n\n        return true;\n    } else return true;\n}', '3791c30762cd962f731834a807f6cd58', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbs_gravatar', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.3', 'Simple gravatar display', '<h3>Description</h3>\n\n<p>This is a simple, clean plugin to display <a href=\"http://www.gravatar.com\" rel=\"nofollow\">gravatars</a> in comment forms. It doesn&#8217;t use any cache and provides access to all Gravatar options.</p>\n\n<p>It outputs an <code>&lt;img&gt;</code> tag. If wanted, the image can be linked to the commenter&#8217;s website.</p>\n\n<h3>Syntax</h3>\n\n<p>In a <em>comment</em> form:</p>\n\n<pre><code>&lt;txp:cbs_gravatar /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<h4>Required</h4>\n\n<p>none.</p>\n\n<h4>Optional</h4>\n\n<ul>\n	<li><strong>default</strong>: Absolute <span class=\"caps\">URL</span> of the default image to display when the commenter has no gravatar. This default image is never resized, so make sure it has the same size as <code>size</code> (see below) for a consistent display. When not specified, Gravatar will return a 1&#215;1 pixel <span class=\"caps\">GIF</span> image.</li>\n	<li><strong>size</strong>: The square size of the gravatar image, in pixels (numeric value in the range 1 to 80). Gravatar will resize the default 80&#215;80 pixels image to this size, using bicubic resizing. When not specified, it will return the image in its original size of 80&#215;80 pixels.</li>\n	<li><strong>rating</strong>: Either ‘G’, ‘PG’, ‘R’ or ‘X’. The commenter&#8217;s gravatar will be displayed if it does not <em>exceed</em> the specified rating, otherwise the default image will be shown (see above). By default, all gravatars are shown without restriction.</li>\n	<li><strong>border</strong>: Hexadecimal value of the color of a 1px border to be overlaid on the gravatar (example: FF0000). Default is no added border.</li>\n	<li><strong>alt</strong>: Alternative text for the image. Default is empty.</li>\n	<li><strong>class</strong>: <span class=\"caps\">CSS</span> class attribute for the <code>&lt;img&gt;</code> tag, default is no class attribute.</li>\n	<li><strong>link</strong>: When set to <code>true</code>, the image will be linked to the commenter&#8217;s website (if available). The link will have a “nofollow” attribute if specified in the Textpattern advanced options. Default is <code>false</code>.</li>\n</ul>', '\n/*\n   cbs_gravatar v0.3\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_gravatar($atts)\n{\n	global $thiscomment, $prefs;\n\n	extract(lAtts(array(\n		\'default\' => false,\n		\'size\'    => 80,\n		\'rating\'  => false,\n		\'border\'  => false,\n		\'alt\'     => \'\',\n		\'class\'   => false,\n		\'link\'    => \'\'\n	),$atts));\n	$size = min($size, 80);\n\n	$out = \'<img src=\"http://www.gravatar.com/avatar.php?gravatar_id=\'.md5($thiscomment[\'email\'])\n		.($default ? \'&amp;default=\'.urlencode($default) : \'\')\n		.(($size != 80) ? \'&amp;size=\'.$size : \'\')\n		.($rating ? \'&amp;rating=\'.$rating : \'\')\n		.($border ? \'&amp;border=\'.$border : \'\')\n		.\'\" alt=\"\'.$alt.\'\" width=\"\'.$size.\'\" height=\"\'.$size\n		.($class ? \'\" class=\"\'.$class.\'\" />\' : \'\" />\');\n\n	if($link == \'true\') {\n		$web = str_replace(\'http://\', \'\', $thiscomment[\'web\']);\n		if($web) return tag($out, \'a\', \' href=\"http://\'.$web.($prefs[\'comment_nofollow\'] ? \'\" rel=\"nofollow\"\' : \'\"\'));\n	}\n\n	return $out;\n}\n', '\n/*\n   cbs_gravatar v0.3\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_gravatar($atts)\n{\n	global $thiscomment, $prefs;\n\n	extract(lAtts(array(\n		\'default\' => false,\n		\'size\'    => 80,\n		\'rating\'  => false,\n		\'border\'  => false,\n		\'alt\'     => \'\',\n		\'class\'   => false,\n		\'link\'    => \'\'\n	),$atts));\n	$size = min($size, 80);\n\n	$out = \'<img src=\"http://www.gravatar.com/avatar.php?gravatar_id=\'.md5($thiscomment[\'email\'])\n		.($default ? \'&amp;default=\'.urlencode($default) : \'\')\n		.(($size != 80) ? \'&amp;size=\'.$size : \'\')\n		.($rating ? \'&amp;rating=\'.$rating : \'\')\n		.($border ? \'&amp;border=\'.$border : \'\')\n		.\'\" alt=\"\'.$alt.\'\" width=\"\'.$size.\'\" height=\"\'.$size\n		.($class ? \'\" class=\"\'.$class.\'\" />\' : \'\" />\');\n\n	if($link == \'true\') {\n		$web = str_replace(\'http://\', \'\', $thiscomment[\'web\']);\n		if($web) return tag($out, \'a\', \' href=\"http://\'.$web.($prefs[\'comment_nofollow\'] ? \'\" rel=\"nofollow\"\' : \'\"\'));\n	}\n\n	return $out;\n}\n', 'fbb6e9dae1247bf8ef69235827387992', '', '', 0, 5, 0),
('zem_cache', 0, 'Threshold State', 'http://thresholdstate.com/', '0.51', 'Caching experiment', '	<p>zem_cache is a simple caching plugin. It might help improve Textpattern performance and reduce server load on pages that involve heavy <span class=\"caps\">SQL</span> queries.</p>\n\n	<p>To use it, surround a page with the txp:zem_cache tag, like this:</p>\n\n	<p><txp:zem_cache><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><br />\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><br />\n<head><br />\n[&#8230;etc&#8230;]<br />\n</body><br />\n</html><br />\n</txp:zem_cache></p>\n\n	<p>note – a writable temp directory must be available.</p>\n\n	<p>You don’t have to cache the entire page. If you’re using more than one zem_cache tag per page, see the ‘id’ and ‘ctx’ attributes below.</p>\n\n	<p>The cache must be flushed manually; it won’t happen automatically when you post or edit an article. To flush the cache, append ”?flush_cache=1” to any Textpattern <span class=\"caps\">URL</span>. Alternatively, place a tag on a seldom-used page, and load that page in your browser to force a flush.</p>\n\n	<p>You can check if it’s working by viewing the source of a cached page and scrolling to the bottom. There should be a message that says “fresh copy”, “cached copy” or “cache flushed”.</p>\n\n	<p>Supported attributes:</p>\n\n	<p><strong>timeout</strong> – Specifies the expiry time in seconds. Default is 3600. A small figure like 120 will give you some protection against heavy load, and eliminate the need for manual flushing.</p>\n\n	<p><strong>id</strong> – Use this if you’re using multiple zem_cache tags per page. Each tag should have a different id. It doesn’t matter what the id is set to – a number is fine – as long as each one is different.</p>\n\n	<p><strong>ctx</strong> – Use this to cache a single copy of something globally across all pages, rather than once per page. For example, <txp:zem_cache ctx=\"recent1\"><txp:recent_articles /></txp:zem_cache></p>\n\n', '\n// ----------------------------------------------------\n// Example public side tags\n\n// Plugin code goes here.  No need to escape quotes.\n\n    if (function_exists(\"register_callback\")) {\n	register_callback(\"zem_flush_event\", \"article\");\n	register_callback(\"zem_flush_event\", \"page\");\n	register_callback(\"zem_flush_event\", \"form\");\n    }\n\n    function zem_flush_event($event, $step) {\n	$count = zem_flush();\n\n	if ($count)\n	    print \"<!-- cache flushed ($count) -->\\n\";\n    }\n\n    function zem_flush($id=0) {\n	global $txpcfg, $zem_cache_dir, $lastmod;\n\n	$count = 0;\n\n	if ($id) {\n	    @unlink(\"$zem_cache_dir/$id\");\n	    ++$count;\n	}\n	elseif (!empty($zem_cache_dir) and $fp = opendir($zem_cache_dir)) {\n	    $last = strtotime($lastmod);\n	    while (false !== ($file = readdir($fp))) {\n		if ($file{0} != \".\" and filemtime(\"$zem_cache_dir/$file\") < $last) {\n		    @unlink(\"$zem_cache_dir/$file\");\n		    ++$count;\n		}\n	    }\n\n	    closedir($fp);\n	}\n\n	return $count;\n    }\n\n    function zem_cache($atts, $thing) {\n	global $tempdir, $txpcfg, $zem_cache_dir;\n\n	extract(lAtts(array(\n	    \'id\'         => 0,\n	    \'ctx\'        => (serverSet(\'REQUEST_URI\') ? serverSet(\'REQUEST_URI\') : serverSet(\'PHP_SELF\')),\n	    \'timeout\'    => 3600,\n	    \'allowflush\' => 1,\n	), $atts));\n\n	$old_umask = umask(0000);\n\n	if (!isset($zem_cache_dir)) {\n	    do {\n		$zem_cache_dir = $tempdir.DS.md5(uniqid(rand(), true));\n	    }\n	    while (file_exists($zem_cache_dir));\n	    safe_insert(\'txp_prefs\',\"prefs_id=1,name=\'zem_cache_dir\',val=\'\".doSlash($zem_cache_dir).\"\'\");\n	}\n	if (!is_dir($zem_cache_dir)) {\n	    if (!@mkdir($zem_cache_dir, 0777))\n		$zem_cache_dir = NULL;\n	}\n\n	if (!is_dir($zem_cache_dir.\"/noreset\")) {\n	    if (!@mkdir($zem_cache_dir.\"/noreset\", 0777))\n		$zem_cache_dir = NULL;\n	}\n\n	if (empty($zem_cache_dir)) {\n	    $content = parse($thing);\n	}\n	else {\n                if ($id==\"0\") {$noreset=\"noreset/\";} else{$noreset=\"\";}\n	    $cache_id = md5($id.$ctx);\n	    $cache_file = \"$zem_cache_dir/$noreset$cache_id\";\n\n	    if ($allowflush and gps(\"flush_cache\")) {\n		zem_flush();\n		$content = parse($thing);\n		$content .= \"\\n<!-- cache flushed -->\\n\";\n	    }\n	    elseif (!empty($_POST)) {\n		$content = parse($thing);\n		zem_flush($cache_id);\n		$content .= \"\\n<!-- cache bypassed -->\\n\";\n	    }\n	    elseif (is_readable($cache_file) and (time() - filemtime($cache_file) < $timeout)) {\n		$content = \"<!-- cached copy -->\\n\";\n		if (is_callable(\'file_get_contents\'))\n		    $content .= file_get_contents($cache_file);\n		else\n		    $content .= join(\"\", file($cache_file));\n		$content .= \"\\n<!-- end cached copy -->\\n\";\n	    }\n	    else {\n		$content = parse($thing);\n		$fp = fopen($cache_file, \"wb\");\n		fwrite($fp, $content);\n		fclose($fp);\n		$content = \"<!-- fresh copy -->\\n\" . $content . \"\\n<!-- end fresh copy -->\\n\";\n	    }\n	}\n\n	umask($old_umask);\n\n	return $content;\n\n    }\n\n', '\n// ----------------------------------------------------\n// Example public side tags\n\n// Plugin code goes here.  No need to escape quotes.\n\n    if (function_exists(\"register_callback\")) {\n	register_callback(\"zem_flush_event\", \"article\");\n	register_callback(\"zem_flush_event\", \"page\");\n	register_callback(\"zem_flush_event\", \"form\");\n    }\n\n    function zem_flush_event($event, $step) {\n	$count = zem_flush();\n\n	if ($count)\n	    print \"<!-- cache flushed ($count) -->\\n\";\n    }\n\n    function zem_flush($id=0) {\n	global $txpcfg, $zem_cache_dir, $lastmod;\n\n	$count = 0;\n\n	if ($id) {\n	    @unlink(\"$zem_cache_dir/$id\");\n	    ++$count;\n	}\n	elseif (!empty($zem_cache_dir) and $fp = opendir($zem_cache_dir)) {\n	    $last = strtotime($lastmod);\n	    while (false !== ($file = readdir($fp))) {\n		if ($file{0} != \".\" and filemtime(\"$zem_cache_dir/$file\") < $last) {\n		    @unlink(\"$zem_cache_dir/$file\");\n		    ++$count;\n		}\n	    }\n\n	    closedir($fp);\n	}\n\n	return $count;\n    }\n\n    function zem_cache($atts, $thing) {\n	global $tempdir, $txpcfg, $zem_cache_dir;\n\n	extract(lAtts(array(\n	    \'id\'         => 0,\n	    \'ctx\'        => (serverSet(\'REQUEST_URI\') ? serverSet(\'REQUEST_URI\') : serverSet(\'PHP_SELF\')),\n	    \'timeout\'    => 3600,\n	    \'allowflush\' => 1,\n	), $atts));\n\n	$old_umask = umask(0000);\n\n	if (!isset($zem_cache_dir)) {\n	    do {\n		$zem_cache_dir = $tempdir.DS.md5(uniqid(rand(), true));\n	    }\n	    while (file_exists($zem_cache_dir));\n	    safe_insert(\'txp_prefs\',\"prefs_id=1,name=\'zem_cache_dir\',val=\'\".doSlash($zem_cache_dir).\"\'\");\n	}\n	if (!is_dir($zem_cache_dir)) {\n	    if (!@mkdir($zem_cache_dir, 0777))\n		$zem_cache_dir = NULL;\n	}\n\n	if (!is_dir($zem_cache_dir.\"/noreset\")) {\n	    if (!@mkdir($zem_cache_dir.\"/noreset\", 0777))\n		$zem_cache_dir = NULL;\n	}\n\n	if (empty($zem_cache_dir)) {\n	    $content = parse($thing);\n	}\n	else {\n                if ($id==\"0\") {$noreset=\"noreset/\";} else{$noreset=\"\";}\n	    $cache_id = md5($id.$ctx);\n	    $cache_file = \"$zem_cache_dir/$noreset$cache_id\";\n\n	    if ($allowflush and gps(\"flush_cache\")) {\n		zem_flush();\n		$content = parse($thing);\n		$content .= \"\\n<!-- cache flushed -->\\n\";\n	    }\n	    elseif (!empty($_POST)) {\n		$content = parse($thing);\n		zem_flush($cache_id);\n		$content .= \"\\n<!-- cache bypassed -->\\n\";\n	    }\n	    elseif (is_readable($cache_file) and (time() - filemtime($cache_file) < $timeout)) {\n		$content = \"<!-- cached copy -->\\n\";\n		if (is_callable(\'file_get_contents\'))\n		    $content .= file_get_contents($cache_file);\n		else\n		    $content .= join(\"\", file($cache_file));\n		$content .= \"\\n<!-- end cached copy -->\\n\";\n	    }\n	    else {\n		$content = parse($thing);\n		$fp = fopen($cache_file, \"wb\");\n		fwrite($fp, $content);\n		fclose($fp);\n		$content = \"<!-- fresh copy -->\\n\" . $content . \"\\n<!-- end fresh copy -->\\n\";\n	    }\n	}\n\n	umask($old_umask);\n\n	return $content;\n\n    }\n\n', 'be31c98f2a78191f2f8bbc69a34e3798', '', '', 1, 5, 0),
('esq_glzcfvallist', 0, '', '', '1.03', 'Output all values of a glz_custom_field', 'For help or more information on <strong>esq_glzcfvallist</strong>, please see the <a href=\"http://forum.textpattern.com/viewtopic.php?id=34687\">forum thread</a>.', '//<?php\nfunction esq_glzcfvallist($atts, $thing=NULL) {\nextract(lAtts(array(\'fieldname\' => \'\', \'delimiter\' => \',\'), $atts));\n$output = \'\';\nforeach(safe_column(\'value\', \'custom_fields\', \'name = \"\'.safe_field(\'name\', \'txp_prefs\', \'val = \"\'.$fieldname.\'\"\').\'\" order by id asc\') as $val) {\n$output .= $val.$delimiter;\n}\nreturn substr($output, 0, strlen($output) - strlen($delimiter));\n}', '//<?php\nfunction esq_glzcfvallist($atts, $thing=NULL) {\nextract(lAtts(array(\'fieldname\' => \'\', \'delimiter\' => \',\'), $atts));\n$output = \'\';\nforeach(safe_column(\'value\', \'custom_fields\', \'name = \"\'.safe_field(\'name\', \'txp_prefs\', \'val = \"\'.$fieldname.\'\"\').\'\" order by id asc\') as $val) {\n$output .= $val.$delimiter;\n}\nreturn substr($output, 0, strlen($output) - strlen($delimiter));\n}', 'a310ad92cb05c2280608f31893891fa5', '', '', 0, 5, 0),
('esq_autoimageresize', 0, '', '', '1.03', 'Automatically resize uploaded images', 'For help or more information on <strong>esq_autoimageresize</strong>, please see the <a href=\"http://forum.textpattern.com/viewtopic.php?id=34756\">forum thread<a>.', '//<?php\nif(@txpinterface == \'admin\') {\nregister_callback(\'esq_autoimageresize\', \'image\');\nregister_callback(\'esq_autoimageresize_setup\', \'plugin_lifecycle.esq_autoimageresize\');\nregister_callback(\'esq_autoimageresize_prefs\', \'plugin_prefs.esq_autoimageresize\');\nadd_privs(\'plugin_prefs.esq_autoimageresize\', \'1,2\');\n}\nfunction esq_autoimageresize($event, $step) {\nclass esq_thumb extends txp_thumb {\nfunction write() {\nif (isset($this->m_ext) && wet_thumb::write(IMPATH.$this->m_id.$this->m_ext, IMPATH.$this->m_id.$this->m_ext)) {\nsafe_update(\'txp_image\', \'w = \'.$this->width.\', h = \'.$this->height, \'id = \'.$this->m_id);\nreturn true;\n} else {\nreturn false;\n}\n}\n}\n$esq_autoimageresize_prefs = get_pref(\'esq_autoimageresize_pref\', false);\nif ($esq_autoimageresize_prefs != false) {\n$esq_autoimageresize_prefs = unserialize($esq_autoimageresize_prefs);\n} else {\nreturn;\n}\n$esq_autoimageresize_js = esq_autoimageresize_js($step, $esq_autoimageresize_prefs);\nif ($esq_autoimageresize_js != null) {\necho \'<script language=\"javascript\" type=\"text/javascript\">\',\nn.\'$(document).ready(function() {\',\nn.join(n, $esq_autoimageresize_js),\nn.\'})\',\nn.\'</script>\';\n}\nesq_autoimageresize_resize($step, $esq_autoimageresize_prefs);\n}\nfunction esq_autoimageresize_js($step, $esq_autoimageresize_prefs) {\nswitch ($step) {\ncase \'image_insert\':\ncase \'image_replace\':\n$out[] = \'$(\\\'img#image-fullsize\\\').removeAttr(\\\'width\\\').removeAttr(\\\'height\\\').removeAttr(\\\'title\\\');\';\ncase \'image_edit\':\nif ($esq_autoimageresize_prefs[\'hide_thumb_controls\'] == 1) {\n$out[] = \'$(\\\'input[value=thumbnail_create],input[value=thumbnail_insert]\\\').parents(\\\'tr\\\').hide();\';\n}\n$out[] = \'$(\\\'select#image-category\\\').parents(\\\'form\\\').append(\\\'<input type=\"hidden\" name=\"previousCategory\" value =\"\\\' + $(\\\'select#image-category option[selected=selected]\\\').attr(\\\'value\\\') + \\\'\" />\\\');\';\nbreak;\n}\nreturn isset($out) ? $out : null;\n}\nfunction esq_autoimageresize_resize($step, $esq_autoimageresize_prefs) {\n$width = \'\';\n$height = \'\';\nswitch ($step) {\ncase \'image_insert\':\ncase \'image_replace\':\n$now = safe_field(\'now()\', \'txp_image\', \'1 = 1\');\n$image_data = safe_row(\'*\', \'txp_image\', \'date = \\\'\'.$now.\'\\\' limit 1\');\nextract($image_data);\nif (count($image_data) == 0) {\nbreak;\n}\nif (\n($esq_autoimageresize_prefs[\'per_cat_resize\'] == 1)\n&& ($category != \'\')\n&& isset($esq_autoimageresize_prefs[$category.\'_width\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_height\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_crop\'])\n) {\n$width = is_numeric($esq_autoimageresize_prefs[$category.\'_width\']) ? $esq_autoimageresize_prefs[$category.\'_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[$category.\'_height\']) ? $esq_autoimageresize_prefs[$category.\'_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[$category.\'_crop\'] == 1 ? true : false;\n} elseif (\n$esq_autoimageresize_prefs[\'per_cat_resize\'] == 0\n) {\n$width = is_numeric($esq_autoimageresize_prefs[\'default_width\']) ? $esq_autoimageresize_prefs[\'default_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[\'default_height\']) ? $esq_autoimageresize_prefs[\'default_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[\'default_crop\'] == 1 ? true : false;\n}\nbreak;\ncase \'image_save\':\n$image_data = safe_row(\'*\', \'txp_image\', \'id = \\\'\'.doSlash(gps(\'id\')).\'\\\' limit 1\');\nextract($image_data);\nif (\n($esq_autoimageresize_prefs[\'per_cat_resize\'] == 1)\n&& ($category != \'\')\n&& (gps(\'previousCategory\') != $category)\n&& isset($esq_autoimageresize_prefs[$category.\'_width\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_height\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_crop\'])\n) {\n$width = is_numeric($esq_autoimageresize_prefs[$category.\'_width\']) ? $esq_autoimageresize_prefs[$category.\'_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[$category.\'_height\']) ? $esq_autoimageresize_prefs[$category.\'_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[$category.\'_crop\'] == 1 ? true : false;\n}\nbreak;\n}\nif (!(($width == \'\') && ($height == \'\')) && isset($id)) {\n$t = new esq_thumb($id);\n$t->crop = $crop;\n$t->hint = \'0\';\n$t->width = $width;\n$t->height = $height;\n$t->write();\n}\n}\nfunction esq_autoimageresize_prefs($event = \'\', $step = \'\') {\n$message = array(0 => array(), 1 => \'\');\n$imageCats = safe_rows(\'name, title\', \'txp_category\', \'type = \\\'image\\\' and parent != \\\'\\\'\');\n$catNames = array();\nforeach($imageCats as $cat) {\n$catNames[] = $cat[\'name\'];\n}\narray_unshift($catNames, \'default\');\nforeach ($catNames as $catName) {\nforeach (array(\'width\', \'height\', \'crop\') as $suffix) {\n$esq_autoimageresize_prefs_gps[] = $catName.\'_\'.$suffix;\n}\n}\narray_unshift($esq_autoimageresize_prefs_gps, \'per_cat_resize\', \'hide_thumb_controls\');\nif ($step == \'esq_autoimageresize_prefupdate\') {\n$esq_autoimageresize_prefs = gpsa($esq_autoimageresize_prefs_gps);\nif (set_pref(\'esq_autoimageresize_pref\', serialize($esq_autoimageresize_prefs), \'esq_air\', PREF_HIDDEN, \'serialized_array\') == false) {\n$message[0][] = \'Failed to update settings.\';\n$message[1] = E_ERROR;\n} else {\n$message[0][] = \'Settings updated OK.\';\n}\n} else {\nforeach (array_flip($esq_autoimageresize_prefs_gps) as $pref => $val) {\n$esq_autoimageresize_prefs_new[$pref] = \'\';\n}\n$esq_autoimageresize_prefs = get_pref(\'esq_autoimageresize_pref\', false);\nif ($esq_autoimageresize_prefs == false) {\n$message[0][] = \'Failed to retrieve settings.\';\n$message[1] = E_ERROR;\n$esq_autoimageresize_prefs = array_merge($esq_autoimageresize_prefs_new, unserialize(esq_autoimageresize_pref_defaults()));\n} else {\n$esq_autoimageresize_prefs = array_merge($esq_autoimageresize_prefs_new, unserialize($esq_autoimageresize_prefs));\n}\n}\nglobal $path_to_site, $img_dir;\ninclude(txpath.\'/include/txp_image.php\');\nif (function_exists(\'gd_info\')) {\nforeach (array(\'.gif\' => \'GIF\', \'.png\' => \'PNG\', \'.jpg\' => \'JPG\') as $ext => $type) {\nif (!check_gd($ext)) {\n$gd_fail[] = $type;\n}\n}\nif (isset($gd_fail)) {\n$message[0][] = \'GD not enabled for \'.join(\', \', $gd_fail).\'.\';\n$message[1] = E_ERROR;\n}\n} else {\n$message[0][] = \'GD not installed.\';\n$message[1] = E_ERROR;\n}\n$message[0] = join(\' \', $message[0]);\nesq_autoimageresize_prefs_ouput($imageCats, $esq_autoimageresize_prefs, $message);\n}\nfunction esq_autoimageresize_prefs_ouput($imageCats, $esq_autoimageresize_prefs, $message) {\npagetop(\'esq_autoimageresize Options\', $message);\nforeach(array_merge(array(\'default\' => array(\'title\' => \'\', \'name\' => \'default\')), $imageCats) as $catType => $cat) {\n$class = $catType === \'default\' ? \'defaultOptions\' : \'categoryOptions\';\n$esq_autoimageresize_prefs_form[] = tr(\ntda(strong($cat[\'title\'].\' Resize Options\'), \' colspan=\"2\"\')\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Width:\')\n.tda(fInput(\'text\', $cat[\'name\'].\'_width\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_width\']))\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Height:\')\n.tda(fInput(\'text\', $cat[\'name\'].\'_height\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_height\']))\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Crop?\')\n.tda(yesnoRadio($cat[\'name\'].\'_crop\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_crop\']))\n, \' class=\"\'.$class.\'\"\');\n}\n$esq_autoimageresize_prefs_form = isset($esq_autoimageresize_prefs_form) ? join(n, $esq_autoimageresize_prefs_form) : \'\';\necho form(\nstartTable(\'list\')\n.tr(\ntda(strong(\'esq_autoimageresize Options\'), \' colspan=\"2\"\')\n)\n.tr(\ntda(\'Use per-category resize settings?\')\n.tda(yesnoRadio(\'per_cat_resize\', $esq_autoimageresize_prefs[\'per_cat_resize\']))\n)\n.tr(\ntda(\'Hide thumbnail create and upload controls?\')\n.tda(yesnoRadio(\'hide_thumb_controls\', $esq_autoimageresize_prefs[\'hide_thumb_controls\']))\n)\n.$esq_autoimageresize_prefs_form\n.tr(\ntda(fInput(\'submit\', \'submit\', \'Update\', \'publish\'), \' colspan=\"2\" style=\"text-align: right;\"\')\n)\n.endTable()\n.eInput(\'plugin_prefs.esq_autoimageresize\')\n.sInput(\'esq_autoimageresize_prefupdate\')\n);\necho <<<EOF\n<script language=\"javascript\" type=\"text/javascript\">\n$(document).ready(function() {\n$(\'input[name=per_cat_resize]\').click(function() {\nper_cat_resize_toggle($(this).attr(\'value\'));\n});\nper_cat_resize_toggle($(\'input[name=per_cat_resize]:checked\').attr(\'value\'));\nif (!$(\'tr.categoryOptions\').length) {\n$(\'input#per_cat_resize-1\').removeAttr(\'checked\').attr(\'disabled\', \'disabled\');\n$(\'input#per_cat_resize-0\').attr(\'checked\', \'checked\');\nper_cat_resize_toggle(0);\n}\n})\nfunction per_cat_resize_toggle(val) {\n$(\'tr.defaultOptions, tr.categoryOptions\').hide();\nif (val == \'0\') {\n$(\'tr.defaultOptions\').show();\n} else if (val == \'1\') {\n$(\'tr.categoryOptions\').show();\n}\n}\n</script>\nEOF;\n}\nfunction esq_autoimageresize_pref_defaults() {\nreturn \'a:5:{s:14:\"per_cat_resize\";s:1:\"0\";s:19:\"hide_thumb_controls\";s:1:\"0\";s:13:\"default_width\";s:0:\"\";s:14:\"default_height\";s:0:\"\";s:12:\"default_crop\";s:1:\"0\";}\';\n}\nfunction esq_autoimageresize_setup($event, $step) {\nswitch ($step) {\ncase \'installed\':\nif (set_pref(\'esq_autoimageresize_pref\', esq_autoimageresize_pref_defaults(), \'esq_air\', PREF_HIDDEN, \'serialized_array\') == false) {\nreturn array(\'<strong>esq_autoimageresize</strong> pref setup failed.\', E_ERROR);\n}\nbreak;\ncase \'deleted\':\nif (safe_delete(\'txp_prefs\', \'name = \\\'esq_autoimageresize_pref\\\'\') == false) {\nreturn array(\'<strong>esq_autoimageresize</strong> pref delete failed.\', E_ERROR);\n}\nbreak;\n}\n}', '//<?php\nif(@txpinterface == \'admin\') {\nregister_callback(\'esq_autoimageresize\', \'image\');\nregister_callback(\'esq_autoimageresize_setup\', \'plugin_lifecycle.esq_autoimageresize\');\nregister_callback(\'esq_autoimageresize_prefs\', \'plugin_prefs.esq_autoimageresize\');\nadd_privs(\'plugin_prefs.esq_autoimageresize\', \'1,2\');\n}\nfunction esq_autoimageresize($event, $step) {\nclass esq_thumb extends txp_thumb {\nfunction write() {\nif (isset($this->m_ext) && wet_thumb::write(IMPATH.$this->m_id.$this->m_ext, IMPATH.$this->m_id.$this->m_ext)) {\nsafe_update(\'txp_image\', \'w = \'.$this->width.\', h = \'.$this->height, \'id = \'.$this->m_id);\nreturn true;\n} else {\nreturn false;\n}\n}\n}\n$esq_autoimageresize_prefs = get_pref(\'esq_autoimageresize_pref\', false);\nif ($esq_autoimageresize_prefs != false) {\n$esq_autoimageresize_prefs = unserialize($esq_autoimageresize_prefs);\n} else {\nreturn;\n}\n$esq_autoimageresize_js = esq_autoimageresize_js($step, $esq_autoimageresize_prefs);\nif ($esq_autoimageresize_js != null) {\necho \'<script language=\"javascript\" type=\"text/javascript\">\',\nn.\'$(document).ready(function() {\',\nn.join(n, $esq_autoimageresize_js),\nn.\'})\',\nn.\'</script>\';\n}\nesq_autoimageresize_resize($step, $esq_autoimageresize_prefs);\n}\nfunction esq_autoimageresize_js($step, $esq_autoimageresize_prefs) {\nswitch ($step) {\ncase \'image_insert\':\ncase \'image_replace\':\n$out[] = \'$(\\\'img#image-fullsize\\\').removeAttr(\\\'width\\\').removeAttr(\\\'height\\\').removeAttr(\\\'title\\\');\';\ncase \'image_edit\':\nif ($esq_autoimageresize_prefs[\'hide_thumb_controls\'] == 1) {\n$out[] = \'$(\\\'input[value=thumbnail_create],input[value=thumbnail_insert]\\\').parents(\\\'tr\\\').hide();\';\n}\n$out[] = \'$(\\\'select#image-category\\\').parents(\\\'form\\\').append(\\\'<input type=\"hidden\" name=\"previousCategory\" value =\"\\\' + $(\\\'select#image-category option[selected=selected]\\\').attr(\\\'value\\\') + \\\'\" />\\\');\';\nbreak;\n}\nreturn isset($out) ? $out : null;\n}\nfunction esq_autoimageresize_resize($step, $esq_autoimageresize_prefs) {\n$width = \'\';\n$height = \'\';\nswitch ($step) {\ncase \'image_insert\':\ncase \'image_replace\':\n$now = safe_field(\'now()\', \'txp_image\', \'1 = 1\');\n$image_data = safe_row(\'*\', \'txp_image\', \'date = \\\'\'.$now.\'\\\' limit 1\');\nextract($image_data);\nif (count($image_data) == 0) {\nbreak;\n}\nif (\n($esq_autoimageresize_prefs[\'per_cat_resize\'] == 1)\n&& ($category != \'\')\n&& isset($esq_autoimageresize_prefs[$category.\'_width\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_height\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_crop\'])\n) {\n$width = is_numeric($esq_autoimageresize_prefs[$category.\'_width\']) ? $esq_autoimageresize_prefs[$category.\'_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[$category.\'_height\']) ? $esq_autoimageresize_prefs[$category.\'_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[$category.\'_crop\'] == 1 ? true : false;\n} elseif (\n$esq_autoimageresize_prefs[\'per_cat_resize\'] == 0\n) {\n$width = is_numeric($esq_autoimageresize_prefs[\'default_width\']) ? $esq_autoimageresize_prefs[\'default_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[\'default_height\']) ? $esq_autoimageresize_prefs[\'default_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[\'default_crop\'] == 1 ? true : false;\n}\nbreak;\ncase \'image_save\':\n$image_data = safe_row(\'*\', \'txp_image\', \'id = \\\'\'.doSlash(gps(\'id\')).\'\\\' limit 1\');\nextract($image_data);\nif (\n($esq_autoimageresize_prefs[\'per_cat_resize\'] == 1)\n&& ($category != \'\')\n&& (gps(\'previousCategory\') != $category)\n&& isset($esq_autoimageresize_prefs[$category.\'_width\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_height\'])\n&& isset($esq_autoimageresize_prefs[$category.\'_crop\'])\n) {\n$width = is_numeric($esq_autoimageresize_prefs[$category.\'_width\']) ? $esq_autoimageresize_prefs[$category.\'_width\'] : \'\';\n$height = is_numeric($esq_autoimageresize_prefs[$category.\'_height\']) ? $esq_autoimageresize_prefs[$category.\'_height\'] : \'\';\n$crop = $esq_autoimageresize_prefs[$category.\'_crop\'] == 1 ? true : false;\n}\nbreak;\n}\nif (!(($width == \'\') && ($height == \'\')) && isset($id)) {\n$t = new esq_thumb($id);\n$t->crop = $crop;\n$t->hint = \'0\';\n$t->width = $width;\n$t->height = $height;\n$t->write();\n}\n}\nfunction esq_autoimageresize_prefs($event = \'\', $step = \'\') {\n$message = array(0 => array(), 1 => \'\');\n$imageCats = safe_rows(\'name, title\', \'txp_category\', \'type = \\\'image\\\' and parent != \\\'\\\'\');\n$catNames = array();\nforeach($imageCats as $cat) {\n$catNames[] = $cat[\'name\'];\n}\narray_unshift($catNames, \'default\');\nforeach ($catNames as $catName) {\nforeach (array(\'width\', \'height\', \'crop\') as $suffix) {\n$esq_autoimageresize_prefs_gps[] = $catName.\'_\'.$suffix;\n}\n}\narray_unshift($esq_autoimageresize_prefs_gps, \'per_cat_resize\', \'hide_thumb_controls\');\nif ($step == \'esq_autoimageresize_prefupdate\') {\n$esq_autoimageresize_prefs = gpsa($esq_autoimageresize_prefs_gps);\nif (set_pref(\'esq_autoimageresize_pref\', serialize($esq_autoimageresize_prefs), \'esq_air\', PREF_HIDDEN, \'serialized_array\') == false) {\n$message[0][] = \'Failed to update settings.\';\n$message[1] = E_ERROR;\n} else {\n$message[0][] = \'Settings updated OK.\';\n}\n} else {\nforeach (array_flip($esq_autoimageresize_prefs_gps) as $pref => $val) {\n$esq_autoimageresize_prefs_new[$pref] = \'\';\n}\n$esq_autoimageresize_prefs = get_pref(\'esq_autoimageresize_pref\', false);\nif ($esq_autoimageresize_prefs == false) {\n$message[0][] = \'Failed to retrieve settings.\';\n$message[1] = E_ERROR;\n$esq_autoimageresize_prefs = array_merge($esq_autoimageresize_prefs_new, unserialize(esq_autoimageresize_pref_defaults()));\n} else {\n$esq_autoimageresize_prefs = array_merge($esq_autoimageresize_prefs_new, unserialize($esq_autoimageresize_prefs));\n}\n}\nglobal $path_to_site, $img_dir;\ninclude(txpath.\'/include/txp_image.php\');\nif (function_exists(\'gd_info\')) {\nforeach (array(\'.gif\' => \'GIF\', \'.png\' => \'PNG\', \'.jpg\' => \'JPG\') as $ext => $type) {\nif (!check_gd($ext)) {\n$gd_fail[] = $type;\n}\n}\nif (isset($gd_fail)) {\n$message[0][] = \'GD not enabled for \'.join(\', \', $gd_fail).\'.\';\n$message[1] = E_ERROR;\n}\n} else {\n$message[0][] = \'GD not installed.\';\n$message[1] = E_ERROR;\n}\n$message[0] = join(\' \', $message[0]);\nesq_autoimageresize_prefs_ouput($imageCats, $esq_autoimageresize_prefs, $message);\n}\nfunction esq_autoimageresize_prefs_ouput($imageCats, $esq_autoimageresize_prefs, $message) {\npagetop(\'esq_autoimageresize Options\', $message);\nforeach(array_merge(array(\'default\' => array(\'title\' => \'\', \'name\' => \'default\')), $imageCats) as $catType => $cat) {\n$class = $catType === \'default\' ? \'defaultOptions\' : \'categoryOptions\';\n$esq_autoimageresize_prefs_form[] = tr(\ntda(strong($cat[\'title\'].\' Resize Options\'), \' colspan=\"2\"\')\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Width:\')\n.tda(fInput(\'text\', $cat[\'name\'].\'_width\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_width\']))\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Height:\')\n.tda(fInput(\'text\', $cat[\'name\'].\'_height\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_height\']))\n, \' class=\"\'.$class.\'\"\')\n.tr(\ntda(\'Crop?\')\n.tda(yesnoRadio($cat[\'name\'].\'_crop\', $esq_autoimageresize_prefs[$cat[\'name\'].\'_crop\']))\n, \' class=\"\'.$class.\'\"\');\n}\n$esq_autoimageresize_prefs_form = isset($esq_autoimageresize_prefs_form) ? join(n, $esq_autoimageresize_prefs_form) : \'\';\necho form(\nstartTable(\'list\')\n.tr(\ntda(strong(\'esq_autoimageresize Options\'), \' colspan=\"2\"\')\n)\n.tr(\ntda(\'Use per-category resize settings?\')\n.tda(yesnoRadio(\'per_cat_resize\', $esq_autoimageresize_prefs[\'per_cat_resize\']))\n)\n.tr(\ntda(\'Hide thumbnail create and upload controls?\')\n.tda(yesnoRadio(\'hide_thumb_controls\', $esq_autoimageresize_prefs[\'hide_thumb_controls\']))\n)\n.$esq_autoimageresize_prefs_form\n.tr(\ntda(fInput(\'submit\', \'submit\', \'Update\', \'publish\'), \' colspan=\"2\" style=\"text-align: right;\"\')\n)\n.endTable()\n.eInput(\'plugin_prefs.esq_autoimageresize\')\n.sInput(\'esq_autoimageresize_prefupdate\')\n);\necho <<<EOF\n<script language=\"javascript\" type=\"text/javascript\">\n$(document).ready(function() {\n$(\'input[name=per_cat_resize]\').click(function() {\nper_cat_resize_toggle($(this).attr(\'value\'));\n});\nper_cat_resize_toggle($(\'input[name=per_cat_resize]:checked\').attr(\'value\'));\nif (!$(\'tr.categoryOptions\').length) {\n$(\'input#per_cat_resize-1\').removeAttr(\'checked\').attr(\'disabled\', \'disabled\');\n$(\'input#per_cat_resize-0\').attr(\'checked\', \'checked\');\nper_cat_resize_toggle(0);\n}\n})\nfunction per_cat_resize_toggle(val) {\n$(\'tr.defaultOptions, tr.categoryOptions\').hide();\nif (val == \'0\') {\n$(\'tr.defaultOptions\').show();\n} else if (val == \'1\') {\n$(\'tr.categoryOptions\').show();\n}\n}\n</script>\nEOF;\n}\nfunction esq_autoimageresize_pref_defaults() {\nreturn \'a:5:{s:14:\"per_cat_resize\";s:1:\"0\";s:19:\"hide_thumb_controls\";s:1:\"0\";s:13:\"default_width\";s:0:\"\";s:14:\"default_height\";s:0:\"\";s:12:\"default_crop\";s:1:\"0\";}\';\n}\nfunction esq_autoimageresize_setup($event, $step) {\nswitch ($step) {\ncase \'installed\':\nif (set_pref(\'esq_autoimageresize_pref\', esq_autoimageresize_pref_defaults(), \'esq_air\', PREF_HIDDEN, \'serialized_array\') == false) {\nreturn array(\'<strong>esq_autoimageresize</strong> pref setup failed.\', E_ERROR);\n}\nbreak;\ncase \'deleted\':\nif (safe_delete(\'txp_prefs\', \'name = \\\'esq_autoimageresize_pref\\\'\') == false) {\nreturn array(\'<strong>esq_autoimageresize</strong> pref delete failed.\', E_ERROR);\n}\nbreak;\n}\n}', '976144e07d4e2275ab1088645f4dd106', '', '', 3, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('glz_custom_fields', 1, 'Gerhard Lazu', 'http://github.com/jools-r/glz_custom_fields', '2.0 beta', 'Unlimited special custom fields', '<h1>Unlimited Custom Fields for Textpattern v4.7+</h1>\n\n<p>This plugin removes the limit of 10 custom fields in Textpattern and also allows you to create a range of different custom field types:</p>\n\n<ul>\n	<li>Text inputs (default)</li>\n	<li>Checkboxes</li>\n	<li>Radio buttons</li>\n	<li>Select dropdowns</li>\n	<li>Multiselect boxes</li>\n	<li>Textareas</li>\n</ul>\n\n<p>You can specify a set of predefined choices for selects, multi-selects, checkboxes and radio button custom fields, one of which may also be set as the <code>{default value}</code> by placing it in curly brackets.</p>\n\n<p>Additionally, there are three special custom field types: date pickers, time pickers and custom fields powered by custom scripts of your own making.</p>\n\n<p>You can provide field label titles for the <a href=\"https://docs.textpattern.io/administration/write-panel\" rel=\"nofollow\">Write panel</a> along with short instruction texts as input hints for admin users (also per UI languages), and define the order in which the custom fields appear on the Write panel.</p>\n\n<h2>Installation</h2>\n\n<ol>\n	<li>Open the plugin text file, copy its entire contents to the clipboard and paste into the ‘Install plugin’ textarea of the <a href=\"https://docs.textpattern.io/administration/plugins-panel#uploading-plugins\" rel=\"nofollow\">Plugins panel</a>. Click ‘Upload’ and then ‘Install’.</li>\n	<li>Upload the <a href=\"https://github.com/jools-r/glz_custom_fields/tree/master/dist/\" rel=\"nofollow\">plugins/glz_custom_fields</a> folder to your Textpattern installation directory.</li>\n	<li>Activate the plugin.</li>\n	<li>Switch to ‘Admin › Preferences › Custom fields preferences’ and click ‘Check paths’ at the top right to verify that the corresponding paths in the plugin preferences are correct. The plugin will indicate if there are potential problems. Correct as necessary to match your server setup.</li>\n</ol>\n\n<h3>Upgrading</h3>\n\n<p>Follow the same process as installation. The installer should detect and convert your existing preferences to new names in the background. Replace all the files in the <a href=\"https://github.com/jools-r/glz_custom_fields/tree/master/dist/\" rel=\"nofollow\">plugins/glz_custom_fields</a> folder as all files have changed to work with the most recent admin layout. The timepicker and datepicker have likewise been updated and minified.</p>\n\n<p><em>Note: You may relocate the css and js files to locations of your own choosing. Change the paths in the plugin preferences panel accordingly. Only the contents of the jquery.datePicker or jquery.timePicker folders must stay together. You may rename the folders but not the filenames.</em></p>\n\n<h3>Deinstallation</h3>\n\n<p>Deinstalling the plugin does not remove the custom fields settings or the data input into the articles. This is intentional to avoid inadvertent data loss. The fields cannot be edited in the meantime, but reinstalling the plugin restores access to them. To remove data input in the custom fields, delete / reset the custom fields on the “Extensions › Custom Fields” page first before deinstalling the plugin.</p>\n\n<h2>Special custom fields</h2>\n\n<p>There are three special custom field types:</p>\n\n<h3>Time and date pickers</h3>\n\n<p>Time and date pickers offer an easy way for users to specify dates and times in a consistent format. The <strong>date format</strong>, <strong>first day of the week</strong> and <strong>start date</strong> for the calendar can be set in the plugin preferences.</p>\n\n<p>Similarly, the <strong>time format</strong> (24 or 12 hours), <strong>start and end times</strong> (e.g. <code>08:00</code> and <code>19:00</code>) and <strong>time step</strong> (intervals, e.g. <code>30</code> minutes) can be set in the plugin preferences.</p>\n\n<p><em><span class=\"caps\">IMPORTANT</span>: For the datepicker and timepicker to function correctly, you must copy the <a href=\"https://github.com/jools-r/glz_custom_fields/tree/master/dist\" rel=\"nofollow\">plugins/glz_custom_fields</a> folder to your Textpattern installation directory and set the corresponding paths in the plugin preferences.</em></p>\n\n<h3>Custom scripts</h3>\n\n<p>Custom scripts make it possible to build-in custom functionality, for example to pull in information from elsewhere in your site, by writing a corresponding php script to create the custom input field. Please refer to <a href=\"https://github.com/jools-r/glz_custom_fields/blob/master/dist/plugins/glz_custom_fields/my_images.php\" rel=\"nofollow\">my_image.php</a> file as an example with inline explanations or ask on the forum for tips and examples.</p>\n\n\n<h2>Custom field names</h2>\n\n<p><strong>Custom field names</strong> should adhere to the following naming rules:</p>\n\n<ul>\n	<li>only lowercase letters and numbers</li>\n	<li>underscores as separators (no spaces or dashes)</li>\n	<li>should not start with a number</li>\n</ul>\n\n<p>This version of glz_custom_fields will convert the names of <em>newly created</em> custom fields to this pattern and notify you when saving. In the interests of backwards compatibility, it will <em>not</em> change existing custom fields but will issue a warning.</p>\n\n<p>Previous versions of glz_custom_fields permitted custom names with spaces, dashes, capital letters and numbers. These can, however, cause problems when using custom field names as an attribute for <a href=\"https://docs.textpattern.io/tags/article_custom#attributes\" rel=\"nofollow\">txp:article_custom</a>.</p>\n\n<p>Future versions of Textpattern will be stricter about custom field naming and it is advisable to adapt your custom field names and code accordingly.</p>\n\n<p><em><strong>Important:</strong> You can rename custom field names with this plugin but remember you will also need to alter all instances of <a href=\"https://docs.textpattern.io/tags/custom_field\" rel=\"nofollow\">txp:custom_field</a> and <a href=\"https://docs.textpattern.io/tags/if_custom_field\" rel=\"nofollow\">txp:if_custom_field</a> as well as all customfieldname attributes in <a href=\"https://docs.textpattern.io/tags/article_custom\" rel=\"nofollow\">txp:article_custom</a> in your page templates and forms to match.</em></p>\n\n<h3>Custom field titles</h3>\n\n<p><strong>Custom field titles</strong> have no strict naming restrictions and can also be specified per language. Switch the “User language” in “Admin › Languages” and enter a new title.</p>\n\n<p>If no custom field title is specified, the custom_field_name will be displayed as the field label as in previous versions.</p>\n\n<p>The custom field title can be accessed from <code>page templates</code> and <code>forms</code> for displaying to the front-end if required as follows:</p>\n\n<pre><code>&lt;txp:custom_field name=&quot;my_custom_field&quot; title=&quot;1&quot; /&gt;</code></pre>\n\n<p>This outputs the custom field&#8217;s title string in the current front-side language. If no title has been defined for the custom field, the tag outputs nothing.</p>\n\n<p><em>Note: The <code>title=&quot;1&quot;</code> attribute does not work when this plugin is not installed.</em></p>\n\n<h2>Version compatibility</h2>\n\n<p>This version is compatible with Version 4.7+ of Textpattern. It uses functionality not available in earlier versions of Textpattern and adds new functionality not previously available in earlier versions of glz_custom_fields. It is therefore not for use with earlier versions of Textpattern.</p>\n\n<p>On installation, the plugin upgrades your settings and preferences to avoid potential naming collisions. Downgrading to an earlier version of Textpattern is therefore inadvisable and requires manually renaming preference names to their old naming scheme in the database.</p>\n\n<h3>Changelog</h3>\n\n<p>v2.0 – March 2018</p>\n\n<ul>\n	<li>Refactored for Textpattern v4.7</li>\n	<li>Plugin preferences now handled in Admin › Preferences</li>\n	<li>Plugin preferences not overwritten during installation</li>\n	<li>Support for custom field title labels and supporting instruction hints (also per UI-language)</li>\n	<li>Custom field titles are accessible in page templates and forms by adding the attribute <code>title=&quot;1&quot;</code> to <a href=\"https://docs.textpattern.io/tags/custom_field\" rel=\"nofollow\">txp:custom_field</a>.</li>\n	<li>Change order of custom fields per drag and drop (deactivatable via a hidden pref)</li>\n	<li>Translatable: UI now uses textpacks throughout, custom field labels also translatable by switching the UI language</li>\n	<li>Under the hood: makes use of Textpattern’s in-built functions for UI creation, error messages/notices, language strings, field labels</li>\n	<li>New prefs for js/css <span class=\"caps\">URL</span> location including support for relative <span class=\"caps\">URL</span>s: relocate your js/css files where you want them</li>\n	<li>Compatibility with multi-site Textpattern installations</li>\n	<li>Updated + minified js/css and datepicker/timepicker labels</li>\n	<li>Adding/deleting/changing a custom field updates site-wide last modified date (for cache renewal)</li>\n</ul>\n\n<h2>Help and Credits</h2>\n\n<h3>Troubleshooting</h3>\n\n<p>Things to check if the plugin does not seem to work as described:</p>\n\n<ul>\n	<li>Have you copied the <a href=\"https://github.com/jools-r/glz_custom_fields/tree/master/dist\" rel=\"nofollow\">plugins folder</a> to your textpattern installation? Without the corresponding css and js files, the plugin cannot work.</li>\n	<li>Check you have copied <em>all</em> the necessary files. If you have updated from an earlier version, make sure you have the most recent versions. The plugin repository has a <a href=\"https://github.com/jools-r/glz_custom_fields/tree/master/dist\" rel=\"nofollow\">plugins.glz_custom_fields.zip file</a> with all the necessary files included.</li>\n	<li>Switch to ‘Admin › Preferences › Custom fields preferences’ and click ‘Check paths’ at the top right to verify that the corresponding paths in the plugin preferences are correct. The plugin will indicate if there are potential problems. Correct as necessary to match your server setup.</li>\n	<li>If you are using your own custom locations, check the paths are set correctly. Note that the css and js files of the Datepicker should be kept in the same folder. The same applies for the TimePicker.</li>\n	<li>If you are on a windows server avoid a mix of backslashes and forward slashes in your paths. Use just forward slashes and php will understand and convert them automatically.</li>\n	<li>If you see a message in the Write pane indicating a problem with the DatePicker or TimePicker, click the error message to check the paths in the settings pane. See above.</li>\n	<li>If you see a message in the Write pane that a custom script could not be found or could not be read:\n	<ul>\n		<li>Check the custom scripts path is correct as described above. Use the ‘Check paths’ link at the top right to verify the path can be found.</li>\n		<li>Check you have a .php file named in the folder you specified above. Check that the file has read permissions (<a href=\"http://www.filepermissions.com/file-permission/0644\" rel=\"nofollow\">0644</a>). Write permissions are <em>not</em> necessary.</li>\n		<li>Check that the name of the .php file matches the name you have specified in the corresponding custom field.</li>\n	</ul></li>\n	</ul>\n\n<h3>Help</h3>\n\n<p>Help is always available from the friendly people at the <a href=\"https://forum.textpattern.io/viewtopic.php?id=48511\" rel=\"nofollow\">Textpattern forum</a>.</p>\n\n<p>Improvements and problem notifications are always welcome. Please <a href=\"https://github.com/jools-r/glz_custom_fields/issues\" rel=\"nofollow\">raise an issue</a> or <a href=\"https://github.com/jools-r/glz_custom_fields/pulls\" rel=\"nofollow\">submit a pull request</a> on GitHub.</p>\n\n<h3>Credits</h3>\n\n<p>This plugin was originally written by <a href=\"https://github.com/gerhard\" rel=\"nofollow\">Gerhard Lazu</a> and dates back to <a href=\"https://forum.textpattern.io/viewtopic.php?pid=157983#p157983\" rel=\"nofollow\">2007</a>. It has been repaired, expanded and adopted by numerous forum members over the years (credited in the code), most recently by <a href=\"https://github.com/Bloke/glz_custom_fields\" rel=\"nofollow\">Bloke</a>. This significantly refactored version adds compatibility with Textpattern v4.7 and is currently looked after by <a href=\"https://github.com/jools-r/glz_custom_fields\" rel=\"nofollow\">jools-r</a>. Its concept and core workings are still those of Gerhard&#8217;s original plugin. The Textpattern core will ultimately support unlimited custom fields, making this plugin obsolete at some point in the future.</p>\n\n<p>This plugin uses <strong>jQuery DatePicker</strong> (<a href=\"http://2008.kelvinluck.com/assets/jquery/datePicker/v2/demo/\" rel=\"nofollow\">homepage</a> / <a href=\"https://github.com/vitch/jQuery-datepicker\" rel=\"nofollow\">github</a>) by Kelvin Luck, <strong>jQuery Timepicker</strong> (<a href=\"https://github.com/perifer/timePicker\" rel=\"nofollow\">github</a>) by Anders Fajerson and <strong>jQuery sortElements</strong> (<a href=\"https://github.com/padolsey-archive/jquery.fn/tree/master/sortElements\" rel=\"nofollow\">github</a>) by James Padolsey.</p>', '\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   DATABASE FUNCTIONS – Standalone reusable DB functions\n#\n##################\n\n/**\n * Adds a new custom field.\n *\n * @param  array $in Incoming array of form values (see below)\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_save()\n *\n * Expects an incoming array as follows:\n * $in = array(\n *     \'custom_set\',\n *     \'custom_field_number\',\n *     \'custom_set_name\',\n *     \'custom_set_name_old\', **\n *     \'custom_set_title\', **\n *     \'custom_set_instructions\', **\n *     \'custom_set_type\',\n *     \'custom_set_position\',\n *     \'value\', **\n *     \'save\', **\n *     \'add_new\'\n * );\n * where ** are optional\n *\n * Do all content checks / validation / data completion / manipulation\n * before passing the array to this function\n */\n\nfunction glz_db_cf_new($in, $debug = false)\n{\n    // Abort if wrong input format\n    if (!is_array($in)) {\n        if ($debug) { dmp(\'$in: wrong input format\'); }\n        return false;\n    }\n\n    // Extract incoming values\n    extract($in);\n    // Get current UI language\n    $current_lang = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n\n    $ok = false;\n\n    // Insert: table \'txp_prefs\' – custom_field entry\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        $ok = safe_insert(\n            \'txp_prefs\',\n            \"name     = \'{$custom_set}\',\n             val      = \'{$custom_set_name}\',\n             type     = \'1\',\n             event    = \'custom\',\n             html     = \'{$custom_set_type}\',\n             position = \'{$custom_set_position}\'\",\n            $debug\n        );\n    } else {\n        // Custom field IDs 1-10 (update entry)\n        $ok = safe_update(\n            \'txp_prefs\',\n            \"val      = \'{$custom_set_name}\',\n             html     = \'{$custom_set_type}\',\n             position = \'{$custom_set_position}\'\",\n             \"name = \'{$custom_set}\'\",\n            $debug\n        );\n    }\n\n    // Insert: table \'textpattern\' – new \'custom_X\' column\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        // If cf type = textarea, column type + default must be different\n        $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n        $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n        $ok = safe_alter(\n            \'textpattern\',\n            \"ADD custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n            $debug\n        );\n    } else {\n        // Custom field IDs 1-10: only update column type and default as necessary\n        // Update: table \'textpattern\' – column type and default if type changes\n        // If cf type = textarea, column type + default must be different\n        $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n        $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n        $ok = safe_alter(\n            \'textpattern\',\n            \"MODIFY custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n            $debug\n        );\n    }\n\n    // Insert: table \'txp_lang\' – prefspane label\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        $custom_set_preflabel = gTxt(\'custom_x_set\', array(\'{number}\' => $custom_field_number));\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'custom_{$custom_field_number}_set\',\n             event   = \'prefs\',\n             owner   = \'\',\n             data    = \'{$custom_set_preflabel}\',\n             lastmod = now()\",\n            $debug\n        );\n    }\n\n    // Insert: table \'txp_lang\' – custom field label title\n    if (!empty($custom_set_title)) {\n        $custom_set_cf_name = glz_cf_langname($custom_set_name);\n        $custom_set_cf_data = doSlash($custom_set_title);\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'{$custom_set_cf_name}\',\n             event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_cf_data}\',\n             lastmod = now()\",\n            $debug\n        );\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    }\n\n    // Insert: table \'txp_lang\' – custom field instructions\n    if (!empty($custom_set_instructions)) {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = doSlash($custom_set_instructions);\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'{$custom_set_instr_name}\',\n             event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_instr_data}\',\n             lastmod = now()\",\n            $debug\n        );\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n    }\n\n    // Insert: table \'custom_fields\' – multiple custom field values\n    // 1. break textarea entries into array removing blanks and duplicates\n    $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n    // 2. fashion insert statement from array\n    if (is_array($cf_values) && !empty($cf_values)) {\n        $insert = \'\';\n        foreach ($cf_values as $key => $value) {\n            // Skip empty values\n            if (!empty($value)) {\n                // Escape special chars before inserting into database\n                $value = doSlash(trim($value));\n                // Build insert row\n                $insert .= \"(\'{$custom_set}\',\'{$value}\'), \";\n            }\n        }\n        // Trim final comma and space from insert statement\n        $insert = rtrim($insert, \', \');\n        // 3. do insert query\n        $ok = safe_query(\"\n            INSERT INTO\n                \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n            VALUES\n                {$insert}\n            \",\n            $debug\n        );\n    }\n\n    // As the table UI doesn\'t include the new strings until a page refresh,\n    // set the language strings in $set_gTxt (if they exist) in the current context\n    // (true = amend/append to existing textpack)\n    if (isset($set_gTxt)) {\n        if ($debug) { dmp(\'$set_gTxt: \'.$set_gTxt); }\n        Txp::get(\'\\Textpattern\\L10n\\Lang\')->setPack($set_gTxt, true);\n    }\n\n    return $ok;\n}\n\n\n/**\n * Saves / updates an existing custom field in the DB.\n *\n * @param  array $in  Incoming array of form values (see below)\n * @param  bool  $debug  Dump query\n * @return bool  FALSE on error\n * @see    glz_db_cf_new()\n *\n * Expects an incoming array as follows:\n * $in = array(\n *     \'custom_set\',\n *     \'custom_field_number\',\n *     \'custom_set_name\',\n *     \'custom_set_name_old\',\n *     \'custom_set_title\', **\n *     \'custom_set_instructions\', **\n *     \'custom_set_type\',\n *     \'custom_set_position\',\n *     \'value\', **\n *     \'save\',\n *     \'add_new\' **\n * );\n * where ** are optional\n *\n * Do all content checks / validation / data completion / manipulation\n * before passing the array to this function\n */\n\nfunction glz_db_cf_save($in, $debug = false)\n{\n    // Abort if wrong input format\n    if (!is_array($in)) {\n        if ($debug) { dmp(\'$in: wrong input format\'); }\n        return false;\n    }\n\n    // Extract incoming values\n    extract($in);\n    // Get current UI language\n    $current_lang = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n\n    // Has the custom field been renamed\n    $is_cf_renamed = ($custom_set_name <> $custom_set_name_old) ? true : false;\n\n    $ok = false;\n\n    // Update: table \'txp_prefs\' – custom_field entry\n    $ok = safe_update(\n        \'txp_prefs\',\n        \"val      = \'{$custom_set_name}\',\n         html     = \'{$custom_set_type}\',\n         position = \'{$custom_set_position}\'\",\n        \"name     = \'{$custom_set}\'\", // WHERE\n        $debug\n    );\n\n    // Update: table \'textpattern\' – column type and default if type changes\n    // If cf type = textarea, column type + default must be different\n    $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n    $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n    $ok = safe_alter(\n        \'textpattern\',\n        \"MODIFY custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n        $debug\n    );\n\n    // Update: table \'custom_fields\' – values entries (textarea requires none)\n    // For textareas we do not need to touch custom_fields table\n    if ($custom_set_type != \"textarea\") {\n        safe_delete(\n            \'custom_fields\',\n            \"name =\'{$custom_set}\'\",\n            $debug\n        );\n        // Insert: table \'custom_fields\' – multiple custom field values\n        // 1. break textarea entries into array removing blanks and duplicates\n        $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n        // 2. fashion insert statement from array\n        if (is_array($cf_values) && !empty($cf_values)) {\n            $insert = \'\';\n            foreach ($cf_values as $key => $value) {\n                // Skip empty values\n                if (!empty($value)) {\n                    // Escape special chars before inserting into database\n                    $value = doSlash(trim($value));\n                    // Build insert row\n                    $insert .= \"(\'{$custom_set}\',\'{$value}\'), \";\n                }\n            }\n            // Trim final comma and space from insert statement\n            $insert = rtrim($insert, \', \');\n            // 3. do insert query\n            $ok = safe_query(\"\n                INSERT INTO\n                    \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n                VALUES\n                    {$insert}\n                \",\n                $debug\n            );\n        }\n    } // endif ($custom_set_type != \"textarea\")\n\n    // Update: table \'txp_lang\' – custom field label title\n    $has_cf_title = (!empty($custom_set_title)) ? true : false;\n    $custom_set_cf_langname = glz_cf_langname($custom_set_name);\n\n    if ($is_cf_renamed) {\n        $custom_set_cf_langname_old = glz_cf_langname($custom_set_name_old);\n        // OK, cf is renamed. Do cfnames still match perchance?\n        // such as when renaming spaces/dashes to underscores or uppercase to lowercase\n        if ($custom_set_cf_langname <> $custom_set_cf_langname_old) {\n            // Update name of all custom fields (if cf_langname has actually changed)\n            $custom_set_cf_name_old = $custom_set_cf_langname_old;\n            $custom_set_cf_data_old = null;\n            $ok = safe_update(\n                \'txp_lang\',\n                \"name = \'{$custom_set_cf_langname}\'\",\n                \"name = \'{$custom_set_cf_langname_old}\'\",\n                $debug\n            );\n            $set_gTxt[$custom_set_cf_name_old] = $custom_set_cf_data_old;\n        }\n    }\n\n    if ($has_cf_title) {\n        // A) Custom field title is specified: update or insert\n        $custom_set_cf_name = $custom_set_cf_langname;\n        $custom_set_cf_data = doSlash($custom_set_title);\n        $ok = safe_upsert(\n            \'txp_lang\',\n            \"event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_cf_data}\',\n             lastmod = now()\",\n             array(\'name\' => $custom_set_cf_name, \'lang\' => $current_lang),\n            $debug\n        );\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    } else {\n        // B) Custom field title not specified (or blanked)\n        $custom_set_cf_name = $custom_set_cf_langname;\n        $custom_set_cf_data = null;\n        // Only delete it if it actually exists\n        $gtxt_data = glz_cf_gtxt($custom_set_name);\n        if (!empty($gtxt_data)) {\n            $ok = safe_delete(\n                \'txp_lang\',\n                \"name = \'{$custom_set_cf_name}\' AND lang = \'{$current_lang}\'\",\n                $debug\n            );\n        }\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    }\n\n    // Update: table \'txp_lang\' – custom field instructions\n\n    // A) If instructions string exists, update existing or insert new entry, or…\n    if (!empty($custom_set_instructions)) {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = doSlash($custom_set_instructions);\n        $ok = safe_upsert(\n            \'txp_lang\',\n            \"event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_instr_data}\',\n             lastmod = now()\",\n             array(\'name\' => $custom_set_instr_name, \'lang\' => $current_lang),\n            $debug\n        );\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n\n    // B) If instructions string is empty but previously existed, remove old entry\n    } elseif (glz_cf_gtxt(\'\', $custom_field_number) != \'\') {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = null;\n        // Only delete it if it actually exists\n        $gtxt_data = glz_cf_gtxt(\'\',$custom_field_number);\n        if (!empty($gtxt_data)) {\n            $ok = safe_delete(\n                \'txp_lang\',\n                \"name = \'{$custom_set_instr_name}\' AND lang = \'{$current_lang}\'\",\n                $debug\n            );\n        }\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n    }\n\n    // As the table UI doesn\'t include the new strings until a page refresh,\n    // set the language strings in $set_gTxt (if they exist) in the current context\n    // (true = amend/append to existing textpack)\n    if (isset($set_gTxt)) {\n        if ($debug) { dmp(\'$set_gTxt: \'.$set_gTxt); }\n        Txp::get(\'\\Textpattern\\L10n\\Lang\')->setPack($set_gTxt, true);\n    }\n\n    return $ok;\n}\n\n\n/**\n * Resets a custom field.\n *\n * Passes through to glz_db_cf_delete()\n * with reset flag set to true\n *\n * @param  string $id Custom field ID#\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_delete()\n *\n * Do all checks on $id (if integer / if exists)\n * before passing $id to this function\n */\n\nfunction glz_db_cf_reset($id, $debug = false)\n{\n    return glz_db_cf_delete($id, $reset = true, $debug);\n}\n\n\n/**\n * Deletes or resets a custom field.\n *\n * ID#s 1-10 are always reset, not deleted\n *\n * @param  string $id Custom field ID#\n * @param  bool $reset Reset rather than delete\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_reset()\n *\n * Do all checks on $id (if integer / if exists)\n * before passing $id to this function\n */\n\nfunction glz_db_cf_delete($id, $reset = false, $debug = false)\n{\n    // Custom fields 1-10 are in-built -> only reset\n    if ($id <= 10) {\n        $reset = true;\n    }\n\n    // Retrieve this custom_field values\n    $custom_set = glz_db_get_custom_set($id);\n\n    $ok = false;\n\n    // --- COMMON DELETE AND RESET STEPS\n\n    // Delete: table \'txp_lang\' – custom field label title\n    if (!empty($custom_set[\'title\'])) {\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'\".glz_cf_langname($custom_set[\'name\']).\"\'\",\n            $debug\n        );\n    }\n\n    // Delete: table \'txp_lang\' – custom field instructions\n    if (!empty($custom_set[\'instructions\'])) {\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'instructions_custom_\".$custom_set[\'id\'].\"\'\",\n            $debug\n        );\n    }\n\n    // Delete: table \'custom_fields\' – custom field multiple values settings\n    $cf_row = safe_row(\'*\', \'custom_fields\', \"name = \'\".$custom_set[\'custom_set\'].\"\'\", $debug);\n    if ($cf_row) {\n        $ok = safe_delete(\n            \'custom_fields\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n    }\n\n    if ($reset) {  // --- RESET ONLY STEPS\n\n        // Reset: table \'txp_prefs\' – reset to standard custom field settings\n        $ok = safe_update(\n            \'txp_prefs\',\n            \"val = \'\',\n             html = \'text_input\'\",\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Reset: table \'textpattern\' – empty custom field article data\n        $ok = safe_update(\n            \'textpattern\',\n            \"custom_\".$custom_set[\'id\'].\" = \'\'\",\n            \"1 = 1\",\n            $debug\n        );\n\n        // Reset: table \'textpattern\' – reset custom_X column type to VARCHAR\n        $ok = safe_alter(\n            \'textpattern\',\n            \"MODIFY custom_\".$custom_set[\'id\'].\" VARCHAR(255) NOT NULL DEFAULT \'\'\",\n            $debug\n        );\n\n    } else {  // --- DELETE ONLY STEPS\n\n        // Delete: table \'txp_lang\' – prefspane label\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Delete: table \'txp_prefs\' – custom field entry\n        $ok = safe_delete(\n            \'txp_prefs\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Delete: table \'textpattern\' – custom field article data\n        $ok = safe_alter(\n            \'textpattern\',\n            \"DROP `custom_\".$custom_set[\'id\'].\"`\",\n            $debug\n        );\n    }\n\n    return $ok;\n}\n\n\nfunction glz_db_get_all_custom_sets()\n{\n    $all_custom_sets = safe_rows(\n        \"`name` AS custom_set,\n         `val` AS name,\n         `position`,\n         `html` AS type\",\n        \'txp_prefs\',\n        \"`event` = \'custom\' ORDER BY `position`\"\n    );\n\n    foreach ($all_custom_sets as $custom_set) {\n        $custom_set[\'id\'] = glz_custom_digit($custom_set[\'custom_set\']);\n        $custom_set[\'title\'] = glz_cf_gtxt($custom_set[\'name\']);\n        $custom_set[\'instructions\'] = glz_cf_gtxt(\'\', $custom_set[\'id\']);\n\n        $out[$custom_set[\'custom_set\']] = array(\n            \'id\'            => $custom_set[\'id\'],\n            \'name\'          => $custom_set[\'name\'],\n            \'title\'         => $custom_set[\'title\'],\n            \'instructions\'  => $custom_set[\'instructions\'],\n            \'position\'      => $custom_set[\'position\'],\n            \'type\'          => $custom_set[\'type\']\n        );\n\n    }\n    return $out;\n}\n\n\nfunction glz_db_get_custom_set($id)\n{\n    if (!intval($id)) {\n        return false;\n    }\n        $custom_set = safe_row(\n            \"name AS custom_set,\n             val AS name,\n             position,\n             html AS type\",\n            \'txp_prefs\',\n            \"name = \'custom_\".doSlash($id).\"_set\'\"\n        );\n\n        $custom_set[\'id\'] = glz_custom_digit($custom_set[\'custom_set\']);\n        $custom_set[\'title\'] = glz_cf_gtxt($custom_set[\'name\']);\n        $custom_set[\'instructions\'] = glz_cf_gtxt(\'\', $custom_set[\'id\']);\n\n        return $custom_set;\n}\n\n\nfunction glz_db_get_custom_field_values($name, $extra)\n{\n    global $prefs;\n\n    if (is_array($extra)) {\n        extract($extra);\n\n        if (!empty($name)) {\n\n            switch ($prefs[\'glz_cf_values_ordering\']) {\n                case \"ascending\":\n                    $orderby = \"value ASC\";\n                    break;\n                case \"descending\":\n                    $orderby = \"value DESC\";\n                    break;\n                default:\n                    $orderby = \"id ASC\";\n            }\n\n            $arr_values = getThings(\"\n                SELECT\n                    `value`\n                FROM\n                    \".safe_pfx(\'custom_fields\').\"\n                WHERE\n                    `name` = \'{$name}\'\n                ORDER BY\n                    {$orderby}\n            \");\n\n            if (count($arr_values) > 0) {\n                // Decode all special characters e.g. \", & etc. and use them for keys\n                foreach ($arr_values as $key => $value) {\n                    $arr_values_formatted[glz_clean_default(htmlspecialchars($value))] = stripslashes($value);\n                }\n                return $arr_values_formatted;\n            }\n        }\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\nfunction glz_db_get_all_existing_cf_values($name, $extra)\n{\n    if (is_array($extra)) {\n        extract(lAtts(array(\n            \'custom_set_name\'   => \"\",\n            \'status\'            => 4\n        ), $extra));\n\n        // On occasions (e.g. initial migration) we may need to check the custom field values for all articles\n        $status_condition = ($status == 0) ? \"<> \'\'\" : \"= \'$status\'\";\n\n        if (!empty($name)) {\n            $arr_values = getThings(\"\n                SELECT DISTINCT\n                    `$name`\n                FROM\n                    \".safe_pfx(\'textpattern\').\"\n                WHERE\n                    `Status` $status_condition\n                AND\n                    `$name` <> \'\'\n                ORDER BY\n                    `$name`\n            \");\n\n            // Trim all values\n            foreach ($arr_values as $key => $value) {\n                $arr_values[$key] = trim($value);\n            }\n\n            // DEBUG\n            // dmp($arr_values);\n\n            // Temporary string of array values for checking for instances of | and -.\n            $values_check = join(\'::\', $arr_values);\n\n            // DEBUG\n            // dmp($values_check);\n\n            // Are any values multiple ones (=‘|’)?\n            if (strstr($values_check, \'|\')) {\n                // Initialize $out\n                $out = array();\n                // Put all values in an array\n                foreach ($arr_values as $value) {\n                    $arr_values = explode(\'|\', $value);\n                    $out = array_merge($out, $arr_values);\n                }\n                // Keep only the unique ones\n                $out = array_unique($out);\n                // Keys and values need to be the same\n                $out = array_combine($out, $out);\n            } else {\n                // Keys and values need to be the same\n                $out = array_combine($arr_values, $arr_values);\n            }\n\n            // Calling stripslashes on all array values\n            array_map(\'glz_array_stripslashes\', $out);\n\n            return $out;\n        }\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\nfunction glz_db_get_article_custom_fields($name, $extra)\n{\n    if (is_array($extra)) {\n        // See what custom fields we need to query for\n        foreach ($extra as $custom => $custom_set) {\n            $select[] = glz_custom_number($custom);\n        }\n\n        // Prepare the select elements\n        $select = implode(\',\', $select);\n\n        $arr_article_customs = safe_row(\n            $select,\n            \'textpattern\',\n            \"`ID`=\'\".$name.\"\'\"\n        );\n        return $arr_article_customs;\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\n// -------------------------------------------------------------\n// Goes through all custom sets, returns the first one which is not being used\n// Returns next free id#.\nfunction glz_next_empty_custom()\n{\n    // get current custom fields in \'txp_prefs\' sorted by number\n    // LENGTH = 1-9 first, 10+ afterwards (not 1, 10, 11 ... 2, 3, ...)\n    $rs = safe_rows(\n        \"name, val\",\n        \'txp_prefs\',\n        \"event = \'custom\' ORDER BY LENGTH(name), name\"\n    );\n    $counter = 1;\n    foreach ($rs as $value) {\n        $cf_number = filter_var($value[\'name\'], FILTER_SANITIZE_NUMBER_INT);\n        // Stop at first empty value in IDs 1-10\n        if ($value[\'val\'] == \'\') {\n            $result = $cf_number;\n            break;\n        }\n        // Stop at first empty number > 10\n        if ($cf_number != $counter) {\n            $result = $counter;\n            break;\n        }\n\n        $counter++;\n    }\n    // No empty IDs in 1-10 and no holes > 11 -> get next number\n    if (!isset($result)) {\n        $result = $counter;\n    }\n    return $result;\n}\n\n\n// -------------------------------------------------------------\n// Check if one of the special custom fields exists (e.g. date-picker / time-picker)\nfunction glz_check_custom_set_exists($name)\n{\n    if (!empty($name)) {\n        return safe_field(\"name\", \'txp_prefs\', \"html = \'\".$name.\"\' AND name LIKE \'custom_%\'\");\n    }\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   HELPERS – Helper functions: checks, sanitizers, preps\n#\n##################\n\n\n// -------------------------------------------------------------\n// The types our custom fields can take\nfunction glz_custom_set_types()\n{\n    return array(\n        \'normal\' => array(\n            \'text_input\',\n            \'checkbox\',\n            \'radio\',\n            \'select\',\n            \'multi-select\',\n            \'textarea\'\n        ),\n        \'special\' => array(\n            \'date-picker\',\n            \'time-picker\',\n            \'custom-script\'\n        )\n    );\n}\n\n\n// -------------------------------------------------------------\n// Outputs only custom fields that have been set, i.e. have a name assigned to them\nfunction glz_check_custom_set($all_custom_sets, $step)\n{\n    $out = array();\n    foreach ($all_custom_sets as $key => $custom_field) {\n        if (!empty($custom_field[\'name\'])) {\n            if (($step == \"body\") && ($custom_field[\'type\'] == \"textarea\")) {\n                $out[$key] = $custom_field;\n            } elseif (($step == \"custom_fields\") && ($custom_field[\'type\'] != \"textarea\")) {\n                $out[$key] = $custom_field;\n            }\n        }\n    }\n    return $out;\n}\n\n\n// -------------------------------------------------------------\n// Converts all values into id safe ones [A-Za-z0-9-]\nfunction glz_cf_idname($text)\n{\n    return str_replace(\"_\", \"-\", glz_sanitize_for_cf($text));\n}\n\n\n// -------------------------------------------------------------\n// Converts input into a gTxt-safe lang string \'cf_\' prefix + [a-z0-9_]\nfunction glz_cf_langname($text)\n{\n    return \'cf_\'.glz_sanitize_for_cf($text);\n}\n\n\n// -------------------------------------------------------------\n// Gets translated title or instruction string if one exists\n// @name = custom_field_name\n// @cf_number = $custom_field_number for instructions\n// returns language string or nothing if none exists\nfunction glz_cf_gtxt($name, $cf_number = null)\n{\n    // get language string\n    if (!empty($cf_number)) {\n        // still work if \'custom_X\' or \'custom_X_set\' is passed in as cf_number\n        if (strstr($cf_number, \'custom_\')) {\n            $parts = explode(\"_\", $cf_number);\n            $cf_number = $parts[1];\n        }\n        $cf_name = \'instructions_custom_\'.$cf_number;\n    } else {\n        $cf_name = glz_cf_langname($name);\n    }\n    $cf_gtxt = gTxt($cf_name);\n    // retrieve gTxt value if it exists\n    return ($cf_gtxt != $cf_name) ? $cf_gtxt : \'\';\n}\n\n\n// -------------------------------------------------------------\n// Cleans strings for custom field names and cf_language_names\nfunction glz_sanitize_for_cf($text, $lite = false)\n{\n    $text = trim($text);\n\n    if ($lite) {\n        // lite (legacy)\n        // U&lc letters, numbers, spaces, dashes and underscores\n        return preg_replace(\'/[^A-Za-z0-9\\s\\_\\-]/\', \'\', $text);\n    } else {\n        // strict\n        // lowercase letters, numbers and single underscores; may not start with a number\n        $patterns[0] = \"/[\\_\\s\\-]+/\"; // space(s), dash(es), underscore(s)\n        $replacements[0] = \"_\";\n        $patterns[1] = \"/[^a-z0-9\\_]/\"; // only a-z, 0-9 and underscore\n        $replacements[1] = \"\";\n        $patterns[2] = \"/^\\d+/\"; // numbers at start of string\n        $replacements[2] = \"\";\n\n        return trim(preg_replace($patterns, $replacements, strtolower($text)), \"_\");\n    }\n}\n\n\n// -------------------------------------------------------------\n// Checks if a custom field contains invalid characters, starts with a number or has double underscores\nfunction glz_is_valid_cf_name($text)\n{\n    global $msg;\n\n    if (preg_match(\'/[^a-z0-9\\_]/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_chars\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    } elseif (preg_match(\'/^\\d+/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_starts_with_number\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    } elseif (preg_match(\'/\\_{2,}/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_double_underscores\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    }\n}\n\n// -------------------------------------------------------------\n// Checks if specified start date matches current date format\nfunction glz_is_valid_start_date($date)\n{\n    global $prefs;\n    $formats = array(\n          \"d/m/Y\" => \"dd/mm/yyyy\",\n          \"m/d/Y\" => \"mm/dd/yyyy\",\n          \"Y-m-d\" => \"yyyy-mm-dd\",\n          \"d m y\" => \"dd mm yy\",\n          \"d.m.Y\" => \"dd.mm.yyyy\"\n    );\n\n    $datepicker_format = array_search($prefs[\'glz_cf_datepicker_format\'], $formats);\n\n    $d = DateTime::createFromFormat($datepicker_format, $date);\n    return $d && $d->format($datepicker_format) == $date;\n}\n\n\n// -------------------------------------------------------------\n// Accommodate relative urls in prefs\n// $addhost = true prepends the hostname\nfunction glz_relative_url($url, $addhost = false)\n{\n    $parsed_url = parse_url($url);\n    if (empty($parsed_url[\'scheme\']) && empty($parsed_url[\'hostname\'])) {\n        if ($addhost) {\n            $hostname = (empty($txpcfg[\'admin_url\']) ? hu : ahu);\n        } else {\n            $hostname = \"/\";\n        }\n        $url = $hostname.ltrim($url, \'/\');\n    }\n    return $url;\n}\n\n\n// -------------------------------------------------------------\n// Removes empty values from arrays - used for new custom fields\nfunction glz_array_empty_values($value)\n{\n    if (!empty($value)) {\n        return $value;\n    }\n}\n\n\n// -------------------------------------------------------------\n// Strips slashes in arrays, used in conjuction with e.g. array_map\nfunction glz_array_stripslashes(&$value)\n{\n    return stripslashes($value);\n}\n\n\n// -------------------------------------------------------------\n// Removes { } from values which are marked as default\nfunction glz_clean_default($value)\n{\n    $pattern = \"/^.*\\{(.*)\\}.*/\";\n    return preg_replace($pattern, \"$1\", $value);\n}\n\n\n// -------------------------------------------------------------\n// Calls glz_clean_default() in an array context\nfunction glz_clean_default_array_values(&$value)\n{\n    $value = glz_clean_default($value);\n}\n\n\n// -------------------------------------------------------------\n// Return our default value from all custom_field values\nfunction glz_default_value($all_values)\n{\n    if (is_array($all_values)) {\n        preg_match(\"/(\\{.*\\})/\", join(\" \", $all_values), $default);\n        return ((!empty($default) && $default[0]) ? $default[0] : \'\');\n    }\n}\n\n\n// -------------------------------------------------------------\n// Custom_set without \"_set\" e.g. custom_1_set => custom_1\n// or custom set formatted for IDs e.g. custom-1\nfunction glz_custom_number($custom_set, $delimiter=\"_\")\n{\n    // Trim \"_set\" from the end of the string\n    $custom_field = substr($custom_set, 0, -4);\n\n    // If a delimeter is specified custom_X to custom{delimeter}X\n    if ($delimiter != \"_\") {\n        $custom_field = str_replace(\"_\", $delimiter, $custom_field);\n    }\n    return $custom_field;\n}\n\n\n// -------------------------------------------------------------\n// Custom_set digit e.g. custom_1_set => 1\nfunction glz_custom_digit($custom_set)\n{\n    $out = explode(\"_\", $custom_set);\n    // $out[0] will always be \'custom\'\n    return $out[1]; // so take $out[1]\n}\n\n\n// -------------------------------------------------------------\n// Returns the custom_X_set from a custom set name e.g. \"rating\" gives us custom_1_set\nfunction glz_get_custom_set($value)\n{\n    $result = safe_field(\n        \"name\",\n        \'txp_prefs\',\n        \"event = \'custom\' AND val = \'\".doSlash(val).\"\'\"\n    );\n    if (!$result) {\n        // No result -> return error message\n        trigger_error(gTxt(\'glz_cf_doesnt_exist\', array(\'{custom_set_name}\' => $value)), E_USER_WARNING);\n        return false;\n    }\n    return true;\n}\n\n\n// -------------------------------------------------------------\n// Get the article ID, even if it\'s newly saved\nfunction glz_get_article_id()\n{\n    return (!empty($GLOBALS[\'ID\']) ? $GLOBALS[\'ID\'] : gps(\'ID\'));\n}\n\n\n// -------------------------------------------------------------\n// Is the custom field name already taken?\nfunction glz_check_custom_set_name($custom_set_name, $custom_set)\n{\n    // Check that the name input by the user as well as its sanitized version don\'t already exist\n    return safe_field(\n        \"name\",\n        \'txp_prefs\',\n        \"event = \'custom\' AND val IN (\'\".doSlash($custom_set_name).\"\', \'\".glz_sanitize_for_cf($custom_set_name).\"\') AND name <> \'\".doSlash($custom_set).\"\'\"\n    );\n}\n\n\n// -------------------------------------------------------------\n// Edit/delete buttons in custom_fields table require a form each\nfunction glz_form_buttons($action, $value, $custom_set, $custom_set_name, $custom_set_type, $custom_set_position, $onsubmit=\'\')\n{\n    $onsubmit = ($onsubmit) ? \'onsubmit=\"\'.$onsubmit.\'\"\' : \'\';\n\n    // ui-icon (see admin hive styling)\n    if ($action == \"delete\") {\n        $ui_icon = \"close\";\n    }\n    if ($action == \"reset\") {\n        $ui_icon = \"trash\";\n    }\n    if ($action == \"add\") {\n        $ui_icon = \"circlesmall-plus\";\n    }\n\n    return\n    \'<form class=\"action-button\" method=\"post\" action=\"index.php\" \'.$onsubmit.\'>\n        <input name=\"custom_set\" value=\"\'.$custom_set.\'\" type=\"hidden\" />\n        <input name=\"custom_set_name\" value=\"\'.$custom_set_name.\'\" type=\"hidden\" />\n        <input name=\"custom_set_type\" value=\"\'.$custom_set_type.\'\" type=\"hidden\" />\n        <input name=\"custom_set_position\" value=\"\'.$custom_set_position.\'\" type=\"hidden\" />\n        <input name=\"event\" value=\"glz_custom_fields\" type=\"hidden\" />\n        <button name=\"\'.$action.\'\" type=\"submit\" value=\"\'.$value.\'\"\n                class=\"jquery-ui-button-icon-left ui-button ui-corner-all ui-widget\">\n            <span class=\"ui-button-icon ui-icon ui-icon-\'.$ui_icon.\'\"></span>\n            <span class=\"ui-button-icon-space\"> </span>\n            \'.gTxt(\"glz_cf_action_\".$action).\'\n        </button>\n    </form>\';\n}\n\n\n// TODO: Appears to be unused?!\n// -------------------------------------------------------------\n// Returns all sections/categories that are searchable\nfunction glz_all_searchable_sections_categories($type)\n{\n    $type = (in_array($type, array(\'category\', \'section\')) ? $type : \'section\');\n    $condition = \"\";\n\n    if ($type == \"section\") {\n        $condition .= \"searchable=\'1\'\";\n    } else {\n        $condition .= \"name <> \'root\' AND type=\'article\'\";\n    }\n\n    $result = safe_rows(\'*\', \"txp_{$type}\", $condition);\n\n    $out = array();\n    foreach ($result as $value) {\n        $out[$value[\'name\']] = $value[\'title\'];\n    }\n\n    return $out;\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   PUBLIC TAGS\n#\n##################\n\n\n/*\n * Adds a title attribute to txp:custom_fields.\n * Set to title=\"1\" to show title in the current language.\n * If no title is available, it returns nothing.\n */\n\n// Divert txp:custom_field calls through glz_custom_field\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'glz_custom_field\', \'custom_field\');\n}\n\nfunction glz_custom_field($atts, $thing = null)\n{\n    // Extract attributes as vars\n    extract(lAtts(array(\n        \'title\' => \'0\',\n        \'name\' => \'\'\n    ), $atts, false));  // false: suppress warnings\n\n    // Unset otherwise non-existent attribute\n    unset($atts[\'title\']);\n\n    // if $title is specified, divert to glz_cf_gtxt\n    return $title ? glz_cf_gtxt($name) : custom_field($atts, $thing);\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   PREFERENCES PANE – Functions for preferences + preferences pane\n#\n##################\n\n\nfunction glz_cf_prefs_install()\n{\n    global $prefs, $txpcfg;\n\n    $position = 200;\n    $base_url = (empty($txpcfg[\'admin_url\'])) ? hu : ahu;\n    $base_path = (empty($txpcfg[\'admin_url\'])) ? $prefs[\'path_to_site\'] : str_replace(\"public\", \"admin\", $prefs[\'path_to_site\']);\n\n    // array: old_prefname => array(\'pref.subevent\', \'html\', \'default-value\')\n    $plugin_prefs = array(\n        \'values_ordering\'        => array(\'\', \'glz_prefs_orderby\', \'custom\'),\n        \'multiselect_size\'       => array(\'\', \'glz_text_input_small\', \'5\'),\n        \'css_asset_url\'          => array(\'\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields\'),\n        \'js_asset_url\'           => array(\'\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields\'),\n        \'custom_scripts_path\'    => array(\'\', \'glz_url_input\', $base_path.\'/plugins/glz_custom_fields\'),\n        \'use_sortable\'           => array(\'\', \'yesnoradio\', 1),\n        \'permit_full_deinstall\'  => array(\'\', \'yesnoradio\', 0),\n        \'datepicker_url\'         => array(\'glz_cf_datepicker\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields/jquery.datePicker\'),\n        \'datepicker_format\'      => array(\'glz_cf_datepicker\', \'glz_prefs_datepicker_format\', \'dd/mm/yyyy\'),\n        \'datepicker_first_day\'   => array(\'glz_cf_datepicker\', \'glz_prefs_datepicker_firstday\', 1),\n        \'datepicker_start_date\'  => array(\'glz_cf_datepicker\', \'glz_input_start_date\', \'01/01/2018\'),\n        \'timepicker_url\'         => array(\'glz_cf_timepicker\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields/jquery.timePicker\'),\n        \'timepicker_start_time\'  => array(\'glz_cf_timepicker\', \'glz_text_input_small\', \'00:00\'),\n        \'timepicker_end_time\'    => array(\'glz_cf_timepicker\', \'glz_text_input_small\', \'23:30\'),\n        \'timepicker_step\'        => array(\'glz_cf_timepicker\', \'glz_text_input_small\', 30),\n        \'timepicker_show_24\'     => array(\'glz_cf_timepicker\', \'glz_prefs_timepicker_format\', true)\n    );\n\n    foreach ($plugin_prefs as $name => $val) {\n        if (get_pref($name, false) === false) {\n            // If pref is new, create new pref with \'glz_cf_\' prefix\n            create_pref(\'glz_cf_\'.$name, $val[2], \'glz_custom_f\'.($val[0] ? \'.\'.$val[0] : \'\'), PREF_PLUGIN, $val[1], $position, \'\');\n        } else {\n            // If pref exists, add \'glz_cf_\' prefix to name, reassign position and html type and set to type PREF_PLUGIN\n            safe_update(\n                \'txp_prefs\',\n                \"name = \'glz_cf_\".$name.\"\',\n                 event = \'glz_custom_f\".($val[0] ? \".\".$val[0] : \"\").\"\',\n                 html = \'\".$val[1].\"\',\n                 type = \".PREF_PLUGIN.\",\n                 position = \".$position,\n                \"name = \'\".$name.\"\'\"\n            );\n        }\n        $position++;\n    }\n\n    // Make some $prefs hidden (for safety and troubleshooting)\n    foreach (array(\n        \'use_sortable\',\n        \'permit_full_deinstall\'\n    ) as $name) {\n        safe_update(\n            \'txp_prefs\',\n            \"type = \".PREF_HIDDEN,\n            \"name = \'glz_cf_\".$name.\"\'\"\n        );\n    }\n\n    // Set \'migrated\' pref to \'glz_cf_migrated\' and to hidden (type = 2);\n    if (get_pref(\'migrated\')) {\n        safe_update(\n            \'txp_prefs\',\n            \"name = \'glz_cf_migrated\',\n             type = \".PREF_HIDDEN,\n            \"name = \'migrated\'\"\n        );\n    }\n\n    // Remove no longer needed \'max_custom_fields\' pref\n    safe_delete(\n        \'txp_prefs\',\n        \"name = \'max_custom_fields\'\"\n    );\n}\n\n\n/**\n * Renders a HTML choice of GLZ value ordering.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_orderby($name, $val)\n{\n    $vals = array(\n        \'ascending\'   => gTxt(\'glz_cf_prefs_value_asc\'),\n        \'descending\'  => gTxt(\'glz_cf_prefs_value_desc\'),\n        \'custom\'      => gTxt(\'glz_cf_prefs_value_custom\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of date formats.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_datepicker_format($name, $val)\n{\n    $vals = array(\n        \"dd/mm/yyyy\"  => gTxt(\'glz_cf_prefs_slash_dmyy\'),\n        \"mm/dd/yyyy\"  => gTxt(\'glz_cf_prefs_slash_mdyy\'),\n        \"yyyy-mm-dd\"  => gTxt(\'glz_cf_prefs_dash_yymd\'),\n        \"dd mm yy\"    => gTxt(\'glz_cf_prefs_space_dmy\'),\n        \"dd.mm.yyyy\"  => gTxt(\'glz_cf_prefs_dot_dmyy\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of weekdays.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_datepicker_firstday($name, $val)\n{\n    $vals = array(\n        0             => gTxt(\'glz_cf_prefs_sunday\'),\n        1             => gTxt(\'glz_cf_prefs_monday\'),\n        2             => gTxt(\'glz_cf_prefs_tuesday\'),\n        3             => gTxt(\'glz_cf_prefs_wednesday\'),\n        4             => gTxt(\'glz_cf_prefs_thursday\'),\n        5             => gTxt(\'glz_cf_prefs_friday\'),\n        6             => gTxt(\'glz_cf_prefs_saturday\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of time formats.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_timepicker_format($name, $val)\n{\n    $vals = array(\n        \'true\'        => gTxt(\'glz_cf_prefs_24_hours\'),\n        \'false\'       => gTxt(\'glz_cf_prefs_12_hours\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n\n/**\n * Renders a small-width HTML &lt;input&gt; element.\n * Checks if start date matches current datepicker date format\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_input_start_date($name, $val)\n{\n    $out = text_input($name, $val, INPUT_SMALL);\n    // Output error notice if start date does not match date format\n    if (!glz_is_valid_start_date($val)) {\n        $out .= \'<br><span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.gTxt(\'glz_cf_datepicker_start_date_error\').\'</span>\';\n    }\n    return $out;\n}\n\n\n/**\n * Renders a medium-width HTML &lt;input&gt; element.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_text_input_medium($name, $val)\n{\n    return text_input($name, $val, INPUT_MEDIUM);\n}\n\n/**\n * Renders a small-width HTML &lt;input&gt; element.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_text_input_small($name, $val)\n{\n    return text_input($name, $val, INPUT_SMALL);\n}\n\n/**\n * Renders a regular-width HTML &lt;input&gt; element for an URL with path check.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_url_input($name, $val)\n{\n    global $use_minified;\n    $min = ($use_minified === true) ? \'.min\' : \'\';\n    $check_paths = (gps(\'check_paths\') == \"1\") ? true : false;\n\n    // Output regular-width text_input for url\n    $out  = fInput(\'text\', $name, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $name);\n\n    // Array of possible expected url inputs and corresponding files and error-msg-stubs\n    // \'pref_name\' => array(\'/targetfilename.ext\', \'gTxt_folder (inserted into error msg)\')\n    // paths do not require a target filename, urls do.\n    $glz_cf_url_inputs = array(\n        \'glz_cf_css_asset_url\'       => array(\'/glz_custom_fields\'.$min.\'.css\', \'glz_cf_css_folder\'),\n        \'glz_cf_js_asset_url\'        => array(\'/glz_custom_fields\'.$min.\'.js\',  \'glz_cf_js_folder\'),\n        \'glz_cf_datepicker_url\'      => array(\'/datePicker\'.$min.\'.js\',         \'glz_cf_datepicker_folder\'),\n        \'glz_cf_timepicker_url\'      => array(\'/timePicker\'.$min.\'.js\',         \'glz_cf_timepicker_folder\'),\n        \'glz_cf_custom_scripts_path\' => array(\'\',                               \'glz_cf_custom_folder\')\n    );\n    // File url or path to test = prefs_val (=url/path) + targetfilename (first item in array)\n    $glz_cf_url_to_test          = $val.$glz_cf_url_inputs[$name][0];\n    // gTxt string ref for folder name for error message (second item in array)\n    $glz_cf_url_input_error_stub = $glz_cf_url_inputs[$name][1];\n\n    // See if url / path is readable. If not, produce error message\n    if ($glz_cf_url_to_test && $check_paths == true) {\n        // permit relative URLs but conduct url test with hostname\n        if (strstr($name, \'url\')) {\n            $glz_cf_url_to_test = glz_relative_url($glz_cf_url_to_test, $addhost = true);\n        }\n        $url_error = (@fopen($glz_cf_url_to_test, \"r\")) ? \'\' : gTxt(\'glz_cf_folder_error\', array(\'{folder}\' => gTxt($glz_cf_url_input_error_stub) ));\n\n        // Output error notice if one exists, else success notice\n        $out .= (!empty($url_error)) ?\n            \'<br><span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.$url_error.\'</span>\' :\n            \'<br><span class=\"success\"><span class=\"ui-icon ui-icon-check\"></span> \'.gTxt(\'glz_cf_folder_success\').\'</span>\';\n    }\n\n    return $out;\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   FIELD TYPES – Rendering of custom fields on \'Write\' pane\n#\n##################\n\n\n// -------------------------------------------------------------\n// Formats the custom set output based on its type\nfunction glz_format_custom_set_by_type($custom, $custom_id, $custom_set_type, $arr_custom_field_values, $custom_value = \"\", $default_value = \"\")\n{\n    if (is_array($arr_custom_field_values)) {\n        $arr_custom_field_values = array_map(\'glz_array_stripslashes\', $arr_custom_field_values);\n    }\n\n    switch ($custom_set_type) {\n        // These are the normal custom fields\n        case \"text_input\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'\'\n            );\n\n        case \"select\":\n            return array(\n                glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-select\'\n            );\n\n        case \"multi-select\":\n            return array(\n                glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value, 1),\n                \'glz-custom-multiselect\'\n            );\n\n        case \"checkbox\":\n            return array(\n                glz_checkbox($custom, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-checkbox\'\n            );\n\n        case \"radio\":\n            return array(\n                glz_radio($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-radio\'\n            );\n\n        case \"textarea\":\n            return array(\n                text_area($custom, 0, 0, $custom_value, $custom_id),\n                \'glz-custom-textarea\'\n            );\n\n        // Here start the special custom fields, might need to refactor the return, starting to repeat itself\n        case \"date-picker\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit date-picker\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'glz-custom-datepicker\'\n            );\n\n        case \"time-picker\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit time-picker\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'glz-custom-timepicker\'\n            );\n\n        case \"custom-script\":\n            global $custom_scripts_path;\n            return array(\n                glz_custom_script($custom_scripts_path.\"/\".reset($arr_custom_field_values), $custom, $custom_id, $custom_value),\n                \'glz-custom-script\'\n            );\n\n        // A type has been passed that is not supported yet\n        default:\n            return array(\n                gTxt(\'glz_cf_type_not_supported\'),\n                \'glz-custom-unknown\'\n            );\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default selectInput() because trimming \\t and \\n didn\'t work + some other mods & multi-select\nfunction glz_selectInput($name = \'\', $id = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\', $multi = \'\')\n{\n    if (is_array($arr_values)) {\n        global $prefs;\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $selected = glz_selected_checked(\'selected\', $key, $custom_value, $default_value);\n            $out[] = \"<option value=\\\"$key\\\"{$selected}>$value</option>\";\n        }\n\n        // We\'ll need the extra attributes as well as a name that will produce an array\n        if ($multi) {\n            $multi = \' multiple=\"multiple\" size=\"\'.$prefs[\'glz_cf_multiselect_size\'].\'\"\';\n            $name .= \"[]\";\n        }\n\n        return \"<select id=\\\"\".glz_cf_idname($id).\"\\\" name=\\\"$name\\\" class=\\\"list\\\"$multi>\".\n      ($default_value ? \'\' : \"<option value=\\\"\\\"$selected>&nbsp;</option>\").\n      ($out ? join(\'\', $out) : \'\').\n      \"</select>\";\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default checkbox() to keep the looping in here and check against existing value/s\nfunction glz_checkbox($name = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\')\n{\n    if (is_array($arr_values)) {\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $checked = glz_selected_checked(\'checked\', $key, $custom_value);\n\n            $out[] = \"<div class=\\\"txp-form-checkbox glz-cf-\".str_replace(\"_\", \"-\", glz_cf_idname($key)).\"\\\"><input type=\\\"checkbox\\\" name=\\\"{$name}[]\\\" value=\\\"$key\\\" class=\\\"checkbox\\\" id=\\\"\".glz_cf_idname($key).\"\\\"{$checked} /> <label for=\\\"\".glz_cf_idname($key).\"\\\">$value</label></div>\";\n        }\n\n        return join(\'\', $out);\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default radio() to keep the looping in here and check against existing value/s\nfunction glz_radio($name = \'\', $id = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\')\n{\n    if (is_array($arr_values)) {\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $checked = glz_selected_checked(\'checked\', $key, $custom_value);\n            $default = ($default_value == $key) ? \' \'.\'default\' : \'\';\n\n            $out[] = \"<div class=\\\"txp-form-radio glz-cf-\".str_replace(\"_\", \"-\", glz_cf_idname($key)).\"\\\"><input type=\\\"radio\\\" name=\\\"$name\\\" value=\\\"$key\\\" class=\\\"radio{$default}\\\" id=\\\"{$id}_\".glz_cf_idname($key).\"\\\"{$checked} /> <label for=\\\"{$id}_\".glz_cf_idname($key).\"\\\">$value</label></div>\";\n        }\n\n        return join(\'\', $out);\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Checking if this custom field has selected or checked values\nfunction glz_selected_checked($range_unit, $value, $custom_value = \'\')\n{\n    // We\'re comparing against a key which is a \"clean\" value\n    $custom_value = htmlspecialchars($custom_value);\n\n    // Make an array if $custom_value contains multiple values\n    if (strpos($custom_value, \'|\')) {\n        $arr_custom_value = explode(\'|\', $custom_value);\n    }\n\n    if (isset($arr_custom_value)) {\n        $out = (in_array($value, $arr_custom_value)) ? \" $range_unit=\\\"$range_unit\\\"\" : \"\";\n    } else {\n        $out = ($value == $custom_value) ? \" $range_unit=\\\"$range_unit\\\"\" : \"\";\n    }\n\n    return $out;\n}\n\n\n//-------------------------------------------------------------\n// Button gets more consistent styling across browsers rather than input type=\"submit\"\n// included in this plugin until in makes it into TXP - if that ever happens...\nfunction glz_fButton($type, $name, $contents=\'Submit\', $value, $class=\'\', $id=\'\', $title=\'\', $onClick=\'\', $disabled = false)\n{\n    $o  = \'<button type=\"\'.$type.\'\" name=\"\'.$name.\'\"\';\n    $o .= \' value=\"\'.htmlspecialchars($value).\'\"\';\n    $o .= ($class)    ? \' class=\"\'.$class.\'\"\' : \'\';\n    $o .= ($id)       ? \' id=\"\'.$id.\'\"\' : \'\';\n    $o .= ($title)    ? \' title=\"\'.$title.\'\"\' : \'\';\n    $o .= ($onClick)  ? \' onclick=\"\'.$onClick.\'\"\' : \'\';\n    $o .= ($disabled) ? \' disabled=\"disabled\"\' : \'\';\n    $o .= \'>\';\n    $o .= $contents;\n    $o .= \'</button>\';\n    return $o;\n}\n\n\n//-------------------------------------------------------------\n// Evals a PHP script and displays output right under the custom field label\nfunction glz_custom_script($script, $custom, $custom_id, $custom_value)\n{\n    global $prefs;\n    if (is_file($prefs[\'glz_cf_custom_scripts_path\'].$script)) {\n        include_once($prefs[\'glz_cf_custom_scripts_path\'].$script);\n        $custom_function = basename($script, \".php\");\n        if (is_callable($custom_function)) {\n            return call_user_func_array($custom_function, array($custom, $custom_id, $custom_value));\n        } else {\n            return gTxt(\'glz_cf_not_callable\', array(\'{function}\' => $custom_function, \'{file}\' => $script));\n        }\n    } else {\n        return \'<span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.gTxt(\'glz_cf_not_found\', array(\'{file}\' => $prefs[\'glz_cf_custom_scripts_path\'].$script)).\'</span>\';\n    }\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   CSS + JSS – For injecting into admin-side <head> area\n#\n##################\n\n\n// -------------------------------------------------------------\n// Contains minified output of glz_custom_fields.css file\n// To update: copy the minified output of the actual css file into the function\nfunction glz_custom_fields_head_css()\n{\n    $css = <<<\'CSS\'\n.glz-cf-setup-switch{float:right}[dir=rtl] .glz-cf-setup-switch{float:left}#glz_custom_fields_container .txp-list-col-id{width:3em;text-align:center}#glz_custom_fields_container .txp-list-col-options,#glz_custom_fields_container .txp-list-col-position{width:5em}#glz_custom_fields_container .txp-list-col-title .cf-instructions.ui-icon{width:2em;height:17px;float:right;background-repeat:no-repeat;background-position:center 2px;opacity:.33;cursor:pointer}#glz_custom_fields_container .txp-list-col-title.disabled .cf-instructions{opacity:1!important;pointer-events:auto}#glz_custom_fields_container .txp-list-col-options{text-align:center}#glz_custom_fields_container .txp-list-col-options .ui-icon{width:4em;background-repeat:no-repeat;background-position:50%}#glz_custom_fields_container .txp-list-col-options .ui-icon:hover{-webkit-filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%);filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%)}#glz_custom_fields_container table.fixed-width{table-layout:fixed}#glz_custom_fields_container table.sortable .txp-list-col-sort{width:3em;text-align:center}#glz_custom_fields_container table.sortable .ui-sortable-handle{cursor:row-resize;text-align:center;opacity:.66}#glz_custom_fields_container table.sortable .txp-list-col-position{display:none}#glz_custom_fields_container .ui-sortable-helper,#glz_custom_fields_container .ui-sortable-placeholder{display:table}#add_edit_custom_field .hidden{display:none}@media screen and (min-width:47em){.txp-edit .txp-form-field .txp-form-field-instructions,.txp-tabs-vertical-group .txp-form-field-instructions{max-width:50%;padding-left:50%}}.check-path{float:right;font-size:.7em;font-weight:400}[dir=rtl] .check-path{float:left}.ui-tabs-nav .check-path{display:none}#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:none}.glz-custom-field-reset.disabled:hover{text-decoration:none}.glz-custom-field-reset.disabled{cursor:default}.glz-custom-checkbox .txp-form-field-value label,.glz-custom-radio .txp-form-field-value label{cursor:pointer}\nCSS;\n\n    return \'<style>\'.n.$css.n.\'</style>\';\n}\n\n\n// -------------------------------------------------------------\n// Contains minified output of glz_custom_fields.css file\n// To update copy the minified output of the actual js file into the function\nfunction glz_custom_fields_head_js()\n{\n    $js = <<<\'JS\'\n$(function(){function e(){$(\".glz-custom-radio\").length>0&&$(\".glz-custom-radio\").each(function(){var e=$(this).find(\"input:first\").attr(\"name\");$(this).find(\"label:first\").after(\' <span class=\"small\"><a href=\"#\" class=\"glz-custom-field-reset\" name=\"\'+e+\'\">Reset</a></span>\'),$(\"input:radio[name=\"+e+\"]\").is(\":checked\")||$(\".glz-custom-field-reset[name=\"+e+\"]\").addClass(\"disabled\")})}function t(){$glz_value_field.find(\"textarea#value\").length&&(s.textarea_value=$glz_value_field.find(\"textarea#value\").html(),$glz_value_field.find(\"textarea#value\").remove()),$glz_value_field.find(\"input#value\").length?0==$glz_value_field.find(\"input#value\").prop(\"disabled\")&&(s.path_value=$glz_value_field.find(\"input#value\").attr(\"value\")):$glz_value_field.find(\".txp-form-field-value\").prepend(\'<input type=\"text\" id=\"value\" name=\"value\" />\'),$glz_value_field.find(\"input#value\").attr(\"value\",\"-----\").prop(\"disabled\",!0),$glz_value_instructions.html(\"\")}function l(){$glz_value_field.find(\"input#value\").length&&(0==$glz_value_field.find(\"input#value\").prop(\"disabled\")&&(s.path_value=$glz_value_field.find(\"input#value\").attr(\"value\")),$glz_value_field.find(\"input#value\").remove(),$glz_value_instructions.html(\"\")),$glz_value_field.find(\"textarea#value\").length||$(\".edit-custom-set-value .txp-form-field-value\").prepend(\'<textarea id=\"value\" name=\"value\" rows=\"5\"></textarea>\'),s.textarea_value&&$glz_value_field.find(\"textarea#value\").html(s.textarea_value),$glz_value_instructions.html(s.messages.textarea)}function a(){$glz_value_field.find(\"textarea#value\").length&&(s.textarea_value=$glz_value_field.find(\"textarea#value\").html(),$glz_value_field.find(\"textarea#value\").remove(),$glz_value_instructions.html(\"\")),$glz_value_field.find(\"input#value\").length||$glz_value_field.find(\".txp-form-field-value\").prepend(\'<input type=\"text\" id=\"value\" name=\"value\" size=\"32\" />\'),\"-----\"==$glz_value_field.find(\"input#value\").attr(\"value\")&&$glz_value_field.find(\"input#value\").attr(\"value\",\"\"),$glz_value_field.find(\"input#value\").prop(\"disabled\",!1),$glz_value_instructions.html(s.messages.customscriptpath),s.path_value&&$glz_value_field.find(\"input#value\").attr(\"value\",s.path_value)}function i(){-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,[\"multi-select\",\"custom-script\"]))?$glz_select_instructions.html(\'<a href=\"//\'+window.location.host+window.location.pathname+\'?event=prefs#prefs_group_glz_custom_f\">\'+s.messages.configure+\"</a>\"):$glz_select_instructions.html(\"\")}textpattern.Relay.register(\"txpAsyncForm.success\",e);var s;s={},s.special_custom_types=[\"date-picker\",\"time-picker\"],s.no_value_custom_types=[\"text_input\",\"textarea\"],$glz_value_field=$(\".edit-custom-set-value\"),$glz_value_instructions=$glz_value_field.find(\".txp-form-field-instructions\"),$glz_select_instructions=$(\".edit-custom-set-type\").find(\".txp-form-field-instructions\"),s.messages={textarea:$(\".glz-custom-textarea-msg\").html(),configure:$glz_select_instructions.text(),customscriptpath:$(\".glz-custom-script-msg\").text()},$(\".glz-custom-script-msg\").remove(),$(\".glz-custom-textarea-msg\").remove(),i(),-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():\"custom-script\"==$(\"select#custom_set_type :selected\").attr(\"value\")&&a(),$(\"select#custom_set_type\").change(function(){i(),-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():\"custom-script\"==$(\"select#custom_set_type :selected\").attr(\"value\")?a():l()}),e(),$(\".txp-layout\").on(\"click\",\".glz-custom-field-reset\",function(){if($(this).hasClass(\"disabled\"))return!1;var e=$(this).attr(\"name\");return $(\"input[name=\"+e+\"]\").prop(\"checked\",!1),$(\"input[name=\"+e+\"].default\").prop(\"checked\",!0),$(this).addClass(\"disabled\"),0===$(this).siblings(\".txp-form-radio-reset\").length&&0===$(\"input[name=\"+e+\"]:checked\").length&&$(this).after(\'<input type=\"hidden\" class=\"txp-form-radio-reset\" value=\"\" name=\"\'+e+\'\" />\'),!1}),$(\".txp-layout\").on(\"click\",\".glz-custom-radio .radio\",function(){var e=$(this).attr(\"name\");$this_reset_button=$(\".glz-custom-field-reset[name=\"+e+\"]\"),$this_reset_button.hasClass(\"disabled\")&&($(\"input[type=hidden][name=\"+e+\"]\").remove(),$this_reset_button.removeClass(\"disabled\"))})});\nJS;\n    return \'<script>\'.n.$js.n.\'</script>\';\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   CALLBACKS – Callback functions called from main.php\n#\n##################\n\n\n// -------------------------------------------------------------\n// Replaces the default custom fields on the \'Write\' panel\nfunction glz_custom_fields_replace($event, $step, $data, $rs)\n{\n    // Get all custom field sets from prefs\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    // Filter all custom fields & keep only those that are set for that render step\n    $arr_custom_fields = glz_check_custom_set($all_custom_sets, $step);\n\n    // DEBUG\n    // dmp($arr_custom_fields);\n\n    $out = \' \';\n\n    if (is_array($arr_custom_fields) && !empty($arr_custom_fields)) {\n        // Get all custom fields values for this article\n        $arr_article_customs = glz_db_get_article_custom_fields(glz_get_article_id(), $arr_custom_fields);\n\n        // DEBUG\n        // dmp($arr_article_customs);\n\n        if (is_array($arr_article_customs)) {\n            extract($arr_article_customs);\n        }\n\n        // Which custom fields are set\n        foreach ($arr_custom_fields as $custom => $custom_set) {\n            // Get all possible/default value(s) for this custom set from custom_fields table\n            $arr_custom_field_values = glz_db_get_custom_field_values($custom, array(\'custom_set_name\' => $custom_set[\'name\']));\n\n            // DEBUG\n            // dmp($arr_custom_field_values);\n\n            // Custom_set formatted for id e.g. custom_1_set => custom-1 - don\'t ask...\n            $custom_id = glz_custom_number($custom, \"-\");\n            // custom_set without \"_set\" e.g. custom_1_set => custom_1\n            $custom = glz_custom_number($custom);\n\n            // If current article holds no value for this custom field and we have no default value, make it empty\n            // (not using empty() as it also eradicates values of \'0\')\n            $custom_value = ((isset($$custom) && trim($$custom) <> \'\') ? $$custom : \'\');\n            // DEBUG\n            // dmp(\"custom_value: {$custom_value}\");\n\n            // Check if there is a default value\n            // if there is, strip the { }\n            $default_value = glz_clean_default(glz_default_value($arr_custom_field_values));\n            // DEBUG\n            // dmp(\"default_value: {$default_value}\");\n\n            // Now that we\'ve found our default, we need to clean our custom_field values\n            if (is_array($arr_custom_field_values)) {\n                array_walk($arr_custom_field_values, \"glz_clean_default_array_values\");\n            }\n\n            // DEBUG\n            // dmp($arr_custom_field_values);\n\n            // The way our custom field value is going to look like\n            list($custom_set_value, $custom_class) = glz_format_custom_set_by_type($custom, $custom_id, $custom_set[\'type\'], $arr_custom_field_values, $custom_value, $default_value);\n\n            // DEBUG\n            // dmp($custom_set_value);\n\n            // cf_lang string (define this in your language to create a field label)\n            $cf_lang = glz_cf_langname($custom_set[\"name\"]);\n            // Get the (localised) label if one exists, otherwise the regular name (as before)\n            $cf_label = (gTxt($cf_lang) != $cf_lang) ? gTxt($cf_lang) : $custom_set[\"name\"];\n\n            $out .= inputLabel(\n                $custom_id,\n                $custom_set_value,\n                $cf_label,\n                array(\'\', \'instructions_\'.$custom),\n                array(\'class\' => \'txp-form-field custom-field glz-cf \'.$custom_class.\' \'.$custom_id.\' cf-\'.glz_cf_idname(str_replace(\'_\', \'-\', $custom_set[\"name\"])))\n            );\n        }\n    }\n\n    // DEBUG\n    // dmp($out);\n\n    // If we\'re writing textarea custom fields, we need to include the excerpt as well\n    if ($step == \"body\") {\n        $out = $data.$out;\n    }\n\n    return $out;\n}\n\n\n// -------------------------------------------------------------\n// Prep custom fields values for db (convert multiple values into a string e.g. multi-selects, checkboxes & radios)\nfunction glz_custom_fields_before_save()\n{\n    // Iterate over POST vars\n    foreach ($_POST as $key => $value) {\n        // Extract custom_{} keys with multiple values as arrays\n        if (strstr($key, \'custom_\') && is_array($value)) {\n            // Convert to delimited string …\n            $value = implode($value, \'|\');\n            // and feed back into $_POST\n            $_POST[$key] = $value;\n        }\n    }\n\n    // DEBUG\n    // dmp($_POST);\n}\n\n\n// -------------------------------------------------------------\n// Inject css & js into admin head\nfunction glz_custom_fields_inject_css_js($debug = false)\n{\n    global $event, $prefs, $use_minified, $debug;\n\n    $msg = array();\n    $min = ($use_minified) ? \'.min\' : \'\';\n\n    // do we have a date-picker or time-picker custom field\n    $date_picker = glz_check_custom_set_exists(\"date-picker\");\n    $time_picker = glz_check_custom_set_exists(\"time-picker\");\n\n    // glz_cf stylesheets (load from file when $debug is set to true)\n    if ($debug) {\n        $css = \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_css_asset_url\']).\'/glz_custom_fields\'.$min.\'.css\">\'.n;\n        // Show hidden fields\n        $css .= \'<style>#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:flex}</style>\';\n    } else {\n        $css = glz_custom_fields_head_css();\n    }\n    // glz_cf javascript\n    $js = \'\';\n\n    if ($event == \'article\') {\n        // If a date picker field exists\n        if ($date_picker) {\n            $css .= \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_datepicker_url\']).\'/datePicker\'.$min.\'.css\" />\'.n;\n            foreach (array(\'date\'.$min.\'.js\', \'datePicker\'.$min.\'.js\') as $file) {\n                $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_datepicker_url\']).\"/\".$file.\'\"></script>\'.n;\n            }\n            $js_datepicker_msg = \'<span class=\"messageflash error\" role=\"alert\" aria-live=\"assertive\"><span class=\"ui-icon ui-icon-alert\"></span> <a href=\"\'.ahu.\'index.php?event=prefs&check_url=1#prefs_group_glz_custom_f\">\'.gTxt(\'glz_cf_public_error_datepicker\').\'</a> <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a></span>\';\n            $js .= <<<JS\n<script>\n$(document).ready(function () {\n    textpattern.Relay.register(\'txpAsyncForm.success\', glzDatePicker);\n\n    function glzDatePicker() {\n        if ($(\"input.date-picker\").length > 0) {\n            try {\n                Date.firstDayOfWeek = {$prefs[\'glz_cf_datepicker_first_day\']};\n                Date.format = \'{$prefs[\"glz_cf_datepicker_format\"]}\';\n                Date.fullYearStart = \'19\';\n                $(\".date-picker\").datePicker({startDate:\'{$prefs[\"glz_cf_datepicker_start_date\"]}\'});\n                $(\".date-picker\").dpSetOffset(29, -1);\n            } catch(err) {\n                $(\'#messagepane\').html(\'{$js_datepicker_msg}\');\n            }\n        }\n    }\n\n    glzDatePicker();\n});\n</script>\nJS;\n        }\n\n        // If a time picker field exists\n        if ($time_picker) {\n            $css .= \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_timepicker_url\']).\'/timePicker\'.$min.\'.css\" />\'.n;\n            $js  .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_timepicker_url\']).\'/timePicker\'.$min.\'.js\"></script>\'.n;\n            $js_timepicker_msg = \'<span class=\"messageflash error\" role=\"alert\" aria-live=\"assertive\"><span class=\"ui-icon ui-icon-alert\"></span> <a href=\"\'.ahu.\'index.php?event=prefs&check_url=1#prefs_group_glz_custom_f\">\'.gTxt(\'glz_cf_public_error_timepicker\').\'</a> <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a></span>\';\n            $js  .= <<<JS\n<script>\n$(document).ready(function () {\n    textpattern.Relay.register(\'txpAsyncForm.success\', glzTimePicker);\n\n    function glzTimePicker() {\n        if ($(\".time-picker\").length > 0) {\n            try {\n                $(\"input.time-picker\").timePicker({\n                    startTime: \'{$prefs[\"glz_cf_timepicker_start_time\"]}\',\n                    endTime: \'{$prefs[\"glz_cf_timepicker_end_time\"]}\',\n                    step: {$prefs[\"glz_cf_timepicker_step\"]},\n                    show24Hours: {$prefs[\"glz_cf_timepicker_show_24\"]}\n                });\n                $(\".glz-custom-timepicker .txp-form-field-value\").on(\"click\", function (){\n                    $(this).children(\".time-picker\").trigger(\"click\");\n                });\n            } catch(err) {\n                $(\"#messagepane\").html(\'{$js_timepicker_msg}\');\n            }\n        }\n    }\n\n    glzTimePicker();\n});\n</script>\nJS;\n        }\n    }\n    if ($event == \'glz_custom_fields\') {\n        $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_js_asset_url\']).\'/glz_jqueryui.sortable\'.$min.\'.js\"></script>\';\n    }\n\n    // glz_cf javascript (load from file when $debug is set to true)\n    if ($event != \'prefs\') {\n        if ($debug) {\n            $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_js_asset_url\']).\'/glz_custom_fields\'.$min.\'.js\"></script>\';\n        } else {\n            $js .= glz_custom_fields_head_js();\n        }\n\n    }\n\n    echo $js.n.t.\n        $css.n.t;\n}\n\n\n// -------------------------------------------------------------\n// Install glz_cf tables and prefs\nfunction glz_custom_fields_install()\n{\n    global $prefs;\n    $msg = \'\';\n\n    // Set plugin preferences\n    glz_cf_prefs_install();\n\n    // Change \'html\' key of default custom fields from \'custom_set\'\n    // to \'text_input\' to avoid confusion with glz set_types()\n    safe_update(\'txp_prefs\', \"html = \'text_input\'\", \"event = \'custom\' AND html = \'custom_set\'\");\n\n/*\n    // LEGACY of the old \'<txp:glz_custom_fields_search_form />\' tag?\n    // Create a search section if not already available (for searching by custom fields)\n    if (empty(safe_row(\"name\", \'txp_section\', \"name=\'search\'\"))) {\n\n        // Retrieve skin name used for \'default\' section\n        $current_skin = safe_field(\'skin\', \'txp_section\', \"name=\'default\'\");\n\n        // Add new \'search\' section\n        safe_insert(\'txp_section\', \"\n            name         = \'search\',\n            title        = \'Search\',\n            skin         = \'\".$current_skin.\"\',\n            page         = \'default\',\n            css          = \'default\',\n            description  = \'\',\n            on_frontpage = \'0\',\n            in_rss       = \'0\',\n            searchable   = \'0\'\n        \");\n\n        $msg = gTxt(\'glz_cf_search_section_created\');\n    }\n*/\n    // Create \'custom_fields\' table if it does not already exist\n    safe_create(\n        \'custom_fields\',\n        \"`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n        `name` varchar(255) NOT NULL default \'\',\n        `value` varchar(255) NOT NULL default \'\',\n        PRIMARY KEY (id),\n        KEY (`name`(50))\",\n        \"ENGINE=MyISAM\"\n    );\n\n    // Add an \'id\' column to an existing legacy \'custom_fields\' table\n    if (!getRows(\"SHOW COLUMNS FROM \".safe_pfx(\'custom_fields\').\" LIKE \'id\'\")) {\n        safe_alter(\n            \'custom_fields\',\n            \"ADD `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT KEY\"\n        );\n    }\n\n    // Migrate existing custom_field data to new \'custom_fields\' table\n\n    // Skip if glz_cf migration has already been performed\n    if (isset($prefs[\'glz_cf_migrated\'])) {\n        return;\n    }\n\n    // Skip if \'custom_fields\' table already contains values (don\'t overwrite anything)\n    if (($count = safe_count(\'custom_fields\', \"1 = 1\")) !== false) {\n        // Set flag in \'txp_prefs\' that migration has already been performed\n        set_pref(\"glz_cf_migrated\", \"1\", \"glz_custom_f\", PREF_HIDDEN);\n        $msg = gTxt(\'glz_cf_migration_skip\');\n        return;\n    }\n\n    // Get all custom field sets from prefs\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    // Iterate over all custom_fields and retrieve all values\n    // in custom field columns in textpattern table\n    foreach ($all_custom_sets as $custom => $custom_set) {\n\n        // Check only custom fields that have been set (have a name)\n        if ($custom_set[\'name\']) {\n\n            // Get all existing custom values for ALL articles\n            $all_values = glz_db_get_all_existing_cf_values(\n                glz_custom_number($custom),\n                array(\n                    \'custom_set_name\' => $custom_set[\'name\'],\n                    \'status\' => 0\n                )\n            );\n\n            // If we have results, assemble SQL insert statement to add them to custom_fields table\n            if (count($all_values) > 0) {\n                $insert = \'\';\n                foreach ($all_values as $escaped_value => $value) {\n                    // skip empty values or values > 255 characters (=probably textareas?)\n                    if (!empty($escaped_value) && strlen($escaped_value) < 255) {\n                        $insert .= \"(\'{$custom}\',\'{$escaped_value}\'),\";\n                    }\n                }\n                // Trim final comma and space\n                $insert = rtrim($insert, \', \');\n                $query = \"\n                    INSERT INTO\n                        \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n                    VALUES\n                        {$insert}\n                    \";\n\n                if (isset($query) && !empty($query)) {\n\n                    // Add all custom field values to \'custom_fields\' table\n                    safe_query($query);\n\n                    // Update the type of this custom field to select\n                    // (might want to make this user-adjustable at some point)\n                    safe_update(\n                        \'txp_prefs\',\n                        \"val      = \'\".$custom_set[\'name\'].\"\',\n                         html     = \'select\',\n                         position = \'\".$custom_set[\'position\'].\"\'\",\n                        \"name = \'{$custom}\'\"\n                    );\n                    $msg = gTxt(\'glz_cf_migration_success\');\n                }\n            }\n        }\n    }\n\n    // Set flag in txp_prefs that migration has been performed\n    set_pref(\"glz_cf_migrated\", \"1\", \"glz_custom_f\", PREF_HIDDEN);\n}\n\n\n/**\n * Uninstaller.\n *\n * IMPORTANT: There has been no uninstall function until to now to prevent\n * accidental loss of user input if uninstalling the plugin.\n *\n * This is intended just as an on-demand clean-up script and is hidden\n * behind a \'safety catch\'. In the \'txp_prefs\' table, set the column \'type\'\n * of \'glz_cf_permit_full_deinstall\' to \'1\' to reveal the switch in the\n * preferences panel. The installer sets this to hidden from the beginning.\n *\n */\n\nfunction glz_custom_fields_uninstall()\n{\n    global $prefs;\n\n    // To prevent inadvertent data loss, full deinstallation is only permitted\n    // if the \'safety catch\' has been disabled: set \'glz_cf_permit_full_deinstall\' = 1\n    if ($prefs[\'glz_cf_permit_full_deinstall\'] == \'1\') {\n\n        // Delete \'custom_fields\' table\n        safe_query(\n            \'DROP TABLE IF EXISTS \'.safe_pfx(\'custom_fields\')\n        );\n\n        // Get all custom fields > 10\n        $additional_cfs = safe_rows(\'name\', \'txp_prefs\', \"name LIKE \'custom\\___\\_set\' AND name <> \'custom_10_set\'\");\n\n        $drop_query =\'\';\n        foreach ($additional_cfs as $val) {\n            // Delete prefs labels for custom fields > 10\n            safe_delete(\'txp_lang\', \"name = \'\".$val[\'name\'].\"\'\");\n            // Build DROP query for \'textpattern\' table\n            $drop_query .= \'DROP \'.str_replace(\"_set\", \"\", $val[\'name\']).\', \';\n        }\n        // Trim final comma and space from drop statement\n        $drop_query = rtrim($drop_query, \', \');\n        // Drop used \'custom_X\' > 10 columns from \'textpattern\' table\n        safe_alter(\'textpattern\', $drop_query);\n\n        // Delete all saved language strings\n        safe_delete(\'txp_lang\', \"event = \'glz_cf\' OR name LIKE \'instructions\\_glz\\_cf%\'\");\n\n        // Delete custom field entries > 10 from \'txp_prefs\' (custom_ __ _set = must have two chars in the middle)\n        safe_delete(\'txp_prefs\', \"name LIKE \'custom\\___\\_set\' AND name <> \'custom_10_set\' AND event = \'custom\'\");\n\n        // Delete plugin prefs\n        safe_delete(\'txp_prefs\', \"event LIKE \'glz\\_custom\\_f%\'\");\n\n        // Reset all remaining custom fields (1-10) back to original type \'custom_set\'\n        safe_update(\'txp_prefs\', \"html = \'custom_set\'\", \"event = \'custom\'\");\n\n        // The following also clears the built-in custom fields 1-10\n        // For the \"full whammy\" uncomment these too.\n    /*\n        // Zero custom field user input in the \'textpattern\' table\n        safe_update(\'textpattern\', \"custom_1 = NULL, custom_2 = NULL, custom_3 = NULL, custom_4 = NULL, custom_5 = NULL, custom_6 = NULL, custom_7 = NULL, custom_8 = NULL, custom_9 = NULL, custom_10 = NULL\", \"1 = 1\");\n        // Erase names from \'txp_prefs\' tables\n        safe_update(\'txp_prefs\', \"val = NULL\", \"name LIKE \'custom\\_%%\\_set\'\");\n    */\n        $message = \"‘glz_custom_fields’ has been deinstalled. ALL CUSTOM FIELD USER DATA has also been removed.\";\n\n    } else {\n\n        // Regular deinstall\n\n        // Should we restore the \'html\' type for custom fields 1-10 to \'text_input\'?\n        // Yes: it prevents errors occurring (or is there an automatic fallback)\n        // No:  switching them back loses their settings. The data is kept but the\n        //      custom_field type is then lost in the case of a reinstallation.\n\n        $message = \"‘glz_custom_fields’ has been deinstalled. Your custom field data has NOT been deleted and will reappear if you reinstall ‘glz_custom_fields’.\";\n    }\n\n}\n\n\n// -------------------------------------------------------------\n// Re-route \'Options\' link on Plugins panel to Admin › Preferences\nfunction glz_custom_fields_prefs_redirect()\n{\n    header(\"Location: index.php?event=prefs#prefs_group_glz_custom_f\");\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position router function\nfunction glz_cf_positionsort_steps($event=\'\', $step=\'\', $msg=\'\')\n{\n    switch ($step) {\n    case \'get_js\':\n        glz_cf_positionsort(gps(\'js\'));\n        break;\n    case \'put\':\n        glz_cf_positionsort(gps(\'type\'));\n        break;\n    }\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position inject js\nfunction glz_cf_positionsort_js()\n{\n    echo <<<HTML\n<script src=\"index.php?event=glz_custom_fields&step=get_js\"></script>\nHTML;\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position steps function\nfunction glz_cf_positionsort($js)\n{\n    header(\'Content-Type: text/javascript\');\n\n    if ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\n\n        header(\'Content-Type: application/json\');\n        $success = true;\n        foreach ($_POST as $customfield => $sort) {\n            if (!safe_update(\'txp_prefs\', \'position=\\\'\'.doSlash($sort).\'\\\'\', \'name=\\\'\'.doSlash($customfield).\'\\\'\')) {\n                $success = false;\n            }\n        }\n        echo json_encode(array(\'success\' => $success));\n\n    } else {\n\n        $position = array();\n        foreach (safe_rows(\'name, position\', \'txp_prefs\', \"event = \'custom\'\") as $row) {\n            $customfield = $row[\'name\'];\n            $sort = $row[\'position\'];\n            if (!strlen($sort)) {\n                $sort = 0;\n            }\n            $position[\'glz_\' . $customfield] = ctype_digit($sort) ? (int)$sort : $sort;\n        }\n\n        // Language strings\n        $ui_sort = gTxt(\'glz_cf_col_sort\');\n        $msg_success = gTxt(\'glz_cf_sort_success\');\n        $msg_error = gTxt(\'glz_cf_sort_error\');\n\n        echo \'var position = \', json_encode($position), \';\'.\"\\n\";\n        echo <<<EOB\n$(function() {\n    $(\'#glz_custom_fields_container thead tr\').prepend(\'<th class=\"txp-list-col-sort\">$ui_sort</th>\').find(\'th\').each(function() {\n        var th = $(this);\n        th.html(th.text());\n    });\n    $(\'#glz_custom_fields_container table\').addClass(\'sortable\').find(\'tbody tr\').prepend(\'<td></td>\').appendTo(\'#glz_custom_fields_container tbody\').sortElements(function(a, b) {\n        var a_sort = position[$(a).attr(\'id\')];\n        var b_sort = position[$(b).attr(\'id\')];\n        if (a_sort == b_sort) {\n            return 0;\n        }\n        return a_sort > b_sort ? 1 : -1;\n    }).parent().sortable({\n        items: \'tr\',\n        helper: function(e, ui) {\n            $(\'.ui-sortable\').parent().addClass(\'fixed-width\');\n            ui.children().each(function() {\n                $(this).width($(this).width());\n            });\n            return ui;\n        },\n        axis: \'y\',\n        handle: \'td:first-child\',\n        start: function(event, ui) {\n        },\n        stop: function() {\n            $(\'.ui-sortable\').parent().removeClass(\'fixed-width\');\n            var position = {};\n            $(this).find(\'tr\').each(function() {\n                var tr = $(this);\n                position[tr.attr(\'id\').replace(\'glz_\', \'\')] = tr.index();\n            });\n            var set_message = function(message, type) {\n                $(\'#messagepane\').html(\'<span id=\"message\" class=\"messageflash \' + type + \'\" role=\"alert\" aria-live=\"assertive\">\' + message + \' <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a>\');\n            }\n            $.ajax(\n                \'index.php?event=glz_custom_fields&step=put\', {\n                    type: \'POST\',\n                    data: position,\n                    dataType: \'json\',\n                    success: function(response) {\n                        if (response.success) {\n                            set_message(\'$msg_success\', \'success\')\n                        } else {\n                            this.error();\n                        }\n                    },\n                    error: function() {\n                        set_message(\'$msg_error\', \'error\');\n                    }\n                }\n            );\n        }\n    }).find(\'tr\').find(\'td:first-child\').html(\'&#9776;\');\n});\nEOB;\n\n    }\n    exit();\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   STEPS – List, Save, Edit, Reset, Delete\n#\n##################\n\n\n/**\n * Renders the main custom fields list pane\n *\n * @param  string $msg  Success, error or warning message shown by Textpattern\n * @return string HTML  Table of custom fields\n */\n\nfunction glz_cf_list($msg=\'\', $debug = false)\n{\n    global $event, $step;\n\n    pageTop(gTxt(\'glz_cf_tab_name\'), $msg);\n\n    // Retrieve array of all custom fields properties\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    $out = array();\n\n    $out[] =\n        tag_start(\'div\', array(\'class\' => \'txp-layout\')).\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                hed(gTxt(\'glz_cf_tab_name\'), 1, array(\'class\' => \'txp-heading\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                href(gTxt(\'tab_preferences\'), \'?event=prefs#prefs_group_glz_custom_f\', array(\'class\' => \'glz-cf-setup-switch\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-1col\', \'id\' => $event.\'_container\'));\n\n    // \'Add new custom field\' button\n    $out[] =\n        n.tag(\n            href(\n                gTxt(\'glz_cf_add_new_cf\'),\n                array(\n            \'event\' => \'glz_custom_fields\',\n            \'step\'  => \'add\',\n            \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\' => \'txp-button\',\n                    \'title\' => gTxt(\'glz_cf_add_new_cf\')\n                )\n            ),\n            \'div\',\n            array(\'class\' => \'txp-control-panel\')\n        );\n\n    // Column headings\n    $headers = array(\n        \'position\'  => \'position\',\n        \'id\'        => \'id\',\n        \'name\'      => \'name\',\n        \'title\'     => \'title\',\n        \'type\'      => \'type\',\n        \'options\'   => \'options\'\n    );\n    $head_row = \'\';\n\n    foreach ($headers as $header => $column_head) {\n        $head_row .= column_head(\n            array(\n                \'options\' => array(\'class\' => trim(\'txp-list-col-\'.$header)),\n                \'value\'   => $column_head,\n                \'sort\'    => $header\n            )\n        );\n    }\n\n    // Table start\n    $out[] =\n        tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n        n.tag_start(\'table\', array(\'class\' => \'txp-list--no-options\')).\n        n.tag_start(\'thead\').\n            tr($head_row).\n        n.tag_end(\'thead\').\n        n.tag_start(\'tbody\');\n\n\n    // Table body rows\n    foreach ($all_custom_sets as $custom => $custom_set) {\n\n        // Edit link (with \'name\' and \'id\' as link text)\n        foreach (array(\'name\', \'id\') as $text) {\n            $edit_link[$text] = href(\n                $custom_set[$text],\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'edit\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'edit-link\',\n                    \'title\'       => gTxt(\'glz_cf_action_edit_title\', array(\'{custom_set_name}\' => gTxt(\'glz_cf_title\').\' #\'.glz_custom_digit($custom)))\n                )\n            );\n        }\n\n        // Reset or delete buttons\n        if ($custom_set[\'id\'] < 11) {\n            $delete_link = href(\n                gTxt(\'reset\'),\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'reset\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'ui-icon ui-icon-trash\',\n                    \'title\'       => gTxt(\'reset\'),\n                    \'data-verify\' => gTxt(\'glz_cf_confirm_reset\', array(\'{custom}\' => \'ID# \'.glz_custom_digit($custom).\': \'.htmlspecialchars($custom_set[\'name\']) )),\n                )\n            );\n        } else {\n            $delete_link = href(\n                gTxt(\'delete\'),\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'delete\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'ui-icon ui-icon-close\',\n                    \'title\'       => gTxt(\'delete\'),\n                    \'data-verify\' => gTxt(\'glz_cf_confirm_delete\', array(\'{custom}\' => \'ID# \'.glz_custom_digit($custom).\': \'.htmlspecialchars($custom_set[\'name\']) )),\n                )\n            );\n        }\n\n        $custom_label = (empty($custom_set[\'title\']) ? gTxt(\'undefined\') : $custom_set[\'title\']);\n\n        if (!empty($custom_set[\"name\"])) {\n            $out[] =\n                tr(\n                    hCell(\n                        $custom_set[\'position\'],\n                        \'\',\n                        array(\'class\' => \'txp-list-col-position\')\n                    ).\n                    td(\n                        $edit_link[\'id\'],\n                        \'\',\n                        \'txp-list-col-id\'\n                    ).\n                    td(\n                        $edit_link[\'name\'],\n                        \'\',\n                        \'txp-list-col-name\'\n                    ).\n                    td(\n                        $custom_label.(empty($custom_set[\'instructions\']) ? \'\' : \' <span class=\"cf-instructions ui-icon ui-icon-clipboard\" title=\"\'.$custom_set[\'instructions\'].\'\"></span>\'),\n                        \'\',\n                        \'txp-list-col-title\'.(empty($custom_set[\'title\']) ? \' disabled\' : \'\')\n                    ).\n                    td(\n                        (($custom_set[\'name\']) ? gTxt(\'glz_cf_\'.$custom_set[\'type\']) : \'\'),\n                        \'\',\n                        \'txp-list-col-type\'\n                    ).\n                    td(\n                        $delete_link,\n                        \'\',\n                        \'txp-list-col-options\'\n                    ),\n                    array(\'id\' => \'glz_custom_\'.$custom_set[\'id\'].\'_set\')\n                );\n        }\n    }\n\n    // Table end\n    $out[] =\n        n.tag_end(\'tbody\').\n        n.tag_end(\'table\').\n        n.tag_end(\'div\'). // End of .txp-listtables.\n        pluggable_ui(\'customfields_ui\', \'table_end\', \'\').\n        tag_end(\'div\'). // End of .txp-layout-1col.\n        tag_end(\'div\'); // End of .txp-layout.\n\n    // Render panel\n    if (is_array($out)) {\n        $out = implode(n, $out);\n    }\n    echo $out;\n}\n\n/**\n * Add a new custom field.\n * Finds the next vacant custom field and passes it to the edit form\n *\n * @param  string $msg  Pass-thru of success, error or warning message shown by Textpattern\n */\nfunction glz_cf_add($msg=\'\', $debug = false)\n{\n    // Get next free custom field id\n    $next_free_cf_id = glz_next_empty_custom();\n    // Pass into edit pane\n    glz_cf_edit($msg, $next_free_cf_id);\n}\n\n\n/**\n * Edit a custom field / Add a new custom field.\n * Retrieves values for ID url variable (or id from glz_cf_add)\n *\n * @param  string  $msg  Success, error or warning message shown by Textpattern\n * @param  integer  $id  passed from glz_cf_add\n */\nfunction glz_cf_edit($msg=\'\', $id=\'\', $debug = false)\n{\n    global $event, $step, $prefs;\n    // get ID from URL of $id not supplied (e.g. by \"add\" step)\n    if (empty($id)) {\n        $id = gps(\'ID\');\n    }\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // If editing (not adding), check ID actually exists, else back to list\n    if (($step === \'edit\') && (!get_pref(\'custom_\'.$id.\'_set\'))) {\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    if ($step === \'edit\') {\n        // \'Edit\' Step: retrieve array of all custom field properties\n        $custom_field = glz_db_get_custom_set($id);\n        $panel_title = gTxt(\'glz_cf_action_edit_title\', array(\'{custom_set_name}\' => gTxt(\'glz_cf_title\').\' #\'.$custom_field[\'id\']));\n    } else {\n        // \'Add\' step: set available starting properties, null others\n        $custom_field = array();\n        $custom_field[\'id\'] = $id;\n        $custom_field[\'custom_set\'] = \'custom_\'.$id.\'_set\';\n        foreach (array(\'name\', \'position\', \'type\', \'title\', \'instructions\') as $key) {\n            $custom_field[$key] = null;\n        }\n        $panel_title = gTxt(\'glz_cf_action_new_title\');\n    }\n\n    // Pass existing name in case custom field is renamed\n    $existing_name = ($step === \'edit\') ?\n        hInput(\'custom_set_name_old\', $custom_field[\'name\']) :\n        \'\';\n    // Pass in existing position as hidden input (change position value in the list)\n    $existing_position = (($step === \'edit\') && ($prefs[\'glz_cf_use_sortable\'] == \'1\')) ?\n        hInput(\'custom_set_position\', $custom_field[\'position\']) :\n        \'\';\n\n    // Custom field types drop-down\n    $arr_custom_set_types = glz_custom_set_types();\n    $custom_set_types = null;\n    foreach ($arr_custom_set_types as $custom_type_group => $custom_types) {\n        $custom_set_types .= \'<optgroup label=\"\'.gTxt(\'glz_cf_types_\'.$custom_type_group).\'\">\'.n;\n        foreach ($custom_types as $custom_type) {\n            $selected = ($custom_field[\'type\'] == $custom_type) ?\n                \' selected=\"selected\"\' :\n                null;\n            $custom_set_types .= \'<option value=\"\'.$custom_type.\'\" dir=\"auto\"\'.$selected.\'>\'.gTxt(\'glz_cf_\'.$custom_type).\'</option>\'.n;\n        }\n        $custom_set_types .= \'</optgroup>\'.n;\n    }\n\n    // Fetch (multiple) type values for this custom field\n    if ($step === \'edit\') {\n        if ($custom_field[\'type\'] == \"text_input\") {\n            $arr_values = glz_db_get_all_existing_cf_values(glz_custom_number($custom_field[\'custom_set\']), array(\'custom_set_name\' => $custom_field[\'name\'], \'status\' => 4));\n        } else {\n            $arr_values = glz_db_get_custom_field_values($custom_field[\'custom_set\'], array(\'custom_set_name\' => $custom_field[\'name\']));\n        }\n        $values = ($arr_values) ? implode(\"\\r\\n\", $arr_values) : \'\';\n    } else {\n        $values = \'\';\n    }\n    // This needs to be different for a script\n    if (isset($custom_field[\'type\']) && $custom_field[\'type\'] == \"custom-script\") {\n        $value = fInput(\'text\', \'value\', $values, \'\', \'\', \'\', \'\', \'\', \'value\');\n        $value_instructions = \'glz_cf_js_script_msg\';\n    } else {\n        $value = text_area(\'value\', 0, 0, $values, \'value\');\n        $value_instructions = \'glz_cf_multiple_values_instructions\';\n    }\n\n    $action = ($step === \'edit\') ?\n        fInput(\'submit\', \'save\', gTxt(\'save\'), \'publish\') :\n        fInput(\'submit\', \'add_new\', gTxt(\'glz_cf_add_new_cf\'), \'publish\');\n\n    // Build the form\n    pageTop($panel_title, $msg);\n    // dmp($custom_field);\n\n    $out = array();\n\n    $out[] = hed($panel_title, 2);\n    $out[] =\n        inputLabel(\n                \'custom_set_name\',\n                fInput(\'text\', \'custom_set_name\', htmlspecialchars($custom_field[\'name\']), \'\', \'\', \'\', 28, \'\', \'custom_set_name\'),\n                \'glz_cf_edit_name\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_name_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_title\',\n                fInput(\'text\', \'custom_set_title\', htmlspecialchars($custom_field[\'title\']), \'\', \'\', \'\', INPUT_REGULAR, \'\', \'custom_set_title\'),\n                \'glz_cf_edit_title\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_title_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_instructions\',\n                fInput(\'text\', \'custom_set_instructions\', htmlspecialchars($custom_field[\'instructions\']), \'\', \'\', \'\', INPUT_REGULAR, \'\', \'custom_set_instructions\'),\n                \'glz_cf_edit_instructions\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_instructions_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_type\',\n                \'<select name=\"custom_set_type\" id=\"custom_set_type\">\'.$custom_set_types.\'</select>\',\n                \'glz_cf_edit_type\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_js_configure_msg\'  // Inline help string\n                )\n            ).\n        ($prefs[\'glz_cf_use_sortable\'] == \'0\' ?\n            inputLabel(\n                    \'custom_set_position\',\n                    fInput(\'text\', \'custom_set_position\', htmlspecialchars($custom_field[\'position\']), \'\', \'\', \'\', INPUT_MEDIUM, \'\', \'custom_set_position\'),\n                    \'glz_cf_edit_position\',\n                    array(\n                        0 => \'\',\n                        1 => \'glz_cf_edit_position_hint\'  // Inline help string\n                    )\n                )\n        : \'\').\n        inputLabel(\n                \'custom_set_value\',\n                $value,\n                \'glz_cf_edit_value\',\n                array(\n                    0 => \'\',\n                    1 => $value_instructions  // Inline help string\n                )\n            ).\n        n.tag(gTxt(\'glz_cf_js_script_msg\'), \'span\', array(\'class\' => \'glz-custom-script-msg hidden\')).\n        n.tag(gTxt(\'glz_cf_js_textarea_msg\'), \'span\', array(\'class\' => \'glz-custom-textarea-msg hidden\')).\n        eInput(\'glz_custom_fields\').\n        sInput(\'save\').\n        hInput(\'custom_set\', $custom_field[\'custom_set\']).\n        hInput(\'custom_field_number\', $custom_field[\'id\']).\n        $existing_name.\n        $existing_position.\n        graf(\n            sLink(\'glz_custom_fields\', \'\', gTxt(\'cancel\'), \'txp-button\').\n            $action,\n            array(\'class\' => \'txp-edit-actions\')\n        );\n\n    echo form(join(\'\', $out), \'\', \'\', \'post\', \'txp-edit\', \'\', \'add_edit_custom_field\');\n}\n\n\n/**\n * Saves a new or existing custom field\n * Retrieves incoming $POST variables\n *\n * @param  string  $msg  Success, error or warning message shown by Textpattern\n */\nfunction glz_cf_save($msg=\'\', $debug = false)\n{\n    global $event, $step, $prefs, $msg;\n\n    $in = array_map(\'assert_string\', psa(array(\n        \'custom_set\',\n        \'custom_field_number\',\n        \'custom_set_name\',\n        \'custom_set_name_old\',\n        \'custom_set_title\',\n        \'custom_set_instructions\',\n        \'custom_set_type\',\n        \'custom_set_position\',\n        \'value\',\n        \'save\',\n        \'add_new\'\n    )));\n\n    extract($in);\n\n    // No name given -> error + return to list\n    if (empty($custom_set_name)) {\n        if ($debug) {\n            dmp(\'No name specified\');\n        } // DEBUG info\n        $msg = array(gTxt(\'glz_cf_no_name\'), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // Same name given as another existing custom field -> error + return to list\n    if (glz_check_custom_set_name($custom_set_name, $custom_set)) {\n        if ($debug) {\n            dmp(\'Same name as other custom field specified\');\n        } // DEBUG info\n        // If the sanitized cf name matches an existing custom field, provide an extra hint in the error message\n        $name_sanitized = glz_sanitize_for_cf($custom_set_name);\n        $name_exists_msg = ($custom_set_name <> $name_sanitized) ? $custom_set_name.\' (\'.$name_sanitized.\')\' : $custom_set_name;\n        $msg = array(gTxt(\'glz_cf_exists\', array(\'{custom_set_name}\' => $name_exists_msg)), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // No values specified for checkbox type -> error + return to list\n    if ($custom_set_type == \'checkbox\' && empty($value)) {\n        $msg = array(gTxt(\'glz_cf_no_values\'), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // At lest two values must specified for radiobutton/multiselect type -> error + return to list\n    $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n    if ( ($custom_set_type == \'radio\' || $custom_set_type == \'multi-select\') && (count($cf_values) < 2) ) {\n        $msg = array(gTxt(\'glz_cf_not_enough_values\', array(\'{cf_type}\' => gTxt(\'glz_cf_\'.$custom_set_type))), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    if ($debug) {\n        dmp(\'CF name as input: \'.$custom_set_name);\n    } // DEBUG info\n\n    $create_new_cf = (!empty($add_new)) ? true : false;\n\n    if ($create_new_cf) {\n        // Adding a new custom field\n        if ($debug) {\n            dmp(\'Creating a new custom field\');\n        } // DEBUG info\n\n        // Note the custom field name input by the user\n        $custom_set_name_input = $custom_set_name;\n        // Sanitize custom field name : use strict mode for new custom fields\n        $custom_set_name = glz_sanitize_for_cf($custom_set_name);\n        // Compare: if different -> Raise information notice\n        if ($custom_set_name_input <> $custom_set_name) {\n            $msg = array(gTxt(\'glz_cf_name_renamed_notice\', array(\'{custom_name_input}\' => $custom_set_name_input, \'{custom_name_output}\' => $custom_set_name )), E_WARNING);\n        }\n    } else {\n        // Editing an existing custom field\n        if ($debug) {\n            dmp(\'Updating an existing custom field\');\n        } // DEBUG info\n\n        // Check if custom field name is valid -> Raise warning notice if not\n        glz_is_valid_cf_name($custom_set_name);\n        // Sanitize custom field name : use $lite mode for backwards compatibility\n        $custom_set_name = glz_sanitize_for_cf($custom_set_name, $lite = true);\n    }\n\n    // Use sanitized custom set name\n    $in[\'custom_set_name\'] = $custom_set_name;\n    if ($debug) {\n        dmp(\'CF name cleaned: \'.$custom_set_name);\n    } // DEBUG info\n\n    // If there is no value for \'position\' specified, use the custom field numbers\n    // if using jqueryui.sortable use 999 (the end of the list)\n    if (empty($custom_set_position)) {\n        $in[\'custom_set_position\'] = ($prefs[\'glz_cf_use_sortable\'] == \'1\') ? \'999\' : $custom_field_number;\n    }\n\n    if ($debug) {\n        dmp(\'$in: \'.$in);\n    } // DEBUG info\n\n    // OK, good to go\n\n    if ($create_new_cf) {\n\n        // ACTION! Save new custom field to DB\n        $result = glz_db_cf_new($in, $debug);\n\n        if ($result) {\n            // update lastmod + corresponding event\n            update_lastmod(\n                \'custom_field_created\',\n                compact(\n                    \'custom_set\',\n                    \'custom_field_number\',\n                    \'custom_set_name\',\n                    \'custom_set_title\',\n                    \'custom_set_instructions\',\n                    \'custom_set_type\',\n                    \'custom_set_position\'\n                    )\n                );\n\n            // Success or warning message (if generated earlier by glz_is_valid_cf_name)\n            if (empty($msg)) {\n                $msg = gTxt(\'glz_cf_created\', array(\'{custom_set_name}\' => $custom_set_name));\n            }\n        }\n    } else {\n\n        // ACTION! Update custom field in DB\n        $result = glz_db_cf_save($in, $debug);\n\n        if ($result) {\n            // update lastmod + corresponding event\n            update_lastmod(\n                \'custom_field_updated\',\n                compact(\n                    \'custom_set\',\n                    \'custom_field_number\',\n                    \'custom_set_name\',\n                    \'custom_set_name_old\',\n                    \'custom_set_title\',\n                    \'custom_set_instructions\',\n                    \'custom_set_type\',\n                    \'custom_set_position\'\n                    )\n                );\n            // Success or warning message (if generated earlier by glz_is_valid_cf_name)\n            if (empty($msg)) {\n                $msg = gTxt(\'glz_cf_updated\', array(\'{custom_set_name}\' => $custom_set_name));\n            }\n        }\n    }\n\n    // Render custom field list\n    glz_cf_list($msg);\n}\n\n\n/**\n * Reset step in UI – for custom field IDs 1-10.\n * Retrieves value for ID from url\n *\n * @param  string  $msg  Pass-thru of success / error / warning message\n * @param  bool  $debug  Switch on debug messaging and query dumps\n */\nfunction glz_cf_reset($msg=\'\', $debug = false)\n{\n    global $event, $step;\n\n    // Get ID from URL\n    $id = gps(\'ID\');\n\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        if ($debug) {\n            dmp($id.\' is not an integer\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // Check ID actually exists before resetting, else back to list\n    if (!get_pref(\'custom_\'.$id.\'_set\')) {\n        if ($debug) {\n            dmp(\'custom_\'.$id.\'_set does not exist\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    // ACTION! Reset in DB\n    $result = glz_db_cf_reset($id, $debug);\n\n    if ($result) {\n        update_lastmod(\'custom_field_reset\', $id);\n        $msg = gTxt(\'glz_cf_reset\', array(\'{custom_set_id}\' => \'ID# \'.$id));\n    } else {\n        $msg = array(gTxt(\'glz_cf_reset_error\', array(\'{custom_set_id}\' => \'ID# \'.$id)), E_ERROR);\n    }\n\n    // Render custom field list + message\n    glz_cf_list($msg);\n}\n\n\n/**\n * Delete step in UI – for custom fields ID > 10.\n * Retrieves value for ID from url\n *\n * @param  string  $msg  Success / error / warning message\n * @param  bool  $debug  Switch on debug messaging and query dumps\n */\nfunction glz_cf_delete($msg=\'\', $reset= false, $debug = false)\n{\n    global $event, $step;\n\n    // Get ID from URL\n    $id = gps(\'ID\');\n\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        if ($debug) {\n            dmp($id.\' is not an integer\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // Check ID actually exists before deleting, else back to list\n    if (!get_pref(\'custom_\'.$id.\'_set\')) {\n        if ($debug) {\n            dmp(\'custom_\'.$id.\'_set does not exist\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    // ACTION! Delete from DB (reset for IDs 1-10)\n    $result = glz_db_cf_delete($id, $reset, $debug);\n\n    if ($result) {\n        update_lastmod(\'custom_field_deleted\', $id);\n        $msg = gTxt(\'glz_cf_deleted\', array(\'{custom_set_id}\' => \'ID# \'.$id));\n    } else {\n        $msg = array(gTxt(\'glz_cf_deleted_error\', array(\'{custom_set_id}\' => \'ID# \'.$id)), E_ERROR);\n    }\n\n    // Render custom field list + message\n    glz_cf_list($msg);\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   MAIN – Register plugin privs + callbacks + dispatcher\n#\n##################\n\nglobal  $event, $step, $txp_permissions, $use_minified, $debug;\n\n// DEBUG: Set $debug to true to load css and js from files instead of injecting into head\n$debug = false;\n// DEBUG: Set $use_minified to false to load regular (non-minified) js and css files\n$use_minified = true;\n\nif(@txpinterface == \'admin\') {\n\n    // glz admin panels / events\n    $glz_admin_events = array(\n        \'article\',\n        \'prefs\',\n        \'glz_custom_fields\'\n    );\n\n    // Add prefs privs\n    add_privs(\'prefs.glz_custom_f\', \'1\');\n    add_privs(\'prefs.glz_custom_f.glz_cf_datepicker\', \'1\');\n    add_privs(\'prefs.glz_custom_f.glz_cf_timepicker\', \'1\');\n\n    // Disable regular customs preferences (remove privs)\n    $txp_permissions[\'prefs.custom\'] = \'\';\n\n    // Redirect \'Options\' link on plugins panel to preferences\n    add_privs(\'plugin_prefs.glz_custom_fields\', \'1\');\n    register_callback(\'glz_custom_fields_prefs_redirect\', \'plugin_prefs.glz_custom_fields\');\n\n    // Install plugin\n    register_callback(\'glz_custom_fields_install\', \'plugin_lifecycle.glz_custom_fields\', \'installed\');\n    register_callback(\'glz_custom_fields_uninstall\', \'plugin_lifecycle.glz_custom_fields\', \'deleted\');\n\n    // Restrict css/js + pre-save to relevant admin pages only\n    if (in_array($event, $glz_admin_events)) {\n\n        // Add CSS & JS to admin head area\n        register_callback(\'glz_custom_fields_inject_css_js\', \'admin_side\', \'head_end\');\n\n        // Use jqueryui.sortable to set the custom field position value\n        if ($prefs[\'glz_cf_use_sortable\'] == \'1\') {\n            register_callback(\'glz_cf_positionsort_js\', \'customfields_ui\', \'table_end\');\n            register_callback(\'glz_cf_positionsort_steps\', \'glz_custom_fields\');\n        }\n\n        // Write tab: multiple value array -> string conversion on save/create\n        if (($step === \'edit\') || ($step === \'create\')) {\n            register_callback(\'glz_custom_fields_before_save\', \'article\', \'\', 1);\n        }\n    }\n\n    // Custom fields tab under extensions\n    add_privs(\'glz_custom_fields\', \'1,2\');\n    register_tab(\'extensions\', \'glz_custom_fields\', gTxt(\'glz_cf_tab_name\'));\n    register_callback(\'glz_cf_dispatcher\', \'glz_custom_fields\');\n\n    // Write tab: replace regular custom fields with glz custom fields\n    // -> custom fields\n    register_callback(\'glz_custom_fields_replace\', \'article_ui\', \'custom_fields\');\n    // -> textareas\n    register_callback(\'glz_custom_fields_replace\', \'article_ui\', \'body\');\n\n}\n\n\n/**\n * Jump off to relevant stub for handling actions.\n */\nfunction glz_cf_dispatcher()\n{\n    global $event, $step;\n\n    // Available steps\n    $steps = array(\n        \'add\'    => true,\n        \'edit\'   => true,\n        \'save\'   => true,\n        \'reset\'  => true,\n        \'delete\' => true\n    );\n\n    // Use default step if nothing matches\n    if(!$step || ((!bouncer($step, $steps)) || !isset($steps[$step]))) {\n        $step = \'list\';\n    }\n\n    // Run the function\n    $func = \'glz_cf_\' . $step;\n    $func();\n}\n', '\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   DATABASE FUNCTIONS – Standalone reusable DB functions\n#\n##################\n\n/**\n * Adds a new custom field.\n *\n * @param  array $in Incoming array of form values (see below)\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_save()\n *\n * Expects an incoming array as follows:\n * $in = array(\n *     \'custom_set\',\n *     \'custom_field_number\',\n *     \'custom_set_name\',\n *     \'custom_set_name_old\', **\n *     \'custom_set_title\', **\n *     \'custom_set_instructions\', **\n *     \'custom_set_type\',\n *     \'custom_set_position\',\n *     \'value\', **\n *     \'save\', **\n *     \'add_new\'\n * );\n * where ** are optional\n *\n * Do all content checks / validation / data completion / manipulation\n * before passing the array to this function\n */\n\nfunction glz_db_cf_new($in, $debug = false)\n{\n    // Abort if wrong input format\n    if (!is_array($in)) {\n        if ($debug) { dmp(\'$in: wrong input format\'); }\n        return false;\n    }\n\n    // Extract incoming values\n    extract($in);\n    // Get current UI language\n    $current_lang = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n\n    $ok = false;\n\n    // Insert: table \'txp_prefs\' – custom_field entry\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        $ok = safe_insert(\n            \'txp_prefs\',\n            \"name     = \'{$custom_set}\',\n             val      = \'{$custom_set_name}\',\n             type     = \'1\',\n             event    = \'custom\',\n             html     = \'{$custom_set_type}\',\n             position = \'{$custom_set_position}\'\",\n            $debug\n        );\n    } else {\n        // Custom field IDs 1-10 (update entry)\n        $ok = safe_update(\n            \'txp_prefs\',\n            \"val      = \'{$custom_set_name}\',\n             html     = \'{$custom_set_type}\',\n             position = \'{$custom_set_position}\'\",\n             \"name = \'{$custom_set}\'\",\n            $debug\n        );\n    }\n\n    // Insert: table \'textpattern\' – new \'custom_X\' column\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        // If cf type = textarea, column type + default must be different\n        $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n        $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n        $ok = safe_alter(\n            \'textpattern\',\n            \"ADD custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n            $debug\n        );\n    } else {\n        // Custom field IDs 1-10: only update column type and default as necessary\n        // Update: table \'textpattern\' – column type and default if type changes\n        // If cf type = textarea, column type + default must be different\n        $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n        $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n        $ok = safe_alter(\n            \'textpattern\',\n            \"MODIFY custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n            $debug\n        );\n    }\n\n    // Insert: table \'txp_lang\' – prefspane label\n    // Custom fields IDs 1-10 already exist (in-built)\n    if ($custom_field_number > 10) {\n        $custom_set_preflabel = gTxt(\'custom_x_set\', array(\'{number}\' => $custom_field_number));\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'custom_{$custom_field_number}_set\',\n             event   = \'prefs\',\n             owner   = \'\',\n             data    = \'{$custom_set_preflabel}\',\n             lastmod = now()\",\n            $debug\n        );\n    }\n\n    // Insert: table \'txp_lang\' – custom field label title\n    if (!empty($custom_set_title)) {\n        $custom_set_cf_name = glz_cf_langname($custom_set_name);\n        $custom_set_cf_data = doSlash($custom_set_title);\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'{$custom_set_cf_name}\',\n             event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_cf_data}\',\n             lastmod = now()\",\n            $debug\n        );\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    }\n\n    // Insert: table \'txp_lang\' – custom field instructions\n    if (!empty($custom_set_instructions)) {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = doSlash($custom_set_instructions);\n        $ok = safe_insert(\n            \'txp_lang\',\n            \"lang    = \'{$current_lang}\',\n             name    = \'{$custom_set_instr_name}\',\n             event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_instr_data}\',\n             lastmod = now()\",\n            $debug\n        );\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n    }\n\n    // Insert: table \'custom_fields\' – multiple custom field values\n    // 1. break textarea entries into array removing blanks and duplicates\n    $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n    // 2. fashion insert statement from array\n    if (is_array($cf_values) && !empty($cf_values)) {\n        $insert = \'\';\n        foreach ($cf_values as $key => $value) {\n            // Skip empty values\n            if (!empty($value)) {\n                // Escape special chars before inserting into database\n                $value = doSlash(trim($value));\n                // Build insert row\n                $insert .= \"(\'{$custom_set}\',\'{$value}\'), \";\n            }\n        }\n        // Trim final comma and space from insert statement\n        $insert = rtrim($insert, \', \');\n        // 3. do insert query\n        $ok = safe_query(\"\n            INSERT INTO\n                \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n            VALUES\n                {$insert}\n            \",\n            $debug\n        );\n    }\n\n    // As the table UI doesn\'t include the new strings until a page refresh,\n    // set the language strings in $set_gTxt (if they exist) in the current context\n    // (true = amend/append to existing textpack)\n    if (isset($set_gTxt)) {\n        if ($debug) { dmp(\'$set_gTxt: \'.$set_gTxt); }\n        Txp::get(\'\\Textpattern\\L10n\\Lang\')->setPack($set_gTxt, true);\n    }\n\n    return $ok;\n}\n\n\n/**\n * Saves / updates an existing custom field in the DB.\n *\n * @param  array $in  Incoming array of form values (see below)\n * @param  bool  $debug  Dump query\n * @return bool  FALSE on error\n * @see    glz_db_cf_new()\n *\n * Expects an incoming array as follows:\n * $in = array(\n *     \'custom_set\',\n *     \'custom_field_number\',\n *     \'custom_set_name\',\n *     \'custom_set_name_old\',\n *     \'custom_set_title\', **\n *     \'custom_set_instructions\', **\n *     \'custom_set_type\',\n *     \'custom_set_position\',\n *     \'value\', **\n *     \'save\',\n *     \'add_new\' **\n * );\n * where ** are optional\n *\n * Do all content checks / validation / data completion / manipulation\n * before passing the array to this function\n */\n\nfunction glz_db_cf_save($in, $debug = false)\n{\n    // Abort if wrong input format\n    if (!is_array($in)) {\n        if ($debug) { dmp(\'$in: wrong input format\'); }\n        return false;\n    }\n\n    // Extract incoming values\n    extract($in);\n    // Get current UI language\n    $current_lang = get_pref(\'language_ui\', TEXTPATTERN_DEFAULT_LANG);\n\n    // Has the custom field been renamed\n    $is_cf_renamed = ($custom_set_name <> $custom_set_name_old) ? true : false;\n\n    $ok = false;\n\n    // Update: table \'txp_prefs\' – custom_field entry\n    $ok = safe_update(\n        \'txp_prefs\',\n        \"val      = \'{$custom_set_name}\',\n         html     = \'{$custom_set_type}\',\n         position = \'{$custom_set_position}\'\",\n        \"name     = \'{$custom_set}\'\", // WHERE\n        $debug\n    );\n\n    // Update: table \'textpattern\' – column type and default if type changes\n    // If cf type = textarea, column type + default must be different\n    $column_type = ($custom_set_type == \"textarea\") ? \"TEXT\" : \"VARCHAR(255)\";\n    $dflt =        ($custom_set_type == \"textarea\") ? \'\'     : \"DEFAULT \'\'\";\n    $ok = safe_alter(\n        \'textpattern\',\n        \"MODIFY custom_{$custom_field_number} {$column_type} NOT NULL {$dflt}\",\n        $debug\n    );\n\n    // Update: table \'custom_fields\' – values entries (textarea requires none)\n    // For textareas we do not need to touch custom_fields table\n    if ($custom_set_type != \"textarea\") {\n        safe_delete(\n            \'custom_fields\',\n            \"name =\'{$custom_set}\'\",\n            $debug\n        );\n        // Insert: table \'custom_fields\' – multiple custom field values\n        // 1. break textarea entries into array removing blanks and duplicates\n        $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n        // 2. fashion insert statement from array\n        if (is_array($cf_values) && !empty($cf_values)) {\n            $insert = \'\';\n            foreach ($cf_values as $key => $value) {\n                // Skip empty values\n                if (!empty($value)) {\n                    // Escape special chars before inserting into database\n                    $value = doSlash(trim($value));\n                    // Build insert row\n                    $insert .= \"(\'{$custom_set}\',\'{$value}\'), \";\n                }\n            }\n            // Trim final comma and space from insert statement\n            $insert = rtrim($insert, \', \');\n            // 3. do insert query\n            $ok = safe_query(\"\n                INSERT INTO\n                    \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n                VALUES\n                    {$insert}\n                \",\n                $debug\n            );\n        }\n    } // endif ($custom_set_type != \"textarea\")\n\n    // Update: table \'txp_lang\' – custom field label title\n    $has_cf_title = (!empty($custom_set_title)) ? true : false;\n    $custom_set_cf_langname = glz_cf_langname($custom_set_name);\n\n    if ($is_cf_renamed) {\n        $custom_set_cf_langname_old = glz_cf_langname($custom_set_name_old);\n        // OK, cf is renamed. Do cfnames still match perchance?\n        // such as when renaming spaces/dashes to underscores or uppercase to lowercase\n        if ($custom_set_cf_langname <> $custom_set_cf_langname_old) {\n            // Update name of all custom fields (if cf_langname has actually changed)\n            $custom_set_cf_name_old = $custom_set_cf_langname_old;\n            $custom_set_cf_data_old = null;\n            $ok = safe_update(\n                \'txp_lang\',\n                \"name = \'{$custom_set_cf_langname}\'\",\n                \"name = \'{$custom_set_cf_langname_old}\'\",\n                $debug\n            );\n            $set_gTxt[$custom_set_cf_name_old] = $custom_set_cf_data_old;\n        }\n    }\n\n    if ($has_cf_title) {\n        // A) Custom field title is specified: update or insert\n        $custom_set_cf_name = $custom_set_cf_langname;\n        $custom_set_cf_data = doSlash($custom_set_title);\n        $ok = safe_upsert(\n            \'txp_lang\',\n            \"event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_cf_data}\',\n             lastmod = now()\",\n             array(\'name\' => $custom_set_cf_name, \'lang\' => $current_lang),\n            $debug\n        );\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    } else {\n        // B) Custom field title not specified (or blanked)\n        $custom_set_cf_name = $custom_set_cf_langname;\n        $custom_set_cf_data = null;\n        // Only delete it if it actually exists\n        $gtxt_data = glz_cf_gtxt($custom_set_name);\n        if (!empty($gtxt_data)) {\n            $ok = safe_delete(\n                \'txp_lang\',\n                \"name = \'{$custom_set_cf_name}\' AND lang = \'{$current_lang}\'\",\n                $debug\n            );\n        }\n        $set_gTxt[$custom_set_cf_name] = $custom_set_cf_data;\n    }\n\n    // Update: table \'txp_lang\' – custom field instructions\n\n    // A) If instructions string exists, update existing or insert new entry, or…\n    if (!empty($custom_set_instructions)) {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = doSlash($custom_set_instructions);\n        $ok = safe_upsert(\n            \'txp_lang\',\n            \"event   = \'glz_cf\',\n             owner   = \'glz_cf_labels\',\n             data    = \'{$custom_set_instr_data}\',\n             lastmod = now()\",\n             array(\'name\' => $custom_set_instr_name, \'lang\' => $current_lang),\n            $debug\n        );\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n\n    // B) If instructions string is empty but previously existed, remove old entry\n    } elseif (glz_cf_gtxt(\'\', $custom_field_number) != \'\') {\n        $custom_set_instr_name = \'instructions_custom_\'.$custom_field_number;\n        $custom_set_instr_data = null;\n        // Only delete it if it actually exists\n        $gtxt_data = glz_cf_gtxt(\'\',$custom_field_number);\n        if (!empty($gtxt_data)) {\n            $ok = safe_delete(\n                \'txp_lang\',\n                \"name = \'{$custom_set_instr_name}\' AND lang = \'{$current_lang}\'\",\n                $debug\n            );\n        }\n        $set_gTxt[$custom_set_instr_name] = $custom_set_instr_data;\n    }\n\n    // As the table UI doesn\'t include the new strings until a page refresh,\n    // set the language strings in $set_gTxt (if they exist) in the current context\n    // (true = amend/append to existing textpack)\n    if (isset($set_gTxt)) {\n        if ($debug) { dmp(\'$set_gTxt: \'.$set_gTxt); }\n        Txp::get(\'\\Textpattern\\L10n\\Lang\')->setPack($set_gTxt, true);\n    }\n\n    return $ok;\n}\n\n\n/**\n * Resets a custom field.\n *\n * Passes through to glz_db_cf_delete()\n * with reset flag set to true\n *\n * @param  string $id Custom field ID#\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_delete()\n *\n * Do all checks on $id (if integer / if exists)\n * before passing $id to this function\n */\n\nfunction glz_db_cf_reset($id, $debug = false)\n{\n    return glz_db_cf_delete($id, $reset = true, $debug);\n}\n\n\n/**\n * Deletes or resets a custom field.\n *\n * ID#s 1-10 are always reset, not deleted\n *\n * @param  string $id Custom field ID#\n * @param  bool $reset Reset rather than delete\n * @param  bool $debug Dump query\n * @return bool FALSE on error\n * @see    glz_db_cf_reset()\n *\n * Do all checks on $id (if integer / if exists)\n * before passing $id to this function\n */\n\nfunction glz_db_cf_delete($id, $reset = false, $debug = false)\n{\n    // Custom fields 1-10 are in-built -> only reset\n    if ($id <= 10) {\n        $reset = true;\n    }\n\n    // Retrieve this custom_field values\n    $custom_set = glz_db_get_custom_set($id);\n\n    $ok = false;\n\n    // --- COMMON DELETE AND RESET STEPS\n\n    // Delete: table \'txp_lang\' – custom field label title\n    if (!empty($custom_set[\'title\'])) {\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'\".glz_cf_langname($custom_set[\'name\']).\"\'\",\n            $debug\n        );\n    }\n\n    // Delete: table \'txp_lang\' – custom field instructions\n    if (!empty($custom_set[\'instructions\'])) {\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'instructions_custom_\".$custom_set[\'id\'].\"\'\",\n            $debug\n        );\n    }\n\n    // Delete: table \'custom_fields\' – custom field multiple values settings\n    $cf_row = safe_row(\'*\', \'custom_fields\', \"name = \'\".$custom_set[\'custom_set\'].\"\'\", $debug);\n    if ($cf_row) {\n        $ok = safe_delete(\n            \'custom_fields\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n    }\n\n    if ($reset) {  // --- RESET ONLY STEPS\n\n        // Reset: table \'txp_prefs\' – reset to standard custom field settings\n        $ok = safe_update(\n            \'txp_prefs\',\n            \"val = \'\',\n             html = \'text_input\'\",\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Reset: table \'textpattern\' – empty custom field article data\n        $ok = safe_update(\n            \'textpattern\',\n            \"custom_\".$custom_set[\'id\'].\" = \'\'\",\n            \"1 = 1\",\n            $debug\n        );\n\n        // Reset: table \'textpattern\' – reset custom_X column type to VARCHAR\n        $ok = safe_alter(\n            \'textpattern\',\n            \"MODIFY custom_\".$custom_set[\'id\'].\" VARCHAR(255) NOT NULL DEFAULT \'\'\",\n            $debug\n        );\n\n    } else {  // --- DELETE ONLY STEPS\n\n        // Delete: table \'txp_lang\' – prefspane label\n        $ok = safe_delete(\n            \'txp_lang\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Delete: table \'txp_prefs\' – custom field entry\n        $ok = safe_delete(\n            \'txp_prefs\',\n            \"name = \'\".$custom_set[\'custom_set\'].\"\'\",\n            $debug\n        );\n\n        // Delete: table \'textpattern\' – custom field article data\n        $ok = safe_alter(\n            \'textpattern\',\n            \"DROP `custom_\".$custom_set[\'id\'].\"`\",\n            $debug\n        );\n    }\n\n    return $ok;\n}\n\n\nfunction glz_db_get_all_custom_sets()\n{\n    $all_custom_sets = safe_rows(\n        \"`name` AS custom_set,\n         `val` AS name,\n         `position`,\n         `html` AS type\",\n        \'txp_prefs\',\n        \"`event` = \'custom\' ORDER BY `position`\"\n    );\n\n    foreach ($all_custom_sets as $custom_set) {\n        $custom_set[\'id\'] = glz_custom_digit($custom_set[\'custom_set\']);\n        $custom_set[\'title\'] = glz_cf_gtxt($custom_set[\'name\']);\n        $custom_set[\'instructions\'] = glz_cf_gtxt(\'\', $custom_set[\'id\']);\n\n        $out[$custom_set[\'custom_set\']] = array(\n            \'id\'            => $custom_set[\'id\'],\n            \'name\'          => $custom_set[\'name\'],\n            \'title\'         => $custom_set[\'title\'],\n            \'instructions\'  => $custom_set[\'instructions\'],\n            \'position\'      => $custom_set[\'position\'],\n            \'type\'          => $custom_set[\'type\']\n        );\n\n    }\n    return $out;\n}\n\n\nfunction glz_db_get_custom_set($id)\n{\n    if (!intval($id)) {\n        return false;\n    }\n        $custom_set = safe_row(\n            \"name AS custom_set,\n             val AS name,\n             position,\n             html AS type\",\n            \'txp_prefs\',\n            \"name = \'custom_\".doSlash($id).\"_set\'\"\n        );\n\n        $custom_set[\'id\'] = glz_custom_digit($custom_set[\'custom_set\']);\n        $custom_set[\'title\'] = glz_cf_gtxt($custom_set[\'name\']);\n        $custom_set[\'instructions\'] = glz_cf_gtxt(\'\', $custom_set[\'id\']);\n\n        return $custom_set;\n}\n\n\nfunction glz_db_get_custom_field_values($name, $extra)\n{\n    global $prefs;\n\n    if (is_array($extra)) {\n        extract($extra);\n\n        if (!empty($name)) {\n\n            switch ($prefs[\'glz_cf_values_ordering\']) {\n                case \"ascending\":\n                    $orderby = \"value ASC\";\n                    break;\n                case \"descending\":\n                    $orderby = \"value DESC\";\n                    break;\n                default:\n                    $orderby = \"id ASC\";\n            }\n\n            $arr_values = getThings(\"\n                SELECT\n                    `value`\n                FROM\n                    \".safe_pfx(\'custom_fields\').\"\n                WHERE\n                    `name` = \'{$name}\'\n                ORDER BY\n                    {$orderby}\n            \");\n\n            if (count($arr_values) > 0) {\n                // Decode all special characters e.g. \", & etc. and use them for keys\n                foreach ($arr_values as $key => $value) {\n                    $arr_values_formatted[glz_clean_default(htmlspecialchars($value))] = stripslashes($value);\n                }\n                return $arr_values_formatted;\n            }\n        }\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\nfunction glz_db_get_all_existing_cf_values($name, $extra)\n{\n    if (is_array($extra)) {\n        extract(lAtts(array(\n            \'custom_set_name\'   => \"\",\n            \'status\'            => 4\n        ), $extra));\n\n        // On occasions (e.g. initial migration) we may need to check the custom field values for all articles\n        $status_condition = ($status == 0) ? \"<> \'\'\" : \"= \'$status\'\";\n\n        if (!empty($name)) {\n            $arr_values = getThings(\"\n                SELECT DISTINCT\n                    `$name`\n                FROM\n                    \".safe_pfx(\'textpattern\').\"\n                WHERE\n                    `Status` $status_condition\n                AND\n                    `$name` <> \'\'\n                ORDER BY\n                    `$name`\n            \");\n\n            // Trim all values\n            foreach ($arr_values as $key => $value) {\n                $arr_values[$key] = trim($value);\n            }\n\n            // DEBUG\n            // dmp($arr_values);\n\n            // Temporary string of array values for checking for instances of | and -.\n            $values_check = join(\'::\', $arr_values);\n\n            // DEBUG\n            // dmp($values_check);\n\n            // Are any values multiple ones (=‘|’)?\n            if (strstr($values_check, \'|\')) {\n                // Initialize $out\n                $out = array();\n                // Put all values in an array\n                foreach ($arr_values as $value) {\n                    $arr_values = explode(\'|\', $value);\n                    $out = array_merge($out, $arr_values);\n                }\n                // Keep only the unique ones\n                $out = array_unique($out);\n                // Keys and values need to be the same\n                $out = array_combine($out, $out);\n            } else {\n                // Keys and values need to be the same\n                $out = array_combine($arr_values, $arr_values);\n            }\n\n            // Calling stripslashes on all array values\n            array_map(\'glz_array_stripslashes\', $out);\n\n            return $out;\n        }\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\nfunction glz_db_get_article_custom_fields($name, $extra)\n{\n    if (is_array($extra)) {\n        // See what custom fields we need to query for\n        foreach ($extra as $custom => $custom_set) {\n            $select[] = glz_custom_number($custom);\n        }\n\n        // Prepare the select elements\n        $select = implode(\',\', $select);\n\n        $arr_article_customs = safe_row(\n            $select,\n            \'textpattern\',\n            \"`ID`=\'\".$name.\"\'\"\n        );\n        return $arr_article_customs;\n    } else {\n        trigger_error(gTxt(\'glz_cf_not_specified\', array(\'{what}\' => \"extra attributes\")), E_ERROR);\n    }\n}\n\n\n// -------------------------------------------------------------\n// Goes through all custom sets, returns the first one which is not being used\n// Returns next free id#.\nfunction glz_next_empty_custom()\n{\n    // get current custom fields in \'txp_prefs\' sorted by number\n    // LENGTH = 1-9 first, 10+ afterwards (not 1, 10, 11 ... 2, 3, ...)\n    $rs = safe_rows(\n        \"name, val\",\n        \'txp_prefs\',\n        \"event = \'custom\' ORDER BY LENGTH(name), name\"\n    );\n    $counter = 1;\n    foreach ($rs as $value) {\n        $cf_number = filter_var($value[\'name\'], FILTER_SANITIZE_NUMBER_INT);\n        // Stop at first empty value in IDs 1-10\n        if ($value[\'val\'] == \'\') {\n            $result = $cf_number;\n            break;\n        }\n        // Stop at first empty number > 10\n        if ($cf_number != $counter) {\n            $result = $counter;\n            break;\n        }\n\n        $counter++;\n    }\n    // No empty IDs in 1-10 and no holes > 11 -> get next number\n    if (!isset($result)) {\n        $result = $counter;\n    }\n    return $result;\n}\n\n\n// -------------------------------------------------------------\n// Check if one of the special custom fields exists (e.g. date-picker / time-picker)\nfunction glz_check_custom_set_exists($name)\n{\n    if (!empty($name)) {\n        return safe_field(\"name\", \'txp_prefs\', \"html = \'\".$name.\"\' AND name LIKE \'custom_%\'\");\n    }\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   HELPERS – Helper functions: checks, sanitizers, preps\n#\n##################\n\n\n// -------------------------------------------------------------\n// The types our custom fields can take\nfunction glz_custom_set_types()\n{\n    return array(\n        \'normal\' => array(\n            \'text_input\',\n            \'checkbox\',\n            \'radio\',\n            \'select\',\n            \'multi-select\',\n            \'textarea\'\n        ),\n        \'special\' => array(\n            \'date-picker\',\n            \'time-picker\',\n            \'custom-script\'\n        )\n    );\n}\n\n\n// -------------------------------------------------------------\n// Outputs only custom fields that have been set, i.e. have a name assigned to them\nfunction glz_check_custom_set($all_custom_sets, $step)\n{\n    $out = array();\n    foreach ($all_custom_sets as $key => $custom_field) {\n        if (!empty($custom_field[\'name\'])) {\n            if (($step == \"body\") && ($custom_field[\'type\'] == \"textarea\")) {\n                $out[$key] = $custom_field;\n            } elseif (($step == \"custom_fields\") && ($custom_field[\'type\'] != \"textarea\")) {\n                $out[$key] = $custom_field;\n            }\n        }\n    }\n    return $out;\n}\n\n\n// -------------------------------------------------------------\n// Converts all values into id safe ones [A-Za-z0-9-]\nfunction glz_cf_idname($text)\n{\n    return str_replace(\"_\", \"-\", glz_sanitize_for_cf($text));\n}\n\n\n// -------------------------------------------------------------\n// Converts input into a gTxt-safe lang string \'cf_\' prefix + [a-z0-9_]\nfunction glz_cf_langname($text)\n{\n    return \'cf_\'.glz_sanitize_for_cf($text);\n}\n\n\n// -------------------------------------------------------------\n// Gets translated title or instruction string if one exists\n// @name = custom_field_name\n// @cf_number = $custom_field_number for instructions\n// returns language string or nothing if none exists\nfunction glz_cf_gtxt($name, $cf_number = null)\n{\n    // get language string\n    if (!empty($cf_number)) {\n        // still work if \'custom_X\' or \'custom_X_set\' is passed in as cf_number\n        if (strstr($cf_number, \'custom_\')) {\n            $parts = explode(\"_\", $cf_number);\n            $cf_number = $parts[1];\n        }\n        $cf_name = \'instructions_custom_\'.$cf_number;\n    } else {\n        $cf_name = glz_cf_langname($name);\n    }\n    $cf_gtxt = gTxt($cf_name);\n    // retrieve gTxt value if it exists\n    return ($cf_gtxt != $cf_name) ? $cf_gtxt : \'\';\n}\n\n\n// -------------------------------------------------------------\n// Cleans strings for custom field names and cf_language_names\nfunction glz_sanitize_for_cf($text, $lite = false)\n{\n    $text = trim($text);\n\n    if ($lite) {\n        // lite (legacy)\n        // U&lc letters, numbers, spaces, dashes and underscores\n        return preg_replace(\'/[^A-Za-z0-9\\s\\_\\-]/\', \'\', $text);\n    } else {\n        // strict\n        // lowercase letters, numbers and single underscores; may not start with a number\n        $patterns[0] = \"/[\\_\\s\\-]+/\"; // space(s), dash(es), underscore(s)\n        $replacements[0] = \"_\";\n        $patterns[1] = \"/[^a-z0-9\\_]/\"; // only a-z, 0-9 and underscore\n        $replacements[1] = \"\";\n        $patterns[2] = \"/^\\d+/\"; // numbers at start of string\n        $replacements[2] = \"\";\n\n        return trim(preg_replace($patterns, $replacements, strtolower($text)), \"_\");\n    }\n}\n\n\n// -------------------------------------------------------------\n// Checks if a custom field contains invalid characters, starts with a number or has double underscores\nfunction glz_is_valid_cf_name($text)\n{\n    global $msg;\n\n    if (preg_match(\'/[^a-z0-9\\_]/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_chars\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    } elseif (preg_match(\'/^\\d+/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_starts_with_number\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    } elseif (preg_match(\'/\\_{2,}/\', $text)) {\n        $msg = array(gTxt(\'glz_cf_name_invalid_double_underscores\', array(\'{custom_name_input}\' => $text)), E_WARNING);\n    }\n}\n\n// -------------------------------------------------------------\n// Checks if specified start date matches current date format\nfunction glz_is_valid_start_date($date)\n{\n    global $prefs;\n    $formats = array(\n          \"d/m/Y\" => \"dd/mm/yyyy\",\n          \"m/d/Y\" => \"mm/dd/yyyy\",\n          \"Y-m-d\" => \"yyyy-mm-dd\",\n          \"d m y\" => \"dd mm yy\",\n          \"d.m.Y\" => \"dd.mm.yyyy\"\n    );\n\n    $datepicker_format = array_search($prefs[\'glz_cf_datepicker_format\'], $formats);\n\n    $d = DateTime::createFromFormat($datepicker_format, $date);\n    return $d && $d->format($datepicker_format) == $date;\n}\n\n\n// -------------------------------------------------------------\n// Accommodate relative urls in prefs\n// $addhost = true prepends the hostname\nfunction glz_relative_url($url, $addhost = false)\n{\n    $parsed_url = parse_url($url);\n    if (empty($parsed_url[\'scheme\']) && empty($parsed_url[\'hostname\'])) {\n        if ($addhost) {\n            $hostname = (empty($txpcfg[\'admin_url\']) ? hu : ahu);\n        } else {\n            $hostname = \"/\";\n        }\n        $url = $hostname.ltrim($url, \'/\');\n    }\n    return $url;\n}\n\n\n// -------------------------------------------------------------\n// Removes empty values from arrays - used for new custom fields\nfunction glz_array_empty_values($value)\n{\n    if (!empty($value)) {\n        return $value;\n    }\n}\n\n\n// -------------------------------------------------------------\n// Strips slashes in arrays, used in conjuction with e.g. array_map\nfunction glz_array_stripslashes(&$value)\n{\n    return stripslashes($value);\n}\n\n\n// -------------------------------------------------------------\n// Removes { } from values which are marked as default\nfunction glz_clean_default($value)\n{\n    $pattern = \"/^.*\\{(.*)\\}.*/\";\n    return preg_replace($pattern, \"$1\", $value);\n}\n\n\n// -------------------------------------------------------------\n// Calls glz_clean_default() in an array context\nfunction glz_clean_default_array_values(&$value)\n{\n    $value = glz_clean_default($value);\n}\n\n\n// -------------------------------------------------------------\n// Return our default value from all custom_field values\nfunction glz_default_value($all_values)\n{\n    if (is_array($all_values)) {\n        preg_match(\"/(\\{.*\\})/\", join(\" \", $all_values), $default);\n        return ((!empty($default) && $default[0]) ? $default[0] : \'\');\n    }\n}\n\n\n// -------------------------------------------------------------\n// Custom_set without \"_set\" e.g. custom_1_set => custom_1\n// or custom set formatted for IDs e.g. custom-1\nfunction glz_custom_number($custom_set, $delimiter=\"_\")\n{\n    // Trim \"_set\" from the end of the string\n    $custom_field = substr($custom_set, 0, -4);\n\n    // If a delimeter is specified custom_X to custom{delimeter}X\n    if ($delimiter != \"_\") {\n        $custom_field = str_replace(\"_\", $delimiter, $custom_field);\n    }\n    return $custom_field;\n}\n\n\n// -------------------------------------------------------------\n// Custom_set digit e.g. custom_1_set => 1\nfunction glz_custom_digit($custom_set)\n{\n    $out = explode(\"_\", $custom_set);\n    // $out[0] will always be \'custom\'\n    return $out[1]; // so take $out[1]\n}\n\n\n// -------------------------------------------------------------\n// Returns the custom_X_set from a custom set name e.g. \"rating\" gives us custom_1_set\nfunction glz_get_custom_set($value)\n{\n    $result = safe_field(\n        \"name\",\n        \'txp_prefs\',\n        \"event = \'custom\' AND val = \'\".doSlash(val).\"\'\"\n    );\n    if (!$result) {\n        // No result -> return error message\n        trigger_error(gTxt(\'glz_cf_doesnt_exist\', array(\'{custom_set_name}\' => $value)), E_USER_WARNING);\n        return false;\n    }\n    return true;\n}\n\n\n// -------------------------------------------------------------\n// Get the article ID, even if it\'s newly saved\nfunction glz_get_article_id()\n{\n    return (!empty($GLOBALS[\'ID\']) ? $GLOBALS[\'ID\'] : gps(\'ID\'));\n}\n\n\n// -------------------------------------------------------------\n// Is the custom field name already taken?\nfunction glz_check_custom_set_name($custom_set_name, $custom_set)\n{\n    // Check that the name input by the user as well as its sanitized version don\'t already exist\n    return safe_field(\n        \"name\",\n        \'txp_prefs\',\n        \"event = \'custom\' AND val IN (\'\".doSlash($custom_set_name).\"\', \'\".glz_sanitize_for_cf($custom_set_name).\"\') AND name <> \'\".doSlash($custom_set).\"\'\"\n    );\n}\n\n\n// -------------------------------------------------------------\n// Edit/delete buttons in custom_fields table require a form each\nfunction glz_form_buttons($action, $value, $custom_set, $custom_set_name, $custom_set_type, $custom_set_position, $onsubmit=\'\')\n{\n    $onsubmit = ($onsubmit) ? \'onsubmit=\"\'.$onsubmit.\'\"\' : \'\';\n\n    // ui-icon (see admin hive styling)\n    if ($action == \"delete\") {\n        $ui_icon = \"close\";\n    }\n    if ($action == \"reset\") {\n        $ui_icon = \"trash\";\n    }\n    if ($action == \"add\") {\n        $ui_icon = \"circlesmall-plus\";\n    }\n\n    return\n    \'<form class=\"action-button\" method=\"post\" action=\"index.php\" \'.$onsubmit.\'>\n        <input name=\"custom_set\" value=\"\'.$custom_set.\'\" type=\"hidden\" />\n        <input name=\"custom_set_name\" value=\"\'.$custom_set_name.\'\" type=\"hidden\" />\n        <input name=\"custom_set_type\" value=\"\'.$custom_set_type.\'\" type=\"hidden\" />\n        <input name=\"custom_set_position\" value=\"\'.$custom_set_position.\'\" type=\"hidden\" />\n        <input name=\"event\" value=\"glz_custom_fields\" type=\"hidden\" />\n        <button name=\"\'.$action.\'\" type=\"submit\" value=\"\'.$value.\'\"\n                class=\"jquery-ui-button-icon-left ui-button ui-corner-all ui-widget\">\n            <span class=\"ui-button-icon ui-icon ui-icon-\'.$ui_icon.\'\"></span>\n            <span class=\"ui-button-icon-space\"> </span>\n            \'.gTxt(\"glz_cf_action_\".$action).\'\n        </button>\n    </form>\';\n}\n\n\n// TODO: Appears to be unused?!\n// -------------------------------------------------------------\n// Returns all sections/categories that are searchable\nfunction glz_all_searchable_sections_categories($type)\n{\n    $type = (in_array($type, array(\'category\', \'section\')) ? $type : \'section\');\n    $condition = \"\";\n\n    if ($type == \"section\") {\n        $condition .= \"searchable=\'1\'\";\n    } else {\n        $condition .= \"name <> \'root\' AND type=\'article\'\";\n    }\n\n    $result = safe_rows(\'*\', \"txp_{$type}\", $condition);\n\n    $out = array();\n    foreach ($result as $value) {\n        $out[$value[\'name\']] = $value[\'title\'];\n    }\n\n    return $out;\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   PUBLIC TAGS\n#\n##################\n\n\n/*\n * Adds a title attribute to txp:custom_fields.\n * Set to title=\"1\" to show title in the current language.\n * If no title is available, it returns nothing.\n */\n\n// Divert txp:custom_field calls through glz_custom_field\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'glz_custom_field\', \'custom_field\');\n}\n\nfunction glz_custom_field($atts, $thing = null)\n{\n    // Extract attributes as vars\n    extract(lAtts(array(\n        \'title\' => \'0\',\n        \'name\' => \'\'\n    ), $atts, false));  // false: suppress warnings\n\n    // Unset otherwise non-existent attribute\n    unset($atts[\'title\']);\n\n    // if $title is specified, divert to glz_cf_gtxt\n    return $title ? glz_cf_gtxt($name) : custom_field($atts, $thing);\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   PREFERENCES PANE – Functions for preferences + preferences pane\n#\n##################\n\n\nfunction glz_cf_prefs_install()\n{\n    global $prefs, $txpcfg;\n\n    $position = 200;\n    $base_url = (empty($txpcfg[\'admin_url\'])) ? hu : ahu;\n    $base_path = (empty($txpcfg[\'admin_url\'])) ? $prefs[\'path_to_site\'] : str_replace(\"public\", \"admin\", $prefs[\'path_to_site\']);\n\n    // array: old_prefname => array(\'pref.subevent\', \'html\', \'default-value\')\n    $plugin_prefs = array(\n        \'values_ordering\'        => array(\'\', \'glz_prefs_orderby\', \'custom\'),\n        \'multiselect_size\'       => array(\'\', \'glz_text_input_small\', \'5\'),\n        \'css_asset_url\'          => array(\'\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields\'),\n        \'js_asset_url\'           => array(\'\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields\'),\n        \'custom_scripts_path\'    => array(\'\', \'glz_url_input\', $base_path.\'/plugins/glz_custom_fields\'),\n        \'use_sortable\'           => array(\'\', \'yesnoradio\', 1),\n        \'permit_full_deinstall\'  => array(\'\', \'yesnoradio\', 0),\n        \'datepicker_url\'         => array(\'glz_cf_datepicker\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields/jquery.datePicker\'),\n        \'datepicker_format\'      => array(\'glz_cf_datepicker\', \'glz_prefs_datepicker_format\', \'dd/mm/yyyy\'),\n        \'datepicker_first_day\'   => array(\'glz_cf_datepicker\', \'glz_prefs_datepicker_firstday\', 1),\n        \'datepicker_start_date\'  => array(\'glz_cf_datepicker\', \'glz_input_start_date\', \'01/01/2018\'),\n        \'timepicker_url\'         => array(\'glz_cf_timepicker\', \'glz_url_input\', $base_url.\'plugins/glz_custom_fields/jquery.timePicker\'),\n        \'timepicker_start_time\'  => array(\'glz_cf_timepicker\', \'glz_text_input_small\', \'00:00\'),\n        \'timepicker_end_time\'    => array(\'glz_cf_timepicker\', \'glz_text_input_small\', \'23:30\'),\n        \'timepicker_step\'        => array(\'glz_cf_timepicker\', \'glz_text_input_small\', 30),\n        \'timepicker_show_24\'     => array(\'glz_cf_timepicker\', \'glz_prefs_timepicker_format\', true)\n    );\n\n    foreach ($plugin_prefs as $name => $val) {\n        if (get_pref($name, false) === false) {\n            // If pref is new, create new pref with \'glz_cf_\' prefix\n            create_pref(\'glz_cf_\'.$name, $val[2], \'glz_custom_f\'.($val[0] ? \'.\'.$val[0] : \'\'), PREF_PLUGIN, $val[1], $position, \'\');\n        } else {\n            // If pref exists, add \'glz_cf_\' prefix to name, reassign position and html type and set to type PREF_PLUGIN\n            safe_update(\n                \'txp_prefs\',\n                \"name = \'glz_cf_\".$name.\"\',\n                 event = \'glz_custom_f\".($val[0] ? \".\".$val[0] : \"\").\"\',\n                 html = \'\".$val[1].\"\',\n                 type = \".PREF_PLUGIN.\",\n                 position = \".$position,\n                \"name = \'\".$name.\"\'\"\n            );\n        }\n        $position++;\n    }\n\n    // Make some $prefs hidden (for safety and troubleshooting)\n    foreach (array(\n        \'use_sortable\',\n        \'permit_full_deinstall\'\n    ) as $name) {\n        safe_update(\n            \'txp_prefs\',\n            \"type = \".PREF_HIDDEN,\n            \"name = \'glz_cf_\".$name.\"\'\"\n        );\n    }\n\n    // Set \'migrated\' pref to \'glz_cf_migrated\' and to hidden (type = 2);\n    if (get_pref(\'migrated\')) {\n        safe_update(\n            \'txp_prefs\',\n            \"name = \'glz_cf_migrated\',\n             type = \".PREF_HIDDEN,\n            \"name = \'migrated\'\"\n        );\n    }\n\n    // Remove no longer needed \'max_custom_fields\' pref\n    safe_delete(\n        \'txp_prefs\',\n        \"name = \'max_custom_fields\'\"\n    );\n}\n\n\n/**\n * Renders a HTML choice of GLZ value ordering.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_orderby($name, $val)\n{\n    $vals = array(\n        \'ascending\'   => gTxt(\'glz_cf_prefs_value_asc\'),\n        \'descending\'  => gTxt(\'glz_cf_prefs_value_desc\'),\n        \'custom\'      => gTxt(\'glz_cf_prefs_value_custom\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of date formats.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_datepicker_format($name, $val)\n{\n    $vals = array(\n        \"dd/mm/yyyy\"  => gTxt(\'glz_cf_prefs_slash_dmyy\'),\n        \"mm/dd/yyyy\"  => gTxt(\'glz_cf_prefs_slash_mdyy\'),\n        \"yyyy-mm-dd\"  => gTxt(\'glz_cf_prefs_dash_yymd\'),\n        \"dd mm yy\"    => gTxt(\'glz_cf_prefs_space_dmy\'),\n        \"dd.mm.yyyy\"  => gTxt(\'glz_cf_prefs_dot_dmyy\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of weekdays.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_datepicker_firstday($name, $val)\n{\n    $vals = array(\n        0             => gTxt(\'glz_cf_prefs_sunday\'),\n        1             => gTxt(\'glz_cf_prefs_monday\'),\n        2             => gTxt(\'glz_cf_prefs_tuesday\'),\n        3             => gTxt(\'glz_cf_prefs_wednesday\'),\n        4             => gTxt(\'glz_cf_prefs_thursday\'),\n        5             => gTxt(\'glz_cf_prefs_friday\'),\n        6             => gTxt(\'glz_cf_prefs_saturday\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n/**\n * Renders a HTML choice of time formats.\n *\n * @param  string $name HTML name and id of the widget\n * @param  string $val  Initial (or current) selected item\n * @return string HTML\n */\nfunction glz_prefs_timepicker_format($name, $val)\n{\n    $vals = array(\n        \'true\'        => gTxt(\'glz_cf_prefs_24_hours\'),\n        \'false\'       => gTxt(\'glz_cf_prefs_12_hours\')\n    );\n    return selectInput($name, $vals, $val, \'\', \'\', $name);\n}\n\n\n/**\n * Renders a small-width HTML &lt;input&gt; element.\n * Checks if start date matches current datepicker date format\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_input_start_date($name, $val)\n{\n    $out = text_input($name, $val, INPUT_SMALL);\n    // Output error notice if start date does not match date format\n    if (!glz_is_valid_start_date($val)) {\n        $out .= \'<br><span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.gTxt(\'glz_cf_datepicker_start_date_error\').\'</span>\';\n    }\n    return $out;\n}\n\n\n/**\n * Renders a medium-width HTML &lt;input&gt; element.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_text_input_medium($name, $val)\n{\n    return text_input($name, $val, INPUT_MEDIUM);\n}\n\n/**\n * Renders a small-width HTML &lt;input&gt; element.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_text_input_small($name, $val)\n{\n    return text_input($name, $val, INPUT_SMALL);\n}\n\n/**\n * Renders a regular-width HTML &lt;input&gt; element for an URL with path check.\n *\n * @param  string $name HTML name and id of the text box\n * @param  string $val  Initial (or current) content of the text box\n * @return string HTML\n */\nfunction glz_url_input($name, $val)\n{\n    global $use_minified;\n    $min = ($use_minified === true) ? \'.min\' : \'\';\n    $check_paths = (gps(\'check_paths\') == \"1\") ? true : false;\n\n    // Output regular-width text_input for url\n    $out  = fInput(\'text\', $name, $val, \'\', \'\', \'\', INPUT_REGULAR, \'\', $name);\n\n    // Array of possible expected url inputs and corresponding files and error-msg-stubs\n    // \'pref_name\' => array(\'/targetfilename.ext\', \'gTxt_folder (inserted into error msg)\')\n    // paths do not require a target filename, urls do.\n    $glz_cf_url_inputs = array(\n        \'glz_cf_css_asset_url\'       => array(\'/glz_custom_fields\'.$min.\'.css\', \'glz_cf_css_folder\'),\n        \'glz_cf_js_asset_url\'        => array(\'/glz_custom_fields\'.$min.\'.js\',  \'glz_cf_js_folder\'),\n        \'glz_cf_datepicker_url\'      => array(\'/datePicker\'.$min.\'.js\',         \'glz_cf_datepicker_folder\'),\n        \'glz_cf_timepicker_url\'      => array(\'/timePicker\'.$min.\'.js\',         \'glz_cf_timepicker_folder\'),\n        \'glz_cf_custom_scripts_path\' => array(\'\',                               \'glz_cf_custom_folder\')\n    );\n    // File url or path to test = prefs_val (=url/path) + targetfilename (first item in array)\n    $glz_cf_url_to_test          = $val.$glz_cf_url_inputs[$name][0];\n    // gTxt string ref for folder name for error message (second item in array)\n    $glz_cf_url_input_error_stub = $glz_cf_url_inputs[$name][1];\n\n    // See if url / path is readable. If not, produce error message\n    if ($glz_cf_url_to_test && $check_paths == true) {\n        // permit relative URLs but conduct url test with hostname\n        if (strstr($name, \'url\')) {\n            $glz_cf_url_to_test = glz_relative_url($glz_cf_url_to_test, $addhost = true);\n        }\n        $url_error = (@fopen($glz_cf_url_to_test, \"r\")) ? \'\' : gTxt(\'glz_cf_folder_error\', array(\'{folder}\' => gTxt($glz_cf_url_input_error_stub) ));\n\n        // Output error notice if one exists, else success notice\n        $out .= (!empty($url_error)) ?\n            \'<br><span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.$url_error.\'</span>\' :\n            \'<br><span class=\"success\"><span class=\"ui-icon ui-icon-check\"></span> \'.gTxt(\'glz_cf_folder_success\').\'</span>\';\n    }\n\n    return $out;\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   FIELD TYPES – Rendering of custom fields on \'Write\' pane\n#\n##################\n\n\n// -------------------------------------------------------------\n// Formats the custom set output based on its type\nfunction glz_format_custom_set_by_type($custom, $custom_id, $custom_set_type, $arr_custom_field_values, $custom_value = \"\", $default_value = \"\")\n{\n    if (is_array($arr_custom_field_values)) {\n        $arr_custom_field_values = array_map(\'glz_array_stripslashes\', $arr_custom_field_values);\n    }\n\n    switch ($custom_set_type) {\n        // These are the normal custom fields\n        case \"text_input\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'\'\n            );\n\n        case \"select\":\n            return array(\n                glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-select\'\n            );\n\n        case \"multi-select\":\n            return array(\n                glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value, 1),\n                \'glz-custom-multiselect\'\n            );\n\n        case \"checkbox\":\n            return array(\n                glz_checkbox($custom, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-checkbox\'\n            );\n\n        case \"radio\":\n            return array(\n                glz_radio($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),\n                \'glz-custom-radio\'\n            );\n\n        case \"textarea\":\n            return array(\n                text_area($custom, 0, 0, $custom_value, $custom_id),\n                \'glz-custom-textarea\'\n            );\n\n        // Here start the special custom fields, might need to refactor the return, starting to repeat itself\n        case \"date-picker\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit date-picker\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'glz-custom-datepicker\'\n            );\n\n        case \"time-picker\":\n            return array(\n                fInput(\"text\", $custom, $custom_value, \"edit time-picker\", \"\", \"\", \"22\", \"\", $custom_id),\n                \'glz-custom-timepicker\'\n            );\n\n        case \"custom-script\":\n            global $custom_scripts_path;\n            return array(\n                glz_custom_script($custom_scripts_path.\"/\".reset($arr_custom_field_values), $custom, $custom_id, $custom_value),\n                \'glz-custom-script\'\n            );\n\n        // A type has been passed that is not supported yet\n        default:\n            return array(\n                gTxt(\'glz_cf_type_not_supported\'),\n                \'glz-custom-unknown\'\n            );\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default selectInput() because trimming \\t and \\n didn\'t work + some other mods & multi-select\nfunction glz_selectInput($name = \'\', $id = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\', $multi = \'\')\n{\n    if (is_array($arr_values)) {\n        global $prefs;\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $selected = glz_selected_checked(\'selected\', $key, $custom_value, $default_value);\n            $out[] = \"<option value=\\\"$key\\\"{$selected}>$value</option>\";\n        }\n\n        // We\'ll need the extra attributes as well as a name that will produce an array\n        if ($multi) {\n            $multi = \' multiple=\"multiple\" size=\"\'.$prefs[\'glz_cf_multiselect_size\'].\'\"\';\n            $name .= \"[]\";\n        }\n\n        return \"<select id=\\\"\".glz_cf_idname($id).\"\\\" name=\\\"$name\\\" class=\\\"list\\\"$multi>\".\n      ($default_value ? \'\' : \"<option value=\\\"\\\"$selected>&nbsp;</option>\").\n      ($out ? join(\'\', $out) : \'\').\n      \"</select>\";\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default checkbox() to keep the looping in here and check against existing value/s\nfunction glz_checkbox($name = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\')\n{\n    if (is_array($arr_values)) {\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $checked = glz_selected_checked(\'checked\', $key, $custom_value);\n\n            $out[] = \"<div class=\\\"txp-form-checkbox glz-cf-\".str_replace(\"_\", \"-\", glz_cf_idname($key)).\"\\\"><input type=\\\"checkbox\\\" name=\\\"{$name}[]\\\" value=\\\"$key\\\" class=\\\"checkbox\\\" id=\\\"\".glz_cf_idname($key).\"\\\"{$checked} /> <label for=\\\"\".glz_cf_idname($key).\"\\\">$value</label></div>\";\n        }\n\n        return join(\'\', $out);\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Had to duplicate the default radio() to keep the looping in here and check against existing value/s\nfunction glz_radio($name = \'\', $id = \'\', $arr_values = \'\', $custom_value = \'\', $default_value = \'\')\n{\n    if (is_array($arr_values)) {\n        $out = array();\n\n        // If there is no custom_value coming from the article, let\'s use our default one\n        if (empty($custom_value)) {\n            $custom_value = $default_value;\n        }\n\n        foreach ($arr_values as $key => $value) {\n            $checked = glz_selected_checked(\'checked\', $key, $custom_value);\n            $default = ($default_value == $key) ? \' \'.\'default\' : \'\';\n\n            $out[] = \"<div class=\\\"txp-form-radio glz-cf-\".str_replace(\"_\", \"-\", glz_cf_idname($key)).\"\\\"><input type=\\\"radio\\\" name=\\\"$name\\\" value=\\\"$key\\\" class=\\\"radio{$default}\\\" id=\\\"{$id}_\".glz_cf_idname($key).\"\\\"{$checked} /> <label for=\\\"{$id}_\".glz_cf_idname($key).\"\\\">$value</label></div>\";\n        }\n\n        return join(\'\', $out);\n    } else {\n        return gTxt(\'glz_cf_field_problems\', array(\'{custom_set_name}\' => $name));\n    }\n}\n\n\n// -------------------------------------------------------------\n// Checking if this custom field has selected or checked values\nfunction glz_selected_checked($range_unit, $value, $custom_value = \'\')\n{\n    // We\'re comparing against a key which is a \"clean\" value\n    $custom_value = htmlspecialchars($custom_value);\n\n    // Make an array if $custom_value contains multiple values\n    if (strpos($custom_value, \'|\')) {\n        $arr_custom_value = explode(\'|\', $custom_value);\n    }\n\n    if (isset($arr_custom_value)) {\n        $out = (in_array($value, $arr_custom_value)) ? \" $range_unit=\\\"$range_unit\\\"\" : \"\";\n    } else {\n        $out = ($value == $custom_value) ? \" $range_unit=\\\"$range_unit\\\"\" : \"\";\n    }\n\n    return $out;\n}\n\n\n//-------------------------------------------------------------\n// Button gets more consistent styling across browsers rather than input type=\"submit\"\n// included in this plugin until in makes it into TXP - if that ever happens...\nfunction glz_fButton($type, $name, $contents=\'Submit\', $value, $class=\'\', $id=\'\', $title=\'\', $onClick=\'\', $disabled = false)\n{\n    $o  = \'<button type=\"\'.$type.\'\" name=\"\'.$name.\'\"\';\n    $o .= \' value=\"\'.htmlspecialchars($value).\'\"\';\n    $o .= ($class)    ? \' class=\"\'.$class.\'\"\' : \'\';\n    $o .= ($id)       ? \' id=\"\'.$id.\'\"\' : \'\';\n    $o .= ($title)    ? \' title=\"\'.$title.\'\"\' : \'\';\n    $o .= ($onClick)  ? \' onclick=\"\'.$onClick.\'\"\' : \'\';\n    $o .= ($disabled) ? \' disabled=\"disabled\"\' : \'\';\n    $o .= \'>\';\n    $o .= $contents;\n    $o .= \'</button>\';\n    return $o;\n}\n\n\n//-------------------------------------------------------------\n// Evals a PHP script and displays output right under the custom field label\nfunction glz_custom_script($script, $custom, $custom_id, $custom_value)\n{\n    global $prefs;\n    if (is_file($prefs[\'glz_cf_custom_scripts_path\'].$script)) {\n        include_once($prefs[\'glz_cf_custom_scripts_path\'].$script);\n        $custom_function = basename($script, \".php\");\n        if (is_callable($custom_function)) {\n            return call_user_func_array($custom_function, array($custom, $custom_id, $custom_value));\n        } else {\n            return gTxt(\'glz_cf_not_callable\', array(\'{function}\' => $custom_function, \'{file}\' => $script));\n        }\n    } else {\n        return \'<span class=\"error\"><span class=\"ui-icon ui-icon-alert\"></span> \'.gTxt(\'glz_cf_not_found\', array(\'{file}\' => $prefs[\'glz_cf_custom_scripts_path\'].$script)).\'</span>\';\n    }\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   CSS + JSS – For injecting into admin-side <head> area\n#\n##################\n\n\n// -------------------------------------------------------------\n// Contains minified output of glz_custom_fields.css file\n// To update: copy the minified output of the actual css file into the function\nfunction glz_custom_fields_head_css()\n{\n    $css = <<<\'CSS\'\n.glz-cf-setup-switch{float:right}[dir=rtl] .glz-cf-setup-switch{float:left}#glz_custom_fields_container .txp-list-col-id{width:3em;text-align:center}#glz_custom_fields_container .txp-list-col-options,#glz_custom_fields_container .txp-list-col-position{width:5em}#glz_custom_fields_container .txp-list-col-title .cf-instructions.ui-icon{width:2em;height:17px;float:right;background-repeat:no-repeat;background-position:center 2px;opacity:.33;cursor:pointer}#glz_custom_fields_container .txp-list-col-title.disabled .cf-instructions{opacity:1!important;pointer-events:auto}#glz_custom_fields_container .txp-list-col-options{text-align:center}#glz_custom_fields_container .txp-list-col-options .ui-icon{width:4em;background-repeat:no-repeat;background-position:50%}#glz_custom_fields_container .txp-list-col-options .ui-icon:hover{-webkit-filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%);filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%)}#glz_custom_fields_container table.fixed-width{table-layout:fixed}#glz_custom_fields_container table.sortable .txp-list-col-sort{width:3em;text-align:center}#glz_custom_fields_container table.sortable .ui-sortable-handle{cursor:row-resize;text-align:center;opacity:.66}#glz_custom_fields_container table.sortable .txp-list-col-position{display:none}#glz_custom_fields_container .ui-sortable-helper,#glz_custom_fields_container .ui-sortable-placeholder{display:table}#add_edit_custom_field .hidden{display:none}@media screen and (min-width:47em){.txp-edit .txp-form-field .txp-form-field-instructions,.txp-tabs-vertical-group .txp-form-field-instructions{max-width:50%;padding-left:50%}}.check-path{float:right;font-size:.7em;font-weight:400}[dir=rtl] .check-path{float:left}.ui-tabs-nav .check-path{display:none}#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:none}.glz-custom-field-reset.disabled:hover{text-decoration:none}.glz-custom-field-reset.disabled{cursor:default}.glz-custom-checkbox .txp-form-field-value label,.glz-custom-radio .txp-form-field-value label{cursor:pointer}\nCSS;\n\n    return \'<style>\'.n.$css.n.\'</style>\';\n}\n\n\n// -------------------------------------------------------------\n// Contains minified output of glz_custom_fields.css file\n// To update copy the minified output of the actual js file into the function\nfunction glz_custom_fields_head_js()\n{\n    $js = <<<\'JS\'\n$(function(){function e(){$(\".glz-custom-radio\").length>0&&$(\".glz-custom-radio\").each(function(){var e=$(this).find(\"input:first\").attr(\"name\");$(this).find(\"label:first\").after(\' <span class=\"small\"><a href=\"#\" class=\"glz-custom-field-reset\" name=\"\'+e+\'\">Reset</a></span>\'),$(\"input:radio[name=\"+e+\"]\").is(\":checked\")||$(\".glz-custom-field-reset[name=\"+e+\"]\").addClass(\"disabled\")})}function t(){$glz_value_field.find(\"textarea#value\").length&&(s.textarea_value=$glz_value_field.find(\"textarea#value\").html(),$glz_value_field.find(\"textarea#value\").remove()),$glz_value_field.find(\"input#value\").length?0==$glz_value_field.find(\"input#value\").prop(\"disabled\")&&(s.path_value=$glz_value_field.find(\"input#value\").attr(\"value\")):$glz_value_field.find(\".txp-form-field-value\").prepend(\'<input type=\"text\" id=\"value\" name=\"value\" />\'),$glz_value_field.find(\"input#value\").attr(\"value\",\"-----\").prop(\"disabled\",!0),$glz_value_instructions.html(\"\")}function l(){$glz_value_field.find(\"input#value\").length&&(0==$glz_value_field.find(\"input#value\").prop(\"disabled\")&&(s.path_value=$glz_value_field.find(\"input#value\").attr(\"value\")),$glz_value_field.find(\"input#value\").remove(),$glz_value_instructions.html(\"\")),$glz_value_field.find(\"textarea#value\").length||$(\".edit-custom-set-value .txp-form-field-value\").prepend(\'<textarea id=\"value\" name=\"value\" rows=\"5\"></textarea>\'),s.textarea_value&&$glz_value_field.find(\"textarea#value\").html(s.textarea_value),$glz_value_instructions.html(s.messages.textarea)}function a(){$glz_value_field.find(\"textarea#value\").length&&(s.textarea_value=$glz_value_field.find(\"textarea#value\").html(),$glz_value_field.find(\"textarea#value\").remove(),$glz_value_instructions.html(\"\")),$glz_value_field.find(\"input#value\").length||$glz_value_field.find(\".txp-form-field-value\").prepend(\'<input type=\"text\" id=\"value\" name=\"value\" size=\"32\" />\'),\"-----\"==$glz_value_field.find(\"input#value\").attr(\"value\")&&$glz_value_field.find(\"input#value\").attr(\"value\",\"\"),$glz_value_field.find(\"input#value\").prop(\"disabled\",!1),$glz_value_instructions.html(s.messages.customscriptpath),s.path_value&&$glz_value_field.find(\"input#value\").attr(\"value\",s.path_value)}function i(){-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,[\"multi-select\",\"custom-script\"]))?$glz_select_instructions.html(\'<a href=\"//\'+window.location.host+window.location.pathname+\'?event=prefs#prefs_group_glz_custom_f\">\'+s.messages.configure+\"</a>\"):$glz_select_instructions.html(\"\")}textpattern.Relay.register(\"txpAsyncForm.success\",e);var s;s={},s.special_custom_types=[\"date-picker\",\"time-picker\"],s.no_value_custom_types=[\"text_input\",\"textarea\"],$glz_value_field=$(\".edit-custom-set-value\"),$glz_value_instructions=$glz_value_field.find(\".txp-form-field-instructions\"),$glz_select_instructions=$(\".edit-custom-set-type\").find(\".txp-form-field-instructions\"),s.messages={textarea:$(\".glz-custom-textarea-msg\").html(),configure:$glz_select_instructions.text(),customscriptpath:$(\".glz-custom-script-msg\").text()},$(\".glz-custom-script-msg\").remove(),$(\".glz-custom-textarea-msg\").remove(),i(),-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():\"custom-script\"==$(\"select#custom_set_type :selected\").attr(\"value\")&&a(),$(\"select#custom_set_type\").change(function(){i(),-1!=$.inArray($(\"select#custom_set_type :selected\").attr(\"value\"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():\"custom-script\"==$(\"select#custom_set_type :selected\").attr(\"value\")?a():l()}),e(),$(\".txp-layout\").on(\"click\",\".glz-custom-field-reset\",function(){if($(this).hasClass(\"disabled\"))return!1;var e=$(this).attr(\"name\");return $(\"input[name=\"+e+\"]\").prop(\"checked\",!1),$(\"input[name=\"+e+\"].default\").prop(\"checked\",!0),$(this).addClass(\"disabled\"),0===$(this).siblings(\".txp-form-radio-reset\").length&&0===$(\"input[name=\"+e+\"]:checked\").length&&$(this).after(\'<input type=\"hidden\" class=\"txp-form-radio-reset\" value=\"\" name=\"\'+e+\'\" />\'),!1}),$(\".txp-layout\").on(\"click\",\".glz-custom-radio .radio\",function(){var e=$(this).attr(\"name\");$this_reset_button=$(\".glz-custom-field-reset[name=\"+e+\"]\"),$this_reset_button.hasClass(\"disabled\")&&($(\"input[type=hidden][name=\"+e+\"]\").remove(),$this_reset_button.removeClass(\"disabled\"))})});\nJS;\n    return \'<script>\'.n.$js.n.\'</script>\';\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   CALLBACKS – Callback functions called from main.php\n#\n##################\n\n\n// -------------------------------------------------------------\n// Replaces the default custom fields on the \'Write\' panel\nfunction glz_custom_fields_replace($event, $step, $data, $rs)\n{\n    // Get all custom field sets from prefs\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    // Filter all custom fields & keep only those that are set for that render step\n    $arr_custom_fields = glz_check_custom_set($all_custom_sets, $step);\n\n    // DEBUG\n    // dmp($arr_custom_fields);\n\n    $out = \' \';\n\n    if (is_array($arr_custom_fields) && !empty($arr_custom_fields)) {\n        // Get all custom fields values for this article\n        $arr_article_customs = glz_db_get_article_custom_fields(glz_get_article_id(), $arr_custom_fields);\n\n        // DEBUG\n        // dmp($arr_article_customs);\n\n        if (is_array($arr_article_customs)) {\n            extract($arr_article_customs);\n        }\n\n        // Which custom fields are set\n        foreach ($arr_custom_fields as $custom => $custom_set) {\n            // Get all possible/default value(s) for this custom set from custom_fields table\n            $arr_custom_field_values = glz_db_get_custom_field_values($custom, array(\'custom_set_name\' => $custom_set[\'name\']));\n\n            // DEBUG\n            // dmp($arr_custom_field_values);\n\n            // Custom_set formatted for id e.g. custom_1_set => custom-1 - don\'t ask...\n            $custom_id = glz_custom_number($custom, \"-\");\n            // custom_set without \"_set\" e.g. custom_1_set => custom_1\n            $custom = glz_custom_number($custom);\n\n            // If current article holds no value for this custom field and we have no default value, make it empty\n            // (not using empty() as it also eradicates values of \'0\')\n            $custom_value = ((isset($$custom) && trim($$custom) <> \'\') ? $$custom : \'\');\n            // DEBUG\n            // dmp(\"custom_value: {$custom_value}\");\n\n            // Check if there is a default value\n            // if there is, strip the { }\n            $default_value = glz_clean_default(glz_default_value($arr_custom_field_values));\n            // DEBUG\n            // dmp(\"default_value: {$default_value}\");\n\n            // Now that we\'ve found our default, we need to clean our custom_field values\n            if (is_array($arr_custom_field_values)) {\n                array_walk($arr_custom_field_values, \"glz_clean_default_array_values\");\n            }\n\n            // DEBUG\n            // dmp($arr_custom_field_values);\n\n            // The way our custom field value is going to look like\n            list($custom_set_value, $custom_class) = glz_format_custom_set_by_type($custom, $custom_id, $custom_set[\'type\'], $arr_custom_field_values, $custom_value, $default_value);\n\n            // DEBUG\n            // dmp($custom_set_value);\n\n            // cf_lang string (define this in your language to create a field label)\n            $cf_lang = glz_cf_langname($custom_set[\"name\"]);\n            // Get the (localised) label if one exists, otherwise the regular name (as before)\n            $cf_label = (gTxt($cf_lang) != $cf_lang) ? gTxt($cf_lang) : $custom_set[\"name\"];\n\n            $out .= inputLabel(\n                $custom_id,\n                $custom_set_value,\n                $cf_label,\n                array(\'\', \'instructions_\'.$custom),\n                array(\'class\' => \'txp-form-field custom-field glz-cf \'.$custom_class.\' \'.$custom_id.\' cf-\'.glz_cf_idname(str_replace(\'_\', \'-\', $custom_set[\"name\"])))\n            );\n        }\n    }\n\n    // DEBUG\n    // dmp($out);\n\n    // If we\'re writing textarea custom fields, we need to include the excerpt as well\n    if ($step == \"body\") {\n        $out = $data.$out;\n    }\n\n    return $out;\n}\n\n\n// -------------------------------------------------------------\n// Prep custom fields values for db (convert multiple values into a string e.g. multi-selects, checkboxes & radios)\nfunction glz_custom_fields_before_save()\n{\n    // Iterate over POST vars\n    foreach ($_POST as $key => $value) {\n        // Extract custom_{} keys with multiple values as arrays\n        if (strstr($key, \'custom_\') && is_array($value)) {\n            // Convert to delimited string …\n            $value = implode($value, \'|\');\n            // and feed back into $_POST\n            $_POST[$key] = $value;\n        }\n    }\n\n    // DEBUG\n    // dmp($_POST);\n}\n\n\n// -------------------------------------------------------------\n// Inject css & js into admin head\nfunction glz_custom_fields_inject_css_js($debug = false)\n{\n    global $event, $prefs, $use_minified, $debug;\n\n    $msg = array();\n    $min = ($use_minified) ? \'.min\' : \'\';\n\n    // do we have a date-picker or time-picker custom field\n    $date_picker = glz_check_custom_set_exists(\"date-picker\");\n    $time_picker = glz_check_custom_set_exists(\"time-picker\");\n\n    // glz_cf stylesheets (load from file when $debug is set to true)\n    if ($debug) {\n        $css = \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_css_asset_url\']).\'/glz_custom_fields\'.$min.\'.css\">\'.n;\n        // Show hidden fields\n        $css .= \'<style>#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:flex}</style>\';\n    } else {\n        $css = glz_custom_fields_head_css();\n    }\n    // glz_cf javascript\n    $js = \'\';\n\n    if ($event == \'article\') {\n        // If a date picker field exists\n        if ($date_picker) {\n            $css .= \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_datepicker_url\']).\'/datePicker\'.$min.\'.css\" />\'.n;\n            foreach (array(\'date\'.$min.\'.js\', \'datePicker\'.$min.\'.js\') as $file) {\n                $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_datepicker_url\']).\"/\".$file.\'\"></script>\'.n;\n            }\n            $js_datepicker_msg = \'<span class=\"messageflash error\" role=\"alert\" aria-live=\"assertive\"><span class=\"ui-icon ui-icon-alert\"></span> <a href=\"\'.ahu.\'index.php?event=prefs&check_url=1#prefs_group_glz_custom_f\">\'.gTxt(\'glz_cf_public_error_datepicker\').\'</a> <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a></span>\';\n            $js .= <<<JS\n<script>\n$(document).ready(function () {\n    textpattern.Relay.register(\'txpAsyncForm.success\', glzDatePicker);\n\n    function glzDatePicker() {\n        if ($(\"input.date-picker\").length > 0) {\n            try {\n                Date.firstDayOfWeek = {$prefs[\'glz_cf_datepicker_first_day\']};\n                Date.format = \'{$prefs[\"glz_cf_datepicker_format\"]}\';\n                Date.fullYearStart = \'19\';\n                $(\".date-picker\").datePicker({startDate:\'{$prefs[\"glz_cf_datepicker_start_date\"]}\'});\n                $(\".date-picker\").dpSetOffset(29, -1);\n            } catch(err) {\n                $(\'#messagepane\').html(\'{$js_datepicker_msg}\');\n            }\n        }\n    }\n\n    glzDatePicker();\n});\n</script>\nJS;\n        }\n\n        // If a time picker field exists\n        if ($time_picker) {\n            $css .= \'<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"\'.glz_relative_url($prefs[\'glz_cf_timepicker_url\']).\'/timePicker\'.$min.\'.css\" />\'.n;\n            $js  .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_timepicker_url\']).\'/timePicker\'.$min.\'.js\"></script>\'.n;\n            $js_timepicker_msg = \'<span class=\"messageflash error\" role=\"alert\" aria-live=\"assertive\"><span class=\"ui-icon ui-icon-alert\"></span> <a href=\"\'.ahu.\'index.php?event=prefs&check_url=1#prefs_group_glz_custom_f\">\'.gTxt(\'glz_cf_public_error_timepicker\').\'</a> <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a></span>\';\n            $js  .= <<<JS\n<script>\n$(document).ready(function () {\n    textpattern.Relay.register(\'txpAsyncForm.success\', glzTimePicker);\n\n    function glzTimePicker() {\n        if ($(\".time-picker\").length > 0) {\n            try {\n                $(\"input.time-picker\").timePicker({\n                    startTime: \'{$prefs[\"glz_cf_timepicker_start_time\"]}\',\n                    endTime: \'{$prefs[\"glz_cf_timepicker_end_time\"]}\',\n                    step: {$prefs[\"glz_cf_timepicker_step\"]},\n                    show24Hours: {$prefs[\"glz_cf_timepicker_show_24\"]}\n                });\n                $(\".glz-custom-timepicker .txp-form-field-value\").on(\"click\", function (){\n                    $(this).children(\".time-picker\").trigger(\"click\");\n                });\n            } catch(err) {\n                $(\"#messagepane\").html(\'{$js_timepicker_msg}\');\n            }\n        }\n    }\n\n    glzTimePicker();\n});\n</script>\nJS;\n        }\n    }\n    if ($event == \'glz_custom_fields\') {\n        $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_js_asset_url\']).\'/glz_jqueryui.sortable\'.$min.\'.js\"></script>\';\n    }\n\n    // glz_cf javascript (load from file when $debug is set to true)\n    if ($event != \'prefs\') {\n        if ($debug) {\n            $js .= \'<script src=\"\'.glz_relative_url($prefs[\'glz_cf_js_asset_url\']).\'/glz_custom_fields\'.$min.\'.js\"></script>\';\n        } else {\n            $js .= glz_custom_fields_head_js();\n        }\n\n    }\n\n    echo $js.n.t.\n        $css.n.t;\n}\n\n\n// -------------------------------------------------------------\n// Install glz_cf tables and prefs\nfunction glz_custom_fields_install()\n{\n    global $prefs;\n    $msg = \'\';\n\n    // Set plugin preferences\n    glz_cf_prefs_install();\n\n    // Change \'html\' key of default custom fields from \'custom_set\'\n    // to \'text_input\' to avoid confusion with glz set_types()\n    safe_update(\'txp_prefs\', \"html = \'text_input\'\", \"event = \'custom\' AND html = \'custom_set\'\");\n\n/*\n    // LEGACY of the old \'<txp:glz_custom_fields_search_form />\' tag?\n    // Create a search section if not already available (for searching by custom fields)\n    if (empty(safe_row(\"name\", \'txp_section\', \"name=\'search\'\"))) {\n\n        // Retrieve skin name used for \'default\' section\n        $current_skin = safe_field(\'skin\', \'txp_section\', \"name=\'default\'\");\n\n        // Add new \'search\' section\n        safe_insert(\'txp_section\', \"\n            name         = \'search\',\n            title        = \'Search\',\n            skin         = \'\".$current_skin.\"\',\n            page         = \'default\',\n            css          = \'default\',\n            description  = \'\',\n            on_frontpage = \'0\',\n            in_rss       = \'0\',\n            searchable   = \'0\'\n        \");\n\n        $msg = gTxt(\'glz_cf_search_section_created\');\n    }\n*/\n    // Create \'custom_fields\' table if it does not already exist\n    safe_create(\n        \'custom_fields\',\n        \"`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n        `name` varchar(255) NOT NULL default \'\',\n        `value` varchar(255) NOT NULL default \'\',\n        PRIMARY KEY (id),\n        KEY (`name`(50))\",\n        \"ENGINE=MyISAM\"\n    );\n\n    // Add an \'id\' column to an existing legacy \'custom_fields\' table\n    if (!getRows(\"SHOW COLUMNS FROM \".safe_pfx(\'custom_fields\').\" LIKE \'id\'\")) {\n        safe_alter(\n            \'custom_fields\',\n            \"ADD `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT KEY\"\n        );\n    }\n\n    // Migrate existing custom_field data to new \'custom_fields\' table\n\n    // Skip if glz_cf migration has already been performed\n    if (isset($prefs[\'glz_cf_migrated\'])) {\n        return;\n    }\n\n    // Skip if \'custom_fields\' table already contains values (don\'t overwrite anything)\n    if (($count = safe_count(\'custom_fields\', \"1 = 1\")) !== false) {\n        // Set flag in \'txp_prefs\' that migration has already been performed\n        set_pref(\"glz_cf_migrated\", \"1\", \"glz_custom_f\", PREF_HIDDEN);\n        $msg = gTxt(\'glz_cf_migration_skip\');\n        return;\n    }\n\n    // Get all custom field sets from prefs\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    // Iterate over all custom_fields and retrieve all values\n    // in custom field columns in textpattern table\n    foreach ($all_custom_sets as $custom => $custom_set) {\n\n        // Check only custom fields that have been set (have a name)\n        if ($custom_set[\'name\']) {\n\n            // Get all existing custom values for ALL articles\n            $all_values = glz_db_get_all_existing_cf_values(\n                glz_custom_number($custom),\n                array(\n                    \'custom_set_name\' => $custom_set[\'name\'],\n                    \'status\' => 0\n                )\n            );\n\n            // If we have results, assemble SQL insert statement to add them to custom_fields table\n            if (count($all_values) > 0) {\n                $insert = \'\';\n                foreach ($all_values as $escaped_value => $value) {\n                    // skip empty values or values > 255 characters (=probably textareas?)\n                    if (!empty($escaped_value) && strlen($escaped_value) < 255) {\n                        $insert .= \"(\'{$custom}\',\'{$escaped_value}\'),\";\n                    }\n                }\n                // Trim final comma and space\n                $insert = rtrim($insert, \', \');\n                $query = \"\n                    INSERT INTO\n                        \".safe_pfx(\'custom_fields\').\" (`name`,`value`)\n                    VALUES\n                        {$insert}\n                    \";\n\n                if (isset($query) && !empty($query)) {\n\n                    // Add all custom field values to \'custom_fields\' table\n                    safe_query($query);\n\n                    // Update the type of this custom field to select\n                    // (might want to make this user-adjustable at some point)\n                    safe_update(\n                        \'txp_prefs\',\n                        \"val      = \'\".$custom_set[\'name\'].\"\',\n                         html     = \'select\',\n                         position = \'\".$custom_set[\'position\'].\"\'\",\n                        \"name = \'{$custom}\'\"\n                    );\n                    $msg = gTxt(\'glz_cf_migration_success\');\n                }\n            }\n        }\n    }\n\n    // Set flag in txp_prefs that migration has been performed\n    set_pref(\"glz_cf_migrated\", \"1\", \"glz_custom_f\", PREF_HIDDEN);\n}\n\n\n/**\n * Uninstaller.\n *\n * IMPORTANT: There has been no uninstall function until to now to prevent\n * accidental loss of user input if uninstalling the plugin.\n *\n * This is intended just as an on-demand clean-up script and is hidden\n * behind a \'safety catch\'. In the \'txp_prefs\' table, set the column \'type\'\n * of \'glz_cf_permit_full_deinstall\' to \'1\' to reveal the switch in the\n * preferences panel. The installer sets this to hidden from the beginning.\n *\n */\n\nfunction glz_custom_fields_uninstall()\n{\n    global $prefs;\n\n    // To prevent inadvertent data loss, full deinstallation is only permitted\n    // if the \'safety catch\' has been disabled: set \'glz_cf_permit_full_deinstall\' = 1\n    if ($prefs[\'glz_cf_permit_full_deinstall\'] == \'1\') {\n\n        // Delete \'custom_fields\' table\n        safe_query(\n            \'DROP TABLE IF EXISTS \'.safe_pfx(\'custom_fields\')\n        );\n\n        // Get all custom fields > 10\n        $additional_cfs = safe_rows(\'name\', \'txp_prefs\', \"name LIKE \'custom\\___\\_set\' AND name <> \'custom_10_set\'\");\n\n        $drop_query =\'\';\n        foreach ($additional_cfs as $val) {\n            // Delete prefs labels for custom fields > 10\n            safe_delete(\'txp_lang\', \"name = \'\".$val[\'name\'].\"\'\");\n            // Build DROP query for \'textpattern\' table\n            $drop_query .= \'DROP \'.str_replace(\"_set\", \"\", $val[\'name\']).\', \';\n        }\n        // Trim final comma and space from drop statement\n        $drop_query = rtrim($drop_query, \', \');\n        // Drop used \'custom_X\' > 10 columns from \'textpattern\' table\n        safe_alter(\'textpattern\', $drop_query);\n\n        // Delete all saved language strings\n        safe_delete(\'txp_lang\', \"event = \'glz_cf\' OR name LIKE \'instructions\\_glz\\_cf%\'\");\n\n        // Delete custom field entries > 10 from \'txp_prefs\' (custom_ __ _set = must have two chars in the middle)\n        safe_delete(\'txp_prefs\', \"name LIKE \'custom\\___\\_set\' AND name <> \'custom_10_set\' AND event = \'custom\'\");\n\n        // Delete plugin prefs\n        safe_delete(\'txp_prefs\', \"event LIKE \'glz\\_custom\\_f%\'\");\n\n        // Reset all remaining custom fields (1-10) back to original type \'custom_set\'\n        safe_update(\'txp_prefs\', \"html = \'custom_set\'\", \"event = \'custom\'\");\n\n        // The following also clears the built-in custom fields 1-10\n        // For the \"full whammy\" uncomment these too.\n    /*\n        // Zero custom field user input in the \'textpattern\' table\n        safe_update(\'textpattern\', \"custom_1 = NULL, custom_2 = NULL, custom_3 = NULL, custom_4 = NULL, custom_5 = NULL, custom_6 = NULL, custom_7 = NULL, custom_8 = NULL, custom_9 = NULL, custom_10 = NULL\", \"1 = 1\");\n        // Erase names from \'txp_prefs\' tables\n        safe_update(\'txp_prefs\', \"val = NULL\", \"name LIKE \'custom\\_%%\\_set\'\");\n    */\n        $message = \"‘glz_custom_fields’ has been deinstalled. ALL CUSTOM FIELD USER DATA has also been removed.\";\n\n    } else {\n\n        // Regular deinstall\n\n        // Should we restore the \'html\' type for custom fields 1-10 to \'text_input\'?\n        // Yes: it prevents errors occurring (or is there an automatic fallback)\n        // No:  switching them back loses their settings. The data is kept but the\n        //      custom_field type is then lost in the case of a reinstallation.\n\n        $message = \"‘glz_custom_fields’ has been deinstalled. Your custom field data has NOT been deleted and will reappear if you reinstall ‘glz_custom_fields’.\";\n    }\n\n}\n\n\n// -------------------------------------------------------------\n// Re-route \'Options\' link on Plugins panel to Admin › Preferences\nfunction glz_custom_fields_prefs_redirect()\n{\n    header(\"Location: index.php?event=prefs#prefs_group_glz_custom_f\");\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position router function\nfunction glz_cf_positionsort_steps($event=\'\', $step=\'\', $msg=\'\')\n{\n    switch ($step) {\n    case \'get_js\':\n        glz_cf_positionsort(gps(\'js\'));\n        break;\n    case \'put\':\n        glz_cf_positionsort(gps(\'type\'));\n        break;\n    }\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position inject js\nfunction glz_cf_positionsort_js()\n{\n    echo <<<HTML\n<script src=\"index.php?event=glz_custom_fields&step=get_js\"></script>\nHTML;\n}\n\n\n// -------------------------------------------------------------\n// Custom field sortable position steps function\nfunction glz_cf_positionsort($js)\n{\n    header(\'Content-Type: text/javascript\');\n\n    if ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {\n\n        header(\'Content-Type: application/json\');\n        $success = true;\n        foreach ($_POST as $customfield => $sort) {\n            if (!safe_update(\'txp_prefs\', \'position=\\\'\'.doSlash($sort).\'\\\'\', \'name=\\\'\'.doSlash($customfield).\'\\\'\')) {\n                $success = false;\n            }\n        }\n        echo json_encode(array(\'success\' => $success));\n\n    } else {\n\n        $position = array();\n        foreach (safe_rows(\'name, position\', \'txp_prefs\', \"event = \'custom\'\") as $row) {\n            $customfield = $row[\'name\'];\n            $sort = $row[\'position\'];\n            if (!strlen($sort)) {\n                $sort = 0;\n            }\n            $position[\'glz_\' . $customfield] = ctype_digit($sort) ? (int)$sort : $sort;\n        }\n\n        // Language strings\n        $ui_sort = gTxt(\'glz_cf_col_sort\');\n        $msg_success = gTxt(\'glz_cf_sort_success\');\n        $msg_error = gTxt(\'glz_cf_sort_error\');\n\n        echo \'var position = \', json_encode($position), \';\'.\"\\n\";\n        echo <<<EOB\n$(function() {\n    $(\'#glz_custom_fields_container thead tr\').prepend(\'<th class=\"txp-list-col-sort\">$ui_sort</th>\').find(\'th\').each(function() {\n        var th = $(this);\n        th.html(th.text());\n    });\n    $(\'#glz_custom_fields_container table\').addClass(\'sortable\').find(\'tbody tr\').prepend(\'<td></td>\').appendTo(\'#glz_custom_fields_container tbody\').sortElements(function(a, b) {\n        var a_sort = position[$(a).attr(\'id\')];\n        var b_sort = position[$(b).attr(\'id\')];\n        if (a_sort == b_sort) {\n            return 0;\n        }\n        return a_sort > b_sort ? 1 : -1;\n    }).parent().sortable({\n        items: \'tr\',\n        helper: function(e, ui) {\n            $(\'.ui-sortable\').parent().addClass(\'fixed-width\');\n            ui.children().each(function() {\n                $(this).width($(this).width());\n            });\n            return ui;\n        },\n        axis: \'y\',\n        handle: \'td:first-child\',\n        start: function(event, ui) {\n        },\n        stop: function() {\n            $(\'.ui-sortable\').parent().removeClass(\'fixed-width\');\n            var position = {};\n            $(this).find(\'tr\').each(function() {\n                var tr = $(this);\n                position[tr.attr(\'id\').replace(\'glz_\', \'\')] = tr.index();\n            });\n            var set_message = function(message, type) {\n                $(\'#messagepane\').html(\'<span id=\"message\" class=\"messageflash \' + type + \'\" role=\"alert\" aria-live=\"assertive\">\' + message + \' <a class=\"close\" role=\"button\" title=\"Close\" href=\"#close\"><span class=\"ui-icon ui-icon-close\">Close</span></a>\');\n            }\n            $.ajax(\n                \'index.php?event=glz_custom_fields&step=put\', {\n                    type: \'POST\',\n                    data: position,\n                    dataType: \'json\',\n                    success: function(response) {\n                        if (response.success) {\n                            set_message(\'$msg_success\', \'success\')\n                        } else {\n                            this.error();\n                        }\n                    },\n                    error: function() {\n                        set_message(\'$msg_error\', \'error\');\n                    }\n                }\n            );\n        }\n    }).find(\'tr\').find(\'td:first-child\').html(\'&#9776;\');\n});\nEOB;\n\n    }\n    exit();\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   STEPS – List, Save, Edit, Reset, Delete\n#\n##################\n\n\n/**\n * Renders the main custom fields list pane\n *\n * @param  string $msg  Success, error or warning message shown by Textpattern\n * @return string HTML  Table of custom fields\n */\n\nfunction glz_cf_list($msg=\'\', $debug = false)\n{\n    global $event, $step;\n\n    pageTop(gTxt(\'glz_cf_tab_name\'), $msg);\n\n    // Retrieve array of all custom fields properties\n    $all_custom_sets = glz_db_get_all_custom_sets();\n\n    $out = array();\n\n    $out[] =\n        tag_start(\'div\', array(\'class\' => \'txp-layout\')).\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                hed(gTxt(\'glz_cf_tab_name\'), 1, array(\'class\' => \'txp-heading\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-2col\')).\n                href(gTxt(\'tab_preferences\'), \'?event=prefs#prefs_group_glz_custom_f\', array(\'class\' => \'glz-cf-setup-switch\')).\n            tag_end(\'div\').\n            tag_start(\'div\', array(\'class\' => \'txp-layout-1col\', \'id\' => $event.\'_container\'));\n\n    // \'Add new custom field\' button\n    $out[] =\n        n.tag(\n            href(\n                gTxt(\'glz_cf_add_new_cf\'),\n                array(\n            \'event\' => \'glz_custom_fields\',\n            \'step\'  => \'add\',\n            \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\' => \'txp-button\',\n                    \'title\' => gTxt(\'glz_cf_add_new_cf\')\n                )\n            ),\n            \'div\',\n            array(\'class\' => \'txp-control-panel\')\n        );\n\n    // Column headings\n    $headers = array(\n        \'position\'  => \'position\',\n        \'id\'        => \'id\',\n        \'name\'      => \'name\',\n        \'title\'     => \'title\',\n        \'type\'      => \'type\',\n        \'options\'   => \'options\'\n    );\n    $head_row = \'\';\n\n    foreach ($headers as $header => $column_head) {\n        $head_row .= column_head(\n            array(\n                \'options\' => array(\'class\' => trim(\'txp-list-col-\'.$header)),\n                \'value\'   => $column_head,\n                \'sort\'    => $header\n            )\n        );\n    }\n\n    // Table start\n    $out[] =\n        tag_start(\'div\', array(\'class\' => \'txp-listtables\')).\n        n.tag_start(\'table\', array(\'class\' => \'txp-list--no-options\')).\n        n.tag_start(\'thead\').\n            tr($head_row).\n        n.tag_end(\'thead\').\n        n.tag_start(\'tbody\');\n\n\n    // Table body rows\n    foreach ($all_custom_sets as $custom => $custom_set) {\n\n        // Edit link (with \'name\' and \'id\' as link text)\n        foreach (array(\'name\', \'id\') as $text) {\n            $edit_link[$text] = href(\n                $custom_set[$text],\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'edit\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'edit-link\',\n                    \'title\'       => gTxt(\'glz_cf_action_edit_title\', array(\'{custom_set_name}\' => gTxt(\'glz_cf_title\').\' #\'.glz_custom_digit($custom)))\n                )\n            );\n        }\n\n        // Reset or delete buttons\n        if ($custom_set[\'id\'] < 11) {\n            $delete_link = href(\n                gTxt(\'reset\'),\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'reset\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'ui-icon ui-icon-trash\',\n                    \'title\'       => gTxt(\'reset\'),\n                    \'data-verify\' => gTxt(\'glz_cf_confirm_reset\', array(\'{custom}\' => \'ID# \'.glz_custom_digit($custom).\': \'.htmlspecialchars($custom_set[\'name\']) )),\n                )\n            );\n        } else {\n            $delete_link = href(\n                gTxt(\'delete\'),\n                array(\n                    \'event\'      => \'glz_custom_fields\',\n                    \'step\'       => \'delete\',\n                    \'ID\'         => $custom_set[\'id\'],\n                    \'_txp_token\' => form_token(),\n                ),\n                array(\n                    \'class\'       => \'ui-icon ui-icon-close\',\n                    \'title\'       => gTxt(\'delete\'),\n                    \'data-verify\' => gTxt(\'glz_cf_confirm_delete\', array(\'{custom}\' => \'ID# \'.glz_custom_digit($custom).\': \'.htmlspecialchars($custom_set[\'name\']) )),\n                )\n            );\n        }\n\n        $custom_label = (empty($custom_set[\'title\']) ? gTxt(\'undefined\') : $custom_set[\'title\']);\n\n        if (!empty($custom_set[\"name\"])) {\n            $out[] =\n                tr(\n                    hCell(\n                        $custom_set[\'position\'],\n                        \'\',\n                        array(\'class\' => \'txp-list-col-position\')\n                    ).\n                    td(\n                        $edit_link[\'id\'],\n                        \'\',\n                        \'txp-list-col-id\'\n                    ).\n                    td(\n                        $edit_link[\'name\'],\n                        \'\',\n                        \'txp-list-col-name\'\n                    ).\n                    td(\n                        $custom_label.(empty($custom_set[\'instructions\']) ? \'\' : \' <span class=\"cf-instructions ui-icon ui-icon-clipboard\" title=\"\'.$custom_set[\'instructions\'].\'\"></span>\'),\n                        \'\',\n                        \'txp-list-col-title\'.(empty($custom_set[\'title\']) ? \' disabled\' : \'\')\n                    ).\n                    td(\n                        (($custom_set[\'name\']) ? gTxt(\'glz_cf_\'.$custom_set[\'type\']) : \'\'),\n                        \'\',\n                        \'txp-list-col-type\'\n                    ).\n                    td(\n                        $delete_link,\n                        \'\',\n                        \'txp-list-col-options\'\n                    ),\n                    array(\'id\' => \'glz_custom_\'.$custom_set[\'id\'].\'_set\')\n                );\n        }\n    }\n\n    // Table end\n    $out[] =\n        n.tag_end(\'tbody\').\n        n.tag_end(\'table\').\n        n.tag_end(\'div\'). // End of .txp-listtables.\n        pluggable_ui(\'customfields_ui\', \'table_end\', \'\').\n        tag_end(\'div\'). // End of .txp-layout-1col.\n        tag_end(\'div\'); // End of .txp-layout.\n\n    // Render panel\n    if (is_array($out)) {\n        $out = implode(n, $out);\n    }\n    echo $out;\n}\n\n/**\n * Add a new custom field.\n * Finds the next vacant custom field and passes it to the edit form\n *\n * @param  string $msg  Pass-thru of success, error or warning message shown by Textpattern\n */\nfunction glz_cf_add($msg=\'\', $debug = false)\n{\n    // Get next free custom field id\n    $next_free_cf_id = glz_next_empty_custom();\n    // Pass into edit pane\n    glz_cf_edit($msg, $next_free_cf_id);\n}\n\n\n/**\n * Edit a custom field / Add a new custom field.\n * Retrieves values for ID url variable (or id from glz_cf_add)\n *\n * @param  string  $msg  Success, error or warning message shown by Textpattern\n * @param  integer  $id  passed from glz_cf_add\n */\nfunction glz_cf_edit($msg=\'\', $id=\'\', $debug = false)\n{\n    global $event, $step, $prefs;\n    // get ID from URL of $id not supplied (e.g. by \"add\" step)\n    if (empty($id)) {\n        $id = gps(\'ID\');\n    }\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // If editing (not adding), check ID actually exists, else back to list\n    if (($step === \'edit\') && (!get_pref(\'custom_\'.$id.\'_set\'))) {\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    if ($step === \'edit\') {\n        // \'Edit\' Step: retrieve array of all custom field properties\n        $custom_field = glz_db_get_custom_set($id);\n        $panel_title = gTxt(\'glz_cf_action_edit_title\', array(\'{custom_set_name}\' => gTxt(\'glz_cf_title\').\' #\'.$custom_field[\'id\']));\n    } else {\n        // \'Add\' step: set available starting properties, null others\n        $custom_field = array();\n        $custom_field[\'id\'] = $id;\n        $custom_field[\'custom_set\'] = \'custom_\'.$id.\'_set\';\n        foreach (array(\'name\', \'position\', \'type\', \'title\', \'instructions\') as $key) {\n            $custom_field[$key] = null;\n        }\n        $panel_title = gTxt(\'glz_cf_action_new_title\');\n    }\n\n    // Pass existing name in case custom field is renamed\n    $existing_name = ($step === \'edit\') ?\n        hInput(\'custom_set_name_old\', $custom_field[\'name\']) :\n        \'\';\n    // Pass in existing position as hidden input (change position value in the list)\n    $existing_position = (($step === \'edit\') && ($prefs[\'glz_cf_use_sortable\'] == \'1\')) ?\n        hInput(\'custom_set_position\', $custom_field[\'position\']) :\n        \'\';\n\n    // Custom field types drop-down\n    $arr_custom_set_types = glz_custom_set_types();\n    $custom_set_types = null;\n    foreach ($arr_custom_set_types as $custom_type_group => $custom_types) {\n        $custom_set_types .= \'<optgroup label=\"\'.gTxt(\'glz_cf_types_\'.$custom_type_group).\'\">\'.n;\n        foreach ($custom_types as $custom_type) {\n            $selected = ($custom_field[\'type\'] == $custom_type) ?\n                \' selected=\"selected\"\' :\n                null;\n            $custom_set_types .= \'<option value=\"\'.$custom_type.\'\" dir=\"auto\"\'.$selected.\'>\'.gTxt(\'glz_cf_\'.$custom_type).\'</option>\'.n;\n        }\n        $custom_set_types .= \'</optgroup>\'.n;\n    }\n\n    // Fetch (multiple) type values for this custom field\n    if ($step === \'edit\') {\n        if ($custom_field[\'type\'] == \"text_input\") {\n            $arr_values = glz_db_get_all_existing_cf_values(glz_custom_number($custom_field[\'custom_set\']), array(\'custom_set_name\' => $custom_field[\'name\'], \'status\' => 4));\n        } else {\n            $arr_values = glz_db_get_custom_field_values($custom_field[\'custom_set\'], array(\'custom_set_name\' => $custom_field[\'name\']));\n        }\n        $values = ($arr_values) ? implode(\"\\r\\n\", $arr_values) : \'\';\n    } else {\n        $values = \'\';\n    }\n    // This needs to be different for a script\n    if (isset($custom_field[\'type\']) && $custom_field[\'type\'] == \"custom-script\") {\n        $value = fInput(\'text\', \'value\', $values, \'\', \'\', \'\', \'\', \'\', \'value\');\n        $value_instructions = \'glz_cf_js_script_msg\';\n    } else {\n        $value = text_area(\'value\', 0, 0, $values, \'value\');\n        $value_instructions = \'glz_cf_multiple_values_instructions\';\n    }\n\n    $action = ($step === \'edit\') ?\n        fInput(\'submit\', \'save\', gTxt(\'save\'), \'publish\') :\n        fInput(\'submit\', \'add_new\', gTxt(\'glz_cf_add_new_cf\'), \'publish\');\n\n    // Build the form\n    pageTop($panel_title, $msg);\n    // dmp($custom_field);\n\n    $out = array();\n\n    $out[] = hed($panel_title, 2);\n    $out[] =\n        inputLabel(\n                \'custom_set_name\',\n                fInput(\'text\', \'custom_set_name\', htmlspecialchars($custom_field[\'name\']), \'\', \'\', \'\', 28, \'\', \'custom_set_name\'),\n                \'glz_cf_edit_name\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_name_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_title\',\n                fInput(\'text\', \'custom_set_title\', htmlspecialchars($custom_field[\'title\']), \'\', \'\', \'\', INPUT_REGULAR, \'\', \'custom_set_title\'),\n                \'glz_cf_edit_title\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_title_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_instructions\',\n                fInput(\'text\', \'custom_set_instructions\', htmlspecialchars($custom_field[\'instructions\']), \'\', \'\', \'\', INPUT_REGULAR, \'\', \'custom_set_instructions\'),\n                \'glz_cf_edit_instructions\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_edit_instructions_hint\' // Inline help string\n                )\n            ).\n        inputLabel(\n                \'custom_set_type\',\n                \'<select name=\"custom_set_type\" id=\"custom_set_type\">\'.$custom_set_types.\'</select>\',\n                \'glz_cf_edit_type\',\n                array(\n                    0 => \'\',\n                    1 => \'glz_cf_js_configure_msg\'  // Inline help string\n                )\n            ).\n        ($prefs[\'glz_cf_use_sortable\'] == \'0\' ?\n            inputLabel(\n                    \'custom_set_position\',\n                    fInput(\'text\', \'custom_set_position\', htmlspecialchars($custom_field[\'position\']), \'\', \'\', \'\', INPUT_MEDIUM, \'\', \'custom_set_position\'),\n                    \'glz_cf_edit_position\',\n                    array(\n                        0 => \'\',\n                        1 => \'glz_cf_edit_position_hint\'  // Inline help string\n                    )\n                )\n        : \'\').\n        inputLabel(\n                \'custom_set_value\',\n                $value,\n                \'glz_cf_edit_value\',\n                array(\n                    0 => \'\',\n                    1 => $value_instructions  // Inline help string\n                )\n            ).\n        n.tag(gTxt(\'glz_cf_js_script_msg\'), \'span\', array(\'class\' => \'glz-custom-script-msg hidden\')).\n        n.tag(gTxt(\'glz_cf_js_textarea_msg\'), \'span\', array(\'class\' => \'glz-custom-textarea-msg hidden\')).\n        eInput(\'glz_custom_fields\').\n        sInput(\'save\').\n        hInput(\'custom_set\', $custom_field[\'custom_set\']).\n        hInput(\'custom_field_number\', $custom_field[\'id\']).\n        $existing_name.\n        $existing_position.\n        graf(\n            sLink(\'glz_custom_fields\', \'\', gTxt(\'cancel\'), \'txp-button\').\n            $action,\n            array(\'class\' => \'txp-edit-actions\')\n        );\n\n    echo form(join(\'\', $out), \'\', \'\', \'post\', \'txp-edit\', \'\', \'add_edit_custom_field\');\n}\n\n\n/**\n * Saves a new or existing custom field\n * Retrieves incoming $POST variables\n *\n * @param  string  $msg  Success, error or warning message shown by Textpattern\n */\nfunction glz_cf_save($msg=\'\', $debug = false)\n{\n    global $event, $step, $prefs, $msg;\n\n    $in = array_map(\'assert_string\', psa(array(\n        \'custom_set\',\n        \'custom_field_number\',\n        \'custom_set_name\',\n        \'custom_set_name_old\',\n        \'custom_set_title\',\n        \'custom_set_instructions\',\n        \'custom_set_type\',\n        \'custom_set_position\',\n        \'value\',\n        \'save\',\n        \'add_new\'\n    )));\n\n    extract($in);\n\n    // No name given -> error + return to list\n    if (empty($custom_set_name)) {\n        if ($debug) {\n            dmp(\'No name specified\');\n        } // DEBUG info\n        $msg = array(gTxt(\'glz_cf_no_name\'), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // Same name given as another existing custom field -> error + return to list\n    if (glz_check_custom_set_name($custom_set_name, $custom_set)) {\n        if ($debug) {\n            dmp(\'Same name as other custom field specified\');\n        } // DEBUG info\n        // If the sanitized cf name matches an existing custom field, provide an extra hint in the error message\n        $name_sanitized = glz_sanitize_for_cf($custom_set_name);\n        $name_exists_msg = ($custom_set_name <> $name_sanitized) ? $custom_set_name.\' (\'.$name_sanitized.\')\' : $custom_set_name;\n        $msg = array(gTxt(\'glz_cf_exists\', array(\'{custom_set_name}\' => $name_exists_msg)), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // No values specified for checkbox type -> error + return to list\n    if ($custom_set_type == \'checkbox\' && empty($value)) {\n        $msg = array(gTxt(\'glz_cf_no_values\'), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    // At lest two values must specified for radiobutton/multiselect type -> error + return to list\n    $cf_values = array_unique(array_filter(explode(\"\\r\\n\", $value), \'glz_array_empty_values\'));\n    if ( ($custom_set_type == \'radio\' || $custom_set_type == \'multi-select\') && (count($cf_values) < 2) ) {\n        $msg = array(gTxt(\'glz_cf_not_enough_values\', array(\'{cf_type}\' => gTxt(\'glz_cf_\'.$custom_set_type))), E_ERROR);\n        glz_cf_list($msg);\n        return;\n    }\n\n    if ($debug) {\n        dmp(\'CF name as input: \'.$custom_set_name);\n    } // DEBUG info\n\n    $create_new_cf = (!empty($add_new)) ? true : false;\n\n    if ($create_new_cf) {\n        // Adding a new custom field\n        if ($debug) {\n            dmp(\'Creating a new custom field\');\n        } // DEBUG info\n\n        // Note the custom field name input by the user\n        $custom_set_name_input = $custom_set_name;\n        // Sanitize custom field name : use strict mode for new custom fields\n        $custom_set_name = glz_sanitize_for_cf($custom_set_name);\n        // Compare: if different -> Raise information notice\n        if ($custom_set_name_input <> $custom_set_name) {\n            $msg = array(gTxt(\'glz_cf_name_renamed_notice\', array(\'{custom_name_input}\' => $custom_set_name_input, \'{custom_name_output}\' => $custom_set_name )), E_WARNING);\n        }\n    } else {\n        // Editing an existing custom field\n        if ($debug) {\n            dmp(\'Updating an existing custom field\');\n        } // DEBUG info\n\n        // Check if custom field name is valid -> Raise warning notice if not\n        glz_is_valid_cf_name($custom_set_name);\n        // Sanitize custom field name : use $lite mode for backwards compatibility\n        $custom_set_name = glz_sanitize_for_cf($custom_set_name, $lite = true);\n    }\n\n    // Use sanitized custom set name\n    $in[\'custom_set_name\'] = $custom_set_name;\n    if ($debug) {\n        dmp(\'CF name cleaned: \'.$custom_set_name);\n    } // DEBUG info\n\n    // If there is no value for \'position\' specified, use the custom field numbers\n    // if using jqueryui.sortable use 999 (the end of the list)\n    if (empty($custom_set_position)) {\n        $in[\'custom_set_position\'] = ($prefs[\'glz_cf_use_sortable\'] == \'1\') ? \'999\' : $custom_field_number;\n    }\n\n    if ($debug) {\n        dmp(\'$in: \'.$in);\n    } // DEBUG info\n\n    // OK, good to go\n\n    if ($create_new_cf) {\n\n        // ACTION! Save new custom field to DB\n        $result = glz_db_cf_new($in, $debug);\n\n        if ($result) {\n            // update lastmod + corresponding event\n            update_lastmod(\n                \'custom_field_created\',\n                compact(\n                    \'custom_set\',\n                    \'custom_field_number\',\n                    \'custom_set_name\',\n                    \'custom_set_title\',\n                    \'custom_set_instructions\',\n                    \'custom_set_type\',\n                    \'custom_set_position\'\n                    )\n                );\n\n            // Success or warning message (if generated earlier by glz_is_valid_cf_name)\n            if (empty($msg)) {\n                $msg = gTxt(\'glz_cf_created\', array(\'{custom_set_name}\' => $custom_set_name));\n            }\n        }\n    } else {\n\n        // ACTION! Update custom field in DB\n        $result = glz_db_cf_save($in, $debug);\n\n        if ($result) {\n            // update lastmod + corresponding event\n            update_lastmod(\n                \'custom_field_updated\',\n                compact(\n                    \'custom_set\',\n                    \'custom_field_number\',\n                    \'custom_set_name\',\n                    \'custom_set_name_old\',\n                    \'custom_set_title\',\n                    \'custom_set_instructions\',\n                    \'custom_set_type\',\n                    \'custom_set_position\'\n                    )\n                );\n            // Success or warning message (if generated earlier by glz_is_valid_cf_name)\n            if (empty($msg)) {\n                $msg = gTxt(\'glz_cf_updated\', array(\'{custom_set_name}\' => $custom_set_name));\n            }\n        }\n    }\n\n    // Render custom field list\n    glz_cf_list($msg);\n}\n\n\n/**\n * Reset step in UI – for custom field IDs 1-10.\n * Retrieves value for ID from url\n *\n * @param  string  $msg  Pass-thru of success / error / warning message\n * @param  bool  $debug  Switch on debug messaging and query dumps\n */\nfunction glz_cf_reset($msg=\'\', $debug = false)\n{\n    global $event, $step;\n\n    // Get ID from URL\n    $id = gps(\'ID\');\n\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        if ($debug) {\n            dmp($id.\' is not an integer\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // Check ID actually exists before resetting, else back to list\n    if (!get_pref(\'custom_\'.$id.\'_set\')) {\n        if ($debug) {\n            dmp(\'custom_\'.$id.\'_set does not exist\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    // ACTION! Reset in DB\n    $result = glz_db_cf_reset($id, $debug);\n\n    if ($result) {\n        update_lastmod(\'custom_field_reset\', $id);\n        $msg = gTxt(\'glz_cf_reset\', array(\'{custom_set_id}\' => \'ID# \'.$id));\n    } else {\n        $msg = array(gTxt(\'glz_cf_reset_error\', array(\'{custom_set_id}\' => \'ID# \'.$id)), E_ERROR);\n    }\n\n    // Render custom field list + message\n    glz_cf_list($msg);\n}\n\n\n/**\n * Delete step in UI – for custom fields ID > 10.\n * Retrieves value for ID from url\n *\n * @param  string  $msg  Success / error / warning message\n * @param  bool  $debug  Switch on debug messaging and query dumps\n */\nfunction glz_cf_delete($msg=\'\', $reset= false, $debug = false)\n{\n    global $event, $step;\n\n    // Get ID from URL\n    $id = gps(\'ID\');\n\n    // Check ID is properly formed, else back to list\n    if (!intval($id)) {\n        if ($debug) {\n            dmp($id.\' is not an integer\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    }\n    // Check ID actually exists before deleting, else back to list\n    if (!get_pref(\'custom_\'.$id.\'_set\')) {\n        if ($debug) {\n            dmp(\'custom_\'.$id.\'_set does not exist\');\n        } // DEBUG info\n        glz_cf_list(array(gTxt(\'glz_cf_no_such_custom_field\'), E_ERROR));\n        return false;\n    };\n\n    // ACTION! Delete from DB (reset for IDs 1-10)\n    $result = glz_db_cf_delete($id, $reset, $debug);\n\n    if ($result) {\n        update_lastmod(\'custom_field_deleted\', $id);\n        $msg = gTxt(\'glz_cf_deleted\', array(\'{custom_set_id}\' => \'ID# \'.$id));\n    } else {\n        $msg = array(gTxt(\'glz_cf_deleted_error\', array(\'{custom_set_id}\' => \'ID# \'.$id)), E_ERROR);\n    }\n\n    // Render custom field list + message\n    glz_cf_list($msg);\n}\n\n\n\n##################\n#\n#	glz_custom_fields for Textpattern\n#	version 2.0 – jools-r\n#	Original version: Gerhard Lazu\n#\n##################\n\n##################\n#\n#   MAIN – Register plugin privs + callbacks + dispatcher\n#\n##################\n\nglobal  $event, $step, $txp_permissions, $use_minified, $debug;\n\n// DEBUG: Set $debug to true to load css and js from files instead of injecting into head\n$debug = false;\n// DEBUG: Set $use_minified to false to load regular (non-minified) js and css files\n$use_minified = true;\n\nif(@txpinterface == \'admin\') {\n\n    // glz admin panels / events\n    $glz_admin_events = array(\n        \'article\',\n        \'prefs\',\n        \'glz_custom_fields\'\n    );\n\n    // Add prefs privs\n    add_privs(\'prefs.glz_custom_f\', \'1\');\n    add_privs(\'prefs.glz_custom_f.glz_cf_datepicker\', \'1\');\n    add_privs(\'prefs.glz_custom_f.glz_cf_timepicker\', \'1\');\n\n    // Disable regular customs preferences (remove privs)\n    $txp_permissions[\'prefs.custom\'] = \'\';\n\n    // Redirect \'Options\' link on plugins panel to preferences\n    add_privs(\'plugin_prefs.glz_custom_fields\', \'1\');\n    register_callback(\'glz_custom_fields_prefs_redirect\', \'plugin_prefs.glz_custom_fields\');\n\n    // Install plugin\n    register_callback(\'glz_custom_fields_install\', \'plugin_lifecycle.glz_custom_fields\', \'installed\');\n    register_callback(\'glz_custom_fields_uninstall\', \'plugin_lifecycle.glz_custom_fields\', \'deleted\');\n\n    // Restrict css/js + pre-save to relevant admin pages only\n    if (in_array($event, $glz_admin_events)) {\n\n        // Add CSS & JS to admin head area\n        register_callback(\'glz_custom_fields_inject_css_js\', \'admin_side\', \'head_end\');\n\n        // Use jqueryui.sortable to set the custom field position value\n        if ($prefs[\'glz_cf_use_sortable\'] == \'1\') {\n            register_callback(\'glz_cf_positionsort_js\', \'customfields_ui\', \'table_end\');\n            register_callback(\'glz_cf_positionsort_steps\', \'glz_custom_fields\');\n        }\n\n        // Write tab: multiple value array -> string conversion on save/create\n        if (($step === \'edit\') || ($step === \'create\')) {\n            register_callback(\'glz_custom_fields_before_save\', \'article\', \'\', 1);\n        }\n    }\n\n    // Custom fields tab under extensions\n    add_privs(\'glz_custom_fields\', \'1,2\');\n    register_tab(\'extensions\', \'glz_custom_fields\', gTxt(\'glz_cf_tab_name\'));\n    register_callback(\'glz_cf_dispatcher\', \'glz_custom_fields\');\n\n    // Write tab: replace regular custom fields with glz custom fields\n    // -> custom fields\n    register_callback(\'glz_custom_fields_replace\', \'article_ui\', \'custom_fields\');\n    // -> textareas\n    register_callback(\'glz_custom_fields_replace\', \'article_ui\', \'body\');\n\n}\n\n\n/**\n * Jump off to relevant stub for handling actions.\n */\nfunction glz_cf_dispatcher()\n{\n    global $event, $step;\n\n    // Available steps\n    $steps = array(\n        \'add\'    => true,\n        \'edit\'   => true,\n        \'save\'   => true,\n        \'reset\'  => true,\n        \'delete\' => true\n    );\n\n    // Use default step if nothing matches\n    if(!$step || ((!bouncer($step, $steps)) || !isset($steps[$step]))) {\n        $step = \'list\';\n    }\n\n    // Run the function\n    $func = \'glz_cf_\' . $step;\n    $func();\n}\n', '4925974b88100b742732fac234a07871', '#@glz_cf\n#@language de-de\nglz_cf_tab_name => Benutzerdefinierte Felder\nglz_cf_title => Benutzerdefiniertes Feld\nglz_cf_no_name => Das <strong>benutzerdefinierte Feld</strong> hat keinen Namen.\nglz_cf_deleted => Das <strong>benutzerdefinierte Feld {custom_set_id}</strong> wurde gelöscht.\nglz_cf_deleted_error => Es gab ein Problem beim Löschen des <strong>benutzerdefinierten Felds {custom_set_id}</strong>.\nglz_cf_reset => <strong>Benutzerdefiniertes Feld {custom_set_id}</strong> wurde zurückgesetzt.\nglz_cf_reset_error => Es gab ein Problem beim Zurücksetzen des <strong>benutzerdefinierten Felds {custom_set_id}</strong>.\nglz_cf_created => <strong>{custom_set_name}</strong> wurde erstellt.\nglz_cf_updated => <strong>{custom_set_name}</strong> wurde aktualisiert.\nglz_cf_exists => <strong>{custom_set_name}</strong> existiert bereits.\nglz_cf_doesnt_exist => <strong>{custom_set_name}</strong> existiert nicht.\nglz_cf_name_renamed_notice => Unzulässiger Name: <strong>{custom_name_input}</strong> wurde als <strong>{custom_name_output}</strong> erstellt. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.\nglz_cf_name_invalid_chars => <strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nur <strong>kleingeschriebene Zeichen, Ziffern und Unterstriche</strong> enthalten, jedoch <strong>keine Leerzeichen</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.\nglz_cf_name_invalid_starts_with_number => <strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nicht mit einer Ziffer beginnen. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.\nglz_cf_name_invalid_double_underscores => <strong>{custom_name_input}</strong> wurde aktualisiert, sollte aber umbenannt werden: Namen sollten keine doppelten Unterstreichungszeichen enthalten. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">» Erklärung</a>.\nglz_cf_no_values => Ein Checkbox-Feld benötigt mindestens einen Wert!\nglz_cf_not_enough_values => Ein {cf_type}-Feld benötigt mindestens zwei Werte!\nglz_cf_field_problems => <strong>{custom_set_name}</strong> ist fehlerhaft. <a href=\"?event=glz_custom_fields\">Bitte berichtigen.</a>.\nglz_cf_types_normal => Normal\nglz_cf_types_special => Spezial\nglz_cf_text_input => Texteingabe\nglz_cf_select => Auswahlmenü\nglz_cf_multi-select => Multiselect\nglz_cf_textarea => Textblock\nglz_cf_checkbox => Checkboxen\nglz_cf_radio => Radiobuttons\nglz_cf_date-picker => Datumswähler\nglz_cf_time-picker => Zeitwähler\nglz_cf_custom-script => Benutzerdefiniertes Skript\nglz_cf_type_not_supported => Typ nicht unterstützt\nglz_cf_action_add => Hinzufügen\nglz_cf_action_edit => Bearbeiten\nglz_cf_action_delete => Löschen\nglz_cf_action_reset => Zurücksetzen\nglz_cf_not_specified => {what} wurde nicht angegeben.\nglz_cf_migration_success => Datenmigration der benutzerdefinierten Felder erfolgreich.\nglz_cf_migration_skip => <strong>custom_fields</strong> Datenbanktabelle existiert bereits: Die Datenmigration wurde übersprungen.\nglz_cf_search_section_created => <strong>search</strong> Rubrik wurde erstellt.\nglz_cf_not_found => <strong>{file}</strong> wurde nicht gefunden. Bitte den Pfad überprüfen.\nglz_cf_not_callable => <strong>{function}()</strong> konnte nicht aufgerufen werden. Bitte sicherstellen, dass <strong>{file}</strong> ausführbar ist.\nglz_cf_no_such_custom_field => Kein benutzerdefiniertes Feld mit dieser ID!\nglz_cf_prefpane_title => Benutzerdefinierte Felder\nglz_cf_js_textarea_msg => Einen Wert pro Zeile.<br>Bei Bedarf einen {voreingestellten} Wert angeben.\nglz_cf_js_script_msg => php-Dateiname im custom scripts Pfad.\nglz_cf_js_configure_msg => Zum Konfigurieren siehe Einstellungen » Benutzerdefinierte Felder\nglz_cf_public_error_datepicker => Problembehebung erforderlich: <u>Pfad des Datumswähler-Plugins überprüfen</u>\nglz_cf_public_error_timepicker => Problembehebung erforderlich: <u>Pfad des Zeitwähler-Plugins überprüfen</u>\nposition => Position\nglz_cf_col_sort => Reihenfolge\nglz_cf_action_new_title => Neues benutzerdefiniertes Feld\nglz_cf_action_edit_title => {custom_set_name} bearbeiten.\nglz_cf_add_new_cf => Neues benutzerdefiniertes Feld\nglz_cf_multiple_values_instructions => Einen Wert pro Zeile <br>Bei Bedarf einen {voreingestellten} Wert angeben.\nglz_cf_edit_name_hint => Nur alphanumerische Zeichen und einzelne Unterstriche\nglz_cf_edit_position_hint => Wird automatisch vergeben, falls leer.\nglz_cf_edit_name => Name:\nglz_cf_edit_title => Titel:\nglz_cf_edit_title_hint => Feldbezeichnung\nglz_cf_edit_instructions => Anleitungstext:\nglz_cf_edit_instructions_hint => Optionaler Hinweistext (erscheint zwischen Label und Element)\nglz_cf_edit_type => Typ:\nglz_cf_edit_position => Position:\nglz_cf_edit_value => Wert(e):\nglz_cf_confirm_reset => ALLE DATEN für {custom} ZURÜCKSETZEN – Sind Sie sicher?\nglz_cf_confirm_delete => ALLE DATEN für {custom} LÖSCHEN – Sind Sie sicher?\nglz_custom_f => Benutzerdefinierte Felder <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Pfade überprüfen</a>\nglz_cf_values_ordering => Anzeigereihenfolge bei mehreren Werten\nglz_cf_prefs_value_asc => Aufsteigend\nglz_cf_prefs_value_desc => Absteigend\nglz_cf_prefs_value_custom => Wie eingetragen\nglz_cf_multiselect_size => Zeilenanzahl der Multiselects\nglz_cf_css_folder => URL des CSS-Verzeichnis für glz custom fields\nglz_cf_js_folder => URL des JS-Verzeichnis für glz custom fields\nglz_cf_custom_folder => Pfad für benutzerdefinierte Skripte\nglz_cf_use_sortable => Reihenfolge der Felder per Maus ändern\nglz_cf_permit_full_deinstall => Beim Deinstallieren alle Daten löschen\nglz_cf_datepicker_folder => URL des Datumswähler-Verzeichnisses\nglz_cf_timepicker_folder => URL des Zeitwähler-Verzeichnisses\nglz_cf_folder_error => URL/Pfad enthält Fehler, bitte überprüfen.\nglz_cf_folder_success => URL/Pfad ist korrekt.\nglz_cf_css_asset_url => URL des CSS-Verzeichnisses\nglz_cf_js_asset_url => URL des JS-Verzeichnisses\nglz_cf_custom_scripts_path => Pfad für benutzerdefinierte Skripte\nglz_cf_custom_scripts_path_error => Pfad für benutzerdefinierte Skripte fehlerhaft, bitte überprüfen.\nglz_cf_datepicker => Datumswähler\nglz_cf_datepicker_url => URL des Datumswähler-Plugins\nglz_cf_datepicker_url_error => URL/Pfad enthält Fehler, bitte überprüfen.\nglz_cf_datepicker_format => Datumsformat\nglz_cf_datepicker_first_day => Wochenbeginn\nglz_cf_datepicker_start_date => Anfangsdatum\nglz_cf_datepicker_start_date_error => Format des Anfangsdatums muss dem Datumsformat entsprechen.\nglz_cf_prefs_monday => Montag\nglz_cf_prefs_tuesday => Dienstag\nglz_cf_prefs_wednesday => Mittwoch\nglz_cf_prefs_thursday => Donnerstag\nglz_cf_prefs_friday => Freitag\nglz_cf_prefs_saturday => Samstag\nglz_cf_prefs_sunday => Sonntag\nglz_cf_prefs_slash_dmyy => TT/MM/JJJJ\nglz_cf_prefs_slash_mdyy => MM/TT/JJJJ\nglz_cf_prefs_dash_yymd => JJJJ-MM-TT\nglz_cf_prefs_space_dmy => TT MM JJ\nglz_cf_prefs_dot_dmyy => TT.MM.JJJJ\nglz_cf_timepicker => Zeitwähler\nglz_cf_timepicker_url => URL des Zeitwähler-Plugins\nglz_cf_timepicker_url_error => URL/Pfad enthält Fehler, bitte überprüfen.\nglz_cf_timepicker_start_time => Erster Zeiteintrag im Menü\nglz_cf_timepicker_end_time => Letzter Zeiteintrag im Menü\nglz_cf_timepicker_step => Zeitsprünge des Menüs in Minuten\nglz_cf_timepicker_show_24 => Zeitformat\nglz_cf_prefs_24_hours => 24 Stunden\nglz_cf_prefs_12_hours => 12 Stunden\nglz_cf_sort_success => Reihenfolge geändert\nglz_cf_sort_error => Reihenfolge konnte nicht geändert werden.\n#@prefs\ncustom_x_set => Name des benutzerdefinierten Feldes Nr. {number}\ninstructions_glz_cf_datepicker_start_date => Die Angabe muss mit dem Datumsformat übereinstimmen.\ninstructions_glz_cf_custom_scripts_path => Pfad relativ zum root-Verzeichnis\ninstructions_glz_cf_permit_full_deinstall => ACHTUNG: \"Ja\" löscht ALLE DATEN in den benutzerdefinierten Feldern.\n\n#@glz_cf\n#@language en, en-gb, en-us, en-ca\nglz_cf_tab_name => Custom Fields\nglz_cf_title => Custom Field\nglz_cf_no_name => A <strong>custom field</strong> must have a name\nglz_cf_deleted => <strong>Custom field {custom_set_id}</strong> was deleted\nglz_cf_deleted_error => There was a problem deleting <strong>custom field {custom_set_id}</strong>\nglz_cf_reset => <strong>Custom field {custom_set_id}</strong> was reset\nglz_cf_reset_error => There was a problem resetting <strong>custom field {custom_set_id}</strong>\nglz_cf_created => <strong>{custom_set_name}</strong> was created\nglz_cf_updated => <strong>{custom_set_name}</strong> was updated\nglz_cf_exists => <strong>{custom_set_name}</strong> already exists\nglz_cf_doesnt_exist => <strong>{custom_set_name}</strong> is not set\nglz_cf_name_renamed_notice => Invalid name: <strong>{custom_name_input}</strong> has been created as <strong>{custom_name_output}</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See why</a>.\nglz_cf_name_invalid_chars => <strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should contain <strong>only lowercase letters, numbers, underscores</strong> and <strong>no spaces</strong>. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.\nglz_cf_name_invalid_starts_with_number => <strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not start with a number. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.\nglz_cf_name_invalid_double_underscores => <strong>{custom_name_input}</strong> was updated, but consider renaming it. Names should not have double underscores. <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_custom_field_names\">» See plugin help</a>.\nglz_cf_no_values => A checkbox custom field must have at least one value\nglz_cf_not_enough_values => A {cf_type} custom field must have at least two values\nglz_cf_field_problems => <strong>{custom_set_name}</strong> has some problems. <a href=\"?event=glz_custom_fields\">Go fix it</a>.\nglz_cf_types_normal => Normal\nglz_cf_types_special => Special\nglz_cf_text_input => Text Input\nglz_cf_select => Select\nglz_cf_multi-select => Multi-Select\nglz_cf_textarea => Textarea\nglz_cf_checkbox => Checkbox\nglz_cf_radio => Radio\nglz_cf_date-picker => Date Picker\nglz_cf_time-picker => Time Picker\nglz_cf_custom-script => Custom Script\nglz_cf_type_not_supported => Type not supported\nglz_cf_action_add => Add\nglz_cf_action_edit => Edit\nglz_cf_action_delete => Delete\nglz_cf_action_reset => Reset\nglz_cf_not_specified => {what} is not specified\nglz_cf_migration_success => Migrating custom fields was successful\nglz_cf_migration_skip => <strong>custom_fields</strong> table already has data in it, migration skipped.\nglz_cf_search_section_created => <strong>search</strong> section has been created\nglz_cf_not_found => <strong>{file}</strong> cannot be found, check path\nglz_cf_not_callable => <strong>{function}()</strong> cannot be called. Ensure <strong>{file}</strong> can be executed.\nglz_cf_no_such_custom_field => No such custom field!\nglz_cf_prefpane_title => Custom Fields Preferences\nglz_cf_js_textarea_msg => Each value on a separate line<br>One {default} value allowed\nglz_cf_js_script_msg => php file name in your custom scripts path\nglz_cf_js_configure_msg => For settings, see custom field preferences\nglz_cf_public_error_datepicker => Fix the DatePicker jQuery plugin » <u>Check path</u>\nglz_cf_public_error_timepicker => Fix the TimePicker jQuery plugin » <u>Check path</u>\nposition => Position\nglz_cf_col_sort => Sort\nglz_cf_action_new_title => New custom field\nglz_cf_action_edit_title => Edit {custom_set_name}\nglz_cf_add_new_cf => New custom field\nglz_cf_multiple_values_instructions => Each value on a separate line<br>One {default} value allowed\nglz_cf_edit_name_hint => Alphanumeric characters and underscores\nglz_cf_edit_position_hint => Automatically assigned if blank\nglz_cf_edit_name => Name:\nglz_cf_edit_title => Title:\nglz_cf_edit_title_hint => Descriptive field label\nglz_cf_edit_instructions => Instructions:\nglz_cf_edit_instructions_hint => Optional help text (appears alongside label)\nglz_cf_edit_type => Type:\nglz_cf_edit_position => Position:\nglz_cf_edit_value => Value(s):\nglz_cf_confirm_reset => This will RESET ALL data for custom field {custom}. Are you sure?\nglz_cf_confirm_delete => This will DELETE ALL data for custom field {custom}. Are you sure?\nglz_custom_f => Custom fields preferences <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Check paths</a>\nglz_cf_values_ordering => Order for custom field values\nglz_cf_prefs_value_asc => Ascending\nglz_cf_prefs_value_desc => Descending\nglz_cf_prefs_value_custom => As entered\nglz_cf_multiselect_size => Multi-select field size\nglz_cf_css_folder => Glz custom fields CSS folder\nglz_cf_js_folder => Glz custom fields JS folder\nglz_cf_custom_folder => Custom scripts path\nglz_cf_use_sortable => Set custom field order per drag and drop\nglz_cf_permit_full_deinstall => Delete all data when deinstalling\nglz_cf_datepicker_folder => Date picker folder\nglz_cf_timepicker_folder => Time picker folder\nglz_cf_folder_error => {folder} does not exist, please create it.\nglz_cf_folder_success => URL / path is correct\nglz_cf_css_asset_url => CSS folder URL\nglz_cf_js_asset_url => JS folder URL\nglz_cf_custom_scripts_path => Custom scripts path\nglz_cf_custom_scripts_path_error => Custom scripts folder does not exist, please create it.\nglz_cf_datepicker => Date picker\nglz_cf_datepicker_url => Date picker plugin folder URL\nglz_cf_datepicker_url_error => Date picker folder does not exist, please create it.\nglz_cf_datepicker_format => Date format\nglz_cf_datepicker_first_day => First day of week\nglz_cf_datepicker_start_date => Start date\nglz_cf_datepicker_start_date_error => Start date does not match date format\nglz_cf_prefs_monday => Monday\nglz_cf_prefs_tuesday => Tuesday\nglz_cf_prefs_wednesday => Wednesday\nglz_cf_prefs_thursday => Thursday\nglz_cf_prefs_friday => Friday\nglz_cf_prefs_saturday => Saturday\nglz_cf_prefs_sunday => Sunday\nglz_cf_prefs_slash_dmyy => dd/mm/yyyy\nglz_cf_prefs_slash_mdyy => mm/dd/yyyy\nglz_cf_prefs_dash_yymd => yyyy-mm-dd\nglz_cf_prefs_space_dmy => dd mm yy\nglz_cf_prefs_dot_dmyy => dd.mm.yyyy\nglz_cf_timepicker => Time picker\nglz_cf_timepicker_url => Time picker plugin folder URL\nglz_cf_timepicker_url_error => Time picker folder does not exist, please create it.\nglz_cf_timepicker_start_time => Start time\nglz_cf_timepicker_end_time => End time\nglz_cf_timepicker_step => Step\nglz_cf_timepicker_show_24 => Time format\nglz_cf_prefs_24_hours => 24 hours\nglz_cf_prefs_12_hours => 12 hours\nglz_cf_sort_success => Custom field order changed\nglz_cf_sort_error => Custom field order save failed\n#@prefs\ncustom_x_set => Custom field {number} name\ninstructions_glz_cf_datepicker_start_date => Format MUST match \"Date format\"\ninstructions_glz_cf_custom_scripts_path => Path from your web root folder\ninstructions_glz_cf_permit_full_deinstall => WARNING: \"Yes\" = Deinstalling deletes All USER INPUT in custom fields\n\n#@glz_cf\n#@language fr\nglz_cf_tab_name => Saisie personnalisée\nglz_cf_title => Champ personnalisé\nglz_cf_no_name => Un champ personnalisé doit avoir un nom\nglz_cf_deleted => <strong>Champ personnalisé {custom_set_id}</strong> supprimé\nglz_cf_deleted_error => Échec de suppression du <strong>champ personnalisé {custom_set_id}</strong>\nglz_cf_reset => <strong>Champ personnalisé {custom_set_id}</strong> réinitialisé\nglz_cf_reset_error => Échec de la réinitialisation du <strong>champ personnalisé {custom_set_id}</strong>\nglz_cf_created => <strong>{custom_set_name}</strong> correctement créé\nglz_cf_updated => <strong>{custom_set_name}</strong> correctement mis à jour\nglz_cf_exists => <strong>{custom_set_name}</strong> est déjà présent\nglz_cf_doesnt_exist => <strong>{custom_set_name}</strong> n’existe pas\nglz_cf_name_renamed_notice => Nom du champ personnalisé invalide : <strong>{custom_name_input}</strong> a été créé comme <strong>{custom_name_output}</strong>. » <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">Voyez pourquoi</a>.\nglz_cf_name_invalid_chars => <strong>{custom_name_input}</strong> mis à jour, mais envisager de le renommer. Le nom du champ personnalisé doit contenir uniquement des <strong>lettres minuscules, des nombres, des sous-tirets</strong> mais aucun <strong>espaces</strong>. » <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">Voyez l’aide du plugin</a>.\nglz_cf_name_invalid_starts_with_number => <strong>{custom_name_input}</strong> mis à jour, mais envisagez de le renommer. Le nom du champ personnalisé ne peut pas commencer par un nombre. » <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">Voyez l’aide du plugin</a>.\nglz_cf_name_invalid_double_underscores => <strong>{custom_name_input}</strong mis à jour, mais envisager de le renommer. Le nom du champ personnalisé ne peut pas contenir de double sous-tirets. » <a href=\"?event=plugin&step=plugin_help&name=glz_custom_fields#plugin_help_section_special_custom_fields\">Voyez l’aide du plugin</a>.\nglz_cf_no_values => Un champ personnalisé du type case à cocher doit avoir au moins une valeur\nglz_cf_not_enough_values => Un champ personnalisé du type {cf_type} doit avoir au moins deux valeurs\nglz_cf_field_problems => <strong>{custom_set_name}</strong> comporte des problèmes. <a href=\"?event=glz_custom_fields\">Corrigez-les</a>.\nglz_cf_types_normal => Normal\nglz_cf_types_special => Spécial\nglz_cf_text_input => Saisie de texte\nglz_cf_select => Select\nglz_cf_multi-select => Multi-select\nglz_cf_textarea => textarea\nglz_cf_checkbox => Case à cocher\nglz_cf_radio => Bouton radio\nglz_cf_date-picker => Sélecteur de dates\nglz_cf_time-picker => Sélecteur d’heures\nglz_cf_custom-script => Script personnalisé\nglz_cf_type_not_supported => Type non pris en charge\nglz_cf_action_add => Ajouter\nglz_cf_action_edit => Éditer\nglz_cf_action_delete => Supprimer\nglz_cf_action_reset => Réinitialisation\nglz_cf_not_specified => {what} n’est pas spécifié\nglz_cf_migration_success => La mise à jour des champs personnalisés a été correctement effectuée\nglz_cf_migration_skip => La table <strong>custom_fields</strong> contient déjà des données : la mise à jour a été ignorée\nglz_cf_search_section_created => <strong>recherche</strong> créée\nglz_cf_not_found => <strong>{file}</strong> n’a pu être chargé correctement, vérifiez son chemin d’accès\nglz_cf_not_callable => La fonction <strong>{function}()</strong> ne peut pas être utilisée. Vérifiez que <strong>{file}</strong> est exécutable.\nglz_cf_no_such_custom_field => Ce champ personnalisé n’existe pas !\nglz_cf_prefpane_title => Saisie personnalisée\nglz_cf_js_textarea_msg => Chaque valeur sur une ligne distincte<br>Une valeur par {défaut} autorisée\nglz_cf_js_script_msg => Nom du fichier php dans votre chemin des scripts personnalisés\nglz_cf_js_configure_msg => Pour les paramètres, voyez les préférences des champs personnalisés\nglz_cf_public_error_datepicker => Corrigez le plugin jQuery DatePicker » <u>Vérifiez son chemin d’accès</u>\nglz_cf_public_error_timepicker => Corrigez le plugin jQuery  TimePicker » <u>Vérifiez son chemin d’accès</u>\nposition => Position\nglz_cf_col_sort => Trier\nglz_cf_action_new_title => Nouveau champ personnalisé\nglz_cf_action_edit_title => Édition du {custom_set_name}\nglz_cf_add_new_cf => Nouveau champ personnalisé\nglz_cf_multiple_values_instructions => Chaque valeur sur une ligne distincte<br>Une valeur par {défaut} autorisé\nglz_cf_edit_name_hint => Caractères alphanumériques et sous-tirets\nglz_cf_edit_position_hint => Attribué automatiquement si vide\nglz_cf_edit_name => Nom du champ personnalisé\nglz_cf_edit_title => Titre du champ personnalisé\nglz_cf_edit_title_hint => Étiquette descriptive\nglz_cf_edit_instructions => Instructions\nglz_cf_edit_instructions_hint => Texte d’aide facultatif (apparaît à côté de l’étiquette)\nglz_cf_edit_type => Type de champ personnalisé :\nglz_cf_edit_position => Position du champ personnalisé\nglz_cf_edit_value => Valeur(s) du champ personnalisé\nglz_cf_confirm_reset => Cette action va RÉINITIALISER toutes les données du champ personnalisé {custom}. Êtes-vous certain ?\nglz_cf_confirm_delete => Cette action va SUPPRIMER toutes les données du champ personnalisé  {custom}. Êtes-vous certain ?\nglz_custom_f => Saisie personnalisée <a class=\"check-path\" href=\"?event=prefs&check_paths=1\">Vérifiez les chemins d’accès</a>\nglz_cf_values_ordering => Ordre de tri des champs personnalisés\nglz_cf_prefs_value_asc => Ordre ascendant\nglz_cf_prefs_value_desc => Ordre descendant\nglz_cf_prefs_value_custom => Tel qu’enregistré\nglz_cf_multiselect_size => Taille du champ multi-select\nglz_cf_css_folder => Répertoire CSS Glz custom fields\nglz_cf_js_folder => Répertoire JS Glz custom fields\nglz_cf_custom_folder => Chemin des scripts personnalisés\nglz_cf_use_sortable => Trier les champs personnalisés par glisser/déposer\nglz_cf_permit_full_deinstall => Supprimer toutes les données lors de la désinstallation\nglz_cf_datepicker_folder => Répertoire du sélecteur de dates\nglz_cf_timepicker_folder => Répertoire du sélecteur d’heures\nglz_cf_folder_error => {folder} n’existe pas, veuillez le créer.\nglz_cf_folder_success => URL / chemin est correct\nglz_cf_css_asset_url => URL du répertoire CSS\nglz_cf_js_asset_url => URL du répertoire JS\nglz_cf_custom_scripts_path => Chemin des scripts personnalisés\nglz_cf_custom_scripts_path_error => Le répertoire des scripts personnalisés n’existe pas, veuillez le créer.\nglz_cf_datepicker => Sélecteur de dates\nglz_cf_datepicker_url => URL du répertoire du plugin sélecteur de dates\nglz_cf_datepicker_url_error => Le répertoire du sélecteur de dates n’existe pas, veuillez le créer.\nglz_cf_datepicker_format => Format de la date\nglz_cf_datepicker_first_day => Début de la semaine\nglz_cf_datepicker_start_date => Date de commencement du calendrier\nglz_cf_datepicker_start_date_error => La date de commencement ne correspond pas au format de la date\nglz_cf_prefs_monday => Lundi\nglz_cf_prefs_tuesday => Mardi\nglz_cf_prefs_wednesday => Mercredi\nglz_cf_prefs_thursday => Jeudi\nglz_cf_prefs_friday => Vendredi\nglz_cf_prefs_saturday => Samedi\nglz_cf_prefs_sunday => Dimanche\nglz_cf_prefs_slash_dmyy => jj/mm/aaaa\nglz_cf_prefs_slash_mdyy => mm/jj/aaaa\nglz_cf_prefs_dash_yymd => aaaa-mm-jj\nglz_cf_prefs_space_dmy => jj mm aa\nglz_cf_prefs_dot_dmyy => jj.mm.aaaa\nglz_cf_timepicker => Sélecteur de temps\nglz_cf_timepicker_url => URL du répertoire du plugin sélecteur d’heures\nglz_cf_timepicker_url_error => Le répertoire du sélecteur d’heures n’existe pas, veuillez le créer.\nglz_cf_timepicker_start_time => Heure de début\nglz_cf_timepicker_end_time => Heure de fin\nglz_cf_timepicker_step => Décalage horaire (en minutes)\nglz_cf_timepicker_show_24 => Format horaire\nglz_cf_prefs_24_hours => 24 heures\nglz_cf_prefs_12_hours => 12 heures\nglz_cf_sort_success => L’ordre de tri du champ personnalisé a été modifié\nglz_cf_sort_error => L’enregistrement de l’ordre de tri des champs personnalisés a échoué\n#@prefs\ncustom_x_set => Nom du champ personnalisé {number}\ninstructions_glz_cf_datepicker_start_date => Le format DOIT correspondre au format de la date\ninstructions_glz_cf_custom_scripts_path => Chemin depuis le répertoire racine (root)\ninstructions_glz_cf_permit_full_deinstall => Attention : \"Oui\" = La désinstallation supprime TOUTES les données définies par l’utilisateur dans les champs personnalisés\n', '', 5, 9, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('jcr_file_custom', 0, 'jcr / txpbuilders', 'http://txp.builders', '0.1.1', 'Adds a custom field to the files panel', '<h1>jcr_file_custom</h1>\n\n<p>Adds a single extra custom field of up to 255 characters to the <a href=\"http://docs.textpattern.io/administration/files-panel\">Content › Files</a> panel and provides a corresponding tag to output the custom field.</p>\n\n\n<h2>Use cases</h2>\n\n<p>Use whenever extra information needs to be stored with a file. For example:</p>\n\n<ul>\n	<li>Store a txp image ID number and use it to associate a cover image with the file.</li>\n	<li>Store associated details, for example the author(s) of a document or duration of a music track.</li>\n	<li>…</li>\n</ul>\n\n\n<h2 id=\"installation\">Installation</h2>\n\n<p>Paste the code into the  <em>Admin › Plugins</em> panel, install and enable the plugin.</p>\n\n\n<h2 id=\"tags\">Tags</h2>\n\n<pre><code>&lt;txp:jcr_file_custom /&gt;</code></pre>\n\n<p>Outputs the content of the file custom field.</p>\n\n<h3>Tag attributes</h3>\n\n<p><strong>escape</strong><br />\nEscape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> prior to echoing the field contents.<br />\nExample: Use <code>escape=&quot;&quot;</code> to suppress conversion. Default: <code>html</code>.</p>\n\n\n<h2 id=\"examples\">Example</h2>\n\n<p>Produce a list of downloadable documents (assigned to the file category &#8220;issues&#8221;) with their publication covers:</p>\n\n<pre><code>&lt;txp:file_download_list wraptag=&quot;ul&quot; break=&quot;li&quot; category=&quot;issues&quot;&gt;\n  &lt;a href=&quot;&lt;txp:file_download_link /&gt;&quot; title=&quot;&lt;txp:file_download_name /&gt;&quot;&gt;\n    &lt;txp:image id=&#39;&lt;txp:jcr_file_custom /&gt;&#39; /&gt;\n  &lt;/a&gt;\n&lt;/txp:file_download_list&gt;</code></pre>\n\n<p>when the file custom field is used to store the Image ID# of the document cover image.</p>\n\n\n<h2>Changing the label of the custom field</h2>\n\n<p>The name of custom field can be changed by specifying a new label using the <em>Install from Textpack</em> field in the <a href=\"http://docs.textpattern.io/administration/languages-panel\">Admin › Languages</a> panel. Enter your own information in the following pattern and click <strong>Upload</strong>:</p>\n\n<pre><code>#@admin\n#@language en-gb\njcr_file_custom =&gt; Your label</code></pre>\n\n<p>replacing <code>en-gb</code> with your own language and <code>Your label</code> with your own desired label.</p>\n\n\n<h2 id=\"deinstallation\">De-installation</h2>\n\n<p>The plugin cleans up after itself: deinstalling the plugin removes the extra column from the database. To stop using the plugin but keep the database tables, just disable (deactivate) the plugin but don&#8217;t delete it.</p>\n\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<h3>Version 0.1.1 – 2016/12/05</h3>\n\n<ul>\n	<li>Remedy table not being created on install</li>\n</ul>\n\n<h3>Version 0.1 – 2016/03/04</h3>\n\n<ul>\n	<li>First release</li>\n</ul>\n\n\n<h2 id=\"credits\">Credits</h2>\n\n<p>Robert Wetzlmayr’s <a href=\"https://github.com/rwetzlmayr/wet_profile\">wet_profile</a> plugin for the starting point, and further examples by <a href=\"http://www.stefdawson.com\">Stef Dawson</a> and <a href=\"https://github.com/gocom\">Jukka Svahn</a>.</p>', 'class jcr_file_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		// Hook into the system\'s callbacks.\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_file_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'file_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'file\', \'file_save\');\n	}\n\n	/**\n   * Add and remove custom field from txp_file table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_file\',\n                \'ADD COLUMN jcr_file_custom VARCHAR(255) NULL AFTER author\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_file\',\n                \'DROP COLUMN jcr_file_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for file custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current file\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_file_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_file_custom\', fInput(\'text\', \'jcr_file_custom\', $jcr_file_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_file_custom\'), \'jcr_file_custom\').n;\n	}\n\n	/**\n	 * Save additional file custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_file_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_file\',\n		  \"jcr_file_custom = \'$jcr_file_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_file_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_file_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom file field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string custom field output\n   * <code>\n   *        <txp:jcr_file_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_file_custom($atts)\n    {\n        global $thisfile;\n\n        assert_file();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thisfile[\'jcr_file_custom\'])\n            : $thisfile[\'jcr_file_custom\'];\n    }', 'class jcr_file_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		// Hook into the system\'s callbacks.\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_file_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'file_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'file\', \'file_save\');\n	}\n\n	/**\n   * Add and remove custom field from txp_file table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_file\',\n                \'ADD COLUMN jcr_file_custom VARCHAR(255) NULL AFTER author\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_file\',\n                \'DROP COLUMN jcr_file_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for file custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current file\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_file_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_file_custom\', fInput(\'text\', \'jcr_file_custom\', $jcr_file_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_file_custom\'), \'jcr_file_custom\').n;\n	}\n\n	/**\n	 * Save additional file custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_file_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_file\',\n		  \"jcr_file_custom = \'$jcr_file_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_file_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_file_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom file field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string custom field output\n   * <code>\n   *        <txp:jcr_file_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_file_custom($atts)\n    {\n        global $thisfile;\n\n        assert_file();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thisfile[\'jcr_file_custom\'])\n            : $thisfile[\'jcr_file_custom\'];\n    }', '7348057b02d22f82e3e56b1107404ba0', '#@admin\n#@language en-gb\njcr_file_custom => Image ID\n#@language de-de\njcr_file_custom => Bild ID\n', '', 1, 5, 2),
('jcr_image_custom', 0, 'jcr / txpbuilders', 'http://txp.builders', '0.1.1', 'Adds a custom field to the images panel', '<h1>jcr_image_custom</h1>\n\n<p>Adds a single extra custom field of up to 255 characters to the <a href=\"http://docs.textpattern.io/administration/images-panel\">Content › Images</a> panel and provides a corresponding tag to output the custom field.</p>\n\n\n<h2>Use cases</h2>\n\n<p>Use whenever extra information needs to be stored with an image. For example:</p>\n\n<ul>\n	<li>Store a youtube/vimeo link or video-ID with an image for generating custom video poster images.</li>\n	<li>Store the download url of a corresponding hi-res press image not stored in txp.</li>\n	<li>…</li>\n</ul>\n\n\n<h2 id=\"installation\">Installation</h2>\n\n<p>Paste the code into the  <em>Admin › Plugins</em> panel, install and enable the plugin.</p>\n\n\n<h2 id=\"tags\">Tags</h2>\n\n<pre><code>&lt;txp:jcr_image_custom /&gt;</code></pre>\n\n<p>Outputs the content of the image custom field.</p>\n\n<h3>Tag attributes</h3>\n\n<p><strong>escape</strong><br />\nEscape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> prior to echoing the field contents.<br />\nExample: Use <code>escape=&quot;&quot;</code> to suppress conversion. Default: <code>html</code>.</p>\n\n\n<h2 id=\"examples\">Example</h2>\n\n<p>Produce a gallery of custom video poster-images (from images assigned to the image category &#8220;videos&#8221;) that open a corresponding youtube video (defined in the image custom field) in a lightbox modal:</p>\n\n<pre><code>&lt;txp:images wraptag=&quot;ul&quot; break=&quot;li&quot; category=&quot;videos&quot; class=&quot;video-gallery&quot;&gt;\n  &lt;a href=&quot;//www.youtube.com/watch?v=&lt;txp:jcr_image_custom /&gt;&quot; title=&quot;&lt;txp:image_info type=&quot;caption&quot; /&gt;&quot; data-lity&gt;\n    &lt;txp:thumbnail /&gt;\n  &lt;/a&gt;\n&lt;/txp:images&gt;</code></pre>\n\n<p>where the image custom field is used to store the Video ID of the YouTube video. This example uses the <a href=\"http://sorgalla.com/lity/\">lity</a> lightbox script.</p>\n\n\n<h2>Changing the label of the custom field</h2>\n\n<p>The name of custom field can be changed by specifying a new label using the <em>Install from Textpack</em> field in the <a href=\"http://docs.textpattern.io/administration/languages-panel.html\">Admin › Languages</a> panel. Enter your own information in the following pattern and click <strong>Upload</strong>:</p>\n\n<pre><code>#@admin\n#@language en-gb\njcr_image_custom =&gt; Your label</code></pre>\n\n<p>replacing <code>en-gb</code> with your own language and <code>Your label</code> with your own desired label.</p>\n\n\n<h2 id=\"deinstallation\">De-installation</h2>\n\n<p>The plugin cleans up after itself: deinstalling the plugin removes the extra column from the database. To stop using the plugin but keep the database tables, just disable (deactivate) the plugin but don&#8217;t delete it.</p>\n\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<h3>Version 0.1.1 – 2016/12/05</h3>\n\n<ul>\n	<li>Remedy table not being created on install</li>\n</ul>\n\n<h3>Version 0.1 – 2016/04/16</h3>\n\n<ul>\n	<li>First release</li>\n</ul>\n\n\n<h2 id=\"credits\">Credits</h2>\n\n<p>Robert Wetzlmayr’s <a href=\"https://github.com/rwetzlmayr/wet_profile\">wet_profile</a> plugin for the starting point, and further examples by <a href=\"http://www.stefdawson.com\">Stef Dawson</a> and <a href=\"https://github.com/gocom\">Jukka Svahn</a>.</p>', 'class jcr_image_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_image_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'image_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'image\', \'image_save\');\n	}\n\n  /**\n   * Add and remove custom field from txp_image table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_image\',\n                \'ADD COLUMN jcr_image_custom VARCHAR(255) NULL AFTER thumb_h\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_image\',\n                \'DROP COLUMN jcr_image_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for image custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current image\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_image_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_image_custom\', fInput(\'text\', \'jcr_image_custom\', $jcr_image_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_image_custom\'), \'jcr_image_custom\').n;\n	}\n\n	/**\n	 * Save additional image custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_image_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_image\',\n		  \"jcr_image_custom = \'$jcr_image_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_image_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_image_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom image field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string  custom field output\n   * <code>\n   *        <txp:jcr_image_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_image_custom($atts)\n    {\n        global $thisimage;\n\n        assert_image();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thisimage[\'jcr_image_custom\'])\n            : $thisimage[\'jcr_image_custom\'];\n    }', 'class jcr_image_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_image_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'image_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'image\', \'image_save\');\n	}\n\n  /**\n   * Add and remove custom field from txp_image table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_image\',\n                \'ADD COLUMN jcr_image_custom VARCHAR(255) NULL AFTER thumb_h\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_image\',\n                \'DROP COLUMN jcr_image_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for image custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current image\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_image_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_image_custom\', fInput(\'text\', \'jcr_image_custom\', $jcr_image_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_image_custom\'), \'jcr_image_custom\').n;\n	}\n\n	/**\n	 * Save additional image custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_image_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_image\',\n		  \"jcr_image_custom = \'$jcr_image_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_image_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_image_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom image field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string  custom field output\n   * <code>\n   *        <txp:jcr_image_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_image_custom($atts)\n    {\n        global $thisimage;\n\n        assert_image();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thisimage[\'jcr_image_custom\'])\n            : $thisimage[\'jcr_image_custom\'];\n    }', 'd76549833890b5185e36f47e99aa9a18', '#@admin\n#@language en-gb\njcr_image_custom => Video ID\n#@language de-de\njcr_image_custom => Video ID\n', '', 1, 5, 2),
('jcr_link_custom', 0, 'jcr / txpbuilders', 'http://txp.builders', '0.1.1', 'Adds a custom field to the links panel', '<h1>jcr_link_custom</h1>\n\n<p>Adds a single extra custom field of up to 255 characters to the <a href=\"http://docs.textpattern.io/administration/links-panel\">Content › Links</a> panel and provides a corresponding tag to output the custom field.</p>\n\n\n<h2>Use cases</h2>\n\n<p>Use whenever extra information needs to be stored with a link. For example:</p>\n\n<ul>\n	<li>Store a txp image ID number and use it to attach a logo to a link.</li>\n	<li>Store associated details, for example the location of a linked organisation.</li>\n	<li>…</li>\n</ul>\n\n\n<h2 id=\"installation\">Installation</h2>\n\n<p>Paste the code into the  <em>Admin › Plugins</em> panel, install and enable the plugin.</p>\n\n\n<h2 id=\"tags\">Tags</h2>\n\n<pre><code>&lt;txp:jcr_link_custom /&gt;</code></pre>\n\n<p>Outputs the content of the link custom field.</p>\n\n<h3>Tag attributes</h3>\n\n<p><strong>escape</strong><br />\nEscape <span class=\"caps\">HTML</span> entities such as <code>&lt;</code>, <code>&gt;</code> and <code>&amp;</code> prior to echoing the field contents.<br />\nExample: Use <code>escape=&quot;&quot;</code> to suppress conversion. Default: <code>html</code>.</p>\n\n\n<h2 id=\"examples\">Example</h2>\n\n<p>Produce a list of linked partner logos (from link assigned to the link category &#8220;partners&#8221;):</p>\n\n<pre><code>&lt;txp:linklist wraptag=&quot;ul&quot; break=&quot;li&quot; category=&quot;partners&quot;&gt;\n  &lt;a href=&quot;&lt;txp:link_url /&gt;&quot; title=&quot;&lt;txp:link_name /&gt;&quot;&gt;&lt;txp:image id=&#39;&lt;txp:jcr_link_custom /&gt;&#39; /&gt;&lt;/a&gt;\n&lt;/txp:linklist&gt;</code></pre>\n\n<p>when the link custom field is used to store the Image ID# of the logo.</p>\n\n\n<h2>Changing the label of the custom field</h2>\n\n<p>The name of custom field can be changed by specifying a new label using the <em>Install from Textpack</em> field in the <a href=\"http://docs.textpattern.io/administration/languages-panel\">Admin › Languages</a> panel. Enter your own information in the following pattern and click <strong>Upload</strong>:</p>\n\n<pre><code>#@admin\n#@language en-gb\njcr_link_custom =&gt; Your label</code></pre>\n\n<p>replacing <code>en-gb</code> with your own language and <code>Your label</code> with your own desired label.</p>\n\n\n<h2 id=\"deinstallation\">De-installation</h2>\n\n<p>The plugin cleans up after itself: deinstalling the plugin removes the extra column from the database. To stop using the plugin but keep the database tables, just disable (deactivate) the plugin but don&#8217;t delete it.</p>\n\n\n<h2 id=\"changelog\">Changelog</h2>\n\n<h3>Version 0.1.1 – 2016/12/05</h3>\n\n<ul>\n	<li>Remedy table not being created on install</li>\n</ul>\n\n<h3>Version 0.1 – 2016/03/04</h3>\n\n<ul>\n	<li>First release</li>\n</ul>\n\n\n<h2 id=\"credits\">Credits</h2>\n\n<p>Robert Wetzlmayr’s <a href=\"https://github.com/rwetzlmayr/wet_profile\">wet_profile</a> plugin for the starting point, and further examples by <a href=\"http://www.stefdawson.com\">Stef Dawson</a> and <a href=\"https://github.com/gocom\">Jukka Svahn</a>.</p>', 'class jcr_link_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_link_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'link_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'link\', \'link_save\');\n	}\n\n  /**\n   * Add and remove custom field from txp_link table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_link\',\n                \'ADD COLUMN jcr_link_custom VARCHAR(255) NULL AFTER author\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_link\',\n                \'DROP COLUMN jcr_link_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for link custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current link\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_link_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_link_custom\', fInput(\'text\', \'jcr_link_custom\', $jcr_link_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_link_custom\'), \'jcr_link_custom\').n;\n	}\n\n	/**\n	 * Save additional link custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_link_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_link\',\n		  \"jcr_link_custom = \'$jcr_link_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_link_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_link_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom link field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string  custom field output\n   * <code>\n   *        <txp:jcr_link_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_link_custom($atts)\n    {\n        global $thislink;\n\n        assert_link();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thislink[\'jcr_link_custom\'])\n            : $thislink[\'jcr_link_custom\'];\n    }', 'class jcr_link_custom\n{\n	/**\n	 * Initialise.\n	 */\n	 function __construct()\n	{\n		register_callback(array(__CLASS__, \'lifecycle\'), \'plugin_lifecycle.jcr_link_custom\');\n		register_callback(array(__CLASS__, \'ui\'), \'link_ui\', \'extend_detail_form\');\n		register_callback(array(__CLASS__, \'save\'), \'link\', \'link_save\');\n	}\n\n  /**\n   * Add and remove custom field from txp_link table.\n   *\n   * @param $event string\n   * @param $step string  The lifecycle phase of this plugin\n   */\n  public static function lifecycle($event, $step)\n  {\n      switch ($step) {\n          case \'enabled\':\n              break;\n          case \'disabled\':\n              break;\n          case \'installed\':\n              safe_alter(\n                \'txp_link\',\n                \'ADD COLUMN jcr_link_custom VARCHAR(255) NULL AFTER author\'\n              );\n              break;\n          case \'deleted\':\n              safe_alter(\n                \'txp_link\',\n                \'DROP COLUMN jcr_link_custom\'\n              );\n              break;\n      }\n      return;\n  }\n\n	/**\n	 * Paint additional fields for link custom field\n	 *\n	 * @param $event string\n	 * @param $step string\n	 * @param $dummy string\n	 * @param $rs array The current link\'s data\n	 * @return string\n	 */\n	public static function ui($event, $step, $dummy, $rs)\n	{\n		extract(lAtts(array(\n			\'jcr_link_custom\' => \'\'\n		), $rs, 0));\n\n		return\n			inputLabel(\'jcr_link_custom\', fInput(\'text\', \'jcr_link_custom\', $jcr_link_custom, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'jcr_link_custom\'), \'jcr_link_custom\').n;\n	}\n\n	/**\n	 * Save additional link custom fields\n	 *\n	 * @param $event string\n	 * @param $step string\n	 */\n	public static function save($event, $step)\n	{\n		extract(doSlash(psa(array(\'jcr_link_custom\', \'id\'))));\n		$id = assert_int($id);\n		safe_update(\'txp_link\',\n		  \"jcr_link_custom = \'$jcr_link_custom\'\",\n			\"id = $id\"\n		);\n	}\n}\n\nif (txpinterface === \'admin\') {\n\n    new jcr_link_custom;\n\n} elseif (txpinterface === \'public\') {\n\n    if (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n        Txp::get(\'\\Textpattern\\Tag\\Registry\')\n            ->register(\'jcr_link_custom\');\n    }\n\n}\n\n  /**\n   * Public tag: Output custom link field\n   * @param  string $atts[escape] Convert special characters to HTML entities.\n   * @return string  custom field output\n   * <code>\n   *        <txp:jcr_link_custom escape=\"html\" />\n   * </code>\n   */\n\n    function jcr_link_custom($atts)\n    {\n        global $thislink;\n\n        assert_link();\n\n        extract(lAtts(array(\n            \'escape\' => \'html\',\n        ), $atts));\n\n        return ($escape == \'html\')\n            ? txpspecialchars($thislink[\'jcr_link_custom\'])\n            : $thislink[\'jcr_link_custom\'];\n    }', '6a408ed0a8fe80a4b3cf5a28b9ed08b4', '#@admin\n#@language en-gb\njcr_link_custom => Image ID\n#@language de-de\njcr_link_custom => Bild ID\n', '', 1, 5, 2),
('rah_privileges', 0, 'Jukka Svahn', 'http://rahforum.biz', '0.1.3', 'Configure admin-side user-group privileges', '<h1>rah_privileges</h1>\n\n<p><a href=\"https://github.com/gocom/rah_privileges/releases\" rel=\"nofollow\">Download</a> | <a href=\"https://packagist.org/packages/rah/rah_privileges\" rel=\"nofollow\">Packagist</a> | <a href=\"http://rahforum.biz/donate/rah_privileges\" rel=\"nofollow\">Donate</a></p>\n\n<p>Configure admin-side user-group permissions from Textpattern&#8217;s Advanced Preferences panel.</p>\n\n<h2>Install</h2>\n\n<p>Using <a href=\"http://getcomposer.org\" rel=\"nofollow\">Composer</a>:</p>\n\n<pre><code>$ composer require rah/rah_privileges:*</code></pre>\n\n<p>Or <a href=\"https://github.com/gocom/rah_privileges/releases\" rel=\"nofollow\">download</a> a plugin package.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern 4.4.1 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.2.0 or newer.</li>\n</ul>\n\n<h2>Toolshed notice</h2>\n\n<p>This is a toolshed project. Experimental and not part of the main supported product line of Rah.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.1.3 &#8211; 2017/02/03 – jakob</h3>\n\n<ul>\n	<li>Switched to type 5 plugin (thanks etc).</li>\n</ul>\n\n<h3>Version 0.1.2 &#8211; 2016/11/23 – jakob</h3>\n\n<ul>\n	<li>Updates for v.4.6 prefs pane and layout corrections.</li>\n</ul>\n\n<h3>Version 0.1.1 &#8211; 2015/03/04</h3>\n\n<ul>\n	<li>Doesn&#8217;t unnecessarily load the plugin on non-admin pages.</li>\n</ul>\n\n<h3>Version 0.1.0 &#8211; 2015/03/04</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/*\n * rah_privileges - Configure admin-side privileges\n * https://github.com/gocom/rah_privileges\n *\n * Copyright (C) 2015 Jukka Svahn\n *\n * This file is part of rah_privileges.\n *\n * rah_privileges is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_privileges is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with hpw_admincss. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * The plugin class.\n *\n * @internal\n */\n\nclass Rah_Privileges\n{\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        add_privs(\'prefs.rah_privs\', \'1\');\n        register_callback(array($this, \'install\'), \'plugin_lifecycle.rah_privileges\', \'installed\');\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.rah_privileges\', \'deleted\');\n        register_callback(array($this, \'savePrefs\'), \'prefs\', \'\', 1);\n        register_callback(array($this, \'inject_css\'), \'admin_side\', \'head_end\');\n        $this->mergePrivileges();\n    }\n\n    /**\n     * Installer.\n     */\n\n    public function install()\n    {\n        if (get_pref(\'rah_privileges_privs\', false) === false) {\n            if (defined(\'PREF_PLUGIN\')) {\n                set_pref(\'rah_privileges_privs\', 0, \'rah_privs\', PREF_PLUGIN, \'rah_privileges_input\', 80);\n            } else {\n                set_pref(\'rah_privileges_privs\', 0, \'rah_privs\', PREF_ADVANCED, \'rah_privileges_input\', 80);\n            }\n        }\n    }\n\n    /**\n     * Uninstaller.\n     */\n\n    public function uninstall()\n    {\n        safe_delete(\'txp_prefs\', \"name like \'rah\\_privileges\\_%\'\");\n    }\n\n    /**\n     * Merges privileges table with our overwrites.\n     */\n\n    public function mergePrivileges()\n    {\n        global $txp_permissions, $event;\n\n        if (!get_pref(\'rah_privileges_privs\')) {\n            return;\n        }\n\n        $privs = json_decode(get_pref(\'rah_privileges_privs\'), true);\n\n        if (!is_array($privs)) {\n            return;\n        }\n\n        if ($event === \'prefs\') {\n            unset(\n                $privs[\'prefs\'],\n                $privs[\'prefs.rah_privs\']\n            );\n        }\n\n        foreach ($privs as $resource => $groups) {\n            if (!$groups) {\n                $txp_permissions[$resource] = null;\n            } else {\n                $txp_permissions[$resource] = implode(\',\', (array) $groups);\n            }\n        }\n    }\n\n    /**\n     * Saves privileges configuration.\n     */\n\n    public function savePrefs()\n    {\n        global $prefs;\n\n        if (!isset($_POST[\'rah_privileges_resource_0\'])) {\n            return;\n        }\n\n        $data = array();\n\n        foreach ($_POST as $name => $value) {\n            if (strpos($name, \'rah_privileges_resource_\') === 0) {\n                $index = substr($name, strlen(\'rah_privileges_resource_\'));\n                $groups = ps(\'rah_privileges_groups_\'.$index);\n                $resource = (string) ps($name);\n                $data[$resource] = $groups;\n                unset($_POST[\'rah_privileges_groups_\'.$index], $_POST[$name]);\n            }\n        }\n\n        $prefs[\'rah_privileges_privs\'] = $_POST[\'rah_privileges_privs\'] = json_encode($data);\n        $this->mergePrivileges();\n    }\n  \n    /**\n     * Inject style rules into the head of the page.\n     *\n     * @return string      Style rules, or nothing if not the correct $event\n     */\n\n    public function inject_css()\n    {\n        global $event;\n\n        if ($event === \'prefs\') {\n            $rah_plugin_styles = $this->get_style_rules();\n\n            echo \'<style>\' . $rah_plugin_styles[\'rah_privileges\'] . \'</style>\';\n        }\n\n        return;\n    }\n\n    /**\n     * CSS definitions: hopefully kind to themers.\n     *\n     * @return string      Style rules\n     */\n\n    protected function get_style_rules()\n    {\n        $rah_plugin_styles = array(\n            \'rah_privileges\' => \'\n#prefs-rah_privileges_privs { \n  display: block;\n}\n.rah_privileges-checkbox-item {\n  display: inline-block;\n  white-space: nowrap;\n}\n.rah_privileges-checkbox-item .checkbox + label {\n    padding: 0 2em 0 0.5em;\n}\n            \',\n        );\n\n        return $rah_plugin_styles;\n    }\n}\n\n/**\n * Renders input for setting privilege settings.\n *\n * @return string HTML widget\n */\n\nfunction rah_privileges_input()\n{\n    global $txp_permissions, $plugin_areas;\n\n    $permissions = $txp_permissions;\n    $mergedpermissions = $out = $panels = array();\n    $index = 0;\n    $levels = get_groups();\n\n    unset($levels[0]);\n\n    $labels = array_keys($permissions);\n    $panels = array_combine(array_values($labels), array_fill(0, count($labels), 1));\n    $labels = array_combine($labels, $labels);\n\n    if (get_pref(\'rah_privileges_privs\')) {\n        $mergedpermissions = json_decode(get_pref(\'rah_privileges_privs\'), true);\n    }\n\n    foreach (areas() as $area => $events) {\n        foreach ($events as $title => $event) {\n            if (array_key_exists($event, $labels)) {\n                $labels[$event] = $title;\n                $panels[$event] = 0;\n            }\n        }\n    }\n\n    array_multisort($panels, SORT_ASC, SORT_NUMERIC, $labels, SORT_ASC, SORT_STRING, $permissions);\n\n    foreach ($permissions as $resource => $groups) {\n        $out[] = hInput(\'rah_privileges_resource_\'.$index, $resource);\n\n        if ($index !== 0) {\n            $out[] = br.br;\n        }\n\n        $out[] = tag($labels[$resource], \'strong\').br;\n\n        if (isset($mergedpermissions[$resource])) {\n            $groups = $mergedpermissions[$resource];\n        } elseif ($groups !== null) {\n            $groups = explode(\',\', (string) $groups);\n        }\n\n        foreach ($levels as $group => $label) {\n            $checked = is_array($groups) && in_array($group, $groups);\n            $name = \'rah_privileges_groups_\'.$index.\'[]\';\n            $id = \'rah_privileges_groups_\'.$index.\'_\'.intval($group);\n\n            $out[] = \'<div class=\"rah_privileges-checkbox-item\">\';\n            $out[] = checkbox(\n                $name,\n                $group,\n                $checked,\n                \'\',\n                $id\n            );\n\n            $out[] = \'<label for=\"\'.$id.\'\">\'.$label.\'</label>\';\n            $out[] = \'</div>\';\n        }\n\n        $index++;\n    }\n\n    return implode(\'\', $out);\n}\n\nnew Rah_Privileges();\n', '\n\n/*\n * rah_privileges - Configure admin-side privileges\n * https://github.com/gocom/rah_privileges\n *\n * Copyright (C) 2015 Jukka Svahn\n *\n * This file is part of rah_privileges.\n *\n * rah_privileges is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * rah_privileges is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with hpw_admincss. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * The plugin class.\n *\n * @internal\n */\n\nclass Rah_Privileges\n{\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        add_privs(\'prefs.rah_privs\', \'1\');\n        register_callback(array($this, \'install\'), \'plugin_lifecycle.rah_privileges\', \'installed\');\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.rah_privileges\', \'deleted\');\n        register_callback(array($this, \'savePrefs\'), \'prefs\', \'\', 1);\n        register_callback(array($this, \'inject_css\'), \'admin_side\', \'head_end\');\n        $this->mergePrivileges();\n    }\n\n    /**\n     * Installer.\n     */\n\n    public function install()\n    {\n        if (get_pref(\'rah_privileges_privs\', false) === false) {\n            if (defined(\'PREF_PLUGIN\')) {\n                set_pref(\'rah_privileges_privs\', 0, \'rah_privs\', PREF_PLUGIN, \'rah_privileges_input\', 80);\n            } else {\n                set_pref(\'rah_privileges_privs\', 0, \'rah_privs\', PREF_ADVANCED, \'rah_privileges_input\', 80);\n            }\n        }\n    }\n\n    /**\n     * Uninstaller.\n     */\n\n    public function uninstall()\n    {\n        safe_delete(\'txp_prefs\', \"name like \'rah\\_privileges\\_%\'\");\n    }\n\n    /**\n     * Merges privileges table with our overwrites.\n     */\n\n    public function mergePrivileges()\n    {\n        global $txp_permissions, $event;\n\n        if (!get_pref(\'rah_privileges_privs\')) {\n            return;\n        }\n\n        $privs = json_decode(get_pref(\'rah_privileges_privs\'), true);\n\n        if (!is_array($privs)) {\n            return;\n        }\n\n        if ($event === \'prefs\') {\n            unset(\n                $privs[\'prefs\'],\n                $privs[\'prefs.rah_privs\']\n            );\n        }\n\n        foreach ($privs as $resource => $groups) {\n            if (!$groups) {\n                $txp_permissions[$resource] = null;\n            } else {\n                $txp_permissions[$resource] = implode(\',\', (array) $groups);\n            }\n        }\n    }\n\n    /**\n     * Saves privileges configuration.\n     */\n\n    public function savePrefs()\n    {\n        global $prefs;\n\n        if (!isset($_POST[\'rah_privileges_resource_0\'])) {\n            return;\n        }\n\n        $data = array();\n\n        foreach ($_POST as $name => $value) {\n            if (strpos($name, \'rah_privileges_resource_\') === 0) {\n                $index = substr($name, strlen(\'rah_privileges_resource_\'));\n                $groups = ps(\'rah_privileges_groups_\'.$index);\n                $resource = (string) ps($name);\n                $data[$resource] = $groups;\n                unset($_POST[\'rah_privileges_groups_\'.$index], $_POST[$name]);\n            }\n        }\n\n        $prefs[\'rah_privileges_privs\'] = $_POST[\'rah_privileges_privs\'] = json_encode($data);\n        $this->mergePrivileges();\n    }\n  \n    /**\n     * Inject style rules into the head of the page.\n     *\n     * @return string      Style rules, or nothing if not the correct $event\n     */\n\n    public function inject_css()\n    {\n        global $event;\n\n        if ($event === \'prefs\') {\n            $rah_plugin_styles = $this->get_style_rules();\n\n            echo \'<style>\' . $rah_plugin_styles[\'rah_privileges\'] . \'</style>\';\n        }\n\n        return;\n    }\n\n    /**\n     * CSS definitions: hopefully kind to themers.\n     *\n     * @return string      Style rules\n     */\n\n    protected function get_style_rules()\n    {\n        $rah_plugin_styles = array(\n            \'rah_privileges\' => \'\n#prefs-rah_privileges_privs { \n  display: block;\n}\n.rah_privileges-checkbox-item {\n  display: inline-block;\n  white-space: nowrap;\n}\n.rah_privileges-checkbox-item .checkbox + label {\n    padding: 0 2em 0 0.5em;\n}\n            \',\n        );\n\n        return $rah_plugin_styles;\n    }\n}\n\n/**\n * Renders input for setting privilege settings.\n *\n * @return string HTML widget\n */\n\nfunction rah_privileges_input()\n{\n    global $txp_permissions, $plugin_areas;\n\n    $permissions = $txp_permissions;\n    $mergedpermissions = $out = $panels = array();\n    $index = 0;\n    $levels = get_groups();\n\n    unset($levels[0]);\n\n    $labels = array_keys($permissions);\n    $panels = array_combine(array_values($labels), array_fill(0, count($labels), 1));\n    $labels = array_combine($labels, $labels);\n\n    if (get_pref(\'rah_privileges_privs\')) {\n        $mergedpermissions = json_decode(get_pref(\'rah_privileges_privs\'), true);\n    }\n\n    foreach (areas() as $area => $events) {\n        foreach ($events as $title => $event) {\n            if (array_key_exists($event, $labels)) {\n                $labels[$event] = $title;\n                $panels[$event] = 0;\n            }\n        }\n    }\n\n    array_multisort($panels, SORT_ASC, SORT_NUMERIC, $labels, SORT_ASC, SORT_STRING, $permissions);\n\n    foreach ($permissions as $resource => $groups) {\n        $out[] = hInput(\'rah_privileges_resource_\'.$index, $resource);\n\n        if ($index !== 0) {\n            $out[] = br.br;\n        }\n\n        $out[] = tag($labels[$resource], \'strong\').br;\n\n        if (isset($mergedpermissions[$resource])) {\n            $groups = $mergedpermissions[$resource];\n        } elseif ($groups !== null) {\n            $groups = explode(\',\', (string) $groups);\n        }\n\n        foreach ($levels as $group => $label) {\n            $checked = is_array($groups) && in_array($group, $groups);\n            $name = \'rah_privileges_groups_\'.$index.\'[]\';\n            $id = \'rah_privileges_groups_\'.$index.\'_\'.intval($group);\n\n            $out[] = \'<div class=\"rah_privileges-checkbox-item\">\';\n            $out[] = checkbox(\n                $name,\n                $group,\n                $checked,\n                \'\',\n                $id\n            );\n\n            $out[] = \'<label for=\"\'.$id.\'\">\'.$label.\'</label>\';\n            $out[] = \'</div>\';\n        }\n\n        $index++;\n    }\n\n    return implode(\'\', $out);\n}\n\nnew Rah_Privileges();\n', 'ca12cf648c0a3cb17715c4d2eb1c9723', '#@admin\nrah_privs => User Privileges\nrah_privileges_privs => Set account-level rights for access to textpattern’s actions or panels\n\n#@admin\n#@language fi-fi\nrah_privs => Käyttöoikeudet\nrah_privileges_privs => Rastita käyttäjätasot joilla tulisi olla oikeudet toimintoon\n\n#@admin\n#@language de-DE\nrah_privs => Benutzerrechte\nrah_privileges_privs => Benutzerrechte verwalten für Admin-Bereiche bzw. Aktionen\n', '', 5, 1, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('sed_textile_upgrade', 0, 'Netcarver + jcr', 'https://github.com/jools-r/sed_textile_upgrade', '0.2', 'Facilitates textile upgrades to your Textpattern v4.7+ article body and excerpt texts', '<h1>sed_textile_upgrade</h1>\n\n<p><strong>For Textpattern v4.7+ only</strong></p>\n\n<p>Batch converts all your site articles marked as textile encoded, then self-terminates. Note, it makes a backup of your <code>textpattern</code> table before it does the conversion.</p>\n\n<p>To upgrade to a new version of textile:</p>\n\n<ul>\n	<li>If required, update your textpattern installation&#8217;s version of Textile in <code>/textpattern/vendors/Netcarver/Textile</code>.</li>\n	<li>Install the plugin.</li>\n	<li>Activate the plugin and navigate to any other tab.</li>\n	<li>Wait while your articles are converted.</li>\n</ul>\n\n<p>That&#8217;s it. You should now:</p>\n\n<ul>\n	<li>find a new table, <code>textpattern_pre_textile_upgrade</code> which is a backup of your original <code>textpattern</code> table</li>\n	<li>have all your articles that were originally marked as using textile re-encoded using the latest version</li>\n	<li>no longer have this plugin installed.</li>\n</ul>\n\n<p>Post upgrade:</p>\n\n<p>Please <strong>check all is well with your freshly updated articles.</strong> If not, please use phpmyadmin (or similar) to rename your textpattern table to something (don’t just drop it) and then rename <code>textpattern_pre_textile_update</code> to <code>textpattern</code>.</p>\n\n<p><strong>NB:</strong> You won’t be able to run the script again until you remove the table <code>textpattern_pre_textile_upgrade</code>.</p>\n\n<h2>Changelog</h2>\n\n<ul>\n	<li><strong>v0.20</strong> – update for Textpattern v4.7+ new textile parser.</li>\n</ul>', 'global $event;\n\nif( @txpinterface === \'admin\' )\n{\n	register_callback(\'sed_textile_upgrade_installed\', \'plugin_lifecycle.sed_textile_upgrade\', \'installed\' );\n}\n\n\n#\n#	Installation handler...\n#\nfunction sed_textile_upgrade_installed( $evt, $stp )\n{\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"admin-themes/hive/assets/css/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div>\n		<h1>** WARNING: <em>sed_textile_upgrade attempts to re-process ALL articles marked as using textile.</em> **</h1>\n		<p>If you make the sed_textile_upgrade plugin <strong>ACTIVE</strong> (at any time),\n        a backup of your textpattern table will be made as textpattern_pre_textile_upgrade and then\n		all your site content marked as using textile encoding will be run through textile.\n		To make use of this feature to upgrade your site to a new version of textile, first overwrite your textpattern\n		installation\'s old copy of textile with the newer version then ENABLE this plugin and navigate to any other tab.</p>\n		<p>Clicking <strong>OK</strong> will take you back to the TXP Plugins tab -- please read the help page before activating.</p>\n		<p><em>Decide what you want to do when you get there:</em></p>\n		<ul>\n			<li>Make the plugin <strong>ACTIVE</strong> when you are ready to re-encode your textile articles&#8230;</li>\n			<li>OR <strong>DELETE</strong> the plugin to prevent any possible re-encoding of your content&#8230;</li>\n			<li>OR leave the plugin <strong>INACTIVE</strong> to do nothing yet.</li>\n		</ul>\n		<form action=\"index.php\" method=\"GET\">\n			<input type=\"hidden\" name=\"event\" value=\"plugin\">\n			<input value=\"OK\" name=\"sed_textile_upgrade_warning\" class=\"publish\" type=\"submit\">\n		</form>\n	</div>\n</body>\n</html>\nHTML;\n	exit(0);\n}\n\n\nif( @txpinterface === \'admin\' && $event !== \'plugin\' ) # Should only happen if plugin enabled.\n{\n	$debug = 0;\n\n	#\n	#		Backup the textpattern table (if possible)\n	#\n	$sql = \"CREATE TABLE `textpattern_pre_textile_upgrade` LIKE `textpattern`;\";\n	$result = safe_query( $sql, $debug );\n	if( $result ) {\n		$sql = \"INSERT INTO `textpattern_pre_textile_upgrade` SELECT * FROM `textpattern`;\";\n		$result = safe_query( $sql, $debug );\n		if( $result ) {\n			#\n			#		Table backup went ok, so go ahead and re-textile all articles that need it...\n			#\n			$list = getRows( \"\n				SELECT\n					`ID`, `Body`, `textile_body`, `Excerpt`, `textile_excerpt`, `Title`\n				FROM\n					textpattern\n				WHERE\n					\'1\' = `textile_body` or \'1\' = `textile_excerpt`\n				\", $debug );\n\n			if( !empty( $list ) ) {\n				$textile = new \\Textpattern\\Textile\\Parser();\n				foreach( $list as $info ) {\n					if( 1 == $info[\'textile_body\'] ) {\n						$set[] = \"Body_html    = \'\" . doSlash( $textile->parse( $info[\'Body\']    ) ) . \"\'\";\n					}\n\n					if( 1 == $info[\'textile_excerpt\'] )\n						$set[] = \"Excerpt_html = \'\" . doSlash( $textile->parse( $info[\'Excerpt\'] ) ) . \"\'\";\n\n					$set = implode( \', \', $set );\n					$result = safe_update( \'textpattern\', $set, \"ID = {$info[\'ID\']}\", $debug );\n					unset( $set );\n				}\n			} else {\n				echo \"No textiled articles or excerpts were found.\";\n			}\n		} else {\n			echo \"Could not INSERT INTO...\";\n		}\n	} else {\n		echo \"Could not CREATE TABLE...\";\n	}\n\n\n	#\n	# Finally, we self-destruct unless debugging and redirect to the article list tab...\n	#\n	safe_delete( \'txp_plugin\', \"`name`=\'sed_textile_upgrade\'\", $debug );\n	if( !$debug ) {\n		while( @ob_end_clean() );\n		header(\'Location: \'.ahu.\'index.php?event=list\');\n		header(\'Connection: close\');\n		header(\'Content-Length: 0\');\n	}\n}', 'global $event;\n\nif( @txpinterface === \'admin\' )\n{\n	register_callback(\'sed_textile_upgrade_installed\', \'plugin_lifecycle.sed_textile_upgrade\', \'installed\' );\n}\n\n\n#\n#	Installation handler...\n#\nfunction sed_textile_upgrade_installed( $evt, $stp )\n{\n	echo<<<HTML\n<html>\n<head>\n	<title>WARNING: Site content at risk.</title>\n	<link href=\"admin-themes/hive/assets/css/textpattern.css\" rel=\"stylesheet\" type=\"text/css\" />\n	<style type=\"text/css\">\n		div { width:40em; margin:4em auto; padding:2em 2em 1em; border:0.1em solid #ccc; text-align:center }\n		ul { margin:0 0 2em; list-style:none }\n		li { margin:0 0 0.5em 0 }\n	</style>\n</head>\n<body>\n	<div>\n		<h1>** WARNING: <em>sed_textile_upgrade attempts to re-process ALL articles marked as using textile.</em> **</h1>\n		<p>If you make the sed_textile_upgrade plugin <strong>ACTIVE</strong> (at any time),\n        a backup of your textpattern table will be made as textpattern_pre_textile_upgrade and then\n		all your site content marked as using textile encoding will be run through textile.\n		To make use of this feature to upgrade your site to a new version of textile, first overwrite your textpattern\n		installation\'s old copy of textile with the newer version then ENABLE this plugin and navigate to any other tab.</p>\n		<p>Clicking <strong>OK</strong> will take you back to the TXP Plugins tab -- please read the help page before activating.</p>\n		<p><em>Decide what you want to do when you get there:</em></p>\n		<ul>\n			<li>Make the plugin <strong>ACTIVE</strong> when you are ready to re-encode your textile articles&#8230;</li>\n			<li>OR <strong>DELETE</strong> the plugin to prevent any possible re-encoding of your content&#8230;</li>\n			<li>OR leave the plugin <strong>INACTIVE</strong> to do nothing yet.</li>\n		</ul>\n		<form action=\"index.php\" method=\"GET\">\n			<input type=\"hidden\" name=\"event\" value=\"plugin\">\n			<input value=\"OK\" name=\"sed_textile_upgrade_warning\" class=\"publish\" type=\"submit\">\n		</form>\n	</div>\n</body>\n</html>\nHTML;\n	exit(0);\n}\n\n\nif( @txpinterface === \'admin\' && $event !== \'plugin\' ) # Should only happen if plugin enabled.\n{\n	$debug = 0;\n\n	#\n	#		Backup the textpattern table (if possible)\n	#\n	$sql = \"CREATE TABLE `textpattern_pre_textile_upgrade` LIKE `textpattern`;\";\n	$result = safe_query( $sql, $debug );\n	if( $result ) {\n		$sql = \"INSERT INTO `textpattern_pre_textile_upgrade` SELECT * FROM `textpattern`;\";\n		$result = safe_query( $sql, $debug );\n		if( $result ) {\n			#\n			#		Table backup went ok, so go ahead and re-textile all articles that need it...\n			#\n			$list = getRows( \"\n				SELECT\n					`ID`, `Body`, `textile_body`, `Excerpt`, `textile_excerpt`, `Title`\n				FROM\n					textpattern\n				WHERE\n					\'1\' = `textile_body` or \'1\' = `textile_excerpt`\n				\", $debug );\n\n			if( !empty( $list ) ) {\n				$textile = new \\Textpattern\\Textile\\Parser();\n				foreach( $list as $info ) {\n					if( 1 == $info[\'textile_body\'] ) {\n						$set[] = \"Body_html    = \'\" . doSlash( $textile->parse( $info[\'Body\']    ) ) . \"\'\";\n					}\n\n					if( 1 == $info[\'textile_excerpt\'] )\n						$set[] = \"Excerpt_html = \'\" . doSlash( $textile->parse( $info[\'Excerpt\'] ) ) . \"\'\";\n\n					$set = implode( \', \', $set );\n					$result = safe_update( \'textpattern\', $set, \"ID = {$info[\'ID\']}\", $debug );\n					unset( $set );\n				}\n			} else {\n				echo \"No textiled articles or excerpts were found.\";\n			}\n		} else {\n			echo \"Could not INSERT INTO...\";\n		}\n	} else {\n		echo \"Could not CREATE TABLE...\";\n	}\n\n\n	#\n	# Finally, we self-destruct unless debugging and redirect to the article list tab...\n	#\n	safe_delete( \'txp_plugin\', \"`name`=\'sed_textile_upgrade\'\", $debug );\n	if( !$debug ) {\n		while( @ob_end_clean() );\n		header(\'Location: \'.ahu.\'index.php?event=list\');\n		header(\'Connection: close\');\n		header(\'Content-Length: 0\');\n	}\n}', '7326d9cd3dfa480bb11b64600b7f1e79', '', '', 3, 1, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('mka_ical', 0, 'Martin Kozianka + jcr', 'http://github.com/jools-r/mka_ical', '0.7.0', 'Display events from an ical file (Google Calendar, etc.)', '<h1>mka_ical &#8211; Help</h1>\n\n<h3>Display ical calendars as event lists</h3>\n\n<p>mka_ical is a Textpattern plugin for displaying events from an iCal <span class=\"caps\">URL</span> (e.g. Google Calendar) or ics file using a standard or user-definable template.</p>\n\n<p>It&#8217;s also possible to specify a folder or a Textpattern category for images referenced in the feed. The image is drawn from the title of the event and the name of the image.</p>\n\n\n<h2>Tags</h2>\n\n<h3>Tag: mka_ical</h3>\n\n<p>Inserts a public calendar from an ics url or file, or a googlemail account. Can be used as a standalone or as a container tag.</p>\n\n<p>The following attributes are available:</p>\n\n<p><strong>url</strong> (string, default: “”, required)<br />\nUrl of the <span class=\"caps\">ICS</span> file</p>\n\n<p><strong>googlemail</strong><br />\nGoogleMail address. The user&#8217;s public google calendar will automatically be referenced.</p>\n\n<p><strong>limit</strong> (int, default: <code>0</code>, optional)<br />\nDefines how many events are shown. Defaults to 0 (= all)</p>\n\n<p><strong>pastevents</strong> (boolean, default: <code>FALSE</code>, optional)<br />\nDisplay past events</p>\n\n<p><strong>html_id</strong> (string, default: <code>icalevents</code>, optional)<br />\nSets the html id for the event list</p>\n\n<p><strong>class</strong><br />\nSets the <span class=\"caps\">CSS</span> class for the event list</p>\n\n<p><strong>details</strong> (boolean, default: <code>TRUE</code>, optional)<br />\nDisplay the details</p>\n\n<p><strong>cachingtime</strong> (int, default: <code>30</code> (minutes), optional)<br />\nDuration that local cache file persists</p>\n\n<p><strong>form</strong> (string, default: none, optional)<br />\nName of txp form to use to format each event item</p>\n\n<p><strong>fmttime</strong> (string, default: <code>%H:%M</code>, optional)<br />\nFormat string for the time</p>\n\n<p><strong>fmtdate</strong> (string, default: <code>%d.%m.%Y</code>, optional)<br />\nFormat string for the date</p>\n\n<p><strong>img</strong> (boolean, default: <code>FALSE</code>)<br />\nDisplay an image for each date (requires following attributes)</p>\n\n<p><strong>imgfolder</strong> (string, default: <code>images</code>, optional)<br />\nImage folder</p>\n\n<p><strong>defaultimg</strong> (string, default: <code>none.gif</code>, optional)<br />\nDefault image</p>\n\n\n<h3>Tag: mka_if_first_event</h3>\n\n<p>Use as a container tag. The tag will execute the contained statements if the displayed event is the <em>first</em> in the currently displayed calendar.</p>\n\n\n<h3>Tag: mka_if_last_event</h3>\n\n<p>Use as a container tag. The tag will execute the contained statements if the displayed event is the <em>last</em> in the currently displayed calendar.</p>\n\n\n<h3>Tag: mka_if_different</h3>\n\n<p>Use as a container tag. The tag will execute the contained statements when the value of the contained statement differs from the preceding value for that contained statement.</p>\n\n<p>If additionally processing the <code>{placeholder}</code> values, e.g. with mka_format_date, set the <code>key</code> attribute to a name of your choosing. See Example 4.</p>\n\n<p>Attribute:</p>\n\n<p><strong>key</strong> (string, default: empty, optional)<br />\nA name for your if_different instance.</p>\n\n\n<h3>Tag: mka_format_date</h3>\n\n<p>Use as a container tag around an existing date, time or datetime placeholder. The tag will reformat the date or time output according to a specified format. </p>\n\n<p>Attribute:</p>\n\n<p><strong>format</strong> (date parameter, default: empty)<br />\nUse the date formatting parameters of <a href=\"http://php.net/manual/en/function.strftime.php\" rel=\"nofollow\">strftime</a> to define the date and/or time format.</p>\n\n<p><em>Note: The <code>fmtdate</code> and <code>fmttime</code> attributes of <code>mka_ical</code> control how the placeholders <code>{start_date}</code>, <code>{end_date}</code> and <code>{start_time}</code> and <code>{endtime}</code> are formatted for the entire calendar. This tag allows you to override the date or time format.</em></p>\n\n\n<h2>Placeholder variables</h2>\n\n<dl>\n	<dt><code>{date}</code></dt>\n	<dd>Date (format according to “fmtdate” and “fmttime” attributes)</dd>\n	<dt><code>{start_datetime}</code></dt>\n	<dd>Start datetime in <code>%Y-%m-%d %H:%M</code> format</dd>\n	<dt><code>{start_date}</code></dt>\n	<dd>Start date (format according to “fmtdate” attribute)</dd>\n	<dt><code>{start_time}</code></dt>\n	<dd>Start time (format according to “fmttime” attribute)</dd>\n	<dt><code>{end_datetime}</code></dt>\n	<dd>End datetime in <code>%Y-%m-%d %H:%M</code> format</dd>\n	<dt><code>{end_date}</code></dt>\n	<dd>End date (format according to “fmtdate” attribute)</dd>\n	<dt><code>{end_time}</code></dt>\n	<dd>End time (format according to “fmttime” attribute)</dd>\n	<dt><code>{duration}</code></dt>\n	<dd>Duration (formatted as days:hours:minutes:seconds)</dd>\n	<dt><code>{title}</code></dt>\n	<dd>Title (ics summary entry)</dd>\n	<dt><code>{description}</code></dt>\n	<dd>Description</dd>\n	<dt><code>{location}</code></dt>\n	<dd>Location/venue</dd>\n	<dt><code>{uid}</code></dt>\n	<dd>Event unique ID</dd>\n	<dt><code>{imagesrc}</code></dt>\n	<dd>Link to image</dd>\n	<dt><code>{imageid}</code></dt>\n	<dd>ID# of image (when using images from categories)</dd>\n</dl>\n\n\n<h2>Examples</h2>\n\n\n<h3>Example 1 (using a public Google Calendar)</h3>\n\n<pre><code>&lt;txp:mka_ical googlemail=&quot;YOURADDRESSNAME@googlemail.com&quot; /&gt;</code></pre>\n\n<ul>\n	<li><strong>googlemail</strong>: Your GoogleMail address or name.</li>\n</ul>\n\n\n<h3>Example 2 (using a custom url and additional options)</h3>\n\n<pre><code>&lt;txp:mka_ical url=&quot;https://domain.com/url-of-ics-file/calendar.ics&quot;\n    pastevents=&quot;1&quot; img=&quot;1&quot; imgfolder=&quot;images/events/&quot; defaultimg=&quot;none.gif&quot; /&gt;</code></pre>\n\n<ul>\n	<li><strong>url</strong>: <span class=\"caps\">URL</span> of a calendar file</li>\n	<li><strong>pastevents</strong>: [0,1] Show past events?</li>\n	<li><strong>limit</strong>: [0,1,2,3,4,…] Show only X events</li>\n	<li><strong>img</strong>: [0,1] Show images?</li>\n	<li><strong>imgfolder</strong>: Where are the images?</li>\n	<li><strong>defaultimg</strong>: Name of the default image</li>\n</ul>\n\n\n<h3>Example 3 (mka_ical as a container tag)</h3>\n\n<pre><code>&lt;txp:mka_ical wraptag=&quot;&quot; break=&quot;&quot;\n    googlemail=&quot;YOURADDRESSNAME@googlemail.com&quot;&gt;\n    &lt;div class=&quot;h-event&quot;&gt; &lt;!-- microdata h-event --&gt;\n    &lt;h1 class=&quot;p-name&quot;&gt;{title}&lt;/h1&gt;\n    &lt;p class=&quot;h-event__meta&quot;&gt;\n    From &lt;time class=&quot;dt-start&quot; datetime=&quot;{start_datetime}&quot;&gt;{start_date} {start_time}&lt;/time&gt;\n        to &lt;time class=&quot;dt-end&quot; datetime=&quot;{end_datetime}&quot;&gt;{end_time}&lt;/time&gt;\n        at &lt;span class=&quot;p-location&quot;&gt;{location}&lt;/span&gt;\n    &lt;/p&gt;\n    &lt;img class=&quot;event-img&quot; src=&quot;{imagesrc}&quot; border=&quot;0&quot; align=&quot;left&quot;&gt;\n    &lt;p class=&quot;p-summary&quot;&gt;{description}&lt;/p&gt;\n    &lt;a class=&quot;u-url&quot; href=&quot;{event_url}&quot;&gt;More information&lt;/a&gt;\n    &lt;/div&gt;\n&lt;/txp:mka_ical&gt;</code></pre>\n\n<h3>Example 4 (“Month year” headings using mka_if_different and mka_format_date)</h3>\n\n<pre><code>&lt;txp:mka_ical url=&quot;calendar.ics&quot; wraptag=&quot;&quot; break=&quot;&quot;&gt;\n    &lt;txp:mka_if_different key=&quot;month_year&quot;&gt;&lt;h2&gt;&lt;txp:mka_format_date format=&quot;%B %Y&quot;&gt;{start_date}&lt;/txp:mka_format_date&gt;&lt;/h2&gt;&lt;/txp:mka_if_different&gt;\n    &lt;div class=&quot;h-event&quot;&gt;\n        &lt;p class=&quot;h-event__meta&quot;&gt;\n            &lt;time class=&quot;dt-start&quot; datetime=&quot;{start_datetime}&quot;&gt;{start_date}&lt;/time&gt; –\n            &lt;time class=&quot;dt-end&quot; datetime=&quot;{end_datetime}&quot;&gt;{end_time}&lt;/time&gt;,\n            &lt;span class=&quot;p-location&quot;&gt;{location}&lt;/span&gt;\n        &lt;/p&gt;\n        &lt;txp:evaluate wraptag=&quot;p&quot; class=&quot;description&quot;&gt;{description}&lt;/txp:evaluate&gt;\n    &lt;/div&gt;\n&lt;/txp:mka_ical&gt;</code></pre>\n\n\n<h3>Standard output</h3>\n\n<pre><code>&lt;span class=&quot;date&quot;&gt;{date}&lt;/span&gt;\n&lt;span class=&quot;title&quot;&gt;{title}&lt;/span&gt;\n&lt;span class=&quot;description&quot;&gt;{description}&lt;/span&gt;</code></pre>\n\n\n<h2>Changelog</h2>\n\n<ul>\n	<li><strong>0.7.0</strong> – Textpattern v4.7 and php 7 compatibility (jools-r)\n	<ul>\n		<li>Plugin cleanup and reconfiguration for rah_blobin/plugin compiler</li>\n		<li>Inclusion of updated ical parser library (php5.6+ compatibility) in plugin</li>\n		<li>Updated google calendar <span class=\"caps\">URL</span></li>\n		<li>New ‘mka_if_first_event’ tag</li>\n		<li>New ‘mka_if_last_event’ tag</li>\n		<li>New ‘mka_if_different’ tag</li>\n		<li>New ‘mka_format_date’ tag</li>\n		<li>Tag registration for Txp 4.6+</li>\n		<li>Updated help</li>\n		<li>Additional replacement placeholders</li>\n		<li>Attribute ‘cssid’ renamed to ‘html_id’ in line with other txp tags (cssid still accepted for now)</li>\n		<li>New ‘googlemaildomain’ attribute if using just google username but need to use ‘gmail’ rather than ‘googlemail’ (default setting)</li>\n		<li>Cached json file now located in /tmp folder (as defined in prefs), not /files</li>\n	</ul></li>\n	<li><strong>0.6.3</strong> – Debug details removed (Martin Kozianka)</li>\n	<li><strong>0.6.2</strong> &#8211; Output the date with <strong>strftime</strong>. Resolve: error when displaying without image (Martin Kozianka)</li>\n	<li><strong>0.6.1</strong> – First public version (Martin Kozianka)</li>\n</ul>\n\n\n<h2>Credits</h2>\n\n<p>This plugin was originally the work of Martin Kozianka (<a href=\"https://kozianka.de\" rel=\"nofollow\">kozianka.de</a>) and has since been updated to work with Textpattern 4.7+.</p>\n\n<p>It makes use of the ICal parsing library <a href=\"https://github.com/TKr/intouch-iCalendar\" rel=\"nofollow\">TKr/intouch-iCalendar</a> which is a fork of <a href=\"https://github.com/mfkahn/intouch-iCalendar\" rel=\"nofollow\">intouch-iCalendar</a> which is in turn based on <a href=\"http://sevengoslings.net/icalendar\" rel=\"nofollow\">SG-iCalendar</a> by Morten Fangel.</p>', '\n\n// TXP 4.6+ tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'mka_ical\')\n        ->register(\'mka_format_date\')\n        ->register(\'mka_if_first_event\')\n        ->register(\'mka_if_last_event\')\n        ->register(\'mka_if_different\')\n    ;\n}\n\n// -------------------------------------------------------------\nfunction mka_ical($atts, $thing = \'\')\n{\n    global $mka_this_ical_event;\n\n    extract(lAtts(array(\n        \'url\' => \'\',\n        \'cachingtime\' => 30,\n        \'googlemail\' => \'\',\n        \'googlemaildomain\' => \'googlemail\',\n        \'limit\' => 0,\n        \'pastevents\' => false,\n        \'fmttime\' => \'%H:%M\',\n        \'fmtdate\' => \'%d.%m.%Y\',\n        \'imgfolder\' => \'\',\n        \'imgcat\' => \'\',\n        \'defaultimage\' => \'none.gif\',\n        \'break\' => \'li\',\n        \'class\' => __FUNCTION__,\n        \'cssid\' => \'icalevents\',\n        \'html_id\' => \'icalevents\',\n        \'wraptag\' => \'ul\',\n        \'form\' => \'\'\n    ), $atts, false));\n\n    // Cater for legacy cssid -> html_id\n    // if cssid is specified and not default value, update html_id\n    if ($cssid != $html_id) {\n        $html_id = $cssid;\n    }\n\n    if (strlen($thing) > 0) {\n        // Use contained code\n        $body = $thing;\n    } elseif (!empty($form)) {\n        // OR: Get form if specified\n        $body = fetch_form($form);\n    } else {\n        // ELSE: Default output\n        $body =  \'<span class=\"date\">{date}</span>\'\n                .\'<span class=\"title\">{title}</span>\'\n                .\'<span class=\"description\">{description}</span>\';\n    }\n\n    // Get url from googlemail-address\n    if (strlen($googlemail) > 0) {\n        $tmp = explode(\"@\", $googlemail);\n        if (count($tmp > 1)) {\n            // Full email address: just encode \'@\' to \'%40\'\n            $gmail = rawurlencode($googlemail);\n        } else {\n            // Just username: construct email using attribute \"googlemaildomain\" (e.g. googlemail / gmail)\n            $gmail = $tmp[0].\"%40\".$googlemaildomain.\".com\";\n        }\n//      $urls[] = \"https://www.google.com/calendar/ical/\".$gmail.\"/public/basic.ics\";\n        $urls[] = \"https://calendar.google.com/calendar/ical/\".$gmail.\"/public/basic.ics\";\n    }\n\n    // Add given urls from attribute url\n    if (strlen($url) > 0) {\n        foreach (explode(\",\", $url) as $u) {\n            $urls[] = trim($u);\n        }\n    }\n\n    // No url --> nothing to show --> error message.\n    if (sizeof($urls) === 0) {\n        return \"mka_ical Error: No url specified.\";\n    }\n\n    // Get images from folder and/or category\n    $show_img = false;\n    if ((strlen($imgcat) > 0) || (strlen($imgfolder) > 0)) {\n        $images = array();\n\n        // Add images from given categories\n        if (strlen($imgcat) > 0) {\n            $images = mka_imgarrayfromcat($imgcat);\n        }\n\n        // Add images from given folder\n        if (file_exists($imgfolder)) {\n            $images = array_merge(mka_imgarrayfromfolder($imgfolder));\n        }\n\n        if (sizeof($images) > 0) {\n            $show_img = true;\n\n            // Get default image\n            $defimg = ical_defimg($defaultimage);\n        }\n    }\n\n    // Get events from cache\n    $events = checkCache($urls, $cachingtime);\n\n    // Start counter\n    $count = 0;\n    // Get uid of last item\n    $last_event = end($events)->uid;\n\n    // Loop through events\n    foreach ($events as $evt) {\n\n        // TODO :: recurrence auswerten\n\n        // Is event in future or ongoing?\n        // if (($evt->getEnd() > time()) && ($limit == 0  || $count < $limit ))\n        if (($pastevents || $evt->end > time()) && ($limit == 0 || $count < $limit)) {\n\n            // Advance counter\n            ++$count;\n\n            // First event\n            $mka_this_ical_event[\'is_first\'] = ($count == \'1\');\n\n            // Last event in array or last in limit\n            if (($evt->uid == $last_event) || ($count == $limit)) {\n                $mka_this_ical_event[\'is_last\'] = true;\n            }\n\n            // TODO :: TextileRestricted($text, $lite = 1, $noimage = 1, $rel = \'nofollow\')\n            $evt->sum = ical_formatText($evt->sum);\n            $evt->des = ical_formatText($evt->des);\n            $evt->loc = ical_formatText($evt->loc);\n\n            $dateView  = ical_getDateView($evt, $fmtdate, $fmttime);\n            $startDatetime = ical_getDateTime($evt, \"%Y-%m-%d %H:%M\", 0); // datetime format\n            $startDate = ical_getDateTime($evt, $fmtdate, 0);\n            $startTime = ical_getDateTime($evt, $fmttime, 0);\n            $endDatetime = ical_getDateTime($evt, \"%Y-%m-%d %H:%M\", 0); // datetime format\n            $endDate   = ical_getDateTime($evt, $fmtdate, 1);\n            $endTime   = ical_getDateTime($evt, $fmttime, 1);\n            $duration  = gmdate(\"z:H:i:s\", $evt->duration); // convert seconds to days:hours:minutes:seconds\n\n            $keys = array();\n            $vals = array();\n            if ($show_img) {\n                $image = ical_img($evt->sum, $images, $defimg);\n                $keys[] = \'{imagesrc}\';\n                $vals[] = $image[\'src\'];\n                $keys[] = \'{imageid}\';\n                $vals[] = $image[\'id\'];\n            }\n            $keys[] = \'{date}\';\n            $vals[] = $dateView;\n            $keys[] = \'{start_datetime}\';\n            $vals[] = $startDatetime;\n            $keys[] = \'{start_date}\';\n            $vals[] = $startDate;\n            $keys[] = \'{start_time}\';\n            $vals[] = $startTime;\n            $keys[] = \'{end_datetime}\';\n            $vals[] = $endDatetime;\n            $keys[] = \'{end_date}\';\n            $vals[] = $endDate;\n            $keys[] = \'{end_time}\';\n            $vals[] = $endTime;\n            $keys[] = \'{duration}\';\n            $vals[] = $duration;\n            $keys[] = \'{title}\';\n            $vals[] = $evt->sum;\n            $keys[] = \'{description}\';\n            $vals[] = $evt->des;\n            $keys[] = \'{location}\';\n            $vals[] = $evt->loc;\n            $keys[] = \'{uid}\';\n            $vals[] = $evt->uid;\n\n            $out[] = parse(str_replace($keys, $vals, $body));\n        }\n\n    }\n\n    return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n}\n\n// -------------------------------------------------------------\n\n/* Simple date reformatter */\n/* Use with {start_datetime} and {end_datetime} */\n/* date formats as per http://php.net/manual/en/function.strftime.php */\n\nfunction mka_format_date($atts, $thing = \'\')\n{\n    extract(lAtts(array(\n        \'format\' => \'\'\n    ), $atts, false));\n\n    if ($format === \'\') {\n        return $thing;\n    } else {\n        return strftime($format, strtotime($thing));\n    }\n}\n\n// -------------------------------------------------------------\nfunction mka_if_first_event($atts, $thing = null)\n{\n    global $mka_this_ical_event;\n    $x = !empty($mka_this_ical_event[\'is_first\']);\n    return isset($thing) ? parse($thing, $x) : $x;\n}\n\n// -------------------------------------------------------------\nfunction mka_if_last_event($atts, $thing = null)\n{\n    global $mka_this_ical_event;\n    $x = !empty($mka_this_ical_event[\'is_last\']);\n    return isset($thing) ? parse($thing, $x) : $x;\n}\n\n// -------------------------------------------------------------\nfunction mka_if_different($atts, $thing)\n{\n    static $last = array();\n\n    extract(lAtts(array(\'key\' => md5($thing)), $atts));\n    $out = parse($thing, 1);\n\n    if (empty($last[$key]) || $out != $last[$key]) {\n        return $last[$key] = $out;\n    } else {\n        return parse($thing, 0);\n    }\n}\n\n// -------------------------------------------------------------\nfunction ical_img($title, $images, $defimg)\n{\n    $search = array(\" \", \".\", \"-\");\n    $rep = array(\"\", \"\", \"\");\n    $t = str_replace($search, $rep, strtolower($title));\n    foreach ($images as $im) {\n        if (strpos($t, $im[\'name\']) !== false) {\n            return $im;\n        }\n    }\n    // return default image src\n    return $defimg;\n}\n\n// -------------------------------------------------------------\nfunction ical_getDateView($evt, $fmt_date, $fmt_time)\n{\n    $date_separator = \" – \";\n\n    $fmt_dati = $fmt_date.\" \".$fmt_time;\n    // event on one day\n    if (date(\"dmY\", $evt->start) == date(\"dmY\", $evt->end-1)) {\n        // no exact time definition\n        if (date(\"H:i\", $evt->start) == \"00:00\" && date(\"H:i\", $evt->end) == \"00:00\") {\n            $dateView = strftime($fmt_date, $evt->start);\n        } else {\n            $dateView = strftime($fmt_dati, $evt->start).$date_separator.strftime($fmt_time, $evt->start + $evt->duration);\n        }\n    } else {\n        $dateView  = strftime($fmt_date, $evt->start).$date_separator.strftime($fmt_date, $evt->end-1);\n    }\n    return $dateView;\n}\n\n// -------------------------------------------------------------\nfunction ical_getDateTime($evt, $formatter, $end)\n{\n    $dateView = ($end == 1) ? strftime($formatter, $evt->end) : strftime($formatter, $evt->start);\n    return $dateView;\n}\n\n// -------------------------------------------------------------\nfunction mka_makearr_sql($str, $type=\'string\', $delimeter =\',\')\n{\n    $ret = array();\n    $arr = explode($delimeter, $str);\n    foreach ($arr as $el) {\n        if ($type == \'string\') {\n            $ret[] = \"\'\".trim($el).\"\'\";\n        } else {\n            $ret[] = $el;\n        }\n    }\n    return $ret;\n}\n\n// -------------------------------------------------------------\nfunction mka_imgarrayfromcat($cats)\n{\n    $imgs = array();\n\n    $cats = mka_makearr_sql($cats);\n    $clause = \'category IN (\'.implode(\",\", $cats).\')\';\n    $attrs = \'id, name, ext\';\n    $rs = safe_rows($attrs, \'txp_image\', $clause);\n\n    $prefix = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\".$GLOBALS[\'prefs\'][\'img_dir\'].\"/\";\n\n    if (count($rs) != 0) {\n        foreach ($rs as $col) {\n            $col[\'src\'] = $prefix.$col[\'id\'].$col[\'ext\'];\n            $imgs[] = $col;\n        }\n    }\n    return $imgs;\n}\n\n// -------------------------------------------------------------\nfunction mka_imgarrayfromfolder($imgfolder)\n{\n    $imgs = array();\n    if ($handle = @opendir($imgfolder)) {\n        while (($file = @readdir($handle)) !== false) {\n            $img = array();\n            $parts = explode(\".\", $file);\n            $img[\'ext\']  = (count($parts) > 1) ? \".\".array_pop($parts) : \'\';\n            $img[\'name\'] = implode(\".\", $parts);\n            $img[\'src\'] = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\".$imgfolder.\"/\".$file;\n            if (strlen($img[\'name\']) > 0 && in_array(strtolower($img[\'ext\']), array(\".png\", \".jpg\", \".jpeg\", \".gif\"))) {\n                $imgs[] = $img;\n            }\n        }\n        closedir($handle);\n    }\n    return $imgs;\n}\n\n// -------------------------------------------------------------\nfunction ical_defimg($defimg)\n{\n    $url = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\";\n    $img = array();\n    // Is defimg id?\n    if (intval($defimg) !== 0) {\n        $id = intval($defimg);\n        $img = safe_row(\'id, name, ext\', \'txp_image\', \'id = \'.$id);\n    }\n\n    // Image with id not found?\n    if (sizeof($img) === 0) {\n        $parts = explode(\".\", basename($defimg));\n        $img[\'ext\']  = (count($parts) > 1) ? \".\".array_pop($parts) : \'\';\n        $img[\'name\'] = implode(\".\", $parts);\n        $img[\'src\'] = $url.$defimg;\n    } else {\n        $img[\"src\"] = $url.$GLOBALS[\'prefs\'][\'img_dir\'].\"/\".$img[\'id\'].$img[\'ext\'];\n    }\n    return $img;\n}\n\n// -------------------------------------------------------------\nfunction ical_shorten_link($string)\n{\n    $text_word_maxlength = 55;\n    if (count($string) == 2) {\n        $pre = \"\";\n        $url = $string[1];\n    } else {\n        $pre = $string[1];\n        $url = $string[2];\n    }\n    $shortened_url = $url;\n    if (strlen($url) > $text_word_maxlength) {\n        $shortened_url = substr($url, 0, ($text_word_maxlength/2)) . \"…\" . substr($url, - ($text_word_maxlength-3-$text_word_maxlength/2));\n    }\n    return $pre.\"<a href=\\\"\".$url.\"\\\">\".$shortened_url.\"</a>\";\n}\n\n// -------------------------------------------------------------\nfunction ical_formatText($string)\n{\n    $string = \' \' . $string;\n    $string = preg_replace_callback(\"#(^|[\\n ])([\\w]+?://.*?[^ \\\"\\n\\r\\t<]*)#is\", \"ical_shorten_link\", $string);\n    $string = preg_replace(\"#(^|[\\n ])((www|ftp)\\.[\\w\\-]+\\.[\\w\\-.\\~]+(?:/[^ \\\"\\t\\n\\r<]*)?)#is\", \"$1<a href=\\\"http://$2\\\">$2</a>\", $string);\n    $string = str_replace(\"\\n\", \"<br/>\", $string);\n    return $string;\n}\n\n\n\n// -------------------------------------------------------------\nfunction checkCache($urls, $cachingtime)\n{\n    $hash = md5(implode(\",\", $urls));\n	// Use cached json file in temp directory\n    $fn = $GLOBALS[\'prefs\'][\"tempdir\"]. \"/mka_ical_\".$hash.\".json\";\n\n    if (file_exists($fn) && (time()-filemtime($fn) < ($cachingtime*60))) {\n        // Eventstring from cache file\n        $cache_file = fopen($fn, \"r\");\n        $data = fread($cache_file, filesize($fn));\n        fclose($cache_file);\n        return json_decode($data);\n    } else {\n        // Generate new eventstring\n        $events = getEvents($urls);\n\n        $cf = fopen($fn, \"w+\");\n        fwrite($cf, json_encode($events));\n        fclose($cf);\n\n        return $events;\n    }\n}\n\n// -------------------------------------------------------------\nfunction getEvents($urls)\n{\n    $events = array();\n\n    // Instantiate ICal() library\n    $reader = new ICal();\n\n    foreach ($urls as $u) {\n        $reader->setUrl($u, true);\n        $e = $reader->getEvents();\n        foreach ($e as $event) {\n            // $plainEvents = getPlainEvents($event);\n            // $events = array_merge($events, $plainEvents);\n            $events[] = getPlainEvent($event);\n        }\n    }\n    // Sort events by startdate\n    usort($events, \"cmp_event\");\n    return $events;\n}\n\n// -------------------------------------------------------------\nfunction getPlainEvent($evt)\n{\n    $plainEvent = array();\n\n    $plainEvent = new stdClass;\n    $plainEvent->start     = $evt->getStart();\n    $plainEvent->end       = $evt->getEnd();\n    $plainEvent->duration  = $evt->getDuration();\n    $plainEvent->sum       = $evt->getSummary();\n    $plainEvent->des       = $evt->getDescription();\n    $plainEvent->loc       = $evt->getLocation();\n    $plainEvent->uid       = $evt->getUID();\n\n    $rec = $evt->getProperty(\'recurrence\');\n    if ($rec === null) {\n        $plainEvent->rec = false;\n    } else {\n        // $plainEvent->rec = json_encode(getPlainRec($rec));\n        $plainEvent->rec = getPlainRec($rec);\n    }\n    return $plainEvent;\n}\n\n// -------------------------------------------------------------\nfunction getPlainRec($rec)\n{\n    $r = new stdClass;\n    $class_methods = get_class_methods($rec);\n    foreach ($class_methods as $method) {\n        if (substr($method, 0, 3) === \'get\') {\n            $prop = strtolower(substr($method, 3));\n            $r->$prop = $rec->$method();\n        }\n    }\n    return $r;\n}\n\n// -------------------------------------------------------------\nfunction cmp_event($a, $b)\n{\n    $da = $a->start;\n    $db = $b->start;\n    if ($da == $db) {\n        return 1;\n    }\n    return ($da < $db) ? -1 : 1;\n}\n\n\n\n/**\n * This file is a concatenation of the src files for\n * https://github.com/TKr/intouch-iCalendar/tree/master/src\n * with the following modifications\n * Removal of intermediary <?php … ?> tags\n * Commenting out of intermediary namespace (except for initial namespace)\n * Commenting out of duplicate \"use\" statements\n *\n */\n\ndefine(\'SG_ICALREADER_VERSION\', \'0.8.0\');\n\n/**\n * A simple ICal parser.\n *\n * http://github.com/mfkahn/intouch-iCalendar based on\n * http://github.com/fangel/SG-iCalendar\n *\n * Roadmap:\n * - Finish FREQUENCY-parsing.\n * - Add API for recurring events\n *\n * A simple example:\n * <code>\n * <?php\n * use TKr\\ical\\ICal;\n * $ical = new ICal(\"http://example.com/calendar.ics\");\n * foreach ( $ical->getEvents() As $event ) {\n *   // Do stuff with the event $event\n * }\n * ?>\n * </code>\n *\n * @author Morten Fangel (C) 2008\n * @author xonev (C) 2010\n * @author Tanguy Pruvot (C) 2010\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass ICal\n{\n    //objects\n    public $information; //VCalendar\n    public $timezones;   //VTimeZone\n\n    protected $events; //VEvent[]\n\n    /**\n     * Constructs a new iCalReader. You can supply the url now, or later using setUrl\n     * @param $url string\n     */\n    public function __construct($url = false)\n    {\n        $this->setUrl($url);\n    }\n\n    /**\n     * Sets (or resets) the url this reader reads from.\n     * @param $url string\n     */\n    public function setUrl( $url = false )\n    {\n        if ($url !== false) {\n            Parser::Parse($url, $this);\n        }\n    }\n\n    /**\n     * Returns the main calendar info. You can then query the returned\n     * object with ie getTitle().\n     * @return VCalendar\n     */\n    public function getCalendarInfo()\n    {\n        return $this->information;\n    }\n\n    /**\n     * Sets the calendar info for this calendar\n     * @param VCalendar $info\n     */\n    public function setCalendarInfo( VCalendar $info )\n    {\n        $this->information = $info;\n    }\n\n    /**\n     * Returns a given timezone for the calendar. This is mainly used\n     * by VEvents to adjust their date-times if they have specified a\n     * timezone.\n     *\n     * If no timezone is given, all timezones in the calendar is\n     * returned.\n     *\n     * @param $tzid string\n     * @return VTimeZone\n     */\n    public function getTimeZoneInfo( $tzid = null )\n    {\n        if ($tzid == null) {\n            return $this->timezones;\n        } else {\n            if ( !isset($this->timezones)) {\n                return null;\n            }\n            foreach ($this->timezones AS $tz) {\n                if ( $tz->getTimeZoneId() == $tzid ) {\n                    return $tz;\n                }\n            }\n\n            return null;\n        }\n    }\n\n    /**\n     * Adds a new timezone to this calendar\n     * @param VTimeZone $tz\n     */\n    public function addTimeZone( VTimeZone $tz )\n    {\n        $this->timezones[] = $tz;\n    }\n\n    /**\n     * Returns the events found\n     * @return array\n     */\n    public function getEvents()\n    {\n        return $this->events;\n    }\n\n    /**\n     * Adds a event to this calendar\n     * @param VEvent $event\n     */\n    public function addEvent( VEvent $event )\n    {\n        $this->events[] = $event;\n    }\n}\n\n/**\n * Legacy - empty subclass of ICal\n * @internal\n */\nclass iCalReader extends ICal {}\n\n// namespace TKr\\ICal;\n\n/**\n * A class for calculating how many seconds a duration-string is\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\n\nclass Duration\n{\n    protected $dur;\n\n    /**\n     * Constructs a new intouch\\ical\\Duration from a duration-rule.\n     * The basic build-up of DURATIONs are:\n     *  ([\"+\"] / \"-\") \"P\" (dur-date / dur-date + \"T\" + dur-time / dur-time / dur-week)\n     * Is solved via a really fugly reg-exp with way to many ()\'s..\n     *\n     * @param $duration string\n     */\n    public function __construct( $duration )\n    {\n        $ts = 0;\n\n        if (preg_match(\'/[\\\\+\\\\-]{0,1}P((\\d+)W)?((\\d+)D)?(T)?((\\d+)H)?((\\d+)M)?((\\d+)S)?/\', $duration, $matches) === 1) {\n            $results = array(\n                \'weeks\'=>  (int) @ $matches[2],\n                \'days\'=>   (int) @ $matches[4],\n                \'hours\'=>  (int) @ $matches[7],\n                \'minutes\'=>(int) @ $matches[9],\n                \'seconds\'=>(int) @ $matches[11]\n            );\n\n            $ts += $results[\'seconds\'];\n            $ts += 60 * $results[\'minutes\'];\n            $ts += 60 * 60 * $results[\'hours\'];\n            $ts += 24 * 60 * 60 * $results[\'days\'];\n            $ts += 7 * 24 * 60 * 60 * $results[\'weeks\'];\n        } else {\n            // Invalid duration!\n        }\n\n        $dir = ($duration{0} == \'-\') ? -1 : 1;\n\n        $this->dur = $dir * $ts;\n    }\n\n    /**\n     * Returns the duration in seconds\n     * @return int\n     */\n    public function getDuration()\n    {\n        return $this->dur;\n    }\n}\n\n// namespace TKr\\ICal;\n// use ArrayObject;\n\n/**\n * A simple Factory for converting a section/data pair into the\n * corrosponding block-object. If the section isn\'t known a simple\n * ArrayObject is used instead.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Factory\n{\n    /**\n     * Returns a new block-object for the section/data-pair. The list\n     * of returned objects is:\n     *\n     * vcalendar => intouch\\ical\\VCalendar\n     * vtimezone => intouch\\ical\\VTimeZone\n     * vevent => intouch\\ical\\VEvent\n     * * => ArrayObject\n     *\n     * @param $ical ICal The reader this section/data-pair belongs to\n     * @param $section string\n     * @param intouch\\ical\\Line[]\n     */\n    public static function portal( ICal $ical, $section, $data )\n    {\n        switch ($section) {\n            case \"vcalendar\":\n                return new VCalendar(Line::Remove_Line($data), $ical );\n            case \"vtimezone\":\n                return new VTimeZone(Line::Remove_Line($data), $ical );\n            case \"vevent\":\n                return new VEvent($data, $ical );\n\n            default:\n                return new ArrayObject(Line::Remove_Line((array) $data) );\n        }\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * A class to store Frequency-rules in. Will allow a easy way to find the\n * last and next occurrence of the rule.\n *\n * No - this is so not pretty. But.. ehh.. You do it better, and I will\n * gladly accept patches.\n *\n * Created by trail-and-error on the examples given in the RFC.\n *\n * TODO: Update to a better way of doing calculating the different options.\n * Instead of only keeping track of the best of the current dates found\n * it should instead keep a array of all the calculated dates within the\n * period.\n * This should fix the issues with multi-rule + multi-rule interference,\n * and make it possible to implement the SETPOS rule.\n * By pushing the next period onto the stack as the last option will\n * (hopefully) remove the need for the awful simpleMode\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Freq\n{\n    protected $weekdays = array(\'MO\'=>\'monday\', \'TU\'=>\'tuesday\', \'WE\'=>\'wednesday\', \'TH\'=>\'thursday\', \'FR\'=>\'friday\', \'SA\'=>\'saturday\', \'SU\'=>\'sunday\');\n    protected $knownRules = array(\'month\', \'weekno\', \'day\', \'monthday\', \'yearday\', \'hour\', \'minute\'); //others : \'setpos\', \'second\'\n    protected $ruleModifiers = array(\'wkst\');\n    protected $simpleMode = true;\n\n    protected $rules = array(\'freq\'=>\'yearly\', \'interval\'=>1);\n    protected $start = 0;\n    protected $freq = \'\';\n\n    protected $excluded = array(); //EXDATE\n    protected $added;    //RDATE\n\n    protected $cache; // getAllOccurrences()\n\n    /**\n     * Constructs a new Freqency-rule\n     * @param $rule string\n     * @param $start int Unix-timestamp (important : Need to be the start of Event)\n     * @param $excluded array of int (timestamps), see EXDATE documentation\n     * @param $added array of int (timestamps), see RDATE documentation\n     */\n    public function __construct($recurrence, $start, $excluded=array(), $added=array(), $until = null)\n    {\n        $this->start = $start;\n        $this->excluded = array();\n        $rule = $recurrence->rrule;\n\n        $rules = array();\n        foreach ( explode(\';\', $rule) as $v) {\n            list($k, $v) = explode(\'=\', $v);\n            $this->rules[ strtolower($k) ] = $v;\n        }\n\n        if (!isset($this->rules[\'until\'])) {\n            if (!$until && isset($recurrence->until)) {\n                $this->rules[\'until\'] = $recurrence->until;\n            } elseif($until) {\n                $this->rules[\'until\'] = $until;\n            }\n        }\n        if (isset($this->rules[\'until\']) && is_string($this->rules[\'until\']) ) {\n            $this->rules[\'until\'] = strtotime($this->rules[\'until\']);\n        }\n        $this->freq = strtolower($this->rules[\'freq\']);\n\n        foreach ($this->knownRules AS $rule) {\n            if ( isset($this->rules[\'by\' . $rule]) ) {\n                if ( $this->isPrerule($rule, $this->freq) ) {\n                    $this->simpleMode = false;\n                }\n            }\n        }\n\n        if (!$this->simpleMode) {\n            if (! (isset($this->rules[\'byday\']) || isset($this->rules[\'bymonthday\']) || isset($this->rules[\'byyearday\']))) {\n                $this->rules[\'bymonthday\'] = date(\'d\', $this->start);\n            }\n        }\n\n        //set until, and cache\n        if ( isset($this->rules[\'count\']) ) {\n\n            $cache[$ts] = $ts = $this->start;\n            for ($n=1; $n < $this->rules[\'count\']; $n++) {\n                $ts = $this->findNext($ts);\n                $cache[$ts] = $ts;\n            }\n            $this->rules[\'until\'] = $ts;\n\n            //EXDATE\n            if (!empty($excluded)) {\n                foreach ($excluded as $ts) {\n                    unset($cache[$ts]);\n                }\n            }\n            //RDATE\n            if (!empty($added)) {\n                $cache = $cache + $added;\n                asort($cache);\n            }\n\n            $this->cache = array_values($cache);\n        }\n\n        $this->excluded = $excluded;\n        $this->added = $added;\n    }\n\n    /**\n     * Returns all timestamps array(), build the cache if not made before\n     * @return array\n     */\n    public function getAllOccurrences($limit = 100, $offset = false)\n    {\n        if (empty($this->cache)) {\n            //build cache\n            $next = $this->firstOccurrence($offset);\n            $c = 0;\n            while ($next && $c < $limit) {\n                $cache[] = $next;\n                $next = $this->findNext($next);\n                $c++;\n            }\n            if (!empty($this->added)) {\n                $cache = $cache + $this->added;\n                asort($cache);\n            }\n            $this->cache = $cache;\n        }\n\n        return $this->cache;\n    }\n\n    /**\n     * Returns the previous (most recent) occurrence of the rule from the\n     * given offset\n     * @param  int $offset\n     * @return int\n     */\n    public function previousOccurrence( $offset )\n    {\n        if (!empty($this->cache)) {\n            $t2=$this->start;\n            foreach ($this->cache as $ts) {\n                if ($ts >= $offset)\n                    return $t2;\n                $t2 = $ts;\n            }\n        } else {\n            $ts = $this->start;\n            while ( ($t2 = $this->findNext($ts)) < $offset) {\n                if ($t2 == false) {\n                    break;\n                }\n                $ts = $t2;\n            }\n        }\n\n        return $ts;\n    }\n\n    /**\n     * Returns the next occurrence of this rule after the given offset\n     * @param  int $offset\n     * @return int\n     */\n    public function nextOccurrence( $offset )\n    {\n        if ($offset < $this->start)\n            return $this->firstOccurrence();\n        return $this->findNext($offset);\n    }\n\n    /**\n     * Finds the first occurrence of the rule.\n     * @return int timestamp\n     */\n    public function firstOccurrence($offset = false)\n    {\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        $t = ($offset !== false) ? $offset : $this->start;\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        if (in_array($t, $this->excluded))\n            $t = $this->findNext($t);\n\n        return $t;\n    }\n\n    /**\n     * Finds the absolute last occurrence of the rule from the given offset.\n     * Builds also the cache, if not set before...\n     * @return int timestamp\n     */\n    public function lastOccurrence()\n    {\n        //build cache if not done\n        $this->getAllOccurrences();\n        //return last timestamp in cache\n        return end($this->cache);\n    }\n\n    /**\n     * Calculates the next time after the given offset that the rule\n     * will apply.\n     *\n     * The approach to finding the next is as follows:\n     * First we establish a timeframe to find timestamps in. This is\n     * between $offset and the end of the period that $offset is in.\n     *\n     * We then loop though all the rules (that is a Prerule in the\n     * current freq.), and finds the smallest timestamp inside the\n     * timeframe.\n     *\n     * If we find something, we check if the date is a valid recurrence\n     * (with validDate). If it is, we return it. Otherwise we try to\n     * find a new date inside the same timeframe (but using the new-\n     * found date as offset)\n     *\n     * If no new timestamps were found in the period, we try in the\n     * next period\n     *\n     * @param  int $offset\n     * @return int\n     */\n    public function findNext($offset)\n    {\n        if (!empty($this->cache)) {\n            foreach ($this->cache as $ts) {\n                if ($ts > $offset)\n                    return $ts;\n            }\n        }\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        $debug = false;\n\n        //make sure the offset is valid\n        if ( $offset === false || (isset($this->rules[\'until\']) && $offset > $this->rules[\'until\']) ) {\n            if($debug) echo \'STOP: \' . date(\'r\', $offset) . \"\\n\";\n\n            return false;\n        }\n\n        $found = true;\n\n        //set the timestamp of the offset (ignoring hours and minutes unless we want them to be\n        //part of the calculations.\n        if($debug) echo \'O: \' . date(\'r\', $offset) . \"\\n\";\n        $hour = (in_array($this->freq, array(\'hourly\',\'minutely\')) && $offset > $this->start) ? date(\'H\', $offset) : date(\'H\', $this->start);\n        $minute = (($this->freq == \'minutely\' || isset($this->rules[\'byminute\'])) && $offset > $this->start) ? date(\'i\', $offset) : date(\'i\', $this->start);\n        $t = mktime($hour, $minute, date(\'s\', $this->start), date(\'m\', $offset), date(\'d\', $offset), date(\'Y\',$offset));\n        if($debug) echo \'START: \' . date(\'r\', $t) . \"\\n\";\n\n        if ($this->simpleMode) {\n            if ($offset < $t) {\n                $ts = $t;\n                if ($ts && in_array($ts, $this->excluded))\n                    $ts = $this->findNext($ts);\n            } else {\n                $ts = $this->findStartingPoint( $t, $this->rules[\'interval\'], false );\n                if ( !$this->validDate( $ts ) ) {\n                    $ts = $this->findNext($ts);\n                }\n            }\n\n            return $ts;\n        }\n\n        $eop = $this->findEndOfPeriod($offset);\n        if($debug) echo \'EOP: \' . date(\'r\', $eop) . \"\\n\";\n\n        foreach ($this->knownRules AS $rule) {\n            if ( $found && isset($this->rules[\'by\' . $rule]) ) {\n                if ( $this->isPrerule($rule, $this->freq) ) {\n                    $subrules = explode(\',\', $this->rules[\'by\' . $rule]);\n                    $_t = null;\n                    foreach ($subrules AS $subrule) {\n                        $imm = call_user_func_array(array($this, \'ruleBy\' . $rule), array($subrule, $t));\n                        if ($imm === false) {\n                            break;\n                        }\n                        if($debug) echo strtoupper($rule) . \': \' . date(\'r\', $imm) . \' A: \' . ((int) ($imm > $offset && $imm < $eop)) . \"\\n\";\n                        if ( $imm > $offset && $imm < $eop && ($_t == null || $imm < $_t) ) {\n                            $_t = $imm;\n                        }\n                    }\n                    if ($_t !== null) {\n                        $t = $_t;\n                    } else {\n                        $found = $this->validDate($t);\n                    }\n                }\n            }\n        }\n\n        if ($offset < $this->start && $this->start < $t) {\n            $ts = $this->start;\n        } elseif ( $found && ($t != $offset)) {\n            if ( $this->validDate( $t ) ) {\n                if($debug) echo \'OK\' . \"\\n\";\n                $ts = $t;\n            } else {\n                if($debug) echo \'Invalid\' . \"\\n\";\n                $ts = $this->findNext($t);\n            }\n        } else {\n            if($debug) echo \'Not found\' . \"\\n\";\n            $ts = $this->findNext( $this->findStartingPoint( $offset, $this->rules[\'interval\'] ) );\n        }\n        if ($ts && in_array($ts, $this->excluded))\n            return $this->findNext($ts);\n\n        return $ts;\n    }\n\n    /**\n     * Finds the starting point for the next rule. It goes $interval\n     * \'freq\' forward in time since the given offset\n     * @param  int     $offset\n     * @param  int     $interval\n     * @param  boolean $truncate\n     * @return int\n     */\n    private function findStartingPoint( $offset, $interval, $truncate = true )\n    {\n        $_freq = ($this->freq == \'daily\') ? \'day__\' : $this->freq;\n        $t = \'+\' . $interval . \' \' . substr($_freq,0,-2) . \'s\';\n        if ($_freq == \'monthly\' && $truncate) {\n            if ($interval > 1) {\n                $offset = strtotime(\'+\' . ($interval - 1) . \' months \', $offset);\n            }\n            $t = \'+\' . (date(\'t\', $offset) - date(\'d\', $offset) + 1) . \' days\';\n        }\n\n        $sp = strtotime($t, $offset);\n\n        if ($truncate) {\n            $sp = $this->truncateToPeriod($sp, $this->freq);\n        }\n\n        return $sp;\n    }\n\n    /**\n     * Finds the earliest timestamp posible outside this perioid\n     * @param  int $offset\n     * @return int\n     */\n    public function findEndOfPeriod($offset)\n    {\n        return $this->findStartingPoint($offset, 1);\n    }\n\n    /**\n     * Resets the timestamp to the beginning of the\n     * period specified by freq\n     *\n     * Yes - the fall-through is on purpose!\n     *\n     * @param  int $time\n     * @param  int $freq\n     * @return int\n     */\n    private function truncateToPeriod( $time, $freq )\n    {\n        $date = getdate($time);\n        switch ($freq) {\n            case \"yearly\":\n                $date[\'mon\'] = 1;\n            case \"monthly\":\n                $date[\'mday\'] = 1;\n            case \"daily\":\n                $date[\'hours\'] = 0;\n            case \'hourly\':\n                $date[\'minutes\'] = 0;\n            case \"minutely\":\n                $date[\'seconds\'] = 0;\n                break;\n            case \"weekly\":\n                if ( date(\'N\', $time) == 1) {\n                    $date[\'hours\'] = 0;\n                    $date[\'minutes\'] = 0;\n                    $date[\'seconds\'] = 0;\n                } else {\n                    $date = getdate(strtotime(\"last monday 0:00\", $time));\n                }\n                break;\n        }\n        $d = mktime($date[\'hours\'], $date[\'minutes\'], $date[\'seconds\'], $date[\'mon\'], $date[\'mday\'], $date[\'year\']);\n\n        return $d;\n    }\n\n    /**\n     * Applies the BYDAY rule to the given timestamp\n     * @param  string $rule\n     * @param  int    $t\n     * @return int\n     */\n    private function ruleByday($rule, $t)\n    {\n        $rule = str_replace(\'\"\', \'\', $rule);\n        $dir = ($rule{0} == \'-\') ? -1 : 1;\n        $dir_t = ($dir == 1) ? \'next\' : \'last\';\n\n        $d = $this->weekdays[substr($rule,-2)];\n        $s = $dir_t . \' \' . $d . \' \' . date(\'H:i:s\',$t);\n\n        if ( $rule == substr($rule, -2) ) {\n            if ( date(\'l\', $t) == ucfirst($d) ) {\n                $s = \'today \' . date(\'H:i:s\',$t);\n            }\n\n            $_t = strtotime($s, $t);\n\n            if ( $_t == $t && in_array($this->freq, array(\'monthly\', \'yearly\')) ) {\n                // Yes. This is not a great idea.. but hey, it works.. for now\n                $s = \'next \' . $d . \' \' . date(\'H:i:s\',$t);\n                $_t = strtotime($s, $_t);\n            }\n\n            return $_t;\n        } else {\n            $_f = $this->freq;\n            if ( isset($this->rules[\'bymonth\']) && $this->freq == \'yearly\' ) {\n                $this->freq = \'monthly\';\n            }\n            if ($dir == -1) {\n                $_t = $this->findEndOfPeriod($t);\n            } else {\n                $_t = $this->truncateToPeriod($t, $this->freq);\n            }\n            $this->freq = $_f;\n\n            $c = preg_replace(\'/[^0-9]/\',\'\',$rule);\n            $c = ($c == \'\') ? 1 : $c;\n\n            $n = $_t;\n            while ($c > 0) {\n                if ( $dir == 1 && $c == 1 && date(\'l\', $t) == ucfirst($d) ) {\n                    $s = \'today \' . date(\'H:i:s\',$t);\n                }\n                $n = strtotime($s, $n);\n                $c--;\n            }\n\n            return $n;\n        }\n    }\n\n    private function ruleBymonth($rule, $t)\n    {\n        $_t = mktime(date(\'H\',$t), date(\'i\',$t), date(\'s\',$t), $rule, date(\'d\', $t), date(\'Y\', $t));\n        if ( $t == $_t && isset($this->rules[\'byday\']) ) {\n            // TODO: this should check if one of the by*day\'s exists, and have a multi-day value\n            return false;\n        } else {\n            return $_t;\n        }\n    }\n\n    private function ruleBymonthday($rule, $t)\n    {\n        if ($rule < 0) {\n            $rule = date(\'t\', $t) + $rule + 1;\n        }\n\n        return mktime(date(\'H\',$t), date(\'i\',$t), date(\'s\',$t), date(\'m\', $t), $rule, date(\'Y\', $t));\n    }\n\n    private function ruleByyearday($rule, $t)\n    {\n        if ($rule < 0) {\n            $_t = $this->findEndOfPeriod();\n            $d = \'-\';\n        } else {\n            $_t = $this->truncateToPeriod($t, $this->freq);\n            $d = \'+\';\n        }\n        $s = $d . abs($rule -1) . \' days \' . date(\'H:i:s\',$t);\n\n        return strtotime($s, $_t);\n    }\n\n    private function ruleByweekno($rule, $t)\n    {\n        if ($rule < 0) {\n            $_t = $this->findEndOfPeriod();\n            $d = \'-\';\n        } else {\n            $_t = $this->truncateToPeriod($t, $this->freq);\n            $d = \'+\';\n        }\n\n        $sub = (date(\'W\', $_t) == 1) ? 2 : 1;\n        $s = $d . abs($rule - $sub) . \' weeks \' . date(\'H:i:s\',$t);\n        $_t  = strtotime($s, $_t);\n\n        return $_t;\n    }\n\n    private function ruleByhour($rule, $t)\n    {\n        $_t = mktime($rule, date(\'i\',$t), date(\'s\',$t), date(\'m\',$t), date(\'d\', $t), date(\'Y\', $t));\n\n        return $_t;\n    }\n\n    private function ruleByminute($rule, $t)\n    {\n        $_t = mktime(date(\'h\',$t), $rule, date(\'s\',$t), date(\'m\',$t), date(\'d\', $t), date(\'Y\', $t));\n\n        return $_t;\n    }\n\n    private function validDate( $t )\n    {\n        if ( isset($this->rules[\'until\']) && $t > $this->rules[\'until\'] ) {\n            return false;\n        }\n\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        if (in_array($t, $this->excluded)) {\n            return false;\n        }\n\n        if ( isset($this->rules[\'bymonth\']) ) {\n            $months = explode(\',\', $this->rules[\'bymonth\']);\n            if ( !in_array(date(\'m\', $t), $months)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byday\']) ) {\n            $days = explode(\',\', $this->rules[\'byday\']);\n            foreach ($days As $i => $k) {\n                $days[$i] = $this->weekdays[ preg_replace(\'/[^A-Z]/\', \'\', $k)];\n            }\n            if ( !in_array(strtolower(date(\'l\', $t)), $days)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byweekno\']) ) {\n            $weeks = explode(\',\', $this->rules[\'byweekno\']);\n            if ( !in_array(date(\'W\', $t), $weeks)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'bymonthday\'])) {\n            $weekdays = explode(\',\', $this->rules[\'bymonthday\']);\n            foreach ($weekdays As $i => $k) {\n                if ($k < 0) {\n                    $weekdays[$i] = date(\'t\', $t) + $k + 1;\n                }\n            }\n            if ( !in_array(date(\'d\', $t), $weekdays)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byhour\']) ) {\n            $hours = explode(\',\', $this->rules[\'byhour\']);\n            if ( !in_array(date(\'H\', $t), $hours)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private function isPrerule($rule, $freq)\n    {\n        if( $rule == \'year\')\n\n            return false;\n        if( $rule == \'month\' && $freq == \'yearly\')\n\n            return true;\n        if( $rule == \'monthday\' && in_array($freq, array(\'yearly\', \'monthly\')) && !isset($this->rules[\'byday\']))\n\n            return true;\n        // TODO: is it faster to do monthday first, and ignore day if monthday exists? - prolly by a factor of 4..\n        if( $rule == \'yearday\' && $freq == \'yearly\' )\n\n            return true;\n        if( $rule == \'weekno\' && $freq == \'yearly\' )\n\n            return true;\n        if( $rule == \'day\' && in_array($freq, array(\'yearly\', \'monthly\', \'weekly\')))\n\n            return true;\n        if( $rule == \'hour\' && in_array($freq, array(\'yearly\', \'monthly\', \'weekly\', \'daily\')))\n\n            return true;\n        if( $rule == \'minute\' )\n\n            return true;\n\n        return false;\n    }\n}\n\n// namespace TKr\\ICal;\n\n// use \\ArrayAccess;\n// use ArrayIterator;\n// use \\Countable;\n// use \\IteratorAggregate;\n\n/**\n * A class for storing a single (complete) line of the ICal file.\n * Will find the line-type, the arguments and the data of the file and\n * store them.\n *\n * The line-type can be found by querying getIdent(), data via either\n * getData() or typecasting to a string.\n * Params can be access via the ArrayAccess. A iterator is also avilable\n * to iterator over the params.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Line implements ArrayAccess, Countable, IteratorAggregate\n{\n    protected $ident;\n    protected $data;\n    protected $params = array();\n\n    protected $replacements = array(\'from\'=>array(\'\\\\,\', \'\\\\n\', \'\\\\;\', \'\\\\:\', \'\\\\\"\'), \'to\'=>array(\',\', \"\\n\", \';\', \':\', \'\"\'));\n\n    /**\n     * Constructs a new line.\n     */\n    public function __construct( $line )\n    {\n        $split = strpos($line, \':\');\n        $idents = explode(\';\', substr($line, 0, $split));\n        $ident = strtolower(array_shift($idents));\n\n        $data = trim(substr($line, $split+1));\n        $data = str_replace($this->replacements[\'from\'], $this->replacements[\'to\'], $data);\n\n        $params = array();\n        foreach ($idents AS $v) {\n            list($k, $v) = explode(\'=\', $v);\n            $params[ strtolower($k) ] = $v;\n        }\n\n        $this->ident = $ident;\n        $this->params = $params;\n        $this->data = $data;\n    }\n\n    /**\n     * Is this line the begining of a new block?\n     * @return bool\n     */\n    public function isBegin()\n    {\n        return $this->ident == \'begin\';\n    }\n\n    /**\n     * Is this line the end of a block?\n     * @return bool\n     */\n    public function isEnd()\n    {\n        return $this->ident == \'end\';\n    }\n\n    /**\n     * Returns the line-type (ident) of the line\n     * @return string\n     */\n    public function getIdent()\n    {\n        return $this->ident;\n    }\n\n    /**\n     * Returns the content of the line\n     * @return string\n     */\n    public function getData()\n    {\n        return $this->data;\n    }\n\n    /**\n     * Returns the content of the line\n     * @return string\n     */\n    public function getDataAsArray()\n    {\n        if (strpos($this->data,\",\") !== false) {\n            return explode(\",\",$this->data);\n        } else\n\n            return array($this->data);\n    }\n\n    /**\n     * A static helper to get a array of intouch\\ical\\Line\'s, and calls\n     * getData() on each of them to lay the data \"bare\"..\n     *\n     * @param intouch\\ical\\Line[]\n     * @return array\n     */\n    public static function Remove_Line($arr)\n    {\n        $rtn = array();\n        foreach ($arr AS $k => $v) {\n            if (is_array($v)) {\n                $rtn[$k] = self::Remove_Line($v);\n            } elseif ($v instanceof Line) {\n                $rtn[$k] = $v->getData();\n            } else {\n                $rtn[$k] = $v;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * @see ArrayAccess.offsetExists\n     */\n    public function offsetExists( $param )\n    {\n        return isset($this->params[ strtolower($param) ]);\n    }\n\n    /**\n     * @see ArrayAccess.offsetGet\n     */\n    public function offsetGet( $param )\n    {\n        $index = strtolower($param);\n        if (isset($this->params[ $index ])) {\n            return $this->params[ $index ];\n        }\n    }\n\n    /**\n     * Disabled ArrayAccess requirement\n     * @see ArrayAccess.offsetSet\n     */\n    public function offsetSet( $param, $val )\n    {\n        return false;\n    }\n\n    /**\n     * Disabled ArrayAccess requirement\n     * @see ArrayAccess.offsetUnset\n     */\n    public function offsetUnset( $param )\n    {\n        return false;\n    }\n\n    /**\n     * toString method.\n     * @see getData()\n     */\n    public function __toString()\n    {\n        return $this->getData();\n    }\n\n    /**\n     * @see Countable.count\n     */\n    public function count()\n    {\n        return count($this->params);\n    }\n\n    /**\n     * @see IteratorAggregate.getIterator\n     */\n    public function getIterator()\n    {\n        return new ArrayIterator($this->params);\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * Class Parser\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Parser\n{\n    /**\n     * Fetches $url and passes it on to be parsed\n     * @param string $url\n     * @param ICal $ical\n     */\n    public static function Parse( $url, ICal $ical )\n    {\n        $content = self::Fetch( $url );\n        $content = self::UnfoldLines($content);\n        self::_Parse( $content, $ical );\n    }\n\n    /**\n     * Passes a text string on to be parsed\n     * @param string            $content\n     * @param ICal $ical\n     */\n    public static function ParseString($content, ICal $ical )\n    {\n        $content = self::UnfoldLines($content);\n        self::_Parse( $content, $ical );\n    }\n\n    /**\n     * Fetches a resource and tries to make sure it\'s UTF8\n     * encoded\n     * @return string\n     */\n    protected static function Fetch( $resource )\n    {\n        $is_utf8 = true;\n\n        if ( is_file( $resource ) ) {\n            // The resource is a local file\n            $content = file_get_contents($resource);\n\n            if ( ! self::_ValidUtf8( $content ) ) {\n                // The file doesn\'t appear to be UTF8\n                $is_utf8 = false;\n            }\n        } else {\n            // The resource isn\'t local, so it\'s assumed to\n            // be a URL\n            $c = curl_init();\n            curl_setopt($c, CURLOPT_URL, $resource);\n            curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\n            if ( !ini_get(\'safe_mode\') && !ini_get(\'open_basedir\') ) {\n                curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);\n            }\n            $content = curl_exec($c);\n\n            $ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);\n            $enc = preg_replace(\'/^.*charset=([-a-zA-Z0-9]+).*$/\', \'$1\', $ct);\n            if ( $ct != \'\' && strtolower(str_replace(\'-\',\'\', $enc)) != \'utf8\' ) {\n                // Well, the encoding says it ain\'t utf-8\n                $is_utf8 = false;\n            } elseif ( ! self::_ValidUtf8( $content ) ) {\n                // The data isn\'t utf-8\n                $is_utf8 = false;\n            }\n        }\n\n        if (!$is_utf8) {\n            $content = utf8_encode($content);\n        }\n\n        return $content;\n    }\n\n    /**\n     * Takes the string $content, and creates a array of ICal lines.\n     * This includes unfolding multi-line entries into a single line.\n     * @param $content string\n     */\n    protected static function UnfoldLines($content)\n    {\n        $data = array();\n        $content = explode(\"\\n\", $content);\n        for ( $i=0; $i < count($content); $i++) {\n            $line = rtrim($content[$i]);\n            while ( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == \' \' || $content[$i+1]{0} == \"\\t\" )) {\n                $line .= rtrim(substr($content[++$i],1));\n            }\n            $data[] = $line;\n        }\n\n        return $data;\n    }\n\n    /**\n     * Parses the feed found in content and calls storeSection to store\n     * parsed data\n     * @param string $content\n     * @param ICal $ical\n     */\n    private static function _Parse( $content, ICal $ical )\n    {\n        $main_sections = array(\'vevent\', \'vjournal\', \'vtodo\', \'vtimezone\', \'vcalendar\');\n        $array_idents = array(\'exdate\',\'rdate\',\'attendee\');\n        $sections = array();\n        $section = \'\';\n        $current_data = array();\n\n        foreach ($content AS $line) {\n            $line = new Line($line);\n            if ( $line->isBegin() ) {\n                // New block of data, $section = new block\n                $section = strtolower($line->getData());\n                $sections[] = strtolower($line->getData());\n            } elseif ( $line->isEnd() ) {\n                // End of block of data ($removed = just ended block, $section = new top-block)\n                $removed = array_pop($sections);\n                $section = end($sections);\n\n                if ( array_search($removed, $main_sections) !== false ) {\n                    self::StoreSection( $removed, $current_data[$removed], $ical);\n                    $current_data[$removed] = array();\n                }\n            } else {\n                // Data line\n                foreach ($main_sections AS $s) {\n                    // Loops though the main sections\n                    if ( array_search($s, $sections) !== false ) {\n                        // This section is in the main section\n                        if ($section == $s) {\n                            // It _is_ the main section else\n                            if (in_array($line->getIdent(), $array_idents)) {\n                                //exdate could appears more that once\n                                $current_data[$s][$line->getIdent()][] = $line;\n                            }\n                            else {\n                                $current_data[$s][$line->getIdent()] = $line;\n                            }\n                        } else {\n                            // Sub section\n                            $current_data[$s][$section][$line->getIdent()] = $line;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n        $current_data = array();\n    }\n\n    /**\n     * Stores the data in provided intouch\\ical\\iCalobject\n     * @param string $section eg \'vcalender\', \'vevent\' etc\n     * @param string $data\n     * @param ICal $ical\n     */\n    protected static function storeSection( $section, $data, ICal $ical )\n    {\n        $data = Factory::portal($ical, $section, $data);\n        switch ($section) {\n            case \'vcalendar\':\n                return $ical->setCalendarInfo( $data );\n            case \'vevent\':\n                return $ical->addEvent( $data );\n            case \'vjournal\':\n            case \'vtodo\':\n                return true; // TODO: Implement\n            case \'vtimezone\':\n                return $ical->addTimeZone( $data );\n        }\n    }\n\n    /**\n     * This functions does some regexp checking to see if the value is\n     * valid UTF-8.\n     *\n     * The function is from the book \"Building Scalable Web Sites\" by\n     * Cal Henderson.\n     *\n     * @param  string $data\n     * @return bool\n     */\n    private static function _ValidUtf8( $data )\n    {\n        $rx  = \'[\\xC0-\\xDF]([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xE0-\\xEF].{0,1}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xF0-\\xF7].{0,2}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xF8-\\xFB].{0,3}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xFC-\\xFD].{0,4}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xFE-\\xFE].{0,5}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\x00-\\x7F][\\x80-\\xBF]\';\n        $rx .= \'|[\\xC0-\\xDF].[\\x80-\\xBF]\';\n        $rx .= \'|[\\xE0-\\xEF]..[\\x80-\\xBF]\';\n        $rx .= \'|[\\xF0-\\xF7]...[\\x80-\\xBF]\';\n        $rx .= \'|[\\xF8-\\xFB]....[\\x80-\\xBF]\';\n        $rx .= \'|[\\xFC-\\xFD].....[\\x80-\\xBF]\';\n        $rx .= \'|[\\xFE-\\xFE]......[\\x80-\\xBF]\';\n        $rx .= \'|^[\\x80-\\xBF]\';\n\n        return ( ! (bool) preg_match(\'!\'.$rx.\'!\', $data) );\n    }\n}\n\n// namespace TKr\\ICal;\n// use Exception;\n\n/**\n *\n * A collection of functions to query the events in a calendar.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Query\n{\n    /**\n     * Returns all events from the calendar between two timestamps\n     *\n     * Note that the events returned needs only slightly overlap.\n     *\n     * @param  intouch\\ical\\iCal |array $ical  The calendar to query\n     * @param  int                      $start\n     * @param  int                      $end\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function Between(ICal $ical, $start, $end )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::Between called with invalid input!\');\n        }\n\n        $rtn = array();\n        foreach ($ical AS $e) {\n            if( ($start <= $e->getStart() && $e->getStart() < $end)\n             || ($start < $e->getRangeEnd() && $e->getRangeEnd() <= $end) ) {\n                $rtn[] = $e;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * Returns all events from the calendar after a given timestamp\n     *\n     * @param  intouch\\ical\\iCal|array $ical  The calendar to query\n     * @param  int                     $start\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function After( $ical, $start )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::After called with invalid input!\');\n        }\n\n        $rtn = array();\n        foreach ($ical AS $e) {\n            if ($e->getStart() >= $start || $e->getRangeEnd() >= $start) {\n                $rtn[] = $e;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * Sorts the events from the calendar after the specified column.\n     * Column can be all valid entires that getProperty can return.\n     * So stuff like uid, start, end, summary etc.\n     * @param  intouch\\ical\\iCal|array $ical   The calendar to query\n     * @param  string                  $column\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function Sort( $ical, $column )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::Sort called with invalid input!\');\n        }\n\n        $cmp = create_function(\'$a, $b\', \'return strcmp($a->getProperty(\"\' . $column . \'\"), $b->getProperty(\"\' . $column . \'\"));\');\n        usort($ical, $cmp);\n\n        return $ical;\n    }\n}\n\n// namespace TKr\\ICal;\n\n// use \\DateTime;\n\n/**\n * A wrapper for recurrence rules in iCalendar.  Parses the given line and puts the\n * recurrence rules in the correct field of this object.\n *\n * See http://tools.ietf.org/html/rfc2445 for more information.  Page 39 and onward contains more\n * information on the recurrence rules themselves.  Page 116 and onward contains\n * some great examples which were often used for testing.\n *\n * @author Steven Oxley\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Recurrence\n{\n    public $rrule;\n\n    protected $freq;\n\n    public $until;\n    protected $count;\n\n    protected $interval;\n    protected $bysecond;\n    protected $byminute;\n    protected $byhour;\n    protected $byday;\n    protected $bymonthday;\n    protected $byyearday;\n    protected $byweekno;\n    protected $bymonth;\n    protected $bysetpos;\n\n    protected $wkst;\n\n    /**\n     * A list of the properties that can have comma-separated lists for values.\n     * @var array\n     */\n    protected $listProperties = array(\n        \'bysecond\', \'byminute\', \'byhour\', \'byday\', \'bymonthday\',\n        \'byyearday\', \'byweekno\', \'bymonth\', \'bysetpos\'\n    );\n\n    /**\n     * Creates an recurrence object with a passed in line.  Parses the line.\n     * @param object $line an intouch\\ical\\Line object which will be parsed to get the\n     * desired information.\n     */\n    public function __construct(Line $line)\n    {\n        $this->parseLine($line->getData());\n    }\n\n    /**\n     * Parses an \'RRULE\' line and sets the member variables of this object.\n     * Expects a string that looks like this:  \'FREQ=WEEKLY;INTERVAL=2;BYDAY=SU,TU,WE\'\n     * @param string $line the line to be parsed\n     */\n    protected function parseLine($line)\n    {\n        $this->rrule = $line;\n\n        //split up the properties\n        $recurProperties = explode(\';\', $line);\n        $recur = array();\n\n        //loop through the properties in the line and set their associated\n        //member variables\n        foreach ($recurProperties as $property) {\n            $nameAndValue = explode(\'=\', $property);\n\n            //need the lower-case name for setting the member variable\n            $propertyName = strtolower($nameAndValue[0]);\n            $propertyValue = $nameAndValue[1];\n\n            //split up the list of values into an array (if it\'s a list)\n            if (in_array($propertyName, $this->listProperties)) {\n                $propertyValue = explode(\',\', $propertyValue);\n            }\n            $this->$propertyName = $propertyValue;\n        }\n    }\n\n    /**\n     * Set the $until member\n     * @param mixed timestamp (int) / Valid DateTime format (string)\n     */\n    public function setUntil($ts)\n    {\n        if ( is_int($ts) )\n            $dt = new DateTime(\'@\'.$ts);\n        else\n            $dt = new DateTime($ts);\n        $this->until = $dt->format(\'Ymd\\THisO\');\n    }\n\n    /**\n     * Retrieves the desired member variable and returns it (if it\'s set)\n     * @param  string $member name of the member variable\n     * @return mixed  the variable value (if set), false otherwise\n     */\n    protected function getMember($member)\n    {\n        if (isset($this->$member)) {\n            return $this->$member;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns the frequency - corresponds to FREQ in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getFreq()\n    {\n        return $this->getMember(\'freq\');\n    }\n\n    /**\n     * Returns when the event will go until - corresponds to UNTIL in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getUntil()\n    {\n        return $this->getMember(\'until\');\n    }\n\n    /**\n     * Returns the count of the times the event will occur (should only appear if \'until\'\n     * does not appear) - corresponds to COUNT in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getCount()\n    {\n        return $this->getMember(\'count\');\n    }\n\n    /**\n     * Returns the interval - corresponds to INTERVAL in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getInterval()\n    {\n        return $this->getMember(\'interval\');\n    }\n\n    /**\n     * Returns the bysecond part of the event - corresponds to BYSECOND in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getBySecond()\n    {\n        return $this->getMember(\'bysecond\');\n    }\n\n    /**\n     * Returns the byminute information for the event - corresponds to BYMINUTE in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMinute()\n    {\n        return $this->getMember(\'byminute\');\n    }\n\n    /**\n     * Corresponds to BYHOUR in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByHour()\n    {\n        return $this->getMember(\'byhour\');\n    }\n\n    /**\n     *Corresponds to BYDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByDay()\n    {\n        return $this->getMember(\'byday\');\n    }\n\n    /**\n     * Corresponds to BYMONTHDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMonthDay()\n    {\n        return $this->getMember(\'bymonthday\');\n    }\n\n    /**\n     * Corresponds to BYYEARDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByYearDay()\n    {\n        return $this->getMember(\'byyearday\');\n    }\n\n    /**\n     * Corresponds to BYWEEKNO in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByWeekNo()\n    {\n        return $this->getMember(\'byweekno\');\n    }\n\n    /**\n     * Corresponds to BYMONTH in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMonth()\n    {\n        return $this->getMember(\'bymonth\');\n    }\n\n    /**\n     * Corresponds to BYSETPOS in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getBySetPos()\n    {\n        return $this->getMember(\'bysetpos\');\n    }\n\n    /**\n     * Corresponds to WKST in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getWkst()\n    {\n        return $this->getMember(\'wkst\');\n    }\n}\n\n// namespace TKr\\ICal;\n\n//use ArrayIterator;\n//// use \\IteratorAggregate;\n\n/**\n * The wrapper for the main vcalendar data. Used instead of ArrayObject\n * so you can easily query for title and description.\n * Exposes a iterator that will loop though all the data\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VCalendar implements IteratorAggregate\n{\n    protected $data;\n\n    /**\n     * Creates a new intouch\\ical\\VCalendar.\n     */\n    public function __construct($data)\n    {\n        $this->data = $data;\n    }\n\n    /**\n     * Returns the title of the calendar. If no title is known, NULL\n     * will be returned\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ( isset($this->data[\'x-wr-calname\']) ) {\n            return $this->data[\'x-wr-calname\'];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Returns the description of the calendar. If no description is\n     * known, NULL will be returned.\n     * @return string\n     */\n    public function getDescription()\n    {\n        if ( isset($this->data[\'x-wr-caldesc\']) ) {\n            return $this->data[\'x-wr-caldesc\'];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * @see IteratorAggregate.getIterator()\n     */\n    public function getIterator()\n    {\n        return new ArrayIterator($this->data);\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * The wrapper for vevents. Will reveal a unified and simple api for\n * the events, which include always finding a start and end (except\n * when no end or duration is given) and checking if the event is\n * blocking or similar.\n *\n * Will apply the specified timezone to timestamps if a tzid is\n * specified\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VEvent\n{\n    const DEFAULT_CONFIRMED = true;\n\n    protected $uid;\n\n    protected $start;\n    protected $end;\n\n    protected $summary;\n    protected $description;\n    protected $location;\n\n    protected $laststart;\n    protected $lastend;\n\n    public $recurrence; //RRULE\n    public $recurex;    //EXRULE\n    public $excluded;   //EXDATE(s)\n    public $added;      //RDATE(s)\n\n    public $freq; //getFrequency() intouch\\ical\\Freq\n\n    public $data;\n\n    /**\n     * Constructs a new intouch\\ical\\VEvent. Needs the ICal\n     * supplied so it can query for timezones.\n     * @param Line[] $data\n     * @param ICal   $ical\n     */\n    public function __construct($data, ICal $ical, $yearsLimit = 3)\n    {\n        if ( isset($data[\'uid\']) ) {\n            $this->uid = $data[\'uid\']->getData();\n            unset($data[\'uid\']);\n        }\n\n        if ( isset($data[\'rrule\']) ) {\n            $this->recurrence = new Recurrence($data[\'rrule\']);\n            unset($data[\'rrule\']);\n        }\n\n        if ( isset($data[\'exrule\']) ) {\n            $this->recurex = new Recurrence($data[\'exrule\']);\n            unset($data[\'exrule\']);\n        }\n\n        if ( isset($data[\'dtstart\']) ) {\n            $this->start = $this->getTimestamp($data[\'dtstart\'], $ical);\n            unset($data[\'dtstart\']);\n        }\n\n        if ( isset($data[\'dtend\']) ) {\n            $this->end = $this->getTimestamp($data[\'dtend\'], $ical);\n            unset($data[\'dtend\']);\n        } elseif ( isset($data[\'duration\']) ) {\n            $dur = new Duration( $data[\'duration\']->getData() );\n            $this->end = $this->start + $dur->getDuration();\n            unset($data[\'duration\']);\n        }\n\n        //google cal set dtend as end of initial event (duration)\n        if ( isset($this->recurrence) ) {\n            //if there is a recurrence rule\n\n            //exclusions\n            if ( isset($data[\'exdate\']) ) {\n                foreach ($data[\'exdate\'] as $exdate) {\n                    foreach ($exdate->getDataAsArray() as $ts) {\n                        $this->excluded[] = strtotime($ts);\n                    }\n                }\n                unset($data[\'exdate\']);\n            }\n            //additions\n            if ( isset($data[\'rdate\']) ) {\n                foreach ($data[\'rdate\'] as $rdate) {\n                    foreach ($rdate->getDataAsArray() as $ts) {\n                        $this->added[] = strtotime($ts);\n                    }\n                }\n                unset($data[\'rdate\']);\n            }\n\n            $until = $this->recurrence->getUntil();\n            $count = $this->recurrence->getCount();\n            //check if there is either \'until\' or \'count\' set\n            if ($until) {\n                //ok..\n            } elseif ($count) {\n                //if count is set, then figure out the last occurrence and set that as the end date\n                $this->getFrequency();\n                $until = $this->freq->lastOccurrence($this->start);\n            } else {\n                //forever... limit to 3 years\n                $this->recurrence->setUntil(\'+\' . $yearsLimit .\' years\');\n                $until = $this->recurrence->getUntil();\n            }\n            //date_default_timezone_set( xx ) needed ?;\n            $this->laststart = strtotime($until);\n            $this->lastend = $this->laststart + $this->getDuration();\n        }\n\n        $imports = array(\'summary\',\'description\',\'location\');\n        foreach ($imports AS $import) {\n            if ( isset($data[$import]) ) {\n                $this->$import = $data[$import]->getData();\n                unset($data[$import]);\n            }\n        }\n\n        if ( isset($this->previous_tz) ) {\n            date_default_timezone_set($this->previous_tz);\n        }\n        $this->data = Line::Remove_Line($data);\n    }\n\n    /**\n     * Returns the Event Occurrences Iterator (if recurrence set)\n     * @return intouch\\ical\\Freq\n     */\n    public function getFrequency($until = null)\n    {\n        if (! isset($this->freq)) {\n            if ( isset($this->recurrence) ) {\n                $this->freq = new Freq($this->recurrence, $this->start, $this->excluded, $this->added, $until);\n            }\n        }\n\n        return $this->freq;\n    }\n\n    /**\n     * Returns the UID of the event\n     * @return string\n     */\n    public function getUID()\n    {\n        return $this->uid;\n    }\n\n    /**\n     * Returns the summary (or null if none is given) of the event\n     * @return string\n     */\n    public function getSummary()\n    {\n        return $this->summary;\n    }\n\n    /**\n     * Returns the description (or null if none is given) of the event\n     * @return string\n     */\n    public function getDescription()\n    {\n        return $this->description;\n    }\n\n    /**\n     * Returns the location (or null if none is given) of the event\n     * @return string\n     */\n    public function getLocation()\n    {\n        return $this->location;\n    }\n\n    /**\n     * Returns true if the event is blocking (ie not transparent)\n     * @return bool\n     */\n    public function isBlocking()\n    {\n        return !(isset($this->data[\'transp\']) && $this->data[\'transp\'] == \'TRANSPARENT\');\n    }\n\n    /**\n     * Returns true if the event is confirmed\n     * @return bool\n     */\n    public function isConfirmed()\n    {\n        if ( !isset($this->data[\'status\']) ) {\n            return self::DEFAULT_CONFIRMED;\n        } else {\n            return $this->data[\'status\'] == \'CONFIRMED\';\n        }\n    }\n\n    /**\n     * Returns true if duration is multiple of 86400\n     * @return bool\n     */\n    public function isWholeDay()\n    {\n        if (isset($this->data[\'x-microsoft-cdo-alldayevent\']) &&\n            strtolower($this->data[\'x-microsoft-cdo-alldayevent\']) === \'true\') {\n            $result = true;\n        } else {\n            $dur = $this->getDuration();\n            if ($dur > 0 && ($dur % 86400) == 0) {\n                $result = true;\n            } else {\n                $result = false;\n            }\n        }\n        return $result;\n    }\n\n    /**\n     * Returns the timestamp for the beginning of the event\n     * @return int\n     */\n    public function getStart()\n    {\n        return $this->start;\n    }\n\n    /**\n     * Returns the timestamp for the end of the event\n     * @return int\n     */\n    public function getEnd()\n    {\n        return $this->end;\n    }\n\n    /**\n     * Returns the timestamp for the end of the last event\n     * @return int\n     */\n    public function getRangeEnd()\n    {\n        return max($this->end,$this->lastend);\n    }\n\n    /**\n     * Returns the duration of this event in seconds\n     * @return int\n     */\n    public function getDuration()\n    {\n        return $this->end - $this->start;\n    }\n\n    /**\n     * Returns the given property of the event.\n     * @param  string $prop\n     * @return string\n     */\n    public function getProperty( $prop )\n    {\n        if ( isset($this->$prop) ) {\n            return $this->$prop;\n        } elseif ( isset($this->data[$prop]) ) {\n            return $this->data[$prop];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Set default timezone (temporary) to get timestamps\n     * @return string\n     */\n    protected function setLineTimeZone( Line $line )\n    {\n        if ( isset($line[\'tzid\']) ) {\n            if (!isset($this->previous_tz)) {\n                $this->previous_tz = @ date_default_timezone_get();\n            }\n            $this->tzid = $line[\'tzid\'];\n            date_default_timezone_set($this->tzid);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Calculates the timestamp from a DT line.\n     * @param $line Line\n     * @return int\n     */\n    protected function getTimestamp( Line $line, ICal $ical )\n    {\n        if ( isset($line[\'tzid\']) ) {\n            $this->setLineTimeZone($line);\n            //$tz = $ical->getTimeZoneInfo($line[\'tzid\']);\n            //$offset = $tz->getOffset($ts);\n            //$ts = strtotime(date(\'D, d M Y H:i:s\', $ts) . \' \' . $offset);\n        }\n        $ts = strtotime($line->getData());\n\n        return $ts;\n    }\n}\n\n// namespace TKr\\ICal;\n// use DateTime;\n// use DateTimeZone;\n\n/**\n * The wrapper for vtimezones. Stores the timezone-id and the setup for\n * daylight savings and standard time.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VTimeZone\n{\n    protected $tzid;\n    protected $daylight;\n    protected $standard;\n    protected $cache = array();\n\n    /**\n     * Constructs a new intouch\\ical\\VTimeZone\n     */\n    public function __construct( $data )\n    {\n        $this->tzid = $data[\'tzid\'];\n        $this->daylight = $data[\'daylight\'];\n        $this->standard = $data[\'standard\'];\n    }\n\n    /**\n     * Returns the timezone-id for this timezone. (Used to\n     * differentiate between different tzs in a calendar)\n     * @return string\n     */\n    public function getTimeZoneId()\n    {\n        return $this->tzid;\n    }\n\n    /**\n     * Returns the given offset in this timezone for the given\n     * timestamp. (eg +0200)\n     * @param  int    $ts\n     * @return string\n     */\n    public function getOffset( $ts )\n    {\n        $act = $this->getActive($ts);\n\n        return $this->{$act}[\'tzoffsetto\'];\n    }\n\n    /**\n     * Returns the timezone name for the given timestamp (eg CEST)\n     * @param  int    $ts\n     * @return string\n     */\n    public function getTimeZoneName($ts)\n    {\n        $act = $this->getActive($ts);\n\n        return $this->{$act}[\'tzname\'];\n    }\n\n    /**\n     * Determines which of the daylight or standard is the active\n     * setting.\n     * The call is cached for a given timestamp, so a call to\n     * getOffset and getTimeZoneName with the same ts won\'t calculate\n     * the answer twice.\n     * @param  int    $ts\n     * @return string standard|daylight\n     */\n    private function getActive( $ts )\n    {\n        if (class_exists(\'DateTimeZone\')) {\n\n            //PHP >= 5.2\n            $tz = new DateTimeZone( $this->tzid );\n            $date = new DateTime(\"@$ts\", $tz);\n\n            return ($date->format(\'I\') == 1) ? \'daylight\' : \'standard\';\n\n        } else {\n\n            if ( isset($this->cache[$ts]) ) {\n                return $this->cache[$ts];\n            }\n\n            $daylight_freq = new Freq($this->daylight[\'rrule\'], strtotime($this->daylight[\'dtstart\']));\n            $standard_freq = new Freq($this->standard[\'rrule\'], strtotime($this->standard[\'dtstart\']));\n            $last_standard = $standard_freq->previousOccurrence($ts);\n            $last_dst = $daylight_freq->previousOccurrence($ts);\n            if ($last_dst > $last_standard) {\n                $this->cache[$ts] = \'daylight\';\n            } else {\n                $this->cache[$ts] = \'standard\';\n            }\n\n            return $this->cache[$ts];\n        }\n    }\n}\n', '\n\n// TXP 4.6+ tag registration\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\n    Txp::get(\'\\Textpattern\\Tag\\Registry\')\n        ->register(\'mka_ical\')\n        ->register(\'mka_format_date\')\n        ->register(\'mka_if_first_event\')\n        ->register(\'mka_if_last_event\')\n        ->register(\'mka_if_different\')\n    ;\n}\n\n// -------------------------------------------------------------\nfunction mka_ical($atts, $thing = \'\')\n{\n    global $mka_this_ical_event;\n\n    extract(lAtts(array(\n        \'url\' => \'\',\n        \'cachingtime\' => 30,\n        \'googlemail\' => \'\',\n        \'googlemaildomain\' => \'googlemail\',\n        \'limit\' => 0,\n        \'pastevents\' => false,\n        \'fmttime\' => \'%H:%M\',\n        \'fmtdate\' => \'%d.%m.%Y\',\n        \'imgfolder\' => \'\',\n        \'imgcat\' => \'\',\n        \'defaultimage\' => \'none.gif\',\n        \'break\' => \'li\',\n        \'class\' => __FUNCTION__,\n        \'cssid\' => \'icalevents\',\n        \'html_id\' => \'icalevents\',\n        \'wraptag\' => \'ul\',\n        \'form\' => \'\'\n    ), $atts, false));\n\n    // Cater for legacy cssid -> html_id\n    // if cssid is specified and not default value, update html_id\n    if ($cssid != $html_id) {\n        $html_id = $cssid;\n    }\n\n    if (strlen($thing) > 0) {\n        // Use contained code\n        $body = $thing;\n    } elseif (!empty($form)) {\n        // OR: Get form if specified\n        $body = fetch_form($form);\n    } else {\n        // ELSE: Default output\n        $body =  \'<span class=\"date\">{date}</span>\'\n                .\'<span class=\"title\">{title}</span>\'\n                .\'<span class=\"description\">{description}</span>\';\n    }\n\n    // Get url from googlemail-address\n    if (strlen($googlemail) > 0) {\n        $tmp = explode(\"@\", $googlemail);\n        if (count($tmp > 1)) {\n            // Full email address: just encode \'@\' to \'%40\'\n            $gmail = rawurlencode($googlemail);\n        } else {\n            // Just username: construct email using attribute \"googlemaildomain\" (e.g. googlemail / gmail)\n            $gmail = $tmp[0].\"%40\".$googlemaildomain.\".com\";\n        }\n//      $urls[] = \"https://www.google.com/calendar/ical/\".$gmail.\"/public/basic.ics\";\n        $urls[] = \"https://calendar.google.com/calendar/ical/\".$gmail.\"/public/basic.ics\";\n    }\n\n    // Add given urls from attribute url\n    if (strlen($url) > 0) {\n        foreach (explode(\",\", $url) as $u) {\n            $urls[] = trim($u);\n        }\n    }\n\n    // No url --> nothing to show --> error message.\n    if (sizeof($urls) === 0) {\n        return \"mka_ical Error: No url specified.\";\n    }\n\n    // Get images from folder and/or category\n    $show_img = false;\n    if ((strlen($imgcat) > 0) || (strlen($imgfolder) > 0)) {\n        $images = array();\n\n        // Add images from given categories\n        if (strlen($imgcat) > 0) {\n            $images = mka_imgarrayfromcat($imgcat);\n        }\n\n        // Add images from given folder\n        if (file_exists($imgfolder)) {\n            $images = array_merge(mka_imgarrayfromfolder($imgfolder));\n        }\n\n        if (sizeof($images) > 0) {\n            $show_img = true;\n\n            // Get default image\n            $defimg = ical_defimg($defaultimage);\n        }\n    }\n\n    // Get events from cache\n    $events = checkCache($urls, $cachingtime);\n\n    // Start counter\n    $count = 0;\n    // Get uid of last item\n    $last_event = end($events)->uid;\n\n    // Loop through events\n    foreach ($events as $evt) {\n\n        // TODO :: recurrence auswerten\n\n        // Is event in future or ongoing?\n        // if (($evt->getEnd() > time()) && ($limit == 0  || $count < $limit ))\n        if (($pastevents || $evt->end > time()) && ($limit == 0 || $count < $limit)) {\n\n            // Advance counter\n            ++$count;\n\n            // First event\n            $mka_this_ical_event[\'is_first\'] = ($count == \'1\');\n\n            // Last event in array or last in limit\n            if (($evt->uid == $last_event) || ($count == $limit)) {\n                $mka_this_ical_event[\'is_last\'] = true;\n            }\n\n            // TODO :: TextileRestricted($text, $lite = 1, $noimage = 1, $rel = \'nofollow\')\n            $evt->sum = ical_formatText($evt->sum);\n            $evt->des = ical_formatText($evt->des);\n            $evt->loc = ical_formatText($evt->loc);\n\n            $dateView  = ical_getDateView($evt, $fmtdate, $fmttime);\n            $startDatetime = ical_getDateTime($evt, \"%Y-%m-%d %H:%M\", 0); // datetime format\n            $startDate = ical_getDateTime($evt, $fmtdate, 0);\n            $startTime = ical_getDateTime($evt, $fmttime, 0);\n            $endDatetime = ical_getDateTime($evt, \"%Y-%m-%d %H:%M\", 0); // datetime format\n            $endDate   = ical_getDateTime($evt, $fmtdate, 1);\n            $endTime   = ical_getDateTime($evt, $fmttime, 1);\n            $duration  = gmdate(\"z:H:i:s\", $evt->duration); // convert seconds to days:hours:minutes:seconds\n\n            $keys = array();\n            $vals = array();\n            if ($show_img) {\n                $image = ical_img($evt->sum, $images, $defimg);\n                $keys[] = \'{imagesrc}\';\n                $vals[] = $image[\'src\'];\n                $keys[] = \'{imageid}\';\n                $vals[] = $image[\'id\'];\n            }\n            $keys[] = \'{date}\';\n            $vals[] = $dateView;\n            $keys[] = \'{start_datetime}\';\n            $vals[] = $startDatetime;\n            $keys[] = \'{start_date}\';\n            $vals[] = $startDate;\n            $keys[] = \'{start_time}\';\n            $vals[] = $startTime;\n            $keys[] = \'{end_datetime}\';\n            $vals[] = $endDatetime;\n            $keys[] = \'{end_date}\';\n            $vals[] = $endDate;\n            $keys[] = \'{end_time}\';\n            $vals[] = $endTime;\n            $keys[] = \'{duration}\';\n            $vals[] = $duration;\n            $keys[] = \'{title}\';\n            $vals[] = $evt->sum;\n            $keys[] = \'{description}\';\n            $vals[] = $evt->des;\n            $keys[] = \'{location}\';\n            $vals[] = $evt->loc;\n            $keys[] = \'{uid}\';\n            $vals[] = $evt->uid;\n\n            $out[] = parse(str_replace($keys, $vals, $body));\n        }\n\n    }\n\n    return doWrap($out, $wraptag, $break, $class, \'\', \'\', \'\', $html_id);\n}\n\n// -------------------------------------------------------------\n\n/* Simple date reformatter */\n/* Use with {start_datetime} and {end_datetime} */\n/* date formats as per http://php.net/manual/en/function.strftime.php */\n\nfunction mka_format_date($atts, $thing = \'\')\n{\n    extract(lAtts(array(\n        \'format\' => \'\'\n    ), $atts, false));\n\n    if ($format === \'\') {\n        return $thing;\n    } else {\n        return strftime($format, strtotime($thing));\n    }\n}\n\n// -------------------------------------------------------------\nfunction mka_if_first_event($atts, $thing = null)\n{\n    global $mka_this_ical_event;\n    $x = !empty($mka_this_ical_event[\'is_first\']);\n    return isset($thing) ? parse($thing, $x) : $x;\n}\n\n// -------------------------------------------------------------\nfunction mka_if_last_event($atts, $thing = null)\n{\n    global $mka_this_ical_event;\n    $x = !empty($mka_this_ical_event[\'is_last\']);\n    return isset($thing) ? parse($thing, $x) : $x;\n}\n\n// -------------------------------------------------------------\nfunction mka_if_different($atts, $thing)\n{\n    static $last = array();\n\n    extract(lAtts(array(\'key\' => md5($thing)), $atts));\n    $out = parse($thing, 1);\n\n    if (empty($last[$key]) || $out != $last[$key]) {\n        return $last[$key] = $out;\n    } else {\n        return parse($thing, 0);\n    }\n}\n\n// -------------------------------------------------------------\nfunction ical_img($title, $images, $defimg)\n{\n    $search = array(\" \", \".\", \"-\");\n    $rep = array(\"\", \"\", \"\");\n    $t = str_replace($search, $rep, strtolower($title));\n    foreach ($images as $im) {\n        if (strpos($t, $im[\'name\']) !== false) {\n            return $im;\n        }\n    }\n    // return default image src\n    return $defimg;\n}\n\n// -------------------------------------------------------------\nfunction ical_getDateView($evt, $fmt_date, $fmt_time)\n{\n    $date_separator = \" – \";\n\n    $fmt_dati = $fmt_date.\" \".$fmt_time;\n    // event on one day\n    if (date(\"dmY\", $evt->start) == date(\"dmY\", $evt->end-1)) {\n        // no exact time definition\n        if (date(\"H:i\", $evt->start) == \"00:00\" && date(\"H:i\", $evt->end) == \"00:00\") {\n            $dateView = strftime($fmt_date, $evt->start);\n        } else {\n            $dateView = strftime($fmt_dati, $evt->start).$date_separator.strftime($fmt_time, $evt->start + $evt->duration);\n        }\n    } else {\n        $dateView  = strftime($fmt_date, $evt->start).$date_separator.strftime($fmt_date, $evt->end-1);\n    }\n    return $dateView;\n}\n\n// -------------------------------------------------------------\nfunction ical_getDateTime($evt, $formatter, $end)\n{\n    $dateView = ($end == 1) ? strftime($formatter, $evt->end) : strftime($formatter, $evt->start);\n    return $dateView;\n}\n\n// -------------------------------------------------------------\nfunction mka_makearr_sql($str, $type=\'string\', $delimeter =\',\')\n{\n    $ret = array();\n    $arr = explode($delimeter, $str);\n    foreach ($arr as $el) {\n        if ($type == \'string\') {\n            $ret[] = \"\'\".trim($el).\"\'\";\n        } else {\n            $ret[] = $el;\n        }\n    }\n    return $ret;\n}\n\n// -------------------------------------------------------------\nfunction mka_imgarrayfromcat($cats)\n{\n    $imgs = array();\n\n    $cats = mka_makearr_sql($cats);\n    $clause = \'category IN (\'.implode(\",\", $cats).\')\';\n    $attrs = \'id, name, ext\';\n    $rs = safe_rows($attrs, \'txp_image\', $clause);\n\n    $prefix = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\".$GLOBALS[\'prefs\'][\'img_dir\'].\"/\";\n\n    if (count($rs) != 0) {\n        foreach ($rs as $col) {\n            $col[\'src\'] = $prefix.$col[\'id\'].$col[\'ext\'];\n            $imgs[] = $col;\n        }\n    }\n    return $imgs;\n}\n\n// -------------------------------------------------------------\nfunction mka_imgarrayfromfolder($imgfolder)\n{\n    $imgs = array();\n    if ($handle = @opendir($imgfolder)) {\n        while (($file = @readdir($handle)) !== false) {\n            $img = array();\n            $parts = explode(\".\", $file);\n            $img[\'ext\']  = (count($parts) > 1) ? \".\".array_pop($parts) : \'\';\n            $img[\'name\'] = implode(\".\", $parts);\n            $img[\'src\'] = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\".$imgfolder.\"/\".$file;\n            if (strlen($img[\'name\']) > 0 && in_array(strtolower($img[\'ext\']), array(\".png\", \".jpg\", \".jpeg\", \".gif\"))) {\n                $imgs[] = $img;\n            }\n        }\n        closedir($handle);\n    }\n    return $imgs;\n}\n\n// -------------------------------------------------------------\nfunction ical_defimg($defimg)\n{\n    $url = \"http://\".$GLOBALS[\'prefs\'][\"siteurl\"].\"/\";\n    $img = array();\n    // Is defimg id?\n    if (intval($defimg) !== 0) {\n        $id = intval($defimg);\n        $img = safe_row(\'id, name, ext\', \'txp_image\', \'id = \'.$id);\n    }\n\n    // Image with id not found?\n    if (sizeof($img) === 0) {\n        $parts = explode(\".\", basename($defimg));\n        $img[\'ext\']  = (count($parts) > 1) ? \".\".array_pop($parts) : \'\';\n        $img[\'name\'] = implode(\".\", $parts);\n        $img[\'src\'] = $url.$defimg;\n    } else {\n        $img[\"src\"] = $url.$GLOBALS[\'prefs\'][\'img_dir\'].\"/\".$img[\'id\'].$img[\'ext\'];\n    }\n    return $img;\n}\n\n// -------------------------------------------------------------\nfunction ical_shorten_link($string)\n{\n    $text_word_maxlength = 55;\n    if (count($string) == 2) {\n        $pre = \"\";\n        $url = $string[1];\n    } else {\n        $pre = $string[1];\n        $url = $string[2];\n    }\n    $shortened_url = $url;\n    if (strlen($url) > $text_word_maxlength) {\n        $shortened_url = substr($url, 0, ($text_word_maxlength/2)) . \"…\" . substr($url, - ($text_word_maxlength-3-$text_word_maxlength/2));\n    }\n    return $pre.\"<a href=\\\"\".$url.\"\\\">\".$shortened_url.\"</a>\";\n}\n\n// -------------------------------------------------------------\nfunction ical_formatText($string)\n{\n    $string = \' \' . $string;\n    $string = preg_replace_callback(\"#(^|[\\n ])([\\w]+?://.*?[^ \\\"\\n\\r\\t<]*)#is\", \"ical_shorten_link\", $string);\n    $string = preg_replace(\"#(^|[\\n ])((www|ftp)\\.[\\w\\-]+\\.[\\w\\-.\\~]+(?:/[^ \\\"\\t\\n\\r<]*)?)#is\", \"$1<a href=\\\"http://$2\\\">$2</a>\", $string);\n    $string = str_replace(\"\\n\", \"<br/>\", $string);\n    return $string;\n}\n\n\n\n// -------------------------------------------------------------\nfunction checkCache($urls, $cachingtime)\n{\n    $hash = md5(implode(\",\", $urls));\n	// Use cached json file in temp directory\n    $fn = $GLOBALS[\'prefs\'][\"tempdir\"]. \"/mka_ical_\".$hash.\".json\";\n\n    if (file_exists($fn) && (time()-filemtime($fn) < ($cachingtime*60))) {\n        // Eventstring from cache file\n        $cache_file = fopen($fn, \"r\");\n        $data = fread($cache_file, filesize($fn));\n        fclose($cache_file);\n        return json_decode($data);\n    } else {\n        // Generate new eventstring\n        $events = getEvents($urls);\n\n        $cf = fopen($fn, \"w+\");\n        fwrite($cf, json_encode($events));\n        fclose($cf);\n\n        return $events;\n    }\n}\n\n// -------------------------------------------------------------\nfunction getEvents($urls)\n{\n    $events = array();\n\n    // Instantiate ICal() library\n    $reader = new ICal();\n\n    foreach ($urls as $u) {\n        $reader->setUrl($u, true);\n        $e = $reader->getEvents();\n        foreach ($e as $event) {\n            // $plainEvents = getPlainEvents($event);\n            // $events = array_merge($events, $plainEvents);\n            $events[] = getPlainEvent($event);\n        }\n    }\n    // Sort events by startdate\n    usort($events, \"cmp_event\");\n    return $events;\n}\n\n// -------------------------------------------------------------\nfunction getPlainEvent($evt)\n{\n    $plainEvent = array();\n\n    $plainEvent = new stdClass;\n    $plainEvent->start     = $evt->getStart();\n    $plainEvent->end       = $evt->getEnd();\n    $plainEvent->duration  = $evt->getDuration();\n    $plainEvent->sum       = $evt->getSummary();\n    $plainEvent->des       = $evt->getDescription();\n    $plainEvent->loc       = $evt->getLocation();\n    $plainEvent->uid       = $evt->getUID();\n\n    $rec = $evt->getProperty(\'recurrence\');\n    if ($rec === null) {\n        $plainEvent->rec = false;\n    } else {\n        // $plainEvent->rec = json_encode(getPlainRec($rec));\n        $plainEvent->rec = getPlainRec($rec);\n    }\n    return $plainEvent;\n}\n\n// -------------------------------------------------------------\nfunction getPlainRec($rec)\n{\n    $r = new stdClass;\n    $class_methods = get_class_methods($rec);\n    foreach ($class_methods as $method) {\n        if (substr($method, 0, 3) === \'get\') {\n            $prop = strtolower(substr($method, 3));\n            $r->$prop = $rec->$method();\n        }\n    }\n    return $r;\n}\n\n// -------------------------------------------------------------\nfunction cmp_event($a, $b)\n{\n    $da = $a->start;\n    $db = $b->start;\n    if ($da == $db) {\n        return 1;\n    }\n    return ($da < $db) ? -1 : 1;\n}\n\n\n\n/**\n * This file is a concatenation of the src files for\n * https://github.com/TKr/intouch-iCalendar/tree/master/src\n * with the following modifications\n * Removal of intermediary <?php … ?> tags\n * Commenting out of intermediary namespace (except for initial namespace)\n * Commenting out of duplicate \"use\" statements\n *\n */\n\ndefine(\'SG_ICALREADER_VERSION\', \'0.8.0\');\n\n/**\n * A simple ICal parser.\n *\n * http://github.com/mfkahn/intouch-iCalendar based on\n * http://github.com/fangel/SG-iCalendar\n *\n * Roadmap:\n * - Finish FREQUENCY-parsing.\n * - Add API for recurring events\n *\n * A simple example:\n * <code>\n * <?php\n * use TKr\\ical\\ICal;\n * $ical = new ICal(\"http://example.com/calendar.ics\");\n * foreach ( $ical->getEvents() As $event ) {\n *   // Do stuff with the event $event\n * }\n * ?>\n * </code>\n *\n * @author Morten Fangel (C) 2008\n * @author xonev (C) 2010\n * @author Tanguy Pruvot (C) 2010\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass ICal\n{\n    //objects\n    public $information; //VCalendar\n    public $timezones;   //VTimeZone\n\n    protected $events; //VEvent[]\n\n    /**\n     * Constructs a new iCalReader. You can supply the url now, or later using setUrl\n     * @param $url string\n     */\n    public function __construct($url = false)\n    {\n        $this->setUrl($url);\n    }\n\n    /**\n     * Sets (or resets) the url this reader reads from.\n     * @param $url string\n     */\n    public function setUrl( $url = false )\n    {\n        if ($url !== false) {\n            Parser::Parse($url, $this);\n        }\n    }\n\n    /**\n     * Returns the main calendar info. You can then query the returned\n     * object with ie getTitle().\n     * @return VCalendar\n     */\n    public function getCalendarInfo()\n    {\n        return $this->information;\n    }\n\n    /**\n     * Sets the calendar info for this calendar\n     * @param VCalendar $info\n     */\n    public function setCalendarInfo( VCalendar $info )\n    {\n        $this->information = $info;\n    }\n\n    /**\n     * Returns a given timezone for the calendar. This is mainly used\n     * by VEvents to adjust their date-times if they have specified a\n     * timezone.\n     *\n     * If no timezone is given, all timezones in the calendar is\n     * returned.\n     *\n     * @param $tzid string\n     * @return VTimeZone\n     */\n    public function getTimeZoneInfo( $tzid = null )\n    {\n        if ($tzid == null) {\n            return $this->timezones;\n        } else {\n            if ( !isset($this->timezones)) {\n                return null;\n            }\n            foreach ($this->timezones AS $tz) {\n                if ( $tz->getTimeZoneId() == $tzid ) {\n                    return $tz;\n                }\n            }\n\n            return null;\n        }\n    }\n\n    /**\n     * Adds a new timezone to this calendar\n     * @param VTimeZone $tz\n     */\n    public function addTimeZone( VTimeZone $tz )\n    {\n        $this->timezones[] = $tz;\n    }\n\n    /**\n     * Returns the events found\n     * @return array\n     */\n    public function getEvents()\n    {\n        return $this->events;\n    }\n\n    /**\n     * Adds a event to this calendar\n     * @param VEvent $event\n     */\n    public function addEvent( VEvent $event )\n    {\n        $this->events[] = $event;\n    }\n}\n\n/**\n * Legacy - empty subclass of ICal\n * @internal\n */\nclass iCalReader extends ICal {}\n\n// namespace TKr\\ICal;\n\n/**\n * A class for calculating how many seconds a duration-string is\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\n\nclass Duration\n{\n    protected $dur;\n\n    /**\n     * Constructs a new intouch\\ical\\Duration from a duration-rule.\n     * The basic build-up of DURATIONs are:\n     *  ([\"+\"] / \"-\") \"P\" (dur-date / dur-date + \"T\" + dur-time / dur-time / dur-week)\n     * Is solved via a really fugly reg-exp with way to many ()\'s..\n     *\n     * @param $duration string\n     */\n    public function __construct( $duration )\n    {\n        $ts = 0;\n\n        if (preg_match(\'/[\\\\+\\\\-]{0,1}P((\\d+)W)?((\\d+)D)?(T)?((\\d+)H)?((\\d+)M)?((\\d+)S)?/\', $duration, $matches) === 1) {\n            $results = array(\n                \'weeks\'=>  (int) @ $matches[2],\n                \'days\'=>   (int) @ $matches[4],\n                \'hours\'=>  (int) @ $matches[7],\n                \'minutes\'=>(int) @ $matches[9],\n                \'seconds\'=>(int) @ $matches[11]\n            );\n\n            $ts += $results[\'seconds\'];\n            $ts += 60 * $results[\'minutes\'];\n            $ts += 60 * 60 * $results[\'hours\'];\n            $ts += 24 * 60 * 60 * $results[\'days\'];\n            $ts += 7 * 24 * 60 * 60 * $results[\'weeks\'];\n        } else {\n            // Invalid duration!\n        }\n\n        $dir = ($duration{0} == \'-\') ? -1 : 1;\n\n        $this->dur = $dir * $ts;\n    }\n\n    /**\n     * Returns the duration in seconds\n     * @return int\n     */\n    public function getDuration()\n    {\n        return $this->dur;\n    }\n}\n\n// namespace TKr\\ICal;\n// use ArrayObject;\n\n/**\n * A simple Factory for converting a section/data pair into the\n * corrosponding block-object. If the section isn\'t known a simple\n * ArrayObject is used instead.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Factory\n{\n    /**\n     * Returns a new block-object for the section/data-pair. The list\n     * of returned objects is:\n     *\n     * vcalendar => intouch\\ical\\VCalendar\n     * vtimezone => intouch\\ical\\VTimeZone\n     * vevent => intouch\\ical\\VEvent\n     * * => ArrayObject\n     *\n     * @param $ical ICal The reader this section/data-pair belongs to\n     * @param $section string\n     * @param intouch\\ical\\Line[]\n     */\n    public static function portal( ICal $ical, $section, $data )\n    {\n        switch ($section) {\n            case \"vcalendar\":\n                return new VCalendar(Line::Remove_Line($data), $ical );\n            case \"vtimezone\":\n                return new VTimeZone(Line::Remove_Line($data), $ical );\n            case \"vevent\":\n                return new VEvent($data, $ical );\n\n            default:\n                return new ArrayObject(Line::Remove_Line((array) $data) );\n        }\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * A class to store Frequency-rules in. Will allow a easy way to find the\n * last and next occurrence of the rule.\n *\n * No - this is so not pretty. But.. ehh.. You do it better, and I will\n * gladly accept patches.\n *\n * Created by trail-and-error on the examples given in the RFC.\n *\n * TODO: Update to a better way of doing calculating the different options.\n * Instead of only keeping track of the best of the current dates found\n * it should instead keep a array of all the calculated dates within the\n * period.\n * This should fix the issues with multi-rule + multi-rule interference,\n * and make it possible to implement the SETPOS rule.\n * By pushing the next period onto the stack as the last option will\n * (hopefully) remove the need for the awful simpleMode\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Freq\n{\n    protected $weekdays = array(\'MO\'=>\'monday\', \'TU\'=>\'tuesday\', \'WE\'=>\'wednesday\', \'TH\'=>\'thursday\', \'FR\'=>\'friday\', \'SA\'=>\'saturday\', \'SU\'=>\'sunday\');\n    protected $knownRules = array(\'month\', \'weekno\', \'day\', \'monthday\', \'yearday\', \'hour\', \'minute\'); //others : \'setpos\', \'second\'\n    protected $ruleModifiers = array(\'wkst\');\n    protected $simpleMode = true;\n\n    protected $rules = array(\'freq\'=>\'yearly\', \'interval\'=>1);\n    protected $start = 0;\n    protected $freq = \'\';\n\n    protected $excluded = array(); //EXDATE\n    protected $added;    //RDATE\n\n    protected $cache; // getAllOccurrences()\n\n    /**\n     * Constructs a new Freqency-rule\n     * @param $rule string\n     * @param $start int Unix-timestamp (important : Need to be the start of Event)\n     * @param $excluded array of int (timestamps), see EXDATE documentation\n     * @param $added array of int (timestamps), see RDATE documentation\n     */\n    public function __construct($recurrence, $start, $excluded=array(), $added=array(), $until = null)\n    {\n        $this->start = $start;\n        $this->excluded = array();\n        $rule = $recurrence->rrule;\n\n        $rules = array();\n        foreach ( explode(\';\', $rule) as $v) {\n            list($k, $v) = explode(\'=\', $v);\n            $this->rules[ strtolower($k) ] = $v;\n        }\n\n        if (!isset($this->rules[\'until\'])) {\n            if (!$until && isset($recurrence->until)) {\n                $this->rules[\'until\'] = $recurrence->until;\n            } elseif($until) {\n                $this->rules[\'until\'] = $until;\n            }\n        }\n        if (isset($this->rules[\'until\']) && is_string($this->rules[\'until\']) ) {\n            $this->rules[\'until\'] = strtotime($this->rules[\'until\']);\n        }\n        $this->freq = strtolower($this->rules[\'freq\']);\n\n        foreach ($this->knownRules AS $rule) {\n            if ( isset($this->rules[\'by\' . $rule]) ) {\n                if ( $this->isPrerule($rule, $this->freq) ) {\n                    $this->simpleMode = false;\n                }\n            }\n        }\n\n        if (!$this->simpleMode) {\n            if (! (isset($this->rules[\'byday\']) || isset($this->rules[\'bymonthday\']) || isset($this->rules[\'byyearday\']))) {\n                $this->rules[\'bymonthday\'] = date(\'d\', $this->start);\n            }\n        }\n\n        //set until, and cache\n        if ( isset($this->rules[\'count\']) ) {\n\n            $cache[$ts] = $ts = $this->start;\n            for ($n=1; $n < $this->rules[\'count\']; $n++) {\n                $ts = $this->findNext($ts);\n                $cache[$ts] = $ts;\n            }\n            $this->rules[\'until\'] = $ts;\n\n            //EXDATE\n            if (!empty($excluded)) {\n                foreach ($excluded as $ts) {\n                    unset($cache[$ts]);\n                }\n            }\n            //RDATE\n            if (!empty($added)) {\n                $cache = $cache + $added;\n                asort($cache);\n            }\n\n            $this->cache = array_values($cache);\n        }\n\n        $this->excluded = $excluded;\n        $this->added = $added;\n    }\n\n    /**\n     * Returns all timestamps array(), build the cache if not made before\n     * @return array\n     */\n    public function getAllOccurrences($limit = 100, $offset = false)\n    {\n        if (empty($this->cache)) {\n            //build cache\n            $next = $this->firstOccurrence($offset);\n            $c = 0;\n            while ($next && $c < $limit) {\n                $cache[] = $next;\n                $next = $this->findNext($next);\n                $c++;\n            }\n            if (!empty($this->added)) {\n                $cache = $cache + $this->added;\n                asort($cache);\n            }\n            $this->cache = $cache;\n        }\n\n        return $this->cache;\n    }\n\n    /**\n     * Returns the previous (most recent) occurrence of the rule from the\n     * given offset\n     * @param  int $offset\n     * @return int\n     */\n    public function previousOccurrence( $offset )\n    {\n        if (!empty($this->cache)) {\n            $t2=$this->start;\n            foreach ($this->cache as $ts) {\n                if ($ts >= $offset)\n                    return $t2;\n                $t2 = $ts;\n            }\n        } else {\n            $ts = $this->start;\n            while ( ($t2 = $this->findNext($ts)) < $offset) {\n                if ($t2 == false) {\n                    break;\n                }\n                $ts = $t2;\n            }\n        }\n\n        return $ts;\n    }\n\n    /**\n     * Returns the next occurrence of this rule after the given offset\n     * @param  int $offset\n     * @return int\n     */\n    public function nextOccurrence( $offset )\n    {\n        if ($offset < $this->start)\n            return $this->firstOccurrence();\n        return $this->findNext($offset);\n    }\n\n    /**\n     * Finds the first occurrence of the rule.\n     * @return int timestamp\n     */\n    public function firstOccurrence($offset = false)\n    {\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        $t = ($offset !== false) ? $offset : $this->start;\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        if (in_array($t, $this->excluded))\n            $t = $this->findNext($t);\n\n        return $t;\n    }\n\n    /**\n     * Finds the absolute last occurrence of the rule from the given offset.\n     * Builds also the cache, if not set before...\n     * @return int timestamp\n     */\n    public function lastOccurrence()\n    {\n        //build cache if not done\n        $this->getAllOccurrences();\n        //return last timestamp in cache\n        return end($this->cache);\n    }\n\n    /**\n     * Calculates the next time after the given offset that the rule\n     * will apply.\n     *\n     * The approach to finding the next is as follows:\n     * First we establish a timeframe to find timestamps in. This is\n     * between $offset and the end of the period that $offset is in.\n     *\n     * We then loop though all the rules (that is a Prerule in the\n     * current freq.), and finds the smallest timestamp inside the\n     * timeframe.\n     *\n     * If we find something, we check if the date is a valid recurrence\n     * (with validDate). If it is, we return it. Otherwise we try to\n     * find a new date inside the same timeframe (but using the new-\n     * found date as offset)\n     *\n     * If no new timestamps were found in the period, we try in the\n     * next period\n     *\n     * @param  int $offset\n     * @return int\n     */\n    public function findNext($offset)\n    {\n        if (!empty($this->cache)) {\n            foreach ($this->cache as $ts) {\n                if ($ts > $offset)\n                    return $ts;\n            }\n        }\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        $debug = false;\n\n        //make sure the offset is valid\n        if ( $offset === false || (isset($this->rules[\'until\']) && $offset > $this->rules[\'until\']) ) {\n            if($debug) echo \'STOP: \' . date(\'r\', $offset) . \"\\n\";\n\n            return false;\n        }\n\n        $found = true;\n\n        //set the timestamp of the offset (ignoring hours and minutes unless we want them to be\n        //part of the calculations.\n        if($debug) echo \'O: \' . date(\'r\', $offset) . \"\\n\";\n        $hour = (in_array($this->freq, array(\'hourly\',\'minutely\')) && $offset > $this->start) ? date(\'H\', $offset) : date(\'H\', $this->start);\n        $minute = (($this->freq == \'minutely\' || isset($this->rules[\'byminute\'])) && $offset > $this->start) ? date(\'i\', $offset) : date(\'i\', $this->start);\n        $t = mktime($hour, $minute, date(\'s\', $this->start), date(\'m\', $offset), date(\'d\', $offset), date(\'Y\',$offset));\n        if($debug) echo \'START: \' . date(\'r\', $t) . \"\\n\";\n\n        if ($this->simpleMode) {\n            if ($offset < $t) {\n                $ts = $t;\n                if ($ts && in_array($ts, $this->excluded))\n                    $ts = $this->findNext($ts);\n            } else {\n                $ts = $this->findStartingPoint( $t, $this->rules[\'interval\'], false );\n                if ( !$this->validDate( $ts ) ) {\n                    $ts = $this->findNext($ts);\n                }\n            }\n\n            return $ts;\n        }\n\n        $eop = $this->findEndOfPeriod($offset);\n        if($debug) echo \'EOP: \' . date(\'r\', $eop) . \"\\n\";\n\n        foreach ($this->knownRules AS $rule) {\n            if ( $found && isset($this->rules[\'by\' . $rule]) ) {\n                if ( $this->isPrerule($rule, $this->freq) ) {\n                    $subrules = explode(\',\', $this->rules[\'by\' . $rule]);\n                    $_t = null;\n                    foreach ($subrules AS $subrule) {\n                        $imm = call_user_func_array(array($this, \'ruleBy\' . $rule), array($subrule, $t));\n                        if ($imm === false) {\n                            break;\n                        }\n                        if($debug) echo strtoupper($rule) . \': \' . date(\'r\', $imm) . \' A: \' . ((int) ($imm > $offset && $imm < $eop)) . \"\\n\";\n                        if ( $imm > $offset && $imm < $eop && ($_t == null || $imm < $_t) ) {\n                            $_t = $imm;\n                        }\n                    }\n                    if ($_t !== null) {\n                        $t = $_t;\n                    } else {\n                        $found = $this->validDate($t);\n                    }\n                }\n            }\n        }\n\n        if ($offset < $this->start && $this->start < $t) {\n            $ts = $this->start;\n        } elseif ( $found && ($t != $offset)) {\n            if ( $this->validDate( $t ) ) {\n                if($debug) echo \'OK\' . \"\\n\";\n                $ts = $t;\n            } else {\n                if($debug) echo \'Invalid\' . \"\\n\";\n                $ts = $this->findNext($t);\n            }\n        } else {\n            if($debug) echo \'Not found\' . \"\\n\";\n            $ts = $this->findNext( $this->findStartingPoint( $offset, $this->rules[\'interval\'] ) );\n        }\n        if ($ts && in_array($ts, $this->excluded))\n            return $this->findNext($ts);\n\n        return $ts;\n    }\n\n    /**\n     * Finds the starting point for the next rule. It goes $interval\n     * \'freq\' forward in time since the given offset\n     * @param  int     $offset\n     * @param  int     $interval\n     * @param  boolean $truncate\n     * @return int\n     */\n    private function findStartingPoint( $offset, $interval, $truncate = true )\n    {\n        $_freq = ($this->freq == \'daily\') ? \'day__\' : $this->freq;\n        $t = \'+\' . $interval . \' \' . substr($_freq,0,-2) . \'s\';\n        if ($_freq == \'monthly\' && $truncate) {\n            if ($interval > 1) {\n                $offset = strtotime(\'+\' . ($interval - 1) . \' months \', $offset);\n            }\n            $t = \'+\' . (date(\'t\', $offset) - date(\'d\', $offset) + 1) . \' days\';\n        }\n\n        $sp = strtotime($t, $offset);\n\n        if ($truncate) {\n            $sp = $this->truncateToPeriod($sp, $this->freq);\n        }\n\n        return $sp;\n    }\n\n    /**\n     * Finds the earliest timestamp posible outside this perioid\n     * @param  int $offset\n     * @return int\n     */\n    public function findEndOfPeriod($offset)\n    {\n        return $this->findStartingPoint($offset, 1);\n    }\n\n    /**\n     * Resets the timestamp to the beginning of the\n     * period specified by freq\n     *\n     * Yes - the fall-through is on purpose!\n     *\n     * @param  int $time\n     * @param  int $freq\n     * @return int\n     */\n    private function truncateToPeriod( $time, $freq )\n    {\n        $date = getdate($time);\n        switch ($freq) {\n            case \"yearly\":\n                $date[\'mon\'] = 1;\n            case \"monthly\":\n                $date[\'mday\'] = 1;\n            case \"daily\":\n                $date[\'hours\'] = 0;\n            case \'hourly\':\n                $date[\'minutes\'] = 0;\n            case \"minutely\":\n                $date[\'seconds\'] = 0;\n                break;\n            case \"weekly\":\n                if ( date(\'N\', $time) == 1) {\n                    $date[\'hours\'] = 0;\n                    $date[\'minutes\'] = 0;\n                    $date[\'seconds\'] = 0;\n                } else {\n                    $date = getdate(strtotime(\"last monday 0:00\", $time));\n                }\n                break;\n        }\n        $d = mktime($date[\'hours\'], $date[\'minutes\'], $date[\'seconds\'], $date[\'mon\'], $date[\'mday\'], $date[\'year\']);\n\n        return $d;\n    }\n\n    /**\n     * Applies the BYDAY rule to the given timestamp\n     * @param  string $rule\n     * @param  int    $t\n     * @return int\n     */\n    private function ruleByday($rule, $t)\n    {\n        $rule = str_replace(\'\"\', \'\', $rule);\n        $dir = ($rule{0} == \'-\') ? -1 : 1;\n        $dir_t = ($dir == 1) ? \'next\' : \'last\';\n\n        $d = $this->weekdays[substr($rule,-2)];\n        $s = $dir_t . \' \' . $d . \' \' . date(\'H:i:s\',$t);\n\n        if ( $rule == substr($rule, -2) ) {\n            if ( date(\'l\', $t) == ucfirst($d) ) {\n                $s = \'today \' . date(\'H:i:s\',$t);\n            }\n\n            $_t = strtotime($s, $t);\n\n            if ( $_t == $t && in_array($this->freq, array(\'monthly\', \'yearly\')) ) {\n                // Yes. This is not a great idea.. but hey, it works.. for now\n                $s = \'next \' . $d . \' \' . date(\'H:i:s\',$t);\n                $_t = strtotime($s, $_t);\n            }\n\n            return $_t;\n        } else {\n            $_f = $this->freq;\n            if ( isset($this->rules[\'bymonth\']) && $this->freq == \'yearly\' ) {\n                $this->freq = \'monthly\';\n            }\n            if ($dir == -1) {\n                $_t = $this->findEndOfPeriod($t);\n            } else {\n                $_t = $this->truncateToPeriod($t, $this->freq);\n            }\n            $this->freq = $_f;\n\n            $c = preg_replace(\'/[^0-9]/\',\'\',$rule);\n            $c = ($c == \'\') ? 1 : $c;\n\n            $n = $_t;\n            while ($c > 0) {\n                if ( $dir == 1 && $c == 1 && date(\'l\', $t) == ucfirst($d) ) {\n                    $s = \'today \' . date(\'H:i:s\',$t);\n                }\n                $n = strtotime($s, $n);\n                $c--;\n            }\n\n            return $n;\n        }\n    }\n\n    private function ruleBymonth($rule, $t)\n    {\n        $_t = mktime(date(\'H\',$t), date(\'i\',$t), date(\'s\',$t), $rule, date(\'d\', $t), date(\'Y\', $t));\n        if ( $t == $_t && isset($this->rules[\'byday\']) ) {\n            // TODO: this should check if one of the by*day\'s exists, and have a multi-day value\n            return false;\n        } else {\n            return $_t;\n        }\n    }\n\n    private function ruleBymonthday($rule, $t)\n    {\n        if ($rule < 0) {\n            $rule = date(\'t\', $t) + $rule + 1;\n        }\n\n        return mktime(date(\'H\',$t), date(\'i\',$t), date(\'s\',$t), date(\'m\', $t), $rule, date(\'Y\', $t));\n    }\n\n    private function ruleByyearday($rule, $t)\n    {\n        if ($rule < 0) {\n            $_t = $this->findEndOfPeriod();\n            $d = \'-\';\n        } else {\n            $_t = $this->truncateToPeriod($t, $this->freq);\n            $d = \'+\';\n        }\n        $s = $d . abs($rule -1) . \' days \' . date(\'H:i:s\',$t);\n\n        return strtotime($s, $_t);\n    }\n\n    private function ruleByweekno($rule, $t)\n    {\n        if ($rule < 0) {\n            $_t = $this->findEndOfPeriod();\n            $d = \'-\';\n        } else {\n            $_t = $this->truncateToPeriod($t, $this->freq);\n            $d = \'+\';\n        }\n\n        $sub = (date(\'W\', $_t) == 1) ? 2 : 1;\n        $s = $d . abs($rule - $sub) . \' weeks \' . date(\'H:i:s\',$t);\n        $_t  = strtotime($s, $_t);\n\n        return $_t;\n    }\n\n    private function ruleByhour($rule, $t)\n    {\n        $_t = mktime($rule, date(\'i\',$t), date(\'s\',$t), date(\'m\',$t), date(\'d\', $t), date(\'Y\', $t));\n\n        return $_t;\n    }\n\n    private function ruleByminute($rule, $t)\n    {\n        $_t = mktime(date(\'h\',$t), $rule, date(\'s\',$t), date(\'m\',$t), date(\'d\', $t), date(\'Y\', $t));\n\n        return $_t;\n    }\n\n    private function validDate( $t )\n    {\n        if ( isset($this->rules[\'until\']) && $t > $this->rules[\'until\'] ) {\n            return false;\n        }\n\n        if (!is_array($this->excluded)) {\n            $this->excluded = (array)$this->excluded;\n        }\n        if (in_array($t, $this->excluded)) {\n            return false;\n        }\n\n        if ( isset($this->rules[\'bymonth\']) ) {\n            $months = explode(\',\', $this->rules[\'bymonth\']);\n            if ( !in_array(date(\'m\', $t), $months)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byday\']) ) {\n            $days = explode(\',\', $this->rules[\'byday\']);\n            foreach ($days As $i => $k) {\n                $days[$i] = $this->weekdays[ preg_replace(\'/[^A-Z]/\', \'\', $k)];\n            }\n            if ( !in_array(strtolower(date(\'l\', $t)), $days)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byweekno\']) ) {\n            $weeks = explode(\',\', $this->rules[\'byweekno\']);\n            if ( !in_array(date(\'W\', $t), $weeks)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'bymonthday\'])) {\n            $weekdays = explode(\',\', $this->rules[\'bymonthday\']);\n            foreach ($weekdays As $i => $k) {\n                if ($k < 0) {\n                    $weekdays[$i] = date(\'t\', $t) + $k + 1;\n                }\n            }\n            if ( !in_array(date(\'d\', $t), $weekdays)) {\n                return false;\n            }\n        }\n        if ( isset($this->rules[\'byhour\']) ) {\n            $hours = explode(\',\', $this->rules[\'byhour\']);\n            if ( !in_array(date(\'H\', $t), $hours)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private function isPrerule($rule, $freq)\n    {\n        if( $rule == \'year\')\n\n            return false;\n        if( $rule == \'month\' && $freq == \'yearly\')\n\n            return true;\n        if( $rule == \'monthday\' && in_array($freq, array(\'yearly\', \'monthly\')) && !isset($this->rules[\'byday\']))\n\n            return true;\n        // TODO: is it faster to do monthday first, and ignore day if monthday exists? - prolly by a factor of 4..\n        if( $rule == \'yearday\' && $freq == \'yearly\' )\n\n            return true;\n        if( $rule == \'weekno\' && $freq == \'yearly\' )\n\n            return true;\n        if( $rule == \'day\' && in_array($freq, array(\'yearly\', \'monthly\', \'weekly\')))\n\n            return true;\n        if( $rule == \'hour\' && in_array($freq, array(\'yearly\', \'monthly\', \'weekly\', \'daily\')))\n\n            return true;\n        if( $rule == \'minute\' )\n\n            return true;\n\n        return false;\n    }\n}\n\n// namespace TKr\\ICal;\n\n// use \\ArrayAccess;\n// use ArrayIterator;\n// use \\Countable;\n// use \\IteratorAggregate;\n\n/**\n * A class for storing a single (complete) line of the ICal file.\n * Will find the line-type, the arguments and the data of the file and\n * store them.\n *\n * The line-type can be found by querying getIdent(), data via either\n * getData() or typecasting to a string.\n * Params can be access via the ArrayAccess. A iterator is also avilable\n * to iterator over the params.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Line implements ArrayAccess, Countable, IteratorAggregate\n{\n    protected $ident;\n    protected $data;\n    protected $params = array();\n\n    protected $replacements = array(\'from\'=>array(\'\\\\,\', \'\\\\n\', \'\\\\;\', \'\\\\:\', \'\\\\\"\'), \'to\'=>array(\',\', \"\\n\", \';\', \':\', \'\"\'));\n\n    /**\n     * Constructs a new line.\n     */\n    public function __construct( $line )\n    {\n        $split = strpos($line, \':\');\n        $idents = explode(\';\', substr($line, 0, $split));\n        $ident = strtolower(array_shift($idents));\n\n        $data = trim(substr($line, $split+1));\n        $data = str_replace($this->replacements[\'from\'], $this->replacements[\'to\'], $data);\n\n        $params = array();\n        foreach ($idents AS $v) {\n            list($k, $v) = explode(\'=\', $v);\n            $params[ strtolower($k) ] = $v;\n        }\n\n        $this->ident = $ident;\n        $this->params = $params;\n        $this->data = $data;\n    }\n\n    /**\n     * Is this line the begining of a new block?\n     * @return bool\n     */\n    public function isBegin()\n    {\n        return $this->ident == \'begin\';\n    }\n\n    /**\n     * Is this line the end of a block?\n     * @return bool\n     */\n    public function isEnd()\n    {\n        return $this->ident == \'end\';\n    }\n\n    /**\n     * Returns the line-type (ident) of the line\n     * @return string\n     */\n    public function getIdent()\n    {\n        return $this->ident;\n    }\n\n    /**\n     * Returns the content of the line\n     * @return string\n     */\n    public function getData()\n    {\n        return $this->data;\n    }\n\n    /**\n     * Returns the content of the line\n     * @return string\n     */\n    public function getDataAsArray()\n    {\n        if (strpos($this->data,\",\") !== false) {\n            return explode(\",\",$this->data);\n        } else\n\n            return array($this->data);\n    }\n\n    /**\n     * A static helper to get a array of intouch\\ical\\Line\'s, and calls\n     * getData() on each of them to lay the data \"bare\"..\n     *\n     * @param intouch\\ical\\Line[]\n     * @return array\n     */\n    public static function Remove_Line($arr)\n    {\n        $rtn = array();\n        foreach ($arr AS $k => $v) {\n            if (is_array($v)) {\n                $rtn[$k] = self::Remove_Line($v);\n            } elseif ($v instanceof Line) {\n                $rtn[$k] = $v->getData();\n            } else {\n                $rtn[$k] = $v;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * @see ArrayAccess.offsetExists\n     */\n    public function offsetExists( $param )\n    {\n        return isset($this->params[ strtolower($param) ]);\n    }\n\n    /**\n     * @see ArrayAccess.offsetGet\n     */\n    public function offsetGet( $param )\n    {\n        $index = strtolower($param);\n        if (isset($this->params[ $index ])) {\n            return $this->params[ $index ];\n        }\n    }\n\n    /**\n     * Disabled ArrayAccess requirement\n     * @see ArrayAccess.offsetSet\n     */\n    public function offsetSet( $param, $val )\n    {\n        return false;\n    }\n\n    /**\n     * Disabled ArrayAccess requirement\n     * @see ArrayAccess.offsetUnset\n     */\n    public function offsetUnset( $param )\n    {\n        return false;\n    }\n\n    /**\n     * toString method.\n     * @see getData()\n     */\n    public function __toString()\n    {\n        return $this->getData();\n    }\n\n    /**\n     * @see Countable.count\n     */\n    public function count()\n    {\n        return count($this->params);\n    }\n\n    /**\n     * @see IteratorAggregate.getIterator\n     */\n    public function getIterator()\n    {\n        return new ArrayIterator($this->params);\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * Class Parser\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Parser\n{\n    /**\n     * Fetches $url and passes it on to be parsed\n     * @param string $url\n     * @param ICal $ical\n     */\n    public static function Parse( $url, ICal $ical )\n    {\n        $content = self::Fetch( $url );\n        $content = self::UnfoldLines($content);\n        self::_Parse( $content, $ical );\n    }\n\n    /**\n     * Passes a text string on to be parsed\n     * @param string            $content\n     * @param ICal $ical\n     */\n    public static function ParseString($content, ICal $ical )\n    {\n        $content = self::UnfoldLines($content);\n        self::_Parse( $content, $ical );\n    }\n\n    /**\n     * Fetches a resource and tries to make sure it\'s UTF8\n     * encoded\n     * @return string\n     */\n    protected static function Fetch( $resource )\n    {\n        $is_utf8 = true;\n\n        if ( is_file( $resource ) ) {\n            // The resource is a local file\n            $content = file_get_contents($resource);\n\n            if ( ! self::_ValidUtf8( $content ) ) {\n                // The file doesn\'t appear to be UTF8\n                $is_utf8 = false;\n            }\n        } else {\n            // The resource isn\'t local, so it\'s assumed to\n            // be a URL\n            $c = curl_init();\n            curl_setopt($c, CURLOPT_URL, $resource);\n            curl_setopt($c, CURLOPT_RETURNTRANSFER, true);\n            if ( !ini_get(\'safe_mode\') && !ini_get(\'open_basedir\') ) {\n                curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);\n            }\n            $content = curl_exec($c);\n\n            $ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);\n            $enc = preg_replace(\'/^.*charset=([-a-zA-Z0-9]+).*$/\', \'$1\', $ct);\n            if ( $ct != \'\' && strtolower(str_replace(\'-\',\'\', $enc)) != \'utf8\' ) {\n                // Well, the encoding says it ain\'t utf-8\n                $is_utf8 = false;\n            } elseif ( ! self::_ValidUtf8( $content ) ) {\n                // The data isn\'t utf-8\n                $is_utf8 = false;\n            }\n        }\n\n        if (!$is_utf8) {\n            $content = utf8_encode($content);\n        }\n\n        return $content;\n    }\n\n    /**\n     * Takes the string $content, and creates a array of ICal lines.\n     * This includes unfolding multi-line entries into a single line.\n     * @param $content string\n     */\n    protected static function UnfoldLines($content)\n    {\n        $data = array();\n        $content = explode(\"\\n\", $content);\n        for ( $i=0; $i < count($content); $i++) {\n            $line = rtrim($content[$i]);\n            while ( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == \' \' || $content[$i+1]{0} == \"\\t\" )) {\n                $line .= rtrim(substr($content[++$i],1));\n            }\n            $data[] = $line;\n        }\n\n        return $data;\n    }\n\n    /**\n     * Parses the feed found in content and calls storeSection to store\n     * parsed data\n     * @param string $content\n     * @param ICal $ical\n     */\n    private static function _Parse( $content, ICal $ical )\n    {\n        $main_sections = array(\'vevent\', \'vjournal\', \'vtodo\', \'vtimezone\', \'vcalendar\');\n        $array_idents = array(\'exdate\',\'rdate\',\'attendee\');\n        $sections = array();\n        $section = \'\';\n        $current_data = array();\n\n        foreach ($content AS $line) {\n            $line = new Line($line);\n            if ( $line->isBegin() ) {\n                // New block of data, $section = new block\n                $section = strtolower($line->getData());\n                $sections[] = strtolower($line->getData());\n            } elseif ( $line->isEnd() ) {\n                // End of block of data ($removed = just ended block, $section = new top-block)\n                $removed = array_pop($sections);\n                $section = end($sections);\n\n                if ( array_search($removed, $main_sections) !== false ) {\n                    self::StoreSection( $removed, $current_data[$removed], $ical);\n                    $current_data[$removed] = array();\n                }\n            } else {\n                // Data line\n                foreach ($main_sections AS $s) {\n                    // Loops though the main sections\n                    if ( array_search($s, $sections) !== false ) {\n                        // This section is in the main section\n                        if ($section == $s) {\n                            // It _is_ the main section else\n                            if (in_array($line->getIdent(), $array_idents)) {\n                                //exdate could appears more that once\n                                $current_data[$s][$line->getIdent()][] = $line;\n                            }\n                            else {\n                                $current_data[$s][$line->getIdent()] = $line;\n                            }\n                        } else {\n                            // Sub section\n                            $current_data[$s][$section][$line->getIdent()] = $line;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n        $current_data = array();\n    }\n\n    /**\n     * Stores the data in provided intouch\\ical\\iCalobject\n     * @param string $section eg \'vcalender\', \'vevent\' etc\n     * @param string $data\n     * @param ICal $ical\n     */\n    protected static function storeSection( $section, $data, ICal $ical )\n    {\n        $data = Factory::portal($ical, $section, $data);\n        switch ($section) {\n            case \'vcalendar\':\n                return $ical->setCalendarInfo( $data );\n            case \'vevent\':\n                return $ical->addEvent( $data );\n            case \'vjournal\':\n            case \'vtodo\':\n                return true; // TODO: Implement\n            case \'vtimezone\':\n                return $ical->addTimeZone( $data );\n        }\n    }\n\n    /**\n     * This functions does some regexp checking to see if the value is\n     * valid UTF-8.\n     *\n     * The function is from the book \"Building Scalable Web Sites\" by\n     * Cal Henderson.\n     *\n     * @param  string $data\n     * @return bool\n     */\n    private static function _ValidUtf8( $data )\n    {\n        $rx  = \'[\\xC0-\\xDF]([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xE0-\\xEF].{0,1}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xF0-\\xF7].{0,2}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xF8-\\xFB].{0,3}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xFC-\\xFD].{0,4}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\xFE-\\xFE].{0,5}([^\\x80-\\xBF]|$)\';\n        $rx .= \'|[\\x00-\\x7F][\\x80-\\xBF]\';\n        $rx .= \'|[\\xC0-\\xDF].[\\x80-\\xBF]\';\n        $rx .= \'|[\\xE0-\\xEF]..[\\x80-\\xBF]\';\n        $rx .= \'|[\\xF0-\\xF7]...[\\x80-\\xBF]\';\n        $rx .= \'|[\\xF8-\\xFB]....[\\x80-\\xBF]\';\n        $rx .= \'|[\\xFC-\\xFD].....[\\x80-\\xBF]\';\n        $rx .= \'|[\\xFE-\\xFE]......[\\x80-\\xBF]\';\n        $rx .= \'|^[\\x80-\\xBF]\';\n\n        return ( ! (bool) preg_match(\'!\'.$rx.\'!\', $data) );\n    }\n}\n\n// namespace TKr\\ICal;\n// use Exception;\n\n/**\n *\n * A collection of functions to query the events in a calendar.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Query\n{\n    /**\n     * Returns all events from the calendar between two timestamps\n     *\n     * Note that the events returned needs only slightly overlap.\n     *\n     * @param  intouch\\ical\\iCal |array $ical  The calendar to query\n     * @param  int                      $start\n     * @param  int                      $end\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function Between(ICal $ical, $start, $end )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::Between called with invalid input!\');\n        }\n\n        $rtn = array();\n        foreach ($ical AS $e) {\n            if( ($start <= $e->getStart() && $e->getStart() < $end)\n             || ($start < $e->getRangeEnd() && $e->getRangeEnd() <= $end) ) {\n                $rtn[] = $e;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * Returns all events from the calendar after a given timestamp\n     *\n     * @param  intouch\\ical\\iCal|array $ical  The calendar to query\n     * @param  int                     $start\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function After( $ical, $start )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::After called with invalid input!\');\n        }\n\n        $rtn = array();\n        foreach ($ical AS $e) {\n            if ($e->getStart() >= $start || $e->getRangeEnd() >= $start) {\n                $rtn[] = $e;\n            }\n        }\n\n        return $rtn;\n    }\n\n    /**\n     * Sorts the events from the calendar after the specified column.\n     * Column can be all valid entires that getProperty can return.\n     * So stuff like uid, start, end, summary etc.\n     * @param  intouch\\ical\\iCal|array $ical   The calendar to query\n     * @param  string                  $column\n     * @return intouch\\ical\\VEvent[]\n     */\n    public static function Sort( $ical, $column )\n    {\n        if ($ical instanceof ICal) {\n            $ical = $ical->getEvents();\n        }\n        if ( !is_array($ical) ) {\n            throw new Exception(\'intouch\\ical\\Query::Sort called with invalid input!\');\n        }\n\n        $cmp = create_function(\'$a, $b\', \'return strcmp($a->getProperty(\"\' . $column . \'\"), $b->getProperty(\"\' . $column . \'\"));\');\n        usort($ical, $cmp);\n\n        return $ical;\n    }\n}\n\n// namespace TKr\\ICal;\n\n// use \\DateTime;\n\n/**\n * A wrapper for recurrence rules in iCalendar.  Parses the given line and puts the\n * recurrence rules in the correct field of this object.\n *\n * See http://tools.ietf.org/html/rfc2445 for more information.  Page 39 and onward contains more\n * information on the recurrence rules themselves.  Page 116 and onward contains\n * some great examples which were often used for testing.\n *\n * @author Steven Oxley\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass Recurrence\n{\n    public $rrule;\n\n    protected $freq;\n\n    public $until;\n    protected $count;\n\n    protected $interval;\n    protected $bysecond;\n    protected $byminute;\n    protected $byhour;\n    protected $byday;\n    protected $bymonthday;\n    protected $byyearday;\n    protected $byweekno;\n    protected $bymonth;\n    protected $bysetpos;\n\n    protected $wkst;\n\n    /**\n     * A list of the properties that can have comma-separated lists for values.\n     * @var array\n     */\n    protected $listProperties = array(\n        \'bysecond\', \'byminute\', \'byhour\', \'byday\', \'bymonthday\',\n        \'byyearday\', \'byweekno\', \'bymonth\', \'bysetpos\'\n    );\n\n    /**\n     * Creates an recurrence object with a passed in line.  Parses the line.\n     * @param object $line an intouch\\ical\\Line object which will be parsed to get the\n     * desired information.\n     */\n    public function __construct(Line $line)\n    {\n        $this->parseLine($line->getData());\n    }\n\n    /**\n     * Parses an \'RRULE\' line and sets the member variables of this object.\n     * Expects a string that looks like this:  \'FREQ=WEEKLY;INTERVAL=2;BYDAY=SU,TU,WE\'\n     * @param string $line the line to be parsed\n     */\n    protected function parseLine($line)\n    {\n        $this->rrule = $line;\n\n        //split up the properties\n        $recurProperties = explode(\';\', $line);\n        $recur = array();\n\n        //loop through the properties in the line and set their associated\n        //member variables\n        foreach ($recurProperties as $property) {\n            $nameAndValue = explode(\'=\', $property);\n\n            //need the lower-case name for setting the member variable\n            $propertyName = strtolower($nameAndValue[0]);\n            $propertyValue = $nameAndValue[1];\n\n            //split up the list of values into an array (if it\'s a list)\n            if (in_array($propertyName, $this->listProperties)) {\n                $propertyValue = explode(\',\', $propertyValue);\n            }\n            $this->$propertyName = $propertyValue;\n        }\n    }\n\n    /**\n     * Set the $until member\n     * @param mixed timestamp (int) / Valid DateTime format (string)\n     */\n    public function setUntil($ts)\n    {\n        if ( is_int($ts) )\n            $dt = new DateTime(\'@\'.$ts);\n        else\n            $dt = new DateTime($ts);\n        $this->until = $dt->format(\'Ymd\\THisO\');\n    }\n\n    /**\n     * Retrieves the desired member variable and returns it (if it\'s set)\n     * @param  string $member name of the member variable\n     * @return mixed  the variable value (if set), false otherwise\n     */\n    protected function getMember($member)\n    {\n        if (isset($this->$member)) {\n            return $this->$member;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns the frequency - corresponds to FREQ in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getFreq()\n    {\n        return $this->getMember(\'freq\');\n    }\n\n    /**\n     * Returns when the event will go until - corresponds to UNTIL in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getUntil()\n    {\n        return $this->getMember(\'until\');\n    }\n\n    /**\n     * Returns the count of the times the event will occur (should only appear if \'until\'\n     * does not appear) - corresponds to COUNT in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getCount()\n    {\n        return $this->getMember(\'count\');\n    }\n\n    /**\n     * Returns the interval - corresponds to INTERVAL in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getInterval()\n    {\n        return $this->getMember(\'interval\');\n    }\n\n    /**\n     * Returns the bysecond part of the event - corresponds to BYSECOND in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getBySecond()\n    {\n        return $this->getMember(\'bysecond\');\n    }\n\n    /**\n     * Returns the byminute information for the event - corresponds to BYMINUTE in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMinute()\n    {\n        return $this->getMember(\'byminute\');\n    }\n\n    /**\n     * Corresponds to BYHOUR in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByHour()\n    {\n        return $this->getMember(\'byhour\');\n    }\n\n    /**\n     *Corresponds to BYDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByDay()\n    {\n        return $this->getMember(\'byday\');\n    }\n\n    /**\n     * Corresponds to BYMONTHDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMonthDay()\n    {\n        return $this->getMember(\'bymonthday\');\n    }\n\n    /**\n     * Corresponds to BYYEARDAY in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByYearDay()\n    {\n        return $this->getMember(\'byyearday\');\n    }\n\n    /**\n     * Corresponds to BYWEEKNO in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByWeekNo()\n    {\n        return $this->getMember(\'byweekno\');\n    }\n\n    /**\n     * Corresponds to BYMONTH in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getByMonth()\n    {\n        return $this->getMember(\'bymonth\');\n    }\n\n    /**\n     * Corresponds to BYSETPOS in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getBySetPos()\n    {\n        return $this->getMember(\'bysetpos\');\n    }\n\n    /**\n     * Corresponds to WKST in RFC 2445.\n     * @return mixed string if the member has been set, false otherwise\n     */\n    public function getWkst()\n    {\n        return $this->getMember(\'wkst\');\n    }\n}\n\n// namespace TKr\\ICal;\n\n//use ArrayIterator;\n//// use \\IteratorAggregate;\n\n/**\n * The wrapper for the main vcalendar data. Used instead of ArrayObject\n * so you can easily query for title and description.\n * Exposes a iterator that will loop though all the data\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VCalendar implements IteratorAggregate\n{\n    protected $data;\n\n    /**\n     * Creates a new intouch\\ical\\VCalendar.\n     */\n    public function __construct($data)\n    {\n        $this->data = $data;\n    }\n\n    /**\n     * Returns the title of the calendar. If no title is known, NULL\n     * will be returned\n     * @return string\n     */\n    public function getTitle()\n    {\n        if ( isset($this->data[\'x-wr-calname\']) ) {\n            return $this->data[\'x-wr-calname\'];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Returns the description of the calendar. If no description is\n     * known, NULL will be returned.\n     * @return string\n     */\n    public function getDescription()\n    {\n        if ( isset($this->data[\'x-wr-caldesc\']) ) {\n            return $this->data[\'x-wr-caldesc\'];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * @see IteratorAggregate.getIterator()\n     */\n    public function getIterator()\n    {\n        return new ArrayIterator($this->data);\n    }\n}\n\n// namespace TKr\\ICal;\n\n/**\n * The wrapper for vevents. Will reveal a unified and simple api for\n * the events, which include always finding a start and end (except\n * when no end or duration is given) and checking if the event is\n * blocking or similar.\n *\n * Will apply the specified timezone to timestamps if a tzid is\n * specified\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VEvent\n{\n    const DEFAULT_CONFIRMED = true;\n\n    protected $uid;\n\n    protected $start;\n    protected $end;\n\n    protected $summary;\n    protected $description;\n    protected $location;\n\n    protected $laststart;\n    protected $lastend;\n\n    public $recurrence; //RRULE\n    public $recurex;    //EXRULE\n    public $excluded;   //EXDATE(s)\n    public $added;      //RDATE(s)\n\n    public $freq; //getFrequency() intouch\\ical\\Freq\n\n    public $data;\n\n    /**\n     * Constructs a new intouch\\ical\\VEvent. Needs the ICal\n     * supplied so it can query for timezones.\n     * @param Line[] $data\n     * @param ICal   $ical\n     */\n    public function __construct($data, ICal $ical, $yearsLimit = 3)\n    {\n        if ( isset($data[\'uid\']) ) {\n            $this->uid = $data[\'uid\']->getData();\n            unset($data[\'uid\']);\n        }\n\n        if ( isset($data[\'rrule\']) ) {\n            $this->recurrence = new Recurrence($data[\'rrule\']);\n            unset($data[\'rrule\']);\n        }\n\n        if ( isset($data[\'exrule\']) ) {\n            $this->recurex = new Recurrence($data[\'exrule\']);\n            unset($data[\'exrule\']);\n        }\n\n        if ( isset($data[\'dtstart\']) ) {\n            $this->start = $this->getTimestamp($data[\'dtstart\'], $ical);\n            unset($data[\'dtstart\']);\n        }\n\n        if ( isset($data[\'dtend\']) ) {\n            $this->end = $this->getTimestamp($data[\'dtend\'], $ical);\n            unset($data[\'dtend\']);\n        } elseif ( isset($data[\'duration\']) ) {\n            $dur = new Duration( $data[\'duration\']->getData() );\n            $this->end = $this->start + $dur->getDuration();\n            unset($data[\'duration\']);\n        }\n\n        //google cal set dtend as end of initial event (duration)\n        if ( isset($this->recurrence) ) {\n            //if there is a recurrence rule\n\n            //exclusions\n            if ( isset($data[\'exdate\']) ) {\n                foreach ($data[\'exdate\'] as $exdate) {\n                    foreach ($exdate->getDataAsArray() as $ts) {\n                        $this->excluded[] = strtotime($ts);\n                    }\n                }\n                unset($data[\'exdate\']);\n            }\n            //additions\n            if ( isset($data[\'rdate\']) ) {\n                foreach ($data[\'rdate\'] as $rdate) {\n                    foreach ($rdate->getDataAsArray() as $ts) {\n                        $this->added[] = strtotime($ts);\n                    }\n                }\n                unset($data[\'rdate\']);\n            }\n\n            $until = $this->recurrence->getUntil();\n            $count = $this->recurrence->getCount();\n            //check if there is either \'until\' or \'count\' set\n            if ($until) {\n                //ok..\n            } elseif ($count) {\n                //if count is set, then figure out the last occurrence and set that as the end date\n                $this->getFrequency();\n                $until = $this->freq->lastOccurrence($this->start);\n            } else {\n                //forever... limit to 3 years\n                $this->recurrence->setUntil(\'+\' . $yearsLimit .\' years\');\n                $until = $this->recurrence->getUntil();\n            }\n            //date_default_timezone_set( xx ) needed ?;\n            $this->laststart = strtotime($until);\n            $this->lastend = $this->laststart + $this->getDuration();\n        }\n\n        $imports = array(\'summary\',\'description\',\'location\');\n        foreach ($imports AS $import) {\n            if ( isset($data[$import]) ) {\n                $this->$import = $data[$import]->getData();\n                unset($data[$import]);\n            }\n        }\n\n        if ( isset($this->previous_tz) ) {\n            date_default_timezone_set($this->previous_tz);\n        }\n        $this->data = Line::Remove_Line($data);\n    }\n\n    /**\n     * Returns the Event Occurrences Iterator (if recurrence set)\n     * @return intouch\\ical\\Freq\n     */\n    public function getFrequency($until = null)\n    {\n        if (! isset($this->freq)) {\n            if ( isset($this->recurrence) ) {\n                $this->freq = new Freq($this->recurrence, $this->start, $this->excluded, $this->added, $until);\n            }\n        }\n\n        return $this->freq;\n    }\n\n    /**\n     * Returns the UID of the event\n     * @return string\n     */\n    public function getUID()\n    {\n        return $this->uid;\n    }\n\n    /**\n     * Returns the summary (or null if none is given) of the event\n     * @return string\n     */\n    public function getSummary()\n    {\n        return $this->summary;\n    }\n\n    /**\n     * Returns the description (or null if none is given) of the event\n     * @return string\n     */\n    public function getDescription()\n    {\n        return $this->description;\n    }\n\n    /**\n     * Returns the location (or null if none is given) of the event\n     * @return string\n     */\n    public function getLocation()\n    {\n        return $this->location;\n    }\n\n    /**\n     * Returns true if the event is blocking (ie not transparent)\n     * @return bool\n     */\n    public function isBlocking()\n    {\n        return !(isset($this->data[\'transp\']) && $this->data[\'transp\'] == \'TRANSPARENT\');\n    }\n\n    /**\n     * Returns true if the event is confirmed\n     * @return bool\n     */\n    public function isConfirmed()\n    {\n        if ( !isset($this->data[\'status\']) ) {\n            return self::DEFAULT_CONFIRMED;\n        } else {\n            return $this->data[\'status\'] == \'CONFIRMED\';\n        }\n    }\n\n    /**\n     * Returns true if duration is multiple of 86400\n     * @return bool\n     */\n    public function isWholeDay()\n    {\n        if (isset($this->data[\'x-microsoft-cdo-alldayevent\']) &&\n            strtolower($this->data[\'x-microsoft-cdo-alldayevent\']) === \'true\') {\n            $result = true;\n        } else {\n            $dur = $this->getDuration();\n            if ($dur > 0 && ($dur % 86400) == 0) {\n                $result = true;\n            } else {\n                $result = false;\n            }\n        }\n        return $result;\n    }\n\n    /**\n     * Returns the timestamp for the beginning of the event\n     * @return int\n     */\n    public function getStart()\n    {\n        return $this->start;\n    }\n\n    /**\n     * Returns the timestamp for the end of the event\n     * @return int\n     */\n    public function getEnd()\n    {\n        return $this->end;\n    }\n\n    /**\n     * Returns the timestamp for the end of the last event\n     * @return int\n     */\n    public function getRangeEnd()\n    {\n        return max($this->end,$this->lastend);\n    }\n\n    /**\n     * Returns the duration of this event in seconds\n     * @return int\n     */\n    public function getDuration()\n    {\n        return $this->end - $this->start;\n    }\n\n    /**\n     * Returns the given property of the event.\n     * @param  string $prop\n     * @return string\n     */\n    public function getProperty( $prop )\n    {\n        if ( isset($this->$prop) ) {\n            return $this->$prop;\n        } elseif ( isset($this->data[$prop]) ) {\n            return $this->data[$prop];\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Set default timezone (temporary) to get timestamps\n     * @return string\n     */\n    protected function setLineTimeZone( Line $line )\n    {\n        if ( isset($line[\'tzid\']) ) {\n            if (!isset($this->previous_tz)) {\n                $this->previous_tz = @ date_default_timezone_get();\n            }\n            $this->tzid = $line[\'tzid\'];\n            date_default_timezone_set($this->tzid);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Calculates the timestamp from a DT line.\n     * @param $line Line\n     * @return int\n     */\n    protected function getTimestamp( Line $line, ICal $ical )\n    {\n        if ( isset($line[\'tzid\']) ) {\n            $this->setLineTimeZone($line);\n            //$tz = $ical->getTimeZoneInfo($line[\'tzid\']);\n            //$offset = $tz->getOffset($ts);\n            //$ts = strtotime(date(\'D, d M Y H:i:s\', $ts) . \' \' . $offset);\n        }\n        $ts = strtotime($line->getData());\n\n        return $ts;\n    }\n}\n\n// namespace TKr\\ICal;\n// use DateTime;\n// use DateTimeZone;\n\n/**\n * The wrapper for vtimezones. Stores the timezone-id and the setup for\n * daylight savings and standard time.\n *\n * @author Morten Fangel (C) 2008\n * @author Michael Kahn (C) 2013\n * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK\n */\nclass VTimeZone\n{\n    protected $tzid;\n    protected $daylight;\n    protected $standard;\n    protected $cache = array();\n\n    /**\n     * Constructs a new intouch\\ical\\VTimeZone\n     */\n    public function __construct( $data )\n    {\n        $this->tzid = $data[\'tzid\'];\n        $this->daylight = $data[\'daylight\'];\n        $this->standard = $data[\'standard\'];\n    }\n\n    /**\n     * Returns the timezone-id for this timezone. (Used to\n     * differentiate between different tzs in a calendar)\n     * @return string\n     */\n    public function getTimeZoneId()\n    {\n        return $this->tzid;\n    }\n\n    /**\n     * Returns the given offset in this timezone for the given\n     * timestamp. (eg +0200)\n     * @param  int    $ts\n     * @return string\n     */\n    public function getOffset( $ts )\n    {\n        $act = $this->getActive($ts);\n\n        return $this->{$act}[\'tzoffsetto\'];\n    }\n\n    /**\n     * Returns the timezone name for the given timestamp (eg CEST)\n     * @param  int    $ts\n     * @return string\n     */\n    public function getTimeZoneName($ts)\n    {\n        $act = $this->getActive($ts);\n\n        return $this->{$act}[\'tzname\'];\n    }\n\n    /**\n     * Determines which of the daylight or standard is the active\n     * setting.\n     * The call is cached for a given timestamp, so a call to\n     * getOffset and getTimeZoneName with the same ts won\'t calculate\n     * the answer twice.\n     * @param  int    $ts\n     * @return string standard|daylight\n     */\n    private function getActive( $ts )\n    {\n        if (class_exists(\'DateTimeZone\')) {\n\n            //PHP >= 5.2\n            $tz = new DateTimeZone( $this->tzid );\n            $date = new DateTime(\"@$ts\", $tz);\n\n            return ($date->format(\'I\') == 1) ? \'daylight\' : \'standard\';\n\n        } else {\n\n            if ( isset($this->cache[$ts]) ) {\n                return $this->cache[$ts];\n            }\n\n            $daylight_freq = new Freq($this->daylight[\'rrule\'], strtotime($this->daylight[\'dtstart\']));\n            $standard_freq = new Freq($this->standard[\'rrule\'], strtotime($this->standard[\'dtstart\']));\n            $last_standard = $standard_freq->previousOccurrence($ts);\n            $last_dst = $daylight_freq->previousOccurrence($ts);\n            if ($last_dst > $last_standard) {\n                $this->cache[$ts] = \'daylight\';\n            } else {\n                $this->cache[$ts] = \'standard\';\n            }\n\n            return $this->cache[$ts];\n        }\n    }\n}\n', '5e384413777b6337ce6e36eff8bf0fed', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('pat_public_bar', 0, 'Patrick LEFEVRE', 'http://twitter.com/lowel', '0.4.1', 'Add a bar on your website to access side to side from public to admin pages', '<h1>pat_public_bar</h1>\n<h2>Plugin Help</h2>\n<p>See the <a href=\"<h1>Plugin help</h1>\" target=\"_blank\">plugin GitHub repro for more informations</a>.</p>\n<h2>Usage</h2>\n<p>In your page templates where to show this bar on your website (public side) only available for login-in users, just add:</p>\n<pre><code><code>&lt;txp:pat_public_bar /&gt;</code></code></pre>\n<p>Notice: due to the support of multi-site installations the bar only disappear when the client\'s browser is closed.</p>\n<h2>Attributes for designers</h2>\n<p>These attributes allow you to customize the entire appearance of the public bar:</p>\n<ul>\n<li><code>position</code> String (optional): CSS position of the bar. Set to <code>absolute</code> is better for small screens support. Default: <code>fixed</code>.</li>\n<li><code>js_top_adjust</code> Integer (optional): top CSS position of the bar for conveniences. Default: <code>0</code>.</li>\n<li><code>bgcolor</code> String (optional): change the background color of the bar. Default: <code>#23282d</code></li>\n<li><code>color</code> String (optional): change the font color into the bar. Default: <code>#fff</code>.</li>\n<li><code>title</code> String (optional): change the color of the different parts title. Default: <code>#84878b</code>.</li>\n<li><code>hover</code> String (optional): change the color of links on hover. Default: <code>#62bbdc</code>.</li>\n<li><code>icon</code> String (optinal): change the color of the SVG icons. Default: <code>#ccc</code>.</li>\n</ul>\n<h2>Changelog</h2>\n<ul><li>October 2017: v 0.4.1. Support for user\'s IP address for permanent display; some CSS cosmetics; plugin\'s help updated..</li><li>10th October 2017: v 0.4.0. Better support for small screens.</li><li>25th September 2017: v 0.3.9. Better positioning & js adaptation.</li><li>22th September 2017: v 0.3.8. Consistent \"logout\" into the list.</li></li><li>24th August 2017: v 0.3.7. ADD TXP forum link.</li><li>18th July 2017: v 0.3.6. Add a preference radio button to disable the plugin.</li><li>17th July 2017: v 0.3.5. Removes a strong tag into the \"Article\" link. CSS improvements.</li><li>15th October 2015: v 0.3.3. New UI, better support for multisite installation and 3 new attributes for customization.</li><li>2d August 2015: v 0.3.2. Admin privs can access to \"Section\", \"Page\" and \"Style\" tabs.</li><li>1st August 2015: v. 0.3.1. Add \"bgcolor\" and \"color\" attributes.</li><li>30th July 2015: v. 0.3.0. Support for multi-site installations.</li><li>29th April 2015: v 0.2. Add image & category page links.</li><li>25th July 2014: first release.</li></ul>', '/**\r\n * pat_public_bar Textpattern CMS plugin\r\n * @author:  Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-public-bar.cara-tm.com\r\n * @type:    Admin + Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.4.1\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * This plugin tag registry\r\n *\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_public_bar\');\r\n}\r\n\r\nif (@txpinterface == \'admin\') {\r\n\r\n	register_callback(\'_pat_public_bar_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'_pat_public_bar_cleanup\', \'plugin_lifecycle.pat_public_bar\', \'deleted\');\r\n\r\n}\r\n\r\n\r\nif (@txpinterface == \'public\') {\r\n\r\n	/**\r\n	 * Inject an HTML block on the public side for all login-in users.\r\n	 * Allow access side to side from public to admin pages.\r\n	 *\r\n	 * @param  $atts  $things\r\n	 * @return HTML content for login-in users\r\n	 */\r\n	function pat_public_bar($atts) {\r\n\r\n		global $prefs, $pretext, $thisarticle, $thiscomment;\r\n\r\n		extract(lAtts(array(\r\n			\'interface\' 	=> $prefs[\'pat_admin_url\'],\r\n			\'position\' 	=> \'fixed\',\r\n			\'js_top_adjust\' => \'-45\',\r\n			\'bgcolor\' 	=> \'#23282d\',\r\n			\'color\' 	=> \'#fff\',\r\n			\'title\' 	=> \'#84878b\',\r\n			\'hover\' 	=> \'#62bbdc\',\r\n			\'icon\' 		=> \'#ccc\',\r\n		), $atts));\r\n\r\n		if ( cs(\'txp_login_public\') && true != $prefs[\'pat_public_bar_disable\'] ) {\r\n\r\n\r\n			if ($position != \'fixed\' || $position != \'absolute\')\r\n				$position = \'fixed\';\r\n\r\n\r\n			/* List of user privs who have full bar access:\r\n		   	1 = administrators\r\n		   	2 = administrator assistants\r\n		   	6 = designers\r\n			*/\r\n			$pat_privs_array = array(1, 2, 6);\r\n\r\n			$name = substr(cs(\'txp_login_public\'), 10);\r\n			$rs = safe_row(\'name, RealName, privs\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			ob_end_clean();\r\n\r\n			if ( in_array($rs[\'privs\'], $pat_privs_array) ) {\r\n\r\n				$section = \'<li><hr>\'.ucfirst(gTxt(\'structure\')).\'</li> <li><a href=\"\'.$interface.\'/index.php?event=section&amp;step=section_edit&amp;name=\'.$pretext[\'s\'].\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 297 297\" xml:space=\"preserve\"><path d=\"M268.3 63.5h-20.5V42.7c0-5.5-4.5-10-10-10h-23.9V10c0-5.5-4.5-10-10-10h-175.3c-5.5 0-10 4.5-10 10v213.5c0 5.5 4.5 10 10 10h23.9v22.7c0 5.5 4.5 10 10 10h20.5v20.9c0 5.5 4.5 10 10 10h175.3c5.5 0 10-4.5 10-10V73.5C278.2 68 273.8 63.5 268.3 63.5zM38.7 19.9h155.4v193.6H38.7V19.9zM72.5 233.5h131.5c5.5 0 10-4.5 10-10V52.6h13.9v193.6H72.5V233.5zM258.3 277.1H102.9v-10.9h134.9c5.5 0 10-4.5 10-10V83.5h10.5V277.1z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'section\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\' (<strong>\'.$pretext[\'s\'].\'</strong>)</a></li> \';\r\n				$page = \'<li><a href=\"\'.$interface.\'/index.php?event=page&amp;name=\'.$pretext[\'page\'].\'&amp;_txp_token=\'.form_token().\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><path d=\"M0 2v28h32L32 2H0zM10 4h12v2H10V4zM6 4h2v2H6V4zM2 4h2v2H2V4zM30 28H2V8h28V28zM30 6h-4V4h4V6z\"/><rect x=\"18\" y=\"20\" width=\"6\" height=\"2\"/><rect x=\"18\" y=\"16\" width=\"10\" height=\"2\"/><rect x=\"18\" y=\"12\" width=\"10\" height=\"2\"/><rect x=\"4\" y=\"12\" width=\"10\" height=\"10\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'page\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\' (<strong>\'.$pretext[\'page\'].\'</strong>)</a></li> \';\r\n				$form = \'<li><a href=\"\'.$interface.\'/index.php?event=form\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 100 100\" xml:space=\"preserve\"><path d=\"M41.8 59.1v6.1L20 55.8v-5.2l21.8-9.4v6.2l-15.2 5.8L41.8 59.1zM55.2 41l-6.1 24.9c-0.2 0.8-0.4 1.5-0.5 1.9 -0.2 0.5-0.4 0.8-0.7 1.1 -0.3 0.3-0.8 0.4-1.4 0.4 -1.5 0-2.2-0.6-2.2-1.9 0-0.3 0.1-1.2 0.4-2.5l6-24.9c0.3-1.3 0.6-2.2 0.9-2.7 0.3-0.5 0.9-0.7 1.7-0.7 0.7 0 1.3 0.2 1.7 0.5 0.4 0.3 0.6 0.8 0.6 1.4C55.7 39 55.5 39.8 55.2 41zM80 55.8L58.2 65.3v-6.1l15.2-6 -15.2-5.9v-6.1L80 50.6V55.8z\" fill=\"\'.$icon.\'\"/><path d=\"M94 6H6c-3.3 0-6 2.7-6 6v76c0 3.3 2.7 6 6 6h88c3.3 0 6-2.7 6-6V12C100 8.7 97.3 6 94 6zM79 10c2.2 0 4 1.8 4 4s-1.8 4-4 4 -4-1.8-4-4S76.8 10 79 10zM68 10c2.2 0 4 1.8 4 4s-1.8 4-4 4 -4-1.8-4-4S65.8 10 68 10zM94 88H6V22h88V88zM90 18c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4S92.2 18 90 18z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_forms\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'edit\')).\'</a></li>\';\r\n				$style = \'<li><a href=\"\'.$interface.\'/index.php?event=css\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 13.33 13.33\" xml:space=\"preserve\" enable-background=\"new 0 0 13.33 13.33\"><path d=\"M12.48 0H0.85c-0.47 0-0.85 0.38-0.85 0.85v11.63c0 0.47 0.38 0.85 0.85 0.85h11.63c0.47 0 0.85-0.38 0.85-0.85V0.85C13.33 0.38 12.95 0 12.48 0zM3.96 7.73c0.28 0 0.5-0.05 0.68-0.12l0.14 0.87c-0.21 0.09-0.61 0.17-1.06 0.17 -1.23 0-2.02-0.75-2.02-1.95 0-1.11 0.76-2.03 2.18-2.03 0.31 0 0.66 0.06 0.91 0.15L4.6 5.71C4.46 5.64 4.25 5.59 3.93 5.59c-0.62 0-1.03 0.44-1.02 1.07C2.91 7.36 3.38 7.73 3.96 7.73zM6.45 8.65c-0.5 0-0.94-0.11-1.23-0.27l0.21-0.85C5.64 7.67 6.1 7.83 6.45 7.83c0.36 0 0.51-0.12 0.51-0.32S6.84 7.22 6.39 7.07C5.6 6.8 5.3 6.38 5.31 5.92c0-0.71 0.61-1.25 1.55-1.25 0.44 0 0.84 0.1 1.08 0.22l-0.21 0.82C7.56 5.62 7.23 5.49 6.9 5.49c-0.29 0-0.45 0.12-0.45 0.31 0 0.18 0.15 0.27 0.62 0.44C7.79 6.49 8.09 6.86 8.1 7.42 8.1 8.13 7.54 8.65 6.45 8.65zM9.83 8.65c-0.5 0-0.94-0.11-1.23-0.27l0.21-0.85C9.03 7.67 9.48 7.83 9.83 7.83c0.36 0 0.51-0.12 0.51-0.32s-0.12-0.29-0.56-0.44C8.99 6.8 8.69 6.38 8.7 5.92c0-0.71 0.61-1.25 1.55-1.25 0.44 0 0.84 0.1 1.08 0.22l-0.21 0.82c-0.17-0.09-0.5-0.22-0.83-0.22 -0.29 0-0.45 0.12-0.45 0.31 0 0.18 0.15 0.27 0.62 0.44 0.73 0.25 1.03 0.62 1.04 1.18C11.49 8.13 10.93 8.65 9.83 8.65z\"/></svg>&nbsp;&bull;&nbsp;\'.strtoupper(gTxt(\'css\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\'</a></li> \';\r\n				$pref = \'<hr><li>&bull;</li> <li><a href=\"\'.$interface.\'/index.php?event=prefs\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 268.8 268.8\" xml:space=\"preserve\"><path d=\"M267.9 119.5c-0.4-3.8-4.8-6.6-8.6-6.6 -12.3 0-23.2-7.2-27.8-18.4 -4.7-11.5-1.7-24.8 7.5-33.2 2.9-2.6 3.2-7.1 0.8-10.1 -6.3-8-13.5-15.2-21.3-21.5 -3.1-2.5-7.6-2.1-10.2 0.8 -8 8.9-22.4 12.2-33.5 7.5 -11.6-4.9-18.9-16.6-18.2-29.2 0.2-4-2.7-7.4-6.6-7.8 -10-1.2-20.2-1.2-30.2-0.1 -3.9 0.4-6.8 3.8-6.7 7.7 0.4 12.5-6.9 24-18.4 28.7 -11 4.5-25.3 1.2-33.3-7.6 -2.6-2.9-7.1-3.3-10.1-0.9 -8.1 6.3-15.4 13.6-21.7 21.5 -2.5 3.1-2.1 7.6 0.8 10.2 9.4 8.5 12.4 21.9 7.5 33.5 -4.6 11-16.1 18.2-29.2 18.2 -4.3-0.1-7.3 2.7-7.8 6.6 -1.2 10.1-1.2 20.4-0.1 30.6 0.4 3.8 5 6.6 8.8 6.6 11.7-0.3 22.9 6.9 27.7 18.4 4.7 11.5 1.7 24.8-7.5 33.2 -2.9 2.6-3.2 7.1-0.8 10.1 6.2 8 13.4 15.2 21.3 21.5 3.1 2.5 7.6 2.1 10.2-0.8 8-8.9 22.4-12.2 33.5-7.5 11.6 4.9 18.9 16.6 18.2 29.2 -0.2 4 2.7 7.4 6.6 7.9 5.1 0.6 10.3 0.9 15.5 0.9 4.9 0 9.8-0.3 14.8-0.8 3.9-0.4 6.8-3.8 6.7-7.7 -0.5-12.5 6.9-24 18.4-28.7 11.1-4.5 25.3-1.2 33.3 7.6 2.7 2.9 7 3.2 10.1 0.8 8-6.3 15.3-13.5 21.7-21.5 2.5-3.1 2.1-7.6-0.8-10.2 -9.4-8.5-12.4-21.9-7.5-33.5 4.6-10.9 15.6-18.2 27.5-18.2l1.7 0c3.9 0.3 7.4-2.7 7.9-6.6C269 139.9 269.1 129.6 267.9 119.5zM134.6 179.5c-24.7 0-44.8-20.1-44.8-44.8 0-24.7 20.1-44.8 44.8-44.8 24.7 0 44.8 20.1 44.8 44.8C179.4 159.4 159.3 179.5 134.6 179.5z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_preferences\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'edit\')).\'</a></li>\';\r\n				$log = \'<li><a href=\"\'.$interface.\'/index.php??event=log\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 525.2 525.2\" xml:space=\"preserve\"><path d=\"M196.9 525.2H328.2V0H196.9V525.2zM0 525.2h131.3V262.6H0V525.2zM393.9 164.1v361h131.3V164.1H393.9z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'logs\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'view\')).\'</a>\'.$pat_admin_interface.\'</li>\';\r\n\r\n			}\r\n\r\n			$logged = \'<li><a href=\"\'.$interface.\'/index.php?event=admin\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 409.2 409.2\" xml:space=\"preserve\"><path d=\"M204.6 216.7c50.7 0 91.7-48.1 91.7-107.4 0-82.2-41.1-107.4-91.7-107.4 -50.7 0-91.7 25.1-91.7 107.4C112.8 168.6 153.9 216.7 204.6 216.7zM407.2 374.7L360.9 270.5c-2.1-4.8-5.8-8.7-10.5-11.1l-71.8-37.4c-1.6-0.8-3.5-0.7-4.9 0.4 -20.3 15.4-44.2 23.5-69.1 23.5 -24.9 0-48.8-8.1-69.1-23.5 -1.4-1.1-3.3-1.2-4.9-0.4L58.8 259.3c-4.6 2.4-8.3 6.4-10.5 11.1L2 374.7c-3.2 7.2-2.5 15.4 1.8 22 4.3 6.6 11.5 10.5 19.4 10.5h362.9c7.9 0 15.1-3.9 19.4-10.5C409.7 390.1 410.4 381.9 407.2 374.7z\"/></svg>&nbsp;&bull;&nbsp;\'.$rs[\'RealName\'].\'</a></li> \';\r\n			$file = \'<hr><li>\'.ucfirst(gTxt(\'tab_content\')).\'</li> <li><a href=\"\'.$interface.\'/index.php?event=file\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><path d=\"M22.4 0H4v32h24V5.6L22.4 0zM22 2.4L25.6 6h-3.6C22 6 22 2.4 22 2.4zM26 30H6V2h14v6h6V30z\"/><rect x=\"8\" y=\"20\" width=\"12\" height=\"2\"/><rect x=\"8\" y=\"12\" width=\"16\" height=\"2\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"2\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_file\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'add\')).\'</a></li>\';\r\n			$link = \'<li><a href=\"\'.$interface.\'/index.php?event=link\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 612 612\" xml:space=\"preserve\"><path d=\"M499 350.1c-27.3-11.4-63.1-20.1-103.7-25.1 2.3-22.6 3.5-46.2 3.5-69.6 0-23.4-1.1-47-3.5-69.7 40.6-5.1 76.3-13.8 103.6-25.2 14.1 28.6 22.1 60.8 22.1 94.9C521 289.3 513.1 321.5 499 350.1zM298.3 40.4c2.6-0.1 5.2-0.2 7.7-0.2 2.6 0 5.2 0.1 7.7 0.2 15.3 10.4 35.2 49.8 45.7 116.4 -35.9 2.7-72.8 2.4-106.8-0.5C263.2 90 283 50.8 298.3 40.4zM245.5 255.3c0-24.3 1.1-46.7 3.1-67 19.5 1.8 39.8 2.6 60.6 2.6 18.1 0 36.3-0.7 54.2-2.1 2 20.2 3.1 42.4 3.1 66.5s-1.1 46.2-3.1 66.4c-17.4-1.5-35.6-2.1-54.2-2.1 -20.8 0-41.2 0.8-60.6 2.6C246.6 301.9 245.5 279.6 245.5 255.3zM482.3 132.5c-24 9.4-55.3 16.8-91 21.2 -6.4-42.2-17.1-79.8-31.7-106.6C410.1 60 453.3 90.9 482.3 132.5zM252.4 47.1c-14.6 26.6-25.1 63.9-31.7 105.8 -35.2-4.8-66.4-12.6-89.6-22.4C160.1 89.9 202.7 59.9 252.4 47.1zM468.4 450.5c57-46.9 92.9-115.8 92.9-195.2C561.3 114.5 446.8 0 306 0 180.6 0 76.1 90.8 54.7 210.2c-2.7 14.7-4 29.7-4 45.1 0 140.8 74.9 251.3 219.3 292.9V612l212.9-122.9L270 366.1v60.4c-116.5 0-178.6-94.5-178.6-184.1 0.6-11 2.1-21.7 4.3-32.2 3.9-18.3 10.2-35.7 18.5-51.9 26.6 11.8 62 21 102.5 26.5 -2.4 23-3.5 46.7-3.5 70.5 0 23.7 1.1 47.5 3.5 70.4 -24.7 3.3-47.4 8.1-67.4 13.9 1.2 1.5 2.3 2.9 3.4 4.4 3.5 4.5 6.8 8.9 10.3 12.2l0.8 0.7 0.7 0.9c2.6 2.9 5.7 5.7 9.2 8.6 14.4-3.5 30.2-6.4 47-8.7 2.3 14.3 4.9 28.1 8.1 41.1h0.1c5.2 1.6 9.8 3 13 3.1h1c0 0 2.3 0.2 3.1 0.3v-47.5 -30.5l48.1 27.7c5.1-0.1 10.2-0.2 15.2-0.2 17.2 0 34.2 0.6 50.3 1.9 -1.9 11.7-3.9 22.6-6.3 32.6l29 16.8c3.6-14.4 6.8-30 9.2-46.1 35.8 4.4 67 11.8 91 21.2 -14.3 20.4-31.9 38.2-52.2 52.6L468.4 450.5z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'links\')).\'&nbsp;:&nbsp;\'.ucfirst(gtxt(\'add\')).\'</a></li>\';\r\n			$category = ( ($thisarticle[\'category1\'] || $thisarticle[\'category2\']) ? \'<li><a href=\"\'.$interface.\'/index.php?event=category\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" x=\"0\" y=\"0\" viewBox=\"0 0 28 28\" xml:space=\"preserve\"><path d=\"M15 18v-4h-4v8l4 0V20h8v8h-8v-4H9V8H5V0h8v8h-2v4h4v-2h8v8H15z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'categories\')).\'&nbsp;:&nbsp;\'.gtxt(\'edit\').\'</a></li>\' : \'\');\r\n			$add = \'<a href=\"\'.$interface.\'/index.php?event=article&amp;Section=\'.$pretext[\'s\'].\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 386.1 386.1\" xml:space=\"preserve\" style=\"margin:0 5px 0 -5px\"><path d=\"M382.7 14l-1.8-1.6 -2.4 0.4c-1.2 0.2-29.6 5.3-70 22.6 -37.3 15.9-92.1 45.6-141.6 96.7 -37.5 38.7-64 72.5-78.6 100.3 -2.5 4.7-4.6 9.3-6.4 13.6L3.1 349.7c-5 6.6-3.7 16 2.9 21 2.7 2.1 5.9 3.1 9.1 3.1 4.5 0 9-2 12-5.9l53.4-70.3c3.4 5 7.3 7.5 9.1 8.4 13.3 11.8 30.9 18 51 18 0 0 0 0 0 0 44 0 97-29.5 141.6-78.9 0.8-0.9 1.6-1.8 2.4-2.8l4.9-5.6 -61.5-20.7 86.3-14.9 1.2-2c11.7-19.6 22.3-41.5 31.5-65l1.9-4.9 -44.9-15.1 52.9-9.1 1.1-2.2c11.3-22.2 19.7-42.2 24.3-57.8C388.7 23 385.9 16.8 382.7 14z\"/></svg>&bull;&nbsp;\'.ucfirst(gTxt(\'article\')).\'&nbsp;:&nbsp;\'.gTxt(\'add\').\' </a>\';\r\n			$article = \'<li>\'.(\r\n			$thisarticle[\'thisid\'] ? \r\n				\'<strong>\'.$add.\'</strong> / <strong><a href=\"\'.$interface.\'/index.php?event=article&amp;step=edit&amp;ID=\'.$thisarticle[\'thisid\'].\'&amp;_txp_token=\'.form_token().\'\">\'.gTxt(\'edit\').\'  (<strong>\'.$pretext[\'s\'].\'</strong>)</a></strong>\' : \r\n				$add \r\n			).\'</li>\';\r\n			$image = \'<li><a href=\"\'.$interface.\'/index.php?event=image\'._pat_public_bar_one_pic().\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 315.6 315.6\" xml:space=\"preserve\"><path d=\"M310.6 33.3H5c-2.8 0-5 2.2-5 5v238.9c0 2.8 2.2 5 5 5h305.6c2.8 0 5-2.2 5-5V38.3C315.6 35.6 313.3 33.3 310.6 33.3zM285.6 242.4l-68.8-71.2c-0.8-0.8-2-0.8-2.8-0.1l-47.7 42 -61-75.1c-0.4-0.5-1-0.8-1.6-0.8 -0.6 0-1.2 0.3-1.6 0.8L30 234.8V63.3h255.6V242.4zM210.1 135.6c13.5 0 24.5-11 24.5-24.5 0-13.5-11-24.5-24.5-24.5 -13.5 0-24.5 11-24.5 24.5C185.5 124.6 196.5 135.6 210.1 135.6z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'image\')).\'&nbsp;:&nbsp;\'.( $thisarticle[\'article_image\'] ? gTxt(\'edit\') : gTxt(\'add\') ).\'</a></li>\';\r\n			$comment = ($thisarticle[\'comments_count\'] > 0 ? \'<li><a href=\"\'.$interface.\'/index.php?search_method=parent&crit=\'.$thisarticle[\'thisid\'].\'&event=discuss&step=list\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 29.3 29.3\" xml:space=\"preserve\"><path d=\"M27.2 1.6H2.2C1 1.6 0 2.6 0 3.8v17.6c0 1.2 1 2.2 2.2 2.2h13.5l5.1 3.8c0.4 0.3 0.8 0.5 1.3 0.5 0.8 0 1.6-0.6 1.6-1.9v-2.3h3.5c1.2 0 2.2-1 2.2-2.2V3.8C29.3 2.6 28.4 1.6 27.2 1.6zM27.3 21.3c0 0.1-0.1 0.2-0.2 0.2h-5.5v4l-5.3-4H2.2c-0.1 0-0.2-0.1-0.2-0.2V3.8c0-0.1 0.1-0.2 0.2-0.2v0h25c0.1 0 0.2 0.1 0.2 0.2L27.3 21.3 27.3 21.3zM5.5 10.8h4.3v4.3H5.5C5.5 15.1 5.5 10.8 5.5 10.8zM12.5 10.8h4.3v4.3h-4.3V10.8zM19.5 10.8h4.3v4.3h-4.3V10.8z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'comments\')).\'&nbsp;:&nbsp;\'.gTxt(\'view\').\'</a></li>\' : \'\');\r\n			$disconnect = \'<li><hr>&nbsp;</li><li><a href=\"\'.$interface.\'/index.php?logout=1\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><polygon points=\"8 0 8 8 12 8 12 4 28 4 28 28 12 28 12 24 8 24 8 32 32 32 32 0 \" fill=\"\'.$icon.\'\"/><polygon points=\"14 22 20 16 14 10 14 14 0 14 0 18 14 18\"/></svg>&nbsp;&bull;&nbsp;\'.gTxt(\'logout\').\'&nbsp;</a></li>\';\r\n			$version = \'<li><strong style=\"display:inline-block;width:100%;text-align:right\">v 0.4.1</strong></li>\';\r\n	\r\n			return <<<EOT\r\n	\r\n<div id=\"pat-previewer-plugin\"><span><img width=\"22\" height=\"22\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAABv0lEQVR4AZWUr66zMBiHmyw5CReAw2HxqJkKZC22vrYed+SS+VrU9LmCXcHuYXYGh3o+0lDC3tNvH19/ipHn2fsHUPJQcyNQo45FXEb8yYOR5iPYUOYENT+8AHhy+1CFwXMlKrL4qgiUeRyLoiAq9vidif254/6Kq6QQuBAUmd797qogbMUfwhUXvnZXGid7T+eRxQ3dO866d4nDRI/EKwaJo1TcuzwvegxO4Kn8L3pCuqsIGcFIT4fGZ3DPi5Eq3ZEtPJijYMCgaRne8BMjE0Y+yk0cYpq8BSYClnZJw7fA29y7UBO4b4vzm+JMheYmcSGIKXG7xX1D7NRhCBLPC2Suq2KU+FGBIgA/Gw4X1P8Jql3v8GTAHhXIxcFMh8JijgkkDi98nIBDHxCIvV8BeODRlL8VWVxM3q4Kh6FZa9nyTzzGMEdFj6HAo4VA4KfM3rtNod9n8Rs/YyQeo5lWRUeJSxuReMt5w2XatQq7RKelyn/XEhdVpEYcDT02CTw+jq7J43IW3DFYWkz8qHJOn6csLmM2Rb9kSK9vjWLI4zL0EBXdkmv8iRMXHDMOdSjp0QoUiqQY0hfpUDwzC476A2M87nWHub3fAAAAAElFTkSuQmCC\" aria-hidden=\"true\" alt=\"pat-public-admin-bar for TXP CMS\" />&nbsp;&nbsp; {$prefs[\'sitename\']}</span><ul>{$logged}<li><a href=\"{$interface}\" style=\"text-align:right;color:#ffda44\">Textpattern CMS {$prefs[\'version\']}</a></li><li><a href=\"https://forum.textpattern.io/\" target=_blank\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" viewBox=\"0 0 45.6 45.6\"><path d=\"M43.2 37.6L31.2 15.4V2.2C31.2 1 30.2 0 28.9 0H16.7c-1.2 0-2.3 1-2.3 2.2v13.2L2.5 37.6c-0.9 1.7-0.9 3.7 0.1 5.4 1 1.6 2.8 2.7 4.7 2.7h31.1c1.9 0 3.7-1 4.7-2.7C44 41.3 44.1 39.3 43.2 37.6zM24.8 28.3c1.1 0 1.9 0.9 1.9 1.9 0 1.1-0.9 1.9-1.9 1.9 -1.1 0-1.9-0.9-1.9-1.9C22.9 29.2 23.7 28.3 24.8 28.3zM19.3 16.6c1.1 0 1.9 0.9 1.9 1.9 0 1.1-0.9 1.9-1.9 1.9 -1.1 0-1.9-0.9-1.9-1.9C17.4 17.5 18.3 16.6 19.3 16.6zM19.3 23.4c1.7 0 3.1 1.4 3.1 3.1 0 1.7-1.4 3.1-3.1 3.1 -1.7 0-3.1-1.4-3.1-3.1S17.6 23.4 19.3 23.4zM37.4 39.6c-0.4 0.7-1.1 1.1-1.9 1.1H10.2c-0.8 0-1.5-0.4-1.9-1.1 -0.4-0.7-0.4-1.5 0-2.2l3-5.5c0.4-0.8 1.3-1.2 2.2-1.1 4 0.4 8.6 3.8 12.6 3.7 2.2-0.1 4.2-0.9 6.1-1.9 1.1-0.6 2.5-0.2 3.1 0.9l2.2 4C37.8 38 37.8 38.9 37.4 39.6z\"/></svg>&nbsp;&bull;&nbsp;TXP Forum</a></li>{$section}{$page}{$form}{$style}{$file}{$link}{$category}{$article}{$image}{$comment}{$pref}{$log}{$disconnect}{$version}</ul></div><style scoped>body{padding-top:35px!important}#pat-previewer-plugin{position:{$position};z-index:9999;top:{$js_top_adjust}px;left:0;min-width:320px;width:100%;height:30px;background:{$bgcolor};box-sizing:content-box;border-top:10px solid rgba(0,0,0,.8);color:{$color};font-size:14px;line-height:25px;font-family:\"HelveticaNeue-Light\",\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,Arial,\"Lucida Grande\",sans-serif;font-weight:300;cursor:default;box-shadow:0 3px 5px rgba(0,0,0,0.3);backface-visibility:hidden}#pat-previewer-plugin::before{content:\" \";display:block;position:absolute;z-index:9999;top:-10px;left:50%;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid {$color}}#pat-previewer-plugin:hover::before{border:0}#pat-previewer-plugin span{position:absolute;z-index:9998;top:-32px;left:0;float:left;min-width:300px;width:95.5%;margin:1em auto 0;padding:15px 2.5% 0px 2%;background:{$bgcolor};text-align:left}#pat-previewer-plugin:hover span{display:block;color:#2ea2cc;background:{$bgcolor}}#pat-previewer-plugin span b{float:right;font-weight:normal}#pat-previewer-plugin img{display:inline;width:22px;margin:0;vertical-align:middle}#pat-previewer-plugin ul{position:absolute;top:-800px;min-width:290px;margin:30px 0 0 0;padding:0 0 30px 30px;background:{$bgcolor};text-align:left;box-shadow:0 3px 5px rgba(0,0,0,.3);transition:top 800ms ease 0s}#pat-previewer-plugin:hover ul{display:block;top:0}#pat-previewer-plugin ul li{max-width:90%;margin-left:0;list-style:none;color:#84878b;line-height:30px}#pat-previewer-plugin a{overflow:hidden;display:block;border:none;white-space:nowrap;outline:none;text-overflow:ellipsis;text-decoration:none;color:{$color}}#pat-previewer-plugin a:hover{color:{$hover}}#pat-previewer-plugin strong{text-overflow:ellipsis;font-weight:normal}#pat-previewer-plugin strong a{display:inline;text-overflow:ellipsis}#pat-previewer-plugin svg{vertical-align:middle}#pat-previewer-plugin svg *{fill:{$icon}}#pat-previewer-plugin a:hover svg path,#pat-previewer-plugin a:hover svg *{fill:{$hover}!important}#pat-previewer-plugin hr{position:relative;z-index:0;top:25px;left:185px;width:100px;height:1px;margin:5px 0;background:{$title};border:medium none}img[alt=\"\"],img:not([alt]){outline:5px dashed #c00}</style><script>(function(){\'use strict\';var _el=document.getElementById(\'pat-previewer-plugin\');if(window.innerHeight<660){_el.style.position=\'absolute\';_el.style.top=\'{$js_top_adjust}\';}})();</script>\r\nEOT;\r\n\r\n		}\r\n\r\n\r\n	}\r\n\r\n}\r\n\r\n\r\n/*\r\n * Check protocol\r\n *\r\n * @param  string  $type\r\n * @return string  URI or protocol security value for cookie\r\n */\r\nfunction _pat_public_bar_protocol($type = NULL)\r\n{\r\n	$uri = strtolower( substr($_SERVER[\"SERVER_PROTOCOL\"], 0, strpos($_SERVER[\"SERVER_PROTOCOL\"], \'/\')) );\r\n\r\n	if($type === \'cookie\')\r\n		$out = ($uri == \'http\' ? \'false\' : \'true\');\r\n	else\r\n		$out = $uri.\'://\';\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n/**\r\n * Keep only domain name and extension from URI\r\n * \r\n * @param  $str   String\r\n * @return string URI without protocol\r\n */\r\nfunction _pat_public_bar_sanitize_uri($str)\r\n{\r\n\r\n	return preg_replace(\'#^https?://(w{3}\\.)?#\', \'\', $str);\r\n\r\n}\r\n\r\n/**\r\n * Retrieve only the first article image from a list\r\n *\r\n * @param \r\n * @return integer  article image ID\r\n */\r\nfunction _pat_public_bar_one_pic()\r\n{\r\n\r\n	global $thisarticle;\r\n\r\n	$pics = $thisarticle[\'article_image\'];\r\n	$pos = strpos($pics, \',\');\r\n	if ($pos)\r\n		$pic = substr( $pics, 0, $pos );\r\n	else\r\n		$pic = $pics;\r\n\r\n	return $pic ? \'&amp;step=image_edit&amp;id=\'.$pic.\'&amp;_txp_token=\'.form_token() : \'\';\r\n}\r\n\r\n\r\n/**\r\n * Plugin prefs: TXP admin URL.\r\n *\r\n */\r\nfunction _pat_public_bar_prefs()\r\n{\r\n	global $prefs, $textarray;\r\n\r\n	$textarray[\'pat_admin_url\'] = \'This interface URL\';\r\n	$textarray[\'pat_public_bar_disable\'] = \'Temporarily disable pat_public_bar?\';\r\n	$textarray[\'pat_admin_ip\'] = \'IP address for permanent display\';\r\n\r\n	$_pat_interface = hu.\'textpattern\';\r\n\r\n	if ( $prefs[\'siteurl\'] != $_SERVER[\'HTTP_HOST\'].preg_replace(\'#[/\\\\\\\\]$#\', \'\', dirname(dirname($_SERVER[\'SCRIPT_NAME\']))) )\r\n		$pat_interface = \'http\'.( isset($_SERVER[\'HTTPS\']) ? \'s\' : \'\' ).\'://\'.\"{$_SERVER[\'HTTP_HOST\']}\";\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_admin_url\'\")) \r\n		safe_insert(\'txp_prefs\', \"name=\'pat_admin_url\', val=\'\".doSlash($_pat_interface).\"\', type=1, event=\'admin\', html=\'text_input\', position=23\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_public_bar_disable\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_public_bar_disable\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=24\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_public_bar_ip\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_public_bar_ip\', val=\'n/a\', type=1, event=\'admin\', html=\'text_input\', position=25\");\r\n\r\n	safe_repair(\'txp_plugin\');\r\n}\r\n\r\n\r\n/**\r\n * Delete this plugin prefs.\r\n *\r\n */\r\nfunction _pat_public_bar_cleanup()\r\n{\r\n	\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_admin_url\'\");\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_public_bar_disable\'\");\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_public_bar_ip\'\");\r\n\r\n	safe_repair(\'txp_plugin\');\r\n}', '/**\r\n * pat_public_bar Textpattern CMS plugin\r\n * @author:  Patrick LEFEVRE, all rights reserved. <patrick[dot]lefevre[at]gmail[dot]com>\r\n * @link:    http://pat-public-bar.cara-tm.com\r\n * @type:    Admin + Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.4.1\r\n * @license: GPLv2\r\n*/\r\n\r\n\r\n/**\r\n * This plugin tag registry\r\n *\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_public_bar\');\r\n}\r\n\r\nif (@txpinterface == \'admin\') {\r\n\r\n	register_callback(\'_pat_public_bar_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'_pat_public_bar_cleanup\', \'plugin_lifecycle.pat_public_bar\', \'deleted\');\r\n\r\n}\r\n\r\n\r\nif (@txpinterface == \'public\') {\r\n\r\n	/**\r\n	 * Inject an HTML block on the public side for all login-in users.\r\n	 * Allow access side to side from public to admin pages.\r\n	 *\r\n	 * @param  $atts  $things\r\n	 * @return HTML content for login-in users\r\n	 */\r\n	function pat_public_bar($atts) {\r\n\r\n		global $prefs, $pretext, $thisarticle, $thiscomment;\r\n\r\n		extract(lAtts(array(\r\n			\'interface\' 	=> $prefs[\'pat_admin_url\'],\r\n			\'position\' 	=> \'fixed\',\r\n			\'js_top_adjust\' => \'-45\',\r\n			\'bgcolor\' 	=> \'#23282d\',\r\n			\'color\' 	=> \'#fff\',\r\n			\'title\' 	=> \'#84878b\',\r\n			\'hover\' 	=> \'#62bbdc\',\r\n			\'icon\' 		=> \'#ccc\',\r\n		), $atts));\r\n\r\n		if ( cs(\'txp_login_public\') && true != $prefs[\'pat_public_bar_disable\'] ) {\r\n\r\n\r\n			if ($position != \'fixed\' || $position != \'absolute\')\r\n				$position = \'fixed\';\r\n\r\n\r\n			/* List of user privs who have full bar access:\r\n		   	1 = administrators\r\n		   	2 = administrator assistants\r\n		   	6 = designers\r\n			*/\r\n			$pat_privs_array = array(1, 2, 6);\r\n\r\n			$name = substr(cs(\'txp_login_public\'), 10);\r\n			$rs = safe_row(\'name, RealName, privs\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\r\n\r\n			ob_end_clean();\r\n\r\n			if ( in_array($rs[\'privs\'], $pat_privs_array) ) {\r\n\r\n				$section = \'<li><hr>\'.ucfirst(gTxt(\'structure\')).\'</li> <li><a href=\"\'.$interface.\'/index.php?event=section&amp;step=section_edit&amp;name=\'.$pretext[\'s\'].\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 297 297\" xml:space=\"preserve\"><path d=\"M268.3 63.5h-20.5V42.7c0-5.5-4.5-10-10-10h-23.9V10c0-5.5-4.5-10-10-10h-175.3c-5.5 0-10 4.5-10 10v213.5c0 5.5 4.5 10 10 10h23.9v22.7c0 5.5 4.5 10 10 10h20.5v20.9c0 5.5 4.5 10 10 10h175.3c5.5 0 10-4.5 10-10V73.5C278.2 68 273.8 63.5 268.3 63.5zM38.7 19.9h155.4v193.6H38.7V19.9zM72.5 233.5h131.5c5.5 0 10-4.5 10-10V52.6h13.9v193.6H72.5V233.5zM258.3 277.1H102.9v-10.9h134.9c5.5 0 10-4.5 10-10V83.5h10.5V277.1z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'section\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\' (<strong>\'.$pretext[\'s\'].\'</strong>)</a></li> \';\r\n				$page = \'<li><a href=\"\'.$interface.\'/index.php?event=page&amp;name=\'.$pretext[\'page\'].\'&amp;_txp_token=\'.form_token().\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><path d=\"M0 2v28h32L32 2H0zM10 4h12v2H10V4zM6 4h2v2H6V4zM2 4h2v2H2V4zM30 28H2V8h28V28zM30 6h-4V4h4V6z\"/><rect x=\"18\" y=\"20\" width=\"6\" height=\"2\"/><rect x=\"18\" y=\"16\" width=\"10\" height=\"2\"/><rect x=\"18\" y=\"12\" width=\"10\" height=\"2\"/><rect x=\"4\" y=\"12\" width=\"10\" height=\"10\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'page\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\' (<strong>\'.$pretext[\'page\'].\'</strong>)</a></li> \';\r\n				$form = \'<li><a href=\"\'.$interface.\'/index.php?event=form\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 100 100\" xml:space=\"preserve\"><path d=\"M41.8 59.1v6.1L20 55.8v-5.2l21.8-9.4v6.2l-15.2 5.8L41.8 59.1zM55.2 41l-6.1 24.9c-0.2 0.8-0.4 1.5-0.5 1.9 -0.2 0.5-0.4 0.8-0.7 1.1 -0.3 0.3-0.8 0.4-1.4 0.4 -1.5 0-2.2-0.6-2.2-1.9 0-0.3 0.1-1.2 0.4-2.5l6-24.9c0.3-1.3 0.6-2.2 0.9-2.7 0.3-0.5 0.9-0.7 1.7-0.7 0.7 0 1.3 0.2 1.7 0.5 0.4 0.3 0.6 0.8 0.6 1.4C55.7 39 55.5 39.8 55.2 41zM80 55.8L58.2 65.3v-6.1l15.2-6 -15.2-5.9v-6.1L80 50.6V55.8z\" fill=\"\'.$icon.\'\"/><path d=\"M94 6H6c-3.3 0-6 2.7-6 6v76c0 3.3 2.7 6 6 6h88c3.3 0 6-2.7 6-6V12C100 8.7 97.3 6 94 6zM79 10c2.2 0 4 1.8 4 4s-1.8 4-4 4 -4-1.8-4-4S76.8 10 79 10zM68 10c2.2 0 4 1.8 4 4s-1.8 4-4 4 -4-1.8-4-4S65.8 10 68 10zM94 88H6V22h88V88zM90 18c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4S92.2 18 90 18z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_forms\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'edit\')).\'</a></li>\';\r\n				$style = \'<li><a href=\"\'.$interface.\'/index.php?event=css\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 13.33 13.33\" xml:space=\"preserve\" enable-background=\"new 0 0 13.33 13.33\"><path d=\"M12.48 0H0.85c-0.47 0-0.85 0.38-0.85 0.85v11.63c0 0.47 0.38 0.85 0.85 0.85h11.63c0.47 0 0.85-0.38 0.85-0.85V0.85C13.33 0.38 12.95 0 12.48 0zM3.96 7.73c0.28 0 0.5-0.05 0.68-0.12l0.14 0.87c-0.21 0.09-0.61 0.17-1.06 0.17 -1.23 0-2.02-0.75-2.02-1.95 0-1.11 0.76-2.03 2.18-2.03 0.31 0 0.66 0.06 0.91 0.15L4.6 5.71C4.46 5.64 4.25 5.59 3.93 5.59c-0.62 0-1.03 0.44-1.02 1.07C2.91 7.36 3.38 7.73 3.96 7.73zM6.45 8.65c-0.5 0-0.94-0.11-1.23-0.27l0.21-0.85C5.64 7.67 6.1 7.83 6.45 7.83c0.36 0 0.51-0.12 0.51-0.32S6.84 7.22 6.39 7.07C5.6 6.8 5.3 6.38 5.31 5.92c0-0.71 0.61-1.25 1.55-1.25 0.44 0 0.84 0.1 1.08 0.22l-0.21 0.82C7.56 5.62 7.23 5.49 6.9 5.49c-0.29 0-0.45 0.12-0.45 0.31 0 0.18 0.15 0.27 0.62 0.44C7.79 6.49 8.09 6.86 8.1 7.42 8.1 8.13 7.54 8.65 6.45 8.65zM9.83 8.65c-0.5 0-0.94-0.11-1.23-0.27l0.21-0.85C9.03 7.67 9.48 7.83 9.83 7.83c0.36 0 0.51-0.12 0.51-0.32s-0.12-0.29-0.56-0.44C8.99 6.8 8.69 6.38 8.7 5.92c0-0.71 0.61-1.25 1.55-1.25 0.44 0 0.84 0.1 1.08 0.22l-0.21 0.82c-0.17-0.09-0.5-0.22-0.83-0.22 -0.29 0-0.45 0.12-0.45 0.31 0 0.18 0.15 0.27 0.62 0.44 0.73 0.25 1.03 0.62 1.04 1.18C11.49 8.13 10.93 8.65 9.83 8.65z\"/></svg>&nbsp;&bull;&nbsp;\'.strtoupper(gTxt(\'css\')).\'&nbsp;:&nbsp;\'.gTxt(\'edit\').\'</a></li> \';\r\n				$pref = \'<hr><li>&bull;</li> <li><a href=\"\'.$interface.\'/index.php?event=prefs\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 268.8 268.8\" xml:space=\"preserve\"><path d=\"M267.9 119.5c-0.4-3.8-4.8-6.6-8.6-6.6 -12.3 0-23.2-7.2-27.8-18.4 -4.7-11.5-1.7-24.8 7.5-33.2 2.9-2.6 3.2-7.1 0.8-10.1 -6.3-8-13.5-15.2-21.3-21.5 -3.1-2.5-7.6-2.1-10.2 0.8 -8 8.9-22.4 12.2-33.5 7.5 -11.6-4.9-18.9-16.6-18.2-29.2 0.2-4-2.7-7.4-6.6-7.8 -10-1.2-20.2-1.2-30.2-0.1 -3.9 0.4-6.8 3.8-6.7 7.7 0.4 12.5-6.9 24-18.4 28.7 -11 4.5-25.3 1.2-33.3-7.6 -2.6-2.9-7.1-3.3-10.1-0.9 -8.1 6.3-15.4 13.6-21.7 21.5 -2.5 3.1-2.1 7.6 0.8 10.2 9.4 8.5 12.4 21.9 7.5 33.5 -4.6 11-16.1 18.2-29.2 18.2 -4.3-0.1-7.3 2.7-7.8 6.6 -1.2 10.1-1.2 20.4-0.1 30.6 0.4 3.8 5 6.6 8.8 6.6 11.7-0.3 22.9 6.9 27.7 18.4 4.7 11.5 1.7 24.8-7.5 33.2 -2.9 2.6-3.2 7.1-0.8 10.1 6.2 8 13.4 15.2 21.3 21.5 3.1 2.5 7.6 2.1 10.2-0.8 8-8.9 22.4-12.2 33.5-7.5 11.6 4.9 18.9 16.6 18.2 29.2 -0.2 4 2.7 7.4 6.6 7.9 5.1 0.6 10.3 0.9 15.5 0.9 4.9 0 9.8-0.3 14.8-0.8 3.9-0.4 6.8-3.8 6.7-7.7 -0.5-12.5 6.9-24 18.4-28.7 11.1-4.5 25.3-1.2 33.3 7.6 2.7 2.9 7 3.2 10.1 0.8 8-6.3 15.3-13.5 21.7-21.5 2.5-3.1 2.1-7.6-0.8-10.2 -9.4-8.5-12.4-21.9-7.5-33.5 4.6-10.9 15.6-18.2 27.5-18.2l1.7 0c3.9 0.3 7.4-2.7 7.9-6.6C269 139.9 269.1 129.6 267.9 119.5zM134.6 179.5c-24.7 0-44.8-20.1-44.8-44.8 0-24.7 20.1-44.8 44.8-44.8 24.7 0 44.8 20.1 44.8 44.8C179.4 159.4 159.3 179.5 134.6 179.5z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_preferences\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'edit\')).\'</a></li>\';\r\n				$log = \'<li><a href=\"\'.$interface.\'/index.php??event=log\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 525.2 525.2\" xml:space=\"preserve\"><path d=\"M196.9 525.2H328.2V0H196.9V525.2zM0 525.2h131.3V262.6H0V525.2zM393.9 164.1v361h131.3V164.1H393.9z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'logs\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'view\')).\'</a>\'.$pat_admin_interface.\'</li>\';\r\n\r\n			}\r\n\r\n			$logged = \'<li><a href=\"\'.$interface.\'/index.php?event=admin\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 409.2 409.2\" xml:space=\"preserve\"><path d=\"M204.6 216.7c50.7 0 91.7-48.1 91.7-107.4 0-82.2-41.1-107.4-91.7-107.4 -50.7 0-91.7 25.1-91.7 107.4C112.8 168.6 153.9 216.7 204.6 216.7zM407.2 374.7L360.9 270.5c-2.1-4.8-5.8-8.7-10.5-11.1l-71.8-37.4c-1.6-0.8-3.5-0.7-4.9 0.4 -20.3 15.4-44.2 23.5-69.1 23.5 -24.9 0-48.8-8.1-69.1-23.5 -1.4-1.1-3.3-1.2-4.9-0.4L58.8 259.3c-4.6 2.4-8.3 6.4-10.5 11.1L2 374.7c-3.2 7.2-2.5 15.4 1.8 22 4.3 6.6 11.5 10.5 19.4 10.5h362.9c7.9 0 15.1-3.9 19.4-10.5C409.7 390.1 410.4 381.9 407.2 374.7z\"/></svg>&nbsp;&bull;&nbsp;\'.$rs[\'RealName\'].\'</a></li> \';\r\n			$file = \'<hr><li>\'.ucfirst(gTxt(\'tab_content\')).\'</li> <li><a href=\"\'.$interface.\'/index.php?event=file\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><path d=\"M22.4 0H4v32h24V5.6L22.4 0zM22 2.4L25.6 6h-3.6C22 6 22 2.4 22 2.4zM26 30H6V2h14v6h6V30z\"/><rect x=\"8\" y=\"20\" width=\"12\" height=\"2\"/><rect x=\"8\" y=\"12\" width=\"16\" height=\"2\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"2\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'tab_file\')).\'&nbsp;:&nbsp;\'.ucfirst(gTxt(\'add\')).\'</a></li>\';\r\n			$link = \'<li><a href=\"\'.$interface.\'/index.php?event=link\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 612 612\" xml:space=\"preserve\"><path d=\"M499 350.1c-27.3-11.4-63.1-20.1-103.7-25.1 2.3-22.6 3.5-46.2 3.5-69.6 0-23.4-1.1-47-3.5-69.7 40.6-5.1 76.3-13.8 103.6-25.2 14.1 28.6 22.1 60.8 22.1 94.9C521 289.3 513.1 321.5 499 350.1zM298.3 40.4c2.6-0.1 5.2-0.2 7.7-0.2 2.6 0 5.2 0.1 7.7 0.2 15.3 10.4 35.2 49.8 45.7 116.4 -35.9 2.7-72.8 2.4-106.8-0.5C263.2 90 283 50.8 298.3 40.4zM245.5 255.3c0-24.3 1.1-46.7 3.1-67 19.5 1.8 39.8 2.6 60.6 2.6 18.1 0 36.3-0.7 54.2-2.1 2 20.2 3.1 42.4 3.1 66.5s-1.1 46.2-3.1 66.4c-17.4-1.5-35.6-2.1-54.2-2.1 -20.8 0-41.2 0.8-60.6 2.6C246.6 301.9 245.5 279.6 245.5 255.3zM482.3 132.5c-24 9.4-55.3 16.8-91 21.2 -6.4-42.2-17.1-79.8-31.7-106.6C410.1 60 453.3 90.9 482.3 132.5zM252.4 47.1c-14.6 26.6-25.1 63.9-31.7 105.8 -35.2-4.8-66.4-12.6-89.6-22.4C160.1 89.9 202.7 59.9 252.4 47.1zM468.4 450.5c57-46.9 92.9-115.8 92.9-195.2C561.3 114.5 446.8 0 306 0 180.6 0 76.1 90.8 54.7 210.2c-2.7 14.7-4 29.7-4 45.1 0 140.8 74.9 251.3 219.3 292.9V612l212.9-122.9L270 366.1v60.4c-116.5 0-178.6-94.5-178.6-184.1 0.6-11 2.1-21.7 4.3-32.2 3.9-18.3 10.2-35.7 18.5-51.9 26.6 11.8 62 21 102.5 26.5 -2.4 23-3.5 46.7-3.5 70.5 0 23.7 1.1 47.5 3.5 70.4 -24.7 3.3-47.4 8.1-67.4 13.9 1.2 1.5 2.3 2.9 3.4 4.4 3.5 4.5 6.8 8.9 10.3 12.2l0.8 0.7 0.7 0.9c2.6 2.9 5.7 5.7 9.2 8.6 14.4-3.5 30.2-6.4 47-8.7 2.3 14.3 4.9 28.1 8.1 41.1h0.1c5.2 1.6 9.8 3 13 3.1h1c0 0 2.3 0.2 3.1 0.3v-47.5 -30.5l48.1 27.7c5.1-0.1 10.2-0.2 15.2-0.2 17.2 0 34.2 0.6 50.3 1.9 -1.9 11.7-3.9 22.6-6.3 32.6l29 16.8c3.6-14.4 6.8-30 9.2-46.1 35.8 4.4 67 11.8 91 21.2 -14.3 20.4-31.9 38.2-52.2 52.6L468.4 450.5z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'links\')).\'&nbsp;:&nbsp;\'.ucfirst(gtxt(\'add\')).\'</a></li>\';\r\n			$category = ( ($thisarticle[\'category1\'] || $thisarticle[\'category2\']) ? \'<li><a href=\"\'.$interface.\'/index.php?event=category\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" x=\"0\" y=\"0\" viewBox=\"0 0 28 28\" xml:space=\"preserve\"><path d=\"M15 18v-4h-4v8l4 0V20h8v8h-8v-4H9V8H5V0h8v8h-2v4h4v-2h8v8H15z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'categories\')).\'&nbsp;:&nbsp;\'.gtxt(\'edit\').\'</a></li>\' : \'\');\r\n			$add = \'<a href=\"\'.$interface.\'/index.php?event=article&amp;Section=\'.$pretext[\'s\'].\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 386.1 386.1\" xml:space=\"preserve\" style=\"margin:0 5px 0 -5px\"><path d=\"M382.7 14l-1.8-1.6 -2.4 0.4c-1.2 0.2-29.6 5.3-70 22.6 -37.3 15.9-92.1 45.6-141.6 96.7 -37.5 38.7-64 72.5-78.6 100.3 -2.5 4.7-4.6 9.3-6.4 13.6L3.1 349.7c-5 6.6-3.7 16 2.9 21 2.7 2.1 5.9 3.1 9.1 3.1 4.5 0 9-2 12-5.9l53.4-70.3c3.4 5 7.3 7.5 9.1 8.4 13.3 11.8 30.9 18 51 18 0 0 0 0 0 0 44 0 97-29.5 141.6-78.9 0.8-0.9 1.6-1.8 2.4-2.8l4.9-5.6 -61.5-20.7 86.3-14.9 1.2-2c11.7-19.6 22.3-41.5 31.5-65l1.9-4.9 -44.9-15.1 52.9-9.1 1.1-2.2c11.3-22.2 19.7-42.2 24.3-57.8C388.7 23 385.9 16.8 382.7 14z\"/></svg>&bull;&nbsp;\'.ucfirst(gTxt(\'article\')).\'&nbsp;:&nbsp;\'.gTxt(\'add\').\' </a>\';\r\n			$article = \'<li>\'.(\r\n			$thisarticle[\'thisid\'] ? \r\n				\'<strong>\'.$add.\'</strong> / <strong><a href=\"\'.$interface.\'/index.php?event=article&amp;step=edit&amp;ID=\'.$thisarticle[\'thisid\'].\'&amp;_txp_token=\'.form_token().\'\">\'.gTxt(\'edit\').\'  (<strong>\'.$pretext[\'s\'].\'</strong>)</a></strong>\' : \r\n				$add \r\n			).\'</li>\';\r\n			$image = \'<li><a href=\"\'.$interface.\'/index.php?event=image\'._pat_public_bar_one_pic().\'\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 315.6 315.6\" xml:space=\"preserve\"><path d=\"M310.6 33.3H5c-2.8 0-5 2.2-5 5v238.9c0 2.8 2.2 5 5 5h305.6c2.8 0 5-2.2 5-5V38.3C315.6 35.6 313.3 33.3 310.6 33.3zM285.6 242.4l-68.8-71.2c-0.8-0.8-2-0.8-2.8-0.1l-47.7 42 -61-75.1c-0.4-0.5-1-0.8-1.6-0.8 -0.6 0-1.2 0.3-1.6 0.8L30 234.8V63.3h255.6V242.4zM210.1 135.6c13.5 0 24.5-11 24.5-24.5 0-13.5-11-24.5-24.5-24.5 -13.5 0-24.5 11-24.5 24.5C185.5 124.6 196.5 135.6 210.1 135.6z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'image\')).\'&nbsp;:&nbsp;\'.( $thisarticle[\'article_image\'] ? gTxt(\'edit\') : gTxt(\'add\') ).\'</a></li>\';\r\n			$comment = ($thisarticle[\'comments_count\'] > 0 ? \'<li><a href=\"\'.$interface.\'/index.php?search_method=parent&crit=\'.$thisarticle[\'thisid\'].\'&event=discuss&step=list\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 29.3 29.3\" xml:space=\"preserve\"><path d=\"M27.2 1.6H2.2C1 1.6 0 2.6 0 3.8v17.6c0 1.2 1 2.2 2.2 2.2h13.5l5.1 3.8c0.4 0.3 0.8 0.5 1.3 0.5 0.8 0 1.6-0.6 1.6-1.9v-2.3h3.5c1.2 0 2.2-1 2.2-2.2V3.8C29.3 2.6 28.4 1.6 27.2 1.6zM27.3 21.3c0 0.1-0.1 0.2-0.2 0.2h-5.5v4l-5.3-4H2.2c-0.1 0-0.2-0.1-0.2-0.2V3.8c0-0.1 0.1-0.2 0.2-0.2v0h25c0.1 0 0.2 0.1 0.2 0.2L27.3 21.3 27.3 21.3zM5.5 10.8h4.3v4.3H5.5C5.5 15.1 5.5 10.8 5.5 10.8zM12.5 10.8h4.3v4.3h-4.3V10.8zM19.5 10.8h4.3v4.3h-4.3V10.8z\"/></svg>&nbsp;&bull;&nbsp;\'.ucfirst(gTxt(\'comments\')).\'&nbsp;:&nbsp;\'.gTxt(\'view\').\'</a></li>\' : \'\');\r\n			$disconnect = \'<li><hr>&nbsp;</li><li><a href=\"\'.$interface.\'/index.php?logout=1\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0\" y=\"0\" width=\"22\" height=\"22\" viewBox=\"0 0 32 32\" xml:space=\"preserve\"><polygon points=\"8 0 8 8 12 8 12 4 28 4 28 28 12 28 12 24 8 24 8 32 32 32 32 0 \" fill=\"\'.$icon.\'\"/><polygon points=\"14 22 20 16 14 10 14 14 0 14 0 18 14 18\"/></svg>&nbsp;&bull;&nbsp;\'.gTxt(\'logout\').\'&nbsp;</a></li>\';\r\n			$version = \'<li><strong style=\"display:inline-block;width:100%;text-align:right\">v 0.4.1</strong></li>\';\r\n	\r\n			return <<<EOT\r\n	\r\n<div id=\"pat-previewer-plugin\"><span><img width=\"22\" height=\"22\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAABv0lEQVR4AZWUr66zMBiHmyw5CReAw2HxqJkKZC22vrYed+SS+VrU9LmCXcHuYXYGh3o+0lDC3tNvH19/ipHn2fsHUPJQcyNQo45FXEb8yYOR5iPYUOYENT+8AHhy+1CFwXMlKrL4qgiUeRyLoiAq9vidif254/6Kq6QQuBAUmd797qogbMUfwhUXvnZXGid7T+eRxQ3dO866d4nDRI/EKwaJo1TcuzwvegxO4Kn8L3pCuqsIGcFIT4fGZ3DPi5Eq3ZEtPJijYMCgaRne8BMjE0Y+yk0cYpq8BSYClnZJw7fA29y7UBO4b4vzm+JMheYmcSGIKXG7xX1D7NRhCBLPC2Suq2KU+FGBIgA/Gw4X1P8Jql3v8GTAHhXIxcFMh8JijgkkDi98nIBDHxCIvV8BeODRlL8VWVxM3q4Kh6FZa9nyTzzGMEdFj6HAo4VA4KfM3rtNod9n8Rs/YyQeo5lWRUeJSxuReMt5w2XatQq7RKelyn/XEhdVpEYcDT02CTw+jq7J43IW3DFYWkz8qHJOn6csLmM2Rb9kSK9vjWLI4zL0EBXdkmv8iRMXHDMOdSjp0QoUiqQY0hfpUDwzC476A2M87nWHub3fAAAAAElFTkSuQmCC\" aria-hidden=\"true\" alt=\"pat-public-admin-bar for TXP CMS\" />&nbsp;&nbsp; {$prefs[\'sitename\']}</span><ul>{$logged}<li><a href=\"{$interface}\" style=\"text-align:right;color:#ffda44\">Textpattern CMS {$prefs[\'version\']}</a></li><li><a href=\"https://forum.textpattern.io/\" target=_blank\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" viewBox=\"0 0 45.6 45.6\"><path d=\"M43.2 37.6L31.2 15.4V2.2C31.2 1 30.2 0 28.9 0H16.7c-1.2 0-2.3 1-2.3 2.2v13.2L2.5 37.6c-0.9 1.7-0.9 3.7 0.1 5.4 1 1.6 2.8 2.7 4.7 2.7h31.1c1.9 0 3.7-1 4.7-2.7C44 41.3 44.1 39.3 43.2 37.6zM24.8 28.3c1.1 0 1.9 0.9 1.9 1.9 0 1.1-0.9 1.9-1.9 1.9 -1.1 0-1.9-0.9-1.9-1.9C22.9 29.2 23.7 28.3 24.8 28.3zM19.3 16.6c1.1 0 1.9 0.9 1.9 1.9 0 1.1-0.9 1.9-1.9 1.9 -1.1 0-1.9-0.9-1.9-1.9C17.4 17.5 18.3 16.6 19.3 16.6zM19.3 23.4c1.7 0 3.1 1.4 3.1 3.1 0 1.7-1.4 3.1-3.1 3.1 -1.7 0-3.1-1.4-3.1-3.1S17.6 23.4 19.3 23.4zM37.4 39.6c-0.4 0.7-1.1 1.1-1.9 1.1H10.2c-0.8 0-1.5-0.4-1.9-1.1 -0.4-0.7-0.4-1.5 0-2.2l3-5.5c0.4-0.8 1.3-1.2 2.2-1.1 4 0.4 8.6 3.8 12.6 3.7 2.2-0.1 4.2-0.9 6.1-1.9 1.1-0.6 2.5-0.2 3.1 0.9l2.2 4C37.8 38 37.8 38.9 37.4 39.6z\"/></svg>&nbsp;&bull;&nbsp;TXP Forum</a></li>{$section}{$page}{$form}{$style}{$file}{$link}{$category}{$article}{$image}{$comment}{$pref}{$log}{$disconnect}{$version}</ul></div><style scoped>body{padding-top:35px!important}#pat-previewer-plugin{position:{$position};z-index:9999;top:{$js_top_adjust}px;left:0;min-width:320px;width:100%;height:30px;background:{$bgcolor};box-sizing:content-box;border-top:10px solid rgba(0,0,0,.8);color:{$color};font-size:14px;line-height:25px;font-family:\"HelveticaNeue-Light\",\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,Arial,\"Lucida Grande\",sans-serif;font-weight:300;cursor:default;box-shadow:0 3px 5px rgba(0,0,0,0.3);backface-visibility:hidden}#pat-previewer-plugin::before{content:\" \";display:block;position:absolute;z-index:9999;top:-10px;left:50%;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid {$color}}#pat-previewer-plugin:hover::before{border:0}#pat-previewer-plugin span{position:absolute;z-index:9998;top:-32px;left:0;float:left;min-width:300px;width:95.5%;margin:1em auto 0;padding:15px 2.5% 0px 2%;background:{$bgcolor};text-align:left}#pat-previewer-plugin:hover span{display:block;color:#2ea2cc;background:{$bgcolor}}#pat-previewer-plugin span b{float:right;font-weight:normal}#pat-previewer-plugin img{display:inline;width:22px;margin:0;vertical-align:middle}#pat-previewer-plugin ul{position:absolute;top:-800px;min-width:290px;margin:30px 0 0 0;padding:0 0 30px 30px;background:{$bgcolor};text-align:left;box-shadow:0 3px 5px rgba(0,0,0,.3);transition:top 800ms ease 0s}#pat-previewer-plugin:hover ul{display:block;top:0}#pat-previewer-plugin ul li{max-width:90%;margin-left:0;list-style:none;color:#84878b;line-height:30px}#pat-previewer-plugin a{overflow:hidden;display:block;border:none;white-space:nowrap;outline:none;text-overflow:ellipsis;text-decoration:none;color:{$color}}#pat-previewer-plugin a:hover{color:{$hover}}#pat-previewer-plugin strong{text-overflow:ellipsis;font-weight:normal}#pat-previewer-plugin strong a{display:inline;text-overflow:ellipsis}#pat-previewer-plugin svg{vertical-align:middle}#pat-previewer-plugin svg *{fill:{$icon}}#pat-previewer-plugin a:hover svg path,#pat-previewer-plugin a:hover svg *{fill:{$hover}!important}#pat-previewer-plugin hr{position:relative;z-index:0;top:25px;left:185px;width:100px;height:1px;margin:5px 0;background:{$title};border:medium none}img[alt=\"\"],img:not([alt]){outline:5px dashed #c00}</style><script>(function(){\'use strict\';var _el=document.getElementById(\'pat-previewer-plugin\');if(window.innerHeight<660){_el.style.position=\'absolute\';_el.style.top=\'{$js_top_adjust}\';}})();</script>\r\nEOT;\r\n\r\n		}\r\n\r\n\r\n	}\r\n\r\n}\r\n\r\n\r\n/*\r\n * Check protocol\r\n *\r\n * @param  string  $type\r\n * @return string  URI or protocol security value for cookie\r\n */\r\nfunction _pat_public_bar_protocol($type = NULL)\r\n{\r\n	$uri = strtolower( substr($_SERVER[\"SERVER_PROTOCOL\"], 0, strpos($_SERVER[\"SERVER_PROTOCOL\"], \'/\')) );\r\n\r\n	if($type === \'cookie\')\r\n		$out = ($uri == \'http\' ? \'false\' : \'true\');\r\n	else\r\n		$out = $uri.\'://\';\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n/**\r\n * Keep only domain name and extension from URI\r\n * \r\n * @param  $str   String\r\n * @return string URI without protocol\r\n */\r\nfunction _pat_public_bar_sanitize_uri($str)\r\n{\r\n\r\n	return preg_replace(\'#^https?://(w{3}\\.)?#\', \'\', $str);\r\n\r\n}\r\n\r\n/**\r\n * Retrieve only the first article image from a list\r\n *\r\n * @param \r\n * @return integer  article image ID\r\n */\r\nfunction _pat_public_bar_one_pic()\r\n{\r\n\r\n	global $thisarticle;\r\n\r\n	$pics = $thisarticle[\'article_image\'];\r\n	$pos = strpos($pics, \',\');\r\n	if ($pos)\r\n		$pic = substr( $pics, 0, $pos );\r\n	else\r\n		$pic = $pics;\r\n\r\n	return $pic ? \'&amp;step=image_edit&amp;id=\'.$pic.\'&amp;_txp_token=\'.form_token() : \'\';\r\n}\r\n\r\n\r\n/**\r\n * Plugin prefs: TXP admin URL.\r\n *\r\n */\r\nfunction _pat_public_bar_prefs()\r\n{\r\n	global $prefs, $textarray;\r\n\r\n	$textarray[\'pat_admin_url\'] = \'This interface URL\';\r\n	$textarray[\'pat_public_bar_disable\'] = \'Temporarily disable pat_public_bar?\';\r\n	$textarray[\'pat_admin_ip\'] = \'IP address for permanent display\';\r\n\r\n	$_pat_interface = hu.\'textpattern\';\r\n\r\n	if ( $prefs[\'siteurl\'] != $_SERVER[\'HTTP_HOST\'].preg_replace(\'#[/\\\\\\\\]$#\', \'\', dirname(dirname($_SERVER[\'SCRIPT_NAME\']))) )\r\n		$pat_interface = \'http\'.( isset($_SERVER[\'HTTPS\']) ? \'s\' : \'\' ).\'://\'.\"{$_SERVER[\'HTTP_HOST\']}\";\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_admin_url\'\")) \r\n		safe_insert(\'txp_prefs\', \"name=\'pat_admin_url\', val=\'\".doSlash($_pat_interface).\"\', type=1, event=\'admin\', html=\'text_input\', position=23\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_public_bar_disable\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_public_bar_disable\', val=\'0\', type=1, event=\'admin\', html=\'yesnoradio\', position=24\");\r\n\r\n	if (!safe_field (\'name\', \'txp_prefs\', \"name=\'pat_public_bar_ip\'\"))\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_public_bar_ip\', val=\'n/a\', type=1, event=\'admin\', html=\'text_input\', position=25\");\r\n\r\n	safe_repair(\'txp_plugin\');\r\n}\r\n\r\n\r\n/**\r\n * Delete this plugin prefs.\r\n *\r\n */\r\nfunction _pat_public_bar_cleanup()\r\n{\r\n	\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_admin_url\'\");\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_public_bar_disable\'\");\r\n	safe_delete(\'txp_prefs\', \"name=\'pat_public_bar_ip\'\");\r\n\r\n	safe_repair(\'txp_plugin\');\r\n}', 'ae3aa5f22c0c01b53518c36b25b987c7', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('jcr_export_txp_templates', 0, 'mcw, hcg, mem + jcr', 'http://txp.builders/', '0.15', 'Exports txp page and form templates, styles, plugins and site variables', '<h1>Export page and form templates as files</h1>\n\n<p>This plugin adds a new pane under ‘extensions’ from which you can export pages, forms, and <span class=\"caps\">CSS</span> styling to a specified templates folder for convenient editing using your preferred code editor. Use it to archive a site&#8217;s templates, or as an initial export from an existing site for use with <a href=\"https://github.com/gocom/rah_flat\" rel=\"nofollow\">rah_flat</a>.</p>\n\n\n<h2>Installation</h2>\n\n<p>Paste the .txt or .zip.txt package code into the Admin &gt; Plugins tab, install and enable the plugin. Visit the “Extensions” tab and get ready to export.</p>\n\n\n<h2>Usage</h2>\n\n<p>By default the plugin looks for a directory called <code>templates/export</code> alongside the <code>textpattern</code> directory. If the directory does not exist, the plugin will attempt to create it the first time you export your templates. If this should fail, you will need to create the directory manually and assign write permissions using <code>chmod 775</code> (or <code>chmod 777</code> if your host requires it) before you can proceed.</p>\n\n<h2>Settings</h2>\n\n<p>Forms are named according to the naming scheme <code>{form-name}.{type}.txp</code> by default, but if you prefer type-name notation, set the variable <code>form_naming_scheme</code> in the plugin source code appropriately:</p>\n\n<pre><code>\'form_naming_scheme\' =&gt;  \'type_name\'</code></pre>\n\n\n<h2>History</h2>\n\n<p>This is an excerpted and reworked version of <a href=\"https://github.com/MST-Textpattern/mrdale-txp-plugins-from-Bitbucket/tree/5c8099c89d26d622621071aadff019dadf190a14/mem_templates\" rel=\"nofollow\">mem_templates</a> v0.2 with just the export facility. mem_templates was an adaptation of <a href=\"http://bertgarcia.org/txpide%3Astart#hcg_templates\" rel=\"nofollow\">hcg_templates</a>, which in turn was a modification of <a href=\"http://mikewest.org/archive/mcw-templates\" rel=\"nofollow\">mcw_templates</a> (<a href=\"https://github.com/mikewest/mcw_templates/tree/master\" rel=\"nofollow\">github</a>).</p>\n\n\n<h2>Changelog</h2>\n\n<h3>0.15 – 2016-06-17</h3>\n\n<ul>\n	<li>Inclusion of section.json files</li>\n	<li>Support for oui_flat_vars site variables (if present)</li>\n	<li>Textpack support</li>\n	<li>Uses Textpattern&#8217;s built-in error notices + Hive styling</li>\n</ul>\n\n<h3>0.1 – 2016-06-16</h3>\n\n<ul>\n	<li>Initial version of stripped down mem_templates</li>\n	<li>Code modifications and updates for newer txp versions</li>\n</ul>', '\n/*\n  TODO: \n    – provide \"choose what to export\" option\n    – making \"naming scheme\" an option in the ui\n*/\n\n\n    /**\n     * Plugin config variables\n     */\n\n    global $jcr_export_txp_templates_prefs;\n    \n    $jcr_export_txp_templates_prefs = array(\n        \'base_dir\'           =>  \'templates/export\',\n        \'form_naming_scheme\' =>  \'name_type\',  // switch to \'type_name\' if preferred\n\n        \'subdir_section\' 	   =>	 \'sections\',\n        \'subdir_plugins\' 	   =>	 \'plugins\',\n        \'subdir_pages\'       =>  \'pages\',\n        \'subdir_forms\'       =>  \'forms\',\n        \'subdir_css\'         =>  \'styles\',\n        \'subdir_variables\'   =>  \'variables\',\n\n        \'ext_section\'        =>  \'.json\',\n        \'ext_plugins\'        =>  \'.txt\',\n        \'ext_pages\'          =>  \'.txp\',\n        \'ext_forms\'          =>  \'.txp\',\n        \'ext_css\'            =>  \'.css\',\n        \'ext_variables\'      =>  \'.json\'\n    );\n\n\n\n    /**\n     * Plugin instantiation \n     */\n \n    if (@txpinterface == \'admin\') {\n\n        add_privs(\'jcr_export_txp_templates\', \'1,2\');\n        register_tab(\'extensions\', \'jcr_export_txp_templates\', gTxt(\'jcr_export_txp_tabname\'));\n        register_callback(\'jcr_dispatcher\', \'jcr_export_txp_templates\');\n    }\n\n\n\n    /**\n     * Act on step actions: only \"export\" or main ui\n     */\n\n    function jcr_dispatcher($event, $step)\n    {\n       // true requires a valid txp_token or the action will fail.\n       $available_steps = array(\n          \'jcr_export_templates\'  => true\n       );\n    \n       // If nothing matches, define a default action.\n       if (!$step or !bouncer($step, $available_steps)) {\n          $step = \'jcr_export_ui_pane\';\n       }\n    \n       // Run the function.\n       $step();\n    }\n    \n   \n    \n    /**\n     * Main interface – option to set export directory name\n     */\n\n    function jcr_export_ui_pane($msg = \'\')\n    {\n       pagetop(gTxt(\'jcr_export_txp_pageheader\'), $msg);\n    \n       print \"\n           <h1>\" . gTxt(\'jcr_export_txp_pageheader\') . \"</h1>\n       \".form(\n             graf(gTxt(\'jcr_export_txp_name_export\').\n             fInput(\'text\', \'jcr_export_dir\', \'\').\n             fInput(\'submit\', \'go\', gTxt(\'jcr_export_txp_export_btn\'), \'smallerbox\').\n             eInput(\'jcr_export_txp_templates\').sInput(\'jcr_export_templates\')\n           )\n       );\n\n    }\n\n\n    /**\n     * Export the templates and report on progress.\n     */\n\n    function jcr_export_templates($msg = \'\')\n    {\n       global $prefs;\n\n       pagetop(gTxt(\'jcr_export_txp_pageheader\'), $msg);\n       \n       $template = new jcr_export_txp_template(); \n       $dir = trim(sanitizeForUrl(ps(\'jcr_export_dir\')), \'-\');\n       $template->export($dir);\n  \n    }\n\n\n    /**\n     * Export class\n     */\n\n    class jcr_export_txp_template {\n\n        function jcr_export_txp_template() {\n\n            global $prefs, $jcr_export_txp_templates_prefs;\n\n            $this->_config = $jcr_export_txp_templates_prefs;\n\n\n            /* internal config variables */\n            \n            $this->_config[\'full_base_path\'] = $prefs[\'path_to_site\'] . \'/\' . $this->_config[\'base_dir\'];                                     \n\n            $this->exportTypes = array(\n                \"plugins\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_plugins\'],\n                                \"data\"       =>  \"code\",\n                                \"fields\"     =>  \"name, status, author, author_uri, version, description, help, code, code_restore, code_md5, type\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_plugins_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_plugins\'),\n                                \"sql\"        =>  \"`status` = %d, `author` = \'%s\', `author_uri` = \'%s\', `version` = \'%s\', `description` = \'%s\', `help` = \'%s\', `code` = \'%s\', `code_restore` = \'%s\', `code_md5` = \'%s\', `type` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_plugins\'],\n                                \"table\"      =>  \"txp_plugin\",\n                                \"filter\"     =>  \"`status` = 1\"\n                            ),\n                \"pages\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_pages\'],\n                                \"data\"       =>  \"user_html\",\n                                \"fields\"     =>  \"name, user_html\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_pages_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_pages\'),\n                                \"sql\"        =>  \"`user_html` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_pages\'],\n                                \"table\"      =>  \"txp_page\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"forms\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_forms\'],\n                                \"data\"       =>  \"Form\",\n                                \"fields\"     =>  \"name, type, Form\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_forms_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_forms\'),\n                                \"sql\"        =>  \"`Form` = \'%s\', `type` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_forms\'],\n                                \"table\"      =>  \"txp_form\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"css\"   =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_css\'],\n                                \"data\"       =>  \"css\",\n                                \"fields\"     =>  \"name, css\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_styles_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_styles\'),\n                                \"sql\"        =>  \"`css` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_css\'],\n                                \"table\"      =>  \"txp_css\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"section\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_section\'],\n                                \"data\"       =>  \"name\",\n                                \"fields\"     =>  \"name, page, css, in_rss, on_frontpage, searchable, title\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_sections_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_sections\'),\n                                \"sql\"        =>  \"`title` = \'%s\', `page` = \'%s\', `in_rss` = %d, `on_frontpage` = %d, `searchable` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_section\'],\n                                \"table\"      =>  \"txp_section\",\n                                \"filter\"     =>	\"1=1\"\n                            ),\n                \"variable\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_variables\'],\n                                \"data\"       =>  \"val\",\n                                \"fields\"     =>  \"name, val, type, event, html, position\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_variables_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_variables\'),\n                                \"sql\"        =>  \"`name` = \'%s\', `val` = \'%s\', `type` = %d, `event` = \'%s\', `html` = \'%s\', `position` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_variables\'],\n                                \"table\"      =>  \"txp_prefs\",\n                                \"filter\"     =>	\"`event` = \'oui_flat_var\'\"\n                            )\n\n            );\n        }\n\n        /**\n         * Check if directory exists and is writable and report error if not\n         */\n         \n         function checkdir($dir = \'\') {\n  \n            $msg = \'\';\n            $dir = $this->_config[\'full_base_path\'] . \'/\' . $dir;\n\n            $outputDir =  array(\n                            $dir,\n                            $dir.\'/\'.$this->_config[\'subdir_plugins\'],\n                            $dir.\'/\'.$this->_config[\'subdir_pages\'],\n                            $dir.\'/\'.$this->_config[\'subdir_css\'],\n                            $dir.\'/\'.$this->_config[\'subdir_forms\'],\n                            $dir.\'/\'.$this->_config[\'subdir_section\'],\n                            $dir.\'/\'.$this->_config[\'subdir_variables\']\n                        );\n\n            foreach ($outputDir as $curDir) {\n \n                if (!is_dir($curDir)) {\n                    if (!@mkdir($curDir, 0777)) {\n                        $msg = array( gTxt(\'jcr_export_txp_dir_missing\', array(\'{path}\' => $curDir)), E_ERROR);\n                        break;\n                    }\n                }\n                if (!is_writable($curDir)) {\n                    $msg = array( gTxt(\'jcr_export_txp_dir_not_writable\', array(\'{path}\' => $curDir)), E_ERROR);\n                    break;\n                }\n            }\n            \n            return $msg;\n\n        }\n\n\n\n        /**\n         * Check if directory exists and is writable and report error if not\n         */\n\n        function export($dir = \'\') {\n\n            // if directory does not exist or is non-writable, abort      \n            $dir_problem = $this->checkdir($dir);\n            \n            if ($dir_problem != \'\') {\n                return jcr_export_ui_pane($dir_problem);\n            }\n\n            // report successful exports and any errors\n            foreach ($this->exportTypes as $type => $config) {\n                print \"\n                    <h1>\" . gTxt(\'jcr_export_txp_exporting\') . \" \" . $config[\'nice_title\'] . \"</h1>\n                    <ul class=\'results\'>\n                \";\n\n                $rows = safe_rows( $config[\'fields\'], $config[\'table\'], $config[\'filter\'] );\n\n                foreach ($rows as $row) {\n                \n                    if ($type == \'plugins\') {\n                        $file_name = $row[\'name\'] . (isset($row[\'version\']) ? \".\".$row[\'version\'] : \"\");\n                    } else if ($type == \'forms\') {\n                        if ($this->_config[\'form_naming_scheme\'] == \'name_type\') {\n                            $file_name = $row[\'name\'] . (isset($row[\'type\']) ? \".\".$row[\'type\'] : \"\");\n                        } else {\n                            $file_name = (isset($row[\'type\']) ? \".\".$row[\'type\'] : \"\") . $row[\'name\'];\n                        }\n                    } else {\n                        $file_name = $row[\'name\'];\n                    }\n\n                    $filename     =   sprintf(\n                                          \"%s/%s/%s/%s%s\",\n                                          $this->_config[\'full_base_path\'],\n                                          $dir,\n                                          $config[\'subdir\'],\n                                          $file_name,\n                                          $config[\'ext\']\n                                      );\n                    $nicefilename =   sprintf(\n                                          \"/%s/%s/%s%s\",\n                                          $dir,\n                                          $config[\'subdir\'],\n                                          $file_name,\n                                          $config[\'ext\']\n                                      );\n\n										$data = \'\';\n										// if plugin, base64 encode\n                    if ($type==\'plugins\') {\n                      $data = chunk_split(base64_encode(serialize($row)), 72);\n                    }\n                    // if section, make json\n                    else if ($type==\'section\' || $type==\'variable\') {\n                      $data = json_encode($row, JSON_PRETTY_PRINT);\n                    // if other, then get specific data\n                    } else {\n                    	$data = $row[$config[\'data\']];\n                    }\n\n                    // open file\n                    $f = @fopen($filename, \"w+\");\n\n                    // if file can be created, write file, close it and report success, else error\n                    if ($f) {\n                        fwrite($f,$data);\n                        fclose($f);\n                        $report_result = \"success\";\n                    } else {\n                        $report_result = \"error\";\n                    }\n                    print \"\n                      <li><span class=\'\" . $report_result . \"\'>\" . gTxt(\'jcr_export_txp_export_\'.$report_result) . \"</span> \" . $config[\'nice_name\'] . \" \'\" . $row[\'name\'] . \"\' \" . gTxt(\'jcr_export_txp_export_to\') . \" \'\" . $nicefilename . \"\'</li>\n                    \";\n                }\n                print \"\n                    </ul>\n                \";\n            }\n        }\n\n    }', '\n/*\n  TODO: \n    – provide \"choose what to export\" option\n    – making \"naming scheme\" an option in the ui\n*/\n\n\n    /**\n     * Plugin config variables\n     */\n\n    global $jcr_export_txp_templates_prefs;\n    \n    $jcr_export_txp_templates_prefs = array(\n        \'base_dir\'           =>  \'templates/export\',\n        \'form_naming_scheme\' =>  \'name_type\',  // switch to \'type_name\' if preferred\n\n        \'subdir_section\' 	   =>	 \'sections\',\n        \'subdir_plugins\' 	   =>	 \'plugins\',\n        \'subdir_pages\'       =>  \'pages\',\n        \'subdir_forms\'       =>  \'forms\',\n        \'subdir_css\'         =>  \'styles\',\n        \'subdir_variables\'   =>  \'variables\',\n\n        \'ext_section\'        =>  \'.json\',\n        \'ext_plugins\'        =>  \'.txt\',\n        \'ext_pages\'          =>  \'.txp\',\n        \'ext_forms\'          =>  \'.txp\',\n        \'ext_css\'            =>  \'.css\',\n        \'ext_variables\'      =>  \'.json\'\n    );\n\n\n\n    /**\n     * Plugin instantiation \n     */\n \n    if (@txpinterface == \'admin\') {\n\n        add_privs(\'jcr_export_txp_templates\', \'1,2\');\n        register_tab(\'extensions\', \'jcr_export_txp_templates\', gTxt(\'jcr_export_txp_tabname\'));\n        register_callback(\'jcr_dispatcher\', \'jcr_export_txp_templates\');\n    }\n\n\n\n    /**\n     * Act on step actions: only \"export\" or main ui\n     */\n\n    function jcr_dispatcher($event, $step)\n    {\n       // true requires a valid txp_token or the action will fail.\n       $available_steps = array(\n          \'jcr_export_templates\'  => true\n       );\n    \n       // If nothing matches, define a default action.\n       if (!$step or !bouncer($step, $available_steps)) {\n          $step = \'jcr_export_ui_pane\';\n       }\n    \n       // Run the function.\n       $step();\n    }\n    \n   \n    \n    /**\n     * Main interface – option to set export directory name\n     */\n\n    function jcr_export_ui_pane($msg = \'\')\n    {\n       pagetop(gTxt(\'jcr_export_txp_pageheader\'), $msg);\n    \n       print \"\n           <h1>\" . gTxt(\'jcr_export_txp_pageheader\') . \"</h1>\n       \".form(\n             graf(gTxt(\'jcr_export_txp_name_export\').\n             fInput(\'text\', \'jcr_export_dir\', \'\').\n             fInput(\'submit\', \'go\', gTxt(\'jcr_export_txp_export_btn\'), \'smallerbox\').\n             eInput(\'jcr_export_txp_templates\').sInput(\'jcr_export_templates\')\n           )\n       );\n\n    }\n\n\n    /**\n     * Export the templates and report on progress.\n     */\n\n    function jcr_export_templates($msg = \'\')\n    {\n       global $prefs;\n\n       pagetop(gTxt(\'jcr_export_txp_pageheader\'), $msg);\n       \n       $template = new jcr_export_txp_template(); \n       $dir = trim(sanitizeForUrl(ps(\'jcr_export_dir\')), \'-\');\n       $template->export($dir);\n  \n    }\n\n\n    /**\n     * Export class\n     */\n\n    class jcr_export_txp_template {\n\n        function jcr_export_txp_template() {\n\n            global $prefs, $jcr_export_txp_templates_prefs;\n\n            $this->_config = $jcr_export_txp_templates_prefs;\n\n\n            /* internal config variables */\n            \n            $this->_config[\'full_base_path\'] = $prefs[\'path_to_site\'] . \'/\' . $this->_config[\'base_dir\'];                                     \n\n            $this->exportTypes = array(\n                \"plugins\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_plugins\'],\n                                \"data\"       =>  \"code\",\n                                \"fields\"     =>  \"name, status, author, author_uri, version, description, help, code, code_restore, code_md5, type\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_plugins_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_plugins\'),\n                                \"sql\"        =>  \"`status` = %d, `author` = \'%s\', `author_uri` = \'%s\', `version` = \'%s\', `description` = \'%s\', `help` = \'%s\', `code` = \'%s\', `code_restore` = \'%s\', `code_md5` = \'%s\', `type` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_plugins\'],\n                                \"table\"      =>  \"txp_plugin\",\n                                \"filter\"     =>  \"`status` = 1\"\n                            ),\n                \"pages\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_pages\'],\n                                \"data\"       =>  \"user_html\",\n                                \"fields\"     =>  \"name, user_html\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_pages_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_pages\'),\n                                \"sql\"        =>  \"`user_html` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_pages\'],\n                                \"table\"      =>  \"txp_page\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"forms\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_forms\'],\n                                \"data\"       =>  \"Form\",\n                                \"fields\"     =>  \"name, type, Form\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_forms_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_forms\'),\n                                \"sql\"        =>  \"`Form` = \'%s\', `type` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_forms\'],\n                                \"table\"      =>  \"txp_form\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"css\"   =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_css\'],\n                                \"data\"       =>  \"css\",\n                                \"fields\"     =>  \"name, css\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_styles_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_styles\'),\n                                \"sql\"        =>  \"`css` = \'%s\'\",\n                                \"subdir\"     =>  $this->_config[\'subdir_css\'],\n                                \"table\"      =>  \"txp_css\",\n                                \"filter\"     =>  \"1=1\"\n                            ),\n                \"section\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_section\'],\n                                \"data\"       =>  \"name\",\n                                \"fields\"     =>  \"name, page, css, in_rss, on_frontpage, searchable, title\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_sections_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_sections\'),\n                                \"sql\"        =>  \"`title` = \'%s\', `page` = \'%s\', `in_rss` = %d, `on_frontpage` = %d, `searchable` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_section\'],\n                                \"table\"      =>  \"txp_section\",\n                                \"filter\"     =>	\"1=1\"\n                            ),\n                \"variable\" =>  array(\n                                \"ext\"        =>  $this->_config[\'ext_variables\'],\n                                \"data\"       =>  \"val\",\n                                \"fields\"     =>  \"name, val, type, event, html, position\",\n                                \"nice_title\" =>  gTxt(\'jcr_export_txp_variables_title\'),\n                                \"nice_name\"  =>  gTxt(\'jcr_export_txp_variables\'),\n                                \"sql\"        =>  \"`name` = \'%s\', `val` = \'%s\', `type` = %d, `event` = \'%s\', `html` = \'%s\', `position` = %d\",\n                                \"subdir\"     =>  $this->_config[\'subdir_variables\'],\n                                \"table\"      =>  \"txp_prefs\",\n                                \"filter\"     =>	\"`event` = \'oui_flat_var\'\"\n                            )\n\n            );\n        }\n\n        /**\n         * Check if directory exists and is writable and report error if not\n         */\n         \n         function checkdir($dir = \'\') {\n  \n            $msg = \'\';\n            $dir = $this->_config[\'full_base_path\'] . \'/\' . $dir;\n\n            $outputDir =  array(\n                            $dir,\n                            $dir.\'/\'.$this->_config[\'subdir_plugins\'],\n                            $dir.\'/\'.$this->_config[\'subdir_pages\'],\n                            $dir.\'/\'.$this->_config[\'subdir_css\'],\n                            $dir.\'/\'.$this->_config[\'subdir_forms\'],\n                            $dir.\'/\'.$this->_config[\'subdir_section\'],\n                            $dir.\'/\'.$this->_config[\'subdir_variables\']\n                        );\n\n            foreach ($outputDir as $curDir) {\n \n                if (!is_dir($curDir)) {\n                    if (!@mkdir($curDir, 0777)) {\n                        $msg = array( gTxt(\'jcr_export_txp_dir_missing\', array(\'{path}\' => $curDir)), E_ERROR);\n                        break;\n                    }\n                }\n                if (!is_writable($curDir)) {\n                    $msg = array( gTxt(\'jcr_export_txp_dir_not_writable\', array(\'{path}\' => $curDir)), E_ERROR);\n                    break;\n                }\n            }\n            \n            return $msg;\n\n        }\n\n\n\n        /**\n         * Check if directory exists and is writable and report error if not\n         */\n\n        function export($dir = \'\') {\n\n            // if directory does not exist or is non-writable, abort      \n            $dir_problem = $this->checkdir($dir);\n            \n            if ($dir_problem != \'\') {\n                return jcr_export_ui_pane($dir_problem);\n            }\n\n            // report successful exports and any errors\n            foreach ($this->exportTypes as $type => $config) {\n                print \"\n                    <h1>\" . gTxt(\'jcr_export_txp_exporting\') . \" \" . $config[\'nice_title\'] . \"</h1>\n                    <ul class=\'results\'>\n                \";\n\n                $rows = safe_rows( $config[\'fields\'], $config[\'table\'], $config[\'filter\'] );\n\n                foreach ($rows as $row) {\n                \n                    if ($type == \'plugins\') {\n                        $file_name = $row[\'name\'] . (isset($row[\'version\']) ? \".\".$row[\'version\'] : \"\");\n                    } else if ($type == \'forms\') {\n                        if ($this->_config[\'form_naming_scheme\'] == \'name_type\') {\n                            $file_name = $row[\'name\'] . (isset($row[\'type\']) ? \".\".$row[\'type\'] : \"\");\n                        } else {\n                            $file_name = (isset($row[\'type\']) ? \".\".$row[\'type\'] : \"\") . $row[\'name\'];\n                        }\n                    } else {\n                        $file_name = $row[\'name\'];\n                    }\n\n                    $filename     =   sprintf(\n                                          \"%s/%s/%s/%s%s\",\n                                          $this->_config[\'full_base_path\'],\n                                          $dir,\n                                          $config[\'subdir\'],\n                                          $file_name,\n                                          $config[\'ext\']\n                                      );\n                    $nicefilename =   sprintf(\n                                          \"/%s/%s/%s%s\",\n                                          $dir,\n                                          $config[\'subdir\'],\n                                          $file_name,\n                                          $config[\'ext\']\n                                      );\n\n										$data = \'\';\n										// if plugin, base64 encode\n                    if ($type==\'plugins\') {\n                      $data = chunk_split(base64_encode(serialize($row)), 72);\n                    }\n                    // if section, make json\n                    else if ($type==\'section\' || $type==\'variable\') {\n                      $data = json_encode($row, JSON_PRETTY_PRINT);\n                    // if other, then get specific data\n                    } else {\n                    	$data = $row[$config[\'data\']];\n                    }\n\n                    // open file\n                    $f = @fopen($filename, \"w+\");\n\n                    // if file can be created, write file, close it and report success, else error\n                    if ($f) {\n                        fwrite($f,$data);\n                        fclose($f);\n                        $report_result = \"success\";\n                    } else {\n                        $report_result = \"error\";\n                    }\n                    print \"\n                      <li><span class=\'\" . $report_result . \"\'>\" . gTxt(\'jcr_export_txp_export_\'.$report_result) . \"</span> \" . $config[\'nice_name\'] . \" \'\" . $row[\'name\'] . \"\' \" . gTxt(\'jcr_export_txp_export_to\') . \" \'\" . $nicefilename . \"\'</li>\n                    \";\n                }\n                print \"\n                    </ul>\n                \";\n            }\n        }\n\n    }', 'e7b48ebabd490b27be8e2fbc3b3807b9', '#@admin\n#@jcr_export_txp_templates\n#Default language, en-us, en-gb\njcr_export_txp_tabname => Export templates\njcr_export_txp_pageheader => Export templates\njcr_export_txp_exporting => Exporting\njcr_export_txp_export_btn => Export\njcr_export_txp_export_success => Successfully exported\njcr_export_txp_export_error => Failure exporting\njcr_export_txp_export_to => to\njcr_export_txp_pages_title => page templates\njcr_export_txp_pages => page template\njcr_export_txp_forms_title => form templates\njcr_export_txp_forms => form template\njcr_export_txp_styles_title => CSS stylesheets\njcr_export_txp_styles => CSS stylesheet\njcr_export_txp_sections_title => sections\njcr_export_txp_sections => section\njcr_export_txp_plugins_title => plugins\njcr_export_txp_plugins => plugin\njcr_export_txp_variables_title => variables\njcr_export_txp_variables => variable\njcr_export_txp_name_export => Name this export\njcr_export_txp_dir_missing => <strong>Template directory missing</strong>: The template directory <strong>{path}</strong> does not exist and could not be automatically created. Please create the directory manually and make it writable. See the plugin help.\njcr_export_txp_dir_not_writable => <strong>Template directory not writable<strong>: Please make the directory <strong>{path}</strong> writable manually. See the plugin help for details.\";\n\n#@admin\n#@jcr_export_txp_templates\n#@language de-de\njcr_export_txp_tabname => Textpattern Vorlagen exportieren\njcr_export_txp_pageheader => Textpattern Vorlagen exportieren\njcr_export_txp_exporting => Exportiert\njcr_export_txp_export_btn => Exportieren\njcr_export_txp_export_success => Export erfolgreich:\njcr_export_txp_export_error => Export fehlgeschlagen:\njcr_export_txp_export_to => zu\njcr_export_txp_pages_title => Seitenvorlagen\njcr_export_txp_pages => Seitenvorlage\njcr_export_txp_forms_title => Bausteine\njcr_export_txp_forms => Baustein\njcr_export_txp_styles_title => CSS Stilvorlagen\njcr_export_txp_styles => CSS Stilvorlage\njcr_export_txp_sections_title => Sektionen\njcr_export_txp_sections => Sektion\njcr_export_txp_plugins_title => Plugins\njcr_export_txp_plugins => Plugin\njcr_export_txp_variables_title => Variablen\njcr_export_txp_variables => variable\njcr_export_txp_name_export => Export-Ordner benennen\njcr_export_txp_dir_missing => <strong>Export-Verzeichnis fehlt</strong>: Das Verzeichnis <strong>{path}</strong> existiert nicht und konnte nicht automatisch angelegt werden. Bitte das Verzeichnis manuell anlegen und mit Schreibrechte versehen. Siehe die Plugin-Hilfe.\njcr_export_txp_dir_not_writable => <strong>Export-Verzeichnis hat keine Schreibrechte<strong>: Bitte das Verzeichnis <strong>{path}</strong> manuell mit Schreibrechte versehen. Siehe die Plugin-Hilfe.\";\n', '', 1, 5, 0),
('pro_mobile', 0, 'Hilary Quinn', 'http://www.proximowebdesign.ie', '0.4', 'Txp tag to output content if browser is mobile based', '<h2>V4. Support for \'opera mobi\' added</h2>\n\n<h2>Option 1:</h2>\n\n<code>&lt;txp:pro_mobile&gt; <br />\n// Show mobile content here <br />\n&lt;/txp:pro_mobile&gt;</code>\n\n<h2>Option 2:</h2>\n\n<p>Use as an if/else: </p>\n\n<code>&lt;txp:pro_mobile&gt; <br />\n// Mobile content here <br />\n&lt;txp:else /&gt; <br />\n// Regular content here <br />\n&lt;/txp:pro_mobile&gt;</code>\n\n<h2>Option 3:</h2>\n<p>Define custom content per browser</p>\n<code>&lt;txp:pro_if_mobile name=\"opera mini\" &gt; <br />\nYou use opera mini, great browser! <br />\n&lt;/txp:pro_if_mobile&gt;</code>\n\n<p>Names available for you to use include: </p>\n\n<ul>\n<li>iphone</li>\n<li>ipod</li>\n<li>ipad</li>\n<li>aspen</li>\n<li>nexus</li>\n<li>dream</li>\n<li>android</li>\n<li>cupcake</li>\n<li>blackberry9500</li>\n<li>blackberry9530</li>\n<li>opera mini</li>\n<li>webos</li>\n<li>incognito</li>\n<li>webmate</li>\n</ul>\n\n<h2>Option 4:</h2>\n<p>Feel free to wrap tags!</p>\n\n<code>\n\n&lt;txp:pro_mobile&gt; <br />\n\nWelcome mobile user!<br />\n\n&lt;txp:pro_if_mobile name=\"opera mini\"&gt; <br />\n\nYou use opera mini, great browser! <br />\n\n&lt;/txp:pro_if_mobile&gt; <br />\n\n&lt;txp:pro_if_mobile name=\"android\"&gt; <br />\n\nYou use android, great browser! <br />\n\n&lt;/txp:pro_if_mobile&gt; <br />\n\n&lt;txp:else /&gt; <br />\n\n// normal content <br />\n\n&lt;/txp:pro_mobile&gt;\n\n</code>\n\n<p>Tested with Textpattern version: 4.4.1</p>\n<!-- *** BEGIN PLUGIN CSS *** -->\n<style type=\"text/css\">\ncode{\ndisplay: block;\nmargin: 10px 0 10px 0;\npadding: 10px;\nbackground: #F4F4F2;\nborder-top: 1px dotted #D8D8C3;\nborder-bottom: 1px dotted #D8D8C3;\n}\n</style>\n<!-- *** END PLUGIN CSS *** -->\n', '/*********************\r\n\r\npro_mobile: by Hilary Quinn - www.proximowebdesign.ie\r\n\r\nReleased as a Textpattern plugin under the GNU General Public License\r\n\r\nVersion history: 0.3\r\n\r\n******************************************/\r\n\r\nfunction pro_if_mobile($atts, $thing=NULL) {     //output content if browser type is specified\r\n\r\n  extract(lAtts(array(\r\n    \'name\'  => \'\',\r\n    ),$atts));\r\n\r\n  $user_agent = strtolower($_SERVER[\'HTTP_USER_AGENT\']);\r\n  if(strpos($user_agent, $name)) {\r\n    return parse(EvalElse( $thing, true ));\r\n  }\r\n  return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction pro_mobile($atts, $thing=NULL) {     // output all mobile content\r\n\r\n  if ( pro_mobile_display() ) {\r\n    return parse( EvalElse( $thing, true ) );\r\n  }\r\n    return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction pro_mobile_display($custom_agents = array()) {\r\n \r\n  $agents = array_merge($custom_agents, array(\r\n	\'iphone\',           // Apple iPhone\r\n	\'ipod\',             // Apple iPod touch\r\n        \'ipad\',             // Apple iPad\r\n	\'aspen\',            // iPhone simulator\r\n	\'nexus\',            // Nexus One Android\r\n	\'dream\',            // Pre 1.5 Android\r\n	\'android\',          // 1.5+ Android\r\n	\'cupcake\',          // 1.5+ Android\r\n	\'blackberry9500\',   // Storm\r\n	\'blackberry9530\',   // Storm\r\n	\'opera mini\',       // Opera mobile\r\n        \'opera mobi\',       // Opera mobile\r\n	\'webos\',            //  Nokia Browser\r\n	\'incognito\',        // Other iPhone browser\r\n	\'webmate\'           // Other iPhone browser\r\n		));\r\n  $user_agent = strtolower($_SERVER[\'HTTP_USER_AGENT\']);\r\n  foreach($agents as $agent) if(strpos($user_agent, $agent)) return TRUE;\r\n}', '/*********************\r\n\r\npro_mobile: by Hilary Quinn - www.proximowebdesign.ie\r\n\r\nReleased as a Textpattern plugin under the GNU General Public License\r\n\r\nVersion history: 0.3\r\n\r\n******************************************/\r\n\r\nfunction pro_if_mobile($atts, $thing=NULL) {     //output content if browser type is specified\r\n\r\n  extract(lAtts(array(\r\n    \'name\'  => \'\',\r\n    ),$atts));\r\n\r\n  $user_agent = strtolower($_SERVER[\'HTTP_USER_AGENT\']);\r\n  if(strpos($user_agent, $name)) {\r\n    return parse(EvalElse( $thing, true ));\r\n  }\r\n  return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction pro_mobile($atts, $thing=NULL) {     // output all mobile content\r\n\r\n  if ( pro_mobile_display() ) {\r\n    return parse( EvalElse( $thing, true ) );\r\n  }\r\n    return parse( EvalElse( $thing, false ) );\r\n}\r\n\r\nfunction pro_mobile_display($custom_agents = array()) {\r\n \r\n  $agents = array_merge($custom_agents, array(\r\n	\'iphone\',           // Apple iPhone\r\n	\'ipod\',             // Apple iPod touch\r\n        \'ipad\',             // Apple iPad\r\n	\'aspen\',            // iPhone simulator\r\n	\'nexus\',            // Nexus One Android\r\n	\'dream\',            // Pre 1.5 Android\r\n	\'android\',          // 1.5+ Android\r\n	\'cupcake\',          // 1.5+ Android\r\n	\'blackberry9500\',   // Storm\r\n	\'blackberry9530\',   // Storm\r\n	\'opera mini\',       // Opera mobile\r\n        \'opera mobi\',       // Opera mobile\r\n	\'webos\',            //  Nokia Browser\r\n	\'incognito\',        // Other iPhone browser\r\n	\'webmate\'           // Other iPhone browser\r\n		));\r\n  $user_agent = strtolower($_SERVER[\'HTTP_USER_AGENT\']);\r\n  foreach($agents as $agent) if(strpos($user_agent, $agent)) return TRUE;\r\n}', 'e73427367550a9a94006515fe6066954', '', '', 0, 5, 0),
('pro_modernizr_form', 0, 'Hilary Quinn', 'http://www.proximowebdesign.ie', '0.1', 'Companion plugin for zem_contact_reborn - Modernizr fallback for HTML5 form input', '	<h4>Modernizr placeholder fallback</h4>\n\n	<p>Download a copy of <a href=\"http://modernizr.com/download/\">Modernizr</a> with HTML5 input attributes and input types compatibility, host this via your own web site. It is not recommended to use the <span class=\"caps\">CDN</span> for Modernizr, however there is a production copy available for temporary use only:</p>\n\n	<p><code>&lt;script type=&quot;text/javascript&quot; src=&quot;//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js&quot;&gt;&lt;/script&gt;</code></p>\n\n	<h4>Modernizr input type fallback</h4>\n\n	<p>Input types currently supported for Modernizr fallback include:</p>\n\n	<ul>\n		<li>input type=date</li>\n		<li>input type=range</li>\n		<li>input type=number</li>\n	</ul>\n\n	<h4>Jquery code for fallback solutions</h4>\n\n	<p>Insert the Jquery and Modernizr links before the /head tag of your page. (I would usually prefer to load before /body for speed, however testing in IE7-IE9 revealed it only recognises the .js links in the head of the page, typical IE behaviour!) This includes a css file needed to style the Jquery fallbacks.</p>\n\n	<p>Jquery themes, including &#8216;roll your own&#8217; <a href=\"http://jqueryui.com/themeroller/\">available here.</a></p>\n\n	<p>Latest Google Jquery versions <a href=\"https://developers.google.com/speed/libraries/devguide#jquery\">available here.</a></p>\n\n	<p><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css&quot;&gt;</code><br />\n<code>&lt;script type=&quot;text/javascript&quot; src=&quot;//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js&quot;&gt;&lt;/script&gt;</code><br />\n<code>&lt;script type=&quot;text/javascript&quot; src=&quot;//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js&quot;&gt;&lt;/script&gt;</code></p>\n\n	<p>Download Modernizr as instructed above and insert link:</p>\n\n	<p><code>&lt;script type=&quot;text/javascript&quot; src=&quot;&lt;txp:site_url /&gt;js/modernizr.mycustomnumber.js&quot;&gt;&lt;/script&gt;</code></p>\n\n	<h4>New tag to execute Modernizr check and Jquery fallback</h4>\n\n	<p>Insert the tag below before the /body :</p>\n\n	<p><code>&lt;txp:pro_modernizr_form /&gt;</code></p>\n\n	<p>This tag will load the Modernizr checks to see if the fallback Jquery is needed, and if it is it will execute that code.</p>\n\n	<p>Tested in Firefox, Safari, IE 7-10, Chrome</p>', 'function pro_modernizr_form($atts)\r\n{\r\n	\r\n$var1 = escape_js(gps(\'param\'));\r\n$var2 = escape_js(gps(\'param\'));\r\n        $var1 = \"\r\n        <script type=\\\"text/javascript\\\">\r\n            var placeholder = $( \\\".zemText, .zemTextarea\\\" );\r\n                if (!Modernizr.input.placeholder) {\r\n                    var placeholder = $( \\\".zemText, .zemTextarea\\\" ).each(function() {\r\n                        if ($(this).val() == \'\') {\r\n                        $(this).val($(this).attr(\'placeholder\'));\r\n                        $(this).addClass(\'placeholder\');\r\n                    }\r\n                    $(this).focus(function() {\r\n                        if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                        $(this).val(\'\');\r\n                        $(this).removeClass(\'placeholder\');\r\n                    }\r\n                    }).blur(function() {\r\n                        if($(this).val() == \'\') {\r\n                         $(this).val($(this).attr(\'placeholder\'));\r\n                        $(this).addClass(\'placeholder\');\r\n                    }\r\n                    });\r\n                    });\r\n                    $(\'[placeholder]\').parents(\'form\').submit(function() {\r\n                        $(this).find(\'[placeholder]\').each(function() {\r\n                        if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                            $(this).val(\'\');\r\n                        }\r\n                    });\r\n                    });\r\n                    window.onbeforeunload = function() {\r\n                        $(\'[placeholder]\').each(function() {\r\n                            if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                            $(this).val(\'\');\r\n                            }\r\n                    });\r\n                    };   \r\n}\r\n        </script>\";\r\n        $var2 = \"\r\n        <script type=\\\"text/javascript\\\">\r\n                if (!Modernizr.input.date) {\r\n                    $(function() {\r\n		        var date = $( \\\"#date\\\" ).datepicker({\r\n                        dateFormat: \'yy-mm-dd\'\r\n                        });\r\n                 });\r\n                } \r\n                if (!Modernizr.input.number) {\r\n                    $(function() {\r\n                        var number = $( \\\"#number\\\" ).spinner();\r\n                    });\r\n                }\r\n                if (!Modernizr.input.range) {\r\n                    $(function() {\r\n                        var range = $(\\\"#range\\\");\r\n		        $(\\\"#range\\\").each(function(){\r\n                            var range = $(this);\r\n            \r\n                        var sliderDiv = $(\\\"<div/>\\\");\r\n                        sliderDiv.width(range.width());\r\n            \r\n                        range.after(\r\n                            sliderDiv.slider({\r\n                            min: parseFloat(range.attr(\\\"min\\\")),\r\n                            max: parseFloat(range.attr(\\\"max\\\")),\r\n                            value: parseFloat(range.val()),\r\n                            step: parseFloat(range.attr(\\\"step\\\")),\r\n                            slide: function(evt, ui) {\r\n                                range.val(ui.value);\r\n                            },\r\n                            change: function(evt, ui) {\r\n                                range.val(ui.value);\r\n                            }\r\n                            })\r\n                        ).\r\n                        hide();\r\n                    });\r\n                    });\r\n                }\r\n        </script>\";\r\n\r\nreturn \"$var1 $var2\";\r\n}', 'function pro_modernizr_form($atts)\r\n{\r\n	\r\n$var1 = escape_js(gps(\'param\'));\r\n$var2 = escape_js(gps(\'param\'));\r\n        $var1 = \"\r\n        <script type=\\\"text/javascript\\\">\r\n            var placeholder = $( \\\".zemText, .zemTextarea\\\" );\r\n                if (!Modernizr.input.placeholder) {\r\n                    var placeholder = $( \\\".zemText, .zemTextarea\\\" ).each(function() {\r\n                        if ($(this).val() == \'\') {\r\n                        $(this).val($(this).attr(\'placeholder\'));\r\n                        $(this).addClass(\'placeholder\');\r\n                    }\r\n                    $(this).focus(function() {\r\n                        if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                        $(this).val(\'\');\r\n                        $(this).removeClass(\'placeholder\');\r\n                    }\r\n                    }).blur(function() {\r\n                        if($(this).val() == \'\') {\r\n                         $(this).val($(this).attr(\'placeholder\'));\r\n                        $(this).addClass(\'placeholder\');\r\n                    }\r\n                    });\r\n                    });\r\n                    $(\'[placeholder]\').parents(\'form\').submit(function() {\r\n                        $(this).find(\'[placeholder]\').each(function() {\r\n                        if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                            $(this).val(\'\');\r\n                        }\r\n                    });\r\n                    });\r\n                    window.onbeforeunload = function() {\r\n                        $(\'[placeholder]\').each(function() {\r\n                            if ($(this).val() == $(this).attr(\'placeholder\') && $(this).hasClass(\'placeholder\')) {\r\n                            $(this).val(\'\');\r\n                            }\r\n                    });\r\n                    };   \r\n}\r\n        </script>\";\r\n        $var2 = \"\r\n        <script type=\\\"text/javascript\\\">\r\n                if (!Modernizr.input.date) {\r\n                    $(function() {\r\n		        var date = $( \\\"#date\\\" ).datepicker({\r\n                        dateFormat: \'yy-mm-dd\'\r\n                        });\r\n                 });\r\n                } \r\n                if (!Modernizr.input.number) {\r\n                    $(function() {\r\n                        var number = $( \\\"#number\\\" ).spinner();\r\n                    });\r\n                }\r\n                if (!Modernizr.input.range) {\r\n                    $(function() {\r\n                        var range = $(\\\"#range\\\");\r\n		        $(\\\"#range\\\").each(function(){\r\n                            var range = $(this);\r\n            \r\n                        var sliderDiv = $(\\\"<div/>\\\");\r\n                        sliderDiv.width(range.width());\r\n            \r\n                        range.after(\r\n                            sliderDiv.slider({\r\n                            min: parseFloat(range.attr(\\\"min\\\")),\r\n                            max: parseFloat(range.attr(\\\"max\\\")),\r\n                            value: parseFloat(range.val()),\r\n                            step: parseFloat(range.attr(\\\"step\\\")),\r\n                            slide: function(evt, ui) {\r\n                                range.val(ui.value);\r\n                            },\r\n                            change: function(evt, ui) {\r\n                                range.val(ui.value);\r\n                            }\r\n                            })\r\n                        ).\r\n                        hide();\r\n                    });\r\n                    });\r\n                }\r\n        </script>\";\r\n\r\nreturn \"$var1 $var2\";\r\n}', '90e9a7066ef53a6a9f63114c2c041ab0', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cnk_versioning', 0, 'Christian Nowak (mod by Maniqui)', 'http://www.cnowak.de', '0.1.7 (mod)', 'Autoload Templates', '', 'if (!function_exists(\'file_put_contents\'))\n{\n    function file_put_contents($filename, $data)\n    {\n        $f = @fopen($filename, \'w\');\n\n        if (!$f)\n        {\n            return false;\n        }\n        else\n        {\n            $bytes = fwrite($f, $data);\n            fclose($f);\n            return $bytes;\n        }\n    }\n}\n\nglobal $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n$CNK_VER_OUTPUT_PATH = \'admin/\'; //e.g. \'textpattern/_templates/versioning/\'\n$CNK_VER_EXT = \'txp\';\n$CNK_VER_EXT_CSS = \'css\';\n\n// This names will be ignored on exporting/importing\n$CNK_VER_IGNORED_NAMES = array(\'adi_variables\');\n\n/*\n	DO NOT EDIT BELOW THIS LINE!\n*/\n\n$CNK_VER_OUTPUT_PATH = trim($CNK_VER_OUTPUT_PATH, \'/\').($CNK_VER_OUTPUT_PATH?\'/\':\'\');\n\nif(@txpinterface == \'admin\')\n{\n	add_privs(\'cnk_versioning\',\'1,2\');\n	register_tab(\'presentation\', \'cnk_versioning\', \"Versioning\");\n	register_callback(\'cnk_ver_handler\', \'cnk_versioning\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'page\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'form\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'css\');\n}\nelse if (@txpinterface == \'public\')\n{\n	register_callback(\'cnk_ver_textpattern\', \'textpattern\');\n}\n\nfunction cnk_ver_textpattern()\n{\n	global $production_status, $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS;\n\n	if ($production_status != \'live\')\n	{\n		$error = false;\n\n		// read all files\n		$forms = glob($CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT);\n		$pages = glob($CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT);\n		$css = glob($CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS);\n\n		if ($forms !== false) $error = !cnk_ver_push_forms($forms);\n		if ($pages !== false) $error = !cnk_ver_push_pages($pages);\n		if ($css !== false) $error = !cnk_ver_push_css($css);\n\n		if ($error) echo \'Errors while synchronising database and files\';\n	}\n}\n\nfunction cnk_ver_manual_update()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS;\n\n	pagetop(\'Versioning\', \'\');\n\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'Updated!\';\n\n	echo \'</div>\';\n\n		$error = false;\n\n		// read all files\n		$forms = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT);\n		$pages = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT);\n		$css = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS);\n\n		if ($forms !== false) $error = !cnk_ver_push_forms($forms);\n		if ($pages !== false) $error = !cnk_ver_push_pages($pages);\n		if ($css !== false) $error = !cnk_ver_push_css($css);\n\n		if ($error) echo \'Errors while synchronising database and files\';\n\n}\n\nfunction cnk_ver_push_forms($form_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$forms = array();\n\n	$rs = safe_rows_start(\'name,type,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_form\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$forms[$CNK_VER_OUTPUT_PATH.\'forms/\'.$r[\'name\'].\'.\'.$r[\'type\'].\'.\'.$CNK_VER_EXT] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($form_files); $i++)\n	{\n		$cols = explode(\'.\', basename($form_files[$i], \'.\'.$CNK_VER_EXT));\n		$name = $cols[0];\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Form updated from file: $name<br />\";\n			if (isset($forms[$form_files[$i]]) && $forms[$form_files[$i]] < filemtime($form_files[$i]))\n			{\n				cnk_ver_update_form($form_files[$i]);\n			}\n			else if (!isset($forms[$form_files[$i]]))\n			{\n				cnk_ver_update_form($form_files[$i], true);\n			}\n		} else {\n			// echo \"Form NOT updated from file: $name<br />\";\n		}\n		$forms[$form_files[$i]] = \'processed\';\n	}\n\n	// delete removed forms\n	foreach ($forms as $key => $value)\n	{\n		if ($value != \'processed\')\n		{\n			// echo \"Form: $key<br />\";\n			$cols = explode(\'.\', basename($key, \'.\'.$CNK_VER_EXT));\n			$name = $cols[0];\n			$type = $cols[1];\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"Form deleted: $name<br />\";\n				safe_delete(\'txp_form\', \"name = \'$name\' AND type = \'$type\'\");\n			} else {\n				// echo \"Form NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_form($filename, $create = false)\n{\n	global $CNK_VER_EXT;\n\n	$cols = explode(\'.\', basename($filename, \'.\'.$CNK_VER_EXT));\n\n	$name = $cols[0];\n	$type = $cols[1];\n	$form = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_form\', \"name = \'$name\', type = \'$type\', form = \'$form\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_form\', \"form = \'$form\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\' AND type = \'$type\'\");\n	}\n}\n\nfunction cnk_ver_push_pages($page_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$pages = array();\n\n	$rs = safe_rows_start(\'name,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_page\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$pages[$CNK_VER_OUTPUT_PATH.\'pages/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($page_files); $i++)\n	{\n		$name = basename($page_files[$i], \'.\'.$CNK_VER_EXT);\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Page updated from file: $name<br />\";\n			if (isset($pages[$page_files[$i]]) && $pages[$page_files[$i]] < filemtime($page_files[$i]))\n			{\n				cnk_ver_update_page($page_files[$i]);\n			}\n			else if (!isset($pages[$page_files[$i]]))\n			{\n				cnk_ver_update_page($page_files[$i], true);\n			}\n		} else {\n			// echo \"Page NOT updated from file: $name<br />\";\n		}\n		$pages[$page_files[$i]] = \'processed\';\n	}\n\n	// delete removed pages\n	foreach ($pages as $key => $value)\n	{\n		// echo \"Page: $key<br />\";\n		if ($value != \'processed\')\n		{\n			$name = basename($key, \'.\'.$CNK_VER_EXT);\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"Page deleted: $name<br />\";\n				safe_delete(\'txp_page\', \"name = \'$name\'\");\n			} else {\n				// echo \"Page NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_page($filename, $create = false)\n{\n	global $CNK_VER_EXT;\n\n	$name = basename($filename, \'.\'.$CNK_VER_EXT);\n	$user_html = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_page\', \"name = \'$name\', user_html = \'$user_html\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_page\', \"user_html = \'$user_html\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\'\");\n	}\n}\n\nfunction cnk_ver_push_css($css_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	$css = array();\n\n	$rs = safe_rows_start(\'name,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_css\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$css[$CNK_VER_OUTPUT_PATH.\'css/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT_CSS] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($css_files); $i++)\n	{\n		$name = basename($css_files[$i], \'.\'.$CNK_VER_EXT);\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"CSS updated from file: $name<br />\";\n			if (isset($css[$css_files[$i]]) && $css[$css_files[$i]] < filemtime($css_files[$i]))\n			{\n				cnk_ver_update_css($css_files[$i]);\n			}\n			else if (!isset($css[$css_files[$i]]))\n			{\n				cnk_ver_update_css($css_files[$i], true);\n			}\n		} else {\n			// echo \"CSS NOT updated from file: $name<br />\";\n		}\n		$css[$css_files[$i]] = \'processed\';\n	}\n\n	// delete removed css files\n	foreach ($css as $key => $value)\n	{\n		// echo \"CSS: $key<br />\";\n		if ($value != \'processed\')\n		{\n			$name = basename($key, \'.\'.$CNK_VER_EXT_CSS);\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"CSS deleted: $name<br />\";\n				safe_delete(\'txp_css\', \"name = \'$name\'\");\n			} else {\n				// echo \"CSS NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_css($filename, $create = false)\n{\n	global $CNK_VER_EXT_CSS;\n\n	$name = basename($filename, \'.\'.$CNK_VER_EXT_CSS);\n	$css = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_css\', \"name = \'$name\', css = \'$css\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_css\', \"css = \'$css\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\'\");\n	}\n}\n\nfunction cnk_ver_handler($event, $step)\n{\n	if(!$step or !in_array($step, array(\'cnk_ver_menu\',\n										\'cnk_ver_pull_all\',\n										\'cnk_ver_install\',\n										\'cnk_ver_deinstall\',\n										\'cnk_ver_manual_update\')))\n	{\n		cnk_ver_menu();\n	}\n	else\n	{\n		$step();\n	}\n}\n\nfunction cnk_ver_disable_online_editing($event, $step)\n{\n	// clear ob\n	ob_end_clean();\n\n	// return error message\n\n	pagetop(\'Versioning\');\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'While cnk_versioning is enabled, this function is disabled.\';\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_menu($message = \'\')\n{\n	pagetop(\'Versioning\', $message);\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_pull_all\">Write pages & forms to files</a></li>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_manual_update\">Manually trigger Form, Page &amp; CSS update</li>\';\n\n	echo \'</ul>\';\n\n	echo \'<ul style=\"margin-top: 100px\">\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_install\">Install</a></li>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_deinstall\">Deinstall</a></li>\';\n\n	echo \'</ul>\';\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_pull_all()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	pagetop(\'Write to files\', \'\');\n\n	echo \'<div style=\"margin: auto; text-align: center\">\';\n\n	if (gps(\'do_pull\') || (glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT) === false && glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT) === false && glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS) === false))\n	{\n		$error = false;\n\n		// test if folders exist and have write permissions\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/forms/\" is not writable.<br /><br />\';\n		}\n\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/pages/\" is not writable.<br /><br />\';\n		}\n\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/css/\" is not writable.\';\n		}\n\n		if (!$error)\n		{\n				echo \"Ignoring: \".implode(\', \', $CNK_VER_IGNORED_NAMES).\'<br />\';\n				if (cnk_ver_pull_forms())\n				{\n					echo \'Forms were successfully written to the \"/forms/\" directory.<br /><br />\';\n				}\n				else\n				{\n					echo \'There was an error while processing forms.<br /><br />\';\n				}\n\n				if (cnk_ver_pull_pages())\n				{\n					echo \'Pages were successfully written to the \"/pages/\" directory.<br /><br />\';\n				}\n				else\n				{\n					echo \'There was an error while processing pages.<br /><br />\';\n				}\n\n				if (cnk_ver_pull_css())\n				{\n					echo \'Styles were successfully written to the \"/css/\" directory.\';\n				}\n				else\n				{\n					echo \'There was an error while processing css.\';\n				}\n		}\n\n		echo \'<br /><br /><a href=\"?event=cnk_versioning\">Back to menu...</a>\';\n	}\n	else\n	{\n		echo \'There are already some files in the pages, forms or css directory, which will be overriden. Do you want to continue?<br /><br />\'.\n\n		\'<a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_pull_all\'.a.\'do_pull=1\">Yes, overwrite existing files...</a><br /><br />\'.\n\n		\'<a href=\"?event=cnk_versioning\">No, bring me back to the menu...</a>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_pull_forms()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, type, form\', \'txp_form\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Form written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/\'.$r[\'name\'].\'.\'.$r[\'type\'].\'.\'.$CNK_VER_EXT, $r[\'form\']) === false) return false;\n		} else {\n			// echo \"Form NOT written to file: {$r[\'name\']}<br />\";\n		}\n\n	}\n\n	safe_update(\'txp_form\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_pull_pages()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, user_html\', \'txp_page\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Page written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT, $r[\'user_html\']) === false) return false;\n		} else {\n			// echo \"Page NOT written to file: {$r[\'name\']}<br />\";\n		}\n	}\n\n	safe_update(\'txp_page\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_pull_css()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, css\', \'txp_css\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"CSS written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT_CSS, $r[\'css\']) === false) return false;\n		} else {\n			// echo \"CSS NOT written to file: {$r[\'name\']}<br />\";\n		}\n	}\n\n	safe_update(\'txp_css\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_install()\n{\n	pagetop(\'Versioning Plugin Installation\', \'\');\n\n	echo \'<div style=\"margin:auto; text-align:center\">\';\n\n	if (cnk_ver_do_install())\n	{\n		echo \'<p>Installation was successful</p>\';\n	}\n	else\n	{\n		echo \'<p>Installation aborted</p>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_do_install()\n{\n	$sql = \"ALTER TABLE \".safe_pfx(\'txp_form\').\" ADD `file_mod_time` DATETIME NULL;ALTER TABLE \".safe_pfx(\'txp_page\').\" ADD `file_mod_time` DATETIME NULL;ALTER TABLE \".safe_pfx(\'txp_css\').\" ADD `file_mod_time` DATETIME NULL\";\n\n	if (!cnk_ver_batch_query($sql))\n	{\n		return false;\n	}\n	else\n	{\n		return true;\n	}\n}\n\nfunction cnk_ver_deinstall()\n{\n	pagetop(\'Versioning Plugin Deinstallation\', \'\');\n\n	echo \'<div style=\"margin:auto; text-align:center\">\';\n\n	if (gps(\'do_deinstall\'))\n	{\n		if (cnk_ver_do_deinstall())\n		{\n			echo \'<p>Deinstallation was successful</p>\';\n		}\n		else\n		{\n			echo \'<p>Deinstallation aborted</p>\';\n		}\n	}\n	else\n	{\n		echo \'<a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_deinstall\'.a.\'do_deinstall=1\">Yes, I really want to deinstall</a>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_do_deinstall()\n{\n	$sql = \"ALTER TABLE \".safe_pfx(\'txp_form\').\" DROP `file_mod_time`;ALTER TABLE \".safe_pfx(\'txp_page\').\" DROP `file_mod_time`;ALTER TABLE \".safe_pfx(\'txp_css\').\" DROP `file_mod_time`;\";\n\n	if (!cnk_ver_batch_query($sql))\n	{\n		return false;\n	}\n	else\n	{\n		return true;\n	}\n}\n\nfunction cnk_ver_batch_query ($p_query, $p_transaction_safe = true)\n{\n	if ($p_transaction_safe)\n	{\n		$p_query = \'START TRANSACTION;\' . $p_query . \'; COMMIT;\';\n	}\n\n	$query_split = preg_split (\"/[;]+/\", $p_query);\n\n	foreach ($query_split as $command_line)\n	{\n		$command_line = trim($command_line);\n\n		if ($command_line != \'\')\n		{\n			$query_result = safe_query($command_line);\n\n			if ($query_result === false)\n			{\n				break;\n			}\n		}\n	}\n\n	return $query_result;\n}', 'if (!function_exists(\'file_put_contents\'))\n{\n    function file_put_contents($filename, $data)\n    {\n        $f = @fopen($filename, \'w\');\n\n        if (!$f)\n        {\n            return false;\n        }\n        else\n        {\n            $bytes = fwrite($f, $data);\n            fclose($f);\n            return $bytes;\n        }\n    }\n}\n\nglobal $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n$CNK_VER_OUTPUT_PATH = \'admin/\'; //e.g. \'textpattern/_templates/versioning/\'\n$CNK_VER_EXT = \'txp\';\n$CNK_VER_EXT_CSS = \'css\';\n\n// This names will be ignored on exporting/importing\n$CNK_VER_IGNORED_NAMES = array(\'adi_variables\');\n\n/*\n	DO NOT EDIT BELOW THIS LINE!\n*/\n\n$CNK_VER_OUTPUT_PATH = trim($CNK_VER_OUTPUT_PATH, \'/\').($CNK_VER_OUTPUT_PATH?\'/\':\'\');\n\nif(@txpinterface == \'admin\')\n{\n	add_privs(\'cnk_versioning\',\'1,2\');\n	register_tab(\'presentation\', \'cnk_versioning\', \"Versioning\");\n	register_callback(\'cnk_ver_handler\', \'cnk_versioning\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'page\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'form\');\n	register_callback(\'cnk_ver_disable_online_editing\', \'css\');\n}\nelse if (@txpinterface == \'public\')\n{\n	register_callback(\'cnk_ver_textpattern\', \'textpattern\');\n}\n\nfunction cnk_ver_textpattern()\n{\n	global $production_status, $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS;\n\n	if ($production_status != \'live\')\n	{\n		$error = false;\n\n		// read all files\n		$forms = glob($CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT);\n		$pages = glob($CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT);\n		$css = glob($CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS);\n\n		if ($forms !== false) $error = !cnk_ver_push_forms($forms);\n		if ($pages !== false) $error = !cnk_ver_push_pages($pages);\n		if ($css !== false) $error = !cnk_ver_push_css($css);\n\n		if ($error) echo \'Errors while synchronising database and files\';\n	}\n}\n\nfunction cnk_ver_manual_update()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS;\n\n	pagetop(\'Versioning\', \'\');\n\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'Updated!\';\n\n	echo \'</div>\';\n\n		$error = false;\n\n		// read all files\n		$forms = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT);\n		$pages = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT);\n		$css = glob(\'../\' . $CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS);\n\n		if ($forms !== false) $error = !cnk_ver_push_forms($forms);\n		if ($pages !== false) $error = !cnk_ver_push_pages($pages);\n		if ($css !== false) $error = !cnk_ver_push_css($css);\n\n		if ($error) echo \'Errors while synchronising database and files\';\n\n}\n\nfunction cnk_ver_push_forms($form_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$forms = array();\n\n	$rs = safe_rows_start(\'name,type,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_form\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$forms[$CNK_VER_OUTPUT_PATH.\'forms/\'.$r[\'name\'].\'.\'.$r[\'type\'].\'.\'.$CNK_VER_EXT] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($form_files); $i++)\n	{\n		$cols = explode(\'.\', basename($form_files[$i], \'.\'.$CNK_VER_EXT));\n		$name = $cols[0];\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Form updated from file: $name<br />\";\n			if (isset($forms[$form_files[$i]]) && $forms[$form_files[$i]] < filemtime($form_files[$i]))\n			{\n				cnk_ver_update_form($form_files[$i]);\n			}\n			else if (!isset($forms[$form_files[$i]]))\n			{\n				cnk_ver_update_form($form_files[$i], true);\n			}\n		} else {\n			// echo \"Form NOT updated from file: $name<br />\";\n		}\n		$forms[$form_files[$i]] = \'processed\';\n	}\n\n	// delete removed forms\n	foreach ($forms as $key => $value)\n	{\n		if ($value != \'processed\')\n		{\n			// echo \"Form: $key<br />\";\n			$cols = explode(\'.\', basename($key, \'.\'.$CNK_VER_EXT));\n			$name = $cols[0];\n			$type = $cols[1];\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"Form deleted: $name<br />\";\n				safe_delete(\'txp_form\', \"name = \'$name\' AND type = \'$type\'\");\n			} else {\n				// echo \"Form NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_form($filename, $create = false)\n{\n	global $CNK_VER_EXT;\n\n	$cols = explode(\'.\', basename($filename, \'.\'.$CNK_VER_EXT));\n\n	$name = $cols[0];\n	$type = $cols[1];\n	$form = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_form\', \"name = \'$name\', type = \'$type\', form = \'$form\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_form\', \"form = \'$form\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\' AND type = \'$type\'\");\n	}\n}\n\nfunction cnk_ver_push_pages($page_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$pages = array();\n\n	$rs = safe_rows_start(\'name,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_page\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$pages[$CNK_VER_OUTPUT_PATH.\'pages/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($page_files); $i++)\n	{\n		$name = basename($page_files[$i], \'.\'.$CNK_VER_EXT);\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Page updated from file: $name<br />\";\n			if (isset($pages[$page_files[$i]]) && $pages[$page_files[$i]] < filemtime($page_files[$i]))\n			{\n				cnk_ver_update_page($page_files[$i]);\n			}\n			else if (!isset($pages[$page_files[$i]]))\n			{\n				cnk_ver_update_page($page_files[$i], true);\n			}\n		} else {\n			// echo \"Page NOT updated from file: $name<br />\";\n		}\n		$pages[$page_files[$i]] = \'processed\';\n	}\n\n	// delete removed pages\n	foreach ($pages as $key => $value)\n	{\n		// echo \"Page: $key<br />\";\n		if ($value != \'processed\')\n		{\n			$name = basename($key, \'.\'.$CNK_VER_EXT);\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"Page deleted: $name<br />\";\n				safe_delete(\'txp_page\', \"name = \'$name\'\");\n			} else {\n				// echo \"Page NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_page($filename, $create = false)\n{\n	global $CNK_VER_EXT;\n\n	$name = basename($filename, \'.\'.$CNK_VER_EXT);\n	$user_html = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_page\', \"name = \'$name\', user_html = \'$user_html\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_page\', \"user_html = \'$user_html\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\'\");\n	}\n}\n\nfunction cnk_ver_push_css($css_files)\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	$css = array();\n\n	$rs = safe_rows_start(\'name,IFNULL(unix_timestamp(file_mod_time), 0) as mod_time\', \'txp_css\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		$css[$CNK_VER_OUTPUT_PATH.\'css/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT_CSS] = $r[\'mod_time\'];\n	}\n\n	for ($i=0; $i < count($css_files); $i++)\n	{\n		$name = basename($css_files[$i], \'.\'.$CNK_VER_EXT);\n		if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n			// echo \"CSS updated from file: $name<br />\";\n			if (isset($css[$css_files[$i]]) && $css[$css_files[$i]] < filemtime($css_files[$i]))\n			{\n				cnk_ver_update_css($css_files[$i]);\n			}\n			else if (!isset($css[$css_files[$i]]))\n			{\n				cnk_ver_update_css($css_files[$i], true);\n			}\n		} else {\n			// echo \"CSS NOT updated from file: $name<br />\";\n		}\n		$css[$css_files[$i]] = \'processed\';\n	}\n\n	// delete removed css files\n	foreach ($css as $key => $value)\n	{\n		// echo \"CSS: $key<br />\";\n		if ($value != \'processed\')\n		{\n			$name = basename($key, \'.\'.$CNK_VER_EXT_CSS);\n\n			if (!in_array($name, $CNK_VER_IGNORED_NAMES)) {\n				// echo \"CSS deleted: $name<br />\";\n				safe_delete(\'txp_css\', \"name = \'$name\'\");\n			} else {\n				// echo \"CSS NOT deleted: $name<br />\";\n			}\n		}\n	}\n\n	return true;\n}\n\nfunction cnk_ver_update_css($filename, $create = false)\n{\n	global $CNK_VER_EXT_CSS;\n\n	$name = basename($filename, \'.\'.$CNK_VER_EXT_CSS);\n	$css = doSlash(file_get_contents($filename));\n\n	if ($create)\n	{\n		safe_insert(\'txp_css\', \"name = \'$name\', css = \'$css\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\");\n	}\n	else\n	{\n		safe_update(\'txp_css\', \"css = \'$css\', file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \"name = \'$name\'\");\n	}\n}\n\nfunction cnk_ver_handler($event, $step)\n{\n	if(!$step or !in_array($step, array(\'cnk_ver_menu\',\n										\'cnk_ver_pull_all\',\n										\'cnk_ver_install\',\n										\'cnk_ver_deinstall\',\n										\'cnk_ver_manual_update\')))\n	{\n		cnk_ver_menu();\n	}\n	else\n	{\n		$step();\n	}\n}\n\nfunction cnk_ver_disable_online_editing($event, $step)\n{\n	// clear ob\n	ob_end_clean();\n\n	// return error message\n\n	pagetop(\'Versioning\');\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'While cnk_versioning is enabled, this function is disabled.\';\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_menu($message = \'\')\n{\n	pagetop(\'Versioning\', $message);\n\n	echo \'<div style=\"margin: auto; text-align: center\"><ul>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_pull_all\">Write pages & forms to files</a></li>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_manual_update\">Manually trigger Form, Page &amp; CSS update</li>\';\n\n	echo \'</ul>\';\n\n	echo \'<ul style=\"margin-top: 100px\">\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_install\">Install</a></li>\';\n\n	echo \'<li><a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_deinstall\">Deinstall</a></li>\';\n\n	echo \'</ul>\';\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_pull_all()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	pagetop(\'Write to files\', \'\');\n\n	echo \'<div style=\"margin: auto; text-align: center\">\';\n\n	if (gps(\'do_pull\') || (glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/*.\'.$CNK_VER_EXT) === false && glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/*.\'.$CNK_VER_EXT) === false && glob(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/*.\'.$CNK_VER_EXT_CSS) === false))\n	{\n		$error = false;\n\n		// test if folders exist and have write permissions\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/forms/\" is not writable.<br /><br />\';\n		}\n\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/pages/\" is not writable.<br /><br />\';\n		}\n\n		if (@is_writable(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/\') === false)\n		{\n			$error = true;\n			echo \'Folder \"/css/\" is not writable.\';\n		}\n\n		if (!$error)\n		{\n				echo \"Ignoring: \".implode(\', \', $CNK_VER_IGNORED_NAMES).\'<br />\';\n				if (cnk_ver_pull_forms())\n				{\n					echo \'Forms were successfully written to the \"/forms/\" directory.<br /><br />\';\n				}\n				else\n				{\n					echo \'There was an error while processing forms.<br /><br />\';\n				}\n\n				if (cnk_ver_pull_pages())\n				{\n					echo \'Pages were successfully written to the \"/pages/\" directory.<br /><br />\';\n				}\n				else\n				{\n					echo \'There was an error while processing pages.<br /><br />\';\n				}\n\n				if (cnk_ver_pull_css())\n				{\n					echo \'Styles were successfully written to the \"/css/\" directory.\';\n				}\n				else\n				{\n					echo \'There was an error while processing css.\';\n				}\n		}\n\n		echo \'<br /><br /><a href=\"?event=cnk_versioning\">Back to menu...</a>\';\n	}\n	else\n	{\n		echo \'There are already some files in the pages, forms or css directory, which will be overriden. Do you want to continue?<br /><br />\'.\n\n		\'<a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_pull_all\'.a.\'do_pull=1\">Yes, overwrite existing files...</a><br /><br />\'.\n\n		\'<a href=\"?event=cnk_versioning\">No, bring me back to the menu...</a>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_pull_forms()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, type, form\', \'txp_form\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Form written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'forms/\'.$r[\'name\'].\'.\'.$r[\'type\'].\'.\'.$CNK_VER_EXT, $r[\'form\']) === false) return false;\n		} else {\n			// echo \"Form NOT written to file: {$r[\'name\']}<br />\";\n		}\n\n	}\n\n	safe_update(\'txp_form\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_pull_pages()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, user_html\', \'txp_page\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"Page written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'pages/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT, $r[\'user_html\']) === false) return false;\n		} else {\n			// echo \"Page NOT written to file: {$r[\'name\']}<br />\";\n		}\n	}\n\n	safe_update(\'txp_page\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_pull_css()\n{\n	global $CNK_VER_OUTPUT_PATH, $CNK_VER_EXT_CSS, $CNK_VER_IGNORED_NAMES;\n\n	$rs = safe_rows_start(\'name, css\', \'txp_css\', \'1=1\');\n\n	while ($rs && $r = nextRow($rs))\n	{\n		if (!in_array($r[\'name\'], $CNK_VER_IGNORED_NAMES)) {\n			// echo \"CSS written to file: {$r[\'name\']}<br />\";\n			if (@file_put_contents(\'../\'.$CNK_VER_OUTPUT_PATH.\'css/\'.$r[\'name\'].\'.\'.$CNK_VER_EXT_CSS, $r[\'css\']) === false) return false;\n		} else {\n			// echo \"CSS NOT written to file: {$r[\'name\']}<br />\";\n		}\n	}\n\n	safe_update(\'txp_css\', \"file_mod_time = FROM_UNIXTIME(\'\".time().\"\')\", \'1=1\');\n\n	return true;\n}\n\nfunction cnk_ver_install()\n{\n	pagetop(\'Versioning Plugin Installation\', \'\');\n\n	echo \'<div style=\"margin:auto; text-align:center\">\';\n\n	if (cnk_ver_do_install())\n	{\n		echo \'<p>Installation was successful</p>\';\n	}\n	else\n	{\n		echo \'<p>Installation aborted</p>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_do_install()\n{\n	$sql = \"ALTER TABLE \".safe_pfx(\'txp_form\').\" ADD `file_mod_time` DATETIME NULL;ALTER TABLE \".safe_pfx(\'txp_page\').\" ADD `file_mod_time` DATETIME NULL;ALTER TABLE \".safe_pfx(\'txp_css\').\" ADD `file_mod_time` DATETIME NULL\";\n\n	if (!cnk_ver_batch_query($sql))\n	{\n		return false;\n	}\n	else\n	{\n		return true;\n	}\n}\n\nfunction cnk_ver_deinstall()\n{\n	pagetop(\'Versioning Plugin Deinstallation\', \'\');\n\n	echo \'<div style=\"margin:auto; text-align:center\">\';\n\n	if (gps(\'do_deinstall\'))\n	{\n		if (cnk_ver_do_deinstall())\n		{\n			echo \'<p>Deinstallation was successful</p>\';\n		}\n		else\n		{\n			echo \'<p>Deinstallation aborted</p>\';\n		}\n	}\n	else\n	{\n		echo \'<a href=\"?event=cnk_versioning\'.a.\'step=cnk_ver_deinstall\'.a.\'do_deinstall=1\">Yes, I really want to deinstall</a>\';\n	}\n\n	echo \'</div>\';\n}\n\nfunction cnk_ver_do_deinstall()\n{\n	$sql = \"ALTER TABLE \".safe_pfx(\'txp_form\').\" DROP `file_mod_time`;ALTER TABLE \".safe_pfx(\'txp_page\').\" DROP `file_mod_time`;ALTER TABLE \".safe_pfx(\'txp_css\').\" DROP `file_mod_time`;\";\n\n	if (!cnk_ver_batch_query($sql))\n	{\n		return false;\n	}\n	else\n	{\n		return true;\n	}\n}\n\nfunction cnk_ver_batch_query ($p_query, $p_transaction_safe = true)\n{\n	if ($p_transaction_safe)\n	{\n		$p_query = \'START TRANSACTION;\' . $p_query . \'; COMMIT;\';\n	}\n\n	$query_split = preg_split (\"/[;]+/\", $p_query);\n\n	foreach ($query_split as $command_line)\n	{\n		$command_line = trim($command_line);\n\n		if ($command_line != \'\')\n		{\n			$query_result = safe_query($command_line);\n\n			if ($query_result === false)\n			{\n				break;\n			}\n		}\n	}\n\n	return $query_result;\n}', '3b52d7ca83f62f82dab5ee25ca5fed3f', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('jbx_multiple_image_upload', 0, 'Jonas Bach', 'https://github.com/jbach', '0.5.5', 'Multiple Image Upload', '<h1>jbx_multiple_upload</h1>\n\n<p>Allows you to upload multiple images simultaneously. See Extensions tab for configuration.</p>\n\n<p>This plugin is a modification of the fpx_image_import plugin.</p>\n\n<h2>Usage</h2>\n\n<p>Just activate!</p>\n\n<h2>Credits</h2>\n\n<ul>\n	<li><a href=\"http://dirksteins.de/\" rel=\"nofollow\">Dirk Steins</a> for fpx_image_import</li>\n	<li><a href=\"http://jmdeldin.com/\" rel=\"nofollow\">Jon-Michael Deldin</a> for inspiration / functions</li>\n</ul>', '\n/**\n * Multiple Image Upload Plugin\n * @author Jonas Bach <hallo@jonasbach.de>\n */\nclass jbx_MIU{\n	/**\n	 * Stores an instance\n	 * @var jbx_MIU\n	 */\n	protected static $instance;\n\n	/**\n	 * Slug of this plugin\n	 * @var string\n	 */\n	protected static $slug = \'jbx_multiple_image_upload\';\n\n	/**\n	 * Plugin prefix\n	 * @var string\n	 */\n	protected static $prefix = \'jbx_\';\n\n	/**\n	 * All available preferences for this plugin\n	 * @var array\n	 */\n	protected static $preferences = array(\n		//\'importinfo\'=> array(\'label\'=>\'Import additional info\', \'descr\'=>\'Import meta info into caption.\', \'type\'=>2, \'default\'=>\'none\'),\n	);\n\n	/**\n	 * external assets\n	 * @var array\n	 */\n	protected static $assets = array(\n		\'jquery.uploadify.min.js\' => \' \',\n		\'uploadify.swf\' => \' \',\n		\'jbx_multiple_image_upload.js\' => \'eJyNV0tv4zYQPtu/gnUDUEoUpdtbkzgFmuyhQPfQZnsqioCWKJuJTKkkZSdd+L93hkO9/EiTg8PHzDfDeSsqGp05VenoLGFbpfNqm7C8ypq11C5hz4vXp40wSixKaRPW6FwWSss8Zt+mE1VE7q2WVTEmY/P5nPGOlMdAOjHSNUbfTCe76QQo2dlamJemZnN2FvHbXG1YVgpr5zOE2hpR19LM7m5XP95xdjHGT60zSi9t2tRlJfKndVM6VZcS6PjtFXDclmIhy3cYM+GenHLEwm6tLGXmhgoAgVxW5g00uKJbWBAqgQ+Jtdx2DF712R0/KdlIoPQsXoELEK903TiGlpzPnHx1s1PgM3bVq7FnsmCL3nJD1EKVcsa0WId1oJ4dApAIwL67XTTOVTog0GbEYJvFWjlWN4tS2dU7bw6qBbwL8BEtP+ZdC5uhb71yPL6ZTidXV0zkOQvmUdJSaMEeYhDoqxoDm21E2cAT0Jd0crd30zvlQ57zTwhIHCL6LJUiW0Vjtl6phHUpphJUzueDV/LiUEuQD1cp+grkeH1wH6J1LHgXo3RKpBRyLY94OoxeHqcrty4jlIWkZB4jIMnXv+Zgok6xQYoyPggn9E70RbhVWpRVZWhJAFHMztlP/i9G8B16BPGVVu5Pz66KtwMhPvedMEvpfO67lfK60QUk1rwtDWlWVlpG3tPoaqWtNI7RJRwBcUpnv8iiMjIKsMTg4UKIzhF4aCA6573ccfYcMozvDxhPMYADhHMm4irnSWf4KO75wTWHzL3/OrpQBk5reFCEDpnfZxrQUxvw7qFldyWNqQwm119/t26xKyC9YiuVS4bJ0eKhhzBywfE8Wwm9lDzZiwVsIJ4GYh/CybeNPs2ob3Qy/HrfDmlmbcRzZetSvAE+X5RV9kJPmexkaWWHgQoex8CbiFi6S68Sb4HgZzeIQwippg1wfCZt0u4s8kK53RZ8Mrneqyc1pBBUlW2ReCJigpg6QUj3RCsaV3nEQsDL6Awif/0gnOCMXTN6LH9CIFe9SM33Qf1pMiBrIIEOqPDQP5xkULn+Cq3pgLKtjgtTbTuVoMM8qn/lbwqTbJ9jdNszfIU28yBtdlKCWoslFNMRw+dXZ4GBn6dLVdyw8/S5XuK/Wi/5UPV77FpIqMWmVPolXBq5rjbyvlrD8OBgUBkZttKPvvFzsGsXtig4hGUoLinGZoieXctJ5e8zJgvf404oh+6rvF1+scuwevRPDfiUamnd2BXF02SC/ACHKHSwtsvrHsSf7Y6q8thkmfQm2FMmh9AJAiEZcce+wzT81GYfRryXQLu+gHzPLxAiVTkUuTz3Jo54lwOXnikk0Cm+UIp6ptpUSwOKXi4E8FJ2b1XuVjzhn+rXj8HhK9rWx9klwybm3xmYj1j20LRkW2SjfbCrrwW9dX9vZNMF0NC6/+BFGylURNOqKCK+8L2q0b5iQKS3TwLrB71KqZduxe7YD60LjkzInpaJErrfpa955CScF46nD4HjAEHz017rpGBu39gXzknQHUQIfFhqJJTaTEb8Z7mBL4S5T0s+Ms6oWlpoym25nPZZg52dZ6WCYj3oCsFiMq2NR3+QhYDhPqTXfvM5An+kFnMrnUMTYHvoqmXCvvGu812zvgsB0agrXrNRS4hDHByRE1o+AJzTeOoVzGE2dGoDcIxqkZ0Oiseg9RxBhL6GTgRIZyCaOsxgHSayDPi1E/g5UuGgPB2Gmz4SbQctuJ36Tgy9ACu3wmjYUV3xyu66gQ+0gdkVxpo/pMihGZ5FPb5vR2d7czHNaDammXk0K8ZhpN3F0TMklnn73w/S+OY/YyelwQ==\',\n		\'jbx_multiple_image_upload.css\' => \' \'\n	);\n\n	/**\n	 * Returns an instance\n	 * @return jbx_MIU instance\n	 */\n	static function get_instance(){\n		if ( null == self::$instance ) {\n			self::$instance = new jbx_MIU();\n		}\n\n		return self::$instance;\n	}\n\n	/**\n	 * Constructor\n	 */\n	public function __construct(){\n		if(@txpinterface === \'admin\'){\n			add_privs(self::$slug, \'1,2,3,4,5,6\');\n			//add_privs(\'plugin_prefs.\'.self::$slug, \'1\');\n			register_callback(array($this, \'handle_request\'), self::$slug, \'\', 1);\n			register_callback(array($this, \'inject_assets\'), \'admin_side\', \'head_end\');\n			//register_callback(array($this, \'render_prefs\'), \'plugin_prefs.\'.self::$slug);\n			register_callback(array($this, \'install\'), \'plugin_lifecycle.\'.self::$slug, \'installed\');\n			//register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.\'.self::$slug, \'deleted\');\n		}\n		register_callback(array($this, \'handle_public\'), \'textpattern\');\n	}\n\n	/**\n	 * Called on plugin installation\n	 */\n	public function install(){\n		$token = uniqid(get_pref(\'blog_uid\'), true);\n		set_pref($this->prefix(\'token\'), $token, self::$slug, 2);\n	}\n\n	/**\n	 * Called on plugin deletion\n	 */\n	public function uninstall(){\n\n	}\n\n	/**\n	 * Called on public side\n	 */\n	public function handle_public(){\n		if (gps(self::$slug)) {\n			// check for token\n			if (gps(\'_jbx_token\') != get_pref($this->prefix(\'token\'))) {\n				txp_die(\'\', 403);\n			}\n\n			$this->handle_upload();\n		}\n	}\n\n	/**\n	 * Inject scripts/styles in <head>\n	 */\n	public function inject_assets(){\n		global $event;\n		if($event === \'image\'){\n			echo \'<link type=\"text/css\" href=\"?event=\'.self::$slug.\'&step=css\" rel=\"stylesheet\" />\';\n			echo \'<script type=\"text/javascript\" src=\"?event=\'.self::$slug.\'&step=js\"></script>\';\n		}\n	}\n\n	/**\n	 * Entry point for requests\n	 * @param  string $event 	the event being called\n	 * @param  string $request  the step being called\n	 */\n	public function handle_request($event, $request){\n		$requests = array(\n			\'js\' => false,\n			\'css\' => false,\n			\'swf\' => false\n		);\n		if (!$request || !bouncer($request, $requests)) {\n			return;\n		}\n\n		switch ($request) {\n			default:\n				$this->{\'render_\'.$request}();\n				break;\n		}\n	}\n\n	public function handle_upload(){\n		// todo: handle meta\n		$meta = array(\'category\' => \'\', \'caption\' => \'\', \'alt\' => \'\');\n\n		// category\n		$meta[\'category\'] = $this->get_category(gps(\'category\'), gps(\'new_category\'));\n\n		// insert image, create thumbnail\n		$msg = $this->create_image($_FILES[\'Filedata\'], $meta);\n		\n		\n		if(is_array($msg)){\n			// success\n			die(\'1\');\n		}else{\n			// error\n			die($msg);\n		}\n	}\n\n	/**\n	 * Render JS assets\n	 * @todo: use render_asset\n	 */\n	public function render_js(){\n		global $file_max_upload_size, $siteurl, $txp_user;\n		$script = \'\';\n\n		// pass variables to JS\n		$script_variables = array(\n			\'slug\' => self::$slug,\n			\'paths\' => array(\n				\'swf\' => \'?event=\'.self::$slug.\'&step=swf\',\n				\'upload\' => $siteurl.\'/?\'.self::$slug.\'=upload\'\n			),\n			\'fileSizeLimit\' => $file_max_upload_size,\n			\'targets\' => \'.upload-form\', // todo: make hackable by callback\n			\'token\' => get_pref($this->prefix(\'token\')),\n			\'user\' => $txp_user,\n			\'categories\' => getTree(\'root\', \'image\'),\n			\'strings\' => array(\n				\'closewarning\' => gTxt(\'Upload in progress. Are you sure you want to leave the page anyway?\'),\n				\'errors\' => gTxt(\'<strong>Some errors were encountered.</strong> Check the errors above, then <a href=\"?event=image\">reload</a> the page to see your successfully imported images.\'),\n				\'create_new\' => gTxt(\'Create new…\'),\n				\'create_new_title\' => gTxt(\'New category name\'),\n				\'images\' => gTxt(\'Image files\'),\n				\'upload_multiple\' => gTxt(\'Upload Multiple Images\'),\n				\'upload_single\' => gTxt(\'Upload Single Image\'),\n				\'upload_button\' => gTxt(\'Upload\'),\n				\'cat_title\' => gTxt(\'Assign category\'),\n				\'browse\' => gTxt(\'Select files…\')\n			)\n		);\n\n		$script .= $this->get_asset(\'jquery.uploadify.min.js\');\n		$script .= \'var jbx_variables = \'.json_encode($script_variables).\';\';\n		$script .= $this->get_asset(\'jbx_multiple_image_upload.js\');\n		send_script_response($script);\n		die();\n	}\n\n	/**\n	 * Render swf\n	 */\n	public function render_swf(){\n		$this->render_asset(\'uploadify.swf\', \'application/x-shockwave-flash\');\n	}\n\n	/**\n	 * Render stylesheets\n	 */\n	public function render_css(){\n		$this->render_asset(\'jbx_multiple_image_upload.css\', \'text/css\');\n	}\n\n	/**\n	 * Generic asset renderer\n	 * @param  string $file file handle\n	 * @param  string $mime mimetype\n	 */\n	public function render_asset($file, $mime = \'text/plain\'){\n		$file = $this->get_asset($file);\n		ob_clean();\n		header(\'Content-Type: \'.$mime.\'; charset=utf-8\');\n		txp_status_header(\'200 OK\');\n		echo $file;\n		die();\n	}\n\n	/**\n	 * Render options page\n	 * @param  string $event the event being called\n	 * @param  string $step  the step being called\n	 */\n	public function render_prefs($event, $step){\n		if($step === \'update\'){\n			$this->update_prefs();\n		}\n\n		pageTop(gTxt(\'Multiple Image Upload\'));\n\n		// todo: show preflight/sanity errors if any\n		\n		// Generate Preferences Table\n		$out = hed(gTxt(\'Multiple Image Upload - Preferences\'), 1);\n		$out .= startTable($this->prefix(\'preferences\'), \'center\', 5);\n		foreach (self::$preferences as $key => $pref) {\n			$out .= $this->render_pref($key, $pref);\n		}\n		\n		// render save button\n		$out .= tr(tdcs(eInput(\'plugin_prefs.\'.self::$slug).sInput(\'update\').fInput(\'submit\', \'save\', gTxt(\'save_button\'), \'publish\'), 3, \'\', \'nolin\'));\n		\n		$out .= endtable();\n\n		echo form($out);\n	}\n\n	/**\n	 * Render single preference row\n	 * @param  array $pref preference array\n	 * @return string      <tr> containing the preference\n	 */\n	private function render_pref($id, $pref){\n		$value = $this->get_pref($id);\n		$id = $this->prefix($id);\n\n		// render label\n		$out = fLabelCell(gTxt($pref[\'label\']), \'\', $id);\n\n		// render field\n		switch($pref[\'type\']){\n			case 2:\n				$out .= td(selectInput($id, array(gTxt(\'None\') => \'\', \'EXIF\'=>\'exif\', \'IPTC\'=>\'iptc\'), $value));\n			break;\n\n			case 1:\n				$out .= td(yesnoRadio($id, $value));\n			break;\n\n			default:\n				$out .= fInputCell($id, $value, \'\', 20, \'\', $id);\n			break;\n		}\n\n		// render help\n		$out .= td(gTxt($pref[\'descr\']));\n\n		// render save\n		\n		return tr($out);\n	}\n\n	/**\n	 * Update preferences from submitted form\n	 */\n	private function update_prefs(){\n		foreach (self::$preferences as $key => $pref) {\n			$this->set_pref($key, gps($this->prefix($key)));\n		}\n		txp_die(\'\', \'302\', \'?event=plugin_prefs.\'.self::$slug);\n	}\n\n	/**\n	 * Get prefixed string\n	 * @param  string $value unprefixed string\n	 * @return string        prefixed string\n	 */\n	private function prefix($value = \'\'){\n		return self::$prefix.$value;\n	}\n\n	/**\n	 * Get preference from DB\n	 * @param  string $id field to get\n	 * @return mixed      stored preference\n	 */\n	private function get_pref($id){\n		return get_pref($this->prefix($id), self::$preferences[$id][\'default\']);\n	}\n\n	/**\n	 * Set preference (update/insert)\n	 * @param string $id  unprefixed key of preference\n	 * @param string $val value to save\n	 */\n	private function set_pref($id, $val = \'\', $default = \'\'){\n		$default = ($default === \'\')? self::$preferences[$id][\'default\'] : $default;\n		$val = trim($val);\n		$val = ($val === \'\') ? $default : $val;\n		return set_pref($this->prefix($id), $val, self::$slug, 2);\n	}\n\n	/**\n	 * Unzip and decode asset from gzipped, base64 string\n	 * @param  string $file file handle\n	 * @return string       file content\n	 */\n	private function get_asset($file){\n		if(array_key_exists($file, self::$assets)){\n			return gzuncompress(base64_decode(self::$assets[$file]));\n		}\n		return \'\';\n	}\n\n	/**\n	 * Get valid category\n	 * @param  string $category     selected category (name)\n	 * @param  string $new_category Title of new category (when $category === \'create_new\')\n	 * @return string               name of valid category\n	 */\n	private function get_category($category, $new_category){\n		$all_image_cats = getTree(\'root\', \'image\');\n		$categories = array(\'\');\n\n		foreach ($all_image_cats as $row) {\n			$categories[] = $row[\'name\'];\n		}\n\n		if($category === \'create_new\' && !empty($new_category)){\n			// create a category\n			return $this->create_category($new_category);\n		}else if(in_array($category, $categories)){\n			// existing category\n			return $category;\n		}\n\n		return \'\';\n	}\n\n	/**\n	 * Insert Image and create thumbnail\n	 * @param  array $file 	$_FILES[\'Filedata\']\n	 * @param  array  $meta Meta data for image\n	 * @return mixed       	image dat (array) on success, error message (string) on failure\n	 */\n	private function create_image($file, $meta = array(\'category\' => \'\', \'caption\' => \'\', \'alt\' => \'\')){\n		global $path_to_site, $img_dir, $txp_user;\n\n		$txp_user = empty($txp_user) ? gps(\'_jbx_user\') : $txp_user;\n\n		$event = \'\'; // satisfies txp_image use of $event global\n		include_once txpath . \'/include/txp_image.php\';\n\n		return image_data($file, $meta);\n	}\n\n	/**\n	 * Create category\n	 * @param  string $title The title used for the category\n	 * @return string        name field of the created category\n	 */\n	private function create_category($title){\n		$name = strtolower(sanitizeForUrl($title));\n\n		if (!$name){\n			return \'\';\n		}\n\n		$exists = safe_field(\'name\', \'txp_category\', \"name = \'\".doSlash($name).\"\' and type = \'image\'\");\n\n		if ($exists){\n			return $name;\n		}\n\n		$q = safe_insert(\'txp_category\', \"name = \'\".doSlash($name).\"\', title = \'\".doSlash($title).\"\', type = \'image\', parent = \'root\'\");\n\n		if ($q){\n			rebuild_tree_full(\'image\');\n			return $name;\n		}\n\n		return \'\';\n	}\n}\n\n// Initialize plugin\njbx_MIU::get_instance();\n', '\n/**\n * Multiple Image Upload Plugin\n * @author Jonas Bach <hallo@jonasbach.de>\n */\nclass jbx_MIU{\n	/**\n	 * Stores an instance\n	 * @var jbx_MIU\n	 */\n	protected static $instance;\n\n	/**\n	 * Slug of this plugin\n	 * @var string\n	 */\n	protected static $slug = \'jbx_multiple_image_upload\';\n\n	/**\n	 * Plugin prefix\n	 * @var string\n	 */\n	protected static $prefix = \'jbx_\';\n\n	/**\n	 * All available preferences for this plugin\n	 * @var array\n	 */\n	protected static $preferences = array(\n		//\'importinfo\'=> array(\'label\'=>\'Import additional info\', \'descr\'=>\'Import meta info into caption.\', \'type\'=>2, \'default\'=>\'none\'),\n	);\n\n	/**\n	 * external assets\n	 * @var array\n	 */\n	protected static $assets = array(\n		\'jquery.uploadify.min.js\' => \' \',\n		\'uploadify.swf\' => \' \',\n		\'jbx_multiple_image_upload.js\' => \'eJyNV0tv4zYQPtu/gnUDUEoUpdtbkzgFmuyhQPfQZnsqioCWKJuJTKkkZSdd+L93hkO9/EiTg8PHzDfDeSsqGp05VenoLGFbpfNqm7C8ypq11C5hz4vXp40wSixKaRPW6FwWSss8Zt+mE1VE7q2WVTEmY/P5nPGOlMdAOjHSNUbfTCe76QQo2dlamJemZnN2FvHbXG1YVgpr5zOE2hpR19LM7m5XP95xdjHGT60zSi9t2tRlJfKndVM6VZcS6PjtFXDclmIhy3cYM+GenHLEwm6tLGXmhgoAgVxW5g00uKJbWBAqgQ+Jtdx2DF712R0/KdlIoPQsXoELEK903TiGlpzPnHx1s1PgM3bVq7FnsmCL3nJD1EKVcsa0WId1oJ4dApAIwL67XTTOVTog0GbEYJvFWjlWN4tS2dU7bw6qBbwL8BEtP+ZdC5uhb71yPL6ZTidXV0zkOQvmUdJSaMEeYhDoqxoDm21E2cAT0Jd0crd30zvlQ57zTwhIHCL6LJUiW0Vjtl6phHUpphJUzueDV/LiUEuQD1cp+grkeH1wH6J1LHgXo3RKpBRyLY94OoxeHqcrty4jlIWkZB4jIMnXv+Zgok6xQYoyPggn9E70RbhVWpRVZWhJAFHMztlP/i9G8B16BPGVVu5Pz66KtwMhPvedMEvpfO67lfK60QUk1rwtDWlWVlpG3tPoaqWtNI7RJRwBcUpnv8iiMjIKsMTg4UKIzhF4aCA6573ccfYcMozvDxhPMYADhHMm4irnSWf4KO75wTWHzL3/OrpQBk5reFCEDpnfZxrQUxvw7qFldyWNqQwm119/t26xKyC9YiuVS4bJ0eKhhzBywfE8Wwm9lDzZiwVsIJ4GYh/CybeNPs2ob3Qy/HrfDmlmbcRzZetSvAE+X5RV9kJPmexkaWWHgQoex8CbiFi6S68Sb4HgZzeIQwippg1wfCZt0u4s8kK53RZ8Mrneqyc1pBBUlW2ReCJigpg6QUj3RCsaV3nEQsDL6Awif/0gnOCMXTN6LH9CIFe9SM33Qf1pMiBrIIEOqPDQP5xkULn+Cq3pgLKtjgtTbTuVoMM8qn/lbwqTbJ9jdNszfIU28yBtdlKCWoslFNMRw+dXZ4GBn6dLVdyw8/S5XuK/Wi/5UPV77FpIqMWmVPolXBq5rjbyvlrD8OBgUBkZttKPvvFzsGsXtig4hGUoLinGZoieXctJ5e8zJgvf404oh+6rvF1+scuwevRPDfiUamnd2BXF02SC/ACHKHSwtsvrHsSf7Y6q8thkmfQm2FMmh9AJAiEZcce+wzT81GYfRryXQLu+gHzPLxAiVTkUuTz3Jo54lwOXnikk0Cm+UIp6ptpUSwOKXi4E8FJ2b1XuVjzhn+rXj8HhK9rWx9klwybm3xmYj1j20LRkW2SjfbCrrwW9dX9vZNMF0NC6/+BFGylURNOqKCK+8L2q0b5iQKS3TwLrB71KqZduxe7YD60LjkzInpaJErrfpa955CScF46nD4HjAEHz017rpGBu39gXzknQHUQIfFhqJJTaTEb8Z7mBL4S5T0s+Ms6oWlpoym25nPZZg52dZ6WCYj3oCsFiMq2NR3+QhYDhPqTXfvM5An+kFnMrnUMTYHvoqmXCvvGu812zvgsB0agrXrNRS4hDHByRE1o+AJzTeOoVzGE2dGoDcIxqkZ0Oiseg9RxBhL6GTgRIZyCaOsxgHSayDPi1E/g5UuGgPB2Gmz4SbQctuJ36Tgy9ACu3wmjYUV3xyu66gQ+0gdkVxpo/pMihGZ5FPb5vR2d7czHNaDammXk0K8ZhpN3F0TMklnn73w/S+OY/YyelwQ==\',\n		\'jbx_multiple_image_upload.css\' => \' \'\n	);\n\n	/**\n	 * Returns an instance\n	 * @return jbx_MIU instance\n	 */\n	static function get_instance(){\n		if ( null == self::$instance ) {\n			self::$instance = new jbx_MIU();\n		}\n\n		return self::$instance;\n	}\n\n	/**\n	 * Constructor\n	 */\n	public function __construct(){\n		if(@txpinterface === \'admin\'){\n			add_privs(self::$slug, \'1,2,3,4,5,6\');\n			//add_privs(\'plugin_prefs.\'.self::$slug, \'1\');\n			register_callback(array($this, \'handle_request\'), self::$slug, \'\', 1);\n			register_callback(array($this, \'inject_assets\'), \'admin_side\', \'head_end\');\n			//register_callback(array($this, \'render_prefs\'), \'plugin_prefs.\'.self::$slug);\n			register_callback(array($this, \'install\'), \'plugin_lifecycle.\'.self::$slug, \'installed\');\n			//register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.\'.self::$slug, \'deleted\');\n		}\n		register_callback(array($this, \'handle_public\'), \'textpattern\');\n	}\n\n	/**\n	 * Called on plugin installation\n	 */\n	public function install(){\n		$token = uniqid(get_pref(\'blog_uid\'), true);\n		set_pref($this->prefix(\'token\'), $token, self::$slug, 2);\n	}\n\n	/**\n	 * Called on plugin deletion\n	 */\n	public function uninstall(){\n\n	}\n\n	/**\n	 * Called on public side\n	 */\n	public function handle_public(){\n		if (gps(self::$slug)) {\n			// check for token\n			if (gps(\'_jbx_token\') != get_pref($this->prefix(\'token\'))) {\n				txp_die(\'\', 403);\n			}\n\n			$this->handle_upload();\n		}\n	}\n\n	/**\n	 * Inject scripts/styles in <head>\n	 */\n	public function inject_assets(){\n		global $event;\n		if($event === \'image\'){\n			echo \'<link type=\"text/css\" href=\"?event=\'.self::$slug.\'&step=css\" rel=\"stylesheet\" />\';\n			echo \'<script type=\"text/javascript\" src=\"?event=\'.self::$slug.\'&step=js\"></script>\';\n		}\n	}\n\n	/**\n	 * Entry point for requests\n	 * @param  string $event 	the event being called\n	 * @param  string $request  the step being called\n	 */\n	public function handle_request($event, $request){\n		$requests = array(\n			\'js\' => false,\n			\'css\' => false,\n			\'swf\' => false\n		);\n		if (!$request || !bouncer($request, $requests)) {\n			return;\n		}\n\n		switch ($request) {\n			default:\n				$this->{\'render_\'.$request}();\n				break;\n		}\n	}\n\n	public function handle_upload(){\n		// todo: handle meta\n		$meta = array(\'category\' => \'\', \'caption\' => \'\', \'alt\' => \'\');\n\n		// category\n		$meta[\'category\'] = $this->get_category(gps(\'category\'), gps(\'new_category\'));\n\n		// insert image, create thumbnail\n		$msg = $this->create_image($_FILES[\'Filedata\'], $meta);\n		\n		\n		if(is_array($msg)){\n			// success\n			die(\'1\');\n		}else{\n			// error\n			die($msg);\n		}\n	}\n\n	/**\n	 * Render JS assets\n	 * @todo: use render_asset\n	 */\n	public function render_js(){\n		global $file_max_upload_size, $siteurl, $txp_user;\n		$script = \'\';\n\n		// pass variables to JS\n		$script_variables = array(\n			\'slug\' => self::$slug,\n			\'paths\' => array(\n				\'swf\' => \'?event=\'.self::$slug.\'&step=swf\',\n				\'upload\' => $siteurl.\'/?\'.self::$slug.\'=upload\'\n			),\n			\'fileSizeLimit\' => $file_max_upload_size,\n			\'targets\' => \'.upload-form\', // todo: make hackable by callback\n			\'token\' => get_pref($this->prefix(\'token\')),\n			\'user\' => $txp_user,\n			\'categories\' => getTree(\'root\', \'image\'),\n			\'strings\' => array(\n				\'closewarning\' => gTxt(\'Upload in progress. Are you sure you want to leave the page anyway?\'),\n				\'errors\' => gTxt(\'<strong>Some errors were encountered.</strong> Check the errors above, then <a href=\"?event=image\">reload</a> the page to see your successfully imported images.\'),\n				\'create_new\' => gTxt(\'Create new…\'),\n				\'create_new_title\' => gTxt(\'New category name\'),\n				\'images\' => gTxt(\'Image files\'),\n				\'upload_multiple\' => gTxt(\'Upload Multiple Images\'),\n				\'upload_single\' => gTxt(\'Upload Single Image\'),\n				\'upload_button\' => gTxt(\'Upload\'),\n				\'cat_title\' => gTxt(\'Assign category\'),\n				\'browse\' => gTxt(\'Select files…\')\n			)\n		);\n\n		$script .= $this->get_asset(\'jquery.uploadify.min.js\');\n		$script .= \'var jbx_variables = \'.json_encode($script_variables).\';\';\n		$script .= $this->get_asset(\'jbx_multiple_image_upload.js\');\n		send_script_response($script);\n		die();\n	}\n\n	/**\n	 * Render swf\n	 */\n	public function render_swf(){\n		$this->render_asset(\'uploadify.swf\', \'application/x-shockwave-flash\');\n	}\n\n	/**\n	 * Render stylesheets\n	 */\n	public function render_css(){\n		$this->render_asset(\'jbx_multiple_image_upload.css\', \'text/css\');\n	}\n\n	/**\n	 * Generic asset renderer\n	 * @param  string $file file handle\n	 * @param  string $mime mimetype\n	 */\n	public function render_asset($file, $mime = \'text/plain\'){\n		$file = $this->get_asset($file);\n		ob_clean();\n		header(\'Content-Type: \'.$mime.\'; charset=utf-8\');\n		txp_status_header(\'200 OK\');\n		echo $file;\n		die();\n	}\n\n	/**\n	 * Render options page\n	 * @param  string $event the event being called\n	 * @param  string $step  the step being called\n	 */\n	public function render_prefs($event, $step){\n		if($step === \'update\'){\n			$this->update_prefs();\n		}\n\n		pageTop(gTxt(\'Multiple Image Upload\'));\n\n		// todo: show preflight/sanity errors if any\n		\n		// Generate Preferences Table\n		$out = hed(gTxt(\'Multiple Image Upload - Preferences\'), 1);\n		$out .= startTable($this->prefix(\'preferences\'), \'center\', 5);\n		foreach (self::$preferences as $key => $pref) {\n			$out .= $this->render_pref($key, $pref);\n		}\n		\n		// render save button\n		$out .= tr(tdcs(eInput(\'plugin_prefs.\'.self::$slug).sInput(\'update\').fInput(\'submit\', \'save\', gTxt(\'save_button\'), \'publish\'), 3, \'\', \'nolin\'));\n		\n		$out .= endtable();\n\n		echo form($out);\n	}\n\n	/**\n	 * Render single preference row\n	 * @param  array $pref preference array\n	 * @return string      <tr> containing the preference\n	 */\n	private function render_pref($id, $pref){\n		$value = $this->get_pref($id);\n		$id = $this->prefix($id);\n\n		// render label\n		$out = fLabelCell(gTxt($pref[\'label\']), \'\', $id);\n\n		// render field\n		switch($pref[\'type\']){\n			case 2:\n				$out .= td(selectInput($id, array(gTxt(\'None\') => \'\', \'EXIF\'=>\'exif\', \'IPTC\'=>\'iptc\'), $value));\n			break;\n\n			case 1:\n				$out .= td(yesnoRadio($id, $value));\n			break;\n\n			default:\n				$out .= fInputCell($id, $value, \'\', 20, \'\', $id);\n			break;\n		}\n\n		// render help\n		$out .= td(gTxt($pref[\'descr\']));\n\n		// render save\n		\n		return tr($out);\n	}\n\n	/**\n	 * Update preferences from submitted form\n	 */\n	private function update_prefs(){\n		foreach (self::$preferences as $key => $pref) {\n			$this->set_pref($key, gps($this->prefix($key)));\n		}\n		txp_die(\'\', \'302\', \'?event=plugin_prefs.\'.self::$slug);\n	}\n\n	/**\n	 * Get prefixed string\n	 * @param  string $value unprefixed string\n	 * @return string        prefixed string\n	 */\n	private function prefix($value = \'\'){\n		return self::$prefix.$value;\n	}\n\n	/**\n	 * Get preference from DB\n	 * @param  string $id field to get\n	 * @return mixed      stored preference\n	 */\n	private function get_pref($id){\n		return get_pref($this->prefix($id), self::$preferences[$id][\'default\']);\n	}\n\n	/**\n	 * Set preference (update/insert)\n	 * @param string $id  unprefixed key of preference\n	 * @param string $val value to save\n	 */\n	private function set_pref($id, $val = \'\', $default = \'\'){\n		$default = ($default === \'\')? self::$preferences[$id][\'default\'] : $default;\n		$val = trim($val);\n		$val = ($val === \'\') ? $default : $val;\n		return set_pref($this->prefix($id), $val, self::$slug, 2);\n	}\n\n	/**\n	 * Unzip and decode asset from gzipped, base64 string\n	 * @param  string $file file handle\n	 * @return string       file content\n	 */\n	private function get_asset($file){\n		if(array_key_exists($file, self::$assets)){\n			return gzuncompress(base64_decode(self::$assets[$file]));\n		}\n		return \'\';\n	}\n\n	/**\n	 * Get valid category\n	 * @param  string $category     selected category (name)\n	 * @param  string $new_category Title of new category (when $category === \'create_new\')\n	 * @return string               name of valid category\n	 */\n	private function get_category($category, $new_category){\n		$all_image_cats = getTree(\'root\', \'image\');\n		$categories = array(\'\');\n\n		foreach ($all_image_cats as $row) {\n			$categories[] = $row[\'name\'];\n		}\n\n		if($category === \'create_new\' && !empty($new_category)){\n			// create a category\n			return $this->create_category($new_category);\n		}else if(in_array($category, $categories)){\n			// existing category\n			return $category;\n		}\n\n		return \'\';\n	}\n\n	/**\n	 * Insert Image and create thumbnail\n	 * @param  array $file 	$_FILES[\'Filedata\']\n	 * @param  array  $meta Meta data for image\n	 * @return mixed       	image dat (array) on success, error message (string) on failure\n	 */\n	private function create_image($file, $meta = array(\'category\' => \'\', \'caption\' => \'\', \'alt\' => \'\')){\n		global $path_to_site, $img_dir, $txp_user;\n\n		$txp_user = empty($txp_user) ? gps(\'_jbx_user\') : $txp_user;\n\n		$event = \'\'; // satisfies txp_image use of $event global\n		include_once txpath . \'/include/txp_image.php\';\n\n		return image_data($file, $meta);\n	}\n\n	/**\n	 * Create category\n	 * @param  string $title The title used for the category\n	 * @return string        name field of the created category\n	 */\n	private function create_category($title){\n		$name = strtolower(sanitizeForUrl($title));\n\n		if (!$name){\n			return \'\';\n		}\n\n		$exists = safe_field(\'name\', \'txp_category\', \"name = \'\".doSlash($name).\"\' and type = \'image\'\");\n\n		if ($exists){\n			return $name;\n		}\n\n		$q = safe_insert(\'txp_category\', \"name = \'\".doSlash($name).\"\', title = \'\".doSlash($title).\"\', type = \'image\', parent = \'root\'\");\n\n		if ($q){\n			rebuild_tree_full(\'image\');\n			return $name;\n		}\n\n		return \'\';\n	}\n}\n\n// Initialize plugin\njbx_MIU::get_instance();\n', 'cb0fb1596433911e163a70887762679f', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('upm_file', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-file', '0.4.2', 'Associate single or multiple files with an article.', '<style type=\"text/css\" media=\"screen,projection\">\n<!--\nvar {\npadding: 0.1em;\nfont-style: normal;\nfont-weight: bold;\nfont-family: monospace;\nbackground-color: #eee;\n}\n\ntable.upm_tag_atts {\nmargin: 0 0 15px 0;\nborder: 1px solid #eee;\nborder-collapse: collapse;\n}\n\ntable.upm_tag_atts caption {\npadding: 0.5em;\nfont-weight: bold;\ntext-align: left;\nbackground-color: #ddd;\n}\n\ntable.upm_tag_atts tr {\nborder-bottom: 1px solid #eee;\n}\n\ntable.upm_tag_atts th, table.upm_tag_atts td {\nmargin: 0;\npadding: 0.5em 1em;\n}\n\ntable.upm_tag_atts th {\nfont-weight: normal;\ntext-align: left;\nbackground-color: #eee;\n}\n\n-->\n</style>\n\n<h1>upm_file</h1>\n\n<ol id=\"start\">\n	<li><a href=\"#introduction\">Introduction</a></li>\n	<li><a href=\"#tags\">Tags</a></li>\n	<li><a href=\"#footnotes\">Footnotes</a></li>\n</ol>\n\n\n\n<h2 id=\"intro\">Introduction</h2>\n\n<p>This plugin allows you to associate single or multiple files with an article. It requires\n	you to use one of your custom fields to hold the file list. The field&#8217;s human-friendly\n	name may be anything you wish. All that you need to do is pick one to be used, then update\n	the plugin&#8217;s preference to point to it.</p>\n\n<h2 id=\"tags\">Tags</h2>\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_article_file_list</caption>\n\n	<tr>\n		<th>Attribute</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>break</td>\n		<td>(X)HTML tag to be used for line breaks.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML element name.</td>\n		<td><var>br</var></td>\n	</tr>\n\n	<tr>\n		<td>class</td>\n		<td>(X)HTML/CSS class to apply to the file or if specified, the <var>wraptag</var>.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS class name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>id</td>\n		<td>Restrict to files for specific article.</td>\n		<td>No</td>\n		<td>Any valid article ID number.</td>\n		<td>Unset. The current article is used.</td>\n	</tr>\n\n	<tr>\n		<td>file_category</td>\n		<td>Restrict to files with specified category.</td>\n		<td>No</td>\n		<td>Any valid file category name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>form</td>\n		<td>Customize the file details output.</td>\n		<td>No</td>\n		<td>Any valid form of the type &#8220;file&#8221;.</td>\n		<td><var>files</var></td>\n	</tr>\n\n	<tr>\n		<td>label</td>\n		<td>Label for the top of the list.</td>\n		<td>No</td>\n		<td>Any desired text.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>labeltag</td>\n		<td>(X)HTML tag to wrap around the label.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML element name</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>limit</td>\n		<td>Maximum number of files to display.</td>\n		<td>No</td>\n		<td>Any valid whole number.</td>\n		<td>Unset (display all)</td>\n	</tr>\n\n	<tr>\n		<td>offset</td>\n		<td>From the beginning of the list, the number of files to exclude from the list.</td>\n		<td>No</td>\n		<td>Any valid whole number.</td>\n		<td>Unset (display all)</td>\n	</tr>\n\n	<tr>\n		<td>sort</td>\n		<td>How to sort retrieved files (comma-separated).</td>\n		<td>No</td>\n		<td>\n			<ul>\n				<li><var>category</var></li>\n				<li><var>description</var></li>\n				<li><var>downloads</var></li>\n				<li><var>filename</var></li>\n				<li><var>id</var></li>\n			</ul>\n			<p><var>asc</var>ending or <var>desc</var>ending</p>\n		</td>\n		<td>Sorts by the order entered into article file packets field.</td>\n	</tr>\n\n	<tr>\n		<td>wraptag</td>\n		<td>(X)HTML tag to wrap around the file (or the list, if using multiple files).</td>\n		<td>No</td>\n		<td>Any valid (X)HTML element name.</td>\n		<td>Unset</td>\n	</tr>\n</table>\n\n<p>The following attributes are identical to those belonging to the built-in tags<sup>[<a href=\"#fn1\">1</a>]</sup>, with one addition and one slightly modified:</p>\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_file_article <span style=\"font-weight: normal;\">and</span> upm_file_article_custom</caption>\n\n	<tr>\n		<th>Attribute</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>include_expired</td>\n		<td>Whether to include expired articles in the list.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td>Admin preference \"Publish expired articles?\"</td>\n	</tr>\n\n	<tr>\n		<td>sort</td>\n		<td>How to sort retrieved files (comma-separated).</td>\n		<td>No</td>\n		<td>Any sort value available for article<sup>[<a href=\"#fn1\">1</a>]</sup> and file_download_list<sup>[<a href=\"#fn2\">2</a>]</sup></td>\n		<td><var>downloads desc, Posted desc</var></td>\n	</tr>\n\n	<tr>\n		<td>withfiles</td>\n		<td>Only return articles with one or more associated files.</td>\n		<td>No</td>\n		<td><var>1</var> (yes) or <var>0</var> (no)</td>\n		<td><var>0</var></td>\n	</tr>\n\n</table>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n\n<h2 id=\"footnotes\">Footnotes</h2>\n\n<ol>\n	<li id=\"fn1\">TextBook: <a href=\"http://textpattern.net/wiki/index.php?title=Txp:article_/\">article</a> and <a href=\"http://textpattern.net/wiki/index.php?title=Txp:article_custom_/\">article_custom</a></li>\n	<li id=\"fn2\">TextBook: <a href=\"http://textpattern.net/wiki/index.php?title=Txp:file_download_list_/\">file_download_list</a></li>\n</ol>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>', 'if (txpinterface == \'admin\')\n	{\n		upm_file_install();\n\n		add_privs(\'upm_file_prefs\', \'1,2\');\n		register_tab(\'extensions\', \'upm_file_prefs\', \'upm_file\');\n		register_callback(\'upm_file_prefs\', \'upm_file_prefs\');\n\n		add_privs(\'upm_file_install\', \'1,2\');\n		register_callback(\'upm_file_install\', \'upm_file_prefs\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_article_file_list($atts = array())\n	{\n		global $thisfile, $thisarticle, $file_base_path, $prefs;\n\n		extract(lAtts(array(\n			\'break\'					=> br,\n			\'class\'					=> \'\',\n			\'id\'						=> $thisarticle[\'thisid\'],\n			\'file_category\' => \'\',\n			\'form\'					=> \'files\',\n			\'label\'					=> \'\',\n			\'labeltag\'			=> \'\',\n			\'limit\'					=> 0,\n			\'offset\'				=> 0,\n			\'sort\'					=> \'\',\n			\'sortby\'				=> \'\', // deprecated\n			\'wraptag\'				=> \'\'\n		), $atts));\n\n		$id = (int) $id;\n\n		if ($id > 0)\n		{\n			$field = fetch($prefs[\'upm_file_field\'], \'textpattern\', \'ID\', doSlash($id));\n		}\n\n		if (empty($id) or empty($field))\n		{\n			return;\n		}\n\n		// get custom file field and turn into array\n		$list = explode(\',\', $field);\n\n		// chop off unwanted IDs\n		$list = ($limit) ? array_slice($list, $offset, $limit) : array_slice($list, $offset);\n\n		// sanitize for query\n		// make sure only real numbers are used\n		$list = doSlash(join(\',\', array_map(create_function(\'$value\', \'return (int) $value;\'), $list)));\n\n		if ($file_category)\n		{\n			$file_category = join(\"\',\'\", doSlash(do_list($file_category)));\n\n			$file_category_sql = \"and category in(\'$file_category\')\";\n		}\n\n		else\n		{\n			$file_category_sql = \'\';\n		}\n\n		if ($sortby)\n		{\n			$sort = $sortby;\n		}\n\n		$sort = ($sort) ? doSlash($sort) : \"field(id, $list)\";\n\n		$rs = safe_rows_start(\'*\', \'txp_file\', \"id in($list) $file_category_sql order by $sort\");\n\n		if ($rs)\n		{\n			$form = fetch_form($form);\n\n			$out = array();\n\n			while ($a = nextRow($rs))\n			{\n				$GLOBALS[\'thisfile\'] = file_download_format_info($a);\n\n				$out[] = parse($form);\n\n				$GLOBALS[\'thisfile\'] = \'\';\n			}\n\n			if ($out)\n			{\n				return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n			}\n		}\n\n		return \'\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_article($atts = array(), $thing = \'\')\n	{\n		global $is_article_body, $has_article_tag;\n\n		if ($is_article_body)\n		{\n			trigger_error(gTxt(\'article_tag_illegal_body\'));\n			return \'\';\n		}\n\n		$has_article_tag = true;\n\n		return upm_file_parseArticles($atts, 0, $thing);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_doArticles($atts, $iscustom = 0, $thing = \'\')\n	{\n		global $pretext, $prefs;\n\n		extract($pretext);\n		extract($prefs);\n\n		$customFields = getCustomFields();\n		$customlAtts = array_null(array_flip($customFields));\n\n		$theAtts = lAtts(array(\n			\'allowoverride\'   => (!$q and !$iscustom),\n			\'author\'				  => \'\',\n			\'break\'           => \'\',\n			\'category\'			  => \'\',\n			\'class\'           => \'\',\n			\'excerpted\'			  => \'\',\n			\'form\'					  => \'default\',\n			\'frontpage\'			  => \'\',\n			\'id\'						  => \'\',\n			\'include_expired\' => ($publish_expired_articles ? \'yes\' : \'no\'),\n			\'keywords\'			  => \'\',\n			\'label\'           => \'\',\n			\'labeltag\'        => \'\',\n			\'limit\'					  => 10,\n			\'listform\'			  => \'\',\n			\'month\'					  => \'\',\n			\'offset\'				  => 0,\n			\'pageby\'				  => \'\',\n			\'pgonly\'				  => 0,\n			\'searchall\'			  => 1,\n			\'searchform\'		  => \'\',\n			\'searchsticky\'	  => 0,\n			\'section\'				  => \'\',\n			\'sort\'					  => \'\',\n			\'sortby\'				  => \'\',\n			\'sortdir\'				  => \'\',\n			\'status\'				  => \'4\',\n			\'time\'					  => \'past\',\n			\'withfiles\'			  => false,\n			\'wraptag\'         => \'\',\n		) + $customlAtts, $atts);\n\n		// if an article ID is specified, treat it as a custom list\n		$iscustom = !empty($theAtts[\'id\']) ? true : $iscustom;\n\n		// for the article tag, some attributes are taken from globals\n		// override them before extract\n		if (!$iscustom)\n		{\n			$theAtts[\'author\']		= !empty($author) ? \"a.$author\" : \'\';\n			$theAtts[\'category\']	= ($c) ? \"a.$c\" : \'\';\n			$theAtts[\'excerpted\'] = \'\';\n			$theAtts[\'frontpage\'] = ($s && $s == \'default\') ? true : false;\n			$theAtts[\'month\']			= !empty($month) ? \"a.$month\" : \'\';\n			$theAtts[\'section\']		= ($s && $s != \'default\') ? \"a.$s\" : \'\';\n		}\n\n		extract($theAtts);\n\n		// if a listform is specified, $thing is for doArticle() - hence ignore here.\n		if (!empty($listform))\n		{\n			$thing = \'\';\n		}\n\n		$pageby = (empty($pageby) ? $limit : $pageby);\n\n		// treat sticky articles differently wrt search filtering, etc\n		$status = in_array(strtolower($status), array(\'sticky\', \'5\')) ? 5 : 4;\n		$issticky = ($status == 5);\n\n		// give control to search, if necesary\n		if ($q && !$iscustom && !$issticky)\n		{\n			include_once(txpath.\'/publish/search.php\');\n\n			$s_filter = ($searchall) ? upm_file_filterSearch() : \'\';\n\n			$q = doSlash($q);\n\n      // searchable article fields are limited to the columns of\n      // the textpattern table and a matching fulltext index must exist.\n			$cols = do_list($searchable_article_fields);\n\n			if (empty($cols) or $cols[0] == \'\')\n			{\n				$cols = array(\'a.Title\', \'a.Body\');\n			}\n\n			$match = \', match (\'.join(\', \', $cols).\") against (\'$q\') as score\";\n\n			for ($i = 0; $i < count($cols); $i++)\n			{\n				$cols[$i] = \"$cols[$i] rlike \'$q\'\";\n			}\n\n			$search = \" and (\".join(\" or \", $cols).\") $s_filter\";\n\n			// searchall=0 can be used to show search results for the current section only\n			if ($searchall)\n			{\n				$section = \'\';\n			}\n\n			if (!$sort)\n			{\n				$sort = \"downloads desc, score desc\";\n			}\n		}\n\n		else\n		{\n			$match = $search = \'\';\n\n			if (!$sort)\n			{\n				$sort = \"downloads desc, Posted desc\";\n			}\n		}\n\n		// for backwards compatibility\n		// sortby and sortdir are deprecated\n		if ($sortby)\n		{\n			if (!$sortdir)\n			{\n				$sortdir = \'desc\';\n			}\n\n			$sort = \"$sortby $sortdir\";\n		}\n\n		elseif ($sortdir)\n		{\n			$sort = \"a.Posted $sortdir\";\n		}\n\n		// building query parts\n		$author	= ($author)	? \" and a.AuthorID in(\'\".join(\"\',\'\", doSlash(do_list($author))).\"\')\" : \'\';\n		$category = join(\"\',\'\", doSlash(do_list($category)));\n		$category	= ($category)	? \" and ( a.Category1 in(\'\".doSlash($category).\"\') or a.Category2 in(\'\".doSlash($category).\"\') ) \" : \'\';\n		$excerpted = ($excerpted == \'y\') ? \" and a.Excerpt != \'\'\" : \'\';\n		$frontpage = ($frontpage and (!$q or $issticky)) ? upm_file_filterFrontPage() : \'\';\n		$id	= ($id)	? \" and a.ID in(\'\".join(\',\', array_map(\'intval\', do_list($id))).\"\')\" : \'\';\n		$month = ($month) ? \" and a.Posted like \'\".doSlash($month).\"%\'\" : \'\';\n		$section = ($section)	? \" and a.Section in(\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\" : \'\';\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and a.Posted > now()\";\n			break;\n\n			default:\n				$time = \" and a.Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		// trying custom fields here\n\n		$custom = \'\';\n\n		$customFields = getCustomFields();\n\n		if ($customFields)\n		{\n			foreach ($customFields as $cField)\n			{\n				if (isset($atts[$cField]))\n				{\n					$customPairs[$cField] = $atts[$cField];\n				}\n			}\n\n			if (!empty($customPairs))\n			{\n				$custom =	 buildCustomSql($customFields, $customPairs);\n			}\n\n			else\n			{\n				$custom = \'\';\n			}\n		}\n\n		// Allow keywords for no-custom articles. That tagging mode, you know\n		if ($keywords)\n		{\n			$keys = doSlash(do_list($keywords));\n\n			foreach ($keys as $key)\n			{\n				$keyparts[] = \"find_in_set(\'\".$key.\"\', a.Keywords)\";\n			}\n\n			$keywords = \" and (\" . join(\' or \', $keyparts) . \")\";\n		}\n\n		if ($q and $searchsticky)\n		{\n			$statusq = \' and a.Status >= 4\';\n		}\n\n		elseif ($id)\n		{\n			$statusq = \' and a.Status >= 4\';\n		}\n\n		else\n		{\n			$statusq = \' and a.Status = \'.intval($status);\n		}\n\n		$where = \"1\". $statusq. $time. $search. $id. $category. $section. $excerpted. $month. $author. $keywords. $custom. $frontpage;\n\n		//do not paginate if we are on a custom list\n		if (!$iscustom and !$issticky)\n		{\n			$grand_total = getThing(\"select count(*) from \".safe_pfx_j(\'textpattern\').\" as a where $where\");\n			$total = $grand_total - $offset;\n			$numPages = ceil($total / $pageby);\n			$pg = (!$pg) ? 1 : $pg;\n			$pgoffset = $offset + (($pg - 1) * $pageby);\n\n			// send paging info to txp:newer and txp:older\n			$pageout[\'s\']						= $s;\n			$pageout[\'c\']						= $c;\n			$pageout[\'pg\']					= $pg;\n			$pageout[\'total\']				= $total;\n			$pageout[\'grand_total\'] = $grand_total;\n			$pageout[\'numPages\']		= $numPages;\n\n			global $thispage;\n\n			if (empty($thispage))\n			{\n				$thispage = $pageout;\n			}\n\n			if ($pgonly)\n			{\n				return;\n			}\n		}\n\n		else\n		{\n			$pgoffset = $offset;\n		}\n\n		$join_type = ($withfiles) ? \'right\' : \'left\';\n\n		$rs = safe_query(\"select a.*, unix_timestamp(a.Posted) as uPosted, unix_timestamp(a.Expires) as uExpires, unix_timestamp(a.LastMod) as uLastMod\n			$match\n			from \".safe_pfx(\'textpattern\').\" as a $join_type outer join \".safe_pfx(\'txp_file\').\" as f on(a.$upm_file_field = f.id)\n			where $where order by \".doSlash($sort).\" limit \".intval($pgoffset).\", \".intval($limit));\n\n		// get the form name\n		if ($q and !$iscustom and !$issticky)\n		{\n			$form = ($searchform ? $searchform : \'search_results\');\n		}\n\n		else\n		{\n			$form = ($listform ? $listform : $form);\n		}\n\n		if ($rs)\n		{\n			$count = 0;\n\n			$articles = array();\n\n			while ($a = nextRow($rs))\n			{\n				++$count;\n\n				populateArticleData($a);\n\n				global $thisarticle, $uPosted, $limit;\n\n				$thisarticle[\'is_first\'] = ($count == 1);\n				$thisarticle[\'is_last\'] = ($count == numRows($rs));\n\n				if (@constant(\'txpinterface\') === \'admin\' and gps(\'Form\'))\n				{\n					$articles[] = parse(gps(\'Form\'));\n				}\n\n				elseif ($allowoverride and $a[\'override_form\'])\n				{\n					$articles[] = parse(fetch_form($a[\'override_form\']));\n				}\n\n				else\n				{\n					$articles[] = ($thing) ? parse($thing) : parse(fetch_form($form));\n				}\n\n				// sending these to paging_link(); Required?\n				$uPosted = $a[\'uPosted\'];\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			return doLabel($label, $labeltag).doWrap($articles, $wraptag, $break, $class);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_filterFrontPage()\n	{\n		static $filterFrontPage;\n\n		if (isset($filterFrontPage))\n		{\n			return $filterFrontPage;\n		}\n\n		$rs = safe_column(\'name\', \'txp_section\', \"on_frontpage != \'1\'\");\n\n		if ($rs)\n		{\n			foreach ($rs as $name)\n			{\n				$filters[] = \" and a.Section != \'\".doSlash($name).\"\'\";\n			}\n\n			$filterFrontPage = join(\' \', $filters);\n		}\n\n		else\n		{\n			$filterFrontPage = false;\n		}\n\n		return $filterFrontPage;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_filterSearch()\n	{\n		$rs = safe_column(\'name\', \'txp_section\', \"searchable != \'1\'\");\n\n		if ($rs)\n		{\n			foreach ($rs as $name)\n			{\n				$filters[] = \" and a.Section != \'\".doSlash($name).\"\'\";\n			}\n\n			return join(\' \', $filters);\n		}\n\n		return false;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_article_custom($atts = array(), $thing = \'\')\n	{\n		return upm_file_parseArticles($atts, 1, $thing);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_parseArticles($atts, $iscustom = \'\', $thing = \'\')\n	{\n		global $pretext, $is_article_list;\n\n		$old_ial = $is_article_list;\n\n		$is_article_list = ($pretext[\'id\'] && !$iscustom) ? false : true;\n\n		article_push();\n\n		$r = ($is_article_list) ? upm_file_doArticles($atts, $iscustom, $thing) : doArticle($atts, $thing);\n\n		article_pop();\n\n		$is_article_list = $old_ial;\n\n		return $r;\n	}\n\n\n// -------------------------------------------------------------\n\n	function upm_file_prefs($event, $step)\n	{\n		global $prefs;\n\n		if ($step == \'update\')\n		{\n			$field = doSlash(strip_tags(ps(\'field\')));\n\n			safe_update(\'txp_prefs\', \"val = \'$field\'\", \"name = \'upm_file_field\'\");\n		}\n\n		extract(get_prefs());\n\n		pagetop(\'upm_file\'.sp.gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		$fields = array();\n\n		$max = get_pref(\'max_custom_fields\', 10);\n\n\n		for($i = 1; $i <= $max; $i++)\n		{\n			$field_name = \"custom_{$i}_set\";\n			$field_title = $prefs[$field_name];\n\n			if (!empty($prefs[\"custom_{$i}_set\"]))\n			{\n				$options[ \"custom_{$i}\" ] = $prefs[\"custom_{$i}_set\"];\n			}\n		}\n\n		$option_list = array();\n\n		foreach ($options as $key => $value)\n		{\n			if ($key == $upm_file_field)\n			{\n				$option_list[] = n.t.\'<option value=\"\'.$key.\'\" selected=\"selected\">\'.$value.\'</option>\';\n			}\n\n			elseif (!empty($value))\n			{\n				$option_list[] = n.t.\'<option value=\"\'.$key.\'\">\'.$value.\'</option>\';\n			}\n		}\n\n		echo n.\'<div style=\"margin: 3em auto auto auto; width: 16em;\">\'.\n		n.hed(\'upm_file\'.sp.gTxt(\'prefs\'),\'1\').\n		n.n.form(\n			n.eInput(\'upm_file_prefs\').\n			n.sInput(\'update\').\n			n.upm_file_gTxt(\'custom_field\').br.n.\'<select name=\"field\" class=\"list\">\'.join(n, $option_list).\'</select>\'.\n			br.br.\n			n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n		).n.\'</div>\';\n\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_install()\n	{\n		global $prefs;\n\n		$field = \'custom_1\';\n\n		// for upgraders\n		if (isset($prefs[\'upm_file_packets_field\']))\n		{\n			$field = $prefs[\'upm_file_packets_field\'];\n\n			safe_delete(\'txp_prefs\', \"name = \'upm_file_packets_field\' limit 1\");\n		}\n\n		if (!isset($prefs[\'upm_file_field\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_file_field\',\n				val = \'$field\',\n				html = \'text_input\',\n				prefs_id = 1,\n				type = 2,\n				event = \'admin\',\n				position = 0\n			\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_gTxt($var, $atts = array())\n	{\n		$lang = array(\n			\'custom_field\' => \'File Custom Field\',\n		);\n\n		return strtr($lang[$var], $atts);\n	}', 'if (txpinterface == \'admin\')\n	{\n		upm_file_install();\n\n		add_privs(\'upm_file_prefs\', \'1,2\');\n		register_tab(\'extensions\', \'upm_file_prefs\', \'upm_file\');\n		register_callback(\'upm_file_prefs\', \'upm_file_prefs\');\n\n		add_privs(\'upm_file_install\', \'1,2\');\n		register_callback(\'upm_file_install\', \'upm_file_prefs\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_article_file_list($atts = array())\n	{\n		global $thisfile, $thisarticle, $file_base_path, $prefs;\n\n		extract(lAtts(array(\n			\'break\'					=> br,\n			\'class\'					=> \'\',\n			\'id\'						=> $thisarticle[\'thisid\'],\n			\'file_category\' => \'\',\n			\'form\'					=> \'files\',\n			\'label\'					=> \'\',\n			\'labeltag\'			=> \'\',\n			\'limit\'					=> 0,\n			\'offset\'				=> 0,\n			\'sort\'					=> \'\',\n			\'sortby\'				=> \'\', // deprecated\n			\'wraptag\'				=> \'\'\n		), $atts));\n\n		$id = (int) $id;\n\n		if ($id > 0)\n		{\n			$field = fetch($prefs[\'upm_file_field\'], \'textpattern\', \'ID\', doSlash($id));\n		}\n\n		if (empty($id) or empty($field))\n		{\n			return;\n		}\n\n		// get custom file field and turn into array\n		$list = explode(\',\', $field);\n\n		// chop off unwanted IDs\n		$list = ($limit) ? array_slice($list, $offset, $limit) : array_slice($list, $offset);\n\n		// sanitize for query\n		// make sure only real numbers are used\n		$list = doSlash(join(\',\', array_map(create_function(\'$value\', \'return (int) $value;\'), $list)));\n\n		if ($file_category)\n		{\n			$file_category = join(\"\',\'\", doSlash(do_list($file_category)));\n\n			$file_category_sql = \"and category in(\'$file_category\')\";\n		}\n\n		else\n		{\n			$file_category_sql = \'\';\n		}\n\n		if ($sortby)\n		{\n			$sort = $sortby;\n		}\n\n		$sort = ($sort) ? doSlash($sort) : \"field(id, $list)\";\n\n		$rs = safe_rows_start(\'*\', \'txp_file\', \"id in($list) $file_category_sql order by $sort\");\n\n		if ($rs)\n		{\n			$form = fetch_form($form);\n\n			$out = array();\n\n			while ($a = nextRow($rs))\n			{\n				$GLOBALS[\'thisfile\'] = file_download_format_info($a);\n\n				$out[] = parse($form);\n\n				$GLOBALS[\'thisfile\'] = \'\';\n			}\n\n			if ($out)\n			{\n				return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n			}\n		}\n\n		return \'\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_article($atts = array(), $thing = \'\')\n	{\n		global $is_article_body, $has_article_tag;\n\n		if ($is_article_body)\n		{\n			trigger_error(gTxt(\'article_tag_illegal_body\'));\n			return \'\';\n		}\n\n		$has_article_tag = true;\n\n		return upm_file_parseArticles($atts, 0, $thing);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_doArticles($atts, $iscustom = 0, $thing = \'\')\n	{\n		global $pretext, $prefs;\n\n		extract($pretext);\n		extract($prefs);\n\n		$customFields = getCustomFields();\n		$customlAtts = array_null(array_flip($customFields));\n\n		$theAtts = lAtts(array(\n			\'allowoverride\'   => (!$q and !$iscustom),\n			\'author\'				  => \'\',\n			\'break\'           => \'\',\n			\'category\'			  => \'\',\n			\'class\'           => \'\',\n			\'excerpted\'			  => \'\',\n			\'form\'					  => \'default\',\n			\'frontpage\'			  => \'\',\n			\'id\'						  => \'\',\n			\'include_expired\' => ($publish_expired_articles ? \'yes\' : \'no\'),\n			\'keywords\'			  => \'\',\n			\'label\'           => \'\',\n			\'labeltag\'        => \'\',\n			\'limit\'					  => 10,\n			\'listform\'			  => \'\',\n			\'month\'					  => \'\',\n			\'offset\'				  => 0,\n			\'pageby\'				  => \'\',\n			\'pgonly\'				  => 0,\n			\'searchall\'			  => 1,\n			\'searchform\'		  => \'\',\n			\'searchsticky\'	  => 0,\n			\'section\'				  => \'\',\n			\'sort\'					  => \'\',\n			\'sortby\'				  => \'\',\n			\'sortdir\'				  => \'\',\n			\'status\'				  => \'4\',\n			\'time\'					  => \'past\',\n			\'withfiles\'			  => false,\n			\'wraptag\'         => \'\',\n		) + $customlAtts, $atts);\n\n		// if an article ID is specified, treat it as a custom list\n		$iscustom = !empty($theAtts[\'id\']) ? true : $iscustom;\n\n		// for the article tag, some attributes are taken from globals\n		// override them before extract\n		if (!$iscustom)\n		{\n			$theAtts[\'author\']		= !empty($author) ? \"a.$author\" : \'\';\n			$theAtts[\'category\']	= ($c) ? \"a.$c\" : \'\';\n			$theAtts[\'excerpted\'] = \'\';\n			$theAtts[\'frontpage\'] = ($s && $s == \'default\') ? true : false;\n			$theAtts[\'month\']			= !empty($month) ? \"a.$month\" : \'\';\n			$theAtts[\'section\']		= ($s && $s != \'default\') ? \"a.$s\" : \'\';\n		}\n\n		extract($theAtts);\n\n		// if a listform is specified, $thing is for doArticle() - hence ignore here.\n		if (!empty($listform))\n		{\n			$thing = \'\';\n		}\n\n		$pageby = (empty($pageby) ? $limit : $pageby);\n\n		// treat sticky articles differently wrt search filtering, etc\n		$status = in_array(strtolower($status), array(\'sticky\', \'5\')) ? 5 : 4;\n		$issticky = ($status == 5);\n\n		// give control to search, if necesary\n		if ($q && !$iscustom && !$issticky)\n		{\n			include_once(txpath.\'/publish/search.php\');\n\n			$s_filter = ($searchall) ? upm_file_filterSearch() : \'\';\n\n			$q = doSlash($q);\n\n      // searchable article fields are limited to the columns of\n      // the textpattern table and a matching fulltext index must exist.\n			$cols = do_list($searchable_article_fields);\n\n			if (empty($cols) or $cols[0] == \'\')\n			{\n				$cols = array(\'a.Title\', \'a.Body\');\n			}\n\n			$match = \', match (\'.join(\', \', $cols).\") against (\'$q\') as score\";\n\n			for ($i = 0; $i < count($cols); $i++)\n			{\n				$cols[$i] = \"$cols[$i] rlike \'$q\'\";\n			}\n\n			$search = \" and (\".join(\" or \", $cols).\") $s_filter\";\n\n			// searchall=0 can be used to show search results for the current section only\n			if ($searchall)\n			{\n				$section = \'\';\n			}\n\n			if (!$sort)\n			{\n				$sort = \"downloads desc, score desc\";\n			}\n		}\n\n		else\n		{\n			$match = $search = \'\';\n\n			if (!$sort)\n			{\n				$sort = \"downloads desc, Posted desc\";\n			}\n		}\n\n		// for backwards compatibility\n		// sortby and sortdir are deprecated\n		if ($sortby)\n		{\n			if (!$sortdir)\n			{\n				$sortdir = \'desc\';\n			}\n\n			$sort = \"$sortby $sortdir\";\n		}\n\n		elseif ($sortdir)\n		{\n			$sort = \"a.Posted $sortdir\";\n		}\n\n		// building query parts\n		$author	= ($author)	? \" and a.AuthorID in(\'\".join(\"\',\'\", doSlash(do_list($author))).\"\')\" : \'\';\n		$category = join(\"\',\'\", doSlash(do_list($category)));\n		$category	= ($category)	? \" and ( a.Category1 in(\'\".doSlash($category).\"\') or a.Category2 in(\'\".doSlash($category).\"\') ) \" : \'\';\n		$excerpted = ($excerpted == \'y\') ? \" and a.Excerpt != \'\'\" : \'\';\n		$frontpage = ($frontpage and (!$q or $issticky)) ? upm_file_filterFrontPage() : \'\';\n		$id	= ($id)	? \" and a.ID in(\'\".join(\',\', array_map(\'intval\', do_list($id))).\"\')\" : \'\';\n		$month = ($month) ? \" and a.Posted like \'\".doSlash($month).\"%\'\" : \'\';\n		$section = ($section)	? \" and a.Section in(\'\".join(\"\',\'\", doSlash(do_list($section))).\"\')\" : \'\';\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and a.Posted > now()\";\n			break;\n\n			default:\n				$time = \" and a.Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		// trying custom fields here\n\n		$custom = \'\';\n\n		$customFields = getCustomFields();\n\n		if ($customFields)\n		{\n			foreach ($customFields as $cField)\n			{\n				if (isset($atts[$cField]))\n				{\n					$customPairs[$cField] = $atts[$cField];\n				}\n			}\n\n			if (!empty($customPairs))\n			{\n				$custom =	 buildCustomSql($customFields, $customPairs);\n			}\n\n			else\n			{\n				$custom = \'\';\n			}\n		}\n\n		// Allow keywords for no-custom articles. That tagging mode, you know\n		if ($keywords)\n		{\n			$keys = doSlash(do_list($keywords));\n\n			foreach ($keys as $key)\n			{\n				$keyparts[] = \"find_in_set(\'\".$key.\"\', a.Keywords)\";\n			}\n\n			$keywords = \" and (\" . join(\' or \', $keyparts) . \")\";\n		}\n\n		if ($q and $searchsticky)\n		{\n			$statusq = \' and a.Status >= 4\';\n		}\n\n		elseif ($id)\n		{\n			$statusq = \' and a.Status >= 4\';\n		}\n\n		else\n		{\n			$statusq = \' and a.Status = \'.intval($status);\n		}\n\n		$where = \"1\". $statusq. $time. $search. $id. $category. $section. $excerpted. $month. $author. $keywords. $custom. $frontpage;\n\n		//do not paginate if we are on a custom list\n		if (!$iscustom and !$issticky)\n		{\n			$grand_total = getThing(\"select count(*) from \".safe_pfx_j(\'textpattern\').\" as a where $where\");\n			$total = $grand_total - $offset;\n			$numPages = ceil($total / $pageby);\n			$pg = (!$pg) ? 1 : $pg;\n			$pgoffset = $offset + (($pg - 1) * $pageby);\n\n			// send paging info to txp:newer and txp:older\n			$pageout[\'s\']						= $s;\n			$pageout[\'c\']						= $c;\n			$pageout[\'pg\']					= $pg;\n			$pageout[\'total\']				= $total;\n			$pageout[\'grand_total\'] = $grand_total;\n			$pageout[\'numPages\']		= $numPages;\n\n			global $thispage;\n\n			if (empty($thispage))\n			{\n				$thispage = $pageout;\n			}\n\n			if ($pgonly)\n			{\n				return;\n			}\n		}\n\n		else\n		{\n			$pgoffset = $offset;\n		}\n\n		$join_type = ($withfiles) ? \'right\' : \'left\';\n\n		$rs = safe_query(\"select a.*, unix_timestamp(a.Posted) as uPosted, unix_timestamp(a.Expires) as uExpires, unix_timestamp(a.LastMod) as uLastMod\n			$match\n			from \".safe_pfx(\'textpattern\').\" as a $join_type outer join \".safe_pfx(\'txp_file\').\" as f on(a.$upm_file_field = f.id)\n			where $where order by \".doSlash($sort).\" limit \".intval($pgoffset).\", \".intval($limit));\n\n		// get the form name\n		if ($q and !$iscustom and !$issticky)\n		{\n			$form = ($searchform ? $searchform : \'search_results\');\n		}\n\n		else\n		{\n			$form = ($listform ? $listform : $form);\n		}\n\n		if ($rs)\n		{\n			$count = 0;\n\n			$articles = array();\n\n			while ($a = nextRow($rs))\n			{\n				++$count;\n\n				populateArticleData($a);\n\n				global $thisarticle, $uPosted, $limit;\n\n				$thisarticle[\'is_first\'] = ($count == 1);\n				$thisarticle[\'is_last\'] = ($count == numRows($rs));\n\n				if (@constant(\'txpinterface\') === \'admin\' and gps(\'Form\'))\n				{\n					$articles[] = parse(gps(\'Form\'));\n				}\n\n				elseif ($allowoverride and $a[\'override_form\'])\n				{\n					$articles[] = parse(fetch_form($a[\'override_form\']));\n				}\n\n				else\n				{\n					$articles[] = ($thing) ? parse($thing) : parse(fetch_form($form));\n				}\n\n				// sending these to paging_link(); Required?\n				$uPosted = $a[\'uPosted\'];\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			return doLabel($label, $labeltag).doWrap($articles, $wraptag, $break, $class);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_filterFrontPage()\n	{\n		static $filterFrontPage;\n\n		if (isset($filterFrontPage))\n		{\n			return $filterFrontPage;\n		}\n\n		$rs = safe_column(\'name\', \'txp_section\', \"on_frontpage != \'1\'\");\n\n		if ($rs)\n		{\n			foreach ($rs as $name)\n			{\n				$filters[] = \" and a.Section != \'\".doSlash($name).\"\'\";\n			}\n\n			$filterFrontPage = join(\' \', $filters);\n		}\n\n		else\n		{\n			$filterFrontPage = false;\n		}\n\n		return $filterFrontPage;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_filterSearch()\n	{\n		$rs = safe_column(\'name\', \'txp_section\', \"searchable != \'1\'\");\n\n		if ($rs)\n		{\n			foreach ($rs as $name)\n			{\n				$filters[] = \" and a.Section != \'\".doSlash($name).\"\'\";\n			}\n\n			return join(\' \', $filters);\n		}\n\n		return false;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_article_custom($atts = array(), $thing = \'\')\n	{\n		return upm_file_parseArticles($atts, 1, $thing);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_parseArticles($atts, $iscustom = \'\', $thing = \'\')\n	{\n		global $pretext, $is_article_list;\n\n		$old_ial = $is_article_list;\n\n		$is_article_list = ($pretext[\'id\'] && !$iscustom) ? false : true;\n\n		article_push();\n\n		$r = ($is_article_list) ? upm_file_doArticles($atts, $iscustom, $thing) : doArticle($atts, $thing);\n\n		article_pop();\n\n		$is_article_list = $old_ial;\n\n		return $r;\n	}\n\n\n// -------------------------------------------------------------\n\n	function upm_file_prefs($event, $step)\n	{\n		global $prefs;\n\n		if ($step == \'update\')\n		{\n			$field = doSlash(strip_tags(ps(\'field\')));\n\n			safe_update(\'txp_prefs\', \"val = \'$field\'\", \"name = \'upm_file_field\'\");\n		}\n\n		extract(get_prefs());\n\n		pagetop(\'upm_file\'.sp.gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		$fields = array();\n\n		$max = get_pref(\'max_custom_fields\', 10);\n\n\n		for($i = 1; $i <= $max; $i++)\n		{\n			$field_name = \"custom_{$i}_set\";\n			$field_title = $prefs[$field_name];\n\n			if (!empty($prefs[\"custom_{$i}_set\"]))\n			{\n				$options[ \"custom_{$i}\" ] = $prefs[\"custom_{$i}_set\"];\n			}\n		}\n\n		$option_list = array();\n\n		foreach ($options as $key => $value)\n		{\n			if ($key == $upm_file_field)\n			{\n				$option_list[] = n.t.\'<option value=\"\'.$key.\'\" selected=\"selected\">\'.$value.\'</option>\';\n			}\n\n			elseif (!empty($value))\n			{\n				$option_list[] = n.t.\'<option value=\"\'.$key.\'\">\'.$value.\'</option>\';\n			}\n		}\n\n		echo n.\'<div style=\"margin: 3em auto auto auto; width: 16em;\">\'.\n		n.hed(\'upm_file\'.sp.gTxt(\'prefs\'),\'1\').\n		n.n.form(\n			n.eInput(\'upm_file_prefs\').\n			n.sInput(\'update\').\n			n.upm_file_gTxt(\'custom_field\').br.n.\'<select name=\"field\" class=\"list\">\'.join(n, $option_list).\'</select>\'.\n			br.br.\n			n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n		).n.\'</div>\';\n\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_install()\n	{\n		global $prefs;\n\n		$field = \'custom_1\';\n\n		// for upgraders\n		if (isset($prefs[\'upm_file_packets_field\']))\n		{\n			$field = $prefs[\'upm_file_packets_field\'];\n\n			safe_delete(\'txp_prefs\', \"name = \'upm_file_packets_field\' limit 1\");\n		}\n\n		if (!isset($prefs[\'upm_file_field\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_file_field\',\n				val = \'$field\',\n				html = \'text_input\',\n				prefs_id = 1,\n				type = 2,\n				event = \'admin\',\n				position = 0\n			\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_gTxt($var, $atts = array())\n	{\n		$lang = array(\n			\'custom_field\' => \'File Custom Field\',\n		);\n\n		return strtr($lang[$var], $atts);\n	}', 'a67e260ac8a6179c6fe0cdfc600d6902', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('upm_file_popper', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm_file_popper', '0.2.2', 'An easy-to-use file picker, for use with upm_file.', '<h1>upm_file_popper</h1>\n\n<p>This plugin adds a link to a popup window of a list of your files, to be used with upm_file. upm_file is required in order for upm_file_popper to work.</p>\n\n<p>The “Attach” icon is from <a href=\"http://www.famfamfam.com/lab/icons/silk/\" rel=\"nofollow\">Mark James’ Silk”icon set</a>.</p>', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_file_popper\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper\', \'upm_file_popper\');\n\n		add_privs(\'upm_file_popper_link\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_link\', \'article\');\n\n		add_privs(\'upm_file_popper_js\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_js\', \'upm_file_popper_js\', \'\', 1);\n\n		add_privs(\'upm_file_popper_img\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_img\', \'upm_file_popper_img\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper()\n	{\n		global $step, $prefs;\n\n		if ($step == \'upm_file_popper_change_pageby\')\n		{\n			event_change_pageby(\'file\');\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n\n		upm_file_popper_list();\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_list()\n	{\n		global $file_base_path, $prefs;\n\n		pagetop(upm_file_popper_gTxt(\'file_selector\'));\n\n	echo <<<css\n<style type=\"text/css\">\n<!--\nlabel {\ncursor: pointer;\ncolor: #000;\n}\n\nimg {\nborder: none;\n}\n\nul {\nmargin: 0;\npadding: 0;\nlist-style: none;\n}\n\nli {\nmargin: 0 0 2px 0;\npadding: 0;\n}\n\np {\nmargin: 5px 0;\npadding: 0;\n}\n\n.right {\ntext-align: right;\n}\n\n.center {\nmargin: 0 auto;\ntext-align: center;\n}\n\n#no {\nmargin-left: 2em;\n}\n\n#return, #success, #failed {\npadding: 1em;\ntext-align: center;\nbackground-color: #fff;\nborder: 5px solid #fc3;\n}\n-->\n</style>\n\n<script type=\"text/javascript\" src=\"index.php?event=upm_file_popper_js&#38;name=pop\"></script>\ncss;\n\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		switch ($sort)\n		{\n			case \'id\':\n				$sort_sql = \"id $dir, filename asc\";\n			break;\n\n			case \'filename\':\n				$sort_sql = \"filename $dir\";\n			break;\n\n			case \'description\':\n				$sort_sql = \'description \'.$dir.\', filename desc\';\n			break;\n\n			case \'category\':\n				$sort_sql = \"category $dir, filename asc\";\n			break;\n\n			case \'downloads\':\n				$sort_sql = \"downloads $dir, filename asc\";\n			break;\n\n			default:\n				$dir = \'desc\';\n				$sort_sql = \"filename $dir\";\n			break;\n		}\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$criteria = 1;\n\n		if ($search_method and $crit)\n		{\n			$crit_escaped = doSlash($crit);\n\n			$critsql = array(\n				\'id\'					=> \"id = \'$crit_escaped\'\",\n				\'filename\'		=> \"filename like \'%$crit_escaped%\'\",\n				\'category\'		=> \"category like \'%$crit_escaped%\'\",\n				\'description\' => \"description like \'%$crit_escaped%\'\"\n			);\n\n			if (array_key_exists($search_method, $critsql))\n			{\n				$criteria = $critsql[$search_method];\n				$limit = 500;\n			}\n\n			else\n			{\n				$search_method = \'\';\n				$crit = \'\';\n			}\n		}\n\n		else\n		{\n			$search_method = \'\';\n			$crit = \'\';\n		}\n\n		$total = safe_count(\'txp_file\', \"$criteria\");\n\n		if ($total < 1)\n		{\n			if ($criteria != 1)\n			{\n				echo n.upm_file_popper_search_form($crit, $search_method, $sort, $dir).\n					n.graf(gTxt(\'no_results_found\'), \' style=\"text-align: center;\"\');\n			}\n\n			else\n			{\n				echo n.graf(gTxt(\'no_files_recorded\'), \' style=\"text-align: center;\"\');\n			}\n\n			return;\n		}\n\n		$limit = max($prefs[\'file_list_pageby\'], 15);\n\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n		echo upm_file_popper_search_form($crit, $search_method, $sort, $dir);\n\n		$rs = safe_rows_start(\'*\', \'txp_file\', \"$criteria order by $sort_sql limit $offset, $limit\");\n\n		if ($rs)\n		{\n			$sort_link = \'upm_file_popper\'.a.\'bm=true\';\n\n			echo startTable(\'list\'),\n				tr(\n					column_head(\'ID\', \'id\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'file_name\', \'filename\', $sort_link, true,	$switch_dir, $crit, $search_method).\n					column_head(\'description\', \'description\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'file_category\', \'category\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell().\n					hCell(gTxt(\'tags\')).\n					hCell(gTxt(\'status\')).\n					column_head(\'downloads\', \'downloads\', $sort_link, true, $switch_dir, $crit, $search_method)\n				);\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				// god I hate javascript in php\n				$insert = \'<a href=\"#\" onclick=\"upm_file_popper_attachFile(\'.chr(39).$id.chr(39).\'); return false;\"><img src=\"index.php?event=upm_file_popper_img\" width=\"16px\" height=\"16px\" alt=\"\'.upm_file_popper_gTxt(\'attach_file\').\'\" title=\"\'.upm_file_popper_gTxt(\'attach_file\').\'\" /></a>\';\n\n				$tag_url = \'?event=tag\'.a.\'tag_name=file_download_link\'.a.\'id=\'.$id.a.\'description=\'.urlencode($description).\n					a.\'filename=\'.urlencode($filename);\n\n				$file_exists = file_exists(build_file_path($file_base_path, $filename));\n\n				$status = ($file_exists) ?\n					\'<span class=\"ok\">\'.gTxt(\'file_status_ok\').\'</span>\' :\n					\'<span class=\"not-ok\">\'.gTxt(\'file_status_missing\').\'</span>\';\n\n				if (!isset($downloads))\n				{\n					safe_alter(\'txp_file\', \"ADD downloads INT DEFAULT \'0\' NOT NULL\");\n					$downloads = 0;\n				}\n\n				elseif (empty($downloads))\n				{\n					$downloads = \'0\';\n				}\n\n\n				echo n.n.tr(\n\n					n.td($id).\n\n					td(\'<a href=\"#\" onclick=\"upm_file_popper_warnEditFile(\'.$id.\'); return false;\">\'.$filename.\'</a>\', 125).\n\n					td($description, 150).\n					td($category, 90).\n\n					td($insert).\n\n					td(\n						n.\'<ul>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=textile\" onclick=\"popWin(this.href, 400, 250); return false;\">Textile</a></li>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=textpattern\" onclick=\"popWin(this.href, 400, 250); return false;\">Textpattern</a></li>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=xhtml\" onclick=\"popWin(this.href, 400, 250); return false;\">XHTML</a></li>\'.\n						n.\'</ul>\'\n					, 75).\n\n					td($status, 45).\n\n					td(\n						($downloads == \'0\' ? gTxt(\'none\') : $downloads)\n					, 25)\n				);\n			}\n\n			echo endTable().\n\n				nav_form(\'upm_file_popper\', $page, $numPages, $sort, $dir, $crit, $search_method).\n\n				upm_file_popper_pageby_form().\n\n				n.tag(\n					n.graf(upm_file_popper_gTxt(\'return\')).\n					n.graf(\'<a id=\"yes\" href=\"#\" onclick=\"upm_file_popper_goBack(this.href); return false;\">\'.gTxt(\'yes\').\'</a>.\'.\n						\' <a id=\"no\" href=\"#\" onclick=\"upm_file_popper_done(\\\'return\\\'); return false;\">\'.gTxt(\'no\').\'</a>.\')\n				, \'div\',	\' id=\"return\" style=\"display: none; width: 200px; height: 75px;\"\').\n\n				n.graf(upm_file_popper_gTxt(\'success\'), \' id=\"success\" style=\"display: none; width: 150px; height: 35px;\"\').\n				n.graf(upm_file_popper_gTxt(\'failed\'), \' id=\"failed\" style=\"display: none; width: 150px; height: 35px;\"\');\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_search_form($crit, $method, $sort, $dir)\n	{\n		$default_method = \'filename\';\n\n		$method = ($method) ? $method : $default_method;\n\n		$methods =	array(\n			\'id\'					=> gTxt(\'id\'),\n			\'filename\'		=> gTxt(\'file_name\'),\n			\'description\' => gTxt(\'description\'),\n			\'category\'		=> gTxt(\'file_category\')\n		);\n\n		return n.n.\'<form method=\"get\" action=\"index.php\" style=\"margin: auto; text-align: center;\">\'.\n			graf(\n				\'<label for=\"upm-file-popper-search\">\'.gTxt(\'search\').\'</label>\'.sp.\n				selectInput(\'search_method\', $methods, $method, \'\', \'\', \'upm-file-popper-search\').sp.\n				fInput(\'text\', \'crit\', $crit, \'edit\', \'\', \'\', \'15\').\n				eInput(\'upm_file_popper\').\n				hInput(\'bm\',\'true\').\n				fInput(\'submit\', \'search\', gTxt(\'go\'), \'smallerbox\')\n			).\n\n			($crit ? n.graf(\'<a href=\"?event=upm_file_popper\'.a.\'bm=true\">\'.upm_file_popper_gTxt(\'view_all\').\'</a>\') : \'\').\n\n		n.\'</form>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_pageby_form()\n	{\n		global $prefs;\n\n		$vals = array(\n			15	=> 15,\n			25	=> 25,\n			50	=> 50,\n			100 => 100\n		);\n\n		$select_page = selectInput(\'qty\', $vals, $prefs[\'file_list_pageby\'], \'\', 1);\n\n		// proper localisation\n		$page = str_replace(\'{page}\', $select_page, gTxt(\'view_per_page\'));\n\n		return form(\n			\'<div style=\"margin: auto; text-align: center;\">\'.\n				$page.\n				eInput(\'upm_file_popper\').\n				sInput(\'upm_file_popper_change_pageby\').\n				hInput(\'bm\', \'1\').\n				\'<noscript> <input type=\"submit\" value=\"\'.gTxt(\'go\').\'\" class=\"smallerbox\" /></noscript>\'.\n			\'</div>\'\n		);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_link()\n	{\n		$view = gps(\'view\');\n\n		if ($view == \'text\' or empty($view))\n		{\n			echo <<<form\n\n<form action=\"\">\n<input type=\"hidden\" id=\"upm_file_popper_start\" value=\"\">\n</form>\n\n<script type=\"text/javascript\" src=\"index.php?event=upm_file_popper_js&#38;name=link\"></script>\nform;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_js()\n	{\n		global $prefs;\n\n		while (@ob_end_clean());\n\n		header(\"Content-type: text/javascript\");\n\n		if (empty($prefs[\'upm_file_field\']))\n		{\n			$lang_forgot_field = upm_file_popper_gTxt(\'forgot_field\');\n\n			echo <<<js\n/*\nupm_file_popper - custom field has not been defined\n*/\n\n$(document).ready(function() {\n	alert(\'$lang_forgot_field\');\n});\njs;\n			exit(0);\n		}\n\n		else\n		{\n			$upm_file_field = !empty($prefs[\'upm_file_field\']) ? $prefs[\'upm_file_field\'] : \'\';\n			$upm_file_field = str_replace(\'_\', \'-\', $upm_file_field);\n		}\n\n		if (gps(\'name\') == \'link\')\n		{\n			$txt = gTxt(\'advanced_options\');\n\n			$lang_attach_file = upm_file_popper_gTxt(\'attach_file\');\n\n			echo <<<js\n/*\nupm_file_popper - article window\n*/\n\n$(document).ready(function() {\n	// ------------------------------\n	// setup article edit tracking\n\n	$(\'#upm_file_popper_start\').val( $(\'body\').val() );\n\n	// ------------------------------\n	// create and insert popup link\n\n	var w = 575;\n	var h = 375;\n\n	var t = (screen.height) ? (screen.height - h) / 2 : 0;\n	var l = (screen.width) ? (screen.width - w) / 2 : 0;\n\n	var a = $(document.createElement(\'a\')).\n		text(\'$lang_attach_file\').\n		attr({\n			target: \'_blank\',\n			href: \'?event=upm_file_popper\\u0026bm=1\'\n		}).\n		click(function () {\n			var upm_file_popper = window.open(this.href, \'upm_file_popper\', \'top = \'+t+\', left = \'+l+\', width = \'+w+\', height = \'+h+\', toolbar = no, location = no, directories = no, status = yes, menubar = no, scrollbars = yes, copyhistory = no, resizable = yes\');\n			upm_file_popper.focus();\n			return false;\n		});\n\n	var link = $(document.createElement(\'h3\')).\n		addClass(\'plain\').\n		append(a);\n\n	$(\'h3.plain > a:contains(\"$txt\")\').\n		before(link);\n});\njs;\n\n		exit(0);\n	}\n\n	elseif (gps(\'name\') == \'pop\')\n	{\n		$lang_check_leave = upm_file_popper_gTxt(\'check_leave\');\n\n		echo <<<js\n/*\nupm_file_popper - popup window\n*/\n\n	function upm_file_popper_attachFile(id)\n	{\n		var field = $(\'#$upm_file_field\', window.opener.document).val();\n\n		if ($(\'form[name=\"article\"]\', window.opener))\n		{\n			if (field == \'\')\n			{\n				$(\'#$upm_file_field\', window.opener.document).val(id);\n				return upm_file_popper_showMsg(\'success\');\n			}\n\n			else\n			{\n				$(\'#$upm_file_field\', window.opener.document).val(field + \',\' + id);\n				return upm_file_popper_showMsg(\'success\');\n			}\n		}\n\n		return upm_file_popper_showMsg(\'failed\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_warnEditFile(id)\n	{\n		if ($(\'#body\', window.opener.document).val() != $(\'#upm_file_popper_start\', window.opener.document).val())\n		{\n			var check = confirm(\'$lang_check_leave\');\n\n			if (check)\n			{\n				upm_file_popper_goEditFile(id);\n			}\n		}\n\n		else\n		{\n			upm_file_popper_goEditFile(id);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_goEditFile(id)\n	{\n		window.opener.focus();\n\n		if ($(\'input[name=\"step\"]\', window.opener.document).val() != \'create\')\n		{\n			$(\'#yes\').attr(\'href\', window.opener.document.location);\n			upm_file_popper_showMsg(\'return\');\n		}\n\n		window.opener.document.location = \'?event=file\\u0026step=file_edit\\u0026id=\'+id;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_goBack(url)\n	{\n		window.opener.focus();\n\n		upm_file_popper_done(\'return\');\n\n		window.opener.document.location = url;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_showMsg(id)\n	{\n		var msg = $(\'#\'+id);\n\n		msg.css({\n			position: \'absolute\',\n			top:			100 + upm_file_popper_scrollTop() + \'px\',\n			left:			( (upm_file_popper_pageWidth() / 2) - (parseInt(msg.css(\'width\')) / 2) ) + upm_file_popper_scrollLeft() + \'px\',\n			display:	\'block\'\n		});\n\n		if (id != \'return\')\n		{\n			setTimeout(\"upm_file_popper_done(\'\"+id+\"\')\", 1000);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_done(id)\n	{\n		$(\'#\'+id).css(\'display\', \'none\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_pageWidth()\n	{\n		if (self.innerWidth)\n		{\n			return self.innerWidth;\n		}\n\n		else if (document.documentElement && document.documentElement.clientWidth)\n		{\n			return document.documentElement.clientWidth;\n		}\n\n		return 0;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_scrollTop()\n	{\n		if (self.pageYOffset)\n		{\n			return self.pageYOffset;\n		}\n\n		else if (document.documentElement && document.documentElement.scrollTop)\n		{\n			return document.documentElement.scrollTop;\n		}\n\n		return 0;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_scrollLeft()\n	{\n		if (self.pageYOffset)\n		{\n			return self.pageXOffset;\n		}\n\n		else if (document.documentElement && document.documentElement.scrollTop)\n		{\n			return document.documentElement.scrollLeft;\n		}\n\n		return 0;\n	}\njs;\n\n		exit(0);\n	}\n}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_img()\n	{\n		while (@ob_end_clean());\n\n		header(\"Content-type: image/gif\");\n\n		echo base64_decode(\'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6\'.\n			\'QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYO\'.\n			\'khlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VT\'.\n			\'UfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/\'.\n			\'We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI\'.\n			\'9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+\'.\n			\'b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKI\'.\n			\'J0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJ\'.\n			\'bDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3\'.\n			\'tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1\'.\n			\'S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAj\'.\n			\'bcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAAB\'.\n			\'JRU5ErkJggg==\');\n\n		exit(0);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_gTxt($var, $atts = array())\n	{\n		$lang = array(\n			\'attach\'              => \'Attach\',\n			\'attach_file\'         => \'Attach File\',\n			\'check_leave\'         => \'You have made changes to the current post which you have not saved. Continue without saving?\',\n			\'description_for\'     => \'Description for\',\n			\'failed\'              => \'Insert failed.\',\n			\'file_selector\'       => \'File Selector\',\n			\'forgot_field\'        => \'You forgot to define the custom field for upm_file/upm_file_popper to use. Ensure that you have a custom field available, and that you have upm_file installed and activated, then visit Extensions > upm_file.\',\n			\'no_description\'      => \'No description has been defined for this file.\',\n			\'packet_custom_field\' => \'Packet Custom Field\',\n			\'return\'              => \'Return to editing the post?\',\n			\'success\'             => \'Success.\',\n			\'view_all\'            => \'View All\'\n		);\n\n		return strtr($lang[$var], $atts);\n	}', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_file_popper\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper\', \'upm_file_popper\');\n\n		add_privs(\'upm_file_popper_link\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_link\', \'article\');\n\n		add_privs(\'upm_file_popper_js\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_js\', \'upm_file_popper_js\', \'\', 1);\n\n		add_privs(\'upm_file_popper_img\', \'1,2,3,4,5\');\n		register_callback(\'upm_file_popper_img\', \'upm_file_popper_img\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper()\n	{\n		global $step, $prefs;\n\n		if ($step == \'upm_file_popper_change_pageby\')\n		{\n			event_change_pageby(\'file\');\n			$GLOBALS[\'prefs\'] = get_prefs();\n		}\n\n		upm_file_popper_list();\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_list()\n	{\n		global $file_base_path, $prefs;\n\n		pagetop(upm_file_popper_gTxt(\'file_selector\'));\n\n	echo <<<css\n<style type=\"text/css\">\n<!--\nlabel {\ncursor: pointer;\ncolor: #000;\n}\n\nimg {\nborder: none;\n}\n\nul {\nmargin: 0;\npadding: 0;\nlist-style: none;\n}\n\nli {\nmargin: 0 0 2px 0;\npadding: 0;\n}\n\np {\nmargin: 5px 0;\npadding: 0;\n}\n\n.right {\ntext-align: right;\n}\n\n.center {\nmargin: 0 auto;\ntext-align: center;\n}\n\n#no {\nmargin-left: 2em;\n}\n\n#return, #success, #failed {\npadding: 1em;\ntext-align: center;\nbackground-color: #fff;\nborder: 5px solid #fc3;\n}\n-->\n</style>\n\n<script type=\"text/javascript\" src=\"index.php?event=upm_file_popper_js&#38;name=pop\"></script>\ncss;\n\n		extract(gpsa(array(\'page\', \'sort\', \'dir\', \'crit\', \'search_method\')));\n\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n\n		switch ($sort)\n		{\n			case \'id\':\n				$sort_sql = \"id $dir, filename asc\";\n			break;\n\n			case \'filename\':\n				$sort_sql = \"filename $dir\";\n			break;\n\n			case \'description\':\n				$sort_sql = \'description \'.$dir.\', filename desc\';\n			break;\n\n			case \'category\':\n				$sort_sql = \"category $dir, filename asc\";\n			break;\n\n			case \'downloads\':\n				$sort_sql = \"downloads $dir, filename asc\";\n			break;\n\n			default:\n				$dir = \'desc\';\n				$sort_sql = \"filename $dir\";\n			break;\n		}\n\n		$switch_dir = ($dir == \'desc\') ? \'asc\' : \'desc\';\n\n		$criteria = 1;\n\n		if ($search_method and $crit)\n		{\n			$crit_escaped = doSlash($crit);\n\n			$critsql = array(\n				\'id\'					=> \"id = \'$crit_escaped\'\",\n				\'filename\'		=> \"filename like \'%$crit_escaped%\'\",\n				\'category\'		=> \"category like \'%$crit_escaped%\'\",\n				\'description\' => \"description like \'%$crit_escaped%\'\"\n			);\n\n			if (array_key_exists($search_method, $critsql))\n			{\n				$criteria = $critsql[$search_method];\n				$limit = 500;\n			}\n\n			else\n			{\n				$search_method = \'\';\n				$crit = \'\';\n			}\n		}\n\n		else\n		{\n			$search_method = \'\';\n			$crit = \'\';\n		}\n\n		$total = safe_count(\'txp_file\', \"$criteria\");\n\n		if ($total < 1)\n		{\n			if ($criteria != 1)\n			{\n				echo n.upm_file_popper_search_form($crit, $search_method, $sort, $dir).\n					n.graf(gTxt(\'no_results_found\'), \' style=\"text-align: center;\"\');\n			}\n\n			else\n			{\n				echo n.graf(gTxt(\'no_files_recorded\'), \' style=\"text-align: center;\"\');\n			}\n\n			return;\n		}\n\n		$limit = max($prefs[\'file_list_pageby\'], 15);\n\n		list($page, $offset, $numPages) = pager($total, $limit, $page);\n\n		echo upm_file_popper_search_form($crit, $search_method, $sort, $dir);\n\n		$rs = safe_rows_start(\'*\', \'txp_file\', \"$criteria order by $sort_sql limit $offset, $limit\");\n\n		if ($rs)\n		{\n			$sort_link = \'upm_file_popper\'.a.\'bm=true\';\n\n			echo startTable(\'list\'),\n				tr(\n					column_head(\'ID\', \'id\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'file_name\', \'filename\', $sort_link, true,	$switch_dir, $crit, $search_method).\n					column_head(\'description\', \'description\', $sort_link, true, $switch_dir, $crit, $search_method).\n					column_head(\'file_category\', \'category\', $sort_link, true, $switch_dir, $crit, $search_method).\n					hCell().\n					hCell(gTxt(\'tags\')).\n					hCell(gTxt(\'status\')).\n					column_head(\'downloads\', \'downloads\', $sort_link, true, $switch_dir, $crit, $search_method)\n				);\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				// god I hate javascript in php\n				$insert = \'<a href=\"#\" onclick=\"upm_file_popper_attachFile(\'.chr(39).$id.chr(39).\'); return false;\"><img src=\"index.php?event=upm_file_popper_img\" width=\"16px\" height=\"16px\" alt=\"\'.upm_file_popper_gTxt(\'attach_file\').\'\" title=\"\'.upm_file_popper_gTxt(\'attach_file\').\'\" /></a>\';\n\n				$tag_url = \'?event=tag\'.a.\'tag_name=file_download_link\'.a.\'id=\'.$id.a.\'description=\'.urlencode($description).\n					a.\'filename=\'.urlencode($filename);\n\n				$file_exists = file_exists(build_file_path($file_base_path, $filename));\n\n				$status = ($file_exists) ?\n					\'<span class=\"ok\">\'.gTxt(\'file_status_ok\').\'</span>\' :\n					\'<span class=\"not-ok\">\'.gTxt(\'file_status_missing\').\'</span>\';\n\n				if (!isset($downloads))\n				{\n					safe_alter(\'txp_file\', \"ADD downloads INT DEFAULT \'0\' NOT NULL\");\n					$downloads = 0;\n				}\n\n				elseif (empty($downloads))\n				{\n					$downloads = \'0\';\n				}\n\n\n				echo n.n.tr(\n\n					n.td($id).\n\n					td(\'<a href=\"#\" onclick=\"upm_file_popper_warnEditFile(\'.$id.\'); return false;\">\'.$filename.\'</a>\', 125).\n\n					td($description, 150).\n					td($category, 90).\n\n					td($insert).\n\n					td(\n						n.\'<ul>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=textile\" onclick=\"popWin(this.href, 400, 250); return false;\">Textile</a></li>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=textpattern\" onclick=\"popWin(this.href, 400, 250); return false;\">Textpattern</a></li>\'.\n						n.t.\'<li><a target=\"_blank\" href=\"\'.$tag_url.a.\'type=xhtml\" onclick=\"popWin(this.href, 400, 250); return false;\">XHTML</a></li>\'.\n						n.\'</ul>\'\n					, 75).\n\n					td($status, 45).\n\n					td(\n						($downloads == \'0\' ? gTxt(\'none\') : $downloads)\n					, 25)\n				);\n			}\n\n			echo endTable().\n\n				nav_form(\'upm_file_popper\', $page, $numPages, $sort, $dir, $crit, $search_method).\n\n				upm_file_popper_pageby_form().\n\n				n.tag(\n					n.graf(upm_file_popper_gTxt(\'return\')).\n					n.graf(\'<a id=\"yes\" href=\"#\" onclick=\"upm_file_popper_goBack(this.href); return false;\">\'.gTxt(\'yes\').\'</a>.\'.\n						\' <a id=\"no\" href=\"#\" onclick=\"upm_file_popper_done(\\\'return\\\'); return false;\">\'.gTxt(\'no\').\'</a>.\')\n				, \'div\',	\' id=\"return\" style=\"display: none; width: 200px; height: 75px;\"\').\n\n				n.graf(upm_file_popper_gTxt(\'success\'), \' id=\"success\" style=\"display: none; width: 150px; height: 35px;\"\').\n				n.graf(upm_file_popper_gTxt(\'failed\'), \' id=\"failed\" style=\"display: none; width: 150px; height: 35px;\"\');\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_search_form($crit, $method, $sort, $dir)\n	{\n		$default_method = \'filename\';\n\n		$method = ($method) ? $method : $default_method;\n\n		$methods =	array(\n			\'id\'					=> gTxt(\'id\'),\n			\'filename\'		=> gTxt(\'file_name\'),\n			\'description\' => gTxt(\'description\'),\n			\'category\'		=> gTxt(\'file_category\')\n		);\n\n		return n.n.\'<form method=\"get\" action=\"index.php\" style=\"margin: auto; text-align: center;\">\'.\n			graf(\n				\'<label for=\"upm-file-popper-search\">\'.gTxt(\'search\').\'</label>\'.sp.\n				selectInput(\'search_method\', $methods, $method, \'\', \'\', \'upm-file-popper-search\').sp.\n				fInput(\'text\', \'crit\', $crit, \'edit\', \'\', \'\', \'15\').\n				eInput(\'upm_file_popper\').\n				hInput(\'bm\',\'true\').\n				fInput(\'submit\', \'search\', gTxt(\'go\'), \'smallerbox\')\n			).\n\n			($crit ? n.graf(\'<a href=\"?event=upm_file_popper\'.a.\'bm=true\">\'.upm_file_popper_gTxt(\'view_all\').\'</a>\') : \'\').\n\n		n.\'</form>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_pageby_form()\n	{\n		global $prefs;\n\n		$vals = array(\n			15	=> 15,\n			25	=> 25,\n			50	=> 50,\n			100 => 100\n		);\n\n		$select_page = selectInput(\'qty\', $vals, $prefs[\'file_list_pageby\'], \'\', 1);\n\n		// proper localisation\n		$page = str_replace(\'{page}\', $select_page, gTxt(\'view_per_page\'));\n\n		return form(\n			\'<div style=\"margin: auto; text-align: center;\">\'.\n				$page.\n				eInput(\'upm_file_popper\').\n				sInput(\'upm_file_popper_change_pageby\').\n				hInput(\'bm\', \'1\').\n				\'<noscript> <input type=\"submit\" value=\"\'.gTxt(\'go\').\'\" class=\"smallerbox\" /></noscript>\'.\n			\'</div>\'\n		);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_link()\n	{\n		$view = gps(\'view\');\n\n		if ($view == \'text\' or empty($view))\n		{\n			echo <<<form\n\n<form action=\"\">\n<input type=\"hidden\" id=\"upm_file_popper_start\" value=\"\">\n</form>\n\n<script type=\"text/javascript\" src=\"index.php?event=upm_file_popper_js&#38;name=link\"></script>\nform;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_js()\n	{\n		global $prefs;\n\n		while (@ob_end_clean());\n\n		header(\"Content-type: text/javascript\");\n\n		if (empty($prefs[\'upm_file_field\']))\n		{\n			$lang_forgot_field = upm_file_popper_gTxt(\'forgot_field\');\n\n			echo <<<js\n/*\nupm_file_popper - custom field has not been defined\n*/\n\n$(document).ready(function() {\n	alert(\'$lang_forgot_field\');\n});\njs;\n			exit(0);\n		}\n\n		else\n		{\n			$upm_file_field = !empty($prefs[\'upm_file_field\']) ? $prefs[\'upm_file_field\'] : \'\';\n			$upm_file_field = str_replace(\'_\', \'-\', $upm_file_field);\n		}\n\n		if (gps(\'name\') == \'link\')\n		{\n			$txt = gTxt(\'advanced_options\');\n\n			$lang_attach_file = upm_file_popper_gTxt(\'attach_file\');\n\n			echo <<<js\n/*\nupm_file_popper - article window\n*/\n\n$(document).ready(function() {\n	// ------------------------------\n	// setup article edit tracking\n\n	$(\'#upm_file_popper_start\').val( $(\'body\').val() );\n\n	// ------------------------------\n	// create and insert popup link\n\n	var w = 575;\n	var h = 375;\n\n	var t = (screen.height) ? (screen.height - h) / 2 : 0;\n	var l = (screen.width) ? (screen.width - w) / 2 : 0;\n\n	var a = $(document.createElement(\'a\')).\n		text(\'$lang_attach_file\').\n		attr({\n			target: \'_blank\',\n			href: \'?event=upm_file_popper\\u0026bm=1\'\n		}).\n		click(function () {\n			var upm_file_popper = window.open(this.href, \'upm_file_popper\', \'top = \'+t+\', left = \'+l+\', width = \'+w+\', height = \'+h+\', toolbar = no, location = no, directories = no, status = yes, menubar = no, scrollbars = yes, copyhistory = no, resizable = yes\');\n			upm_file_popper.focus();\n			return false;\n		});\n\n	var link = $(document.createElement(\'h3\')).\n		addClass(\'plain\').\n		append(a);\n\n	$(\'h3.plain > a:contains(\"$txt\")\').\n		before(link);\n});\njs;\n\n		exit(0);\n	}\n\n	elseif (gps(\'name\') == \'pop\')\n	{\n		$lang_check_leave = upm_file_popper_gTxt(\'check_leave\');\n\n		echo <<<js\n/*\nupm_file_popper - popup window\n*/\n\n	function upm_file_popper_attachFile(id)\n	{\n		var field = $(\'#$upm_file_field\', window.opener.document).val();\n\n		if ($(\'form[name=\"article\"]\', window.opener))\n		{\n			if (field == \'\')\n			{\n				$(\'#$upm_file_field\', window.opener.document).val(id);\n				return upm_file_popper_showMsg(\'success\');\n			}\n\n			else\n			{\n				$(\'#$upm_file_field\', window.opener.document).val(field + \',\' + id);\n				return upm_file_popper_showMsg(\'success\');\n			}\n		}\n\n		return upm_file_popper_showMsg(\'failed\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_warnEditFile(id)\n	{\n		if ($(\'#body\', window.opener.document).val() != $(\'#upm_file_popper_start\', window.opener.document).val())\n		{\n			var check = confirm(\'$lang_check_leave\');\n\n			if (check)\n			{\n				upm_file_popper_goEditFile(id);\n			}\n		}\n\n		else\n		{\n			upm_file_popper_goEditFile(id);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_goEditFile(id)\n	{\n		window.opener.focus();\n\n		if ($(\'input[name=\"step\"]\', window.opener.document).val() != \'create\')\n		{\n			$(\'#yes\').attr(\'href\', window.opener.document.location);\n			upm_file_popper_showMsg(\'return\');\n		}\n\n		window.opener.document.location = \'?event=file\\u0026step=file_edit\\u0026id=\'+id;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_goBack(url)\n	{\n		window.opener.focus();\n\n		upm_file_popper_done(\'return\');\n\n		window.opener.document.location = url;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_showMsg(id)\n	{\n		var msg = $(\'#\'+id);\n\n		msg.css({\n			position: \'absolute\',\n			top:			100 + upm_file_popper_scrollTop() + \'px\',\n			left:			( (upm_file_popper_pageWidth() / 2) - (parseInt(msg.css(\'width\')) / 2) ) + upm_file_popper_scrollLeft() + \'px\',\n			display:	\'block\'\n		});\n\n		if (id != \'return\')\n		{\n			setTimeout(\"upm_file_popper_done(\'\"+id+\"\')\", 1000);\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_done(id)\n	{\n		$(\'#\'+id).css(\'display\', \'none\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_pageWidth()\n	{\n		if (self.innerWidth)\n		{\n			return self.innerWidth;\n		}\n\n		else if (document.documentElement && document.documentElement.clientWidth)\n		{\n			return document.documentElement.clientWidth;\n		}\n\n		return 0;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_scrollTop()\n	{\n		if (self.pageYOffset)\n		{\n			return self.pageYOffset;\n		}\n\n		else if (document.documentElement && document.documentElement.scrollTop)\n		{\n			return document.documentElement.scrollTop;\n		}\n\n		return 0;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_scrollLeft()\n	{\n		if (self.pageYOffset)\n		{\n			return self.pageXOffset;\n		}\n\n		else if (document.documentElement && document.documentElement.scrollTop)\n		{\n			return document.documentElement.scrollLeft;\n		}\n\n		return 0;\n	}\njs;\n\n		exit(0);\n	}\n}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_img()\n	{\n		while (@ob_end_clean());\n\n		header(\"Content-type: image/gif\");\n\n		echo base64_decode(\'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6\'.\n			\'QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYO\'.\n			\'khlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VT\'.\n			\'UfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/\'.\n			\'We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI\'.\n			\'9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+\'.\n			\'b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKI\'.\n			\'J0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJ\'.\n			\'bDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3\'.\n			\'tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1\'.\n			\'S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAj\'.\n			\'bcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAAB\'.\n			\'JRU5ErkJggg==\');\n\n		exit(0);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_file_popper_gTxt($var, $atts = array())\n	{\n		$lang = array(\n			\'attach\'              => \'Attach\',\n			\'attach_file\'         => \'Attach File\',\n			\'check_leave\'         => \'You have made changes to the current post which you have not saved. Continue without saving?\',\n			\'description_for\'     => \'Description for\',\n			\'failed\'              => \'Insert failed.\',\n			\'file_selector\'       => \'File Selector\',\n			\'forgot_field\'        => \'You forgot to define the custom field for upm_file/upm_file_popper to use. Ensure that you have a custom field available, and that you have upm_file installed and activated, then visit Extensions > upm_file.\',\n			\'no_description\'      => \'No description has been defined for this file.\',\n			\'packet_custom_field\' => \'Packet Custom Field\',\n			\'return\'              => \'Return to editing the post?\',\n			\'success\'             => \'Success.\',\n			\'view_all\'            => \'View All\'\n		);\n\n		return strtr($lang[$var], $atts);\n	}', '0a68f5513785f8f020895e4d5286126e', '', '', 1, 5, 0),
('ja_browser_png', 0, 'Jaspaul Aggarwal', 'http://www.jaspaul.com/', '0.1', 'The \'get_browser_png\' plugin allows transparent images with the \'png\' extension to be displayed in browsers in both Internet Explorer and other browsers.', '\nh1. ja_browser_png help file.\n\nPortable Network Graphics (PNGs) are highly compressed image format which has many advantages over other image formats such as being free from patents and very high quality. However Internet Explorer seems to give unexpected results after showing transparent PNG, giving a dark background.\n\nThe \'ja_browser_png textpattern\' plugin is designed to display transparent PNG images in Internet Explorer as well as other browsers. To call the plugin simply use the \'picture\' attribute to point to the transparent PNG image you wish to show, for example see below:\n&#60;txp:ja_browser_png picture=\'/images/7.png\' /&#62;\n', '\nfunction ja_browser_png($atts) {\n\n	if (is_array($atts)) {\n		extract($atts);\n	} else {\n		# error check.\n		return \"Error: get_browser_png must have a picture attribute.  Please edit the plugin call.<br/>\";\n	}\n\n	$viewer = getenv(\"HTTP_USER_AGENT\");\n	if (preg_match ( \"/MSIE/i\", \"$viewer\" ) )\n	{\n		#If internet explorer\n		return \"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'$picture\');\";\n	} else\n	{\n		#Otherwise for other browsers\n		return \"background-image:url(\'$picture\');\";\n	}\n}\n', '\nfunction ja_browser_png($atts) {\n\n	if (is_array($atts)) {\n		extract($atts);\n	} else {\n		# error check.\n		return \"Error: get_browser_png must have a picture attribute.  Please edit the plugin call.<br/>\";\n	}\n\n	$viewer = getenv(\"HTTP_USER_AGENT\");\n	if (preg_match ( \"/MSIE/i\", \"$viewer\" ) )\n	{\n		#If internet explorer\n		return \"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'$picture\');\";\n	} else\n	{\n		#Otherwise for other browsers\n		return \"background-image:url(\'$picture\');\";\n	}\n}\n', '513cb3b8de61b03fec2a0ad82baae84d', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('jea_pygments_txp', 0, 'Julius Adorf', 'http://bitbucket.org/jeadorf/pygments-txp', '0.2', 'Syntax highlighting using pygments', '<h1>Syntax highlighting using pygments</h1>\n\n	<p>This plugin adds syntax hightlighting to textpattern. You can embed code in<br />\narticles. The plugin is based on <a href=\"http://pygments.org/\">Pygments</a>.</p>\n\n	<h2>Installation</h2>\n\n	<ol>\n		<li>Add jea_pygments_txp plugin to textpattern</li>\n		<li>Install python on your webserver</li>\n		<li>Install pygments on your webserver</li>\n	</ol>\n\n	<h3>Optional dependencies</h3>\n\n	<ul>\n		<li>Support for customization:\n    Install textpattern plugin <a href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a></li>\n	</ul>\n\n	<h3>Troubleshooting</h3>\n\n	<p>If you run into troubles, simply ask! Here are some tips that might help:</p>\n\n	<ul>\n		<li>Verify that pygmentize is correctly installed and accessible from the server&#8217;s shell</li>\n		<li>Make sure &#8216;/usr/bin/pygmentize&#8217; is available. If you have a local python environment\n  (e.g. <em>virtualenv</em>) you have to specify the script location manually in\n  the plugin options tab in your admin area. You might have to hit save on the\n  article where you use the highlighting tag in order to prevent textpattern from serving\n  the cached version of your article output.</li>\n		<li>When in doubt: do not waste time and ask. That might save you some time.</li>\n		<li>Make sure that your Pygments installation supports the language of the file you\n  want to highlight.</li>\n	</ul>\n\n	<h2>Usage</h2>\n\n	<p>For embedding highlighted code in a textpattern article, use the tag <strong>&lt;txp:jea_highlight /&gt;</strong>.<br />\nIt supports the following parameters:</p>\n\n	<ul>\n		<li><strong>file</strong> &#8211; The name of an uploaded file in &lt;textpattern&gt;/files. Paths are\n    not allowed. File names are restricted to alphanumerical characters,\n    underscores, dots, and hyphens. Files must not begin with a dot.</li>\n		<li><strong>linenos</strong> &#8211; Enables line numbers if set to &#8216;true&#8217;. Corresponds to Pygments&#8217;\n    linenos=inline option.</li>\n		<li><strong>from</strong> &#8211; The first line to display.</li>\n		<li><strong>to</strong> &#8211; The last line to display.</li>\n		<li><strong>lang</strong> &#8211; Specifies the language of the code to embed. If not given\n    Pygments tries to guess the language by looking at the file name or even at\n    the content (which might fail). Find out with &#8216;pygmentize -L lexers&#8217; or go\n    to plugin options to see what languages are available.</li>\n	</ul>\n\n	<p>You can also embed and highlight code directly in articles. Therefore, simply wrap the code to highlight<br />\nlike this:</p>\n\n<pre>&lt;txp:jea_highlight lang=&#39;python&#39;&gt;&lt;notextile&gt;\ndef hypotenuse(a, b):\n    return sqrt(a*a + b*b);\n&lt;/notextile&gt;&lt;/txp:jea_highlight&gt;\n</pre>\n\n	<p>The &#8216;notextile&#8217; tags ensure that textpattern will not process the tag content using<br />\nthe textile markup generation engine. Be careful though, if you try to highlight<br />\ntextile markup that contains &#8216;notextile&#8217; tags itself! And do not include any whitespace<br />\nbetween the &#8216;notextile&#8217; and the &#8216;txp:jea_highlight&#8217; tags. If in doubt, highlight <br />\ncode using the &#8216;file&#8217; attribute.</p>\n\n	<h3>Examples</h3>\n\n<pre>&lt;txp:jea_highlight file=&#39;join.sql&#39; /&gt;\n&lt;txp:jea_highlight file=&#39;oca.ml&#39; linenos=&#39;true&#39; /&gt;\n&lt;txp:jea_highlight file=&#39;NotTheIsland.java&#39; linenos=&#39;false&#39; /&gt;\n&lt;txp:jea_highlight file=&#39;ramide.py&#39; from=&#39;3&#39; to=&#39;14&#39;/&gt;\n</pre>\n\n	<h3>Customization</h3>\n\n	<p>The plugin can be customized in several ways. You can go to the plugin options<br />\ntab and change the default values for most of the parameters used by the<br />\n&#8216;txp:jea_highlight&#8217; tag.</p>\n\n	<ul>\n		<li><strong>Pygmentize script location</strong> &#8211; Specify the location of your pygmentize script\n    that is shipped with Pygments. Note that for security reasons the script&#8217;s file\n    name must be &#8216;pygmentize&#8217;.</li>\n	</ul>\n\n	<ul>\n		<li><strong>Line numbers</strong> &#8211; See parameter &#8216;linenos&#8217;.</li>\n	</ul>\n\n	<ul>\n		<li><strong>Inline <span class=\"caps\">CSS</span></strong> &#8211; This causes Pygments to create inline style information instead\n    of class names. Use this if you do not plan to include an extra style sheet\n    to format the highlighted code.</li>\n	</ul>\n\n	<ul>\n		<li><strong>Highlighting style</strong> &#8211; There are several predefined styles available. Find\n   out with &#8216;pygmentize -L styles&#8217; what styles are available, or look in the\n   plugin options.</li>\n	</ul>\n\n	<h2>Known issues</h2>\n\n	<ul>\n		<li>styles should be listed in a drop-box on the preferences panel</li>\n		<li>caching should make things go faster</li>\n		<li>character encoding not thorougly tested</li>\n	</ul>', '\n\n/* interface */\n\nfunction jea_highlight($attrs, $thing=\'\') {\n    try {\n        return jea_highlight::highlight($attrs, $thing);\n    } catch (Exception $e) {\n        return \'<p><pre>\'.$e->getMessage().\'</pre></p>\';\n    }\n}\n\n/* setup */\n\nif (@txpinterface == \'admin\') {\n    @require_plugin(\'soo_plugin_pref\');\n    add_privs(\'jea_pygments_txp\', \'1,2\');\n    add_privs(\'plugin_prefs.jea_pygments_txp\', \'1,2\');\n    register_callback(\'jea_pygments_txp_on_prefs\', \'plugin_prefs.jea_pygments_txp\');\n}\n\n/* default configuration and utilities */\n\nclass jea_pygments_txp {\n\n    public static $defaults = array(\n        \'inline_css\' => \'1\',\n        \'linenos\' => \'0\',\n        \'pygmentize\' => \'/usr/bin/pygmentize\',\n        \'style\' => \'default\',\n    );\n\n    public static $patterns = array(\n        \'file\' => \'/[a-zA-Z0-9_\\-]+(\\.[a-zA-Z0-9_\\-]+)*\\.?/\',\n        \'from\' => \'/[1-9][0-9]*/\',\n        \'inline_css\' => \'/[A-Za-z01]+/\',\n        \'lang\' => \'/[a-zA-Z0-9_\\-]*/\',\n        \'linenos\' => \'/[A-Za-z01]+/\',\n        \'pygmentize\' => \'/([0-9a-zA-Z\\-_\\/]*\\/)?pygmentize/\',\n        \'style\' => \'/[a-zA-Z0-9_\\-]*/\',\n        \'to\' => \'/[1-9][0-9]*/\',\n    );\n\n\n    public static function preferences() {\n        return array(\n            \'pygmentize\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'pygmentize\'],\n                \'html\'  => \'text_input\',\n                \'text\'  => \'Pygmentize script location\'\n            ),\n            \'linenos\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'linenos\'],\n                \'html\'  => \'yesnoradio\',\n                \'text\'  => \'Line numbers\'\n            ),\n            \'inline_css\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'inline_css\'],\n                \'html\'  => \'yesnoradio\',\n                \'text\'  => \'Inline CSS\'\n            ),\n            \'style\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'style\'],\n                \'html\'  => \'text_input\',\n                \'text\'  => \'Highlighting style\'\n            )\n\n        );\n    }\n\n    public function get_valid($name, $attrs = NULL) {\n        if ($attrs !== NULL && array_key_exists($name, $attrs)) {\n            $val = $attrs[$name];\n        } else {\n            $defaultval = NULL;\n            if (array_key_exists($name, jea_pygments_txp::$defaults)) {\n                $defaultval = jea_pygments_txp::$defaults[$name];\n            }\n            $val = get_pref(\"jea_pygments_txp.$name\", $defaultval);\n        }\n\n        if ($val === NULL || jea_pygments_txp::valid($name, $val, $ret_msg)) {\n            return $val;\n        } else {\n            throw new Exception($ret_msg);\n        }\n    }\n\n    public static function valid($name, $strval, &$ret_msg) {\n        if (array_key_exists($name, jea_pygments_txp::$patterns)) {\n            preg_match(jea_pygments_txp::$patterns[$name], $strval, $matches);\n            if (count($matches) == 0 || strlen($matches[0]) != strlen($strval)) {\n                $ret_msg = \"<p>jea_pygments_txp: invalid value for attribute \'$name\' </p>\";\n                return False;\n            } else {\n                return True;\n            }\n        } else {\n            $ret_msg = \"<p>jea_pygments_txp: invalid attribute \'$name\'</p>\";\n            return False;\n        }\n    }\n\n    public static function as_bool($s) {\n        $s = strtolower($s);\n        return (strcmp($s, \'true\') == 0\n            || strcmp($s, \'on\') == 0\n            || strcmp($s, \'yes\') == 0\n            || strcmp($s, \'1\') == 0);\n    }\n\n    public static function subprocess($cmd, $input) {\n        $dspec = array(\n            0 => array(\"pipe\", \"r\"), // stdin\n            1 => array(\"pipe\", \"w\")  // stdout\n        );\n        $p = proc_open($cmd, $dspec, $pipes, NULL, NULL);\n        if (is_resource($p)) {\n            fwrite($pipes[0], $input);\n            fclose($pipes[0]);\n\n            $output = stream_get_contents($pipes[1]);\n            fclose($pipes[1]);\n            if (proc_close($p) == 0) {\n                return $output;\n            } else {\n                throw new Exception(\"Child process exited with error\");\n            }\n        } else {\n            throw new Exception(\"Cannot execute command\");\n        }\n    }\n\n}\n\n/* preferences management */\n\nfunction jea_pygments_txp_on_prefs($event, $step) {\n    if (function_exists(\'soo_plugin_pref\')) {\n        soo_plugin_pref($event, $step, jea_pygments_txp::preferences());\n        jea_pygments_txp_print_features();\n    } else if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n        $plugin = substr($event, 13);\n        $message = \'<p style=\\\'text-align: center\\\'><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n            gTxt(\'edit_preferences\') . \':</strong><br /><br/>\' .\n            gTxt(\'install_plugin\') . \' <a\n            href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a><br/></p>\';\n        pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n        print $message;\n    }\n}\n\nfunction jea_pygments_txp_print_features() {\n    require_once(\'lib/classTextile.php\');\n    $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n    $textile = new Textile();\n    $help = $textile->TextileThis(shell_exec(escapeshellcmd(\"$pygmentize -L\")));\n    print \"<div style=\'border: 1px solid ; margin: 30px auto; padding: 20px; width: 500px\'>\n           <strong>Pygmentize features generated with \'pygmentize -L\'</strong>\n           <hr/><p/>$help</div>\";\n}\n\n/* highlight tag */\n\nclass jea_highlight {\n\n    public static function highlight($raw_attrs, $thing=\'\') {\n        $lang = jea_pygments_txp::get_valid(\'lang\', $raw_attrs);\n        $file = jea_pygments_txp::get_valid(\'file\', $raw_attrs);\n        $linenos= jea_pygments_txp::as_bool(jea_pygments_txp::get_valid(\'linenos\', $raw_attrs));\n        $from = jea_pygments_txp::get_valid(\'from\', $raw_attrs);\n        $to = jea_pygments_txp::get_valid(\'to\', $raw_attrs);\n        $style = jea_pygments_txp::get_valid(\'style\', $raw_attrs);\n        $inline_css = jea_pygments_txp::as_bool(jea_pygments_txp::get_valid(\'inline_css\', $raw_attrs));\n        $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n\n        $o = \'\';\n\n        $cmd = escapeshellcmd($pygmentize);\n        $cmd .= \' -f html\';\n\n        if ($linenos) {\n            $cmd .= \' -O linenos=inline\';\n        }\n        if ($inline_css) {\n            $cmd .= \' -O noclasses=True\';\n            // workaround for lost background issue\n            // http://dev.pocoo.org/projects/pygments/ticket/427\n            $csscmd = escapeshellcmd($pygmentize);\n            $csscmd .= \' -f html\';\n            $csscmd .= \' -S \' . escapeshellarg($style);\n            $csshack = shell_exec($csscmd);\n            if (preg_match(\'/\\.hll {([^\\}]*)}/\', $csshack, $matches)) {\n                $cmd .= \' -O \'.escapeshellarg(\'cssstyles=\'.substr($matches[0], 6, -1));\n            }\n        }\n        $cmd .= \' -O \'.escapeshellarg(\"cssclass=jea_pygments_txp_$style\");\n        $cmd .= \' -O \'.escapeshellarg(\"style=$style\");\n\n        if ($lang === NULL && $file !== NULL) {\n            $lang = jea_highlight::guess_lexer($file);\n        }\n        if ($lang === NULL) {\n            $cmd .= \' -g\';\n        } else {\n            $cmd .= \' -l \'.escapeshellarg($lang);\n        }\n\n        if ($file !== NULL) {\n            global $txpcfg;\n            $path = dirname($txpcfg[\'txpath\']) . \'/files/\' . $file;\n            if (!file_exists($path)) {\n                throw new Exception(\"<p>jea_pygments_txp: File \'$file\' does not exist.</p>\");\n            }\n            $thing = file_get_contents($path);\n        }\n\n        if ($from !== NULL || $from !== \'1\' || $to !== NULL) {\n            $lines = explode(\"\\n\", $thing);\n            if ($from === NULL) {\n                $f = 0;\n            } else {\n                $f = max(0, intval($from) - 1);\n            }\n            if ($to === NULL) {\n                $t = count($lines);\n            } else {\n                $t = min(count($lines), intval($to));\n            }\n            $lines = array_slice($lines, $f, $t - $f);\n            $thing = implode(\"\\n\", $lines);\n            $cmd .= \' -O \'.escapeshellarg(\'linenostart=\'.strval($f+1));\n        }\n\n        $cmd .= \' -O \'.escapeshellarg(\'encoding=iso-8859-1\');\n\n        $o .= jea_pygments_txp::subprocess($cmd, $thing);\n        return $o;\n    }\n\n    public static function guess_lexer($file) {\n        $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n        return trim(shell_exec(\"$pygmentize -N \".escapeshellarg($file)));\n    }\n\n}\n\n', '\n\n/* interface */\n\nfunction jea_highlight($attrs, $thing=\'\') {\n    try {\n        return jea_highlight::highlight($attrs, $thing);\n    } catch (Exception $e) {\n        return \'<p><pre>\'.$e->getMessage().\'</pre></p>\';\n    }\n}\n\n/* setup */\n\nif (@txpinterface == \'admin\') {\n    @require_plugin(\'soo_plugin_pref\');\n    add_privs(\'jea_pygments_txp\', \'1,2\');\n    add_privs(\'plugin_prefs.jea_pygments_txp\', \'1,2\');\n    register_callback(\'jea_pygments_txp_on_prefs\', \'plugin_prefs.jea_pygments_txp\');\n}\n\n/* default configuration and utilities */\n\nclass jea_pygments_txp {\n\n    public static $defaults = array(\n        \'inline_css\' => \'1\',\n        \'linenos\' => \'0\',\n        \'pygmentize\' => \'/usr/bin/pygmentize\',\n        \'style\' => \'default\',\n    );\n\n    public static $patterns = array(\n        \'file\' => \'/[a-zA-Z0-9_\\-]+(\\.[a-zA-Z0-9_\\-]+)*\\.?/\',\n        \'from\' => \'/[1-9][0-9]*/\',\n        \'inline_css\' => \'/[A-Za-z01]+/\',\n        \'lang\' => \'/[a-zA-Z0-9_\\-]*/\',\n        \'linenos\' => \'/[A-Za-z01]+/\',\n        \'pygmentize\' => \'/([0-9a-zA-Z\\-_\\/]*\\/)?pygmentize/\',\n        \'style\' => \'/[a-zA-Z0-9_\\-]*/\',\n        \'to\' => \'/[1-9][0-9]*/\',\n    );\n\n\n    public static function preferences() {\n        return array(\n            \'pygmentize\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'pygmentize\'],\n                \'html\'  => \'text_input\',\n                \'text\'  => \'Pygmentize script location\'\n            ),\n            \'linenos\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'linenos\'],\n                \'html\'  => \'yesnoradio\',\n                \'text\'  => \'Line numbers\'\n            ),\n            \'inline_css\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'inline_css\'],\n                \'html\'  => \'yesnoradio\',\n                \'text\'  => \'Inline CSS\'\n            ),\n            \'style\' => array(\n                \'val\'   => jea_pygments_txp::$defaults[\'style\'],\n                \'html\'  => \'text_input\',\n                \'text\'  => \'Highlighting style\'\n            )\n\n        );\n    }\n\n    public function get_valid($name, $attrs = NULL) {\n        if ($attrs !== NULL && array_key_exists($name, $attrs)) {\n            $val = $attrs[$name];\n        } else {\n            $defaultval = NULL;\n            if (array_key_exists($name, jea_pygments_txp::$defaults)) {\n                $defaultval = jea_pygments_txp::$defaults[$name];\n            }\n            $val = get_pref(\"jea_pygments_txp.$name\", $defaultval);\n        }\n\n        if ($val === NULL || jea_pygments_txp::valid($name, $val, $ret_msg)) {\n            return $val;\n        } else {\n            throw new Exception($ret_msg);\n        }\n    }\n\n    public static function valid($name, $strval, &$ret_msg) {\n        if (array_key_exists($name, jea_pygments_txp::$patterns)) {\n            preg_match(jea_pygments_txp::$patterns[$name], $strval, $matches);\n            if (count($matches) == 0 || strlen($matches[0]) != strlen($strval)) {\n                $ret_msg = \"<p>jea_pygments_txp: invalid value for attribute \'$name\' </p>\";\n                return False;\n            } else {\n                return True;\n            }\n        } else {\n            $ret_msg = \"<p>jea_pygments_txp: invalid attribute \'$name\'</p>\";\n            return False;\n        }\n    }\n\n    public static function as_bool($s) {\n        $s = strtolower($s);\n        return (strcmp($s, \'true\') == 0\n            || strcmp($s, \'on\') == 0\n            || strcmp($s, \'yes\') == 0\n            || strcmp($s, \'1\') == 0);\n    }\n\n    public static function subprocess($cmd, $input) {\n        $dspec = array(\n            0 => array(\"pipe\", \"r\"), // stdin\n            1 => array(\"pipe\", \"w\")  // stdout\n        );\n        $p = proc_open($cmd, $dspec, $pipes, NULL, NULL);\n        if (is_resource($p)) {\n            fwrite($pipes[0], $input);\n            fclose($pipes[0]);\n\n            $output = stream_get_contents($pipes[1]);\n            fclose($pipes[1]);\n            if (proc_close($p) == 0) {\n                return $output;\n            } else {\n                throw new Exception(\"Child process exited with error\");\n            }\n        } else {\n            throw new Exception(\"Cannot execute command\");\n        }\n    }\n\n}\n\n/* preferences management */\n\nfunction jea_pygments_txp_on_prefs($event, $step) {\n    if (function_exists(\'soo_plugin_pref\')) {\n        soo_plugin_pref($event, $step, jea_pygments_txp::preferences());\n        jea_pygments_txp_print_features();\n    } else if ( substr($event, 0, 12) == \'plugin_prefs\' ) {\n        $plugin = substr($event, 13);\n        $message = \'<p style=\\\'text-align: center\\\'><br /><strong>\' . gTxt(\'edit\') . \" $plugin \" .\n            gTxt(\'edit_preferences\') . \':</strong><br /><br/>\' .\n            gTxt(\'install_plugin\') . \' <a\n            href=\"http://ipsedixit.net/txp/92/soo_plugin_pref\">soo_plugin_pref</a><br/></p>\';\n        pagetop(gTxt(\'edit_preferences\') . \" &#8250; $plugin\", $message);\n        print $message;\n    }\n}\n\nfunction jea_pygments_txp_print_features() {\n    require_once(\'lib/classTextile.php\');\n    $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n    $textile = new Textile();\n    $help = $textile->TextileThis(shell_exec(escapeshellcmd(\"$pygmentize -L\")));\n    print \"<div style=\'border: 1px solid ; margin: 30px auto; padding: 20px; width: 500px\'>\n           <strong>Pygmentize features generated with \'pygmentize -L\'</strong>\n           <hr/><p/>$help</div>\";\n}\n\n/* highlight tag */\n\nclass jea_highlight {\n\n    public static function highlight($raw_attrs, $thing=\'\') {\n        $lang = jea_pygments_txp::get_valid(\'lang\', $raw_attrs);\n        $file = jea_pygments_txp::get_valid(\'file\', $raw_attrs);\n        $linenos= jea_pygments_txp::as_bool(jea_pygments_txp::get_valid(\'linenos\', $raw_attrs));\n        $from = jea_pygments_txp::get_valid(\'from\', $raw_attrs);\n        $to = jea_pygments_txp::get_valid(\'to\', $raw_attrs);\n        $style = jea_pygments_txp::get_valid(\'style\', $raw_attrs);\n        $inline_css = jea_pygments_txp::as_bool(jea_pygments_txp::get_valid(\'inline_css\', $raw_attrs));\n        $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n\n        $o = \'\';\n\n        $cmd = escapeshellcmd($pygmentize);\n        $cmd .= \' -f html\';\n\n        if ($linenos) {\n            $cmd .= \' -O linenos=inline\';\n        }\n        if ($inline_css) {\n            $cmd .= \' -O noclasses=True\';\n            // workaround for lost background issue\n            // http://dev.pocoo.org/projects/pygments/ticket/427\n            $csscmd = escapeshellcmd($pygmentize);\n            $csscmd .= \' -f html\';\n            $csscmd .= \' -S \' . escapeshellarg($style);\n            $csshack = shell_exec($csscmd);\n            if (preg_match(\'/\\.hll {([^\\}]*)}/\', $csshack, $matches)) {\n                $cmd .= \' -O \'.escapeshellarg(\'cssstyles=\'.substr($matches[0], 6, -1));\n            }\n        }\n        $cmd .= \' -O \'.escapeshellarg(\"cssclass=jea_pygments_txp_$style\");\n        $cmd .= \' -O \'.escapeshellarg(\"style=$style\");\n\n        if ($lang === NULL && $file !== NULL) {\n            $lang = jea_highlight::guess_lexer($file);\n        }\n        if ($lang === NULL) {\n            $cmd .= \' -g\';\n        } else {\n            $cmd .= \' -l \'.escapeshellarg($lang);\n        }\n\n        if ($file !== NULL) {\n            global $txpcfg;\n            $path = dirname($txpcfg[\'txpath\']) . \'/files/\' . $file;\n            if (!file_exists($path)) {\n                throw new Exception(\"<p>jea_pygments_txp: File \'$file\' does not exist.</p>\");\n            }\n            $thing = file_get_contents($path);\n        }\n\n        if ($from !== NULL || $from !== \'1\' || $to !== NULL) {\n            $lines = explode(\"\\n\", $thing);\n            if ($from === NULL) {\n                $f = 0;\n            } else {\n                $f = max(0, intval($from) - 1);\n            }\n            if ($to === NULL) {\n                $t = count($lines);\n            } else {\n                $t = min(count($lines), intval($to));\n            }\n            $lines = array_slice($lines, $f, $t - $f);\n            $thing = implode(\"\\n\", $lines);\n            $cmd .= \' -O \'.escapeshellarg(\'linenostart=\'.strval($f+1));\n        }\n\n        $cmd .= \' -O \'.escapeshellarg(\'encoding=iso-8859-1\');\n\n        $o .= jea_pygments_txp::subprocess($cmd, $thing);\n        return $o;\n    }\n\n    public static function guess_lexer($file) {\n        $pygmentize = jea_pygments_txp::get_valid(\'pygmentize\');\n        return trim(shell_exec(\"$pygmentize -N \".escapeshellarg($file)));\n    }\n\n}\n\n', '75f8681b91e3dc0bc1a30c3afc6b6d58', '', '', 1, 5, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('mem_form', 0, 'Michael Manfre', 'http://manfre.net/', '0.8.4', 'A library plugin that provides support for html forms.', '<h1>mem_form plugin</h1>\n\n	<h2>Summary</h2>\n\n	<p>This plugin provides <span class=\"caps\">HTML</span> form capabilities for other plugins. This allows for consistent form tags and behaviors, while reducing overall plugin size and development time.</p>\n\n	<h2>Author Contact</h2>\n\n	<p><a href=\"mailto:mmanfre@gmail.com?subject=Textpattern%20mem_form%20plugin\" rel=\"nofollow\">Michael Manfre</a><br />\n<a href=\"http://manfre.net\" rel=\"nofollow\">http://manfre.net</a></p>\n\n	<h2>License</h2>\n\n	<p>This plugin is licensed under the <a href=\"http://www.fsf.org/licensing/licenses/info/GPLv2.html\" rel=\"nofollow\">GPLv2</a>.</p>\n\n	<h2>Tags</h2>\n\n	<ul>\n		<li><a href=\"#mem_form\" rel=\"nofollow\">mem_form</a></li>\n		<li><a href=\"#mem_form_checkbox\" rel=\"nofollow\">mem_form_checkbox</a></li>\n		<li><a href=\"#mem_form_email\" rel=\"nofollow\">mem_form_email</a></li>\n		<li><a href=\"#mem_form_file\" rel=\"nofollow\">mem_form_file</a></li>\n		<li><a href=\"#mem_form_hidden\" rel=\"nofollow\">mem_form_hidden</a></li>\n		<li><a href=\"#mem_form_radio\" rel=\"nofollow\">mem_form_radio</a></li>\n		<li><a href=\"#mem_form_secret\" rel=\"nofollow\">mem_form_secret</a></li>\n		<li><a href=\"#mem_form_select\" rel=\"nofollow\">mem_form_select</a></li>\n		<li><a href=\"#mem_form_select_category\" rel=\"nofollow\">mem_form_select_category</a></li>\n		<li><a href=\"#mem_form_select_section\" rel=\"nofollow\">mem_form_select_section</a></li>\n		<li><a href=\"#mem_form_serverinfo\" rel=\"nofollow\">mem_form_serverinfo</a></li>\n		<li><a href=\"#mem_form_submit\" rel=\"nofollow\">mem_form_submit</a></li>\n		<li><a href=\"#mem_form_text\" rel=\"nofollow\">mem_form_text</a></li>\n		<li><a href=\"#mem_form_textarea\" rel=\"nofollow\">mem_form_textarea</a></li>\n		<li><a href=\"#mem_form_value\" rel=\"nofollow\">mem_form_value</a></li>\n	</ul>\n\n	<h3>mem_form</h3>\n\n	<p>This tag will create an <span class=\"caps\">HTML</span> form and contains all of the processing and validation.</p>\n\n	<ul>\n		<li><span>form</span> <span>string</span> Name of a form that will be parsed to display the form.</li>\n		<li><span>thanks_form</span> <span>string</span> Name of a form that will be parsed upon successful form submission.</li>\n		<li><span>label</span> <span>string</span> Accessible name for the form.</li>\n		<li><span>type</span> <span>string</span> Name of the form to identify itself to bound plugin.</li>\n		<li><span>thanks</span> <span>string</span> Message to display to user upon successful form submission.</li>\n		<li><span>redirect</span> <span>url</span> <span class=\"caps\">URL</span> to redirect upon successful form submission. Overrides &#8220;thanks&#8221; and &#8220;thanks_form&#8221;.</li>\n		<li><span>redirect_form</span> <span>string</span> Name of a form that will be parsed as displayed to the user on a redirect. The string &#8220;<em>{uri}</em>&#8221; will be replaced with the redirect url.</li>\n		<li><span>enctype</span> <span>string</span> <span class=\"caps\">HTML</span> encoding type used when the form is submitted. <code>enctype=\"multipart/form-data\"</code> is required when using mem_form_file.</li>\n	</ul>\n\n	<h3>mem_form_checkbox</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> checkbox field.</p>\n\n	<ul>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>checked</span> <span>int</span> Is this box checked. Default &#8220;0&#8221;.</li>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n	</ul>\n\n	<h3>mem_form_email</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> text input field and validates the submitted value as an email address.</p>\n\n	<ul>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>default</span> <span>string</span> The default value.</li>\n		<li><span>max</span> <span>int</span> Max character length.</li>\n		<li><span>min</span> <span>int</span> Min character length.</li>\n		<li><span>size</span> <span>int</span> Size of input field.</li>\n	</ul>\n\n	<h3>mem_form_file</h3>\n\n	<p>+p(tag-summary). This will output an <span class=\"caps\">HTML</span> file input field. You must add the <code>enctype=\"multipart/form-data\"</code> attribute to your enclosing mem_form for this to work.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>no_replace</span> <span>int</span> Specifies whether a user can upload another file and replace the existing file that will be submitted on successful completion of the form. If &#8220;1&#8221;, the file input field will be replaced with details about the already uploaded file.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>size</span> <span>int</span> Size of input field.</li>\n		<li><span>max_file_size</span> <span>int</span> Maximum size for the uploaded file. Checked server-side.</li>\n		<li><span>accept</span> <span>string</span> The <span class=\"caps\">HTML</span> file input field&#8217;s &#8220;accept&#8221; argument that specifies which file types the field should permit.</li>\n	</ul>\n\n	<h3>mem_form_hidden</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> hidden text input field.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>value</span> <span>string</span> The input value.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>escape_value</span> <span>int</span> Set to &#8220;0&#8221; to prevent html escaping the value. Default &#8220;1&#8221;.</li>\n	</ul>\n\n	<h3>mem_form_radio</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> radio button.</p>\n\n	<ul>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>group</span> <span>string</span> A name that identifies a group of radio buttons.</li>\n		<li><span>value</span> <span>string</span> The value of the radio button. If not set, a unique value is generated.</li>\n		<li><span>checked</span> <span>int</span> Is this box checked. Default &#8220;0&#8221;.</li>\n	</ul>\n\n	<h3>mem_form_secret</h3>\n\n	<p>This will output nothing in <span class=\"caps\">HTML</span> and is meant to pass information to the sumbit handler plugins.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>value</span> <span>string</span> The input value.</li>\n	</ul>\n\n	<h3>mem_form_select</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> select field.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>delimiter</span> <span>string</span> List separator. Default &#8220;,&#8221;</li>\n		<li><span>items</span> <span>string</span> Delimited list containing a select list display values.</li>\n		<li><span>values</span> <span>string</span> Delimited list containing a select list item values.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>selected</span> <span>string</span> The value of the selected item.</li>\n		<li><span>first</span> <span>string</span> Display value of the first item in the list. E.g. &#8220;Select a Section&#8221; or &#8220;&#8221; for a blank option.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>select_limit</span> <span>int</span> Specifies the maximum number of items that may be selected. If set to a value greater than 1, a multiselect will be used. The stored value will be an array.</li>\n		<li><span>as_csv</span> <span>int</span> If set to 1, the value will be stored as a delimited string of values instead of an array. This does nothing when select_limit is less than 2.</li>\n	</ul>\n\n	<h3>mem_form_select_category</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> select field populated with the specified Textpattern categories.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>delimiter</span> <span>string</span> List separator. Default &#8220;,&#8221;</li>\n		<li><span>items</span> <span>string</span> Delimited list containing a select list display values.</li>\n		<li><span>values</span> <span>string</span> Delimited list containing a select list item values.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>selected</span> <span>string</span> The value of the selected item.</li>\n		<li><span>first</span> <span>string</span> Display value of the first item in the list. E.g. &#8220;Select a Section&#8221; or &#8220;&#8221; for a blank option.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>exclude</span> <span>string</span> List of item values that will not be included.</li>\n		<li><span>sort</span> <span>string</span>  How will the list values be sorted.</li>\n		<li><span>type</span> <span>string</span> Category type name. E.g. &#8220;article&#8221;</li>\n	</ul>\n\n	<h3>mem_form_select_section</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> select field populated with the specified Textpattern sections.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>delimiter</span> <span>string</span> List separator. Default &#8220;,&#8221;</li>\n		<li><span>items</span> <span>string</span> Delimited list containing a select list display values.</li>\n		<li><span>values</span> <span>string</span> Delimited list containing a select list item values.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>selected</span> <span>string</span> The value of the selected item.</li>\n		<li><span>first</span> <span>string</span> Display value of the first item in the list. E.g. &#8220;Select a Section&#8221; or &#8220;&#8221; for a blank option.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>exclude</span> <span>string</span> List of item values that will not be included.</li>\n		<li><span>sort</span> <span>string</span>  How will the list values be sorted.</li>\n	</ul>\n\n	<h3>mem_form_serverinfo</h3>\n\n	<p>This will output no <span class=\"caps\">HTML</span> and is used to pass server information to the plugin handling the form submission.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n	</ul>\n\n	<h3>mem_form_submit</h3>\n\n	<p>This will output either an <span class=\"caps\">HTML</span> submit input field or an <span class=\"caps\">HTML</span> button.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>button</span> <span>int</span> If &#8220;1&#8221;, an html button tag will be used instead of an input tag.</li>\n	</ul>\n\n	<h3>mem_form_text</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> text input field.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>default</span> <span>string</span> The default value.</li>\n		<li><span>format</span> <span>string</span> A regex pattern that will be matched against the input value. You must escape all backslashes &#8216;\\&#8217;. E.g &#8220;/\\\\d/&#8221; is a single digit.</li>\n		<li><span>example</span> <span>string</span> An example of a correctly formatted input value.</li>\n		<li><span>password</span> <span>int</span> Specifies if the input field is a password field.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>max</span> <span>int</span> Max character length.</li>\n		<li><span>min</span> <span>int</span> Min character length.</li>\n		<li><span>size</span> <span>int</span> Size of input field.</li>\n		<li><span>escape_value</span> <span>int</span> Set to &#8220;0&#8221; to prevent html escaping the value. Default &#8220;1&#8221;.</li>\n	</ul>\n\n	<h3>mem_form_textarea</h3>\n\n	<p>This will output an <span class=\"caps\">HTML</span> textarea.</p>\n\n	<ul>\n		<li><span>label</span> <span>string</span> Friendly name for the input field. If set, this will output an <span class=\"caps\">HTML</span> &lt;label&gt; tag linked to the input field.</li>\n		<li><span>name</span> <span>string</span> Input field name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>break</span> <span>string</span> Separator between label tag and input tag.</li>\n		<li><span>default</span> <span>string</span> The default value.</li>\n		<li><span>max</span> <span>int</span> Max character length.</li>\n		<li><span>min</span> <span>int</span> Min character length.</li>\n		<li><span>required</span> <span>int</span> Specifies if input is required.</li>\n		<li><span>rows</span> <span>int</span> Number of rows in the textarea.</li>\n		<li><span>cols</span> <span>int</span> Number of columns in the textarea.</li>\n		<li><span>escape_value</span> <span>int</span> Set to &#8220;0&#8221; to prevent html escaping the value. Default &#8220;1&#8221;.</li>\n	</ul>\n\n	<h3>mem_form_value</h3>\n\n	<p>This will output the value associated with a form field. Useful to mix <span class=\"caps\">HTML</span> input fields with mem_form.</p>\n\n	<ul>\n		<li><span>id</span> <span>string</span> ID for output wrap tag.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name.</li>\n		<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class.</li>\n		<li><span>wraptag</span> <span>string</span> <span class=\"caps\">HTML</span> tag to wrap around the value.</li>\n		<li><span>attributes</span> <span>string</span> Additional <span class=\"caps\">HTML</span> tag attributes that should be passed to the output tag.</li>\n	</ul>\n\n	<h2>Exposed Functions</h2>\n\n	<h3>mem_form_mail</h3>\n\n	<p>This will send an email message.</p>\n\n	<ul>\n		<li><span>Return Value</span> <span>bool</span> Returns true or false, indicating whether the email was successfully given to the mail system. This does not indicate the validity of the email address or that the recipient actually received the email.</li>\n		<li><span>from</span> <span>string</span> The From email address.</li>\n		<li><span>reply</span> <span>string</span> The Reply To email address.</li>\n		<li><span>to</span> <span>string</span> The To email address(es).</li>\n		<li><span>subject</span> <span>string</span> The email&#8217;s Subject.</li>\n		<li><span>msg</span> <span>string</span> The email message.</li>\n	</ul>\n\n	<h3>mem_form_error</h3>\n\n	<p>This will set or get errors associated with the form.</p>\n\n	<ul>\n		<li><span>Return Value</span> <span>mixed</span> If err is <span class=\"caps\">NULL</span>, then it will return an array of errors that have been set.</li>\n		<li><span>err</span> <span>string</span> An error that will be added to the list of form errors that will be displayed to the form user.</li>\n	</ul>\n\n	<h3>mem_form_default</h3>\n\n	<p>This will get or set a default value for a form.</p>\n\n	<ul>\n		<li><span>Return Value</span> <span>mixed</span> If <span>val is <span class=\"caps\">NULL</span>, then it will return the default value set for the input field matching %(atts-name)key</span>. If <span>key</span> does not exist, then it will return <span class=\"caps\">FALSE</span>.</li>\n		<li><span>key</span> <span>string</span> The name of the input field.</li>\n		<li><span>val</span> <span>string</span> If specified, this will be specified as the default value for the input field named &#8220;key&#8221;.</li>\n	</ul>\n\n	<h3>mem_form_store</h3>\n\n	<p>This will store the name, label and value for a field in to the appropriate global variables.</p>\n\n	<ul>\n		<li><span>name</span> <span>string</span> The name of the field.</li>\n		<li><span>label</span> <span>string</span> The label of the field.</li>\n		<li><span>value</span> <span>mixed</span> The value of the field.</li>\n	</ul>\n\n	<h3>mem_form_remove</h3>\n\n	<p>This will remove the information associated with a field that has been stored.</p>\n\n	<ul>\n		<li><span>name</span> <span>string</span> The name of the field.</li>\n	</ul>\n\n	<h2>Global Variables</h2>\n\n	<p>This library allows other plugins to hook in to events with the <code>register_callback</code> function.</p>\n\n	<ul>\n		<li><span>$mem_form_type</span> <span>string</span> A text value that allows a plugin determine if it should process the current form.</li>\n		<li><span>$mem_form_submit</span> <span>bool</span> This specifies if the form is doing a postback.</li>\n		<li><span>$mem_form_default</span> <span>array</span> An array containing the default values to use when displaying the form.</li>\n		<li><span>$mem_form</span> <span>array</span> An array mapping all input labels to their values.</li>\n		<li><span>$mem_form_labels</span> <span>array</span> An array mapping all input names to their labels.</li>\n		<li><span>$mem_form_values</span> <span>array</span> An array mapping all input names to their values.</li>\n		<li><span>$mem_form_thanks_form</span> <span>string</span> Contains the message that will be shown to the user after a successful submission. Either the &#8220;thanks_form&#8221; or the &#8220;thanks&#8221; attribute. A plugin can modify this value or return a string to over</li>\n	</ul>\n\n	<h2>Plugin Events</h2>\n\n	<h3>mem_form.defaults</h3>\n\n	<p>Allows a plugin to alter the default values for a form prior to being displayed.</p>\n\n	<h3>mem_form.display</h3>\n\n	<p>Allows a plugin to insert additional html in the rendered html form tag.</p>\n\n	<h3>mem_form.submit</h3>\n\n	<p>Allows a plugin to act upon a successful form submission.</p>\n\n	<h3>mem_form.spam</h3>\n\n	<p>Allows a plugin to test a submission as spam. The function get_mem_form_evaluator() returns the evaluator.</p>\n\n	<h3>mem_form.store_value</h3>\n\n	<p>On submit, this event is called for each field that passed the builtin checks and was just stored in to the global variables. The callback step is the field name. This callback can be used for custom field validation. If the value is invalid, return <span class=\"caps\">FALSE</span>. Warning: This event is called for each field even if a previously checked field has failed.</p>\n\n	<h3>mem_form.validate</h3>\n\n	<p>This event is called on form submit, after the individual fields are parsed and validated. This event is not called if there are any errors after the fields are validated. Any multi-field or form specific validation should happen here. Use mem_form_error() to set any validation error messages to prevent a successful post.</p>', '$mem_glz_custom_fields_plugin = load_plugin(\'glz_custom_fields\');\r\n\r\n// needed for MLP\r\ndefine( \'MEM_FORM_PREFIX\' , \'mem_form\' );\r\n\r\nglobal $mem_form_lang;\r\n\r\nif (!is_array($mem_form_lang))\r\n{\r\n	$mem_form_lang = array(\r\n		\'error_file_extension\'	=> \'File upload failed for field {label}.\',\r\n		\'error_file_failed\'	=> \'Failed to upload file for field {label}.\',\r\n		\'error_file_size\'	=> \'Failed to upload File for field {label}. File is too large.\',\r\n		\'field_missing\'	=> \'The field {label} is required.\',\r\n		\'form_expired\'	=>	\'The form has expired.\',\r\n		\'form_misconfigured\'	=> \'The mem_form is misconfigured. You must specify the \"form\" attribute.\',\r\n		\'form_sorry\'	=> \'The form is currently unavailable.\',\r\n		\'form_used\'	=>	\'This form has already been used to submit.\',\r\n		\'general_inquiry\'	=> \'\',\r\n		\'invalid_email\'	=> \'The email address {email} is invalid.\',\r\n		\'invalid_host\'	=> \'The host {domain} is invalid.\',\r\n		\'invalid_utf8\'	=> \'Invalid UTF8 string for field {label}.\',\r\n		\'invalid_value\'	=> \'The value \"{value}\" is invalid for the input field {label}.\',\r\n		\'invalid_format\'	=>	\'The input field {label} must match the format \"{example}\".\',\r\n		\'invalid_too_many_selected\'	=> \'The input field {label} only allows {count} selected {plural}.\',\r\n		\'item\'	=> \'item\',\r\n		\'items\'	=> \'items\',\r\n		\'max_warning\'	=> \'The input field {label} must be smaller than {max} characters long.\',\r\n		\'min_warning\'	=> \'The input field {label} must be at least {min} characters long.\',\r\n		\'refresh\'	=> \'Refresh\',\r\n		\'spam\'	=> \'Your submission was blocked by a spam filter.\',\r\n		\'submitted_thanks\'	=>	\'You have successfully submitted the form. Thank you.\',\r\n	);\r\n}\r\n\r\nregister_callback( \'mem_form_enumerate_strings\' , \'l10n.enumerate_strings\' );\r\nfunction mem_form_enumerate_strings($event , $step=\'\' , $pre=0)\r\n{\r\n	global $mem_form_lang;\r\n	$r = array	(\r\n				\'owner\'		=> \'mem_form\',			#	Change to your plugin\'s name\r\n				\'prefix\'	=> MEM_FORM_PREFIX,		#	Its unique string prefix\r\n				\'lang\'		=> \'en-gb\',				#	The language of the initial strings.\r\n				\'event\'		=> \'public\',			#	public/admin/common = which interface the strings will be loaded into\r\n				\'strings\'	=> $mem_form_lang,		#	The strings themselves.\r\n				);\r\n	return $r;\r\n}\r\n\r\n\r\nfunction mem_form_gTxt($what,$args = array())\r\n{\r\n	global $mem_form_lang, $textarray;\r\n\r\n	$key = strtolower( MEM_FORM_PREFIX . \'-\' . $what );\r\n\r\n	if (isset($textarray[$key]))\r\n	{\r\n		$str = $textarray[$key];\r\n	}\r\n	else\r\n	{\r\n		$key = strtolower($what);\r\n\r\n		if (isset($mem_form_lang[$key]))\r\n			$str = $mem_form_lang[$key];\r\n		elseif (isset($textarray[$key]))\r\n			$str = $textarray[$key];\r\n		else\r\n			$str = $what;\r\n	}\r\n\r\n	if( !empty($args) )\r\n		$str = strtr( $str , $args );\r\n\r\n	return $str;\r\n}\r\n\r\n\r\nfunction mem_form($atts, $thing=\'\', $default=false)\r\n{\r\n	global $sitename, $prefs, $file_max_upload_size, $mem_form_error, $mem_form_submit,\r\n		$mem_form, $mem_form_labels, $mem_form_values,\r\n		$mem_form_default, $mem_form_type, $mem_form_thanks_form,\r\n		$mem_glz_custom_fields_plugin;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'form\'		=> \'\',\r\n		\'thanks_form\'	=> \'\',\r\n		\'thanks\'	=> graf(mem_form_gTxt(\'submitted_thanks\')),\r\n		\'label\'		=> \'\',\r\n		\'type\'		=> \'\',\r\n		\'redirect\'	=> \'\',\r\n		\'redirect_form\'	=> \'\',\r\n		\'class\'		=> \'memForm\',\r\n		\'enctype\'	=> \'\',\r\n		\'file_accept\'	=> \'\',\r\n		\'max_file_size\'	=> $file_max_upload_size,\r\n		\'form_expired_msg\' => mem_form_gTxt(\'form_expired\'),\r\n		\'show_error\'	=> 1,\r\n		\'show_input\'	=> 1,\r\n	), $atts));\r\n\r\n	if (empty($type) or (empty($form) && empty($thing))) {\r\n		trigger_error(\'Argument not specified for mem_form tag\', E_USER_WARNING);\r\n\r\n		return \'\';\r\n	}\r\n	$out = \'\';\r\n\r\n	// init error structure\r\n	mem_form_error();\r\n\r\n	$mem_form_type = $type;\r\n\r\n	$mem_form_default = is_array($default) ? $default : array();\r\n	callback_event(\'mem_form.defaults\');\r\n\r\n	unset($atts[\'show_error\'], $atts[\'show_input\']);\r\n	$mem_form_id = md5(serialize($atts).preg_replace(\'/[\\t\\s\\r\\n]/\',\'\',$thing));\r\n	$mem_form_submit = (ps(\'mem_form_id\') == $mem_form_id);\r\n\r\n	$nonce   = doSlash(ps(\'mem_form_nonce\'));\r\n	$renonce = false;\r\n\r\n	if ($mem_form_submit) {\r\n		safe_delete(\'txp_discuss_nonce\', \'issue_time < date_sub(now(), interval 10 minute)\');\r\n		if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\"))\r\n		{\r\n			if ($rs[\'used\'])\r\n			{\r\n				unset($mem_form_error);\r\n				mem_form_error(mem_form_gTxt(\'form_used\'));\r\n				$renonce = true;\r\n\r\n				$_POST[\'mem_form_submit\'] = TRUE;\r\n				$_POST[\'mem_form_id\'] = $mem_form_id;\r\n				$_POST[\'mem_form_nonce\'] = $nonce;\r\n			}\r\n		}\r\n		else\r\n		{\r\n			mem_form_error($form_expired_msg);\r\n			$renonce = true;\r\n		}\r\n	}\r\n\r\n	if ($mem_form_submit and $nonce and !$renonce)\r\n	{\r\n		$mem_form_nonce = $nonce;\r\n	}\r\n\r\n	elseif (!$show_error or $show_input)\r\n	{\r\n		$mem_form_nonce = md5(uniqid(rand(), true));\r\n		safe_insert(\'txp_discuss_nonce\', \"issue_time = now(), nonce = \'$mem_form_nonce\'\");\r\n	}\r\n\r\n	$form = ($form) ? fetch_form($form) : $thing;\r\n	$form = parse($form);\r\n\r\n	if ($mem_form_submit && empty($mem_form_error))\r\n	{\r\n		// let plugins validate after individual fields are validated\r\n		callback_event(\'mem_form.validate\');\r\n	}\r\n\r\n	if (!$mem_form_submit) {\r\n	  # don\'t show errors or send mail\r\n	}\r\n	elseif (mem_form_error())\r\n	{\r\n		if ($show_error or !$show_input)\r\n		{\r\n			$out .= mem_form_display_error();\r\n\r\n			if (!$show_input) return $out;\r\n		}\r\n	}\r\n	elseif ($show_input and is_array($mem_form))\r\n	{\r\n		if ($mem_glz_custom_fields_plugin) {\r\n			// prep the values\r\n			glz_custom_fields_before_save();\r\n		}\r\n\r\n		callback_event(\'mem_form.spam\');\r\n\r\n		/// load and check spam plugins/\r\n		$evaluator =& get_mem_form_evaluator();\r\n		$is_spam = $evaluator->is_spam();\r\n\r\n		if ($is_spam) {\r\n			return mem_form_gTxt(\'spam\');\r\n		}\r\n\r\n		$mem_form_thanks_form = ($thanks_form ? fetch_form($thanks_form) : $thanks);\r\n\r\n		safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = now()\", \"nonce = \'$nonce\'\");\r\n\r\n		$result = callback_event(\'mem_form.submit\');\r\n\r\n		if (mem_form_error()) {\r\n			$out .= mem_form_display_error();\r\n			$redirect = false;\r\n		}\r\n\r\n		$thanks_form = $mem_form_thanks_form;\r\n		unset($mem_form_thanks_form);\r\n\r\n		if (!empty($result))\r\n			return $result;\r\n\r\n		if (mem_form_error() and $show_input)\r\n		{\r\n			// no-op, reshow form with errors\r\n		}\r\n		else if ($redirect)\r\n		{\r\n			$_POST = array();\r\n\r\n			while (@ob_end_clean());\r\n			$uri = hu.ltrim($redirect,\'/\');\r\n			if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\']))\r\n			{\r\n				txp_status_header(\'303 See Other\');\r\n				header(\'Location: \'.$uri);\r\n				header(\'Connection: close\');\r\n				header(\'Content-Length: 0\');\r\n			}\r\n			else\r\n			{\r\n				$uri = htmlspecialchars($uri);\r\n				$refresh = mem_form_gTxt(\'refresh\');\r\n\r\n				if (!empty($redirect_form))\r\n				{\r\n					$redirect_form = fetch_form($redirect_form);\r\n\r\n					echo str_replace(\'{uri}\', $uri, $redirect_form);\r\n				}\r\n\r\n				if (empty($redirect_form))\r\n				{\r\n					echo <<<END\r\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n<head>\r\n	<title>$sitename</title>\r\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n	<meta http-equiv=\"refresh\" content=\"0;url=$uri\" />\r\n</head>\r\n<body>\r\n<a href=\"$uri\">$refresh</a>\r\n</body>\r\n</html>\r\nEND;\r\n				}\r\n			}\r\n			exit;\r\n		}\r\n		else {\r\n			return \'<div class=\"memThanks\" id=\"mem\'.$mem_form_id.\'\">\' .\r\n				$thanks_form . \'</div>\';\r\n		}\r\n	}\r\n\r\n	if ($show_input)\r\n	{\r\n		$file_accept = (!empty($file_accept) ? \' accept=\"\'.$file_accept.\'\"\' : \'\');\r\n\r\n		$class = htmlspecialchars($class);\r\n\r\n		$enctype = !empty($enctype) ? \' enctype=\"\'.$enctype.\'\"\' : \'\';\r\n\r\n		return \'<form method=\"post\"\'.((!$show_error and $mem_form_error) ? \'\' : \' id=\"mem\'.$mem_form_id.\'\"\').\' class=\"\'.$class.\'\" action=\"\'.htmlspecialchars(serverSet(\'REQUEST_URI\')).\'#mem\'.$mem_form_id.\'\"\'.$file_accept.$enctype.\'>\'.\r\n			( $label ? n.\'<fieldset>\' : n.\'<div>\' ).\r\n			( $label ? n.\'<legend>\'.htmlspecialchars($label).\'</legend>\' : \'\' ).\r\n			$out.\r\n			n.\'<input type=\"hidden\" name=\"mem_form_nonce\" value=\"\'.$mem_form_nonce.\'\" />\'.\r\n			n.\'<input type=\"hidden\" name=\"mem_form_id\" value=\"\'.$mem_form_id.\'\" />\'.\r\n			(!empty($max_file_size) ? n.\'<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"\'.$max_file_size.\'\" />\' : \'\' ).\r\n			callback_event(\'mem_form.display\',\'\',1).\r\n			$form.\r\n			callback_event(\'mem_form.display\').\r\n			( $label ? (n.\'</fieldset>\') : (n.\'</div>\') ).\r\n			n.\'</form>\';\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\nfunction mem_form_text($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'text\'),\r\n		\'max\'		=> 100,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memText\',\r\n		\'required\'	=> 1,\r\n		\'size\'		=> \'\',\r\n		\'password\'	=> 0,\r\n		\'format\'	=> \'\',\r\n		\'example\'	=> \'\',\r\n		\'escape_value\'	=> 1\r\n	), $atts));\r\n\r\n	$min = intval($min);\r\n	$max = intval($max);\r\n	$size = intval($size);\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = trim(ps($name));\r\n		$utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\r\n		$hlabel = empty($label) ? htmlspecialchars($name) : htmlspecialchars($label);\r\n\r\n\r\n		if (strlen($value) == 0 && $required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n			$isError = true;\r\n		}\r\n		elseif ($required && !empty($format) && !preg_match($format, $value))\r\n		{\r\n			//echo \"format=$format<br />value=$value<br />\";\r\n			$mem_form_error[] = mem_form_gTxt(\'invalid_format\', array(\'{label}\'=>$hlabel, \'{example}\'=> htmlspecialchars($example)));\r\n			$isError = true;\r\n		}\r\n		elseif (strlen($value))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', array(\'{label}\'=>$hlabel));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($min and $utf8len < $min)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'min_warning\', array(\'{label}\'=>$hlabel, \'{min}\'=>$min));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($max and $utf8len > $max)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'max_warning\', array(\'{label}\'=>$hlabel, \'{max}\'=>$max));\r\n				$isError = true;\r\n			}\r\n\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else\r\n			$value = $default;\r\n	}\r\n\r\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\r\n	$maxlength = ($max) ? \' maxlength=\"\'.$max.\'\"\' : \'\';\r\n\r\n	$isError = $isError ? \"errorElement\" : \'\';\r\n\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n    return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		\'<input type=\"\'.($password ? \'password\' : \'text\').\'\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\'.$size.$maxlength.\' />\';\r\n}\r\n\r\n\r\nfunction mem_form_file($atts)\r\n{\r\n	global $mem_form_submit, $mem_form_error, $mem_form_default, $file_max_upload_size, $tempdir;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'file\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memFile\',\r\n		\'size\'		=> \'\',\r\n		\'accept\'	=> \'\',\r\n		\'no_replace\' => 1,\r\n		\'max_file_size\'	=> $file_max_upload_size,\r\n		\'required\'	=> 1,\r\n		\'default\'	=> FALSE,\r\n	), $atts));\r\n\r\n	$fname = ps(\'file_\'.$name);\r\n	$frealname = ps(\'file_info_\'.$name.\'_name\');\r\n	$ftype = ps(\'file_info_\'.$name.\'_type\');\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	$out = \'\';\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (!empty($fname))\r\n		{\r\n			// see if user uploaded a different file to replace already uploaded\r\n			if (isset($_FILES[$name]) && !empty($_FILES[$name][\'tmp_name\']))\r\n			{\r\n				// unlink last temp file\r\n				if (file_exists($fname) && substr_compare($fname, $tempdir, 0, strlen($tempdir), 1)==0)\r\n					unlink($fname);\r\n\r\n				$fname = \'\';\r\n			}\r\n			else\r\n			{\r\n				// pass through already uploaded filename\r\n				mem_form_store($name, $label, array(\'tmp_name\'=>$fname, \'name\' => $frealname, \'type\' => $ftype));\r\n				$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n						. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($frealname).\"\' />\"\r\n						. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($ftype).\"\' />\";\r\n			}\r\n		}\r\n\r\n		if (empty($fname))\r\n		{\r\n			$hlabel = empty($label) ? htmlspecialchars($name) : htmlspecialchars($label);\r\n\r\n			$fname = $_FILES[$name][\'tmp_name\'];\r\n			$frealname = $_FILES[$name][\'name\'];\r\n			$ftype = $_FILES[$name][\'type\'];\r\n			$err = 0;\r\n\r\n			switch ($_FILES[$name][\'error\']) {\r\n				case UPLOAD_ERR_OK:\r\n					if (is_uploaded_file($fname) and $max_file_size >= filesize($fname))\r\n						mem_form_store($name, $label, $_FILES[$name]);\r\n					elseif (!is_uploaded_file($fname)) {\r\n						if ($required) {\r\n							$mem_form_error[] = mem_form_gTxt(\'error_file_failed\', array(\'{label}\'=>$hlabel));\r\n							$err = 1;\r\n						}\r\n					}\r\n					else {\r\n						$mem_form_error[] = mem_form_gTxt(\'error_file_size\', array(\'{label}\'=>$hlabel));\r\n						$err = 1;\r\n					}\r\n					break;\r\n\r\n				case UPLOAD_ERR_NO_FILE:\r\n					if ($required) {\r\n						$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n						$err = 1;\r\n					}\r\n					break;\r\n\r\n				case UPLOAD_ERR_EXTENSION:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_extension\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n\r\n				case UPLOAD_ERR_INI_SIZE:\r\n				case UPLOAD_ERR_FORM_SIZE:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_size\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n\r\n				default:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_failed\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n			}\r\n\r\n			if (!$err)\r\n			{\r\n				// store as a txp tmp file to be used later\r\n				$fname = get_uploaded_file($fname);\r\n				$err = false === mem_form_store($name, $label, array(\'tmp_name\'=>$fname, \'name\' => $frealname, \'type\' => $ftype));\r\n				if ($err)\r\n				{\r\n					// clean up file\r\n					@unlink($fname);\r\n				}\r\n				else\r\n				{\r\n					$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n							. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($_FILES[$name][\'name\']).\"\' />\"\r\n							. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($_FILES[$name][\'type\']).\"\' />\";\r\n				}\r\n			}\r\n\r\n			$isError = $err ? \'errorElement\' : \'\';\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if (is_array($default))\r\n			$value = $default;\r\n\r\n		if (is_array(@$value))\r\n		{\r\n			$fname = @$value[\'tmp_name\'];\r\n			$frealname = @$value[\'name\'];\r\n			$ftype = @$value[\'type\'];\r\n			$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n				. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($frealname).\"\' />\"\r\n				. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($ftype).\"\' />\";\r\n		}\r\n	}\r\n\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\r\n	$accept = (!empty($accept) ? \' accept=\"\'.$accept.\'\"\' : \'\');\r\n\r\n\r\n	$field_out = \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break;\r\n\r\n	if (!empty($frealname) && $no_replace)\r\n	{\r\n		$field_out .= \'<div id=\"\'.$name.\'\">\'.htmlspecialchars($frealname) . \' <span id=\"\'.$name.\'_ftype\">(\'. htmlspecialchars($ftype).\')</span></div>\';\r\n	}\r\n	else\r\n	{\r\n		$field_out .= \'<input type=\"file\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\"\' .$size.\' />\';\r\n	}\r\n\r\n  return $out.$field_out;\r\n}\r\n\r\nfunction mem_form_textarea($atts, $thing=\'\')\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'cols\'		=> 58,\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'textarea\'),\r\n		\'max\'		=> 10000,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memTextarea\',\r\n		\'required\'	=> 1,\r\n		\'rows\'		=> 8,\r\n		\'escape_value\'	=> 1\r\n	), $atts));\r\n\r\n	$min = intval($min);\r\n	$max = intval($max);\r\n	$cols = intval($cols);\r\n	$rows = intval($rows);\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\r\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\r\n		$hlabel = htmlspecialchars($label);\r\n\r\n		if (strlen(ltrim($value)))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', array(\'{label}\'=>$hlabel));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($min and $utf8len < $min)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'min_warning\', array(\'{label}\'=>$hlabel, \'{min}\'=>$min));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($max and $utf8len > $max)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'max_warning\', array(\'{label}\'=>$hlabel, \'{max}\'=>$max));\r\n				$isError = true;\r\n			}\r\n\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n\r\n		elseif ($required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n			$isError = true;\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if (!empty($default))\r\n			$value = $default;\r\n		else\r\n			$value = parse($thing);\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n	return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		\'<textarea id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\" cols=\"\'.$cols.\'\" rows=\"\'.$rows.\'\">\'.$value.\'</textarea>\';\r\n}\r\n\r\nfunction mem_form_email($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_from, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'email\'),\r\n		\'max\'		=> 100,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'required\'	=> 1,\r\n		\'break\'		=> br,\r\n		\'size\'		=> \'\',\r\n		\'class\'		=> \'memEmail\',\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$email = trim(ps($name));\r\n\r\n		if (strlen($email))\r\n		{\r\n			if (!is_valid_email($email))\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_email\', array(\'{email}\'=>htmlspecialchars($email)));\r\n				$isError = true;\r\n			}\r\n			else\r\n			{\r\n				preg_match(\"/@(.+)$/\", $email, $match);\r\n				$domain = $match[1];\r\n\r\n				if (is_callable(\'checkdnsrr\') and checkdnsrr(\'textpattern.com.\',\'A\') and !checkdnsrr($domain.\'.\',\'MX\') and !checkdnsrr($domain.\'.\',\'A\'))\r\n				{\r\n					$mem_form_error[] = mem_form_gTxt(\'invalid_host\', array(\'{domain}\'=>htmlspecialchars($domain)));\r\n					$isError = true;\r\n				}\r\n				else\r\n				{\r\n					$mem_form_from = $email;\r\n				}\r\n			}\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$email = $mem_form_default[$name];\r\n		else\r\n			$email = $default;\r\n	}\r\n\r\n	return mem_form_text(array(\r\n		\'default\'	=> $email,\r\n		\'isError\'	=> $isError,\r\n		\'label\'		=> $label,\r\n		\'max\'		=> $max,\r\n		\'min\'		=> $min,\r\n		\'name\'		=> $name,\r\n		\'required\'	=> $required,\r\n		\'break\'		=> $break,\r\n		\'size\'		=> $size,\r\n		\'class\'		=> $class,\r\n	));\r\n}\r\n\r\nfunction mem_form_select_section($atts)\r\n{\r\n	extract(mem_form_lAtts(array(\r\n		\'exclude\'	=> \'\',\r\n		\'sort\'		=> \'name ASC\',\r\n		\'delimiter\'	=> \',\',\r\n	),$atts,false));\r\n\r\n	if (!empty($exclude)) {\r\n		$exclusion = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$exclude)));\r\n		$exclusion = array_map(\'strtolower\', $exclusion);\r\n\r\n		if (count($exclusion))\r\n			$exclusion = join($delimiter, quote_list($exclusion));\r\n	}\r\n\r\n	$where = empty($exclusion) ? \'1=1\' : \'LOWER(name) NOT IN (\'.$exclusion.\')\';\r\n\r\n	$sort = empty($sort) ? \'\' : \' ORDER BY \'. doSlash($sort);\r\n\r\n	$rs = safe_rows(\'name, title\',\'txp_section\',$where . $sort);\r\n\r\n	$items = array();\r\n	$values = array();\r\n\r\n	if ($rs) {\r\n		foreach($rs as $r) {\r\n			$items[] = $r[\'title\'];\r\n			$values[] = $r[\'name\'];\r\n		}\r\n	}\r\n\r\n	unset($atts[\'exclude\'], $atts[\'sort\']);\r\n\r\n	$atts[\'items\'] = join($delimiter, $items);\r\n	$atts[\'values\'] = join($delimiter, $values);\r\n\r\n	return mem_form_select($atts);\r\n}\r\n\r\nfunction mem_form_select_category($atts)\r\n{\r\n	extract(mem_form_lAtts(array(\r\n		\'root\'	=> \'root\',\r\n		\'exclude\'	=> \'\',\r\n		\'delimiter\'	=> \',\',\r\n		\'type\'	=> \'article\'\r\n	),$atts,false));\r\n\r\n	$rs = getTree($root, $type);\r\n\r\n	if (!empty($exclude)) {\r\n		$exclusion = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$exclude)));\r\n		$exclusion = array_map(\'strtolower\', $exclusion);\r\n	}\r\n	else\r\n		$exclusion = array();\r\n\r\n	$items = array();\r\n	$values = array();\r\n\r\n	if ($rs) {\r\n		foreach ($rs as $cat) {\r\n			if (count($exclusion) && in_array(strtolower($cat[\'name\']), $exclusion))\r\n				continue;\r\n\r\n			$items[] = $cat[\'title\'];\r\n			$values[] = $cat[\'name\'];\r\n		}\r\n	}\r\n\r\n	unset($atts[\'root\'], $atts[\'type\']);\r\n\r\n	$atts[\'items\'] = join($delimiter, $items);\r\n	$atts[\'values\'] = join($delimiter, $values);\r\n\r\n	return mem_form_select($atts);\r\n}\r\n\r\nfunction mem_form_select($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'break\'		=> \' \',\r\n		\'delimiter\'	=> \',\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'option\'),\r\n		\'items\'		=> mem_form_gTxt(\'general_inquiry\'),\r\n		\'values\'	=> \'\',\r\n		\'first\'		=> FALSE,\r\n		\'required\'	=> 1,\r\n		\'select_limit\'	=> FALSE,\r\n		\'as_csv\'	=> FALSE,\r\n		\'selected\'	=> \'\',\r\n		\'class\'		=> \'memSelect\',\r\n	), $atts, false));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if (!empty($items) && $items[0] == \'<\') $items = parse($items);\r\n	if (!empty($values) && $values[0] == \'<\') $values = parse($values);\r\n\r\n	if ($first !== FALSE) {\r\n		$items = $first.$delimiter.$atts[\'items\'];\r\n		$values = $first.$delimiter.$atts[\'values\'];\r\n	}\r\n\r\n	$select_limit = empty($select_limit) ? 1 : assert_int($select_limit);\r\n\r\n	$items = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$items)));\r\n	$values = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$values)));\r\n	if ($select_limit > 1)\r\n	{\r\n		$selected = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$seelcted)));\r\n	}\r\n	else\r\n	{\r\n		$selected = array(trim($selected));\r\n	}\r\n\r\n	$use_values_array = (count($items) == count($values));\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (strpos($name, \'[]\'))\r\n		{\r\n			$value = ps(substr($name, 0, strlen($name)-2));\r\n\r\n			$selected = $value;\r\n\r\n			if ($as_csv)\r\n			{\r\n				$value = implode($delimiter, $value);\r\n			}\r\n		}\r\n		else\r\n		{\r\n			$value = trim(ps($name));\r\n\r\n			$selected = array($value);\r\n		}\r\n\r\n		if (!empty($selected))\r\n		{\r\n			if (count($selected) <= $select_limit)\r\n			{\r\n				foreach ($selected as $v)\r\n				{\r\n					$is_valid = ($use_values_array && in_array($v, $values)) or (!$use_values_array && in_array($v, $items));\r\n					if (!$is_valid)\r\n					{\r\n						$invalid_value = $v;\r\n						break;\r\n					}\r\n				}\r\n\r\n				if ($is_valid)\r\n				{\r\n					$isError = false === mem_form_store($name, $label, $value);\r\n				}\r\n				else\r\n				{\r\n					$mem_form_error[] = mem_form_gTxt(\'invalid_value\', array(\'{label}\'=> htmlspecialchars($label), \'{value}\'=> htmlspecialchars($invalid_value)));\r\n					$isError = true;\r\n				}\r\n			}\r\n			else\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_too_many_selected\', array(\r\n											\'{label}\'=> htmlspecialchars($label),\r\n											\'{count}\'=> $select_limit,\r\n											\'{plural}\'=> ($select_limit==1 ? mem_form_gTxt(\'item\') : mem_form_gTxt(\'items\'))\r\n										));\r\n				$isError = true;\r\n			}\r\n		}\r\n\r\n		elseif ($required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=> htmlspecialchars($label)));\r\n			$isError = true;\r\n		}\r\n	}\r\n	else if (isset($mem_form_default[$name]))\r\n	{\r\n		$selected = array($mem_form_default[$name]);\r\n	}\r\n\r\n	$out = \'\';\r\n\r\n	foreach ($items as $item)\r\n	{\r\n		$v = $use_values_array ? array_shift($values) : $item;\r\n\r\n		$sel = !empty($selected) && in_array($v, $selected);\r\n\r\n		$out .= n.t.\'<option\'.($use_values_array ? \' value=\"\'.$v.\'\"\' : \'\').($sel ? \' selected=\"selected\">\' : \'>\').\r\n				(strlen($item) ? htmlspecialchars($item) : \' \').\'</option>\';\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	$multiple = $select_limit > 1 ? \' multiple=\"multiple\"\' : \'\';\r\n\r\n	return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		n.\'<select id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\"\' . $multiple . \'>\'.\r\n			$out.\r\n		n.\'</select>\';\r\n}\r\n\r\nfunction mem_form_checkbox($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> \' \',\r\n		\'checked\'	=> 0,\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'checkbox\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memCheckbox\',\r\n		\'required\'	=> 1\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = (bool) ps($name);\r\n\r\n		if ($required and !$value)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=> htmlspecialchars($label)));\r\n			$isError = true;\r\n		}\r\n\r\n		else\r\n		{\r\n			$isError = false === mem_form_store($name, $label, $value ? gTxt(\'yes\') : gTxt(\'no\'));\r\n		}\r\n	}\r\n\r\n	else {\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else\r\n			$value = $checked;\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	return \'<input type=\"checkbox\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\"\'.\r\n		($value ? \' checked=\"checked\"\' : \'\').\' />\'.$break.\r\n		\'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\r\n}\r\n\r\n\r\nfunction mem_form_serverinfo($atts)\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'label\'		=> \'\',\r\n		\'name\'		=> \'\'\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if (strlen($name) and $mem_form_submit)\r\n	{\r\n		if (!$label) $label = $name;\r\n		mem_form_store($name, $label, serverSet($name));\r\n	}\r\n}\r\n\r\nfunction mem_form_secret($atts, $thing = \'\')\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'	=> \'\',\r\n		\'label\'	=> mem_form_gTxt(\'secret\'),\r\n		\'value\'	=> \'\'\r\n	), $atts));\r\n\r\n\r\n	$name = mem_form_label2name($name ? $name : $label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if ($thing)\r\n			$value = trim(parse($thing));\r\n		else\r\n			$value = trim(parse($value));\r\n\r\n		mem_form_store($name, $label, $value);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\nfunction mem_form_hidden($atts, $thing=\'\')\r\n{\r\n	global $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'hidden\'),\r\n		\'value\'		=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'required\'	=> 1,\r\n		\'class\'		=> \'memHidden\',\r\n		\'escape_value\'	=> 1,\r\n	), $atts));\r\n\r\n	$name = mem_form_label2name($name ? $name : $label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\r\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\r\n		$hlabel = htmlspecialchars($label);\r\n\r\n		if (strlen($value))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', $hlabel);\r\n				$isError = true;\r\n			}\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if ($thing)\r\n			$value = trim(parse($thing));\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n	return \'<input type=\"hidden\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name\r\n			. \'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\" id=\"\'.$name.\'\" />\';\r\n}\r\n\r\nfunction mem_form_radio($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_values, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> \' \',\r\n		\'checked\'	=> 0,\r\n		\'group\'		=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'option\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memRadio\',\r\n		\'isError\'	=> \'\',\r\n		\'value\'		=> false\r\n	), $atts));\r\n\r\n	static $cur_name = \'\';\r\n	static $cur_group = \'\';\r\n\r\n	if (!$name and !$group and !$cur_name and !$cur_group) {\r\n		$cur_group = mem_form_gTxt(\'radio\');\r\n		$cur_name = $cur_group;\r\n	}\r\n	if ($group and !$name and $group != $cur_group) $name = $group;\r\n\r\n	if ($name) $cur_name = $name;\r\n	else $name = $cur_name;\r\n\r\n	if ($group) $cur_group = $group;\r\n	else $group = $cur_group;\r\n\r\n	$id   = \'q\'.md5($name.\'=>\'.$label);\r\n	$name = mem_form_label2name($name);\r\n\r\n	$value = $value === false ? $id : $value;\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$is_checked = (ps($name) == $value);\r\n\r\n		if ($is_checked or $checked and !isset($mem_form_values[$name]))\r\n		{\r\n			$isError = false === mem_form_store($name, $group, $value);\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$is_checked = $mem_form_default[$name] == $value;\r\n		else\r\n			$is_checked = $checked;\r\n	}\r\n\r\n	$class = htmlspecialchars($class);\r\n\r\n	$isError = $isError ? \' errorElement\' : \'\';\r\n\r\n	return \'<input value=\"\'.$value.\'\" type=\"radio\" id=\"\'.$id.\'\" class=\"\'.$class.\' \'.$name.$isError.\'\" name=\"\'.$name.\'\"\'.\r\n		( $is_checked ? \' checked=\"checked\" />\' : \' />\').$break.\r\n		\'<label for=\"\'.$id.\'\" class=\"\'.$class.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\r\n}\r\n\r\nfunction mem_form_submit($atts, $thing=\'\')\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'button\'	=> 0,\r\n		\'label\'		=> mem_form_gTxt(\'save\'),\r\n		\'name\'		=> \'mem_form_submit\',\r\n		\'class\'		=> \'memSubmit\',\r\n	), $atts));\r\n\r\n	$label = htmlspecialchars($label);\r\n	$name = htmlspecialchars($name);\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = ps($name);\r\n\r\n		if (!empty($value) && $value == $label)\r\n		{\r\n			// save the clicked button value\r\n			mem_form_store($name, $label, $value);\r\n		}\r\n	}\r\n\r\n	if ($button or strlen($thing))\r\n	{\r\n		return \'<button type=\"submit\" class=\"\'.$class.\'\" name=\"\'.$name.\'\" value=\"\'.$label.\'\">\'.($thing ? trim(parse($thing)) : $label).\'</button>\';\r\n	}\r\n	else\r\n	{\r\n		return \'<input type=\"submit\" class=\"\'.$class.\'\" name=\"\'.$name.\'\" value=\"\'.$label.\'\" />\';\r\n	}\r\n}\r\n\r\nfunction mem_form_lAtts($arr, $atts, $warn=true)\r\n{\r\n	foreach(array(\'button\', \'checked\', \'required\', \'show_input\', \'show_error\') as $key)\r\n	{\r\n		if (isset($atts[$key]))\r\n		{\r\n			$atts[$key] = ($atts[$key] === \'yes\' or intval($atts[$key])) ? 1 : 0;\r\n		}\r\n	}\r\n	if (isset($atts[\'break\']) and $atts[\'break\'] == \'br\') $atts[\'break\'] = \'<br />\';\r\n	return lAtts($arr, $atts, $warn);\r\n}\r\n\r\nfunction mem_form_label2name($label)\r\n{\r\n	$label = trim($label);\r\n	if (strlen($label) == 0) return \'invalid\';\r\n	if (strlen($label) <= 32 and preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) return $label;\r\n	else return \'q\'.md5($label);\r\n}\r\n\r\nfunction mem_form_store($name, $label, $value)\r\n{\r\n	global $mem_form, $mem_form_labels, $mem_form_values;\r\n\r\n	$mem_form[$label] = $value;\r\n	$mem_form_labels[$name] = $label;\r\n	$mem_form_values[$name] = $value;\r\n\r\n	$is_valid = false !== callback_event(\'mem_form.store_value\', $name);\r\n\r\n	// invalid data, unstore it\r\n	if (!$is_valid)\r\n		mem_form_remove($name);\r\n\r\n	return $is_valid;\r\n}\r\n\r\nfunction mem_form_remove($name)\r\n{\r\n	global $mem_form, $mem_form_labels, $mem_form_values;\r\n\r\n	$label = $mem_form_labels[$name];\r\n	unset($mem_form_labels[$name], $mem_form[$label], $mem_form_values[$name]);\r\n}\r\n\r\nfunction mem_form_display_error()\r\n{\r\n	global $mem_form_error;\r\n\r\n	$out = n.\'<div class=\"alert alert-info alert-block\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\">×</button>\'; \r\n\r\n	foreach (array_unique($mem_form_error) as $error)\r\n	{\r\n		$out .= n.t.\'\'.$error.\'\';\r\n	}\r\n\r\n	$out .= n.\'</div>\';\r\n\r\n	return $out;\r\n}\r\n\r\nfunction mem_form_value($atts, $thing)\r\n{\r\n	global $mem_form_submit, $mem_form_values, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'wraptag\'	=> \'\',\r\n		\'class\'		=> \'\',\r\n		\'attributes\'=> \'\',\r\n		\'id\'		=> \'\',\r\n	), $atts));\r\n\r\n	$out = \'\';\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (isset($mem_form_values[$name]))\r\n			$out = $mem_form_values[$name];\r\n	}\r\n	else {\r\n		if (isset($mem_form_default[$name]))\r\n			$out = $mem_form_default[$name];\r\n	}\r\n\r\n	return doTag($out, $wraptag, $class, $attributes, $id);\r\n}\r\n\r\nfunction mem_form_error($err=NULL)\r\n{\r\n	global $mem_form_error;\r\n\r\n	if (!is_array($mem_form_error))\r\n		$mem_form_error = array();\r\n\r\n	if ($err == NULL)\r\n		return !empty($mem_form_error) ? $mem_form_error : false;\r\n\r\n	$mem_form_error[] = $err;\r\n}\r\n\r\nfunction mem_form_default($key,$val=NULL)\r\n{\r\n	global $mem_form_default;\r\n\r\n	if (is_array($key))\r\n	{\r\n		foreach ($key as $k=>$v)\r\n		{\r\n			mem_form_default($k,$v);\r\n		}\r\n		return;\r\n	}\r\n\r\n	$name = mem_form_label2name($key);\r\n\r\n	if ($val == NULL)\r\n	{\r\n		return (isset($mem_form_default[$name]) ? $mem_form_default[$name] : false);\r\n	}\r\n\r\n	$mem_form_default[$name] = $val;\r\n\r\n	return $val;\r\n}\r\n\r\n\r\n\r\nfunction mem_form_mail($from,$reply,$to,$subject,$msg, $content_type=\'text/plain\')\r\n{\r\n	global $prefs;\r\n\r\n	if (!is_callable(\'mail\'))\r\n		return false;\r\n\r\n	$to = mem_form_strip($to);\r\n	$from = mem_form_strip($from);\r\n	$reply = mem_form_strip($reply);\r\n	$subject = mem_form_strip($subject);\r\n	$msg = mem_form_strip($msg,FALSE);\r\n\r\n	if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\')) {\r\n		$charset = \'ISO-8859-1\';\r\n		$subject = utf8_decode($subject);\r\n		$msg     = utf8_decode($msg);\r\n	}\r\n	else {\r\n		$charset = \'UTF-8\';\r\n	}\r\n\r\n	$subject = mem_form_mailheader($subject,\'text\');\r\n\r\n	$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\r\n\r\n	$headers = \'From: \'.$from.\r\n		($reply ? ($sep.\'Reply-To: \'.$reply) : \'\').\r\n		$sep.\'X-Mailer: Textpattern (mem_form)\'.\r\n		$sep.\'X-Originating-IP: \'.mem_form_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'].\' via \' : \'\').$_SERVER[\'REMOTE_ADDR\']).\r\n		$sep.\'Content-Transfer-Encoding: 8bit\'.\r\n		$sep.\'Content-Type: \'.$content_type.\'; charset=\"\'.$charset.\'\"\';\r\n\r\n	return mail($to, $subject, $msg, $headers);\r\n}\r\n\r\nfunction mem_form_mailheader($string, $type)\r\n{\r\n	global $prefs;\r\n\r\n	if (!strstr($string,\'=?\') and !preg_match(\'/[\\x00-\\x1F\\x7F-\\xFF]/\', $string)) {\r\n		if (\"phrase\" == $type) {\r\n			if (preg_match(\'/[][()<>@,;:\".\\x5C]/\', $string)) {\r\n				$string = \'\"\'. strtr($string, array(\"\\\\\" => \"\\\\\\\\\", \'\"\' => \'\\\"\')) . \'\"\';\r\n			}\r\n		}\r\n		elseif (\"text\" != $type) {\r\n			trigger_error(\'Unknown encode_mailheader type\', E_USER_WARNING);\r\n		}\r\n		return $string;\r\n	}\r\n	if ($prefs[\'override_emailcharset\']) {\r\n		$start = \'=?ISO-8859-1?B?\';\r\n		$pcre  = \'/.{1,42}/s\';\r\n	}\r\n	else {\r\n		$start = \'=?UTF-8?B?\';\r\n		$pcre  = \'/.{1,45}(?=[\\x00-\\x7F\\xC0-\\xFF]|$)/s\';\r\n	}\r\n	$end = \'?=\';\r\n	$sep = is_windows() ? \"\\r\\n\" : \"\\n\";\r\n	preg_match_all($pcre, $string, $matches);\r\n	return $start . join($end.$sep.\' \'.$start, array_map(\'base64_encode\',$matches[0])) . $end;\r\n}\r\n\r\nfunction mem_form_strip($str, $header = TRUE) {\r\n	if ($header) $str = strip_rn($str);\r\n	return preg_replace(\'/[\\x00]/\', \' \', $str);\r\n}\r\n\r\n///////////////////////////////////////////////\r\n// Spam Evaluator\r\nclass mem_form_evaluation\r\n{\r\n	var $status;\r\n\r\n	function mem_form_evaluation() {\r\n		$this->status = 0;\r\n	}\r\n\r\n	function add_status($rating=-1) {\r\n		$this->status += $rating;\r\n	}\r\n\r\n	function get_status() {\r\n		return $this->status;\r\n	}\r\n\r\n	function is_spam() {\r\n		return ($this->status < 0);\r\n	}\r\n}\r\n\r\nfunction &get_mem_form_evaluator()\r\n{\r\n	static $instance;\r\n\r\n	if(!isset($instance)) {\r\n		$instance = new mem_form_evaluation();\r\n	}\r\n	return $instance;\r\n}', '$mem_glz_custom_fields_plugin = load_plugin(\'glz_custom_fields\');\r\n\r\n// needed for MLP\r\ndefine( \'MEM_FORM_PREFIX\' , \'mem_form\' );\r\n\r\nglobal $mem_form_lang;\r\n\r\nif (!is_array($mem_form_lang))\r\n{\r\n	$mem_form_lang = array(\r\n		\'error_file_extension\'	=> \'File upload failed for field {label}.\',\r\n		\'error_file_failed\'	=> \'Failed to upload file for field {label}.\',\r\n		\'error_file_size\'	=> \'Failed to upload File for field {label}. File is too large.\',\r\n		\'field_missing\'	=> \'The field {label} is required.\',\r\n		\'form_expired\'	=>	\'The form has expired.\',\r\n		\'form_misconfigured\'	=> \'The mem_form is misconfigured. You must specify the \"form\" attribute.\',\r\n		\'form_sorry\'	=> \'The form is currently unavailable.\',\r\n		\'form_used\'	=>	\'This form has already been used to submit.\',\r\n		\'general_inquiry\'	=> \'\',\r\n		\'invalid_email\'	=> \'The email address {email} is invalid.\',\r\n		\'invalid_host\'	=> \'The host {domain} is invalid.\',\r\n		\'invalid_utf8\'	=> \'Invalid UTF8 string for field {label}.\',\r\n		\'invalid_value\'	=> \'The value \"{value}\" is invalid for the input field {label}.\',\r\n		\'invalid_format\'	=>	\'The input field {label} must match the format \"{example}\".\',\r\n		\'invalid_too_many_selected\'	=> \'The input field {label} only allows {count} selected {plural}.\',\r\n		\'item\'	=> \'item\',\r\n		\'items\'	=> \'items\',\r\n		\'max_warning\'	=> \'The input field {label} must be smaller than {max} characters long.\',\r\n		\'min_warning\'	=> \'The input field {label} must be at least {min} characters long.\',\r\n		\'refresh\'	=> \'Refresh\',\r\n		\'spam\'	=> \'Your submission was blocked by a spam filter.\',\r\n		\'submitted_thanks\'	=>	\'You have successfully submitted the form. Thank you.\',\r\n	);\r\n}\r\n\r\nregister_callback( \'mem_form_enumerate_strings\' , \'l10n.enumerate_strings\' );\r\nfunction mem_form_enumerate_strings($event , $step=\'\' , $pre=0)\r\n{\r\n	global $mem_form_lang;\r\n	$r = array	(\r\n				\'owner\'		=> \'mem_form\',			#	Change to your plugin\'s name\r\n				\'prefix\'	=> MEM_FORM_PREFIX,		#	Its unique string prefix\r\n				\'lang\'		=> \'en-gb\',				#	The language of the initial strings.\r\n				\'event\'		=> \'public\',			#	public/admin/common = which interface the strings will be loaded into\r\n				\'strings\'	=> $mem_form_lang,		#	The strings themselves.\r\n				);\r\n	return $r;\r\n}\r\n\r\n\r\nfunction mem_form_gTxt($what,$args = array())\r\n{\r\n	global $mem_form_lang, $textarray;\r\n\r\n	$key = strtolower( MEM_FORM_PREFIX . \'-\' . $what );\r\n\r\n	if (isset($textarray[$key]))\r\n	{\r\n		$str = $textarray[$key];\r\n	}\r\n	else\r\n	{\r\n		$key = strtolower($what);\r\n\r\n		if (isset($mem_form_lang[$key]))\r\n			$str = $mem_form_lang[$key];\r\n		elseif (isset($textarray[$key]))\r\n			$str = $textarray[$key];\r\n		else\r\n			$str = $what;\r\n	}\r\n\r\n	if( !empty($args) )\r\n		$str = strtr( $str , $args );\r\n\r\n	return $str;\r\n}\r\n\r\n\r\nfunction mem_form($atts, $thing=\'\', $default=false)\r\n{\r\n	global $sitename, $prefs, $file_max_upload_size, $mem_form_error, $mem_form_submit,\r\n		$mem_form, $mem_form_labels, $mem_form_values,\r\n		$mem_form_default, $mem_form_type, $mem_form_thanks_form,\r\n		$mem_glz_custom_fields_plugin;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'form\'		=> \'\',\r\n		\'thanks_form\'	=> \'\',\r\n		\'thanks\'	=> graf(mem_form_gTxt(\'submitted_thanks\')),\r\n		\'label\'		=> \'\',\r\n		\'type\'		=> \'\',\r\n		\'redirect\'	=> \'\',\r\n		\'redirect_form\'	=> \'\',\r\n		\'class\'		=> \'memForm\',\r\n		\'enctype\'	=> \'\',\r\n		\'file_accept\'	=> \'\',\r\n		\'max_file_size\'	=> $file_max_upload_size,\r\n		\'form_expired_msg\' => mem_form_gTxt(\'form_expired\'),\r\n		\'show_error\'	=> 1,\r\n		\'show_input\'	=> 1,\r\n	), $atts));\r\n\r\n	if (empty($type) or (empty($form) && empty($thing))) {\r\n		trigger_error(\'Argument not specified for mem_form tag\', E_USER_WARNING);\r\n\r\n		return \'\';\r\n	}\r\n	$out = \'\';\r\n\r\n	// init error structure\r\n	mem_form_error();\r\n\r\n	$mem_form_type = $type;\r\n\r\n	$mem_form_default = is_array($default) ? $default : array();\r\n	callback_event(\'mem_form.defaults\');\r\n\r\n	unset($atts[\'show_error\'], $atts[\'show_input\']);\r\n	$mem_form_id = md5(serialize($atts).preg_replace(\'/[\\t\\s\\r\\n]/\',\'\',$thing));\r\n	$mem_form_submit = (ps(\'mem_form_id\') == $mem_form_id);\r\n\r\n	$nonce   = doSlash(ps(\'mem_form_nonce\'));\r\n	$renonce = false;\r\n\r\n	if ($mem_form_submit) {\r\n		safe_delete(\'txp_discuss_nonce\', \'issue_time < date_sub(now(), interval 10 minute)\');\r\n		if ($rs = safe_row(\'used\', \'txp_discuss_nonce\', \"nonce = \'$nonce\'\"))\r\n		{\r\n			if ($rs[\'used\'])\r\n			{\r\n				unset($mem_form_error);\r\n				mem_form_error(mem_form_gTxt(\'form_used\'));\r\n				$renonce = true;\r\n\r\n				$_POST[\'mem_form_submit\'] = TRUE;\r\n				$_POST[\'mem_form_id\'] = $mem_form_id;\r\n				$_POST[\'mem_form_nonce\'] = $nonce;\r\n			}\r\n		}\r\n		else\r\n		{\r\n			mem_form_error($form_expired_msg);\r\n			$renonce = true;\r\n		}\r\n	}\r\n\r\n	if ($mem_form_submit and $nonce and !$renonce)\r\n	{\r\n		$mem_form_nonce = $nonce;\r\n	}\r\n\r\n	elseif (!$show_error or $show_input)\r\n	{\r\n		$mem_form_nonce = md5(uniqid(rand(), true));\r\n		safe_insert(\'txp_discuss_nonce\', \"issue_time = now(), nonce = \'$mem_form_nonce\'\");\r\n	}\r\n\r\n	$form = ($form) ? fetch_form($form) : $thing;\r\n	$form = parse($form);\r\n\r\n	if ($mem_form_submit && empty($mem_form_error))\r\n	{\r\n		// let plugins validate after individual fields are validated\r\n		callback_event(\'mem_form.validate\');\r\n	}\r\n\r\n	if (!$mem_form_submit) {\r\n	  # don\'t show errors or send mail\r\n	}\r\n	elseif (mem_form_error())\r\n	{\r\n		if ($show_error or !$show_input)\r\n		{\r\n			$out .= mem_form_display_error();\r\n\r\n			if (!$show_input) return $out;\r\n		}\r\n	}\r\n	elseif ($show_input and is_array($mem_form))\r\n	{\r\n		if ($mem_glz_custom_fields_plugin) {\r\n			// prep the values\r\n			glz_custom_fields_before_save();\r\n		}\r\n\r\n		callback_event(\'mem_form.spam\');\r\n\r\n		/// load and check spam plugins/\r\n		$evaluator =& get_mem_form_evaluator();\r\n		$is_spam = $evaluator->is_spam();\r\n\r\n		if ($is_spam) {\r\n			return mem_form_gTxt(\'spam\');\r\n		}\r\n\r\n		$mem_form_thanks_form = ($thanks_form ? fetch_form($thanks_form) : $thanks);\r\n\r\n		safe_update(\'txp_discuss_nonce\', \"used = \'1\', issue_time = now()\", \"nonce = \'$nonce\'\");\r\n\r\n		$result = callback_event(\'mem_form.submit\');\r\n\r\n		if (mem_form_error()) {\r\n			$out .= mem_form_display_error();\r\n			$redirect = false;\r\n		}\r\n\r\n		$thanks_form = $mem_form_thanks_form;\r\n		unset($mem_form_thanks_form);\r\n\r\n		if (!empty($result))\r\n			return $result;\r\n\r\n		if (mem_form_error() and $show_input)\r\n		{\r\n			// no-op, reshow form with errors\r\n		}\r\n		else if ($redirect)\r\n		{\r\n			$_POST = array();\r\n\r\n			while (@ob_end_clean());\r\n			$uri = hu.ltrim($redirect,\'/\');\r\n			if (empty($_SERVER[\'FCGI_ROLE\']) and empty($_ENV[\'FCGI_ROLE\']))\r\n			{\r\n				txp_status_header(\'303 See Other\');\r\n				header(\'Location: \'.$uri);\r\n				header(\'Connection: close\');\r\n				header(\'Content-Length: 0\');\r\n			}\r\n			else\r\n			{\r\n				$uri = htmlspecialchars($uri);\r\n				$refresh = mem_form_gTxt(\'refresh\');\r\n\r\n				if (!empty($redirect_form))\r\n				{\r\n					$redirect_form = fetch_form($redirect_form);\r\n\r\n					echo str_replace(\'{uri}\', $uri, $redirect_form);\r\n				}\r\n\r\n				if (empty($redirect_form))\r\n				{\r\n					echo <<<END\r\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n<head>\r\n	<title>$sitename</title>\r\n	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n	<meta http-equiv=\"refresh\" content=\"0;url=$uri\" />\r\n</head>\r\n<body>\r\n<a href=\"$uri\">$refresh</a>\r\n</body>\r\n</html>\r\nEND;\r\n				}\r\n			}\r\n			exit;\r\n		}\r\n		else {\r\n			return \'<div class=\"memThanks\" id=\"mem\'.$mem_form_id.\'\">\' .\r\n				$thanks_form . \'</div>\';\r\n		}\r\n	}\r\n\r\n	if ($show_input)\r\n	{\r\n		$file_accept = (!empty($file_accept) ? \' accept=\"\'.$file_accept.\'\"\' : \'\');\r\n\r\n		$class = htmlspecialchars($class);\r\n\r\n		$enctype = !empty($enctype) ? \' enctype=\"\'.$enctype.\'\"\' : \'\';\r\n\r\n		return \'<form method=\"post\"\'.((!$show_error and $mem_form_error) ? \'\' : \' id=\"mem\'.$mem_form_id.\'\"\').\' class=\"\'.$class.\'\" action=\"\'.htmlspecialchars(serverSet(\'REQUEST_URI\')).\'#mem\'.$mem_form_id.\'\"\'.$file_accept.$enctype.\'>\'.\r\n			( $label ? n.\'<fieldset>\' : n.\'<div>\' ).\r\n			( $label ? n.\'<legend>\'.htmlspecialchars($label).\'</legend>\' : \'\' ).\r\n			$out.\r\n			n.\'<input type=\"hidden\" name=\"mem_form_nonce\" value=\"\'.$mem_form_nonce.\'\" />\'.\r\n			n.\'<input type=\"hidden\" name=\"mem_form_id\" value=\"\'.$mem_form_id.\'\" />\'.\r\n			(!empty($max_file_size) ? n.\'<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"\'.$max_file_size.\'\" />\' : \'\' ).\r\n			callback_event(\'mem_form.display\',\'\',1).\r\n			$form.\r\n			callback_event(\'mem_form.display\').\r\n			( $label ? (n.\'</fieldset>\') : (n.\'</div>\') ).\r\n			n.\'</form>\';\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\nfunction mem_form_text($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'text\'),\r\n		\'max\'		=> 100,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memText\',\r\n		\'required\'	=> 1,\r\n		\'size\'		=> \'\',\r\n		\'password\'	=> 0,\r\n		\'format\'	=> \'\',\r\n		\'example\'	=> \'\',\r\n		\'escape_value\'	=> 1\r\n	), $atts));\r\n\r\n	$min = intval($min);\r\n	$max = intval($max);\r\n	$size = intval($size);\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = trim(ps($name));\r\n		$utf8len = preg_match_all(\"/./su\", $value, $utf8ar);\r\n		$hlabel = empty($label) ? htmlspecialchars($name) : htmlspecialchars($label);\r\n\r\n\r\n		if (strlen($value) == 0 && $required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n			$isError = true;\r\n		}\r\n		elseif ($required && !empty($format) && !preg_match($format, $value))\r\n		{\r\n			//echo \"format=$format<br />value=$value<br />\";\r\n			$mem_form_error[] = mem_form_gTxt(\'invalid_format\', array(\'{label}\'=>$hlabel, \'{example}\'=> htmlspecialchars($example)));\r\n			$isError = true;\r\n		}\r\n		elseif (strlen($value))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', array(\'{label}\'=>$hlabel));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($min and $utf8len < $min)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'min_warning\', array(\'{label}\'=>$hlabel, \'{min}\'=>$min));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($max and $utf8len > $max)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'max_warning\', array(\'{label}\'=>$hlabel, \'{max}\'=>$max));\r\n				$isError = true;\r\n			}\r\n\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else\r\n			$value = $default;\r\n	}\r\n\r\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\r\n	$maxlength = ($max) ? \' maxlength=\"\'.$max.\'\"\' : \'\';\r\n\r\n	$isError = $isError ? \"errorElement\" : \'\';\r\n\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n    return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		\'<input type=\"\'.($password ? \'password\' : \'text\').\'\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\"\'.$size.$maxlength.\' />\';\r\n}\r\n\r\n\r\nfunction mem_form_file($atts)\r\n{\r\n	global $mem_form_submit, $mem_form_error, $mem_form_default, $file_max_upload_size, $tempdir;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'file\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memFile\',\r\n		\'size\'		=> \'\',\r\n		\'accept\'	=> \'\',\r\n		\'no_replace\' => 1,\r\n		\'max_file_size\'	=> $file_max_upload_size,\r\n		\'required\'	=> 1,\r\n		\'default\'	=> FALSE,\r\n	), $atts));\r\n\r\n	$fname = ps(\'file_\'.$name);\r\n	$frealname = ps(\'file_info_\'.$name.\'_name\');\r\n	$ftype = ps(\'file_info_\'.$name.\'_type\');\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	$out = \'\';\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (!empty($fname))\r\n		{\r\n			// see if user uploaded a different file to replace already uploaded\r\n			if (isset($_FILES[$name]) && !empty($_FILES[$name][\'tmp_name\']))\r\n			{\r\n				// unlink last temp file\r\n				if (file_exists($fname) && substr_compare($fname, $tempdir, 0, strlen($tempdir), 1)==0)\r\n					unlink($fname);\r\n\r\n				$fname = \'\';\r\n			}\r\n			else\r\n			{\r\n				// pass through already uploaded filename\r\n				mem_form_store($name, $label, array(\'tmp_name\'=>$fname, \'name\' => $frealname, \'type\' => $ftype));\r\n				$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n						. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($frealname).\"\' />\"\r\n						. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($ftype).\"\' />\";\r\n			}\r\n		}\r\n\r\n		if (empty($fname))\r\n		{\r\n			$hlabel = empty($label) ? htmlspecialchars($name) : htmlspecialchars($label);\r\n\r\n			$fname = $_FILES[$name][\'tmp_name\'];\r\n			$frealname = $_FILES[$name][\'name\'];\r\n			$ftype = $_FILES[$name][\'type\'];\r\n			$err = 0;\r\n\r\n			switch ($_FILES[$name][\'error\']) {\r\n				case UPLOAD_ERR_OK:\r\n					if (is_uploaded_file($fname) and $max_file_size >= filesize($fname))\r\n						mem_form_store($name, $label, $_FILES[$name]);\r\n					elseif (!is_uploaded_file($fname)) {\r\n						if ($required) {\r\n							$mem_form_error[] = mem_form_gTxt(\'error_file_failed\', array(\'{label}\'=>$hlabel));\r\n							$err = 1;\r\n						}\r\n					}\r\n					else {\r\n						$mem_form_error[] = mem_form_gTxt(\'error_file_size\', array(\'{label}\'=>$hlabel));\r\n						$err = 1;\r\n					}\r\n					break;\r\n\r\n				case UPLOAD_ERR_NO_FILE:\r\n					if ($required) {\r\n						$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n						$err = 1;\r\n					}\r\n					break;\r\n\r\n				case UPLOAD_ERR_EXTENSION:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_extension\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n\r\n				case UPLOAD_ERR_INI_SIZE:\r\n				case UPLOAD_ERR_FORM_SIZE:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_size\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n\r\n				default:\r\n					$mem_form_error[] = mem_form_gTxt(\'error_file_failed\', array(\'{label}\'=>$hlabel));\r\n					$err = 1;\r\n					break;\r\n			}\r\n\r\n			if (!$err)\r\n			{\r\n				// store as a txp tmp file to be used later\r\n				$fname = get_uploaded_file($fname);\r\n				$err = false === mem_form_store($name, $label, array(\'tmp_name\'=>$fname, \'name\' => $frealname, \'type\' => $ftype));\r\n				if ($err)\r\n				{\r\n					// clean up file\r\n					@unlink($fname);\r\n				}\r\n				else\r\n				{\r\n					$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n							. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($_FILES[$name][\'name\']).\"\' />\"\r\n							. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($_FILES[$name][\'type\']).\"\' />\";\r\n				}\r\n			}\r\n\r\n			$isError = $err ? \'errorElement\' : \'\';\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if (is_array($default))\r\n			$value = $default;\r\n\r\n		if (is_array(@$value))\r\n		{\r\n			$fname = @$value[\'tmp_name\'];\r\n			$frealname = @$value[\'name\'];\r\n			$ftype = @$value[\'type\'];\r\n			$out .= \"<input type=\'hidden\' name=\'file_\".$name.\"\' value=\'\".htmlspecialchars($fname).\"\' />\"\r\n				. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_name\' value=\'\".htmlspecialchars($frealname).\"\' />\"\r\n				. \"<input type=\'hidden\' name=\'file_info_\".$name.\"_type\' value=\'\".htmlspecialchars($ftype).\"\' />\";\r\n		}\r\n	}\r\n\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	$size = ($size) ? \' size=\"\'.$size.\'\"\' : \'\';\r\n	$accept = (!empty($accept) ? \' accept=\"\'.$accept.\'\"\' : \'\');\r\n\r\n\r\n	$field_out = \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break;\r\n\r\n	if (!empty($frealname) && $no_replace)\r\n	{\r\n		$field_out .= \'<div id=\"\'.$name.\'\">\'.htmlspecialchars($frealname) . \' <span id=\"\'.$name.\'_ftype\">(\'. htmlspecialchars($ftype).\')</span></div>\';\r\n	}\r\n	else\r\n	{\r\n		$field_out .= \'<input type=\"file\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\"\' .$size.\' />\';\r\n	}\r\n\r\n  return $out.$field_out;\r\n}\r\n\r\nfunction mem_form_textarea($atts, $thing=\'\')\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> br,\r\n		\'cols\'		=> 58,\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'textarea\'),\r\n		\'max\'		=> 10000,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memTextarea\',\r\n		\'required\'	=> 1,\r\n		\'rows\'		=> 8,\r\n		\'escape_value\'	=> 1\r\n	), $atts));\r\n\r\n	$min = intval($min);\r\n	$max = intval($max);\r\n	$cols = intval($cols);\r\n	$rows = intval($rows);\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\r\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\r\n		$hlabel = htmlspecialchars($label);\r\n\r\n		if (strlen(ltrim($value)))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', array(\'{label}\'=>$hlabel));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($min and $utf8len < $min)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'min_warning\', array(\'{label}\'=>$hlabel, \'{min}\'=>$min));\r\n				$isError = true;\r\n			}\r\n\r\n			elseif ($max and $utf8len > $max)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'max_warning\', array(\'{label}\'=>$hlabel, \'{max}\'=>$max));\r\n				$isError = true;\r\n			}\r\n\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n\r\n		elseif ($required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=>$hlabel));\r\n			$isError = true;\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if (!empty($default))\r\n			$value = $default;\r\n		else\r\n			$value = parse($thing);\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n	return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		\'<textarea id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\" cols=\"\'.$cols.\'\" rows=\"\'.$rows.\'\">\'.$value.\'</textarea>\';\r\n}\r\n\r\nfunction mem_form_email($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_from, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'default\'	=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'email\'),\r\n		\'max\'		=> 100,\r\n		\'min\'		=> 0,\r\n		\'name\'		=> \'\',\r\n		\'required\'	=> 1,\r\n		\'break\'		=> br,\r\n		\'size\'		=> \'\',\r\n		\'class\'		=> \'memEmail\',\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$email = trim(ps($name));\r\n\r\n		if (strlen($email))\r\n		{\r\n			if (!is_valid_email($email))\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_email\', array(\'{email}\'=>htmlspecialchars($email)));\r\n				$isError = true;\r\n			}\r\n			else\r\n			{\r\n				preg_match(\"/@(.+)$/\", $email, $match);\r\n				$domain = $match[1];\r\n\r\n				if (is_callable(\'checkdnsrr\') and checkdnsrr(\'textpattern.com.\',\'A\') and !checkdnsrr($domain.\'.\',\'MX\') and !checkdnsrr($domain.\'.\',\'A\'))\r\n				{\r\n					$mem_form_error[] = mem_form_gTxt(\'invalid_host\', array(\'{domain}\'=>htmlspecialchars($domain)));\r\n					$isError = true;\r\n				}\r\n				else\r\n				{\r\n					$mem_form_from = $email;\r\n				}\r\n			}\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$email = $mem_form_default[$name];\r\n		else\r\n			$email = $default;\r\n	}\r\n\r\n	return mem_form_text(array(\r\n		\'default\'	=> $email,\r\n		\'isError\'	=> $isError,\r\n		\'label\'		=> $label,\r\n		\'max\'		=> $max,\r\n		\'min\'		=> $min,\r\n		\'name\'		=> $name,\r\n		\'required\'	=> $required,\r\n		\'break\'		=> $break,\r\n		\'size\'		=> $size,\r\n		\'class\'		=> $class,\r\n	));\r\n}\r\n\r\nfunction mem_form_select_section($atts)\r\n{\r\n	extract(mem_form_lAtts(array(\r\n		\'exclude\'	=> \'\',\r\n		\'sort\'		=> \'name ASC\',\r\n		\'delimiter\'	=> \',\',\r\n	),$atts,false));\r\n\r\n	if (!empty($exclude)) {\r\n		$exclusion = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$exclude)));\r\n		$exclusion = array_map(\'strtolower\', $exclusion);\r\n\r\n		if (count($exclusion))\r\n			$exclusion = join($delimiter, quote_list($exclusion));\r\n	}\r\n\r\n	$where = empty($exclusion) ? \'1=1\' : \'LOWER(name) NOT IN (\'.$exclusion.\')\';\r\n\r\n	$sort = empty($sort) ? \'\' : \' ORDER BY \'. doSlash($sort);\r\n\r\n	$rs = safe_rows(\'name, title\',\'txp_section\',$where . $sort);\r\n\r\n	$items = array();\r\n	$values = array();\r\n\r\n	if ($rs) {\r\n		foreach($rs as $r) {\r\n			$items[] = $r[\'title\'];\r\n			$values[] = $r[\'name\'];\r\n		}\r\n	}\r\n\r\n	unset($atts[\'exclude\'], $atts[\'sort\']);\r\n\r\n	$atts[\'items\'] = join($delimiter, $items);\r\n	$atts[\'values\'] = join($delimiter, $values);\r\n\r\n	return mem_form_select($atts);\r\n}\r\n\r\nfunction mem_form_select_category($atts)\r\n{\r\n	extract(mem_form_lAtts(array(\r\n		\'root\'	=> \'root\',\r\n		\'exclude\'	=> \'\',\r\n		\'delimiter\'	=> \',\',\r\n		\'type\'	=> \'article\'\r\n	),$atts,false));\r\n\r\n	$rs = getTree($root, $type);\r\n\r\n	if (!empty($exclude)) {\r\n		$exclusion = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$exclude)));\r\n		$exclusion = array_map(\'strtolower\', $exclusion);\r\n	}\r\n	else\r\n		$exclusion = array();\r\n\r\n	$items = array();\r\n	$values = array();\r\n\r\n	if ($rs) {\r\n		foreach ($rs as $cat) {\r\n			if (count($exclusion) && in_array(strtolower($cat[\'name\']), $exclusion))\r\n				continue;\r\n\r\n			$items[] = $cat[\'title\'];\r\n			$values[] = $cat[\'name\'];\r\n		}\r\n	}\r\n\r\n	unset($atts[\'root\'], $atts[\'type\']);\r\n\r\n	$atts[\'items\'] = join($delimiter, $items);\r\n	$atts[\'values\'] = join($delimiter, $values);\r\n\r\n	return mem_form_select($atts);\r\n}\r\n\r\nfunction mem_form_select($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'break\'		=> \' \',\r\n		\'delimiter\'	=> \',\',\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'option\'),\r\n		\'items\'		=> mem_form_gTxt(\'general_inquiry\'),\r\n		\'values\'	=> \'\',\r\n		\'first\'		=> FALSE,\r\n		\'required\'	=> 1,\r\n		\'select_limit\'	=> FALSE,\r\n		\'as_csv\'	=> FALSE,\r\n		\'selected\'	=> \'\',\r\n		\'class\'		=> \'memSelect\',\r\n	), $atts, false));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if (!empty($items) && $items[0] == \'<\') $items = parse($items);\r\n	if (!empty($values) && $values[0] == \'<\') $values = parse($values);\r\n\r\n	if ($first !== FALSE) {\r\n		$items = $first.$delimiter.$atts[\'items\'];\r\n		$values = $first.$delimiter.$atts[\'values\'];\r\n	}\r\n\r\n	$select_limit = empty($select_limit) ? 1 : assert_int($select_limit);\r\n\r\n	$items = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$items)));\r\n	$values = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$values)));\r\n	if ($select_limit > 1)\r\n	{\r\n		$selected = array_map(\'trim\', explode($delimiter, preg_replace(\'/[\\r\\n\\t\\s]+/\', \' \',$seelcted)));\r\n	}\r\n	else\r\n	{\r\n		$selected = array(trim($selected));\r\n	}\r\n\r\n	$use_values_array = (count($items) == count($values));\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (strpos($name, \'[]\'))\r\n		{\r\n			$value = ps(substr($name, 0, strlen($name)-2));\r\n\r\n			$selected = $value;\r\n\r\n			if ($as_csv)\r\n			{\r\n				$value = implode($delimiter, $value);\r\n			}\r\n		}\r\n		else\r\n		{\r\n			$value = trim(ps($name));\r\n\r\n			$selected = array($value);\r\n		}\r\n\r\n		if (!empty($selected))\r\n		{\r\n			if (count($selected) <= $select_limit)\r\n			{\r\n				foreach ($selected as $v)\r\n				{\r\n					$is_valid = ($use_values_array && in_array($v, $values)) or (!$use_values_array && in_array($v, $items));\r\n					if (!$is_valid)\r\n					{\r\n						$invalid_value = $v;\r\n						break;\r\n					}\r\n				}\r\n\r\n				if ($is_valid)\r\n				{\r\n					$isError = false === mem_form_store($name, $label, $value);\r\n				}\r\n				else\r\n				{\r\n					$mem_form_error[] = mem_form_gTxt(\'invalid_value\', array(\'{label}\'=> htmlspecialchars($label), \'{value}\'=> htmlspecialchars($invalid_value)));\r\n					$isError = true;\r\n				}\r\n			}\r\n			else\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_too_many_selected\', array(\r\n											\'{label}\'=> htmlspecialchars($label),\r\n											\'{count}\'=> $select_limit,\r\n											\'{plural}\'=> ($select_limit==1 ? mem_form_gTxt(\'item\') : mem_form_gTxt(\'items\'))\r\n										));\r\n				$isError = true;\r\n			}\r\n		}\r\n\r\n		elseif ($required)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=> htmlspecialchars($label)));\r\n			$isError = true;\r\n		}\r\n	}\r\n	else if (isset($mem_form_default[$name]))\r\n	{\r\n		$selected = array($mem_form_default[$name]);\r\n	}\r\n\r\n	$out = \'\';\r\n\r\n	foreach ($items as $item)\r\n	{\r\n		$v = $use_values_array ? array_shift($values) : $item;\r\n\r\n		$sel = !empty($selected) && in_array($v, $selected);\r\n\r\n		$out .= n.t.\'<option\'.($use_values_array ? \' value=\"\'.$v.\'\"\' : \'\').($sel ? \' selected=\"selected\">\' : \'>\').\r\n				(strlen($item) ? htmlspecialchars($item) : \' \').\'</option>\';\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	$multiple = $select_limit > 1 ? \' multiple=\"multiple\"\' : \'\';\r\n\r\n	return \'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\'.$break.\r\n		n.\'<select id=\"\'.$name.\'\" name=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\"\' . $multiple . \'>\'.\r\n			$out.\r\n		n.\'</select>\';\r\n}\r\n\r\nfunction mem_form_checkbox($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> \' \',\r\n		\'checked\'	=> 0,\r\n		\'isError\'	=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'checkbox\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memCheckbox\',\r\n		\'required\'	=> 1\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = (bool) ps($name);\r\n\r\n		if ($required and !$value)\r\n		{\r\n			$mem_form_error[] = mem_form_gTxt(\'field_missing\', array(\'{label}\'=> htmlspecialchars($label)));\r\n			$isError = true;\r\n		}\r\n\r\n		else\r\n		{\r\n			$isError = false === mem_form_store($name, $label, $value ? gTxt(\'yes\') : gTxt(\'no\'));\r\n		}\r\n	}\r\n\r\n	else {\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else\r\n			$value = $checked;\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n	$class = htmlspecialchars($class);\r\n\r\n	return \'<input type=\"checkbox\" id=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\'\" name=\"\'.$name.\'\"\'.\r\n		($value ? \' checked=\"checked\"\' : \'\').\' />\'.$break.\r\n		\'<label for=\"\'.$name.\'\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\r\n}\r\n\r\n\r\nfunction mem_form_serverinfo($atts)\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'label\'		=> \'\',\r\n		\'name\'		=> \'\'\r\n	), $atts));\r\n\r\n	if (empty($name)) $name = mem_form_label2name($label);\r\n\r\n	if (strlen($name) and $mem_form_submit)\r\n	{\r\n		if (!$label) $label = $name;\r\n		mem_form_store($name, $label, serverSet($name));\r\n	}\r\n}\r\n\r\nfunction mem_form_secret($atts, $thing = \'\')\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'	=> \'\',\r\n		\'label\'	=> mem_form_gTxt(\'secret\'),\r\n		\'value\'	=> \'\'\r\n	), $atts));\r\n\r\n\r\n	$name = mem_form_label2name($name ? $name : $label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if ($thing)\r\n			$value = trim(parse($thing));\r\n		else\r\n			$value = trim(parse($value));\r\n\r\n		mem_form_store($name, $label, $value);\r\n	}\r\n\r\n	return \'\';\r\n}\r\n\r\nfunction mem_form_hidden($atts, $thing=\'\')\r\n{\r\n	global $mem_form_submit, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'hidden\'),\r\n		\'value\'		=> \'\',\r\n		\'isError\'	=> \'\',\r\n		\'required\'	=> 1,\r\n		\'class\'		=> \'memHidden\',\r\n		\'escape_value\'	=> 1,\r\n	), $atts));\r\n\r\n	$name = mem_form_label2name($name ? $name : $label);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = preg_replace(\'/^\\s*[\\r\\n]/\', \'\', rtrim(ps($name)));\r\n		$utf8len = preg_match_all(\"/./su\", ltrim($value), $utf8ar);\r\n		$hlabel = htmlspecialchars($label);\r\n\r\n		if (strlen($value))\r\n		{\r\n			if (!$utf8len)\r\n			{\r\n				$mem_form_error[] = mem_form_gTxt(\'invalid_utf8\', $hlabel);\r\n				$isError = true;\r\n			}\r\n			else\r\n			{\r\n				$isError = false === mem_form_store($name, $label, $value);\r\n			}\r\n		}\r\n	}\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$value = $mem_form_default[$name];\r\n		else if ($thing)\r\n			$value = trim(parse($thing));\r\n	}\r\n\r\n	$isError = $isError ? \'errorElement\' : \'\';\r\n	$memRequired = $required ? \'memRequired\' : \'\';\r\n\r\n	if ($escape_value)\r\n	{\r\n		$value = htmlspecialchars($value);\r\n	}\r\n\r\n	return \'<input type=\"hidden\" class=\"\'.$class.\' \'.$memRequired.$isError.\' \'.$name\r\n			. \'\" name=\"\'.$name.\'\" value=\"\'.$value.\'\" id=\"\'.$name.\'\" />\';\r\n}\r\n\r\nfunction mem_form_radio($atts)\r\n{\r\n	global $mem_form_error, $mem_form_submit, $mem_form_values, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'break\'		=> \' \',\r\n		\'checked\'	=> 0,\r\n		\'group\'		=> \'\',\r\n		\'label\'		=> mem_form_gTxt(\'option\'),\r\n		\'name\'		=> \'\',\r\n		\'class\'		=> \'memRadio\',\r\n		\'isError\'	=> \'\',\r\n		\'value\'		=> false\r\n	), $atts));\r\n\r\n	static $cur_name = \'\';\r\n	static $cur_group = \'\';\r\n\r\n	if (!$name and !$group and !$cur_name and !$cur_group) {\r\n		$cur_group = mem_form_gTxt(\'radio\');\r\n		$cur_name = $cur_group;\r\n	}\r\n	if ($group and !$name and $group != $cur_group) $name = $group;\r\n\r\n	if ($name) $cur_name = $name;\r\n	else $name = $cur_name;\r\n\r\n	if ($group) $cur_group = $group;\r\n	else $group = $cur_group;\r\n\r\n	$id   = \'q\'.md5($name.\'=>\'.$label);\r\n	$name = mem_form_label2name($name);\r\n\r\n	$value = $value === false ? $id : $value;\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$is_checked = (ps($name) == $value);\r\n\r\n		if ($is_checked or $checked and !isset($mem_form_values[$name]))\r\n		{\r\n			$isError = false === mem_form_store($name, $group, $value);\r\n		}\r\n	}\r\n\r\n	else\r\n	{\r\n		if (isset($mem_form_default[$name]))\r\n			$is_checked = $mem_form_default[$name] == $value;\r\n		else\r\n			$is_checked = $checked;\r\n	}\r\n\r\n	$class = htmlspecialchars($class);\r\n\r\n	$isError = $isError ? \' errorElement\' : \'\';\r\n\r\n	return \'<input value=\"\'.$value.\'\" type=\"radio\" id=\"\'.$id.\'\" class=\"\'.$class.\' \'.$name.$isError.\'\" name=\"\'.$name.\'\"\'.\r\n		( $is_checked ? \' checked=\"checked\" />\' : \' />\').$break.\r\n		\'<label for=\"\'.$id.\'\" class=\"\'.$class.\' \'.$name.\'\">\'.htmlspecialchars($label).\'</label>\';\r\n}\r\n\r\nfunction mem_form_submit($atts, $thing=\'\')\r\n{\r\n	global $mem_form_submit;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'button\'	=> 0,\r\n		\'label\'		=> mem_form_gTxt(\'save\'),\r\n		\'name\'		=> \'mem_form_submit\',\r\n		\'class\'		=> \'memSubmit\',\r\n	), $atts));\r\n\r\n	$label = htmlspecialchars($label);\r\n	$name = htmlspecialchars($name);\r\n	$class = htmlspecialchars($class);\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		$value = ps($name);\r\n\r\n		if (!empty($value) && $value == $label)\r\n		{\r\n			// save the clicked button value\r\n			mem_form_store($name, $label, $value);\r\n		}\r\n	}\r\n\r\n	if ($button or strlen($thing))\r\n	{\r\n		return \'<button type=\"submit\" class=\"\'.$class.\'\" name=\"\'.$name.\'\" value=\"\'.$label.\'\">\'.($thing ? trim(parse($thing)) : $label).\'</button>\';\r\n	}\r\n	else\r\n	{\r\n		return \'<input type=\"submit\" class=\"\'.$class.\'\" name=\"\'.$name.\'\" value=\"\'.$label.\'\" />\';\r\n	}\r\n}\r\n\r\nfunction mem_form_lAtts($arr, $atts, $warn=true)\r\n{\r\n	foreach(array(\'button\', \'checked\', \'required\', \'show_input\', \'show_error\') as $key)\r\n	{\r\n		if (isset($atts[$key]))\r\n		{\r\n			$atts[$key] = ($atts[$key] === \'yes\' or intval($atts[$key])) ? 1 : 0;\r\n		}\r\n	}\r\n	if (isset($atts[\'break\']) and $atts[\'break\'] == \'br\') $atts[\'break\'] = \'<br />\';\r\n	return lAtts($arr, $atts, $warn);\r\n}\r\n\r\nfunction mem_form_label2name($label)\r\n{\r\n	$label = trim($label);\r\n	if (strlen($label) == 0) return \'invalid\';\r\n	if (strlen($label) <= 32 and preg_match(\'/^[a-zA-Z][A-Za-z0-9:_-]*$/\', $label)) return $label;\r\n	else return \'q\'.md5($label);\r\n}\r\n\r\nfunction mem_form_store($name, $label, $value)\r\n{\r\n	global $mem_form, $mem_form_labels, $mem_form_values;\r\n\r\n	$mem_form[$label] = $value;\r\n	$mem_form_labels[$name] = $label;\r\n	$mem_form_values[$name] = $value;\r\n\r\n	$is_valid = false !== callback_event(\'mem_form.store_value\', $name);\r\n\r\n	// invalid data, unstore it\r\n	if (!$is_valid)\r\n		mem_form_remove($name);\r\n\r\n	return $is_valid;\r\n}\r\n\r\nfunction mem_form_remove($name)\r\n{\r\n	global $mem_form, $mem_form_labels, $mem_form_values;\r\n\r\n	$label = $mem_form_labels[$name];\r\n	unset($mem_form_labels[$name], $mem_form[$label], $mem_form_values[$name]);\r\n}\r\n\r\nfunction mem_form_display_error()\r\n{\r\n	global $mem_form_error;\r\n\r\n	$out = n.\'<div class=\"alert alert-info alert-block\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\">×</button>\'; \r\n\r\n	foreach (array_unique($mem_form_error) as $error)\r\n	{\r\n		$out .= n.t.\'\'.$error.\'\';\r\n	}\r\n\r\n	$out .= n.\'</div>\';\r\n\r\n	return $out;\r\n}\r\n\r\nfunction mem_form_value($atts, $thing)\r\n{\r\n	global $mem_form_submit, $mem_form_values, $mem_form_default;\r\n\r\n	extract(mem_form_lAtts(array(\r\n		\'name\'		=> \'\',\r\n		\'wraptag\'	=> \'\',\r\n		\'class\'		=> \'\',\r\n		\'attributes\'=> \'\',\r\n		\'id\'		=> \'\',\r\n	), $atts));\r\n\r\n	$out = \'\';\r\n\r\n	if ($mem_form_submit)\r\n	{\r\n		if (isset($mem_form_values[$name]))\r\n			$out = $mem_form_values[$name];\r\n	}\r\n	else {\r\n		if (isset($mem_form_default[$name]))\r\n			$out = $mem_form_default[$name];\r\n	}\r\n\r\n	return doTag($out, $wraptag, $class, $attributes, $id);\r\n}\r\n\r\nfunction mem_form_error($err=NULL)\r\n{\r\n	global $mem_form_error;\r\n\r\n	if (!is_array($mem_form_error))\r\n		$mem_form_error = array();\r\n\r\n	if ($err == NULL)\r\n		return !empty($mem_form_error) ? $mem_form_error : false;\r\n\r\n	$mem_form_error[] = $err;\r\n}\r\n\r\nfunction mem_form_default($key,$val=NULL)\r\n{\r\n	global $mem_form_default;\r\n\r\n	if (is_array($key))\r\n	{\r\n		foreach ($key as $k=>$v)\r\n		{\r\n			mem_form_default($k,$v);\r\n		}\r\n		return;\r\n	}\r\n\r\n	$name = mem_form_label2name($key);\r\n\r\n	if ($val == NULL)\r\n	{\r\n		return (isset($mem_form_default[$name]) ? $mem_form_default[$name] : false);\r\n	}\r\n\r\n	$mem_form_default[$name] = $val;\r\n\r\n	return $val;\r\n}\r\n\r\n\r\n\r\nfunction mem_form_mail($from,$reply,$to,$subject,$msg, $content_type=\'text/plain\')\r\n{\r\n	global $prefs;\r\n\r\n	if (!is_callable(\'mail\'))\r\n		return false;\r\n\r\n	$to = mem_form_strip($to);\r\n	$from = mem_form_strip($from);\r\n	$reply = mem_form_strip($reply);\r\n	$subject = mem_form_strip($subject);\r\n	$msg = mem_form_strip($msg,FALSE);\r\n\r\n	if ($prefs[\'override_emailcharset\'] and is_callable(\'utf8_decode\')) {\r\n		$charset = \'ISO-8859-1\';\r\n		$subject = utf8_decode($subject);\r\n		$msg     = utf8_decode($msg);\r\n	}\r\n	else {\r\n		$charset = \'UTF-8\';\r\n	}\r\n\r\n	$subject = mem_form_mailheader($subject,\'text\');\r\n\r\n	$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\r\n\r\n	$headers = \'From: \'.$from.\r\n		($reply ? ($sep.\'Reply-To: \'.$reply) : \'\').\r\n		$sep.\'X-Mailer: Textpattern (mem_form)\'.\r\n		$sep.\'X-Originating-IP: \'.mem_form_strip((!empty($_SERVER[\'HTTP_X_FORWARDED_FOR\']) ? $_SERVER[\'HTTP_X_FORWARDED_FOR\'].\' via \' : \'\').$_SERVER[\'REMOTE_ADDR\']).\r\n		$sep.\'Content-Transfer-Encoding: 8bit\'.\r\n		$sep.\'Content-Type: \'.$content_type.\'; charset=\"\'.$charset.\'\"\';\r\n\r\n	return mail($to, $subject, $msg, $headers);\r\n}\r\n\r\nfunction mem_form_mailheader($string, $type)\r\n{\r\n	global $prefs;\r\n\r\n	if (!strstr($string,\'=?\') and !preg_match(\'/[\\x00-\\x1F\\x7F-\\xFF]/\', $string)) {\r\n		if (\"phrase\" == $type) {\r\n			if (preg_match(\'/[][()<>@,;:\".\\x5C]/\', $string)) {\r\n				$string = \'\"\'. strtr($string, array(\"\\\\\" => \"\\\\\\\\\", \'\"\' => \'\\\"\')) . \'\"\';\r\n			}\r\n		}\r\n		elseif (\"text\" != $type) {\r\n			trigger_error(\'Unknown encode_mailheader type\', E_USER_WARNING);\r\n		}\r\n		return $string;\r\n	}\r\n	if ($prefs[\'override_emailcharset\']) {\r\n		$start = \'=?ISO-8859-1?B?\';\r\n		$pcre  = \'/.{1,42}/s\';\r\n	}\r\n	else {\r\n		$start = \'=?UTF-8?B?\';\r\n		$pcre  = \'/.{1,45}(?=[\\x00-\\x7F\\xC0-\\xFF]|$)/s\';\r\n	}\r\n	$end = \'?=\';\r\n	$sep = is_windows() ? \"\\r\\n\" : \"\\n\";\r\n	preg_match_all($pcre, $string, $matches);\r\n	return $start . join($end.$sep.\' \'.$start, array_map(\'base64_encode\',$matches[0])) . $end;\r\n}\r\n\r\nfunction mem_form_strip($str, $header = TRUE) {\r\n	if ($header) $str = strip_rn($str);\r\n	return preg_replace(\'/[\\x00]/\', \' \', $str);\r\n}\r\n\r\n///////////////////////////////////////////////\r\n// Spam Evaluator\r\nclass mem_form_evaluation\r\n{\r\n	var $status;\r\n\r\n	function mem_form_evaluation() {\r\n		$this->status = 0;\r\n	}\r\n\r\n	function add_status($rating=-1) {\r\n		$this->status += $rating;\r\n	}\r\n\r\n	function get_status() {\r\n		return $this->status;\r\n	}\r\n\r\n	function is_spam() {\r\n		return ($this->status < 0);\r\n	}\r\n}\r\n\r\nfunction &get_mem_form_evaluator()\r\n{\r\n	static $instance;\r\n\r\n	if(!isset($instance)) {\r\n		$instance = new mem_form_evaluation();\r\n	}\r\n	return $instance;\r\n}', '2f8808385cf9d9b389458e244df3aefa', '', '', 2, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('mem_self_register', 0, 'Michael Manfre', 'http://manfre.net/', '0.9.3', 'User self registration. Read the help to install.', '<h1>User Self Registration</h1>\n\n<h2>Summary</h2>\n\n<p>This plugin is designed to enable community driven sites. Users will be able to register themselves for an account to access the system. This plugin is compatible with the ign_user_password plugin alternate user table. This plugin provides user side tags for generating an html form where users can edit their account information.</p>\n\n<h2>Author Contact</h2>\n\n<p><a href=\"mailto:mmanfre@gmail.com?subject=Textpattern%20mem_self_register%20plugin\" rel=\"nofollow\">Michael Manfre</a><br />\n<a href=\"http://manfre.net\" rel=\"nofollow\">http://manfre.net</a></p>\n\n<h2>License</h2>\n\n<p>This plugin is licensed under the <a href=\"http://www.fsf.org/licensing/licenses/info/GPLv2.html\" rel=\"nofollow\">GPLv2</a>.</p>\n\n<h2>Installation</h2>\n\n<p>This plugin requires the plugin mem_form <a href=\"./index.php?event=plugin&amp;step=plugin_help&amp;name=mem_form\" rel=\"nofollow\">(help)</a>.</p>\n\n<p><a href=\"./index.php?event=self-reg&amp;step=preinstall&amp;area=admin\" rel=\"nofollow\">Start Install Wizard</a></p>\n\n\n<h2>Tags</h2>\n\n<ul>\n	<li><a href=\"#mem_self_register_form\" rel=\"nofollow\">mem_self_register_form</a></li>\n	<li><a href=\"#mem_change_password_form\" rel=\"nofollow\">mem_change_password_form</a></li>\n	<li><a href=\"#mem_self_user_edit_form\" rel=\"nofollow\">mem_self_user_edit_form</a></li>\n	<li><a href=\"#mem_profile\" rel=\"nofollow\">mem_profile</a></li>\n	<li><a href=\"#self_register_email_message\" rel=\"nofollow\">self_register_email_message</a></li>\n	<li><a href=\"#self_register_status_message\" rel=\"nofollow\">self_register_status_message</a></li>\n	<li><a href=\"#if_self_registered\" rel=\"nofollow\">if_self_registered</a></li>\n	<li><a href=\"#mem_self_user_count\" rel=\"nofollow\">mem_self_user_count</a></li>\n</ul>\n\n<h3>mem_self_register_form</h3>\n\n<p>This will output an <span class=\"caps\">HTML</span> form that will allow a user to register themselves with an account.</p>\n\n<ul>\n	<li><span>form</span> <span>string</span> Specifies which form contains the <span class=\"caps\">HTML</span> form template. Default is “self_register_form”.</li>\n	<li><span>email_form</span> <span>string</span> Specifies which form contains the email message template that will be sent to a user upon registering. Default is “self_register_email”.</li>\n	<li><span>from</span> <span>string</span> Registration email&#8217;s From address. Defaults to mem_self_admin_email preference.</li>\n	<li><span>reply</span> <span>string</span> Registration email&#8217;s Reply To address.</li>\n	<li><span>subject</span> <span>string</span> Subject for email. Default is “[SITENAME] Your Login Info”</li>\n	<li><span>login_url</span> <span>string</span> <span class=\"caps\">URL</span> to the login page. Default is Texpattern admin interface.</li>\n</ul>\n\n<p>Starter Template:</p>\n\n<p>&lt;code&gt;\n	&lt;fieldset&gt;\n	&lt;legend&gt;Register&lt;/legend&gt;\n		&lt;txp:mem_form_text name=“RealName” label=“Full Name” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_text name=“name” label=“Username” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_email name=“email” label=“E-Mail” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_submit /&gt;\n	&lt;/fieldset&gt;<br />\n&lt;/code&gt;</p>\n\n<p>Template tags that can be used within the email template form. In addition to the tags listed below, any other Textpattern tag can be used.</p>\n\n<ul>\n	<li>&lt;code&gt;&lt;txp:mem_name /&gt;&lt;/code&gt; &#8212; The name submitted by the user.</li>\n	<li>&lt;code&gt;&lt;txp:mem_username /&gt;&lt;/code&gt; &#8212; The username submitted by the user.</li>\n	<li>&lt;code&gt;&lt;txp:mem_email /&gt;&lt;/code&gt; &#8212; The email address submitted by the user.</li>\n	<li>&lt;code&gt;&lt;txp:mem_password /&gt;&lt;/code&gt; &#8212; The password generated for the user.</li>\n	<li>&lt;code&gt;&lt;txp:mem_sitename /&gt;&lt;/code&gt; &#8212; The site name as specified in the Site Configuration (prefs tab).</li>\n	<li>&lt;code&gt;&lt;txp:mem_siteurl /&gt;&lt;/code&gt; &#8212; The url to the front page of this site.</li>\n	<li>&lt;code&gt;&lt;txp:mem_loginurl /&gt;&lt;/code&gt; &#8212; The url to the Textpattern log in page.</li>\n	<li>&lt;code&gt;&lt;txp:mem_admin_name /&gt;&lt;/code&gt; &#8212; The name of the administrative contact. This will automatically be in the email from field.</li>\n	<li>&lt;code&gt;&lt;txp:mem_admin_email /&gt;&lt;/code&gt; &#8212; The email address of the administrative contact. This will automatically be in the email from field.</li>\n</ul>\n\n<p>Starter Template:</p>\n\n<p>&lt;code&gt;<br />\nDear &lt;txp:mem_name /&gt;,\n  Thank you for registering for &lt;txp:mem_sitename /&gt;. Below are you user account details.</p>\n\n<p>Username: &lt;txp:mem_username /&gt;<br />\nPassword: &lt;txp:mem_password /&gt;<br />\nSite <span class=\"caps\">URL</span>: &lt;txp:mem_siteurl /&gt;<br />\nLogin <span class=\"caps\">URL</span>: &lt;txp:mem_loginurl /&gt;</p>\n\n<p>Sincerely,<br />\n&lt;txp:mem_admin_name /&gt;<br />\nEmail: &lt;txp:mem_admin_email /&gt;<br />\n&lt;/code&gt;</p>\n\n\n<p>Example:</p>\n\n<p>==&lt;code&gt;<br />\n&lt;txp:mem_self_register_form from=“admin@mysite.com” subject=”[MySite] Your account info” thanks_form=“registered_form”&gt;\n	&lt;fieldset&gt;\n	&lt;legend&gt;Register&lt;/legend&gt;\n		&lt;txp:mem_form_text name=“RealName” label=“Full Name” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_text name=“name” label=“Username” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_email name=“email” label=“E-Mail” /&gt;&lt;br /&gt;\n		&lt;br /&gt;</p>\n\n<p>		&lt;txp:mem_form_submit /&gt;\n	&lt;/fieldset&gt;<br />\n&lt;/txp:mem_self_register_form&gt;<br />\n&lt;/code&gt;==</p>\n\n<p>&lt;hr /&gt;</p>\n\n\n<h3>if_self_registered</h3>\n\n<p>This will output the contents of the tag if the user has already been registered. To work properly, this requires browser cookies after the initial form submission. This tag supports &lt;code&gt;&lt;txp:else /&gt;&lt;/code&gt;</p>\n\n<p>Example:</p>\n\n<p>&lt;code&gt;\n&lt;txp:if_self_registered&gt;\nYou already have an account.\n&lt;txp:else /&gt;\n...registration form...\n&lt;/txp:if_self_registered&gt;\n&lt;/code&gt;</p>\n\n\n<h3>mem_self_change_password_form</h3>\n\n<p>This tag allows a change password form to be displayed on the user portion of the website. This is meant to be used with the ign_password_protect plugin. This tag supports the tag txp:mem_profile (see below).</p>\n\n<ul>\n	<li><span>form</span> <span>string</span> Specifies which form contains the <span class=\"caps\">HTML</span> form template.</li>\n	<li><span>email_form</span> <span>string</span> Specifies which form contains the email message template that will be sent to a user upon registering.</li>\n	<li><span>from</span> <span>string</span> Registration email&#8217;s From address. Defaults to mem_self_admin_email preference.</li>\n	<li><span>reply</span> <span>string</span> Registration email&#8217;s Reply To address.</li>\n	<li><span>subject</span> <span>string</span> Subject for email. Default is “[SITENAME] Password Changed”</li>\n</ul>\n\n\n<h3>mem_self_user_edit_form</h3>\n\n<p>This tag allows a form to modify user information to be displayed on the user portion of the website. This is meant to be used with the ign_password_protect plugin. This tag supports the tag txp:mem_profile.</p>\n\n\n<h3>mem_profile</h3>\n\n<p>This tag will output the values of the user&#8217;s profile.</p>\n\n<p>Tag Attributes:</p>\n\n<ul>\n	<li><strong>var</strong> &#8212; Specifies the profile value name to output. Supported values are “user_id”, “user”, “RealName”, “email”, and any other db field.</li>\n	<li><strong>form</strong> &#8212; A form containing other tags to parse.</li>\n	<li><strong>userid</strong> &#8212; If specified, the profile information for the given user_id will be fetched.</li>\n	<li><strong>user</strong> &#8212; If specified, the profile information for the user with the given name will be fetched.</li>\n</ul>\n\n<p>Example for non logged in user<br />\n&lt;code&gt;&lt;txp:mem_profile user=“jdoe”&gt;\n	The email address for &lt;txp:mem_profile var=“RealName” /&gt; is &lt;txp:mem_profile var=“email” /&gt;.\n	&lt;txp:else /&gt;\n		I&#8217;m sorry, but we do not have a record for this user.<br />\n&lt;/txp:mem_profile&gt;&lt;/code&gt;</p>\n\n<p>Example for logged in user<br />\n&lt;code&gt;Welcome back &lt;txp:mem_profile var=“RealName” /&gt;&lt;/code&gt;</p>\n\n<h3>mem_self_password_reset_form</h3>\n\n<p>This tag will allow a user to request a new password to be sent to their email address.</p>\n\n<p>Starter Template<br />\n&lt;code&gt;<br />\n&lt;txp:mem_self_password_reset_form form_mail=“reset_password_form”&gt;\n	&lt;txp:mem_form_text name=“name” label=“Username:” /&gt;\n	&lt;br /&gt;\n	&lt;txp:mem_form_text name=“email” label=“Email Address:” /&gt;\n	&lt;br /&gt;\n	&lt;txp:mem_form_submit name=“submit” label=“Submit” /&gt;<br />\n&lt;/txp:mem_self_password_reset_form&gt;<br />\n&lt;/code&gt;</p>\n\n<h3>mem_self_user_count</h3>\n\n<p>Returns the number of users.</p>\n\n<ul>\n	<li><span>user_levels</span> <span>string</span> Comma separated list of user levels that should be included in the count. Default is all “0,1,2,3,4,5,6”</li>\n	<li><span>wraptag</span> <span>string</span> <span class=\"caps\">HTML</span> tag to wrap around the result.</li>\n	<li><span>class</span> <span>string</span> <span class=\"caps\">CSS</span> class name for wraptag.</li>\n</ul>', '\n////////////////////////////////////////////////////////////\n// Plugin mem_self_register\n// Author: Michael Manfre (http://manfre.net/)\n////////////////////////////////////////////////////////////\nrequire_plugin(\'mem_form\');\n\n// MLP\nglobal $mem_self_lang;\nif (!is_array($mem_self_lang))\n{\n	$mem_self_lang = array(\n		\'account_created_mail_failed\'	=>	\'Your account has been created, but an error was encountered while attempting to email your the account information. Please contact the site administrator for help.\',\n		\'admin_name\'		=>	\'Admin Name\',\n		\'admin_email\'		=>	\'Admin Email\',\n		\'error_adding_new_author\'	=>	\'Error adding new author\',\n		\'greeting\'			=>	\'Hello {name}\',\n		\'invalid_form_tags\' =>	\'Invalid form tags provided to form \"{form}\"\',\n		\'log_in_at\'			=>	\'Log in at {url}\',\n		\'log_added_pref\'	=>	\'Added pref {name}\',\n		\'log_pref_failed\'	=>	\'Failed to add pref {name}. {error}\',\n		\'log_pref_exists\'	=>	\'Pref {name} is already installed. Current value is \"{value}\"\',\n		\'log_col_added\'		=>	\'Added column {name} to user table {table}\',\n		\'log_col_failed\'	=>	\'Failed to add column {name} to table {table}. {error}\',\n		\'log_col_exists\'	=>	\'Table {table} already has column {name}\',\n		\'log_form_added\'	=>	\'Added form {name}\',\n		\'log_form_failed\'	=>	\'Failed to add form {name}. {error}<br>You need to manually create a form template. Here is an example.\',\n		\'log_form_found\'	=>	\'Found form {name}. Skipping installation of default form.\',\n		\'log_xmpl_tag\'		=>	\'Example tag to use in your page template.\',\n		\'mail_sorry\'		=>	\'Our mail system is currently down. Please try again later.\',\n		\'missing_form_field\'	=>	\'The required form field {name} is empty or missing.\',\n		\'password_changed\'	=>	\'Password changed\',\n		\'password_change_failed\'	=>	\'Failed to change password\',\n		\'password_invalid\'	=> \'Invalid password\',\n		\'password_sent_to\'	=>	\'Password sent to {email}\',\n		\'saved_user_profile\'	=>	\'Saved User Profile\',\n		\'user_exists\'		=>	\'Username already exists. Please try another name\',\n		\'user_not_found\'	=>	\'A user account could not be found with the provided information.\',\n		\'your_login_info\'	=>	\'Your Login Info\',\n		\'your_new_password\'	=>	\'Your new password\',\n		\'your_password_is\'	=>	\'Your password is {password}\',\n	);\n}\n\ndefine( \'MEM_SELF_PREFIX\' , \'mem_self\' );\n\nregister_callback( \'mem_self_enumerate_strings\' , \'l10n.enumerate_strings\' );\nfunction mem_self_enumerate_strings($event , $step=\'\' , $pre=0)\n{\n	global $mem_self_lang;\n	$r = array	(\n				\'owner\'		=> \'mem_self_register\',			#	Change to your plugin\'s name\n				\'prefix\'	=> MEM_SELF_PREFIX,				#	Its unique string prefix\n				\'lang\'		=> \'en-gb\',						#	The language of the initial strings.\n				\'event\'		=> \'public\',					#	public/admin/common = which interface the strings will be loaded into\n				\'strings\'	=> $mem_self_lang,				#	The strings themselves.\n				);\n	return $r;\n}\nfunction mem_self_gTxt($what,$args = array())\n{\n	global $mem_self_lang, $textarray;\n\n	$key = strtolower( MEM_SELF_PREFIX . \'-\' . $what );\n\n	if (isset($textarray[$key]))\n	{\n		$str = $textarray[$key];\n	}\n	else\n	{\n		$key = strtolower($what);\n\n		if (isset($mem_self_lang[$key]))\n			$str = $mem_self_lang[$key];\n		elseif (isset($textarray[$key]))\n			$str = $textarray[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n\nglobal $event, $levels;\n\n\nif (txpinterface == \'public\' or $event != \'admin\')\n{\n	if (file_exists( txpath.\'/lib/txplib_admin.php\' ))\n	{\n		require_once txpath.\'/lib/txplib_admin.php\';\n	}\n\n	require_once txpath.\'/include/txp_admin.php\';\n\n	if (empty($levels))\n	{\n		// copied from txp_admin.php\n		$levels = array(\n			0 => mem_self_gTxt(\'none\'),\n			6 => mem_self_gTxt(\'designer\'),\n			5 => mem_self_gTxt(\'freelancer\'),\n			4 => mem_self_gTxt(\'staff_writer\'),\n			3 => mem_self_gTxt(\'copy_editor\'),\n			2 => mem_self_gTxt(\'managing_editor\'),\n			1 => mem_self_gTxt(\'publisher\')\n		);\n	}\n\n//-------------------------------------------------------------\n	if (!function_exists(\'priv_levels\')) {\n		function priv_levels($item, $var) {\n			global $levels;\n\n			return selectInput($item, $levels, $var);\n		}\n	}\n}\n\n\nglobal $mem_self, $prefs;\n\n$mem_self = array(\n				\'admin_email\'		=>	\'\',\n				\'admin_name\'		=>	\'\',\n				\'admin_bcc\'			=>	\'0\',\n				\'new_user_priv\'		=>	\'0\',\n				\'status\'			=>	false,\n				\'status_message\'	=>	\'You are already registered.\',\n				\'email_message\'		=>	\'\'\n			);\n\n$mem_self[\'admin_email\'] = isset($prefs[\'mem_self_admin_email\']) ? $prefs[\'mem_self_admin_email\'] : \'\';\n$mem_self[\'admin_name\'] = isset($prefs[\'mem_self_admin_name\']) ? $prefs[\'mem_self_admin_name\'] : \'\';\n$mem_self[\'new_user_priv\'] = isset($prefs[\'mem_self_new_user_priv\']) ? $prefs[\'mem_self_new_user_priv\'] : \'0\';\n$mem_self[\'admin_bcc\'] = isset($prefs[\'mem_self_admin_bcc\']) ? $prefs[\'mem_self_admin_bcc\'] : \'0\';\n\nif ( @txpinterface == \'admin\' ) {\n	add_privs(\'self-reg\',\'1\');\n\n	register_callback(\'mem_self_register\',\'self-reg\',\'\', 1);\n	if ($event==\'self-reg\') {\n		// fake tabs when using them. Silences warnings from pageTop()\n		register_tab(\'admin\',\'self-reg\',\'self-reg\');\n\n		include_once txpath.\'/publish/taghandlers.php\';\n	}\n\n	function mem_self_register($event, $step)\n	{\n		global $prefs;\n\n		extract($prefs);\n\n		if ($event!=\'self-reg\')\n			return;\n\n		pageTop(\'Self Registration\',\'\');\n\n		if ($step == \'install\') {\n			echo mem_self_register_install();\n		} else if ($step==\'preinstall\') {\n			$mem_xtra_columns	= mem_get_extra_user_columns();\n\n			$mem_admin_name = !empty($mem_admin_name) ? $mem_admin_name : \'Admin\';\n			$mem_admin_email = !empty($mem_admin_email) ? $mem_admin_email : \'Admin@yourdomain.com\';\n			$mem_new_use_priv = !empty($mem_new_use_priv) ? $mem_new_use_priv : \'0\';\n			$mem_self_admin_bcc = !empty($mem_self_admin_bcc) ? $mem_self_admin_bcc : \'0\';\n\n			$use_ign_input = \'\';\n			// is ign_password_protect loaded on the system?\n			if (load_plugin(\'ign_password_protect\') || (isset($mem_use_ign_db) && $mem_use_ign_db)) {\n				$mem_use_ign_db = !empty($mem_use_ign_db) ? $mem_use_ign_db : \'0\';\n				$use_ign_input = yesnoradio(\'use_ign_db\',$mem_use_ign_db);\n			}\n\n			$xtra_columns = mem_get_extra_user_columns();\n			$has_address = in_array(\'address\',$xtra_columns);\n			$has_phone = in_array(\'phone\',$xtra_columns);\n\n			$add_address_input = yesnoradio(\'add_address\',\'0\');\n			$add_phone_input = yesnoradio(\'add_phone\',\'0\');\n\n			echo form(\n				eInput(\'self-reg\').sInput(\'install\').\n				startTable(\'edit\') .\n					tr( fLabelCell(\'admin_name\')	. tda(fInput(\'text\',\'admin_name\',$mem_admin_name,\'edit\')) ) .\n					tr( fLabelCell(\'admin_email\')	. tda(fInput(\'text\',\'admin_email\',$mem_admin_email,\'edit\')) ) .\n					tr( fLabelCell(\'admin_bcc\')	. tda(yesnoRadio(\'admin_bcc\',$mem_self_admin_bcc)) ) .\n					tr( fLabelCell(\'new_user_priv\')	. tda(priv_levels(\'new_user_priv\',$mem_new_use_priv)) ) .\n					tr( fLabelCell(\'use_ign_db\')	. tda($use_ign_input) ) .\n					($has_address ? \'\' : tr( fLabelCell(\'add_address_field\') . tda($add_address_input) ) ) .\n					($has_phone ? \'\' : tr( fLabelCell(\'add_phone_field\') . tda($add_phone_input) ) ) .\n					tr( td() . td( fInput(\"submit\", \'submit\',mem_self_gTxt(\'install\'),\"Publish\"), 2 ) ) .\n				endTable()\n				);\n		} else {\n			echo \'<div><a href=\"?event=self-reg&step=preinstall\">\'.mem_self_gTxt(\'install\').\'</a></div>\';\n		}\n	}\n\n\n	function mem_self_register_install()\n	{\n		global $mem_self;\n\n		extract(doSlash(gpsa(array(\n			\'admin_email\',\n			\'admin_name\',\n			\'new_user_priv\',\n			\'use_ign_db\',\n			\'add_address\',\n			\'add_phone\'\n		))));\n\n		if (!isset($new_user_priv) || empty($new_user_priv)) $new_user_priv = \'0\';\n\n		$log = array();\n\n		if (!($rs=safe_field(\'val,html\',\'txp_prefs\',\"name=\'mem_self_use_ign_db\'\"))) {\n			if ( set_pref(\'mem_self_use_ign_db\',$use_ign_db,\'self_reg\',1,0,\'yesnoradio\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_use_ign_db\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_use_ign_db\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			if ($rs[\'html\'] != \'yesnoradio\') {\n				safe_update(\'txp_prefs\',\"html=\'yesnoradio\'\",\"name=\'mem_self_use_ign_db\'\");\n			}\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_use_ign_db\',\'{value}\'=>$rs));\n		}\n\n		$user_table = mem_get_user_table_name();\n\n		$xtra_columns = mem_get_extra_user_columns();\n		if ($add_address) {\n			if (!in_array(\'address\',$xtra_columns)) {\n				if (safe_alter($user_table,\"ADD `address` VARCHAR( 128 )\")) {\n					$log[] = mem_self_gTxt(\'log_col_added\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table));\n				} else {\n					$log[] = mem_self_gTxt(\'log_col_failed\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table,\'{error}\'=>mysql_error()));\n				}\n			} else {\n				$log[] = mem_self_gTxt(\'log_col_exists\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table));\n			}\n		}\n		if ($add_phone) {\n			if (!in_array(\'phone\',$xtra_columns)) {\n				if (safe_alter($user_table,\"ADD `phone` VARCHAR( 32 )\")) {\n					$log[] = mem_self_gTxt(\'log_col_added\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table));\n				} else {\n					$log[] = mem_self_gTxt(\'log_col_failed\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table,\'{error}\'=>mysql_error()));\n				}\n			} else {\n				$log[] = mem_self_gTxt(\'log_col_exists\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table));\n			}\n		}\n\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_email\'\"))) {\n			if ( set_pref(\'mem_self_admin_email\',$admin_email,\'self_reg\',1)) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_email\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_email\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_email\',\'{value}\'=>$rs));\n		}\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_name\'\"))) {\n			if ( set_pref(\'mem_self_admin_name\',$admin_name,\'self_reg\',1)) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_name\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_name\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_name\',\'{value}\'=>$rs));\n		}\n		if (!($rs=safe_row(\'val,html\',\'txp_prefs\',\"name=\'mem_self_new_user_priv\'\"))) {\n			if ( set_pref(\'mem_self_new_user_priv\',$new_user_priv,\'self_reg\',1,0,\'priv_levels\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_new_user_priv\'));\n				$mem_self[\'new_user_priv\'] = $new_user_priv;\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_newuser_priv\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			safe_update(\'txp_prefs\',\"html=\'priv_levels\'\",\"name=\'mem_self_new_user_priv\'\");\n\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_new_user_priv\',\'{value}\' => $rs));\n		}\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_bcc\'\"))) {\n			if ( set_pref(\'mem_self_admin_bcc\',\'0\',\'self_reg\',1,\'yesnoradio\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_bcc\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_bcc\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_bcc\',\'{value}\'=>$rs));\n		}\n\n		// create default registration form\n		$form_html = <<<EOF\n	<fieldset>\n	<legend>Register</legend>\n		<txp:mem_form_text name=\"RealName\" label=\"Full Name\" /><br />\n		<br />\n\n		<txp:mem_form_text name=\"name\" label=\"Username\" /><br />\n		<br />\n\n		<txp:mem_form_email name=\"email\" label=\"E-Mail\" /><br />\n		<br />\n\n		<txp:mem_form_submit />\n	</fieldset>\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_form\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_form\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_form\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_form\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_form\'));\n		}\n\n		// create default successful registration form to show the user\n		$form_html = <<<EOF\n<h3>Account Created</h3>\n<p>An email containing your password has been sent to <txp:mem_profile var=\"email\" />.</p>\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_success\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_success\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_success\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_success\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_success\'));\n		}\n\n		// create default successful registration email form\n		$form_html = <<<EOF\nDear <txp:mem_name />,\n\nThank you for registering at <txp:mem_siteurl />.\n\nYour login name: <txp:mem_username />\nYour password: <txp:mem_password />\n\nIf you have any questions please reply to this email address.\n\nSincerely,\n<txp:mem_admin_name />\n<txp:mem_admin_email />\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_email\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_email\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_email\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_email\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_email\'));\n		}\n\n		$tag_help = \'<txp:mem_self_register_form form=\"self_register_form\" />\';\n		$log[] = mem_self_gTxt(\'log_xmpl_tag\').br.\n			\'<textarea style=\"width:400px;height:40px;\">\'.htmlspecialchars($tag_help).\'</textarea>\';\n\n		return doWrap($log,\'ul\',\'li\');\n	}\n}\n\n\nregister_callback(\'mem_self_register_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_register_form($atts,$thing=\'\')\n{\n	global $prefs, $sitename, $production_status;\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'email_form\'	=> \'\',\n		\'from\'		=> $prefs[\'mem_self_admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \'[\'.$sitename.\'] \'. mem_self_gTxt(\'your_login_info\'),\n		\'login_url\'	=> rtrim(hu,\'/\').\'/textpattern/index.php\',\n	),$atts,false));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	foreach(array(\'from\',\'reply\',\'subject\',\'login_url\',\'email_form\') as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts+array(\'type\'=>\'mem_self_register\'),$thing);\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_form_submit()\n{\n	global $prefs, $mem_self, $sitename, $mem_profile, $mem_form_type, $mem_form_values, $mem_form_thanks_form;\n\n	if ($mem_form_type != \'mem_self_register\') return;\n\n	extract($mem_self);\n\n	$pw = generate_password(10);\n\n	if (!$mem_profile) $mem_profile = array();\n\n	$mem_profile[\'nonce\'] = $nonce = md5( uniqid( rand(), true ) );\n	$mem_profile[\'RealName\'] = $name = $mem_form_values[\'RealName\'];\n	$mem_profile[\'email\'] = $email = $mem_form_values[\'email\'];\n	$mem_profile[\'name\'] = $username = $mem_form_values[\'name\'];\n	$mem_profile[\'privs\'] = $new_user_priv;\n\n	if (safe_row(\'user_id\', mem_get_user_table_name(), \"name = \'\".doSlash($username).\"\'\")) {\n		return mem_form_error(mem_self_gTxt(\'user_exists\'));\n	}\n\n	$xtra_columns = mem_get_extra_user_columns();\n\n	foreach($xtra_columns as $c) {\n		$c_name = trim( $c[\'Field\'] );\n\n		if (isset($mem_form_values[$c_name]))\n			$mem_profile[$c_name] = $mem_form_values[$c_name];\n	}\n\n	$rs = false;\n\n	$xtra = mem_get_extra_user_columns_insert_string();\n\n	$rs = safe_insert(\n		mem_get_user_table_name(),\n		\"privs    = \'\".doSlash($new_user_priv).\"\',\n		 name     = \'\".doSlash($username).\"\',\n		 email    = \'\".doSlash($email).\"\',\n		 RealName = \'\".doSlash($name).\"\',\n		 pass     =  password(lower(\'\".doSlash($pw).\"\')),\n		 nonce    = \'\".doSlash($nonce).\"\'\" . $xtra\n	);\n\n	if ($rs) {\n		$mem_profile[\'user_id\'] = $rs;\n		$mem_profile[\'last_access\'] = 0;\n\n		$message = @fetch_form($mem_form_values[\'email_form\']);\n\n		if (empty($message)) {\n			$message = <<<EOF\n{RealName},\n	You have successfully registered at {sitename}. You can login at {login_url}.\n\nUsername: {username}\nPassword: {password}\n\nRegards,\n{admin_name}\nEOF;\n		}\n\n		if (!empty($message)) {\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			$vals[\'password\']		= $pw;\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\', $b, $message);\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$mem_form_thanks_form = str_replace(\'<txp:mem_\'.$a.\' />\', $b, $mem_form_thanks_form);\n				$mem_form_thanks_form = str_replace(\'{\'.$a.\'}\', $b, $mem_form_thanks_form);\n			}\n\n			$message = parse($message);\n			$to = $mem_profile[\'email\'];\n			$from = $mem_form_values[\'from\'];\n			$reply = $mem_form_values[\'reply\'];\n			$subject = $mem_form_values[\'subject\'];\n\n			if ($mem_self[\'admin_bcc\'])\n			{\n				$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n				$from .= $sep . \'Bcc: \' . $from;\n			}\n\n			$sent = mem_form_mail($from,$reply,$to,$subject,$message);\n\n			$mem_self[\'email_status\'] = $sent;\n			$mem_self[\'email_message\'] = $message;\n			$mem_self[\'status\'] = true;\n\n			if ($sent) {\n				$cookietime = time() + (365*24*3600);\n				setcookie(\"txp_self_registered\", \"1\",  $cookietime, \"/\");\n\n				$mem_self[\'status_message\'] = mem_self_gTxt(\'password_sent_to\', array(\'email\'=>$email));\n			} else {\n				// failed to send email\n				return mem_form_error( mem_self_gTxt(\'account_created_mail_failed\') );\n			}\n		}\n	} else {\n		return mem_form_error( mem_self_gTxt(\'error_adding_new_author\') );\n	}\n}\n\n\n/** Returns the name of the user table (without PFXS) */\nfunction mem_get_user_table_name() {\n	global $prefs;\n\n	extract($prefs);\n\n	$table_name = \'txp_users\';\n\n	if (isset($mem_self_use_ign_db) && $mem_self_use_ign_db == \'1\') {\n\n		if (isset($ign_use_custom) && $ign_use_custom==\'1\') {\n\n			if (isset($ign_user_db) && !empty($ign_user_db))\n				$table_name = $ign_user_db;\n		}\n	}\n	return $table_name;\n}\n\n/** SQL string builder for non-standard fields */\nfunction mem_get_extra_user_columns_insert_string() {\n	$xtra_columns = mem_get_extra_user_columns();\n\n	$xtra = \'\';\n\n	foreach ($xtra_columns as $xcol) {\n		$name = $xcol[\'Field\'];\n		$type = strtolower($xcol[\'Type\']);\n		$val = gps($name);\n\n		if ( strstr($type,\'int\')\n				|| $type==\'float\'\n				|| $type==\'decimal\'\n				|| $type==\'double\'\n				|| $type==\'bool\' ) {\n			// don\'t quote value\n			if (!intval($val))\n				$val = ( $val==\'yes\' || $val==\'on\' ) ? $val = 1 : $val = 0;\n\n			$xtra .= \", {$name}=\" . ($val == false ? \'0\' : doSlash($val));\n		} else {\n			// quote value\n			$xtra .= \", {$name}=\'\" . doSlash($val) .\"\'\";\n		}\n	}\n\n	return $xtra;\n}\n\n/** Diff user table and return non-standard columns */\nfunction mem_get_extra_user_columns()\n{\n	static $default_columns = array(\'user_id\',\'name\',\'pass\',\'RealName\',\'email\',\'privs\',\'last_access\',\'nonce\');\n	static $xtra_columns = false;\n\n	if (is_array($xtra_columns))\n		return $xtra_columns;\n\n	$table_name = mem_get_user_table_name();\n	$txpdesc = getRows(\'describe \'.PFX. $table_name);\n\n	$xtra_cols = array();\n\n	$dcols = $default_columns;\n\n	foreach($txpdesc as $r) {\n		if ( !in_array($r[\'Field\'], $default_columns) )\n			$xtra_cols[] = $r;\n	}\n\n	return $xtra_cols;\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_email_message($atts)\n{\n	global $mem_self;\n	return $mem_self[\'email_message\'];\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_status_message($atts)\n{\n	global $mem_self;\n	return $mem_self[\'status_message\'];\n}\n\n\n// -------------------------------------------------------------\nfunction mem_if_message_sent($atts,$thing)\n{\n	global $mem_self;\n	$condition = ($mem_self[\'email_status\']);\n	return parse(EvalElse($thing, $condition));\n}\n\n// -------------------------------------------------------------\nfunction mem_if_self_registered($atts,$thing)\n{\n	global $mem_self,$txp_user,$ign_user;\n	$condition = ($mem_self[\'status\'] or !empty($_COOKIE[\'txp_self_registered\']) or !empty($txp_user) or !empty($ign_user) );\n	return parse(EvalElse($thing, $condition));\n}\n\n\n////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////\n// User Side Profile and Password Change Forms\n// -------------------------------------------------------------\nif (txpinterface != \'admin\' and !function_exists(\'txp_validate\')) {\n	require_once txpath.\'/include/txp_auth.php\';\n}\n\nfunction mem_self_password_reset_form($atts,$thing=\'\')\n{\n	global $mem_self, $sitename, $production_status;\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'form_mail\'	=> \'\',\n		\'from\'		=> $mem_self[\'admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \"[$sitename] \".mem_self_gTxt(\'password_reset_confirmation_request\'),\n		\'confirm_url\'	=> \'\',\n		\'check_name\'	=> 1,\n		\'check_email\'	=> 1\n	),$atts,false));\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n\n	if (gps(\'mem_self_confirm\'))\n	{\n		sleep(3);\n\n		$confirm = pack(\'H*\', gps(\'mem_self_confirm\'));\n		$name    = substr($confirm, 5);\n		$nonce   = safe_field(\'nonce\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\n\n		if ($nonce and $confirm === pack(\'H*\', substr(md5($nonce), 0, 10)).$name)\n		{\n			$email = safe_field(\'email\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\n			$new_pass = doSlash(generate_password(10));\n\n			$rs = safe_update(\'txp_users\', \"pass = password(lower(\'$new_pass\'))\", \"name = \'\".doSlash($name).\"\'\");\n\n			if ($rs)\n			{\n				if (send_new_password($new_pass, $email, $name))\n					return mem_self_gTxt(\'password_sent_to\', array(\'{email}\'=>$email));\n				else\n					return mem_self_gTxt(\'mail_sorry\');\n			}\n			else\n				return mem_self_gTxt(\'password_change_failed\');\n		}\n	}\n\n	if (!$check_name and !$check_email)\n		return mem_self_gTxt(\'invalid_form_tags\',array(\'{form}\'=>\'mem_self_password_reset_form\'));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	$secrets = array(\'form_mail\',\'from\',\'reply\',\'subject\',\'confirm_url\', \'check_name\', \'check_email\');\n\n	foreach($secrets as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_password_reset\'),$thing);\n}\n\nregister_callback(\'mem_self_password_reset_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_password_reset_form_submit()\n{\n	global $mem_form_type, $mem_form_values, $mem_profile, $prefs, $sitename;\n\n	if ($mem_form_type != \'mem_self_password_reset\')\n		return;\n\n	$check_name = $mem_form_values[\'check_name\'];\n	$check_email = $mem_form_values[\'check_email\'];\n\n	$where = array();\n\n	if ($check_name) {\n		foreach(array(\'name\',\'p_userid\',\'username\') as $n)\n		{\n			if (isset($mem_form_values[$n])) {\n				$name = $mem_form_values[$n];\n				break;\n			}\n		}\n\n		if (!isset($name))\n			return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'name\'));\n\n		$where[] = \"name = \'\".doSlash($name).\"\'\";\n	}\n\n	if ($check_email) {\n		$email = @$mem_form_values[\'email\'];\n\n		if (empty($email))\n			return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'email\'));\n\n		$where[] = \"email = \'\".doSlash($email).\"\'\";\n	}\n\n	if (empty($where))\n		return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'name\'));\n\n	$rs = safe_row(\'name, email, nonce, RealName\', mem_get_user_table_name(), join(\' and \',$where));\n\n	if ($rs)\n	{\n		$url = @$mem_form_values[\'confirm_url\'];\n		$url = empty($url) ? hu.\'textpattern/index.php\' : hu.ltrim($url,\'/\');\n		$url .= (strstr($url, \'?\')===false) ? \'?\' : \'&\';\n\n		extract($rs);\n\n		$confirm = bin2hex(pack(\'H*\', substr(md5($nonce), 0, 10)).$name);\n\n		$message = fetch_form($mem_form_values[\'form_mail\']);\n\n		if (empty($message)) {\n			$msg = mem_self_gTxt(\'greeting\').\' \'.$name.\',\'.\n					n.n.mem_self_gTxt(\'password_reset_confirmation\').\': \'.\n					n. $url . \'mem_self_confirm=\'.$confirm;\n		}\n		else {\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			//$vals[\'password\']		= $pw;\n			$vals[\'confirm_url\'] = $url;\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\',$b,$message);\n			}\n\n			$msg = parse($message);\n		}\n\n		$to = $email;\n		$from = $mem_form_values[\'from\'];\n		$reply = $mem_form_values[\'reply\'];\n		$subject = $mem_form_values[\'subject\'];\n\n		if (mem_form_mail($from,$reply,$to,$subject,$msg))\n			return mem_self_gTxt(\'password_reset_confirmation_request_sent\');\n		else\n			return mem_self_gTxt(\'mail_sorry\');\n	}\n	else\n		return mem_self_gTxt(\'user_not_found\');\n}\n\n\nfunction mem_self_change_password_form($atts,$thing=\'\')\n{\n	global $mem_self, $sitename, $production_status;\n\n	header(\'Cache-Control: no-cache\');\n	header(\'Pragma: no-cache\');\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'email_form\'	=> \'\',\n		\'from\'		=> $mem_self[\'admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \'[\'.$sitename.\'] \'. mem_self_gTxt(\'password_changed\'),\n	),$atts,false));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	foreach(array(\'from\',\'reply\',\'subject\',\'email_form\') as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_password\'),$thing);\n}\n\nregister_callback(\'mem_self_password_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_password_form_submit()\n{\n	global $prefs, $txp_user, $ign_user, $mem_form_type, $mem_form_values, $mem_form_thanks_form, $mem_self;\n\n	if ($mem_form_type != \'mem_self_password\')\n		return;\n\n	$verify_old = array_key_exists(\'old_password\', $mem_form_values);\n	$confirm = array_key_exists(\'password_confrim\', $mem_form_values);\n\n	$new_pass = $mem_form_values[\'password\'];\n	$old_pass = $mem_form_values[\'old_password\'];\n\n	$user = isset($ign_user) ? $ign_user : $txp_user;\n\n	$where = \"name = \'\".doSlash($user).\"\'\";\n\n	$is_valid = $verify_old ? txp_validate($user,$old_pass) : true;\n\n	if (!$is_valid) {\n		return mem_form_error(mem_self_gTxt(\'password_invalid\'));\n	}\n\n	if ($confirm and ($new_pass != $mem_form_values[\'password_confirm\'])) {\n		return mem_form_error(mem_self_gTxt(\'password_mismatch\'));\n	}\n\n	$rs = safe_update( mem_get_user_table_name(), \"pass = password(lower(\'$new_pass\'))\", $where);\n\n	if (!$rs) {\n		return mem_form_error(mem_self_gTxt(\'password_change_failed\'));\n	}\n\n	// successful\n	$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'{$user}\'\");\n\n	if ($mem_profile)\n	{\n		$mem_profile[\'new_pass\'] = $new_pass;\n\n		$message = @fetch_form($mem_form_values[\'email_form\']);\n\n		if (!empty($message))\n		{\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			$vals[\'password\']		= $mem_profile[\'new_pass\'];\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\',$b,$message);\n			}\n\n		}\n		else {\n			$message = mem_self_gTxt(\'greeting\', array(\'{name}\'=>$mem_form_values[\'RealName\'])).\"\\r\\n\".\n				mem_self_gTxt(\'your_password_is\', array(\'{password}\'=>$new_pass)).\"\\r\\n\".\n				mem_self_gTxt(\'log_in_at\', array(\'{url}\'=> $mem_form_values[\'login_url\']));\n		}\n\n		$msg = parse($message);\n\n		$to = $mem_profile[\'email\'];\n		$from = $mem_form_values[\'from\'];\n		$reply = $mem_form_values[\'reply\'];\n		$subject = $mem_form_values[\'subject\'];\n\n		if (mem_form_mail($from,$reply,$to,$subject,$msg))\n			return mem_self_gTxt(\'password_changed\');\n		else\n			return mem_self_gTxt(\'password_changed_mail_failed\');\n	}\n	else {\n		// no email, fail silently\n	}\n\n}\n\nregister_callback(\'mem_self_user_edit_submit\',\'mem_form.submit\');\nregister_callback(\'mem_self_register_defaults\',\'mem_form.defaults\');\n\nfunction mem_self_user_edit_form($atts,$thing=\'\')\n{\n	header(\'Cache-Control: no-cache\');\n	header(\'Pragma: no-cache\');\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_user_edit\'),$thing);\n}\n\nfunction mem_self_register_defaults()\n{\n	global $txp_user, $ign_user, $mem_form_type, $mem_profile;\n\n	if ($mem_form_type != \'mem_self_user_edit\') return;\n\n	$user = isset($ign_user) ? $ign_user : $txp_user;\n\n\n	$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'{$user}\'\");\n\n	if ($mem_profile) {\n		mem_form_default($mem_profile);\n	}\n}\n\nfunction mem_self_user_edit_submit()\n{\n	global $prefs, $txp_user, $ign_user, $mem_form_type, $mem_form_values, $mem_form_thanks_form, $mem_profile;\n\n	if ($mem_form_type != \'mem_self_user_edit\')\n		return;\n\n	if (isset($ign_user)) $txp_user = $ign_user;\n\n	$name = isset($mem_form_values[\'name\']) ? trim($mem_form_values[\'name\']) : \'\';\n\n	$xtra = mem_get_extra_user_columns_insert_string();\n\n	$mem_profile = array();\n\n	$sql = \'\';\n\n	if (!empty($mem_form_values[\'email\']))\n		$sql  = \"email = \'\".doSlash($mem_form_values[\'email\']).\"\'\";\n	if (!empty($mem_form_values[\'RealName\']))\n		$sql .= \", RealName = \'\".doSlash($mem_form_values[\'RealName\']).\"\'\";\n\n	if (!empty($name))\n	{\n		$sql .= \", name = \'\".doSlash($name).\"\'\";\n\n		// need to remove the ign_password_protect cookie\n		setcookie(\'ign_login\', \'\', time()-86400);\n	}\n\n	if (empty($sql))\n		return mem_self_gTxt(\'saved_user_profile_failed\');\n\n	$rs = safe_update( mem_get_user_table_name(),\n				$sql . $xtra,\n				\"name = \'{$txp_user}\'\");\n\n	if ($rs) {\n		return mem_self_gTxt(\'saved_user_profile\');\n	}\n	else {\n		return mem_self_gTxt(\'saved_user_profile_failed\');\n	}\n}\n\n\nfunction mem_profile($atts, $body=\'\')\n{\n	global $mem_profile,$txp_user,$ign_user;\n\n	if (isset($ign_user)) $txp_user = $ign_user;\n\n	extract(lAtts(array(\n		\'user\'		=> \'\',\n		\'userid\'	=> \'\',\n		\'var\'			=> \'RealName\',\n		\'form\'		=> \'\'\n	),$atts));\n\n	if (empty($user) && empty($userid)) {\n		// use the old method\n		if (!is_array($mem_profile) && $txp_user)\n			$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'\". doSlash($txp_user).\"\'\");\n	} else {\n		$mem_profile = (is_array($mem_profile) ? $mem_profile : array());\n\n		// look up a potentially new user\n		if (!empty($user)) {\n			if (!array_key_exists(\'name\', $mem_profile) || strcmp($mem_profile[\'name\'],$user)!=0)\n				$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'\". doSlash($user).\"\'\");\n		}\n\n		if (!empty($userid) && is_numeric($userid)) {\n			if (!array_key_exists(\'user_id\', $mem_profile) || strcmp($mem_profile[\'user_id\'],$userid)!=0)\n				$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"user_id = \". doSlash($userid));\n		}\n	}\n\n	$out = \'\';\n\n	if (empty($form) && empty($body)) {\n		if ($mem_profile)\n			$out = array_key_exists($var,$mem_profile) ? $mem_profile[$var] : \'\';\n	} else {\n		$thing = empty($body) ? fetch_form($form) : $body;\n\n		$out = parse(EvalElse($thing, !empty($mem_profile)));\n	}\n\n	return $out;\n}\n\n\nfunction mem_submit($atts) {\n	extract($atts);\n	if (isset($value)) {\n		$atts[\'label\'] = $value;\n		unset($atts[\'value\']);\n	}\n	$atts[\'name\'] = \'save\';\n	return mem_form_submit($atts);\n}\nfunction mem_password_input($atts) {\n	global $mem_profile;\n	$atts[\'password\'] = 1;\n	return mem_form_text( mem_self_map_tag($atts,\'new_pass\',\'\') );\n}\nfunction mem_realname_input($atts) {\n	global $mem_profile;\n	return mem_form_text( mem_self_map_tag($atts,\'RealName\',$mem_profile[\'RealName\']) );\n}\nfunction mem_email_input($atts) {\n	global $mem_profile;\n	return mem_form_email( mem_self_map_tag($atts,\'email\',$mem_profile[\'email\']) );\n}\nfunction mem_phone_input($atts) {\n	global $mem_profile;\n	return mem_form_text( mem_self_map_tag($atts,\'phone\',$mem_profile[\'phone\']) );\n}\nfunction mem_address_input($atts) {\n	global $mem_profile;\n	return mem_form_textarea( mem_self_map_tag($atts,\'address\', $mem_profile[\'address\']) );\n}\nfunction mem_self_map_tag($atts,$name,$default) {\n	$atts[\'name\'] = $name;\n	if (!empty($default))\n		$atts[\'default\'] = $default;\n	return $atts;\n}\n\nfunction mem_self_user_count($atts)\n{\n	global $mem_self;\n\n	extract(lAtts(array(\n		\'user_levels\'	=> \'0,1,2,3,4,5,6\',\n		\'wraptag\'	=> \'\',\n		\'class\'		=> \'\'\n	),$atts));\n\n	if (!empty($user_levels) || $user_levels==\'0\')\n		$user_levels = doSlash(split(\',\',$user_levels));\n	else\n		$user_levels = array($mem_self[\'new_user_priv\']);\n\n	$levels = join(\',\',$user_levels);\n	$count = safe_field(\'COUNT(*)\', mem_get_user_table_name(), \"privs IN ({$levels})\");\n\n	return doTag($count,$wraptag,$class);\n}\n\n\n\n', '\n////////////////////////////////////////////////////////////\n// Plugin mem_self_register\n// Author: Michael Manfre (http://manfre.net/)\n////////////////////////////////////////////////////////////\nrequire_plugin(\'mem_form\');\n\n// MLP\nglobal $mem_self_lang;\nif (!is_array($mem_self_lang))\n{\n	$mem_self_lang = array(\n		\'account_created_mail_failed\'	=>	\'Your account has been created, but an error was encountered while attempting to email your the account information. Please contact the site administrator for help.\',\n		\'admin_name\'		=>	\'Admin Name\',\n		\'admin_email\'		=>	\'Admin Email\',\n		\'error_adding_new_author\'	=>	\'Error adding new author\',\n		\'greeting\'			=>	\'Hello {name}\',\n		\'invalid_form_tags\' =>	\'Invalid form tags provided to form \"{form}\"\',\n		\'log_in_at\'			=>	\'Log in at {url}\',\n		\'log_added_pref\'	=>	\'Added pref {name}\',\n		\'log_pref_failed\'	=>	\'Failed to add pref {name}. {error}\',\n		\'log_pref_exists\'	=>	\'Pref {name} is already installed. Current value is \"{value}\"\',\n		\'log_col_added\'		=>	\'Added column {name} to user table {table}\',\n		\'log_col_failed\'	=>	\'Failed to add column {name} to table {table}. {error}\',\n		\'log_col_exists\'	=>	\'Table {table} already has column {name}\',\n		\'log_form_added\'	=>	\'Added form {name}\',\n		\'log_form_failed\'	=>	\'Failed to add form {name}. {error}<br>You need to manually create a form template. Here is an example.\',\n		\'log_form_found\'	=>	\'Found form {name}. Skipping installation of default form.\',\n		\'log_xmpl_tag\'		=>	\'Example tag to use in your page template.\',\n		\'mail_sorry\'		=>	\'Our mail system is currently down. Please try again later.\',\n		\'missing_form_field\'	=>	\'The required form field {name} is empty or missing.\',\n		\'password_changed\'	=>	\'Password changed\',\n		\'password_change_failed\'	=>	\'Failed to change password\',\n		\'password_invalid\'	=> \'Invalid password\',\n		\'password_sent_to\'	=>	\'Password sent to {email}\',\n		\'saved_user_profile\'	=>	\'Saved User Profile\',\n		\'user_exists\'		=>	\'Username already exists. Please try another name\',\n		\'user_not_found\'	=>	\'A user account could not be found with the provided information.\',\n		\'your_login_info\'	=>	\'Your Login Info\',\n		\'your_new_password\'	=>	\'Your new password\',\n		\'your_password_is\'	=>	\'Your password is {password}\',\n	);\n}\n\ndefine( \'MEM_SELF_PREFIX\' , \'mem_self\' );\n\nregister_callback( \'mem_self_enumerate_strings\' , \'l10n.enumerate_strings\' );\nfunction mem_self_enumerate_strings($event , $step=\'\' , $pre=0)\n{\n	global $mem_self_lang;\n	$r = array	(\n				\'owner\'		=> \'mem_self_register\',			#	Change to your plugin\'s name\n				\'prefix\'	=> MEM_SELF_PREFIX,				#	Its unique string prefix\n				\'lang\'		=> \'en-gb\',						#	The language of the initial strings.\n				\'event\'		=> \'public\',					#	public/admin/common = which interface the strings will be loaded into\n				\'strings\'	=> $mem_self_lang,				#	The strings themselves.\n				);\n	return $r;\n}\nfunction mem_self_gTxt($what,$args = array())\n{\n	global $mem_self_lang, $textarray;\n\n	$key = strtolower( MEM_SELF_PREFIX . \'-\' . $what );\n\n	if (isset($textarray[$key]))\n	{\n		$str = $textarray[$key];\n	}\n	else\n	{\n		$key = strtolower($what);\n\n		if (isset($mem_self_lang[$key]))\n			$str = $mem_self_lang[$key];\n		elseif (isset($textarray[$key]))\n			$str = $textarray[$key];\n		else\n			$str = $what;\n	}\n\n	if( !empty($args) )\n		$str = strtr( $str , $args );\n\n	return $str;\n}\n\nglobal $event, $levels;\n\n\nif (txpinterface == \'public\' or $event != \'admin\')\n{\n	if (file_exists( txpath.\'/lib/txplib_admin.php\' ))\n	{\n		require_once txpath.\'/lib/txplib_admin.php\';\n	}\n\n	require_once txpath.\'/include/txp_admin.php\';\n\n	if (empty($levels))\n	{\n		// copied from txp_admin.php\n		$levels = array(\n			0 => mem_self_gTxt(\'none\'),\n			6 => mem_self_gTxt(\'designer\'),\n			5 => mem_self_gTxt(\'freelancer\'),\n			4 => mem_self_gTxt(\'staff_writer\'),\n			3 => mem_self_gTxt(\'copy_editor\'),\n			2 => mem_self_gTxt(\'managing_editor\'),\n			1 => mem_self_gTxt(\'publisher\')\n		);\n	}\n\n//-------------------------------------------------------------\n	if (!function_exists(\'priv_levels\')) {\n		function priv_levels($item, $var) {\n			global $levels;\n\n			return selectInput($item, $levels, $var);\n		}\n	}\n}\n\n\nglobal $mem_self, $prefs;\n\n$mem_self = array(\n				\'admin_email\'		=>	\'\',\n				\'admin_name\'		=>	\'\',\n				\'admin_bcc\'			=>	\'0\',\n				\'new_user_priv\'		=>	\'0\',\n				\'status\'			=>	false,\n				\'status_message\'	=>	\'You are already registered.\',\n				\'email_message\'		=>	\'\'\n			);\n\n$mem_self[\'admin_email\'] = isset($prefs[\'mem_self_admin_email\']) ? $prefs[\'mem_self_admin_email\'] : \'\';\n$mem_self[\'admin_name\'] = isset($prefs[\'mem_self_admin_name\']) ? $prefs[\'mem_self_admin_name\'] : \'\';\n$mem_self[\'new_user_priv\'] = isset($prefs[\'mem_self_new_user_priv\']) ? $prefs[\'mem_self_new_user_priv\'] : \'0\';\n$mem_self[\'admin_bcc\'] = isset($prefs[\'mem_self_admin_bcc\']) ? $prefs[\'mem_self_admin_bcc\'] : \'0\';\n\nif ( @txpinterface == \'admin\' ) {\n	add_privs(\'self-reg\',\'1\');\n\n	register_callback(\'mem_self_register\',\'self-reg\',\'\', 1);\n	if ($event==\'self-reg\') {\n		// fake tabs when using them. Silences warnings from pageTop()\n		register_tab(\'admin\',\'self-reg\',\'self-reg\');\n\n		include_once txpath.\'/publish/taghandlers.php\';\n	}\n\n	function mem_self_register($event, $step)\n	{\n		global $prefs;\n\n		extract($prefs);\n\n		if ($event!=\'self-reg\')\n			return;\n\n		pageTop(\'Self Registration\',\'\');\n\n		if ($step == \'install\') {\n			echo mem_self_register_install();\n		} else if ($step==\'preinstall\') {\n			$mem_xtra_columns	= mem_get_extra_user_columns();\n\n			$mem_admin_name = !empty($mem_admin_name) ? $mem_admin_name : \'Admin\';\n			$mem_admin_email = !empty($mem_admin_email) ? $mem_admin_email : \'Admin@yourdomain.com\';\n			$mem_new_use_priv = !empty($mem_new_use_priv) ? $mem_new_use_priv : \'0\';\n			$mem_self_admin_bcc = !empty($mem_self_admin_bcc) ? $mem_self_admin_bcc : \'0\';\n\n			$use_ign_input = \'\';\n			// is ign_password_protect loaded on the system?\n			if (load_plugin(\'ign_password_protect\') || (isset($mem_use_ign_db) && $mem_use_ign_db)) {\n				$mem_use_ign_db = !empty($mem_use_ign_db) ? $mem_use_ign_db : \'0\';\n				$use_ign_input = yesnoradio(\'use_ign_db\',$mem_use_ign_db);\n			}\n\n			$xtra_columns = mem_get_extra_user_columns();\n			$has_address = in_array(\'address\',$xtra_columns);\n			$has_phone = in_array(\'phone\',$xtra_columns);\n\n			$add_address_input = yesnoradio(\'add_address\',\'0\');\n			$add_phone_input = yesnoradio(\'add_phone\',\'0\');\n\n			echo form(\n				eInput(\'self-reg\').sInput(\'install\').\n				startTable(\'edit\') .\n					tr( fLabelCell(\'admin_name\')	. tda(fInput(\'text\',\'admin_name\',$mem_admin_name,\'edit\')) ) .\n					tr( fLabelCell(\'admin_email\')	. tda(fInput(\'text\',\'admin_email\',$mem_admin_email,\'edit\')) ) .\n					tr( fLabelCell(\'admin_bcc\')	. tda(yesnoRadio(\'admin_bcc\',$mem_self_admin_bcc)) ) .\n					tr( fLabelCell(\'new_user_priv\')	. tda(priv_levels(\'new_user_priv\',$mem_new_use_priv)) ) .\n					tr( fLabelCell(\'use_ign_db\')	. tda($use_ign_input) ) .\n					($has_address ? \'\' : tr( fLabelCell(\'add_address_field\') . tda($add_address_input) ) ) .\n					($has_phone ? \'\' : tr( fLabelCell(\'add_phone_field\') . tda($add_phone_input) ) ) .\n					tr( td() . td( fInput(\"submit\", \'submit\',mem_self_gTxt(\'install\'),\"Publish\"), 2 ) ) .\n				endTable()\n				);\n		} else {\n			echo \'<div><a href=\"?event=self-reg&step=preinstall\">\'.mem_self_gTxt(\'install\').\'</a></div>\';\n		}\n	}\n\n\n	function mem_self_register_install()\n	{\n		global $mem_self;\n\n		extract(doSlash(gpsa(array(\n			\'admin_email\',\n			\'admin_name\',\n			\'new_user_priv\',\n			\'use_ign_db\',\n			\'add_address\',\n			\'add_phone\'\n		))));\n\n		if (!isset($new_user_priv) || empty($new_user_priv)) $new_user_priv = \'0\';\n\n		$log = array();\n\n		if (!($rs=safe_field(\'val,html\',\'txp_prefs\',\"name=\'mem_self_use_ign_db\'\"))) {\n			if ( set_pref(\'mem_self_use_ign_db\',$use_ign_db,\'self_reg\',1,0,\'yesnoradio\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_use_ign_db\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_use_ign_db\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			if ($rs[\'html\'] != \'yesnoradio\') {\n				safe_update(\'txp_prefs\',\"html=\'yesnoradio\'\",\"name=\'mem_self_use_ign_db\'\");\n			}\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_use_ign_db\',\'{value}\'=>$rs));\n		}\n\n		$user_table = mem_get_user_table_name();\n\n		$xtra_columns = mem_get_extra_user_columns();\n		if ($add_address) {\n			if (!in_array(\'address\',$xtra_columns)) {\n				if (safe_alter($user_table,\"ADD `address` VARCHAR( 128 )\")) {\n					$log[] = mem_self_gTxt(\'log_col_added\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table));\n				} else {\n					$log[] = mem_self_gTxt(\'log_col_failed\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table,\'{error}\'=>mysql_error()));\n				}\n			} else {\n				$log[] = mem_self_gTxt(\'log_col_exists\', array(\'{name}\'=>\'address\',\'{table}\'=>$user_table));\n			}\n		}\n		if ($add_phone) {\n			if (!in_array(\'phone\',$xtra_columns)) {\n				if (safe_alter($user_table,\"ADD `phone` VARCHAR( 32 )\")) {\n					$log[] = mem_self_gTxt(\'log_col_added\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table));\n				} else {\n					$log[] = mem_self_gTxt(\'log_col_failed\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table,\'{error}\'=>mysql_error()));\n				}\n			} else {\n				$log[] = mem_self_gTxt(\'log_col_exists\', array(\'{name}\'=>\'phone\',\'{table}\'=>$user_table));\n			}\n		}\n\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_email\'\"))) {\n			if ( set_pref(\'mem_self_admin_email\',$admin_email,\'self_reg\',1)) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_email\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_email\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_email\',\'{value}\'=>$rs));\n		}\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_name\'\"))) {\n			if ( set_pref(\'mem_self_admin_name\',$admin_name,\'self_reg\',1)) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_name\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_name\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_name\',\'{value}\'=>$rs));\n		}\n		if (!($rs=safe_row(\'val,html\',\'txp_prefs\',\"name=\'mem_self_new_user_priv\'\"))) {\n			if ( set_pref(\'mem_self_new_user_priv\',$new_user_priv,\'self_reg\',1,0,\'priv_levels\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_new_user_priv\'));\n				$mem_self[\'new_user_priv\'] = $new_user_priv;\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_newuser_priv\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			safe_update(\'txp_prefs\',\"html=\'priv_levels\'\",\"name=\'mem_self_new_user_priv\'\");\n\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_new_user_priv\',\'{value}\' => $rs));\n		}\n		if (!($rs=safe_field(\'val\',\'txp_prefs\',\"name=\'mem_self_admin_bcc\'\"))) {\n			if ( set_pref(\'mem_self_admin_bcc\',\'0\',\'self_reg\',1,\'yesnoradio\')) {\n				$log[] = mem_self_gTxt(\'log_added_pref\', array(\'{name}\'=>\'mem_self_admin_bcc\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_pref_failed\', array(\'{name}\'=>\'mem_self_admin_bcc\',\'{error}\'=>mysql_error()));\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_pref_exists\', array(\'{name}\'=>\'mem_self_admin_bcc\',\'{value}\'=>$rs));\n		}\n\n		// create default registration form\n		$form_html = <<<EOF\n	<fieldset>\n	<legend>Register</legend>\n		<txp:mem_form_text name=\"RealName\" label=\"Full Name\" /><br />\n		<br />\n\n		<txp:mem_form_text name=\"name\" label=\"Username\" /><br />\n		<br />\n\n		<txp:mem_form_email name=\"email\" label=\"E-Mail\" /><br />\n		<br />\n\n		<txp:mem_form_submit />\n	</fieldset>\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_form\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_form\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_form\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_form\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_form\'));\n		}\n\n		// create default successful registration form to show the user\n		$form_html = <<<EOF\n<h3>Account Created</h3>\n<p>An email containing your password has been sent to <txp:mem_profile var=\"email\" />.</p>\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_success\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_success\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_success\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_success\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_success\'));\n		}\n\n		// create default successful registration email form\n		$form_html = <<<EOF\nDear <txp:mem_name />,\n\nThank you for registering at <txp:mem_siteurl />.\n\nYour login name: <txp:mem_username />\nYour password: <txp:mem_password />\n\nIf you have any questions please reply to this email address.\n\nSincerely,\n<txp:mem_admin_name />\n<txp:mem_admin_email />\nEOF;\n\n		$form = fetch(\'Form\',\'txp_form\',\'name\',\'self_register_email\');\n		if (!$form) {\n			if (safe_insert(\'txp_form\',\"name=\'self_register_email\',type=\'misc\',Form=\'{$form_html}\'\")) {\n				$log[] = mem_self_gTxt(\'log_form_added\', array(\'{name}\'=>\'self_register_email\'));\n			} else {\n				$log[] = mem_self_gTxt(\'log_form_failed\', array(\'{name}\'=>\'self_register_email\',\'{error}\'=>mysql_error())).br.\n					\'<textpattern style=\"width:300px;height:150px;\">\'.htmlspecialchars($form_html).\'</textarea>\';\n			}\n		} else {\n			$log[] = mem_self_gTxt(\'log_form_found\', array(\'{name}\'=>\'self_register_email\'));\n		}\n\n		$tag_help = \'<txp:mem_self_register_form form=\"self_register_form\" />\';\n		$log[] = mem_self_gTxt(\'log_xmpl_tag\').br.\n			\'<textarea style=\"width:400px;height:40px;\">\'.htmlspecialchars($tag_help).\'</textarea>\';\n\n		return doWrap($log,\'ul\',\'li\');\n	}\n}\n\n\nregister_callback(\'mem_self_register_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_register_form($atts,$thing=\'\')\n{\n	global $prefs, $sitename, $production_status;\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'email_form\'	=> \'\',\n		\'from\'		=> $prefs[\'mem_self_admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \'[\'.$sitename.\'] \'. mem_self_gTxt(\'your_login_info\'),\n		\'login_url\'	=> rtrim(hu,\'/\').\'/textpattern/index.php\',\n	),$atts,false));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	foreach(array(\'from\',\'reply\',\'subject\',\'login_url\',\'email_form\') as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts+array(\'type\'=>\'mem_self_register\'),$thing);\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_form_submit()\n{\n	global $prefs, $mem_self, $sitename, $mem_profile, $mem_form_type, $mem_form_values, $mem_form_thanks_form;\n\n	if ($mem_form_type != \'mem_self_register\') return;\n\n	extract($mem_self);\n\n	$pw = generate_password(10);\n\n	if (!$mem_profile) $mem_profile = array();\n\n	$mem_profile[\'nonce\'] = $nonce = md5( uniqid( rand(), true ) );\n	$mem_profile[\'RealName\'] = $name = $mem_form_values[\'RealName\'];\n	$mem_profile[\'email\'] = $email = $mem_form_values[\'email\'];\n	$mem_profile[\'name\'] = $username = $mem_form_values[\'name\'];\n	$mem_profile[\'privs\'] = $new_user_priv;\n\n	if (safe_row(\'user_id\', mem_get_user_table_name(), \"name = \'\".doSlash($username).\"\'\")) {\n		return mem_form_error(mem_self_gTxt(\'user_exists\'));\n	}\n\n	$xtra_columns = mem_get_extra_user_columns();\n\n	foreach($xtra_columns as $c) {\n		$c_name = trim( $c[\'Field\'] );\n\n		if (isset($mem_form_values[$c_name]))\n			$mem_profile[$c_name] = $mem_form_values[$c_name];\n	}\n\n	$rs = false;\n\n	$xtra = mem_get_extra_user_columns_insert_string();\n\n	$rs = safe_insert(\n		mem_get_user_table_name(),\n		\"privs    = \'\".doSlash($new_user_priv).\"\',\n		 name     = \'\".doSlash($username).\"\',\n		 email    = \'\".doSlash($email).\"\',\n		 RealName = \'\".doSlash($name).\"\',\n		 pass     =  password(lower(\'\".doSlash($pw).\"\')),\n		 nonce    = \'\".doSlash($nonce).\"\'\" . $xtra\n	);\n\n	if ($rs) {\n		$mem_profile[\'user_id\'] = $rs;\n		$mem_profile[\'last_access\'] = 0;\n\n		$message = @fetch_form($mem_form_values[\'email_form\']);\n\n		if (empty($message)) {\n			$message = <<<EOF\n{RealName},\n	You have successfully registered at {sitename}. You can login at {login_url}.\n\nUsername: {username}\nPassword: {password}\n\nRegards,\n{admin_name}\nEOF;\n		}\n\n		if (!empty($message)) {\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			$vals[\'password\']		= $pw;\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\', $b, $message);\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$mem_form_thanks_form = str_replace(\'<txp:mem_\'.$a.\' />\', $b, $mem_form_thanks_form);\n				$mem_form_thanks_form = str_replace(\'{\'.$a.\'}\', $b, $mem_form_thanks_form);\n			}\n\n			$message = parse($message);\n			$to = $mem_profile[\'email\'];\n			$from = $mem_form_values[\'from\'];\n			$reply = $mem_form_values[\'reply\'];\n			$subject = $mem_form_values[\'subject\'];\n\n			if ($mem_self[\'admin_bcc\'])\n			{\n				$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n				$from .= $sep . \'Bcc: \' . $from;\n			}\n\n			$sent = mem_form_mail($from,$reply,$to,$subject,$message);\n\n			$mem_self[\'email_status\'] = $sent;\n			$mem_self[\'email_message\'] = $message;\n			$mem_self[\'status\'] = true;\n\n			if ($sent) {\n				$cookietime = time() + (365*24*3600);\n				setcookie(\"txp_self_registered\", \"1\",  $cookietime, \"/\");\n\n				$mem_self[\'status_message\'] = mem_self_gTxt(\'password_sent_to\', array(\'email\'=>$email));\n			} else {\n				// failed to send email\n				return mem_form_error( mem_self_gTxt(\'account_created_mail_failed\') );\n			}\n		}\n	} else {\n		return mem_form_error( mem_self_gTxt(\'error_adding_new_author\') );\n	}\n}\n\n\n/** Returns the name of the user table (without PFXS) */\nfunction mem_get_user_table_name() {\n	global $prefs;\n\n	extract($prefs);\n\n	$table_name = \'txp_users\';\n\n	if (isset($mem_self_use_ign_db) && $mem_self_use_ign_db == \'1\') {\n\n		if (isset($ign_use_custom) && $ign_use_custom==\'1\') {\n\n			if (isset($ign_user_db) && !empty($ign_user_db))\n				$table_name = $ign_user_db;\n		}\n	}\n	return $table_name;\n}\n\n/** SQL string builder for non-standard fields */\nfunction mem_get_extra_user_columns_insert_string() {\n	$xtra_columns = mem_get_extra_user_columns();\n\n	$xtra = \'\';\n\n	foreach ($xtra_columns as $xcol) {\n		$name = $xcol[\'Field\'];\n		$type = strtolower($xcol[\'Type\']);\n		$val = gps($name);\n\n		if ( strstr($type,\'int\')\n				|| $type==\'float\'\n				|| $type==\'decimal\'\n				|| $type==\'double\'\n				|| $type==\'bool\' ) {\n			// don\'t quote value\n			if (!intval($val))\n				$val = ( $val==\'yes\' || $val==\'on\' ) ? $val = 1 : $val = 0;\n\n			$xtra .= \", {$name}=\" . ($val == false ? \'0\' : doSlash($val));\n		} else {\n			// quote value\n			$xtra .= \", {$name}=\'\" . doSlash($val) .\"\'\";\n		}\n	}\n\n	return $xtra;\n}\n\n/** Diff user table and return non-standard columns */\nfunction mem_get_extra_user_columns()\n{\n	static $default_columns = array(\'user_id\',\'name\',\'pass\',\'RealName\',\'email\',\'privs\',\'last_access\',\'nonce\');\n	static $xtra_columns = false;\n\n	if (is_array($xtra_columns))\n		return $xtra_columns;\n\n	$table_name = mem_get_user_table_name();\n	$txpdesc = getRows(\'describe \'.PFX. $table_name);\n\n	$xtra_cols = array();\n\n	$dcols = $default_columns;\n\n	foreach($txpdesc as $r) {\n		if ( !in_array($r[\'Field\'], $default_columns) )\n			$xtra_cols[] = $r;\n	}\n\n	return $xtra_cols;\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_email_message($atts)\n{\n	global $mem_self;\n	return $mem_self[\'email_message\'];\n}\n\n// -------------------------------------------------------------\nfunction mem_self_register_status_message($atts)\n{\n	global $mem_self;\n	return $mem_self[\'status_message\'];\n}\n\n\n// -------------------------------------------------------------\nfunction mem_if_message_sent($atts,$thing)\n{\n	global $mem_self;\n	$condition = ($mem_self[\'email_status\']);\n	return parse(EvalElse($thing, $condition));\n}\n\n// -------------------------------------------------------------\nfunction mem_if_self_registered($atts,$thing)\n{\n	global $mem_self,$txp_user,$ign_user;\n	$condition = ($mem_self[\'status\'] or !empty($_COOKIE[\'txp_self_registered\']) or !empty($txp_user) or !empty($ign_user) );\n	return parse(EvalElse($thing, $condition));\n}\n\n\n////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////\n// User Side Profile and Password Change Forms\n// -------------------------------------------------------------\nif (txpinterface != \'admin\' and !function_exists(\'txp_validate\')) {\n	require_once txpath.\'/include/txp_auth.php\';\n}\n\nfunction mem_self_password_reset_form($atts,$thing=\'\')\n{\n	global $mem_self, $sitename, $production_status;\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'form_mail\'	=> \'\',\n		\'from\'		=> $mem_self[\'admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \"[$sitename] \".mem_self_gTxt(\'password_reset_confirmation_request\'),\n		\'confirm_url\'	=> \'\',\n		\'check_name\'	=> 1,\n		\'check_email\'	=> 1\n	),$atts,false));\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n\n	if (gps(\'mem_self_confirm\'))\n	{\n		sleep(3);\n\n		$confirm = pack(\'H*\', gps(\'mem_self_confirm\'));\n		$name    = substr($confirm, 5);\n		$nonce   = safe_field(\'nonce\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\n\n		if ($nonce and $confirm === pack(\'H*\', substr(md5($nonce), 0, 10)).$name)\n		{\n			$email = safe_field(\'email\', \'txp_users\', \"name = \'\".doSlash($name).\"\'\");\n			$new_pass = doSlash(generate_password(10));\n\n			$rs = safe_update(\'txp_users\', \"pass = password(lower(\'$new_pass\'))\", \"name = \'\".doSlash($name).\"\'\");\n\n			if ($rs)\n			{\n				if (send_new_password($new_pass, $email, $name))\n					return mem_self_gTxt(\'password_sent_to\', array(\'{email}\'=>$email));\n				else\n					return mem_self_gTxt(\'mail_sorry\');\n			}\n			else\n				return mem_self_gTxt(\'password_change_failed\');\n		}\n	}\n\n	if (!$check_name and !$check_email)\n		return mem_self_gTxt(\'invalid_form_tags\',array(\'{form}\'=>\'mem_self_password_reset_form\'));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	$secrets = array(\'form_mail\',\'from\',\'reply\',\'subject\',\'confirm_url\', \'check_name\', \'check_email\');\n\n	foreach($secrets as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_password_reset\'),$thing);\n}\n\nregister_callback(\'mem_self_password_reset_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_password_reset_form_submit()\n{\n	global $mem_form_type, $mem_form_values, $mem_profile, $prefs, $sitename;\n\n	if ($mem_form_type != \'mem_self_password_reset\')\n		return;\n\n	$check_name = $mem_form_values[\'check_name\'];\n	$check_email = $mem_form_values[\'check_email\'];\n\n	$where = array();\n\n	if ($check_name) {\n		foreach(array(\'name\',\'p_userid\',\'username\') as $n)\n		{\n			if (isset($mem_form_values[$n])) {\n				$name = $mem_form_values[$n];\n				break;\n			}\n		}\n\n		if (!isset($name))\n			return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'name\'));\n\n		$where[] = \"name = \'\".doSlash($name).\"\'\";\n	}\n\n	if ($check_email) {\n		$email = @$mem_form_values[\'email\'];\n\n		if (empty($email))\n			return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'email\'));\n\n		$where[] = \"email = \'\".doSlash($email).\"\'\";\n	}\n\n	if (empty($where))\n		return mem_self_gTxt(\'missing_form_field\',array(\'{name}\'=>\'name\'));\n\n	$rs = safe_row(\'name, email, nonce, RealName\', mem_get_user_table_name(), join(\' and \',$where));\n\n	if ($rs)\n	{\n		$url = @$mem_form_values[\'confirm_url\'];\n		$url = empty($url) ? hu.\'textpattern/index.php\' : hu.ltrim($url,\'/\');\n		$url .= (strstr($url, \'?\')===false) ? \'?\' : \'&\';\n\n		extract($rs);\n\n		$confirm = bin2hex(pack(\'H*\', substr(md5($nonce), 0, 10)).$name);\n\n		$message = fetch_form($mem_form_values[\'form_mail\']);\n\n		if (empty($message)) {\n			$msg = mem_self_gTxt(\'greeting\').\' \'.$name.\',\'.\n					n.n.mem_self_gTxt(\'password_reset_confirmation\').\': \'.\n					n. $url . \'mem_self_confirm=\'.$confirm;\n		}\n		else {\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			//$vals[\'password\']		= $pw;\n			$vals[\'confirm_url\'] = $url;\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\',$b,$message);\n			}\n\n			$msg = parse($message);\n		}\n\n		$to = $email;\n		$from = $mem_form_values[\'from\'];\n		$reply = $mem_form_values[\'reply\'];\n		$subject = $mem_form_values[\'subject\'];\n\n		if (mem_form_mail($from,$reply,$to,$subject,$msg))\n			return mem_self_gTxt(\'password_reset_confirmation_request_sent\');\n		else\n			return mem_self_gTxt(\'mail_sorry\');\n	}\n	else\n		return mem_self_gTxt(\'user_not_found\');\n}\n\n\nfunction mem_self_change_password_form($atts,$thing=\'\')\n{\n	global $mem_self, $sitename, $production_status;\n\n	header(\'Cache-Control: no-cache\');\n	header(\'Pragma: no-cache\');\n\n	if (!is_callable(\'mail\'))\n	{\n		return ($production_status == \'live\') ?\n			mem_self_gTxt(\'mail_sorry\') :\n			gTxt(\'warn_mail_unavailable\');\n	}\n\n	extract(lAtts(array(\n		\'form\'		=> \'\',\n		\'email_form\'	=> \'\',\n		\'from\'		=> $mem_self[\'admin_email\'],\n		\'reply\'		=> \'\',\n		\'subject\'	=> \'[\'.$sitename.\'] \'. mem_self_gTxt(\'password_changed\'),\n	),$atts,false));\n\n	if (!empty($form)) {\n		$thing = fetch_form($form);\n		unset($atts[\'form\']);\n	}\n\n	foreach(array(\'from\',\'reply\',\'subject\',\'email_form\') as $a) {\n		$thing .= \'<txp:mem_form_secret name=\"\'.$a.\'\" value=\"\'.$$a.\'\" />\';\n		unset($atts[$a]);\n	}\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_password\'),$thing);\n}\n\nregister_callback(\'mem_self_password_form_submit\',\'mem_form.submit\');\n\nfunction mem_self_password_form_submit()\n{\n	global $prefs, $txp_user, $ign_user, $mem_form_type, $mem_form_values, $mem_form_thanks_form, $mem_self;\n\n	if ($mem_form_type != \'mem_self_password\')\n		return;\n\n	$verify_old = array_key_exists(\'old_password\', $mem_form_values);\n	$confirm = array_key_exists(\'password_confrim\', $mem_form_values);\n\n	$new_pass = $mem_form_values[\'password\'];\n	$old_pass = $mem_form_values[\'old_password\'];\n\n	$user = isset($ign_user) ? $ign_user : $txp_user;\n\n	$where = \"name = \'\".doSlash($user).\"\'\";\n\n	$is_valid = $verify_old ? txp_validate($user,$old_pass) : true;\n\n	if (!$is_valid) {\n		return mem_form_error(mem_self_gTxt(\'password_invalid\'));\n	}\n\n	if ($confirm and ($new_pass != $mem_form_values[\'password_confirm\'])) {\n		return mem_form_error(mem_self_gTxt(\'password_mismatch\'));\n	}\n\n	$rs = safe_update( mem_get_user_table_name(), \"pass = password(lower(\'$new_pass\'))\", $where);\n\n	if (!$rs) {\n		return mem_form_error(mem_self_gTxt(\'password_change_failed\'));\n	}\n\n	// successful\n	$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'{$user}\'\");\n\n	if ($mem_profile)\n	{\n		$mem_profile[\'new_pass\'] = $new_pass;\n\n		$message = @fetch_form($mem_form_values[\'email_form\']);\n\n		if (!empty($message))\n		{\n			$vals = $mem_form_values;\n			$vals[\'sitename\']	= $sitename;\n			$vals[\'admin_name\']	= $prefs[\'mem_self_admin_name\'];\n			$vals[\'admin_email\']	= $vals[\'from\'];\n			$vals[\'password\']		= $mem_profile[\'new_pass\'];\n			$vals[\'siteurl\']		= hu;\n			$vals[\'username\']		= $vals[\'name\'];\n\n			foreach ($vals as $a=>$b) {\n				$message = str_replace(\'{\'.$a.\'}\', $b, $message);\n				$message = str_replace(\'<txp:mem_\'.$a.\' />\',$b,$message);\n			}\n\n		}\n		else {\n			$message = mem_self_gTxt(\'greeting\', array(\'{name}\'=>$mem_form_values[\'RealName\'])).\"\\r\\n\".\n				mem_self_gTxt(\'your_password_is\', array(\'{password}\'=>$new_pass)).\"\\r\\n\".\n				mem_self_gTxt(\'log_in_at\', array(\'{url}\'=> $mem_form_values[\'login_url\']));\n		}\n\n		$msg = parse($message);\n\n		$to = $mem_profile[\'email\'];\n		$from = $mem_form_values[\'from\'];\n		$reply = $mem_form_values[\'reply\'];\n		$subject = $mem_form_values[\'subject\'];\n\n		if (mem_form_mail($from,$reply,$to,$subject,$msg))\n			return mem_self_gTxt(\'password_changed\');\n		else\n			return mem_self_gTxt(\'password_changed_mail_failed\');\n	}\n	else {\n		// no email, fail silently\n	}\n\n}\n\nregister_callback(\'mem_self_user_edit_submit\',\'mem_form.submit\');\nregister_callback(\'mem_self_register_defaults\',\'mem_form.defaults\');\n\nfunction mem_self_user_edit_form($atts,$thing=\'\')\n{\n	header(\'Cache-Control: no-cache\');\n	header(\'Pragma: no-cache\');\n\n	return mem_form($atts + array(\'type\'=>\'mem_self_user_edit\'),$thing);\n}\n\nfunction mem_self_register_defaults()\n{\n	global $txp_user, $ign_user, $mem_form_type, $mem_profile;\n\n	if ($mem_form_type != \'mem_self_user_edit\') return;\n\n	$user = isset($ign_user) ? $ign_user : $txp_user;\n\n\n	$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'{$user}\'\");\n\n	if ($mem_profile) {\n		mem_form_default($mem_profile);\n	}\n}\n\nfunction mem_self_user_edit_submit()\n{\n	global $prefs, $txp_user, $ign_user, $mem_form_type, $mem_form_values, $mem_form_thanks_form, $mem_profile;\n\n	if ($mem_form_type != \'mem_self_user_edit\')\n		return;\n\n	if (isset($ign_user)) $txp_user = $ign_user;\n\n	$name = isset($mem_form_values[\'name\']) ? trim($mem_form_values[\'name\']) : \'\';\n\n	$xtra = mem_get_extra_user_columns_insert_string();\n\n	$mem_profile = array();\n\n	$sql = \'\';\n\n	if (!empty($mem_form_values[\'email\']))\n		$sql  = \"email = \'\".doSlash($mem_form_values[\'email\']).\"\'\";\n	if (!empty($mem_form_values[\'RealName\']))\n		$sql .= \", RealName = \'\".doSlash($mem_form_values[\'RealName\']).\"\'\";\n\n	if (!empty($name))\n	{\n		$sql .= \", name = \'\".doSlash($name).\"\'\";\n\n		// need to remove the ign_password_protect cookie\n		setcookie(\'ign_login\', \'\', time()-86400);\n	}\n\n	if (empty($sql))\n		return mem_self_gTxt(\'saved_user_profile_failed\');\n\n	$rs = safe_update( mem_get_user_table_name(),\n				$sql . $xtra,\n				\"name = \'{$txp_user}\'\");\n\n	if ($rs) {\n		return mem_self_gTxt(\'saved_user_profile\');\n	}\n	else {\n		return mem_self_gTxt(\'saved_user_profile_failed\');\n	}\n}\n\n\nfunction mem_profile($atts, $body=\'\')\n{\n	global $mem_profile,$txp_user,$ign_user;\n\n	if (isset($ign_user)) $txp_user = $ign_user;\n\n	extract(lAtts(array(\n		\'user\'		=> \'\',\n		\'userid\'	=> \'\',\n		\'var\'			=> \'RealName\',\n		\'form\'		=> \'\'\n	),$atts));\n\n	if (empty($user) && empty($userid)) {\n		// use the old method\n		if (!is_array($mem_profile) && $txp_user)\n			$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'\". doSlash($txp_user).\"\'\");\n	} else {\n		$mem_profile = (is_array($mem_profile) ? $mem_profile : array());\n\n		// look up a potentially new user\n		if (!empty($user)) {\n			if (!array_key_exists(\'name\', $mem_profile) || strcmp($mem_profile[\'name\'],$user)!=0)\n				$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"name = \'\". doSlash($user).\"\'\");\n		}\n\n		if (!empty($userid) && is_numeric($userid)) {\n			if (!array_key_exists(\'user_id\', $mem_profile) || strcmp($mem_profile[\'user_id\'],$userid)!=0)\n				$mem_profile = safe_row(\'*\',mem_get_user_table_name(),\"user_id = \". doSlash($userid));\n		}\n	}\n\n	$out = \'\';\n\n	if (empty($form) && empty($body)) {\n		if ($mem_profile)\n			$out = array_key_exists($var,$mem_profile) ? $mem_profile[$var] : \'\';\n	} else {\n		$thing = empty($body) ? fetch_form($form) : $body;\n\n		$out = parse(EvalElse($thing, !empty($mem_profile)));\n	}\n\n	return $out;\n}\n\n\nfunction mem_submit($atts) {\n	extract($atts);\n	if (isset($value)) {\n		$atts[\'label\'] = $value;\n		unset($atts[\'value\']);\n	}\n	$atts[\'name\'] = \'save\';\n	return mem_form_submit($atts);\n}\nfunction mem_password_input($atts) {\n	global $mem_profile;\n	$atts[\'password\'] = 1;\n	return mem_form_text( mem_self_map_tag($atts,\'new_pass\',\'\') );\n}\nfunction mem_realname_input($atts) {\n	global $mem_profile;\n	return mem_form_text( mem_self_map_tag($atts,\'RealName\',$mem_profile[\'RealName\']) );\n}\nfunction mem_email_input($atts) {\n	global $mem_profile;\n	return mem_form_email( mem_self_map_tag($atts,\'email\',$mem_profile[\'email\']) );\n}\nfunction mem_phone_input($atts) {\n	global $mem_profile;\n	return mem_form_text( mem_self_map_tag($atts,\'phone\',$mem_profile[\'phone\']) );\n}\nfunction mem_address_input($atts) {\n	global $mem_profile;\n	return mem_form_textarea( mem_self_map_tag($atts,\'address\', $mem_profile[\'address\']) );\n}\nfunction mem_self_map_tag($atts,$name,$default) {\n	$atts[\'name\'] = $name;\n	if (!empty($default))\n		$atts[\'default\'] = $default;\n	return $atts;\n}\n\nfunction mem_self_user_count($atts)\n{\n	global $mem_self;\n\n	extract(lAtts(array(\n		\'user_levels\'	=> \'0,1,2,3,4,5,6\',\n		\'wraptag\'	=> \'\',\n		\'class\'		=> \'\'\n	),$atts));\n\n	if (!empty($user_levels) || $user_levels==\'0\')\n		$user_levels = doSlash(split(\',\',$user_levels));\n	else\n		$user_levels = array($mem_self[\'new_user_priv\']);\n\n	$levels = join(\',\',$user_levels);\n	$count = safe_field(\'COUNT(*)\', mem_get_user_table_name(), \"privs IN ({$levels})\");\n\n	return doTag($count,$wraptag,$class);\n}\n\n\n\n', '6a27f284ddcbe726984d647f519ffc4e', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('upm_date_archive', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-date-archive', '0.2.5', 'Simple, clean and efficient date archive listings.', '<style type=\"text/css\" media=\"screen,projection\">\n<!--\nvar {\n	padding: 0.1em;\n	font-style: normal;\n	font-weight: bold;\n	font-family: monospace;\n	background-color: #eee;\n}\n\ntable.upm_tag_atts {\n	margin: 0 0 15px 0;\n	border: 1px solid #eee;\n	border-collapse: collapse;\n}\n\ntable.upm_tag_atts caption {\n	padding: 0.5em;\n	font-weight: bold;\n	text-align: left;\n	background-color: #ddd;\n}\n\ntable.upm_tag_atts tr {\n	border-bottom: 1px solid #eee;\n}\n\ntable.upm_tag_atts th, table.upm_tag_atts td {\n	margin: 0;\n	padding: 0.5em 1em;\n	vertical-align: top;\n}\n\ntable.upm_tag_atts th {\n	font-weight: normal;\n	text-align: left;\n	background-color: #eee;\n}\n-->\n</style>\n\n<h1>upm_date_archive</h1>\n\n<ol id=\"start\">\n	<li><a href=\"#intro\">Intro</a></li>\n	<li><a href=\"#setup\">Setup</a></li>\n	<li><a href=\"#tags\">New Tags</a></li>\n	<li><a href=\"#credit\">Credit and Developer Notes</a></li>\n	<li><a href=\"#footnotes\">Footnotes</a></li>\n</ol>\n\n\n\n<h2 id=\"intro\">Intro</h2>\n\n<p>Simple, fast and efficient article archives.</p>\n\n\n\n<h2 id=\"setup\">Setup</h2>\n\n<p>Because of how Textpattern&#8217;s error handling works, if you are using clean urls, and wish to make use of the plugin&#8217;s &#8220;smart&#8221; mode, you will need to tell the plugin when it should &#8220;listen&#8221; for archive requests. Simply head to the plugin&#8217;s preferences, and check off the sections you want. This is not necessary for messy urls.</p>\n\n<p>upm_date_menu can only be used on the front page by using messy urls. This is due to how Textpattern understands sections and the front page.</p>\n\n\n\n<h2 id=\"tags\">New Tags</h2>\n\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_date_menu</caption>\n\n	<tr>\n		<th>Name</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>category</td>\n		<td>The category/categories of articles to include in the archive. You can either include or exclude (using <var>exclude_category</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing category name(s), comma-separated.</td>\n		<td>Current category</td>\n	</tr>\n\n	<tr>\n		<td>class</td>\n		<td>The (X)HTML/CSS class to apply to the list.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS class name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>exclude_category</td>\n		<td>The category/categories of articles to exclude from the archive. You can either exclude or include (using <var>category</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing category name(s), comma-separated.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>exclude_section</td>\n		<td>The section(s) of articles to exclude from the archive. You can either exclude or include (using <var>section</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing section name(s), comma-separated.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>id</td>\n		<td>The (X)HTML/CSS id to apply to the list.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS id.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>include_expired</td>\n		<td>Whether to include expired articles in the list.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td>Admin preference \"Publish expired articles?\"</td>\n	</tr>\n\n	<tr>\n		<td>insert_empty_months</td>\n		<td>Whether to include in the list months without articles.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td><var>yes</var></td>\n	</tr>\n\n	<tr>\n		<td>link_to</td>\n		<td>The section to include within the month url.</td>\n		<td></td>\n		<td>Any valid section for which you have enabled smart mode, and also contains the upm_date_archive tag.</td>\n		<td>Current section</td>\n	</tr>\n\n	<tr>\n		<td>month_format</td>\n		<td>How to format the month display.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://php.net/strftime\">strftime() format string</a>.</td>\n		<td><var>%b</var></td>\n	</tr>\n\n	<tr>\n		<td>remain_linked</td>\n		<td>When viewing a month&#8217;s post list, whether to display the month as a url or text.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td><var>yes</var></td>\n	</tr>\n\n	<tr>\n		<td>section</td>\n		<td>The section(s) of articles to include in the archive. You can either include or exclude (using <var>exclude_section</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing section name(s), comma-separated.</td>\n		<td>Current section</td>\n	</tr>\n\n	<tr>\n		<td>show_article_count</td>\n		<td>Whether to display the count of the number of articles for a given month. If enabled, months with any articles in them display the number in parenthesis after the month name.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td><var>no</var></td>\n	</tr>\n\n	<tr>\n		<td>sort</td>\n		<td></td>\n		<td>No</td>\n		<td><var>the_month</var> or <var>the_year</var>, <var>asc</var> or <var>desc</var></td>\n		<td><var>the_year desc, the_month asc</var></td>\n	</tr>\n\n	<tr>\n		<td>time</td>\n		<td>Restrict by date/time posted.</td>\n		<td>No</td>\n		<td><var>past</var>, <var>future</var> or <var>any</var></td>\n		<td><var>past</var></td>\n	</tr>\n\n	<tr>\n		<td>year_format</td>\n		<td>How to format the year heading display.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://php.net/strftime\">strftime() format string</a>.</td>\n		<td><var>%Y</var></td>\n	</tr>\n</table>\n\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_date_archive</caption>\n\n	<tr>\n		<th>Name</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>category</td>\n		<td>The category/categories of articles to include in the archive. You can either include or exclude (using <var>exclude_category</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing category name(s), comma-separated.</td>\n		<td>Current category</td>\n	</tr>\n\n	<tr>\n		<td>date_format</td>\n		<td>If including the date with <var>include_date</var> attribute, how to format the date display for dach day.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://php.net/strftime\">strftime() format string</a>.</td>\n		<td><var>%e</var><sup><a href=\"#fn1\" id=\"ref1\">1</a></sup></td>\n	</tr>\n\n	<tr>\n		<td>exclude_category</td>\n		<td>The category/categories of articles to exclude from the archive. You can either exclude or include (using <var>category</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing category name(s), comma-separated.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>exclude_section</td>\n		<td>The section(s) of articles to exclude from the archive. You can either exclude or include (using <var>section</var>), but not both at the same time.</td>\n		<td>No</td>\n		<td>Any existing section name(s), comma-separated.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>form</td>\n		<td>Form to use for displaying each article within the list.</td>\n		<td>No</td>\n		<td>Any existing article form.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>heading_class</td>\n		<td>The (X)HTML/CSS class to apply to the month/year heading.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS class name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>heading_format</td>\n		<td>How to format the month/year heading date display.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://php.net/strftime\">strftime() format string</a></td>\n		<td><var>%B %Y</var></td>\n	</tr>\n\n	<tr>\n		<td>heading_id</td>\n		<td>The (X)HTML/CSS id to apply to the month/year heading.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS id.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>heading_level</td>\n		<td>The level of (X)HTML heading element to use.</td>\n		<td>No</td>\n		<td><var>1</var> &#8211; <var>6</var> (h1 &#8211; h6)</td>\n		<td>Smart mode: <var>1</var> (h1 element)<br /><br />Full mode: <var>2</var> (h2 element)</td>\n	</tr>\n\n	<tr>\n		<td>include_date</td>\n		<td>Whether to list individual day dates.</td>\n		<td>No</td>\n		<td><var>yes</var> (outputs definition list) or <var>no</var> (outputs unordered list).\n		<td><var>yes</var></td>\n	</tr>\n\n	<tr>\n		<td>include_expired</td>\n		<td>Whether to include expired articles in the list.</td>\n		<td>No</td>\n		<td><var>yes</var> or <var>no</var></td>\n		<td>Admin preference \"Publish expired articles?\"</td>\n	</tr>\n\n	<tr>\n		<td>list_class</td>\n		<td>The (X)HTML/CSS class to apply to the article list.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS class name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>list_id</td>\n		<td>The (X)HTML/CSS id to apply to the article list.</td>\n		<td>No</td>\n		<td>Any valid (X)HTML/CSS id.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>mode</td>\n		<td>How to display the list.</td>\n		<td>No</td>\n		<td><var>smart</var> (each month list as requested) or <var>full</var> (complete list at once<sup><a href=\"#fn2\" id=\"ref2\">2</a></sup>)</td>\n		<td><var>smart</var></td>\n	</tr>\n\n	<tr>\n		<td>none_found</td>\n		<td>The name of the form to return if a request is made for a month where no articles exist.<sup><a href=\"#fn3\" id=\"ref3\">3</a></sup> This can be used to return a custom message to the visitor.<br /><br />The default message returned is:<br />&#8220;No posts could be found for the requested month.&#8221;<br /><br />Applies <strong>only</strong> when using the plugin in &#8220;smart mode&#8221;.</td>\n		<td>No</td>\n		<td>Any existing form name.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>section</td>\n		<td>The section(s) of articles to include in the archive. You can either include or exclude (using <var>exclude_section</var>), but not both at the same time.</td>\n		<td>No.</td>\n		<td>Any existing section name(s), comma-separated.</td>\n		<td>Current section</td>\n	</tr>\n\n	<tr>\n		<td>sort</td>\n		<td>How to sort the archive list.</td>\n		<td>No</td>\n		<td><var>the_year</var>, <var>the_month</var>, <var>the_date</var> (full timestamp).</td>\n		<td>\n			Smart mode: <var>the_date asc</var><br /><br />\n			Full mode: <var>the_year desc, the_month desc, the_date asc</var>.\n		</td>\n	</tr>\n\n	<tr>\n		<td>time</td>\n		<td>Restrict by date/time posted.</td>\n		<td>No</td>\n		<td><var>past</var>, <var>future</var> or <var>any</var></td>\n		<td><var>past</var></td>\n	</tr>\n\n</table>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n\n<h2 id=\"credit-section\">Credit and Developer Notes</h2>\n\n<p>Much thanks to Sencer for helping me out with working around Textpattern&#8217;s error handling, upon which the &#8220;smart mode&#8221; feature rests.</p>\n\n<p>While other plugins similar in focus or features can be found, such as <a href=\"http://www.wilshireone.com/textpattern-plugins/rss-suparchive\">Rob Sable&#8217;s rss_suparchive</a>, this plugin is an entirely original work, built from the bottom-up by myself.</p>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n<h2 id=\"footnote-section\">Footnotes</h2>\n\n<ol>\n	<li id=\"fn1\">Windows does not understand <var>%e</var>, so its default is <var>%d</var>. <a href=\"#ref1\" title=\"Jump back to footnote #1 reference\">&#8617;</a></li>\n\n	<li id=\"fn2\">I advise to avoid using &#8220;full&#8221; mode for very large archives. While I&#8217;ve taken care to keep the queries as efficient as possible, the larger you keep this list, the longer it will take to load it. Use your best judgement, and remember that I cannot be held responsible for improper use of the plugin (like calling it multiple times on one page). If in doubt about it, ask your host. <a href=\"#ref2\" title=\"Jump back to footnote #2 reference\">&#8617;</a></li>\n\n	<li id=\"fn3\">\n		<p>When listing articles in &#8220;smart mode&#8221;, no invalid urls are generated. However, in the event of a visitor attempting to view other archive dates, one could make a request for a month with no articles.</p>\n\n		<p>I decided against returning a 404 error, for a couple reasons:</p>\n\n		<p>One, Textpattern&#8217;s error handling is such that, in order for &#8220;smart mode&#8221; to work correctly, a plugin must &#8220;fool&#8221; Textpattern into thinking requests under certain circumstances are valid, existing documents. By the time the actual request for articles is made, the page has already started to render. Put simply: it means it is not simple to refine this process completely, and a plugin that works well everywhere else is better than one not existing at all.</p>\n\n		<p>Two, it can actually be more confusing and frustrating to the visitor.</p>\n\n		<p>I don&#8217;t have any plans to change this behaviour at present. <a href=\"#ref3\" title=\"Jump back to footnote #3 reference\">&#8617;</a></p>\n	</li>\n</ol>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>', 'upm_date_archive_install();\n\n	if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_date_archive\', \'1,2,3,6\');\n		register_tab(\'extensions\', \'upm_date_archive\', \'upm_date_archive\');\n		register_callback(\'upm_date_archive_prefs\', \'upm_date_archive\');\n	}\n\n	else\n	{\n		if ($GLOBALS[\'permlink_mode\'] != \'messy\')\n		{\n			register_callback(\'upm_date_archive_load\', \'textpattern\');\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_menu($atts)\n	{\n		global $prefs, $pretext;\n\n		extract(lAtts(array(\n			\'category\'						=> \'\',\n			\'class\'								=> \'\',\n			\'exclude_category\'		=> \'\',\n			\'exclude_section\'			=> \'\',\n			\'id\'									=> \'\',\n			\'include_expired\'     => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'insert_empty_months\' => \'yes\',\n			\'link_to\'							=> \'\',\n			\'month_format\'				=> \'%b\',\n			\'remain_linked\'				=> \'yes\',\n			\'section\'							=> $pretext[\'s\'],\n			\'show_article_count\'  => \'no\',\n			\'sort\'								=> \'the_year desc, the_month asc\',\n			\'time\'								=> \'past\',\n			\'year_format\'					=> \'%Y\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		if ($show_article_count == \'yes\')\n		{\n			$count_sql = \",count(id) as num\";\n		}\n\n		else\n		{\n			$count_sql = \'\';\n		}\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \"and Posted > now()\";\n			break;\n\n			default:\n				$time = \"and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\n			$count_sql\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql\n			group by date_format(date_add(Posted, interval \'$offset\' second), \'%Y-%m\')\n			order by \".doSlash($sort));\n\n		if ($rs)\n		{\n			$available_months = array(\n				1 => \'Jan 01\',\n				2 => \'Feb 01\',\n				3 => \'Mar 01\',\n				4 => \'Apr 01\',\n				5 => \'May 01\',\n				6 => \'Jun 01\',\n				7 => \'Jul 01\',\n				8 => \'Aug 01\',\n				9 => \'Sep 01\',\n				10 => \'Oct 01\',\n				11 => \'Nov 01\',\n				12 => \'Dec 01\'\n			);\n\n			$working_month = \'\';\n			$working_year = \'\';\n\n			$out = array();\n\n			// ----------------------------\n\n			while ($row = nextRow($rs))\n			{\n				extract($row);\n\n				$the_month = intval(ltrim($the_month, \'0\'));\n\n				if ($the_year != $working_year)\n				{\n					if ($insert_empty_months == \'yes\' and !empty($working_month) and !empty($working_year))\n					{\n						for ($i = $working_month + 1; $i <= 12; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					$out[] = n.\'<dt>\'.strftime($year_format, $the_date).\'</dt>\';\n\n					if ($insert_empty_months == \'yes\' and !empty($working_month) and $the_month != 1)\n					{\n						for ($i = 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					$working_year = $the_year;\n				}\n\n				if ($insert_empty_months == \'yes\')\n				{\n					if (empty($working_month))\n					{\n						for ($i = 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					elseif ($working_month + 1 != $the_month)\n					{\n						for ($i = $working_month + 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n				}\n\n				$what = strftime($month_format, $the_date);\n\n				if ($remain_linked == \'yes\')\n				{\n					if ($link_to == false)\n					{\n						$link_to = $pretext[\'s\'];\n					}\n\n					if ($link_to == \'default\')\n					{\n						$url = \'?month=\';\n					}\n\n					else\n					{\n						if ($prefs[\'permlink_mode\'] == \'messy\')\n						{\n							$url = \'?s=\'.$link_to.a.\'month=\';\n						}\n\n						else\n						{\n							$url = hu.$link_to.\'/\';\n						}\n					}\n\n					$what = \'<a href=\"\'.$url.strftime(\'%Y-%m\', $the_date).\'\">\'.$what.\'</a>\';\n				}\n\n				if (strftime(\'%Y-%m\', $the_date) == $pretext[\'month\'])\n				{\n					$out[] = n.t.\'<dd class=\"active\">\'.$what.($show_article_count == \'yes\' ? \" ($num)\" : \'\').\'</dd>\';\n				}\n\n				else\n				{\n					$out[] = n.t.\'<dd>\'.$what.($show_article_count == \'yes\' ? \" ($num)\" : \'\').\'</dd>\';\n				}\n\n				$working_month = $the_month;\n			}\n\n			// ----------------------------\n\n			if ($out)\n			{\n				if ($insert_empty_months == \'yes\' and $working_month < 12)\n				{\n					for ($i = $working_month + 1; $i <= 12; $i++)\n					{\n						$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n					}\n				}\n\n				$out = join(\'\', $out);\n\n				return doTag($out, \'dl\', $class, \'\', $id);\n			}\n		}\n\n		return;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive($atts, $thing = null)\n	{\n		// since gAtt is deprecated...\n		$mode = isset($atts[\'mode\']) ? $atts[\'mode\'] : \'smart\';\n\n		// prevent lAtts errors later on\n		unset($atts[\'mode\']);\n\n		switch ($mode)\n		{\n			case \'full\':\n				return upm_date_archive_full($atts, $thing);\n			break;\n\n			case \'smart\':\n			default:\n				return upm_date_archive_smart($atts, $thing);\n			break;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_smart($atts, $thing)\n	{\n		global $prefs, $pretext;\n\n		if ($pretext[\'month\'])\n		{\n			$month = $pretext[\'month\'];\n		}\n\n		else\n		{\n			return;\n		}\n\n		// ----------------------------\n\n		extract(lAtts(array(\n			\'category\'				 => \'\',\n			\'date_format\'			 => (is_numeric(strftime(\'%e\')) ? \'%e\' : \'%d\'),\n			\'exclude_category\' => \'\',\n			\'exclude_section\'	 => \'\',\n			\'form\'						 => \'\',\n			\'heading_class\'		 => \'\',\n			\'heading_format\'	 => \'%B %Y\',\n			\'heading_id\'			 => \'\',\n			\'heading_level\'		 => \'1\',\n			\'include_date\'		 => \'yes\',\n			\'include_expired\'  => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'list_class\'			 => \'\',\n			\'list_id\'					 => \'\',\n			\'none_found\'			 => \'\',\n			\'section\'					 => $pretext[\'s\'],\n			\'sort\'						 => \'the_date asc\',\n			\'time\'						 => \'past\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and Posted > now()\";\n			break;\n\n			case \'past\':\n			default:\n				$time = \" and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		list($year, $month) = explode(\'-\', $month);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod,\n			unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%d\') as the_day,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql and\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') = \'\".doSlash($year).\"\' and\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') = \'\".doSlash($month).\"\'\n			order by \".doSlash($sort));\n\n		if (!$rs)\n		{\n			return;\n		}\n\n		$post_count = numRows($rs);\n\n		if (!$post_count)\n		{\n			return n.graf(upm_date_archive_gTxt(\'no_month_archive\'));\n		}\n\n		// ----------------------------\n		// display results\n\n		$heading_id = ($heading_id) ? \' id=\"\'.$heading_id.\'\"\' : \'\';\n		$heading_class = ($heading_class) ? \' class=\"\'.$heading_class.\'\"\' : \'\';\n\n		$list_type = ($include_date == \'yes\') ? \'dl\' : \'ul\';\n		$list_id = ($list_id) ? \' id=\"\'.$list_id.\'\"\' : \'\';\n		$list_class = ($list_class) ? \' class=\"\'.$list_class.\'\"\' : \'\';\n\n		if ($form)\n		{\n			$what = fetch_form($form);\n		}\n\n		elseif ($thing)\n		{\n			$what = $thing;\n		}\n\n		$i = 0;\n\n		$current_day = \'\';\n\n		while ($row = nextRow($rs))\n		{\n			extract($row);\n\n			if ($include_date == \'yes\' && $the_day != $current_day)\n			{\n				$out[] = n.\'<dt>\'.strftime($date_format, $the_date).\'</dt>\';\n\n				$current_day = $the_day;\n			}\n\n			if ($form or $thing)\n			{\n				++$i;\n\n				$row[\'uPosted\'] = $the_date;\n\n				populateArticleData($row);\n\n				$GLOBALS[\'thisarticle\'][\'is_first\'] = ($i == 1);\n				$GLOBALS[\'thisarticle\'][\'is_last\'] = ($i == $post_count);\n\n				$contents = parse($what);\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			else\n			{\n				$contents = \'<a rel=\"bookmark\" href=\"\'.permlinkurl_id($ID).\'\">\'.$Title.\'</a>\';\n			}\n\n			$out[] = ($include_date == \'yes\') ?\n				\'<dd>\'.$contents.\'</dd>\' :\n				\'<li>\'.$contents.\'</li>\';\n		}\n\n		return n.tag(strftime($heading_format, $the_date), \'h\'.$heading_level, $heading_id.$heading_class).\n			n.n.\'<\'.$list_type.$list_id.$list_class.\'>\'.\n			n.t.join(n.t, $out).\n			n.\'</\'.$list_type.\'>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_full($atts, $thing)\n	{\n		global $prefs, $pretext;\n\n		extract(lAtts(array(\n			\'category\'				 => \'\',\n			\'date_format\'			 => (is_numeric(strftime(\'%e\')) ? \'%e\' : \'%d\'),\n			\'exclude_category\' => \'\',\n			\'exclude_section\'	 => \'\',\n			\'form\'						 => \'\',\n			\'heading_class\'		 => \'\',\n			\'heading_format\'	 => \'%B %Y\',\n			\'heading_id\'			 => \'\',\n			\'heading_level\'		 => 2,\n			\'include_date\'		 => \'yes\',\n			\'include_expired\'  => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'list_class\'			 => \'\',\n			\'list_id\'					 => \'\',\n			\'section\'					 => $pretext[\'s\'],\n			\'sort\'						 => \'the_year desc, the_month desc, the_date asc\',\n			\'time\'						 => \'past\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and Posted > now()\";\n			break;\n\n			default:\n				$time = \" and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod,\n			unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%d\') as the_day,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m %Y\') as month_year,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql\n			order by \".doSlash($sort));\n\n		if (!$rs)\n		{\n			return;\n		}\n\n		$post_count = numRows($rs);\n\n		if (!$post_count)\n		{\n			return;\n		}\n\n		// ----------------------------\n		// display results\n\n		$heading_id = ($heading_id) ? \' id=\"\'.$heading_id.\'\"\' : \'\';\n		$heading_class = ($heading_class) ? \' class=\"\'.$heading_class.\'\"\' : \'\';\n\n		$list_id = ($list_id) ? \' id=\"\'.$list_id.\'\"\' : \'\';\n		$list_class = ($list_class) ? \' class=\"\'.$list_class.\'\"\' : \'\';\n\n		if ($form)\n		{\n			$what = fetch_form($form);\n		}\n\n		elseif ($thing)\n		{\n			$what = $thing;\n		}\n\n		$out = array();\n\n		$current_month = \'\';\n		$current_day = \'\';\n\n		$i = 0;\n\n		while ($row = nextRow($rs))\n		{\n			extract($row);\n\n			if ($month_year != $current_month)\n			{\n				if (!empty($current_month))\n				{\n					$out[] = ($include_date == \'yes\') ?\n						n.\'</dl>\' :\n						n.\'</ul>\';\n				}\n\n				$out[] = n.n.tag(strftime($heading_format, $the_date), \'h\'.$heading_level, $heading_id.$heading_class);\n\n				$out[] = ($include_date == \'yes\') ?\n					n.n.\'<dl\'.$list_id.$list_class.\'>\' :\n					n.n.\'<ul\'.$list_id.$list_class.\'>\';\n\n				$current_month = $month_year;\n			}\n\n			if ($include_date == \'yes\')\n			{\n				if ($the_day != $current_day)\n				{\n					$out[] = n.t.\'<dt>\'.strftime($date_format, $the_date).\'</dt>\';\n\n					$current_day = $the_day;\n				}\n			}\n\n			if ($form or $thing)\n			{\n				++$i;\n\n				$row[\'uPosted\'] = $the_date;\n\n				populateArticleData($row);\n\n				$GLOBALS[\'thisarticle\'][\'is_first\'] = ($i == 1);\n				$GLOBALS[\'thisarticle\'][\'is_last\'] = ($i == $post_count);\n\n				$contents = parse($what);\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			else\n			{\n				$contents = \'<a rel=\"bookmark\" href=\"\'.permlinkurl_id($ID).\'\">\'.$Title.\'</a>\';\n			}\n\n			$out[] = ($include_date == \'yes\') ?\n				n.t.\'<dd>\'.$contents.\'</dd>\' :\n				n.t.\'<li>\'.$contents.\'</li>\';\n		}\n\n		return ($include_date == \'yes\') ?\n			join(\'\', $out).n.\'</dl>\' :\n			join(\'\', $out).n.\'</ul>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category)\n	{\n		// ----- sections\n\n		$section_sql = \'\';\n\n		if ($exclude_section)\n		{\n			if ($exclude_section == \'default\')\n			{\n				$exclude_section = safe_column(\'name\', \'txp_section\', \"on_frontpage = \'1\'\");\n			}\n\n			else\n			{\n				$exclude_section = do_list($exclude_section);\n			}\n\n			$exclude_section = join(\"\',\'\", doSlash($exclude_section));\n\n			$section_sql = \"and Section not in(\'$exclude_section\')\";\n		}\n\n		elseif ($section)\n		{\n			if ($section == \'default\')\n			{\n				$section = safe_column(\'name\', \'txp_section\', \"on_frontpage = \'1\'\");\n			}\n\n			else\n			{\n				$section = do_list($section);\n			}\n\n			$section = join(\"\',\'\", doSlash($section));\n\n			$section_sql = \"and Section in(\'$section\')\";\n		}\n\n		// ----- categories\n\n		$category_sql = \'\';\n\n		if ($exclude_category)\n		{\n			$exclude_category = join(\"\',\'\", doSlash(do_list($exclude_category)));\n\n			$category_sql = \"and (Category1 not in(\'$exclude_category\') and Category2 not in(\'$exclude_category\'))\";\n		}\n\n		elseif ($category)\n		{\n			$category = join(\"\',\'\", doSlash(do_list($category)));\n\n			$category_sql = \"and (Category1 in(\'$category\') or Category2 in(\'$category\'))\";\n		}\n\n		return array($section_sql, $category_sql);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_load()\n	{\n		global $prefs, $pretext, $s, $month, $status, $page;\n\n		if (empty($prefs[\'upm_date_archive_listen\']))\n		{\n			return;\n		}\n\n		$sections = unserialize($prefs[\'upm_date_archive_listen\']);\n\n		if (empty($sections))\n		{\n			return;\n		}\n\n		if ($pretext[\'status\'] != \'404\')\n		{\n			return;\n		}\n\n		$request_uri = preg_replace(\"|^https?://[^/]+|i\", \'\', serverSet(\'REQUEST_URI\'));\n\n		if (!$request_uri and serverSet(\'SCRIPT_NAME\'))\n		{\n			$request_uri = serverSet(\'SCRIPT_NAME\').(serverSet(\'QUERY_STRING\') ? \'?\'.serverSet(\'QUERY_STRING\') : \'\');\n		}\n\n		if (!$request_uri and $argv = serverSet(\'argv\'))\n		{\n			$request_uri = @substr($argv[0], strpos($argv[0], \';\') + 1);\n		}\n\n		$subpath = preg_quote(preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\", \"$1\", hu), \'/\');\n\n		$req = preg_replace(\"/^$subpath/i\", \'/\', $request_uri);\n\n		$req = chopUrl($req);\n\n		if (!$pretext[\'id\'] and in_array($req[\'u1\'], $sections))\n		{\n			$pretext[\'status\'] = $status = \'200\';\n\n			$pretext[\'s\'] = $s = $req[\'u1\'];\n\n			$rs = safe_row(\'page\', \'txp_section\', \"name = \'\".doSlash($pretext[\'s\']).\"\' limit 1\");\n			$pretext[\'page\'] = $page = @$rs[\'page\'];\n\n			if ($req[\'u2\'] and preg_match(\'/^[0-9]{4}-{1}[0-9]{2}$/\', $req[\'u2\']))\n			{\n				$pretext[\'month\'] = $month = $req[\'u2\'];\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_prefs($event, $step)\n	{\n		global $prefs;\n\n		pagetop(upm_date_archive_gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		echo <<<css\n\n<style type=\"text/css\" media=\"screen,projection\">\n<!--\n\n#upm_date_archive {\nmargin: 3em auto auto auto;\nwidth: 20em;\n}\n\n#upm_date_archive ul {\nmargin: 0 auto 15px auto;\npadding: 0;\nwidth: 14em;\nlist-style: none none;\n}\n\n#upm_date_archive li {\nmargin: 0 0 5px 0;\npadding: 0;\n}\n\n#upm_date_archive li input {\nmargin: 0;\npadding: 0;\nvertical-align: middle;\n}\n\n-->\n</style>\n\ncss;\n\n		if ($step == \'update\')\n		{\n			$sections = gps(\'sections\');\n\n			if (empty($sections))\n			{\n				$sections = array();\n			}\n\n			else\n			{\n				$sections = doSlash($sections);\n			}\n\n			$sections = serialize($sections);\n\n			safe_update(\'txp_prefs\', \"val = \'$sections\'\", \"name = \'upm_date_archive_listen\'\");\n\n			$prefs[\'upm_date_archive_listen\'] = $sections;\n		}\n\n		$sections = unserialize($prefs[\'upm_date_archive_listen\']);\n\n		echo n.n.\'<div id=\"upm_date_archive\">\'.\n			n.n.hed(upm_date_archive_gTxt(\'prefs\'), \'1\');\n\n		$rs = safe_rows_start(\'name\', \'txp_section\', \"name != \'default\' order by name asc\");\n\n		if ($rs)\n		{\n			$out = array();\n\n			while ($row = nextRow($rs))\n			{\n				extract($row);\n\n				$temp = n.t.\'<li><label><input type=\"checkbox\" name=\"sections[]\" value=\"\'.$name.\'\"\';\n\n				if (is_array($sections) and in_array($name, $sections))\n				{\n					$temp .= \' checked=\"checked\"\';\n				}\n\n				$temp .= \' /> \'.$name.\'</label></li>\';\n\n				$out[] = $temp;\n			}\n\n			if ($out)\n			{\n				echo n.n.form(\n					n.eInput(\'upm_date_archive\').\n					n.sInput(\'update\').\n\n					n.n.\'<ul>\'.\n						join(\'\', $out).\n					n.\'</ul>\'.\n\n					n.n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n				);\n			}\n		}\n\n		echo n.n.\'</div>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_install()\n	{\n		global $prefs;\n\n		if (!isset($prefs[\'upm_date_archive_listen\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_date_archive_listen\',\n				val = \'\',\n				html = \'text_input\',\n				prefs_id = 1,\n				type = 2,\n				event = \'publish\',\n				position = 0\n			\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'no_month_archive\' => \'No articles could be found for the requested month.\',\n			\'prefs\'						 => \'upm_date_archive Preferences\'\n		);\n\n		return strtr($lang[$what], $atts);\n	}', 'upm_date_archive_install();\n\n	if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_date_archive\', \'1,2,3,6\');\n		register_tab(\'extensions\', \'upm_date_archive\', \'upm_date_archive\');\n		register_callback(\'upm_date_archive_prefs\', \'upm_date_archive\');\n	}\n\n	else\n	{\n		if ($GLOBALS[\'permlink_mode\'] != \'messy\')\n		{\n			register_callback(\'upm_date_archive_load\', \'textpattern\');\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_menu($atts)\n	{\n		global $prefs, $pretext;\n\n		extract(lAtts(array(\n			\'category\'						=> \'\',\n			\'class\'								=> \'\',\n			\'exclude_category\'		=> \'\',\n			\'exclude_section\'			=> \'\',\n			\'id\'									=> \'\',\n			\'include_expired\'     => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'insert_empty_months\' => \'yes\',\n			\'link_to\'							=> \'\',\n			\'month_format\'				=> \'%b\',\n			\'remain_linked\'				=> \'yes\',\n			\'section\'							=> $pretext[\'s\'],\n			\'show_article_count\'  => \'no\',\n			\'sort\'								=> \'the_year desc, the_month asc\',\n			\'time\'								=> \'past\',\n			\'year_format\'					=> \'%Y\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		if ($show_article_count == \'yes\')\n		{\n			$count_sql = \",count(id) as num\";\n		}\n\n		else\n		{\n			$count_sql = \'\';\n		}\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \"and Posted > now()\";\n			break;\n\n			default:\n				$time = \"and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\n			$count_sql\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql\n			group by date_format(date_add(Posted, interval \'$offset\' second), \'%Y-%m\')\n			order by \".doSlash($sort));\n\n		if ($rs)\n		{\n			$available_months = array(\n				1 => \'Jan 01\',\n				2 => \'Feb 01\',\n				3 => \'Mar 01\',\n				4 => \'Apr 01\',\n				5 => \'May 01\',\n				6 => \'Jun 01\',\n				7 => \'Jul 01\',\n				8 => \'Aug 01\',\n				9 => \'Sep 01\',\n				10 => \'Oct 01\',\n				11 => \'Nov 01\',\n				12 => \'Dec 01\'\n			);\n\n			$working_month = \'\';\n			$working_year = \'\';\n\n			$out = array();\n\n			// ----------------------------\n\n			while ($row = nextRow($rs))\n			{\n				extract($row);\n\n				$the_month = intval(ltrim($the_month, \'0\'));\n\n				if ($the_year != $working_year)\n				{\n					if ($insert_empty_months == \'yes\' and !empty($working_month) and !empty($working_year))\n					{\n						for ($i = $working_month + 1; $i <= 12; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					$out[] = n.\'<dt>\'.strftime($year_format, $the_date).\'</dt>\';\n\n					if ($insert_empty_months == \'yes\' and !empty($working_month) and $the_month != 1)\n					{\n						for ($i = 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					$working_year = $the_year;\n				}\n\n				if ($insert_empty_months == \'yes\')\n				{\n					if (empty($working_month))\n					{\n						for ($i = 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n\n					elseif ($working_month + 1 != $the_month)\n					{\n						for ($i = $working_month + 1; $i < $the_month; $i++)\n						{\n							$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n						}\n					}\n				}\n\n				$what = strftime($month_format, $the_date);\n\n				if ($remain_linked == \'yes\')\n				{\n					if ($link_to == false)\n					{\n						$link_to = $pretext[\'s\'];\n					}\n\n					if ($link_to == \'default\')\n					{\n						$url = \'?month=\';\n					}\n\n					else\n					{\n						if ($prefs[\'permlink_mode\'] == \'messy\')\n						{\n							$url = \'?s=\'.$link_to.a.\'month=\';\n						}\n\n						else\n						{\n							$url = hu.$link_to.\'/\';\n						}\n					}\n\n					$what = \'<a href=\"\'.$url.strftime(\'%Y-%m\', $the_date).\'\">\'.$what.\'</a>\';\n				}\n\n				if (strftime(\'%Y-%m\', $the_date) == $pretext[\'month\'])\n				{\n					$out[] = n.t.\'<dd class=\"active\">\'.$what.($show_article_count == \'yes\' ? \" ($num)\" : \'\').\'</dd>\';\n				}\n\n				else\n				{\n					$out[] = n.t.\'<dd>\'.$what.($show_article_count == \'yes\' ? \" ($num)\" : \'\').\'</dd>\';\n				}\n\n				$working_month = $the_month;\n			}\n\n			// ----------------------------\n\n			if ($out)\n			{\n				if ($insert_empty_months == \'yes\' and $working_month < 12)\n				{\n					for ($i = $working_month + 1; $i <= 12; $i++)\n					{\n						$out[] = n.t.\'<dd>\'.strftime($month_format, strtotime($available_months[$i].\' \'.$the_year)).\'</dd>\';\n					}\n				}\n\n				$out = join(\'\', $out);\n\n				return doTag($out, \'dl\', $class, \'\', $id);\n			}\n		}\n\n		return;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive($atts, $thing = null)\n	{\n		// since gAtt is deprecated...\n		$mode = isset($atts[\'mode\']) ? $atts[\'mode\'] : \'smart\';\n\n		// prevent lAtts errors later on\n		unset($atts[\'mode\']);\n\n		switch ($mode)\n		{\n			case \'full\':\n				return upm_date_archive_full($atts, $thing);\n			break;\n\n			case \'smart\':\n			default:\n				return upm_date_archive_smart($atts, $thing);\n			break;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_smart($atts, $thing)\n	{\n		global $prefs, $pretext;\n\n		if ($pretext[\'month\'])\n		{\n			$month = $pretext[\'month\'];\n		}\n\n		else\n		{\n			return;\n		}\n\n		// ----------------------------\n\n		extract(lAtts(array(\n			\'category\'				 => \'\',\n			\'date_format\'			 => (is_numeric(strftime(\'%e\')) ? \'%e\' : \'%d\'),\n			\'exclude_category\' => \'\',\n			\'exclude_section\'	 => \'\',\n			\'form\'						 => \'\',\n			\'heading_class\'		 => \'\',\n			\'heading_format\'	 => \'%B %Y\',\n			\'heading_id\'			 => \'\',\n			\'heading_level\'		 => \'1\',\n			\'include_date\'		 => \'yes\',\n			\'include_expired\'  => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'list_class\'			 => \'\',\n			\'list_id\'					 => \'\',\n			\'none_found\'			 => \'\',\n			\'section\'					 => $pretext[\'s\'],\n			\'sort\'						 => \'the_date asc\',\n			\'time\'						 => \'past\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and Posted > now()\";\n			break;\n\n			case \'past\':\n			default:\n				$time = \" and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		list($year, $month) = explode(\'-\', $month);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod,\n			unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%d\') as the_day,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql and\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') = \'\".doSlash($year).\"\' and\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') = \'\".doSlash($month).\"\'\n			order by \".doSlash($sort));\n\n		if (!$rs)\n		{\n			return;\n		}\n\n		$post_count = numRows($rs);\n\n		if (!$post_count)\n		{\n			return n.graf(upm_date_archive_gTxt(\'no_month_archive\'));\n		}\n\n		// ----------------------------\n		// display results\n\n		$heading_id = ($heading_id) ? \' id=\"\'.$heading_id.\'\"\' : \'\';\n		$heading_class = ($heading_class) ? \' class=\"\'.$heading_class.\'\"\' : \'\';\n\n		$list_type = ($include_date == \'yes\') ? \'dl\' : \'ul\';\n		$list_id = ($list_id) ? \' id=\"\'.$list_id.\'\"\' : \'\';\n		$list_class = ($list_class) ? \' class=\"\'.$list_class.\'\"\' : \'\';\n\n		if ($form)\n		{\n			$what = fetch_form($form);\n		}\n\n		elseif ($thing)\n		{\n			$what = $thing;\n		}\n\n		$i = 0;\n\n		$current_day = \'\';\n\n		while ($row = nextRow($rs))\n		{\n			extract($row);\n\n			if ($include_date == \'yes\' && $the_day != $current_day)\n			{\n				$out[] = n.\'<dt>\'.strftime($date_format, $the_date).\'</dt>\';\n\n				$current_day = $the_day;\n			}\n\n			if ($form or $thing)\n			{\n				++$i;\n\n				$row[\'uPosted\'] = $the_date;\n\n				populateArticleData($row);\n\n				$GLOBALS[\'thisarticle\'][\'is_first\'] = ($i == 1);\n				$GLOBALS[\'thisarticle\'][\'is_last\'] = ($i == $post_count);\n\n				$contents = parse($what);\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			else\n			{\n				$contents = \'<a rel=\"bookmark\" href=\"\'.permlinkurl_id($ID).\'\">\'.$Title.\'</a>\';\n			}\n\n			$out[] = ($include_date == \'yes\') ?\n				\'<dd>\'.$contents.\'</dd>\' :\n				\'<li>\'.$contents.\'</li>\';\n		}\n\n		return n.tag(strftime($heading_format, $the_date), \'h\'.$heading_level, $heading_id.$heading_class).\n			n.n.\'<\'.$list_type.$list_id.$list_class.\'>\'.\n			n.t.join(n.t, $out).\n			n.\'</\'.$list_type.\'>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_full($atts, $thing)\n	{\n		global $prefs, $pretext;\n\n		extract(lAtts(array(\n			\'category\'				 => \'\',\n			\'date_format\'			 => (is_numeric(strftime(\'%e\')) ? \'%e\' : \'%d\'),\n			\'exclude_category\' => \'\',\n			\'exclude_section\'	 => \'\',\n			\'form\'						 => \'\',\n			\'heading_class\'		 => \'\',\n			\'heading_format\'	 => \'%B %Y\',\n			\'heading_id\'			 => \'\',\n			\'heading_level\'		 => 2,\n			\'include_date\'		 => \'yes\',\n			\'include_expired\'  => ($prefs[\'publish_expired_articles\'] ? \'yes\' : \'no\'),\n			\'list_class\'			 => \'\',\n			\'list_id\'					 => \'\',\n			\'section\'					 => $pretext[\'s\'],\n			\'sort\'						 => \'the_year desc, the_month desc, the_date asc\',\n			\'time\'						 => \'past\'\n		), $atts));\n\n		// ----------------------------\n		// prepare query\n\n		$offset = tz_offset();\n\n		switch ($time)\n		{\n			case \'any\':\n				$time = \'\';\n			break;\n\n			case \'future\':\n				$time = \" and Posted > now()\";\n			break;\n\n			default:\n				$time = \" and Posted <= now()\";\n			break;\n		}\n\n		if ($include_expired == \'no\')\n		{\n			$time .= \" and (now() <= Expires or Expires = \".NULLDATETIME.\")\";\n		}\n\n		list($section_sql, $category_sql) = upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category);\n\n		// ----------------------------\n\n		$rs = safe_rows_start(\"*, unix_timestamp(Posted) as uPosted, unix_timestamp(Expires) as uExpires, unix_timestamp(LastMod) as uLastMod,\n			unix_timestamp(date_add(Posted, interval \'$offset\' second)) as the_date,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%d\') as the_day,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m\') as the_month,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%m %Y\') as month_year,\n			date_format(date_add(Posted, interval \'$offset\' second), \'%Y\') as the_year\",\n			\'textpattern\',\n			\"Status = 4\n			$time\n			$section_sql $category_sql\n			order by \".doSlash($sort));\n\n		if (!$rs)\n		{\n			return;\n		}\n\n		$post_count = numRows($rs);\n\n		if (!$post_count)\n		{\n			return;\n		}\n\n		// ----------------------------\n		// display results\n\n		$heading_id = ($heading_id) ? \' id=\"\'.$heading_id.\'\"\' : \'\';\n		$heading_class = ($heading_class) ? \' class=\"\'.$heading_class.\'\"\' : \'\';\n\n		$list_id = ($list_id) ? \' id=\"\'.$list_id.\'\"\' : \'\';\n		$list_class = ($list_class) ? \' class=\"\'.$list_class.\'\"\' : \'\';\n\n		if ($form)\n		{\n			$what = fetch_form($form);\n		}\n\n		elseif ($thing)\n		{\n			$what = $thing;\n		}\n\n		$out = array();\n\n		$current_month = \'\';\n		$current_day = \'\';\n\n		$i = 0;\n\n		while ($row = nextRow($rs))\n		{\n			extract($row);\n\n			if ($month_year != $current_month)\n			{\n				if (!empty($current_month))\n				{\n					$out[] = ($include_date == \'yes\') ?\n						n.\'</dl>\' :\n						n.\'</ul>\';\n				}\n\n				$out[] = n.n.tag(strftime($heading_format, $the_date), \'h\'.$heading_level, $heading_id.$heading_class);\n\n				$out[] = ($include_date == \'yes\') ?\n					n.n.\'<dl\'.$list_id.$list_class.\'>\' :\n					n.n.\'<ul\'.$list_id.$list_class.\'>\';\n\n				$current_month = $month_year;\n			}\n\n			if ($include_date == \'yes\')\n			{\n				if ($the_day != $current_day)\n				{\n					$out[] = n.t.\'<dt>\'.strftime($date_format, $the_date).\'</dt>\';\n\n					$current_day = $the_day;\n				}\n			}\n\n			if ($form or $thing)\n			{\n				++$i;\n\n				$row[\'uPosted\'] = $the_date;\n\n				populateArticleData($row);\n\n				$GLOBALS[\'thisarticle\'][\'is_first\'] = ($i == 1);\n				$GLOBALS[\'thisarticle\'][\'is_last\'] = ($i == $post_count);\n\n				$contents = parse($what);\n\n				unset($GLOBALS[\'thisarticle\']);\n			}\n\n			else\n			{\n				$contents = \'<a rel=\"bookmark\" href=\"\'.permlinkurl_id($ID).\'\">\'.$Title.\'</a>\';\n			}\n\n			$out[] = ($include_date == \'yes\') ?\n				n.t.\'<dd>\'.$contents.\'</dd>\' :\n				n.t.\'<li>\'.$contents.\'</li>\';\n		}\n\n		return ($include_date == \'yes\') ?\n			join(\'\', $out).n.\'</dl>\' :\n			join(\'\', $out).n.\'</ul>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_query_build($section, $exclude_section, $category, $exclude_category)\n	{\n		// ----- sections\n\n		$section_sql = \'\';\n\n		if ($exclude_section)\n		{\n			if ($exclude_section == \'default\')\n			{\n				$exclude_section = safe_column(\'name\', \'txp_section\', \"on_frontpage = \'1\'\");\n			}\n\n			else\n			{\n				$exclude_section = do_list($exclude_section);\n			}\n\n			$exclude_section = join(\"\',\'\", doSlash($exclude_section));\n\n			$section_sql = \"and Section not in(\'$exclude_section\')\";\n		}\n\n		elseif ($section)\n		{\n			if ($section == \'default\')\n			{\n				$section = safe_column(\'name\', \'txp_section\', \"on_frontpage = \'1\'\");\n			}\n\n			else\n			{\n				$section = do_list($section);\n			}\n\n			$section = join(\"\',\'\", doSlash($section));\n\n			$section_sql = \"and Section in(\'$section\')\";\n		}\n\n		// ----- categories\n\n		$category_sql = \'\';\n\n		if ($exclude_category)\n		{\n			$exclude_category = join(\"\',\'\", doSlash(do_list($exclude_category)));\n\n			$category_sql = \"and (Category1 not in(\'$exclude_category\') and Category2 not in(\'$exclude_category\'))\";\n		}\n\n		elseif ($category)\n		{\n			$category = join(\"\',\'\", doSlash(do_list($category)));\n\n			$category_sql = \"and (Category1 in(\'$category\') or Category2 in(\'$category\'))\";\n		}\n\n		return array($section_sql, $category_sql);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_load()\n	{\n		global $prefs, $pretext, $s, $month, $status, $page;\n\n		if (empty($prefs[\'upm_date_archive_listen\']))\n		{\n			return;\n		}\n\n		$sections = unserialize($prefs[\'upm_date_archive_listen\']);\n\n		if (empty($sections))\n		{\n			return;\n		}\n\n		if ($pretext[\'status\'] != \'404\')\n		{\n			return;\n		}\n\n		$request_uri = preg_replace(\"|^https?://[^/]+|i\", \'\', serverSet(\'REQUEST_URI\'));\n\n		if (!$request_uri and serverSet(\'SCRIPT_NAME\'))\n		{\n			$request_uri = serverSet(\'SCRIPT_NAME\').(serverSet(\'QUERY_STRING\') ? \'?\'.serverSet(\'QUERY_STRING\') : \'\');\n		}\n\n		if (!$request_uri and $argv = serverSet(\'argv\'))\n		{\n			$request_uri = @substr($argv[0], strpos($argv[0], \';\') + 1);\n		}\n\n		$subpath = preg_quote(preg_replace(\"/https?:\\/\\/.*(\\/.*)/Ui\", \"$1\", hu), \'/\');\n\n		$req = preg_replace(\"/^$subpath/i\", \'/\', $request_uri);\n\n		$req = chopUrl($req);\n\n		if (!$pretext[\'id\'] and in_array($req[\'u1\'], $sections))\n		{\n			$pretext[\'status\'] = $status = \'200\';\n\n			$pretext[\'s\'] = $s = $req[\'u1\'];\n\n			$rs = safe_row(\'page\', \'txp_section\', \"name = \'\".doSlash($pretext[\'s\']).\"\' limit 1\");\n			$pretext[\'page\'] = $page = @$rs[\'page\'];\n\n			if ($req[\'u2\'] and preg_match(\'/^[0-9]{4}-{1}[0-9]{2}$/\', $req[\'u2\']))\n			{\n				$pretext[\'month\'] = $month = $req[\'u2\'];\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_prefs($event, $step)\n	{\n		global $prefs;\n\n		pagetop(upm_date_archive_gTxt(\'prefs\'), ($step == \'update\' ? gTxt(\'preferences_saved\') : \'\'));\n\n		echo <<<css\n\n<style type=\"text/css\" media=\"screen,projection\">\n<!--\n\n#upm_date_archive {\nmargin: 3em auto auto auto;\nwidth: 20em;\n}\n\n#upm_date_archive ul {\nmargin: 0 auto 15px auto;\npadding: 0;\nwidth: 14em;\nlist-style: none none;\n}\n\n#upm_date_archive li {\nmargin: 0 0 5px 0;\npadding: 0;\n}\n\n#upm_date_archive li input {\nmargin: 0;\npadding: 0;\nvertical-align: middle;\n}\n\n-->\n</style>\n\ncss;\n\n		if ($step == \'update\')\n		{\n			$sections = gps(\'sections\');\n\n			if (empty($sections))\n			{\n				$sections = array();\n			}\n\n			else\n			{\n				$sections = doSlash($sections);\n			}\n\n			$sections = serialize($sections);\n\n			safe_update(\'txp_prefs\', \"val = \'$sections\'\", \"name = \'upm_date_archive_listen\'\");\n\n			$prefs[\'upm_date_archive_listen\'] = $sections;\n		}\n\n		$sections = unserialize($prefs[\'upm_date_archive_listen\']);\n\n		echo n.n.\'<div id=\"upm_date_archive\">\'.\n			n.n.hed(upm_date_archive_gTxt(\'prefs\'), \'1\');\n\n		$rs = safe_rows_start(\'name\', \'txp_section\', \"name != \'default\' order by name asc\");\n\n		if ($rs)\n		{\n			$out = array();\n\n			while ($row = nextRow($rs))\n			{\n				extract($row);\n\n				$temp = n.t.\'<li><label><input type=\"checkbox\" name=\"sections[]\" value=\"\'.$name.\'\"\';\n\n				if (is_array($sections) and in_array($name, $sections))\n				{\n					$temp .= \' checked=\"checked\"\';\n				}\n\n				$temp .= \' /> \'.$name.\'</label></li>\';\n\n				$out[] = $temp;\n			}\n\n			if ($out)\n			{\n				echo n.n.form(\n					n.eInput(\'upm_date_archive\').\n					n.sInput(\'update\').\n\n					n.n.\'<ul>\'.\n						join(\'\', $out).\n					n.\'</ul>\'.\n\n					n.n.fInput(\'submit\', \'update\', \'Update\', \'smallerbox\')\n				);\n			}\n		}\n\n		echo n.n.\'</div>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_install()\n	{\n		global $prefs;\n\n		if (!isset($prefs[\'upm_date_archive_listen\']))\n		{\n			safe_insert(\'txp_prefs\', \"\n				name = \'upm_date_archive_listen\',\n				val = \'\',\n				html = \'text_input\',\n				prefs_id = 1,\n				type = 2,\n				event = \'publish\',\n				position = 0\n			\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_date_archive_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'no_month_archive\' => \'No articles could be found for the requested month.\',\n			\'prefs\'						 => \'upm_date_archive Preferences\'\n		);\n\n		return strtr($lang[$what], $atts);\n	}', '3b10e7156724c7c7aa8c56bc9a890e65', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('upm_quicktags', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-quicktags', '0.5', 'Implement Alex King\'s Quicktags for articles, forms and pages.', '<style type=\"text/css\" media=\"screen,projection\">\n<!--\npre, code {\nbackground-color: #eee;\n}\n-->\n</style>\n\n<h1>upm_quicktags</h1>\n\n<p>This plugin is comprised of a folder labelled &#8220;upm_quicktags&#8221;, along with the plugin code\nitself. Since you&#8217;ve already installed this plugin, make sure that the &#8220;upm_quicktags&#8221;\nfolder is sitting inside the &#8220;textpattern&#8221; admin folder, and that you have made the plugin\nactive.</p>\n\n<h2>Usage</h2>\n\n<p>upm_quicktags can output any kind of text, be it Textile syntax, Textpattern tags, HTML, or other\nbits of text you use often.</p>\n\n<p>The plugin ships with the buttons configured to return HTML. Using HTML in Textpattern articles/posts\n(&#8220;write&#8221; tab) usually means disabling, adjusting, or escaping Textile. To stop Textile\nfrom wrapping paragraph tags around a line, you need to start the line with a space. The default buttons\nfor the &#8220;write&#8221; tab have been adjusted accordingly. When running the plugin with the default\nsettings, it should return valid xhtml markup, that still plays well with Textile.</p>\n\n<p>It is <strong>not</strong> currently possible to switch upm_quicktags on and off per user.</p>\n\n<h2>Further Customization (Optional)</h2>\n\n<p>Quicktags allows easy customization of its buttons, and this plugin allows you to maintain a different\nset of buttons for articles (write.js), forms (forms.js), and pages (pages.js). Additionally, you are\nprovided with a stylesheet (style.css), which will allow you to customize the look of your buttons and\ntoolbar. Together, these four files (found inside the &#8220;upm_quicktags&#8221; folder) are a powerful\ncombination.</p>\n\n<h3>Button Template</h3>\n\n<pre><code>edButtons[edButtons.length] = new edButton(id, label, tagStart, tagEnd, tooltip);\n</code></pre>\n\n<h3>Button Example</h3>\n\n<pre><code>edButtons[edButtons.length] = new edButton(\'qt-str\', \'str\', \'<strong>\', \'</strong>\', \'Strongly Emphasize\');\n</code></pre>\n\n<h3>Button Argument Explanation</h3>\n\n<table cellpadding=\"2\" cellspacing=\"2\">\n	<tr>\n		<th>id</th>\n		<td>unique id (used internally by Quicktags), any text you wish (be careful about special characters\n		or really long strings)</td>\n	</tr>\n\n	<tr>\n		<th>label</th>\n		<td>any text you wish</td>\n	</tr>\n\n	<tr>\n		<th>tagStart</th>\n		<td>Opening tag, any text or html you wish (does not have to be strictly a single html tag). May be\n		left empty.</td>\n	</tr>\n\n	<tr>\n		<th>tagEnd</th>\n		<td>Closing tag, any text or html you wish (does not have to be strictly a single html tag). May be\n		left empty.</td>\n	</tr>\n\n	<tr>\n		<th>tooltip</th>\n		<td>Text that appears when the mouse cursor hovers over the button. May be left empty.</td>\n	</tr>\n</table>\n\n<p>Adding an image instead of a button is very simple, and uses nearly identical settings.</p>\n\n<h3>Image Button Template</h3>\n\n<pre><code>edImgButtons[edImgButtons.length] = new edImgButton(id, src, tagStart, tagEnd, tooltip);\n</code></pre>\n\n<h3>Image Button Example</h3>\n\n<pre><code>edImgButtons[edImgButtons.length] = new edImgButton(\'qt-bold\', \'bold.gif\', \'<strong>\', \'</strong>\', \'Bold\');\n</code></pre>\n\n<h3>Button Argument Explanation</h3>\n\n<p>The only difference in setting is that it takes the argument, &#8220;src&#8221; in place of the\nargument &#8220;label&#8221;.</p>\n\n<table cellpadding=\"2\" cellspacing=\"2\">\n	<tr>\n		<th>src</th>\n		<td>image filename. Images need to be be placed into the &#8220;img&#8221; sub-folder of &#8220;quicktags&#8221;.</td>\n	</tr>\n</table>\n\n<h3>Link Template</h3>\n\n<pre><code>edLinks[edLinks.length] = new edLink(text, url, target, rel, tooltip);\n</code></pre>\n\n<h3>Link Example</h3>\n\n<pre><code>edLinks[edLinks.length] = new edLink(\'Textpattern\', \'http://textpattern.com/\', \'\', \'I &#38;#9829; Textpattern\');\n</code></pre>\n\nh3. Link Argument Explanation\n\n<table cellpadding=\"2\" cellspacing=\"2\">\n	<tr>\n		<th>text</th>\n		<td>Link title, any text you wish.</td>\n	</tr>\n\n	<tr>\n		<th>url</th>\n		<td>Link url.</td>\n	</tr>\n\n	<tr>\n		<th>target</th>\n		<td>Link target window. May be left empty.</td>\n	</tr>\n\n	<tr>\n		<th>rel</th>\n		<td>Define link relationship. May be left empty.</td>\n	</tr>\n\n	<tr>\n		<th>tooltip</th>\n		<td>Text that appears when the mouse cursor hovers over the button. May be left empty.</td>\n	</tr>\n</table>\n\n<p>Look through the Quicktag configuration files (write.js, forms.js and pages.js) for further\nexamples.</p>\n\n<h3>&#8220;Special&#8221; Buttons</h3>\n\n<p>Seven of the default buttons are marked &#8220;special&#8221;. This means that they are programmed\nto do special user interaction, and therefore only their label and/or tooltip may be customized.</p>\n\n<h4>Further Info</h4>\n\n<ul>\n	<li><strong>qt-spacer</strong>: its purpose is to cause visual separation of buttons, so you may have the\n	buttons appear in groups, rather than immediately side-by-side.\n	<li><strong>qt-spacer-br</strong>: its purpose is to cause visual separation of buttons, starts a new row\n	(thanks Joshua!)</li>\n	<li><strong>qt-image</strong>: inserts an image.</li>\n	<li><strong>qt-img-popper</strong>: ties upm_quicktags to upm_img_popper plugin (you have to have upm_img_popper\n	installed and activated for this to work)</li>\n	<li><strong>qt-link</strong>: inserts a link.</li>\n	<li><strong>qt-spell</strong>: looks up a word in the dictionary.</li>\n	<li><strong>qt-close</strong>: closes any and all open tags.</li>\n</ul>\n\n<h3>Entities Gotcha</h3>\n\n<p>Should you desire to use numeric entities (special characters, like a copyright sign, for example) in\na button&#8217;s label, you will need to enter them as their ISO 10646 (&#8220;Unicode&#8221;) character\nnumbers (for the label only). A short list of common entities has been provided below.\n<a href=\"http://www.w3.org/TR/html4/sgml/entities.html\">A more complete list is available</a>, and a\nconverter has been included here to help you as well. This is not required for the actual text that the\nbuttons insert.</p>\n\n<h4>Character and Entity Convertor</h4>\n\n<script type=\"text/javascript\" src=\"upm_quicktags/lib/converter.js\"></script>\n\n<form action=\"\">\n<fieldset>\n	<legend>Character to Unicode</legend>\n\n	<p>Example: &#169; (a copyright sign)</p>\n	<label for=\"symbol\">Character</label>: <input type=\"text\" id=\"symbol\" name=\"symbol\" value=\"©\" /><br />\n\n	<label for=\"symbol_out\">Unicode</label>: <input type=\"text\" id=\"symbol_out\" name=\"symbol_out\" value=\"\" /><br />\n	<input type=\"submit\" value=\"Convert\" onclick=\"fromChar(); return false;\" />\n</fieldset>\n</form>\n\n<form action=\"\">\n<fieldset>\n	<legend>ASCII to Unicode</legend>\n\n	<p>Example: 169 (a copyright sign)</p>\n	<label for=\"asc\">Entity</label>: <input type=\"text\" id=\"asc\" name=\"asc\" value=\"169\" /><br />\n	<label for=\"asc_out\">Unicode</label>: <input type=\"text\" id=\"asc_out\" name=\"asc_out\" value=\"\" /><br />\n\n	<input type=\"submit\" value=\"Convert\" onclick=\"fromAsc(); return false;\" />\n</fieldset>\n</form>\n\n<h4>Common Entities</h4>\n\n<table cellpadding=\"5\" cellspacing=\"5\" border=\"0\" id=\"list\">\n	<tr>\n		<th>Name</th>\n		<th>Character</th>\n		<th>Numeric Entity</th>\n		<th>Unicode</th>\n	</tr>\n\n	<tr>\n		<td>ampersand</td>\n		<td>&#38;</td>\n		<td><code>&#38;#38;</code></td>\n		<td><code>\\u0026</code></td>\n	</tr>\n\n	<tr>\n		<td>copyright</td>\n		<td>&#169;</td>\n		<td><code>&#38;#169;</code></td>\n		<td><code>\\u00a9</code></td>\n	</tr>\n\n	<tr>\n		<td>en dash</td>\n		<td>&#8211;</td>\n		<td><code>&#38;#8211;</code></td>\n		<td><code>\\u2013</code></td>\n	</tr>\n\n	<tr>\n		<td>em dash</td>\n		<td>&#8212;</td>\n		<td><code>&#38;#8212;</code></td>\n		<td><code>\\u2014</code></td>\n	</tr>\n\n	<tr>\n		<td>left single quotation mark</td>\n		<td>&#8216;</td>\n		<td>&#38;#8216;</td>\n		<td><code>\\u2018</code></td>\n	</tr>\n\n	<tr>\n		<td>right single quotation mark</td>\n		<td>&#8217;</td>\n		<td><code>&#38;#8217;</code></td>\n		<td><code>\\u2019</code></td>\n	</tr>\n\n	<tr>\n		<td>left double quotation mark</td>\n		<td>&#8220;</td>\n		<td><code>&#38;#8220;</code></td>\n		<td><code>\\u201c</code></td>\n	</tr>\n\n	<tr>\n		<td>right double quotation mark</td>\n		<td>&#8221;</td>\n		<td><code>&#38;#8221;</code></td>\n		<td><code>\\u201d</code></td>\n	</tr>\n</table>\n\n<h2>Extra Settings</h2>\n\n<p>You may leave these extra setting lines out of your config files altogether, and the plugin will use\ndefault values.</p>\n\n<h3>xhtml</h3>\n\n<p>All three config. files contain this line:</p>\n\n<pre><code>xhtml = \'yes\';\n</code></pre>\n\n<p>By default, the special insert image button outputs xhtml. If you are using html in your site, simply\nchange this setting to &#8216;no&#8217;.</p>\n\n<h3>showLinks</h3>\n\n<p>All three config. files contain a similar line:</p>\n\n<pre><code>showLinks = \'yes\';\n</code></pre>\n\n<p>One of Quicktag&#8217;s features is the ability to quickly add markup for frequently referenced urls,\nwhich appear in a dropdown menu. Should you wish to turn this feature off, change the value to\n&#8216;no&#8217;.</p>\n\n<h3>toolbar_width</h3>\n\n<p>The form (forms.js) and page (pages.js) config. files contain a similar line:</p>\n\n<pre><code>var toolbar_width = \'600px\';\n</code></pre>\n\n<p>Its function is to wrap the toolbar to match the textarea&#8217;s size. While I could hard-code this\ninformation, I decided to make it a setting, in case of a user mod or plugin changing the textarea&#8217;s\nsize. If this happens, change the width to match the new size of the textarea.</p>\n\n<h2>Advanced Enhancements</h2>\n\n<p>upm_quicktags allows you to extend it without hacks. Simply add your own JavaScript to the supplied\ncustom.js file. You can use your own functions and create your own buttons, but still make use of the\ngeneric functions supplied. You can use any, all, or none of the default buttons. It&#8217;s all up to\nyour imagination and your scripting skills.</p>\n\n<p>This is considered advanced, and is intended to be used by those who know JavaScript. I can&#8217;t\npromise to support whatever changes you make, so don&#8217;t do this unless you&#8217;re prepared to\nhandle it yourself.</p>\n\n<h2>Supported Browsers</h2>\n\n<p>Please notify me of any browsers this plugin does or does not work in, if it has not already been\nlisted below.</p>\n\n<p>This plugin is known to work in: Firefox 1.0.2 (Win) and Internet Explorer 6 SP2 (Win). It has almost\nseamless support in Opera 8 (Win), but limited support in the versions preceeding it.</p>\n\n<h2>Suggestions</h2>\n\n<p>I am always willing to take suggestions for this plugin. Suggested changes for the default settings and\nbuttons shipped with this plugin may be used, if it is felt that a large number of users would desire it.\nNevertheless, feel free to share your Quicktags configuration with other users.</p>\n\n<h2>Credits</h2>\n\n<p><a href=\"http://www.alexking.org/\">Alex King</a> wrote Quicktags, and released it under the\n<a href=\"http://gnu.org/copyleft/lesser.html\">LGPL license</a>.</p>\n\n<p>Much thanks goes to:</p>\n\n<ul>\n	<li><a href=\"http://www.hakjoon.com/\">Patrick Woods</a> (aka &#8220;hakjoon&#8221;)</li>\n	<li><a href=\"http://thresholdstate.com/\">Alex Shiels</a> (aka &#8220;zem&#8221;)</li>\n	<li><a href=\"http://micampe.it/log\">Michele Campeotto</a> (aka &#8220;micampe&#8221;)</li>\n	<li><a href=\"http://www.wilshireone.com/\">Rob Sable</a> (aka &#8220;wilshire&#8221;)</li>\n</ul>\n\n<p>I studied their examples and plugins in order to make this one. Particular thanks goes to Patrick:\nI&#8217;ve made use of his modified version of Quicktags.</p>\n\n<p>Additional thanks goes to the several users who &#8220;beta-tested&#8221; this plugin, offered their\nideas, and helped me iron out the wrinkles.&#8212;Mary</p>', 'if (txpinterface == \'admin\')\n	{\n		register_callback(\'upm_quicktags_article\', \'article\');\n		register_callback(\'upm_quicktags_form\', \'form\');\n		register_callback(\'upm_quicktags_page\', \'page\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_article($event)\n	{\n		echo n.\'<script id=\"write_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/write.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_form($event)\n	{\n		echo n.\'<script id=\"forms_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\">var toolbar_width = \\\'368px\\\';</script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/forms.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_page($event)\n	{\n		echo n.\'<script id=\"pages_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\">var toolbar_width = \\\'600px\\\';</script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/pages.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}', 'if (txpinterface == \'admin\')\n	{\n		register_callback(\'upm_quicktags_article\', \'article\');\n		register_callback(\'upm_quicktags_form\', \'form\');\n		register_callback(\'upm_quicktags_page\', \'page\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_article($event)\n	{\n		echo n.\'<script id=\"write_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/write.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_form($event)\n	{\n		echo n.\'<script id=\"forms_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\">var toolbar_width = \\\'368px\\\';</script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/forms.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_quicktags_page($event)\n	{\n		echo n.\'<script id=\"pages_tab\" type=\"text/javascript\" src=\"upm_quicktags/lib/quicktags.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\">var toolbar_width = \\\'600px\\\';</script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/pages.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/lib/txp.js\"></script>\';\n		echo n.\'<script type=\"text/javascript\" src=\"upm_quicktags/custom.js\"></script>\';\n	}', '621b596ed2686af66f24c1522f51f792', '', '', 1, 5, 0),
('upm_savenew', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-savenew', '0.4.2', '\"Save New\" button for articles and forms.', '<h1>upm_savenew</h1>\n\n<p>Once installed and activated, you&#8217;re ready to go.</p>\n\n<p>This plugin started as <a href=\"http://forum.textpattern.com/viewtopic.php?id=2586\" rel=\"nofollow\">a hack created by <strong>grapeice925</strong></a> of the Textpattern forum. I converted it into an admin-side plugin, and extended it to include a “Save New” button for forms.</p>', 'if (txpinterface == \'admin\')\n	{\n		// link to javascript\n		add_privs(\'upm_savenew_js_link_load\', \'1,2,3,4,5,6\');\n		register_callback(\'upm_savenew_js_link_load\', \'article\', \'edit\', 1);\n		register_callback(\'upm_savenew_js_link_load\', \'form\', \'\', 1);\n\n		// load javascript\n		add_privs(\'upm_savenew_js\', \'1,2,3,4,5,6\');\n		register_callback(\'upm_savenew_js\', \'upm_savenew_js\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_savenew_js_link_load()\n	{\n		ob_start(\'upm_savenew_js_link\');\n	}\n\n	function upm_savenew_js_link($buffer)\n	{\n		$find = \'</head>\';\n		$replace = n.n.t.t.\'<script type=\"text/javascript\" src=\"index.php?event=upm_savenew_js\"></script>\'.n.t;\n\n		return str_replace($find, $replace.$find, $buffer);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_savenew_js()\n	{\n		while (@ob_end_clean());\n\n		$save_new = gTxt(\'save_new\');\n\n		header(\"Content-type: text/javascript\");\n\n		echo <<<js\n/*\nupm_savenew\n*/\n\n	$(document).ready(function() {\n		// create new article submit button\n		$(\'#page-article input[name=\"save\"]\').\n			after(\' <input type=\"submit\" name=\"publish\" value=\"$save_new\" class=\"publish\" />\');\n\n		// article save new button\n		$(\'#page-article input[name=\"save\"] + input[name=\"publish\"]\').\n			// onclick...\n			click(function(){\n				// check reset time checkbox\n				$(\'#reset_time\').attr({\n					name: \'publish_now\',\n					checked: true\n				});\n\n				// empty URL-only title\n				$(\'#url-title\').attr(\'value\', \'\');\n			});\n\n		// create new form submit button\n		$(\'#page-form input[name=\"save\"]\').\n			after(\' <input type=\"submit\" name=\"savenew\" value=\"$save_new\" class=\"publish\" />\');\n\n		// forms save new button\n		$(\'#page-form input[name=\"save\"] + input[name=\"savenew\"]\').\n			// onclick...\n			click(function(){\n				// change form name from original to original_copy\n				$(\'input[name=\"name\"]\').attr(\'value\', $(\'input[name=\"name\"]\').attr(\'value\') + \'_copy\');\n			});\n	});\n\njs;\n		exit(0);\n	}', 'if (txpinterface == \'admin\')\n	{\n		// link to javascript\n		add_privs(\'upm_savenew_js_link_load\', \'1,2,3,4,5,6\');\n		register_callback(\'upm_savenew_js_link_load\', \'article\', \'edit\', 1);\n		register_callback(\'upm_savenew_js_link_load\', \'form\', \'\', 1);\n\n		// load javascript\n		add_privs(\'upm_savenew_js\', \'1,2,3,4,5,6\');\n		register_callback(\'upm_savenew_js\', \'upm_savenew_js\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_savenew_js_link_load()\n	{\n		ob_start(\'upm_savenew_js_link\');\n	}\n\n	function upm_savenew_js_link($buffer)\n	{\n		$find = \'</head>\';\n		$replace = n.n.t.t.\'<script type=\"text/javascript\" src=\"index.php?event=upm_savenew_js\"></script>\'.n.t;\n\n		return str_replace($find, $replace.$find, $buffer);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_savenew_js()\n	{\n		while (@ob_end_clean());\n\n		$save_new = gTxt(\'save_new\');\n\n		header(\"Content-type: text/javascript\");\n\n		echo <<<js\n/*\nupm_savenew\n*/\n\n	$(document).ready(function() {\n		// create new article submit button\n		$(\'#page-article input[name=\"save\"]\').\n			after(\' <input type=\"submit\" name=\"publish\" value=\"$save_new\" class=\"publish\" />\');\n\n		// article save new button\n		$(\'#page-article input[name=\"save\"] + input[name=\"publish\"]\').\n			// onclick...\n			click(function(){\n				// check reset time checkbox\n				$(\'#reset_time\').attr({\n					name: \'publish_now\',\n					checked: true\n				});\n\n				// empty URL-only title\n				$(\'#url-title\').attr(\'value\', \'\');\n			});\n\n		// create new form submit button\n		$(\'#page-form input[name=\"save\"]\').\n			after(\' <input type=\"submit\" name=\"savenew\" value=\"$save_new\" class=\"publish\" />\');\n\n		// forms save new button\n		$(\'#page-form input[name=\"save\"] + input[name=\"savenew\"]\').\n			// onclick...\n			click(function(){\n				// change form name from original to original_copy\n				$(\'input[name=\"name\"]\').attr(\'value\', $(\'input[name=\"name\"]\').attr(\'value\') + \'_copy\');\n			});\n	});\n\njs;\n		exit(0);\n	}', '41ed3143534381dfd6a0f63d6c696a96', '', '', 1, 5, 0),
('upm_insert_tab', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-insert-tab', '0.3', 'Allow tab insertion within admin textareas.', '<h1>upm_insert_tab</h1>\n\n<h2>Introduction</h2>\n\n<p>Allows you to insert a tab within all built-in admin page textareas, with the exception of the plugin edit page, as plugins are turned off for the plugin tab as a safety measure.</p>\n\n<p>Once the textarea has focus, pressing the tab key acts like a text editor. To “escape” the textarea (make it lose focus), you can either click the mouse somewhere else on the page, or press the escape key (Esc).</p>\n\n<p>Known to work in IE 6 and Firefox 1, works strangely in Opera 8 (all on Windows).</p>\n\n<h2>Plugin Developers</h2>\n\n<p>If you want to enable this plugin for your plugin-created textareas, simply add the following to your plugin tab&#8217;s page:</p>\n\n<pre><code>if (function_exists(\'upm_insert_tab\'))\n{\n	upm_insert_tab();\n}</code></pre>\n\n<p>If the user has this plugin installed and activated, your plugin textareas will allow tabbing. If it is not, textareas will work as they normally do, with no errors.</p>', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_insert_tab_js\',\'1,2,3,4,5,6\');\n\n		register_callback(\'upm_insert_tab\', \'article\');\n		register_callback(\'upm_insert_tab\', \'link\');\n		register_callback(\'upm_insert_tab\', \'image\');\n		register_callback(\'upm_insert_tab\', \'file\');\n		register_callback(\'upm_insert_tab\', \'discuss\');\n\n		register_callback(\'upm_insert_tab\', \'page\');\n		register_callback(\'upm_insert_tab\', \'form\');\n		register_callback(\'upm_insert_tab\', \'css\');\n\n		register_callback(\'upm_insert_tab_js\', \'upm_insert_tab_js\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_insert_tab()\n	{\n		echo n.\'<script type=\"text/javascript\" src=\"index.php?event=upm_insert_tab_js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_insert_tab_js()\n	{\n		header(\"Content-type: text/javascript\");\n\n		echo <<<js\n\n// -------------------------------------------------------------\n\n	var areas = document.getElementsByTagName(\'textarea\');\n\n	for (var i = 0; i < areas.length; i++)\n	{\n		if (document.all)\n		{\n			areas[i].onkeydown = function ()\n			{\n				return catchKey(event, this);\n			};\n\n			areas[i].onkeyup = function ()\n			{\n				return catchKey(event, this);\n			};\n\n			areas[i].onkeypress = function ()\n			{\n				return catchKey(event, this);\n			};\n		}\n\n		else\n		{\n			areas[i].setAttribute(\'onkeydown\', \"return catchKey(event, this);\");\n			areas[i].setAttribute(\'onkeyup\', \"return catchKey(event, this);\");\n			areas[i].setAttribute(\'onkeypress\', \"return catchKey(event, this);\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function insertTab(obj)\n	{\n		if (obj.setSelectionRange)\n		{\n			var s = obj.selectionStart;\n			var e = obj.selectionEnd;\n			var t = obj.scrollTop;\n\n			obj.value = obj.value.substring(0, s) + \'\\u0009\' + obj.value.substr(e);\n			obj.setSelectionRange(s + 1, s + 1);\n\n			obj.focus();\n			obj.scrollTop = t;\n		}\n\n		else if (obj.createTextRange)\n		{\n			sel = document.selection.createRange().text = \'\\u0009\';\n			obj.onblur = function() { this.onblur = null; };\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function catchKey(event, obj)\n	{\n		var keycode = (event.which) ? event.which : event.keyCode;\n\n		if (keycode == 9)\n		{\n			if (event.type == \'keydown\')\n			{\n				insertTab(obj);\n\n				if (event.preventDefault)\n				{\n					event.preventDefault();\n				}\n\n				else if (event.returnValue)\n				{\n					event.returnValue = false;\n				}\n			}\n\n			return false;\n		}\n\n		if (keycode == 27)\n		{\n			if (event.type == \'keydown\')\n			{\n				obj.blur();\n			}\n		}\n\n		return true;\n	}\njs;\n\n		exit(0);\n	}', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_insert_tab_js\',\'1,2,3,4,5,6\');\n\n		register_callback(\'upm_insert_tab\', \'article\');\n		register_callback(\'upm_insert_tab\', \'link\');\n		register_callback(\'upm_insert_tab\', \'image\');\n		register_callback(\'upm_insert_tab\', \'file\');\n		register_callback(\'upm_insert_tab\', \'discuss\');\n\n		register_callback(\'upm_insert_tab\', \'page\');\n		register_callback(\'upm_insert_tab\', \'form\');\n		register_callback(\'upm_insert_tab\', \'css\');\n\n		register_callback(\'upm_insert_tab_js\', \'upm_insert_tab_js\', \'\', 1);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_insert_tab()\n	{\n		echo n.\'<script type=\"text/javascript\" src=\"index.php?event=upm_insert_tab_js\"></script>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_insert_tab_js()\n	{\n		header(\"Content-type: text/javascript\");\n\n		echo <<<js\n\n// -------------------------------------------------------------\n\n	var areas = document.getElementsByTagName(\'textarea\');\n\n	for (var i = 0; i < areas.length; i++)\n	{\n		if (document.all)\n		{\n			areas[i].onkeydown = function ()\n			{\n				return catchKey(event, this);\n			};\n\n			areas[i].onkeyup = function ()\n			{\n				return catchKey(event, this);\n			};\n\n			areas[i].onkeypress = function ()\n			{\n				return catchKey(event, this);\n			};\n		}\n\n		else\n		{\n			areas[i].setAttribute(\'onkeydown\', \"return catchKey(event, this);\");\n			areas[i].setAttribute(\'onkeyup\', \"return catchKey(event, this);\");\n			areas[i].setAttribute(\'onkeypress\', \"return catchKey(event, this);\");\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function insertTab(obj)\n	{\n		if (obj.setSelectionRange)\n		{\n			var s = obj.selectionStart;\n			var e = obj.selectionEnd;\n			var t = obj.scrollTop;\n\n			obj.value = obj.value.substring(0, s) + \'\\u0009\' + obj.value.substr(e);\n			obj.setSelectionRange(s + 1, s + 1);\n\n			obj.focus();\n			obj.scrollTop = t;\n		}\n\n		else if (obj.createTextRange)\n		{\n			sel = document.selection.createRange().text = \'\\u0009\';\n			obj.onblur = function() { this.onblur = null; };\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function catchKey(event, obj)\n	{\n		var keycode = (event.which) ? event.which : event.keyCode;\n\n		if (keycode == 9)\n		{\n			if (event.type == \'keydown\')\n			{\n				insertTab(obj);\n\n				if (event.preventDefault)\n				{\n					event.preventDefault();\n				}\n\n				else if (event.returnValue)\n				{\n					event.returnValue = false;\n				}\n			}\n\n			return false;\n		}\n\n		if (keycode == 27)\n		{\n			if (event.type == \'keydown\')\n			{\n				obj.blur();\n			}\n		}\n\n		return true;\n	}\njs;\n\n		exit(0);\n	}', 'e68ca63c361a82b56350f7b4676b6d74', '', '', 1, 5, 0),
('upm_comment_time', 0, 'Mary', 'http://utterplush.com/txp-plugins', '0.2', 'Customizable comment time', '<h1><code>&lt;txp:upm_comment_time /&gt;</code></h1>\n\n<p>Customizable comment time. Allows you to use date rather than the default strftime format strings.</p>\n\n<dl>\n	<dt><code><b>format=\"</b><em>value</em><b>\"</b></code></dt>\n	<dd>The format string to use.</dd>\n	<dd>Required: No.</dd>\n	<dd>Available values: any valid string for the used function. If using\n	the function type <code><b>date</b></code>, any valid <a href=\"http://php.net/date\">date format string</a>.\n	Otherwise any valid <a href=\"http://php.net/strftime\">strftime format string</a>.</dd>\n	<dd>Default value: comment format preference value.</dd>\n\n	<dt><code><b>function=\"</b><em>value</em><b>\"</b></code></dt>\n	<dd>The PHP format string type to use.</dd>\n	<dd>Required: No.</dd>\n	<dd>Available values: <code><b>date</b></code>, unset (strftime).</dd>\n	<dd>Default value: unset (strftime).</dd>\n</dl>', 'function upm_comment_time($atts)\n{\n	global $thiscomment, $comments_dateformat;\n\n	extract(lAtts(array(\n		\'format\' => $comments_dateformat,\n		\'function\' => false\n	), $atts));\n\n	if ($format == \'since\')\n	{\n		return since($thiscomment[\'time\']);\n	}\n\n	if ($function == \'date\')\n	{\n		return date($format, $thiscomment[\'time\'] + tz_offset());\n	}\n\n	return safe_strftime($format, $thiscomment[\'time\']);\n}', 'function upm_comment_time($atts)\n{\n	global $thiscomment, $comments_dateformat;\n\n	extract(lAtts(array(\n		\'format\' => $comments_dateformat,\n		\'function\' => false\n	), $atts));\n\n	if ($format == \'since\')\n	{\n		return since($thiscomment[\'time\']);\n	}\n\n	if ($function == \'date\')\n	{\n		return date($format, $thiscomment[\'time\'] + tz_offset());\n	}\n\n	return safe_strftime($format, $thiscomment[\'time\']);\n}', 'a88ac84fb979e526d10e948230b2a5b2', '', '', 0, 5, 0),
('upm_textile', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-textile', '0.3', 'Parse any text with Textile.', '<style type=\"text/css\" media=\"screen,projection\">\n<!--\nvar {\npadding: 0.1em;\nfont-style: normal;\nfont-weight: bold;\nfont-family: monospace;\nbackground-color: #eee;\n}\n\ntable.upm_tag_atts {\nmargin: 0 0 15px 0;\nborder: 1px solid #eee;\nborder-collapse: collapse;\n}\n\ntable.upm_tag_atts caption {\npadding: 0.5em;\nfont-weight: bold;\ntext-align: left;\nbackground-color: #ddd;\n}\n\ntable.upm_tag_atts tr {\nborder-bottom: 1px solid #eee;\n}\n\ntable.upm_tag_atts th, table.upm_tag_atts td {\nmargin: 0;\npadding: 0.5em 1em;\n}\n\ntable.upm_tag_atts th {\nfont-weight: normal;\ntext-align: left;\nbackground-color: #eee;\n}\n-->\n</style>\n\n<h1>upm_textile</h1>\n\n<ol id=\"start\">\n	<li><a href=\"#introduction\">Introduction</a></li>\n	<li><a href=\"#tags\">Tags</a></li>\n	<li><a href=\"#usage-examples\">Usage Examples</a></li>\n	<li><a href=\"#footnotes\">Footnotes</a></li>\n</ol>\n\n\n\n<h2 id=\"introduction\">Introduction</h2>\n\n<p>This plugin tag allows you to parse any text with Textile. Simply wrap it around\n	whatever text you\'d like to parse.</p>\n\n<p><strong>Please note:</strong> this uses the same Textile version supplied with your\n	copy of Textpattern and I only provide support for this plugin, not Textile\n	itself. The attributes below do not add any new/improved functionality to Textile,\n	merely the ability to use in-built Textile parameters.</p>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n\n<h2 id=\"tags\">Tags</h2>\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_textile</caption>\n\n	<tr>\n		<th>Name</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>encode</td>\n		<td>Dropped. Deprecated in Textile and doesn&#8217;t make much sense. Do not use.</td>\n	</tr>\n\n	<tr>\n		<td>lite</td>\n		<td>Whether to exclude all code and block tags.</td>\n		<td>No</td>\n		<td><var>0</var> (no) or <var>1</var> (yes)</td>\n		<td><var>0</var></td>\n	</tr>\n\n	<tr>\n		<td>no_image</td>\n		<td>Whether to strip images.</td>\n		<td>No</td>\n		<td><var>0</var> (leave alone) or <var>1</var> (strip images)</td>\n		<td><var>0</var></td>\n	</tr>\n\n	<tr>\n		<td>parse_tags</td>\n		<td>Whether to parse any contained Textpattern tags.</td>\n		<td>No</td>\n		<td><var>0</var> (no) or <var>1</var> (yes)</td>\n		<td><var>1</var></td>\n	</tr>\n\n	<tr>\n		<td>rel</td>\n		<td>(X)HTML <var>rel</var> attribute to apply to links.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://www.w3.org/TR/REC-html40/types.html#type-links\">(X)HTML link relationship type</a>.</td>\n		<td>Unset</td>\n	</tr>\n\n	<tr>\n		<td>strict</td>\n		<td>Whether to strip extra whitespace.</td>\n		<td>No</td>\n		<td><var>0</var> (yes) or <var>1</var> (leave alone)</td>\n		<td><var>0</var></td>\n	</tr>\n</table>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n<table class=\"upm_tag_atts\" cellpadding=\"0\" cellspacing=\"0\">\n\n	<caption>upm_textile_restricted</caption>\n\n	<tr>\n		<th>Name</th>\n		<th>Description</th>\n		<th>Required?</th>\n		<th>Available Values</th>\n		<th>Default Value</th>\n	</tr>\n\n	<tr>\n		<td>lite</td>\n		<td>Whether to exclude all code and all block tags, except for paragraphs and blockquotes.</td>\n		<td>No</td>\n		<td><var>0</var> (leave alone) or <var>1</var> (yes)</td>\n		<td><var>1</var></td>\n	</tr>\n\n	<tr>\n		<td>no_image</td>\n		<td>Whether to strip images.</td>\n		<td>No</td>\n		<td><var>0</var> (leave alone) or <var>1</var> (yes)</td>\n		<td><var>1</var></td>\n	</tr>\n\n	<tr>\n		<td>parse_tags</td>\n		<td>Whether to parse any contained Textpattern tags.</td>\n		<td>No</td>\n		<td><var>0</var> (no) or <var>1</var> (yes)</td>\n		<td><var>1</var></td>\n	</tr>\n\n	<tr>\n		<td>rel</td>\n		<td>(X)HTML <var>rel</var> attribute to apply to links.</td>\n		<td>No</td>\n		<td>Any valid <a href=\"http://www.w3.org/TR/REC-html40/types.html#type-links\">(X)HTML link relationship type</a>.</td>\n		<td><var>nofollow</var><sup><a href=\"#fn1\">1</a></sup></td>\n	</tr>\n\n</table>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n<h3>Notes On Restricted Textile</h3>\n\n<p>Version 4.0.4 of Textpattern introduced a new function to Textile, named\n	TextileRestricted. This new function does several things:</p>\n\n<ul>\n	<li>Escapes all raw (X)HTML and attempts to clean all possible XSS attempts. This requires\n		stripping all <var>style</var>, <var>class</var>, <var>id</var>, <var>colspan</var> and\n		<var>rowspan</var> (X)THML attributes. The <var>lang</var> attribute is still allowed.</li>\n\n	<li>Automatically converts all plain-text urls into links.</li>\n\n	<li>Only <var>http</var>, <var>https</var>, <var>ftp</var>, and <var>mailto</var> URI schemes\n		are permitted<sup><a href=\"#fn1\">1</a></sup>.</li>\n\n	<li>By default, applies <var>nofollow</var> relationship\n		attribute<sup><a href=\"#fn2\">2</a></sup> to all links.</li>\n\n	<li>By default, enforces stricter use of Textile, but still allows for the essential\n		basics of paragraphs and blockquotes.</li>\n</ul>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n\n<h2 id=\"usage-examples\">Usage Examples</h2>\n\n<p><code>&lt;txp:upm_textile&gt;The text _you_ would like\n	\"Textiled\":http://textism.com/tools/textile/.&lt;/txp:upm_textile&gt;</code></p>\n\n<p><code>&lt;txp:upm_textile_restricted&gt;Even the output of Textpattern tags,\n	such as &lt;txp:site_slogan /&gt;, can be Textiled.&lt;/txp:upm_textile_restricted&gt;</code></p>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>\n\n\n\n<h2 id=\"footnotes\">Footnotes</h2>\n\n<ol>\n	<li id=\"fn1\">Wikipedia: <a href=\"http://en.wikipedia.org/wiki/URI_scheme\">URI scheme</a></li>\n	<li id=\"fn2\">A <a href=\"http://google.com/search?q=nofollow\">Google search query for &#8220;nofollow&#8221;</a> can\n		provide all the necessary information on what this is used for.</li>\n</ol>\n\n<p><a href=\"#start\" title=\"Jump back contents list\">&#8617;</a></p>', 'function upm_textile($atts = array(), $thing = \'\')\n	{\n		global $prefs;\n\n		extract(lAtts(array(\n			\'lite\'		   => \'\',\n			\'no_image\'   => \'\',\n			\'parse_tags\' => 1,\n			\'rel\'			   => \'\',\n			\'strict\'	   => \'\',\n		), $atts));\n\n		if ($thing)\n		{\n			@include_once(txpath.\'/lib/classTextile.php\');\n\n			if (class_exists(\'Textile\'))\n			{\n				$textile = new Textile();\n\n				if ($parse_tags)\n				{\n					$thing = parse($thing);\n				}\n\n				return $textile->TextileThis($thing, $lite, false, $no_image, $strict, $rel);\n			}\n\n			elseif ($prefs[\'production_status\'] != \'live\')\n			{\n				return upm_textile_gTxt(\'textile_missing\');\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_textile_restricted($atts = array(), $thing = \'\')\n	{\n		global $prefs;\n\n		extract(lAtts(array(\n			\'lite\'		   => 1,\n			\'no_image\'   => 1,\n			\'parse_tags\' => 1,\n			\'rel\'			   => \'nofollow\',\n		), $atts));\n\n		if ($thing)\n		{\n			@include_once(txpath.\'/lib/classTextile.php\');\n\n			if (class_exists(\'Textile\'))\n			{\n				$textile = new Textile();\n\n				if ($parse_tags)\n				{\n					$thing = parse($thing);\n				}\n\n				return $textile->TextileRestricted($thing, $lite, $no_image, $rel);\n			}\n\n			elseif ($prefs[\'production_status\'] != \'live\')\n			{\n				return upm_textile_gTxt(\'textile_missing\');\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_textile_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'textile_missing\' => \'upm_textile: Textile appears to be missing.\',\n		);\n\n		return strtr($lang[$what], $atts);\n	}', 'function upm_textile($atts = array(), $thing = \'\')\n	{\n		global $prefs;\n\n		extract(lAtts(array(\n			\'lite\'		   => \'\',\n			\'no_image\'   => \'\',\n			\'parse_tags\' => 1,\n			\'rel\'			   => \'\',\n			\'strict\'	   => \'\',\n		), $atts));\n\n		if ($thing)\n		{\n			@include_once(txpath.\'/lib/classTextile.php\');\n\n			if (class_exists(\'Textile\'))\n			{\n				$textile = new Textile();\n\n				if ($parse_tags)\n				{\n					$thing = parse($thing);\n				}\n\n				return $textile->TextileThis($thing, $lite, false, $no_image, $strict, $rel);\n			}\n\n			elseif ($prefs[\'production_status\'] != \'live\')\n			{\n				return upm_textile_gTxt(\'textile_missing\');\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_textile_restricted($atts = array(), $thing = \'\')\n	{\n		global $prefs;\n\n		extract(lAtts(array(\n			\'lite\'		   => 1,\n			\'no_image\'   => 1,\n			\'parse_tags\' => 1,\n			\'rel\'			   => \'nofollow\',\n		), $atts));\n\n		if ($thing)\n		{\n			@include_once(txpath.\'/lib/classTextile.php\');\n\n			if (class_exists(\'Textile\'))\n			{\n				$textile = new Textile();\n\n				if ($parse_tags)\n				{\n					$thing = parse($thing);\n				}\n\n				return $textile->TextileRestricted($thing, $lite, $no_image, $rel);\n			}\n\n			elseif ($prefs[\'production_status\'] != \'live\')\n			{\n				return upm_textile_gTxt(\'textile_missing\');\n			}\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_textile_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'textile_missing\' => \'upm_textile: Textile appears to be missing.\',\n		);\n\n		return strtr($lang[$what], $atts);\n	}', '8d71ab28b2cf21e23531c5c4cbac91f3', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('upm_pending_notify', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-pending-notify', '0.1.2', 'Pending article notification.', '<h1>upm_pending_notify</h1>\n\n<h2>What this plugin does:</h2>\n\n<ol>\n	<li>When someone creates an article with the status “Pending”, all the “Publisher” users receive an email. This email contains very basic information of the article, and link to further view and manage the article. If the author happens to be a Publisher, he does not receive the email.</li>\n	<li>When an article is changed from “Pending” to “Live” status, the article&#8217;s author receives a link to view their article on the live site. If the author happens to be a Publisher <strong>and</strong> set the article to “Live” herself, she does not receive the email.</li>\n</ol>\n\n<h2>What this plugin <strong>does not</strong> do:</h2>\n\n<ol>\n	<li>Author notifications are <strong>not</strong> sent out when article status is changed with the multi-edit feature of the Articles tab. For technical reasons, this could too easily cause grand problems with your site and make your web host very upset with you.</li>\n	<li>Allow all kinds of customizations to the emails sent concerning which article fields are included in the email for preview. The plugin remains nice and lighter-weight by providing only the essential information.</li>\n</ol>\n\n<h2>How to use it:</h2>\n\n<ol>\n	<li>Install the plugin (which you&#8217;ve already done).</li>\n	<li>Activate the plugin.</li>\n	<li>All done.</li>\n</ol>', 'if (txpinterface == \'admin\')\n	{\n		// i quite hate doing it this way, the write tab\'s setup is screwy\n\n		add_privs(\'upm_pending_notify_created\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_created\', \'article\', \'edit\');\n\n		add_privs(\'upm_pending_notify_updated_pre\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_updated_pre\', \'article\', \'\', 1);\n\n		add_privs(\'upm_pending_notify_updated_post\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_updated_post\', \'article\', \'edit\');\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_created()\n	{\n		global $ID;\n\n		// not trying to create a new article\n		// begone!\n		if (!gps(\'publish\'))\n		{\n			return;\n		}\n\n		$Status = (int) ps(\'Status\');\n\n		if (!has_privs(\'article.publish\') && $Status >= 4)\n		{\n			$Status = 3;\n		}\n\n		if ($Status == 3)\n		{\n			$ID	= (int) $ID;\n			$Title = ps(\'Title\');\n\n			upm_pending_notify_publisher($ID, $Title);\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_updated_pre()\n	{\n		$GLOBALS[\'upm_pending_notify\'] = false;\n\n		// article updated\n		if (gps(\'save\'))\n		{\n			$ID = (int) ps(\'ID\');\n\n			$Status = (int) ps(\'Status\');\n			$old_status = (int) safe_field(\'Status\', \'textpattern\', \"ID = $ID\");\n\n			// only notify if...\n\n			// ...marked as pending\n			// publishers are notified\n			// see: upm_pending_notify_updated_post()\n			if ($Status == 3)\n			{\n				$GLOBALS[\'upm_pending_notify\'] = \'publishers\';\n			}\n\n			// ...marked as live/sticky and was previously pending\n			// author is notified\n			// see: upm_pending_notify_updated_post()\n			elseif ($Status > 3 and $old_status == 3)\n			{\n				$GLOBALS[\'upm_pending_notify\'] = \'author\';\n			}\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_updated_post()\n	{\n		global $upm_pending_notify, $txp_user, $prefs;\n\n		if (!$upm_pending_notify)\n		{\n			return;\n		}\n\n		$ID = (int) ps(\'ID\');\n		$Title = ps(\'Title\');\n\n		switch ($upm_pending_notify)\n		{\n			case \'publishers\':\n				upm_pending_notify_publisher($ID, $Title);\n			break;\n\n			case \'author\':\n				$AuthorID = ps(\'AuthorID\');\n				$LastModID = ps(\'LastModID\');\n\n				upm_pending_notify_author($ID, $Title, $AuthorID, $LastModID);\n			break;\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_publisher($ID, $Title)\n	{\n		global $txp_user, $prefs;\n\n		$publishers = safe_rows_start(\'RealName, email\', \'txp_users\', \"privs = 1 and name != \'\".doSlash($txp_user).\"\'\");\n\n		if ($publishers)\n		{\n			$Title = ($Title) ? strip_tags($Title) : gTxt(\'untitled\');\n			$author = get_author_name($txp_user);\n\n			$subject = upm_pending_notify_gTxt(\'email_subject_publisher\', array(\n				\'{sitename}\' => $prefs[\'sitename\'],\n				\'{title}\'		 => $Title,\n			));\n\n			while ($publisher = nextRow($publishers))\n			{\n				$body = upm_pending_notify_gTxt(\'email_message_publisher\', array(\n					\'{article_url}\' => hu.\'textpattern/index.php?event=article&step=edit&ID=\'.$ID,\n					\'{author}\'			=> $author,\n					\'{publisher}\'		=> $publisher[\'RealName\'],\n					\'{title}\'				=> $Title,\n				));\n\n				upm_pending_notify_mail($publisher[\'RealName\'], $publisher[\'email\'], $subject, $body);\n			}\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_author($ID, $Title, $AuthorID, $LastModID)\n	{\n		global $txp_user, $prefs;\n\n		$author = safe_row(\'RealName, email\', \'txp_users\', \"name = \'\".doSlash($AuthorID).\"\' and name != \'\".doSlash($txp_user).\"\'\");\n\n		if ($author)\n		{\n			include_once txpath.\'/publish/taghandlers.php\';\n\n			$Title = ($Title) ? strip_tags($Title) : gTxt(\'untitled\');\n			$url = permlinkurl_id($ID);\n			$publisher = get_author_name($txp_user);\n\n			$subject = upm_pending_notify_gTxt(\'email_subject_author\', array(\n				\'{sitename}\' => $prefs[\'sitename\'],\n				\'{title}\'		 => $Title,\n			));\n\n			$body = upm_pending_notify_gTxt(\'email_message_author\', array(\n				\'{article_url}\' => $url,\n				\'{author}\'			=> $author[\'RealName\'],\n				\'{publisher}\'		=> $publisher,\n				\'{title}\'				=> $Title,\n			));\n\n			upm_pending_notify_mail($author[\'RealName\'], $author[\'email\'], $subject, $body);\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_mail($name, $email, $subject, $body)\n	{\n		global $prefs;\n\n		if ($prefs[\'override_emailcharset\'])\n		{\n			$charset = \'ISO-8859-1\';\n\n			if (is_callable(\'utf8_decode\'))\n			{\n				$name		 = utf8_decode($name);\n				$email	 = utf8_decode($email);\n\n				$subject = utf8_decode($subject);\n				$body		 = utf8_decode($body);\n			}\n		}\n\n		else\n		{\n			$charset = \'UTF-8\';\n		}\n\n		$name = encode_mailheader(strip_rn($name), \'phrase\');\n		$email = strip_rn($email);\n\n		$subject = encode_mailheader(strip_rn($subject), \'text\');\n\n		$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n\n		$body = str_replace(\"\\r\\n\", \"\\n\", $body);\n		$body = str_replace(\"\\r\", \"\\n\", $body);\n		$body = str_replace(\"\\n\", $sep, $body);\n\n		return mail(\"$name <$email>\", $subject, $body,\n			\"From: $name <$email>\".\n			$sep.\'X-Mailer: upm_pending_notify Textpattern plugin\'.\n			$sep.\'Content-Transfer-Encoding: 8bit\'.\n			$sep.\'Content-Type: text/plain; charset=\"\'.$charset.\'\"\'.\n			$sep\n		);\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_gTxt($what, $vars = array())\n	{\n		$lang = array();\n\n		$lang[\'email_subject_author\']		 = \'[{sitename}] Article published: {title}\';\n		$lang[\'email_message_author\']		 = <<<eml\nDear {author},\n\n{publisher} has published your article:\n\n{title}\n{article_url}\neml;\n\n		$lang[\'email_subject_publisher\'] = \'[{sitename}] Article submitted: {title}\';\n		$lang[\'email_message_publisher\'] = <<<eml\nDear {publisher},\n\n{author} has submitted an article for review:\n\n{title}\n{article_url}\neml;\n\n		return strtr($lang[$what], $vars);\n	}', 'if (txpinterface == \'admin\')\n	{\n		// i quite hate doing it this way, the write tab\'s setup is screwy\n\n		add_privs(\'upm_pending_notify_created\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_created\', \'article\', \'edit\');\n\n		add_privs(\'upm_pending_notify_updated_pre\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_updated_pre\', \'article\', \'\', 1);\n\n		add_privs(\'upm_pending_notify_updated_post\', \'1,2,3,4,5\');\n		register_callback(\'upm_pending_notify_updated_post\', \'article\', \'edit\');\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_created()\n	{\n		global $ID;\n\n		// not trying to create a new article\n		// begone!\n		if (!gps(\'publish\'))\n		{\n			return;\n		}\n\n		$Status = (int) ps(\'Status\');\n\n		if (!has_privs(\'article.publish\') && $Status >= 4)\n		{\n			$Status = 3;\n		}\n\n		if ($Status == 3)\n		{\n			$ID	= (int) $ID;\n			$Title = ps(\'Title\');\n\n			upm_pending_notify_publisher($ID, $Title);\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_updated_pre()\n	{\n		$GLOBALS[\'upm_pending_notify\'] = false;\n\n		// article updated\n		if (gps(\'save\'))\n		{\n			$ID = (int) ps(\'ID\');\n\n			$Status = (int) ps(\'Status\');\n			$old_status = (int) safe_field(\'Status\', \'textpattern\', \"ID = $ID\");\n\n			// only notify if...\n\n			// ...marked as pending\n			// publishers are notified\n			// see: upm_pending_notify_updated_post()\n			if ($Status == 3)\n			{\n				$GLOBALS[\'upm_pending_notify\'] = \'publishers\';\n			}\n\n			// ...marked as live/sticky and was previously pending\n			// author is notified\n			// see: upm_pending_notify_updated_post()\n			elseif ($Status > 3 and $old_status == 3)\n			{\n				$GLOBALS[\'upm_pending_notify\'] = \'author\';\n			}\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_updated_post()\n	{\n		global $upm_pending_notify, $txp_user, $prefs;\n\n		if (!$upm_pending_notify)\n		{\n			return;\n		}\n\n		$ID = (int) ps(\'ID\');\n		$Title = ps(\'Title\');\n\n		switch ($upm_pending_notify)\n		{\n			case \'publishers\':\n				upm_pending_notify_publisher($ID, $Title);\n			break;\n\n			case \'author\':\n				$AuthorID = ps(\'AuthorID\');\n				$LastModID = ps(\'LastModID\');\n\n				upm_pending_notify_author($ID, $Title, $AuthorID, $LastModID);\n			break;\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_publisher($ID, $Title)\n	{\n		global $txp_user, $prefs;\n\n		$publishers = safe_rows_start(\'RealName, email\', \'txp_users\', \"privs = 1 and name != \'\".doSlash($txp_user).\"\'\");\n\n		if ($publishers)\n		{\n			$Title = ($Title) ? strip_tags($Title) : gTxt(\'untitled\');\n			$author = get_author_name($txp_user);\n\n			$subject = upm_pending_notify_gTxt(\'email_subject_publisher\', array(\n				\'{sitename}\' => $prefs[\'sitename\'],\n				\'{title}\'		 => $Title,\n			));\n\n			while ($publisher = nextRow($publishers))\n			{\n				$body = upm_pending_notify_gTxt(\'email_message_publisher\', array(\n					\'{article_url}\' => hu.\'textpattern/index.php?event=article&step=edit&ID=\'.$ID,\n					\'{author}\'			=> $author,\n					\'{publisher}\'		=> $publisher[\'RealName\'],\n					\'{title}\'				=> $Title,\n				));\n\n				upm_pending_notify_mail($publisher[\'RealName\'], $publisher[\'email\'], $subject, $body);\n			}\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_author($ID, $Title, $AuthorID, $LastModID)\n	{\n		global $txp_user, $prefs;\n\n		$author = safe_row(\'RealName, email\', \'txp_users\', \"name = \'\".doSlash($AuthorID).\"\' and name != \'\".doSlash($txp_user).\"\'\");\n\n		if ($author)\n		{\n			include_once txpath.\'/publish/taghandlers.php\';\n\n			$Title = ($Title) ? strip_tags($Title) : gTxt(\'untitled\');\n			$url = permlinkurl_id($ID);\n			$publisher = get_author_name($txp_user);\n\n			$subject = upm_pending_notify_gTxt(\'email_subject_author\', array(\n				\'{sitename}\' => $prefs[\'sitename\'],\n				\'{title}\'		 => $Title,\n			));\n\n			$body = upm_pending_notify_gTxt(\'email_message_author\', array(\n				\'{article_url}\' => $url,\n				\'{author}\'			=> $author[\'RealName\'],\n				\'{publisher}\'		=> $publisher,\n				\'{title}\'				=> $Title,\n			));\n\n			upm_pending_notify_mail($author[\'RealName\'], $author[\'email\'], $subject, $body);\n		}\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_mail($name, $email, $subject, $body)\n	{\n		global $prefs;\n\n		if ($prefs[\'override_emailcharset\'])\n		{\n			$charset = \'ISO-8859-1\';\n\n			if (is_callable(\'utf8_decode\'))\n			{\n				$name		 = utf8_decode($name);\n				$email	 = utf8_decode($email);\n\n				$subject = utf8_decode($subject);\n				$body		 = utf8_decode($body);\n			}\n		}\n\n		else\n		{\n			$charset = \'UTF-8\';\n		}\n\n		$name = encode_mailheader(strip_rn($name), \'phrase\');\n		$email = strip_rn($email);\n\n		$subject = encode_mailheader(strip_rn($subject), \'text\');\n\n		$sep = !is_windows() ? \"\\n\" : \"\\r\\n\";\n\n		$body = str_replace(\"\\r\\n\", \"\\n\", $body);\n		$body = str_replace(\"\\r\", \"\\n\", $body);\n		$body = str_replace(\"\\n\", $sep, $body);\n\n		return mail(\"$name <$email>\", $subject, $body,\n			\"From: $name <$email>\".\n			$sep.\'X-Mailer: upm_pending_notify Textpattern plugin\'.\n			$sep.\'Content-Transfer-Encoding: 8bit\'.\n			$sep.\'Content-Type: text/plain; charset=\"\'.$charset.\'\"\'.\n			$sep\n		);\n	}\n\n//--------------------------------------------------------------\n\n	function upm_pending_notify_gTxt($what, $vars = array())\n	{\n		$lang = array();\n\n		$lang[\'email_subject_author\']		 = \'[{sitename}] Article published: {title}\';\n		$lang[\'email_message_author\']		 = <<<eml\nDear {author},\n\n{publisher} has published your article:\n\n{title}\n{article_url}\neml;\n\n		$lang[\'email_subject_publisher\'] = \'[{sitename}] Article submitted: {title}\';\n		$lang[\'email_message_publisher\'] = <<<eml\nDear {publisher},\n\n{author} has submitted an article for review:\n\n{title}\n{article_url}\neml;\n\n		return strtr($lang[$what], $vars);\n	}', '17660e2018477965d301383b7a2a65a4', '', '', 1, 5, 0),
('upm_plugin_restore', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-plugin-restore', '0.1', 'Restore modified plugins to their original contents.', '<h1>upm_plugin_restore</h1>\n\n<p>This plugin allows you to restore your modified plugins to their original contents. To use it, activate the plugin, and a new “Plugin Restore” tab will appear under the Admin tab. The rest should be fairly obvious.</p>\n\n<p>Why does the plugin not simply add a new link to the existing Plugins tab? Quite simply, Textpattern does not load plugins of any kind on the plugins page. The reason for this is to prevent a situation where a plugin breaks the plugins page (either intentionally or otherwise) and stops you from uninstalling the rogue plugin.</p>', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_plugin_restore\', \'1,2\');\n		register_tab(\'admin\', \'upm_plugin_restore\', upm_plugin_restore_gTxt(\'plugin_restore\'));\n		register_callback(\'upm_plugin_restore\', \'upm_plugin_restore\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore($event, $step)\n	{\n		switch ($step)\n		{\n			case \'restore\':\n				upm_plugin_restore_restore();\n			break;\n\n			case \'list\':\n			default:\n				upm_plugin_restore_list();\n			break;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_list($message = \'\')\n	{\n		pagetop(upm_plugin_restore_gTxt(\'plugin_restore\'), $message);\n\n		echo n.n.\'<div style=\"margin: 0 auto 10px; width: 38em;\">\'.\n\n			n.n.hed(upm_plugin_restore_gTxt(\'plugin_restore\'), 1);\n\n		$rs = safe_rows_start(\'name, description\', \'txp_plugin\', \"md5(code) != code_md5 order by name\");\n\n		if ($rs and numRows($rs) > 0)\n		{\n			echo n.n.graf(upm_plugin_restore_gTxt(\'summary\')).\n\n				n.n.graf(upm_plugin_restore_gTxt(\'warning\')).\n\n				n.n.startTable(\'list\').\n				n.assHead(\'plugin\', \'description\', \'\');\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				$name        = htmlspecialchars($name);\n				$description = htmlspecialchars($description);\n\n				// Fix up the description for clean cases\n				$description = preg_replace(\n					array(\n						\'#&lt;br /&gt;#\',\n						\'#&lt;(/?(a|b|i|em|strong))&gt;#\',\n						\'#&lt;a href=&quot;(https?|\\.|\\/|ftp)([A-Za-z0-9:/?.=_]+?)&quot;&gt;#\'\n					),\n					array(\'<br />\', \'<$1>\', \'<a href=\"$1$2\">\'),\n					$description\n				);\n\n				echo tr(\n					n.td($name).\n					td($description, 260).\n					td(\n						\'<form method=\"post\" action=\"index.php\" onclick=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\');\">\'.\n							\'<div>\'.\n								\'<input type=\"hidden\" name=\"event\" value=\"upm_plugin_restore\" />\'.\n								\'<input type=\"hidden\" name=\"step\" value=\"restore\" />\'.\n								\'<input type=\"hidden\" name=\"name\" value=\"\'.$name.\'\" />\'.\n								\'<input type=\"submit\" class=\"smallerbox\" value=\"\'.upm_plugin_restore_gTxt(\'restore\').\'\" />\'.\n							\'</div>\'.\n						\'</form>\'\n					)\n				);\n			}\n\n			echo endTable();\n		}\n\n		else\n		{\n			echo n.n.graf(upm_plugin_restore_gTxt(\'none_modified\'));\n		}\n\n		echo n.n.\'</div>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_restore()\n	{\n		$name = ps(\'name\');\n\n		if (safe_update(\'txp_plugin\', \"code = code_restore\", \"name = \'\".doSlash($name).\"\'\"))\n		{\n			$message = upm_plugin_restore_gTxt(\'restore_success\', array(\'{name}\' => $name));\n		}\n\n		else\n		{\n			$message = upm_plugin_restore_gTxt(\'restore_failure\', array(\'{name}\' => $name));\n		}\n\n		upm_plugin_restore_list($message);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'none_modified\'   => \'None of your plugins have been modified from the original.\',\n			\'plugin_restore\'  => \'Plugin Restore\',\n			\'restore\'	        => \'Restore\',\n			\'restore_failure\' => \'Plugin <strong>{name}</strong> could not be restored.\',\n			\'restore_success\' => \'Plugin <strong>{name}</strong> restored.\',\n			\'summary\'         => \'The following plugins have been modified from the original.\',\n			\'warning\'         => \'<strong>Note:</strong> Once a plugin has been restored, there is no way to recover the previous modifications made.\',\n		);\n\n		return strtr($lang[$what], $atts);\n	}', 'if (txpinterface == \'admin\')\n	{\n		add_privs(\'upm_plugin_restore\', \'1,2\');\n		register_tab(\'admin\', \'upm_plugin_restore\', upm_plugin_restore_gTxt(\'plugin_restore\'));\n		register_callback(\'upm_plugin_restore\', \'upm_plugin_restore\');\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore($event, $step)\n	{\n		switch ($step)\n		{\n			case \'restore\':\n				upm_plugin_restore_restore();\n			break;\n\n			case \'list\':\n			default:\n				upm_plugin_restore_list();\n			break;\n		}\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_list($message = \'\')\n	{\n		pagetop(upm_plugin_restore_gTxt(\'plugin_restore\'), $message);\n\n		echo n.n.\'<div style=\"margin: 0 auto 10px; width: 38em;\">\'.\n\n			n.n.hed(upm_plugin_restore_gTxt(\'plugin_restore\'), 1);\n\n		$rs = safe_rows_start(\'name, description\', \'txp_plugin\', \"md5(code) != code_md5 order by name\");\n\n		if ($rs and numRows($rs) > 0)\n		{\n			echo n.n.graf(upm_plugin_restore_gTxt(\'summary\')).\n\n				n.n.graf(upm_plugin_restore_gTxt(\'warning\')).\n\n				n.n.startTable(\'list\').\n				n.assHead(\'plugin\', \'description\', \'\');\n\n			while ($a = nextRow($rs))\n			{\n				extract($a);\n\n				$name        = htmlspecialchars($name);\n				$description = htmlspecialchars($description);\n\n				// Fix up the description for clean cases\n				$description = preg_replace(\n					array(\n						\'#&lt;br /&gt;#\',\n						\'#&lt;(/?(a|b|i|em|strong))&gt;#\',\n						\'#&lt;a href=&quot;(https?|\\.|\\/|ftp)([A-Za-z0-9:/?.=_]+?)&quot;&gt;#\'\n					),\n					array(\'<br />\', \'<$1>\', \'<a href=\"$1$2\">\'),\n					$description\n				);\n\n				echo tr(\n					n.td($name).\n					td($description, 260).\n					td(\n						\'<form method=\"post\" action=\"index.php\" onclick=\"return verify(\\\'\'.gTxt(\'are_you_sure\').\'\\\');\">\'.\n							\'<div>\'.\n								\'<input type=\"hidden\" name=\"event\" value=\"upm_plugin_restore\" />\'.\n								\'<input type=\"hidden\" name=\"step\" value=\"restore\" />\'.\n								\'<input type=\"hidden\" name=\"name\" value=\"\'.$name.\'\" />\'.\n								\'<input type=\"submit\" class=\"smallerbox\" value=\"\'.upm_plugin_restore_gTxt(\'restore\').\'\" />\'.\n							\'</div>\'.\n						\'</form>\'\n					)\n				);\n			}\n\n			echo endTable();\n		}\n\n		else\n		{\n			echo n.n.graf(upm_plugin_restore_gTxt(\'none_modified\'));\n		}\n\n		echo n.n.\'</div>\';\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_restore()\n	{\n		$name = ps(\'name\');\n\n		if (safe_update(\'txp_plugin\', \"code = code_restore\", \"name = \'\".doSlash($name).\"\'\"))\n		{\n			$message = upm_plugin_restore_gTxt(\'restore_success\', array(\'{name}\' => $name));\n		}\n\n		else\n		{\n			$message = upm_plugin_restore_gTxt(\'restore_failure\', array(\'{name}\' => $name));\n		}\n\n		upm_plugin_restore_list($message);\n	}\n\n// -------------------------------------------------------------\n\n	function upm_plugin_restore_gTxt($what, $atts = array())\n	{\n		$lang = array(\n			\'none_modified\'   => \'None of your plugins have been modified from the original.\',\n			\'plugin_restore\'  => \'Plugin Restore\',\n			\'restore\'	        => \'Restore\',\n			\'restore_failure\' => \'Plugin <strong>{name}</strong> could not be restored.\',\n			\'restore_success\' => \'Plugin <strong>{name}</strong> restored.\',\n			\'summary\'         => \'The following plugins have been modified from the original.\',\n			\'warning\'         => \'<strong>Note:</strong> Once a plugin has been restored, there is no way to recover the previous modifications made.\',\n		);\n\n		return strtr($lang[$what], $atts);\n	}', 'a4d18aa9e2c36cd25195cc867a160024', '', '', 1, 5, 0),
('upm_restrict_comment_size', 0, 'Mary Fredborg', 'http://utterplush.com/txp-plugins/upm-restrict-comment-size', '0.1', 'Restrict comment size.', '<style type=\"text/css\" media=\"screen,projection\">\n<!--\ndt {\n	font-weight: bold;\n}\n\nvar {\n	padding: 0.1em;\n	font-style: normal;\n	font-weight: bold;\n	font-family: monospace;\n	background-color: #eee;\n}\n\n-->\n</style>\n\n<h1>upm_restrict_comment_size</h1>\n\n<h2 id=\"intro-section\">Intro</h2>\n\n<p>Quite simply, this plugin allows you to more finely control the amount of text entered in any given comment.</p>\n\n\n<h2 id=\"tag-section\">New Tag</h2>\n\n<p>This plugin provides you with a single new comment form (<var>comment_form</var>) tag. It is recommended that this tag be placed <em>before</em> the <var>comments_error</var> tag in the form.</p>\n\n<h3>upm_restrict_comment_size</h3>\n\n<dl>\n	<dt>error_message</dt>\n	<dd>The error message displayed when size restrictions are not met. max, min and type are the available variables (surrounded with <var>{</var> and <var>}</var>) which may be used.</dd>\n	<dd>Required: no</dd>\n	<dd>Available values: any desired text</dd>\n	<dd>Default value: <var>Please enter between {min} and {max} {type} in your message.</var></dd>\n\n	<dt>max</dt>\n	<dd>The maximum amount of <var>type</var> allowed.</dd>\n	<dd>Required: no</dd>\n	<dd>Available values: any desired number</dd>\n	<dd>Default value: <var>1500</var></dd>\n\n	<dt>min</dt>\n	<dd>The minimum of amount of <var>type</var> required.</dd>\n	<dd>Required: no</dd>\n	<dd>Available values: any desired number</dd>\n	<dd>Default value: <var>15</var></dd>\n\n	<dt>type</dt>\n	<dd>Whether to restrict by the number of characters or words.</dd>\n	<dd>Required: no</dd>\n	<dd>Available values: <var>chars</var> or <var>words</var></dd>\n	<dd>Default value: <var>chars</var></dd>\n</dl>', 'if (txpinterface == \'public\')\n	{\n		upm_restrict_comment_size_error();\n	}\n\n// -------------------------------------------------------------\n// have to do a buffer workaround\n// because of current Txp plugin limitations\n\n\n	function upm_restrict_comment_size_error()\n	{\n		ob_start(\'upm_restrict_comment_size_buffer\');\n	}\n\n// -------------------------------------------------------------\n// make sure the textarea is marked as having an error (for styling)\n\n	function upm_restrict_comment_size_buffer($buffer)\n	{\n		global $upm_restrict_comment_size_error;\n\n		if (!empty($upm_restrict_comment_size_error))\n		{\n			$find = \'txpCommentInputMessage\';\n			$replace = \'txpCommentInputMessage comments_error\';\n\n			$buffer = str_replace($find, $replace, $buffer);\n		}\n\n		return $buffer;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_restrict_comment_size($atts = array())\n	{\n		extract(lAtts(array(\n			\'error_message\' => \'Please enter between {min} and {max} {type} in your message.\',\n			\'max\'           => 1500,\n			\'min\'           => 15,\n			\'type\'          => \'chars\',\n		), $atts));\n\n		$GLOBALS[\'upm_restrict_comment_size_error\'] = false;\n\n		$preview = ps(\'preview\');\n\n		if ($preview)\n		{\n			$message = ps(\'message\');\n\n			if ($message == \'\')\n			{\n				$in = getComment();\n				$message = $in[\'message\'];\n			}\n\n			$message = doStripTags(doDeEnt($message));\n\n			// if the message is completely empty\n			// the error condition is already handled by Txp\n			if (!empty($message))\n			{\n				switch ($type)\n				{\n					case \'words\':\n						$size = count(explode(chr(32), $message));\n					break;\n\n					case \'chars\':\n					case \'characters\':\n					default:\n						$size = strlen($message);\n					break;\n				}\n\n				$condition_low = ($min > 1 and $size < $min);\n				$condition_high = ($size > $max);\n\n				if ($condition_low or $condition_high)\n				{\n					$evaluator =& get_comment_evaluator();\n\n					$evaluator->add_estimate(RELOAD, 1,\n						strtr($error_message, array(\n							\'{min}\'  => $min,\n							\'{max}\'  => $max,\n							\'{type}\' => $type\n						))\n					);\n\n					$GLOBALS[\'upm_restrict_comment_size_error\'] = true;\n				}\n			}\n		}\n	}', 'if (txpinterface == \'public\')\n	{\n		upm_restrict_comment_size_error();\n	}\n\n// -------------------------------------------------------------\n// have to do a buffer workaround\n// because of current Txp plugin limitations\n\n\n	function upm_restrict_comment_size_error()\n	{\n		ob_start(\'upm_restrict_comment_size_buffer\');\n	}\n\n// -------------------------------------------------------------\n// make sure the textarea is marked as having an error (for styling)\n\n	function upm_restrict_comment_size_buffer($buffer)\n	{\n		global $upm_restrict_comment_size_error;\n\n		if (!empty($upm_restrict_comment_size_error))\n		{\n			$find = \'txpCommentInputMessage\';\n			$replace = \'txpCommentInputMessage comments_error\';\n\n			$buffer = str_replace($find, $replace, $buffer);\n		}\n\n		return $buffer;\n	}\n\n// -------------------------------------------------------------\n\n	function upm_restrict_comment_size($atts = array())\n	{\n		extract(lAtts(array(\n			\'error_message\' => \'Please enter between {min} and {max} {type} in your message.\',\n			\'max\'           => 1500,\n			\'min\'           => 15,\n			\'type\'          => \'chars\',\n		), $atts));\n\n		$GLOBALS[\'upm_restrict_comment_size_error\'] = false;\n\n		$preview = ps(\'preview\');\n\n		if ($preview)\n		{\n			$message = ps(\'message\');\n\n			if ($message == \'\')\n			{\n				$in = getComment();\n				$message = $in[\'message\'];\n			}\n\n			$message = doStripTags(doDeEnt($message));\n\n			// if the message is completely empty\n			// the error condition is already handled by Txp\n			if (!empty($message))\n			{\n				switch ($type)\n				{\n					case \'words\':\n						$size = count(explode(chr(32), $message));\n					break;\n\n					case \'chars\':\n					case \'characters\':\n					default:\n						$size = strlen($message);\n					break;\n				}\n\n				$condition_low = ($min > 1 and $size < $min);\n				$condition_high = ($size > $max);\n\n				if ($condition_low or $condition_high)\n				{\n					$evaluator =& get_comment_evaluator();\n\n					$evaluator->add_estimate(RELOAD, 1,\n						strtr($error_message, array(\n							\'{min}\'  => $min,\n							\'{max}\'  => $max,\n							\'{type}\' => $type\n						))\n					);\n\n					$GLOBALS[\'upm_restrict_comment_size_error\'] = true;\n				}\n			}\n		}\n	}', '9dc7833e43afb1e466aa23f0a8680ce2', '', '', 0, 5, 0),
('kch_lastfm_recently', 0, 'Kevin Chabowski', 'http://kch42.de/', '0.5.1', 'Add your recently played tracks from last.fm to your website.', '	<h1>kch_lastfm_recently</h1>\n\n	<p>Returns a unsorted list of your recently listened songs.</p>\n\n	<h2>Usage</h2>\n\n	<p>Insert this txp tag to your template:<br />\n<code>&lt;txp:kch_lastfm_recently /&gt;</code></p>\n\n	<p>Here is a table of parameters:</p>\n\n	<table>\n		<tr>\n			<th>Parameter</th>\n			<th>Needed</th>\n			<th>Default</th>\n			<th>Explanation</th>\n		</tr>\n		<tr>\n			<td>name</td>\n			<td>Yes</td>\n			<td><code> </code></td>\n			<td>Your last.fm account name</td>\n		</tr>\n		<tr>\n			<td>count</td>\n			<td>No</td>\n			<td>3</td>\n			<td>How many songs should be listed</td>\n		</tr>\n		<tr>\n			<td>cover</td>\n			<td>No</td>\n			<td>1</td>\n			<td>Whether album covers should be displayed, or not</td>\n		</tr>\n		<tr>\n			<td>track_format</td>\n			<td>No</td>\n			<td>{ar} &#8211; {s} ({t})</td>\n			<td>Format of track information (See beyond)</td>\n		</tr>\n		<tr>\n			<td>date_format</td>\n			<td>No</td>\n			<td>%d %h %Y %H:%M:%S</td>\n			<td>Time/Date format as used by strftime<sup class=\"footnote\"><a href=\"#fn16153874754c5696e133803\">1</a></sup></td>\n		</tr>\n		<tr>\n			<td>caching</td>\n			<td>No</td>\n			<td>1</td>\n			<td>Should we enable caching? (See section caching)</td>\n		</tr>\n	</table>\n\n	<p>And here is a list of <strong>track_format</strong> tokens (case sensitive):\n	<ul>\n		<li><code>{ar}</code> &#8211; *Ar*tist</li>\n		<li><code>{s}</code> &#8211; *S*ong name</li>\n		<li><code>{al}</code> &#8211; *Al*bum name ( will turn to &#8220;(?)&#8221;, if Album is unknown)</li>\n		<li><code>{t}</code> &#8211; The *T*ime, you&#8217;ve listened to the song</li>\n	</ul></p>\n\n	<h2>Output</h2>\n\n	<p>The output will be a unsorted list (<code>&lt;ul&gt; ... &lt;/ul&gt;</code>) with the <span class=\"caps\">CSS</span> class <code>kch_lastfm_recently</code> .<br />\nThe single list elements have this format:</p>\n\n<pre><code>&lt;li&gt;\n  &lt;img src=&quot;http://lastfm-or-amazon-server.foo/path/to/image&quot; alt=&quot;Album cover&quot; style=&quot;width:60px;height:60px;&quot; /&gt;\n  &lt;a href=&quot;http://last.fm/path/to/song/informations&quot;&gt;The parsed track_format&lt;/a&gt;&lt;/li&gt;&quot;;\n&lt;/li&gt;\n</code></pre>\n\n	<h2>Hidden errors</h2>\n\n	<p>If something went wrong, <code>&lt;txp:kch_lastfm_recently /&gt;</code> will not displaying anything, but it will write an <span class=\"caps\">HTML</span> comment with an errormessage. So if you can not see anything, first check the returned <span class=\"caps\">HTML</span> code, if there is a comment with an error message.</p>\n\n	<h2>Caching</h2>\n\n	<p>If you have enabled caching, kch_lastfm_recently will save the results to &#8220;/my/textpattern/installation/textpattern/cache&#8221;.<br />\nSo if you want to use caching, you have to create a directory called &#8220;caching&#8221; inside the textpattern directory and the <span class=\"caps\">HTTP</span> server must have write permission to it.<br />\nResults are cached for one minute. If you have multiple <code>&lt;txp:kch_lastfm_recently /&gt;</code> tags with different configurations, this is not a problem, because the cache file gets a unique identifier based on the parameters.<br />\nIf you have the possibility to create the cache directory and set the required permissions, you really should use this feature, because the last.fm <span class=\"caps\">API</span> calls can generate a lot of traffic, if your website has much hits.</p>\n\n	<p id=\"fn16153874754c5696e133803\" class=\"footnote\"><sup>1</sup> Documentation of <a href=\"http://php.net/strftime\">strftime</a></p>', '\n/* For internal use only... */\nfunction kch_INTERNAL_lastfm_api_call($function, $params)\n{\n	/* Prepare the API call */\n	$api_call_url = \"http://ws.audioscrobbler.com/2.0/?method=$function\";\n	$params[\'api_key\'] = \"68a0243a9eef21f92766800a084c566c\";\n	foreach($params as $p_key => $p_value)\n		$api_call_url .= \"&$p_key=\".urlencode($p_value);\n\n	$raw_response = @file_get_contents($api_call_url);\n	return $raw_response==false ?  false : new SimpleXMLElement($raw_response);\n}\n\nfunction kch_lastfm_recently($atts)\n{\n	$plugin_params = lAtts(array(\n		\"name\" => \"\",\n		\"count\" => \"3\",\n		\"cover\" => \"1\",\n		\"track_format\" => \"{ar} - {s} ({t})\",\n		\"date_format\" => \"%d %h %Y %H:%M:%S\",\n		\"caching\" => \"1\"\n	), $atts);\n\n	$img_default = \"http://cdn.last.fm/flatness/catalogue/noimage/2/default_artist_mega.png\";\n\n	/* Check attributes */\n	if(empty($plugin_params[\'name\'] ))\n		return \"<!-- lastfm-plugin: Invalid params -->\";\n	foreach(array(\'count\',\'cover\',\'caching\') as $testme)\n		if(!is_numeric($plugin_params[$testme]))\n			return \"<!-- lastfm-plugin: Invalid params -->\";\n\n	/* get cached data (if available and not too old) */\n	if($plugin_params[\'caching\'])\n	{\n		$cache_filename = getcwd() . \'/textpattern/cache/kch_lastfm_recently_\' . md5(implode(\':\',$plugin_params));\n		if(file_exists($cache_filename) and (filemtime($cache_filename)>(time()-60)))\n			return file_get_contents($cache_filename);\n	}\n\n	$output = \'<ul class=\"kch_lastfm_recently\">\';\n	$count = 0; /* We have to count all entries, because last.fm sometimes send more tracks */\n\n	$lastfm_query_result = kch_INTERNAL_lastfm_api_call(\"user.getRecentTracks\", array(\n		\"user\"	=> $plugin_params[\'name\'],\n		\"limit\"	=> $plugin_params[\'count\'])\n	);\n	if($lastfm_query_result == false)\n		return \"<!-- lastfm-plugin: failed to call last.fm. correct username? -->\";\n\n	foreach($lastfm_query_result->recenttracks->track as $track)\n	{\n		if($count++ == $plugin_params[\'count\'])\n			break;\n\n		$d_album = empty($track->album) ? \'(?)\' : $track->album;\n\n		/* Get album cover or, if not available, a picture of the artist. */\n		if(!empty($track->image))\n			$d_image = $track->image[1];\n		else\n		{\n			if(!($artist_image_pre = kch_INTERNAL_lastfm_api_call(\"artist.getInfo\",  array(\n				\"artist\" => $track->artist\n			))))\n				$d_image = $img_default;\n			else\n				$d_image = !empty($artist_image_pre->artist->image) ? $artist_image_pre->artist->image[1] : $img_default;\n		}\n\n		$datetext = $track->date[\"uts\"] > 0 ? @strftime($plugin_params[\'date_format\'],$track->date[\"uts\"]+0) : \' \';\n\n		/* WARNING: The following command is *very* ugly! */\n		$infotext = str_replace(array(\n			\"{ar}\", \"{s}\", \"{al}\", \"{t}\"), array(\n			$track->artist, $track->name, $d_album, $datetext),\n			$plugin_params[\'track_format\']);\n		$song_url = $track->url;\n		$output .= \"<li>\";\n		if($plugin_params[\'cover\'])\n			$output .= \"<img src=\\\"$d_image\\\" alt=\\\"Album cover\\\" style=\\\"width:60px;height:60px;\\\" />\";\n		$output .= \"<a href=\\\"$song_url\\\">$infotext</a></li>\";\n	}\n	$output .= \'</ul><a href=\"http://www.last.fm\" class=\"kch_lastfm_recently_lfm_credits\">Powered by Last.fm</a>\';\n	/* The TOS of last.fm forces us to credit them... */\n\n	if($plugin_params[\'caching\'] and ($track->date[\"uts\"]>0))\n	{\n		$cfh = fopen($cache_filename,\"w\");\n		fwrite($cfh, $output);\n		fclose($cfh);\n	}\n\n	return $output;\n}\n', '\n/* For internal use only... */\nfunction kch_INTERNAL_lastfm_api_call($function, $params)\n{\n	/* Prepare the API call */\n	$api_call_url = \"http://ws.audioscrobbler.com/2.0/?method=$function\";\n	$params[\'api_key\'] = \"68a0243a9eef21f92766800a084c566c\";\n	foreach($params as $p_key => $p_value)\n		$api_call_url .= \"&$p_key=\".urlencode($p_value);\n\n	$raw_response = @file_get_contents($api_call_url);\n	return $raw_response==false ?  false : new SimpleXMLElement($raw_response);\n}\n\nfunction kch_lastfm_recently($atts)\n{\n	$plugin_params = lAtts(array(\n		\"name\" => \"\",\n		\"count\" => \"3\",\n		\"cover\" => \"1\",\n		\"track_format\" => \"{ar} - {s} ({t})\",\n		\"date_format\" => \"%d %h %Y %H:%M:%S\",\n		\"caching\" => \"1\"\n	), $atts);\n\n	$img_default = \"http://cdn.last.fm/flatness/catalogue/noimage/2/default_artist_mega.png\";\n\n	/* Check attributes */\n	if(empty($plugin_params[\'name\'] ))\n		return \"<!-- lastfm-plugin: Invalid params -->\";\n	foreach(array(\'count\',\'cover\',\'caching\') as $testme)\n		if(!is_numeric($plugin_params[$testme]))\n			return \"<!-- lastfm-plugin: Invalid params -->\";\n\n	/* get cached data (if available and not too old) */\n	if($plugin_params[\'caching\'])\n	{\n		$cache_filename = getcwd() . \'/textpattern/cache/kch_lastfm_recently_\' . md5(implode(\':\',$plugin_params));\n		if(file_exists($cache_filename) and (filemtime($cache_filename)>(time()-60)))\n			return file_get_contents($cache_filename);\n	}\n\n	$output = \'<ul class=\"kch_lastfm_recently\">\';\n	$count = 0; /* We have to count all entries, because last.fm sometimes send more tracks */\n\n	$lastfm_query_result = kch_INTERNAL_lastfm_api_call(\"user.getRecentTracks\", array(\n		\"user\"	=> $plugin_params[\'name\'],\n		\"limit\"	=> $plugin_params[\'count\'])\n	);\n	if($lastfm_query_result == false)\n		return \"<!-- lastfm-plugin: failed to call last.fm. correct username? -->\";\n\n	foreach($lastfm_query_result->recenttracks->track as $track)\n	{\n		if($count++ == $plugin_params[\'count\'])\n			break;\n\n		$d_album = empty($track->album) ? \'(?)\' : $track->album;\n\n		/* Get album cover or, if not available, a picture of the artist. */\n		if(!empty($track->image))\n			$d_image = $track->image[1];\n		else\n		{\n			if(!($artist_image_pre = kch_INTERNAL_lastfm_api_call(\"artist.getInfo\",  array(\n				\"artist\" => $track->artist\n			))))\n				$d_image = $img_default;\n			else\n				$d_image = !empty($artist_image_pre->artist->image) ? $artist_image_pre->artist->image[1] : $img_default;\n		}\n\n		$datetext = $track->date[\"uts\"] > 0 ? @strftime($plugin_params[\'date_format\'],$track->date[\"uts\"]+0) : \' \';\n\n		/* WARNING: The following command is *very* ugly! */\n		$infotext = str_replace(array(\n			\"{ar}\", \"{s}\", \"{al}\", \"{t}\"), array(\n			$track->artist, $track->name, $d_album, $datetext),\n			$plugin_params[\'track_format\']);\n		$song_url = $track->url;\n		$output .= \"<li>\";\n		if($plugin_params[\'cover\'])\n			$output .= \"<img src=\\\"$d_image\\\" alt=\\\"Album cover\\\" style=\\\"width:60px;height:60px;\\\" />\";\n		$output .= \"<a href=\\\"$song_url\\\">$infotext</a></li>\";\n	}\n	$output .= \'</ul><a href=\"http://www.last.fm\" class=\"kch_lastfm_recently_lfm_credits\">Powered by Last.fm</a>\';\n	/* The TOS of last.fm forces us to credit them... */\n\n	if($plugin_params[\'caching\'] and ($track->date[\"uts\"]>0))\n	{\n		$cfh = fopen($cache_filename,\"w\");\n		fwrite($cfh, $output);\n		fclose($cfh);\n	}\n\n	return $output;\n}\n', '8b3570f2c859aac7c49dde98d45c7d40', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('abl_droploader', 0, 'Andreas Blaser', 'http://www.blaser-it.ch', '0.20', 'Drag & drop file upload utility', '<h1>abl_droploader</h1>\n\n<p>DropLoader for <a href=\"http://textpattern.com/\" rel=\"nofollow\">Textpattern <span class=\"caps\">CMS</span></a> allows you to upload multiple images at once, simply by dragging files from the desktop onto the browser window. The user interface of DropLoader is a transparent area, shown above Textpatterns own UI.</p>\n\n<p>A special feature of DropLoder is the fact, that it hides the standard upload-form, but uses it in the background for sending the data to server. The form-data will therefore be processed by Textpatterns regular image upload-script, txp_image.php.</p>\n\n<p>DropLoader is enabled in two places. On the image-tab (Menu: Content &gt; Images), where it replaces the standard upload-form, but also on the write-tab (Content &gt; Write). This makes it possible to upload images right from there, without a need to switch between the write- and image-tabs. Uploaded images will be automatically assigned to the current article (field article image), if DropLoader is opened directly from the write-tab.</p>\n\n<p>It is also possible to automatically assign a category to the uploaded images. Simply select a category, <em>before</em> files are dropped or selected.</p>\n\n<p>Both, “Drag &amp; Drop” and the upload functionality is based on the jQuery plugin <a href=\"https://github.com/weixiyen/jquery-filedrop\" rel=\"nofollow\">jquery.filedrop.js</a> by Weixi Yen. Thanks!</p>\n\n<h2>Browser Support</h2>\n\n<p>The jQuery-plugin filedrop requires the “File Reader <span class=\"caps\">API</span>” and the “Drag &amp; Drop <span class=\"caps\">API</span>”. <strong>DropLoader disables itself, if these <span class=\"caps\">API</span>&#8217;s are not supported.</strong></p>\n\n<p>Recent versions of <strong>Firefox</strong> and <strong>Google Chrome</strong> supports all features and work perfectly. However, Internet Explorer (incl. IE9), Opera and Safari currently do not support all of the required <span class=\"caps\">API</span>&#8217;s, so DropLoader will not be available with these browsers.</p>\n\n<h2>Features</h2>\n\n<ul>\n	<li>Enables uploading of multiple images at once</li>\n	<li>File selection using drap &amp; drop or the file open-dialog</li>\n	<li>Hides the standard upload-form in the images-tab</li>\n	<li>Automatically assign image-category to each uploaded image (optional)</li>\n	<li>Automatically create thumbnail, if thumbnail dimensions are available</li>\n	<li>Fully compatible to the standard image upload-form: Form data is posted to the same script (txp_image.php) for processing</li>\n	<li>Enables image uploads directly from the write-tab (Article detail/edit view)</li>\n	<li>Automatically assign uploaded images to the current article&#8217;s images-field (only if DropLoader has been opened directly from the write-tab)</li>\n	<li>Supports localisation (l10n) using textpacks.</li>\n	<li>DropLoader can also be used by other Textpattern plugins, e.g. from file- or image-pickers/selectors.</li>\n</ul>\n\n<h2>Requirements</h2>\n\n<p>Textpattern 4.4.1+.</p>\n\n<p><strong>Important:</strong> To be able to edit preferences, the plugin <em>soo_plugin_pref</em> is required. Hard coded default preferences apply, if soo_plugin_pref is not installed.</p>\n\n<h2>Author</h2>\n\n<p>Andreas Blaser (<a href=\"http://www.blaser-it.ch/\" rel=\"nofollow\">web</a>)</p>\n\n<h2>Installation</h2>\n\n<p>This plugin can be installed and activated as usual on the plugins-tab.</p>\n\n<p>DropLoader requires some additional resources (jQuery plugins, stylesheet), which are installed when the plugin is activated.</p>\n\n<p><strong>Note:</strong><br />\nWhen upgrading abl_droploader from a previous version, you may need to clear the browser cache.</p>\n\n<p>The following files will be installed on plugin activation:</p>\n\n<ul>\n	<li>/ (site-root)\n	<ul>\n		<li>res/\n		<ul>\n			<li>css/\n			<ul>\n				<li>abl.droploader-app.css</li>\n			</ul></li>\n			<li>js/\n			<ul>\n				<li>jquery.filedrop.js</li>\n				<li>jquery.droploader.js</li>\n				<li>abl.droploader-app.js</li>\n			</ul></li>\n			</ul></li>\n			</ul></li>\n			</ul>\n\n<h2>Localisation</h2>\n\n<p>DropLoader uses textpacks for localisation. The distribution package already contains textpacks in english (en-gb) and german (de-de).</p>\n\n<p>The name of all language strings begins with ‘abl_droploader_’.</p>\n\n<p><strong>Note:</strong> Starting with Version 0.13, the english textpack will also be installed under the language-key for the current site language, if this is <strong>not</strong> en-gb or de-de. These strings can then be translated to the desired language, for example using the snippet editor of the <span class=\"caps\">MLP</span> (Multi-Language Pack).</p>\n\n<p>Thanks to Stef Dawson for this tip!</p>\n\n<h2>Changelog</h2>\n\n<h4>Version 0.20 (2013-02-28)</h4>\n\n<ul>\n	<li>Improved installation procedure:\n	<ul>\n		<li>Log errors, warnings or notices in servers error-log if something remarkable happens.</li>\n		<li>Set plugin load-order to 9 in database table txp_plugin.</li>\n	</ul></li>\n	</ul>\n\n<h4>Version 0.19 (2013-02-21)</h4>\n\n<ul>\n	<li>Write Pane: Rendering of the droploader open-link changed. Function ‘wrapRegion’ will be used, if available (Txp 4.6).</li>\n	<li>Correction in jquery.droploader.js: Ignore drop-events when the drop-area is not visible.</li>\n	<li>Cleanup php and javascripts (remove commented code).</li>\n</ul>\n\n<h4>Version 0.18 (2013-02-19)</h4>\n\n<ul>\n	<li>Resolved a compatibility issue with jQuery 1.9 (Txp 4.6-<span class=\"caps\">SVN</span>): Use ‘delegate’ instead of ‘live’ for event-handler attachment (live-method has been removed in jQuery 1.9). The delegate-method is available in jQuery Versions 1.4.2 or newer.</li>\n</ul>\n\n<h4>Version 0.17 (2013-01-26)</h4>\n\n<ul>\n	<li>Changed plugin load order from 5 (default) to 9 (low priority). The reason is, that there may be other plugins (like <strong>smd_thumbnail</strong>), that uses the callback <strong>image_uploaded</strong> from txp_image.php. Because DropLoader does a <span class=\"caps\">PHP</span> exit within that callback, other plugins <strong>must be called before DropLoader</strong>. Otherwise the callback-event is not fired for these plugins.</li>\n	<li>Added a new option ‘article-image fields’, which is a comma separated list of article-image <span class=\"caps\">CSS</span> field-id&#8217;s (default: #article-image). When multiple fields are given, the image-ids&#8217;s are inserted into all of these fields. Use #custom-n for the article custom fields, where “n” is the field-number.</li>\n	<li>Corrected a typo in the textpack: <em>abl_droploader_prefs_custom_stylesheetb</em> renamed to <em>abl_droploader_prefs_custom_stylesheet</em>.</li>\n	<li>New entry added to the textpack for the new option in the prefs-panel.</li>\n	<li>Correction in installation procedure: properly add new options to the prefs. Version 0.16 failed to do that correctly.</li>\n</ul>\n\n<h4>Version 0.16 (2013-01-16)</h4>\n\n<ul>\n	<li>Enable DropLoader also after editing an image. I forget to change this in 0.15.</li>\n	<li>Added two new options for a more flexible UI styling. Option ‘use default styles’ (default: yes) and ‘custom stylesheet’ (default: empty) which is a stylesheet (path/filename) of your own. The custom stylesheet will be included <em>after</em> the default stylesheet, if this is also enabled.</li>\n	<li>Two entries added to the textpack for the new options in the prefs-panel.</li>\n	<li>Added an installation note concerning upgrading DropLoader and browser caching.</li>\n	<li>Cleaned up jquery.droploader.js (comments removed)</li>\n</ul>\n\n<h4>Version 0.15 (2013-01-07)</h4>\n\n<ul>\n	<li>Do not override thumb dimensions on installation if they are already set.</li>\n	<li><del>Enable DropLoader also after editing an image.</del></li>\n	<li>Better error handling in JavaScript after upload error.</li>\n	<li>Default for option “reload image list after upload” changed to false, because pages may contain post-data.</li>\n</ul>\n\n<h4>Version 0.14 (2013-01-02)</h4>\n\n<ul>\n	<li>Avoid display of a <span class=\"caps\">JSON</span>-String like ‘{“status”: 1,“image_id”: 123}’, for example after editing a single image.</li>\n	<li>Enable DropLoader also on empty image-list (no images, no search result), after doing list operations (multi-edit) and after changing the page-size.</li>\n	<li>Better integration with admin themes:\n	<ul>\n		<li>Avoid overlay of the close-button for the <strong>Hive</strong> admin-theme (Txp 4.5.x).</li>\n		<li>Write Tab: Rendering of open-link changed.</li>\n	</ul></li>\n	</ul>\n\n<h3>Version 0.13 (2012-05-25)</h3>\n\n<ul>\n	<li>Installation procedure corrected for international users (see ‘Localisation’ above).</li>\n</ul>\n\n<h3>Version 0.12 (2012-05-22)</h3>\n\n<ul>\n	<li>Edit preferences: Show default values (not editable) if soo_plugin_pref is not installed.</li>\n	<li>Thumbnail size not set: If thumbnail defaults where never set (e.g. in a fresh Txp-install), DropLoader sets default values (thumb_w/h: 100/100, thumb_crop: 1) for these preferences upon installation.</li>\n</ul>\n\n<h3>Version 0.11 (2012-05-21)</h3>\n\n<ul>\n	<li>fix for “white screen” if soo_plugin_pref is not installed: Use hard-coded defaults.</li>\n	<li>Text of Open trigger changed from DropLoader to Upload Images</li>\n</ul>\n\n<h3>Version 0.1 (2012-05-18)</h3>\n\n<ul>\n	<li>Initial release</li>\n</ul>', 'if (@txpinterface == \'admin\') {\n\n	// privs\n    add_privs(\'abl_droploader\', \'1,2,3,4,6\');\n	add_privs(\'plugin_lifecycle.abl_droploader\', \'1,2\');\n	add_privs(\'plugin_prefs.abl_droploader\', \'1,2\');\n\n	// inject js & css in html-header\n	register_callback(\'abl_droploader_head_end\', \'admin_side\', \'head_end\');\n	// trigger (button/link) elements\n	register_callback(\'abl_droploader_article_ui\', \'article_ui\', \'extend_col_1\');\n	// following callback has been replaced by javascript!\n	//register_callback(\'abl_droploader_image_ui\', \'image_ui\', \'extend_controls\');\n	// ajax interface\n    register_callback(\'abl_droploader_ajax\', \'abl_droploader\');\n	// intercept image_uploaded-event from txp_image\n    register_callback(\'abl_droploader_image_uploaded\', \'image_uploaded\', \'image\');\n	// lifecycle\n	register_callback(\'abl_droploader_lifecycle\', \'plugin_lifecycle.abl_droploader\');\n	// prefs\n	$abl_droploader_prefs = abl_droploader_load_prefs(); // load hard-coded defaults\n	@require_plugin(\'soo_plugin_pref\');\n	register_callback(\'abl_droploader_prefs\', \'plugin_prefs.abl_droploader\');\n\n}\n\n\n	/**\n	 * handle plugin lifecycle events\n	 *\n	 * @param string $event (plugin_lifecycle.abl_droploader)\n	 * @param string $step (enabled, disabled, installed, deleted)\n	 */\n	function abl_droploader_lifecycle($event, $step) {\n		global $prefs;\n\n		$msg = \'\';\n		$msg1 = \'\';\n		switch ($step) {\n			case \'installed\':\n				if (function_exists(\'soo_plugin_pref\')) {\n					abl_droploader_prefs($event, $step);\n				} else {\n					$msg1 = \'Please install <em>soo_plugin_pref</em> to edit preferences (Default preferences apply).\';\n				}\n				$rc = abl_droploader_install();\n				if ($rc == E_ERROR) {\n					$msg = \'<strong>An error occurred. Please check the servers error-log.</strong>\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				} elseif ($rc == E_WARNING || $rc == E_NOTICE) {\n					$msg = \'<em>abl_droploader</em> successfully installed. Please check the servers error-log.\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				} else {\n					$msg = \'<em>abl_droploader</em> successfully installed.\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				}\n\n				// set txp\'s thumb prefs if they are not set (fresh install)\n				if (!isset($prefs[\'thumb_w\'])) set_pref(\'thumb_w\', \'100\', \'image\', PREF_HIDDEN);\n				if (!isset($prefs[\'thumb_h\'])) set_pref(\'thumb_h\', \'100\', \'image\', PREF_HIDDEN);\n				if (!isset($prefs[\'thumb_crop\'])) set_pref(\'thumb_crop\', \'1\', \'image\', PREF_HIDDEN);\n				break;\n			//case \'deleted\':\n			//	break;\n		}\n		return $msg;\n\n	}\n\n	/**\n	 * add plugin prefs to database\n	 *\n	 * @param string $event (plugin_prefs.abl_droploader)\n	 * @param string $step (-)\n	 */\n    function abl_droploader_prefs($event, $step) {\n		if (function_exists(\'soo_plugin_pref\')) {\n			soo_plugin_pref($event, $step, abl_droploader_defaults());\n			return true;\n		} else {\n			$msg = \'Please install <em>soo_plugin_pref</em> to edit preferences (Default preferences apply).\';\n			pagetop(gTxt(\'edit_preferences\') . \" &#8250; abl_droploader\", $msg);\n			$default_prefs = abl_droploader_defaults();\n			$html = \'<table id=\"list\" align=\"center\" border=\"0\" cellpadding=\"3\" cellspacing=\"0\">\n<thead>\n<tr>\n<th colspan=\"2\"><h1>DropLoader default preferences</h1></th>\n</tr>\n<tr>\n<th>Option</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n\';\n			foreach ($default_prefs as $key => $pref) {\n				$html .= \'<tr>\n<td>\' . htmlspecialchars($pref[\'text\']) . \'</td>\n<td>\' . htmlspecialchars($pref[\'val\']) . \'</td>\n</tr>\n\';\n			}\n			$html .= \'</tbody>\n</table>\n\';\n			echo $html;\n			return false;\n		}\n	}\n\n	/**\n	 * get plugin prefs default values\n	 *\n	 * @param string $values_only\n	 */\n	function abl_droploader_defaults($values_only = false) {\n\n		$defaults = array(\n			\'imageMaxUploadCount\' => array(\n				\'val\'	=> \'10\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_image_max_upload_count\'),\n			),\n			\'reloadImagesTab\' => array(\n				\'val\'	=> \'0\',\n				\'html\'	=> \'yesnoradio\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_reload_image_tab\'),\n			),\n			\'useDefaultStylesheet\' => array(\n				\'val\'	=> \'1\',\n				\'html\'	=> \'yesnoradio\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_use_default_stylesheet\'),\n			),\n			\'customStylesheet\' => array(\n				\'val\'	=> \'\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_custom_stylesheet\'),\n			),\n			\'articleImageFields\' => array(\n				\'val\'	=> \'#article-image\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_article_image_fields\'),\n			),\n			//\'fileMaxUploadCount\' => array(\n			//	\'val\'	=> \'10\',\n			//	\'html\'	=> \'text_input\',\n			//	\'text\'	=> gTxt(\'abl_droploader_prefs_file_max_upload_count\'),\n			//),\n		);\n		if ($values_only)\n			foreach ($defaults as $name => $arr)\n				$defaults[$name] = $arr[\'val\'];\n		return $defaults;\n	}\n\n\n	/**\n	 * Load plugin prefs\n	 *\n	 * @param string $values_only\n	 */\n	function abl_droploader_load_prefs() {\n		return function_exists(\'soo_plugin_pref_vals\')\n			? array_merge(abl_droploader_defaults(true), soo_plugin_pref_vals(\'abl_droploader\'))\n			: abl_droploader_defaults(true);\n	}\n\n	/**\n	 * install plugin resources (css, js)\n	 *\n	 * @param mixed $resources\n	 * @param string $path\n	 */\n	function abl_droploader_install($resources=null, $path=\'\') {\n		global $prefs;\n\n		$state = 0;\n		if ($resources === null) {\n			$path = $prefs[\'path_to_site\'];\n			$resources = array();\n			$resources[\'/res/css/abl.droploader-app.css\'] =  \'H4sIAAAAAAAAA71X207jMBB9pl9hqVppF+EQUtKtwtP+xL6unNhJLRw7st0LIP59x3aSpm3a\nwAotoCq+zZw5Z3occkVf0NvsRm2ZLoXa4X2G1pxSJp8Gky8ZMoVWQjzN3mf3t4jXpGJIcGPR\n7f1s7od/CiUt7EERyQWmWjVCEco0Vg2TLgVEIjZDmldrC8FroisusR9m6IHVMFdCCP+MfjNN\niSR36JfmRNwhQ6TBhmlettvwjoWTuRLUwZrNT/IWQhnmElNuGkGghlyo4hnON8pwy5XMEMmN\nEhvLYNKqJkOJh9FiilI/agilXFYZWjR79BDDx7J9gMVcacgFmGEKQnGK5kVRuHBsbzFlhdIk\npJJKujRAqeUFEZgIXkkH31pVdwfayYJJy3RPSPIPhNwUSigANk+SxOEkxXOl1UZSmCpLd+gV\nc0kZ6L2AH3dgo4070Sgesr+PU5qtXV84YrsUpUtxroDbcpnqGB4EK2142nFq10BjHH+D0bot\nJQwhMvTcafBS6Rpx2WwsevNdOLZO+faoA4IIZ4WhaOOfgJFSfRXsy4piw18ZbGsb7Eg9CbCJ\nuKqfrnLyPUnTO3T4iKNV+uNc1JFKDROssLjkgpkvK7UDu1wuR8E+xoCz/4ijnx8E68Yl3zOK\nAeYxWj89XWHzSQdIfT2Xyg6mBac2VvnlKY0XVxQ+tYIafFeMtSf2BosLAlGZ+GRBq6GlBZs9\n6IM74YJp9Vbn903hCDw7OK2Xh64JXT2iC68r3Gi25Wx3LKVmAkxy+6nGg0byBtwpsnDG7GQZ\n3FuDq8zdVdjYF8GwfWlYb8d9xZfEHK3DsZCrvaui/yKslh7PJVEBgWT4sNujvxoc6Arc7nFL\nwaHm/Wmk/iJaHC6iYPO4Vq8uHjZrQh0nMfwmsGsex65o6Mv8mdtrWy4vTagcTcndFpbGoYhe\n3Hh5JO7DQNwPi3QOI6Ig+vBtZ9AgXWrfBxou1xZsmPeWZTo2r623bF7boq6s+terBgoIr1iN\nVpVmxoxdcG2r9Fsucdx3SuI57XvpmOL0wPCHfekMQjQOZmBJh6/1yQ3SworH7SlJV0sad68C\nTj4ixHV2vP1Ps+MNZ5Ge9N/jkKquO0e6cYSSk7z/iRKmtdII/gpVN4K5JKiGrK6Jxthp1zoX\nm6ZmmovJu9DAtef/g/gL2bND4HUMAAA=\';\n			$resources[\'/res/js/abl.droploader-app.js\'] =  \'H4sIAAAAAAAAA60Y227bNvTZ/go2DSK5sZWkLx3iukPRdg/DLljbPbWFwUi0zUQWBYpK7KX6\n951DUhRlSUk6LAhsied+5TkOV2UWKy6y8HhC7sfj0S2VJGErWqZqKXKEFGQBkNHojidrppY8\nuSQBvUpniRR5KmjCZDAFcMFSFqvliqesWMYpLQrAM4czfaix+Jau2TKmiq2F5IBpMABVgxER\n+V5qiaOcSrrN6JYBXG0YQjXaaEt3muclMQzh3UhuQfk/zEcotbpLPNZopUyBL88StovyTW44\nr2iaXtH4xtipiWeG0MA3DC0urIKjIN5QWTAVAHKpVrOfAjyu4KNC9PTiPLOoQZyKgiHeyfPd\n+Xnyam4YmvOl4irV0Hf4St6DE35rnDsKmJRCLrdMbUSCaL/rJ3J/b46qiiSCFSQTirAdLxTh\nGYEoRU2UZjTPo+siQg1BwWpugg1B1l5rEJcbniQsg7ArWbI2DA6zMk3nkCnH0SqLQESj6ts8\nB7jLKKOZSSstyhzU+TTiGVfkssG36TaxruUrEj6zJBEvPpV5LqRiSTiZEMlUKTMCwSrYXGOj\nGcD4OGI7xbIkvK+mh3k8JbWEuRNwHAbPc4wxlYrHEIFJlLJsrTbkDTmvNRlZ4NLk0oqzNEFH\nsDvyVkq6Dy3D0UpIEqKlHMDnc/h6TXpoIbcKK2ZOTk+5k9Mn6Av/hnaFA3wAXEuvxs1nbZsJ\nJRZcY9li4ZkGSJHJ7xlovw0mVpMIsgX9GLzmWV4qovY5WxwZdkeEJ4sjCP2ySYwjgnXaPb2l\naQnHF0fk7M3X7AfYN5rXrG3X2DumyDOw1jdmYyZEA30GbQ+CxviIXtNdWHvfNISf2S3L1KJt\nyEmhWL7A/odeWiZU0ROu2LZYOElWxBRr3hQt/CHiZ7AN+F4XojmnxT6LL00C12dFGccM26ar\nCKRukkNbh0dRoagqC23MReAhjB62nWjiQ4XnbWpUv0bFZweuxq3vquV4SKMrkewhyRJgugbZ\nkHztW2JmGuCUQMfj8U3k3x+NycyZU5e+JnMl1q38WpGnqGD6Odg+A9vJiwNlhnX5P6Uar2vR\nG5qt2WOyO2UM2Q+1rTa8MM+Tx33TauCdkvdCoaunMe3AEkAFTXQwxiNi/pxYVChiNN6Ezgiw\nwWqgb8OR18W9LK8ttXzCIJF0vQbJLMBbLBFxuYWKjGLJwL0fUoZvYVDkFPrJhJyckBDbh1iR\nX+DW/6gvaPIMaqOEq33FM5YEvhICGs+BdPI0W8auCrsu7Lk1PK/7Hg4kw4GhDlq/m4sU/Pxe\n3GU9CvSRkKg1ak0imiTvcAgL9ay24juWzHJRNGJ7L3wvdYyYjdqmwO2KY6++Yfsyf7RurYtY\ndLfhsb5tXr7ygQOF3Z/AfuNx2ew9VX6am/hqtj3p9QSnSbYVt+whvw0H6++8J1RDQ9XclbZ1\ncJkZF/verW3smb78RDTzi+4xrTzEwpBMj7ym5St6hWXhJWgqoKOAwpHBC3u92o2K8XPCCiXF\nngxXcl+g9WiM7UOrbzG+mO9vlr5NXgNxZEj3uu9NzdwV5VIogbWPIYhZFMPwDoPSWvcLwLow\nrbEiDFQnKNA2CsMSchO6hLi6xn5Mvn8nz0gzs3bNwHm1pYST5EvRlDCG4rwewsDwGQbS0N2s\nkT/HT8k9CdxIb3WqrM7oorMXpNiIO6KJCPzHYpunDH0N6EUBUSUvznBvshEgtUAFH1MzJ4Gi\nWfrySk7M0L3CCRWPIRcWxLVIzBZ37s1IbqQFO+Ue+7HBckmEMMnWbJfbkfgjW3/Y5WFwfx+Q\nU0t2SoKqwitvzV1ao4pYDvAF+Qc3YMxCw6hW/Ism/uYPGlVtgzZJBxDrqdamj+fZ16/Z2Rpk\nvwYCNzD60XqQ7JOSPFtHKym272DXeyfg/rs4nzTZ7O4MoK4jhxleTcLrv0om93hbjo+97uD2\n7KY7tFdtulKwTaWpV1q6RVkz0X79flDtfWvKM9MyOuF8eEPp3U6Q7HZoF8E9xEwjXne6PZi3\nR0huNL8WPAuDqcuGVjwQ8RQop5hBA/h1B31Il9saexjNzGDhYY7527tZlsem3/YtvWHgDTY4\nkriNF2aXZQoCSqxUPXnZfk+VgkkAQcHEjEZaL/sTRcqzG7Kwv4m0QThuaJDroT1gz+nHEbSE\nXz/9+UeoDfzR7QYaqt1Z+peSx1aSPrO8LcQDzPsorLVdCgTUFN0y+q8rUA+noXWolQOfoAPk\nh7cnfnYuhiEy3e5dF+/Dakr/wd8ssA8MTqf+5t8sLnYV7/jXa3FdjvUe8PgcMiiww3QyJ2dn\nBEcwggdu4LLhEnAbCCyeHHp0R2PMIcOAZwWTiuCBb0BnTYsO1jRcDJ68ng78btLXG2BsABbd\n3Bom0ZoMTl8uYSq8ZObjfwFwbFnExBUAAA==\';\n			$resources[\'/res/js/jquery.droploader.js\'] =  \'H4sIAAAAAAAAA7UaXXPbNvJZ/hWILy2pVKaVztyLHXcmlyZzmUnSXOI+3DQdDSxCEhOS4IBQ\nbJ+q/367iw8CIqUozcQPkrjAfmC/sXS6WNdzXcg6fThmm5OT0WeuWC4WfF3qll0BaDRaFKXI\nlWwu6GnUcMWrmlfigiVNMU8mCKz4HW5rL9jjaQQo/gf7fibQWpWAgpRKyXOhsmbVGOwFL8sb\nPv80K3LYgXgv62atzWLONQfWW3pYCcRs8RkeCVZUfClmc67FUqpCtLNWlGKugRDhG8lF1eh7\nfLwRC6nEcz5fXTB/djg6U0KvVc20WotLRoT5Qgv1tCwDbKGUVMHzmo7yXnOlRd6Dvyjqol1F\nC42SSyXa9vcGjhWtlI+ntdVwUS/kTIs7PMOvID4jTbKVUOLJjWLnv5jPD7VUbF4W809MS2ZO\nbbYaxYGws6L+zMsinyFY3zdos2e8rqVmRkS22eASmnO7zdhvdXnPSKEt40owsIu8FTlLPzbL\nCfvYCPhcFosJa+rl+EHH5kbJ21aoGRCeteumkUYfyX/lWjG7yHIJVJG13cH+ff361T9JYitN\nG5DUUs4qXt/PrF8l11IyBJgjPmBvS8Fb4c692TiP224Z16ySrQ6o4QqRLLlaohbCcxt1wiqj\nVYsGZze8Z0Y4OtBm4x5mjplcWCXO5brWADDWcvsyQ62WfWLXaFHS82ZDnoXkzI+MvZGW0C3t\n2iHXykp8JcG/IzrG2Mloe2kTg9E1ULOZ4bbIlwK8VK+KduKfTRR3cU4SQ9RVF6xel6ULyht5\n1wEMZWfrIehsXvIW/cAAzzpHx3DpcCh43F5zkjOEJSb8xOdC3M6Mi3dIMdyjF9XyzC4lYfTe\ncBXieqBHJOHO3ArhVvADiEen7mAe04LOAJaEmtc8ysdksAs27ZINOsDU5yggNSV0Z7cFLslG\ntyGG0SyQ/eNPEqddmofY3pCKqqaEdAVryZO8+MxI2KvT0BKnkI4S9hP6Jv0lT5pfnpw3PWjb\n8JoV+dUpvynPOh85m5eyFaeAghtirOTJOTBFmJPLmWufYHa9L1Owh0yNat7dBdvA7pRfY2Qn\nxWGKzugDZCMJ3a5hsn1gpIWT0cNsUWed/kADvpqBleGrNfV8VCxYivGZQWSm03FWy1y8gbTH\nrkBpL3579zoZm6pz/ohBtZCfIN3Lmpl4vVW8sengDCHs0TkVKKBpuWQ+5tkDoLiuoXkoapFb\norT1YZr8A07CeijjDFLwUq9QlqnDGJG0yBlqb2q0hg4zSAFgySnYKhlfEvKWPtHfQSP7+dJm\nSI0lFJC+nD7NfYOkAzSOknWIt5H2JJY4Nldgo7O5rDUHGyhrLcsBhb00CX3UcSGrEnv8lUwo\nVxDHeE+2KnKRji+dS1mdgj7AjcCThXZ+RA+pEdo2VUjTsI7omhN+wVAndFQguW6YReB1zlxT\nyppyvSxqc1TMb0gug95J1HkKTaNvZiedaieOEQmJSJktSEaWla5KYyQ86YNAOtCF5ZsFPes4\ng3Bu0qQCPkVTimRsVXEEJtdaBZgTFlDxKrPOmXla/cI45KCGLRqPWtl0J50bE32BLjota5xf\nZKiaFGxwDRpOCQX71sx3rBPsuqmJHh9PHlO+40AxsY8NFYmx3WM0pwtNatuzeUbrx8nizsib\nBp2Hdu25XIxdIPVIGQ86go337JW8NT2w2Qp+yfKCl3JpfBqo3cj8PhlnOawvQZIvkU/oTpCF\nDVhXHMTxrklkbCCLrNWyeVlVAmTT4i1s5iALXZ7CSAcCrSDl+APuD1UXdZ5/ShUrvGHG4vmF\nCVA+Z9DuMnzCPlbD74ezFy9fPX/PRK3VfXwrjek4eP+mOrwPlrobbLwFQP0r7F6lxnfYeJsF\nT0gF5tbas1qUXKkVIvkufQkTmcYrjM6MMw6W5JFz1HizITKiTjPzPSY2Frhq04utXKYOGYZ4\nP79WvG4XUHEihuzHH1m8fqRYfZSvF27fWeE+vBZ7yT0OSzPZx9QclxSiJs7HT3fnoBp/qEn0\nOL1rg0M93CZ+sQv1DHYvF4b+8fhDd6JDNBzQOqdXzG5G7JQVNBt2f8xzF3NIoiEaXrF9Ajs6\n72N3WttF3tVnhAvZqBXYlmADhu7LtJQnvkckCNLzJXkMqVtBmofU8lQpfp/uOQJ4J7qdB0Ed\nAI/UqzSZ/uBUjSXktZEuTSL9U1K1hb6XonF8RlktHmL5pFNMKLomDKLLBSmyeonqT3EpZBVR\niXFDZoPzN6LlMxxJSTS7zQE7S6g3SevL7bY40g/JDobUXr06ALr6D8mQVSzHAT59rXYjwL54\ngNJAEwq/dIF1dbHwWqa85G7pP/10ebz4yeNp5xgRBs/zZ+i3aZLLWiRBDWmEmkPNBKuCj77m\nepUpSId5mqaxIHg9IYAZMYzZIwbcxuyc7STSr/XlQIBQ6/GYImvW7Sp1SrOdmbsdhT7odH5Q\n06Gp7FS3q7ZKGcydS2yGM4tFcSfys0ai9FHL3QuvUagsZ8Tvqe/2ttAQKrDb19Q5jkiTf5kJ\n7Bup37vhbHJhUzcOfYxq+y303tGubai7cKUs5wbmqV+9UYJ/ugxFuZbyNa/vX9D07lgZ4lnw\nhG3Ylzo7tv37IqJsIOYr7BeOFjEeMKOIbr58Qb8yalQDqb41OGLJ7S334nsQD5ohcsQgPrrG\nM3RU77fekSFCdl3Weegh62yDIO3exoSvbewIRV9DXMu1Tk8xTE8H+7JTOLQSlfyMY4zTCYbS\ndN8BoinPIdP3XxKg6d0INqQ5YXE6u9jRkdGonevuNqad32D+wIENCfVRFjW0pPT6wpsu7AYq\np8ioJ+6OGxpoyv76i+1qYZ/VDiql965jn06Cc8USHiQ/8O7jOyt9u0/377Uq6mW2ULJ6tuLq\nmcxF+njq8XgplO6M0OtvvL/HUrqBX3eNdi7PjAO/NY1w6u/yA+1x4OxujASXcbo/hnM0PHTR\n6mLemnt5wIlumn5aMNTAxzwGrlPTAN75WgR2vmaBA68mCB68m+hOQ7MTe2wcApRyzkszTIkP\ns9O2mjFD8CaBevPdtwrjiUkOlSnUoIOiWiYTh2MsQ1SUsDP4Gkhh+XhHADsuNRRMwsUr5tSc\n1EBXoliutAObSQq9HJc1vZ+9CocAm04gO/5q1Rwk8pdbMBpUAes/ETn8etr+Cu3g7+9eBd20\nPYy9317LQVcylIJuMlTCjjnIngzfTEtQoyD126vTjlFcZJu5Id3LIWDr8ucbZU6KecqA+3MF\nD2eJGzzjO0ZEUGYAxIra7vJZy9hqKe4aa6t3Yvn8rkmTzQa926BBHdxucRC7LHxORRFxhA5f\noEnwjrlIDSEn+B+E/GdUuNwZ6Eg0KafBqJVmiOb5hw/1+RJ4Rzk9yo2H0A6mo+1JUG0B+xss\nF5UY8+30cNhyY0/wypt/h4CdHpPIO/duMxa2z5Hn0f8psPmKQ+tf28Fp8O4hE3SF9H3DxmW3\nS4YJlsj4w9F/ZGDiA+h2nH78z1qoe9j1fzWE6IErIwAA\';\n			$resources[\'/res/js/jquery.filedrop.js\'] =  \'H4sIAAAAAAAAA80a/VPbRvZn81e8uplYLkY2adPLwTl3JJAJd5CmgbR3kzLMIq1tBVlSVyvA\nl/K/33v7pZUlEpK2M8dMG3v37dv3/bUef7MB38A+n7EqlSD5jYQteP9jxcUKirSaJxnMcgEL\nuUwfQyzYHFfmMEtSXsJM5EuIeXkp8wJkDhcivy65QISEc6+Si1zswM88uUngPzwz6wdLlqQ7\n8O5FIkqZsSU/e3fEzKd/zGkzjPKlAX6eFyuRzBcSgmgIjybbE3jDy7zI07SSSW5xHiURz0oe\nQ5XFXIBccDg+PIVUL+8QCCAPstgZj6+vr8O8wPW8EhEPczEfG7hyvEzklvkSFovCYH8t8vc8\nkrDIlx245olcVBdE8/iaWF3xbPz+VxLgFokpFrnF8xMXJdK8AzAJt8OJWX3BmawELw1m/NtL\nUxQklDyLSdj5DFAtgkHBBApJIha4xkuVFkJ36udcXJoNFC2f5Tfwbfj9ptt/UdE1W0hnkSYs\nkxr05enx0WMoCx5BcJ2kKVwjGr31M7+4TKTVagksi+Hw4K9Dwvi2ZHMti94J5/DmYG//+ACY\nhMKTVYEwmsnxxm4wq7KIVBY8GMKHjY2eNrKQX/FMhnisKMOiKhdBP2aSnQqWlTMu+sNdBL1i\nAu1Mmeh5XsgSpoih15uxNL1g0eV5Eu/AYDDCpUqk9iOKAKW62IHtyWRCC0p8ZGYIUSFHJD4F\nuWQ3yqB34NHjEdR/4zEczrNcoF0lM0CVVlwbfkLKkfAUJt7p5L+IeHvknz5+pnQEtIfGiNaF\n8DWeHZiMGrcdsxvjWheoP8H1nWQDATlhysScw1WeVksOVZHmLC6HFuM1SyQyMJk0KPiRtoD2\nQCZ4CvmYVWmKh8ZjRmbGY7pQrgoi590gWaLOxu+L+WBkPhdZ/XmezAZnI4XYnQKDBi5W6K2Z\nRG1uneJGCOjphVwBE4KtYMlRoZDlgDqRIlGWUCIZ6vSrPPvnyQ+v0LOLnPwVULMlJ9WQLezA\nh1v6vOAMnbu0X8mxdoDTHformx/g9WJt7Yer1tIRZ1fcX8uj1sk8Wj+YR+vntJIOWLTwFtkM\nMaEHe0uCa6tzC1wIiozOI/D7SEl0BMlQmTagWLmQtIMe0OspjrXKTyQTksceOr3+IsmSctHY\nQK+ao7zLtwXKsbGDDs/j5rK9RRFHLvau/0y7/qtcnlRFkdO1/RH0T/P8mGWrF2Sp9J0+4NoR\nmaf7jiaA5/a0cfTPjAjPU5LhOdkislxi4jhPc8we01rlygHOo7zCEDWFiVuiD0l5rpkln/DO\nlDydUaB4EM6y0EZdArASxqhBBqeFS+HEhJEHIYZWjLMB2lQjxIzAHiG86gIEl4uk3FUaRT5K\nfkCxqwyUhi6SLDbfCUofexAMvh7Aprot9MLVMCTwYBAtWDbnjuLByLMJTWsPPTbw2R6iB2Eg\nzzTzdHNPYafzeEgt6BgyBR5K0olUF5T1lpOu+hamPJvLhdpeE7AUlb5CL2pOe5gbhQrbpmww\ny13KURvrBN9q4VhWwRMdynWJHwzv5psRFnHoi4r+ofDX6zkADFLkyE0oExdaoPlVG5KcvgWo\nbHYdUgUDzfiDAC27UnTfRWoe7d+PWhOKPk2sDlD3oNXELR1GGnJfs2It87tNtsq6jbYtBAu5\nDkNedNdmkzRjzX+EC/jlhOcIhNQATaeQYVKE336DeoUqyVmS8dgQoa9QwTHQIfLd5Ezf1TJw\nZOb/xdUaUuVZlMf8vJKzJwFmYcOZOVNlvIxYwQMN9fbN4XMsFfMML1fAHVrC0PKsSlLMykqU\nlOV0HiOZj2CZ0PcLFEDMxMrcpmo5Vi7oPyR9sLWlarBeLxIqxA5+Eb9kZulCI6fVgQoZSmta\n13iD1QyhVMWdjujqowcVlljwymD8cGzUhUEfc3agj3gh1+IzCBNhIr5FMB2P4JHBgfUTQRHL\nCBVzI7KAjtWGYaCuWNoG2nZAXYhq2a9h7MC3Brt9FgpepCziwfiXzfEcC7YBDIYWgRXq5tTp\nob1jldbeITW1Vwe29NtPsIYrE6n6HCyPllukg13F37RPgUVxugmD/uC+yLtXfWNGgXSwV5+7\nHRq33PiIAO5ivwvjl7GubNK1IZ4QfHS7YH1JH/L5tDvD7rOavLvoo5IMWx88So55H9Y68Vn/\nvhfw5wr4LnhHnQlWZmtXlcrNoGSL3kb64CbykqtUkl2kvBE8uMCeX2I+Rrc6ZnIRCqIvoGMY\nZLG5+Ya6yCGMqaTKpbM2Qq0iRFQJrPLla3M3fDX1kOp+l/46YX1QY8TaUJrVu74IUye/Gemw\npDuGLpy6wDLc8ZQVNBvBLMevYR9xBcMQQ/cpWkHgh5U4mc1okdKmObPVQK9aD3OAWHcHnk61\nbZue2wVShxZvZSobG3kavCUh3LfGZOBVb0LxzZ4bO8p2qfX81zMSGHbgUZ7F5py63W9q7pKW\ngrFM95pEeAQ2AHzua9kYkNsN9/9bmyCRSN3LxjQWY5lp1n0rtVnemEarE6rT3VeqcvjSKgRJ\nwaZfZ8P1hl/NW+DhQ+je1R4z/GCw9DCkBaQfgjgkwa5XNW5ja2vXHFM8GBbeuf2zkG6geusB\nqmiPBgRBJ8iom7Qh/A0m9gaadHTJ5VstFw3Rg7Z81E4tJfdBZwnLabmPqdU2oqaoe8NpxqWM\ndFIryq/3nmrC7WzICdkbIlGROblbrdsfUeuGqixoaosFqh4SlWRpS5ZRDNO2RYEwwlhgmaHB\nnh4HYWt/tmuXDRQWkh2bMfLuL5uL9+IYsCoVK3QPLEDxZpq4ugtU8ZsLUNaSKCHhP38DT0C4\nsLlpmXcH9QAwGTbZfMlT8nfnOngdJkEM4VijVSWn+WjNBO3SuMucJTKSzO5rURGImoJyyZ0U\nFCZvYEATiryyjSh2Lipc4kpgcI3AgvhqalL+2lzrIsAIOxeMJmVyxdfk79/t8kXD20xIJ0tz\n0WK9E1IQePHzBY8uic+Sq5kfE5zEoNlfYjnhUK1b5VOYkLGuWYXxcBfn6wMu1BtClCA9rKQL\nI6FbjJwo5A+2MoZTdsmRP1FK0IaUz2Z6/vyrNSPtcDbaOEPBoGfCb207VKRjyTsZwbZLf8ZW\nURCehXjI19lU9udutHRrZFKsLO0kuHr41wpdQ0r/SiN1IrQBwrAynfreYNXoZ0Ghpp0madNE\n7Y1aCIYjA0SD53NCcq7Gy1OsUL578vgv32Ot0og9uGnF0dNIdVo00dvYVpPKRiBW6J9C47qa\nsY7Q9ehMd4E+lhGsi7Vn8uQyv+L6KclTTFs1KG0tbOslWPNXmCAu+cojRnGgdpyM9Z0tizbm\ngse1wZjzt/XHRqjf3HTrxtJdw96zRYCTb56pKiKjBEGvOLvNbfpnr3yWZFgAn0iBJLVNyKqs\n6TR30GRqEIiYjBaghsaem90h4i8S8O8TrxPuR6qYdr6zDkjPMRgfOJSSHqoW7ErnHHLvOB+5\nkFaHBBu16jxrubZ173hckMxUvwaHZYmMff3dNtghPoZiTIEDCaLK4HrBM3rt0IGSQqowiugI\nb86TdPnQcHesfht6rMVb39vQrKHSE8btritSSm1nzcFxO30gSBBQ9ZTPsK0pRXRgR62kyb4b\nd/WH8Hc3OIYdaMDqyOHUcZIvOWXBUuV/HVUSeuWRwKREY+KxKQ7sCTXLgvyCGA8xrCHp9MS4\nUk9kIbxkmLZIFWUleOg06ubYJnB1TOd66zA0nFKMP1udIO6g2bjd1hK6Wdgo++/jo5dSFm84\nekkpXaQ15dSUIEOTx+vcZIvgd00CzgyIpaa5azZV56EeQ+5ozgyc7Zb1vIz+llUqkwJP05zB\nblNrH1g0HVhsz+xYR9hXeu6kn6iUxYZqwLDrgJaaPLVF9lM3vsqYaiRkRqUKZ32nlXqC580K\nzYlRLRRsGpGh1sSwq27oxOjovjdOLQStTWcySZ0LzY6n4cZ6nF9n7jHO9My1Nhuga93jx6G8\nocCkse83qc0dFuvHk6OklDxDYfTt4KA/csOQkalHjPLJkuk3EEH/9Q8np33TZlUiHam0ZoRE\nUFj4Gn94qeuPQWTGSaT+wQgGzhTH3szLintKNunJ3sYBqsrMe65aMiNZRYZZ9wazlyO4cibV\nSZWCqGd9HvmZS7WBMR27r3n231QDMy/QowK/QPPllplw0J4bk1cQiDAP2af8RjZnIVl+/XFX\nxzoTv+4nM5qFE/SWZzEORtu2LStcEmNRxAuJx7Itek8HS4eXvOt+eu3R3SuivErX3QWm8m++\nNTcFZn7RgbZQckLdloVumog7r+JyZcvwi279MEAByaoc7MD6fbed99marVlZ/dl83jaV5ddz\nVCJQLecgGiVbd+fyhfXx7yyPnW/5rRUNCXzK3NCg3U6ttxmfVRy1qyMnVEJo9Td1vZc/VPOq\ndVMta2pcr36YJTJBn1SljCdzOr/RaxSOGx3vYH6p4TVInzMGqZuvxDRdyEqj2TKlcWJzmP2f\ne8EzRVH7pc7kd/124L/6mbFtvds+u9bpdpxfh2jj8Arq1ummWtffgc3rvYsOUcqZsLOYtV+U\nfPTN1DwR1/i6b6NX9T/qMv1Gb5+kHQHeYvt+9Vj/GQQ4rPagIYuMH6uJgs2ZTlId1+lfJbjL\nPsFL44KPPzTbnzH8cYL0lfbpy/88JX7y6qb+1q5T8wA3SWwVEJ2SxiT2aDIx6tNhq37Qpx9u\n0XEiw6ZtiiPNVoaekrD7oV/l+QWRP3a61wG/6KE6r/4NQR0sVpL/RAkmuLFcGZHdhNGCied5\nzPdkgA35Q5jczGbuoUL/KkvEVPqqFwGPhiUrwgiLFnvpqL5m6GbVVfKEmdrqbZLJJ/pZgTBq\nGP3WQ7/1IsjwoprNjP5JrH4RgsK8HQY6yyPA/wBgnJmDIy0AAA==\';\n		}\n\n		foreach($resources as $file => $resource) {\n			$newfile = $path . $file;\n			if (is_array($resource)) {\n				abl_droploader_install($resource, $newfile . \'/\');\n			} else {\n				$ok = is_dir(dirname($newfile));\n				if (!$ok) {\n					$ok = mkdir(dirname($newfile), 0755, true);\n				}\n				if ($ok) {\n					$temp = base64_decode($resource);\n					if (strncmp($temp, \"\\x1F\\x8B\", 2) === 0) {\n						$temp = gzinflate(substr($temp, 10));\n					}\n					if (file_exists($newfile)) unlink($newfile);\n					$handle = fopen($newfile, \'w\');\n					if ($handle !== false) {\n						fwrite($handle, $temp);\n						chmod($newfile, 0644);\n						fclose($handle);\n					} else {\n						$state = E_ERROR;\n						error_log(\'abl_droploader: ERROR: Creating file \\\'\' . $newfile . \'\\\' failed.\');\n					}\n				} else {\n					$state = E_ERROR;\n					error_log(\'abl_droploader: ERROR: Creating directory \\\'\' . dirname($newfile) . \'\\\' failed.\');\n				}\n			}\n		}\n\n		// install new prefs (not present in previous plugin versions)\n		$pref_prefix = \'abl_droploader.\';\n		$pi_pref_defaults = abl_droploader_defaults();\n		$max_pos = safe_field(\'position\', \'txp_prefs\', \"name like \'\" . $pref_prefix . \"%\' ORDER BY position DESC\");\n		if ($max_pos === false) {\n			$max_pos = 0;\n		} else {\n			$max_pos++;\n		}\n		foreach ($pi_pref_defaults as $name => $pref) {\n			if (get_pref($pref_prefix . $name, \'nope\', 1) == \'nope\') {\n				$rc = set_pref(\n					$pref_prefix . $name,\n					$pref[\'val\'],\n					\'plugin_prefs\',\n					2,\n					$pref[\'html\'],\n					$max_pos\n				);\n				if ($rc === false) {\n					$state = E_ERROR;\n					error_log(\'abl_droploader: WARNING: Preference \\\'\' . $pref_prefix . $name . \'\\\' not created.\');\n				}\n				$max_pos++;\n			}\n		}\n		// delete obsolete prefs (if any)\n		$current_prefs = safe_column(\'name\', \'txp_prefs\', \"name like \'\" . $pref_prefix . \"%\'\");\n		foreach ($current_prefs as $current_pref) {\n			$p = substr($current_pref, strlen($pref_prefix));\n			if (!array_key_exists($p, $pi_pref_defaults)) {\n				if (!safe_delete(\'txp_prefs\', \"name = \'\" . $pref_prefix . $p .\"\'\")) {\n					if ($state == 0) $state = E_NOTICE;\n					error_log(\'abl_droploader: Notice: Removal of obsolete preference \\\'\' . $pref_prefix . $name . \'\\\' failed.\');\n				}\n			}\n		}\n		// order prefs\n		$pos = 0;\n		foreach ($pi_pref_defaults as $name => $pref) {\n			safe_update(\'txp_prefs\', \"position = \'\" . $pos . \"\'\", \"name = \'\" . $pref_prefix . $name . \"\'\");\n			$pos++;\n		}\n\n		// set load order to 9 (important!)\n		if (!safe_update(\'txp_plugin\', \'load_order = 9\', \"name=\'abl_droploader\'\")) {\n			if ($state == 0 or $state == E_NOTICE) $state = E_WARNING;\n			error_log(\'abl_droploader: WARNING: Setting the plugin load order to 9 failed.\');\n		}\n		return $state;\n\n	}\n\n	/**\n	 * inject css & js in html-header\n	 *\n	 * @param string $evt (admin_side)\n	 * @param string $stp (head_end)\n	 */\n	function abl_droploader_head_end($evt, $stp) {\n		global $event, $step, $prefs, $abl_droploader_prefs;\n\n		if (($event == \'image\'\n			&& (in_array($step, array(\'list\', \'image_list\', \'image_multi_edit\', \'image_change_pageby\', \'image_save\', \'\'))))\n		|| ($event == \'article\'\n			&& (in_array($step, array(\'create\', \'edit\'))))) {\n			$abl_droploader_prefs = abl_droploader_load_prefs();\n			$css = \'\';\n			if (intval($abl_droploader_prefs[\'useDefaultStylesheet\']) != 0) {\n				$css .= \'<link rel=\"stylesheet\" href=\"../res/css/abl.droploader-app.css\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			if ($abl_droploader_prefs[\'customStylesheet\'] != \'\') {\n				$css .= \'<link rel=\"stylesheet\" href=\"\' . $abl_droploader_prefs[\'customStylesheet\'] . \'\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			if ($css == \'\') {\n				$css = \'<link rel=\"stylesheet\" href=\"../res/css/abl.droploader-app.css\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			$article_image_field_ids = \'\"\' . implode(\'\", \"\', explode(\',\', $abl_droploader_prefs[\'articleImageFields\'])) . \'\"\';\n			$script = \'<script type=\"text/javascript\">\n	var file_max_upload_size = \' . sprintf(\'%F\', (intval($prefs[\'file_max_upload_size\']) / 1048576)) . \';\n	var file_max_files = 5;\n	var image_max_upload_size = 1;\n	var image_max_files = \' . intval($abl_droploader_prefs[\'imageMaxUploadCount\']) . \';\n	var reload_image_tab = \' . intval($abl_droploader_prefs[\'reloadImagesTab\']) . \';\n	var article_image_field_ids = new Array(\' . $article_image_field_ids . \');\n	var article_image_field = null;\n</script>\n<script src=\"../res/js/jquery.filedrop.js\" type=\"text/javascript\"></script>\n<script src=\"../res/js/jquery.droploader.js\" type=\"text/javascript\"></script>\n<script src=\"../res/js/abl.droploader-app.js\" type=\"text/javascript\"></script>\' . n;\n			echo $css . $script;\n		}\n\n	}\n\n\n	/**\n	 * Insert DropLoader link in column 1 on the write-tab\n	 *\n	 * @param string $event (article_ui)\n	 * @param string $step (extend_col_1)\n	 */\n	function abl_droploader_article_ui($event, $step) {\n		$content = \'\n<ul class=\"abl_droploader plain-list\">\n<li><a id=\"abl-droploader-open\" class=\"abl-droploader-open\" href=\"#\" title=\"\' . gTxt(\'abl_droploader_open_title\') . \'\">\' . gTxt(\'abl_droploader_open\') . \'</a></li>\n</ul>\';\n		if (is_callable(\'wrapRegion\')) { // new in txp 4.6\n			return wrapRegion(\'abl_droploader_group\', $content, \'abl_droploader_link\', \'upload\', \'article_abl_droploader\');\n		} else {\n			return \'\n<div id=\"abl_droploader_group\"><h3 class=\"plain lever\"><a href=\"#abl_droploader-link\">\' . gTxt(\'upload\') . \'</a></h3>\n<div id=\"abl_droploader-link\" class=\"toggle\" style=\"display:none\">\' .\n$content . \'\n</div>\n</div>\n\';\n		}\n	}\n\n	/**\n	 * Insert DropLoader link above the image-list on the image-tab\n	 *\n	 * @param string $event (image_ui)\n	 * @param string $step (extend_controls)\n	 */\n	function abl_droploader_image_ui($event, $step) {\n		return \'<p class=\"plain\"><a id=\"abl-droploader-open\" class=\"abl-droploader-open\" href=\"#\" title=\"\' . gTxt(\'abl_droploader_open_title\') . \'\">\' . gTxt(\'abl_droploader_open\') . \'</a></p>\' . n;\n	}\n\n	/**\n	 * handle AJAX requests\n	 * return JSON and exit\n	 *\n	 * @param string $event (abl_droploader)\n	 * @param string $step (get_form_data, get_image_data)\n	 */\n	function abl_droploader_ajax($event, $step) {\n\n		if ($event == \'abl_droploader\') {\n			switch ($step) {\n				case \'get_form_data\':\n					$response = array(\n						\'status\' => 0,\n						\'image_upload_link\' => \'\',\n						\'image_upload_form\' => \'\',\n						\'image_cat_select\' => \'\',\n						\'l10n\' => \'\'\n					);\n					$items = explode(\',\', gps(\'items\'));\n					foreach ($items as $item) {\n						switch ($item) {\n							case \'image_upload_link\':\n								$response[\'image_upload_link\'] = abl_droploader_image_ui(\'\', \'\');\n								break;\n							case \'image_upload_form\':\n								$response[\'image_upload_form\'] = abl_droploader_get_image_upload_form();\n								break;\n							case \'image_cat_select\':\n								$response[\'image_cat_select\'] = abl_droploader_get_image_cat_select();\n								break;\n							case \'l10n\':\n								$response[\'l10n\'] = abl_droploader_get_localisation();\n								break;\n							case \'all\':\n								$response[\'image_upload_link\'] = abl_droploader_image_ui(\'\', \'\');\n								$response[\'image_upload_form\'] = abl_droploader_get_image_upload_form();\n								$response[\'image_cat_select\'] = abl_droploader_get_image_cat_select();\n								$response[\'l10n\'] = abl_droploader_get_localisation();\n								break;\n						}\n					}\n					$response[\'status\'] = 1;\n					break;\n			}\n			echo json_encode($response);\n			exit;\n		}\n\n	}\n\n	/**\n	 * get image category-selector form element\n	 */\n	function abl_droploader_get_image_cat_select() {\n		$image_categories = getTree(\'root\', \'image\');\n		//$image_categories_select = str_ireplace(\"\\n\", \'\', tag(\'<label for=\"image-category\">\' . gTxt(\'image_category\') . \'</label>\' . br .\n		//	treeSelectInput(\'category\', $image_categories, \'\', \'image-category\'), \'div\', \' id=\"abl-droploader-image-cat-sel\" class=\"category\"\'));\n		//$alt_caption =	tag(\'<label for=\"alt-text\">\'.gTxt(\'alt_text\').\'</label>\'.br.\n		//		fInput(\'text\', \'alt\', \'\', \'edit\', \'\', \'\', 50, \'\', \'alt-text\'), \'div\', \' class=\"alt text\"\').\n		//	tag(\'<label for=\"caption\">\'.gTxt(\'caption\').\'</label>\'.br.\n		//		\'<textarea id=\"caption\" name=\"caption\"></textarea>\'\n		//		, \'div\', \' class=\"caption description text\"\');\n		$image_categories_select = str_ireplace(\"\\n\", \'\', tag(tag(\'<label for=\"image-category\">\'.gTxt(\'image_category\').\'</label>\'.br.\n				treeSelectInput(\'category\', $image_categories, \'\', \'image-category\'), \'div\', \' class=\"category\"\'), \'div\', \' id=\"abl-droploader-image-cat-sel\"\'));\n		return $image_categories_select;\n	}\n\n	/**\n	 * get image upload-form\n	 */\n	function abl_droploader_get_image_upload_form() {\n		$upload_form = upload_form(gTxt(\'upload_image\'), \'upload_image\', \'image_insert\', \'image\', \'\');\n		//return \'<div id=\"droploader\">\' . n . $upload_form . n . \'</div>\' . n;\n		return $upload_form;\n	}\n\n	/**\n	 * get localised items from textpack\n	 */\n	function abl_droploader_get_localisation() {\n		$l10n = array(\n			\'open\' => \'\',\n			\'open_title\' => \'\',\n			\'close\' => \'\',\n			\'close_title\' => \'\',\n			\'error_method\' => \'\',\n			\'info_text\' => \'\',\n			\'err_invalid_filetype\' => \'\',\n			\'err_browser_not_supported\' => \'\',\n			\'err_too_many_files\' => \'\',\n			\'err_file_too_large\' => \'\',\n			\'all_files_uploaded\' => \'\',\n			\'no_files_uploaded\' => \'\',\n			\'some_files_uploaded\' => \'\',\n		);\n		foreach ($l10n as $k => $v) {\n			$l10n[$k] = gTxt(\'abl_droploader_\' . $k);\n		}\n		return $l10n;\n	}\n\n	/**\n	 * image_uploaded callback (txp_image.php)\n	 * return JSON (image-id) and exit\n	 *\n	 * @param string $event (image_uploaded)\n	 * @param string $step (image)\n	 * @param string $id (image_id)\n	 */\n	function abl_droploader_image_uploaded($event, $step, $id) {\n		if (ps(\'abl_droploader\') == \'\') return;\n		$response = array(\n			\'status\' => 1,\n			\'image_id\' => $id,\n		);\n		echo json_encode($response);\n		exit;\n	}', 'if (@txpinterface == \'admin\') {\n\n	// privs\n    add_privs(\'abl_droploader\', \'1,2,3,4,6\');\n	add_privs(\'plugin_lifecycle.abl_droploader\', \'1,2\');\n	add_privs(\'plugin_prefs.abl_droploader\', \'1,2\');\n\n	// inject js & css in html-header\n	register_callback(\'abl_droploader_head_end\', \'admin_side\', \'head_end\');\n	// trigger (button/link) elements\n	register_callback(\'abl_droploader_article_ui\', \'article_ui\', \'extend_col_1\');\n	// following callback has been replaced by javascript!\n	//register_callback(\'abl_droploader_image_ui\', \'image_ui\', \'extend_controls\');\n	// ajax interface\n    register_callback(\'abl_droploader_ajax\', \'abl_droploader\');\n	// intercept image_uploaded-event from txp_image\n    register_callback(\'abl_droploader_image_uploaded\', \'image_uploaded\', \'image\');\n	// lifecycle\n	register_callback(\'abl_droploader_lifecycle\', \'plugin_lifecycle.abl_droploader\');\n	// prefs\n	$abl_droploader_prefs = abl_droploader_load_prefs(); // load hard-coded defaults\n	@require_plugin(\'soo_plugin_pref\');\n	register_callback(\'abl_droploader_prefs\', \'plugin_prefs.abl_droploader\');\n\n}\n\n\n	/**\n	 * handle plugin lifecycle events\n	 *\n	 * @param string $event (plugin_lifecycle.abl_droploader)\n	 * @param string $step (enabled, disabled, installed, deleted)\n	 */\n	function abl_droploader_lifecycle($event, $step) {\n		global $prefs;\n\n		$msg = \'\';\n		$msg1 = \'\';\n		switch ($step) {\n			case \'installed\':\n				if (function_exists(\'soo_plugin_pref\')) {\n					abl_droploader_prefs($event, $step);\n				} else {\n					$msg1 = \'Please install <em>soo_plugin_pref</em> to edit preferences (Default preferences apply).\';\n				}\n				$rc = abl_droploader_install();\n				if ($rc == E_ERROR) {\n					$msg = \'<strong>An error occurred. Please check the servers error-log.</strong>\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				} elseif ($rc == E_WARNING || $rc == E_NOTICE) {\n					$msg = \'<em>abl_droploader</em> successfully installed. Please check the servers error-log.\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				} else {\n					$msg = \'<em>abl_droploader</em> successfully installed.\' . (($msg1 != \'\') ? \' \' . $msg1 : \'\');\n				}\n\n				// set txp\'s thumb prefs if they are not set (fresh install)\n				if (!isset($prefs[\'thumb_w\'])) set_pref(\'thumb_w\', \'100\', \'image\', PREF_HIDDEN);\n				if (!isset($prefs[\'thumb_h\'])) set_pref(\'thumb_h\', \'100\', \'image\', PREF_HIDDEN);\n				if (!isset($prefs[\'thumb_crop\'])) set_pref(\'thumb_crop\', \'1\', \'image\', PREF_HIDDEN);\n				break;\n			//case \'deleted\':\n			//	break;\n		}\n		return $msg;\n\n	}\n\n	/**\n	 * add plugin prefs to database\n	 *\n	 * @param string $event (plugin_prefs.abl_droploader)\n	 * @param string $step (-)\n	 */\n    function abl_droploader_prefs($event, $step) {\n		if (function_exists(\'soo_plugin_pref\')) {\n			soo_plugin_pref($event, $step, abl_droploader_defaults());\n			return true;\n		} else {\n			$msg = \'Please install <em>soo_plugin_pref</em> to edit preferences (Default preferences apply).\';\n			pagetop(gTxt(\'edit_preferences\') . \" &#8250; abl_droploader\", $msg);\n			$default_prefs = abl_droploader_defaults();\n			$html = \'<table id=\"list\" align=\"center\" border=\"0\" cellpadding=\"3\" cellspacing=\"0\">\n<thead>\n<tr>\n<th colspan=\"2\"><h1>DropLoader default preferences</h1></th>\n</tr>\n<tr>\n<th>Option</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n\';\n			foreach ($default_prefs as $key => $pref) {\n				$html .= \'<tr>\n<td>\' . htmlspecialchars($pref[\'text\']) . \'</td>\n<td>\' . htmlspecialchars($pref[\'val\']) . \'</td>\n</tr>\n\';\n			}\n			$html .= \'</tbody>\n</table>\n\';\n			echo $html;\n			return false;\n		}\n	}\n\n	/**\n	 * get plugin prefs default values\n	 *\n	 * @param string $values_only\n	 */\n	function abl_droploader_defaults($values_only = false) {\n\n		$defaults = array(\n			\'imageMaxUploadCount\' => array(\n				\'val\'	=> \'10\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_image_max_upload_count\'),\n			),\n			\'reloadImagesTab\' => array(\n				\'val\'	=> \'0\',\n				\'html\'	=> \'yesnoradio\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_reload_image_tab\'),\n			),\n			\'useDefaultStylesheet\' => array(\n				\'val\'	=> \'1\',\n				\'html\'	=> \'yesnoradio\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_use_default_stylesheet\'),\n			),\n			\'customStylesheet\' => array(\n				\'val\'	=> \'\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_custom_stylesheet\'),\n			),\n			\'articleImageFields\' => array(\n				\'val\'	=> \'#article-image\',\n				\'html\'	=> \'text_input\',\n				\'text\'	=> gTxt(\'abl_droploader_prefs_article_image_fields\'),\n			),\n			//\'fileMaxUploadCount\' => array(\n			//	\'val\'	=> \'10\',\n			//	\'html\'	=> \'text_input\',\n			//	\'text\'	=> gTxt(\'abl_droploader_prefs_file_max_upload_count\'),\n			//),\n		);\n		if ($values_only)\n			foreach ($defaults as $name => $arr)\n				$defaults[$name] = $arr[\'val\'];\n		return $defaults;\n	}\n\n\n	/**\n	 * Load plugin prefs\n	 *\n	 * @param string $values_only\n	 */\n	function abl_droploader_load_prefs() {\n		return function_exists(\'soo_plugin_pref_vals\')\n			? array_merge(abl_droploader_defaults(true), soo_plugin_pref_vals(\'abl_droploader\'))\n			: abl_droploader_defaults(true);\n	}\n\n	/**\n	 * install plugin resources (css, js)\n	 *\n	 * @param mixed $resources\n	 * @param string $path\n	 */\n	function abl_droploader_install($resources=null, $path=\'\') {\n		global $prefs;\n\n		$state = 0;\n		if ($resources === null) {\n			$path = $prefs[\'path_to_site\'];\n			$resources = array();\n			$resources[\'/res/css/abl.droploader-app.css\'] =  \'H4sIAAAAAAAAA71X207jMBB9pl9hqVppF+EQUtKtwtP+xL6unNhJLRw7st0LIP59x3aSpm3a\nwAotoCq+zZw5Z3occkVf0NvsRm2ZLoXa4X2G1pxSJp8Gky8ZMoVWQjzN3mf3t4jXpGJIcGPR\n7f1s7od/CiUt7EERyQWmWjVCEco0Vg2TLgVEIjZDmldrC8FroisusR9m6IHVMFdCCP+MfjNN\niSR36JfmRNwhQ6TBhmlettvwjoWTuRLUwZrNT/IWQhnmElNuGkGghlyo4hnON8pwy5XMEMmN\nEhvLYNKqJkOJh9FiilI/agilXFYZWjR79BDDx7J9gMVcacgFmGEKQnGK5kVRuHBsbzFlhdIk\npJJKujRAqeUFEZgIXkkH31pVdwfayYJJy3RPSPIPhNwUSigANk+SxOEkxXOl1UZSmCpLd+gV\nc0kZ6L2AH3dgo4070Sgesr+PU5qtXV84YrsUpUtxroDbcpnqGB4EK2142nFq10BjHH+D0bot\nJQwhMvTcafBS6Rpx2WwsevNdOLZO+faoA4IIZ4WhaOOfgJFSfRXsy4piw18ZbGsb7Eg9CbCJ\nuKqfrnLyPUnTO3T4iKNV+uNc1JFKDROssLjkgpkvK7UDu1wuR8E+xoCz/4ijnx8E68Yl3zOK\nAeYxWj89XWHzSQdIfT2Xyg6mBac2VvnlKY0XVxQ+tYIafFeMtSf2BosLAlGZ+GRBq6GlBZs9\n6IM74YJp9Vbn903hCDw7OK2Xh64JXT2iC68r3Gi25Wx3LKVmAkxy+6nGg0byBtwpsnDG7GQZ\n3FuDq8zdVdjYF8GwfWlYb8d9xZfEHK3DsZCrvaui/yKslh7PJVEBgWT4sNujvxoc6Arc7nFL\nwaHm/Wmk/iJaHC6iYPO4Vq8uHjZrQh0nMfwmsGsex65o6Mv8mdtrWy4vTagcTcndFpbGoYhe\n3Hh5JO7DQNwPi3QOI6Ig+vBtZ9AgXWrfBxou1xZsmPeWZTo2r623bF7boq6s+terBgoIr1iN\nVpVmxoxdcG2r9Fsucdx3SuI57XvpmOL0wPCHfekMQjQOZmBJh6/1yQ3SworH7SlJV0sad68C\nTj4ixHV2vP1Ps+MNZ5Ge9N/jkKquO0e6cYSSk7z/iRKmtdII/gpVN4K5JKiGrK6Jxthp1zoX\nm6ZmmovJu9DAtef/g/gL2bND4HUMAAA=\';\n			$resources[\'/res/js/abl.droploader-app.js\'] =  \'H4sIAAAAAAAAA60Y227bNvTZ/go2DSK5sZWkLx3iukPRdg/DLljbPbWFwUi0zUQWBYpK7KX6\n951DUhRlSUk6LAhsied+5TkOV2UWKy6y8HhC7sfj0S2VJGErWqZqKXKEFGQBkNHojidrppY8\nuSQBvUpniRR5KmjCZDAFcMFSFqvliqesWMYpLQrAM4czfaix+Jau2TKmiq2F5IBpMABVgxER\n+V5qiaOcSrrN6JYBXG0YQjXaaEt3muclMQzh3UhuQfk/zEcotbpLPNZopUyBL88StovyTW44\nr2iaXtH4xtipiWeG0MA3DC0urIKjIN5QWTAVAHKpVrOfAjyu4KNC9PTiPLOoQZyKgiHeyfPd\n+Xnyam4YmvOl4irV0Hf4St6DE35rnDsKmJRCLrdMbUSCaL/rJ3J/b46qiiSCFSQTirAdLxTh\nGYEoRU2UZjTPo+siQg1BwWpugg1B1l5rEJcbniQsg7ArWbI2DA6zMk3nkCnH0SqLQESj6ts8\nB7jLKKOZSSstyhzU+TTiGVfkssG36TaxruUrEj6zJBEvPpV5LqRiSTiZEMlUKTMCwSrYXGOj\nGcD4OGI7xbIkvK+mh3k8JbWEuRNwHAbPc4wxlYrHEIFJlLJsrTbkDTmvNRlZ4NLk0oqzNEFH\nsDvyVkq6Dy3D0UpIEqKlHMDnc/h6TXpoIbcKK2ZOTk+5k9Mn6Av/hnaFA3wAXEuvxs1nbZsJ\nJRZcY9li4ZkGSJHJ7xlovw0mVpMIsgX9GLzmWV4qovY5WxwZdkeEJ4sjCP2ySYwjgnXaPb2l\naQnHF0fk7M3X7AfYN5rXrG3X2DumyDOw1jdmYyZEA30GbQ+CxviIXtNdWHvfNISf2S3L1KJt\nyEmhWL7A/odeWiZU0ROu2LZYOElWxBRr3hQt/CHiZ7AN+F4XojmnxT6LL00C12dFGccM26ar\nCKRukkNbh0dRoagqC23MReAhjB62nWjiQ4XnbWpUv0bFZweuxq3vquV4SKMrkewhyRJgugbZ\nkHztW2JmGuCUQMfj8U3k3x+NycyZU5e+JnMl1q38WpGnqGD6Odg+A9vJiwNlhnX5P6Uar2vR\nG5qt2WOyO2UM2Q+1rTa8MM+Tx33TauCdkvdCoaunMe3AEkAFTXQwxiNi/pxYVChiNN6Ezgiw\nwWqgb8OR18W9LK8ttXzCIJF0vQbJLMBbLBFxuYWKjGLJwL0fUoZvYVDkFPrJhJyckBDbh1iR\nX+DW/6gvaPIMaqOEq33FM5YEvhICGs+BdPI0W8auCrsu7Lk1PK/7Hg4kw4GhDlq/m4sU/Pxe\n3GU9CvSRkKg1ak0imiTvcAgL9ay24juWzHJRNGJ7L3wvdYyYjdqmwO2KY6++Yfsyf7RurYtY\ndLfhsb5tXr7ygQOF3Z/AfuNx2ew9VX6am/hqtj3p9QSnSbYVt+whvw0H6++8J1RDQ9XclbZ1\ncJkZF/verW3smb78RDTzi+4xrTzEwpBMj7ym5St6hWXhJWgqoKOAwpHBC3u92o2K8XPCCiXF\nngxXcl+g9WiM7UOrbzG+mO9vlr5NXgNxZEj3uu9NzdwV5VIogbWPIYhZFMPwDoPSWvcLwLow\nrbEiDFQnKNA2CsMSchO6hLi6xn5Mvn8nz0gzs3bNwHm1pYST5EvRlDCG4rwewsDwGQbS0N2s\nkT/HT8k9CdxIb3WqrM7oorMXpNiIO6KJCPzHYpunDH0N6EUBUSUvznBvshEgtUAFH1MzJ4Gi\nWfrySk7M0L3CCRWPIRcWxLVIzBZ37s1IbqQFO+Ue+7HBckmEMMnWbJfbkfgjW3/Y5WFwfx+Q\nU0t2SoKqwitvzV1ao4pYDvAF+Qc3YMxCw6hW/Ism/uYPGlVtgzZJBxDrqdamj+fZ16/Z2Rpk\nvwYCNzD60XqQ7JOSPFtHKym272DXeyfg/rs4nzTZ7O4MoK4jhxleTcLrv0om93hbjo+97uD2\n7KY7tFdtulKwTaWpV1q6RVkz0X79flDtfWvKM9MyOuF8eEPp3U6Q7HZoF8E9xEwjXne6PZi3\nR0huNL8WPAuDqcuGVjwQ8RQop5hBA/h1B31Il9saexjNzGDhYY7527tZlsem3/YtvWHgDTY4\nkriNF2aXZQoCSqxUPXnZfk+VgkkAQcHEjEZaL/sTRcqzG7Kwv4m0QThuaJDroT1gz+nHEbSE\nXz/9+UeoDfzR7QYaqt1Z+peSx1aSPrO8LcQDzPsorLVdCgTUFN0y+q8rUA+noXWolQOfoAPk\nh7cnfnYuhiEy3e5dF+/Dakr/wd8ssA8MTqf+5t8sLnYV7/jXa3FdjvUe8PgcMiiww3QyJ2dn\nBEcwggdu4LLhEnAbCCyeHHp0R2PMIcOAZwWTiuCBb0BnTYsO1jRcDJ68ng78btLXG2BsABbd\n3Bom0ZoMTl8uYSq8ZObjfwFwbFnExBUAAA==\';\n			$resources[\'/res/js/jquery.droploader.js\'] =  \'H4sIAAAAAAAAA7UaXXPbNvJZ/hWILy2pVKaVztyLHXcmlyZzmUnSXOI+3DQdDSxCEhOS4IBQ\nbJ+q/367iw8CIqUozcQPkrjAfmC/sXS6WNdzXcg6fThmm5OT0WeuWC4WfF3qll0BaDRaFKXI\nlWwu6GnUcMWrmlfigiVNMU8mCKz4HW5rL9jjaQQo/gf7fibQWpWAgpRKyXOhsmbVGOwFL8sb\nPv80K3LYgXgv62atzWLONQfWW3pYCcRs8RkeCVZUfClmc67FUqpCtLNWlGKugRDhG8lF1eh7\nfLwRC6nEcz5fXTB/djg6U0KvVc20WotLRoT5Qgv1tCwDbKGUVMHzmo7yXnOlRd6Dvyjqol1F\nC42SSyXa9vcGjhWtlI+ntdVwUS/kTIs7PMOvID4jTbKVUOLJjWLnv5jPD7VUbF4W809MS2ZO\nbbYaxYGws6L+zMsinyFY3zdos2e8rqVmRkS22eASmnO7zdhvdXnPSKEt40owsIu8FTlLPzbL\nCfvYCPhcFosJa+rl+EHH5kbJ21aoGRCeteumkUYfyX/lWjG7yHIJVJG13cH+ff361T9JYitN\nG5DUUs4qXt/PrF8l11IyBJgjPmBvS8Fb4c692TiP224Z16ySrQ6o4QqRLLlaohbCcxt1wiqj\nVYsGZze8Z0Y4OtBm4x5mjplcWCXO5brWADDWcvsyQ62WfWLXaFHS82ZDnoXkzI+MvZGW0C3t\n2iHXykp8JcG/IzrG2Mloe2kTg9E1ULOZ4bbIlwK8VK+KduKfTRR3cU4SQ9RVF6xel6ULyht5\n1wEMZWfrIehsXvIW/cAAzzpHx3DpcCh43F5zkjOEJSb8xOdC3M6Mi3dIMdyjF9XyzC4lYfTe\ncBXieqBHJOHO3ArhVvADiEen7mAe04LOAJaEmtc8ysdksAs27ZINOsDU5yggNSV0Z7cFLslG\ntyGG0SyQ/eNPEqddmofY3pCKqqaEdAVryZO8+MxI2KvT0BKnkI4S9hP6Jv0lT5pfnpw3PWjb\n8JoV+dUpvynPOh85m5eyFaeAghtirOTJOTBFmJPLmWufYHa9L1Owh0yNat7dBdvA7pRfY2Qn\nxWGKzugDZCMJ3a5hsn1gpIWT0cNsUWed/kADvpqBleGrNfV8VCxYivGZQWSm03FWy1y8gbTH\nrkBpL3579zoZm6pz/ohBtZCfIN3Lmpl4vVW8sengDCHs0TkVKKBpuWQ+5tkDoLiuoXkoapFb\norT1YZr8A07CeijjDFLwUq9QlqnDGJG0yBlqb2q0hg4zSAFgySnYKhlfEvKWPtHfQSP7+dJm\nSI0lFJC+nD7NfYOkAzSOknWIt5H2JJY4Nldgo7O5rDUHGyhrLcsBhb00CX3UcSGrEnv8lUwo\nVxDHeE+2KnKRji+dS1mdgj7AjcCThXZ+RA+pEdo2VUjTsI7omhN+wVAndFQguW6YReB1zlxT\nyppyvSxqc1TMb0gug95J1HkKTaNvZiedaieOEQmJSJktSEaWla5KYyQ86YNAOtCF5ZsFPes4\ng3Bu0qQCPkVTimRsVXEEJtdaBZgTFlDxKrPOmXla/cI45KCGLRqPWtl0J50bE32BLjota5xf\nZKiaFGxwDRpOCQX71sx3rBPsuqmJHh9PHlO+40AxsY8NFYmx3WM0pwtNatuzeUbrx8nizsib\nBp2Hdu25XIxdIPVIGQ86go337JW8NT2w2Qp+yfKCl3JpfBqo3cj8PhlnOawvQZIvkU/oTpCF\nDVhXHMTxrklkbCCLrNWyeVlVAmTT4i1s5iALXZ7CSAcCrSDl+APuD1UXdZ5/ShUrvGHG4vmF\nCVA+Z9DuMnzCPlbD74ezFy9fPX/PRK3VfXwrjek4eP+mOrwPlrobbLwFQP0r7F6lxnfYeJsF\nT0gF5tbas1qUXKkVIvkufQkTmcYrjM6MMw6W5JFz1HizITKiTjPzPSY2Frhq04utXKYOGYZ4\nP79WvG4XUHEihuzHH1m8fqRYfZSvF27fWeE+vBZ7yT0OSzPZx9QclxSiJs7HT3fnoBp/qEn0\nOL1rg0M93CZ+sQv1DHYvF4b+8fhDd6JDNBzQOqdXzG5G7JQVNBt2f8xzF3NIoiEaXrF9Ajs6\n72N3WttF3tVnhAvZqBXYlmADhu7LtJQnvkckCNLzJXkMqVtBmofU8lQpfp/uOQJ4J7qdB0Ed\nAI/UqzSZ/uBUjSXktZEuTSL9U1K1hb6XonF8RlktHmL5pFNMKLomDKLLBSmyeonqT3EpZBVR\niXFDZoPzN6LlMxxJSTS7zQE7S6g3SevL7bY40g/JDobUXr06ALr6D8mQVSzHAT59rXYjwL54\ngNJAEwq/dIF1dbHwWqa85G7pP/10ebz4yeNp5xgRBs/zZ+i3aZLLWiRBDWmEmkPNBKuCj77m\nepUpSId5mqaxIHg9IYAZMYzZIwbcxuyc7STSr/XlQIBQ6/GYImvW7Sp1SrOdmbsdhT7odH5Q\n06Gp7FS3q7ZKGcydS2yGM4tFcSfys0ai9FHL3QuvUagsZ8Tvqe/2ttAQKrDb19Q5jkiTf5kJ\n7Bup37vhbHJhUzcOfYxq+y303tGubai7cKUs5wbmqV+9UYJ/ugxFuZbyNa/vX9D07lgZ4lnw\nhG3Ylzo7tv37IqJsIOYr7BeOFjEeMKOIbr58Qb8yalQDqb41OGLJ7S334nsQD5ohcsQgPrrG\nM3RU77fekSFCdl3Weegh62yDIO3exoSvbewIRV9DXMu1Tk8xTE8H+7JTOLQSlfyMY4zTCYbS\ndN8BoinPIdP3XxKg6d0INqQ5YXE6u9jRkdGonevuNqad32D+wIENCfVRFjW0pPT6wpsu7AYq\np8ioJ+6OGxpoyv76i+1qYZ/VDiql965jn06Cc8USHiQ/8O7jOyt9u0/377Uq6mW2ULJ6tuLq\nmcxF+njq8XgplO6M0OtvvL/HUrqBX3eNdi7PjAO/NY1w6u/yA+1x4OxujASXcbo/hnM0PHTR\n6mLemnt5wIlumn5aMNTAxzwGrlPTAN75WgR2vmaBA68mCB68m+hOQ7MTe2wcApRyzkszTIkP\ns9O2mjFD8CaBevPdtwrjiUkOlSnUoIOiWiYTh2MsQ1SUsDP4Gkhh+XhHADsuNRRMwsUr5tSc\n1EBXoliutAObSQq9HJc1vZ+9CocAm04gO/5q1Rwk8pdbMBpUAes/ETn8etr+Cu3g7+9eBd20\nPYy9317LQVcylIJuMlTCjjnIngzfTEtQoyD126vTjlFcZJu5Id3LIWDr8ucbZU6KecqA+3MF\nD2eJGzzjO0ZEUGYAxIra7vJZy9hqKe4aa6t3Yvn8rkmTzQa926BBHdxucRC7LHxORRFxhA5f\noEnwjrlIDSEn+B+E/GdUuNwZ6Eg0KafBqJVmiOb5hw/1+RJ4Rzk9yo2H0A6mo+1JUG0B+xss\nF5UY8+30cNhyY0/wypt/h4CdHpPIO/duMxa2z5Hn0f8psPmKQ+tf28Fp8O4hE3SF9H3DxmW3\nS4YJlsj4w9F/ZGDiA+h2nH78z1qoe9j1fzWE6IErIwAA\';\n			$resources[\'/res/js/jquery.filedrop.js\'] =  \'H4sIAAAAAAAAA80a/VPbRvZn81e8uplYLkY2adPLwTl3JJAJd5CmgbR3kzLMIq1tBVlSVyvA\nl/K/33v7pZUlEpK2M8dMG3v37dv3/bUef7MB38A+n7EqlSD5jYQteP9jxcUKirSaJxnMcgEL\nuUwfQyzYHFfmMEtSXsJM5EuIeXkp8wJkDhcivy65QISEc6+Si1zswM88uUngPzwz6wdLlqQ7\n8O5FIkqZsSU/e3fEzKd/zGkzjPKlAX6eFyuRzBcSgmgIjybbE3jDy7zI07SSSW5xHiURz0oe\nQ5XFXIBccDg+PIVUL+8QCCAPstgZj6+vr8O8wPW8EhEPczEfG7hyvEzklvkSFovCYH8t8vc8\nkrDIlx245olcVBdE8/iaWF3xbPz+VxLgFokpFrnF8xMXJdK8AzAJt8OJWX3BmawELw1m/NtL\nUxQklDyLSdj5DFAtgkHBBApJIha4xkuVFkJ36udcXJoNFC2f5Tfwbfj9ptt/UdE1W0hnkSYs\nkxr05enx0WMoCx5BcJ2kKVwjGr31M7+4TKTVagksi+Hw4K9Dwvi2ZHMti94J5/DmYG//+ACY\nhMKTVYEwmsnxxm4wq7KIVBY8GMKHjY2eNrKQX/FMhnisKMOiKhdBP2aSnQqWlTMu+sNdBL1i\nAu1Mmeh5XsgSpoih15uxNL1g0eV5Eu/AYDDCpUqk9iOKAKW62IHtyWRCC0p8ZGYIUSFHJD4F\nuWQ3yqB34NHjEdR/4zEczrNcoF0lM0CVVlwbfkLKkfAUJt7p5L+IeHvknz5+pnQEtIfGiNaF\n8DWeHZiMGrcdsxvjWheoP8H1nWQDATlhysScw1WeVksOVZHmLC6HFuM1SyQyMJk0KPiRtoD2\nQCZ4CvmYVWmKh8ZjRmbGY7pQrgoi590gWaLOxu+L+WBkPhdZ/XmezAZnI4XYnQKDBi5W6K2Z\nRG1uneJGCOjphVwBE4KtYMlRoZDlgDqRIlGWUCIZ6vSrPPvnyQ+v0LOLnPwVULMlJ9WQLezA\nh1v6vOAMnbu0X8mxdoDTHformx/g9WJt7Yer1tIRZ1fcX8uj1sk8Wj+YR+vntJIOWLTwFtkM\nMaEHe0uCa6tzC1wIiozOI/D7SEl0BMlQmTagWLmQtIMe0OspjrXKTyQTksceOr3+IsmSctHY\nQK+ao7zLtwXKsbGDDs/j5rK9RRFHLvau/0y7/qtcnlRFkdO1/RH0T/P8mGWrF2Sp9J0+4NoR\nmaf7jiaA5/a0cfTPjAjPU5LhOdkislxi4jhPc8we01rlygHOo7zCEDWFiVuiD0l5rpkln/DO\nlDydUaB4EM6y0EZdArASxqhBBqeFS+HEhJEHIYZWjLMB2lQjxIzAHiG86gIEl4uk3FUaRT5K\nfkCxqwyUhi6SLDbfCUofexAMvh7Aprot9MLVMCTwYBAtWDbnjuLByLMJTWsPPTbw2R6iB2Eg\nzzTzdHNPYafzeEgt6BgyBR5K0olUF5T1lpOu+hamPJvLhdpeE7AUlb5CL2pOe5gbhQrbpmww\ny13KURvrBN9q4VhWwRMdynWJHwzv5psRFnHoi4r+ofDX6zkADFLkyE0oExdaoPlVG5KcvgWo\nbHYdUgUDzfiDAC27UnTfRWoe7d+PWhOKPk2sDlD3oNXELR1GGnJfs2It87tNtsq6jbYtBAu5\nDkNedNdmkzRjzX+EC/jlhOcIhNQATaeQYVKE336DeoUqyVmS8dgQoa9QwTHQIfLd5Ezf1TJw\nZOb/xdUaUuVZlMf8vJKzJwFmYcOZOVNlvIxYwQMN9fbN4XMsFfMML1fAHVrC0PKsSlLMykqU\nlOV0HiOZj2CZ0PcLFEDMxMrcpmo5Vi7oPyR9sLWlarBeLxIqxA5+Eb9kZulCI6fVgQoZSmta\n13iD1QyhVMWdjujqowcVlljwymD8cGzUhUEfc3agj3gh1+IzCBNhIr5FMB2P4JHBgfUTQRHL\nCBVzI7KAjtWGYaCuWNoG2nZAXYhq2a9h7MC3Brt9FgpepCziwfiXzfEcC7YBDIYWgRXq5tTp\nob1jldbeITW1Vwe29NtPsIYrE6n6HCyPllukg13F37RPgUVxugmD/uC+yLtXfWNGgXSwV5+7\nHRq33PiIAO5ivwvjl7GubNK1IZ4QfHS7YH1JH/L5tDvD7rOavLvoo5IMWx88So55H9Y68Vn/\nvhfw5wr4LnhHnQlWZmtXlcrNoGSL3kb64CbykqtUkl2kvBE8uMCeX2I+Rrc6ZnIRCqIvoGMY\nZLG5+Ya6yCGMqaTKpbM2Qq0iRFQJrPLla3M3fDX1kOp+l/46YX1QY8TaUJrVu74IUye/Gemw\npDuGLpy6wDLc8ZQVNBvBLMevYR9xBcMQQ/cpWkHgh5U4mc1okdKmObPVQK9aD3OAWHcHnk61\nbZue2wVShxZvZSobG3kavCUh3LfGZOBVb0LxzZ4bO8p2qfX81zMSGHbgUZ7F5py63W9q7pKW\ngrFM95pEeAQ2AHzua9kYkNsN9/9bmyCRSN3LxjQWY5lp1n0rtVnemEarE6rT3VeqcvjSKgRJ\nwaZfZ8P1hl/NW+DhQ+je1R4z/GCw9DCkBaQfgjgkwa5XNW5ja2vXHFM8GBbeuf2zkG6geusB\nqmiPBgRBJ8iom7Qh/A0m9gaadHTJ5VstFw3Rg7Z81E4tJfdBZwnLabmPqdU2oqaoe8NpxqWM\ndFIryq/3nmrC7WzICdkbIlGROblbrdsfUeuGqixoaosFqh4SlWRpS5ZRDNO2RYEwwlhgmaHB\nnh4HYWt/tmuXDRQWkh2bMfLuL5uL9+IYsCoVK3QPLEDxZpq4ugtU8ZsLUNaSKCHhP38DT0C4\nsLlpmXcH9QAwGTbZfMlT8nfnOngdJkEM4VijVSWn+WjNBO3SuMucJTKSzO5rURGImoJyyZ0U\nFCZvYEATiryyjSh2Lipc4kpgcI3AgvhqalL+2lzrIsAIOxeMJmVyxdfk79/t8kXD20xIJ0tz\n0WK9E1IQePHzBY8uic+Sq5kfE5zEoNlfYjnhUK1b5VOYkLGuWYXxcBfn6wMu1BtClCA9rKQL\nI6FbjJwo5A+2MoZTdsmRP1FK0IaUz2Z6/vyrNSPtcDbaOEPBoGfCb207VKRjyTsZwbZLf8ZW\nURCehXjI19lU9udutHRrZFKsLO0kuHr41wpdQ0r/SiN1IrQBwrAynfreYNXoZ0Ghpp0madNE\n7Y1aCIYjA0SD53NCcq7Gy1OsUL578vgv32Ot0og9uGnF0dNIdVo00dvYVpPKRiBW6J9C47qa\nsY7Q9ehMd4E+lhGsi7Vn8uQyv+L6KclTTFs1KG0tbOslWPNXmCAu+cojRnGgdpyM9Z0tizbm\ngse1wZjzt/XHRqjf3HTrxtJdw96zRYCTb56pKiKjBEGvOLvNbfpnr3yWZFgAn0iBJLVNyKqs\n6TR30GRqEIiYjBaghsaem90h4i8S8O8TrxPuR6qYdr6zDkjPMRgfOJSSHqoW7ErnHHLvOB+5\nkFaHBBu16jxrubZ173hckMxUvwaHZYmMff3dNtghPoZiTIEDCaLK4HrBM3rt0IGSQqowiugI\nb86TdPnQcHesfht6rMVb39vQrKHSE8btritSSm1nzcFxO30gSBBQ9ZTPsK0pRXRgR62kyb4b\nd/WH8Hc3OIYdaMDqyOHUcZIvOWXBUuV/HVUSeuWRwKREY+KxKQ7sCTXLgvyCGA8xrCHp9MS4\nUk9kIbxkmLZIFWUleOg06ubYJnB1TOd66zA0nFKMP1udIO6g2bjd1hK6Wdgo++/jo5dSFm84\nekkpXaQ15dSUIEOTx+vcZIvgd00CzgyIpaa5azZV56EeQ+5ozgyc7Zb1vIz+llUqkwJP05zB\nblNrH1g0HVhsz+xYR9hXeu6kn6iUxYZqwLDrgJaaPLVF9lM3vsqYaiRkRqUKZ32nlXqC580K\nzYlRLRRsGpGh1sSwq27oxOjovjdOLQStTWcySZ0LzY6n4cZ6nF9n7jHO9My1Nhuga93jx6G8\nocCkse83qc0dFuvHk6OklDxDYfTt4KA/csOQkalHjPLJkuk3EEH/9Q8np33TZlUiHam0ZoRE\nUFj4Gn94qeuPQWTGSaT+wQgGzhTH3szLintKNunJ3sYBqsrMe65aMiNZRYZZ9wazlyO4cibV\nSZWCqGd9HvmZS7WBMR27r3n231QDMy/QowK/QPPllplw0J4bk1cQiDAP2af8RjZnIVl+/XFX\nxzoTv+4nM5qFE/SWZzEORtu2LStcEmNRxAuJx7Itek8HS4eXvOt+eu3R3SuivErX3QWm8m++\nNTcFZn7RgbZQckLdloVumog7r+JyZcvwi279MEAByaoc7MD6fbed99marVlZ/dl83jaV5ddz\nVCJQLecgGiVbd+fyhfXx7yyPnW/5rRUNCXzK3NCg3U6ttxmfVRy1qyMnVEJo9Td1vZc/VPOq\ndVMta2pcr36YJTJBn1SljCdzOr/RaxSOGx3vYH6p4TVInzMGqZuvxDRdyEqj2TKlcWJzmP2f\ne8EzRVH7pc7kd/124L/6mbFtvds+u9bpdpxfh2jj8Arq1ummWtffgc3rvYsOUcqZsLOYtV+U\nfPTN1DwR1/i6b6NX9T/qMv1Gb5+kHQHeYvt+9Vj/GQQ4rPagIYuMH6uJgs2ZTlId1+lfJbjL\nPsFL44KPPzTbnzH8cYL0lfbpy/88JX7y6qb+1q5T8wA3SWwVEJ2SxiT2aDIx6tNhq37Qpx9u\n0XEiw6ZtiiPNVoaekrD7oV/l+QWRP3a61wG/6KE6r/4NQR0sVpL/RAkmuLFcGZHdhNGCied5\nzPdkgA35Q5jczGbuoUL/KkvEVPqqFwGPhiUrwgiLFnvpqL5m6GbVVfKEmdrqbZLJJ/pZgTBq\nGP3WQ7/1IsjwoprNjP5JrH4RgsK8HQY6yyPA/wBgnJmDIy0AAA==\';\n		}\n\n		foreach($resources as $file => $resource) {\n			$newfile = $path . $file;\n			if (is_array($resource)) {\n				abl_droploader_install($resource, $newfile . \'/\');\n			} else {\n				$ok = is_dir(dirname($newfile));\n				if (!$ok) {\n					$ok = mkdir(dirname($newfile), 0755, true);\n				}\n				if ($ok) {\n					$temp = base64_decode($resource);\n					if (strncmp($temp, \"\\x1F\\x8B\", 2) === 0) {\n						$temp = gzinflate(substr($temp, 10));\n					}\n					if (file_exists($newfile)) unlink($newfile);\n					$handle = fopen($newfile, \'w\');\n					if ($handle !== false) {\n						fwrite($handle, $temp);\n						chmod($newfile, 0644);\n						fclose($handle);\n					} else {\n						$state = E_ERROR;\n						error_log(\'abl_droploader: ERROR: Creating file \\\'\' . $newfile . \'\\\' failed.\');\n					}\n				} else {\n					$state = E_ERROR;\n					error_log(\'abl_droploader: ERROR: Creating directory \\\'\' . dirname($newfile) . \'\\\' failed.\');\n				}\n			}\n		}\n\n		// install new prefs (not present in previous plugin versions)\n		$pref_prefix = \'abl_droploader.\';\n		$pi_pref_defaults = abl_droploader_defaults();\n		$max_pos = safe_field(\'position\', \'txp_prefs\', \"name like \'\" . $pref_prefix . \"%\' ORDER BY position DESC\");\n		if ($max_pos === false) {\n			$max_pos = 0;\n		} else {\n			$max_pos++;\n		}\n		foreach ($pi_pref_defaults as $name => $pref) {\n			if (get_pref($pref_prefix . $name, \'nope\', 1) == \'nope\') {\n				$rc = set_pref(\n					$pref_prefix . $name,\n					$pref[\'val\'],\n					\'plugin_prefs\',\n					2,\n					$pref[\'html\'],\n					$max_pos\n				);\n				if ($rc === false) {\n					$state = E_ERROR;\n					error_log(\'abl_droploader: WARNING: Preference \\\'\' . $pref_prefix . $name . \'\\\' not created.\');\n				}\n				$max_pos++;\n			}\n		}\n		// delete obsolete prefs (if any)\n		$current_prefs = safe_column(\'name\', \'txp_prefs\', \"name like \'\" . $pref_prefix . \"%\'\");\n		foreach ($current_prefs as $current_pref) {\n			$p = substr($current_pref, strlen($pref_prefix));\n			if (!array_key_exists($p, $pi_pref_defaults)) {\n				if (!safe_delete(\'txp_prefs\', \"name = \'\" . $pref_prefix . $p .\"\'\")) {\n					if ($state == 0) $state = E_NOTICE;\n					error_log(\'abl_droploader: Notice: Removal of obsolete preference \\\'\' . $pref_prefix . $name . \'\\\' failed.\');\n				}\n			}\n		}\n		// order prefs\n		$pos = 0;\n		foreach ($pi_pref_defaults as $name => $pref) {\n			safe_update(\'txp_prefs\', \"position = \'\" . $pos . \"\'\", \"name = \'\" . $pref_prefix . $name . \"\'\");\n			$pos++;\n		}\n\n		// set load order to 9 (important!)\n		if (!safe_update(\'txp_plugin\', \'load_order = 9\', \"name=\'abl_droploader\'\")) {\n			if ($state == 0 or $state == E_NOTICE) $state = E_WARNING;\n			error_log(\'abl_droploader: WARNING: Setting the plugin load order to 9 failed.\');\n		}\n		return $state;\n\n	}\n\n	/**\n	 * inject css & js in html-header\n	 *\n	 * @param string $evt (admin_side)\n	 * @param string $stp (head_end)\n	 */\n	function abl_droploader_head_end($evt, $stp) {\n		global $event, $step, $prefs, $abl_droploader_prefs;\n\n		if (($event == \'image\'\n			&& (in_array($step, array(\'list\', \'image_list\', \'image_multi_edit\', \'image_change_pageby\', \'image_save\', \'\'))))\n		|| ($event == \'article\'\n			&& (in_array($step, array(\'create\', \'edit\'))))) {\n			$abl_droploader_prefs = abl_droploader_load_prefs();\n			$css = \'\';\n			if (intval($abl_droploader_prefs[\'useDefaultStylesheet\']) != 0) {\n				$css .= \'<link rel=\"stylesheet\" href=\"../res/css/abl.droploader-app.css\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			if ($abl_droploader_prefs[\'customStylesheet\'] != \'\') {\n				$css .= \'<link rel=\"stylesheet\" href=\"\' . $abl_droploader_prefs[\'customStylesheet\'] . \'\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			if ($css == \'\') {\n				$css = \'<link rel=\"stylesheet\" href=\"../res/css/abl.droploader-app.css\" type=\"text/css\" media=\"screen,projection\" />\' . n;\n			}\n			$article_image_field_ids = \'\"\' . implode(\'\", \"\', explode(\',\', $abl_droploader_prefs[\'articleImageFields\'])) . \'\"\';\n			$script = \'<script type=\"text/javascript\">\n	var file_max_upload_size = \' . sprintf(\'%F\', (intval($prefs[\'file_max_upload_size\']) / 1048576)) . \';\n	var file_max_files = 5;\n	var image_max_upload_size = 1;\n	var image_max_files = \' . intval($abl_droploader_prefs[\'imageMaxUploadCount\']) . \';\n	var reload_image_tab = \' . intval($abl_droploader_prefs[\'reloadImagesTab\']) . \';\n	var article_image_field_ids = new Array(\' . $article_image_field_ids . \');\n	var article_image_field = null;\n</script>\n<script src=\"../res/js/jquery.filedrop.js\" type=\"text/javascript\"></script>\n<script src=\"../res/js/jquery.droploader.js\" type=\"text/javascript\"></script>\n<script src=\"../res/js/abl.droploader-app.js\" type=\"text/javascript\"></script>\' . n;\n			echo $css . $script;\n		}\n\n	}\n\n\n	/**\n	 * Insert DropLoader link in column 1 on the write-tab\n	 *\n	 * @param string $event (article_ui)\n	 * @param string $step (extend_col_1)\n	 */\n	function abl_droploader_article_ui($event, $step) {\n		$content = \'\n<ul class=\"abl_droploader plain-list\">\n<li><a id=\"abl-droploader-open\" class=\"abl-droploader-open\" href=\"#\" title=\"\' . gTxt(\'abl_droploader_open_title\') . \'\">\' . gTxt(\'abl_droploader_open\') . \'</a></li>\n</ul>\';\n		if (is_callable(\'wrapRegion\')) { // new in txp 4.6\n			return wrapRegion(\'abl_droploader_group\', $content, \'abl_droploader_link\', \'upload\', \'article_abl_droploader\');\n		} else {\n			return \'\n<div id=\"abl_droploader_group\"><h3 class=\"plain lever\"><a href=\"#abl_droploader-link\">\' . gTxt(\'upload\') . \'</a></h3>\n<div id=\"abl_droploader-link\" class=\"toggle\" style=\"display:none\">\' .\n$content . \'\n</div>\n</div>\n\';\n		}\n	}\n\n	/**\n	 * Insert DropLoader link above the image-list on the image-tab\n	 *\n	 * @param string $event (image_ui)\n	 * @param string $step (extend_controls)\n	 */\n	function abl_droploader_image_ui($event, $step) {\n		return \'<p class=\"plain\"><a id=\"abl-droploader-open\" class=\"abl-droploader-open\" href=\"#\" title=\"\' . gTxt(\'abl_droploader_open_title\') . \'\">\' . gTxt(\'abl_droploader_open\') . \'</a></p>\' . n;\n	}\n\n	/**\n	 * handle AJAX requests\n	 * return JSON and exit\n	 *\n	 * @param string $event (abl_droploader)\n	 * @param string $step (get_form_data, get_image_data)\n	 */\n	function abl_droploader_ajax($event, $step) {\n\n		if ($event == \'abl_droploader\') {\n			switch ($step) {\n				case \'get_form_data\':\n					$response = array(\n						\'status\' => 0,\n						\'image_upload_link\' => \'\',\n						\'image_upload_form\' => \'\',\n						\'image_cat_select\' => \'\',\n						\'l10n\' => \'\'\n					);\n					$items = explode(\',\', gps(\'items\'));\n					foreach ($items as $item) {\n						switch ($item) {\n							case \'image_upload_link\':\n								$response[\'image_upload_link\'] = abl_droploader_image_ui(\'\', \'\');\n								break;\n							case \'image_upload_form\':\n								$response[\'image_upload_form\'] = abl_droploader_get_image_upload_form();\n								break;\n							case \'image_cat_select\':\n								$response[\'image_cat_select\'] = abl_droploader_get_image_cat_select();\n								break;\n							case \'l10n\':\n								$response[\'l10n\'] = abl_droploader_get_localisation();\n								break;\n							case \'all\':\n								$response[\'image_upload_link\'] = abl_droploader_image_ui(\'\', \'\');\n								$response[\'image_upload_form\'] = abl_droploader_get_image_upload_form();\n								$response[\'image_cat_select\'] = abl_droploader_get_image_cat_select();\n								$response[\'l10n\'] = abl_droploader_get_localisation();\n								break;\n						}\n					}\n					$response[\'status\'] = 1;\n					break;\n			}\n			echo json_encode($response);\n			exit;\n		}\n\n	}\n\n	/**\n	 * get image category-selector form element\n	 */\n	function abl_droploader_get_image_cat_select() {\n		$image_categories = getTree(\'root\', \'image\');\n		//$image_categories_select = str_ireplace(\"\\n\", \'\', tag(\'<label for=\"image-category\">\' . gTxt(\'image_category\') . \'</label>\' . br .\n		//	treeSelectInput(\'category\', $image_categories, \'\', \'image-category\'), \'div\', \' id=\"abl-droploader-image-cat-sel\" class=\"category\"\'));\n		//$alt_caption =	tag(\'<label for=\"alt-text\">\'.gTxt(\'alt_text\').\'</label>\'.br.\n		//		fInput(\'text\', \'alt\', \'\', \'edit\', \'\', \'\', 50, \'\', \'alt-text\'), \'div\', \' class=\"alt text\"\').\n		//	tag(\'<label for=\"caption\">\'.gTxt(\'caption\').\'</label>\'.br.\n		//		\'<textarea id=\"caption\" name=\"caption\"></textarea>\'\n		//		, \'div\', \' class=\"caption description text\"\');\n		$image_categories_select = str_ireplace(\"\\n\", \'\', tag(tag(\'<label for=\"image-category\">\'.gTxt(\'image_category\').\'</label>\'.br.\n				treeSelectInput(\'category\', $image_categories, \'\', \'image-category\'), \'div\', \' class=\"category\"\'), \'div\', \' id=\"abl-droploader-image-cat-sel\"\'));\n		return $image_categories_select;\n	}\n\n	/**\n	 * get image upload-form\n	 */\n	function abl_droploader_get_image_upload_form() {\n		$upload_form = upload_form(gTxt(\'upload_image\'), \'upload_image\', \'image_insert\', \'image\', \'\');\n		//return \'<div id=\"droploader\">\' . n . $upload_form . n . \'</div>\' . n;\n		return $upload_form;\n	}\n\n	/**\n	 * get localised items from textpack\n	 */\n	function abl_droploader_get_localisation() {\n		$l10n = array(\n			\'open\' => \'\',\n			\'open_title\' => \'\',\n			\'close\' => \'\',\n			\'close_title\' => \'\',\n			\'error_method\' => \'\',\n			\'info_text\' => \'\',\n			\'err_invalid_filetype\' => \'\',\n			\'err_browser_not_supported\' => \'\',\n			\'err_too_many_files\' => \'\',\n			\'err_file_too_large\' => \'\',\n			\'all_files_uploaded\' => \'\',\n			\'no_files_uploaded\' => \'\',\n			\'some_files_uploaded\' => \'\',\n		);\n		foreach ($l10n as $k => $v) {\n			$l10n[$k] = gTxt(\'abl_droploader_\' . $k);\n		}\n		return $l10n;\n	}\n\n	/**\n	 * image_uploaded callback (txp_image.php)\n	 * return JSON (image-id) and exit\n	 *\n	 * @param string $event (image_uploaded)\n	 * @param string $step (image)\n	 * @param string $id (image_id)\n	 */\n	function abl_droploader_image_uploaded($event, $step, $id) {\n		if (ps(\'abl_droploader\') == \'\') return;\n		$response = array(\n			\'status\' => 1,\n			\'image_id\' => $id,\n		);\n		echo json_encode($response);\n		exit;\n	}', '5ccc386882bbcb196a8b66d5b4b59c4b', '#@admin\n# current users language\nabl_droploader_open => Upload Images\nabl_droploader_open_title => Open drag & drop multiple image uploader\nabl_droploader_close => &#x00d7;\nabl_droploader_close_title => Close DropLoader\nabl_droploader_error_method => Method {{method}} does not exist in abl.droploader-app.js.\nabl_droploader_info_text => Drop files here\\n\\nor click to select files\nabl_droploader_err_invalid_filetype => Cannot upload {{filename}}. Only images are allowed (jpg, jpeg, gif, png)!\nabl_droploader_err_browser_not_supported => Your browser does not support HTML5 file uploads!\nabl_droploader_err_too_many_files => Too many files!\\nPlease select {{maxfiles}} at most!\nabl_droploader_err_file_too_large => {{filename}} is too large!\nabl_droploader_all_files_uploaded => {{filecount}} files uploaded.\nabl_droploader_no_files_uploaded => \\nNo files where uploaded.\nabl_droploader_some_files_uploaded => \\n{{uploaded_files}} of {{filecount}} files uploaded.\nabl_droploader_prefs_image_max_upload_count => Maximum file count of image uploads\nabl_droploader_prefs_file_max_upload_count => Maximum file count of file uploads\nabl_droploader_prefs_reload_image_tab => Images-Tab: Close Droploader and reload image list after upload\nabl_droploader_prefs_use_default_stylesheet => Use default styles\nabl_droploader_prefs_custom_stylesheet => Custom stylesheet (path/filename)\nabl_droploader_prefs_article_image_fields => Article-image field(s) (comma separated list of CSS fieldnames, use #custom-n for custom field names)\n#@language en-gb\nabl_droploader_open => Upload Images\nabl_droploader_open_title => Open drag & drop multiple image uploader\nabl_droploader_close => &#x00d7;\nabl_droploader_close_title => Close DropLoader\nabl_droploader_error_method => Method {{method}} does not exist in abl.droploader-app.js.\nabl_droploader_info_text => Drop files here\\n\\nor click to select files\nabl_droploader_err_invalid_filetype => Cannot upload {{filename}}. Only images are allowed (jpg, jpeg, gif, png)!\nabl_droploader_err_browser_not_supported => Your browser does not support HTML5 file uploads!\nabl_droploader_err_too_many_files => Too many files!\\nPlease select {{maxfiles}} at most!\nabl_droploader_err_file_too_large => {{filename}} is too large!\nabl_droploader_all_files_uploaded => {{filecount}} files uploaded.\nabl_droploader_no_files_uploaded => \\nNo files where uploaded.\nabl_droploader_some_files_uploaded => \\n{{uploaded_files}} of {{filecount}} files uploaded.\nabl_droploader_prefs_image_max_upload_count => Maximum file count of image uploads\nabl_droploader_prefs_file_max_upload_count => Maximum file count of file uploads\nabl_droploader_prefs_reload_image_tab => Images-Tab: Close Droploader and reload image list after upload\nabl_droploader_prefs_use_default_stylesheet => Use default styles\nabl_droploader_prefs_custom_stylesheet => Custom stylesheet (path/filename)\nabl_droploader_prefs_article_image_fields => Article-image field(s) (comma separated list of CSS fieldnames, use #custom-n for custom field names)\n#@language de-de\nabl_droploader_open => Bilder hochladen\nabl_droploader_open_title => Drag & Drop Bild Uploader öffnen\nabl_droploader_close => &#x00d7;\nabl_droploader_close_title => DropLoader schliessen\nabl_droploader_error_method => Methode {{method}} existiert in abl.droploader-app.js nicht.\nabl_droploader_info_text => Bilder hier ablegen\\noder klicken um auszuwählen\nabl_droploader_err_invalid_filetype => {{filename}} nicht hochgeladen. Es sind nur Bilder erlaubt (jpg, jpeg, gif, png)!\nabl_droploader_err_browser_not_supported => Ihr Browser unterstützt keine HTML5 Datei Uploads!\nabl_droploader_err_too_many_files => Zu viele Dateien ausgewählt!\\n\\nEs können maximal {{maxfiles}} Dateien gleichzeitig hochgeladen werden!\nabl_droploader_err_file_too_large => {{filename}} ist zu gross!\nabl_droploader_all_files_uploaded => {{filecount}} Dateien hochgeladen.\nabl_droploader_no_files_uploaded => \\nEs wurden keine Dateien hochgeladen.\nabl_droploader_some_files_uploaded => \\n{{uploaded_files}} von {{filecount}} Dateien wurden hochgeladen.\nabl_droploader_prefs_image_max_upload_count => Maximale Anzahl Dateien beim Bild-Upload\nabl_droploader_prefs_file_max_upload_count => Maximale Anzahl Dateien beim Datei-Upload\nabl_droploader_prefs_reload_image_tab => Bilder Register: Droploader nach dem upload schliessen und Bildliste neu laden\nabl_droploader_prefs_use_default_stylesheet => Standarddarstellung verwenden\nabl_droploader_prefs_custom_stylesheet => Eigenes Stylesheet (Pfad/Dateiname)\nabl_droploader_prefs_article_image_fields => Feldnamen für Artikelbilder (Komma-getrennte Liste von Feldnamen, #custom-n für Benutzerdefinierte Felder)', '', 3, 9, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('abl_robots_txt', 0, 'Andreas Blaser', 'http://www-blaser-it.ch/', '0.1', 'Simple robots.txt editor', '<h1>abl_robots_txt</h1>\n\n<h2>Summary</h2>\n\n<p>This admin-side plugin is a simple tool for managing the <strong>robots.txt</strong> file from within Textpattern. It&#8217;s a little addition to your <span class=\"caps\">SEO</span> toolbox.</p>\n\n<p>Upon install, it creates a new tab <em>robots.txt</em> under Textpatterns <em>extensions</em> tab.</p>\n\n\n<h2>Features</h2>\n\n<ul>\n	<li>Create, update or delete the robots.txt file\n	<ul>\n		<li>Create or update the file, if the textarea is not empty when submitted</li>\n		<li>Delete robots.txt if the textarea is saved empty</li>\n	</ul></li>\n	<li>Contains basic help and some useful links</li>\n</ul>\n\n\n<h2>Requirements/Permissions</h2>\n\n<p>Users must have Publisher (1) or Managing Editor (2) permissions to access this plugin.</p>\n\n<p>Tested with Textpattern 4.4.1.</p>\n\n\n<h2>Author</h2>\n\n<p><a href=\"http://www.blaser-it.ch/\" rel=\"nofollow\">Andreas Blaser</a></p>\n\n\n<h2>Installation</h2>\n\n<p>Install and activate the plugin &#8211; as usual &#8211; by importing it via <span class=\"caps\">TXP</span>&#8217;s plugin tab.</p>\n\n\n<h2>Uninstallation</h2>\n\n<p>Deactivate and delete the plugin from <span class=\"caps\">TXP</span>&#8217;s plugin tab. Please note, uninstalling the plugin <strong>does not remove the robots.txt</strong> file if there is one.</p>\n\n\n<h2>Usage</h2>\n\n<p>Go to <em>Extensions &gt; robots.txt</em> and enter some robots rules/directives.</p>\n\n\n<h3>Adding links to the list of “Useful Resources”</h3>\n\n<p>You can add your own links to the list of resources displayed below the help. To do so, you need to add a link-category with the name ‘robots_txt’. Then you can enter your links under <em>Content &gt; Links</em> and assign them the formerly created link-category.</p>\n\n\n<h2>Changelog</h2>\n\n<h3>Version 0.1</h3>\n\n<ul>\n	<li>First public release.</li>\n</ul>', '/**\n* Plugin:      abl_robots_txt\n* Version:     0.1\n* URL:         http://www-blaser-it.ch/\n* Description: Simple robots.txt editor\n* Year:        2011\n* MD5:         d3fc6b08e5ee8ca3e1d9df4faa856ba5\n*\n* Author:      Andreas Blaser\n*\n* License:     GPL v2\n*\n*/\n	// init\n	if (@txpinterface == \'admin\') {\n		$myevent = \'abl_robots_txt\';\n		$mytab = \'robots.txt\';\n		// Set the privilege levels for our new event\n		add_privs($myevent, \'1,2\');\n		// Add a new tab under \'extensions\' associated with our event\n		register_tab(\'extensions\', $myevent, $mytab);\n		// \'abl_robots_txt_gui\' will be called to handle the new event\n		register_callback($myevent . \'_gui\', $myevent);\n	}\n	// main\n	function abl_robots_txt_gui($event, $step) {\n		// get or process data\n		$result = abl_robots_txt_data($event, $step);\n		// create gui\n		pagetop(\"Edit robots.txt\", $result[\'pagetop_msg\']);\n        $links = abl_robots_txt_links();\n        echo \'<div style=\"text-align:left; margin:0pt auto; width:790px; float:none;\">\n<div style=\"float:left; width:250px; margin-right:20px;\">\n<h3>robots.txt Help</h3>\n<h4>User-agent</h4>\n<ul class=\"plain-list\">\n<li><code>User-agent: *</code><br />Matches all User-Agents.</li>\n<li><code>User-agent: BadBot</code><br />Matches User-agent \"BadBot\".</li>\n</ul>\n<h4>Disallow</h4>\n<ul class=\"plain-list\">\n<li><code>Disallow:</code><br />Allow all Files and Directories.</li>\n<li><code>Disallow: /</code><br />Disallow all Files and Directories.</li>\n<li><code>Disallow: /help</code><br />Disallow help.html and all files in Directory \"help\".</li>\n<li><code>Disallow: /help/</code><br />Allow help.html but disallow all files in Directory \"help\".</li>\n</ul>\n<h4>XML Sitemap</h4>\n<ul class=\"plain-list\">\n<li><code>Sitemap: http://www.example.com/sitemap.xml</code><br />XML-Sitemap location.</li>\n</ul>\n<h4>Useful Resources</h4>\n<ul class=\"plain-list\">\n<li><a href=\"http://www.robotstxt.org/\" target=\"_blank\">robotstxt.org</a></li>\n<li><a href=\"http://en.wikipedia.org/wiki/Robots_exclusion_standard\" target=\"_blank\">wikipedia</a></li>\n<li><a href=\"http://www.google.com/support/webmasters/bin/answer.py?hl=en&amp;answer=156449\" target=\"_blank\">Google</a></li>\n\' . $links . \'</ul>\n</div>\n</div>\' . n;\n		echo \'<div style=\"float: left; width: 520px;\">\' . n .\n            form(\n			graf(text_area($event . \'_editor\', \'300\', \'500\', $result[\'content\']).\'<br />\' .\n				fInput(\'submit\', $event . \'_submit\', \'Save\', \'publish\') .\n				eInput($event) . sInput($event . \'_save\'),\n				\' style=\"float:left; width: 550px; text-align:left;\"\')\n			) . n .\n        \'</div>\' . n;\n		echo \'<div style=\"float:none; clear:both; display:block; width:100%; height:0;\"></div>\' . n;\n        echo \'<div style=\"text-align:center;\"><p>\' . abl_robots_txt_identify($event) . \'</p></div>\' . n;\n	}\n\n    // get additional links from txp_links\n	function abl_robots_txt_links() {\n		//$sort = get_pref(\'link_sort_column\', \'name\');\n        $sort = \'linksort\';\n		$dir = get_pref(\'link_sort_dir\', \'asc\');\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n        $criteria = \'category = \"robots_txt\"\';\n        $sort_sql = $sort . \' \' . $dir;\n		$rs = safe_rows_start(\'*, unix_timestamp(date) as uDate\', \'txp_link\',\n                              \"$criteria order by $sort_sql limit 0, 10\");\n        $links = \'\';\n        if ($rs) {\n            while ($row = nextRow($rs)) {\n                extract($row);\n                $links .= \'<li><a href=\"\' . $url . \'\" target=\"_blank\" title=\"\' . $description . \'\">\' . $linkname . \'</a></li>\' . n;\n            }\n        }\n        return $links;\n    }\n\n	// read & write robots.txt\n	function abl_robots_txt_data($event, $step) {\n		$fn = $GLOBALS[\'prefs\'][\'path_to_site\'] . \'/robots.txt\';\n		$robots_exists = file_exists($fn);\n		$content = \'\';\n		$pagetop_msg = \'\';\n		if (ps($event . \'_submit\')) {\n			// create or update file\n			$content = trim(ps($event . \'_editor\'));\n			if ($content != \'\') {\n				$ret = file_put_contents($fn, $content);\n				if ($ret === false) {\n					if ($robots_exists) {\n						$pagetop_msg = \"Error! \'robots.txt\' NOT updated!\";\n					} else {\n						$pagetop_msg = \"Error! \'robots.txt\' NOT created!\";\n					}\n				} else {\n					if ($robots_exists) {\n						$pagetop_msg = \"\'robots.txt\' updated.\";\n					} else {\n						$pagetop_msg = \"\'robots.txt\' created.\";\n					}\n				}\n			} else  {\n				if ($robots_exists) {\n					$ret = unlink($fn);\n					if (!unlink($fn)) {\n						// unlink failed, try to empty that file\n						$ret = file_put_contents($fn, \"\");\n						if ($ret === false) {\n							$pagetop_msg = \"Error! Could NOT delete or clear \'robots.txt\'.\";\n						} else {\n							$pagetop_msg = \"File \'robots.txt\' cleared.\";\n						}\n					}\n				}\n			}\n		} else {\n			// read file if it exists\n			if ($robots_exists) $content = file_get_contents($fn);\n			if ($content === false) {\n				$pagetop_msg = \"Error! Could not retrieve \'robots.txt\'.\";\n				$content = \"\";\n			}\n		}\n		return array(\"content\" => $content, \"pagetop_msg\" => $pagetop_msg);\n	}\n\n	// identity\n	function abl_robots_txt_identify($event) {\n		global $plugins_ver;\n		return $event . \' &#183; \' . $plugins_ver[$event];\n	}', '/**\n* Plugin:      abl_robots_txt\n* Version:     0.1\n* URL:         http://www-blaser-it.ch/\n* Description: Simple robots.txt editor\n* Year:        2011\n* MD5:         d3fc6b08e5ee8ca3e1d9df4faa856ba5\n*\n* Author:      Andreas Blaser\n*\n* License:     GPL v2\n*\n*/\n	// init\n	if (@txpinterface == \'admin\') {\n		$myevent = \'abl_robots_txt\';\n		$mytab = \'robots.txt\';\n		// Set the privilege levels for our new event\n		add_privs($myevent, \'1,2\');\n		// Add a new tab under \'extensions\' associated with our event\n		register_tab(\'extensions\', $myevent, $mytab);\n		// \'abl_robots_txt_gui\' will be called to handle the new event\n		register_callback($myevent . \'_gui\', $myevent);\n	}\n	// main\n	function abl_robots_txt_gui($event, $step) {\n		// get or process data\n		$result = abl_robots_txt_data($event, $step);\n		// create gui\n		pagetop(\"Edit robots.txt\", $result[\'pagetop_msg\']);\n        $links = abl_robots_txt_links();\n        echo \'<div style=\"text-align:left; margin:0pt auto; width:790px; float:none;\">\n<div style=\"float:left; width:250px; margin-right:20px;\">\n<h3>robots.txt Help</h3>\n<h4>User-agent</h4>\n<ul class=\"plain-list\">\n<li><code>User-agent: *</code><br />Matches all User-Agents.</li>\n<li><code>User-agent: BadBot</code><br />Matches User-agent \"BadBot\".</li>\n</ul>\n<h4>Disallow</h4>\n<ul class=\"plain-list\">\n<li><code>Disallow:</code><br />Allow all Files and Directories.</li>\n<li><code>Disallow: /</code><br />Disallow all Files and Directories.</li>\n<li><code>Disallow: /help</code><br />Disallow help.html and all files in Directory \"help\".</li>\n<li><code>Disallow: /help/</code><br />Allow help.html but disallow all files in Directory \"help\".</li>\n</ul>\n<h4>XML Sitemap</h4>\n<ul class=\"plain-list\">\n<li><code>Sitemap: http://www.example.com/sitemap.xml</code><br />XML-Sitemap location.</li>\n</ul>\n<h4>Useful Resources</h4>\n<ul class=\"plain-list\">\n<li><a href=\"http://www.robotstxt.org/\" target=\"_blank\">robotstxt.org</a></li>\n<li><a href=\"http://en.wikipedia.org/wiki/Robots_exclusion_standard\" target=\"_blank\">wikipedia</a></li>\n<li><a href=\"http://www.google.com/support/webmasters/bin/answer.py?hl=en&amp;answer=156449\" target=\"_blank\">Google</a></li>\n\' . $links . \'</ul>\n</div>\n</div>\' . n;\n		echo \'<div style=\"float: left; width: 520px;\">\' . n .\n            form(\n			graf(text_area($event . \'_editor\', \'300\', \'500\', $result[\'content\']).\'<br />\' .\n				fInput(\'submit\', $event . \'_submit\', \'Save\', \'publish\') .\n				eInput($event) . sInput($event . \'_save\'),\n				\' style=\"float:left; width: 550px; text-align:left;\"\')\n			) . n .\n        \'</div>\' . n;\n		echo \'<div style=\"float:none; clear:both; display:block; width:100%; height:0;\"></div>\' . n;\n        echo \'<div style=\"text-align:center;\"><p>\' . abl_robots_txt_identify($event) . \'</p></div>\' . n;\n	}\n\n    // get additional links from txp_links\n	function abl_robots_txt_links() {\n		//$sort = get_pref(\'link_sort_column\', \'name\');\n        $sort = \'linksort\';\n		$dir = get_pref(\'link_sort_dir\', \'asc\');\n		$dir = ($dir == \'desc\') ? \'desc\' : \'asc\';\n        $criteria = \'category = \"robots_txt\"\';\n        $sort_sql = $sort . \' \' . $dir;\n		$rs = safe_rows_start(\'*, unix_timestamp(date) as uDate\', \'txp_link\',\n                              \"$criteria order by $sort_sql limit 0, 10\");\n        $links = \'\';\n        if ($rs) {\n            while ($row = nextRow($rs)) {\n                extract($row);\n                $links .= \'<li><a href=\"\' . $url . \'\" target=\"_blank\" title=\"\' . $description . \'\">\' . $linkname . \'</a></li>\' . n;\n            }\n        }\n        return $links;\n    }\n\n	// read & write robots.txt\n	function abl_robots_txt_data($event, $step) {\n		$fn = $GLOBALS[\'prefs\'][\'path_to_site\'] . \'/robots.txt\';\n		$robots_exists = file_exists($fn);\n		$content = \'\';\n		$pagetop_msg = \'\';\n		if (ps($event . \'_submit\')) {\n			// create or update file\n			$content = trim(ps($event . \'_editor\'));\n			if ($content != \'\') {\n				$ret = file_put_contents($fn, $content);\n				if ($ret === false) {\n					if ($robots_exists) {\n						$pagetop_msg = \"Error! \'robots.txt\' NOT updated!\";\n					} else {\n						$pagetop_msg = \"Error! \'robots.txt\' NOT created!\";\n					}\n				} else {\n					if ($robots_exists) {\n						$pagetop_msg = \"\'robots.txt\' updated.\";\n					} else {\n						$pagetop_msg = \"\'robots.txt\' created.\";\n					}\n				}\n			} else  {\n				if ($robots_exists) {\n					$ret = unlink($fn);\n					if (!unlink($fn)) {\n						// unlink failed, try to empty that file\n						$ret = file_put_contents($fn, \"\");\n						if ($ret === false) {\n							$pagetop_msg = \"Error! Could NOT delete or clear \'robots.txt\'.\";\n						} else {\n							$pagetop_msg = \"File \'robots.txt\' cleared.\";\n						}\n					}\n				}\n			}\n		} else {\n			// read file if it exists\n			if ($robots_exists) $content = file_get_contents($fn);\n			if ($content === false) {\n				$pagetop_msg = \"Error! Could not retrieve \'robots.txt\'.\";\n				$content = \"\";\n			}\n		}\n		return array(\"content\" => $content, \"pagetop_msg\" => $pagetop_msg);\n	}\n\n	// identity\n	function abl_robots_txt_identify($event) {\n		global $plugins_ver;\n		return $event . \' &#183; \' . $plugins_ver[$event];\n	}', '3188f9d21d7f76360468d61a5638adf7', '', '', 1, 5, 0),
('zcr_mailchimp', 0, 'Stef Dawson', 'http://stefdawson.com', '0.10', 'Textpattern CMS Mailchimp module for zem_contact_reborn', '<h1>zcr_mailchimp</h1>\n\n<p>A Textpattern <span class=\"caps\">CMS</span> plugin module for the zem_contact_reborn mailer, permitting people to subscribe to mailing lists you have set up in MailChimp.</p>\n\n<h2>Installation</h2>\n\n<p><span class=\"caps\">IMPORTANT</span>: requires zem_contact_reborn v4.5.0.0+ to be installed and active.</p>\n\n<p>Download the plugin, visit your <em>Admin-&gt;Plugins</em> panel, paste the code in the box, upload, install, activate. Done.</p>\n\n<h2>Usage</h2>\n\n<p>The plugin silently sits waiting for zem_contact_reborn submissions. If any submission has the correct combination of mailchimp fields, the plugin wakes up, performs the designated action (which by default is subscribe to list) and then tells <span class=\"caps\">ZCR</span> it&#8217;s done. In its default configuration, the send-an-email portion of the process is bypassed. This can be overridden using <code>zcr_return_action</code>: see below.</p>\n\n<p>The way you set up the plugin is by writing a standard <code>&lt;txp:zem_contact&gt;</code> form, but with specific elements to configure and control the process. Use <code>&lt;txp:zem_contact_secret /&gt;</code> to tell the plugin how to interact with MailChimp. Any other, regular fields will simply be passed to MailChimp as <a href=\"http://kb.mailchimp.com/merge-tags/all-the-merge-tags-cheatsheet\">merge tags</a>.</p>\n\n<p>Here&#8217;s a simple example, which collects an email address, first name, and last name for subscription purposes:</p>\n\n<pre><code>&lt;p&gt;Sign up for our newsletter!&lt;/p&gt;\n&lt;txp:variable name=&quot;chimpApiKey&quot;&gt;abc123abc123abc123abc123abc123-us1&lt;/txp:variable&gt;\n&lt;txp:zem_contact to=&quot;not_important@example.org&quot;&gt;\n   &lt;txp:zem_contact_secret label=&quot;mailchimp_api_key&quot; value=&#39;&lt;txp:variable name=&quot;chimpApiKey&quot; /&gt;&#39; /&gt;\n   &lt;txp:zem_contact_secret label=&quot;mailchimp_double_optin:b&quot; value=&quot;false&quot; /&gt;\n   &lt;txp:zem_contact_secret label=&quot;list_id&quot; value=&quot;mylist1234&quot; /&gt;\n   &lt;txp:zem_contact_email name=&quot;email&quot; /&gt;\n   &lt;txp:zem_contact_text name=&quot;FNAME&quot; label=&quot;First name&quot; /&gt;\n   &lt;txp:zem_contact_text name=&quot;LNAME&quot; label=&quot;Last name&quot; /&gt;\n   &lt;txp:zem_contact_submit label=&quot;Subscribe&quot; /&gt;\n&lt;/txp:zem_contact&gt;</code></pre>\n\n<p>Pretty simple. Any secret values that begin with <code>mailchimp_</code> are treated as configuration parameters. These can be:</p>\n\n<ul>\n	<li><strong>mailchimp_api_key</strong> The trigger for the plugin to wake up. Obtain one of these when you sign up for MailChimp. The example above stores it in a variable so you can use it on other pages, potentially for other lists or actions.</li>\n	<li><strong>mailchimp_action</strong> The <span class=\"caps\">API</span> action to perform on MailChimp. Defaults to <code>lists/subscribe</code>. Another useful one might be <code>lists/unsubscribe</code>.</li>\n	<li><strong>mailchimp_????</strong> Any other parameters required for the action. Examples include <code>double_optin</code>, <code>send_welcome</code>, <code>update_existing</code>, <code>email_type</code>, <code>send_goodbye</code>. See the <a href=\"https://apidocs.mailchimp.com/api/2.0/\"><span class=\"caps\">API</span> docs</a> for a complete list. The above example sets <code>double_optin</code> to <code>false</code>.</li>\n</ul>\n\n<p>When sending extra parameters, you tell the plugin each parameter&#8217;s data type by adding a colon and a single letter to denote its type. Options are:</p>\n\n<ul>\n	<li><code>:b</code> Boolean.</li>\n	<li><code>:i</code> Integer.</li>\n	<li><code>:s</code> String (default).</li>\n</ul>\n\n<p>Other variables (without any prefix) are treated as regular parameters (strings) to send to MailChimp for the purposes of storage. They are all passed as Merge Tags, with the exception of two special ones:</p>\n\n<ul>\n	<li><strong>list_id</strong> The subscription list ID of the list to which you wish people to be able to subscribe.</li>\n	<li><strong>email</strong> The email address of the person who is subscribing.</li>\n</ul>\n\n<p>There are also zem_contact_reborn configuration parameters, which begin with <code>zcr_</code>. At the moment there&#8217;s only one:</p>\n\n<ul>\n	<li><strong>return_action</strong> Determines what happens when the MailChimp process completes:\n	<ul>\n		<li><strong>skip</strong> The default, which causes the regular <span class=\"caps\">ZCR</span> mail process to be bypassed, while still returning the regular &#8216;success&#8217; message.</li>\n		<li><strong>fail</strong> Tell <span class=\"caps\">ZCR</span> to return a failure code.</li>\n		<li><strong>success</strong> To allow <span class=\"caps\">ZCR</span> to continue sending an email to the recipient. Note that the recipient in this case is the <code>to</code> attribute of the <code>&lt;txp:zem_contact&gt;</code> tag (probably you!), not the person signing up.</li>\n	</ul></li>\n	</ul>\n\n<p>If you elect to use <code>copysender</code> in <span class=\"caps\">ZCR</span>, the signup request will still only be performed once, and <span class=\"caps\">ZCR</span> will send out an email to the subscriber, irrespective of the setting of <code>return_action</code>.</p>\n\n<h2>Author / Credits</h2>\n\n<p>Written by Stef &#8216;Bloke&#8217; Dawson, but the plugin could not have existed without the awesomeness of Drew McLellan who wrote the microscopic MailChimp <span class=\"caps\">API</span> broomstick upon which this plugin rides. All the kudos go to him.</p>\n\n<h2>Changelog</h2>\n\n<ul>\n	<li>10 Dec 2014 | v0.10 | Initial release</li>\n</ul>', 'if (txpinterface === \'public\') {\n    register_callback(\'zcr_mailchimp\', \'zemcontact.deliver\');\n}\n\n/**\n * Callback hook for zem_contact_reborn to handle delivery.\n */\nfunction zcr_mailchimp($evt, $stp, &$payload)\n{\n    // If using copysender, it\'s the 2nd time the plugin has been called so no need\n    // to do anything. ZCR will continue as normal and mail out the email.\n    if ($stp === \'copysender\') {\n        return \'\';\n    }\n\n    $zcrConfig = array(\'return_action\' => \'skip\');\n    $chimpConfig = array(\'email\' => array(\'email\' => null), \'id\' => null);\n    $chimpVars = array();\n    $apiKey = null;\n    $action = \'lists/subscribe\';\n\n    foreach ($payload[\'fields\'] as $key => $value) {\n        if ($key === \'mailchimp_api_key\') {\n           $apiKey = $value;\n        } elseif ($key === \'mailchimp_action\') {\n           $action = $value;\n        } elseif (strpos($key, \'mailchimp_\') === 0) {\n            $rawKey = substr($key, 10); // Strip off mailchimp_ prefix\n\n            // Optional parameter type can be specified at end of key.\n            // Default to string.\n            // Todo: arrays?\n            $parts = explode(\':\', $rawKey);\n            $param = $parts[0];\n            $type = (isset($parts[1])) ? $parts[1] : \'s\';\n\n            switch ($type) {\n                case \'b\':\n                    $chimpConfig[$param] = (bool)$value;\n                    break;\n                case \'i\':\n                    $chimpConfig[$param] = (int)$value;\n                    break;\n                case \'s\':\n                default:\n                    $chimpConfig[$param] = $value;\n                    break;\n            }\n        } elseif (strpos($key, \'zcr_\') === 0) {\n            $param = substr($key, 4); // Strip off zcr_ prefix\n            $zcrConfig[$param] = $value;\n        } elseif ($key === \'list_id\') {\n            $chimpConfig[\'id\'] = $value;\n        } elseif ($key === \'email\') {\n            // Don\'t ask. It\'s their spec!\n            $chimpConfig[\'email\'][\'email\'] = $value;\n        } else {\n            $chimpVars[$key] = $value;\n        }\n    }\n\n    if ($apiKey) {\n        $chimpConfig[\'merge_vars\'] = $chimpVars;\n\n        $mc = new zcr_Mailchimp($apiKey);\n        $result = $mc->call($action, $chimpConfig);\n\n        // Todo: what to do with the result?\n\n        if ($zcrConfig[\'return_action\']) {\n            return \'zemcontact.\' . $zcrConfig[\'return_action\'];\n        }\n    } else {\n        // Not for us.\n        return \'\';\n    }\n}\n\n/**\n * Super-simple, minimum abstraction MailChimp API v2 wrapper\n *\n * Uses curl if available, falls back to file_get_contents and HTTP stream.\n * This probably has more comments than code.\n *\n * Contributors:\n * Michael Minor <me@pixelbacon.com>\n * Lorna Jane Mitchell, github.com/lornajane\n *\n * @author Drew McLellan <drew.mclellan@gmail.com>\n * @version 1.1.1\n */\nclass zcr_MailChimp\n{\n    private $api_key;\n    private $api_endpoint = \'https://<dc>.api.mailchimp.com/2.0\';\n    private $verify_ssl   = false;\n\n    /**\n     * Create a new instance\n     * @param string $api_key Your MailChimp API key\n     */\n    function __construct($api_key)\n    {\n        $this->api_key = $api_key;\n        list(, $datacentre) = explode(\'-\', $this->api_key);\n        $this->api_endpoint = str_replace(\'<dc>\', $datacentre, $this->api_endpoint);\n    }\n\n    /**\n     * Call an API method. Every request needs the API key, so that is added automatically -- you don\'t need to pass it in.\n     * @param  string $method The API method to call, e.g. \'lists/list\'\n     * @param  array  $args   An array of arguments to pass to the method. Will be json-encoded for you.\n     * @return array          Associative array of json decoded API response.\n     */\n    public function call($method, $args=array(), $timeout = 10)\n    {\n        return $this->makeRequest($method, $args, $timeout);\n    }\n\n    /**\n     * Performs the underlying HTTP request. Not very exciting\n     * @param  string $method The API method to be called\n     * @param  array  $args   Assoc array of parameters to be passed\n     * @return array          Assoc array of decoded result\n     */\n    private function makeRequest($method, $args=array(), $timeout = 10)\n    {\n        $args[\'apikey\'] = $this->api_key;\n\n        $url = $this->api_endpoint.\'/\'.$method.\'.json\';\n\n        if (function_exists(\'curl_init\') && function_exists(\'curl_setopt\')){\n            $ch = curl_init();\n            curl_setopt($ch, CURLOPT_URL, $url);\n            curl_setopt($ch, CURLOPT_HTTPHEADER, array(\'Content-Type: application/json\'));\n            curl_setopt($ch, CURLOPT_USERAGENT, \'PHP-MCAPI/2.0\');\n            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\n            curl_setopt($ch, CURLOPT_POST, true);\n            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);\n            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($args));\n            $result = curl_exec($ch);\n            curl_close($ch);\n        } else {\n            $json_data = json_encode($args);\n            $result    = file_get_contents($url, null, stream_context_create(array(\n                \'http\' => array(\n                    \'protocol_version\' => 1.1,\n                    \'user_agent\'       => \'PHP-MCAPI/2.0\',\n                    \'method\'           => \'POST\',\n                    \'header\'           => \"Content-type: application/json\\r\\n\".\n                                          \"Connection: close\\r\\n\" .\n                                          \"Content-length: \" . strlen($json_data) . \"\\r\\n\",\n                    \'content\'          => $json_data,\n                ),\n            )));\n        }\n\n        return $result ? json_decode($result, true) : false;\n    }\n}', 'if (txpinterface === \'public\') {\n    register_callback(\'zcr_mailchimp\', \'zemcontact.deliver\');\n}\n\n/**\n * Callback hook for zem_contact_reborn to handle delivery.\n */\nfunction zcr_mailchimp($evt, $stp, &$payload)\n{\n    // If using copysender, it\'s the 2nd time the plugin has been called so no need\n    // to do anything. ZCR will continue as normal and mail out the email.\n    if ($stp === \'copysender\') {\n        return \'\';\n    }\n\n    $zcrConfig = array(\'return_action\' => \'skip\');\n    $chimpConfig = array(\'email\' => array(\'email\' => null), \'id\' => null);\n    $chimpVars = array();\n    $apiKey = null;\n    $action = \'lists/subscribe\';\n\n    foreach ($payload[\'fields\'] as $key => $value) {\n        if ($key === \'mailchimp_api_key\') {\n           $apiKey = $value;\n        } elseif ($key === \'mailchimp_action\') {\n           $action = $value;\n        } elseif (strpos($key, \'mailchimp_\') === 0) {\n            $rawKey = substr($key, 10); // Strip off mailchimp_ prefix\n\n            // Optional parameter type can be specified at end of key.\n            // Default to string.\n            // Todo: arrays?\n            $parts = explode(\':\', $rawKey);\n            $param = $parts[0];\n            $type = (isset($parts[1])) ? $parts[1] : \'s\';\n\n            switch ($type) {\n                case \'b\':\n                    $chimpConfig[$param] = (bool)$value;\n                    break;\n                case \'i\':\n                    $chimpConfig[$param] = (int)$value;\n                    break;\n                case \'s\':\n                default:\n                    $chimpConfig[$param] = $value;\n                    break;\n            }\n        } elseif (strpos($key, \'zcr_\') === 0) {\n            $param = substr($key, 4); // Strip off zcr_ prefix\n            $zcrConfig[$param] = $value;\n        } elseif ($key === \'list_id\') {\n            $chimpConfig[\'id\'] = $value;\n        } elseif ($key === \'email\') {\n            // Don\'t ask. It\'s their spec!\n            $chimpConfig[\'email\'][\'email\'] = $value;\n        } else {\n            $chimpVars[$key] = $value;\n        }\n    }\n\n    if ($apiKey) {\n        $chimpConfig[\'merge_vars\'] = $chimpVars;\n\n        $mc = new zcr_Mailchimp($apiKey);\n        $result = $mc->call($action, $chimpConfig);\n\n        // Todo: what to do with the result?\n\n        if ($zcrConfig[\'return_action\']) {\n            return \'zemcontact.\' . $zcrConfig[\'return_action\'];\n        }\n    } else {\n        // Not for us.\n        return \'\';\n    }\n}\n\n/**\n * Super-simple, minimum abstraction MailChimp API v2 wrapper\n *\n * Uses curl if available, falls back to file_get_contents and HTTP stream.\n * This probably has more comments than code.\n *\n * Contributors:\n * Michael Minor <me@pixelbacon.com>\n * Lorna Jane Mitchell, github.com/lornajane\n *\n * @author Drew McLellan <drew.mclellan@gmail.com>\n * @version 1.1.1\n */\nclass zcr_MailChimp\n{\n    private $api_key;\n    private $api_endpoint = \'https://<dc>.api.mailchimp.com/2.0\';\n    private $verify_ssl   = false;\n\n    /**\n     * Create a new instance\n     * @param string $api_key Your MailChimp API key\n     */\n    function __construct($api_key)\n    {\n        $this->api_key = $api_key;\n        list(, $datacentre) = explode(\'-\', $this->api_key);\n        $this->api_endpoint = str_replace(\'<dc>\', $datacentre, $this->api_endpoint);\n    }\n\n    /**\n     * Call an API method. Every request needs the API key, so that is added automatically -- you don\'t need to pass it in.\n     * @param  string $method The API method to call, e.g. \'lists/list\'\n     * @param  array  $args   An array of arguments to pass to the method. Will be json-encoded for you.\n     * @return array          Associative array of json decoded API response.\n     */\n    public function call($method, $args=array(), $timeout = 10)\n    {\n        return $this->makeRequest($method, $args, $timeout);\n    }\n\n    /**\n     * Performs the underlying HTTP request. Not very exciting\n     * @param  string $method The API method to be called\n     * @param  array  $args   Assoc array of parameters to be passed\n     * @return array          Assoc array of decoded result\n     */\n    private function makeRequest($method, $args=array(), $timeout = 10)\n    {\n        $args[\'apikey\'] = $this->api_key;\n\n        $url = $this->api_endpoint.\'/\'.$method.\'.json\';\n\n        if (function_exists(\'curl_init\') && function_exists(\'curl_setopt\')){\n            $ch = curl_init();\n            curl_setopt($ch, CURLOPT_URL, $url);\n            curl_setopt($ch, CURLOPT_HTTPHEADER, array(\'Content-Type: application/json\'));\n            curl_setopt($ch, CURLOPT_USERAGENT, \'PHP-MCAPI/2.0\');\n            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);\n            curl_setopt($ch, CURLOPT_POST, true);\n            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);\n            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($args));\n            $result = curl_exec($ch);\n            curl_close($ch);\n        } else {\n            $json_data = json_encode($args);\n            $result    = file_get_contents($url, null, stream_context_create(array(\n                \'http\' => array(\n                    \'protocol_version\' => 1.1,\n                    \'user_agent\'       => \'PHP-MCAPI/2.0\',\n                    \'method\'           => \'POST\',\n                    \'header\'           => \"Content-type: application/json\\r\\n\".\n                                          \"Connection: close\\r\\n\" .\n                                          \"Content-length: \" . strlen($json_data) . \"\\r\\n\",\n                    \'content\'          => $json_data,\n                ),\n            )));\n        }\n\n        return $result ? json_decode($result, true) : false;\n    }\n}', '530a07e3130c43b352d7345fb246ebbd', '', '', 0, 5, 0),
('cnn_title_case', 0, 'Chris Newkirk', 'http://chrisnewkirk.com/', '1.0', 'Attempts to convert text to Title Case.', '	<h1>Plugin <code>cnn_title_case</code></h1>\n\n	<p>This is just a Textpattern port of the Wordpress plugin <a href=\"http://nanovivid.com/stuff/wordpress/title-case/\">Title Case</a> by <a href=\"http://nanovivid.com/\">Adam Nolley</a>. It is ultimatly based on <a href=\"http://daringfireball.net/2008/05/title_case\">code by John Gruber</a>.</p>\n\n	<p>To use this plugin, just wrap any text in the tag.</p>\n\n	<h2>Example Usage</h2>\n\n	<p>For example, to title-case all article titles use in an article form like this:</p>\n\n<pre><code>&lt;txp:cnn_title_case&gt;&lt;txp:title /&gt;&lt;/txp:cnn_title_case&gt;\n</code></pre>\n\n', '\nfunction cnn_title_case($atts, $thing=\'\')\n{\n	return nv_title_case(parse($thing));\n}\n\n\n/*\nPlugin Name: Title Case\nPlugin URI: http://nanovivid.com/stuff/wordpress/title-case/\nDescription: Attempts to properly capitalize post titles. Based on <a href=\"http://daringfireball.net/2008/05/title_case\">code by John Gruber</a>.\nVersion: 1.1\nAuthor: Adam Nolley\nAuthor URI: http://nanovivid.com/\n*/\nfunction nv_title_case($str)\n{\n    // Edit this list to change what words should be lowercase\n    $small_words = \"a an and as at but by en for if in of on or the to v[.]? via vs[.]?\";\n    $small_re = str_replace(\" \", \"|\", $small_words);\n\n    // Replace HTML entities for spaces and record their old positions\n    $htmlspaces = \"/&nbsp;|&#160;|&#32;/\";\n    $oldspaces = array();\n    preg_match_all($htmlspaces, $str, $oldspaces, PREG_OFFSET_CAPTURE);\n\n    // Remove HTML space entities\n    $words = preg_replace($htmlspaces, \" \", $str);\n\n    // Split around sentance divider-ish stuff\n    $words = preg_split(\'/( [:.;?!][ ] | (?:[ ]|^)[\"“])/x\', $words, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n    for ($i = 0; $i < count($words); $i++) {\n\n        // Skip words with dots in them like del.icio.us\n        $words[$i] = preg_replace_callback(\'/\\b([[:alpha:]][[:lower:].\\\'’(&\\#8217;)]*)\\b/x\', \'nv_title_skip_dotted\', $words[$i]);\n\n        // Lowercase our list of small words\n        $words[$i] = preg_replace(\"/\\b($small_re)\\b/ei\", \"strtolower(\\\"$1\\\")\", $words[$i]);\n\n        // If the first word in the title is a small word, capitalize it\n        $words[$i] = preg_replace(\"/\\A([[:punct:]]*)($small_re)\\b/e\", \"\\\"$1\\\" . ucfirst(\\\"$2\\\")\", $words[$i]);\n\n        // If the last word in the title is a small word, capitalize it\n        $words[$i] = preg_replace(\"/\\b($small_re)([[:punct:]]*)\\Z/e\", \"ucfirst(\\\"$1\\\") . \\\"$2\\\"\", $words[$i]);\n    }\n\n    $words = join($words);\n\n    // Oddities\n    $words = preg_replace(\"/ V(s?)\\. /i\", \" v$1. \", $words);                    // v, vs, v., and vs.\n    $words = preg_replace(\"/([\'’]|&#8217;)S\\b/i\", \"$1s\", $words);               // \'s\n    $words = preg_replace(\"/\\b(AT&T|Q&A)\\b/ie\", \"strtoupper(\\\"$1\\\")\", $words);  // AT&T and Q&A\n    $words = preg_replace(\"/-ing\\b/i\", \"-ing\", $words);                         // -ing\n    $words = preg_replace(\"/(&[[:alpha:]]+;)/Ue\", \"strtolower(\\\"$1\\\")\", $words);          // html entities\n\n    // Put HTML space entities back\n    $offset = 0;\n    for ($i = 0; $i < count($oldspaces[0]); $i++) {\n        $offset = $oldspaces[0][$i][1];\n        $words = substr($words, 0, $offset) . $oldspaces[0][$i][0] . substr($words, $offset + 1);\n        $offset += strlen($oldspaces[0][$i][0]);\n    }\n\n    return $words;\n}\nfunction nv_title_skip_dotted($matches) {\n    return preg_match(\'/[[:alpha:]] [.] [[:alpha:]]/x\', $matches[0]) ? $matches[0] : ucfirst($matches[0]);\n}\n', '\nfunction cnn_title_case($atts, $thing=\'\')\n{\n	return nv_title_case(parse($thing));\n}\n\n\n/*\nPlugin Name: Title Case\nPlugin URI: http://nanovivid.com/stuff/wordpress/title-case/\nDescription: Attempts to properly capitalize post titles. Based on <a href=\"http://daringfireball.net/2008/05/title_case\">code by John Gruber</a>.\nVersion: 1.1\nAuthor: Adam Nolley\nAuthor URI: http://nanovivid.com/\n*/\nfunction nv_title_case($str)\n{\n    // Edit this list to change what words should be lowercase\n    $small_words = \"a an and as at but by en for if in of on or the to v[.]? via vs[.]?\";\n    $small_re = str_replace(\" \", \"|\", $small_words);\n\n    // Replace HTML entities for spaces and record their old positions\n    $htmlspaces = \"/&nbsp;|&#160;|&#32;/\";\n    $oldspaces = array();\n    preg_match_all($htmlspaces, $str, $oldspaces, PREG_OFFSET_CAPTURE);\n\n    // Remove HTML space entities\n    $words = preg_replace($htmlspaces, \" \", $str);\n\n    // Split around sentance divider-ish stuff\n    $words = preg_split(\'/( [:.;?!][ ] | (?:[ ]|^)[\"“])/x\', $words, -1, PREG_SPLIT_DELIM_CAPTURE);\n\n    for ($i = 0; $i < count($words); $i++) {\n\n        // Skip words with dots in them like del.icio.us\n        $words[$i] = preg_replace_callback(\'/\\b([[:alpha:]][[:lower:].\\\'’(&\\#8217;)]*)\\b/x\', \'nv_title_skip_dotted\', $words[$i]);\n\n        // Lowercase our list of small words\n        $words[$i] = preg_replace(\"/\\b($small_re)\\b/ei\", \"strtolower(\\\"$1\\\")\", $words[$i]);\n\n        // If the first word in the title is a small word, capitalize it\n        $words[$i] = preg_replace(\"/\\A([[:punct:]]*)($small_re)\\b/e\", \"\\\"$1\\\" . ucfirst(\\\"$2\\\")\", $words[$i]);\n\n        // If the last word in the title is a small word, capitalize it\n        $words[$i] = preg_replace(\"/\\b($small_re)([[:punct:]]*)\\Z/e\", \"ucfirst(\\\"$1\\\") . \\\"$2\\\"\", $words[$i]);\n    }\n\n    $words = join($words);\n\n    // Oddities\n    $words = preg_replace(\"/ V(s?)\\. /i\", \" v$1. \", $words);                    // v, vs, v., and vs.\n    $words = preg_replace(\"/([\'’]|&#8217;)S\\b/i\", \"$1s\", $words);               // \'s\n    $words = preg_replace(\"/\\b(AT&T|Q&A)\\b/ie\", \"strtoupper(\\\"$1\\\")\", $words);  // AT&T and Q&A\n    $words = preg_replace(\"/-ing\\b/i\", \"-ing\", $words);                         // -ing\n    $words = preg_replace(\"/(&[[:alpha:]]+;)/Ue\", \"strtolower(\\\"$1\\\")\", $words);          // html entities\n\n    // Put HTML space entities back\n    $offset = 0;\n    for ($i = 0; $i < count($oldspaces[0]); $i++) {\n        $offset = $oldspaces[0][$i][1];\n        $words = substr($words, 0, $offset) . $oldspaces[0][$i][0] . substr($words, $offset + 1);\n        $offset += strlen($oldspaces[0][$i][0]);\n    }\n\n    return $words;\n}\nfunction nv_title_skip_dotted($matches) {\n    return preg_match(\'/[[:alpha:]] [.] [[:alpha:]]/x\', $matches[0]) ? $matches[0] : ucfirst($matches[0]);\n}\n', '74bed073f8c6b0bae3e05aefbe642f5b', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_youtube', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed YouTube video players through oui_player v2+', '<h1>oui_youtube</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_youtube\">oui_youtube</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://youtube.com/\">YouTube</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_youtube&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_youtube/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_youtube:<br />\n<code>$ composer require nicolasgraph/oui_youtube</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_youtube</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_youtube</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_youtube vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_youtube /&gt;</code> tag is a shortcut to embed YouTube videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;youtube&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Valid values for the play attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>list=<strong>list_ID</strong>;</li>\n	<li>http(s)://(www.)youtu.be/<strong>ID</strong>;</li>\n	<li>http(s)://(www.)youtube.com/v/<strong>ID</strong>(&amp;list=<strong>list_ID</strong>);</li>\n	<li>http(s)://(www.)youtube.com/watch?v=<strong>ID</strong>;</li>\n	<li>http(s)://(www.)youtube.com/embed/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_youtube</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>560</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>315</code>.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>autohide</code></dt>\n	<dd><p><code>0</code>, <code>1</code> or <code>2</code>; initially set to <code>2</code>.<br />\nIndicates whether the video controls will automatically hide after a video begins playing.<br />\nThis parameter has been deprecated for HTML5 players, which display or hide the video progress bar and player controls automatically (autohide=1).</p></dd>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nSpecifies whether the initial video will automatically start to play when the player loads.</p></dd>\n	<dt><code>cc_load_policy</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\n1 causes closed captions to be shown by default, even if the user has turned captions off.</p></dd>\n	<dt><code>color</code></dt>\n	<dd><p><code>red</code> or <code>white</code>; initially set to <code>red</code>.<br />\nSpecifies the color used in the player&#8217;s progress bar.</p></dd>\n	<dt><code>controls</code></dt>\n	<dd><p><code>0</code>, <code>1</code> or <code>2</code>; initially set to <code>1</code>.<br />\nIndicates whether the video player controls are displayed.</p></dd>\n	<dt><code>disablekb</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>;<br />\nDisables keyboard commands.</p></dd>\n	<dt><code>enablejsapi</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>;<br />\nEnables or disables <a href=\"https://developers.google.com/youtube/js_api_reference\">the javascript player api</a>.</p></dd>\n	<dt><code>end</code></dt>\n	<dd><p>Integer; initially not set.<br />\nSpecifies the time, measured in seconds, when the player should stop playing the video.</p></dd>\n	<dt><code>fs</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nSetting this parameter to 0 prevents the fullscreen button from displaying in the player.</p></dd>\n	<dt><code>hl</code></dt>\n	<dd><p><span class=\"caps\">ISO</span> 639-1 two-letter language code or a fully specified locale; initially not set.<br />\nSets the player&#8217;s interface language.</p></dd>\n	<dt><code>iv_load_policy</code></dt>\n	<dd><p><code>1</code> or <code>3</code>; initially set to <code>1</code>.<br />\nSetting the parameter&#8217;s value to 1 causes video annotations to be shown by default.</p></dd>\n	<dt><code>loop</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nCauses the player to play the video again and again.</p></dd>\n	<dt><code>modestbranding</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nLets you use a YouTube player that does not show a YouTube logo.</p></dd>\n	<dt><code>origin</code></dt>\n	<dd><p><span class=\"caps\">URL</span>; initially not set.<br />\n<span class=\"caps\">URL</span> scheme (<code>http://</code> or <code>https://</code>) and full domain of your host page.<br />\nRequired for <code>https://</code> <span class=\"caps\">URL</span>&#8217;s; including it protects against malicious third-party JavaScript being injected into your page and hijacking control of your YouTube player.</p></dd>\n	<dt><code>playlist</code></dt>\n	<dd><p>Comma separeted list of video id&#8217;s; initially not set.<br />\nA list of video id&#8217;s to read after the one provided via the <code>video</code> attribute.</p></dd>\n	<dt><code>playsinline</code></dt>\n	<dd><p>Initially not set.<br />\nControls whether videos play inline or fullscreen in an HTML5 player on iOS.</p></dd>\n	<dt><code>rel</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nIndicates whether the player should show related videos when playback of the initial video ends.</p></dd>\n	<dt><code>showinfo</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nWhether to display the video informations or not.</p></dd>\n	<dt><code>start</code></dt>\n	<dd><p>Integer; initially not set.<br />\nSpecifies the time (in seconds) when the video should start playing.</p></dd>\n	<dt><code>theme</code></dt>\n	<dd><p><code>dark</code> or <code>light</code>; initially set to <code>dark</code>;<br />\nIndicates whether the embedded player will display player controls (like a play button or volume control) within a dark or light control bar.<br />\nThis parameter has been deprecated for HTML5 players.</p></dd>\n</dl>\n\n<p class=\"alert-block information\"><span class=\"ui-icon ui-icon-info\">.</span> Some prefs/attributes may be deprecated for HTML5 players or overriden by the provider depending on several conditions. More informations on <a href=\"https://developers.google.com/youtube/player_parameters\">this Youtube webpage</a>.</p>\n\n<h3>oui_if_youtube vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension related provider, the <code>&lt;txp:oui_if_youtube&gt;…&lt;/txp:oui_if_youtube&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against YouTube related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;youtube&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://youtu.be/9FWIG_c6PfI</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;youtube&quot; play=&quot;9FWIG_c6PfI&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_youtube play=&quot;9FWIG_c6PfI&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;youtube&quot; play=&quot;https://www.youtube.com/watch?v=9FWIG_c6PfI&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_youtube play=&quot;https://www.youtube.com/watch?v=9FWIG_c6PfI&quot;&gt;\n    &lt;txp:oui_youtube /&gt;\n&lt;/txp:oui_if_youtube&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_youtube version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_youtube v1.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Added: <code>list</code> support.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_youtube,\n * a oui_player v2+ extension to easily embed\n * YouTube customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_youtube\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Youtube\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Youtube extends Provider\n    {\n        protected static $srcBase = \'//www.youtube-nocookie.com/\';\n        protected static $srcGlue = array(\'embed/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'560\',\n            \'height\'     => \'315\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autohide\'       => array(\n                \'default\' => \'2\',\n                \'valid\'   => array(\'0\', \'1\', \'2\'),\n            ),\n            \'autoplay\'       => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'cc_load_policy\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'color\'          => array(\n                \'default\' => \'red\',\n                \'valid\'   => array(\'red\', \'white\'),\n            ),\n            \'controls\'       => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\', \'2\'),\n            ),\n            \'disablekb\'      => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'enablejsapi\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'end\'            => array(\n                \'default\' => \'\',\n                \'valid\'   => \'number\',\n            ),\n            \'fs\'             => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hl\'             => \'\',\n            \'iv_load_policy\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'1\', \'3\'),\n            ),\n            \'listType\'       => array(\n                \'default\' => \'\',\n                \'valid\'   => array(\'playlist\', \'search\', \'user_uploads\'),\n            ),\n            \'loop\'           => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'modestbranding\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'origin\'         => array(\n                \'default\' => \'\',\n                \'valid\'   => \'url\',\n            ),\n            \'playlist\'    => \'\',\n            \'playsinline\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'rel\'            => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'start\'          => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\',\n            ),\n            \'showinfo\'       => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'theme\'          => array(\n                \'default\' => \'dark\',\n                \'valid\'   => array(\'dark\', \'light\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'video\' => array(\n                \'scheme\' => \'#^https?://(www\\.)?(youtube\\.com/(watch\\?v=|embed/|v/)|youtu\\.be/)(([^&?/]+)?)#i\',\n                \'id\'     => \'4\',\n                \'glue\'   => \'&amp;\',\n            ),\n            \'list\'  => array(\n                \'scheme\' => \'#^https?://(www\\.)?(youtube\\.com/(watch\\?v=|embed/|v/)|youtu\\.be/)[\\S]+list=([^&?/]+)?#i\',\n                \'id\'     => \'4\',\n                \'prefix\' => \'list=\',\n            ),\n        );\n\n        protected function resetSrcGlue($media)\n        {\n            if (isset($this->mediaInfos[$media][\'pattern\'])) {\n                self::setSrcGlue(0, $this->mediaInfos[$media][\'pattern\'] === \'list\' ? \'embed?\' : \'embed/\');\n            }\n        }\n    }\n}\n', '\n\n/*\n * This file is part of oui_youtube,\n * a oui_player v2+ extension to easily embed\n * YouTube customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_youtube\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Youtube\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Youtube extends Provider\n    {\n        protected static $srcBase = \'//www.youtube-nocookie.com/\';\n        protected static $srcGlue = array(\'embed/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'560\',\n            \'height\'     => \'315\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autohide\'       => array(\n                \'default\' => \'2\',\n                \'valid\'   => array(\'0\', \'1\', \'2\'),\n            ),\n            \'autoplay\'       => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'cc_load_policy\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'color\'          => array(\n                \'default\' => \'red\',\n                \'valid\'   => array(\'red\', \'white\'),\n            ),\n            \'controls\'       => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\', \'2\'),\n            ),\n            \'disablekb\'      => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'enablejsapi\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'end\'            => array(\n                \'default\' => \'\',\n                \'valid\'   => \'number\',\n            ),\n            \'fs\'             => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hl\'             => \'\',\n            \'iv_load_policy\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'1\', \'3\'),\n            ),\n            \'listType\'       => array(\n                \'default\' => \'\',\n                \'valid\'   => array(\'playlist\', \'search\', \'user_uploads\'),\n            ),\n            \'loop\'           => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'modestbranding\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'origin\'         => array(\n                \'default\' => \'\',\n                \'valid\'   => \'url\',\n            ),\n            \'playlist\'    => \'\',\n            \'playsinline\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'rel\'            => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'start\'          => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\',\n            ),\n            \'showinfo\'       => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'theme\'          => array(\n                \'default\' => \'dark\',\n                \'valid\'   => array(\'dark\', \'light\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'video\' => array(\n                \'scheme\' => \'#^https?://(www\\.)?(youtube\\.com/(watch\\?v=|embed/|v/)|youtu\\.be/)(([^&?/]+)?)#i\',\n                \'id\'     => \'4\',\n                \'glue\'   => \'&amp;\',\n            ),\n            \'list\'  => array(\n                \'scheme\' => \'#^https?://(www\\.)?(youtube\\.com/(watch\\?v=|embed/|v/)|youtu\\.be/)[\\S]+list=([^&?/]+)?#i\',\n                \'id\'     => \'4\',\n                \'prefix\' => \'list=\',\n            ),\n        );\n\n        protected function resetSrcGlue($media)\n        {\n            if (isset($this->mediaInfos[$media][\'pattern\'])) {\n                self::setSrcGlue(0, $this->mediaInfos[$media][\'pattern\'] === \'list\' ? \'embed?\' : \'embed/\');\n            }\n        }\n    }\n}\n', '7dc18f0ff1f5f546ab44ca26e0bd3d3d', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_youtube => Youtube\noui_player_youtube => Youtube player\noui_player_youtube_prefs => Display the Youtube player preferences\noui_player_youtube_no_cookie => Use the cookie free service\noui_player_youtube_autohide =>  Player commands display while playing\noui_player_youtube_autohide_0 => Commands and progress bar always visible\noui_player_youtube_autohide_1 => Commands and progress bar on hover only\noui_player_youtube_autohide_2 => Commands on hover and progress bar always visible\noui_player_youtube_autoplay => Automatic playing\noui_player_youtube_cc_load_policy => Force subtitles display\noui_player_youtube_color => Preferred colour for UI player elements\noui_player_youtube_color_red => Red\noui_player_youtube_color_white => White\noui_player_youtube_controls => Commands display and player loading\noui_player_youtube_controls_0 => Player loaded directly without commands\noui_player_youtube_controls_1 => Player loaded directly with commands\noui_player_youtube_controls_2 => Commands displayed and player loading on playing\noui_player_youtube_disablekb => Disable keyboard commands\noui_player_youtube_enablejsapi => Enable the player JS API\noui_player_youtube_end => Stop playing at second:\noui_player_youtube_fs => Allow full screen display\noui_player_youtube_hl => Player interface language\noui_player_youtube_iv_load_policy => Video annotations display\noui_player_youtube_iv_load_policy_1 => Enable by default\noui_player_youtube_iv_load_policy_3 => Disable by default\noui_player_youtube_loop => Replay the video once finished\noui_player_youtube_listtype => List Type\noui_player_youtube_listtype_playlist => playlist\noui_player_youtube_listtype_search => search\noui_player_youtube_listtype_user_uploads => user_uploads\noui_player_youtube_modestbranding => Modest branding\noui_player_youtube_origin => Origin domain URL\noui_player_youtube_playsinline => Avoid fullscreen playback by default on iOS\noui_player_youtube_playlist => Video list (id\'s) to play then\noui_player_youtube_rel => Display related videos at the end\noui_player_youtube_showinfo => Display video informations\noui_player_youtube_start => Start playing at second:\noui_player_youtube_theme => Preferred theme for the player\noui_player_youtube_theme_dark => Dark\noui_player_youtube_theme_light => Light\noui_player_youtube_width => Player width\noui_player_youtube_height => Player height\noui_player_youtube_ratio => Player ratio by default\noui_player_youtube_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_youtube => Youtube\noui_player_youtube => Lecteur Youtube\noui_player_youtube_prefs => Afficher les préférences du lecteur Youtube\noui_player_youtube_width => Largeur du lecteur\noui_player_youtube_height => Hauteur du lecteur\noui_player_youtube_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_youtube_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_youtube_no_cookie => Utiliser la version sans cookie\noui_player_youtube_autohide => Affichage des commandes pendant la lecture\noui_player_youtube_autohide_0 => Commandes et état du chargement toujours visibles\noui_player_youtube_autohide_1 => Commandes et état du chargement au survol uniquement\noui_player_youtube_autohide_2 => Commandes au survol et état du chargement toujours visible\noui_player_youtube_autoplay => Lecture automatique\noui_player_youtube_cc_load_policy => Forcer l\'affichage des sous-titres\noui_player_youtube_color => Couleur préférée pour les éléments d\'interface du lecteur\noui_player_youtube_color_red => Rouge\noui_player_youtube_color_white => Blanc\noui_player_youtube_controls => Affichage des commandes et chargement du lecteur\noui_player_youtube_controls_0 => Chargement direct du lecteur sans commandes\noui_player_youtube_controls_1 => Chargement direct du lecteur avec commandes\noui_player_youtube_controls_2 => Affichage des commandes et chargement du lecteur à la lecture\noui_player_youtube_disablekb => Désactiver les commandes aux clavier\noui_player_youtube_enablejsapi => Activer l\'API JS du lecteur\noui_player_youtube_end => Terminer la lecture à la seconde:\noui_player_youtube_fs => Permettre l\'affichage plein écran\noui_player_youtube_hl => Langue de l\'interface du lecteur\noui_player_youtube_iv_load_policy => Affichage des annotations de la vidéo\noui_player_youtube_iv_load_policy_1 => Activer par défaut\noui_player_youtube_iv_load_policy_3 => Désactiver par défault\noui_player_youtube_loop => Lire en boucle\noui_player_youtube_listtype => Type de liste\noui_player_youtube_listtype_playlist => playlist\noui_player_youtube_listtype_search => search\noui_player_youtube_listtype_user_uploads => user_uploads\noui_player_youtube_modestbranding => Logo discret\noui_player_youtube_origin => URL du domaine diffuseur\noui_player_youtube_playsinline => Empêcher la lecture plein écran par défaut sur iOS\noui_player_youtube_playlist => Liste de vidéos (id\'s) à lire ensuite\noui_player_youtube_rel => Afficher les vidéos associées en fin de lecture\noui_player_youtube_start => Commencer la lecture à la seconde:\noui_player_youtube_showinfo => Afficher les informations sur la vidéo\noui_player_youtube_theme => Thème préféré pour le lecteur\noui_player_youtube_theme_dark => Sombre\noui_player_youtube_theme_light => Clair\n', '', 5, 4, 3),
('oui_spotify', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA1', 'Embed Spotify audio players through oui_player v2+', '<h1>oui_spotify</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_spotify\">oui_spotify</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://spotify.go.com/\">Spotify</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_spotify&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_spotify/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_spotify:<br />\n<code>$ composer require nicolasgraph/oui_spotify</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_spotify</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_spotify</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_spotify vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_spotify /&gt;</code> tag is a shortcut to embed Spotify videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;spotify&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://open.spotify.com/<strong>type</strong>/<strong>ID</strong>;</li>\n	<li>http(s)://play.spotify.com/<strong>type</strong>/<strong>ID</strong>.</li>\n</ul>\n\n<p>…where <strong>type</strong> can be <code>album</code>, <code>track</code>, <code>artist</code> or <code>playlist</code>.<br />\nWhen using the <code>oui_player</code> tag (vs <code>oui_spotify</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>300</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>380</code> (<code>80</code> is the usual compact player height value).<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_spotify vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_spotify&gt;…&lt;/txp:oui_if_spotify&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Spotify related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;spotify&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://open.spotify.com/album/4RuzGKLG99XctuBMBkFFOC</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;spotify&quot; play=&quot;album/4RuzGKLG99XctuBMBkFFOC&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_spotify play=&quot;album/4RuzGKLG99XctuBMBkFFOC&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;spotify&quot; play=&quot;https://open.spotify.com/track/4vtgqkR7eeosgF0h4QD4kQ&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_spotify play=&quot;https://open.spotify.com/track/4vtgqkR7eeosgF0h4QD4kQ&quot;&gt;\n    &lt;txp:oui_spotify /&gt;\n&lt;/txp:oui_if_spotify&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_spotify version 1.0.0-BETA1, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_spotify v1.0.0-BETA1</strong> (2018-08-15)</li>\n</ul>', '\n\n/*\n * This file is part of oui_spotify,\n * a oui_player v2+ extension to easily embed\n * Spotify customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_spotify\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Spotify\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Spotify extends Provider\n    {\n        protected static $srcBase = \'//open.spotify.com/embed/\';\n        protected static $srcGlue = array(\'\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'300\',\n            \'height\'     => \'380\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'album\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/album/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'album/\'\n            ),\n            \'track\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/track/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'track/\'\n            ),\n            \'artist\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/artist/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'artist/\'\n            ),\n            \'playlist\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/playlist/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'playlist/\'\n            ),\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_spotify,\n * a oui_player v2+ extension to easily embed\n * Spotify customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_spotify\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Spotify\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Spotify extends Provider\n    {\n        protected static $srcBase = \'//open.spotify.com/embed/\';\n        protected static $srcGlue = array(\'\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'300\',\n            \'height\'     => \'380\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'album\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/album/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'album/\'\n            ),\n            \'track\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/track/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'track/\'\n            ),\n            \'artist\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/artist/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'artist/\'\n            ),\n            \'playlist\' => array(\n                \'scheme\' => \'#^https?://(open|play)\\.spotify\\.com/playlist/(.+)$#i\',\n                \'id\'     => \'2\',\n                \'prefix\' => \'playlist/\'\n            ),\n        );\n    }\n}\n', '2fb32187a788ce17ccbd7a5e74fc2ad2', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_spotify => Spotify\noui_player_spotify => Spotify player\noui_player_spotify_prefs => Display the Spotify player preferences\noui_player_spotify_width => Player width\noui_player_spotify_height => Player height\noui_player_spotify_ratio => Player ratio by default\noui_player_spotify_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_spotify => Spotify\noui_player_spotify => Lecteur Spotify\noui_player_spotify_prefs => Afficher les préférences du lecteur Spotify\noui_player_spotify_width => Largeur du lecteur\noui_player_spotify_height => Hauteur du lecteur\noui_player_spotify_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_spotify_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_audio', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA4', 'Create HTML5 audio players through oui_player v2+', '<h1>oui_audio</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_audio\">oui_audio</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://video.com/\"><span class=\"caps\">HTML</span> video</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_audio&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_audio/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_audio:<br />\n<code>$ composer require nicolasgraph/oui_audio</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_audio</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_audio</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_audio vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_audio /&gt;</code> tag is a shortcut to embed <span class=\"caps\">HTML</span> audio videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;audio&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li>comma separated list of <strong>File ID&#8217;s</strong>;</li>\n	<li>comma separated list of <strong>filenames</strong> using one of the following extension: <code>.mp3</code>, <code>.ogg</code>, <code>.oga</code>, <code>.wav</code>, <code>aac</code> or <code>.flac</code>;</li>\n	<li>comma separated list of <strong><span class=\"caps\">URL</span>&#8217;s</strong> using one of the following extensions: <code>.mp3</code>, <code>.ogg</code>, <code>.oga</code>, <code>.wav</code>, <code>aac</code> or <code>.flac</code>.</li>\n</ul>\n\n<p>When using the <code>&lt;txp:oui_player /&gt;</code> tag (vs <code>&lt;txp:oui_audio /&gt;</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially not set;<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nEnables the automatic playing if set to <code>1</code>.</p></dd>\n	<dt><code>controlse</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nDisplays the player controls.</p></dd>\n	<dt><code>loop</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nReads the video over and over.</p></dd>\n	<dt><code>muted</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nDisables the sound if set to <code>1</code>.</p></dd>\n	<dt><code>poster</code></dt>\n	<dd><p><span class=\"caps\">URL</span>; initially not set.<br />\nSpecifies a start shot <span class=\"caps\">URL</span>.</p></dd>\n	<dt><code>preload</code></dt>\n	<dd><p><code>none</code>, <code>metadata</code> or <code>auto</code>; initially set to <code>auto</code>.<br />\nSpecifies the way the browser load the media.</p></dd>\n	<dt><code>volume</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially not set.<br />\nSet the sound volume.</p></dd>\n</dl>\n\n<h3>oui_if_audio vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension related provider, the <code>&lt;txp:oui_if_audio&gt;…&lt;/txp:oui_if_audio&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against <span class=\"caps\">HTML</span> audio related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;audio&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://www.w3schools.com/html/horse.ogg, https://www.w3schools.com/html/horse.mp3</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;audio&quot; play=&quot;3&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_audio play=&quot;3&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;audio&quot; play=&quot;horse.ogg&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_audio play=&quot;horse.ogg&quot;&gt;\n    &lt;txp:oui_audio /&gt;\n&lt;/txp:oui_if_audio&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_audio version 1.0.0-BETA4, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_audio v1.0.0-BETA4</strong> (2018-08-15)\n	<ul>\n		<li>Added: <span class=\"caps\">MIME</span> types managements;</li>\n		<li>added: player width management;</li>\n		<li>added: <code>.aac</code> and <code>.flac</code> support;</li>\n		<li>changed: does not try to play non existing files (triggers errors).</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_player_abcnews,\n * a oui_player v2+ extension to easily create\n * HTML5 customizable video and audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_audio\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Video\n *\n * Manages HTML5 <video> player.\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Audio extends Provider\n    {\n        protected static $srcBase = \'\';\n        protected static $srcGlue = \' \';\n        protected static $iniDims = array(\n            \'width\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'controls\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'loop\'     => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'muted\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'preload\'  => array(\n                \'default\' => \'auto\',\n                \'valid\'   => array(\'none\', \'metadata\', \'auto\'),\n            ),\n            \'volume\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => \'number\',\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'filename\' => array(\n                \'scheme\' => \'#^((?!(http|https)://(www\\.)?)\\S+\\.(mp3|ogg|oga|wav|aac|flac))$#i\',\n                \'id\'     => \'1\',\n            ),\n            \'url\' => array(\n                \'scheme\' => \'#^((https?:\\/\\/(www.)?)\\S+\\.(mp3|ogg|oga|wav|aac|flac))$#i\',\n                \'id\'     => \'1\',\n            ),\n        );\n        protected static $mediaMimeTypes = array(\n            \'mp3\'  => \'audio/mp3\',\n            \'ogg\'  => \'video/ogg\',\n            \'oga\'  => \'video/ogg\',\n            \'wav\'  => \'video/wave\',\n            \'aac\'  => \'audio/aac\',\n            \'flac\' => \'audio/flac\',\n        );\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public static function getMediaMimeType($extension)\n        {\n            return static::$mediaMimeTypes[$extension];\n        }\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public function getParams()\n        {\n            $params = array();\n\n            foreach (self::getIniParams() as $param => $infos) {\n                $pref = $this->getPref($param);\n                $default = $infos[\'default\'];\n                $value = isset($this->config[$param]) ? $this->config[$param] : \'\';\n\n                // Add attributes values in use or modified prefs values as player parameters.\n                if ($value === \'\' && $pref !== $default) {\n                    if ($infos[\'valid\'] === array(\'0\', \'1\')) {\n                        $params[$param] = true;\n                    } else {\n                        $params[$param] = $pref;\n                    }\n                } elseif ($value !== \'\') {\n                    $validArray = is_array($infos[\'valid\']) ? $infos[\'valid\'] : \'\';\n\n                    if ($validArray && !in_array($value, $validArray)) {\n                        trigger_error(\'Unknown attribute value for \"\' . $param . \'\". Valid values are: \"\' . implode(\'\", \"\', $validArray) . \'\".\');\n                    } if ($infos[\'valid\'] === array(\'0\', \'1\')) {\n                        $params[$param] = true;\n                    } else {\n                        $params[$param] =  $value;\n                    }\n                }\n            }\n\n            return $params;\n        }\n\n        /**\n         * Get the player code\n         */\n\n        protected function resetSrcGlue($media)\n        {\n            extract($this->mediaInfos[$media]);\n            isset($pattern) ?: $pattern = \'id\';\n\n            if ($pattern === \'url\') {\n                $sources[] = $media;\n            } else {\n                if ($pattern === \'id\') {\n                    $where = \'id = \'.intval($media).\' and created <= \'.now(\'created\');\n                } elseif ($pattern === \'filename\') {\n                    $where = \"filename = \'\".doSlash($media).\"\' and created <= \".now(\'created\');\n                }\n\n                $file = fileDownloadFetchInfo($where);\n\n                if ($file) {\n                    $this->mediaInfos[$media] = array(\n                        \'id\'  => $file[\'id\'],\n                        \'uri\' => filedownloadurl($file[\'id\'], $file[\'filename\'])\n                    );\n                } else {\n                    trigger_error(\'Unknown file to play:\"\' . $media . \'\"\');\n                }\n            }\n        }\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public function getHTML()\n        {\n            if ($sources = $this->getMediaInfos()) {\n                $src = array_shift($sources)[\'uri\'];\n                $sourcesStr = array();\n\n                foreach ($sources as $source) {\n                    $sourcesStr[] = \'<source src=\"\' . $source[\'uri\'] . \'\" type=\"\' . self::getMediaMimeType(pathinfo($source[\'uri\'], PATHINFO_EXTENSION)). \'\">\';\n                }\n\n                $paramsStr = \'\';\n\n                foreach ($this->getParams() as $param => $value) {\n                    $paramsStr .= self::getSrcGlue() . ($value === true ? $param : $param . \'=\' . $value);\n                }\n\n                $dims = $this->getDims();\n\n                extract($dims);\n\n                $style = \'\';\n\n                if (!empty($width)) {\n                    is_string($width) ?: $width .= \'px\';\n                    $style = \' style=\"width:\' . $width . \'\"\';\n                }\n\n                $player = sprintf(\n                    \'<audio src=\"%s\"%s%s>%s%s</audio>\',\n                    $src,\n                    $style,\n                    $paramsStr,\n                    ($sourcesStr ? n . implode(n, $sourcesStr) : \'\'),\n                    n . gtxt(\n                        \'oui_player_html_player_not_supported\',\n                        array(\n                            \'{player}\' => \'<audio>\',\n                            \'{src}\'    => $src,\n                            \'{file}\'   => basename($src),\n                        )\n                    ) . n\n                );\n\n                list($wraptag, $class) = $this->getWrap();\n                list($label, $labeltag) = $this->getLabel();\n\n                $wraptag ? $player = n . $player . n : \'\';\n\n                return doLabel($label, $labeltag) . n . doTag($player, $wraptag, $class);\n            }\n        }\n    }\n}\n', '\n\n/*\n * This file is part of oui_player_abcnews,\n * a oui_player v2+ extension to easily create\n * HTML5 customizable video and audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_audio\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Video\n *\n * Manages HTML5 <video> player.\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Audio extends Provider\n    {\n        protected static $srcBase = \'\';\n        protected static $srcGlue = \' \';\n        protected static $iniDims = array(\n            \'width\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'controls\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'loop\'     => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'muted\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'preload\'  => array(\n                \'default\' => \'auto\',\n                \'valid\'   => array(\'none\', \'metadata\', \'auto\'),\n            ),\n            \'volume\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => \'number\',\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'filename\' => array(\n                \'scheme\' => \'#^((?!(http|https)://(www\\.)?)\\S+\\.(mp3|ogg|oga|wav|aac|flac))$#i\',\n                \'id\'     => \'1\',\n            ),\n            \'url\' => array(\n                \'scheme\' => \'#^((https?:\\/\\/(www.)?)\\S+\\.(mp3|ogg|oga|wav|aac|flac))$#i\',\n                \'id\'     => \'1\',\n            ),\n        );\n        protected static $mediaMimeTypes = array(\n            \'mp3\'  => \'audio/mp3\',\n            \'ogg\'  => \'video/ogg\',\n            \'oga\'  => \'video/ogg\',\n            \'wav\'  => \'video/wave\',\n            \'aac\'  => \'audio/aac\',\n            \'flac\' => \'audio/flac\',\n        );\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public static function getMediaMimeType($extension)\n        {\n            return static::$mediaMimeTypes[$extension];\n        }\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public function getParams()\n        {\n            $params = array();\n\n            foreach (self::getIniParams() as $param => $infos) {\n                $pref = $this->getPref($param);\n                $default = $infos[\'default\'];\n                $value = isset($this->config[$param]) ? $this->config[$param] : \'\';\n\n                // Add attributes values in use or modified prefs values as player parameters.\n                if ($value === \'\' && $pref !== $default) {\n                    if ($infos[\'valid\'] === array(\'0\', \'1\')) {\n                        $params[$param] = true;\n                    } else {\n                        $params[$param] = $pref;\n                    }\n                } elseif ($value !== \'\') {\n                    $validArray = is_array($infos[\'valid\']) ? $infos[\'valid\'] : \'\';\n\n                    if ($validArray && !in_array($value, $validArray)) {\n                        trigger_error(\'Unknown attribute value for \"\' . $param . \'\". Valid values are: \"\' . implode(\'\", \"\', $validArray) . \'\".\');\n                    } if ($infos[\'valid\'] === array(\'0\', \'1\')) {\n                        $params[$param] = true;\n                    } else {\n                        $params[$param] =  $value;\n                    }\n                }\n            }\n\n            return $params;\n        }\n\n        /**\n         * Get the player code\n         */\n\n        protected function resetSrcGlue($media)\n        {\n            extract($this->mediaInfos[$media]);\n            isset($pattern) ?: $pattern = \'id\';\n\n            if ($pattern === \'url\') {\n                $sources[] = $media;\n            } else {\n                if ($pattern === \'id\') {\n                    $where = \'id = \'.intval($media).\' and created <= \'.now(\'created\');\n                } elseif ($pattern === \'filename\') {\n                    $where = \"filename = \'\".doSlash($media).\"\' and created <= \".now(\'created\');\n                }\n\n                $file = fileDownloadFetchInfo($where);\n\n                if ($file) {\n                    $this->mediaInfos[$media] = array(\n                        \'id\'  => $file[\'id\'],\n                        \'uri\' => filedownloadurl($file[\'id\'], $file[\'filename\'])\n                    );\n                } else {\n                    trigger_error(\'Unknown file to play:\"\' . $media . \'\"\');\n                }\n            }\n        }\n\n        /**\n         * {@inheritdoc}\n         */\n\n        public function getHTML()\n        {\n            if ($sources = $this->getMediaInfos()) {\n                $src = array_shift($sources)[\'uri\'];\n                $sourcesStr = array();\n\n                foreach ($sources as $source) {\n                    $sourcesStr[] = \'<source src=\"\' . $source[\'uri\'] . \'\" type=\"\' . self::getMediaMimeType(pathinfo($source[\'uri\'], PATHINFO_EXTENSION)). \'\">\';\n                }\n\n                $paramsStr = \'\';\n\n                foreach ($this->getParams() as $param => $value) {\n                    $paramsStr .= self::getSrcGlue() . ($value === true ? $param : $param . \'=\' . $value);\n                }\n\n                $dims = $this->getDims();\n\n                extract($dims);\n\n                $style = \'\';\n\n                if (!empty($width)) {\n                    is_string($width) ?: $width .= \'px\';\n                    $style = \' style=\"width:\' . $width . \'\"\';\n                }\n\n                $player = sprintf(\n                    \'<audio src=\"%s\"%s%s>%s%s</audio>\',\n                    $src,\n                    $style,\n                    $paramsStr,\n                    ($sourcesStr ? n . implode(n, $sourcesStr) : \'\'),\n                    n . gtxt(\n                        \'oui_player_html_player_not_supported\',\n                        array(\n                            \'{player}\' => \'<audio>\',\n                            \'{src}\'    => $src,\n                            \'{file}\'   => basename($src),\n                        )\n                    ) . n\n                );\n\n                list($wraptag, $class) = $this->getWrap();\n                list($label, $labeltag) = $this->getLabel();\n\n                $wraptag ? $player = n . $player . n : \'\';\n\n                return doLabel($label, $labeltag) . n . doTag($player, $wraptag, $class);\n            }\n        }\n    }\n}\n', 'b1684505075678a0bdafcdce5f90917a', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_audio => HTML audio\noui_player_audio => HTML audio player\noui_player_audio_prefs => Display the HTML audio player prefs\noui_player_audio_width => Player width\noui_player_audio_responsive => Render responsive players from the provided dimensions\noui_player_audio_autoplay => automatic playing\noui_player_audio_controls => Display controls\noui_player_audio_loop => Replay once finished\noui_player_audio_muted => Disable sound\noui_player_audio_preload => Loading type\noui_player_audio_preload_auto => auto\noui_player_audio_preload_metadata => metadata\noui_player_audio_preload_none => none\noui_player_audio_volume => Volume (0 to 1)\noui_player_audio_unavailable => Sorry, the HTML audio player is not supported by your browser.\n#@public\noui_player_html_player_not_supported => Unfortunatly, your browser is not able to read <a href=\"{src}\">{file}</a>. The HTML5 {player} tag is not supported.\n\n#@language fr-fr\n#@admin\noui_player_provider_audio => Audio\noui_player_audio => Audio player\noui_player_audio_prefs => Display the Audio player prefs\noui_player_audio_width => Largeur du lecteur\noui_player_audio_responsive => Rendre les lecteurs adaptatifs (= width 100%)\noui_player_audio_autoplay => Audio\noui_player_audio_controls => Afficher les commandes\noui_player_audio_loop => Lire en boucle\noui_player_audio_muted => Désactiver le son\noui_player_audio_preload => Type de chargement\noui_player_audio_preload_auto => auto\noui_player_audio_preload_metadata => metadata\noui_player_audio_preload_none => none\noui_player_audio_volume => Volume (0 à 1)\noui_player_audio_unavailable => Désolé, le lecteur audio HTML n\'est pas supporté par votre navigateur.\n#@public\noui_player_html_player_not_supported => Malheureusement, votre navigateur n\'est pas capable de lire <a href=\"{src}\">{file}</a>. La balise HTML5 {player} n\'est prise en charge.\n', '', 5, 4, 3),
('oui_abcnews', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed ABC News video players through oui_player v2+', '<h1>oui_abcnews</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_abcnews\">oui_abcnews</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://abcnews.go.com/\"><span class=\"caps\">ABC</span> News</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_abcnews&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_abcnews/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_abcnews:<br />\n<code>$ composer require nicolasgraph/oui_abcnews</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_abcnews</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_abcnews</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_abcnews vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_abcnews /&gt;</code> tag is a shortcut to embed <span class=\"caps\">ABC</span> News videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;abcnews&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://abcnews.go.com/…/video/…<strong>ID</strong>;</li>\n	<li>http(s)://abcnews.go.com/video/embed?id=<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_abcnews</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>640</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>360</code>.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_abcnews vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_abcnews&gt;…&lt;/txp:oui_if_abcnews&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against <span class=\"caps\">ABC</span> News related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;abcnews&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://abcnews.go.com/video/embed?id=55148128</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;abcnews&quot; play=&quot;55148128&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_abcnews play=&quot;55148128&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;abcnews&quot; play=&quot;https://abcnews.go.com/Technology/video/nintendo-release-nes-classic-edition-console-55148128&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_abcnews play=&quot;https://abcnews.go.com/Technology/video/nintendo-release-nes-classic-edition-console-55148128&quot;&gt;\n    &lt;txp:oui_abcnews /&gt;\n&lt;/txp:oui_if_abcnews&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_abcnews version v1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><a href=\"https://github.com/NicolasGraph/oui_abcnews/releases/tag/1.0.0-beta\">oui_abcnews v1.0.0-BETA3</a> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player/releases/tag/1.3.1\">oui_player v1.3.1</a> (2017-07-13)</li>\n</ul>', '\n\n/*\n * This file is part of oui_abcnews,\n * a oui_player v2+ extension to easily embed\n * ABC News customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_abcnews\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Abcnews\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Abcnews extends Provider\n    {\n        protected static $srcBase = \'//abcnews.go.com/\';\n        protected static $srcGlue = array(\'video/embed?id=\', \'&amp;\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'360\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(abcnews\\.go\\.com/([a-zA-Z]+/)?video)/[^0-9]+([0-9]+)$#i\',\n            \'id\'     => \'3\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_abcnews,\n * a oui_player v2+ extension to easily embed\n * ABC News customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_abcnews\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Abcnews\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Abcnews extends Provider\n    {\n        protected static $srcBase = \'//abcnews.go.com/\';\n        protected static $srcGlue = array(\'video/embed?id=\', \'&amp;\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'360\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(abcnews\\.go\\.com/([a-zA-Z]+/)?video)/[^0-9]+([0-9]+)$#i\',\n            \'id\'     => \'3\',\n        );\n    }\n}\n', '3e92414e9b570561f71fbae6c3598e3d', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_abcnews => ABC News\noui_player_abcnews => ABC News player\noui_player_abcnews_prefs => Display the ABC News player preferences\noui_player_abcnews_width => Player width\noui_player_abcnews_height => Player height\noui_player_abcnews_ratio => Player ratio by default\noui_player_abcnews_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_abcnews => ABC News\noui_player_abcnews => Lecteur ABC News\noui_player_abcnews_prefs => Afficher les préférences du lecteur ABC News\noui_player_abcnews_width => Largeur du lecteur\noui_player_abcnews_height => Hauteur du lecteur\noui_player_abcnews_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_abcnews_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_soundcloud', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed SoundCloud audio players through oui_player v2+', '<h1>oui_soundcloud</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_soundcloud\">oui_soundcloud</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://soundcloud.go.com/\">Soundcloud</a> provided player in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_soundcloud&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_soundcloud/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_soundcloud:<br />\n<code>$ composer require nicolasgraph/oui_soundcloud</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_soundcloud</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_soundcloud</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_soundcloud vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any player from any extension-related provider, the <code>&lt;txp:oui_soundcloud /&gt;</code> tag is a shortcut to embed Soundcloud player only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;soundcloud&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li>http(s)://(api.)soundcloud.com/….</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_soundcloud</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>100%</code>.<br />\nThe width of the player in pixels (OK, pourcentage works also…).<br />\n<strong>Default:</strong> related plugin pref</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>166</code> (alt height is <code>450</code>, usually when <code>visual=&quot;true&quot;</code>).<br />\nThe height of the player in pixels</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>Some prefs/attributes can conditionate the behavior of others (e.g. <code>visual=&quot;true&quot;</code> desactivates <code>color</code>).<br />\nFrom Txp 4.7, paramaters accepting <code>true</code> and <code>false</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>auto_play</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nWhether to play after loading or not.</p></dd>\n	<dt><code>buying</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the <em>Buy</em> button or not.</p></dd>\n	<dt><code>color</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>#ff8800</code>.<br />\nChanges the colour of the user interface elements.</p></dd>\n	<dt><code>download</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the <em>Download</em> button or not.</p></dd>\n	<dt><code>hide_related</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nWhether to display related tracks or not.</p></dd>\n	<dt><code>sharing</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to enable the sharing features or not.</p></dd>\n	<dt><code>show_artwork</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>;<br />\nWhether to display the artwork or not.</p></dd>\n	<dt><code>show_comments</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display comments or not.</p></dd>\n	<dt><code>show_reposts</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display reposts or not.</p></dd>\n	<dt><code>show_playcount</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the playcount or not.</p></dd>\n	<dt><code>show_user</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the uploader or not.</p></dd>\n	<dt><code>single_active</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to stop playing while another player is on or not.</p></dd>\n	<dt><code>start_track</code></dt>\n	<dd><p>Integer; initially set to <code>0</code>;<br />\nSpecifies the time (in seconds) when the track should start playing.</p></dd>\n	<dt><code>theme_color</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>#ff3300</code>.<br />\nChanges the color of the player theme.</p></dd>\n	<dt><code>visual</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nWhether to display the artwork as a background or not.</p></dd>\n</dl>\n\n<h5>Sets</h5>\n\n<ul>\n	<li>Album:\n	<ul>\n		<li>big:\n		<ul>\n			<li><code>height=&quot;450&quot;</code>;</li>\n			<li><code>visual=&quot;true&quot;</code>.</li>\n		</ul></li>\n		<li>medium:\n		<ul>\n			<li><code>height=&quot;300&quot;</code>.</li>\n		</ul></li>\n		</ul></li>\n	<li>track :\n	<ul>\n		<li>big:\n		<ul>\n			<li><code>height=&quot;300&quot;</code>;</li>\n			<li><code>visual=&quot;true&quot;</code>.</li>\n		</ul></li>\n		<li>medium:\n		<ul>\n			<li>initial values.</li>\n		</ul></li>\n		</ul></li>\n		</ul>\n\n<h3>oui_if_soundcloud vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_soundcloud&gt;…&lt;/txp:oui_if_soundcloud&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Soundcloud related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;soundcloud&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://soundcloud.com/uiceheidd/lucid-dreams-forget-me</code>.</p>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;soundcloud&quot; play=&quot;https://soundcloud.com/uiceheidd/lucid-dreams-forget-me&quot;&gt;\n    &lt;txp:oui_player height=&quot;300&quot; /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_soundcloud play=&quot;https://soundcloud.com/uiceheidd/lucid-dreams-forget-me&quot;&gt;\n    &lt;txp:oui_soundcloud height=&quot;300&quot; /&gt;\n&lt;/txp:oui_if_soundcloud&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_soundcloud version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_soundcloud v1.0.0-BETA3</strong> (2018-08-15)</li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_soundcloud,\n * a oui_player v2+ extension to easily embed\n * Soundcloud customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_soundcloud\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Soundcloud\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Soundcloud extends Provider\n    {\n        protected static $srcBase = \'//w.soundcloud.com/\';\n        protected static $srcGlue = array(\'player/?url=\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'100%\',\n            \'height\'     => \'166\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'auto_play\'      => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'buying\'         => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'color\'          => array(\n                \'default\' => \'#ff8800\',\n                \'valid\'   => \'color\',\n            ),\n            \'download\'       => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'hide_related\'   => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'sharing\'        => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_artwork\'   => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_comments\'  => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_playcount\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_reposts\'   => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_user\'      => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'single_active\'  => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'start_track\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\',\n            ),\n            \'theme_color\'    => array(\n                \'default\' => \'#ff3300\',\n                \'valid\'   => \'color\',\n            ),\n            \'visual\'         => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#(https?://(api\\.)?soundcloud\\.com/[\\S]+)#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_soundcloud,\n * a oui_player v2+ extension to easily embed\n * Soundcloud customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_soundcloud\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Soundcloud\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Soundcloud extends Provider\n    {\n        protected static $srcBase = \'//w.soundcloud.com/\';\n        protected static $srcGlue = array(\'player/?url=\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'100%\',\n            \'height\'     => \'166\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'auto_play\'      => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'buying\'         => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'color\'          => array(\n                \'default\' => \'#ff8800\',\n                \'valid\'   => \'color\',\n            ),\n            \'download\'       => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'hide_related\'   => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'sharing\'        => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_artwork\'   => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_comments\'  => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_playcount\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_reposts\'   => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'show_user\'      => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'single_active\'  => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'start_track\'    => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\',\n            ),\n            \'theme_color\'    => array(\n                \'default\' => \'#ff3300\',\n                \'valid\'   => \'color\',\n            ),\n            \'visual\'         => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#(https?://(api\\.)?soundcloud\\.com/[\\S]+)#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '82c4abaa89059bdf1538cb13e9aaab60', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_soundcloud => Soundcloud\noui_player_soundcloud => Soundcloud player\noui_player_soundcloud_prefs => Display the Soundcloud player preferences\noui_player_soundcloud_color => Preferred colour for UI player elements\noui_player_soundcloud_theme_color => Preferred theme color\noui_player_soundcloud_text_download_track => Text for the <em>Download</em> link\noui_player_soundcloud_buying => Enable the <em>Buy</em> button\noui_player_soundcloud_sharing => Enable sharing features\noui_player_soundcloud_download => Enable the <em>Download</em> link\noui_player_soundcloud_hide_related => Hide related tracks\noui_player_soundcloud_show_reposts => Display reposts\noui_player_soundcloud_show_playcount => Display the play count\noui_player_soundcloud_start_track => Start playing at second:\noui_player_soundcloud_single_active => Stop playing when another Soundcloud player is on\noui_player_soundcloud_show_user => Display the uploader name\noui_player_soundcloud_auto_play => Automatic playing\noui_player_soundcloud_show_artwork => Display the art work\noui_player_soundcloud_show_comments => Display comments\noui_player_soundcloud_visual => Display the visual as a player background\noui_player_soundcloud_width => Player width\noui_player_soundcloud_height => Player height\noui_player_soundcloud_ratio => Player ratio by default\noui_player_soundcloud_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_soundcloud => Soundcloud\noui_player_soundcloud => Lecteur Soundcloud\noui_player_soundcloud_prefs => Afficher les préférences du lecteur Soundcloud\noui_player_soundcloud_width => Largeur du lecteur\noui_player_soundcloud_height => Hauteur du lecteur\noui_player_soundcloud_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_soundcloud_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_soundcloud_color => Couleur préférée pour les éléments d\'interface du lecteur\noui_player_soundcloud_theme_color => Couleur du thème\noui_player_soundcloud_buying => Afficher le lien <em>Acheter</em>\noui_player_soundcloud_hide_related => Désactiver les titres liés\noui_player_soundcloud_show_reposts => Afficher les partages\noui_player_soundcloud_sharing => Activer les fonctionnalités de partage\noui_player_soundcloud_download => Afficher le bouton <em>Télécharger</em>\noui_player_soundcloud_show_bpm => Afficher les BPM\noui_player_soundcloud_show_playcount => Afficher le compte des lectures\noui_player_soundcloud_start_track => Commencer la lecteur à la seconde:\noui_player_soundcloud_single_active => Stopper à l\'activation d\'un autre lecteur Soundcloud\noui_player_soundcloud_show_user => Afficher le nom d\'utilisateur\noui_player_soundcloud_auto_play => Lecture automatique\noui_player_soundcloud_show_artwork => Afficher la pochette\noui_player_soundcloud_show_comments => Afficher les commentaires\noui_player_soundcloud_visual => Afficher la pochette en arrière-plan\n', '', 5, 4, 3),
('oui_giphy', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Giphy players through oui_player v2+', '<h1>oui_giphy</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_giphy\">oui_giphy</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://giphy.go.com/\">Giphy</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_giphy&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_giphy/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_giphy:<br />\n<code>$ composer require nicolasgraph/oui_giphy</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_giphy</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_giphy</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_giphy vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_giphy /&gt;</code> tag is a shortcut to embed Giphy videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;giphy&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://giphy.com/embed/<strong>ID</strong></li>\n	<li>http(s)://giphy.com/gifs/…-<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_giphy</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>480</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>480</code>.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_giphy vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_giphy&gt;…&lt;/txp:oui_if_giphy&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Giphy related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;giphy&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://giphy.com/gifs/freak-out-oh-wow-nik-dudukovic-xT0Gqp6MtboDS5CTRK</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;giphy&quot; play=&quot;xT0Gqp6MtboDS5CTRK&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_giphy play=&quot;xT0Gqp6MtboDS5CTRK&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;giphy&quot; play=&quot;https://giphy.com/embed/xT0Gqp6MtboDS5CTRK&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_giphy play=&quot;https://giphy.com/embed/xT0Gqp6MtboDS5CTRK&quot;&gt;\n    &lt;txp:oui_giphy /&gt;\n&lt;/txp:oui_if_giphy&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_giphy version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_giphy v1.0.0-BETA3</strong> (2018-08-15)</li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_giphy,\n * a oui_player v2+ extension to easily embed\n * Giphy customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_giphy\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Giphy extends Provider\n    {\n        protected static $srcBase = \'//giphy.com/\';\n        protected static $srcGlue = array(\'embed/\', \'\', \'\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'480\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://giphy\\.com/(embed/|gifs/[\\S]+[-])([0-9a-zA-Z]+)$#i\',\n            \'id\'     => \'2\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_giphy,\n * a oui_player v2+ extension to easily embed\n * Giphy customizable players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_giphy\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Giphy extends Provider\n    {\n        protected static $srcBase = \'//giphy.com/\';\n        protected static $srcGlue = array(\'embed/\', \'\', \'\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'480\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://giphy\\.com/(embed/|gifs/[\\S]+[-])([0-9a-zA-Z]+)$#i\',\n            \'id\'     => \'2\',\n        );\n    }\n}\n', '2bb0def794eaffd955a8ca42dae83b50', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_giphy => Giphy\noui_player_giphy => Giphy player\noui_player_giphy_prefs => Display the Giphy player preferences\noui_player_giphy_width => Player width\noui_player_giphy_height => Player height\noui_player_giphy_ratio => Player ratio by default\noui_player_giphy_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_giphy => Giphy\noui_player_giphy => Lecteur Giphy\noui_player_giphy_prefs => Afficher les préférences du lecteur giphy\noui_player_giphy_width => Largeur du lecteur\noui_player_giphy_height => Hauteur du lecteur\noui_player_giphy_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_giphy_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_vimeo', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Vimeo video players through oui_player v2+', '<h1>oui_vimeo</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_vimeo\">oui_vimeo</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://vimeo.go.com/\">Vimeo</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_vimeo&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_vimeo/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_vimeo:<br />\n<code>$ composer require nicolasgraph/oui_vimeo</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_vimeo</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_vimeo</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_vimeo vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_vimeo /&gt;</code> tag is a shortcut to embed Vimeo videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;vimeo&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://vimeo.com/<strong>ID</strong>;</li>\n	<li>http(s)://player.vimeo.com/video/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_vimeo</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>640</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>360</code>.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>api</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nEnables or disables <a href=\"https://github.com/vimeo/player.js\">the javascript player api</a>.</p></dd>\n	<dt><code>autopause</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nEnables or disables pausing this video when another video is played.</p></dd>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nPlay the video automatically on load. Note that this won’t work on some devices.</p></dd>\n	<dt><code>byline</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nShow the user’s byline on the video.</p></dd>\n	<dt><code>color</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>#00adef</code>;<br />\nSpecify the color of the video controls.</p></dd>\n	<dt><code>loop</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nPlay the video again when it reaches the end.</p></dd>\n	<dt><code>portrait</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nShow the user’s portrait on the video.</p></dd>\n	<dt><code>title</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>1</code>.<br />\nShow the title on the video.</p></dd>\n</dl>\n\n<p class=\"alert-block information\"><span class=\"ui-icon ui-icon-info\">.</span> More informations on <a href=\"https://developer.vimeo.com/player/embedding\">this Vimeo webpage</a>.</p>\n\n<h3>oui_if_vimeo vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_vimeo&gt;…&lt;/txp:oui_if_vimeo&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Vimeo related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;vimeo&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://vimeo.com/59777392</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;vimeo&quot; play=&quot;59777392&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_vimeo play=&quot;59777392&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;vimeo&quot; play=&quot;https://player.vimeo.com/video/59777392&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_vimeo play=&quot;https://player.vimeo.com/video/59777392&quot;&gt;\n    &lt;txp:oui_vimeo /&gt;\n&lt;/txp:oui_if_vimeo&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_vimeo version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_vimeo v1.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_vimeo,\n * a oui_player v2+ extension to easily embed\n * Vimeo customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vimeo\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Vimeo extends Provider\n    {\n        protected static $srcBase = \'//player.vimeo.com/\';\n        protected static $srcGlue = array(\'video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'360\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'api\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'autopause\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'autoplay\'  => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'byline\'    => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'color\'     => array(\n                \'default\' => \'#00adef\',\n                \'valid\'   => \'color\',\n            ),\n            \'loop\'      => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'portrait\'  => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'title\'     => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://((player\\.vimeo\\.com/video)|(vimeo\\.com))/(\\d+)$#i\',\n            \'id\'     => \'4\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_vimeo,\n * a oui_player v2+ extension to easily embed\n * Vimeo customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vimeo\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Vimeo extends Provider\n    {\n        protected static $srcBase = \'//player.vimeo.com/\';\n        protected static $srcGlue = array(\'video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'360\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'api\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'autopause\' => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'autoplay\'  => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'byline\'    => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'color\'     => array(\n                \'default\' => \'#00adef\',\n                \'valid\'   => \'color\',\n            ),\n            \'loop\'      => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'portrait\'  => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'title\'     => array(\n                \'default\' => \'1\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://((player\\.vimeo\\.com/video)|(vimeo\\.com))/(\\d+)$#i\',\n            \'id\'     => \'4\',\n        );\n    }\n}\n', 'e2fc6b81ddaff68a6c2d236bacb65172', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_vimeo => Vimeo\noui_player_vimeo => Vimeo player\noui_player_vimeo_prefs => Display the Vimeo player preferences\noui_player_vimeo_width => Player width\noui_player_vimeo_height => Player height\noui_player_vimeo_ratio => Player ratio by default\noui_player_vimeo_responsive => Render responsive players from the provided dimensions/ratio\noui_player_vimeo_api => Enable the player JS API\noui_player_vimeo_autopause => Automatic pausing when another video is playing\noui_player_vimeo_autoplay => Automatic playing\noui_player_vimeo_badge => Display the badge\noui_player_vimeo_loop => Replay the video once finished\noui_player_vimeo_title => Display the video title\noui_player_vimeo_byline => Display the video author\noui_player_vimeo_color => Preferred colour for UI player elements\noui_player_vimeo_portrait => Display the author avatar\n\n#@language fr-fr\n#@admin\noui_player_provider_vimeo => Vimeo\noui_player_vimeo => Lecteur Vimeo\noui_player_vimeo_prefs => Afficher les préférences du lecteur Vimeo\noui_player_vimeo_width => Largeur du lecteur\noui_player_vimeo_height => Hauteur du lecteur\noui_player_vimeo_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_vimeo_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_vimeo_api => Activer l\'API JS du lecteur\noui_player_vimeo_autopause => Pause automatique\noui_player_vimeo_autoplay => Lecture automatique\noui_player_vimeo_badge => Afficher le badge\noui_player_vimeo_loop => Lire en boucle\noui_player_vimeo_title => Afficher le titre de la vidéo\noui_player_vimeo_byline => Afficher l\'auteur\noui_player_vimeo_color => Couleur préférée pour les éléments d\'interface du lecteur\noui_player_vimeo_portrait => Afficher l\'avatar de l\'auteur\n', '', 5, 4, 3),
('oui_mixcloud', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Mixcloud audio players through oui_player v2+', '<h1>oui_mixcloud</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_mixcloud\">oui_mixcloud</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://mixcloud.go.com/\">Mixcloud</a> provided player in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_mixcloud&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_mixcloud/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_mixcloud:<br />\n<code>$ composer require nicolasgraph/oui_mixcloud</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_mixcloud</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_mixcloud</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_mixcloud vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any player from any extension-related provider, the <code>&lt;txp:oui_mixcloud /&gt;</code> tag is a shortcut to embed Mixcloud player only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;mixcloud&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li>http(s)://(www.)mixcloud.com/….</li>\n</ul>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>100%</code>;<br />\nThe width of the player in pixels (OK, pourcentage works also…).</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>400</code> (alt native heights are <code>120</code> and <code>60</code>);<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nWhether to play after loading or not.</p></dd>\n	<dt><code>light</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nEnables the light theme.</p></dd>\n	<dt><code>hide_artwork</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nHides the background image.</p></dd>\n	<dt><code>hide_cover</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nHides the cover.</p></dd>\n	<dt><code>mini</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nUses the <em>mini</em> player config.</p></dd>\n</dl>\n\n<h5>Sets</h5>\n\n<p>The sets of values below allow to render different player types.</p>\n\n<ul>\n	<li>Picture:\n	<ul>\n		<li>initial values;</li>\n	</ul></li>\n	<li>Classic:\n	<ul>\n		<li><code>hide_cover=&quot;1&quot;</code>;</li>\n		<li><code>height=&quot;120&quot;</code>.</li>\n	</ul></li>\n	<li>Mini:\n	<ul>\n		<li><code>hide_cover=&quot;1&quot;</code>;</li>\n		<li><code>height=&quot;60&quot;</code>;</li>\n		<li><code>mini=&quot;1&quot;</code>.</li>\n	</ul></li>\n	</ul>\n\n<h3>oui_if_mixcloud vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_mixcloud&gt;…&lt;/txp:oui_if_mixcloud&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Mixcloud related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;mixcloud&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://www.mixcloud.com/musicismysanctuary/mims-guest-mix-champagne-funk-balamii-radio-london/</code>.</p>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;mixcloud&quot; play=&quot;https://www.mixcloud.com/musicismysanctuary/mims-guest-mix-champagne-funk-balamii-radio-london/&quot;&gt;\n    &lt;txp:oui_player height=&quot;120&quot; /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_mixcloud play=&quot;https://www.mixcloud.com/musicismysanctuary/mims-guest-mix-champagne-funk-balamii-radio-london/&quot;&gt;\n    &lt;txp:oui_mixcloud height=&quot;120&quot; /&gt;\n&lt;/txp:oui_if_mixcloud&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_mixcloud version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_mixcloud v1.0.0-BETA3</strong> (2018-08-15)</li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_mixcloud,\n * a oui_player v2+ extension to easily embed\n * Mixcloud customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_mixcloud\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Mixcloud\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Mixcloud extends Provider\n    {\n        protected static $srcBase = \'//www.mixcloud.com/\';\n        protected static $srcGlue = array(\'widget/iframe/?feed=\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'100%\',\n            \'height\'     => \'400\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\'     => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'light\'        => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hide_artwork\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hide_cover\'   => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'mini\'         => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^(https?://(www\\.)?mixcloud.com/[\\S]+)$#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_mixcloud,\n * a oui_player v2+ extension to easily embed\n * Mixcloud customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_mixcloud\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Mixcloud\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Mixcloud extends Provider\n    {\n        protected static $srcBase = \'//www.mixcloud.com/\';\n        protected static $srcGlue = array(\'widget/iframe/?feed=\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'100%\',\n            \'height\'     => \'400\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\'     => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'light\'        => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hide_artwork\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'hide_cover\'   => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'mini\'         => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^(https?://(www\\.)?mixcloud.com/[\\S]+)$#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '406c5941f46e69a3dcc0d9f4ed667ba4', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_mixcloud => Mixcloud\noui_player_mixcloud => Mixcloud player\noui_player_mixcloud_prefs => Display the Mixcloud player preferences\noui_player_mixcloud_width => Player width\noui_player_mixcloud_height => Player height\noui_player_mixcloud_ratio => Player ratio by default\noui_player_mixcloud_responsive => Render responsive players from the provided dimensions\noui_player_mixcloud_autoplay => Automatic playing\noui_player_mixcloud_light => Enables the light theme\noui_player_mixcloud_hide_artwork => Hides the artwork\noui_player_mixcloud_hide_cover => Hides the cover\noui_player_mixcloud_mini => Uses the mini player config\n\n#@language fr-fr\n#@admin\noui_player_provider_mixcloud => Mixcloud\noui_player_mixcloud => Lecteur Mixcloud\noui_player_mixcloud_prefs => Afficher les préférences du lecteur Mixcloud\noui_player_mixcloud_width => Largeur du lecteur\noui_player_mixcloud_height => Hauteur du lecteur\noui_player_mixcloud_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_mixcloud_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_mixcloud_autoplay => Lecture automatique\noui_player_mixcloud_light => Activer le thème clair\noui_player_mixcloud_hide_artwork => Cacher le visuel d\'arrière-plan\noui_player_mixcloud_hide_cover => Cacher le visuel\noui_player_mixcloud_mini => Utiliser la configuration <em>mini</em>\n', '', 5, 4, 3),
('oui_archive', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Archive media players through oui_player v2+', '<h1>oui_archive</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_archive\">oui_archive</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://archive.org/\">Archive</a> provided medias in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_archive&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_archive/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_archive:<br />\n<code>$ composer require nicolasgraph/oui_archive</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre class=\"language-markup xml\"><code>$ composer update nicolasgraph/oui_archive</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre class=\"language-markup xml\"><code>$ composer remove nicolasgraph/oui_archive</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_archive vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any media from any extension-related provider, the <code>&lt;txp:oui_archive /&gt;</code> tag is a shortcut to embed Archive medias only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;archive&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s):(www.)//archive.org/details/<strong>ID</strong>;</li>\n	<li>http(s):(www.)//archive.org/embed/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_archive</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>640</code> (use <code>500</code> for a music player).<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer, initially set to <code>480</code> (use <code>30</code> for a music player).<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nSpecifies whether the initial video will automatically start to play when the player loads.</p></dd>\n	<dt><code>playlist</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nSpecifies whether to display the next titles if the item to play is a playlist.</p></dd>\n	<dt><code>poster</code></dt>\n	<dd><p>Image <span class=\"caps\">URL</span>; initially not set.<br />\nSpecifies the <span class=\"caps\">URL</span> of an image to display as the starting shot.</p></dd>\n</dl>\n\n<h3>oui_if_archive vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_archive&gt;…&lt;/txp:oui_if_archive&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Archive related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;archive&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup xml\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://archive.org/details/CC_1916_07_10_TheVagabond</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup xml\"><code>&lt;txp:oui_player provider=&quot;archive&quot; play=&quot;CC_1916_07_10_TheVagabond&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup xml\"><code>&lt;txp:oui_archive play=&quot;CC_1916_07_10_TheVagabond&quot; /&gt;</code></pre>\n\n<h3>Conditonally embed a player using attributes</h3>\n\n<pre class=\"language-markup xml\"><code>&lt;txp:oui_if_player provider=&quot;archive&quot; play=&quot;https://archive.org/embed/CC_1916_07_10_TheVagabond&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup xml\"><code>&lt;txp:oui_if_archive play=&quot;https://archive.org/embed/CC_1916_07_10_TheVagabond&quot;&gt;\n    &lt;txp:oui_archive /&gt;\n&lt;/txp:oui_if_archive&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_archive version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_archive v1.0.0-BETA3</strong> (2018-08-15)</li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_archive,\n * a oui_player v2+ extension to easily embed\n * Archive.org customizable media players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_archive\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Archive\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Archive extends Provider\n    {\n        protected static $mediaType = \'media\';\n        protected static $srcBase = \'//archive.org/\';\n        protected static $srcGlue = array(\'embed/\', \'&amp;\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'480\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'playlist\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'poster\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => \'url\',\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?archive\\.org/(details|embed)/([^&?/]+)$#i\',\n            \'id\'     => \'3\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_archive,\n * a oui_player v2+ extension to easily embed\n * Archive.org customizable media players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_archive\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Archive\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Archive extends Provider\n    {\n        protected static $mediaType = \'media\';\n        protected static $srcBase = \'//archive.org/\';\n        protected static $srcGlue = array(\'embed/\', \'&amp;\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'640\',\n            \'height\'     => \'480\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'autoplay\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'playlist\' => array(\n                \'default\' => \'0\',\n                \'valid\'   => array(\'0\', \'1\'),\n            ),\n            \'poster\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => \'url\',\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?archive\\.org/(details|embed)/([^&?/]+)$#i\',\n            \'id\'     => \'3\',\n        );\n    }\n}\n', '02467cc00b1519ce8e7283e3a7ca8ab7', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_archive => Archive\noui_player_archive => Archive player\noui_player_archive_prefs => Display the Archive player preferences\noui_player_archive_width => Player width\noui_player_archive_height => Player height\noui_player_archive_ratio => Player ratio by default\noui_player_archive_responsive => Render responsive players from the provided dimensions\noui_player_archive_autoplay => Automatic playing\noui_player_archive_playlist => Display the next titles for a playlist\noui_player_archive_poster => Image url of a custom starting shot\n\n#@language fr-fr\n#@admin\noui_player_provider_archive => Archive\noui_player_archive => Lecteur Archive\noui_player_archive_prefs => Afficher les préférences du lecteur Archive\noui_player_archive_width => Largeur du lecteur\noui_player_archive_height => Hauteur du lecteur\noui_player_archive_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_archive_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_archive_autoplay => Lecture automatique\noui_player_archive_playlist => Afficher la liste des titres pour une playlist\noui_player_archive_poster => URL d\'une image à affichée comme image de début\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_bandcamp', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Bandcamp audio players through oui_player v2+', '<h1>oui_bandcamp</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_bandcamp\">oui_bandcamp</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://bandcamp.com/\">Bandcamp</a> provided albums and tracks in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_bandcamp&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_bandcamp/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_bandcamp:<br />\n<code>$ composer require nicolasgraph/oui_bandcamp</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_bandcamp</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_bandcamp</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_bandcamp vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any media from any extension-related provider, the <code>&lt;txp:oui_bandcamp /&gt;</code> tag is a shortcut to embed Bandcamp albums and tracks only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;bandcamp&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li>album=<strong>ID</strong>;</li>\n	<li>track=<strong>ID</strong>;</li>\n	<li>album=<strong>ID</strong>/track=<strong>ID</strong>;</li>\n	<li>http(s)://bandcamp.com/(EmbeddedPlayer/)album=<strong>ID</strong>;</li>\n	<li>http(s)://bandcamp.com/(EmbeddedPlayer/)track=<strong>ID</strong>;</li>\n	<li>http(s)://bandcamp.com/(EmbeddedPlayer/)album=<strong>ID</strong>/(…/)track=<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_bandcamp</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p><code>Integer</code>; initially set to <code>350</code>.<br />\nThe width of the player.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p><code>Integer</code>; initially set to <code>470</code>.<br />\nThe height of the player.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>true</code> and <code>false</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>artwork</code></dt>\n	<dd><p><code>big</code> or <code>small</code>; initially set to <code>big</code>.<br />\nDefines the artwork size.</p></dd>\n	<dt><code>bgcol</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>#ffffff</code>.<br />\nBackground color.</p></dd>\n	<dt><code>linkcol</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>#0687f5</code>.<br />\nLinks colors.</p></dd>\n	<dt><code>tracklist</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>;<br />\nDisplays the tracklist.</p></dd>\n	<dt><code>size</code></dt>\n	<dd><p><code>big</code> or <code>small</code>; initially not set.<br />\nDefines the player size type; recommended width and height values are:<br />\n* <code>big</code>: <code>350px</code> × <code>470px</code> (without the tracklist)<br />\n* <code>small</code>: <code>100%</code> × <code>120px</code> (or <code>42px</code> for a slim player)</p></dd>\n</dl>\n\n<h5>Sets</h5>\n\n<p>The sets of values below allow to render different player types.</p>\n\n<ul>\n	<li>Big:\n	<ul>\n		<li><code>width=&quot;470&quot;</code>;</li>\n	</ul></li>\n	<li>medium:\n	<ul>\n		<li><code>artwork=&quot;small&quot;</code>;</li>\n		<li><code>width=&quot;100%&quot;</code>;</li>\n		<li><code>height=&quot;120&quot;</code>;</li>\n	</ul></li>\n	<li>small:\n	<ul>\n		<li><code>size=&quot;small&quot;</code>;</li>\n		<li><code>width=&quot;100%&quot;</code>;</li>\n		<li><code>height=&quot;42&quot;</code>.</li>\n	</ul></li>\n	</ul>\n\n<h3>oui_if_bandcamp vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_bandcamp&gt;…&lt;/txp:oui_if_bandcamp&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Bandcamp related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;bandcamp&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://bandcamp.com/EmbeddedPlayer/album=3819613117</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;bandcamp&quot; play=&quot;album=3819613117&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_bandcamp play=&quot;album=3819613117&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;bandcamp&quot; play=&quot;https://bandcamp.com/EmbeddedPlayer/album=3819613117/track=1169742928&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_bandcamp play=&quot;https://bandcamp.com/EmbeddedPlayer/album=3819613117/track=1169742928&quot;&gt;\n    &lt;txp:oui_bandcamp /&gt;\n&lt;/txp:oui_if_bandcamp&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_bandcamp version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_bandcamp v1.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_bandcamp,\n * a oui_player v2+ extension to easily embed\n * Bandcamp customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_bandcamp\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Bandcamp\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Bandcamp extends Provider\n    {\n        protected static $srcBase = \'//bandcamp.com/\';\n        protected static $srcGlue = array(\'EmbeddedPlayer/\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'350\',\n            \'height\'     => \'470\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'size\'      => array(\n                \'default\' => \'large\',\n                \'force\'   => true,\n                \'valid\'   => array(\'large\', \'small\'),\n            ),\n            \'artwork\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => array(\'\', \'none\', \'big\', \'small\'),\n            ),\n            \'bgcol\'     => array(\n                \'default\' => \'#ffffff\',\n                \'valid\'   => \'color\',\n            ),\n            \'linkcol\'   => array(\n                \'default\' => \'#0687f5\',\n                \'valid\'   => \'color\',\n            ),\n            \'tracklist\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'album\' => array(\n                \'scheme\' => \'#(https?://bandcamp\\.com/(EmbeddedPlayer/)?album=(\\d+)/?)#i\',\n                \'id\'     => 3,\n                \'prefix\' => \'album=\',\n                \'glue\' => \'/\',\n            ),\n            \'track\' => array(\n                \'scheme\' => \'#(https?://bandcamp\\.com/(EmbeddedPlayer/)?[\\S]+track=(\\d+)/?)#i\',\n                \'id\'     => 3,\n                \'prefix\' => \'track=\',\n            ),\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_bandcamp,\n * a oui_player v2+ extension to easily embed\n * Bandcamp customizable audio players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_bandcamp\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Bandcamp\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Bandcamp extends Provider\n    {\n        protected static $srcBase = \'//bandcamp.com/\';\n        protected static $srcGlue = array(\'EmbeddedPlayer/\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'350\',\n            \'height\'     => \'470\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'size\'      => array(\n                \'default\' => \'large\',\n                \'force\'   => true,\n                \'valid\'   => array(\'large\', \'small\'),\n            ),\n            \'artwork\'   => array(\n                \'default\' => \'\',\n                \'valid\'   => array(\'\', \'none\', \'big\', \'small\'),\n            ),\n            \'bgcol\'     => array(\n                \'default\' => \'#ffffff\',\n                \'valid\'   => \'color\',\n            ),\n            \'linkcol\'   => array(\n                \'default\' => \'#0687f5\',\n                \'valid\'   => \'color\',\n            ),\n            \'tracklist\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaType = \'audio\';\n        protected static $mediaPatterns = array(\n            \'album\' => array(\n                \'scheme\' => \'#(https?://bandcamp\\.com/(EmbeddedPlayer/)?album=(\\d+)/?)#i\',\n                \'id\'     => 3,\n                \'prefix\' => \'album=\',\n                \'glue\' => \'/\',\n            ),\n            \'track\' => array(\n                \'scheme\' => \'#(https?://bandcamp\\.com/(EmbeddedPlayer/)?[\\S]+track=(\\d+)/?)#i\',\n                \'id\'     => 3,\n                \'prefix\' => \'track=\',\n            ),\n        );\n    }\n}\n', '53d4a242bbff30c4dd4536d638c3c490', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_bandcamp => Bandcamp\noui_player_bandcamp => Bandcamp player\noui_player_bandcamp_prefs => Display the Bandcamp player preferences\noui_player_bandcamp_width => Player width\noui_player_bandcamp_height => Player height\noui_player_bandcamp_ratio => Player ratio by default\noui_player_bandcamp_responsive => Render responsive players from the provided dimensions\noui_player_bandcamp_artwork => Artwork (optional)\noui_player_bandcamp_artwork_none => None\noui_player_bandcamp_artwork_big => Big (large player only)\noui_player_bandcamp_artwork_small => Small (large player only)\noui_player_bandcamp_bgcol => Background color\noui_player_bandcamp_linkcol => Links color\noui_player_bandcamp_minimal => Artwork only\noui_player_bandcamp_tracklist => Display the tracklist (large player only)\noui_player_bandcamp_size => Size\noui_player_bandcamp_size_large => Large\noui_player_bandcamp_size_small => Small\n\n#@language fr-fr\n#@admin\noui_player_bandcamp => Lecteur Bandcamp\noui_player_bandcamp_prefs => Afficher les préférences du lecteur Bandcamp\noui_player_bandcamp_width => Largeur du lecteur\noui_player_bandcamp_height => Hauteur du lecteur\noui_player_bandcamp_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_bandcamp_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_bandcamp_artwork => Visuel (optionel)\noui_player_bandcamp_artwork_none => Aucun\noui_player_bandcamp_artwork_big => Grand (grand lecteur)\noui_player_bandcamp_artwork_small => Petit (grand lecteur)\noui_player_bandcamp_bgcol => Couleur d\'arrière-plan\noui_player_bandcamp_linkcol => Couleur des liens\noui_player_bandcamp_tracklist => Afficher la liste de lecture (grand lecteur)\noui_player_bandcamp_size => Taille type du lecteur\noui_player_bandcamp_size_large => Grand\noui_player_bandcamp_size_small => Petit\n', '', 5, 4, 3),
('oui_myspace', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed MySpace video players through oui_player v2+', '<h1>oui_myspace</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_myspace\">oui_myspace</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://myspace.go.com/\">MySpace</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_myspace&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_myspace/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_myspace:<br />\n<code>$ composer require nicolasgraph/oui_myspace</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_myspace</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_myspace</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_myspace vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_myspace /&gt;</code> tag is a shortcut to embed MySpace videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;myspace&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://myspace.com/…/video/…/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_myspace</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>480</code>;<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>270</code>;<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set;<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_myspace vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_myspace&gt;…&lt;/txp:oui_if_myspace&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against MySpace related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;myspace&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://myspace.com/myspace/video/ok-go-the-ferris-wheel-interviews/109583400</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;myspace&quot; play=&quot;109583400&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_myspace play=&quot;109583400&quot; /&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_myspace version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_myspace v1.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_myspace,\n * a oui_player v2+ extension to easily embed\n * MySpace customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_myspace\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Myspace\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Myspace extends Provider\n    {\n        protected static $srcBase = \'//media.myspace.com/\';\n        protected static $srcGlue = array(\'play/video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'270\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://myspace\\.com/[\\S]+/video/[\\S]+/(\\d+)$#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_myspace,\n * a oui_player v2+ extension to easily embed\n * MySpace customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_myspace\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Myspace\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Myspace extends Provider\n    {\n        protected static $srcBase = \'//media.myspace.com/\';\n        protected static $srcGlue = array(\'play/video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'270\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://myspace\\.com/[\\S]+/video/[\\S]+/(\\d+)$#i\',\n            \'id\'     => \'1\',\n        );\n    }\n}\n', '3b70aa9835786891d4dbb7e008c2fca5', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_myspace => Myspace videos\noui_player_myspace => Myspace player\noui_player_myspace_prefs => Display the Myspace player preferences\noui_player_myspace_width => Player width\noui_player_myspace_height => Player height\noui_player_myspace_ratio => Player ratio by default\noui_player_myspace_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_myspace => Myspace Videos\noui_player_myspace => Lecteur Myspace Videos\noui_player_myspace_prefs => Afficher les préférences du lecteur Myspace\noui_player_myspace_width => Largeur du lecteur\noui_player_myspace_height => Hauteur du lecteur\noui_player_myspace_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_myspace_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3),
('oui_viddsee', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA3', 'Embed Viddsee video players through oui_player v2+', '<h1>oui_viddsee</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_viddsee\">oui_viddsee</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://viddsee.go.com/\">Viddsee</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_viddsee&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_viddsee/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_viddsee:<br />\n<code>$ composer require nicolasgraph/oui_viddsee</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_viddsee</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_viddsee</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_viddsee vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_viddsee /&gt;</code> tag is a shortcut to embed Viddsee videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;viddsee&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s):(www.)//viddsee.com/video/…/<strong>ID</strong>;</li>\n	<li>http(s):(www.)//viddsee.com/player/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_viddsee</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>560</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>315</code>.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_viddsee vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_viddsee&gt;…&lt;/txp:oui_if_viddsee&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Viddsee related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;viddsee&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://www.viddsee.com/video/the-lying-theory/j2zn3</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;viddsee&quot; play=&quot;j2zn3&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_viddsee play=&quot;j2zn3&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;viddsee&quot; play=&quot;https://www.viddsee.com/player/j2zn3&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_viddsee play=&quot;https://www.viddsee.com/player/j2zn3&quot;&gt;\n    &lt;txp:oui_viddsee /&gt;\n&lt;/txp:oui_if_viddsee&gt;</code></pre>\n\n<p>…or as a <a href=\"https://docs.textpattern.io/tags/tag-basics/core-short-tags\">short tag</a> (Txp 4.7+):</p>\n\n<pre class=\"language-markup\"><code>&lt;oui::if_viddsee play=&quot;https://www.viddsee.com/player/j2zn3&quot; /&gt;\n    &lt;oui::viddsee /&gt;\n&lt;/oui::if_viddsee&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_viddsee version 1.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_viddsee v1.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_viddsee,\n * a oui_player v2+ extension to easily embed\n * Viddsee customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_viddsee\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Viddsee\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Viddsee extends Provider\n    {\n        protected static $srcBase = \'//www.viddsee.com/\';\n        protected static $srcGlue = array(\'player/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'560\',\n            \'height\'     => \'315\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?(viddsee\\.com/(video|player)/)(\\S+/)?([^&?/]+)$#i\',\n            \'id\'     => \'5\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_viddsee,\n * a oui_player v2+ extension to easily embed\n * Viddsee customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_viddsee\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Viddsee\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Viddsee extends Provider\n    {\n        protected static $srcBase = \'//www.viddsee.com/\';\n        protected static $srcGlue = array(\'player/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'560\',\n            \'height\'     => \'315\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?(viddsee\\.com/(video|player)/)(\\S+/)?([^&?/]+)$#i\',\n            \'id\'     => \'5\',\n        );\n    }\n}\n', 'b6d15d5350c6d149a03fffa951da88cc', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_viddsee => Viddsee\noui_player_viddsee => Viddsee player\noui_player_viddsee_prefs => Display the Viddsee player preferences\noui_player_viddsee_width => Player width\noui_player_viddsee_height => Player height\noui_player_viddsee_ratio => Player ratio by default\noui_player_viddsee_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_viddsee => Viddsee\noui_player_viddsee => Lecteur Viddsee\noui_player_viddsee_prefs => Afficher les préférences du lecteur Viddsee\noui_player_viddsee_width => Largeur du lecteur\noui_player_viddsee_height => Hauteur du lecteur\noui_player_viddsee_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_viddsee_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_vine', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA2', 'Embed Vine players through oui_player v2+', '<h1>oui_vine</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_vine\">oui_vine</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://vine.go.com/\">Vine</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_vine&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_vine/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_vine:<br />\n<code>$ composer require nicolasgraph/oui_vine</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_vine</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_vine</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_vine vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_vine /&gt;</code> tag is a shortcut to embed Vine videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;vine&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://(www.)vine.co/v/<strong>ID</strong>;</li>\n	<li><strong>Name of a field</strong> containing any of the above value.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_vine</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>600</code>.<br />\nThe width of the player in pixels.<br />\n<strong>valid:</strong> integer</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>600</code>.<br />\n<strong>Default:</strong> related plugin pref.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer, initially no set.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.<br />\nThe related preference is a generic oui_player preference; it is applied to all providers or none.</p></dd>\n</dl>\n\n<h5>Parameters</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>0</code> and <code>1</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>type</code></dt>\n	<dd><p><code>simple</code> or <code>postcard</code>, initially set to <code>simple</code>.<br />\nSet the player type.</p></dd>\n	<dt><code>audio</code></dt>\n	<dd><p><code>0</code> or <code>1</code>; initially set to <code>0</code>.<br />\nEnable sound.</p></dd>\n</dl>\n\n<h3>oui_if_vine vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_vine&gt;…&lt;/txp:oui_if_vine&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Vine related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;vine&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://vine.co/v/5pQpM3tjHP1</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;vine&quot; play=&quot;5pQpM3tjHP1&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_vine play=&quot;5pQpM3tjHP1&quot; /&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_vine version 1.0.0-BETA2, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_vine v1.0.0-BETA2</strong> (2018-07-05)\n	<ul>\n		<li>Fixed: multiple script embedding.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_vine,\n * a oui_player v2+ extension to easily embed\n * Vine customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vine\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vine\n *\n * @package Oui\\Player\n */\n\nnamespace Oui {\n\n    if (class_exists(\'Oui\\Provider\')) {\n\n        class Vine extends Provider\n        {\n            protected static $patterns = array(\n                \'scheme\' => \'#^(http|https)://(www\\.)?vine.co/(v/)?([^&?/]+)#i\',\n                \'id\'     => \'4\'\n            );\n            protected static $src = \'//vine.co/\';\n            protected static $script = \'https://platform.vine.co/static/scripts/embed.js\';\n            protected static $glue = array(\'v/\', \'/embed/\', \'?\');\n            protected static $dims = array(\n                \'width\'  => \'600\',\n                \'height\' => \'600\',\n                \'ratio\'  => \'\',\n            );\n            protected static $params = array(\n                \'type\' => array(\n                    \'default\' => \'simple\',\n                    \'valid\'   => array(\'simple\', \'postcard\'),\n                ),\n                \'audio\' => array(\n                    \'default\' => \'0\',\n                    \'valid\'   => array(\'0\', \'1\'),\n                ),\n            );\n\n            /**\n             * {@inheritdoc}\n             */\n\n            public function getPlayerParams()\n            {\n                $params = array();\n\n                foreach (self::getParams() as $param => $infos) {\n                    $pref = get_pref(strtolower(str_replace(\'\\\\\', \'_\', get_class($this))) . \'_\' . $param);\n                    $default = $infos[\'default\'];\n                    $value = isset($this->config[$param]) ? $this->config[$param] : \'\';\n\n                    // Add attributes values in use or modified prefs values as player parameters.\n                    if ($param === \'type\') {\n                        $params[] = $value ?: $pref;\n                    } elseif ($value === \'\' && $pref !== $default) {\n                        $params[] = $param . \'=\' . $pref;\n                    } elseif ($value !== \'\') {\n                        $params[] = $param . \'=\' . $value;\n                    }\n                }\n\n                return $params;\n            }\n        }\n    }\n}\n', '\n\n/*\n * This file is part of oui_vine,\n * a oui_player v2+ extension to easily embed\n * Vine customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vine\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vine\n *\n * @package Oui\\Player\n */\n\nnamespace Oui {\n\n    if (class_exists(\'Oui\\Provider\')) {\n\n        class Vine extends Provider\n        {\n            protected static $patterns = array(\n                \'scheme\' => \'#^(http|https)://(www\\.)?vine.co/(v/)?([^&?/]+)#i\',\n                \'id\'     => \'4\'\n            );\n            protected static $src = \'//vine.co/\';\n            protected static $script = \'https://platform.vine.co/static/scripts/embed.js\';\n            protected static $glue = array(\'v/\', \'/embed/\', \'?\');\n            protected static $dims = array(\n                \'width\'  => \'600\',\n                \'height\' => \'600\',\n                \'ratio\'  => \'\',\n            );\n            protected static $params = array(\n                \'type\' => array(\n                    \'default\' => \'simple\',\n                    \'valid\'   => array(\'simple\', \'postcard\'),\n                ),\n                \'audio\' => array(\n                    \'default\' => \'0\',\n                    \'valid\'   => array(\'0\', \'1\'),\n                ),\n            );\n\n            /**\n             * {@inheritdoc}\n             */\n\n            public function getPlayerParams()\n            {\n                $params = array();\n\n                foreach (self::getParams() as $param => $infos) {\n                    $pref = get_pref(strtolower(str_replace(\'\\\\\', \'_\', get_class($this))) . \'_\' . $param);\n                    $default = $infos[\'default\'];\n                    $value = isset($this->config[$param]) ? $this->config[$param] : \'\';\n\n                    // Add attributes values in use or modified prefs values as player parameters.\n                    if ($param === \'type\') {\n                        $params[] = $value ?: $pref;\n                    } elseif ($value === \'\' && $pref !== $default) {\n                        $params[] = $param . \'=\' . $pref;\n                    } elseif ($value !== \'\') {\n                        $params[] = $param . \'=\' . $value;\n                    }\n                }\n\n                return $params;\n            }\n        }\n    }\n}\n', '2404177b8316c47eba262d9479b9fbe8', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_vine => Vine\noui_player_vine => Vine player\noui_player_vine_prefs => Display the Vine player preferences\noui_player_vine_width => Video width\noui_player_vine_height => Video height\noui_player_vine_ratio => Player ratio by default\noui_player_vine_type => Display type\noui_player_vine_type_simple => Simple\noui_player_vine_type_postcard => Postcard\noui_player_vine_audio => Enable sound by default\noui_player_vine_ratio => Player ratio by default\n\n#@language fr-fr\n#@admin\noui_player_provider_vine => Vine\noui_player_vine => Vine player\noui_player_vine_prefs => Afficher les préférences du lecteur Vine\noui_player_vine_width => Largeur de la vidéo\noui_player_vine_height => Hauteur de la vidéo\noui_player_vine_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_vine_type => Type de lecteur\noui_player_vine_type_simple => Simple\noui_player_vine_type_postcard => Postcard\noui_player_vine_audio => Activer le son par défaut\n', '', 1, 4, 3),
('oui_dailymotion', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '2.0.0-BETA3', 'Embed Dailymotion video players through oui_player v2+', '<h1>oui_dailymotion</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_dailymotion\">oui_dailymotion</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://dailymotion.com/\">Dailymotion</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_dailymotion&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_dailymotion/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_dailymotion:<br />\n<code>$ composer require nicolasgraph/oui_dailymotion</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_dailymotion</code></pre>\n\n<h3>Uninstall</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_dailymotion</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_dailymotion vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_dailymotion /&gt;</code> tag is a shortcut to embed Dailymotion videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;dailymotion&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://dai.ly/<strong>ID</strong>;</li>\n	<li>http(s)://www.dailymotion.com/video/<strong>ID</strong>…;</li>\n	<li>http(s)://www.dailymotion.com/embed/video/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>&lt;txp:oui_player /&gt;</code> tag (vs <code>&lt;oui_dailymotion /&gt;</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>480</code>;<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially set to <code>270</code>;<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially not set;<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>From Txp 4.7, paramaters accepting <code>true</code> and <code>false</code> as valid values can be enable through boolean/valueless attributes.</p>\n\n<dl>\n	<dt><code>api</code></dt>\n	<dd><p><code>false</code>, <code>postMessage</code>, <code>location</code> or <code>1</code>; initially set to <code>false</code>.<br />\nEnables or disables <a href=\"https://developer.dailymotion.com/player#player-api\">the javascript player api</a>.</p></dd>\n	<dt><code>autoplay</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nStarts the playback of the video automatically after the player load.<br />\nThis parameter may not work on some mobile OS.</p></dd>\n	<dt><code>controls</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the player controls or not.</p></dd>\n	<dt><code>endscreen_enable</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to enable the end screen or not.</p></dd>\n	<dt><code>mute</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nWhether to mute the video or not.</p></dd>\n	<dt><code>origin</code></dt>\n	<dd><p><span class=\"caps\">URL</span>; initially not set.<br />\nThe domain of the page hosting the Dailymotion player.<br />\nWhen using <code>api=&quot;postMessage&quot;</code>, you might want to specify origin for extra security.</p></dd>\n	<dt><code>quality</code></dt>\n	<dd><p><code>240</code>, <code>380</code>, <code>480</code>, <code>720</code>, <code>1080</code>, <code>1440</code> or <code>2160</code>; initially set to <code>auto</code>.<br />\nSet the suggested playback quality for the video.</p></dd>\n	<dt><code>sharing_enable</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the sharing button or not.</p></dd>\n	<dt><code>start</code></dt>\n	<dd><p>Integer, initially not set.<br />\nSpecifies the time (in seconds) from which the video should start playing.</p></dd>\n	<dt><code>subtitles_default</code></dt>\n	<dd><p>Initially not set.<br />\nSpecifies the default selected subtitles language.</p></dd>\n	<dt><code>syndication</code></dt>\n	<dd><p>Initially not set.<br />\nPasses your syndication key to the player.</p></dd>\n	<dt><code>ui_highlight</code></dt>\n	<dd><p>Hexadecimal color value; initially set to <code>ffcc33</code>.<br />\nChanges the default colour used in the controls.</p></dd>\n	<dt><code>ui_logo</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to display the Dailymotion logo or not.</p></dd>\n	<dt><code>ui_start_screen_info</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>true</code>.<br />\nWhether to show video information on the start screen or not.</p></dd>\n	<dt><code>ui_theme</code></dt>\n	<dd><p><code>dark</code> or <code>light</code>, initially set to <code>dark</code>.<br />\nChoose the default base colour theme.</p></dd>\n</dl>\n\n<h3>oui_if_dailymotion vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_dailymotion&gt;…&lt;/txp:oui_if_dailymotion&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Dailymotion related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;dailymotion&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>http://dai.ly/x2m8jpp</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;dailymotion&quot; play=&quot;x2m8jpp&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_dailymotion play=&quot;x2m8jpp&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;dailymotion&quot; play=&quot;https://www.dailymotion.com/video/x2m8jpp&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_dailymotion play=&quot;https://www.dailymotion.com/video/x2m8jpp&quot;&gt;\n    &lt;txp:oui_dailymotion /&gt;\n&lt;/txp:oui_if_dailymotion&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_dailymotion version 2.0.0-BETA3, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_dailymotion v2.0.0-BETA3</strong> (2018-08-15)\n	<ul>\n		<li>Changed: initial player size.</li>\n	</ul></li>\n	<li><strong>oui_player v1.3.0</strong> (2017-05-24)</li>\n</ul>', '\n\n/*\n * This file is part of oui_dailymtion,\n * a oui_player v2+ extension to easily embed\n * Dailymotion customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_dailymotion\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Dailymotion\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Dailymotion extends Provider\n    {\n        protected static $srcBase = \'//www.dailymotion.com/\';\n        protected static $srcGlue = array(\'embed/video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'270\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'api\'                  => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'false\', \'postMessage\', \'location\', \'1\'),\n            ),\n            \'autoplay\'             => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'controls\'             => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'endscreen-enable\'     => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'mute\'                 => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'origin\'               => \'\',\n            \'quality\'              => array(\n                \'default\' => \'auto\',\n                \'valid\'   => array(\'auto\', \'240\', \'380\', \'480\', \'720\', \'1080\', \'1440\', \'2160\'),\n            ),\n            \'sharing-enable\'       => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'start\'                => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\'\n            ),\n            \'subtitles-default\'    => array(\n                \'default\' => \'\',\n            ),\n            \'ui-highlight\'         => array(\n                \'default\' => \'#ffcc33\',\n                \'valid\'   => \'color\',\n            ),\n            \'ui-logo\'              => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'ui-theme\'             => array(\n                \'default\' => \'dark\',\n                \'valid\'   => array(\'dark\', \'light\'),\n            ),\n            \'ui-start-screen-info\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?(dailymotion\\.com/((embed/video)|(video))|(dai\\.ly?))/([A-Za-z0-9]+)#i\',\n            \'id\'     => \'7\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_dailymtion,\n * a oui_player v2+ extension to easily embed\n * Dailymotion customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_dailymotion\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Dailymotion\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Dailymotion extends Provider\n    {\n        protected static $srcBase = \'//www.dailymotion.com/\';\n        protected static $srcGlue = array(\'embed/video/\', \'?\', \'&amp;\');\n        protected static $iniDims = array(\n            \'width\'      => \'480\',\n            \'height\'     => \'270\',\n            \'ratio\'      => \'\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $iniParams = array(\n            \'api\'                  => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'false\', \'postMessage\', \'location\', \'1\'),\n            ),\n            \'autoplay\'             => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'controls\'             => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'endscreen-enable\'     => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'mute\'                 => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'origin\'               => \'\',\n            \'quality\'              => array(\n                \'default\' => \'auto\',\n                \'valid\'   => array(\'auto\', \'240\', \'380\', \'480\', \'720\', \'1080\', \'1440\', \'2160\'),\n            ),\n            \'sharing-enable\'       => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'start\'                => array(\n                \'default\' => \'0\',\n                \'valid\'   => \'number\'\n            ),\n            \'subtitles-default\'    => array(\n                \'default\' => \'\',\n            ),\n            \'ui-highlight\'         => array(\n                \'default\' => \'#ffcc33\',\n                \'valid\'   => \'color\',\n            ),\n            \'ui-logo\'              => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n            \'ui-theme\'             => array(\n                \'default\' => \'dark\',\n                \'valid\'   => array(\'dark\', \'light\'),\n            ),\n            \'ui-start-screen-info\' => array(\n                \'default\' => \'true\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www\\.)?(dailymotion\\.com/((embed/video)|(video))|(dai\\.ly?))/([A-Za-z0-9]+)#i\',\n            \'id\'     => \'7\',\n        );\n    }\n}\n', '4f6c63859c643b40623af605ba4c7867', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_dailymotion => Dailymotion\noui_player_dailymotion => Dailymotion player\noui_player_dailymotion_prefs => Display the Dailymotion player preferences\noui_player_dailymotion_api => Player JS API\noui_player_dailymotion_api_false => Disable\noui_player_dailymotion_api_postmessage => postMessage\noui_player_dailymotion_api_location => location (native mobile apps)\noui_player_dailymotion_api_1 => Enable\noui_player_dailymotion_autoplay => Automatic playing\noui_player_dailymotion_controls => Control bar display\noui_player_dailymotion_endscreen-enable => Display related videos at the end\noui_player_dailymotion_mute => Disable sound by default\noui_player_dailymotion_origin => Origin domain URL\noui_player_dailymotion_quality => Video quality\noui_player_dailymotion_quality_auto => Auto\noui_player_dailymotion_quality_240 => 240p\noui_player_dailymotion_quality_380 => 380p\noui_player_dailymotion_quality_480 => 480p\noui_player_dailymotion_quality_720 => 720p\noui_player_dailymotion_quality_1080 => 1080p\noui_player_dailymotion_quality_1440 => 1440p\noui_player_dailymotion_quality_2160 => 2160p\noui_player_dailymotion_sharing-enable => Enable sharing features\noui_player_dailymotion_start => Commencer la lecture à la seconde:\noui_player_dailymotion_subtitles-default => Subtitles language by default\noui_player_dailymotion_syndication => Syndication key for the player\noui_player_dailymotion_ui-highlight => Preferred colour for UI player elements\noui_player_dailymotion_ui-logo => Display the Dailymotion logo\noui_player_dailymotion_ui-start-screen-info => Display video informations\noui_player_dailymotion_ui-theme => Preferred theme for the player\noui_player_dailymotion_ui-theme_dark => Dark\noui_player_dailymotion_ui-theme_light => Light\noui_player_dailymotion_width => Player width\noui_player_dailymotion_height => Player height\noui_player_dailymotion_ratio => Player ratio by default\noui_player_dailymotion_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_dailymotion => Dailymotion\noui_player_dailymotion => Lecteur Dailymotion\noui_player_dailymotion_prefs => Afficher les préférences du lecteur Dailymotion\noui_player_dailymotion_width => Largeur du lecteur\noui_player_dailymotion_height => Hauteur du lecteur\noui_player_dailymotion_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_dailymotion_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\noui_player_dailymotion_api => API JS du lecteur\noui_player_dailymotion_api_false => Désactivée\noui_player_dailymotion_api_postmessage => postMessage\noui_player_dailymotion_api_location => location (applications mobiles)\noui_player_dailymotion_api_1 => Activée\noui_player_dailymotion_autoplay => Lecture automatique\noui_player_dailymotion_controls => Afficher la barre le contrôle\noui_player_dailymotion_endscreen-enable => Afficher les vidéos associées en fin de lecture\noui_player_dailymotion_mute => Désactiver le son par défaut\noui_player_dailymotion_origin => URL du domaine diffuseur\noui_player_dailymotion_quality => Qualité de la vidéo\noui_player_dailymotion_quality_auto => Auto\noui_player_dailymotion_quality_240 => 240p\noui_player_dailymotion_quality_380 => 380p\noui_player_dailymotion_quality_480 => 480p\noui_player_dailymotion_quality_720 => 720p\noui_player_dailymotion_quality_1080 => 1080p\noui_player_dailymotion_quality_1440 => 1440p\noui_player_dailymotion_quality_2160 => 2160p\noui_player_dailymotion_sharing-enable => Afficher les fonctionnalités de partage\noui_player_dailymotion_start => Commencer la lecture à la seconde:\noui_player_dailymotion_subtitles-default => Langue des sous-titres par défaut\noui_player_dailymotion_syndication => Clé de syndication pour le lecteur\noui_player_dailymotion_ui-highlight => Couleur préférée pour les éléments d\'interface du lecteur\noui_player_dailymotion_ui-logo => Afficher le logo Dailymotion\noui_player_dailymotion_ui-start-screen-info => Afficher les informations sur la vidéo\noui_player_dailymotion_ui-theme => Thème préféré pour le lecteur\noui_player_dailymotion_ui-theme_dark => Sombre\noui_player_dailymotion_ui-theme_light => Clair\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_ted', 0, 'Nicolas Morand', 'http://github.com/NicolasGraph', '1.0.0-BETA1', 'Embed Ted video players through oui_player v2+', '<h1>oui_ted</h1>\n\n<h2>Introduction</h2>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_ted\">oui_ted</a> is a <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+ extension to easily embed <a href=\"https://ted.go.com/\">Ted</a> provided videos in <a href=\"https://textpattern.com/\">Textpattern</a> powered websites.</p>\n\n<p>The above platform serves cookies with its player, according to the EU legislation, you should warn your users about these cookies before they are added. This can be done by using <a href=\"https://github.com/NicolasGraph/oui_cookie\">oui_cookie</a>.</p>\n\n<h3>Plugin requirements</h3>\n\n<p>oui_ted&#8217;s minimum requirements:</p>\n\n<ul>\n	<li><a href=\"http://textpattern.com/\">Textpattern <span class=\"caps\">CMS</span></a> v4.6+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> v2+;</li>\n	<li><a href=\"https://github.com/NicolasGraph/oui_provider\">oui_provider</a> v1+.</li>\n</ul>\n\n<h2>Plugin management</h2>\n\n<h3>Installation</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li><a href=\"https://github.com/NicolasGraph/oui_ted/releases\">Download</a> the compiled plugin file or the source to compile a customized file.</li>\n	<li>Paste the content of the compiled plugin file under the <a href=\"?event=plugin\">Admin &gt; Plugins</a> tab and click the <em>Upload</em> button.</li>\n	<li>Confirm the plugin install by clicking the <em>Install</em> button on the plugin preview page.</li>\n	<li>Enable the plugin and click the <em>Options</em> link (or visit the <em>Preferences</em> tab) to set the provider preferences.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<p>After <a href=\"https://getcomposer.org/doc/00-intro.md%E2%80%A6\">installing Composer</a></p>\n\n<ol>\n	<li>Target your project directory:<br />\n<code>$ cd /path/to/your/textpattern/installation/dir</code></li>\n	<li>If it&#8217;s not already done, lock your version of Txp:<br />\n<code>$ composer require textpattern/lock:4.6.2</code>, where <code>4.6.2</code> is the Txp version in use.</li>\n	<li>Install oui_ted:<br />\n<code>$ composer require nicolasgraph/oui_ted</code></li>\n</ol>\n\n<h3>Update</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Follow the installation instruction above.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer update nicolasgraph/oui_ted</code></pre>\n\n<h3>Deletion</h3>\n\n<h4>From the admin interface</h4>\n\n<ol>\n	<li>Check the box on the left of the plugin row under the <a href=\"?event=plugin\">Admin &gt; Plugins</a>.</li>\n	<li>open the select list at the bottom of the plugins table and choose <em>Delete</em>.</li>\n	<li>confirm the plugin deletion.</li>\n</ol>\n\n<h4>Via Composer (not available yet)</h4>\n\n<pre><code>$ composer remove nicolasgraph/oui_ted</code></pre>\n\n<h2>Tags and parameters</h2>\n\n<h3>oui_ted vs oui_player</h3>\n\n<p>While the <code>&lt;txp:oui_player /&gt;</code> tag is able to embed any video from any extension-related provider, the <code>&lt;txp:oui_ted /&gt;</code> tag is a shortcut to embed Ted videos only. It is strictly equivalent to <code>&lt;txp:oui_player provider=&quot;ted&quot; /&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h4>Recognised values for the <code>play</code> attribute</h4>\n\n<ul>\n	<li><strong>ID</strong>;</li>\n	<li>http(s)://(www.)ted.com/talks/<strong>ID</strong>;</li>\n	<li>http(s)://embed.ted.com/talks/<strong>ID</strong>.</li>\n</ul>\n\n<p>When using the <code>oui_player</code> tag (vs <code>oui_ted</code>), the right provider needs to be set if the <code>play</code> value or its field related one is an ID. It is optional, but can fasten the rendering, if the value is an <span class=\"caps\">URL</span>.</p>\n\n<h4>Player customization</h4>\n\n<h5>Preferences vs attributes</h5>\n\n<p>While some player customization elements, related to the media to play, should be used through attributes, these used as global settings should preferrably be set through the plugin preferences to avoid a massive use of tag attributes.</p>\n\n<p>Of course, it is still possible to override a preference value via an attribute when needed.</p>\n\n<h5>Size</h5>\n\n<dl>\n	<dt><code>width</code></dt>\n	<dd><p>Integer; initially set to <code>854</code>.<br />\nThe width of the player in pixels.</p></dd>\n	<dt><code>height</code></dt>\n	<dd><p>Integer; initially not set.<br />\nThe height of the player in pixels.</p></dd>\n	<dt><code>ratio</code></dt>\n	<dd><p>Integer[<code>:</code>]integer; initially set to <code>16:9</code>.<br />\nThe ratio used to calculate the missing value for the width or the height.</p></dd>\n	<dt><code>responsive</code></dt>\n	<dd><p><code>true</code> or <code>false</code>; initially set to <code>false</code>.<br />\nAdapts the final player size from the provided width and/or height and/or ratio values to fit the container width.<br />\nWraps the player and adds some styles.</p></dd>\n</dl>\n\n<h5>Parameters (a→z)</h5>\n\n<p>There is no player parameter available for this provider.</p>\n\n<h3>oui_if_ted vs oui_if_player</h3>\n\n<p>While the <code>&lt;txp:oui_if_player&gt;…&lt;/txp:oui_if_player&gt;</code> tag is able to check an <span class=\"caps\">URL</span> against any <span class=\"caps\">URL</span> from any extension-related provider, the <code>&lt;txp:oui_if_ted&gt;…&lt;/txp:oui_if_ted&gt;</code> tag is a shortcut to check an <span class=\"caps\">URL</span> against Ted related <span class=\"caps\">URL</span> schemes only. It is strictly equivalent to <code>&lt;txp:oui_if_player provider=&quot;ted&quot;&gt;…&lt;/txp:oui_if_player&gt;</code>.</p>\n\n<p>Check the <a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> documentation for more informations.</p>\n\n<h2>Examples</h2>\n\n<h3>Embed a player using the preference values</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player /&gt;</code></pre>\n\n<p><a href=\"https://github.com/NicolasGraph/oui_player\">oui_player</a> preference related field value: <code>https://www.ted.com/talks/aparna_rao_high_tech_art_with_a_sense_of_humor</code>.</p>\n\n<h3>Embed a player by defining its provider and a media ID through attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_player provider=&quot;ted&quot; play=&quot;aparna_rao_high_tech_art_with_a_sense_of_humor&quot; /&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_ted play=&quot;aparna_rao_high_tech_art_with_a_sense_of_humor&quot; /&gt;</code></pre>\n\n<h3>Conditionally embed a player using attributes</h3>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_player provider=&quot;ted&quot; play=&quot;https://www.ted.com/talks/aparna_rao_high_tech_art_with_a_sense_of_humor&quot;&gt;\n    &lt;txp:oui_player /&gt;\n&lt;/txp:oui_if_player&gt;</code></pre>\n\n<p>…or:</p>\n\n<pre class=\"language-markup\"><code>&lt;txp:oui_if_ted play=&quot;https://www.ted.com/talks/aparna_rao_high_tech_art_with_a_sense_of_humor&quot;&gt;\n    &lt;txp:oui_ted /&gt;\n&lt;/txp:oui_if_ted&gt;</code></pre>\n\n<h2>Credits</h2>\n\n<h3>Author</h3>\n\n<p><a href=\"https://twitter.com/NicolasGraph\">Nicolas Morand</a><br />\n<em>Thank you to the Textpattern community and the core team.</em></p>\n\n<h3>License</h3>\n\n<p>This plugin is distributed under <a href=\"https://www.gnu.org/licenses/gpl-2.0.txt\"><span class=\"caps\">GPL</span> v2.0</a>.</p>\n\n<p>oui_ted version 1.0.0-BETA1, Copyright &#169; 2018 Nicolas Morand<br />\nThis Textpattern plugin comes with <span class=\"caps\">ABSOLUTELY</span> <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#section11\">NO <span class=\"caps\">WARRANTY</span></a>.<br />\nThis is free software, and you are welcome to redistribute it <a href=\"https://www.gnu.org/licenses/gpl-2.0.html#SEC3\">under certain conditions</a>.</p>\n\n<h3>Changelog</h3>\n\n<ul>\n	<li><strong>oui_ted v1.0.0-BETA1</strong> (2018-08-15)</li>\n</ul>', '\n\n/*\n * This file is part of oui_vimeo,\n * a oui_player v2+ extension to easily embed\n * Vimeo customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vimeo\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Ted extends Provider\n    {\n        protected static $srcBase = \'//embed.ted.com/\';\n        protected static $srcGlue = array(\'talks/\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'854\',\n            \'height\'     => \'\',\n            \'ratio\'      => \'16:9\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www.|embed.)?ted\\.com/talks/([^\\?]+)#i\',\n            \'id\'     => \'2\',\n        );\n    }\n}\n', '\n\n/*\n * This file is part of oui_vimeo,\n * a oui_player v2+ extension to easily embed\n * Vimeo customizable video players in Textpattern CMS.\n *\n * https://github.com/NicolasGraph/oui_vimeo\n *\n * Copyright (C) 2018 Nicolas Morand\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; version 2 of the License.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..\n */\n\n/**\n * Vimeo\n *\n * @package Oui\\Player\n */\n\nnamespace Oui;\n\nif (class_exists(\'Oui\\Provider\')) {\n\n    class Ted extends Provider\n    {\n        protected static $srcBase = \'//embed.ted.com/\';\n        protected static $srcGlue = array(\'talks/\', \'/\', \'/\');\n        protected static $iniDims = array(\n            \'width\'      => \'854\',\n            \'height\'     => \'\',\n            \'ratio\'      => \'16:9\',\n            \'responsive\' => array(\n                \'default\' => \'false\',\n                \'valid\'   => array(\'true\', \'false\'),\n            ),\n        );\n        protected static $mediaPatterns = array(\n            \'scheme\' => \'#^https?://(www.|embed.)?ted\\.com/talks/([^\\?]+)#i\',\n            \'id\'     => \'2\',\n        );\n    }\n}\n', '177d6285f8fbf2e26989d146287a790b', '#Default language, en-us, en-gb\n#@admin\noui_player_provider_ted => Ted\noui_player_ted => Ted player\noui_player_ted_prefs => Display the Ted player preferences\noui_player_ted_width => Player width\noui_player_ted_height => Player height\noui_player_ted_ratio => Player ratio by default\noui_player_ted_responsive => Render responsive players from the provided dimensions\n\n#@language fr-fr\n#@admin\noui_player_provider_ted => Ted\noui_player_ted => Lecteur Ted\noui_player_ted_prefs => Afficher les préférences du lecteur Ted\noui_player_ted_width => Largeur du lecteur\noui_player_ted_height => Hauteur du lecteur\noui_player_ted_ratio => Ratio utilisé en l\'absence de l\'une des dimensions\noui_player_ted_responsive => Rendre les lecteurs adaptatifs à partir des dimensions fournies\n', '', 5, 4, 3);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('oui_flat', 0, 'Nicolas Morand, Jukka Svahn', 'https://github.com/nicolasgraph', '0.7.0-beta2', 'Edit templates as flat files (rah_flat fork)', '<h1>Contributing</h1>\n\n<p>Please take a quick look at this document before to make contribution process easier for all parties involved.</p>\n\n<h2>License</h2>\n\n<p><a href=\"https://github.com/gocom/rah_flat/blob/master/LICENSE\"><span class=\"caps\">GNU</span> General Public License, version 2</a>. By contributing code, you agree to license your additions under the GPLv2 license.</p>\n\n<h2>Configure git</h2>\n\n<p>For convenience your committer, git user, should be linked to your GitHub account:</p>\n\n<pre><code>$ git config --global user.name &quot;John Doe&quot;\n$ git config --global user.email john.doe@example.com</code></pre>\n\n<p>Make sure to use an email address that is linked to your GitHub account. It can be a throwaway address or you can use GitHub&#8217;s email protection features. We don&#8217;t want your emails, but this is to make sure we know who did what. All commits nicely link to their author, instead of them coming from foobar@invalid.tld.</p>\n\n<h2>Dependencies</h2>\n\n<p>Dependencies are managed using <a href=\"http://getcomposer.org\">Composer</a>. After you have cloned the repository, run composer install:</p>\n\n<pre><code>$ composer install</code></pre>\n\n<p>And update before testing and committing:</p>\n\n<pre><code>$ composer update</code></pre>\n\n<h2>Coding standard</h2>\n\n<p>The project follows the <a href=\"https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md\"><span class=\"caps\">PSR</span>-0</a> and <a href=\"https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide-meta.md\"><span class=\"caps\">PSR</span>-2</a> standards with <span class=\"caps\">PHP</span> 5.2 style namespacing. You can use <span class=\"caps\">PHP</span>_CodeSniffer to make sure your additions follow them too:</p>\n\n<pre><code>$ ./vendor/bin/phpcs --standard=phpcs.xml src</code></pre>\n\n<h2>Versioning</h2>\n\n<p><a href=\"http://semver.org/\">Semantic Versioning</a> and major.minor.path format.</p>\n<h1>oui_flat</h1>\n\n<p><a href=\"https://github.com/nicolasgraph/oui_flat/releases\">Download</a> | <a href=\"https://packagist.org/packages/oui/oui_flat\">Packagist</a> | <a href=\"http://ouiforum.biz/donate/oui_flat\">Donate to the original author</a></p>\n\n<p>This plugin makes your <a href=\"http://www.textpattern.com\">Textpattern <span class=\"caps\">CMS</span></a> database more flat, manageable and editable. Edit templates, forms, pages, preferences and sections as flat files. Use any editor, work in teams and store your website&#8217;s source under your favorite <a href=\"http://en.wikipedia.org/wiki/Revision_control\">version control system</a>.</p>\n\n<p><strong>Warning: this plugin will permanently remove some of your current templates when activated.</strong></p>\n\n<h2>Installing</h2>\n\n<p>Using <a href=\"http://getcomposer.org\">Composer</a>:</p>\n\n<pre><code>$ composer require oui/oui_flat:*</code></pre>\n\n<p>Or <a href=\"https://github.com/nicolasgraph/oui_flat/releases\">download</a> a plugin package.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern 4.6.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.3.6 or newer.</li>\n</ul>\n\n<h2>Basics</h2>\n\n<p>oui_flat imports normal, flat files from a specified directory to your Textpattern database. This, in essence, lets you to edit your database contents from any regular editor and store the source as flat files.</p>\n\n<p>oui_flat comes with built-in support for a few essential content types: styles, forms, pages, preferences, sections and textpacks. It is also able to manage variables via plugin prefs. See the <a href=\"https://github.com/nicolasgraph/oui_flat/tree/master/templates\">templates</a> directory on GitHub for an example how this all works.</p>\n\n<p>Your flat files are imported to the database:</p>\n\n<ul>\n	<li>Automatically on the background when the site is in the production status selected via the related plugin preference.</li>\n	<li>When the public callback hook <span class=\"caps\">URL</span> is accessed. The <span class=\"caps\">URL</span> can be used for deployment.</li>\n</ul>\n\n<p>If you want to exclude a certain content type from importing, just don&#8217;t create a directory for it. No directory, and oui_flat will leave the database alone when it comes to that content type.</p>\n\n<h2>Preferences</h2>\n\n<p>The plugin has set of preferences you can find on Textpattern&#8217;s normal preferences panel.</p>\n\n<h3>Path to the templates directory</h3>\n\n<p>Specifies path to the root templates directory containing all the content-type specific directories. This path is relative to your &#8216;textpattern&#8217; installation directory. For example, a path <code>../templates</code> would point to a directory located in the same directory as your <em>textpattern</em> directory and the main <em>index.php</em> file.</p>\n\n<h3>Security key for the public callback</h3>\n\n<p>Security key for the public callback hook <span class=\"caps\">URL</span>. Importing is done when the <span class=\"caps\">URL</span> is accessed. The <span class=\"caps\">URL</span> follows the format of:</p>\n\n<pre><code>http://example.com/?oui_flat_key={yourKey}</code></pre>\n\n<p>Where <code>http://example.com/</code> is your site&#8217;s <span class=\"caps\">URL</span>, and <code>{yourKey}</code> is the security key you specified.</p>\n\n<h2>Templates folder example</h2>\n\n<pre><code>┌ templates\n├── forms\n├── pages\n├── prefs\n├── sections\n├── styles\n├── textpacks\n└── variables</code></pre>\n\n<p>For most existing template subfolders, the related admin tab will be disable.<br />\nThe <em>Admin &gt; Preferences</em> tab will still be accessible, however saved preferences will be overriden by existing flat files while the <code>/prefs</code> folder exists<br />\nThe admin <em>Admin &gt; languages</em> tab will still be accessible and usable while the <code>/pages</code> folder exists.</p>\n\n<p>Contained files can use whatever extension.</p>\n\n<h3>Forms</h3>\n\n<p>Form types can be defined by filename prefixes or <code>/forms</code> subfolder names.<br />\n<code>misc</code> type is used by default when no prefix is found in files stored directly in the <code>/form</code> folder.</p>\n\n<pre><code>┌ templates\n├── forms\n├──── article.example_1.txp\n├──── example_2.txp\n├──── …\n├── …</code></pre>\n\n<p>or</p>\n\n<pre><code>┌ templates\n├── forms\n├──── article\n├────── example_1.txp\n├────── …\n├──── misc\n├────── example_2.txp\n├────── …\n├──── …\n├── …</code></pre>\n\n<h3>Pages</h3>\n\n<pre><code>├── …\n├── pages\n├──── example.txp\n├──── …\n├── …</code></pre>\n\n<h3>Prefs</h3>\n\n<p>Prefs files can contain a <span class=\"caps\">JSON</span> tree or just the preference value as text.</p>\n\n<pre><code>├── …\n├── prefs\n├──── sitename.json\n├──── site_slogan.txt\n├──── …\n├── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> field is:</p>\n\n<ul>\n	<li><code>val</code> (string): preference value;</li>\n</ul>\n\n<p><code>sitename.txt</code> would just contain the preference related value.</p>\n\n<h3>Sections</h3>\n\n<p>Sections files are managed via <span class=\"caps\">JSON</span> files.</p>\n\n<pre><code>├── …\n├── sections\n├──── example.json\n├──── …\n├── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> fields are:</p>\n\n<ul>\n	<li><code>title</code> (string): section title;</li>\n	<li><code>description</code> (string): section meta-description;</li>\n	<li><code>page</code> (string): page in use;</li>\n	<li><code>css</code> (string): styles in use;</li>\n	<li><code>is_default</code> (bool): <code>true</code> makes it the default section;</li>\n	<li><code>in_rss</code> (bool): <code>true</code> includes section articles in feeds;</li>\n	<li><code>on_frontpage</code> (bool): <code>true</code> displays section articles on the frontpage;</li>\n	<li><code>searchable</code> (bool): <code>true</code> includes section articles in search results.</li>\n</ul>\n\n<h3>Styles</h3>\n\n<pre><code>├── …\n├── styles\n├──── default.css\n├──── …\n├── …</code></pre>\n\n<h3>textpacks</h3>\n\n<pre><code>├── …\n├── textpacks\n├──── example.textpack\n├──── …\n├── …</code></pre>\n\n<h3>Variables</h3>\n\n<p>Variables can be defined via <span class=\"caps\">JSON</span> files as plugin prefs and used through the <code>&lt;txp:variable /&gt;</code> tag.</p>\n\n<pre><code>├── …\n├── variables\n├──── example.json\n├──── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> fields are:</p>\n\n<ul>\n	<li><code>value</code> (string): variable value;</li>\n	<li><code>html</code> (string): name of the function to use in the admin (if the <code>/variables</code> is removed). Natively available functions are: <code>text_input</code>, <code>pref_long_text</code>, <code>yesnoradio</code>.</li>\n	<li><code>position</code> (int): variable position.</li>\n</ul>\n\n<h2>Toolshed notice</h2>\n\n<p>This is a toolshed project. Experimental fork from <a href=\"https://github.com/gocom/oui_flat\">rah_flat</a> which is not part of the main supported product line of &#8220;Rah&#8220;https://github.com/gocom. Not yet at least. Please use at your own risk.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.7.0-beta (oui_flat from upcoming rah_flat-v0.4) &#8211; 2017/06/25</h3>\n\n<ul>\n	<li>Changed: requires Textpatern 4.6+</li>\n	<li>Added: Do not delete pages or styles that are used by a section.</li>\n	<li>Changed: variables use <code>rah_flat_variables</code> event only and are all visible or hidden as for other contents;</li>\n	<li>Changed: preferences stay always visible.</li>\n	<li>Improved: Uses original Textpacks format;</li>\n</ul>\n\n<h3>Version 0.6.0 (oui_flat) &#8211; 2017/03/14</h3>\n\n<ul>\n	<li>Changed: Back to <code>oui_</code> plugin author prefix.</li>\n	<li>Changed: Lighten compiled code by removing duplicated license infos.</li>\n</ul>\n\n<h3>Version 0.5.0 (oui_flat) &#8211; 2016/08/26</h3>\n\n<ul>\n	<li>Changed: Forms are stored by types in subfolders and don&#8217;t need prefixes anymore.</li>\n	<li>Added: Custom form types are changed to &#8216;misc&#8217; when the plugin is disable to avoid an error in the Forms tab.</li>\n	<li>Changed: Preferences update affects values only.</li>\n	<li>Added: Prefs are hidden in the admin if set via flat files and get back to visible when the plugin is disabled.</li>\n	<li>Added: Custom prefs (in the variables folder) accept more paramters in .json files.</li>\n	<li>Changed: Custom prefs (in the variables folder) now have a oui_flat_var_ prefix added to their name.</li>\n</ul>\n\n<h3>Version 0.4.0 (oui_flat) &#8211; 2015/11/29</h3>\n\n<ul>\n	<li>Added: Custom preferences can be created and use as Txp variables.</li>\n	<li>Changed: Forms naming convention is now <code>type.name.txp</code>.</li>\n</ul>\n\n<h3>Version 0.3.0 (rah_flat) &#8211; 2014/03/28</h3>\n\n<ul>\n	<li>Added: Drop access to a admin-side panel only if the specific content-type is active and has a directory set up.</li>\n	<li>Added: Invokable <code>oui_flat.import</code> callback event.</li>\n	<li>Added: Sections and preferences get their names from the filename.</li>\n	<li>Added: Preferences are always just updated, no strands are created.</li>\n	<li>Added: Preference fields that are not specified in a file are kept as-is in the database.</li>\n	<li>Added: French translation by <a href=\"https://github.com/cara-tm\">Patrick Lefevre</a>.</li>\n	<li>Changed: Renamed confusing <code>Oui_Flat_Import_Template</code> interface to <code>Oui_Flat_Import_ImportInterface</code>.</li>\n</ul>\n\n<h3>Version 0.2.0 (rah_flat) &#8211; 2014/03/19</h3>\n\n<ul>\n	<li>Reworked.</li>\n</ul>\n\n<h3>Version 0.1.0 (rah_flat) &#8211; 2013/05/07</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>\n<h1>oui_flat</h1>\n\n<p><a href=\"https://github.com/nicolasgraph/oui_flat/releases\">Download</a> | <a href=\"https://packagist.org/packages/oui/oui_flat\">Packagist</a> | <a href=\"http://ouiforum.biz/donate/oui_flat\">Donate to the original author</a></p>\n\n<p>This plugin makes your <a href=\"http://www.textpattern.com\">Textpattern <span class=\"caps\">CMS</span></a> database more flat, manageable and editable. Edit templates, forms, pages, preferences and sections as flat files. Use any editor, work in teams and store your website&#8217;s source under your favorite <a href=\"http://en.wikipedia.org/wiki/Revision_control\">version control system</a>.</p>\n\n<p><strong>Warning: this plugin will permanently remove some of your current templates when activated.</strong></p>\n\n<h2>Installing</h2>\n\n<p>Using <a href=\"http://getcomposer.org\">Composer</a>:</p>\n\n<pre><code>$ composer require oui/oui_flat:*</code></pre>\n\n<p>Or <a href=\"https://github.com/nicolasgraph/oui_flat/releases\">download</a> a plugin package.</p>\n\n<h2>Requirements</h2>\n\n<ul>\n	<li>Textpattern 4.6.0 or newer.</li>\n	<li><span class=\"caps\">PHP</span> 5.3.6 or newer.</li>\n</ul>\n\n<h2>Basics</h2>\n\n<p>oui_flat imports normal, flat files from a specified directory to your Textpattern database. This, in essence, lets you to edit your database contents from any regular editor and store the source as flat files.</p>\n\n<p>oui_flat comes with built-in support for a few essential content types: styles, forms, pages, preferences, sections and textpacks. It is also able to manage variables via plugin prefs. See the <a href=\"https://github.com/nicolasgraph/oui_flat/tree/master/templates\">templates</a> directory on GitHub for an example how this all works.</p>\n\n<p>Your flat files are imported to the database:</p>\n\n<ul>\n	<li>Automatically on the background when the site is in the production status selected via the related plugin preference.</li>\n	<li>When the public callback hook <span class=\"caps\">URL</span> is accessed. The <span class=\"caps\">URL</span> can be used for deployment.</li>\n</ul>\n\n<p>If you want to exclude a certain content type from importing, just don&#8217;t create a directory for it. No directory, and oui_flat will leave the database alone when it comes to that content type.</p>\n\n<h2>Preferences</h2>\n\n<p>The plugin has set of preferences you can find on Textpattern&#8217;s normal preferences panel.</p>\n\n<h3>Path to the templates directory</h3>\n\n<p>Specifies path to the root templates directory containing all the content-type specific directories. This path is relative to your &#8216;textpattern&#8217; installation directory. For example, a path <code>../templates</code> would point to a directory located in the same directory as your <em>textpattern</em> directory and the main <em>index.php</em> file.</p>\n\n<h3>Security key for the public callback</h3>\n\n<p>Security key for the public callback hook <span class=\"caps\">URL</span>. Importing is done when the <span class=\"caps\">URL</span> is accessed. The <span class=\"caps\">URL</span> follows the format of:</p>\n\n<pre><code>http://example.com/?oui_flat_key={yourKey}</code></pre>\n\n<p>Where <code>http://example.com/</code> is your site&#8217;s <span class=\"caps\">URL</span>, and <code>{yourKey}</code> is the security key you specified.</p>\n\n<h2>Templates folder example</h2>\n\n<pre><code>┌ templates\n├── forms\n├── pages\n├── prefs\n├── sections\n├── styles\n├── textpacks\n└── variables</code></pre>\n\n<p>For most existing template subfolders, the related admin tab will be disable.<br />\nThe <em>Admin &gt; Preferences</em> tab will still be accessible, however saved preferences will be overriden by existing flat files while the <code>/prefs</code> folder exists<br />\nThe admin <em>Admin &gt; languages</em> tab will still be accessible and usable while the <code>/pages</code> folder exists.</p>\n\n<p>Contained files can use whatever extension.</p>\n\n<h3>Forms</h3>\n\n<p>Form types can be defined by filename prefixes or <code>/forms</code> subfolder names.<br />\n<code>misc</code> type is used by default when no prefix is found in files stored directly in the <code>/form</code> folder.</p>\n\n<pre><code>┌ templates\n├── forms\n├──── article.example_1.txp\n├──── example_2.txp\n├──── …\n├── …</code></pre>\n\n<p>or</p>\n\n<pre><code>┌ templates\n├── forms\n├──── article\n├────── example_1.txp\n├────── …\n├──── misc\n├────── example_2.txp\n├────── …\n├──── …\n├── …</code></pre>\n\n<h3>Pages</h3>\n\n<pre><code>├── …\n├── pages\n├──── example.txp\n├──── …\n├── …</code></pre>\n\n<h3>Prefs</h3>\n\n<p>Prefs files can contain a <span class=\"caps\">JSON</span> tree or just the preference value as text.</p>\n\n<pre><code>├── …\n├── prefs\n├──── sitename.json\n├──── site_slogan.txt\n├──── …\n├── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> field is:</p>\n\n<ul>\n	<li><code>val</code> (string): preference value;</li>\n</ul>\n\n<p><code>sitename.txt</code> would just contain the preference related value.</p>\n\n<h3>Sections</h3>\n\n<p>Sections files are managed via <span class=\"caps\">JSON</span> files.</p>\n\n<pre><code>├── …\n├── sections\n├──── example.json\n├──── …\n├── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> fields are:</p>\n\n<ul>\n	<li><code>title</code> (string): section title;</li>\n	<li><code>description</code> (string): section meta-description;</li>\n	<li><code>page</code> (string): page in use;</li>\n	<li><code>css</code> (string): styles in use;</li>\n	<li><code>is_default</code> (bool): <code>true</code> makes it the default section;</li>\n	<li><code>in_rss</code> (bool): <code>true</code> includes section articles in feeds;</li>\n	<li><code>on_frontpage</code> (bool): <code>true</code> displays section articles on the frontpage;</li>\n	<li><code>searchable</code> (bool): <code>true</code> includes section articles in search results.</li>\n</ul>\n\n<h3>Styles</h3>\n\n<pre><code>├── …\n├── styles\n├──── default.css\n├──── …\n├── …</code></pre>\n\n<h3>textpacks</h3>\n\n<pre><code>├── …\n├── textpacks\n├──── example.textpack\n├──── …\n├── …</code></pre>\n\n<h3>Variables</h3>\n\n<p>Variables can be defined via <span class=\"caps\">JSON</span> files as plugin prefs and used through the <code>&lt;txp:variable /&gt;</code> tag.</p>\n\n<pre><code>├── …\n├── variables\n├──── example.json\n├──── …</code></pre>\n\n<p>Available <span class=\"caps\">JSON</span> fields are:</p>\n\n<ul>\n	<li><code>value</code> (string): variable value;</li>\n	<li><code>html</code> (string): name of the function to use in the admin (if the <code>/variables</code> is removed). Natively available functions are: <code>text_input</code>, <code>pref_long_text</code>, <code>yesnoradio</code>.</li>\n	<li><code>position</code> (int): variable position.</li>\n</ul>\n\n<h2>Toolshed notice</h2>\n\n<p>This is a toolshed project. Experimental fork from <a href=\"https://github.com/gocom/oui_flat\">rah_flat</a> which is not part of the main supported product line of &#8220;Rah&#8220;https://github.com/gocom. Not yet at least. Please use at your own risk.</p>\n\n<h2>Changelog</h2>\n\n<h3>Version 0.7.0-beta (oui_flat from upcoming rah_flat-v0.4) &#8211; 2017/06/25</h3>\n\n<ul>\n	<li>Changed: requires Textpatern 4.6+</li>\n	<li>Added: Do not delete pages or styles that are used by a section.</li>\n	<li>Changed: variables use <code>rah_flat_variables</code> event only and are all visible or hidden as for other contents;</li>\n	<li>Changed: preferences stay always visible.</li>\n	<li>Improved: Uses original Textpacks format;</li>\n</ul>\n\n<h3>Version 0.6.0 (oui_flat) &#8211; 2017/03/14</h3>\n\n<ul>\n	<li>Changed: Back to <code>oui_</code> plugin author prefix.</li>\n	<li>Changed: Lighten compiled code by removing duplicated license infos.</li>\n</ul>\n\n<h3>Version 0.5.0 (oui_flat) &#8211; 2016/08/26</h3>\n\n<ul>\n	<li>Changed: Forms are stored by types in subfolders and don&#8217;t need prefixes anymore.</li>\n	<li>Added: Custom form types are changed to &#8216;misc&#8217; when the plugin is disable to avoid an error in the Forms tab.</li>\n	<li>Changed: Preferences update affects values only.</li>\n	<li>Added: Prefs are hidden in the admin if set via flat files and get back to visible when the plugin is disabled.</li>\n	<li>Added: Custom prefs (in the variables folder) accept more paramters in .json files.</li>\n	<li>Changed: Custom prefs (in the variables folder) now have a oui_flat_var_ prefix added to their name.</li>\n</ul>\n\n<h3>Version 0.4.0 (oui_flat) &#8211; 2015/11/29</h3>\n\n<ul>\n	<li>Added: Custom preferences can be created and use as Txp variables.</li>\n	<li>Changed: Forms naming convention is now <code>type.name.txp</code>.</li>\n</ul>\n\n<h3>Version 0.3.0 (rah_flat) &#8211; 2014/03/28</h3>\n\n<ul>\n	<li>Added: Drop access to a admin-side panel only if the specific content-type is active and has a directory set up.</li>\n	<li>Added: Invokable <code>oui_flat.import</code> callback event.</li>\n	<li>Added: Sections and preferences get their names from the filename.</li>\n	<li>Added: Preferences are always just updated, no strands are created.</li>\n	<li>Added: Preference fields that are not specified in a file are kept as-is in the database.</li>\n	<li>Added: French translation by <a href=\"https://github.com/cara-tm\">Patrick Lefevre</a>.</li>\n	<li>Changed: Renamed confusing <code>Oui_Flat_Import_Template</code> interface to <code>Oui_Flat_Import_ImportInterface</code>.</li>\n</ul>\n\n<h3>Version 0.2.0 (rah_flat) &#8211; 2014/03/19</h3>\n\n<ul>\n	<li>Reworked.</li>\n</ul>\n\n<h3>Version 0.1.0 (rah_flat) &#8211; 2013/05/07</h3>\n\n<ul>\n	<li>Initial release.</li>\n</ul>', '\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Template iterator.\n *\n * This class iterates over template files.\n *\n * <code>\n * $templates = new RecursiveIteratorIterator(\n *     new Oui_Flat_FilterIterator(\n *         new Oui_Flat_TemplateIterator(\'/path/to/dir\')\n *     )\n * );\n * foreach ($templates as $template) {\n *     $template->getTemplateName();\n *     $template->getTemplateContents();\n * }\n * </code>\n *\n * @see \\RecursiveDirectoryIterator\n */\n\nclass Oui_Flat_TemplateIterator extends RecursiveDirectoryIterator\n{\n    /**\n     * Template name pattern.\n     *\n     * This regular expression pattern is used to\n     * validate template filenames.\n     *\n     * @var string\n     */\n\n    protected $templateNamePattern = \'/[a-z][a-z0-9_\\-\\.]{0,63}\\.[a-z0-9]+/i\';\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function __construct($path, $flags = null)\n    {\n        if ($flags === null) {\n            $flags = FilesystemIterator::FOLLOW_SYMLINKS |\n                FilesystemIterator::CURRENT_AS_SELF |\n                FilesystemIterator::SKIP_DOTS;\n        }\n\n        parent::__construct($path, $flags);\n    }\n\n    /**\n     * Gets the template contents.\n     *\n     * @throws Exception\n     */\n\n    public function getTemplateContents()\n    {\n        if (($contents = file_get_contents($this->getPathname())) !== false) {\n            return preg_replace(\'/[\\r|\\n]+$/\', \'\', $contents);\n        }\n\n        throw new Exception(\'Unable to read.\');\n    }\n\n    /**\n     * Gets JSON file contents as an object.\n     *\n     * @return stdClass\n     * @throws Exception\n     */\n\n    public function getTemplateJSONContents()\n    {\n        if (($file = $this->getTemplateContents()) && $file = @json_decode($file)) {\n            return $file;\n        }\n\n        throw new Exception(\'Invalid JSON file.\');\n    }\n\n    /**\n     * Gets the template name.\n     *\n     * @return string\n     */\n\n    public function getTemplateName()\n    {\n        return pathinfo($this->getFilename(), PATHINFO_FILENAME);\n    }\n\n    /**\n     * Validates a template file name and stats.\n     *\n     * Template file must be a regular file or a symbolic link,\n     * readable and the name must be fewer than 65 characters long,\n     * start with an ASCII character (A-z), followed by A-z, 0-9, -, _ and\n     * and end to a file extension.\n     *\n     * Valid template name would include:\n     *\n     * <code>\n     * sitename.json\n     * default.article.txp\n     * form.name.misc.txp\n     * default.txp\n     * error_default.html\n     * </code>\n     *\n     * But the following would be invalid:\n     *\n     * <code>\n     * .sitename\n     * _form.misc.txp\n     * 0template.html\n     * </code>\n     *\n     * @return bool TRUE if the name is valid\n     */\n\n    public function isValidTemplate()\n    {\n        if (!$this->isDot() && $this->isReadable() && ($this->isFile() || $this->isLink())) {\n            return (bool) preg_match($this->templateNamePattern, $this->getFilename());\n        }\n\n        return false;\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Filters template iterator results.\n *\n * This class iterates over template files.\n *\n * <code>\n * $filteredTemplates = new Oui_Flat_FilterIterator(\n *    Oui_Flat_TemplateIterator(\'/path/to/dir\')\n * );\n * </code>\n *\n * @see \\RecursiveFilterIterator\n */\n\nclass Oui_Flat_FilterIterator extends RecursiveFilterIterator\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function accept() {\n        return $this->isDir() || $this->isValidTemplate();\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Form partial iterator.\n *\n * @see Oui_Flat_TemplateIterator\n */\n\nclass Oui_Flat_FormIterator extends Oui_Flat_TemplateIterator\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateName()\n    {\n        $filename = pathinfo($this->getFilename(), PATHINFO_FILENAME);\n        \n        return pathinfo($filename, PATHINFO_EXTENSION) ?: $filename;\n    }\n\n    /**\n     * Gets the template type.\n     *\n     * If the file is named as:\n     *\n     * <code>\n     * misc.filename.ext\n     * </code>\n     *\n     * The \'misc\' would be used as the type. Alternatively, the type can be\n     * left out from the name and set with the parent directory name:\n     *\n     * <code>\n     * misc/filenname.ext\n     * </code>\n     *\n     * The \'misc\' would be used as the type.\n     *\n     * If no type is specified at all, or it\'s not a valid type recognized by\n     * Textpattern, it defaults to \'misc\'.\n     *\n     * @return string\n     */\n\n    public function getTemplateType()\n    {\n        $types = get_form_types();\n        $types = array_keys($types);\n        $type = pathinfo(pathinfo($this->getFilename(), PATHINFO_FILENAME), PATHINFO_FILENAME);\n\n        if (in_array($type, $types)) {\n            return $type;\n        }\n\n        $type = basename($this->getPath());\n\n        if (in_array($type, $types)) {\n            return $type;\n        }\n\n        return \'misc\';\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Interface for import definitions.\n *\n * <code>\n * class Abc_Import_Definition implements Oui_Flat_Import_ImportInterface\n * {\n * }\n * </code>\n */\n\ninterface Oui_Flat_Import_ImportInterface\n{\n    /**\n     * Constructor.\n     *\n     * Registers the importer definition when the class is initialized.\n     * The used event should be considered private and should not\n     * be accessed manually.\n     *\n     * <code>\n     * new Oui_Flat_Import_Forms(\'forms\');\n     * </code>\n     *\n     * @param string $directory The directory hosting the templates\n     */\n\n    public function __construct($directory);\n\n    /**\n     * Gets a new template iterator instance.\n     *\n     * @param string $directory The directory hosting the templates\n     * @return \\RecursiveIteratorIterator\n     */\n\n    public function getTemplateIterator($directory);\n\n    /**\n     * Initializes the importer.\n     *\n     * This method is called when the import event is executed.\n     *\n     * @throws Exception\n     */\n\n    public function init();\n\n    /**\n     * Drops permissions to the panel.\n     *\n     * This makes sure the template items are not\n     * modified through the GUI.\n     *\n     * This method only affects the admin-side interface and doesn\'t\n     * truly reset permissions application wide. This is to\n     * avoid unneccessary I/O activity that would otherwise have to\n     * take place.\n     */\n\n    public function dropPermissions();\n\n    /**\n     * Drops removed template rows from the database.\n     *\n     * For most impletations this method removes all rows that aren\'t\n     * present in the flat directory, but for some it might\n     * not do anything.\n     *\n     * @param Iterator $templates\n     * @throws Exception\n     */\n\n    public function dropRemoved(Iterator $templates);\n\n    /**\n     * Gets the panel name.\n     *\n     * The panel name is used to recognize the content-types\n     * registered event and remove access to it.\n     *\n     * @return string\n     */\n\n    public function getPanelName();\n\n    /**\n     * Gets database table name.\n     *\n     * @return string\n     */\n\n    public function getTableName();\n\n    /**\n     * Gets essential items that should not be removed from the database.\n     *\n     * This method needs to either return an array, or an iterator\n     * that implements toString().\n     *\n     * @return array|Iterator\n     * @since 0.4.0\n     */\n\n    public function getEssentials();\n\n    /**\n     * Imports the template file.\n     *\n     * This method executes the SQL statement to import\n     * the template file.\n     *\n     * @param  Oui_Flat_TemplateIterator $file The template file\n     * @throws Exception\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file);\n\n    /**\n     * Gets an array of database columns in the table.\n     *\n     * @return array\n     */\n\n    public function getTableColumns();\n\n    /**\n     * Gets a path to the directory hosting the flat files.\n     *\n     * @return string|bool The path, or FALSE\n     */\n\n    public function getDirectoryPath();\n\n    /**\n     * Whether the content-type is enabled and has a directory.\n     *\n     * @return bool TRUE if its enabled, FALSE otherwise\n     */\n\n    public function isEnabled();\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Base class for import definitions.\n *\n * To create a new importable template object, extend\n * this class or its theriatives.\n *\n * For instance the following would create a new import\n * definition using the Oui_Flat_Import_Pages as the\n * base:\n *\n * <code>\n * class Abc_My_Import_Definition extends Oui_Flat_Import_Pages\n * {\n *     public function getPanelName()\n *     {\n *         return \'abc_mypanel\';\n *     }\n *     public function getTableName()\n *     {\n *         return \'abc_mytable\';\n *     }\n * }\n * </code>\n *\n * It would automatically disable access to \'abc_mypanel\' admin-side panel\n * and import items to \'abc_mytable\' database table, consisting of \'name\'\n * and \'user_html\' columns, as with page templates and its txp_page table.\n *\n * To initialize the import, just create a new instance of the class. Pass\n * constructor the directory name the templates reside in the configured\n * templates directory.\n *\n * <code>\n * new Abc_My_Import_Definition(\'abc_mydirectory\');\n * </code>\n */\n\nabstract class Oui_Flat_Import_Base implements Oui_Flat_Import_ImportInterface\n{\n    /**\n     * The directory.\n     *\n     * @var string\n     */\n\n    protected $directory;\n\n    /**\n     * An array of database table columns.\n     *\n     * @var array\n     */\n\n    private $columns = array();\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function __construct($directory)\n    {\n        $this->directory = $directory;\n        register_callback(array($this, \'init\'), \'oui_flat.import_to_database\');\n        $this->dropPermissions();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateIterator($directory)\n    {\n        return new RecursiveIteratorIterator(\n            new Oui_Flat_FilterIterator(\n                new Oui_Flat_TemplateIterator($directory)\n            )\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getDirectoryPath()\n    {\n        if ($this->directory && ($directory = get_pref(\'oui_flat_path\'))) {\n            $directory = txpath . \'/\' . $directory . \'/\' . $this->directory;\n            return $directory;\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function isEnabled()\n    {\n        if ($directory = $this->getDirectoryPath()) {\n            return file_exists($directory) && is_dir($directory) && is_readable($directory);\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function init()\n    {\n        if ($this->isEnabled()) {\n            $templates = $this->getTemplateIterator($this->getDirectoryPath());\n\n            foreach ($templates as $template) {\n                if ($this->importTemplate($template) === false) {\n                    throw new Exception(\'Unable to import \' . $template->getTemplateName());\n                }\n            }\n\n            $this->dropRemoved($templates);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropPermissions()\n    {\n        if (txpinterface === \'admin\' && $this->isEnabled()) {\n            unset($GLOBALS[\'txp_permissions\'][$this->getPanelName()]);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return array();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableColumns()\n    {\n        if (!$this->columns) {\n            $this->columns = doArray((array) @getThings(\'describe \' . safe_pfx($this->getTableName())), \'strtolower\');\n        }\n\n        return $this->columns;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n        $name = array();\n\n        foreach ($templates as $template) {\n            $name[] = \"\'\" . doSlash($template->getTemplateName()) . \"\'\";\n        }\n\n        foreach ($this->getEssentials() as $template) {\n            $name[] = \"\'\" . doSlash((string) $template) . \"\'\";\n        }\n\n        if ($name) {\n            safe_delete($this->getTableName(), \'name not in (\' . implode(\',\', $name) . \')\');\n        } else {\n            safe_delete($this->getTableName(), \'1 = 1\');\n        }\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports page templates.\n */\n\nclass Oui_Flat_Import_Pages extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'page\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_page\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return safe_column(\'page\', \'txp_section\', \'1 = 1\');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"user_html = \'\".doSlash($file->getTemplateContents()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports form partials.\n */\n\nclass Oui_Flat_Import_Forms extends Oui_Flat_Import_Pages\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'form\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_form\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return get_essential_forms();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateIterator($directory)\n    {\n        return new RecursiveIteratorIterator(\n            new Oui_Flat_FilterIterator(\n                new Oui_Flat_FormIterator($directory)\n            )\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"Form = \'\".doSlash($file->getTemplateContents()).\"\',\n            type = \'\".doSlash($file->getTemplateType()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports sections.\n */\n\nclass Oui_Flat_Import_Sections extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'section\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_section\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        $sql = array();\n        $where = \"name = \'\".doSlash($file->getTemplateName()).\"\'\";\n\n        foreach ($file->getTemplateJSONContents() as $key => $value) {\n            if ($key !== \'name\' && in_array(strtolower((string) $key), $this->getTableColumns(), true)) {\n                $sql[] = $this->formatStatement($key, $value);\n            }\n        }\n\n        return $sql && safe_upsert($this->getTableName(), implode(\',\', $sql), $where);\n    }\n\n    /**\n     * Formats a SQL insert statement value.\n     *\n     * @param  string $field The field\n     * @param  string $value The value\n     * @return string\n     */\n\n    protected function formatStatement($field, $value)\n    {\n        if ($value === null) {\n            return \"`{$field}` = NULL\";\n        }\n\n        if (is_bool($value) || is_int($value)) {\n            return \"`{$field}` = \".intval($value);\n        }\n\n        if (is_array($value)) {\n            $value = implode(\', \', $value);\n        }\n\n        return \"`{$field}` = \'\".doSlash((string) $value).\"\'\";\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports preferences.\n */\n\nclass Oui_Flat_Import_Prefs extends Oui_Flat_Import_Sections\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_prefs\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        $sql = array();\n        $where = \"name = \'\".doSlash($file->getTemplateName()).\"\' and user_name = \'\'\";\n\n        if ($file->getExtension() === \'json\') {\n            foreach ($file->getTemplateJSONContents() as $key => $value) {\n                if (in_array(strtolower((string) $key), $this->getTableColumns(), true)) {\n                    $sql[] = $this->formatStatement($key, $value);\n                }\n            }\n        } else {\n            $sql[] = \'val = \"\' . doSlash($file->getTemplateContents()) . \'\"\';\n        }\n\n        return $sql && safe_update($this->getTableName(), implode(\',\', $sql), $where);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $template)\n    {\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports variables.\n */\n\nclass Oui_Flat_Import_Variables extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'prefs.oui_flat_variables\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_prefs\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        extract(lAtts(array(\n            \'value\'      => \'\',\n            \'type\'       => \'PREF_PLUGIN\',\n            \'html\'       => \'text_input\',\n            \'position\'   => \'\',\n            \'is_private\' => false,\n        ), $file->getTemplateJSONContents(), false));\n\n        $name = \'oui_flat_variable_\' . $file->getTemplateName();\n\n        set_pref($name, $value, \'oui_flat_variables\', constant($type), $html, $position, $is_private);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n        $sql = $names = array();\n\n        foreach ($templates as $template) {\n            $names[] = \'oui_flat_variable_\' . $template->getTemplateName();\n        }\n\n        $sql[] = \"event = \'oui_flat_variables\'\";\n\n        if ($names) {\n            $sql[] = \"name not in(\" . implode(\',\', quote_list($names)) . \")\";\n        }\n\n        safe_delete($this->getTableName(), implode(\' and \', $sql));\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports template styles.\n */\n\nclass Oui_Flat_Import_Styles extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'css\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_css\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return safe_column(\'css\', \'txp_section\', \'1 = 1\');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"css = \'\".doSlash($file->getTemplateContents()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports textpacks.\n */\n\nclass Oui_Flat_Import_Textpacks extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        install_textpack(\'#@owner oui_flat\' . n . $file->getTemplateContents());\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Main plugin class.\n *\n * @internal\n */\n\nclass Oui_Flat\n{\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        global $event;\n\n        add_privs(\'prefs.oui_flat\', \'1\');\n        add_privs(\'prefs.oui_flat_variables\', \'1\');\n        register_callback(array($this, \'install\'), \'plugin_lifecycle.oui_flat\', \'installed\');\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.oui_flat\', \'deleted\');\n\n        if (get_pref(\'oui_flat_path\')) {\n            new Oui_Flat_Import_Prefs(\'prefs\');\n            new Oui_Flat_Import_Variables(\'variables\');\n            new Oui_Flat_Import_Sections(\'sections\');\n            new Oui_Flat_Import_Pages(\'pages\');\n            new Oui_Flat_Import_Forms(\'forms\');\n            new Oui_Flat_Import_Styles(\'styles\');\n            new Oui_Flat_Import_Textpacks(\'textpacks\');\n\n            register_callback(array($this, \'endpoint\'), \'textpattern\');\n            register_callback(array($this, \'initWrite\'), \'oui_flat.import\');\n\n            if (in_list(get_pref(\'production_status\'), get_pref(\'oui_flat_status\')) && $event !== \'plugin\') {\n                register_callback(array($this, \'callbackHandler\'), \'textpattern\');\n                register_callback(array($this, \'callbackHandler\'), \'admin_side\', \'body_end\');\n            }\n            \n            register_callback(array($this, \'setVariables\'), \'textpattern\');\n        }\n    }\n\n    /**\n     * Installer.\n     */\n\n    public function install()\n    {\n        $position = 250;\n\n        $options = array(\n            \'oui_flat_path\'   => array(\'text_input\', \'../../src/templates\'),\n            \'oui_flat_key\'    => array(\'text_input\', md5(uniqid(mt_rand(), true))),\n            \'oui_flat_status\' => array(\'oui_flat_status\', \'debug, testing\'),\n        );\n\n        foreach ($options as $name => $val) {\n            if (get_pref($name, false) === false) {\n                set_pref($name, $val[1], \'oui_flat\', PREF_PLUGIN, $val[0], $position);\n            }\n\n            $position++;\n        }\n    }\n\n    /**\n     * Uninstaller.\n     */\n\n    public function uninstall()\n    {\n        remove_pref(null, \'oui_flat\');\n        remove_pref(null, \'oui_flat_variables\');\n    }\n\n    /**\n     * Initializes the importers.\n     */\n\n    public function initWrite()\n    {\n        callback_event(\'oui_flat.import_to_database\');\n    }\n\n    /**\n     * Initializes template variables.\n     */\n\n    public function setVariables()\n    {\n        global $prefs, $variable;\n\n        $prefix = \'oui_flat_variable_\';\n        $offset = strlen($prefix);\n\n        foreach ($prefs as $name => $value) {\n            if (strpos($name, $prefix) === 0) {\n                $variable[substr($name, $offset)] = $value;\n            }\n        }\n    }\n\n    /**\n     * Registered callback handler.\n     */\n\n    public function callbackHandler()\n    {\n        try {\n            callback_event(\'oui_flat.import\');\n        } catch (Exception $e) {\n            trigger_error($e->getMessage());\n        }\n    }\n\n    /**\n     * Import endpoint.\n     */\n\n    public function endpoint()\n    {\n        if (!get_pref(\'oui_flat_key\') || get_pref(\'oui_flat_key\') !== gps(\'oui_flat_key\')) {\n            return;\n        }\n\n        header(\'Content-Type: application/json; charset=utf-8\');\n\n        try {\n            callback_event(\'oui_flat.import\');\n        } catch (Exception $e) {\n            txp_status_header(\'500 Internal Server Error\');\n\n            die(json_encode(array(\n                \'success\' => false,\n                \'error\'   => $e->getMessage(),\n            )));\n        }\n\n        update_lastmod();\n\n        die(json_encode(array(\n            \'success\' => true,\n        )));\n    }\n}\n\n/**\n * Set oui_flat_status pref\n */\nfunction oui_flat_status($name, $val)\n{\n     $vals = array(\n        \'debug\'          => gTxt(\'production_debug\'),\n        \'testing\'        => gTxt(\'production_test\'),\n        \'debug, testing\' => gTxt(\'production_debug\').\', \'.lcfirst(gTxt(\'production_test\')),\n    );\n    return selectInput($name, $vals, $val, true, \'\', $name);\n}\n\nnew Oui_Flat();\n', '\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Template iterator.\n *\n * This class iterates over template files.\n *\n * <code>\n * $templates = new RecursiveIteratorIterator(\n *     new Oui_Flat_FilterIterator(\n *         new Oui_Flat_TemplateIterator(\'/path/to/dir\')\n *     )\n * );\n * foreach ($templates as $template) {\n *     $template->getTemplateName();\n *     $template->getTemplateContents();\n * }\n * </code>\n *\n * @see \\RecursiveDirectoryIterator\n */\n\nclass Oui_Flat_TemplateIterator extends RecursiveDirectoryIterator\n{\n    /**\n     * Template name pattern.\n     *\n     * This regular expression pattern is used to\n     * validate template filenames.\n     *\n     * @var string\n     */\n\n    protected $templateNamePattern = \'/[a-z][a-z0-9_\\-\\.]{0,63}\\.[a-z0-9]+/i\';\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function __construct($path, $flags = null)\n    {\n        if ($flags === null) {\n            $flags = FilesystemIterator::FOLLOW_SYMLINKS |\n                FilesystemIterator::CURRENT_AS_SELF |\n                FilesystemIterator::SKIP_DOTS;\n        }\n\n        parent::__construct($path, $flags);\n    }\n\n    /**\n     * Gets the template contents.\n     *\n     * @throws Exception\n     */\n\n    public function getTemplateContents()\n    {\n        if (($contents = file_get_contents($this->getPathname())) !== false) {\n            return preg_replace(\'/[\\r|\\n]+$/\', \'\', $contents);\n        }\n\n        throw new Exception(\'Unable to read.\');\n    }\n\n    /**\n     * Gets JSON file contents as an object.\n     *\n     * @return stdClass\n     * @throws Exception\n     */\n\n    public function getTemplateJSONContents()\n    {\n        if (($file = $this->getTemplateContents()) && $file = @json_decode($file)) {\n            return $file;\n        }\n\n        throw new Exception(\'Invalid JSON file.\');\n    }\n\n    /**\n     * Gets the template name.\n     *\n     * @return string\n     */\n\n    public function getTemplateName()\n    {\n        return pathinfo($this->getFilename(), PATHINFO_FILENAME);\n    }\n\n    /**\n     * Validates a template file name and stats.\n     *\n     * Template file must be a regular file or a symbolic link,\n     * readable and the name must be fewer than 65 characters long,\n     * start with an ASCII character (A-z), followed by A-z, 0-9, -, _ and\n     * and end to a file extension.\n     *\n     * Valid template name would include:\n     *\n     * <code>\n     * sitename.json\n     * default.article.txp\n     * form.name.misc.txp\n     * default.txp\n     * error_default.html\n     * </code>\n     *\n     * But the following would be invalid:\n     *\n     * <code>\n     * .sitename\n     * _form.misc.txp\n     * 0template.html\n     * </code>\n     *\n     * @return bool TRUE if the name is valid\n     */\n\n    public function isValidTemplate()\n    {\n        if (!$this->isDot() && $this->isReadable() && ($this->isFile() || $this->isLink())) {\n            return (bool) preg_match($this->templateNamePattern, $this->getFilename());\n        }\n\n        return false;\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Filters template iterator results.\n *\n * This class iterates over template files.\n *\n * <code>\n * $filteredTemplates = new Oui_Flat_FilterIterator(\n *    Oui_Flat_TemplateIterator(\'/path/to/dir\')\n * );\n * </code>\n *\n * @see \\RecursiveFilterIterator\n */\n\nclass Oui_Flat_FilterIterator extends RecursiveFilterIterator\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function accept() {\n        return $this->isDir() || $this->isValidTemplate();\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Form partial iterator.\n *\n * @see Oui_Flat_TemplateIterator\n */\n\nclass Oui_Flat_FormIterator extends Oui_Flat_TemplateIterator\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateName()\n    {\n        $filename = pathinfo($this->getFilename(), PATHINFO_FILENAME);\n        \n        return pathinfo($filename, PATHINFO_EXTENSION) ?: $filename;\n    }\n\n    /**\n     * Gets the template type.\n     *\n     * If the file is named as:\n     *\n     * <code>\n     * misc.filename.ext\n     * </code>\n     *\n     * The \'misc\' would be used as the type. Alternatively, the type can be\n     * left out from the name and set with the parent directory name:\n     *\n     * <code>\n     * misc/filenname.ext\n     * </code>\n     *\n     * The \'misc\' would be used as the type.\n     *\n     * If no type is specified at all, or it\'s not a valid type recognized by\n     * Textpattern, it defaults to \'misc\'.\n     *\n     * @return string\n     */\n\n    public function getTemplateType()\n    {\n        $types = get_form_types();\n        $types = array_keys($types);\n        $type = pathinfo(pathinfo($this->getFilename(), PATHINFO_FILENAME), PATHINFO_FILENAME);\n\n        if (in_array($type, $types)) {\n            return $type;\n        }\n\n        $type = basename($this->getPath());\n\n        if (in_array($type, $types)) {\n            return $type;\n        }\n\n        return \'misc\';\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Interface for import definitions.\n *\n * <code>\n * class Abc_Import_Definition implements Oui_Flat_Import_ImportInterface\n * {\n * }\n * </code>\n */\n\ninterface Oui_Flat_Import_ImportInterface\n{\n    /**\n     * Constructor.\n     *\n     * Registers the importer definition when the class is initialized.\n     * The used event should be considered private and should not\n     * be accessed manually.\n     *\n     * <code>\n     * new Oui_Flat_Import_Forms(\'forms\');\n     * </code>\n     *\n     * @param string $directory The directory hosting the templates\n     */\n\n    public function __construct($directory);\n\n    /**\n     * Gets a new template iterator instance.\n     *\n     * @param string $directory The directory hosting the templates\n     * @return \\RecursiveIteratorIterator\n     */\n\n    public function getTemplateIterator($directory);\n\n    /**\n     * Initializes the importer.\n     *\n     * This method is called when the import event is executed.\n     *\n     * @throws Exception\n     */\n\n    public function init();\n\n    /**\n     * Drops permissions to the panel.\n     *\n     * This makes sure the template items are not\n     * modified through the GUI.\n     *\n     * This method only affects the admin-side interface and doesn\'t\n     * truly reset permissions application wide. This is to\n     * avoid unneccessary I/O activity that would otherwise have to\n     * take place.\n     */\n\n    public function dropPermissions();\n\n    /**\n     * Drops removed template rows from the database.\n     *\n     * For most impletations this method removes all rows that aren\'t\n     * present in the flat directory, but for some it might\n     * not do anything.\n     *\n     * @param Iterator $templates\n     * @throws Exception\n     */\n\n    public function dropRemoved(Iterator $templates);\n\n    /**\n     * Gets the panel name.\n     *\n     * The panel name is used to recognize the content-types\n     * registered event and remove access to it.\n     *\n     * @return string\n     */\n\n    public function getPanelName();\n\n    /**\n     * Gets database table name.\n     *\n     * @return string\n     */\n\n    public function getTableName();\n\n    /**\n     * Gets essential items that should not be removed from the database.\n     *\n     * This method needs to either return an array, or an iterator\n     * that implements toString().\n     *\n     * @return array|Iterator\n     * @since 0.4.0\n     */\n\n    public function getEssentials();\n\n    /**\n     * Imports the template file.\n     *\n     * This method executes the SQL statement to import\n     * the template file.\n     *\n     * @param  Oui_Flat_TemplateIterator $file The template file\n     * @throws Exception\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file);\n\n    /**\n     * Gets an array of database columns in the table.\n     *\n     * @return array\n     */\n\n    public function getTableColumns();\n\n    /**\n     * Gets a path to the directory hosting the flat files.\n     *\n     * @return string|bool The path, or FALSE\n     */\n\n    public function getDirectoryPath();\n\n    /**\n     * Whether the content-type is enabled and has a directory.\n     *\n     * @return bool TRUE if its enabled, FALSE otherwise\n     */\n\n    public function isEnabled();\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Base class for import definitions.\n *\n * To create a new importable template object, extend\n * this class or its theriatives.\n *\n * For instance the following would create a new import\n * definition using the Oui_Flat_Import_Pages as the\n * base:\n *\n * <code>\n * class Abc_My_Import_Definition extends Oui_Flat_Import_Pages\n * {\n *     public function getPanelName()\n *     {\n *         return \'abc_mypanel\';\n *     }\n *     public function getTableName()\n *     {\n *         return \'abc_mytable\';\n *     }\n * }\n * </code>\n *\n * It would automatically disable access to \'abc_mypanel\' admin-side panel\n * and import items to \'abc_mytable\' database table, consisting of \'name\'\n * and \'user_html\' columns, as with page templates and its txp_page table.\n *\n * To initialize the import, just create a new instance of the class. Pass\n * constructor the directory name the templates reside in the configured\n * templates directory.\n *\n * <code>\n * new Abc_My_Import_Definition(\'abc_mydirectory\');\n * </code>\n */\n\nabstract class Oui_Flat_Import_Base implements Oui_Flat_Import_ImportInterface\n{\n    /**\n     * The directory.\n     *\n     * @var string\n     */\n\n    protected $directory;\n\n    /**\n     * An array of database table columns.\n     *\n     * @var array\n     */\n\n    private $columns = array();\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function __construct($directory)\n    {\n        $this->directory = $directory;\n        register_callback(array($this, \'init\'), \'oui_flat.import_to_database\');\n        $this->dropPermissions();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateIterator($directory)\n    {\n        return new RecursiveIteratorIterator(\n            new Oui_Flat_FilterIterator(\n                new Oui_Flat_TemplateIterator($directory)\n            )\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getDirectoryPath()\n    {\n        if ($this->directory && ($directory = get_pref(\'oui_flat_path\'))) {\n            $directory = txpath . \'/\' . $directory . \'/\' . $this->directory;\n            return $directory;\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function isEnabled()\n    {\n        if ($directory = $this->getDirectoryPath()) {\n            return file_exists($directory) && is_dir($directory) && is_readable($directory);\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function init()\n    {\n        if ($this->isEnabled()) {\n            $templates = $this->getTemplateIterator($this->getDirectoryPath());\n\n            foreach ($templates as $template) {\n                if ($this->importTemplate($template) === false) {\n                    throw new Exception(\'Unable to import \' . $template->getTemplateName());\n                }\n            }\n\n            $this->dropRemoved($templates);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropPermissions()\n    {\n        if (txpinterface === \'admin\' && $this->isEnabled()) {\n            unset($GLOBALS[\'txp_permissions\'][$this->getPanelName()]);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return array();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableColumns()\n    {\n        if (!$this->columns) {\n            $this->columns = doArray((array) @getThings(\'describe \' . safe_pfx($this->getTableName())), \'strtolower\');\n        }\n\n        return $this->columns;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n        $name = array();\n\n        foreach ($templates as $template) {\n            $name[] = \"\'\" . doSlash($template->getTemplateName()) . \"\'\";\n        }\n\n        foreach ($this->getEssentials() as $template) {\n            $name[] = \"\'\" . doSlash((string) $template) . \"\'\";\n        }\n\n        if ($name) {\n            safe_delete($this->getTableName(), \'name not in (\' . implode(\',\', $name) . \')\');\n        } else {\n            safe_delete($this->getTableName(), \'1 = 1\');\n        }\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports page templates.\n */\n\nclass Oui_Flat_Import_Pages extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'page\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_page\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return safe_column(\'page\', \'txp_section\', \'1 = 1\');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"user_html = \'\".doSlash($file->getTemplateContents()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports form partials.\n */\n\nclass Oui_Flat_Import_Forms extends Oui_Flat_Import_Pages\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'form\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_form\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return get_essential_forms();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTemplateIterator($directory)\n    {\n        return new RecursiveIteratorIterator(\n            new Oui_Flat_FilterIterator(\n                new Oui_Flat_FormIterator($directory)\n            )\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"Form = \'\".doSlash($file->getTemplateContents()).\"\',\n            type = \'\".doSlash($file->getTemplateType()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports sections.\n */\n\nclass Oui_Flat_Import_Sections extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'section\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_section\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        $sql = array();\n        $where = \"name = \'\".doSlash($file->getTemplateName()).\"\'\";\n\n        foreach ($file->getTemplateJSONContents() as $key => $value) {\n            if ($key !== \'name\' && in_array(strtolower((string) $key), $this->getTableColumns(), true)) {\n                $sql[] = $this->formatStatement($key, $value);\n            }\n        }\n\n        return $sql && safe_upsert($this->getTableName(), implode(\',\', $sql), $where);\n    }\n\n    /**\n     * Formats a SQL insert statement value.\n     *\n     * @param  string $field The field\n     * @param  string $value The value\n     * @return string\n     */\n\n    protected function formatStatement($field, $value)\n    {\n        if ($value === null) {\n            return \"`{$field}` = NULL\";\n        }\n\n        if (is_bool($value) || is_int($value)) {\n            return \"`{$field}` = \".intval($value);\n        }\n\n        if (is_array($value)) {\n            $value = implode(\', \', $value);\n        }\n\n        return \"`{$field}` = \'\".doSlash((string) $value).\"\'\";\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports preferences.\n */\n\nclass Oui_Flat_Import_Prefs extends Oui_Flat_Import_Sections\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_prefs\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        $sql = array();\n        $where = \"name = \'\".doSlash($file->getTemplateName()).\"\' and user_name = \'\'\";\n\n        if ($file->getExtension() === \'json\') {\n            foreach ($file->getTemplateJSONContents() as $key => $value) {\n                if (in_array(strtolower((string) $key), $this->getTableColumns(), true)) {\n                    $sql[] = $this->formatStatement($key, $value);\n                }\n            }\n        } else {\n            $sql[] = \'val = \"\' . doSlash($file->getTemplateContents()) . \'\"\';\n        }\n\n        return $sql && safe_update($this->getTableName(), implode(\',\', $sql), $where);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $template)\n    {\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports variables.\n */\n\nclass Oui_Flat_Import_Variables extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'prefs.oui_flat_variables\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_prefs\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        extract(lAtts(array(\n            \'value\'      => \'\',\n            \'type\'       => \'PREF_PLUGIN\',\n            \'html\'       => \'text_input\',\n            \'position\'   => \'\',\n            \'is_private\' => false,\n        ), $file->getTemplateJSONContents(), false));\n\n        $name = \'oui_flat_variable_\' . $file->getTemplateName();\n\n        set_pref($name, $value, \'oui_flat_variables\', constant($type), $html, $position, $is_private);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n        $sql = $names = array();\n\n        foreach ($templates as $template) {\n            $names[] = \'oui_flat_variable_\' . $template->getTemplateName();\n        }\n\n        $sql[] = \"event = \'oui_flat_variables\'\";\n\n        if ($names) {\n            $sql[] = \"name not in(\" . implode(\',\', quote_list($names)) . \")\";\n        }\n\n        safe_delete($this->getTableName(), implode(\' and \', $sql));\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports template styles.\n */\n\nclass Oui_Flat_Import_Styles extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getPanelName()\n    {\n        return \'css\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getTableName()\n    {\n        return \'txp_css\';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function getEssentials()\n    {\n        return safe_column(\'css\', \'txp_section\', \'1 = 1\');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        safe_upsert(\n            $this->getTableName(),\n            \"css = \'\".doSlash($file->getTemplateContents()).\"\'\",\n            \"name = \'\".doSlash($file->getTemplateName()).\"\'\"\n        );\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Imports textpacks.\n */\n\nclass Oui_Flat_Import_Textpacks extends Oui_Flat_Import_Base\n{\n    /**\n     * {@inheritdoc}\n     */\n\n    public function importTemplate(Oui_Flat_TemplateIterator $file)\n    {\n        install_textpack(\'#@owner oui_flat\' . n . $file->getTemplateContents());\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n\n    public function dropRemoved(Iterator $templates)\n    {\n    }\n}\n\n\n\n/*\n * oui_flat - Flat templates for Textpattern CMS\n * https://github.com/nicolasgraph/oui_flat\n *\n * Copyright (C) 2017 Jukka Svahn\n *\n * This file is part of oui_flat.\n *\n * oui_flat is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation, version 2.\n *\n * oui_flat is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with oui_flat. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * Main plugin class.\n *\n * @internal\n */\n\nclass Oui_Flat\n{\n    /**\n     * Constructor.\n     */\n\n    public function __construct()\n    {\n        global $event;\n\n        add_privs(\'prefs.oui_flat\', \'1\');\n        add_privs(\'prefs.oui_flat_variables\', \'1\');\n        register_callback(array($this, \'install\'), \'plugin_lifecycle.oui_flat\', \'installed\');\n        register_callback(array($this, \'uninstall\'), \'plugin_lifecycle.oui_flat\', \'deleted\');\n\n        if (get_pref(\'oui_flat_path\')) {\n            new Oui_Flat_Import_Prefs(\'prefs\');\n            new Oui_Flat_Import_Variables(\'variables\');\n            new Oui_Flat_Import_Sections(\'sections\');\n            new Oui_Flat_Import_Pages(\'pages\');\n            new Oui_Flat_Import_Forms(\'forms\');\n            new Oui_Flat_Import_Styles(\'styles\');\n            new Oui_Flat_Import_Textpacks(\'textpacks\');\n\n            register_callback(array($this, \'endpoint\'), \'textpattern\');\n            register_callback(array($this, \'initWrite\'), \'oui_flat.import\');\n\n            if (in_list(get_pref(\'production_status\'), get_pref(\'oui_flat_status\')) && $event !== \'plugin\') {\n                register_callback(array($this, \'callbackHandler\'), \'textpattern\');\n                register_callback(array($this, \'callbackHandler\'), \'admin_side\', \'body_end\');\n            }\n            \n            register_callback(array($this, \'setVariables\'), \'textpattern\');\n        }\n    }\n\n    /**\n     * Installer.\n     */\n\n    public function install()\n    {\n        $position = 250;\n\n        $options = array(\n            \'oui_flat_path\'   => array(\'text_input\', \'../../src/templates\'),\n            \'oui_flat_key\'    => array(\'text_input\', md5(uniqid(mt_rand(), true))),\n            \'oui_flat_status\' => array(\'oui_flat_status\', \'debug, testing\'),\n        );\n\n        foreach ($options as $name => $val) {\n            if (get_pref($name, false) === false) {\n                set_pref($name, $val[1], \'oui_flat\', PREF_PLUGIN, $val[0], $position);\n            }\n\n            $position++;\n        }\n    }\n\n    /**\n     * Uninstaller.\n     */\n\n    public function uninstall()\n    {\n        remove_pref(null, \'oui_flat\');\n        remove_pref(null, \'oui_flat_variables\');\n    }\n\n    /**\n     * Initializes the importers.\n     */\n\n    public function initWrite()\n    {\n        callback_event(\'oui_flat.import_to_database\');\n    }\n\n    /**\n     * Initializes template variables.\n     */\n\n    public function setVariables()\n    {\n        global $prefs, $variable;\n\n        $prefix = \'oui_flat_variable_\';\n        $offset = strlen($prefix);\n\n        foreach ($prefs as $name => $value) {\n            if (strpos($name, $prefix) === 0) {\n                $variable[substr($name, $offset)] = $value;\n            }\n        }\n    }\n\n    /**\n     * Registered callback handler.\n     */\n\n    public function callbackHandler()\n    {\n        try {\n            callback_event(\'oui_flat.import\');\n        } catch (Exception $e) {\n            trigger_error($e->getMessage());\n        }\n    }\n\n    /**\n     * Import endpoint.\n     */\n\n    public function endpoint()\n    {\n        if (!get_pref(\'oui_flat_key\') || get_pref(\'oui_flat_key\') !== gps(\'oui_flat_key\')) {\n            return;\n        }\n\n        header(\'Content-Type: application/json; charset=utf-8\');\n\n        try {\n            callback_event(\'oui_flat.import\');\n        } catch (Exception $e) {\n            txp_status_header(\'500 Internal Server Error\');\n\n            die(json_encode(array(\n                \'success\' => false,\n                \'error\'   => $e->getMessage(),\n            )));\n        }\n\n        update_lastmod();\n\n        die(json_encode(array(\n            \'success\' => true,\n        )));\n    }\n}\n\n/**\n * Set oui_flat_status pref\n */\nfunction oui_flat_status($name, $val)\n{\n     $vals = array(\n        \'debug\'          => gTxt(\'production_debug\'),\n        \'testing\'        => gTxt(\'production_test\'),\n        \'debug, testing\' => gTxt(\'production_debug\').\', \'.lcfirst(gTxt(\'production_test\')),\n    );\n    return selectInput($name, $vals, $val, true, \'\', $name);\n}\n\nnew Oui_Flat();\n', 'bc00f48c432dfd1f0f1b9a24cd8e5e7c', '#@admin\noui_flat => Flat template files\noui_flat_key => Security key for the public callback\noui_flat_path => Path to the templates directory\noui_flat_status => Production status for the systematic upload\noui_flat_variables => Template variables\n\n#@admin\n#@language fi-fi\noui_flat => Kiinteät sivupohjatiedostot\noui_flat_key => Julkisen soittopyyntö-osoitteen käyttämä tietoturva-avain\noui_flat_path => Tiedostopolku sivupohja-hakemistoon\noui_flat_status => (Missing translation) Production status for the systematic upload\noui_flat_variables => Mallimuuttujat\n\n#@admin\n#@language fr-fr\noui_flat => Fichiers du modèle\noui_flat_key => Clé sécurisée pour l\'accès public\noui_flat_path => Chemin vers le répertoire modèles\noui_flat_status => Statut(s) de production pour le téléchargement systématique\noui_flat_variables => Variables de modèle\n', '', 1, 5, 2);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_amazonassoc', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wetamazonassoc-build-amazon-associates-links-with-textpattern', '0.3', 'Generates Amazon associate links', '\n	<h3>Amazon associates link</h3>\n\n	<p><strong>wet_amazonassoc</strong> creates the markup to link to a single Amazon merchandise designated by its ASIN. Both the <code>&lt;a&gt;</code> tag and the Ad Impression tracking <code>&lt;img&gt;</code> tag are created.</p>\n\n	<h4>usage:</h4>\n\n<code>&lt;txp:wet_amazonassoc asin=\"0123456789\" associd=\"myassocid\" tld=\"com\" class=\"aClass\"&gt;anchortext&lt;/txp:wet_amazonassoc&gt;</code>\n\n	<h4>required attributes:</h4>\n\n	<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n		</tr>\n		<tr>\n			<td><strong>asin</strong></td>\n			<td><a href=\"http://en.wikipedia.org/wiki/ASIN\">Amazon Standard Identification Number</a></td>\n		</tr>\n	</table>\n\n	<h4>optional attributes:</h4>\n\n	<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n		</tr>\n		<tr>\n			<td><strong>associd</strong></td>\n			<td>Your Amazon Associate ID. Defaults to &#8220;notslippethou-21&#8221;.</td>\n		</tr>\n		<tr>\n			<td><strong>tld</strong></td>\n			<td>Top level domain of the local amazon site you are associated with (e. g. &#8220;com&#8221;, &#8220;co.uk&#8221;, &#8220;at&#8221;). Defaults to &#8220;at&#8221;. The plug-in was tested with &#8220;amazon.com&#8221;, &#8220;amazon.de&#8221;, and &#8220;amazon.at&#8221;</td>\n		</tr>\n		<tr>\n			<td><strong>class</strong></td>\n			<td>CSS class which will be applied to the <code>&lt;a&gt;</code> tag. Defaults to &#8220;wet_amazonassoc&#8221;.</td>\n		</tr>\n	</table>\n\n	<h4>Licence and Disclaimer</h4>\n\n	<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\">Gnu General Public Licence</a>.</p>\n\n\n\n <h4>Find this useful?</h4>\n<p>Show your appreciation and support future development of this plugin by a small donation. Thank you!</p></form>\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\">\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />\n<input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but04.gif\" border=\"0\" name=\"submit\" alt=\"Make payments with PayPal - it\'s fast, free and secure!\" />\n<input type=\"hidden\" name=\"encrypted\" value=\"-----BEGIN PKCS7-----MIIHsQYJKoZIhvcNAQcEoIIHojCCB54CAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAhVyX+PfUOcjjdjaB/k39YahXXmlrM1z8jh5AjMaBH8oEm2phrlI9nEieGLlRhbp/q6bdgS8QkeYeqCpg92FU3Wgl+9R77mw9ITC1OBD/xiIfHGWwHY6rb8nmIdqbhhHQNOXzrSuE+YqAfe563JYi34EDr9hPLoCUGJF5fWh+dWjELMAkGBSsOAwIaBQAwggEtBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBJtqTkE36PqgIIBCLDdv3eQ6WriZipcOdyJJI7Kx74C8UcDPf03VmoEEe5yDYGf5Czfn8sgiIRjF+szjcgLPjfkSxxy5KcCbD6Px3WwbcTxBEjuol7zP4AFpmjUK6wyKcED4uqgXZRu9eDIS8xuNrl1+O3bcpUk/NMvJfpD0zJFiXI4l/5LuFwomkywzI8hWf7cvH0cH509DDjlxLC+JRy/dRHYpGLRzBmBM3KLHiDjBD6AYBf9OvI5zTk+TKOGwqSkN18Im0OO6IHaasGB3xtMYTssSw6eUEmCXN7Y69S1QqWrZ82BtIGHWDo7Ho9xKxFSBj27Z/SMbvZ500j/lnv/wKR8E4clnraPC8JDvmUaIqHjEqCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MTIwODExNDU1NVowIwYJKoZIhvcNAQkEMRYEFApvMc2fZuC+WaNvkES4c9v4IDt7MA0GCSqGSIb3DQEBAQUABIGAFMKWx3/IG4LZN7B45RFK8lqx/7e1WCjQfD56it6KDuIWk/6jxtvQb1QB43DBFcZvziQ+jb21x8RBMnJBWHiAeGt/ZZa0PIhU04tT3VreAiwvHAsoQ9AJ9ab4Na4Bh2OZsVc7q3qlLvXmAMFmNFdYgFOYwGqpcyqyDnKjZKfbAUM=-----END PKCS7-----\"/>', '\n/**\n * register redirection url handler\n */\nif (txpinterface == \'public\')\n{\n	register_callback (\'wet_amazonassoc_redir\', \'pretext\');\n}\n\n/**\n * redirect via status 302 Moved Temporarily\n * - Do not transfer Page Rank to Amazon\n * - Create an entry in your server log\n */\nfunction wet_amazonassoc_redir($event)\n{\n	$associd = gps(\'wet_amazonassoc\');\n	if (!empty($associd))\n	{\n		$tld = gps(\'tld\');\n		$asin = gps(\'asin\');\n\n        if(!empty($asin) && !empty($tld))\n        {\n	        $href = \"http://www.amazon.$tld/exec/obidos/redirect?\" .\n	                \"link_code=ur2&camp=1638&tag=$associd&\" .\n	                \"creative=6742&path=ASIN/$asin\";\n\n			header(\'HTTP/1.1 302 Moved Temporarily\');\n			header(\'Status: 302 Moved Temporarily\');\n			header(\'Location: \'.$href);\n			exit(0);\n        }\n	}\n}\n\n/**\n * txp tag: build tracking web bug and redirect link\n */\nfunction wet_amazonassoc($atts , $anchortext=\'\' )\n{\n	extract (lAtts(array(\n		\'associd\' => \'notslippethou-21\', // customize!\n		\'tld\' => \'at\', // customize!\n		\'asin\' => \'\',\n		\'class\' => __FUNCTION__\n	),$atts));\n\n	if (!empty($asin))\n	{\n		// remap some minor cTLD to the real websites\n		$realtld = array (\'at\' => \'de\', \'ch\' => \'de\');\n		$ctld = (isset($realtld[$tld])) ? $realtld[$tld] : $tld;\n        $here = $_SERVER[\'REQUEST_URI\'];\n        $href = $here.(strchr($here, \'?\') ? \'&amp;\' : \'?\').\"asin=$asin&amp;tld=$ctld&amp;wet_amazonassoc=$associd\";\n        $webbug = \"<img src=\\\"http://www.assoc-amazon.$ctld/e/ir?t=$associd\" .\n                \'&amp;l=ur2&amp;o=3\" width=\"1\" height=\"1\" alt=\"\" \' .\n                \'style=\"border:none !important; margin:0 !important; padding:0 !important;\" />\';\n        return tag($anchortext, \'a\', (isset ($class) ? \" class=\\\"$class\\\"\" : \'\').\" href=\\\"$href\\\"\").$webbug;\n	}\n	else\n        return \'<a href=\"#\" title=\"wet_amazonassoc: missing ASIN attribute\">***\'.$anchortext.\'***</a>\';\n}', '\n/**\n * register redirection url handler\n */\nif (txpinterface == \'public\')\n{\n	register_callback (\'wet_amazonassoc_redir\', \'pretext\');\n}\n\n/**\n * redirect via status 302 Moved Temporarily\n * - Do not transfer Page Rank to Amazon\n * - Create an entry in your server log\n */\nfunction wet_amazonassoc_redir($event)\n{\n	$associd = gps(\'wet_amazonassoc\');\n	if (!empty($associd))\n	{\n		$tld = gps(\'tld\');\n		$asin = gps(\'asin\');\n\n        if(!empty($asin) && !empty($tld))\n        {\n	        $href = \"http://www.amazon.$tld/exec/obidos/redirect?\" .\n	                \"link_code=ur2&camp=1638&tag=$associd&\" .\n	                \"creative=6742&path=ASIN/$asin\";\n\n			header(\'HTTP/1.1 302 Moved Temporarily\');\n			header(\'Status: 302 Moved Temporarily\');\n			header(\'Location: \'.$href);\n			exit(0);\n        }\n	}\n}\n\n/**\n * txp tag: build tracking web bug and redirect link\n */\nfunction wet_amazonassoc($atts , $anchortext=\'\' )\n{\n	extract (lAtts(array(\n		\'associd\' => \'notslippethou-21\', // customize!\n		\'tld\' => \'at\', // customize!\n		\'asin\' => \'\',\n		\'class\' => __FUNCTION__\n	),$atts));\n\n	if (!empty($asin))\n	{\n		// remap some minor cTLD to the real websites\n		$realtld = array (\'at\' => \'de\', \'ch\' => \'de\');\n		$ctld = (isset($realtld[$tld])) ? $realtld[$tld] : $tld;\n        $here = $_SERVER[\'REQUEST_URI\'];\n        $href = $here.(strchr($here, \'?\') ? \'&amp;\' : \'?\').\"asin=$asin&amp;tld=$ctld&amp;wet_amazonassoc=$associd\";\n        $webbug = \"<img src=\\\"http://www.assoc-amazon.$ctld/e/ir?t=$associd\" .\n                \'&amp;l=ur2&amp;o=3\" width=\"1\" height=\"1\" alt=\"\" \' .\n                \'style=\"border:none !important; margin:0 !important; padding:0 !important;\" />\';\n        return tag($anchortext, \'a\', (isset ($class) ? \" class=\\\"$class\\\"\" : \'\').\" href=\\\"$href\\\"\").$webbug;\n	}\n	else\n        return \'<a href=\"#\" title=\"wet_amazonassoc: missing ASIN attribute\">***\'.$anchortext.\'***</a>\';\n}', '8207e3468b2799ade62547a3ea1fb474', '', '', 0, 5, 0),
('wet_article_thumb', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wetarticlethumb-thumbnail-tag-for-textpattern', '0.4', 'Displays the thumbnail for an article\'s image with an optional link to the article.', '<h3>Article image thumbnail plug-in</h3>\n\n<p><strong>wet_article_thumb</strong> displays the thumbnail which belongs to an article&#8217;s image with an optional link to the corresponding article. More than one article image may be specified by adding all article image ids to a comma separated list. In that case, the very first image&#8217;s thumbnail will be used.</p>\n\n<h4>usage:</h4>\n\n<p><code>&lt;txp:wet_article_thumb /&gt;</code></p>\n\n<h4>required attributes:</h4>\n\n<p>None.</p>\n\n<h4>optional attributes:</h4>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n		</tr>\n		<tr>\n			<td><strong>class</strong> </td>\n			<td> <span class=\"caps\">CSS</span> class which will be applied to the <code>&lt;img&gt;</code> tag. Defaults to “wet_article_thumb”.</td>\n		</tr>\n		<tr>\n			<td><strong>style</strong> </td>\n			<td> <code>style</code> attribute which will be applied to the <code>&lt;img&gt;</code> tag. Default is empty. </td>\n		</tr>\n		<tr>\n			<td><strong>align</strong> </td>\n			<td> <code>align</code> attribute which will be applied to the <code>&lt;img&gt;</code> tag. Default is empty. </td>\n		</tr>\n		<tr>\n			<td><strong>link</strong> </td>\n			<td> Link to article. Defaults to “1”. Set to “0” to disable the thumbnail&#8217;s link to the article.</td>\n		</tr>\n		<tr>\n			<td><strong>rel</strong> </td>\n			<td> <code>rel</code> attribute appplied to the article link. Defaults to “bookmark”, as microformats are hip these days.</td>\n		</tr>\n		<tr>\n			<td><strong>link_class</strong> </td>\n			<td> <span class=\"caps\">CSS</span> class which will be applied to the article permlink. Default is empty. </td>\n		</tr>\n</table>\n\n<h4>Licence</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nfunction wet_article_thumb($atts)\n{\n	global $thisarticle,$img_dir,$path_to_site;\n	extract(lAtts(array(\n		\'style\' => \'\',\n		\'align\' => \'\',\n		\'link\' => \'1\',\n		\'class\' => __FUNCTION__,\n		\'rel\' => \'bookmark\',\n		\'link_class\' => \'\'\n	),$atts));\n\n	$images = ($thisarticle[\'article_image\']) ? $thisarticle[\'article_image\'] : \'\';\n	$title = (function_exists(\"escape_title\")?escape_title($thisarticle[\'title\']):htmlentities($thisarticle[\'title\']));\n\n	$images = explode(\',\', $images);\n	$theimage = $images[0];\n\n	if (is_numeric($theimage)) {\n		$rs = safe_row(\'*\',\'txp_image\',\"id=\'$theimage\'\");\n		if ($rs) {\n			extract($rs);\n			$image_url = hu.$img_dir.\'/\'.$id.\'t\'.$ext;\n			$image_file = $path_to_site.DS.$img_dir.DS.$id.\'t\'.$ext;\n			if (file_exists($image_file)) {\n				$imgsize = sizeImage($image_file);\n				$out = array(\n					\'<img\',\n					\'src=\"\'.$image_url.\'\"\',\n					\'alt=\"\'.$title.\'\"\',\n					\'title=\"\'.$title.\'\"\',\n					(!empty($style)) ? \'style=\"\'.$style.\'\"\' : \'\',\n					(!empty($align)) ? \'align=\"\'.$align.\'\"\' : \'\',\n					(!empty($class)) ? \'class=\"\'.$class.\'\"\' : \'\',\n					$imgsize,\n					\' />\'\n					);\n				if($link && isset($thisarticle[\'thisid\'])) {\n					if(!empty($rel)) {\n						$rel = \' rel=\"\'.$rel.\'\"\';\n					}\n					if(!empty($link_class)) {\n						$link_class = \' class=\"\'.$link_class.\'\"\';\n					}\n					$title = \' title=\"\'.$title.\'\"\';\n					$href = \' href=\"\'.permlinkurl_id($thisarticle[\'thisid\']).\'\"\';\n					return tag(join(\' \', $out), \'a\', $href.$title.$rel.$link_class);\n				}\n				return join(\' \',$out);\n			}\n		}\n	}\n	else return \'\'; // die silently in lack of an article image.\n}\n\n', '\nfunction wet_article_thumb($atts)\n{\n	global $thisarticle,$img_dir,$path_to_site;\n	extract(lAtts(array(\n		\'style\' => \'\',\n		\'align\' => \'\',\n		\'link\' => \'1\',\n		\'class\' => __FUNCTION__,\n		\'rel\' => \'bookmark\',\n		\'link_class\' => \'\'\n	),$atts));\n\n	$images = ($thisarticle[\'article_image\']) ? $thisarticle[\'article_image\'] : \'\';\n	$title = (function_exists(\"escape_title\")?escape_title($thisarticle[\'title\']):htmlentities($thisarticle[\'title\']));\n\n	$images = explode(\',\', $images);\n	$theimage = $images[0];\n\n	if (is_numeric($theimage)) {\n		$rs = safe_row(\'*\',\'txp_image\',\"id=\'$theimage\'\");\n		if ($rs) {\n			extract($rs);\n			$image_url = hu.$img_dir.\'/\'.$id.\'t\'.$ext;\n			$image_file = $path_to_site.DS.$img_dir.DS.$id.\'t\'.$ext;\n			if (file_exists($image_file)) {\n				$imgsize = sizeImage($image_file);\n				$out = array(\n					\'<img\',\n					\'src=\"\'.$image_url.\'\"\',\n					\'alt=\"\'.$title.\'\"\',\n					\'title=\"\'.$title.\'\"\',\n					(!empty($style)) ? \'style=\"\'.$style.\'\"\' : \'\',\n					(!empty($align)) ? \'align=\"\'.$align.\'\"\' : \'\',\n					(!empty($class)) ? \'class=\"\'.$class.\'\"\' : \'\',\n					$imgsize,\n					\' />\'\n					);\n				if($link && isset($thisarticle[\'thisid\'])) {\n					if(!empty($rel)) {\n						$rel = \' rel=\"\'.$rel.\'\"\';\n					}\n					if(!empty($link_class)) {\n						$link_class = \' class=\"\'.$link_class.\'\"\';\n					}\n					$title = \' title=\"\'.$title.\'\"\';\n					$href = \' href=\"\'.permlinkurl_id($thisarticle[\'thisid\']).\'\"\';\n					return tag(join(\' \', $out), \'a\', $href.$title.$rel.$link_class);\n				}\n				return join(\' \',$out);\n			}\n		}\n	}\n	else return \'\'; // die silently in lack of an article image.\n}\n\n', 'd7ffbee9e53fb15a02e1ca57f03c0260', '', '', 0, 5, 0),
('wet_if_page', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/kategorie/textpattern', '0.1', 'Conditionally tests for the current page number being member of a list.', '\n	<h3>wet_if_page: tests for listing page number</h3>\n\n	<p><strong>wet_if_page</strong> is a conditional tag. In article list mode, it takes a comma separated list of page numbers and compares it to the current page. The contained markup will be rendered when conditions are met, otherwise the &#8220;else&#8221; part is evaluated. <strong>wet_if_page</strong> works in articles, forms and page templates.</p>\n\n	<h4>usage:</h4>\n\n<code>&lt;txp:wet_if_page page=\"1,8,15,47\"&gt;</code><br />\n<code> condition met</code><br />\n<code>&lt;txp:else /&gt;</code><br />\n<code> condition not met</code><br />\n<code>&lt;/txp:wet_if_page&gt;</code>\n\n	<p>A typical use case is testing for the presence of the very first page in a sequence of article list pages to exclude the presentation of sticky article on pages 2 ff:</p>\n\n<code>&lt;txp:wet_if_page&gt;</code><br />\n<code> &lt;txp:article status=\"sticky\" /&gt;</code><br />\n<code>&lt;/txp:wet_if_page&gt;</code>\n\n	<p>NB: <code>wet_if_page</code> defaults to checking for the first page.</p>\n\n	<h4>required attributes:</h4>\n\n	<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>page</strong> </td>\n			<td> Comma separated list of page numbers </td>\n			<td> &#8220;1&#8221; </td>\n		</tr>\n	</table>\n\n	<h4>optional attributes:</h4>\n\n	<p>None.</p>\n\n	<h4>Licence</h4>\n\n	<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\">Gnu General Public Licence</a>.</p>\n\n\n\n <h4>Find this useful?</h4>\n<p>Show your appreciation and support future development of this plugin by a small donation. Thank you!</p></form>\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\">\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />\n<input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but04.gif\" border=\"0\" name=\"submit\" alt=\"Make payments with PayPal - it\'s fast, free and secure!\" />\n<input type=\"hidden\" name=\"encrypted\" value=\"-----BEGIN PKCS7-----MIIHsQYJKoZIhvcNAQcEoIIHojCCB54CAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAhVyX+PfUOcjjdjaB/k39YahXXmlrM1z8jh5AjMaBH8oEm2phrlI9nEieGLlRhbp/q6bdgS8QkeYeqCpg92FU3Wgl+9R77mw9ITC1OBD/xiIfHGWwHY6rb8nmIdqbhhHQNOXzrSuE+YqAfe563JYi34EDr9hPLoCUGJF5fWh+dWjELMAkGBSsOAwIaBQAwggEtBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBJtqTkE36PqgIIBCLDdv3eQ6WriZipcOdyJJI7Kx74C8UcDPf03VmoEEe5yDYGf5Czfn8sgiIRjF+szjcgLPjfkSxxy5KcCbD6Px3WwbcTxBEjuol7zP4AFpmjUK6wyKcED4uqgXZRu9eDIS8xuNrl1+O3bcpUk/NMvJfpD0zJFiXI4l/5LuFwomkywzI8hWf7cvH0cH509DDjlxLC+JRy/dRHYpGLRzBmBM3KLHiDjBD6AYBf9OvI5zTk+TKOGwqSkN18Im0OO6IHaasGB3xtMYTssSw6eUEmCXN7Y69S1QqWrZ82BtIGHWDo7Ho9xKxFSBj27Z/SMbvZ500j/lnv/wKR8E4clnraPC8JDvmUaIqHjEqCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MTIwODExNDU1NVowIwYJKoZIhvcNAQkEMRYEFApvMc2fZuC+WaNvkES4c9v4IDt7MA0GCSqGSIb3DQEBAQUABIGAFMKWx3/IG4LZN7B45RFK8lqx/7e1WCjQfD56it6KDuIWk/6jxtvQb1QB43DBFcZvziQ+jb21x8RBMnJBWHiAeGt/ZZa0PIhU04tT3VreAiwvHAsoQ9AJ9ab4Na4Bh2OZsVc7q3qlLvXmAMFmNFdYgFOYwGqpcyqyDnKjZKfbAUM=-----END PKCS7-----\"/>', '\nfunction wet_if_page($atts, $thing)\n{\n	global $pretext;\n\n	extract(lAtts(array(\n		\'page\' => \'1\'\n	),$atts));\n	$here = ( empty($pretext[\'pg\']) ? \'1\' : $pretext[\'pg\'] );\n	return parse(EvalElse($thing, in_list($here, $page)));\n}', '\nfunction wet_if_page($atts, $thing)\n{\n	global $pretext;\n\n	extract(lAtts(array(\n		\'page\' => \'1\'\n	),$atts));\n	$here = ( empty($pretext[\'pg\']) ? \'1\' : $pretext[\'pg\'] );\n	return parse(EvalElse($thing, in_list($here, $page)));\n}', 'ecee2c75723e8d8585d7e988b46733b3', '', '', 0, 5, 0),
('wet_if_status', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wet_if_status-testing-article-stati-with-a-textpattern-plugin', '0.2', 'Tests for various article stati', '<h3>Tests for various article stati</h3>\n\n<h4>Usage:</h4>\n\n<p>This particular plugin contains two conditional tags which check for an article&#8217;s status being either “live” or “sticky”, respectively. Usage is plain and simple:</p>\n\n<p><code>&lt;txp:wet_if_live&gt;...&lt;/txp:wet_if_live&gt;</code></p>\n\n<p><code>&lt;txp:wet_if_sticky&gt;...&lt;/txp:wet_if_sticky&gt;</code></p>\n\n<p>Both tags are bound to be used in an article context, either as part of an article form or inside the body/excerpt.</p>\n\n<h4>Licence</h4>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nfunction wet_if_live($atts, $thing)\n{\n	global $thisarticle;\n	assert_article();\n	$id = $thisarticle[\'thisid\'];\n	return parse(EvalElse($thing, 4 == safe_field(\'Status\', \'textpattern\', \"ID = \'$id\'\")));\n}\n\nfunction wet_if_sticky($atts, $thing)\n{\n	global $thisarticle;\n	assert_article();\n	$id = $thisarticle[\'thisid\'];\n	return parse(EvalElse($thing, 5 == safe_field(\'Status\', \'textpattern\', \"ID = \'$id\'\")));\n}', '\nfunction wet_if_live($atts, $thing)\n{\n	global $thisarticle;\n	assert_article();\n	$id = $thisarticle[\'thisid\'];\n	return parse(EvalElse($thing, 4 == safe_field(\'Status\', \'textpattern\', \"ID = \'$id\'\")));\n}\n\nfunction wet_if_sticky($atts, $thing)\n{\n	global $thisarticle;\n	assert_article();\n	$id = $thisarticle[\'thisid\'];\n	return parse(EvalElse($thing, 5 == safe_field(\'Status\', \'textpattern\', \"ID = \'$id\'\")));\n}', '6827f6e687a0262f75c73aa16259ff59', '', '', 0, 5, 0),
('wet_linkinfo', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/software/wet_linkinfo-textpattern-plugin', '0.2', 'Extract one particular link\'s properties from Textpattern\'s link management', '<h3>Extract one particular link&#8217;s properties from Textpattern&#8217;s link management</h3>\n\n<p><strong>wet_linkinfo</strong> takes a link id number and returns either the results of parsing a form passed by attribute, parsing the contained text, or one of these assigned properties:</p>\n\n<ul>\n	<li>date</li>\n	<li>category</li>\n	<li>url</li>\n	<li>linkname</li>\n	<li>linksort</li>\n	<li>description</li>\n</ul>\n\n<h3>Usage</h3>\n\n<p><code>&lt;txp:wet_linkinfo id=&quot;2&quot; /&gt;</code></p>\n\n<h3>Required attributes</h3>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>id</strong> </td>\n			<td> The link&#8217;s numerical <code>id</code> as listed in the <a href=\"http://localhost/site1/?event=link\" rel=\"nofollow\">links page</a>. </td>\n			<td> None. </td>\n		</tr>\n</table>\n\n\n<h3>Optional attributes</h3>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>type</strong> </td>\n			<td> Either “date”, “category”, “url”, “linkname”, “linksort”, or “description”. </td>\n			<td> <code>&quot;url&quot;</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>escape</strong> </td>\n			<td> Replace special characters with their <span class=\"caps\">HTML</span> entities. </td>\n			<td> Empty. </td>\n		</tr>\n		<tr>\n			<td> <strong>form</strong> </td>\n			<td> A form&#8217;s name which will be parsed to produce the tag&#8217;s output. The form can contain any text, <span class=\"caps\">HTML</span> elements, or Textpattern tags, and will produce reasonable results when one of these tags is encountered:<br />\n<ul>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_/\" rel=\"nofollow\">txp:link</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_category_/\" rel=\"nofollow\">txp:link_category</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_date_/\" rel=\"nofollow\">txp:link_date</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:linkdesctitle_/\" rel=\"nofollow\">txp:linkdesctitle</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_name_/\" rel=\"nofollow\">txp:link_name</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_url_/\" rel=\"nofollow\">txp:link_url</a></li>\n	<li><a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_description_/\" rel=\"nofollow\">txp:link_description</a></li>\n</ul></td>\n			<td> Empty. </td>\n		</tr>\n</table>\n\n\n<h3>Sample use</h3>\n\n<table>\n		<tr>\n			<th>tag </th>\n			<th>function </th>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_linkinfo id=&quot;3&quot; type=&quot;url&quot; /&gt;</code> </td>\n			<td> Return a link&#8217;s <span class=\"caps\">URL</span> </td>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_linkinfo id=&quot;3&quot; type=&quot;description&quot; escape=&quot;html&quot; /&gt;</code> </td>\n			<td> Return a link&#8217;s description and replace special characters with <span class=\"caps\">HTML</span> entities. </td>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_linkinfo id=&quot;3&quot; form=&quot;mylinkinfo&quot; /&gt;</code> </td>\n			<td> Return the result of parsing the form named “mylinkinfo”. Create a form of type <code>link</code> and put something useful in it. </td>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_linkinfo id=&quot;3&quot;&gt;&lt;txp:link /&gt;&lt;br /&gt;&lt;txp:link_date /&gt;&lt;/txp:wet_linkinfo&gt;</code> </td>\n			<td> Return a link and its creation date, separated by a line break. </td>\n		</tr>\n</table>\n\n<h3>Additional information</h3>\n\n<p>Visit the <a href=\"http://awasteofwords.com/software/wet_linkinfo-textpattern-plugin\" rel=\"nofollow\">wet_linkinfo documentation page</a>.</p>\n\n<h3>Licence</h3>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nfunction wet_linkinfo($atts, $thing=\'\')\n{\n	global $thislink;\n	extract (lAtts(array(\n		\'id\' => \'\',\n		\'type\' => \'url\',\n		\'escape\' => \'\',\n		\'form\' => \'\'\n		), $atts, 0));\n\n	if (empty($id)) return \'\';\n	$id = assert_int($id);\n\n	if(!empty($form)) {\n		$form = fetch_form($form);\n	} elseif(!empty($thing)) {\n		$form = $thing;\n	}\n\n	if(empty($form)) {\n		$r = safe_field($type, \'txp_link\', \"id=$id\");\n		return ($escape == \'html\') ? escape_output($r) : $r;\n	} else {\n		$rs = safe_row(\'*, unix_timestamp(date) as uDate\', \'txp_link\', \"id=$id\");\n		if($rs) {\n			if(isset($thislink)) {\n				$mylink = $thislink;\n			}\n			extract($rs);\n			$thislink = array(\n				\'linkname\'    => $linkname,\n				\'url\'         => $url,\n				\'description\' => $description,\n				\'date\'        => $uDate,\n				\'category\'    => $category,\n			);\n			$out = parse($form);\n			if (isset($mylink)) {\n				$thislink = $mylink;\n			}\n			return $out;\n		}\n	}\n	return \'\';\n}\n', '\nfunction wet_linkinfo($atts, $thing=\'\')\n{\n	global $thislink;\n	extract (lAtts(array(\n		\'id\' => \'\',\n		\'type\' => \'url\',\n		\'escape\' => \'\',\n		\'form\' => \'\'\n		), $atts, 0));\n\n	if (empty($id)) return \'\';\n	$id = assert_int($id);\n\n	if(!empty($form)) {\n		$form = fetch_form($form);\n	} elseif(!empty($thing)) {\n		$form = $thing;\n	}\n\n	if(empty($form)) {\n		$r = safe_field($type, \'txp_link\', \"id=$id\");\n		return ($escape == \'html\') ? escape_output($r) : $r;\n	} else {\n		$rs = safe_row(\'*, unix_timestamp(date) as uDate\', \'txp_link\', \"id=$id\");\n		if($rs) {\n			if(isset($thislink)) {\n				$mylink = $thislink;\n			}\n			extract($rs);\n			$thislink = array(\n				\'linkname\'    => $linkname,\n				\'url\'         => $url,\n				\'description\' => $description,\n				\'date\'        => $uDate,\n				\'category\'    => $category,\n			);\n			$out = parse($form);\n			if (isset($mylink)) {\n				$thislink = $mylink;\n			}\n			return $out;\n		}\n	}\n	return \'\';\n}\n', '05de24163fb2419683a9065e80b459dd', '', '', 0, 5, 0),
('wet_opensearch', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wet_opensearch-add-opensearch-to-a-textpattern-powered-site', '0.1', 'Adds OpenSearch to your site', '<h3>Open Search template generation and auto discovery</h3>\n\n<p><strong>wet_opensearch</strong> adds an OpenSearch template to your site as specified by the <a href=\"http://www.opensearch.org/Specifications/OpenSearch/1.1\" rel=\"nofollow\">OpenSearch Specification</a>. Additionally, it contains a tag which renders the necessary markup allowing search clients like modern browsers to discover your on-site search facility. Visitors can easily add the site search to their browser&#8217;s list of search providers.</p>\n\n<h4>usage:</h4>\n\n<p>Place this tag somewhere into the <code>&lt;head&gt;...&lt;/head&gt;</code> element of your site to add an OpenSearch auto discovery link:</p>\n\n<p><code>&lt;txp:wet_opensearch_link /&gt;</code></p>\n\n<h4>required attributes:</h4>\n\n<p>None.</p>\n\n<h4>optional attributes:</h4>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td><strong>searchurl</strong></td>\n			<td>A path starting at your site&#8217;s Textpattern root which displays search results. If yor site uses the default section to list search term hits, leave this empty. If you employ a special section like <code>/search/</code> to display search hits enter the search section&#8217;s <span class=\"caps\">URL</span> without a leading slash (i.e. “search/”).</td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td><strong>favicon</strong></td>\n			<td>The <span class=\"caps\">URL</span> of a 16 &#215; 16 pixel icon for your site.</td>\n			<td> <code>http://www.yoursite.com/txp-root/favicon.ico</code> </td>\n		</tr>\n		<tr>\n			<td><strong>avatar</strong></td>\n			<td>The <span class=\"caps\">URL</span> of a 64 &#215; 64 pixel <span class=\"caps\">PNG</span> image for your site.</td>\n			<td> None </td>\n		</tr>\n</table>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\n/**\n * register OpenSearch template url handler\n */\nif (txpinterface == \'public\')\n{\n	register_callback (\'wet_opensearch\', \'pretext\');\n}\n\n/**\n * Return an OpenSearch template for Textpattern powered sites\n * @link http://www.opensearch.org/Specifications/OpenSearch/1.1 OpenSearch Specification\n */\nfunction wet_opensearch($event)\n{\n	global $sitename, $site_slogan;\n\n	$searchurl = rawurldecode(gps(\'wet_opensearch_url\'));\n	if (!empty($searchurl))\n	{\n		$favicon = rawurldecode(gps(\'favicon\'));\n		if(empty($favicon)) $favicon = rawurldecode(gps(\'amp;favicon\')); // some clients don\'t decode template URLs\n		$avatar = rawurldecode(gps(\'avatar\'));\n		if(empty($avatar)) $avatar = rawurldecode(gps(\'amp;avatar\'));\n\n		if(!empty($favicon)) $favicon =	\'<Image height=\"16\" width=\"16\" type=\"image/x-icon\">\'.$favicon.\'</Image>\';\n 		if(!empty($avatar)) $avatar = \'<Image height=\"64\" width=\"64\" type=\"image/png\">\'.$avatar.\'</Image>\';\n		ob_end_clean(); // eat previous output to asser well-formed XML\n		header(\'Content-Type: text/xml; charset=utf-8\');\n 		echo \'<?xml version=\"1.0\" encoding=\"UTF-8\"?>\'.n.\n			\'<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\'.n.\n		 	\'<ShortName>\'.htmlspecialchars(substr($sitename, 0, 16)).\'</ShortName>\'.n.\n		 	\'<Description>\'.htmlspecialchars(substr($sitename.\': \'.$site_slogan, 0, 1024)).\'</Description>\'.n.\n		 	\'<Language>\'.LANG.\'</Language>\'.n.\n   			\'<OutputEncoding>UTF-8</OutputEncoding>\'.n.\n   			\'<InputEncoding>UTF-8</InputEncoding>\'.n.\n		 	\'<Url type=\"text/html\" template=\"\'.hu.$searchurl.\'{searchTerms}\"/>\'.n.\n		 	$favicon.n.$avatar.n.\n			\'</OpenSearchDescription>\'.n;\n		exit(0);\n	}\n}\n\n/**\n * txp tag: insert auto discovery link\n */\nfunction wet_opensearch_link($atts , $anchortext=\'\' )\n{\n	global $sitename;\n	extract (lAtts(array(\n		\'searchurl\' => \'\',\n		\'favicon\' => hu.\'favicon.ico\',\n		\'avatar\' => \'\'\n	),$atts));\n\n	return \'<link rel=\"search\" href=\"\'.hu.\'?wet_opensearch_url=\'.rawurlencode($searchurl.\'?q=\').\'&amp;favicon=\'.rawurlencode($favicon).\'&amp;avatar=\'.rawurlencode($avatar).\n			\'\" type=\"application/opensearchdescription+xml\" title=\"\'.htmlspecialchars($sitename).\'\" />\';\n}', '\n/**\n * register OpenSearch template url handler\n */\nif (txpinterface == \'public\')\n{\n	register_callback (\'wet_opensearch\', \'pretext\');\n}\n\n/**\n * Return an OpenSearch template for Textpattern powered sites\n * @link http://www.opensearch.org/Specifications/OpenSearch/1.1 OpenSearch Specification\n */\nfunction wet_opensearch($event)\n{\n	global $sitename, $site_slogan;\n\n	$searchurl = rawurldecode(gps(\'wet_opensearch_url\'));\n	if (!empty($searchurl))\n	{\n		$favicon = rawurldecode(gps(\'favicon\'));\n		if(empty($favicon)) $favicon = rawurldecode(gps(\'amp;favicon\')); // some clients don\'t decode template URLs\n		$avatar = rawurldecode(gps(\'avatar\'));\n		if(empty($avatar)) $avatar = rawurldecode(gps(\'amp;avatar\'));\n\n		if(!empty($favicon)) $favicon =	\'<Image height=\"16\" width=\"16\" type=\"image/x-icon\">\'.$favicon.\'</Image>\';\n 		if(!empty($avatar)) $avatar = \'<Image height=\"64\" width=\"64\" type=\"image/png\">\'.$avatar.\'</Image>\';\n		ob_end_clean(); // eat previous output to asser well-formed XML\n		header(\'Content-Type: text/xml; charset=utf-8\');\n 		echo \'<?xml version=\"1.0\" encoding=\"UTF-8\"?>\'.n.\n			\'<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\'.n.\n		 	\'<ShortName>\'.htmlspecialchars(substr($sitename, 0, 16)).\'</ShortName>\'.n.\n		 	\'<Description>\'.htmlspecialchars(substr($sitename.\': \'.$site_slogan, 0, 1024)).\'</Description>\'.n.\n		 	\'<Language>\'.LANG.\'</Language>\'.n.\n   			\'<OutputEncoding>UTF-8</OutputEncoding>\'.n.\n   			\'<InputEncoding>UTF-8</InputEncoding>\'.n.\n		 	\'<Url type=\"text/html\" template=\"\'.hu.$searchurl.\'{searchTerms}\"/>\'.n.\n		 	$favicon.n.$avatar.n.\n			\'</OpenSearchDescription>\'.n;\n		exit(0);\n	}\n}\n\n/**\n * txp tag: insert auto discovery link\n */\nfunction wet_opensearch_link($atts , $anchortext=\'\' )\n{\n	global $sitename;\n	extract (lAtts(array(\n		\'searchurl\' => \'\',\n		\'favicon\' => hu.\'favicon.ico\',\n		\'avatar\' => \'\'\n	),$atts));\n\n	return \'<link rel=\"search\" href=\"\'.hu.\'?wet_opensearch_url=\'.rawurlencode($searchurl.\'?q=\').\'&amp;favicon=\'.rawurlencode($favicon).\'&amp;avatar=\'.rawurlencode($avatar).\n			\'\" type=\"application/opensearchdescription+xml\" title=\"\'.htmlspecialchars($sitename).\'\" />\';\n}', 'c6a08a71a2c05e9ee3a810d62e66df46', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_quicklink', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/help/wet_quicklink', '0.8.2', 'Pick and insert site internal links into articles', '<h3>Quick internal link builder for Textpattern articles</h3>\n\n<p><strong>wet_quicklink</strong> is a plugin for Textpattern which extends the article edit screen to add an easy method for defining site internal links to other articles.</p>\n\n<p>Candidate articles can be chosen by a wide variety of methods, ranging from sorting by posting date or assigned section, to a live content search inside the articles’ body or title.</p>\n\n<h4>Usage</h4>\n\n<ol>\n	<li>Textpattern 4.3+ is required, wet_quicklink will <em>not</em> work with any prior versions.</li>\n	<li>Install both <code>wet_quicklink</code> and <code>wet_peex</code> as a Textpattern plugin.</li>\n	<li>Done. If all went well, the <a href=\"./?event=article\" rel=\"nofollow\">Content &gt; Edit</a> screen will sport a new menu entry called “Insert Link” located just above “Advanced”.</li>\n</ol>\n\n<p>Visit the <a href=\"http://awasteofwords.com/help/wet_quicklink\" rel=\"nofollow\">plugin help page</a> for more detailled instructions.</p>\n\n<h4>Localization</h4>\n\n<p><code>wet_quicklink</code> supports its user interface in your language. English and German translations are included in the plugin itself.</p>\n\n<p>For other languages, use the sample textpack below like so:</p>\n\n<ol>\n	<li>Change the language code from <code>en-gb</code> to the one matching your site&#8217;s language settings</li>\n	<li>Replace the two English texts right to the <code>=&gt;</code> symbols with your own</li>\n	<li>Paste it into the Textpack box at the <a href=\"http://localhost/site1/?event=prefs&amp;step=list_languages#text_uploader\" rel=\"nofollow\">Admin &gt; Preferences &gt; Language</a> screen</li>\n</ol>\n\n<p>This is a sample textpack to get you started:</p>\n\n<pre><code>#@admin\n#@language en-gb\nwet_quicklink_insert_link =&gt; Insert Link\nwet_quicklink_loading =&gt; Loading…</code></pre>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nglobal $textarray;\n\n// launch the UI overlay\nregister_callback(\'wet_quicklink_menu\', \'article_ui\', \'extend_col_1\');\n// build the UI overlay\nregister_callback(\'wet_quicklink_clutch\', \'article\');\nregister_callback(\'wet_quicklink_css\', \'admin_side\', \'head_end\');\nregister_callback(\'wet_quicklink_save_key\', \'wet_quicklink\', \'save_key\');\n\n// Don\'t let the unwashed masses fiddle with the way we add links\nadd_privs(\'wet_quicklink.preferences\', \'1,2\');\n\n// serve assorted resources\nswitch(gps(\'wet_rsrc\')) {\n	case \'quicklink_js\':\n		wet_quicklink_js();\n		break;\n	default:\n		break;\n}\n\n// AJAX handler\nswitch(ps(\'wet_quicklink\')) {\n	case \'save_key\':\n		wet_quicklink_safe_key();\n		break;\n	default:\n		break;\n}\n\n/**\n * Insert a menu entry in the first sidebar\n */\nfunction wet_quicklink_menu()\n{\n	// add a link in left column to launch the overlay\n	return \'<h3 class=\"plain\"><a href=\"#insert-link\" id=\"insert-link\">\'.gTxt(\'wet_quicklink_insert_link\').\'</a></h3>\'.n;\n}\n\n/**\n * Inject a few JS constants and pull in the JS worker file near the end of the page\n */\nfunction wet_quicklink_clutch($event, $step)\n{\n	global $textarray;\n	echo \'<script src=\"./index.php?wet_rsrc=quicklink_js&amp;v=0.8.2\" type=\"text/javascript\"></script>\'.n;\n\n	// Link building patterns for various markup methods\n	//\n	// Patterns are used literally besides some special place holders:\n	// {id}: article id\n	// {title}: article title\n	// {url}: fully qualified article URL per current URL mode\n	// {text}: selected text from insertion target\n	$patterns = array (\n		\'Textile\' 		=> \'\"{text}({title})\":{url}\',\n		\'txp:permlink\' 	=> \'<txp:permlink id=\"{id}\" title=\"{title}\">{text}</txp:permlink>\',\n		\'txp:wet_link\' 	=> \'<txp:wet_link href=\"{id}\">{text}</txp:wet_link>\',\n		\'HTML\' 			=> \'<a href=\"{url}\" title=\"{title}\" rel=\"bookmark\">{text}</a>\',\n		\'URL\' 			=> \'{url}\'\n	);\n\n	// build a JS array of patterns, quote quoted quotes, escape closing tags\n	foreach ($patterns as $key => $value) {\n		$patterns_js[] = \"\'$key\' : \'\".str_replace(array(\'\"\', \'</\'), array(\'\\\"\', \'<\\/\'), $value).\"\'\";\n	}\n	$patterns_js = join(\',\'.n.t, $patterns_js);\n\n	// Gather link markup preferences\n	$curr_key = get_pref(\'wet_quicklink.key\', \'txp:permlink\');\n	$change_key_privs = has_privs(\'wet_quicklink.preferences\') ? \'true\' : \'false\';\n\n	// TODO: Will not work in PHP 4 :( $f = \'gTxt\'; // ugly hack to trick PHP into invoking a function from the CFG HEREDOC later\n	// TODO: Remove the next line and use gTxt() once PHP 5 cna be taken for granted\n	if (empty($textarray[\'wet_quicklink_loading\'])) $textarray[\'wet_quicklink_loading\'] = \'wet_quicklink_loading\';\n\n	echo script_js( <<<CFG\n	wet_quicklink.patterns = {\n	$patterns_js\n	};\n\n	wet_quicklink.currKey = \"$curr_key\";\n	wet_quicklink.changeKeyPrivs = $change_key_privs;\n\n	wet_quicklink.gTxt = {\n		title : 	\"{$textarray[\'title\']}\",\n		body : 		\"{$textarray[\'tag_body\']}\",\n		category1 : \"{$textarray[\'category1\']}\",\n		category2 : \"{$textarray[\'category2\']}\",\n		section : 	\"{$textarray[\'section\']}\",\n		posted : 	\"{$textarray[\'posted\']}\",\n		modified : 	\"{$textarray[\'modified\']}\",\n		permlink_mode : \"{$textarray[\'permlink_mode\']}\",\n		help : 		\"{$textarray[\'help\']}\",\n		search : 	\"{$textarray[\'search\']}\",\n		loading : 	\"{$textarray[\'wet_quicklink_loading\']}\"\n	}\nCFG\n);\n	require_plugin(\'wet_peex\'); // won\'t help for loading wet_peex on time, but point out the lack of it to unwary users.\n}\n\n/**\n * AJAX endpoint: Safe current key as system preference\n */\nfunction wet_quicklink_safe_key()\n{\n	if (has_privs(\'wet_quicklink.preferences\')) {\n		set_pref(\'wet_quicklink.key\', doSlash(ps(\'key\')), \'wet_foo\', 2);\n		send_xml_response();\n	} else {\n		send_xml_response(array(\'http-status\' => \'403 Forbidden\'));\n	}\n}\n\n/**\n * Serve JS resource, as either an embedded resource or from a file while in development\n */\nfunction wet_quicklink_js()\n{\n	$debug = false;\n	while(@ob_end_clean());\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \".date(\"r\", time() + ($debug ? -3600 : 3600)));\n	header(\"Cache-Control: public\");\n	if ($debug) {\n		readfile( dirname(__FILE__). \'/wet_quicklink_js.js\');\n	} else {\n		echo <<<js\n/*=*=*=* script goes here */\n/**\n * wet_quicklink: A Textpattern link builder plugin\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/help/wet_quicklink\n * @version 0.8.2\n */\n\nvar wet_quicklink = {\n	pattern : \"\",\n	currKey : \"\",\n 	rows : 10,\n	firstrow : 0,\n	maxrows : 0,\n	sortdir : \"desc\",\n	crit : \"lastmod\",\n	search : getCookie(\"wet_quicklink-search\") || \"\",\n	target : $(\"#body\")[0], // insert links into article body unless focus changes\n\n	paint : function () {\n	   	// ui box\n		var menu = \"<form id=\'ql-menu\'>\";\n		menu += \"<label for=\'ql-search\'>\"+wet_quicklink.gTxt.search+\":</label>\";\n		menu += \"<input type=\'text\' class=\'edit\' size=\'15\' id=\'ql-search\' style=\'margin-right: 1em;\' value=\'\"+\n			wet_quicklink.search+\"\' />\";\n\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-rev\' class=\'smallerbox\' value=\'&lt;\' />\";\n		menu += \"<span id=\'pager\' style=\'padding: 0 0.4em;\'></span>\";\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-fwd\' value=\'&gt;\' />\";\n\n		menu += \"<label for=\'ql-pattern\' style=\'margin-left: 2em;\'>\"+wet_quicklink.gTxt.permlink_mode+\n			\":</label><select id=\'ql-pattern\'\" + (wet_quicklink.changeKeyPrivs ? \'\' : \' disabled=\"disabled\"\') + \">\";\n		for(key in wet_quicklink.patterns) {\n			menu += \"<option value=\'\"+key+\"\'>\"+key+\"</option>\";\n		}\n		menu += \"</select>\";\n		menu += \"<a id=\'ql-help\' href=\'http://awasteofwords.com/help/wet_quicklink\' target=\'_blank\'>\"+\n			wet_quicklink.gTxt.help+\"</a>\";\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-close\' value=\'&#215;\' />\";\n		menu += \"</form>\";\n		wet_quicklink.pattern = wet_quicklink.patterns[this.currKey] || wet_quicklink.patterns[\"Textile\"];\n\n	   	$(\"body\").append(\"<div id=\'quicklink\'>\"+menu+\n			\"<div><table id=\'list\'></table></div><p id=\'quicklink-msg\'>\"+wet_quicklink.gTxt.loading+\"</p></div>\");\n\n		// preselect pattern option from saved cookie\n		try {\n			$(\"#ql-pattern option[value=\'\"+this.currKey+\"\']\")[0].selected = true;\n		} catch(e) {}\n		// waiting for the master\'s voice\n	 	$(\"#quicklink\").hide();\n	 	$(\"#quicklink-msg\").hide();\n		this.behaviours();\n	},\n\n	// the worker function\n	refresh : function () {\n		var box = $(\"#list\");\n		box.empty();\n		wet_quicklink.loading(true);\n		$.get(\n	 		\"\",\n	 		{ wet_peex: \"article\", limit: wet_quicklink.rows.toString(), offset: wet_quicklink.firstrow.toString(),\n				dir: wet_quicklink.sortdir, sort: wet_quicklink.crit, search: wet_quicklink.search },\n			function(xml){\n				wet_quicklink.maxrows = $(\"articles\", xml).attr(\"count\");\n\n				$(\"#pager\").html(Math.ceil(wet_quicklink.firstrow/wet_quicklink.rows + 1).toString() + \"/\"\n					+ Math.ceil(wet_quicklink.maxrows/wet_quicklink.rows).toString());\n\n	    			// paint the article table header\n	    			var table = \"<tr><th class=\'ql-id\'>id</th>\"+\n					\"<th class=\'ql-title\'><a rel=\'title\' href=\'#\'>\"+wet_quicklink.gTxt.title+\"</a></th>\"+\n					\"<th class=\'ql-teaser\'><a rel=\'body_html\' href=\'#\'>\"+wet_quicklink.gTxt.body+\"</a></th>\"+\n					\"<th class=\'ql-posted\'><a rel=\'posted\' href=\'#\'>\"+wet_quicklink.gTxt.posted+\"</a></th>\"+\n					\"<th class=\'ql-lastmod\'><a rel=\'lastmod\' href=\'#\'>\"+wet_quicklink.gTxt.modified+\"</a></th>\"+\n					\"<th class=\'ql-section\'><a rel=\'section\' href=\'#\'>\"+wet_quicklink.gTxt.section+\"</a></th>\"+\n					\"<th class=\'ql-category1\'><a rel=\'category1\' href=\'#\'>\"+wet_quicklink.gTxt.category1.replace(/ /g,\"&#160;\")+\"</a></th>\"+\n					\"<th class=\'ql-category2\'><a rel=\'category2\' href=\'#\'>\"+wet_quicklink.gTxt.category2.replace(/ /g,\"&#160;\")+\"</a></th>\"+\n					\"<th class=\'ql-url\'><a rel=\'url\' href=\'#\'>url</a></th></tr>\";\n\n				// parse the XML response\n				$(\"article\", xml).each (\n					function(i) {\n		    			// paint one article row\n		    			table += \"<tr\" +\n						\"><td class=\'ql-id\'>\" + $(\"id\", this).text() +\n						\"</td><td class=\'ql-title\'>\" +\"<a href=\'#\'>\" +\n							wet_quicklink.htmlspecialchars($(\"title\", this).text()) +\"</a>\"+\n						\"</td><td class=\'ql-teaser\'>\" +\n							wet_quicklink.htmlspecialchars($(\"teaser\", this).text())+ \"&#8230;\" +\n						\"</td><td class=\'ql-posted\'>\" +\n							$(\"posted\", this).text().replace(/ [0-9]*:[0-9]*:[0-9]*/, \"\") + // remove hh:mm:ss\n		    			\"</td><td class=\'ql-lastmod\'>\" +\n							$(\"lastmod\", this).text().replace(/ [0-9]*:[0-9]*:[0-9]*/, \"\") +\n						\"</td><td class=\'ql-section\'>\" +\n							wet_quicklink.htmlspecialchars($(\"section\", this).text()) +\n						\"</td><td class=\'ql-category1\'>\" +\n							wet_quicklink.htmlspecialchars($(\"category[level=\'1\']\", this).text()) +\n						\"</td><td class=\'ql-category2\'>\" +\n							wet_quicklink.htmlspecialchars($(\"category[level=\'2\']\", this).text()) +\n						\"</td><td class=\'ql-url\'>\" + $(\"permlink\", this).text() +\n						\"</td></tr>\";\n					}\n				);\n\n				// inject table into ui\n	    			box.html(table);\n\n				// table heads act as ui for sort direction\n				$(\"#list tr th a\")\n					.click( function() {\n						// revert sort order just the second time around\n						if(wet_quicklink.crit == $(this).attr(\"rel\")) {\n							wet_quicklink.sortdir = (wet_quicklink.sortdir == \"desc\" ? \"asc\" : \"desc\");\n						}\n						// define sort criterion\n						wet_quicklink.crit = $(this).attr(\"rel\");\n						wet_quicklink.firstrow = 0;\n						wet_quicklink.refresh();\n						return false;\n					});\n\n				// zebra stripes\n				$(\'#list tr:odd\').addClass(\'odd\');\n\n				// style table heads as indicators for sort direction\n				$(\"#list tr th a[rel=\'\"+wet_quicklink.crit+\"\']\").addClass(wet_quicklink.sortdir);\n				wet_quicklink.loading(false);\n\n				/**\n				 * Insert content at caret position\n				 * @link http://alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript\n				 */\n				function edInsertContent(myField, myValue) {\n					//IE support\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						sel.text = myValue;\n						myField.focus();\n					}\n					//MOZILLA/NETSCAPE support\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						var scrollTop = myField.scrollTop;\n						myField.value = myField.value.substring(0, startPos)\n						              + myValue\n				                      + myField.value.substring(endPos, myField.value.length);\n						myField.focus();\n						myField.selectionStart = startPos + myValue.length;\n						myField.selectionEnd = startPos + myValue.length;\n						myField.scrollTop = scrollTop;\n					} else {\n						myField.value += myValue;\n						myField.focus();\n					}\n				}\n\n				function getSel(myField) {\n					//IE support\n					if (document.selection) {\n						return document.selection.createRange().text;\n					}\n					//MOZILLA/NETSCAPE support\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						return myField.value.substring(startPos, endPos);\n					} else {\n						return \"\";\n					}\n				}\n\n				// insert permlink on title click\n				$(\'td.ql-title a\').click(\n					function() {\n						// find the insertion target\n						var t = wet_quicklink.target;\n						// build tag\n						var tag = wet_quicklink.pattern;\n						var cell = $(this).parent(); // td > a\n\n						var id = cell.siblings(\".ql-id\").text();  // the article id\n						tag = tag.replace(/{id}/gi, id);\n\n						var url = cell.siblings(\".ql-url\").text();  // the url\n						tag = tag.replace(/{url}/gi, url);\n\n						var title = cell.children(\"a\").text();  // the title\n						tag = tag.replace(/{title}/gi, title);\n\n						tag = tag.replace(/{text}/gi, getSel(t));\n						edInsertContent(t, tag);\n						$(\"#quicklink\").hide();\n						return false;\n					}\n				);\n	 		}\n		);\n	},\n\n	// launch overlay on menu click\n	hook : function () {\n		$(\'#insert-link\').click(\n			function() {\n				wet_quicklink.refresh();\n				$(\"#quicklink\").show();\n				return false;\n			}\n		);\n	},\n\n	// add behaviours\n	behaviours : function() {\n		$(\'input#ql-close\').click(\n			function() {\n				$(\"#quicklink\").hide();\n				return false;\n			}\n		);\n\n		$(\'input#ql-fwd\').click(\n			function() {\n				var here = wet_quicklink.firstrow;\n				if(wet_quicklink.firstrow + wet_quicklink.rows < wet_quicklink.maxrows) {\n					wet_quicklink.firstrow += wet_quicklink.rows;\n				}\n				if(here != wet_quicklink.firstrow) wet_quicklink.refresh();\n				return false;\n			}\n		);\n\n		$(\'input#ql-rev\').click(\n			function() {\n				var here = wet_quicklink.firstrow;\n				if(wet_quicklink.firstrow >= wet_quicklink.rows) {\n					wet_quicklink.firstrow -= wet_quicklink.rows;\n				}\n				if(here != wet_quicklink.firstrow) wet_quicklink.refresh();\n				return false;\n			}\n		);\n\n		$(\'input#ql-search\').keyup(\n			function() {\n				if(this.value != wet_quicklink.search) {\n					wet_quicklink.firstrow = 0;\n					wet_quicklink.search = this.value;\n					setCookie(\"wet_quicklink-search\", this.value);\n\n					try {\n						window.clearTimeout(this.lazy);\n					} catch(e) {}\n					this.lazy = window.setTimeout(wet_quicklink.refresh, 750);\n				}\n			}\n		);\n\n		$(\'select#ql-pattern\').click(\n			function() {\n				var currKey = this.options[this.options.selectedIndex].value;\n				wet_quicklink.pattern = wet_quicklink.patterns[currKey];\n				sendAsyncEvent(\n					{\n						wet_quicklink: \'save_key\',\n						key: currKey\n					}\n				);\n				return false;\n			}\n		);\n	},\n\n	htmlspecialchars : function (s) {\n		s = s.replace(/&/g,\"&amp;\");\n		s = s.replace(/</g,\"&lt;\");\n		s = s.replace(/>/g,\"&gt;\");\n		s = s.replace(/\"/g,\"&quot;\");\n		return s;\n	},\n\n	status : function (s, is_ok) {\n		var msg = $(\"#quicklink-msg\");\n		msg.html(s);\n		if(is_ok) {\n			msg.addClass(\"success\");\n		} /* @todo remove class later */\n		msg.fadeIn(\"slow\");\n		msg.fadeOut(\"slow\");\n	},\n\n	loading : function (is_on) {\n		var msg = $(\"#quicklink-msg\");\n		msg.addClass(\"success\");	 /* @todo remove class later */\n		if(is_on) {\n			msg.fadeIn(\"slow\");\n		} else {\n			msg.fadeOut(\"slow\");\n		}\n	}\n};\n\n$(document).ready(\n	function(){\n		wet_quicklink.hook();\n		wet_quicklink.paint();\n\n		// insertion targets\n		var targets = new Array(\"title\", \"body\", \"excerpt\",\n			\"custom-1\", \"custom-2\", \"custom-3\", \"custom-4\", \"custom-5\",\n			\"custom-6\", \"custom-7\", \"custom-8\", \"custom-9\", \"custom-10\",\n			\"keywords\");\n		var i, l = targets.length;\n		// click handler stores \"active\" textarea/input element\n		for (i = 0; i < l; i++) {\n			var t = $(\"#\"+targets[i]);\n			if(t.length == 0) continue;\n			t.click(function() {\n					wet_quicklink.target = this;\n				}\n			);\n		}\n	}\n);\n/*=*=*=* script ends here */\njs;\n	}\n	exit();\n}\n\nfunction wet_quicklink_css()\n{\n	global $event;\n	if ($event != \'article\') return;\n\n	echo <<<css\n<style type=\"text/css\">\ndiv#quicklink {\n	border: 3px solid #ffcc33;\n	padding: 0.5em;\n	background-color: white;\n	position: absolute;\n	z-index: 999;\n	top: 7em; right: 1em;\n	width: auto; height: auto;\n	max-height: 90%;\n	max-width: 75em;\n}\n\ndiv#quicklink div {\n	overflow: auto;\n}\n\ndiv#quicklink #ql-close {\n	position: absolute;\n	right: 2px; top: 2px;\n	width: 16px; height: 16px;\n	padding: 0px;\n	background-color: #c66;\n	color: #fff;\n}\n\ndiv#quicklink #ql-help {\n	position: absolute;\n	right: 2.5em; top: 4px;\n}\n\ndiv#quicklink table {\n	width: 100%;\n	margin-top: 0.5em;\n	table-layout: fixed;\n}\ndiv#quicklink .ql-id, div#quicklink .ql-url {\n	display: none;\n}\n\ndiv#quicklink td, div#quicklink th {\n	overflow: hidden;\n}\n\ndiv#quicklink .ql-title, div#quicklink .ql-teaser {\n	width: 15em;\n}\n\ndiv#quicklink .ql-section {\n	width: 6em;\n}\n\ndiv#quicklink .ql-posted, div#quicklink .ql-lastmod {\n	width: 7em;\n}\n\ndiv#quicklink .ql-category1, div#quicklink .ql-category2 {\n	width: 8em;\n}\n\ndiv#quicklink tr.odd {\n	background-color: #f8f8ff;\n}\n\ndiv#quicklink td {\n	padding: 0.2em 0;\n}\n\ndiv#quicklink th {\n	padding: 0.2em 0.2em;\n}\n\ndiv#quicklink th a.asc {\n	background: transparent url(txp_img/arrowupdn.gif) no-repeat right 2px;\n	padding-right: 14px;\n}\n\ndiv#quicklink th a.desc {\n	background: transparent url(txp_img/arrowupdn.gif) no-repeat right -18px;\n	padding-right: 14px;\n}\n\n#quicklink-msg {\n	position: absolute;\n	top: 0;\n	right: 0;\n	padding: 0.5em 2em;\n}\n\n#quicklink-msg.success {\n	color: white;\n	background-color: #8c8;\n}\n\n#quicklink-msg.failure {\n	color: white;\n	font-weight: bold;\n	background-color: #f00;\n}\n</style>\n\ncss;\n}\n', '\nglobal $textarray;\n\n// launch the UI overlay\nregister_callback(\'wet_quicklink_menu\', \'article_ui\', \'extend_col_1\');\n// build the UI overlay\nregister_callback(\'wet_quicklink_clutch\', \'article\');\nregister_callback(\'wet_quicklink_css\', \'admin_side\', \'head_end\');\nregister_callback(\'wet_quicklink_save_key\', \'wet_quicklink\', \'save_key\');\n\n// Don\'t let the unwashed masses fiddle with the way we add links\nadd_privs(\'wet_quicklink.preferences\', \'1,2\');\n\n// serve assorted resources\nswitch(gps(\'wet_rsrc\')) {\n	case \'quicklink_js\':\n		wet_quicklink_js();\n		break;\n	default:\n		break;\n}\n\n// AJAX handler\nswitch(ps(\'wet_quicklink\')) {\n	case \'save_key\':\n		wet_quicklink_safe_key();\n		break;\n	default:\n		break;\n}\n\n/**\n * Insert a menu entry in the first sidebar\n */\nfunction wet_quicklink_menu()\n{\n	// add a link in left column to launch the overlay\n	return \'<h3 class=\"plain\"><a href=\"#insert-link\" id=\"insert-link\">\'.gTxt(\'wet_quicklink_insert_link\').\'</a></h3>\'.n;\n}\n\n/**\n * Inject a few JS constants and pull in the JS worker file near the end of the page\n */\nfunction wet_quicklink_clutch($event, $step)\n{\n	global $textarray;\n	echo \'<script src=\"./index.php?wet_rsrc=quicklink_js&amp;v=0.8.2\" type=\"text/javascript\"></script>\'.n;\n\n	// Link building patterns for various markup methods\n	//\n	// Patterns are used literally besides some special place holders:\n	// {id}: article id\n	// {title}: article title\n	// {url}: fully qualified article URL per current URL mode\n	// {text}: selected text from insertion target\n	$patterns = array (\n		\'Textile\' 		=> \'\"{text}({title})\":{url}\',\n		\'txp:permlink\' 	=> \'<txp:permlink id=\"{id}\" title=\"{title}\">{text}</txp:permlink>\',\n		\'txp:wet_link\' 	=> \'<txp:wet_link href=\"{id}\">{text}</txp:wet_link>\',\n		\'HTML\' 			=> \'<a href=\"{url}\" title=\"{title}\" rel=\"bookmark\">{text}</a>\',\n		\'URL\' 			=> \'{url}\'\n	);\n\n	// build a JS array of patterns, quote quoted quotes, escape closing tags\n	foreach ($patterns as $key => $value) {\n		$patterns_js[] = \"\'$key\' : \'\".str_replace(array(\'\"\', \'</\'), array(\'\\\"\', \'<\\/\'), $value).\"\'\";\n	}\n	$patterns_js = join(\',\'.n.t, $patterns_js);\n\n	// Gather link markup preferences\n	$curr_key = get_pref(\'wet_quicklink.key\', \'txp:permlink\');\n	$change_key_privs = has_privs(\'wet_quicklink.preferences\') ? \'true\' : \'false\';\n\n	// TODO: Will not work in PHP 4 :( $f = \'gTxt\'; // ugly hack to trick PHP into invoking a function from the CFG HEREDOC later\n	// TODO: Remove the next line and use gTxt() once PHP 5 cna be taken for granted\n	if (empty($textarray[\'wet_quicklink_loading\'])) $textarray[\'wet_quicklink_loading\'] = \'wet_quicklink_loading\';\n\n	echo script_js( <<<CFG\n	wet_quicklink.patterns = {\n	$patterns_js\n	};\n\n	wet_quicklink.currKey = \"$curr_key\";\n	wet_quicklink.changeKeyPrivs = $change_key_privs;\n\n	wet_quicklink.gTxt = {\n		title : 	\"{$textarray[\'title\']}\",\n		body : 		\"{$textarray[\'tag_body\']}\",\n		category1 : \"{$textarray[\'category1\']}\",\n		category2 : \"{$textarray[\'category2\']}\",\n		section : 	\"{$textarray[\'section\']}\",\n		posted : 	\"{$textarray[\'posted\']}\",\n		modified : 	\"{$textarray[\'modified\']}\",\n		permlink_mode : \"{$textarray[\'permlink_mode\']}\",\n		help : 		\"{$textarray[\'help\']}\",\n		search : 	\"{$textarray[\'search\']}\",\n		loading : 	\"{$textarray[\'wet_quicklink_loading\']}\"\n	}\nCFG\n);\n	require_plugin(\'wet_peex\'); // won\'t help for loading wet_peex on time, but point out the lack of it to unwary users.\n}\n\n/**\n * AJAX endpoint: Safe current key as system preference\n */\nfunction wet_quicklink_safe_key()\n{\n	if (has_privs(\'wet_quicklink.preferences\')) {\n		set_pref(\'wet_quicklink.key\', doSlash(ps(\'key\')), \'wet_foo\', 2);\n		send_xml_response();\n	} else {\n		send_xml_response(array(\'http-status\' => \'403 Forbidden\'));\n	}\n}\n\n/**\n * Serve JS resource, as either an embedded resource or from a file while in development\n */\nfunction wet_quicklink_js()\n{\n	$debug = false;\n	while(@ob_end_clean());\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \".date(\"r\", time() + ($debug ? -3600 : 3600)));\n	header(\"Cache-Control: public\");\n	if ($debug) {\n		readfile( dirname(__FILE__). \'/wet_quicklink_js.js\');\n	} else {\n		echo <<<js\n/*=*=*=* script goes here */\n/**\n * wet_quicklink: A Textpattern link builder plugin\n *\n * @author Robert Wetzlmayr\n * @link http://awasteofwords.com/help/wet_quicklink\n * @version 0.8.2\n */\n\nvar wet_quicklink = {\n	pattern : \"\",\n	currKey : \"\",\n 	rows : 10,\n	firstrow : 0,\n	maxrows : 0,\n	sortdir : \"desc\",\n	crit : \"lastmod\",\n	search : getCookie(\"wet_quicklink-search\") || \"\",\n	target : $(\"#body\")[0], // insert links into article body unless focus changes\n\n	paint : function () {\n	   	// ui box\n		var menu = \"<form id=\'ql-menu\'>\";\n		menu += \"<label for=\'ql-search\'>\"+wet_quicklink.gTxt.search+\":</label>\";\n		menu += \"<input type=\'text\' class=\'edit\' size=\'15\' id=\'ql-search\' style=\'margin-right: 1em;\' value=\'\"+\n			wet_quicklink.search+\"\' />\";\n\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-rev\' class=\'smallerbox\' value=\'&lt;\' />\";\n		menu += \"<span id=\'pager\' style=\'padding: 0 0.4em;\'></span>\";\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-fwd\' value=\'&gt;\' />\";\n\n		menu += \"<label for=\'ql-pattern\' style=\'margin-left: 2em;\'>\"+wet_quicklink.gTxt.permlink_mode+\n			\":</label><select id=\'ql-pattern\'\" + (wet_quicklink.changeKeyPrivs ? \'\' : \' disabled=\"disabled\"\') + \">\";\n		for(key in wet_quicklink.patterns) {\n			menu += \"<option value=\'\"+key+\"\'>\"+key+\"</option>\";\n		}\n		menu += \"</select>\";\n		menu += \"<a id=\'ql-help\' href=\'http://awasteofwords.com/help/wet_quicklink\' target=\'_blank\'>\"+\n			wet_quicklink.gTxt.help+\"</a>\";\n		menu += \"<input type=\'submit\' class=\'smallerbox\' id=\'ql-close\' value=\'&#215;\' />\";\n		menu += \"</form>\";\n		wet_quicklink.pattern = wet_quicklink.patterns[this.currKey] || wet_quicklink.patterns[\"Textile\"];\n\n	   	$(\"body\").append(\"<div id=\'quicklink\'>\"+menu+\n			\"<div><table id=\'list\'></table></div><p id=\'quicklink-msg\'>\"+wet_quicklink.gTxt.loading+\"</p></div>\");\n\n		// preselect pattern option from saved cookie\n		try {\n			$(\"#ql-pattern option[value=\'\"+this.currKey+\"\']\")[0].selected = true;\n		} catch(e) {}\n		// waiting for the master\'s voice\n	 	$(\"#quicklink\").hide();\n	 	$(\"#quicklink-msg\").hide();\n		this.behaviours();\n	},\n\n	// the worker function\n	refresh : function () {\n		var box = $(\"#list\");\n		box.empty();\n		wet_quicklink.loading(true);\n		$.get(\n	 		\"\",\n	 		{ wet_peex: \"article\", limit: wet_quicklink.rows.toString(), offset: wet_quicklink.firstrow.toString(),\n				dir: wet_quicklink.sortdir, sort: wet_quicklink.crit, search: wet_quicklink.search },\n			function(xml){\n				wet_quicklink.maxrows = $(\"articles\", xml).attr(\"count\");\n\n				$(\"#pager\").html(Math.ceil(wet_quicklink.firstrow/wet_quicklink.rows + 1).toString() + \"/\"\n					+ Math.ceil(wet_quicklink.maxrows/wet_quicklink.rows).toString());\n\n	    			// paint the article table header\n	    			var table = \"<tr><th class=\'ql-id\'>id</th>\"+\n					\"<th class=\'ql-title\'><a rel=\'title\' href=\'#\'>\"+wet_quicklink.gTxt.title+\"</a></th>\"+\n					\"<th class=\'ql-teaser\'><a rel=\'body_html\' href=\'#\'>\"+wet_quicklink.gTxt.body+\"</a></th>\"+\n					\"<th class=\'ql-posted\'><a rel=\'posted\' href=\'#\'>\"+wet_quicklink.gTxt.posted+\"</a></th>\"+\n					\"<th class=\'ql-lastmod\'><a rel=\'lastmod\' href=\'#\'>\"+wet_quicklink.gTxt.modified+\"</a></th>\"+\n					\"<th class=\'ql-section\'><a rel=\'section\' href=\'#\'>\"+wet_quicklink.gTxt.section+\"</a></th>\"+\n					\"<th class=\'ql-category1\'><a rel=\'category1\' href=\'#\'>\"+wet_quicklink.gTxt.category1.replace(/ /g,\"&#160;\")+\"</a></th>\"+\n					\"<th class=\'ql-category2\'><a rel=\'category2\' href=\'#\'>\"+wet_quicklink.gTxt.category2.replace(/ /g,\"&#160;\")+\"</a></th>\"+\n					\"<th class=\'ql-url\'><a rel=\'url\' href=\'#\'>url</a></th></tr>\";\n\n				// parse the XML response\n				$(\"article\", xml).each (\n					function(i) {\n		    			// paint one article row\n		    			table += \"<tr\" +\n						\"><td class=\'ql-id\'>\" + $(\"id\", this).text() +\n						\"</td><td class=\'ql-title\'>\" +\"<a href=\'#\'>\" +\n							wet_quicklink.htmlspecialchars($(\"title\", this).text()) +\"</a>\"+\n						\"</td><td class=\'ql-teaser\'>\" +\n							wet_quicklink.htmlspecialchars($(\"teaser\", this).text())+ \"&#8230;\" +\n						\"</td><td class=\'ql-posted\'>\" +\n							$(\"posted\", this).text().replace(/ [0-9]*:[0-9]*:[0-9]*/, \"\") + // remove hh:mm:ss\n		    			\"</td><td class=\'ql-lastmod\'>\" +\n							$(\"lastmod\", this).text().replace(/ [0-9]*:[0-9]*:[0-9]*/, \"\") +\n						\"</td><td class=\'ql-section\'>\" +\n							wet_quicklink.htmlspecialchars($(\"section\", this).text()) +\n						\"</td><td class=\'ql-category1\'>\" +\n							wet_quicklink.htmlspecialchars($(\"category[level=\'1\']\", this).text()) +\n						\"</td><td class=\'ql-category2\'>\" +\n							wet_quicklink.htmlspecialchars($(\"category[level=\'2\']\", this).text()) +\n						\"</td><td class=\'ql-url\'>\" + $(\"permlink\", this).text() +\n						\"</td></tr>\";\n					}\n				);\n\n				// inject table into ui\n	    			box.html(table);\n\n				// table heads act as ui for sort direction\n				$(\"#list tr th a\")\n					.click( function() {\n						// revert sort order just the second time around\n						if(wet_quicklink.crit == $(this).attr(\"rel\")) {\n							wet_quicklink.sortdir = (wet_quicklink.sortdir == \"desc\" ? \"asc\" : \"desc\");\n						}\n						// define sort criterion\n						wet_quicklink.crit = $(this).attr(\"rel\");\n						wet_quicklink.firstrow = 0;\n						wet_quicklink.refresh();\n						return false;\n					});\n\n				// zebra stripes\n				$(\'#list tr:odd\').addClass(\'odd\');\n\n				// style table heads as indicators for sort direction\n				$(\"#list tr th a[rel=\'\"+wet_quicklink.crit+\"\']\").addClass(wet_quicklink.sortdir);\n				wet_quicklink.loading(false);\n\n				/**\n				 * Insert content at caret position\n				 * @link http://alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript\n				 */\n				function edInsertContent(myField, myValue) {\n					//IE support\n					if (document.selection) {\n						myField.focus();\n						sel = document.selection.createRange();\n						sel.text = myValue;\n						myField.focus();\n					}\n					//MOZILLA/NETSCAPE support\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						var scrollTop = myField.scrollTop;\n						myField.value = myField.value.substring(0, startPos)\n						              + myValue\n				                      + myField.value.substring(endPos, myField.value.length);\n						myField.focus();\n						myField.selectionStart = startPos + myValue.length;\n						myField.selectionEnd = startPos + myValue.length;\n						myField.scrollTop = scrollTop;\n					} else {\n						myField.value += myValue;\n						myField.focus();\n					}\n				}\n\n				function getSel(myField) {\n					//IE support\n					if (document.selection) {\n						return document.selection.createRange().text;\n					}\n					//MOZILLA/NETSCAPE support\n					else if (myField.selectionStart || myField.selectionStart == \'0\') {\n						var startPos = myField.selectionStart;\n						var endPos = myField.selectionEnd;\n						return myField.value.substring(startPos, endPos);\n					} else {\n						return \"\";\n					}\n				}\n\n				// insert permlink on title click\n				$(\'td.ql-title a\').click(\n					function() {\n						// find the insertion target\n						var t = wet_quicklink.target;\n						// build tag\n						var tag = wet_quicklink.pattern;\n						var cell = $(this).parent(); // td > a\n\n						var id = cell.siblings(\".ql-id\").text();  // the article id\n						tag = tag.replace(/{id}/gi, id);\n\n						var url = cell.siblings(\".ql-url\").text();  // the url\n						tag = tag.replace(/{url}/gi, url);\n\n						var title = cell.children(\"a\").text();  // the title\n						tag = tag.replace(/{title}/gi, title);\n\n						tag = tag.replace(/{text}/gi, getSel(t));\n						edInsertContent(t, tag);\n						$(\"#quicklink\").hide();\n						return false;\n					}\n				);\n	 		}\n		);\n	},\n\n	// launch overlay on menu click\n	hook : function () {\n		$(\'#insert-link\').click(\n			function() {\n				wet_quicklink.refresh();\n				$(\"#quicklink\").show();\n				return false;\n			}\n		);\n	},\n\n	// add behaviours\n	behaviours : function() {\n		$(\'input#ql-close\').click(\n			function() {\n				$(\"#quicklink\").hide();\n				return false;\n			}\n		);\n\n		$(\'input#ql-fwd\').click(\n			function() {\n				var here = wet_quicklink.firstrow;\n				if(wet_quicklink.firstrow + wet_quicklink.rows < wet_quicklink.maxrows) {\n					wet_quicklink.firstrow += wet_quicklink.rows;\n				}\n				if(here != wet_quicklink.firstrow) wet_quicklink.refresh();\n				return false;\n			}\n		);\n\n		$(\'input#ql-rev\').click(\n			function() {\n				var here = wet_quicklink.firstrow;\n				if(wet_quicklink.firstrow >= wet_quicklink.rows) {\n					wet_quicklink.firstrow -= wet_quicklink.rows;\n				}\n				if(here != wet_quicklink.firstrow) wet_quicklink.refresh();\n				return false;\n			}\n		);\n\n		$(\'input#ql-search\').keyup(\n			function() {\n				if(this.value != wet_quicklink.search) {\n					wet_quicklink.firstrow = 0;\n					wet_quicklink.search = this.value;\n					setCookie(\"wet_quicklink-search\", this.value);\n\n					try {\n						window.clearTimeout(this.lazy);\n					} catch(e) {}\n					this.lazy = window.setTimeout(wet_quicklink.refresh, 750);\n				}\n			}\n		);\n\n		$(\'select#ql-pattern\').click(\n			function() {\n				var currKey = this.options[this.options.selectedIndex].value;\n				wet_quicklink.pattern = wet_quicklink.patterns[currKey];\n				sendAsyncEvent(\n					{\n						wet_quicklink: \'save_key\',\n						key: currKey\n					}\n				);\n				return false;\n			}\n		);\n	},\n\n	htmlspecialchars : function (s) {\n		s = s.replace(/&/g,\"&amp;\");\n		s = s.replace(/</g,\"&lt;\");\n		s = s.replace(/>/g,\"&gt;\");\n		s = s.replace(/\"/g,\"&quot;\");\n		return s;\n	},\n\n	status : function (s, is_ok) {\n		var msg = $(\"#quicklink-msg\");\n		msg.html(s);\n		if(is_ok) {\n			msg.addClass(\"success\");\n		} /* @todo remove class later */\n		msg.fadeIn(\"slow\");\n		msg.fadeOut(\"slow\");\n	},\n\n	loading : function (is_on) {\n		var msg = $(\"#quicklink-msg\");\n		msg.addClass(\"success\");	 /* @todo remove class later */\n		if(is_on) {\n			msg.fadeIn(\"slow\");\n		} else {\n			msg.fadeOut(\"slow\");\n		}\n	}\n};\n\n$(document).ready(\n	function(){\n		wet_quicklink.hook();\n		wet_quicklink.paint();\n\n		// insertion targets\n		var targets = new Array(\"title\", \"body\", \"excerpt\",\n			\"custom-1\", \"custom-2\", \"custom-3\", \"custom-4\", \"custom-5\",\n			\"custom-6\", \"custom-7\", \"custom-8\", \"custom-9\", \"custom-10\",\n			\"keywords\");\n		var i, l = targets.length;\n		// click handler stores \"active\" textarea/input element\n		for (i = 0; i < l; i++) {\n			var t = $(\"#\"+targets[i]);\n			if(t.length == 0) continue;\n			t.click(function() {\n					wet_quicklink.target = this;\n				}\n			);\n		}\n	}\n);\n/*=*=*=* script ends here */\njs;\n	}\n	exit();\n}\n\nfunction wet_quicklink_css()\n{\n	global $event;\n	if ($event != \'article\') return;\n\n	echo <<<css\n<style type=\"text/css\">\ndiv#quicklink {\n	border: 3px solid #ffcc33;\n	padding: 0.5em;\n	background-color: white;\n	position: absolute;\n	z-index: 999;\n	top: 7em; right: 1em;\n	width: auto; height: auto;\n	max-height: 90%;\n	max-width: 75em;\n}\n\ndiv#quicklink div {\n	overflow: auto;\n}\n\ndiv#quicklink #ql-close {\n	position: absolute;\n	right: 2px; top: 2px;\n	width: 16px; height: 16px;\n	padding: 0px;\n	background-color: #c66;\n	color: #fff;\n}\n\ndiv#quicklink #ql-help {\n	position: absolute;\n	right: 2.5em; top: 4px;\n}\n\ndiv#quicklink table {\n	width: 100%;\n	margin-top: 0.5em;\n	table-layout: fixed;\n}\ndiv#quicklink .ql-id, div#quicklink .ql-url {\n	display: none;\n}\n\ndiv#quicklink td, div#quicklink th {\n	overflow: hidden;\n}\n\ndiv#quicklink .ql-title, div#quicklink .ql-teaser {\n	width: 15em;\n}\n\ndiv#quicklink .ql-section {\n	width: 6em;\n}\n\ndiv#quicklink .ql-posted, div#quicklink .ql-lastmod {\n	width: 7em;\n}\n\ndiv#quicklink .ql-category1, div#quicklink .ql-category2 {\n	width: 8em;\n}\n\ndiv#quicklink tr.odd {\n	background-color: #f8f8ff;\n}\n\ndiv#quicklink td {\n	padding: 0.2em 0;\n}\n\ndiv#quicklink th {\n	padding: 0.2em 0.2em;\n}\n\ndiv#quicklink th a.asc {\n	background: transparent url(txp_img/arrowupdn.gif) no-repeat right 2px;\n	padding-right: 14px;\n}\n\ndiv#quicklink th a.desc {\n	background: transparent url(txp_img/arrowupdn.gif) no-repeat right -18px;\n	padding-right: 14px;\n}\n\n#quicklink-msg {\n	position: absolute;\n	top: 0;\n	right: 0;\n	padding: 0.5em 2em;\n}\n\n#quicklink-msg.success {\n	color: white;\n	background-color: #8c8;\n}\n\n#quicklink-msg.failure {\n	color: white;\n	font-weight: bold;\n	background-color: #f00;\n}\n</style>\n\ncss;\n}\n', 'd2d0b7f72e567b20ad0dccb054fefd42', '#@admin\r\n#@language en-gb\r\nwet_quicklink_insert_link => Insert Link\r\nwet_quicklink_loading => Loading…\r\n#@language de-de\r\nwet_quicklink_insert_link => Link einfügen\r\nwet_quicklink_loading => Laden…', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('wet_recent_comments', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/kategorie/textpattern', '0.4', 'Lists recent comments, both per article and sitewide.', '\n	<h3>wet_recent_comments: Lists recent comments, both per article and site wide.</h3>\n\n	<p><strong>wet_recent_comments</strong> is a single tag. It lists a limited amount of the most recent comments, optionally including the comment message.</p>\n\n	<p><strong>wet_recent_comments</strong> is context sensitive: When <strong>wet_recent_comments</strong> is used in a page template, comments are taken from all articles, while in an article form comments are specifically chosen from the ones concerning the current article.</p>\n\n	<p>Options include:</p>\n\n	<ul>\n	<li>Limit word count in comment message</li>\n		<li>Strip HTML from comment message</li>\n		<li>Customizable comment date presentation</li>\n		<li>Plus the common attributes <code>wraptag</code>, <code>break</code>, <code>class</code>, and <code>labeltag</code></li>\n	</ul>\n\n	<h3>Usage</h3>\n\n<code>&lt;txp:wet_recent_comments /&gt;</code>\n\n	<ul>\n	<li>Display the ten most recent comments in the format  <code>&#60;cite&#62;Commenter\'s name&#60;/cite&#62; (article title)</code>.</li>\n	</ul>\n\n<code>&lt;txp:wet_recent_comments limit=\"5\" showcomment=\"yes\" maxwords=\"25\" striptags=\"yes\" dateformat=\"default\" /&gt;</code>\n\n	<ul>\n	<li>Display the five most recent comments in the format <code>&#60;cite&#62;Commenter\'s name&#60;/cite&#62; &#60;span&#62;comment date&#60;/span&#62; (article title)&#60;p&#62;comment message&#60;/p&#62;</code>.</li>\n		<li>Limit comment message to a maximum of 25 words plus a trailing ellipsis when appropriate.</li>\n		<li>Strip all HTML tags from the message.</li>\n		<li>Use comment date format as set in the preferences.</li>\n	</ul>\n\n<code>&lt;txp:wet_recent_comments dateformat=\"on %B, %d\" /&gt;</code>\n	<ul>\n	<li>Display the ten most recent comments in the format  <code>&#60;cite&#62;Commenter\'s name&#60;/cite&#62; &#60;span&#62;on MONTHNAME, DAY&#60;/span&#62; (article title)&#60;p&#62;comment message&#60;/p&#62;</code>.</li>\n	</ul>\n\n	<p>NB: <code>(article title)</code> is ommited when this tag is used in an article context.</p>\n\n	<h3>Required attributes</h3>\n\n	<p>None.</p>\n\n	<h3>Optional attributes</h3>\n\n	<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>limit</strong> </td>\n			<td> Maximum number of comments to display. </td>\n			<td> <code>10</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>showcomment</strong> </td>\n			<td> Show comment message (yes/no). </td>\n			<td> <code>no</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>striptags</strong> </td>\n			<td> Strip HTML from comment message (yes/no). </td>\n			<td> <code>no</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>maxwords</strong> </td>\n			<td> Truncate messages at this word count and append an ellipsis. </td>\n			<td> <code>50</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>dateformat</strong> </td>\n			<td> If present, the comment&#8217;s date is included in the output. The string is printed literally with some special formatting codes being replaced by the actual values:\n	<ol>\n	<li><a href=\"http://www.php.net/manual/en/function.strftime.php\">strftime conversion specifiers</a>.</li>\n		<li><code>\"default\"</code>: use the comments date format from the preference settings.</li>\n		<li>all other values for <code>txp:posted</code>&#8217;s attribute <a href=\"http://textpattern.net/wiki/index.php?title=Txp:posted_/\">format</a>.</td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>class</strong> </td>\n			<td> <code>class</code> attribute which will be applied to the to the <code>wraptag</code>. </td>\n			<td> <code>wet_recent_comments</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>wraptag</strong> </td>\n			<td> (X)HTML tag used to enclose the list. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#wraptag\">Textbook</a> for further reference.</td>\n			<td> <code>ul</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>break</strong> </td>\n			<td> This string will be used to separate list items. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#break\">Textbook</a> for further reference. </td>\n			<td> <code>li</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>label</strong> </td>\n			<td> This string will be prepended to the list.  See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#label\">Textbook</a> for further reference.</td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>labeltag</strong> </td>\n			<td> (X)HTML tag used to enclose the label. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#labeltag\">Textbook</a> for further reference.</td>\n			<td> Empty </td>\n		</tr>\n	</table>\n\n	<h3>Credits</h3>\n\n	<p><strong>wet_recent_comments</strong> is based on <code>rei_recent_comments</code>, a plugin by <a href=\"http://reinier.biz/\">Reinier Ladan</a>.</p>\n\n	<h3>Licence</h3>\n\n	<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\">Gnu General Public Licence</a>.</p>\n\n\n\n <h4>Find this useful?</h4>\n<p>Show your appreciation and support future development of this plugin by a small donation. Thank you!</p></form>\n<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\">\n<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />\n<input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but04.gif\" border=\"0\" name=\"submit\" alt=\"Make payments with PayPal - it\'s fast, free and secure!\" />\n<input type=\"hidden\" name=\"encrypted\" value=\"-----BEGIN PKCS7-----MIIHsQYJKoZIhvcNAQcEoIIHojCCB54CAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAhVyX+PfUOcjjdjaB/k39YahXXmlrM1z8jh5AjMaBH8oEm2phrlI9nEieGLlRhbp/q6bdgS8QkeYeqCpg92FU3Wgl+9R77mw9ITC1OBD/xiIfHGWwHY6rb8nmIdqbhhHQNOXzrSuE+YqAfe563JYi34EDr9hPLoCUGJF5fWh+dWjELMAkGBSsOAwIaBQAwggEtBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBJtqTkE36PqgIIBCLDdv3eQ6WriZipcOdyJJI7Kx74C8UcDPf03VmoEEe5yDYGf5Czfn8sgiIRjF+szjcgLPjfkSxxy5KcCbD6Px3WwbcTxBEjuol7zP4AFpmjUK6wyKcED4uqgXZRu9eDIS8xuNrl1+O3bcpUk/NMvJfpD0zJFiXI4l/5LuFwomkywzI8hWf7cvH0cH509DDjlxLC+JRy/dRHYpGLRzBmBM3KLHiDjBD6AYBf9OvI5zTk+TKOGwqSkN18Im0OO6IHaasGB3xtMYTssSw6eUEmCXN7Y69S1QqWrZ82BtIGHWDo7Ho9xKxFSBj27Z/SMbvZ500j/lnv/wKR8E4clnraPC8JDvmUaIqHjEqCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MTIwODExNDU1NVowIwYJKoZIhvcNAQkEMRYEFApvMc2fZuC+WaNvkES4c9v4IDt7MA0GCSqGSIb3DQEBAQUABIGAFMKWx3/IG4LZN7B45RFK8lqx/7e1WCjQfD56it6KDuIWk/6jxtvQb1QB43DBFcZvziQ+jb21x8RBMnJBWHiAeGt/ZZa0PIhU04tT3VreAiwvHAsoQ9AJ9ab4Na4Bh2OZsVc7q3qlLvXmAMFmNFdYgFOYwGqpcyqyDnKjZKfbAUM=-----END PKCS7-----\"/>', '\nfunction wet_recent_comments($atts)\n{\n	global $pretext, $thisarticle, $comments_dateformat;\n\n\n	extract(lAtts(array(\n		\'label\' => \'\',\n		\'limit\' => \'10\',\n		\'break\' => \'li\',\n		\'wraptag\' => \'ul\',\n		\'showcomment\' => \'no\',\n		\'striptags\' => \'no\',\n		\'maxwords\'  => 50,\n		\'labeltag\'  => \'\',\n		\'dateformat\' => \'\',\n		\'class\' => __FUNCTION__\n	),$atts));\n\n	$clause = \" where \".PFX.\"txp_discuss.visible=\'1\'\";\n\n    if (!empty($thisarticle[\'thisid\'])) {\n	  $clause .= \" and \".PFX.\"textpattern.ID = \'\".$thisarticle[\'thisid\'].\"\'\";\n	}\n\n	$q = \"select \".PFX.\"txp_discuss.*,\".PFX.\"txp_discuss.posted as comment_posted,\".PFX.\"textpattern.* from \".PFX.\"txp_discuss left join \".PFX.\"textpattern on \".PFX.\"textpattern.ID = \".PFX.\"txp_discuss.parentid\".$clause.\" order by \".PFX.\"txp_discuss.posted desc limit $limit\";\n\n	$rs = getRows($q);\n\n	if ($rs) {\n		foreach ($rs as $a) {\n\n			extract($a);\n\n            if ($showcomment == \'no\') {\n                $message = \'\';\n            } else {\n                if ($striptags != \'no\') $message = strip_tags($message);\n                $wrds = explode(\' \', $message);\n\n                if(count($wrds) > $maxwords){\n                    $message = \'\';\n\n                    for($i=0; $i<$maxwords; $i++) {\n                        $message .= $wrds[$i].\' \';\n                    }\n                    $message = \'<p>\'.$message.\'&#8230;</p>\';\n                } else {\n                    $message = \'<p>\'.$message.\'</p>\';\n                }\n            }\n\n			if( !empty($dateformat) ) {\n				$dateformat = str_replace( \'default\', $comments_dateformat, $dateformat );\n				$comments_date = \' <span>\'.safe_strftime( $dateformat, strtotime($comment_posted) ).\'</span>\';\n			} else {\n				$comments_date = \'\';\n			}\n\n			$out[] = \'<a href=\"\'.permlinkurl_id($ID).\'#c\'.$discussid.\'\"><cite>\'.$name.\'</cite>\'.$comments_date.(empty($thisarticle[\'thisid\']) ? \" ($Title)\" : \'\').\'</a>\'.$message;\n		}\n\n        if (is_array($out)) {\n            return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n        }\n	}\n	return \'\';\n}\n', '\nfunction wet_recent_comments($atts)\n{\n	global $pretext, $thisarticle, $comments_dateformat;\n\n\n	extract(lAtts(array(\n		\'label\' => \'\',\n		\'limit\' => \'10\',\n		\'break\' => \'li\',\n		\'wraptag\' => \'ul\',\n		\'showcomment\' => \'no\',\n		\'striptags\' => \'no\',\n		\'maxwords\'  => 50,\n		\'labeltag\'  => \'\',\n		\'dateformat\' => \'\',\n		\'class\' => __FUNCTION__\n	),$atts));\n\n	$clause = \" where \".PFX.\"txp_discuss.visible=\'1\'\";\n\n    if (!empty($thisarticle[\'thisid\'])) {\n	  $clause .= \" and \".PFX.\"textpattern.ID = \'\".$thisarticle[\'thisid\'].\"\'\";\n	}\n\n	$q = \"select \".PFX.\"txp_discuss.*,\".PFX.\"txp_discuss.posted as comment_posted,\".PFX.\"textpattern.* from \".PFX.\"txp_discuss left join \".PFX.\"textpattern on \".PFX.\"textpattern.ID = \".PFX.\"txp_discuss.parentid\".$clause.\" order by \".PFX.\"txp_discuss.posted desc limit $limit\";\n\n	$rs = getRows($q);\n\n	if ($rs) {\n		foreach ($rs as $a) {\n\n			extract($a);\n\n            if ($showcomment == \'no\') {\n                $message = \'\';\n            } else {\n                if ($striptags != \'no\') $message = strip_tags($message);\n                $wrds = explode(\' \', $message);\n\n                if(count($wrds) > $maxwords){\n                    $message = \'\';\n\n                    for($i=0; $i<$maxwords; $i++) {\n                        $message .= $wrds[$i].\' \';\n                    }\n                    $message = \'<p>\'.$message.\'&#8230;</p>\';\n                } else {\n                    $message = \'<p>\'.$message.\'</p>\';\n                }\n            }\n\n			if( !empty($dateformat) ) {\n				$dateformat = str_replace( \'default\', $comments_dateformat, $dateformat );\n				$comments_date = \' <span>\'.safe_strftime( $dateformat, strtotime($comment_posted) ).\'</span>\';\n			} else {\n				$comments_date = \'\';\n			}\n\n			$out[] = \'<a href=\"\'.permlinkurl_id($ID).\'#c\'.$discussid.\'\"><cite>\'.$name.\'</cite>\'.$comments_date.(empty($thisarticle[\'thisid\']) ? \" ($Title)\" : \'\').\'</a>\'.$message;\n		}\n\n        if (is_array($out)) {\n            return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n        }\n	}\n	return \'\';\n}\n', 'cb3cb230caaca825e74538f1a810ec7f', '', '', 0, 5, 0),
('wet_thumbfilter', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wetthumbfilter-textpattern-thumbnail-gallery', '0.6', 'Displays the thumbnail for articles\' images meeting various filter criteria with an optional link to the articles.', '<h3>Article image thumbnail filter</h3>\n\n<p><strong>wet_thumbfilter</strong> displays the thumbnail for articles’ images meeting various filter criteria with an optional link to the articles.</p>\n\n<h4>Usage:</h4>\n\n<p><code>&lt;txp:wet_thumbfilter /&gt;</code></p>\n\n<h4>Required attributes:</h4>\n\n<p>None.</p>\n\n<h4>Optional attributes:</h4>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>category</strong> </td>\n			<td> Category filter, comma separated list </td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>section</strong> </td>\n			<td> Section filter, comma separated list </td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>offset</strong> </td>\n			<td> A positive integer number. The n-th article with (an) assigned image(s) meeting the filter criteria will be used to determine the first returned thumbnail. </td>\n			<td> 0 </td>\n		</tr>\n		<tr>\n			<td> <strong>sortby</strong> </td>\n			<td> Article field to sort by. Use either “posted” , “LastMod” or other article properties. </td>\n			<td> <code>posted</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>limit</strong> </td>\n			<td> Limits the amount of articles containing thumbnails. Set to &#8220;&#8221; (empty) to return all thumbnails matching the criteria.</td>\n			<td> 1 </td>\n		</tr>\n		<tr>\n			<td> <strong>first</strong> </td>\n			<td> Use only the first image for each article, even if there&#8217;s an image list containing more images.</td>\n			<td> Empty. </td>\n		</tr>\n		<tr>\n			<td> <strong>time</strong> </td>\n			<td> Filters articles by posting date. Possible values: <code>past</code>, <code>future</code>, <code>any</code>.</td>\n			<td> <code>past</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>sortdir</strong> </td>\n			<td> Sorting direction. Use either “asc” for ascending article order, “desc” for descending order or “rand()” for random order. </td>\n			<td> <code>desc</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>class</strong> </td>\n			<td> <code>class</code> attribute which will be applied to the to the <code>wraptag</code>. If <code>wraptag</code> is empty, this attribute will be applied to the <code>&lt;img&gt;</code> tag. </td>\n			<td> <code>wet_thumbfilter</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>align</strong> </td>\n			<td> <code>align</code> attribute which will be applied to the <code>&lt;img&gt;</code> tag. </td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>link</strong> </td>\n			<td> Link to article. Set to “0” to disable the thumbnail&#8217;s link to the article. </td>\n			<td> <code>1</code> </td>\n		</tr>\n		<tr>\n			<td> <strong>style</strong> </td>\n			<td> <code>style</code> attribute which will be applied to the <code>&lt;img&gt;</code> tag. </td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>wraptag</strong> </td>\n			<td> (X)HTML tag used to enclose the list. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#wraptag\" rel=\"nofollow\">Textbook</a> for further reference.</td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>break</strong> </td>\n			<td> This string will be used to separate list items. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#break\" rel=\"nofollow\">Textbook</a> for further reference. </td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>label</strong> </td>\n			<td> This string will be prepended to the list.  See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#label\" rel=\"nofollow\">Textbook</a> for further reference.</td>\n			<td> Empty </td>\n		</tr>\n		<tr>\n			<td> <strong>labeltag</strong> </td>\n			<td> (X)HTML tag used to enclose the label. See <a href=\"http://textpattern.net/wiki/index.php?title=Attributes_Cross_Reference#labeltag\" rel=\"nofollow\">Textbook</a> for further reference.</td>\n			<td> Empty </td>\n		</tr>\n</table>\n\n<h4>Sample use</h4>\n\n<table>\n		<tr>\n			<th>tag </th>\n			<th>function </th>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_thumbfilter category=&quot;events&quot; /&gt;</code> </td>\n			<td> displays the most recent thumbnail belonging to an article in the “events” category. </td>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_thumbfilter category=&quot;events&quot; sortby=&quot;LastMod&quot; offset=&quot;1&quot; /&gt;</code> </td>\n			<td> displays the thumbnail which is associated with an article out of the “events” category. Articles are sorted by the date of last modification. The article at offset 1 (i.e. the second) is chosen. </td>\n		</tr>\n		<tr>\n			<td> <code>&lt;txp:wet_thumbfilter category=&quot;events,fairs&quot; limit=&quot;3&quot; wraptag=&quot;ul&quot; break=&quot;li&quot; label=&quot;Recent pics&quot; labeltag=&quot;h3&quot; /&gt;</code> </td>\n			<td> displays thumbnails from the three most recent articles out of the “events” or “fairs” category. The thumbnails are embedded into an unordered list and labelled by a <code>h3</code>. </td>\n		</tr>\n</table>\n\n<h4>Licence</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\nfunction wet_thumbfilter($atts)\n{\n	global $img_dir,$path_to_site;\n	extract(lAtts(array(\n	// filter criteria\n		\'category\' => \'\',\n		\'section\' => \'\',\n		\'offset\' => \'0\',\n		\'sortby\' => \'posted\',\n		\'sortdir\' => \'desc\',\n		\'limit\'  => \'1\',\n		\'first\' => \'\',\n		\'time\' => \'\',\n	// output modifiers\n		\'link\' => \'1\',\n		\'style\'  => \'\',\n		\'wraptag\'  => \'\',\n		\'break\'  => \'\',\n		\'label\'  => \'\',\n		\'labeltag\'  => \'\',\n		\'class\' => __FUNCTION__,\n		\'align\' => \'\'\n	),$atts));\n\n	if (!function_exists(\'wet_sqlarray_2\'))\n	{\n		function wet_sqlarray_2 ($a)\n		{\n			$a = do_list($a);\n			$a = join(\"\',\'\", doSlash($a));\n			return \"(\'$a\')\";\n		}\n	}\n\n	$clause = \"image <> \'\' and status >= 4\";\n	switch ($time)\n	{\n		case \'any\':\n			break;\n		case \'future\':\n			$clause .= \' and Posted > now()\'; break;\n		default:\n			$clause .= \' and Posted <= now()\'; break;\n	}\n	if (!empty($category)) $clause .= \" and (Category1 in \" . wet_sqlarray_2(doSlash($category)). \" or Category2 in \". wet_sqlarray_2(doSlash($category)). \")\";\n	if (!empty($section)) $clause .= \" and Section in \" . wet_sqlarray_2(doSlash($section));\n	if (!empty($sortby) || !empty($sortdir)) $clause .= \" order by $sortby $sortdir\";\n	if (!is_numeric($offset)) $offset= \'0\';\n	if (is_numeric($limit)) $clause .= \" limit $offset,$limit\";\n	// find all qualified articles\n	$rs = safe_rows_start(\'image, id, title\',\'textpattern\', $clause);\n	if ($rs) {\n		while ($a = nextRow($rs)) {\n			extract($a);\n			$article_id = $id;\n			// find thumbnail file(s) for *one* article\n            if (!empty($first))\n            {\n                list($image) = explode(\',\',$image);\n            }\n			$rst = safe_rows_start(\'*\',\'txp_image\',\"id in \". wet_sqlarray_2($image). \" and thumbnail <> \'0\'\");\n			if ($rst) {\n				while ($t = nextRow($rst)) {\n					extract($t);\n					$image_url = hu.$img_dir.\'/\'.$id.\'t\'.$ext;\n					$image_file = $path_to_site.DS.$img_dir.DS.$id.\'t\'.$ext;\n					if (file_exists($image_file)) {\n						$imgsize = sizeImage($image_file);\n						$tag = \'<img \'.\n							\'src=\"\'.$image_url.\'\" \'.\n							\'alt=\"\'.$title.\'\" \'.\n							\'title=\"\'.$title.\'\" \'.\n							((!empty($style)) ? \'style=\"\'.$style.\'\" \' : \'\').\n							((!empty($align)) ? \'align=\"\'.$align.\'\" \' : \'\').\n							((!empty($class) && empty($wraptag)) ? \'class=\"\'.$class.\'\" \' : \'\').\n							$imgsize.\' />\';\n						if ($link) {\n							$tag = \'<a href=\"\'.permlinkurl_id($article_id).\'\" title=\"\'.$title.\'\">\'.$tag.\'</a>\';\n						}\n						$out[] = $tag;\n					}\n					else\n						$out[] = \"<img src=\\\"./wet_thumbfilter/no-thumbnail-file/$id\\\" />\";\n				}\n			}\n			else\n				$out[] = \"<img src=\\\"./wet_thumbfilter/no-image/$image\\\" />\";\n		}\n	}\n	else\n		$out[] = \'<img src=\"./wet_thumbfilter/no-image\" />\';\n\n	if (!empty($out)) {\n		return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n	}\n	return \'\';\n}\n', '\nfunction wet_thumbfilter($atts)\n{\n	global $img_dir,$path_to_site;\n	extract(lAtts(array(\n	// filter criteria\n		\'category\' => \'\',\n		\'section\' => \'\',\n		\'offset\' => \'0\',\n		\'sortby\' => \'posted\',\n		\'sortdir\' => \'desc\',\n		\'limit\'  => \'1\',\n		\'first\' => \'\',\n		\'time\' => \'\',\n	// output modifiers\n		\'link\' => \'1\',\n		\'style\'  => \'\',\n		\'wraptag\'  => \'\',\n		\'break\'  => \'\',\n		\'label\'  => \'\',\n		\'labeltag\'  => \'\',\n		\'class\' => __FUNCTION__,\n		\'align\' => \'\'\n	),$atts));\n\n	if (!function_exists(\'wet_sqlarray_2\'))\n	{\n		function wet_sqlarray_2 ($a)\n		{\n			$a = do_list($a);\n			$a = join(\"\',\'\", doSlash($a));\n			return \"(\'$a\')\";\n		}\n	}\n\n	$clause = \"image <> \'\' and status >= 4\";\n	switch ($time)\n	{\n		case \'any\':\n			break;\n		case \'future\':\n			$clause .= \' and Posted > now()\'; break;\n		default:\n			$clause .= \' and Posted <= now()\'; break;\n	}\n	if (!empty($category)) $clause .= \" and (Category1 in \" . wet_sqlarray_2(doSlash($category)). \" or Category2 in \". wet_sqlarray_2(doSlash($category)). \")\";\n	if (!empty($section)) $clause .= \" and Section in \" . wet_sqlarray_2(doSlash($section));\n	if (!empty($sortby) || !empty($sortdir)) $clause .= \" order by $sortby $sortdir\";\n	if (!is_numeric($offset)) $offset= \'0\';\n	if (is_numeric($limit)) $clause .= \" limit $offset,$limit\";\n	// find all qualified articles\n	$rs = safe_rows_start(\'image, id, title\',\'textpattern\', $clause);\n	if ($rs) {\n		while ($a = nextRow($rs)) {\n			extract($a);\n			$article_id = $id;\n			// find thumbnail file(s) for *one* article\n            if (!empty($first))\n            {\n                list($image) = explode(\',\',$image);\n            }\n			$rst = safe_rows_start(\'*\',\'txp_image\',\"id in \". wet_sqlarray_2($image). \" and thumbnail <> \'0\'\");\n			if ($rst) {\n				while ($t = nextRow($rst)) {\n					extract($t);\n					$image_url = hu.$img_dir.\'/\'.$id.\'t\'.$ext;\n					$image_file = $path_to_site.DS.$img_dir.DS.$id.\'t\'.$ext;\n					if (file_exists($image_file)) {\n						$imgsize = sizeImage($image_file);\n						$tag = \'<img \'.\n							\'src=\"\'.$image_url.\'\" \'.\n							\'alt=\"\'.$title.\'\" \'.\n							\'title=\"\'.$title.\'\" \'.\n							((!empty($style)) ? \'style=\"\'.$style.\'\" \' : \'\').\n							((!empty($align)) ? \'align=\"\'.$align.\'\" \' : \'\').\n							((!empty($class) && empty($wraptag)) ? \'class=\"\'.$class.\'\" \' : \'\').\n							$imgsize.\' />\';\n						if ($link) {\n							$tag = \'<a href=\"\'.permlinkurl_id($article_id).\'\" title=\"\'.$title.\'\">\'.$tag.\'</a>\';\n						}\n						$out[] = $tag;\n					}\n					else\n						$out[] = \"<img src=\\\"./wet_thumbfilter/no-thumbnail-file/$id\\\" />\";\n				}\n			}\n			else\n				$out[] = \"<img src=\\\"./wet_thumbfilter/no-image/$image\\\" />\";\n		}\n	}\n	else\n		$out[] = \'<img src=\"./wet_thumbfilter/no-image\" />\';\n\n	if (!empty($out)) {\n		return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n	}\n	return \'\';\n}\n', 'b9230e06ec410a9a4575fe58f5252c01', '', '', 0, 5, 0),
('wet_wetfloor', 0, 'Robert Wetzlmayr', 'http://awasteofwords.com/article/wet_wetfloor-textpattern-plugin-image-reflections', '0.2', 'Image reflections for Textpattern', '<h3>Add a “wet floor” reflection effect to images</h3>\n\n<p><strong>wet_wetfloor</strong> is a Textpattern plugin easing the use of Cow&#8217;s <a href=\"http://cow.neondragon.net/stuff/reflection/\" rel=\"nofollow\">reflection.js</a>.</p>\n\n<h4>Usage:</h4>\n\n<p>Include <code>&lt;txp:wet_wetfloor_script /&gt;</code> to load the script somewhere in your page, either in your template&#8217;s <code>&lt;head&gt;</code> element or anywhere else above the first instance of <code>&lt;txp:wet_wetfloor&gt;</code>.</p>\n\n<p>Embrace areas where the effect will be applied to <code>&lt;img&gt;</code> tags with <code>&lt;txp:wet_wetfloor&gt; ... &lt;/txp:wet_wetfloor&gt;</code>. Additionally, you can specify the reflected image&#8217;s height and opacity by two optional attributes:</p>\n\n<p><code>&lt;txp:wet_wetfloor height=&quot;20&quot; opacity=&quot;70&quot;&gt;</code><br />\n<code>&lt;img src=&quot;/images/4.jpg&quot; /&gt;</code><br />\n<code>p. an interstitial paragraph.</code><br />\n<code>&lt;txp:article_image /&gt;</code><br />\n<code>&lt;/txp:wet_wetfloor&gt;</code></p>\n\n<h4>Required attributes:</h4>\n\n<p>None.</p>\n\n<h4>Optional attributes:</h4>\n\n<table>\n		<tr>\n			<th>attribute </th>\n			<th>description </th>\n			<th>default </th>\n		</tr>\n		<tr>\n			<td> <strong>height</strong> </td>\n			<td> Reflection height as percentage of original image size. </td>\n			<td> 50 </td>\n		</tr>\n		<tr>\n			<td> <strong>opacity</strong> </td>\n			<td> The reflected image&#8217;s opacity in percent. </td>\n			<td> 50 </td>\n		</tr>\n</table>\n\n<h4>Licence and Disclaimer</h4>\n\n<p>This plug-in is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', '\n/**\n * txp tag: add \"reflect\", \"ropacity\", and \"rheight\" class to <img> elements\n */\nfunction wet_wetfloor($atts, $thing)\n{\n	global $wet_wetfloor_height;\n	global $wet_wetfloor_opacity;\n\n	extract (lAtts(array(\n		\'height\' => \'\',\n		\'opacity\' => \'\'\n	),$atts));\n\n	$out = parse($thing);\n	$wet_wetfloor_height = $height;\n	$wet_wetfloor_opacity = $opacity;\n	return preg_replace_callback(\'@<img([^>]+)@i\', \"wet_wetfloor_r\", $out);\n}\n\n/* add class \'reflect\' etc. to a single img tag */\nfunction wet_wetfloor_r($tag)\n{\n	global $wet_wetfloor_height;\n	global $wet_wetfloor_opacity;\n\n	$class = \"reflect\";\n	if(!empty($wet_wetfloor_opacity)) $class .= \" ropacity$wet_wetfloor_opacity\";\n	if(!empty($wet_wetfloor_height)) $class .= \" rheight$wet_wetfloor_height\";\n\n	$p = \'/class\\s*=\\s*\\\"(.*?)\\\"/i\';\n	if(preg_match($p, $tag[1])) {\n		$tag[1] = preg_replace($p, \'class=\"$1 \'.$class.\'\"\', $tag[1]);\n	} else {\n		$tag[1] = \' class=\"\'.$class.\'\"\'.$tag[1];\n	}\n	return \"<img$tag[1]\";\n}\n\n/**\n * txp tag: insert script tag for reflection.js\n */\nfunction wet_wetfloor_script($atts, $thing)\n{\n	return \"<script src=\'\".hu.\"?wet_rsrc=reflection_js&amp;v=1.7\' type=\'text/javascript\'></script>\\n\";\n}\n\nif(gps(\'wet_rsrc\') == \'reflection_js\') {\n	while(@ob_end_clean());\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \".date(\"r\", time()+3600));\n	header(\"Cache-Control: public\");\n	echo <<<js\n/**\n * reflection.js v1.7\n *\n * Contributors: Cow http://cow.neondragon.net\n *               Gfx http://www.jroller.com/page/gfx/\n *               Sitharus http://www.sitharus.com\n *               Andreas Linde http://www.andreaslinde.de\n *               Tralala, coder @ http://www.vbulletin.org\n *\n * Freely distributable under MIT-style license.\n */\n\n/* From prototype.js */\ndocument.getElementsByClassName = function(className) {\n	var children = document.getElementsByTagName(\'*\') || document.all;\n	var elements = new Array();\n\n	for (var i = 0; i < children.length; i++) {\n		var child = children[i];\n		var classNames = child.className.split(\' \');\n		for (var j = 0; j < classNames.length; j++) {\n			if (classNames[j] == className) {\n				elements.push(child);\n				break;\n			}\n		}\n	}\n	return elements;\n}\n\nvar Reflection = {\n	defaultHeight : 0.5,\n	defaultOpacity: 0.5,\n\n	add: function(image, options) {\n		Reflection.remove(image);\n\n		doptions = { \"height\" : Reflection.defaultHeight, \"opacity\" : Reflection.defaultOpacity }\n		if (options) {\n			for (var i in doptions) {\n				if (!options[i]) {\n					options[i] = doptions[i];\n				}\n			}\n		} else {\n			options = doptions;\n		}\n\n		try {\n			var d = document.createElement(\'div\');\n			var p = image;\n\n			var classes = p.className.split(\' \');\n			var newClasses = \'\';\n			for (j=0;j<classes.length;j++) {\n				if (classes[j] != \"reflect\") {\n					if (newClasses) {\n						newClasses += \' \'\n					}\n\n					newClasses += classes[j];\n				}\n			}\n\n			var reflectionHeight = Math.floor(p.height*options[\'height\']);\n			var divHeight = Math.floor(p.height*(1+options[\'height\']));\n\n			var reflectionWidth = p.width;\n\n			if (document.all && !window.opera) {\n				/* Fix hyperlinks */\n                if(p.parentElement.tagName == \'A\') {\n	                var d = document.createElement(\'a\');\n	                d.href = p.parentElement.href;\n                }\n\n				/* Copy original image\'s classes & styles to div */\n				d.className = newClasses;\n				p.className = \'reflected\';\n\n				d.style.cssText = p.style.cssText;\n				p.style.cssText = \'vertical-align: bottom\';\n\n				var reflection = document.createElement(\'img\');\n				reflection.src = p.src;\n				reflection.style.width = reflectionWidth+\'px\';\n\n				reflection.style.marginBottom = \"-\"+(p.height-reflectionHeight)+\'px\';\n				reflection.style.filter = \'flipv progid:DXImageTransform.Microsoft.Alpha(opacity=\'+(options[\'opacity\']*100)+\', style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy=\'+(options[\'height\']*100)+\')\';\n\n				d.style.width = reflectionWidth+\'px\';\n				d.style.height = divHeight+\'px\';\n				p.parentNode.replaceChild(d, p);\n\n				d.appendChild(p);\n				d.appendChild(reflection);\n			} else {\n				var canvas = document.createElement(\'canvas\');\n				if (canvas.getContext) {\n					/* Copy original image\'s classes & styles to div */\n					d.className = newClasses;\n					p.className = \'reflected\';\n\n					d.style.cssText = p.style.cssText;\n					p.style.cssText = \'vertical-align: bottom\';\n\n					var context = canvas.getContext(\"2d\");\n\n					canvas.style.height = reflectionHeight+\'px\';\n					canvas.style.width = reflectionWidth+\'px\';\n					canvas.height = reflectionHeight;\n					canvas.width = reflectionWidth;\n\n					d.style.width = reflectionWidth+\'px\';\n					d.style.height = divHeight+\'px\';\n					p.parentNode.replaceChild(d, p);\n\n					d.appendChild(p);\n					d.appendChild(canvas);\n\n					context.save();\n\n					context.translate(0,image.height-1);\n					context.scale(1,-1);\n\n					context.drawImage(image, 0, 0, reflectionWidth, image.height);\n\n					context.restore();\n\n					context.globalCompositeOperation = \"destination-out\";\n					var gradient = context.createLinearGradient(0, 0, 0, reflectionHeight);\n\n					gradient.addColorStop(1, \"rgba(255, 255, 255, 1.0)\");\n					gradient.addColorStop(0, \"rgba(255, 255, 255, \"+(1-options[\'opacity\'])+\")\");\n\n					context.fillStyle = gradient;\n					if (navigator.appVersion.indexOf(\'WebKit\') != -1) {\n						context.fill();\n					} else {\n						context.fillRect(0, 0, reflectionWidth, reflectionHeight*2);\n					}\n				}\n			}\n		} catch (e) {\n	    }\n	},\n\n	remove : function(image) {\n		if (image.className == \"reflected\") {\n			image.className = image.parentNode.className;\n			image.parentNode.parentNode.replaceChild(image, image.parentNode);\n		}\n	}\n}\n\nfunction addReflections() {\n	var rimages = document.getElementsByClassName(\'reflect\');\n	for (i=0;i<rimages.length;i++) {\n		var rheight = null;\n		var ropacity = null;\n\n		var classes = rimages[i].className.split(\' \');\n		for (j=0;j<classes.length;j++) {\n			if (classes[j].indexOf(\"rheight\") == 0) {\n				var rheight = classes[j].substring(7)/100;\n			} else if (classes[j].indexOf(\"ropacity\") == 0) {\n				var ropacity = classes[j].substring(8)/100;\n			}\n		}\n\n		Reflection.add(rimages[i], { height: rheight, opacity : ropacity});\n	}\n}\n\nvar previousOnload = window.onload;\nwindow.onload = function () { if(previousOnload) previousOnload(); addReflections(); }\njs;\n	exit();\n}\n', '\n/**\n * txp tag: add \"reflect\", \"ropacity\", and \"rheight\" class to <img> elements\n */\nfunction wet_wetfloor($atts, $thing)\n{\n	global $wet_wetfloor_height;\n	global $wet_wetfloor_opacity;\n\n	extract (lAtts(array(\n		\'height\' => \'\',\n		\'opacity\' => \'\'\n	),$atts));\n\n	$out = parse($thing);\n	$wet_wetfloor_height = $height;\n	$wet_wetfloor_opacity = $opacity;\n	return preg_replace_callback(\'@<img([^>]+)@i\', \"wet_wetfloor_r\", $out);\n}\n\n/* add class \'reflect\' etc. to a single img tag */\nfunction wet_wetfloor_r($tag)\n{\n	global $wet_wetfloor_height;\n	global $wet_wetfloor_opacity;\n\n	$class = \"reflect\";\n	if(!empty($wet_wetfloor_opacity)) $class .= \" ropacity$wet_wetfloor_opacity\";\n	if(!empty($wet_wetfloor_height)) $class .= \" rheight$wet_wetfloor_height\";\n\n	$p = \'/class\\s*=\\s*\\\"(.*?)\\\"/i\';\n	if(preg_match($p, $tag[1])) {\n		$tag[1] = preg_replace($p, \'class=\"$1 \'.$class.\'\"\', $tag[1]);\n	} else {\n		$tag[1] = \' class=\"\'.$class.\'\"\'.$tag[1];\n	}\n	return \"<img$tag[1]\";\n}\n\n/**\n * txp tag: insert script tag for reflection.js\n */\nfunction wet_wetfloor_script($atts, $thing)\n{\n	return \"<script src=\'\".hu.\"?wet_rsrc=reflection_js&amp;v=1.7\' type=\'text/javascript\'></script>\\n\";\n}\n\nif(gps(\'wet_rsrc\') == \'reflection_js\') {\n	while(@ob_end_clean());\n	header(\"Content-Type: text/javascript; charset=utf-8\");\n	header(\"Expires: \".date(\"r\", time()+3600));\n	header(\"Cache-Control: public\");\n	echo <<<js\n/**\n * reflection.js v1.7\n *\n * Contributors: Cow http://cow.neondragon.net\n *               Gfx http://www.jroller.com/page/gfx/\n *               Sitharus http://www.sitharus.com\n *               Andreas Linde http://www.andreaslinde.de\n *               Tralala, coder @ http://www.vbulletin.org\n *\n * Freely distributable under MIT-style license.\n */\n\n/* From prototype.js */\ndocument.getElementsByClassName = function(className) {\n	var children = document.getElementsByTagName(\'*\') || document.all;\n	var elements = new Array();\n\n	for (var i = 0; i < children.length; i++) {\n		var child = children[i];\n		var classNames = child.className.split(\' \');\n		for (var j = 0; j < classNames.length; j++) {\n			if (classNames[j] == className) {\n				elements.push(child);\n				break;\n			}\n		}\n	}\n	return elements;\n}\n\nvar Reflection = {\n	defaultHeight : 0.5,\n	defaultOpacity: 0.5,\n\n	add: function(image, options) {\n		Reflection.remove(image);\n\n		doptions = { \"height\" : Reflection.defaultHeight, \"opacity\" : Reflection.defaultOpacity }\n		if (options) {\n			for (var i in doptions) {\n				if (!options[i]) {\n					options[i] = doptions[i];\n				}\n			}\n		} else {\n			options = doptions;\n		}\n\n		try {\n			var d = document.createElement(\'div\');\n			var p = image;\n\n			var classes = p.className.split(\' \');\n			var newClasses = \'\';\n			for (j=0;j<classes.length;j++) {\n				if (classes[j] != \"reflect\") {\n					if (newClasses) {\n						newClasses += \' \'\n					}\n\n					newClasses += classes[j];\n				}\n			}\n\n			var reflectionHeight = Math.floor(p.height*options[\'height\']);\n			var divHeight = Math.floor(p.height*(1+options[\'height\']));\n\n			var reflectionWidth = p.width;\n\n			if (document.all && !window.opera) {\n				/* Fix hyperlinks */\n                if(p.parentElement.tagName == \'A\') {\n	                var d = document.createElement(\'a\');\n	                d.href = p.parentElement.href;\n                }\n\n				/* Copy original image\'s classes & styles to div */\n				d.className = newClasses;\n				p.className = \'reflected\';\n\n				d.style.cssText = p.style.cssText;\n				p.style.cssText = \'vertical-align: bottom\';\n\n				var reflection = document.createElement(\'img\');\n				reflection.src = p.src;\n				reflection.style.width = reflectionWidth+\'px\';\n\n				reflection.style.marginBottom = \"-\"+(p.height-reflectionHeight)+\'px\';\n				reflection.style.filter = \'flipv progid:DXImageTransform.Microsoft.Alpha(opacity=\'+(options[\'opacity\']*100)+\', style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy=\'+(options[\'height\']*100)+\')\';\n\n				d.style.width = reflectionWidth+\'px\';\n				d.style.height = divHeight+\'px\';\n				p.parentNode.replaceChild(d, p);\n\n				d.appendChild(p);\n				d.appendChild(reflection);\n			} else {\n				var canvas = document.createElement(\'canvas\');\n				if (canvas.getContext) {\n					/* Copy original image\'s classes & styles to div */\n					d.className = newClasses;\n					p.className = \'reflected\';\n\n					d.style.cssText = p.style.cssText;\n					p.style.cssText = \'vertical-align: bottom\';\n\n					var context = canvas.getContext(\"2d\");\n\n					canvas.style.height = reflectionHeight+\'px\';\n					canvas.style.width = reflectionWidth+\'px\';\n					canvas.height = reflectionHeight;\n					canvas.width = reflectionWidth;\n\n					d.style.width = reflectionWidth+\'px\';\n					d.style.height = divHeight+\'px\';\n					p.parentNode.replaceChild(d, p);\n\n					d.appendChild(p);\n					d.appendChild(canvas);\n\n					context.save();\n\n					context.translate(0,image.height-1);\n					context.scale(1,-1);\n\n					context.drawImage(image, 0, 0, reflectionWidth, image.height);\n\n					context.restore();\n\n					context.globalCompositeOperation = \"destination-out\";\n					var gradient = context.createLinearGradient(0, 0, 0, reflectionHeight);\n\n					gradient.addColorStop(1, \"rgba(255, 255, 255, 1.0)\");\n					gradient.addColorStop(0, \"rgba(255, 255, 255, \"+(1-options[\'opacity\'])+\")\");\n\n					context.fillStyle = gradient;\n					if (navigator.appVersion.indexOf(\'WebKit\') != -1) {\n						context.fill();\n					} else {\n						context.fillRect(0, 0, reflectionWidth, reflectionHeight*2);\n					}\n				}\n			}\n		} catch (e) {\n	    }\n	},\n\n	remove : function(image) {\n		if (image.className == \"reflected\") {\n			image.className = image.parentNode.className;\n			image.parentNode.parentNode.replaceChild(image, image.parentNode);\n		}\n	}\n}\n\nfunction addReflections() {\n	var rimages = document.getElementsByClassName(\'reflect\');\n	for (i=0;i<rimages.length;i++) {\n		var rheight = null;\n		var ropacity = null;\n\n		var classes = rimages[i].className.split(\' \');\n		for (j=0;j<classes.length;j++) {\n			if (classes[j].indexOf(\"rheight\") == 0) {\n				var rheight = classes[j].substring(7)/100;\n			} else if (classes[j].indexOf(\"ropacity\") == 0) {\n				var ropacity = classes[j].substring(8)/100;\n			}\n		}\n\n		Reflection.add(rimages[i], { height: rheight, opacity : ropacity});\n	}\n}\n\nvar previousOnload = window.onload;\nwindow.onload = function () { if(previousOnload) previousOnload(); addReflections(); }\njs;\n	exit();\n}\n', 'db1251560d41bf33206588d39e3746fe', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('SimplePie Plugin', 0, 'Ryan Parman Geoffrey Sneddon and Mike Small', 'http://simplepie.org', '1.2.2', 'Textpattern plugin for SimplePie: a super-fast, easy-to-use RSS and Atom parser.', '<p><strong>SimplePie Plugin for Textpattern</strong></p><p><font color=\"#ff0000\">Changes from version 1.2.1 to 1.2.2 highlighted in red.&nbsp;</font></p> <p>SimplePie Plugin for Textpattern adds one tag to your Textpattern installation: the &lt;txp:feed&gt; tag. Here&rsquo;s how you use it:</p> <ol><li>&lt;txp:feed&gt;http://example.com/feed.xml&lt;/txp:feed&gt; &mdash; To load a feed on your Textpattern pages, simply wrap the feed URL with &lt;txp:feed&gt; tags. Doing this with no attributes will display the default way: <ul> 		<li>An &lt;h3&gt; containing the feed&rsquo;s title, linked back to the originating site.</li> 		<li>An unordered list, containing all of the news items in the feed.</li> 		<li>The news item&rsquo;s title, linked back to the originating post.</li> 	</ul><ul> 		<li>The full HTML description for each news item.</li> </ul> </li> 	 		<li><strong><em>items</em></strong> attribute &mdash; Limits the number of items returned. If you set this value to 5, then you&rsquo;ll get back the 5 most recent posts. If there&rsquo;s a feed with fewer than 5 posts, SimplePie will return all of them. Defaults to all.</li> 		<li><strong><em>showdesc</em></strong> attribute &mdash; Determines whether the description should be shown or not. If set to false, descriptions are omitted, and the ordered list will display only the linked item titles with no special formatting. Defaults to true.</li> 		<li><em><strong>showdate</strong></em> attribute &mdash; Displays the date of the news item. Accepts anything that&rsquo;s allowed in PHP&rsquo;s <a href=\"http://php.net/date\">date()</a> function. Defaults to blank.</li> 		<li><strong><em>shortdesc</em></strong> attribute &mdash; Strips all tags from the item&rsquo;s description and limits the number of characters that are displayed. Accepts any numeric value. If more characters are allowed than are in the description, the entire description will be displayed. If the text wasn&rsquo;t cut at the end of a sentence (ending with a period, exclamation point, or question mark), an ellipsis will be added to the end of the text. Defaults to all characters.</li> 		<li><strong><em>showtitle</em></strong> attribute &mdash; Determines whether the built-in feed title is displayed or not.  Defaults to true.</li> 		<li><strong><em>alttitle</em></strong> attribute &mdash; Displays a custom title in place of the feed&rsquo;s built-in title.  <font color=\"#ff0000\">If &quot;-1&quot; is entered no title is displayed (removes the built-in title too).</font> Defaults to blank.</li><li><font color=\"#ff0000\"><em><strong>h</strong></em> attribute - Define the html header used for the title (eg h4) defaults to h3.</font></li><li><font color=\"#ff0000\"><em><strong>listtype</strong></em> attribute - defines the list type. Eg listtype=&quot;ol&quot; will give an ordered list. Defaults to &quot;ul&quot;.</font><br /></li> <li><em><strong>error</strong></em> keyword &mdash; Displays a custom error message for when there is a problem retrieving the feed. Defaults to the standard error messages. 	<p>&nbsp;You can find more documentation at the <a href=\"http://simplepie.org/docs/installation/textpattern/\">SimplePie Plugin for Textpattern</a> page.</p>    </li></ol>', 'global $txpcfg;\r\nif (!class_exists(\'SimplePie\')) require($txpcfg[\'txpath\'].\'/lib/simplepie.inc\');\r\n\r\nfunction feed($atts, $thing=\'\') {\r\n	global $txpcfg;\r\n	$BLOGROOT = explode(\'/index.php\', $_SERVER[\"PHP_SELF\"]);\r\n	$BLOGROOT = $BLOGROOT[0];\r\n	$TXPROOT = $BLOGROOT . \'/textpattern\';\r\n\r\n	$input = $thing;\r\n	$argv = $atts;\r\n\r\n	$feed = new SimplePie();\r\n	$feed->set_feed_url($input);\r\n	$feed->set_cache_location($txpcfg[\'txpath\'] . \'/lib/simplepie_cache\');\r\n	$success = $feed->init();\r\n	\r\n	## define some optional defaults\r\n	if (!isset($argv[\'listtype\']) || empty($argv[\'listtype\'])) {\r\n			$argv[\'listtype\']=\"ul\";\r\n	} \r\n	if (!isset($argv[\'h\']) || empty($argv[\'h\'])) {\r\n			$argv[\'h\']=\"h3\";\r\n	} \r\n	##\r\n	if ($success && $feed->data) {\r\n		$flink = $feed->get_permalink();\r\n		$ftitle = $feed->get_title();\r\n\r\n		$output=\'\';\r\n		$output .= \'<div class=\"simplepie\">\';\r\n		if (!isset($argv[\'showtitle\']) || empty($argv[\'showtitle\']) || $argv[\'showtitle\'] == \"true\") {\r\n			if($argv[\'alttitle\'] == -1) {\r\n				## no title\r\n			} elseif (isset($argv[\'alttitle\']) && !empty($argv[\'alttitle\'])) {\r\n				if ($ftitle != \'\' && $flink != \'\') {\r\n					$output .= \"<{$argv[\'h\']}><a href=\\\"$flink\\\">\" . $argv[\'alttitle\'] . \"</a></{$argv[\'h\']}>\";\r\n				} elseif ($ftitle != \'\') {\r\n					$output .= \"<{$argv[\'h\']}>\" . $argv[\'alttitle\'] . \"</{$argv[\'h\']}>\";\r\n				}\r\n			} else {\r\n				if ($ftitle != \'\' && $flink != \'\') $output .= \"<h3><a href=\\\"$flink\\\">$ftitle</a></h3>\";\r\n				else if ($ftitle != \'\') $output .= \"<h3>$ftitle</h3>\";\r\n			}\r\n		}\r\n		if (!isset($argv[\'listtype\']) || empty($argv[\'listtype\'])) {\r\n			$argv[\'listtype\']=\"ul\";\r\n		} \r\n		$output .= \"<\".$argv[\'listtype\'].\">\";\r\n		\r\n		$max = $feed->get_item_quantity();\r\n		if (isset($argv[\'items\']) && !empty($argv[\'items\'])) $max = $feed->get_item_quantity($argv[\'items\']);\r\n\r\n		for($x=0; $x<$max; $x++) {\r\n			$item = $feed->get_item($x);\r\n			$link = $item->get_permalink();\r\n			$title = StupefyEntities($item->get_title());\r\n			$full_desc = StupefyEntities($item->get_description());\r\n			$desc = $full_desc;\r\n\r\n			if (isset($argv[\'shortdesc\']) && !empty($argv[\'shortdesc\'])) {\r\n				$suffix = \'...\';\r\n				$short_desc = trim(str_replace(\"\\n\", \' \', str_replace(\"\\r\", \' \', strip_tags(StupefyEntities($item->get_description())))));\r\n				$desc = substr($short_desc, 0, $argv[\'shortdesc\']);\r\n				$lastchar = substr($desc, -1, 1);\r\n				if ($lastchar == \'.\' || $lastchar == \'!\' || $lastchar == \'?\') $suffix=\'\';\r\n				$desc .= $suffix;\r\n			}\r\n\r\n			if (isset($argv[\'showdesc\']) && !empty($argv[\'showdesc\']) && $argv[\'showdesc\']===\'false\') {\r\n				if (isset($argv[\'showdate\']) && !empty($argv[\'showdate\'])) {\r\n					$output .= \"<li><a href=\\\"$link\\\">$title</a> <span class=\\\"date\\\">\" . $item->get_date($argv[\'showdate\']) . \"</span></li>\";\r\n				} else {\r\n					$output .= \"<li><a href=\\\"$link\\\">$title</a></li>\";\r\n				}\r\n			} else {\r\n				if (isset($argv[\'showdate\']) && !empty($argv[\'showdate\'])) {\r\n					$output .= \"<li><strong><a href=\\\"$link\\\">$title</a> <span class=\\\"date\\\">\" . $item->get_date($argv[\'showdate\']) . \"</span></strong><br />$desc</li>\";\r\n				} else {\r\n					$output .= \"<li><strong><a href=\\\"$link\\\">$title</a></strong><br />$desc</li>\";\r\n				}\r\n			}\r\n		}\r\n\r\n		$output .= \"</\".$argv[\'listtype\'].\">\";;\r\n		$output .= \'</div>\';\r\n	}\r\n	else {\r\n		if (isset($argv[\'error\']) && !empty($argv[\'error\'])) $output = $argv[\'error\'];\r\n		else if (isset($feed->error)) $output = $feed->error;\r\n	}\r\n\r\n	return $output;\r\n}\r\n\r\n// SmartyPants 1.5.1 changes rolled in May 2004 by Alex Rosenberg, http://monauraljerk.org/smartypants-php/\r\nfunction StupefyEntities($s = \'\') {\r\n	$inputs = array(\'&#8211;\', \'&#8212;\', \'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8230;\', \'&#91;\', \'&#93;\');\r\n	$outputs = array(\'-\', \'--\', \"\'\", \"\'\", \'\"\', \'\"\', \'...\', \'[\', \']\');\r\n	$s = str_replace($inputs, $outputs, $s);\r\n	return $s;\r\n}', 'global $txpcfg;\r\nif (!class_exists(\'SimplePie\')) require($txpcfg[\'txpath\'].\'/lib/simplepie.inc\');\r\n\r\nfunction feed($atts, $thing=\'\') {\r\n	global $txpcfg;\r\n	$BLOGROOT = explode(\'/index.php\', $_SERVER[\"PHP_SELF\"]);\r\n	$BLOGROOT = $BLOGROOT[0];\r\n	$TXPROOT = $BLOGROOT . \'/textpattern\';\r\n\r\n	$input = $thing;\r\n	$argv = $atts;\r\n\r\n	$feed = new SimplePie();\r\n	$feed->set_feed_url($input);\r\n	$feed->set_cache_location($txpcfg[\'txpath\'] . \'/lib/simplepie_cache\');\r\n	$success = $feed->init();\r\n	\r\n	## define some optional defaults\r\n	if (!isset($argv[\'listtype\']) || empty($argv[\'listtype\'])) {\r\n			$argv[\'listtype\']=\"ul\";\r\n	} \r\n	if (!isset($argv[\'h\']) || empty($argv[\'h\'])) {\r\n			$argv[\'h\']=\"h3\";\r\n	} \r\n	##\r\n	if ($success && $feed->data) {\r\n		$flink = $feed->get_permalink();\r\n		$ftitle = $feed->get_title();\r\n\r\n		$output=\'\';\r\n		$output .= \'<div class=\"simplepie\">\';\r\n		if (!isset($argv[\'showtitle\']) || empty($argv[\'showtitle\']) || $argv[\'showtitle\'] == \"true\") {\r\n			if($argv[\'alttitle\'] == -1) {\r\n				## no title\r\n			} elseif (isset($argv[\'alttitle\']) && !empty($argv[\'alttitle\'])) {\r\n				if ($ftitle != \'\' && $flink != \'\') {\r\n					$output .= \"<{$argv[\'h\']}><a href=\\\"$flink\\\">\" . $argv[\'alttitle\'] . \"</a></{$argv[\'h\']}>\";\r\n				} elseif ($ftitle != \'\') {\r\n					$output .= \"<{$argv[\'h\']}>\" . $argv[\'alttitle\'] . \"</{$argv[\'h\']}>\";\r\n				}\r\n			} else {\r\n				if ($ftitle != \'\' && $flink != \'\') $output .= \"<h3><a href=\\\"$flink\\\">$ftitle</a></h3>\";\r\n				else if ($ftitle != \'\') $output .= \"<h3>$ftitle</h3>\";\r\n			}\r\n		}\r\n		if (!isset($argv[\'listtype\']) || empty($argv[\'listtype\'])) {\r\n			$argv[\'listtype\']=\"ul\";\r\n		} \r\n		$output .= \"<\".$argv[\'listtype\'].\">\";\r\n		\r\n		$max = $feed->get_item_quantity();\r\n		if (isset($argv[\'items\']) && !empty($argv[\'items\'])) $max = $feed->get_item_quantity($argv[\'items\']);\r\n\r\n		for($x=0; $x<$max; $x++) {\r\n			$item = $feed->get_item($x);\r\n			$link = $item->get_permalink();\r\n			$title = StupefyEntities($item->get_title());\r\n			$full_desc = StupefyEntities($item->get_description());\r\n			$desc = $full_desc;\r\n\r\n			if (isset($argv[\'shortdesc\']) && !empty($argv[\'shortdesc\'])) {\r\n				$suffix = \'...\';\r\n				$short_desc = trim(str_replace(\"\\n\", \' \', str_replace(\"\\r\", \' \', strip_tags(StupefyEntities($item->get_description())))));\r\n				$desc = substr($short_desc, 0, $argv[\'shortdesc\']);\r\n				$lastchar = substr($desc, -1, 1);\r\n				if ($lastchar == \'.\' || $lastchar == \'!\' || $lastchar == \'?\') $suffix=\'\';\r\n				$desc .= $suffix;\r\n			}\r\n\r\n			if (isset($argv[\'showdesc\']) && !empty($argv[\'showdesc\']) && $argv[\'showdesc\']===\'false\') {\r\n				if (isset($argv[\'showdate\']) && !empty($argv[\'showdate\'])) {\r\n					$output .= \"<li><a href=\\\"$link\\\">$title</a> <span class=\\\"date\\\">\" . $item->get_date($argv[\'showdate\']) . \"</span></li>\";\r\n				} else {\r\n					$output .= \"<li><a href=\\\"$link\\\">$title</a></li>\";\r\n				}\r\n			} else {\r\n				if (isset($argv[\'showdate\']) && !empty($argv[\'showdate\'])) {\r\n					$output .= \"<li><strong><a href=\\\"$link\\\">$title</a> <span class=\\\"date\\\">\" . $item->get_date($argv[\'showdate\']) . \"</span></strong><br />$desc</li>\";\r\n				} else {\r\n					$output .= \"<li><strong><a href=\\\"$link\\\">$title</a></strong><br />$desc</li>\";\r\n				}\r\n			}\r\n		}\r\n\r\n		$output .= \"</\".$argv[\'listtype\'].\">\";;\r\n		$output .= \'</div>\';\r\n	}\r\n	else {\r\n		if (isset($argv[\'error\']) && !empty($argv[\'error\'])) $output = $argv[\'error\'];\r\n		else if (isset($feed->error)) $output = $feed->error;\r\n	}\r\n\r\n	return $output;\r\n}\r\n\r\n// SmartyPants 1.5.1 changes rolled in May 2004 by Alex Rosenberg, http://monauraljerk.org/smartypants-php/\r\nfunction StupefyEntities($s = \'\') {\r\n	$inputs = array(\'&#8211;\', \'&#8212;\', \'&#8216;\', \'&#8217;\', \'&#8220;\', \'&#8221;\', \'&#8230;\', \'&#91;\', \'&#93;\');\r\n	$outputs = array(\'-\', \'--\', \"\'\", \"\'\", \'\"\', \'\"\', \'...\', \'[\', \']\');\r\n	$s = str_replace($inputs, $outputs, $s);\r\n	return $s;\r\n}', 'c2d02c31a3142dee65aed68f76355aeb', '', '', 0, 5, 0),
('pat_if_amp', 0, 'Patrick LEFEVRE (original idea by Michael K Pate)', 'https://github.com/cara-tm/', '0.3.7', 'A Textpattern Conditional Plugin for Google\'s Accelerated Mobile Pages Project (AMP) ', '<h1>pat_if_amp</h1>\n\n<p>AMP pages for Textpattern CMS.</p>\n\n<p>This conditional tag examines the URL of the current page and determines if the URL ends in \'amp.\' This allows for a custom page to be rendered using the standards for Google\'s Accelerated Mobile Pages (AMP) project.</p>\n\n<h2>Links</h2>\n\n<ul>\n<li><a href=\"https://github.com/cara-tm/pat_if_amp\" target=\"_blank\">GitHub Code Repository</a></li>\n<li><a href=\"https://github.com/cara-tm/pat_if_amp/blob/master/pat_if_amp_v3.5_zip.txt\" target=\"_blank\">Download</a></li>\n<li><a href=\"http://ampdemo.cmsstyles.com/\" target=\"_blank\">Demo Site for initial plugin (mkp_if_amp)</a></li>\n</ul>\n\n<p>Don\'t forget to check out my other plugin that\'s also Google AMP compatible: <a href=\"https://github.com/cara-tm/pat_article_social\" target=\"_blank\">pat_article_social</a></p>\n\n<h2>pat_if_amp</h2>\n\n<h3>Attributes (v3.3 onwards)</h3>\n\n<ul>\n<li><code>url</code> <em>string</em> (optional): The location (URL) of the website. Default: <code>hu</code> (website URL as set in the Textpattern preferences panel).</li>\n<li><code>subdomain</code> <em>string</em> (optional): Sets the subdomain name of where to redirect to. Default: <code>amp</code>.</li>\n<li><code>permlink</code> <em>boolean</em> (optional): if set to <code>0</code> (false) the redirection for the same article\'s URL mode within the subdomain will be not made. Default: <code>1</code> (true).</li>\n</ul>\n\n<h3>Example</h3>\n\n<p>Place code similar to below within your individual article template header, so that the AMP page can be properly detected:</p>\n\n<pre><code> &lt;txp:if_individual_article&gt;\n    &lt;link rel=\"canonical\" href=\"&lt;txp:permlink />\"&gt;\n    &lt;link rel=\"amphtml\" href=\"&lt;txp:permlink /&gt;/amp/\"&gt;\n&lt;/txp:if_individual_article&gt;\n</code></pre>\n\n<p>Place something similar to below within your individual article template to display the alternative formatting:</p>\n\n<pre><code> &lt;txp:pat_if_amp&gt;\n    &lt;txp:output_form form=\"amp_page\" /&gt;\n&lt;txp:else /&gt;\n    &lt;txp:output_form form=\"standard_page\" /&gt;\n&lt;/txp:pat_if_amp&gt;\n</code></pre>\n\n<h2>Textattern variable usage</h2>\n\n<p>A <code>&lt;txp:variable name=\"pat_amp\" /&gt;</code> with a true/false value (<code>1</code> or <code>0</code>) is automatically generated, for testing when this is an AMP context or not.</p>\n\n<h3>Usage</h3>\n\n<pre><code> &lt;txp:if_variable name=\"pat_amp\" value=\"1\"&gt;\n    AMP context\n&lt;txp:else /&gt;\n    Normal context\n&lt;/txp:if_variable&gt;\n</code></pre>\n\n<h3>Example</h3>\n\n<pre><code> &lt;txp:if_variable name=\"pat_amp\" value=\"1\"&gt;\n    ...Do some AMP things...\n    &lt;a href=\"&lt;txp:site_url /&gt;category/&lt;txp:category name=\'&lt;txp:category1 /&gt;\' /&gt;?amp\"&gt;AMP Category 1 Link&lt;/a&gt;\n&lt;txp:else /&gt;\n    ...Do some normal (non-AMP) things...\n&lt;/txp:if_variable&gt;\n</code></pre>\n\n<h2>pat_amp_sanitize (v3.5 onwards)</h2>\n\n<p>A tag to render article text content without any inline styles. To use as a drop-in replacement of the standard Textpattern <code>&lt;txp:body /&gt;</code> tag and/or <code>&lt;txp:excerpt /&gt;</code> tag.</p>\n\n<h3>Attributes</h3>\n\n<ul>\n<li><code>content</code> <em>string</em> (optional): choose either <code>body</code> or <code>excerpt</code> article content to sanitize. Default: <code>body</code>.</li>\n</ul>\n\n<h3>Usage</h3>\n\n<p>In an article Form context:</p>\n\n<pre><code>&lt;txp:pat_amp_sanitize content=\"excerpt\" /&gt;</code></pre>\n\n<h2>pat_amp_redirect (v3.0 onwards)</h2>\n\n<p>A simple tag for web redirection to a subdomain name. Same behaviour as used in <em>The Guardian</em> website. Must be used with a mobiles detection script (i.e. <a href=\"http://www.greatoceanmedia.com.au/txp/?plugin=adi_mobile\" target=\"_blank\">adi_mobile plugin</a>). Some web designers could choose to use a subdomain for their mobiles websites (may be not recommended, but the official AMP Blog is located in a subdomain, see: <a href=\"https://amphtml.wordpress.com\" target=\"_blank\">https://amphtml.wordpress.com</a>). In this case, <strong>the Textpattern multi-sites capacities makes things easier</strong>.</p>\n\n<p><strong>Note:</strong> Doesn\'t work on local server installations, only on live servers.</p>\n\n<h3>Usage</h3>\n\n<pre><code>&lt;txp:pat_amp_redirect url=\"\" subdomain=\"\" permlink=\"\" /&gt;</code></pre>\n\n<p><strong>Good advice</strong>: Copy the <code>config.php</code> file of the main site into the subdomain configuration folder in order to share the same database. So, all articles written by the Copy Editors will be sent automatically to the \'AMP powered website\' from only one Textpattern administration interface attached to the \'normal\' website. But designers have a separate and cleaner space to build their AMP pages.</p>\n\n<h3>Attributes</h3>\n<ul>\n<li><code>url</code> <em>string</em> (optional): The location (URL) of the website. Default: <code>hu</code> (website URL as set in the Textpattern preferences panel).</li>\n<li><code>subdomain</code> <em>string</em> (optional): Sets the subdomain name of where to redirect to. Default: <code>amp</code>.</li>\n<li><code>permlink</code> <em>boolean</em> (optional): (v3.2 onwards) Choose to redirect the same individual article\'s title URL within the subdomain. Default: <code>0</code> (false).</li>\n</ul>\n\n<h3>Example</h3>\n\n<pre><code>&lt;txp:pat_amp_redirect subdomain=\"amp\" permlink=\"1\" /&gt;</code></pre>\n\n<p>In this case, if your main domain is <code>example.com</code>, it redirects to the <code>amp.example.com</code> subdomain and uses the same individual article\'s URL from the main domain.</p>\n\n<h3>Release history</h3>\n<ul>\n<li>Version 1.0: March 24th, 2016</li>\n<li>Version 2.0: April 5th, 2016. Strongest support for different systems with in build GLOBAL variable. Added a Textpattern variable that sniffs for a <code>?amp</code> query in URLs.</li>\n<li>Version 3.0: April 6th, 2016. Added a new Textpattern tag for subdomain redirection.</li>\n<li>Version 3.1: April 30th, 2016. Better context recognition and better variable creation. Variable name changed to <code>pat_amp</code>.</li>\n<li>Version 3.2: May 2nd, 2016. Support for same individual article\'s URL within the subdomain.</li>\n<li>Version 3.3: 3rd, 2016. Support for redirection to a subdomain with the same article\'s title URL when addresses have a <code>/amp</code> or <code>?amp</code> query.</li>\n<li>Version 3.5: January 13th 2017. New tag added <code><txp:pat_amp_sanitize /></code> that removes any inline CSS style within an article\'s text contents (body/excerpt). Thanks whocarez.</li>\n<li>May 9th 2017. Redistribution as \'pat_if_amp\'.</li>\n<li>Version 3.6: August 10th 2017. Fix for PHP 5.4.</li>\n<li>Version 3.7: April 21th 2018. Fix a misunderstood with some too closest words of \'amp\' into URLs (e.g. \'camp\'). Thank you Heather.</li>\n</ul>\n\n<h3>Acknowledgments</h3>\n<p>Many thanks to <a href=\"http://www.designhive.com/contact/phil-wareham\" target=\"_blank\">Phil Wareham</a> who tidied up this plugin code and its help file.<br />\nThank you to <a href=\"https://github.com/michaelkpate\" target=\"_blank\">Michael K. Pate</a>, for its original idea, who accepted to distribute my entire rewriting code  as a \'pat\' prefixed plugin.</p>', '/**\r\n * pat_if_amp plugin. Support for Google AMP with Textpattern CMS.\r\n * @author:  Michael K Pate & Patrick LEFEVRE.\r\n * @link:    https://github.com/cara-tm/pat_if_amp, https://github.com/michaelkpate/mkp_if_amp\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.3.7\r\n * @license: GPLv2\r\n*/\r\n\r\n/**\r\n * This plugin tag registry.\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_if_amp\')\r\n		->register(\'pat_amp_sanitize\')\r\n		->register(\'pat_amp_redirect\');\r\n}\r\n\r\n/**\r\n * Register callback when public pages are rendering.\r\n *\r\n */\r\nif (txpinterface === \'public\') {\r\n	// Loads a callback with init function for public context.\r\n	register_callback(\'pat_if_amp_init\', \'textpattern\');\r\n}\r\n\r\n/**\r\n * Init function which create the mkp_variable.\r\n *\r\n * @param\r\n * @return boolean $variable\r\n */\r\nfunction pat_if_amp_init()\r\n{\r\n	global $variable;\r\n\r\n	// Initiates a TXP variable which sniffs for \'amp\' (with or without a final backslash) in URLs or a simple query \'?amp\'\r\n	$variable[\'pat_amp\'] = (preg_match(\'@(\\/|\\?)(amp)@sm\', $GLOBALS[\'pretext\'][\'request_uri\'] ) || gps(\'amp\') ? 1 : 0);\r\n}\r\n\r\n/**\r\n * Main plugin function.\r\n *\r\n * @param  $atts   string This plugin attributes\r\n * @param  $thing  string\r\n * @return string\r\n */\r\nfunction pat_if_amp($atts, $thing=\'\')\r\n{\r\n	global $variable;\r\n\r\n	extract(lAtts(array(\r\n		\'redirect\'  => false,\r\n		\'url\'       => hu,\r\n		\'subdomain\' => \'amp\',\r\n		\'permlink\'  => true,\r\n	), $atts));\r\n\r\n	$path = parse_url($GLOBALS[\'pretext\'][\'request_uri\'], PHP_URL_PATH);\r\n	$els = explode(\'/\', $path);\r\n\r\n	// Splits URL parts\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", $GLOBALS[\'pretext\'][\'request_uri\']), count($els));\r\n\r\n	if ($redirect && \'1\' == $variable[\'pat_amp\']) {\r\n		// Redirect to same article\'s title within the subdomain.\r\n		pat_amp_redirect(array(\'url\'=>$url,\'subdomain\'=>$subdomain,\'permlink\'=>$permlink));\r\n	} else {\r\n		// If the URL ends in \'amp\' this will return true; otherwise false.\r\n		return (end($parts) == \'amp\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\r\n	}\r\n}\r\n\r\n/**\r\n * Sanitize all inline CSS styles within body/excerpt text content.\r\n *\r\n * @param:  $atts array Plugin attribute\r\n * @return: string      Text content\r\n */\r\nfunction pat_amp_sanitize($atts)\r\n{\r\n	extract(lAtts(array(\r\n		\'content\' => \'body\',\r\n	), $atts));\r\n\r\n	$out = \'\';\r\n\r\n	if (in_array($content, array(\'body\', \'excerpt\')) ) {\r\n		$out = preg_replace(\'/(<[^>]+) style=\".*?\"/i\',\'$1\', $content());\r\n	} else {\r\n		$out = trigger_error( gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'content\')), E_USER_WARNING );\r\n	}\r\n\r\n	return $out;\r\n}\r\n\r\n/**\r\n * Extracts the domain name and redirects to a subdomain.\r\n *\r\n * @param: $atts array Plugin attribute\r\n * @return redirection or false\r\n */\r\nfunction pat_amp_redirect($atts)\r\n{\r\n	global $pretext, $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'url\'       => hu,\r\n		\'subdomain\' => \'amp\',\r\n		\'permlink\'  => false,\r\n	), $atts));\r\n\r\n	// Array of the URL.\r\n	$parts = parse_url($url);\r\n	// Verify the host.\r\n	$domain = isset($parts[\'host\']) ? $parts[\'host\'] : \'\';\r\n\r\n	// Regex for a well spelling domain name.\r\n	if (preg_match(\'/(?P<domain>[a-z0-9][a-z0-9\\-]{1,63}\\.[a-z\\.]{2,6})$/i\', $domain, $matches) ) {\r\n		// Redirects to subdomain without or with the current article\'s URL title.\r\n		return header(\'Location: \'.$parts[\'scheme\'].\'://\'.$subdomain.\'.\'.$matches[\'domain\'].($permlink ? \'/\'.str_replace(hu, \'\', permlinkurl($thisarticle)) : \'\'));\r\n	}\r\n\r\n	// Otherwise, do nothing.\r\n	return false;\r\n}', '/**\r\n * pat_if_amp plugin. Support for Google AMP with Textpattern CMS.\r\n * @author:  Michael K Pate & Patrick LEFEVRE.\r\n * @link:    https://github.com/cara-tm/pat_if_amp, https://github.com/michaelkpate/mkp_if_amp\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.3.7\r\n * @license: GPLv2\r\n*/\r\n\r\n/**\r\n * This plugin tag registry.\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_if_amp\')\r\n		->register(\'pat_amp_sanitize\')\r\n		->register(\'pat_amp_redirect\');\r\n}\r\n\r\n/**\r\n * Register callback when public pages are rendering.\r\n *\r\n */\r\nif (txpinterface === \'public\') {\r\n	// Loads a callback with init function for public context.\r\n	register_callback(\'pat_if_amp_init\', \'textpattern\');\r\n}\r\n\r\n/**\r\n * Init function which create the mkp_variable.\r\n *\r\n * @param\r\n * @return boolean $variable\r\n */\r\nfunction pat_if_amp_init()\r\n{\r\n	global $variable;\r\n\r\n	// Initiates a TXP variable which sniffs for \'amp\' (with or without a final backslash) in URLs or a simple query \'?amp\'\r\n	$variable[\'pat_amp\'] = (preg_match(\'@(\\/|\\?)(amp)@sm\', $GLOBALS[\'pretext\'][\'request_uri\'] ) || gps(\'amp\') ? 1 : 0);\r\n}\r\n\r\n/**\r\n * Main plugin function.\r\n *\r\n * @param  $atts   string This plugin attributes\r\n * @param  $thing  string\r\n * @return string\r\n */\r\nfunction pat_if_amp($atts, $thing=\'\')\r\n{\r\n	global $variable;\r\n\r\n	extract(lAtts(array(\r\n		\'redirect\'  => false,\r\n		\'url\'       => hu,\r\n		\'subdomain\' => \'amp\',\r\n		\'permlink\'  => true,\r\n	), $atts));\r\n\r\n	$path = parse_url($GLOBALS[\'pretext\'][\'request_uri\'], PHP_URL_PATH);\r\n	$els = explode(\'/\', $path);\r\n\r\n	// Splits URL parts\r\n	$parts = explode(\'/\', preg_replace(\"|^https?://[^/]+|i\", \"\", $GLOBALS[\'pretext\'][\'request_uri\']), count($els));\r\n\r\n	if ($redirect && \'1\' == $variable[\'pat_amp\']) {\r\n		// Redirect to same article\'s title within the subdomain.\r\n		pat_amp_redirect(array(\'url\'=>$url,\'subdomain\'=>$subdomain,\'permlink\'=>$permlink));\r\n	} else {\r\n		// If the URL ends in \'amp\' this will return true; otherwise false.\r\n		return (end($parts) == \'amp\') ? parse(EvalElse($thing, true)) : parse(EvalElse($thing, false));\r\n	}\r\n}\r\n\r\n/**\r\n * Sanitize all inline CSS styles within body/excerpt text content.\r\n *\r\n * @param:  $atts array Plugin attribute\r\n * @return: string      Text content\r\n */\r\nfunction pat_amp_sanitize($atts)\r\n{\r\n	extract(lAtts(array(\r\n		\'content\' => \'body\',\r\n	), $atts));\r\n\r\n	$out = \'\';\r\n\r\n	if (in_array($content, array(\'body\', \'excerpt\')) ) {\r\n		$out = preg_replace(\'/(<[^>]+) style=\".*?\"/i\',\'$1\', $content());\r\n	} else {\r\n		$out = trigger_error( gTxt(\'invalid_attribute_value\', array(\'{name}\' => \'content\')), E_USER_WARNING );\r\n	}\r\n\r\n	return $out;\r\n}\r\n\r\n/**\r\n * Extracts the domain name and redirects to a subdomain.\r\n *\r\n * @param: $atts array Plugin attribute\r\n * @return redirection or false\r\n */\r\nfunction pat_amp_redirect($atts)\r\n{\r\n	global $pretext, $thisarticle;\r\n\r\n	extract(lAtts(array(\r\n		\'url\'       => hu,\r\n		\'subdomain\' => \'amp\',\r\n		\'permlink\'  => false,\r\n	), $atts));\r\n\r\n	// Array of the URL.\r\n	$parts = parse_url($url);\r\n	// Verify the host.\r\n	$domain = isset($parts[\'host\']) ? $parts[\'host\'] : \'\';\r\n\r\n	// Regex for a well spelling domain name.\r\n	if (preg_match(\'/(?P<domain>[a-z0-9][a-z0-9\\-]{1,63}\\.[a-z\\.]{2,6})$/i\', $domain, $matches) ) {\r\n		// Redirects to subdomain without or with the current article\'s URL title.\r\n		return header(\'Location: \'.$parts[\'scheme\'].\'://\'.$subdomain.\'.\'.$matches[\'domain\'].($permlink ? \'/\'.str_replace(hu, \'\', permlinkurl($thisarticle)) : \'\'));\r\n	}\r\n\r\n	// Otherwise, do nothing.\r\n	return false;\r\n}', '75d24e99de74a526193a0fc016768d46', '', '', 0, 5, 0),
('pat_eu_cookies_law', 1, 'Patrick LEFEVRE', 'https://github.com/cara-tm/EU-Cookies-Law-Compliance', '0.1.6', 'EU Cookies law compliance plugin for Textpattern CMS', '<h1>pat_eu_cookies_law</h1>\n<p>A EU Cookies Law Compliance plugin for Textpattern CMS.</p>\n<h2>Documentation</h2>\n<p>See the <a href=\"https://github.com/cara-tm/EU-Cookies-Law-Compliance\" target=\"_blank\"><strong>GitHub dedicated page for more informations</strong></a>.</p>\n<h2>Plugin Preferences</h2>\n<p>After this plugin installation and activation, visit your <a href=\"index.php?event=prefs\"><strong>Site Preferences</strong></a> to set:\n<ul>\n<li>A comma separated list of js files to load if your visitors accept the use of Cookies into the \"<em>List of javascript files to load</em>\" field (i.e. <code>http://my-domain.com/js/first-file.js, http://my-domain.com/js/second-file.js</code>);</li>\n<li>The 2 digits country codes for the EU members (usefull for country deletion or addition. See the \"<em>List of EU country members code</em>\" field.).</li>\n<li>The name of the services generating third party cookies into the pat_eu_cookies_law_widgets field.</li>\n</ul>\n<h2>Plugin Attributes</h2>\n<ul>\n<li><code>lang</code> (string): the 2 letters language code (ISO2) for localisation (i.e. <code>lang=\"en\"</code>). Default: the <strong>TXP active language</strong> preferences sets within <a href=\"index.php?event=lang\">the <strong>Textpattern Languages page</strong></a>.</li>\n<li><code>duration</code>(string or integer): the delay in month for the saving user\'s choice. Default: 1 Month (can be set with a number only. i.e. <code>1</code>).</li>\n<li><code>force_reload</code> (boolean <code>1</code> or <code>0</code>): if set to <code>1</code> (true) the page will be reloaded on user acceptation. Default: <code>0</code>(false).</li>\n<li><code>infos</code> (string): a section name where you want to display your legal staatement or confidential policy. That attribute create a link after the counting delay. Defaut: empty (no link creation).</li>\n<li><code>more</code> (string): the label of the link above for translation conveniencies. Default: empty.</li>\n</ul>\n<h2>JSON Files for Translations</h2>\n<p>Install the JSON folder and its content into your <code>/root/</code> directory.</p>\n<h2>Usage Example</h2>\n<p>Best place into a page template:</p>\n<pre><code>&lt;txp:pat_eu_cookies_law lang=\"en\" duration=\"2 Months\" force_reload=\"1\" infos=\"legal-statement\" more=\"Read more\" /&gt;</pre></code>\n<h2>Changelog</h2>\n<ul>\n<li>17<sup>th</sup> March 2018: version 0.1.6.</li>\n<li>3<sup>td</sup> January 2018: version 0.1.5</li>\n<li>2<sup>sd</sup> January 2018: version 0.1.4</li>\n<li>16<sup>th</sup> December 2017: version 0.1.3</li>\n<li>8<sup>th</sup> December 2017: version 0.1.2</li>\n<li>27<sup>th</sup> July 2017: version 0.1.1</li>\n<li>15<sup>th</sup> July 2017: version 0.1</li>\n</ul>', '/**\r\n * pat_eu_cookies_law plugin. EU Cookies law compliance plugin for Textpattern CMS.\r\n * @author:  Patrick LEFEVRE.\r\n * @link:    https://github.com/cara-tm/pat_eu_cookies_law\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.1.6\r\n * @license: GPLv2\r\n */\r\n\r\n/**\r\n * This plugin tag registry\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_eu_cookies_law\');\r\n}\r\n\r\n\r\n/**\r\n * This plugin admin callbacks\r\n */\r\nif (txpinterface == \'admin\')\r\n{\r\n	register_callback(\'pat_eu_cookies_law_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'pat_eu_cookies_law_cleanup\', \'plugin_lifecycle.pat_eu_cookies_law\', \'deleted\');\r\n}\r\n\r\n\r\n/**\r\n * Main plugin function\r\n *\r\n * @param array   This plugin attributes\r\n * @param string\r\n * @return string HTML markup\r\n */\r\nfunction pat_eu_cookies_law($atts, $thing = null)\r\n{\r\n\r\n	global $path_to_site;\r\n\r\n	extract(lAtts(array(\r\n		\'lang\'         => substr(get_pref(\'language\'), 0, 2),\r\n		\'duration\'     => \'1 Month\',\r\n		\'force_reload\' => false,\r\n		\'infos\'        => false,\r\n		\'more\'         => false,\r\n	), $atts));\r\n\r\n	// js expression for force reload on demand\r\n	$force = ($force_reload ? \",window.location=\'\'\" : \"\");\r\n\r\n\r\n	// The default string entries for international translation\r\n	$_default = array(\r\n			\'refuse\' => \'You refuse external third-party cookies: none, at the initiative of this site, are present into your device\', \r\n			\'msg\' => \'This website stores some third parts cookies within your device.\',\r\n			\'links\' => \'You can <a href=\"#!\" title=\"I accept the use of cookies and I close this message\" id=\"ok-cookies\">Accept</a> or <a href=\"#!\" title=\"I refuse to use Cookies and a message will continue to appear\" id=\"no-cookies\">Refuse</a> them.\',\r\n			\'remind\' => \'Time remaining before Cookies automatic launch\',\r\n			\'no_allowed\' => \'Currently, your browser is set to disable cookies (check preferences).\',\r\n			);\r\n\r\n	// Get content of the json translation file based on the \'lang\' plugin\'s attribute\r\n	$json = @file_get_contents(hu.\'json/pat_eu_cookies_law_\'.$lang.\'.json\');\r\n\r\n	// Decode the json datas\r\n	if (strlen($json) > 0) {\r\n		assert_string($json);\r\n		// An error comes below:\r\n		$json_datas = json_decode($json, true);\r\n	}\r\n\r\n	// Can we proceed? Simple test within the first json value\r\n	if (json_last_error() === JSON_ERROR_NONE) {\r\n		// Loop throught the json\r\n		foreach ($json_datas as $key => $value) {\r\n			// Change default values by json ones\r\n			$_default[$key] = $value;\r\n		}\r\n	}\r\n\r\n	return \'<div class=\"pat_eu_cookies_law\"><div id=\"msg-cookies\"><p>\'.$_default[\'msg\'].\' \'.@get_pref(\'pat_eu_cookies_law_widjets\').\'. <br />\'.$_default[\'links\'].\'<br /><span id=\"cookies-delay\">\'.$_default[\'remind\'].\' <strong id=\"counter\">1:00</strong>\'.($infos ? \' <a href=\"\'.hu.$infos.\'\">\'.$more.\'</a>\' : \'\').\'</span></p></div> <p><span id=\"cookie-choices\"></span></p></div>\'.n._pat_eu_cookies_law_inject( $_default[\'refuse\'], $_default[\'no_allowed\'], $duration, $force );\r\n\r\n}\r\n\r\n\r\n/**\r\n * Javascript injector\r\n *\r\n * @param  string ($refuse)     A message for users\r\n * @param  string ($no_allowed) A message for users\r\n * @param  string ($future)     The delay for the stored user\'s choice into the internal cookie\r\n * @return string               javascript code\r\n */\r\nfunction _pat_eu_cookies_law_inject($refuse, $no_allowed, $future, $force)\r\n{\r\n\r\n	global $prefs;\r\n\r\n	// Variable convertion for convenient insertion into js. Removes all spaces\r\n	$list = preg_replace(\'/\\s+/\', \'\', $prefs[\'pat_eu_cookies_law_js\']);\r\n	// Converts to an array\r\n	$list = explode(\',\', $list);\r\n	// Affects each quoted values\r\n	foreach($list as $key => $value) {\r\n		$list[$key] = \'\"\'.$value.\'\"\';\r\n	}\r\n	// $files is a list of quoted strings ready for a js array\r\n	$files = implode($list, \',\');\r\n\r\n	$out = <<<EOJ\r\n<script>\r\n/*! Simple EU Cookies Law Compliance without dependencies by cara-tm.com, 2017. MIT license - https://github.com/cara-tm/EU-Cookies-Law-Compliance/ */\r\nfunction EU_cookies_law(b){\'use strict\';function d(D){return document.getElementById(\'cookies-delay\').innerHTML=\'\',document.getElementById(\'msg-cookies\').innerHTML=D}var f=\'$refuse\',g=\'$future\',l=window.location.hostname,n=navigator.language||navigator.browserLanguage,o=[{$prefs[\'pat_eu_cookies_law_countries\']}],p=1,q=60,t=1,u=document.getElementById(\'ok-cookies\'),v=document.getElementById(\'no-cookies\');if(!1!==navigator.cookieEnabled){for(var x=function(){g=parseInt(g.substring(0,1));var D=new Date(new Date().setMonth(new Date().getMonth()+g));A(\'Ok\',D),B(b),d(\'\')},y=function(D){var E=new RegExp(\'(?:; )?\'+D+\'=([^;]*);?\');return E.test(document.cookie)?decodeURIComponent(RegExp.$1):null},z=function(){C(),y(l)===\'Ok\'+l?(d(\'\'),B(b)):y(l)===\'No\'+l&&d(f)},A=function(D,E){return document.cookie=l+\'=\'+encodeURIComponent(D+l)+\';expires=\'+E.toGMTString()},B=function(D){var E=[],F=document.getElementsByTagName(\'script\')[0];if(!window.scriptHasRun){window.scriptHasRun=!0;for(var G=0;G<D.length;G++)0===D[G]&&window.scriptHasRun||(window.scriptHasRun=!0,E[G]=document.createElement(\'script\'),E[G].src=D[G],document.getElementsByTagName(\'head\')[0].appendChild(E[G])||F.parentNode.insertBefore(E[G],F))}},C=function(){if(null!==document.getElementById(\'counter\')){var D=document.getElementById(\'counter\');q--,null!==typeof D.innerHTML&&(D.innerHTML=(t-1).toString()+\':\'+(10>q?\'0\':\'\')+(q+\'\')),0<q?setTimeout(C,1e3):1<t,0==q&&(x(),d(\'\'))}else null===document.getElementById(\'cookies-delay\')?\'\':document.getElementById(\'cookies-delay\').innerHTML=\'\'},w=0;w<o.length;w++)if(o[w]===n.substring(0,2).toUpperCase()){break}z(),u.onclick=function(D){D.preventDefault(),x(D)$force},v.onclick=function(D){var E=new Date(new Date().setDate(new Date().getDate()+1));A(\'No\',E),d(f),D.preventDefault()}}else d(\'$no_allowed\')};\r\n/*! Array of third part JS files to load */ \r\nEU_cookies_law([$files]);\r\n</script>\r\nEOJ;\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin preferences installation\r\n *\r\n * @param\r\n * @return null Safe_field insertions\r\n */\r\nfunction pat_eu_cookies_law_prefs()\r\n{\r\n\r\n	global $textarray;\r\n\r\n	$textarray[\'pat_eu_cookies_law_countries\'] = \'List of EU country members codes\';\r\n	$textarray[\'pat_eu_cookies_law_js\'] = \'Comma separated list of js files to load\';\r\n	$textarray[\'pat_eu_cookies_law_widjets\'] = \'List of widgets with third part cookies\';\r\n\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_countries\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_countries\', val=\'\\\'AT\\\',\\\'BE\\\',\\\'BG\\\',\\\'HR\\\',\\\'CZ\\\',\\\'CY\\\',\\\'DK\\\',\\\'EE\\\',\\\'FI\\\',\\\'FR\\\',\\\'DE\\\',\\\'EL\\\',\\\'HU\\\',\\\'IE\\\',\\\'IT\\\',\\\'LV\\\',\\\'LT\\\',\\\'LU\\\',\\\'MT\\\',\\\'NL\\\',\\\'PL\\\',\\\'PT\\\',\\\'SK\\\',\\\'SI\\\',\\\'ES\\\',\\\'SE\\\',\\\'GB\\\',\\\'UK\\\'\', type=1, event=\'admin\', html=\'text_input\', position=21\");\r\n	}\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_js\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_js\', val=\'\".hu.\"js/file.js\', type=1, event=\'admin\', html=\'text_input\', position=22\");\r\n	}\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_widjets\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_widjets\', val=\'\', type=1, event=\'admin\', html=\'text_input\', position=23\");\r\n	}\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin lifecycle for deletion\r\n *\r\n * @param\r\n * @return null Safe_field deletions\r\n */\r\nfunction pat_eu_cookies_law_cleanup() {\r\n\r\n	// Array of tables & rows to be removed\r\n	$els = array(\'txp_prefs\' => \'pat_eu_cookies_law_countries\', \'txp_prefs\' => \'pat_eu_cookies_law_js\');\r\n	// Process actions\r\n	foreach ($els as $table => $row) {\r\n		safe_delete($table, \"name LIKE \'\".str_replace(\'_\', \'\\_\', $row).\"\\_%\'\");\r\n		safe_repair($table);\r\n	}\r\n	safe_repair(\'txp_plugin\');\r\n\r\n}\r\n', '/**\r\n * pat_eu_cookies_law plugin. EU Cookies law compliance plugin for Textpattern CMS.\r\n * @author:  Patrick LEFEVRE.\r\n * @link:    https://github.com/cara-tm/pat_eu_cookies_law\r\n * @type:    Public\r\n * @prefs:   no\r\n * @order:   5\r\n * @version: 0.1.6\r\n * @license: GPLv2\r\n */\r\n\r\n/**\r\n * This plugin tag registry\r\n */\r\nif (class_exists(\'\\Textpattern\\Tag\\Registry\')) {\r\n	Txp::get(\'\\Textpattern\\Tag\\Registry\')\r\n		->register(\'pat_eu_cookies_law\');\r\n}\r\n\r\n\r\n/**\r\n * This plugin admin callbacks\r\n */\r\nif (txpinterface == \'admin\')\r\n{\r\n	register_callback(\'pat_eu_cookies_law_prefs\', \'prefs\', \'\', 1);\r\n	register_callback(\'pat_eu_cookies_law_cleanup\', \'plugin_lifecycle.pat_eu_cookies_law\', \'deleted\');\r\n}\r\n\r\n\r\n/**\r\n * Main plugin function\r\n *\r\n * @param array   This plugin attributes\r\n * @param string\r\n * @return string HTML markup\r\n */\r\nfunction pat_eu_cookies_law($atts, $thing = null)\r\n{\r\n\r\n	global $path_to_site;\r\n\r\n	extract(lAtts(array(\r\n		\'lang\'         => substr(get_pref(\'language\'), 0, 2),\r\n		\'duration\'     => \'1 Month\',\r\n		\'force_reload\' => false,\r\n		\'infos\'        => false,\r\n		\'more\'         => false,\r\n	), $atts));\r\n\r\n	// js expression for force reload on demand\r\n	$force = ($force_reload ? \",window.location=\'\'\" : \"\");\r\n\r\n\r\n	// The default string entries for international translation\r\n	$_default = array(\r\n			\'refuse\' => \'You refuse external third-party cookies: none, at the initiative of this site, are present into your device\', \r\n			\'msg\' => \'This website stores some third parts cookies within your device.\',\r\n			\'links\' => \'You can <a href=\"#!\" title=\"I accept the use of cookies and I close this message\" id=\"ok-cookies\">Accept</a> or <a href=\"#!\" title=\"I refuse to use Cookies and a message will continue to appear\" id=\"no-cookies\">Refuse</a> them.\',\r\n			\'remind\' => \'Time remaining before Cookies automatic launch\',\r\n			\'no_allowed\' => \'Currently, your browser is set to disable cookies (check preferences).\',\r\n			);\r\n\r\n	// Get content of the json translation file based on the \'lang\' plugin\'s attribute\r\n	$json = @file_get_contents(hu.\'json/pat_eu_cookies_law_\'.$lang.\'.json\');\r\n\r\n	// Decode the json datas\r\n	if (strlen($json) > 0) {\r\n		assert_string($json);\r\n		// An error comes below:\r\n		$json_datas = json_decode($json, true);\r\n	}\r\n\r\n	// Can we proceed? Simple test within the first json value\r\n	if (json_last_error() === JSON_ERROR_NONE) {\r\n		// Loop throught the json\r\n		foreach ($json_datas as $key => $value) {\r\n			// Change default values by json ones\r\n			$_default[$key] = $value;\r\n		}\r\n	}\r\n\r\n	return \'<div class=\"pat_eu_cookies_law\"><div id=\"msg-cookies\"><p>\'.$_default[\'msg\'].\' \'.@get_pref(\'pat_eu_cookies_law_widjets\').\'. <br />\'.$_default[\'links\'].\'<br /><span id=\"cookies-delay\">\'.$_default[\'remind\'].\' <strong id=\"counter\">1:00</strong>\'.($infos ? \' <a href=\"\'.hu.$infos.\'\">\'.$more.\'</a>\' : \'\').\'</span></p></div> <p><span id=\"cookie-choices\"></span></p></div>\'.n._pat_eu_cookies_law_inject( $_default[\'refuse\'], $_default[\'no_allowed\'], $duration, $force );\r\n\r\n}\r\n\r\n\r\n/**\r\n * Javascript injector\r\n *\r\n * @param  string ($refuse)     A message for users\r\n * @param  string ($no_allowed) A message for users\r\n * @param  string ($future)     The delay for the stored user\'s choice into the internal cookie\r\n * @return string               javascript code\r\n */\r\nfunction _pat_eu_cookies_law_inject($refuse, $no_allowed, $future, $force)\r\n{\r\n\r\n	global $prefs;\r\n\r\n	// Variable convertion for convenient insertion into js. Removes all spaces\r\n	$list = preg_replace(\'/\\s+/\', \'\', $prefs[\'pat_eu_cookies_law_js\']);\r\n	// Converts to an array\r\n	$list = explode(\',\', $list);\r\n	// Affects each quoted values\r\n	foreach($list as $key => $value) {\r\n		$list[$key] = \'\"\'.$value.\'\"\';\r\n	}\r\n	// $files is a list of quoted strings ready for a js array\r\n	$files = implode($list, \',\');\r\n\r\n	$out = <<<EOJ\r\n<script>\r\n/*! Simple EU Cookies Law Compliance without dependencies by cara-tm.com, 2017. MIT license - https://github.com/cara-tm/EU-Cookies-Law-Compliance/ */\r\nfunction EU_cookies_law(b){\'use strict\';function d(D){return document.getElementById(\'cookies-delay\').innerHTML=\'\',document.getElementById(\'msg-cookies\').innerHTML=D}var f=\'$refuse\',g=\'$future\',l=window.location.hostname,n=navigator.language||navigator.browserLanguage,o=[{$prefs[\'pat_eu_cookies_law_countries\']}],p=1,q=60,t=1,u=document.getElementById(\'ok-cookies\'),v=document.getElementById(\'no-cookies\');if(!1!==navigator.cookieEnabled){for(var x=function(){g=parseInt(g.substring(0,1));var D=new Date(new Date().setMonth(new Date().getMonth()+g));A(\'Ok\',D),B(b),d(\'\')},y=function(D){var E=new RegExp(\'(?:; )?\'+D+\'=([^;]*);?\');return E.test(document.cookie)?decodeURIComponent(RegExp.$1):null},z=function(){C(),y(l)===\'Ok\'+l?(d(\'\'),B(b)):y(l)===\'No\'+l&&d(f)},A=function(D,E){return document.cookie=l+\'=\'+encodeURIComponent(D+l)+\';expires=\'+E.toGMTString()},B=function(D){var E=[],F=document.getElementsByTagName(\'script\')[0];if(!window.scriptHasRun){window.scriptHasRun=!0;for(var G=0;G<D.length;G++)0===D[G]&&window.scriptHasRun||(window.scriptHasRun=!0,E[G]=document.createElement(\'script\'),E[G].src=D[G],document.getElementsByTagName(\'head\')[0].appendChild(E[G])||F.parentNode.insertBefore(E[G],F))}},C=function(){if(null!==document.getElementById(\'counter\')){var D=document.getElementById(\'counter\');q--,null!==typeof D.innerHTML&&(D.innerHTML=(t-1).toString()+\':\'+(10>q?\'0\':\'\')+(q+\'\')),0<q?setTimeout(C,1e3):1<t,0==q&&(x(),d(\'\'))}else null===document.getElementById(\'cookies-delay\')?\'\':document.getElementById(\'cookies-delay\').innerHTML=\'\'},w=0;w<o.length;w++)if(o[w]===n.substring(0,2).toUpperCase()){break}z(),u.onclick=function(D){D.preventDefault(),x(D)$force},v.onclick=function(D){var E=new Date(new Date().setDate(new Date().getDate()+1));A(\'No\',E),d(f),D.preventDefault()}}else d(\'$no_allowed\')};\r\n/*! Array of third part JS files to load */ \r\nEU_cookies_law([$files]);\r\n</script>\r\nEOJ;\r\n\r\n	return $out;\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin preferences installation\r\n *\r\n * @param\r\n * @return null Safe_field insertions\r\n */\r\nfunction pat_eu_cookies_law_prefs()\r\n{\r\n\r\n	global $textarray;\r\n\r\n	$textarray[\'pat_eu_cookies_law_countries\'] = \'List of EU country members codes\';\r\n	$textarray[\'pat_eu_cookies_law_js\'] = \'Comma separated list of js files to load\';\r\n	$textarray[\'pat_eu_cookies_law_widjets\'] = \'List of widgets with third part cookies\';\r\n\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_countries\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_countries\', val=\'\\\'AT\\\',\\\'BE\\\',\\\'BG\\\',\\\'HR\\\',\\\'CZ\\\',\\\'CY\\\',\\\'DK\\\',\\\'EE\\\',\\\'FI\\\',\\\'FR\\\',\\\'DE\\\',\\\'EL\\\',\\\'HU\\\',\\\'IE\\\',\\\'IT\\\',\\\'LV\\\',\\\'LT\\\',\\\'LU\\\',\\\'MT\\\',\\\'NL\\\',\\\'PL\\\',\\\'PT\\\',\\\'SK\\\',\\\'SI\\\',\\\'ES\\\',\\\'SE\\\',\\\'GB\\\',\\\'UK\\\'\', type=1, event=\'admin\', html=\'text_input\', position=21\");\r\n	}\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_js\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_js\', val=\'\".hu.\"js/file.js\', type=1, event=\'admin\', html=\'text_input\', position=22\");\r\n	}\r\n	if ( !safe_field(\'name\', \'txp_prefs\', \"name=\'pat_eu_cookies_law_widjets\'\") ) {\r\n		safe_insert(\'txp_prefs\', \"name=\'pat_eu_cookies_law_widjets\', val=\'\', type=1, event=\'admin\', html=\'text_input\', position=23\");\r\n	}\r\n\r\n}\r\n\r\n\r\n/**\r\n * This plugin lifecycle for deletion\r\n *\r\n * @param\r\n * @return null Safe_field deletions\r\n */\r\nfunction pat_eu_cookies_law_cleanup() {\r\n\r\n	// Array of tables & rows to be removed\r\n	$els = array(\'txp_prefs\' => \'pat_eu_cookies_law_countries\', \'txp_prefs\' => \'pat_eu_cookies_law_js\');\r\n	// Process actions\r\n	foreach ($els as $table => $row) {\r\n		safe_delete($table, \"name LIKE \'\".str_replace(\'_\', \'\\_\', $row).\"\\_%\'\");\r\n		safe_repair($table);\r\n	}\r\n	safe_repair(\'txp_plugin\');\r\n\r\n}\r\n', '0533f9793216b2255972eee1b66763ba', '', '', 1, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('jcp_simple_gravatar', 0, 'John Ankarström', 'http://jocap.se', '1.0', 'Simple Gravatar avatars for Textpattern.', '<h1>jcp_simple_gravatar</h1>\n\n<p>Use <code>&lt;txp:jcp_simple_gravatar /&gt;</code> in your templates. It will return the gravatar <span class=\"caps\">URL</span>.</p>\n\n<p>There&#8217;s four attributes, and all are optional;</p>\n\n<ul>\n	<li><code>email</code> &#8212; custom email address for gravatar (if not specified, the plugin uses the comment author&#8217;s email)</li>\n	<li><code>user</code> &#8212; get user&#8217;s gravatar (input: the user&#8217;s username)</li>\n	<li><code>default</code> &#8212; default avatar, if no gravatar was found</li>\n	<li><code>default_local</code> &#8212; default avatar, local file like <code>images/9.png</code> that will become <code>http://yoursite.com/images/9.png</code> (your site have to be live and visible to Gravatar)</li>\n	<li><code>size</code> &#8212; the image size, in pixels (default: 80)</li>\n	<li><code>format</code> &#8212; the file format, like .jpg or .png, without the dot (default: jpg)</li>\n	<li><code>where</code> &#8212; is the gravatar in an article or in a comment? This is totally optional, <code>jcp_simple_gravatar</code> can pick the right one for you.</li>\n</ul>\n\n<h2>Example use</h2>\n\n<p>In the comments form:</p>\n\n<pre>&lt;img src=&quot;&lt;txp:jcp_simple_gravatar default=&quot;http://thesite.net/images/avatar.jpg&quot; size=&quot;50&quot; format=&quot;png&quot; /&gt;&quot; width=&quot;50&quot; height=&quot;50&quot; /&gt;</pre>\n\n<p>May return:</p>\n\n<pre>&lt;img src=&quot;http://gravatar.com/avatar/4fdce145bab6d27d69e34403f99fd11c.png?s=50&amp;d=http%3A%2F%2Fthesite.net%2Fimages%2Favatar.jpg&quot; width=&quot;50&quot; height=&quot;50&quot; /&gt;</pre>\n\n<p>In an article:</p>\n\n<pre>Author: &lt;txp:author /&gt;&lt;br /&gt;\n&lt;img src=&quot;&lt;txp:jcp_simple_gravatar default_local=&quot;/images/6.png&quot; size=&quot;60&quot; /&gt;&quot; /&gt;</pre>\n\n<p>May return:</p>\n\n<pre>Author: Andrew Dorsey&lt;br /&gt;\n&lt;img src=&quot;http://gravatar.com/avatar/e0a2988a7d97b4def8426cbead7b1a9b.png?s=50&amp;d=http%3A%2F%2Fmysite.com%2Fimages%2F6t.png&quot; width=&quot;50&quot; height=&quot;50&quot; /&gt;</pre>\n\n<p>Get user&#8217;s avatar:</p>\n\n<pre>About Andrew:&lt;br /&gt;\n&lt;img src=&quot;&lt;txp:jcp_simple_gravatar user=&quot;andrew&quot; /&gt;&quot;</pre>\n\n<p>May return:</p>\n\n<pre>About Andrew:&lt;br /&gt;\n&lt;img src=&quot;http://gravatar.com/avatar/e0a2988a7d97b4def8426cbead7b1a9b.png?s=80&quot; /&gt;</pre>\n\n<h2>Information</h2>\n\n<p><code>jcp_simple_gravatar</code> for Textpattern is based on the <a href=\"http://code.google.com/p/textpattern-plugins/wiki/GlxGravatar\" rel=\"nofollow\">glx_gravatar</a> plugin, and is licensed under the <a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\" rel=\"nofollow\"><span class=\"caps\">GNU</span> General Public License v2</a>.</p>\n\n<h3>Bugs, issues or any other problems?</h3>\n\n<p>Please open a ticket at <a href=\"http://dev.jocap.se/txp/jcp_simple_gravatar\" rel=\"nofollow\">http://dev.jocap.se/txp/jcp_simple_gravatar</a>.</p>\n\n<h3>History</h3>\n\n<p>2010-08-24 1.0\n  C: release of the plugin &#8212; yay!</p>\n\n<p>Legend:</p>\n\n<ul>\n	<li>B: Bugfix</li>\n	<li>C: Code</li>\n	<li>D: Documentation</li>\n	<li>F: Feature</li>\n	<li>I: Internationalization</li>\n</ul>', '\nfunction jcp_simple_gravatar( $atts ) {\n\n  global $prefs;\n  global $thiscomment;\n  global $thisarticle;\n\n  $size    = ( !empty( $atts[\'size\'] ) )    ? $atts[\'size\']    : \'80\';\n  $default = ( !empty( $atts[\'default\'] ) ) ? $atts[\'default\'] : \'default\';\n  $format  = ( !empty( $atts[\'format\'] ) )  ? $atts[\'format\']  : \'jpg\';\n  $rating  = (!empty($atts[\'rating\']))      ? $atts[\'rating\']	 : \'G\';\n  // TODO: Add support for Gravatar id\'s\n\n  // Comment or article author\'s email address?\n  // TODO: Clean up code, make it more DRY or somehthing.\n  if ( empty( $atts[\'user\'] ) ) {\n    if ( empty( $atts[\'where\'] ) ) {\n      if ( !empty( $thiscomment ) ) {\n        $email = ( !empty( $atts[\'email\'] ) )   ? $atts[\'email\']   : $thiscomment[\'email\'];\n      } elseif ( !empty( $thisarticle ) ) {\n        $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$thisarticle[\'authorid\'].\"\'\");\n      }\n    } else {\n      if ( $atts[\'where\'] == \"comment\") {\n        $email = ( !empty( $atts[\'email\'] ) )   ? $atts[\'email\']   : $thiscomment[\'email\'];\n      } elseif ( $atts[\'where\'] == \"article\" ) {\n        $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$thisarticle[\'authorid\'].\"\'\");\n      }\n    }\n  } else {\n    $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$atts[\'user\'].\"\'\");\n  }\n\n  // The Gravatar URL before adding the parameters!\n  $url = \"http://gravatar.com/avatar/\" . md5( strtolower( $email ) ) . \".$format\";\n\n  // Parameters\n  if ( $rating != \'G\' ) {\n    $parameters[] = \'r=\' . $rating;\n  }\n  $parameters[] = \'s=\' . $size;\n  if ( !empty( $atts[\'default\'] ) ) {\n    $parameters[] = \'d=\'.urlencode( $default );\n  } elseif ( !empty( $atts[\'default_local\'] ) ) {\n    // Get the site URL\n    if ( strstr( \'http://\', $prefs[\'siteurl\'] ) ) {\n      $siteurl = $prefs[\'siteurl\'];\n    } else {\n      $siteurl = \"http://\" . $prefs[\'siteurl\'];\n    }\n    $parameters[] = \'d=\'.urlencode( $siteurl . $atts[\'default_local\'] );\n  }\n  if ( isset( $parameters ) ) {\n    $par = join( \"&amp;\", $parameters );\n    $url .= \"?\" . $par;\n  };\n\n  // We\'re done, yay!\n  return $url;\n}\n\n', '\nfunction jcp_simple_gravatar( $atts ) {\n\n  global $prefs;\n  global $thiscomment;\n  global $thisarticle;\n\n  $size    = ( !empty( $atts[\'size\'] ) )    ? $atts[\'size\']    : \'80\';\n  $default = ( !empty( $atts[\'default\'] ) ) ? $atts[\'default\'] : \'default\';\n  $format  = ( !empty( $atts[\'format\'] ) )  ? $atts[\'format\']  : \'jpg\';\n  $rating  = (!empty($atts[\'rating\']))      ? $atts[\'rating\']	 : \'G\';\n  // TODO: Add support for Gravatar id\'s\n\n  // Comment or article author\'s email address?\n  // TODO: Clean up code, make it more DRY or somehthing.\n  if ( empty( $atts[\'user\'] ) ) {\n    if ( empty( $atts[\'where\'] ) ) {\n      if ( !empty( $thiscomment ) ) {\n        $email = ( !empty( $atts[\'email\'] ) )   ? $atts[\'email\']   : $thiscomment[\'email\'];\n      } elseif ( !empty( $thisarticle ) ) {\n        $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$thisarticle[\'authorid\'].\"\'\");\n      }\n    } else {\n      if ( $atts[\'where\'] == \"comment\") {\n        $email = ( !empty( $atts[\'email\'] ) )   ? $atts[\'email\']   : $thiscomment[\'email\'];\n      } elseif ( $atts[\'where\'] == \"article\" ) {\n        $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$thisarticle[\'authorid\'].\"\'\");\n      }\n    }\n  } else {\n    $email = safe_field(\"email\", \"txp_users\", \"name=\'\".$atts[\'user\'].\"\'\");\n  }\n\n  // The Gravatar URL before adding the parameters!\n  $url = \"http://gravatar.com/avatar/\" . md5( strtolower( $email ) ) . \".$format\";\n\n  // Parameters\n  if ( $rating != \'G\' ) {\n    $parameters[] = \'r=\' . $rating;\n  }\n  $parameters[] = \'s=\' . $size;\n  if ( !empty( $atts[\'default\'] ) ) {\n    $parameters[] = \'d=\'.urlencode( $default );\n  } elseif ( !empty( $atts[\'default_local\'] ) ) {\n    // Get the site URL\n    if ( strstr( \'http://\', $prefs[\'siteurl\'] ) ) {\n      $siteurl = $prefs[\'siteurl\'];\n    } else {\n      $siteurl = \"http://\" . $prefs[\'siteurl\'];\n    }\n    $parameters[] = \'d=\'.urlencode( $siteurl . $atts[\'default_local\'] );\n  }\n  if ( isset( $parameters ) ) {\n    $par = join( \"&amp;\", $parameters );\n    $url .= \"?\" . $par;\n  };\n\n  // We\'re done, yay!\n  return $url;\n}\n\n', '3915ed92b1ad25eaeab7018c7b91409b', '', '', 0, 5, 0),
('cwh_link_to_home', 0, 'Christopher Horrell', 'http://horrell.ca/', '0.1', 'A replacement tag for <txp:link_to_home /> that includes the ability to set the output format as a <link> tag.', '	<h1>cwh_link_to_home</h1>\n\n	<p>This is a replacement tag for <code>&#60;txp:link_to_home /&#62;</code> that includes a new <code>format</code> attribute which provides the ability to set the link format as a <code>&#60;link&#62;</code> tag suitable for placing in the <code>&#60;head&#62;</code> of any page on your site. You can do this by setting the attribute <code>format</code> to <em>link</em>, like so:</p>\n\n	<p><code>&#60;txp:cwh_link_to_home title=&#34;Home&#34; format=&#34;link&#34; /&#62;</code></p>\n\n	<p>which will produce something similar to:</p>\n\n	<p><code>&#60;link rel=&#34;home&#34; title=&#34;Home&#34; href=&#34;http://yourdomain.tld/&#34; /&#62;</code></p>\n\n	<p>This tag also shares all of the same attributes as <code>&#60;txp:link_to_home /&#62;</code>. For a complete list of applicable attributes, see <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_to_home\">this Textbook entry</a>.</p>\n\n	<p>For a further explanation of the benefits of using <code>&#60;link&#62;</code> tags for your site&#8217;s navigation, please see <a href=\"http://diveintoaccessibility.org/day_9_providing_additional_navigation_aids.html\">Day 9: Providing additional navigation aids</a> from Mark Pilgrim&#8217;s <a href=\"http://diveintoaccessibility.org/\">\nDive Into Accessibility</a>.</p>\n\n', '\n// Plugin code goes here.  No need to escape quotes.\n\n\nfunction cwh_link_to_home($atts, $thing)\n{\n	extract(lAtts(array(\n		\'class\' => false,\n		\'format\'   => \'a\', // new format attribute\n		\'title\'    => \'\',\n		\'wraptag\'  => \'\',\n	), $atts));\n\n    if ($format == \'a\')\n    {\n	$class = ($class) ? \' class=\"\'.$class.\'\"\' : \'\';\n	return \'<a rel=\"home\" title=\"\'.$title.\'\" href=\"\'.hu.\'\"\'.$class.\'>\'.parse($thing).\'</a>\';\n    }\n\n	else\n	{\n		return \'<link rel=\"home\" title=\"\'.$title.\'\" href=\"\'.hu.\'\" />\';\n	}\n\n	return ($wraptag) ? tag($out, $wraptag) : $out;\n}\n\n', '\n// Plugin code goes here.  No need to escape quotes.\n\n\nfunction cwh_link_to_home($atts, $thing)\n{\n	extract(lAtts(array(\n		\'class\' => false,\n		\'format\'   => \'a\', // new format attribute\n		\'title\'    => \'\',\n		\'wraptag\'  => \'\',\n	), $atts));\n\n    if ($format == \'a\')\n    {\n	$class = ($class) ? \' class=\"\'.$class.\'\"\' : \'\';\n	return \'<a rel=\"home\" title=\"\'.$title.\'\" href=\"\'.hu.\'\"\'.$class.\'>\'.parse($thing).\'</a>\';\n    }\n\n	else\n	{\n		return \'<link rel=\"home\" title=\"\'.$title.\'\" href=\"\'.hu.\'\" />\';\n	}\n\n	return ($wraptag) ? tag($out, $wraptag) : $out;\n}\n\n', 'f9c3939808499bf2c4c574f3b94ccf8c', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cwh_hobo', 0, 'Christopher Horrell', 'http://horrell.ca/', '0.2', 'Generates a random hobo name from John Hogeman\'s book \"The Areas of my Expertise\".', '	<h1>cwh_hobo</h1>\n\n	<p>This plugin will display a random hobo name from John Hodgeman&#8217;s list of 700 hobo names taken from his book <a href=\"http://www.amazon.com/Areas-My-Expertise-John-Hodgman/dp/1594482225/ref=pd_bbs_sr_1/002-8070310-4622414?ie=UTF8&#38;s=books&#38;qid=1172890799&#38;sr=8-1\">The Areas of my Expertise</a>.</p>\n\n	<p>To use it, simply insert the following tag wherever you want a random hobo name to appear:</p>\n\n<pre><code>&#60;txp:cwh_hobo /&#62;\n</code></pre>\n\n	<p>and this will produce a hobo name and designated number, like so:</p>\n\n<pre><code>#171: Twink the Reading-Room Snoozer\n</code></pre>\n\n	<p>cwh_hobo also accepts two attributes: <code>wraptag</code> and <code>class</code>. So, for instance, if you want to wrap the output in a paragraph tag with the class of &#8220;hobo&#8221;, you would have the following:</p>\n\n<pre><code>&#60;txp:cwh_hobo wraptag=&#34;p&#34; class=&#34;hobo&#34; /&#62;\n</code></pre>\n\n	<p>which would produce:</p>\n\n<pre><code>&#60;p class=&#34;hobo&#34;&#62;#171: Twink the Reading-Room Snoozer&#60;/p&#62;\n</code></pre>\n\n', '\n// Attributes\n\nfunction cwh_hobo($atts)\n{\n	extract(lAtts(array(\n		\'class\'		 => \'\',\n		\'wraptag\'	 => \'\',\n	), $atts));\n\n	// Delightfull array of hobo names\n	$hobos = array(\n			\"#1: Stewbuilder Dennis\",\n			\"#2: Cholly the Yegg\",\n			\"#3: Holden the Expert Dreamtwister\",\n			\"#4: The Rza\",\n			\"#5: Jack Skunk\",\n			\"#6: Jack Skunk Fils\",\n			\"#7: Lord Dan X. Still-Standing\",\n			\"#8: Marlon Fitz-fancy\",\n			\"#9: Bazino Bazino, The Kid Whose Hair Is On Fire\",\n			\"#10: Whispering-Lies McGruder\",\n			\"#11: Nit Louse\",\n			\"#12: Dan&#8217;l Dinsmore Tackadoo\",\n			\"#13: Hobo Zero\",\n			\"#14: The Silver Jacket Man\",\n			\"#15: No-Shoulders Smalltooth Jones\",\n			\"#16: Sistery Brothery Nabob\",\n			\"#17: Name Withheld\",\n			\"#18: Staniel the Spaniel\",\n			\"#19: Frederick Bannister, the Tree Surgeon\",\n			\"#20: Tarnose Cohen\",\n			\"#21: Mr. Wilson Fancypants\",\n			\"#22: Floyd Dangle\",\n			\"#23: Shane Stoopback\",\n			\"#24: Wicked Paul Fourteen-Toes\",\n			\"#25: Normal-Faced Olaf\",\n			\"#26: Tearbaby Hannity Stoop\",\n			\"#27: The Damned Swede\",\n			\"#28: Pierre Tin-Hat\",\n			\"#29: Ol&#8217; Barb Stab-You-Quick\",\n			\"#30: Mr. Whist\",\n			\"#31: James Fenimore Cooper\",\n			\"#32: Twistback John, the Scoliosis Sufferer\",\n			\"#33: Sweet Daddy Champagne\",\n			\"#34: Senator Cletus Scoffpossum\",\n			\"#35: Horus, the Bird-Headed Fool\",\n			\"#36: 50-Tooth Slim\",\n			\"#37: Monk, the Monkey Man (which is to say: &#8220;the Man&#8221;)\",\n			\"#38: Thad the Bunter\",\n			\"#39: Balloonpopper Chillingsworth\",\n			\"#40: All-but-Dissertation Tucker Dummychuck\",\n			\"#41: Finnish Jim\",\n			\"#42: Flemish Jim\",\n			\"#43: Foreign Tomas, the Strangetalker\",\n			\"#44: Roadhouse Ogilvy and Sons\",\n			\"#45: Jokestealer John Selden\",\n			\"#46: Giancarlo, Master of the Metal Trapeze\",\n			\"#47: Dr. Bill Stain-Chin, the Boxcar Medic\",\n			\"#48: Boxcar Ted\",\n			\"#49: Boxcar Mick\",\n			\"#50: Boxcars [sic] Timothy Twin\",\n			\"#51: Boxcar Jones, the Boxcar Benjamin Disraeli\",\n			\"#52: Boxcar Aldous Huxley\",\n			\"#53: JR Lintstockings\",\n			\"#54: Gila Monster Jr.\",\n			\"#55: Irontrousers the Strong\",\n			\"#56: &#8220;X&#8221; the Anonymous Man or Woman\",\n			\"#57: Orphaned Reynaldo, the Child with Haunting Eyes (while there were children hoboes, Reynaldo took this when he was 45; prior to this, he was known as&#8230;[See #58])\",\n			\"#58: Reynaldo Reynaldoson, Who Will One Day Kill His Father\",\n			\"#59: Thoughtless Harry Hsu\",\n			\"#60: Clinical Psychiatrist Huga Rivera\",\n			\"#61: Peter Ox-Hands\",\n			\"#62: Ponytail Douglas Winthrop\",\n			\"#63: Lil&#8217; Jonny Songbird, the Songbird-Eater\",\n			\"#64: King Snake: The Eternal Mystery\",\n			\"#65: Ghostly Nose Silvie\",\n			\"#66: Fonzie\",\n			\"#67: DiCapa the Hound\",\n			\"#68: Beef-or-Chicken Bob Nubbins\",\n			\"#69: Honest Amelia Dirt\",\n			\"#70: Slow Motion Jones\",\n			\"#71: Canadian Football Pete\",\n			\"#72: Meep Meep, the Italian Tailor\",\n			\"#73: Jonathan William Coulton, the Colchester Kid\",\n			\"#74: Maria the Pumpkin-Patch Crooner\",\n			\"#75: Bix Shmix\",\n			\"#76: Vice President Garrett Hobart\",\n			\"#77: Stun Gun Jones\",\n			\"#78: Prostate Davey\",\n			\"#79: Flea Stick\",\n			\"#80: Niles Butterbal, the Frozen Turkey\",\n			\"#81: Todd Four-Flush\",\n			\"#82: Stick-Legs McOhio\",\n			\"#83: The Unanswered Question of Timothy\",\n			\"#84: Mickey the Assistant Manager\",\n			\"#85: Guesstimate Jones\",\n			\"#86: Goofus\",\n			\"#87: Gallant\",\n			\"#88: Sir Roundbelly DeDelight\",\n			\"#89: Newton Fig\",\n			\"#90: Chicken Nugget Will\",\n			\"#91: Parlor Peter, the Sneak Thief\",\n			\"#92: Ovid\",\n			\"#93: Bathsheba Ditz\",\n			\"#94: Alan Pockmark, Esq.\",\n			\"#95: Lolly Hoot Holler\",\n			\"#96: Von Skump\",\n			\"#97: Lonnie Choke\",\n			\"#98: Chisolm Chesthair\",\n			\"#99: Freak Le Freak, the Freakster\",\n			\"#100: Rex Spangler, the Bedazzler\",\n			\"#101: Randall Mouth-Harp\",\n			\"#102: Chrysler LeBaron\",\n			\"#103: The Fishin&#8217; Physician\",\n			\"#104: Persuasive Frederick\",\n			\"#105: Celestial Stubbs\",\n			\"#106: Teary-Eyed Fingal\",\n			\"#107: Mairah Nix\",\n			\"#108: Cthulhu Carl\",\n			\"#109: Del Folksy-Beard\",\n			\"#110: No-Banjo Burnes\",\n			\"#111: Chainmail Giles Godfrey\",\n			\"#112: Lois &#8220;Charles&#8221; Ladyfinger\",\n			\"#113: Plausible Zane Scarrey\",\n			\"#114: Huckle Smothered\",\n			\"#115: MmmmmDandy Dundee\",\n			\"#116: Mountain-Humper Edgar Ames\",\n			\"#117: Spasmodic Hilary\",\n			\"#118: Doc Aquatic\",\n			\"#119: Molly Bewigged\",\n			\"#120: Cincinnati O&#8217;Gurk\",\n			\"#121: Metuchen O&#8217;Sullivan\",\n			\"#122: Cherry Hill O&#8217;Manley\",\n			\"#123: Cheesequake O&#8217;Lennox\",\n			\"#124: Booper O&#8217;Montauk\",\n			\"#125: Zaxxon Galaxian\",\n			\"#126: Drinky Drunky Thom, the Drunk\",\n			\"#127: Terry Gross\",\n			\"#128: Spooky-Night Spooky Day\",\n			\"#129: Zipgun Gloucester Gluck\",\n			\"#130: Human Hair Frum\",\n			\"#131: Sherlock-Holmes-Hat Carl III\",\n			\"#132: Patrick Intergalactic\",\n			\"#133: Ambidextrous Stang\",\n			\"#134: Yum-Yum Sinclair Snowballeater\",\n			\"#135: Ponzi-Scheme Jeremiah Ponzi\",\n			\"#136: Toodles Strunk\",\n			\"#137: Monkeybars Matthew Manx\",\n			\"#138: Pineneedle-Jacket Jericho Fop\",\n			\"#139: Robert the Tot\",\n			\"#140: Robert the Child-Size\",\n			\"#141: Robert the Minuscule\",\n			\"#142: Robert the Wee\",\n			\"#143: Robert Fits-in-a-Case\",\n			\"#144: Robert Eats-for-Free\",\n			\"#145: Robert Is-He-an-Elf? (The seven Silk brothers, all named Robert, were also known for the small stature and predictable bitterness.)\",\n			\"#146: Dennis Big-Ear Fox\",\n			\"#147: Jethro the Pagan\",\n			\"#148: Asterix the Gaul\",\n			\"#149: Black Bolt, King of the Inhumans\",\n			\"#150: Strictly Local Henry Bobtail\",\n			\"#151: Manny the High-Ranking Mason\",\n			\"#152: Fry-Pan Jim Fry\",\n			\"#153: Slo-Mo Deuteronomy\",\n			\"#154: Half-Bearded Mark\",\n			\"#155: Knee-Brace Kenny\",\n			\"#156: Morris the Personal Trainer\",\n			\"#157: Thundertwine\",\n			\"#158: Cleats Onionpocket\",\n			\"#159: Deformed Abe\",\n			\"#160: Trainwhistle Abejundio\",\n			\"#161: David No-Ears\",\n			\"#162: Achilles Snail-Hair the Buddha\",\n			\"#163: Frog-Eatin&#8217; Lou\",\n			\"#164: Admiral&#8217;s Club Wilbur\",\n			\"#165: Max Meatboots, the First-Class Lounger\",\n			\"#166: Dora the Explorer\",\n			\"#167: Ms. Mary Manx, the Tailless Cat\",\n			\"#168: Free-Peanuts Doug\",\n			\"#169: Steve the Human Tunneler\",\n			\"#170: Redball Charlie Dickens\",\n			\"#171: Twink the Reading-Room Snoozer\",\n			\"#172: Microfiche Roy, the Side-Scroller\",\n			\"#173: McGurk, Who May Be Found by the Card Catalogue\",\n			\"#174: Booster D&#8217;Souza\",\n			\"#175: Commodore Sixty-Four\",\n			\"#176: Moped Enid, the Mopedist\",\n			\"#177: Lamant the Junkman\",\n			\"#178: Fast-Neck Nell\",\n			\"#179: Bill Never-Uses-a-Cookbook\",\n			\"#180: Bee-Beard\",\n			\"#181: Lil&#8217; Max Meatboots\",\n			\"#182: The Personal Secretary to Jed\",\n			\"#183: Dee Snider\",\n			\"#184: Sausage Patty\",\n			\"#185: Desert Locust\",\n			\"#186: Gummy Miles\",\n			\"#187: Gyppo Moot, the Enigma Machine\",\n			\"#188: Ol&#8217; Stiffpants\",\n			\"#189: Skywise the Sexual Elf\",\n			\"#190: Craine T. Eyebrow-Smeller\",\n			\"#191: Lonely Heiney Alan Meister\",\n			\"#192: Shakey Aitch the Boneyard Concierge\",\n			\"#193: Woody Damn\",\n			\"#194: Alatar\",\n			\"#195: Pallando\",\n			\"#196: Saltfish Bunyan\",\n			\"#197: Poor, Poor, Poor Charlie Short\",\n			\"#198: Venomous Byron\",\n			\"#199: Five-Chambered-Stomach Mort St. John\",\n			\"#200: Gravybelly Dunstan\",\n			\"#201: Extra-Skin Dave\",\n			\"#202: Beanbag-Chair Bill\",\n			\"#203: Grant Sharpnails, the Scratcher\",\n			\"#204: Tommy Lice-Comb\",\n			\"#205: &#8220;Medicated Shampoo&#8221; Jonah Jump\",\n			\"#206: General Woundwort, the Giant Rabbit\",\n			\"#207: Genius L. Cravat, the Gentleman\",\n			\"#208: Giant Bat Wings Roland\",\n			\"#209: Nick Nolte\",\n			\"#210: Salty Salty Friday\",\n			\"#211: Fatman and the Creature (note: there was no creature)\",\n			\"#212: Cecelia Graveside\",\n			\"#213: Hoosegow Earl French\",\n			\"#214: Stymie Stonewrist\",\n			\"#215: Roadrunner &#8220;Meep Meep&#8221; Fabong\",\n			\"#216: Bruised-Rib Johansson, the Beefer\",\n			\"#217: Joachim Bat-in-Hair\",\n			\"#218: Food-Eating Micah\",\n			\"#219: Rubbery Dmitry, the Mad Monk\",\n			\"#220: Honey Bunches of Donald\",\n			\"#221: Crispy Morton\",\n			\"#222: Feminine Forearms Rosengarten\",\n			\"#223: Two-Headed Mike Hoover\",\n			\"#224: Manny Stillwaggon, the Man with the Handlebar Eyebrows\",\n			\"#225: Bean-Hoarder Newt\",\n			\"#226: Texas Emil\",\n			\"#227: The Moor of Venice\",\n			\"#228: Averroes Nix\",\n			\"#229: Human Hair Blanket Morris Burnes\",\n			\"#230: Canadian Paul Tough\",\n			\"#231: Crooner Sy\",\n			\"#232: Manuel Pants-Too-High\",\n			\"#233: Sylvia Patience Hidden-Forks\",\n			\"#234: Sung, the Land Pirate\",\n			\"#235: Opie, the Boston Bum\",\n			\"#236: Hard-Flossing Hope Peak\",\n			\"#237: Stingo the Bandana Origami Prodigy\",\n			\"#238: Franklin Ape and His Inner Ear Infection\",\n			\"#239: Questionable-Judgement Theodore Stomachbrace\",\n			\"#240: Thermos H. Christ\",\n			\"#241: Sir Mix-a-Lot\",\n			\"#242: The Nine Doctor Whos\",\n			\"#243: Lord Winston Two-Monocles\",\n			\"#244: The Freewheelin&#8217; Barry Sin\",\n			\"#245: Diego the Spark-Spitter\",\n			\"#246: American Citizen Zane Pain\",\n			\"#247: Abraham, the Secret Collector of Decorative China\",\n			\"#248: Linty Sullivan, the Lint-Collector\",\n			\"#249: Socks Monster\",\n			\"#250: Ma Churchill\",\n			\"#251: Pappy Churchill\",\n			\"#252: The Young Churchill\",\n			\"#253: The Young Churchill&#8217;s Hated Bride\",\n			\"#254: Churchill-Lover Phineas Redfish\",\n			\"#255: Crispus T. Muzzlewitt\",\n			\"#256: Stain-Sucker Duncan\",\n			\"#257: Dick the Candy Dandy\",\n			\"#258: Albuterol Inhaler Preson McWeak\",\n			\"#259: Longtime Listener, First-Time Caller\",\n			\"#260: Mastiff Mama\",\n			\"#261: Tennessee Ernie Dietz\",\n			\"#262: Sharkey, the Secret Cop\",\n			\"#263: Gooseberry Johnson, Head Brain of the Hobosphere\",\n			\"#264: Weekend-Circular Deborah\",\n			\"#265: Marcus Chickenstock\",\n			\"#266: Stunted Newton\",\n			\"#267: Magnus Shortwave\",\n			\"#268: U.S. Fool\",\n			\"#269: Manatee the Railyard Toreador\",\n			\"#270: Utah Manfred Succor-Munt\",\n			\"#271: Laura Delite\",\n			\"#272: Edwin Winnipeg\",\n			\"#273: Eyepatch Resse Andiron\",\n			\"#274: Tom False-Lips Real-Teeth\",\n			\"#275: Fabulon Darkness\",\n			\"#276: Cricket-Eating Charles Digges\",\n			\"#277: Pally McAffable, Everybody&#8217;s Friend\",\n			\"#278: Sully Straightjacket\",\n			\"#279: Half-Dollar Funk Nelson\",\n			\"#280: Whitman Sampler\",\n			\"#281: Chili-Mix Wilma Bensen\",\n			\"#282: Sting, the Glowing Blade\",\n			\"#283: Professor Challenger\",\n			\"#284: Lil&#8217; Shorty Longhorn\",\n			\"#285: Rumpshaker Phil\",\n			\"#286: Swing State Myron\",\n			\"#287: Alistair Crowley, the Devil\",\n			\"#288: Gutthrower Sy Salt\",\n			\"#289: Sweetback Barney, the Dilettante\",\n			\"#290: The Car-Knocker Killer\",\n			\"#291: The Chamberlain\",\n			\"#292: The Emperor\",\n			\"#293: The Ritual-Master\",\n			\"#294: The Garthim-Master\",\n			\"#295: The Scientist\",\n			\"#296: The Gourmand\",\n			\"#297: The Slave-Master\",\n			\"#298: The Treasurer\",\n			\"#299: The Scroll Keeper\",\n			\"#300: The Ornamentalist\",\n			\"#301: Captain Slick-Talk\",\n			\"#302: Sackfist, the Tapdancing Trombo\",\n			\"#303: Souvenir-Selling Mlodinow\",\n			\"#304: Blind Buck and &#8220;Woozy,&#8221; the Invisible Seeing-Eye Dog\",\n			\"#305: Roundhouse Farter\",\n			\"#306: Red Ball Pnutz\",\n			\"#307: Fake Cockney Accent Alan Strippe\",\n			\"#308: Air and Whiskey Dale McGlue\",\n			\"#309: Johnny RC Airplane\",\n			\"#310: Narcotic Morgan Suds\",\n			\"#311: Sir Francis Drank\",\n			\"#312: Mahayana Mike\",\n			\"#313: Miniyana Geoffrey\",\n			\"#314: Three-Bean Otz\",\n			\"#315: Maury the Monsoon\",\n			\"#316: Czech Czarlie Czill\",\n			\"#317: Sssssssssssssssss, the Hisser\",\n			\"#318: Thanatos Koch\",\n			\"#319: Henry Eatsmelts\",\n			\"#320: Modem-Sniffer Gunderson\",\n			\"#321: Half-Albino Alejandro\",\n			\"#322: Gluttonous-Slim\",\n			\"#323: Ragweed-Allergic Matt\",\n			\"#324: Amorous Luminous Dirk\",\n			\"#325: Moray Eel Ken Elmer\",\n			\"#326: The Railbender\",\n			\"#327: Antonio the Ombudsman\",\n			\"#328: Karl Solenoid IV\",\n			\"#329: Czar King Rex the Glorious Leader\",\n			\"#330: Andy Bunkum\",\n			\"#331: Plastic-Moustache Mortimer Tall\",\n			\"#332: Samuel Gel Insole\",\n			\"#333: Lemuel Gel Insole\",\n			\"#334: Amanda Until\",\n			\"#335: Crispy Whiskery\",\n			\"#336: Robert Louis Stevenson, the Pirate\",\n			\"#337: Hobo Overload\",\n			\"#338: Leopard Print Steven Kane\",\n			\"#339: Astonishing Shaun Eyelash\",\n			\"#340: Billy Creak Knees\",\n			\"#341: Owlie\",\n			\"#342: Anwar, the Bionic\",\n			\"#343: Reasonably Priced Motel Resse Unger\",\n			\"#344: Fibery Dana\",\n			\"#345: Cranberry Sauce Oppenheimer\",\n			\"#346: Nancified Frederick\",\n			\"#347: The Loon\",\n			\"#348: Itinerant Jane\",\n			\"#349: Holy Hannah Hottentot-Smythe\",\n			\"#350: Fleabottle Boone\",\n			\"#351: Amazin&#8217; Jack Caroo\",\n			\"#352: Stupefying P, the Riddle-Maker\",\n			\"#353: Todd Flaky-Palms\",\n			\"#354: Waspwaist Fritz\",\n			\"#355: Judge Roughneck\",\n			\"#356: Slam Dance Dooze\",\n			\"#357: Mariah Duckface, the Beaked Woman\",\n			\"#358: Count Mesmerize\",\n			\"#359: Sonny-Boy Oedipus Acre\",\n			\"#360: Pick Mama Susan Xavier\",\n			\"#361: Chelsea Bacon\",\n			\"#362: Archie Axe\",\n			\"#363: Sally Hoot-Hoot\",\n			\"#364: Mr. Pendleton\",\n			\"#365: Saves-Receipts Dave\",\n			\"#366: Sir Walter British\",\n			\"#367: Elmer, the Crankscout\",\n			\"#368: Golden Neck\",\n			\"#369: Marinated Alex Pons\",\n			\"#370: El Boot\",\n			\"#371: Shapeshifting Demon\",\n			\"#372: Jeremiah Tip Top\",\n			\"#373: Amanda CeeCee Strobelight\",\n			\"#374: Irving Alva Edison, Inventor of the Hobophone\",\n			\"#375: Leather Apron\",\n			\"#376: Lead Apron\",\n			\"#377: Foil Apron\",\n			\"#378: Burnt Goathead\",\n			\"#379: Saint Sorryass\",\n			\"#380: Overly Familiar Fung\",\n			\"#381: Chalmers, the Bridge Champ\",\n			\"#382: Elephantine McMoot\",\n			\"#383: Neekerbeeker Perry Toenz\",\n			\"#384: Teattime BB Stiles\",\n			\"#385: Coalie T\",\n			\"#386: Hubbel &#8220;I Predicted Lindy Hop&#8221; Deerblind\",\n			\"#387: Hubie Hewitt, the Broadway Legend\",\n			\"#388: Huge Crybaby McWeepy\",\n			\"#389: Poo-Knickers Elias\",\n			\"#390: Elffriend Weingarten\",\n			\"#391: Forktongue Nigel Fork\",\n			\"#392: Woodeye Apfel\",\n			\"#393: Hairlip Mikhail\",\n			\"#394: Solid First Draft Patton Taylor\",\n			\"#395: Prettynickels, the Lamb\",\n			\"#396: Not-Only But-Also Pete\",\n			\"#397: Penthief Hickock\",\n			\"#398: La Grande Mel\",\n			\"#399: Applebee O&#8217;Bennigan McFridays\",\n			\"#400: Lardy Jerry Lardo\",\n			\"#401: Low-Carb Aleks Stovepipe\",\n			\"#402: Hugo Stares\",\n			\"#403: Eldred Splinters\",\n			\"#404: Oliver, the Train-Oyster\",\n			\"#405: Pring, Ultralord of the Hobo Jungle\",\n			\"#406: Utz, the Crab Chip\",\n			\"#407: Salt-and-Pepper Chest\",\n			\"#408: Beverly Hills Buntz\",\n			\"#409: Mississippi Barry Phlegm\",\n			\"#410: Matter-Eater Brad\",\n			\"#411: 49-State Apthorp, the Alaska-Phobe\",\n			\"#412: New Hampshire Todd\",\n			\"#413: &#8220;Taxachusetts&#8221; Glenn\",\n			\"#414: Hydrocephalic Jones\",\n			\"#415: Vermont &#8220;Greenmountain Boy&#8221; Phil Marijuana\",\n			\"#416: Alaska Mick the Crabber\",\n			\"#417: Arizona Ludwig\",\n			\"#418: California Ainsley Shortpants\",\n			\"#419: Collegeboy Brainiac, the Hobo Einstein\",\n			\"#420: Dr. Zizmor\",\n			\"#421: Silas Swollentoe\",\n			\"#422: Slimneck Holden Fop\",\n			\"#423: Aspiring Jaster\",\n			\"#424: Illinois Obama\",\n			\"#425: Sammy Austere\",\n			\"#426: New Mexico Anselm Turquoise-Eater\",\n			\"#427: Caboose-Fouling Ferris Ntz\",\n			\"#428: Prayerful Stan, the Bent-Knee Yahoo\",\n			\"#429: Four-Fisted Jock Socko\",\n			\"#430: Buttery-Cheeks Anton\",\n			\"#431: Shadow (&#8220;Blinky&#8221;) Preston\",\n			\"#432: Godigisel the Vandal\",\n			\"#433: Gunderic Godigiselson\",\n			\"#434: Panzo the Spiral-Cut Ham\",\n			\"#435: Smoke-Collecting Reg\",\n			\"#436: Hot Gnome Jimmy Jackson\",\n			\"#437: Pontius Cornsilk-Heart\",\n			\"#438: Sanford Who Lacks Fingerprints\",\n			\"#439: Treesap-Covered N. Magruder\",\n			\"#440: Thor Hammerskold, the Mexican\",\n			\"#441: Bingo-Balls Nick Chintz\",\n			\"#442: Bleedingtoe the Barefoot &#8217;Bo\",\n			\"#443: Hondo &#8220;Whatever That Lizard Is That Walks on Water&#8221;\",\n			\"#444: Salami Sunshine\",\n			\"#445: Fourteen-Bindelstick Frank\",\n			\"#446: Oregon Brucie Shunt\",\n			\"#447: Pirandello, the Many-Bearded\",\n			\"#448: Quinn and His Quaker Oats Box Drum\",\n			\"#449: Fatneck Runt\",\n			\"#450: my-e-hobo.com\",\n			\"#451: Somersaulting Mike Spitz\",\n			\"#452: Bo &#8217;Bo\",\n			\"#453: Abelard &#8220;Sunken Treasure&#8221; Lowtrousers\",\n			\"#454: Colin, That Cheerful Fuck\",\n			\"#455: Battling Joe Frickinfrack\",\n			\"#456: Monsieur Dookie, the Francophonic\",\n			\"#457: Happy Horace Noosemaker\",\n			\"#458: Hieronymous Crosseyes\",\n			\"#459: Crumbjacket Timmy\",\n			\"#460: Overload-the-Dishwasher Mac\",\n			\"#461: Phythmic Clyde Hopp\",\n			\"#462: Microbrew Stymie\",\n			\"#463: El Caballo, the Spanish Steed\",\n			\"#464: Lee Burned-Beyond-Recognition\",\n			\"#465: Hollering Martin Mandible\",\n			\"#466: Damien Pitchfork, the Freightyard Satan\",\n			\"#467: Handformed Hamburger Clarence West\",\n			\"#468: Dr. Nobel Dynamite\",\n			\"#469: Pickled-Noggin Nettles\",\n			\"#470: Mischievous Craig\",\n			\"#471: Baldy Lutz, the Amityville Horror\",\n			\"#472: Ashen Merle Buzzard\",\n			\"#473: Frypan Nonstick McGee\",\n			\"#474: Singleminded Hubbard\",\n			\"#475: Maryland Sol Saynomore\",\n			\"#476: Baked Salmon Salad Finn\",\n			\"#477: Unshakably Morose Flo\",\n			\"#478: Fr. Christian Irish, the Deep-Fat Friar\",\n			\"#479: Smokestack-Hugger Jools Nygaard\",\n			\"#480: Fossilwise Opie Fingernail\",\n			\"#481: Tab-Collar Dix\",\n			\"#482: George Slay, the Duck Throttler\",\n			\"#483: Eldon Waxhat, the Waterproof Man\",\n			\"#484: Timely Clayton, the Human Wristwatch (so named not because of his punctuality, but because one arm was significantly shorter than the other)\",\n			\"#485: Both Dakotas Dave\",\n			\"#486: Duke Jeremiah Choo-Choo\",\n			\"#487: Transistorized Maximillian, the Hobo Cyborg\",\n			\"#488: Gravelbed Gavin Astor\",\n			\"#489: Pantless, Sockless, Shoeless Buster Bareass\",\n			\"#490: Alternate-Dimension Bela Boost\",\n			\"#491: Atlas Flatshoulders\",\n			\"#492: Scurvied Leo Falsebreath\",\n			\"#493: Toby Anchovy, the Canned Man\",\n			\"#494: Mad Max\",\n			\"#495: The Goose\",\n			\"#496: Not the Goose\",\n			\"#497: Mister Torso, the Legless Wonder\",\n			\"#498: Jedediah Dryasdust\",\n			\"#499: Loving Vincent Hugsalot\",\n			\"#500: The Rambling, Rambling Boris Wander\",\n			\"#501: Business-Class Klaus Riel\",\n			\"#502: Emergency Exit Aisle Gustav Nook\",\n			\"#503: Unnervingly Candid Nicky Thain\",\n			\"#504: Snoops Lightstep Trenchcoat, the Hobo PI\",\n			\"#505: William Carlos Williams\",\n			\"#506: Beef Grease Porter Dripchin\",\n			\"#507: Exoskeleton Chester Fields\",\n			\"#508: Roth IRA Romeo Leeds, the Well Prepared\",\n			\"#509: Bum-Smiter Phillip\",\n			\"#510: Bum-Hating Virgil Hate-Bum\",\n			\"#511: Thor the Bum-Hammer\",\n			\"#512: Bum-Tolerant Brendan Sleek\",\n			\"#513: Most Agree: It&#8217;s Kilpatrick\",\n			\"#514: The Beloved Dale Thankyounote\",\n			\"#515: Unpronounceable\",\n			\"#516: Thad Malfeasance\",\n			\"#517: Chiseltooth Muck Manly\",\n			\"#518: Amsterdam Jocko\",\n			\"#519: Sinister Leonard Longhair\",\n			\"#520: Beery Clive the Eunuch\",\n			\"#521: Chaim the Squirrelkeeper\",\n			\"#522: Nightblind and Colorblind, the Blind Twins\",\n			\"#523: Milosz the Anarchist Puppeteer\",\n			\"#524: Jimmy &#8220;New Man&#8221; Neandertal\",\n			\"#525: Lonnie Pina Colada\",\n			\"#526: Washington State Amy Swipe\",\n			\"#527: Gopher-State Sam, the Minnesota Man\",\n			\"#528: Candle-Eyed Sally\",\n			\"#529: Packrat Red and his Cart o&#8217; Sad Crap\",\n			\"#530: Trixie of the East\",\n			\"#531: Trixie of the West\",\n			\"#532: Fine-Nipple Tom Bazoo\",\n			\"#533: The Friends of Reginald McHate Society\",\n			\"#534: Oregon Perry Hashpipe\",\n			\"#535: Bold &#8217;n&#8217; Zesty Brad\",\n			\"#536: Mermaid Betty Scales\",\n			\"#537: Spotted Dick\",\n			\"#538: Shanty Queen Elizabeth Regina\",\n			\"#539: Nichols Crackknuckle\",\n			\"#540: Stew Socksarewarm\",\n			\"#541: Huge-Calves Dwight\",\n			\"#542: A-Number-1\",\n			\"#543: N-Number-13\",\n			\"#544: Arthur Moonlight\",\n			\"#545: Andrea Clarke, the Human Shark\",\n			\"#546: Monkey&#8217;s-Paw Patterson\",\n			\"#547: Myron Biscuitspear, the Dumpster Archeologist\",\n			\"#548: Ollie Ebonsquirrel\",\n			\"#549: The Classic Brett Martin\",\n			\"#550: Douglas, the Future of Hoboing\",\n			\"#551: Ironbelly Norton\",\n			\"#552: Dilly Shinguards\",\n			\"#553: Rufus Caboose\",\n			\"#554: Rear Admiral JF Grease Pencil\",\n			\"#555: King Cotton\",\n			\"#556: Prince Hal Oystershuck, the Royal Shucker\",\n			\"#557: Unconditional Gavin\",\n			\"#558: Squirrelcloak\",\n			\"#559: Idaho Woody Harrelson\",\n			\"#560: Jane the Boxcar Beekeeper\",\n			\"#561: Aaron Three-Shirts\",\n			\"#562: Paste-Smeller Luke\",\n			\"#563: Lowly Highley\",\n			\"#564: Elihu Skinpockets\",\n			\"#565: Marian May Wyomingsong\",\n			\"#566: Stitches the Railyard Sutureman\",\n			\"#567: Klonopin Clyde\",\n			\"#568: Benny Twenty-Squirrels\",\n			\"#569: Chickeny-Flavored Remy Bunk\",\n			\"#570: Juicepockets Thomas Moone\",\n			\"#571: Eustace Feetbeer\",\n			\"#572: Amnesiac Jared Stringy\",\n			\"#573: Shagrat, Orc of the Ozarks\",\n			\"#574: Billy Butterfly Net\",\n			\"#575: Ammonia Cocktail Jones\",\n			\"#576: Norma Shinynickels\",\n			\"#577: Jonathan Crouton\",\n			\"#578: Antigone Spit\",\n			\"#579: El Top-Hat Swindlefingers\",\n			\"#580: PomPom the Texas Dancing Dog\",\n			\"#581: Gin-Bucket Greg\",\n			\"#582: Yuri Trimble, the Alien Pod-Person\",\n			\"#583: South Carolina Sarah Lardblood\",\n			\"#584: Bloody-Stool LaSalle\",\n			\"#585: Pith-Helmet Andy\",\n			\"#586: Self-Taught-Guitarist Edmund\",\n			\"#587: Don Tomasino di Shit-the-Bed\",\n			\"#588: Markansas\",\n			\"#589: Neckfat JK Trestle\",\n			\"#590: Pansy Overpass\",\n			\"#591: Ralph Raclette Cornichon, Hobo of the Mountains\",\n			\"#592: Montana Nbdego Tch!k\",\n			\"#593: Unbearably Oenophilic Ned\",\n			\"#594: Jonas Tugboy, Professional Masturbator\",\n			\"#595: Cinderfella Dana Dane\",\n			\"#596: Kerosene-Soaked Tom\",\n			\"#597: Black-Bottle Priam\",\n			\"#598: Pinprick Butell\",\n			\"#599: Stool-Sample Frank\",\n			\"#600: Iowa Noam Chomksy\",\n			\"#601: Etienne, Roi of the Rapier\",\n			\"#602: Amesy Squirrelstomper, the Chipmunk Preferrer\",\n			\"#603: Ned Gravelshirt\",\n			\"#604: NPR Willard Hotz, the Soothing-Voiced\",\n			\"#605: Amen to Polly Fud\",\n			\"#606: Constantly Sobbing Forrester\",\n			\"#607: Maine-iac Leonid\",\n			\"#608: Magnetized James\",\n			\"#609: Hobo Jake Jerrold, Representing the Whole Mid-Atlantic Region\",\n			\"#610: Jiminy Sinner\",\n			\"#611: Pamela Chickeneggs (i.e., Hobo Caviar)\",\n			\"#612: Chuck McKindred: Not So Holy, But Very Moley\",\n			\"#613: Q the Quantum Man\",\n			\"#614: Salad-Fork Ron\",\n			\"#615: Warbling Timmy Tin Voice and His Voicebox\",\n			\"#616: Ambassador Roasting Pan\",\n			\"#617: Warren Smazell, Founder of Hobotics&#174;\",\n			\"#618: Ventriloquism Jimmy and &#8220;Madam&#8221; the Talking Bean Can\",\n			\"#619: Nosepicker Rick Pick\",\n			\"#620: The Black Squirrel Fairy\",\n			\"#621: Alabama Edsel Brainquake\",\n			\"#622: Kid Silverhair, the Man of Indeterminate Age\",\n			\"#623: Catscratch Tremont Nude\",\n			\"#624: Bill Jaundice\",\n			\"#625: Sugarhouse Morris the Sapper\",\n			\"#626: Nutrition-Shake Emery\",\n			\"#627: Nicknameless Norris Shine\",\n			\"#628: Stinging Polly Papercuts\",\n			\"#629: Deke Hidden Hornets&#8217; Nest\",\n			\"#630: The Wisconsin Scourge\",\n			\"#631: Brendan Headbristles\",\n			\"#632: His Excellency Nooney Sockjelly\",\n			\"#633: Whistling Anus Mecham, Le Petomaine\",\n			\"#634: Talmidge, the Bactine Bearer\",\n			\"#635: Tailstump Gunther, the Vestigial Man\",\n			\"#636: The Hon. Charlie Weed-Farmer\",\n			\"#637: Philatelist Joey Licks\",\n			\"#638: Old Pliny Dance-for-Ham\",\n			\"#639: Rheumy Sven\",\n			\"#640: Wormy Glenn and Nootka the Flatworm\",\n			\"#641: Hidalgo, the Devil Stick Artiste\",\n			\"#642: The Fucky From Kentucky\",\n			\"#643: Prince Bert in Exile, the Man in the Foil Mask\",\n			\"#644: Siderodromophobic Billy\",\n			\"#645: Antlered Calvin\",\n			\"#646: Cambridge Massachusetts Claude\",\n			\"#647: Cyrus the Persian Sturgeon\",\n			\"#648: Kneepants Erasmus, the Humanist\",\n			\"#649: Little Gavin Spittle\",\n			\"#650: Tar-tongue Godfrey Strange\",\n			\"#651: Honeypalms Gordon Lips\",\n			\"#652: Luke &#8220;the Lifestyle&#8221; Dammmers\",\n			\"#653: Simon Squirrelskin\",\n			\"#654: Scabpicker Sandy Rump\",\n			\"#655: Chicken Butt, Five Cents a Cut\",\n			\"#656: Wise Solomon Babysplitter\",\n			\"#657: Telekinetic Dave B.\",\n			\"#658: Telekinetic Dave F.\",\n			\"#659: Whiskeyblood Willie Sot\",\n			\"#660: Unger and his Duststorm Bride\",\n			\"#661: Zachary Goatflirter\",\n			\"#662: &#8220;La Grippe&#8221;\",\n			\"#663: Uranus John, the Star-Traveler\",\n			\"#664: Accusin&#8217; Tim Dunn\",\n			\"#665: Tennessee Linthelmet\",\n			\"#666: The Unformed Twin of Tennessee Linthelmet\",\n			\"#667: Turkeyballs Paco\",\n			\"#668: Andre the Indianapolist\",\n			\"#669: Wally Dregs, the Newfoundland Screech\",\n			\"#670: Flaky Mike Psoriasis\",\n			\"#671: Hell&#8217;s Own Breath Hinkley\",\n			\"#672: Gerald Chapcheeks\",\n			\"#673: Acid-Saliva Curly Stokes\",\n			\"#674: Oklahoma Stilgar\",\n			\"#675: Rocky Shitstain Mankowicz\",\n			\"#676: Rocky Shitstain Mankowicz Part II, the Quickening\",\n			\"#677: Professor &#937;\",\n			\"#678: Sanitized-for-Your-Protection Eddie Summers\",\n			\"#679: Jan, the Jager-Meister\",\n			\"#680: Big-Tipper Silas Fake-Nickel\",\n			\"#681: Anaerobic Eben Stiles\",\n			\"#682: Replicant Wemberly Plastiskin and his Clockwork Squirrel &#8220;Toothy&#8221;\",\n			\"#683: Harry Coughblood\",\n			\"#684: Aesop Bedroll, the Fluffy Pillow Man\",\n			\"#685: Widow-Kisser Roger\",\n			\"#686: Experimental Hobo Infiltration Droid &#8220;41-K&#8221;\",\n			\"#687: Baron Bayonet, the Bull-Sticker\",\n			\"#688: Mikey Gluesniff\",\n			\"#689: Bell&#8217;s-Palsy Brennan\",\n			\"#690: Chiptooth Berman, the Bottle Biter\",\n			\"#691: Undertaker Robert, the Lint-Coffin Weaver\",\n			\"#692: Betty, the Exorcist\",\n			\"#693: Tittytwister Blake Horrid\",\n			\"#694: Mallory Many-Bruises\",\n			\"#695: Mad or Sad Judd (no one can tell)\",\n			\"#696: Troglodytic Amory Funt\",\n			\"#697: Smokehouse &#8220;Frankie&#8221; Jowl-Poker\",\n			\"#698: Utility-Belt Deana\",\n			\"#699: The Unshakable Will of Wade Terps\",\n			\"#700: Trainwhistle Ernie Roosevelt, the President&#8217;s Long-Lost Brother\",\n	);\n\n	// Pick the key for a random entry\n	$randhobo = array_rand($hobos, 1);\n\n	// Assigns value from randomly selected key to $showhobo\n	$showhobo = $hobos[$randhobo];\n\n	// Returns value with optional wrap tag and class\n	return ($wraptag) ? doTag ($showhobo, $wraptag, $class) : $showhobo;\n\n}\n', '\n// Attributes\n\nfunction cwh_hobo($atts)\n{\n	extract(lAtts(array(\n		\'class\'		 => \'\',\n		\'wraptag\'	 => \'\',\n	), $atts));\n\n	// Delightfull array of hobo names\n	$hobos = array(\n			\"#1: Stewbuilder Dennis\",\n			\"#2: Cholly the Yegg\",\n			\"#3: Holden the Expert Dreamtwister\",\n			\"#4: The Rza\",\n			\"#5: Jack Skunk\",\n			\"#6: Jack Skunk Fils\",\n			\"#7: Lord Dan X. Still-Standing\",\n			\"#8: Marlon Fitz-fancy\",\n			\"#9: Bazino Bazino, The Kid Whose Hair Is On Fire\",\n			\"#10: Whispering-Lies McGruder\",\n			\"#11: Nit Louse\",\n			\"#12: Dan&#8217;l Dinsmore Tackadoo\",\n			\"#13: Hobo Zero\",\n			\"#14: The Silver Jacket Man\",\n			\"#15: No-Shoulders Smalltooth Jones\",\n			\"#16: Sistery Brothery Nabob\",\n			\"#17: Name Withheld\",\n			\"#18: Staniel the Spaniel\",\n			\"#19: Frederick Bannister, the Tree Surgeon\",\n			\"#20: Tarnose Cohen\",\n			\"#21: Mr. Wilson Fancypants\",\n			\"#22: Floyd Dangle\",\n			\"#23: Shane Stoopback\",\n			\"#24: Wicked Paul Fourteen-Toes\",\n			\"#25: Normal-Faced Olaf\",\n			\"#26: Tearbaby Hannity Stoop\",\n			\"#27: The Damned Swede\",\n			\"#28: Pierre Tin-Hat\",\n			\"#29: Ol&#8217; Barb Stab-You-Quick\",\n			\"#30: Mr. Whist\",\n			\"#31: James Fenimore Cooper\",\n			\"#32: Twistback John, the Scoliosis Sufferer\",\n			\"#33: Sweet Daddy Champagne\",\n			\"#34: Senator Cletus Scoffpossum\",\n			\"#35: Horus, the Bird-Headed Fool\",\n			\"#36: 50-Tooth Slim\",\n			\"#37: Monk, the Monkey Man (which is to say: &#8220;the Man&#8221;)\",\n			\"#38: Thad the Bunter\",\n			\"#39: Balloonpopper Chillingsworth\",\n			\"#40: All-but-Dissertation Tucker Dummychuck\",\n			\"#41: Finnish Jim\",\n			\"#42: Flemish Jim\",\n			\"#43: Foreign Tomas, the Strangetalker\",\n			\"#44: Roadhouse Ogilvy and Sons\",\n			\"#45: Jokestealer John Selden\",\n			\"#46: Giancarlo, Master of the Metal Trapeze\",\n			\"#47: Dr. Bill Stain-Chin, the Boxcar Medic\",\n			\"#48: Boxcar Ted\",\n			\"#49: Boxcar Mick\",\n			\"#50: Boxcars [sic] Timothy Twin\",\n			\"#51: Boxcar Jones, the Boxcar Benjamin Disraeli\",\n			\"#52: Boxcar Aldous Huxley\",\n			\"#53: JR Lintstockings\",\n			\"#54: Gila Monster Jr.\",\n			\"#55: Irontrousers the Strong\",\n			\"#56: &#8220;X&#8221; the Anonymous Man or Woman\",\n			\"#57: Orphaned Reynaldo, the Child with Haunting Eyes (while there were children hoboes, Reynaldo took this when he was 45; prior to this, he was known as&#8230;[See #58])\",\n			\"#58: Reynaldo Reynaldoson, Who Will One Day Kill His Father\",\n			\"#59: Thoughtless Harry Hsu\",\n			\"#60: Clinical Psychiatrist Huga Rivera\",\n			\"#61: Peter Ox-Hands\",\n			\"#62: Ponytail Douglas Winthrop\",\n			\"#63: Lil&#8217; Jonny Songbird, the Songbird-Eater\",\n			\"#64: King Snake: The Eternal Mystery\",\n			\"#65: Ghostly Nose Silvie\",\n			\"#66: Fonzie\",\n			\"#67: DiCapa the Hound\",\n			\"#68: Beef-or-Chicken Bob Nubbins\",\n			\"#69: Honest Amelia Dirt\",\n			\"#70: Slow Motion Jones\",\n			\"#71: Canadian Football Pete\",\n			\"#72: Meep Meep, the Italian Tailor\",\n			\"#73: Jonathan William Coulton, the Colchester Kid\",\n			\"#74: Maria the Pumpkin-Patch Crooner\",\n			\"#75: Bix Shmix\",\n			\"#76: Vice President Garrett Hobart\",\n			\"#77: Stun Gun Jones\",\n			\"#78: Prostate Davey\",\n			\"#79: Flea Stick\",\n			\"#80: Niles Butterbal, the Frozen Turkey\",\n			\"#81: Todd Four-Flush\",\n			\"#82: Stick-Legs McOhio\",\n			\"#83: The Unanswered Question of Timothy\",\n			\"#84: Mickey the Assistant Manager\",\n			\"#85: Guesstimate Jones\",\n			\"#86: Goofus\",\n			\"#87: Gallant\",\n			\"#88: Sir Roundbelly DeDelight\",\n			\"#89: Newton Fig\",\n			\"#90: Chicken Nugget Will\",\n			\"#91: Parlor Peter, the Sneak Thief\",\n			\"#92: Ovid\",\n			\"#93: Bathsheba Ditz\",\n			\"#94: Alan Pockmark, Esq.\",\n			\"#95: Lolly Hoot Holler\",\n			\"#96: Von Skump\",\n			\"#97: Lonnie Choke\",\n			\"#98: Chisolm Chesthair\",\n			\"#99: Freak Le Freak, the Freakster\",\n			\"#100: Rex Spangler, the Bedazzler\",\n			\"#101: Randall Mouth-Harp\",\n			\"#102: Chrysler LeBaron\",\n			\"#103: The Fishin&#8217; Physician\",\n			\"#104: Persuasive Frederick\",\n			\"#105: Celestial Stubbs\",\n			\"#106: Teary-Eyed Fingal\",\n			\"#107: Mairah Nix\",\n			\"#108: Cthulhu Carl\",\n			\"#109: Del Folksy-Beard\",\n			\"#110: No-Banjo Burnes\",\n			\"#111: Chainmail Giles Godfrey\",\n			\"#112: Lois &#8220;Charles&#8221; Ladyfinger\",\n			\"#113: Plausible Zane Scarrey\",\n			\"#114: Huckle Smothered\",\n			\"#115: MmmmmDandy Dundee\",\n			\"#116: Mountain-Humper Edgar Ames\",\n			\"#117: Spasmodic Hilary\",\n			\"#118: Doc Aquatic\",\n			\"#119: Molly Bewigged\",\n			\"#120: Cincinnati O&#8217;Gurk\",\n			\"#121: Metuchen O&#8217;Sullivan\",\n			\"#122: Cherry Hill O&#8217;Manley\",\n			\"#123: Cheesequake O&#8217;Lennox\",\n			\"#124: Booper O&#8217;Montauk\",\n			\"#125: Zaxxon Galaxian\",\n			\"#126: Drinky Drunky Thom, the Drunk\",\n			\"#127: Terry Gross\",\n			\"#128: Spooky-Night Spooky Day\",\n			\"#129: Zipgun Gloucester Gluck\",\n			\"#130: Human Hair Frum\",\n			\"#131: Sherlock-Holmes-Hat Carl III\",\n			\"#132: Patrick Intergalactic\",\n			\"#133: Ambidextrous Stang\",\n			\"#134: Yum-Yum Sinclair Snowballeater\",\n			\"#135: Ponzi-Scheme Jeremiah Ponzi\",\n			\"#136: Toodles Strunk\",\n			\"#137: Monkeybars Matthew Manx\",\n			\"#138: Pineneedle-Jacket Jericho Fop\",\n			\"#139: Robert the Tot\",\n			\"#140: Robert the Child-Size\",\n			\"#141: Robert the Minuscule\",\n			\"#142: Robert the Wee\",\n			\"#143: Robert Fits-in-a-Case\",\n			\"#144: Robert Eats-for-Free\",\n			\"#145: Robert Is-He-an-Elf? (The seven Silk brothers, all named Robert, were also known for the small stature and predictable bitterness.)\",\n			\"#146: Dennis Big-Ear Fox\",\n			\"#147: Jethro the Pagan\",\n			\"#148: Asterix the Gaul\",\n			\"#149: Black Bolt, King of the Inhumans\",\n			\"#150: Strictly Local Henry Bobtail\",\n			\"#151: Manny the High-Ranking Mason\",\n			\"#152: Fry-Pan Jim Fry\",\n			\"#153: Slo-Mo Deuteronomy\",\n			\"#154: Half-Bearded Mark\",\n			\"#155: Knee-Brace Kenny\",\n			\"#156: Morris the Personal Trainer\",\n			\"#157: Thundertwine\",\n			\"#158: Cleats Onionpocket\",\n			\"#159: Deformed Abe\",\n			\"#160: Trainwhistle Abejundio\",\n			\"#161: David No-Ears\",\n			\"#162: Achilles Snail-Hair the Buddha\",\n			\"#163: Frog-Eatin&#8217; Lou\",\n			\"#164: Admiral&#8217;s Club Wilbur\",\n			\"#165: Max Meatboots, the First-Class Lounger\",\n			\"#166: Dora the Explorer\",\n			\"#167: Ms. Mary Manx, the Tailless Cat\",\n			\"#168: Free-Peanuts Doug\",\n			\"#169: Steve the Human Tunneler\",\n			\"#170: Redball Charlie Dickens\",\n			\"#171: Twink the Reading-Room Snoozer\",\n			\"#172: Microfiche Roy, the Side-Scroller\",\n			\"#173: McGurk, Who May Be Found by the Card Catalogue\",\n			\"#174: Booster D&#8217;Souza\",\n			\"#175: Commodore Sixty-Four\",\n			\"#176: Moped Enid, the Mopedist\",\n			\"#177: Lamant the Junkman\",\n			\"#178: Fast-Neck Nell\",\n			\"#179: Bill Never-Uses-a-Cookbook\",\n			\"#180: Bee-Beard\",\n			\"#181: Lil&#8217; Max Meatboots\",\n			\"#182: The Personal Secretary to Jed\",\n			\"#183: Dee Snider\",\n			\"#184: Sausage Patty\",\n			\"#185: Desert Locust\",\n			\"#186: Gummy Miles\",\n			\"#187: Gyppo Moot, the Enigma Machine\",\n			\"#188: Ol&#8217; Stiffpants\",\n			\"#189: Skywise the Sexual Elf\",\n			\"#190: Craine T. Eyebrow-Smeller\",\n			\"#191: Lonely Heiney Alan Meister\",\n			\"#192: Shakey Aitch the Boneyard Concierge\",\n			\"#193: Woody Damn\",\n			\"#194: Alatar\",\n			\"#195: Pallando\",\n			\"#196: Saltfish Bunyan\",\n			\"#197: Poor, Poor, Poor Charlie Short\",\n			\"#198: Venomous Byron\",\n			\"#199: Five-Chambered-Stomach Mort St. John\",\n			\"#200: Gravybelly Dunstan\",\n			\"#201: Extra-Skin Dave\",\n			\"#202: Beanbag-Chair Bill\",\n			\"#203: Grant Sharpnails, the Scratcher\",\n			\"#204: Tommy Lice-Comb\",\n			\"#205: &#8220;Medicated Shampoo&#8221; Jonah Jump\",\n			\"#206: General Woundwort, the Giant Rabbit\",\n			\"#207: Genius L. Cravat, the Gentleman\",\n			\"#208: Giant Bat Wings Roland\",\n			\"#209: Nick Nolte\",\n			\"#210: Salty Salty Friday\",\n			\"#211: Fatman and the Creature (note: there was no creature)\",\n			\"#212: Cecelia Graveside\",\n			\"#213: Hoosegow Earl French\",\n			\"#214: Stymie Stonewrist\",\n			\"#215: Roadrunner &#8220;Meep Meep&#8221; Fabong\",\n			\"#216: Bruised-Rib Johansson, the Beefer\",\n			\"#217: Joachim Bat-in-Hair\",\n			\"#218: Food-Eating Micah\",\n			\"#219: Rubbery Dmitry, the Mad Monk\",\n			\"#220: Honey Bunches of Donald\",\n			\"#221: Crispy Morton\",\n			\"#222: Feminine Forearms Rosengarten\",\n			\"#223: Two-Headed Mike Hoover\",\n			\"#224: Manny Stillwaggon, the Man with the Handlebar Eyebrows\",\n			\"#225: Bean-Hoarder Newt\",\n			\"#226: Texas Emil\",\n			\"#227: The Moor of Venice\",\n			\"#228: Averroes Nix\",\n			\"#229: Human Hair Blanket Morris Burnes\",\n			\"#230: Canadian Paul Tough\",\n			\"#231: Crooner Sy\",\n			\"#232: Manuel Pants-Too-High\",\n			\"#233: Sylvia Patience Hidden-Forks\",\n			\"#234: Sung, the Land Pirate\",\n			\"#235: Opie, the Boston Bum\",\n			\"#236: Hard-Flossing Hope Peak\",\n			\"#237: Stingo the Bandana Origami Prodigy\",\n			\"#238: Franklin Ape and His Inner Ear Infection\",\n			\"#239: Questionable-Judgement Theodore Stomachbrace\",\n			\"#240: Thermos H. Christ\",\n			\"#241: Sir Mix-a-Lot\",\n			\"#242: The Nine Doctor Whos\",\n			\"#243: Lord Winston Two-Monocles\",\n			\"#244: The Freewheelin&#8217; Barry Sin\",\n			\"#245: Diego the Spark-Spitter\",\n			\"#246: American Citizen Zane Pain\",\n			\"#247: Abraham, the Secret Collector of Decorative China\",\n			\"#248: Linty Sullivan, the Lint-Collector\",\n			\"#249: Socks Monster\",\n			\"#250: Ma Churchill\",\n			\"#251: Pappy Churchill\",\n			\"#252: The Young Churchill\",\n			\"#253: The Young Churchill&#8217;s Hated Bride\",\n			\"#254: Churchill-Lover Phineas Redfish\",\n			\"#255: Crispus T. Muzzlewitt\",\n			\"#256: Stain-Sucker Duncan\",\n			\"#257: Dick the Candy Dandy\",\n			\"#258: Albuterol Inhaler Preson McWeak\",\n			\"#259: Longtime Listener, First-Time Caller\",\n			\"#260: Mastiff Mama\",\n			\"#261: Tennessee Ernie Dietz\",\n			\"#262: Sharkey, the Secret Cop\",\n			\"#263: Gooseberry Johnson, Head Brain of the Hobosphere\",\n			\"#264: Weekend-Circular Deborah\",\n			\"#265: Marcus Chickenstock\",\n			\"#266: Stunted Newton\",\n			\"#267: Magnus Shortwave\",\n			\"#268: U.S. Fool\",\n			\"#269: Manatee the Railyard Toreador\",\n			\"#270: Utah Manfred Succor-Munt\",\n			\"#271: Laura Delite\",\n			\"#272: Edwin Winnipeg\",\n			\"#273: Eyepatch Resse Andiron\",\n			\"#274: Tom False-Lips Real-Teeth\",\n			\"#275: Fabulon Darkness\",\n			\"#276: Cricket-Eating Charles Digges\",\n			\"#277: Pally McAffable, Everybody&#8217;s Friend\",\n			\"#278: Sully Straightjacket\",\n			\"#279: Half-Dollar Funk Nelson\",\n			\"#280: Whitman Sampler\",\n			\"#281: Chili-Mix Wilma Bensen\",\n			\"#282: Sting, the Glowing Blade\",\n			\"#283: Professor Challenger\",\n			\"#284: Lil&#8217; Shorty Longhorn\",\n			\"#285: Rumpshaker Phil\",\n			\"#286: Swing State Myron\",\n			\"#287: Alistair Crowley, the Devil\",\n			\"#288: Gutthrower Sy Salt\",\n			\"#289: Sweetback Barney, the Dilettante\",\n			\"#290: The Car-Knocker Killer\",\n			\"#291: The Chamberlain\",\n			\"#292: The Emperor\",\n			\"#293: The Ritual-Master\",\n			\"#294: The Garthim-Master\",\n			\"#295: The Scientist\",\n			\"#296: The Gourmand\",\n			\"#297: The Slave-Master\",\n			\"#298: The Treasurer\",\n			\"#299: The Scroll Keeper\",\n			\"#300: The Ornamentalist\",\n			\"#301: Captain Slick-Talk\",\n			\"#302: Sackfist, the Tapdancing Trombo\",\n			\"#303: Souvenir-Selling Mlodinow\",\n			\"#304: Blind Buck and &#8220;Woozy,&#8221; the Invisible Seeing-Eye Dog\",\n			\"#305: Roundhouse Farter\",\n			\"#306: Red Ball Pnutz\",\n			\"#307: Fake Cockney Accent Alan Strippe\",\n			\"#308: Air and Whiskey Dale McGlue\",\n			\"#309: Johnny RC Airplane\",\n			\"#310: Narcotic Morgan Suds\",\n			\"#311: Sir Francis Drank\",\n			\"#312: Mahayana Mike\",\n			\"#313: Miniyana Geoffrey\",\n			\"#314: Three-Bean Otz\",\n			\"#315: Maury the Monsoon\",\n			\"#316: Czech Czarlie Czill\",\n			\"#317: Sssssssssssssssss, the Hisser\",\n			\"#318: Thanatos Koch\",\n			\"#319: Henry Eatsmelts\",\n			\"#320: Modem-Sniffer Gunderson\",\n			\"#321: Half-Albino Alejandro\",\n			\"#322: Gluttonous-Slim\",\n			\"#323: Ragweed-Allergic Matt\",\n			\"#324: Amorous Luminous Dirk\",\n			\"#325: Moray Eel Ken Elmer\",\n			\"#326: The Railbender\",\n			\"#327: Antonio the Ombudsman\",\n			\"#328: Karl Solenoid IV\",\n			\"#329: Czar King Rex the Glorious Leader\",\n			\"#330: Andy Bunkum\",\n			\"#331: Plastic-Moustache Mortimer Tall\",\n			\"#332: Samuel Gel Insole\",\n			\"#333: Lemuel Gel Insole\",\n			\"#334: Amanda Until\",\n			\"#335: Crispy Whiskery\",\n			\"#336: Robert Louis Stevenson, the Pirate\",\n			\"#337: Hobo Overload\",\n			\"#338: Leopard Print Steven Kane\",\n			\"#339: Astonishing Shaun Eyelash\",\n			\"#340: Billy Creak Knees\",\n			\"#341: Owlie\",\n			\"#342: Anwar, the Bionic\",\n			\"#343: Reasonably Priced Motel Resse Unger\",\n			\"#344: Fibery Dana\",\n			\"#345: Cranberry Sauce Oppenheimer\",\n			\"#346: Nancified Frederick\",\n			\"#347: The Loon\",\n			\"#348: Itinerant Jane\",\n			\"#349: Holy Hannah Hottentot-Smythe\",\n			\"#350: Fleabottle Boone\",\n			\"#351: Amazin&#8217; Jack Caroo\",\n			\"#352: Stupefying P, the Riddle-Maker\",\n			\"#353: Todd Flaky-Palms\",\n			\"#354: Waspwaist Fritz\",\n			\"#355: Judge Roughneck\",\n			\"#356: Slam Dance Dooze\",\n			\"#357: Mariah Duckface, the Beaked Woman\",\n			\"#358: Count Mesmerize\",\n			\"#359: Sonny-Boy Oedipus Acre\",\n			\"#360: Pick Mama Susan Xavier\",\n			\"#361: Chelsea Bacon\",\n			\"#362: Archie Axe\",\n			\"#363: Sally Hoot-Hoot\",\n			\"#364: Mr. Pendleton\",\n			\"#365: Saves-Receipts Dave\",\n			\"#366: Sir Walter British\",\n			\"#367: Elmer, the Crankscout\",\n			\"#368: Golden Neck\",\n			\"#369: Marinated Alex Pons\",\n			\"#370: El Boot\",\n			\"#371: Shapeshifting Demon\",\n			\"#372: Jeremiah Tip Top\",\n			\"#373: Amanda CeeCee Strobelight\",\n			\"#374: Irving Alva Edison, Inventor of the Hobophone\",\n			\"#375: Leather Apron\",\n			\"#376: Lead Apron\",\n			\"#377: Foil Apron\",\n			\"#378: Burnt Goathead\",\n			\"#379: Saint Sorryass\",\n			\"#380: Overly Familiar Fung\",\n			\"#381: Chalmers, the Bridge Champ\",\n			\"#382: Elephantine McMoot\",\n			\"#383: Neekerbeeker Perry Toenz\",\n			\"#384: Teattime BB Stiles\",\n			\"#385: Coalie T\",\n			\"#386: Hubbel &#8220;I Predicted Lindy Hop&#8221; Deerblind\",\n			\"#387: Hubie Hewitt, the Broadway Legend\",\n			\"#388: Huge Crybaby McWeepy\",\n			\"#389: Poo-Knickers Elias\",\n			\"#390: Elffriend Weingarten\",\n			\"#391: Forktongue Nigel Fork\",\n			\"#392: Woodeye Apfel\",\n			\"#393: Hairlip Mikhail\",\n			\"#394: Solid First Draft Patton Taylor\",\n			\"#395: Prettynickels, the Lamb\",\n			\"#396: Not-Only But-Also Pete\",\n			\"#397: Penthief Hickock\",\n			\"#398: La Grande Mel\",\n			\"#399: Applebee O&#8217;Bennigan McFridays\",\n			\"#400: Lardy Jerry Lardo\",\n			\"#401: Low-Carb Aleks Stovepipe\",\n			\"#402: Hugo Stares\",\n			\"#403: Eldred Splinters\",\n			\"#404: Oliver, the Train-Oyster\",\n			\"#405: Pring, Ultralord of the Hobo Jungle\",\n			\"#406: Utz, the Crab Chip\",\n			\"#407: Salt-and-Pepper Chest\",\n			\"#408: Beverly Hills Buntz\",\n			\"#409: Mississippi Barry Phlegm\",\n			\"#410: Matter-Eater Brad\",\n			\"#411: 49-State Apthorp, the Alaska-Phobe\",\n			\"#412: New Hampshire Todd\",\n			\"#413: &#8220;Taxachusetts&#8221; Glenn\",\n			\"#414: Hydrocephalic Jones\",\n			\"#415: Vermont &#8220;Greenmountain Boy&#8221; Phil Marijuana\",\n			\"#416: Alaska Mick the Crabber\",\n			\"#417: Arizona Ludwig\",\n			\"#418: California Ainsley Shortpants\",\n			\"#419: Collegeboy Brainiac, the Hobo Einstein\",\n			\"#420: Dr. Zizmor\",\n			\"#421: Silas Swollentoe\",\n			\"#422: Slimneck Holden Fop\",\n			\"#423: Aspiring Jaster\",\n			\"#424: Illinois Obama\",\n			\"#425: Sammy Austere\",\n			\"#426: New Mexico Anselm Turquoise-Eater\",\n			\"#427: Caboose-Fouling Ferris Ntz\",\n			\"#428: Prayerful Stan, the Bent-Knee Yahoo\",\n			\"#429: Four-Fisted Jock Socko\",\n			\"#430: Buttery-Cheeks Anton\",\n			\"#431: Shadow (&#8220;Blinky&#8221;) Preston\",\n			\"#432: Godigisel the Vandal\",\n			\"#433: Gunderic Godigiselson\",\n			\"#434: Panzo the Spiral-Cut Ham\",\n			\"#435: Smoke-Collecting Reg\",\n			\"#436: Hot Gnome Jimmy Jackson\",\n			\"#437: Pontius Cornsilk-Heart\",\n			\"#438: Sanford Who Lacks Fingerprints\",\n			\"#439: Treesap-Covered N. Magruder\",\n			\"#440: Thor Hammerskold, the Mexican\",\n			\"#441: Bingo-Balls Nick Chintz\",\n			\"#442: Bleedingtoe the Barefoot &#8217;Bo\",\n			\"#443: Hondo &#8220;Whatever That Lizard Is That Walks on Water&#8221;\",\n			\"#444: Salami Sunshine\",\n			\"#445: Fourteen-Bindelstick Frank\",\n			\"#446: Oregon Brucie Shunt\",\n			\"#447: Pirandello, the Many-Bearded\",\n			\"#448: Quinn and His Quaker Oats Box Drum\",\n			\"#449: Fatneck Runt\",\n			\"#450: my-e-hobo.com\",\n			\"#451: Somersaulting Mike Spitz\",\n			\"#452: Bo &#8217;Bo\",\n			\"#453: Abelard &#8220;Sunken Treasure&#8221; Lowtrousers\",\n			\"#454: Colin, That Cheerful Fuck\",\n			\"#455: Battling Joe Frickinfrack\",\n			\"#456: Monsieur Dookie, the Francophonic\",\n			\"#457: Happy Horace Noosemaker\",\n			\"#458: Hieronymous Crosseyes\",\n			\"#459: Crumbjacket Timmy\",\n			\"#460: Overload-the-Dishwasher Mac\",\n			\"#461: Phythmic Clyde Hopp\",\n			\"#462: Microbrew Stymie\",\n			\"#463: El Caballo, the Spanish Steed\",\n			\"#464: Lee Burned-Beyond-Recognition\",\n			\"#465: Hollering Martin Mandible\",\n			\"#466: Damien Pitchfork, the Freightyard Satan\",\n			\"#467: Handformed Hamburger Clarence West\",\n			\"#468: Dr. Nobel Dynamite\",\n			\"#469: Pickled-Noggin Nettles\",\n			\"#470: Mischievous Craig\",\n			\"#471: Baldy Lutz, the Amityville Horror\",\n			\"#472: Ashen Merle Buzzard\",\n			\"#473: Frypan Nonstick McGee\",\n			\"#474: Singleminded Hubbard\",\n			\"#475: Maryland Sol Saynomore\",\n			\"#476: Baked Salmon Salad Finn\",\n			\"#477: Unshakably Morose Flo\",\n			\"#478: Fr. Christian Irish, the Deep-Fat Friar\",\n			\"#479: Smokestack-Hugger Jools Nygaard\",\n			\"#480: Fossilwise Opie Fingernail\",\n			\"#481: Tab-Collar Dix\",\n			\"#482: George Slay, the Duck Throttler\",\n			\"#483: Eldon Waxhat, the Waterproof Man\",\n			\"#484: Timely Clayton, the Human Wristwatch (so named not because of his punctuality, but because one arm was significantly shorter than the other)\",\n			\"#485: Both Dakotas Dave\",\n			\"#486: Duke Jeremiah Choo-Choo\",\n			\"#487: Transistorized Maximillian, the Hobo Cyborg\",\n			\"#488: Gravelbed Gavin Astor\",\n			\"#489: Pantless, Sockless, Shoeless Buster Bareass\",\n			\"#490: Alternate-Dimension Bela Boost\",\n			\"#491: Atlas Flatshoulders\",\n			\"#492: Scurvied Leo Falsebreath\",\n			\"#493: Toby Anchovy, the Canned Man\",\n			\"#494: Mad Max\",\n			\"#495: The Goose\",\n			\"#496: Not the Goose\",\n			\"#497: Mister Torso, the Legless Wonder\",\n			\"#498: Jedediah Dryasdust\",\n			\"#499: Loving Vincent Hugsalot\",\n			\"#500: The Rambling, Rambling Boris Wander\",\n			\"#501: Business-Class Klaus Riel\",\n			\"#502: Emergency Exit Aisle Gustav Nook\",\n			\"#503: Unnervingly Candid Nicky Thain\",\n			\"#504: Snoops Lightstep Trenchcoat, the Hobo PI\",\n			\"#505: William Carlos Williams\",\n			\"#506: Beef Grease Porter Dripchin\",\n			\"#507: Exoskeleton Chester Fields\",\n			\"#508: Roth IRA Romeo Leeds, the Well Prepared\",\n			\"#509: Bum-Smiter Phillip\",\n			\"#510: Bum-Hating Virgil Hate-Bum\",\n			\"#511: Thor the Bum-Hammer\",\n			\"#512: Bum-Tolerant Brendan Sleek\",\n			\"#513: Most Agree: It&#8217;s Kilpatrick\",\n			\"#514: The Beloved Dale Thankyounote\",\n			\"#515: Unpronounceable\",\n			\"#516: Thad Malfeasance\",\n			\"#517: Chiseltooth Muck Manly\",\n			\"#518: Amsterdam Jocko\",\n			\"#519: Sinister Leonard Longhair\",\n			\"#520: Beery Clive the Eunuch\",\n			\"#521: Chaim the Squirrelkeeper\",\n			\"#522: Nightblind and Colorblind, the Blind Twins\",\n			\"#523: Milosz the Anarchist Puppeteer\",\n			\"#524: Jimmy &#8220;New Man&#8221; Neandertal\",\n			\"#525: Lonnie Pina Colada\",\n			\"#526: Washington State Amy Swipe\",\n			\"#527: Gopher-State Sam, the Minnesota Man\",\n			\"#528: Candle-Eyed Sally\",\n			\"#529: Packrat Red and his Cart o&#8217; Sad Crap\",\n			\"#530: Trixie of the East\",\n			\"#531: Trixie of the West\",\n			\"#532: Fine-Nipple Tom Bazoo\",\n			\"#533: The Friends of Reginald McHate Society\",\n			\"#534: Oregon Perry Hashpipe\",\n			\"#535: Bold &#8217;n&#8217; Zesty Brad\",\n			\"#536: Mermaid Betty Scales\",\n			\"#537: Spotted Dick\",\n			\"#538: Shanty Queen Elizabeth Regina\",\n			\"#539: Nichols Crackknuckle\",\n			\"#540: Stew Socksarewarm\",\n			\"#541: Huge-Calves Dwight\",\n			\"#542: A-Number-1\",\n			\"#543: N-Number-13\",\n			\"#544: Arthur Moonlight\",\n			\"#545: Andrea Clarke, the Human Shark\",\n			\"#546: Monkey&#8217;s-Paw Patterson\",\n			\"#547: Myron Biscuitspear, the Dumpster Archeologist\",\n			\"#548: Ollie Ebonsquirrel\",\n			\"#549: The Classic Brett Martin\",\n			\"#550: Douglas, the Future of Hoboing\",\n			\"#551: Ironbelly Norton\",\n			\"#552: Dilly Shinguards\",\n			\"#553: Rufus Caboose\",\n			\"#554: Rear Admiral JF Grease Pencil\",\n			\"#555: King Cotton\",\n			\"#556: Prince Hal Oystershuck, the Royal Shucker\",\n			\"#557: Unconditional Gavin\",\n			\"#558: Squirrelcloak\",\n			\"#559: Idaho Woody Harrelson\",\n			\"#560: Jane the Boxcar Beekeeper\",\n			\"#561: Aaron Three-Shirts\",\n			\"#562: Paste-Smeller Luke\",\n			\"#563: Lowly Highley\",\n			\"#564: Elihu Skinpockets\",\n			\"#565: Marian May Wyomingsong\",\n			\"#566: Stitches the Railyard Sutureman\",\n			\"#567: Klonopin Clyde\",\n			\"#568: Benny Twenty-Squirrels\",\n			\"#569: Chickeny-Flavored Remy Bunk\",\n			\"#570: Juicepockets Thomas Moone\",\n			\"#571: Eustace Feetbeer\",\n			\"#572: Amnesiac Jared Stringy\",\n			\"#573: Shagrat, Orc of the Ozarks\",\n			\"#574: Billy Butterfly Net\",\n			\"#575: Ammonia Cocktail Jones\",\n			\"#576: Norma Shinynickels\",\n			\"#577: Jonathan Crouton\",\n			\"#578: Antigone Spit\",\n			\"#579: El Top-Hat Swindlefingers\",\n			\"#580: PomPom the Texas Dancing Dog\",\n			\"#581: Gin-Bucket Greg\",\n			\"#582: Yuri Trimble, the Alien Pod-Person\",\n			\"#583: South Carolina Sarah Lardblood\",\n			\"#584: Bloody-Stool LaSalle\",\n			\"#585: Pith-Helmet Andy\",\n			\"#586: Self-Taught-Guitarist Edmund\",\n			\"#587: Don Tomasino di Shit-the-Bed\",\n			\"#588: Markansas\",\n			\"#589: Neckfat JK Trestle\",\n			\"#590: Pansy Overpass\",\n			\"#591: Ralph Raclette Cornichon, Hobo of the Mountains\",\n			\"#592: Montana Nbdego Tch!k\",\n			\"#593: Unbearably Oenophilic Ned\",\n			\"#594: Jonas Tugboy, Professional Masturbator\",\n			\"#595: Cinderfella Dana Dane\",\n			\"#596: Kerosene-Soaked Tom\",\n			\"#597: Black-Bottle Priam\",\n			\"#598: Pinprick Butell\",\n			\"#599: Stool-Sample Frank\",\n			\"#600: Iowa Noam Chomksy\",\n			\"#601: Etienne, Roi of the Rapier\",\n			\"#602: Amesy Squirrelstomper, the Chipmunk Preferrer\",\n			\"#603: Ned Gravelshirt\",\n			\"#604: NPR Willard Hotz, the Soothing-Voiced\",\n			\"#605: Amen to Polly Fud\",\n			\"#606: Constantly Sobbing Forrester\",\n			\"#607: Maine-iac Leonid\",\n			\"#608: Magnetized James\",\n			\"#609: Hobo Jake Jerrold, Representing the Whole Mid-Atlantic Region\",\n			\"#610: Jiminy Sinner\",\n			\"#611: Pamela Chickeneggs (i.e., Hobo Caviar)\",\n			\"#612: Chuck McKindred: Not So Holy, But Very Moley\",\n			\"#613: Q the Quantum Man\",\n			\"#614: Salad-Fork Ron\",\n			\"#615: Warbling Timmy Tin Voice and His Voicebox\",\n			\"#616: Ambassador Roasting Pan\",\n			\"#617: Warren Smazell, Founder of Hobotics&#174;\",\n			\"#618: Ventriloquism Jimmy and &#8220;Madam&#8221; the Talking Bean Can\",\n			\"#619: Nosepicker Rick Pick\",\n			\"#620: The Black Squirrel Fairy\",\n			\"#621: Alabama Edsel Brainquake\",\n			\"#622: Kid Silverhair, the Man of Indeterminate Age\",\n			\"#623: Catscratch Tremont Nude\",\n			\"#624: Bill Jaundice\",\n			\"#625: Sugarhouse Morris the Sapper\",\n			\"#626: Nutrition-Shake Emery\",\n			\"#627: Nicknameless Norris Shine\",\n			\"#628: Stinging Polly Papercuts\",\n			\"#629: Deke Hidden Hornets&#8217; Nest\",\n			\"#630: The Wisconsin Scourge\",\n			\"#631: Brendan Headbristles\",\n			\"#632: His Excellency Nooney Sockjelly\",\n			\"#633: Whistling Anus Mecham, Le Petomaine\",\n			\"#634: Talmidge, the Bactine Bearer\",\n			\"#635: Tailstump Gunther, the Vestigial Man\",\n			\"#636: The Hon. Charlie Weed-Farmer\",\n			\"#637: Philatelist Joey Licks\",\n			\"#638: Old Pliny Dance-for-Ham\",\n			\"#639: Rheumy Sven\",\n			\"#640: Wormy Glenn and Nootka the Flatworm\",\n			\"#641: Hidalgo, the Devil Stick Artiste\",\n			\"#642: The Fucky From Kentucky\",\n			\"#643: Prince Bert in Exile, the Man in the Foil Mask\",\n			\"#644: Siderodromophobic Billy\",\n			\"#645: Antlered Calvin\",\n			\"#646: Cambridge Massachusetts Claude\",\n			\"#647: Cyrus the Persian Sturgeon\",\n			\"#648: Kneepants Erasmus, the Humanist\",\n			\"#649: Little Gavin Spittle\",\n			\"#650: Tar-tongue Godfrey Strange\",\n			\"#651: Honeypalms Gordon Lips\",\n			\"#652: Luke &#8220;the Lifestyle&#8221; Dammmers\",\n			\"#653: Simon Squirrelskin\",\n			\"#654: Scabpicker Sandy Rump\",\n			\"#655: Chicken Butt, Five Cents a Cut\",\n			\"#656: Wise Solomon Babysplitter\",\n			\"#657: Telekinetic Dave B.\",\n			\"#658: Telekinetic Dave F.\",\n			\"#659: Whiskeyblood Willie Sot\",\n			\"#660: Unger and his Duststorm Bride\",\n			\"#661: Zachary Goatflirter\",\n			\"#662: &#8220;La Grippe&#8221;\",\n			\"#663: Uranus John, the Star-Traveler\",\n			\"#664: Accusin&#8217; Tim Dunn\",\n			\"#665: Tennessee Linthelmet\",\n			\"#666: The Unformed Twin of Tennessee Linthelmet\",\n			\"#667: Turkeyballs Paco\",\n			\"#668: Andre the Indianapolist\",\n			\"#669: Wally Dregs, the Newfoundland Screech\",\n			\"#670: Flaky Mike Psoriasis\",\n			\"#671: Hell&#8217;s Own Breath Hinkley\",\n			\"#672: Gerald Chapcheeks\",\n			\"#673: Acid-Saliva Curly Stokes\",\n			\"#674: Oklahoma Stilgar\",\n			\"#675: Rocky Shitstain Mankowicz\",\n			\"#676: Rocky Shitstain Mankowicz Part II, the Quickening\",\n			\"#677: Professor &#937;\",\n			\"#678: Sanitized-for-Your-Protection Eddie Summers\",\n			\"#679: Jan, the Jager-Meister\",\n			\"#680: Big-Tipper Silas Fake-Nickel\",\n			\"#681: Anaerobic Eben Stiles\",\n			\"#682: Replicant Wemberly Plastiskin and his Clockwork Squirrel &#8220;Toothy&#8221;\",\n			\"#683: Harry Coughblood\",\n			\"#684: Aesop Bedroll, the Fluffy Pillow Man\",\n			\"#685: Widow-Kisser Roger\",\n			\"#686: Experimental Hobo Infiltration Droid &#8220;41-K&#8221;\",\n			\"#687: Baron Bayonet, the Bull-Sticker\",\n			\"#688: Mikey Gluesniff\",\n			\"#689: Bell&#8217;s-Palsy Brennan\",\n			\"#690: Chiptooth Berman, the Bottle Biter\",\n			\"#691: Undertaker Robert, the Lint-Coffin Weaver\",\n			\"#692: Betty, the Exorcist\",\n			\"#693: Tittytwister Blake Horrid\",\n			\"#694: Mallory Many-Bruises\",\n			\"#695: Mad or Sad Judd (no one can tell)\",\n			\"#696: Troglodytic Amory Funt\",\n			\"#697: Smokehouse &#8220;Frankie&#8221; Jowl-Poker\",\n			\"#698: Utility-Belt Deana\",\n			\"#699: The Unshakable Will of Wade Terps\",\n			\"#700: Trainwhistle Ernie Roosevelt, the President&#8217;s Long-Lost Brother\",\n	);\n\n	// Pick the key for a random entry\n	$randhobo = array_rand($hobos, 1);\n\n	// Assigns value from randomly selected key to $showhobo\n	$showhobo = $hobos[$randhobo];\n\n	// Returns value with optional wrap tag and class\n	return ($wraptag) ? doTag ($showhobo, $wraptag, $class) : $showhobo;\n\n}\n', 'b63f1392d1cfc71c65dac4644633223a', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cwh_link_to_next', 0, 'Christopher Horrell', 'http://horrell.ca/', '0.1', 'A replacement tag for <txp:link_to_next /> that includes the ability to set the output format as a <link> tag.', '	<p>This is a replacement tag for <code>&#60;txp:link_to_next /&#62;</code> that includes a new <em>format</em> attribute which provides the ability to set the link format as a <code>&#60;link&#62;</code> tag suitable for placing in the <code>&#60;head&#62;</code> of any individual article page on your site. You can do this by setting the <em>format</em> attribute like so:</p>\n\n	<p><code>&#60;txp:cwh_link_to_home title=&#34;Home&#34; format=&#34;link&#34; /&#62;</code></p>\n\n	<p>which will produce something similar to:</p>\n\n	<p><code>&#60;link rel=&#34;next&#34; href=&#34;http://example.com/article/2/next-post&#34; title=&#34;Next Post&#34; /&#62;</code></p>\n\n	<p>Note that when used as a single tag without the <em>format</em> attribute specified:</p>\n\n	<p><code>&#60;txp:cwh_link_to_next /&#62;</code></p>\n\n	<p>it will produce a plain text link much in the same way as <code>&#60;txp:link_to_next /&#62;</code>:</p>\n\n	<p><code>http://example.com/article/2/next-post</code></p>\n\n	<p>Finally, it can also be used as a container tag in the same manner as <code>&#60;txp:link_to_next /&#62;</code> if no <em>format</em> value is specified:</p>\n\n	<p><code>&#60;txp:cwh_link_to_next&#62; ...Text or Tag... &#60;/txp:cwh_link_to_next&#62;</code></p>\n\n	<p>If you do specify a <em>link</em> value for the <em>format</em> attribute while used as a container tag, it will still produce a <code>&#60;link&#62;</code> tag.</p>\n\n	<p>This tag also shares all of the same attributes as <code>&#60;txp:link_to_next /&#62;</code>. For a complete list of applicable attributes, see <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_to_next\">this Textbook entry</a>.</p>\n\n	<p>For a further explanation of the benefits of using <code>&#60;link&#62;</code> tags for your site&#8217;s navigation, please see <a href=\"http://diveintoaccessibility.org/day_9_providing_additional_navigation_aids.html\">Day 9: Providing additional navigation aids</a> from Mark Pilgrim&#8217;s <a href=\"http://diveintoaccessibility.org/\">\nDive Into Accessibility</a>.</p>', '\n// Plugin code goes here.  No need to escape quotes.\n\n\nfunction cwh_link_to_next($atts, $thing)\n{\n	global $id, $next_id, $next_title;\n\n	extract(lAtts(array(\n		\'format\'     => \'\', // new format attribute\n		\'showalways\' => 0,\n	), $atts));\n\n	if (intval($id) == 0)\n	{\n		global $thisarticle, $s;\n\n		extract(getNextPrev(\n			@$thisarticle[\'thisid\'],\n			@strftime(\'%Y-%m-%d %H:%M:%S\', $thisarticle[\'posted\']),\n			@$s\n		));\n	}\n\n	if ($next_id)\n	{\n		$url = permlinkurl_id($next_id);\n\n			if ($format == \'link\')\n            {\n        		return \'<link rel=\"next\" href=\"\'.$url.\'\"\'.\n            		($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\' />\';\n        	}\n\n\n            elseif ($thing)\n            {\n                $thing = parse($thing);\n\n                return \'<a rel=\"next\" href=\"\'.$url.\'\"\'.\n    			    ($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\n    				\'>\'.$thing.\'</a>\';\n            }\n\n            return $url;\n	}\n     return ($showalways) ? parse($thing) : \'\';\n}\n', '\n// Plugin code goes here.  No need to escape quotes.\n\n\nfunction cwh_link_to_next($atts, $thing)\n{\n	global $id, $next_id, $next_title;\n\n	extract(lAtts(array(\n		\'format\'     => \'\', // new format attribute\n		\'showalways\' => 0,\n	), $atts));\n\n	if (intval($id) == 0)\n	{\n		global $thisarticle, $s;\n\n		extract(getNextPrev(\n			@$thisarticle[\'thisid\'],\n			@strftime(\'%Y-%m-%d %H:%M:%S\', $thisarticle[\'posted\']),\n			@$s\n		));\n	}\n\n	if ($next_id)\n	{\n		$url = permlinkurl_id($next_id);\n\n			if ($format == \'link\')\n            {\n        		return \'<link rel=\"next\" href=\"\'.$url.\'\"\'.\n            		($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\' />\';\n        	}\n\n\n            elseif ($thing)\n            {\n                $thing = parse($thing);\n\n                return \'<a rel=\"next\" href=\"\'.$url.\'\"\'.\n    			    ($next_title != $thing ? \' title=\"\'.$next_title.\'\"\' : \'\').\n    				\'>\'.$thing.\'</a>\';\n            }\n\n            return $url;\n	}\n     return ($showalways) ? parse($thing) : \'\';\n}\n', '2396d4f15b92442f11d143a7aa364373', '', '', 0, 5, 0),
('cwh_link_to_prev', 0, 'Christopher Horrell', 'http://horrell.ca/', '0.1', 'A replacement tag for <txp:link_to_prev /> that includes the ability to set the output format as a <link> tag.', '	<p>This is a replacement tag for <code>&#60;txp:link_to_prev /&#62;</code> that includes a new <em>format</em> attribute which provides the ability to set the link format as a <code>&#60;link&#62;</code> tag suitable for placing in the <code>&#60;head&#62;</code> of any individual article page on your site. You can do this by setting the <em>format</em> attribute like so:</p>\n\n	<p><code>&#60;txp:cwh_link_to_home title=&#34;Home&#34; format=&#34;link&#34; /&#62;</code></p>\n\n	<p>which will produce something similar to:</p>\n\n	<p><code>&#60;link rel=&#34;prev&#34; href=&#34;http://example.com/article/1/previous-post&#34; title=&#34;Previous Post&#34; /&#62;</code></p>\n\n	<p>Note that when used as a single tag without the <em>format</em> attribute specified:</p>\n\n	<p><code>&#60;txp:cwh_link_to_prev /&#62;</code></p>\n\n	<p>it will produce a plain text link much in the same way as <code>&#60;txp:link_to_prev /&#62;</code>:</p>\n\n	<p><code>http://example.com/article/1/previous-post</code></p>\n\n	<p>Finally, it can also be used as a container tag in the same manner as <code>&#60;txp:link_to_prev /&#62;</code> if no <em>format</em> value is specified:</p>\n\n	<p><code>&#60;txp:cwh_link_to_prev&#62; ...Text or Tag... &#60;/txp:cwh_link_to_prev&#62;</code></p>\n\n	<p>If you do specify a <em>link</em> value for the <em>format</em> attribute while used as a container tag, it will still produce a <code>&#60;link&#62;</code> tag.</p>\n\n	<p>This tag also shares all of the same attributes as <code>&#60;txp:link_to_prev /&#62;</code>. For a complete list of applicable attributes, see <a href=\"http://textbook.textpattern.net/wiki/index.php?title=Txp:link_to_prev\">this Textbook entry</a>.</p>\n\n	<p>For a further explanation of the benefits of using <code>&#60;link&#62;</code> tags for your site&#8217;s navigation, please see <a href=\"http://diveintoaccessibility.org/day_9_providing_additional_navigation_aids.html\">Day 9: Providing additional navigation aids</a> from Mark Pilgrim&#8217;s <a href=\"http://diveintoaccessibility.org/\">\nDive Into Accessibility</a>.</p>', '\nfunction cwh_link_to_prev($atts, $thing)\n{\n	global $id, $prev_id, $prev_title;\n\n	extract(lAtts(array(\n		\'format\'     => \'\', // new format attribute\n		\'showalways\' => 0,\n	), $atts));\n\n	if (intval($id) == 0)\n	{\n		global $thisarticle, $s;\n\n		extract(getNextPrev(\n			$thisarticle[\'thisid\'],\n			@strftime(\'%Y-%m-%d %H:%M:%S\', $thisarticle[\'posted\']),\n			@$s\n		));\n	}\n\n	if ($prev_id)\n	{\n		$url = permlinkurl_id($prev_id);\n\n            if ($format == \'link\')\n			{\n			    return \'<link rel=\"prev\" href=\"\'.$url.\'\"\'.\n    				($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\' />\';\n			}\n\n\n            elseif ($thing)\n            {\n                $thing = parse($thing);\n\n			    return \'<a rel=\"prev\" href=\"\'.$url.\'\"\'.\n				    ($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\n				    \'>\'.$thing.\'</a>\';\n			}\n\n            return $url;\n	}\n\n	return ($showalways) ? parse($thing) : \'\';\n}\n\n', '\nfunction cwh_link_to_prev($atts, $thing)\n{\n	global $id, $prev_id, $prev_title;\n\n	extract(lAtts(array(\n		\'format\'     => \'\', // new format attribute\n		\'showalways\' => 0,\n	), $atts));\n\n	if (intval($id) == 0)\n	{\n		global $thisarticle, $s;\n\n		extract(getNextPrev(\n			$thisarticle[\'thisid\'],\n			@strftime(\'%Y-%m-%d %H:%M:%S\', $thisarticle[\'posted\']),\n			@$s\n		));\n	}\n\n	if ($prev_id)\n	{\n		$url = permlinkurl_id($prev_id);\n\n            if ($format == \'link\')\n			{\n			    return \'<link rel=\"prev\" href=\"\'.$url.\'\"\'.\n    				($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\' />\';\n			}\n\n\n            elseif ($thing)\n            {\n                $thing = parse($thing);\n\n			    return \'<a rel=\"prev\" href=\"\'.$url.\'\"\'.\n				    ($prev_title != $thing ? \' title=\"\'.$prev_title.\'\"\' : \'\').\n				    \'>\'.$thing.\'</a>\';\n			}\n\n            return $url;\n	}\n\n	return ($showalways) ? parse($thing) : \'\';\n}\n\n', '52e580a7512cc93f33341b52d62fc2f7', '', '', 0, 5, 0),
('cbs_live_search', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.8', 'AJAX-powered search with visual effects using jQuery or MooTools', '<h3>Description</h3>\n\n<p>This plugin displays a search box similarly to <code>&lt;txp:search_input /&gt;</code>. If Javascript is enabled, a results list will fade in and resize in real time below the search box with a nice animation effect as the user types his query.</p>\n\n<p>Optionaly, a clickable image can be displayed at the right of the search box to clear its content. When the search box is empty or contains less than 3 characters, the results list shrinks and fades out.</p>\n\n<p>Also, an image (such as a spinner) can be displayed at the right of the search box when a search is in progress, and disappears when the results are shown.</p>\n\n<p>When the user hits the Enter key he gets redirected to a traditional search results page, so the plugin also degrades nicely in non-Javascript browsers.</p>\n\n<h3>Requirements</h3>\n\n<p>Since version 0.8, cbs_live_search can use either the <a href=\"http://jquery.com/\" rel=\"nofollow\">jQuery</a> or <a href=\"http://mootools.net/\" rel=\"nofollow\">MooTools</a> javascript framework.<br />\n<strong>Just pick your favorite one:</strong></p>\n\n<h4>jQuery</h4>\n\n<p>jQuery <strong>1.2</strong> or more recent is required.</p>\n\n<p>You can use the <code>jquery.js</code> javascript file which is bundled with Textpattern and you need to upload the provided <code>livesearch.jquery.js</code> file to your web server (for example in a folder called “/js/”) and include both of these files in the header of the web pages where you want to use the plugin:</p>\n\n<pre><code>&lt;script type=&quot;text/javascript&quot; src=&quot;/textpattern/jquery.js&quot;&gt;&lt;/script&gt;\n&lt;script type=&quot;text/javascript&quot; src=&quot;/js/livesearch.jquery.js&quot;&gt;&lt;/script&gt;</code></pre>\n\n<em>Remarks:</em>\n<ol>\n	<li>If you are already using jQuery, don&#8217;t include it twice!</li>\n	<li>If you also use other javascript frameworks (Prototype, Dojo, <span class=\"caps\">YUI</span>, etc.), it is recommended that you include jQuery <em>first</em>. cbs_live_search is also compatible with the <a href=\"http://docs.jquery.com/Core/jQuery.noConflict\" rel=\"nofollow\">jQuery.noConflict()</a> mode.</li>\n</ol>\n\n<h4>MooTools</h4>\n\n<p>MooTools <strong>1.2</strong> or more recent is required. Because MooTools is modular, you can make your download smaller by only downloading the modules that your scripts are actually using, thanks to the <a href=\"http://mootools.net/core\" rel=\"nofollow\">Core Builder</a>. Here are the MooTools Core modules required by cbs_live_search:</p>\n\n<ul>\n	<li><strong>Native:</strong> all</li>\n	<li><strong>Class:</strong> all</li>\n	<li><strong>Element:</strong> Element.Event, Element.Style</li>\n	<li><strong>Fx:</strong> Fx.Morph</li>\n	<li><strong>Request:</strong> Request</li>\n</ul>\n\n<p>The cbs_live_search plugin comes bundled with a file named <code>mootools.js</code> which contains these minimal dependencies only. This ensures the smallest download size for your visitors if you don&#8217;t use MooTools or jQuery at all for other features on your public website.</p>\n\n<p>You need to upload the javascript files <code>mootools.js</code> and the provided <code>livesearch.mootools.js</code> to your web server (for example in a folder called “/js/”) and include them in the header of the web pages where you want to use the plugin:</p>\n\n<pre><code>&lt;script type=&quot;text/javascript&quot; src=&quot;/js/mootools.js&quot;&gt;&lt;/script&gt;\n&lt;script type=&quot;text/javascript&quot; src=&quot;/js/livesearch.mootools.js&quot;&gt;&lt;/script&gt;</code></pre>\n\n<em>Remarks:</em>\n<ol>\n	<li>If you are already using MooTools, don&#8217;t include it twice!</li>\n	<li>If you use the <a href=\"http://www.prototypejs.org/\" rel=\"nofollow\">Prototype</a> framework for other scripts, include it <em>before</em> MooTools.</li>\n</ol>\n\n<h3>Syntax</h3>\n\n<pre><code>&lt;txp:cbs_live_search /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<h4>Required</h4>\n\n<p>none.</p>\n\n<h4>Optional</h4>\n\n<ul>\n	<li><strong>section</strong>: Name of the section to redirect the user to when he hits the Enter key, in order to display the standard search results page. Default is the <code>default</code> section (home page).</li>\n	<li><strong>html_id</strong>: <span class=\"caps\">HTML</span> <code>id</code> attribute that will be applied to the search form. Default is <code>live_search</code>. You need to set this attribute to different values if you use more than one live search box inside the same web page.</li>\n	<li><strong>label</strong>: Displays a label above the search box with the specified text. Default is no label.</li>\n	<li><strong>size</strong>: Size of the search box (number of characters). Default is <code>15</code>.</li>\n	<li><strong>default_value</strong>: Invitation text that will de displayed in the box when Javascript is enabled. This text disappears when the box gets the focus or when the user has typed something into it. Default is empty.</li>\n	<li><strong>wraptag</strong>: <span class=\"caps\">HTML</span> tag to wrap the optional label, the search field and the reset and wait images with, inside the form. Default is <code>p</code> (a paragraph).</li>\n	<li><strong>safari</strong>: When set to <code>true</code>, the classic input box will be replaced by a Mac OS X search widget in Safari if Javascript is enabled. The reset image (see the <span class=\"caps\">CSS</span> Styling section) will then <strong>never</strong> be displayed in these browsers. Other browsers will see the classic input box. Default is <code>false</code>.</li>\n	<li><strong>results_break</strong>: <span class=\"caps\">HTML</span> tag to be used for line breaks in the content inside the live results list, without brackets. Default is <code>br</code>.</li>\n	<li><strong>results_wraptag</strong>: <span class=\"caps\">HTML</span> tag to be used as the wraptag for the content inside the live results list, without brackets. Default is <code>p</code>.</li>\n	<li><strong>results_max</strong>: Maximum number of results in the live results list. Default is <code>5</code>.</li>\n	<li><strong>results_excerpts</strong>: When set to <code>true</code>, displays excerpts of the articles containing the search terms. Search terms will be wrapped in <code>&lt;strong&gt;</code> tags. Default is <code>false</code>.</li>\n	<li><strong>results_dates</strong>: When set to <code>true</code>, displays the article date at the bottom of each result entry. Default is <code>false</code>.</li>\n	<li><strong>no_results</strong>: Text that will be displayed in the live results list when no articles are found. Default is “0 articles found” localized in the language of your Textpattern installation.</li>\n</ul>\n\n<h3><span class=\"caps\">CSS</span> Styling</h3>\n\n<p>The live search consists of a form with the default <code>id</code> attribute value “live_search” (which can be changed using the “html_id” parameter). This form contains two zones. You can style any element by referencing the id attribute of the form and the <span class=\"caps\">CSS</span> class of the element (or its tag) in your <span class=\"caps\">CSS</span>.</p>\n\n<p>If you define a label, you can choose to make it invisible by setting its display property to “none” in the <span class=\"caps\">CSS</span>, so it will only be displayed in non-<span class=\"caps\">CSS</span> browsers that will usually not be able to display the text of the “default_value” parameter (see above).</p>\n\n<p><em>Example:</em></p>\n\n<pre><code>#live_search label {\n	display: none;\n}</code></pre>\n\n<h4>The search zone</h4>\n\n<p>The first zone is a block with the applied class “ls_search” which contains: the optional label (followed by a line break if defined), the text input box and the reset and wait images. The tag of this block is defined by the “wraptag” parameter (de fault is <code>p</code>).</p>\n\n<p>The search box is an input tag. If you want, you can style it using a <span class=\"caps\">CSS</span> rule like this one:</p>\n\n<pre><code>#live_search input {\n	font-size: 10px;\n	color: #000;\n}</code></pre>\n\n<p>When the search box is inactive (empty with no focus), the “ls_inactive” class is applied to it, so you can style it differently while it is in this state, typically by making the font brighter.</p>\n\n<p><em>Example:</em></p>\n\n<pre><code>#live_search input.ls_inactive {\n	color: #666;\n}</code></pre>\n\n<p>If you set the “safari” parameter to <code>true</code> and the browser is detected as Safari, the seach box will be replaced by a Mac OS X search widget and the reset image will not be shown because this special search widget already has one, and the “ls_safari” class will be applied to the <strong>form</strong>. This allows you to style the form and its subelements differently when displaying the Mac widget, for example by making the search widget larger because you know that the reset image will not be shown.</p>\n\n<p>Immediately to the right of the search box, still inside the block, you have the reset image and then the wait image. These images are facultative and must be defined using <span class=\"caps\">CSS</span> if you want to use them. If you don&#8217;t declare any <span class=\"caps\">CSS</span> rule for one of these images, it will never appear.</p>\n\n<p>The reset image is a <code>&lt;span&gt;</code> tag with the class “ls_reset_image” which is visible when the search box is not empty. Clicking on this image will clear the search box like a reset button. Note that even if you define a <span class=\"caps\">CSS</span> rule for a reset image, it will never be displayed in Safari browsers when the “safari” parameter is set to <code>true</code> (see above).</p>\n\n<p>The wait image is a <code>&lt;span&gt;</code> tag with the class “ls_wait_image” which is visible only during a search operation and disappears as soon as the results are shown. Typically, this image is a spinner or a hourglass.</p>\n\n<p>When you define the <span class=\"caps\">CSS</span> rules for these images, you <strong>must</strong>:</p>\n\n<ul>\n	<li>Set the display property to “inline-block”,</li>\n	<li>Set the width and height properties to the width and height of the image (in pixels),</li>\n	<li>Set the background property to the image file <span class=\"caps\">URL</span>,</li>\n	<li>For the reset image only, set the cursor property to “pointer” so that the mouse cursor indicates to the user that it works like a button.</li>\n</ul>\n\n<p>Also, it is a good idea to define a margin for some of these elements in order to add some space between the input box and the images.</p>\n\n<p><em>Example:</em></p>\n\n<pre><code>#live_search .ls_reset_image\n{\n	display: inline-block;\n	width: 14px;\n	height: 14px;\n	background: transparent url(../img/reset.gif) no-repeat center;\n	cursor: pointer;\n	margin-left: 10px;\n}</code></pre>\n\n<pre><code>#live_search .ls_wait_image\n{\n	display: inline-block;\n	width: 16px;\n	height: 16px;\n	background: transparent url(../img/spinner.gif) no-repeat center;\n	margin-left: 10px;\n}</code></pre>\n\n<p><strong>Remark:</strong> Browsers using previous versions of the Gecko engine like Firefox 1 or 2 normally do not support the “inline-block” value but cbs_live_search includes a javascript fix to make it work in these browsers, so you don&#8217;t need to worry about it.</p>\n\n<p>Ensure that you have enough space to display all elements of this block in the same row (by using correctly sized images and setting elements margins and the search box width with correct values for example).</p>\n\n<h4>The results zone</h4>\n\n<p>The results zone is a <code>&lt;div&gt;</code> block with the class “ls_results”, containing the results list.</p>\n\n<p>You can choose to style this <code>&lt;div&gt;</code> block as you want, but they are a few constraints:</p>\n\n<ol>\n	<li>You <strong>must</strong> set a non-transparent background for it (a color and optionnaly an image), otherwise if you keep the default transparent background, the div will display wrong in Internet Explorer.</li>\n	<li>You <strong>must</strong> set the “overflow” property to “hidden”, otherwise the effect will not work as expected.</li>\n	<li>You <strong>must</strong> set the “width” OR “height” of the <code>&lt;div&gt;</code> block (not both). If you set the width, ensure that it can expand vertically. If you set the height, ensure that it can expand horizontally.</li>\n	<li>You <strong>must not</strong> set its padding (leave it to zero, as it is by default), otherwise the script will not be able to compute the right size for the scale effect. Instead, you may set a margin for the content of the <code>&lt;div&gt;</code> block (see below how to style the content).</li>\n	<li>You <strong>must not</strong> set “display” and “visibility” properties. The block will simply not appear at all when Javascript is disabled.</li>\n</ol>\n\n<p>You <strong>may</strong> set any border to any side. Enventually, you can choose to style this <code>&lt;div&gt;</code> block with an absolute position or not. If you choose absolute (specify <code>position: absolute;</code>), it will fade in above your page like a popup.</p>\n\n<p><em>Example 1 (normal positioning):</em></p>\n\n<pre><code>#live_search .ls_results\n{\n	background-color: #ebebeb;\n	overflow: hidden;\n	width: 200px;\n}</code></pre>\n\n<p><em>Example 2 (absolute positioning):</em></p>\n\n<pre><code>#live_search .ls_results\n{\n	position: absolute;\n	left: 0;\n	top: 0;\n	background-color: #000;\n	overflow: hidden;\n	width: 500px;\n	border: 1px solid #fff;\n}</code></pre>\n\n<p>The content of the results block will be either:</p>\n\n<ol>\n	<li><em>At least one result is found:</em> A list with wrap and break tags according to your “wraptag” and “break” parameters (see above). The title of the articles will be wrapped in a link, so you can style the title differently from the (optional) excerpt based on the <code>&lt;a&gt;</code> tag (example: <code>#live_search .ls_results a:link, #live_search .ls_results a:visited { ... }</code>). The search expression will be wrapped in a <code>&lt;strong&gt;</code> tag inside the excerpts.</li>\n	<li><em>No results found:</em> A message (according to the “noresults” parameter described above) wrapped in a paragraph (<code>&lt;p&gt;</code> tag).</li>\n</ol>\n\n<p>You can style the results content as you want with no limitations. It is recommended to set a margin because its container (the <code>&lt;div&gt;</code> block with the class “ls_results”) must not have any padding.</p>\n\n<p><em>Example:</em></p>\n\n<pre><code>#live_search .ls_results p\n{\n	margin: 10px;\n}</code></pre>', '\n/*\n   cbs_live_search v0.8\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nregister_callback(\'cbs_search_results\', \'textpattern\');\n\nfunction cbs_search_results($event, $step)\n{\n	if(($q = ps(\'q\')) == \'\') {\n		return;\n	}\n\n	$break = ps(\'b\');\n	$wraptag = ps(\'w\');\n	if(!($max = intval(ps(\'m\')))) {\n		$max = 5;\n	}\n	$excerpts = ps(\'e\');\n	$dates = ps(\'d\');\n\n	header(\'Content-Type: text/xml\');\n\n	$q = trim($q);\n	if(empty($q)) {\n		exit(\'<none />\');\n	}\n	$q2 = addcslashes($q, \'\\\'\"\\\\%_\');\n\n	$rs = safe_column(\'name\', \'txp_section\', \"searchable != \'1\'\");\n	if($rs) {\n		foreach($rs as $name) {\n			$filters[] = \" AND Section != \'$name\'\";\n		}\n		$s_filter = join(\'\', $filters);\n	} else {\n		$s_filter = \'\';\n	}\n\n	$rs = safe_rows(\n		\"*, unix_timestamp(Posted) AS posted, MATCH (Title,Body) AGAINST (\'$q2\') AS score\",\n		\'textpattern\',\n		\"(Title LIKE \'%$q2%\' OR Body LIKE \'%$q2%\')$s_filter AND Status >= 4 AND Posted <= now() ORDER BY score DESC LIMIT $max\"\n	);\n	if(!$rs) {\n		exit(\'<none />\');\n	}\n\n	global $prefs;\n	$q = preg_quote(str_replace(\"\'\", \'&#8217;\', htmlspecialchars($q)), \'/\');\n	$o = array();\n	$concat = \'\';\n	foreach($rs as $a) {\n		$hurl = permlinkurl($a);\n\n		if($excerpts) {\n			$result = preg_replace(\'/>\\s+</\', \'> <\', $a[\'Body_html\']);\n			preg_match_all(\'/(?:^|\\s).{0,50}\'.$q.\'.{0,50}(?:\\s|$)/i\', $result, $concat);\n			$concat = join(\' ... \', $concat[0]);\n			$concat = strip_tags($concat);\n			$concat = preg_replace(\'/(?:^[^>]*>|<[^<]*$)/U\', \'\', $concat);\n			$concat = preg_replace(\'/(\'.$q.\')/i\', \'<strong>$1</strong>\', $concat);\n			if($concat) $concat = \'<br />... \'.$concat.\' ...\';\n		}\n		$o[] = \'<a href=\"\'.$hurl.\'\">\'.$a[\'Title\'].\'</a>\'.$concat\n				.(($dates) ? \'<br />&#8212; \'.safe_strftime($prefs[\'archive_dateformat\'], $a[\'posted\']) : \'\');\n	}\n	echo(\'<?xml version=\\\'1.0\\\' encoding=\\\'utf-8\\\' ?>\');\n	exit(doWrap($o, $wraptag, $break));\n}\n\nfunction cbs_live_search($atts)\n{\n	extract(lAtts(array(\n		\'section\'         => false,\n		\'html_id\'         => \'live_search\',\n		\'label\'           => false,\n		\'size\'            => 15,\n		\'default_value\'   => \'\',\n		\'wraptag\'         => \'p\',\n		\'safari\'          => false,\n		\'results_break\'   => \'br\',\n		\'results_wraptag\' => \'p\',\n		\'results_max\'     => false,\n		\'results_excerpts\'=> false,\n		\'results_dates\'   => false,\n		\'no_results\'      => \'0 \'.gTxt(\'articles_found\')\n	),$atts));\n	$section = $section ? pagelinkurl(array(\'s\'=>$section)) : hu;\n	$safari = ($safari == \'true\') ? 1 : 0;\n\n	$results_opts = array();\n	if($results_break) $results_opts[] =\'b:\"\'.$results_break.\'\"\';\n	if($results_wraptag) $results_opts[] = \'w:\"\'.$results_wraptag.\'\"\';\n	if($results_max) $results_opts[] = \'m:\'.$results_max;\n	if($results_excerpts == \'true\') $results_opts[] = \'e:1\';\n	if($results_dates == \'true\') $results_opts[] = \'d:1\';\n\n	return \'<form id=\"\'.$html_id.\'\" method=\"get\" action=\"\'.$section.\'\">\'.n\n		.doTag( ($label ? \'<label>\'.$label.\'<br /></label>\' : \'\')\n		.\'<input type=\"text\" name=\"q\" size=\"\'.$size.\'\" />\', $wraptag, \"ls_search\" ).n.\'</form>\'\n		.\'<script type=\"text/javascript\">//<![CDATA[\'.n.\'cbs_live_search(\"\'.$html_id.\'\",\"\'.$default_value\n		.\'\",\"\'.$no_results.\'\",\'.$safari.\',{\'.join(\',\', $results_opts).\'});\'.n.\'//]]></script>\';\n}\n', '\n/*\n   cbs_live_search v0.8\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nregister_callback(\'cbs_search_results\', \'textpattern\');\n\nfunction cbs_search_results($event, $step)\n{\n	if(($q = ps(\'q\')) == \'\') {\n		return;\n	}\n\n	$break = ps(\'b\');\n	$wraptag = ps(\'w\');\n	if(!($max = intval(ps(\'m\')))) {\n		$max = 5;\n	}\n	$excerpts = ps(\'e\');\n	$dates = ps(\'d\');\n\n	header(\'Content-Type: text/xml\');\n\n	$q = trim($q);\n	if(empty($q)) {\n		exit(\'<none />\');\n	}\n	$q2 = addcslashes($q, \'\\\'\"\\\\%_\');\n\n	$rs = safe_column(\'name\', \'txp_section\', \"searchable != \'1\'\");\n	if($rs) {\n		foreach($rs as $name) {\n			$filters[] = \" AND Section != \'$name\'\";\n		}\n		$s_filter = join(\'\', $filters);\n	} else {\n		$s_filter = \'\';\n	}\n\n	$rs = safe_rows(\n		\"*, unix_timestamp(Posted) AS posted, MATCH (Title,Body) AGAINST (\'$q2\') AS score\",\n		\'textpattern\',\n		\"(Title LIKE \'%$q2%\' OR Body LIKE \'%$q2%\')$s_filter AND Status >= 4 AND Posted <= now() ORDER BY score DESC LIMIT $max\"\n	);\n	if(!$rs) {\n		exit(\'<none />\');\n	}\n\n	global $prefs;\n	$q = preg_quote(str_replace(\"\'\", \'&#8217;\', htmlspecialchars($q)), \'/\');\n	$o = array();\n	$concat = \'\';\n	foreach($rs as $a) {\n		$hurl = permlinkurl($a);\n\n		if($excerpts) {\n			$result = preg_replace(\'/>\\s+</\', \'> <\', $a[\'Body_html\']);\n			preg_match_all(\'/(?:^|\\s).{0,50}\'.$q.\'.{0,50}(?:\\s|$)/i\', $result, $concat);\n			$concat = join(\' ... \', $concat[0]);\n			$concat = strip_tags($concat);\n			$concat = preg_replace(\'/(?:^[^>]*>|<[^<]*$)/U\', \'\', $concat);\n			$concat = preg_replace(\'/(\'.$q.\')/i\', \'<strong>$1</strong>\', $concat);\n			if($concat) $concat = \'<br />... \'.$concat.\' ...\';\n		}\n		$o[] = \'<a href=\"\'.$hurl.\'\">\'.$a[\'Title\'].\'</a>\'.$concat\n				.(($dates) ? \'<br />&#8212; \'.safe_strftime($prefs[\'archive_dateformat\'], $a[\'posted\']) : \'\');\n	}\n	echo(\'<?xml version=\\\'1.0\\\' encoding=\\\'utf-8\\\' ?>\');\n	exit(doWrap($o, $wraptag, $break));\n}\n\nfunction cbs_live_search($atts)\n{\n	extract(lAtts(array(\n		\'section\'         => false,\n		\'html_id\'         => \'live_search\',\n		\'label\'           => false,\n		\'size\'            => 15,\n		\'default_value\'   => \'\',\n		\'wraptag\'         => \'p\',\n		\'safari\'          => false,\n		\'results_break\'   => \'br\',\n		\'results_wraptag\' => \'p\',\n		\'results_max\'     => false,\n		\'results_excerpts\'=> false,\n		\'results_dates\'   => false,\n		\'no_results\'      => \'0 \'.gTxt(\'articles_found\')\n	),$atts));\n	$section = $section ? pagelinkurl(array(\'s\'=>$section)) : hu;\n	$safari = ($safari == \'true\') ? 1 : 0;\n\n	$results_opts = array();\n	if($results_break) $results_opts[] =\'b:\"\'.$results_break.\'\"\';\n	if($results_wraptag) $results_opts[] = \'w:\"\'.$results_wraptag.\'\"\';\n	if($results_max) $results_opts[] = \'m:\'.$results_max;\n	if($results_excerpts == \'true\') $results_opts[] = \'e:1\';\n	if($results_dates == \'true\') $results_opts[] = \'d:1\';\n\n	return \'<form id=\"\'.$html_id.\'\" method=\"get\" action=\"\'.$section.\'\">\'.n\n		.doTag( ($label ? \'<label>\'.$label.\'<br /></label>\' : \'\')\n		.\'<input type=\"text\" name=\"q\" size=\"\'.$size.\'\" />\', $wraptag, \"ls_search\" ).n.\'</form>\'\n		.\'<script type=\"text/javascript\">//<![CDATA[\'.n.\'cbs_live_search(\"\'.$html_id.\'\",\"\'.$default_value\n		.\'\",\"\'.$no_results.\'\",\'.$safari.\',{\'.join(\',\', $results_opts).\'});\'.n.\'//]]></script>\';\n}\n', '173daf618202acad8299ddfccd182ef5', '', '', 0, 5, 0),
('cbs_category_list', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.9', 'Section-sensitive article category list', '<h3>Description</h3>\n\n<p>This simple plugin is basically equivalent to category_list, excepted that it is <strong>section-sensitive</strong> and only lists <strong>article</strong> categories.</p>\n\n<p>Categories are displayed using their title and optionally their article count for the selected section and sorted alphabetically by title.</p>\n\n<p>If no article is found in the section for a category, this category will not be shown.</p>\n\n<h3>Syntax</h3>\n\n<pre><code>&lt;txp:cbs_category_list /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<h4>Required</h4>\n\n<p>none.</p>\n\n<h4>Optional</h4>\n\n<ul>\n	<li><strong>section</strong>: The section to display the related article categories. When ‘default’ (the frontpage) is specified, it shows all categories related to sections which are part of the frontpage. Default is the <em>current section</em>.</li>\n	<li><strong>sticky</strong>: When set to <code>true</code>, will display the category list of ‘sticky’ articles instead of ‘live’ articles. Default is <code>false</code>.</li>\n	<li><strong>posted</strong>: Either ‘past’, ‘future’ or ‘all’. Will only count articles with the specified publication date. ‘all’ is slightly faster because MySQL will be able to cache the query. Default is ‘past’.</li>\n	<li><strong>parent</strong>: The parent category name. If specified, will return only the <em>direct</em> children categories for this parent. It will not show children of children. Default is any category.</li>\n	<li><strong>showcount</strong>: When set to <code>true</code>, displays the number of related articles next to each category. Default is <code>false</code>.</li>\n	<li><strong>label</strong>: Label for the top of the list. Default is no label.</li>\n	<li><strong>labeltag</strong>:  Independent wraptag for label for the top of the list. Default is empty.</li>\n	<li><strong>break</strong>: <span class=\"caps\">HTML</span> tag to be used for line breaks, without brackets. Default is <code>br</code>.</li>\n	<li><strong>wraptag</strong>: <span class=\"caps\">HTML</span> tag to be used as the wraptag, without brackets. Default is empty.</li>\n	<li><strong>class</strong>: <span class=\"caps\">CSS</span> class attribute for wraptag, default is <code>cbs_category_list</code>.</li>\n	<li><strong>activeclass</strong>: <span class=\"caps\">CSS</span> class attribute for the currently active category, if any. By default, the active category is displayed like the other ones.</li>\n</ul>', '\n/*\n   cbs_category_list v0.9\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_category_list($atts)\n{\n	global $s, $c;\n\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'labeltag\' => \'\',\n		\'break\'    => \'br\',\n		\'wraptag\'  => \'\',\n		\'parent\'   => \'\',\n		\'posted\'   => \'\',\n		\'section\'  => $s,\n		\'sticky\'   => \'\',\n		\'showcount\'=> \'\',\n		\'class\'    => __FUNCTION__,\n		\'activeclass\'=> \'\'\n	),$atts));\n\n	$parent = ($parent) ? \" AND c.parent = \'$parent\'\" : \'\';\n	$showcount = ($showcount == \'true\') ? \', COUNT(*) AS count\' : \'\';\n	$sticky = ($sticky == \'true\') ? \'5\' : \'4\';\n\n	switch($posted) {\n		case \'all\':\n			$posted = \'\';\n			break;\n		case \'future\':\n			$posted = \' AND t.Posted >= now()\';\n			break;\n		default:\n			$posted = \' AND t.Posted < now()\';\n	}\n\n	if($section == \'default\') {\n		$table = \', `\'.PFX.\'txp_section` AS s\';\n		$sqlsection = \'s.name AND s.on_frontpage = 1\';\n	} else {\n		$table = \'\';\n		$sqlsection = \"\'$section\'\";\n	}\n\n	$rs = startRows(\n	  \'SELECT c.name, c.title\'.$showcount.\' FROM `\'.PFX.\'txp_category` AS c, `\'.PFX.\'textpattern` AS t\'.$table\n	  .\' WHERE (c.name = t.Category1 OR c.name = t.Category2) AND c.type = \\\'article\\\' AND t.Section = \'.$sqlsection.$parent.$posted\n	  .\' AND t.Status = \'.$sticky.\' GROUP BY c.name ORDER BY c.title\'\n	);\n	if(!$rs)\n		return \'\';\n\n	$count = \'\';\n	$out = array();\n	while($a = nextRow($rs)) {\n		extract($a);\n		$out[] = tag(str_replace(\'& \',\'&#38; \', $title), \'a\', \' href=\"\'.pagelinkurl(array(\'c\'=>$name, \'s\'=>$section))\n				.(($activeclass && ($s == $section) && ($c == $name)) ? \'\" class=\"\'.$activeclass.\'\"\' : \'\"\')).($count ? \' (\'.$count.\')\' : \'\');\n	}\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n}\n', '\n/*\n   cbs_category_list v0.9\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_category_list($atts)\n{\n	global $s, $c;\n\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'labeltag\' => \'\',\n		\'break\'    => \'br\',\n		\'wraptag\'  => \'\',\n		\'parent\'   => \'\',\n		\'posted\'   => \'\',\n		\'section\'  => $s,\n		\'sticky\'   => \'\',\n		\'showcount\'=> \'\',\n		\'class\'    => __FUNCTION__,\n		\'activeclass\'=> \'\'\n	),$atts));\n\n	$parent = ($parent) ? \" AND c.parent = \'$parent\'\" : \'\';\n	$showcount = ($showcount == \'true\') ? \', COUNT(*) AS count\' : \'\';\n	$sticky = ($sticky == \'true\') ? \'5\' : \'4\';\n\n	switch($posted) {\n		case \'all\':\n			$posted = \'\';\n			break;\n		case \'future\':\n			$posted = \' AND t.Posted >= now()\';\n			break;\n		default:\n			$posted = \' AND t.Posted < now()\';\n	}\n\n	if($section == \'default\') {\n		$table = \', `\'.PFX.\'txp_section` AS s\';\n		$sqlsection = \'s.name AND s.on_frontpage = 1\';\n	} else {\n		$table = \'\';\n		$sqlsection = \"\'$section\'\";\n	}\n\n	$rs = startRows(\n	  \'SELECT c.name, c.title\'.$showcount.\' FROM `\'.PFX.\'txp_category` AS c, `\'.PFX.\'textpattern` AS t\'.$table\n	  .\' WHERE (c.name = t.Category1 OR c.name = t.Category2) AND c.type = \\\'article\\\' AND t.Section = \'.$sqlsection.$parent.$posted\n	  .\' AND t.Status = \'.$sticky.\' GROUP BY c.name ORDER BY c.title\'\n	);\n	if(!$rs)\n		return \'\';\n\n	$count = \'\';\n	$out = array();\n	while($a = nextRow($rs)) {\n		extract($a);\n		$out[] = tag(str_replace(\'& \',\'&#38; \', $title), \'a\', \' href=\"\'.pagelinkurl(array(\'c\'=>$name, \'s\'=>$section))\n				.(($activeclass && ($s == $section) && ($c == $name)) ? \'\" class=\"\'.$activeclass.\'\"\' : \'\"\')).($count ? \' (\'.$count.\')\' : \'\');\n	}\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n}\n', '3830dab0c260af4ecfd73aed6d583b3c', '', '', 0, 5, 0),
('cbs_navigation_menu', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.4', 'Simple customizable sections navigation menu', '<h3>Description</h3>\n\n<p>This plugin allows to create a custom navigation menu based on Textpattern sections and/or full external <span class=\"caps\">URL</span>&#8217;s. You can choose which sections you want to display in which order and style the current section differently.</p>\n\n<p>Or, instead of sections you can specify full <span class=\"caps\">URL</span>&#8217;s (starting with http://, ftp://, mailto: or any other prefix) and they will be linked “as is”.</p>\n\n<p>You can also specify access keys for each menu entry.</p>\n\n<h3>Syntax</h3>\n\n<pre><code>&lt;txp:cbs_navigation_menu sections=&quot;a,b,c&quot; titles=&quot;A,B,C&quot; /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<h4>Required</h4>\n\n<ul>\n	<li><strong>sections</strong>: Comma-separated list of section names or full <span class=\"caps\">URL</span>&#8217;s you want to link in your navigation menu, in order, with no spaces before and after commas. For the homepage, specify ‘default’ as section name. Each entry is interpreted as a Textpattern section, unless it contains a ‘:’ character like in any full <span class=\"caps\">URL</span> starting with ‘http://’, ‘ftp://’, ‘mailto:’, &#8230;</li>\n	<li><strong>titles</strong>: Comma-separated list of titles you want to display for each section of the “sections” attribute above, with no spaces before and after commas.</li>\n</ul>\n\n<h4>Optional</h4>\n\n<ul>\n	<li><strong>accesskeys</strong>: Comma-separated list of keyboard access keys to associate with each section link of the “sections” attribute above, with no spaces before and after commas. If you don&#8217;t want to specify an access key for a section, leave it empty (‘’), but you must have the same number of commas as in the “sections” and “titles” attributes. Default is no access keys.</li>\n	<li><strong>break</strong>: <span class=\"caps\">HTML</span> tag to be used for line breaks, without brackets. Default is <code>br</code>.</li>\n	<li><strong>wraptag</strong>: <span class=\"caps\">HTML</span> tag to be used as the wraptag, without brackets. Default is empty.</li>\n	<li><strong>class</strong>: <span class=\"caps\">CSS</span> class attribute for wraptag, default is no class attribute.</li>\n	<li><strong>activeclass</strong>: <span class=\"caps\">CSS</span> class attribute to apply to links pointing to the current section, if any. Default is no class attribute.</li>\n	<li><strong>activenolink</strong>: When set to <code>true</code>, the current section will not be linked, but wrapped with a <code>&lt;span&gt;</code> tag instead. The class of the “activeclass” attribute and the id of the “ids” attribute, if specified, will be applied to this <code>&lt;span&gt;</code> tag. Default is <code>false</code>.</li>\n	<li><strong>ids</strong>: Comma-separated list of <span class=\"caps\">XHTML</span> id attributes to apply to each menu item, with no spaces before and after commas. If you don&#8217;t want to add an id attribute for a section, leave it empty (‘’), but you must have the same number of commas as in the “sections” and “titles” attributes. Default is no id attribute.</li>\n</ul>', '\n/*\n   cbs_navigation_menu v0.4\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_navigation_menu($atts)\n{\n	global $s;\n\n	extract(lAtts(array(\n		\'sections\'    => false,\n		\'titles\'      => false,\n		\'accesskeys\'  => false,\n		\'break\'       => \'br\',\n		\'wraptag\'     => \'\',\n		\'class\'       => \'\',\n		\'activeclass\' => \'\',\n		\'activenolink\'=> \'\',\n		\'ids\'         => false\n	),$atts));\n\n	if(($sections === false) || ($titles === false))\n		return \'\';\n	$sections = explode(\',\', $sections);\n	$size = count($sections);\n	$titles = explode(\',\', $titles);\n	if(count($titles) != $size)\n		return \'\';\n	if($accesskeys !== false) {\n		$accesskeys = explode(\',\', $accesskeys);\n		if(count($accesskeys) != $size)\n			return \'\';\n	}\n	$activenolink = ($activenolink == \'true\');\n	if($ids !== false) {\n		$ids = explode(\',\', $ids);\n		if(count($ids) != $size)\n			return \'\';\n	}\n	$out = array();\n	for($i = 0; $i < $size; ++$i) {\n		$options = (($ids && ($ids[$i] != \'\')) ? \' id=\"\'.$ids[$i].\'\"\' : \'\');\n		if(strpos($sections[$i], \':\') !== false) {\n			$link = $sections[$i];\n		} else {\n			if($sections[$i] == $s) {\n				if($activeclass)\n					$options .= \' class=\"\'.$activeclass.\'\"\';\n				if($activenolink) {\n					$out[] = tag($titles[$i], \'span\', $options);\n					continue;\n				}\n			}\n			$link = pagelinkurl(array(\'s\'=>$sections[$i]));\n		}\n		$out[] = tag($titles[$i], \'a\', \' href=\"\'.$link\n					.(($accesskeys && ($accesskeys[$i] != \'\')) ? \'\" accesskey=\"\'.$accesskeys[$i].\'\"\' : \'\"\')\n					.$options);\n	}\n	return doWrap($out, $wraptag, $break, $class);\n}\n', '\n/*\n   cbs_navigation_menu v0.4\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_navigation_menu($atts)\n{\n	global $s;\n\n	extract(lAtts(array(\n		\'sections\'    => false,\n		\'titles\'      => false,\n		\'accesskeys\'  => false,\n		\'break\'       => \'br\',\n		\'wraptag\'     => \'\',\n		\'class\'       => \'\',\n		\'activeclass\' => \'\',\n		\'activenolink\'=> \'\',\n		\'ids\'         => false\n	),$atts));\n\n	if(($sections === false) || ($titles === false))\n		return \'\';\n	$sections = explode(\',\', $sections);\n	$size = count($sections);\n	$titles = explode(\',\', $titles);\n	if(count($titles) != $size)\n		return \'\';\n	if($accesskeys !== false) {\n		$accesskeys = explode(\',\', $accesskeys);\n		if(count($accesskeys) != $size)\n			return \'\';\n	}\n	$activenolink = ($activenolink == \'true\');\n	if($ids !== false) {\n		$ids = explode(\',\', $ids);\n		if(count($ids) != $size)\n			return \'\';\n	}\n	$out = array();\n	for($i = 0; $i < $size; ++$i) {\n		$options = (($ids && ($ids[$i] != \'\')) ? \' id=\"\'.$ids[$i].\'\"\' : \'\');\n		if(strpos($sections[$i], \':\') !== false) {\n			$link = $sections[$i];\n		} else {\n			if($sections[$i] == $s) {\n				if($activeclass)\n					$options .= \' class=\"\'.$activeclass.\'\"\';\n				if($activenolink) {\n					$out[] = tag($titles[$i], \'span\', $options);\n					continue;\n				}\n			}\n			$link = pagelinkurl(array(\'s\'=>$sections[$i]));\n		}\n		$out[] = tag($titles[$i], \'a\', \' href=\"\'.$link\n					.(($accesskeys && ($accesskeys[$i] != \'\')) ? \'\" accesskey=\"\'.$accesskeys[$i].\'\"\' : \'\"\')\n					.$options);\n	}\n	return doWrap($out, $wraptag, $break, $class);\n}\n', '596363ed7b16f67545e7f5da2045ba8f', '', '', 0, 5, 0),
('cbs_article_index', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.2', 'Article index made from heading titles anchors in the article body', '<h3>Description</h3>\n\n<p>This plugin creates a list of links pointing to anchors present in heading titles in the body of an article, so the user can easily navigate through it. It must be used in an article form.</p>\n\n<h3>Prerequisite</h3>\n\n<p>In the body of the article, add an anchor (an “id” attribute) for each heading title you want to appear in the article index. This may be any kind of heading title, from <code>&lt;h1&gt;</code> to <code>&lt;h6&gt;</code>.</p>\n\n<p>You can use any value for the “id” attribute, but make sure it is unique in your page.</p>\n\n<p>Example:</p>\n\n<p>Textile:</p>\n\n<pre><code>h3(#intro). Introduction</code></pre>\n\n<p><span class=\"caps\">HTML</span>:</p>\n\n<pre><code>&lt;h3 id=&quot;intro&quot;&gt;Introduction&lt;/h3&gt;</code></pre>\n\n<p>Of course you can add other attributes to the heading title tag or add other tags inside the heading title such as <code>&lt;em&gt;</code> or <code>&lt;strong&gt;</code>, it will also work.</p>\n\n<p>If the plugin does not find any heading title with an “id” attribute in the body of the article, it will not display anything.</p>\n\n<h3>Syntax</h3>\n\n<p>In an article form:</p>\n\n<pre><code>&lt;txp:cbs_article_index /&gt;</code></pre>\n\n<h3>Attributes</h3>\n\n<h4>Required</h4>\n\n<p>none.</p>\n\n<h4>Optional</h4>\n\n<ul>\n	<li><strong>label</strong>: Label for the top of the list. Default is no label.</li>\n	<li><strong>labeltag</strong>: Independent wraptag for label for the top of the list. Default is empty.</li>\n	<li><strong>break</strong>: <span class=\"caps\">HTML</span> tag to be used for line breaks, without brackets. Default is <code>br</code>.</li>\n	<li><strong>wraptag</strong>: <span class=\"caps\">HTML</span> tag to be used as the wraptag, without brackets. Default is empty.</li>\n	<li><strong>class</strong>: <span class=\"caps\">CSS</span> class attribute for wraptag, default is <code>cbs_article_index</code>.</li>\n</ul>', '\n/*\n   cbs_article_index v0.2\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_article_index($atts)\n{\n	global $thisarticle;\n\n	$count = preg_match_all(\'/<h\\d[^>]*?\\sid\\s*=\\s*[\"\\\']([^\"\\\']*)[\"\\\'][^>]*>(.*?)<\\/h\\d/i\', $thisarticle[\'body\'], $out, PREG_SET_ORDER);\n	if(!$count)\n		return \'\';\n\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'labeltag\' => \'\',\n		\'break\'    => \'br\',\n		\'wraptag\'  => \'\',\n		\'class\'    => __FUNCTION__\n	),$atts));\n\n	for($i=0; $i<$count; ++$i) {\n		$out[$i] = tag($out[$i][2], \'a\', \' href=\"#\'.$out[$i][1].\'\"\');\n	}\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n}\n', '\n/*\n   cbs_article_index v0.2\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nfunction cbs_article_index($atts)\n{\n	global $thisarticle;\n\n	$count = preg_match_all(\'/<h\\d[^>]*?\\sid\\s*=\\s*[\"\\\']([^\"\\\']*)[\"\\\'][^>]*>(.*?)<\\/h\\d/i\', $thisarticle[\'body\'], $out, PREG_SET_ORDER);\n	if(!$count)\n		return \'\';\n\n	extract(lAtts(array(\n		\'label\'    => \'\',\n		\'labeltag\' => \'\',\n		\'break\'    => \'br\',\n		\'wraptag\'  => \'\',\n		\'class\'    => __FUNCTION__\n	),$atts));\n\n	for($i=0; $i<$count; ++$i) {\n		$out[$i] = tag($out[$i][2], \'a\', \' href=\"#\'.$out[$i][1].\'\"\');\n	}\n	return doLabel($label, $labeltag).doWrap($out, $wraptag, $break, $class);\n}\n', '4a3491f9165ba30b12bb0915ffb09cd2', '', '', 0, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbs_maintenance_mode', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.3', 'Takes the public website offline with a custom message and shows a warning in the admin interface', '<h3>Description</h3>\n\n<p>This plugin is a modified version of <a href=\"http://vanmelick.com/txp/\" rel=\"nofollow\">rvm_maintenance</a> by Ruud van Melick. It allows you to take the entire public website offline for maintenance in a few clicks. Anyone attempting to access the public part of the website (including logged users) will then see in the default Textpattern error template for an <span class=\"caps\">HTTP</span> 503 error instead (<strong>error_503</strong> if available, otherwise <strong>error_default</strong>), including a custom message of your choice which will be displayed by the <code>&lt;txp: error_message /&gt;</code> tag in the template. A “503 &#8211; unavailable” <span class=\"caps\">HTTP</span> header will be sent along the error template in order to notify bots that they must come back later.</p>\n\n<p>The admin interface will always remain online. A small warning message will be shown at the top of the admin pages as long as the website is in maintenance mode.</p>\n\n<h3>Remark</h3>\n\n<p>Simply activating the plugin will not take your website offline until the cbs_maintenance_mode_enabled setting is set to <code>true</code> (see below).</p>\n\n<h3>Settings</h3>\n\n<p>The plugin settings are located in the “Publish” section of the main settings panel.</p>\n\n<ul>\n	<li><strong>cbs_maintenance_mode_enabled</strong>: Boolean, when set to <code>true</code> maintenance mode will be enabled and the public website will be taken offline, displaying the default error template for an <span class=\"caps\">HTTP</span> 503 error. The admin interface will also display a warning message at the top of every page. Set to <code>false</code> to take the website back online when maintenance is complete. Default is <code>false</code>.</li>\n	<li><strong>cbs_maintenance_mode_message</strong>: The message that will be shown by the <code>&lt;txp: error_message /&gt;</code> tag in the error template when the public website is offline. Default is “The website is currently under maintenance. Please come back later.”.</li>\n</ul>', '\n/*\n   cbs_maintenance_mode v0.3\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nif(!isset($prefs[\'cbs_maintenance_mode_message\'])) {\n	$prefs[\'cbs_maintenance_mode_message\'] = \'The website is currently under maintenance.\'.br.\'Please come back later.\';\n	set_pref(\'cbs_maintenance_mode_message\', $prefs[\'cbs_maintenance_mode_message\'], \'publish\', 0);\n}\nif(!isset($prefs[\'cbs_maintenance_mode_enabled\'])) {\n	set_pref(\'cbs_maintenance_mode_enabled\', 0, \'publish\', 0, \'yesnoradio\');\n} elseif($prefs[\'cbs_maintenance_mode_enabled\']) {\n	if(@txpinterface == \'admin\') {\n		register_callback(\'cbs_maintenance_mode_warning\', \'admin_side\', \'pagetop_end\');\n	} else {\n		register_callback(\'cbs_maintenance_mode\', \'textpattern\');\n	}\n}\n\nfunction cbs_maintenance_mode() {\n	global $prefs;\n	txp_die($prefs[\'cbs_maintenance_mode_message\'], 503);\n}\n\nfunction cbs_maintenance_mode_warning() {\n	echo \'<p style=\"color: #f00; text-align: center;\"><strong>Warning:</strong> The website is currently in maintenance mode.</p>\';\n}\n', '\n/*\n   cbs_maintenance_mode v0.3\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nif(!isset($prefs[\'cbs_maintenance_mode_message\'])) {\n	$prefs[\'cbs_maintenance_mode_message\'] = \'The website is currently under maintenance.\'.br.\'Please come back later.\';\n	set_pref(\'cbs_maintenance_mode_message\', $prefs[\'cbs_maintenance_mode_message\'], \'publish\', 0);\n}\nif(!isset($prefs[\'cbs_maintenance_mode_enabled\'])) {\n	set_pref(\'cbs_maintenance_mode_enabled\', 0, \'publish\', 0, \'yesnoradio\');\n} elseif($prefs[\'cbs_maintenance_mode_enabled\']) {\n	if(@txpinterface == \'admin\') {\n		register_callback(\'cbs_maintenance_mode_warning\', \'admin_side\', \'pagetop_end\');\n	} else {\n		register_callback(\'cbs_maintenance_mode\', \'textpattern\');\n	}\n}\n\nfunction cbs_maintenance_mode() {\n	global $prefs;\n	txp_die($prefs[\'cbs_maintenance_mode_message\'], 503);\n}\n\nfunction cbs_maintenance_mode_warning() {\n	echo \'<p style=\"color: #f00; text-align: center;\"><strong>Warning:</strong> The website is currently in maintenance mode.</p>\';\n}\n', '7c1131ff904b9dd629d3f4f2aa9e5707', '', '', 0, 1, 0),
('cbs_section_language', 0, 'Christophe Beyls', 'http://www.digitalia.be/', '0.2', 'Overrides the default language for specific sections', '<h3>Description</h3>\n\n<p>This plugin allows you to globally override the default language of your Textpattern installation for some specific sections. You can use any installed language for any section, but you can only use one language per section.</p>\n\n<h3>Prerequisite</h3>\n\n<p>Please check first in the language administration panel that all the languages you want to use are installed.</p>\n\n<h3>Settings</h3>\n\n<p>The plugin settings are located in the “Publish” section of the advanced settings panel.</p>\n\n<ul>\n	<li><strong>cbs_section_language_sections</strong>: Comma-separated list of the names of the sections where you want to override the default language. For the homepage, specify “default” as section name. Whitespaces are allowed before and after commas.</li>\n	<li><strong>cbs_section_language_languages</strong>: Comma-separated list of language codes, matching each section of the <strong>cbs_section_language_sections</strong> setting above. A language code consists of a two-letter language acronym, followed by a hyphen, followed by a two-letter country acronym, for example: “en-us” or “fr-fr”. Whitespaces are allowed before and after commas.</li>\n</ul>', '\n/*\n   cbs_section_language v0.2\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nif(!isset($prefs[\'cbs_section_language_sections\'])) {\n	set_pref(\'cbs_section_language_sections\', \'\', \'publish\', 1);\n}\nif(!isset($prefs[\'cbs_section_language_languages\'])) {\n	set_pref(\'cbs_section_language_languages\', \'\', \'publish\', 1);\n}\nif(!empty($prefs[\'cbs_section_language_sections\']) && !empty($prefs[\'cbs_section_language_languages\'])) {\n	register_callback(\'cbs_section_language\', \'comment.save\');\n	register_callback(\'cbs_section_language\', \'textpattern\');\n}\n\nfunction cbs_section_language() {\n	global $prefs, $s, $textarray;\n	static $done = false;\n	if($done) return;\n\n	$done = true;\n	$sections = explode(\',\', $prefs[\'cbs_section_language_sections\']);\n	$size = count($sections);\n	$languages = explode(\',\', $prefs[\'cbs_section_language_languages\']);\n	if($size != count($languages)) {\n		trigger_error(\'The number of sections and languages for cbs_section_language is not equal\', E_USER_ERROR);\n		return;\n	}\n	for($i = 0; $i < $size; ++$i) {\n		$section = trim($sections[$i]);\n		if($section == $s) {\n			$language = trim($languages[$i]);\n			$prefs[\'language\'] = $language;\n			$textarray = load_lang($language);\n			break;\n		}\n	}\n}\n', '\n/*\n   cbs_section_language v0.2\n   by Christophe Beyls [http://www.digitalia.be]\n*/\n\nif(!isset($prefs[\'cbs_section_language_sections\'])) {\n	set_pref(\'cbs_section_language_sections\', \'\', \'publish\', 1);\n}\nif(!isset($prefs[\'cbs_section_language_languages\'])) {\n	set_pref(\'cbs_section_language_languages\', \'\', \'publish\', 1);\n}\nif(!empty($prefs[\'cbs_section_language_sections\']) && !empty($prefs[\'cbs_section_language_languages\'])) {\n	register_callback(\'cbs_section_language\', \'comment.save\');\n	register_callback(\'cbs_section_language\', \'textpattern\');\n}\n\nfunction cbs_section_language() {\n	global $prefs, $s, $textarray;\n	static $done = false;\n	if($done) return;\n\n	$done = true;\n	$sections = explode(\',\', $prefs[\'cbs_section_language_sections\']);\n	$size = count($sections);\n	$languages = explode(\',\', $prefs[\'cbs_section_language_languages\']);\n	if($size != count($languages)) {\n		trigger_error(\'The number of sections and languages for cbs_section_language is not equal\', E_USER_ERROR);\n		return;\n	}\n	for($i = 0; $i < $size; ++$i) {\n		$section = trim($sections[$i]);\n		if($section == $s) {\n			$language = trim($languages[$i]);\n			$prefs[\'language\'] = $language;\n			$textarray = load_lang($language);\n			break;\n		}\n	}\n}\n', 'dfae7a57082b99d801bc4c245820c328', '', '', 0, 1, 0),
('wet_lorem_ipsum', 0, 'Robert Wetzlmayr', 'http://wetzlmayr.com/', '0.2.1', 'Create new articles as test data', '<h3>Purpose</h3>\n\n<p>Creates articles with randomized content to serve as test data during performance reviews and the like.</p>\n\n<h3>How to use</h3>\n\n<ol>\n	<li>Navigate to <a href=\"http://480.test/?event=wet_lorem_ipsum\" rel=\"nofollow\">Content &gt; wet_lorem_ipsum</a>.</li>\n	<li>Enter the desired amount of new articles.</li>\n	<li>Hit “Save”.</li>\n	<li>Sit and wait. Depending on the performance of your MySQL server and the amount of new articles it can take several minutes until the operation finishes.</li>\n</ol>\n\n<h3>Licence</h3>\n\n<p>This plugin is released under the <a href=\"http://www.gnu.org/licenses/gpl.txt\" rel=\"nofollow\">Gnu General Public Licence</a>.</p>', 'class wet_lorem_ipsum\r\n{\r\n	static private $words = array(\r\n		\'Lorem\',  \'ipsum\', \'dolor\', \'sit\', \'amet\', \'consectetur\', \'adipiscing\', \'elit\'\r\n	);\r\n\r\n	static private $grafs = array(\r\n		\'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vitae nisl sed justo porttitor faucibus in sed elit. Pellentesque venenatis hendrerit imperdiet. Sed at volutpat nisi. Nulla auctor, nisi condimentum sagittis consequat, magna felis condimentum lacus, eu imperdiet odio leo ut diam. Nullam vel quam elit. Donec ipsum sem, bibendum sit amet tincidunt quis, suscipit ut ligula. Maecenas vehicula, leo id eleifend facilisis, augue nibh sodales ante, eu aliquam dui lectus et nisi. Aliquam quis metus sed nisi venenatis fringilla. Sed eget justo felis, eget sodales erat. Pellentesque id est ac felis imperdiet ultrices. Pellentesque fermentum euismod elementum. Proin turpis velit, imperdiet et interdum a, fringilla vel sem.\',\r\n		\'Sed tincidunt porta sollicitudin. Duis diam enim, molestie ut scelerisque ut, rutrum eu augue. Suspendisse velit nunc, malesuada et luctus non, scelerisque et turpis. Sed turpis ligula, gravida at vestibulum sed, semper ut felis. Fusce turpis diam, adipiscing commodo elementum ac, porttitor vitae mauris. Sed quis urna sed nulla tincidunt condimentum. Proin dignissim, sapien eget vulputate commodo, purus arcu porttitor mauris, ac sollicitudin diam leo et mauris.\',\r\n		\'Pellentesque nec velit in erat lacinia sagittis. Donec eu lacus non quam vulputate sollicitudin. Morbi sodales tempor diam, vel tincidunt felis feugiat vestibulum. Curabitur nec dapibus ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum neque sapien, aliquam quis interdum vel, dignissim in risus. Nulla nec est sed tortor pharetra tristique aliquet vel lacus. Nam at ipsum velit.\',\r\n		\'Nam cursus elit dignissim mi pharetra nec ullamcorper lectus tristique. Duis vitae lacus sit amet tellus venenatis faucibus at ut lectus. Etiam porttitor neque at eros laoreet sed pulvinar augue facilisis. Integer condimentum orci velit. Duis interdum metus sit amet sapien imperdiet at egestas nisi pretium. Nulla facilisi. Maecenas porta scelerisque magna non imperdiet. Nullam commodo ornare consequat. Phasellus pulvinar porttitor diam, quis convallis erat pharetra ac. Phasellus nec leo justo.\',\r\n		\'Donec congue venenatis erat at imperdiet. In egestas, lacus et pretium molestie, orci turpis convallis justo, consequat egestas ante felis nec magna. Aenean augue risus, bibendum ut auctor non, tincidunt commodo elit. Duis at metus vel urna ultrices pulvinar. Nullam at fermentum turpis. Aenean eget ornare lectus. Donec ac est at diam rutrum imperdiet. Maecenas sit amet placerat magna. Pellentesque eleifend iaculis sodales. Vivamus sodales justo vel erat euismod non lobortis enim suscipit. Integer hendrerit pharetra mauris id imperdiet. Ut consectetur elit eu lorem tempor viverra. In hac habitasse platea dictumst. In nec libero ut quam molestie venenatis. Sed et ultricies felis. Nullam dictum lorem in sem congue eget fringilla velit fermentum. \',\r\n	);\r\n\r\n	static private $section_names;\r\n\r\n	/**\r\n	 * Hook UI, setup privileges\r\n	 */\r\n	function __construct()\r\n	{\r\n		if (txpinterface == \'admin\') {\r\n			add_privs(\'wet_lorem_ipsum\', \'1,2\');\r\n			register_tab(\'content\', \'wet_lorem_ipsum\', gTxt(\'wet_lorem_ipsum\'));\r\n			register_callback(array(__CLASS__, \'ui\'), \'wet_lorem_ipsum\');\r\n\r\n			self::$section_names = safe_column(\'name\', \'txp_section\', \'name != \\\'default\\\'\');\r\n		}\r\n	}\r\n\r\n	/**\r\n	 * User interface\r\n	 */\r\n	static function ui()\r\n	{\r\n		global $step;\r\n\r\n		$message = \'\';\r\n		$ok = true;\r\n		$vars = array(\'articles\', \'sections\');\r\n\r\n		extract(psa($vars));\r\n\r\n		if (bouncer($step, array(\r\n			\'add_things\' => true\r\n		))) {\r\n			foreach ($vars as $var) {\r\n				if ($$var !== \'\') {\r\n					$$var = assert_int($$var);\r\n\r\n					// If amount is greater zero: Create new elements\r\n					if ($$var > 0) {\r\n						$f = \'add_\'.$var;\r\n						$ok *= self::$f($$var);\r\n					}\r\n					$message = ($ok) ? gTxt(\'ok\') : array(gTxt(\'nok\'), E_ERROR);\r\n				}\r\n			}\r\n		}\r\n\r\n		pagetop(gTxt(__CLASS__), $message);\r\n		$out[] = hed(gTxt(\'wet_lorem_ipsum\'), 2);\r\n		$out[] =  inputLabel(\'articles\', fInput(\'text\', \'articles\', $articles, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'articles\'), \'articles\');\r\n//	TODO	$out[] =  inputLabel(\'sections\', fInput(\'text\', \'sections\', $sections, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'sections\'), \'sections\');\r\n\r\n		$out[] = graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')).n.\r\n			eInput(__CLASS__).n.\r\n			sInput(\'add_things\');\r\n\r\n		echo form(\r\n			n.tag(join(\'\', $out).n, \'section\', array(\'class\' => \'txp-edit\'))\r\n			, \'\', \'\', \'post\', \'edit-form\', \'\', __CLASS__);\r\n	}\r\n\r\n	/**\r\n	 * Add new articles\r\n	 */\r\n	static function add_articles($amount)\r\n	{\r\n		global $txp_user;\r\n\r\n		@set_time_limit(0);\r\n		$textile = new \\Textpattern\\Textile\\Parser();\r\n\r\n		$user = safe_escape($txp_user);\r\n		$textile_body = $textile_excerpt = USE_TEXTILE;\r\n		$ok = true;\r\n\r\n		for ($i = 0; $i < $amount; $i++) {\r\n			$Title = safe_escape(__CLASS__ . \' \' . join(\' \', self::array_random(self::$words, mt_rand(2, count(self::$words)))));\r\n			$Body = join(n.n, self::array_random(self::$grafs, mt_rand(2, count(self::$grafs))));\r\n			$Body_html = safe_escape($textile->parse($Body));\r\n			$Body = safe_escape($Body);\r\n			$url_title = safe_escape(md5(uniqid(rand(), true)));\r\n			$Section = safe_escape(self::array_random(self::$section_names, 1));\r\n			$custom_1 = safe_escape(self::array_random(self::$words, 1));\r\n			$custom_2 = safe_escape(self::array_random(self::$words, 1));\r\n			$Keywords = safe_escape(join(\', \', self::array_random(self::$words, mt_rand(2, count(self::$words)))));\r\n\r\n			$ok *= safe_insert(\r\n				\"textpattern\",\r\n				\"Title           = \'$Title\',\r\n					Body            = \'$Body\',\r\n					Body_html       = \'$Body_html\',\r\n					Excerpt         = \'\',\r\n					Excerpt_html    = \'\',\r\n					Image           = \'\',\r\n					Keywords        = \'$Keywords\',\r\n					Status          =  \".STATUS_LIVE.\",\r\n					Posted          =  DATE_SUB(now(), INTERVAL \".mt_rand(0, 3600*24*7).\" SECOND),\r\n					Expires         =  NULL,\r\n					AuthorID        = \'$user\',\r\n					LastMod         =  DATE_SUB(now(), INTERVAL \".mt_rand(0, 3600*24*7).\" SECOND),\r\n					LastModID       = \'\',\r\n					Section         = \'$Section\',\r\n					Category1       = \'\',\r\n					Category2       = \'\',\r\n					textile_body    = \'$textile_body\',\r\n					textile_excerpt = \'$textile_excerpt\',\r\n					Annotate        =  0,\r\n					override_form   = \'\',\r\n					url_title       = \'$url_title\',\r\n					AnnotateInvite  = \'\',\r\n					custom_1        = \'$custom_1\',\r\n					custom_2        = \'$custom_2\',\r\n					uid             = \'\".md5(uniqid(rand(), true)).\"\',\r\n					feed_time       = now()\"\r\n			);\r\n\r\n		}\r\n\r\n		return $ok;\r\n	}\r\n\r\n	/**\r\n	 * Add new sections\r\n	 */\r\n	static function add_sections($amount)\r\n	{\r\n		return true;\r\n	}\r\n\r\n	private static function array_random($arr, $num = 1)\r\n	{\r\n		shuffle($arr);\r\n		return ($num == 1) ? $arr[0] : array_slice($arr, 0, $num);\r\n	}\r\n}\r\n\r\nnew wet_lorem_ipsum;\r\n', 'class wet_lorem_ipsum\r\n{\r\n	static private $words = array(\r\n		\'Lorem\',  \'ipsum\', \'dolor\', \'sit\', \'amet\', \'consectetur\', \'adipiscing\', \'elit\'\r\n	);\r\n\r\n	static private $grafs = array(\r\n		\'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vitae nisl sed justo porttitor faucibus in sed elit. Pellentesque venenatis hendrerit imperdiet. Sed at volutpat nisi. Nulla auctor, nisi condimentum sagittis consequat, magna felis condimentum lacus, eu imperdiet odio leo ut diam. Nullam vel quam elit. Donec ipsum sem, bibendum sit amet tincidunt quis, suscipit ut ligula. Maecenas vehicula, leo id eleifend facilisis, augue nibh sodales ante, eu aliquam dui lectus et nisi. Aliquam quis metus sed nisi venenatis fringilla. Sed eget justo felis, eget sodales erat. Pellentesque id est ac felis imperdiet ultrices. Pellentesque fermentum euismod elementum. Proin turpis velit, imperdiet et interdum a, fringilla vel sem.\',\r\n		\'Sed tincidunt porta sollicitudin. Duis diam enim, molestie ut scelerisque ut, rutrum eu augue. Suspendisse velit nunc, malesuada et luctus non, scelerisque et turpis. Sed turpis ligula, gravida at vestibulum sed, semper ut felis. Fusce turpis diam, adipiscing commodo elementum ac, porttitor vitae mauris. Sed quis urna sed nulla tincidunt condimentum. Proin dignissim, sapien eget vulputate commodo, purus arcu porttitor mauris, ac sollicitudin diam leo et mauris.\',\r\n		\'Pellentesque nec velit in erat lacinia sagittis. Donec eu lacus non quam vulputate sollicitudin. Morbi sodales tempor diam, vel tincidunt felis feugiat vestibulum. Curabitur nec dapibus ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum neque sapien, aliquam quis interdum vel, dignissim in risus. Nulla nec est sed tortor pharetra tristique aliquet vel lacus. Nam at ipsum velit.\',\r\n		\'Nam cursus elit dignissim mi pharetra nec ullamcorper lectus tristique. Duis vitae lacus sit amet tellus venenatis faucibus at ut lectus. Etiam porttitor neque at eros laoreet sed pulvinar augue facilisis. Integer condimentum orci velit. Duis interdum metus sit amet sapien imperdiet at egestas nisi pretium. Nulla facilisi. Maecenas porta scelerisque magna non imperdiet. Nullam commodo ornare consequat. Phasellus pulvinar porttitor diam, quis convallis erat pharetra ac. Phasellus nec leo justo.\',\r\n		\'Donec congue venenatis erat at imperdiet. In egestas, lacus et pretium molestie, orci turpis convallis justo, consequat egestas ante felis nec magna. Aenean augue risus, bibendum ut auctor non, tincidunt commodo elit. Duis at metus vel urna ultrices pulvinar. Nullam at fermentum turpis. Aenean eget ornare lectus. Donec ac est at diam rutrum imperdiet. Maecenas sit amet placerat magna. Pellentesque eleifend iaculis sodales. Vivamus sodales justo vel erat euismod non lobortis enim suscipit. Integer hendrerit pharetra mauris id imperdiet. Ut consectetur elit eu lorem tempor viverra. In hac habitasse platea dictumst. In nec libero ut quam molestie venenatis. Sed et ultricies felis. Nullam dictum lorem in sem congue eget fringilla velit fermentum. \',\r\n	);\r\n\r\n	static private $section_names;\r\n\r\n	/**\r\n	 * Hook UI, setup privileges\r\n	 */\r\n	function __construct()\r\n	{\r\n		if (txpinterface == \'admin\') {\r\n			add_privs(\'wet_lorem_ipsum\', \'1,2\');\r\n			register_tab(\'content\', \'wet_lorem_ipsum\', gTxt(\'wet_lorem_ipsum\'));\r\n			register_callback(array(__CLASS__, \'ui\'), \'wet_lorem_ipsum\');\r\n\r\n			self::$section_names = safe_column(\'name\', \'txp_section\', \'name != \\\'default\\\'\');\r\n		}\r\n	}\r\n\r\n	/**\r\n	 * User interface\r\n	 */\r\n	static function ui()\r\n	{\r\n		global $step;\r\n\r\n		$message = \'\';\r\n		$ok = true;\r\n		$vars = array(\'articles\', \'sections\');\r\n\r\n		extract(psa($vars));\r\n\r\n		if (bouncer($step, array(\r\n			\'add_things\' => true\r\n		))) {\r\n			foreach ($vars as $var) {\r\n				if ($$var !== \'\') {\r\n					$$var = assert_int($$var);\r\n\r\n					// If amount is greater zero: Create new elements\r\n					if ($$var > 0) {\r\n						$f = \'add_\'.$var;\r\n						$ok *= self::$f($$var);\r\n					}\r\n					$message = ($ok) ? gTxt(\'ok\') : array(gTxt(\'nok\'), E_ERROR);\r\n				}\r\n			}\r\n		}\r\n\r\n		pagetop(gTxt(__CLASS__), $message);\r\n		$out[] = hed(gTxt(\'wet_lorem_ipsum\'), 2);\r\n		$out[] =  inputLabel(\'articles\', fInput(\'text\', \'articles\', $articles, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'articles\'), \'articles\');\r\n//	TODO	$out[] =  inputLabel(\'sections\', fInput(\'text\', \'sections\', $sections, \'\', \'\', \'\', INPUT_REGULAR, \'\', \'sections\'), \'sections\');\r\n\r\n		$out[] = graf(fInput(\'submit\', \'\', gTxt(\'save\'), \'publish\')).n.\r\n			eInput(__CLASS__).n.\r\n			sInput(\'add_things\');\r\n\r\n		echo form(\r\n			n.tag(join(\'\', $out).n, \'section\', array(\'class\' => \'txp-edit\'))\r\n			, \'\', \'\', \'post\', \'edit-form\', \'\', __CLASS__);\r\n	}\r\n\r\n	/**\r\n	 * Add new articles\r\n	 */\r\n	static function add_articles($amount)\r\n	{\r\n		global $txp_user;\r\n\r\n		@set_time_limit(0);\r\n		$textile = new \\Textpattern\\Textile\\Parser();\r\n\r\n		$user = safe_escape($txp_user);\r\n		$textile_body = $textile_excerpt = USE_TEXTILE;\r\n		$ok = true;\r\n\r\n		for ($i = 0; $i < $amount; $i++) {\r\n			$Title = safe_escape(__CLASS__ . \' \' . join(\' \', self::array_random(self::$words, mt_rand(2, count(self::$words)))));\r\n			$Body = join(n.n, self::array_random(self::$grafs, mt_rand(2, count(self::$grafs))));\r\n			$Body_html = safe_escape($textile->parse($Body));\r\n			$Body = safe_escape($Body);\r\n			$url_title = safe_escape(md5(uniqid(rand(), true)));\r\n			$Section = safe_escape(self::array_random(self::$section_names, 1));\r\n			$custom_1 = safe_escape(self::array_random(self::$words, 1));\r\n			$custom_2 = safe_escape(self::array_random(self::$words, 1));\r\n			$Keywords = safe_escape(join(\', \', self::array_random(self::$words, mt_rand(2, count(self::$words)))));\r\n\r\n			$ok *= safe_insert(\r\n				\"textpattern\",\r\n				\"Title           = \'$Title\',\r\n					Body            = \'$Body\',\r\n					Body_html       = \'$Body_html\',\r\n					Excerpt         = \'\',\r\n					Excerpt_html    = \'\',\r\n					Image           = \'\',\r\n					Keywords        = \'$Keywords\',\r\n					Status          =  \".STATUS_LIVE.\",\r\n					Posted          =  DATE_SUB(now(), INTERVAL \".mt_rand(0, 3600*24*7).\" SECOND),\r\n					Expires         =  NULL,\r\n					AuthorID        = \'$user\',\r\n					LastMod         =  DATE_SUB(now(), INTERVAL \".mt_rand(0, 3600*24*7).\" SECOND),\r\n					LastModID       = \'\',\r\n					Section         = \'$Section\',\r\n					Category1       = \'\',\r\n					Category2       = \'\',\r\n					textile_body    = \'$textile_body\',\r\n					textile_excerpt = \'$textile_excerpt\',\r\n					Annotate        =  0,\r\n					override_form   = \'\',\r\n					url_title       = \'$url_title\',\r\n					AnnotateInvite  = \'\',\r\n					custom_1        = \'$custom_1\',\r\n					custom_2        = \'$custom_2\',\r\n					uid             = \'\".md5(uniqid(rand(), true)).\"\',\r\n					feed_time       = now()\"\r\n			);\r\n\r\n		}\r\n\r\n		return $ok;\r\n	}\r\n\r\n	/**\r\n	 * Add new sections\r\n	 */\r\n	static function add_sections($amount)\r\n	{\r\n		return true;\r\n	}\r\n\r\n	private static function array_random($arr, $num = 1)\r\n	{\r\n		shuffle($arr);\r\n		return ($num == 1) ? $arr[0] : array_slice($arr, 0, $num);\r\n	}\r\n}\r\n\r\nnew wet_lorem_ipsum;\r\n', '1367e48f87faebe128ba39dcdb5025c1', '', '', 3, 5, 0);
INSERT INTO `txp_plugin` (`name`, `status`, `author`, `author_uri`, `version`, `description`, `help`, `code`, `code_restore`, `code_md5`, `textpack`, `data`, `type`, `load_order`, `flags`) VALUES
('cbe_rand_content', 0, 'Claire Brione', 'https://github.com/ClaireBrione/cbe_rand_content', '0.2.2', 'Generate mass (fake) articles and comments', '<p>&lt;div id=“cbe-plugin-help”&gt;</p>\n\n<p>&lt;h1&gt;cbe_rand_content&lt;/h1&gt;</p>\n\n<p>&lt;p&gt;A minimalist, but functional admin-side plugin to populate your content tables.&lt;/p&gt;</p>\n\n<p>&lt;h2&gt;Table of contents&lt;/h2&gt;</p>\n\n<p>&lt;ul&gt;\n  &lt;li&gt;&lt;a href=”#features”&gt;Features&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=”#requirements”&gt;Plugin requirements&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=”#install-supp”&gt;Installation and support&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=”#usage”&gt;Usage&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=”#changelog”&gt;Changelog&lt;/a&gt;&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;h2 id=“features”&gt;Features&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;If you need content to test your front-end, this plugin will generate random articles and comments for you.&lt;/p&gt;<br />\n&lt;p&gt;One trouble, though: they are written in Martian, with sometimes a very little bit of lorem ipsum. But even on Mars, sentences begin with an uppercase and end with a period, names begin with an uppercase.&lt;/p&gt;<br />\n&lt;p&gt;Chain articles and their comments generation (already existing articles will be left untouched).&lt;/p&gt;<br />\n&lt;p&gt;Rules for articles and comments are customizable in the functions &lt;code&gt;_cbe_rndc_pop_art&lt;/code&gt; and &lt;code&gt;_cbe_rndc_pop_com&lt;/code&gt;:&lt;/p&gt;<br />\n&lt;p&gt;Rules for articles:&lt;/p&gt;</p>\n\n<p>&lt;ul&gt;\n  &lt;li&gt;between 10 and 15 articles are generated at a time&lt;/li&gt;\n  &lt;li&gt;the probability for an article to be live is 8 over 11 (nearly 72%)&lt;/li&gt;\n  &lt;li&gt;publish date is somewhere between 300 days ago and 60 days ahead&lt;/li&gt;\n  &lt;li&gt;sometimes, expiration date is a few months after publish date&lt;/li&gt;\n  &lt;li&gt;sometimes, an article has no category2&lt;/li&gt;\n  &lt;li&gt;title: 3 to 5 words, 3 to 6 characters each word&lt;/li&gt;\n  &lt;li&gt;excerpt: 1 paragraph, 6 to 10 words,  2 to 8 characters each word&lt;/li&gt;\n  &lt;li&gt;body: 2 to 5 paragraphs, 6 to 8 sentences each paragraph,  5 to 10 words each paragraph, 2 to 8 characters each word&lt;/li&gt;\n  &lt;li&gt;keywords and custom fields are &lt;b&gt;not&lt;/b&gt; populated&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;p&gt;Rules for comments:&lt;/p&gt;</p>\n\n<p>&lt;ul&gt;\n  &lt;li&gt;generation will fail if comments are not used&lt;/li&gt;\n  &lt;li&gt;only for non-expired live articles&lt;/li&gt;\n  &lt;li&gt;sometimes, a non-expired live article won&#8217;t receive any comment&lt;/li&gt;\n  &lt;li&gt;between 3 and 10 comments are generated for each article&lt;/li&gt;\n  &lt;li&gt;per comment: 2 to 5 paragraphs, 6 to 10 words each paragraph,  2 to 8 characters each word&lt;/li&gt;\n  &lt;li&gt;name: 2 words, 4 to 7 characters and 4 to 9 characters&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;h2 id=“requirements”&gt;Plugin requirements&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;Developed and tested with Textpattern 4.5.4.&lt;/p&gt;</p>\n\n<p>&lt;h2 id=“install-supp”&gt;Installation and support&lt;/h2&gt;</p>\n\n<p>&lt;ul&gt;\n  &lt;li&gt;Copy/paste in the Admin &gt; Plugins tab to install or uninstall, activate or desactivate.&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;h2 id=“usage”&gt;Usage&lt;/h2&gt;</p>\n\n<p>&lt;p&gt;Although there is no risk of damage, it is strongly suggested to export your &lt;code&gt;textpattern&lt;/code&gt; and &lt;code&gt;txp_discuss&lt;/code&gt; tables first.&lt;/p&gt;<br />\n&lt;p&gt;You will find the item “Random content” in the Extensions tab. Then, choose articles or comments generation.&lt;/p&gt;<br />\n&lt;p&gt;A list if the new IDs will be displayed when the process is complete.&lt;/p&gt;<br />\n&lt;p&gt;You can run it as many times as you wish.&lt;/p&gt;</p>\n\n<p>&lt;h2 id=“changelog”&gt;Changelog&lt;/h2&gt;</p>\n\n<p>&lt;ul&gt;\n  &lt;li&gt;25 May 13 &#8211; 0.1 &#8211; Initial release&lt;/li&gt;\n  &lt;li&gt;21 Jul 15 &#8211; 0.2 &#8211; Fixed &lt;a href=“http://forum.textpattern.com/viewtopic.php?pid=293584#p293584”&gt;fatal error&lt;/a&gt; and preparing &lt;code&gt;_cbe_rndc_init()&lt;/code&gt; and &lt;code&gt;_cbe_rndc_reinit()&lt;/code&gt; if, one day&#8230;&lt;/li&gt;<br />\n&lt;/ul&gt;</p>\n\n<p>&lt;/div&gt;</p>', '/**\n * cbe_rand_content - Generate mass (fake) articles and comments\n *\n * 0.1 - 25 May 2013 - Initial release\n * 0.2 - 21 Jul 2015 - http://forum.textpattern.com/viewtopic.php?pid=293584#p293584\n *                     preparing _cbe_rndc_init() and _cbe_rndc_reinit() if, one day...\n *\n * @author  Claire Brione\n * @link    http://www.clairebrione.com/\n * @version 0.2\n */\n\n/* =========================== Constants ============================ */\ndefine( \'CBE_RNDC_VERSION\', \'0.2\' ) ;               // Current version\ndefine( \'CBE_RNDC_EVENT\'  , \'cbe_rand_content\' ) ;  // This event\'s name\ndefine( \'CBE_RNDC_SPFX\'   , \'cbe_rndc_\'  ) ;        // Internal short prefix\ndefine( \'CBE_RNDC_LPFX\'   , CBE_RNDC_EVENT.\'_\' ) ;  // Internal long prefix\n\nif( @txpinterface == \'admin\' ) {\n\n/**\n * _cbe_rndc_texts - Admin-side: language strings definitions\n *\n * @return  array\n */\n  function _cbe_rndc_texts()\n  {\n\n/* ============== Possible language customisation here ============== */\n      return( array( CBE_RNDC_LPFX.\'tab_label\'       => \'Random content\'            // \'Contenu aléatoire\'\n                   , CBE_RNDC_LPFX.\'pop_com\'         => \'Comments generation\'       // \'Génération de commentaires\'\n                   , CBE_RNDC_LPFX.\'pop_art\'         => \'Articles generation\'       // \'Génération d\'articles\'\n                   , CBE_RNDC_LPFX.\'go_back\'         => \'Back\'                      // \'Retour\'\n                   , CBE_RNDC_LPFX.\'no_comm_allowed\' => \'Comments are not allowed\'  // \'Les commentaires ne sont pas autorisés\'\n                   , CBE_RNDC_LPFX.\'populate\'        => \'Populate !\'                // \'Générer !\'\n                   , CBE_RNDC_LPFX.\'populate_end\'    => \'Populating finished\'       // \'Génération terminée\'\n                   , CBE_RNDC_LPFX.\'with_errors\'     => \'with errors\'               // \'avec des erreurs\'\n            )      ) ;\n/* =========================== Stop editing =========================== */\n\n  }\n\n  global $textarray ;\n  $textarray += call_user_func( \'_\'.CBE_RNDC_SPFX.\'texts\' ) ;\n  add_privs( CBE_RNDC_EVENT, \'1, 2\' ) ;\n  register_tab( \'extensions\', CBE_RNDC_EVENT, gTxt( CBE_RNDC_LPFX.\'tab_label\' ) ) ;\n  register_callback( CBE_RNDC_LPFX.\'lifecycle\', \'plugin_lifecycle.\'.CBE_RNDC_EVENT ) ;\n  register_callback( CBE_RNDC_LPFX.\'router\' , CBE_RNDC_EVENT ) ;\n\n/* ============================ Internal ============================ */\ndefine( \'DIGITS\'    , 0 ) ;\ndefine( \'ALPHAMAJUS\', 1 ) ;\ndefine( \'ALPHAMINUS\', 2 ) ;\n\nfunction _cbe_rndc_minimax( &$mini, &$maxi )\n{\n	if( $maxi == 0 )\n		$maxi = $mini ;\n\n	if( $maxi < $mini ) {\n		$a    = $mini ;\n		$mini = $maxi ;\n		$maxi = $a    ;\n	}\n}\n\n/**\n *\n * @param  string $randword csv list of words to choose from\n * @param  int    $alphanum type\n *\n * @return string           random word\n */\nfunction _cbe_rndc( $randword, $alphanum, $long_min, $long_max = 0 )\n{\n	$aCars = array( DIGITS     => \"0123456789\"\n		      , ALPHAMAJUS => \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n		      , ALPHAMINUS => \"aabcdeefghiijkalmenoopqrstuuvwaxyz\"\n		      ) ;\n\n	if( ! in_array( $alphanum, array( DIGITS, ALPHAMAJUS, ALPHAMINUS ) ) )\n		return ;\n\n	if( $randword !== false && rand( 1, 100 ) == 50 ) {\n		$aWords = explode( \",\", $randword ) ;\n		$word   = $aWords[ array_rand( $aWords ) ] ;\n		return( $word ) ;\n	}\n\n	_cbe_rndc_minimax( $long_min, $long_max ) ;\n\n	$long_fin = rand( $long_min, $long_max ) ;\n	$randmax = strlen( $aCars[ $alphanum ] ) - 1 ;\n\n	$word = \'\' ;\n	for( $k = 0 ; $k < $long_fin ; $k++ )\n		$word .= $aCars[ $alphanum ][ rand( 0, $randmax ) ] ;\n\n	if( $alphanum == DIGITS && $word[0] == \'0\' )\n		$word[0] = $aCars[ $alphanum ][ rand( 1, $randmax ) ] ;\n\n	return( $word ) ;\n}\n\nfunction _cbe_rndc_name( $long_min, $long_max = 0 )\n{\n	_cbe_rndc_minimax( $long_min, $long_max ) ;\n	$out = array() ;\n	$out[] = _cbe_rndc( false, ALPHAMAJUS, 1 ) ;\n	$out[] = _cbe_rndc( false, ALPHAMINUS, $long_min-1, $long_max-1 ) ;\n	return( join( \"\", $out ) ) ;\n}\n\nfunction _cbe_rndc_sentence( $word_min = 7, $word_max = 0, $char_min = 5, $char_max = 0 )\n{\n	_cbe_rndc_minimax( $word_min, $word_max ) ;\n	_cbe_rndc_minimax( $char_min, $char_max ) ;\n	$out = array() ;\n	$mots = rand( $word_min, $word_max ) ;\n	for( $i = 0 ; $i < $mots ; $i++ ) {\n		$out[] = _cbe_rndc( \'lorem,ipsum,dolor\', ALPHAMINUS, $char_min, $char_max ) ;\n	}\n	return( _cbe_rndc( false, ALPHAMAJUS, 1 ) . join( \" \", $out ) . \'.\' ) ;\n}\n\nfunction _cbe_rndc_text( $sent_min = 5, $sent_max = 0, $word_min = 7, $word_max = 0 )\n{\n	_cbe_rndc_minimax( $sent_min, $sent_max ) ;\n	_cbe_rndc_minimax( $word_min, $word_max ) ;\n	$out = array() ;\n	$phrases = rand( $sent_min, $sent_max ) ;\n	for( $i = 0 ; $i < $phrases ; $i++ ) {\n		$out[] = _cbe_rndc_sentence( $word_min, $word_max, 2, 8 ) ;\n	}\n	return( join( \" \", $out ) ) ;\n}\n/* =========================== / Internal =========================== */\n\n/* =================== Plugin\'s lifecycle related =================== */\n/**\n * _cbe_rndc_init - Admin-side: plugin table creation, prefs insertion\n *\n * @access private\n */\n  function _cbe_rndc_init()\n  {\n      // Nothing at the moment\n      return ;\n  }\n\n/**\n * _cbe_rndc_reinit - Admin-side: plugin table update, prefs update\n *\n * @access private\n */\n  function _cbe_rndc_reinit()\n  {\n      // Nothing at the moment\n      return ;\n  }\n\n/**\n * cbe_rndc_lifecycle_installed - Admin-side: fires when plugin is installed\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_installed()\n  {\n      $registered_version = get_pref( CBE_RNDC_LPFX.\'version\', \'none\' ) ;\n\n      if( $registered_version === CBE_RNDC_VERSION ) {\n          return ;\n\n      } elseif( $registered_version === \'none\' ) {\n          _cbe_rndc_init() ;\n\n      } else {\n          _cbe_rndc_reinit() ;\n      }\n\n      set_pref( CBE_RNDC_LPFX.\'version\', CBE_RNDC_VERSION, CBE_RNDC_EVENT, PREF_HIDDEN, \'\' ) ;\n\n      return ;\n  }\n\n/**\n * cbe_rndc_lifecycle_enabled - Admin-side: fires when plugin is enabled\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_enabled()\n  {\n      return( cbe_rndc_lifecycle_installed() ) ;\n  }\n\n/**\n * cbe_rndc_lifecycle_deleted - Admin-side: fires when plugin is deleted\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_deleted()\n  {\n      safe_delete( \'txp_prefs\', \"event=\'\".CBE_RNDC_EVENT.\"\'\" ) ;\n      return ;\n  }\n\n/**\n * cbe_rand_content_lifecycle - Admin-side: plugin lifecycle\n *\n * @param   string $event admin event\n * @param   string $step  admin step\n * @return  void\n */\n  function cbe_rand_content_lifecycle( $event = \'\', $step = \'\' )\n  {\n      if( $step && is_callable( $func = CBE_RNDC_SPFX.\"lifecycle_$step\" ) )\n          return( $func() ) ;\n\n      return ;\n  }\n\n/* =================== Tab \'Extensions\' ===================== */\n/**\n * _cbe_rndc_initiate - Admin-side: First screen\n *\n * @return  array\n */\n  function _cbe_rndc_initiate( &$message, &$html )\n  {\n      global $event ;\n      $next_step = NULL ;\n      $out = array() ;\n\n      $out[] = form( hed( gTxt( CBE_RNDC_LPFX.\'pop_art\' ), 2 )\n                   .n. tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'populate\' ), \'smallerbox\' )\n                          .n. sInput( CBE_RNDC_SPFX.\'pop_art\' )\n                          .n. eInput( $event )\n                          , \'div\' )\n                   , \'\', \"verify(\'\".gTxt(\'are_you_sure\').\"\')\" ) ;\n\n      $out[] = form( hed( gTxt( CBE_RNDC_LPFX.\'pop_com\' ), 2 )\n                   .n. tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'populate\' ), \'smallerbox\' )\n                          .n. sInput( CBE_RNDC_SPFX.\'pop_com\' )\n                          .n. eInput( $event )\n                          , \'div\' )\n                   , \'\', \"verify(\'\".gTxt(\'are_you_sure\').\"\')\" ) ;\n\n      $html = join( \'<hr />\', $out ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * _cbe_rndc_pop_art - Admin-side: Generate articles\n *\n * See \"Rules for articles\" in the helpfile\n *\n * @return  array\n */\n  function _cbe_rndc_pop_art( &$message, &$html )\n  {\n      global $event, $comments_on_default, $comments_default_invite ;\n      $next_step = NULL ;\n      include_once txpath.\'/lib/classTextile.php\';\n      $out = array() ;\n      $globerrlevel = \'\' ;\n      $message      = gTxt( CBE_RNDC_LPFX.\'populate_end\' ) ;\n      if( ($use_textile = get_pref( \'use_textile\' )) == USE_TEXTILE ) {\n          include_once txpath.\'/lib/classTextile.php\';\n      }\n\n      $authors     = safe_column_num( \'name\', \'txp_users\', \"`privs`<6\" ) ;\n      $posauthor   = count( $authors ) - 1 ;\n\n      $sections    = safe_column_num( \'name\', \'txp_section\', \"`on_frontpage`=1 AND `name`!=\'default\'\" ) ;\n      $possection  = count( $sections ) - 1 ;\n\n      $categories  = safe_column_num( \'name\', \'txp_category\', \" `name`!=\'root\' AND `type`=\'article\'\" ) ;\n      $poscategory = count( $categories ) - 1 ;\n\n      $stati = array( STATUS_LIVE, STATUS_LIVE\n                    , STATUS_DRAFT\n                    , STATUS_LIVE, STATUS_LIVE\n                    , STATUS_HIDDEN\n                    , STATUS_LIVE, STATUS_LIVE\n                    , STATUS_PENDING\n                    , STATUS_LIVE, STATUS_LIVE\n                    ) ;\n      $posstatus = count( $stati ) - 1 ;\n\n      $rndnb =  rand( 10, 15 ) ;\n      $aAids = array() ;\n      $errlevel = \"success\" ;\n\n      for( $i = 0 ; $i < $rndnb ; $i++ ) {\n\n          $seeddate  = rand( time()-(300*24*60*60), time()+(60*24*60*60) ) ;\n          $in        = rand( 0, 9 ) ;\n          $status    = $stati[ rand( 0, $posstatus ) ] ;\n          $published = date( \"Y-m-d H:i:s\", $seeddate ) ;\n          $lastmod   = date( \"Y-m-d H:i:s\" ) ;\n          $expires   = ( in_array( $in, array( 0, 4, 8 ) ) ) ? \'null\' : \"\'\".date( \"Y-m-d H:i:s\", strtotime( \"+{$in} months\", $seeddate ) ).\"\'\";\n          $feeddate  = date( \"Y-m-d\", $seeddate ) ;\n          $author    = $authors[ rand( 0, $posauthor ) ] ;\n          $section   = $sections[ rand( 0, $possection ) ] ;\n          $category1 = $categories[ rand( 0, $poscategory ) ] ;\n          if( ($category2 = $in == 0 ? \'\' : $categories[ rand( 0, $poscategory ) ]) == $category1 ) {\n              $category2 = \'\' ;\n          }\n          $title     = substr( _cbe_rndc_sentence( 3, 5, 3, 6 ), 0, -1 ) ;\n          $url_title = stripSpace( $title, 1 ) ;\n\n          $excerpt   = _cbe_rndc_text( 6, 10, 2, 8 ) ;\n          $arrbody = array() ;\n          $parag = rand( 2, 5 ) ;\n          for( $j = 0 ; $j < $parag ; $j++ ) {\n              $arrbody[] .= _cbe_rndc_text( 6, 8, 5, 10 ) . n ;\n          }\n          $body = join( n, $arrbody ) ;\n\n          switch( $use_textile ) {\n          case USE_TEXTILE :\n              $textile = new Textile( get_pref( \'doctype\' ) ) ;\n              $body_html    = $textile -> parse( $body ) ;\n              $excerpt_html = $textile -> parse( $excerpt ) ;\n              break ;\n\n          case LEAVE_TEXT_UNTOUCHED :\n              $body_html    = trim( $body ) ;\n              $excerpt_html = trim( $excerpt ) ;\n              break ;\n\n          case CONVERT_LINEBREAKS :\n              $body_html    = nl2br( trim( $body ) ) ;\n              $excerpt_html = nl2br( trim( $excerpt ) ) ;\n              break ;\n\n          default :\n              break ;\n          }\n\n          if( $insertd = safe_insert( \"textpattern\",\n                                      \"Title           = \'$title\',\n                                       Body            = \'$body\',\n                                       Body_html       = \'$body_html\',\n                                       Excerpt         = \'$excerpt\',\n                                       Excerpt_html    = \'$excerpt_html\',\n                                       Status          = \'$status\',\n                                       Posted          = \'$published\',\n                                       Expires         = $expires,\n                                       AuthorID        = \'$author\',\n                                       LastMod         = \'$lastmod\',\n                                       LastModID       = \'$author\',\n                                       Section         = \'$section\',\n                                       Category1       = \'$category1\',\n                                       Category2       = \'$category2\',\n                                       textile_body    =  $use_textile,\n                                       textile_excerpt =  $use_textile,\n                                       Annotate        =  $comments_on_default,\n                                       url_title       = \'\".doSlash($url_title).\"\',\n                                       AnnotateInvite  = \'$comments_default_invite\',\n                                       uid             = \'\".md5(uniqid(rand(),true)).\"\',\n                                       feed_time       = \'$feeddate\'\"\n                                     ) ) {\n              $aAids[] = $insertd ;\n\n          } else {\n              $errlevel     = \"warning\" ;\n              $globerrlevel = E_ERROR   ;\n          }\n      }\n\n      $out[] = graf( tag( gTxt( CBE_RNDC_LPFX.\'populate_end\' ), \'span\', \' class=\"\'.$errlevel.\'\"\' )\n                   . \': \' . join( \", \", $aAids ) ) ;\n\n      if( ! empty( $globerrlevel ) )\n          $message .= \' \'. gTxt( CBE_RNDC_LPFX.\'with_errors\' ) ;\n\n      $back  = tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'go_back\' ), \'publish\')\n                    .n. sInput( CBE_RNDC_SPFX.\'initiate\' )\n                    .n. eInput( $event )\n                  , \'div\' ) ;\n\n      $html    = join( n, $out ) . form( $back ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * _cbe_rndc_pop_com - Admin-side: Generate comments\n *\n * See \"Rules for comments\" in the helpfile\n *\n * @return  array\n */\n  function _cbe_rndc_pop_com( &$message, &$html )\n  {\n      global $event, $use_comments, $comments_disabled_after ;\n      $next_step = NULL ;\n      $out = array() ;\n      $globerrlevel = \'\' ;\n      $aIds = safe_rows( \"ID, Title, unix_timestamp(Posted) as uPosted\", \"textpattern\"\n                       , \"`Posted`<=now() AND\n                          (`Expires`>now() OR `Expires` IS NULL) AND\n                          `Status`=\".STATUS_LIVE.\" AND\n                          `Annotate`=\'1\'\n                         ORDER BY ID\" ) ;\n\n      if( ! $use_comments ) {\n          $globerrlevel = E_ERROR   ;\n          $message      = gTxt( CBE_RNDC_LPFX.\'no_comm_allowed\' ) ;\n\n      } else {\n          $message = gTxt( CBE_RNDC_LPFX.\'populate_end\' ) ;\n          if( ($lifespan = $comments_disabled_after * 86400) > 0 ) {\n              array_walk( $aIds\n                        , create_function( \'&$v, $k, $p\', \'$v[ \"ID\" ] = (time()-$v[\"uPosted\"] < $p) ? $v[\"ID\"] : false ;\' )\n                        , $lifespan ) ;\n              $aIds = array_filter( $aIds, create_function( \'$v\', \'return( $v[ \"ID\" ] ) ;\' ) ) ;\n          }\n\n          foreach( $aIds as $article ) {\n              if( rand( 0, 99 ) == 50 )\n                  continue ;\n\n              $rndnb = rand( 3, 10 ) ;\n              $aCids = array() ;\n              $errlevel = \"success\" ;\n\n              for( $i = 0 ; $i < $rndnb ; $i++ ) {\n\n                  $comm = \'\' ;\n                  $parag = rand( 2, 5 ) ;\n                  for( $j = 0 ; $j < $parag ; $j++ ) {\n                      $comm .= \'<p>\' . _cbe_rndc_text( 6, 10, 2, 8 ) . \'</p>\' ;\n                  }\n\n                  if( $insertd = safe_insert( \"txp_discuss\"\n                                            , \"`parentid`=\'{$article[\'ID\']}\',\n                                               `name`=\'\"     . doSlash( _cbe_rndc_name( 4, 7 ).\' \'._cbe_rndc_name( 4, 9 ) ) . \"\',\n                                               `email`=\'\"    . doSlash( _cbe_rndc( false, ALPHAMINUS, 4, 7 ).\'@\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 4, 9 ).\'.\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 3 ) )\n                                                             . \"\',\n                                               `web`=\'\"      . doSlash( \'http://\'._cbe_rndc( false, ALPHAMINUS, 4, 7 ).\'.\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 3 ) )\n                                                             . \"\',\n                                               `message`=\'\"  . doSlash( $comm ) . \"\',\n                                               `posted`=\'\"   . doSlash( date( \"Y-m-d H:i:s\", rand( $article[\"uPosted\"], time() ) ) )\n                                                             . \"\'\"\n                                            ) ) {\n                      $aCids[] = $insertd ;\n\n                  } else {\n                      $errlevel     = \"warning\" ;\n                      $globerrlevel = E_ERROR   ;\n                  }\n              }\n\n              update_comments_count( $article[ \"ID\" ] ) ;\n\n              $out[] = graf( tag( $article[\'Title\'], \'span\', \' class=\"\'.$errlevel.\'\"\' ) . \': \' . join( \", \", $aCids ) ) ;\n          }\n\n          if( ! empty( $globerrlevel ) ) {\n              $message .= \' \'. gTxt( CBE_RNDC_LPFX.\'with_errors\' ) ;\n          }\n      }\n\n      $back = tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'go_back\' ), \'publish\')\n                    .n. sInput( CBE_RNDC_SPFX.\'initiate\' )\n                    .n. eInput( $event )\n                  , \'div\' ) ;\n\n      $html = join( n, $out ) . form( $back ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * cbe_rand_content_router - Admin-side: Entry point\n *\n * @param   string $event admin event\n * @param   string $step  admin step\n * @return  void\n */\n  function cbe_rand_content_router( $event, $step )\n  {\n      global $step, $event ;\n      require_privs( $event ) ;\n\n      ${CBE_RNDC_SPFX.\'message\'} = \'\' ;\n      ${CBE_RNDC_SPFX.\'html\'}    = \'\' ;\n\n      $available_steps = array( CBE_RNDC_SPFX.\'initiate\' => false\n                              , CBE_RNDC_SPFX.\'pop_art\'  => true\n                              , CBE_RNDC_SPFX.\'pop_com\'  => true\n                              ) ;\n\n      if( ! $step ) {\n          $step = CBE_RNDC_SPFX.\'initiate\' ;\n      }\n\n      while( $step && bouncer( $step, $available_steps ) && is_callable( $func = \"_$step\" ) ) {\n          $step = $func( ${CBE_RNDC_SPFX.\'message\'}, ${CBE_RNDC_SPFX.\'html\'} ) ;\n      }\n\n      pagetop( gTxt( CBE_RNDC_LPFX.\'tab_label\' ), ${CBE_RNDC_SPFX.\'message\'} ) ;\n\n      echo ( hed( gTxt( CBE_RNDC_LPFX.\'tab_label\' ), \'1\', \' class=\"txp-heading\"\' )\n           . tag( ${CBE_RNDC_SPFX.\'html\'}, \'div\', \' class=\"text-column\"\' )\n           ) ;\n\n      return ;\n  }\n} // @txpinterface == \'admin\'\n\nelseif( defined( \'txpinterface\' ) ) {\n\n  return ;\n\n} // @txpinterface == \'public|css\'\n\nelse {\n\n  return ;\n\n} // txpinterface undef', '/**\n * cbe_rand_content - Generate mass (fake) articles and comments\n *\n * 0.1 - 25 May 2013 - Initial release\n * 0.2 - 21 Jul 2015 - http://forum.textpattern.com/viewtopic.php?pid=293584#p293584\n *                     preparing _cbe_rndc_init() and _cbe_rndc_reinit() if, one day...\n *\n * @author  Claire Brione\n * @link    http://www.clairebrione.com/\n * @version 0.2\n */\n\n/* =========================== Constants ============================ */\ndefine( \'CBE_RNDC_VERSION\', \'0.2\' ) ;               // Current version\ndefine( \'CBE_RNDC_EVENT\'  , \'cbe_rand_content\' ) ;  // This event\'s name\ndefine( \'CBE_RNDC_SPFX\'   , \'cbe_rndc_\'  ) ;        // Internal short prefix\ndefine( \'CBE_RNDC_LPFX\'   , CBE_RNDC_EVENT.\'_\' ) ;  // Internal long prefix\n\nif( @txpinterface == \'admin\' ) {\n\n/**\n * _cbe_rndc_texts - Admin-side: language strings definitions\n *\n * @return  array\n */\n  function _cbe_rndc_texts()\n  {\n\n/* ============== Possible language customisation here ============== */\n      return( array( CBE_RNDC_LPFX.\'tab_label\'       => \'Random content\'            // \'Contenu aléatoire\'\n                   , CBE_RNDC_LPFX.\'pop_com\'         => \'Comments generation\'       // \'Génération de commentaires\'\n                   , CBE_RNDC_LPFX.\'pop_art\'         => \'Articles generation\'       // \'Génération d\'articles\'\n                   , CBE_RNDC_LPFX.\'go_back\'         => \'Back\'                      // \'Retour\'\n                   , CBE_RNDC_LPFX.\'no_comm_allowed\' => \'Comments are not allowed\'  // \'Les commentaires ne sont pas autorisés\'\n                   , CBE_RNDC_LPFX.\'populate\'        => \'Populate !\'                // \'Générer !\'\n                   , CBE_RNDC_LPFX.\'populate_end\'    => \'Populating finished\'       // \'Génération terminée\'\n                   , CBE_RNDC_LPFX.\'with_errors\'     => \'with errors\'               // \'avec des erreurs\'\n            )      ) ;\n/* =========================== Stop editing =========================== */\n\n  }\n\n  global $textarray ;\n  $textarray += call_user_func( \'_\'.CBE_RNDC_SPFX.\'texts\' ) ;\n  add_privs( CBE_RNDC_EVENT, \'1, 2\' ) ;\n  register_tab( \'extensions\', CBE_RNDC_EVENT, gTxt( CBE_RNDC_LPFX.\'tab_label\' ) ) ;\n  register_callback( CBE_RNDC_LPFX.\'lifecycle\', \'plugin_lifecycle.\'.CBE_RNDC_EVENT ) ;\n  register_callback( CBE_RNDC_LPFX.\'router\' , CBE_RNDC_EVENT ) ;\n\n/* ============================ Internal ============================ */\ndefine( \'DIGITS\'    , 0 ) ;\ndefine( \'ALPHAMAJUS\', 1 ) ;\ndefine( \'ALPHAMINUS\', 2 ) ;\n\nfunction _cbe_rndc_minimax( &$mini, &$maxi )\n{\n	if( $maxi == 0 )\n		$maxi = $mini ;\n\n	if( $maxi < $mini ) {\n		$a    = $mini ;\n		$mini = $maxi ;\n		$maxi = $a    ;\n	}\n}\n\n/**\n *\n * @param  string $randword csv list of words to choose from\n * @param  int    $alphanum type\n *\n * @return string           random word\n */\nfunction _cbe_rndc( $randword, $alphanum, $long_min, $long_max = 0 )\n{\n	$aCars = array( DIGITS     => \"0123456789\"\n		      , ALPHAMAJUS => \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n		      , ALPHAMINUS => \"aabcdeefghiijkalmenoopqrstuuvwaxyz\"\n		      ) ;\n\n	if( ! in_array( $alphanum, array( DIGITS, ALPHAMAJUS, ALPHAMINUS ) ) )\n		return ;\n\n	if( $randword !== false && rand( 1, 100 ) == 50 ) {\n		$aWords = explode( \",\", $randword ) ;\n		$word   = $aWords[ array_rand( $aWords ) ] ;\n		return( $word ) ;\n	}\n\n	_cbe_rndc_minimax( $long_min, $long_max ) ;\n\n	$long_fin = rand( $long_min, $long_max ) ;\n	$randmax = strlen( $aCars[ $alphanum ] ) - 1 ;\n\n	$word = \'\' ;\n	for( $k = 0 ; $k < $long_fin ; $k++ )\n		$word .= $aCars[ $alphanum ][ rand( 0, $randmax ) ] ;\n\n	if( $alphanum == DIGITS && $word[0] == \'0\' )\n		$word[0] = $aCars[ $alphanum ][ rand( 1, $randmax ) ] ;\n\n	return( $word ) ;\n}\n\nfunction _cbe_rndc_name( $long_min, $long_max = 0 )\n{\n	_cbe_rndc_minimax( $long_min, $long_max ) ;\n	$out = array() ;\n	$out[] = _cbe_rndc( false, ALPHAMAJUS, 1 ) ;\n	$out[] = _cbe_rndc( false, ALPHAMINUS, $long_min-1, $long_max-1 ) ;\n	return( join( \"\", $out ) ) ;\n}\n\nfunction _cbe_rndc_sentence( $word_min = 7, $word_max = 0, $char_min = 5, $char_max = 0 )\n{\n	_cbe_rndc_minimax( $word_min, $word_max ) ;\n	_cbe_rndc_minimax( $char_min, $char_max ) ;\n	$out = array() ;\n	$mots = rand( $word_min, $word_max ) ;\n	for( $i = 0 ; $i < $mots ; $i++ ) {\n		$out[] = _cbe_rndc( \'lorem,ipsum,dolor\', ALPHAMINUS, $char_min, $char_max ) ;\n	}\n	return( _cbe_rndc( false, ALPHAMAJUS, 1 ) . join( \" \", $out ) . \'.\' ) ;\n}\n\nfunction _cbe_rndc_text( $sent_min = 5, $sent_max = 0, $word_min = 7, $word_max = 0 )\n{\n	_cbe_rndc_minimax( $sent_min, $sent_max ) ;\n	_cbe_rndc_minimax( $word_min, $word_max ) ;\n	$out = array() ;\n	$phrases = rand( $sent_min, $sent_max ) ;\n	for( $i = 0 ; $i < $phrases ; $i++ ) {\n		$out[] = _cbe_rndc_sentence( $word_min, $word_max, 2, 8 ) ;\n	}\n	return( join( \" \", $out ) ) ;\n}\n/* =========================== / Internal =========================== */\n\n/* =================== Plugin\'s lifecycle related =================== */\n/**\n * _cbe_rndc_init - Admin-side: plugin table creation, prefs insertion\n *\n * @access private\n */\n  function _cbe_rndc_init()\n  {\n      // Nothing at the moment\n      return ;\n  }\n\n/**\n * _cbe_rndc_reinit - Admin-side: plugin table update, prefs update\n *\n * @access private\n */\n  function _cbe_rndc_reinit()\n  {\n      // Nothing at the moment\n      return ;\n  }\n\n/**\n * cbe_rndc_lifecycle_installed - Admin-side: fires when plugin is installed\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_installed()\n  {\n      $registered_version = get_pref( CBE_RNDC_LPFX.\'version\', \'none\' ) ;\n\n      if( $registered_version === CBE_RNDC_VERSION ) {\n          return ;\n\n      } elseif( $registered_version === \'none\' ) {\n          _cbe_rndc_init() ;\n\n      } else {\n          _cbe_rndc_reinit() ;\n      }\n\n      set_pref( CBE_RNDC_LPFX.\'version\', CBE_RNDC_VERSION, CBE_RNDC_EVENT, PREF_HIDDEN, \'\' ) ;\n\n      return ;\n  }\n\n/**\n * cbe_rndc_lifecycle_enabled - Admin-side: fires when plugin is enabled\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_enabled()\n  {\n      return( cbe_rndc_lifecycle_installed() ) ;\n  }\n\n/**\n * cbe_rndc_lifecycle_deleted - Admin-side: fires when plugin is deleted\n *\n * @return  void\n */\n  function cbe_rndc_lifecycle_deleted()\n  {\n      safe_delete( \'txp_prefs\', \"event=\'\".CBE_RNDC_EVENT.\"\'\" ) ;\n      return ;\n  }\n\n/**\n * cbe_rand_content_lifecycle - Admin-side: plugin lifecycle\n *\n * @param   string $event admin event\n * @param   string $step  admin step\n * @return  void\n */\n  function cbe_rand_content_lifecycle( $event = \'\', $step = \'\' )\n  {\n      if( $step && is_callable( $func = CBE_RNDC_SPFX.\"lifecycle_$step\" ) )\n          return( $func() ) ;\n\n      return ;\n  }\n\n/* =================== Tab \'Extensions\' ===================== */\n/**\n * _cbe_rndc_initiate - Admin-side: First screen\n *\n * @return  array\n */\n  function _cbe_rndc_initiate( &$message, &$html )\n  {\n      global $event ;\n      $next_step = NULL ;\n      $out = array() ;\n\n      $out[] = form( hed( gTxt( CBE_RNDC_LPFX.\'pop_art\' ), 2 )\n                   .n. tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'populate\' ), \'smallerbox\' )\n                          .n. sInput( CBE_RNDC_SPFX.\'pop_art\' )\n                          .n. eInput( $event )\n                          , \'div\' )\n                   , \'\', \"verify(\'\".gTxt(\'are_you_sure\').\"\')\" ) ;\n\n      $out[] = form( hed( gTxt( CBE_RNDC_LPFX.\'pop_com\' ), 2 )\n                   .n. tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'populate\' ), \'smallerbox\' )\n                          .n. sInput( CBE_RNDC_SPFX.\'pop_com\' )\n                          .n. eInput( $event )\n                          , \'div\' )\n                   , \'\', \"verify(\'\".gTxt(\'are_you_sure\').\"\')\" ) ;\n\n      $html = join( \'<hr />\', $out ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * _cbe_rndc_pop_art - Admin-side: Generate articles\n *\n * See \"Rules for articles\" in the helpfile\n *\n * @return  array\n */\n  function _cbe_rndc_pop_art( &$message, &$html )\n  {\n      global $event, $comments_on_default, $comments_default_invite ;\n      $next_step = NULL ;\n      include_once txpath.\'/lib/classTextile.php\';\n      $out = array() ;\n      $globerrlevel = \'\' ;\n      $message      = gTxt( CBE_RNDC_LPFX.\'populate_end\' ) ;\n      if( ($use_textile = get_pref( \'use_textile\' )) == USE_TEXTILE ) {\n          include_once txpath.\'/lib/classTextile.php\';\n      }\n\n      $authors     = safe_column_num( \'name\', \'txp_users\', \"`privs`<6\" ) ;\n      $posauthor   = count( $authors ) - 1 ;\n\n      $sections    = safe_column_num( \'name\', \'txp_section\', \"`on_frontpage`=1 AND `name`!=\'default\'\" ) ;\n      $possection  = count( $sections ) - 1 ;\n\n      $categories  = safe_column_num( \'name\', \'txp_category\', \" `name`!=\'root\' AND `type`=\'article\'\" ) ;\n      $poscategory = count( $categories ) - 1 ;\n\n      $stati = array( STATUS_LIVE, STATUS_LIVE\n                    , STATUS_DRAFT\n                    , STATUS_LIVE, STATUS_LIVE\n                    , STATUS_HIDDEN\n                    , STATUS_LIVE, STATUS_LIVE\n                    , STATUS_PENDING\n                    , STATUS_LIVE, STATUS_LIVE\n                    ) ;\n      $posstatus = count( $stati ) - 1 ;\n\n      $rndnb =  rand( 10, 15 ) ;\n      $aAids = array() ;\n      $errlevel = \"success\" ;\n\n      for( $i = 0 ; $i < $rndnb ; $i++ ) {\n\n          $seeddate  = rand( time()-(300*24*60*60), time()+(60*24*60*60) ) ;\n          $in        = rand( 0, 9 ) ;\n          $status    = $stati[ rand( 0, $posstatus ) ] ;\n          $published = date( \"Y-m-d H:i:s\", $seeddate ) ;\n          $lastmod   = date( \"Y-m-d H:i:s\" ) ;\n          $expires   = ( in_array( $in, array( 0, 4, 8 ) ) ) ? \'null\' : \"\'\".date( \"Y-m-d H:i:s\", strtotime( \"+{$in} months\", $seeddate ) ).\"\'\";\n          $feeddate  = date( \"Y-m-d\", $seeddate ) ;\n          $author    = $authors[ rand( 0, $posauthor ) ] ;\n          $section   = $sections[ rand( 0, $possection ) ] ;\n          $category1 = $categories[ rand( 0, $poscategory ) ] ;\n          if( ($category2 = $in == 0 ? \'\' : $categories[ rand( 0, $poscategory ) ]) == $category1 ) {\n              $category2 = \'\' ;\n          }\n          $title     = substr( _cbe_rndc_sentence( 3, 5, 3, 6 ), 0, -1 ) ;\n          $url_title = stripSpace( $title, 1 ) ;\n\n          $excerpt   = _cbe_rndc_text( 6, 10, 2, 8 ) ;\n          $arrbody = array() ;\n          $parag = rand( 2, 5 ) ;\n          for( $j = 0 ; $j < $parag ; $j++ ) {\n              $arrbody[] .= _cbe_rndc_text( 6, 8, 5, 10 ) . n ;\n          }\n          $body = join( n, $arrbody ) ;\n\n          switch( $use_textile ) {\n          case USE_TEXTILE :\n              $textile = new Textile( get_pref( \'doctype\' ) ) ;\n              $body_html    = $textile -> parse( $body ) ;\n              $excerpt_html = $textile -> parse( $excerpt ) ;\n              break ;\n\n          case LEAVE_TEXT_UNTOUCHED :\n              $body_html    = trim( $body ) ;\n              $excerpt_html = trim( $excerpt ) ;\n              break ;\n\n          case CONVERT_LINEBREAKS :\n              $body_html    = nl2br( trim( $body ) ) ;\n              $excerpt_html = nl2br( trim( $excerpt ) ) ;\n              break ;\n\n          default :\n              break ;\n          }\n\n          if( $insertd = safe_insert( \"textpattern\",\n                                      \"Title           = \'$title\',\n                                       Body            = \'$body\',\n                                       Body_html       = \'$body_html\',\n                                       Excerpt         = \'$excerpt\',\n                                       Excerpt_html    = \'$excerpt_html\',\n                                       Status          = \'$status\',\n                                       Posted          = \'$published\',\n                                       Expires         = $expires,\n                                       AuthorID        = \'$author\',\n                                       LastMod         = \'$lastmod\',\n                                       LastModID       = \'$author\',\n                                       Section         = \'$section\',\n                                       Category1       = \'$category1\',\n                                       Category2       = \'$category2\',\n                                       textile_body    =  $use_textile,\n                                       textile_excerpt =  $use_textile,\n                                       Annotate        =  $comments_on_default,\n                                       url_title       = \'\".doSlash($url_title).\"\',\n                                       AnnotateInvite  = \'$comments_default_invite\',\n                                       uid             = \'\".md5(uniqid(rand(),true)).\"\',\n                                       feed_time       = \'$feeddate\'\"\n                                     ) ) {\n              $aAids[] = $insertd ;\n\n          } else {\n              $errlevel     = \"warning\" ;\n              $globerrlevel = E_ERROR   ;\n          }\n      }\n\n      $out[] = graf( tag( gTxt( CBE_RNDC_LPFX.\'populate_end\' ), \'span\', \' class=\"\'.$errlevel.\'\"\' )\n                   . \': \' . join( \", \", $aAids ) ) ;\n\n      if( ! empty( $globerrlevel ) )\n          $message .= \' \'. gTxt( CBE_RNDC_LPFX.\'with_errors\' ) ;\n\n      $back  = tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'go_back\' ), \'publish\')\n                    .n. sInput( CBE_RNDC_SPFX.\'initiate\' )\n                    .n. eInput( $event )\n                  , \'div\' ) ;\n\n      $html    = join( n, $out ) . form( $back ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * _cbe_rndc_pop_com - Admin-side: Generate comments\n *\n * See \"Rules for comments\" in the helpfile\n *\n * @return  array\n */\n  function _cbe_rndc_pop_com( &$message, &$html )\n  {\n      global $event, $use_comments, $comments_disabled_after ;\n      $next_step = NULL ;\n      $out = array() ;\n      $globerrlevel = \'\' ;\n      $aIds = safe_rows( \"ID, Title, unix_timestamp(Posted) as uPosted\", \"textpattern\"\n                       , \"`Posted`<=now() AND\n                          (`Expires`>now() OR `Expires` IS NULL) AND\n                          `Status`=\".STATUS_LIVE.\" AND\n                          `Annotate`=\'1\'\n                         ORDER BY ID\" ) ;\n\n      if( ! $use_comments ) {\n          $globerrlevel = E_ERROR   ;\n          $message      = gTxt( CBE_RNDC_LPFX.\'no_comm_allowed\' ) ;\n\n      } else {\n          $message = gTxt( CBE_RNDC_LPFX.\'populate_end\' ) ;\n          if( ($lifespan = $comments_disabled_after * 86400) > 0 ) {\n              array_walk( $aIds\n                        , create_function( \'&$v, $k, $p\', \'$v[ \"ID\" ] = (time()-$v[\"uPosted\"] < $p) ? $v[\"ID\"] : false ;\' )\n                        , $lifespan ) ;\n              $aIds = array_filter( $aIds, create_function( \'$v\', \'return( $v[ \"ID\" ] ) ;\' ) ) ;\n          }\n\n          foreach( $aIds as $article ) {\n              if( rand( 0, 99 ) == 50 )\n                  continue ;\n\n              $rndnb = rand( 3, 10 ) ;\n              $aCids = array() ;\n              $errlevel = \"success\" ;\n\n              for( $i = 0 ; $i < $rndnb ; $i++ ) {\n\n                  $comm = \'\' ;\n                  $parag = rand( 2, 5 ) ;\n                  for( $j = 0 ; $j < $parag ; $j++ ) {\n                      $comm .= \'<p>\' . _cbe_rndc_text( 6, 10, 2, 8 ) . \'</p>\' ;\n                  }\n\n                  if( $insertd = safe_insert( \"txp_discuss\"\n                                            , \"`parentid`=\'{$article[\'ID\']}\',\n                                               `name`=\'\"     . doSlash( _cbe_rndc_name( 4, 7 ).\' \'._cbe_rndc_name( 4, 9 ) ) . \"\',\n                                               `email`=\'\"    . doSlash( _cbe_rndc( false, ALPHAMINUS, 4, 7 ).\'@\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 4, 9 ).\'.\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 3 ) )\n                                                             . \"\',\n                                               `web`=\'\"      . doSlash( \'http://\'._cbe_rndc( false, ALPHAMINUS, 4, 7 ).\'.\'\n                                                                      . _cbe_rndc( false, ALPHAMINUS, 3 ) )\n                                                             . \"\',\n                                               `message`=\'\"  . doSlash( $comm ) . \"\',\n                                               `posted`=\'\"   . doSlash( date( \"Y-m-d H:i:s\", rand( $article[\"uPosted\"], time() ) ) )\n                                                             . \"\'\"\n                                            ) ) {\n                      $aCids[] = $insertd ;\n\n                  } else {\n                      $errlevel     = \"warning\" ;\n                      $globerrlevel = E_ERROR   ;\n                  }\n              }\n\n              update_comments_count( $article[ \"ID\" ] ) ;\n\n              $out[] = graf( tag( $article[\'Title\'], \'span\', \' class=\"\'.$errlevel.\'\"\' ) . \': \' . join( \", \", $aCids ) ) ;\n          }\n\n          if( ! empty( $globerrlevel ) ) {\n              $message .= \' \'. gTxt( CBE_RNDC_LPFX.\'with_errors\' ) ;\n          }\n      }\n\n      $back = tag( fInput( \'submit\', \'submit\', gTxt( CBE_RNDC_LPFX.\'go_back\' ), \'publish\')\n                    .n. sInput( CBE_RNDC_SPFX.\'initiate\' )\n                    .n. eInput( $event )\n                  , \'div\' ) ;\n\n      $html = join( n, $out ) . form( $back ) ;\n      return( $next_step ) ;\n  }\n\n/**\n * cbe_rand_content_router - Admin-side: Entry point\n *\n * @param   string $event admin event\n * @param   string $step  admin step\n * @return  void\n */\n  function cbe_rand_content_router( $event, $step )\n  {\n      global $step, $event ;\n      require_privs( $event ) ;\n\n      ${CBE_RNDC_SPFX.\'message\'} = \'\' ;\n      ${CBE_RNDC_SPFX.\'html\'}    = \'\' ;\n\n      $available_steps = array( CBE_RNDC_SPFX.\'initiate\' => false\n                              , CBE_RNDC_SPFX.\'pop_art\'  => true\n                              , CBE_RNDC_SPFX.\'pop_com\'  => true\n                              ) ;\n\n      if( ! $step ) {\n          $step = CBE_RNDC_SPFX.\'initiate\' ;\n      }\n\n      while( $step && bouncer( $step, $available_steps ) && is_callable( $func = \"_$step\" ) ) {\n          $step = $func( ${CBE_RNDC_SPFX.\'message\'}, ${CBE_RNDC_SPFX.\'html\'} ) ;\n      }\n\n      pagetop( gTxt( CBE_RNDC_LPFX.\'tab_label\' ), ${CBE_RNDC_SPFX.\'message\'} ) ;\n\n      echo ( hed( gTxt( CBE_RNDC_LPFX.\'tab_label\' ), \'1\', \' class=\"txp-heading\"\' )\n           . tag( ${CBE_RNDC_SPFX.\'html\'}, \'div\', \' class=\"text-column\"\' )\n           ) ;\n\n      return ;\n  }\n} // @txpinterface == \'admin\'\n\nelseif( defined( \'txpinterface\' ) ) {\n\n  return ;\n\n} // @txpinterface == \'public|css\'\n\nelse {\n\n  return ;\n\n} // txpinterface undef', '12fc59ca2bda0187bc17a2d4aa6e8cce', '', '', 3, 5, 2);

-- --------------------------------------------------------

--
-- Table structure for table `txp_prefs`
--

CREATE TABLE `txp_prefs` (
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `val` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `type` smallint(5) UNSIGNED NOT NULL DEFAULT '2',
  `event` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'publish',
  `html` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'text_input',
  `position` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
  `user_name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_prefs`
--

INSERT INTO `txp_prefs` (`name`, `val`, `type`, `event`, `html`, `position`, `user_name`) VALUES
('img_dir', 'images', 0, 'admin', 'text_input', 20, ''),
('skin_dir', 'themes', 0, 'admin', 'text_input', 30, ''),
('file_base_path', '/Users/shambala/Sites/site1/files', 0, 'admin', 'text_input', 40, ''),
('file_max_upload_size', '2000000', 0, 'admin', 'text_input', 60, ''),
('tempdir', '/Users/shambala/Sites/site1/textpattern/tmp', 0, 'admin', 'text_input', 80, ''),
('plugin_cache_dir', '', 0, 'admin', 'text_input', 100, ''),
('smtp_from', '', 0, 'admin', 'text_input', 110, ''),
('publisher_email', '', 0, 'admin', 'text_input', 115, ''),
('override_emailcharset', '0', 0, 'admin', 'yesnoradio', 120, ''),
('enable_xmlrpc_server', '0', 0, 'admin', 'yesnoradio', 130, ''),
('default_event', 'article', 0, 'admin', 'default_event', 150, ''),
('theme_name', 'hive', 0, 'admin', 'themename', 160, ''),
('module_pophelp', '1', 0, 'admin', 'module_pophelp', 170, ''),
('advanced_options', '1', 0, 'admin', 'onoffradio', 200, ''),
('comments_on_default', '1', 0, 'comments', 'yesnoradio', 20, ''),
('comments_default_invite', 'Comment', 0, 'comments', 'text_input', 40, ''),
('comments_moderate', '1', 0, 'comments', 'yesnoradio', 60, ''),
('comments_disabled_after', '42', 0, 'comments', 'weeks', 80, ''),
('comments_auto_append', '0', 0, 'comments', 'yesnoradio', 100, ''),
('comments_mode', '0', 0, 'comments', 'commentmode', 120, ''),
('comments_dateformat', '%b %Oe, %I:%M %p', 0, 'comments', 'dateformats', 140, ''),
('comments_sendmail', '1', 0, 'comments', 'commentsendmail', 160, ''),
('comments_are_ol', '1', 0, 'comments', 'yesnoradio', 180, ''),
('comment_means_site_updated', '1', 0, 'comments', 'yesnoradio', 200, ''),
('comments_require_name', '1', 0, 'comments', 'yesnoradio', 220, ''),
('comments_require_email', '1', 0, 'comments', 'yesnoradio', 240, ''),
('never_display_email', '1', 0, 'comments', 'yesnoradio', 260, ''),
('comment_nofollow', '1', 0, 'comments', 'yesnoradio', 280, ''),
('comments_disallow_images', '0', 0, 'comments', 'yesnoradio', 300, ''),
('comments_use_fat_textile', '0', 0, 'comments', 'yesnoradio', 320, ''),
('spam_blacklists', '', 0, 'comments', 'text_input', 340, ''),
('custom_1_set', 'custom1', 0, 'custom', 'checkbox', 1, ''),
('custom_2_set', 'custom2', 0, 'custom', 'text_input', 2, ''),
('custom_3_set', '', 0, 'custom', 'text_input', 3, ''),
('custom_4_set', '', 0, 'custom', 'text_input', 4, ''),
('custom_5_set', '', 0, 'custom', 'text_input', 5, ''),
('custom_6_set', '', 0, 'custom', 'text_input', 6, ''),
('custom_7_set', '', 0, 'custom', 'text_input', 7, ''),
('custom_8_set', '', 0, 'custom', 'text_input', 8, ''),
('custom_9_set', '', 0, 'custom', 'text_input', 9, ''),
('custom_10_set', '', 0, 'custom', 'text_input', 10, ''),
('syndicate_body_or_excerpt', '1', 0, 'feeds', 'yesnoradio', 20, ''),
('rss_how_many', '5', 0, 'feeds', 'text_input', 40, ''),
('show_comment_count_in_feed', '1', 0, 'feeds', 'yesnoradio', 60, ''),
('include_email_atom', '0', 0, 'feeds', 'yesnoradio', 80, ''),
('use_mail_on_feeds_id', '0', 0, 'feeds', 'yesnoradio', 100, ''),
('title_no_widow', '0', 0, 'publish', 'yesnoradio', 20, ''),
('articles_use_excerpts', '1', 0, 'publish', 'yesnoradio', 40, ''),
('allow_form_override', '1', 0, 'publish', 'yesnoradio', 60, ''),
('attach_titles_to_permalinks', '1', 0, 'publish', 'yesnoradio', 80, ''),
('permlink_format', '1', 0, 'publish', 'permlink_format', 100, ''),
('send_lastmod', '1', 0, 'publish', 'yesnoradio', 120, ''),
('publish_expired_articles', '0', 0, 'publish', 'yesnoradio', 130, ''),
('use_textile', '1', 0, 'publish', 'pref_text', 200, ''),
('enable_short_tags', '1', 0, 'publish', 'yesnoradio', 230, ''),
('use_plugins', '1', 0, 'publish', 'yesnoradio', 260, ''),
('admin_side_plugins', '1', 0, 'publish', 'yesnoradio', 280, ''),
('allow_page_php_scripting', '1', 0, 'publish', 'yesnoradio', 300, ''),
('allow_article_php_scripting', '1', 0, 'publish', 'yesnoradio', 320, ''),
('max_url_len', '1000', 0, 'publish', 'text_input', 340, ''),
('blog_mail_uid', '5230b3c7fafe23d031c97987cea19366blog@gmail.com', 2, 'publish', 'text_input', 0, ''),
('blog_time_uid', '2005', 2, 'publish', 'text_input', 0, ''),
('blog_uid', '6f18674c3167f80286fec71234ac6ae2', 2, 'publish', 'text_input', 0, ''),
('dbupdatetime', '0', 2, 'publish', 'text_input', 0, ''),
('language', 'en', 2, 'publish', 'text_input', 0, ''),
('lastmod', '2019-08-13 00:42:32', 2, 'publish', 'text_input', 0, ''),
('locale', 'C', 2, 'publish', 'text_input', 0, ''),
('path_from_root', '/', 2, 'publish', 'text_input', 0, ''),
('path_to_site', '/Users/shambala/Sites/site1', 2, 'publish', 'text_input', 0, ''),
('searchable_article_fields', 'Title, Body', 2, 'publish', 'text_input', 0, ''),
('textile_updated', '1', 2, 'publish', 'text_input', 0, ''),
('timeoffset', '0', 2, 'publish', 'text_input', 0, ''),
('timezone_key', 'Europe/Berlin', 2, 'publish', 'text_input', 0, ''),
('url_mode', '1', 2, 'publish', 'text_input', 0, ''),
('use_categories', '1', 2, 'publish', 'text_input', 0, ''),
('use_sections', '1', 2, 'publish', 'text_input', 0, ''),
('sql_now_posted', '2147483647', 2, 'publish', 'text_input', 0, ''),
('sql_now_expires', '2147483647', 2, 'publish', 'text_input', 0, ''),
('sql_now_created', '1563911933', 2, 'publish', 'text_input', 0, ''),
('version', '4.7.3', 2, 'publish', 'text_input', 0, ''),
('default_section', 'articles', 2, 'section', 'text_input', 0, ''),
('sitename', 'textplosion', 0, 'site', 'text_input', 20, ''),
('siteurl', 'localhost/site1', 0, 'site', 'text_input', 40, ''),
('site_slogan', 'explosion of text', 0, 'site', 'text_input', 60, ''),
('production_status', 'live', 0, 'site', 'prod_levels', 80, ''),
('gmtoffset', '+7200', 0, 'site', 'gmtoffset_select', 110, ''),
('auto_dst', '0', 0, 'site', 'yesnoradio', 115, ''),
('is_dst', '0', 0, 'site', 'is_dst', 120, ''),
('dateformat', 'since', 0, 'site', 'dateformats', 140, ''),
('archive_dateformat', '%b %Oe, %I:%M %p', 0, 'site', 'dateformats', 160, ''),
('permlink_mode', 'messy', 0, 'site', 'permlinkmodes', 180, ''),
('doctype', 'html5', 0, 'site', 'doctypes', 190, ''),
('logging', 'none', 0, 'site', 'logging', 220, ''),
('expire_logs_after', '7', 0, 'site', 'text_input', 230, ''),
('use_comments', '1', 0, 'site', 'yesnoradio', 240, ''),
('txp_evaluate_functions', '', 0, 'advanced_options', 'text_input', 100, ''),
('custom_form_types', ';[js]\r\n;mediatype=\"application/javascript\"\r\n;title=\"JavaScript\"', 0, 'advanced_options', 'longtext_input', 200, ''),
('skin_delete_from_database', '', 2, 'skin', 'text_input', 0, 'marios'),
('enable_dev_preview', '1', 0, 'admin', 'yesnoradio', 180, 'marios'),
('language_ui', 'en', 2, 'admin', 'text_input', 0, 'marios'),
('default_publish_status', '4', 0, 'publish', 'defaultPublishStatus', 15, 'marios'),
('glz_cf_migrated', '1', 2, 'glz_custom_f', 'text_input', 0, ''),
('skin_editing', 'four-point-seven', 2, 'skin', 'text_input', 0, 'marios'),
('last_update_check', '{\"when\":1564175601,\"msg\":\"\",\"msg2\":\"\"}', 2, 'publish', 'text_input', 0, ''),
('rvm_css_dir', 'css', 1, 'admin', 'text_input', 20, ''),
('cbe_swap_body_excerpt_version', '1.0.1', 2, 'cbe_swapbe', '', 0, ''),
('cbe_helpful_version', '0.6', 2, 'cbe_helpful', '', 0, ''),
('arc_meta_homepage_title', '%n | %t', 1, 'arc_meta', 'text_input', 10, ''),
('arc_meta_article_title', '%a | %n', 1, 'arc_meta', 'text_input', 20, ''),
('arc_meta_comment_title', 'comments_on %a | %n', 1, 'arc_meta', 'text_input', 30, ''),
('arc_meta_search_title', 'Search results: %q | %n', 1, 'arc_meta', 'text_input', 40, ''),
('arc_meta_category_title', '%c | %n', 1, 'arc_meta', 'text_input', 50, ''),
('arc_meta_section_title', '%s | %n', 1, 'arc_meta', 'text_input', 60, ''),
('arc_meta_section_category_title', '%c - %s | %n', 1, 'arc_meta', 'text_input', 70, ''),
('arc_meta_section_tab', 'content', 1, 'arc_meta', '_arc_meta_section_tab_select', 80, ''),
('ied_plugin_editor', 'none', 2, 'ied_plugin', 'ied_plugin_code_editors', 10, ''),
('ied_plugin_editor_path', 'http://localhost/site1/js/', 2, 'ied_plugin', 'text_input', 20, ''),
('ied_plugin_editor_options', '', 2, 'ied_plugin', 'textarea', 30, ''),
('ied_plugin_editor_width', '90%', 2, 'ied_plugin', 'text_input', 40, ''),
('ied_plugin_help_editor', 'textilee', 2, 'ied_plugin', 'ied_plugin_help_editors', 50, ''),
('ied_plugin_help_editor_path', 'http://localhost/site1/js/', 2, 'ied_plugin', 'text_input', 60, ''),
('ied_plugin_help_editor_options', 'theme_advanced_toolbar_location : \"top\",\r\n    theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,forecolor,backcolor,removeformat,numlist,bullist,outdent,indent,justifyleft,justifycenter,justifyright,justifyfull\",\r\n    theme_advanced_buttons2 : \"link,unlink,separator,ibrowser,separator,search,replace,separator,cut,copy,paste,separator,code,separator,formatselect\",\r\n    theme_advanced_buttons3 : \"\"', 2, 'ied_plugin', 'textarea', 70, ''),
('ied_plugin_interface_elems', 'style', 2, 'ied_plugin', 'checkboxset', 80, ''),
('ied_plugin_lifecycle_options', '', 2, 'ied_plugin', 'checkboxset', 90, ''),
('ied_plugin_auto_enable', '2', 2, 'ied_plugin', 'radioset', 100, ''),
('ied_plugin_syntax_check', '1', 2, 'ied_plugin', 'yesnoradio', 110, ''),
('ied_plugin_lang_choose', 'installed', 2, 'ied_plugin', 'ied_plugin_lang_options', 120, ''),
('ied_plugin_lang_default', '', 2, 'ied_plugin', 'ied_plugin_lang_default', 130, ''),
('ied_plugin_output_order', '0', 2, 'ied_plugin', 'radioset', 140, ''),
('ied_plugin_output_sfile', '{name}_v{version}.txt', 2, 'ied_plugin', 'text_input', 150, ''),
('ied_plugin_output_sfilec', '{name}_v{version}_zip.txt', 2, 'ied_plugin', 'text_input', 160, ''),
('ied_plugin_output_sfilep', '{name}_v{version}.php', 2, 'ied_plugin', 'text_input', 170, ''),
('ied_plugin_output_sfilet', '{name}_v{version}_{lang}_textpack.txt', 2, 'ied_plugin', 'text_input', 180, ''),
('ied_plugin_output_tmpcache', '/Users/shambala/Sites/site1/textpattern/tmp', 2, 'ied_plugin', 'text_input', 190, ''),
('ied_plugin_sort_column', 'name', 2, 'ied_plugin', '', 0, 'marios'),
('ied_plugin_sort_dir', 'asc', 2, 'ied_plugin', '', 0, 'marios'),
('plugin_list_pageby', '96', 2, 'plugin', 'text_input', 0, 'marios'),
('plugin_sort_column', 'name', 2, 'plugin', '', 0, 'marios'),
('plugin_sort_dir', 'asc', 2, 'plugin', '', 0, 'marios'),
('smd_at_vlast', '1563923424', 2, 'smd_at', 'text_input', 0, ''),
('smd_at_versions', 'a:0:{}', 2, 'smd_at', 'text_input', 0, ''),
('smd_thumb_backup_w', '', 2, 'smd_thumb', 'text_input', 0, ''),
('smd_thumb_backup_h', '', 2, 'smd_thumb', 'text_input', 1, ''),
('smd_thumb_backup_c', '', 2, 'smd_thumb', 'text_input', 2, ''),
('thumb_w', '60', 2, 'image', 'text_input', 0, ''),
('thumb_h', '60', 2, 'image', 'text_input', 0, ''),
('thumb_crop', '', 2, 'image', 'text_input', 0, ''),
('smd_um_current_users', 'a:1:{s:6:\"marios\";i:1563925615;}', 2, 'smd_um', '', 0, ''),
('smd_tag_p_enable', '1111', 1, 'smd_tags', 'text_input', 1, ''),
('smd_tag_p_input', '0', 1, 'smd_tags', 'text_input', 2, ''),
('smd_tag_p_lbi', '0', 1, 'smd_tags', 'text_input', 3, ''),
('smd_tag_p_linkcat', '0', 1, 'smd_tags', 'text_input', 4, ''),
('smd_tag_p_listpar', '1', 1, 'smd_tags', 'text_input', 5, ''),
('smd_tag_p_master', '', 1, 'smd_tags', 'text_input', 6, ''),
('smd_tag_p_qtag', '0', 1, 'smd_tags', 'text_input', 7, ''),
('smd_tag_p_qtpath', '', 1, 'smd_tags', 'text_input', 8, ''),
('smd_tag_p_qtstyl', '', 1, 'smd_tags', 'text_input', 9, ''),
('smd_tag_p_size', '6', 1, 'smd_tags', 'text_input', 10, ''),
('smd_tag_t_astart', '0', 1, 'smd_tags', 'text_input', 11, ''),
('smd_tag_t_auto', '1', 1, 'smd_tags', 'text_input', 12, ''),
('smd_tag_t_cols', '1', 1, 'smd_tags', 'text_input', 13, ''),
('smd_tag_t_colsord', '0', 1, 'smd_tags', 'text_input', 14, ''),
('smd_tag_t_count', '1', 1, 'smd_tags', 'text_input', 15, ''),
('smd_tag_t_deltree', '0', 1, 'smd_tags', 'text_input', 16, ''),
('smd_tag_t_delused', '0', 1, 'smd_tags', 'text_input', 17, ''),
('smd_tag_t_desc_textile', '1', 1, 'smd_tags', 'text_input', 18, ''),
('smd_tag_t_desc_tooltip', '1', 1, 'smd_tags', 'text_input', 19, ''),
('smd_tag_t_enrep', '1', 1, 'smd_tags', 'text_input', 20, ''),
('smd_tag_t_hilite', '91919a', 1, 'smd_tags', 'text_input', 21, ''),
('smd_tag_t_hover', 'aaa', 1, 'smd_tags', 'text_input', 22, ''),
('smd_tag_t_indent', '&nbsp;&nbsp;', 1, 'smd_tags', 'text_input', 23, ''),
('smd_tag_t_mdelim', ',', 1, 'smd_tags', 'text_input', 24, ''),
('smd_tag_t_size_desc', '240, 80', 1, 'smd_tags', 'text_input', 25, ''),
('smd_tag_u_combi', '1', 1, 'smd_tags', 'text_input', 26, ''),
('smd_tag_u_combi_and', '+', 1, 'smd_tags', 'text_input', 27, ''),
('smd_tag_u_combi_or', '|', 1, 'smd_tags', 'text_input', 28, ''),
('smd_tag_u_pnam', 'smd_tag', 1, 'smd_tags', 'text_input', 29, ''),
('smd_tag_u_ptyp', 'smd_tagtype', 1, 'smd_tags', 'text_input', 30, ''),
('smd_tag_u_sec', 'smd_tags', 1, 'smd_tags', 'text_input', 31, ''),
('arc_twitter_user', '', 1, 'arc_twitter', 'text_input', 0, ''),
('arc_twitter_prefix', 'Just posted:', 2, 'arc_twitter', 'text_input', 0, ''),
('arc_twitter_suffix', '', 2, 'arc_twitter', 'text_input', 0, ''),
('arc_twitter_cache_dir', '/Users/shambala/Sites/site1/textpattern/Users/shambala/Sites/site1/textpattern/tmp', 1, 'arc_twitter', 'text_input', 0, ''),
('arc_twitter_tweet_default', '1', 2, 'arc_twitter', 'yesnoRadio', 0, ''),
('arc_twitter_url_method', 'full', 2, 'arc_twitter', 'arc_twitter_url_method_select', 0, ''),
('arc_short_url', '0', 2, 'arc_twitter', 'yesnoRadio', 0, ''),
('arc_short_site_url', 'localhost/site1', 2, 'arc_twitter', 'text_input', 0, ''),
('arc_twitter_tab', 'extensions', 2, 'arc_twitter', 'arc_twitter_tab_select', 0, ''),
('arc_short_utm', '0', 2, 'arc_twitter', 'yesnoRadio', 0, ''),
('search_options_plugin', 'name, author, author_uri, description, code, help, textpack, status, type, load_order', 2, 'plugin', 'text_input', 0, 'marios'),
('rah_wrach_show_sections', '', 1, 'rah_wrach', 'text_input', 250, ''),
('rah_wrach_hide_section_input', '0', 1, 'rah_wrach', 'yesnoradio', 251, ''),
('rah_wrach_version', '0.3', 2, 'rah_wrach', '', 0, ''),
('rah_bitly_login', '', 1, 'rah_bitly', 'text_input', 250, ''),
('rah_bitly_apikey', '', 1, 'rah_bitly', 'text_input', 251, ''),
('rah_bitly_field', '', 1, 'rah_bitly', 'rah_bitly_fields', 252, ''),
('rah_bitly_version', '0.4.2', 2, 'rah_bitly', 'text_input', 0, ''),
('rah_comment_spam_nonce', '77eda0c64636768c6a7d9cb608433f04', 2, 'rah_cspam', '', 0, ''),
('rah_comment_spam_version', '0.7', 2, 'rah_cspam', '', 0, ''),
('rah_default_category_1', '', 1, 'rah_defcat', 'rah_default_category_list', 245, ''),
('rah_default_category_2', '', 1, 'rah_defcat', 'rah_default_category_list', 246, ''),
('rah_default_category_version', '0.6', 2, 'rah_defcat', '', 0, ''),
('rah_plugin_installer_version', '0.4', 2, 'rah_pins', '', 0, ''),
('rah_plugin_installer_updated', '0', 2, 'rah_pins', '', 0, ''),
('rah_plugin_installer_checksum', '', 2, 'rah_pins', '', 0, ''),
('rah_tabtor_version', '0.3.1', 2, 'rah_tabtor', '', 0, ''),
('rah_textile_bar_version', '0.7', 2, 'rah_txtbar', '', 0, ''),
('rah_unlog_me_auto', '1', 0, 'publish', 'yesnoradio', 221, ''),
('rah_unlog_me_ip', '', 0, 'publish', 'text_input', 222, ''),
('rah_unlog_me_version', '1.4.1', 2, 'rah_unlog_me', '', 0, ''),
('goe_sass_path', '/Users/shambala/Sites/site1/textpattern/vendor/phpsass', 0, 'publish', 'text_input', 0, ''),
('goe_sass_css_dir', '/css', 0, 'publish', 'text_input', 0, ''),
('goe_sass_style', 'nested', 0, 'publish', 'text_input', 0, ''),
('goe_sass_syntax', 'scss', 0, 'publish', 'text_input', 0, ''),
('spf_js_dir', 'js', 1, 'admin', 'text_input', 35, ''),
('sed_das_default_status', '4', 10, 'publish', 'sed_das_status_list', 0, ''),
('pref_name1', '', 1, 'oui_prefs', 'text_input', 10, ''),
('pref_name2', '', 1, 'oui_prefs', 'text_input', 20, ''),
('rah_pathway_field', '', 1, 'site', 'Rah_Pathway::renderFieldOption', 181, ''),
('search_options_link', 'id, name, url, description, category, author, linksort', 2, 'link', 'text_input', 0, 'marios'),
('rss_dbbk_lock', '1', 2, 'publish', 'text_input', 0, ''),
('rss_dbbk_txplog', '1', 2, 'publish', 'text_input', 0, ''),
('rss_dbbk_debug', '0', 2, 'publish', 'text_input', 0, ''),
('rss_dbbk_path', '/Users/shambala/Sites/site1/files', 2, 'publish', 'text_input', 0, ''),
('rss_dbbk_dump', 'mysqldump', 2, 'publish', 'text_input', 0, ''),
('rss_dbbk_mysql', 'mysql', 2, 'publish', 'text_input', 0, ''),
('image_sort_column', 'id', 2, 'image', '', 0, 'marios'),
('image_sort_dir', 'desc', 2, 'image', '', 0, 'marios'),
('last_form_saved', 'default', 2, 'form', 'text_input', 0, 'marios'),
('article_list_pageby', '24', 2, 'list', 'text_input', 0, 'marios'),
('article_sort_column', 'posted', 2, 'list', '', 0, 'marios'),
('article_sort_dir', 'desc', 2, 'list', '', 0, 'marios'),
('esq_autoimageresize_pref', 'a:5:{s:14:\"per_cat_resize\";s:1:\"0\";s:19:\"hide_thumb_controls\";s:1:\"0\";s:13:\"default_width\";s:0:\"\";s:14:\"default_height\";s:0:\"\";s:12:\"default_crop\";s:1:\"0\";}', 2, 'esq_air', 'serialized_array', 0, ''),
('glz_cf_values_ordering', 'custom', 1, 'glz_custom_f', 'glz_prefs_orderby', 200, ''),
('glz_cf_multiselect_size', '5', 1, 'glz_custom_f', 'glz_text_input_small', 201, ''),
('glz_cf_css_asset_url', 'http://localhost/site1/plugins/glz_custom_fields', 1, 'glz_custom_f', 'glz_url_input', 202, ''),
('glz_cf_js_asset_url', 'http://localhost/site1/plugins/glz_custom_fields', 1, 'glz_custom_f', 'glz_url_input', 203, ''),
('glz_cf_custom_scripts_path', '/Users/shambala/Sites/site1/plugins/glz_custom_fields', 1, 'glz_custom_f', 'glz_url_input', 204, ''),
('glz_cf_use_sortable', '1', 2, 'glz_custom_f', 'yesnoradio', 205, ''),
('glz_cf_permit_full_deinstall', '0', 2, 'glz_custom_f', 'yesnoradio', 206, ''),
('glz_cf_datepicker_url', 'http://localhost/site1/plugins/glz_custom_fields/jquery.datePicker', 1, 'glz_custom_f.glz_cf_datepicker', 'glz_url_input', 207, ''),
('glz_cf_datepicker_format', 'dd/mm/yyyy', 1, 'glz_custom_f.glz_cf_datepicker', 'glz_prefs_datepicker_format', 208, ''),
('glz_cf_datepicker_first_day', '1', 1, 'glz_custom_f.glz_cf_datepicker', 'glz_prefs_datepicker_firstday', 209, ''),
('glz_cf_datepicker_start_date', '01/01/2018', 1, 'glz_custom_f.glz_cf_datepicker', 'glz_input_start_date', 210, ''),
('glz_cf_timepicker_url', 'http://localhost/site1/plugins/glz_custom_fields/jquery.timePicker', 1, 'glz_custom_f.glz_cf_timepicker', 'glz_url_input', 211, ''),
('glz_cf_timepicker_start_time', '00:00', 1, 'glz_custom_f.glz_cf_timepicker', 'glz_text_input_small', 212, ''),
('glz_cf_timepicker_end_time', '23:30', 1, 'glz_custom_f.glz_cf_timepicker', 'glz_text_input_small', 213, ''),
('glz_cf_timepicker_step', '30', 1, 'glz_custom_f.glz_cf_timepicker', 'glz_text_input_small', 214, ''),
('glz_cf_timepicker_show_24', '1', 1, 'glz_custom_f.glz_cf_timepicker', 'glz_prefs_timepicker_format', 215, ''),
('bot_wtc_script', '', 2, 'bot_wtc_', 'text_input', 0, ''),
('bot_wtc_static_sections', '', 2, 'bot_wtc_', 'text_input', 0, ''),
('rah_privileges_privs', '0', 1, 'rah_privs', 'rah_privileges_input', 80, ''),
('pane_ied_plugin_composer_visible', 'pack', 2, 'ied_plugin_composer', 'text_input', 0, 'marios'),
('link_list_pageby', '96', 2, 'link', 'text_input', 0, 'marios'),
('link_sort_column', 'date', 2, 'link', '', 0, 'marios'),
('link_sort_dir', 'desc', 2, 'link', '', 0, 'marios'),
('jbx_token', '6f18674c3167f80286fec71234ac6ae25d38f0740171e4.47865270', 2, 'jbx_multiple_image_upload', 'text_input', 0, ''),
('abl_droploader.imageMaxUploadCount', '10', 2, 'plugin_prefs', 'text_input', 0, ''),
('abl_droploader.reloadImagesTab', '0', 2, 'plugin_prefs', 'yesnoradio', 1, ''),
('abl_droploader.useDefaultStylesheet', '1', 2, 'plugin_prefs', 'yesnoradio', 2, ''),
('abl_droploader.customStylesheet', '', 2, 'plugin_prefs', 'text_input', 3, ''),
('abl_droploader.articleImageFields', '#article-image', 2, 'plugin_prefs', 'text_input', 4, ''),
('oui_flat_path', '../../src/templates', 1, 'oui_flat', 'text_input', 250, ''),
('oui_flat_key', 'e947de0057011f11a4ff0db880eeaa7a', 1, 'oui_flat', 'text_input', 251, ''),
('oui_flat_status', 'debug, testing', 1, 'oui_flat', 'oui_flat_status', 252, ''),
('discuss_sort_column', 'date', 2, 'discuss', '', 0, 'marios'),
('discuss_sort_dir', 'desc', 2, 'discuss', '', 0, 'marios'),
('max_custom_fields', '10', 2, 'publish', 'text_input', 0, ''),
('last_page_saved', 'test_layout', 2, 'page', 'text_input', 0, 'marios'),
('adi_image_tab_recent', '6', 2, 'adi_image_tab', 'text_input', 0, 'marios'),
('cxc_tpl_current', '', 2, 'publish', 'text_input', 0, ''),
('pat_eu_cookies_law_countries', '\'AT\',\'BE\',\'BG\',\'HR\',\'CZ\',\'CY\',\'DK\',\'EE\',\'FI\',\'FR\',\'DE\',\'EL\',\'HU\',\'IE\',\'IT\',\'LV\',\'LT\',\'LU\',\'MT\',\'NL\',\'PL\',\'PT\',\'SK\',\'SI\',\'ES\',\'SE\',\'GB\',\'UK\'', 1, 'admin', 'text_input', 21, ''),
('pat_eu_cookies_law_js', 'http://localhost/site1/js/file.js', 1, 'admin', 'text_input', 22, ''),
('pat_eu_cookies_law_widjets', '', 1, 'admin', 'text_input', 23, ''),
('search_options_section', 'skin', 2, 'section', 'text_input', 0, 'marios'),
('skin_sort_column', 'name', 2, 'skin', '', 0, 'marios'),
('skin_sort_dir', 'desc', 2, 'skin', '', 0, 'marios'),
('cbe_rand_content_version', '0.2', 2, 'cbe_rand_content', '', 0, '');

-- --------------------------------------------------------

--
-- Table structure for table `txp_section`
--

CREATE TABLE `txp_section` (
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `skin` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'default',
  `page` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `css` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `in_rss` int(11) NOT NULL DEFAULT '1',
  `on_frontpage` int(11) NOT NULL DEFAULT '1',
  `searchable` int(11) NOT NULL DEFAULT '1',
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `dev_skin` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `dev_page` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `dev_css` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `sectionsort` tinyint(4) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_section`
--

INSERT INTO `txp_section` (`name`, `skin`, `page`, `css`, `description`, `in_rss`, `on_frontpage`, `searchable`, `title`, `dev_skin`, `dev_page`, `dev_css`, `sectionsort`) VALUES
('default', 'four-point-seven', 'default', 'default', '', 1, 1, 1, 'Default', 'four-point-seven', '', '', NULL),
('articles', 'four-point-seven', 'archive', 'default', '', 1, 1, 1, 'Articles', 'four-point-seven', '', '', NULL),
('agi', 'four-point-seven', 'default', 'default', '', 1, 1, 1, 'Artificial general intelligence', 'four-point-seven', '', '', NULL),
('layout', 'four-point-seven', 'test_layout', 'default', 'Layout', 1, 1, 1, 'layout', 'four-point-seven', '', '', NULL),
('opencog', 'four-point-seven', 'archive', 'default', 'This is a Summery Page for Opencog News', 1, 1, 1, 'The Opensource Software Opencog', 'four-point-seven', '', '', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_skin`
--

CREATE TABLE `txp_skin` (
  `name` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'default',
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Default',
  `version` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '1.0',
  `description` varchar(10240) COLLATE utf8mb4_unicode_ci DEFAULT '',
  `author` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '',
  `author_uri` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '',
  `lastmod` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_skin`
--

INSERT INTO `txp_skin` (`name`, `title`, `version`, `description`, `author`, `author_uri`, `lastmod`) VALUES
('four-point-seven', 'Four Point Seven', '4.7.3', 'The default theme shipped with Textpattern CMS 4.7.', 'Team Textpattern', 'https://github.com/textpattern/textpattern-default-theme', NULL);

-- --------------------------------------------------------

--
-- Table structure for table `txp_token`
--

CREATE TABLE `txp_token` (
  `id` int(11) NOT NULL,
  `reference_id` int(11) NOT NULL,
  `type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `selector` varchar(12) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `expires` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_token`
--

INSERT INTO `txp_token` (`id`, `reference_id`, `type`, `selector`, `token`, `expires`) VALUES
(3, 1, 'password_reset', 'dd672426c3a2', '2bbf3fd133664e41a47e31706394b84eaad422c12d9ef840ccb5f0083a64a816', '2020-05-15 15:13:44');

-- --------------------------------------------------------

--
-- Table structure for table `txp_users`
--

CREATE TABLE `txp_users` (
  `user_id` int(11) NOT NULL,
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `pass` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
  `RealName` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `email` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `privs` tinyint(4) NOT NULL DEFAULT '1',
  `last_access` datetime DEFAULT NULL,
  `nonce` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Dumping data for table `txp_users`
--

INSERT INTO `txp_users` (`user_id`, `name`, `pass`, `RealName`, `email`, `privs`, `last_access`, `nonce`) VALUES
(1, 'marios', '$P$BT1dChyRoywIK.TOzT747ga8TfjpdI1', 'mariosbuttner', 'bodobuttner@gmail.com', 1, '2020-05-15 13:44:41', 'eb9a86ef43d9d7ccae0d2a37f4aa6d15');

--
-- Indexes for dumped tables
--

--
-- Indexes for table `arc_meta`
--
ALTER TABLE `arc_meta`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `arc_redirect`
--
ALTER TABLE `arc_redirect`
  ADD PRIMARY KEY (`arc_redirectID`);

--
-- Indexes for table `arc_twitter`
--
ALTER TABLE `arc_twitter`
  ADD PRIMARY KEY (`arc_twitterid`);

--
-- Indexes for table `bot_wtc`
--
ALTER TABLE `bot_wtc`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `custom_fields`
--
ALTER TABLE `custom_fields`
  ADD PRIMARY KEY (`id`),
  ADD KEY `name` (`name`(50));

--
-- Indexes for table `kuo_file_stats`
--
ALTER TABLE `kuo_file_stats`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `kuo_sortable`
--
ALTER TABLE `kuo_sortable`
  ADD PRIMARY KEY (`id`),
  ADD KEY `username` (`username`);

--
-- Indexes for table `kuo_user_last_action`
--
ALTER TABLE `kuo_user_last_action`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `username` (`username`);

--
-- Indexes for table `kuo_zcr_smtp`
--
ALTER TABLE `kuo_zcr_smtp`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `rah_plugin_installer_def`
--
ALTER TABLE `rah_plugin_installer_def`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `rah_post_versions_prefs`
--
ALTER TABLE `rah_post_versions_prefs`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `rah_tabtor`
--
ALTER TABLE `rah_tabtor`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `smd_bio`
--
ALTER TABLE `smd_bio`
  ADD UNIQUE KEY `user_ref` (`user_ref`);

--
-- Indexes for table `smd_bio_meta`
--
ALTER TABLE `smd_bio_meta`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `name` (`name`);

--
-- Indexes for table `smd_featured`
--
ALTER TABLE `smd_featured`
  ADD PRIMARY KEY (`feat_id`);

--
-- Indexes for table `smd_macro`
--
ALTER TABLE `smd_macro`
  ADD PRIMARY KEY (`macro_name`);

--
-- Indexes for table `smd_tabber`
--
ALTER TABLE `smd_tabber`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `smd_tags`
--
ALTER TABLE `smd_tags`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `smd_tags_cat`
--
ALTER TABLE `smd_tags_cat`
  ADD PRIMARY KEY (`tag_id`,`cat_id`);

--
-- Indexes for table `smd_tags_used`
--
ALTER TABLE `smd_tags_used`
  ADD PRIMARY KEY (`item_id`,`tag_id`);

--
-- Indexes for table `smd_thumbnail`
--
ALTER TABLE `smd_thumbnail`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `smd_um_groups`
--
ALTER TABLE `smd_um_groups`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `smd_um_privs`
--
ALTER TABLE `smd_um_privs`
  ADD PRIMARY KEY (`area`,`priv`);

--
-- Indexes for table `spf_js`
--
ALTER TABLE `spf_js`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `textpattern`
--
ALTER TABLE `textpattern`
  ADD PRIMARY KEY (`ID`),
  ADD KEY `categories_idx` (`Category1`(10),`Category2`(10)),
  ADD KEY `Posted` (`Posted`),
  ADD KEY `Expires_idx` (`Expires`),
  ADD KEY `author_idx` (`AuthorID`),
  ADD KEY `section_status_idx` (`Section`(249),`Status`),
  ADD KEY `url_title_idx` (`url_title`(250));
ALTER TABLE `textpattern` ADD FULLTEXT KEY `searching` (`Title`,`Body`);

--
-- Indexes for table `txp_category`
--
ALTER TABLE `txp_category`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `txp_css`
--
ALTER TABLE `txp_css`
  ADD UNIQUE KEY `name_skin` (`name`(63),`skin`);

--
-- Indexes for table `txp_discuss`
--
ALTER TABLE `txp_discuss`
  ADD PRIMARY KEY (`discussid`),
  ADD KEY `parentid` (`parentid`);

--
-- Indexes for table `txp_discuss_nonce`
--
ALTER TABLE `txp_discuss_nonce`
  ADD PRIMARY KEY (`nonce`(250));

--
-- Indexes for table `txp_file`
--
ALTER TABLE `txp_file`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `filename` (`filename`(250)),
  ADD KEY `author_idx` (`author`);

--
-- Indexes for table `txp_form`
--
ALTER TABLE `txp_form`
  ADD UNIQUE KEY `name_skin` (`name`(63),`skin`);

--
-- Indexes for table `txp_image`
--
ALTER TABLE `txp_image`
  ADD PRIMARY KEY (`id`),
  ADD KEY `author_idx` (`author`);

--
-- Indexes for table `txp_lang`
--
ALTER TABLE `txp_lang`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `lang` (`lang`,`name`),
  ADD KEY `lang_2` (`lang`,`event`),
  ADD KEY `owner` (`owner`);

--
-- Indexes for table `txp_link`
--
ALTER TABLE `txp_link`
  ADD PRIMARY KEY (`id`),
  ADD KEY `author_idx` (`author`);

--
-- Indexes for table `txp_log`
--
ALTER TABLE `txp_log`
  ADD PRIMARY KEY (`id`),
  ADD KEY `time` (`time`);

--
-- Indexes for table `txp_page`
--
ALTER TABLE `txp_page`
  ADD UNIQUE KEY `name_skin` (`name`(63),`skin`);

--
-- Indexes for table `txp_plugin`
--
ALTER TABLE `txp_plugin`
  ADD UNIQUE KEY `name` (`name`),
  ADD KEY `status_type_idx` (`status`,`type`);

--
-- Indexes for table `txp_prefs`
--
ALTER TABLE `txp_prefs`
  ADD UNIQUE KEY `prefs_idx` (`name`(185),`user_name`),
  ADD KEY `name` (`name`(250)),
  ADD KEY `user_name` (`user_name`);

--
-- Indexes for table `txp_section`
--
ALTER TABLE `txp_section`
  ADD KEY `page_skin` (`page`(50),`skin`),
  ADD KEY `css_skin` (`css`(50),`skin`);

--
-- Indexes for table `txp_skin`
--
ALTER TABLE `txp_skin`
  ADD PRIMARY KEY (`name`);

--
-- Indexes for table `txp_token`
--
ALTER TABLE `txp_token`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `ref_type` (`reference_id`,`type`(50));

--
-- Indexes for table `txp_users`
--
ALTER TABLE `txp_users`
  ADD PRIMARY KEY (`user_id`),
  ADD UNIQUE KEY `name` (`name`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `arc_meta`
--
ALTER TABLE `arc_meta`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `arc_redirect`
--
ALTER TABLE `arc_redirect`
  MODIFY `arc_redirectID` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `arc_twitter`
--
ALTER TABLE `arc_twitter`
  MODIFY `arc_twitterid` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `bot_wtc`
--
ALTER TABLE `bot_wtc`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `custom_fields`
--
ALTER TABLE `custom_fields`
  MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

--
-- AUTO_INCREMENT for table `kuo_file_stats`
--
ALTER TABLE `kuo_file_stats`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `kuo_sortable`
--
ALTER TABLE `kuo_sortable`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `kuo_user_last_action`
--
ALTER TABLE `kuo_user_last_action`
  MODIFY `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `kuo_zcr_smtp`
--
ALTER TABLE `kuo_zcr_smtp`
  MODIFY `id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

--
-- AUTO_INCREMENT for table `rah_tabtor`
--
ALTER TABLE `rah_tabtor`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `smd_bio_meta`
--
ALTER TABLE `smd_bio_meta`
  MODIFY `id` int(4) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `smd_tags`
--
ALTER TABLE `smd_tags`
  MODIFY `id` int(6) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;

--
-- AUTO_INCREMENT for table `textpattern`
--
ALTER TABLE `textpattern`
  MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;

--
-- AUTO_INCREMENT for table `txp_category`
--
ALTER TABLE `txp_category`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15;

--
-- AUTO_INCREMENT for table `txp_discuss`
--
ALTER TABLE `txp_discuss`
  MODIFY `discussid` int(6) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

--
-- AUTO_INCREMENT for table `txp_file`
--
ALTER TABLE `txp_file`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `txp_image`
--
ALTER TABLE `txp_image`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;

--
-- AUTO_INCREMENT for table `txp_lang`
--
ALTER TABLE `txp_lang`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8844;

--
-- AUTO_INCREMENT for table `txp_link`
--
ALTER TABLE `txp_link`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=51;

--
-- AUTO_INCREMENT for table `txp_log`
--
ALTER TABLE `txp_log`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `txp_token`
--
ALTER TABLE `txp_token`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

--
-- AUTO_INCREMENT for table `txp_users`
--
ALTER TABLE `txp_users`
  MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
